[
  {
    "path": ".cirrus.yml",
    "content": "task:\n  name: freebsd-14-amd64\n  freebsd_instance:\n    image_family: freebsd-14-2\n  setup_script:\n    - pkg update\n    - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace\n    - git submodule update --init --recursive\n  test_script:\n    - ./scripts/cirrus/build_freebsd.sh\n\ntask:\n  name: freebsd-15-amd64\n  freebsd_instance:\n    image_family: freebsd-15-0-snap\n  setup_script:\n    - pkg update\n    - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace\n    - git submodule update --init --recursive\n  test_script:\n    - ./scripts/cirrus/build_freebsd.sh\n"
  },
  {
    "path": ".editorconfig",
    "content": "# this file is just a suggestion, you might follow it, you might not\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = tab\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nij_formatter_off_tag = @formatter:off\nij_formatter_on_tag = @formatter:on\n\n[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]\n\n# Visual C++ Formatting settings\ncpp_indent_braces = false\ncpp_indent_multi_line_relative_to = innermost_parenthesis\ncpp_indent_within_parentheses = indent\ncpp_indent_preserve_within_parentheses = true\ncpp_indent_case_contents = true\ncpp_indent_case_labels = false\ncpp_indent_case_contents_when_block = false\ncpp_indent_lambda_braces_when_parameter = true\ncpp_indent_goto_labels = one_left\ncpp_indent_preprocessor = leftmost_column\ncpp_indent_access_specifiers = false\ncpp_indent_namespace_contents = true\ncpp_indent_preserve_comments = false\ncpp_new_line_before_open_brace_namespace = new_line\ncpp_new_line_before_open_brace_type = new_line\ncpp_new_line_before_open_brace_function = new_line\ncpp_new_line_before_open_brace_block = new_line\ncpp_new_line_before_open_brace_lambda = new_line\ncpp_new_line_scope_braces_on_separate_lines = true\ncpp_new_line_close_brace_same_line_empty_type = false\ncpp_new_line_close_brace_same_line_empty_function = false\ncpp_new_line_before_catch = true\ncpp_new_line_before_else = true\ncpp_new_line_before_while_in_do_while = false\ncpp_space_before_function_open_parenthesis = remove\ncpp_space_within_parameter_list_parentheses = true\ncpp_space_between_empty_parameter_list_parentheses = true\ncpp_space_after_keywords_in_control_flow_statements = false\ncpp_space_within_control_flow_statement_parentheses = true\ncpp_space_before_lambda_open_parenthesis = false\ncpp_space_within_cast_parentheses = false\ncpp_space_after_cast_close_parenthesis = false\ncpp_space_within_expression_parentheses = true\ncpp_space_before_block_open_brace = true\ncpp_space_between_empty_braces = true\ncpp_space_before_initializer_list_open_brace = false\ncpp_space_within_initializer_list_braces = true\ncpp_space_preserve_in_initializer_list = true\ncpp_space_before_open_square_bracket = false\ncpp_space_within_square_brackets = false\ncpp_space_before_empty_square_brackets = false\ncpp_space_between_empty_square_brackets = false\ncpp_space_group_square_brackets = true\ncpp_space_within_lambda_brackets = false\ncpp_space_between_empty_lambda_brackets = false\ncpp_space_before_comma = false\ncpp_space_after_comma = true\ncpp_space_remove_around_member_operators = true\ncpp_space_before_inheritance_colon = true\ncpp_space_before_constructor_colon = true\ncpp_space_remove_before_semicolon = true\ncpp_space_after_semicolon = true\ncpp_space_remove_around_unary_operator = true\ncpp_space_around_binary_operator = insert\ncpp_space_around_assignment_operator = insert\ncpp_space_pointer_reference_alignment = right\ncpp_space_around_ternary_operator = insert\ncpp_use_unreal_engine_macro_formatting = true\ncpp_wrap_preserve_blocks = one_liners\n\n# IDEA settings\nij_c_add_brief_tag = false\nij_c_add_getter_prefix = true\nij_c_add_setter_prefix = true\nij_c_align_dictionary_pair_values = false\nij_c_align_group_field_declarations = false\nij_c_align_init_list_in_columns = true\nij_c_align_multiline_array_initializer_expression = false\nij_c_align_multiline_assignment = false\nij_c_align_multiline_binary_operation = false\nij_c_align_multiline_chained_methods = false\nij_c_align_multiline_for = true\nij_c_align_multiline_ternary_operation = false\nij_c_array_initializer_comma_on_next_line = false\nij_c_array_initializer_new_line_after_left_brace = false\nij_c_array_initializer_right_brace_on_new_line = false\nij_c_array_initializer_wrap = off\nij_c_assignment_wrap = off\nij_c_binary_operation_sign_on_next_line = false\nij_c_binary_operation_wrap = off\nij_c_blank_lines_after_class_header = 0\nij_c_blank_lines_after_imports = 1\nij_c_blank_lines_around_class = 1\nij_c_blank_lines_around_field = 0\nij_c_blank_lines_around_field_in_interface = 0\nij_c_blank_lines_around_method = 1\nij_c_blank_lines_around_method_in_interface = 1\nij_c_blank_lines_around_namespace = 0\nij_c_blank_lines_around_properties_in_declaration = 0\nij_c_blank_lines_around_properties_in_interface = 0\nij_c_blank_lines_before_imports = 1\nij_c_blank_lines_before_method_body = 0\nij_c_block_brace_placement = next_line\nij_c_block_brace_style = next_line\nij_c_block_comment_at_first_column = true\nij_c_catch_on_new_line = false\nij_c_class_brace_style = next_line\nij_c_class_constructor_init_list_align_multiline = true\nij_c_class_constructor_init_list_comma_on_next_line = false\nij_c_class_constructor_init_list_new_line_after_colon = never\nij_c_class_constructor_init_list_new_line_before_colon = if_long\nij_c_class_constructor_init_list_wrap = normal\nij_c_copy_is_deep = false\nij_c_create_interface_for_categories = true\nij_c_declare_generated_methods = true\nij_c_description_include_member_names = true\nij_c_discharged_short_ternary_operator = false\nij_c_do_not_add_breaks = false\nij_c_do_while_brace_force = never\nij_c_else_on_new_line = false\nij_c_enum_constants_comma_on_next_line = false\nij_c_enum_constants_wrap = off\nij_c_for_brace_force = never\nij_c_for_statement_new_line_after_left_paren = false\nij_c_for_statement_right_paren_on_new_line = false\nij_c_for_statement_wrap = off\nij_c_function_brace_placement = next_line\nij_c_function_call_arguments_align_multiline = true\nij_c_function_call_arguments_align_multiline_pars = false\nij_c_function_call_arguments_comma_on_next_line = false\nij_c_function_call_arguments_new_line_after_lpar = false\nij_c_function_call_arguments_new_line_before_rpar = false\nij_c_function_call_arguments_wrap = normal\nij_c_function_non_top_after_return_type_wrap = normal\nij_c_function_parameters_align_multiline = true\nij_c_function_parameters_align_multiline_pars = false\nij_c_function_parameters_comma_on_next_line = false\nij_c_function_parameters_new_line_after_lpar = false\nij_c_function_parameters_new_line_before_rpar = false\nij_c_function_parameters_wrap = normal\nij_c_function_top_after_return_type_wrap = normal\nij_c_generate_additional_eq_operators = true\nij_c_generate_additional_rel_operators = true\nij_c_generate_class_constructor = true\nij_c_generate_comparison_operators_use_std_tie = false\nij_c_generate_instance_variables_for_properties = ask\nij_c_generate_operators_as_members = true\nij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT}\nij_c_if_brace_force = never\nij_c_in_line_short_ternary_operator = true\nij_c_indent_block_comment = true\nij_c_indent_c_struct_members = 4\nij_c_indent_case_from_switch = true\nij_c_indent_class_members = 4\nij_c_indent_directive_as_code = false\nij_c_indent_implementation_members = 0\nij_c_indent_inside_code_block = 4\nij_c_indent_interface_members = 0\nij_c_indent_interface_members_except_ivars_block = false\nij_c_indent_namespace_members = 4\nij_c_indent_preprocessor_directive = 0\nij_c_indent_visibility_keywords = 0\nij_c_insert_override = true\nij_c_insert_virtual_with_override = false\nij_c_introduce_auto_consts = false\nij_c_introduce_auto_vars = false\nij_c_introduce_const_params = false\nij_c_introduce_const_vars = false\nij_c_introduce_constexpr_consts = false\nij_c_introduce_generate_property = false\nij_c_introduce_generate_synthesize = true\nij_c_introduce_globals_to_header = true\nij_c_introduce_prop_to_private_category = false\nij_c_introduce_static_consts = true\nij_c_introduce_use_ns_types = false\nij_c_ivars_prefix = _\nij_c_ivars_suffix =\nij_c_keep_blank_lines_before_end = 2\nij_c_keep_blank_lines_before_right_brace = 2\nij_c_keep_blank_lines_in_code = 2\nij_c_keep_blank_lines_in_declarations = 2\nij_c_keep_case_expressions_in_one_line = false\nij_c_keep_control_statement_in_one_line = false\nij_c_keep_directive_at_first_column = true\nij_c_keep_first_column_comment = false\nij_c_keep_line_breaks = true\nij_c_keep_nested_namespaces_in_one_line = false\nij_c_keep_simple_blocks_in_one_line = false\nij_c_keep_simple_methods_in_one_line = false\nij_c_keep_structures_in_one_line = false\nij_c_lambda_capture_list_align_multiline = false\nij_c_lambda_capture_list_align_multiline_bracket = false\nij_c_lambda_capture_list_comma_on_next_line = false\nij_c_lambda_capture_list_new_line_after_lbracket = false\nij_c_lambda_capture_list_new_line_before_rbracket = false\nij_c_lambda_capture_list_wrap = off\nij_c_line_comment_add_space = false\nij_c_line_comment_at_first_column = true\nij_c_method_brace_placement = end_of_line\nij_c_method_call_arguments_align_by_colons = true\nij_c_method_call_arguments_align_multiline = false\nij_c_method_call_arguments_special_dictionary_pairs_treatment = true\nij_c_method_call_arguments_wrap = off\nij_c_method_call_chain_wrap = off\nij_c_method_parameters_align_by_colons = true\nij_c_method_parameters_align_multiline = false\nij_c_method_parameters_wrap = off\nij_c_namespace_brace_placement = next_line\nij_c_parentheses_expression_new_line_after_left_paren = false\nij_c_parentheses_expression_right_paren_on_new_line = false\nij_c_place_assignment_sign_on_next_line = false\nij_c_property_nonatomic = true\nij_c_put_ivars_to_implementation = true\nij_c_refactor_compatibility_aliases_and_classes = true\nij_c_refactor_properties_and_ivars = true\nij_c_release_style = ivar\nij_c_retain_object_parameters_in_constructor = true\nij_c_semicolon_after_method_signature = false\nij_c_shift_operation_align_multiline = true\nij_c_shift_operation_wrap = normal\nij_c_show_non_virtual_functions = false\nij_c_space_after_colon = true\nij_c_space_after_colon_in_foreach = true\nij_c_space_after_colon_in_selector = false\nij_c_space_after_comma = true\nij_c_space_after_cup_in_blocks = false\nij_c_space_after_dictionary_literal_colon = true\nij_c_space_after_for_semicolon = true\nij_c_space_after_init_list_colon = true\nij_c_space_after_method_parameter_type_parentheses = false\nij_c_space_after_method_return_type_parentheses = false\nij_c_space_after_pointer_in_declaration = false\nij_c_space_after_quest = true\nij_c_space_after_reference_in_declaration = false\nij_c_space_after_reference_in_rvalue = false\nij_c_space_after_structures_rbrace = true\nij_c_space_after_superclass_colon = true\nij_c_space_after_type_cast = false\nij_c_space_after_visibility_sign_in_method_declaration = true\nij_c_space_before_autorelease_pool_lbrace = true\nij_c_space_before_catch_keyword = true\nij_c_space_before_catch_left_brace = true\nij_c_space_before_catch_parentheses = false\nij_c_space_before_category_parentheses = true\nij_c_space_before_chained_send_message = true\nij_c_space_before_class_left_brace = true\nij_c_space_before_colon = true\nij_c_space_before_colon_in_foreach = true\nij_c_space_before_comma = false\nij_c_space_before_dictionary_literal_colon = true\nij_c_space_before_do_left_brace = true\nij_c_space_before_else_keyword = true\nij_c_space_before_else_left_brace = true\nij_c_space_before_export_lbrace = true\nij_c_space_before_for_left_brace = true\nij_c_space_before_for_parentheses = false\nij_c_space_before_for_semicolon = false\nij_c_space_before_if_left_brace = true\nij_c_space_before_if_parentheses = false\nij_c_space_before_init_list = false\nij_c_space_before_init_list_colon = true\nij_c_space_before_method_call_parentheses = false\nij_c_space_before_method_left_brace = true\nij_c_space_before_method_parentheses = false\nij_c_space_before_namespace_lbrace = true\nij_c_space_before_pointer_in_declaration = true\nij_c_space_before_property_attributes_parentheses = false\nij_c_space_before_protocols_brackets = true\nij_c_space_before_quest = true\nij_c_space_before_reference_in_declaration = true\nij_c_space_before_superclass_colon = true\nij_c_space_before_switch_left_brace = true\nij_c_space_before_switch_parentheses = false\nij_c_space_before_template_call_lt = false\nij_c_space_before_template_declaration_lt = true\nij_c_space_before_try_left_brace = true\nij_c_space_before_while_keyword = true\nij_c_space_before_while_left_brace = true\nij_c_space_before_while_parentheses = false\nij_c_space_between_adjacent_brackets = false\nij_c_space_between_operator_and_punctuator = false\nij_c_space_within_empty_array_initializer_braces = true\nij_c_spaces_around_additive_operators = true\nij_c_spaces_around_assignment_operators = true\nij_c_spaces_around_bitwise_operators = true\nij_c_spaces_around_equality_operators = true\nij_c_spaces_around_lambda_arrow = true\nij_c_spaces_around_logical_operators = true\nij_c_spaces_around_multiplicative_operators = true\nij_c_spaces_around_pm_operators = false\nij_c_spaces_around_relational_operators = true\nij_c_spaces_around_shift_operators = true\nij_c_spaces_around_unary_operator = false\nij_c_spaces_within_array_initializer_braces = true\nij_c_spaces_within_braces = false\nij_c_spaces_within_brackets = false\nij_c_spaces_within_cast_parentheses = false\nij_c_spaces_within_catch_parentheses = true\nij_c_spaces_within_category_parentheses = false\nij_c_spaces_within_empty_braces = false\nij_c_spaces_within_empty_function_call_parentheses = true\nij_c_spaces_within_empty_function_declaration_parentheses = true\nij_c_spaces_within_empty_lambda_capture_list_bracket = false\nij_c_spaces_within_empty_template_call_ltgt = false\nij_c_spaces_within_empty_template_declaration_ltgt = false\nij_c_spaces_within_for_parentheses = true\nij_c_spaces_within_function_call_parentheses = true\nij_c_spaces_within_function_declaration_parentheses = true\nij_c_spaces_within_if_parentheses = true\nij_c_spaces_within_lambda_capture_list_bracket = false\nij_c_spaces_within_method_parameter_type_parentheses = false\nij_c_spaces_within_method_return_type_parentheses = false\nij_c_spaces_within_parentheses = true\nij_c_spaces_within_property_attributes_parentheses = false\nij_c_spaces_within_protocols_brackets = false\nij_c_spaces_within_send_message_brackets = false\nij_c_spaces_within_structured_binding_list_bracket = false\nij_c_spaces_within_switch_parentheses = true\nij_c_spaces_within_template_call_ltgt = false\nij_c_spaces_within_template_declaration_ltgt = false\nij_c_spaces_within_template_double_gt = false\nij_c_spaces_within_while_parentheses = true\nij_c_special_else_if_treatment = true\nij_c_structured_binding_list_align_multiline = false\nij_c_structured_binding_list_align_multiline_bracket = false\nij_c_structured_binding_list_comma_on_next_line = false\nij_c_structured_binding_list_new_line_after_lbracket = false\nij_c_structured_binding_list_new_line_before_rbracket = false\nij_c_structured_binding_list_wrap = off\nij_c_superclass_list_after_colon = never\nij_c_superclass_list_align_multiline = true\nij_c_superclass_list_before_colon = if_long\nij_c_superclass_list_comma_on_next_line = false\nij_c_superclass_list_wrap = on_every_item\nij_c_tag_prefix_of_block_comment = at\nij_c_tag_prefix_of_line_comment = back_slash\nij_c_template_call_arguments_align_multiline = false\nij_c_template_call_arguments_align_multiline_pars = false\nij_c_template_call_arguments_comma_on_next_line = false\nij_c_template_call_arguments_new_line_after_lt = false\nij_c_template_call_arguments_new_line_before_gt = false\nij_c_template_call_arguments_wrap = off\nij_c_template_declaration_function_body_indent = false\nij_c_template_declaration_function_wrap = split_into_lines\nij_c_template_declaration_struct_body_indent = false\nij_c_template_declaration_struct_wrap = split_into_lines\nij_c_template_parameters_align_multiline = false\nij_c_template_parameters_align_multiline_pars = false\nij_c_template_parameters_comma_on_next_line = false\nij_c_template_parameters_new_line_after_lt = false\nij_c_template_parameters_new_line_before_gt = false\nij_c_template_parameters_wrap = off\nij_c_ternary_operation_signs_on_next_line = false\nij_c_ternary_operation_wrap = off\nij_c_type_qualifiers_placement = before\nij_c_use_modern_casts = true\nij_c_use_setters_in_constructor = true\nij_c_while_brace_force = never\nij_c_while_on_new_line = false\nij_c_wrap_property_declaration = off\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.c\ttext eol=lf diff=cpp\n*.h\ttext eol=lf diff=cpp\nwscript text eol=lf diff=python\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/donate.md\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/crash-report.md",
    "content": "---\nname: Crash report\nabout: The renderer crashed. Let us know\ntitle: ''\nlabels: bug, crash\nassignees: ''\n\n---\n\nNote that this is only for Vulkan/Ray tracing renderer. Prior to submitting anything here make sure that the game does not crash with native GL renderer (`-ref gl`).\n\n**To Reproduce**\n1. Map name or attached save file\n2. Actions to perform (e.g. go to that room and do this; screenshots appreciated)\n\n**Artifacts**\nE.g. attach last few lines of logs (it may be an assert that's informative).\n\n**Moar context:**\n - Commit hash of the build\n - OS\n - GPU vendor and model\n - Driver version\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/visual-glitches-report.md",
    "content": "---\nname: Visual glitches report\nabout: Something doesn't look right\ntitle: ''\nlabels: bug, ray tracing, visual bug\nassignees: ''\n\n---\n\nNote that:\n- this is only for Vulkan/Ray tracing renderer. Prior to submitting anything here make sure that the game looks correct with native GL renderer (`-ref gl`).\n- the renderer is WIP so there are way too many known visual bugs. Make sure to search issues first for it is very likely that we already know about it.\n- Traditional rasterizer is not being actively maintained, so visual glitches in that won't be addressed for a while (unless they stall rt renderer progress).\n\n**To reproduce**\n1. Map name or attached save file\n2. Steps to do (e.g. go to a specific room and perform some action)\n\n**Screenshots**\n1. The thing that looks wrong\n2. How it's supposed to look, e.g.:\n  - screenshot from the same angle made using vanilla GL renderer\n  - screenshot from a production ready PBR/RT renderer of a similar scene with similar materials and lighting parameters.\n\n**Moar context**\n- Commit hash\n- OS\n- GPU vendor and model\n- Driver version\n"
  },
  {
    "path": ".github/workflows/c-cpp.yml",
    "content": "name: Build & Deploy Engine\non:\n  push:\n    paths-ignore:\n      - '**.md'\n      - 'ref/vk/data/**'\n  pull_request:\n    paths-ignore:\n      - '**.md'\n      - 'ref/vk/data/**'\njobs:\n#  cleanup:\n#    runs-on: self-hosted\n#    steps:\n#    - name: Cleanup\n#      run: rm -rf .* || true\n  build:\n    runs-on: ${{ matrix.os }}\n    continue-on-error: true\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          # FIXME Linux build specifically want oldest Ubuntu as possible\n          # to be crossdistribution compatible, otherwise use ubuntu-latest\n          - os: ubuntu-22.04\n            targetos: linux\n            targetarch: amd64\n          - os: ubuntu-22.04\n            targetos: linux\n            targetarch: i386\n          - os: ubuntu-22.04\n            targetos: linux\n            targetarch: arm64\n            cross: true\n          - os: ubuntu-22.04\n            targetos: linux\n            targetarch: armhf\n            cross: true\n# FIXME currently vulkan build fails for these, as Vulkan SDK is not easily available.\n#          - os: ubuntu-24.04 # riscv64 would benefit from having latest compilers\n#            targetos: linux\n#            targetarch: riscv64\n#            cross: true\n#          - os: ubuntu-22.04\n#            targetos: linux\n#            targetarch: ppc64el\n#            cross: true\n\n#          - os: ubuntu-aarch64-22.04\n#            targetos: linux\n#            targetarch: aarch64\n#          - os: ubuntu-latest\n#            targetos: linux\n#            targetarch: e2k-8c\n#            cross: true\n\n# FIXME Enable Vulkan for Android too\n#          - os: ubuntu-latest\n#            targetos: android\n#            targetarch: multiarch\n\n#          - os: ubuntu-22.04\n#            targetos: motomagx\n#            targetarch: armv6\n#          - os: ubuntu-20.04\n#            targetos: nswitch\n#            targetarch: arm64\n#          - os: ubuntu-20.04\n#            targetos: psvita\n#            targetarch: armv7hf\n          - os: windows-latest\n            targetos: win32\n            targetarch: amd64\n          - os: windows-2022 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes\n            targetos: win32\n            targetarch: i386\n\n# FIXME Vulkan doesn't care about these for now\n#          - os: macos-14 # arm64 as per github documentation\n#            targetos: apple\n#            targetarch: arm64\n#          - os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..)\n#            targetos: apple\n#            targetarch: amd64\n    env:\n      SDL_VERSION: 2.32.8\n      FFMPEG_VERSION: 7.1\n      VULKAN_SDK_VERSION: 1.4.321.1\n      GH_CPU_ARCH: ${{ matrix.targetarch }}\n      GH_CPU_OS: ${{ matrix.targetos }}\n      GH_CROSSCOMPILING: ${{ matrix.cross }}\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n      with:\n        submodules: recursive\n    - name: Install dependencies\n      run: bash scripts/gha/deps_${{ matrix.targetos }}.sh\n    - name: Install Vulkan SDK\n      uses: jakoch/install-vulkan-sdk-action@v1\n      with:\n        vulkan_version: ${{ env.VULKAN_SDK_VERSION }}\n        install_runtime: false\n        cache: true\n        stripdown: true\n    - name: Build engine\n      env:\n        FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }}\n      run: bash scripts/gha/build_${{ matrix.targetos }}.sh\n    - name: Upload engine (artifacts)\n      uses: actions/upload-artifact@v4\n      with:\n        name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }}\n        path: artifacts/*\n  flatpak:\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    strategy:\n      matrix:\n        include:\n          - app: su.xash.Engine.Compat.i386\n    container:\n      image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08\n      options: --privileged\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n      with:\n        submodules: recursive\n    - name: Build flatpak (Compat.i386)\n      uses: FWGS/flatpak-github-actions/flatpak-builder@v6.5\n      with:\n        bundle: ${{ matrix.app }}.flatpak\n        manifest-path: scripts/flatpak/${{ matrix.app }}.yml\n        cache: false\n  release:\n    name: \"Upload releases\"\n    runs-on: ubuntu-latest\n    needs: [build, flatpak]\n    if: ${{ github.event_name == 'push' }}\n    steps:\n    - name: Remove old release, fetch artifacts, repackage binaries and upload new release\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        RELEASE_TAG: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }}\n      run: |\n        gh release delete \"$RELEASE_TAG\" \\\n          --yes \\\n          --cleanup-tag \\\n          --repo \"$GITHUB_REPOSITORY\" || true\n        gh run download \"$GITHUB_RUN_ID\" \\\n          --dir artifacts/ \\\n          --repo \"$GITHUB_REPOSITORY\"\n        pushd artifacts/\n        echo \"Found artifacts:\"\n        ls\n        for i in $(find -mindepth 1 -maxdepth 1 -type d); do\n          mv \"$i\"/* .\n          rm -rf \"$i\"\n        done\n        echo \"Repackaged artifacts:\"\n        ls -R\n        popd\n        sleep 20s\n        gh release create \"$RELEASE_TAG\" artifacts/* \\\n          --title \"Xash3D FWGS Continuous ${{ github.ref_name }} Build\" \\\n          --target $GITHUB_SHA \\\n          --repo \"$GITHUB_REPOSITORY\" \\\n          --prerelease\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries\n*.o\n*.so\n*.a\n*.framework\n\n# Other\n*.save\nprefix/\n\n# Qt Creator for some reason creates *.user.$version files, so exclude it too\n*.user*\n*~\n\n### Xcode ###\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.xcuserstate\n\n\n### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n\n# Thumbnails\n._*\n\n# Files that might appear on external disk\n.Spotlight-V100\n.Trashes\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### CMake ###\nCMakeCache.txt\nCMakeFiles\nMakefile\ncmake_install.cmake\ninstall_manifest.txt\nCMakeLists.txt*\nCMakeScripts\nTesting\ncompile_commands.json\n_deps\n# makedepend\nMakefile.dep\n*.bak\nALL_BUILD.*\nINSTALL.*\nZERO_CHECK.*\nCMakeLists.txt\n\n# Visual Studio\n*.obj\n*.dll\n*.exp\n*.lib\n*.suo\n*.sdf\nDebug/\nRelease/\nipch/\n*.opensdf\n# Use CMake for generating projects\n*.vcxproj.filters\n*.vcxproj\n*.sln\n\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\n[Bb]in/\n[Oo]bj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Windows Azure Build Output\ncsx/\n*.build.csdef\n\n# Windows Store app package directory\nAppPackages/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n[Ss]tyle[Cc]op.*\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# PVS Studio for Linux output\n*.cl.cfg\n\n# Kate\n*.kate-swp\n*.swp\n\n# QtCreator\nbuild-*\n\n# Android\n*.apk\n*.config\n*.creator\n*.includes\n*.files\n\n# Waf\nbuild_current\n*waf-*/\n*waf3-*/\n.lock-waf*\n*.lastbuildstate\n*.unsuccessfulbuild\n__pycache__\n*.pyc\n.waf*\n\n# MSVC projects\n*.vcproj\n*.sln\n*.vcxproj\n\n# vim/cscope/coc/clangd\ncompile_commands.json\ncscope.out\ncore\n\n# Visual Studio Code\n.vscode/*\n*.code-workspace\n.history/*\n.cache/*\nenc_temp_folder/\n\n# KDevelop4\n*.kdev4\n\n# ccls langauge server\n.ccls-*\n\n# JetBrains\n.idea/\ncmake-build-*\n\n# some Android-specific build stuff\n3rdparty/SDL\n3rdparty/hlsdk-portable\n!scripts/build-ninja.py\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"mainui\"]\n\tpath = 3rdparty/mainui\n\turl = https://github.com/FWGS/mainui_cpp\n[submodule \"ref_gl/nanogl\"]\n\tpath = 3rdparty/nanogl\n\turl = https://github.com/FWGS/nanogl\n[submodule \"ref_gl/gl-wes-v2\"]\n\tpath = 3rdparty/gl-wes-v2\n\turl = https://github.com/FWGS/gl-wes-v2\n[submodule \"ref_gl/gl4es\"]\n\tpath = 3rdparty/gl4es/gl4es\n\turl = https://github.com/ptitSeb/gl4es\n[submodule \"vgui_support\"]\n\tpath = 3rdparty/vgui_support\n\turl = https://github.com/FWGS/vgui_support\n[submodule \"opus\"]\n\tpath = 3rdparty/opus/opus\n\turl = https://github.com/xiph/opus\n[submodule \"3rdparty/xash-extras\"]\n\tpath = 3rdparty/extras/xash-extras\n\turl = https://github.com/FWGS/xash-extras\n[submodule \"3rdparty/bzip2/bzip2\"]\n\tpath = 3rdparty/bzip2/bzip2\n\turl = https://gitlab.com/bzip2/bzip2\n[submodule \"3rdparty/MultiEmulator\"]\n\tpath = 3rdparty/MultiEmulator\n\turl = https://github.com/FWGS/MultiEmulator\n[submodule \"3rdparty/libogg/libogg\"]\n\tpath = 3rdparty/libogg/libogg\n\turl = https://github.com/xiph/ogg.git\n[submodule \"3rdparty/vorbis/vorbis-src\"]\n\tpath = 3rdparty/vorbis/vorbis-src\n\turl = https://github.com/xiph/vorbis.git\n[submodule \"3rdparty/opusfile/opusfile\"]\n\tpath = 3rdparty/opusfile/opusfile\n\turl = https://github.com/xiph/opusfile.git\n[submodule \"3rdparty/libbacktrace/libbacktrace\"]\n\tpath = 3rdparty/libbacktrace/libbacktrace\n\turl = https://github.com/ianlancetaylor/libbacktrace\n[submodule \"3rdparty/maintui\"]\n\tpath = 3rdparty/maintui\n\turl = https://github.com/numas13/xash3d-maintui.git\n"
  },
  {
    "path": "3rdparty/bzip2/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tconf.define('_GNU_SOURCE', 1)\n\tconf.define('BZ_NO_STDIO', 1)\n\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.define('BZ_LCCWIN32', 1)\n\telse:\n\t\tconf.define('BZ_UNIX', 1)\n\ndef build(bld):\n\tbld(features = 'subst', source = 'bzip2/bz_version.h.in', target = 'bzip2/bz_version.h', BZ_VERSION='1.1.0-fwgs', name = 'bz_version')\n\n\tbz_sources = ['bzip2/blocksort.c', 'bzip2/huffman.c', 'bzip2/crctable.c', 'bzip2/randtable.c', 'bzip2/compress.c', 'bzip2/decompress.c', 'bzip2/bzlib.c']\n\n\tbld.stlib(\n\t\tsource = bz_sources,\n\t\ttarget = 'bzip2',\n\t\tuse = 'bz_version',\n\t\tincludes = 'bzip2/',\n\t\texport_includes = 'bzip2/'\n\t)\n"
  },
  {
    "path": "3rdparty/extras/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nimport os\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('xash-extras'):\n\t\tconf.fatal('Can\\'t find xash-extras submodule.')\n\t\treturn\n\n\tconf.load('zip')\n\ndef build(bld):\n\tsrcdir = bld.path.find_dir('xash-extras')\n\n\tif bld.env.DEST_OS in ['android']:\n\t\tinstall_path = bld.env.PREFIX\n\telse:\n\t\tinstall_path = os.path.join(bld.env.SHAREDIR, bld.env.GAMEDIR)\n\n\tbld(features='zip',\n\t\tname = 'extras.pk3',\n\t\tfiles = srcdir.ant_glob('**/*'),\n\t\trelative_to = srcdir,\n\t\tinstall_path = install_path)\n"
  },
  {
    "path": "3rdparty/gl4es/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nimport os\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('gl4es') or not conf.path.find_dir('gl4es/src'):\n\t\tconf.fatal('Can\\'t find gl4es submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\ndef build(bld):\n\tgl4es_srcdir = bld.path.find_node('gl4es/src')\n\tcflags = []\n\tif bld.env.COMPILER_CC != 'msvc':\n\t\tcflags += ['-w', '-fvisibility=hidden', '-std=gnu99']\n\tbld.stlib(source   = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),\n\t\ttarget   = 'gl4es',\n\t\tincludes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],\n\t\tdefines  = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'],\n\t\tcflags   = cflags,\n\t\texport_includes = '.')\n"
  },
  {
    "path": "3rdparty/libbacktrace/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nfrom waflib import TaskGen\nfrom waflib.Tools.c_config import DEFKEYS\n\nFRAGMENT_ATOMIC='''int i;\nint main(void) {\n\t__atomic_load_n(&i, __ATOMIC_ACQUIRE);\n\t__atomic_store_n(&i, 1, __ATOMIC_RELEASE);\n\treturn 0;\n}'''\n\nFRAGMENT_SYNC='''int i;\nint main (void) {\n\t__sync_bool_compare_and_swap (&i, i, i);\n\t__sync_lock_test_and_set (&i, 1);\n\t__sync_lock_release (&i);\n\treturn 0;\n}'''\n\nFRAGMENT_GETPAGESIZE='''#include <unistd.h>\nint main(void) { return getpagesize() }'''\n\nFRAGMENT_GETEXECNAME='''#include <stdlib.h>\nint main(void) { return getexecname() != 0 }'''\n\nFRAGMENT_STRNLEN='''#include <string.h>\nint main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }'''\n\nFRAGMENT_DL_ITERATE_PHDR='''#include <%s>\nint main(void) { return dl_iterate_phdr(0, 0); }'''\n\nFRAGMENT_FCNTL='''#include <fcntl.h>\nint main(void) { return fcntl(0, 0, 0); }'''\n\nFRAGMENT_GETIPINFO='''#include \"unwind.h\"\nstruct _Unwind_Context *context;\nint ip_before_insn = 0;\nint main(void) { return _Unwind_GetIPInfo(context, &ip_before_insn); }'''\n\nFRAGMENT_LOADQUERY='''#include <sys/ldr.h>\n#include <sys/debug.h>\nint main(void) { return loadquery(0, 0, 0); }'''\n\nFRAGMENT_KERN_PROC='''#include <sys/sysctl.h>\n#if !defined(%s) || !defined(KERN_PROC_PATHNAME)\n#error\n#endif\nint main(void) { return 0; }'''\n\nFRAGMENT_LSTAT='''#include <sys/stat.h>\nstruct stat st;\nint main(int argc, char **argv) { return lstat(argv[0], &st); }'''\n\nFRAGMENT_READLINK='''#include <unistd.h>\nchar buf[100];\nint main(int argc, char **argv) { return readlink(argv[0], buf, sizeof(buf)); }'''\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\t# add unsupported platforms here\n\tif conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']:\n\t\tconf.env.DISABLE_LIBBACKTRACE = True\n\t\treturn\n\n\t# win32 has it's own dbghelp-based backtrace, that's why we ship PDBs\n\tif conf.env.COMPILER_CC == 'msvc':\n\t\tconf.env.DISABLE_LIBBACKTRACE = True\n\t\treturn\n\n\tif not conf.path.find_dir('libbacktrace') or not conf.path.find_dir('libbacktrace/config'):\n\t\tconf.fatal('Can\\'t find libbacktrace submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\n\tconf.define('BACKTRACE_ELF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32)\n\tconf.define('BACKTRACE_XCOFF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32)\n\tconf.define('_ALL_SOURCE', 1)\n\tconf.define('_GNU_SOURCE', 1)\n\tconf.define('_POSIX_PTHREAD_SEMANTICS', 1)\n\tconf.define('_TANDEM_SOURCE', 1)\n\tconf.define('__EXTENSIONS__', 1)\n\tconf.define('_DARWIN_USE_64_BIT_INODE', 1)\n\tconf.define('_LARGE_FILES', 1)\n\tconf.check_large_file(compiler='c', execute=False, mandatory=False) # sets _FILE_OFFSET_BITS\n\n\tconf.env.CFLAGS_EXTRAFLAGS = conf.filter_cflags(['-funwind-tables', '-g'], [])\n\n\tif conf.filter_cflags(['-frandom-seed=test'], []):\n\t\tconf.env.HAVE_FRANDOM_SEED = True\n\n\tdef check_header(hdr):\n\t\treturn {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr, 'compiler': 'c'}\n\n\tdef check_frag(frag, msg, define, **kw):\n\t\treturn dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'compiler': 'c'}, **kw)\n\n\tconf.multicheck(\n\t\tcheck_header('dlfcn.h'),\n\t\tcheck_header('inttypes.h'),\n\t\tcheck_header('link.h'),\n\t\tcheck_header('sys/link.h'),\n\t\tcheck_header('mach-o/dyld.h'),\n\t\tcheck_header('memory.h'),\n\t\tcheck_header('stdint.h'),\n\t\tcheck_header('stdlib.h'),\n\t\tcheck_header('strings.h'),\n\t\tcheck_header('string.h'),\n\t\tcheck_header('sys/ldr.h'),\n\t\tcheck_header('sys/mman.h'),\n\t\tcheck_header('sys/stat.h'),\n\t\tcheck_header('sys/types.h'),\n\t\tcheck_header('tlhelp32.h'),\n\t\tcheck_header('unistd.h'),\n\t\tcheck_header('windows.h'),\n\n\t\tcheck_frag(FRAGMENT_ATOMIC, '__atomic extensions', 'HAVE_ATOMIC_FUNCTIONS'),\n\t\tcheck_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'),\n\t\tcheck_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'),\n\t\tcheck_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'),\n\t\tcheck_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']),\n\t\tcheck_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['sys/link.h']),\n\t\tcheck_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'),\n\t\tcheck_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'),\n\t\tcheck_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'),\n\t\tcheck_frag(FRAGMENT_KERN_PROC % 'KERN_PROC', 'KERN_PROC and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC'),\n\t\tcheck_frag(FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC_ARGS'),\n\t\tcheck_frag(FRAGMENT_LOADQUERY, 'loadquery function', 'HAVE_LOADQUERY'),\n\t\tcheck_frag(FRAGMENT_LSTAT, 'lstat function', 'HAVE_LSTAT'),\n\t\tcheck_frag(FRAGMENT_READLINK, 'readlink function', 'HAVE_READLINK'),\n\n#\t\t{'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False},\n#\t\t{'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False},\n#\t\t{'lib':'zstd', 'define_name':'HAVE_ZSTD', 'uselib_store':'zstd', 'msg':'... zstd library', 'mandatory':False},\n\t\tmsg='Checking for in parallel'\n\t)\n\n\tconf.env[DEFKEYS].sort()\n\n\tconf.write_config_header()\n\n\tconf.define('BACKTRACE_SUPPORTED', 1)\n\tconf.define('BACKTRACE_USES_MALLOC', 0)\n\tconf.define('BACKTRACE_SUPPORTS_THREADS', 1)\n\tconf.define('BACKTRACE_SUPPORTS_DATA', conf.env.DEST_BINFMT in ['elf', 'mac-o'])\n\n\tconf.write_config_header('backtrace-supported.h')\n\n@TaskGen.feature('frandomseed')\n@TaskGen.after_method('propagate_uselib_vars')\ndef process_frandom_seed(ctx):\n\ttasks = getattr(ctx, 'compiled_tasks', [])\n\n\tfor task in tasks:\n\t\tout = task.outputs[0]\n\t\ttask.env.CFLAGS = list(task.env.CFLAGS) # need a copy\n\t\ttask.env.CFLAGS += ['-frandom-seed=%s' % out.path_from(out.ctx.bldnode)]\n\ndef build(bld):\n\tif bld.env.DISABLE_LIBBACKTRACE:\n\t\treturn\n\n\t# we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers\n\tsources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c']\n\n\tif bld.env.DEST_BINFMT == 'pe':\n\t\tsources += ['pecoff.c']\n\telif bld.env.DEST_BINFMT == 'mac-o':\n\t\tsources += ['macho.c']\n\telif bld.env.DEST_BINFMT == 'elf':\n\t\tsources += ['elf.c']\n\telse:\n\t\tsources += ['unknown.c']\n\n\ttask = bld.stlib(\n\t\tsource = ['libbacktrace/' + i for i in sources],\n\t\ttarget = 'backtrace',\n\t\tfeatures = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '',\n\t\tuse = 'EXTRAFLAGS lzma z zstd',\n\t\tincludes = '. libbacktrace/',\n\t\texport_defines = 'HAVE_LIBBACKTRACE=1',\n\t\texport_includes = 'libbacktrace/'\n\t)\n\n"
  },
  {
    "path": "3rdparty/libogg/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('libogg') or not conf.path.find_dir('libogg/src'):\n\t\tconf.fatal('Can\\'t find libogg submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\n\tconf.env.INCLUDE_INTTYPES_H = 0\n\tconf.env.INCLUDE_SYS_TYPES_H = 0\n\tconf.env.INCLUDE_STDINT_H = 0\n\n\tif conf.check_cc(header_name='inttypes.h', mandatory = False):\n\t\tconf.env.INCLUDE_INTTYPES_H = 1\n\telif conf.check_cc(header_name='sys/types.h', mandatory = False):\n\t\tconf.env.INCLUDE_SYS_TYPES_H = 1\n\telif conf.check_cc(header_name='stdint.h', mandatory = False):\n\t\tconf.env.INCLUDE_STDINT_H = 1\n\ndef build(bld):\n\tsources = bld.path.ant_glob([\n\t\t'libogg/src/*.c'\n\t])\n\n\tbld(\n\t\tfeatures = 'subst',\n\t\tname = 'libogg_config_types',\n\t\tsource = 'libogg/include/ogg/config_types.h.in',\n\t\ttarget = 'libogg/include/ogg/config_types.h',\n\t\tINCLUDE_INTTYPES_H = bld.env.INCLUDE_INTTYPES_H,\n\t\tINCLUDE_SYS_TYPES_H = bld.env.INCLUDE_SYS_TYPES_H,\n\t\tINCLUDE_STDINT_H = bld.env.INCLUDE_STDINT_H,\n\t\tSIZE16 = 'int16_t',\n\t\tUSIZE16 = 'uint16_t',\n\t\tSIZE32 = 'int32_t',\n\t\tUSIZE32 = 'uint32_t',\n\t\tSIZE64 = 'int64_t',\n\t\tUSIZE64 = 'uint64_t'\n\t)\n\n\tbld.stlib(\n\t\tsource = sources,\n\t\ttarget = 'ogg',\n\t\tuse = 'libogg_config_types',\n\t\tincludes = 'libogg/include/',\n\t\texport_includes = 'libogg/include/'\n\t)\n"
  },
  {
    "path": "3rdparty/opus/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nimport os\n\nFRAGMENT_VLA='''int main (int argc, char **argv) {\n\tchar a[argc];\n\ta[sizeof( a ) - 1] = 0;\n\tint N;\n\treturn a[0];\n}'''\n\nFRAGMENT_ALLOCA_H='''#include <alloca.h>\nint main (void) {\n\tint foo=10;\n\tint * array = alloca(foo);\n}'''\n\nFRAGMENT_STDLIB_H='''#include <malloc.h>\n#include <stdlib.h>\nint main (void) {\n\tint foo=10;\n\tint * array = alloca(foo);\n}'''\n\nFRAGMENT_LRINT='''#include <math.h>\n#include <stdlib.h>\nint main (int argc, char **argv) {\n\treturn lrint%s((%s)atof(argv[1]));\n}'''\n\n# assuming MSVC always enables NEON\nFRAGMENT_NEON='''#if !defined __ARM_NEON__ && !defined _MSC_VER\n#error\n#endif'''\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('opus') or not conf.path.find_dir('opus/src'):\n\t\tconf.fatal('Can\\'t find opus submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\n\tif conf.check_cc(fragment=FRAGMENT_LRINT % ('', 'double'), msg = 'Checking for C99 lrint', use = 'M', mandatory = False):\n\t\tconf.define('HAVE_LRINT', 1)\n\n\tif conf.check_cc(fragment=FRAGMENT_LRINT % ('f', 'float'), msg = 'Checking for C99 lrintf', use = 'M', mandatory = False):\n\t\tconf.define('HAVE_LRINTF', 1)\n\n\t# Check for C99 variable-size arrays, or alloca() as fallback\n\tif conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False):\n\t\tconf.define('VAR_ARRAYS', 1)\n\telif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False):\n\t\tconf.define('USE_ALLOCA', 1)\n\t\tconf.define('HAVE_ALLOCA_H', 1)\n\telif conf.check_cc(fragment=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False):\n\t\tconf.define('USE_ALLOCA', 1)\n\n\tif conf.env.DEST_CPU in ['thumb', 'arm']:\n\t\tif conf.check(header_name='arm_neon.h', mandatory = False):\n\t\t\tif conf.check(fragment=FRAGMENT_NEON, msg = 'Checking if compiler enabled NEON', mandatory = False):\n\t\t\t\tconf.env.HAVE_NEON = True\n\t\t\t\tconf.define('OPUS_ARM_MAY_HAVE_NEON_INTR', 1)\n\t\t\t\tconf.define('OPUS_ARM_PRESUME_NEON', 1)\n\telif conf.env.DEST_CPU.startswith('x86'):\n\t\tif conf.check(header_name='xmmintrin.h', mandatory = False):\n\t\t\tif conf.env.COMPILER_CC != 'msvc':\n\t\t\t\tconf.env.CFLAGS += ['-msse']\n\t\t\tconf.define('OPUS_X86_MAY_HAVE_SSE', 1)\n\t\t\tif conf.env.DEST_SIZEOF_VOID_P > 4:\n\t\t\t\tconf.define('OPUS_X86_PRESUME_SSE', 1)\n\n\t\tif conf.check(header_name='emmintrin.h', mandatory = False):\n\t\t\tif conf.env.COMPILER_CC != 'msvc':\n\t\t\t\tconf.env.CFLAGS += ['-msse2']\n\t\t\tconf.define('OPUS_X86_MAY_HAVE_SSE2', 1)\n\t\t\tif conf.env.DEST_SIZEOF_VOID_P > 4:\n\t\t\t\tconf.define('OPUS_X86_PRESUME_SSE2', 1)\n\n\t\t# on x86_64 we enable both SSE and SSE2, so RTCD can be disabled (it's actually doesn't even build with RTCD enabled)\n\t\tif conf.env.DEST_SIZEOF_VOID_P == 4:\n\t\t\tif conf.check(header_name='cpuid.h', mandatory=False):\n\t\t\t\tconf.define('CPU_INFO_BY_C', 1)\n\t\t\telse:\n\t\t\t\tconf.define('CPU_INFO_BY_ASM', 1)\n\n\t\t\tconf.define('OPUS_HAVE_RTCD', 1)\n\n#\t\tif conf.check(header_name='smmintrin.h', mandatory = False):\n#\t\t\tif conf.env.COMPILER_CC != 'msvc':\n#\t\t\t\tconf.env.CFLAGS += ['-msse4.1']\n#\t\t\tconf.define('OPUS_X86_MAY_HAVE_SSE4_1', 1)\n\n#\t\tif conf.check(header_name='immintrin.h', mandatory = False):\n#\t\t\tif conf.env.COMPILER_CC != 'msvc':\n#\t\t\t\tconf.env.CFLAGS += ['-mavx']\n#\t\t\tconf.define('OPUS_X86_MAY_HAVE_AVX', 1)\n\n\t# TODO: ARM/x86 intrinsics detection\n\t# TODO: maybe call autotools/cmake/meson instead?\n\ndef build(bld):\n\tsources = bld.path.ant_glob([\n\t\t'opus/src/*.c',\n\t\t'opus/celt/*.c',\n\t\t'opus/silk/*.c',\n\t\t'opus/silk/float/*.c'\n\t], excl = [\n\t\t'opus/src/repacketizer_demo.c',\n\t\t'opus/src/opus_demo.c',\n\t\t'opus/src/opus_compare.c',\n\t\t'opus/celt/opus_custom_demo.c'\n\t])\n\n\tincludes = ['opus', 'opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/']\n\n\tif bld.env.DEST_CPU in ['thumb', 'arm']:\n\t\tif bld.env.HAVE_NEON:\n\t\t\tsources += bld.path.ant_glob(['opus/silk/arm/*.c', 'opus/celt/arm/*.c'])\n\t\t\tincludes += ['opus/silk/arm', 'opus/celt/arm']\n\telif bld.env.DEST_CPU.startswith('x86'):\n\t\tsources += ['opus/silk/x86/x86_silk_map.c',\n\t\t\t'opus/celt/x86/pitch_sse.c',\n\t\t\t'opus/celt/x86/pitch_sse2.c',\n\t\t\t'opus/celt/x86/vq_sse2.c',\n\t\t\t'opus/celt/x86/x86_celt_map.c',\n\t\t\t'opus/celt/x86/x86cpu.c']\n\t\tincludes += ['opus/silk/x86', 'opus/celt/x86']\n\n\tdefines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION=\"1.4.0\"', 'CUSTOM_MODES', 'ENABLE_HARDENING']\n\n\tbld.stlib(\n\t\tsource = sources,\n\t\ttarget = 'opus',\n\t\tfeatures = 'c',\n\t\tincludes = includes,\n\t\tdefines = defines,\n\t\texport_includes = ['opus/include/']\n\t)\n"
  },
  {
    "path": "3rdparty/opusfile/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('opusfile') or not conf.path.find_dir('opusfile/src'):\n\t\tconf.fatal('Can\\'t find opusfile submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\n\tif conf.env.COMPILER_CC == 'msvc':\n\t\tconf.define('_CRT_SECURE_NO_WARNINGS', 1)\n\t\tconf.define('_CRT_SECURE_NO_DEPRECATE', 1)\n\t\tconf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)\n\n\tif conf.env.DEST_OS == 'android':\n\t\t# HACKHACK: set it to 32 here because opusfile can't be built on Android SDK < 24\n\t\t# with _FILE_OFFSET_BITS 64 (which it sets automatically in src/internal.h)\n\t\t# we are not (????) relying on this part of the API, so it should be harmless\n\t\tconf.define('_FILE_OFFSET_BITS', 32)\n\ndef build(bld):\n\tsources = [\n\t\t'opusfile/src/info.c',\n\t\t'opusfile/src/internal.c',\n\t\t'opusfile/src/opusfile.c',\n\t\t'opusfile/src/stream.c'\n\t]\n\n\tbld.stlib(\n\t\tsource = sources,\n\t\ttarget = 'opusfile',\n\t\tincludes = 'opusfile/include/',\n\t\tuse = 'ogg opus',\n\t\texport_includes = 'opusfile/include/'\n\t)\n"
  },
  {
    "path": "3rdparty/vorbis/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nFRAGMENT_MEMORY_H='''#include <memory.h>\nint main (void) {\n\treturn 0;\n}'''\n\nFRAGMENT_ALLOCA_H='''#include <alloca.h>\nint main (void) {\n\tint foo=10;\n\tint * array = alloca(foo);\n}'''\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif not conf.path.find_dir('vorbis-src') or not conf.path.find_dir('vorbis-src/lib'):\n\t\tconf.fatal('Can\\'t find Vorbis submodule. Run `git submodule update --init --recursive`.')\n\t\treturn\n\n\tif conf.check_cc(fragment=FRAGMENT_MEMORY_H, msg = 'Checking for memory.h header', mandatory = False):\n\t\tconf.define('USE_MEMORY_H', 1)\n\n\tconf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False)\n\n\tif conf.env.COMPILER_CC == 'msvc':\n\t\tconf.define('_CRT_SECURE_NO_WARNINGS', 1)\n\t\tconf.define('_CRT_SECURE_NO_DEPRECATE', 1)\n\t\tconf.define('_CRT_NONSTDC_NO_DEPRECATE', 1)\n\ndef build(bld):\n\tlibvorbis_sources = [\n\t\t'vorbis-src/lib/mdct.c',\n\t\t'vorbis-src/lib/smallft.c',\n\t\t'vorbis-src/lib/block.c',\n\t\t'vorbis-src/lib/envelope.c',\n\t\t'vorbis-src/lib/window.c',\n\t\t'vorbis-src/lib/lsp.c',\n\t\t'vorbis-src/lib/lpc.c',\n\t\t'vorbis-src/lib/analysis.c',\n\t\t'vorbis-src/lib/synthesis.c',\n\t\t'vorbis-src/lib/psy.c',\n\t\t'vorbis-src/lib/info.c',\n\t\t'vorbis-src/lib/floor1.c',\n\t\t'vorbis-src/lib/floor0.c',\n\t\t'vorbis-src/lib/res0.c',\n\t\t'vorbis-src/lib/mapping0.c',\n\t\t'vorbis-src/lib/registry.c',\n\t\t'vorbis-src/lib/codebook.c',\n\t\t'vorbis-src/lib/sharedbook.c',\n\t\t'vorbis-src/lib/lookup.c',\n\t\t'vorbis-src/lib/bitrate.c'\n\t]\n\n\tbld.stlib(\n\t\tsource = libvorbis_sources,\n\t\ttarget = 'vorbis',\n\t\tincludes = 'vorbis-src/include/',\n\t\tuse = 'ogg',\n\t\texport_includes = 'vorbis-src/include/'\n\t)\n\n\tbld.stlib(\n\t\tsource = 'vorbis-src/lib/vorbisfile.c',\n\t\ttarget = 'vorbisfile',\n\t\tincludes = 'vorbis-src/include/',\n\t\tuse = 'vorbis',\n\t\texport_includes = 'vorbis-src/include/'\n\t)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Specific instructions for this fork\n\n## Introduction\n1. This fork's only concern is the `ref_vk` Vulkan/RT renderer. Engine and other renderers issues and functionality are absolutely out of scope, unless directly and specifically related to `ref_vk`.\n2. Primary focus is Ray Tracing with PBR materials. \"Traditional\" (triangle rasterization) mode is low proirity and has quite a few known issues and deficiencies.\n3. The primary development branch is `vulkan`, it should contain the latest working and stable code. Other branches (including `master`) are not supported.\n4. Check out the upstream xash3d-fwgs CONTRIBUTING.md too.\n\n## Reporting issues\n1. Precondition: you're supposed to know how to build and run stuff manually. It is not ready to be used by non-developers.\n2. This is a very actively developing project. There are lots of known issues. Search them first.\n3. Run with `-dev 2 -log -vkdebug -vkvalidate -vkverboselogs` and provide `engine.log`.\n4. Specify detailed steps to reproduce. A savefile might be helpful too.\n5. Although it is deducible from the log, provide the following information explicitly: map, location, OS, GPU, driver version.\n6. Attach a screenshot if possible (i.e. if it is not a crash at init time)\n\n## Contributing code\nWe are very glad to hear that you want to help. And there are certainly quite a few issues that could be worked on in parallel.\nThe renderer code is being mostly written as a for-fun-only hobby project by a single person who has neither mental capacity nor time to make and maintain a comprehensive documentation or development structure.\nMaking it a collaboration effort with multiple active participants would require a completely different approach to development, communication, and progress tracking. We might or might not be able to get there eventually.\n\nThat said, we are still happy to hear that you'd like to help. Your involvement might be instrumental to reorganize and allow more collaborators.\n\nStrongly suggested checklist for contributing anything, **before you start writing any code that you'd like to land here**:\n1. Find an existing issue (e.g. with `good first issue` label), or suggest your own.\n2. Let us know that you'd want to work on it, e.g. by leaving a comment on it. **Why:** any given issue might be stale, no longer relevant, being actively worked on as part of something else, or conflicting with some other approach being deliberated.\n3. Work with us on a design review for the issue. **Why:** this is live C codebase, it is rather fragile and is constantly changing. There are no good practices or stable building blocks. It is also a bit idiosyncratic in places. We just know more context about where are we going to, and where we might be heading. You might also get into surprising conflicts with things that we're working on. There are unfortunately no stable scaffolding or guardrails that would allow for easy independent collaboration yet. Working on a design review means that we'll suggest a compatible way of doing things, and will schedule our work to minimize conflicts with yours.\n4. Open a draft PR as early as possible, even if it is not ready yet. That way we can coordinate effort, suggest things and anwer any questions.\n\n## Code and PR\n1. Do not worry that much about code style. Be reasonable, try to either imitate the surrounding code (which has no strict style yet), or follow upstream recommendations listed below under `Code style` section.\n2. Try to limit your changes, e.g. don't re-format lines which are not crucial to your change.\n3. Ping us in the PR if you're not hearing any feedback for a couple of days. I'm usually way too busy *with life* to be on the internets all the time, but a little nudge might be able to allocate some attention.\n\n\n---------------------------------------------\n# UPSTREAM XASH3D-FWGS CONTRIBUTING.md FOLLOWS\n---------------------------------------------\n\n## If you are reporting bugs\n\n1. Check you are using latest version. You can build latest Xash3D FWGS for yourself, look to README.md.\n2. Check open issues is your bug is already reported and closed issues if it reported and fixed. Don't send bug if it's already reported.\n3. Re-run engine with `-dev 2 -log` arguments, reproduce bug and post engine.log which can be found in your working directory.\n3. Describe steps to reproduce bug.\n4. Describe which OS and architecture you are using.\n6. Attach screenshot if it will help clarify the situation.\n\n## If you are contributing code\n\n### Which branch?\n\n* We recommend using `master` branch.\n\n### Third-party libraries\n\n* Philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too.\n* Adding new library is allowed only if there is a REAL reason to use it. It's will be nice, if you will leave a possibility to remove new dependency at build-time.\n* Adding new dependencies for Waf Build System is not welcomed.\n\n### Portability level\n\n* Xash3D have it's own crt library. It's recommended to use it. It most cases it's just a wrappers around standart C library.\n* If your feature need platform-specific code, move it to `engine/platform` and try to implement to every supported OS and every supported compiler or at least leave a stubs.\n* You must put it under appopriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to public/build.h file.\n\n### Code style\n\n* This project uses mixed Quake's and HLSDK's C/C++ code style convention. \n* In short:\n  * Use spaces in parenthesis.\n  * Only tabs for indentation.\n  * Any brace must have it's own line.\n  * Short blocks, if statements and loops on single line are allowed.\n  * Avoid magic numbers.\n  * While macros are powerful, it's better to avoid overusing them.\n  * If you unsure, try to mimic code style from anywhere else of engine source code.\n* **ANY** commit message should start from declaring a tags, in format:\n  \n  `tag: added some bugs`\n  \n  `tag: subtag: fixed some features`\n  \n  Tags can be any: subsystem, simple feature name or even just a filename, without extension.\n  Just keep them always same, it helps keep history clean and commit messages short.\n\n## LLM-based tools usage.\n\nWhile we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay.\n\nWhether you're reporting bug or contributing the code, you take complete authorship and responsibility over provided content and the same rules will apply to you as for everybody else, so validate the bug report or the patch before sending it.\n"
  },
  {
    "path": "Documentation/bug-compatibility.md",
    "content": "# Bug-compatibility in Xash3D FWGS\n\nXash3D FWGS has special mode for games that rely on original engine bugs. In this mode, we emulate the behaviour of selected functions that may help running mods relying on engine bugs, but enabling them by default may break majority of other games.\n\nAt this time, we only have implemented GoldSrc bug-compatibility. It can be enabled with `-bugcomp` command line switch.\n\nWhen `-bugcomp` is specified without argument, it enables everything. This behavior might be changed or removed in future versions.\n\nWhen `-bugcomp` is specified with argument, it interpreted as flags separated with `+`. This way it's possible to combine multiple levels of bug-compatibility.\n\n## GoldSrc bug-compatibility\n\n| Flag    | Description | Games that require this flag |\n| ------- | ----------- | ---------------------------- |\n| `peoei` | Reverts `pfnPEntityOfEntIndex` behavior to GoldSrc, where it returns NULL for last player due to incorrect player index comparison | * Counter-Strike: Condition Zero - Deleted Scenes |\n| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol.<br>Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods |\n| `sp_attn_none` | Makes sounds with attenuation zero spatialized, i.e. have a stereo effect. | Possibly, every game that was made for GoldSrc. |\n| `get_game_dir_full` | Makes server return full path in server's `pfnGetGameDir` API function | Mods targetting engine before HL 1.1.1.1, according to MetaMod [documentation](http://metamod.org/engine_notes.html#GetGameDir) |\n"
  },
  {
    "path": "Documentation/cross-compiling-for-windows-with-wine.md",
    "content": "# Cross-compiling for Windows with Wine\n\nThis can be useful to test engine in Wine without using virtual machines or dual-booting to Windows.\n\n0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part)\n1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain\n2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot`\n3. Run `PKGCONFIG=/bin/false ./waf configure -T <build-type> --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual.\n4. .. other typical steps to build from console ...\n\n> [!NOTE]\n> Notice the usage of PKGCONFIG=/bin/false here. We're disabling pkg-config so we don't accidentally pull\n> system-wide dependencies and force building them from source. In future builds we might set custom\n> directory to pull dependencies from, like ffmpeg...\n"
  },
  {
    "path": "Documentation/debugging-using-minidumps.md",
    "content": "# Debugging your mod using minidump files (Windows only)\nMinidump files is awesome instrument for debugging your mod after it's being released, or for catch specific crashes which are presented only in one specific configuration, but doesn't happens in other configurations or even on developer machine. It contains a lot of information useful for debugging, and therefore it size is not so small: around hundreds or even thousands of megabytes. But this is not a problem, since minidump files compresses very effectively using common algorithms.\nThere are short algorithm, explaining how to use this debugging instrument:\n\n1. User starts your mod with `-minidumps` startup parameter\n2. Finally, crash happened on user's machine, and minidump file being written. This file has `.mdmp` extension and located in same folder, where mod folder located\n3. User packs this minidump file to .zip/.7z archive, and somehow sends it to developer\n4. Developer just opens minidump file in Visual Studio, then a virtual debugging session is opened and now reasons of crash is pretty easy to detect: you can see call stack, local function variables and global variables, information about exception and a lot of other information\n\nYou can find more information about minidumps in this [awesome article](https://learn.microsoft.com/en-us/windows/win32/dxtecharts/crash-dump-analysis?source=recommendations#writing-a-minidump).\n"
  },
  {
    "path": "Documentation/donate.md",
    "content": "# Developers donation page\n\nOn this page you can find links where you can support each developer individually, who has provided public sponsorship information.\n\n## [a1batross](https://github.com/a1batross)\n\nInitial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of non-commercial Flying With Gauss organization.\n\n* [Boosty page](https://boosty.to/a1ba)\n\n## [nekonomicon](https://github.com/nekonomicon)\n\n[hlsdk-portable](https://github.com/FWGS/hlsdk-portable), [mdldec](../utils/mdldec), [opensource-mods.md](opensource-mods.md) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=nekonomicon) (*BSD/clang port, PNG support, etc).\n\n* [Boosty page](https://boosty.to/nekonomicon)\n\n## [Velaron](https://github.com/Velaron)\n\n[cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc).\n\n* [Boosty page](https://boosty.to/velaron)\n\n## [SNMetamorph](https://github.com/SNMetamorph)\n\n[PrimeXT](https://github.com/SNMetamorph/PrimeXT) & [GoldSrc Monitor](https://github.com/SNMetamorph/goldsrc-monitor) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=SNMetamorph) (Windows port, voice chat, etc).\n\n* [Boosty page](https://boosty.to/snmetamorph)\n* [Other donation methods](https://snmetamorph.github.io/donate)\n\n## [$_Vladislav](https://github.com/Vladislav4KZ)\n\n[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester,\neditor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).\n\nAlso does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.\n\n* [Boosty page](https://boosty.to/rasstaman1337)"
  },
  {
    "path": "Documentation/engine-porting-guide.md",
    "content": "## Abstract\n\nBefore start, I would recommend you to compile and run engine for already supported and well-spread platform, such as GNU/Linux or Windows. Hack it, get familiar with engine.\n\nOne beauties of Xash3D FWGS Engine is it modularity, so game logic, UI and renderers are located in platform-independent libraries. Every library is loaded at run time and may or may be not optional.  For example, if your platform doesn't have OpenGL of any kind, you can skip it and use our software renderer! Of course, there is no real reason to run game engine, if you don't have game logic of any kind, so the minimal build of Xash3D FWGS is headless dedicated server.\n\nHistorically, Xash3D Engine was even more modular(see github.com/a1batross/Xash3D_ancient), but we thought that to fulfill our crossplatform needs we will keep all platform-specific stuff in the engine itself and libraries must import or expose platform-independent APIs.\n\nOk, get to the point!\n\n## Porting guidelines\n\nIt will not be a complete tutorial as covering everything in one article is probably just impossible. Instead, I will give you hints on how engine can be ported, how to get your port to upstreamed and how you should maintain your port.\n\n0) Get to know your platform. Maybe I asked for this lately, but you MUST KNOW YOUR PLATFORM, i.e. what's this capable of.\n\nThe one of unsupported configurations at this time is when platform can't load dynamic libraries(`*.so` or DLLs). We can't help you as supporting full-static ports are violating the GPL license in various ways.\n\nThe other yet unsupported configuration is the big endian.\n\n1) Setup toolchain. For dirty port, you can write Makefile for yourself, engine is written that way, so **it doesn't relies** on any generated data or any special processing. But I recommend you to use Waf anyway, it's better integrated to engine and self-documentable. Also, I will cover only our Waf build options.\n\n2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard.\n\nAlso, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).\n\nWe have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information.\n\nIn short, you need to call your platform in unique way. For engine side it's done in `engine/common/build.c` file in engine source code, for HLSDK side it's done in `cmake/LibraryNaming.cmake` and `scripts/waifulib/library-naming.py` files in HLSDK source code.\n\nYou can use [predef project wiki](https://sourceforge.net/p/predef/wiki/Home/) for reference.\n\n2) Look at the `engine/platform` directory. We usually try to have all platform-specific stuff inside this folder.\n\n* Functions that must be available on your platform are declared in `platform.h` header. Most of them aren't used in headless dedicated build, but I will cover that later.\n\n* The POSIX-compliant(for *nix operating systems) goes into `posix` subdirectory.\n\n* The SDL-specific code goes into `sdl` subdirectory. Note that even I said before that platform-specific code is in `platform` directory, SDL obviously isn't a __platform__, so you can met checks for XASH_SDL inside whole engine, usually in input.\n\n* Custom SWAP implementation for *nix-based systems is in `swap` subdirectory. It's used when hardware may not have enough memory and you can't add swap memory. I will cover that later\nIt relies on a fact that systems with paged memory will load pages into RAM and move unused to mmap()-ed by custom swap area.\nThat allowed to run game engine on music player with MIPS CPU, Linux without SWAP support and\n\n* Other folders as `win32`, `android`, `linux` and so on are self-descriptive.\n\n3) As proof of concept, you can try to test that network features and dynamic library loading are implemented correctly. Build a dedicated server with XASH_DEDICATED or `--dedicated` passed  to `waf configure`.\n\nStart a server and try to connect to it from PC. If everything is fine, then you can move to next step.\n\nIf you get out of memory issues, you can try Low Memory Mode, it's enabled with `--low-memory-mode=N` passed to `waf configure` where N is 1 or 2. Low Memory Mode 1 means that we will NOT break a network compatibility. 2 is deeper and doesn't guarantee protocol compatibility and available multiplayer. Of course, with Low Memory Mode 2 you can't test your port dedicated server.\n\nIf you got anything compiling, it's nice time to make a commit. Commit your changes into git and push them somewhere(GitHub, GitLab or what you prefer), so you will not lose them accidentally.\n\n4) Here is most interesting, building the client part.\n\nThere are three possible situations for you, by increasing difficulty.\n\n1. Do you have SDL2 for your platform? If yes, go ahead and compile engine with client part enabled.\n\n2. Do you have SDL1.2 for your platform? If yes, try to compile it, but SDL1.2 support is very limited and not tested well.\n\n3. You don't have SDL and you can't port it for some reason by yourself or SDL port just don't stable for your platform? Then you need to implement an engine backend.\n\nHow to implement engine backend? Well, we have a backends system that was introduced in Old Engine. It's simply two files: backends.h and defaults.h in `common` folder in repository root. You will need to add a macros for your backends in `backends.h` and define a logic in `defaults.h`. We have already have some, check them out, as they may be handy for you.\n\nOnce you did it, go back to `platform` directory and open `platform.h` headers. Create new subdirectory in `platform` folder and start implementing missing platform-specific backends one by one. I can't explain this as for obvious reasons it's too specific. You can use macros you defined before in `build.h` to check that you're compiling for your platform and `backends.h` macros to check if platform-specific backend must be enabled.\n\nAs you finished, compile engine, fix errors and commit your changes and push to repository.\n\nRemember, if something doesn't work and you can't figure out why, you can join our Discord server at discord.me/fwgs. Ping me (@a1batross) or other engine developer and attach link to your repository, so we can discuss and help you in porting.\n"
  },
  {
    "path": "Documentation/environment-variables.md",
    "content": "## Environment variables\n\n#### Xash3D FWGS\n\nThe engine respects these environment variables:\n\n| Variable              | Type       | Description |\n| --------------------- | ---------- | ----------- |\n| `XASH3D_GAME`         | _string_   | Overrides default game directory. Ignored if `-game` command line argument is set |\n| `XASH3D_BASEDIR`      | _string_   | Sets path to base (root) directory, instead of current working directory |\n| `XASH3D_RODIR`        | _string_   | Sets path to read-only base (root) directory. Ignored if `-rodir` command line argument is set |\n| `XASH3D_EXTRAS_PAK1`  | _string_   | Archive file from specified path will be added to virtual filesystem search path in the lowest possible priority |\n| `XASH3D_EXTRAS_PAK2`  | _string_   | Similar to `XASH3D_EXTRAS_PAK1` but next to it in priority list |\n\nEnvironment variables NOT listed in the table above are used internally, and aren't considered as stable interface.\n\n#### mdldec\n\n| Variable              | Type       | Description |\n| --------------------- | ---------- | ----------- |\n| `MDLDEC_ACT_PATH`     | _string_   | If set, will read activities list from this path |\n"
  },
  {
    "path": "Documentation/extensions/addon-folders.md",
    "content": "# Addon folders in Xash3D FWGS\n\nXash3D FWGS supports both GoldSource-style addon folders and has few own. Each directory can have it's own archives that will be mounted with lower priority than directory itself.\n\nBelow is the mounts map, in order of precedence from least important to most important.\n\n|--------------------|------|\n| Directory          | Note |\n|--------------------|------|\n| `$game/downloaded` | Always added. Used to store server downloads.|\n| `$game`            | This is the game directory.  |\n| `$game/custom`     | Always added. Used for user modifications content. |\n| `$game_hd`         | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. |\n| `$game_addon`      | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. |\n| `$game_lv`         | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. |\n| `$game_$language`  | Added with `fs_mount_l10n` set to non-zero value. Language is controlled with `ui_language` cvar or `-language` command line switch. Used for localization content, similar to GoldSrc. |\n"
  },
  {
    "path": "Documentation/extensions/console-scripting.md",
    "content": "## Console variables\n\nConsole variables (or CVars) are present in all quake-based games.\n\nBy default, it is settings, created by engine, server or client libraries.\n\nBut you can use `set` command to define variables even if they are not created by the engine.\n\nFor example, you can set cvar before it is registered in code.\n\n`set defaultmap crossfire`\n\nThis works even in server.cfg before server cvars initialization and the engine will reuse its value on cvar creation\n\n## Aliases\n\nAn alias allows to define new commands.\n\n`alias wnext \"invnext;wait;wait;+attack;wait;-attack\"`\n\nYou can hook any command by adding an alias to it and unaliasing it, when you want to use original command.\n\n```\nalias invnext1 \"unalias invnext;wnext;alias invnext invnext1\"\nalias invnext invnext1\n```\n\n## Scripting extensions\n\nThis is an extensions of Xash3D FWGS(merged to original Xash3D since build 3887), that can be enabled by cmd_scripting cvar.\n\nEnabling scripting: `cmd_scripting 1`\n\nThis is an archive cvar and it will be saved.\n\n### CVar substitution\n\nYou can substitute cvar value to any command by adding \\$ symbol:\n\n`echo $sv_cheats`\n\n### Condition checking\n\nAllows checking cvar values.\n\n```\nif <value1> <operator> <value2>\n:<action1>\n:if <value3>\n::<action2>\n:<action3>\nelse\n:<action4>\n```\n\n* Values are any string or numeric values (for example, substituted cvars).\n* Operator is = (or ==), \\!=, \\<, \\>, \\<=, \\>=. == is same to =.\n* If single value specified, condition is true when value is non-zero\n\nExample:\n\n```\nif $sv_cheats == 1\n:echo Cheats enabled, adding cheat menu\n:exec cheatmenu.cfg\nelse\n:echo Please enable cheats to use this!\n```\n"
  },
  {
    "path": "Documentation/extensions/entity-tools.md",
    "content": "# Entity tools\n\nFor some features described below, you need to enable [console scripting](https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/extensions/console-scripting.md) with command `cmd_scripting 1` in console.\nTo get more information about it, check another according page related to console scripting.\n\n## Commands description\n\n### ent_create\nCreate entity with specified classname and key/values.\n\n`ent_create <classname> <key> <value> <key> <value> ...`\n\nFor example:\n\n`ent_create monster_zombie targetname zomb1`\n\nAfter creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description.\n\n### ent_fire\n\nMakes some actions on entity.\n\n`ent_fire <pattern> <command> <args>`\n\n#### Available commands:\n\nSet fields (only set entity field, does not call any functions)\n* health\n* gravity\n* movetype\n* solid\n* rendermode\n* rendercolor (vector)\n* renderfx\n* renderamt\n* hullmin (vector)\n* hullmax (vector)\n\nActions\n* rename: set entity targetname\n* settarget: set entity target (only targetnames)\n* setmodel: set entity model (does not update)\n* set: set key/value by server library\n    * See game FGD to get list.\n    * command takes two arguments\n* touch: touch entity by current player.\n* use: use entity by current player.\n* movehere: place entity in player fov.\n* drop2floor: place entity to nearest floor surface\n* moveup: move entity to 25 units up\n* moveup (value): move by y axis relatively to specified value\n\nFlags (set/clear specified flag bit, arg is bit number):\n* setflag\n* clearflag\n* setspawnflag\n* clearspawnflag\n\n### ent_info\nPrint information about entity by identificator.\n\n`ent_info <identificator>`\n\n### ent_getvars\nSet client cvars containing entity information (useful for [scripting](extensions/console-scripting.md)) and then calls ent_last_cb.\n\n`ent_getvars <identificator>`\n\nThese cvars are set:\n```\nent_last_name\nent_last_num\nent_last_inst\nent_last_origin\nent_last_class\n```\n\n### ent_list\nPrint short information about antities, filtered by pattern.\n\n`ent_list <pattern>`\n\n## Syntax description\n\n#### \\<identificator\\>\n* !cross: entity under aim\n* !\\<number\\>_\\<serial\\>: instance code\n* Set by ent_getvars command\n* Entity index\n* Targetname pattern\n\n#### \\<pattern\\>\n\nPattern is like identificator, but may filter many entities by classname.\n\n#### (vector)\n\nUsed by ent_fire command. vector means three float values, entered without quotes.\n\n#### key/value\n\nAll entities parameters may be set by specifiing key and value strings.\n\nOriginally, this mechanizm is used in map/bsp format, but it can be used in enttools too.\n\nKeys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters.\n\nIf value contains spaces, it must be put in quotes:\n\n`ent_fire !cross set origin \"0 0 0\"`\n\n## Using with scripting\n\nent_create and ent_getvars commands are setting cvars on client\n\nIt can be used with ent_last_cb alias that is executed after setting cvars.\n\nSimple example:\n\n```\nent_create weapon_c4\nalias ent_last_cb \"ent_fire \\$ent_last_inst use\"\n```\n\nUse weapon_c4 after creating it.\n\nNote that you cannot use many different callbacks at the same time.\n\nYou can set entity name by by pattern and create special script, contatning all callbacks.\n\nExample:\n\n> example.cfg\n```\nalias ent_last_cb exec entity_cb.cfg\nent create \\<class\\> targetname my_ent1_$name\nent_create \\<class\\> targetname my_ent2_$name\n```\n> entity_cb.cfg\n```\nif $ent_last_name == my_ent1_$name\n:(ent1 actions)\nif $ent_last_name == my_ent2_$name\n:(ent2 actions)\n```\nNote that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks.\n\n## Server side configuration\n\nTo enable entity tools on server, set sv_enttools_enable to 1\n\nTo change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number.\n"
  },
  {
    "path": "Documentation/extensions/expanded-common-structures.md",
    "content": "# Expanded structures that used by engine and mods\nTo make porting and developing mods on 64-bit platforms less painful, we decided to expand size of several structures.\nThis information important in case you are using codebase like XashXT, Paranoia 2: Savior and want to compile your mod for platform with 64-bit pointer size: you should replace old definitions with new ones, otherwise your mod will not work with Xash3D FWGS (typically, it's just crashing when starting map).\n| Structure name | Locates in file | Original size on 64-bit | Current size on 64-bit |\n|----------------|-----------------|-------------------------|------------------------|\n|`mfaceinfo_t` | `common/com_model.h` | 176 bytes |  304 bytes |\n|`decal_s` | `common/com_model.h` | 72 bytes |  88 bytes |\n|`mextrasurf_t` | `common/com_model.h` | 376 bytes |  504 bytes |\n"
  },
  {
    "path": "Documentation/extensions/input-interface-ru.md",
    "content": "## Цель\n\nНа текущий момент клиенты имеют некоторые части платформозависимого кода внутри себя. Это плохо, т.к. мы не можем использовать одинаковые функции (если не перепишем почти половину кода SDL) на разных платформах.\n\n## Клиентская часть\n\n* Клиент будет иметь возможность полностью реализовать touch-ввод. Отрисовка может быть произведена через HUD.\n* Клиент получит простые события движения и обзора от движка\n\n### Клиентская реализация\n\n#### Клиент будет экспортировать некоторые функции движку (опционально):\n\n* `int IN_ClientTouchEvent ( int fingerID, float x, float y, float dx, float dy );`\n\nВернёт 1 если касание активно, иначе 0.\n\n* `void IN_ClientMoveEvent ( float forwardmove, float sidemove );`\n\nКлиент будет накапливать значения величин для команд движения перед созданием команд и очищать их при CreateMove.\n\n* `void IN_ClientLookEvent ( float relyaw, float relpitch );`\n\nКлиент будет вращать камерой когда нужно, прямо как в реализации мыши\n\n## Движковая часть\n\n* Движок будет управлять событиями платформы и вызывать клиентские функции\n* Движок реализует стандартную систему взгляда и движения если её нет в клиенте\n\n### Движковая реализация\n\n#### События касания\n\nПеред вызовом ClientMove движок обязан получить события о касании.\n\nЕсли из клиента экспортирована функция IN_ClientTouchEvent, событие будет отправлено клиенту.\n\nИначе, движок будет рисовать свой touch-интерфейс.\n\n#### Другие события\n\nИнтерфейс прикосновений и код джойстика в движке будут генерировать следующие два типа событий:\n* События движения (функция IN_ClientMoveEvent)\n* События просмотра (функция IN_ClientLookEvent)\n\nЕсли клиент экспортирует эти функции, события будут отправляться клиенту перед CreateMove\nИначе события просмотра будут происходить перед CreateMove, но после MoveEvent. Они будут применены к генерируемым командам\n"
  },
  {
    "path": "Documentation/extensions/input-interface.md",
    "content": "## Purpose\n\nClients have different platform-depended input code now.\nIt is bad because we cannot use same functions (if we won't rewrite almost half of SDL) on different platforms.\n\n## Client part\n\n* Client will have ability to fully implement touch input. Drawing may be done by HUD.\n* Client will receive basic motion and look events from engine\n\n### Client implementation\n\n#### Client will optionally export some functions to Engine:\n* `int IN_ClientTouchEvent ( int fingerID, float x, float y, float dx, float dy );`\n\nReturn 1 if touch is active, 0 otherwise.\n\n* `void IN_ClientMoveEvent ( float forwardmove, float sidemove );`\n\nClient wil accumulate move values before creating commands and flush it on CreateMove.\n\n* `void IN_ClientLookEvent ( float relyaw, float relpitch );`\n\nClient will rotate camera when needed as in mouse implementation\n\n## Engine part\n\n* Engine will handle platform events and call client functions.\n* Engine will implement fallback look and movement system when client interface not present\n\n### Engine implementation\n\n#### Touch events\n\nBefore calling ClientMove engine must get touch events.\n\nIf client exported IN_ClientTouchEvent, event will be sent to client.\n\nOtherwise engine will draw own touch interface.\n\n#### Other events\n\nEngine touch interface and joystick support code will generate two types of events:\n* Move events (IN_ClientMoveEvent function)\n* Look events (IN_ClientLookEvent function)\n\nIf client exported these functions, events will be sent to client before CreateMove\nOtherwise Look Event will be processed before CreateMove, but MoveEvent after. It will be applied to generated command\n"
  },
  {
    "path": "Documentation/extensions/library-naming.md",
    "content": "I propose a new library naming scheme, which will allow to distribute mods and games in single archive to different operating systems and CPUs:\n\nLegend:\n* $os -- Q_buildos() return value, in lower case.\n* $arch -- Q_buildarch() return value, in lower case.\n* $ext -- OS-specific extension: dll, so, dylib, etc.\n\nThe scheme will be:\n\n1. Client library:\n* ```client.$ext``` for **Win/Lin/Mac** with **x86**.\n* ```client_$arch.$ext``` for **Win/Lin/Mac** with **NON-x86**.\n* ```client_$os_$arch.$ext``` for everything else.\n\n2. Menu library:\n* ```menu.$ext``` for **Win/Lin/Mac** with **x86**.\n* ```menu_$arch.$ext``` for **Win/Lin/Mac** with **NON-x86**.\n* ```menu_$os_$arch.$ext``` for everything else.\n\n3. Server library:\n* On  **Win/Lin/Mac** with **x86**, it **MUST** use the raw gamedll name for corresponding OS field from `gameinfo.txt`.\n* On **Win/Lin/Mac** with **NON-x86**, it **MUST** use the raw gamedll name for corresponding OS field from `gameinfo.txt`, but append ```_$arch``` before file extension. Like: ```hl_amd64.so``` or ```cs_e2k.so```.\n* On everything else, it must use gamedll name from ```gamedll_linux``` field, but append ```_$os_$arch``` before file extension. Like: ```hl_haiku_amd64.so``` or ```cs_freebsd_armhf.so```.\nWhy ```gamedll_linux``` and not ```gamedll```? Because it looks more logic that way, most operating systems are *nix-like and share code with Linux, rather than Windows.\n\n4. Refresh library: not needed, as RefAPI is not stable and it's not intended to distribute with mods.\n\nFor any libraries distributed **with** engine, naming scheme should be used more convenient for OS port.\n\nIssue #0. Inconsistency between ABI and Q_buildarch.\\\nResolution: Change Q_buildarch return value to use Debian-styled architectures list: https://www.debian.org/ports/, which includes a special naming for big/little-endian and hard/soft-float ARM.\n\nIssue #1: Build-system integration.\\\nResolution: implemented as [LibraryNaming.cmake](https://github.com/FWGS/hlsdk-portable/blob/master/cmake/LibraryNaming.cmake) and [library_naming.py](https://github.com/FWGS/hlsdk-portable/blob/master/scripts/waifulib/library_naming.py) extensions, see \n\nIssue #2(related to #0): Which ARM flavours we actually need to handle?\\\nResolution: Little-endian only, as there is no known big-endian ARM platforms in the wild.\nArchitecture is coded this way:\n* ```armvxy```, where `x` is ARM instruction set level and `y` is hard-float ABI presence: `hf` where hard float ABI used, otherwise `l`.\n\nIssue #3: Some mods (like The Specialists, Tyrian, ...) already apply suffixes _i386, _i686 to the gamedll path:\\\nResolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme.\n\nSee discussion: https://github.com/FWGS/xash3d-fwgs/issues/39\n\nIssue #4: When distributing game libraries on Android inside an APK, they couldn't be loaded.\nResolution: Enable `useLegacyPackaging` option in build.gradle, when distributing games in APK. Always force game libraries to have `lib` prefix on Android, regardless if they are packaged in APK or not..\n"
  },
  {
    "path": "Documentation/extensions/mp3-loops.md",
    "content": "## Looping MP3 extension\n\nIt is now possible to loop MP3 file in Xash3D FWGS by adding a custom text tag with `LOOP_START` or `LOOPSTART` in description and time point (in raw samples) in value.\n\n### Example with foobar2000\n1. Open Foobar2000\n2. Add your .mp3 file to playlist\n3. Right click to newly added file and select Properties\n4. In Metadata tab, at the bottom of the table, select \"+add new\"\n5. In newly added line replace `input field name` with `LOOP_START` (without any symbols).\n6. Press Tab and enter loop time point in raw samples. For example, `0` will replay sound file from beginning to end indefinitely.\n\n### Possible alternatives\n1. Classic WAV files looping. HQ WAV files can take too much disk space, and recommended software supporting cue points is paid, outdated and can't run on modern systems. (Although there is alternative that's proven to work with idTech-based engines called LoopAuditioneer.)\n2. Vorbis looping through comment. Engine doesn't support Vorbis but this extension was highly inspired by this hack.\n\n### Known bugs and limitations\n1. At this time using MP3 as SFX requires complete decoding. This can cause noticeable stutters, so keep MP3 file length in mind.\n2. We deliberately only support modern ID3v2.3 and ID3v2.4 tags. Using ID3v1 is not possible.\n\n\n\n"
  },
  {
    "path": "Documentation/extensions/native-object.md",
    "content": "# GetNativeObject API\n\nTo be able to use platform-specific features or get optional engine interfaces, we've added a simple call to MobilityAPI on client DLL and PhysicsAPI for server DLL and extended MenuAPI for menu DLL.\n\nIt's defined like this:\n\n```\nvoid *pfnGetNativeObject( const char *name );\n```\n\n#### Cross-platform objects\n\nOnly these objects are guaranteed to be available on all targets.\n\n| Object name | Interface |\n|-------------|-----------|\n| `VFileSystem009` | Provides C++ interface to filesystem, binary-compatible with Valve's VFileSystem009. |\n| `XashFileSystemXXX` | Provides C interface to filesystem. This interface is unstable and not recommended for generic use, outside of engine internals. For more info about current version look into `filesystem.h`. |\n\n#### Android-specific objects\n\n| Object name | Interface |\n|-------------|-----------|\n| `JNIEnv`    | Allows interfacing with Java Native Interface. |\n| `ActivityClass` | Returns JNI object for engine Android activity class. |\n"
  },
  {
    "path": "Documentation/extensions/sounds.lst.md",
    "content": "# sounds.lst.md\n\nUsing sounds.lst located in scripts folder, modder can override some of the hardcoded sounds in temp entities and server physics.\n\nFile format:\n```\n<group name>\n{\n\t<path1>\n\t<path2>\n\t<path3>\n}\n\n<group2 name> <path with %d> <min number> <max number>\n```\n\n* Sounds can use any supported sound format (WAV or MP3).\n* The path must be relative to the sounds/ folder in the game or base directory root, addon folder, or archive root.\n* Groups can be empty or omitted from the file to load no sound.\n* Groups can either list a set of files or specify a format string and a range.\n* Anything after // will be considered a comment and ignored.\n* Behavior is undefined if the group was listed multiple times.\n\nCurrently supported groups are:\n|Group name|Usage|\n|----------|-----|\n|`BouncePlayerShell`|Used for BOUNCE_SHELL tempentity hitsound|\n|`BounceWeaponShell`|Used for BOUCNE_SHOTSHELL tempentity hitsound|\n|`BounceConcrete`|Used for BOUNCE_CONCRETE tempentity hitsound|\n|`BounceGlass`|Used for BOUCNE_GLASS|\n|`BounceMetal`|Used for BOUNCE_METAL|\n|`BounceFlesh`|Used for BOUNCE_FLESH|\n|`BounceWood`|Used for BOUNCE_WOOD|\n|`Ricochet`|Used for BOUNCE_SHRAP and ricochet tempentities|\n|`Explode`|Used for tempentity explosions|\n|`EntityWaterEnter`|Used for entity entering water|\n|`EntityWaterExit`|Used for entity exiting water|\n|`PlayerWaterEnter`|Used for player entering water|\n|`PlayerWaterExit`|Used for player exiting water|\n\n## Example\n\nThis example is based on defaults sounds used in Half-Life:\n\n```\nBouncePlayerShell \"player/pl_shell%d.wav\" 1 3\nBounceWeaponShell \"weapons/sshell%d.wav\" 1 3\nBounceConcrete \"debris/concrete%d.wav\" 1 3\nBounceGlass \"debris/glass%d.wav\" 1 4\nBounceMetal \"debris/metal%d.wav\" 1 6\nBounceFlesh \"debris/flesh%d.wav\" 1 7\nBounceWood \"debris/wood%d.wav\" 1 4\nRicochet \"weapons/ric%d.wav\" 1 5\nExplode \"weapons/explode%d.wav\" 3 5\nEntityWaterEnter \"player/pl_wade%d.wav\" 1 4\nEntityWaterExit \"player/pl_wade%d.wav\" 1 4\nPlayerWaterEnter\n{\n\t\"player/pl_wade1.wav\"\n}\nPlayerWaterExit\n{\n\t\"player/pl_wade2.wav\"\n}\n```\n"
  },
  {
    "path": "Documentation/gameinfo.md",
    "content": "# Game definition and information file\n\ngameinfo.txt is an essential part of any Xash3D based game. It allows basic customization for games creators, like setting game title, DLL paths, etc.\n\nThis document defines gameinfo.txt syntax, supported keys and liblist.gam conversion rules for the latest version of the engine. Note for engine developers, keep this document in sync with an implementation.\n\n## gameinfo.txt syntax\n\n* gameinfo.txt is a simple list of keys and values, separated by newline.\n* You can add single line comments using double slashes (//).\n* Keys can accept integer, float, string or boolean values.\n* Boolean keys use 0 as false and 1 as true value.\n* To have spaces in string values, you must enclose them in double quotes. Then, to have double quotes, you must escape it with backslash, and to have a backslash you need to use double backslashes.\n\nThe example:\n```\n// this is a comment :)\n// this is another comment\nsome_integer_key 123\n\nthis_is_float_key 13.37 // optional comment\n\nenable_feature 1 // boolean, 1 to enable, 0 to disable\n\nexample_string_key string_value\nexample_spaces \"string with spaces\"\nexample_title \"Fate\\\\Stay Night\" // engine will parse it as Fate\\Stay Night\n```\n\n## gameinfo.txt keys\n\nThis is a list of all gameinfo.txt keys supported by the engine. Be aware that the engine will silently skip all unrecognized keys.\n\n| Key              | Type       | Default value   | Description |\n| ---------------- | ---------- | --------------- | ----------- |\n| `ambient0`       | string     | Empty string    | Automatic ambient sound |\n| `ambient1`       | string     | Empty string    | Automatic ambient sound |\n| `ambient2`       | string     | Empty string    | Automatic ambient sound |\n| `ambient3`       | string     | Empty string    | Automatic ambient sound |\n| `basedir`        | string     | `valve`         | Game base directory, used to share assets between games |\n| `date`           | string     | Empty string    | Game release date. Unused. |\n| `dllpath`        | string     | `cl_dlls`       | Game DLL path. Engine will search custom DLLs (client or menu, for example) in this directory, except `gamedll`, see below. |\n| `fallback_dir`   | string     | Empty string    | Additional game base directory |\n| `gamedir`        | string     | Current gamedir | Game directory, ignored in FWGS, as game directory is defined by the game directory name |\n| `gamedll`        | string     | `dlls/hl.dll`   | Game server DLL for 32-bit x86 Windows (see LibraryNaming.md for details) |\n| `gamemode`       | string     | Empty string    | Game type. When set to `singleplayer_only` or `multiplayer_only` marks the game as SP or MP only respectively, hiding the option in game UI. Omitting this key or using custom values mark the game as both MP and SP compatible. |\n| `icon`           | string     | `game.ico`      | Game icon. Engine will automatically append .ico and may automatically switch to .tga icon as well |\n| `max_beams`      | integer    | 128             | Beams limit, 64 min, 512 max |\n| `max_edicts`     | integer    | 900             | Entities limit, 600 min, 8192 max (protocol limit). In FWGS, minimum is 64. |\n| `max_particles`  | integer    | 4096            | Particles limit, 1024 min, 131072 max |\n| `max_tempents`   | integer    | 500             | Temporary entities limit. 300 min, 2048 max |\n| `mp_entity`      | string     | `info_player_deathmatch` | Entity used to mark maps as multiplayer |\n| `mp_filter`      | string     | Empty string    | When set, used to filter multiplayer maps instead of `mp_entity`.<br>If the map name starts with the same characters as this filter, it's considered a multiplayer map |\n| `nomodels`       | boolean    | 0               | When set to 1, disallows changing player model in UI |\n| `noskills`       | boolean    | 0               | When set to 1, disallows selection of game difficulty |\n| `secure`         | boolean    | 0               | When set to 1, original Unkle Mike's engine will completely disable developer mode. FWGS ignores but preserves this value for compatibility. |\n| `size`           | integer    | 0               | Game directory size in bytes, used in Change Game dialog only |\n| `startmap`       | string     | `c0a0`          | The name of the map used in new game |\n| `sp_entity`      | string     | `info_player_start` | Entity used to mark map as single player. Used in map validation |\n| `title`          | string     | `New Game`      | Game title, used in window title, default server name, etc. |\n| `trainmap`       | string     | `t0a0`          | The name of the training map (Hazard Course) |\n| `type`           | string     | Empty string    | Game type, used in Change Game UI. |\n| `url_info`       | string     | Empty string    | Game homepage URL, used in Change Game UI |\n| `url_update`     | string     | Empty string    | Game updates URL, used in Settings UI |\n| `version`        | float      | 1.0             | Game version, used in Change Game dialog and in server info |\n\n## FWGS-specific gameinfo.txt keys\n\nThese strings are specific to Xash3D FWGS.\n\n| Key                     | Type       | Default value            | Description |\n| ----------------------- | ---------- | ------------------------ | ----------- |\n| `animated_title`        | boolean    | 0                        | Use animated title in main menu (WON Half-Life logo.avi imitation from Half-Life 25-th anniversary update)\n| `autosave_aged_count`   | integer    | 2                        | Auto saves limit used in saves rotation |\n| `gamedll_linux`         | string     | Generated from `gamedll` | Game server DLL for 32-bit x86 Linux (see LibraryNaming.md for details) |\n| `gamedll_osx`           | string     | Generated from `gamedll` | Game server DLL for 32-bit x86 macOS (see LibraryNaming.md for details) |\n| `hd_background`         | boolean    | 0                        | Use HD background for main menu (Half-Life 25-th anniversary update) |\n| `internal_vgui_support` | boolean    | 0                        | Only for programmers! Required to be set as 1 for PrimeXT!<br>When set to 1, the engine will not load vgui_support DLL, as VGUI support is done (or intentionally ignored) on the game side. |\n| `render_picbutton_text` | boolean    | 0                        | When set to 1, the UI will not use prerendered `btns_main.bmp` and dynamically render them instead |\n| `quicksave_aged_count`  | integer    | 2                        | Quick saves limit used in saves rotation |\n| `demomap`               | string     | Empty string             | The name of the demo chapter map (Half-Life Uplink) |\n\n## Note on GoldSrc liblist.gam support\n\nAs Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\\\nXash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam.\n\n> [!NOTE]\n> Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console.\n\nFor game creators who plan supporting only Xash3D, using this file is not recommended.\n\nThe table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored.\n\n| `liblist.gam` key | `gameinfo.txt` key | Note |\n| ----------------- | ------------------ | ---- |\n| `animated_title`  | `animated_title`   |      |\n| `edicts`          | `max_edicts`       |      |\n| `fallback_dir`    | `fallback_dir`     |      |\n| `game`            | `title`            |      |\n| `gamedir`         | `gamedir`          |      |\n| `gamedll`         | `gamedll`          |      |\n| `gamedll_linux`   | `gamedll_linux`    |      |\n| `gamedll_osx`     | `gamedll_osx`      |      |\n| `hd_background`   | `hd_background`    |      |\n| `icon`            | `icon`             |      |\n| `mpentity`        | `mp_entity`        |      |\n| `mpfilter`        | `mp_filter`        |      |\n| `nomodels`        | `nomodels`         |      |\n| `secure`          | `secure`           | In GoldSrc it's used to mark the multiplayer game as anti-cheat enabled.<br>Original Xash3D misinterprets its value for disallowing console and developer mode.<br>FWGS ignores this key but preserves for compatibility. |\n| `startmap`        | `startmap`         |      |\n| `size`            | `size`             |      |\n| `trainingmap`     | `trainmap`         |      |\n| `trainmap`        | `trainmap`         |      |\n| `type`            | `type` & `gamemode`| In `liblist.gam` this key works as both `type` and `gamemode`.<br>If value is `singleplayer_only` or `multiplayer_only` the game is marked as SP or MP only, and `gameinfo.txt` type set to `Single` or `Multiplayer`.<br>Any custom value will mark the game as both SP and MP compatible, and type is set to whatever custom value. |\n| `url_dl`          | `url_update`       |      |\n| `url_info`        | `url_info`         |      |\n| `version`         | `version`          |      |\n\n\n"
  },
  {
    "path": "Documentation/goldsrc-protocol-support.md",
    "content": "# Support for GoldSrc network protocol\nThis feature is still work-in-progress, but for now it's available for all users, and we appreciate any bug-reports and contributions around it.\n\nFor connecting to GoldSrc-based servers, use this command:\n```\nconnect ip:port gs\n```\n\nBut keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion.\nWithout this requirement, you will just get \"Steam validation rejected\" error on connecting.\n\nThat is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it).\n\nAlso, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as \"fake clients\" and banning/kicking them. Maybe this problem will be\nsolved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks.\n"
  },
  {
    "path": "Documentation/hd-textures.md",
    "content": "### HD (external) textures support\n\nXash3D supports loading texture replacements in TGA format for almost all types of models in the game, except alias models at this time.\n\nTextures are expected to be located at:\n* `modfolder/materials/<mapname>` - for a specific map\n* `modfolder/materials/common` - common for all maps\n* `modfolder/materials/decals` - for decals\n* `modfolder/materials/models/<model>` - for models (texture name must match the internal texture name in the model)\n\nSupport for high-resolution textures is enabled setting `host_allow_materials` cvar to `1` or in the menu, in \"Video options\" section.\n\n#### Xash3D FWGS additions\n\nIn addition to paths above, Xash3D FWGS checks following paths:\n\n* `modfolder/materials/sprites/<sprite>` - for sprites, except HUD sprites\n\nAlso, to check which texture replacements are loaded successfully, failed or weren't found, a mod developer can set `host_allow_materials` cvar value to `2`. The engine will spew log at any developer level in the following format:\n\n```\nLooking for <replacement> replacement... <status code> (<path relative to mod directory>)\n```\n\nStatus codes:\n* `OK` - texture replacement file was found and loaded into GPU memory successfully\n* `FAIL` - texture file was found but hasn't been parsed or loaded successfully. Refer to engine log for more details.\n* `MISS` - texture file wasn't found\n\nExample:\n```\nLooking for maps/bounce.bsp:!waterblue tex replacement...OK (materials/common/!waterblue.tga)\nLooking for maps/bounce.bsp:!waterblue_luma tex replacement...MISS (not found)\nLooking for {shot2 decal replacement...MISS (materials/decals/{shot2.tga)\nLooking for {shot4 decal replacement...MISS (materials/decals/{shot4.tga)\nLooking for {shot3 decal replacement...MISS (materials/decals/{shot3.tga)\nLooking for models/gman tex replacement...FAIL (materials/models/gman/GMan_Case1.tga)\nLooking for models/gman tex replacement...FAIL (materials/models/gman/inside_1.tga)\n```\n\n"
  },
  {
    "path": "Documentation/mod-porting-guide.md",
    "content": "# Self-made port\n## Compatibility with RISC architectures\n### Unaligned access\nUnaligned access on **i386** causes only performance penalty, but on **RISC** it can cause unstable work.\n\nFor HLSDK at least you need such patches in util.cpp:\n - https://github.com/FWGS/halflife/commit/7bfefe86e35d67867ae7af830ac1fc38f2908360\n - https://github.com/FWGS/hlsdk-portable/commit/617d75545f2ecb9b2d46cc30728dc37c9eb6d35e\n\n### Signed chars\n`char` type defined as **signed** for **x86** and as **unsigned** for **arm**.\n\nAnd sometimes such difference can break logic.\n\nAs a solution you can use `signed char` type in code directly or use `-fsigned-char` option for gcc/clang compilers.\n\nFor HLSDK at least you need such [fix](https://github.com/FWGS/hlsdk-portable/commit/1ca34fcb4381682bd517612b530db22a1354a795) in nodes.cpp/.h.\n\n## Compatibility with 64bit architectures\nYou need list of patches for Studio Model Render, MAKE_STRING macro and nodes:\n - https://github.com/FWGS/hlsdk-portable/commit/d287ed446332e615ab5fb25ca81b99fa14d18a73\n - https://github.com/FWGS/hlsdk-portable/commit/3bce17e3a04f8af10a927a07ceb8ab0f09152ec4\n - https://github.com/FWGS/hlsdk-portable/commit/9ebfc981773ec4c7a89ffe52d9c249e1fbef9634\n - https://github.com/FWGS/hlsdk-portable/commit/00833188dab87ef5746286479ba5aeb9d83b4a0c\n - https://github.com/FWGS/hlsdk-portable/commit/4661b5c1a5245b27a5532745c11e44b5540e4172\n - https://github.com/FWGS/hlsdk-portable/commit/2b61380146b1d58a8c465f0e312c061b12bda115\n - https://github.com/FWGS/hlsdk-portable/commit/8ef6cb2427ee16a763103bd3f315f38e2f01cfe2\n\n## Mobility API\nXash3D FWGS has special extended interface in `mobility_int.h` which adds some new features like vibration on mobile platforms.\n\n## Porting server-side code\nOriginal valve's server code was compatible with linux and gcc 2.x.\n\nNewer gcc versions have restriction which breaks build.\n\nNow, to make it building with gcc 4.x+ or clang, you need to do following:\n* Go to cbase.h and redefine macros as following\n```\n#define SetThink( a ) m_pfnThink = static_cast <void (CBaseEntity::*)(void)> (&a)\n#define SetTouch( a ) m_pfnTouch = static_cast <void (CBaseEntity::*)(CBaseEntity *)> (&a)\n#define SetUse( a ) m_pfnUse = static_cast <void (CBaseEntity::*)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )> (&a)\n#define SetBlocked( a ) m_pfnBlocked = static_cast <void (CBaseEntity::*)(CBaseEntity *)> (&a)\n#define ResetThink( ) m_pfnThink = static_cast <void (CBaseEntity::*)(void)> (NULL)\n#define ResetTouch( ) m_pfnTouch = static_cast <void (CBaseEntity::*)(CBaseEntity *)> (NULL)\n#define ResetUse( ) m_pfnUse = static_cast <void (CBaseEntity::*)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )> (NULL)\n#define ResetBlocked( ) m_pfnBlocked = static_cast <void (CBaseEntity::*)(CBaseEntity *)> (NULL)\n...\n#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast <void (CBaseToggle::*)(void)> (&a)\n```\n* Replace all SetThink(NULL), SetTouch(NULL), setUse(NULL) and SetBlocked(NULL) by ResetThink(), ResetTouch(), ResetUse() and ResetBlocked()\n* Sometimes you may need to add #include <ctype.h> if functions tolower or isspace are missing\n\n## Porting client-side code\n\n* Redefine all DLLEXPORT defines as empty field (Place it under _WIN32 macro if you want to keep windows compatibility).\n* Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp.\n* Fix CAPS filenames in includes (like STDIO.H, replace by stdio.h).\n* Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-portable port (cl_util.h).\n* Add ctype.h where is needed (tolower, isspace functions).\n* Add string.h where is needed (memcpy, strcpy, etc).\n* Use in_defs.h from hlsdk-portable.\n* Add input_xash3d.cpp from hlsdk-portable project to fix input.\n\nNow your client should be able to build and work correctly.\n\n# Porting mod to hlsdk-portable\nLook at changes which was made.\n\nIf there are not too much changes (for example, only some weapons was added), add these changes in hlsdk-portable.\n\nYou can use diff with original HLSDK and apply it as patch to hlsdk-portable.\n\n```\nNOTE: Many an old mods was made on HLSDK 2.0-2.1 and some rare mods on HLSDK 1.0.\nSo you need to have different versions of HLSDK to make diffs.\nPlus different Spirit of Half-Life versions if mod was made on it.\nAlso, weapons in old HLSDK versions does not use client weapon prediction system and you may be need to port standart half-life weapons to server side.\n```\nFiles must have same line endings (use dos2unix on all files).\n\nWe recommend to enable ignoring space changes in diff.\n\nMove all new files to separate directories.\n\n# Possible replacements for non-portable external dependencies\n1. If mod uses **fmod.dll** or **base.dll** to play mp3/ogg on client-side or to precache sounds on server-side, you can replace it with:\n\t- [pfnPrimeMusicStream](https://github.com/FWGS/hlsdk-portable/blob/master/engine/cdll_int.h#L293=) engine callback;\n\t- [miniaudio](https://github.com/mackron/miniaudio);\n\t- [phonon](https://community.kde.org/Phonon).\n\n2. If mod uses **OpenGL**, porting code to Xash3D render interface is recommended.\n\n3. If mod uses **cg.dll**, you can try to port code to [NVFX](https://github.com/tlorach/nvFX).\n\n4. If mod uses detours, comment code or try to find replacement somehow by yourself.\n\n# Additional recommendations\n1. If mod uses STL, you can replace it with [MiniUTL](https://github.com/FWGS/MiniUTL).\n2. Avoid to use dynamic casts to make small size binaries.\n3. Avoid to use exceptions to make small size binaries.\n \n# Writing build scripts\n\nUse wscript/CMakeLists.txt files from hlsdk-portable as build scripts example.\n\nGet .c and .cpp file lists from Visual Studio project.\n\nAdd missing include dirs.\n\n"
  },
  {
    "path": "Documentation/musl.md",
    "content": "# Xash3D FWGS on `musl`\n\nXash3D FWGS works on `musl` out of the box. However, the engine doesn't try to differentiate glibc and musl anymore. If you see error similar to:\n\n```\nHost_InitError: can't initialize cl_dlls/client.so: Error relocating valve/cl_dlls/client.so: __sprintf_chk: symbol not found\n```\n\n... or you know that the game you're running is linked against glibc, you can try using `libgcompat`, like this:\n\n```\n$ LD_PRELOAD=/lib/libgcompat.so.0 ./xash3d ...\n```\n\nIt will automatically add the missing symbols that glibc binaries usually need. In the future we might automatically link engine against `libgcompat` for better compatibility with prebuilt or closed-source games, if there will be any use for this.\n"
  },
  {
    "path": "Documentation/nat-bypass-usage.md",
    "content": "# NAT bypass feature in Xash3D FWGS\nSince IPv6 not as widespread as we would like, NAT (Network Address Translation) still being actively used by many internet service providers in an attempts to\nmitigate IPv4 addresses exhaustion. In short, they uses one IPv4 address and doing some tricks with ports to represent many other users behind this address.\n\nBut this leads to a problem for users: they cannot accept direct\nincoming connections anymore. This means that if you are behind provider's NAT and will try to setup Xash3D FWGS server - nobody will be able to connect to it,\nand the server will not show up in servers public list.\n\n## Is it possible to avoid this problem?\nIn most cases, it is possible to bypass NAT with UDP hole punching, and Xash3D FWGS uses this method too. But this method is not 100% guaranteed to work - it depends\non NAT configurations on both server and client side, and there is no way to control it.\n\nFirst of all, server should not be behind symmetric NAT. You can check your NAT type on [this page](https://www.checkmynat.com/).\nIf you get \"Symmetric NAT\" result in this test, that means you cannot setup publicly available server with internet connection that you are using.\n\nHere is more detailed scheme of different NAT types compatibility, it explains are users with different NAT types can connect to each other or not.\nClient NAT type on rows, server NAT type on columns:\n\n| *NAT Type*           | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric |\n| -------------------- | --------- | --------------- | -------------------- | --------- |\n| Full Cone            | ✔️        | ✔️              | ✔️                    | ✔️        | \n| Restricted Cone      | ✔️        | ✔️              | ✔️                    | ✔️        |\n| Port Restricted Cone | ✔️        | ✔️              | ✔️                    | ❌        |\n| Symmetric            | ✔️        | ✔️              | ❌                   | ❌        |\n\n## How to use NAT bypass feature?\nIf you are starting server within the game, you need to enable option \"*Use NAT Bypass instead of direct mode*\" in bottom left corner of screen.\n\nIf you are starting dedicated server, you should add console variable `sv_nat 1` to server.cfg file, or add `+sv_nat 1` to server startup parameters.\n\nIf you need to connect to server behind NAT, there is separate tab named *NAT* for such servers in server browser. Note that it is useless to add NAT server to favorites,\nor somehow trying manually to store it, because it has changing address and port.\n"
  },
  {
    "path": "Documentation/not-supported-mod-list-and-reasons-why.md",
    "content": "# Not supported mods and reasons why\n\n|Name\t\t\t\t\t\t\t|Version\t\t\t|Why not working\t\t\t\t\t\t|What was made for that\n|----\t\t\t\t\t\t\t|-------\t\t\t|---------------\t\t\t\t\t\t|----------------------\n|Area 51\t\t\t\t\t\t|Update 1\t\t\t|Uses outdated BSP31 map format and custom HLFX SDK libraries.\t|You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works.\n|Arrange Mod: Rebirth\t\t\t\t\t|v150\t\t\t\t|No idea yet.\t\t\t\t\t\t\t|\n|Blue Shift\t\t\t\t\t\t|The latest steam release\t|Uses vgui2 library which xash3d does not support.\t\t|Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift.\n|Counter Strike\t\t\t\t\t\t|Beta 6.5-\t\t\t|Uses an old WON HL 1.0.0.16- interface.\t\t\t|\n|\t\t\t\t\t\t\t|1.4\t\t\t\t|Has encrypted blob instead of normal client.dll.\t\t|You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works.\n|\t\t\t\t\t\t\t|1.5\t\t\t\t|Has encrypted blob instead of normal client.dll.\t\t|Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845.\n|\t\t\t\t\t\t\t|1.6(The latest steam release)\t|Uses vgui2 library which xash3d does not support.\t\t|Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. Recreated Client Source Code here: https://github.com/Velaron/cs16-client.\n|Counter Strike: Condition Zero\t\t\t\t|The latest steam release\t|Uses vgui2 library which xash3d does not support.\t\t|Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. Recreated Client Source Code here: https://github.com/Velaron/cs16-client.\n|Counter Strike: Condition Zero - Deleted scenes\t|The latest steam release\t|Uses vgui2 library which xash3d does not support. Uses new sequences code on engine-side that was never used in any other mods before.\t\t|Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface.\n|Day of Defeat\t\t\t\t\t\t|The latest steam release\t|Uses vgui2 library which xash3d does not support.\t\t|Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface.\n|Half-Life: Extended\t\t\t\t\t|Day One demo\t\t\t|Uses many hooks to GoldSource engine and version check.\t|Just wait new version or use more old version.\n|Icon of Hell\t\t\t\t\t\t|Beta 0.99\t\t\t|Uses outdated BSP31 map format and paranoia 2 libraries.\t|You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps and use Paranoia 2: The Savior 1.51 libraries but there no warranty if it works.\n|Paranoia 2: The Savior\t\t\t\t\t|All builds older 1.51\t\t|Uses an old renderer interface and engine features.\t\t|\n|Rebellion\t\t\t\t\t\t|1.0\t\t\t\t|Uses an old WON HL 1.0.0.16- interface.\t\t\t|Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion.\n|Sven-Coop\t\t\t\t\t\t|5.0+\t\t\t\t|Uses custom GoldSrc engine.\t\t\t\t\t|\n|Time Shadows\t\t\t\t\t\t|Beta 0.1\t\t\t|Uses Direct3D renderer.\t\t\t\t\t|\n"
  },
  {
    "path": "Documentation/opensource-mods.md",
    "content": "Source code of this mods is available for modders and porters in public access.\n\n# Original Work\n## Absolute Zero\nOfficial gitlab repository - https://gitlab.com/Cobalt-57/half-life-absolute-zero/\n\n## Adrenaline Gamer\nOfficial github repository by Martin \\\"Bullet\\\" Webrant - https://github.com/martinwebrant/agmod\n\n## Arrangement\nMirrored on github - https://github.com/JoelTroch/am_src_30jan2011\n\n## Arrangemode: Rebirth\nMirrored on github - https://github.com/JoelTroch/am_src_rebirth\n\n## Battle Grounds\nMirrored on github - https://github.com/nekonomicon/BattleGrounds\n\n## Bubblemod\nDownload page on official site(WM snapshot) - [http://www.bubblemod.org/dl_default.php](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)\n\nMirrored on github - https://github.com/HLSources/BubbleMod\n\n## Chicken Fortress\nOfficial github repository - https://github.com/CKFDevPowered/CKF3Alpha\n\n## Cold Ice\nVersion 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk\n\nVersion 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci\n\n## Cold Ice Ressurection\nMirrored on github - https://github.com/solidi/hl-mods/tree/master/cir\n\n## Counter-Life\nAvailable on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code\n\n## Cthulhu\nUploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK\n\n## Deathmatch Classic\nAvailable in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc\n\n## Delta Particles\nAvailable on ModDB - https://www.moddb.com/mods/half-life-delta/downloads/delta-particles-full-sources-maps-and-c-code\n\n## Earth Special Forces\nAlpha 2.0 - https://www.gamers-desire.de/details/2830\n\n## ESHQ\nOfficial github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ\n\n## Flat-Life\nAvailable on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life)\n\n## Gang Wars\nMirrored on github - https://github.com/nekonomicon/gw1.45src\n\n## Go-mod\nVersions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads\n\nVersion 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30\n\n## GT mod\nAvailable on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)\n\n## Half-Life: Advanced Deathmatch\nMirrored on github - https://github.com/solidi/hl-mods/tree/master/hla\n\n## Half-Life: Decay\nMirrored on github(PC Port) - https://github.com/hoaxer/Half-Life-Decay\n\n## Half-Life: Echoes\nAvailable on ModDB - https://www.moddb.com/mods/half-life-echoes\n\n## Half-Life: Expanded Arsenal\nAvailable on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal\n\n## Half-Life: Gravgun mod\nBranch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun\n\n## Half-Life: Invasion\nOfficial github repository - https://github.com/jlecorre/hlinvasion\n\n## Half-Life: Pong\nMirrored on github - https://github.com/solidi/hl-mods/tree/master/pong\n\n## Half-Life: Quest Mode\nAvailable on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030\n\n## Half-Life: Top-Down\nOfficial gitlab repository - https://gitlab.com/Sockman/hltopdown\n\n## Half-Life: Update\nOfficial github repository - https://github.com/Fograin/hl-subsmod-ex\n\n## Half-Life: Rally\nOfficial gitlab repository - https://gitlab.com/hlrally/src\n\n## Half-Life: Weapon Edition\nAvailable on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src\n\n## Half-Life: Year of the Dragon\nAvailable on ModDB - https://www.moddb.com/mods/year-of-the-dragon\n\n## Half-Nuked\nAvailable on ModDB - https://www.moddb.com/mods/half-nuked/downloads/half-nuked-src\n\n## Half-Payne\nOfficial github repository - https://github.com/suXinjke/HalfPayne\n\n## Half-Quake\nOfficial github repository - https://github.com/muddasheep/hqtrilogy\n\n## Half-Rats: Parasomnia\nOfficial github repository - https://github.com/HeathGames/half_rats_parasomnia_src\n\n## Half-Screwed\nOfficial github repository - https://github.com/desukuran/half-screwed\n\n## Headcrab Frenzy\nVersion 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code\n\n## Heart of Evil\nMirrored on github - https://github.com/nekonomicon/HeartOfEvil\n\n## Ingram Chillin' Mod\nOfficial SourceForge repository - https://sourceforge.net/projects/icm-hl/\n\n## Master Sword\nOfficial github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic\n\nOfficial github repository of Master Sword Rebirth - https://github.com/MSRevive/MasterSwordRebirth\n\n## MechMod\nOfficial github repository - https://github.com/vermagav/mechmod\n\n## Mortal Combat Forever\nAvailable on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Mortal_Combat_Forever_(Polnie_ishodniki)\n\n## Natural Selection\nOfficial github repository - https://github.com/unknownworlds/NS\n\n## Overturn\nAvailable in mod archive on ModDB - https://www.moddb.com/mods/overturn\n\n## Oz Deathmatch\nMirrored on github - https://github.com/nekonomicon/OZDM\n\n## Paranoia\nAvailable on ModDB - https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit\n\n## Paranoia 2: The Savior\nPrealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient\n\nVersion 1.51, mirrored on github - https://github.com/a1batross/Paranoia2_original\n\n## Raven City\n*Unfinished mod*\n\nWas found on HLFX - http://hlfx.ru/forum/showthread.php?s=2c892dfc52f72be52a89c3f52a397cd0&threadid=1819&postid=44674#post44674\n\n## Ricochet\nAvailable in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/ricochet\n\n## Spirit of Half-Life\n[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life\n\n## The Wastes\nVersion 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk\n\n## Threewave CTF\n*Unfinished mod by Valve Software*\n\nAvailable in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc\n\n## Trinity Render\nAvailable on ModDB - https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f\n\n## Tyrian: Ground Assault\nAvailable on ModDB - https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src\n\n## Wasteland\nMirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wasteland-sdk\n\n## Wizard Wars\nDownload page on official site - http://www.thothie.com/ww/\n\n## XashXT\nMirrored on github - https://github.com/a1batross/XashXT_original\n\n## Xen-Warrior\n*Source code is a part of Spirit of Half-Life 1.0-1.2 under XENWARRIOR macro*\n\n[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life\n\n## Zombie-X\nAvailable in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version\n\n## ZXC\nAvailable on ModDB - https://www.moddb.com/mods/zxc-mod-133/downloads/zxc-mod-136-final\n\nMirrored on github - https://github.com/ZXCmod/ZXCmod\n\n# Reimplementation\n## Absolute Redemption\nBranch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/redempt\n\n## Adrenaline Gamer\nOpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG\n\n## Afraid of Monsters\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-aom\n\nBranch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom\n\n## Afraid of Monsters: Director's cut\nReverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc\n\n## Azure Sheep\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-asheep\n\nReverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep\n\n## Big Lolly\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-biglolly\n\nBranch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly\n\n## Black Ops\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-blackops\n\nBranch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops\n\n## Bloody Pizza: Vendetta\nBranch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed\n\n## Case Closed\nBranch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed\n\n## Cleaner's Adventures\nBranch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd\n\n## Counter Strike\nReverse-engineered code of client part by a1batross - https://github.com/Velaron/cs16-client\n\nReverse-engineered code of server part by nagist - https://github.com/nagist/cs16nd\n\nReverse-engineered code of server part by s1lentq - https://github.com/s1lentq/ReGameDLL_CS\n\n## Counter Strike Online\nCSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE\n\n## Crack-Life\nReverse-engineered code: branch **cracklife** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/cracklife\n\n## Crack-Life: Campaign Mode\nRecreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life\n\nReverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign\n\n## Escape from the Darkness\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-eftd\n\nReverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd\n\n## Half-Life: Black Guard\n*This mod uses dlls from Cleaner's Adventures*\n\nBranch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd\n\n## Half-Life: Blue Shift\nUnkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253\n\nReverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift\n\n## Half-Life: Induction\nBranch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction\n\n## Half-Life: Opposing Force\nRecreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d\n\nReverse-engineered code: clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor\n\nSpirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition\n\n## Half-Life: Rebellion\nReverse-engineered code: branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion\n\n## Half-Life: Urbicide\nBranch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide\n\n## Half-Life: Visitors\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-visitors\n\nReverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors\n\n## Half-Secret\nBranch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret\n\n## Night at the Office\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-nato\n\nBranch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice\n\n## Poke 646\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646\n\nReverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646\n\n## Poke 646: Vendetta\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646-vendetta\n\nReverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta\n\n## Residual Life\nReverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point\n\n## Residual Point\nReverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point\n\n## Sewer Beta\nBranch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta\n\n## Team Fortress Classic\nReverse-engineered code by Velaron - https://github.com/Velaron/tf15-client\n\n## The Gate\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-thegate\n\nReverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate\n\n## They Hunger\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-theyhunger\n\nReverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger\n\nThey Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation\n\n## Times of Troubles\nmalortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-tot\n\nBranch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot\n\n# Derived work\n## Adrenaline Gamer\nBranch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aghl\n\n## Bubblemod\nBranch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod\n\n## Deathmatch Classic\nDeathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc\n\nDeathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq2\n\nBranch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc\n\n## Half-Life: Echoes\nBranch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes\n\n## Half-Life: Invasion\nPort to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion\n\nPort to Linux by fmoraw - https://github.com/fmoraw/hlinvasion\n\nBranch **invasion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/invasion\n\n## Half-Life: Top-Down\nBranch **hltopdown** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hltopdown\n\n## Half-Screwed\nBranch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-screwed\n\n## Natural Selection\nPort to Linux - https://github.com/fmoraw/NS\n\n## Spirit of Half-Life\nVersion 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2\n\n## Swiss Cheese Halloween 2002\nJust more playable version by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-shall\n\nBranch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween\n\n## The Wastes\nVersion 1.5: Port to Linux - https://git.mentality.rip/a1batross/halflife-thewastes-sdk\n\n## Threewave CTF\nBranch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc\n\n## Xen-Warrior\nBranch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2\n\n## Zombie-X\nBranch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x\n\n"
  },
  {
    "path": "Documentation/ports.md",
    "content": "Xash3D FWGS is intended to be easily portable for various platforms, however main issue is maintaining such ports. \n\nThis page is about merged ports to main source tree and responsible for it developers.\n\nFor porting guidelines, read engine-porting-guide.md.\n\nStatus: \n* **Supported**: active, confirmed to be fully functional, gets built on CI.\n* **Orphaned**: some work was done but not finished or actively tested due to lack of human resources.\n* **In progress**: active, under development.\n* **Old Engine**: port was for old engine fork.\n* **Deprecated**: not supported anymore.\n\nTable is sorted by status and platform.\n\n| Platform        | Status                     | Maintainer               | Note\n| --------        | ------                     | ----------               | ----\n| Android         | Supported                  | @Velaron                 |\n| *BSD            | Supported                  | @nekonomicon             |\n| GNU/Linux       | Supported                  | @a1batross, @mittorn     |\n| macOS           | Supported                  | @sofakng                 | \n| PSVita          | Supported                  | @fgsfdsfgs               |\n| Switch          | Supported                  | @fgsfdsfgs               |\n| Windows         | Supported                  | @a1batross, @SNMetamorph |\n| DOS4GW          | Orphaned                   | N/A                      | Haven't been confirmed to work for a very long time\n| Haiku           | Orphaned                   | N/A                      | Was added by #478 and #483\n| IRIX            | Orphaned                   | N/A                      | Undone, compiles but requires big endian port\n| MotoMAGX        | Orphaned                   | N/A                      | Should work but the compiler used for this platform is very unstable and easy to crash (it's GCC 3.4)\n| SerenityOS      | Orphaned                   | N/A                      | Works but not throughly tested\n| Solaris         | Orphaned                   | N/A                      | Works but not throughly tested\n| WebAssembly System Interface | Orphaned      | N/A                      | Undone, WASI is missing a lot of APIs we want to use\n| Dreamcast       | In progress                | @maximqaxd               | [GitHub Repository](https://github.com/maximqaxd/xash3d-fwgs_dc/)\n| PSP             | In progress                | @Crow_bar, @Velaron      | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs)\n| Wii             | In progress                | Collaborative effort     | [GitHub Repository](https://github.com/saucesaft/xash3d-wii)\n| Emscripten      | Old Engine                 | N/A                      | \n| 3DS             | Old Engine fork            | N/A                      | [GitHub Repository](https://github.com/masterfeizz/Xash3DS)\n| Oculus Quest    | Old Engine fork            | N/A                      | [GitHub Repository](https://github.com/DrBeef/Lambda1VR)\n| iOS             | Deprecated                 | N/A                      | See GitHub issue #61\n"
  },
  {
    "path": "Documentation/psvita.md",
    "content": "## PlayStation Vita port\n\n### Prerequisites\n1. Make sure your PSVita is [set up to run homebrew applications](https://vita.hacks.guide/).\n2. Install [kubridge](https://github.com/TheOfficialFloW/kubridge/releases/). It is recommended to use kubridge version `0.1`, because other versions aren't tested, we don't know are they suitable or not.\n\n   Worth to notice, we got reports that automatic plugins management app EasyPlugin have issues with installing kubridge plugin, so it's better to install it manually: by copying `kubridge.suprx` to your taiHEN plugins folder (usually `ux0:/tai`, but could be `ur0:/tai`) and add it to your `config.txt`, for example:\n   ```\n   *KERNEL\n   ux0:tai/kubridge.skprx\n   ```\n\n3. Install `libshacccg.suprx` by following [this guide](https://cimmerian.gitbook.io/vita-troubleshooting-guide/shader-compiler/extract-libshacccg.suprx).\n\n### Installation\n1. If you have an old vitaXash3D install, remove it.\n2. Get `xash3d-fwgs-psvita.7z` from the latest [automatic build](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous).\n3. Install `xash.vpk` from the 7z archive onto your PSVita.\n4. Copy the `data` directory from the 7z archive to the root of your PSVita's SD card.\n5. Copy the valve folder and any other mod folders from your Half-Life install to `ux0:/data/xash3d/` (you can use other mountpoints instead of `ux0`). **Do not overwrite anything.**\n\n### Build instructions\n1. Install [VitaSDK](https://vitasdk.org/).\n2. Build and install [vitaGL](https://github.com/Rinnegatamante/vitaGL):\n    ```\n    git clone https://github.com/Rinnegatamante/vitaGL.git\n    make -C vitaGL NO_TEX_COMBINER=1 HAVE_UNFLIPPED_FBOS=1 HAVE_PTHREAD=1 SINGLE_THREADED_GC=1 MATH_SPEEDHACK=1 DRAW_SPEEDHACK=1 HAVE_CUSTOM_HEAP=1 -j2 install\n    ```\n3. Build and install [vita-rtld](https://github.com/fgsfdsfgs/vita-rtld):\n    ```\n    git clone https://github.com/fgsfdsfgs/vita-rtld.git && cd vita-rtld\n    mkdir build && cd build\n    cmake -DCMAKE_BUILD_TYPE=Release ..\n    make -j2 install\n    ```\n4. Build and install [this SDL2 fork](https://github.com/Northfear/SDL) with vitaGL integration:\n    ```\n    git clone https://github.com/Northfear/SDL.git && cd SDL\n    mkdir build && cd build\n    cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON ..\n    make -j2 install\n    ```\n5. Use `waf`:\n    ```\n    ./waf configure -T release --psvita\n    ./waf build\n    ```\n6. Copy all the resulting `.so` files into a single folder:\n    ```\n    ./waf install --destdir=xash3d\n    ```\n7. `xash.vpk` is located in `build/engine/`.\n"
  },
  {
    "path": "Documentation/supported-mod-list.md",
    "content": "- [List of mods which work on Linux and other non-Windows platforms without troubles](#list-of-mods-which-work-on-linux-and-other-non-windows-platforms-without-troubles)\n   - [List of Half-life-based mods](#list-of-half-life-based-mods)\n        - [The big singleplayer mods and mappacks](#the-big-singleplayer-mods-and-mappacks)\n        - [Unfinished singleplayer mods and mappacks](#unfinished-singleplayer-mods-and-mappacks)\n        - [Just mappacks](#just-mappacks)\n        - [Singleplayer mods or mappacks with a little incompatibilities](#singleplayer-mods-or-mappacks-with-a-little-incompatibilities)\n        - [Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries](#singleplayer-mods-which-has-custom-gamedll-with-minor-changes-and-fully-playable-with-vanilla-half-life-libraries)\n        - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps)\n   - [List of mappacks for Half-Life: Blue Shift](#list-of-mappacks-for-half-life-blue-shift)\n   - [List of Cleaner's Adventures-based mods](#list-of-cleaners-adventures-based-mods)\n   - [List of mods which based on Spirit of Half-Life](#list-of-mods-which-based-on-spirit-of-half-life)\n        - [Mods which based on vanilla Spirit of Half-Life 1.2 or older](#mods-which-based-on-vanilla-spirit-of-half-life-12-or-olderfully-playable-with-sohl-12-libraries)\n        - [Mods which based on modified Spirit of Half-Life 1.2 or older](#mods-which-based-on-modified-spirit-of-half-life-12-or-olderpartially-playable-with-sohl-12-libraries-or-not-playable-at-all)\n        - [Mods which based on Spirit of Half-Life 1.4/1.5/1.8](#mods-which-based-on-spirit-of-half-life-141518fully-playable-with-sohl-18-libraries-partially-playable-with-sohl-12-libraries-or-not-playable-at-all)\n        - [Mods which based on Spirit of Half-Life 1.3/1.6/1.7/1.9](#mods-which-based-on-spirit-of-half-life-13161719fully-playable-with-sohl-19-libraries-partially-playable-with-sohl-12-libraries-or-not-playable-at-all)\n   - [List of Opposing Force-based mods](#list-of-opposing-force-based-mods)\n        - [The big singleplayer mods or mappacks](#the-big-singleplayer-mods-or-mappacks)\n        - [Unfinished mods](#unfinished-mods)\n        - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps-1)\n   - [List of XashXT-based mods](#list-of-xashxt-based-mods)\n   - [List of They Hunger-based mods](#list-of-they-hunger-based-mods)\n        - [The big singleplayers mods](#the-big-singleplayers-mods)\n        - [Single maps](#single-maps)\n   - [List of supported mods from mobile_hacks branch of HLSDK Portable by FWGS](#list-of-supported-mods-from-mobile_hacks-branch-of-hlsdk-portable-by-fwgs)\n   - [List of games and mods with custom gamedll](#list-of-games-and-mods-with-custom-gamedll)\n   - [List of Counter Strike-based mods](#list-of-counter-strike-based-mods)\n\n# List of mods which work on Linux and other non-Windows platforms without troubles\nMost universal way to install and run mods - place your mod folder beside valve folder and launch engine with command-line arguments `-game <modfolder>`\n\nAlso, on some platforms you can see \"Custom Game\" menu in game where you can choose and run any installed mod.\n\nFor mappacks - place *.bsp files to **valve/custom/maps** folder, *.wad files to **valve/custom** and type **`map <mapname>`** cmd in game.\n\n## List of Half-Life-based mods\nOriginally this list was written by @Qwertyus3D (*Qortez*)\n\n### The big singleplayer mods and mappacks\n1. [101 grunts](https://www.runthinkshootlive.com/posts/101-grunts/)\n2. [1986(Black Mesa Circa 1979,1986)](https://www.fileplanet.com/110726/110000/fileinfo/Black-Mesa-Circa-1979,-1986)\n3. [2009EB](https://hldm.ucoz.ru/forum/5-185-1)\n4. [A Day in the Life of a Coward](https://www.runthinkshootlive.com/posts/a-day-in-the-life-of-a-coward/)\n5. **[A dream](https://gamebanana.com/maps/162091)** aka [Map a Dream](https://twhl.info/vault.php?map=5529) by *Skals* (set **fps_max** to **60** to prevent a possible sticking of the box in the elevator at the map **comp28m2**)\n6. [Adam](https://www.moddb.com/mods/adam)\n7. **[Affliction](https://www.moddb.com/mods/affliction)**\n9. [Agamemnon Icarus Glass](https://twhl.info/vault.php?map=1978)\n10. **[Aggregate Pain](https://www.runthinkshootlive.com/posts/aggregate-pain/)**\n11. **[Alternative Way: Part 1](https://www.runthinkshootlive.com/posts/alternative-way-part-1/)**\n12. [Assassin Mark 2](https://www.fileplanet.com/112020/110000/fileinfo/Assassin-Mark-2)\n13. [Assault on Roswell v2.0](https://www.moddb.com/mods/assault-on-roswell)\n14. **[Back to Xen](https://www.runthinkshootlive.com/posts/back-to-xen/)**\n15. **[Back to Xen 2](https://www.runthinkshootlive.com/posts/back-to-xen-2/)**\n16. [Be Careful!](https://www.fileplanet.com/110304/110000/fileinfo/Be-Careful!)\n17. **[Beginnings](https://www.runthinkshootlive.com/posts/beginnings/)**\n18. [Betrayal](https://www.moddb.com/mods/betrayal) (this mod has inner bugs)\n19. **[Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/)**\n20. [Black Mesa Energy Testing Chamber](https://twhl.info/competitions.php?results=17)\n21. **[Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline)**\n22. [BlackMesa 2007( Black Mesa Missions 2007 )](https://www.moddb.com/addons/blackmesa-2007)\n23. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/)\n24. [Boom (Huknenn) v1.0](https://gamebanana.com/mods/35976) & [HL Boom: Gold Edition v1.1](https://www.moddb.com/mods/boom)\n25. [BoomeNShtein3D: Episode 1](https://www.moddb.com/games/half-life/addons/boomenshtein3d-episode-1)\n26. **[Brave Brain](https://www.moddb.com/mods/brave-brain)**\n27. [Breakdown](https://www.runthinkshootlive.com/posts/breakdown-1/)\n28. [Breakdown 2: Afterwards](https://www.runthinkshootlive.com/posts/breakdown-2-afterwards/)\n29. [Buddhist Wars](https://www.moddb.com/mods/buddhist-wars)\n30. **[C&C Tiberian Dawn v2.0](https://www.moddb.com/mods/half-life-cc-tiberian-dawn)**\n31. [Castle Creep](https://www.runthinkshootlive.com/posts/castle-creep/) (there is an inner bug in the end of 4th map - don't go further of that point where a bomb is placed, otherwise level change will not work)\n32. [Castle Disposed](https://www.moddb.com/mods/castle-disposed)\n33. **[Chaos Theory](https://www.runthinkshootlive.com/posts/chaos-theory/)**\n34. [Chernobyl](https://www.moddb.com/downloads/half-life3) (this mod has inner bugs; in the beginning you have to use **noclip**, to fix a sticking in the bus)\n35. [ChickenMix](https://twhl.info/vault.php?map=3118)\n36. [ChickenMix 2](https://twhl.info/vault.php?map=3434) (there are a couple of minor bugs, but they are inner bugs of the mod)\n37. [Confronted with Consequences](https://www.moddb.com/mods/confronted-with-consequences) & [Confronted with Consequences 2016 Remake](https://www.moddb.com/mods/confronted-with-consequences-2016-remake)\n38. [Construction](https://www.runthinkshootlive.com/posts/construction/)\n39. [Conundrum](https://www.runthinkshootlive.com/posts/conundrum/)\n40. [Conundrum 2](https://www.runthinkshootlive.com/posts/conundrum-2/)\n41. [Crash](https://www.runthinkshootlive.com/posts/crash/) (there is a small issue with a lift in a final map - you should stay closely to the lift's button after you've pressed it, otherwise door of the lift won't open)\n42. [CWC Board Mappack Initiative](https://www.moddb.com/mods/cwc-pack-initiative) (the mod has inner bugs)\n43. **[DAV Sub](https://www.moddb.com/mods/davsub)**\n44. **[DAV Train](https://www.moddb.com/mods/davtrain)**\n45. [death = power](https://www.fileplanet.com/53953/50000/fileinfo/Death=power)\n46. [Death in the Dark](https://www.moddb.com/games/half-life/addons/death-in-the-dark) (there are some minor inner bugs, but they don't interfere with the mod's progress)\n47. [DejaVu v2.00](https://www.moddb.com/mods/dejavu)\n48. **[Destination Black Mesa](https://www.moddb.com/mods/destination-black-mesa)** (there is a small inner problem that can randomly appear in the mod's ending - the last map with credits may not work properly; type restart command into console if you get this issue)\n49. [Destiny](https://twhl.info/competitions.php?results=15)\n50. [Deth](https://www.runthinkshootlive.com/posts/deth/)\n51. [Do](https://www.runthinkshootlive.com/posts/do/)\n52. [Doomed-life](https://www.moddb.com/mods/doomed-life)\n53. [Down Time](https://www.runthinkshootlive.com/posts/down-time/)\n54. [Drug Barons](https://www.runthinkshootlive.com/posts/drug-barons/)\n55. [Dungeon Death](http://darktreemedia.com/dungeondeath/)\n56. [Dust Runner v1.1](https://www.runthinkshootlive.com/posts/dust-runner/)\n57. [Dwell](https://www.runthinkshootlive.com/posts/dwell/)\n58. **[E. T. C. ( Earthquake Testing Centre )](https://www.moddb.com/mods/etc2)**\n59. **[E. T. F.](https://www.moddb.com/mods/e-t-f)**\n60. **[E7: Black Star](https://www.moddb.com/mods/e7-black-star)** (set **fps_max** to **50** for the map an_ele to prevent a possible sticking of boxes on a big elevator's platform; or you can try to push a box down from the platrofm before the elevator stops)\n61. **[Edge of Darkness](https://www.moddb.com/mods/hl-edge-of-darkness)**\n62. [Edge of Death - Series 1](https://twhl.info/vault.php?map=5088)\n63. [Engineering Mankind](https://twhl.info/competitions.php?results=13)\n64. [Episode Power Plant and China](https://www.artpeter.net/Data/HalfLife/Hl.php)\n65. [Episode Secret Weapon](https://www.artpeter.net/Data/HalfLife/Hl.php) (you will face a serious inner bug at the second map if you try to play this mod on hard difficulty)\n66. **[Escape](https://www.runthinkshootlive.com/posts/escape-by-leo-ring/)** by *Leo Ring*\n67. [Escape from the Egyptian Tomb( RacerX Compo 12 Egyptian Museum )](https://twhl.info/vault.php?map=2368)\n68. [Espionage](https://www.runthinkshootlive.com/posts/espionage/)\n69. [Exodus](https://www.moddb.com/games/half-life/addons/exodus-1)\n70. [Exodus 2](https://www.moddb.com/games/half-life/addons/exodus-2)\n71. [Failure](https://www.moddb.com/games/half-life/addons/failure)\n72. [Faraon](https://www.moddb.com/games/half-life/addons/faraon-1-4)(it's a set of 4 maps which can be combined in one singleplayer episode: [map 1](http://www.fileplanet.com/50963/50000/fileinfo/Fara%C3%B3n-1), [map 2](http://www.fileplanet.com/50459/50000/fileinfo/Faraon2), [maps 3-4](http://www.fileplanet.com/51799/50000/fileinfo/Faraon-3-4))\n73. **[Fate Reversal v1.1](https://www.moddb.com/mods/fate-reversal)**\n74. **[Fathom 2.4](https://twhl.info/vault.php?map=4759)**\n75. [Freeman](https://www.runthinkshootlive.com/posts/freeman/)\n76. [Freeman's Escape](https://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*)\n77. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/)\n78. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12)\n79. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11)\n80. **[Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/)**\n81. [Freeman's Tomb( The Crypt )](https://twhl.info/vault.php?map=3787)\n82. **[G-Invasion v2.6](https://www.moddb.com/mods/g-man-invasion)** aka G-Man Invasion\n83. [G-Man House](https://www.moddb.com/games/half-life/addons/g-man-house)\n84. [GameStar( Episode 4 GameStar )](https://www.runthinkshootlive.com/posts/gamestar/)\n85. [Gateway](https://www.moddb.com/mods/gateway)\n86. [Gateway 2](https://www.moddb.com/mods/gateway-2)\n87. [Gina's Adventures](https://www.moddb.com/mods/ginas-adventures)\n88. **[Gman Island Part 1](https://www.runthinkshootlive.com/posts/gman-island-part-1/)**\n89. **[Gman Island Part 2](https://www.runthinkshootlive.com/posts/gman-island-part-2/)**\n90. **[Graber v1.1](https://www.moddb.com/mods/graber-half-life-mod)**\n91. [Graf War](https://www.runthinkshootlive.com/posts/graf-war/)\n92. **[Ground Zero](https://www.moddb.com/mods/half-life-ground-zero)** by *Derek 'Hellfire' McBurney*\n93. **[Gut Reaction](https://www.moddb.com/mods/gut-reaction)**\n94. **[Half-Life C.A.G.E.D.](https://store.steampowered.com/app/679990/HalfLife_Caged/)**\n95. **[Half-Life: Dreamcast v1.1](https://www.moddb.com/mods/half-life-dreamcast)**\n96. [Half-Life: OPS](https://www.runthinkshootlive.com/posts/half-life-ops/) \n97. **[Half-Quake](https://www.moddb.com/mods/halfquake-amen)**\n98. **[Half-Quake 2: Amen](https://www.moddb.com/mods/halfquake-amen)**\n99. [Hard 2](https://www.runthinkshootlive.com/posts/hard-2/) (set **fps_max** to **60** to prevent a possible sticking of Barney on an elevator in the beginning of the mod)\n100. [Hardman - In the City](https://www.runthinkshootlive.com/posts/hardman-in-the-city/) (the updated version of the mod has been tested)\n101. [Haunted](https://www.runthinkshootlive.com/posts/haunted/) (set **sv_validate_changelevel** to **0** to avoid a problem with level change between maps **pagan7** and **pagan8**)\n102. [Hazardous Materials: Episode 1 & 2](https://www.moddb.com/mods/half-life-hazardous-materials)\n103. **[Hazardous-Course 2](https://www.moddb.com/mods/hazardous-course-2)**\n104. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted)\n105. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/)\n106. [High Speed](https://www.moddb.com/games/half-life/addons/high-speed-english-version)\n107. **[hlife_hotdog_compo26](https://twhl.info/vault.php?map=5181)**\n108. [Home Alone -NOT](https://www.fileplanet.com/49895/40000/fileinfo/Home-Alone--NOT)\n109. **[Hour-Glass](https://www.moddb.com/mods/hour-glass)**\n110. [Idol Hunt](https://www.runthinkshootlive.com/posts/idol-hunt/)\n111. [Independence Day](https://www.runthinkshootlive.com/posts/independence-day/) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions)\n112. [Infestation](https://www.runthinkshootlive.com/posts/half-life-infestation/)\n113. [Infiltracja](https://unquenque.com/tenfour/)\n114. **[Infinite Rift](https://www.moddb.com/mods/infinite-rift)**\n115. **[Instinct](https://www.moddb.com/mods/instinct)**\n116. [Insurrection](https://www.runthinkshootlive.com/posts/insurrection/)\n117. [Irreality](https://www.moddb.com/mods/irreality)\n118. [Ispitatel](https://www.moddb.com/mods/ispitatel)\n119. [Jailbreak](https://www.runthinkshootlive.com/posts/jailbreak/)\n120. [Kill All Greenpeace](https://www.moddb.com/mods/kill-all-greenpeace)\n121. [Krypton](https://www.runthinkshootlive.com/posts/krypton/) (there is one inner bug on the map with a rocket pad - the rocket can get stuck when you try to launch it)\n122. [Lands of Lore v2](https://www.moddb.com/mods/half-life-lands-of-lore)\n123. [Last-life](https://www.moddb.com/mods/hl-last-life)\n124. **[Life's End](https://www.moddb.com/mods/lifes-end2)**\n125. [Macky's Adventure](https://www.moddb.com/mods/mackys-adventure-expansion-set)\n126. [MadCrabs](https://www.runthinkshootlive.com/posts/mad-crabs-1/) (there are couple of potential scripting problems, but they seem to be inner problems of the mod, so if you stuck somewhere just try to replay your game from last autosave)\n127. [Marine Invasion: Episode 1 & 2](https://gamebanana.com/gamefiles/2537)\n128. **[Mario Keys](https://www.moddb.com/mods/mario-keys)**\n129. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/)\n130. [Medieval World](https://www.moddb.com/mods/medieval-world)\n131. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor)\n132. **[MINIMICUS](https://twhl.info/vault.php?map=163)**\n133. [Misantrophy](https://www.runthinkshootlive.com/posts/misanthropy/)\n134. **[Mission Failed](https://www.moddb.com/mods/mission-failed)**\n135. [Mission MC Poker](https://www.moddb.com/mods/half-life-mission-mc-poker)\n136. **[Mission of Mercy](https://www.runthinkshootlive.com/posts/mission-of-mercy/)**\n137. [Mission to Kill](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm)\n138. [Mission to Kill 2](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm)\n139. [Mistaken Identity](https://www.runthinkshootlive.com/posts/mistaken-identity/)\n140. [Mistaken Identity 2](https://www.runthinkshootlive.com/posts/mistaken-identity-part-2/)\n141. **[Moonwalker](https://www.moddb.com/mods/moonwalker)**\n142. [Mystery House v1.0](https://www.moddb.com/mods/mystery-house)\n143. [NewBlackMesaVille](https://www.runthinkshootlive.com/posts/newblackmesaville/)\n144. [Night Shooting](https://www.moddb.com/games/half-life/addons/night-shooting)\n145. **[No Exit](https://www.moddb.com/mods/no-exit)**\n146. [No-Life](https://www.fileplanet.com/120501/120000/fileinfo/No-Life)\n147. **[Nuclear Power Plant](https://www.runthinkshootlive.com/posts/nuclear-power-plant/)**\n148. [Occupied Territory](https://www.moddb.com/games/half-life/addons/occupied-territory)\n149. [Office Commando](https://www.runthinkshootlive.com/posts/office-commando/)\n150. **[Operacja Gargantua](https://www.runthinkshootlive.com/posts/operation-gargantua/)**\n151. **[Operacja Mirra](https://unquenque.com/tenfour/)**\n152. **[Operation Krautsalat v1.1](https://www.runthinkshootlive.com/posts/operation-krautsalat/)** (there is a potential inner bug at third map - a door to the control room will not be opened, until all alien grunts leaved their cells, so don't kill any of them, which are still in cells)\n153. **[Operation: Nova](https://www.moddb.com/mods/half-life-operation-nova)** (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it)\n154. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear)\n155. [Outrun](https://www.moddb.com/mods/outrun)\n156. [Outwards (Day One)](https://web.archive.org/web/20161026104825/http://visgi.info/maps/)\n157. [Overhaul Pack](https://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life)\n158. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/)\n159. [pagoda](https://www.moddb.com/mods/pagoda1)\n160. **[Peaces Like Us v1.0](https://www.runthinkshootlive.com/posts/peaces-like-us/)**\n161. [Phobos IV](https://www.moddb.com/mods/phobos-iv)\n162. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks)\n163. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/)\n164. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2)\n165. **[Prisoner of Event](https://wp.vondur.net/?page_id=40)**\n166. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/)\n167. [Project Quantum Leap](https://www.moddb.com/mods/project-quantum-leap)\n168. [Project VIP](https://www.pcosmos.ca/mods-hl/downloads/?mod=projectVIP)\n169. **[Projekt Einstein](https://unquenque.com/tenfour/)**\n170. [Recon( Reconnaissance )](https://www.moddb.com/games/half-life/addons/reconnaissance)\n171. [Red Mesa](https://www.moddb.com/mods/red-mesa-1)\n172. [Red Mesa 2](https://www.moddb.com/mods/red-mesa-2)\n173. [RES v1.0](https://www.fileplanet.com/29002/20000/fileinfo/RES)\n174. [Rescue 9-1-Freeman](https://www.moddb.com/mods/rescue-9-1-freeman)\n175. [Resistance](https://www.runthinkshootlive.com/posts/resistance/)\n176. [Resistance 2](https://www.runthinkshootlive.com/posts/resistance-2/)\n177. [Resistance 3](https://www.runthinkshootlive.com/posts/resistance-3/)\n178. [Return](https://www.runthinkshootlive.com/posts/return/)\n179. [Return 2](https://www.runthinkshootlive.com/posts/half-life-return-2/) (there is an inner problem in the first map with a moving of the second metal box, just keep pushing the box, until it begins to move)\n180. [Reviviscence](https://www.moddb.com/mods/reviviscence)\n181. [Road of Destiny](https://www.fileplanet.com/10393/10000/fileinfo/Road-of-Destiny)\n182. **[Rooms: TWHL COOP Project( Rooms Half-Life or TWHL Rooms )](https://twhl.info/vault.php?map=5394)**\n183. [Run for Life](https://www.runthinkshootlive.com/posts/run-for-life/) (the \"stolen\" version of this mod with changed menu background also known as *Mad Escape*)\n184. [S.W.A.T.](https://www.moddb.com/mods/swat2)\n185. [Sabotage](https://www.runthinkshootlive.com/posts/sabotage-2/)\n186. [Sagharmath](https://www.moddb.com/games/half-life/addons/sagharmath) \n187. [Sandscroll](https://gamebanana.com/maps/15988)\n188. [Saving Bob](https://www.runthinkshootlive.com/posts/saving-bob/)\n189. [Secret Base](https://www.runthinkshootlive.com/posts/secret-base/)\n190. **[Secret Santa 2011 for Soup Miner](https://twhl.info/vault.php?map=5724)**\n191. [Secret Santa 2011 for Stojke](https://twhl.info/vault.php?map=5728)\n192. [Senses in Decay](https://www.moddb.com/mods/senses-in-decay)\n193. [Shift-Two v1.1](https://www.moddb.com/mods/shift-two1)\n194. [Sky Mesa](https://www.moddb.com/mods/sky-mesa)\n195. [Sleephorst v1.5](https://www.moddb.com/mods/sleephorst-v15)\n196. [Slimy Situation](https://www.moddb.com/mods/slimy-situation)\n197. **[Smart Decoy](https://www.moddb.com/mods/smart-decoy)**\n198. **[Somewhere in Time](https://www.runthinkshootlive.com/posts/somewhere-in-time/)**\n199. [Sooper2](https://www.runthinkshootlive.com/posts/sooper-2/) \n200. **[Split-Second](https://twhl.info/vault.php?map=4343)**\n201. **[Subhumanity](https://www.runthinkshootlive.com/posts/sub-humanity/)**\n202. Terminal Monstrosity (4 maps by Anthony Plant)\n203. [Terror Side](https://www.runthinkshootlive.com/posts/terror-side/) (set **sv_validate_changelevel** to **0** to avoid problem with level change between some maps)\n204. [Test Your Skill](https://www.moddb.com/games/half-life/addons/test-your-skill) \n205. [The Challenger Deep](https://www.runthinkshootlive.com/posts/challenger-deep/)\n206. **[The Challenger Deep 2](https://www.moddb.com/mods/the-challenger-deep-2)**\n207. [The Evil Thing](https://www.moddb.com/mods/the-evil-thing)\n208. [The Evil World](https://www.moddb.com/mods/the-evil-world)\n209. [The Haunted Lab](https://www.runthinkshootlive.com/posts/the-haunted-lab/)\n210. **[The Long Night](https://www.moddb.com/mods/the-long-night)**\n211. [The Mansion](https://twhl.info/vault.php?map=2438)\n212. [The Night Things](https://twhl.info/vault.php?map=2439)\n213. [The Night Things 2: Woodstock Manor](https://twhl.info/vault.php?map=2706)\n214. [The Returning](https://www.moddb.com/games/half-life/addons/the-returning)\n215. [The Ropes](https://www.runthinkshootlive.com/posts/the-ropes/) (set **fps_max** to **60** to prevent a possible sticking of a barrel which you need to lift down with an elevator at the second map)\n216. [The Way Is Clear](https://www.runthinkshootlive.com/posts/the-way-is-clear/)\n217. [The Way Is Clear 2](https://www.runthinkshootlive.com/posts/the-way-is-clear-2/)\n218. **[The World Machine](https://www.moddb.com/mods/half-life-the-world-machine)**\n219. **[The Xeno Project](https://www.moddb.com/mods/xeno-project-i-ii)**\n220. **[The Xeno Project 2](https://www.moddb.com/mods/xeno-project-i-ii)**\n221. [Then and Now](https://twhl.info/vault.php?map=6048)\n222. [They Live( Chungo )](https://www.runthinkshootlive.com/posts/they-live/)\n223. [Timefall](https://www.moddb.com/mods/hl-timefall)\n224. **[Timeline](https://www.moddb.com/mods/timeline-series)**\n225. **[Timeline II: Iced Earth](https://www.moddb.com/mods/timeline-series)**\n226. **[Todesangst](https://www.moddb.com/mods/todesangst)**\n227. **[Tokami Island](https://www.fileplanet.com/110517/110000/fileinfo/Tokami-Island)**\n228. [Tower](https://hlperfectzone.narod.ru/mods.htm) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions)\n229. [Trespasser](https://www.moddb.com/mods/trespasser)\n230. [Try, Try Again](https://www.runthinkshootlive.com/posts/try-try-again/)\n231. [Tucked Up](https://www.moddb.com/games/half-life/addons/tucked-up)\n232. [TWHLmix](https://twhl.info/vault.php?map=2516)\n233. [Two Smoking Barrels](https://www.runthinkshootlive.com/posts/two-smoking-barrels/)\n234. [Typical Disaster](https://www.moddb.com/mods/typical-disaster)\n235. [Typical Disaster: The Lost Levels](https://www.runthinkshootlive.com/posts/typical-disaster-the-lost-levels/)\n236. [U-Life](https://www.moddb.com/mods/u-life)\n237. **[Underground v2.0](https://www.moddb.com/mods/underground)** (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed)\n238. [Underground Territory](https://www.moddb.com/games/half-life/addons/underground-territory-part-1)\n239. [Undertime](https://twhl.info/vault.php?map=3906)\n240. **[Uplink Addon v1.2](https://www.fileplanet.com/82828/80000/fileinfo/Half-Life-Uplink-Addon-1.2)**\n241. **[Uplink Lite](https://www.fileplanet.com/7482/0/fileinfo/Uplink-Lite)** & **[Uplink Lite Plus Demo](https://www.fileplanet.com/128970/120000/fileinfo/Uplink-Lite-Plus-Demo)**\n242. **[Uplinked](https://www.moddb.com/mods/uplinked)**\n243. **[USS Darkstar](https://www.moddb.com/mods/uss-darkstar)**\n244. [Vengeance](https://www.moddb.com/mods/half-life-vengeance)\n245. [Virtual Reality: The Real World](https://www.runthinkshootlive.com/posts/virtual-reality-the-real-world/)\n246. [VLOKAM](https://www.ceskemody.cz/mapy.php?lng=1&clanek=32&razeni_hra=1&pn=vlokam-1)\n247. [VLOKAM 2](https://www.ceskemody.cz/mapy.php?lng=1&clanek=33&razeni_hra=1&pn=vlokam-2)\n248. [Wail of Death](https://www.runthinkshootlive.com/posts/wail-of-death/)\n249. [Wail of Death 2: The Hell Master](https://www.runthinkshootlive.com/posts/wail-of-death-2-the-hell-master/)\n250. [War Crimes v1.2](https://www.moddb.com/games/half-life/addons/war-crimes)\n251. [Way to Victory](https://www.moddb.com/mods/2gn)\n252. [Windmill v2.0](https://www.runthinkshootlive.com/posts/windmill/)\n253. [World War III Missions (parts 1, 2 & 3)](https://www.moddb.com/mods/world-war-iii-missions-1-2-3)\n254. [Worst Holiday](https://www.moddb.com/games/half-life/addons/worst-holiday) (there are 2 problems with doors; a door at second map can be opened if you sit down near it; to pass the door in laboratory you should run quickly as you can from ventilaion shaft - there is an autosave point on a previous map, if you failed)\n255. [Xen](https://www.runthinkshootlive.com/posts/half-life-xen-2/)\n256. [You Are in Army Now](https://www.pcosmos.ca/mods-hl/downloads/?mod=armynow)\n257. [Zubben](https://www.runthinkshootlive.com/posts/zubben/)\n\n### Unfinished singleplayer mods and mappacks\n1. [Alpha Research Facility v0.3](https://www.moddb.com/games/half-life/downloads/alpha-research-facility-v-03-steam)\n2. [Back in Future Alpha version](https://www.runthinkshootlive.com/posts/back-in-future/)\n3. [Biohazard 2 (unfinished mod version)](https://www.moddb.com/mods/biohazard-2-outside-of-black-mesa)\n4. Black Meat (unfinished mappack of 3 maps)\n5. [Bloodreign](https://www.runthinkshootlive.com/posts/blood-reign/) (the mod has inner bugs, especially second map)\n6. [Boreality: Part 1](https://www.runthinkshootlive.com/posts/boreality/) (there is a visual glitch problem on the map **mell04**, but same happens for original Half-Life too)\n7. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/)\n8. [Cro-man's Office Mappack](https://twhl.info/vault.php?map=5547)\n9. Great Escape (unfinished mappack of 2 maps)\n10. [Half-Life Episode Two Demo Alpha v1.0](https://www.moddb.com/games/half-life/downloads/half-life-episode-two-v10-demo)\n11. Half-Life: Marine Demo (by EDOC32)\n12. [Hazardous-Course (first version of Hazardous-Course 2)](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)\n13. [High Tech v0.2](https://hl.loess.ru/?mod=499)\n14. [HL: Paranormal Demo](https://gamebanana.com/mods/157378)\n15. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1)\n16. [HLNewEnd](https://twhl.info/vault.php?map=2275)\n17. [Kleiners Adventures Demo](https://www.moddb.com/mods/kleiners-adventures)\n18. [Kleiners Adventures: The White Line Demo](https://www.moddb.com/mods/kleiners-adventure-siemka321-edition) \n19. [LV-426: Episode 1](https://www.runthinkshootlive.com/posts/lv426-version-a/)\n20. [Malevolence(1.3 and older)](https://www.moddb.com/mods/malevolence)\n21. [Meth-Life Demo v1.0](https://www.gamewatcher.com/mods/half-life-mod/meth-life-mod-demo-1-0)\n22. [Night-Fire Demo](https://efreeman1.narod.ru/Index.htm)\n23. [On the Other Side](https://twhl.info/competitions.php?results=14) aka *OTOS* (there is a train-related bug in the beginning, which makes you to use **noclip**, but it's an inner flaw of the map)\n24. [Orion: Part 1( Project Orion )](https://www.moddb.com/mods/orionold)\n25. [Padle Mesto Demo](https://www.moddb.com/games/half-life/addons/padle-mesto-demo) (some scripts & buttons are not configured properly in this mod, so they can be activated/deactivated automaically after reloading of a saved game)\n26. [Panic at Black Mesa Demo](https://www.moddb.com/mods/half-life-panic-at-black-mesa)\n27. [Plan B v1.1](https://www.fileplanet.com/129198/120000/fileinfo/Plan-B-SP-Mappack)\n28. [Preview of the \"Paradox\"](https://www.runthinkshootlive.com/posts/half-life-preview-of-the-paradox/)\n29. [Project Focus North v1.6 Demo](https://www.fileplanet.com/84637/80000/fileinfo/Project-Focus-North)\n30. [Qortez( Quortez )](https://www.runthinkshootlive.com/posts/qortez/) (it's recommended to use only default low-poly model of scientist for this mod; also set **fps_max** to **60** via console to prevent an issue with one of scripted sequences)\n31. [Return to Lambdacore Demo](https://www.moddb.com/games/half-life/addons/return-to-lamdacore)\n32. [Route City Beta 1](https://www.moddb.com/mods/route-city)\n33. [SHAFT - Part 1](https://hl.loess.ru/?mod=913)\n34. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/)\n35. Stargate Test (3 maps, it's earliest outlines of known Stargate mod)\n36. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka)\n37. [Striker's Compo 26](https://twhl.info/vault.php?map=5190) (buggy)\n38. [Technology Test 2](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)\n39. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo)\n40. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/)\n41. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/)\n42. [Time-Shift](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)\n43. [Train Single Beta](https://csm.dev/threads/half-strike-rezultaty.36394/) (Remove **gfx.wad** from **TrainSingle** folder)\n44. [WAR: The Killer Beta 0.1](https://www.moddb.com/mods/war)\n45. [White Force Beta( Residual Point prototype )](https://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002)\n46. [Your First Mission Demo](https://twhl.info/forums.php?thread=3925)\n47. [Zombieland v1.1](https://www.moddb.com/mods/zombieland)\n\n### Just mappacks\n1. [AvsM 1 v1.2](https://www.moddb.com/mods/avsm-1-2003-version-re-release/) \n2. [Cook The Headcrab Episodes 1 & 2](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-series)\n3. [Cook The Headcrab Episode 3](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-3)\n4. [Cook The Headcrab Episode 4](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-4)\n5. **[DAV HL Pack 1](https://www.moddb.com/games/half-life/addons/dav-level-pack-1-hlarches-and-hlattack)**\n6. [Discoman v2.2](https://www.fileplanet.com/117675/110000/fileinfo/Discoman-Mod)\n7. [Dramatic Measures( Surprise! )](https://twhl.info/vault.php?map=5624)\n8. **[Funny Map Pack 1](https://www.mediafire.com/file/4iis8uhfj1ct8sa/Funny_Map_Pack_1_by_Richman.zip)**\n9. **[Half-Life Shorts](https://twhl.info/vault.php?map=2845)**\n10. [Half-Starwars](https://www.pcosmos.ca/mods-hl/downloads/?mod=hlstarwars)\n11. [Japanese Episodes](https://www.runthinkshootlive.com/posts/japanese-episodes/)\n12. [K7 Trouble](https://www.moddb.com/games/half-life/addons/k7-trouble) (Remove string **secure 1** from **liblist.gam**)\n13. [Night Shift Beta](https://www.runthinkshootlive.com/posts/night-shift/)\n14. [Pillars of Pain: A Buddy & Kona Saga( PoP-BaKs )](https://www.runthinkshootlive.com/posts/pillars-of-pain/)\n15. [Star Wars Half-Life](https://twhl.info/vault.php?map=274) (second map running via console using **map swpart2** cmd)\n16. [The Crabulator v0.1.9.6.1](https://www.fileplanet.com/148679/140000/fileinfo/Half-Life---The-Crabulator-v0.1.9.6.1) (Rename mod folder to **TheCrabulator**)\n17. [The Mansion](https://twhl.info/competitions.php?results=14) by *Unbreakable*\n18. [TS_MF](https://www.runthinkshootlive.com/posts/ts_mf/)\n19. [TWHL Cubicles](https://twhl.info/vault.php?map=5499)\n20. **[Valve ERC Contest #1 - The martyred pop machine](https://www.runthinkshootlive.com/posts/contest-1-maps/)**\n21. **[Valve ERC Contest #2 - Best train-ride sequence](https://www.runthinkshootlive.com/posts/contest-2-maps/)**\n22. [VOLCAN Beta 1.3](https://www.moddb.com/games/half-life/addons/volcan-beta13)\n23. [Woodpigeon's Map Pack](https://www.fileplanet.com/63729/60000/fileinfo/Woodpigeon's-Map-Pack)\n\n### Singleplayer mods or mappacks with a little incompatibilities\n1. [Alternate Path](https://www.runthinkshootlive.com/posts/alternate-path/) (there is an issue with deep sticking in elevator, when you return from map **out3** to map **out2**, so you'll be forced to use **noclip** as solution; also you need to set **sv_validate_changelevel** to **0**, because of unstable level transition between map outside and map **out2**)\n2. [Final Run](https://www.runthinkshootlive.com/posts/final-run/)\n3. [Hard](https://www.runthinkshootlive.com/posts/hard/) (you can get stuck inside boxes in a moving truck on the second map of the mod - type **restart** command in the console to fix your position when the next map is loaded and truck is stopped)\n4. [Pulse Episode One Beta 1](https://www.moddb.com/mods/pulse), [Terrorist Attack](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) (maps of these mods have a \"leak\" bug, it causes level change problems with incorrect transferring entities between maps, disabled lighting and potential significant performance drops after passing of several maps)\n5. [The Hill](https://www.runthinkshootlive.com/posts/the-hill/) (there is a mapper's flaw in changelevel settings between maps **thehill & thesequel**, it can be corrected only manually by editing of entpatches for these maps - you need to rename **\"bottom rung\"** value to **\"bottomrung\"**; after editing you new to start new game)\n6. [Threatening Skies](https://twhl.info/vault.php?map=3372) *Pre-Demo* (aka *Half-Life 1.5*; you can get stuck in a roof of a train on level change between maps **game010c** and **game010d**; use **noclip** or **restart** command to fix this)\n\n### Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries\n1. [Citizen Arms Demo 2](https://www.moddb.com/mods/citizen-arms) (there are few inner bugs in the mod, but it still playable)\n2. [DALEK unbidden](https://www.moddb.com/mods/dalek-unbidden)\n3. [Fight for Life](https://www.moddb.com/mods/fight-for-life)\n4. [Half-Life Baby v1.4](https://www.moddb.com/games/half-life/addons/half-life-baby) (it's an unfinished but playable mod; after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll \"..\\hlbaby\\dlls\\hl.dll\"** for **gamedll \"dlls\\hl.dll\"**, otherwise you'll not be able to start a game)\n5. **[Lost in Black Mesa(first version without HLFX)](https://www.moddb.com/mods/hlfx-lost-in-black-mesa/downloads/lost-in-black-mesa-simple-version)**\n6. [Soldier](https://www.moddb.com/mods/half-life-soldier)\n7. [Solo Operations](https://www.moddb.com/mods/solo-operations)\n8. [The Blood v1.1](https://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress)\n9. [The Escape](https://www.moddb.com/mods/neophus) (there is a couple of strange glitches on a map **evasion7**, but they are not interrupting a gameplay, just don't forget to download and install all of presented fixes for the mod)\n10. [Wilson Chronicles: The Unfinished Edition](https://www.gamewatcher.com/mods/half-life-mod/half-life-wilson-chronicles)\n\n### Singleplayer mods with one map or single maps\n1. [3rd505th](https://hl.loess.ru/?mod=5)\n2. [5 More Ways to Die](https://twhl.info/vault.php?map=3575)\n3. [5 Ways to Die](https://twhl.info/vault.php?map=2984)\n4. [5 Worse Ways to Die](https://twhl.info/vault.php?map=4029)\n5. **[A Bad Day](https://twhl.info/competitions.php?results=6)**\n6. [A Disgruntled Christmas](https://www.fileplanet.com/7882/0/fileinfo/A-Disgruntled-Christmas)\n7. [Accidental Life Demo](https://www.runthinkshootlive.com/posts/accidental-life/)\n8. **[Aftermath](https://www.fileplanet.com/112021/110000/fileinfo/Aftermath)**\n9. [Ali Meyer](https://gamebanana.com/maps/178855)\n10. [Alien](https://hl.loess.ru/?mods=&search=alien) (map by *Nicolas Gadenne*)\n11. [Alien Blast](https://www.fileplanet.com/136194/130000/fileinfo/Alien-Blast)\n12. [Alien Survival](https://www.moddb.com/games/half-life/addons/alien-survival-map)\n13. [American Training Facility](https://gamebanana.com/maps/146598)\n14. [An Escape](https://gamebanana.com/maps/167506)\n15. [Area Assault](https://www.fileplanet.com/7462/0/fileinfo/Area-Assault)\n16. [Assassination](https://www.runthinkshootlive.com/posts/assassination/)\n17. [Atom's Mini Compo by Tetsu0](https://twhl.info/vault.php?map=5983)\n18. [Atom's Mini Compo by zeeba-G](https://twhl.info/vault.php?map=5985)\n19. [B.O.G aka Black.Orange.Grenade](https://twhl.info/vault.php?map=3411) (you need to have additional textures to play this map properly - **opfor.wad** & **tfc.wad**; also game crashes in the final because of inner mapping flaw )\n20. [Barney's Dream Demo( B-dream Beta )](https://www.fileplanet.com/112446/110000/fileinfo/B-dream-Beta)\n21. [Barrel of Grunts](https://twhl.info/vault.php?map=3390)\n22. BDMAP1 (MoP1.bsp - map by unknown author)\n23. [Beach Party](https://twhl.info/vault.php?map=5572)\n24. [Beastie](https://www.runthinkshootlive.com/posts/beastie/)\n25. **[Black Mesa South](https://twhl.info/vault.php?map=5309)** (there are few minor glitches, but they are inner flaws of the map and don't interfere with completing a game)\n26. [Black Mesa Storage Facility - Bay A2](https://www.fileplanet.com/7479/0/fileinfo/Black-Mesa-Storage-Facility---Bay-A2)\n27. [Black Mesa Xmas(A Black Mesa Christmas)](https://twhl.info/vault.php?map=4918)\n28. Blame the Scientists (map by Incy247)\n29. [Blood and Guts](https://www.runthinkshootlive.com/posts/blood-and-guts/)\n30. **[Blood1](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)**\n31. [Bhop](https://twhl.info/vault.php?map=5065)\n32. Bow (map by Daniel Will)\n33. **[Breakout](https://www.runthinkshootlive.com/posts/breakout-by-benny-blanco/)** (map for *Single Mapping Competition* by *BennyBlanco*)\n34. [Breakout](https://hl.loess.ru/?mods=&search=Breakout) (map by *Mediocre MapGuy*)\n35. Bridge (map by Monkey)\n36. [Bullsuid Will Survive](https://www.moddb.com/games/half-life/addons/bullsuid-will-survive)\n37. [C2](https://www.runthinkshootlive.com/posts/c2/)\n38. [C3](https://www.runthinkshootlive.com/posts/c3/)\n39. [C5](https://www.runthinkshootlive.com/posts/c5/)\n40. **[Caged](https://web.archive.org/web/20081226051021/http://www.caylegeorge.com/ld_history/cg_maps.htm)** \n41. [Camera Puzzle](https://twhl.info/vault.php?map=740) (this map perfectly demonstrates an improved feature of Xash3D Engine - game is correctly saving and restoring 3rd person view for the player after loading a previously saved game)\n42. [CataXen](https://twhl.info/vault.php?map=3429)\n43. [Cause of Death](https://twhl.info/competitions.php?results=7)\n44. [Challenge](https://web.archive.org/web/20231010044342/http://snarkpit.net/index.php?s=maps&map=3266) (map by *tnkqwe*)\n45. [CHALLENGE](https://twhl.info/vault.php?map=2229) aka Can You Live (map by *killer487554*; remove spaces in the map name before you play)\n46. [Choices](https://www.runthinkshootlive.com/posts/choices/)\n47. [Chuck - Texas Rangers](https://twhl.info/vault.php?map=4253)\n48. Cleaner's Adventures Begin Demo (map by InvisibleBullet)\n49. [Coach](https://www.runthinkshootlive.com/posts/coach/)\n50. [Cook The Headcrab](https://www.moddb.com/games/half-life/addons/cook-the-headcrab)\n51. [Core01](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design competition* by *Flash*)\n52. [Crawler](https://www.runthinkshootlive.com/posts/crawler/)\n53. [Crysis 3](https://hl.loess.ru/?mod=215)  (map for *Single Mapping Competition* by *Raid*)\n54. [CUBE](https://twhl.info/competitions.php?results=7) (map by *Jobabob*)\n55. [danger1](https://twhl.info/vault.php?map=2302)\n56. [Data-base](https://www.runthinkshootlive.com/posts/database/)\n57. [de_dust2_azabetfeN](https://csm.dev/threads/half-strike-rezultaty.36394/) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game)\n58. [De-railed](https://twhl.info/competitions.php?results=7)\n59. [Dead Shift Beta](https://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://web.archive.org/web/20151030005310/http://www.gamefront.com/files/13532512)\n60. [Deep](https://twhl.info/vault.php?map=1669)\n61. [Desert Attack](https://fyzzer.narod.ru/index.html)\n62. [Desert Combat Demo](https://www.fileplanet.com/190487/190000/fileinfo/Half-Life---Desert-Combat-Mod) (despite a big file size there's only one small unfinished map)\n63. [Desert Strike](https://hl.loess.ru/?mod=267)\n64. [Devil Mesa](https://hosting.cecak.cz/forum-modifikace/hl1/index.php?text=mod&modifikace=dm)\n65. [Disco Party v1.1](https://twhl.info/vault.php?map=1004)\n66. **[dissolution](https://twhl.info/vault.php?map=5494)** (you need to have additional textures to play this map properly - **nw.wad** from [Nightwatch Texture Pack](https://www.moddb.com/games/half-life/addons/nightwatch-texture-pack) and **decals.wad** from Opposing Force)\n67. [Doomed Demo](https://web.archive.org/web/20231010122104/http://www.snarkpit.net/index.php?s=maps&map=3351)\n68. [Dream1_ver3](https://twhl.info/vault.php?map=579), [Dream1_ver2](https://twhl.info/vault.php?map=371), [Dream1](https://twhl.info/vault.php?map=359)\n69. [Dressed to Kill](https://www.fileplanet.com/48578/40000/fileinfo/Dressed-to-kill)\n70. [Dying at Sea( Signs )](https://twhl.info/vault.php?map=5379)\n71. [eif coloseum](https://www.runthinkshootlive.com/posts/eif-coloseum/)\n72. [eif_room](https://www.runthinkshootlive.com/posts/eif-room/)\n73. [eif_school](https://www.runthinkshootlive.com/posts/eif-school/)\n74. [Emissary](https://twhl.info/competitions.php?results=15)\n75. **[En Route 66](https://www.moddb.com/mods/en-route-66)**\n76. [Enclosed Space](https://twhl.info/vault.php?map=4496)\n77. **[Endlevel Boss](https://twhl.info/vault.php?map=786)**\n78. [ES](https://csm.dev/threads/hl-smc-es.29017/) (map for *Single Mapping Competition* by *Flash*)\n79. [escape](https://twhl.info/vault.php?map=3632) by *killer1102* (there is a potential bug with a scientist, who should open a door for you, but it's an inner scripting problem of the map)\n80. [Escape from Black Mesa v1.44](https://twhl.info/vault.php?map=) (map from *TWHL* by *Satchmo*) (wrong link!)\n81. [Escape Off](https://hl.loess.ru/?mod=324) (a part of this map was used later in *Friendship 2.0* mod)\n82. [Evasion](https://www.fileplanet.com/53430/50000/fileinfo/Half-life-:-Evasion)\n83. [Evil Space](https://www.runthinkshootlive.com/posts/evil-space/)\n84. **[Experimental Problems](https://www.runthinkshootlive.com/posts/experimental-problems/)**\n85. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt)\n86. [Facility](https://twhl.info/vault.php?map=5482)\n87. [Facility Escape](https://twhl.info/vault.php?map=3673)\n88. [Fallout](https://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!)\n89. [Final Assault](https://twhl.info/vault.php?map=4500)\n90. [Flat](https://hl.loess.ru/?mods=&search=Flat)\n91. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/)\n92. [Freeman's Escape](https://www.runthinkshootlive.com/posts/freemans-escape/) (map by *Dave Crabb*)\n93. [Freeman's Suicide( Kill Yourself )](https://www.runthinkshootlive.com/posts/kill-yourself/)\n94. [Funhouse](https://twhl.info/competitions.php?results=11)\n95. [Func_breakable - The Invasion](https://hl.loess.ru/?mod=384) (map from *TWHL* by *Archie* aka *The Hunter*)\n96. [Genetic Research Facility](https://twhl.info/vault.php?map=4775)\n97. [Gladiator](https://csm.dev/threads/hl-gladiator.36140/)\n98. [Go to Xen Awalk(Xen Walk/SP-Offyxen)](https://www.moddb.com/mods/go-to-xen-awalk-half-life-map)\n99. [goldsource](https://twhl.info/vault.php?map=5737) (link dead!)\n100. [Govnomod](https://half-life.ru/forum/showthread.php?threadid=12118) ([DevTest Demo](http://half-life.ru/forum/attachment.php?postid=226623) was tested; for the proper installation you should have Counter-Strike mod preliminarily installed in your main game folder; demo map text is on russian) (links dead!)\n101. [Govnomod: Mysterious Force](http://forums.playground.ru/half-life/g_vnomod-640221/) \n102. [GruntMatch](https://www.runthinkshootlive.com/posts/grunt-match/)\n103. [Grunts Domain( archiveSP01 )](https://www.runthinkshootlive.com/posts/grunts-domain/)\n104. [Gunship v1](https://www.runthinkshootlive.com/posts/gunship/)\n105. [Half-Life 1: Traptown E3 2003](https://gamebanana.com/maps/168032)\n106. [Half-Life 2 Tech Demo Parody( Half-Life 2 Physics Test Level )](https://www.fileplanet.com/124837/120000/fileinfo/Half-Life-2-Techdemo-HL1-Map-v.2)\n107. Hard Map 2 (map from TWHL by Custom)\n108. [Hard Way](https://hl.loess.ru/?mod=480)\n109. [Headcrab Revenge](https://twhl.info/vault.php?map=3519)\n110. [Headcrab-BOSS](https://www.moddb.com/games/half-life/downloads/headcrab-boss)\n111. [HL Dance( Half-Dance )](https://twhl.info/vault.php?map=2991)\n112. [hl_egzekucja](https://gamebanana.com/maps/50132)\n113. **[HLywood](https://twhl.info/vault.php?map=455)**\n114. **[Hnaii - Office Komplex](https://www.fileplanet.com/113757/110000/fileinfo/Hnaii---Office-Komplex)**\n115. [Hospital](https://gamebanana.com/maps/167446) (you need to edit **liblist.gam** file in the mod's folder - delete *gamedll & type* strings from it before you start to play)\n116. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/)\n117. [House](https://www.artpeter.net/Data/HalfLife/Hl.php)\n118. [Impulse 101 Fun - The Train](https://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*)\n119. [In America](https://web.archive.org/web/20170221213409/http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476)\n120. [In the Kitchen](https://twhl.info/vault.php?map=4314)\n121. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration)\n122. [Interactivity & Lots of Entities](https://twhl.info/vault.php?map=5744) (link dead!)\n123. [Interior](https://www.runthinkshootlive.com/posts/interior/)\n124. [Into the Frying Pan](https://www.runthinkshootlive.com/posts/into-the-frying-pan/)\n125. **[Island Bombing](https://twhl.info/competitions.php?results=13)**\n126. **[Ivy](https://twhl.info/vault.php?map=4869)**\n127. [Jump Program](https://hl.loess.ru/?mod=575)\n128. [Killer on the Run](https://www.runthinkshootlive.com/posts/killer-on-the-run/)\n129. [Killing House for Half-Life](https://www.runthinkshootlive.com/posts/killing-house/)\n130. [Kosovo 2000](https://www.moddb.com/games/half-life/addons/kosovo-2000)\n131. [Kosovo II – The Second Day](htstp://www.moddb.com/games/half-life/addons/kosovo-2)\n132. [KotiteolliSuus](https://twhl.info/vault.php?map=533)\n133. [Kyo Half-Strike](https://csm.dev/threads/half-strike-rezultaty.36394/) (there are few error messages at start, which can be safely skipped; also the code in the beginning is 3141)\n134. **[l33t Test 1: Conclusive Analysis](https://twhl.info/vault.php?map=3023)**\n135. [Lab](https://twhl.info/vault.php?map=1797)\n136. **[Lambda Station](https://www.runthinkshootlive.com/posts/lambda-station/)**\n137. [LC](https://www.runthinkshootlive.com/posts/lc-by-sluxe/) (map for *Fast Level Design* competition by *Slux*)\n138. **[LDSF( Laser Deployed Security Force )](https://twhl.info/competitions.php?results=4)**\n139. [Locked Up](https://www.fileplanet.com/8842/0/fileinfo/Locked-Up)\n140. [Looping Stage](https://web.archive.org/web/20041104030218/http://cariad.co.za/twhl/mapvault_map.php?id=1250)\n141. [Lord's Lair](https://www.fileplanet.com/7471/0/fileinfo/Lords-Lair)\n142. [Losspower](https://twhl.info/vault.php?map=3919)\n143. **[Lost-World](https://gamebanana.com/maps/156214)**\n144. [Lounge](https://www.fileplanet.com/7461/0/fileinfo/The-Lounge)\n145. Matrixroom v0.7 (map by Joe Hunter)\n146. [MatrixTrainstation](https://twhl.info/vault.php?map=2531)\n147. **[Maze01a](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)**\n148. [Meat, Blood, Gun](https://www.moddb.com/games/half-life/addons/meat-blood-gun) (there are 2 maps, but only first map is playable, in fact)\n149. [Merry Christmas to hlife_hotdog aka *Happy Holidays*](https://twhl.info/vault.php?map=5717) (to play the right map, enter map **happyholidays** into console, or edit *startmap* parameter in **liblist.gam** file inside mod's folder)\n150. Metro (map by unknown author)\n151. Monster Arena v1 & Monster Arena v2 (maps by unknown author)\n152. Monster Shoot (map by unknown author)\n153. [Museum Lockdown](https://twhl.info/vault.php?map=2211)\n154. [My Backyard](https://twhl.info/vault.php?map=1796)\n155. [My Black Mesa](https://www.runthinkshootlive.com/posts/my-black-mesa/) (map for *Single Mapping Competition* by *Zanzer*)\n156. **[Nameless](https://www.runthinkshootlive.com/posts/nameless/)** (map for *Single Mapping Competition* by *AGRESSOR*)\n157. **[Need for Energy(SP-Energy)](https://scrama.3dn.ru/load/2-1-0-2)**\n158. **[Nightmare: A horror map](https://twhl.info/vault.php?map=2106)**\n159. [Nightshift_FsC](https://twhl.info/vault.php?map=2561)\n160. [No Chance](https://gamebanana.com/maps/160536)\n161. [No Regret](https://www.runthinkshootlive.com/posts/no-regret/)\n162. [Nuclear Aftermath](https://web.archive.org/web/20170708121214/http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map **48h_map1_pre** by *Bluthund*) (download link dead!)\n163. Nuclear Plant (map by SaCo)\n164. **[Nuke](https://web.archive.org/web/20170708121214/http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html)** (map **tmdnuke** by *the-middleman*) (download link dead!)\n165. [Observatory](https://www.runthinkshootlive.com/posts/observatory/)\n166. [Ominous Reality: Part 1](https://twhl.info/vault.php?map=1328)\n167. [Operation Rainbow](https://twhl.info/vault.php?map=3068)\n168. [Operation Randomosity](https://www.moddb.com/games/half-life/addons/operation-randomosity)\n169. **[Orb](https://twhl.info/competitions.php?results=20)**\n170. [Osprey Chopper Competition](https://twhl.info/competitions.php?results=1) (map by *Andy*)\n171. [Otage Beta v0.05](https://hl.loess.ru/?mods=&search=Otage)\n172. [Outpost](https://www.fileplanet.com/81860/80000/fileinfo/Outpost)\n173. [Oxidum](https://gamebanana.com/maps/162722) (the map is unfinished and after a dead-end there is another interesting place that you can reach only by using of **noclip**)\n174. **[Pac-Man](https://twhl.info/competitions.php?results=29)**\n175. **[Parallax Beta](https://www.runthinkshootlive.com/posts/parallax/)** (there are 2 maps, but only first map is playable, in fact)\n176. [Postal](https://www.runthinkshootlive.com/posts/postal/)\n177. [Problems in Building 2](https://gamebanana.com/maps/166032)\n178. [Pulse: Demo #1](https://www.moddb.com/mods/pulse/downloads/pulse-demo-1)\n179. [Quake](https://www.runthinkshootlive.com/posts/quake/)\n180. [Quilted Thought Organ](https://www.runthinkshootlive.com/posts/quilited-thought-organ/)\n181. **[Radix](https://unquenque.com/radix.html)**  (this map is also a part of *Project Quantum Leap* mod)\n182. [Rat Hunt: Quarters](https://twhl.info/vault.php?map=5990) (there is only 1 little issue - rats counter messages do not displayed until all rats are eliminated)\n183. [Remember All](https://csm.dev/threads/hl-smc-remember_all.29029/)\n184. [Rescue](https://hl.loess.ru/?mods=&search=Rescue) by *SaCo* (there is an inner bug - once you have activated a scientist in the end of the map, you should run quickly from him to the door with a scanner, otherwise he gets stuck into you; so you have to kill all enemies before you activate him)\n185. [Rescue 2](https://www.moddb.com/games/half-life/addons/rescue-2) (map by *SaCo*)\n186. [Revamp](https://www.runthinkshootlive.com/posts/revamp/)\n187. [Rogat](https://www.runthinkshootlive.com/posts/rogat/)\n188. **[Rooms](https://twhl.info/vault.php?map=24)** (map by *ghost2*)\n189. **[Ruled by Insanity](https://twhl.info/vault.php?map=3580)**\n190. **[Rube Goldberg](https://twhl.info/vault.php?map=5014)** aka Entity Challenge 2(map by *Captain Terror*)\n191. [Rube Goldberg Machine](https://twhl.info/vault.php?map=5006) (map by *TJB*)\n192. [Rum](https://www.runthinkshootlive.com/posts/rum/)\n193. [Rumble](https://hl.loess.ru/?mods=&search=Rumble)\n194. [Run, Run, Run!](https://www.moddb.com/games/half-life/addons/run-run-run)\n195. [Runaway](https://hl.loess.ru/?mods=&search=Runaway)\n196. [Runder](https://www.runthinkshootlive.com/posts/runder/)\n197. S1_INFIL, aka Infiltration (old unfinished map by *Silencer[=S7=]*)\n198. [s_3_LumbdaCore](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design* competition by *Sania3*)\n199. [Saving Santa](https://twhl.info/vault.php?map=5726) ( Secret Santa 2011 for *Rimrook* by *Urby* )\n200. [sand_01](https://twhl.info/vault.php?map=3166)\n201. [SciMaker 1.1 (With Kill Func)](https://twhl.info/vault.php?map=1845)\n202. [Scientist Killing](https://twhl.info/vault.php?map=1969)\n203. [Scientists Hideout](https://web.archive.org/web/20190622004216/http://www.isolated-design.de:80/half-life-mods/thewall-48h-contest/) (map **48h_m01** by *ToTac*) (download link dead!)\n204. [Scramble](https://www.moddb.com/mods/scramble)\n205. Second (map by unknown author)\n206. [Seek and Destroy](https://www.fileplanet.com/13006/10000/fileinfo/Seek-and-Destroy)\n207. **[SelfKill](https://www.moddb.com/games/half-life/addons/selfkill)**\n208. [SEMTEX](https://twhl.info/competitions.php?results=7) (map by *kol*)\n209. [Sepulcher](https://www.fileplanet.com/7478/0/fileinfo/Sepulcher)\n210. [Shootout Alpha 3](https://www.fileplanet.com/111687/110000/fileinfo/Shootout-ALPHA-3)\n211. [Small Battle](https://twhl.info/vault.php?map=2337)\n212. [Small Battle 2](https://twhl.info/vault.php?map=2393)\n213. [Smash Half-Life](https://www.runthinkshootlive.com/posts/smash/)\n214. [Snatch](https://jqbros.3dn.ru/load/1-1-0-9)\n215. [Soft Boiled](https://www.fileplanet.com/7475/0/fileinfo/Soft-Boiled)\n216. **[Someplace Else](https://hylobatidae.org/minerva/parallax/someplace-else.html)** (this map is also a part of *Project Quantum Leap* mod)\n217. [Somewhere( Kasperg: Unique Map )](https://twhl.info/competitions.php?results=13)\n218. [Southeastern Lan Party](https://www.fileplanet.com/7476/0/fileinfo/Southeastern-Lan-Party)\n219. **[sp_valley](https://gamebanana.com/maps/61099)**\n220. **[Space_Lasercore](https://twhl.info/vault.php?map=1938)**\n221. [Spearhead](https://twhl.info/vault.php?map=1018)\n222. [Spellbinder - The Summoning Tower v1.2](https://twhl.info/vault.php?map=1502)\n223. [Sproutch Mod!!!](https://www.runthinkshootlive.com/posts/sproutch/) (another variation of this map is [Bullsquids Pet](https://www.fileplanet.com/126221/120000/fileinfo/Bullsquids-Pet))\n224. [Star](https://www.ceskemody.cz/mapy.php?lng=1&clanek=36&razeni_hra=1&pn=star) (there are 2 maps, but only first map is playable, in fact; also changelevel is not working properly there, but it's an inner bug of the mod)\n225. [Stacja](https://www.runthinkshootlive.com/posts/stacja/)\n226. [Station17](https://gamebanana.com/maps/161063)\n227. [Storage Facility](https://hl.loess.ru/?mods=&search=Storage+Facility) (map by *T.J Brosnan*)\n228. [Strange Findings Part 1](https://twhl.info/vault.php?map=3617)\n229. [Subway - The Longest Fall](https://twhl.info/vault.php?map=3926) (you need to have additional textures to play this map properly: **opfor.wad** from *Opposing Force*, **specialists.wad** from *The Specialists*, **tfc2.wad** from *Team Fortress Classic* and **wanted.wad** from *Wanted!*)\n230. **[Surfacerun](https://gamebanana.com/maps/156304)**\n231. [SwirusMap (call it \"Dream\")](https://twhl.info/vault.php?map=1588)\n232. [Target Practice](https://twhl.info/vault.php?map=5460)\n233. **[Technical Problems](https://www.runthinkshootlive.com/posts/technical-problems/)**\n234. [Teh Hammre](https://www.runthinkshootlive.com/posts/teh-hammre/)\n235. **[Test Lab 16](https://twhl.info/competitions.php?results=17)**\n236. [Test Map](https://www.moddb.com/games/half-life/addons/test-map-update) (map by *NinjaBlack1337* aka *Guillermo_SPY*)\n237. [Test of Destruction](https://twhl.info/vault.php?map=3892)\n238. [The Abandon](http://half-life.ru/forum/showthread.php?threadid=14267) (link dead!)\n239. [The Arena - Room 1](https://twhl.info/vault.php?map=3523)\n240. [The Crab Lab](https://twhl.info/vault.php?map=4105)\n241. [The Cupboard of Doom](https://www.moddb.com/mods/the-cupboard-of-doom)\n242. [The Desert of Doom](https://hl.loess.ru/?mod=265)\n243. **[The Gloom](https://www.moddb.com/mods/the-gloom)**\n244. [The History Can Be Changed](https://www.runthinkshootlive.com/posts/history-can-be-changed/) (you need to have additional textures to play this map properly - **opfor.wad** from *Opposing Force*)\n245. [The House Beta 0.1](https://gamebanana.com/maps/138258)\n246. [The Innocent Eternity](https://www.fileplanet.com/10027/10000/fileinfo/The-Innocent-Eternity) (there are 2 maps, but only first map is playable, in fact)\n247. [The Interview, Stage 1](https://www.fileplanet.com/7468/0/fileinfo/The-Interview,-stage-1)\n248. [The Last Survivor](https://www.moddb.com/games/half-life/addons/the-last-survivor) (there is an issue with saved games in this map - they don't work properly after loading, but it's an inner flaw of the map, not an engine's bug)\n249. [The Leech Pits](https://www.runthinkshootlive.com/posts/the-leech-pit/)\n250. [The Origin of Symmetry](https://gamebanana.com/maps/56796)\n251. **[The Plague](https://scrama.3dn.ru/load/2-1-0-31)**\n252. **[The Playtest](https://www.runthinkshootlive.com/posts/the-playtest/)**\n253. [The Poseidon Incident( USCM: Infestation Demo )](https://www.runthinkshootlive.com/posts/the-poseidon-incident/)\n254. **[The Run](https://twhl.info/vault.php?map=4890)**\n255. [The Secret Mission](https://www.geocities.ws/rawrguilds/maps.html)\n256. [The Silo Station](https://www.fileplanet.com/7480/0/fileinfo/The-Silo-Station)\n257. [The Stupendous Quest of the Annoying Microwave](https://twhl.info/vault.php?map=5937)\n258. [The Swimmingpool](https://www.fileplanet.com/54276/50000/fileinfo/The-swimmingpool) (there is an inner bug of unmovable trashcan, so you have to use **noclip** on your way back)\n259. [The Transporter( Xen Transportation )](https://twhl.info/vault.php?map=3408)\n260. The Trap (map by Keks)\n261. [The_Work_Area v1.0](https://twhl.info/vault.php?map=2420)\n262. [Torching the Light](https://www.moddb.com/mods/torching-the-light)\n263. [Torture a Friendly NPC](https://twhl.info/vault.php?map=3672)\n264. [Torture That Alien](https://twhl.info/vault.php?map=3248)\n265. [Total Evasion](https://www.moddb.com/mods/total-evasion)\n266. [Tower](https://twhl.info/vault.php?map=250) (Required Spirit of Half-Life for item_suit)\n267. [TriggerHappy](https://www.fileplanet.com/8929/0/fileinfo/TriggerHappy)\n268. [TriggerHappy2](https://www.fileplanet.com/13503/10000/fileinfo/TriggerHappy2)\n269. [TriggerHappy2.5](https://www.moddb.com/games/half-life/addons/trigger-happy-v25) \n270. [Trouble](https://gamebanana.com/maps/166033)\n271. [Tunnels](https://twhl.info/vault.php?map=5350)\n272. **[Twisted Hazard Course](https://www.fileplanet.com/52919/50000/fileinfo/Twisted-Hazard-Course)**\n273. [Two Towers](https://twhl.info/vault.php?map=2327)\n274. [Underground Facility](https://twhl.info/vault.php?map=1762)\n275. [Unnamed](https://twhl.info/competitions.php?results=20) by *rowleybob*\n276. [Urb's Challenge](https://twhl.info/vault.php?map=2677)\n277. [USAF](https://hl.loess.ru/?mod=1085)\n278. **[USS Gaspra](http://biomech.itstudios.ru/gallery/uss_gaspra.htm)** (download link dead!)\n279. [USSL Blue Mesa](https://web.archive.org/web/20041109135949/http://cariad.co.za/twhl/mapvault_map.php?id=1402) (remove space from the end of the map's name before you play)\n280. [Valve Pressure Beta v2](https://www.fileplanet.com/87528/80000/fileinfo/Valve-Pressure---Single-Player-Map)\n281. [Vassy Compo](https://twhl.info/vault.php?map=771)\n282. [Vilcabamba](https://www.moddb.com/games/half-life/addons/vilcabamba) (this map is also used as a first map in *Idol Hunt* mod)\n283. [Vital Signs](https://twhl.info/vault.php?map=3908)\n284. [Wake Up and Stay Alive](http://half-life.ru/forum/showthread.php?threadid=11956) (link dead!)\n285. [war_coop1](https://twhl.info/vault.php?map=4991)\n286. [Warehouse Firefight](https://twhl.info/vault.php?map=2279)\n287. [We Got Work to Do!](https://twhl.info/vault.php?map=3107)\n288. [Weird Dreams](https://twhl.info/vault.php?map=1473) (Required Spirit of Half-Life for item_suit)\n289. [When the Army Came to the Office](https://www.runthinkshootlive.com/posts/when-the-army-came-to-the-office/)\n290. [WTF](https://www.runthinkshootlive.com/posts/wtf/)\n291. [WWE Bullsquid Royal Rumble](https://www.moddb.com/games/half-life/addons/wwe-bullsquid-royal-rumble)\n292. **[Wybuchowka](https://www.runthinkshootlive.com/posts/wybuchowka/)**\n293. **[X-treme Violence](https://www.moddb.com/mods/x-treme-violence)**\n294. [XargoL's Entry for Vassy's Compo](https://twhl.info/vault.php?map=769)\n295. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN)\n296. [Xen World](https://web.archive.org/web/20150926134355/http://www.lambda-force.org/load/0-0-0-481-20) (download link dead!)\n297. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL)\n298. [Zeeba-G's TWHL Compo 26 Entry](https://twhl.info/competitions.php?results=26)\n299. [Zombies!](https://gamebanana.com/maps/160812)\n300. [Zone: Map 1 (Intro)](https://www.moddb.com/mods/zone/downloads/1-map)\n301. [Zone: Map 2 (Part 1)](https://www.moddb.com/mods/zone/downloads/zone-part-1)\n302. [Zone: Map 3 (Part 2)](https://www.moddb.com/mods/zone/downloads/zone-map-3)\n\n## List of mappacks for Half-Life: Blue Shift\nTo install - place *.bsp files to **bshift/custom/maps** folder, *.wad files to **bshift/custom** and type **`map <mapname>`** cmd in game.\n\n1. [The Infinite Shift](https://www.fileplanet.com/archive/p-19156/The-Infinite-Shift)\n\n## List of Cleaner's Adventures-based mods\nTo run this mods on specific platforms you may be need to compile libraries from **CAd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/CAd).\n\n1. **[Half-Life: Black Guard](https://www.moddb.com/mods/half-lifeblack-guard)** - uses dlls copied from original Cleaner's Adventures.\n\n## List of mods which based on Spirit of Half-Life\nOriginally this list was written by @Qwertyus3D (*Qortez*)\n\nSpirit of Half-Life - extended toolkit for HL1 mappers by *Laurie Cheers*.\n \nTo run this mods on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/sohl1.2).\n\n### Mods which based on vanilla Spirit of Half-Life 1.2 or older(Fully playable with SoHL 1.2 libraries)\n1. [Accidental Assassin](https://www.moddb.com/mods/accidental-assassin) (this mod has inner bugs; type **restart** if you stuck on changelevel)\n2. **[Big Scientists](https://www.moddb.com/mods/big-scientists)**\n3. [Black Silla Assault DEMO v1.2](https://www.moddb.com/mods/black-silla-assault)\n4. **[Blbej Den](https://www.moddb.com/mods/blbej-den)**\n5. [Cold Experiment v1](https://www.moddb.com/games/half-life/addons/cold-experiment) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites)\n6. **[Dead Sector v1.0a](https://www.moddb.com/mods/dead-sector)**\n7. [Death Is Dead](https://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map)\n8. [Escape from Black Mesa Alpha](https://www.moddb.com/mods/escape-from-black-mesa)\n9. [ESCAPE: Trainingroom](https://www.moddb.com/mods/half-life-escape)\n10. [City Crush v0.1](http://half-life.ru/forum/showthread.php?threadid=10671) (link dead!)\n11. [Invasion 105 v2.0](https://www.moddb.com/mods/half-life-invasion-105)\n12. [Issues( Project Quantum Leap 2 )](https://www.moddb.com/mods/issues)\n13. [Mission Impracticable 2](https://www.moddb.com/mods/mission-impracticable-2)\n14. **[Prison v2.1](https://www.moddb.com/mods/half-life-prison)**\n15. [Prize v1.1](https://www.moddb.com/mods/prize)\n16. **[Radiation Alert: Episode 1 v1.1](https://www.moddb.com/mods/radiation-alert-episode-1)**\n17. [Run From Hell v1.1b](https://www.runthinkshootlive.com/posts/run-from-hell/) (there is an inner bug with underwater crates in the map **firstmap9** - they can't be broken, so you should use **noclip** to pass through them)\n18. **[Santa's Revenge](https://twhl.info/vault.php?map=4332)** (set **fps_max** to **60** to avoid an inner problem of the last map with final scripted sequence, otherwise the mod can not be finished properly)\n19. [Sector 6](https://www.moddb.com/mods/sector-6/) \n20. [Space Prisoner v1.1](https://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll \"..\\prison\\dlls\\spirit.dll\"** for **gamedll \"dlls\\spirit.dll\"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place)\n21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2)\n22. **[Timeline III: The Heart of Darkness](https://www.moddb.com/mods/timeline-series)**\n23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo)\n\n### Mods which based on modified Spirit of Half-Life 1.2 or older(Partially playable with SoHL 1.2 libraries or not playable at all)\n1. [Black Death](https://www.moddb.com/mods/half-life-black-death)\n2. [Borderlands v0.4](https://www.moddb.com/games/half-life/addons/borderlands-v04)\n3. **[Dark Territory](https://www.moddb.com/mods/dark-territory)** (set **fps_max** to **60** to avoid an inner problem of the map **tau_jungle_02** with a scripted sequence, otherwise you can get a gamebreaking bug)\n4. [Dead Way Alpha](https://csm.dev/threads/hl-dead-way-1-nostalgija.12381/)\n5. [Emergency](https://www.runthinkshootlive.com/posts/half-life-emergency/)\n6. **[ESCAPE (final)](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive*\n7. **[ESCAPE 2](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive*\n8. [Force of Evil](https://www.moddb.com/mods/force-of-evil)\n9. [Friendship: Town of half-life.ru mappers v2.0](https://www.runthinkshootlive.com/posts/friendship/) (this mod has inner bugs)\n10. [Malevolence v1.4 Open Source Beta](https://www.moddb.com/mods/malevolence)\n11. **[Portrait of Freeman v1.1](https://www.runthinkshootlive.com/posts/portrait-of-freeman/)**\n12. [Project M.L.P v1.0](https://www.moddb.com/mods/project-mlp) (Remove **halflife.wad** from mod folder)\n13. [Snark Planet Demo](https://www.moddb.com/mods/snark-planet)\n14. **[Survive in Catacombs](https://www.moddb.com/mods/survive-in-catacombs)**\n15. **[Survive in Catacombs 2: Fear (Bloodbath)](https://www.moddb.com/mods/survive-in-catacombs-2)**\n16. **[The Lost Hell](https://www.runthinkshootlive.com/posts/the-lost-hell/)**\n17. **[The Trap v1.60](https://www.moddb.com/mods/the-trap) (mod by *Reaktor*)**\n18. **[Ispitatel 4: Classic](https://www.moddb.com/mods/ispitatel-4-classic)**\n19. [Half-Life 2: Classic Demo](https://www.moddb.com/mods/half-life-2-classic)\n\n### Mods which based on Spirit of Half-Life 1.4/1.5/1.8(Fully playable with SoHL 1.8 libraries, partially playable with SoHL 1.2 libraries or not playable at all)\n1. **[Before v1.1](https://www.moddb.com/mods/half-life-before)**\n2. [Christmas Life v1.0](https://www.moddb.com/mods/christmas-life) (initial mod works properly, but additional mappacks for this mod contain few maps that have some gameplay problems under Xash3D)\n3. [COLONY 42 Alpha](https://twhl.info/vault.php?map=6055)\n4. [Crazy Crabs Demo 1 & 2](https://www.moddb.com/mods/mad-crabs) \n5. [Far Crab Demo v2](https://www.moddb.com/mods/far-crab)\n6. [Firefighter Demo v1.1](https://www.moddb.com/mods/firefighter-mod)\n7. **[Halfquake 3: Sunrise](https://www.moddb.com/mods/halfquake-amen)**\n8. **[Prototype 98](https://www.moddb.com/mods/prototype-98)** (set **fps_max** to **60**, otherwise you can get stuck in some places)\n9. **[Reissues v1.1](https://www.moddb.com/mods/reissues)**\n10. **[Santa's Revenge 2: Xmas Meltdown](https://twhl.info/vault.php?map=4931)**\n11. **[Tactical Espionage Action v1.1](https://www.moddb.com/mods/tactical-espionage-action)**\n12. **[TWHL Tower](https://www.moddb.com/mods/twhl-tower/downloads/twhl-tower-steam-xash3d)**\n\n### Mods which based on Spirit of Half-Life 1.3/1.6/1.7/1.9(Fully playable with SoHL 1.9 libraries, partially playable with SoHL 1.2 libraries or not playable at all)\n1. [Chaos Theory unfinished](https://hlfx.ru/forum/showthread.php?threadid=1772)\n2. [Silent Zhildor Demo](https://www.runthinkshootlive.com/posts/silent-zhildor/)\n\n## List of Opposing Force-based mods\nOriginally this list was written by @Qwertyus3D (*Qortez*)\n\nTo install mods from this category - first of all place mod folder and **gearbox** folder from *Opposing Force* beside **valve** folder.\n\nTo run this mods on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/opfor).\n\nFor mappacks - place *.bsp files to **gearbox/custom/maps** folder, *.wad files to **gearbox/custom** and type **`map <mapname>`** cmd in game.\n### The big singleplayer mods or mappacks\n1. **[Aron](https://csm.dev/threads/hl-aron.37791/)**\n2. **[A Soldiers Tale](https://www.moddb.com/mods/a-soldiers-tale)**\n3. [Black Bag Operations](https://www.runthinkshootlive.com/posts/black-bag-operations/)\n4. **[Bootleg Squadrog](https://www.moddb.com/mods/bootleg-squadrog)** (there is an inner bug - don't save/load your game at the second map of the mod, otherwise a script that giving you weapons in the armory will not work)\n5. [Camp Aign](https://twhl.info/vault.php?map=621)\n6. [Campaign: Part 1](https://www.runthinkshootlive.com/posts/campaign-part-1/)\n7. **[Dark Operations](https://www.moddb.com/mods/op-dark-operations)**\n8. [Double Helix](https://www.runthinkshootlive.com/posts/double-helix/)\n9. **[Fallback, Fire, and Ice](https://www.runthinkshootlive.com/posts/fallback-fire-and-ice/)**, plus simply Fallback (initial version of the mod with 5 maps)\n10. [Fallen Chopper](https://www.runthinkshootlive.com/posts/fallen-chopper/)\n11. **[Focalpoint](https://www.moddb.com/mods/focalpoint)**\n12. [Foothold](https://www.moddb.com/mods/foothold)\n13. [Ground Zero](https://web.archive.org/web/20231010064923/http://www.snarkpit.net/index.php?s=maps&map=3423) by *Necromancer*\n14. **[Ground Zero 2: Fallout](https://www.moddb.com/mods/ground-zero-2-fallout)** by *Derek 'Hellfire' McBurney* (there is a potential inner bug with unmovable barrel at the second map; you can break that barrel, but it very durable and has 9000 health points)\n15. **[Intolerable Threat](https://www.moddb.com/mods/intolerable-threat)**\n16. **[Little Skyscraper of Horrors](https://www.moddb.com/mods/little-sky-scraper-of-horrors)**\n17. [Mechanized Death: An Army of None](https://www.moddb.com/games/half-life-opposing-force/addons/mechanized-death-army-of-none)\n18. **[Military Duty v1.0.1](https://www.moddb.com/mods/military-duty)**\n19. **[Nuclear Winter](https://www.moddb.com/mods/nuclear-winter-opfor)**\n20. [Opposing Force Aliens Addon](https://www.fileplanet.com/82829/80000/fileinfo/Half-Life:-Opposing-Force-Aliens-Addon-1.2)\n21. [Opposing Life2Life](https://www.moddb.com/games/half-life/addons/opposing-life2life-final-hd)\n22. [Realms](https://web.archive.org/web/20231009194345/http://snarkpit.net/index.php?s=maps&map=3483) by *Necromancer*\n23. **[Retaliation](https://www.moddb.com/mods/op-retaliation/downloads/retaliation1)**\n24. **[Shepard's Adventures](https://www.moddb.com/mods/shepards-adventures)**\n25. [Snowy Rock](https://www.moddb.com/games/half-life-opposing-force/addons/snow-rock)\n26. [Space Disaster](https://www.runthinkshootlive.com/posts/space-disaster/)\n27. **[The Alpha Unit](https://www.moddb.com/mods/na18509)**\n28. **[The Evasion](https://www.moddb.com/mods/the-evasion)**\n29. **[The Tower](https://www.moddb.com/mods/the-tower1)**\n30. [The Xen Campaigns](https://www.moddb.com/mods/the-xen-campaigns)\n31. **[Ultimate Attack](https://www.moddb.com/mods/half-life-ultimate-attack)**\n32. **[Under the Blackmoon](https://www.moddb.com/mods/under-the-black-moon)**\n33. [Warzone](https://www.runthinkshootlive.com/posts/warzone/)\n\n### Unfinished mods\n1. [Black Op Mission](https://www.moddb.com/mods/black-op-mission)\n2. [Mission Impossible](https://www.runthinkshootlive.com/posts/mission-impossible/) (the mod is not fully finished and changelevel to the last map doesn't work)\n3. [Poisonheadcrab Nightmare Mappack](https://www.moddb.com/mods/poisonheadcrab-nightmare-mappack-for-half-life) Demo + Update (there are some bugs, but they are inner bugs of the mod)\n4. [Return to Black Mesa](https://web.archive.org/web/20160903151337/http://www.hl-rtbm.wbs.cz/) (download link dead!)\n5. [The Bounty Hunter](https://www.moddb.com/mods/half-life-the-bounty-hunter)\n6. [Xen Assault](https://www.moddb.com/mods/xen-assault) (there are inner bugs, which interfere with level change in introductury maps **p1vs1** and **xenmp**; you can just skip them and begin to play directly from the map **p1v1**)\n\n### Singleplayer mods with one map or single maps\n1. [Alternate Points](https://twhl.info/vault.php?map=4499)\n2. [Battle](https://www.runthinkshootlive.com/posts/battle/)\n3. [Bomb Squad](https://web.archive.org/web/20231009205618/http://snarkpit.net/index.php?s=maps&map=3471)\n4. [Corruption](https://web.archive.org/web/20231009205616/http://www.snarkpit.net/index.php?s=maps&map=3154)\n5. [Critical Mass](https://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1)\n6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/)\n7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/)\n8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/)\n9. [Guitar Star Pre-Alpha](https://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha))\n10. [J2000](https://www.runthinkshootlive.com/posts/j2000/)\n11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/)\n12. [Klabautermann](https://www.runthinkshootlive.com/posts/klabautermann/) (though the map has some inner issues, it can be properly finished, just don't let the welder soldier die and don't press the fifth button until you mount a special gun in its' place)\n13. [Little Escape](https://www.runthinkshootlive.com/posts/little-escape/)\n14. [Marine Invasion: Freak-Lager](http://hl.gamebanana.com/gamefiles/2537)\n15. [Madness 2](https://twhl.info/vault.php?map=1758)\n16. **[Operation: Sandblast](https://unquenque.com/sandblast.html)**\n17. [OpFor Postal](https://www.runthinkshootlive.com/posts/opfor-postal/)\n18. [Super Nova Space Station](https://www.runthinkshootlive.com/posts/supernova-space-station/)\n19. [The Red Area](https://www.runthinkshootlive.com/posts/the-red-area/)\n20. [Underhalls](https://twhl.info/vault.php?map=4070)\n21. [Without Doubts Beta](https://www.runthinkshootlive.com/posts/without-doubts/)\n22. [X-Abaddon](https://www.moddb.com/mods/red-alert-half-life-x-pantion/addons/x-abaddon)\n23. [You Die v2](https://www.runthinkshootlive.com/posts/you-die/)\n24. [Zombies](https://www.runthinkshootlive.com/posts/zombies/)\n\n## List of XashXT-based mods\nXashXT - SoHL-based extended toolkit for Xash3D mappers by *Unkle Mike*.\n\nTo install mods from this category - first of all place mod folder beside **valve** folder.\n\nCurrently, you can try to run this mods under PrimeXT.\n\n1. **[Meanwile in Russia demo(MIR)](https://www.moddb.com/games/mir)**\n2. Monorail Quest\n\n## List of They Hunger-based mods\nTo run this mods on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n### The big singleplayers mods\n1. **[They Hunger: Rebuild](https://www.moddb.com/mods/they-hungerrebuild)** - This mod uses new **func_vehicle** entity and our **hlsdk-portable** libraries.\n\n### Single maps\n1. [Awful changes](https://yadi.sk/d/OE6SDqbkoLy6o) by *MSteam*\n2. [Dark Rock](https://yadi.sk/d/Lc4ahljfoigcQ) by *Adam 'Jeb_Radec' Brown*\n3. [Hospital](https://yadi.sk/d/AAXjfvwLodkPG) by *Nod24*\n4. [They Hunger: Escape](https://yadi.sk/d/f0lcjCycoT5t5) by *Gua*\n5. [They Hunger: Lab of Horrors](https://yadi.sk/d/V_us_m78pFaMK) by *Jordan*\n6. [They Hunger: Route666 Demo](https://yadi.sk/d/XKWCseAPohkG6) by *MadMapper*\n\n## List of supported mods from mobile_hacks branch of HLSDK Portable by FWGS\nCurrently, fresh Xash3D FWGS builds for Android, PS Vita and Nintendo Switch uses libraries from this branch, so you can play mods below out-of-the-box.\n\n1. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)**\n2. **[Big Lolly](https://www.moddb.com/mods/big-lolly)**\n3. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**\n4. **[Half-Secret](https://www.moddb.com/mods/half-secret)**\n5. **[Case Closed](https://www.moddb.com/mods/caseclosed)**\n6. [Bloody Pizza - Vendetta](https://www.moddb.com/games/half-life/addons/bloody-pizza-vendetta)\n7. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04)\n8. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction/downloads/half-life-induction-12)**\n9. **[Redemption/Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)**\n10. [Sewer beta](https://www.moddb.com/games/half-life/addons/sewer-beta)\n11. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**\n12. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**\n\n## List of games and mods with custom gamedll\nFor mods from this category - first of all place mod folder beside **valve** folder.\n\n1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **redemption** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n2. **[Adrenaline Gamer](https://www.moddb.com/mods/adrenaline-gamer)**\n\nOn **x86 Linux** you can use *OpenAG* client by *YaLTeR*\n\nTo run this mod on specific platforms you may be need to compile libraries from **aghl** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n3. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **aom** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n4. **[Afraid of Monsters: Director's Cut](https://www.moddb.com/mods/afraid-of-monsters-dc)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **aomdc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n5. **[Azure Sheep](https://www.moddb.com/mods/azure-sheep)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **asheep** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n6. **[Big Lolly](https://www.moddb.com/mods/big-lolly)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **biglolly** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n7. **[Black OPS](https://www.moddb.com/mods/black-ops)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **blackops** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n8. [Bloody Pizza: Vendetta](https://www.runthinkshootlive.com/posts/bloody-pizza-vendetta/)\n\nTo run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n9. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04)\n\nTo run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n10. **[Bubblemod](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)**\n\nThis mod already has version for **x86 Linux**.\n\nTo run this mod on specific platforms you may be need to compile libraries from **bubblemod** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n11. **[Case Closed](https://www.moddb.com/mods/caseclosed)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n12. **[Cleaner's Adventures](https://www.moddb.com/mods/cleaners-adventures)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **CAd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n13. **[Cold Ice Remastered](https://www.moddb.com/mods/cold-ice-remastered)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n14. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)**\n\nOn x86 you may be need to compile client part.\n\nFor other specific platforms you may be need to compile server part too.\n\nSource code:\n\nClient: https://github.com/Velaron/cs16-client/\n\nServer: https://github.com/rehlds/ReGameDLL_CS\n\n15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**\n\nSteam version already supports x86 Linux.\n\nTo run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n20. **[Gang Wars](https://www.moddb.com/mods/gangwars)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**\n\nCurrently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\nSupport for other platforms may be available later.\n\n23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*\n\n*Support temporary abandoned*\n\nTo run this mod on specific platforms you may be need to compile libraries from **gravgun** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\nCurrently, MSVC is not supported, use MinGW if you need build for Windows.\n\nMod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).\n\n26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**\n\nThis mod supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**\n\nThis mod already supports x86 Linux.\n\nTo run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).\n\n29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**\n\nSteam version already supports x86 Linux.\n\nTo run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n36. **[Half-Secret](https://www.moddb.com/mods/half-secret)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n39. **[Paranoia](https://www.moddb.com/mods/paranoia)**\n\n*Support temporary abandoned*\n\n[Source code](https://github.com/FWGS/paranoia_toolkit).\n\n40. **[Poke646](https://poke646.com/)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n41. **[Poke646: Vendetta](https://poke646.com/)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)\n\n[Source code](https://github.com/SNMetamorph/PrimeXT)\n\n43. **[Quake Remake](https://www.moddb.com/games/quake-remake)**\n\n*Support was fully abandoned due Quake Wrapper release.*\n\n[Actual source code](https://github.com/FWGS/quakeremake)\n\n44. **[Rebellion](https://www.moddb.com/mods/rebellion1)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)\n\nTo run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)\n\nTo run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**\n\nThis mod already supports x86 Linux.\n\nCurrently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.\n\n52. **[The Gate](https://www.moddb.com/mods/the-gate)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)\n\n*Support was fully abandoned due PrimeXT development*\n\n[Source code](https://github.com/FWGS/XashXT-FWGS/)\n\n57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**\n\nThis mod already supports x86 Linux.\n\nSupport for other platforms may be available later.\n\n58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**\n\nTo run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\nDon't forget to enable XENWARRIOR build option!!!\n\n59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)\n\nTo run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).\n\n## List of Counter Strike-based mods\n\n1. **[CSMoE](https://github.com/MoeMod/CSMoE/releases)** by *Chinese Xash3D FWGS Community* - uses custom Xash3D FWGS build.\n\n"
  },
  {
    "path": "Documentation/touch-controls.md",
    "content": "# Touch controls configuring\n\n## Introduction\n\nThanks to mittorn, we have the ability to fully customize the controls in Xash3D. The new config allows you to not only add and change control buttons, but also create custom menus. There is also a built-in visual editor available, which simplifies the customization process without the need for manual file editing.\n\n## Editor mode usage\n\n1. Launch Xash3D and start the game.\n2. To enter the edit mode, click on the gear icon (command `touch_enableedit`).\n3. In the edit mode, the grid is displayed. The number of cells can be changed with the command `touch_grid_count` (default is 50). The grid can be disabled with the command `touch_grid_enable 0`.\n\n*Touch controls layout editor*\n![](images/editor.jpg)\n\n*Touch profiles window*\n![](images/touch-profiles.jpg)\n\n*Touch buttons parameters window*\n![](images/touch-buttons.jpg)\n\n## Layout editor features\n\n* **Moving buttons**: click on a button, drag it to the desired location, and release.\n* **Resizing buttons**: place your first finger on the top left corner of the button, and use your second finger to resize it.\n* **Hiding/showing buttons**: select the button (it will turn red), then use the menu:\n    * **Close**: closes the editing mode (`touch_disableedit`).\n    * **Reset**: resets the button to its default values.\n    * **Hide/Show**: hides or shows the button (`touch_hide <name>` / `touch_show <name>`).\n\n## Working with configuration files / console\n\nAll changes made in the visual editor are automatically saved to the touch profile file. The files are located in the `touch_profiles` folder inside game directory. The file name depends on the selected profile.\n\n### Common commands in configuration files\n\n```\n// Comment: lines starting with // are ignored.\n\n// Swipe zones for movement. Specify how far you need to swipe to speed up.\n// The default values ​​are set to be optimal for a small swipe to immediately walk quickly.\ntouch_forwardzone \"0.060000\" ; touch_sidezone \"0.060000\"\n\n// Sensitivity settings (pitch - horizontal, yaw - vertical).\ntouch_pitch ; touch_yaw\n\n// Grid settings (see above).\ntouch_grid_count ; touch_grid_enable\n\n// Draws a border around the button.\ntouch_set_stroke <thickness> <c> <v> <e> <t>\n(<r> - red; <g> - green; <b> - blue; <a> - alpha/transparency)\n\n// Show (1) or hide (0) client buttons.\ntouch_setclientonly\n\n// Removes all buttons from the screen.\ntouch_removeall\n\n// Shows all available buttons in the configuration file.\ntouch_list\n\n// Saves the config after editing. You can specify a new file name.\ntouch_config_file command.cfg\n```\n\n*The full list of buttons in the standard `touch.cfg` file, displayed in the console when using `touch_list`.*\n![](images/example2.jpg)\n\n\n## Adding new buttons\n\nTo add a new button, use the console command:\n\n```\ntouch_addbutton \"digits\" \"touch/key_1.png\" \"toggle_digits\" 0.340000 0.782222 0.400000 0.888889 255 255 255 100 0\n```\n\n### Parameters of `touch_addbutton` command\n\n| Parameter | Description |\n| --- | --- |\n| `\"digits\"` | Unique name of the button. |\n| `\"touch/key_1.png\"` | Path to the icon file (`.png` format). If the icon is not needed, leave `\"\"`. |\n| `\"toggle_digits\"` | Command to execute after clicking (for example, `\"buy\"` for purchase). |\n| `0.340000` | X coordinate of the upper-left corner of the button. |\n| `0.782222` | Y coordinate of the upper-left corner of the button. |\n| `0.400000` | X coordinate of the lower-right corner of the button. |\n| `0.888889` | Y coordinate of the lower-right corner of the button. |\n| `255 255 255` | Button color in RGB format. |\n| `100` | Button transparency (0 - fully transparent, 255 - fully opaque). |\n| `0` | Flags (see next section). |\n\n## List of flags\n\nFlags define the behavior of the button. Their values ​​are powers of two:\n\n| Flag | Value | Description |\n| --- | --- | --- |\n| `TOUCH_FL_HIDE` | 1 | Hides the button (not displayed in the game, but visible in the editor). |\n| `TOUCH_FL_NOEDIT` | 2 | Disables editing of the button in the editor. |\n| `TOUCH_FL_CLIENT` | 4 | The button is client-side (not saved in the main control file). |\n| `TOUCH_FL_MP` | 8 | The button is displayed only in multiplayer. |\n| `TOUCH_FL_SP` | 16 | The button is displayed only in singleplayer. |\n| `TOUCH_FL_DEF_SHOW` | 32 | The button is always displayed on startup. |\n| `TOUCH_FL_DEF_HIDE` | 64 | The button is always hidden on startup. |\n| `TOUCH_FL_DRAW_ADDITIVE` | 128 | The button colors are added together in blend mode. |\n| `TOUCH_FL_STROKE` | 256 | Enables outline stroke around the button. |\n\nFlags can be combined by adding their values ​​together. For example, `5 = 1 + 4` is the combination of `TOUCH_FL_HIDE` and `TOUCH_FL_CLIENT` flags, which is a hidden client button.\n\n*In the image below, the `spray`, `scores`, `messagemode` buttons are displayed simultaneously with the flag 8 and `loadquick`, `savequick` with the flag 16, and each is displayed in the corresponding game mode.*\n![](images/example1.jpg)\n\n## Useful commands\n\n* `touch_hide <pattern>`: hides buttons by pattern.\n* `touch_setcommand`: changes the command bound to a button.\n* `touch_settexture`: quickly changes the button image.\n* `touch_setcolor`: sets the button color.\n* `touch_exportconfig`: exports the current configuration, including aspect ratio.\n\n## Usage examples\n\n* `touch_hide menu*` hides all buttons with names starting with `menu`.\n* `touch_setcolor \"attack\" 255 160 0 128` changes the color of the primary fire button from opaque white to translucent orange, similar to the color of the HUD in Half-Life.\n\n    *Result of executing this command*\n    ![](images/example3.jpg)\n\n* Example of adding a new button with a custom icon (the `lastinv` command is used - quick change between weapons)\n    ![](images/example4.jpg)\n\n    *View of this button in layout editor*\n    ![](images/example5.jpg)\n\n## Tips\n\n* To prevent the `look` and `move` buttons from interfering with editing other elements, place them before the others in the configuration file.\n* And vice versa, to make a button appear on top of others, place it at the end of the configuration file.\n* You can assign commands to buttons that change other buttons, see the previous section \"Usage examples\".\n* After editing each parameter of each individual button in the Touch Buttons section, do not forget to press Save, otherwise the applied parameters will not be saved.\n* To create your own icon for the button, you can use any graphics editor (for Android, Photo Editor by iudesk is suitable). Saving conditions:\n    * Image format - `.png` with transparency (e.g. alpha channel)\n    * Aspect ratio / size - 1:1 / 256x256\n    * Path to the icons location - `touch/gfx` inside game directory.\n    \n### Additional links\n* [Handy palette for selecting color in RGB format](https://www.rapidtables.com/web/color/RGB_Color.html)\n"
  },
  {
    "path": "README.md",
    "content": "# Vulkan plus Ray Tracing (RTX) temporary fork of Xash3D FWGS engine\n[![GitHub Actions Status](https://github.com/w23/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/w23/xash3d-fwgs/actions/workflows/c-cpp.yml)\n\n## TL;DR\n- ![image](https://github.com/w23/xash3d-fwgs/assets/321361/12200b56-df80-4d33-b433-71f5690fb4f5)\n- This fork adds Vulkan renderer to Xash3D-FWGS engine.\n- This is work-in-progress. It is in early stages and is not ready for unsupervised usage.\n- Vulkan renderer targets two different modes:\n  - Traditional rasterizer. It is intended to produce pixel-perfect identical frames to existing GL renderer as possible.\n  - Ray tracing. It implements real time path traced global illumination lighting with PBR materials. It will look noticeably different from original game.\n- It is intended to be merged back into upstream/master when it gets mature and stable enough.\n- Ray tracing requires 64-bit build. 32-bit drivers do not expose vulkan ray tracing extensions.\n- For more information, check out the [wiki](https://github.com/w23/xash3d-fwgs/wiki).\n- [Page on Mod DB](https://www.moddb.com/mods/half-life-rtx) (screenshots, etc).\n\n## Current status\n- See [issues](https://github.com/w23/xash3d-fwgs/issues) and [project](https://github.com/users/w23/projects/2/views/12)\n- Traditional rasterizer mostly works:\n\t- Works on Windows and Linux with any Vulkan GPU (and at some point it worked on Raspberry Pi 4 even).\n\t- It is slower than OpenGL renderer (1. I suck at Vulkan. 2. No visibility culling is performed).\n\t- Some features are not implemented yet, like decals, dynamic lighting is different and way off, etc.\n- Ray tracer mostly works too, with dynamic GI and stuff.\n\t- It also requires material remaster (i.e. newer textures for PBR parameters) and missing RAD files for most of the game maps.\n\t- Works under both Windows and Linux.\n\t- Works on both AMD and Nvidia GPUs.\n\t- Works on Steam Deck with _interactive framerates_.\n- If you feel adventurous, you can follow [build instructions](https://github.com/w23/xash3d-fwgs/wiki/How-to-build-a-64bit). Note that they might be slightly out of date, kek.\n\n## Follow development\nThis project is 99% developed live on stream. I'm not a graphics programmer, and have no idea what I'm doing. I'm essentially learning Vulkan, game engine renderer development, linear algebra, and ray tracing techniques while getting hands dirty with this. This is all for your amusement.\n\nYou can watch me making a fool of myself publicly here:\n- [Archive playlist on YouTube/floba23](https://www.youtube.com/playlist?list=PLP0z1CQXyu5CrDa522FklxbOC0SM_Manl)\n- [Twitch/provod](https://twitch.tv/provod)\n\n---\n\nRegular upstream Xash3D README.md follows.\n\n---\n\n# Xash3D FWGS Engine <img align=\"right\" width=\"128\" height=\"128\" src=\"https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png\" alt=\"Xash3D FWGS icon\" />\n[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \\\n[![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://fwgsdiscord.mentality.rip/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \\\n[![Download Daily Build](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)\n\nXash3D ([pronounced](https://ipa-reader.com/?text=ks%C9%91%CA%82) `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.\n\nXash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike.\n\n## Donate\n[![Donate to FWGS button](https://img.shields.io/badge/Donate_to_FWGS-%3C3-magenta)](Documentation/donate.md) \\\nIf you like Xash3D FWGS, consider supporting individual engine maintainers. By supporting us, you help to continue developing this game engine further. The sponsorship links are available in [documentation](Documentation/donate.md).\n\n## Fork features\n* Steam Half-Life (HLSDK 2.5) support.\n* Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md).\n* Better multiplayer: multiple master servers, headless dedicated server, voice chat, [GoldSrc protocol support](Documentation/goldsrc-protocol-support.md) and IPv6 support.\n* Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software.\n* Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform.\n* Mobility API: better game integration on mobile devices (vibration, touch controls).\n* Different input methods: touch and gamepad in addition to mouse & keyboard.\n* TrueType font rendering, as a part of mainui_cpp.\n* External VGUI support module.\n* PNG & KTX2 image format support.\n* Ogg Vorbis (`.ogg`) & Ogg Opus (`.opus`) audio formats support.\n* [A set of small improvements](Documentation/), without broken compatibility.\n\n## Installation & Running\n0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code.\n1) Copy engine binaries to some directory.\n2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries.\nIf your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable).\nThis repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods.\nYou still needed to copy `valve` directory as all game resources located there.\n3) Run the main executable (`xash3d.exe` or AppImage).\n\nFor additional info, run Xash3D with `-help` command line key.\n\n### Android\n0) Install the APK file.\n1) Copy `valve` directory to a folder named `xash` in the Internal storage.\n2) Run games from within the app.\n\n## Contributing\n* Before sending an issue, check if someone already reported your issue. Make sure you're following \"How To Ask Questions The Smart Way\" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html.\n* Issues are accepted in both English and Russian.\n* Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file.\n\n## Build instructions\nWe are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/).\n\n**NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using!**\n\n### Prerequisites\nIf your CPU is x86 compatible and you're on Windows or Linux, we are building 32-bit code by default. This was done to maintain compatibility with Steam releases of Half-Life and based on it's engine games.\nEven if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code!\n\nIf your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable).\nThis repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods.\n\n#### Windows (Visual Studio)\n* Install Visual Studio.\n* Install latest [Python](https://python.org) **OR** run `cinst python.install` if you have Chocolatey.\n* Install latest [Git](https://git-scm.com/download/win) **OR** run `cinst git.install` if you have Chocolatey.\n* Download [SDL2](https://libsdl.org/download-2.0.php) development package for Visual Studio.\n* Clone this repository: `git clone --recursive https://github.com/FWGS/xash3d-fwgs`.\n* Make sure you have at least 12GB of free space to store all build-time dependencies: ~10GB for Visual Studio, 300 MB for Git, 100 MB for Python and other.\n\n#### GNU/Linux\n##### Debian/Ubuntu\n* Only for 32-bit engine on 64-bit x86 operating system:\n  * Enable i386 on your system: `$ sudo dpkg --add-architecture i386`.\n  * Install `aptitude` ([why?](https://github.com/FWGS/xash3d-fwgs/issues/1828#issuecomment-2415131759)):  `$ sudo apt update && sudo apt upgrade && sudo apt install aptitude`\n  * Install development tools: `$ sudo aptitude --without-recommends install git build-essential gcc-multilib g++-multilib libsdl2-dev:i386 libfreetype-dev:i386 libopus-dev:i386 libbz2-dev:i386 libvorbis-dev:i386 libopusfile-dev:i386 libogg-dev:i386`.\n  * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig`.\n\n* For 64-bit engine on 64-bit x86 and other non-x86 systems:\n  * Install development tools: `$ sudo apt install git build-essential python libsdl2-dev libfreetype6-dev libopus-dev libbz2-dev libvorbis-dev libopusfile-dev libogg-dev`.\n\n* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.\n\n##### RedHat/Fedora\n* Only for 32-bit engine on 64-bit x86 operating system:\n  * Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL3-devel.i686 sdl2-compat-devel.i686 opus-devel.i686 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-devel.i686`.\n  * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`.\n\n* For 64-bit engine on 64-bit x86 and other non-x86 systems:\n  * Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL3-devel sdl2-compat-devel opus-devel freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`.\n\n* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.\n\n#### Android (Windows/Linux/macOS)\n* Install [Android Studio](https://developer.android.com/studio) (or the command line tools).\n* Install [Python](https://python.org) (at least 2.7, latest is better).\n* Install [Git](https://git-scm.com/download/win).\n* Install [Ninja](https://ninja-build.org/).\n* Install [CMake](https://cmake.org/) (for some dependencies).\n\n* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`.\n\n### Building\n#### Windows (Visual Studio)\n0) Open command line.\n1) Navigate to `xash3d-fwgs` directory.\n2) (optional) Examine which build options are available: `waf --help`.\n3) Configure build: `waf configure --sdl2=c:/path/to/SDL2`.\n4) Compile: `waf build`.\n5) Install: `waf install --destdir=c:/path/to/any/output/directory`.\n\n#### Linux\nIf compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step is set correctly, prior to running configure.\n\n0) (optional) Examine which build options are available: `./waf --help`.\n1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor).\n2) Compile: `./waf build`.\n3) Install: `./waf install --destdir=/path/to/any/output/directory`.\n\n#### Android (Windows/Linux/macOS)\nYou can just open the `android` folder in Android Studio and build from here, or use `gradlew` to build from command line."
  },
  {
    "path": "android/.gitignore",
    "content": ".gradle/\nbuild/\n.externalNativeBuild\n.cxx/\n.idea/\nlocal.properties\n.project\n.classpath\n.gradle\n.settings\nrelease/\n*.hprof\n.vscode/\n*.bak"
  },
  {
    "path": "android/app/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport java.time.LocalDateTime\nimport java.time.Month\nimport java.time.temporal.ChronoUnit\n\nplugins {\n\talias(libs.plugins.android.application)\n\talias(libs.plugins.kotlin.android)\n}\n\nandroid {\n\tnamespace = \"su.xash.engine\"\n\tndkVersion = \"28.2.13676358\"\n\tcompileSdk = 35\n\n\tdefaultConfig {\n\t\tapplicationId = \"su.xash.engine\"\n\t\tversionName = \"0.21-\" + getGitHash()\n\t\tversionCode = getBuildNum()\n\t\tminSdk = 21\n\t\ttargetSdk = 35\n\n\t\texternalNativeBuild {\n\t\t\tval engineRoot = projectDir.parentFile.parent\n\n\t\t\texperimentalProperties[\"ninja.abiFilters\"] = setOf(\"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\")\n\t\t\texperimentalProperties[\"ninja.path\"] = File(engineRoot, \"wscript\").path\n\t\t\texperimentalProperties[\"ninja.configure\"] = \"run-python\"\n\t\t\texperimentalProperties[\"ninja.arguments\"] = setOf(\n\t\t\t\tFile(engineRoot, \"scripts/configure-ninja.py\").path,\n\t\t\t\tengineRoot,\n\t\t\t\t\"--variant=\\${ndk.variantName}\",\n\t\t\t\t\"--abi=\\${ndk.abi}\",\n\t\t\t\t\"--configuration-dir=\\${ndk.buildRoot}\",\n\t\t\t\t\"--ndk-version=\\${ndk.moduleNdkVersion}\",\n\t\t\t\t\"--min-sdk-version=\\${ndk.minPlatform}\",\n\t\t\t\t\"--ndk-root=${android.ndkDirectory}\",\n\t\t\t\t// shut up, fake options\n\t\t\t\t\"-p:Configuration=\\${ndk.variantName}\",\n\t\t\t\t\"-p:Platform=\\${ndk.abi}\"\n\t\t\t)\n\t\t}\n\t}\n\n\tcompileOptions {\n\t\tsourceCompatibility = JavaVersion.VERSION_11\n\t\ttargetCompatibility = JavaVersion.VERSION_11\n\t}\n\n\tkotlin {\n\t\tcompilerOptions {\n\t\t\tjvmTarget = JvmTarget.JVM_11\n\t\t}\n\t}\n\n\tbuildFeatures {\n\t\tviewBinding = true\n\t\tbuildConfig = true\n\t}\n\n\tlint {\n\t\tabortOnError = false\n\t}\n\n/*\n\tandroidResources {\n\t\tnoCompress += \"\"\n\t}\n*/\n\n\tpackaging {\n\t\tjniLibs {\n\t\t\tkeepDebugSymbols.add(\"**/*.so\")\n\t\t\tuseLegacyPackaging = true\n\t\t}\n\t}\n\n\tsourceSets {\n\t\tgetByName(\"main\") {\n\t\t\tassets.srcDirs(\"../../3rdparty/extras/xash-extras\")\n\t\t\tjava.srcDir(\"../../3rdparty/SDL/android-project/app/src/main/java\")\n\t\t}\n\t}\n\n\tbuildTypes {\n\t\tdebug {\n\t\t\tisMinifyEnabled = false\n\t\t\tisShrinkResources = false\n\t\t\tisDebuggable = true\n\t\t\tapplicationIdSuffix = \".test\"\n\t\t\tproguardFiles(\n\t\t\t\tgetDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\"\n\t\t\t)\n\t\t}\n\n\t\trelease {\n\t\t\tisMinifyEnabled = true\n\t\t\tisShrinkResources = true\n\t\t\tproguardFiles(\n\t\t\t\tgetDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\"\n\t\t\t)\n\t\t}\n\n\t\tregister(\"asan\") {\n\t\t\tinitWith(getByName(\"debug\"))\n\t\t}\n\n\t\tregister(\"continuous\") {\n\t\t\tinitWith(getByName(\"release\"))\n\t\t\tapplicationIdSuffix = \".test\"\n\t\t}\n\t}\n}\n\ndependencies {\n\timplementation(libs.material)\n\n\timplementation(libs.appcompat)\n\timplementation(libs.navigation.runtime.ktx)\n\timplementation(libs.navigation.fragment.ktx)\n\timplementation(libs.navigation.ui.ktx)\n\timplementation(libs.preference.ktx)\n\timplementation(libs.swiperefreshlayout)\n\n\timplementation(libs.acra.http)\n}\n\nfun getBuildNum(): Int {\n\tval now = LocalDateTime.now()\n\tval releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0)\n\tval qBuildNum = releaseDate.until(now, ChronoUnit.DAYS)\n\tval minuteOfDay = now.hour * 60 + now.minute\n\treturn (qBuildNum * 10000 + minuteOfDay).toInt()\n}\n\nfun getGitHash(): String {\n\tval process = ProcessBuilder(\"git\", \"rev-parse\", \"--short\", \"HEAD\").directory(project.rootDir)\n\t\t.redirectErrorStream(true).start()\n\treturn process.inputStream.bufferedReader().readText().trim()\n}\n"
  },
  {
    "path": "android/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keep class su.xash.engine.XashActivity {\n    java.lang.String loadAndroidID();\n    java.lang.String getAndroidID();\n    void saveAndroidID(java.lang.String);\n    java.lang.String getCallingPackage();\n    java.lang.String[] getAssetsList(boolean, java.lang.String);\n    android.content.res.AssetManager getAssets(boolean);\n}\n\n-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {\n    void nativeCommitText(java.lang.String, int);\n    void nativeGenerateScancodeForUnichar(char);\n}\n\n-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {\n    # for some reason these aren't compatible with allowoptimization modifier\n    boolean supportsRelativeMouse();\n    void setWindowStyle(boolean);\n}\n\n-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {\n    java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it\n    boolean onNativeSoftReturnKey();\n    void onNativeKeyboardFocusLost();\n    boolean isScreenKeyboardShown();\n    android.util.DisplayMetrics getDisplayDPI();\n    java.lang.String clipboardGetText();\n    boolean clipboardHasText();\n    void clipboardSetText(java.lang.String);\n    int createCustomCursor(int[], int, int, int, int);\n    void destroyCustomCursor(int);\n    android.content.Context getContext();\n    boolean getManifestEnvironmentVariables();\n    android.view.Surface getNativeSurface();\n    void initTouch();\n    boolean isAndroidTV();\n    boolean isChromebook();\n    boolean isDeXMode();\n    boolean isTablet();\n    void manualBackButton();\n    int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);\n    void minimizeWindow();\n    int openURL(java.lang.String);\n    void requestPermission(java.lang.String, int);\n    int showToast(java.lang.String, int, int, int, int);\n    boolean sendMessage(int, int);\n    boolean setActivityTitle(java.lang.String);\n    boolean setCustomCursor(int);\n    void setOrientation(int, int, boolean, java.lang.String);\n    boolean setRelativeMouseEnabled(boolean);\n    boolean setSystemCursor(int);\n    boolean shouldMinimizeOnFocusLoss();\n    boolean showTextInput(int, int, int, int);\n}\n\n-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {\n    boolean initialize(boolean, boolean);\n    boolean openDevice(int);\n    int sendOutputReport(int, byte[]);\n    int sendFeatureReport(int, byte[]);\n    boolean getFeatureReport(int, byte[]);\n    void closeDevice(int);\n}\n\n-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {\n    int[] getAudioOutputDevices();\n    int[] getAudioInputDevices();\n    int[] audioOpen(int, int, int, int, int);\n    void audioWriteFloatBuffer(float[]);\n    void audioWriteShortBuffer(short[]);\n    void audioWriteByteBuffer(byte[]);\n    void audioClose();\n    int[] captureOpen(int, int, int, int, int);\n    int captureReadFloatBuffer(float[], boolean);\n    int captureReadShortBuffer(short[], boolean);\n    int captureReadByteBuffer(byte[], boolean);\n    void captureClose();\n    void audioSetThreadPriority(boolean, int);\n    native int nativeSetupJNI();\n    native void removeAudioDevice(boolean, int);\n    native void addAudioDevice(boolean, int);\n}\n\n-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {\n    void pollInputDevices();\n    void pollHapticDevices();\n    void hapticRun(int, float, int);\n    void hapticStop(int);\n}\n\n# Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor.\n-dontwarn javax.annotation.processing.Processor\n-dontwarn javax.annotation.processing.AbstractProcessor\n-dontwarn javax.annotation.processing.SupportedOptions\n"
  },
  {
    "path": "android/app/run-python",
    "content": "#!/bin/bash\n\nexec python $@\n"
  },
  {
    "path": "android/app/run-python.bat",
    "content": "python %*\n"
  },
  {
    "path": "android/app/src/asan/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">Xash3D FWGS (Test)</string>\n    <string name=\"authority\" translatable=\"false\">su.xash.engine.test.documents</string>\n</resources>"
  },
  {
    "path": "android/app/src/asan/resources/lib/arm64-v8a/wrap.sh",
    "content": "\n#!/system/bin/sh\nHERE=$(cd \"$(dirname \"$0\")\" && pwd)\ncmd=$1\nshift\n# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-\n# bit app running on a 64-bit device, the 64-bit getprop will fail to load\n# because it will preload a 32-bit ASan runtime.\n# https://github.com/android/ndk/issues/1744\nos_version=$(getprop ro.build.version.sdk)\nif [ \"$os_version\" -eq \"27\" ]; then\n  cmd=\"$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelif [ \"$os_version\" -eq \"28\" ]; then\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelse\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@\"\nfi\nexport ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1\nASAN_LIB=$(ls \"$HERE\"/libclang_rt.asan-*-android.so)\nif [ -f \"$HERE/libc++_shared.so\" ]; then\n    # Workaround for https://github.com/android-ndk/ndk/issues/988.\n    export LD_PRELOAD=\"$ASAN_LIB $HERE/libc++_shared.so\"\nelse\n    export LD_PRELOAD=\"$ASAN_LIB\"\nfi\nexec $cmd"
  },
  {
    "path": "android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh",
    "content": "\n#!/system/bin/sh\nHERE=$(cd \"$(dirname \"$0\")\" && pwd)\ncmd=$1\nshift\n# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-\n# bit app running on a 64-bit device, the 64-bit getprop will fail to load\n# because it will preload a 32-bit ASan runtime.\n# https://github.com/android/ndk/issues/1744\nos_version=$(getprop ro.build.version.sdk)\nif [ \"$os_version\" -eq \"27\" ]; then\n  cmd=\"$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelif [ \"$os_version\" -eq \"28\" ]; then\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelse\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@\"\nfi\nexport ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1\nASAN_LIB=$(ls \"$HERE\"/libclang_rt.asan-*-android.so)\nif [ -f \"$HERE/libc++_shared.so\" ]; then\n    # Workaround for https://github.com/android-ndk/ndk/issues/988.\n    export LD_PRELOAD=\"$ASAN_LIB $HERE/libc++_shared.so\"\nelse\n    export LD_PRELOAD=\"$ASAN_LIB\"\nfi\nexec $cmd"
  },
  {
    "path": "android/app/src/asan/resources/lib/x86_64/wrap.sh",
    "content": "\n#!/system/bin/sh\nHERE=$(cd \"$(dirname \"$0\")\" && pwd)\ncmd=$1\nshift\n# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32-\n# bit app running on a 64-bit device, the 64-bit getprop will fail to load\n# because it will preload a 32-bit ASan runtime.\n# https://github.com/android/ndk/issues/1744\nos_version=$(getprop ro.build.version.sdk)\nif [ \"$os_version\" -eq \"27\" ]; then\n  cmd=\"$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelif [ \"$os_version\" -eq \"28\" ]; then\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@\"\nelse\n  cmd=\"$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@\"\nfi\nexport ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1\nASAN_LIB=$(ls \"$HERE\"/libclang_rt.asan-*-android.so)\nif [ -f \"$HERE/libc++_shared.so\" ]; then\n    # Workaround for https://github.com/android-ndk/ndk/issues/988.\n    export LD_PRELOAD=\"$ASAN_LIB $HERE/libc++_shared.so\"\nelse\n    export LD_PRELOAD=\"$ASAN_LIB\"\nfi\nexec $cmd"
  },
  {
    "path": "android/app/src/continuous/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">Xash3D FWGS (Test)</string>\n    <string name=\"authority\" translatable=\"false\">su.xash.engine.test.documents</string>\n</resources>"
  },
  {
    "path": "android/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\tandroid:allowAudioPlaybackCapture=\"true\"\n\tandroid:installLocation=\"preferExternal\">\n\t<!-- OpenGL ES 1.1 -->\n\t<uses-feature android:glEsVersion=\"0x00010000\" />\n\t<!-- Touchscreen support -->\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.touchscreen\"\n\t\tandroid:required=\"false\" />\n\t<!-- Game controller support -->\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.bluetooth\"\n\t\tandroid:required=\"false\" />\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.gamepad\"\n\t\tandroid:required=\"false\" />\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.usb.host\"\n\t\tandroid:required=\"false\" />\n\t<!-- External mouse input events -->\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.type.pc\"\n\t\tandroid:required=\"false\" />\n\t<!-- Audio recording support -->\n\t<uses-feature\n\t\tandroid:name=\"android.hardware.microphone\"\n\t\tandroid:required=\"false\" />\n\t<!-- Allow downloading to the external storage on Android 5.1 and older -->\n\t<uses-permission\n\t\tandroid:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n\t\tandroid:maxSdkVersion=\"29\" />\n\t<uses-permission\n\t\tandroid:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n\t\tandroid:maxSdkVersion=\"29\" />\n\t<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\" />\n\t<!-- Allow access to Bluetooth devices -->\n\t<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->\n\t<uses-permission\n\t\tandroid:name=\"android.permission.BLUETOOTH\"\n\t\tandroid:maxSdkVersion=\"30\" />\n\t<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\n\t<!-- Allow access to the vibrator -->\n\t<uses-permission android:name=\"android.permission.VIBRATE\" />\n\t<!-- Allow recording audio -->\n\t<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n\t<!-- Allow internet access -->\n\t<uses-permission android:name=\"android.permission.INTERNET\" />\n\t<!-- Dedicated server -->\n\t<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n\n\t<application\n\t\tandroid:name=\".MainApplication\"\n\t\tandroid:allowBackup=\"true\"\n\t\tandroid:hardwareAccelerated=\"true\"\n\t\tandroid:icon=\"@mipmap/ic_launcher\"\n\t\tandroid:label=\"@string/app_name\"\n\t\tandroid:roundIcon=\"@mipmap/ic_launcher\"\n\t\tandroid:theme=\"@style/Theme.App\"\n\t\tandroid:usesCleartextTraffic=\"true\"\n\t\tandroid:requestLegacyExternalStorage=\"true\">\n\t\t<activity\n\t\t\tandroid:name=\".MainActivity\"\n\t\t\tandroid:exported=\"true\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.intent.action.MAIN\" />\n\n\t\t\t\t<category android:name=\"android.intent.category.LAUNCHER\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t\t<activity\n\t\t\tandroid:name=\".XashActivity\"\n\t\t\tandroid:alwaysRetainTaskState=\"true\"\n\t\t\tandroid:configChanges=\"orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation|touchscreen\"\n\t\t\tandroid:exported=\"true\"\n\t\t\tandroid:launchMode=\"singleTask\"\n\t\t\tandroid:preferMinimalPostProcessing=\"true\"\n\t\t\tandroid:windowSoftInputMode=\"adjustResize\">\n\t\t\t<intent-filter>\n\t\t\t\t<action android:name=\"android.hardware.usb.action.USB_DEVICE_ATTACHED\" />\n\t\t\t</intent-filter>\n\t\t</activity>\n\t</application>\n\t<queries>\n\t\t<intent>\n\t\t\t<action android:name=\"su.xash.engine.MOD\" />\n\t\t</intent>\n\t</queries>\n</manifest>\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/DedicatedActivity.kt",
    "content": "package su.xash.engine\n\nclass DedicatedActivity {}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/DedicatedService.kt",
    "content": "package su.xash.engine\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\nclass DedicatedService : Service() {\n\toverride fun onBind(intent: Intent?): IBinder? {\n\t\tTODO(\"Not yet implemented\")\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/MainActivity.kt",
    "content": "package su.xash.engine\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.navigation.NavController\nimport androidx.navigation.fragment.NavHostFragment\nimport androidx.navigation.ui.AppBarConfiguration\nimport androidx.navigation.ui.navigateUp\nimport androidx.navigation.ui.setupActionBarWithNavController\nimport su.xash.engine.databinding.ActivityMainBinding\n\nclass MainActivity : AppCompatActivity() {\n\tprivate lateinit var binding: ActivityMainBinding\n\tprivate lateinit var appBarConfiguration: AppBarConfiguration\n\tprivate lateinit var navController: NavController\n\n\toverride fun onCreate(savedInstanceState: Bundle?) {\n\t\tsuper.onCreate(savedInstanceState)\n\n\t\tbinding = ActivityMainBinding.inflate(layoutInflater)\n\t\tsetContentView(binding.root)\n\n\t\tsetSupportActionBar(binding.toolbar)\n\n\t\tval navHostFragment =\n\t\t\tsupportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment\n\t\tnavController = navHostFragment.navController\n\t\tappBarConfiguration = AppBarConfiguration(navController.graph)\n\t\tsetupActionBarWithNavController(navController, appBarConfiguration)\n\t}\n\n\toverride fun onSupportNavigateUp(): Boolean {\n\t\treturn navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/MainApplication.kt",
    "content": "package su.xash.engine\n\nimport android.app.Application\nimport android.content.Context\nimport android.os.StrictMode\nimport org.acra.data.StringFormat\nimport org.acra.ktx.initAcra\n\nclass MainApplication : Application() {\n\toverride fun attachBaseContext(base: Context?) {\n\t\tsuper.attachBaseContext(base)\n\n\t\tif (!BuildConfig.DEBUG) {\n            initAcra {\n                buildConfigClass = BuildConfig::class.java\n                reportFormat = StringFormat.JSON\n\n//                httpSender {\n//                    uri = \"http://bodis.pp.ua:5000/report\"\n//                }\n            }\n\t\t} else {\n\t\t\t// enable strict mode to detect memory leaks etc.\n\t\t\tStrictMode.enableDefaults();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/XashActivity.java",
    "content": "package su.xash.engine;\n\nimport android.annotation.SuppressLint;\nimport android.content.pm.ActivityInfo;\nimport android.content.res.AssetManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.provider.Settings.Secure;\nimport android.util.Log;\nimport android.view.KeyEvent;\nimport android.view.WindowManager;\n\nimport org.libsdl.app.SDLActivity;\n\nimport su.xash.engine.util.AndroidBug5497Workaround;\n\npublic class XashActivity extends SDLActivity {\n\tprivate boolean mUseVolumeKeys;\n\tprivate String mPackageName;\n\tprivate static final String TAG = \"XashActivity\";\n\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\tsuper.onCreate(savedInstanceState);\n\n\t\tsetRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n\t\t\t//getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);\n\t\t\tgetWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;\n\t\t}\n\n\t\tAndroidBug5497Workaround.assistActivity(this);\n\t}\n\n\t@Override\n\tpublic void onDestroy() {\n\t\tsuper.onDestroy();\n\n\t\t// Now that we don't exit from native code, we need to exit here, resetting\n\t\t// application state (actually global variables that we don't cleanup on exit)\n\t\t//\n\t\t// When the issue with global variables will be resolved, remove that exit() call\n\t\tSystem.exit(0);\n\t}\n\n\t@Override\n\tprotected String[] getLibraries() {\n\t\treturn new String[]{\"SDL2\", \"xash\"};\n\t}\n\n\t@SuppressLint(\"HardwareIds\")\n\tprivate String getAndroidID() {\n\t\treturn Secure.getString(getContentResolver(), Secure.ANDROID_ID);\n\t}\n\n\t@SuppressLint(\"ApplySharedPref\")\n\tprivate void saveAndroidID(String id) {\n\t\tgetSharedPreferences(\"xash_preferences\", MODE_PRIVATE).edit().putString(\"xash_id\", id).commit();\n\t}\n\n\tprivate String loadAndroidID() {\n\t\treturn getSharedPreferences(\"xash_preferences\", MODE_PRIVATE).getString(\"xash_id\", \"\");\n\t}\n\n\t@Override\n\tpublic String getCallingPackage() {\n\t\tif (mPackageName != null) {\n\t\t\treturn mPackageName;\n\t\t}\n\n\t\treturn super.getCallingPackage();\n\t}\n\n\tprivate AssetManager getAssets(boolean isEngine) {\n\t\tAssetManager am = null;\n\n\t\tif (isEngine) {\n\t\t\tam = getAssets();\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tam = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets();\n\t\t\t} catch (Exception e) {\n\t\t\t\tLog.e(TAG, \"Unable to load mod assets!\");\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\n\t\treturn am;\n\t}\n\n\tprivate String[] getAssetsList(boolean isEngine, String path) {\n\t\tAssetManager am = getAssets(isEngine);\n\n\t\ttry {\n\t\t\treturn am.list(path);\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t}\n\n\t\treturn new String[]{};\n\t}\n\n\t@Override\n\tpublic boolean dispatchKeyEvent(KeyEvent event) {\n\t\tif (SDLActivity.mBrokenLibraries) {\n\t\t\treturn false;\n\t\t}\n\n\t\tint keyCode = event.getKeyCode();\n\t\tif (!mUseVolumeKeys) {\n\t\t\tif (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn getWindow().superDispatchKeyEvent(event);\n\t}\n\n\t// TODO: REMOVE LATER, temporary launchers support?\n\t@Override\n\tprotected String[] getArguments() {\n\t\tString gamedir = getIntent().getStringExtra(\"gamedir\");\n\t\tif (gamedir == null) gamedir = \"valve\";\n\t\tnativeSetenv(\"XASH3D_GAME\", gamedir);\n\n\t\tString gamelibdir = getIntent().getStringExtra(\"gamelibdir\");\n\t\tif (gamelibdir != null) nativeSetenv(\"XASH3D_GAMELIBDIR\", gamelibdir);\n\n\t\tString pakfile = getIntent().getStringExtra(\"pakfile\");\n\t\tif (pakfile != null) nativeSetenv(\"XASH3D_EXTRAS_PAK2\", pakfile);\n\n\t\tString basedir = getIntent().getStringExtra(\"basedir\");\n\t\tif (basedir != null) {\n\t\t\tnativeSetenv(\"XASH3D_BASEDIR\", basedir);\n\t\t} else {\n\t\t\tString rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + \"/xash\";\n\t\t\tnativeSetenv(\"XASH3D_BASEDIR\", rootPath);\n\t\t}\n\n\t\tmUseVolumeKeys = getIntent().getBooleanExtra(\"usevolume\", false);\n\t\tmPackageName = getIntent().getStringExtra(\"package\");\n\n\t\tString[] env = getIntent().getStringArrayExtra(\"env\");\n\t\tif (env != null) {\n\t\t\tfor (int i = 0; i < env.length; i += 2)\n\t\t\t\tnativeSetenv(env[i], env[i + 1]);\n\t\t}\n\n\t\tString argv = getIntent().getStringExtra(\"argv\");\n\t\tif (argv == null) argv = \"-console -log\";\n\t\treturn argv.split(\" \");\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/adapters/GameAdapter.kt",
    "content": "package su.xash.engine.adapters\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.navigation.findNavController\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport su.xash.engine.R\nimport su.xash.engine.databinding.CardGameBinding\nimport su.xash.engine.model.Game\nimport su.xash.engine.ui.library.LibraryViewModel\n\n\nclass GameAdapter(private val libraryViewModel: LibraryViewModel) :\n\tListAdapter<Game, GameAdapter.GameViewHolder>(DiffCallback()) {\n\n\toverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder {\n\t\tval binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)\n\t\treturn GameViewHolder(binding)\n\t}\n\n\toverride fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) {\n\t\treturn holder.bind(getItem(position))\n\t}\n\n\tprivate class DiffCallback : DiffUtil.ItemCallback<Game>() {\n\t\toverride fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {\n\t\t\treturn oldItem.basedir.name == newItem.basedir.name\n\t\t}\n\n\t\toverride fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {\n\t\t\treturn oldItem.basedir.name == newItem.basedir.name\n\t\t}\n\n\t}\n\n\tinner class GameViewHolder(val binding: CardGameBinding) :\n\t\tRecyclerView.ViewHolder(binding.root) {\n\t\tfun bind(game: Game) {\n\t\t\tbinding.apply {\n\t\t\t\tgameTitle.text = game.title\n\n\t\t\t\tif (game.icon != null) {\n\t\t\t\t\tgameIcon.setImageBitmap(game.icon)\n\t\t\t\t} else {\n\t\t\t\t\tgameIcon.visibility = View.GONE\n\t\t\t\t}\n\n\t\t\t\tif (game.cover != null) {\n\t\t\t\t\tgameCover.setImageBitmap(game.cover)\n\t\t\t\t} else {\n\t\t\t\t\tgameCover.visibility = View.GONE\n\t\t\t\t}\n\n\t\t\t\tsettingsButton.setOnClickListener {\n\t\t\t\t\tlibraryViewModel.setSelectedGame(game)\n\t\t\t\t\tit.findNavController()\n\t\t\t\t\t\t.navigate(R.id.action_libraryFragment_to_gameSettingsFragment)\n\t\t\t\t}\n\n\t\t\t\troot.setOnClickListener { libraryViewModel.startEngine(it.context, game) }\n\t\t\t\tlaunchButton.setOnClickListener {\n\t\t\t\t\tlibraryViewModel.startEngine(it.context, game)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/model/BackgroundBitmap.kt",
    "content": "package su.xash.engine.model\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport su.xash.engine.util.TGAReader\nimport java.io.File\nimport java.io.FileInputStream\nimport java.util.Scanner\n\n\nobject BackgroundBitmap {\n\tprivate const val BACKGROUND_ROWS = 3\n\tprivate const val BACKGROUND_COLUMNS = 4\n\tprivate const val BACKGROUND_WIDTH = 800\n\tprivate const val BACKGROUND_HEIGHT = 600\n\n\tfun createBackground(file: File): Bitmap {\n\t\tvar bitmap =\n\t\t\tBitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888)\n\t\tvar canvas = Canvas(bitmap)\n\t\tvar x: Int\n\t\tvar y = 0\n\t\tvar width: Int\n\t\tvar height = 0\n\n\t\tval resourceFolder = File(file, \"resource\")\n\t\tvar bgLayout = File(resourceFolder, \"HD_BackgroundLayout.txt\")\n\t\tif (!bgLayout.exists()) {\n\t\t\tbgLayout = File(resourceFolder, \"BackgroundLayout.txt\")\n\t\t}\n\n\t\tif (!bgLayout.exists()) {\n\t\t\tval dir = File(resourceFolder, \"background\")\n\t\t\tfor (i in 0 until BACKGROUND_ROWS) {\n\t\t\t\tx = 0\n\t\t\t\tfor (j in 0 until BACKGROUND_COLUMNS) {\n\t\t\t\t\tval filename = \"${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga\"\n\t\t\t\t\tval bmpFile = File(dir, filename)\n\t\t\t\t\tval bmpImage = loadTga(bmpFile)\n\n\t\t\t\t\tcanvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null)\n\t\t\t\t\tx += bmpImage.width\n\t\t\t\t\theight = bmpImage.height\n\n\t\t\t\t}\n\t\t\t\ty += height\n\t\t\t}\n\t\t\treturn bitmap\n\t\t}\n\n\t\tFileInputStream(bgLayout).use { inputStream ->\n\t\t\tScanner(inputStream).use { scanner ->\n\t\t\t\twhile (scanner.hasNext()) {\n\t\t\t\t\twhen (val str = scanner.next()) {\n\t\t\t\t\t\t\"resolution\" -> {\n\t\t\t\t\t\t\twidth = scanner.nextInt()\n\t\t\t\t\t\t\theight = scanner.nextInt()\n\t\t\t\t\t\t\tbitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)\n\t\t\t\t\t\t\tcanvas = Canvas(bitmap)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\telse -> {\n\t\t\t\t\t\t\tvar bmpFile = file\n\t\t\t\t\t\t\tstr.split(\"/\").forEach { bmpFile = File(bmpFile, it) }\n\t\t\t\t\t\t\t//skip\n\t\t\t\t\t\t\tscanner.next()\n\t\t\t\t\t\t\tx = scanner.nextInt()\n\t\t\t\t\t\t\ty = scanner.nextInt()\n\t\t\t\t\t\t\tval bmp = loadTga(bmpFile)\n\t\t\t\t\t\t\tcanvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn bitmap\n\t}\n\n\tprivate fun loadTga(file: File): Bitmap {\n\t\tFileInputStream(file).use {\n\t\t\tval buffer = it.readBytes()\n\t\t\tval pixels = TGAReader.read(buffer, TGAReader.ARGB)\n\n\t\t\tval width = TGAReader.getWidth(buffer)\n\t\t\tval height = TGAReader.getHeight(buffer)\n\n\t\t\treturn Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/model/Game.kt",
    "content": "package su.xash.engine.model\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport su.xash.engine.XashActivity\nimport java.io.File\nimport java.io.FileInputStream\n\n\nclass Game(val ctx: Context, val basedir: File) {\n\tprivate var iconName = \"game.ico\"\n\tvar title = \"Unknown Game\"\n\tvar icon: Bitmap? = null\n\tvar cover: Bitmap? = null\n\n\tprivate val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE)\n\n\tinit {\n\t\tval gameInfo = File(basedir, \"gameinfo.txt\")\n\t\tif (gameInfo.exists()) {\n\t\t\tparseGameInfo(gameInfo)\n\t\t} else {\n\t\t\tval libListGam = File(basedir, \"liblist.gam\")\n\t\t\tif (libListGam.exists()) parseGameInfo(libListGam)\n\t\t}\n\n\t\tval iconFile = File(basedir, iconName)\n\t\tif (iconFile.exists()) {\n\t\t\ticon = BitmapFactory.decodeFile(iconFile.path)\n\t\t}\n\n\t\ttry {\n\t\t\tcover = BackgroundBitmap.createBackground(basedir)\n\t\t} catch (e: Exception) {\n\t\t\te.printStackTrace()\n\t\t}\n\t}\n\n\tfun startEngine(ctx: Context) {\n\t\tctx.startActivity(Intent(ctx, XashActivity::class.java).apply {\n\t\t\tflags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK\n\t\t\tputExtra(\"gamedir\", basedir.name)\n\t\t\tputExtra(\"argv\", pref.getString(\"arguments\", \"-console -log\"))\n\t\t\tputExtra(\"usevolume\", pref.getBoolean(\"use_volume_buttons\", false))\n\t\t\tputExtra(\"basedir\", basedir.parent)\n\t\t\t//.putExtra(\"gamelibdir\", getGameLibDir(context))\n\t\t\t//.putExtra(\"package\", getPackageName()) }\n\t\t})\n\t}\n\n\tprivate fun parseGameInfo(file: File) {\n\t\tFileInputStream(file).use { inputStream ->\n\t\t\tinputStream.bufferedReader().use { reader ->\n\t\t\t\treader.forEachLine {\n\t\t\t\t\tval tokens = it.split(\"\\\\s+\".toRegex(), limit = 2)\n\t\t\t\t\tif (tokens.size >= 2) {\n\t\t\t\t\t\tval k = tokens[0]\n\t\t\t\t\t\tval v = tokens[1].trim('\"')\n\n\t\t\t\t\t\tif (k == \"title\" || k == \"game\") title = v\n\t\t\t\t\t\tif (k == \"icon\") iconName = v\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate fun getPackageName(): String? {\n//        return if (mDbEntry != null) {\n//            mDbEntry.getPackageName()\n//        } else null\n\t\treturn null\n\t}\n\n\tprivate fun getGameLibDir(ctx: Context): String? {\n\t\tval pkgName = getPackageName()\n\t\tif (pkgName != null) {\n\t\t\tval pkgInfo: PackageInfo = try {\n\t\t\t\tctx.packageManager.getPackageInfo(pkgName, 0)\n\t\t\t} catch (e: PackageManager.NameNotFoundException) {\n\t\t\t\te.printStackTrace()\n\t\t\t\tctx.startActivity(\n\t\t\t\t\tIntent(\n\t\t\t\t\t\tIntent.ACTION_VIEW, Uri.parse(\"market://details?id=$pkgName\")\n\t\t\t\t\t).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)\n\t\t\t\t)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn pkgInfo.applicationInfo?.nativeLibraryDir\n\t\t}\n\t\treturn ctx.applicationInfo.nativeLibraryDir\n\t}\n\n\tcompanion object {\n\t\tfun getGames(ctx: Context, file: File): List<Game> {\n\t\t\tval games = mutableListOf<Game>()\n\n\t\t\tif (checkIfGamedir(file)) {\n\t\t\t\tgames.add(Game(ctx, file))\n\t\t\t} else {\n\t\t\t\tfile.listFiles()?.forEach {\n\t\t\t\t\tif (it.isDirectory) {\n\t\t\t\t\t\tif (checkIfGamedir(it)) {\n\t\t\t\t\t\t\tgames.add(Game(ctx, it))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn games\n\t\t}\n\n\t\tfun checkIfGamedir(file: File): Boolean {\n\t\t\tif (File(file, \"liblist.gam\").exists()) return true\n\t\t\tif (File(file, \"gameinfo.txt\").exists()) return true\n\n\t\t\treturn false\n\t\t}\n\t}\n}\n\n//    Intent intent = new Intent(\"su.xash.engine.MOD\");\n//                for (ResolveInfo info : context.getPackageManager()\n//                        .queryIntentActivities(intent, PackageManager.GET_META_DATA)) {\n//                        String packageName = info.activityInfo.applicationInfo.packageName;\n//                        String gameDir = info.activityInfo.applicationInfo.metaData.getString(\n//                        \"su.xash.engine.gamedir\");\n//                        Log.d(TAG, \"package = \" + packageName + \" gamedir = \" + gameDir);\n//                        }\n\n//public void startEngine(Context context) {\n//    context.startActivity(new Intent(context, XashActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra(\"gamedir\", getGameDir()).putExtra(\"argv\", getArguments()).putExtra(\"usevolume\", getVolumeState()).putExtra(\"gamelibdir\", getGameLibDir(context)).putExtra(\"package\", getPackageName()));\n//}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt",
    "content": "package su.xash.engine.ui.library\n\nimport android.Manifest\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Environment\nimport android.provider.Settings\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.navigation.fragment.findNavController\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport su.xash.engine.BuildConfig\nimport su.xash.engine.R\nimport su.xash.engine.adapters.GameAdapter\nimport su.xash.engine.databinding.FragmentLibraryBinding\n\n\nclass LibraryFragment : Fragment(), MenuProvider {\n\tprivate var _binding: FragmentLibraryBinding? = null\n\tprivate val binding get() = _binding!!\n\n\tprivate val libraryViewModel: LibraryViewModel by activityViewModels()\n\n\tprivate val startActivityForResult =\n\t\tregisterForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n\t\t\tif (checkStoragePermissions()) {\n\t\t\t\tlibraryViewModel.reloadGames(requireContext())\n\t\t\t}\n\t\t}\n\n\tprivate val requiredPermissions = arrayOf(\n\t\tManifest.permission.READ_EXTERNAL_STORAGE,\n\t\tManifest.permission.WRITE_EXTERNAL_STORAGE\n\t)\n\n\tprivate val requestPermissionLauncher = registerForActivityResult(\n\t\tActivityResultContracts.RequestMultiplePermissions()\n\t) { permissions ->\n\t\tval granted = permissions.entries.all { it.value }\n\t\tif (granted) {\n\t\t\tlibraryViewModel.reloadGames(requireContext())\n\t\t} else {\n\t\t\tcheckStoragePermissions()\n\t\t}\n\t}\n\n\tprivate fun checkStoragePermissions(): Boolean {\n\t\tif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n\t\t\tif (!Environment.isExternalStorageManager())\n\t\t\t\tMaterialAlertDialogBuilder(requireContext()).apply {\n\t\t\t\t\tsetTitle(R.string.file_access_required)\n\t\t\t\t\tsetMessage(R.string.file_access_message)\n\t\t\t\t\tsetPositiveButton(android.R.string.ok) { _, _ ->\n\t\t\t\t\t\tstartActivityForResult.launch(\n\t\t\t\t\t\t\tIntent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData(\n\t\t\t\t\t\t\t\tUri.fromParts(\"package\", BuildConfig.APPLICATION_ID, null)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tsetCancelable(false)\n\t\t\t\t\tshow()\n\n\t\t\t\t\treturn false\n\t\t\t\t} else {\n\t\t\t\treturn true\n\t\t\t}\n\t\t} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n\t\t\tval permissionsNeeded = requiredPermissions.filter {\n\t\t\t\tContextCompat.checkSelfPermission(\n\t\t\t\t\trequireContext(),\n\t\t\t\t\tit\n\t\t\t\t) != PackageManager.PERMISSION_GRANTED\n\t\t\t}.toTypedArray()\n\n\t\t\tif (!permissionsNeeded.isEmpty()) {\n\t\t\t\tval showRationale = permissionsNeeded.any {\n\t\t\t\t\tActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it)\n\t\t\t\t}\n\n\t\t\t\tMaterialAlertDialogBuilder(requireContext()).apply {\n\t\t\t\t\tsetTitle(R.string.external_storage_required)\n\t\t\t\t\tsetMessage(R.string.external_storage_message)\n\t\t\t\t\tsetPositiveButton(android.R.string.ok) { _, _ ->\n\t\t\t\t\t\tif (showRationale) {\n\t\t\t\t\t\t\trequestPermissionLauncher.launch(permissionsNeeded)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tval intent =\n\t\t\t\t\t\t\t\tIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\n\t\t\t\t\t\t\t\t\tdata =\n\t\t\t\t\t\t\t\t\t\tUri.fromParts(\"package\", requireContext().packageName, null)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tstartActivity(intent)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsetCancelable(false)\n\t\t\t\t\tshow()\n\t\t\t\t}\n\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\treturn true\n\t\t\t}\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t}\n\n\toverride fun onCreateView(\n\t\tinflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n\t): View {\n\t\t_binding = FragmentLibraryBinding.inflate(inflater, container, false)\n\n\t\tval adapter = GameAdapter(libraryViewModel)\n\t\tbinding.gamesList.adapter = adapter\n\n\t\trequireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)\n\n\t\treturn binding.root\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tbinding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) }\n\n\t\tlibraryViewModel.isReloading.observe(viewLifecycleOwner) {\n\t\t\tbinding.swipeRefresh.isRefreshing = it\n\t\t}\n\n\t\tlibraryViewModel.installedGames.observe(viewLifecycleOwner) {\n\t\t\t(binding.gamesList.adapter as GameAdapter).submitList(it)\n\t\t}\n\n\t\tif (checkStoragePermissions()) {\n\t\t\tlibraryViewModel.reloadGames(requireContext())\n\t\t}\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\t_binding = null\n\t}\n\n\toverride fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n\t\tmenuInflater.inflate(R.menu.menu_library, menu)\n\t}\n\n\toverride fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n\t\twhen (menuItem.itemId) {\n\t\t\tR.id.action_settings -> {\n\t\t\t\tfindNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment)\n\t\t\t}\n\t\t}\n\n\t\treturn false\n\t}\n\n\toverride fun onResume() {\n\t\tsuper.onResume()\n\n\t\tif (checkStoragePermissions()) {\n\t\t\tlibraryViewModel.reloadGames(requireContext())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/library/LibraryViewModel.kt",
    "content": "package su.xash.engine.ui.library\n\nimport android.app.Application\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.os.Environment\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport su.xash.engine.model.Game\nimport java.io.File\n\nclass LibraryViewModel(application: Application) : AndroidViewModel(application) {\n\tval installedGames: LiveData<List<Game>> get() = _installedGames\n\tprivate val _installedGames = MutableLiveData(emptyList<Game>())\n\n\tval isReloading: LiveData<Boolean> get() = _isReloading\n\tprivate val _isReloading = MutableLiveData(false)\n\n\tval selectedItem: LiveData<Game> get() = _selectedItem\n\tprivate val _selectedItem = MutableLiveData<Game>()\n\n\tprivate val appPreferences: SharedPreferences =\n\t\tapplication.getSharedPreferences(\"app_preferences\", Context.MODE_PRIVATE)\n\n\tfun reloadGames(ctx: Context) {\n\t\tif (isReloading.value == true) {\n\t\t\treturn\n\t\t}\n\t\t_isReloading.value = true\n\n\t\tviewModelScope.launch {\n\t\t\twithContext(Dispatchers.IO) {\n\t\t\t\tval rootPath = appPreferences.getString(\"game_path\", null)\n\t\t\t\t\t?: (Environment.getExternalStorageDirectory().absolutePath + \"/xash\")\n\t\t\t\tval root = File(rootPath)\n\n\t\t\t\t_installedGames.postValue(Game.getGames(ctx, root))\n\t\t\t\t_isReloading.postValue(false)\n\t\t\t}\n\t\t}\n\t}\n\n\tfun setSelectedGame(game: Game) {\n\t\t_selectedItem.value = game\n\t}\n\n\tfun startEngine(ctx: Context, game: Game) {\n\t\tgame.startEngine(ctx)\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsFragment.kt",
    "content": "package su.xash.engine.ui.settings\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport su.xash.engine.databinding.FragmentAppSettingsBinding\n\nclass AppSettingsFragment : Fragment() {\n\tprivate var _binding: FragmentAppSettingsBinding? = null\n\tprivate val binding get() = _binding!!\n\n\toverride fun onCreateView(\n\t\tinflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n\t): View {\n\t\t_binding = FragmentAppSettingsBinding.inflate(inflater, container, false)\n\t\treturn binding.root\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\t\tchildFragmentManager.beginTransaction()\n\t\t\t.add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit();\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\t_binding = null\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsPreferenceFragment.kt",
    "content": "package su.xash.engine.ui.settings\n\nimport android.os.Bundle\nimport androidx.preference.PreferenceFragmentCompat\nimport su.xash.engine.R\n\nclass AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\tpreferenceManager.sharedPreferencesName = \"app_preferences\";\n\t\tsetPreferencesFromResource(R.xml.app_preferences, rootKey);\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsFragment.kt",
    "content": "package su.xash.engine.ui.settings\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.activityViewModels\nimport su.xash.engine.databinding.FragmentGameSettingsBinding\nimport su.xash.engine.ui.library.LibraryViewModel\n\nclass GameSettingsFragment : Fragment() {\n\tprivate var _binding: FragmentGameSettingsBinding? = null\n\tprivate val binding get() = _binding!!\n\n\tprivate val libraryViewModel: LibraryViewModel by activityViewModels()\n\n\toverride fun onCreateView(\n\t\tinflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n\t): View {\n\t\t_binding = FragmentGameSettingsBinding.inflate(inflater, container, false)\n\t\treturn binding.root\n\t}\n\n\toverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n\t\tsuper.onViewCreated(view, savedInstanceState)\n\n\t\tval game = libraryViewModel.selectedItem.value!!\n\n\t\tbinding.gameCard.apply {\n\t\t\tgameTitle.text = game.title\n\n\t\t\tif (game.icon != null) {\n\t\t\t\tgameIcon.setImageBitmap(game.icon)\n\t\t\t} else {\n\t\t\t\tgameIcon.visibility = View.GONE\n\t\t\t}\n\n\t\t\tif (game.cover != null) {\n\t\t\t\tgameCover.setImageBitmap(game.cover)\n\t\t\t} else {\n\t\t\t\tgameCover.visibility = View.GONE\n\t\t\t}\n\n\t\t\tbuttonsContainer.visibility = View.GONE\n\t\t}\n\n\t\tchildFragmentManager.beginTransaction()\n\t\t\t.add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game))\n\t\t\t.commit();\n\t}\n\n\toverride fun onDestroyView() {\n\t\tsuper.onDestroyView()\n\t\t_binding = null\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsPreferenceFragment.kt",
    "content": "package su.xash.engine.ui.settings\n\nimport android.os.Bundle\nimport androidx.preference.ListPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.SwitchPreferenceCompat\nimport su.xash.engine.R\nimport su.xash.engine.model.Game\n\nclass GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {\n\toverride fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n\t\tpreferenceManager.sharedPreferencesName = game.basedir.name;\n\t\tsetPreferencesFromResource(R.xml.game_preferences, rootKey);\n\n\t\tval packageList = findPreference<ListPreference>(\"package_name\")!!\n\t\tpackageList.entries = arrayOf(getString(R.string.app_name))\n\t\tpackageList.entryValues = arrayOf(requireContext().packageName)\n\n\t\tif (packageList.value == null) {\n\t\t\tpackageList.setValueIndex(0);\n\t\t}\n\n\t\tval separatePackages = findPreference<SwitchPreferenceCompat>(\"separate_libraries\")!!\n\t\tval clientPackage = findPreference<ListPreference>(\"client_package\")!!\n\t\tval serverPackage = findPreference<ListPreference>(\"server_package\")!!\n\t\tseparatePackages.setOnPreferenceChangeListener { _, newValue ->\n\t\t\tif (newValue == true) {\n\t\t\t\tpackageList.isVisible = false\n\t\t\t\tclientPackage.isVisible = true\n\t\t\t\tserverPackage.isVisible = true\n\t\t\t} else {\n\t\t\t\tpackageList.isVisible = true\n\t\t\t\tclientPackage.isVisible = false\n\t\t\t\tserverPackage.isVisible = false\n\t\t\t}\n\n\t\t\ttrue\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/util/AndroidBug5497Workaround.java",
    "content": "package su.xash.engine.util;\n\nimport android.app.Activity;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.widget.FrameLayout;\n\npublic class AndroidBug5497Workaround {\n\t// For more information, see https://code.google.com/p/android/issues/detail?id=5497\n\t// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.\n\n\tpublic static void assistActivity(Activity activity) {\n\t\tnew AndroidBug5497Workaround(activity);\n\t}\n\n\tprivate View mChildOfContent;\n\tprivate int usableHeightPrevious;\n\tprivate FrameLayout.LayoutParams frameLayoutParams;\n\n\tprivate AndroidBug5497Workaround(Activity activity) {\n\t\tFrameLayout content = activity.findViewById(android.R.id.content);\n\t\tmChildOfContent = content.getChildAt(0);\n\t\tmChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);\n\t\tframeLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();\n\t}\n\n\tprivate void possiblyResizeChildOfContent() {\n\t\tint usableHeightNow = computeUsableHeight();\n\t\tif (usableHeightNow != usableHeightPrevious) {\n\t\t\tint usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();\n\t\t\tint heightDifference = usableHeightSansKeyboard - usableHeightNow;\n\t\t\tif (heightDifference > (usableHeightSansKeyboard / 4)) {\n\t\t\t\t// keyboard probably just became visible\n\t\t\t\tframeLayoutParams.height = usableHeightSansKeyboard - heightDifference;\n\t\t\t} else {\n\t\t\t\t// keyboard probably just became hidden\n\t\t\t\tframeLayoutParams.height = usableHeightSansKeyboard;\n\t\t\t}\n\t\t\tmChildOfContent.requestLayout();\n\t\t\tusableHeightPrevious = usableHeightNow;\n\t\t}\n\t}\n\n\tprivate int computeUsableHeight() {\n\t\tRect r = new Rect();\n\t\tmChildOfContent.getWindowVisibleDisplayFrame(r);\n\t\treturn (r.bottom - r.top);\n\t}\n}\n"
  },
  {
    "path": "android/app/src/main/java/su/xash/engine/util/TGAReader.java",
    "content": "/**\n * TGAReader.java\n * <p>\n * Copyright (c) 2014 Kenji Sasaki\n * Released under the MIT license.\n * https://github.com/npedotnet/TGAReader/blob/master/LICENSE\n * <p>\n * English document\n * https://github.com/npedotnet/TGAReader/blob/master/README.md\n * <p>\n * Japanese document\n * http://3dtech.jp/wiki/index.php?TGAReader\n */\n\npackage su.xash.engine.util;\n\nimport java.io.IOException;\n\npublic final class TGAReader {\n\n\tpublic static final Order ARGB = new Order(16, 8, 0, 24);\n\tpublic static final Order ABGR = new Order(0, 8, 16, 24);\n\n\tpublic static int getWidth(byte[] buffer) {\n\t\treturn (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8;\n\t}\n\n\tpublic static int getHeight(byte[] buffer) {\n\t\treturn (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8;\n\t}\n\n\tpublic static int[] read(byte[] buffer, Order order) throws IOException {\n\n\t\t// header\n//\t\tint idFieldLength = buffer[0] & 0xFF;\n//\t\tint colormapType = buffer[1] & 0xFF;\n\t\tint type = buffer[2] & 0xFF;\n\t\tint colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8;\n\t\tint colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8;\n\t\tint colormapDepth = buffer[7] & 0xFF;\n//\t\tint originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported\n//\t\tint originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported\n\t\tint width = getWidth(buffer);\n\t\tint height = getHeight(buffer);\n\t\tint depth = buffer[16] & 0xFF;\n\t\tint descriptor = buffer[17] & 0xFF;\n\n\t\tint[] pixels;\n\n\t\t// data\n\t\tswitch (type) {\n\t\t\tcase COLORMAP: {\n\t\t\t\tint imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;\n\t\t\t\tpixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order);\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase RGB:\n\t\t\t\tpixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order);\n\t\t\t\tbreak;\n\t\t\tcase GRAYSCALE:\n\t\t\t\tpixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order);\n\t\t\t\tbreak;\n\t\t\tcase COLORMAP_RLE: {\n\t\t\t\tint imageDataOffset = 18 + (colormapDepth / 8) * colormapLength;\n\t\t\t\tbyte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset);\n\t\t\t\tpixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order);\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase RGB_RLE: {\n\t\t\t\tbyte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);\n\t\t\t\tpixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order);\n\t\t\t}\n\t\t\tbreak;\n\t\t\tcase GRAYSCALE_RLE: {\n\t\t\t\tbyte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18);\n\t\t\t\tpixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order);\n\t\t\t}\n\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new IOException(\"Unsupported image type: \" + type);\n\t\t}\n\n\t\treturn pixels;\n\n\t}\n\n\tprivate static final int COLORMAP = 1;\n\tprivate static final int RGB = 2;\n\tprivate static final int GRAYSCALE = 3;\n\tprivate static final int COLORMAP_RLE = 9;\n\tprivate static final int RGB_RLE = 10;\n\tprivate static final int GRAYSCALE_RLE = 11;\n\n\tprivate static final int RIGHT_ORIGIN = 0x10;\n\tprivate static final int UPPER_ORIGIN = 0x20;\n\n\tprivate static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) {\n\t\tint elementCount = depth / 8;\n\t\tbyte[] elements = new byte[elementCount];\n\t\tint decodeBufferLength = elementCount * width * height;\n\t\tbyte[] decodeBuffer = new byte[decodeBufferLength];\n\t\tint decoded = 0;\n\t\twhile (decoded < decodeBufferLength) {\n\t\t\tint packet = buffer[offset++] & 0xFF;\n\t\t\tif ((packet & 0x80) != 0) { // RLE\n\t\t\t\tfor (int i = 0; i < elementCount; i++) {\n\t\t\t\t\telements[i] = buffer[offset++];\n\t\t\t\t}\n\t\t\t\tint count = (packet & 0x7F) + 1;\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tfor (int j = 0; j < elementCount; j++) {\n\t\t\t\t\t\tdecodeBuffer[decoded++] = elements[j];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else { // RAW\n\t\t\t\tint count = (packet + 1) * elementCount;\n\t\t\t\tfor (int i = 0; i < count; i++) {\n\t\t\t\t\tdecodeBuffer[decoded++] = buffer[offset++];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn decodeBuffer;\n\t}\n\n\tprivate static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException {\n\t\tint[] pixels;\n\t\tint rs = order.redShift;\n\t\tint gs = order.greenShift;\n\t\tint bs = order.blueShift;\n\t\tint as = order.alphaShift;\n\t\tswitch (depth) {\n\t\t\tcase 24:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 3 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 3 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 3 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * i + j] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 3 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 32:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 4 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = palette[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 4 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = palette[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 4 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = palette[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * i + j] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint colormapIndex = bytes[offset + width * i + j] &\n\t\t\t\t\t\t\t\t\t0xFF - colormapOrigin;\n\t\t\t\t\t\t\t\tint color = 0xFFFFFFFF;\n\t\t\t\t\t\t\t\tif (colormapIndex >= 0) {\n\t\t\t\t\t\t\t\t\tint index = 4 * colormapIndex + 18;\n\t\t\t\t\t\t\t\t\tint b = palette[index] & 0xFF;\n\t\t\t\t\t\t\t\t\tint g = palette[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\t\tint r = palette[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\t\tint a = palette[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\t\tcolor = (r << rs) | (g << gs) | (b << bs) | (a << as);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = color;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new IOException(\"Unsupported depth:\" + depth);\n\t\t}\n\t\treturn pixels;\n\t}\n\n\tprivate static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {\n\t\tint[] pixels;\n\t\tint rs = order.redShift;\n\t\tint gs = order.greenShift;\n\t\tint bs = order.blueShift;\n\t\tint as = order.alphaShift;\n\t\tswitch (depth) {\n\t\t\tcase 24:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 3 * width * i + 3 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 3 * width * i + 3 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 3 * width * i + 3 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + j] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 3 * width * i + 3 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 32:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 4 * width * i + 4 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 4 * width * i + 4 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 4 * width * i + 4 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + j] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint index = offset + 4 * width * i + 4 * j;\n\t\t\t\t\t\t\t\tint b = bytes[index] & 0xFF;\n\t\t\t\t\t\t\t\tint g = bytes[index + 1] & 0xFF;\n\t\t\t\t\t\t\t\tint r = bytes[index + 2] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[index + 3] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = (r << rs) |\n\t\t\t\t\t\t\t\t\t(g << gs) |\n\t\t\t\t\t\t\t\t\t(b << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new IOException(\"Unsupported depth:\" + depth);\n\t\t}\n\t\treturn pixels;\n\t}\n\n\tprivate static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException {\n\t\tint[] pixels;\n\t\tint rs = order.redShift;\n\t\tint gs = order.greenShift;\n\t\tint bs = order.blueShift;\n\t\tint as = order.alphaShift;\n\t\tswitch (depth) {\n\t\t\tcase 8:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + width * i + j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + width * i + j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + width * i + j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + j] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + width * i + j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 16:\n\t\t\t\tpixels = new int[width * height];\n\t\t\t\tif ((descriptor & RIGHT_ORIGIN) != 0) {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + (width - j - 1)] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerRight\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif ((descriptor & UPPER_ORIGIN) != 0) {\n\t\t\t\t\t\t// UpperLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * i + j] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// LowerLeft\n\t\t\t\t\t\tfor (int i = 0; i < height; i++) {\n\t\t\t\t\t\t\tfor (int j = 0; j < width; j++) {\n\t\t\t\t\t\t\t\tint e = bytes[offset + 2 * width * i + 2 * j] & 0xFF;\n\t\t\t\t\t\t\t\tint a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF;\n\t\t\t\t\t\t\t\tpixels[width * (height - i - 1) + j] = (e << rs) |\n\t\t\t\t\t\t\t\t\t(e << gs) |\n\t\t\t\t\t\t\t\t\t(e << bs) |\n\t\t\t\t\t\t\t\t\t(a << as);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new IOException(\"Unsupported depth:\" + depth);\n\t\t}\n\t\treturn pixels;\n\t}\n\n\tprivate TGAReader() {\n\t}\n\n\tpublic static final class Order {\n\t\tOrder(int redShift, int greenShift, int blueShift, int alphaShift) {\n\t\t\tthis.redShift = redShift;\n\t\t\tthis.greenShift = greenShift;\n\t\t\tthis.blueShift = blueShift;\n\t\t\tthis.alphaShift = alphaShift;\n\t\t}\n\n\t\tpublic int redShift;\n\t\tpublic int greenShift;\n\t\tpublic int blueShift;\n\t\tpublic int alphaShift;\n\t}\n\n}\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_add_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_delete_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M8,5v14l11,-7z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_settings_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/drawable/ic_baseline_terminal_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z\" />\n</vector>\n"
  },
  {
    "path": "android/app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\"\n\tandroid:fitsSystemWindows=\"true\">\n\n\t<com.google.android.material.appbar.AppBarLayout\n\t\tandroid:id=\"@+id/appBarLayout\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:layout_constraintLeft_toLeftOf=\"parent\"\n\t\tapp:layout_constraintRight_toRightOf=\"parent\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t\t<androidx.appcompat.widget.Toolbar\n\t\t\tandroid:id=\"@+id/toolbar\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\" />\n\n\t</com.google.android.material.appbar.AppBarLayout>\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/fragmentContainerView\"\n\t\tandroid:name=\"androidx.navigation.fragment.NavHostFragment\"\n\t\tandroid:layout_width=\"0dp\"\n\t\tandroid:layout_height=\"0dp\"\n\t\tapp:defaultNavHost=\"true\"\n\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/appBarLayout\"\n\t\tapp:navGraph=\"@navigation/nav_graph\"\n\t\ttools:layout=\"@layout/fragment_library\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "android/app/src/main/res/layout/card_game.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"@style/Widget.Material3.CardView.Filled\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_marginTop=\"8dp\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"128dp\">\n\n\t\t<ImageView\n\t\t\tandroid:id=\"@+id/gameCover\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:adjustViewBounds=\"true\"\n\t\t\tandroid:scaleType=\"centerCrop\"\n\t\t\ttools:src=\"@android:mipmap/sym_def_app_icon\" />\n\n\t\t<com.google.android.material.card.MaterialCardView\n\t\t\tstyle=\"@style/Widget.Material3.CardView.Filled\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"48dp\"\n\t\t\tandroid:layout_margin=\"8dp\"\n\t\t\tapp:contentPadding=\"10dp\"\n\t\t\tapp:layout_constrainedWidth=\"true\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/buttonsContainer\"\n\t\t\tapp:layout_constraintHorizontal_bias=\"0\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\">\n\n\t\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"match_parent\">\n\n\t\t\t\t<ImageView\n\t\t\t\t\tandroid:id=\"@+id/gameIcon\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\t\tandroid:layout_marginRight=\"8dp\"\n\t\t\t\t\tandroid:adjustViewBounds=\"true\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@id/gameTitle\"\n\t\t\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\ttools:src=\"@android:mipmap/sym_def_app_icon\" />\n\n\t\t\t\t<com.google.android.material.textview.MaterialTextView\n\t\t\t\t\tandroid:id=\"@+id/gameTitle\"\n\t\t\t\t\tstyle=\"@style/TextAppearance.Material3.TitleMedium\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\t\tandroid:gravity=\"center\"\n\t\t\t\t\tandroid:singleLine=\"true\"\n\t\t\t\t\tapp:layout_constrainedWidth=\"true\"\n\t\t\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\t\t\tapp:layout_constraintStart_toEndOf=\"@id/gameIcon\"\n\t\t\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\t\t\ttools:text=\"Game Title\" />\n\t\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\t\t</com.google.android.material.card.MaterialCardView>\n\n\t\t<com.google.android.material.card.MaterialCardView\n\t\t\tandroid:id=\"@+id/buttonsContainer\"\n\t\t\tstyle=\"@style/Widget.Material3.CardView.Filled\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:layout_marginEnd=\"8dp\"\n\t\t\tandroid:layout_marginBottom=\"8dp\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\">\n\n\t\t\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\tandroid:layout_height=\"wrap_content\">\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/launchButton\"\n\t\t\t\t\tstyle=\"@style/Widget.Material3.Button.IconButton\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_baseline_play_arrow_24\"\n\t\t\t\t\tapp:layout_constraintEnd_toStartOf=\"@+id/settingsButton\" />\n\n\t\t\t\t<com.google.android.material.button.MaterialButton\n\t\t\t\t\tandroid:id=\"@+id/settingsButton\"\n\t\t\t\t\tstyle=\"@style/Widget.Material3.Button.IconButton\"\n\t\t\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\t\t\tapp:icon=\"@drawable/ic_baseline_settings_24\"\n\t\t\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\" />\n\t\t\t</androidx.constraintlayout.widget.ConstraintLayout>\n\t\t</com.google.android.material.card.MaterialCardView>\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "android/app/src/main/res/layout/edit_text_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"@style/Widget.Material3.CardView.Filled\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_marginHorizontal=\"8dp\"\n\tandroid:layout_marginTop=\"8dp\"\n\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:padding=\"10dp\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@android:id/title\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:text=\"EditText Preference\" />\n\n\t\t<TextView\n\t\t\tandroid:id=\"@android:id/summary\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:textAppearance=\"@style/TextAppearance.Material3.BodyMedium\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toBottomOf=\"@android:id/title\"\n\t\t\ttools:text=\"Summary\" />\n\n\t\t<!--\t\t<com.google.android.material.button.MaterialButton-->\n\t\t<!--\t\t\tstyle=\"@style/Widget.Material3.Button.IconButton\"-->\n\t\t<!--\t\t\tandroid:layout_width=\"wrap_content\"-->\n\t\t<!--\t\t\tandroid:layout_height=\"wrap_content\"-->\n\t\t<!--\t\t\tapp:icon=\"@drawable/baseline_edit_24\"-->\n\t\t<!--\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"-->\n\t\t<!--\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"-->\n\t\t<!--\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />-->\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n</com.google.android.material.card.MaterialCardView>\n\n"
  },
  {
    "path": "android/app/src/main/res/layout/fragment_app_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/settingsFragment\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\"\n\t\ttools:layout=\"@android:layout/list_content\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "android/app/src/main/res/layout/fragment_game_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<include\n\t\tandroid:id=\"@+id/gameCard\"\n\t\tlayout=\"@layout/card_game\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:layout_margin=\"8dp\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<androidx.fragment.app.FragmentContainerView\n\t\tandroid:id=\"@+id/settingsFragment\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@id/gameCard\"\n\t\ttools:layout=\"@android:layout/list_content\">\n\n\t</androidx.fragment.app.FragmentContainerView>\n\n\t<!--    <com.google.android.material.bottomnavigation.BottomNavigationView-->\n\t<!--        android:id=\"@+id/bottomNavigation\"-->\n\t<!--        style=\"@style/Widget.MaterialComponents.BottomNavigationView.Colored\"-->\n\t<!--        android:layout_width=\"match_parent\"-->\n\t<!--        android:layout_height=\"wrap_content\"-->\n\t<!--        app:layout_constraintBottom_toBottomOf=\"parent\"-->\n\t<!--        app:menu=\"@menu/menu_game_settings\" />-->\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "android/app/src/main/res/layout/fragment_library.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tandroid:id=\"@+id/swipeRefresh\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"match_parent\">\n\n\t<RelativeLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"match_parent\">\n\n\t\t<androidx.recyclerview.widget.RecyclerView\n\t\t\tandroid:id=\"@+id/gamesList\"\n\t\t\tandroid:layout_width=\"match_parent\"\n\t\t\tandroid:layout_height=\"match_parent\"\n\t\t\tandroid:clipToPadding=\"false\"\n\t\t\tandroid:paddingHorizontal=\"8dp\"\n\t\t\tapp:layoutManager=\"LinearLayoutManager\"\n\t\t\ttools:listitem=\"@layout/card_game\" />\n\t</RelativeLayout>\n\n</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n"
  },
  {
    "path": "android/app/src/main/res/layout/list_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\">\n\n\t<TextView\n\t\tandroid:id=\"@android:id/title\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\n\t<TextView\n\t\tandroid:id=\"@android:id/summary\"\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:textAppearance=\"@style/TextAppearance.Material3.BodyMedium\"\n\t\tapp:layout_constraintTop_toBottomOf=\"@android:id/title\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "android/app/src/main/res/layout/switch_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\txmlns:tools=\"http://schemas.android.com/tools\"\n\tstyle=\"@style/Widget.Material3.CardView.Filled\"\n\tandroid:layout_width=\"match_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:layout_marginHorizontal=\"8dp\"\n\tandroid:layout_marginTop=\"8dp\"\n\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\tapp:layout_constraintTop_toTopOf=\"parent\">\n\n\t<androidx.constraintlayout.widget.ConstraintLayout\n\t\tandroid:layout_width=\"match_parent\"\n\t\tandroid:layout_height=\"wrap_content\"\n\t\tandroid:padding=\"10dp\">\n\n\t\t<TextView\n\t\t\tandroid:id=\"@android:id/title\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tandroid:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintStart_toStartOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\"\n\t\t\ttools:text=\"Switch preference\" />\n\n\t\t<com.google.android.material.materialswitch.MaterialSwitch\n\t\t\tandroid:id=\"@+id/switchWidget\"\n\t\t\tandroid:layout_width=\"wrap_content\"\n\t\t\tandroid:layout_height=\"wrap_content\"\n\t\t\tapp:layout_constraintBottom_toBottomOf=\"parent\"\n\t\t\tapp:layout_constraintEnd_toEndOf=\"parent\"\n\t\t\tapp:layout_constraintTop_toTopOf=\"parent\" />\n\t</androidx.constraintlayout.widget.ConstraintLayout>\n</com.google.android.material.card.MaterialCardView>\n\n"
  },
  {
    "path": "android/app/src/main/res/menu/menu_game_settings.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\t<item\n\t\tandroid:id=\"@+id/action_dedicated\"\n\t\tandroid:icon=\"@drawable/ic_baseline_terminal_24\"\n\t\tandroid:title=\"@string/dedicated_server\"\n\t\tandroid:visible=\"false\"\n\t\tapp:showAsAction=\"always|withText\" />\n</menu>\n"
  },
  {
    "path": "android/app/src/main/res/menu/menu_library.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\t<item\n\t\tandroid:id=\"@+id/action_settings\"\n\t\tandroid:icon=\"@drawable/ic_baseline_settings_24\"\n\t\tandroid:title=\"@string/app_settings\"\n\t\tapp:showAsAction=\"always|withText\" />\n</menu>\n"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@mipmap/ic_launcher_monochrome\"/>\n</adaptive-icon>"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"108dp\" android:viewportHeight=\"1024\" android:viewportWidth=\"1024\" android:width=\"108dp\">\n      \n    <path android:fillColor=\"#FFFFFF\" android:pathData=\"M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z\"/>\n    \n</vector>"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_monochrome.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"108dp\" android:viewportHeight=\"1024\" android:viewportWidth=\"1024\" android:width=\"108dp\">\n      \n    <path android:fillColor=\"#FF000000\" android:pathData=\"M352.3,335.9l0,64.8l45.7,0l69.9,113l-109.9,171.8l87.3,0l68.4,-103.5l65.5,103.5l90,0l0,-62l-42,-0.7l-70.4,-112.1l109.3,-174.8l-84.1,0l-68.3,106.4l-69,-106.4z\"/>\n    \n</vector>"
  },
  {
    "path": "android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "android/app/src/main/res/navigation/nav_graph.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\"\n\tandroid:id=\"@+id/nav_graph\"\n\tandroid:label=\"@string/library\"\n\tapp:startDestination=\"@id/libraryFragment\">\n\t<fragment\n\t\tandroid:id=\"@+id/libraryFragment\"\n\t\tandroid:name=\"su.xash.engine.ui.library.LibraryFragment\"\n\t\tandroid:label=\"@string/library\">\n\t\t<action\n\t\t\tandroid:id=\"@+id/action_libraryFragment_to_gameSettingsFragment\"\n\t\t\tapp:destination=\"@id/gameSettingsFragment\" />\n\t\t<action\n\t\t\tandroid:id=\"@+id/action_libraryFragment_to_appSettingsFragment\"\n\t\t\tapp:destination=\"@id/appSettingsFragment\" />\n\t</fragment>\n\t<fragment\n\t\tandroid:id=\"@+id/gameSettingsFragment\"\n\t\tandroid:name=\"su.xash.engine.ui.settings.GameSettingsFragment\"\n\t\tandroid:label=\"@string/game_settings\" />\n\t<fragment\n\t\tandroid:id=\"@+id/appSettingsFragment\"\n\t\tandroid:name=\"su.xash.engine.ui.settings.AppSettingsFragment\"\n\t\tandroid:label=\"@string/app_settings\" />\n</navigation>\n"
  },
  {
    "path": "android/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<color name=\"valve_red\">#F74843</color>\n\t<color name=\"hl_orange\">#FB7E14</color>\n\t<color name=\"black\">#000000</color>\n\t<color name=\"hl_dark_grey\">#151515</color>\n\t<color name=\"hl_grey\">#292929</color>\n</resources>\n\n"
  },
  {
    "path": "android/app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<color name=\"ic_launcher_background\">#F74843</color>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"app_name\" translatable=\"false\">Xash3D FWGS</string>\n\t<string name=\"library\">Library</string>\n\t<string name=\"dedicated_server\">Dedicated</string>\n\t<string name=\"app_settings\">Settings</string>\n\t<string name=\"game_settings\">Game Settings</string>\n\t<string name=\"game_settings_command_line\">Command-line arguments</string>\n\t<string name=\"game_settings_volume_buttons\">Use volume buttons in-game</string>\n\t<string name=\"preferences_package_name\">Libraries package</string>\n\t<string name=\"preferences_separate_libraries\">Libraries from separate packages</string>\n\t<string name=\"preferences_client_package\">Client package</string>\n\t<string name=\"preferences_server_package\">Server package</string>\n\t<string name=\"preferences_use_icons\">Use icons instead of backgrounds</string>\n\t<string name=\"game_data_location\">Game data location</string>\n\t<string name=\"file_access_required\">All-files access required</string>\n\t<string name=\"file_access_message\">All-files access is required for the app to function properly.</string>\n\t<string name=\"select_current_directory\">Select current directory</string>\n\t<string name=\"external_storage_required\">External storage access required</string>\n\t<string name=\"external_storage_message\">External storage access is required for the app to function properly.</string>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n\t<style name=\"ShapeAppearance.App.MediumComponent\" parent=\"ShapeAppearance.Material3.MediumComponent\">\n\t\t<item name=\"cornerSize\">4dp</item>\n\t</style>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values/themes.xml",
    "content": "<resources>\n\n\t<style name=\"Theme.App\" parent=\"Theme.Material3.Dark.NoActionBar\">\n\t\t<item name=\"colorPrimary\">@color/hl_orange</item>\n\t\t<item name=\"colorPrimaryDark\">@color/hl_dark_grey</item>\n\t\t<!--        <item name=\"colorPrimaryContainer\">@color/valve_red</item>-->\n\t\t<item name=\"colorSurface\">@color/hl_grey</item>\n\t\t<item name=\"colorSurfaceVariant\">@color/hl_grey</item>\n\t\t<item name=\"android:colorBackground\">@color/hl_dark_grey</item>\n\t\t<item name=\"colorControlNormal\">@color/hl_orange</item>\n\t\t<item name=\"colorOnPrimary\">@color/hl_dark_grey</item>\n\t\t<!--        <item name=\"colorOnPrimaryContainer\">@android:color/white</item>-->\n\t\t<!--        <item name=\"colorOnSurface\">@android:color/white</item>-->\n\t\t<!--        <item name=\"colorOnSurfaceVariant\">@android:color/white</item>-->\n\n\t\t<!--        <item name=\"shapeAppearanceMediumComponent\">@style/ShapeAppearance.App.MediumComponent</item>-->\n\t</style>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"app_name\" translatable=\"false\">Xash3D FWGS</string>\n\t<string name=\"library\">Librería</string>\n\t<string name=\"dedicated_server\">Dedicado</string>\n\t<string name=\"app_settings\">Ajustes</string>\n\t<string name=\"game_settings\">Ajustes del juego</string>\n\t<string name=\"game_settings_command_line\">Argumentos de la línea de comandos</string>\n\t<string name=\"game_settings_volume_buttons\">Usar botones de volumen en el juego</string>\n\t<string name=\"preferences_package_name\">Paquete de librerías</string>\n\t<string name=\"preferences_separate_libraries\">Librerías de paquetes separados</string>\n\t<string name=\"preferences_client_package\">Paquete de cliente</string>\n\t<string name=\"preferences_server_package\">Paquete de servidor</string>\n\t<string name=\"preferences_use_icons\">Usar iconos en lugar de fondos</string>\n\t<string name=\"game_data_location\">Ubicación de los datos del juego</string>\n\t<string name=\"file_access_required\">Se requiere acceso a todos los archivos</string>\n\t<string name=\"file_access_message\">Se requiere acceso a todos los archivos para que la aplicación funcione correctamente.</string>\n\t<string name=\"select_current_directory\">Seleccionar directorio actual</string>\n\t<string name=\"external_storage_required\">Se requiere acceso al almacenamiento externo</string>\n\t<string name=\"external_storage_message\">Se requiere acceso al almacenamiento externo para que la aplicación funcione correctamente.</string>\n</resources>"
  },
  {
    "path": "android/app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"library\">Biblioteca</string>\n\t<string name=\"dedicated_server\">Dedicado</string>\n\t<string name=\"app_settings\">Configurações</string>\n\t<string name=\"game_settings\">Configurações do jogo</string>\n\t<string name=\"game_settings_command_line\">Argumentos de linha de comando</string>\n\t<string name=\"game_settings_volume_buttons\">Usar botões de volume no jogo</string>\n\t<string name=\"preferences_package_name\">Pacote de bibliotecas</string>\n\t<string name=\"preferences_separate_libraries\">Bibliotecas de pacotes separados</string>\n\t<string name=\"preferences_client_package\">Pacote do cliente</string>\n\t<string name=\"preferences_server_package\">Pacote do servidor</string>\n\t<string name=\"preferences_use_icons\">Usar ícones em vez de fundos</string>\n\t<string name=\"game_data_location\">Local dos dados do jogo</string>\n\t<string name=\"file_access_required\">Acesso a todos os arquivos necessário</string>\n\t<string name=\"file_access_message\">É necessário acesso a todos os arquivos para que o aplicativo funcione corretamente.</string>\n\t<string name=\"select_current_directory\">Selecionar diretório atual</string>\n\t<string name=\"external_storage_required\">Acesso ao armazenamento externo necessário</string>\n\t<string name=\"external_storage_message\">É necessário acesso ao armazenamento externo para que o aplicativo funcione corretamente.</string>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string name=\"library\">Библиотека</string>\n\t<string name=\"dedicated_server\">Выделенный</string>\n\t<string name=\"app_settings\">Настройки</string>\n\t<string name=\"game_settings\">Настройки игры</string>\n\t<string name=\"game_settings_command_line\">Аргументы командной строки</string>\n\t<string name=\"game_settings_volume_buttons\">Использовать кнопки громкости</string>\n\t<string name=\"preferences_package_name\">Пакет библиотек</string>\n\t<string name=\"preferences_separate_libraries\">Библиотеки из отдельных пакетов</string>\n\t<string name=\"preferences_client_package\">Пакет клиента</string>\n\t<string name=\"preferences_server_package\">Пакет сервера</string>\n\t<string name=\"preferences_use_icons\">Использовать иконки вместо фона</string>\n\t<string name=\"game_data_location\">Расположение игровых данных</string>\n\t<string name=\"file_access_required\">Требуется доступ ко всем файлам</string>\n\t<string name=\"file_access_message\">Для правильной работы приложения требуется доступ ко всем файлам.</string>\n\t<string name=\"select_current_directory\">Выбрать текущую директорию</string>\n\t<string name=\"external_storage_required\">Требуется доступ к внешнему хранилищу</string>\n\t<string name=\"external_storage_message\">Для правильной работы приложения требуется доступ к внешнему хранилищу.</string>\n</resources>\n"
  },
  {
    "path": "android/app/src/main/res/xml/app_preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\txmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\t<Preference\n\t\tapp:key=\"game_path\"\n\t\tapp:layout=\"@layout/edit_text_preference\"\n\t\tapp:title=\"@string/game_data_location\"\n\t\tandroid:summary=\"/storage/emulated/0/xash\" />\n\n\t<SwitchPreferenceCompat\n\t\tapp:key=\"use_icons\"\n\t\tapp:layout=\"@layout/switch_preference\"\n\t\tapp:title=\"@string/preferences_use_icons\" />\n</PreferenceScreen>\n"
  },
  {
    "path": "android/app/src/main/res/xml/game_preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\t<EditTextPreference\n\t\tapp:defaultValue=\"-console -log\"\n\t\tapp:key=\"arguments\"\n\t\tapp:layout=\"@layout/edit_text_preference\"\n\t\tapp:title=\"@string/game_settings_command_line\"\n\t\tapp:useSimpleSummaryProvider=\"true\" />\n\n\t<SwitchPreferenceCompat\n\t\tapp:key=\"use_volume_buttons\"\n\t\tapp:layout=\"@layout/switch_preference\"\n\t\tapp:title=\"@string/game_settings_volume_buttons\" />\n\n\t<SwitchPreferenceCompat\n\t\tapp:key=\"separate_libraries\"\n\t\tapp:layout=\"@layout/switch_preference\"\n\t\tapp:title=\"@string/preferences_separate_libraries\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n\t<ListPreference\n\t\tapp:key=\"package_name\"\n\t\tapp:layout=\"@layout/list_preference\"\n\t\tapp:title=\"@string/preferences_package_name\"\n\t\tapp:useSimpleSummaryProvider=\"true\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n\t<ListPreference\n\t\tapp:key=\"client_package\"\n\t\tapp:layout=\"@layout/list_preference\"\n\t\tapp:title=\"@string/preferences_client_package\"\n\t\tapp:useSimpleSummaryProvider=\"true\"\n\t\tapp:isPreferenceVisible=\"false\" />\n\n\t<ListPreference\n\t\tapp:key=\"server_package\"\n\t\tapp:layout=\"@layout/list_preference\"\n\t\tapp:title=\"@string/preferences_server_package\"\n\t\tapp:useSimpleSummaryProvider=\"true\"\n\t\tapp:isPreferenceVisible=\"false\" />\n</PreferenceScreen>\n"
  },
  {
    "path": "android/build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n\talias(libs.plugins.android.application) apply false\n\talias(libs.plugins.kotlin.android) apply false\n}\n"
  },
  {
    "path": "android/gradle/libs.versions.toml",
    "content": "[versions]\nacraHttp = \"5.12.0\"\nagp = \"8.11.1\"\nappcompat = \"1.7.1\"\nkotlin = \"2.2.0\"\nmaterial = \"1.12.0\"\nnavigationRuntimeKtx = \"2.9.1\"\npreferenceKtx = \"1.2.1\"\nswiperefreshlayout = \"1.1.0\"\n\n[libraries]\nacra-http = { module = \"ch.acra:acra-http\", version.ref = \"acraHttp\" }\nappcompat = { module = \"androidx.appcompat:appcompat\", version.ref = \"appcompat\" }\nmaterial = { module = \"com.google.android.material:material\", version.ref = \"material\" }\nnavigation-fragment-ktx = { module = \"androidx.navigation:navigation-fragment-ktx\", version.ref = \"navigationRuntimeKtx\" }\nnavigation-runtime-ktx = { module = \"androidx.navigation:navigation-runtime-ktx\", version.ref = \"navigationRuntimeKtx\" }\nnavigation-ui-ktx = { module = \"androidx.navigation:navigation-ui-ktx\", version.ref = \"navigationRuntimeKtx\" }\npreference-ktx = { module = \"androidx.preference:preference-ktx\", version.ref = \"preferenceKtx\" }\nswiperefreshlayout = { module = \"androidx.swiperefreshlayout:swiperefreshlayout\", version.ref = \"swiperefreshlayout\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nkotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\n"
  },
  {
    "path": "android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Jul 01 19:51:06 EEST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\n\n\n# Enable verbose output for CMake\nandroid.native.buildOutput=verbose\n"
  },
  {
    "path": "android/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "android/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "android/settings.gradle.kts",
    "content": "pluginManagement {\n\trepositories {\n\t\tgoogle {\n\t\t\tcontent {\n\t\t\t\tincludeGroupByRegex(\"com\\\\.android.*\")\n\t\t\t\tincludeGroupByRegex(\"com\\\\.google.*\")\n\t\t\t\tincludeGroupByRegex(\"androidx.*\")\n\t\t\t}\n\t\t}\n\t\tmavenCentral()\n\t\tgradlePluginPortal()\n\t}\n}\ndependencyResolutionManagement {\n\trepositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n\trepositories {\n\t\tgoogle()\n\t\tmavenCentral()\n\t}\n}\n\nrootProject.name = \"Xash3D FWGS\"\ninclude(\":app\")\n"
  },
  {
    "path": "common/backends.h",
    "content": "/*\nbackends.h - backend macro definitions\nCopyright (C) 2016 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef BACKENDS_H\n#define BACKENDS_H\n\n// video backends (XASH_VIDEO)\n#define VIDEO_NULL    0\n#define VIDEO_SDL     1\n#define VIDEO_FBDEV   3\n#define VIDEO_DOS     4\n\n// audio backends (XASH_SOUND)\n#define SOUND_NULL     0\n#define SOUND_SDL      1\n#define SOUND_ALSA     3\n\n// input (XASH_INPUT)\n#define INPUT_NULL    0\n#define INPUT_SDL     1\n#define INPUT_EVDEV   3\n\n// timer (XASH_TIMER)\n#define TIMER_NULL  0 // not used\n#define TIMER_SDL   1\n#define TIMER_POSIX 2\n#define TIMER_WIN32 3\n#define TIMER_DOS   4\n\n// messageboxes (XASH_MESSAGEBOX)\n#define MSGBOX_STDERR  0\n#define MSGBOX_SDL     1\n#define MSGBOX_WIN32   3\n#define MSGBOX_NSWITCH 4\n\n// library loading (XASH_LIB)\n#define LIB_NULL 0\n#define LIB_POSIX 1\n#define LIB_WIN32 2\n#define LIB_STATIC 3\n\n// movies (XASH_AVI)\n#define AVI_NULL   0\n#define AVI_FFMPEG 1\n\n#endif /* BACKENDS_H */\n"
  },
  {
    "path": "common/beamdef.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef BEAMDEF_H\n#define BEAMDEF_H\n\n#define FBEAM_STARTENTITY\t\t0x00000001\n#define FBEAM_ENDENTITY\t\t0x00000002\n#define FBEAM_FADEIN\t\t0x00000004\n#define FBEAM_FADEOUT\t\t0x00000008\n#define FBEAM_SINENOISE\t\t0x00000010\n#define FBEAM_SOLID\t\t\t0x00000020\n#define FBEAM_SHADEIN\t\t0x00000040\n#define FBEAM_SHADEOUT\t\t0x00000080\n#define FBEAM_STARTVISIBLE\t\t0x10000000 // Has this client actually seen this beam's start entity yet?\n#define FBEAM_ENDVISIBLE\t\t0x20000000 // Has this client actually seen this beam's end entity yet?\n#define FBEAM_ISACTIVE\t\t0x40000000\n#define FBEAM_FOREVER\t\t0x80000000\n\ntypedef struct beam_s BEAM;\nstruct beam_s\n{\n\tBEAM\t\t*next;\n\tint\t\ttype;\n\tint\t\tflags;\n\tvec3_t\t\tsource;\n\tvec3_t\t\ttarget;\n\tvec3_t\t\tdelta;\n\tfloat\t\tt;\t\t// 0 .. 1 over lifetime of beam\n\tfloat\t\tfreq;\n\tfloat\t\tdie;\n\tfloat\t\twidth;\n\tfloat\t\tamplitude;\n\tfloat\t\tr, g, b;\n\tfloat\t\tbrightness;\n\tfloat\t\tspeed;\n\tfloat\t\tframeRate;\n\tfloat\t\tframe;\n\tint\t\tsegments;\n\tint\t\tstartEntity;\n\tint\t\tendEntity;\n\tint\t\tmodelIndex;\n\tint\t\tframeCount;\n\tstruct model_s\t*pFollowModel;\n\tstruct particle_s\t*particles;\n};\n\n#endif//BEAMDEF_H\n"
  },
  {
    "path": "common/bspfile.h",
    "content": "/*\nbspfile.h - BSP format included q1, hl1 support\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef BSPFILE_H\n#define BSPFILE_H\n\n/*\n==============================================================================\n\nBRUSH MODELS\n\n.bsp contain level static geometry with including PVS and lightning info\n==============================================================================\n*/\n\n// header\n#define Q1BSP_VERSION\t29\t// quake1 regular version (beta is 28)\n#define HLBSP_VERSION\t30\t// half-life regular version\n#define QBSP2_VERSION\t(('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24))\n\n#define IDEXTRAHEADER\t(('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian \"XASH\"\n#define EXTRA_VERSION\t4\t// ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior\n\t\t\t\t// ver. 3 was occupied by experimental versions of P2:savior change fmt\n\n#define DELUXEMAP_VERSION\t1\n#define IDDELUXEMAPHEADER\t(('T'<<24)+('I'<<16)+('L'<<8)+'Q') // little-endian \"QLIT\"\n\n// worldcraft predefined angles\n#define ANGLE_UP\t\t\t-1\n#define ANGLE_DOWN\t\t\t-2\n\n// bmodel limits\n#define MAX_MAP_HULLS\t\t4\t\t// MAX_HULLS\n\n#define SURF_PLANEBACK\t\tBIT( 1 )\t\t// plane should be negated\n#define SURF_DRAWSKY\t\tBIT( 2 )\t\t// sky surface\n#define SURF_DRAWTURB_QUADS\t\tBIT( 3 )\t\t// all subidivided polygons are quads\n#define SURF_DRAWTURB\t\tBIT( 4 )\t\t// warp surface\n#define SURF_DRAWTILED\t\tBIT( 5 )\t\t// face without lighmap\n#define SURF_CONVEYOR\t\tBIT( 6 )\t\t// scrolled texture (was SURF_DRAWBACKGROUND)\n#define SURF_UNDERWATER\t\tBIT( 7 )\t\t// caustics\n#define SURF_TRANSPARENT\t\tBIT( 8 )\t\t// it's a transparent texture (was SURF_DONTWARP)\n\n// lightstyle management\n#define LM_STYLES\t\t\t4\t\t// MAXLIGHTMAPS\n#define LS_NORMAL\t\t\t0x00\n#define LS_UNUSED\t\t\t0xFE\n#define LS_NONE\t\t\t0xFF\n\n#define MAX_MAP_CLIPNODES_HLBSP 32767\n#define MAX_MAP_CLIPNODES_BSP2  524288\n\n// these limis not using by modelloader but only for displaying 'mapstats' correctly\n#define MAX_MAP_MODELS\t\t2048\t\t// embedded models\n#define MAX_MAP_ENTSTRING\t\t0x200000\t\t// 2 Mb should be enough\n#define MAX_MAP_PLANES\t\t131072\t\t// can be increased without problems\n#define MAX_MAP_NODES\t\t262144\t\t// can be increased without problems\n#define MAX_MAP_CLIPNODES\t\tMAX_MAP_CLIPNODES_BSP2\t\t// can be increased without problems\n#define MAX_MAP_LEAFS\t\t131072\t\t// CRITICAL STUFF to run ad_sepulcher!!!\n#define MAX_MAP_VERTS\t\t524288\t\t// can be increased without problems\n#define MAX_MAP_FACES\t\t262144\t\t// can be increased without problems\n#define MAX_MAP_MARKSURFACES\t\t524288\t\t// can be increased without problems\n\n#define MAX_MAP_ENTITIES\t\t8192\t\t// network limit\n#define MAX_MAP_TEXINFO\t\tMAX_MAP_FACES\t// in theory each face may have personal texinfo\n#define MAX_MAP_EDGES\t\t0x100000\t\t// can be increased but not needs\n#define MAX_MAP_SURFEDGES\t\t0x200000\t\t// can be increased but not needs\n#define MAX_MAP_TEXTURES\t\t2048\t\t// can be increased but not needs\n#define MAX_MAP_MIPTEX\t\t0x2000000\t\t// 32 Mb internal textures data\n#define MAX_MAP_LIGHTING\t\t0x2000000\t\t// 32 Mb lightmap raw data (can contain deluxemaps)\n#define MAX_MAP_VISIBILITY\t\t0x1000000\t\t// 16 Mb visdata\n#define MAX_MAP_FACEINFO\t\t8192\t\t// can be increased but not needs\n\n// quake lump ordering\n#define LUMP_ENTITIES\t\t0\n#define LUMP_PLANES\t\t\t1\n#define LUMP_TEXTURES\t\t2\t\t// internal textures\n#define LUMP_VERTEXES\t\t3\n#define LUMP_VISIBILITY\t\t4\n#define LUMP_NODES\t\t\t5\n#define LUMP_TEXINFO\t\t6\n#define LUMP_FACES\t\t\t7\n#define LUMP_LIGHTING\t\t8\n#define LUMP_CLIPNODES\t\t9\n#define LUMP_LEAFS\t\t\t10\n#define LUMP_MARKSURFACES\t\t11\n#define LUMP_EDGES\t\t\t12\n#define LUMP_SURFEDGES\t\t13\n#define LUMP_MODELS\t\t\t14\t\t// internal submodels\n#define HEADER_LUMPS\t\t15\n\n// extra lump ordering\n#define LUMP_LIGHTVECS\t\t0\t// deluxemap data\n#define LUMP_FACEINFO\t\t1\t// landscape and lightmap resolution info\n#define LUMP_CUBEMAPS\t\t2\t// cubemap description\n#define LUMP_VERTNORMALS\t\t3\t// phong shaded vertex normals\n#define LUMP_LEAF_LIGHTING\t\t4\t// store vertex lighting for statics\n#define LUMP_WORLDLIGHTS\t\t5\t// list of all the virtual and real lights (used to relight models in-game)\n#define LUMP_COLLISION\t\t6\t// physics engine collision hull dump (userdata)\n#define LUMP_AINODEGRAPH\t\t7\t// node graph that stored into the bsp (userdata)\n#define LUMP_SHADOWMAP\t\t8\t// contains shadow map for direct light\n#define LUMP_VERTEX_LIGHT\t\t9\t// store vertex lighting for statics\n#define LUMP_UNUSED0\t\t10\t// one lump reserved for me\n#define LUMP_UNUSED1\t\t11\t// one lump reserved for me\n#define EXTRA_LUMPS\t\t\t12\t// count of the extra lumps\n\n// texture flags\n#define TEX_SPECIAL\t\t\tBIT( 0 )\t// sky or slime, no lightmap or 256 subdivision\n#define TEX_WORLD_LUXELS\t\tBIT( 1 )\t// alternative lightmap matrix will be used (luxels per world units instead of luxels per texels)\n#define TEX_AXIAL_LUXELS\t\tBIT( 2 )\t// force world luxels to axial positive scales\n#define TEX_EXTRA_LIGHTMAP\t\tBIT( 3 )\t// bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel\n#define TEX_SCROLL\t\t\tBIT( 6 )\t// Doom special FX\n\n#define IsLiquidContents( cnt )\t( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA )\n\n// ambient sound types\nenum\n{\n\tAMBIENT_WATER = 0,\t\t// waterfall\n\tAMBIENT_SKY,\t\t// wind\n\tAMBIENT_SLIME,\t\t// never used in quake\n\tAMBIENT_LAVA,\t\t// never used in quake\n\tNUM_AMBIENTS,\t\t// automatic ambient sounds\n};\n\n//\n// BSP File Structures\n//\n\ntypedef struct\n{\n\tint\tfileofs;\n\tint\tfilelen;\n} dlump_t;\n\ntypedef struct\n{\n\tint\tversion;\n\tdlump_t\tlumps[HEADER_LUMPS];\n} dheader_t;\n\ntypedef struct\n{\n\tint\tid;\t\t\t// must be little endian XASH\n\tint\tversion;\n\tdlump_t\tlumps[EXTRA_LUMPS];\n} dextrahdr_t;\n\ntypedef struct\n{\n\tvec3_t\tmins;\n\tvec3_t\tmaxs;\n\tvec3_t\torigin;\t\t\t// for sounds or lights\n\tint\theadnode[MAX_MAP_HULLS];\n\tint\tvisleafs;\t\t\t// not including the solid leaf 0\n\tint\tfirstface;\n\tint\tnumfaces;\n} dmodel_t;\n\ntypedef struct\n{\n\tint\tnummiptex;\n\tint\tdataofs[4];\t\t// [nummiptex]\n} dmiptexlump_t;\n\ntypedef struct\n{\n\tvec3_t\tpoint;\n} dvertex_t;\n\ntypedef struct\n{\n\tvec3_t\tnormal;\n\tfloat\tdist;\n\tint\ttype;\t\t\t// PLANE_X - PLANE_ANYZ ?\n} dplane_t;\n\ntypedef struct\n{\n\tint\tplanenum;\n\tshort\tchildren[2];\t\t// negative numbers are -(leafs + 1), not nodes\n\tshort\tmins[3];\t\t\t// for sphere culling\n\tshort\tmaxs[3];\n\tword\tfirstface;\n\tword\tnumfaces;\t\t\t// counting both sides\n} dnode_t;\n\ntypedef struct\n{\n\tint\tplanenum;\n\tint\tchildren[2];\t\t// negative numbers are -(leafs+1), not nodes\n\tfloat\tmins[3];\t\t\t// for sphere culling\n\tfloat\tmaxs[3];\n\tint\tfirstface;\n\tint\tnumfaces;\t\t\t// counting both sides\n} dnode32_t;\n\n// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas\n// all other leafs need visibility info\ntypedef struct\n{\n\tint\tcontents;\n\tint\tvisofs;\t\t\t// -1 = no visibility info\n\n\tshort\tmins[3];\t\t\t// for frustum culling\n\tshort\tmaxs[3];\n\tword\tfirstmarksurface;\n\tword\tnummarksurfaces;\n\n\t// automatic ambient sounds\n\tbyte\tambient_level[NUM_AMBIENTS];\t// ambient sound level (0 - 255)\n} dleaf_t;\n\ntypedef struct\n{\n\tint\tcontents;\n\tint\tvisofs;\t\t\t// -1 = no visibility info\n\n\tfloat\tmins[3];\t\t\t// for frustum culling\n\tfloat\tmaxs[3];\n\n\tint\tfirstmarksurface;\n\tint\tnummarksurfaces;\n\n\tbyte\tambient_level[NUM_AMBIENTS];\n} dleaf32_t;\n\ntypedef struct\n{\n\tint\tplanenum;\n\tshort\tchildren[2];\t\t// negative numbers are contents\n} dclipnode_t;\n\ntypedef struct\n{\n\tint\tplanenum;\n\tint\tchildren[2];\t\t// negative numbers are contents\n} dclipnode32_t;\n\ntypedef struct\n{\n\tfloat\tvecs[2][4];\t\t// texmatrix [s/t][xyz offset]\n\tint\tmiptex;\n\tshort\tflags;\n\tshort\tfaceinfo;\t\t\t// -1 no face info otherwise dfaceinfo_t\n} dtexinfo_t;\n\ntypedef struct\n{\n\tchar\t\tlandname[16];\t// name of decsription in mapname_land.txt\n\tunsigned short\ttexture_step;\t// default is 16, pixels\\luxels ratio\n\tunsigned short\tmax_extent;\t// default is 16, subdivision step ((texture_step * max_extent) - texture_step)\n\tshort\t\tgroupid;\t\t// to determine equal landscapes from various groups, -1 - no group\n} dfaceinfo_t;\n\ntypedef word\tdmarkface_t;\t\t// leaf marksurfaces indexes\ntypedef int\tdmarkface32_t;\t\t// leaf marksurfaces indexes\n\ntypedef int\tdsurfedge_t;\t\t// map surfedges\n\n// NOTE: that edge 0 is never used, because negative edge nums\n// are used for counterclockwise use of the edge in a face\ntypedef struct\n{\n\tword\tv[2];\t\t\t// vertex numbers\n} dedge_t;\n\ntypedef struct\n{\n\tint\tv[2];\t\t\t// vertex numbers\n} dedge32_t;\n\ntypedef struct\n{\n\tword\tplanenum;\n\tshort\tside;\n\n\tint\tfirstedge;\t\t// we must support > 64k edges\n\tshort\tnumedges;\n\tshort\ttexinfo;\n\n\t// lighting info\n\tbyte\tstyles[LM_STYLES];\n\tint\tlightofs;\t\t\t// start of [numstyles*surfsize] samples\n} dface_t;\n\ntypedef struct\n{\n\tint\tplanenum;\n\tint\tside;\n\n\tint\tfirstedge;\t\t// we must support > 64k edges\n\tint\tnumedges;\n\tint\ttexinfo;\n\n\t// lighting info\n\tbyte\tstyles[LM_STYLES];\n\tint\tlightofs;\t\t\t// start of [numstyles*surfsize] samples\n} dface32_t;\n\n#endif//BSPFILE_H\n"
  },
  {
    "path": "common/cl_entity.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef CL_ENTITY_H\n#define CL_ENTITY_H\n\ntypedef struct efrag_s\n{\n\tstruct mleaf_s\t*leaf;\n\tstruct efrag_s\t*leafnext;\n\tstruct cl_entity_s\t*entity;\n\tstruct efrag_s\t*entnext;\n} efrag_t;\n\ntypedef struct\n{\n\tbyte\t\tmouthopen;\t// 0 = mouth closed, 255 = mouth agape\n\tbyte\t\tsndcount;\t\t// counter for running average\n\tint\t\tsndavg;\t\t// running average\n} mouth_t;\n\ntypedef struct\n{\n\tfloat\t\tprevanimtime;\n\tfloat\t\tsequencetime;\n\tbyte\t\tprevseqblending[2];\n\tvec3_t\t\tprevorigin;\n\tvec3_t\t\tprevangles;\n\n\tint\t\tprevsequence;\n\tfloat\t\tprevframe;\n\n\tbyte\t\tprevcontroller[4];\n\tbyte\t\tprevblending[2];\n} latchedvars_t;\n\ntypedef struct\n{\n\t// Time stamp for this movement\n\tfloat\t\tanimtime;\n\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n} position_history_t;\n\ntypedef struct cl_entity_s cl_entity_t;\n\n#define HISTORY_MAX\t\t64\t\t// Must be power of 2\n#define HISTORY_MASK\t( HISTORY_MAX - 1 )\n\n#include \"entity_state.h\"\n#include \"event_args.h\"\n\nstruct cl_entity_s\n{\n\tint\t\tindex;      \t// Index into cl_entities ( should match actual slot, but not necessarily )\n\tqboolean\t\tplayer;     \t// True if this entity is a \"player\"\n\n\tentity_state_t\tbaseline;   \t// The original state from which to delta during an uncompressed message\n\tentity_state_t\tprevstate;  \t// The state information from the penultimate message received from the server\n\tentity_state_t\tcurstate;   \t// The state information from the last message received from server\n\n\tint\t\tcurrent_position;\t// Last received history update index\n\tposition_history_t\tph[HISTORY_MAX];\t// History of position and angle updates for this player\n\n\tmouth_t\t\tmouth;\t\t// For synchronizing mouth movements.\n\n\tlatchedvars_t\tlatched;\t\t// Variables used by studio model rendering routines\n\n\t// Information based on interplocation, extrapolation, prediction, or just copied from last msg received.\n\t//\n\tfloat\t\tlastmove;\n\n\t// Actual render position and angles\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n\n\t// Attachment points\n\tvec3_t\t\tattachment[4];\n\n\t// Other entity local information\n\tint\t\ttrivial_accept;\n\n\tstruct model_s\t*model;\t// cl.model_precache[ curstate.modelindes ];  all visible entities have a model\n\tstruct efrag_s\t*efrag;\t// linked list of efrags\n\tstruct mnode_s\t*topnode;\t// for bmodels, first world node that splits bmodel, or NULL if not split\n\n\tfloat\t\tsyncbase;\t// for client-side animations -- used by obsolete alias animation system, remove?\n\tint\t\tvisframe;\t// last frame this entity was found in an active leaf\n\tcolorVec\t\tcvFloorColor;\n};\n\n#endif//CL_ENTITY_H\n"
  },
  {
    "path": "common/com_image.h",
    "content": "#pragma once\n/*\n========================================================================\n\ninternal image format\n\ntypically expanded to rgba buffer\nNOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == PF_RGB_888\n========================================================================\n*/\n#define ImageRAW( type )\t(type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE)\n#define ImageCompressed( type ) \\\n\t(  type == PF_DXT1 \\\n\t|| type == PF_DXT3 \\\n\t|| type == PF_DXT5 \\\n\t|| type == PF_ATI2 \\\n\t|| type == PF_BC4_SIGNED \\\n\t|| type == PF_BC4_UNSIGNED \\\n\t|| type == PF_BC5_SIGNED \\\n\t|| type == PF_BC5_UNSIGNED \\\n\t|| type == PF_BC6H_SIGNED \\\n\t|| type == PF_BC6H_UNSIGNED \\\n\t|| type == PF_BC7_UNORM \\\n\t|| type == PF_BC7_SRGB \\\n\t|| type == PF_KTX2_RAW )\n\ntypedef enum\n{\n\tPF_UNKNOWN = 0,\n\tPF_INDEXED_24,\t// inflated palette (768 bytes)\n\tPF_INDEXED_32,\t// deflated palette (1024 bytes)\n\tPF_RGBA_32,\t// normal rgba buffer\n\tPF_BGRA_32,\t// big endian RGBA (MacOS)\n\tPF_RGB_24,\t// uncompressed dds or another 24-bit image\n\tPF_BGR_24,\t// big-endian RGB (MacOS)\n\tPF_LUMINANCE,\n\tPF_DXT1,\t\t// s3tc DXT1/BC1 format\n\tPF_DXT3,\t\t// s3tc DXT3/BC2 format\n\tPF_DXT5,\t\t// s3tc DXT5/BC3 format\n\tPF_ATI2,\t\t// latc ATI2N/BC5 format\n\tPF_BC4_SIGNED,\n\tPF_BC4_UNSIGNED,\n\tPF_BC5_SIGNED,\n\tPF_BC5_UNSIGNED,\n\tPF_BC6H_SIGNED,\t// bptc BC6H signed FP16 format\n\tPF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format\n\tPF_BC7_UNORM,\t\t\t// bptc BC7 format\n\tPF_BC7_SRGB,\n\tPF_KTX2_RAW, // Raw KTX2 data, used for yet unsupported KTX2 subformats\n\tPF_TOTALCOUNT,\t// must be last\n} pixformat_t;\n\ntypedef struct bpc_desc_s\n{\n\tint\tformat;\t// pixelformat\n\tchar\tname[16];\t// used for debug\n\tuint\tglFormat;\t// RGBA format\n\tint\tbpp;\t// channels (e.g. rgb = 3, rgba = 4)\n} bpc_desc_t;\n\n// imagelib global settings\ntypedef enum\n{\n\tIL_USE_LERPING\t= BIT(0),\t// lerping images during resample\n\tIL_KEEP_8BIT\t= BIT(1),\t// don't expand paletted images\n\tIL_ALLOW_OVERWRITE\t= BIT(2),\t// allow to overwrite stored images\n\tIL_DONTFLIP_TGA\t= BIT(3),\t// Steam background completely ignore tga attribute 0x20 (stupid lammers!)\n\tIL_DDS_HARDWARE\t= BIT(4),\t// DXT compression is support\n\tIL_LOAD_DECAL\t= BIT(5),\t// special mode for load gradient decals\n\tIL_OVERVIEW\t= BIT(6),\t// overview required some unque operations\n\tIL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals\n\tIL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)\n} ilFlags_t;\n\n// goes into rgbdata_t->encode\n#define DXT_ENCODE_DEFAULT\t\t0\t// don't use custom encoders\n#define DXT_ENCODE_COLOR_YCoCg\t0x1A01\t// make sure that value dosn't collide with anything\n#define DXT_ENCODE_ALPHA_1BIT\t\t0x1A02\t// normal 1-bit alpha\n#define DXT_ENCODE_ALPHA_8BIT\t\t0x1A03\t// normal 8-bit alpha\n#define DXT_ENCODE_ALPHA_SDF\t\t0x1A04\t// signed distance field\n#define DXT_ENCODE_NORMAL_AG_ORTHO\t0x1A05\t// orthographic projection\n#define DXT_ENCODE_NORMAL_AG_STEREO\t0x1A06\t// stereographic projection\n#define DXT_ENCODE_NORMAL_AG_PARABOLOID\t0x1A07\t// paraboloid projection\n#define DXT_ENCODE_NORMAL_AG_QUARTIC\t0x1A08\t// newton method\n#define DXT_ENCODE_NORMAL_AG_AZIMUTHAL\t0x1A09\t// Lambert Azimuthal Equal-Area\n\n// rgbdata output flags\ntypedef enum\n{\n\t// rgbdata->flags\n\tIMAGE_CUBEMAP\t= BIT(0),\t\t// it's 6-sides cubemap buffer\n\tIMAGE_HAS_ALPHA\t= BIT(1),\t\t// image contain alpha-channel\n\tIMAGE_HAS_COLOR\t= BIT(2),\t\t// image contain RGB-channel\n\tIMAGE_COLORINDEX\t= BIT(3),\t\t// all colors in palette is gradients of last color (decals)\n\tIMAGE_HAS_LUMA\t= BIT(4),\t\t// image has luma pixels (q1-style maps)\n\tIMAGE_SKYBOX\t= BIT(5),\t\t// only used by FS_SaveImage - for write right suffixes\n\tIMAGE_QUAKESKY\t= BIT(6),\t\t// it's a quake sky double layered clouds (so keep it as 8 bit)\n\tIMAGE_DDS_FORMAT\t= BIT(7),\t\t// a hint for GL loader\n\tIMAGE_MULTILAYER\t= BIT(8),\t\t// to differentiate from 3D texture\n\tIMAGE_ONEBIT_ALPHA\t= BIT(9),\t\t// binary alpha\n\tIMAGE_QUAKEPAL\t= BIT(10),\t// image has quake1 palette\n\n\t// Image_Process manipulation flags\n\tIMAGE_FLIP_X\t= BIT(16),\t// flip the image by width\n\tIMAGE_FLIP_Y\t= BIT(17),\t// flip the image by height\n\tIMAGE_ROT_90\t= BIT(18),\t// flip from upper left corner to down right corner\n\tIMAGE_ROT180\t= IMAGE_FLIP_X|IMAGE_FLIP_Y,\n\tIMAGE_ROT270\t= IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90,\n// reserved\n\tIMAGE_RESAMPLE\t= BIT(20),\t// resample image to specified dims\n// reserved\n// reserved\n\tIMAGE_FORCE_RGBA\t= BIT(23),\t// force image to RGBA buffer\n\tIMAGE_MAKE_LUMA\t= BIT(24),\t// create luma texture from indexed\n\tIMAGE_QUANTIZE\t= BIT(25),\t// make indexed image from 24 or 32- bit image\n\tIMAGE_LIGHTGAMMA\t= BIT(26),\t// apply gamma for image\n\tIMAGE_REMAP\t= BIT(27),\t// interpret width and height as top and bottom color\n} imgFlags_t;\n\ntypedef struct rgbdata_s\n{\n\tword\twidth;\t\t// image width\n\tword\theight;\t\t// image height\n\tword\tdepth;\t\t// image depth\n\tuint\ttype;\t\t// compression type\n\tuint\tflags;\t\t// misc image flags\n\tword\tencode;\t\t// DXT may have custom encoder, that will be decoded in GLSL-side\n\tbyte\tnumMips;\t\t// mipmap count\n\tbyte\t*palette;\t\t// palette if present\n\tbyte\t*buffer;\t\t// image buffer\n\trgba_t\tfogParams;\t// some water textures in hl1 has info about fog color and alpha\n\tsize_t\tsize;\t\t// for bounds checking\n} rgbdata_t;\n\n"
  },
  {
    "path": "common/com_model.h",
    "content": "/*\ncom_model.h - cient model structures\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef COM_MODEL_H\n#define COM_MODEL_H\n\n#include \"xash3d_types.h\"\n#include \"bspfile.h\"\t// we need some declarations from it\n\n/*\n==============================================================================\n\n\tENGINE MODEL FORMAT\n==============================================================================\n*/\n#define STUDIO_RENDER\t1\n#define STUDIO_EVENTS\t2\n\n#define ZISCALE\t\t((float)0x8000)\n\n#define MIPLEVELS\t\t4\n#define VERTEXSIZE\t\t7\n#define MAXLIGHTMAPS\t4\t// max light styles per face\n#define MAXDYNLIGHTS\t8\t// maximum dynamic lights per one pixel\n#define NUM_AMBIENTS\t4\t\t// automatic ambient sounds\n\n// model types\ntypedef enum\n{\n\tmod_bad = -1,\n\tmod_brush,\n\tmod_sprite,\n\tmod_alias,\n\tmod_studio\n} modtype_t;\n\ntypedef struct mplane_s\n{\n\tvec3_t\t\tnormal;\n\tfloat\t\tdist;\n\tbyte\t\ttype;\t\t// for fast side tests\n\tbyte\t\tsignbits;\t\t// signx + (signy<<1) + (signz<<1)\n\tbyte\t\tpad[2];\n} mplane_t;\n\ntypedef struct\n{\n\tvec3_t\t\tposition;\n} mvertex_t;\n\ntypedef struct mclipnode32_s\n{\n\tint planenum;\n\tint children[2]; // negative numbers are contents\n} mclipnode32_t;\n\ntypedef struct mclipnode16_s\n{\n\tint   planenum;\n\tshort children[2];\t// negative numbers are contents\n} mclipnode16_t;\n\n// size is matched but representation is not\ntypedef struct medge32_s\n{\n\tunsigned int\tv[2];\n} medge32_t;\n\ntypedef struct medge16_s\n{\n\tunsigned short\tv[2];\n\tunsigned int\tcachededgeoffset;\n} medge16_t;\n\ntypedef struct texture_s\n{\n\tchar\t\tname[16];\n\tunsigned int\twidth, height;\n\tint\t\tgl_texturenum;\n\tstruct msurface_s\t*texturechain;\t// for gl_texsort drawing\n\tint\t\tanim_total;\t// total tenths in sequence ( 0 = no)\n\tint\t\tanim_min, anim_max;\t// time for this frame min <=time< max\n\tstruct texture_s\t*anim_next;\t// in the animation sequence\n\tstruct texture_s\t*alternate_anims;\t// bmodels in frame 1 use these\n\tunsigned short\tfb_texturenum;\t// auto-luma texturenum\n\tunsigned short\tdt_texturenum;\t// detail-texture binding\n\tunsigned int\tunused[3];\t// reserved\n} texture_t;\n\ntypedef struct\n{\n\tchar\t\tlandname[16];\t// name of decsription in mapname_land.txt\n\tunsigned short\ttexture_step;\t// default is 16, pixels\\luxels ratio\n\tunsigned short\tmax_extent;\t// default is 16, subdivision step ((texture_step * max_extent) - texture_step)\n\tshort\t\tgroupid;\t\t// to determine equal landscapes from various groups, -1 - no group\n\n\tvec3_t\t\tmins, maxs;\t// terrain bounds (fill by user)\n\n\tintptr_t\treserved[32];\t// just for future expansions or mod-makers\n} mfaceinfo_t;\n\ntypedef struct\n{\n\tmplane_t\t\t*edges;\n\tint\t\tnumedges;\n\tvec3_t\t\torigin;\n\tvec_t\t\tradius;\t\t// for culling tests\n\tint\t\tcontents;\t\t// sky or solid\n} mfacebevel_t;\n\ntypedef struct\n{\n\tfloat\t\tvecs[2][4];\t// [s/t] unit vectors in world space.\n\t\t\t\t\t// [i][3] is the s/t offset relative to the origin.\n\t\t\t\t\t// s or t = dot( 3Dpoint, vecs[i] ) + vecs[i][3]\n\tmfaceinfo_t\t*faceinfo;\t// pointer to landscape info and lightmap resolution (may be NULL)\n\ttexture_t\t\t*texture;\n\tint\t\tflags;\t\t// sky or slime, no lightmap or 256 subdivision\n} mtexinfo_t;\n\n// a1ba: changed size to avoid undefined behavior. Check your allocations if you take this header!\n// For example:\n//  before: malloc( sizeof( glpoly_t ) + ( numverts - 4 ) * VERTEXSIZE * sizeof( float ))\n//  after (C): malloc( sizeof( glpoly_t ) + numverts * VERTEXSIZE * sizeof( float ))\n//  after (C++): malloc( sizeof( glpoly_t ) + ( numverts - 1 ) * VERTEXSIZE * sizeof( float ))\ntypedef struct glpoly2_s\n{\n\tstruct glpoly2_s\t*next;\n\tstruct glpoly2_s\t*chain;\n\tint\t\tnumverts;\n\tint\t\tflags;          \t\t// for SURF_UNDERWATER\n#ifdef __cplusplus\n\tfloat\tverts[1][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2)\n#else\n\tfloat\tverts[][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2)\n#endif\n} glpoly2_t;\n\ntypedef struct mnode_s\n{\n// common with leaf\n\tint\t\tcontents;\t\t// 0, to differentiate from leafs\n\tint\t\tvisframe;\t\t// node needs to be traversed if current\n\n\tfloat\t\tminmaxs[6];\t// for bounding box culling\n\tstruct mnode_s\t*parent;\n\n// node specific\n\tmplane_t\t\t*plane;\n\n#if !XASH_64BIT\n\tunion\n\t{\n\t\tstruct mnode_s *children_[2];\n\t\tstruct\n\t\t{\n\t\t\t// the ordering is important\n\t\t\tint child_0_leaf    : 1;\n\t\t\tint child_0_off     : 23;\n\t\t\tint firstsurface_1  : 8;\n\t\t\tint child_1_leaf    : 1;\n\t\t\tint child_1_off     : 23;\n\t\t\tint numsurfaces_1   : 8;\n\t\t};\n\t};\n\tunsigned short\tfirstsurface_0;\n\tunsigned short\tnumsurfaces_0;\n#else\n\t// in 64-bit ABI this struct has 4 more bytes of padding, let's use it!\n\tstruct mnode_s\t*children_[2];\n\tunsigned short\tfirstsurface_0;\n\tunsigned short\tnumsurfaces_0;\n\tunsigned short\tfirstsurface_1;\n\tunsigned short\tnumsurfaces_1;\n#endif\n} mnode_t;\n\ntypedef struct msurface_s\tmsurface_t;\ntypedef struct decal_s\tdecal_t;\n\n// JAY: Compress this as much as possible\nstruct decal_s\n{\n\tdecal_t\t\t*pnext;\t\t// linked list for each surface\n\tmsurface_t\t*psurface;\t// Surface id for persistence / unlinking\n\tfloat\t\tdx;\t\t// local texture coordinates\n\tfloat\t\tdy;\t\t//\n\tfloat\t\tscale;\t\t// Pixel scale\n\tshort\t\ttexture;\t\t// Decal texture\n\tshort\t\tflags;\t\t// Decal flags  FDECAL_*\n\tshort\t\tentityIndex;\t// Entity this is attached to\n// Xash3D specific\n\tvec3_t\t\tposition;\t\t// location of the decal center in world space.\n\tglpoly2_t\t*polys;\t\t// precomputed decal vertices\n\tintptr_t\treserved[4];\t// just for future expansions or mod-makers\n};\n\ntypedef struct mleaf_s\n{\n// common with node\n\tint\t\tcontents;\n\tint\t\tvisframe;\t\t// node needs to be traversed if current\n\n\tfloat\t\tminmaxs[6];\t// for bounding box culling\n\n\tstruct mnode_s\t*parent;\n// leaf specific\n\tbyte\t\t*compressed_vis;\n\tstruct efrag_s\t*efrags;\n\n\tmsurface_t\t**firstmarksurface;\n\tint\t\tnummarksurfaces;\n\tint\t\tcluster;\t\t// helper to acess to uncompressed visdata\n\tbyte\t\tambient_sound_level[NUM_AMBIENTS];\n} mleaf_t;\n\n// surface extradata\ntypedef struct mextrasurf_s\n{\n\tvec3_t\t\tmins, maxs;\n\tvec3_t\t\torigin;\t\t// surface origin\n\tstruct msurface_s\t*surf;\t\t// upcast to surface\n\n\t// extended light info\n\tint\t\tdlight_s, dlight_t;\t// gl lightmap coordinates for dynamic lightmaps\n\n\tshort\t\tlightmapmins[2];\t// lightmatrix\n\tshort\t\tlightextents[2];\n\tfloat\t\tlmvecs[2][4];\n\n\tcolor24\t\t*deluxemap;\t// note: this is the actual deluxemap data for this surface\n\tbyte\t\t*shadowmap;\t// note: occlusion map for this surface\n// begin userdata\n\tstruct msurface_s\t*lightmapchain;\t// lightmapped polys\n\tstruct mextrasurf_s\t*detailchain;\t// for detail textures drawing\n\tmfacebevel_t\t*bevel;\t\t// for exact face traceline\n\tstruct mextrasurf_s\t*lumachain;\t// draw fullbrights\n\tstruct cl_entity_s\t*parent;\t\t// upcast to owner entity\n\n\tint\t\tmirrortexturenum;\t// gl texnum\n\tfloat\t\tmirrormatrix[4][4];\n\n\tstruct grasshdr_s\t*grass;\t\t// grass that linked by this surface\n\tunsigned short\tgrasscount;\t// number of bushes per polygon (used to determine total VBO size)\n\tunsigned short\tnumverts;\t\t// world->vertexes[]\n\tint\t\tfirstvertex;\t// fisrt look up in tr.tbn_vectors[], then acess to world->vertexes[]\n\n\tintptr_t\treserved[32];\t// just for future expansions or mod-makers\n} mextrasurf_t;\n\n#ifdef SUPPORT_HL25_EXTENDED_STRUCTS\n// additional struct at the end of msurface_t for HL25 compatibility\ntypedef struct mdisplaylist_s\n{\n\tunsigned int gl_displaylist;\n\tint          rendermode;\n\tfloat        scrolloffset;\n\tint          renderDetailTexture;\n} mdisplaylist_t;\n#endif\n\nstruct msurface_s\n{\n\tint\t\tvisframe;\t\t// should be drawn when node is crossed\n\n\tmplane_t\t\t*plane;\t\t// pointer to shared plane\n\tint\t\tflags;\t\t// see SURF_ #defines\n\n\tint\t\tfirstedge;\t// look up in model->surfedges[], negative numbers\n\tint\t\tnumedges;\t\t// are backwards edges\n\n\tshort\t\ttexturemins[2];\n\tshort\t\textents[2];\n\n\tint\t\tlight_s, light_t;\t// gl lightmap coordinates\n\n\tglpoly2_t\t\t*polys;\t\t// multiple if warped\n\tstruct msurface_s\t*texturechain;\n\n\tmtexinfo_t\t*texinfo;\n\n\t// lighting info\n\tint\t\tdlightframe;\t// last frame the surface was checked by an animated light\n\tint\t\tdlightbits;\t// dynamically generated. Indicates if the surface illumination\n\t\t\t\t\t// is modified by an animated light.\n\n\tint\t\tlightmaptexturenum;\n\tbyte\t\tstyles[MAXLIGHTMAPS];\n\tint\t\tcached_light[MAXLIGHTMAPS];\t// values currently used in lightmap\n\tmextrasurf_t\t*info;\t\t// pointer to surface extradata (was cached_dlight)\n\n\tcolor24\t\t*samples;\t\t// note: this is the actual lightmap data for this surface\n\tdecal_t\t\t*pdecals;\n\n#ifdef SUPPORT_HL25_EXTENDED_STRUCTS\n\tmdisplaylist_t displaylist;\n#endif\n};\n\ntypedef struct hull_s\n{\n\tunion\n\t{\n\t\tmclipnode16_t *clipnodes16;\n\t\tmclipnode32_t *clipnodes32;\n\t};\n\tmplane_t\t\t*planes;\n\tint\t\tfirstclipnode;\n\tint\t\tlastclipnode;\n\tvec3_t\t\tclip_mins;\n\tvec3_t\t\tclip_maxs;\n} hull_t;\n\n#ifndef CACHE_USER\n#define CACHE_USER\ntypedef struct cache_user_s\n{\n\tvoid\t\t*data;\t\t// extradata\n} cache_user_t;\n#endif\n\ntypedef struct model_s\n{\n\tchar\t\tname[64];\t\t// model name\n\tqboolean\t\tneedload;\t\t// bmodels and sprites don't cache normally\n\n\t// shared modelinfo\n\tmodtype_t\t\ttype;\t\t// model type\n\tint\t\tnumframes;\t// sprite's framecount\n\tpoolhandle_t mempool;\t\t// private mempool (was synctype)\n\tint\t\tflags;\t\t// hl compatibility\n\n//\n// volume occupied by the model\n//\n\tvec3_t\t\tmins, maxs;\t// bounding box at angles '0 0 0'\n\tfloat\t\tradius;\n\n\t// brush model\n\tint\t\tfirstmodelsurface;\n\tint\t\tnummodelsurfaces;\n\n\tint\t\tnumsubmodels;\n\tdmodel_t\t\t*submodels;\t// or studio animations\n\n\tint\t\tnumplanes;\n\tmplane_t\t\t*planes;\n\n\tint\t\tnumleafs;\t\t// number of visible leafs, not counting 0\n\tmleaf_t\t\t*leafs;\n\n\tint\t\tnumvertexes;\n\tmvertex_t\t\t*vertexes;\n\n\tint\t\tnumedges;\n\tunion\n\t{\n\t\tmedge16_t *edges16;\n\t\tmedge32_t *edges32;\n\t};\n\n\n\tint\t\tnumnodes;\n\tmnode_t\t\t*nodes;\n\n\tint\t\tnumtexinfo;\n\tmtexinfo_t\t*texinfo;\n\n\tint\t\tnumsurfaces;\n\tmsurface_t\t*surfaces;\n\n\tint\t\tnumsurfedges;\n\tint\t\t*surfedges;\n\n\tint\t\tnumclipnodes;\n\tunion\n\t{\n\t\tmclipnode16_t *clipnodes16;\n\t\tmclipnode32_t *clipnodes32;\n\t};\n\n\tint\t\tnummarksurfaces;\n\tmsurface_t\t**marksurfaces;\n\n\thull_t\t\thulls[MAX_MAP_HULLS];\n\n\tint\t\tnumtextures;\n\ttexture_t\t\t**textures;\n\n\tbyte\t\t*visdata;\n\n\tcolor24\t\t*lightdata;\n\tchar\t\t*entities;\n//\n// additional model data\n//\n\tcache_user_t\tcache;\t\t// only access through Mod_Extradata\n} model_t;\n\ntypedef struct alight_s\n{\n\tint\t\tambientlight;\t// clip at 128\n\tint\t\tshadelight;\t// clip at 192 - ambientlight\n\tvec3_t\t\tcolor;\n\tfloat\t\t*plightvec;\n} alight_t;\n\ntypedef struct auxvert_s\n{\n\tfloat\t\tfv[3];\t\t// viewspace x, y\n} auxvert_t;\n\n#define MAX_SCOREBOARDNAME\t32\n#define MAX_INFO_STRING\t256\n\n#include \"custom.h\"\n\ntypedef struct player_info_s\n{\n\tint\t\tuserid;\t\t\t// User id on server\n\tchar\t\tuserinfo[MAX_INFO_STRING];\t// User info string\n\tchar\t\tname[MAX_SCOREBOARDNAME];\t// Name (extracted from userinfo)\n\tint\t\tspectator;\t\t// Spectator or not, unused (frags for quake demo playback)\n\n\tint\t\tping;\n\tint\t\tpacket_loss;\n\n\t// skin information\n\tchar\t\tmodel[64];\n\tint\t\ttopcolor;\n\tint\t\tbottomcolor;\n\n\t// last frame rendered\n\tint\t\trenderframe;\n\n\t// Gait frame estimation\n\tint\t\tgaitsequence;\n\tfloat\t\tgaitframe;\n\tfloat\t\tgaityaw;\n\tvec3_t\t\tprevgaitorigin;\n\n\tcustomization_t\tcustomdata;\n\n\t// hashed cd key\n\tchar\t\thashedcdkey[16];\n} player_info_t;\n\n//\n// sprite representation in memory\n//\ntypedef enum { SPR_SINGLE = 0, SPR_GROUP, SPR_ANGLED } spriteframetype_t;\n\ntypedef struct mspriteframe_s\n{\n\tint\t\twidth;\n\tint\t\theight;\n\tfloat\t\tup, down, left, right;\n\tint\t\tgl_texturenum;\n} mspriteframe_t;\n\ntypedef struct\n{\n\tint\t\tnumframes;\n\tfloat\t\t*intervals;\n\tmspriteframe_t\t*frames[1];\n} mspritegroup_t;\n\ntypedef struct\n{\n\tspriteframetype_t\ttype;\n\tmspriteframe_t\t*frameptr;\n} mspriteframedesc_t;\n\ntypedef struct\n{\n\tshort\t\ttype;\n\tshort\t\ttexFormat;\n\tint\t\tmaxwidth;\n\tint\t\tmaxheight;\n\tint\t\tnumframes;\n\tint\t\tradius;\n\tint\t\tfacecull;\n\tint\t\tsynctype;\n\tmspriteframedesc_t\tframes[1];\n} msprite_t;\n\n/*\n==============================================================================\n\nALIAS MODELS\n\nAlias models are position independent, so the cache manager can move them.\n==============================================================================\n*/\n#define MAXALIASVERTS\t2048\n#define MAXALIASFRAMES\t256\n#define MAXALIASTRIS\t4096\n#define MAX_SKINS\t\t32\n\n// This mirrors trivert_t in trilib.h, is present so Quake knows how to\n// load this data\ntypedef struct\n{\n\tbyte\t\tv[3];\n\tbyte\t\tlightnormalindex;\n} trivertex_t;\n\ntypedef struct\n{\n\tint\t\tfirstpose;\n\tint\t\tnumposes;\n\ttrivertex_t\tbboxmin;\n\ttrivertex_t\tbboxmax;\n\tfloat\t\tinterval;\n\tchar\t\tname[16];\n} maliasframedesc_t;\n\ntypedef struct\n{\n\tint\t\tident;\n\tint\t\tversion;\n\tvec3_t\t\tscale;\n\tvec3_t\t\tscale_origin;\n\tfloat\t\tboundingradius;\n\tvec3_t\t\teyeposition;\n\tint\t\tnumskins;\n\tint\t\tskinwidth;\n\tint\t\tskinheight;\n\tint\t\tnumverts;\n\tint\t\tnumtris;\n\tint\t\tnumframes;\n\tint\t\tsynctype;\n\tint\t\tflags;\n\tfloat\t\tsize;\n\n\tconst trivertex_t **pposeverts; // only valid during loading, used to build GL mesh\n\tintptr_t\treserved[7];\t\t// VBO offsets\n\n\tint\t\tnumposes;\n\tint\t\tposeverts;\n\ttrivertex_t\t*posedata;\t// numposes * poseverts trivert_t\n\tint\t\t*commands;\t// gl command list with embedded s/t\n\tunsigned short\tgl_texturenum[MAX_SKINS][4];\n\tunsigned short\tfb_texturenum[MAX_SKINS][4];\n\tunsigned short\tgl_reserved0[MAX_SKINS][4];\t// detail tex\n\tunsigned short\tgl_reserved1[MAX_SKINS][4];\t// normalmap\n\tunsigned short\tgl_reserved2[MAX_SKINS][4];\t// glossmap\n\n\tmaliasframedesc_t\tframes[1];\t// variable sized\n} aliashdr_t;\n\n\n\n// remapping info\n#define SUIT_HUE_START\t\t192\n#define SUIT_HUE_END\t\t223\n#define PLATE_HUE_START\t\t160\n#define PLATE_HUE_END\t\t191\n\n#define SHIRT_HUE_START\t\t16\n#define SHIRT_HUE_END\t\t32\n#define PANTS_HUE_START\t\t96\n#define PANTS_HUE_END\t\t112\n\n\n// 1/32 epsilon to keep floating point happy\n#define DIST_EPSILON\t\t(1.0f / 32.0f)\n#define FRAC_EPSILON\t\t(1.0f / 1024.0f)\n#define BACKFACE_EPSILON\t\t0.01f\n#define MAX_BOX_LEAFS\t\t256\n#define ANIM_CYCLE\t\t\t2\n#define MOD_FRAMES\t\t\t20\n\n#define MAX_DEMOS\t\t32\n#define MAX_MOVIES\t\t8\n#define MAX_CDTRACKS\t32\n#define MAX_CLIENT_SPRITES\t512\t// SpriteTextures (0-256 hud, 256-512 client)\n#define MAX_REQUESTS\t64\n\nSTATIC_CHECK_SIZEOF( mnode_t, 52, 72 );\nSTATIC_CHECK_SIZEOF( mextrasurf_t, 324, 496 );\nSTATIC_CHECK_SIZEOF( decal_t, 60, 88 );\nSTATIC_CHECK_SIZEOF( mfaceinfo_t, 176, 304 );\n\n// model flags (stored in model_t->flags)\n#define MODEL_QBSP2 BIT( 28 ) // uses 32-bit types\n\n// access functions\nstatic inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mod )\n{\n#if !XASH_64BIT\n\tif( unlikely( mod->flags & MODEL_QBSP2 )) // MODEL_QBSP2\n\t{\n\t\tif( side == 0 )\n\t\t{\n\t\t\tif( n->child_0_leaf )\n\t\t\t\treturn (mnode_t *)(mod->leafs + n->child_0_off);\n\t\t\telse\n\t\t\t\treturn (mnode_t *)(mod->nodes + n->child_0_off);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( n->child_1_leaf )\n\t\t\t\treturn (mnode_t *)(mod->leafs + n->child_1_off);\n\t\t\telse\n\t\t\t\treturn (mnode_t *)(mod->nodes + n->child_1_off);\n\t\t}\n\t}\n\n\treturn n->children_[side];\n#else\n\treturn n->children_[side];\n#endif\n}\n\nstatic inline void node_children( mnode_t *children[2], const mnode_t *n, const model_t *mod )\n{\n\tchildren[0] = node_child( n, 0, mod );\n\tchildren[1] = node_child( n, 1, mod );\n}\n\nstatic inline int node_firstsurface( const mnode_t *n, const model_t *mod )\n{\n\tif( mod->flags & MODEL_QBSP2 )\n\t\treturn n->firstsurface_0 + ( n->firstsurface_1 << 16 );\n\telse\n\t\treturn n->firstsurface_0;\n}\n\nstatic inline int node_numsurfaces( const mnode_t *n, const model_t *mod )\n{\n\tif( mod->flags & MODEL_QBSP2 )\n\t\treturn n->numsurfaces_0 + ( n->numsurfaces_1 << 16 );\n\telse\n\t\treturn n->numsurfaces_0;\n}\n\n#endif//COM_MODEL_H\n"
  },
  {
    "path": "common/con_nprint.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#ifndef CON_NPRINT_H\n#define CON_NPRINT_H\n\ntypedef struct con_nprint_s\n{\n\tint\tindex;\t\t// Row #\n\tfloat\ttime_to_live;\t// # of seconds before it dissappears\n\tfloat\tcolor[3];\t\t// RGB colors ( 0.0 -> 1.0 scale )\n} con_nprint_t;\n\n#endif//CON_NPRINT_H\n"
  },
  {
    "path": "common/const.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#ifndef CONST_H\n#define CONST_H\n//\n// Constants shared by the engine and dlls\n// This header file included by engine files and DLL files.\n// Most came from server.h\n\n// edict->flags\n#define FL_FLY\t\t(1U<<0)\t// Changes the SV_Movestep() behavior to not need to be on ground\n#define FL_SWIM\t\t(1U<<1)\t// Changes the SV_Movestep() behavior to not need to be on ground (but stay in water)\n#define FL_CONVEYOR\t\t(1U<<2)\n#define FL_CLIENT\t\t(1U<<3)\n#define FL_INWATER\t\t(1U<<4)\n#define FL_MONSTER\t\t(1U<<5)\n#define FL_GODMODE\t\t(1U<<6)\n#define FL_NOTARGET\t\t(1U<<7)\n#define FL_SKIPLOCALHOST\t(1U<<8)\t// Don't send entity to local host, it's predicting this entity itself\n#define FL_ONGROUND\t\t(1U<<9)\t// At rest / on the ground\n#define FL_PARTIALGROUND\t(1U<<10)\t// not all corners are valid\n#define FL_WATERJUMP\t(1U<<11)\t// player jumping out of water\n#define FL_FROZEN\t\t(1U<<12)\t// Player is frozen for 3rd person camera\n#define FL_FAKECLIENT\t(1U<<13)\t// JAC: fake client, simulated server side; don't send network messages to them\n#define FL_DUCKING\t\t(1U<<14)\t// Player flag -- Player is fully crouched\n#define FL_FLOAT\t\t(1U<<15)\t// Apply floating force to this entity when in water\n#define FL_GRAPHED\t\t(1U<<16)\t// worldgraph has this ent listed as something that blocks a connection\n\n// UNDONE: Do we need these?\n#define FL_IMMUNE_WATER\t(1U<<17)\n#define FL_IMMUNE_SLIME\t(1U<<18)\n#define FL_IMMUNE_LAVA\t(1U<<19)\n\n#define FL_PROXY\t\t(1U<<20)\t// This is a spectator proxy\n#define FL_ALWAYSTHINK\t(1U<<21)\t// Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path)\n#define FL_BASEVELOCITY\t(1U<<22)\t// Base velocity has been applied this frame (used to convert base velocity into momentum)\n#define FL_MONSTERCLIP\t(1U<<23)\t// Only collide in with monsters who have FL_MONSTERCLIP set\n#define FL_ONTRAIN\t\t(1U<<24)\t// Player is _controlling_ a train, so movement commands should be ignored on client during prediction.\n#define FL_WORLDBRUSH\t(1U<<25)\t// Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something)\n#define FL_SPECTATOR\t(1U<<26)\t// This client is a spectator, don't run touch functions, etc.\n#define FL_LASERDOT\t\t(1U<<27)\t// Predicted laser spot from rocket launcher\n\n#define FL_CUSTOMENTITY\t(1U<<29)\t// This is a custom entity\n#define FL_KILLME\t\t(1U<<30)\t// This entity is marked for death -- This allows the engine to kill ents at the appropriate time\n#define FL_DORMANT\t\t(1U<<31)\t// Entity is dormant, no updates to client\n\n// Goes into globalvars_t.trace_flags\n#define FTRACE_SIMPLEBOX\t\t(1U<<0)\t// Traceline with a simple box\n#define FTRACE_IGNORE_GLASS\t\t(1U<<1)\t// traceline will be ignored entities with rendermode != kRenderNormal\n\n// walkmove modes\n#define WALKMOVE_NORMAL\t\t0\t// normal walkmove\n#define WALKMOVE_WORLDONLY\t\t1\t// doesn't hit ANY entities, no matter what the solid type\n#define WALKMOVE_CHECKONLY\t\t2\t// move, but don't touch triggers\n\n// edict->movetype values\n#define MOVETYPE_NONE\t\t0\t// never moves\n//#define\tMOVETYPE_ANGLENOCLIP\t1\n//#define\tMOVETYPE_ANGLECLIP\t\t2\n#define MOVETYPE_WALK\t\t3\t// Player only - moving on the ground\n#define MOVETYPE_STEP\t\t4\t// gravity, special edge handling -- monsters use this\n#define MOVETYPE_FLY\t\t5\t// No gravity, but still collides with stuff\n#define MOVETYPE_TOSS\t\t6\t// gravity/collisions\n#define MOVETYPE_PUSH\t\t7\t// no clip to world, push and crush\n#define MOVETYPE_NOCLIP\t\t8\t// No gravity, no collisions, still do velocity/avelocity\n#define MOVETYPE_FLYMISSILE\t\t9\t// extra size to monsters\n#define MOVETYPE_BOUNCE\t\t10\t// Just like Toss, but reflect velocity when contacting surfaces\n#define MOVETYPE_BOUNCEMISSILE\t11\t// bounce w/o gravity\n#define MOVETYPE_FOLLOW\t\t12\t// track movement of aiment\n#define MOVETYPE_PUSHSTEP\t\t13\t// BSP model that needs physics/world collisions (uses nearest hull for world collision)\n#define MOVETYPE_COMPOUND\t\t14\t// glue two entities together (simple movewith)\n\n// edict->solid values\n// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves\n// SOLID only effects OTHER entities colliding with this one when they move - UGH!\n#define SOLID_NOT\t\t\t0\t// no interaction with other objects\n#define SOLID_TRIGGER\t\t1\t// touch on edge, but not blocking\n#define SOLID_BBOX\t\t\t2\t// touch on edge, block\n#define SOLID_SLIDEBOX\t\t3\t// touch on edge, but not an onground\n#define SOLID_BSP\t\t\t4\t// bsp clip, touch on edge, block\n#define SOLID_CUSTOM\t\t5\t// call external callbacks for tracing\n#define SOLID_PORTAL\t\t6\t// borrowed from FTE\n\n// edict->deadflag values\n#define DEAD_NO\t\t\t0 \t// alive\n#define DEAD_DYING\t\t\t1 \t// playing death animation or still falling off of a ledge waiting to hit ground\n#define DEAD_DEAD\t\t\t2 \t// dead. lying still.\n#define DEAD_RESPAWNABLE\t\t3\n#define DEAD_DISCARDBODY\t\t4\n\n#define DAMAGE_NO\t\t\t0\n#define DAMAGE_YES\t\t\t1\n#define DAMAGE_AIM\t\t\t2\n\n// entity effects\n#define EF_BRIGHTFIELD\t\t1\t// swirling cloud of particles\n#define EF_MUZZLEFLASH\t\t2\t// single frame ELIGHT on entity attachment 0\n#define EF_BRIGHTLIGHT\t\t4\t// DLIGHT centered at entity origin\n#define EF_DIMLIGHT\t\t\t8\t// player flashlight\n#define EF_INVLIGHT\t\t\t16\t// get lighting from ceiling\n#define EF_NOINTERP\t\t\t32\t// don't interpolate the next frame\n#define EF_LIGHT\t\t\t64\t// rocket flare glow sprite\n#define EF_NODRAW\t\t\t128\t// don't draw entity\n\n#define EF_WATERSIDES\t\t(1U<<26)\t// Do not remove sides for func_water entity\n#define EF_FULLBRIGHT\t\t(1U<<27)\t// Just get fullbright\n#define EF_NOSHADOW\t\t\t(1U<<28)\t// ignore shadow for this entity\n#define EF_MERGE_VISIBILITY\t\t(1U<<29)\t// this entity allowed to merge vis (e.g. env_sky or portal camera)\n#define EF_REQUEST_PHS\t\t(1U<<30)\t// This entity requested phs bitvector instead of pvsbitvector in AddToFullPack calls\n// g-cont. one reserved bit here for me\n\n// entity flags\n#define EFLAG_SLERP\t\t\t1\t// do studio interpolation of this entity\n\n//\n// temp entity events\n//\n#define\tTE_BEAMPOINTS\t\t0\t// beam effect between two points\n// coord coord coord (start position)\n// coord coord coord (end position)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define\tTE_BEAMENTPOINT\t\t1\t// beam effect between point and entity\n// short (start entity)\n// coord coord coord (end position)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define\tTE_GUNSHOT\t\t2\t// particle effect plus ricochet sound\n// coord coord coord (position)\n\n#define\tTE_EXPLOSION\t\t3\t// additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps\n// coord coord coord (position)\n// short (sprite index)\n// byte (scale in 0.1's)\n// byte (framerate)\n// byte (flags)\n//\n// The Explosion effect has some flags to control performance/aesthetic features:\n#define TE_EXPLFLAG_NONE\t\t0\t// all flags clear makes default Half-Life explosion\n#define TE_EXPLFLAG_NOADDITIVE\t1\t// sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite)\n#define TE_EXPLFLAG_NODLIGHTS\t\t2\t// do not render dynamic lights\n#define TE_EXPLFLAG_NOSOUND\t\t4\t// do not play client explosion sound\n#define TE_EXPLFLAG_NOPARTICLES\t8\t// do not draw particles\n#define TE_EXPLFLAG_DRAWALPHA\t\t16\t// sprite will be drawn alpha\n#define TE_EXPLFLAG_ROTATE\t\t32\t// rotate the sprite randomly\n\n#define\tTE_TAREXPLOSION\t\t4\t// Quake1 \"tarbaby\" explosion with sound\n// coord coord coord (position)\n\n#define\tTE_SMOKE\t\t\t5\t// alphablend sprite, move vertically 30 pps\n// coord coord coord (position)\n// short (sprite index)\n// byte (scale in 0.1's)\n// byte (framerate)\n\n#define\tTE_TRACER\t\t\t6\t// tracer effect from point to point\n// coord, coord, coord (start)\n// coord, coord, coord (end)\n\n#define\tTE_LIGHTNING\t\t7\t// TE_BEAMPOINTS with simplified parameters\n// coord, coord, coord (start)\n// coord, coord, coord (end)\n// byte (life in 0.1's)\n// byte (width in 0.1's)\n// byte (amplitude in 0.01's)\n// short (sprite model index)\n\n#define\tTE_BEAMENTS\t\t8\n// short (start entity)\n// short (end entity)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define\tTE_SPARKS\t\t\t9\t// 8 random tracers with gravity, ricochet sprite\n// coord coord coord (position)\n\n#define\tTE_LAVASPLASH\t\t10\t// Quake1 lava splash\n// coord coord coord (position)\n\n#define\tTE_TELEPORT\t\t11\t// Quake1 teleport splash\n// coord coord coord (position)\n\n#define TE_EXPLOSION2\t\t12\t// Quake1 colormaped (base palette) particle explosion with sound\n// coord coord coord (position)\n// byte (starting color)\n// byte (num colors)\n\n#define TE_BSPDECAL\t\t\t13\t// Decal from the .BSP file\n// coord, coord, coord (x,y,z), decal position (center of texture in world)\n// short (texture index of precached decal texture name)\n// short (entity index)\n// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity)\n\n#define TE_IMPLOSION\t\t14\t// tracers moving toward a point\n// coord, coord, coord (position)\n// byte (radius)\n// byte (count)\n// byte (life in 0.1's)\n\n#define TE_SPRITETRAIL\t\t15\t// line of moving glow sprites with gravity, fadeout, and collisions\n// coord, coord, coord (start)\n// coord, coord, coord (end)\n// short (sprite index)\n// byte (count)\n// byte (life in 0.1's)\n// byte (scale in 0.1's)\n// byte (velocity along vector in 10's)\n// byte (randomness of velocity in 10's)\n\n#define TE_BEAM\t\t\t16\t// obsolete\n\n#define TE_SPRITE\t\t\t17\t// additive sprite, plays 1 cycle\n// coord, coord, coord (position)\n// short (sprite index)\n// byte (scale in 0.1's)\n// byte (brightness)\n\n#define TE_BEAMSPRITE\t\t18\t// A beam with a sprite at the end\n// coord, coord, coord (start position)\n// coord, coord, coord (end position)\n// short (beam sprite index)\n// short (end sprite index)\n\n#define TE_BEAMTORUS\t\t19\t// screen aligned beam ring, expands to max radius over lifetime\n// coord coord coord (center position)\n// coord coord coord (axis and radius)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define TE_BEAMDISK\t\t\t20\t// disk that expands to max radius over lifetime\n// coord coord coord (center position)\n// coord coord coord (axis and radius)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define TE_BEAMCYLINDER\t\t21\t// cylinder that expands to max radius over lifetime\n// coord coord coord (center position)\n// coord coord coord (axis and radius)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define TE_BEAMFOLLOW\t\t22\t// create a line of decaying beam segments until entity stops moving\n// short (entity:attachment to follow)\n// short (sprite index)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte,byte,byte (color)\n// byte (brightness)\n\n#define TE_GLOWSPRITE\t\t23\n// coord, coord, coord (pos) short (model index) byte (scale / 10)\n\n#define TE_BEAMRING\t\t\t24\t// connect a beam ring to two entities\n// short (start entity)\n// short (end entity)\n// short (sprite index)\n// byte (starting frame)\n// byte (frame rate in 0.1's)\n// byte (life in 0.1's)\n// byte (line width in 0.1's)\n// byte (noise amplitude in 0.01's)\n// byte,byte,byte (color)\n// byte (brightness)\n// byte (scroll speed in 0.1's)\n\n#define TE_STREAK_SPLASH\t25\t\t// oriented shower of tracers\n// coord coord coord (start position)\n// coord coord coord (direction vector)\n// byte (color)\n// short (count)\n// short (base speed)\n// short (random velocity)\n\n#define TE_BEAMHOSE\t\t\t26\t// obsolete\n\n#define TE_DLIGHT\t\t\t27\t// dynamic light, effect world, minor entity effect\n// coord, coord, coord (pos)\n// byte (radius in 10's)\n// byte byte byte (color)\n// byte (life in 10's)\n// byte (decay rate in 10's)\n\n#define TE_ELIGHT\t\t\t28\t// point entity light, no world effect\n// short (entity:attachment to follow)\n// coord coord coord (initial position)\n// coord (radius)\n// byte byte byte (color)\n// byte (life in 0.1's)\n// coord (decay rate)\n\n#define TE_TEXTMESSAGE\t\t29\n// short 1.2.13 x (-1 = center)\n// short 1.2.13 y (-1 = center)\n// byte Effect 0 = fade in/fade out\n// 1 is flickery credits\n// 2 is write out (training room)\n// 4 bytes r,g,b,a color1\t(text color)\n// 4 bytes r,g,b,a color2\t(effect color)\n// ushort 8.8 fadein time\n// ushort 8.8  fadeout time\n// ushort 8.8 hold time\n// optional ushort 8.8 fxtime\t(time the highlight lags behing the leading text in effect 2)\n// string text message\t\t(512 chars max sz string)\n#define TE_LINE\t\t\t30\n// coord, coord, coord\tstartpos\n// coord, coord, coord\tendpos\n// short life in 0.1 s\n// 3 bytes r, g, b\n\n#define TE_BOX\t\t\t31\n// coord, coord, coord\tboxmins\n// coord, coord, coord\tboxmaxs\n// short life in 0.1 s\n// 3 bytes r, g, b\n\n#define TE_KILLBEAM\t\t\t99\t// kill all beams attached to entity\n// short (entity)\n\n#define TE_LARGEFUNNEL\t\t100\n// coord coord coord (funnel position)\n// short (sprite index)\n// short (flags)\n\n#define\tTE_BLOODSTREAM\t\t101\t// particle spray\n// coord coord coord (start position)\n// coord coord coord (spray vector)\n// byte (color)\n// byte (speed)\n\n#define\tTE_SHOWLINE\t\t102\t// line of particles every 5 units, dies in 30 seconds\n// coord coord coord (start position)\n// coord coord coord (end position)\n\n#define TE_BLOOD\t\t\t103\t// particle spray\n// coord coord coord (start position)\n// coord coord coord (spray vector)\n// byte (color)\n// byte (speed)\n\n#define TE_DECAL\t\t\t104\t// Decal applied to a brush entity (not the world)\n// coord, coord, coord (x,y,z), decal position (center of texture in world)\n// byte (texture index of precached decal texture name)\n// short (entity index)\n\n#define TE_FIZZ\t\t\t105\t// create alpha sprites inside of entity, float upwards\n// short (entity)\n// short (sprite index)\n// byte (density)\n\n#define TE_MODEL\t\t\t106\t// create a moving model that bounces and makes a sound when it hits\n// coord, coord, coord (position)\n// coord, coord, coord (velocity)\n// angle (initial yaw)\n// short (model index)\n// byte (bounce sound type)\n// byte (life in 0.1's)\n\n#define TE_EXPLODEMODEL\t\t107\t// spherical shower of models, picks from set\n// coord, coord, coord (origin)\n// coord (velocity)\n// short (model index)\n// short (count)\n// byte (life in 0.1's)\n\n#define TE_BREAKMODEL\t\t108\t// box of models or sprites\n// coord, coord, coord (position)\n// coord, coord, coord (size)\n// coord, coord, coord (velocity)\n// byte (random velocity in 10's)\n// short (sprite or model index)\n// byte (count)\n// byte (life in 0.1 secs)\n// byte (flags)\n\n#define TE_GUNSHOTDECAL\t\t109\t// decal and ricochet sound\n// coord, coord, coord (position)\n// short (entity index???)\n// byte (decal???)\n\n#define TE_SPRITE_SPRAY\t\t110\t// spay of alpha sprites\n// coord, coord, coord (position)\n// coord, coord, coord (velocity)\n// short (sprite index)\n// byte (count)\n// byte (speed)\n// byte (noise)\n\n#define TE_ARMOR_RICOCHET\t\t111\t// quick spark sprite, client ricochet sound.\n// coord, coord, coord (position)\n// byte (scale in 0.1's)\n\n#define TE_PLAYERDECAL\t\t112\t// ???\n// byte (playerindex)\n// coord, coord, coord (position)\n// short (entity???)\n// byte (decal number???)\n// [optional] short (model index???)\n\n#define TE_BUBBLES\t\t\t113\t// create alpha sprites inside of box, float upwards\n// coord, coord, coord (min start position)\n// coord, coord, coord (max start position)\n// coord (float height)\n// short (model index)\n// byte (count)\n// coord (speed)\n\n#define TE_BUBBLETRAIL\t\t114\t// create alpha sprites along a line, float upwards\n// coord, coord, coord (min start position)\n// coord, coord, coord (max start position)\n// coord (float height)\n// short (model index)\n// byte (count)\n// coord (speed)\n\n#define TE_BLOODSPRITE\t\t115\t// spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent)\n// coord, coord, coord (position)\n// short (sprite1 index)\n// short (sprite2 index)\n// byte (color)\n// byte (scale)\n\n#define TE_WORLDDECAL\t\t116\t// Decal applied to the world brush\n// coord, coord, coord (x,y,z), decal position (center of texture in world)\n// byte (texture index of precached decal texture name)\n\n#define TE_WORLDDECALHIGH\t\t117\t// Decal (with texture index > 256) applied to world brush\n// coord, coord, coord (x,y,z), decal position (center of texture in world)\n// byte (texture index of precached decal texture name - 256)\n\n#define TE_DECALHIGH\t\t118\t// Same as TE_DECAL, but the texture index was greater than 256\n// coord, coord, coord (x,y,z), decal position (center of texture in world)\n// byte (texture index of precached decal texture name - 256)\n// short (entity index)\n\n#define TE_PROJECTILE\t\t119\t// Makes a projectile (like a nail) (this is a high-priority tent)\n// coord, coord, coord (position)\n// coord, coord, coord (velocity)\n// short (modelindex)\n// byte (life)\n// byte (owner)  projectile won't collide with owner (if owner == 0, projectile will hit any client).\n\n#define TE_SPRAY\t\t\t120\t// Throws a shower of sprites or models\n// coord, coord, coord (position)\n// coord, coord, coord (direction)\n// short (modelindex)\n// byte (count)\n// byte (speed)\n// byte (noise)\n// byte (rendermode)\n\n#define TE_PLAYERSPRITES\t\t121\t// sprites emit from a player's bounding box (ONLY use for players!)\n// byte (playernum)\n// short (sprite modelindex)\n// byte (count)\n// byte (variance) (0 = no variance in size) (10 = 10% variance in size)\n\n#define TE_PARTICLEBURST\t\t122\t// very similar to lavasplash.\n// coord (origin)\n// short (radius)\n// byte (particle color)\n// byte (duration * 10) (will be randomized a bit)\n\n#define TE_FIREFIELD\t\t123\t// makes a field of fire.\n// coord (origin)\n// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius)\n// short (modelindex)\n// byte (count)\n// byte (flags)\n// byte (duration (in seconds) * 10) (will be randomized a bit)\n//\n// to keep network traffic low, this message has associated flags that fit into a byte:\n#define TEFIRE_FLAG_ALLFLOAT\t1 // all sprites will drift upwards as they animate\n#define TEFIRE_FLAG_SOMEFLOAT\t2 // some of the sprites will drift upwards. (50% chance)\n#define TEFIRE_FLAG_LOOP\t4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration.\n#define TEFIRE_FLAG_ALPHA\t8 // if set, sprite is rendered alpha blended at 50% else, opaque\n#define TEFIRE_FLAG_PLANAR\t16 // if set, all fire sprites have same initial Z instead of randomly filling a cube.\n#define TEFIRE_FLAG_ADDITIVE\t32 // if set, sprite is rendered as additive\n\n#define TE_PLAYERATTACHMENT\t\t124\t// attaches a TENT to a player (this is a high-priority tent)\n// byte (entity index of player)\n// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset )\n// short (model index)\n// short (life * 10 );\n\n#define TE_KILLPLAYERATTACHMENTS\t125\t// will expire all TENTS attached to a player.\n// byte (entity index of player)\n\n#define TE_MULTIGUNSHOT\t\t126\t// much more compact shotgun message\n// This message is used to make a client approximate a 'spray' of gunfire.\n// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is\n// a good candidate for MULTIGUNSHOT use. (shotguns)\n//\n// NOTE: This effect makes the client do traces for each bullet, these client traces ignore\n// entities that have studio models.Traces are 4096 long.\n//\n// coord (origin)\n// coord (origin)\n// coord (origin)\n// coord (direction)\n// coord (direction)\n// coord (direction)\n// coord (x noise * 100)\n// coord (y noise * 100)\n// byte (count)\n// byte (bullethole decal texture index)\n\n#define TE_USERTRACER\t\t127\t// larger message than the standard tracer, but allows some customization.\n// coord (origin)\n// coord (origin)\n// coord (origin)\n// coord (velocity)\n// coord (velocity)\n// coord (velocity)\n// byte ( life * 10 )\n// byte ( color ) this is an index into an array of color vectors in the engine. (0 - )\n// byte ( length * 10 )\n\n#define MSG_BROADCAST\t\t0\t// unreliable to all\n#define MSG_ONE\t\t\t1\t// reliable to one (msg_entity)\n#define MSG_ALL\t\t\t2\t// reliable to all\n#define MSG_INIT\t\t\t3\t// write to the init string\n#define MSG_PVS\t\t\t4\t// Ents in PVS of org\n#define MSG_PAS\t\t\t5\t// Ents in PAS of org\n#define MSG_PVS_R\t\t\t6\t// Reliable to PVS\n#define MSG_PAS_R\t\t\t7\t// Reliable to PAS\n#define MSG_ONE_UNRELIABLE\t\t8\t// Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped )\n#define MSG_SPEC\t\t\t9\t// Sends to all spectator proxies\n\n// contents of a spot in the world\n#define CONTENTS_EMPTY\t\t-1\n#define CONTENTS_SOLID\t\t-2\n#define CONTENTS_WATER\t\t-3\n#define CONTENTS_SLIME\t\t-4\n#define CONTENTS_LAVA\t\t-5\n#define CONTENTS_SKY\t\t-6\n// These additional contents constants are defined in bspfile.h\n#define CONTENTS_ORIGIN\t\t-7\t// removed at csg time\n#define CONTENTS_CLIP\t\t-8\t// changed to contents_solid\n#define CONTENTS_CURRENT_0\t\t-9\n#define CONTENTS_CURRENT_90\t\t-10\n#define CONTENTS_CURRENT_180\t\t-11\n#define CONTENTS_CURRENT_270\t\t-12\n#define CONTENTS_CURRENT_UP\t\t-13\n#define CONTENTS_CURRENT_DOWN\t\t-14\n#define CONTENTS_TRANSLUCENT\t\t-15\n\n#define CONTENTS_LADDER\t\t-16\n\n#define CONTENT_FLYFIELD\t\t-17\n#define CONTENT_GRAVITY_FLYFIELD\t-18\n#define CONTENT_FOG\t\t\t-19\n\n// channels\n#define CHAN_AUTO\t\t\t0\n#define CHAN_WEAPON\t\t\t1\n#define CHAN_VOICE\t\t\t2\n#define CHAN_ITEM\t\t\t3\n#define CHAN_BODY\t\t\t4\n#define CHAN_STREAM\t\t\t5\t// allocate stream channel from the static or dynamic area\n#define CHAN_STATIC\t\t\t6\t// allocate channel from the static area\n#define CHAN_NETWORKVOICE_BASE\t7\t// voice data coming across the network\n#define CHAN_NETWORKVOICE_END\t\t500\t// network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END).\n\n// attenuation values\n#define ATTN_NONE\t\t\t0\n#define ATTN_NORM\t\t\t(float)0.8\n#define ATTN_IDLE\t\t\t(float)2\n#define ATTN_STATIC\t\t\t(float)1.25\n\n// pitch values\n#define PITCH_NORM\t\t\t100\t// non-pitch shifted\n#define PITCH_LOW\t\t\t95\t// other values are possible - 0-255, where 255 is very high\n#define PITCH_HIGH\t\t\t120\n\n// volume values\n#define VOL_NORM\t\t\t1.0\n\n// plats\n#define PLAT_LOW_TRIGGER\t\t1\n\n// Trains\n#define SF_TRAIN_WAIT_RETRIGGER\t1\n#define SF_TRAIN_START_ON\t\t4\t// Train is initially moving\n#define SF_TRAIN_PASSABLE\t\t8\t// Train is not solid -- used to make water trains\n\n// buttons\n#define IN_ATTACK\t\t\t(1U<<0)\n#define IN_JUMP\t\t\t(1U<<1)\n#define IN_DUCK\t\t\t(1U<<2)\n#define IN_FORWARD\t\t\t(1U<<3)\n#define IN_BACK\t\t\t(1U<<4)\n#define IN_USE\t\t\t(1U<<5)\n#define IN_CANCEL\t\t\t(1U<<6)\n#define IN_LEFT\t\t\t(1U<<7)\n#define IN_RIGHT\t\t\t(1U<<8)\n#define IN_MOVELEFT\t\t\t(1U<<9)\n#define IN_MOVERIGHT\t\t(1U<<10)\n#define IN_ATTACK2\t\t\t(1U<<11)\n#define IN_RUN\t\t\t(1U<<12)\n#define IN_RELOAD\t\t\t(1U<<13)\n#define IN_ALT1\t\t\t(1U<<14)\n#define IN_SCORE\t\t\t(1U<<15)   // Used by client.dll for when scoreboard is held down\n\n// Break Model Defines\n#define BREAK_TYPEMASK\t\t0x4F\n#define BREAK_GLASS\t\t\t0x01\n#define BREAK_METAL\t\t\t0x02\n#define BREAK_FLESH\t\t\t0x04\n#define BREAK_WOOD\t\t\t0x08\n#define BREAK_SMOKE\t\t\t0x10\n#define BREAK_TRANS\t\t\t0x20\n#define BREAK_CONCRETE\t\t0x40\n#define BREAK_2\t\t\t0x80\n\n// Colliding temp entity sounds\n#define BOUNCE_GLASS\t\tBREAK_GLASS\n#define BOUNCE_METAL\t\tBREAK_METAL\n#define BOUNCE_FLESH\t\tBREAK_FLESH\n#define BOUNCE_WOOD\t\t\tBREAK_WOOD\n#define BOUNCE_SHRAP\t\t0x10\n#define BOUNCE_SHELL\t\t0x20\n#define BOUNCE_CONCRETE\t\tBREAK_CONCRETE\n#define BOUNCE_SHOTSHELL\t\t0x80\n\n// Temp entity bounce sound types\n#define TE_BOUNCE_NULL\t\t0\n#define TE_BOUNCE_SHELL\t\t1\n#define TE_BOUNCE_SHOTSHELL\t\t2\n\n// Rendering constants\nenum\n{\n\tkRenderNormal,\t\t// src\n\tkRenderTransColor,\t\t// c*a+dest*(1-a)\n\tkRenderTransTexture,\t// src*a+dest*(1-a)\n\tkRenderGlow,\t\t// src*a+dest -- No Z buffer checks\n\tkRenderTransAlpha,\t\t// src*srca+dest*(1-srca)\n\tkRenderTransAdd,\t\t// src*a+dest\n};\n\nenum\n{\n\tkRenderFxNone = 0,\n\tkRenderFxPulseSlow,\n\tkRenderFxPulseFast,\n\tkRenderFxPulseSlowWide,\n\tkRenderFxPulseFastWide,\n\tkRenderFxFadeSlow,\n\tkRenderFxFadeFast,\n\tkRenderFxSolidSlow,\n\tkRenderFxSolidFast,\n\tkRenderFxStrobeSlow,\n\tkRenderFxStrobeFast,\n\tkRenderFxStrobeFaster,\n\tkRenderFxFlickerSlow,\n\tkRenderFxFlickerFast,\n\tkRenderFxNoDissipation,\n\tkRenderFxDistort,\t\t\t// Distort/scale/translate flicker\n\tkRenderFxHologram,\t\t\t// kRenderFxDistort + distance fade\n\tkRenderFxDeadPlayer,\t\t// kRenderAmt is the player index\n\tkRenderFxExplode,\t\t\t// Scale up really big!\n\tkRenderFxGlowShell,\t\t\t// Glowing Shell\n\tkRenderFxClampMinScale,\t\t// Keep this sprite from getting very small (SPRITES only!)\n\tkRenderFxLightMultiplier,\n};\n\ntypedef int\t\tfunc_t;\ntypedef int\t\tstring_t;\n\ntypedef unsigned short\tword;\n\n#include \"xash3d_types.h\"\n\ntypedef struct\n{\n\tbyte\tr, g, b;\n} color24;\n\ntypedef struct\n{\n\tunsigned\tr, g, b, a;\n} colorVec;\n\ntypedef struct link_s\n{\n\tstruct link_s\t*prev, *next;\n} link_t;\n\ntypedef struct edict_s edict_t;\n\ntypedef struct\n{\n\tvec3_t\tnormal;\n\tfloat\tdist;\n} plane_t;\n\ntypedef struct\n{\n\tqboolean\tallsolid;\t\t// if true, plane is not valid\n\tqboolean\tstartsolid;\t// if true, the initial point was in a solid area\n\tqboolean\tinopen, inwater;\n\tfloat\tfraction;\t\t// time completed, 1.0 = didn't hit anything\n\tvec3_t\tendpos;\t\t// final position\n\tplane_t\tplane;\t\t// surface normal at impact\n\tedict_t\t*ent;\t\t// entity the surface is on\n\tint\thitgroup;\t\t// 0 == generic, non zero is specific body part\n} trace_t;\n\n#endif//CONST_H\n"
  },
  {
    "path": "common/cvardef.h",
    "content": "/*\ncvardef.h - quake cvar definition\nCopyright (C) 1997-2001 Id Software, Inc.\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n#ifndef CVAR\n#define CVAR\n\n#include STDINT_H\n#include \"xash3d_types.h\"\n\n/*\n\ncvar_t variables are used to hold scalar or string variables that can be changed\nor displayed at the console or prog code as well as accessed directly\nin C code.\n\nThe user can access cvars from the console in three ways:\nr_draworder             prints the current value\nr_draworder 0           sets the current value to 0\nset r_draworder 0       as above, but creates the cvar if not present\n\nCvars are restricted from having the same names as commands to keep this\ninterface from being ambiguous.\n\nThe are also occasionally used to communicated information between different\nmodules of the program.\n\n*/\n\nenum\n{\n\t// GoldSrc compatibility flags\n\tFCVAR_ARCHIVE           = 1 << 0,  // set to cause it to be saved to vars.rc\n\tFCVAR_USERINFO          = 1 << 1,  // added to userinfo when changed\n\tFCVAR_SERVER            = 1 << 2,  // added to serverinfo when changed, will notify clients by default\n\tFCVAR_EXTDLL            = 1 << 3,  // defined by server.dll\n\tFCVAR_CLIENTDLL         = 1 << 4,  // defined by client.dll\n\tFCVAR_PROTECTED         = 1 << 5,  // private server cvar\n\tFCVAR_SPONLY            = 1 << 6,  // can be set only in singleplayer\n\tFCVAR_PRINTABLEONLY     = 1 << 7,  // only allows printable characters\n\tFCVAR_UNLOGGED          = 1 << 8,  // disables notifying client about server cvar change\n\tFCVAR_NOEXTRAWHITESPACE = 1 << 9,  // removes space characters from the beginning and the end of the string\n\tFCVAR_PRIVILEGED        = 1 << 10, // only available in privileged mode\n\tFCVAR_FILTERABLE        = 1 << 11, // treated as privileged if cl_filterstuffcmd is 1, otherwise ignored\n\n\t// Xash3D public flags\n\t// FCVAR_LATCH         = 1 << 11, // deprecated Xash3D flag, conflicts with FCVAR_FILTERABLE. Use another FCVAR_LATCH!\n\tFCVAR_GLCONFIG         = 1 << 12, // set to cause it to be saved to <renderer>.cfg (see RefAPI)\n\tFCVAR_CHANGED          = 1 << 13, // set each time the cvar changed\n\tFCVAR_GAMEUIDLL        = 1 << 14, // defined by menu.dll\n\tFCVAR_CHEAT            = 1 << 15, // cannot be changed if sv_cheats is 0\n\n#if REF_DLL || ENGINE_DLL // Xash3D internal flags, MUST NOT be used outside of engine\n\tFCVAR_RENDERINFO   = 1 << 16, // set to cause it to be saved to video.cfg\n\tFCVAR_READ_ONLY    = 1 << 17, // display only, cannot be set by user at all\n\tFCVAR_EXTENDED     = 1 << 18, // extended cvar structure\n\tFCVAR_ALLOCATED    = 1 << 19, // allocated by the engine, must be freed with Mem_Free\n\tFCVAR_VIDRESTART   = 1 << 20, // triggers video subsystem to recreate/modify window parameters\n\tFCVAR_TEMPORARY    = 1 << 21, // only used to temporarly hold some value, can be unlinked\n\tFCVAR_MOVEVARS     = 1 << 22, // access to movevars_t structure, synchornized between client and server\n\tFCVAR_USER_CREATED = 1 << 23, // created by a set command\n\n\tFCVAR_REFDLL     = 1 << 29, // (Xash3D FWGS internal flag) defined by the renderer DLL\n#endif // REF_DLL || ENGINE_DLL\n\n\tFCVAR_LATCH      = 1 << 30, // (Xash3D FWGS public flag, was FCVAR_FILTERABLE in Xash3D) save changes until server restart\n};\n\nstruct cvar_s {\n\tchar     *name;\n\tchar     *string;\n\tuint32_t  flags;\n\tfloat     value;\n\tstruct cvar_s *next;\n};\ntypedef struct cvar_s cvar_t;\n\nSTATIC_CHECK_SIZEOF( struct cvar_s, 20, 32 );\n\n#if REF_DLL || ENGINE_DLL // Xash3D internal cvar format, MUST NOT be used outside of engine\nstruct convar_s {\n\tchar     *name;\n\tchar     *string;\n\tuint32_t  flags;\n\tfloat     value;\n\tstruct convar_s *next;\n\tchar     *desc;\n\tchar     *def_string;\n};\ntypedef struct convar_s convar_t;\n\n#if XASH_64BIT\n#define CVAR_SENTINEL (uintptr_t)0xDEADBEEFDEADBEEF\n#else\n#define CVAR_SENTINEL (uintptr_t)0xDEADBEEF\n#endif\n\n#define CVAR_CHECK_SENTINEL( cv )\t((uintptr_t)(cv)->next == CVAR_SENTINEL)\n\n#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \\\n\tconvar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, (char*)cvdesc, NULL }\n\n#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) CVAR_DEFINE( cv, #cv, cvstr, cvflags, cvdesc )\n#endif // REF_DLL || ENGINE_DLL\n\n#endif // CVAR\n"
  },
  {
    "path": "common/defaults.h",
    "content": "/*\ndefaults.h - set up default configuration\nCopyright (C) 2016 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef DEFAULTS_H\n#define DEFAULTS_H\n\n#include \"backends.h\"\n#include \"build.h\"\n\n/*\n===================================================================\n\nSETUP BACKENDS DEFINITIONS\n\n===================================================================\n*/\n#if !XASH_DEDICATED\n\t#if XASH_SDL\n\t\t// we are building using libSDL\n\t\t#ifndef XASH_VIDEO\n\t\t\t#define XASH_VIDEO VIDEO_SDL\n\t\t#endif // XASH_VIDEO\n\n\t\t#ifndef XASH_INPUT\n\t\t\t#define XASH_INPUT INPUT_SDL\n\t\t#endif // XASH_INPUT\n\n\t\t#ifndef XASH_SOUND\n\t\t\t#define XASH_SOUND SOUND_SDL\n\t\t#endif // XASH_SOUND\n\n\t\t#if XASH_SDL == 2\n\t\t\t#ifndef XASH_TIMER\n\t\t\t\t#define XASH_TIMER TIMER_SDL\n\t\t\t#endif // XASH_TIMER\n\n\t\t\t#ifndef XASH_MESSAGEBOX\n\t\t\t\t#if !XASH_NSWITCH // SDL2 messageboxes not available\n\t\t\t\t\t#define XASH_MESSAGEBOX MSGBOX_SDL\n\t\t\t\t#endif\n\t\t\t#endif // XASH_MESSAGEBOX\n\t\t#endif\n\t#elif XASH_LINUX\n\t\t// we are building for Linux without SDL2, can draw only to framebuffer yet\n\t\t#ifndef XASH_VIDEO\n\t\t\t#define XASH_VIDEO VIDEO_FBDEV\n\t\t#endif // XASH_VIDEO\n\n\t\t#ifndef XASH_INPUT\n\t\t\t#define XASH_INPUT INPUT_EVDEV\n\t\t#endif // XASH_INPUT\n\n\t\t#ifndef XASH_SOUND\n\t\t\t#define XASH_SOUND SOUND_ALSA\n\t\t#endif // XASH_SOUND\n\n\t\t#define XASH_USE_EVDEV 1\n\t#elif XASH_DOS4GW\n\t\t#ifndef XASH_VIDEO\n\t\t\t#define XASH_VIDEO VIDEO_DOS\n\t\t#endif\n\t\t#ifndef XASH_TIMER\n\t\t\t#define XASH_TIMER TIMER_DOS\n\t\t#endif\n\n\t\t// usually only 10-20 fds availiable\n\t\t#define XASH_REDUCE_FD\n\t#endif\n#endif // XASH_DEDICATED\n\n//\n// select messagebox implementation\n//\n#ifndef XASH_MESSAGEBOX\n\t#if XASH_WIN32\n\t\t#define XASH_MESSAGEBOX MSGBOX_WIN32\n\t#elif XASH_NSWITCH\n\t\t#define XASH_MESSAGEBOX MSGBOX_NSWITCH\n\t#else // !XASH_WIN32\n\t\t#define XASH_MESSAGEBOX MSGBOX_STDERR\n\t#endif // !XASH_WIN32\n#endif // XASH_MESSAGEBOX\n\n//\n// no timer - no xash\n//\n#ifndef XASH_TIMER\n\t#if XASH_WIN32\n\t\t#define XASH_TIMER TIMER_WIN32\n\t#else // !XASH_WIN32\n\t\t#define XASH_TIMER TIMER_POSIX\n\t#endif // !XASH_WIN32\n#endif\n\n//\n// determine movie playback backend\n//\n#ifndef XASH_AVI\n\t#if HAVE_FFMPEG\n\t\t#define XASH_AVI AVI_FFMPEG\n\t#else\n\t\t#define XASH_AVI AVI_NULL\n\t#endif\n#endif\n\n#ifdef XASH_STATIC_LIBS\n#define XASH_LIB LIB_STATIC\n#define XASH_INTERNAL_GAMELIBS\n#define XASH_ALLOW_SAVERESTORE_OFFSETS\n#elif XASH_WIN32\n#define XASH_LIB LIB_WIN32\n#elif XASH_POSIX\n#define XASH_LIB LIB_POSIX\n#endif\n\n//\n// fallback to NULL\n//\n#ifndef XASH_VIDEO\n\t#define XASH_VIDEO VIDEO_NULL\n#endif // XASH_VIDEO\n\n#ifndef XASH_SOUND\n\t#define XASH_SOUND SOUND_NULL\n#endif // XASH_SOUND\n\n#ifndef XASH_INPUT\n\t#define XASH_INPUT INPUT_NULL\n#endif // XASH_INPUT\n\n/*\n=========================================================================\n\nDefault build-depended cvar and constant values\n\n=========================================================================\n*/\n\n// Platform overrides\n#if XASH_NSWITCH\n\t#define DEFAULT_TOUCH_ENABLE \"1\"\n\t#define DEFAULT_M_IGNORE     \"1\"\n\t#define DEFAULT_MODE_WIDTH   1280\n\t#define DEFAULT_MODE_HEIGHT  720\n\t#define DEFAULT_ALLOWCONSOLE 1\n#elif XASH_PSVITA\n\t#define DEFAULT_TOUCH_ENABLE \"1\"\n\t#define DEFAULT_M_IGNORE     \"1\"\n\t#define DEFAULT_MODE_WIDTH   960\n\t#define DEFAULT_MODE_HEIGHT  544\n\t#define DEFAULT_ALLOWCONSOLE 1\n#elif XASH_ANDROID\n\t#define DEFAULT_TOUCH_ENABLE \"1\"\n#elif XASH_MOBILE_PLATFORM\n\t#define DEFAULT_TOUCH_ENABLE \"1\"\n\t#define DEFAULT_M_IGNORE     \"1\"\n#endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH\n\n#if XASH_IOS\n\t// this means that libraries are provided with engine, but not in game data\n\t// You need add library loading code to library.c when adding new platform\n\t#define XASH_INTERNAL_GAMELIBS\n#endif // XASH_IOS\n\n// Defaults\n#ifndef DEFAULT_TOUCH_ENABLE\n\t#define DEFAULT_TOUCH_ENABLE \"0\"\n#endif // DEFAULT_TOUCH_ENABLE\n\n#ifndef DEFAULT_M_IGNORE\n\t#define DEFAULT_M_IGNORE \"0\"\n#endif // DEFAULT_M_IGNORE\n\n#ifndef DEFAULT_JOY_DEADZONE\n\t#define DEFAULT_JOY_DEADZONE \"4096\"\n#endif // DEFAULT_JOY_DEADZONE\n\n#ifndef DEFAULT_DEV\n\t#define DEFAULT_DEV 0\n#endif // DEFAULT_DEV\n\n#ifndef DEFAULT_ALLOWCONSOLE\n\t#define DEFAULT_ALLOWCONSOLE 0\n#endif // DEFAULT_ALLOWCONSOLE\n\n#ifndef DEFAULT_FULLSCREEN\n\t#define DEFAULT_FULLSCREEN \"1\" // must be a string\n#endif // DEFAULT_FULLSCREEN\n\n#ifndef DEFAULT_MAX_EDICTS\n\t#define DEFAULT_MAX_EDICTS 1200 // was 900 before HL25\n#endif // DEFAULT_MAX_EDICTS\n\n#endif // DEFAULTS_H\n"
  },
  {
    "path": "common/demo_api.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef DEMO_API_H\n#define DEMO_API_H\n\ntypedef struct demo_api_s\n{\n\tint\t(*IsRecording)( void );\n\tint\t(*IsPlayingback)( void );\n\tint\t(*IsTimeDemo)( void );\n\tvoid\t(*WriteBuffer)( int size, unsigned char *buffer );\n} demo_api_t;\n\n#endif//DEMO_API_H\n"
  },
  {
    "path": "common/dlight.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef DLIGHT_H\n#define DLIGHT_H\n\ntypedef struct dlight_s\n{\n\tvec3_t\t\torigin;\n\tfloat\t\tradius;\n\tcolor24\t\tcolor;\n\tfloat\t\tdie;\t// stop lighting after this time\n\tfloat\t\tdecay;\t// drop this each second\n\tfloat\t\tminlight;\t// don't add when contributing less\n\tint\t\tkey;\n\tqboolean\t\tdark;\t// subtracts light instead of adding\n} dlight_t;\n\n#endif//DLIGHT_H\n"
  },
  {
    "path": "common/enginefeatures.h",
    "content": "/*\nenginefeatures.h - engine features that can be enabled by mod-maker request\nCopyright (C) 2012 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef FEATURES_H\n#define FEATURES_H\n\n// list of engine features that can be enabled through callback SV_CheckFeatures\n#define ENGINE_WRITE_LARGE_COORD\t(1<<0)\t// replace standard message WRITE_COORD with big message for support more than 8192 units in world\n#define ENGINE_QUAKE_COMPATIBLE\t(1<<1)\t// make engine compatible with quake (flags and effects)\n#define ENGINE_LOAD_DELUXEDATA\t(1<<2)\t// loading deluxemap for map (if present)\n#define ENGINE_PHYSICS_PUSHER_EXT\t(1<<3)\t// enable sets of improvements for MOVETYPE_PUSH physics\n#define ENGINE_LARGE_LIGHTMAPS\t(1<<4)\t// change lightmap sizes from 128x128 to 1024x1024\n#define ENGINE_COMPENSATE_QUAKE_BUG\t(1<<5)\t// compensate stupid quake bug (inverse pitch) for mods where this bug is fixed\n#define ENGINE_IMPROVED_LINETRACE\t(1<<6)\t// new traceline that tracing through alphatextures\n#define ENGINE_COMPUTE_STUDIO_LERP\t(1<<7)\t// enable MOVETYPE_STEP lerping back in engine\n#define ENGINE_LINEAR_GAMMA_SPACE\t(1<<8)\t// disable influence of gamma/brightness cvars to textures/lightmaps, for mods with custom renderer\n\n#define ENGINE_DISABLE_HDTEXTURES   (1U<<30) // disable support of HD-textures in case custom renderer have separate way to load them\n#define ENGINE_STEP_POSHISTORY_LERP (1U<<31) // enable MOVETYPE_STEP interpolation based on position history. Incompatible with ENGINE_COMPUTE_STUDIO_LERP!\n\n// adjust the mask when features will be added or removed\n#define ENGINE_FEATURES_MASK      \\\n\t( ENGINE_WRITE_LARGE_COORD    \\\n\t| ENGINE_QUAKE_COMPATIBLE     \\\n\t| ENGINE_LOAD_DELUXEDATA      \\\n\t| ENGINE_PHYSICS_PUSHER_EXT   \\\n\t| ENGINE_LARGE_LIGHTMAPS      \\\n\t| ENGINE_COMPENSATE_QUAKE_BUG \\\n\t| ENGINE_IMPROVED_LINETRACE   \\\n\t| ENGINE_COMPUTE_STUDIO_LERP  \\\n\t| ENGINE_LINEAR_GAMMA_SPACE   \\\n\t| ENGINE_STEP_POSHISTORY_LERP )\n\n#endif//FEATURES_H\n"
  },
  {
    "path": "common/entity_state.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#ifndef ENTITY_STATE_H\n#define ENTITY_STATE_H\n\n// For entityType below\n#define ENTITY_NORMAL\t\t(1<<0)\n#define ENTITY_BEAM\t\t\t(1<<1)\n\n// Entity state is used for the baseline and for delta compression of a packet of\n//  entities that is sent to a client.\ntypedef struct entity_state_s entity_state_t;\n\nstruct entity_state_s\n{\n// Fields which are filled in by routines outside of delta compression\n\tint\t\tentityType;\n\t// Index into cl_entities array for this entity.\n\tint\t\tnumber;\n\tfloat\t\tmsg_time;\n\n\t// Message number last time the player/entity state was updated.\n\tint\t\tmessagenum;\n\n// Fields which can be transitted and reconstructed over the network stream\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n\n\tint\t\tmodelindex;\n\tint\t\tsequence;\n\tfloat\t\tframe;\n\tint\t\tcolormap;\n\tshort\t\tskin;\n\tshort\t\tsolid;\n\tint\t\teffects;\n\tfloat\t\tscale;\n\tbyte\t\teflags;\n\n\t// Render information\n\tint\t\trendermode;\n\tint\t\trenderamt;\n\tcolor24\t\trendercolor;\n\tint\t\trenderfx;\n\n\tint\t\tmovetype;\n\tfloat\t\tanimtime;\n\tfloat\t\tframerate;\n\tint\t\tbody;\n\tbyte\t\tcontroller[4];\n\tbyte\t\tblending[4];\n\tvec3_t\t\tvelocity;\n\n\t// Send bbox down to client for use during prediction.\n\tvec3_t\t\tmins;\n\tvec3_t\t\tmaxs;\n\n\tint\t\taiment;\n\t// If owned by a player, the index of that player ( for projectiles ).\n\tint\t\towner;\n\n\t// Friction, for prediction.\n\tfloat\t\tfriction;\n\t// Gravity multiplier\n\tfloat\t\tgravity;\n\n// PLAYER SPECIFIC\n\tint\t\tteam;\n\tint\t\tplayerclass;\n\tint\t\thealth;\n\tqboolean\t\tspectator;\n\tint\t\tweaponmodel;\n\tint\t\tgaitsequence;\n\t// If standing on conveyor, e.g.\n\tvec3_t\t\tbasevelocity;\n\t// Use the crouched hull, or the regular player hull.\n\tint\t\tusehull;\n\t// Latched buttons last time state updated.\n\tint\t\toldbuttons;\n\t// -1 = in air, else pmove entity number\n\tint\t\tonground;\n\tint\t\tiStepLeft;\n\t// How fast we are falling\n\tfloat\t\tflFallVelocity;\n\n\tfloat\t\tfov;\n\tint\t\tweaponanim;\n\n\t// Parametric movement overrides\n\tvec3_t\t\tstartpos;\n\tvec3_t\t\tendpos;\n\tfloat\t\timpacttime;\n\tfloat\t\tstarttime;\n\n\t// For mods\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n\tvec3_t\t\tvuser1;\n\tvec3_t\t\tvuser2;\n\tvec3_t\t\tvuser3;\n\tvec3_t\t\tvuser4;\n};\n\n#include \"pm_info.h\"\n\ntypedef struct clientdata_s\n{\n\tvec3_t\t\torigin;\n\tvec3_t\t\tvelocity;\n\n\tint\t\tviewmodel;\n\tvec3_t\t\tpunchangle;\n\tint\t\tflags;\n\tint\t\twaterlevel;\n\tint\t\twatertype;\n\tvec3_t\t\tview_ofs;\n\tfloat\t\thealth;\n\n\tint\t\tbInDuck;\n\tint\t\tweapons; // remove?\n\n\tint\t\tflTimeStepSound;\n\tint\t\tflDuckTime;\n\tint\t\tflSwimTime;\n\tint\t\twaterjumptime;\n\n\tfloat\t\tmaxspeed;\n\n\tfloat\t\tfov;\n\tint\t\tweaponanim;\n\n\tint\t\tm_iId;\n\tint\t\tammo_shells;\n\tint\t\tammo_nails;\n\tint\t\tammo_cells;\n\tint\t\tammo_rockets;\n\tfloat\t\tm_flNextAttack;\n\n\tint\t\ttfstate;\n\tint\t\tpushmsec;\n\tint\t\tdeadflag;\n\tchar\t\tphysinfo[MAX_PHYSINFO_STRING];\n\n\t// For mods\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n\tvec3_t\t\tvuser1;\n\tvec3_t\t\tvuser2;\n\tvec3_t\t\tvuser3;\n\tvec3_t\t\tvuser4;\n\n} clientdata_t;\n\n#include \"weaponinfo.h\"\n\n#define MAX_LOCAL_WEAPONS\t64\t// max weapons that can be predicted on the client\n\ntypedef struct local_state_s\n{\n\tentity_state_t\tplayerstate;\n\tclientdata_t\tclient;\n\tweapon_data_t\tweapondata[MAX_LOCAL_WEAPONS];\n} local_state_t;\n\n#endif//ENTITY_STATE_H\n"
  },
  {
    "path": "common/entity_types.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef ENTITY_TYPES_H\n#define ENTITY_TYPES_H\n\n#define ET_NORMAL\t\t0\n#define ET_PLAYER\t\t1\n#define ET_TEMPENTITY\t2\n#define ET_BEAM\t\t3\n#define ET_FRAGMENTED\t4\t// BMODEL or SPRITE that was split across BSP nodes\n\n#endif//ENTITY_TYPES_H\n"
  },
  {
    "path": "common/event_api.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef EVENT_API_H\n#define EVENT_API_H\n\n#define EVENT_API_VERSION\t1\n\ntypedef struct event_api_s\n{\n\tint\tversion;\n\tvoid\t( *EV_PlaySound )( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch );\n\tvoid\t( *EV_StopSound )( int ent, int channel, const char *sample );\n\tint\t( *EV_FindModelIndex )( const char *pmodel );\n\tint\t( *EV_IsLocal )( int playernum );\n\tint\t( *EV_LocalPlayerDucking )( void );\n\tvoid\t( *EV_LocalPlayerViewheight )( float * );\n\tvoid\t( *EV_LocalPlayerBounds )( int hull, float *mins, float *maxs );\n\tint\t( *EV_IndexFromTrace)( struct pmtrace_s *pTrace );\n\tstruct physent_s *( *EV_GetPhysent )( int idx );\n\tvoid\t( *EV_SetUpPlayerPrediction )( int dopred, int bIncludeLocalClient );\n\tvoid\t( *EV_PushPMStates )( void );\n\tvoid\t( *EV_PopPMStates )( void );\n\tvoid\t( *EV_SetSolidPlayers )( int playernum );\n\tvoid\t( *EV_SetTraceHull )( int hull );\n\tvoid\t( *EV_PlayerTrace )( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr );\n\tvoid\t( *EV_WeaponAnimation )( int sequence, int body );\n\tunsigned short ( *EV_PrecacheEvent )( int type, const char* psz );\n\tvoid\t( *EV_PlaybackEvent )( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\n\tconst char *( *EV_TraceTexture )( int ground, float *vstart, float *vend );\n\tvoid\t( *EV_StopAllSounds )( int entnum, int entchannel );\n\tvoid\t( *EV_KillEvents )( int entnum, const char *eventname );\n\n\t// Xash3D extension\n\tvoid\t( *EV_PlayerTraceExt )( float *start, float *end, int traceFlags, int (*pfnIgnore)( struct physent_s *pe ), struct pmtrace_s *tr );\n\tconst char *(*EV_SoundForIndex)( int index );\n\tstruct msurface_s *( *EV_TraceSurface )( int ground, float *vstart, float *vend );\n\tstruct movevars_s *( *EV_GetMovevars )( void );\n\tstruct pmtrace_s *( *EV_VisTraceLine )( float *start, float *end, int flags );\n\tstruct physent_s *( *EV_GetVisent )( int idx );\n\tint\t( *EV_TestLine)( const vec3_t start, const vec3_t end, int flags );\n\tvoid\t( *EV_PushTraceBounds)( int hullnum, const float *mins, const float *maxs );\n\tvoid\t( *EV_PopTraceBounds)( void );\n} event_api_t;\n\n#endif//EVENT_API_H\n"
  },
  {
    "path": "common/event_args.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#ifndef EVENT_ARGS_H\n#define EVENT_ARGS_H\n\n// Event was invoked with stated origin\n#define FEVENT_ORIGIN\t( 1<<0 )\n\n// Event was invoked with stated angles\n#define FEVENT_ANGLES\t( 1<<1 )\n\ntypedef struct event_args_s\n{\n\tint\t\tflags;\n\n\t// Transmitted\n\tint\t\tentindex;\n\n\tfloat\t\torigin[3];\n\tfloat\t\tangles[3];\n\tfloat\t\tvelocity[3];\n\n\tint\t\tducking;\n\n\tfloat\t\tfparam1;\n\tfloat\t\tfparam2;\n\n\tint\t\tiparam1;\n\tint\t\tiparam2;\n\n\tint\t\tbparam1;\n\tint\t\tbparam2;\n} event_args_t;\n\n#endif//EVENT_ARGS_H\n"
  },
  {
    "path": "common/event_flags.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef EVENT_FLAGS_H\n#define EVENT_FLAGS_H\n\n// Skip local host for event send.\n#define FEV_NOTHOST\t\t(1<<0)\n\n// Send the event reliably.  You must specify the origin and angles and use\n// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything\n// that depends on the event origin/angles.  I.e., the origin/angles are not\n// taken from the invoking edict for reliable events.\n#define FEV_RELIABLE\t(1<<1)\n\n// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC\n//  sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ).\n#define FEV_GLOBAL\t\t(1<<2)\n\n// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate\n//\n#define FEV_UPDATE\t\t(1<<3)\n\n// Only send to entity specified as the invoker\n#define FEV_HOSTONLY\t(1<<4)\n\n// Only send if the event was created on the server.\n#define FEV_SERVER\t\t(1<<5)\n\n// Only issue event client side ( from shared code )\n#define FEV_CLIENT\t\t(1<<6)\n\n#endif//EVENT_FLAGS_H\n"
  },
  {
    "path": "common/gameinfo.h",
    "content": "/*\ngameinfo.h - current game info\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GAMEINFO_H\n#define GAMEINFO_H\n\n#define GFL_NOMODELS\t(1<<0)\n#define GFL_NOSKILLS\t(1<<1)\n#define GFL_RENDER_PICBUTTON_TEXT   (1<<2)\n#define GFL_HD_BACKGROUND (1<<3)\n#define GFL_ANIMATED_TITLE (1<<4)\n\n/*\n========================================================================\n\nGAMEINFO stuff\n\ninternal shared gameinfo structure (readonly for engine parts)\n========================================================================\n*/\ntypedef struct GAMEINFO_s\n{\n\t// filesystem info\n\tchar\t\tgamefolder[64];\t// used for change game '-game x'\n\tchar\t\tstartmap[64];\t// map to start singleplayer game\n\tchar\t\ttrainmap[64];\t// map to start hazard course (if specified)\n\tchar\t\ttitle[64];\t// Game Main Title\n\tchar\t\tversion[14];\t// game version (optional)\n\tshort\t\tflags;\t\t// game flags\n\n\t// about mod info\n\tchar\t\tgame_url[256];\t// link to a developer's site\n\tchar\t\tupdate_url[256];\t// link to updates page\n\tchar\t\ttype[64];\t\t// single, toolkit, multiplayer etc\n\tchar\t\tdate[64];\n\tchar\t\tsize[64];\t\t// displayed mod size\n\n\tint\t\tgamemode;\n} GAMEINFO;\n\n/*\n========================================================================\n\nExtended GameInfo struct introduced in Xash3D FWGS\n\nGAMEINFO can't be reliably extended, as nor engine, nor menu can't be\nsure about struct size. By adding struct versioning, we can check the\npresense for extra fields.\n========================================================================\n*/\n\n#define GAMEINFO_VERSION 2\n\ntypedef enum gametype_e\n{\n\tGAME_NORMAL,\n\tGAME_SINGLEPLAYER_ONLY,\n\tGAME_MULTIPLAYER_ONLY,\n} gametype_t;\n\ntypedef struct gameinfo2_s\n{\n\tint gi_version; // should be set to desired struct version, e.g. GAMEINFO_VERSION\n\n\t// filesystem info\n\tchar     gamefolder[64]; // used for change game\n\tchar     startmap[64];   // map to start singleplayer game from\n\tchar     trainmap[64];   // map to start hazardous course from (if specified)\n\tchar     demomap[64];    // map to start demo chapter from (if specified)\n\tchar     title[64];      // game title\n\tchar     iconpath[64];   // path to game icon\n\tchar     version[16];    // game version (optional)\n\tuint32_t flags;          // gameinfo flags, extended to fit more flags\n\n\t// mod info\n\tchar     game_url[256];   // link to a developer's site\n\tchar     update_url[256]; // link to updates page\n\tchar     type[64];        // single, toolkit, multiplayer, etc\n\tchar     date[64];        // release date\n\tuint64_t size;            // size in bytes\n\n\tgametype_t gamemode;\n} gameinfo2_t;\n\n#endif//GAMEINFO_H\n"
  },
  {
    "path": "common/hltv.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef HLTV_H\n#define HLTV_H\n\n#define TYPE_CLIENT\t\t\t0\t// client is a normal HL client (default)\n#define TYPE_PROXY\t\t\t1\t// client is another proxy\n#define TYPE_COMMENTATOR\t\t3\t// client is a commentator\n#define TYPE_DEMO\t\t\t4\t// client is a demo file\n\n// sub commands of svc_hltv:\n#define HLTV_ACTIVE\t\t\t0\t// tells client that he's an spectator and will get director commands\n#define HLTV_STATUS\t\t\t1\t// send status infos about proxy\n#define HLTV_LISTEN\t\t\t2\t// tell client to listen to a multicast stream\n\n// sub commands of svc_director:\n#define DRC_CMD_NONE\t\t0\t// NULL director command\n#define DRC_CMD_START\t\t1\t// start director mode\n#define DRC_CMD_EVENT\t\t2\t// informs about director command\n#define DRC_CMD_MODE\t\t3\t// switches camera modes\n#define DRC_CMD_CAMERA\t\t4\t// sets camera registers\n#define DRC_CMD_TIMESCALE\t\t5\t// sets time scale\n#define DRC_CMD_MESSAGE\t\t6\t// send HUD centerprint\n#define DRC_CMD_SOUND\t\t7\t// plays a particular sound\n#define DRC_CMD_STATUS\t\t8\t// status info about broadcast\n#define DRC_CMD_BANNER\t\t9\t// banner file name for HLTV gui\n#define DRC_CMD_FADE\t\t10\t// send screen fade command\n#define DRC_CMD_SHAKE\t\t11\t// send screen shake command\n#define DRC_CMD_STUFFTEXT\t\t12\t// like the normal svc_stufftext but as director command\n\n#define DRC_CMD_LAST\t\t12\n\n// HLTV_EVENT event flags\n#define DRC_FLAG_PRIO_MASK\t\t0x0F\t// priorities between 0 and 15 (15 most important)\n#define DRC_FLAG_SIDE\t\t(1<<4)\t//\n#define DRC_FLAG_DRAMATIC\t\t(1<<5)\t// is a dramatic scene\n#define DRC_FLAG_SLOWMOTION\t\t(1<<6)\t// would look good in SloMo\n#define DRC_FLAG_FACEPLAYER\t\t(1<<7)\t// player is doning something (reload/defuse bomb etc)\n#define DRC_FLAG_INTRO\t\t(1<<8)\t// is a introduction scene\n#define DRC_FLAG_FINAL\t\t(1<<9)\t// is a final scene\n#define DRC_FLAG_NO_RANDOM\t\t(1<<10)\t// don't randomize event data\n\n#define MAX_DIRECTOR_CMD_PARAMETERS\t4\n#define MAX_DIRECTOR_CMD_STRING\t128\n\n#endif//HLTV_H\n"
  },
  {
    "path": "common/ivoicetweak.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef IVOICETWEAK_H\n#define IVOICETWEAK_H\n\n// These provide access to the voice controls.\ntypedef enum\n{\n\tMicrophoneVolume = 0,\t\t// values 0-1.\n\tOtherSpeakerScale\t\t\t// values 0-1. Scales how loud other players are.\n} VoiceTweakControl;\n\ntypedef struct IVoiceTweak_s\n{\n\t// These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back\n\t// without sending to the server.\n\tint\t(*StartVoiceTweakMode)( void );\t// Returns 0 on error.\n\tvoid\t(*EndVoiceTweakMode)( void );\n\n\t// Get/set control values.\n\tvoid\t(*SetControlFloat)( VoiceTweakControl iControl, float value );\n\tfloat\t(*GetControlFloat)( VoiceTweakControl iControl );\n} IVoiceTweak;\n\n#endif//IVOICETWEAK_H\n"
  },
  {
    "path": "common/kbutton.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n#ifndef KBUTTON_H\n#define KBUTTON_H\n\n// a1ba: copied from WinQuake/client.h\n//\n// cl_input\n//\ntypedef struct\n{\n\tint\t\tdown[2];\t\t// key nums holding it down\n\tint\t\tstate;\t\t\t// low bit is down state\n} kbutton_t;\n\nSTATIC_CHECK_SIZEOF( kbutton_t, 12, 12 );\n\n#endif // KBUTTON_H\n"
  },
  {
    "path": "common/lightstyle.h",
    "content": "/*\nlightstyle.h - lighstyle description\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef LIGHTSTYLE_H\n#define LIGHTSTYLE_H\n\ntypedef struct\n{\n\tchar\t\tpattern[256];\n\tfloat\t\tmap[256];\n\tint\t\tlength;\n\tfloat\t\tvalue;\n\tqboolean\t\tinterp;\t\t// allow to interpolate this lightstyle\n\tfloat\t\ttime;\t\t// local time is gurantee what new style begins from the start, not mid or end of the sequence\n} lightstyle_t;\n\n#endif//LIGHTSTYLE_H\n"
  },
  {
    "path": "common/mathlib.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n// mathlib.h\n\n#include <math.h>\n\ntypedef float vec_t;\ntypedef vec_t vec2_t[2];\ntypedef vec_t vec3_t[3];\ntypedef vec_t vec4_t[4];\t// x,y,z,w\n\n#ifndef M_PI\n#define M_PI\t\t3.14159265358979323846\t// matches value in gcc v2 math.h\n#endif\n\nstruct mplane_s;\n\nextern vec3_t vec3_origin;\nextern\tint nanmask;\n\n#define\tIS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)\n\n#ifndef VECTOR_H\n\t#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])\n#endif\n\n#define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];}\n#define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];}\n#define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];}\n#define VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;}\n\nvoid VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc);\n\nvec_t _DotProduct (vec3_t v1, vec3_t v2);\nvoid _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out);\nvoid _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out);\nvoid _VectorCopy (vec3_t in, vec3_t out);\n\nint VectorCompare (const vec3_t v1, const vec3_t v2);\nfloat Length (const vec3_t v);\nvoid CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross);\nfloat VectorNormalize (vec3_t v);\t\t// returns vector length\nvoid VectorInverse (vec3_t v);\nvoid VectorScale (const vec3_t in, vec_t scale, vec3_t out);\n\nvoid R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]);\nvoid R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]);\n\nvoid AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);\nvoid AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);\n#define AngleIVectors\tAngleVectorsTranspose\n\nvoid AngleMatrix (const vec3_t angles, float (*matrix)[4] );\nvoid AngleIMatrix (const vec3_t angles, float (*matrix)[4] );\nvoid VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out);\n\nvoid NormalizeAngles( vec3_t angles );\nvoid InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac );\nfloat AngleBetweenVectors( const vec3_t v1, const vec3_t v2 );\n\nvoid VectorMatrix( vec3_t forward, vec3_t right, vec3_t up);\nvoid VectorAngles( const vec3_t forward, vec3_t angles );\n\nint InvertMatrix( const float * m, float *out );\n\nint BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane);\nfloat\tanglemod(float a);\n\n#define BOX_ON_PLANE_SIDE(emins, emaxs, p)\t\\\n\t(((p)->type < 3)?\t\t\t\t\t\t\\\n\t(\t\t\t\t\t\t\t\t\t\t\\\n\t\t((p)->dist <= (emins)[(p)->type])?\t\\\n\t\t\t1\t\t\t\t\t\t\t\t\\\n\t\t:\t\t\t\t\t\t\t\t\t\\\n\t\t(\t\t\t\t\t\t\t\t\t\\\n\t\t\t((p)->dist >= (emaxs)[(p)->type])?\\\n\t\t\t\t2\t\t\t\t\t\t\t\\\n\t\t\t:\t\t\t\t\t\t\t\t\\\n\t\t\t\t3\t\t\t\t\t\t\t\\\n\t\t)\t\t\t\t\t\t\t\t\t\\\n\t)\t\t\t\t\t\t\t\t\t\t\\\n\t:\t\t\t\t\t\t\t\t\t\t\\\n\t\tBoxOnPlaneSide( (emins), (emaxs), (p)))\n"
  },
  {
    "path": "common/net_api.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef NET_API_H\n#define NET_API_H\n\n#include \"netadr.h\"\n\n#define NETAPI_REQUEST_SERVERLIST\t\t( 0 )  // Doesn't need a remote address\n#define NETAPI_REQUEST_PING\t\t\t( 1 )\n#define NETAPI_REQUEST_RULES\t\t\t( 2 )\n#define NETAPI_REQUEST_PLAYERS\t\t( 3 )\n#define NETAPI_REQUEST_DETAILS\t\t( 4 )\n\n// Set this flag for things like broadcast requests, etc. where the engine should not\n// kill the request hook after receiving the first response\n#define FNETAPI_MULTIPLE_RESPONSE\t\t( 1<<0 )\n#define FNETAPI_LEGACY_PROTOCOL\t\t\t( 1<<1 ) // xash3d-fwgs extension\n\nstruct net_response_s;\ntypedef void (*net_api_response_func_t) ( struct net_response_s *response );\n\n#define NET_SUCCESS\t\t\t\t( 0 )\n#define NET_ERROR_TIMEOUT\t\t\t( 1<<0 )\n#define NET_ERROR_PROTO_UNSUPPORTED\t\t( 1<<1 )\n#define NET_ERROR_UNDEFINED\t\t\t( 1<<2 )\n#define NET_ERROR_FORBIDDEN\t\t\t( 1<<3 ) // xash3d-fwgs extension\n\ntypedef struct net_adrlist_s\n{\n\tstruct net_adrlist_s\t*next;\n\tnetadr_t\t\t\tremote_address;\n} net_adrlist_t;\n\ntypedef struct net_response_s\n{\n\t// NET_SUCCESS or an error code\n\tint\t\terror;\n\t// Context ID\n\tint\t\tcontext;\n\t// Type\n\tint\t\ttype;\n\t// Server that is responding to the request\n\tnetadr_t\t\tremote_address;\n\t// Response RTT ping time\n\tdouble\t\tping;\n\t// Key/Value pair string ( separated by backlash \\ characters )\n\t// WARNING:  You must copy this buffer in the callback function, because it is freed\n\t// by the engine right after the call!!!!\n\t// ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's\n\tvoid\t\t*response;\n} net_response_t;\n\ntypedef struct net_status_s\n{\n\t// Connected to remote server?  1 == yes, 0 otherwise\n\tint\t\tconnected;\n\t// Client's IP address\n\tnetadr_t\t\tlocal_address;\n\t// Address of remote server\n\tnetadr_t\t\tremote_address;\n\t// Packet Loss ( as a percentage )\n\tint\t\tpacket_loss;\n\t// Latency, in seconds ( multiply by 1000.0 to get milliseconds )\n\tdouble\t\tlatency;\n\t// Connection time, in seconds\n\tdouble\t\tconnection_time;\n\t// Rate setting ( for incoming data )\n\tdouble\t\trate;\n} net_status_t;\n\ntypedef struct net_api_s\n{\n\t// APIs\n\tvoid\t\t(*InitNetworking)( void );\n\tvoid\t\t(*Status )( struct net_status_s *status );\n\tvoid\t\t(*SendRequest)( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response );\n\tvoid\t\t(*CancelRequest)( int context );\n\tvoid\t\t(*CancelAllRequests)( void );\n\tconst char\t*(*AdrToString)( struct netadr_s *a );\n\tint\t\t( *CompareAdr)( struct netadr_s *a, struct netadr_s *b );\n\tint\t\t( *StringToAdr)( char *s, struct netadr_s *a );\n\tconst char\t*(*ValueForKey)( const char *s, const char *key );\n\tvoid\t\t(*RemoveKey)( char *s, const char *key );\n\tvoid\t\t(*SetValueForKey)( char *s, const char *key, const char *value, int maxsize );\n} net_api_t;\n\n#endif//NET_APIH\n"
  },
  {
    "path": "common/netadr.h",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n#ifndef NET_ADR_H\n#define NET_ADR_H\n\n#include STDINT_H\n\n// net.h -- quake's interface to the networking layer\n// a1ba: copied from Quake-2/qcommon/qcommon.h and modified to support IPv6\n\n#define\tPORT_ANY\t-1\n\ntypedef enum netadrtype_e\n{\n\tNA_UNDEFINED = 0,\n\tNA_LOOPBACK,\n\tNA_BROADCAST,\n\tNA_IP,\n\tNA_IPX,\n\tNA_BROADCAST_IPX,\n\tNA_IP6,\n\tNA_MULTICAST_IP6\n} netadrtype_t;\n\n/*\nOriginal Quake-2 structure:\ntypedef struct\n{\n\tnetadrtype_t\ttype;\n\n\tbyte\tip[4];\n\tbyte\tipx[10];\n\n\tunsigned short\tport;\n} netadr_t;\n*/\n\n#pragma pack( push, 1 )\ntypedef struct netadr_s\n{\n// the reason we do this evil thing, is that when this struct contains IPv6\n// address the `type` is 2-byte wide, but when it doesn't `type` must 4-byte\n// wide _and_ ip6_0 must be zeroed, to keep it binary compatible.\n#if XASH_LITTLE_ENDIAN\n\tuint16_t type;\n\tuint8_t  ip6_0[2];\n#elif XASH_BIG_ENDIAN\n\tuint8_t  ip6_0[2];\n\tuint16_t type;\n#else\n#error\n#endif\n\n\tunion\n\t{\n\t\t// IPv6 struct\n\t\tuint8_t\tip6_1[14];\n\t\tstruct\n\t\t{\n\t\t\tunion\n\t\t\t{\n\t\t\t\tuint8_t  ip[4];\n\t\t\t\tuint32_t ip4; // for easier conversions\n\t\t\t};\n\t\t\tuint8_t\tipx[10];\n\t\t};\n\t};\n\tuint16_t port;\n} netadr_t;\n#pragma pack( pop )\n\nstatic inline netadrtype_t NET_NetadrType( const netadr_t *a )\n{\n\tif( a->type == NA_IP6 || a->type == NA_MULTICAST_IP6 )\n\t\treturn (netadrtype_t)a->type;\n\n\tif( a->ip6_0[0] || a->ip6_0[1] )\n\t\treturn NA_UNDEFINED;\n\n\treturn (netadrtype_t)a->type;\n}\n\nstatic inline void NET_NetadrSetType( netadr_t *a, netadrtype_t type )\n{\n\tif( type == NA_IP6 || type == NA_MULTICAST_IP6 )\n\t{\n\t\ta->type = type;\n\t\treturn;\n\t}\n\n\ta->ip6_0[0] = a->ip6_0[1] = 0;\n\ta->type = type;\n}\n\nSTATIC_CHECK_SIZEOF( netadr_t, 20, 20 );\n\n#endif // NET_ADR_H\n"
  },
  {
    "path": "common/particledef.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef PARTICLEDEF_H\n#define PARTICLEDEF_H\n\ntypedef enum\n{\n\tpt_static,\n\tpt_grav,\n\tpt_slowgrav,\n\tpt_fire,\n\tpt_explode,\n\tpt_explode2,\n\tpt_blob,\n\tpt_blob2,\n\tpt_vox_slowgrav,\n\tpt_vox_grav,\n\tpt_clientcustom\t// Must have callback function specified\n} ptype_t;\n\ntypedef struct particle_s\n{\n\tvec3_t\t\torg;\n\tshort\t\tcolor;\n\tshort\t\tpackedColor;\n\tstruct particle_s\t*next;\n\tvec3_t\t\tvel;\n\tfloat\t\tramp;\n\tfloat\t\tdie;\n\tptype_t\t\ttype;\n\tvoid\t\t(*deathfunc)( struct particle_s *particle );\n\n\t// for pt_clientcusttom, we'll call this function each frame\n\tvoid\t\t(*callback)( struct particle_s *particle, float frametime );\n\n\t// For deathfunc, etc.\n\tunsigned char\tcontext;\n} particle_t;\n\n#endif//PARTICLEDEF_H\n"
  },
  {
    "path": "common/pmtrace.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef PM_TRACE_H\n#define PM_TRACE_H\n\ntypedef struct\n{\n\tvec3_t\tnormal;\n\tfloat\tdist;\n} pmplane_t;\n\ntypedef struct pmtrace_s pmtrace_t;\n\nstruct pmtrace_s\n{\n\tqboolean\tallsolid;\t\t// if true, plane is not valid\n\tqboolean\tstartsolid;\t// if true, the initial point was in a solid area\n\tqboolean\tinopen, inwater;  // End point is in empty space or in water\n\tfloat\tfraction;\t\t// time completed, 1.0 = didn't hit anything\n\tvec3_t\tendpos;\t\t// final position\n\tpmplane_t\tplane;\t\t// surface normal at impact\n\tint\tent;\t\t// entity at impact\n\tvec3_t\tdeltavelocity;\t// Change in player's velocity caused by impact.\n\t\t\t\t// Only run on server.\n\tint\thitgroup;\n};\n\n#endif//PM_TRACE_H\n"
  },
  {
    "path": "common/port.h",
    "content": "/*\nport.h -- Portability Layer for Windows types\nCopyright (C) 2015 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef PORT_H\n#define PORT_H\n\n#include \"build.h\"\n\n#if !XASH_WIN32\n\t#if XASH_APPLE\n\t\t#include <sys/syslimits.h>\n\t\t#define OS_LIB_EXT \"dylib\"\n\t\t#define OPEN_COMMAND \"open\"\n\t#elif XASH_EMSCRIPTEN\n\t\t#define OS_LIB_EXT \"wasm\"\n\t\t#define OPEN_COMMAND \"???\"\n\t#else\n\t\t#define OS_LIB_EXT \"so\"\n\t\t#define OPEN_COMMAND \"xdg-open\"\n\t#endif\n\t#define OS_LIB_PREFIX \"lib\"\n\t#define VGUI_SUPPORT_DLL \"libvgui_support.\" OS_LIB_EXT\n\n\t// Windows-specific\n\t#define __cdecl\n\t#define __stdcall\n\t#define _inline\tstatic inline\n\n\t#if XASH_POSIX\n\t\t#include <unistd.h>\n\t\t#if XASH_NSWITCH\n\t\t\t#define SOLDER_LIBDL_COMPAT\n\t\t\t#include <solder.h>\n\t\t#elif XASH_PSVITA\n\t\t\t#define VRTLD_LIBDL_COMPAT\n\t\t\t#include <vrtld.h>\n\t\t\t#define O_BINARY 0\n\t\t#else\n\t\t\t#include <dlfcn.h>\n\t\t\t#define HAVE_DUP\n\t\t\t#define O_BINARY 0\n\t\t#endif\n\t\t#define O_TEXT 0\n\t\t#define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH )\n\t#endif\n\n\ttypedef void* HANDLE;\n\ttypedef void* HINSTANCE;\n\n\ttypedef struct tagPOINT\n\t{\n\t\tint x, y;\n\t} POINT;\n#else // WIN32\n\t#define open _open\n\t#define read _read\n\t#define alloca _alloca\n\n\t#define HSPRITE WINAPI_HSPRITE\n\t\t#define WIN32_LEAN_AND_MEAN\n\t\t#include <winsock2.h>\n\t\t#include <windows.h>\n\t#undef HSPRITE\n\n\t#define OS_LIB_PREFIX \"\"\n\t#define OS_LIB_EXT \"dll\"\n\t#define VGUI_SUPPORT_DLL \"../vgui_support.\" OS_LIB_EXT\n\t#define HAVE_DUP\n#endif //WIN32\n\n#ifndef XASH_LOW_MEMORY\n#define XASH_LOW_MEMORY 0\n#endif\n\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#endif // PORT_H\n"
  },
  {
    "path": "common/qfont.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef QFONT_H\n#define QFONT_H\n\n// Font stuff\n\n#define NUM_GLYPHS\t\t256\n\ntypedef struct\n{\n\tshort\tstartoffset;\n\tshort\tcharwidth;\n} charinfo;\n\ntypedef struct qfont_s\n{\n\tint\twidth, height;\n\tint\trowcount;\n\tint\trowheight;\n\tcharinfo\tfontinfo[NUM_GLYPHS];\n\tbyte\tdata[4];\n} qfont_t;\n\n#endif//QFONT_H\n"
  },
  {
    "path": "common/r_efx.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef R_EFX_H\n#define R_EFX_H\n\n// particle_t\n#if !defined( PARTICLEDEFH )\n#include \"particledef.h\"\n#endif\n\n// BEAM\n#if !defined( BEAMDEFH )\n#include \"beamdef.h\"\n#endif\n\n// dlight_t\n#if !defined ( DLIGHTH )\n#include \"dlight.h\"\n#endif\n\n// cl_entity_t\n#if !defined( CL_ENTITYH )\n#include \"cl_entity.h\"\n#endif\n\n/*\n// FOR REFERENCE, These are the built-in tracer colors.  Note, color 4 is the one\n// that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings\ncolor24 gTracerColors[] =\n{\n\t{ 255, 255, 255 },\t\t// White\n\t{ 255, 0, 0 },\t\t// Red\n\t{ 0, 255, 0 },\t\t// Green\n\t{ 0, 0, 255 },\t\t// Blue\n\t{ 0, 0, 0 },\t\t// Tracer default, filled in from cvars, etc.\n\t{ 255, 167, 17 },\t\t// Yellow-orange sparks\n\t{ 255, 130, 90 },\t\t// Yellowish streaks (garg)\n\t{ 55, 60, 144 },\t\t// Blue egon streak\n\t{ 255, 130, 90 },\t\t// More Yellowish streaks (garg)\n\t{ 255, 140, 90 },\t\t// More Yellowish streaks (garg)\n\t{ 200, 130, 90 },\t\t// More red streaks (garg)\n\t{ 255, 120, 70 },\t\t// Darker red streaks (garg)\n};\n*/\n\n#define TRACER_COLORINDEX_DEFAULT 4\n\n// Temporary entity array\n#define TENTPRIORITY_LOW\t0\n#define TENTPRIORITY_HIGH\t1\n\n// TEMPENTITY flags\n#define FTENT_NONE\t\t\t0x00000000\n#define FTENT_SINEWAVE\t\t0x00000001\n#define FTENT_GRAVITY\t\t0x00000002\n#define FTENT_ROTATE\t\t0x00000004\n#define FTENT_SLOWGRAVITY\t\t0x00000008\n#define FTENT_SMOKETRAIL\t\t0x00000010\n#define FTENT_COLLIDEWORLD\t\t0x00000020\n#define FTENT_FLICKER\t\t0x00000040\n#define FTENT_FADEOUT\t\t0x00000080\n#define FTENT_SPRANIMATE\t\t0x00000100\n#define FTENT_HITSOUND\t\t0x00000200\n#define FTENT_SPIRAL\t\t0x00000400\n#define FTENT_SPRCYCLE\t\t0x00000800\n#define FTENT_COLLIDEALL\t\t0x00001000 // will collide with world and slideboxes\n#define FTENT_PERSIST\t\t0x00002000 // tent is not removed when unable to draw\n#define FTENT_COLLIDEKILL\t\t0x00004000 // tent is removed upon collision with anything\n#define FTENT_PLYRATTACHMENT\t\t0x00008000 // tent is attached to a player (owner)\n#define FTENT_SPRANIMATELOOP\t\t0x00010000 // animating sprite doesn't die when last frame is displayed\n#define FTENT_SPARKSHOWER\t\t0x00020000\n#define FTENT_NOMODEL\t\t0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things )\n#define FTENT_CLIENTCUSTOM\t\t0x00080000 // Must specify callback.  Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things )\n#define FTENT_SCALE\t\t\t0x00100000 // An experiment\n\nstruct pmtrace_s;\ntypedef struct tempent_s\n{\n\tint\t\tflags;\n\tfloat\t\tdie;\n\tfloat\t\tframeMax;\n\tfloat\t\tx;\n\tfloat\t\ty;\n\tfloat\t\tz;\n\tfloat\t\tfadeSpeed;\n\tfloat\t\tbounceFactor;\n\tint\t\thitSound;\n\tvoid\t\t(*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr );\n\tvoid\t\t(*callback)( struct tempent_s *ent, float frametime, float currenttime );\n\tstruct tempent_s\t*next;\n\tint\t\tpriority;\n\tshort\t\tclientIndex;\t// if attached, this is the index of the client to stick to\n\t\t\t\t\t// if COLLIDEALL, this is the index of the client to ignore\n\t\t\t\t\t// TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex!\n\n\tvec3_t\t\ttentOffset;\t// if attached, client origin + tentOffset = tent origin.\n\tcl_entity_t\tentity;\n\n\t// baseline.origin\t\t- velocity\n\t// baseline.renderamt\t- starting fadeout intensity\n\t// baseline.angles\t\t- angle velocity\n} TEMPENTITY;\n\ntypedef struct efx_api_s efx_api_t;\n\nstruct efx_api_s\n{\n\tparticle_t\t*(*R_AllocParticle)( void (*callback)( struct particle_s *particle, float frametime ));\n\tvoid\t\t(*R_BlobExplosion)( const float *org );\n\tvoid\t\t(*R_Blood)( const float *org, const float *dir, int pcolor, int speed );\n\tvoid\t\t(*R_BloodSprite)( const float *org, int colorindex, int modelIndex, int modelIndex2, float size );\n\tvoid\t\t(*R_BloodStream)( const float *org, const float *dir, int pcolor, int speed );\n\tvoid\t\t(*R_BreakModel)( const float *pos, const float *size, const float *dir, float random, float life, int count, int modelIndex, char flags );\n\tvoid\t\t(*R_Bubbles)( const float *mins, const float *maxs, float height, int modelIndex, int count, float speed );\n\tvoid\t\t(*R_BubbleTrail)( const float *start, const float *end, float height, int modelIndex, int count, float speed );\n\tvoid\t\t(*R_BulletImpactParticles)( const float *pos );\n\tvoid\t\t(*R_EntityParticles)( struct cl_entity_s *ent );\n\tvoid\t\t(*R_Explosion)( float *pos, int model, float scale, float framerate, int flags );\n\tvoid\t\t(*R_FizzEffect)( struct cl_entity_s *pent, int modelIndex, int density );\n\tvoid\t\t(*R_FireField)( float *org, int radius, int modelIndex, int count, int flags, float life );\n\tvoid\t\t(*R_FlickerParticles)( const float *org );\n\tvoid\t\t(*R_FunnelSprite)( const float *org, int modelIndex, int reverse );\n\tvoid\t\t(*R_Implosion)( const float *end, float radius, int count, float life );\n\tvoid\t\t(*R_LargeFunnel)( const float *org, int reverse );\n\tvoid\t\t(*R_LavaSplash)( const float *org );\n\tvoid\t\t(*R_MultiGunshot)( const float *org, const float *dir, const float *noise, int count, int decalCount, int *decalIndices );\n\tvoid\t\t(*R_MuzzleFlash)( const float *pos1, int type );\n\tvoid\t\t(*R_ParticleBox)( const float *mins, const float *maxs, unsigned char r, unsigned char g, unsigned char b, float life );\n\tvoid\t\t(*R_ParticleBurst)( const float *pos, int size, int color, float life );\n\tvoid\t\t(*R_ParticleExplosion)( const float *org );\n\tvoid\t\t(*R_ParticleExplosion2)( const float *org, int colorStart, int colorLength );\n\tvoid\t\t(*R_ParticleLine)( const float *start, const float *end, unsigned char r, unsigned char g, unsigned char b, float life );\n\tvoid\t\t(*R_PlayerSprites)( int client, int modelIndex, int count, int size );\n\tvoid\t\t(*R_Projectile)( const float *origin, const float *velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) );\n\tvoid\t\t(*R_RicochetSound)( const float *pos );\n\tvoid\t\t(*R_RicochetSprite)( const float *pos, struct model_s *pmodel, float duration, float scale );\n\tvoid\t\t(*R_RocketFlare)( const float *pos );\n\tvoid\t\t(*R_RocketTrail)( float *start, float *end, int type );\n\tvoid\t\t(*R_RunParticleEffect)( const float *org, const float *dir, int color, int count );\n\tvoid\t\t(*R_ShowLine)( const float *start, const float *end );\n\tvoid\t\t(*R_SparkEffect)( const float *pos, int count, int velocityMin, int velocityMax );\n\tvoid\t\t(*R_SparkShower)( const float *pos );\n\tvoid\t\t(*R_SparkStreaks)( const float *pos, int count, int velocityMin, int velocityMax );\n\tvoid\t\t(*R_Spray)( const float *pos, const float *dir, int modelIndex, int count, int speed, int spread, int rendermode );\n\tvoid\t\t(*R_Sprite_Explode)( TEMPENTITY *pTemp, float scale, int flags );\n\tvoid\t\t(*R_Sprite_Smoke)( TEMPENTITY *pTemp, float scale );\n\tvoid\t\t(*R_Sprite_Spray)( const float *pos, const float *dir, int modelIndex, int count, int speed, int iRand );\n\tvoid\t\t(*R_Sprite_Trail)( int type, float *start, float *end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed );\n\tvoid\t\t(*R_Sprite_WallPuff)( TEMPENTITY *pTemp, float scale );\n\tvoid\t\t(*R_StreakSplash)( const float *pos, const float *dir, int color, int count, float speed, int velocityMin, int velocityMax );\n\tvoid\t\t(*R_TracerEffect)( const float *start, const float *end );\n\tvoid\t\t(*R_UserTracerParticle)( float *org, float *vel, float life, int colorIndex, float length, unsigned char deathcontext, void (*deathfunc)( struct particle_s *particle ));\n\tparticle_t\t*(*R_TracerParticles)( float *org, float *vel, float life );\n\tvoid\t\t(*R_TeleportSplash)( const float *org );\n\tvoid\t\t(*R_TempSphereModel)( const float *pos, float speed, float life, int count, int modelIndex );\n\tTEMPENTITY\t*(*R_TempModel)( const float *pos, const float *dir, const float *angles, float life, int modelIndex, int soundtype );\n\tTEMPENTITY\t*(*R_DefaultSprite)( const float *pos, int spriteIndex, float framerate );\n\tTEMPENTITY\t*(*R_TempSprite)( float *pos, const float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags );\n\tint\t\t(*Draw_DecalIndex)( int id );\n\tint\t\t(*Draw_DecalIndexFromName)( const char *name );\n\tvoid\t\t(*R_DecalShoot)( int textureIndex, int entity, int modelIndex, float *position, int flags );\n\tvoid\t\t(*R_AttachTentToPlayer)( int client, int modelIndex, float zoffset, float life );\n\tvoid\t\t(*R_KillAttachedTents)( int client );\n\tBEAM\t\t*(*R_BeamCirclePoints)( int type, float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\n\tBEAM\t\t*(*R_BeamEntPoint)( int startEnt, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\n\tBEAM\t\t*(*R_BeamEnts)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\n\tBEAM\t\t*(*R_BeamFollow)( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness );\n\tvoid\t\t(*R_BeamKill)( int deadEntity );\n\tBEAM\t\t*(*R_BeamLightning)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed );\n\tBEAM\t\t*(*R_BeamPoints)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\n\tBEAM\t\t*(*R_BeamRing)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\n\tdlight_t\t\t*(*CL_AllocDlight)( int key );\n\tdlight_t\t\t*(*CL_AllocElight)( int key );\n\tTEMPENTITY\t*(*CL_TempEntAlloc)( const float *org, struct model_s *model );\n\tTEMPENTITY\t*(*CL_TempEntAllocNoModel)( const float *org );\n\tTEMPENTITY\t*(*CL_TempEntAllocHigh)( const float *org, struct model_s *model );\n\tTEMPENTITY\t*(*CL_TentEntAllocCustom)( const float *origin, struct model_s *model, int high, void (*callback)( struct tempent_s *ent, float frametime, float currenttime ));\n\tvoid\t\t(*R_GetPackedColor)( short *packed, short color );\n\tshort\t\t(*R_LookupColor)( unsigned char r, unsigned char g, unsigned char b );\n\tvoid\t\t(*R_DecalRemoveAll)( int textureIndex ); // textureIndex points to the decal index in the array, not the actual texture index.\n\tvoid\t\t(*R_FireCustomDecal)( int textureIndex, int entity, int modelIndex, float *position, int flags, float scale );\n};\n\n#endif//R_EFX_H\n"
  },
  {
    "path": "common/r_studioint.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n\n#ifndef R_STUDIOINT_H\n#define R_STUDIOINT_H\n\n#define STUDIO_INTERFACE_VERSION 1\n\ntypedef struct engine_studio_api_s\n{\n\t// Allocate number*size bytes and zero it\n\tvoid\t\t*( *Mem_Calloc )( int number, size_t size );\n\t// Check to see if pointer is in the cache\n\tvoid\t\t*( *Cache_Check )( struct cache_user_s *c );\n\t// Load file into cache ( can be swapped out on demand )\n\tvoid\t\t( *LoadCacheFile )( const char *path, struct cache_user_s *cu );\n\t// Retrieve model pointer for the named model\n\tstruct model_s\t*( *Mod_ForName )( const char *name, int crash_if_missing );\n\t// Retrieve pointer to studio model data block from a model\n\tvoid\t\t*( *Mod_Extradata )( struct model_s *mod );\n\t// Retrieve indexed model from client side model precache list\n\tstruct model_s\t*( *GetModelByIndex )( int index );\n\t// Get entity that is set for rendering\n\tstruct cl_entity_s * ( *GetCurrentEntity )( void );\n\t// Get referenced player_info_t\n\tstruct player_info_s *( *PlayerInfo )( int index );\n\t// Get most recently received player state data from network system\n\tstruct entity_state_s *( *GetPlayerState )( int index );\n\t// Get viewentity\n\tstruct cl_entity_s * ( *GetViewEntity )( void );\n\t// Get current frame count, and last two timestampes on client\n\tvoid\t\t( *GetTimes )( int *framecount, double *current, double *old );\n\t// Get a pointer to a cvar by name\n\tstruct cvar_s\t*( *GetCvar )( const char *name );\n\t// Get current render origin and view vectors ( up, right and vpn )\n\tvoid\t\t( *GetViewInfo )( float *origin, float *upv, float *rightv, float *vpnv );\n\t// Get sprite model used for applying chrome effect\n\tstruct model_s\t*( *GetChromeSprite )( void );\n\t// Get model counters so we can incement instrumentation\n\tvoid\t\t( *GetModelCounters )( int **s, int **a );\n\t// Get software scaling coefficients\n\tvoid\t\t( *GetAliasScale )( float *x, float *y );\n\n\t// Get bone, light, alias, and rotation matrices\n\tfloat\t\t****( *StudioGetBoneTransform )( void );\n\tfloat\t\t****( *StudioGetLightTransform )( void );\n\tfloat\t\t***( *StudioGetAliasTransform )( void );\n\tfloat\t\t***( *StudioGetRotationMatrix )( void );\n\n\t// Set up body part, and get submodel pointers\n\tvoid\t\t( *StudioSetupModel )( int bodypart, void **ppbodypart, void **ppsubmodel );\n\t// Check if entity's bbox is in the view frustum\n\tint\t\t( *StudioCheckBBox )( void );\n\t// Apply lighting effects to model\n\tvoid\t\t( *StudioDynamicLight )( struct cl_entity_s *ent, struct alight_s *plight );\n\tvoid\t\t( *StudioEntityLight )( struct alight_s *plight );\n\tvoid\t\t( *StudioSetupLighting )( struct alight_s *plighting );\n\n\t// Draw mesh vertices\n\tvoid\t\t( *StudioDrawPoints )( void );\n\n\t// Draw hulls around bones\n\tvoid\t\t( *StudioDrawHulls )( void );\n\t// Draw bbox around studio models\n\tvoid\t\t( *StudioDrawAbsBBox )( void );\n\t// Draws bones\n\tvoid\t\t( *StudioDrawBones )( void );\n\t// Loads in appropriate texture for model\n\tvoid\t\t( *StudioSetupSkin )( void *ptexturehdr, int index );\n\t// Sets up for remapped colors\n\tvoid\t\t( *StudioSetRemapColors )( int top, int bottom );\n\t// Set's player model and returns model pointer\n\tstruct model_s\t*( *SetupPlayerModel )( int index );\n\t// Fires any events embedded in animation\n\tvoid\t\t( *StudioClientEvents )( void );\n\t// Retrieve/set forced render effects flags\n\tint\t\t( *GetForceFaceFlags )( void );\n\tvoid\t\t( *SetForceFaceFlags )( int flags );\n\t// Tell engine the value of the studio model header\n\tvoid\t\t( *StudioSetHeader )( void *header );\n\t// Tell engine which model_t * is being renderered\n\tvoid\t\t( *SetRenderModel )( struct model_s *model );\n\n\t// Final state setup and restore for rendering\n\tvoid\t\t( *SetupRenderer )( int rendermode );\n\tvoid\t\t( *RestoreRenderer )( void );\n\n\t// Set render origin for applying chrome effect\n\tvoid\t\t( *SetChromeOrigin )( void );\n\n\t// True if using D3D/OpenGL\n\tint\t\t( *IsHardware )( void );\n\n\t// Only called by hardware interface\n\tvoid\t\t( *GL_StudioDrawShadow )( void );\n\tvoid\t\t( *GL_SetRenderMode )( int mode );\n\n\tvoid\t\t( *StudioSetRenderamt )( int iRenderamt );\n\tvoid\t\t( *StudioSetCullState )( int iCull );\n\tvoid\t\t( *StudioRenderShadow )( int iSprite, float *p1, float *p2, float *p3, float *p4 );\n} engine_studio_api_t;\n\ntypedef struct server_studio_api_s\n{\n\t// Allocate number*size bytes and zero it\n\tvoid\t\t*( *Mem_Calloc )( int number, size_t size );\n\t// Check to see if pointer is in the cache\n\tvoid\t\t*( *Cache_Check )( struct cache_user_s *c );\n\t// Load file into cache ( can be swapped out on demand )\n\tvoid\t\t( *LoadCacheFile )( const char *path, struct cache_user_s *cu );\n\t// Retrieve pointer to studio model data block from a model\n\tvoid\t\t*( *Mod_Extradata )( struct model_s *mod );\n} server_studio_api_t;\n\n// client blending\ntypedef struct r_studio_interface_s\n{\n\tint\t\tversion;\n\tint\t\t( *StudioDrawModel\t)( int flags );\n\tint\t\t( *StudioDrawPlayer\t)( int flags, struct entity_state_s *pplayer );\n} r_studio_interface_t;\n\n// server blending\n#define SV_BLENDING_INTERFACE_VERSION 1\n\ntypedef struct sv_blending_interface_s\n{\n\tint\tversion;\n\n\tvoid\t( *SV_StudioSetupBones )( struct model_s *pModel,\n\t\t\t\t\tfloat frame,\n\t\t\t\t\tint sequence,\n\t\t\t\t\tconst vec3_t angles,\n\t\t\t\t\tconst vec3_t origin,\n\t\t\t\t\tconst byte *pcontroller,\n\t\t\t\t\tconst byte *pblending,\n\t\t\t\t\tint iBone,\n\t\t\t\t\tconst edict_t *pEdict );\n} sv_blending_interface_t;\n\n#endif//R_STUDIOINT_H\n"
  },
  {
    "path": "common/ref_device.h",
    "content": "/*\nref_device.h - common structures for retrieving GPU information for\nrefs, menu and engine\nCopyright (C) 2021 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef REF_DEVICE_H\n#define REF_DEVICE_H\n\n// modeled after Vulkan and currently useful only for it\ntypedef enum ref_device_type_e\n{\n\tREF_DEVICE_TYPE_OTHER = 0,\n\tREF_DEVICE_TYPE_INTERGRATED_GPU,\n\tREF_DEVICE_TYPE_DISCRETE_GPU,\n\tREF_DEVICE_TYPE_VIRTUAL_GPU,\n\tREF_DEVICE_TYPE_CPU,\n\tREF_DEVICE_TYPE_LAST,\n} ref_device_type_t;\n\n#define REF_DEVICE_NAME_SIZE 256\n\n// only add new fields to the end of the struct!!!\ntypedef struct ref_device_s {\n\tint\tvendorID;\n\tint\tdeviceID;\n\tref_device_type_t\tdeviceType;\n\tchar\tdeviceName[REF_DEVICE_NAME_SIZE];\n} ref_device_t;\n\n#endif /* REF_DEVICE_H */\n"
  },
  {
    "path": "common/ref_params.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef REF_PARAMS_H\n#define REF_PARAMS_H\n\ntypedef struct ref_params_s\n{\n\t// output\n\tvec3_t\t\tvieworg;\n\tvec3_t\t\tviewangles;\n\n\tvec3_t\t\tforward;\n\tvec3_t\t\tright;\n\tvec3_t\t\tup;\n\n\t// Client frametime;\n\tfloat\t\tframetime;\n\t// Client time\n\tfloat\t\ttime;\n\n\t// Misc\n\tint\t\tintermission;\n\tint\t\tpaused;\n\tint\t\tspectator;\n\tint\t\tonground;\n\tint\t\twaterlevel;\n\n\tvec3_t\t\tsimvel;\n\tvec3_t\t\tsimorg;\n\n\tvec3_t\t\tviewheight;\n\tfloat\t\tidealpitch;\n\n\tvec3_t\t\tcl_viewangles;\n\tint\t\thealth;\n\tvec3_t\t\tcrosshairangle;\n\tfloat\t\tviewsize;\n\n\tvec3_t\t\tpunchangle;\n\tint\t\tmaxclients;\n\tint\t\tviewentity;\n\tint\t\tplayernum;\n\tint\t\tmax_entities;\n\tint\t\tdemoplayback;\n\tint\t\thardware;\n\tint\t\tsmoothing;\n\n\t// Last issued usercmd\n\tstruct usercmd_s\t*cmd;\n\n\t// Movevars\n\tstruct movevars_s\t*movevars;\n\n\tint\t\tviewport[4];\t// the viewport coordinates x, y, width, height\n\tint\t\tnextView;\t\t// the renderer calls ClientDLL_CalcRefdef() and Renderview\n\t\t\t\t\t// so long in cycles until this value is 0 (multiple views)\n\tint\t\tonlyClientDraw;\t// if !=0 nothing is drawn by the engine except clientDraw functions\n} ref_params_t;\n\n// same as ref_params but for overview mode\ntypedef struct ref_overview_s\n{\n\tvec3_t\t\torigin;\n\tqboolean\t\trotated;\n\n\tfloat\t\txLeft;\n\tfloat\t\txRight;\n\tfloat\t\tyTop;\n\tfloat\t\tyBottom;\n\tfloat\t\tzFar;\n\tfloat\t\tzNear;\n\tfloat\t\tflZoom;\n} ref_overview_t;\n\n// ref_viewpass_t->flags\n#define RF_DRAW_WORLD\t(1<<0)\t\t// pass should draw the world (otherwise it's player menu model)\n#define RF_DRAW_CUBEMAP\t(1<<1)\t\t// special 6x pass to render cubemap\\skybox sides\n#define RF_DRAW_OVERVIEW\t(1<<2)\t\t// overview mode is active\n#define RF_ONLY_CLIENTDRAW\t(1<<3)\t\t// nothing is drawn by the engine except clientDraw functions\n\n// intermediate struct for viewpass (or just a single frame)\ntypedef struct ref_viewpass_s\n{\n\tint\t\tviewport[4];\t// size of new viewport\n\tvec3_t\t\tvieworigin;\t// view origin\n\tvec3_t\t\tviewangles;\t// view angles\n\tint\t\tviewentity;\t// entitynum (P2: Savior uses this)\n\tfloat\t\tfov_x, fov_y;\t// vertical & horizontal FOV\n\tint\t\tflags;\t\t// if !=0 nothing is drawn by the engine except clientDraw functions\n} ref_viewpass_t;\n\n#endif//REF_PARAMS_H\n"
  },
  {
    "path": "common/render_api.h",
    "content": "/*\nrender_api.h - Xash3D extension for client interface\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef RENDER_API_H\n#define RENDER_API_H\n\n#include \"lightstyle.h\"\n#include \"dlight.h\"\n\n#define CL_RENDER_INTERFACE_VERSION\t37\t// Xash3D 1.0\n#define MAX_STUDIO_DECALS\t\t4096\t// + unused space of BSP decals\n\n// render info parms\n#define PARM_TEX_WIDTH\t1\t// all parms with prefix 'TEX_' receive arg as texnum\n#define PARM_TEX_HEIGHT\t2\t// otherwise it's not used\n#define PARM_TEX_SRC_WIDTH\t3\n#define PARM_TEX_SRC_HEIGHT\t4\n#define PARM_TEX_SKYBOX\t5\t// second arg as skybox ordering num\n#define PARM_TEX_SKYTEXNUM\t6\t// skytexturenum for quake sky\n#define PARM_TEX_LIGHTMAP\t7\t// second arg as number 0 - 128\n#define PARM_TEX_TARGET\t8\n#define PARM_TEX_TEXNUM\t9\n#define PARM_TEX_FLAGS\t10\n#define PARM_TEX_DEPTH\t11\t// 3D texture depth or 2D array num layers\n//reserved\n#define PARM_TEX_GLFORMAT\t13\t// get a texture GL-format\n#define PARM_TEX_ENCODE\t14\t// custom encoding for DXT image\n#define PARM_TEX_MIPCOUNT\t15\t// count of mipmaps (0 - autogenerated, 1 - disabled of mipmapping)\n#define PARM_BSP2_SUPPORTED\t16\t// tell custom renderer what engine is support BSP2 in this build\n#define PARM_SKY_SPHERE\t17\t// sky is quake sphere ?\n#define PARAM_GAMEPAUSED\t18\t// game is paused\n#define PARM_MAP_HAS_DELUXE\t19\t// map has deluxedata\n#define PARM_MAX_ENTITIES\t20\n#define PARM_WIDESCREEN\t21\n#define PARM_FULLSCREEN\t22\n#define PARM_SCREEN_WIDTH\t23\n#define PARM_SCREEN_HEIGHT\t24\n#define PARM_CLIENT_INGAME\t25\n#define PARM_FEATURES\t26\t// same as movevars->features\n#define PARM_ACTIVE_TMU\t27\t// for debug\n#define PARM_LIGHTSTYLEVALUE\t28\t// second arg is stylenum\n#define PARM_MAX_IMAGE_UNITS\t29\n#define PARM_CLIENT_ACTIVE\t30\n#define PARM_REBUILD_GAMMA\t31\t// if true lightmaps rebuilding for gamma change\n#define PARM_DEDICATED_SERVER\t32\n#define PARM_SURF_SAMPLESIZE\t33\t// lightmap resolution per face (second arg interpret as facenumber)\n#define PARM_GL_CONTEXT_TYPE\t34\t// opengl or opengles\n#define PARM_GLES_WRAPPER\t35\t//\n#define PARM_STENCIL_ACTIVE\t36\n#define PARM_WATER_ALPHA\t37\n#define PARM_TEX_MEMORY\t38\t// returns total memory of uploaded texture in bytes\n#define PARM_DELUXEDATA\t39\t// nasty hack, convert int to pointer\n#define PARM_SHADOWDATA\t40\t// nasty hack, convert int to pointer\n#define PARM_MODERNFLASHLIGHT\t41\t// new dynamic flashlight, initially for Vulkan render\n\n// skybox ordering\nenum\n{\n\tSKYBOX_RIGHT\t= 0,\n\tSKYBOX_BACK,\n\tSKYBOX_LEFT,\n\tSKYBOX_FORWARD,\n\tSKYBOX_UP,\n\tSKYBOX_DOWN,\n};\n\ntypedef enum\n{\n\tTF_COLORMAP\t= 0,\t\t// just for tabulate source\n\tTF_NEAREST\t= (1<<0),\t\t// disable texfilter\n\tTF_KEEP_SOURCE\t= (1<<1),\t\t// some images keep source\n\tTF_NOFLIP_TGA\t= (1<<2),\t\t// Steam background completely ignore tga attribute 0x20\n\tTF_EXPAND_SOURCE\t= (1<<3),\t\t// Don't keep source as 8-bit expand to RGBA\n// reserved\n\tTF_RECTANGLE\t= (1<<5),\t\t// this is GL_TEXTURE_RECTANGLE\n\tTF_CUBEMAP\t= (1<<6),\t\t// it's cubemap texture\n\tTF_DEPTHMAP\t= (1<<7),\t\t// custom texture filter used\n\tTF_QUAKEPAL\t= (1<<8),\t\t// image has an quake1 palette\n\tTF_LUMINANCE\t= (1<<9),\t\t// force image to grayscale\n\tTF_SKYSIDE\t= (1<<10),\t// this is a part of skybox\n\tTF_CLAMP\t\t= (1<<11),\t// clamp texcoords to [0..1] range\n\tTF_NOMIPMAP\t= (1<<12),\t// don't build mips for this image\n\tTF_HAS_LUMA\t= (1<<13),\t// sets by GL_UploadTexture\n\tTF_MAKELUMA\t= (1<<14),\t// create luma from quake texture (only q1 textures contain luma-pixels)\n\tTF_NORMALMAP\t= (1<<15),\t// is a normalmap\n\tTF_HAS_ALPHA\t= (1<<16),\t// image has alpha (used only for GL_CreateTexture)\n\tTF_FORCE_COLOR\t= (1<<17),\t// force upload monochrome textures as RGB (detail textures)\n\tTF_UPDATE\t\t= (1<<18),\t// allow to update already loaded texture\n\tTF_BORDER\t\t= (1<<19),\t// zero clamp for projected textures\n\tTF_TEXTURE_3D\t= (1<<20),\t// this is GL_TEXTURE_3D\n\tTF_ATLAS_PAGE\t= (1<<21),\t// bit who indicate lightmap page or deluxemap page\n\tTF_ALPHACONTRAST\t= (1<<22),\t// special texture mode for A2C\n// reserved\n// reserved\n\tTF_IMG_UPLOADED\t= (1<<25),\t// this is set for first time when called glTexImage, otherwise it will be call glTexSubImage\n\tTF_ARB_FLOAT\t= (1<<26),\t// float textures\n\tTF_NOCOMPARE\t= (1<<27),\t// disable comparing for depth textures\n\tTF_ARB_16BIT\t= (1<<28),\t// keep image as 16-bit (not 24)\n\tTF_MULTISAMPLE\t= (1<<29),\t// multisampling texture\n\tTF_ALLOW_NEAREST = (1<<30),\t// allows toggling nearest filtering for TF_NOMIPMAP textures\n} texFlags_t;\n\ntypedef enum\n{\n\tCONTEXT_TYPE_GL = 0, // compatibility profile\n\tCONTEXT_TYPE_GLES_1_X,\n\tCONTEXT_TYPE_GLES_2_X,\n\tCONTEXT_TYPE_GL_CORE\n} gl_context_type_t;\n\ntypedef enum\n{\n\tGLES_WRAPPER_NONE = 0,\t\t// native GL\n\tGLES_WRAPPER_NANOGL,\t\t// used on GLES platforms\n\tGLES_WRAPPER_WES,\t\t// used on GLES platforms\n\tGLES_WRAPPER_GL4ES,\t\t// used on GLES platforms\n} gles_wrapper_t;\n\n// 30 bytes here\ntypedef struct modelstate_s\n{\n\tshort\t\tsequence;\n\tshort\t\tframe;\t\t// 10 bits multiple by 4, should be enough\n\tbyte\t\tblending[2];\n\tbyte\t\tcontroller[4];\n\tbyte\t\tposeparam[16];\n\tbyte\t\tbody;\n\tbyte\t\tskin;\n\tshort\t\tscale;\t\t// model scale (multiplied by 16)\n} modelstate_t;\n\ntypedef struct decallist_s\n{\n\tvec3_t\t\tposition;\n\tchar\t\tname[64];\n\tshort\t\tentityIndex;\n\tbyte\t\tdepth;\n\tbyte\t\tflags;\n\tfloat\t\tscale;\n\n\t// this is the surface plane that we hit so that\n\t// we can move certain decals across\n\t// transitions if they hit similar geometry\n\tvec3_t\t\timpactPlaneNormal;\n\n\tmodelstate_t\tstudio_state;\t// studio decals only\n} decallist_t;\n\nenum movie_parms_e\n{\n\tAVI_PARM_LAST = 0, // marker for SetParm to end parse parsing arguments\n\tAVI_RENDER_TEXNUM, // (int) sets texture to draw into, if 0 will draw to screen\n\tAVI_RENDER_X, // (int) when set to screen, sets position where to draw\n\tAVI_RENDER_Y,\n\tAVI_RENDER_W, // (int) sets texture or screen width\n\tAVI_RENDER_H, // set to -1 to draw full screen\n\tAVI_REWIND, // no argument, rewind playback to the beginning\n\tAVI_ENTNUM, // (int) entity number, -1 for no spatialization\n\tAVI_VOLUME, // (int) volume from 0 to 255\n\tAVI_ATTN, // (float) attenuation value\n\tAVI_PAUSE, // no argument, pauses playback\n\tAVI_RESUME, // no argument, resumes playback\n};\n\nstruct movie_state_s;\nstruct ref_viewpass_s;\n\ntypedef struct render_api_s\n{\n\t// Get renderer info (doesn't changes engine state at all)\n\tintptr_t\t(*RenderGetParm)( int parm, int arg );\t// generic\n\tvoid\t\t(*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale );\n\tvoid\t\t(*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha );\n\tlightstyle_t*\t(*GetLightStyle)( int number );\n\tdlight_t*\t\t(*GetDynamicLight)( int number );\n\tdlight_t*\t\t(*GetEntityLight)( int number );\n\tbyte\t\t(*LightToTexGamma)( byte color );\t// software gamma support\n\tfloat\t\t(*GetFrameTime)( void );\n\n\t// Set renderer info (tell engine about changes)\n\tvoid\t\t(*R_SetCurrentEntity)( struct cl_entity_s *ent ); // tell engine about both currententity and currentmodel\n\tvoid\t\t(*R_SetCurrentModel)( struct model_s *mod );\t// change currentmodel but leave currententity unchanged\n\tint\t\t(*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis );\n\tvoid\t\t(*R_StoreEfrags)( struct efrag_s **ppefrag, int framecount );// store efrags for static entities\n\n\t// Texture tools\n\tint\t\t(*GL_FindTexture)( const char *name );\n\tconst char*\t(*GL_TextureName)( unsigned int texnum );\n\tconst byte*\t(*GL_TextureData)( unsigned int texnum ); // may be NULL\n\tint\t\t(*GL_LoadTexture)( const char *name, const byte *buf, size_t size, int flags );\n\tint\t\t(*GL_CreateTexture)( const char *name, int width, int height, const void *buffer, texFlags_t flags );\n\tint\t\t(*GL_LoadTextureArray)( const char **names, int flags );\n\tint\t\t(*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags );\n\tvoid\t\t(*GL_FreeTexture)( unsigned int texnum );\n\n\t// Decals manipulating (draw & remove)\n\tvoid\t\t(*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa );\n\tfloat\t\t*(*R_DecalSetupVerts)( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount );\n\tvoid\t\t(*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only)\n\n\t// AVIkit support\n\tstruct movie_state_s *(*AVI_LoadVideo)( const char *filename, qboolean load_audio );\n\tqboolean\t\t(*AVI_GetVideoInfo)( struct movie_state_s *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int\n\tint\t\t(*AVI_GetVideoFrameNumber)( struct movie_state_s *Avi, float time );\n\tbyte\t\t*(*AVI_GetVideoFrame)( struct movie_state_s *Avi, int frame );\n\tvoid\t\t(*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data );\n\tvoid\t\t(*AVI_FreeVideo)( struct movie_state_s *Avi );\n\tqboolean\t\t(*AVI_IsActive)( struct movie_state_s *Avi );\n\tvoid\t\t(*AVI_StreamSound)( struct movie_state_s *Avi, int entnum, float fvol, float attn, float synctime );\n\tqboolean\t(*AVI_Think)( struct movie_state_s *Avi );\n\tqboolean\t(*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... );\n\n\t// glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client)\n\tvoid\t\t(*GL_Bind)( int tmu, unsigned int texnum );\n\tvoid\t\t(*GL_SelectTexture)( int tmu );\n\tvoid\t\t(*GL_LoadTextureMatrix)( const float *glmatrix );\n\tvoid\t\t(*GL_TexMatrixIdentity)( void );\n\tvoid\t\t(*GL_CleanUpTextureUnits)( int last );\t// pass 0 for clear all the texture units\n\tvoid\t\t(*GL_TexGen)( unsigned int coord, unsigned int mode );\n\tvoid\t\t(*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture\n\tvoid\t\t(*GL_TexCoordArrayMode)( unsigned int texmode );\n\tvoid*\t\t(*GL_GetProcAddress)( const char *name );\n\tvoid\t\t(*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics\n\tvoid\t\t(*GL_Reserved0)( void );\t// for potential interface expansion without broken compatibility\n\tvoid\t\t(*GL_Reserved1)( void );\n\n\t// Misc renderer functions\n\tvoid\t\t(*GL_DrawParticles)( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime );\n\tvoid\t\t(*EnvShot)( const float *vieworg, const char *name, qboolean skyshot, int shotsize ); // store skybox into gfx\\env folder\n\tint\t\t(*SPR_LoadExt)( const char *szPicName, unsigned int texFlags ); // extended version of SPR_Load\n\tcolorVec\t\t(*LightVec)( const float *start, const float *end, float *lightspot, float *lightvec );\n\tstruct mstudiotex_s *( *StudioGetTexture )( struct cl_entity_s *e );\n\tconst struct ref_overview_s *( *GetOverviewParms )( void );\n\tconst char\t*( *GetFileByIndex )( int fileindex );\n\tint\t\t(*pfnSaveFile)( const char *filename, const void *data, int len );\n\tvoid\t\t(*R_Reserved0)( void );\n\n\t// static allocations\n\tvoid\t\t*(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 );\n\tvoid\t\t(*pfnMemFree)( void *mem, const char *filename, const int fileline );\n\n \t// engine utils (not related with render API but placed here)\n\tchar\t\t**(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly );\n\tunsigned int\t(*pfnFileBufferCRC32)( const void *buffer, const int length );\n\tint\t\t(*COM_CompareFileTime)( const char *filename1, const char *filename2, int *iCompare );\n\tvoid\t\t(*Host_Error)( const char *error, ... ); // cause Host Error\n\tvoid*\t\t( *pfnGetModel )( int modelindex );\n\tfloat\t\t(*pfnTime)( void );\t\t\t\t// Sys_DoubleTime\n\tvoid\t\t(*Cvar_Set)( const char *name, const char *value );\n\tvoid\t\t(*S_FadeMusicVolume)( float fadePercent );\t// fade background track (0-100 percents)\n\t// a1ba: changed long to int\n\tvoid\t\t(*SetRandomSeed)( int lSeed );\t\t// set custom seed for RANDOM_FLOAT\\RANDOM_LONG for predictable random\n\t// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT.  INTERFACE VERSION IS FROZEN AT 37\n} render_api_t;\n\n// render callbacks\ntypedef struct render_interface_s\n{\n\tint\t\tversion;\n\t// passed through R_RenderFrame (0 - use engine renderer, 1 - use custom client renderer)\n\tint\t\t(*GL_RenderFrame)( const struct ref_viewpass_s *rvp );\n\t// build all the lightmaps on new level or when gamma is changed\n\tvoid\t\t(*GL_BuildLightmaps)( void );\n\t// setup map bounds for ortho-projection when we in dev_overview mode\n\tvoid\t\t(*GL_OrthoBounds)( const float *mins, const float *maxs );\n\t// prepare studio decals for save\n\tint\t\t(*R_CreateStudioDecalList)( decallist_t *pList, int count );\n\t// clear decals by engine request (e.g. for demo recording or vid_restart)\n\tvoid\t\t(*R_ClearStudioDecals)( void );\n\t// grab r_speeds message\n\tqboolean\t(*R_SpeedsMessage)( char *out, size_t size );\n\t// alloc or destroy model custom data\n\tvoid\t\t(*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer );\n\t// alloc or destroy entity custom data\n\tvoid\t\t(*R_ProcessEntData)( qboolean allocate );\n\t// get visdata for current frame from custom renderer\n\tbyte*\t\t(*Mod_GetCurrentVis)( void );\n\t// tell the renderer what new map is started\n\tvoid\t\t(*R_NewMap)( void );\n\t// clear the render entities before each frame\n\tvoid\t\t(*R_ClearScene)( void );\n\t// shuffle previous & next states for lerping\n\tvoid\t\t(*CL_UpdateLatchedVars)( struct cl_entity_s *e, qboolean reset );\n} render_interface_t;\n\n#endif//RENDER_API_H\n"
  },
  {
    "path": "common/screenfade.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef SCREENFADE_H\n#define SCREENFADE_H\n\ntypedef struct screenfade_s\n{\n\tfloat\tfadeSpeed;\t\t\t// How fast to fade (tics / second) (+ fade in, - fade out)\n\tfloat\tfadeEnd;\t\t\t\t// When the fading hits maximum\n\tfloat\tfadeTotalEnd;\t\t\t// Total End Time of the fade (used for FFADE_OUT)\n\tfloat\tfadeReset;\t\t\t// When to reset to not fading (for fadeout and hold)\n\tbyte\tfader, fadeg, fadeb, fadealpha;\t// Fade color\n\tint\tfadeFlags;\t\t\t// Fading flags\n} screenfade_t;\n\n#endif//SCREENFADE_H\n"
  },
  {
    "path": "common/studio_event.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#pragma once\n#ifndef STUDIO_EVENT_H\n#define STUDIO_EVENT_H\n\n#define MAXEVENTSTRING\t\t64\n\ntypedef struct mstudioevent_s\n{\n\t// the frame at which this animation event occurs\n\tint32_t \tframe;\n\n\t// the script event type\n\tint32_t\t\tevent;\n\n\t// was \"type\"\n\tint32_t\t\tunused;\n\n\t// options\n\t// could be path to sound WAVE files\n\tchar\t\toptions[MAXEVENTSTRING];\n} mstudioevent_t;\n\n#endif // STUDIO_EVENT_H\n"
  },
  {
    "path": "common/synctype.h",
    "content": "/*\nsynctype.h -- shared synctype_t definition\nCopyright (C) 1996-1997 Id Software, Inc.\nCopyright (C) 2023 Alibek Omarov\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n#ifndef SYNCTYPE_H\n#define SYNCTYPE_H\ntypedef enum {ST_SYNC=0, ST_RAND } synctype_t;\n#endif\n"
  },
  {
    "path": "common/triangleapi.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef TRIANGLEAPI_H\n#define TRIANGLEAPI_H\n\ntypedef enum\n{\n\tTRI_FRONT = 0,\n\tTRI_NONE = 1,\n} TRICULLSTYLE;\n\n#define TRI_API_VERSION\t1\n\n#define TRI_TRIANGLES\t0\n#define TRI_TRIANGLE_FAN\t1\n#define TRI_QUADS\t\t2\n#define TRI_POLYGON\t\t3\n#define TRI_LINES\t\t4\n#define TRI_TRIANGLE_STRIP\t5\n#define TRI_QUAD_STRIP\t6\n#define TRI_POINTS\t\t7\t// Xash3D added\n\ntypedef struct triangleapi_s\n{\n\tint\tversion;\n\n\tvoid\t(*RenderMode)( int mode );\n\tvoid\t(*Begin)( int primitiveCode );\n\tvoid\t(*End)( void );\n\n\tvoid\t(*Color4f)( float r, float g, float b, float a );\n\tvoid\t(*Color4ub)( unsigned char r, unsigned char g, unsigned char b, unsigned char a );\n\tvoid\t(*TexCoord2f)( float u, float v );\n\tvoid\t(*Vertex3fv)( const float *worldPnt );\n\tvoid\t(*Vertex3f)( float x, float y, float z );\n\tvoid\t(*Brightness)( float brightness );\n\tvoid\t(*CullFace)( TRICULLSTYLE style );\n\tint\t(*SpriteTexture)( struct model_s *pSpriteModel, int frame );\n\tint\t(*WorldToScreen)( const float *world, float *screen );  // Returns 1 if it's z clipped\n\tvoid\t(*Fog)( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b.\n\tvoid\t(*ScreenToWorld)( const float *screen, float *world  );\n\tvoid\t(*GetMatrix)( const int pname, float *matrix );\n\tint\t(*BoxInPVS)( float *mins, float *maxs );\n\tvoid\t(*LightAtPoint)( float *pos, float *value );\n\tvoid\t(*Color4fRendermode)( float r, float g, float b, float a, int rendermode );\n\tvoid\t(*FogParams)( float flDensity, int iFogSkybox );\n} triangleapi_t;\n\n#endif//TRIANGLEAPI_H\n"
  },
  {
    "path": "common/usercmd.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef USERCMD_H\n#define USERCMD_H\n\ntypedef struct usercmd_s\n{\n\tshort\t\tlerp_msec;\t// Interpolation time on client\n\tbyte\t\tmsec;\t\t// Duration in ms of command\n\tvec3_t\t\tviewangles;\t// Command view angles\n\n\t// intended velocities\n\tfloat\t\tforwardmove;\t// Forward velocity\n\tfloat\t\tsidemove;\t\t// Sideways velocity\n\tfloat\t\tupmove;\t\t// Upward velocity\n\tbyte\t\tlightlevel;\t// Light level at spot where we are standing.\n\tunsigned short\tbuttons;\t\t// Attack and move buttons\n\tbyte\t\timpulse;\t\t// Impulse command issued\n\tbyte\t\tweaponselect;\t// Current weapon id\n\n\t// Experimental player impact stuff.\n\tint\t\timpact_index;\n\tvec3_t\t\timpact_position;\n} usercmd_t;\n\n#endif//USERCMD_H\n"
  },
  {
    "path": "common/wadfile.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef WADFILE_H\n#define WADFILE_H\n\n/*\n========================================================================\n.WAD archive format\t(WhereAllData - WAD)\n\nList of compressed files, that can be identify only by TYP_*\n\n<format>\nheader:\tdwadinfo_t[dwadinfo_t]\nfile_1:\tbyte[dwadinfo_t[num]->disksize]\nfile_2:\tbyte[dwadinfo_t[num]->disksize]\nfile_3:\tbyte[dwadinfo_t[num]->disksize]\n...\nfile_n:\tbyte[dwadinfo_t[num]->disksize]\ninfotable\tdlumpinfo_t[dwadinfo_t->numlumps]\n========================================================================\n*/\n\n#define IDWAD2HEADER\t(('2'<<24)+('D'<<16)+('A'<<8)+'W')\t// little-endian \"WAD2\" quake wads\n#define IDWAD3HEADER\t(('3'<<24)+('D'<<16)+('A'<<8)+'W')\t// little-endian \"WAD3\" half-life wads\n#define WAD3_NAMELEN\t16\n\n// dlumpinfo_t->attribs\n#define ATTR_NONE\t\t0\t// allow to read-write\n#define ATTR_READONLY\tBIT( 0 )\t// don't overwrite this lump in anyway\n#define ATTR_COMPRESSED\tBIT( 1 )\t// not used for now, just reserved\n#define ATTR_HIDDEN\t\tBIT( 2 )\t// not used for now, just reserved\n#define ATTR_SYSTEM\t\tBIT( 3 )\t// not used for now, just reserved\n\n// dlumpinfo_t->type\n#define TYP_ANY\t\t-1\t// any type can be accepted\n#define TYP_NONE\t\t0\t// unknown lump type\n#define TYP_LABEL\t\t1\t// legacy from Doom1. Empty lump - label (like P_START, P_END etc)\n#define TYP_PALETTE\t\t64\t// quake or half-life palette (768 bytes)\n#define TYP_DDSTEX\t\t65\t// contain DDS texture\n#define TYP_GFXPIC\t\t66\t// menu or hud image (not contain mip-levels)\n#define TYP_MIPTEX\t\t67\t// quake1 and half-life in-game textures with four miplevels\n#define TYP_SCRIPT\t\t68\t// contain script files\n#define TYP_COLORMAP2\t69\t// old stuff. build palette from LBM file (not used)\n#define TYP_QFONT\t\t70\t// half-life font (qfont_t)\n\n/*\n========================================================================\n\n.LMP image format\t(Half-Life gfx.wad lumps)\n\n========================================================================\n*/\ntypedef struct lmp_s\n{\n\tunsigned int\twidth;\n\tunsigned int\theight;\n} lmp_t;\n\n/*\n========================================================================\n\n.MIP image format\t(half-Life textures)\n\n========================================================================\n*/\ntypedef struct mip_s\n{\n\tchar\t\tname[16];\n\tunsigned int\twidth;\n\tunsigned int\theight;\n\tunsigned int\toffsets[4];\t// four mip maps stored\n} mip_t;\n\n/*\n========================================================================\n\n.WAD header format\t(half-Life textures)\n\n========================================================================\n*/\ntypedef struct\n{\n\tint\t\tident;\t\t// should be WAD3\n\tint\t\tnumlumps;\t\t// num files\n\tint\t\tinfotableofs;\t// LUT offset\n} dwadinfo_t;\n\n/*\n========================================================================\n\n.WAD struct\t(half-Life textures)\n\n========================================================================\n*/\ntypedef struct\n{\n\tint\t\tfilepos;\t\t// file offset in WAD\n\tint\t\tdisksize;\t\t// compressed or uncompressed\n\tint\t\tsize;\t\t// uncompressed\n\tsigned char\ttype;\t\t// TYP_*\n\tsigned char\tattribs;\t\t// file attribs\n\tsigned char\tpad0;\n\tsigned char\tpad1;\n\tchar\t\tname[WAD3_NAMELEN];\t// must be null terminated\n} dlumpinfo_t;\n\n#endif//WADFILE_H\n"
  },
  {
    "path": "common/weaponinfo.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef WEAPONINFO_H\n#define WEAPONINFO_H\n\n// Info about weapons player might have in his/her possession\ntypedef struct weapon_data_s\n{\n\tint\t\tm_iId;\n\tint\t\tm_iClip;\n\n\tfloat\t\tm_flNextPrimaryAttack;\n\tfloat\t\tm_flNextSecondaryAttack;\n\tfloat\t\tm_flTimeWeaponIdle;\n\n\tint\t\tm_fInReload;\n\tint\t\tm_fInSpecialReload;\n\tfloat\t\tm_flNextReload;\n\tfloat\t\tm_flPumpTime;\n\tfloat\t\tm_fReloadTime;\n\n\tfloat\t\tm_fAimedDamage;\n\tfloat\t\tm_fNextAimBonus;\n\tint\t\tm_fInZoom;\n\tint\t\tm_iWeaponState;\n\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n} weapon_data_t;\n\n#endif//WEAPONINFO_H\n"
  },
  {
    "path": "common/wrect.h",
    "content": "/*\nwrect.h - rectangle definition\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef WRECT_H\n#define WRECT_H\n\ntypedef struct wrect_s\n{\n\tint\tleft, right, top, bottom;\n} wrect_t;\n\n#endif//WRECT_H\n"
  },
  {
    "path": "common/xash3d_types.h",
    "content": "// basic typedefs\n#ifndef XASH_TYPES_H\n#define XASH_TYPES_H\n\n#include \"build.h\"\n\n#if XASH_IRIX\n#include <port.h>\n#endif\n\n#if XASH_WIN32\n#include <wchar.h> // off_t\n#endif // _WIN32\n\n#include <sys/types.h> // off_t\n#ifdef STDINT_H\n#include STDINT_H\n#else // !STDINT_H\n#include <stdint.h>\n#endif // !STDINT_H\n#include <assert.h>\n\ntypedef uint8_t  byte;\ntypedef float    vec_t;\ntypedef vec_t    vec2_t[2];\n#ifndef vec3_t // SDK renames it to Vector\ntypedef vec_t    vec3_t[3];\n#endif\ntypedef vec_t    vec4_t[4];\ntypedef vec_t    quat_t[4];\ntypedef byte     rgba_t[4];\t// unsigned byte colorpack\ntypedef byte     rgb_t[3];\t\t// unsigned byte colorpack\ntypedef vec_t    matrix3x4[3][4];\ntypedef vec_t    matrix4x4[4][4];\ntypedef uint32_t poolhandle_t;\n\n#undef true\n#undef false\n\n// true and false are keywords in C++ and C23\n#if !__cplusplus &&  __STDC_VERSION__ < 202311L\nenum { false, true };\n#endif\ntypedef int qboolean;\n\n#define MAX_STRING    256  // generic string\n#define MAX_VA_STRING 1024 // compatibility macro\n#define MAX_SYSPATH   1024 // system filepath\n#define MAX_MODS      512  // environment games that engine can keep visible\n\n#define BIT( n )\t\t( 1U << ( n ))\n#define BIT64( n )\t\t( 1ULL << ( n ))\n\n#define SetBits( iBitVector, bits )\t((iBitVector) = (iBitVector) | (bits))\n#define ClearBits( iBitVector, bits )\t((iBitVector) = (iBitVector) & ~(bits))\n#define FBitSet( iBitVector, bit )\t((iBitVector) & (bit))\n\n#ifndef __cplusplus\n#ifdef NULL\n#undef NULL\n#endif\n\n#define NULL\t\t((void *)0)\n#endif\n\n// color strings\n#define IsColorString( p )\t( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' )\n#define ColorIndex( c )\t((( c ) - '0' ) & 7 )\n\n#undef EXPORT\n\n#if defined( __GNUC__ )\n\t#if defined( __i386__ )\n\t\t#define EXPORT         __attribute__(( visibility( \"default\" ), force_align_arg_pointer ))\n\t\t#define GAME_EXPORT    __attribute__(( force_align_arg_pointer ))\n\t#else\n\t\t#define EXPORT         __attribute__(( visibility ( \"default\" )))\n\t\t#define GAME_EXPORT\n\t#endif\n\n\t#define MALLOC __attribute__(( malloc ))\n\n\t// added in GCC 11\n\t#if __GNUC__ >= 11\n\t\t// might want to set noclone due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116893\n\t\t// but it's easier to not force mismatched-dealloc to error yet\n\t\t#define MALLOC_LIKE( x, y ) __attribute__(( malloc( x, y )))\n\t#else\n\t\t#define MALLOC_LIKE( x, y ) MALLOC\n\t#endif\n\t#define NORETURN           __attribute__(( noreturn ))\n\t#define NONNULL            __attribute__(( nonnull ))\n\t#define RETURNS_NONNULL    __attribute__(( returns_nonnull ))\n\t#if __clang__ || __MCST__\n\t\t// clang has bugged returns_nonnull for functions pointers, it's ignored and generates a warning about objective-c? O_o\n\t\t// lcc doesn't support it at all\n\t\t#define PFN_RETURNS_NONNULL\n\t#else\n\t\t#define PFN_RETURNS_NONNULL RETURNS_NONNULL\n\t#endif\n\t#define FORMAT_CHECK( x )  __attribute__(( format( printf, x, x + 1 )))\n\t#define ALLOC_CHECK( x )   __attribute__(( alloc_size( x )))\n\t#define NO_ASAN            __attribute__(( no_sanitize( \"address\" )))\n\t#define WARN_UNUSED_RESULT __attribute__(( warn_unused_result ))\n\t#define RENAME_SYMBOL( x ) asm( x )\n#else\n\t#if defined( _MSC_VER )\n\t\t#define EXPORT         __declspec( dllexport )\n\t\t#define NO_ASAN        __declspec( no_sanitize_address )\n\t#else\n\t\t#define EXPORT\n\t\t#define NO_ASAN\n\t#endif\n\t#define GAME_EXPORT\n\t#define NORETURN\n\t#define NONNULL\n\t#define RETURNS_NONNULL\n\t#define PFN_RETURNS_NONNULL\n\t#define FORMAT_CHECK( x )\n\t#define ALLOC_CHECK( x )\n\t#define RENAME_SYMBOL( x )\n\t#define MALLOC\n\t#define MALLOC_LIKE( x, y )\n\t#define WARN_UNUSED_RESULT\n#endif\n\n#if defined( __has_feature )\n\t#if __has_feature( address_sanitizer )\n\t\t#define USE_ASAN 1\n\t#endif // __has_feature\n#endif // defined( __has_feature )\n\n#if !defined( USE_ASAN ) && defined( __SANITIZE_ADDRESS__ )\n#define USE_ASAN 1\n#endif\n\n#if __GNUC__ >= 3\n\t#define unlikely( x )     __builtin_expect( x, 0 )\n\t#define likely( x )       __builtin_expect( x, 1 )\n#elif defined( __has_builtin )\n\t#if __has_builtin( __builtin_expect ) // this must be after defined() check\n\t\t#define unlikely( x )     __builtin_expect( x, 0 )\n\t\t#define likely( x )       __builtin_expect( x, 1 )\n\t#endif\n#endif\n\n#if !defined( unlikely ) || !defined( likely )\n\t#define unlikely( x ) ( x )\n\t#define likely( x )   ( x )\n#endif\n\n#if __STDC_VERSION__ >= 202311L || __cplusplus >= 201103L // C23 or C++ static_assert is a keyword\n\t#define STATIC_ASSERT_( ignore, x, y ) static_assert( x, y )\n\t#define STATIC_ASSERT  static_assert\n#elif __STDC_VERSION__ >= 201112L // in C11 it's _Static_assert\n\t#define STATIC_ASSERT_( ignore, x, y ) _Static_assert( x, y )\n\t#define STATIC_ASSERT  _Static_assert\n#else\n\t#define STATIC_ASSERT_( id, x, y ) extern int id[( x ) ? 1 : -1]\n\t// need these to correctly expand the line macro\n\t#define STATIC_ASSERT_3( line, x, y ) STATIC_ASSERT_( static_assert_ ## line, x, y )\n\t#define STATIC_ASSERT_2( line, x, y ) STATIC_ASSERT_3( line, x, y )\n\t#define STATIC_ASSERT( x, y ) STATIC_ASSERT_2( __LINE__, x, y )\n#endif\n\n// at least, statically check size of some public structures\n#if XASH_64BIT\n#define STATIC_CHECK_SIZEOF( type, size32, size64 ) \\\n\tSTATIC_ASSERT( sizeof( type ) == size64, #type \" unexpected size\" )\n#else\n#define STATIC_CHECK_SIZEOF( type, size32, size64 ) \\\n\tSTATIC_ASSERT( sizeof( type ) == size32, #type \" unexpected size\" )\n#endif\n\n#if !defined( __cplusplus ) && __STDC_VERSION__ >= 199101L // not C++ and C99 or newer\n\t#define XASH_RESTRICT restrict\n#elif _MSC_VER || __GNUC__ || __clang__ // compiler-specific extensions\n\t#define XASH_RESTRICT __restrict\n#else\n\t#define XASH_RESTRICT // nothing\n#endif\n\n#ifdef XASH_BIG_ENDIAN\n#define LittleLong(x) (((int)(((x)&255)<<24)) + ((int)((((x)>>8)&255)<<16)) + ((int)(((x)>>16)&255)<<8) + (((x) >> 24)&255))\n#define LittleLongSW(x) (x = LittleLong(x) )\n#define LittleShort(x) ((short)( (((short)(x) >> 8) & 255) + (((short)(x) & 255) << 8)))\n#define LittleShortSW(x) (x = LittleShort(x) )\n_inline float LittleFloat( float f )\n{\n\tunion\n\t{\n\t\tfloat f;\n\t\tunsigned char b[4];\n\t} dat1, dat2;\n\n\tdat1.f = f;\n\tdat2.b[0] = dat1.b[3];\n\tdat2.b[1] = dat1.b[2];\n\tdat2.b[2] = dat1.b[1];\n\tdat2.b[3] = dat1.b[0];\n\n\treturn dat2.f;\n}\n#else\n#define LittleLong(x) (x)\n#define LittleLongSW(x)\n#define LittleShort(x) (x)\n#define LittleShortSW(x)\n#define LittleFloat(x) (x)\n#endif\n\n\ntypedef unsigned int dword;\ntypedef unsigned int uint;\ntypedef char         string[MAX_STRING];\ntypedef off_t        fs_offset_t;\n#if XASH_WIN32\ntypedef int          fs_size_t; // return type of _read, _write funcs\n#else /* !XASH_WIN32 */\ntypedef ssize_t      fs_size_t;\n#endif /* !XASH_WIN32 */\n\ntypedef void *(*pfnCreateInterface_t)( const char *, int * );\n\n// config strings are a general means of communication from\n// the server to all connected clients.\n// each config string can be at most CS_SIZE characters.\n#if XASH_LOW_MEMORY == 0\n#define MAX_QPATH\t\t64\t// max length of a game pathname\n#elif XASH_LOW_MEMORY == 2\n#define MAX_QPATH\t\t32 // should be enough for singleplayer\n#elif XASH_LOW_MEMORY == 1\n#define MAX_QPATH 48\n#endif\n#define MAX_OSPATH\t\t260\t// max length of a filesystem pathname\n#define CS_SIZE\t\t64\t// size of one config string\n#define CS_TIME\t\t16\t// size of time string\n\n#endif // XASH_TYPES_H\n"
  },
  {
    "path": "engine/alias.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef ALIAS_H\n#define ALIAS_H\n\n#include \"build.h\"\n#include STDINT_H\n#include \"synctype.h\"\n\n/*\n==============================================================================\n\nALIAS MODELS\n\nAlias models are position independent, so the cache manager can move them.\n==============================================================================\n*/\n\n#define IDALIASHEADER\t(('O'<<24)+('P'<<16)+('D'<<8)+'I')\t// little-endian \"IDPO\"\n\n#define ALIAS_VERSION\t6\n\n// client-side model flags\n#define ALIAS_ROCKET\t\t0x0001\t// leave a trail\n#define ALIAS_GRENADE\t\t0x0002\t// leave a trail\n#define ALIAS_GIB\t\t\t0x0004\t// leave a trail\n#define ALIAS_ROTATE\t\t0x0008\t// rotate (bonus items)\n#define ALIAS_TRACER\t\t0x0010\t// green split trail\n#define ALIAS_ZOMGIB\t\t0x0020\t// small blood trail\n#define ALIAS_TRACER2\t\t0x0040\t// orange split trail + rotate\n#define ALIAS_TRACER3\t\t0x0080\t// purple trail\n\ntypedef enum\n{\n\tALIAS_SINGLE = 0,\n\tALIAS_GROUP\n} aliasframetype_t;\n\ntypedef enum\n{\n\tALIAS_SKIN_SINGLE = 0,\n\tALIAS_SKIN_GROUP\n} aliasskintype_t;\n\ntypedef struct\n{\n\tint32_t\t\tident;\n\tint32_t\t\tversion;\n\tvec3_t\t\tscale;\n\tvec3_t\t\tscale_origin;\n\tfloat\t\tboundingradius;\n\tvec3_t\t\teyeposition;\n\tint32_t\t\tnumskins;\n\tint32_t\t\tskinwidth;\n\tint32_t\t\tskinheight;\n\tint32_t\t\tnumverts;\n\tint32_t\t\tnumtris;\n\tint32_t\t\tnumframes;\n\tuint32_t\tsynctype; // was synctype_t\n\tint32_t\t\tflags;\n\tfloat\t\tsize;\n} daliashdr_t;\n\nSTATIC_CHECK_SIZEOF( daliashdr_t, 84, 84 );\n\ntypedef struct\n{\n\tint32_t\t\tonseam;\n\tint32_t\t\ts;\n\tint32_t\t\tt;\n} stvert_t;\n\nSTATIC_CHECK_SIZEOF( stvert_t, 12, 12 );\n\ntypedef struct dtriangle_s\n{\n\tint32_t\t\tfacesfront;\n\tint32_t\t\tvertindex[3];\n} dtriangle_t;\n\nSTATIC_CHECK_SIZEOF( dtriangle_t, 16, 16 );\n\n#define DT_FACES_FRONT\t0x0010\n#define ALIAS_ONSEAM\t0x0020\n\ntypedef struct\n{\n\ttrivertex_t\tbboxmin;\t// lightnormal isn't used\n\ttrivertex_t\tbboxmax;\t// lightnormal isn't used\n\tchar\t\tname[16];\t// frame name from grabbing\n} daliasframe_t;\n\nSTATIC_CHECK_SIZEOF( daliasframe_t, 24, 24 );\n\ntypedef struct\n{\n\tint32_t\t\tnumframes;\n\ttrivertex_t\tbboxmin;\t// lightnormal isn't used\n\ttrivertex_t\tbboxmax;\t// lightnormal isn't used\n} daliasgroup_t;\n\nSTATIC_CHECK_SIZEOF( daliasgroup_t, 12, 12 );\n\ntypedef struct\n{\n\tint32_t\t\tnumskins;\n} daliasskingroup_t;\n\nSTATIC_CHECK_SIZEOF( daliasskingroup_t, 4, 4 );\n\ntypedef struct\n{\n\tfloat\t\tinterval;\n} daliasinterval_t;\n\nSTATIC_CHECK_SIZEOF( daliasinterval_t, 4, 4 );\n\ntypedef struct\n{\n\tfloat\t\tinterval;\n} daliasskininterval_t;\n\nSTATIC_CHECK_SIZEOF( daliasskininterval_t, 4, 4 );\n\ntypedef struct\n{\n\tuint32_t\ttype; // was aliasframetype_t\n} daliasframetype_t;\n\nSTATIC_CHECK_SIZEOF( daliasframetype_t, 4, 4 );\n\ntypedef struct\n{\n\tuint32_t\ttype; // was aliasskintype_t\n} daliasskintype_t;\n\nSTATIC_CHECK_SIZEOF( daliasskintype_t, 4, 4 );\n\n#endif//ALIAS_H\n"
  },
  {
    "path": "engine/anorms.h",
    "content": "/*\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  \n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n{-0.525731, 0.000000, 0.850651}, \n{-0.442863, 0.238856, 0.864188}, \n{-0.295242, 0.000000, 0.955423}, \n{-0.309017, 0.500000, 0.809017}, \n{-0.162460, 0.262866, 0.951056}, \n{0.000000, 0.000000, 1.000000}, \n{0.000000, 0.850651, 0.525731}, \n{-0.147621, 0.716567, 0.681718}, \n{0.147621, 0.716567, 0.681718}, \n{0.000000, 0.525731, 0.850651}, \n{0.309017, 0.500000, 0.809017}, \n{0.525731, 0.000000, 0.850651}, \n{0.295242, 0.000000, 0.955423}, \n{0.442863, 0.238856, 0.864188}, \n{0.162460, 0.262866, 0.951056}, \n{-0.681718, 0.147621, 0.716567}, \n{-0.809017, 0.309017, 0.500000}, \n{-0.587785, 0.425325, 0.688191}, \n{-0.850651, 0.525731, 0.000000}, \n{-0.864188, 0.442863, 0.238856}, \n{-0.716567, 0.681718, 0.147621}, \n{-0.688191, 0.587785, 0.425325}, \n{-0.500000, 0.809017, 0.309017}, \n{-0.238856, 0.864188, 0.442863}, \n{-0.425325, 0.688191, 0.587785}, \n{-0.716567, 0.681718, -0.147621}, \n{-0.500000, 0.809017, -0.309017}, \n{-0.525731, 0.850651, 0.000000}, \n{0.000000, 0.850651, -0.525731}, \n{-0.238856, 0.864188, -0.442863}, \n{0.000000, 0.955423, -0.295242}, \n{-0.262866, 0.951056, -0.162460}, \n{0.000000, 1.000000, 0.000000}, \n{0.000000, 0.955423, 0.295242}, \n{-0.262866, 0.951056, 0.162460}, \n{0.238856, 0.864188, 0.442863}, \n{0.262866, 0.951056, 0.162460}, \n{0.500000, 0.809017, 0.309017}, \n{0.238856, 0.864188, -0.442863}, \n{0.262866, 0.951056, -0.162460}, \n{0.500000, 0.809017, -0.309017}, \n{0.850651, 0.525731, 0.000000}, \n{0.716567, 0.681718, 0.147621}, \n{0.716567, 0.681718, -0.147621}, \n{0.525731, 0.850651, 0.000000}, \n{0.425325, 0.688191, 0.587785}, \n{0.864188, 0.442863, 0.238856}, \n{0.688191, 0.587785, 0.425325}, \n{0.809017, 0.309017, 0.500000}, \n{0.681718, 0.147621, 0.716567}, \n{0.587785, 0.425325, 0.688191}, \n{0.955423, 0.295242, 0.000000}, \n{1.000000, 0.000000, 0.000000}, \n{0.951056, 0.162460, 0.262866}, \n{0.850651, -0.525731, 0.000000}, \n{0.955423, -0.295242, 0.000000}, \n{0.864188, -0.442863, 0.238856}, \n{0.951056, -0.162460, 0.262866}, \n{0.809017, -0.309017, 0.500000}, \n{0.681718, -0.147621, 0.716567}, \n{0.850651, 0.000000, 0.525731}, \n{0.864188, 0.442863, -0.238856}, \n{0.809017, 0.309017, -0.500000}, \n{0.951056, 0.162460, -0.262866}, \n{0.525731, 0.000000, -0.850651}, \n{0.681718, 0.147621, -0.716567}, \n{0.681718, -0.147621, -0.716567}, \n{0.850651, 0.000000, -0.525731}, \n{0.809017, -0.309017, -0.500000}, \n{0.864188, -0.442863, -0.238856}, \n{0.951056, -0.162460, -0.262866}, \n{0.147621, 0.716567, -0.681718}, \n{0.309017, 0.500000, -0.809017}, \n{0.425325, 0.688191, -0.587785}, \n{0.442863, 0.238856, -0.864188}, \n{0.587785, 0.425325, -0.688191}, \n{0.688191, 0.587785, -0.425325}, \n{-0.147621, 0.716567, -0.681718}, \n{-0.309017, 0.500000, -0.809017}, \n{0.000000, 0.525731, -0.850651}, \n{-0.525731, 0.000000, -0.850651}, \n{-0.442863, 0.238856, -0.864188}, \n{-0.295242, 0.000000, -0.955423}, \n{-0.162460, 0.262866, -0.951056}, \n{0.000000, 0.000000, -1.000000}, \n{0.295242, 0.000000, -0.955423}, \n{0.162460, 0.262866, -0.951056}, \n{-0.442863, -0.238856, -0.864188}, \n{-0.309017, -0.500000, -0.809017}, \n{-0.162460, -0.262866, -0.951056}, \n{0.000000, -0.850651, -0.525731}, \n{-0.147621, -0.716567, -0.681718}, \n{0.147621, -0.716567, -0.681718}, \n{0.000000, -0.525731, -0.850651}, \n{0.309017, -0.500000, -0.809017}, \n{0.442863, -0.238856, -0.864188}, \n{0.162460, -0.262866, -0.951056}, \n{0.238856, -0.864188, -0.442863}, \n{0.500000, -0.809017, -0.309017}, \n{0.425325, -0.688191, -0.587785}, \n{0.716567, -0.681718, -0.147621}, \n{0.688191, -0.587785, -0.425325}, \n{0.587785, -0.425325, -0.688191}, \n{0.000000, -0.955423, -0.295242}, \n{0.000000, -1.000000, 0.000000}, \n{0.262866, -0.951056, -0.162460}, \n{0.000000, -0.850651, 0.525731}, \n{0.000000, -0.955423, 0.295242}, \n{0.238856, -0.864188, 0.442863}, \n{0.262866, -0.951056, 0.162460}, \n{0.500000, -0.809017, 0.309017}, \n{0.716567, -0.681718, 0.147621}, \n{0.525731, -0.850651, 0.000000}, \n{-0.238856, -0.864188, -0.442863}, \n{-0.500000, -0.809017, -0.309017}, \n{-0.262866, -0.951056, -0.162460}, \n{-0.850651, -0.525731, 0.000000}, \n{-0.716567, -0.681718, -0.147621}, \n{-0.716567, -0.681718, 0.147621}, \n{-0.525731, -0.850651, 0.000000}, \n{-0.500000, -0.809017, 0.309017}, \n{-0.238856, -0.864188, 0.442863}, \n{-0.262866, -0.951056, 0.162460}, \n{-0.864188, -0.442863, 0.238856}, \n{-0.809017, -0.309017, 0.500000}, \n{-0.688191, -0.587785, 0.425325}, \n{-0.681718, -0.147621, 0.716567}, \n{-0.442863, -0.238856, 0.864188}, \n{-0.587785, -0.425325, 0.688191}, \n{-0.309017, -0.500000, 0.809017}, \n{-0.147621, -0.716567, 0.681718}, \n{-0.425325, -0.688191, 0.587785}, \n{-0.162460, -0.262866, 0.951056}, \n{0.442863, -0.238856, 0.864188}, \n{0.162460, -0.262866, 0.951056}, \n{0.309017, -0.500000, 0.809017}, \n{0.147621, -0.716567, 0.681718}, \n{0.000000, -0.525731, 0.850651}, \n{0.425325, -0.688191, 0.587785}, \n{0.587785, -0.425325, 0.688191}, \n{0.688191, -0.587785, 0.425325}, \n{-0.955423, 0.295242, 0.000000}, \n{-0.951056, 0.162460, 0.262866}, \n{-1.000000, 0.000000, 0.000000}, \n{-0.850651, 0.000000, 0.525731}, \n{-0.955423, -0.295242, 0.000000}, \n{-0.951056, -0.162460, 0.262866}, \n{-0.864188, 0.442863, -0.238856}, \n{-0.951056, 0.162460, -0.262866}, \n{-0.809017, 0.309017, -0.500000}, \n{-0.864188, -0.442863, -0.238856}, \n{-0.951056, -0.162460, -0.262866}, \n{-0.809017, -0.309017, -0.500000}, \n{-0.681718, 0.147621, -0.716567}, \n{-0.681718, -0.147621, -0.716567}, \n{-0.850651, 0.000000, -0.525731}, \n{-0.688191, 0.587785, -0.425325}, \n{-0.587785, 0.425325, -0.688191}, \n{-0.425325, 0.688191, -0.587785}, \n{-0.425325, -0.688191, -0.587785}, \n{-0.587785, -0.425325, -0.688191}, \n{-0.688191, -0.587785, -0.425325}, \n"
  },
  {
    "path": "engine/cdll_exp.h",
    "content": "/*\ncdll_exp.h - exports for client\nCopyright (C) 2013 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef CDLL_EXP_H\n#define CDLL_EXP_H\n\nstruct tempent_s;\nstruct usercmd_s;\nstruct physent_s;\nstruct playermove_s;\nstruct mstudioevent_s;\nstruct engine_studio_api_s;\nstruct r_studio_interface_s;\n\n// NOTE: ordering is important!\ntypedef struct cldll_func_s\n{\n\tint\t(*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion );\n\tvoid\t(*pfnInit)( void );\n\tint\t(*pfnVidInit)( void );\n\tint\t(*pfnRedraw)( float flTime, int intermission );\n\tint\t(*pfnUpdateClientData)( client_data_t *cdata, float flTime );\n\tvoid\t(*pfnReset)( void );\n\tvoid\t(*pfnPlayerMove)( struct playermove_s *ppmove, int server );\n\tvoid\t(*pfnPlayerMoveInit)( struct playermove_s *ppmove );\n\tchar\t(*pfnPlayerMoveTexture)( char *name );\n\tvoid\t(*IN_ActivateMouse)( void );\n\tvoid\t(*IN_DeactivateMouse)( void );\n\tvoid\t(*IN_MouseEvent)( int mstate );\n\tvoid\t(*IN_ClearStates)( void );\n\tvoid\t(*IN_Accumulate)( void );\n\tvoid\t(*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active );\n\tint\t(*CL_IsThirdPerson)( void );\n\tvoid\t(*CL_CameraOffset)( float *ofs );\t// unused\n\tvoid\t*(*KB_Find)( const char *name );\n\tvoid\t(*CAM_Think)( void );\t\t// camera stuff\n\tvoid\t(*pfnCalcRefdef)( ref_params_t *pparams );\n\tint\t(*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname );\n\tvoid\t(*pfnCreateEntities)( void );\n\tvoid\t(*pfnDrawNormalTriangles)( void );\n\tvoid\t(*pfnDrawTransparentTriangles)( void );\n\tvoid\t(*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity );\n\tvoid\t(*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed );\n\tvoid\t(*pfnShutdown)( void );\n\tvoid\t(*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client );\n\tvoid\t(*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src );\n\tvoid\t(*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd );\n\tvoid\t(*pfnDemo_ReadBuffer)( int size, byte *buffer );\n\tint\t(*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size );\n\tint\t(*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs );\n\tvoid\t(*pfnFrame)( double time );\n\tint\t(*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding );\n\tvoid\t(*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ));\n\tcl_entity_t *(*pfnGetUserEntity)( int index );\n\tvoid\t(*pfnVoiceStatus)( int entindex, qboolean bTalking );\n\tvoid\t(*pfnDirectorMessage)( int iSize, void *pbuf );\n\tint\t(*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio );\n\tvoid\t(*pfnChatInputPosition)( int *x, int *y );\n\t// Xash3D extension\n\tint\t(*pfnGetRenderInterface)( int version, render_api_t *renderfuncs, render_interface_t *callback );\n\tvoid\t(*pfnClipMoveToEntity)( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr );\n\t// Xash3D FWGS extension\n\tint (*pfnTouchEvent)( int type, int fingerID, float x, float y, float dx, float dy );\n\tvoid (*pfnMoveEvent)( float forwardmove, float sidemove );\n\tvoid (*pfnLookEvent)( float relyaw, float relpitch );\n} cldll_func_t;\n\n#endif//CDLL_EXP_H\n"
  },
  {
    "path": "engine/cdll_int.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n//\n//  cdll_int.h\n//\n// 4-23-98\n// JOHN:  client dll interface declarations\n//\n\n#ifndef CDLL_INT_H\n#define CDLL_INT_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"const.h\"\n#include <stdint.h>\n\n#define MAX_ALIAS_NAME\t32\n\ntypedef struct cmdalias_s\n{\n\tstruct cmdalias_s\t*next;\n\tchar\t\tname[MAX_ALIAS_NAME];\n\tchar\t\t*value;\n} cmdalias_t;\n\n// this file is included by both the engine and the client-dll,\n// so make sure engine declarations aren't done twice\n\ntypedef int HSPRITE;\t// handle to a graphic\ntypedef int (*pfnUserMsgHook)( const char *pszName, int iSize, void *pbuf );\n\n#include \"wrect.h\"\n\n#define SCRINFO_SCREENFLASH\t1\n#define SCRINFO_STRETCHED\t2\n\ntypedef struct SCREENINFO_s\n{\n\tint\t\tiSize;\n\tint\t\tiWidth;\n\tint\t\tiHeight;\n\tint\t\tiFlags;\n\tint\t\tiCharHeight;\n\tshort\t\tcharWidths[256];\n} SCREENINFO;\n\ntypedef struct client_data_s\n{\n\t// fields that cannot be modified  (ie. have no effect if changed)\n\tvec3_t\t\torigin;\n\n\t// fields that can be changed by the cldll\n\tvec3_t\t\tviewangles;\n\tint\t\tiWeaponBits;\n\tfloat\t\tfov;\t\t// field of view\n} client_data_t;\n\ntypedef struct client_sprite_s\n{\n\tchar\t\tszName[64];\n\tchar\t\tszSprite[64];\n\tint\t\thspr;\n\tint\t\tiRes;\n\twrect_t\t\trc;\n} client_sprite_t;\n\ntypedef struct client_textmessage_s\n{\n\tint\t\teffect;\n\tbyte\t\tr1, g1, b1, a1;\t// 2 colors for effects\n\tbyte\t\tr2, g2, b2, a2;\n\tfloat\t\tx;\n\tfloat\t\ty;\n\tfloat\t\tfadein;\n\tfloat\t\tfadeout;\n\tfloat\t\tholdtime;\n\tfloat\t\tfxtime;\n\tconst char\t*pName;\n\tconst char\t*pMessage;\n} client_textmessage_t;\n\ntypedef struct hud_player_info_s\n{\n\tchar\t\t*name;\n\tshort\t\tping;\n\tbyte\t\tthisplayer;\t// TRUE if this is the calling player\n\n\t// stuff that's unused at the moment,  but should be done\n\tbyte\t\tspectator;\n\tbyte\t\tpacketloss;\n\tchar\t\t*model;\n\tshort\t\ttopcolor;\n\tshort\t\tbottomcolor;\n\n\tuint64_t\tm_nSteamID;\n} hud_player_info_t;\n\nstruct screenfade_s;\nstruct tagPOINT;\nstruct event_args_s;\n\ntypedef struct cl_enginefuncs_s\n{\n\t// sprite handlers\n\tHSPRITE\t(*pfnSPR_Load)( const char *szPicName );\n\tint\t(*pfnSPR_Frames)( HSPRITE hPic );\n\tint\t(*pfnSPR_Height)( HSPRITE hPic, int frame );\n\tint\t(*pfnSPR_Width)( HSPRITE hPic, int frame );\n\tvoid\t(*pfnSPR_Set)( HSPRITE hPic, int r, int g, int b );\n\tvoid\t(*pfnSPR_Draw)( int frame, int x, int y, const wrect_t *prc );\n\tvoid\t(*pfnSPR_DrawHoles)( int frame, int x, int y, const wrect_t *prc );\n\tvoid\t(*pfnSPR_DrawAdditive)( int frame, int x, int y, const wrect_t *prc );\n\tvoid\t(*pfnSPR_EnableScissor)( int x, int y, int width, int height );\n\tvoid\t(*pfnSPR_DisableScissor)( void );\n\tclient_sprite_t *(*pfnSPR_GetList)( char *psz, int *piCount );\n\n\t// screen handlers\n\tvoid\t(*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a );\n\tint\t(*pfnGetScreenInfo)( SCREENINFO *pscrinfo );\n\tvoid\t(*pfnSetCrosshair)( HSPRITE hspr, wrect_t rc, int r, int g, int b );\n\n\t// cvar handlers\n\tstruct cvar_s *(*pfnRegisterVariable)( const char *szName, const char *szValue, int flags );\n\tfloat\t(*pfnGetCvarFloat)( const char *szName );\n\tconst char*\t(*pfnGetCvarString)( const char *szName );\n\n\t// command handlers\n\tint\t(*pfnAddCommand)( const char *cmd_name, void (*function)(void) );\n\tint\t(*pfnHookUserMsg)( const char *szMsgName, pfnUserMsgHook pfn );\n\tint\t(*pfnServerCmd)( const char *szCmdString );\n\tint\t(*pfnClientCmd)( const char *szCmdString );\n\n\tvoid\t(*pfnGetPlayerInfo)( int ent_num, hud_player_info_t *pinfo );\n\n\t// sound handlers\n\tvoid\t(*pfnPlaySoundByName)( const char *szSound, float volume );\n\tvoid\t(*pfnPlaySoundByIndex)( int iSound, float volume );\n\n\t// vector helpers\n\tvoid\t(*pfnAngleVectors)( const float *vecAngles, float *forward, float *right, float *up );\n\n\t// text message system\n\tclient_textmessage_t *(*pfnTextMessageGet)( const char *pName );\n\tint\t(*pfnDrawCharacter)( int x, int y, int number, int r, int g, int b );\n\tint\t(*pfnDrawConsoleString)( int x, int y, char *string );\n\tvoid\t(*pfnDrawSetTextColor)( float r, float g, float b );\n\tvoid\t(*pfnDrawConsoleStringLen)(  const char *string, int *length, int *height );\n\n\tvoid\t(*pfnConsolePrint)( const char *string );\n\tvoid\t(*pfnCenterPrint)( const char *string );\n\n\t// Added for user input processing\n\tint\t(*GetWindowCenterX)( void );\n\tint\t(*GetWindowCenterY)( void );\n\tvoid\t(*GetViewAngles)( float * );\n\tvoid\t(*SetViewAngles)( float * );\n\tint\t(*GetMaxClients)( void );\n\tvoid\t(*Cvar_SetValue)( const char *cvar, float value );\n\n\tint   (*Cmd_Argc)( void );\n\tconst char\t*(*Cmd_Argv)( int arg );\n\tvoid\t(*Con_Printf)( const char *fmt, ... );\n\tvoid\t(*Con_DPrintf)( const char *fmt, ... );\n\tvoid\t(*Con_NPrintf)( int pos, const char *fmt, ... );\n\tvoid\t(*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... );\n\n\tconst char* (*PhysInfo_ValueForKey)( const char *key );\n\tconst char* (*ServerInfo_ValueForKey)( const char *key );\n\tfloat\t(*GetClientMaxspeed)( void );\n\tint\t(*CheckParm)( char *parm, char **ppnext );\n\n\tvoid\t(*Key_Event)( int key, int down );\n\tvoid\t(*GetMousePosition)( int *mx, int *my );\n\tint\t(*IsNoClipping)( void );\n\n\tstruct cl_entity_s *(*GetLocalPlayer)( void );\n\tstruct cl_entity_s *(*GetViewModel)( void );\n\tstruct cl_entity_s *(*GetEntityByIndex)( int idx );\n\n\tfloat\t(*GetClientTime)( void );\n\tvoid\t(*V_CalcShake)( void );\n\tvoid\t(*V_ApplyShake)( float *origin, float *angles, float factor );\n\n\tint\t(*PM_PointContents)( const float *point, int *truecontents );\n\tint\t(*PM_WaterEntity)( const float *p );\n\tstruct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe );\n\n\tstruct model_s *(*CL_LoadModel)( const char *modelname, int *index );\n\tint\t(*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent );\n\n\tconst struct model_s* (*GetSpritePointer)( HSPRITE hSprite );\n\tvoid\t(*pfnPlaySoundByNameAtLocation)( char *szSound, float volume, float *origin );\n\n\tunsigned short (*pfnPrecacheEvent)( int type, const char* psz );\n\tvoid\t(*pfnPlaybackEvent)( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\n\tvoid\t(*pfnWeaponAnim)( int iAnim, int body );\n\tfloat\t(*pfnRandomFloat)( float flLow, float flHigh );\n\tint\t(*pfnRandomLong)( int lLow, int lHigh );\n\tvoid\t(*pfnHookEvent)( const char *name, void ( *pfnEvent )( struct event_args_s *args ));\n\n\tint\t(*Con_IsVisible) ( void );\n\tconst char *(*pfnGetGameDirectory)( void );\n\tstruct cvar_s *(*pfnGetCvarPointer)( const char *szName );\n\tconst char *(*Key_LookupBinding)( const char *pBinding );\n\tconst char *(*pfnGetLevelName)( void );\n\tvoid\t(*pfnGetScreenFade)( struct screenfade_s *fade );\n\tvoid\t(*pfnSetScreenFade)( struct screenfade_s *fade );\n\tvoid*\t(*VGui_GetPanel)( void );\n\tvoid\t(*VGui_ViewportPaintBackground)( int extents[4] );\n\n\tbyte*\t(*COM_LoadFile)( const char *path, int usehunk, int *pLength );\n\tchar*\t(*COM_ParseFile)( char *data, char *token );\n\tvoid\t(*COM_FreeFile)( void *buffer );\n\n\tstruct triangleapi_s\t*pTriAPI;\n\tstruct efx_api_s\t\t*pEfxAPI;\n\tstruct event_api_s\t\t*pEventAPI;\n\tstruct demo_api_s\t\t*pDemoAPI;\n\tstruct net_api_s\t\t*pNetAPI;\n\tstruct IVoiceTweak_s\t*pVoiceTweak;\n\n\t// returns 1 if the client is a spectator only (connected to a proxy), 0 otherwise or 2 if in dev_overview mode\n\tint\t(*IsSpectateOnly)( void );\n\tstruct model_s *(*LoadMapSprite)( const char *filename );\n\n\t// file search functions\n\tvoid\t (*COM_AddAppDirectoryToSearchPath)( const char *pszBaseDir, const char *appName );\n\tint\t (*COM_ExpandFilename)( const char *fileName, char *nameOutBuffer, int nameOutBufferSize );\n\n\t// User info\n\t// playerNum is in the range (1, MaxClients)\n\t// returns NULL if player doesn't exit\n\t// returns \"\" if no value is set\n\tconst char *( *PlayerInfo_ValueForKey )( int playerNum, const char *key );\n\tvoid\t(*PlayerInfo_SetValueForKey )( const char *key, const char *value );\n\n\t// Gets a unique ID for the specified player. This is the same even if you see the player on a different server.\n\t// iPlayer is an entity index, so client 0 would use iPlayer=1.\n\t// Returns false if there is no player on the server in the specified slot.\n\tqboolean\t(*GetPlayerUniqueID)(int iPlayer, char playerID[16]);\n\n\t// TrackerID access\n\tint\t(*GetTrackerIDForPlayer)(int playerSlot);\n\tint\t(*GetPlayerForTrackerID)(int trackerID);\n\n\t// Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream\n\t// (but it might not get there).\n\tint\t( *pfnServerCmdUnreliable )( char *szCmdString );\n\n\tvoid\t(*pfnGetMousePos)( struct tagPOINT *ppt );\n\tvoid\t(*pfnSetMousePos)( int x, int y );\n\tvoid\t(*pfnSetMouseEnable)( qboolean fEnable );\n\n\t// undocumented interface starts here\n\tstruct cvar_s*\t(*pfnGetFirstCvarPtr)( void );\n\tvoid*\t\t(*pfnGetFirstCmdFunctionHandle)( void );\n\tvoid*\t\t(*pfnGetNextCmdFunctionHandle)( void *cmdhandle );\n\tconst char*\t(*pfnGetCmdFunctionName)( void *cmdhandle );\n\tfloat\t\t(*pfnGetClientOldTime)( void );\n\tfloat\t\t(*pfnGetGravity)( void );\n\tstruct model_s*\t(*pfnGetModelByIndex)( int index );\n\tvoid\t\t(*pfnSetFilterMode)( int mode ); // same as gl_texsort in original Quake\n\tvoid\t\t(*pfnSetFilterColor)( float red, float green, float blue );\n\tvoid\t\t(*pfnSetFilterBrightness)( float brightness );\n\tvoid\t\t*(*pfnSequenceGet)( const char *fileName, const char *entryName );\n\tvoid\t\t(*pfnSPR_DrawGeneric)( int frame, int x, int y, const wrect_t *prc, int blendsrc, int blenddst, int width, int height );\n\tvoid\t\t*(*pfnSequencePickSentence)( const char *groupName, int pickMethod, int *entryPicked );\n\tint\t\t(*pfnDrawString)( int x, int y, const char *str, int r, int g, int b );\n\tint\t\t(*pfnDrawStringReverse)( int x, int y, const char *str, int r, int g, int b );\n\tconst char\t*(*LocalPlayerInfo_ValueForKey)( const char* key );\n\tint\t\t(*pfnVGUI2DrawCharacter)( int x, int y, int ch, unsigned int font );\n\tint\t\t(*pfnVGUI2DrawCharacterAdditive)( int x, int y, int ch, int r, int g, int b, unsigned int font );\n\tunsigned int\t(*pfnGetApproxWavePlayLen)( const char *filename );\n\tvoid*\t\t(*GetCareerGameUI)( void );\t// g-cont. !!!! potential crash-point!\n\tvoid\t\t(*Cvar_Set)( const char *name, const char *value );\n\tint\t\t(*pfnIsPlayingCareerMatch)( void );\n\tvoid\t\t(*pfnPlaySoundVoiceByName)( char *szSound, float volume, int pitch );\n\tvoid\t\t(*pfnPrimeMusicStream)( char *filename, int looping );\n\tdouble\t\t(*pfnSys_FloatTime)( void );\n\n\t// decay funcs\n\tvoid\t\t(*pfnProcessTutorMessageDecayBuffer)( int *buffer, int buflen );\n\tvoid\t\t(*pfnConstructTutorMessageDecayBuffer)( int *buffer, int buflen );\n\tvoid\t\t(*pfnResetTutorMessageDecayData)( void );\n\n\tvoid\t\t(*pfnPlaySoundByNameAtPitch)( char *szSound, float volume, int pitch );\n\tvoid\t\t(*pfnFillRGBABlend)( int x, int y, int width, int height, int r, int g, int b, int a );\n\tint\t\t(*pfnGetAppID)( void );\n\tcmdalias_t\t*(*pfnGetAliases)( void );\n\tvoid\t\t(*pfnVguiWrap2_GetMouseDelta)( int *x, int *y );\n\n\t// added in 2019 update, not documented yet\n\tint\t\t(*pfnFilteredClientCmd)( const char *cmd );\n} cl_enginefunc_t;\n\n#define CLDLL_INTERFACE_VERSION\t7\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif//CDLL_INT_H\n"
  },
  {
    "path": "engine/client/avi/avi.h",
    "content": "/*\navi.h -- common avi support header\nCopyright (C) 2018 a1batross, Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef AVI_H\n#define AVI_H\n\n//\n// avikit.c\n//\ntypedef struct movie_state_s movie_state_t;\nint AVI_GetVideoFrameNumber( movie_state_t *Avi, float time );\nbyte *AVI_GetVideoFrame( movie_state_t *Avi, int frame );\nqboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration );\nqboolean AVI_HaveAudioTrack( const movie_state_t *Avi );\nvoid AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet );\nmovie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio );\nint AVI_TimeToSoundPosition( movie_state_t *Avi, int time );\nvoid AVI_CloseVideo( movie_state_t *Avi );\nqboolean AVI_IsActive( movie_state_t *Avi );\nvoid AVI_FreeVideo( movie_state_t *Avi );\nmovie_state_t *AVI_GetState( int num );\nqboolean AVI_Initailize( void );\nvoid AVI_Shutdown( void );\n\nqboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... );\nqboolean AVI_Think( movie_state_t *Avi );\n\n#endif // AVI_H\n"
  },
  {
    "path": "engine/client/avi/avi_ffmpeg.c",
    "content": "/*\navi_ffmpreg.c - playing AVI files (ffmpeg backend)\nCopyright (C) FTEQW developers (for plugins/avplug/avdecode.c)\nCopyright (C) Sam Lantinga (for tests/testffmpeg.c)\nCopyright (C) 2023-2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"defaults.h\"\n#include \"common.h\"\n#include \"client.h\"\n\nstatic qboolean avi_initialized;\nstatic poolhandle_t avi_mempool;\n\n#if XASH_AVI == AVI_FFMPEG\n#define XASH_FFMPEG_DLOPEN 1\n#include \"avi_ffmpeg.h\"\n\nstruct movie_state_s\n{\n\t// ffmpeg contexts\n\tAVFormatContext *fmt_ctx;\n\tAVCodecContext *video_ctx;\n\tAVCodecContext *audio_ctx;\n\tstruct SwsContext *sws_ctx;\n\tstruct SwrContext *swr_ctx;\n\n\tAVPacket *pkt;\n\tAVFrame *aframe;\n\tAVFrame *vframe;\n\tAVFrame *vframe_copy;\n\n\tint64_t first_time;\n\tint64_t last_time;\n\n\t// video stream\n\tbyte *dst;\n\tdouble duration;\n\tint video_stream;\n\tint xres;\n\tint yres;\n\tint dst_linesize;\n\tenum AVPixelFormat pix_fmt;\n\n\t// rendering video parameters\n\tint x, y, w, h; // passed to R_DrawStretchRaw\n\tint texture; // passed to R_UploadStretchRaw\n\n\t// audio stream\n\tint audio_stream;\n\tint channels;\n\tint rate;\n\tenum AVSampleFormat s_fmt;\n\n\tbyte *cached_audio;\n\tsize_t cached_audio_buf_len; // absolute size of cached_audio array\n\tsize_t cached_audio_len; // how many data in bytes we have in cached_audio array\n\tsize_t cached_audio_pos; // how far we've read into cached_audio array\n\n\t// rendering audio parameters\n\tfloat attn;\n\tint16_t entnum; // MAX_ENTITY_BITS is 13\n\tbyte volume;\n\tbyte active : 1;\n\tbyte quiet  : 1;\n\tbyte paused : 1;\n};\n\nqboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... )\n{\n\tqboolean ret = true;\n\tva_list va;\n\tva_start( va, parm );\n\n\twhile( parm != AVI_PARM_LAST )\n\t{\n\t\tfloat fval;\n\t\tint val;\n\n\t\tswitch( parm )\n\t\t{\n\t\tcase AVI_RENDER_TEXNUM:\n\t\t\tAvi->texture = va_arg( va, int );\n\t\t\tbreak;\n\t\tcase AVI_RENDER_X:\n\t\t\tAvi->x = va_arg( va, int );\n\t\t\tbreak;\n\t\tcase AVI_RENDER_Y:\n\t\t\tAvi->y = va_arg( va, int );\n\t\t\tbreak;\n\t\tcase AVI_RENDER_W:\n\t\t\tAvi->w = va_arg( va, int );\n\t\t\tbreak;\n\t\tcase AVI_RENDER_H:\n\t\t\tAvi->h = va_arg( va, int );\n\t\t\tbreak;\n\t\tcase AVI_REWIND:\n\t\t\tif( Avi->audio_ctx )\n\t\t\t\tpavcodec_flush_buffers( Avi->audio_ctx );\n\t\t\tpavcodec_flush_buffers( Avi->video_ctx );\n\t\t\tAvi->cached_audio_len = Avi->cached_audio_pos = 0;\n\t\t\tAvi->last_time = -1;\n\t\t\tAvi->first_time = 0;\n\t\t\tpav_seek_frame( Avi->fmt_ctx, -1, 0, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD );\n\t\t\tbreak;\n\t\tcase AVI_ENTNUM:\n\t\t\tval = va_arg( va, int );\n\t\t\tAvi->entnum = bound( 0, val, MAX_EDICTS );\n\t\t\tbreak;\n\t\tcase AVI_VOLUME:\n\t\t\tval = va_arg( va, int );\n\t\t\tAvi->volume = bound( 0, val, 255 );\n\t\t\tbreak;\n\t\tcase AVI_ATTN:\n\t\t\tfval = va_arg( va, double );\n\t\t\tAvi->attn = Q_max( 0.0f, fval );\n\t\t\tbreak;\n\t\tcase AVI_PAUSE:\n\t\t\tAvi->paused = true;\n\t\t\tbreak;\n\t\tcase AVI_RESUME:\n\t\t\tAvi->paused = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tret = false;\n\t\t}\n\n\t\tparm = va_arg( va, enum movie_parms_e );\n\t}\n\n\tva_end( va );\n\n\treturn ret;\n}\n\nstatic void AVI_SpewError( qboolean quiet, const char *fmt, ... ) FORMAT_CHECK( 2 );\nstatic void AVI_SpewError( qboolean quiet, const char *fmt, ... )\n{\n\tchar buf[MAX_VA_STRING];\n\tva_list va;\n\n\tif( quiet )\n\t\treturn;\n\n\tva_start( va, fmt );\n\tQ_vsnprintf( buf, sizeof( buf ), fmt, va );\n\tva_end( va );\n\n\tCon_Printf( S_ERROR \"%s\", buf );\n}\n\nstatic void AVI_SpewAvError( qboolean quiet, const char *func, int numerr )\n{\n\tif( !quiet )\n\t{\n\t\tchar errstr[AV_ERROR_MAX_STRING_SIZE];\n\t\tpav_strerror( numerr, errstr, sizeof( errstr ));\n\t\tCon_Printf( S_ERROR \"%s: %s (%d)\\n\", func, errstr, numerr );\n\t}\n}\n\nstatic int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type, qboolean quiet )\n{\n\tconst AVCodec *dec;\n\tAVCodecContext *dec_ctx;\n\tAVStream *st;\n\tint idx, ret;\n\n\tif(( ret = pav_find_best_stream( fmt_ctx, type, -1, -1, NULL, 0 )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"av_find_best_stream\", ret );\n\t\treturn ret;\n\t}\n\n\tidx = ret;\n\tst = fmt_ctx->streams[idx];\n\n\tif( !( dec = pavcodec_find_decoder( st->codecpar->codec_id )))\n\t{\n\t\tAVI_SpewError( quiet, S_ERROR \"Failed to find %s codec\\n\", pav_get_media_type_string( type ));\n\t\treturn AVERROR( EINVAL );\n\t}\n\n\tif( !( dec_ctx = pavcodec_alloc_context3( dec )))\n\t{\n\t\tAVI_SpewError( quiet, S_ERROR \"Failed to allocate %s codec context\", dec->name );\n\t\treturn AVERROR( ENOMEM );\n\t}\n\n\tif(( ret = pavcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"avcodec_parameters_to_context\", ret );\n\t\tpavcodec_free_context( &dec_ctx );\n\t\treturn ret;\n\t}\n\n\tdec_ctx->pkt_timebase = st->time_base;\n\n\tif(( ret = pavcodec_open2( dec_ctx, dec, NULL )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"avcodec_open2\", ret );\n\t\tpavcodec_free_context( &dec_ctx );\n\t\treturn ret;\n\t}\n\n\t*dst_dec_ctx = dec_ctx;\n\treturn idx; // always positive\n}\n\nint AVI_GetVideoFrameNumber( movie_state_t *Avi, float time )\n{\n\treturn 0;\n}\n\nint AVI_TimeToSoundPosition( movie_state_t *Avi, int time )\n{\n\treturn 0;\n}\n\nqboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration )\n{\n\tif( !Avi->active )\n\t\treturn false;\n\n\tif( xres )\n\t\t*xres = Avi->xres;\n\n\tif( yres )\n\t\t*yres = Avi->yres;\n\n\tif( duration )\n\t\t*duration = Avi->duration;\n\n\treturn true;\n}\n\nqboolean AVI_HaveAudioTrack( const movie_state_t *Avi )\n{\n\treturn Avi ? Avi->active && Avi->audio_ctx : false;\n}\n\n// just let it compile, bruh!\nbyte *AVI_GetVideoFrame( movie_state_t *Avi, int target )\n{\n\treturn Avi->dst;\n}\n\nstatic void AVI_StreamAudio( movie_state_t *Avi )\n{\n\tint buffer_samples, file_samples, file_bytes;\n\trawchan_t *ch = NULL;\n\n\t// keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled\n\tqboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !s_listener.streaming : false;\n\n\tif( !dma.initialized || disable_stream || s_listener.paused || !Avi->cached_audio )\n\t\treturn;\n\n\tch = S_FindRawChannel( Avi->entnum, true );\n\n\tif( !ch )\n\t\treturn;\n\n\tch->master_vol = Avi->volume;\n\tch->dist_mult = (Avi->attn / SND_CLIP_DISTANCE);\n\n\tif( ch->s_rawend < soundtime )\n\t\tch->s_rawend = soundtime;\n\n\twhile( ch->s_rawend < soundtime + ch->max_samples )\n\t{\n\t\tsize_t copy;\n\n\t\tbuffer_samples = ch->max_samples - (ch->s_rawend - soundtime);\n\n\t\tfile_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED);\n\t\tif( file_samples <= 1 ) return; // no more samples need\n\n\t\tfile_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;\n\n\t\tif( file_bytes > ch->max_samples )\n\t\t{\n\t\t\tfile_bytes = ch->max_samples;\n\t\t\tfile_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels );\n\t\t}\n\n\t\tcopy = Q_min( file_bytes, Q_max( Avi->cached_audio_len - Avi->cached_audio_pos, 0 ));\n\n\t\tif( !copy )\n\t\t\tbreak;\n\n\t\tif( file_bytes > copy )\n\t\t{\n\t\t\tfile_bytes = copy;\n\t\t\tfile_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels );\n\t\t}\n\n\t\tch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, file_samples, Avi->rate, pav_get_bytes_per_sample( Avi->s_fmt ), Avi->channels, Avi->cached_audio + Avi->cached_audio_pos );\n\t\tAvi->cached_audio_pos += copy;\n\t}\n}\n\nstatic void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )\n{\n\tint samples = frame->nb_samples;\n\tsize_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;\n\tint outsamples;\n\tuint8_t *ptr;\n\n\t// allocate data\n\tif( !Avi->cached_audio )\n\t{\n\t\tAvi->cached_audio_buf_len = len;\n\t\tAvi->cached_audio_pos = 0;\n\t\tAvi->cached_audio_len = 0;\n\t\tAvi->cached_audio = Mem_Malloc( avi_mempool, len );\n\t}\n\telse\n\t{\n\t\tif( Avi->cached_audio_pos )\n\t\t{\n\t\t\t// Con_Printf( \"%s: erasing old data of size %d\\n\", __func__, Avi->cached_audio_pos );\n\t\t\tAvi->cached_audio_len -= Avi->cached_audio_pos;\n\t\t\tmemmove( Avi->cached_audio, Avi->cached_audio + Avi->cached_audio_pos, Avi->cached_audio_len );\n\t\t\tAvi->cached_audio_pos = 0;\n\t\t}\n\n\t\tif( len + Avi->cached_audio_len > Avi->cached_audio_buf_len )\n\t\t{\n\t\t\t// Con_Printf( \"%s: resizing old buffer of size %d to size %d\\n\", __func__, Avi->cached_audio_buf_len, len + Avi->cached_audio_buf_len );\n\t\t\tAvi->cached_audio_buf_len = len + Avi->cached_audio_len;\n\t\t\tAvi->cached_audio = Mem_Realloc( avi_mempool, Avi->cached_audio, Avi->cached_audio_buf_len );\n\t\t}\n\t}\n\n\tptr = Avi->cached_audio + Avi->cached_audio_len;\n\toutsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples );\n\tAvi->cached_audio_len += outsamples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;\n\n\t// Con_Printf( \"%s: got audio chunk of size %d samples\\n\", __func__, outsamples );\n}\n\nqboolean AVI_Think( movie_state_t *Avi )\n{\n\tqboolean decoded = false;\n\tqboolean flushing = false;\n\tqboolean redraw = false;\n\tconst double timebase = (double)Avi->video_ctx->pkt_timebase.den / Avi->video_ctx->pkt_timebase.num;\n\tint64_t curtime = round( Platform_DoubleTime() * timebase );\n\n\tif( !Avi->first_time ) // always remember at which timestamp we started playing\n\t\tAvi->first_time = curtime;\n\n\tif( Avi->paused )\n\t{\n\t\t// FIXME: there might be a better way to do this\n\t\tAvi->last_time = curtime;\n\t\treturn true;\n\t}\n\n\t// Con_NPrintf( 1, \"cached_audio_buf_len = %zu\", Avi->cached_audio_buf_len );\n\n\twhile( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps\n\t{\n\t\tint res;\n\n\t\tAVI_StreamAudio( Avi ); // always flush audio buffers\n\n\t\t// recalc time so we always play last possible frame\n\t\tcurtime = round( Platform_DoubleTime() * timebase );\n\n\t\tif( Avi->last_time > curtime )\n\t\t\tbreak;\n\n\t\tif(( res = pav_read_frame( Avi->fmt_ctx, Avi->pkt )) >= 0 )\n\t\t{\n\t\t\tif( Avi->pkt->stream_index == Avi->audio_stream )\n\t\t\t{\n\t\t\t\tres = pavcodec_send_packet( Avi->audio_ctx, Avi->pkt );\n\t\t\t\tif( res < 0 )\n\t\t\t\t\tAVI_SpewAvError( Avi->quiet, \"avcodec_send_packet (audio)\", res );\n\t\t\t}\n\t\t\telse if( Avi->pkt->stream_index == Avi->video_stream )\n\t\t\t{\n\t\t\t\tres = pavcodec_send_packet( Avi->video_ctx, Avi->pkt );\n\t\t\t\tif( res < 0 )\n\t\t\t\t\tAVI_SpewAvError( Avi->quiet, \"avcodec_send_packet (video)\", res );\n\t\t\t}\n\t\t\tpav_packet_unref( Avi->pkt );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( res != AVERROR_EOF )\n\t\t\t\tAVI_SpewAvError( Avi->quiet, \"av_read_frame\", res );\n\n\t\t\tif( Avi->audio_ctx )\n\t\t\t\tpavcodec_flush_buffers( Avi->audio_ctx );\n\n\t\t\tpavcodec_flush_buffers( Avi->video_ctx );\n\t\t\tflushing = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tif( Avi->audio_ctx )\n\t\t{\n\t\t\twhile( pavcodec_receive_frame( Avi->audio_ctx, Avi->aframe ) == 0 )\n\t\t\t{\n\t\t\t\tAVI_HandleAudio( Avi, Avi->aframe );\n\t\t\t\tdecoded = true;\n\t\t\t}\n\t\t}\n\n\t\twhile( pavcodec_receive_frame( Avi->video_ctx, Avi->vframe ) == 0 )\n\t\t{\n\t\t\tAvi->last_time = Avi->first_time + Avi->vframe->best_effort_timestamp;\n\t\t\tdecoded = true;\n\n\t\t\tif( FBitSet( Avi->vframe->flags, AV_FRAME_FLAG_CORRUPT|AV_FRAME_FLAG_DISCARD ))\n\t\t\t\tcontinue;\n\n\t\t\tif( Avi->vframe->decode_error_flags != 0 )\n\t\t\t\tcontinue;\n\n\t\t\tpav_frame_unref( Avi->vframe_copy );\n\t\t\tif( pav_frame_ref( Avi->vframe_copy, Avi->vframe ) == 0 )\n\t\t\t\tredraw = true;\n\t\t}\n\t}\n\n\tif( redraw )\n\t{\n\t\tpsws_scale( Avi->sws_ctx, (void*)Avi->vframe_copy->data, Avi->vframe_copy->linesize, 0, Avi->video_ctx->height,\n\t\t\t&Avi->dst, &Avi->dst_linesize );\n\t\tpav_frame_unref( Avi->vframe_copy );\n\t}\n\n\tif( Avi->texture == 0 )\n\t{\n\t\tint w = Avi->w >= 0 ? Avi->w : refState.width;\n\t\tint h = Avi->h >= 0 ? Avi->h : refState.height;\n\n\t\tref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw );\n\t}\n\telse if( redraw && Avi->texture > 0 )\n\t\tref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );\n\n\tif( flushing && !decoded )\n\t\treturn false; // probably hit an EOF\n\n\treturn true;\n}\n\nvoid AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet )\n{\n\tbyte *dst[4];\n\tint dst_linesize[4];\n\tint ret;\n\n\tif( Avi->active )\n\t\tAVI_CloseVideo( Avi );\n\n\tif( !filename || !avi_initialized )\n\t\treturn;\n\n\tAvi->active = false;\n\tAvi->quiet = quiet;\n\tAvi->video_ctx = Avi->audio_ctx = NULL;\n\tAvi->fmt_ctx = NULL;\n\n\tif(( ret = pavformat_open_input( &Avi->fmt_ctx, filename, NULL, NULL )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"avformat_open_input\", ret );\n\t\treturn;\n\t}\n\n\tif(( ret = pavformat_find_stream_info( Avi->fmt_ctx, NULL )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"avformat_find_stream_info\", ret );\n\t\treturn;\n\t}\n\n\tif( !( Avi->pkt = pav_packet_alloc( )))\n\t{\n\t\tAVI_SpewAvError( quiet, \"av_packet_alloc\", 0 );\n\t\treturn;\n\t}\n\n\tif( !( Avi->vframe = pav_frame_alloc( )))\n\t{\n\t\tAVI_SpewAvError( quiet, \"av_frame_alloc (video)\", 0 );\n\t\treturn;\n\t}\n\n\tif( !( Avi->vframe_copy = pav_frame_alloc( )))\n\t{\n\t\tAVI_SpewAvError( quiet, \"av_frame_alloc (video)\", 0 );\n\t\treturn;\n\t}\n\n\n\tAvi->video_stream = AVI_OpenCodecContext( &Avi->video_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_VIDEO, quiet );\n\n\tif( Avi->video_stream < 0 )\n\t\treturn;\n\n\tAvi->xres     = Avi->video_ctx->width;\n\tAvi->yres     = Avi->video_ctx->height;\n\tAvi->pix_fmt  = Avi->video_ctx->pix_fmt;\n\tAvi->duration = Avi->fmt_ctx->duration / (double)AV_TIME_BASE;\n\tAvi->entnum   = S_RAW_SOUND_SOUNDTRACK;\n\tAvi->attn     = ATTN_NONE;\n\tAvi->volume   = 255;\n\n\tif( !( Avi->sws_ctx = psws_getContext( Avi->xres, Avi->yres, Avi->pix_fmt,\n\t\tAvi->xres, Avi->yres, AV_PIX_FMT_BGR0, SWS_POINT, NULL, NULL, NULL )))\n\t{\n\t\tAVI_SpewAvError( quiet, \"sws_getContext\", 0 );\n\t\treturn;\n\t}\n\n\tif(( ret = pav_image_alloc( dst, dst_linesize, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, 1 )) < 0 )\n\t{\n\t\tAVI_SpewAvError( quiet, \"av_image_alloc\", ret );\n\t\treturn;\n\t}\n\n\tAvi->dst = dst[0];\n\tAvi->dst_linesize = dst_linesize[0];\n\n\tif( load_audio )\n\t{\n\t\tif( !( Avi->aframe = pav_frame_alloc( )))\n\t\t{\n\t\t\tAVI_SpewAvError( quiet, \"av_frame_alloc (audio)\", 0 );\n\t\t\treturn;\n\t\t}\n\n\t\tAvi->audio_stream = AVI_OpenCodecContext( &Avi->audio_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_AUDIO, quiet );\n\n\t\t// audio stream was requested but it wasn't found\n\t\tif( Avi->audio_stream < 0 )\n\t\t\treturn;\n\n\t\tAvi->channels = Q_min( Avi->audio_ctx->ch_layout.nb_channels, 2 );\n\t\tif( Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8 || Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8P )\n\t\t\tAvi->s_fmt = AV_SAMPLE_FMT_U8;\n\t\telse Avi->s_fmt = AV_SAMPLE_FMT_S16;\n\t\tAvi->rate = Avi->audio_ctx->sample_rate;\n\n\t\tif(( ret = pswr_alloc_set_opts2( &Avi->swr_ctx, &Avi->audio_ctx->ch_layout, Avi->s_fmt, Avi->rate,\n\t\t\t&Avi->audio_ctx->ch_layout, Avi->audio_ctx->sample_fmt, Avi->audio_ctx->sample_rate, 0, 0 )) < 0 )\n\t\t{\n\t\t\tAVI_SpewAvError( quiet, \"swr_alloc_set_opts2\", ret );\n\t\t\treturn;\n\t\t}\n\n\t\tif(( ret = pswr_init( Avi->swr_ctx )) < 0 )\n\t\t{\n\t\t\tAVI_SpewAvError( quiet, \"swr_init\", ret );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tAvi->active = true;\n}\n\nvoid AVI_CloseVideo( movie_state_t *Avi )\n{\n\tif( Avi->active )\n\t{\n\t\tif( Avi->cached_audio )\n\t\t\tMem_Free( Avi->cached_audio );\n\n\t\tpswr_free( &Avi->swr_ctx );\n\t\tpavcodec_free_context( &Avi->audio_ctx );\n\t\tpav_frame_free( &Avi->aframe );\n\n\t\tpav_free( Avi->dst );\n\t\tpsws_freeContext( Avi->sws_ctx );\n\t\tpavcodec_free_context( &Avi->video_ctx );\n\t\tpav_frame_free( &Avi->vframe );\n\t\tpav_frame_free( &Avi->vframe_copy );\n\n\t\tpav_packet_free( &Avi->pkt );\n\n\t\tpavformat_close_input( &Avi->fmt_ctx );\n\t}\n\n\tmemset( Avi, 0, sizeof( *Avi ));\n}\n\n#if XASH_FFMPEG_DLOPEN\n#define F( x ) #x, (void **)&p##x\nstatic const dllfunc_t libavutil_funcs[] =\n{\n\t{ F( avutil_version ) },\n\t{ F( av_frame_alloc ) },\n\t{ F( av_frame_free ) },\n\t{ F( av_frame_ref ) },\n\t{ F( av_frame_unref ) },\n\t{ F( av_strerror ) },\n\t{ F( av_free ) },\n\t{ F( av_get_bytes_per_sample ) },\n\t{ F( av_get_media_type_string ) },\n\t{ F( av_image_alloc ) },\n};\n\nstatic const dllfunc_t libavformat_funcs[] =\n{\n\t{ F( avformat_version ) },\n\t{ F( av_find_best_stream ) },\n\t{ F( av_read_frame ) },\n\t{ F( av_seek_frame ) },\n\t{ F( avformat_close_input ) },\n\t{ F( avformat_find_stream_info ) },\n\t{ F( avformat_open_input ) },\n};\n\nstatic const dllfunc_t libavcodec_funcs[] =\n{\n\t{ F( avcodec_version ) },\n\t{ F( av_packet_alloc ) },\n\t{ F( av_packet_free ) },\n\t{ F( av_packet_unref ) },\n\t{ F( avcodec_alloc_context3 ) },\n\t{ F( avcodec_find_decoder ) },\n\t{ F( avcodec_flush_buffers ) },\n\t{ F( avcodec_free_context ) },\n\t{ F( avcodec_open2 ) },\n\t{ F( avcodec_parameters_to_context ) },\n\t{ F( avcodec_receive_frame ) },\n\t{ F( avcodec_send_packet ) },\n};\n\nstatic const dllfunc_t libswresample_funcs[] =\n{\n\t{ F( swresample_version ) },\n\t{ F( swr_alloc_set_opts2 ) },\n\t{ F( swr_convert ) },\n\t{ F( swr_free ) },\n\t{ F( swr_init ) },\n};\n\nstatic const dllfunc_t libswscale_funcs[] =\n{\n\t{ F( swscale_version ) },\n\t{ F( sws_freeContext ) },\n\t{ F( sws_getContext ) },\n\t{ F( sws_scale ) },\n};\n#undef F\n\n#define SS( x ) #x\n#define S( x ) SS( x )\n\nstatic dll_info_t libavutil_info =\n{\n#if XASH_WIN32\n\t.name = \"avutil-\" S( SUPPORTED_AVU_VERSION_MAJOR ) \".dll\",\n#else\n\t.name = \"libavutil.so.\" S( SUPPORTED_AVU_VERSION_MAJOR ),\n#endif\n\t.fcts = libavutil_funcs,\n\t.num_fcts = ARRAYSIZE( libavutil_funcs ),\n};\n\nstatic dll_info_t libavformat_info =\n{\n#if XASH_WIN32\n\t.name = \"avformat-\" S( SUPPORTED_AVF_VERSION_MAJOR ) \".dll\",\n#else\n\t.name = \"libavformat.so.\" S( SUPPORTED_AVF_VERSION_MAJOR ),\n#endif\n\t.fcts = libavformat_funcs,\n\t.num_fcts = ARRAYSIZE( libavformat_funcs ),\n};\n\nstatic dll_info_t libavcodec_info =\n{\n#if XASH_WIN32\n\t.name = \"avcodec-\" S( SUPPORTED_AVC_VERSION_MAJOR ) \".dll\",\n#else\n\t.name = \"libavcodec.so.\" S( SUPPORTED_AVC_VERSION_MAJOR ),\n#endif\n\t.fcts = libavcodec_funcs,\n\t.num_fcts = ARRAYSIZE( libavcodec_funcs ),\n};\n\nstatic dll_info_t libswresample_info =\n{\n#if XASH_WIN32\n\t.name = \"swresample-\" S( SUPPORTED_SWR_VERSION_MAJOR ) \".dll\",\n#else\n\t.name = \"libswresample.so.\" S( SUPPORTED_SWR_VERSION_MAJOR ),\n#endif\n\t.fcts = libswresample_funcs,\n\t.num_fcts = ARRAYSIZE( libswresample_funcs ),\n};\n\nstatic dll_info_t libswscale_info =\n{\n#if XASH_WIN32\n\t.name = \"swscale-\" S( SUPPORTED_SWS_VERSION_MAJOR ) \".dll\",\n#else\n\t.name = \"libswscale.so.\" S( SUPPORTED_SWS_VERSION_MAJOR ),\n#endif\n\t.fcts = libswscale_funcs,\n\t.num_fcts = ARRAYSIZE( libswscale_funcs ),\n};\n\nstatic qboolean AVI_LoadFFmpeg( void )\n{\n\tif( !Sys_LoadLibrary( &libavutil_info ))\n\t\treturn false;\n\n\tif( !Sys_LoadLibrary( &libavformat_info ))\n\t\treturn false;\n\n\tif( !Sys_LoadLibrary( &libavcodec_info ))\n\t\treturn false;\n\n\tif( !Sys_LoadLibrary( &libswresample_info ))\n\t\treturn false;\n\n\tif( !Sys_LoadLibrary( &libswscale_info ))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic void AVI_UnloadFFmpeg( void )\n{\n\tSys_FreeLibrary( &libavutil_info );\n\tSys_FreeLibrary( &libavformat_info );\n\tSys_FreeLibrary( &libavcodec_info );\n\tSys_FreeLibrary( &libswresample_info );\n\tSys_FreeLibrary( &libswscale_info );\n}\n\n#else\nstatic qboolean AVI_LoadFFmpeg( void )\n{\n\treturn true;\n}\n\nstatic void AVI_UnloadFFmpeg( void )\n{\n\n}\n#endif\n\nstatic qboolean AVI_ValidateFFmpegVersion( void )\n{\n\tuint ver;\n\n\t// print version we're compiled with and which version we're running with\n\tver = pavutil_version();\n\tCon_Reportf( \"AVI: %s (runtime %d.%d.%d)\\n\", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver ));\n\n\tif( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVU_VERSION_MAJOR )\n\t{\n\t\tCon_Printf( S_ERROR \"AVI: Unsupported libavutil version.\\n\" );\n\t\treturn false;\n\t}\n\n\tver = pavformat_version();\n\tCon_Reportf( \"AVI: %s (runtime %d.%d.%d)\\n\", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver ));\n\n\tif( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVF_VERSION_MAJOR )\n\t{\n\t\tCon_Printf( S_ERROR \"AVI: Unsupported libavformat version.\\n\" );\n\t\treturn false;\n\t}\n\n\tver = pavcodec_version();\n\tCon_Reportf( \"AVI: %s (runtime %d.%d.%d)\\n\", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver ));\n\n\tif( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVC_VERSION_MAJOR )\n\t{\n\t\tCon_Printf( S_ERROR \"AVI: Unsupported libavcodec version.\\n\" );\n\t\treturn false;\n\t}\n\n\tver = pswscale_version();\n\tCon_Reportf( \"AVI: %s (runtime %d.%d.%d)\\n\", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver ));\n\n\tif( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWS_VERSION_MAJOR )\n\t{\n\t\tCon_Printf( S_ERROR \"AVI: Unsupported libswscale version.\\n\" );\n\t\treturn false;\n\t}\n\n\tver = pswresample_version();\n\tCon_Reportf( \"AVI: %s (runtime %d.%d.%d)\\n\", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver ));\n\n\tif( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWR_VERSION_MAJOR )\n\t{\n\t\tCon_Printf( S_ERROR \"AVI: Unsupported libswresample version.\\n\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n#else\nstruct movie_state_s\n{\n\tqboolean active;\n};\n\nint AVI_GetVideoFrameNumber( movie_state_t *Avi, float time )\n{\n\treturn 0;\n}\n\nbyte *AVI_GetVideoFrame( movie_state_t *Avi, int frame )\n{\n\treturn NULL;\n}\n\nqboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration )\n{\n\treturn false;\n}\n\nqboolean AVI_HaveAudioTrack( const movie_state_t *Avi )\n{\n\treturn false;\n}\n\nvoid AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet )\n{\n\t;\n}\n\nint AVI_TimeToSoundPosition( movie_state_t *Avi, int time )\n{\n\treturn 0;\n}\n\nvoid AVI_CloseVideo( movie_state_t *Avi )\n{\n\t;\n}\n\nqboolean AVI_Think( movie_state_t *Avi )\n{\n\treturn false;\n}\n\nqboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... )\n{\n\treturn false;\n}\n\nstatic qboolean AVI_ValidateFFmpegVersion( void )\n{\n\treturn false;\n}\n\nstatic qboolean AVI_LoadFFmpeg( void )\n{\n\treturn false;\n}\n\nstatic void AVI_UnloadFFmpeg( void )\n{\n\n}\n#endif // XASH_AVI == AVI_NULL\n\nstatic movie_state_t avi[2];\nmovie_state_t *AVI_GetState( int num )\n{\n\treturn &avi[num];\n}\n\nqboolean AVI_IsActive( movie_state_t *Avi )\n{\n\treturn Avi ? Avi->active : false;\n}\n\nqboolean AVI_Initailize( void )\n{\n\tif( XASH_AVI == AVI_NULL )\n\t{\n\t\tCon_Printf( \"AVI: Not supported\\n\" );\n\t\treturn false;\n\t}\n\n\tif( Sys_CheckParm( \"-noavi\" ))\n\t{\n\t\tCon_Printf( \"AVI: Disabled\\n\" );\n\t\treturn false;\n\t}\n\n\tif( !AVI_LoadFFmpeg( ))\n\t\treturn false;\n\n\tif( !AVI_ValidateFFmpegVersion( ))\n\t\treturn false;\n\n\tavi_initialized = true;\n\tavi_mempool = Mem_AllocPool( \"AVI Zone\" );\n\n\treturn false;\n}\n\nvoid AVI_Shutdown( void )\n{\n\tMem_FreePool( &avi_mempool );\n\tavi_initialized = false;\n\n\tAVI_UnloadFFmpeg();\n}\n\nmovie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio )\n{\n\tmovie_state_t\t*Avi;\n\tstring\t\tpath;\n\tconst char\t*fullpath;\n\n\t// fast reject\n\tif( !avi_initialized )\n\t\treturn NULL;\n\n\t// open cinematic\n\tQ_snprintf( path, sizeof( path ), \"media/%s\", filename );\n\tCOM_DefaultExtension( path, \".avi\", sizeof( path ));\n\tfullpath = FS_GetDiskPath( path, false );\n\n\tif( FS_FileExists( path, false ) && !fullpath )\n\t{\n\t\tCon_Printf( \"Couldn't load %s from packfile. Please extract it\\n\", path );\n\t\treturn NULL;\n\t}\n\n\tAvi = Mem_Calloc( avi_mempool, sizeof( movie_state_t ));\n\tAVI_OpenVideo( Avi, fullpath, load_audio, false );\n\n\tif( !AVI_IsActive( Avi ))\n\t{\n\t\tAVI_FreeVideo( Avi ); // something bad happens\n\t\treturn NULL;\n\t}\n\n\t// all done\n\treturn Avi;\n}\n\nvoid AVI_FreeVideo( movie_state_t *Avi )\n{\n\tif( !Avi )\n\t\treturn;\n\n\tAVI_CloseVideo( Avi );\n\n\tif( Mem_IsAllocatedExt( avi_mempool, Avi ))\n\t\tMem_Free( Avi );\n}\n"
  },
  {
    "path": "engine/client/avi/avi_ffmpeg.h",
    "content": "/*\navi_ffmpreg.c - playing AVI files (ffmpeg backend)\nCopyright (C) FTEQW developers (for plugins/avplug/avdecode.c)\nCopyright (C) Sam Lantinga (for tests/testffmpeg.c)\nCopyright (C) 2023-2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef AVI_FFMPEG_H\n#define AVI_FFMPEG_H\n\n#if XASH_AVI == AVI_FFMPEG\n#include <libavformat/avformat.h>\n#include <libavcodec/avcodec.h>\n#include <libavutil/imgutils.h>\n#include <libswscale/swscale.h>\n#include <libswresample/swresample.h>\n\n#if XASH_FFMPEG_DLOPEN\n\n// the following symbols were taken from ffmpeg public headers\n// on each major ffmpeg uprgade, they must be validated\n// ffmpeg guarantees API and ABI compatibility between major versions\n// so complain if this gets compiled against unsupported yet version\n// the same check will be done in runtime to ensure compatibility\n#define SUPPORTED_AVU_VERSION_MAJOR 59\n#define SUPPORTED_AVF_VERSION_MAJOR 61\n#define SUPPORTED_AVC_VERSION_MAJOR 61\n#define SUPPORTED_SWR_VERSION_MAJOR 5\n#define SUPPORTED_SWS_VERSION_MAJOR 8\n\n#if SUPPORTED_AVU_VERSION_MAJOR != LIBAVUTIL_VERSION_MAJOR\n#error\n#endif\n\n#if SUPPORTED_AVF_VERSION_MAJOR != LIBAVFORMAT_VERSION_MAJOR\n#error\n#endif\n\n#if SUPPORTED_AVC_VERSION_MAJOR != LIBAVCODEC_VERSION_MAJOR\n#error\n#endif\n\n#if SUPPORTED_SWR_VERSION_MAJOR != LIBSWRESAMPLE_VERSION_MAJOR\n#error\n#endif\n\n#if SUPPORTED_SWS_VERSION_MAJOR != LIBSWSCALE_VERSION_MAJOR\n#error\n#endif\n\n// libavutil\nunsigned          (*pavutil_version)( void );\nAVFrame           *(*pav_frame_alloc)( void );\nvoid              (*pav_frame_free)( AVFrame **frame );\nint               (*pav_frame_ref)( AVFrame *dst, const AVFrame *src );\nvoid              (*pav_frame_unref)( AVFrame *frame );\nint               (*pav_strerror)( int errnum, char *errbuf, size_t errbuf_size );\nvoid              (*pav_free)( void *ptr );\nint               (*pav_get_bytes_per_sample)( enum AVSampleFormat sample_fmt );\nconst char        *(*pav_get_media_type_string)( enum AVMediaType media_type );\nint               (*pav_image_alloc)( uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align );\n\n// libavformat\nunsigned          (*pavformat_version)( void );\nint               (*pav_find_best_stream)( AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, const struct AVCodec **decoder_ret, int flags );\nint               (*pav_read_frame)( AVFormatContext *s, AVPacket *pkt );\nint               (*pav_seek_frame)( AVFormatContext *s, int stream_index, int64_t timestamp, int flags );\nvoid              (*pavformat_close_input)(AVFormatContext **s);\nint               (*pavformat_find_stream_info)(AVFormatContext *ic, AVDictionary **options);\nint               (*pavformat_open_input)(AVFormatContext **ps, const char *url, const AVInputFormat *fmt, AVDictionary **options);\n\n// libavcodec\nunsigned          (*pavcodec_version)( void );\nAVPacket          *(*pav_packet_alloc)( void );\nvoid              (*pav_packet_free)( AVPacket **pkt );\nvoid              (*pav_packet_unref)( AVPacket *pkt );\nAVCodecContext    *(*pavcodec_alloc_context3)( const AVCodec *codec );\nconst AVCodec     *(*pavcodec_find_decoder)( enum AVCodecID id );\nvoid              (*pavcodec_flush_buffers)( AVCodecContext *avctx );\nvoid              (*pavcodec_free_context)( AVCodecContext **avctx );\nint               (*pavcodec_open2)( AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options );\nint               (*pavcodec_parameters_to_context)( AVCodecContext *codec, const struct AVCodecParameters *par );\nint               (*pavcodec_receive_frame)( AVCodecContext *avctx, AVFrame *frame );\nint               (*pavcodec_send_packet)( AVCodecContext *avctx, const AVPacket *avpkt );\n\n// libswresample\nunsigned          (*pswresample_version)( void );\nint               (*pswr_alloc_set_opts2)( struct SwrContext **ps, const AVChannelLayout *out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, const AVChannelLayout *in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx );\nint               (*pswr_convert)( struct SwrContext *s, uint8_t *const *out, int out_count, const uint8_t *const *in, int in_count );\nvoid              (*pswr_free)( struct SwrContext **s );\nint               (*pswr_init)( struct SwrContext *s );\n\n// libswscale\nunsigned          (*pswscale_version)( void );\nvoid              (*psws_freeContext)( struct SwsContext *swsContext );\nstruct SwsContext *(*psws_getContext)( int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param );\nint               (*psws_scale)( struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[] );\n\n#else // !XASH_FFMPEG_DLOPEN\n\n#define SUPPORTED_AVU_VERSION_MAJOR LIBAVUTIL_VERSION_MAJOR\n#define SUPPORTED_AVF_VERSION_MAJOR LIBAVFORMAT_VERSION_MAJOR\n#define SUPPORTED_AVC_VERSION_MAJOR LIBAVCODEC_VERSION_MAJOR\n#define SUPPORTED_SWR_VERSION_MAJOR LIBSWRESAMPLE_VERSION_MAJOR\n#define SUPPORTED_SWS_VERSION_MAJOR LIBSWSCALE_VERSION_MAJOR\n\n// libavutil\n#define pavutil_version           avutil_version\n#define pav_frame_alloc           av_frame_alloc\n#define pav_frame_ref             av_frame_ref\n#define pav_frame_unref           av_Frame_unref\n#define pav_strerror              av_strerror\n#define pav_free                  av_free\n#define pav_get_bytes_per_sample  av_get_bytes_per_sample\n#define pav_get_media_type_string av_get_media_type_string\n#define pav_image_alloc           av_image_alloc\n\n// libavformat\n#define pavformat_version    avformat_version\n#define pav_find_best_stream av_find_best_stream\n#define pav_read_frame       av_read_frame\n#define pav_seek_frame       av_seek_frame\n#define pavformat_close_input avformat_close_input\n#define pavformat_find_stream_info avformat_find_stream_info\n#define pavformat_open_input avformat_open_input\n\n// libavcodec\n#define pavcodec_version               avcodec_version\n#define pav_packet_alloc               av_packet_alloc\n#define pav_packet_free                av_packet_free\n#define pav_packet_unref               av_packet_unref\n#define pavcodec_alloc_context3        avcodec_alloc_context3\n#define pavcodec_find_decoder          avcodec_find_decoder\n#define pavcodec_flush_buffers         avcodec_flush_buffers\n#define pavcodec_free_context          avcodec_free_context\n#define pavcodec_open2                 avcodec_open2\n#define pavcodec_parameters_to_context avcodec_parameters_to_context\n#define pavcodec_receive_frame         avcodec_receive_frame\n#define pavcodec_send_packet           avcodec_send_packet\n\n// libswresample\n#define pswresample_version  swresample_version\n#define pswr_alloc_set_opts2 swr_alloc_set_opts2\n#define pswr_convert         swr_convert\n#define pswr_free            swr_free\n#define pswr_init            swr_init\n\n// libswscale\n#define pswscale_version swscale_version\n#define psws_freeContext sws_freeContext\n#define psws_getContext  sws_getContext\n#define psws_scale       sws_scale\n\n#endif // !XASH_FFMPEG_DLOPEN\n#endif // XASH_AVI == AVI_FFMPEG\n#endif // AVI_FFMPEG_H\n"
  },
  {
    "path": "engine/client/cl_cmds.c",
    "content": "/*\ncl_cmds.c - client console commnds\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n\n/*\n====================\nCL_PlayVideo_f\n\nmovie <moviename>\n====================\n*/\nvoid CL_PlayVideo_f( void )\n{\n\tstring\tpath;\n\n\tif( Cmd_Argc() != 2 && Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"movie <moviename> [full]\\n\" );\n\t\treturn;\n\t}\n\n\tif( cls.state == ca_active )\n\t{\n\t\tCon_Printf( \"Can't play movie while connected to a server.\\nPlease disconnect first.\\n\" );\n\t\treturn;\n\t}\n\n\tswitch( Cmd_Argc( ))\n\t{\n\tcase 2:\t// simple user version\n\t\tQ_snprintf( path, sizeof( path ), \"media/%s\", Cmd_Argv( 1 ));\n\t\tCOM_DefaultExtension( path, \".avi\", sizeof( path ));\n\t\tSCR_PlayCinematic( path );\n\t\tbreak;\n\tcase 3:\t// sequenced cinematics used this\n\t\tSCR_PlayCinematic( Cmd_Argv( 1 ));\n\t\tbreak;\n\t}\n}\n\n/*\n===============\nCL_PlayCDTrack_f\n\nEmulate audio-cd system\n===============\n*/\nvoid CL_PlayCDTrack_f( void )\n{\n\tconst char\t*command;\n\tconst char\t*pszTrack;\n\tstatic int\ttrack = 0;\n\tstatic qboolean\tpaused = false;\n\tstatic qboolean\tlooped = false;\n\tstatic qboolean\tenabled = true;\n\n\tif( Cmd_Argc() < 2 ) return;\n\tcommand = Cmd_Argv( 1 );\n\tpszTrack = Cmd_Argv( 2 );\n\n\tif( !enabled && Q_stricmp( command, \"on\" ))\n\t\treturn; // CD-player is disabled\n\n\tif( !Q_stricmp( command, \"play\" ))\n\t{\n\t\tif( Q_isdigit( pszTrack ))\n\t\t{\n\t\t\ttrack = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS );\n\t\t\tS_StartBackgroundTrack( clgame.cdtracks[track-1], NULL, 0, false );\n\t\t}\n\t\telse S_StartBackgroundTrack( pszTrack, NULL, 0, true );\n\t\tpaused = false;\n\t\tlooped = false;\n\t}\n\telse if( !Q_stricmp( command, \"playfile\" ))\n\t{\n\t\tS_StartBackgroundTrack( pszTrack, NULL, 0, true );\n\t\tpaused = false;\n\t\tlooped = false;\n\t}\n\telse if( !Q_stricmp( command, \"loop\" ))\n\t{\n\t\tif( Q_isdigit( pszTrack ))\n\t\t{\n\t\t\ttrack = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS );\n\t\t\tS_StartBackgroundTrack( clgame.cdtracks[track-1], clgame.cdtracks[track-1], 0, false );\n\t\t}\n\t\telse S_StartBackgroundTrack( pszTrack, pszTrack, 0, true );\n\t\tpaused = false;\n\t\tlooped = true;\n\t}\n\telse if( !Q_stricmp( command, \"loopfile\" ))\n\t{\n\t\tS_StartBackgroundTrack( pszTrack, pszTrack, 0, true );\n\t\tpaused = false;\n\t\tlooped = true;\n\t}\n\telse if( !Q_stricmp( command, \"pause\" ))\n\t{\n\t\tS_StreamSetPause( true );\n\t\tpaused = true;\n\t}\n\telse if( !Q_stricmp( command, \"resume\" ))\n\t{\n\t\tS_StreamSetPause( false );\n\t\tpaused = false;\n\t}\n\telse if( !Q_stricmp( command, \"stop\" ))\n\t{\n\t\tS_StopBackgroundTrack();\n\t\tpaused = false;\n\t\tlooped = false;\n\t\ttrack = 0;\n\t}\n\telse if( !Q_stricmp( command, \"on\" ))\n\t{\n\t\tenabled = true;\n\t}\n\telse if( !Q_stricmp( command, \"off\" ))\n\t{\n\t\tenabled = false;\n\t}\n\telse if( !Q_stricmp( command, \"info\" ))\n\t{\n\t\tint\ti, maxTrack;\n\n\t\tfor( maxTrack = i = 0; i < MAX_CDTRACKS; i++ )\n\t\t\tif( COM_CheckStringEmpty( clgame.cdtracks[i] ) ) maxTrack++;\n\n\t\tCon_Printf( \"%u tracks\\n\", maxTrack );\n\t\tif( track )\n\t\t{\n\t\t\tif( paused ) Con_Printf( \"Paused %s track %u\\n\", looped ? \"looping\" : \"playing\", track );\n\t\t\telse Con_Printf( \"Currently %s track %u\\n\", looped ? \"looping\" : \"playing\", track );\n\t\t}\n\t\tCon_Printf( \"Volume is %f\\n\", s_musicvolume.value );\n\t\treturn;\n\t}\n\telse Con_Printf( \"%s: unknown command %s\\n\", Cmd_Argv( 0 ), command );\n}\n\n/*\n==============================================================================\n\n\t\t\tSCREEN SHOTS\n\n==============================================================================\n*/\n/*\n==================\nCL_LevelShot_f\n\nsplash logo while map is loading\n==================\n*/\nvoid CL_LevelShot_f( void )\n{\n\tsize_t\tft1, ft2;\n\tstring\tfilename;\n\n\tif( cls.scrshot_request != scrshot_plaque ) return;\n\tcls.scrshot_request = scrshot_inactive;\n\n\t// check for exist\n\tif( cls.demoplayback && ( cls.demonum != -1 ))\n\t{\n\t\tQ_snprintf( cls.shotname, sizeof( cls.shotname ),\n\t\t\t\"levelshots/%s_%s.bmp\", cls.demoname, refState.wideScreen ? \"16x9\" : \"4x3\" );\n\t\tQ_snprintf( filename, sizeof( filename ), \"%s.dem\", cls.demoname );\n\n\t\t// make sure what levelshot is newer than demo\n\t\tft1 = FS_FileTime( filename, false );\n\t\tft2 = FS_FileTime( cls.shotname, true );\n\t}\n\telse\n\t{\n\t\tQ_snprintf( cls.shotname, sizeof( cls.shotname ),\n\t\t\t\"levelshots/%s_%s.bmp\", clgame.mapname, refState.wideScreen ? \"16x9\" : \"4x3\" );\n\n\t\t// make sure what levelshot is newer than bsp\n\t\tft1 = FS_FileTime( cl.worldmodel->name, false );\n\t\tft2 = FS_FileTime( cls.shotname, true );\n\t}\n\n\t// missing levelshot or level never than levelshot\n\tif( ft2 == -1 || ft1 > ft2 )\n\t\tcls.scrshot_action = scrshot_plaque;\t// build new frame for levelshot\n\telse cls.scrshot_action = scrshot_inactive;\t// disable - not needs\n}\n\nstatic scrshot_t CL_GetScreenshotTypeFromString( const char *string )\n{\n\tif( !Q_stricmp( string, \"snapshot\" ))\n\t\treturn scrshot_snapshot;\n\n\tif( !Q_stricmp( string, \"screenshot\" ))\n\t\treturn scrshot_normal;\n\n\tif( !Q_stricmp( string, \"saveshot\" ))\n\t\treturn scrshot_savegame;\n\n\tif( !Q_stricmp( string, \"envshot\" ))\n\t\treturn scrshot_envshot;\n\n\tif( !Q_stricmp( string, \"skyshot\" ))\n\t\treturn scrshot_skyshot;\n\n\treturn scrshot_inactive;\n}\n\nvoid CL_GenericShot_f( void )\n{\n\tconst char *argv0 = Cmd_Argv( 0 );\n\tscrshot_t type;\n\n\ttype = CL_GetScreenshotTypeFromString( argv0 );\n\n\tif( type == scrshot_normal || type == scrshot_snapshot )\n\t{\n\t\tif( CL_IsDevOverviewMode() == 1 )\n\t\t\ttype = scrshot_mapshot;\n\t}\n\telse\n\t{\n\t\tif( Cmd_Argc() < 2 )\n\t\t{\n\t\t\tCon_Printf( S_USAGE \"%s <shotname>\\n\", argv0 );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tswitch( type )\n\t{\n\tcase scrshot_envshot:\n\tcase scrshot_skyshot:\n\t\tQ_snprintf( cls.shotname, sizeof( cls.shotname ), \"gfx/env/%s\", Cmd_Argv( 1 ));\n\t\tbreak;\n\tcase scrshot_savegame:\n\t\tQ_snprintf( cls.shotname, sizeof( cls.shotname ), DEFAULT_SAVE_DIRECTORY \"%s.bmp\", Cmd_Argv( 1 ));\n\t\tbreak;\n\tcase scrshot_mapshot:\n\t\tQ_snprintf( cls.shotname, sizeof( cls.shotname ), \"overviews/%s.bmp\", clgame.mapname );\n\t\tbreak;\n\tcase scrshot_normal:\n\tcase scrshot_snapshot:\n\t{\n\t\tstring checkname;\n\t\tint i;\n\n\t\t// allow overriding screenshot by users request\n\t\tif( Cmd_Argc() > 1 )\n\t\t{\n\t\t\tQ_strncpy( cls.shotname, Cmd_Argv( 1 ), sizeof( cls.shotname ));\n\t\t\tbreak;\n\t\t}\n\n\t\tif( type == scrshot_snapshot )\n\t\t\tFS_AllowDirectPaths( true );\n\n\t\tfor( i = 0; i < 9999; i++ )\n\t\t{\n\t\t\tint ret;\n\n\t\t\tif( type == scrshot_snapshot )\n\t\t\t\tret = Q_snprintf( checkname, sizeof( checkname ), \"../%s_%04d.png\", clgame.mapname, i );\n\t\t\telse\n\t\t\t\tret = Q_snprintf( checkname, sizeof( checkname ), \"scrshots/%s_shot%04d.png\", clgame.mapname, i );\n\n\t\t\tif( ret <= 0 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"unable to write %s\\n\", argv0 );\n\t\t\t\tFS_AllowDirectPaths( false );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif( !FS_FileExists( checkname, true ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tFS_AllowDirectPaths( false );\n\n\t\tQ_strncpy( cls.shotname, checkname, sizeof( cls.shotname ));\n\t\tbreak;\n\t}\n\tcase scrshot_inactive:\n\tcase scrshot_plaque:\n\tdefault:\n\t\treturn; // shouldn't happen\n\t}\n\n\tcls.scrshot_action = type; // build new frame for saveshot\n\tcls.envshot_vieworg = NULL;\n\tcls.envshot_viewsize = 0;\n}\n\n/*\n==============\nCL_DeleteDemo_f\n\n==============\n*/\nvoid CL_DeleteDemo_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"killdemo <name>\\n\" );\n\t\treturn;\n\t}\n\n\tif( cls.demorecording && !Q_stricmp( cls.demoname, Cmd_Argv( 1 )))\n\t{\n\t\tCon_Printf( \"Can't delete %s - recording\\n\", Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\t// delete demo\n\tFS_Delete( va( \"%s.dem\", Cmd_Argv( 1 )));\n}\n\n/*\n=================\nCL_SetSky_f\n\nSet a specified skybox (only for local clients)\n=================\n*/\nvoid CL_SetSky_f( void )\n{\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"skyname <skybox>\\n\" );\n\t\treturn;\n\t}\n\n\tR_SetupSky( Cmd_Argv( 1 ));\n}\n\n/*\n=============\nSCR_Viewpos_f\n\nviewpos (level-designer helper)\n=============\n*/\nvoid SCR_Viewpos_f( void )\n{\n\tCon_Printf( \"org ( %g %g %g )\\n\", refState.vieworg[0], refState.vieworg[1], refState.vieworg[2] );\n\tCon_Printf( \"ang ( %g %g %g )\\n\", refState.viewangles[0], refState.viewangles[1], refState.viewangles[2] );\n}\n\n/*\n=============\nCL_WavePlayLen_f\n\n=============\n*/\nvoid CL_WavePlayLen_f( void )\n{\n\tconst char *name;\n\tuint msecs;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( \"waveplaylen <wave file name>: returns approximate number of milliseconds a wave file will take to play.\\n\" );\n\t\treturn;\n\t}\n\n\tname = Cmd_Argv( 1 );\n\tmsecs = Sound_GetApproxWavePlayLen( name );\n\n\tif( msecs == 0 )\n\t{\n\t\tCon_Printf( \"Unable to read %s, file may be missing or incorrectly formatted.\\n\", name );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Play time is approximately %dms\\n\", msecs );\n}\n"
  },
  {
    "path": "engine/client/cl_custom.c",
    "content": "/*\ncl_custom.c - downloading custom resources\nCopyright (C) 2018 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n\nqboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource )\n{\n\tchar\tfilepath[MAX_QPATH];\n\n\tswitch( pResource->type )\n\t{\n\tcase t_sound:\n\tcase t_model:\n\t\t// built-in resources not needs to be downloaded\n\t\tif( pResource->szFileName[0] == '*' )\n\t\t\treturn true;\n\t\tbreak;\n\t}\n\n\t// resource was missed on server\n\tif( pResource->nDownloadSize == -1 )\n\t{\n\t\tClearBits( pResource->ucFlags, RES_FATALIFMISSING );\n\t\treturn true;\n\t}\n\n\tif( pResource->type == t_sound )\n\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", pResource->szFileName );\n\telse Q_strncpy( filepath, pResource->szFileName, sizeof( filepath ));\n\n\tif( !COM_IsSafeFileToDownload( filepath ))\n\t{\n\t\tCon_Reportf( \"refusing to download %s\\n\", filepath );\n\t\treturn true;\n\t}\n\n\tif( !cl_allow_download.value )\n\t{\n\t\tCon_Reportf( \"Download refused, cl_allowdownload is 0\\n\" );\n\t\treturn true;\n\t}\n\n\tif( cls.state == ca_active && !cl_download_ingame.value )\n\t{\n\t\tCon_Reportf( \"In-game download refused...\\n\" );\n\t\treturn true;\n\t}\n\n\t// don't request downloads from local client it's silly\n\tif( Host_IsLocalClient() || FS_FileExists( filepath, false ))\n\t\treturn true;\n\n\tif( cls.demoplayback )\n\t{\n\t\tCon_Reportf( S_WARN \"file %s missing during demo playback.\\n\", filepath );\n\t\treturn true;\n\t}\n\n\thost.downloadcount++;\n\n\tif( cl.http_download )\n\t{\n\t\tHTTP_AddDownload( filepath, pResource->nDownloadSize, true, pResource );\n\t}\n\telse\n\t{\n\t\tMSG_BeginClientCmd( msg, clc_stringcmd );\n\t\tMSG_WriteStringf( msg, \"dlfile %s\", filepath );\n\t}\n\n\treturn false;\n}\n\nvoid CL_AddToResourceList( resource_t *pResource, resource_t *pList )\n{\n\tif( pResource->pPrev != NULL || pResource->pNext != NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"Resource already linked\\n\" );\n\t\treturn;\n\t}\n\n\tif( pList->pPrev == NULL || pList->pNext == NULL )\n\t\tHost_Error( \"Resource list corrupted.\\n\" );\n\n\tpResource->pPrev = pList->pPrev;\n\tpResource->pNext = pList;\n\tpList->pPrev->pNext = pResource;\n\tpList->pPrev = pResource;\n}\n\nvoid CL_RemoveFromResourceList( resource_t *pResource )\n{\n\tif( pResource->pPrev == NULL || pResource->pNext == NULL )\n\t\tHost_Error( \"mislinked resource in %s\\n\", __func__ );\n\n\tif( pResource->pNext == pResource || pResource->pPrev == pResource )\n\t\tHost_Error( \"attempt to free last entry in list.\\n\" );\n\n\tpResource->pPrev->pNext = pResource->pNext;\n\tpResource->pNext->pPrev = pResource->pPrev;\n\tpResource->pPrev = NULL;\n\tpResource->pNext = NULL;\n}\n\nvoid CL_MoveToOnHandList( resource_t *pResource )\n{\n\tif( !pResource )\n\t{\n\t\tCon_Reportf( \"Null resource passed to %s\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tCL_RemoveFromResourceList( pResource );\n\tCL_AddToResourceList( pResource, &cl.resourcesonhand );\n}\n\nstatic void CL_ClearResourceList( resource_t *pList )\n{\n\tresource_t\t*p, *n;\n\n\tfor( p = pList->pNext; p != pList && p; p = n )\n\t{\n\t\tn = p->pNext;\n\n\t\tCL_RemoveFromResourceList( p );\n\t\tMem_Free( p );\n\t}\n\n\tpList->pPrev = pList;\n\tpList->pNext = pList;\n}\n\nvoid CL_ClearResourceLists( void )\n{\n\tCL_ClearResourceList( &cl.resourcesneeded );\n\tCL_ClearResourceList( &cl.resourcesonhand );\n}\n"
  },
  {
    "path": "engine/client/cl_debug.c",
    "content": "/*\ncl_debug.c - server message debugging\nCopyright (C) 2018 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"particledef.h\"\n#include \"cl_tent.h\"\n#include \"shake.h\"\n#include \"hltv.h\"\n#include \"input.h\"\n\n#define MSG_COUNT\t\t32\t\t// last 32 messages parsed\n#define MSG_MASK\t\t(MSG_COUNT - 1)\n\ntypedef struct\n{\n\tint\tcommand;\n\tint\tstarting_offset;\n\tint\tframe_number;\n} oldcmd_t;\n\ntypedef struct\n{\n\toldcmd_t\toldcmd[MSG_COUNT];\n\tint\tcurrentcmd;\n\tqboolean\tparsing;\n} msg_debug_t;\n\nstatic msg_debug_t\tcls_message_debug;\n\nconst char *CL_MsgInfo( int cmd )\n{\n\tstatic string\tsz;\n\n\tQ_strncpy( sz, \"???\", sizeof( sz ));\n\n\tif( cmd >= 0 && cmd <= svc_lastmsg )\n\t{\n\t\t// get engine message name\n\t\tconst char *svc_string = NULL;\n\n\t\tswitch( cls.legacymode )\n\t\t{\n\t\tcase PROTO_CURRENT:\n\t\t\tsvc_string = svc_strings[cmd];\n\t\t\tbreak;\n\t\tcase PROTO_LEGACY:\n\t\t\tsvc_string = svc_legacy_strings[cmd];\n\t\t\tbreak;\n\t\tcase PROTO_QUAKE:\n\t\t\tsvc_string = svc_quake_strings[cmd];\n\t\t\tbreak;\n\t\tcase PROTO_GOLDSRC:\n\t\t\tsvc_string = svc_goldsrc_strings[cmd];\n\t\t\tbreak;\n\t\t}\n\n\t\t// fall back to current protocol strings\n\t\tif( !svc_string )\n\t\t\tsvc_string = svc_strings[cmd];\n\n\t\tQ_strncpy( sz, svc_string, sizeof( sz ));\n\t}\n\telse if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES ))\n\t{\n\t\tint\ti;\n\n\t\tfor( i = 0; i < MAX_USER_MESSAGES; i++ )\n\t\t{\n\t\t\tif( clgame.msg[i].number == cmd )\n\t\t\t{\n\t\t\t\tQ_strncpy( sz, clgame.msg[i].name, sizeof( sz ));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn sz;\n}\n\n/*\n=====================\nCL_Parse_Debug\n\nenable message debugging\n=====================\n*/\nvoid CL_Parse_Debug( qboolean enable )\n{\n\tcls_message_debug.parsing = enable;\n}\n\n/*\n=====================\nCL_Parse_RecordCommand\n\nrecord new message params into debug buffer\n=====================\n*/\nvoid CL_Parse_RecordCommand( int cmd, int startoffset )\n{\n\tint\tslot;\n\n\tif( cmd == svc_nop ) return;\n\n\tslot = ( cls_message_debug.currentcmd++ & MSG_MASK );\n\tcls_message_debug.oldcmd[slot].command = cmd;\n\tcls_message_debug.oldcmd[slot].starting_offset = startoffset;\n\tcls_message_debug.oldcmd[slot].frame_number = host.framecount;\n}\n\n/*\n=====================\nCL_ResetFrame\n=====================\n*/\nvoid CL_ResetFrame( frame_t *frame )\n{\n\tmemset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t ));\n\tframe->receivedtime = host.realtime;\n\tframe->valid = true;\n\tframe->choked = false;\n\tframe->latency = 0.0;\n\tframe->time = cl.mtime[0];\n}\n\n/*\n=====================\nCL_WriteErrorMessage\n\nwrite net_message into buffer.dat for debugging\n=====================\n*/\nstatic void CL_WriteErrorMessage( int current_count, sizebuf_t *msg )\n{\n\tconst char\t*buffer_file = \"buffer.dat\";\n\tfile_t\t\t*fp;\n\n\tfp = FS_Open( buffer_file, \"wb\", false );\n\tif( !fp ) return;\n\n\tFS_Write( fp, &cls.starting_count, sizeof( int ));\n\tFS_Write( fp, &current_count, sizeof( int ));\n\tFS_Write( fp, &cls.legacymode, sizeof( cls.legacymode ));\n\tFS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg ));\n\tFS_Close( fp );\n\n\tCon_Printf( \"Wrote erroneous message to %s\\n\", buffer_file );\n}\n\n/*\n=====================\nCL_WriteMessageHistory\n\nlist last 32 messages for debugging net troubleshooting\n=====================\n*/\nvoid CL_WriteMessageHistory( void )\n{\n\toldcmd_t\t*old;\n\tsizebuf_t\t*msg = &net_message;\n\tint\ti, thecmd;\n\n\tif( !cls.initialized || cls.state == ca_disconnected )\n\t\treturn;\n\n\tif( !cls_message_debug.parsing )\n\t\treturn;\n\n\tCon_Printf( \"Last %i messages parsed.\\n\", MSG_COUNT );\n\n\t// finish here\n\tthecmd = cls_message_debug.currentcmd - 1;\n\tthecmd -= ( MSG_COUNT - 1 );\t// back up to here\n\n\tfor( i = 0; i < MSG_COUNT - 1; i++ )\n\t{\n\t\tthecmd &= MSG_MASK;\n\t\told = &cls_message_debug.oldcmd[thecmd];\n\t\tCon_Printf( \"%i %04i %s\\n\", old->frame_number, old->starting_offset, CL_MsgInfo( old->command ));\n\t\tthecmd++;\n\t}\n\n\told = &cls_message_debug.oldcmd[thecmd];\n\tCon_Printf( S_RED \"BAD: \" S_DEFAULT \"%i %04i %s\\n\", old->frame_number, old->starting_offset, CL_MsgInfo( old->command ));\n\tCL_WriteErrorMessage( old->starting_offset, msg );\n\tcls_message_debug.parsing = false;\n}\n\nvoid CL_ReplayBufferDat_f( void )\n{\n\tfile_t *f = FS_Open( Cmd_Argv( 1 ), \"rb\", true );\n\tsizebuf_t msg;\n\tchar buffer[NET_MAX_MESSAGE];\n\tint starting_count, current_count, protocol;\n\tfs_offset_t len;\n\n\tif( !f )\n\t\treturn;\n\n\tFS_Read( f, &starting_count, sizeof( starting_count ));\n\tFS_Read( f, &current_count, sizeof( current_count ));\n\tFS_Read( f, &protocol, sizeof( protocol ));\n\n\tcls.legacymode = protocol;\n\n\tlen = FS_Read( f, buffer, sizeof( buffer ));\n\tFS_Close( f );\n\n\tMSG_Init( &msg, __func__, buffer, len );\n\n\tDelta_Shutdown();\n\tDelta_Init();\n\n\tclgame.maxEntities = MAX_EDICTS;\n\tclgame.entities = Mem_Calloc( clgame.mempool, sizeof( *clgame.entities ) * clgame.maxEntities );\n\n\t// ad-hoc implement\n#if 0\n\t{\n\t\tconst int message_pos = 12; // put real number here\n\t\tMSG_SeekToBit( &msg, ( message_pos - 12 + 1 ) << 3, SEEK_SET );\n\n\t\tCL_ParseYourMom( &msg, protocol );\n\t}\n#endif\n\n\tSys_Quit( __func__ );\n}\n"
  },
  {
    "path": "engine/client/cl_demo.c",
    "content": "/*\ncl_demo.c - demo record & playback\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n\n#define dem_unknown\t\t0\t// unknown command\n#define dem_norewind\t1\t// startup message\n#define dem_read\t\t2\t// it's a normal network packet\n#define dem_jumptime\t3\t// move the demostart time value forward by this amount\n#define dem_userdata\t4\t// userdata from the client.dll\n#define dem_usercmd\t\t5\t// read usercmd_t\n#define dem_stop\t\t6\t// end of time\n#define dem_lastcmd\t\tdem_stop\n\n#define DEMO_STARTUP\t0\t// this lump contains startup info needed to spawn into the server\n#define DEMO_NORMAL\t\t1\t// this lump contains playback info of messages, etc., needed during playback.\n\n// Demo flags\n#define FDEMO_TITLE\t\t0x01\t// Show title\n#define FDEMO_PLAY\t\t0x04\t// Playing cd track\n#define FDEMO_FADE_IN_SLOW\t0x08\t// Fade in (slow)\n#define FDEMO_FADE_IN_FAST\t0x10\t// Fade in (fast)\n#define FDEMO_FADE_OUT_SLOW\t0x20\t// Fade out (slow)\n#define FDEMO_FADE_OUT_FAST\t0x40\t// Fade out (fast)\n\n#define IDEMOHEADER\t\t(('M'<<24)+('E'<<16)+('D'<<8)+'I') // little-endian \"IDEM\"\n#define DEMO_PROTOCOL\t3\n\n#define PROTOCOL_GOLDSRC_VERSION_DEMO (PROTOCOL_GOLDSRC_VERSION | (BIT( 7 ))) // should be 48, only to differentiate it from PROTOCOL_LEGACY_VERSION\n\nconst char *demo_cmd[dem_lastcmd+1] =\n{\n\t\"dem_unknown\",\n\t\"dem_norewind\",\n\t\"dem_read\",\n\t\"dem_jumptime\",\n\t\"dem_userdata\",\n\t\"dem_usercmd\",\n\t\"dem_stop\",\n};\n\n#pragma pack( push, 1 )\ntypedef struct\n{\n\tint\t\tid;\t\t// should be IDEM\n\tint\t\tdem_protocol;\t// should be DEMO_PROTOCOL\n\tint\t\tnet_protocol;\t// should be PROTOCOL_VERSION\n\tdouble\t\thost_fps;\t\t// fps for demo playing\n\tchar\t\tmapname[64];\t// name of map\n\tchar\t\tcomment[64];\t// comment for demo\n\tchar\t\tgamedir[64];\t// name of game directory (FS_Gamedir())\n\tint\t\tdirectory_offset;\t// offset of Entry Directory.\n} demoheader_t;\n#pragma pack( pop )\n\ntypedef struct\n{\n\tint\t\tentrytype;\t// DEMO_STARTUP or DEMO_NORMAL\n\tfloat\t\tplayback_time;\t// time of track\n\tint\t\tplayback_frames;\t// # of frames in track\n\tint\t\toffset;\t\t// file offset of track data\n\tint\t\tlength;\t\t// length of track\n\tint\t\tflags;\t\t// FX-flags\n\tchar\t\tdescription[64];\t// entry description\n} demoentry_t;\n\ntypedef struct\n{\n\tdemoentry_t\t*entries;\t\t// track entry info\n\tint32_t\t\tnumentries;\t// number of tracks\n} demodirectory_t;\n\n// add angles\ntypedef struct\n{\n\tfloat\t\tstarttime;\n\tvec3_t\t\tviewangles;\n} demoangle_t;\n\n// private demo states\nstruct\n{\n\tdemoheader_t\theader;\n\tdemoentry_t\t*entry;\n\tdemodirectory_t\tdirectory;\n\tint\t\tframecount;\n\tfloat\t\tstarttime;\n\tfloat\t\trealstarttime;\n\tfloat\t\ttimestamp;\n\tfloat\t\tlasttime;\n\tint\t\tentryIndex;\n\n\t// interpolation stuff\n\tdemoangle_t\tcmds[ANGLE_BACKUP];\n\tint\t\tangle_position;\n} demo;\n\nstatic qboolean CL_NextDemo( void );\n\nstatic int CL_GetDemoNetProtocol( connprotocol_t proto )\n{\n\tswitch( proto )\n\t{\n\tcase PROTO_CURRENT:\n\t\treturn PROTOCOL_VERSION;\n\tcase PROTO_LEGACY:\n\t\treturn PROTOCOL_LEGACY_VERSION;\n\tcase PROTO_QUAKE:\n\t\treturn PROTOCOL_VERSION_QUAKE;\n\tcase PROTO_GOLDSRC:\n\t\treturn PROTOCOL_GOLDSRC_VERSION_DEMO;\n\t}\n\n\treturn PROTOCOL_VERSION;\n}\n\nstatic connprotocol_t CL_GetProtocolFromDemo( int net_protocol )\n{\n\tswitch( net_protocol )\n\t{\n\tcase PROTOCOL_VERSION:\n\t\treturn PROTO_CURRENT;\n\tcase PROTOCOL_LEGACY_VERSION:\n\t\treturn PROTO_LEGACY;\n\tcase PROTOCOL_VERSION_QUAKE:\n\t\treturn PROTO_QUAKE;\n\tcase PROTOCOL_GOLDSRC_VERSION_DEMO:\n\t\treturn PROTO_GOLDSRC;\n\t}\n\n\treturn PROTO_CURRENT;\n}\n\n/*\n====================\nCL_StartupDemoHeader\n\nspooling demo header in case\nwe record a demo on this level\n====================\n*/\nvoid CL_StartupDemoHeader( void )\n{\n\tCL_CloseDemoHeader();\n\n\tcls.demoheader = FS_Open( \"demoheader.tmp\", \"w+bm\", true );\n\n\tif( !cls.demoheader )\n\t{\n\t\tCon_DPrintf( S_ERROR \"couldn't open temporary header file.\\n\" );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Spooling demo header.\\n\" );\n}\n\n/*\n====================\nCL_CloseDemoHeader\n\nclose demoheader file on engine shutdown\n====================\n*/\nvoid CL_CloseDemoHeader( void )\n{\n\tif( !cls.demoheader )\n\t\treturn;\n\n\tFS_Close( cls.demoheader );\n}\n\n/*\n====================\nCL_GetDemoRecordClock\n\nwrite time while demo is recording\n====================\n*/\nstatic float CL_GetDemoRecordClock( void )\n{\n\treturn cl.mtime[0];\n}\n\n/*\n====================\nCL_GetDemoPlaybackClock\n\noverwrite host.realtime\n====================\n*/\nstatic float CL_GetDemoPlaybackClock( void )\n{\n\treturn host.realtime + host.frametime;\n}\n\n/*\n====================\nCL_GetDemoFramerate\n\noverwrite host.frametime\n====================\n*/\ndouble CL_GetDemoFramerate( void )\n{\n\tif( cls.timedemo )\n\t\treturn 0.0;\n\treturn bound( MIN_FPS, demo.header.host_fps, MAX_FPS_HARD );\n}\n\n/*\n=================\nCL_DemoAborted\n=================\n*/\nstatic void CL_DemoAborted( void )\n{\n\tif( cls.demofile )\n\t\tFS_Close( cls.demofile );\n\tcls.demoplayback = false;\n\tcls.changedemo = false;\n\tcls.timedemo = false;\n\tdemo.framecount = 0;\n\tcls.demofile = NULL;\n\tcls.demonum = -1;\n\n\tCvar_DirectSet( &v_dark, \"0\" );\n}\n\n/*\n====================\nCL_WriteDemoCmdHeader\n\nWrites the demo command header and time-delta\n====================\n*/\nstatic void CL_WriteDemoCmdHeader( byte cmd, file_t *file )\n{\n\tfloat\tdt;\n\n\tAssert( cmd >= 1 && cmd <= dem_lastcmd );\n\tif( !file ) return;\n\n\t// command\n\tFS_Write( file, &cmd, sizeof( byte ));\n\n\t// time offset\n\tdt = (float)(CL_GetDemoRecordClock() - demo.starttime);\n\tFS_Write( file, &dt, sizeof( float ));\n}\n\n/*\n====================\nCL_WriteDemoJumpTime\n\nUpdate level time on a next level\n====================\n*/\nvoid CL_WriteDemoJumpTime( void )\n{\n\tif( cls.demowaiting || !cls.demofile )\n\t\treturn;\n\n\tdemo.starttime = CL_GetDemoRecordClock(); // setup the demo starttime\n\n\t// demo playback should read this as an incoming message.\n\t// write the client's realtime value out so we can synchronize the reads.\n\tCL_WriteDemoCmdHeader( dem_jumptime, cls.demofile );\n}\n\n/*\n====================\nCL_WriteDemoUserCmd\n\nWrites the current user cmd\n====================\n*/\nvoid CL_WriteDemoUserCmd( int cmdnumber )\n{\n\tsizebuf_t\tbuf;\n\tword\tbytes;\n\tbyte\tdata[1024];\n\n\tif( !cls.demorecording || !cls.demofile )\n\t\treturn;\n\n\tCL_WriteDemoCmdHeader( dem_usercmd, cls.demofile );\n\n\tFS_Write( cls.demofile, &cls.netchan.outgoing_sequence, sizeof( int ));\n\tFS_Write( cls.demofile, &cmdnumber, sizeof( int ));\n\n\t// write usercmd_t\n\tMSG_Init( &buf, \"UserCmd\", data, sizeof( data ));\n\tCL_WriteUsercmd( PROTO_CURRENT, &buf, -1, cmdnumber ); // always no delta, always in current protocol\n\n\tbytes = MSG_GetNumBytesWritten( &buf );\n\n\tFS_Write( cls.demofile, &bytes, sizeof( word ));\n\tFS_Write( cls.demofile, data, bytes );\n}\n\n/*\n====================\nCL_WriteDemoSequence\n\nSave state of cls.netchan sequences\nso that we can play the demo correctly.\n====================\n*/\nstatic void CL_WriteDemoSequence( file_t *file )\n{\n\tAssert( file != NULL );\n\n\tFS_Write( file, &cls.netchan.incoming_sequence, sizeof( int ));\n\tFS_Write( file, &cls.netchan.incoming_acknowledged, sizeof( int ));\n\tFS_Write( file, &cls.netchan.incoming_reliable_acknowledged, sizeof( int ));\n\tFS_Write( file, &cls.netchan.incoming_reliable_sequence, sizeof( int ));\n\tFS_Write( file, &cls.netchan.outgoing_sequence, sizeof( int ));\n\tFS_Write( file, &cls.netchan.reliable_sequence, sizeof( int ));\n\tFS_Write( file, &cls.netchan.last_reliable_sequence, sizeof( int ));\n}\n\n/*\n====================\nCL_WriteDemoMessage\n\nDumps the current net message, prefixed by the length\n====================\n*/\nvoid CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg )\n{\n\tfile_t\t*file = startup ? cls.demoheader : cls.demofile;\n\tint\tswlen;\n\tbyte\tc;\n\n\tif( !file ) return;\n\n\tswlen = MSG_GetNumBytesWritten( msg ) - start;\n\tif( swlen <= 0 ) return;\n\n\tif( !startup ) demo.framecount++;\n\n\t// demo playback should read this as an incoming message.\n\tc = (cls.state != ca_active) ? dem_norewind : dem_read;\n\n\tCL_WriteDemoCmdHeader( c, file );\n\tCL_WriteDemoSequence( file );\n\n\t// write the length out.\n\tFS_Write( file, &swlen, sizeof( int ));\n\n\t// output the buffer. Skip the network packet stuff.\n\tFS_Write( file, MSG_GetData( msg ) + start, swlen );\n}\n\n/*\n====================\nCL_WriteDemoUserMessage\n\nDumps the user message (demoaction)\n====================\n*/\nvoid GAME_EXPORT CL_WriteDemoUserMessage( int size, byte *buffer )\n{\n\tif( !cls.demorecording || cls.demowaiting )\n\t\treturn;\n\n\tif( !cls.demofile || !buffer || size <= 0 )\n\t\treturn;\n\n\tCL_WriteDemoCmdHeader( dem_userdata, cls.demofile );\n\n\t// write the length out.\n\tFS_Write( cls.demofile, &size, sizeof( int ));\n\n\t// output the buffer.\n\tFS_Write( cls.demofile, buffer, size );\n}\n\n/*\n====================\nCL_WriteDemoHeader\n\nWrite demo header\n====================\n*/\nstatic void CL_WriteDemoHeader( const char *name )\n{\n\tdouble maxfps;\n\tint copysize;\n\tint savepos;\n\tint curpos;\n\n\tCon_Printf( \"recording to %s.\\n\", name );\n\tcls.demofile = FS_Open( name, \"wb\", false );\n\tcls.demotime = 0.0;\n\n\tif( !cls.demofile )\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't open %s.\\n\", name );\n\t\treturn;\n\t}\n\n\tcls.demorecording = true;\n\tcls.demowaiting = true;\t// don't start saving messages until a non-delta compressed message is received\n\n\tmaxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT;\n\n\tmemset( &demo.header, 0, sizeof( demo.header ));\n\n\tdemo.header.id = IDEMOHEADER;\n\tdemo.header.dem_protocol = DEMO_PROTOCOL;\n\tdemo.header.net_protocol = CL_GetDemoNetProtocol( cls.legacymode );\n\tdemo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps;\n\tQ_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname ));\n\tQ_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment ));\n\tQ_strncpy( demo.header.gamedir, FS_Gamedir(), sizeof( demo.header.gamedir ));\n\n\t// write header\n\tFS_Write( cls.demofile, &demo.header, sizeof( demo.header ));\n\n\tdemo.directory.numentries = 2;\n\tdemo.directory.entries = Mem_Calloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries );\n\n\t// DIRECTORY ENTRY # 0\n\tdemo.entry = &demo.directory.entries[0];\t// only one here.\n\tdemo.entry->entrytype = DEMO_STARTUP;\n\tdemo.entry->playback_time = 0.0f;\t\t// startup takes 0 time.\n\tdemo.entry->offset = FS_Tell( cls.demofile );\t// position for this chunk.\n\n\t// finish off the startup info.\n\tCL_WriteDemoCmdHeader( dem_stop, cls.demoheader );\n\tFS_Flush( cls.demoheader );\n\n\t// now copy the stuff we cached from the server.\n\tcopysize = savepos = FS_Tell( cls.demoheader );\n\n\tFS_Seek( cls.demoheader, 0, SEEK_SET );\n\n\tFS_FileCopy( cls.demofile, cls.demoheader, copysize );\n\n\t// jump back to end, in case we record another demo for this session.\n\tFS_Seek( cls.demoheader, savepos, SEEK_SET );\n\n\tdemo.starttime = CL_GetDemoRecordClock();\t// setup the demo starttime\n\tdemo.realstarttime = demo.starttime;\n\tdemo.framecount = 0;\n\tcls.td_startframe = host.framecount;\n\tcls.td_lastframe = -1;\t\t\t// get a new message this frame\n\n\t// now move on to entry # 1, the first data chunk.\n\tcurpos = FS_Tell( cls.demofile );\n\tdemo.entry->length = curpos - demo.entry->offset;\n\n\t// now we are writing the first real lump.\n\tdemo.entry = &demo.directory.entries[1]; // first real data lump\n\tdemo.entry->entrytype = DEMO_NORMAL;\n\tdemo.entry->playback_time = 0.0f; // startup takes 0 time.\n\n\tdemo.entry->offset = FS_Tell( cls.demofile );\n\n\t// demo playback should read this as an incoming message.\n\t// write the client's realtime value out so we can synchronize the reads.\n\tCL_WriteDemoCmdHeader( dem_jumptime, cls.demofile );\n\n\tif( clgame.hInstance ) clgame.dllFuncs.pfnReset();\n\n\tCbuf_InsertText( \"fullupdate\\n\" );\n\tCbuf_Execute();\n}\n\n/*\n=================\nCL_StopRecord\n\nfinish recording demo\n=================\n*/\nstatic void CL_StopRecord( void )\n{\n\tint\ti, curpos;\n\tfloat\tstoptime;\n\tint\tframes;\n\n\tif( !cls.demorecording ) return;\n\n\t// demo playback should read this as an incoming message.\n\tCL_WriteDemoCmdHeader( dem_stop, cls.demofile );\n\n\tstoptime = CL_GetDemoRecordClock();\n\tif( clgame.hInstance ) clgame.dllFuncs.pfnReset();\n\n\tcurpos = FS_Tell( cls.demofile );\n\tdemo.entry->length = curpos - demo.entry->offset;\n\tdemo.entry->playback_time = stoptime - demo.realstarttime;\n\tdemo.entry->playback_frames = demo.framecount;\n\n\t//  Now write out the directory and free it and touch up the demo header.\n\tFS_Write( cls.demofile, &demo.directory.numentries, sizeof( int ));\n\n\tfor( i = 0; i < demo.directory.numentries; i++ )\n\t\tFS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t ));\n\n\tMem_Free( demo.directory.entries );\n\tdemo.directory.numentries = 0;\n\n\tdemo.header.directory_offset = curpos;\n\tFS_Seek( cls.demofile, 0, SEEK_SET );\n\tFS_Write( cls.demofile, &demo.header, sizeof( demo.header ));\n\n\tFS_Close( cls.demofile );\n\tcls.demofile = NULL;\n\tcls.demorecording = false;\n\tcls.demoname[0] = '\\0';\n\tcls.td_lastframe = host.framecount;\n\tgameui.globals->demoname[0] = '\\0';\n\tdemo.header.host_fps = 0.0;\n\n\tframes = cls.td_lastframe - cls.td_startframe;\n\tCon_Printf( \"Completed demo\\nRecording time: %02d:%02d, frames %i\\n\", (int)(cls.demotime / 60.0f), (int)fmod(cls.demotime, 60.0f), frames );\n\tcls.demotime = 0.0;\n}\n\n/*\n=================\nCL_DrawDemoRecording\n=================\n*/\nvoid CL_DrawDemoRecording( void )\n{\n\tchar\tstring[64];\n\trgba_t\tcolor = { 255, 255, 255, 255 };\n\tint\tpos;\n\tint\tlen;\n\n\tif(!( host_developer.value && cls.demorecording ))\n\t\treturn;\n\n\tpos = FS_Tell( cls.demofile );\n\tQ_snprintf( string, sizeof( string ), \"^1RECORDING:^7 %s: %s time: %02d:%02d\", cls.demoname,\n\t\tQ_memprint( pos ), (int)(cls.demotime / 60.0f ), (int)fmod( cls.demotime, 60.0f ));\n\n\tCon_DrawStringLen( string, &len, NULL );\n\tCon_DrawString(( refState.width - len ) >> 1, refState.height >> 4, string, color );\n}\n\n/*\n=======================================================================\n\nCLIENT SIDE DEMO PLAYBACK\n\n=======================================================================\n*/\n/*\n=================\nCL_ReadDemoCmdHeader\n\nread the demo command\n=================\n*/\nstatic qboolean CL_ReadDemoCmdHeader( byte *cmd, float *dt )\n{\n\t// read the command\n\t// HACKHACK: skip NOPs\n\tdo\n\t{\n\t\tFS_Read( cls.demofile, cmd, sizeof( byte ));\n\t} while( *cmd == dem_unknown );\n\n\tif( *cmd > dem_lastcmd )\n\t{\n\t\tCon_Printf( S_ERROR \"Demo cmd %d > %d, file offset = %d\\n\", *cmd, dem_lastcmd, (int)FS_Tell( cls.demofile ));\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\t// read the timestamp\n\tFS_Read( cls.demofile, dt, sizeof( float ));\n\n\treturn true;\n}\n\n/*\n=================\nCL_ReadDemoUserCmd\n\nread the demo usercmd for predicting\nand smooth movement during playback the demo\n=================\n*/\nstatic void CL_ReadDemoUserCmd( qboolean discard )\n{\n\tbyte\tdata[1024];\n\tint\tcmdnumber;\n\tint\toutgoing_sequence;\n\truncmd_t\t*pcmd;\n\tword\tbytes;\n\n\tFS_Read( cls.demofile, &outgoing_sequence, sizeof( int ));\n\tFS_Read( cls.demofile, &cmdnumber, sizeof( int ));\n\tFS_Read( cls.demofile, &bytes, sizeof( short ));\n\n\tif( bytes >= sizeof( data ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: too large dem_usercmd (size %u seq %i)\\n\", __func__, bytes, outgoing_sequence );\n\t\tCL_DemoAborted();\n\t\treturn;\n\t}\n\n\tFS_Read( cls.demofile, data, bytes );\n\n\tif( !discard )\n\t{\n\t\tconst usercmd_t nullcmd = { 0 };\n\t\tsizebuf_t\t\tbuf;\n\t\tdemoangle_t\t*a;\n\n\t\tMSG_Init( &buf, \"UserCmd\", data, sizeof( data ));\n\n\t\t// a1ba: I have no proper explanation why\n\t\tcmdnumber++;\n\n\t\tpcmd = &cl.commands[cmdnumber & CL_UPDATE_MASK];\n\t\tpcmd->processedfuncs = false;\n\t\tpcmd->senttime = 0.0f;\n\t\tpcmd->receivedtime = 0.1f;\n\t\tpcmd->frame_lerp = 0.1f;\n\t\tpcmd->heldback = false;\n\t\tpcmd->sendsize = 1;\n\n\t\t// always delta'ing from null\n\t\tMSG_ReadDeltaUsercmd( &buf, &nullcmd, &pcmd->cmd );\n\n\t\t// make sure what interp info contain angles from different frames\n\t\t// or lerping will stop working\n\t\tif( demo.lasttime != demo.timestamp )\n\t\t{\n\t\t\t// select entry into circular buffer\n\t\t\tdemo.angle_position = (demo.angle_position + 1) & ANGLE_MASK;\n\t\t\ta = &demo.cmds[demo.angle_position];\n\n\t\t\t// record update\n\t\t\ta->starttime = demo.timestamp;\n\t\t\tVectorCopy( pcmd->cmd.viewangles, a->viewangles );\n\t\t\tdemo.lasttime = demo.timestamp;\n\t\t}\n\n\t\t// NOTE: we need to have the current outgoing sequence correct\n\t\t// so we can do prediction correctly during playback\n\t\tcls.netchan.outgoing_sequence = outgoing_sequence;\n\n\t\t// save last usercmd\n\t\tcl.cmd = pcmd->cmd;\n\t}\n}\n\n/*\n=================\nCL_ReadDemoSequence\n\nread netchan sequences\n=================\n*/\nstatic void CL_ReadDemoSequence( qboolean discard )\n{\n\tint\tincoming_sequence;\n\tint\tincoming_acknowledged;\n\tint\tincoming_reliable_acknowledged;\n\tint\tincoming_reliable_sequence;\n\tint\toutgoing_sequence;\n\tint\treliable_sequence;\n\tint\tlast_reliable_sequence;\n\n\tFS_Read( cls.demofile, &incoming_sequence, sizeof( int ));\n\tFS_Read( cls.demofile, &incoming_acknowledged, sizeof( int ));\n\tFS_Read( cls.demofile, &incoming_reliable_acknowledged, sizeof( int ));\n\tFS_Read( cls.demofile, &incoming_reliable_sequence, sizeof( int ));\n\tFS_Read( cls.demofile, &outgoing_sequence, sizeof( int ));\n\tFS_Read( cls.demofile, &reliable_sequence, sizeof( int ));\n\tFS_Read( cls.demofile, &last_reliable_sequence, sizeof( int ));\n\n\tif( discard ) return;\n\n\tcls.netchan.incoming_sequence\t= incoming_sequence;\n\tcls.netchan.incoming_acknowledged = incoming_acknowledged;\n\tcls.netchan.incoming_reliable_acknowledged = incoming_reliable_acknowledged;\n\tcls.netchan.incoming_reliable_sequence = incoming_reliable_sequence;\n\tcls.netchan.outgoing_sequence\t= outgoing_sequence;\n\tcls.netchan.reliable_sequence\t= reliable_sequence;\n\tcls.netchan.last_reliable_sequence = last_reliable_sequence;\n}\n\n/*\n=================\nCL_DemoStartPlayback\n=================\n*/\nstatic void CL_DemoStartPlayback( int mode )\n{\n\tif( cls.changedemo )\n\t{\n\t\tint maxclients = cl.maxclients;\n\n\t\tS_StopAllSounds( true );\n\t\tSCR_BeginLoadingPlaque( false );\n\n\t\tCL_ClearState( );\n\t\tCL_InitEdicts( maxclients ); // re-arrange edicts\n\t}\n\telse\n\t{\n\t\t// NOTE: at this point demo is still valid\n\t\tCL_Disconnect();\n\t\tSV_Shutdown( \"Server was killed due to demo playback start\\n\" );\n\n\t\tCon_FastClose();\n\t\tUI_SetActiveMenu( false );\n\t}\n\n\tcls.demoplayback = mode;\n\tcls.state = ca_connected;\n\tcl.background = (cls.demonum != -1) ? true : false;\n\tcls.spectator = false;\n\tcls.signon = 0;\n\n\tdemo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message\n\n\tCL_SetupNetchanForProtocol( cls.legacymode );\n\n\tmemset( demo.cmds, 0, sizeof( demo.cmds ));\n\tdemo.angle_position = 1;\n\tdemo.framecount = 0;\n\tcls.lastoutgoingcommand = -1;\n \tcls.nextcmdtime = host.realtime;\n\tcl.last_command_ack = -1;\n}\n\n/*\n=================\nCL_DemoCompleted\n=================\n*/\nvoid CL_DemoCompleted( void )\n{\n\tif( cls.demonum != -1 )\n\t\tcls.changedemo = true;\n\n\tCL_StopPlayback();\n\n\tif( !CL_NextDemo() && !cls.changedemo )\n\t\tUI_SetActiveMenu( true );\n\n\tCvar_DirectSet( &v_dark, \"0\" );\n}\n\n/*\n=================\nCL_DemoMoveToNextSection\n\nreturns true on success, false on failure\ng-cont. probably captain obvious mode is ON\n=================\n*/\nstatic qboolean CL_DemoMoveToNextSection( void )\n{\n\tif( ++demo.entryIndex >= demo.directory.numentries )\n\t{\n\t\t// done\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\t// switch to next section, we got a dem_stop\n\tdemo.entry = &demo.directory.entries[demo.entryIndex];\n\n\t// ready to continue reading, reset clock.\n\tFS_Seek( cls.demofile, demo.entry->offset, SEEK_SET );\n\n\t// time is now relative to this chunk's clock.\n\tdemo.starttime = CL_GetDemoPlaybackClock();\n\tdemo.framecount = 0;\n\n\treturn true;\n}\n\nstatic qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length )\n{\n\tint\tmsglen = 0;\n\n\tAssert( buffer != NULL );\n\tAssert( length != NULL );\n\n\t*length = 0; // assume we fail\n\tFS_Read( cls.demofile, &msglen, sizeof( int ));\n\n\tif( msglen < 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"Demo message length < 0\\n\" );\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\tif( msglen > MAX_INIT_MSG )\n\t{\n\t\tCon_Reportf( S_ERROR \"Demo message %i > %i\\n\", msglen, MAX_INIT_MSG );\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\tif( msglen > 0 )\n\t{\n\t\tif( FS_Read( cls.demofile, buffer, msglen ) != msglen )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"Error reading demo message data\\n\" );\n\t\t\tCL_DemoCompleted();\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcls.netchan.last_received = host.realtime;\n\tcls.netchan.total_received += msglen;\n\t*length = msglen;\n\n\tif( cls.state != ca_active )\n\t\tCbuf_Execute();\n\n\treturn true;\n}\n\n/*\n=================\nCL_DemoReadMessageQuake\n\nreads demo data and write it to client\n=================\n*/\nstatic qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length )\n{\n\tvec3_t\t\tviewangles;\n\tint\t\tmsglen = 0;\n\tdemoangle_t\t*a;\n\n\t*length = 0; // assume we fail\n\n\t// decide if it is time to grab the next message\n\tif( cls.signon == SIGNONS )\t// allways grab until fully connected\n\t{\n\t\tif( cls.timedemo )\n\t\t{\n\t\t\tif( host.framecount == cls.td_lastframe )\n\t\t\t\treturn false; // already read this frame's message\n\n\t\t\tcls.td_lastframe = host.framecount;\n\n\t\t\t// if this is the second frame, grab the real td_starttime\n\t\t\t// so the bogus time on the first frame doesn't count\n\t\t\tif( host.framecount == cls.td_startframe + 1 )\n\t\t\t\tcls.td_starttime = host.realtime;\n\t\t}\n\t\telse if( cl.time <= cl.mtime[0] )\n\t\t{\n\t\t\t// don't need another message yet\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// get the next message\n\tFS_Read( cls.demofile, &msglen, sizeof( int ));\n\tFS_Read( cls.demofile, &viewangles[0], sizeof( float ));\n\tFS_Read( cls.demofile, &viewangles[1], sizeof( float ));\n\tFS_Read( cls.demofile, &viewangles[2], sizeof( float ));\n\tcls.netchan.incoming_sequence++;\n\tdemo.timestamp = cl.mtime[0];\n\tcl.skip_interp = false;\n\n\t// make sure what interp info contain angles from different frames\n\t// or lerping will stop working\n\tif( demo.lasttime != demo.timestamp )\n\t{\n\t\t// select entry into circular buffer\n\t\tdemo.angle_position = (demo.angle_position + 1) & ANGLE_MASK;\n\t\ta = &demo.cmds[demo.angle_position];\n\n\t\t// record update\n\t\ta->starttime = demo.timestamp;\n\t\tVectorCopy( viewangles, a->viewangles );\n\t\tdemo.lasttime = demo.timestamp;\n\t}\n\n\tif( msglen < 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"Demo message length < 0\\n\" );\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\tif( msglen > MAX_INIT_MSG )\n\t{\n\t\tCon_Reportf( S_ERROR \"Demo message %i > %i\\n\", msglen, MAX_INIT_MSG );\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\tif( msglen > 0 )\n\t{\n\t\tif( FS_Read( cls.demofile, buffer, msglen ) != msglen )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"Error reading demo message data\\n\" );\n\t\t\tCL_DemoCompleted();\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcls.netchan.last_received = host.realtime;\n\tcls.netchan.total_received += msglen;\n\t*length = msglen;\n\n\tif( cls.state != ca_active )\n\t\tCbuf_Execute();\n\treturn true;\n}\n\n/*\n=================\nCL_DemoReadMessage\n\nreads demo data and write it to client\n=================\n*/\nqboolean CL_DemoReadMessage( byte *buffer, size_t *length )\n{\n\tsize_t\t\tcurpos = 0, lastpos = 0;\n\tfloat\t\tfElapsedTime = 0.0f;\n\tqboolean\t\tswallowmessages = true;\n\tstatic int\ttdlastdemoframe = 0;\n\tbyte\t\t*userbuf = NULL;\n\tsize_t\t\tsize = 0;\n\tbyte\t\tcmd;\n\n\tif( !cls.demofile )\n\t{\n\t\tCL_DemoCompleted();\n\t\treturn false;\n\t}\n\n\tif(( !cl.background && ( cl.paused || cls.key_dest != key_game )) || cls.key_dest == key_console )\n\t{\n\t\tdemo.starttime += host.frametime;\n\t\treturn false; // paused\n\t}\n\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t\treturn CL_DemoReadMessageQuake( buffer, length );\n\n\tdo\n\t{\n\t\tqboolean\tbSkipMessage = false;\n\n\t\tif( !cls.demofile ) break;\n\t\tcurpos = FS_Tell( cls.demofile );\n\n\t\tif( !CL_ReadDemoCmdHeader( &cmd, &demo.timestamp ))\n\t\t\treturn false;\n\n\t\tfElapsedTime = CL_GetDemoPlaybackClock() - demo.starttime;\n\t\tif( !cls.timedemo ) bSkipMessage = ((demo.timestamp - cl_serverframetime()) >= fElapsedTime) ? true : false;\n\t\tif( cls.changelevel ) demo.framecount = 1;\n\n\t\t// changelevel issues\n\t\tif( demo.framecount <= 2 && ( fElapsedTime - demo.timestamp ) > host.frametime )\n\t\t\tdemo.starttime = CL_GetDemoPlaybackClock();\n\n\t\t// not ready for a message yet, put it back on the file.\n\t\tif( cmd != dem_norewind && cmd != dem_stop && bSkipMessage )\n\t\t{\n\t\t\t// never skip first message\n\t\t\tif( demo.framecount != 0 )\n\t\t\t{\n\t\t\t\tFS_Seek( cls.demofile, curpos, SEEK_SET );\n\t\t\t\treturn false; // not time yet.\n\t\t\t}\n\t\t}\n\n\t\t// we already have the usercmd_t for this frame\n\t\t// don't read next usercmd_t so predicting will work properly\n\t\tif( cmd == dem_usercmd && lastpos != 0 && demo.framecount != 0 )\n\t\t{\n\t\t\tFS_Seek( cls.demofile, lastpos, SEEK_SET );\n\t\t\treturn false; // not time yet.\n\t\t}\n\n\t\t// COMMAND HANDLERS\n\t\tswitch( cmd )\n\t\t{\n\t\tcase dem_jumptime:\n\t\t\tdemo.starttime = CL_GetDemoPlaybackClock();\n\t\t\treturn false; // time is changed, skip frame\n\t\tcase dem_stop:\n\t\t\tCL_DemoMoveToNextSection();\n\t\t\treturn false; // header is ended, skip frame\n\t\tcase dem_userdata:\n\t\t\tFS_Read( cls.demofile, &size, sizeof( int ));\n\t\t\tuserbuf = Mem_Malloc( cls.mempool, size );\n\t\t\tFS_Read( cls.demofile, userbuf, size );\n\n\t\t\tif( clgame.hInstance )\n\t\t\t\tclgame.dllFuncs.pfnDemo_ReadBuffer( size, userbuf );\n\t\t\tMem_Free( userbuf );\n\t\t\tuserbuf = NULL;\n\t\t\tbreak;\n\t\tcase dem_usercmd:\n\t\t\tCL_ReadDemoUserCmd( false );\n\t\t\tlastpos = FS_Tell( cls.demofile );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tswallowmessages = false;\n\t\t\tbreak;\n\t\t}\n\t} while( swallowmessages );\n\n\t// If we are playing back a timedemo, and we've already passed on a\n\t//  frame update for this host_frame tag, then we'll just skip this message.\n\tif( cls.timedemo && ( tdlastdemoframe == host.framecount ))\n\t{\n\t\tFS_Seek( cls.demofile, FS_Tell ( cls.demofile ) - 5, SEEK_SET );\n\t\treturn false;\n\t}\n\n\ttdlastdemoframe = host.framecount;\n\n\tif( !cls.demofile )\n\t\treturn false;\n\n\t// if not on \"LOADING\" section, check a few things\n\tif( demo.entryIndex )\n\t{\n\t\t// We are now on the second frame of a new section,\n\t\t// if so, reset start time (unless in a timedemo)\n\t\tif( demo.framecount == 1 && !cls.timedemo )\n\t\t{\n\t\t\t// cheat by moving the relative start time forward.\n\t\t\tdemo.starttime = CL_GetDemoPlaybackClock();\n\t\t}\n\t}\n\n\tdemo.framecount++;\n\tCL_ReadDemoSequence( false );\n\n\treturn CL_ReadRawNetworkData( buffer, length );\n}\n\nstatic void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next )\n{\n\tint\ti, i0, i1, imod;\n\tfloat\tat;\n\n\tif( cls.timedemo ) return;\n\n\timod = demo.angle_position - 1;\n\ti0 = (imod + 1) & ANGLE_MASK;\n\ti1 = (imod + 0) & ANGLE_MASK;\n\n\tif( demo.cmds[i0].starttime >= t )\n\t{\n\t\tfor( i = 0; i < ANGLE_BACKUP - 2; i++ )\n\t\t{\n\t\t\tat = demo.cmds[imod & ANGLE_MASK].starttime;\n\t\t\tif( at == 0.0f ) break;\n\n\t\t\tif( at < t )\n\t\t\t{\n\t\t\t\ti0 = (imod + 1) & ANGLE_MASK;\n\t\t\t\ti1 = (imod + 0) & ANGLE_MASK;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\timod--;\n\t\t}\n\t}\n\n\t*next = &demo.cmds[i0];\n\t*prev = &demo.cmds[i1];\n\n\t// avoid division by zero (probably this should never happens)\n\tif((*prev)->starttime == (*next)->starttime )\n\t{\n\t\t*prev = *next;\n\t\t*frac = 0.0f;\n\t\treturn;\n\t}\n\n\t// time spans the two entries\n\t*frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime );\n\t*frac = bound( 0.0f, *frac, 1.0f );\n}\n\n/*\n==============\nCL_DemoInterpolateAngles\n\nWe can predict or inpolate player movement with standed client code\nbut viewangles interpolate here\n==============\n*/\nvoid CL_DemoInterpolateAngles( void )\n{\n\tdemoangle_t\t*prev = NULL, *next = NULL;\n\tfloat\t\tfrac = 0.0f;\n\tfloat\t\tcurtime;\n\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t{\n\t\t// manually select next & prev states\n\t\tnext = &demo.cmds[(demo.angle_position - 0) & ANGLE_MASK];\n\t\tprev = &demo.cmds[(demo.angle_position - 1) & ANGLE_MASK];\n\t\tif( cl.skip_interp ) *prev = *next; // camera was teleported\n\t\tfrac = cl.lerpFrac;\n\t}\n\telse\n\t{\n\t\tcurtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime;\n\t\tif( curtime > demo.timestamp )\n\t\t\tcurtime = demo.timestamp; // don't run too far\n\n\t\tCL_DemoFindInterpolatedViewAngles( curtime, &frac, &prev, &next );\n\t}\n\n\tif( prev && next )\n\t{\n\t\tvec4_t\tq, q1, q2;\n\n\t\tAngleQuaternion( next->viewangles, q1, false );\n\t\tAngleQuaternion( prev->viewangles, q2, false );\n\t\tQuaternionSlerp( q2, q1, frac, q );\n\t\tQuaternionAngle( q, cl.viewangles );\n\t}\n\telse VectorCopy( cl.cmd.viewangles, cl.viewangles );\n}\n\n/*\n==============\nCL_FinishTimeDemo\n\nshow stats\n==============\n*/\nstatic void CL_FinishTimeDemo( void )\n{\n\tqboolean temp = host.allow_console;\n\tint\tframes;\n\tdouble\ttime;\n\n\tcls.timedemo = false;\n\n\t// the first frame didn't count\n\tframes = (host.framecount - cls.td_startframe) - 1;\n\ttime = host.realtime - cls.td_starttime;\n\tif( !time ) time = 1.0;\n\n\thost.allow_console = true;\n\tCon_Printf( \"timedemo result: %i frames %5.3f seconds %5.3f fps\\n\", frames, time, frames / time );\n\thost.allow_console = temp;\n\n\tif( Sys_CheckParm( \"-timedemo\" ))\n\t\tCL_Quit_f();\n}\n\n/*\n==============\nCL_StopPlayback\n\nCalled when a demo file runs out, or the user starts a game\n==============\n*/\nvoid CL_StopPlayback( void )\n{\n\tif( !cls.demoplayback ) return;\n\n\t// release demofile\n\tFS_Close( cls.demofile );\n\tcls.demoplayback = false;\n\tdemo.framecount = 0;\n\tcls.demofile = NULL;\n\n\tcls.olddemonum = Q_max( -1, cls.demonum - 1 );\n\tif( demo.directory.entries != NULL )\n\t\tMem_Free( demo.directory.entries );\n\tcls.td_lastframe = host.framecount;\n\tdemo.directory.numentries = 0;\n\tdemo.directory.entries = NULL;\n\tdemo.header.host_fps = 0.0;\n\tdemo.entry = NULL;\n\n\tcls.demoname[0] = '\\0';\t// clear demoname too\n\tgameui.globals->demoname[0] = '\\0';\n\n\tif( cls.timedemo )\n\t\tCL_FinishTimeDemo();\n\n\tif( cls.changedemo )\n\t{\n\t\tS_StopAllSounds( true );\n\t\tS_StopBackgroundTrack();\n\t}\n\telse\n\t{\n\t\t// let game known about demo state\n\t\tCvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\t\tcls.state = ca_disconnected;\n\t\tmemset( &cls.serveradr, 0, sizeof( cls.serveradr ) );\n\t\tcls.set_lastdemo = false;\n\t\tS_StopBackgroundTrack();\n\t\tcls.connect_time = 0;\n\t\tcls.demonum = -1;\n\t\tcls.signon = 0;\n\n\t\t// and finally clear the state\n\t\tCL_ClearState ();\n\t}\n}\n\n/*\n==================\nCL_GetDemoComment\n==================\n*/\nint GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment )\n{\n\tfile_t\t\t*demfile;\n\tdemoheader_t\tdemohdr;\n\tdemodirectory_t\tdirectory;\n\tdemoentry_t\tentry;\n\tfloat\t\tplaytime = 0.0f;\n\tint\t\ti;\n\n\tif( !comment ) return false;\n\n\tdemfile = FS_Open( demoname, \"rb\", false );\n\tif( !demfile )\n\t{\n\t\tcomment[0] = '\\0';\n\t\treturn false;\n\t}\n\n\t// read in the m_DemoHeader\n\tFS_Read( demfile, &demohdr, sizeof( demoheader_t ));\n\n\tif( demohdr.id != IDEMOHEADER )\n\t{\n\t\tFS_Close( demfile );\n\t\tQ_strncpy( comment, \"<corrupted>\", MAX_STRING );\n\t\treturn false;\n\t}\n\n\tif(( demohdr.net_protocol != PROTOCOL_VERSION &&\n\t\tdemohdr.net_protocol != PROTOCOL_LEGACY_VERSION ) ||\n\t\tdemohdr.dem_protocol != DEMO_PROTOCOL )\n\t{\n\t\tFS_Close( demfile );\n\t\tQ_strncpy( comment, \"<invalid protocol>\", MAX_STRING );\n\t\treturn false;\n\t}\n\n\t// now read in the directory structure.\n\tFS_Seek( demfile, demohdr.directory_offset, SEEK_SET );\n\tFS_Read( demfile, &directory.numentries, sizeof( int ));\n\n\tif( directory.numentries < 1 || directory.numentries > 1024 )\n\t{\n\t\tFS_Close( demfile );\n\t\tQ_strncpy( comment, \"<corrupted>\", MAX_STRING );\n\t\treturn false;\n\t}\n\n\tfor( i = 0; i < directory.numentries; i++ )\n\t{\n\t\tFS_Read( demfile, &entry, sizeof( demoentry_t ));\n\t\tplaytime += entry.playback_time;\n\t}\n\n\t// split comment to sections\n\tQ_strncpy( comment, demohdr.mapname, CS_SIZE );\n\tQ_strncpy( comment + CS_SIZE, demohdr.comment, CS_SIZE );\n\tQ_snprintf( comment + CS_SIZE * 2, CS_TIME, \"%g sec\", playtime );\n\n\t// all done\n\tFS_Close( demfile );\n\n\treturn true;\n}\n\n/*\n==================\nCL_NextDemo\n\nCalled when a demo finishes\n==================\n*/\nstatic qboolean CL_NextDemo( void )\n{\n\tchar\tstr[MAX_QPATH];\n\n\tif( cls.demonum == -1 )\n\t\treturn false; // don't play demos\n\tS_StopAllSounds( true );\n\n\tif( !cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS )\n\t{\n\t\tcls.demonum = 0;\n\t\tif( !cls.demos[cls.demonum][0] )\n\t\t{\n\t\t\tCon_Printf( \"no demos listed with startdemos\\n\" );\n\t\t\tcls.demonum = -1;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tQ_snprintf( str, MAX_STRING, \"playdemo %s\\n\", cls.demos[cls.demonum] );\n\tCbuf_InsertText( str );\n\tcls.demonum++;\n\n\treturn true;\n}\n\n/*\n==================\nCL_CheckStartupDemos\n\nqueue demos loop after movie playing\n==================\n*/\nvoid CL_CheckStartupDemos( void )\n{\n\tif( !cls.demos_pending )\n\t\treturn; // no demos in loop\n\n\tif( cls.movienum != -1 )\n\t\treturn; // wait until movies finished\n\n\tif( GameState->nextstate != STATE_RUNFRAME || cls.demoplayback )\n\t{\n\t\t// commandline override\n\t\tcls.demos_pending = false;\n\t\tcls.demonum = -1;\n\t\treturn;\n\t}\n\n\t// run demos loop in background mode\n\tCvar_DirectSet( &v_dark, \"1\" );\n\tcls.demos_pending = false;\n\tcls.demonum = 0;\n\tCL_NextDemo ();\n}\n\n/*\n==================\nCL_DemoGetName\n==================\n*/\nstatic void CL_DemoGetName( int lastnum, char *filename, size_t size )\n{\n\tif( lastnum < 0 || lastnum > 9999 )\n\t{\n\t\t// bound\n\t\tQ_strncpy( filename, \"demo9999\", size );\n\t\treturn;\n\t}\n\n\tQ_snprintf( filename, size, \"demo%04d\", lastnum );\n}\n\n/*\n====================\nCL_Record_f\n\nrecord <demoname>\nBegins recording a demo from the current position\n====================\n*/\nvoid CL_Record_f( void )\n{\n\tstring\t\tdemoname, demopath;\n\tconst char\t*name;\n\tint\t\tn;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tname = \"new\";\n\t}\n\telse if( Cmd_Argc() == 2 )\n\t{\n\t\tname = Cmd_Argv( 1 );\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_USAGE \"record <demoname>\\n\" );\n\t\treturn;\n\t}\n\n\tif( cls.demorecording )\n\t{\n\t\tCon_Printf( \"Already recording.\\n\");\n\t\treturn;\n\t}\n\n\tif( cls.demoplayback )\n\t{\n\t\tCon_Printf( \"Can't record during demo playback.\\n\");\n\t\treturn;\n\t}\n\n\tif( !cls.demoheader || cls.state != ca_active )\n\t{\n\t\tCon_Printf( \"You must be in a level to record.\\n\");\n\t\treturn;\n\t}\n\n\tif( !Q_stricmp( name, \"new\" ))\n\t{\n\t\t// scan for a free filename\n\t\tfor( n = 0; n < 10000; n++ )\n\t\t{\n\t\t\tCL_DemoGetName( n, demoname, sizeof( demoname ));\n\t\t\tQ_snprintf( demopath, sizeof( demopath ), \"%s.dem\", demoname );\n\n\t\t\tif( !FS_FileExists( demopath, true ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( n == 10000 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"no free slots for demo recording\\n\" );\n\t\t\treturn;\n\t\t}\n\t}\n\telse Q_strncpy( demoname, name, sizeof( demoname ));\n\n\t// open the demo file\n\tQ_snprintf( demopath, sizeof( demopath ), \"%s.dem\", demoname );\n\n\t// make sure that old demo is removed\n\tif( FS_FileExists( demopath, false ))\n\t\tFS_Delete( demopath );\n\n\tQ_strncpy( cls.demoname, demoname, sizeof( cls.demoname ));\n\tQ_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname ));\n\n\tCL_WriteDemoHeader( demopath );\n}\n\nstatic qboolean CL_ParseDemoHeader( const char *callee, const char *filename, file_t *f, demoheader_t *hdr, int32_t *numentries )\n{\n\tif( FS_Read( f, hdr, sizeof( *hdr )) != sizeof( *hdr ) || hdr->id != IDEMOHEADER )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s is not in supported format or not a demo file\\n\", callee, filename );\n\t\treturn false;\n\t}\n\n\t// force null terminate strings\n\thdr->mapname[sizeof( hdr->mapname ) - 1] = 0;\n\thdr->comment[sizeof( hdr->comment ) - 1] = 0;\n\thdr->gamedir[sizeof( hdr->gamedir ) - 1] = 0;\n\n\tif( hdr->dem_protocol != DEMO_PROTOCOL )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: demo protocol outdated (%i should be %i)\\n\",\n\t\t\tcallee, hdr->net_protocol, DEMO_PROTOCOL );\n\t\treturn false;\n\t}\n\n\tif( hdr->net_protocol != PROTOCOL_VERSION && hdr->net_protocol != PROTOCOL_LEGACY_VERSION && hdr->net_protocol != PROTOCOL_GOLDSRC_VERSION_DEMO )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: net protocol outdated (%i should be %i or %i)\\n\",\n\t\t\tcallee, hdr->net_protocol, PROTOCOL_VERSION, PROTOCOL_LEGACY_VERSION );\n\t\treturn false;\n\t}\n\n\tif( FS_Seek( f, hdr->directory_offset, SEEK_SET ) < 0\n\t\t|| FS_Read( f, numentries, sizeof( *numentries )) != sizeof( *numentries ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: can't find directory offset in %s, demo file corrupted\\n\",\n\t\t\tcallee, filename );\n\t\treturn false;\n\t}\n\n\tif(( *numentries < 1 ) || ( *numentries > 1024 ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: demo have bogus # of directory entries: %i\\n\",\n\t\t\tcallee, *numentries );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n====================\nCL_PlayDemo_f\n\nplaydemo <demoname>\n====================\n*/\nvoid CL_PlayDemo_f( void )\n{\n\tchar\tfilename[MAX_QPATH];\n\tchar\tdemoname[MAX_QPATH];\n\tint\ti, ident;\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"%s <demoname>\\n\", Cmd_Argv( 0 ));\n\t\treturn;\n\t}\n\n\tif( cls.demoplayback )\n\t\tCL_StopPlayback();\n\n\tif( cls.demorecording )\n\t{\n\t\tCon_Printf( \"Can't playback during demo record.\\n\");\n\t\treturn;\n\t}\n\n\tQ_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname ));\n\tCOM_StripExtension( demoname );\n\tQ_snprintf( filename, sizeof( filename ), \"%s.dem\", demoname );\n\n\t// hidden parameter\n\tif( Cmd_Argc() > 2 )\n\t\tcls.set_lastdemo = Q_atoi( Cmd_Argv( 2 ));\n\n\t// member last demo\n\tif( cls.set_lastdemo )\n\t\tCvar_Set( \"lastdemo\", demoname );\n\n\tif( !FS_FileExists( filename, true ))\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't open %s\\n\", filename );\n\t\tCL_DemoAborted();\n\t\treturn;\n\t}\n\n\tcls.demofile = FS_Open( filename, \"rb\", true );\n\tQ_strncpy( cls.demoname, demoname, sizeof( cls.demoname ));\n\tQ_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname ));\n\n\tFS_Read( cls.demofile, &ident, sizeof( int ));\n\tFS_Seek( cls.demofile, 0, SEEK_SET ); // rewind back to start\n\tcls.forcetrack = 0;\n\n\t// check for quake demos\n\tif( ident != IDEMOHEADER )\n\t{\n\t\tint\tc, neg = false;\n\n\t\tdemo.header.host_fps = host_maxfps.value;\n\n\t\twhile(( c = FS_Getc( cls.demofile )) != '\\n' )\n\t\t{\n\t\t\tif( c == '-' ) neg = true;\n\t\t\telse cls.forcetrack = cls.forcetrack * 10 + (c - '0');\n\t\t}\n\n\t\tif( neg ) cls.forcetrack = -cls.forcetrack;\n\t\tCL_DemoStartPlayback( DEMO_QUAKE1 );\n\t\tcls.legacymode = PROTO_QUAKE;\n\t\treturn; // quake demo is started\n\t}\n\n\t// read in the demo header\n\tif( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, cls.demofile, &demo.header, &demo.directory.numentries ))\n\t{\n\t\tCL_DemoAborted();\n\t\treturn;\n\t}\n\n\t// allocate demo entries\n\tdemo.directory.entries = Mem_Malloc( cls.mempool, sizeof( *demo.directory.entries ) * demo.directory.numentries );\n\n\tfor( i = 0; i < demo.directory.numentries; i++ )\n\t{\n\t\tdemoentry_t *entry = &demo.directory.entries[i];\n\n\t\tif( FS_Read( cls.demofile, entry, sizeof( *entry )) != sizeof( *entry ))\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: demo entry %i of %s corrupted\", Cmd_Argv( 0 ), i, filename );\n\t\t\tCL_DemoAborted();\n\t\t\treturn;\n\t\t}\n\n\t\tentry->description[sizeof( entry->description ) - 1] = 0;\n\t}\n\n\tdemo.entryIndex = 0;\n\tdemo.entry = &demo.directory.entries[demo.entryIndex];\n\n\tFS_Seek( cls.demofile, demo.entry->offset, SEEK_SET );\n\n\tCL_DemoStartPlayback( DEMO_XASH3D );\n\n\t// must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol\n\tcls.legacymode = CL_GetProtocolFromDemo( demo.header.net_protocol );\n\n\t// g-cont. is this need?\n\tQ_strncpy( cls.servername, demoname, sizeof( cls.servername ));\n\n\t// begin a playback demo\n}\n\n/*\n====================\nCL_TimeDemo_f\n\ntimedemo <demoname>\n====================\n*/\nvoid CL_TimeDemo_f( void )\n{\n\tCL_PlayDemo_f ();\n\n\t// cls.td_starttime will be grabbed at the second frame of the demo, so\n\t// all the loading time doesn't get counted\n\tcls.timedemo = true;\n\tcls.td_starttime = host.realtime;\n\tcls.td_startframe = host.framecount;\n\tcls.td_lastframe = -1;\t\t// get a new message this frame\n}\n\n/*\n==================\nCL_StartDemos_f\n==================\n*/\nvoid CL_StartDemos_f( void )\n{\n\tint\ti, c;\n\n\tif( cls.key_dest != key_menu )\n\t{\n\t\tCon_Printf( \"'startdemos' is not valid from the console\\n\" );\n\t\treturn;\n\t}\n\n\tc = Cmd_Argc() - 1;\n\tif( c > MAX_DEMOS )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: max %i demos in demoloop\\n\", __func__, MAX_DEMOS );\n\t\tc = MAX_DEMOS;\n\t}\n\n\tCon_Printf( \"%i demo%s in loop\\n\", c, (c > 1) ? \"s\" : \"\" );\n\n\tfor( i = 1; i < c + 1; i++ )\n\t\tQ_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] ));\n\tcls.demos_pending = true;\n}\n\n/*\n==================\nCL_Demos_f\n\nReturn to looping demos\n==================\n*/\nvoid CL_Demos_f( void )\n{\n\tif( cls.key_dest != key_menu )\n\t{\n\t\tCon_Printf( \"'demos' is not valid from the console\\n\" );\n\t\treturn;\n\t}\n\n\t// demos loop are not running\n\tif( cls.olddemonum == -1 )\n\t\treturn;\n\n\tcls.demonum = cls.olddemonum;\n\n\t// run demos loop in background mode\n\tif( !SV_Active() && !cls.demoplayback )\n\t\tCL_NextDemo ();\n}\n\n\n/*\n====================\nCL_Stop_f\n\nstop any client activity\n====================\n*/\nvoid CL_Stop_f( void )\n{\n\t// stop all\n\tCL_StopRecord();\n\tCL_StopPlayback();\n\tSCR_StopCinematic();\n\n\t// stop background track that was runned from the console\n\tif( !SV_Active( ))\n\t{\n\t\tS_StopBackgroundTrack();\n\t}\n}\n\nvoid CL_ListDemo_f( void )\n{\n\tdemoheader_t hdr;\n\tint32_t num_entries;\n\tfile_t *f;\n\tchar filename[MAX_QPATH];\n\tchar demoname[MAX_QPATH];\n\tint i;\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"%s <demoname>\\n\", Cmd_Argv( 0 ));\n\t\treturn;\n\t}\n\n\tQ_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname ));\n\tCOM_StripExtension( demoname );\n\tQ_snprintf( filename, sizeof( filename ), \"%s.dem\", demoname );\n\n\tf = FS_Open( filename, \"rb\", true );\n\tif( !f )\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't open %s\\n\", filename );\n\t\treturn;\n\t}\n\n\tif( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, f, &hdr, &num_entries ))\n\t{\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Demo contents for %s:\\n\"\n\t\t\"\\tProtocol: %i net/%i demo\\n\"\n\t\t\"\\tFPS: %g\\n\"\n\t\t\"\\tMap: %s\\n\"\n\t\t\"\\tComment: %s\\n\"\n\t\t\"\\tGame: %s\\n\",\n\t\tfilename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname,\n\t\thdr.comment, hdr.gamedir );\n\n\tfor( i = 0; i < num_entries; i++ )\n\t{\n\t\tdemoentry_t entry;\n\n\t\tCon_Printf( \"Demo entry #%i:\\n\", i );\n\n\t\tif( FS_Read( f, &entry, sizeof( entry )) != sizeof( entry ))\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"can't read demo entry\\n\" );\n\t\t\tFS_Close( f );\n\t\t\treturn;\n\t\t}\n\n\t\tentry.description[sizeof( entry.description ) - 1] = 0;\n\n\t\tif( entry.entrytype == DEMO_STARTUP )\n\t\t{\n\t\t\t// startup entries don't have anything useful\n\t\t\tCon_Printf( \"\\tEntry type: \" S_YELLOW \"startup\" S_DEFAULT \"\\n\" );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( \"\\tEntry type: \" S_GREEN \"normal\" S_DEFAULT \" (%i)\\n\"\n\t\t\t\t\"\\tEntry playback time/frames: %.2f seconds/%i frames\\n\"\n\t\t\t\t\"\\tEntry flags: 0x%x\\n\"\n\t\t\t\t\"\\tEntry description: %s\\n\",\n\t\t\t\tentry.entrytype, entry.playback_time, entry.playback_frames,\n\t\t\t\tentry.flags, entry.description );\n\t\t}\n\t}\n\n\tFS_Close( f );\n}\n"
  },
  {
    "path": "engine/client/cl_efrag.c",
    "content": "/*\ngl_refrag.c - store entity fragments\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"entity_types.h\"\n#include \"studio.h\"\n#include \"world.h\" // BOX_ON_PLANE_SIDE\n#include \"client.h\"\n#include \"xash3d_mathlib.h\"\n\n/*\n===============================================================================\n\n\t\t\tENTITY FRAGMENT FUNCTIONS\n\n===============================================================================\n*/\n\n#define NUM_EFRAGS_ALLOC 64 // alloc 64 efrags (1-2kb each alloc)\n\nstatic efrag_t\t**lastlink;\nstatic mnode_t\t*r_pefragtopnode;\nstatic vec3_t\tr_emins, r_emaxs;\nstatic cl_entity_t\t*r_addent;\nstatic int cl_efrags_num;\nstatic efrag_t *cl_efrags;\n\nstatic efrag_t *CL_AllocEfrags( int num )\n{\n\tint i;\n\tefrag_t *efrags;\n\n\tif( !cl.worldmodel )\n\t{\n\t\tHost_Error( \"%s: called with NULL world\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tif( num == 0 )\n\t\treturn NULL;\n\n\t// set world to be the owner, so it will get automatically cleaned up\n\tefrags = Mem_Calloc( cl.worldmodel->mempool, sizeof( *efrags ) * num );\n\n\t// initialize linked list\n\tfor( i = 0; i < num - 1; i++ )\n\t\tefrags[i].entnext = &efrags[i + 1];\n\n\tcl_efrags_num += num;\n\n\treturn efrags;\n}\n\n/*\n==============\nCL_ClearEfrags\n==============\n*/\nvoid CL_ClearEfrags( void )\n{\n\tcl_efrags_num = 0;\n\tcl_efrags = NULL;\n}\n\n/*\n===================\nR_SplitEntityOnNode\n===================\n*/\nstatic void R_SplitEntityOnNode( mnode_t *node )\n{\n\tefrag_t\t*ef;\n\tmleaf_t\t*leaf;\n\tint\tsides;\n\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn;\n\n\t// add an efrag if the node is a leaf\n\tif( node->contents < 0 )\n\t{\n\t\tif( !r_pefragtopnode )\n\t\t\tr_pefragtopnode = node;\n\n\t\tleaf = (mleaf_t *)node;\n\n\t\t// grab an efrag off the free list\n\t\tef = cl_efrags;\n\t\tif( !ef )\n\t\t\tef = CL_AllocEfrags( NUM_EFRAGS_ALLOC );\n\n\t\tcl_efrags = ef->entnext;\n\t\tef->entity = r_addent;\n\n\t\t// add the entity link\n\t\t*lastlink = ef;\n\t\tlastlink = &ef->entnext;\n\t\tef->entnext = NULL;\n\n\t\t// set the leaf links\n\t\tef->leaf = leaf;\n\t\tef->leafnext = leaf->efrags;\n\t\tleaf->efrags = ef;\n\t\treturn;\n\t}\n\n\t// NODE_MIXED\n\tsides = BOX_ON_PLANE_SIDE( r_emins, r_emaxs, node->plane );\n\n\tif( sides == 3 )\n\t{\n\t\t// split on this plane\n\t\t// if this is the first splitter of this bmodel, remember it\n\t\tif( !r_pefragtopnode ) r_pefragtopnode = node;\n\t}\n\n\t// recurse down the contacted sides\n\tif( sides & 1 )\n\t\tR_SplitEntityOnNode( node_child( node, 0, cl.worldmodel ));\n\tif( sides & 2 )\n\t\tR_SplitEntityOnNode( node_child( node, 1, cl.worldmodel ));\n}\n\n/*\n===========\nR_AddEfrags\n===========\n*/\nvoid R_AddEfrags( cl_entity_t *ent )\n{\n\tmatrix3x4\ttransform;\n\tvec3_t\toutmins, outmaxs;\n\tint\ti;\n\n\tif( !ent->model )\n\t\treturn;\n\n\tr_addent = ent;\n\tlastlink = &ent->efrag;\n\tr_pefragtopnode = NULL;\n\n\t// handle entity rotation for right bbox expanding\n\tMatrix3x4_CreateFromEntity( transform, ent->angles, vec3_origin, 1.0f );\n\tMatrix3x4_TransformAABB( transform, ent->model->mins, ent->model->maxs, outmins, outmaxs );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tr_emins[i] = ent->origin[i] + outmins[i];\n\t\tr_emaxs[i] = ent->origin[i] + outmaxs[i];\n\t}\n\n\tR_SplitEntityOnNode( cl.worldmodel->nodes );\n\tent->topnode = r_pefragtopnode;\n}\n\n/*\n================\nR_StoreEfrags\n\n================\n*/\nvoid R_StoreEfrags( efrag_t **ppefrag, int framecount )\n{\n\tefrag_t *pefrag;\n\tcl_entity_t *pent;\n\tmodel_t *clmodel;\n\n\twhile(( pefrag = *ppefrag ) != NULL )\n\t{\n\t\tpent = pefrag->entity;\n\t\tclmodel = pent->model;\n\n\t\t// how this could happen?\n\t\tif( unlikely( clmodel->type < mod_brush || clmodel->type > mod_studio ))\n\t\t\tcontinue;\n\n\t\tif( pent->visframe != framecount )\n\t\t{\n\t\t\tif( CL_AddVisibleEntity( pent, ET_FRAGMENTED ))\n\t\t\t{\n\t\t\t\t// mark that we've recorded this entity for this frame\n\t\t\t\tpent->curstate.messagenum = cl.parsecount;\n\t\t\t\tpent->visframe = framecount;\n\t\t\t}\n\t\t}\n\n\t\tppefrag = &pefrag->leafnext;\n\t}\n}\n"
  },
  {
    "path": "engine/client/cl_efx.c",
    "content": "\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"customentity.h\"\n#include \"r_efx.h\"\n#include \"cl_tent.h\"\n#include \"pm_local.h\"\n#define PART_SIZE\tQ_max( 0.5f, cl_draw_particles.value )\n\n/*\n==============================================================\n\nPARTICLES MANAGEMENT\n\n==============================================================\n*/\n// particle ramps\nstatic int ramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 };\nstatic int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 };\nstatic int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 };\nstatic int gSparkRamp[9] = { 0xfe, 0xfd, 0xfc, 0x6f, 0x6e, 0x6d, 0x6c, 0x67, 0x60 };\n\nstatic CVAR_DEFINE_AUTO( tracerspeed, \"6000\", 0, \"tracer speed\" );\nstatic CVAR_DEFINE_AUTO( tracerlength, \"0.8\", 0, \"tracer length factor\" );\nstatic CVAR_DEFINE_AUTO( traceroffset, \"30\", 0, \"tracer starting offset\" );\n\nstatic particle_t\t*cl_active_particles;\nstatic particle_t\t*cl_active_tracers;\nstatic particle_t\t*cl_free_particles;\nstatic particle_t\t*cl_particles = NULL;\t// particle pool\nstatic vec3_t\tcl_avelocities[NUMVERTEXNORMALS];\nstatic float\tcl_lasttimewarn = 0.0f;\n\n// expand debugging BBOX particle hulls by this many units.\n#define BOX_GAP\t0.0f\n\n/*\n================\nR_LookupColor\n\nfind nearest color in particle palette\n================\n*/\nshort GAME_EXPORT R_LookupColor( byte r, byte g, byte b )\n{\n\tint\ti, best;\n\tfloat\tdiff, bestdiff;\n\tfloat\trf, gf, bf;\n\n\tbestdiff = 999999;\n\tbest = -1;\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\trf = r - clgame.palette[i].r;\n\t\tgf = g - clgame.palette[i].g;\n\t\tbf = b - clgame.palette[i].b;\n\n\t\t// convert color to monochrome\n\t\tdiff = rf * (rf * 0.2f) + gf * (gf * 0.5f) + bf * (bf * 0.3f);\n\n\t\tif ( diff < bestdiff )\n\t\t{\n\t\t\tbestdiff = diff;\n\t\t\tbest = i;\n\t\t}\n\t}\n\n\treturn best;\n}\n\n/*\n================\nR_GetPackedColor\n\nin hardware mode does nothing\n================\n*/\nvoid GAME_EXPORT R_GetPackedColor( short *packed, short color )\n{\n\tif( packed ) *packed = 0;\n}\n\n/*\n================\nCL_InitParticles\n\n================\n*/\nvoid CL_InitParticles( void )\n{\n\tint\ti;\n\n\tcl_particles = Mem_Calloc( cls.mempool, sizeof( particle_t ) * GI->max_particles );\n\tCL_ClearParticles ();\n\n\t// this is used for EF_BRIGHTFIELD\n\tfor( i = 0; i < NUMVERTEXNORMALS; i++ )\n\t{\n\t\tcl_avelocities[i][0] = COM_RandomFloat( 0.0f, 2.55f );\n\t\tcl_avelocities[i][1] = COM_RandomFloat( 0.0f, 2.55f );\n\t\tcl_avelocities[i][2] = COM_RandomFloat( 0.0f, 2.55f );\n\t}\n\n\tCvar_RegisterVariable( &tracerspeed );\n\tCvar_RegisterVariable( &tracerlength );\n\tCvar_RegisterVariable( &traceroffset );\n}\n\n/*\n================\nCL_ClearParticles\n\n================\n*/\nvoid CL_ClearParticles( void )\n{\n\tint\ti;\n\n\tif( !cl_particles ) return;\n\n\tcl_free_particles = cl_particles;\n\tcl_active_particles = NULL;\n\tcl_active_tracers = NULL;\n\n\tfor( i = 0; i < GI->max_particles - 1; i++ )\n\t\tcl_particles[i].next = &cl_particles[i+1];\n\n\tcl_particles[GI->max_particles-1].next = NULL;\n}\n\n/*\n================\nCL_FreeParticles\n\n================\n*/\nvoid CL_FreeParticles( void )\n{\n\tif( cl_particles )\n\t\tMem_Free( cl_particles );\n\tcl_particles = NULL;\n}\n\n/*\n================\nCL_AllocParticleFast\n\nunconditionally give new particle pointer from cl_free_particles\n================\n*/\nparticle_t *CL_AllocParticleFast( void )\n{\n\tparticle_t *p = NULL;\n\n\tif( cl_free_particles )\n\t{\n\t\tp = cl_free_particles;\n\t\tcl_free_particles = p->next;\n\t}\n\n\treturn p;\n}\n\n/*\n================\nR_AllocParticle\n\ncan return NULL if particles is out\n================\n*/\nparticle_t * GAME_EXPORT R_AllocParticle( void (*callback)( particle_t*, float ))\n{\n\tparticle_t\t*p;\n\n\tif( !cl_draw_particles.value )\n\t\treturn NULL;\n\n\t// never alloc particles when we not in game\n\tif( cl_clientframetime() == 0.0 ) return NULL;\n\n\tif( !cl_free_particles )\n\t{\n\t\tif( cl_lasttimewarn < host.realtime )\n\t\t{\n\t\t\t// don't spam about overflow\n\t\t\tCon_DPrintf( S_ERROR \"Overflow %d particles\\n\", GI->max_particles );\n\t\t\tcl_lasttimewarn = host.realtime + 1.0f;\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tp = cl_free_particles;\n\tcl_free_particles = p->next;\n\tp->next = cl_active_particles;\n\tcl_active_particles = p;\n\n\t// clear old particle\n\tp->type = pt_static;\n\tVectorClear( p->vel );\n\tVectorClear( p->org );\n\tp->packedColor = 0;\n\tp->die = cl.time;\n\tp->color = 0;\n\tp->ramp = 0;\n\n\tif( callback )\n\t{\n\t\tp->type = pt_clientcustom;\n\t\tp->callback = callback;\n\t}\n\n\treturn p;\n}\n\n/*\n================\nR_AllocTracer\n\ncan return NULL if particles is out\n================\n*/\nstatic particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life )\n{\n\tparticle_t\t*p;\n\n\tif( !cl_draw_tracers.value )\n\t\treturn NULL;\n\n\t// never alloc particles when we not in game\n\tif( cl_clientframetime() == 0.0 ) return NULL;\n\n\tif( !cl_free_particles )\n\t{\n\t\tif( cl_lasttimewarn < host.realtime )\n\t\t{\n\t\t\t// don't spam about overflow\n\t\t\tCon_DPrintf( S_ERROR \"Overflow %d tracers\\n\", GI->max_particles );\n\t\t\tcl_lasttimewarn = host.realtime + 1.0f;\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tp = cl_free_particles;\n\tcl_free_particles = p->next;\n\tp->next = cl_active_tracers;\n\tcl_active_tracers = p;\n\n\t// clear old particle\n\tp->type = pt_static;\n\tVectorCopy( org, p->org );\n\tVectorCopy( vel, p->vel );\n\tp->die = cl.time + life;\n\tp->ramp = tracerlength.value;\n\tp->color = TRACER_COLORINDEX_DEFAULT; // select custom color\n\tp->packedColor = 255; // alpha\n\n\treturn p;\n}\n/*\n==============================================================\n\nVIEWBEAMS MANAGEMENT\n\n==============================================================\n*/\nstatic BEAM\t\t*cl_active_beams;\nstatic BEAM\t\t*cl_free_beams;\nstatic BEAM\t\t*cl_viewbeams = NULL;\t\t// beams pool\n\n\n/*\n==============================================================\n\nBEAM ALLOCATE & PROCESSING\n\n==============================================================\n*/\n\n\n/*\n==============\nR_BeamSetAttributes\n\nset beam attributes\n==============\n*/\nstatic void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame )\n{\n\tpbeam->frame = (float)startFrame;\n\tpbeam->frameRate = framerate;\n\tpbeam->r = r;\n\tpbeam->g = g;\n\tpbeam->b = b;\n}\n\n\n\n/*\n==============\nR_BeamAlloc\n\n==============\n*/\nstatic BEAM *R_BeamAlloc( void )\n{\n\tBEAM\t*pBeam;\n\n\tif( !cl_free_beams )\n\t\treturn NULL;\n\n\tpBeam = cl_free_beams;\n\tcl_free_beams = pBeam->next;\n\tmemset( pBeam, 0, sizeof( *pBeam ));\n\tpBeam->next = cl_active_beams;\n\tcl_active_beams = pBeam;\n\tpBeam->die = cl.time;\n\n\treturn pBeam;\n}\n\n/*\n==============\nR_BeamFree\n\n==============\n*/\nstatic void R_BeamFree( BEAM *pBeam )\n{\n\t// free particles that have died off.\n\tR_FreeDeadParticles( &pBeam->particles );\n\n\t// now link into free list;\n\tpBeam->next = cl_free_beams;\n\tcl_free_beams = pBeam;\n}\n\n\n/*\n================\nCL_InitViewBeams\n\n================\n*/\nvoid CL_InitViewBeams( void )\n{\n\tcl_viewbeams = Mem_Calloc( cls.mempool, sizeof( BEAM ) * GI->max_beams );\n\tCL_ClearViewBeams();\n}\n\n/*\n================\nCL_ClearViewBeams\n\n================\n*/\nvoid CL_ClearViewBeams( void )\n{\n\tint\ti;\n\n\tif( !cl_viewbeams ) return;\n\n\t// clear beams\n\tcl_free_beams = cl_viewbeams;\n\tcl_active_beams = NULL;\n\n\tfor( i = 0; i < GI->max_beams - 1; i++ )\n\t\tcl_viewbeams[i].next = &cl_viewbeams[i+1];\n\tcl_viewbeams[GI->max_beams - 1].next = NULL;\n}\n\n/*\n================\nCL_FreeViewBeams\n\n================\n*/\nvoid CL_FreeViewBeams( void )\n{\n\tif( cl_viewbeams )\n\t\tMem_Free( cl_viewbeams );\n\tcl_viewbeams = NULL;\n}\n\n/*\n==============\nR_BeamGetEntity\n\nextract entity number from index\nhandle user entities\n==============\n*/\ncl_entity_t *R_BeamGetEntity( int index )\n{\n\tif( index < 0 )\n\t\treturn clgame.dllFuncs.pfnGetUserEntity( BEAMENT_ENTITY( -index ));\n\treturn CL_GetEntityByIndex( BEAMENT_ENTITY( index ));\n}\n\n/*\n==============\nCL_KillDeadBeams\n\n==============\n*/\nvoid CL_KillDeadBeams( cl_entity_t *pDeadEntity )\n{\n\tBEAM\t\t*pbeam;\n\tBEAM\t\t*pnewlist;\n\tBEAM\t\t*pnext;\n\tparticle_t\t*pHead;\t// build a new list to replace cl_active_beams.\n\n\tpbeam = cl_active_beams;\t// old list.\n\tpnewlist = NULL;\t\t// new list.\n\n\twhile( pbeam )\n\t{\n\t\tcl_entity_t *beament;\n\t\tpnext = pbeam->next;\n\n\t\t// link into new list.\n\t\tif( R_BeamGetEntity( pbeam->startEntity ) != pDeadEntity )\n\t\t{\n\t\t\tpbeam->next = pnewlist;\n\t\t\tpnewlist = pbeam;\n\n\t\t\tpbeam = pnext;\n\t\t\tcontinue;\n\t\t}\n\n\t\tpbeam->flags &= ~(FBEAM_STARTENTITY | FBEAM_ENDENTITY);\n\n\t\tif( pbeam->type != TE_BEAMFOLLOW )\n\t\t{\n\t\t\t// remove beam\n\t\t\tpbeam->die = cl.time - 0.1f;\n\n\t\t\t// kill off particles\n\t\t\tpHead = pbeam->particles;\n\t\t\twhile( pHead )\n\t\t\t{\n\t\t\t\tpHead->die = cl.time - 0.1f;\n\t\t\t\tpHead = pHead->next;\n\t\t\t}\n\n\t\t\t// free the beam\n\t\t\tR_BeamFree( pbeam );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// stay active\n\t\t\tpbeam->next = pnewlist;\n\t\t\tpnewlist = pbeam;\n\t\t}\n\n\t\tpbeam = pnext;\n\t}\n\n\t// We now have a new list with the bogus stuff released.\n\tcl_active_beams = pnewlist;\n}\n\n\n/*\n===============\nCL_ReadLineFile_f\n\nOptimized version of pointfile - use beams instead of particles\n===============\n*/\nvoid CL_ReadLineFile_f( void )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tvec3_t\t\tp1, p2;\n\tint\t\tcount, modelIndex;\n\tchar\t\tfilename[MAX_QPATH];\n\tmodel_t\t\t*model;\n\tstring\t\ttoken;\n\n\tQ_snprintf( filename, sizeof( filename ), \"maps/%s.lin\", clgame.mapname );\n\tafile = FS_LoadFile( filename, NULL, false );\n\n\tif( !afile )\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't open %s\\n\", filename );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Reading %s...\\n\", filename );\n\n\tcount = 0;\n\tpfile = (char *)afile;\n\tmodel = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex );\n\n\twhile( 1 )\n\t{\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp1[0] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp1[1] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp1[2] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\n\t\tif( token[0] != '-' )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s is corrupted\\n\", filename );\n\t\t\tbreak;\n\t\t}\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp2[0] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp2[1] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\tp2[2] = Q_atof( token );\n\n\t\tcount++;\n\n\t\tif( !R_BeamPoints( p1, p2, modelIndex, 0, 2, 0, 255, 0, 0, 0, 255.0f, 0.0f, 0.0f ))\n\t\t{\n\t\t\tif( !model || model->type != mod_sprite )\n\t\t\t\tCon_Printf( S_ERROR \"failed to load \\\"%s\\\"!\\n\", DEFAULT_LASERBEAM_PATH );\n\t\t\telse Con_Printf( S_ERROR \"not enough free beams!\\n\" );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tMem_Free( afile );\n\n\tif( count ) Con_Printf( \"%i lines read\\n\", count );\n\telse Con_Printf( \"map %s has no leaks!\\n\", clgame.mapname );\n}\n\n\n/*\n==============\nR_BeamSprite\n\nCreate a beam with sprite at the end\nValve legacy\n==============\n*/\nstatic void CL_BeamSprite( vec3_t start, vec3_t end, int beamIndex, int spriteIndex )\n{\n\tR_BeamPoints( start, end, beamIndex, 0.01f, 0.4f, 0, COM_RandomFloat( 0.5f, 0.655f ), 5.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f );\n\tR_TempSprite( end, vec3_origin, 0.1f, spriteIndex, kRenderTransAdd, kRenderFxNone, 0.35f, 0.01f, 0.0f );\n}\n\n\n/*\n==============\nR_BeamSetup\n\ngeneric function. all beams must be\npassed through this\n==============\n*/\nstatic void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )\n{\n\tmodel_t\t*sprite = CL_ModelHandle( modelIndex );\n\n\tif( !sprite ) return;\n\n\tpbeam->type = BEAM_POINTS;\n\tpbeam->modelIndex = modelIndex;\n\tpbeam->frame = 0;\n\tpbeam->frameRate = 0;\n\tpbeam->frameCount = sprite->numframes;\n\n\tVectorCopy( start, pbeam->source );\n\tVectorCopy( end, pbeam->target );\n\tVectorSubtract( end, start, pbeam->delta );\n\n\tpbeam->freq = speed * cl.time;\n\tpbeam->die = life + cl.time;\n\tpbeam->amplitude = amplitude;\n\tpbeam->brightness = brightness;\n\tpbeam->width = width;\n\tpbeam->speed = speed;\n\n\tif( amplitude >= 0.50f )\n\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f;\t// one per 4 pixels\n\telse pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f;\t\t// one per 16 pixels\n\n\tpbeam->pFollowModel = NULL;\n\tpbeam->flags = 0;\n}\n\n/*\n==============\nCL_BeamAttemptToDie\n\nCheck for expired beams\n==============\n*/\nstatic qboolean CL_BeamAttemptToDie( BEAM *pBeam )\n{\n\tAssert( pBeam != NULL );\n\n\t// premanent beams never die automatically\n\tif( FBitSet( pBeam->flags, FBEAM_FOREVER ))\n\t\treturn false;\n\n\tif( pBeam->type == TE_BEAMFOLLOW && pBeam->particles )\n\t{\n\t\t// wait for all trails are dead\n\t\treturn false;\n\t}\n\n\t// other beams\n\tif( pBeam->die > cl.time )\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n==============\nR_BeamKill\n\nRemove beam attached to specified entity\nand all particle trails (if this is a beamfollow)\n==============\n*/\nvoid GAME_EXPORT R_BeamKill( int deadEntity )\n{\n\tBEAM *beam;\n\n\tfor( beam = cl_active_beams; beam; beam = beam->next )\n\t{\n\t\tif( FBitSet( beam->flags, FBEAM_STARTENTITY ) && beam->startEntity == deadEntity )\n\t\t{\n\t\t\tif( beam->type != TE_BEAMFOLLOW )\n\t\t\t\tbeam->die = cl.time;\n\n\t\t\tClearBits( beam->flags, FBEAM_STARTENTITY );\n\t\t}\n\n\t\tif( FBitSet( beam->flags, FBEAM_ENDENTITY ) && beam->endEntity == deadEntity )\n\t\t{\n\t\t\tbeam->die = cl.time;\n\t\t\tClearBits( beam->flags, FBEAM_ENDENTITY );\n\t\t}\n\t}\n}\n\n/*\n==============\nCL_ParseViewBeam\n\nhandle beam messages\n==============\n*/\nvoid CL_ParseViewBeam( sizebuf_t *msg, int beamType )\n{\n\tvec3_t\tstart, end;\n\tint\tmodelIndex, startFrame;\n\tfloat\tframeRate, life, width;\n\tint\tstartEnt, endEnt;\n\tfloat\tnoise, speed;\n\tfloat\tr, g, b, a;\n\n\tswitch( beamType )\n\t{\n\tcase TE_BEAMPOINTS:\n\tcase TE_BEAMENTPOINT:\n\tcase TE_BEAMENTS:\n\t\tif( beamType == TE_BEAMENTS )\n\t\t{\n\t\t\tstartEnt = MSG_ReadShort( msg );\n\t\t\tendEnt = MSG_ReadShort( msg );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( beamType == TE_BEAMENTPOINT )\n\t\t\t{\n\t\t\t\tstartEnt = MSG_ReadShort( msg );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstart[0] = MSG_ReadCoord( msg );\n\t\t\t\tstart[1] = MSG_ReadCoord( msg );\n\t\t\t\tstart[2] = MSG_ReadCoord( msg );\n\t\t\t}\n\t\t\tend[0] = MSG_ReadCoord( msg );\n\t\t\tend[1] = MSG_ReadCoord( msg );\n\t\t\tend[2] = MSG_ReadCoord( msg );\n\t\t}\n\t\tmodelIndex = MSG_ReadShort( msg );\n\t\tstartFrame = MSG_ReadByte( msg );\n\t\tframeRate = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tlife = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\twidth = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tnoise = (float)MSG_ReadByte( msg ) * 0.01f;\n\t\tr = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tg = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tb = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\ta = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tspeed = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tif( beamType == TE_BEAMENTS )\n\t\t\tR_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );\n\t\telse if( beamType == TE_BEAMENTPOINT )\n\t\t\tR_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );\n\t\telse\n\t\t\tR_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );\n\t\tbreak;\n\tcase TE_LIGHTNING:\n\t\tstart[0] = MSG_ReadCoord( msg );\n\t\tstart[1] = MSG_ReadCoord( msg );\n\t\tstart[2] = MSG_ReadCoord( msg );\n\t\tend[0] = MSG_ReadCoord( msg );\n\t\tend[1] = MSG_ReadCoord( msg );\n\t\tend[2] = MSG_ReadCoord( msg );\n\t\tlife = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\twidth = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tnoise = (float)MSG_ReadByte( msg ) * 0.01f;\n\t\tmodelIndex = MSG_ReadShort( msg );\n\t\tR_BeamLightning( start, end, modelIndex, life, width, noise, 0.6f, 3.5f );\n\t\tbreak;\n\tcase TE_BEAM:\n\t\tbreak;\n\tcase TE_BEAMSPRITE:\n\t\tstart[0] = MSG_ReadCoord( msg );\n\t\tstart[1] = MSG_ReadCoord( msg );\n\t\tstart[2] = MSG_ReadCoord( msg );\n\t\tend[0] = MSG_ReadCoord( msg );\n\t\tend[1] = MSG_ReadCoord( msg );\n\t\tend[2] = MSG_ReadCoord( msg );\n\t\tmodelIndex = MSG_ReadShort( msg );\t// beam model\n\t\tstartFrame = MSG_ReadShort( msg );\t// sprite model\n\t\tCL_BeamSprite( start, end, modelIndex, startFrame );\n\t\tbreak;\n\tcase TE_BEAMTORUS:\n\tcase TE_BEAMDISK:\n\tcase TE_BEAMCYLINDER:\n\t\tstart[0] = MSG_ReadCoord( msg );\n\t\tstart[1] = MSG_ReadCoord( msg );\n\t\tstart[2] = MSG_ReadCoord( msg );\n\t\tend[0] = MSG_ReadCoord( msg );\n\t\tend[1] = MSG_ReadCoord( msg );\n\t\tend[2] = MSG_ReadCoord( msg );\n\t\tmodelIndex = MSG_ReadShort( msg );\n\t\tstartFrame = MSG_ReadByte( msg );\n\t\tframeRate = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tlife = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\twidth = (float)MSG_ReadByte( msg );\n\t\tnoise = (float)MSG_ReadByte( msg ) * 0.01f;\n\t\tr = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tg = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tb = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\ta = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tspeed = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tR_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );\n\t\tbreak;\n\tcase TE_BEAMFOLLOW:\n\t\tstartEnt = MSG_ReadShort( msg );\n\t\tmodelIndex = MSG_ReadShort( msg );\n\t\tlife = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\twidth = (float)MSG_ReadByte( msg );\n\t\tr = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tg = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tb = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\ta = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tR_BeamFollow( startEnt, modelIndex, life, width, r, g, b, a );\n\t\tbreak;\n\tcase TE_BEAMRING:\n\t\tstartEnt = MSG_ReadShort( msg );\n\t\tendEnt = MSG_ReadShort( msg );\n\t\tmodelIndex = MSG_ReadShort( msg );\n\t\tstartFrame = MSG_ReadByte( msg );\n\t\tframeRate = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tlife = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\twidth = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tnoise = (float)MSG_ReadByte( msg ) * 0.01f;\n\t\tr = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tg = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tb = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\ta = (float)MSG_ReadByte( msg ) / 255.0f;\n\t\tspeed = (float)MSG_ReadByte( msg ) * 0.1f;\n\t\tR_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );\n\t\tbreak;\n\tcase TE_BEAMHOSE:\n\t\tbreak;\n\tcase TE_KILLBEAM:\n\t\tstartEnt = MSG_ReadShort( msg );\n\t\tR_BeamKill( startEnt );\n\t\tbreak;\n\t}\n}\n\n\n/*\n==============\nR_BeamEnts\n\nCreate beam between two ents\n==============\n*/\nBEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness,\n\tfloat speed, int startFrame, float framerate, float r, float g, float b )\n{\n\tcl_entity_t\t*start, *end;\n\tBEAM\t\t*pbeam;\n\tmodel_t\t\t*mod;\n\n\tmod = CL_ModelHandle( modelIndex );\n\n\t// need a valid model.\n\tif( !mod || mod->type != mod_sprite )\n\t\treturn NULL;\n\n\tstart = R_BeamGetEntity( startEnt );\n\tend = R_BeamGetEntity( endEnt );\n\n\tif( !start || !end )\n\t\treturn NULL;\n\n\t// don't start temporary beams out of the PVS\n\tif( life != 0 && ( !start->model || !end->model ))\n\t\treturn NULL;\n\n\tpbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed );\n\tif( !pbeam ) return NULL;\n\n\tpbeam->type = TE_BEAMPOINTS;\n\tSetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );\n\tif( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );\n\n\tpbeam->startEntity = startEnt;\n\tpbeam->endEntity = endEnt;\n\n\tR_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );\n\n\treturn pbeam;\n}\n\n/*\n==============\nR_BeamPoints\n\nCreate beam between two points\n==============\n*/\nBEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude,\n\tfloat brightness, float speed, int startFrame, float framerate, float r, float g, float b )\n{\n\tBEAM\t*pbeam;\n\n\tif( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true ))\n\t\treturn NULL;\n\n\tpbeam = R_BeamAlloc();\n\tif( !pbeam ) return NULL;\n\n\tpbeam->die = cl.time;\n\n\tif( modelIndex < 0 )\n\t\treturn NULL;\n\n\tR_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed );\n\tif( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );\n\n\tR_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );\n\n\treturn pbeam;\n}\n\n/*\n==============\nR_BeamCirclePoints\n\nCreate beam cicrle\n==============\n*/\nBEAM * GAME_EXPORT R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width,\n\tfloat amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b )\n{\n\tBEAM\t*pbeam = R_BeamLightning( start, end, modelIndex, life, width, amplitude, brightness, speed );\n\n\tif( !pbeam ) return NULL;\n\tpbeam->type = type;\n\tif( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );\n\tR_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );\n\n\treturn pbeam;\n}\n\n\n/*\n==============\nR_BeamEntPoint\n\nCreate beam between entity and point\n==============\n*/\nBEAM *GAME_EXPORT R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude,\n\tfloat brightness, float speed, int startFrame, float framerate, float r, float g, float b )\n{\n\tBEAM\t\t*pbeam;\n\tcl_entity_t\t*start;\n\n\tstart = R_BeamGetEntity( startEnt );\n\n\tif( !start ) return NULL;\n\n\tif( life == 0 && !start->model )\n\t\treturn NULL;\n\n\tpbeam = R_BeamAlloc();\n\tif ( !pbeam ) return NULL;\n\n\tpbeam->die = cl.time;\n\tif( modelIndex < 0 )\n\t\treturn NULL;\n\n\tR_BeamSetup( pbeam, vec3_origin, end, modelIndex, life, width, amplitude, brightness, speed );\n\n\tpbeam->type = TE_BEAMPOINTS;\n\tSetBits( pbeam->flags, FBEAM_STARTENTITY );\n\tif( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );\n\tpbeam->startEntity = startEnt;\n\tpbeam->endEntity = 0;\n\n\tR_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );\n\n\treturn pbeam;\n}\n\n/*\n==============\nR_BeamRing\n\nCreate beam between two ents\n==============\n*/\nBEAM * GAME_EXPORT R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness,\n\tfloat speed, int startFrame, float framerate, float r, float g, float b )\n{\n\tBEAM\t\t*pbeam;\n\tcl_entity_t\t*start, *end;\n\n\tstart = R_BeamGetEntity( startEnt );\n\tend = R_BeamGetEntity( endEnt );\n\n\tif( !start || !end )\n\t\treturn NULL;\n\n\tif( life != 0 && ( !start->model || !end->model ))\n\t\treturn NULL;\n\n\tpbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed );\n\tif( !pbeam ) return NULL;\n\n\tpbeam->type = TE_BEAMRING;\n\tSetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );\n\tif( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );\n\tpbeam->startEntity = startEnt;\n\tpbeam->endEntity = endEnt;\n\n\tR_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );\n\n\treturn pbeam;\n}\n\n/*\n==============\nR_BeamFollow\n\nCreate beam following with entity\n==============\n*/\nBEAM *GAME_EXPORT R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness )\n{\n\tBEAM\t*pbeam = R_BeamAlloc();\n\n\tif( !pbeam ) return NULL;\n\tpbeam->die = cl.time;\n\n\tif( modelIndex < 0 )\n\t\treturn NULL;\n\n\tR_BeamSetup( pbeam, vec3_origin, vec3_origin, modelIndex, life, width, life, brightness, 1.0f );\n\n\tpbeam->type = TE_BEAMFOLLOW;\n\tSetBits( pbeam->flags, FBEAM_STARTENTITY );\n\tpbeam->startEntity = startEnt;\n\n\tR_BeamSetAttributes( pbeam, r, g, b, 1.0f, 0 );\n\n\treturn pbeam;\n}\n\n\n/*\n==============\nR_BeamLightning\n\ntemplate for new beams\n==============\n*/\nBEAM *GAME_EXPORT R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )\n{\n\tBEAM\t*pbeam = R_BeamAlloc();\n\n\tif( !pbeam ) return NULL;\n\tpbeam->die = cl.time;\n\n\tif( modelIndex < 0 )\n\t\treturn NULL;\n\n\tR_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed );\n\n\treturn pbeam;\n}\n\n\n\n/*\n===============\nR_EntityParticles\n\nset EF_BRIGHTFIELD effect\n===============\n*/\nvoid GAME_EXPORT R_EntityParticles( cl_entity_t *ent )\n{\n\tfloat\t\tangle;\n\tfloat\t\tsr, sp, sy, cr, cp, cy;\n\tvec3_t\t\tforward;\n\tparticle_t\t*p;\n\tint\t\ti;\n\n\tfor( i = 0; i < NUMVERTEXNORMALS; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tangle = cl.time * cl_avelocities[i][0];\n\t\tSinCos( angle, &sy, &cy );\n\t\tangle = cl.time * cl_avelocities[i][1];\n\t\tSinCos( angle, &sp, &cp );\n\t\tangle = cl.time * cl_avelocities[i][2];\n\t\tSinCos( angle, &sr, &cr );\n\n\t\tVectorSet( forward, cp * cy, cp * sy, -sp );\n\n\t\tp->die = cl.time + 0.001f;\n\t\tp->color = 111; // yellow\n\n\t\tVectorMAMAM( 1.0f, ent->origin, 64.0f, m_bytenormals[i], 16.0f, forward, p->org );\n\t}\n}\n\n/*\n===============\nR_ParticleExplosion\n\n===============\n*/\nvoid GAME_EXPORT R_ParticleExplosion( const vec3_t org )\n{\n\tparticle_t\t*p;\n\tint\t\ti, j;\n\n\tfor( i = 0; i < 1024; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + 5.0f;\n\t\tp->ramp = COM_RandomLong( 0, 3 );\n\t\tp->color = ramp1[0];\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tp->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );\n\t\t\tp->vel[j] = COM_RandomFloat( -256.0f, 256.0f );\n\t\t}\n\n\t\tif( i & 1 ) p->type = pt_explode;\n\t\telse p->type = pt_explode2;\n\t}\n}\n\n/*\n===============\nR_ParticleExplosion2\n\n===============\n*/\nvoid GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength )\n{\n\tint\t\ti, j;\n\tint\t\tcolorMod = 0, packedColor;\n\tparticle_t\t*p;\n\n\tpackedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles\n\n\tfor( i = 0; i < 512; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + 0.3f;\n\t\tp->color = colorStart + ( colorMod % colorLength );\n\t\tp->packedColor = packedColor;\n\t\tcolorMod++;\n\n\t\tp->type = pt_blob;\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tp->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );\n\t\t\tp->vel[j] = COM_RandomFloat( -256.0f, 256.0f );\n\t\t}\n\t}\n}\n\n/*\n===============\nR_BlobExplosion\n\n===============\n*/\nvoid GAME_EXPORT R_BlobExplosion( const vec3_t org )\n{\n\tparticle_t\t*p;\n\tint\t\ti, j, packedColor;\n\n\tpackedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles\n\n\tfor( i = 0; i < 1024; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + COM_RandomFloat( 1.0f, 1.4f );\n\t\tp->packedColor = packedColor;\n\n\t\tif( i & 1 )\n\t\t{\n\t\t\tp->type = pt_blob;\n\t\t\tp->color = COM_RandomLong( 66, 71 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tp->type = pt_blob2;\n\t\t\tp->color = COM_RandomLong( 150, 155 );\n\t\t}\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tp->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );\n\t\t\tp->vel[j] = COM_RandomFloat( -256.0f, 256.0f );\n\t\t}\n\t}\n}\n\n/*\n===============\nParticleEffect\n\nPARTICLE_EFFECT on server\n===============\n*/\nvoid GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count )\n{\n\tparticle_t\t*p;\n\tint\t\ti;\n\n\tif( count == 1024 )\n\t{\n\t\t// rocket explosion\n\t\tR_ParticleExplosion( org );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->color = (color & ~7) + COM_RandomLong( 0, 7 );\n\t\tp->die = cl.time + COM_RandomFloat( 0.1f, 0.4f );\n\t\tp->type = pt_slowgrav;\n\n\t\tVectorAddScalar( org, COM_RandomFloat( -8.0f, 8.0f ), p->org );\n\t\tVectorScale( dir, 15.0f, p->vel );\n\t}\n}\n\n/*\n===============\nR_Blood\n\nparticle spray\n===============\n*/\nvoid GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed )\n{\n\tvec3_t\t\tpos, dir, vec;\n\tfloat\t\tpspeed = speed * 3.0f;\n\tint\t\ti, j;\n\tparticle_t\t*p;\n\n\tVectorNormalize2( ndir, dir );\n\n\tfor( i = 0; i < (speed / 2); i++ )\n\t{\n\t\tVectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos );\n\t\tVectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec );\n\n\t\tfor( j = 0; j < 7; j++ )\n\t\t{\n\t\t\tp = R_AllocParticle( NULL );\n\t\t\tif( !p ) return;\n\n\t\t\tp->die = cl.time + 1.5f;\n\t\t\tp->color = pcolor + COM_RandomLong( 0, 9 );\n\t\t\tp->type = pt_vox_grav;\n\n\t\t\tVectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );\n\t\t\tVectorScale( vec, pspeed, p->vel );\n\t\t}\n\t}\n}\n\n/*\n===============\nR_BloodStream\n\nparticle spray 2\n===============\n*/\nvoid GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor, int speed )\n{\n\tparticle_t\t*p;\n\tint\t\ti, j;\n\tfloat\t\tarc;\n\tint\t\taccel = speed; // must be integer due to bug in GoldSrc\n\tvec3_t dir;\n\n\tVectorNormalize2( ndir, dir );\n\n\tfor( arc = 0.05f, i = 0; i < 100; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + 2.0f;\n\t\tp->type = pt_vox_grav;\n\t\tp->color = pcolor + COM_RandomLong( 0, 9 );\n\n\t\tVectorCopy( org, p->org );\n\t\tVectorCopy( dir, p->vel );\n\n\t\tp->vel[2] -= arc;\n\t\tarc -= 0.005f;\n\t\tVectorScale( p->vel, accel, p->vel );\n\t\taccel -= 0.00001f; // so last few will drip\n\t}\n\n\tfor( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ )\n\t{\n\t\tfloat\tnum;\n\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + 3.0f;\n\t\tp->color = pcolor + COM_RandomLong( 0, 9 );\n\t\tp->type = pt_vox_slowgrav;\n\n\t\tVectorCopy( org, p->org );\n\t\tVectorCopy( dir, p->vel );\n\n\t\tp->vel[2] -= arc;\n\t\tarc -= 0.005f;\n\n\t\tnum = COM_RandomFloat( 0.0f, 1.0f );\n\t\taccel = speed * num;\n\t\tnum *= 1.7f;\n\n\t\tVectorScale( p->vel, num, p->vel );\n\t\tVectorScale( p->vel, accel, p->vel );\n\n\t\tfor( j = 0; j < 2; j++ )\n\t\t{\n\t\t\tp = R_AllocParticle( NULL );\n\t\t\tif( !p ) return;\n\n\t\t\tp->die = cl.time + 3.0f;\n\t\t\tp->color = pcolor + COM_RandomLong( 0, 9 );\n\t\t\tp->type = pt_vox_slowgrav;\n\n\t\t\tp->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f );\n\t\t\tp->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f );\n\t\t\tp->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f );\n\n\t\t\tVectorCopy( dir, p->vel );\n\t\t\tp->vel[2] -= arc;\n\n\t\t\tVectorScale( p->vel, num, p->vel );\n\t\t\tVectorScale( p->vel, accel, p->vel );\n\t\t}\n\t}\n}\n\n/*\n===============\nR_LavaSplash\n\n===============\n*/\nvoid GAME_EXPORT R_LavaSplash( const vec3_t org )\n{\n\tparticle_t\t*p;\n\tfloat\t\tvel;\n\tvec3_t\t\tdir;\n\tint\t\ti, j, k;\n\n\tfor( i = -16; i < 16; i++ )\n\t{\n\t\tfor( j = -16; j <16; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 1; k++ )\n\t\t\t{\n\t\t\t\tp = R_AllocParticle( NULL );\n\t\t\t\tif( !p ) return;\n\n\t\t\t\tp->die = cl.time + COM_RandomFloat( 2.0f, 2.62f );\n\t\t\t\tp->color = COM_RandomLong( 224, 231 );\n\t\t\t\tp->type = pt_slowgrav;\n\n\t\t\t\tdir[0] = j * 8.0f + COM_RandomFloat( 0.0f, 7.0f );\n\t\t\t\tdir[1] = i * 8.0f + COM_RandomFloat( 0.0f, 7.0f );\n\t\t\t\tdir[2] = 256.0f;\n\n\t\t\t\tp->org[0] = org[0] + dir[0];\n\t\t\t\tp->org[1] = org[1] + dir[1];\n\t\t\t\tp->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f );\n\n\t\t\t\tVectorNormalize( dir );\n\t\t\t\tvel = COM_RandomFloat( 50.0f, 113.0f );\n\t\t\t\tVectorScale( dir, vel, p->vel );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nR_ParticleBurst\n\n===============\n*/\nvoid GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float life )\n{\n\tparticle_t\t*p;\n\tvec3_t\t\tdir, dest;\n\tint\t\ti, j;\n\tfloat\t\tdist;\n\n\tfor( i = 0; i < 32; i++ )\n\t{\n\t\tfor( j = 0; j < 32; j++ )\n\t\t{\n\t\t\tp = R_AllocParticle( NULL );\n\t\t\tif( !p ) return;\n\n\t\t\tp->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f );\n\t\t\tp->color = color + COM_RandomLong( 0, 10 );\n\t\t\tp->ramp = 1.0f;\n\n\t\t\tVectorCopy( org, p->org );\n\t\t\tVectorAddScalar( org, COM_RandomFloat( -size, size ), dest );\n\t\t\tVectorSubtract( dest, p->org, dir );\n\t\t\tdist = VectorNormalizeLength( dir );\n\t\t\tVectorScale( dir, ( dist / life ), p->vel );\n\t\t}\n\t}\n}\n\n/*\n===============\nR_LargeFunnel\n\n===============\n*/\nvoid GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse )\n{\n\tparticle_t\t*p;\n\tfloat\t\tvel, dist;\n\tvec3_t\t\tdir, dest;\n\tint\t\ti, j;\n\n\tfor( i = -8; i < 8; i++ )\n\t{\n\t\tfor( j = -8; j < 8; j++ )\n\t\t{\n\t\t\tp = R_AllocParticle( NULL );\n\t\t\tif( !p ) return;\n\n\t\t\tdest[0] = (i * 32.0f) + org[0];\n\t\t\tdest[1] = (j * 32.0f) + org[1];\n\t\t\tdest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f );\n\n\t\t\tif( reverse )\n\t\t\t{\n\t\t\t\tVectorCopy( org, p->org );\n\t\t\t\tVectorSubtract( dest, p->org, dir );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy( dest, p->org );\n\t\t\t\tVectorSubtract( org, p->org, dir );\n\t\t\t}\n\n\t\t\tvel = dest[2] / 8.0f;\n\t\t\tif( vel < 64.0f ) vel = 64.0f;\n\n\t\t\tdist = VectorNormalizeLength( dir );\n\t\t\tvel += COM_RandomFloat( 64.0f, 128.0f );\n\t\t\tVectorScale( dir, vel, p->vel );\n\t\t\tp->die = cl.time + (dist / vel );\n\t\t\tp->color = 244; // green color\n\t\t}\n\t}\n}\n\n/*\n===============\nR_TeleportSplash\n\n===============\n*/\nvoid GAME_EXPORT R_TeleportSplash( const vec3_t org )\n{\n\tparticle_t\t*p;\n\tvec3_t\t\tdir;\n\tfloat\t\tvel;\n\tint\t\ti, j, k;\n\n\tfor( i = -16; i < 16; i += 4 )\n\t{\n\t\tfor( j = -16; j < 16; j += 4 )\n\t\t{\n\t\t\tfor( k = -24; k < 32; k += 4 )\n\t\t\t{\n\t\t\t\tp = R_AllocParticle( NULL );\n\t\t\t\tif( !p ) return;\n\n\t\t\t\tp->die = cl.time + COM_RandomFloat( 0.2f, 0.34f );\n\t\t\t\tp->color = COM_RandomLong( 7, 14 );\n\t\t\t\tp->type = pt_slowgrav;\n\n\t\t\t\tdir[0] = j * 8.0f;\n\t\t\t\tdir[1] = i * 8.0f;\n\t\t\t\tdir[2] = k * 8.0f;\n\n\t\t\t\tp->org[0] = org[0] + i + COM_RandomFloat( 0.0f, 3.0f );\n\t\t\t\tp->org[1] = org[1] + j + COM_RandomFloat( 0.0f, 3.0f );\n\t\t\t\tp->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f );\n\n\t\t\t\tVectorNormalize( dir );\n\t\t\t\tvel = COM_RandomFloat( 50.0f, 113.0f );\n\t\t\t\tVectorScale( dir, vel, p->vel );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nR_RocketTrail\n\n===============\n*/\nvoid GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type )\n{\n\tvec3_t\t\tvec, right, up;\n\tfloat\t\tlen, dec;\n\tparticle_t\t*p;\n\n\tVectorSubtract( end, start, vec );\n\tlen = VectorNormalizeLength( vec );\n\n\tif( type == 7 )\n\t{\n\t\tdec = 1.0f;\n\t\tVectorVectors( vec, right, up );\n\t}\n\telse if( type < 128 )\n\t{\n\t\tdec = 3.0f;\n\t}\n\telse\n\t{\n\t\t// initialize if type will be 7 here\n\t\tVectorVectors( vec, right, up );\n\n\t\tdec = 1.0f;\n\t\ttype -= 128;\n\t}\n\n\tVectorScale( vec, dec, vec );\n\n\twhile( len > 0 )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p )\n\t\t\treturn;\n\n\t\tlen -= dec;\n\t\tp->die = cl.time + 2.0f;\n\n\t\tswitch( type )\n\t\t{\n\t\tcase 0:\n\t\tcase 1:\n\t\t\tp->ramp = COM_RandomLong( 0 + type * 2, 3 + type * 2 );\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tp->type = pt_fire;\n\t\t\tVectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org );\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tp->color = COM_RandomLong( 67, 74 );\n\t\t\tp->type = pt_grav;\n\t\t\tVectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org );\n\t\t\tbreak;\n\t\tcase 3:\n\t\tcase 5:\n\t\t{\n\t\t\tstatic int\ttracercount;\n\t\t\tp->die = cl.time + 0.5f;\n\t\t\tp->color = ( tracercount & 4 ) * 2;\n\n\t\t\tif( type == 3 )\n\t\t\t\tp->color += 52;\n\t\t\telse\n\t\t\t\tp->color += 230;\n\n\t\t\tVectorCopy( start, p->org );\n\t\t\ttracercount++;\n\n\t\t\tp->vel[0] = 30.0f * vec[1];\n\t\t\tp->vel[1] = 30.0f * vec[0];\n\t\t\tp->vel[tracercount & 1] = -p->vel[tracercount & 1];\n\t\t\tbreak;\n\t\t}\n\t\tcase 4:\n\t\t\tp->color = COM_RandomLong( 67, 70 );\n\t\t\tp->type = pt_grav;\n\t\t\tVectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org );\n\t\t\tlen -= 3.0f;\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tp->type = pt_fire;\n\t\t\tp->ramp = COM_RandomLong( 0, 3 );\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tVectorCopy( start, p->org );\n\t\t\tbreak;\n\t\tcase 7:\n\t\t{\n\t\t\tfloat x = COM_RandomLong( 0, 65535 );\n\t\t\tfloat y = COM_RandomLong( 8, 16 );\n\t\t\tfloat s, c;\n\n\t\t\tSinCos( x, &s, &c );\n\t\t\ts *= y;\n\t\t\tc *= y;\n\n\t\t\tVectorMAMAM( 1.0f, start, s, right, c, up, p->org );\n\t\t\tVectorSubtract( start, p->org, p->vel );\n\t\t\tVectorScale( p->vel, 2.0f, p->vel );\n\n\t\t\tx = COM_RandomFloat( 96.0f, 111.0f );\n\t\t\tVectorMA( p->vel, x, vec, p->vel );\n\n\t\t\tp->ramp = COM_RandomLong( 0, 3 );\n\t\t\tp->color = ramp3[(int)p->ramp];\n\t\t\tp->type = pt_explode2;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tVectorCopy( start, p->org );\n\t\t\tbreak;\n\t\t}\n\n\t\tVectorAdd( start, vec, start );\n\t}\n}\n\n/*\n===============\nPM_ParticleLine\n\ndraw line from particles\n================\n*/\nstatic void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel )\n{\n\tfloat\tlen, curdist;\n\tvec3_t\tdiff, pos;\n\n\t// determine distance\n\tVectorSubtract( end, start, diff );\n\tlen = VectorNormalizeLength( diff );\n\tcurdist = 0;\n\n\twhile( curdist <= len )\n\t{\n\t\tVectorMA( start, curdist, diff, pos );\n\t\tCL_Particle( pos, pcolor, life, 0, zvel );\n\t\tcurdist += 2.0f;\n\t}\n}\n\n/*\n================\nPM_DrawRectangle\n\n================\n*/\nstatic void PM_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life )\n{\n\tPM_ParticleLine( tl, bl, pcolor, life, 0 );\n\tPM_ParticleLine( bl, br, pcolor, life, 0 );\n\tPM_ParticleLine( br, tr, pcolor, life, 0 );\n\tPM_ParticleLine( tr, tl, pcolor, life, 0 );\n}\n\n/*\n================\nPM_DrawBBox\n\n================\n*/\nstatic void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life )\n{\n\tvec3_t\tp[8], tmp;\n\tfloat\tgap = BOX_GAP;\n\tint\ti;\n\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\ttmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap;\n\t\ttmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ;\n\t\ttmp[2] = (i & 4) ? mins[2] - gap : maxs[2] + gap ;\n\n\t\tVectorAdd( tmp, origin, tmp );\n\t\tVectorCopy( tmp, p[i] );\n\t}\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tPM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life );\n\t}\n}\n\n/*\n================\nR_ParticleLine\n\n================\n*/\nvoid GAME_EXPORT R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life )\n{\n\tint\tpcolor;\n\n\tpcolor = R_LookupColor( r, g, b );\n\tPM_ParticleLine( start, end, pcolor, life, 0 );\n}\n\n/*\n================\nR_ParticleBox\n\n================\n*/\nvoid GAME_EXPORT R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r, byte g, byte b, float life )\n{\n\tvec3_t\tmins, maxs;\n\tvec3_t\torigin;\n\tint\tpcolor;\n\n\tpcolor = R_LookupColor( r, g, b );\n\n\tVectorAverage( absmax, absmin, origin );\n\tVectorSubtract( absmax, origin, maxs );\n\tVectorSubtract( absmin, origin, mins );\n\n\tPM_DrawBBox( mins, maxs, origin, pcolor, life );\n}\n\n/*\n================\nR_ShowLine\n\n================\n*/\nvoid GAME_EXPORT R_ShowLine( const vec3_t start, const vec3_t end )\n{\n\tvec3_t\t\tdir, org;\n\tfloat\t\tlen;\n\tparticle_t\t*p;\n\n\tVectorSubtract( end, start, dir );\n\tlen = VectorNormalizeLength( dir );\n\tVectorScale( dir, 5.0f, dir );\n\tVectorCopy( start, org );\n\n\twhile( len > 0 )\n\t{\n\t\tlen -= 5.0f;\n\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die = cl.time + 30;\n\t\tp->color = 75;\n\n\t\tVectorCopy( org, p->org );\n\t\tVectorAdd( org, dir, org );\n\t}\n}\n\n/*\n===============\nR_BulletImpactParticles\n\n===============\n*/\nvoid GAME_EXPORT R_BulletImpactParticles( const vec3_t pos )\n{\n\tint\t\ti, quantity;\n\tint\t\tcolor;\n\tfloat\t\tdist;\n\tvec3_t\t\tdir;\n\tparticle_t\t*p;\n\n\tVectorSubtract( pos, refState.vieworg, dir );\n\tdist = VectorLength( dir );\n\tif( dist > 1000.0f ) dist = 1000.0f;\n\n\tquantity = (1000.0f - dist) / 100.0f;\n\tif( quantity == 0 ) quantity = 1;\n\n\tcolor = 3 - ((30 * quantity) / 100 );\n\tR_SparkStreaks( pos, 2, -200, 200 );\n\n\tfor( i = 0; i < quantity * 4; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tVectorCopy( pos, p->org);\n\n\t\tp->vel[0] = COM_RandomFloat( -1.0f, 1.0f );\n\t\tp->vel[1] = COM_RandomFloat( -1.0f, 1.0f );\n\t\tp->vel[2] = COM_RandomFloat( -1.0f, 1.0f );\n\t\tVectorScale( p->vel, COM_RandomFloat( 50.0f, 100.0f ), p->vel );\n\n\t\tp->die = cl.time + 0.5;\n\t\tp->color = 3 - color;\n\t\tp->type = pt_grav;\n\t}\n}\n\n/*\n===============\nR_FlickerParticles\n\n===============\n*/\nvoid GAME_EXPORT R_FlickerParticles( const vec3_t org )\n{\n\tparticle_t\t*p;\n\tint\t\ti;\n\n\tfor( i = 0; i < 15; i++ )\n\t{\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tVectorCopy( org, p->org );\n\t\tp->vel[0] = COM_RandomFloat( -32.0f, 32.0f );\n\t\tp->vel[1] = COM_RandomFloat( -32.0f, 32.0f );\n\t\tp->vel[2] = COM_RandomFloat( 80.0f, 143.0f );\n\n\t\tp->die = cl.time + 2.0f;\n\t\tp->type = pt_blob2;\n\t\tp->color = 254;\n\t}\n}\n\n/*\n===============\nR_StreakSplash\n\ncreate a splash of streaks\n===============\n*/\nvoid GAME_EXPORT R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velocityMin, int velocityMax )\n{\n\tvec3_t\t\tvel, vel2;\n\tparticle_t\t*p;\n\tint\t\ti;\n\n\tVectorScale( dir, speed, vel );\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tVectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 );\n\t\tp = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f ));\n\t\tif( !p ) return;\n\n\t\tp->type = pt_grav;\n\t\tp->color = color;\n\t\tp->ramp = 1.0f;\n\t}\n}\n\n/*\n===============\nCL_Particle\n\npmove debugging particle\n===============\n*/\nvoid CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel )\n{\n\tparticle_t\t*p;\n\n\tp = R_AllocParticle( NULL );\n\tif( !p ) return;\n\n\tif( org ) VectorCopy( org, p->org );\n\tp->die = cl.time + life;\n\tp->vel[2] += zvel;\t// ???\n\tp->color = color;\n}\n\n/*\n===============\nR_TracerEffect\n\n===============\n*/\nvoid GAME_EXPORT R_TracerEffect( const vec3_t start, const vec3_t end )\n{\n\tvec3_t\tpos, vel, dir;\n\tfloat\tlen, speed;\n\tfloat\toffset;\n\n\tspeed = Q_max( tracerspeed.value, 3.0f );\n\n\tVectorSubtract( end, start, dir );\n\tlen = VectorLength( dir );\n\tif( len == 0.0f ) return;\n\n\tVectorScale( dir, 1.0f / len, dir ); // normalize\n\toffset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value;\n\tVectorScale( dir, offset, vel );\n\tVectorAdd( start, vel, pos );\n\tVectorScale( dir, speed, vel );\n\n\tR_AllocTracer( pos, vel, len / speed );\n}\n\n/*\n===============\nR_UserTracerParticle\n\n===============\n*/\nvoid GAME_EXPORT R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( particle_t *p ))\n{\n\tparticle_t\t*p;\n\n\tif( colorIndex < 0 )\n\t\treturn;\n\n\tif(( p = R_AllocTracer( org, vel, life )) != NULL )\n\t{\n\t\tp->context = deathcontext;\n\t\tp->deathfunc = deathfunc;\n\t\tp->color = colorIndex;\n\t\tp->ramp = length;\n\t}\n}\n\n/*\n===============\nR_TracerParticles\n\nallow more customization\n===============\n*/\nparticle_t *R_TracerParticles( float *org, float *vel, float life )\n{\n\treturn R_AllocTracer( org, vel, life );\n}\n\n/*\n===============\nR_SparkStreaks\n\ncreate a streak tracers\n===============\n*/\nvoid GAME_EXPORT R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax )\n{\n\tparticle_t\t*p;\n\tvec3_t\t\tvel;\n\tint\t\ti;\n\n\tfor( i = 0; i<count; i++ )\n\t{\n\t\tvel[0] = COM_RandomFloat( velocityMin, velocityMax );\n\t\tvel[1] = COM_RandomFloat( velocityMin, velocityMax );\n\t\tvel[2] = COM_RandomFloat( velocityMin, velocityMax );\n\n\t\tp = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f ));\n\t\tif( !p ) return;\n\n\t\tp->color = 5;\n\t\tp->type = pt_grav;\n\t\tp->ramp = 0.5f;\n\t}\n}\n\n/*\n===============\nR_Implosion\n\nmake implosion tracers\n===============\n*/\nvoid GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float life )\n{\n\tfloat\t\tdist = ( radius / 100.0f );\n\tvec3_t\t\tstart, temp, vel;\n\tfloat\t\tfactor;\n\tparticle_t\t*p;\n\tint\t\ti;\n\n\tif( life <= 0.0f ) life = 0.1f; // to avoid divide by zero\n\tfactor = -1.0 / life;\n\n\tfor ( i = 0; i < count; i++ )\n\t{\n\t\ttemp[0] = dist * COM_RandomFloat( -100.0f, 100.0f );\n\t\ttemp[1] = dist * COM_RandomFloat( -100.0f, 100.0f );\n\t\ttemp[2] = dist * COM_RandomFloat( 0.0f, 100.0f );\n\t\tVectorScale( temp, factor, vel );\n\t\tVectorAdd( temp, end, start );\n\n\t\tif(( p = R_AllocTracer( start, vel, life )) == NULL )\n\t\t\treturn;\n\n\t\tp->type = pt_explode;\n\t}\n}\n\n\n/*\n==============\nR_FreeDeadParticles\n\nFree particles that time has expired\n==============\n*/\nvoid R_FreeDeadParticles( particle_t **ppparticles )\n{\n\tparticle_t\t*p, *kill;\n\n\t// kill all the ones hanging direcly off the base pointer\n\twhile( 1 )\n\t{\n\t\tkill = *ppparticles;\n\t\tif( kill && kill->die < cl.time )\n\t\t{\n\t\t\tif( kill->deathfunc )\n\t\t\t\tkill->deathfunc( kill );\n\t\t\tkill->deathfunc = NULL;\n\t\t\t*ppparticles = kill->next;\n\t\t\tkill->next = cl_free_particles;\n\t\t\tcl_free_particles = kill;\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\n\t// kill off all the others\n\tfor( p = *ppparticles; p; p = p->next )\n\t{\n\t\twhile( 1 )\n\t\t{\n\t\t\tkill = p->next;\n\t\t\tif( kill && kill->die < cl.time )\n\t\t\t{\n\t\t\t\tif( kill->deathfunc )\n\t\t\t\t\tkill->deathfunc( kill );\n\t\t\t\tkill->deathfunc = NULL;\n\t\t\t\tp->next = kill->next;\n\t\t\t\tkill->next = cl_free_particles;\n\t\t\t\tcl_free_particles = kill;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n===============\nCL_ReadPointFile_f\n\n===============\n*/\nvoid CL_ReadPointFile_f( void )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tvec3_t\t\torg;\n\tint\t\tcount;\n\tparticle_t\t*p;\n\tchar\t\tfilename[64];\n\tstring\t\ttoken;\n\n\tQ_snprintf( filename, sizeof( filename ), \"maps/%s.pts\", clgame.mapname );\n\tafile = FS_LoadFile( filename, NULL, false );\n\n\tif( !afile )\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't open %s\\n\", filename );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Reading %s...\\n\", filename );\n\n\tcount = 0;\n\tpfile = (char *)afile;\n\n\twhile( 1 )\n\t{\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\torg[0] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\torg[1] = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tif( !pfile ) break;\n\t\torg[2] = Q_atof( token );\n\n\t\tcount++;\n\n\t\tif( !cl_free_particles )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"not enough free particles!\\n\" );\n\t\t\tbreak;\n\t\t}\n\n\t\t// NOTE: can't use R_AllocParticle because this command\n\t\t// may be executed from the console, while frametime is 0\n\t\tp = cl_free_particles;\n\t\tcl_free_particles = p->next;\n\t\tp->next = cl_active_particles;\n\t\tcl_active_particles = p;\n\n\t\tp->ramp = 0;\n\t\tp->type = pt_static;\n\t\tp->die = cl.time + 99999;\n\t\tp->color = (-count) & 15;\n\t\tVectorCopy( org, p->org );\n\t\tVectorClear( p->vel );\n\t}\n\n\tMem_Free( afile );\n\n\tif( count ) Con_Printf( \"%i points read\\n\", count );\n\telse Con_Printf( \"map %s has no leaks!\\n\", clgame.mapname );\n}\n\nstatic void CL_FreeDeadBeams( void )\n{\n\tBEAM *pBeam, *pNext, *pPrev = NULL;\n\t// draw temporary entity beams\n\tfor( pBeam = cl_active_beams; pBeam; pBeam = pNext )\n\t{\n\t\t// need to store the next one since we may delete this one\n\t\tpNext = pBeam->next;\n\n\t\t// retire old beams\n\t\tif( CL_BeamAttemptToDie( pBeam ))\n\t\t{\n\t\t\t// reset links\n\t\t\tif( pPrev ) pPrev->next = pNext;\n\t\t\telse cl_active_beams = pNext;\n\n\t\t\t// free the beam\n\t\t\tR_BeamFree( pBeam );\n\n\t\t\tpBeam = NULL;\n\t\t\tcontinue;\n\t\t}\n\n\t\tpPrev = pBeam;\n\t}\n}\n\nvoid CL_DrawEFX( float time, qboolean fTrans )\n{\n\tCL_FreeDeadBeams();\n\tif( cl_draw_beams.value )\n\t\tref.dllFuncs.CL_DrawBeams( fTrans, cl_active_beams );\n\n\tif( fTrans )\n\t{\n\t\tR_FreeDeadParticles( &cl_active_particles );\n\t\tif( cl_draw_particles.value )\n\t\t\tref.dllFuncs.CL_DrawParticles( time, cl_active_particles, PART_SIZE );\n\t\tR_FreeDeadParticles( &cl_active_tracers );\n\t\tif( cl_draw_tracers.value )\n\t\t\tref.dllFuncs.CL_DrawTracers( time, cl_active_tracers );\n\t}\n}\n\nvoid CL_ThinkParticle( double frametime, particle_t *p )\n{\n\tfloat\t\ttime3 = 15.0f * frametime;\n\tfloat\t\ttime2 = 10.0f * frametime;\n\tfloat\t\ttime1 = 5.0f * frametime;\n\tfloat\t\tdvel = 4.0f * frametime;\n\tfloat\t\tgrav = frametime * clgame.movevars.gravity * 0.05f;\n\n\n\tif( p->type != pt_clientcustom )\n\t{\n\t\t// update position.\n\t\tVectorMA( p->org, frametime, p->vel, p->org );\n\t}\n\n\tswitch( p->type )\n\t{\n\tcase pt_static:\n\t\tbreak;\n\tcase pt_fire:\n\t\tp->ramp += time1;\n\t\tif( p->ramp >= 6.0f ) p->die = -1.0f;\n\t\telse p->color = ramp3[(int)p->ramp];\n\t\tp->vel[2] += grav;\n\t\tbreak;\n\tcase pt_explode:\n\t\tp->ramp += time2;\n\t\tif( p->ramp >= 8.0f ) p->die = -1.0f;\n\t\telse p->color = ramp1[(int)p->ramp];\n\t\tVectorMA( p->vel, dvel, p->vel, p->vel );\n\t\tp->vel[2] -= grav;\n\t\tbreak;\n\tcase pt_explode2:\n\t\tp->ramp += time3;\n\t\tif( p->ramp >= 8.0f ) p->die = -1.0f;\n\t\telse p->color = ramp2[(int)p->ramp];\n\t\tVectorMA( p->vel,-frametime, p->vel, p->vel );\n\t\tp->vel[2] -= grav;\n\t\tbreak;\n\tcase pt_blob:\n\t\tif( p->packedColor == 255 )\n\t\t{\n\t\t\t// normal blob explosion\n\t\t\tVectorMA( p->vel, dvel, p->vel, p->vel );\n\t\t\tp->vel[2] -= grav;\n\t\t\tbreak;\n\t\t}\n\t\t// intentionally fallthrough\n\tcase pt_blob2:\n\t\tif( p->packedColor == 255 )\n\t\t{\n\t\t\t// normal blob explosion\n\t\t\tp->vel[0] -= p->vel[0] * dvel;\n\t\t\tp->vel[1] -= p->vel[1] * dvel;\n\t\t\tp->vel[2] -= grav;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tp->ramp += time2;\n\t\t\tif( p->ramp >= 9.0f ) p->ramp = 0.0f;\n\t\t\tp->color = gSparkRamp[(int)p->ramp];\n\t\t\tVectorMA( p->vel, -frametime * 0.5f, p->vel, p->vel );\n\t\t\tp->type = COM_RandomLong( 0, 3 ) ? pt_blob : pt_blob2;\n\t\t\tp->vel[2] -= grav * 5.0f;\n\t\t}\n\t\tbreak;\n\tcase pt_grav:\n\t\tp->vel[2] -= grav * 20.0f;\n\t\tbreak;\n\tcase pt_slowgrav:\n\t\tp->vel[2] -= grav;\n\t\tbreak;\n\tcase pt_vox_grav:\n\t\tp->vel[2] -= grav * 8.0f;\n\t\tbreak;\n\tcase pt_vox_slowgrav:\n\t\tp->vel[2] -= grav * 4.0f;\n\t\tbreak;\n\tcase pt_clientcustom:\n\t\tif( p->callback )\n\t\t\tp->callback( p, frametime );\n\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "engine/client/cl_events.c",
    "content": "/*\ncl_events.c - client-side event system implementation\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"event_flags.h\"\n#include \"net_encode.h\"\n#include \"con_nprint.h\"\n\n/*\n===============\nCL_ResetEvent\n\n===============\n*/\nvoid CL_ResetEvent( event_info_t *ei )\n{\n\tei->index = 0;\n\tmemset( &ei->args, 0, sizeof( ei->args ));\n\tei->fire_time = 0.0;\n\tei->flags = 0;\n}\n\n/*\n=============\nCL_CalcPlayerVelocity\n\ncompute velocity for a given client\n=============\n*/\nstatic void CL_CalcPlayerVelocity( int idx, vec3_t velocity )\n{\n\tclientdata_t\t*pcd;\n\tvec3_t\t\tdelta;\n\tdouble\t\tdt;\n\n\tVectorClear( velocity );\n\n\tif( idx <= 0 || idx > cl.maxclients )\n\t\treturn;\n\n\tif( idx == cl.playernum + 1 )\n\t{\n\t\tpcd = &cl.frames[cl.parsecountmod].clientdata;\n\t\tVectorCopy( pcd->velocity, velocity );\n\t}\n\telse\n\t{\n\t\tdt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime;\n\n\t\tif( dt != 0.0 )\n\t\t{\n\t\t\tVectorSubtract( clgame.entities[idx].curstate.velocity, clgame.entities[idx].prevstate.velocity, delta );\n\t\t\tVectorScale( delta, 1.0f / dt, velocity );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( clgame.entities[idx].curstate.velocity, velocity );\n\t\t}\n\t}\n}\n\n/*\n=============\nCL_DescribeEvent\n\n=============\n*/\nstatic void CL_DescribeEvent( event_info_t *ei, int slot )\n{\n\tint\t\tidx = (slot & 63) * 2;\n\tcon_nprint_t\tinfo;\n\tstring origin_str = { 0 }; //, angles_str = { 0 };\n\n\tif( !cl_showevents.value )\n\t\treturn;\n\n\tinfo.time_to_live = 1.0f;\n\tinfo.index = idx;\n\n\t// mark reliable as green and unreliable as red\n\tif( FBitSet( ei->flags, FEV_RELIABLE ))\n\t\tVectorSet( info.color, 0.5f, 1.0f, 0.5f );\n\telse VectorSet( info.color, 1.0f, 0.5f, 0.5f );\n\n\tif( !VectorIsNull( ei->args.origin ))\n\t{\n\t\tQ_snprintf( origin_str, sizeof( origin_str ), \"(%.2f,%.2f,%.2f)\",\n\t\t\tei->args.origin[0], ei->args.origin[1], ei->args.origin[2]);\n\t}\n\n\t/*if( !VectorIsNull( ei->args.angles ))\n\t{\n\t\tQ_snprintf( angles_str, sizeof( angles_str ), \"ang %.2f %.2f %.2f\",\n\t\t\tei->args.angles[0], ei->args.angles[1], ei->args.angles[2]);\n\t}*/\n\n\tCon_NXPrintf( &info, \"%i %.2f %c %s %s\",\n\t\tslot, cl.time,\n\t\t(FBitSet( ei->flags, FEV_CLIENT ) ? 'c' :\n\t\tFBitSet( ei->flags, FEV_SERVER ) ? 's' : '?'),\n\t\tcl.event_precache[ei->index],\n\t\torigin_str);\n\n\tinfo.index++;\n\n\tCon_NXPrintf( &info, \"b(%i,%i) i(%i,%i) f(%.2f,%.2f)\",\n\t\tei->args.bparam1, ei->args.bparam2,\n\t\tei->args.iparam1, ei->args.iparam2,\n\t\tei->args.fparam1, ei->args.fparam2);\n}\n\n/*\n=============\nCL_SetEventIndex\n\n=============\n*/\nvoid CL_SetEventIndex( const char *szEvName, int ev_index )\n{\n\tcl_user_event_t\t*ev;\n\tint\t\ti;\n\n\tif( !szEvName || !*szEvName )\n\t\treturn; // ignore blank names\n\n\t// search event by name to link with\n\tfor( i = 0; i < MAX_EVENTS; i++ )\n\t{\n\t\tev = clgame.events[i];\n\t\tif( !ev ) break;\n\n\t\tif( !Q_stricmp( ev->name, szEvName ))\n\t\t{\n\t\t\tev->index = ev_index;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/*\n=============\nCL_EventIndex\n\n=============\n*/\nword CL_EventIndex( const char *name )\n{\n\tword\ti;\n\n\tif( !COM_CheckString( name ))\n\t\treturn 0;\n\n\tfor( i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( cl.event_precache[i], name ))\n\t\t\treturn i;\n\t}\n\treturn 0;\n}\n\n/*\n=============\nCL_RegisterEvent\n\n=============\n*/\nvoid CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func )\n{\n\tcl_user_event_t\t*ev;\n\n\tif( lastnum == MAX_EVENTS )\n\t\treturn;\n\n\t// clear existing or allocate new one\n\tif( !clgame.events[lastnum] )\n\t\tclgame.events[lastnum] = Mem_Calloc( cls.mempool, sizeof( cl_user_event_t ));\n\telse memset( clgame.events[lastnum], 0, sizeof( cl_user_event_t ));\n\n\tev = clgame.events[lastnum];\n\n\t// NOTE: ev->index will be set later\n\tQ_strncpy( ev->name, szEvName, sizeof( ev->name ));\n\tev->func = func;\n}\n\n/*\n=============\nCL_FireEvent\n\n=============\n*/\nstatic qboolean CL_FireEvent( event_info_t *ei, int slot )\n{\n\tcl_user_event_t\t*ev;\n\tconst char\t*name;\n\tint\t\ti, idx;\n\n\tif( !ei || !ei->index )\n\t\treturn false;\n\n\t// get the func pointer\n\tfor( i = 0; i < MAX_EVENTS; i++ )\n\t{\n\t\tev = clgame.events[i];\n\n\t\tif( !ev )\n\t\t{\n\t\t\tidx = bound( 1, ei->index, ( MAX_EVENTS - 1 ));\n\t\t\tCon_Reportf( S_ERROR \"%s: %s not precached\\n\", __func__, cl.event_precache[idx] );\n\t\t\tbreak;\n\t\t}\n\n\t\tif( ev->index == ei->index )\n\t\t{\n\t\t\tname = cl.event_precache[ei->index];\n\n\t\t\tif( cl_trace_events.value )\n\t\t\t{\n\t\t\t\tCon_Printf( \"^3EVENT %s AT %.2f %.2f %.2f\\n\"    // event name\n\t\t\t\t\t\"\\t%.2f %.2f %i %i %s %s\\n\", // bool params\n\t\t\t\t\tname, ei->args.origin[0], ei->args.origin[1], ei->args.origin[2],\n\t\t\t\t\tei->args.fparam1, ei->args.fparam2,\n\t\t\t\t\tei->args.iparam1, ei->args.iparam2,\n\t\t\t\t\tei->args.bparam1 ? \"TRUE\" : \"FALSE\", ei->args.bparam2 ? \"TRUE\" : \"FALSE\" );\n\t\t\t}\n\n\t\t\tif( ev->func )\n\t\t\t{\n\t\t\t\tCL_DescribeEvent( ei, slot );\n\t\t\t\tev->func( &ei->args );\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tCon_Reportf( S_ERROR \"%s: %s not hooked\\n\", __func__, name );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n=============\nCL_FireEvents\n\ncalled right before draw frame\n=============\n*/\nvoid CL_FireEvents( void )\n{\n\tevent_state_t\t*es;\n\tevent_info_t\t*ei;\n\tint\t\ti;\n\n\tes = &cl.events;\n\n\tfor( i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tei = &es->ei[i];\n\n\t\tif( ei->index == 0 )\n\t\t\tcontinue;\n\n\t\t// delayed event!\n\t\tif( ei->fire_time && ( ei->fire_time > cl.time ))\n\t\t\tcontinue;\n\n\t\tCL_FireEvent( ei, i );\n\n\t\t// zero out the remaining fields\n\t\tCL_ResetEvent( ei );\n\t}\n}\n\n/*\n=============\nCL_FindEvent\n\nfind first empty event\n=============\n*/\nstatic event_info_t *CL_FindEmptyEvent( void )\n{\n\tint\t\ti;\n\tevent_state_t\t*es;\n\tevent_info_t\t*ei;\n\n\tes = &cl.events;\n\n\t// look for first slot where index is != 0\n\tfor( i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tei = &es->ei[i];\n\t\tif( ei->index != 0 )\n\t\t\tcontinue;\n\t\treturn ei;\n\t}\n\n\t// no slots available\n\treturn NULL;\n}\n\n/*\n=============\nCL_FindEvent\n\nreplace only unreliable events\n=============\n*/\nstatic event_info_t *CL_FindUnreliableEvent( void )\n{\n\tevent_state_t\t*es;\n\tevent_info_t\t*ei;\n\tint\t\ti;\n\n\tes = &cl.events;\n\n\tfor ( i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tei = &es->ei[i];\n\t\tif( ei->index != 0 )\n\t\t{\n\t\t\t// it's reliable, so skip it\n\t\t\tif( FBitSet( ei->flags, FEV_RELIABLE ))\n\t\t\t\tcontinue;\n\t\t}\n\t\treturn ei;\n\t}\n\n\t// this should never happen\n\treturn NULL;\n}\n\n/*\n=============\nCL_QueueEvent\n\n=============\n*/\nstatic void CL_QueueEvent( int flags, int index, float delay, event_args_t *args )\n{\n\tevent_info_t\t*ei;\n\n\t// find a normal slot\n\tei = CL_FindEmptyEvent();\n\n\tif( !ei )\n\t{\n\t\tif( FBitSet( flags, FEV_RELIABLE ))\n\t\t{\n\t\t\tei = CL_FindUnreliableEvent();\n\t\t}\n\n\t\tif( !ei ) return;\n\t}\n\n\tei->index\t= index;\n\tei->packet_index = 0;\n\tei->fire_time = delay ? (cl.time + delay) : 0.0f;\n\tei->flags\t= flags;\n\tei->args = *args;\n}\n\n/*\n=============\nCL_ParseReliableEvent\n\n=============\n*/\nvoid CL_ParseReliableEvent( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tevent_index;\n\tevent_args_t\tnullargs, args;\n\tfloat\t\tdelay = 0.0f;\n\n\tmemset( &nullargs, 0, sizeof( nullargs ));\n\n\tevent_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );\n\n\t// reliable events not use delta-compression just null-compression\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tDelta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f );\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tdelay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f);\n\t}\n\telse\n\t{\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tdelay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f);\n\t\tMSG_ReadDeltaEvent( msg, &nullargs, &args );\n\t}\n\n\tif( args.entindex > 0 && args.entindex <= cl.maxclients )\n\t{\n\t\targs.angles[PITCH] *= 3.0f;\n\t\tif( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\t\targs.angles[PITCH] = -args.angles[PITCH];\n\t}\n\n\tCL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args );\n}\n\n\n/*\n=============\nCL_ParseEvent\n\n=============\n*/\nvoid CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tevent_index;\n\tint\t\ti, num_events;\n\tint\t\tpacket_index;\n\tconst event_args_t nullargs = { 0 };\n\tevent_args_t args = { 0 };\n\tentity_state_t\t*state;\n\tfloat\t\tdelay;\n\tint\t\tentity_bits;\n\n\tnum_events = MSG_ReadUBitLong( msg, 5 );\n\n\tif( proto == PROTO_GOLDSRC )\n\t\tentity_bits = MAX_GOLDSRC_ENTITY_BITS;\n\telse if( proto == PROTO_LEGACY )\n\t\tentity_bits = MAX_LEGACY_ENTITY_BITS;\n\telse entity_bits = MAX_ENTITY_BITS;\n\n\t// parse events queue\n\tfor( i = 0 ; i < num_events; i++ )\n\t{\n\t\tevent_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );\n\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t{\n\t\t\tpacket_index = MSG_ReadUBitLong( msg, entity_bits );\n\n\t\t\tif( MSG_ReadOneBit( msg ))\n\t\t\t{\n\t\t\t\tif( proto == PROTO_GOLDSRC )\n\t\t\t\t\tDelta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f );\n\t\t\t\telse MSG_ReadDeltaEvent( msg, &nullargs, &args );\n\t\t\t}\n\t\t}\n\t\telse packet_index = -1;\n\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tdelay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f);\n\t\telse delay = 0.0f;\n\n\t\tif( packet_index != -1 )\n\t\t{\n\t\t\tframe_t\t*frame = &cl.frames[cl.parsecountmod];\n\n\t\t\tif( packet_index < frame->num_entities )\n\t\t\t{\n\t\t\t\tstate = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities];\n\t\t\t\targs.entindex = state->number;\n\n\t\t\t\tif( VectorIsNull( args.origin ))\n\t\t\t\t\tVectorCopy( state->origin, args.origin );\n\n\t\t\t\tif( VectorIsNull( args.angles ))\n\t\t\t\t\tVectorCopy( state->angles, args.angles );\n\n\t\t\t\tCOM_NormalizeAngles( args.angles );\n\n\t\t\t\tif( state->number > 0 && state->number <= cl.maxclients )\n\t\t\t\t{\n\t\t\t\t\targs.angles[PITCH] *= 3.0f;\n\t\t\t\t\tif( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\t\t\t\t\targs.angles[PITCH] = -args.angles[PITCH];\n\t\t\t\t\tCL_CalcPlayerVelocity( state->number, args.velocity );\n\t\t\t\t\targs.ducking = ( state->usehull == 1 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( args.entindex != 0 )\n\t\t\t\t{\n\t\t\t\t\tif( args.entindex > 0 && args.entindex <= cl.maxclients )\n\t\t\t\t\t{\n\t\t\t\t\t\targs.angles[PITCH] /= 3.0f;\n\t\t\t\t\t\tif( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\t\t\t\t\t\targs.angles[PITCH] = -args.angles[PITCH];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Place event on queue\n\t\t\tCL_QueueEvent( FEV_SERVER, event_index, delay, &args );\n\t\t}\n\t}\n}\n\n/*\n=============\nCL_PlaybackEvent\n\n=============\n*/\nvoid GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )\n{\n\tevent_args_t\targs;\n\n\tif( FBitSet( flags, FEV_SERVER ))\n\t\treturn;\n\n\t// first check event for out of bounds\n\tif( eventindex < 1 || eventindex >= MAX_EVENTS )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: invalid eventindex %i\\n\", __func__, eventindex );\n\t\treturn;\n\t}\n\n\t// check event for precached\n\tif( !CL_EventIndex( cl.event_precache[eventindex] ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: event %i was not precached\\n\", __func__, eventindex );\n\t\treturn;\n\t}\n\n\tSetBits( flags, FEV_CLIENT ); // it's a client event\n\tClearBits( flags, FEV_NOTHOST|FEV_HOSTONLY|FEV_GLOBAL );\n\tif( delay < 0.0f ) delay = 0.0f; // fixup negative delays\n\n\tmemset( &args, 0, sizeof( args ));\n\n\tVectorCopy( origin, args.origin );\n\tVectorCopy( angles, args.angles );\n\tVectorCopy( cl.simvel, args.velocity );\n\targs.entindex = cl.playernum + 1;\n\targs.ducking = ( cl.local.usehull == 1 );\n\n\targs.fparam1 = fparam1;\n\targs.fparam2 = fparam2;\n\targs.iparam1 = iparam1;\n\targs.iparam2 = iparam2;\n\targs.bparam1 = bparam1;\n\targs.bparam2 = bparam2;\n\n\tCL_QueueEvent( flags, eventindex, delay, &args );\n}\n"
  },
  {
    "path": "engine/client/cl_font.c",
    "content": "/*\ncl_font.c - bare bones engine font manager\nCopyright (C) 2023 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"filesystem.h\"\n#include \"client.h\"\n#include \"qfont.h\"\n\nqboolean CL_FixedFont( cl_font_t *font )\n{\n\treturn font && font->valid && font->type == FONT_FIXED;\n}\n\nstatic int CL_LoadFontTexture( const char *fontname, uint texFlags, int *width )\n{\n\tint font_width;\n\tint tex;\n\n\tif( !g_fsapi.FileExists( fontname, false ))\n\t\treturn 0;\n\n\ttex = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, texFlags );\n\tif( !tex )\n\t\treturn 0;\n\n\tfont_width = REF_GET_PARM( PARM_TEX_WIDTH, tex );\n\tif( !font_width )\n\t{\n\t\tref.dllFuncs.GL_FreeTexture( tex );\n\t\treturn 0;\n\t}\n\n\t*width = font_width;\n\treturn tex;\n}\n\nstatic int CL_FontRenderMode( convar_t *fontrender )\n{\n\tswitch((int)fontrender->value )\n\t{\n\tcase 0:\n\t\treturn kRenderTransAdd;\n\tcase 1:\n\t\treturn kRenderTransAlpha;\n\tcase 2:\n\t\treturn kRenderTransTexture;\n\tdefault:\n\t\tCvar_DirectSet( fontrender, fontrender->def_string );\n\t}\n\n\treturn kRenderTransTexture;\n}\n\nvoid CL_SetFontRendermode( cl_font_t *font )\n{\n\tref.dllFuncs.GL_SetRenderMode( CL_FontRenderMode( font->rendermode ));\n}\n\nqboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags )\n{\n\tint font_width, i;\n\n\tif( !rendermode )\n\t\treturn false;\n\n\tif( font->valid )\n\t\treturn true; // already loaded\n\n\tfont->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );\n\tif( !font->hFontTexture )\n\t\treturn false;\n\n\tfont->type = FONT_FIXED;\n\tfont->valid = true;\n\tfont->scale = scale;\n\tfont->rendermode = rendermode;\n\tfont->charHeight = Q_rint( font_width / 16 * scale );\n\n\tfor( i = 0; i < ARRAYSIZE( font->fontRc ); i++ )\n\t{\n\t\tfont->fontRc[i].left   = ( i * font_width / 16 ) % font_width;\n\t\tfont->fontRc[i].right  = font->fontRc[i].left + font_width / 16;\n\t\tfont->fontRc[i].top    = ( i / 16 ) * ( font_width / 16 );\n\t\tfont->fontRc[i].bottom = font->fontRc[i].top + font_width / 16;\n\n\t\tfont->charWidths[i] = Q_rint( font_width / 16 * scale );\n\t}\n\n\treturn true;\n}\n\nqboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags )\n{\n\tfs_offset_t length;\n\tqfont_t src;\n\tbyte *pfile;\n\tint font_width, i;\n\n\tif( !rendermode )\n\t\treturn false;\n\n\tif( font->valid )\n\t\treturn true;\n\n\tpfile = g_fsapi.LoadFile( fontname, &length, false );\n\tif( !pfile )\n\t\treturn false;\n\n\tif( length < sizeof( src ))\n\t{\n\t\tMem_Free( pfile );\n\t\treturn false;\n\t}\n\n\tmemcpy( &src, pfile, sizeof( src ));\n\tMem_Free( pfile );\n\n\tfont->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );\n\tif( !font->hFontTexture )\n\t\treturn false;\n\n\tfont->type = FONT_VARIABLE;\n\tfont->valid = true;\n\tfont->scale = scale ? scale : 1.0f;\n\tfont->rendermode = rendermode;\n\tfont->charHeight = Q_rint( src.rowheight * scale );\n\n\tfor( i = 0; i < ARRAYSIZE( font->fontRc ); i++ )\n\t{\n\t\tconst charinfo *ci = &src.fontinfo[i];\n\n\t\tfont->fontRc[i].left   = (word)ci->startoffset % font_width;\n\t\tfont->fontRc[i].right  = font->fontRc[i].left + ci->charwidth;\n\t\tfont->fontRc[i].top    = (word)ci->startoffset / font_width;\n\t\tfont->fontRc[i].bottom = font->fontRc[i].top + src.rowheight;\n\n\t\tfont->charWidths[i] = Q_rint( src.fontinfo[i].charwidth * scale );\n\t}\n\n\treturn true;\n}\n\nvoid CL_FreeFont( cl_font_t *font )\n{\n\tif( !font || !font->valid )\n\t\treturn;\n\n\tref.dllFuncs.GL_FreeTexture( font->hFontTexture );\n\tmemset( font, 0, sizeof( *font ));\n}\n\nstatic int CL_CalcTabStop( const cl_font_t *font, int x )\n{\n\tint space = font->charWidths[' '];\n\tint tab   = space * 6; // 6 spaces\n\tint stop  = tab - x % tab;\n\n\tif( stop < space )\n\t\treturn tab * 2 - x % tab; // select next\n\n\treturn stop;\n}\n\nint CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_t *font, int flags )\n{\n\twrect_t *rc;\n\tfloat w, h;\n\tfloat s1, t1, s2, t2, half = 0.5f;\n\tint texw, texh;\n\n\tif( !font || !font->valid || y < -font->charHeight )\n\t\treturn 0;\n\n\t// check if printable\n\tif( number <= 32 )\n\t{\n\t\tif( number == ' ' )\n\t\t\treturn font->charWidths[' '];\n\t\telse if( number == '\\t' )\n\t\t\treturn CL_CalcTabStop( font, x );\n\t\treturn 0;\n\t}\n\n\tif( FBitSet( flags, FONT_DRAW_UTF8 ))\n\t\tnumber = Con_UtfProcessChar( number & 255 );\n\telse number &= 255;\n\n\tif( !number || !font->charWidths[number])\n\t\treturn 0;\n\n\tR_GetTextureParms( &texw, &texh, font->hFontTexture );\n\tif( !texw || !texh )\n\t\treturn font->charWidths[number];\n\n\trc = &font->fontRc[number];\n\n\tif( font->scale <= 1.f || !REF_GET_PARM( PARM_TEX_FILTERING, font->hFontTexture ))\n\t\thalf = 0;\n\n\ts1 = ((float)rc->left + half ) / texw;\n\tt1 = ((float)rc->top + half ) / texh;\n\ts2 = ((float)rc->right - half ) / texw;\n\tt2 = ((float)rc->bottom - half ) / texh;\n\tw = ( rc->right - rc->left ) * font->scale;\n\th = ( rc->bottom - rc->top ) * font->scale;\n\n\tif( FBitSet( flags, FONT_DRAW_HUD ))\n\t\tSPR_AdjustSize( &x, &y, &w, &h );\n\n\tif( !FBitSet( flags, FONT_DRAW_NORENDERMODE ))\n\t\tCL_SetFontRendermode( font );\n\n\t// don't apply color to fixed fonts it's already colored\n\tif( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8\n\t\tref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );\n\telse ref.dllFuncs.Color4ub( 255, 255, 255, color[3] );\n\tref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, font->hFontTexture );\n\n\treturn font->charWidths[number];\n}\n\nint CL_DrawString( float x, float y, const char *s, const rgba_t color, cl_font_t *font, int flags )\n{\n\trgba_t current_color;\n\tint draw_len = 0;\n\n\tif( !font || !font->valid )\n\t\treturn 0;\n\n\tif( FBitSet( flags, FONT_DRAW_UTF8 ))\n\t\tCon_UtfProcessChar( 0 ); // clear utf state\n\n\tif( !FBitSet( flags, FONT_DRAW_NORENDERMODE ))\n\t\tCL_SetFontRendermode( font );\n\n\tVector4Copy( color, current_color );\n\n\twhile( *s )\n\t{\n\t\tif( *s == '\\n' )\n\t\t{\n\t\t\ts++;\n\n\t\t\tif( !*s )\n\t\t\t\tbreak;\n\n\t\t\t// some client functions ignore newlines\n\t\t\tif( !FBitSet( flags, FONT_DRAW_NOLF ))\n\t\t\t{\n\t\t\t\tdraw_len = 0;\n\t\t\t\ty += font->charHeight;\n\t\t\t}\n\n\t\t\tif( FBitSet( flags, FONT_DRAW_RESETCOLORONLF ))\n\t\t\t\t Vector4Copy( color, current_color );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( IsColorString( s ))\n\t\t{\n\t\t\t// don't copy alpha\n\t\t\tif( !FBitSet( flags, FONT_DRAW_FORCECOL ))\n\t\t\t\tVectorCopy( g_color_table[ColorIndex(*( s + 1 ))], current_color );\n\n\t\t\ts += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// skip setting rendermode, it was changed for this string already\n\t\tdraw_len += CL_DrawCharacter( x + draw_len, y, (byte)*s, current_color, font, flags | FONT_DRAW_NORENDERMODE );\n\n\t\ts++;\n\t}\n\n\treturn draw_len;\n}\n\nint CL_DrawStringf( cl_font_t *font, float x, float y, const rgba_t color, int flags, const char *fmt, ... )\n{\n\tva_list va;\n\tchar buf[MAX_VA_STRING];\n\n\tva_start( va, fmt );\n\tQ_vsnprintf( buf, sizeof( buf ), fmt, va );\n\tva_end( va );\n\n\treturn CL_DrawString( x, y, buf, color, font, flags );\n}\n\nvoid CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height )\n{\n\tif( !font || !font->valid ) return;\n\tif( width )\n\t{\n\t\tif( number == '\\t' )\n\t\t\t*width = CL_CalcTabStop( font, 0 ); // at least return max tabstop\n\t\telse *width = font->charWidths[number & 255];\n\t}\n\tif( height ) *height = font->charHeight;\n}\n\nvoid CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags )\n{\n\tint draw_len = 0;\n\n\tif( !font || !font->valid )\n\t\treturn;\n\n\tif( height )\n\t\t*height = font->charHeight;\n\n\tif( width )\n\t\t*width = 0;\n\n\tif( !COM_CheckString( s ))\n\t\treturn;\n\n\tif( FBitSet( flags, FONT_DRAW_UTF8 ))\n\t\tCon_UtfProcessChar( 0 ); // reset utf state\n\n\twhile( *s )\n\t{\n\t\tint number;\n\n\t\tif( *s == '\\n' )\n\t\t{\n\t\t\t// BUG: no check for end string here\n\t\t\t// but high chances somebody's relying on this\n\t\t\ts++;\n\t\t\tdraw_len = 0;\n\t\t\tif( !FBitSet( flags, FONT_DRAW_NOLF ))\n\t\t\t{\n\t\t\t\tif( height )\n\t\t\t\t\t*height += font->charHeight;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\telse if( *s == '\\t' )\n\t\t{\n\t\t\tdraw_len += CL_CalcTabStop( font, 0 ); // at least return max tabstop\n\t\t\ts++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( IsColorString( s ))\n\t\t{\n\t\t\ts += 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( FBitSet( flags, FONT_DRAW_UTF8 ))\n\t\t\tnumber = Con_UtfProcessChar( (byte)*s );\n\t\telse number = (byte)*s;\n\n\t\tif( number )\n\t\t{\n\t\t\tdraw_len += font->charWidths[number];\n\n\t\t\tif( width )\n\t\t\t{\n\t\t\t\tif( draw_len > *width )\n\t\t\t\t\t*width = draw_len;\n\t\t\t}\n\t\t}\n\n\t\ts++;\n\t}\n}\n"
  },
  {
    "path": "engine/client/cl_frame.c",
    "content": "/*\ncl_frame.c - client world snapshot\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"entity_types.h\"\n#include \"pm_local.h\"\n#include \"cl_tent.h\"\n#include \"studio.h\"\n#include \"dlight.h\"\n#include \"sound.h\"\n#include \"input.h\"\n\n// #define STUDIO_INTERPOLATION_FIX\n\n/*\n=========================================================================\n\nFRAME INTERPOLATION\n\n=========================================================================\n*/\n/*\n==================\nCL_UpdatePositions\n\nStore another position into interpolation circular buffer\n==================\n*/\nstatic void CL_UpdatePositions( cl_entity_t *ent )\n{\n\tposition_history_t\t*ph, *prev;\n\n\tprev = &ent->ph[ent->current_position];\n\n\tent->current_position = (ent->current_position + 1) & HISTORY_MASK;\n\tph = &ent->ph[ent->current_position];\n\tVectorCopy( ent->curstate.origin, ph->origin );\n\tVectorCopy( ent->curstate.angles, ph->angles );\n\n\tph->animtime = ent->curstate.animtime;\n\n\t// a1ba: for some reason, this sometimes still may happen\n\t// at this time, I'm not sure whether this bug happens in delta readwrite code\n\t// or server just decides to go backwards and really sends these values\n\tif( ph->animtime < prev->animtime )\n\t{\n\t\t// try to deduce real animtime by looking up the difference between\n\t\t// server messages (cl.mtime is never modified ny the interpolation code)\n\t\tfloat diff = Q_max( 0, ent->curstate.msg_time - ent->prevstate.msg_time );\n\n\t\tph->animtime = prev->animtime + diff;\n\t}\n}\n\n/*\n==================\nCL_ResetPositions\n\nInterpolation init or reset after teleporting\n==================\n*/\nstatic void CL_ResetPositions( cl_entity_t *ent )\n{\n\tposition_history_t\tstore;\n\n\tif( !ent ) return;\n\n\tstore = ent->ph[ent->current_position];\n\tent->current_position = 1;\n\n\tmemset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX );\n\tent->ph[1] = ent->ph[0] = store;\n}\n\n/*\n==================\nCL_EntityTeleported\n\ncheck for instant movement in case\nwe don't want interpolate this\n==================\n*/\nstatic qboolean CL_EntityTeleported( cl_entity_t *ent )\n{\n\tfloat\tlen, maxlen;\n\tvec3_t\tdelta;\n\n\tVectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta );\n\n\t// compute potential max movement in units per frame and compare with entity movement\n\tmaxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));\n\tlen = VectorLength( delta );\n\n\treturn (len > maxlen);\n}\n\n/*\n==================\nCL_CompareTimestamps\n\nround-off floating errors\n==================\n*/\nstatic qboolean CL_CompareTimestamps( float t1, float t2 )\n{\n\tint\tiTime1 = t1 * 1000;\n\tint\tiTime2 = t2 * 1000;\n\n\treturn (( iTime1 - iTime2 ) <= 1 );\n}\n\n/*\n==================\nCL_EntityIgnoreLerp\n\nsome ents will be ignore lerping\n==================\n*/\nstatic qboolean CL_EntityIgnoreLerp( cl_entity_t *e )\n{\n\tif( cl_nointerp.value > 0.f )\n\t\treturn true;\n\n\tif( e->model && e->model->type == mod_alias )\n\t\treturn false;\n\n\treturn (e->curstate.movetype == MOVETYPE_NONE) ? true : false;\n}\n\n/*\n==================\nCL_EntityCustomLerp\n\n==================\n*/\nstatic qboolean CL_EntityCustomLerp( cl_entity_t *e )\n{\n\tswitch( e->curstate.movetype )\n\t{\n\tcase MOVETYPE_NONE:\n\tcase MOVETYPE_STEP:\n\tcase MOVETYPE_WALK:\n\tcase MOVETYPE_FLY:\n\tcase MOVETYPE_COMPOUND:\n\t\treturn false;\n\n\t// ABSOLUTELY STUPID HACK TO ALLOW MONSTERS\n\t// INTERPOLATION IN GRAVGUNMOD COOP\n\t// MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT\n\tcase MOVETYPE_TOSS:\n\t\tif( cls.legacymode == PROTO_LEGACY && e->model && e->model->type == mod_studio )\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nCL_ParametricMove\n\ncheck for parametrical moved entities\n==================\n*/\nstatic qboolean CL_ParametricMove( cl_entity_t *ent )\n{\n\tfloat\tfrac, dt, t;\n\tvec3_t\tdelta;\n\n\tif( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f )\n\t\treturn false;\n\n\tVectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta );\n\tdt = ent->curstate.impacttime - ent->curstate.starttime;\n\n\tif( dt != 0.0f )\n\t{\n\t\tif( ent->lastmove > cl.time )\n\t\t\tt = ent->lastmove;\n\t\telse t = cl.time;\n\n\t\tfrac = ( t - ent->curstate.starttime ) / dt;\n\t\tfrac = bound( 0.0f, frac, 1.0f );\n\t\tVectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin );\n\n\t\tent->lastmove = t;\n\t}\n\n\tVectorNormalize( delta );\n\tif( VectorLength( delta ) > 0.0f )\n\t\tVectorAngles( delta, ent->curstate.angles ); // re-aim projectile\n\n\treturn true;\n}\n\n/*\n====================\nCL_UpdateLatchedVars\n\n====================\n*/\nstatic void CL_UpdateLatchedVars( cl_entity_t *ent )\n{\n\tif( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio ))\n\t\treturn; // below fields used only for alias and studio interpolation\n\n\tVectorCopy( ent->prevstate.origin, ent->latched.prevorigin );\n\tVectorCopy( ent->prevstate.angles, ent->latched.prevangles );\n\n\tif( ent->model->type == mod_alias )\n\t\tent->latched.prevframe = ent->prevstate.frame;\n\tent->latched.prevanimtime = ent->prevstate.animtime;\n\n\tif( ent->curstate.sequence != ent->prevstate.sequence )\n\t{\n\t\tmemcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->latched.prevseqblending ));\n\t\tent->latched.prevsequence = ent->prevstate.sequence;\n\t\tent->latched.sequencetime = ent->curstate.animtime;\n\t}\n\n\tmemcpy( ent->latched.prevcontroller, ent->prevstate.controller, sizeof( ent->latched.prevcontroller ));\n\tmemcpy( ent->latched.prevblending, ent->prevstate.blending, sizeof( ent->latched.prevblending ));\n\n\t// update custom latched vars\n\tif( clgame.drawFuncs.CL_UpdateLatchedVars != NULL )\n\t\tclgame.drawFuncs.CL_UpdateLatchedVars( ent, false );\n}\n\n/*\n====================\nCL_GetStudioEstimatedFrame\n\n====================\n*/\nstatic float CL_GetStudioEstimatedFrame( cl_entity_t *ent )\n{\n\tstudiohdr_t\t*pstudiohdr;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tint\t\tsequence;\n\n\tif( ent->model != NULL && ent->model->type == mod_studio )\n\t{\n\t\tpstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model );\n\n\t\tif( pstudiohdr && pstudiohdr->numseq > 0 )\n\t\t{\n\t\t\tsequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 );\n\t\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;\n\t\t\treturn ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time );\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/*\n====================\nCL_ResetLatchedVars\n\n====================\n*/\nvoid CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset )\n{\n\tif( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio ))\n\t\treturn; // below fields used only for alias and studio interpolation\n\n\tif( full_reset )\n\t{\n\t\t// don't modify for sprites to avoid broke sprite interp\n\t\tmemcpy( ent->latched.prevblending, ent->curstate.blending, sizeof( ent->latched.prevblending ));\n\t\tent->latched.sequencetime = ent->curstate.animtime;\n\t\tmemcpy( ent->latched.prevcontroller, ent->curstate.controller, sizeof( ent->latched.prevcontroller ));\n\t\tif( ent->model->type == mod_studio )\n\t\t\tent->latched.prevframe = CL_GetStudioEstimatedFrame( ent );\n\t\telse if( ent->model->type == mod_alias )\n\t\t\tent->latched.prevframe = ent->curstate.frame;\n\t\tent->prevstate = ent->curstate;\n\t}\n\n\tent->latched.prevanimtime = ent->curstate.animtime = cl.mtime[0];\n\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\tVectorCopy( ent->curstate.angles, ent->latched.prevangles );\n\tent->latched.prevsequence = ent->curstate.sequence;\n\n\t// update custom latched vars\n\tif( clgame.drawFuncs.CL_UpdateLatchedVars != NULL )\n\t\tclgame.drawFuncs.CL_UpdateLatchedVars( ent, true );\n}\n\n/*\n==================\nCL_ProcessEntityUpdate\n\napply changes since new frame received\n==================\n*/\nstatic void CL_ProcessEntityUpdate( cl_entity_t *ent )\n{\n\tqboolean\tparametric;\n\n\tent->model = CL_ModelHandle( ent->curstate.modelindex );\n\tent->index = ent->curstate.number;\n\n\tif( FBitSet( ent->curstate.entityType, ENTITY_NORMAL ))\n\t\tCOM_NormalizeAngles( ent->curstate.angles );\n\n\tparametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f;\n\n\t// allow interpolation on bmodels too\n\tif( ent->model && ent->model->type == mod_brush )\n\t\tent->curstate.animtime = ent->curstate.msg_time;\n\n\tif( CL_EntityCustomLerp( ent ) && !parametric )\n\t\tent->curstate.animtime = ent->curstate.msg_time;\n\n\tif( !CL_CompareTimestamps( ent->curstate.animtime, ent->prevstate.animtime ) || CL_EntityIgnoreLerp( ent ))\n\t{\n\t\tCL_UpdateLatchedVars( ent );\n\t\tCL_UpdatePositions( ent );\n\t}\n\n\t// g-cont. it should be done for all the players?\n\tif( ent->player && !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t\tent->curstate.angles[PITCH] /= -3.0f;\n\n\tVectorCopy( ent->curstate.origin, ent->origin );\n\tVectorCopy( ent->curstate.angles, ent->angles );\n\n\t// initialize attachments for now\n\tVectorCopy( ent->origin, ent->attachment[0] );\n\tVectorCopy( ent->origin, ent->attachment[1] );\n\tVectorCopy( ent->origin, ent->attachment[2] );\n\tVectorCopy( ent->origin, ent->attachment[3] );\n}\n\n/*\n==================\nCL_FindInterpolationUpdates\n\nfind two timestamps\n==================\n*/\nstatic qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 )\n{\n\tqboolean\textrapolate = true;\n\tuint\t\ti, i0, i1, imod;\n\n\timod = ent->current_position;\n\ti0 = (imod - 0) & HISTORY_MASK;\t// curpos (lerp end)\n\ti1 = (imod - 1) & HISTORY_MASK;\t// oldpos (lerp start)\n\n\tfor( i = 1; i < HISTORY_MAX - 1; i++ )\n\t{\n\t\tdouble at = ent->ph[( imod - i ) & HISTORY_MASK].animtime;\n\n\t\tif( at == 0.0f )\n\t\t\tbreak;\n\n\t\tif( targettime > at )\n\t\t{\n\t\t\t// found it\n\t\t\ti0 = (( imod - i ) + 1 ) & HISTORY_MASK;\n\t\t\ti1 = (( imod - i ) + 0 ) & HISTORY_MASK;\n\t\t\textrapolate = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t*ph0 = &ent->ph[i0];\n\t*ph1 = &ent->ph[i1];\n\n\treturn extrapolate;\n}\n\n/*\n==================\nCL_PureOrigin\n\nnon-local players interpolation\n==================\n*/\nstatic void CL_PureOrigin( cl_entity_t *ent, double t, vec3_t outorigin, vec3_t outangles )\n{\n\tdouble\t\tt1, t0, frac;\n\tposition_history_t\t*ph0, *ph1;\n\tvec3_t\t\tdelta;\n\n\t// NOTE: ph0 is next, ph1 is a prev\n\tCL_FindInterpolationUpdates( ent, t, &ph0, &ph1 );\n\n\tt0 = ph0->animtime;\n\tt1 = ph1->animtime;\n\n\tif( t0 != 0.0 )\n\t{\n\t\tvec4_t\tq, q1, q2;\n\n\t\tVectorSubtract( ph0->origin, ph1->origin, delta );\n\n\t\tif( !Q_equal( t0, t1 ))\n\t\t\tfrac = ( t - t1 ) / ( t0 - t1 );\n\t\telse frac = 1.0;\n\n\t\tfrac = bound( 0.0, frac, 1.2 );\n\n\t\tVectorMA( ph1->origin, frac, delta, outorigin );\n\n\t\tAngleQuaternion( ph0->angles, q1, false );\n\t\tAngleQuaternion( ph1->angles, q2, false );\n\t\tQuaternionSlerp( q2, q1, frac, q );\n\t\tQuaternionAngle( q, outangles );\n\t}\n\telse\n\t{\n\t\t// no backup found\n\t\tVectorCopy( ph1->origin, outorigin );\n\t\tVectorCopy( ph1->angles, outangles );\n\t}\n}\n\n/*\n==================\nCL_InterpolateModel\n\nnon-players interpolation\n==================\n*/\nstatic int CL_InterpolateModel( cl_entity_t *e )\n{\n\tposition_history_t  *ph0 = NULL, *ph1 = NULL;\n\tvec3_t\t\torigin, angles, delta;\n\tdouble\t\tt, t1, t2, frac;\n\tvec4_t\t\tq, q1, q2;\n\n\tVectorCopy( e->curstate.origin, e->origin );\n\tVectorCopy( e->curstate.angles, e->angles );\n\n\tif( cls.timedemo || !e->model )\n\t\treturn 1;\n\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t{\n\t\t// quake lerping is easy\n\t\tVectorLerp( e->prevstate.origin, cl.lerpFrac, e->curstate.origin, e->origin );\n\t\tAngleQuaternion( e->prevstate.angles, q1, false );\n\t\tAngleQuaternion( e->curstate.angles, q2, false );\n\t\tQuaternionSlerp( q1, q2, cl.lerpFrac, q );\n\t\tQuaternionAngle( q, e->angles );\n\t\treturn 1;\n\t}\n\n\tif( cl.maxclients <= 1 )\n\t\treturn 1;\n\n\tif( e->model->type == mod_brush && !cl_bmodelinterp.value )\n\t\treturn 1;\n\n\tif( cl.local.moving && cl.local.onground == e->index )\n\t\treturn 1;\n\n\tt = cl.time - cl_interp.value;\n\tCL_FindInterpolationUpdates( e, t, &ph0, &ph1 );\n\n\tt1 = ph1->animtime;\n\tt2 = ph0->animtime;\n\n\tif( t - t1 < 0.0f )\n\t\treturn 0;\n\n\tif( t1 == 0.0f )\n\t{\n\t\tVectorCopy( ph0->origin, e->origin );\n\t\tVectorCopy( ph0->angles, e->angles );\n\t\treturn 0;\n\t}\n\n\tif( Q_equal( t2, t1 ))\n\t{\n\t\tVectorCopy( ph0->origin, e->origin );\n\t\tVectorCopy( ph0->angles, e->angles );\n\t\treturn 1;\n\t}\n\n\tVectorSubtract( ph0->origin, ph1->origin, delta );\n\tfrac = (t - t1) / (t2 - t1);\n\n\tif( frac < 0.0f )\n\t\treturn 0;\n\n\tif( frac > 1.0f )\n\t\tfrac = 1.0f;\n\n\tVectorMA( ph1->origin, frac, delta, origin );\n\n\tAngleQuaternion( ph0->angles, q1, false );\n\tAngleQuaternion( ph1->angles, q2, false );\n\tQuaternionSlerp( q2, q1, frac, q );\n\tQuaternionAngle( q, angles );\n\n\tVectorCopy( origin, e->origin );\n\tVectorCopy( angles, e->angles );\n\n\treturn 1;\n}\n\n/*\n=============\nCL_ComputePlayerOrigin\n\ninterpolate non-local clients\n=============\n*/\nvoid CL_ComputePlayerOrigin( cl_entity_t *ent )\n{\n\tdouble\ttargettime;\n\tvec4_t\tq, q1, q2;\n\tvec3_t\torigin;\n\tvec3_t\tangles;\n\n\tif( !ent->player )\n\t\treturn;\n\n\tif( cl_nointerp.value > 0.f )\n\t{\n\t\tVectorCopy( ent->curstate.angles, ent->angles );\n\t\tVectorCopy( ent->curstate.origin, ent->origin );\n\t\treturn;\n\t}\n\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t{\n\t\t// quake lerping is easy\n\t\tVectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin );\n\t\tAngleQuaternion( ent->prevstate.angles, q1, false );\n\t\tAngleQuaternion( ent->curstate.angles, q2, false );\n\t\tQuaternionSlerp( q1, q2, cl.lerpFrac, q );\n\t\tQuaternionAngle( q, ent->angles );\n\t\treturn;\n\t}\n\n\ttargettime = cl.time - cl_interp.value;\n\tCL_PureOrigin( ent, targettime, origin, angles );\n\n\tVectorCopy( angles, ent->angles );\n\tVectorCopy( origin, ent->origin );\n}\n\n/*\n=================\nCL_ProcessPlayerState\n\nprocess player states after the new packet has received\n=================\n*/\nstatic void CL_ProcessPlayerState( int playerindex, entity_state_t *state )\n{\n\tentity_state_t\t*ps;\n\n\tps = &cl.frames[cl.parsecountmod].playerstate[playerindex];\n\tps->number = state->number;\n\tps->messagenum = cl.parsecount;\n\tps->msg_time = cl.mtime[0];\n\n\tclgame.dllFuncs.pfnProcessPlayerState( ps, state );\n}\n\n/*\n=================\nCL_ResetLatchedState\n\nreset latched state if this frame entity was teleported\nor just EF_NOINTERP was set\n=================\n*/\nstatic void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent )\n{\n\tif( CHECKVISBIT( frame->flags, pnum ))\n\t{\n\t\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\t\tVectorCopy( ent->curstate.angles, ent->latched.prevangles );\n\n\t\tCL_ResetLatchedVars( ent, true );\n\t\tCL_ResetPositions( ent );\n\n\t\t// parametric interpolation will starts at this point\n\t\tif( ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f )\n\t\t\tent->lastmove = cl.time;\n\t}\n}\n\n/*\n=================\nCL_ProcessPacket\n\nprocess player states after the new packet has received\n=================\n*/\nvoid CL_ProcessPacket( frame_t *frame )\n{\n\tentity_state_t\t*state;\n\tcl_entity_t\t*ent;\n\tint\t\tpnum;\n\n\tfor( pnum = 0; pnum < frame->num_entities; pnum++ )\n\t{\n\t\t// request the entity state from circular buffer\n\t\tstate = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities];\n\t\tstate->messagenum = cl.parsecount;\n\t\tstate->msg_time = cl.mtime[0];\n\n\t\t// mark all the players\n\t\tent = &clgame.entities[state->number];\n\t\tent->player = CL_IsPlayerIndex( state->number );\n\n\t\tif( state->number == ( cl.playernum + 1 ))\n\t\t\tclgame.dllFuncs.pfnTxferLocalOverrides( state, &frame->clientdata );\n\n\t\t// shuffle states\n\t\tent->prevstate = ent->curstate;\n\t\tent->curstate = *state;\n\n\t\tCL_ProcessEntityUpdate( ent );\n\t\tCL_ResetLatchedState( pnum, frame, ent );\n\t\tif( !ent->player ) continue;\n\n\t\tCL_ProcessPlayerState(( state->number - 1 ), state );\n\n\t\tif( state->number == ( cl.playernum + 1 ))\n\t\t\tCL_CheckPredictionError();\n\t}\n}\n\n/*\n=========================================================================\n\nFRAME PARSING\n\n=========================================================================\n*/\nstatic qboolean CL_ParseEntityNumFromPacket( sizebuf_t *msg, int *newnum, connprotocol_t proto )\n{\n\tif( proto == PROTO_LEGACY )\n\t{\n\t\t*newnum = MSG_ReadWord( msg );\n\t\tif( *newnum == 0 )\n\t\t\treturn false;\n\t}\n\telse\n\t{\n\t\t*newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\t\tif( *newnum == LAST_EDICT )\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nCL_FlushEntityPacket\n\nRead and ignore whole entity packet.\n=================\n*/\nstatic void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tnewnum;\n\tentity_state_t\tfrom, to;\n\n\tmemset( &from, 0, sizeof( from ));\n\n\tcl.frames[cl.parsecountmod].valid = false;\n\tcl.validsequence = 0; // can't render a frame\n\n\t// read it all, but ignore it\n\twhile( 1 )\n\t{\n\t\tif( !CL_ParseEntityNumFromPacket( msg, &newnum, proto ))\n\t\t\tbreak; // done\n\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\t\tMSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ) ? DELTA_PLAYER : DELTA_ENTITY, cl.mtime[0] );\n\t}\n}\n\nqboolean CL_ValidateDeltaPacket( uint oldpacket, frame_t *oldframe )\n{\n\tint subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF;\n\n\tif( subtracted == 0 )\n\t{\n\t\tCon_NPrintf( 2, \"^3Warning:^1 update too old\\n^7\\n\" );\n\t\treturn false;\n\t}\n\n\tif( subtracted >= CL_UPDATE_MASK )\n\t{\n\t\t// we can't use this, it is too old\n\t\tCon_NPrintf( 2, \"^3Warning:^1 delta frame is too old^7\\n\" );\n\t\treturn false;\n\t}\n\n\tif(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES ))\n\t{\n\t\tCon_NPrintf( 2, \"^3Warning:^1 delta frame is too old^7\\n\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nint CL_UpdateOldEntNum( int oldindex, frame_t *oldframe, entity_state_t **oldent )\n{\n\tif( !oldframe )\n\t{\n\t\t*oldent = NULL;\n\t\treturn MAX_ENTNUMBER;\n\t}\n\n\tif( oldindex >= oldframe->num_entities )\n\t\treturn MAX_ENTNUMBER;\n\n\t*oldent = &cls.packet_entities[(oldframe->first_entity + oldindex) % cls.num_client_entities];\n\treturn (*oldent)->number;\n}\n\n/*\n=================\nCL_DeltaEntity\n\nprocessing delta update\n=================\n*/\nstatic void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update )\n{\n\tcl_entity_t\t*ent;\n\tentity_state_t\t*state;\n\tqboolean\t\tnewent = (old) ? false : true;\n\tint\t\tpack = frame->num_entities;\n\tint\t\tdelta_type = DELTA_ENTITY;\n\tqboolean\t\talive = true;\n\n\t// alloc next slot to store update\n\tstate = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];\n\tif( CL_IsPlayerIndex( newnum )) delta_type = DELTA_PLAYER;\n\n\tif(( newnum < 0 ) || ( newnum >= clgame.maxEntities ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: invalid newnum: %d\\n\", __func__, newnum );\n\t\tif( has_update )\n\t\t\tMSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] );\n\t\treturn;\n\t}\n\n\tent = CL_EDICT_NUM( newnum );\n\tent->index = newnum; // enumerate entity index\n\tif( newent ) old = &ent->baseline;\n\n\tif( has_update )\n\t\talive = MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] );\n\telse *state = *old;\n\n\tif( !alive )\n\t{\n\t\tCL_KillDeadBeams( ent ); // release dead beams\n#if 0\n\t\t// this is for reference\n\t\tif( state->number == -1 )\n\t\t\tCon_DPrintf( \"Entity %i was removed from server\\n\", newnum );\n\t\telse Con_Dprintf( \"Entity %i was removed from delta-message\\n\", newnum );\n#endif\n\t\treturn;\n\t}\n\n\tif( newent )\n\t{\n\t\t// interpolation must be reset\n\t\tSETVISBIT( frame->flags, pack );\n\n\t\t// release beams from previous entity\n\n\t\t// a1ba: check that this entity number was never used on client\n\t\t// as beams can be transferred before this entity was sent to client\n\t\t// (for example, beam was sent over during beam entity spawn\n\t\t// but referenced start point entity hasn't been sent over due to PVS)\n\t\tif( ent->curstate.messagenum != 0 )\n\t\t\tCL_KillDeadBeams( ent );\n\t}\n\n\t// add entity to packet\n\tcls.next_client_entities++;\n\tframe->num_entities++;\n}\n\n/*\n==================\nCL_ParsePacketEntities\n\nAn svc_packetentities has just been parsed, deal with the\nrest of the data stream.\n==================\n*/\nint CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto )\n{\n\tframe_t\t\t*newframe, *oldframe;\n\tint\t\toldindex, newnum, oldnum;\n\tint\t\tplayerbytes = 0;\n\tint\t\tbufStart;\n\tentity_state_t\t*oldent;\n\tqboolean\t\tplayer;\n\tint\t\tcount;\n\n\t// save first uncompressed packet as timestamp\n\tif( cls.changelevel && !delta && cls.demorecording )\n\t\tCL_WriteDemoJumpTime();\n\n\t// sentinel count. save it for debug checking\n\tif( proto == PROTO_LEGACY )\n\t\tcount = MSG_ReadWord( msg );\n\telse count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1;\n\n\tnewframe = &cl.frames[cl.parsecountmod];\n\n\t// allocate parse entities\n\tmemset( newframe->flags, 0, sizeof( newframe->flags ));\n\tnewframe->first_entity = cls.next_client_entities;\n\tnewframe->num_entities = 0;\n\tnewframe->valid = true; // assume valid\n\n\tif( delta )\n\t{\n\t\tuint oldpacket = MSG_ReadByte( msg );\n\t\toldframe = &cl.frames[oldpacket & CL_UPDATE_MASK];\n\n\t\tif( !CL_ValidateDeltaPacket( oldpacket, oldframe ))\n\t\t{\n\t\t\tCL_FlushEntityPacket( msg, proto );\n\t\t\treturn playerbytes;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// this is a full update that we can start delta compressing from now\n\t\toldframe = NULL;\n\t\tcls.demowaiting = false;\t// we can start recording now\n\t}\n\n\t// mark current delta state\n\tcl.validsequence = cls.netchan.incoming_sequence;\n\n\toldent = NULL;\n\toldindex = 0;\n\toldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent );\n\n\twhile( 1 )\n\t{\n\t\tif( !CL_ParseEntityNumFromPacket( msg, &newnum, proto ))\n\t\t\tbreak; // done\n\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\t\tplayer = CL_IsPlayerIndex( newnum );\n\n\t\twhile( oldnum < newnum )\n\t\t{\n\t\t\t// one or more entities from the old packet are unchanged\n\t\t\tCL_DeltaEntity( msg, newframe, oldnum, oldent, false );\n\t\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t\t}\n\n\t\tif( oldnum == newnum )\n\t\t{\n\t\t\t// delta from previous state\n\t\t\tbufStart = MSG_GetNumBytesRead( msg );\n\t\t\tCL_DeltaEntity( msg, newframe, newnum, oldent, true );\n\t\t\tif( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( oldnum > newnum )\n\t\t{\n\t\t\t// delta from baseline ?\n\t\t\tbufStart = MSG_GetNumBytesRead( msg );\n\t\t\tCL_DeltaEntity( msg, newframe, newnum, NULL, true );\n\t\t\tif( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\t// any remaining entities in the old frame are copied over\n\twhile( oldnum != MAX_ENTNUMBER )\n\t{\n\t\t// one or more entities from the old packet are unchanged\n\t\tCL_DeltaEntity( msg, newframe, oldnum, oldent, false );\n\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t}\n\n\tif( newframe->num_entities != count && newframe->num_entities != 0 )\n\t\tCon_Reportf( S_WARN \"%s%s: (%i should be %i)\\n\", __func__, delta ? \"Delta\" : \"\", newframe->num_entities, count );\n\n\tif( !newframe->valid )\n\t\treturn playerbytes; // frame is not valid but message was parsed\n\n\t// now process packet.\n\tCL_ProcessPacket( newframe );\n\n\t// add new entities into physic lists\n\tCL_SetSolidEntities();\n\n\t// first update is the final signon stage where we actually receive an entity (i.e., the world at least)\n\tif( cls.signon == ( SIGNONS - 1 ))\n\t{\n\t\t// we are done with signon sequence.\n\t\tcls.signon = SIGNONS;\n\n\t\t// Clear loading plaque.\n\t\tCL_SignonReply( proto );\n\t}\n\n\treturn playerbytes;\n}\n\n/*\n==========================================================================\n\nINTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS\n\n==========================================================================\n*/\n/*\n=============\nCL_AddVisibleEntity\n\nall the visible entities should pass this filter\n=============\n*/\nqboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )\n{\n\tqboolean draw_player = true;\n\n\tif( !ent || !ent->model )\n\t\treturn false;\n\n\t// don't add the player in firstperson mode\n\tif( RP_LOCALCLIENT( ent ))\n\t{\n\t\tcl.local.apply_effects = true;\n\n\t\tif( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity ))\n\t\t{\n\t\t\t// we don't draw player in default renderer in firstperson mode\n\t\t\t// but let the client.dll know about player entity anyway\n\t\t\t// for use in custom renderers\n\t\t\tdraw_player = false;\n\t\t}\n\t}\n\n\t// check for adding this entity\n\tif( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name ))\n\t{\n\t\t// local player was reject by game code, so ignore any effects\n\t\tif( RP_LOCALCLIENT( ent ))\n\t\t\tcl.local.apply_effects = false;\n\t\treturn false;\n\t}\n\n\tif( !draw_player )\n\t\treturn false;\n\n\tif( entityType == ET_BEAM )\n\t{\n\t\tref.dllFuncs.CL_AddCustomBeam( ent );\n\t\treturn true;\n\t}\n\telse if( !ref.dllFuncs.R_AddEntity( ent, entityType ))\n\t{\n\t\treturn false;\n\t}\n\n\t// because pTemp->entity.curstate.effects\n\t// is already occupied by FTENT_FLICKER\n\tif( entityType != ET_TEMPENTITY && !RP_LOCALCLIENT( ent ) )\n\t{\n\t\t// apply client-side effects\n\t\tCL_AddEntityEffects( ent );\n\n\t\t// alias & studiomodel efefcts\n\t\tCL_AddModelEffects( ent );\n\t}\n\n\treturn true;\n}\n\n/*\n=============\nCL_LinkCustomEntity\n\nAdd server beam to draw list\n=============\n*/\nstatic void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state )\n{\n\tent->curstate.movetype = state->modelindex; // !!!\n\n\tif( ent->model->type != mod_sprite )\n\t\tCon_Reportf( S_WARN \"bad model on beam ( %s )\\n\", ent->model->name );\n\n\tent->latched.prevsequence = ent->curstate.sequence;\n\tVectorCopy( ent->origin, ent->latched.prevorigin );\n\tVectorCopy( ent->angles, ent->latched.prevangles );\n\tent->prevstate = ent->curstate;\n\n\tCL_AddVisibleEntity( ent, ET_BEAM );\n}\n\n/*\n=============\nCL_LinkPlayers\n\nCreate visible entities in the correct position\nfor all current players\n=============\n*/\nstatic void CL_LinkPlayers( frame_t *frame )\n{\n\tentity_state_t\t*state;\n\tcl_entity_t\t*ent;\n\tint\t\ti;\n\n\tent = CL_GetLocalPlayer();\n\n\t// apply muzzleflash to weaponmodel\n\tif( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ))\n\t\tSetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH );\n\n\t// check all the clients but add only visible\n\tfor( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ )\n\t{\n\t\tif( state->messagenum != cl.parsecount )\n\t\t\tcontinue;\t// not present this frame\n\n\t\tif( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))\n\t\t\tcontinue;\n\n\t\tent = &clgame.entities[i + 1];\n\n\t\t// fixup the player indexes...\n\t\tif( ent->index != ( i + 1 )) ent->index = (i + 1);\n\n\t\tif( i == cl.playernum )\n\t\t{\n\t\t\tif( cls.demoplayback != DEMO_QUAKE1 )\n\t\t\t{\n\t\t\t\tVectorCopy( state->origin, ent->origin );\n\t\t\t\tVectorCopy( state->origin, ent->prevstate.origin );\n\t\t\t\tVectorCopy( state->origin, ent->curstate.origin );\n\t\t\t}\n\t\t\tVectorCopy( ent->curstate.angles, ent->angles );\n\t\t}\n\n\t\tif( FBitSet( ent->curstate.effects, EF_NOINTERP ))\n\t\t\tCL_ResetLatchedVars( ent, false );\n\n\t\tif( CL_EntityTeleported( ent ))\n\t\t{\n\t\t\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\t\t\tVectorCopy( ent->curstate.angles, ent->latched.prevangles );\n\t\t\tCL_ResetPositions( ent );\n\t\t}\n\n\t\tif ( i == cl.playernum )\n\t\t{\n\t\t\t// using interpolation only for local player angles\n\t\t\tCL_ComputePlayerOrigin( ent );\n\n\t\t\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t\t\t\tVectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, cl.simorg );\n\t\t\tVectorCopy( cl.simorg, ent->origin );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( ent->curstate.origin, ent->origin );\n\t\t\tVectorCopy( ent->curstate.angles, ent->angles );\n\n\t\t\t// interpolate non-local clients\n\t\t\tCL_ComputePlayerOrigin( ent );\n\t\t}\n\n\t\tVectorCopy( ent->origin, ent->attachment[0] );\n\t\tVectorCopy( ent->origin, ent->attachment[1] );\n\t\tVectorCopy( ent->origin, ent->attachment[2] );\n\t\tVectorCopy( ent->origin, ent->attachment[3] );\n\n\t\tCL_AddVisibleEntity( ent, ET_PLAYER );\n\t}\n\n\t// apply local player effects if entity is not added\n\tif( cl.local.apply_effects ) CL_AddEntityEffects( CL_GetLocalPlayer( ));\n}\n\n/*\n===============\nCL_LinkPacketEntities\n\n===============\n*/\nstatic void CL_LinkPacketEntities( frame_t *frame )\n{\n\tcl_entity_t\t*ent;\n\tentity_state_t\t*state;\n\tqboolean\t\tparametric;\n\tqboolean\t\tinterpolate;\n\tint\t\ti;\n\n\tfor( i = 0; i < frame->num_entities; i++ )\n\t{\n\t\tstate = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];\n\n\t\t// clients are should be done in CL_LinkPlayers\n\t\tif( state->number >= 1 && state->number <= cl.maxclients )\n\t\t\tcontinue;\n\n\t\t// if set to invisible, skip\n\t\tif( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))\n\t\t\tcontinue;\n\n\t\tent = CL_GetEntityByIndex( state->number );\n\n\t\tif( !ent )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: bad entity %i\\n\", __func__, state->number );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// animtime must keep an actual\n\t\tent->curstate.animtime = state->animtime;\n\t\tent->curstate.frame = state->frame;\n\t\tinterpolate = false;\n\n\t\tif( !ent->model ) continue;\n\n\t\tif( ent->curstate.rendermode == kRenderNormal )\n\t\t{\n\t\t\t// auto 'solid' faces\n\t\t\tif( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( ))\n\t\t\t{\n\t\t\t\tent->curstate.rendermode = kRenderTransAlpha;\n\t\t\t\tent->curstate.renderamt = 255;\n\t\t\t}\n\t\t}\n\n\t\tparametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );\n\n\t\tif( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND )\n\t\t{\n\t\t\tif( ent->curstate.animtime == ent->prevstate.animtime && !VectorCompare( ent->curstate.origin, ent->prevstate.origin ))\n\t\t\t\tent->lastmove = cl.time + 0.2;\n\n\t\t\tif( FBitSet( ent->curstate.eflags, EFLAG_SLERP ))\n\t\t\t{\n\t\t\t\tif( ent->curstate.animtime != 0.0f && ( ent->model->type == mod_alias || ent->model->type == mod_studio ))\n\t\t\t\t{\n#ifdef STUDIO_INTERPOLATION_FIX\n\t\t\t\t\tif( ent->lastmove >= cl.time )\n\t\t\t\t\t\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\t\t\t\t\tif( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t\t\t\t\t\tinterpolate = true;\n\t\t\t\t\telse ent->curstate.movetype = MOVETYPE_STEP;\n#else\n\t\t\t\t\tif( ent->lastmove >= cl.time )\n\t\t\t\t\t{\n\t\t\t\t\t\tfloat at = ent->curstate.animtime;\n\n\t\t\t\t\t\tCL_ResetLatchedVars( ent, true );\n\n\t\t\t\t\t\tif( cl_fixmodelinterpolationartifacts.value )\n\t\t\t\t\t\t\tent->latched.prevanimtime = ent->curstate.animtime = at;\n\n\t\t\t\t\t\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\t\t\t\t\t\tVectorCopy( ent->curstate.angles, ent->latched.prevangles );\n\n\t\t\t\t\t\tif( !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// disable step interpolation in client.dll\n\t\t\t\t\t\t\tent->curstate.movetype = MOVETYPE_NONE;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinterpolate = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// restore step interpolation in client.dll\n\t\t\t\t\t\t\tent->curstate.movetype = MOVETYPE_STEP;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n#endif\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( ent->model->type == mod_brush )\n\t\t{\n\t\t\tCL_InterpolateModel( ent );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( parametric )\n\t\t\t{\n\t\t\t\tCL_ParametricMove( ent );\n\n\t\t\t\tVectorCopy( ent->curstate.origin, ent->origin );\n\t\t\t\tVectorCopy( ent->curstate.angles, ent->angles );\n\t\t\t}\n\t\t\telse if( CL_EntityCustomLerp( ent ))\n\t\t\t{\n\t\t\t\tif ( !CL_InterpolateModel( ent ))\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// a1ba: in GoldSrc this is done for cstrike and czero\n\t\t\t// but let modders use this as an engine feature\n\t\t\telse if( FBitSet( host.features, ENGINE_STEP_POSHISTORY_LERP ) &&\n\t\t\t\tent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address ))\n\t\t\t{\n\t\t\t\tif( !CL_InterpolateModel( ent ))\n\t\t\t\t\tcontinue;\n\t\t\t}\n#if 0\n\t\t\t// ABSOLUTELY STUPID HACK TO ALLOW MONSTERS\n\t\t\t// INTERPOLATION IN GRAVGUNMOD COOP\n\t\t\t// MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT\n\t\t\telse if( cls.legacymode == PROTO_LEGACY && ent->model->type == mod_studio && ent->curstate.movetype == MOVETYPE_TOSS )\n\t\t\t{\n\t\t\t\tif( !CL_InterpolateModel( ent ))\n\t\t\t\t\tcontinue;\n\t\t\t}\n#endif\n\t\t\telse\n\t\t\t{\n\t\t\t\t// no interpolation right now\n\t\t\t\tVectorCopy( ent->curstate.origin, ent->origin );\n\t\t\t\tVectorCopy( ent->curstate.angles, ent->angles );\n\t\t\t}\n\n\t\t\tif( ent->model->type == mod_studio )\n\t\t\t{\n\t\t\t\tif( interpolate && FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t\t\t\t\tref.dllFuncs.R_StudioLerpMovement( ent, cl.time, ent->origin, ent->angles );\n\t\t\t}\n\t\t}\n\n\t\tif( !FBitSet( state->entityType, ENTITY_NORMAL ))\n\t\t{\n\t\t\tCL_LinkCustomEntity( ent, state );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( ent->model->type != mod_brush )\n\t\t{\n\t\t\t// NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug\n\t\t\tif( !ent->curstate.rendercolor.r && !ent->curstate.rendercolor.g && !ent->curstate.rendercolor.b )\n\t\t\t\tent->curstate.rendercolor.r = ent->curstate.rendercolor.g = ent->curstate.rendercolor.b = 255;\n\t\t}\n\n\t\t// XASH SPECIFIC\n\t\tif( ent->curstate.rendermode == kRenderNormal && ent->curstate.renderfx == kRenderFxNone )\n\t\t\tent->curstate.renderamt = 255.0f;\n\n\t\tif( ent->curstate.aiment != 0 && ent->curstate.movetype != MOVETYPE_COMPOUND )\n\t\t\tent->curstate.movetype = MOVETYPE_FOLLOW;\n\n\t\tif( FBitSet( ent->curstate.effects, EF_NOINTERP ))\n\t\t\tCL_ResetLatchedVars( ent, false );\n\n\t\tif( CL_EntityTeleported( ent ))\n\t\t{\n\t\t\tVectorCopy( ent->curstate.origin, ent->latched.prevorigin );\n\t\t\tVectorCopy( ent->curstate.angles, ent->latched.prevangles );\n\t\t\tCL_ResetPositions( ent );\n\t\t}\n\n\t\tVectorCopy( ent->origin, ent->attachment[0] );\n\t\tVectorCopy( ent->origin, ent->attachment[1] );\n\t\tVectorCopy( ent->origin, ent->attachment[2] );\n\t\tVectorCopy( ent->origin, ent->attachment[3] );\n\n\t\tCL_AddVisibleEntity( ent, ET_NORMAL );\n\t}\n}\n\n/*\n===============\nCL_MoveThirdpersonCamera\n\nthink thirdperson\n===============\n*/\nvoid CL_MoveThirdpersonCamera( void )\n{\n\tif( cls.state == ca_disconnected || cls.state == ca_cinematic )\n\t\treturn;\n\n\t// think thirdperson camera\n\tclgame.dllFuncs.CAM_Think ();\n}\n\n/*\n===============\nCL_EmitEntities\n\nadd visible entities to refresh list\nprocess frame interpolation etc\n===============\n*/\nvoid CL_EmitEntities( void )\n{\n\tif( cl.paused ) return; // don't waste time\n\n\t// not in server yet, no entities to redraw\n\tif( cls.state != ca_active || !cl.validsequence )\n\t\treturn;\n\n\t// make sure we have at least one valid update\n\tif( !cl.frames[cl.parsecountmod].valid )\n\t\treturn;\n\n\t// animate lightestyles\n\tref.dllFuncs.CL_RunLightStyles( CL_GetLightStyle( 0 ));\n\n\t// decay dynamic lights\n\tCL_DecayLights ();\n\n\t// compute last interpolation amount\n\tCL_UpdateFrameLerp ();\n\n\t// set client ideal pitch when mlook is disabled\n\tCL_SetIdealPitch ();\n\n\tref.dllFuncs.R_ClearScene ();\n\n\t// link all the visible clients first\n\tCL_LinkPlayers ( &cl.frames[cl.parsecountmod] );\n\n\t// link all the entities that actually have update\n\tCL_LinkPacketEntities ( &cl.frames[cl.parsecountmod] );\n\n\t// link custom user temp entities\n\tclgame.dllFuncs.pfnCreateEntities();\n\n\t// evaluate temp entities\n\tCL_TempEntUpdate ();\n\n\t// fire events (client and server)\n\tCL_FireEvents ();\n\n\t// handle spectator camera movement\n\tCL_MoveSpectatorCamera();\n\n\t// perfomance test\n\tCL_TestLights();\n}\n\n/*\n==========================================================================\n\nSOUND ENGINE IMPLEMENTATION\n\n==========================================================================\n*/\nqboolean CL_GetEntitySpatialization( channel_t *ch )\n{\n\tcl_entity_t\t*ent;\n\tqboolean\t\tvalid_origin;\n\n\tif( ch->entnum == 0 )\n\t{\n\t\tch->staticsound = true;\n\t\treturn true; // static sound\n\t}\n\n\tif(( ch->entnum - 1 ) == cl.playernum )\n\t{\n\t\tVectorCopy( refState.vieworg, ch->origin );\n\t\treturn true;\n\t}\n\n\tvalid_origin = VectorIsNull( ch->origin ) ? false : true;\n\tent = CL_GetEntityByIndex( ch->entnum );\n\n\t// entity is not present on the client but has valid origin\n\tif( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount )\n\t\treturn valid_origin;\n\n\t// setup origin\n\tif( ent->model->type == mod_brush )\n\t{\n\t\tVectorAverage( ent->model->mins, ent->model->maxs, ch->origin );\n\t\tVectorAdd( ent->origin, ch->origin, ch->origin );\n\t}\n\telse\n\t{\n\t\tVectorCopy( ent->origin, ch->origin );\n\t}\n\n\treturn true;\n}\n\nqboolean CL_GetMovieSpatialization( rawchan_t *ch )\n{\n\tcl_entity_t\t*ent;\n\tqboolean\t\tvalid_origin;\n\n\tvalid_origin = VectorIsNull( ch->origin ) ? false : true;\n\tent = CL_GetEntityByIndex( ch->entnum );\n\n\t// entity is not present on the client but has valid origin\n\tif( !ent || !ent->index || ent->curstate.messagenum == 0 )\n\t\treturn valid_origin;\n\n\t// setup origin\n\tif( ent->model->type == mod_brush )\n\t{\n\t\tVectorAverage( ent->model->mins, ent->model->maxs, ch->origin );\n\t\tVectorAdd( ent->origin, ch->origin, ch->origin );\n\t}\n\telse\n\t{\n\t\tVectorCopy( ent->origin, ch->origin );\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/cl_game.c",
    "content": "/*\ncl_game.c - client dll interaction\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if XASH_SDL == 2\n#include <SDL.h> // SDL_GetWindowPosition\n#elif XASH_SDL == 3\n#include <SDL3/SDL.h> // SDL_GetWindowPosition\n#endif // XASH_SDL\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"const.h\"\n#include \"triangleapi.h\"\n#include \"r_efx.h\"\n#include \"demo_api.h\"\n#include \"ivoicetweak.h\"\n#include \"pm_local.h\"\n#include \"cl_tent.h\"\n#include \"input.h\"\n#include \"shake.h\"\n#include \"sprite.h\"\n#include \"library.h\"\n#include \"vgui_draw.h\"\n#include \"sound.h\"\t\t// SND_STOP_LOOPING\n#include \"platform/platform.h\"\n\n#define MAX_LINELENGTH\t80\n#define MAX_TEXTCHANNELS\t8\t\t// must be power of two (GoldSrc uses 4 channels)\n#define TEXT_MSGNAME\t\"TextMessage%i\"\n\nstatic char cl_textbuffer[MAX_TEXTCHANNELS][2048];\nstatic client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS];\n\nstatic const dllfunc_t cdll_exports[] =\n{\n{ \"Initialize\", (void **)&clgame.dllFuncs.pfnInitialize },\n{ \"HUD_VidInit\", (void **)&clgame.dllFuncs.pfnVidInit },\n{ \"HUD_Init\", (void **)&clgame.dllFuncs.pfnInit },\n{ \"HUD_Shutdown\", (void **)&clgame.dllFuncs.pfnShutdown },\n{ \"HUD_Redraw\", (void **)&clgame.dllFuncs.pfnRedraw },\n{ \"HUD_UpdateClientData\", (void **)&clgame.dllFuncs.pfnUpdateClientData },\n{ \"HUD_Reset\", (void **)&clgame.dllFuncs.pfnReset },\n{ \"HUD_PlayerMove\", (void **)&clgame.dllFuncs.pfnPlayerMove },\n{ \"HUD_PlayerMoveInit\", (void **)&clgame.dllFuncs.pfnPlayerMoveInit },\n{ \"HUD_PlayerMoveTexture\", (void **)&clgame.dllFuncs.pfnPlayerMoveTexture },\n{ \"HUD_ConnectionlessPacket\", (void **)&clgame.dllFuncs.pfnConnectionlessPacket },\n{ \"HUD_GetHullBounds\", (void **)&clgame.dllFuncs.pfnGetHullBounds },\n{ \"HUD_Frame\", (void **)&clgame.dllFuncs.pfnFrame },\n{ \"HUD_PostRunCmd\", (void **)&clgame.dllFuncs.pfnPostRunCmd },\n{ \"HUD_Key_Event\", (void **)&clgame.dllFuncs.pfnKey_Event },\n{ \"HUD_AddEntity\", (void **)&clgame.dllFuncs.pfnAddEntity },\n{ \"HUD_CreateEntities\", (void **)&clgame.dllFuncs.pfnCreateEntities },\n{ \"HUD_StudioEvent\", (void **)&clgame.dllFuncs.pfnStudioEvent },\n{ \"HUD_TxferLocalOverrides\", (void **)&clgame.dllFuncs.pfnTxferLocalOverrides },\n{ \"HUD_ProcessPlayerState\", (void **)&clgame.dllFuncs.pfnProcessPlayerState },\n{ \"HUD_TxferPredictionData\", (void **)&clgame.dllFuncs.pfnTxferPredictionData },\n{ \"HUD_TempEntUpdate\", (void **)&clgame.dllFuncs.pfnTempEntUpdate },\n{ \"HUD_DrawNormalTriangles\", (void **)&clgame.dllFuncs.pfnDrawNormalTriangles },\n{ \"HUD_DrawTransparentTriangles\", (void **)&clgame.dllFuncs.pfnDrawTransparentTriangles },\n{ \"HUD_GetUserEntity\", (void **)&clgame.dllFuncs.pfnGetUserEntity },\n{ \"Demo_ReadBuffer\", (void **)&clgame.dllFuncs.pfnDemo_ReadBuffer },\n{ \"CAM_Think\", (void **)&clgame.dllFuncs.CAM_Think },\n{ \"CL_IsThirdPerson\", (void **)&clgame.dllFuncs.CL_IsThirdPerson },\n{ \"CL_CameraOffset\", (void **)&clgame.dllFuncs.CL_CameraOffset },\t// unused callback. Now camera code is completely moved to the user area\n{ \"CL_CreateMove\", (void **)&clgame.dllFuncs.CL_CreateMove },\n{ \"IN_ActivateMouse\", (void **)&clgame.dllFuncs.IN_ActivateMouse },\n{ \"IN_DeactivateMouse\", (void **)&clgame.dllFuncs.IN_DeactivateMouse },\n{ \"IN_MouseEvent\", (void **)&clgame.dllFuncs.IN_MouseEvent },\n{ \"IN_Accumulate\", (void **)&clgame.dllFuncs.IN_Accumulate },\n{ \"IN_ClearStates\", (void **)&clgame.dllFuncs.IN_ClearStates },\n{ \"V_CalcRefdef\", (void **)&clgame.dllFuncs.pfnCalcRefdef },\n{ \"KB_Find\", (void **)&clgame.dllFuncs.KB_Find },\n};\n\n// optional exports\nstatic const dllfunc_t cdll_new_exports[] = \t// allowed only in SDK 2.3 and higher\n{\n{ \"HUD_GetStudioModelInterface\", (void **)&clgame.dllFuncs.pfnGetStudioModelInterface },\n{ \"HUD_DirectorMessage\", (void **)&clgame.dllFuncs.pfnDirectorMessage },\n{ \"HUD_VoiceStatus\", (void **)&clgame.dllFuncs.pfnVoiceStatus },\n{ \"HUD_ChatInputPosition\", (void **)&clgame.dllFuncs.pfnChatInputPosition },\n{ \"HUD_GetRenderInterface\", (void **)&clgame.dllFuncs.pfnGetRenderInterface },\t// Xash3D ext\n{ \"HUD_ClipMoveToEntity\", (void **)&clgame.dllFuncs.pfnClipMoveToEntity },\t// Xash3D ext\n{ \"IN_ClientTouchEvent\", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext\n{ \"IN_ClientMoveEvent\", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext\n{ \"IN_ClientLookEvent\", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext\n};\n\nstatic void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc );\n\n/*\n====================\nCL_CreatePlaylist\n\nCreate a default valve playlist\n====================\n*/\nstatic void CL_CreatePlaylist( const char *filename )\n{\n\tfile_t\t*f;\n\n\tf = FS_Open( filename, \"w\", false );\n\tif( !f ) return;\n\n\t// make standard cdaudio playlist\n\tFS_Print( f, \"blank\\n\" );\t\t// #1\n\tFS_Print( f, \"Half-Life01.mp3\\n\" );\t// #2\n\tFS_Print( f, \"Prospero01.mp3\\n\" );\t// #3\n\tFS_Print( f, \"Half-Life12.mp3\\n\" );\t// #4\n\tFS_Print( f, \"Half-Life07.mp3\\n\" );\t// #5\n\tFS_Print( f, \"Half-Life10.mp3\\n\" );\t// #6\n\tFS_Print( f, \"Suspense01.mp3\\n\" );\t// #7\n\tFS_Print( f, \"Suspense03.mp3\\n\" );\t// #8\n\tFS_Print( f, \"Half-Life09.mp3\\n\" );\t// #9\n\tFS_Print( f, \"Half-Life02.mp3\\n\" );\t// #10\n\tFS_Print( f, \"Half-Life13.mp3\\n\" );\t// #11\n\tFS_Print( f, \"Half-Life04.mp3\\n\" );\t// #12\n\tFS_Print( f, \"Half-Life15.mp3\\n\" );\t// #13\n\tFS_Print( f, \"Half-Life14.mp3\\n\" );\t// #14\n\tFS_Print( f, \"Half-Life16.mp3\\n\" );\t// #15\n\tFS_Print( f, \"Suspense02.mp3\\n\" );\t// #16\n\tFS_Print( f, \"Half-Life03.mp3\\n\" );\t// #17\n\tFS_Print( f, \"Half-Life08.mp3\\n\" );\t// #18\n\tFS_Print( f, \"Prospero02.mp3\\n\" );\t// #19\n\tFS_Print( f, \"Half-Life05.mp3\\n\" );\t// #20\n\tFS_Print( f, \"Prospero04.mp3\\n\" );\t// #21\n\tFS_Print( f, \"Half-Life11.mp3\\n\" );\t// #22\n\tFS_Print( f, \"Half-Life06.mp3\\n\" );\t// #23\n\tFS_Print( f, \"Prospero03.mp3\\n\" );\t// #24\n\tFS_Print( f, \"Half-Life17.mp3\\n\" );\t// #25\n\tFS_Print( f, \"Prospero05.mp3\\n\" );\t// #26\n\tFS_Print( f, \"Suspense05.mp3\\n\" );\t// #27\n\tFS_Print( f, \"Suspense07.mp3\\n\" );\t// #28\n\tFS_Close( f );\n}\n\n/*\n====================\nCL_InitCDAudio\n\nInitialize CD playlist\n====================\n*/\nstatic void CL_InitCDAudio( const char *filename )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tstring\ttoken;\n\tint\tc = 0;\n\n\tif( !FS_FileExists( filename, false ))\n\t{\n\t\t// create a default playlist\n\t\tCL_CreatePlaylist( filename );\n\t}\n\n\tafile = FS_LoadFile( filename, NULL, false );\n\tif( !afile ) return;\n\n\tpfile = (char *)afile;\n\n\t// format: trackname\\n [num]\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tif( !Q_stricmp( token, \"blank\" ))\n\t\t\tclgame.cdtracks[c][0] = '\\0';\n\t\telse\n\t\t{\n\t\t\tQ_snprintf( clgame.cdtracks[c], sizeof( clgame.cdtracks[c] ),\n\t\t\t\t\"media/%s\", token );\n\t\t}\n\n\t\tif( ++c > MAX_CDTRACKS - 1 )\n\t\t{\n\t\t\tCon_Reportf( S_WARN \"%s: too many tracks %i in %s\\n\", __func__, MAX_CDTRACKS, filename );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tMem_Free( afile );\n}\n\n/*\n=============\nCL_AdjustXPos\n\nadjust text by x pos\n=============\n*/\nstatic int CL_AdjustXPos( float x, int width, int totalWidth )\n{\n\tint\txPos;\n\n\tif( x == -1 )\n\t{\n\t\txPos = ( clgame.scrInfo.iWidth - width ) * 0.5f;\n\t}\n\telse\n\t{\n\t\tif ( x < 0 )\n\t\t\txPos = (1.0f + x) * clgame.scrInfo.iWidth - totalWidth;\t// Alight right\n\t\telse // align left\n\t\t\txPos = x * clgame.scrInfo.iWidth;\n\t}\n\n\tif( xPos + width > clgame.scrInfo.iWidth )\n\t\txPos = clgame.scrInfo.iWidth - width;\n\telse if( xPos < 0 )\n\t\txPos = 0;\n\n\treturn xPos;\n}\n\n/*\n=============\nCL_AdjustYPos\n\nadjust text by y pos\n=============\n*/\nstatic int CL_AdjustYPos( float y, int height )\n{\n\tint\tyPos;\n\n\tif( y == -1 ) // centered?\n\t{\n\t\tyPos = ( clgame.scrInfo.iHeight - height ) * 0.5f;\n\t}\n\telse\n\t{\n\t\t// Alight bottom?\n\t\tif( y < 0 )\n\t\t\tyPos = (1.0f + y) * clgame.scrInfo.iHeight - height; // Alight bottom\n\t\telse // align top\n\t\t\tyPos = y * clgame.scrInfo.iHeight;\n\t}\n\n\tif( yPos + height > clgame.scrInfo.iHeight )\n\t\tyPos = clgame.scrInfo.iHeight - height;\n\telse if( yPos < 0 )\n\t\tyPos = 0;\n\n\treturn yPos;\n}\n\n/*\n=============\nCL_CenterPrint\n\nprint centerscreen message\n=============\n*/\nvoid CL_CenterPrint( const char *text, float y )\n{\n\tcl_font_t *font = Con_GetCurFont();\n\n\tif( !COM_CheckString( text ) || !font || !font->valid )\n\t\treturn;\n\n\tclgame.centerPrint.totalWidth = 0;\n\tclgame.centerPrint.time = cl.mtime[0]; // allow pause for centerprint\n\tQ_strncpy( clgame.centerPrint.message, text, sizeof( clgame.centerPrint.message ));\n\n\tCL_DrawStringLen( font,\n\t\tclgame.centerPrint.message,\n\t\t&clgame.centerPrint.totalWidth,\n\t\t&clgame.centerPrint.totalHeight,\n\t\tFONT_DRAW_HUD | FONT_DRAW_UTF8 );\n\n\tif( font->charHeight )\n\t\tclgame.centerPrint.lines = clgame.centerPrint.totalHeight / font->charHeight;\n\telse clgame.centerPrint.lines = 1;\n\n\tclgame.centerPrint.y = CL_AdjustYPos( y, clgame.centerPrint.totalHeight );\n}\n\n/*\n====================\nSPR_AdjustSize\n\ndraw hudsprite routine\n====================\n*/\nvoid SPR_AdjustSize( float *x, float *y, float *w, float *h )\n{\n\tfloat\txscale, yscale;\n\n\tif( refState.width == clgame.scrInfo.iWidth && refState.height == clgame.scrInfo.iHeight )\n\t\treturn;\n\n\t// scale for screen sizes\n\txscale = refState.width / (float)clgame.scrInfo.iWidth;\n\tyscale = refState.height / (float)clgame.scrInfo.iHeight;\n\n\t*x *= xscale;\n\t*y *= yscale;\n\t*w *= xscale;\n\t*h *= yscale;\n}\n\nstatic void SPR_AdjustTexCoords( int texnum, float width, float height, float *s1, float *t1, float *s2, float *t2 )\n{\n\tconst qboolean filtering = REF_GET_PARM( PARM_TEX_FILTERING, texnum );\n\tconst int xremainder = refState.width % clgame.scrInfo.iWidth;\n\tconst int yremainder = refState.height % clgame.scrInfo.iHeight;\n\n\tif(( filtering || xremainder ) && refState.width != clgame.scrInfo.iWidth )\n\t{\n\t\t// align to texel if scaling\n\t\t*s1 += 0.5f;\n\t\t*s2 -= 0.5f;\n\t}\n\n\tif(( filtering || yremainder ) && refState.height != clgame.scrInfo.iHeight )\n\t{\n\t\t// align to texel if scaling\n\t\t*t1 += 0.5f;\n\t\t*t2 -= 0.5f;\n\t}\n\n\t*s1 /= width;\n\t*t1 /= height;\n\t*s2 /= width;\n\t*t2 /= height;\n}\n\n/*\n====================\nSPR_DrawGeneric\n\ndraw hudsprite routine\n====================\n*/\nstatic void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc )\n{\n\tfloat\ts1, s2, t1, t2;\n\tint\ttexnum;\n\n\tif( width == -1 && height == -1 )\n\t{\n\t\tint\tw, h;\n\n\t\t// assume we get sizes from image\n\t\tref.dllFuncs.R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );\n\n\t\twidth = w;\n\t\theight = h;\n\t}\n\n\ttexnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame );\n\n\tif( prc )\n\t{\n\t\twrect_t\trc = *prc;\n\n\t\t// Sigh! some stupid modmakers set wrong rectangles in hud.txt\n\t\tif( rc.left <= 0 || rc.left >= width ) rc.left = 0;\n\t\tif( rc.top <= 0 || rc.top >= height ) rc.top = 0;\n\t\tif( rc.right <= 0 || rc.right > width ) rc.right = width;\n\t\tif( rc.bottom <= 0 || rc.bottom > height ) rc.bottom = height;\n\n\t\ts1 = rc.left;\n\t\tt1 = rc.top;\n\t\ts2 = rc.right;\n\t\tt2 = rc.bottom;\n\n\t\t// calc user-defined rectangle\n\t\tSPR_AdjustTexCoords( texnum, width, height, &s1, &t1, &s2, &t2 );\n\t\twidth = rc.right - rc.left;\n\t\theight = rc.bottom - rc.top;\n\t}\n\telse\n\t{\n\t\ts1 = t1 = 0.0f;\n\t\ts2 = t2 = 1.0f;\n\t}\n\n\t// pass scissor test if supposed\n\tif( !CL_Scissor( &clgame.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))\n\t\treturn;\n\n\t// scale for screen sizes\n\tSPR_AdjustSize( &x, &y, &width, &height );\n\tref.dllFuncs.Color4ub( clgame.ds.spriteColor[0], clgame.ds.spriteColor[1], clgame.ds.spriteColor[2], clgame.ds.spriteColor[3] );\n\tref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, texnum );\n}\n\n/*\n=============\nCL_DrawCenterPrint\n\ncalled each frame\n=============\n*/\nvoid CL_DrawCenterPrint( void )\n{\n\tcl_font_t *font = Con_GetCurFont();\n\tchar\t*pText;\n\tint\ti, j, x, y;\n\tint\twidth, lineLength;\n\tbyte\t*colorDefault, line[MAX_LINELENGTH];\n\tint\tcharWidth, charHeight;\n\n\tif( !clgame.centerPrint.time )\n\t\treturn;\n\n\tif(( cl.time - clgame.centerPrint.time ) >= scr_centertime.value )\n\t{\n\t\t// time expired\n\t\tclgame.centerPrint.time = 0.0f;\n\t\treturn;\n\t}\n\n\ty = clgame.centerPrint.y; // start y\n\tcolorDefault = g_color_table[7];\n\tpText = clgame.centerPrint.message;\n\n\tCL_DrawCharacterLen( font, 0, NULL, &charHeight );\n\tCL_SetFontRendermode( font );\n\tfor( i = 0; i < clgame.centerPrint.lines; i++ )\n\t{\n\t\tlineLength = 0;\n\t\twidth = 0;\n\n\t\twhile( *pText && *pText != '\\n' && lineLength < MAX_LINELENGTH )\n\t\t{\n\t\t\tint number = Con_UtfProcessChar(( byte ) * pText );\n\t\t\tpText++;\n\t\t\tif( number == 0 )\n\t\t\t\tcontinue;\n\n\t\t\tline[lineLength] = number;\n\t\t\tCL_DrawCharacterLen( font, number, &charWidth, NULL );\n\t\t\twidth += charWidth;\n\t\t\tlineLength++;\n\t\t}\n\n\t\tif( lineLength == MAX_LINELENGTH )\n\t\t\tlineLength--;\n\n\t\tpText++; // Skip LineFeed\n\t\tline[lineLength] = 0;\n\n\t\tx = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth );\n\n\t\tfor( j = 0; j < lineLength; j++ )\n\t\t{\n\t\t\tif( x >= 0 && y >= 0 && x <= refState.width )\n\t\t\t\tx += CL_DrawCharacter( x, y, line[j], colorDefault, font, FONT_DRAW_HUD | FONT_DRAW_NORENDERMODE );\n\t\t}\n\t\ty += charHeight;\n\t}\n}\n\nstatic int V_FadeAlpha( screenfade_t *sf )\n{\n\tint alpha;\n\n\tif( cl.time > sf->fadeReset && cl.time > sf->fadeEnd )\n\t{\n\t\tif( !FBitSet( sf->fadeFlags, FFADE_STAYOUT ))\n\t\t\treturn 0;\n\t}\n\n\tif( FBitSet( sf->fadeFlags, FFADE_STAYOUT ))\n\t{\n\t\talpha = sf->fadealpha;\n\t\tif( FBitSet( sf->fadeFlags, FFADE_OUT ) && sf->fadeTotalEnd > cl.time )\n\t\t{\n\t\t\talpha += sf->fadeSpeed * ( sf->fadeTotalEnd - cl.time );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsf->fadeEnd = cl.time + 0.1;\n\t\t}\n\t}\n\telse\n\t{\n\t\talpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time );\n\t\tif( FBitSet( sf->fadeFlags, FFADE_OUT ))\n\t\t{\n\t\t\talpha += sf->fadealpha;\n\t\t}\n\t}\n\talpha = bound( 0, alpha, sf->fadealpha );\n\n\treturn alpha;\n}\n\n/*\n=============\nCL_DrawScreenFade\n\nfill screen with specfied color\ncan be modulated\n=============\n*/\nstatic void CL_DrawScreenFade( void )\n{\n\tscreenfade_t\t*sf = &clgame.fade;\n\tint\t\talpha;\n\n\talpha = V_FadeAlpha( sf );\n\n\tif( !alpha )\n\t\treturn;\n\n\tif( !FBitSet( sf->fadeFlags, FFADE_MODULATE ))\n\t{\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\t\tref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha );\n\t}\n\telse if( Host_IsQuakeCompatible( ))\n\t{\n\t\t// Quake Wrapper and Quake Remake use FFADE_MODULATE for item pickups\n\t\t// so hack the check here\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );\n\t\tref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha );\n\t}\n\telse\n\t{\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderScreenFadeModulate );\n\n\t\tref.dllFuncs.Color4ub(\n\t\t\t(uint16_t)( sf->fader * alpha + ( 255 - alpha ) * 255 ) >> 8,\n\t\t\t(uint16_t)( sf->fadeg * alpha + ( 255 - alpha ) * 255 ) >> 8,\n\t\t\t(uint16_t)( sf->fadeb * alpha + ( 255 - alpha ) * 255 ) >> 8,\n\t\t\t255 );\n\t}\n\n\tref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1,\n\t\tR_GetBuiltinTexture( REF_WHITE_TEXTURE ));\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n}\n\n/*\n====================\nCL_InitTitles\n\nparse all messages that declared in titles.txt\nand hold them into permament memory pool\n====================\n*/\nstatic void CL_InitTitles( const char *filename )\n{\n\tfs_offset_t\tfileSize;\n\tbyte\t*pMemFile;\n\tint\ti;\n\n\t// initialize text messages (game_text)\n\tfor( i = 0; i < MAX_TEXTCHANNELS; i++ )\n\t{\n\t\tchar name[MAX_VA_STRING];\n\n\t\tQ_snprintf( name, sizeof( name ), TEXT_MSGNAME, i );\n\n\t\tcl_textmessage[i].pName = copystringpool( clgame.mempool, name );\n\t\tcl_textmessage[i].pMessage = cl_textbuffer[i];\n\t}\n\n\t// clear out any old data that's sitting around.\n\tif( clgame.titles ) Mem_Free( clgame.titles );\n\n\tclgame.titles = NULL;\n\tclgame.numTitles = 0;\n\n\tpMemFile = FS_LoadFile( filename, &fileSize, false );\n\tif( !pMemFile ) return;\n\n\tCL_TextMessageParse( pMemFile, fileSize );\n\tMem_Free( pMemFile );\n}\n\n/*\n====================\nCL_HudMessage\n\nTemplate to show hud messages\n====================\n*/\nvoid CL_HudMessage( const char *pMessage )\n{\n\tif( !COM_CheckString( pMessage )) return;\n\tCL_DispatchUserMessage( \"HudText\", Q_strlen( pMessage ), (void *)pMessage );\n}\n\n/*\n====================\nCL_ParseTextMessage\n\nParse TE_TEXTMESSAGE\n====================\n*/\nvoid CL_ParseTextMessage( sizebuf_t *msg )\n{\n\tstatic int\t\tmsgindex = 0;\n\tclient_textmessage_t\t*text;\n\tint\t\t\tchannel;\n\n\t// read channel ( 0 - auto)\n\tchannel = MSG_ReadByte( msg );\n\n\tif( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 ))\n\t{\n\t\tchannel = msgindex;\n\t\tmsgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);\n\t}\n\n\t// grab message channel\n\ttext = &cl_textmessage[channel];\n\n\ttext->x = (float)(MSG_ReadShort( msg ) / 8192.0f);\n\ttext->y = (float)(MSG_ReadShort( msg ) / 8192.0f);\n\ttext->effect = MSG_ReadByte( msg );\n\ttext->r1 = MSG_ReadByte( msg );\n\ttext->g1 = MSG_ReadByte( msg );\n\ttext->b1 = MSG_ReadByte( msg );\n\ttext->a1 = MSG_ReadByte( msg );\n\ttext->r2 = MSG_ReadByte( msg );\n\ttext->g2 = MSG_ReadByte( msg );\n\ttext->b2 = MSG_ReadByte( msg );\n\ttext->a2 = MSG_ReadByte( msg );\n\ttext->fadein = (float)(MSG_ReadWord( msg ) / 256.0f );\n\ttext->fadeout = (float)(MSG_ReadWord( msg ) / 256.0f );\n\ttext->holdtime = (float)(MSG_ReadWord( msg ) / 256.0f );\n\n\tif( text->effect == 2 )\n\t\ttext->fxtime = (float)(MSG_ReadWord( msg ) / 256.0f );\n\telse text->fxtime = 0.0f;\n\n\t// to prevent grab too long messages\n\tQ_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 );\n\n\tCL_HudMessage( text->pName );\n}\n\n/*\n================\nCL_ParseFinaleCutscene\n\nshow display finale or cutscene message\n================\n*/\nvoid CL_ParseFinaleCutscene( sizebuf_t *msg, int level )\n{\n\tstatic int\t\tmsgindex = 0;\n\tclient_textmessage_t\t*text;\n\tint\t\t\tchannel;\n\n\tcl.intermission = level;\n\n\tchannel = msgindex;\n\tmsgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);\n\n\t// grab message channel\n\ttext = &cl_textmessage[channel];\n\n\t// NOTE: svc_finale and svc_cutscene has a\n\t// predefined settings like Quake-style\n\ttext->x = -1.0f;\n\ttext->y = 0.15f;\n\ttext->effect = 2;\t// scan out effect\n\ttext->r1 = 245;\n\ttext->g1 = 245;\n\ttext->b1 = 245;\n\ttext->a1 = 0;\t// unused\n\ttext->r2 = 0;\n\ttext->g2 = 0;\n\ttext->b2 = 0;\n\ttext->a2 = 0;\n\ttext->fadein = 0.15f;\n\ttext->fadeout = 0.0f;\n\ttext->holdtime = 99999.0f;\n\ttext->fxtime = 0.0f;\n\n\t// to prevent grab too long messages\n\tQ_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 );\n\n\tif( *text->pMessage == '\\0' )\n\t\treturn; // no real text\n\n\tCL_HudMessage( text->pName );\n}\n\n/*\n====================\nCL_GetMaxlients\n\nRender callback for studio models\n====================\n*/\nint GAME_EXPORT CL_GetMaxClients( void )\n{\n\treturn cl.maxclients;\n}\n\n/*\n====================\nCL_SoundFromIndex\n\nreturn soundname from index\n====================\n*/\nstatic const char *CL_SoundFromIndex( int index )\n{\n\tsfx_t\t*sfx = NULL;\n\tint\thSound;\n\n\t// make sure what we in-bounds\n\tindex = bound( 0, index, MAX_SOUNDS );\n\thSound = cl.sound_index[index];\n\n\tif( !hSound )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: invalid sound index %i\\n\", __func__, index );\n\t\treturn NULL;\n\t}\n\n\tsfx = S_GetSfxByHandle( hSound );\n\tif( !sfx )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: bad sfx for index %i\\n\", __func__, index );\n\t\treturn NULL;\n\t}\n\n\treturn sfx->name;\n}\n\n/*\n================\nCL_EnableScissor\n\nenable scissor test\n================\n*/\nvoid CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height )\n{\n\tscissor->x = x;\n\tscissor->y = y;\n\tscissor->width = width;\n\tscissor->height = height;\n\tscissor->test = true;\n}\n\n/*\n================\nCL_DisableScissor\n\ndisable scissor test\n================\n*/\nvoid CL_DisableScissor( scissor_state_t *scissor )\n{\n\tscissor->test = false;\n}\n\n/*\n================\nCL_Scissor\n\nperform common scissor test\n================\n*/\nqboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 )\n{\n\tfloat dudx, dvdy;\n\n\tif( !scissor->test )\n\t\treturn true;\n\n\t// clip sub rect to sprite\n\tif( *width == 0 || *height == 0 )\n\t\treturn false;\n\n\tif( *x + *width <= scissor->x )\n\t\treturn false;\n\tif( *x >= scissor->x + scissor->width )\n\t\treturn false;\n\tif( *y + *height <= scissor->y )\n\t\treturn false;\n\tif( *y >= scissor->y + scissor->height )\n\t\treturn false;\n\n\tdudx = (*u1 - *u0) / *width;\n\tdvdy = (*v1 - *v0) / *height;\n\n\tif( *x < scissor->x )\n\t{\n\t\t*u0 += (scissor->x - *x) * dudx;\n\t\t*width -= scissor->x - *x;\n\t\t*x = scissor->x;\n\t}\n\n\tif( *x + *width > scissor->x + scissor->width )\n\t{\n\t\t*u1 -= (*x + *width - (scissor->x + scissor->width)) * dudx;\n\t\t*width = scissor->x + scissor->width - *x;\n\t}\n\n\tif( *y < scissor->y )\n\t{\n\t\t*v0 += (scissor->y - *y) * dvdy;\n\t\t*height -= scissor->y - *y;\n\t\t*y = scissor->y;\n\t}\n\n\tif( *y + *height > scissor->y + scissor->height )\n\t{\n\t\t*v1 -= (*y + *height - (scissor->y + scissor->height)) * dvdy;\n\t\t*height = scissor->y + scissor->height - *y;\n\t}\n\treturn true;\n}\n\n/*\n=========\nSPR_EnableScissor\n\n=========\n*/\nstatic void GAME_EXPORT SPR_EnableScissor( int x, int y, int width, int height )\n{\n\t// check bounds\n\tx = bound( 0, x, clgame.scrInfo.iWidth );\n\ty = bound( 0, y, clgame.scrInfo.iHeight );\n\twidth = bound( 0, width, clgame.scrInfo.iWidth - x );\n\theight = bound( 0, height, clgame.scrInfo.iHeight - y );\n\n\tCL_EnableScissor( &clgame.ds.scissor, x, y, width, height );\n}\n\n/*\n=========\nSPR_DisableScissor\n\n=========\n*/\nstatic void GAME_EXPORT SPR_DisableScissor( void )\n{\n\tCL_DisableScissor( &clgame.ds.scissor );\n}\n\n/*\n====================\nCL_DrawCrosshair\n\nRender crosshair\n====================\n*/\nstatic void CL_DrawCrosshair( void )\n{\n\tint\tx, y, width, height;\n\tfloat xscale, yscale;\n\n\tif( !clgame.ds.pCrosshair || !cl_crosshair.value )\n\t\treturn;\n\n\t// any camera on or client is died\n\tif( cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 ))\n\t\treturn;\n\n\t// get crosshair dimension\n\twidth = clgame.ds.rcCrosshair.right - clgame.ds.rcCrosshair.left;\n\theight = clgame.ds.rcCrosshair.bottom - clgame.ds.rcCrosshair.top;\n\n\tx = clgame.viewport[0] + ( clgame.viewport[2] >> 1 );\n\ty = clgame.viewport[1] + ( clgame.viewport[3] >> 1 );\n\n\t// g-cont - cl.crosshairangle is the autoaim angle.\n\t// if we're not using autoaim, just draw in the middle of the screen\n\tif( !VectorIsNull( cl.crosshairangle ))\n\t{\n\t\tvec3_t\tangles;\n\t\tvec3_t\tforward;\n\t\tvec3_t\tpoint, screen;\n\n\t\tVectorAdd( refState.viewangles, cl.crosshairangle, angles );\n\t\tAngleVectors( angles, forward, NULL, NULL );\n\t\tVectorAdd( refState.vieworg, forward, point );\n\t\tref.dllFuncs.WorldToScreen( point, screen );\n\n\t\tx += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f;\n\t\ty += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f;\n\t}\n\n\t// back to logical sizes\n\txscale = (float)clgame.scrInfo.iWidth / refState.width;\n\tyscale = (float)clgame.scrInfo.iHeight / refState.height;\n\n\tx *= xscale;\n\ty *= yscale;\n\n\t// move at center the screen\n\tx -= 0.5f * width;\n\ty -= 0.5f * height;\n\n\tclgame.ds.pSprite = clgame.ds.pCrosshair;\n\tVector4Copy( clgame.ds.rgbaCrosshair, clgame.ds.spriteColor );\n\n\tpfnSPR_DrawHoles( 0, x, y, &clgame.ds.rcCrosshair );\n}\n\n/*\n=============\nCL_DrawLoading\n\ndraw loading progress bar\n=============\n*/\nstatic void CL_DrawLoadingOrPaused( int tex )\n{\n\tfloat\tx, y, width, height;\n\tint iWidth, iHeight;\n\n\tR_GetTextureParms( &iWidth, &iHeight, tex );\n\tx = ( clgame.scrInfo.iWidth - iWidth ) / 2.0f;\n\ty = ( clgame.scrInfo.iHeight - iHeight ) / 2.0f;\n\twidth = iWidth;\n\theight = iHeight;\n\n\tSPR_AdjustSize( &x, &y, &width, &height );\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\tref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, tex );\n}\n\nvoid CL_DrawHUD( int state )\n{\n\tif( state == CL_ACTIVE && !cl.video_prepped )\n\t\tstate = CL_LOADING;\n\n\tif( state == CL_ACTIVE && cl.paused )\n\t\tstate = CL_PAUSED;\n\n\tswitch( state )\n\t{\n\tcase CL_ACTIVE:\n\t\tif( !cl.intermission )\n\t\t\tCL_DrawScreenFade ();\n\t\tCL_DrawCrosshair ();\n\t\tCL_DrawCenterPrint ();\n\t\tclgame.dllFuncs.pfnRedraw( cl.time, cl.intermission );\n\t\tif( cl.intermission ) CL_DrawScreenFade ();\n\t\tbreak;\n\tcase CL_PAUSED:\n\t\tCL_DrawScreenFade ();\n\t\tCL_DrawCrosshair ();\n\t\tCL_DrawCenterPrint ();\n\t\tclgame.dllFuncs.pfnRedraw( cl.time, cl.intermission );\n\t\tif( showpause.value )\n\t\t{\n\t\t\tif( !cls.pauseIcon )\n\t\t\t\tcls.pauseIcon = SCR_LoadPauseIcon();\n\t\t\tCL_DrawLoadingOrPaused( Q_max( 0, cls.pauseIcon ));\n\t\t}\n\t\tbreak;\n\tcase CL_LOADING:\n\t\tCL_DrawLoadingOrPaused( cls.loadingBar );\n\t\tbreak;\n\tcase CL_CHANGELEVEL:\n\t\tif( cls.draw_changelevel )\n\t\t{\n\t\t\tCL_DrawLoadingOrPaused( cls.loadingBar );\n\t\t\tcls.draw_changelevel = false;\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic void CL_ClearUserMessage( char *pszName, int svc_num )\n{\n\tint i;\n\n\tfor( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )\n\t\tif( ( clgame.msg[i].number == svc_num ) && Q_stricmp( clgame.msg[i].name, pszName ) )\n\t\t\tclgame.msg[i].number = 0;\n}\n\nvoid CL_LinkUserMessage( char *pszName, const int svc_num, int iSize )\n{\n\tint\ti;\n\n\tif( !pszName || !*pszName )\n\t\tHost_Error( \"%s: bad message name\\n\", __func__ );\n\n\tif( svc_num <= svc_lastmsg )\n\t\tHost_Error( \"%s: tried to hook a system message \\\"%s\\\"\\n\", __func__, svc_strings[svc_num] );\n\n\t// see if already hooked\n\tfor( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )\n\t{\n\t\t// NOTE: no check for DispatchFunc, check only name\n\t\tif( !Q_stricmp( clgame.msg[i].name, pszName ))\n\t\t{\n\t\t\tclgame.msg[i].number = svc_num;\n\t\t\tclgame.msg[i].size = iSize;\n\t\t\tCL_ClearUserMessage( pszName, svc_num );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif( i == MAX_USER_MESSAGES )\n\t{\n\t\tHost_Error( \"%s: MAX_USER_MESSAGES hit!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\t// register new message without DispatchFunc, so we should parse it properly\n\tQ_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name ));\n\tclgame.msg[i].number = svc_num;\n\tclgame.msg[i].size = iSize;\n\tCL_ClearUserMessage( pszName, svc_num );\n}\n\nvoid CL_ClearWorld( void )\n{\n\tif( clgame.entities ) // check if we have entities, legacy protocol support kinda breaks this logic\n\t{\n\t\tcl_entity_t *worldmodel = clgame.entities;\n\n\t\tworldmodel->curstate.modelindex = 1;\t// world model\n\t\tworldmodel->curstate.solid = SOLID_BSP;\n\t\tworldmodel->curstate.movetype = MOVETYPE_PUSH;\n\t\tworldmodel->model = cl.worldmodel;\n\t\tworldmodel->index = 0;\n\t}\n\n\tworld.max_recursion = 0;\n\n\tclgame.ds.cullMode = TRI_FRONT;\n\tclgame.numStatics = 0;\n}\n\nvoid CL_InitEdicts( int maxclients )\n{\n\tAssert( clgame.entities == NULL );\n\n\tif( !clgame.mempool ) return; // Host_Error without client\n#if XASH_LOW_MEMORY != 2\n\tCL_UPDATE_BACKUP = ( maxclients <= 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;\n#endif\n\tcls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES;\n\tcls.packet_entities = Mem_Realloc( clgame.mempool, cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities );\n\tclgame.entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities );\n\tclgame.static_entities = NULL; // will be initialized later\n\tclgame.numStatics = 0;\n\n\tif(( clgame.maxRemapInfos - 1 ) != clgame.maxEntities )\n\t{\n\t\tCL_ClearAllRemaps (); // purge old remap info\n\t\tclgame.maxRemapInfos = clgame.maxEntities + 1;\n\t\tclgame.remap_info = (remap_info_t **)Mem_Calloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos );\n\t}\n\n\tref.dllFuncs.R_ProcessEntData( true, clgame.entities, clgame.maxEntities );\n}\n\nvoid CL_FreeEdicts( void )\n{\n\tref.dllFuncs.R_ProcessEntData( false, NULL, 0 );\n\n\tif( clgame.entities )\n\t\tMem_Free( clgame.entities );\n\tclgame.entities = NULL;\n\n\tif( clgame.static_entities )\n\t\tMem_Free( clgame.static_entities );\n\tclgame.static_entities = NULL;\n\n\tif( cls.packet_entities )\n\t\tZ_Free( cls.packet_entities );\n\n\tcls.packet_entities = NULL;\n\tcls.num_client_entities = 0;\n\tcls.next_client_entities = 0;\n\tclgame.numStatics = 0;\n}\n\nvoid CL_ClearEdicts( void )\n{\n\tif( clgame.entities != NULL )\n\t\treturn;\n\n\t// in case we stopped with error\n\tclgame.maxEntities = 2;\n\tCL_InitEdicts( cl.maxclients );\n}\n\n/*\n==================\nCL_ClearSpriteTextures\n\nfree studio cache on change level\n==================\n*/\nvoid CL_ClearSpriteTextures( void )\n{\n\tint\ti;\n\n\tfor( i = 1; i < MAX_CLIENT_SPRITES; i++ )\n\t\tclgame.sprites[i].needload = NL_UNREFERENCED;\n}\n\n// it's a Valve default value for LoadMapSprite (probably must be power of two)\n#define MAPSPRITE_SIZE\t128\n\n/*\n====================\nMod_LoadMapSprite\n\nLoading a bitmap image as sprite with multiple frames\nas pieces of input image\n====================\n*/\nstatic void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded )\n{\n\trgbdata_t *pix, temp = { 0 };\n\tchar texname[128];\n\tint i, w, h;\n\tint xl, yl;\n\tint numframes;\n\tmsprite_t *psprite;\n\tchar poolname[MAX_VA_STRING];\n\n\tif( loaded ) *loaded = false;\n\tQ_snprintf( texname, sizeof( texname ), \"#%s\", mod->name );\n\tImage_SetForceFlags( IL_OVERVIEW );\n\tpix = FS_LoadImage( texname, buffer, size );\n\tImage_ClearForceFlags();\n\tif( !pix ) return; // bad image or something else\n\n\tmod->type = mod_sprite;\n\n\tif( pix->width % MAPSPRITE_SIZE )\n\t\tw = pix->width - ( pix->width % MAPSPRITE_SIZE );\n\telse w = pix->width;\n\n\tif( pix->height % MAPSPRITE_SIZE )\n\t\th = pix->height - ( pix->height % MAPSPRITE_SIZE );\n\telse h = pix->height;\n\n\tif( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE;\n\tif( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE;\n\n\t// resample image if needed\n\tImage_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f );\n\n\tw = h = MAPSPRITE_SIZE;\n\n\t// check range\n\tif( w > pix->width ) w = pix->width;\n\tif( h > pix->height ) h = pix->height;\n\n\t// determine how many frames we needs\n\tnumframes = (pix->width * pix->height) / (w * h);\n\tQ_snprintf( poolname, sizeof( poolname ), \"^2%s^7\", mod->name );\n\tmod->mempool = Mem_AllocPool( poolname );\n\tpsprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames ));\n\tmod->cache.data = psprite;\t// make link to extradata\n\n\tpsprite->type = SPR_FWD_PARALLEL_ORIENTED;\n\tpsprite->texFormat = SPR_ALPHTEST;\n\tpsprite->numframes = mod->numframes = numframes;\n\tpsprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1)));\n\n\tmod->mins[0] = mod->mins[1] = -w / 2;\n\tmod->maxs[0] = mod->maxs[1] = w / 2;\n\tmod->mins[2] = -h / 2;\n\tmod->maxs[2] = h / 2;\n\n\t// create a temporary pic\n\ttemp.width = w;\n\ttemp.height = h;\n\ttemp.type = pix->type;\n\ttemp.flags = pix->flags;\n\ttemp.size = w * h * PFDesc[temp.type].bpp;\n\ttemp.buffer = Mem_Malloc( mod->mempool, temp.size );\n\ttemp.palette = NULL;\n\n\t// chop the image and upload into video memory\n\tfor( i = xl = yl = 0; i < numframes; i++ )\n\t{\n\t\tmspriteframe_t *pspriteframe;\n\t\tint xh = xl + w, yh = yl + h, x, y, j;\n\t\tint linedelta = ( pix->width - w ) * 4;\n\t\tbyte *src = pix->buffer + ( yl * pix->width + xl ) * 4;\n\t\tbyte *dst = temp.buffer;\n\n\t\t// cut block from source\n\t\tfor( y = yl; y < yh; y++ )\n\t\t{\n\t\t\tfor( x = xl; x < xh; x++ )\n\t\t\t\tfor( j = 0; j < 4; j++ )\n\t\t\t\t\t*dst++ = *src++;\n\t\t\tsrc += linedelta;\n\t\t}\n\n\t\t// build uinque frame name\n\t\tQ_snprintf( texname, sizeof( texname ), \"#MAP/%s_%i%i.spr\", mod->name, i / 10, i % 10 );\n\n\t\tpsprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t ));\n\t\tpspriteframe = psprite->frames[i].frameptr;\n\t\tpspriteframe->width = w;\n\t\tpspriteframe->height = h;\n\t\tpspriteframe->up = ( h >> 1 );\n\t\tpspriteframe->left = -( w >> 1 );\n\t\tpspriteframe->down = ( h >> 1 ) - h;\n\t\tpspriteframe->right = w + -( w >> 1 );\n\t\tpspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE );\n\n\t\txl += w;\n\t\tif( xl >= pix->width )\n\t\t{\n\t\t\txl = 0;\n\t\t\tyl += h;\n\t\t}\n\t}\n\n\tFS_FreeImage( pix );\n\tMem_Free( temp.buffer );\n\tif( loaded ) *loaded = true;\n}\n\n/*\n=============\nCL_LoadHudSprite\n\nupload sprite frames\n=============\n*/\nstatic qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags )\n{\n\tbyte\t*buf;\n\tfs_offset_t\tsize;\n\tqboolean\tloaded;\n\n\tAssert( m_pSprite != NULL );\n\n\tQ_strncpy( m_pSprite->name, szSpriteName, sizeof( m_pSprite->name ));\n\n\t// it's hud sprite, make difference names to prevent free shared textures\n\tif( type == SPR_CLIENT || type == SPR_HUDSPRITE )\n\t\tSetBits( m_pSprite->flags, MODEL_CLIENT );\n\n\tm_pSprite->numtexinfo = texFlags; // store texFlags for renderer into numtexinfo\n\n\tif( !FS_FileExists( szSpriteName, false ) )\n\t{\n\t\tif( cls.state != ca_active && cl.maxclients > 1 )\n\t\t{\n\t\t\t// trying to download sprite from server\n\t\t\tCL_AddClientResource( szSpriteName, t_model );\n\t\t\tm_pSprite->needload = NL_NEEDS_LOADED;\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"Could not load HUD sprite %s\\n\", szSpriteName );\n\t\t\tMod_FreeModel( m_pSprite );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tbuf = FS_LoadFile( szSpriteName, &size, false );\n\tif( buf == NULL )\n\t\treturn false;\n\n\tif( type == SPR_MAPSPRITE )\n\t\tMod_LoadMapSprite( m_pSprite, buf, size, &loaded );\n\telse\n\t{\n\t\tMod_LoadSpriteModel( m_pSprite, buf, &loaded );\n\t\tref.dllFuncs.Mod_ProcessRenderData( m_pSprite, true, buf );\n\t}\n\n\tMem_Free( buf );\n\n\tif( !loaded )\n\t{\n\t\tMod_FreeModel( m_pSprite );\n\t\treturn false;\n\t}\n\n\tm_pSprite->needload = NL_PRESENT;\n\n\treturn true;\n}\n\n/*\n=============\nCL_LoadSpriteModel\n\nsome sprite models is exist only at client: HUD sprites,\ntent sprites or overview images\n=============\n*/\nstatic model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFlags )\n{\n\tchar\tname[MAX_QPATH];\n\tmodel_t\t*mod;\n\tint\ti, start;\n\n\tif( !COM_CheckString( filename ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: bad name!\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 0, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )\n\t{\n\t\tif( !Q_stricmp( mod->name, name ))\n\t\t{\n\t\t\tif( mod->needload == NL_NEEDS_LOADED )\n\t\t\t{\n\t\t\t\tif( CL_LoadHudSprite( name, mod, type, texFlags ))\n\t\t\t\t\treturn mod;\n\t\t\t}\n\n\t\t\t// prolonge registration\n\t\t\tmod->needload = NL_PRESENT;\n\t\t\treturn mod;\n\t\t}\n\t}\n\n\t// find a free model slot spot\n\t// use low indices only for HUD sprites\n\t// for GoldSrc bug compatibility\n\tstart = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2;\n\n\tfor( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ )\n\t{\n\t\tif( !mod->name[0] )\n\t\t\tbreak; // this is a valid spot\n\t}\n\n\tif( i == MAX_CLIENT_SPRITES / 2 )\n\t{\n\t\tCon_Printf( S_ERROR \"MAX_CLIENT_SPRITES limit exceeded (%d)\\n\", MAX_CLIENT_SPRITES / 2 );\n\t\treturn NULL;\n\t}\n\n\t// load new map sprite\n\tif( CL_LoadHudSprite( name, mod, type, texFlags ))\n\t\treturn mod;\n\treturn NULL;\n}\n\n/*\n=============\nCL_LoadClientSprite\n\nload sprites for temp ents\n=============\n*/\nmodel_t *CL_LoadClientSprite( const char *filename )\n{\n\treturn CL_LoadSpriteModel( filename, SPR_CLIENT, 0 );\n}\n\n/*\n===============================================================================\n\tCGame Builtin Functions\n\n===============================================================================\n*/\n/*\n=========\npfnSPR_LoadExt\n\n=========\n*/\nHSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags )\n{\n\tmodel_t\t*spr;\n\n\tif(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL )\n\t\treturn 0;\n\n\treturn (spr - clgame.sprites) + 1; // return index\n}\n\n/*\n=========\npfnSPR_Load\n\nfunction exported for support GoldSrc Monitor utility\n=========\n*/\nHSPRITE EXPORT pfnSPR_Load( const char *szPicName );\nHSPRITE EXPORT pfnSPR_Load( const char *szPicName )\n{\n\tmodel_t\t*spr;\n\n\tif(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL )\n\t\treturn 0;\n\n\treturn (spr - clgame.sprites) + 1; // return index\n}\n\n/*\n=============\nCL_GetSpritePointer\n\n=============\n*/\nstatic const model_t *CL_GetSpritePointer( HSPRITE hSprite )\n{\n\tmodel_t\t*mod;\n\tint index = hSprite - 1;\n\n\tif( index < 0 || index >= MAX_CLIENT_SPRITES )\n\t\treturn NULL; // bad image\n\tmod = &clgame.sprites[index];\n\n\tif( mod->needload == NL_NEEDS_LOADED )\n\t{\n\t\tint\ttype = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE;\n\n\t\tif( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo ))\n\t\t\treturn mod;\n\t}\n\n\tif( mod->mempool )\n\t{\n\t\tmod->needload = NL_PRESENT;\n\t\treturn mod;\n\t}\n\n\treturn NULL;\n}\n\n/*\n=========\npfnSPR_Frames\n\nfunction exported for support GoldSrc Monitor utility\n=========\n*/\nint EXPORT pfnSPR_Frames( HSPRITE hPic );\nint EXPORT pfnSPR_Frames( HSPRITE hPic )\n{\n\tint\tnumFrames = 0;\n\n\tref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));\n\n\treturn numFrames;\n}\n\n/*\n=========\npfnSPR_Height\n\n=========\n*/\nstatic int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame )\n{\n\tint\tsprHeight = 0;\n\n\tref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));\n\n\treturn sprHeight;\n}\n\n/*\n=========\npfnSPR_Width\n\n=========\n*/\nstatic int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame )\n{\n\tint\tsprWidth = 0;\n\n\tref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));\n\n\treturn sprWidth;\n}\n\n/*\n=========\npfnSPR_Set\n\n=========\n*/\nstatic void GAME_EXPORT pfnSPR_Set( HSPRITE hPic, int r, int g, int b )\n{\n\tconst model_t *sprite = CL_GetSpritePointer( hPic );\n\n\t// a1ba: do not alter the state if invalid HSPRITE was passed\n\tif( !sprite )\n\t\treturn;\n\n\tclgame.ds.pSprite = sprite;\n\tclgame.ds.spriteColor[0] = bound( 0, r, 255 );\n\tclgame.ds.spriteColor[1] = bound( 0, g, 255 );\n\tclgame.ds.spriteColor[2] = bound( 0, b, 255 );\n\tclgame.ds.spriteColor[3] = 255;\n}\n\n/*\n=========\npfnSPR_Draw\n\n=========\n*/\nstatic void GAME_EXPORT pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc )\n{\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha );\n\tSPR_DrawGeneric( frame, x, y, -1, -1, prc );\n}\n\n/*\n=========\npfnSPR_DrawHoles\n\n=========\n*/\nstatic void GAME_EXPORT pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc )\n{\n#if 1 // REFTODO\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransColor );\n#else\n\tpglEnable( GL_ALPHA_TEST );\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\tpglEnable( GL_BLEND );\n#endif\n\tSPR_DrawGeneric( frame, x, y, -1, -1, prc );\n\n#if 1\n\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n#else\n\tpglDisable( GL_ALPHA_TEST );\n\tpglDisable( GL_BLEND );\n#endif\n}\n\n/*\n=========\npfnSPR_DrawAdditive\n\n=========\n*/\nstatic void GAME_EXPORT pfnSPR_DrawAdditive( int frame, int x, int y, const wrect_t *prc )\n{\n#if 1 // REFTODO\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );\n#else\n\tpglEnable( GL_BLEND );\n\tpglBlendFunc( GL_ONE, GL_ONE );\n#endif\n\n\tSPR_DrawGeneric( frame, x, y, -1, -1, prc );\n\n#if 1 // REFTODO\n\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n#else\n\tpglDisable( GL_BLEND );\n#endif\n}\n\n/*\n=========\nSPR_GetList\n\nfor parsing half-life scripts - hud.txt etc\n=========\n*/\nstatic client_sprite_t *SPR_GetList( char *psz, int *piCount )\n{\n\tcached_spritelist_t\t*pEntry = &clgame.sprlist[0];\n\tint\t\tslot, index, numSprites = 0;\n\tbyte *afile;\n\tchar *pfile;\n\tstring\t\ttoken;\n\n\tif( piCount ) *piCount = 0;\n\n\t// see if already in list\n\t// NOTE: client.dll is cache hud.txt but reparse weapon lists again and again\n\t// obviously there a memory leak by-design. Cache the sprite lists to prevent it\n\tfor( slot = 0; slot < MAX_CLIENT_SPRITES && pEntry->szListName[0]; slot++ )\n\t{\n\t\tpEntry = &clgame.sprlist[slot];\n\n\t\tif( !Q_stricmp( pEntry->szListName, psz ))\n\t\t{\n\t\t\tif( piCount ) *piCount = pEntry->count;\n\t\t\treturn pEntry->pList;\n\t\t}\n\t}\n\n\tif( slot == MAX_CLIENT_SPRITES )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: overflow cache!\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tif( !clgame.itemspath[0] )\t// typically it's sprites\\*.txt\n\t\tCOM_ExtractFilePath( psz, clgame.itemspath );\n\n\tafile = FS_LoadFile( psz, NULL, false );\n\tif( !afile ) return NULL;\n\n\tpfile = (char *)afile;\n\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\tnumSprites = Q_atoi( token );\n\n\tQ_strncpy( pEntry->szListName, psz, sizeof( pEntry->szListName ));\n\n\t// name, res, pic, x, y, w, h\n\tpEntry->pList = Mem_Calloc( cls.mempool, sizeof( client_sprite_t ) * numSprites );\n\n\tfor( index = 0; index < numSprites; index++ )\n\t{\n\t\tif(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )\n\t\t\tbreak;\n\n\t\tQ_strncpy( pEntry->pList[index].szName, token, sizeof( pEntry->pList[0].szName ));\n\n\t\t// read resolution\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tpEntry->pList[index].iRes = Q_atoi( token );\n\n\t\t// read spritename\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tQ_strncpy( pEntry->pList[index].szSprite, token, sizeof( pEntry->pList[0].szSprite ));\n\n\t\t// parse rectangle\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tpEntry->pList[index].rc.left = Q_atoi( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tpEntry->pList[index].rc.top = Q_atoi( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tpEntry->pList[index].rc.right = pEntry->pList[index].rc.left + Q_atoi( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tpEntry->pList[index].rc.bottom = pEntry->pList[index].rc.top + Q_atoi( token );\n\n\t\tpEntry->count++;\n\t}\n\n\tif( index < numSprites )\n\t\tCon_DPrintf( S_WARN \"unexpected end of %s (%i should be %i)\\n\", psz, numSprites, index );\n\tif( piCount ) *piCount = pEntry->count;\n\tMem_Free( afile );\n\n\treturn pEntry->pList;\n}\n\n/*\n=============\nCL_FillRGBA\n\n=============\n*/\nstatic void GAME_EXPORT CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a )\n{\n\tfloat x_ = x, y_ = y, w_ = w, h_ = h;\n\n\tr = bound( 0, r, 255 );\n\tg = bound( 0, g, 255 );\n\tb = bound( 0, b, 255 );\n\ta = bound( 0, a, 255 );\n\n\tSPR_AdjustSize( &x_, &y_, &w_, &h_ );\n\n\tref.dllFuncs.FillRGBA( kRenderTransAdd, x_, y_, w_, h_, r, g, b, a );\n}\n\n/*\n=============\npfnGetScreenInfo\n\nget actual screen info\n=============\n*/\nint GAME_EXPORT CL_GetScreenInfo( SCREENINFO *pscrinfo )\n{\n\tqboolean apply_scale_factor = false; // we don't want floating point inaccuracies\n\tfloat scale_factor = hud_scale.value;\n\n\tif( FBitSet( hud_fontscale.flags, FCVAR_CHANGED ))\n\t{\n\t\tCL_FreeFont( &cls.creditsFont );\n\t\tSCR_LoadCreditsFont();\n\n\t\tClearBits( hud_fontscale.flags, FCVAR_CHANGED );\n\t}\n\n\t// setup screen info\n\tclgame.scrInfo.iSize = sizeof( clgame.scrInfo );\n\tclgame.scrInfo.iFlags = SCRINFO_SCREENFLASH;\n\n\tif( hud_scale.value >= 320.0f && hud_scale.value >= hud_scale_minimal_width.value )\n\t{\n\t\tscale_factor = refState.width / hud_scale.value;\n\t\tapply_scale_factor = scale_factor > 1.0f;\n\t}\n\telse if( scale_factor && scale_factor != 1.0f )\n\t{\n\t\tfloat scaled_width = (float)refState.width / scale_factor;\n\t\tif( scaled_width >= hud_scale_minimal_width.value )\n\t\t\tapply_scale_factor = true;\n\t}\n\n\tif( apply_scale_factor )\n\t{\n\t\tclgame.scrInfo.iWidth = (float)refState.width / scale_factor;\n\t\tclgame.scrInfo.iHeight = (float)refState.height / scale_factor;\n\t\tSetBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED );\n\t}\n\telse\n\t{\n\t\tclgame.scrInfo.iWidth = refState.width;\n\t\tclgame.scrInfo.iHeight = refState.height;\n\t\tClearBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED );\n\t}\n\n\tif( !pscrinfo ) return 0;\n\n\tif( pscrinfo->iSize != clgame.scrInfo.iSize )\n\t\tclgame.scrInfo.iSize = pscrinfo->iSize;\n\n\t// copy screeninfo out\n\tmemcpy( pscrinfo, &clgame.scrInfo, clgame.scrInfo.iSize );\n\n\treturn 1;\n}\n\n/*\n=============\npfnSetCrosshair\n\nsetup crosshair\n=============\n*/\nstatic void GAME_EXPORT pfnSetCrosshair( HSPRITE hspr, wrect_t rc, int r, int g, int b )\n{\n\tclgame.ds.rgbaCrosshair[0] = (byte)r;\n\tclgame.ds.rgbaCrosshair[1] = (byte)g;\n\tclgame.ds.rgbaCrosshair[2] = (byte)b;\n\tclgame.ds.rgbaCrosshair[3] = (byte)0xFF;\n\tclgame.ds.pCrosshair = CL_GetSpritePointer( hspr );\n\tclgame.ds.rcCrosshair = rc;\n}\n\n\n/*\n=============\npfnCvar_RegisterVariable\n\n=============\n*/\nstatic cvar_t *GAME_EXPORT pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags )\n{\n\t// a1ba: try to mitigate outdated client.dll vulnerabilities\n\tif( !Q_stricmp( szName, \"motdfile\" )\n\t\t|| !Q_stricmp( szName, \"sensitivity\" ))\n\t\tflags |= FCVAR_PRIVILEGED;\n\n\treturn (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_CLIENTDLL ));\n}\n\nstatic int GAME_EXPORT Cmd_AddClientCommand( const char *cmd_name, xcommand_t function )\n{\n\tint flags = CMD_CLIENTDLL;\n\n\t// a1ba: try to mitigate outdated client.dll vulnerabilities\n\tif( !Q_stricmp( cmd_name, \"motd_write\" ))\n\t\tflags |= CMD_PRIVILEGED;\n\n\treturn Cmd_AddCommandEx( cmd_name, function, \"client command\", flags, __func__ );\n}\n\n/*\n=============\npfnHookUserMsg\n\n=============\n*/\nstatic int GAME_EXPORT pfnHookUserMsg( const char *pszName, pfnUserMsgHook pfn )\n{\n\tint\ti;\n\n\t// ignore blank names or invalid callbacks\n\tif( !pszName || !*pszName || !pfn )\n\t\treturn 0;\n\n\tfor( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )\n\t{\n\t\t// see if already hooked\n\t\tif( !Q_stricmp( clgame.msg[i].name, pszName ))\n\t\t\treturn 1;\n\t}\n\n\tif( i == MAX_USER_MESSAGES )\n\t{\n\t\tHost_Error( \"%s: MAX_USER_MESSAGES hit!\\n\", __func__ );\n\t\treturn 0;\n\t}\n\n\t// hook new message\n\tQ_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name ));\n\tclgame.msg[i].func = pfn;\n\n\treturn 1;\n}\n\n/*\n=============\npfnServerCmd\n\n=============\n*/\nstatic int GAME_EXPORT pfnServerCmd( const char *szCmdString )\n{\n\tif( !COM_CheckString( szCmdString ))\n\t\treturn 0;\n\n\t// just like the client typed \"cmd xxxxx\" at the console\n\tMSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd );\n\tMSG_WriteString( &cls.netchan.message, szCmdString );\n\n\treturn 1;\n}\n\n/*\n=============\npfnClientCmd\n\n=============\n*/\nstatic int GAME_EXPORT pfnClientCmd( const char *szCmdString )\n{\n\tif( !COM_CheckString( szCmdString ))\n\t\treturn 0;\n\n\tif( cls.initialized )\n\t{\n\t\tCbuf_AddText( szCmdString );\n\t\tCbuf_AddText( \"\\n\" );\n\t}\n\telse\n\t{\n\t\t// will exec later\n\t\tQ_strncat( host.deferred_cmd, szCmdString, sizeof( host.deferred_cmd ));\n\t\tQ_strncat( host.deferred_cmd, \"\\n\", sizeof( host.deferred_cmd ));\n\t}\n\n\treturn 1;\n}\n\n/*\n=============\npfnFilteredClientCmd\n=============\n*/\nstatic int GAME_EXPORT pfnFilteredClientCmd( const char *szCmdString )\n{\n\tif( !COM_CheckString( szCmdString ))\n\t\treturn 0;\n\n\t// a1ba:\n\t// there should be stufftext validator, that checks\n\t// hardcoded commands and disallows them before passing to\n\t// filtered buffer, returning 0\n\t// I've replaced it by hooking potentially exploitable\n\t// commands and variables(motd_write, motdfile, etc) in client interfaces\n\n\tCbuf_AddFilteredText( szCmdString );\n\tCbuf_AddFilteredText( \"\\n\" );\n\n\treturn 1;\n}\n\n/*\n=============\npfnGetPlayerInfo\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo )\n{\n\tplayer_info_t\t*player;\n\n\tent_num -= 1; // player list if offset by 1 from ents\n\n\tif( ent_num >= cl.maxclients || ent_num < 0 || !cl.players[ent_num].name[0] )\n\t{\n\t\tpinfo->name = NULL;\n\t\tpinfo->thisplayer = false;\n\t\treturn;\n\t}\n\n\tplayer = &cl.players[ent_num];\n\tpinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false;\n\tpinfo->name = player->name;\n\tpinfo->model = player->model;\n\tpinfo->spectator = player->spectator;\n\tpinfo->ping = player->ping;\n\tpinfo->packetloss = player->packet_loss;\n\tpinfo->topcolor = player->topcolor;\n\tpinfo->bottomcolor = player->bottomcolor;\n}\n\n/*\n=============\npfnPlaySoundByName\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySoundByName( const char *szSound, float volume )\n{\n\tint hSound = S_RegisterSound( szSound );\n\tS_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );\n}\n\n/*\n=============\npfnPlaySoundByIndex\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySoundByIndex( int iSound, float volume )\n{\n\tint hSound;\n\n\t// make sure what we in-bounds\n\tiSound = bound( 0, iSound, MAX_SOUNDS );\n\thSound = cl.sound_index[iSound];\n\tif( !hSound ) return;\n\n\tS_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );\n}\n\n/*\n=============\npfnTextMessageGet\n\nreturns specified message from titles.txt\n=============\n*/\nclient_textmessage_t *CL_TextMessageGet( const char *pName )\n{\n\tint\ti;\n\n\t// first check internal messages\n\tfor( i = 0; i < MAX_TEXTCHANNELS; i++ )\n\t{\n\t\tchar name[MAX_VA_STRING];\n\n\t\tQ_snprintf( name, sizeof( name ), TEXT_MSGNAME, i );\n\n\t\tif( !Q_strcmp( pName, name ))\n\t\t\treturn cl_textmessage + i;\n\t}\n\n\t// find desired message\n\tfor( i = 0; i < clgame.numTitles; i++ )\n\t{\n\t\tif( !Q_stricmp( pName, clgame.titles[i].pName ))\n\t\t\treturn clgame.titles + i;\n\t}\n\treturn NULL; // found nothing\n}\n\n/*\n=============\npfnDrawCharacter\n\nreturns drawed chachter width (in real screen pixels)\n=============\n*/\nstatic int GAME_EXPORT pfnDrawCharacter( int x, int y, int number, int r, int g, int b )\n{\n\trgba_t color = { r, g, b, 255 };\n\tint flags = FONT_DRAW_HUD;\n\n\tif( hud_utf8.value )\n\t\tflags |= FONT_DRAW_UTF8;\n\n\treturn CL_DrawCharacter( x, y, number, color, &cls.creditsFont, flags );\n}\n\n/*\n=============\npfnDrawConsoleString\n\ndrawing string like a console string\n=============\n*/\nint GAME_EXPORT pfnDrawConsoleString( int x, int y, char *string )\n{\n\tcl_font_t *font = Con_GetFont( con_fontsize.value );\n\trgba_t color;\n\tVector4Copy( clgame.ds.textColor, color );\n\tVector4Set( clgame.ds.textColor, 255, 255, 255, 255 );\n\n\treturn x + CL_DrawString( x, y, string, color, font, FONT_DRAW_UTF8 | FONT_DRAW_HUD );\n}\n\n/*\n=============\npfnDrawSetTextColor\n\nset color for anything\n=============\n*/\nvoid GAME_EXPORT pfnDrawSetTextColor( float r, float g, float b )\n{\n\t// bound color and convert to byte\n\tclgame.ds.textColor[0] = (byte)bound( 0, r * 255, 255 );\n\tclgame.ds.textColor[1] = (byte)bound( 0, g * 255, 255 );\n\tclgame.ds.textColor[2] = (byte)bound( 0, b * 255, 255 );\n\tclgame.ds.textColor[3] = (byte)0xFF;\n}\n\n/*\n=============\npfnDrawConsoleStringLen\n\ncompute string length in screen pixels\n=============\n*/\nvoid GAME_EXPORT pfnDrawConsoleStringLen( const char *pText, int *length, int *height )\n{\n\tcl_font_t *font = Con_GetFont( con_fontsize.value );\n\n\tif( height ) *height = font->charHeight;\n\tCL_DrawStringLen( font, pText, length, NULL, FONT_DRAW_UTF8 | FONT_DRAW_HUD );\n}\n\n/*\n=============\npfnConsolePrint\n\nprints directly into console (can skip notify)\n=============\n*/\nstatic void GAME_EXPORT pfnConsolePrint( const char *string )\n{\n\tif( !COM_CheckString( string ))\n\t\treturn;\n\n\t// WON GoldSrc behavior\n\tif( string[0] != 1 )\n\t\tCon_Printf( \"%s\", string );\n\telse\n\t\tCon_NPrintf( 0, \"%s\", string + 1 );\n}\n\n/*\n=============\npfnCenterPrint\n\nholds and fade message at center of screen\nlike trigger_multiple message in q1\n=============\n*/\nstatic void GAME_EXPORT pfnCenterPrint( const char *string )\n{\n\tCL_CenterPrint( string, 0.25f );\n}\n\n/*\n=========\nGetWindowCenterX\n\n=========\n*/\nstatic int GAME_EXPORT pfnGetWindowCenterX( void )\n{\n\tint x = 0;\n\n#if XASH_WIN32\n\tif( m_ignore.value )\n\t{\n\t\tPOINT pos;\n\t\tGetCursorPos( &pos );\n\t\treturn pos.x;\n\t}\n#endif\n\n#if XASH_SDL >= 2\n\tSDL_GetWindowPosition( host.hWnd, &x, NULL );\n#endif\n\n\treturn host.window_center_x + x;\n}\n\n/*\n=========\nGetWindowCenterY\n\n=========\n*/\nstatic int GAME_EXPORT pfnGetWindowCenterY( void )\n{\n\tint y = 0;\n\n#if XASH_WIN32\n\tif( m_ignore.value )\n\t{\n\t\tPOINT pos;\n\t\tGetCursorPos( &pos );\n\t\treturn pos.y;\n\t}\n#endif\n\n#if XASH_SDL >= 2\n\tSDL_GetWindowPosition( host.hWnd, NULL, &y );\n#endif\n\n\treturn host.window_center_y + y;\n}\n\n/*\n=============\npfnGetViewAngles\n\nreturn interpolated angles from previous frame\n=============\n*/\nstatic void GAME_EXPORT pfnGetViewAngles( float *angles )\n{\n\tif( angles ) VectorCopy( cl.viewangles, angles );\n}\n\n/*\n=============\npfnSetViewAngles\n\nreturn interpolated angles from previous frame\n=============\n*/\nstatic void GAME_EXPORT pfnSetViewAngles( float *angles )\n{\n\tif( angles ) VectorCopy( angles, cl.viewangles );\n}\n\n/*\n=============\npfnPhysInfo_ValueForKey\n\n=============\n*/\nstatic const char* GAME_EXPORT pfnPhysInfo_ValueForKey( const char *key )\n{\n\treturn Info_ValueForKey( cls.physinfo, key );\n}\n\n/*\n=============\npfnServerInfo_ValueForKey\n\n=============\n*/\nstatic const char* GAME_EXPORT pfnServerInfo_ValueForKey( const char *key )\n{\n\treturn Info_ValueForKey( cl.serverinfo, key );\n}\n\n/*\n=============\npfnGetClientMaxspeed\n\nvalue that come from server\n=============\n*/\nstatic float GAME_EXPORT pfnGetClientMaxspeed( void )\n{\n\treturn cl.local.maxspeed;\n}\n\n/*\n=============\npfnIsNoClipping\n\n=============\n*/\nstatic int GAME_EXPORT pfnIsNoClipping( void )\n{\n\treturn ( cl.frames[cl.parsecountmod].playerstate[cl.playernum].movetype == MOVETYPE_NOCLIP );\n}\n\n/*\n=============\npfnGetViewModel\n\n=============\n*/\nstatic cl_entity_t* GAME_EXPORT CL_GetViewModel( void )\n{\n\treturn &clgame.viewent;\n}\n\n/*\n=============\npfnGetClientTime\n\n=============\n*/\nstatic float GAME_EXPORT pfnGetClientTime( void )\n{\n\treturn cl.time;\n}\n\n/*\n=============\npfnCalcShake\n\n=============\n*/\nstatic void GAME_EXPORT pfnCalcShake( void )\n{\n\tscreen_shake_t *const shake = &clgame.shake;\n\tfloat frametime, fraction, freq;\n\tint i;\n\n\tif( cl.time > shake->time || shake->amplitude <= 0 || shake->frequency <= 0 || shake->duration <= 0 )\n\t{\n\t\t// reset shake\n\t\tif( shake->time != 0 )\n\t\t{\n\t\t\tshake->time = 0;\n\t\t\tshake->applied_angle = 0;\n\t\t\tVectorClear( shake->applied_offset );\n\t\t}\n\n\t\treturn;\n\t}\n\n\tframetime = cl_clientframetime();\n\n\tif( cl.time > shake->next_shake )\n\t{\n\t\t// get next shake time based on frequency over duration\n\t\tshake->next_shake = (float)cl.time + shake->frequency / shake->duration;\n\n\t\t// randomize each shake\n\t\tfor( i = 0; i < 3; i++ )\n\t\t\tshake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude );\n\t\tshake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f );\n\t}\n\n\t// get initial fraction and frequency values over the duration\n\tfraction = ((float)cl.time - shake->time ) / shake->duration;\n\tfreq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f;\n\n\t// quickly approach zero but apply time over sine wave\n\tfraction *= fraction * sin( cl.time * freq );\n\n\t// apply shake offset\n\tfor( i = 0; i < 3; i++ )\n\t\tshake->applied_offset[i] = shake->offset[i] * fraction;\n\n\t// apply roll angle\n\tshake->applied_angle = shake->angle * fraction;\n\n\t// decrease amplitude, but slower on longer shakes or higher frequency\n\tshake->amplitude -= shake->amplitude * ( frametime / ( shake->frequency * shake->duration ));\n}\n\n/*\n=============\npfnApplyShake\n\n=============\n*/\nstatic void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor )\n{\n\tif( origin )\n\t\tVectorMA( origin, factor, clgame.shake.applied_offset, origin );\n\n\tif( angles )\n\t\tangles[ROLL] += clgame.shake.applied_angle * factor;\n}\n\n/*\n=============\npfnIsSpectateOnly\n\n=============\n*/\nstatic int GAME_EXPORT pfnIsSpectateOnly( void )\n{\n\treturn (cls.spectator != 0);\n}\n\n/*\n=============\npfnPointContents\n\n=============\n*/\nint GAME_EXPORT PM_CL_PointContents( const float *p, int *truecontents )\n{\n\treturn PM_PointContentsPmove( clgame.pmove, p, truecontents );\n}\n\npmtrace_t *PM_CL_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )\n{\n\treturn PM_TraceLine( clgame.pmove, start, end, flags, usehull, ignore_pe );\n}\n\nstatic void GAME_EXPORT pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin )\n{\n\tint hSound = S_RegisterSound( szSound );\n\tS_StartSound( origin, cl.viewentity, CHAN_AUTO, hSound, volume, ATTN_NORM, PITCH_NORM, 0 );\n}\n\n/*\n=============\npfnPrecacheEvent\n\n=============\n*/\nstatic word GAME_EXPORT pfnPrecacheEvent( int type, const char* psz )\n{\n\treturn CL_EventIndex( psz );\n}\n\n/*\n=============\npfnHookEvent\n\n=============\n*/\nstatic void GAME_EXPORT pfnHookEvent( const char *filename, pfnEventHook pfn )\n{\n\tchar\t\tname[64];\n\tcl_user_event_t\t*ev;\n\tint\t\ti;\n\n\t// ignore blank names\n\tif( !filename || !*filename )\n\t\treturn;\n\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\t// find an empty slot\n\tfor( i = 0; i < MAX_EVENTS; i++ )\n\t{\n\t\tev = clgame.events[i];\n\t\tif( !ev ) break;\n\n\t\tif( !Q_stricmp( name, ev->name ) && ev->func != NULL )\n\t\t{\n\t\t\tCon_Reportf( S_WARN \"%s: %s already hooked!\\n\", __func__, name );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tCL_RegisterEvent( i, name, pfn );\n}\n\n/*\n=============\npfnKillEvent\n\n=============\n*/\nstatic void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname )\n{\n\tint\t\ti;\n\tevent_state_t\t*es;\n\tevent_info_t\t*ei;\n\tword\t\teventIndex = CL_EventIndex( eventname );\n\n\tif( eventIndex >= MAX_EVENTS )\n\t\treturn;\n\n\tif( entnum < 0 || entnum >= clgame.maxEntities )\n\t\treturn;\n\n\tes = &cl.events;\n\n\t// find all events with specified index and kill it\n\tfor( i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tei = &es->ei[i];\n\n\t\tif( ei->index == eventIndex && ei->entity_index == entnum )\n\t\t{\n\t\t\tCL_ResetEvent( ei );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n=============\npfnPlaySound\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch )\n{\n\tS_StartSound( org, ent, chan, S_RegisterSound( samp ), vol, attn, pitch, flags );\n}\n\n/*\n=============\nCL_FindModelIndex\n\n=============\n*/\nstatic int GAME_EXPORT CL_FindModelIndex( const char *m )\n{\n\tchar filepath[MAX_QPATH];\n\tint  i;\n\n\tif( !COM_CheckString( m ))\n\t\treturn 0;\n\n\tQ_strncpy( filepath, m, sizeof( filepath ));\n\tCOM_FixSlashes( filepath );\n\n\tfor( i = 0; i < cl.nummodels; i++ )\n\t{\n\t\tif( !cl.models[i+1] )\n\t\t\tcontinue;\n\n\t\tif( !Q_stricmp( cl.models[i+1]->name, filepath ))\n\t\t\treturn i+1;\n\t}\n\n\treturn 0;\n}\n\n/*\n=============\npfnIsLocal\n\n=============\n*/\nstatic int GAME_EXPORT pfnIsLocal( int playernum )\n{\n\tif( playernum == cl.playernum )\n\t\treturn true;\n\treturn false;\n}\n\n/*\n=============\npfnLocalPlayerDucking\n\n=============\n*/\nstatic int GAME_EXPORT pfnLocalPlayerDucking( void )\n{\n\treturn (cl.local.usehull == 1) ? true : false;\n}\n\n/*\n=============\npfnLocalPlayerViewheight\n\n=============\n*/\nstatic void GAME_EXPORT pfnLocalPlayerViewheight( float *view_ofs )\n{\n\tif( view_ofs ) VectorCopy( cl.viewheight, view_ofs );\n}\n\n/*\n=============\npfnLocalPlayerBounds\n\n=============\n*/\nstatic void GAME_EXPORT pfnLocalPlayerBounds( int hull, float *mins, float *maxs )\n{\n\tif( hull >= 0 && hull < 4 )\n\t{\n\t\tif( mins ) VectorCopy( host.player_mins[hull], mins );\n\t\tif( maxs ) VectorCopy( host.player_maxs[hull], maxs );\n\t}\n}\n\n/*\n=============\npfnIndexFromTrace\n\n=============\n*/\nstatic int GAME_EXPORT pfnIndexFromTrace( struct pmtrace_s *pTrace )\n{\n#if 0 // Velaron: breaks compatibility with mods that call the function after CL_PopPMStates\n\tif( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent )\n\t{\n\t\t// return cl.entities number\n\t\treturn clgame.pmove->physents[pTrace->ent].info;\n\t}\n\treturn -1;\n#endif\n\treturn clgame.pmove->physents[pTrace->ent].info;\n}\n\n/*\n=============\npfnGetPhysent\n\n=============\n*/\nphysent_t *pfnGetPhysent( int idx )\n{\n\tif( idx >= 0 && idx < clgame.pmove->numphysent )\n\t{\n\t\t// return physent\n\t\treturn &clgame.pmove->physents[idx];\n\t}\n\treturn NULL;\n}\n\n/*\n=============\npfnGetVisent\n\n=============\n*/\nstatic physent_t *pfnGetVisent( int idx )\n{\n\tif( idx >= 0 && idx < clgame.pmove->numvisent )\n\t{\n\t\t// return physent\n\t\treturn &clgame.pmove->visents[idx];\n\t}\n\treturn NULL;\n}\n\nstatic int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags )\n{\n\treturn PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags );\n}\n\n/*\n=============\nCL_PushTraceBounds\n\n=============\n*/\nstatic void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs )\n{\n\tif( !host.trace_bounds_pushed )\n\t{\n\t\tmemcpy( host.player_mins_backup, host.player_mins, sizeof( host.player_mins_backup ));\n\t\tmemcpy( host.player_maxs_backup, host.player_maxs, sizeof( host.player_maxs_backup ));\n\n\t\thost.trace_bounds_pushed = true;\n\t}\n\n\thullnum = bound( 0, hullnum, 3 );\n\tVectorCopy( mins, host.player_mins[hullnum] );\n\tVectorCopy( maxs, host.player_maxs[hullnum] );\n}\n\n/*\n=============\nCL_PopTraceBounds\n\n=============\n*/\nstatic void GAME_EXPORT CL_PopTraceBounds( void )\n{\n\tif( !host.trace_bounds_pushed )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s called without push!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\thost.trace_bounds_pushed = false;\n\tmemcpy( host.player_mins, host.player_mins_backup, sizeof( host.player_mins ));\n\tmemcpy( host.player_maxs, host.player_maxs_backup, sizeof( host.player_maxs ));\n}\n\n/*\n=============\npfnSetTraceHull\n\n=============\n*/\nstatic void GAME_EXPORT CL_SetTraceHull( int hull )\n{\n\tclgame.pmove->usehull = bound( 0, hull, 3 );\n}\n\n/*\n=============\npfnPlayerTrace\n\n=============\n*/\nstatic void GAME_EXPORT CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr )\n{\n\tif( !tr ) return;\n\t*tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );\n}\n\n/*\n=============\npfnPlayerTraceExt\n\n=============\n*/\nstatic void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr )\n{\n\tif( !tr ) return;\n\t*tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pfnIgnore );\n}\n\n/*\n=============\nCL_TraceTexture\n\n=============\n*/\nconst char * GAME_EXPORT PM_CL_TraceTexture( int ground, float *vstart, float *vend )\n{\n\treturn PM_TraceTexture( clgame.pmove, ground, vstart, vend );\n}\n\n/*\n=============\npfnTraceSurface\n\n=============\n*/\nstruct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend )\n{\n\treturn PM_TraceSurfacePmove( clgame.pmove, ground, vstart, vend );\n}\n\n/*\n=============\npfnGetMovevars\n\n=============\n*/\nstatic movevars_t *pfnGetMoveVars( void )\n{\n\treturn &clgame.movevars;\n}\n\n/*\n=============\npfnStopAllSounds\n\n=============\n*/\nstatic void GAME_EXPORT pfnStopAllSounds( int ent, int entchannel )\n{\n\tS_StopSound( ent, entchannel, NULL );\n}\n\n/*\n=============\nCL_LoadModel\n\n=============\n*/\nmodel_t *CL_LoadModel( const char *modelname, int *index )\n{\n\tint\ti;\n\n\tif( index ) *index = -1;\n\n\tif(( i = CL_FindModelIndex( modelname )) == 0 )\n\t\treturn NULL;\n\n\tif( index ) *index = i;\n\n\treturn CL_ModelHandle( i );\n}\n\nstatic int GAME_EXPORT CL_AddEntity( int entityType, cl_entity_t *pEnt )\n{\n\tif( !pEnt ) return false;\n\n\t// clear effects for all temp entities\n\tif( !pEnt->index ) pEnt->curstate.effects = 0;\n\n\t// let the render reject entity without model\n\treturn CL_AddVisibleEntity( pEnt, entityType );\n}\n\n/*\n=============\npfnGetGameDirectory\n\n=============\n*/\nstatic const char *pfnGetGameDirectory( void )\n{\n\tstatic char\tszGetGameDir[MAX_SYSPATH];\n\n\tQ_strncpy( szGetGameDir, GI->gamefolder, sizeof( szGetGameDir ));\n\treturn szGetGameDir;\n}\n\n/*\n=============\npfnGetLevelName\n\n=============\n*/\nstatic const char *pfnGetLevelName( void )\n{\n\tstatic char\tmapname[64];\n\n\t// a1ba: don't return maps/.bsp if no map is loaded yet\n\t// in GoldSrc this is handled by cl.levelname field but we don't have it\n\t// so emulate this behavior here\n\tif( cls.state >= ca_connected && COM_CheckStringEmpty( clgame.mapname ))\n\t\tQ_snprintf( mapname, sizeof( mapname ), \"maps/%s.bsp\", clgame.mapname );\n\telse mapname[0] = '\\0'; // not in game\n\n\treturn mapname;\n}\n\n/*\n=============\npfnGetScreenFade\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetScreenFade( struct screenfade_s *fade )\n{\n\tif( fade ) *fade = clgame.fade;\n}\n\n/*\n=============\npfnSetScreenFade\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetScreenFade( struct screenfade_s *fade )\n{\n\tif( fade ) clgame.fade = *fade;\n}\n\n/*\n=============\npfnLoadMapSprite\n\n=============\n*/\nstatic model_t *pfnLoadMapSprite( const char *filename )\n{\n\tmodel_t *mod;\n\n\tmod = Mod_FindName( filename, false );\n\n\tif( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 ))\n\t\treturn mod;\n\n\treturn NULL;\n}\n\n/*\n=============\nCOM_AddAppDirectoryToSearchPath\n\n=============\n*/\nstatic void GAME_EXPORT COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName )\n{\n\tFS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH );\n}\n\n\n/*\n===========\nCOM_ExpandFilename\n\nFinds the file in the search path, copies over the name with the full path name.\nThis doesn't search in the pak file.\n===========\n*/\nstatic int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize )\n{\n\tchar\t\tresult[MAX_SYSPATH];\n\n\tif( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 )\n\t\treturn 0;\n\n\t// filename examples:\n\t// media\\sierra.avi - D:\\Xash3D\\valve\\media\\sierra.avi\n\t// models\\barney.mdl - D:\\Xash3D\\bshift\\models\\barney.mdl\n\tif( g_fsapi.GetFullDiskPath( result, sizeof( result ), fileName, false ))\n\t{\n\t\t// check for enough room\n\t\tif( Q_strlen( result ) > nameOutBufferSize )\n\t\t\treturn 0;\n\n\t\tQ_strncpy( nameOutBuffer, result, nameOutBufferSize );\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n/*\n=============\nPlayerInfo_ValueForKey\n\n=============\n*/\nstatic const char *PlayerInfo_ValueForKey( int playerNum, const char *key )\n{\n\t// find the player\n\tif(( playerNum > cl.maxclients ) || ( playerNum < 1 ))\n\t\treturn NULL;\n\n\tif( !cl.players[playerNum-1].name[0] )\n\t\treturn NULL;\n\n\treturn Info_ValueForKey( cl.players[playerNum-1].userinfo, key );\n}\n\n/*\n=============\nPlayerInfo_SetValueForKey\n\n=============\n*/\nstatic void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value )\n{\n\tconvar_t\t*var;\n\n\tif( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value ))\n\t\treturn; // no changes ?\n\n\tvar = Cvar_FindVar( key );\n\n\tif( var && FBitSet( var->flags, FCVAR_USERINFO ))\n\t{\n\t\tCvar_DirectSet( var, value );\n\t}\n\telse if( Info_SetValueForStarKey( cls.userinfo, key, value, sizeof( cls.userinfo )))\n\t{\n\t\t// time to update server copy of userinfo\n\t\tCL_UpdateInfo( key, value );\n\t}\n}\n\n/*\n=============\npfnGetPlayerUniqueID\n\n=============\n*/\nstatic qboolean GAME_EXPORT pfnGetPlayerUniqueID( int iPlayer, char playerID[16] )\n{\n\tif( iPlayer < 1 || iPlayer > cl.maxclients )\n\t\treturn false;\n\n\t// make sure there is a player here..\n\tif( !cl.players[iPlayer-1].userinfo[0] || !cl.players[iPlayer-1].name[0] )\n\t\treturn false;\n\n\tmemcpy( playerID, cl.players[iPlayer-1].hashedcdkey, 16 );\n\treturn true;\n}\n\n/*\n=============\npfnGetTrackerIDForPlayer\n\nobsolete, unused\n=============\n*/\nstatic int GAME_EXPORT pfnGetTrackerIDForPlayer( int playerSlot )\n{\n\treturn 0;\n}\n\n/*\n=============\npfnGetPlayerForTrackerID\n\nobsolete, unused\n=============\n*/\nstatic int GAME_EXPORT pfnGetPlayerForTrackerID( int trackerID )\n{\n\treturn 0;\n}\n\n/*\n=============\npfnServerCmdUnreliable\n\n=============\n*/\nstatic int GAME_EXPORT pfnServerCmdUnreliable( char *szCmdString )\n{\n\tif( !COM_CheckString( szCmdString ))\n\t\treturn 0;\n\n\tMSG_BeginClientCmd( &cls.datagram, clc_stringcmd );\n\tMSG_WriteString( &cls.datagram, szCmdString );\n\n\treturn 1;\n}\n\n/*\n=============\npfnGetMousePos\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt )\n{\n\tif( !ppt )\n\t\treturn;\n\n\tPlatform_GetMousePos( &ppt->x, &ppt->y );\n}\n\n/*\n=============\npfnSetMouseEnable\n\nlegacy of dinput code\n=============\n*/\nstatic void GAME_EXPORT pfnSetMouseEnable( qboolean fEnable )\n{\n}\n\n\n/*\n=============\npfnGetServerTime\n\n=============\n*/\nstatic float GAME_EXPORT pfnGetClientOldTime( void )\n{\n\treturn cl.oldtime;\n}\n\n/*\n=============\npfnGetGravity\n\n=============\n*/\nstatic float GAME_EXPORT pfnGetGravity( void )\n{\n\treturn clgame.movevars.gravity;\n}\n\n/*\n=============\npfnEnableTexSort\n\nTODO: implement\n=============\n*/\nstatic void GAME_EXPORT pfnEnableTexSort( int enable )\n{\n}\n\n/*\n=============\npfnSetLightmapColor\n\nTODO: implement\n=============\n*/\nstatic void GAME_EXPORT pfnSetLightmapColor( float red, float green, float blue )\n{\n}\n\n/*\n=============\npfnSetLightmapScale\n\nTODO: implement\n=============\n*/\nstatic void GAME_EXPORT pfnSetLightmapScale( float scale )\n{\n}\n\n/*\n=============\npfnSPR_DrawGeneric\n\n=============\n*/\nstatic void GAME_EXPORT pfnSPR_DrawGeneric( int frame, int x, int y, const wrect_t *prc, int blendsrc, int blenddst, int width, int height )\n{\n#if 0 // REFTODO:\n\tpglEnable( GL_BLEND );\n\tpglBlendFunc( blendsrc, blenddst ); // g-cont. are params is valid?\n#endif\n\tSPR_DrawGeneric( frame, x, y, width, height, prc );\n}\n\n/*\n=============\nLocalPlayerInfo_ValueForKey\n\n=============\n*/\nstatic const char *GAME_EXPORT LocalPlayerInfo_ValueForKey( const char* key )\n{\n\treturn Info_ValueForKey( cls.userinfo, key );\n}\n\n/*\n=============\npfnVGUI2DrawCharacter\n\n=============\n*/\nstatic int GAME_EXPORT pfnVGUI2DrawCharacter( int x, int y, int number, unsigned int font )\n{\n\treturn pfnDrawCharacter( x, y, number, 255, 255, 255 );\n}\n\n/*\n=============\npfnVGUI2DrawCharacterAdditive\n\n=============\n*/\nstatic int GAME_EXPORT pfnVGUI2DrawCharacterAdditive( int x, int y, int ch, int r, int g, int b, unsigned int font )\n{\n\treturn pfnDrawCharacter( x, y, ch, r, g, b );\n}\n\n/*\n=============\npfnDrawString\n\n=============\n*/\nstatic int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b )\n{\n\trgba_t color = { r, g, b, 255 };\n\tint flags = FONT_DRAW_HUD | FONT_DRAW_NOLF;\n\n\tif( hud_utf8.value )\n\t\tSetBits( flags, FONT_DRAW_UTF8 );\n\n\treturn CL_DrawString( x, y, str, color, &cls.creditsFont, flags );\n}\n\n/*\n=============\npfnDrawStringReverse\n\n=============\n*/\nstatic int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int r, int g, int b )\n{\n\trgba_t color = { r, g, b, 255 };\n\tint flags = FONT_DRAW_HUD | FONT_DRAW_NOLF;\n\tint width;\n\n\tif( hud_utf8.value )\n\t\tSetBits( flags, FONT_DRAW_UTF8 );\n\n\tCL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags );\n\n\tx -= width;\n\n\treturn CL_DrawString( x, y, str, color, &cls.creditsFont, flags );\n}\n\n/*\n=============\nGetCareerGameInterface\n\n=============\n*/\nstatic void *GAME_EXPORT GetCareerGameInterface( void )\n{\n\tMsg( \"^1Career GameInterface called!\\n\" );\n\treturn NULL;\n}\n\n/*\n=============\npfnPlaySoundVoiceByName\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySoundVoiceByName( char *filename, float volume, int pitch )\n{\n\tint hSound = S_RegisterSound( filename );\n\n\tS_StartSound( NULL, cl.viewentity, CHAN_NETWORKVOICE_END + 1, hSound, volume, 1.0, pitch, SND_STOP_LOOPING );\n}\n\n/*\n=============\npfnMP3_InitStream\n\n=============\n*/\nstatic void GAME_EXPORT pfnMP3_InitStream( char *filename, int looping )\n{\n\tif( !filename )\n\t{\n\t\tS_StopBackgroundTrack();\n\t\treturn;\n\t}\n\n\tif( looping )\n\t{\n\t\tS_StartBackgroundTrack( filename, filename, 0, false );\n\t}\n\telse\n\t{\n\t\tS_StartBackgroundTrack( filename, NULL, 0, false );\n\t}\n}\n\n/*\n=============\npfnPlaySoundByNameAtPitch\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySoundByNameAtPitch( char *filename, float volume, int pitch )\n{\n\tint hSound = S_RegisterSound( filename );\n\tS_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, 1.0, pitch, SND_STOP_LOOPING );\n}\n\n/*\n=============\npfnFillRGBABlend\n\n=============\n*/\nstatic void GAME_EXPORT CL_FillRGBABlend( int x, int y, int w, int h, int r, int g, int b, int a )\n{\n\tfloat x_ = x, y_ = y, w_ = w, h_ = h;\n\n\tr = bound( 0, r, 255 );\n\tg = bound( 0, g, 255 );\n\tb = bound( 0, b, 255 );\n\ta = bound( 0, a, 255 );\n\n\tSPR_AdjustSize( &x_, &y_, &w_, &h_ );\n\n\tref.dllFuncs.FillRGBA( kRenderTransTexture, x_, y_, w_, h_, r, g, b, a );\n}\n\n/*\n=============\npfnGetAppID\n\n=============\n*/\nstatic int GAME_EXPORT pfnGetAppID( void )\n{\n\treturn 70; // Half-Life AppID\n}\n\n/*\n=============\npfnVguiWrap2_GetMouseDelta\n\nTODO: implement\n=============\n*/\nstatic void GAME_EXPORT pfnVguiWrap2_GetMouseDelta( int *x, int *y )\n{\n}\n\n/*\n=============\npfnParseFile\n\nhandle colon separately\n=============\n*/\nstatic char *pfnParseFile( char *data, char *token )\n{\n\treturn COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL );\n}\n\n/*\n=================\nTriAPI implementation\n\n=================\n*/\n/*\n=================\nTriRenderMode\n=================\n*/\nvoid TriRenderMode( int mode )\n{\n\tclgame.ds.renderMode = mode;\n\tref.dllFuncs.TriRenderMode( mode );\n}\n\n/*\n=================\nTriColor4f\n=================\n*/\nvoid TriColor4f( float r, float g, float b, float a )\n{\n\tif( clgame.ds.renderMode == kRenderTransAlpha )\n\t\tref.dllFuncs.Color4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f );\n\telse ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0 );\n\n\tclgame.ds.triRGBA[0] = r;\n\tclgame.ds.triRGBA[1] = g;\n\tclgame.ds.triRGBA[2] = b;\n\tclgame.ds.triRGBA[3] = a;\n}\n\n/*\n=============\nTriColor4ub\n=============\n*/\nvoid TriColor4ub( byte r, byte g, byte b, byte a )\n{\n\tclgame.ds.triRGBA[0] = r * (1.0f / 255.0f);\n\tclgame.ds.triRGBA[1] = g * (1.0f / 255.0f);\n\tclgame.ds.triRGBA[2] = b * (1.0f / 255.0f);\n\tclgame.ds.triRGBA[3] = a * (1.0f / 255.0f);\n\n\tref.dllFuncs.Color4f( clgame.ds.triRGBA[0], clgame.ds.triRGBA[1], clgame.ds.triRGBA[2], 1.0f );\n}\n\n/*\n=============\nTriBrightness\n=============\n*/\nvoid TriBrightness( float brightness )\n{\n\tfloat\tr, g, b;\n\n\tr = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness;\n\tg = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness;\n\tb = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness;\n\n\tref.dllFuncs.Color4f( r, g, b, 1.0f );\n}\n\n/*\n=============\nTriCullFace\n=============\n*/\nvoid TriCullFace( TRICULLSTYLE style )\n{\n\tclgame.ds.cullMode = style;\n\tref.dllFuncs.CullFace( style );\n}\n\n/*\n=============\nTriWorldToScreen\nconvert world coordinates (x,y,z) into screen (x, y)\n=============\n*/\nint TriWorldToScreen( const float *world, float *screen )\n{\n\treturn ref.dllFuncs.WorldToScreen( world, screen );\n}\n\n/*\n=============\nTriBoxInPVS\n\ncheck box in pvs (absmin, absmax)\n=============\n*/\nint TriBoxInPVS( float *mins, float *maxs )\n{\n\treturn Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));\n}\n\n/*\n=============\nTriLightAtPoint\nNOTE: dlights are ignored\n=============\n*/\nvoid TriLightAtPoint( float *pos, float *value )\n{\n\tcolorVec\tvLightColor;\n\n\tif( !pos || !value ) return;\n\n\tvLightColor = ref.dllFuncs.R_LightPoint( pos );\n\n\tvalue[0] = vLightColor.r;\n\tvalue[1] = vLightColor.g;\n\tvalue[2] = vLightColor.b;\n}\n\n/*\n=============\nTriColor4fRendermode\nHeavy legacy of Quake...\n=============\n*/\nvoid TriColor4fRendermode( float r, float g, float b, float a, int rendermode )\n{\n\tif( rendermode == kRenderTransAlpha )\n\t{\n\t\tclgame.ds.triRGBA[3] = a / 255.0f;\n\t\tref.dllFuncs.Color4f( r, g, b, a );\n\t}\n\telse ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0f );\n}\n\n\n/*\n=============\nTriSpriteTexture\n\nbind current texture\n=============\n*/\nint TriSpriteTexture( model_t *pSpriteModel, int frame )\n{\n\tint\tgl_texturenum;\n\n\tif(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )\n\t\treturn 0;\n\n\tref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );\n\n\treturn 1;\n}\n\n/*\n=================\nDemoApi implementation\n\n=================\n*/\n/*\n=================\nDemo_IsTimeDemo\n\n=================\n*/\nstatic int GAME_EXPORT Demo_IsTimeDemo( void )\n{\n\treturn cls.timedemo;\n}\n\n/*\n=================\nNetworkApi implementation\n\n=================\n*/\n/*\n=================\nNetAPI_InitNetworking\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_InitNetworking( void )\n{\n\tNET_Config( true, false ); // allow remote\n}\n\n/*\n=================\nNetAPI_InitNetworking\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_Status( net_status_t *status )\n{\n\tqboolean\tconnected = false;\n\tint\tpacket_loss = 0;\n\n\tAssert( status != NULL );\n\n\tif( cls.state > ca_disconnected && cls.state != ca_cinematic )\n\t\tconnected = true;\n\n\tif( cls.state == ca_active )\n\t\tpacket_loss = bound( 0, (int)cls.packet_loss, 100 );\n\n\tstatus->connected = connected;\n\tstatus->connection_time = (connected) ? (host.realtime - cls.netchan.connect_time) : 0.0;\n\tstatus->latency = (connected) ? cl.frames[cl.parsecountmod].latency : 0.0;\n\tstatus->remote_address = cls.netchan.remote_address;\n\tstatus->packet_loss = packet_loss;\n\tNET_GetLocalAddress( &status->local_address, NULL ); // NetAPI doesn't know about IPv6\n\tstatus->rate = rate.value;\n}\n\n/*\n=================\nNetAPI_SendRequest\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response )\n{\n\tnet_request_t\t*nr = NULL;\n\tint\t\ti;\n\n\tif( !response )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: no callbcak specified for request with context %i!\\n\", __func__, context );\n\t\treturn;\n\t}\n\n\tif( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX )\n\t\treturn; // IPX no longer support\n\n\tif( request == NETAPI_REQUEST_SERVERLIST )\n\t\treturn; // no support for server list requests\n\n\t// find a free request\n\tfor( i = 0; i < MAX_REQUESTS; i++ )\n\t{\n\t\tnr = &clgame.net_requests[i];\n\t\tif( !nr->pfnFunc ) break;\n\t}\n\n\tif( i == MAX_REQUESTS )\n\t{\n\t\tdouble\tmax_timeout = 0;\n\n\t\t// no free requests? use oldest\n\t\tfor( i = 0, nr = NULL; i < MAX_REQUESTS; i++ )\n\t\t{\n\t\t\tif(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout )\n\t\t\t{\n\t\t\t\tmax_timeout = host.realtime - clgame.net_requests[i].timesend;\n\t\t\t\tnr = &clgame.net_requests[i];\n\t\t\t}\n\t\t}\n\t}\n\n\tAssert( nr != NULL );\n\n\t// clear slot\n\tmemset( nr, 0, sizeof( *nr ));\n\n\t// create a new request\n\tnr->timesend = host.realtime;\n\tnr->timeout = nr->timesend + timeout;\n\tnr->pfnFunc = response;\n\tnr->resp.context = context;\n\tnr->resp.type = request;\n\tnr->resp.remote_address = *remote_address;\n\tnr->flags = flags;\n\n\t// local servers request\n\tNetchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO\" %i %i %i\", FBitSet( flags, FNETAPI_LEGACY_PROTOCOL ) ? PROTOCOL_LEGACY_VERSION : PROTOCOL_VERSION, context, request );\n}\n\n/*\n=================\nNetAPI_CancelRequest\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_CancelRequest( int context )\n{\n\tnet_request_t\t*nr;\n\tint\t\ti;\n;\n\t// find a specified request\n\tfor( i = 0; i < MAX_REQUESTS; i++ )\n\t{\n\t\tnr = &clgame.net_requests[i];\n\n\t\tif( clgame.net_requests[i].resp.context == context )\n\t\t{\n\t\t\tif( nr->pfnFunc )\n\t\t\t{\n\t\t\t\tSetBits( nr->resp.error, NET_ERROR_TIMEOUT );\n\t\t\t\tnr->resp.ping = host.realtime - nr->timesend;\n\t\t\t\tnr->pfnFunc( &nr->resp );\n\t\t\t}\n\n\t\t\tmemset( &clgame.net_requests[i], 0, sizeof( net_request_t ));\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n=================\nNetAPI_CancelAllRequests\n\n=================\n*/\nvoid GAME_EXPORT NetAPI_CancelAllRequests( void )\n{\n\tnet_request_t\t*nr;\n\tint\t\ti;\n\n\t// tell the user about cancel\n\tfor( i = 0; i < MAX_REQUESTS; i++ )\n\t{\n\t\tnr = &clgame.net_requests[i];\n\t\tif( !nr->pfnFunc ) continue;\t// not used\n\t\tSetBits( nr->resp.error, NET_ERROR_TIMEOUT );\n\t\tnr->resp.ping = host.realtime - nr->timesend;\n\t\tnr->pfnFunc( &nr->resp );\n\t}\n\n\tmemset( clgame.net_requests, 0, sizeof( clgame.net_requests ));\n}\n\n/*\n=================\nNetAPI_AdrToString\n\n=================\n*/\nstatic const char *NetAPI_AdrToString( netadr_t *a )\n{\n\treturn NET_AdrToString( *a );\n}\n\n/*\n=================\nNetAPI_CompareAdr\n\n=================\n*/\nstatic int GAME_EXPORT NetAPI_CompareAdr( netadr_t *a, netadr_t *b )\n{\n\treturn NET_CompareAdr( *a, *b );\n}\n\n/*\n=================\nNetAPI_RemoveKey\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_RemoveKey( char *s, const char *key )\n{\n\tInfo_RemoveKey( s, key );\n}\n\n/*\n=================\nNetAPI_SetValueForKey\n\n=================\n*/\nstatic void GAME_EXPORT NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize )\n{\n\tif( key[0] == '*' ) return;\n\tInfo_SetValueForStarKey( s, key, value, maxsize );\n}\n\n\n/*\n=================\nIVoiceTweak implementation\n\nTODO: implement\n=================\n*/\n/*\n=================\nVoice_StartVoiceTweakMode\n\n=================\n*/\nstatic int GAME_EXPORT Voice_StartVoiceTweakMode( void )\n{\n\treturn 0;\n}\n\n/*\n=================\nVoice_EndVoiceTweakMode\n\n=================\n*/\nstatic void GAME_EXPORT Voice_EndVoiceTweakMode( void )\n{\n}\n\n/*\n=================\nVoice_SetControlFloat\n\n=================\n*/\nstatic void GAME_EXPORT Voice_SetControlFloat( VoiceTweakControl iControl, float value )\n{\n}\n\n/*\n=================\nVoice_GetControlFloat\n\n=================\n*/\nstatic float GAME_EXPORT Voice_GetControlFloat( VoiceTweakControl iControl )\n{\n\treturn 1.0f;\n}\n\nstatic void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] )\n{\n\t// stub\n}\n\n// shared between client and server\ntriangleapi_t gTriApi;\n\nstatic efx_api_t gEfxApi =\n{\n\tR_AllocParticle,\n\tR_BlobExplosion,\n\tR_Blood,\n\tR_BloodSprite,\n\tR_BloodStream,\n\tR_BreakModel,\n\tR_Bubbles,\n\tR_BubbleTrail,\n\tR_BulletImpactParticles,\n\tR_EntityParticles,\n\tR_Explosion,\n\tR_FizzEffect,\n\tR_FireField,\n\tR_FlickerParticles,\n\tR_FunnelSprite,\n\tR_Implosion,\n\tR_LargeFunnel,\n\tR_LavaSplash,\n\tR_MultiGunshot,\n\tR_MuzzleFlash,\n\tR_ParticleBox,\n\tR_ParticleBurst,\n\tR_ParticleExplosion,\n\tR_ParticleExplosion2,\n\tR_ParticleLine,\n\tR_PlayerSprites,\n\tR_Projectile,\n\tR_RicochetSound,\n\tR_RicochetSprite,\n\tR_RocketFlare,\n\tR_RocketTrail,\n\tR_RunParticleEffect,\n\tR_ShowLine,\n\tR_SparkEffect,\n\tR_SparkShower,\n\tR_SparkStreaks,\n\tR_Spray,\n\tR_Sprite_Explode,\n\tR_Sprite_Smoke,\n\tR_Sprite_Spray,\n\tR_Sprite_Trail,\n\tR_Sprite_WallPuff,\n\tR_StreakSplash,\n\tR_TracerEffect,\n\tR_UserTracerParticle,\n\tR_TracerParticles,\n\tR_TeleportSplash,\n\tR_TempSphereModel,\n\tR_TempModel,\n\tR_DefaultSprite,\n\tR_TempSprite,\n\tCL_DecalIndex,\n\tCL_DecalIndexFromName,\n\tCL_DecalShoot,\n\tR_AttachTentToPlayer,\n\tR_KillAttachedTents,\n\tR_BeamCirclePoints,\n\tR_BeamEntPoint,\n\tR_BeamEnts,\n\tR_BeamFollow,\n\tR_BeamKill,\n\tR_BeamLightning,\n\tR_BeamPoints,\n\tR_BeamRing,\n\tCL_AllocDlight,\n\tCL_AllocElight,\n\tCL_TempEntAlloc,\n\tCL_TempEntAllocNoModel,\n\tCL_TempEntAllocHigh,\n\tCL_TempEntAllocCustom,\n\tR_GetPackedColor,\n\tR_LookupColor,\n\tCL_DecalRemoveAll,\n\tCL_FireCustomDecal,\n};\n\nstatic event_api_t gEventApi =\n{\n\tEVENT_API_VERSION,\n\tpfnPlaySound,\n\tS_StopSound,\n\tCL_FindModelIndex,\n\tpfnIsLocal,\n\tpfnLocalPlayerDucking,\n\tpfnLocalPlayerViewheight,\n\tpfnLocalPlayerBounds,\n\tpfnIndexFromTrace,\n\tpfnGetPhysent,\n\tCL_SetUpPlayerPrediction,\n\tCL_PushPMStates,\n\tCL_PopPMStates,\n\tCL_SetSolidPlayers,\n\tCL_SetTraceHull,\n\tCL_PlayerTrace,\n\tCL_WeaponAnim,\n\tpfnPrecacheEvent,\n\tCL_PlaybackEvent,\n\tPM_CL_TraceTexture,\n\tpfnStopAllSounds,\n\tpfnKillEvents,\n\tCL_PlayerTraceExt,\t\t// Xash3D added\n\tCL_SoundFromIndex,\n\tpfnTraceSurface,\n\tpfnGetMoveVars,\n\tCL_VisTraceLine,\n\tpfnGetVisent,\n\tCL_TestLine,\n\tCL_PushTraceBounds,\n\tCL_PopTraceBounds,\n};\n\nstatic demo_api_t gDemoApi =\n{\n\t(void *)CL_IsRecordDemo,\n\t(void *)CL_IsPlaybackDemo,\n\tDemo_IsTimeDemo,\n\tCL_WriteDemoUserMessage,\n};\n\nnet_api_t gNetApi =\n{\n\tNetAPI_InitNetworking,\n\tNetAPI_Status,\n\tNetAPI_SendRequest,\n\tNetAPI_CancelRequest,\n\tNetAPI_CancelAllRequests,\n\tNetAPI_AdrToString,\n\tNetAPI_CompareAdr,\n\t(void *)NET_StringToAdr,\n\tInfo_ValueForKey,\n\tNetAPI_RemoveKey,\n\tNetAPI_SetValueForKey,\n};\n\nstatic IVoiceTweak gVoiceApi =\n{\n\tVoice_StartVoiceTweakMode,\n\tVoice_EndVoiceTweakMode,\n\tVoice_SetControlFloat,\n\tVoice_GetControlFloat,\n};\n\n// engine callbacks\nstatic cl_enginefunc_t gEngfuncs =\n{\n\tpfnSPR_Load,\n\tpfnSPR_Frames,\n\tpfnSPR_Height,\n\tpfnSPR_Width,\n\tpfnSPR_Set,\n\tpfnSPR_Draw,\n\tpfnSPR_DrawHoles,\n\tpfnSPR_DrawAdditive,\n\tSPR_EnableScissor,\n\tSPR_DisableScissor,\n\tSPR_GetList,\n\tCL_FillRGBA,\n\tCL_GetScreenInfo,\n\tpfnSetCrosshair,\n\tpfnCvar_RegisterClientVariable,\n\tCvar_VariableValue,\n\tCvar_VariableString,\n\tCmd_AddClientCommand,\n\tpfnHookUserMsg,\n\tpfnServerCmd,\n\tpfnClientCmd,\n\tpfnGetPlayerInfo,\n\tpfnPlaySoundByName,\n\tpfnPlaySoundByIndex,\n\tAngleVectors,\n\tCL_TextMessageGet,\n\tpfnDrawCharacter,\n\tpfnDrawConsoleString,\n\tpfnDrawSetTextColor,\n\tpfnDrawConsoleStringLen,\n\tpfnConsolePrint,\n\tpfnCenterPrint,\n\tpfnGetWindowCenterX,\n\tpfnGetWindowCenterY,\n\tpfnGetViewAngles,\n\tpfnSetViewAngles,\n\tCL_GetMaxClients,\n\tCvar_SetValue,\n\tCmd_Argc,\n\tCmd_Argv,\n\tCon_Printf,\n\tCon_DPrintf,\n\tCon_NPrintf,\n\tCon_NXPrintf,\n\tpfnPhysInfo_ValueForKey,\n\tpfnServerInfo_ValueForKey,\n\tpfnGetClientMaxspeed,\n\tCOM_CheckParm,\n\tKey_Event,\n\tPlatform_GetMousePos,\n\tpfnIsNoClipping,\n\tCL_GetLocalPlayer,\n\tCL_GetViewModel,\n\tCL_GetEntityByIndex,\n\tpfnGetClientTime,\n\tpfnCalcShake,\n\tpfnApplyShake,\n\tPM_CL_PointContents,\n\tCL_WaterEntity,\n\tPM_CL_TraceLine,\n\tCL_LoadModel,\n\tCL_AddEntity,\n\tCL_GetSpritePointer,\n\tpfnPlaySoundByNameAtLocation,\n\tpfnPrecacheEvent,\n\tCL_PlaybackEvent,\n\tCL_WeaponAnim,\n\tCOM_RandomFloat,\n\tCOM_RandomLong,\n\tpfnHookEvent,\n\tCon_Visible,\n\tpfnGetGameDirectory,\n\tpfnCVarGetPointer,\n\tKey_LookupBinding,\n\tpfnGetLevelName,\n\tpfnGetScreenFade,\n\tpfnSetScreenFade,\n\tVGui_GetPanel,\n\tVGui_ViewportPaintBackground,\n\tCOM_LoadFile,\n\tpfnParseFile,\n\tCOM_FreeFile,\n\t&gTriApi,\n\t&gEfxApi,\n\t&gEventApi,\n\t&gDemoApi,\n\t&gNetApi,\n\t&gVoiceApi,\n\tpfnIsSpectateOnly,\n\tpfnLoadMapSprite,\n\tCOM_AddAppDirectoryToSearchPath,\n\tCOM_ExpandFilename,\n\tPlayerInfo_ValueForKey,\n\tPlayerInfo_SetValueForKey,\n\tpfnGetPlayerUniqueID,\n\tpfnGetTrackerIDForPlayer,\n\tpfnGetPlayerForTrackerID,\n\tpfnServerCmdUnreliable,\n\tpfnGetMousePos,\n\tPlatform_SetMousePos,\n\tpfnSetMouseEnable,\n\tCvar_GetList,\n\t(void*)Cmd_GetFirstFunctionHandle,\n\t(void*)Cmd_GetNextFunctionHandle,\n\t(void*)Cmd_GetName,\n\tpfnGetClientOldTime,\n\tpfnGetGravity,\n\tCL_ModelHandle,\n\tpfnEnableTexSort,\n\tpfnSetLightmapColor,\n\tpfnSetLightmapScale,\n\tpfnSequenceGet,\n\tpfnSPR_DrawGeneric,\n\tpfnSequencePickSentence,\n\tpfnDrawString,\n\tpfnDrawStringReverse,\n\tLocalPlayerInfo_ValueForKey,\n\tpfnVGUI2DrawCharacter,\n\tpfnVGUI2DrawCharacterAdditive,\n\tSound_GetApproxWavePlayLen,\n\tGetCareerGameInterface,\n\tCvar_Set,\n\tpfnIsCareerMatch,\n\tpfnPlaySoundVoiceByName,\n\tpfnMP3_InitStream,\n\tSys_DoubleTime,\n\tpfnProcessTutorMessageDecayBuffer,\n\tpfnConstructTutorMessageDecayBuffer,\n\tpfnResetTutorMessageDecayData,\n\tpfnPlaySoundByNameAtPitch,\n\tCL_FillRGBABlend,\n\tpfnGetAppID,\n\tCmd_AliasGetList,\n\tpfnVguiWrap2_GetMouseDelta,\n\tpfnFilteredClientCmd\n};\n\nvoid CL_UnloadProgs( void )\n{\n\tif( !clgame.hInstance ) return;\n\n\tCL_FreeEdicts();\n\tCL_FreeTempEnts();\n\tCL_FreeViewBeams();\n\tCL_FreeParticles();\n\tCL_ClearAllRemaps();\n\tMod_ClearUserData();\n\n\t// NOTE: HLFX 0.5 has strange bug: hanging on exit if no map was loaded\n\tif( Q_stricmp( GI->gamefolder, \"hlfx\" ) || GI->version != 0.5f )\n\t\tclgame.dllFuncs.pfnShutdown();\n\n\tif( GI->internal_vgui_support )\n\t\tVGui_Shutdown();\n\n\tCvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\tCvar_FullSet( \"host_clientloaded\", \"0\", FCVAR_READ_ONLY );\n\n\tCvar_Unlink( FCVAR_CLIENTDLL );\n\tCmd_Unlink( CMD_CLIENTDLL );\n\n\tCOM_FreeLibrary( clgame.hInstance );\n\tMem_FreePool( &cls.mempool );\n\tMem_FreePool( &clgame.mempool );\n\tmemset( &clgame, 0, sizeof( clgame ));\n}\n\nqboolean CL_LoadProgs( const char *name )\n{\n\tstatic playermove_t\t\tgpMove;\n\tCL_EXPORT_FUNCS\tGetClientAPI; // single export\n\tqboolean valid_single_export = false;\n\tqboolean missed_exports = false;\n\tint i;\n\n\tif( clgame.hInstance ) CL_UnloadProgs();\n\n\t// initialize PlayerMove\n\tclgame.pmove = &gpMove;\n\n\tcls.mempool = Mem_AllocPool( \"Client Static Pool\" );\n\tclgame.mempool = Mem_AllocPool( \"Client Edicts Zone\" );\n\tclgame.entities = NULL;\n\n\t// a1ba: we need to check if client.dll has direct dependency on SDL2\n\t// and if so, disable relative mouse mode\n#if XASH_WIN32 && !XASH_64BIT\n\tclgame.client_dll_uses_sdl = COM_CheckLibraryDirectDependency( name, OS_LIB_PREFIX \"SDL2.\" OS_LIB_EXT, false );\n\tCon_Printf( S_NOTE \"%s uses %s for mouse input\\n\", name, clgame.client_dll_uses_sdl ? \"SDL2\" : \"Windows API\" );\n#endif\n\n\t// NOTE: important stuff!\n\t// vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS during LoadLibrary\n\tif( !GI->internal_vgui_support && VGui_LoadProgs( NULL ))\n\t\tVGui_Startup( refState.width, refState.height );\n\telse\n\t\tGI->internal_vgui_support = true; // we failed to load vgui_support, but let's probe client.dll for support anyway\n\n\tclgame.hInstance = COM_LoadLibrary( name, false, false );\n\n\tif( !clgame.hInstance )\n\t\treturn false;\n\n\t// delayed vgui initialization for internal support\n\tif( GI->internal_vgui_support && VGui_LoadProgs( clgame.hInstance ))\n\t\tVGui_Startup( refState.width, refState.height );\n\n\t// clear exports\n\tClearExports( cdll_exports, ARRAYSIZE( cdll_exports ));\n\n\t// trying to get single export\n\tif(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, \"GetClientAPI\" )) != NULL )\n\t{\n\t\tCon_Reportf( \"%s: found single callback export\\n\", __func__ );\n\n\t\t// trying to fill interface now\n\t\tGetClientAPI( &clgame.dllFuncs );\n\t}\n\telse if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, \"F\" )) != NULL )\n\t{\n\t\tCon_Reportf( \"%s: found single callback export (secured client dlls)\\n\", __func__ );\n\n\t\t// trying to fill interface now\n\t\tCL_GetSecuredClientAPI( GetClientAPI );\n\t}\n\n\tif( GetClientAPI != NULL ) // check critical functions again\n\t\tvalid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports ));\n\n\tfor( i = 0; i < ARRAYSIZE( cdll_exports ); i++ )\n\t{\n\t\tif( *(cdll_exports[i].func) != NULL )\n\t\t\tcontinue; // already gott through 'F' or 'GetClientAPI'\n\n\t\t// functions are cleared before all the extensions are evaluated\n\t\tif(( *(cdll_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_exports[i].name )) == NULL )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: failed to get address of %s proc\\n\", __func__, cdll_exports[i].name );\n\n\t\t\t// print all not found exports at once, for debug\n\t\t\tmissed_exports = true;\n\t\t}\n\t}\n\n\tif( missed_exports )\n\t{\n\t\tCOM_FreeLibrary( clgame.hInstance );\n\t\tclgame.hInstance = NULL;\n\t\treturn false;\n\t}\n\n\t// it may be loaded through 'GetClientAPI' so we don't need to clear them\n\tif( !valid_single_export )\n\t\tClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports ));\n\n\tfor( i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ )\n\t{\n\t\tif( *(cdll_new_exports[i].func) != NULL )\n\t\t\tcontinue; // already gott through 'F' or 'GetClientAPI'\n\n\t\t// functions are cleared before all the extensions are evaluated\n\t\t// NOTE: new exports can be missed without stop the engine\n\t\tif(( *(cdll_new_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_new_exports[i].name )) == NULL )\n\t\t\tCon_Reportf( S_WARN \"%s: failed to get address of %s proc\\n\", __func__, cdll_new_exports[i].name );\n\t}\n\n\tif( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION ))\n\t{\n\t\tCOM_FreeLibrary( clgame.hInstance );\n\t\tCon_Reportf( \"%s: can't init client API\\n\", __func__ );\n\t\tclgame.hInstance = NULL;\n\t\treturn false;\n\t}\n\n\tCvar_FullSet( \"host_clientloaded\", \"1\", FCVAR_READ_ONLY );\n\n\tclgame.maxRemapInfos = 0; // will be alloc on first call CL_InitEdicts();\n\tclgame.maxEntities = 2; // world + localclient (have valid entities not in game)\n\n\tCL_InitCDAudio( \"media/cdaudio.txt\" );\n\tCL_InitTitles( \"titles.txt\" );\n\tCL_InitParticles ();\n\tCL_InitViewBeams ();\n\tCL_InitTempEnts ();\n\n\tif( !R_InitRenderAPI())\t// Xash3D extension\n\t\tCon_Reportf( S_WARN \"%s: couldn't get render API\\n\", __func__ );\n\n\tif( !Mobile_Init() ) // Xash3D FWGS extension: mobile interface\n\t\tCon_Reportf( S_WARN \"%s: couldn't get mobility API\\n\", __func__ );\n\n\tCL_InitEdicts( cl.maxclients );\t\t// initailize local player and world\n\tCL_InitClientMove();\t// initialize pm_shared\n\n\t// initialize game\n\tclgame.dllFuncs.pfnInit();\n\n\tref.dllFuncs.CL_InitStudioAPI();\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/cl_gameui.c",
    "content": "/*\ncl_menu.c - menu dlls interaction\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"const.h\"\n#include \"library.h\"\n#include \"input.h\"\n#include \"server.h\" // !!svgame.hInstance\n#include \"vid_common.h\"\n#include \"ref_common.h\"\n\nstatic void \tUI_UpdateUserinfo( void );\n\ngameui_static_t\tgameui;\n\nstatic void UI_ToggleAllowConsole_f( void )\n{\n\thost.allow_console = host.allow_console_init = true;\n\n\tif( gameui.globals )\n\t\tgameui.globals->developer = true;\n}\n\nvoid UI_UpdateMenu( float realtime )\n{\n\tif( !gameui.hInstance ) return;\n\n\t// if some deferred cmds is waiting\n\tif( UI_IsVisible() && COM_CheckString( host.deferred_cmd ))\n\t{\n\t\tCbuf_AddText( host.deferred_cmd );\n\t\thost.deferred_cmd[0] = '\\0';\n\t\tCbuf_Execute();\n\t\treturn;\n\t}\n\n\t// don't show menu while level is loaded\n\tif( GameState->nextstate != STATE_RUNFRAME && !GameState->loadGame )\n\t\treturn;\n\n\t// menu time (not paused, not clamped)\n\tgameui.globals->time = host.realtime;\n\tgameui.globals->frametime = host.realframetime;\n\tgameui.globals->demoplayback = cls.demoplayback;\n\tgameui.globals->demorecording = cls.demorecording;\n\tgameui.globals->developer = host.allow_console;\n\n\tgameui.dllFuncs.pfnRedraw( realtime );\n\tUI_UpdateUserinfo();\n}\n\nvoid UI_KeyEvent( int key, qboolean down )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnKeyEvent( key, down );\n}\n\nvoid UI_MouseMove( int x, int y )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnMouseMove( x, y );\n}\n\nvoid UI_SetActiveMenu( qboolean fActive )\n{\n\tmovie_state_t\t*cin_state;\n\n\tif( !gameui.hInstance )\n\t{\n\t\tif( !fActive )\n\t\t\tKey_SetKeyDest( key_game );\n\t\treturn;\n\t}\n\n\tgameui.drawLogo = fActive;\n\tgameui.dllFuncs.pfnSetActiveMenu( fActive );\n\n\tif( !fActive )\n\t{\n\t\t// close logo when menu is shutdown\n\t\tcin_state = AVI_GetState( CIN_LOGO );\n\t\tAVI_CloseVideo( cin_state );\n\t}\n}\n\nvoid UI_AddServerToList( netadr_t adr, const char *info )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnAddServerToList( adr, info );\n}\n\nvoid UI_GetCursorPos( int *pos_x, int *pos_y )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnGetCursorPos( pos_x, pos_y );\n}\n\nvoid UI_SetCursorPos( int pos_x, int pos_y )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnSetCursorPos( pos_x, pos_y );\n}\n\nvoid UI_ShowCursor( qboolean show )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnShowCursor( show );\n}\n\nqboolean UI_CreditsActive( void )\n{\n\tif( !gameui.hInstance ) return 0;\n\treturn gameui.dllFuncs.pfnCreditsActive();\n}\n\nvoid UI_CharEvent( int key )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnCharEvent( key );\n}\n\nqboolean UI_MouseInRect( void )\n{\n\tif( !gameui.hInstance ) return 1;\n\treturn gameui.dllFuncs.pfnMouseInRect();\n}\n\nqboolean UI_IsVisible( void )\n{\n\tif( !gameui.hInstance ) return 0;\n\treturn gameui.dllFuncs.pfnIsVisible();\n}\n\n/*\n=======================\nUI_AddTouchButtonToList\n\nsend button parameters to menu\n=======================\n*/\nvoid UI_AddTouchButtonToList( const char *name, const char *texture, const char *command, unsigned char *color, int flags )\n{\n\tif( gameui.dllFuncs2.pfnAddTouchButtonToList )\n\t{\n\t\tgameui.dllFuncs2.pfnAddTouchButtonToList( name, texture, command, color, flags );\n\t}\n}\n\n/*\n=================\nUI_ResetPing\n\nnotify gameui dll about latency reset\n=================\n*/\nvoid UI_ResetPing( void )\n{\n\tif( gameui.dllFuncs2.pfnResetPing )\n\t{\n\t\tgameui.dllFuncs2.pfnResetPing( );\n\t}\n}\n\n/*\n=================\nUI_ShowConnectionWarning\n\nshow connection warning dialog implemented by gameui dll\n=================\n*/\nvoid UI_ShowConnectionWarning( void )\n{\n\tif( cls.state != ca_connected )\n\t\treturn;\n\n\tif( Host_IsLocalClient() )\n\t\treturn;\n\n\tif( ++cl.lostpackets == 8 )\n\t{\n\t\tCL_Disconnect();\n\t\tif( gameui.dllFuncs2.pfnShowConnectionWarning )\n\t\t{\n\t\t\tgameui.dllFuncs2.pfnShowConnectionWarning();\n\t\t}\n\t\tCon_DPrintf( S_WARN \"Too many lost packets! Showing Network options menu\\n\" );\n\t}\n}\n\n\n/*\n=================\nUI_ShowConnectionWarning\n\nshow update dialog\n=================\n*/\nvoid UI_ShowUpdateDialog( qboolean preferStore )\n{\n\tif( gameui.dllFuncs2.pfnShowUpdateDialog )\n\t{\n\t\tgameui.dllFuncs2.pfnShowUpdateDialog( preferStore );\n\t}\n\n\tCon_Printf( S_WARN \"This version is not supported anymore. To continue, install latest engine version\\n\" );\n}\n\n/*\n=================\nUI_ShowConnectionWarning\n\nshow message box\n=================\n*/\nqboolean UI_ShowMessageBox( const char *text )\n{\n\tif( gameui.dllFuncs2.pfnShowMessageBox )\n\t{\n\t\tgameui.dllFuncs2.pfnShowMessageBox( text );\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nvoid UI_ConnectionProgress_Disconnect( void )\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_Disconnect )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_Disconnect( );\n\t}\n}\n\nvoid UI_ConnectionProgress_Download( const char *pszFileName, const char *pszServerName, const char *pszServerPath, int iCurrent, int iTotal, const char *comment )\n{\n\tif( !gameui.dllFuncs2.pfnConnectionProgress_Download )\n\t\treturn;\n\n\tif( pszServerPath )\n\t{\n\t\tchar serverpath[MAX_SYSPATH];\n\n\t\tQ_snprintf( serverpath, sizeof( serverpath ), \"%s%s\", pszServerName, pszServerPath );\n\t\tgameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, serverpath, iCurrent, iTotal, comment );\n\t}\n\telse\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, pszServerName, iCurrent, iTotal, comment );\n\t}\n}\n\nvoid UI_ConnectionProgress_DownloadEnd( void )\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_DownloadEnd )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_DownloadEnd( );\n\t}\n}\n\nvoid UI_ConnectionProgress_Precache( void )\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_Precache )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_Precache( );\n\t}\n}\n\nvoid UI_ConnectionProgress_Connect( const char *server ) // NULL for local server\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_Connect )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_Connect( server );\n\t}\n}\n\nvoid UI_ConnectionProgress_ChangeLevel( void )\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_ChangeLevel )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_ChangeLevel( );\n\t}\n}\n\nvoid UI_ConnectionProgress_ParseServerInfo( const char *server )\n{\n\tif( gameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo )\n\t{\n\t\tgameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo( server );\n\t}\n}\n\nstatic void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, float width, float height )\n{\n\tmovie_state_t\t*cin_state;\n\n\tif( !gameui.drawLogo )\n\t\treturn;\n\n\tcin_state = AVI_GetState( CIN_LOGO );\n\n\tif( !AVI_IsActive( cin_state ))\n\t{\n\t\tstring\t\tpath;\n\t\tconst char\t*fullpath;\n\n\t\t// run cinematic if not\n\t\tQ_snprintf( path, sizeof( path ), \"media/%s\", filename );\n\t\tCOM_DefaultExtension( path, \".avi\", sizeof( path ));\n\t\tfullpath = FS_GetDiskPath( path, false );\n\n\t\tif( FS_FileExists( path, false ) && !fullpath )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Couldn't load %s from packfile. Please extract it\\n\", path );\n\t\t\tgameui.drawLogo = false;\n\t\t\treturn;\n\t\t}\n\n\t\tAVI_OpenVideo( cin_state, fullpath, false, true );\n\t\tif( !( AVI_GetVideoInfo( cin_state, &gameui.logo_xres, &gameui.logo_yres, &gameui.logo_length )))\n\t\t{\n\t\t\tAVI_CloseVideo( cin_state );\n\t\t\tgameui.drawLogo = false;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif( width <= 0 || height <= 0 )\n\t{\n\t\t// precache call, don't draw\n\t\treturn;\n\t}\n\n\tAVI_SetParm( cin_state,\n\t\tAVI_RENDER_TEXNUM, 0,\n\t\tAVI_RENDER_X, (int)x,\n\t\tAVI_RENDER_Y, (int)y,\n\t\tAVI_RENDER_W, (int)width,\n\t\tAVI_RENDER_H, (int)height,\n\t\tAVI_PARM_LAST );\n\n\t// read the next frame\n\tif( !AVI_Think( cin_state ))\n\t\tAVI_SetParm( cin_state, AVI_REWIND, AVI_PARM_LAST );\n}\n\nstatic int GAME_EXPORT UI_GetLogoWidth( void )\n{\n\treturn gameui.logo_xres;\n}\n\nstatic int GAME_EXPORT UI_GetLogoHeight( void )\n{\n\treturn gameui.logo_yres;\n}\n\nstatic float GAME_EXPORT UI_GetLogoLength( void )\n{\n\treturn gameui.logo_length;\n}\n\nstatic void UI_UpdateUserinfo( void )\n{\n\tplayer_info_t\t*player;\n\n\tif( !host.userinfo_changed )\n\t\treturn;\n\n\tplayer = &gameui.playerinfo;\n\n\tQ_strncpy( player->userinfo, cls.userinfo, sizeof( player->userinfo ));\n\tQ_strncpy( player->name, Info_ValueForKey( player->userinfo, \"name\" ), sizeof( player->name ));\n\tQ_strncpy( player->model, Info_ValueForKey( player->userinfo, \"model\" ), sizeof( player->model ));\n\tplayer->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, \"topcolor\" ));\n\tplayer->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, \"bottomcolor\" ));\n\thost.userinfo_changed = false; // we got it\n}\n\nvoid Host_Credits( void )\n{\n\tif( !gameui.hInstance ) return;\n\tgameui.dllFuncs.pfnFinalCredits();\n}\n\nstatic void UI_ConvertGameInfo( gameinfo2_t *out, const gameinfo_t *in )\n{\n\tout->gi_version = GAMEINFO_VERSION;\n\n\tQ_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder ));\n\tQ_strncpy( out->startmap, in->startmap, sizeof( out->startmap ));\n\tQ_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap ));\n\tQ_strncpy( out->demomap, in->demomap, sizeof( out->demomap ));\n\tQ_strncpy( out->title, in->title, sizeof( out->title ));\n\tQ_snprintf( out->version, sizeof( out->version ), \"%g\", in->version );\n\tQ_strncpy( out->iconpath, in->iconpath, sizeof( out->iconpath ));\n\n\tQ_strncpy( out->game_url, in->game_url, sizeof( out->game_url ));\n\tQ_strncpy( out->update_url, in->update_url, sizeof( out->update_url ));\n\tout->size = in->size;\n\tQ_strncpy( out->type, in->type, sizeof( out->type ));\n\tQ_strncpy( out->date, in->date, sizeof( out->date ));\n\n\tout->gamemode = in->gamemode;\n\n\tif( in->nomodels )\n\t\tSetBits( out->flags, GFL_NOMODELS );\n\tif( in->noskills )\n\t\tSetBits( out->flags, GFL_NOSKILLS );\n\tif( in->render_picbutton_text )\n\t\tSetBits( out->flags, GFL_RENDER_PICBUTTON_TEXT );\n\tif( in->hd_background )\n\t\tSetBits( out->flags, GFL_HD_BACKGROUND );\n\tif( in->animated_title )\n\t\tSetBits( out->flags, GFL_ANIMATED_TITLE );\n}\n\nstatic void UI_ToOldGameInfo( GAMEINFO *out, const gameinfo2_t *in )\n{\n\tQ_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder ));\n\tQ_strncpy( out->startmap, in->startmap, sizeof( out->startmap ));\n\tQ_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap ));\n\tQ_strncpy( out->title, in->title, sizeof( out->title ));\n\tQ_strncpy( out->version, in->version, sizeof( out->version ));\n\tout->flags = in->flags & 0xFFFF;\n\tQ_strncpy( out->game_url, in->game_url, sizeof( out->game_url ));\n\tQ_strncpy( out->update_url, in->update_url, sizeof( out->update_url ));\n\tQ_strncpy( out->size, Q_memprint( in->size ), sizeof( out->size ));\n\tQ_strncpy( out->type, in->type, sizeof( out->type ));\n\tQ_strncpy( out->date, in->date, sizeof( out->date ));\n\tout->gamemode = in->gamemode;\n}\n\nstatic void UI_GetModsInfo( void )\n{\n\tint i;\n\n\tgameui.modsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.modsInfo ) * FI->numgames );\n\tfor( i = 0; i < FI->numgames; i++ )\n\t\tUI_ConvertGameInfo( &gameui.modsInfo[i], FI->games[i] );\n}\n\n/*\n====================\nPIC_DrawGeneric\n\ndraw hudsprite routine\n====================\n*/\nstatic void PIC_DrawGeneric( float x, float y, float width, float height, const wrect_t *prc )\n{\n\tfloat\ts1, s2, t1, t2;\n\tint\tw, h;\n\n\t// assume we get sizes from image\n\tR_GetTextureParms( &w, &h, gameui.ds.gl_texturenum );\n\n\tif( prc )\n\t{\n\t\t// calc user-defined rectangle\n\t\ts1 = prc->left / (float)w;\n\t\tt1 = prc->top / (float)h;\n\t\ts2 = prc->right / (float)w;\n\t\tt2 = prc->bottom / (float)h;\n\n\t\tif( width == -1 && height == -1 )\n\t\t{\n\t\t\twidth = prc->right - prc->left;\n\t\t\theight = prc->bottom - prc->top;\n\t\t}\n\t}\n\telse\n\t{\n\t\ts1 = t1 = 0.0f;\n\t\ts2 = t2 = 1.0f;\n\t}\n\n\tif( width == -1 && height == -1 )\n\t{\n\t\twidth = w;\n\t\theight = h;\n\t}\n\n\t// pass scissor test if supposed\n\tif( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))\n\t\treturn;\n\n\tref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, gameui.ds.gl_texturenum );\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n}\n\n/*\n===============================================================================\n\tMainUI Builtin Functions\n\n===============================================================================\n*/\n/*\n=========\npfnPIC_Load\n\n=========\n*/\nstatic HIMAGE GAME_EXPORT pfnPIC_Load( const char *szPicName, const byte *image_buf, int image_size, int flags )\n{\n\tHIMAGE\ttx;\n\n\tif( !COM_CheckString( szPicName ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: refusing to load image with empty name\\n\", __func__ );\n\t\treturn 0;\n\t}\n\n\t// add default parms to image\n\tSetBits( flags, TF_IMAGE );\n\n\tImage_SetForceFlags( IL_LOAD_DECAL ); // allow decal images for menu\n\ttx = ref.dllFuncs.GL_LoadTexture( szPicName, image_buf, image_size, flags );\n\tImage_ClearForceFlags();\n\n\treturn tx;\n}\n\n/*\n=========\npfnPIC_Width\n\n=========\n*/\nstatic int GAME_EXPORT pfnPIC_Width( HIMAGE hPic )\n{\n\tint\tpicWidth;\n\n\tR_GetTextureParms( &picWidth, NULL, hPic );\n\n\treturn picWidth;\n}\n\n/*\n=========\npfnPIC_Height\n\n=========\n*/\nstatic int GAME_EXPORT pfnPIC_Height( HIMAGE hPic )\n{\n\tint\tpicHeight;\n\n\tR_GetTextureParms( NULL, &picHeight, hPic );\n\n\treturn picHeight;\n}\n\n/*\n=========\npfnPIC_Set\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a )\n{\n\tgameui.ds.gl_texturenum = hPic;\n\tr = bound( 0, r, 255 );\n\tg = bound( 0, g, 255 );\n\tb = bound( 0, b, 255 );\n\ta = bound( 0, a, 255 );\n\tref.dllFuncs.Color4ub( r, g, b, a );\n}\n\n/*\n=========\npfnPIC_Draw\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc )\n{\n\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n\tPIC_DrawGeneric( x, y, width, height, prc );\n}\n\n/*\n=========\npfnPIC_DrawTrans\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc )\n{\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\tPIC_DrawGeneric( x, y, width, height, prc );\n}\n\n/*\n=========\npfnPIC_DrawHoles\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc )\n{\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha );\n\tPIC_DrawGeneric( x, y, width, height, prc );\n}\n\n/*\n=========\npfnPIC_DrawAdditive\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc )\n{\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );\n\tPIC_DrawGeneric( x, y, width, height, prc );\n}\n\n/*\n=========\npfnPIC_EnableScissor\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_EnableScissor( int x, int y, int width, int height )\n{\n\t// check bounds\n\tx = bound( 0, x, gameui.globals->scrWidth );\n\ty = bound( 0, y, gameui.globals->scrHeight );\n\twidth = bound( 0, width, gameui.globals->scrWidth - x );\n\theight = bound( 0, height, gameui.globals->scrHeight - y );\n\n\tCL_EnableScissor( &gameui.ds.scissor, x, y, width, height );\n}\n\n/*\n=========\npfnPIC_DisableScissor\n\n=========\n*/\nstatic void GAME_EXPORT pfnPIC_DisableScissor( void )\n{\n\tCL_DisableScissor( &gameui.ds.scissor );\n}\n\n/*\n=============\npfnFillRGBA\n\n=============\n*/\nstatic void GAME_EXPORT pfnFillRGBA( int x, int y, int width, int height, int r, int g, int b, int a )\n{\n\tr = bound( 0, r, 255 );\n\tg = bound( 0, g, 255 );\n\tb = bound( 0, b, 255 );\n\ta = bound( 0, a, 255 );\n\n\tref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, width, height, r, g, b, a );\n}\n\n/*\n=============\npfnCvar_RegisterVariable\n\n=============\n*/\nstatic cvar_t *GAME_EXPORT pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags )\n{\n\treturn (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_GAMEUIDLL ));\n}\n\nstatic int GAME_EXPORT Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function )\n{\n\treturn Cmd_AddCommandEx( cmd_name, function, \"gameui command\", CMD_GAMEUIDLL, __func__ );\n}\n\n/*\n=============\npfnClientCmd\n\n=============\n*/\nstatic void GAME_EXPORT pfnClientCmd( int exec_now, const char *szCmdString )\n{\n\tif( !szCmdString || !szCmdString[0] )\n\t\treturn;\n\n\tCbuf_AddText( szCmdString );\n\tCbuf_AddText( \"\\n\" );\n\n\t// client command executes immediately\n\tif( exec_now ) Cbuf_Execute();\n}\n\n/*\n=============\npfnPlaySound\n\n=============\n*/\nstatic void GAME_EXPORT pfnPlaySound( const char *szSound )\n{\n\tif( !COM_CheckString( szSound )) return;\n\tS_StartLocalSound( szSound, VOL_NORM, false );\n}\n\n/*\n=============\npfnDrawCharacter\n\nquakefont draw character\n=============\n*/\nstatic void GAME_EXPORT pfnDrawCharacter( int ix, int iy, int iwidth, int iheight, int ch, int ulRGBA, HIMAGE hFont )\n{\n\trgba_t\tcolor;\n\tfloat\trow, col, size;\n\tfloat\ts1, t1, s2, t2;\n\tfloat\tx = ix, y = iy;\n\tfloat\twidth = iwidth;\n\tfloat\theight = iheight;\n\n\tch &= 255;\n\n\tif( ch == ' ' ) return;\n\tif( y < -height ) return;\n\n\tcolor[3] = (ulRGBA & 0xFF000000) >> 24;\n\tcolor[0] = (ulRGBA & 0xFF0000) >> 16;\n\tcolor[1] = (ulRGBA & 0xFF00) >> 8;\n\tcolor[2] = (ulRGBA & 0xFF) >> 0;\n\tref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );\n\n\tcol = (ch & 15) * 0.0625f + (0.5f / 256.0f);\n\trow = (ch >> 4) * 0.0625f + (0.5f / 256.0f);\n\tsize = 0.0625f - (1.0f / 256.0f);\n\n\ts1 = col;\n\tt1 = row;\n\ts2 = s1 + size;\n\tt2 = t1 + size;\n\n\t// pass scissor test if supposed\n\tif( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))\n\t\treturn;\n\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\tref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, hFont );\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n}\n\n/*\n=============\nUI_DrawConsoleString\n\ndrawing string like a console string\n=============\n*/\nstatic int GAME_EXPORT UI_DrawConsoleString( int x, int y, const char *string )\n{\n\tint\tdrawLen;\n\n\tif( !string || !*string ) return 0; // silent ignore\n\tdrawLen = Con_DrawString( x, y, string, gameui.ds.textColor );\n\tMakeRGBA( gameui.ds.textColor, 255, 255, 255, 255 );\n\n\treturn (x + drawLen); // exclude color prexfixes\n}\n\n/*\n=============\npfnDrawSetTextColor\n\nset color for anything\n=============\n*/\nstatic void GAME_EXPORT UI_DrawSetTextColor( int r, int g, int b, int alpha )\n{\n\t// bound color and convert to byte\n\tgameui.ds.textColor[0] = r;\n\tgameui.ds.textColor[1] = g;\n\tgameui.ds.textColor[2] = b;\n\tgameui.ds.textColor[3] = alpha;\n}\n\n/*\n====================\npfnGetPlayerModel\n\nfor drawing playermodel previews\n====================\n*/\nstatic cl_entity_t* GAME_EXPORT pfnGetPlayerModel( void )\n{\n\treturn &gameui.playermodel;\n}\n\n/*\n====================\npfnSetPlayerModel\n\nfor drawing playermodel previews\n====================\n*/\nstatic void GAME_EXPORT pfnSetPlayerModel( cl_entity_t *ent, const char *path )\n{\n\tent->model = Mod_ForName( path, false, false );\n\tent->curstate.modelindex = MAX_MODELS; // unreachable index\n}\n\n/*\n====================\npfnClearScene\n\nfor drawing playermodel previews\n====================\n*/\nstatic void GAME_EXPORT pfnClearScene( void )\n{\n\tref.dllFuncs.R_PushScene();\n\tref.dllFuncs.R_ClearScene();\n}\n\n/*\n====================\npfnRenderScene\n\nfor drawing playermodel previews\n====================\n*/\nstatic void GAME_EXPORT pfnRenderScene( const ref_viewpass_t *rvp )\n{\n\tref_viewpass_t copy;\n\n\t// to avoid division by zero\n\tif( !rvp || rvp->fov_x <= 0.0f || rvp->fov_y <= 0.0f )\n\t\treturn;\n\n\tcopy = *rvp;\n\n\t// don't allow special modes from menu\n\tcopy.flags = 0;\n\n\tref.dllFuncs.R_Set2DMode( false );\n\tGL_RenderFrame( &copy );\n\tref.dllFuncs.R_Set2DMode( true );\n\tref.dllFuncs.R_PopScene();\n}\n\n/*\n====================\npfnAddEntity\n\nadding player model into visible list\n====================\n*/\nstatic int GAME_EXPORT pfnAddEntity( int entityType, cl_entity_t *ent )\n{\n\tif( !ref.dllFuncs.R_AddEntity( ent, entityType ))\n\t\treturn false;\n\treturn true;\n}\n\n/*\n====================\npfnClientJoin\n\nsend client connect\n====================\n*/\nstatic void GAME_EXPORT pfnClientJoin( const netadr_t adr )\n{\n\tCbuf_AddTextf( \"connect %s\\n\", NET_AdrToString( adr ));\n}\n\n/*\n====================\npfnKeyGetOverstrikeMode\n\nget global key overstrike state\n====================\n*/\nstatic int GAME_EXPORT pfnKeyGetOverstrikeMode( void )\n{\n\treturn host.key_overstrike;\n}\n\n/*\n====================\npfnKeySetOverstrikeMode\n\nset global key overstrike mode\n====================\n*/\nstatic void GAME_EXPORT pfnKeySetOverstrikeMode( int fActive )\n{\n\thost.key_overstrike = fActive;\n}\n\n/*\n====================\npfnKeyGetState\n\nreturns kbutton struct if found\n====================\n*/\nstatic void *pfnKeyGetState( const char *name )\n{\n\tif( clgame.dllFuncs.KB_Find )\n\t\treturn clgame.dllFuncs.KB_Find( name );\n\treturn NULL;\n}\n\n/*\n=========\npfnMemAlloc\n\n=========\n*/\nstatic void *pfnMemAlloc( size_t cb, const char *filename, const int fileline )\n{\n\treturn _Mem_Alloc( gameui.mempool, cb, true, filename, fileline );\n}\n\n/*\n=========\npfnMemFree\n\n=========\n*/\nstatic void GAME_EXPORT pfnMemFree( void *mem, const char *filename, const int fileline )\n{\n\t_Mem_Free( mem, filename, fileline );\n}\n\n/*\n=========\npfnGetGameInfo\n\n=========\n*/\nstatic int GAME_EXPORT pfnGetOldGameInfo( GAMEINFO *pgameinfo )\n{\n\tif( !pgameinfo )\n\t\treturn 0;\n\n\tUI_ToOldGameInfo( pgameinfo, &gameui.gameInfo );\n\treturn 1;\n}\n\n/*\n=========\npfnGetGamesList\n\n=========\n*/\nstatic GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames )\n{\n\tif( numGames )\n\t\t*numGames = FI->numgames;\n\n\tif( !gameui.oldModsInfo )\n\t{\n\t\tint i;\n\n\t\tif( !gameui.modsInfo )\n\t\t\tUI_GetModsInfo();\n\n\t\t// first allocate array of pointers\n\t\tgameui.oldModsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo ) * FI->numgames );\n\t\tfor( i = 0; i < FI->numgames; i++ )\n\t\t{\n\t\t\tgameui.oldModsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo[i] ));\n\t\t\tUI_ToOldGameInfo( gameui.oldModsInfo[i], &gameui.modsInfo[i] );\n\t\t}\n\t}\n\n\treturn gameui.oldModsInfo;\n}\n\n/*\n=========\npfnGetFilesList\n\nrelease prev search on a next call\n=========\n*/\nstatic char ** GAME_EXPORT pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly )\n{\n\tstatic search_t\t*t = NULL;\n\n\tif( t ) Mem_Free( t ); // release prev search\n\n\tt = FS_Search( pattern, true, gamedironly );\n\tif( !t )\n\t{\n\t\tif( numFiles ) *numFiles = 0;\n\t\treturn NULL;\n\t}\n\n\tif( numFiles ) *numFiles = t->numfilenames;\n\treturn t->filenames;\n}\n\n/*\n=========\npfnGetClipboardData\n\npointer must be released in call place\n=========\n*/\nstatic char *pfnGetClipboardData( void )\n{\n\treturn Sys_GetClipboardData();\n}\n\n/*\n=========\npfnCheckGameDll\n\n=========\n*/\nstatic int GAME_EXPORT pfnCheckGameDll( void )\n{\n#ifdef XASH_INTERNAL_GAMELIBS\n\treturn true;\n#else\n\tstring dllpath;\n\n\tif( svgame.hInstance )\n\t\treturn true;\n\n\tCOM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath ));\n\n\tif( FS_FileExists( dllpath, false ))\n\t\treturn true;\n\n\treturn false;\n#endif\n}\n\n/*\n=========\npfnChangeInstance\n\n=========\n*/\nstatic void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage )\n{\n\tCon_Reportf( S_ERROR \"%s menu call is deprecated!\\n\", __func__ );\n}\n\n/*\n=========\npfnHostEndGame\n\n=========\n*/\nstatic void GAME_EXPORT pfnHostEndGame( const char *szFinalMessage )\n{\n\tif( !szFinalMessage ) szFinalMessage = \"\";\n\tHost_EndGame( false, \"%s\", szFinalMessage );\n}\n\n/*\n=========\npfnStartBackgroundTrack\n\n=========\n*/\nstatic void GAME_EXPORT pfnStartBackgroundTrack( const char *introTrack, const char *mainTrack )\n{\n\tS_StartBackgroundTrack( introTrack, mainTrack, 0, false );\n}\n\nstatic void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )\n{\n\tref.dllFuncs.GL_ProcessTexture( texnum, gamma, topColor, bottomColor );\n}\n\n\n/*\n=================\nUI_ShellExecute\n=================\n*/\nstatic void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, int shouldExit )\n{\n\tPlatform_ShellExecute( path, parms );\n\n\tif( shouldExit )\n\t\tSys_Quit( __func__ );\n}\n\n/*\n==============\npfnParseFile\n\nlegacy wrapper\n==============\n*/\nstatic char *pfnParseFile( char *buf, char *token )\n{\n\treturn COM_ParseFile( buf, token, INT_MAX );\n}\n\n/*\n=============\npfnFileExists\n\nlegacy wrapper\n=============\n*/\nstatic int pfnFileExists( const char *path, int gamedironly )\n{\n\treturn FS_FileExists( path, gamedironly );\n}\n\n/*\n=============\npfnDelete\n\nlegacy wrapper\n=============\n*/\nstatic int pfnDelete( const char *path )\n{\n\treturn FS_Delete( path );\n}\n\nstatic void GAME_EXPORT pfnCon_DefaultColor( int r, int g, int b )\n{\n\tCon_DefaultColor( r, g, b, true );\n}\n\nstatic void GAME_EXPORT pfnSetCursor( void *hCursor )\n{\n\tuintptr_t cursor;\n\n\tif( !gameui.use_extended_api )\n\t\treturn; // ignore original Xash menus\n\n\tcursor = (uintptr_t)hCursor;\n\tif( cursor < dc_user || cursor > dc_last )\n\t\treturn;\n\n\tPlatform_SetCursorType( cursor );\n}\n\nstatic void GAME_EXPORT pfnGetGameDir( char *out )\n{\n\tif( !out )\n\t\treturn;\n\n\tQ_strncpy( out, GI->gamefolder, sizeof( GI->gamefolder ));\n}\n\n// engine callbacks\nstatic const ui_enginefuncs_t gEngfuncs =\n{\n\tpfnPIC_Load,\n\tGL_FreeImage,\n\tpfnPIC_Width,\n\tpfnPIC_Height,\n\tpfnPIC_Set,\n\tpfnPIC_Draw,\n\tpfnPIC_DrawHoles,\n\tpfnPIC_DrawTrans,\n\tpfnPIC_DrawAdditive,\n\tpfnPIC_EnableScissor,\n\tpfnPIC_DisableScissor,\n\tpfnFillRGBA,\n\tpfnCvar_RegisterGameUIVariable,\n\tCvar_VariableValue,\n\tCvar_VariableString,\n\tCvar_Set,\n\tCvar_SetValue,\n\tCmd_AddGameUICommand,\n\tpfnClientCmd,\n\tCmd_RemoveCommand,\n\tCmd_Argc,\n\tCmd_Argv,\n\tCmd_Args,\n\tCon_Printf,\n\tCon_DPrintf,\n\tUI_NPrintf,\n\tUI_NXPrintf,\n\tpfnPlaySound,\n\tUI_DrawLogo,\n\tUI_GetLogoWidth,\n\tUI_GetLogoHeight,\n\tUI_GetLogoLength,\n\tpfnDrawCharacter,\n\tUI_DrawConsoleString,\n\tUI_DrawSetTextColor,\n\tCon_DrawStringLen,\n\tpfnCon_DefaultColor,\n\tpfnGetPlayerModel,\n\tpfnSetPlayerModel,\n\tpfnClearScene,\n\tpfnRenderScene,\n\tpfnAddEntity,\n\tHost_Error,\n\tpfnFileExists,\n\tpfnGetGameDir,\n\tCmd_CheckMapsList,\n\tCL_Active,\n\tpfnClientJoin,\n\tCOM_LoadFileForMe,\n\tpfnParseFile,\n\tCOM_FreeFile,\n\tKey_ClearStates,\n\tKey_SetKeyDest,\n\tKey_KeynumToString,\n\tKey_GetBinding,\n\tKey_SetBinding,\n\tKey_IsDown,\n\tpfnKeyGetOverstrikeMode,\n\tpfnKeySetOverstrikeMode,\n\tpfnKeyGetState,\n\tpfnMemAlloc,\n\tpfnMemFree,\n\tpfnGetOldGameInfo,\n\tpfnGetGamesList,\n\tpfnGetFilesList,\n\tSV_GetSaveComment,\n\tCL_GetDemoComment,\n\tpfnCheckGameDll,\n\tpfnGetClipboardData,\n\tUI_ShellExecute,\n\tHost_WriteServerConfig,\n\tpfnChangeInstance,\n\tpfnStartBackgroundTrack,\n\tpfnHostEndGame,\n\tCOM_RandomFloat,\n\tCOM_RandomLong,\n\tpfnSetCursor,\n\tpfnIsMapValid,\n\tGL_ProcessTexture,\n\tpfnCompareFileTime,\n\tVID_GetModeString,\n\t(void*)COM_SaveFile,\n\tpfnDelete\n};\n\nstatic void pfnEnableTextInput( int enable )\n{\n\tKey_EnableTextInput( enable, false );\n}\n\nstatic int pfnGetRenderers( unsigned int num, char *short_name, size_t size1, char *long_name, size_t size2 )\n{\n\tif( num >= ref.num_renderers )\n\t\treturn 0;\n\n\tif( short_name && size1 )\n\t\tQ_strncpy( short_name, ref.short_names[num], size1 );\n\n\tif( long_name && size2 )\n\t\tQ_strncpy( long_name, ref.long_names[num], size2 );\n\n\treturn 1;\n}\n\nstatic char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len )\n{\n\treturn COM_ParseFileSafe( data, buf, size, flags, len, NULL );\n}\n\nstatic gameinfo2_t *pfnGetGameInfo( int gi_version )\n{\n\tif( gi_version != gameui.gameInfo.gi_version )\n\t\treturn NULL;\n\n\treturn &gameui.gameInfo;\n}\n\nstatic gameinfo2_t *pfnGetModInfo( int gi_version, int i )\n{\n\tif( i < 0 || i >= FI->numgames )\n\t\treturn NULL;\n\n\tif( !gameui.modsInfo )\n\t\tUI_GetModsInfo();\n\n\tif( gi_version != gameui.modsInfo[i].gi_version )\n\t\treturn NULL;\n\n\treturn &gameui.modsInfo[i];\n}\n\nstatic int pfnIsCvarReadOnly( const char *name )\n{\n\tconvar_t *cv = Cvar_FindVar( name );\n\n\tif( !cv )\n\t\treturn -1;\n\n\treturn FBitSet( cv->flags, FCVAR_READ_ONLY ) ? 1 : 0;\n}\n\nstatic ui_extendedfuncs_t gExtendedfuncs =\n{\n\tpfnEnableTextInput,\n\tCon_UtfProcessChar,\n\tCon_UtfMoveLeft,\n\tCon_UtfMoveRight,\n\tpfnGetRenderers,\n\tSys_DoubleTime,\n\tpfnParseFileSafe,\n\tNET_AdrToString,\n\tNET_CompareAdrSort,\n\tSys_GetNativeObject,\n\t&gNetApi,\n\tpfnGetGameInfo,\n\tpfnGetModInfo,\n\tpfnIsCvarReadOnly,\n\tR_GetRenderDevice\n};\n\nvoid UI_UnloadProgs( void )\n{\n\tif( !gameui.hInstance ) return;\n\n\t// deinitialize game\n\tgameui.dllFuncs.pfnShutdown();\n\n\tCmd_RemoveCommand( \"ui_allowconsole\" );\n\tCvar_FullSet( \"host_gameuiloaded\", \"0\", FCVAR_READ_ONLY );\n\n\tCvar_Unlink( FCVAR_GAMEUIDLL );\n\tCmd_Unlink( CMD_GAMEUIDLL );\n\n\tCOM_FreeLibrary( gameui.hInstance );\n\tMem_FreePool( &gameui.mempool );\n\tmemset( &gameui, 0, sizeof( gameui ));\n}\n\nqboolean UI_LoadProgs( void )\n{\n\tstatic ui_enginefuncs_t\tgpEngfuncs;\n\tstatic ui_extendedfuncs_t gpExtendedfuncs;\n\tstatic ui_globalvars_t\tgpGlobals;\n\tUIEXTENEDEDAPI GetExtAPI;\n\tUITEXTAPI\tGiveTextApi;\n\tMENUAPI\tGetMenuAPI;\n\tstring dllpath;\n\tint\t\t\ti;\n\n\tif( gameui.hInstance ) UI_UnloadProgs();\n\n\t// setup globals\n\tgameui.globals = &gpGlobals;\n\n\tCOM_GetCommonLibraryPath( LIBRARY_GAMEUI, dllpath, sizeof( dllpath ));\n\n\tif(!( gameui.hInstance = COM_LoadLibrary( dllpath, false, false )))\n\t{\n\t\tstring path = OS_LIB_PREFIX \"menu.\" OS_LIB_EXT;\n\n\t\tFS_AllowDirectPaths( true );\n\n\t\t// no use to load it from engine directory, as library loader\n\t\t// that implements internal gamelibs already knows how to load it\n#ifndef XASH_INTERNAL_GAMELIBS\n\t\tif(!( gameui.hInstance = COM_LoadLibrary( path, false, true )))\n#endif\n\t\t{\n\t\t\tFS_AllowDirectPaths( false );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tFS_AllowDirectPaths( false );\n\n\tif(( GetMenuAPI = (MENUAPI)COM_GetProcAddress( gameui.hInstance, \"GetMenuAPI\" )) == NULL )\n\t{\n\t\tCOM_FreeLibrary( gameui.hInstance );\n\t\tCon_Reportf( \"%s: can't init menu API\\n\", __func__ );\n\t\tgameui.hInstance = NULL;\n\t\treturn false;\n\t}\n\n\n\tgameui.use_extended_api = false;\n\n\t// make local copy of engfuncs to prevent overwrite it with user dll\n\tgpEngfuncs = gEngfuncs;\n\n\tgameui.mempool = Mem_AllocPool( \"Menu Pool\" );\n\n\tif( !GetMenuAPI( &gameui.dllFuncs, &gpEngfuncs, gameui.globals ))\n\t{\n\t\tCOM_FreeLibrary( gameui.hInstance );\n\t\tCon_Reportf( \"%s: can't init menu API\\n\", __func__ );\n\t\tMem_FreePool( &gameui.mempool );\n\t\tgameui.hInstance = NULL;\n\t\treturn false;\n\t}\n\n\t// make local copy of engfuncs to prevent overwrite it with user dll\n\tgpExtendedfuncs = gExtendedfuncs;\n\tmemset( &gameui.dllFuncs2, 0, sizeof( gameui.dllFuncs2 ));\n\n\t// try to initialize new extended API\n\tif( ( GetExtAPI = (UIEXTENEDEDAPI)COM_GetProcAddress( gameui.hInstance, \"GetExtAPI\" ) ) )\n\t{\n\t\tCon_Reportf( \"%s: extended Menu API found\\n\", __func__ );\n\t\tif( GetExtAPI( MENU_EXTENDED_API_VERSION, &gameui.dllFuncs2, &gpExtendedfuncs ) )\n\t\t{\n\t\t\tCon_Reportf( \"%s: extended Menu API initialized\\n\", __func__ );\n\t\t\tgameui.use_extended_api = true;\n\t\t}\n\t}\n\telse // otherwise, fallback to old and deprecated extensions\n\t{\n\t\tif( ( GiveTextApi = (UITEXTAPI)COM_GetProcAddress( gameui.hInstance, \"GiveTextAPI\" ) ) )\n\t\t{\n\t\t\tCon_Reportf( \"%s: extended text API found\\n\", __func__ );\n\t\t\tCon_Reportf( S_WARN \"Text API is deprecated! If you are mod developer, consider moving to Extended Menu API!\\n\" );\n\t\t\tif( GiveTextApi( &gpExtendedfuncs ) ) // they are binary compatible, so we can just pass extended funcs API to menu\n\t\t\t{\n\t\t\t\tCon_Reportf( \"%s: extended text API initialized\\n\", __func__ );\n\t\t\t\tgameui.use_extended_api = true;\n\t\t\t}\n\t\t}\n\n\t\tgameui.dllFuncs2.pfnAddTouchButtonToList = (ADDTOUCHBUTTONTOLIST)COM_GetProcAddress( gameui.hInstance, \"AddTouchButtonToList\" );\n\t\tif( gameui.dllFuncs2.pfnAddTouchButtonToList )\n\t\t{\n\t\t\tCon_Reportf( \"%s: AddTouchButtonToList call found\\n\", __func__ );\n\t\t\tCon_Reportf( S_WARN \"AddTouchButtonToList is deprecated! If you are mod developer, consider moving to Extended Menu API!\\n\" );\n\t\t}\n\t}\n\n\tCvar_FullSet( \"host_gameuiloaded\", \"1\", FCVAR_READ_ONLY );\n\tCmd_AddRestrictedCommand( \"ui_allowconsole\", UI_ToggleAllowConsole_f, \"unlocks developer console\" );\n\n\tUI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo\n\n\t// setup globals\n\tgameui.globals->developer = host.allow_console;\n\n\t// initialize game\n\tgameui.dllFuncs.pfnInit();\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/cl_main.c",
    "content": "/*\ncl_main.c - client main loop\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"cl_tent.h\"\n#include \"input.h\"\n#include \"kbutton.h\"\n#include \"vgui_draw.h\"\n#include \"library.h\"\n#include \"vid_common.h\"\n#include \"pm_local.h\"\n#include \"multi_emulator.h\"\n\n#define MAX_CMD_BUFFER        8000\n#define CL_CONNECTION_TIMEOUT 15.0f\n#define CL_CONNECTION_RETRIES 10\n#define CL_TEST_RETRIES       5\n\nCVAR_DEFINE_AUTO( showpause, \"1\", 0, \"show pause logo when paused\" );\nCVAR_DEFINE_AUTO( mp_decals, \"300\", FCVAR_ARCHIVE, \"decals limit in multiplayer\" );\nstatic CVAR_DEFINE_AUTO( dev_overview, \"0\", 0, \"draw level in overview-mode\" );\nstatic CVAR_DEFINE_AUTO( cl_resend, \"6.0\", 0, \"time to resend connect\" );\nCVAR_DEFINE( cl_allow_download, \"cl_allowdownload\", \"1\", FCVAR_ARCHIVE, \"allow to downloading resources from the server\" );\nstatic CVAR_DEFINE( cl_allow_upload, \"cl_allowupload\", \"1\", FCVAR_ARCHIVE, \"allow to uploading resources to the server\" );\nCVAR_DEFINE_AUTO( cl_download_ingame, \"1\", FCVAR_ARCHIVE, \"allow to downloading resources while client is active\" );\nstatic CVAR_DEFINE_AUTO( cl_logofile, \"lambda\", FCVAR_ARCHIVE, \"player logo name\" );\nstatic CVAR_DEFINE_AUTO( cl_logocolor, \"orange\", FCVAR_ARCHIVE, \"player logo color\" );\nstatic CVAR_DEFINE_AUTO( cl_logoext, \"bmp\", FCVAR_ARCHIVE, \"temporary cvar to tell engine which logo must be packed\" );\nCVAR_DEFINE_AUTO( cl_logomaxdim, \"96\", FCVAR_ARCHIVE, \"maximum decal dimension\" );\nstatic CVAR_DEFINE_AUTO( cl_test_bandwidth, \"1\", FCVAR_ARCHIVE, \"test network bandwith before connection\" );\n\nCVAR_DEFINE( cl_draw_particles, \"r_drawparticles\", \"1\", FCVAR_CHEAT, \"render particles\" );\nCVAR_DEFINE( cl_draw_tracers, \"r_drawtracers\", \"1\", FCVAR_CHEAT, \"render tracers\" );\nCVAR_DEFINE( cl_draw_beams, \"r_drawbeams\", \"1\", FCVAR_CHEAT, \"render beams\" );\n\nstatic CVAR_DEFINE_AUTO( rcon_address, \"\", FCVAR_PRIVILEGED, \"remote control address\" );\nCVAR_DEFINE_AUTO( cl_timeout, \"60\", 0, \"connect timeout (in-seconds)\" );\nCVAR_DEFINE_AUTO( cl_nopred, \"0\", FCVAR_ARCHIVE|FCVAR_USERINFO, \"disable client movement prediction\" );\nstatic CVAR_DEFINE_AUTO( cl_nodelta, \"0\", 0, \"disable delta-compression for server messages\" );\nCVAR_DEFINE( cl_crosshair, \"crosshair\", \"1\", FCVAR_ARCHIVE, \"show weapon chrosshair\" );\nstatic CVAR_DEFINE_AUTO( cl_cmdbackup, \"10\", FCVAR_ARCHIVE, \"how many additional history commands are sent\" );\nCVAR_DEFINE_AUTO( cl_showerror, \"0\", FCVAR_ARCHIVE, \"show prediction error\" );\nCVAR_DEFINE_AUTO( cl_bmodelinterp, \"1\", FCVAR_ARCHIVE, \"enable bmodel interpolation\" );\nstatic CVAR_DEFINE_AUTO( cl_lightstyle_lerping, \"0\", FCVAR_ARCHIVE, \"enables animated light lerping (perfomance option)\" );\nCVAR_DEFINE_AUTO( cl_idealpitchscale, \"0.8\", 0, \"how much to look up/down slopes and stairs when not using freelook\" );\nCVAR_DEFINE_AUTO( cl_nosmooth, \"0\", FCVAR_ARCHIVE, \"disable smooth up stair climbing\" );\nCVAR_DEFINE_AUTO( cl_smoothtime, \"0.1\", FCVAR_ARCHIVE, \"time to smooth up\" );\nCVAR_DEFINE_AUTO( cl_clockreset, \"0.1\", FCVAR_ARCHIVE, \"frametime delta maximum value before reset\" );\nstatic CVAR_DEFINE_AUTO( cl_fixtimerate, \"7.5\", FCVAR_ARCHIVE, \"time in msec to client clock adjusting\" );\nCVAR_DEFINE_AUTO( hud_fontscale, \"1.0\", FCVAR_ARCHIVE|FCVAR_LATCH, \"scale hud font texture\" );\nCVAR_DEFINE_AUTO( hud_fontrender, \"0\", FCVAR_ARCHIVE, \"hud font render mode (0: additive, 1: holes, 2: trans)\" );\nCVAR_DEFINE_AUTO( hud_scale, \"0\", FCVAR_ARCHIVE|FCVAR_LATCH, \"scale hud at current resolution\" );\nCVAR_DEFINE_AUTO( hud_scale_minimal_width, \"640\", FCVAR_ARCHIVE|FCVAR_LATCH, \"if hud_scale results in a HUD virtual screen smaller than this value, it won't be applied\" );\nCVAR_DEFINE_AUTO( cl_solid_players, \"1\", 0, \"Make all players not solid (can't traceline them)\" );\nCVAR_DEFINE_AUTO( cl_updaterate, \"20\", FCVAR_USERINFO|FCVAR_ARCHIVE, \"refresh rate of server messages\" );\nCVAR_DEFINE_AUTO( cl_showevents, \"0\", FCVAR_ARCHIVE, \"show events playback\" );\nCVAR_DEFINE_AUTO( cl_cmdrate, \"30\", FCVAR_ARCHIVE, \"Max number of command packets sent to server per second\" );\nCVAR_DEFINE( cl_interp, \"ex_interp\", \"0.1\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"Interpolate object positions starting this many seconds in past\" );\nCVAR_DEFINE_AUTO( cl_nointerp, \"0\", 0, \"disable interpolation of entities and players\" );\nstatic CVAR_DEFINE_AUTO( cl_dlmax, \"0\", FCVAR_USERINFO|FCVAR_ARCHIVE, \"max allowed outcoming fragment size\" );\nstatic CVAR_DEFINE_AUTO( cl_upmax, \"508\", FCVAR_ARCHIVE, \"max allowed incoming fragment size\" );\n\nCVAR_DEFINE_AUTO( cl_lw, \"1\", FCVAR_ARCHIVE|FCVAR_USERINFO, \"enable client weapon predicting\" );\nCVAR_DEFINE_AUTO( cl_charset, \"utf-8\", FCVAR_ARCHIVE, \"1-byte charset to use (iconv style)\" );\nCVAR_DEFINE_AUTO( cl_trace_consistency, \"0\", FCVAR_ARCHIVE, \"enable consistency info tracing (good for developers)\" );\nCVAR_DEFINE_AUTO( cl_trace_stufftext, \"0\", FCVAR_ARCHIVE, \"enable stufftext (server-to-client console commands) tracing (good for developers)\" );\nCVAR_DEFINE_AUTO( cl_trace_messages, \"0\", FCVAR_ARCHIVE|FCVAR_CHEAT, \"enable message names tracing (good for developers)\" );\nCVAR_DEFINE_AUTO( cl_trace_events, \"0\", FCVAR_ARCHIVE|FCVAR_CHEAT, \"enable events tracing (good for developers)\" );\nstatic CVAR_DEFINE_AUTO( cl_nat, \"0\", 0, \"show servers running under NAT\" );\nCVAR_DEFINE_AUTO( hud_utf8, \"0\", FCVAR_ARCHIVE, \"Use utf-8 encoding for hud text\" );\nCVAR_DEFINE_AUTO( ui_renderworld, \"0\", FCVAR_ARCHIVE, \"render world when UI is visible\" );\nstatic CVAR_DEFINE_AUTO( cl_maxframetime, \"0\", 0, \"set deadline timer for client rendering to catch freezes\" );\nCVAR_DEFINE_AUTO( cl_fixmodelinterpolationartifacts, \"1\", 0, \"try to fix up models interpolation on a moving platforms (monsters on trains for example)\" );\n\n//\n// userinfo\n//\nstatic char username[32];\nstatic CVAR_DEFINE_AUTO( name, username, FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_PRINTABLEONLY|FCVAR_FILTERABLE, \"player name\" );\nstatic CVAR_DEFINE_AUTO( model, \"\", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"player model ('player' is a singleplayer model)\" );\nstatic CVAR_DEFINE_AUTO( topcolor, \"0\", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"player top color\" );\nstatic CVAR_DEFINE_AUTO( bottomcolor, \"0\", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"player bottom color\" );\nCVAR_DEFINE_AUTO( rate, \"25000\", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"player network rate\" );\n\nstatic CVAR_DEFINE_AUTO( cl_ticket_generator, \"revemu2013\", FCVAR_ARCHIVE, \"you wouldn't steal a car\" );\nstatic CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, \"1\", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, \"add [Xash3D] to the nickname when connecting to GoldSrc servers\" );\n\nclient_t\t\tcl;\nclient_static_t\tcls;\nclgame_static_t\tclgame;\n\nstatic void CL_SendMasterServerScanRequest( void );\n\n//======================================================================\nint GAME_EXPORT CL_Active( void )\n{\n\treturn ( cls.state == ca_active );\n}\n\nqboolean CL_Initialized( void )\n{\n\treturn cls.initialized;\n}\n\n//======================================================================\nqboolean CL_IsInGame( void )\n{\n\tif( host.type == HOST_DEDICATED )\n\t\treturn true; // always active for dedicated servers\n\n\tif( cl.background || cl.maxclients > 1 )\n\t\treturn true; // always active for multiplayer or background map\n\n\treturn ( cls.key_dest == key_game ); // active if not menu or console\n}\n\nqboolean CL_IsInConsole( void )\n{\n\treturn ( cls.key_dest == key_console );\n}\n\nqboolean CL_IsIntermission( void )\n{\n\treturn cl.intermission;\n}\n\nqboolean CL_IsPlaybackDemo( void )\n{\n\treturn cls.demoplayback;\n}\n\nqboolean CL_IsRecordDemo( void )\n{\n\treturn cls.demorecording;\n}\n\nqboolean CL_DisableVisibility( void )\n{\n\treturn cls.envshot_disable_vis;\n}\n\nchar *CL_Userinfo( void )\n{\n\treturn cls.userinfo;\n}\n\nint CL_IsDevOverviewMode( void )\n{\n\tif( dev_overview.value > 0.0f )\n\t{\n\t\tif( host_developer.value || cls.spectator )\n\t\t\treturn (int)dev_overview.value;\n\t}\n\n\treturn 0;\n}\n\nconnprotocol_t CL_Protocol( void )\n{\n\treturn cls.legacymode;\n}\n\n/*\n===============\nCL_CheckClientState\n\nfinalize connection process and begin new frame\nwith new cls.state\n===============\n*/\nstatic void CL_CheckClientState( void )\n{\n\t// first update is the pre-final signon stage\n\tif(( cls.state == ca_connected || cls.state == ca_validate ) && ( cls.signon == SIGNONS ))\n\t{\n\t\tcls.state = ca_active;\n\t\tcls.changelevel = false;\t\t// changelevel is done\n\t\tcls.changedemo = false;\t\t// changedemo is done\n\t\tcl.first_frame = true;\t\t// first rendering frame\n\n\t\tSCR_MakeLevelShot();\t\t// make levelshot if needs\n\t\tCvar_SetValue( \"scr_loading\", 0.0f );\t// reset progress bar\n\t\tNetchan_ReportFlow( &cls.netchan );\n\n\t\tCon_DPrintf( \"client connected at %.2f sec\\n\", Sys_DoubleTime() - cls.timestart );\n\t}\n}\n\nstatic int CL_GetGoldSrcFragmentSize( void *unused, fragsize_t mode )\n{\n\tswitch( mode )\n\t{\n\tcase FRAGSIZE_SPLIT:\n\t\treturn 1200; // MAX_RELIABLE_PAYLOAD\n\tcase FRAGSIZE_UNRELIABLE:\n\t\treturn 1400; // MAX_ROUTABLE_PACKET\n\tdefault:\n\t\tif( cls.state == ca_active )\n\t\t\treturn bound( 16, cl_dlmax.value, 1024 );\n\t\treturn 128;\n\t}\n}\n\nstatic int CL_GetFragmentSize( void *unused, fragsize_t mode )\n{\n\tswitch( mode )\n\t{\n\tcase FRAGSIZE_SPLIT:\n\t\treturn 0;\n\tcase FRAGSIZE_UNRELIABLE:\n\t\treturn NET_MAX_MESSAGE;\n\tdefault:\n\t\tif( Netchan_IsLocal( &cls.netchan ))\n\t\t\treturn FRAGMENT_LOCAL_SIZE;\n\t\treturn cl_upmax.value;\n\t}\n}\n\n/*\n=====================\nCL_SignonReply\n\nAn svc_signonnum has been received, perform a client side setup\n=====================\n*/\nvoid CL_SignonReply( connprotocol_t proto )\n{\n\t// g-cont. my favorite message :-)\n\tCon_Reportf( \"%s: %i\\n\", __func__, cls.signon );\n\n\tswitch( cls.signon )\n\t{\n\tcase 1:\n\t\tCL_ServerCommand( true, proto == PROTO_GOLDSRC ? \"sendents\" : \"begin\" );\n\t\tif( host_developer.value >= DEV_EXTENDED )\n\t\t\tMem_PrintStats();\n\t\tbreak;\n\tcase 2:\n\t\tSCR_EndLoadingPlaque();\n\t\tif( cl.proxy_redirect && !cls.spectator )\n\t\t\tCL_Disconnect();\n\t\tcl.proxy_redirect = false;\n\t\tbreak;\n\t}\n}\n\n/*\n===============\nCL_LerpPoint\n\nDetermines the fraction between the last two messages that the objects\nshould be put at.\n===============\n*/\nstatic float CL_LerpPoint( void )\n{\n\tdouble f = cl_serverframetime();\n\tdouble frac;\n\n\tif( f == 0.0 || cls.timedemo )\n\t{\n\t\tdouble fgap = cl_clientframetime();\n\t\tcl.time = cl.mtime[0];\n\n\t\t// maybe don't need for Xash demos\n\t\tif( cls.demoplayback )\n\t\t\tcl.oldtime = cl.mtime[0] - fgap;\n\n\t\treturn 1.0f;\n\t}\n\n\tif( cl_interp.value <= 0.001 )\n\t\treturn 1.0f;\n\n\tfrac = ( cl.time - cl.mtime[0] ) / cl_interp.value;\n\n\treturn frac;\n}\n\n/*\n===============\nCL_DriftInterpolationAmount\n\nDrift interpolation value (this is used for server unlag system)\n===============\n*/\nstatic int CL_DriftInterpolationAmount( int goal )\n{\n\tfloat\tfgoal, maxmove, diff;\n\tint\tmsec;\n\n\tfgoal = (float)goal / 1000.0f;\n\n\tif( fgoal != cl.local.interp_amount )\n\t{\n\t\tmaxmove = host.frametime * 0.05;\n\t\tdiff = fgoal - cl.local.interp_amount;\n\t\tdiff = bound( -maxmove, diff, maxmove );\n\t\tcl.local.interp_amount += diff;\n\t}\n\n\tmsec = cl.local.interp_amount * 1000.0f;\n\tmsec = bound( 0, msec, 100 );\n\n\treturn msec;\n}\n\n/*\n===============\nCL_ComputeClientInterpolationAmount\n\nValidate interpolation cvars, calc interpolation window\n===============\n*/\nstatic void CL_ComputeClientInterpolationAmount( usercmd_t *cmd )\n{\n\tconst float epsilon = 0.001f; // to avoid float invalid comparision\n\tfloat min_interp;\n\tfloat max_interp = MAX_EX_INTERP;\n\tfloat interpolation_time;\n\n\tif( cl_updaterate.value < MIN_UPDATERATE )\n\t{\n\t\tCon_Printf( \"cl_updaterate minimum is %f, resetting to default (20)\\n\", MIN_UPDATERATE );\n\t\tCvar_Reset( \"cl_updaterate\" );\n\t}\n\n\tif( cl_updaterate.value > MAX_UPDATERATE )\n\t{\n\t\tCon_Printf( \"cl_updaterate clamped at maximum (%f)\\n\", MAX_UPDATERATE );\n\t\tCvar_SetValue( \"cl_updaterate\", MAX_UPDATERATE );\n\t}\n\n\tif( cls.spectator )\n\t\tmax_interp = 0.2f;\n\n\tmin_interp = 1.0f / cl_updaterate.value;\n\tinterpolation_time = cl_interp.value * 1000.0;\n\n\tif( (cl_interp.value + epsilon) < min_interp )\n\t{\n\t\tCon_Printf( \"ex_interp forced up to %.1f msec\\n\", min_interp * 1000.f );\n\t\tCvar_SetValue( \"ex_interp\", min_interp );\n\t}\n\telse if( (cl_interp.value - epsilon) > max_interp )\n\t{\n\t\tCon_Printf( \"ex_interp forced down to %.1f msec\\n\", max_interp * 1000.f );\n\t\tCvar_SetValue( \"ex_interp\", max_interp );\n\t}\n\n\tinterpolation_time = bound( min_interp, interpolation_time, max_interp );\n\tcmd->lerp_msec = CL_DriftInterpolationAmount( interpolation_time * 1000 );\n}\n\n/*\n=================\nCL_ComputePacketLoss\n\n=================\n*/\nstatic void CL_ComputePacketLoss( void )\n{\n\tint i, lost = 0;\n\n\tif( host.realtime < cls.packet_loss_recalc_time )\n\t\treturn;\n\n\tcls.packet_loss_recalc_time = host.realtime + 1.0;\n\n\tfor( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ )\n\t{\n\t\tif( cl.frames[i & CL_UPDATE_MASK].receivedtime == -1.0 )\n\t\t\tlost++;\n\t}\n\n\tcls.packet_loss = lost * 100.0f / (float)CL_UPDATE_BACKUP;\n}\n\n/*\n=================\nCL_UpdateFrameLerp\n\n=================\n*/\nvoid CL_UpdateFrameLerp( void )\n{\n\tif( cls.state != ca_active || !cl.validsequence )\n\t\treturn;\n\n\t// compute last interpolation amount\n\tcl.lerpFrac = CL_LerpPoint();\n\n\tcl.commands[(cls.netchan.outgoing_sequence - 1) & CL_UPDATE_MASK].frame_lerp = cl.lerpFrac;\n}\n\nstatic void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, pred_viewangle_t **next )\n{\n\tint\ti, i0, i1, imod;\n\tfloat\tat;\n\n\timod = cl.angle_position - 1;\n\ti0 = (imod + 1) & ANGLE_MASK;\n\ti1 = (imod + 0) & ANGLE_MASK;\n\n\tif( cl.predicted_angle[i0].starttime >= t )\n\t{\n\t\tfor( i = 0; i < ANGLE_BACKUP - 2; i++ )\n\t\t{\n\t\t\tat = cl.predicted_angle[imod & ANGLE_MASK].starttime;\n\t\t\tif( at == 0.0f ) break;\n\n\t\t\tif( at < t )\n\t\t\t{\n\t\t\t\ti0 = (imod + 1) & ANGLE_MASK;\n\t\t\t\ti1 = (imod + 0) & ANGLE_MASK;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\timod--;\n\t\t}\n\t}\n\n\t*next = &cl.predicted_angle[i0];\n\t*prev = &cl.predicted_angle[i1];\n\n\t// avoid division by zero (probably this should never happens)\n\tif((*prev)->starttime == (*next)->starttime )\n\t{\n\t\t*prev = *next;\n\t\t*frac = 0.0f;\n\t\treturn;\n\t}\n\n\t// time spans the two entries\n\t*frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime );\n\t*frac = bound( 0.0f, *frac, 1.0f );\n}\n\nstatic void CL_ApplyAddAngle( void )\n{\n\tpred_viewangle_t\t*prev = NULL, *next = NULL;\n\tfloat\t\taddangletotal = 0.0f;\n\tfloat\t\tamove, frac = 0.0f;\n\n\tCL_FindInterpolatedAddAngle( cl.time, &frac, &prev, &next );\n\n\tif( prev && next )\n\t\taddangletotal = prev->total + frac * ( next->total - prev->total );\n\telse addangletotal = cl.prevaddangletotal;\n\n\tamove = addangletotal - cl.prevaddangletotal;\n\n\t// update input angles\n\tcl.viewangles[YAW] += amove;\n\n\t// remember last total\n\tcl.prevaddangletotal = addangletotal;\n}\n\n\n/*\n=======================================================================\n\nCLIENT MOVEMENT COMMUNICATION\n\n=======================================================================\n*/\n/*\n===============\nCL_ProcessShowTexturesCmds\n\nnavigate around texture atlas\n===============\n*/\nstatic qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd )\n{\n\tstatic int\toldbuttons;\n\tint\t\tchanged;\n\tint\t\treleased;\n\n\tif( !r_showtextures.value || CL_IsDevOverviewMode( ))\n\t\treturn false;\n\n\tchanged = (oldbuttons ^ cmd->buttons);\n\treleased = changed & (~cmd->buttons);\n\n\tif( released & ( IN_RIGHT|IN_MOVERIGHT ))\n\t\tCvar_SetValue( \"r_showtextures\", r_showtextures.value + 1 );\n\tif( released & ( IN_LEFT|IN_MOVELEFT ))\n\t\tCvar_SetValue( \"r_showtextures\", Q_max( 1, r_showtextures.value - 1 ));\n\toldbuttons = cmd->buttons;\n\n\treturn true;\n}\n\n/*\n===============\nCL_ProcessOverviewCmds\n\nTransform user movement into overview adjust\n===============\n*/\nstatic qboolean CL_ProcessOverviewCmds( usercmd_t *cmd )\n{\n\tref_overview_t\t*ov = &clgame.overView;\n\tint\t\tsign = 1;\n\tfloat\t\tsize = world.size[!ov->rotated] / world.size[ov->rotated];\n\tfloat\t\tstep = (2.0f / size) * host.realframetime;\n\tfloat\t\tstep2 = step * 100.0f * (2.0f / ov->flZoom);\n\n\tif( !CL_IsDevOverviewMode() || r_showtextures.value )\n\t\treturn false;\n\n\tif( ov->flZoom < 0.0f ) sign = -1;\n\n\tif( cmd->upmove > 0.0f ) ov->zNear += step;\n\telse if( cmd->upmove < 0.0f ) ov->zNear -= step;\n\n\tif( cmd->buttons & IN_JUMP ) ov->zFar += step;\n\telse if( cmd->buttons & IN_DUCK ) ov->zFar -= step;\n\n\tif( cmd->buttons & IN_FORWARD ) ov->origin[ov->rotated] -= sign * step2;\n\telse if( cmd->buttons & IN_BACK ) ov->origin[ov->rotated] += sign * step2;\n\n\tif( ov->rotated )\n\t{\n\t\tif( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT ))\n\t\t\tov->origin[0] -= sign * step2;\n\t\telse if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT ))\n\t\t\tov->origin[0] += sign * step2;\n\t}\n\telse\n\t{\n\t\tif( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT ))\n\t\t\tov->origin[1] += sign * step2;\n\t\telse if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT ))\n\t\t\tov->origin[1] -= sign * step2;\n\t}\n\n\tif( cmd->buttons & IN_ATTACK ) ov->flZoom += step;\n\telse if( cmd->buttons & IN_ATTACK2 ) ov->flZoom -= step;\n\n\tif( ov->flZoom == 0.0f ) ov->flZoom = 0.0001f; // to prevent disivion by zero\n\n\treturn true;\n}\n\n/*\n=================\nCL_UpdateClientData\n\ntell the client.dll about player origin, angles, fov, etc\n=================\n*/\nstatic void CL_UpdateClientData( void )\n{\n\tclient_data_t\tcdat;\n\n\tif( cls.state != ca_active )\n\t\treturn;\n\n\tmemset( &cdat, 0, sizeof( cdat ) );\n\n\tVectorCopy( cl.viewangles, cdat.viewangles );\n\tVectorCopy( clgame.entities[cl.viewentity].origin, cdat.origin );\n\tcdat.iWeaponBits = cl.local.weapons;\n\tcdat.fov = cl.local.scr_fov;\n\n\tif( clgame.dllFuncs.pfnUpdateClientData( &cdat, cl.time ))\n\t{\n\t\t// grab changes if successful\n\t\tVectorCopy( cdat.viewangles, cl.viewangles );\n\t\tcl.local.scr_fov = cdat.fov;\n\t}\n}\n\n/*\n=================\nCL_CreateCmd\n=================\n*/\nstatic void CL_CreateCmd( void )\n{\n\tusercmd_t nullcmd = { 0 }, *cmd;\n\truncmd_t  *pcmd;\n\tqboolean  active;\n\tdouble    accurate_ms;\n\tvec3_t    angles;\n\tint       input_override;\n\tint       i, ms;\n\n\tif( cls.state <= ca_connected || cls.state == ca_cinematic )\n\t\treturn;\n\n\t// store viewangles in case it's will be freeze\n\tVectorCopy( cl.viewangles, angles );\n\tinput_override = 0;\n\n\t// fix rounding error and framerate depending player move\n\taccurate_ms = host.frametime * 1000;\n\tms = (int)accurate_ms;\n\tcl.frametime_remainder += accurate_ms - ms; // accumulate rounding error each frame\n\n\t// add a ms if error accumulates enough\n\tif( cl.frametime_remainder >= 1.0 )\n\t{\n\t\tint ms2 = (int)cl.frametime_remainder;\n\n\t\tms += ms2;\n\t\tcl.frametime_remainder -= ms2;\n\t}\n\n\t// ms can't be negative, rely on error accumulation only if FPS > 1000\n\tms = Q_min( ms, 255 );\n\n\tCL_SetSolidEntities();\n\tCL_PushPMStates();\n\tCL_SetSolidPlayers( cl.playernum );\n\n\t// message we are constructing.\n\ti = cls.netchan.outgoing_sequence & CL_UPDATE_MASK;\n\tpcmd = &cl.commands[i];\n\n\tif( !cls.demoplayback )\n\t{\n\t\tpcmd->processedfuncs = false;\n\t\tpcmd->senttime = host.realtime;\n\t\tmemset( &pcmd->cmd, 0, sizeof( pcmd->cmd ));\n\t\tpcmd->receivedtime = -1.0;\n\t\tpcmd->heldback = false;\n\t\tpcmd->sendsize = 0;\n\t\tcmd = &pcmd->cmd;\n\t}\n\telse\n\t{\n\t\tcmd = &nullcmd;\n\t}\n\n\tactive = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback );\n\tPlatform_PreCreateMove();\n\tclgame.dllFuncs.CL_CreateMove( host.frametime, cmd, active );\n\tIN_EngineAppendMove( host.frametime, cmd, active );\n\n\tCL_PopPMStates();\n\n\tif( !cls.demoplayback )\n\t{\n\t\tCL_ComputeClientInterpolationAmount( &pcmd->cmd );\n\t\tpcmd->cmd.lightlevel = cl.local.light_level;\n\t\tpcmd->cmd.msec = ms;\n\t}\n\n\tinput_override |= CL_ProcessOverviewCmds( &pcmd->cmd );\n\tinput_override |= CL_ProcessShowTexturesCmds( &pcmd->cmd );\n\n\tif(( cl.background && !cls.demoplayback ) || input_override || cls.changelevel )\n\t{\n\t\tVectorCopy( angles, pcmd->cmd.viewangles );\n\t\tVectorCopy( angles, cl.viewangles );\n\t\tif( !cl.background ) pcmd->cmd.msec = 0;\n\t}\n\n\t// demo always have commands so don't overwrite them\n\tif( !cls.demoplayback ) cl.cmd = pcmd->cmd;\n\n\t// predict all unacknowledged movements\n\tCL_PredictMovement( false );\n}\n\nvoid CL_WriteUsercmd( connprotocol_t proto, sizebuf_t *msg, int from, int to )\n{\n\tconst usercmd_t nullcmd = { 0 };\n\tconst usercmd_t\t*f;\n\tusercmd_t *t;\n\n\tAssert( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP ));\n\tAssert( to >= 0 && to < MULTIPLAYER_BACKUP );\n\n\tf = from == -1 ? &nullcmd : &cl.commands[from].cmd;\n\tt = &cl.commands[to].cmd;\n\n\t// write it into the buffer\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tMSG_StartBitWriting( msg );\n\t\tDelta_WriteGSFields( msg, DT_USERCMD_T, f, t, 0.0f );\n\t\tMSG_EndBitWriting( msg );\n\t}\n\telse MSG_WriteDeltaUsercmd( msg, f, t );\n}\n\n/*\n===================\nCL_WritePacket\n\nCreate and send the command packet to the server\nIncluding both the reliable commands and the usercmds\n===================\n*/\nstatic void CL_WritePacket( void )\n{\n\tsizebuf_t buf;\n\tbyte data[MAX_CMD_BUFFER] = { 0 };\n\truncmd_t *pcmd;\n\tint numbackup, maxbackup, maxcmds;\n\tconst connprotocol_t proto = cls.legacymode;\n\n\t// FIXME: on Xash protocol we don't send move commands until ca_active\n\t// to prevent outgoing_command outrun incoming_acknowledged\n\t// which is fatal for some buggy mods like TFC\n\t//\n\t// ... but GoldSrc don't have (real) ca_validate state, so we consider\n\t// ca_validate the same as ca_active, otherwise we don't pass validation\n\t// of server-side mods like ReAuthCheck\n\tconst connstate_t min_state = proto == PROTO_GOLDSRC ? ca_validate : ca_active;\n\n\t// don't send anything if playing back a demo\n\tif( cls.demoplayback || cls.state < ca_connected || cls.state == ca_cinematic )\n\t\treturn;\n\n\tif( cls.state < min_state )\n\t{\n\t\tNetchan_TransmitBits( &cls.netchan, 0, \"\" );\n\t\treturn;\n\t}\n\t// cls.state can only be ca_validate or ca_active from here\n\n\tCL_ComputePacketLoss( );\n\n\tMSG_Init( &buf, \"ClientData\", data, sizeof( data ));\n\n\tswitch( proto )\n\t{\n\tcase PROTO_GOLDSRC:\n\t\tmaxbackup = MAX_GOLDSRC_BACKUP_CMDS;\n\t\tmaxcmds = MAX_GOLDSRC_TOTAL_CMDS;\n\t\tbreak;\n\tcase PROTO_LEGACY:\n\t\tmaxbackup = MAX_LEGACY_BACKUP_CMDS;\n\t\tmaxcmds = MAX_LEGACY_TOTAL_CMDS;\n\t\tbreak;\n\tdefault:\n\t\tmaxbackup = MAX_BACKUP_COMMANDS;\n\t\tmaxcmds = MAX_TOTAL_CMDS;\n\t\tbreak;\n\t}\n\n\tnumbackup = bound( 0, cl_cmdbackup.value, maxbackup );\n\n\t// allow extended usercmd limit\n\tif( proto == PROTO_GOLDSRC && cls.build_num >= 5971 )\n\t\tmaxcmds = MAX_GOLDSRC_EXTENDED_TOTAL_CMDS - numbackup;\n\n\t// clamp cmdrate\n\tif( cl_cmdrate.value < 10.0f )\n\t\tCvar_DirectSet( &cl_cmdrate, \"10\" );\n\telse if( cl_cmdrate.value > 100.0f )\n\t\tCvar_DirectSet( &cl_cmdrate, \"100\" );\n\n\t// are we hltv spectator?\n\tif( cls.spectator && cl.delta_sequence == cl.validsequence && ( !cls.demorecording || !cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime )\n\t\treturn;\n\n\t// can send this command?\n\tpcmd = &cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK];\n\n\tif( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal.value ) || ( host.realtime >= cls.nextcmdtime && Netchan_CanPacket( &cls.netchan, true )))\n\t\tpcmd->heldback = false;\n\telse pcmd->heldback = true;\n\n\t// immediately add it to the demo, regardless if we send the message or not\n\tif( cls.demorecording )\n\t\tCL_WriteDemoUserCmd( cls.netchan.outgoing_sequence & CL_UPDATE_MASK );\n\n\tif( !pcmd->heldback )\n\t{\n\t\tint newcmds, numcmds;\n\t\tint from, i, key;\n\t\tint packet_loss = bound( 0, (int)cls.packet_loss, 100 );\n\n\t\tcls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate.value );\n\n\t\tif( cls.lastoutgoingcommand < 0 )\n\t\t\tcls.lastoutgoingcommand = cls.netchan.outgoing_sequence;\n\n\t\tnewcmds = cls.netchan.outgoing_sequence - cls.lastoutgoingcommand;\n\t\tnewcmds = bound( 0, newcmds, maxcmds );\n\t\tnumcmds = newcmds + numbackup;\n\n\t\t// goldsrc starts writing clc_move earlier but it doesn't make sense if it's not going to be sent\n\t\tMSG_BeginClientCmd( &buf, clc_move );\n\n\t\tif( proto == PROTO_GOLDSRC )\n\t\t\tMSG_WriteByte( &buf, 0 ); // command length\n\n\t\tkey = MSG_GetRealBytesWritten( &buf );\n\t\tMSG_WriteByte( &buf, 0 );\n\n\t\tif( proto == PROTO_GOLDSRC && voice_loopback.value )\n\t\t\tSetBits( packet_loss, 7 ); // set 7-th bit to tell server that we want voice loopback\n\n\t\tMSG_WriteByte( &buf, packet_loss );\n\t\tMSG_WriteByte( &buf, numbackup );\n\t\tMSG_WriteByte( &buf, newcmds );\n\n\t\tfor( from = -1, i = numcmds - 1; i >= 0; i-- )\n\t\t{\n\t\t\tint to = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK;\n\n\t\t\tCL_WriteUsercmd( proto, &buf, from, to );\n\t\t\tfrom = to;\n\t\t}\n\n\t\t// finalize message\n\t\tif( proto == PROTO_GOLDSRC )\n\t\t{\n\t\t\tint size = MSG_GetRealBytesWritten( &buf ) - key - 1;\n\n\t\t\tbuf.pData[key - 1] = Q_min( size, 255 );\n\t\t\tbuf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );\n\t\t\tCOM_Munge( &buf.pData[key + 1], Q_min( size, 255 ), cls.netchan.outgoing_sequence );\n\t\t}\n\t\telse if( !Host_IsLocalClient( ))\n\t\t{\n\t\t\tint size = MSG_GetRealBytesWritten( &buf ) - key - 1;\n\t\t\tbuf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );\n\t\t}\n\n\t\t// check if we're timing out\n\t\tif( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged >= CL_UPDATE_MASK && host.realtime - cls.netchan.last_received >= CL_CONNECTION_TIMEOUT )\n\t\t{\n\t\t\tCon_NPrintf( 1, \"^3Warning:^1 Connection Problem^7\\n\" );\n\t\t\tCon_NPrintf( 2, \"^1Auto-disconnect in %.1f seconds^7\", cl_timeout.value - ( host.realtime - cls.netchan.last_received ));\n\t\t\tcl.validsequence = 0;\n\t\t}\n\n\t\tif( cl_nodelta.value )\n\t\t\tcl.validsequence = 0;\n\n\t\tif( cl.validsequence && ( !cls.demorecording || !cls.demowaiting ))\n\t\t{\n\t\t\tcl.delta_sequence = cl.validsequence;\n\t\t\tMSG_BeginClientCmd( &buf, clc_delta );\n\t\t\tMSG_WriteByte( &buf, cl.validsequence & 0xff );\n\t\t}\n\t\telse cl.delta_sequence = -1;\n\n\t\t// command finished, remember last sent sequence id\n\t\tcls.lastoutgoingcommand = cls.netchan.outgoing_sequence;\n\t\tpcmd->sendsize = MSG_GetNumBytesWritten( &buf );\n\n\t\tCL_AddVoiceToDatagram();\n\n\t\t// now add unreliable, if there is enough space\n\t\tif( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf ))\n\t\t\tMSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram ));\n\t\tMSG_Clear( &cls.datagram );\n\n\t\tNetchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));\n\t}\n\telse\n\t{\n\t\tcls.netchan.outgoing_sequence++;\n\t}\n\n\t// update download/upload slider.\n\tNetchan_UpdateProgress( &cls.netchan );\n}\n\n/*\n=================\nCL_SendCommand\n\nCalled every frame to builds and sends a command packet to the server.\n=================\n*/\nstatic void CL_SendCommand( void )\n{\n\t// we create commands even if a demo is playing,\n\tCL_CreateCmd();\n\n\t// clc_move, userinfo etc\n\tCL_WritePacket();\n}\n\n/*\n==================\nCL_BeginUpload_f\n==================\n*/\nstatic void CL_BeginUpload_f( void )\n{\n\tconst char\t\t*name;\n\tresource_t\tcustResource;\n\tbyte\t\t*buf = NULL;\n\tint\t\tsize = 0;\n\tbyte\t\tmd5[16];\n\n\tname = Cmd_Argv( 1 );\n\n\tif( !COM_CheckString( name ))\n\t\treturn;\n\n\tif( !cl_allow_upload.value )\n\t\treturn;\n\n\tif( Q_strlen( name ) != 36 || Q_strnicmp( name, \"!MD5\", 4 ))\n\t{\n\t\tCon_Printf( \"Ingoring upload of non-customization\\n\" );\n\t\treturn;\n\t}\n\n\tmemset( &custResource, 0, sizeof( custResource ));\n\tCOM_HexConvert( name + 4, 32, md5 );\n\n\tif( HPAK_ResourceForHash( hpk_custom_file.string, md5, &custResource ))\n\t{\n\t\tif( memcmp( md5, custResource.rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tCon_Reportf( \"Bogus data retrieved from %s, attempting to delete entry\\n\", hpk_custom_file.string );\n\t\t\tHPAK_RemoveLump( hpk_custom_file.string, &custResource );\n\t\t\treturn;\n\t\t}\n\n\t\tif( HPAK_GetDataPointer( hpk_custom_file.string, &custResource, &buf, &size ))\n\t\t{\n\t\t\tbyte\t\tmd5[16];\n\t\t\tMD5Context_t\tctx;\n\n\t\t\tmemset( &ctx, 0, sizeof( ctx ));\n\t\t\tMD5Init( &ctx );\n\t\t\tMD5Update( &ctx, buf, size );\n\t\t\tMD5Final( md5, &ctx );\n\n\t\t\tif( memcmp( custResource.rgucMD5_hash, md5, 16 ))\n\t\t\t{\n\t\t\t\tCon_Reportf( \"HPAK_AddLump called with bogus lump, md5 mismatch\\n\" );\n\t\t\t\tCon_Reportf( \"Purported:  %s\\n\", MD5_Print( custResource.rgucMD5_hash ) );\n\t\t\t\tCon_Reportf( \"Actual   :  %s\\n\", MD5_Print( md5 ) );\n\t\t\t\tCon_Reportf( \"Removing conflicting lump\\n\" );\n\t\t\t\tHPAK_RemoveLump( hpk_custom_file.string, &custResource );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( buf && size > 0 )\n\t{\n\t\tNetchan_CreateFileFragmentsFromBuffer( &cls.netchan, name, buf, size );\n\t\tNetchan_FragSend( &cls.netchan );\n\t\tMem_Free( buf );\n\t}\n}\n\n/*\n==================\nCL_Quit_f\n==================\n*/\nvoid CL_Quit_f( void )\n{\n\tCL_Disconnect();\n\tSys_Quit( \"command\" );\n}\n\n/*\n================\nCL_Drop\n\nCalled after an Host_Error was thrown\n================\n*/\nvoid CL_Drop( void )\n{\n\tif( !cls.initialized )\n\t\treturn;\n\tCL_Disconnect();\n}\n\nstatic void CL_GetCDKey( char *protinfo, size_t protinfosize )\n{\n\tbyte hash[16] = { 0 };\n\tMD5Context_t ctx = { 0 };\n\tchar key[64];\n\tint keylength;\n\n\tkeylength = Q_snprintf( key, sizeof( key ), \"%u\", COM_RandomLong( 0, 0x7ffffffe ));\n\n\tMD5Init( &ctx );\n\tMD5Update( &ctx, key, keylength );\n\tMD5Final( hash, &ctx );\n\n\tQ_strnlwr( MD5_Print( hash ), key, sizeof( key ));\n\n\tInfo_SetValueForKey( protinfo, \"cdkey\", key, protinfosize );\n}\n\nstatic void CL_WriteSteamTicket( sizebuf_t *send )\n{\n\tconst char *s;\n\tuint32_t crc;\n\tchar buf[768] = { 0 }; // setti and steamemu return 768\n\tint i = sizeof( buf );\n\n\tif( !Q_strcmp( cl_ticket_generator.string, \"null\" ))\n\t{\n\t\tMSG_WriteBytes( send, buf, 512 ); // specifically 512 bytes of zeros\n\t\treturn;\n\t}\n\n\t//if( !Q_strcmp( cl_ticket_generator.string, \"steam\" )\n\t//{\n\t//\ti = SteamBroker_InitiateGameConnection( buf, sizeof( buf ));\n\t//\tMSG_WriteBytes( send, buf, i );\n\t//\treturn;\n\t//}\n\n\ts = ID_GetMD5();\n\tCRC32_Init( &crc );\n\tCRC32_ProcessBuffer( &crc, s, Q_strlen( s ));\n\tcrc = CRC32_Final( crc );\n\ti = GenerateRevEmu2013( buf, s, crc );\n\tMSG_WriteBytes( send, buf, i );\n\n\t// RevEmu2013: pTicket[1] = revHash (low), pTicket[5] = 0x01100001 (high)\n\t*(uint32_t*)cls.steamid = LittleLong( ((uint32_t*)buf)[1] );\n\t*(uint32_t*)(cls.steamid + 4) = LittleLong( ((uint32_t*)buf)[5] );\n}\n\n/*\n=======================\nCL_SendConnectPacket\n\nWe have gotten a challenge from the server, so try and\nconnect.\n======================\n*/\nstatic void CL_SendConnectPacket( connprotocol_t proto, int challenge )\n{\n\tchar protinfo[MAX_INFO_STRING];\n\tconst char *key = ID_GetMD5();\n\tnetadr_t adr = { 0 };\n\tint input_devices;\n\tnetadrtype_t adrtype;\n\n\tprotinfo[0] = 0;\n\n\tif( !NET_StringToAdr( cls.servername, &adr ))\n\t{\n\t\tCon_Printf( \"%s: bad server address\\n\", __func__ );\n\t\tcls.connect_time = 0;\n\t\treturn;\n\t}\n\n\tadrtype = NET_NetadrType( &adr );\n\n\tif( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER );\n\n\tinput_devices = IN_CollectInputDevices();\n\tIN_LockInputDevices( adrtype != NA_LOOPBACK ? true : false );\n\n\t// GoldSrc doesn't need sv_cheats set to 0, it's handled by svc_goldsrc_sendextrainfo\n\t// it also doesn't need useragent string\n\tif( adrtype != NA_LOOPBACK && proto != PROTO_GOLDSRC )\n\t{\n\t\tCvar_SetCheatState();\n\t\tCvar_FullSet( \"sv_cheats\", \"0\", FCVAR_READ_ONLY | FCVAR_SERVER );\n\n\t\tInfo_SetValueForKeyf( protinfo, \"d\", sizeof( protinfo ),  \"%d\", input_devices );\n\t\tInfo_SetValueForKey( protinfo, \"v\", XASH_VERSION, sizeof( protinfo ) );\n\t\tInfo_SetValueForKeyf( protinfo, \"b\", sizeof( protinfo ), \"%d\", Q_buildnum( ));\n\t\tInfo_SetValueForKey( protinfo, \"o\", Q_buildos(), sizeof( protinfo ) );\n\t\tInfo_SetValueForKey( protinfo, \"a\", Q_buildarch(), sizeof( protinfo ) );\n\t}\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tconst char *name;\n\t\tsizebuf_t send;\n\t\tbyte send_buf[2048];\n\n\t\tInfo_SetValueForKey( protinfo, \"prot\", \"3\", sizeof( protinfo )); // steam auth type\n\t\tInfo_SetValueForKeyf( protinfo, \"unique\", sizeof( protinfo ), \"%i\", 0xffffffff );\n\t\tInfo_SetValueForKey( protinfo, \"raw\", \"steam\", sizeof( protinfo ));\n\t\tCL_GetCDKey( protinfo, sizeof( protinfo ));\n\n\t\t// remove keys set for legacy protocol\n\t\tInfo_RemoveKey( cls.userinfo, \"cl_maxpacket\" );\n\t\tInfo_RemoveKey( cls.userinfo, \"cl_maxpayload\" );\n\n\t\tname = Info_ValueForKey( cls.userinfo, \"name\" );\n\t\tif( cl_advertise_engine_in_name.value && Q_strnicmp( name, \"[Xash3D]\", 8 ))\n\t\t\tInfo_SetValueForKeyf( cls.userinfo, \"name\", sizeof( cls.userinfo ), \"[Xash3D]%s\", name );\n\n\t\tMSG_Init( &send, \"GoldSrcConnect\", send_buf, sizeof( send_buf ));\n\t\tMSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET );\n\t\tMSG_WriteStringf( &send, C2S_CONNECT\" %i %i \\\"%s\\\" \\\"%s\\\"\\n\",\n\t\t\tPROTOCOL_GOLDSRC_VERSION, challenge, protinfo, cls.userinfo );\n\t\tMSG_SeekToBit( &send, -8, SEEK_CUR ); // rewrite null terminator\n\t\tCL_WriteSteamTicket( &send );\n\n\t\tif( MSG_CheckOverflow( &send ))\n\t\t\tCon_Printf( S_ERROR \"%s: %s overflow!\\n\", __func__, MSG_GetName( &send ) );\n\n\t\tNET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr );\n\t\tCon_Printf( \"Trying to connect with GoldSrc 48 protocol\\n\" );\n\t}\n\telse if( proto == PROTO_LEGACY )\n\t{\n\t\tconst char *dlmax;\n\t\tint qport = Cvar_VariableInteger( \"net_qport\" );\n\n\t\t// reset nickname from cvar value\n\t\tInfo_SetValueForKey( cls.userinfo, \"name\", name.string, sizeof( cls.userinfo ));\n\n\t\t// set related userinfo keys\n\t\tdlmax = ( cl_dlmax.value >= 100 && cl_dlmax.value < 40000 ) ? cl_dlmax.string : \"1400\";\n\t\tInfo_SetValueForKey( cls.userinfo, \"cl_maxpacket\", dlmax, sizeof( cls.userinfo ));\n\n\t\tif( !COM_CheckStringEmpty( Info_ValueForKey( cls.userinfo, \"cl_maxpayload\" )))\n\t\t\tInfo_SetValueForKey( cls.userinfo, \"cl_maxpayload\", \"1000\", sizeof( cls.userinfo ) );\n\n\t\tInfo_SetValueForKey( protinfo, \"i\", key, sizeof( protinfo ));\n\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT\" %i %i %i \\\"%s\\\" %d \\\"%s\\\"\\n\",\n\t\t\tPROTOCOL_LEGACY_VERSION, qport, challenge, cls.userinfo, NET_LEGACY_EXT_SPLIT, protinfo );\n\t\tCon_Printf( \"Trying to connect with legacy protocol\\n\" );\n\t}\n\telse\n\t{\n\t\tconst char *qport = Cvar_VariableString( \"net_qport\" );\n\t\tint extensions = NET_EXT_SPLITSIZE;\n\n\t\t// reset nickname from cvar value\n\t\tInfo_SetValueForKey( cls.userinfo, \"name\", name.string, sizeof( cls.userinfo ));\n\n\t\tif( cl_dlmax.value > FRAGMENT_MAX_SIZE || cl_dlmax.value < FRAGMENT_MIN_SIZE )\n\t\t\tCvar_DirectSetValue( &cl_dlmax, FRAGMENT_DEFAULT_SIZE );\n\n\t\t// remove keys set for legacy protocol\n\t\tInfo_RemoveKey( cls.userinfo, \"cl_maxpacket\" );\n\t\tInfo_RemoveKey( cls.userinfo, \"cl_maxpayload\" );\n\n\t\tInfo_SetValueForKey( protinfo, \"uuid\", key, sizeof( protinfo ));\n\t\tInfo_SetValueForKey( protinfo, \"qport\", qport, sizeof( protinfo ));\n\t\tInfo_SetValueForKeyf( protinfo, \"ext\", sizeof( protinfo ), \"%d\", extensions);\n\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT\" %i %i \\\"%s\\\" \\\"%s\\\"\\n\", PROTOCOL_VERSION, challenge, protinfo, cls.userinfo );\n\t\tCon_Printf( \"Trying to connect with modern protocol\\n\" );\n\t}\n\n\tcls.timestart = Sys_DoubleTime();\n}\n\n/*\n=================\nCL_GetTestFragmentSize\n\nReturns bandwidth test fragment size\n=================\n*/\nstatic int CL_GetTestFragmentSize( void )\n{\n\t// const int fragmentSizes[CL_TEST_RETRIES] = { 64000, 32000, 10666, 5200, 1400 };\n\n\t// it turns out, even if we pass the bandwidth test, it doesn't mean we can use such large fragments\n\t// as a temporary solution, use smaller fragment sizes\n\tconst int fragmentSizes[CL_TEST_RETRIES] = { 1400, 1200, 1000, 800, 508 };\n\tif( cls.connect_retry >= 0 && cls.connect_retry < CL_TEST_RETRIES )\n\t\treturn bound( FRAGMENT_MIN_SIZE, fragmentSizes[cls.connect_retry], FRAGMENT_MAX_SIZE );\n\telse\n\t\treturn FRAGMENT_MIN_SIZE;\n}\n\nstatic void CL_SendGetChallenge( netadr_t to )\n{\n\t// always send GoldSrc-styled getchallenge message\n\t// Xash servers will ignore it but for GoldSrc it will help\n\t// in auto-detection\n\tNetchan_OutOfBandPrint( NS_CLIENT, to, C2S_GETCHALLENGE\" steam\\n\" );\n}\n\n/*\n=================\nCL_CheckForResend\n\nResend a connect message if the last one has timed out\n=================\n*/\nstatic void CL_CheckForResend( void )\n{\n\tnetadr_t adr;\n\tnet_gai_state_t res;\n\tfloat resendTime;\n\tqboolean bandwidthTest;\n\n\tif( cls.internetservers_wait )\n\t\tCL_SendMasterServerScanRequest();\n\n\t// if the local server is running and we aren't then connect\n\tif( cls.state == ca_disconnected && SV_Active( ))\n\t{\n\t\tcls.signon = 0;\n\t\tcls.state = ca_connecting;\n\t\tQ_strncpy( cls.servername, \"localhost\", sizeof( cls.servername ));\n\t\tNET_NetadrSetType( &cls.serveradr, NA_LOOPBACK );\n\t\tcls.legacymode = PROTO_CURRENT;\n\n\t\t// we don't need a challenge on the localhost\n\t\tCL_SendConnectPacket( PROTO_CURRENT, 0 );\n\t\treturn;\n\t}\n\n\t// resend if we haven't gotten a reply yet\n\tif( cls.demoplayback || cls.state != ca_connecting )\n\t\treturn;\n\n\tif( cl_resend.value < CL_MIN_RESEND_TIME )\n\t\tCvar_DirectSetValue( &cl_resend, CL_MIN_RESEND_TIME );\n\telse if( cl_resend.value > CL_MAX_RESEND_TIME )\n\t\tCvar_DirectSetValue( &cl_resend, CL_MAX_RESEND_TIME );\n\n\tbandwidthTest = cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && cls.connect_retry <= CL_TEST_RETRIES;\n\tresendTime = bandwidthTest ? 1.0f : cl_resend.value;\n\n\tif(( host.realtime - cls.connect_time ) < resendTime )\n\t\treturn;\n\n\tres = NET_StringToAdrNB( cls.servername, &adr, false );\n\n\tif( res == NET_EAI_NONAME )\n\t{\n\t\tCL_Disconnect();\n\t\treturn;\n\t}\n\n\tif( res == NET_EAI_AGAIN )\n\t{\n\t\tcls.connect_time = MAX_HEARTBEAT;\n\t\treturn;\n\t}\n\n\t// only retry so many times before failure.\n\tif( cls.connect_retry >= CL_CONNECTION_RETRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: couldn't connect\\n\", __func__ );\n\t\tCL_Disconnect();\n\t\treturn;\n\t}\n\n\tif( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER );\n\n\tif( cls.connect_retry == CL_TEST_RETRIES )\n\t{\n\t\t// too many fails use default connection method\n\t\tCon_Printf( \"Bandwidth test failed, fallback to default connecting method\\n\" );\n\t\tCon_Printf( \"Connecting to %s... (retry #%i)\\n\", cls.servername, cls.connect_retry + 1 );\n\t\tCL_SendGetChallenge( adr );\n\t\tCvar_DirectSetValue( &cl_dlmax, FRAGMENT_MIN_SIZE );\n\t\tcls.connect_time = host.realtime;\n\t\tcls.connect_retry++;\n\t\treturn;\n\t}\n\n\tcls.serveradr = adr;\n\tcls.max_fragment_size = CL_GetTestFragmentSize();\n\tcls.connect_time = host.realtime; // for retransmit requests\n\tcls.connect_retry++;\n\n\tif( bandwidthTest )\n\t{\n\t\tCon_Printf( \"Connecting to %s... (retry #%i, fragment size %i)\\n\", cls.servername, cls.connect_retry, cls.max_fragment_size );\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, adr, C2S_BANDWIDTHTEST\" %i %i\\n\", PROTOCOL_VERSION, cls.max_fragment_size );\n\t}\n\telse\n\t{\n\t\tCon_Printf( \"Connecting to %s... (retry #%i)\\n\", cls.servername, cls.connect_retry );\n\t\tCL_SendGetChallenge( adr );\n\t}\n}\n\nstatic resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index )\n{\n\tresource_t\t*r = &cl.resourcelist[cl.num_resources];\n\n\tif( cl.num_resources >= MAX_RESOURCES )\n\t\tHost_Error( \"Too many resources on client\\n\" );\n\tcl.num_resources++;\n\n\tQ_strncpy( r->szFileName, name, sizeof( r->szFileName ));\n\tr->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0;\n\tr->nDownloadSize = size;\n\tr->nIndex = index;\n\tr->type = type;\n\n\treturn r;\n}\n\nstatic void CL_CreateResourceList( void )\n{\n\tchar szFileName[MAX_OSPATH];\n\tbyte rgucMD5_hash[16] = { 0 };\n\tresource_t\t*pNewResource;\n\tint\t\tnSize;\n\tfile_t\t\t*fp;\n\n\tHPAK_FlushHostQueue();\n\tcl.num_resources = 0;\n\tmemset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));\n\n\t// sanitize cvar value\n\tif( Q_strcmp( cl_logoext.string, \"bmp\" ) && Q_strcmp( cl_logoext.string, \"png\" ))\n\t\tCvar_DirectSet( &cl_logoext, \"bmp\" );\n\n\tQ_snprintf( szFileName, sizeof( szFileName ), \"logos/remapped.%s\", cl_logoext.string );\n\tif( cls.legacymode == PROTO_GOLDSRC )\n\t{\n\t\tCL_ConvertImageToWAD3( szFileName );\n\t\tQ_strncpy( szFileName, \"tempdecal.wad\", sizeof( szFileName ));\n\t}\n\tfp = FS_Open( szFileName, \"rb\", true );\n\n\tif( !fp )\n\t\treturn;\n\n\tMD5_HashFile( rgucMD5_hash, szFileName, NULL );\n\tnSize = FS_FileLength( fp );\n\n\tif( nSize != 0 )\n\t{\n\t\tpNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );\n\n\t\tif( pNewResource )\n\t\t{\n\t\t\tSetBits( pNewResource->ucFlags, RES_CUSTOM );\n\t\t\tmemcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );\n\t\t\tHPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );\n\t\t}\n\t}\n\n\tFS_Close( fp );\n}\n\nstatic qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )\n{\n\tif( !Q_stricmp( s, \"current\" ) || !Q_strcmp( s, \"49\" ))\n\t{\n\t\t*proto = PROTO_CURRENT;\n\t\treturn true;\n\t}\n\n\tif( !Q_stricmp( s, \"legacy\" ) || !Q_strcmp( s, \"48\" ))\n\t{\n\t\t*proto = PROTO_LEGACY;\n\t\treturn true;\n\t}\n\n\tif( !Q_stricmp( s, \"goldsrc\" ) || !Q_stricmp( s, \"gs\" ))\n\t{\n\t\t*proto = PROTO_GOLDSRC;\n\t\treturn true;\n\t}\n\n\t// quake protocol only used for demos\n\tCon_Printf( \"Unknown protocol. Supported are: 49 (current), 48 (legacy), gs (goldsrc)\\n\" );\n\treturn false;\n}\n\n/*\n================\nCL_Connect_f\n\n================\n*/\nstatic void CL_Connect_f( void )\n{\n\tstring\tserver;\n\tconnprotocol_t proto = PROTO_CURRENT;\n\n\t// hint to connect by using legacy protocol\n\tif( Cmd_Argc() == 3 && !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ) && Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"connect <server> [protocol]\\n\" );\n\t\treturn;\n\t}\n\n\tQ_strncpy( server, Cmd_Argv( 1 ), sizeof( server ));\n\n\t// if running a local server, kill it and reissue\n\tif( SV_Active( ))\n\t\tSV_Shutdown( \"Server was killed due to connection to remote server\\n\" );\n\tNET_Config( true, !cl_nat.value ); // allow remote\n\n\tCon_Printf( \"server %s\\n\", server );\n\tCL_Disconnect();\n\n\t// TESTTEST: a see console during connection\n\tUI_SetActiveMenu( false );\n\tKey_SetKeyDest( key_console );\n\n\tcls.state = ca_connecting;\n\tcls.legacymode = proto;\n\tQ_strncpy( cls.servername, server, sizeof( cls.servername ));\n\tcls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately\n\tcls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size\n\tcls.connect_retry = 0;\n\tcls.spectator = false;\n\tcls.signon = 0;\n}\n\n/*\n=====================\nCL_Rcon_f\n\nSend the rest of the command line over as\nan unconnected command.\n=====================\n*/\nstatic void CL_Rcon_f( void )\n{\n\tchar message[1024];\n\tsizebuf_t msg;\n\tnetadr_t to;\n\tint\ti;\n\n\tif( !COM_CheckString( rcon_password.string ))\n\t{\n\t\tCon_Printf( \"You must set 'rcon_password' before issuing an rcon command.\\n\" );\n\t\treturn;\n\t}\n\n\tNET_Config( true, false );\t// allow remote\n\n\tif( cls.state >= ca_connected )\n\t{\n\t\tto = cls.netchan.remote_address;\n\t}\n\telse\n\t{\n\t\tif( !COM_CheckString( rcon_address.string ))\n\t\t{\n\t\t\tCon_Printf( \"You must either be connected or set the 'rcon_address' cvar to issue rcon commands\\n\" );\n\t\t\treturn;\n\t\t}\n\n\t\tNET_StringToAdr( rcon_address.string, &to );\n\t\tif( to.port == 0 )\n\t\t\tto.port = MSG_BigShort( PORT_SERVER );\n\t}\n\n\tMSG_Init( &msg, \"RconMessage\", message, sizeof( message ));\n\tMSG_WriteLong( &msg, -1 );\n\tMSG_WriteStringf( &msg, C2S_RCON\" %s \", rcon_password.string );\n\tMSG_SeekToBit( &msg, -8, SEEK_CUR );\n\n\tfor( i = 1; i < Cmd_Argc(); i++ )\n\t{\n\t\tstring command;\n\n\t\tCmd_Escape( command, Cmd_Argv( i ), sizeof( command ));\n\t\tMSG_WriteString( &msg, command );\n\t\tMSG_SeekToBit( &msg, -8, SEEK_CUR );\n\t\tMSG_WriteChar( &msg, ' ' );\n\t}\n\tMSG_WriteByte( &msg, 0 );\n\n\tNET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ), to );\n}\n\n\n/*\n=====================\nCL_ClearState\n\n=====================\n*/\nvoid CL_ClearState( void )\n{\n\tint\ti;\n\n\tCL_ClearResourceLists();\n\n\tfor( i = 0; i < MAX_CLIENTS; i++ )\n\t\tCOM_ClearCustomizationList( &cl.players[i].customdata, false );\n\n\tS_StopAllSounds ( true );\n\tCL_ClearEffects ();\n\tCL_FreeEdicts ();\n\n\tPM_ClearPhysEnts( clgame.pmove );\n\tNetAPI_CancelAllRequests();\n\n\t// wipe the entire cl structure\n\tmemset( &cl, 0, sizeof( cl ));\n\tMSG_Clear( &cls.netchan.message );\n\tmemset( &clgame.fade, 0, sizeof( clgame.fade ));\n\tmemset( &clgame.shake, 0, sizeof( clgame.shake ));\n\tclgame.mapname[0] = '\\0';\n\tCvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\tcl.maxclients = 1; // allow to drawing player in menu\n\tcl.mtime[0] = cl.mtime[1] = 1.0f; // because level starts from 1.0f second\n\tcls.signon = 0;\n\n\tcl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded;\n\tcl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand;\n\n\tCL_CreateResourceList();\n\tCL_ClearSpriteTextures();\t// now all hud sprites are invalid\n\n\tcl.local.interp_amount = 0.1f;\n\tcl.local.scr_fov = 90.0f;\n\n\tCvar_SetValue( \"scr_download\", -1.0f );\n\tCvar_SetValue( \"scr_loading\", 0.0f );\n\thost.allow_console = host.allow_console_init;\n\tHTTP_ClearCustomServers();\n}\n\n/*\n=====================\nCL_SendDisconnectMessage\n\nSends a disconnect message to the server\n=====================\n*/\nstatic void CL_SendDisconnectMessage( connprotocol_t proto )\n{\n\tsizebuf_t\tbuf;\n\tbyte\tdata[32];\n\n\tif( cls.state == ca_disconnected ) return;\n\n\tMSG_Init( &buf, \"LastMessage\", data, sizeof( data ));\n\tMSG_BeginClientCmd( &buf, clc_stringcmd );\n\tif( proto == PROTO_GOLDSRC )\n\t\tMSG_WriteString( &buf, \"dropclient\\n\" );\n\telse MSG_WriteString( &buf, \"disconnect\" );\n\n\tif( NET_NetadrType( &cls.netchan.remote_address ) == NA_UNDEFINED )\n\t\tNET_NetadrSetType( &cls.netchan.remote_address, NA_LOOPBACK );\n\n\t// make sure message will be delivered\n\tNetchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));\n\tNetchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));\n\tNetchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));\n}\n\nint CL_GetSplitSize( void )\n{\n\tint splitsize = (int)cl_dlmax.value;\n\n\tif( !FBitSet( cls.extensions, NET_EXT_SPLITSIZE ))\n\t\treturn 1400;\n\n\tif(( splitsize < FRAGMENT_MIN_SIZE ) || ( splitsize > FRAGMENT_MAX_SIZE ))\n\t{\n\t\tCvar_SetValue( \"cl_dlmax\", FRAGMENT_DEFAULT_SIZE );\n\t\treturn FRAGMENT_DEFAULT_SIZE;\n\t}\n\n\treturn (int)cl_dlmax.value;\n}\n\nvoid CL_SetupNetchanForProtocol( connprotocol_t proto )\n{\n\tint (*pfnBlockSize)( void *, fragsize_t ) = CL_GetFragmentSize;\n\tuint flags = 0;\n\n\tswitch( proto )\n\t{\n\tcase PROTO_GOLDSRC:\n\t\tSetBits( flags, NETCHAN_USE_MUNGE | NETCHAN_USE_BZIP2 | NETCHAN_GOLDSRC );\n\t\tpfnBlockSize = CL_GetGoldSrcFragmentSize;\n\t\tbreak;\n\tcase PROTO_LEGACY:\n\t\tif( FBitSet( Q_atoi( Cmd_Argv( 1 )), NET_LEGACY_EXT_SPLIT ))\n\t\t{\n\t\t\tSetBits( flags, NETCHAN_USE_LEGACY_SPLIT );\n\t\t\tCon_Reportf( \"^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d)\\n\", (int)cl_dlmax.value, 65536 );\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tif( !Host_IsLocalClient( ))\n\t\t\tSetBits( flags, NETCHAN_USE_LZSS );\n\n\t\tcls.extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), \"ext\" ));\n\n\t\tif( FBitSet( cls.extensions, NET_EXT_SPLITSIZE ))\n\t\t\tCon_Reportf( \"^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\\n\", (int)cl_dlmax.value );\n\t\tbreak;\n\t}\n\n\tNetchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( \"net_qport\" ), NULL, pfnBlockSize, flags );\n}\n\n/*\n=====================\nCL_Reconnect\n\nbuild a request to reconnect client\n=====================\n*/\nstatic void CL_Reconnect( qboolean setup_netchan )\n{\n\tif( setup_netchan )\n\t{\n\t\tCL_SetupNetchanForProtocol( cls.legacymode );\n\t}\n\telse\n\t{\n\t\t// clear channel and stuff\n\t\tNetchan_Clear( &cls.netchan );\n\t\tMSG_Clear( &cls.netchan.message );\n\t}\n\n\tcls.demonum = cls.movienum = -1;\t// not in the demo loop now\n\tcls.state = ca_connected;\n\tcls.signon = 0;\n\n\tCL_ServerCommand( true, \"new\" );\n\n\tcl.validsequence = 0;\t\t// haven't gotten a valid frame update yet\n\tcl.delta_sequence = -1;\t\t// we'll request a full delta from the baseline\n\tcls.lastoutgoingcommand = -1;\t\t// we don't have a backed up cmd history yet\n\tcls.nextcmdtime = host.realtime;\t// we can send a cmd right away\n\tcl.last_command_ack = -1;\n\n\tCL_StartupDemoHeader ();\n}\n\n/*\n=====================\nCL_Disconnect\n\nGoes from a connected state to full screen console state\nSends a disconnect message to the server\nThis is also called on Host_Error, so it shouldn't cause any errors\n=====================\n*/\nvoid CL_Disconnect( void )\n{\n\tif( cls.state == ca_disconnected )\n\t\treturn;\n\n\tcls.connect_time = 0;\n\tcls.changedemo = false;\n\tcls.max_fragment_size = FRAGMENT_MAX_SIZE; // reset fragment size\n\tVoice_Disconnect();\n\tCL_Stop_f();\n\n\t// send a disconnect message to the server\n\tCL_SendDisconnectMessage( cls.legacymode );\n\tCL_ClearState ();\n\n\tS_StopBackgroundTrack ();\n\tSCR_EndLoadingPlaque (); // get rid of loading plaque\n\n\t// clear the network channel, too.\n\tNetchan_Clear( &cls.netchan );\n\n\tIN_LockInputDevices( false ); // unlock input devices\n\n\tcls.state = ca_disconnected;\n\tmemset( &cls.serveradr, 0, sizeof( cls.serveradr ) );\n\tcls.set_lastdemo = false;\n\tcls.connect_retry = 0;\n\tcls.signon = 0;\n\tcls.legacymode = PROTO_CURRENT;\n\n\t// back to menu in non-developer mode\n\tif( host_developer.value || cls.key_dest == key_menu )\n\t\treturn;\n\n\tUI_SetActiveMenu( true );\n}\n\nvoid CL_Disconnect_f( void )\n{\n\tif( Host_IsLocalClient( ))\n\t\tHost_EndGame( true, \"disconnected from server\\n\" );\n\telse CL_Disconnect();\n}\n\nvoid CL_Crashed( void )\n{\n\t// already freed\n\tif( host.status == HOST_CRASHED ) return;\n\tif( host.type != HOST_NORMAL ) return;\n\tif( !cls.initialized ) return;\n\n\thost.status = HOST_CRASHED;\n\n\tCL_Stop_f(); // stop any demos\n\n\t// send a disconnect message to the server\n\tCL_SendDisconnectMessage( cls.legacymode );\n\n\tHost_WriteOpenGLConfig();\n\tHost_WriteConfig();\t// write config\n}\n\n/*\n=================\nCL_LocalServers_f\n=================\n*/\nstatic void CL_LocalServers_f( void )\n{\n\tnetadr_t adr = { 0 };\n\n\tCon_Printf( \"Scanning for servers on the local network area...\\n\" );\n\tNET_Config( true, true ); // allow remote\n\n\t// send a broadcast packet\n\tNET_NetadrSetType( &adr, NA_BROADCAST );\n\tadr.port = MSG_BigShort( PORT_SERVER );\n\tNetchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO\" %i\", PROTOCOL_VERSION );\n\n\tNET_NetadrSetType( &adr, NA_MULTICAST_IP6 );\n\tNetchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO\" %i\", PROTOCOL_VERSION );\n}\n\n/*\n=================\nCL_BuildMasterServerScanRequest\n=================\n*/\nstatic size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t *key, qboolean nat, const char *filter )\n{\n\tsize_t remaining;\n\tchar *info, temp[32];\n\n\tif( unlikely( size < sizeof( MS_SCAN_REQUEST )))\n\t\treturn 0;\n\n\tQ_strncpy( buf, MS_SCAN_REQUEST, size );\n\n\tinfo = buf + sizeof( MS_SCAN_REQUEST ) - 1;\n\tremaining = size - sizeof( MS_SCAN_REQUEST );\n\n\tQ_strncpy( info, filter, remaining );\n\n\t*key = COM_RandomLong( 0, 0x7FFFFFFF );\n\n#ifndef XASH_ALL_SERVERS\n\tInfo_SetValueForKey( info, \"gamedir\", GI->gamefolder, remaining );\n#endif\n\t// let master know about client version\n\tInfo_SetValueForKey( info, \"clver\", XASH_VERSION, remaining );\n\tInfo_SetValueForKey( info, \"nat\", nat ? \"1\" : \"0\", remaining );\n\tInfo_SetValueForKey( info, \"commit\", g_buildcommit, remaining );\n\tInfo_SetValueForKey( info, \"branch\", g_buildbranch, remaining );\n\tInfo_SetValueForKey( info, \"os\", Q_buildos(), remaining );\n\tInfo_SetValueForKey( info, \"arch\", Q_buildarch(), remaining );\n\n\tQ_snprintf( temp, sizeof( temp ), \"%d\", Q_buildnum() );\n\tInfo_SetValueForKey( info, \"buildnum\", temp, remaining );\n\n\tQ_snprintf( temp, sizeof( temp ), \"%x\", *key );\n\tInfo_SetValueForKey( info, \"key\", temp, remaining );\n\n\treturn sizeof( MS_SCAN_REQUEST ) + Q_strlen( info );\n}\n\n/*\n=================\nCL_SendMasterServerScanRequest\n=================\n*/\nstatic void CL_SendMasterServerScanRequest( void )\n{\n\tcls.internetservers_wait = NET_SendToMasters( NS_CLIENT,\n\t\tcls.internetservers_query_len, cls.internetservers_query );\n\tcls.internetservers_pending = true;\n}\n\n/*\n=================\nCL_InternetServers_f\n=================\n*/\nstatic void CL_InternetServers_f( void )\n{\n\tqboolean nat = cl_nat.value != 0.0f;\n\tuint32_t key;\n\n\tif( Cmd_Argc( ) > 2 || ( Cmd_Argc( ) == 2 && !Info_IsValid( Cmd_Argv( 1 ))))\n\t{\n\t\tCon_Printf( S_USAGE \"internetservers [filter]\\n\" );\n\t\treturn;\n\t}\n\n\tcls.internetservers_query_len = CL_BuildMasterServerScanRequest(\n\t\tcls.internetservers_query, sizeof( cls.internetservers_query ),\n\t\t&cls.internetservers_key, nat, Cmd_Argv( 1 ));\n\n\tCon_Printf( \"Scanning for servers on the internet area...\\n\" );\n\n\tNET_Config( true, true ); // allow remote\n\n\tCL_SendMasterServerScanRequest();\n}\n\nstatic void CL_QueryServer_f( void )\n{\n\tnetadr_t adr;\n\tconnprotocol_t proto;\n\n\tif( Cmd_Argc( ) != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"ui_queryserver <adr> <protocol>\\n\" );\n\t\treturn;\n\t}\n\n\tNET_Config( true, false );\n\n\tif( !NET_StringToAdr( Cmd_Argv( 1 ), &adr ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: can't parse %s\", __func__, Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\tif( adr.port == 0 )\n\t\tadr.port = PORT_SERVER;\n\n\tif( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ))\n\t\treturn;\n\n\tswitch( proto )\n\t{\n\tcase PROTO_GOLDSRC:\n\t\tNetchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator!\n\t\tbreak;\n\tcase PROTO_LEGACY:\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO\" %i\", PROTOCOL_LEGACY_VERSION );\n\t\tbreak;\n\tcase PROTO_CURRENT:\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO\" %i\", PROTOCOL_VERSION );\n\t\tbreak;\n\t}\n}\n\n/*\n=================\nCL_Reconnect_f\n\nThe server is changing levels\n=================\n*/\nstatic void CL_Reconnect_f( void )\n{\n\tif( cls.state == ca_disconnected )\n\t\treturn;\n\n\tS_StopAllSounds ( true );\n\n\tif( cls.state == ca_connected )\n\t{\n\t\tCL_Reconnect( false );\n\t\treturn;\n\t}\n\n\tif( COM_CheckString( cls.servername ))\n\t{\n\t\tconnprotocol_t proto = cls.legacymode;\n\n\t\tif( cls.state >= ca_connected )\n\t\t\tCL_Disconnect();\n\n\t\tcls.connect_time = MAX_HEARTBEAT;\t// fire immediately\n\t\tcls.demonum = cls.movienum = -1;\t// not in the demo loop now\n\t\tcls.state = ca_connecting;\n\t\tcls.signon = 0;\n\t\tcls.legacymode = proto; // don't change protocol\n\n\t\tCon_Printf( \"reconnecting...\\n\" );\n\t}\n}\n\n/*\n=================\nCL_FixupColorStringsForInfoString\n\nall the keys and values must be ends with ^7\n=================\n*/\nstatic void CL_FixupColorStringsForInfoString( const char *in, char *out, size_t len )\n{\n\tqboolean\thasPrefix = false;\n\tqboolean\tendOfKeyVal = false;\n\tint\tcolor = 7;\n\tint\tcount = 0;\n\n\tif( *in == '\\\\' )\n\t{\n\t\t*out++ = *in++;\n\t\tcount++;\n\t}\n\n\twhile( *in && count < len )\n\t{\n\t\tif( IsColorString( in ))\n\t\t\tcolor = ColorIndex( *(in+1));\n\n\t\t// color the not reset while end of key (or value) was found!\n\t\tif( *in == '\\\\' && color != 7 )\n\t\t{\n\t\t\tif( IsColorString( out - 2 ))\n\t\t\t{\n\t\t\t\t*(out - 1) = '7';\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*out++ = '^';\n\t\t\t\t*out++ = '7';\n\t\t\t\tcount += 2;\n\t\t\t}\n\t\t\tcolor = 7;\n\t\t}\n\n\t\t*out++ = *in++;\n\t\tcount++;\n\t}\n\n\t// check the remaining value\n\tif( color != 7 )\n\t{\n\t\t// if the ends with another color rewrite it\n\t\tif( IsColorString( out - 2 ))\n\t\t{\n\t\t\t*(out - 1) = '7';\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*out++ = '^';\n\t\t\t*out++ = '7';\n\t\t\tcount += 2;\n\t\t}\n\t}\n\n\t*out = '\\0';\n}\n\n/*\n=================\nCL_ParseStatusMessage\n\nHandle a reply from a info\n=================\n*/\nstatic void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg )\n{\n\tstatic char\tinfostring[512+8];\n\tchar\t\t*s = MSG_ReadString( msg );\n\tint i;\n\tconst char *magic = \": wrong version\\n\", *p;\n\tsize_t len = Q_strlen( s ), magiclen = Q_strlen( magic );\n\n\tif( len >= magiclen && !Q_strcmp( s + len - magiclen, magic ))\n\t{\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, from, A2A_INFO\" %i\", PROTOCOL_LEGACY_VERSION );\n\t\treturn;\n\t}\n\n\tif( !Info_IsValid( s ))\n\t{\n\t\tCon_Printf( \"^1Server^7: %s, invalid infostring\\n\", NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\tCL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring ));\n\n\tif( !COM_CheckString( Info_ValueForKey( infostring, \"gamedir\" )))\n\t{\n\t\tCon_Printf( \"^1Server^7: %s, Info: %s\\n\", NET_AdrToString( from ), infostring );\n\t\treturn; // unsupported proto\n\t}\n\n\tInfo_RemoveKey( infostring, \"gs\" ); // don't let servers pretend they're something else\n\n\tp = Info_ValueForKey( infostring, \"p\" );\n\tif( !COM_CheckStringEmpty( p ))\n\t{\n\t\tInfo_SetValueForKey( infostring, \"legacy\", \"1\", sizeof( infostring ));\n\t\tInfo_SetValueForKey( infostring, \"p\", \"48\", sizeof( infostring ));\n\t\tCon_Printf( \"^3Server^7: %s, Game: %s\\n\", NET_AdrToString( from ), Info_ValueForKey( infostring, \"gamedir\" ));\n\t}\n\telse if( !Q_strcmp( p, \"48\" ))\n\t{\n\t\tInfo_SetValueForKey( infostring, \"legacy\", \"1\", sizeof( infostring ));\n\t\tCon_Printf( \"^3Server^7: %s, Game: %s\\n\", NET_AdrToString( from ), Info_ValueForKey( infostring, \"gamedir\" ));\n\t}\n\telse\n\t{\n\t\t// more info about servers\n\t\tCon_Printf( \"^2Server^7: %s, Game: %s\\n\", NET_AdrToString( from ), Info_ValueForKey( infostring, \"gamedir\" ));\n\t}\n\n\tUI_AddServerToList( from, infostring );\n}\n\nstatic void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )\n{\n\tstatic char\ts[512+8];\n\tint p, numcl, maxcl, password, remaining;\n\tstring host, map, gamedir, version;\n\tconnprotocol_t proto;\n\tchar *replace;\n\n\t// set to beginning but skip header\n\tMSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );\n\n\tp = MSG_ReadByte( msg );\n\tQ_strncpy( host, MSG_ReadString( msg ), sizeof( host ));\n\tQ_strncpy( map, MSG_ReadString( msg ), sizeof( map ));\n\tQ_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));\n\tMSG_ReadString( msg ); // game description\n\tMSG_ReadShort( msg ); // app id\n\tnumcl = MSG_ReadByte( msg );\n\tmaxcl = MSG_ReadByte( msg );\n\tMSG_ReadByte( msg ); // bots count\n\tMSG_ReadByte( msg ); // dedicated\n\tMSG_ReadByte( msg ); // operating system\n\tpassword = MSG_ReadByte( msg );\n\tQ_strncpy( version, MSG_ReadString( msg ), sizeof( version ));\n\n\tif( MSG_CheckOverflow( msg ))\n\t{\n\t\tCon_Printf( \"%s: malfored info packet from %s\\n\", __func__, NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\t// time to figure out protocol\n\tif( p == PROTOCOL_VERSION )\n\t\tproto = PROTO_CURRENT;\n\telse if( p == PROTOCOL_LEGACY_VERSION )\n\t{\n\t\tif( Q_stristr( version, \"Stdio\" ))\n\t\t\tproto = PROTO_GOLDSRC;\n\t\telse\n\t\t\tproto = PROTO_LEGACY;\n\t}\n\telse\n\t{\n\t\tCon_Printf( \"%s: unsupported protocol %d from %s\\n\", __func__, p, NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\t// now construct infostring for mainui\n\tInfo_SetValueForKeyf( s, \"p\", sizeof( s ), \"%i\", proto == PROTO_CURRENT ? PROTOCOL_VERSION : PROTOCOL_LEGACY_VERSION );\n\tInfo_SetValueForKey( s, \"gs\", proto == PROTO_GOLDSRC ? \"1\" : \"0\", sizeof( s ));\n\tInfo_SetValueForKey( s, \"map\", map, sizeof( s ));\n\tInfo_SetValueForKey( s, \"dm\", \"0\", sizeof( s )); // obsolete keys\n\tInfo_SetValueForKey( s, \"team\", \"0\", sizeof( s ));\n\tInfo_SetValueForKey( s, \"coop\", \"0\", sizeof( s ));\n\tInfo_SetValueForKeyf( s, \"numcl\", sizeof( s ), \"%i\", numcl );\n\tInfo_SetValueForKeyf( s, \"maxcl\", sizeof( s ), \"%i\", maxcl );\n\tInfo_SetValueForKey( s, \"gamedir\", gamedir, sizeof( s ));\n\tInfo_SetValueForKey( s, \"password\", password ? \"1\" : \"0\", sizeof( s ));\n\n\t// write host last so we can try to cut off too long hostnames\n\t// TODO: value size limit for infostrings\n\tremaining = sizeof( s ) - Q_strlen( s ) - sizeof( \"\\\\host\\\\\" ) - 1;\n\tif( remaining < 0 )\n\t{\n\t\t// should never happen?\n\t\tCon_Printf( S_ERROR \"%s: infostring overflow!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\twhile(( replace = Q_strpbrk( host, \"\\\\\\\"\" )))\n\t{\n\t\t*replace = ' '; // find a better replacement?\n\t}\n\n\tInfo_SetValueForKey( s, \"host\", host, sizeof( s ));\n\n\tUI_AddServerToList( from, s );\n}\n\n/*\n=================\nCL_ParseNETInfoMessage\n\nHandle a reply from a netinfo\n=================\n*/\nstatic void CL_ParseNETInfoMessage( netadr_t from, const char *s )\n{\n\tnet_request_t\t*nr = NULL;\n\tstatic char\tinfostring[MAX_PRINT_MSG];\n\tint\t\ti, context, type;\n\tint\t\terrorBits = 0;\n\tconst char\t\t*val;\n\tsize_t slen;\n\n\tcontext = Q_atoi( Cmd_Argv( 1 ));\n\ttype = Q_atoi( Cmd_Argv( 2 ));\n\n\t// find request with specified context and type\n\tfor( i = 0; i < MAX_REQUESTS; i++ )\n\t{\n\t\tif( clgame.net_requests[i].resp.context == context && clgame.net_requests[i].resp.type == type )\n\t\t{\n\t\t\tnr = &clgame.net_requests[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// not found, ignore\n\tif( nr == NULL )\n\t\treturn;\n\n\t// find the payload\n\ts = Q_strchr( s, ' ' ); // skip netinfo\n\tif( !s )\n\t\treturn;\n\n\ts = Q_strchr( s + 1, ' ' ); // skip challenge\n\tif( !s )\n\t\treturn;\n\n\ts = Q_strchr( s + 1, ' ' ); // skip type\n\tif( s )\n\t\ts++; // skip final whitespace\n\telse if( type != NETAPI_REQUEST_PING ) // ping have no payload, and that's ok\n\t\treturn;\n\n\tif( s )\n\t{\n\t\tif( s[0] == '\\\\' )\n\t\t{\n\t\t\t// check for errors\n\t\t\tval = Info_ValueForKey( s, \"neterror\" );\n\n\t\t\tif( !Q_stricmp( val, \"protocol\" ))\n\t\t\t\tSetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED );\n\t\t\telse if( !Q_stricmp( val, \"undefined\" ))\n\t\t\t\tSetBits( errorBits, NET_ERROR_UNDEFINED );\n\t\t\telse if( !Q_stricmp( val, \"forbidden\" ))\n\t\t\t\tSetBits( errorBits, NET_ERROR_FORBIDDEN );\n\n\t\t\tCL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_strncpy( infostring, s, sizeof( infostring ));\n\t\t}\n\t}\n\telse\n\t{\n\t\tinfostring[0] = 0;\n\t}\n\n\t// setup the answer\n\tnr->resp.response = infostring;\n\tnr->resp.remote_address = from;\n\tnr->resp.error = NET_SUCCESS;\n\tnr->resp.ping = host.realtime - nr->timesend;\n\n\tif( nr->timeout <= host.realtime )\n\t\tSetBits( nr->resp.error, NET_ERROR_TIMEOUT );\n\tSetBits( nr->resp.error, errorBits ); // misc error bits\n\n\tnr->pfnFunc( &nr->resp );\n\n\tif( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE ))\n\t\tmemset( nr, 0, sizeof( *nr )); // done\n}\n\n/*\n=================\nCL_ProcessNetRequests\n\ncheck for timeouts\n=================\n*/\nstatic void CL_ProcessNetRequests( void )\n{\n\tnet_request_t\t*nr;\n\tint\t\ti;\n\n\t// find a request with specified context\n\tfor( i = 0; i < MAX_REQUESTS; i++ )\n\t{\n\t\tnr = &clgame.net_requests[i];\n\t\tif( !nr->pfnFunc ) continue;\t// not used\n\n\t\tif( nr->timeout <= host.realtime )\n\t\t{\n\t\t\t// setup the answer\n\t\t\tSetBits( nr->resp.error, NET_ERROR_TIMEOUT );\n\t\t\tnr->resp.ping = host.realtime - nr->timesend;\n\n\t\t\tnr->pfnFunc( &nr->resp );\n\t\t\tmemset( nr, 0, sizeof( *nr )); // done\n\t\t}\n\t}\n}\n\n//===================================================================\n/*\n===============\nCL_SetupOverviewParams\n\nGet initial overview values\n===============\n*/\nvoid CL_SetupOverviewParams( void )\n{\n\tref_overview_t\t*ov = &clgame.overView;\n\tfloat\t\tmapAspect, screenAspect, aspect;\n\n\tov->rotated = ( world.size[1] <= world.size[0] ) ? true : false;\n\n\t// calculate nearest aspect\n\tmapAspect = world.size[!ov->rotated] / world.size[ov->rotated];\n\tscreenAspect = (float)refState.width / (float)refState.height;\n\taspect = Q_max( mapAspect, screenAspect );\n\n\tov->zNear = world.maxs[2];\n\tov->zFar = world.mins[2];\n\tov->flZoom = ( 8192.0f / world.size[ov->rotated] ) / aspect;\n\n\tVectorAverage( world.mins, world.maxs, ov->origin );\n\n\tmemset( &cls.spectator_state, 0, sizeof( cls.spectator_state ));\n\n\tif( cls.spectator )\n\t{\n\t\tcls.spectator_state.playerstate.friction = 1;\n\t\tcls.spectator_state.playerstate.gravity = 1;\n\t\tcls.spectator_state.playerstate.number = cl.playernum + 1;\n\t\tcls.spectator_state.playerstate.usehull = 1;\n\t\tcls.spectator_state.playerstate.movetype = MOVETYPE_NOCLIP;\n\t\tcls.spectator_state.client.maxspeed = clgame.movevars.spectatormaxspeed;\n\t}\n}\n\n/*\n=================\nCL_IsFromConnectingServer\n\nUsed for connectionless packets, when netchan may not be ready.\n=================\n*/\nstatic qboolean CL_IsFromConnectingServer( netadr_t from )\n{\n\treturn NET_IsLocalAddress( from ) ||\n\t\tNET_CompareAdr( cls.serveradr, from );\n}\n\nstatic void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg )\n{\n\tbyte\trecv_buf[NET_MAX_FRAGMENT];\n\tdword\tcrcValue;\n\tint\trealsize;\n\tdword\tcrcValue2 = 0;\n\n\t// this message only used during connection\n\t// it doesn't make sense after client_connect\n\tif( cls.state != ca_connecting )\n\t\treturn;\n\n\tif( !CL_IsFromConnectingServer( from ))\n\t\treturn;\n\n\tcrcValue = MSG_ReadLong( msg );\n\trealsize = MSG_GetMaxBytes( msg ) - MSG_GetNumBytesRead( msg );\n\n\tif( cls.max_fragment_size != MSG_GetMaxBytes( msg ))\n\t{\n\t\tif( cls.connect_retry >= CL_TEST_RETRIES )\n\t\t{\n\t\t\t// too many fails use default connection method\n\t\t\tCon_Printf( \"hi-speed connection is failed, use default method\\n\" );\n\t\t\tCL_SendGetChallenge( from );\n\t\t\tCvar_SetValue( \"cl_dlmax\", FRAGMENT_DEFAULT_SIZE );\n\t\t\tcls.connect_time = host.realtime;\n\t\t\treturn;\n\t\t}\n\n\t\t// if we waiting more than cl_timeout or packet was trashed\n\t\tcls.connect_time = MAX_HEARTBEAT;\n\t\treturn; // just wait for a next responce\n\t}\n\n\t// reading test buffer\n\tMSG_ReadBytes( msg, recv_buf, realsize );\n\n\t// procssing the CRC\n\tCRC32_ProcessBuffer( &crcValue2, recv_buf, realsize );\n\n\tif( crcValue == crcValue2 )\n\t{\n\t\t// packet was sucessfully delivered, adjust the fragment size and get challenge\n\n\t\tCon_DPrintf( \"CRC %x is matched, get challenge, fragment size %d\\n\", crcValue, cls.max_fragment_size );\n\t\tCL_SendGetChallenge( from );\n\t\tCvar_SetValue( \"cl_dlmax\", cls.max_fragment_size );\n\t\tcls.connect_time = host.realtime;\n\t}\n\telse\n\t{\n\t\tif( cls.connect_retry >= CL_TEST_RETRIES )\n\t\t{\n\t\t\t// too many fails use default connection method\n\t\t\tCon_Printf( \"hi-speed connection is failed, use default method\\n\" );\n\t\t\tCL_SendGetChallenge( from );\n\t\t\tCvar_SetValue( \"cl_dlmax\", FRAGMENT_MIN_SIZE );\n\t\t\tcls.connect_time = host.realtime;\n\t\t\treturn;\n\t\t}\n\n\t\tMsg( \"got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d\\n\", crcValue2, crcValue, cls.max_fragment_size >> 1 );\n\n\t\t// trying the next size of packet\n\t\tcls.connect_time = MAX_HEARTBEAT;\n\t}\n}\n\nstatic void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from )\n{\n\tif( !CL_IsFromConnectingServer( from ))\n\t\treturn;\n\n\tif( cls.state == ca_connected )\n\t{\n\t\tCon_DPrintf( S_ERROR \"dup connect received. ignored\\n\");\n\t\treturn;\n\t}\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tif( Q_strcmp( c, S2C_GOLDSRC_CONNECTION ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"GoldSrc client connect expected but wasn't received, ignored\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tif( Cmd_Argc() > 4 )\n\t\t\tcls.build_num = Q_atoi( Cmd_Argv( 4 ));\n\t}\n\telse if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"GoldSrc client connect received but wasn't expected, ignored\\n\");\n\t\treturn;\n\t}\n\n\tCL_Reconnect( true );\n\tUI_SetActiveMenu( cl.background );\n}\n\nstatic void CL_Print( const char *c, const char *args, netadr_t from, sizebuf_t *msg )\n{\n\tconst char *s;\n\n\ts = c[0] == A2C_GOLDSRC_PRINT ? args + 1 : MSG_ReadString( msg );\n\n\tif( !COM_CheckStringEmpty( s ))\n\t\treturn;\n\n\tCon_Printf( \"Remote message from %s:\\n\", NET_AdrToString( from ));\n\tCon_Printf( \"%s%c\", s, s[Q_strlen( s ) - 1] != '\\n' ? '\\n' : '\\0' );\n}\n\nstatic void CL_Challenge( const char *c, netadr_t from )\n{\n\tif( cls.state != ca_connecting )\n\t\treturn;\n\n\tif( !CL_IsFromConnectingServer( from ))\n\t\treturn;\n\n\t// try to autodetect protocol by challenge response\n\tif( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE ))\n\t\tcls.legacymode = PROTO_GOLDSRC;\n\n\t// challenge from the server we are connecting to\n\tCL_SendConnectPacket( cls.legacymode, Q_atoi( Cmd_Argv( 1 )));\n}\n\nstatic void CL_ErrorMsg( const char *c, const char *args, netadr_t from, sizebuf_t *msg )\n{\n\tchar formatted_msg[MAX_VA_STRING];\n\n\tif( !CL_IsFromConnectingServer( from ))\n\t\treturn;\n\n\tif( msg != NULL && !Q_strcmp( c, S2C_ERRORMSG ))\n\t{\n\t\tconst char *s = MSG_ReadString( msg );\n\t\tQ_snprintf( formatted_msg, sizeof( formatted_msg ), \"^3Server message^7\\n%s\", s );\n\t}\n\telse if( c[0] == S2C_GOLDSRC_REJECT )\n\t{\n\t\tQ_snprintf( formatted_msg, sizeof( formatted_msg ), \"^3Server message^7\\n%s\", args + 1 );\n\t}\n\telse if( c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD )\n\t{\n\t\tif( !Q_strnicmp( &c[1], \"BADPASSWORD\", 11 ))\n\t\t\tQ_snprintf( formatted_msg, sizeof( formatted_msg ), \"^3Server message^7\\n%s\", args + 12 );\n\t\telse\n\t\t\tQ_snprintf( formatted_msg, sizeof( formatted_msg ), \"^3Server message^7\\n%s\", args + 1 );\n\t}\n\n\t// in case we're in console or it's classic mainui which doesn't support messageboxes\n\tif( !UI_IsVisible() || !UI_ShowMessageBox( formatted_msg ))\n\t\tMsg( \"%s\\n\", formatted_msg );\n\n\t// don't disconnect, errormsg is a FWGS extension and\n\t// always followed by disconnect message\n}\n\nstatic void CL_Reject( const char *c, const char *args, netadr_t from )\n{\n\t// this message only used during connection\n\t// it doesn't make sense after client_connect\n\tif( cls.state != ca_connecting )\n\t\treturn;\n\n\tif( !CL_IsFromConnectingServer( from ))\n\t\treturn;\n\n\tCL_ErrorMsg( c, args, from, NULL );\n\n\t// a disconnect message from the server, which will happen if the server\n\t// dropped the connection but it is still getting packets from us\n\tCL_Disconnect_f();\n}\n\nstatic void CL_ServerList( netadr_t from, sizebuf_t *msg )\n{\n\tif( !NET_IsMasterAdr( from ))\n\t{\n\t\tCon_Printf( S_WARN \"unexpected server list packet from %s\\n\", NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\t// check the extra header\n\tif( MSG_ReadByte( msg ) == 0x7f )\n\t{\n\t\tuint32_t key = MSG_ReadDword( msg );\n\n\t\tif( cls.internetservers_key != key )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"unexpected server list packet from %s (invalid key)\\n\", NET_AdrToString( from ));\n\t\t\treturn;\n\t\t}\n\n\t\tMSG_ReadByte( msg ); // reserved byte\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_WARN \"invalid server list packet from %s (missing extra header)\\n\", NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\t// serverlist got from masterserver\n\twhile( MSG_GetNumBitsLeft( msg ) > 8 )\n\t{\n\t\tuint8_t addr[16];\n\t\tnetadr_t servadr = { 0 };\n\n\t\tif( NET_NetadrType( &from ) == NA_IP6 ) // IPv6 master server only sends IPv6 addresses\n\t\t{\n\t\t\tMSG_ReadBytes( msg, addr, sizeof( addr ));\n\t\t\tNET_IP6BytesToNetadr( &servadr, addr );\n\t\t\tNET_NetadrSetType( &servadr, NA_IP6 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip ));\t// 4 bytes for IP\n\t\t\tNET_NetadrSetType( &servadr, NA_IP );\n\t\t}\n\t\tservadr.port = MSG_ReadShort( msg );\t\t\t// 2 bytes for Port\n\n\t\t// list is ends here\n\t\tif( !servadr.port )\n\t\t\tbreak;\n\n\t\tNET_Config( true, false ); // allow remote\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, servadr, A2A_INFO\" %i\", PROTOCOL_VERSION );\n\t}\n\n\tif( cls.internetservers_pending )\n\t{\n\t\tUI_ResetPing();\n\t\tcls.internetservers_pending = false;\n\t}\n}\n\n/*\n=================\nCL_ConnectionlessPacket\n\nResponses to broadcasts, etc\n=================\n*/\nstatic void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )\n{\n\tchar *args;\n\tconst char *c;\n\n\tMSG_Clear( msg );\n\tMSG_ReadLong( msg ); // skip the -1\n\n\targs = MSG_ReadStringLine( msg );\n\n\tCmd_TokenizeString( args );\n\tc = Cmd_Argv( 0 );\n\n\tCon_Reportf( \"%s: %s : %s\\n\", __func__, NET_AdrToString( from ), c );\n\n\t// server connection\n\tif( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))\n\t{\n\t\tCL_ClientConnect( cls.legacymode, c, from );\n\t}\n\telse if( !Q_strcmp( c, A2A_INFO ))\n\t{\n\t\tCL_ParseStatusMessage( from, msg ); // server responding to a status broadcast\n\t}\n\telse if( c[0] == S2A_GOLDSRC_INFO )\n\t{\n\t\tCL_ParseGoldSrcStatusMessage( from, msg );\n\t}\n\telse if( !Q_strcmp( c, A2A_NETINFO ))\n\t{\n\t\tCL_ParseNETInfoMessage( from, args ); // server responding to a status broadcast\n\t}\n\telse if( c[0] == A2C_GOLDSRC_PRINT || !Q_strcmp( c, A2C_PRINT ))\n\t{\n\t\tCL_Print( c, args, from, msg );\n\t}\n\telse if( !Q_strcmp( c, S2C_BANDWIDTHTEST ))\n\t{\n\t\tCL_HandleTestPacket( from, msg );\n\t}\n\telse if( !Q_strcmp( c, A2A_PING ))\n\t{\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, from, A2A_ACK );\n\t}\n\telse if( !Q_strcmp( c, A2A_GOLDSRC_PING ))\n\t{\n\t\tNetchan_OutOfBandPrint( NS_CLIENT, from, A2A_GOLDSRC_ACK );\n\t}\n\telse if( !Q_strcmp( c, A2A_ACK ) || !Q_strcmp( c, A2A_GOLDSRC_ACK ))\n\t{\n\t\t// no-op\n\t}\n\telse if( !Q_strcmp( c, S2C_CHALLENGE ) || !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE ))\n\t{\n\t\tCL_Challenge( c, from );\n\t}\n\telse if( !Q_strcmp( c, S2C_REJECT ) || c[0] == S2C_GOLDSRC_REJECT || c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD )\n\t{\n\t\tCL_Reject( c, args, from );\n\t}\n\telse if( !Q_strcmp( c, S2C_ERRORMSG ))\n\t{\n\t\tCL_ErrorMsg( c, args, from, msg );\n\t}\n\telse if( !Q_strcmp( c, M2A_SERVERSLIST ))\n\t{\n\t\tCL_ServerList( from, msg );\n\t}\n\telse\n\t{\n\t\tchar buf[MAX_SYSPATH];\n\t\tint len = sizeof( buf );\n\n\t\tif( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len ))\n\t\t{\n\t\t\t// user out of band message (must be handled in SV_ConnectionlessPacket)\n\t\t\tif( len > 0 )\n\t\t\t\tNetchan_OutOfBand( NS_SERVER, from, len, (byte *)buf );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"bad connectionless packet from %s:\\n%s\\n\", NET_AdrToString( from ), args );\n\t\t}\n\t}\n}\n\n/*\n====================\nCL_GetMessage\n\nHandles recording and playback of demos, on top of NET_ code\n====================\n*/\nstatic qboolean CL_GetMessage( byte *data, size_t *length )\n{\n\tif( cls.demoplayback )\n\t\treturn CL_DemoReadMessage( data, length );\n\n\treturn NET_GetPacket( NS_CLIENT, &net_from, data, length );\n}\n\nstatic void CL_ParseNetMessage( sizebuf_t *msg, void (*parsefn)( sizebuf_t * ))\n{\n\tcls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame\n\tCL_Parse_Debug( true ); // begin parsing\n\n\tparsefn( msg );\n\n\tcl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count;\n\tCL_Parse_Debug( false ); // done\n\n\t// we don't know if it is ok to save a demo message until\n\t// after we have parsed the frame\n\tif( !cls.demoplayback )\n\t{\n\t\tif( cls.state != ca_active )\n\t\t\tCL_WriteDemoMessage( true, cls.starting_count, msg );\n\n\t\tif( cls.demorecording && !cls.demowaiting )\n\t\t\tCL_WriteDemoMessage( false, cls.starting_count, msg );\n\t}\n}\n\n\n/*\n=================\nCL_ReadNetMessage\n=================\n*/\nstatic void CL_ReadNetMessage( void )\n{\n\tsize_t\tcurSize;\n\tvoid (*parsefn)( sizebuf_t *msg );\n\n\tswitch( cls.legacymode )\n\t{\n\tcase PROTO_LEGACY:\n\t\tparsefn = CL_ParseLegacyServerMessage;\n\t\tbreak;\n\tcase PROTO_QUAKE:\n\t\tparsefn = CL_ParseQuakeMessage;\n\t\tbreak;\n\tcase PROTO_GOLDSRC:\n\t\tparsefn = CL_ParseGoldSrcServerMessage;\n\t\tbreak;\n\tdefault:\n\t\tparsefn = CL_ParseServerMessage;\n\t\tbreak;\n\t}\n\n\twhile( CL_GetMessage( net_message_buffer, &curSize ))\n\t{\n\t\tconst int split_header = LittleLong( 0xFFFFFFFE );\n\t\tif( cls.legacymode == PROTO_LEGACY && !memcmp( &split_header, net_message_buffer, sizeof( split_header )))\n\t\t{\n\t\t\t// Will rewrite existing packet by merged\n\t\t\tif( !NetSplit_GetLong( &cls.netchan.netsplit, &net_from, net_message_buffer, &curSize ) )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tMSG_Init( &net_message, \"ServerData\", net_message_buffer, curSize );\n\n\t\t// check for connectionless packet (0xffffffff) first\n\t\tif( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 )\n\t\t{\n\t\t\tCL_ConnectionlessPacket( net_from, &net_message );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// can't be a valid sequenced packet\n\t\tif( cls.state < ca_connected ) continue;\n\n\t\tif( !cls.demoplayback )\n\t\t{\n\t\t\tif( MSG_GetMaxBytes( &net_message ) < 8 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"%s: %s:runt packet\\n\", __func__, NET_AdrToString( net_from ));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// packet from server\n\t\t\tif( !NET_CompareAdr( net_from, cls.netchan.remote_address ))\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_ERROR \"%s: %s:sequenced packet without connection\\n\", __func__, NET_AdrToString( net_from ));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif( !Netchan_Process( &cls.netchan, &net_message ))\n\t\t\t\tcontinue;\t// wasn't accepted for some reason\n\t\t}\n\n\t\tif( cls.state == ca_active )\n\t\t{\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false;\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] );\n\t\t}\n\n\t\tCL_ParseNetMessage( &net_message, parsefn );\n\t}\n\n\t// build list of all solid entities per next frame (exclude clients)\n\tCL_SetSolidEntities();\n\n\t// check for fragmentation/reassembly related packets.\n\tif( cls.state != ca_disconnected && Netchan_IncomingReady( &cls.netchan ))\n\t{\n\t\t// process the incoming buffer(s)\n\t\tif( Netchan_CopyNormalFragments( &cls.netchan, &net_message, &curSize ))\n\t\t{\n\t\t\tMSG_Init( &net_message, \"ServerData\", net_message_buffer, curSize );\n\t\t\tCL_ParseNetMessage( &net_message, parsefn );\n\t\t}\n\n\t\tif( Netchan_CopyFileFragments( &cls.netchan, &net_message ))\n\t\t{\n\t\t\t// remove from resource request stuff.\n\t\t\tCL_ProcessFile( true, cls.netchan.incomingfilename );\n\t\t}\n\t}\n\n\tNetchan_UpdateProgress( &cls.netchan );\n\n\t// check requests for time-expire\n\tCL_ProcessNetRequests();\n}\n\n/*\n=================\nCL_ReadPackets\n\nUpdates the local time and reads/handles messages\non client net connection.\n=================\n*/\nstatic void CL_ReadPackets( void )\n{\n\t// decide the simulation time\n\tcl.oldtime = cl.time;\n\n\tif( !cl.paused )\n\t\tcl.time += host.frametime;\n\n\t// demo time\n\tif( cls.demorecording && !cls.demowaiting )\n\t\tcls.demotime += host.frametime;\n\n\tCL_ReadNetMessage();\n\n\tCL_ApplyAddAngle();\n#if 0\n\t// keep cheat cvars are unchanged\n\tif( cl.maxclients > 1 && cls.state == ca_active && !host_developer.value )\n\t\tCvar_SetCheatState();\n#endif\n\t// hot precache and downloading resources\n\tif( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime )\n\t{\n\t\tdouble checktime = Host_IsLocalGame() ? 0.1 : 1.0;\n\n\t\tif( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded )\n\t\t{\n\t\t\t// check resource for downloading and precache\n\t\t\tCL_EstimateNeededResources();\n\t\t\tCL_BatchResourceRequest( false );\n\t\t\tcls.dl.doneregistering = false;\n\t\t\tcls.dl.custom = true;\n\t\t}\n\n\t\tcl.lastresourcecheck = host.realtime + checktime;\n\t}\n\n\t// singleplayer never has connection timeout\n\tif( NET_IsLocalAddress( cls.netchan.remote_address ))\n\t\treturn;\n\n\t// if in the debugger last frame, don't timeout\n\tif( host.frametime > 5.0f ) cls.netchan.last_received = Sys_DoubleTime();\n\n\t// check timeout\n\tif( cls.state >= ca_connected && cls.state != ca_cinematic && !cls.demoplayback )\n\t{\n\t\tif( host.realtime - cls.netchan.last_received > cl_timeout.value )\n\t\t{\n\t\t\tCon_Printf( \"\\nServer connection timed out.\\n\" );\n\t\t\tCL_Disconnect();\n\t\t\treturn;\n\t\t}\n\t}\n\n}\n\n/*\n====================\nCL_CleanFileName\n\nReplace the displayed name for some resources\n====================\n*/\nstatic const char *CL_CleanFileName( const char *filename )\n{\n\tif( COM_CheckString( filename ) && filename[0] == '!' )\n\t\treturn \"customization\";\n\n\treturn filename;\n}\n\n\n/*\n====================\nCL_RegisterCustomization\n\nregister custom resource for player\n====================\n*/\nstatic void CL_RegisterCustomization( resource_t *resource )\n{\n\tqboolean\t\tbFound = false;\n\tcustomization_t\t*pList;\n\n\tfor( pList = cl.players[resource->playernum].customdata.pNext; pList; pList = pList->pNext )\n\t{\n\t\tif( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tbFound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !bFound )\n\t{\n\t\tplayer_info_t\t*player =  &cl.players[resource->playernum];\n\n\t\tif( !COM_CreateCustomization( &player->customdata, resource, resource->playernum, FCUST_FROMHPAK, NULL, NULL ))\n\t\t\tCon_Printf( \"Unable to create custom decal for player %i\\n\", resource->playernum );\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( \"Duplicate resource received and ignored.\\n\" );\n\t}\n}\n\n/*\n====================\nCL_ProcessFile\n\nA file has been received via the fragmentation/reassembly layer, put it in the right spot and\n see if we have finished downloading files.\n====================\n*/\nvoid CL_ProcessFile( qboolean successfully_received, const char *filename )\n{\n\tint\t\tsound_len = sizeof( DEFAULT_SOUNDPATH ) - 1;\n\tbyte\t\trgucMD5_hash[16];\n\tresource_t\t*p;\n\n\tif( COM_CheckString( filename ) && successfully_received )\n\t{\n\t\tif( filename[0] != '!' )\n\t\t\tCon_Printf( \"processing %s\\n\", filename );\n\n\t\tif( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 ))\n\t\t{\n\t\t\t// skip \"downloaded/\" part to avoid mismatch with needed resources list\n\t\t\tfilename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1;\n\t\t}\n\t}\n\telse if( !successfully_received )\n\t{\n\t\tCon_Printf( S_ERROR \"server failed to transmit file '%s'\\n\", CL_CleanFileName( filename ));\n\t}\n\n\tif( cls.legacymode == PROTO_LEGACY )\n\t{\n\t\tif( host.downloadcount > 0 )\n\t\t\thost.downloadcount--;\n\n\t\tif( !host.downloadcount )\n\t\t{\n\t\t\tMSG_WriteByte( &cls.netchan.message, clc_stringcmd );\n\t\t\tMSG_WriteString( &cls.netchan.message, \"continueloading\" );\n\t\t}\n\t\treturn;\n\t}\n\n\tfor( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )\n\t{\n\t\tif( !Q_strnicmp( filename, \"!MD5\", 4 ))\n\t\t{\n\t\t\tCOM_HexConvert( filename + 4, 32, rgucMD5_hash );\n\n\t\t\tif( !memcmp( p->rgucMD5_hash, rgucMD5_hash, 16 ))\n\t\t\t\tbreak;\n\t\t}\n\t\telse if( p->type == t_sound )\n\t\t{\n\t\t\tconst char *pfilename = filename;\n\n\t\t\tif( !Q_strnicmp( filename, DEFAULT_SOUNDPATH, sound_len ))\n\t\t\t\tpfilename += sound_len;\n\n\t\t\tif( !Q_stricmp( p->szFileName, pfilename ))\n\t\t\t\tbreak;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !Q_stricmp( p->szFileName, filename ))\n\t\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( p != &cl.resourcesneeded )\n\t{\n\t\tif( successfully_received )\n\t\t\tClearBits( p->ucFlags, RES_WASMISSING );\n\n\t\tif( filename[0] == '!' )\n\t\t{\n\t\t\tif( cls.netchan.tempbuffer )\n\t\t\t{\n\t\t\t\tif( p->nDownloadSize == cls.netchan.tempbuffersize )\n\t\t\t\t{\n\t\t\t\t\tif( p->ucFlags & RES_CUSTOM )\n\t\t\t\t\t{\n\t\t\t\t\t\tHPAK_AddLump( true, hpk_custom_file.string, p, cls.netchan.tempbuffer, NULL );\n\t\t\t\t\t\tCL_RegisterCustomization( p );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( \"Downloaded %i bytes for purported %i byte file, ignoring download\\n\",\n\t\t\t\t\t\tcls.netchan.tempbuffersize, p->nDownloadSize );\n\t\t\t\t}\n\n\t\t\t\tif( cls.netchan.tempbuffer )\n\t\t\t\t\tMem_Free( cls.netchan.tempbuffer );\n\t\t\t}\n\n\t\t\tcls.netchan.tempbuffersize = 0;\n\t\t\tcls.netchan.tempbuffer = NULL;\n\t\t}\n\n\t\t// moving to 'onhandle' list even if file was missed\n\t\tCL_MoveToOnHandList( p );\n\t}\n\n\tif( cls.state != ca_disconnected )\n\t{\n\t\thost.downloadcount = 0;\n\n\t\tfor( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )\n\t\t\thost.downloadcount++;\n\n\t\tif( cl.resourcesneeded.pNext == &cl.resourcesneeded )\n\t\t{\n\t\t\tbyte\tmsg_buf[MAX_INIT_MSG];\n\t\t\tsizebuf_t msg;\n\n\t\t\tMSG_Init( &msg, \"Resource Registration\", msg_buf, sizeof( msg_buf ));\n\n\t\t\tif( CL_PrecacheResources( ))\n\t\t\t\tCL_RegisterResources( &msg, cls.legacymode );\n\n\t\t\tif( MSG_GetNumBytesWritten( &msg ) > 0 )\n\t\t\t{\n\t\t\t\tNetchan_CreateFragments( &cls.netchan, &msg );\n\t\t\t\tNetchan_FragSend( &cls.netchan );\n\t\t\t}\n\t\t}\n\n\t\tif( cls.netchan.tempbuffer )\n\t\t{\n\t\t\tCon_Printf( \"Received a decal %s, but didn't find it in resources needed list!\\n\", filename );\n\t\t\tMem_Free( cls.netchan.tempbuffer );\n\t\t}\n\n\t\tcls.netchan.tempbuffer = NULL;\n\t\tcls.netchan.tempbuffersize = 0;\n\t}\n}\n\n/*\n====================\nCL_ServerCommand\n\nsend command to a server\n====================\n*/\nvoid CL_ServerCommand( qboolean reliable, const char *fmt, ... )\n{\n\tchar\t\tstring[MAX_SYSPATH];\n\tva_list\t\targptr;\n\n\tif( cls.state < ca_connecting )\n\t\treturn;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( string, sizeof( string ), fmt, argptr );\n\tva_end( argptr );\n\n\tif( reliable )\n\t{\n\t\tMSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd );\n\t\tMSG_WriteString( &cls.netchan.message, string );\n\t}\n\telse\n\t{\n\t\tMSG_BeginClientCmd( &cls.datagram, clc_stringcmd );\n\t\tMSG_WriteString( &cls.datagram, string );\n\t}\n}\n\n/*\n===============\nCL_UpdateInfo\n\ntell server about changed userinfo\n===============\n*/\nvoid CL_UpdateInfo( const char *key, const char *value )\n{\n\tswitch( cls.legacymode )\n\t{\n\tcase PROTO_LEGACY:\n\t\tif( cls.state != ca_active )\n\t\t\tbreak;\n\n\t\tMSG_BeginClientCmd( &cls.netchan.message, clc_legacy_userinfo );\n\t\tMSG_WriteString( &cls.netchan.message, cls.userinfo );\n\t\tbreak;\n\tcase PROTO_GOLDSRC:\n\t\tif( cl_advertise_engine_in_name.value && !Q_stricmp( key, \"name\" ) && Q_strnicmp( value, \"[Xash3D]\", 8 ))\n\t\t{\n\t\t\tCL_ServerCommand( true, \"setinfo \\\"%s\\\" \\\"[Xash3D]%s\\\"\\n\", key, value );\n\t\t\tbreak;\n\t\t}\n\t\t// intentional fallthrough\n\tdefault:\n\t\tCL_ServerCommand( true, \"setinfo \\\"%s\\\" \\\"%s\\\"\\n\", key, value );\n\t\tbreak;\n\t}\n}\n\n//=============================================================================\n/*\n==============\nCL_SetInfo_f\n==============\n*/\nstatic void CL_SetInfo_f( void )\n{\n\tconvar_t\t*var;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( \"User info settings:\\n\" );\n\t\tInfo_Print( cls.userinfo );\n\t\tCon_Printf( \"Total %zu symbols\\n\", Q_strlen( cls.userinfo ));\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"setinfo [ <key> <value> ]\\n\" );\n\t\treturn;\n\t}\n\n\t// NOTE: some userinfo comed from cvars, e.g. cl_lw but we can call \"setinfo cl_lw 1\"\n\t// without real cvar changing. So we need to lookup for cvar first to make sure what\n\t// our key is not linked with console variable\n\tvar = Cvar_FindVar( Cmd_Argv( 1 ));\n\n\t// make sure what cvar is existed and really part of userinfo\n\tif( var && FBitSet( var->flags, FCVAR_USERINFO ))\n\t{\n\t\tCvar_DirectSet( var, Cmd_Argv( 2 ));\n\t}\n\telse if( Info_SetValueForKey( cls.userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), sizeof( cls.userinfo )))\n\t{\n\t\t// send update only on successfully changed userinfo\n\t\tCmd_ForwardToServer ();\n\t}\n}\n\n/*\n==============\nCL_Physinfo_f\n==============\n*/\nstatic void CL_Physinfo_f( void )\n{\n\tCon_Printf( \"Phys info settings:\\n\" );\n\tInfo_Print( cls.physinfo );\n\tCon_Printf( \"Total %zu symbols\\n\", Q_strlen( cls.physinfo ));\n}\n\nstatic qboolean CL_ShouldRescanFilesystem( void )\n{\n\tresource_t *res;\n\tqboolean retval = false;\n\n\tfor( res = cl.resourcesonhand.pNext; res && res != &cl.resourcesonhand; res = res->pNext )\n\t{\n\t\tif( res->type == t_generic )\n\t\t{\n\t\t\tconst char *ext = COM_FileExtension( res->szFileName );\n\n\t\t\tif( !g_fsapi.IsArchiveExtensionSupported( ext, IAES_ONLY_REAL_ARCHIVES ))\n\t\t\t\tcontinue;\n\n\t\t\tif( FBitSet( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED ))\n\t\t\t\tcontinue;\n\n\t\t\tSetBits( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED );\n\t\t\tretval = true;\n\t\t}\n\t}\n\n\treturn retval;\n}\n\nqboolean CL_PrecacheResources( void )\n{\n\tresource_t\t*pRes;\n\n\t// if we downloaded new WAD files or any other archives they must be added to searchpath\n\tif( CL_ShouldRescanFilesystem( ))\n\t\tFS_Rescan_f();\n\n\t// NOTE: world need to be loaded as first model\n\tfor( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext )\n\t{\n\t\tif( FBitSet( pRes->ucFlags, RES_PRECACHED ))\n\t\t\tcontinue;\n\n\t\tif( pRes->type != t_model || pRes->nIndex != WORLD_INDEX )\n\t\t\tcontinue;\n\n\t\tcl.models[pRes->nIndex] = Mod_LoadWorld( pRes->szFileName, true );\n\t\tSetBits( pRes->ucFlags, RES_PRECACHED );\n\t\tcl.nummodels = 1;\n\t\tbreak;\n\t}\n\n\t// then we set up all the world submodels\n\tfor( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext )\n\t{\n\t\tif( FBitSet( pRes->ucFlags, RES_PRECACHED ))\n\t\t\tcontinue;\n\n\t\tif( pRes->type == t_model && pRes->szFileName[0] == '*' )\n\t\t{\n\t\t\tcl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, false );\n\t\t\tcl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 );\n\t\t\tSetBits( pRes->ucFlags, RES_PRECACHED );\n\n\t\t\tif( cl.models[pRes->nIndex] == NULL )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"submodel %s not found\\n\", pRes->szFileName );\n\n\t\t\t\tif( FBitSet( pRes->ucFlags, RES_FATALIFMISSING ))\n\t\t\t\t{\n\t\t\t\t\tCL_Disconnect_f();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif( cls.state != ca_active )\n\t\tS_BeginRegistration();\n\n\t// precache all the remaining resources where order is doesn't matter\n\tfor( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext )\n\t{\n\t\tif( FBitSet( pRes->ucFlags, RES_PRECACHED ))\n\t\t\tcontinue;\n\n\t\tswitch( pRes->type )\n\t\t{\n\t\tcase t_sound:\n\t\t\tif( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.sound_precache ) && pRes->nIndex < ARRAYSIZE( cl.sound_index ))\n\t\t\t{\n\t\t\t\tif( FBitSet( pRes->ucFlags, RES_WASMISSING ))\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( S_ERROR \"Could not load sound \" DEFAULT_SOUNDPATH \"%s\\n\", pRes->szFileName );\n\t\t\t\t\tcl.sound_precache[pRes->nIndex][0] = 0;\n\t\t\t\t\tcl.sound_index[pRes->nIndex] = 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tQ_strncpy( cl.sound_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.sound_precache[0] ));\n\t\t\t\t\tcl.sound_index[pRes->nIndex] = S_RegisterSound( pRes->szFileName );\n\n\t\t\t\t\tif( !cl.sound_index[pRes->nIndex] )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( FBitSet( pRes->ucFlags, RES_FATALIFMISSING ))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tS_EndRegistration();\n\t\t\t\t\t\t\tCL_Disconnect_f();\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// client sounds\n\t\t\t\tS_RegisterSound( pRes->szFileName );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_skin:\n\t\t\tbreak;\n\t\tcase t_model:\n\t\t\tif( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.models ))\n\t\t\t{\n\t\t\t\tcl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 );\n\t\t\t\tif( pRes->szFileName[0] != '*' )\n\t\t\t\t{\n\t\t\t\t\tif( pRes->nIndex != -1 )\n\t\t\t\t\t{\n\t\t\t\t\t\tcl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, true );\n\n\t\t\t\t\t\tif( cl.models[pRes->nIndex] == NULL )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif( FBitSet( pRes->ucFlags, RES_FATALIFMISSING ))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tS_EndRegistration();\n\t\t\t\t\t\t\t\tCL_Disconnect_f();\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tCL_LoadClientSprite( pRes->szFileName );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_decal:\n\t\t\tif( !FBitSet( pRes->ucFlags, RES_CUSTOM ) && pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( host.draw_decals ))\n\t\t\t\tQ_strncpy( host.draw_decals[pRes->nIndex], pRes->szFileName, sizeof( host.draw_decals[0] ));\n\t\t\tbreak;\n\t\tcase t_generic:\n\t\t\tif( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.files_precache ))\n\t\t\t{\n\t\t\t\tQ_strncpy( cl.files_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.files_precache[0] ));\n\t\t\t\tcl.numfiles = Q_max( cl.numfiles, pRes->nIndex + 1 );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_eventscript:\n\t\t\tif( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.event_precache ))\n\t\t\t{\n\t\t\t\tQ_strncpy( cl.event_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.event_precache[0] ));\n\t\t\t\tCL_SetEventIndex( cl.event_precache[pRes->nIndex], pRes->nIndex );\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\tSetBits( pRes->ucFlags, RES_PRECACHED );\n\t}\n\n\t// make sure modelcount is in-range\n\tcl.nummodels = bound( 0, cl.nummodels, MAX_MODELS );\n\tcl.numfiles = bound( 0, cl.numfiles, MAX_CUSTOM );\n\n\tif( cls.state != ca_active )\n\t\tS_EndRegistration();\n\n\treturn true;\n}\n\n/*\n==================\nCL_FullServerinfo_f\n\nSent by server when serverinfo changes\n==================\n*/\nstatic void CL_FullServerinfo_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"fullserverinfo <complete info string>\\n\" );\n\t\treturn;\n\t}\n\n\tQ_strncpy( cl.serverinfo, Cmd_Argv( 1 ), sizeof( cl.serverinfo ));\n}\n\n/*\n=================\nCL_Escape_f\n\nEscape to menu from game\n=================\n*/\nstatic void CL_Escape_f( void )\n{\n\tif( cls.key_dest == key_menu )\n\t\treturn;\n\n\t// the final credits is running\n\tif( UI_CreditsActive( )) return;\n\n\tif( cls.state == ca_cinematic )\n\t\tSCR_NextMovie(); // jump to next movie\n\telse UI_SetActiveMenu( true );\n}\n\nstatic void CL_ListMessages_f( void )\n{\n\tint i;\n\n\tCon_Printf( \"num size name\\n\" );\n\tfor( i = 0; i < MAX_USER_MESSAGES; i++ )\n\t{\n\t\tif( !COM_CheckStringEmpty( clgame.msg[i].name ))\n\t\t\tbreak;\n\n\t\tCon_Printf( \"%3d\\t%3d\\t%s\\n\", clgame.msg[i].number, clgame.msg[i].size, clgame.msg[i].name );\n\t}\n\n\tCon_Printf( \"Total %i messages\\n\", i );\n}\n\n/*\n=================\nCL_InitLocal\n=================\n*/\nstatic void CL_InitLocal( void )\n{\n\tcls.state = ca_disconnected;\n\tcls.signon = 0;\n\tmemset( &cls.serveradr, 0, sizeof( cls.serveradr ) );\n\n\tcl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded;\n\tcl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand;\n\n\tCvar_RegisterVariable( &cl_ticket_generator );\n\tCvar_RegisterVariable( &cl_advertise_engine_in_name );\n\n\tCvar_RegisterVariable( &showpause );\n\tCvar_RegisterVariable( &mp_decals );\n\tCvar_RegisterVariable( &dev_overview );\n\tCvar_RegisterVariable( &cl_resend );\n\tCvar_RegisterVariable( &cl_allow_upload );\n\tCvar_RegisterVariable( &cl_allow_download );\n\tCvar_RegisterVariable( &cl_download_ingame );\n\tCvar_RegisterVariable( &cl_logofile );\n\tCvar_RegisterVariable( &cl_logocolor );\n\tCvar_RegisterVariable( &cl_logoext );\n\tCvar_RegisterVariable( &cl_logomaxdim );\n\tCvar_RegisterVariable( &cl_test_bandwidth );\n\n\tVoice_RegisterCvars();\n\tVGui_RegisterCvars();\n\n\t// register our variables\n\tCvar_RegisterVariable( &cl_crosshair );\n\tCvar_RegisterVariable( &cl_nodelta );\n\tCvar_RegisterVariable( &cl_idealpitchscale );\n\tCvar_RegisterVariable( &cl_solid_players );\n\tCvar_RegisterVariable( &cl_interp );\n\tCvar_RegisterVariable( &cl_timeout );\n\tCvar_RegisterVariable( &cl_charset );\n\tCvar_RegisterVariable( &hud_utf8 );\n\n\tCvar_RegisterVariable( &rcon_address );\n\n\tCvar_RegisterVariable( &cl_trace_consistency );\n\tCvar_RegisterVariable( &cl_trace_stufftext );\n\tCvar_RegisterVariable( &cl_trace_messages );\n\tCvar_RegisterVariable( &cl_trace_events );\n\n\t// userinfo\n\tCvar_RegisterVariable( &cl_nopred );\n\tQ_strncpy( username, Sys_GetCurrentUser(), sizeof( username ));\t// initialize before registering variable\n\tCvar_RegisterVariable( &name );\n\tCvar_Get( \"ui_username\", username, FCVAR_READ_ONLY|FCVAR_PRIVILEGED, \"default user name\" );\n\tCvar_RegisterVariable( &model );\n\tCvar_RegisterVariable( &cl_updaterate );\n\tCvar_RegisterVariable( &cl_dlmax );\n\tCvar_RegisterVariable( &cl_upmax );\n\tCvar_RegisterVariable( &cl_nat );\n\tCvar_RegisterVariable( &rate );\n\tCvar_RegisterVariable( &topcolor );\n\tCvar_RegisterVariable( &bottomcolor );\n\tCvar_RegisterVariable( &cl_lw );\n\tCvar_Get( \"cl_lc\", \"1\", FCVAR_ARCHIVE|FCVAR_USERINFO, \"enable lag compensation\" );\n\tCvar_Get( \"password\", \"\", FCVAR_USERINFO, \"server password\" );\n\tCvar_Get( \"team\", \"\", FCVAR_USERINFO, \"player team\" );\n\tCvar_Get( \"skin\", \"\", FCVAR_USERINFO, \"player skin\" );\n\n\tCvar_RegisterVariable( &cl_nosmooth );\n\tCvar_RegisterVariable( &cl_nointerp );\n\tCvar_RegisterVariable( &cl_smoothtime );\n\tCvar_RegisterVariable( &cl_cmdbackup );\n\tCvar_RegisterVariable( &cl_cmdrate );\n\tCvar_RegisterVariable( &cl_draw_particles );\n\tCvar_RegisterVariable( &cl_draw_tracers );\n\tCvar_RegisterVariable( &cl_draw_beams );\n\tCvar_RegisterVariable( &cl_lightstyle_lerping );\n\tCvar_RegisterVariable( &cl_showerror );\n\tCvar_RegisterVariable( &cl_bmodelinterp );\n\tCvar_RegisterVariable( &cl_clockreset );\n\tCvar_RegisterVariable( &cl_fixtimerate );\n\tCvar_RegisterVariable( &hud_fontscale );\n\tCvar_RegisterVariable( &hud_fontrender );\n\tCvar_RegisterVariable( &hud_scale );\n\tCvar_RegisterVariable( &hud_scale_minimal_width );\n\tCvar_Get( \"cl_background\", \"0\", FCVAR_READ_ONLY, \"indicate what background map is running\" );\n\tCvar_RegisterVariable( &cl_showevents );\n\tCvar_Get( \"lastdemo\", \"\", FCVAR_ARCHIVE, \"last played demo\" );\n\tCvar_RegisterVariable( &ui_renderworld );\n\tCvar_RegisterVariable( &cl_maxframetime );\n\tCvar_RegisterVariable( &cl_fixmodelinterpolationartifacts );\n\n\t// server commands\n\tCmd_AddCommand (\"noclip\", NULL, \"enable or disable no clipping mode\" );\n\tCmd_AddCommand (\"notarget\", NULL, \"notarget mode (monsters do not see you)\" );\n\tCmd_AddCommand (\"fullupdate\", NULL, \"re-init HUD on start demo recording\" );\n\tCmd_AddCommand (\"give\", NULL, \"give specified item or weapon\" );\n\tCmd_AddCommand (\"drop\", NULL, \"drop current/specified item or weapon\" );\n\tCmd_AddCommand (\"gametitle\", NULL, \"show game logo\" );\n\tCmd_AddRestrictedCommand (\"kill\", NULL, \"die instantly\" );\n\tCmd_AddCommand (\"god\", NULL, \"enable godmode\" );\n\tCmd_AddCommand (\"fov\", NULL, \"set client field of view\" );\n\n\tCmd_AddRestrictedCommand (\"ent_list\", NULL, \"list entities on server\" );\n\tCmd_AddRestrictedCommand (\"ent_fire\", NULL, \"fire entity command (be careful)\" );\n\tCmd_AddRestrictedCommand (\"ent_info\", NULL, \"dump entity information\" );\n\tCmd_AddRestrictedCommand (\"ent_create\", NULL, \"create entity with specified values (be careful)\" );\n\tCmd_AddRestrictedCommand (\"ent_getvars\", NULL, \"put parameters of specified entities to client's' ent_last_* cvars\" );\n\n\t// register our commands\n\tCmd_AddCommand (\"pause\", NULL, \"pause the game (if the server allows pausing)\" );\n\tCmd_AddRestrictedCommand( \"localservers\", CL_LocalServers_f, \"collect info about local servers\" );\n\tCmd_AddRestrictedCommand( \"internetservers\", CL_InternetServers_f, \"collect info about internet servers\" );\n\tCmd_AddRestrictedCommand( \"ui_queryserver\", CL_QueryServer_f, \"query server info from console\" );\n\tCmd_AddCommand (\"cd\", CL_PlayCDTrack_f, \"Play cd-track (not real cd-player of course)\" );\n\tCmd_AddCommand (\"mp3\", CL_PlayCDTrack_f, \"Play mp3-track (based on virtual cd-player)\" );\n\tCmd_AddCommand (\"waveplaylen\", CL_WavePlayLen_f, \"Get approximate length of wave file\");\n\n\tCmd_AddRestrictedCommand (\"setinfo\", CL_SetInfo_f, \"examine or change the userinfo string (alias of userinfo)\" );\n\tCmd_AddRestrictedCommand (\"userinfo\", CL_SetInfo_f, \"examine or change the userinfo string (alias of setinfo)\" );\n\tCmd_AddCommand (\"physinfo\", CL_Physinfo_f, \"print current client physinfo\" );\n\tCmd_AddCommand (\"disconnect\", CL_Disconnect_f, \"disconnect from server\" );\n\tCmd_AddRestrictedCommand( \"record\", CL_Record_f, \"record a demo\" );\n\tCmd_AddCommand (\"playdemo\", CL_PlayDemo_f, \"play a demo\" );\n\tCmd_AddCommand (\"timedemo\", CL_TimeDemo_f, \"demo benchmark\" );\n\tCmd_AddRestrictedCommand( \"killdemo\", CL_DeleteDemo_f, \"delete a specified demo file\" );\n\tCmd_AddCommand (\"startdemos\", CL_StartDemos_f, \"start playing back the selected demos sequentially\" );\n\tCmd_AddCommand (\"demos\", CL_Demos_f, \"restart looping demos defined by the last startdemos command\" );\n\tCmd_AddCommand (\"movie\", CL_PlayVideo_f, \"play a movie\" );\n\tCmd_AddCommand (\"stop\", CL_Stop_f, \"stop playing or recording a demo\" );\n\tCmd_AddCommand( \"listdemo\", CL_ListDemo_f, \"list demo entries\" );\n\tCmd_AddCommand (\"info\", NULL, \"collect info about local servers with specified protocol\" );\n\tCmd_AddCommand (\"escape\", CL_Escape_f, \"escape from game to menu\" );\n\tCmd_AddCommand (\"togglemenu\", CL_Escape_f, \"toggle between game and menu\" );\n\tCmd_AddCommand (\"pointfile\", CL_ReadPointFile_f, \"show leaks on a map (if present of course)\" );\n\tCmd_AddCommand (\"linefile\", CL_ReadLineFile_f, \"show leaks on a map (if present of course)\" );\n\tCmd_AddCommand (\"fullserverinfo\", CL_FullServerinfo_f, \"sent by server when serverinfo changes\" );\n\tCmd_AddCommand (\"upload\", CL_BeginUpload_f, \"uploading file to the server\" );\n\n\tCmd_AddRestrictedCommand( \"replaybufferdat\", CL_ReplayBufferDat_f, \"development and debugging tool\" );\n\n\tCmd_AddRestrictedCommand (\"quit\", CL_Quit_f, \"quit from game\" );\n\tCmd_AddRestrictedCommand (\"exit\", CL_Quit_f, \"quit from game\" );\n\n\tCmd_AddCommand (\"screenshot\", CL_GenericShot_f, \"takes a screenshot of the next rendered frame\" );\n\tCmd_AddCommand (\"snapshot\", CL_GenericShot_f, \"takes a snapshot of the next rendered frame\" );\n\tCmd_AddCommand (\"envshot\", CL_GenericShot_f, \"takes a six-sides cubemap shot with specified name\" );\n\tCmd_AddCommand (\"skyshot\", CL_GenericShot_f, \"takes a six-sides envmap (skybox) shot with specified name\" );\n\tCmd_AddCommand (\"levelshot\", CL_LevelShot_f, \"same as \\\"screenshot\\\", used for create plaque images\" );\n\tCmd_AddCommand (\"saveshot\", CL_GenericShot_f, \"used for create save previews with LoadGame menu\" );\n\n\tCmd_AddCommand (\"connect\", CL_Connect_f, \"connect to a server by hostname\" );\n\tCmd_AddCommand (\"reconnect\", CL_Reconnect_f, \"reconnect to current level\" );\n\n\tCmd_AddCommand (\"rcon\", CL_Rcon_f, \"sends a command to the server console (rcon_password and rcon_address required)\" );\n\tCmd_AddCommand (\"precache\", CL_LegacyPrecache_f, \"legacy server compatibility\" );\n\n\tCmd_AddCommand( \"richpresence_gamemode\", Cmd_Null_f, \"compatibility command, does nothing\" );\n\tCmd_AddCommand( \"richpresence_update\", Cmd_Null_f, \"compatibility command, does nothing\" );\n\n\tCmd_AddCommand( \"cl_list_messages\", CL_ListMessages_f, \"list registered user messages\" );\n}\n\n//============================================================================\n/*\n==================\nCL_AdjustClock\n\nslowly adjuct client clock\nto smooth lag effect\n==================\n*/\nstatic void CL_AdjustClock( void )\n{\n\tif( cl.timedelta == 0.0f || !cl_fixtimerate.value )\n\t\treturn;\n\n\tif( cl_fixtimerate.value < 0.0f )\n\t\tCvar_SetValue( \"cl_fixtimerate\", 7.5f );\n\n\tif( fabs( cl.timedelta ) >= 0.001f )\n\t{\n\t\tdouble msec, adjust;\n\t\tdouble sign;\n\n\t\tmsec = ( cl.timedelta * 1000.0 );\n\t\tsign = ( msec < 0 ) ? 1.0 : -1.0;\n\t\tmsec = Q_min( cl_fixtimerate.value, fabs( msec ));\n\t\tadjust = sign * ( msec / 1000.0 );\n\n\t\tif( fabs( adjust ) < fabs( cl.timedelta ))\n\t\t{\n\t\t\tcl.timedelta += adjust;\n\t\t\tcl.time += adjust;\n\t\t}\n\n\t\tif( cl.oldtime > cl.time )\n\t\t\tcl.oldtime = cl.time;\n\t}\n}\n\n/*\n==================\nHost_ClientBegin\n\n==================\n*/\nvoid Host_ClientBegin( void )\n{\n\t// exec console commands\n\tCbuf_Execute ();\n\n\t// if client is not active, do nothing\n\tif( !cls.initialized ) return;\n\n\t// finalize connection process if needs\n\tCL_CheckClientState();\n\n\t// tell the client.dll about client data\n\tCL_UpdateClientData();\n\n\t// if running the server locally, make intentions now\n\tif( SV_Active( )) CL_SendCommand ();\n}\n\n/*\n==================\nHost_ClientFrame\n\n==================\n*/\nvoid Host_ClientFrame( void )\n{\n\t// if client is not active, do nothing\n\tif( !cls.initialized ) return;\n\tif( cls.key_dest == key_game && cls.state == ca_active && !Con_Visible() )\n\t\tPlatform_SetTimer( cl_maxframetime.value );\n\n\t// if running the server remotely, send intentions now after\n\t// the incoming messages have been read\n\tif( !SV_Active( )) CL_SendCommand ();\n\n\tclgame.dllFuncs.pfnFrame( host.frametime );\n\n\t// remember last received framenum\n\tCL_SetLastUpdate ();\n\n\t// read updates from server\n\tCL_ReadPackets ();\n\n\t// do prediction again in case we got\n\t// a new portion updates from server\n\tCL_RedoPrediction ();\n\n\t// update voice\n\tVoice_Idle( host.frametime );\n\n\t// emit visible entities\n\tCL_EmitEntities ();\n\n\t// in case we lost connection\n\tCL_CheckForResend ();\n\n\t// procssing resources on handle\n\twhile( CL_RequestMissingResources( ));\n\n\t// handle thirdperson camera\n\tCL_MoveThirdpersonCamera();\n\n\t// handle spectator movement\n\tCL_MoveSpectatorCamera();\n\n\t// catch changes video settings\n\tVID_CheckChanges();\n\n\t// update the screen\n\tSCR_UpdateScreen ();\n\n\t// update audio\n\tSND_UpdateSound ();\n\n\t// play avi-files\n\tSCR_RunCinematic ();\n\n\t// adjust client time\n\tCL_AdjustClock ();\n}\n\n//============================================================================\n\n/*\n====================\nCL_Init\n====================\n*/\nvoid CL_Init( void )\n{\n\tstring libpath;\n\n\tif( host.type == HOST_DEDICATED )\n\t\treturn; // nothing running on the client\n\n\tCL_InitLocal();\n\n\tVID_Init();\t// init video\n\tS_Init();\t// init sound\n\tVoice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)\n\n\t// unreliable buffer. unsed for unreliable commands and voice stream\n\tMSG_Init( &cls.datagram, \"cls.datagram\", cls.datagram_buf, sizeof( cls.datagram_buf ));\n\n\t// IN_TouchInit();\n\n\tCOM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));\n\n\tif( !CL_LoadProgs( libpath ))\n\t\tHost_Error( \"can't initialize %s: %s\\n\", libpath, COM_GetLibraryError( ));\n\n\tID_Init();\n\n\tcls.build_num = 0;\n\tcls.initialized = true;\n\tcl.maxclients = 1; // allow to drawing player in menu\n\tcls.olddemonum = -1;\n\tcls.demonum = -1;\n}\n\n/*\n===============\nCL_Shutdown\n\n===============\n*/\nvoid CL_Shutdown( void )\n{\n\tCon_Printf( \"%s()\\n\", __func__ );\n\n\tif( host.status != HOST_CRASHED && cls.initialized )\n\t{\n\t\tHost_WriteOpenGLConfig ();\n\t\tHost_WriteVideoConfig ();\n\t\tTouch_WriteConfig();\n\t}\n\n\t// IN_TouchShutdown ();\n\tJoy_Shutdown ();\n\tCL_CloseDemoHeader ();\n\tIN_Shutdown ();\n\tMobile_Shutdown ();\n\tSCR_Shutdown ();\n\tCL_UnloadProgs ();\n\tcls.initialized = false;\n\n\t// for client-side VGUI support we use other order\n\tif( FI && FI->GameInfo && !FI->GameInfo->internal_vgui_support )\n\t\tVGui_Shutdown();\n\n\tif( g_fsapi.Delete )\n\t\tg_fsapi.Delete( \"demoheader.tmp\" ); // remove tmp file\n\tSCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them\n\tS_Shutdown ();\n\tR_Shutdown ();\n\n\tCon_Shutdown ();\n\n}\n"
  },
  {
    "path": "engine/client/cl_mobile.c",
    "content": "/*\ncl_mobile.c - common mobile interface\nCopyright (C) 2015 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"mobility_int.h\"\n#include \"library.h\"\n#include \"input.h\"\n#include \"platform/platform.h\"\n\nstatic CVAR_DEFINE_AUTO( vibration_length, \"1.0\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"vibration length\" );\nstatic CVAR_DEFINE_AUTO( vibration_enable, \"1\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"enable vibration\" );\n\nstatic cl_font_t g_scaled_font;\nstatic float g_font_scale;\n\nstatic void pfnVibrate( float life, char flags )\n{\n\tif( !vibration_enable.value || life < 0.0f )\n\t\treturn;\n\n\t// here goes platform-specific backends\n\tPlatform_Vibrate( life * vibration_length.value, flags );\n}\n\nstatic void Vibrate_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tMsg( S_USAGE \"vibrate <time>\\n\" );\n\t\treturn;\n\t}\n\n\tpfnVibrate( Q_atof( Cmd_Argv( 1 )), VIBRATE_NORMAL );\n}\n\nstatic void pfnEnableTextInput( int enable )\n{\n\tKey_EnableTextInput( enable, false );\n}\n\nstatic int pfnDrawScaledCharacter( int x, int y, int number, int r, int g, int b, float scale )\n{\n\t// this call is very ineffective and possibly broken!\n\trgba_t color = { r, g, b, 255 };\n\tint flags = FONT_DRAW_HUD;\n\n\tif( hud_utf8.value )\n\t\tSetBits( flags, FONT_DRAW_UTF8 );\n\n\tif( fabs( g_font_scale - scale ) > 0.1f ||\n\t\tg_scaled_font.hFontTexture != cls.creditsFont.hFontTexture )\n\t{\n\t\tint i;\n\n\t\tg_scaled_font = cls.creditsFont;\n\t\tg_scaled_font.scale *= scale;\n\t\tg_scaled_font.charHeight *= scale;\n\t\tfor( i = 0; i < ARRAYSIZE( g_scaled_font.charWidths ); i++ )\n\t\t\tg_scaled_font.charWidths[i] *= scale;\n\n\t\tg_font_scale = scale;\n\t}\n\n\treturn CL_DrawCharacter( x, y, number, color, &g_scaled_font, flags );\n}\n\nstatic void pfnTouch_HideButtons( const char *name, byte state )\n{\n\tTouch_HideButtons( name, state, true );\n}\n\nstatic void pfnTouch_RemoveButton( const char *name )\n{\n\tTouch_RemoveButton( name, true );\n}\n\nstatic char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len )\n{\n\treturn COM_ParseFileSafe( data, buf, size, flags, len, NULL );\n}\n\nstatic void GAME_EXPORT pfnSetCustomClientID( const char *id )\n{\n\t// deprecated\n}\n\nstatic const mobile_engfuncs_t gMobileEngfuncs =\n{\n\tMOBILITY_API_VERSION,\n\tpfnVibrate,\n\tpfnEnableTextInput,\n\tTouch_AddClientButton,\n\tTouch_AddDefaultButton,\n\tpfnTouch_HideButtons,\n\tpfnTouch_RemoveButton,\n\tTouch_SetClientOnly,\n\tTouch_ResetDefaultButtons,\n\tpfnDrawScaledCharacter,\n\tSys_Warn,\n\tSys_GetNativeObject,\n\tpfnSetCustomClientID,\n\tpfnParseFileSafe\n};\n\nqboolean Mobile_Init( void )\n{\n\tpfnMobilityInterface ExportToClient;\n\n\tCmd_AddCommand( \"vibrate\", Vibrate_f, \"Vibrate for specified time\");\n\tCvar_RegisterVariable( &vibration_length );\n\tCvar_RegisterVariable( &vibration_enable );\n\n\t// find mobility interface\n\tif(( ExportToClient = COM_GetProcAddress( clgame.hInstance, MOBILITY_CLIENT_EXPORT )))\n\t{\n\t\tstatic mobile_engfuncs_t mobile_engfuncs; // keep a copy, don't let user change engine pointers\n\n\t\tmobile_engfuncs = gMobileEngfuncs;\n\n\t\tif( !ExportToClient( &mobile_engfuncs ))\n\t\t{\n\t\t\tCon_Reportf( \"%s: ^2initailized extended MobilityAPI ^7ver. %i\\n\", __func__, MOBILITY_API_VERSION );\n\t\t\treturn true;\n\t\t}\n\n\t\t// make sure that mobile functions are cleared\n#if 1\n\t\t// some SDKs define export as returning void, breaking the contract\n\t\t// ignore result for now...\n\t\treturn true;\n#else\n\t\tmemset( &mobile_engfuncs, 0, sizeof( mobile_engfuncs ));\n\n\t\treturn false; // just tell user about problems\n#endif\n\t}\n\n\treturn true; // mobile interface is missed\n}\n\nvoid Mobile_Shutdown( void )\n{\n\tCmd_RemoveCommand( \"vibrate\" );\n}\n"
  },
  {
    "path": "engine/client/cl_netgraph.c",
    "content": "/*\ncl_netgraph.c - Draw Net statistics (borrowed from Xash3D SDL code)\nCopyright (C) 2016 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"kbutton.h\"\n\n#if XASH_LOW_MEMORY == 0\n#define NET_TIMINGS\t\t\t1024\n#elif XASH_LOW_MEMORY == 1\n#define NET_TIMINGS\t\t\t256\n#elif XASH_LOW_MEMORY == 2\n#define NET_TIMINGS\t\t\t64\n#endif\n#define NET_TIMINGS_MASK\t\t(NET_TIMINGS - 1)\n#define LATENCY_AVG_FRAC\t\t0.5f\n#define FRAMERATE_AVG_FRAC\t\t0.5f\n#define PACKETLOSS_AVG_FRAC\t\t0.5f\n#define PACKETCHOKE_AVG_FRAC\t\t0.5f\n#define NETGRAPH_LERP_HEIGHT\t\t24\n#define NETGRAPH_NET_COLORS\t\t5\n#define NUM_LATENCY_SAMPLES\t\t8\n\nCVAR_DEFINE_AUTO( net_graph, \"0\", FCVAR_ARCHIVE, \"draw network usage graph\" );\nstatic CVAR_DEFINE_AUTO( net_graphpos, \"1\", FCVAR_ARCHIVE, \"network usage graph position\" );\nstatic CVAR_DEFINE_AUTO( net_scale, \"5\", FCVAR_ARCHIVE, \"network usage graph scale level\" );\nstatic CVAR_DEFINE_AUTO( net_graphwidth, \"192\", FCVAR_ARCHIVE, \"network usage graph width\" );\nstatic CVAR_DEFINE_AUTO( net_graphheight, \"64\", FCVAR_ARCHIVE, \"network usage graph height\" );\nstatic CVAR_DEFINE_AUTO( net_graphsolid, \"1\", FCVAR_ARCHIVE, \"fill segments in network usage graph\" );\n\nstatic struct packet_latency_t\n{\n\tint\tlatency;\n\tint\tchoked;\n} netstat_packet_latency[NET_TIMINGS];\n\nstatic struct cmdinfo_t\n{\n\tfloat\tcmd_lerp;\n\tint\tsize;\n\tqboolean\tsent;\n} netstat_cmdinfo[NET_TIMINGS];\n\nstatic byte netcolors[NETGRAPH_NET_COLORS+NETGRAPH_LERP_HEIGHT][4] =\n{\n\t{ 255, 0,   0,   255 },\n\t{ 0,   0,   255, 255 },\n\t{ 240, 127, 63,  255 },\n\t{ 255, 255, 0,   255 },\n\t{ 63,  255, 63,  150 }\n\t// other will be generated through NetGraph_InitColors()\n};\n\nstatic const byte sendcolor[4] = { 88, 29, 130, 255 };\nstatic const byte holdcolor[4] = { 255, 0, 0, 200 };\nstatic const byte extrap_base_color[4] = { 255, 255, 255, 255 };\nstatic netbandwidthgraph_t\tnetstat_graph[NET_TIMINGS];\nstatic float\t\tpacket_loss;\nstatic float\t\tpacket_choke;\nstatic float\t\tframerate = 0.0;\nstatic int\t\tmaxmsgbytes = 0;\n\n/*\n==========\nNetGraph_DrawRect\n\nNetGraph_FillRGBA shortcut\n==========\n*/\nstatic void NetGraph_DrawRect( const wrect_t *rect, const byte colors[4] )\n{\n\tref.dllFuncs.Color4ub( colors[0], colors[1], colors[2], colors[3] );\t// color for this quad\n\n\tref.dllFuncs.Vertex3f( rect->left, rect->top, 0 );\n\tref.dllFuncs.Vertex3f( rect->left + rect->right, rect->top, 0 );\n\tref.dllFuncs.Vertex3f( rect->left + rect->right, rect->top + rect->bottom, 0 );\n\tref.dllFuncs.Vertex3f( rect->left, rect->top + rect->bottom, 0 );\n}\n\n/*\n==========\nNetGraph_AtEdge\n\nedge detect\n==========\n*/\nstatic qboolean NetGraph_AtEdge( int x, int width )\n{\n\tif( x > 3 )\n\t{\n\t\tif( x >= width - 4 )\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n==========\nNetGraph_InitColors\n\ninit netgraph colors\n==========\n*/\nstatic void NetGraph_InitColors( void )\n{\n\tbyte\tmincolor[2][3];\n\tbyte\tmaxcolor[2][3];\n\tfloat\tdc[2][3];\n\tint\ti, hfrac;\n\tfloat\tf;\n\n\tmincolor[0][0] = 63;\n\tmincolor[0][1] = 0;\n\tmincolor[0][2] = 100;\n\n\tmaxcolor[0][0] = 0;\n\tmaxcolor[0][1] = 63;\n\tmaxcolor[0][2] = 255;\n\n\tmincolor[1][0] = 255;\n\tmincolor[1][1] = 127;\n\tmincolor[1][2] = 0;\n\n\tmaxcolor[1][0] = 250;\n\tmaxcolor[1][1] = 0;\n\tmaxcolor[1][2] = 0;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tdc[0][i] = (float)(maxcolor[0][i] - mincolor[0][i]);\n\t\tdc[1][i] = (float)(maxcolor[1][i] - mincolor[1][i]);\n\t}\n\n\thfrac = NETGRAPH_LERP_HEIGHT / 3;\n\n\tfor( i = 0; i < NETGRAPH_LERP_HEIGHT; i++ )\n\t{\n\t\tif( i < hfrac )\n\t\t{\n\t\t\tf = (float)i / (float)hfrac;\n\t\t\tVectorMA( mincolor[0], f, dc[0], netcolors[NETGRAPH_NET_COLORS + i] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tf = (float)(i - hfrac) / (float)(NETGRAPH_LERP_HEIGHT - hfrac );\n\t\t\tVectorMA( mincolor[1], f, dc[1], netcolors[NETGRAPH_NET_COLORS + i] );\n\t\t}\n\t\tnetcolors[NETGRAPH_NET_COLORS + i][3] = 255;\n\t}\n}\n\n/*\n==========\nNetGraph_GetFrameData\n\nget frame data info, like chokes, packet losses, also update graph, packet and cmdinfo\n==========\n*/\nstatic void NetGraph_GetFrameData( float *latency, int *latency_count )\n{\n\tint\t\ti, choke_count = 0, loss_count = 0;\n\tdouble\t\tnewtime = Sys_DoubleTime();\n\tstatic double\tnexttime = 0;\n\tfloat\t\tloss, choke;\n\n\t*latency_count = 0;\n\t*latency = 0.0f;\n\n\tif( newtime >= nexttime )\n\t{\n\t\t// soft fading of net peak usage\n\t\tmaxmsgbytes = Q_max( 0, maxmsgbytes - 50 );\n\t\tnexttime = newtime + 0.05;\n\t}\n\n\tfor( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ )\n\t{\n\t\tframe_t *f = cl.frames + ( i & CL_UPDATE_MASK );\n\t\tstruct packet_latency_t *p = netstat_packet_latency + ( i & NET_TIMINGS_MASK );\n\t\tnetbandwidthgraph_t *g = netstat_graph + ( i & NET_TIMINGS_MASK );\n\n\t\tp->choked = f->choked;\n\t\tif( p->choked ) choke_count++;\n\n\t\tif( !f->valid )\n\t\t{\n\t\t\tp->latency = 9998; // broken delta\n\t\t}\n\t\telse if( f->receivedtime == -1.0 )\n\t\t{\n\t\t\tp->latency = 9999; // dropped\n\t\t\tloss_count++;\n\t\t}\n\t\telse if( f->receivedtime == -3.0 )\n\t\t{\n\t\t\tp->latency = 9997; // skipped\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint frame_latency = Q_min( 1.0f, f->latency );\n\t\t\tp->latency = (( frame_latency + 0.1f ) / 1.1f ) * ( net_graphheight.value - NETGRAPH_LERP_HEIGHT - 2 );\n\n\t\t\tif( i > cls.netchan.incoming_sequence - NUM_LATENCY_SAMPLES )\n\t\t\t{\n\t\t\t\t(*latency) += 1000.0f * f->latency;\n\t\t\t\t(*latency_count)++;\n\t\t\t}\n\t\t}\n\n\t\tmemcpy( g, &f->graphdata, sizeof( netbandwidthgraph_t ));\n\n\t\tif( g->msgbytes > maxmsgbytes )\n\t\t\tmaxmsgbytes = g->msgbytes;\n\t}\n\n\tif( maxmsgbytes > 1000 )\n\t\tmaxmsgbytes = 1000;\n\n\tfor( i = cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.outgoing_sequence; i++ )\n\t{\n\t\tnetstat_cmdinfo[i & NET_TIMINGS_MASK].cmd_lerp = cl.commands[i & CL_UPDATE_MASK].frame_lerp;\n\t\tnetstat_cmdinfo[i & NET_TIMINGS_MASK].sent = cl.commands[i & CL_UPDATE_MASK].heldback ? false : true;\n\t\tnetstat_cmdinfo[i & NET_TIMINGS_MASK].size = cl.commands[i & CL_UPDATE_MASK].sendsize;\n\t}\n\n\t// packet loss\n\tloss = 100.0f * (float)loss_count / CL_UPDATE_BACKUP;\n\tpacket_loss = PACKETLOSS_AVG_FRAC * packet_loss + ( 1.0f - PACKETLOSS_AVG_FRAC ) * loss;\n\n\t// packet choke\n\tchoke = 100.0f * (float)choke_count / CL_UPDATE_BACKUP;\n\tpacket_choke = PACKETCHOKE_AVG_FRAC * packet_choke + ( 1.0f - PACKETCHOKE_AVG_FRAC ) * choke;\n}\n\n/*\n===========\nNetGraph_DrawTimes\n\n===========\n*/\nstatic void NetGraph_DrawTimes( wrect_t rect, int x, int w )\n{\n\tint\ti, j, extrap_point = NETGRAPH_LERP_HEIGHT / 3, a, h;\n\trgba_t\tcolors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 };\n\twrect_t\tfill;\n\n\tfor( a = 0; a < w; a++ )\n\t{\n\t\ti = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK;\n\t\th = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight.value * 0.7f);\n\n\t\tfill.left = x + w - a - 1;\n\t\tfill.right = fill.bottom = 1;\n\t\tfill.top = rect.top + rect.bottom - 4;\n\n\t\tif( h >= extrap_point )\n\t\t{\n\t\t\tint\tstart = 0;\n\n\t\t\th -= extrap_point;\n\t\t\tfill.top -= extrap_point;\n\n\t\t\tif( !net_graphsolid.value )\n\t\t\t{\n\t\t\t\tfill.top -= (h - 1);\n\t\t\t\tstart = (h - 1);\n\t\t\t}\n\n\t\t\tfor( j = start; j < h; j++ )\n\t\t\t{\n\t\t\t\tint color = NETGRAPH_NET_COLORS + j + extrap_point;\n\t\t\t\tcolor = Q_min( color, ARRAYSIZE( netcolors ) - 1 );\n\n\t\t\t\tNetGraph_DrawRect( &fill, netcolors[color] );\n\t\t\t\tfill.top--;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint\toldh = h;\n\n\t\t\tfill.top -= h;\n\t\t\th = extrap_point - h;\n\n\t\t\tif( !net_graphsolid.value )\n\t\t\t\th = 1;\n\n\t\t\tfor( j = 0; j < h; j++ )\n\t\t\t{\n\t\t\t\tint color = NETGRAPH_NET_COLORS + j + oldh;\n\t\t\t\tcolor = Q_min( color, ARRAYSIZE( netcolors ) - 1 );\n\n\t\t\t\tNetGraph_DrawRect( &fill, netcolors[color] );\n\t\t\t\tfill.top--;\n\t\t\t}\n\t\t}\n\n\t\tfill.top = rect.top + rect.bottom - 4 - extrap_point;\n\n\t\tif( NetGraph_AtEdge( a, w ))\n\t\t\tNetGraph_DrawRect( &fill, extrap_base_color );\n\n\t\tfill.top = rect.top + rect.bottom - 4;\n\n\t\tif( netstat_cmdinfo[i].sent )\n\t\t\tNetGraph_DrawRect( &fill, sendcolor );\n\t\telse NetGraph_DrawRect( &fill, holdcolor );\n\t}\n}\n\n//left = x\n//right = width\n//top = y\n//bottom = height\n\n/*\n===========\nNetGraph_DrawHatches\n\n===========\n*/\nstatic void NetGraph_DrawHatches( int x, int y )\n{\n\tint\tystep = (int)( 10.0f / net_scale.value );\n\tbyte\tcolorminor[4] = { 0, 63, 63, 200 };\n\tbyte\tcolor[4] = { 0, 200, 0, 255 };\n\twrect_t\thatch = { x, 4, y, 1 };\n\tint\tstarty;\n\n\tystep = Q_max( ystep, 1 );\n\n\tfor( starty = hatch.top; hatch.top > 0 && ((starty - hatch.top) * net_scale.value < (maxmsgbytes + 50)); hatch.top -= ystep )\n\t{\n\t\tif(!((int)((starty - hatch.top) * net_scale.value ) % 50 ))\n\t\t{\n\t\t\tNetGraph_DrawRect( &hatch, color );\n\t\t}\n\t\telse if( ystep > 5 )\n\t\t{\n\t\t\tNetGraph_DrawRect( &hatch, colorminor );\n\t\t}\n\t}\n}\n\n/*\n===========\nNetGraph_DrawTextFields\n\n===========\n*/\nstatic void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int count, float avg, int packet_loss, int packet_choke, int graphtype )\n{\n\tstatic int\tlastout;\n\tcl_font_t *font = Con_GetFont( 0 );\n\trgba_t\t\tcolors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 };\n\tint\t\tptx = Q_max( x + w - NETGRAPH_LERP_HEIGHT - 1, 1 );\n\tint\t\tpty = Q_max( rect.top + rect.bottom - NETGRAPH_LERP_HEIGHT - 3, 1 );\n\tint\t\tout, i = ( cls.netchan.outgoing_sequence - 1 ) & NET_TIMINGS_MASK;\n\tint\t\tj = cls.netchan.incoming_sequence & NET_TIMINGS_MASK;\n\tint\t\tlast_y = y - net_graphheight.value;\n\n\tif( count > 0 )\n\t{\n\t\tavg = avg / (float)( count - ( host.frametime * FRAMERATE_AVG_FRAC ));\n\n\t\tif( cl_updaterate.value > 0.0f )\n\t\t\tavg -= 1000.0f / cl_updaterate.value;\n\n\t\t// can't be below zero\n\t\tavg = Q_max( 0.0f, avg );\n\t}\n\telse avg = 0.0;\n\n\t// move rolling average\n\tframerate = FRAMERATE_AVG_FRAC * host.frametime + ( 1.0f - FRAMERATE_AVG_FRAC ) * framerate;\n\n\tCL_SetFontRendermode( font );\n\n\tif( framerate > 0.0f )\n\t{\n\t\ty -= net_graphheight.value;\n\n\t\tCL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, \"%.1f fps\" , 1.0f / framerate);\n\n\t\tif( avg > 1.0f )\n\t\t\tCL_DrawStringf( font, x + 75, y, colors, FONT_DRAW_NORENDERMODE, \"%i ms\" , (int)avg );\n\n\t\ty += 15;\n\n\t\tout = netstat_cmdinfo[i].size;\n\t\tif( !out ) out = lastout;\n\t\telse lastout = out;\n\n\t\tCL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE,\n\t\t\t\"in :  %i %.2f kb/s\", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec );\n\t\ty += 15;\n\n\t\tCL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE,\n\t\t\t\"out:  %i %.2f kb/s\", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec );\n\t\ty += 15;\n\n\t\tif( graphtype > 2 )\n\t\t{\n\t\t\tint\tloss = (int)(( packet_loss + PACKETLOSS_AVG_FRAC ) - 0.01f );\n\t\t\tint\tchoke = (int)(( packet_choke + PACKETCHOKE_AVG_FRAC ) - 0.01f );\n\n\t\t\tCL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, \"loss: %i choke: %i\", loss, choke );\n\t\t}\n\t}\n\n\tif( graphtype < 3 )\n\t\tCL_DrawStringf( font, ptx, pty, colors, FONT_DRAW_NORENDERMODE, \"%i/s\", (int)cl_cmdrate.value );\n\n\tCL_DrawStringf( font, ptx, last_y, colors, FONT_DRAW_NORENDERMODE, \"%i/s\" , (int)cl_updaterate.value );\n}\n\n/*\n===========\nNetGraph_DrawDataSegment\n\n===========\n*/\nstatic int NetGraph_DrawDataSegment( wrect_t *fill, int bytes, byte r, byte g, byte b, byte a )\n{\n\tfloat\th = bytes / net_scale.value;\n\tbyte\tcolors[4] = { r, g, b, a };\n\n\tfill->top -= (int)h;\n\n\tif( net_graphsolid.value )\n\t\tfill->bottom = (int)h;\n\telse fill->bottom = 1;\n\n\tif( fill->top > 1 )\n\t{\n\t\tNetGraph_DrawRect( fill, colors );\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n/*\n===========\nNetGraph_ColorForHeight\n\ncolor based on packet latency\n===========\n*/\nstatic void NetGraph_ColorForHeight( struct packet_latency_t *packet, byte color[4], int *ping )\n{\n\tswitch( packet->latency )\n\t{\n\tcase 9999:\n\t\tmemcpy( color, netcolors[0], sizeof( byte ) * 4 ); // dropped\n\t\t*ping = 0;\n\t\tbreak;\n\tcase 9998:\n\t\tmemcpy( color, netcolors[1], sizeof( byte ) * 4 ); // invalid\n\t\t*ping = 0;\n\t\tbreak;\n\tcase 9997:\n\t\tmemcpy( color, netcolors[2], sizeof( byte ) * 4 ); // skipped\n\t\t*ping = 0;\n\t\tbreak;\n\tdefault:\n\t\t*ping = 1;\n\t\tif( packet->choked )\n\t\t{\n\t\t\tmemcpy( color, netcolors[3], sizeof( byte ) * 4 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmemcpy( color, netcolors[4], sizeof( byte ) * 4 );\n\t\t}\n\t}\n}\n\n/*\n===========\nNetGraph_DrawDataUsage\n\n===========\n*/\nstatic void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype )\n{\n\tint\ta, i, h, lastvalidh = 0, ping;\n\tint\tpingheight = net_graphheight.value - NETGRAPH_LERP_HEIGHT - 2;\n\twrect_t\tfill = { 0 };\n\tbyte\tcolor[4];\n\n\tfor( a = 0; a < w; a++ )\n\t{\n\t\ti = (cls.netchan.incoming_sequence - a) & NET_TIMINGS_MASK;\n\t\th = netstat_packet_latency[i].latency;\n\n\t\tNetGraph_ColorForHeight( &netstat_packet_latency[i], color, &ping );\n\n\t\tif( !ping ) h = lastvalidh;\n\t\telse lastvalidh = h;\n\n\t\tif( h > pingheight )\n\t\t\th = pingheight;\n\n\t\tfill.left = x + w - a - 1;\n\t\tfill.top = y - h;\n\t\tfill.right = 1;\n\t\tfill.bottom = ping ? 1: h;\n\n\t\tif( !ping )\n\t\t{\n\t\t\tif( fill.bottom > 3 )\n\t\t\t{\n\t\t\t\tfill.bottom = 2;\n\t\t\t\tNetGraph_DrawRect( &fill, color );\n\t\t\t\tfill.top += fill.bottom - 2;\n\t\t\t\tNetGraph_DrawRect( &fill, color );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tNetGraph_DrawRect( &fill, color );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tNetGraph_DrawRect( &fill, color );\n\t\t}\n\n\t\tfill.top = y;\n\t\tfill.bottom = 1;\n\n\t\tcolor[0] = 0;\n\t\tcolor[1] = 255;\n\t\tcolor[2] = 0;\n\t\tcolor[3] = 160;\n\n\t\tif( NetGraph_AtEdge( a, w ))\n\t\t\tNetGraph_DrawRect( &fill, color );\n\n\t\tif( graphtype < 2 )\n\t\t\tcontinue;\n\n\t\tcolor[0] = color[1] = color[2] = color[3] = 255;\n\t\tfill.top = y - net_graphheight.value - 1;\n\t\tfill.bottom = 1;\n\n\t\tif( NetGraph_AtEdge( a, w ))\n\t\t\tNetGraph_DrawRect( &fill, color );\n\n\t\tfill.top -= 1;\n\n\t\tif( netstat_packet_latency[i].latency > 9995 )\n\t\t\tcontinue; // skip invalid\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].client, 255, 0, 0, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].players, 255, 255, 0, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].entities, 255, 0, 255, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].tentities, 0, 0, 255, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].sound, 0, 255, 0, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].event, 0, 255, 255, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].usr, 200, 200, 200, 128 ))\n\t\t\tcontinue;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].voicebytes, 255, 255, 255, 255 ))\n\t\t\tcontinue;\n\n\t\tfill.top = y - net_graphheight.value - 1;\n\t\tfill.bottom = 1;\n\t\tfill.top -= 2;\n\n\t\tif( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].msgbytes, 240, 240, 240, 128 ))\n\t\t\tcontinue;\n\t}\n\n\tif( graphtype >= 2 )\n\t\tNetGraph_DrawHatches( x, y - net_graphheight.value - 1 );\n}\n\n/*\n===========\nNetGraph_GetScreenPos\n\n===========\n*/\nstatic void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y )\n{\n\trect->left = rect->top = 0;\n\trect->right = refState.width;\n\trect->bottom = refState.height;\n\n\t*w = Q_min( NET_TIMINGS, net_graphwidth.value );\n\tif( rect->right < *w + 10 )\n\t\t*w = rect->right - 10;\n\n\t// detect x and y position\n\tswitch( (int)net_graphpos.value )\n\t{\n\tcase 1: // right sided\n\t\t*x = rect->left + rect->right - 5 - *w;\n\t\tbreak;\n\tcase 2: // center\n\t\t*x = ( rect->left + ( rect->right - 10 - *w )) / 2;\n\t\tbreak;\n\tdefault: // left sided\n\t\t*x = rect->left + 5;\n\t\tbreak;\n\t}\n\n\t*y = rect->bottom + rect->top - NETGRAPH_LERP_HEIGHT - 5;\n}\n\n/*\n===========\nSCR_DrawNetGraph\n\n===========\n*/\nvoid SCR_DrawNetGraph( void )\n{\n\twrect_t\trect;\n\tfloat\tavg_ping;\n\tint\tping_count;\n\tint\tw, x, y;\n\tkbutton_t *in_graph;\n\tint   graphtype;\n\n\tif( !host.allow_console )\n\t\treturn;\n\n\tif( cls.state != ca_active )\n\t\treturn;\n\n\tin_graph = clgame.dllFuncs.KB_Find( \"in_graph\" );\n\n\tif( in_graph && in_graph->state & 1 )\n\t\tgraphtype = 2;\n\telse if( net_graph.value != 0.0f )\n\t\tgraphtype = (int)net_graph.value;\n\telse return;\n\n\tif( net_scale.value <= 0 )\n\t\tCvar_SetValue( \"net_scale\", 0.1f );\n\n\tNetGraph_GetScreenPos( &rect, &w, &x, &y );\n\n\tNetGraph_GetFrameData( &avg_ping, &ping_count );\n\n\tNetGraph_DrawTextFields( x, y, w, rect, ping_count, avg_ping, packet_loss, packet_choke, graphtype );\n\n\tif( graphtype < 3 )\n\t{\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransColor );\n\t\tref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) );\n\t\tref.dllFuncs.Begin( TRI_QUADS ); // draw all the fills as a long solid sequence of quads for speedup reasons\n\n\t\t// NOTE: fill colors without texture at this point\n\t\tNetGraph_DrawDataUsage( x, y, w, graphtype );\n\t\tNetGraph_DrawTimes( rect, x, w );\n\n\t\tref.dllFuncs.End();\n\t\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n\t}\n}\n\nvoid CL_InitNetgraph( void )\n{\n\tCvar_RegisterVariable( &net_graph );\n\tCvar_RegisterVariable( &net_graphpos );\n\tCvar_RegisterVariable( &net_scale );\n\tCvar_RegisterVariable( &net_graphwidth );\n\tCvar_RegisterVariable( &net_graphheight );\n\tCvar_RegisterVariable( &net_graphsolid );\n\tpacket_loss = packet_choke = 0.0;\n\n\tNetGraph_InitColors();\n}\n"
  },
  {
    "path": "engine/client/cl_parse.c",
    "content": "/*\ncl_parse.c - parse a message received from the server\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"particledef.h\"\n#include \"cl_tent.h\"\n#include \"shake.h\"\n#include \"hltv.h\"\n#include \"input.h\"\n#if XASH_LOW_MEMORY != 2\nint CL_UPDATE_BACKUP = SINGLEPLAYER_BACKUP;\n#endif\n/*\n===============\nCL_UserMsgStub\n\nDefault stub for missed callbacks\n===============\n*/\nstatic int CL_UserMsgStub( const char *pszName, int iSize, void *pbuf )\n{\n\treturn 1;\n}\n\n/*\n==================\nCL_ParseViewEntity\n\n==================\n*/\nvoid CL_ParseViewEntity( sizebuf_t *msg )\n{\n\tcl.viewentity = MSG_ReadWord( msg );\n\n\t// check entity bounds in case we want\n\t// to use this directly in clgame.entities[] array\n\tcl.viewentity = bound( 0, cl.viewentity, clgame.maxEntities - 1 );\n}\n\n/*\n==================\nCL_ParseSoundPacket\n\n==================\n*/\nstatic void CL_ParseSoundPacket( sizebuf_t *msg )\n{\n\tvec3_t\tpos;\n\tint \tchan, sound;\n\tfloat \tvolume, attn;\n\tint\tflags, pitch, entnum;\n\tsound_t\thandle = 0;\n\n\tflags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );\n\tsound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );\n\tchan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );\n\n\tif( FBitSet( flags, SND_VOLUME ))\n\t\tvolume = (float)MSG_ReadByte( msg ) / 255.0f;\n\telse volume = VOL_NORM;\n\n\tif( FBitSet( flags, SND_ATTENUATION ))\n\t\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\telse attn = ATTN_NONE;\n\n\tif( FBitSet( flags, SND_PITCH ))\n\t\tpitch = MSG_ReadByte( msg );\n\telse pitch = PITCH_NORM;\n\n\t// entity reletive\n\tentnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\n\t// positioned in space\n\tMSG_ReadVec3Coord( msg, pos );\n\n\tif( FBitSet( flags, SND_SENTENCE ))\n\t{\n\t\tchar\tsentenceName[32];\n\n\t\tif( FBitSet( flags, SND_SEQUENCE ))\n\t\t\tQ_snprintf( sentenceName, sizeof( sentenceName ), \"!#%i\", sound + MAX_SOUNDS_NONSENTENCE );\n\t\telse Q_snprintf( sentenceName, sizeof( sentenceName ), \"!%i\", sound );\n\n\t\thandle = S_RegisterSound( sentenceName );\n\t}\n\telse handle = cl.sound_index[sound];\t// see precached sound\n\n\tif( !cl.audio_prepped )\n\t\treturn; // too early\n\n\t// g-cont. sound and ambient sound have only difference with channel\n\tif( chan == CHAN_STATIC )\n\t{\n\t\tS_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );\n\t}\n\telse\n\t{\n\t\tS_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags );\n\t}\n}\n\n/*\n==================\nCL_ParseRestoreSoundPacket\n\n==================\n*/\nvoid CL_ParseRestoreSoundPacket( sizebuf_t *msg )\n{\n\tvec3_t\tpos;\n\tint \tchan, sound;\n\tfloat \tvolume, attn;\n\tint\tflags, pitch, entnum;\n\tdouble\tsamplePos, forcedEnd;\n\tint\twordIndex;\n\tsound_t\thandle = 0;\n\n\tflags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );\n\tsound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );\n\tchan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );\n\n\tif( flags & SND_VOLUME )\n\t\tvolume = (float)MSG_ReadByte( msg ) / 255.0f;\n\telse volume = VOL_NORM;\n\n\tif( flags & SND_ATTENUATION )\n\t\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\telse attn = ATTN_NONE;\n\n\tif( flags & SND_PITCH )\n\t\tpitch = MSG_ReadByte( msg );\n\telse pitch = PITCH_NORM;\n\n\t// entity reletive\n\tentnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\n\t// positioned in space\n\tMSG_ReadVec3Coord( msg, pos );\n\n\tif( flags & SND_SENTENCE )\n\t{\n\t\tchar\tsentenceName[32];\n\n\t\tif( flags & SND_SEQUENCE )\n\t\t\tQ_snprintf( sentenceName, sizeof( sentenceName ), \"!#%i\", sound + MAX_SOUNDS_NONSENTENCE );\n\t\telse Q_snprintf( sentenceName, sizeof( sentenceName ), \"!%i\", sound );\n\n\t\thandle = S_RegisterSound( sentenceName );\n\t}\n\telse handle = cl.sound_index[sound]; // see precached sound\n\n\twordIndex = MSG_ReadByte( msg );\n\n\t// 16 bytes here\n\tMSG_ReadBytes( msg, &samplePos, sizeof( samplePos ));\n\tMSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));\n\n\tif( !cl.audio_prepped )\n\t\treturn; // too early\n\n\tS_RestoreSound( pos, entnum, chan, handle, volume, attn, pitch, flags, samplePos, forcedEnd, wordIndex );\n}\n\n/*\n==================\nCL_ParseServerTime\n\n==================\n*/\nvoid CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto )\n{\n\tdouble\tdt;\n\n\tcl.mtime[1] = cl.mtime[0];\n\tcl.mtime[0] = MSG_ReadFloat( msg );\n\n\tif( proto == PROTO_QUAKE )\n\t\treturn; // don't mess the time\n\n\tif( cl.maxclients == 1 )\n\t\tcl.time = cl.mtime[0];\n\n\tdt = cl.time - cl.mtime[0];\n\n\tif( fabs( dt ) > cl_clockreset.value )\t// 0.1 by default\n\t{\n\t\tcl.time = cl.mtime[0];\n\t\tcl.timedelta = 0.0f;\n\t}\n\telse if( dt != 0.0 )\n\t{\n\t\tcl.timedelta = dt;\n\t}\n\n\tif( cl.oldtime > cl.time )\n\t\tcl.oldtime = cl.time;\n}\n\n/*\n==================\nCL_ParseSignon\n\n==================\n*/\nvoid CL_ParseSignon( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\ti = MSG_ReadByte( msg );\n\n\tif( i <= cls.signon )\n\t{\n\t\tCon_Reportf( S_ERROR \"received signon %i when at %i\\n\", i, cls.signon );\n\t\tCL_Disconnect();\n\t\treturn;\n\t}\n\n\tcls.signon = i;\n\tCL_SignonReply( proto );\n}\n\n/*\n==================\nCL_ParseMovevars\n\n==================\n*/\nvoid CL_ParseMovevars( sizebuf_t *msg )\n{\n\tDelta_InitClient ();\t// finalize client delta's\n\n\tMSG_ReadDeltaMovevars( msg, &clgame.oldmovevars, &clgame.movevars );\n\n\t// water alpha is not allowed\n\tif( !FBitSet( world.flags, FWORLD_WATERALPHA ))\n\t\tclgame.movevars.wateralpha = 1.0f;\n\n\t// update sky if changed\n\tif( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped )\n\t\tR_SetupSky( clgame.movevars.skyName );\n\n\tclgame.oldmovevars = clgame.movevars;\n\tclgame.entities->curstate.scale = clgame.movevars.waveHeight;\n\n\t// keep features an actual!\n\tclgame.oldmovevars.features = clgame.movevars.features = host.features;\n}\n\n/*\n==================\nCL_ParseParticles\n\n==================\n*/\nvoid CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto )\n{\n\tvec3_t\t\torg, dir;\n\tint\t\ti, count, color;\n\tfloat\t\tlife;\n\n\tMSG_ReadVec3Coord( msg, org );\n\n\tfor( i = 0; i < 3; i++ )\n\t\tdir[i] = MSG_ReadChar( msg ) * 0.0625f;\n\n\tcount = MSG_ReadByte( msg );\n\tcolor = MSG_ReadByte( msg );\n\tif( count == 255 )\n\t\tcount = 1024;\n\n\tif( proto == PROTO_GOLDSRC )\n\t\tlife = 0.0f;\n\telse life = MSG_ReadByte( msg ) * 0.125f;\n\n\tif( life != 0.0f && count == 1 )\n\t{\n\t\tparticle_t\t*p;\n\n\t\tp = R_AllocParticle( NULL );\n\t\tif( !p ) return;\n\n\t\tp->die += life;\n\t\tp->color = color;\n\t\tp->type = pt_static;\n\n\t\tVectorCopy( org, p->org );\n\t\tVectorCopy( dir, p->vel );\n\t}\n\telse R_RunParticleEffect( org, dir, color, count );\n}\n\n/*\n==================\nCL_ParseStaticEntity\n\nstatic client entity\n==================\n*/\nstatic void CL_ParseStaticEntity( sizebuf_t *msg )\n{\n\tint\t\ti, newnum;\n\tconst entity_state_t from = { 0 };\n\tentity_state_t to;\n\tcl_entity_t\t*ent;\n\n\tif( !clgame.static_entities )\n\t\tclgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );\n\n\tnewnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\tMSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] );\n\n\ti = clgame.numStatics;\n\tif( i >= MAX_STATIC_ENTITIES )\n\t{\n\t\tCon_Printf( S_ERROR \"MAX_STATIC_ENTITIES limit exceeded!\\n\" );\n\t\treturn;\n\t}\n\n\tent = &clgame.static_entities[i];\n\tclgame.numStatics++;\n\n\t// all states are same\n\tent->baseline = ent->curstate = ent->prevstate = to;\n\tent->index = 0; // static entities doesn't has the numbers\n\n\t// statics may be respawned in game e.g. for demo recording\n\tif( cls.state == ca_connected || cls.state == ca_validate )\n\t\tent->trivial_accept = INVALID_HANDLE;\n\n\t// setup the new static entity\n\tVectorCopy( ent->curstate.origin, ent->origin );\n\tVectorCopy( ent->curstate.angles, ent->angles );\n\tent->model = CL_ModelHandle( to.modelindex );\n\tent->curstate.framerate = 1.0f;\n\tCL_ResetLatchedVars( ent, true );\n\n\tif( ent->curstate.rendermode == kRenderNormal && ent->model != NULL )\n\t{\n\t\t// auto 'solid' faces\n\t\tif( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( ))\n\t\t{\n\t\t\tent->curstate.rendermode = kRenderTransAlpha;\n\t\t\tent->curstate.renderamt = 255;\n\t\t}\n\t}\n\n\tR_AddEfrags( ent );\t// add link\n}\n\n\n/*\n==================\nCL_WeaponAnim\n\nSet new weapon animation\n==================\n*/\nvoid GAME_EXPORT CL_WeaponAnim( int iAnim, int body )\n{\n\tcl_entity_t\t*view = &clgame.viewent;\n\n\tcl.local.weaponstarttime = 0.0f;\n\tcl.local.weaponsequence = iAnim;\n\tview->curstate.framerate = 1.0f;\n\tview->curstate.body = body;\n\n#if 0\t// g-cont. for GlowShell testing\n\tview->curstate.renderfx = kRenderFxGlowShell;\n\tview->curstate.rendercolor.r = 255;\n\tview->curstate.rendercolor.g = 128;\n\tview->curstate.rendercolor.b = 0;\n\tview->curstate.renderamt = 150;\n#endif\n}\n\n/*\n==================\nCL_ParseStaticDecal\n\n==================\n*/\nvoid CL_ParseStaticDecal( sizebuf_t *msg )\n{\n\tvec3_t\t\torigin;\n\tint\t\tdecalIndex, entityIndex, modelIndex;\n\tfloat\t\tscale;\n\tint\t\tflags;\n\n\tMSG_ReadVec3Coord( msg, origin );\n\tdecalIndex = MSG_ReadWord( msg );\n\tentityIndex = MSG_ReadShort( msg );\n\n\tif( entityIndex > 0 )\n\t\tmodelIndex = MSG_ReadWord( msg );\n\telse modelIndex = 0;\n\tflags = MSG_ReadByte( msg );\n\tscale = (float)MSG_ReadWord( msg ) / 4096.0f;\n\n\tCL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale );\n}\n\n/*\n==================\nCL_ParseSoundFade\n\n==================\n*/\nvoid CL_ParseSoundFade( sizebuf_t *msg )\n{\n\tfloat\tfadePercent, fadeOutSeconds;\n\tfloat\tholdTime, fadeInSeconds;\n\n\tfadePercent = (float)MSG_ReadByte( msg );\n\tholdTime = (float)MSG_ReadByte( msg );\n\tfadeOutSeconds = (float)MSG_ReadByte( msg );\n\tfadeInSeconds = (float)MSG_ReadByte( msg );\n\n\tS_FadeClientVolume( fadePercent, fadeOutSeconds, holdTime, fadeInSeconds );\n}\n\n/*\n==================\nCL_RequestMissingResources\n\n==================\n*/\nqboolean CL_RequestMissingResources( void )\n{\n\tresource_t\t*p;\n\n\tif( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate ))\n\t{\n\t\tp = cl.resourcesneeded.pNext;\n\n\t\tif( p == &cl.resourcesneeded )\n\t\t{\n\t\t\tcls.dl.doneregistering = true;\n\t\t\thost.downloadcount = 0;\n\t\t\tcls.dl.custom = false;\n\t\t}\n\t\telse if( !FBitSet( p->ucFlags, RES_WASMISSING ))\n\t\t{\n\t\t\tCL_MoveToOnHandList( cl.resourcesneeded.pNext );\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nvoid CL_BatchResourceRequest( qboolean initialize )\n{\n\tbyte\t\tdata[MAX_INIT_MSG];\n\tresource_t\t*p, *n;\n\tsizebuf_t\t\tmsg;\n\tqboolean\t\tdone_downloading = true;\n\n\tMSG_Init( &msg, \"Resource Batch\", data, sizeof( data ));\n\n\t// client resources is not precached by server\n\tif( initialize ) CL_AddClientResources();\n\n\tfor( p = cl.resourcesneeded.pNext; p && p != &cl.resourcesneeded; p = n )\n\t{\n\t\tn = p->pNext;\n\n\t\tif( !FBitSet( p->ucFlags, RES_WASMISSING ))\n\t\t{\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( cls.state == ca_active && !cl_download_ingame.value )\n\t\t{\n\t\t\tCon_Printf( \"skipping in game download of %s\\n\", p->szFileName );\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch( p->type )\n\t\t{\n\t\tcase t_sound:\n\t\tcase t_model:\n\t\tcase t_eventscript:\n\t\t\tif( !CL_CheckFile( &msg, p ))\n\t\t\t{\n\t\t\t\tdone_downloading = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tbreak;\n\t\tcase t_skin:\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tbreak;\n\t\tcase t_decal:\n\t\t\tif( !HPAK_GetDataPointer( hpk_custom_file.string, p, NULL, NULL ))\n\t\t\t{\n\t\t\t\tif( !FBitSet( p->ucFlags, RES_REQUESTED ))\n\t\t\t\t{\n\t\t\t\t\tMSG_BeginClientCmd( &msg, clc_stringcmd );\n\t\t\t\t\tMSG_WriteStringf( &msg, \"dlfile !MD5%s\", MD5_Print( p->rgucMD5_hash ));;\n\t\t\t\t\tSetBits( p->ucFlags, RES_REQUESTED );\n\t\t\t\t\tdone_downloading = false;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tbreak;\n\t\tcase t_generic:\n\t\t\tif( !COM_IsSafeFileToDownload( p->szFileName ))\n\t\t\t{\n\t\t\t\tCL_RemoveFromResourceList( p );\n\t\t\t\tMem_Free( p );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif( !CL_CheckFile( &msg, p ))\n\t\t\t\tbreak;\n\t\t\tCL_MoveToOnHandList( p );\n\t\t\tbreak;\n\t\tcase t_world:\n\t\t\tASSERT( 0 );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( cls.state != ca_disconnected )\n\t{\n\t\tif( done_downloading && CL_PrecacheResources( ))\n\t\t{\n\t\t\tCL_RegisterResources( &msg, cls.legacymode );\n\t\t}\n\n\t\tNetchan_CreateFragments( &cls.netchan, &msg );\n\t\tNetchan_FragSend( &cls.netchan );\n\t}\n}\n\nint CL_EstimateNeededResources( void )\n{\n\tresource_t\t*p;\n\tint\t\tnTotalSize = 0;\n\n\tfor( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )\n\t{\n\t\tswitch( p->type )\n\t\t{\n\t\tcase t_sound:\n\t\t\tif( p->szFileName[0] != '*' && !FS_FileExists( va( DEFAULT_SOUNDPATH \"%s\", p->szFileName ), false ) )\n\t\t\t{\n\t\t\t\tSetBits( p->ucFlags, RES_WASMISSING );\n\t\t\t\tnTotalSize += p->nDownloadSize;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_model:\n\t\t\tif( p->szFileName[0] != '*' && !FS_FileExists( p->szFileName, false ) )\n\t\t\t{\n\t\t\t\tSetBits( p->ucFlags, RES_WASMISSING );\n\t\t\t\tnTotalSize += p->nDownloadSize;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_skin:\n\t\tcase t_generic:\n\t\tcase t_eventscript:\n\t\t\tif( !FS_FileExists( p->szFileName, false ) )\n\t\t\t{\n\t\t\t\tSetBits( p->ucFlags, RES_WASMISSING );\n\t\t\t\tnTotalSize += p->nDownloadSize;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_decal:\n\t\t\tif( FBitSet( p->ucFlags, RES_CUSTOM ))\n\t\t\t{\n\t\t\t\tSetBits( p->ucFlags, RES_WASMISSING );\n\t\t\t\tnTotalSize += p->nDownloadSize;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase t_world:\n\t\t\tASSERT( 0 );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn nTotalSize;\n}\n\nstatic void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom )\n{\n\tresourceinfo_t\tri;\n\n\tif( COM_CheckString( pszMessage ))\n\t\tCon_DPrintf( \"%s\", pszMessage );\n\n\tcls.dl.nTotalSize = COM_SizeofResourceList( &cl.resourcesneeded, &ri );\n\tcls.dl.nTotalToTransfer = CL_EstimateNeededResources();\n\n\tif( bCustom )\n\t{\n\t\tcls.dl.custom = true;\n\t}\n\telse\n\t{\n\t\tHTTP_ResetProcessState();\n\n\t\tcls.state = ca_validate;\n\t\tcls.dl.custom = false;\n\t}\n\n\tcls.dl.doneregistering = false;\n\tcls.dl.fLastStatusUpdate = host.realtime;\n\tcls.dl.nRemainingToTransfer = cls.dl.nTotalToTransfer;\n\tmemset( cls.dl.rgStats, 0, sizeof( cls.dl.rgStats ));\n\tcls.dl.nCurStat = 0;\n\n\tCL_BatchResourceRequest( !bCustom );\n}\n\nstatic customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type )\n{\n\tcustomization_t\t*pList;\n\n\tfor( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext )\n\t{\n\t\tif( pList->resource.type == type )\n\t\t\treturn pList;\n\t}\n\treturn NULL;\n}\n\nstatic void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove )\n{\n\tcustomization_t\t*pList;\n\tcustomization_t\t*pNext;\n\n\tfor( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext )\n\t{\n\t\tpNext = pList->pNext;\n\n\t\tif( pRemove != pList )\n\t\t\tcontinue;\n\n\t\tif( pList->bInUse && pList->pBuffer )\n\t\t\tMem_Free( pList->pBuffer );\n\n\t\tif( pList->bInUse && pList->pInfo )\n\t\t{\n\t\t\tif( pList->resource.type == t_decal )\n\t\t\t{\n\t\t\t\tif( cls.state == ca_active )\n\t\t\t\t\tref.dllFuncs.R_DecalRemoveAll( pList->nUserData1 );\n\t\t\t\tFS_FreeImage( pList->pInfo );\n\t\t\t}\n\t\t}\n\n\t\tcl.players[nPlayerNum].customdata.pNext = pNext;\n\t\tMem_Free( pList );\n\t\tbreak;\n\t}\n}\n\n/*\n==================\nCL_ParseCustomization\n\n==================\n*/\nvoid CL_ParseCustomization( sizebuf_t *msg )\n{\n\tcustomization_t\t*pExistingCustomization;\n\tcustomization_t\t*pList;\n\tqboolean\t\tbFound;\n\tresource_t\t*pRes;\n\tint\t\ti;\n\n\ti = MSG_ReadByte( msg );\n\tif( i >= MAX_CLIENTS )\n\t\tHost_Error( \"Bogus player index during customization parsing.\\n\" );\n\n\tpRes = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\tpRes->type = MSG_ReadByte( msg );\n\n\tQ_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName ));\n\tpRes->nIndex = MSG_ReadShort( msg );\n\tpRes->nDownloadSize = MSG_ReadLong( msg );\n\tpRes->ucFlags = MSG_ReadByte( msg ) & ~RES_WASMISSING;\n\tpRes->pNext = pRes->pPrev = NULL;\n\n\tif( FBitSet( pRes->ucFlags, RES_CUSTOM ))\n\t\tMSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 );\n\tpRes->playernum = i;\n\n\tif( !cl_allow_download.value )\n\t{\n\t\tCon_DPrintf( \"Refusing new resource, cl_allowdownload set to 0\\n\" );\n\t\tMem_Free( pRes );\n\t\treturn;\n\t}\n\n\tif( cls.state == ca_active && !cl_download_ingame.value )\n\t{\n\t\tCon_DPrintf( \"Refusing new resource, cl_download_ingame set to 0\\n\" );\n\t\tMem_Free( pRes );\n\t\treturn;\n\t}\n\n\tpExistingCustomization = CL_PlayerHasCustomization( i, pRes->type );\n\n\tif( pExistingCustomization )\n\t\tCL_RemoveCustomization( i, pExistingCustomization );\n\tbFound = false;\n\n\tfor( pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )\n\t{\n\t\tif( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tbFound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( HPAK_GetDataPointer( hpk_custom_file.string, pRes, NULL, NULL ))\n\t{\n\t\tqboolean\tbError = false;\n\n\t\tif( !bFound )\n\t\t{\n\t\t\tpList = &cl.players[pRes->playernum].customdata;\n\n\t\t\tif( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL ))\n\t\t\t\tbError = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_DPrintf( \"Duplicate resource ignored for local client\\n\" );\n\t\t}\n\n\t\tif( bError ) Con_DPrintf( \"Error loading customization\\n\" );\n\t\tMem_Free( pRes );\n\t}\n\telse\n\t{\n\t\tSetBits( pRes->ucFlags, RES_WASMISSING );\n\t\tCL_AddToResourceList( pRes, &cl.resourcesneeded );\n\t\tCon_Printf( \"Requesting %s from server\\n\", pRes->szFileName );\n\t\tCL_StartResourceDownloading( \"Custom resource propagation...\\n\", true );\n\t}\n}\n\n/*\n==================\nCL_ParseResourceRequest\n\n==================\n*/\nvoid CL_ParseResourceRequest( sizebuf_t *msg )\n{\n\tbyte\tbuffer[MAX_INIT_MSG];\n\tint\ti, arg, nStartIndex;\n\tsizebuf_t\tsbuf;\n\n\tMSG_Init( &sbuf, \"ResourceBlock\", buffer, sizeof( buffer ));\n\n\targ = MSG_ReadLong( msg );\n\tnStartIndex = MSG_ReadLong( msg );\n\n\tif( cl.servercount != arg )\n\t\treturn;\n\n\tif( nStartIndex < 0 && nStartIndex > cl.num_resources )\n\t\treturn;\n\n\tMSG_BeginClientCmd( &sbuf, clc_resourcelist );\n\tMSG_WriteShort( &sbuf, cl.num_resources );\n\n\tfor( i = nStartIndex; i < cl.num_resources; i++ )\n\t{\n\t\tMSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );\n\t\tMSG_WriteByte( &sbuf, cl.resourcelist[i].type );\n\t\tMSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );\n\t\tMSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );\n\t\tMSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );\n\n\t\tif( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))\n\t\t\tMSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );\n\t}\n\n\t// a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always\n\t// write to the buffer\n\t// if( MSG_GetNumBytesWritten( &sbuf ) > 0 )\n\t{\n\t\tNetchan_CreateFragments( &cls.netchan, &sbuf );\n\t\tNetchan_FragSend( &cls.netchan );\n\t}\n}\n\n/*\n==================\nCL_CreateCustomizationList\n\nloading custom decal for self\n==================\n*/\nstatic void CL_CreateCustomizationList( void )\n{\n\tresource_t\t*pResource;\n\tplayer_info_t\t*pPlayer;\n\tint\t\ti;\n\n\tpPlayer = &cl.players[cl.playernum];\n\tpPlayer->customdata.pNext = NULL;\n\n\tfor( i = 0; i < cl.num_resources; i++ )\n\t{\n\t\tpResource = &cl.resourcelist[i];\n\n\t\tif( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL ))\n\t\t\tCon_Printf( \"problem with client customization %s, ignoring...\", pResource->szFileName );\n\t}\n}\n\n/*\n==================\nCL_ParseFileTransferFailed\n\n==================\n*/\nvoid CL_ParseFileTransferFailed( sizebuf_t *msg )\n{\n\tconst char\t*name = MSG_ReadString( msg );\n\n\tif( !cls.demoplayback )\n\t\tCL_ProcessFile( false, name );\n}\n\n/*\n=====================================================================\n\n  SERVER CONNECTING MESSAGES\n\n=====================================================================\n*/\n/*\n==================\nCL_ParseServerData\n==================\n*/\nvoid CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )\n{\n\tchar\tgamefolder[MAX_QPATH];\n\tstring\tmapfile;\n\tqboolean\tbackground;\n\tint\ti, required_version;\n\tuint32_t\tmapCRC;\n\n\tHPAK_CheckSize( hpk_custom_file.string );\n\n\tswitch( proto )\n\t{\n\tcase PROTO_LEGACY:\n\t\trequired_version = PROTOCOL_LEGACY_VERSION;\n\t\tCon_Reportf( \"Legacy serverdata packet received.\\n\" );\n\t\tbreak;\n\tcase PROTO_GOLDSRC:\n\t\trequired_version = PROTOCOL_GOLDSRC_VERSION;\n\t\tCon_Reportf( \"GoldSrc serverdata packet received.\\n\" );\n\t\tbreak;\n\tdefault:\n\t\trequired_version = PROTOCOL_VERSION;\n\t\tCon_Reportf( \"Serverdata packet received.\\n\" );\n\t\tbreak;\n\t}\n\n\tcls.timestart = Sys_DoubleTime();\n\tcls.demowaiting = false;\t// server is changed\n\n\t// wipe the client_t struct\n\tif( !cls.changelevel && !cls.changedemo )\n\t\tCL_ClearState ();\n\n\t// Re-init hud video, especially if we changed game directories\n\tclgame.dllFuncs.pfnVidInit();\n\n\tcls.state = ca_connected;\n\n\t// parse protocol version number\n\ti = MSG_ReadLong( msg );\n\tif( i != required_version ) // GoldSrc protocol version is 48, same as Xash3D 48\n\t\tHost_Error( \"Server use invalid protocol (%i should be %i)\\n\", i, required_version );\n\n\tcl.servercount = MSG_ReadLong( msg );\n\tcl.checksum = MSG_ReadLong( msg );\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tbyte clientdllmd5[16];\n\t\tconst char *s;\n\n\t\tMSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ));\n\t\tcl.maxclients = MSG_ReadByte( msg );\n\t\tcl.playernum = MSG_ReadByte( msg );\n\t\tCOM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff );\n\n\t\tMSG_SeekToBit( msg, sizeof( uint8_t ) << 3, SEEK_CUR ); // quake leftover, coop flag\n\n\t\tQ_strncpy( gamefolder, MSG_ReadString( msg ), sizeof( gamefolder ));\n\t\tCon_Printf( \"Remote host: %s\\n\", MSG_ReadString( msg ));\n\t\tQ_strncpy( clgame.mapname, COM_FileWithoutPath( MSG_ReadString( msg )), sizeof( clgame.mapname ));\n\t\tCOM_StripExtension( clgame.mapname );\n\n\t\ts = MSG_ReadString( msg );\n\t\tif( COM_CheckStringEmpty( s ))\n\t\t\tCon_Printf( \"Server map cycle: %s\\n\", s ); // VALVEWHY?\n\n\t\tif( MSG_ReadByte( msg ))\n\t\t\tCon_Printf( \"Uh, server says it's VAC2 secured.\\n\" );\n\n\t\tbackground = false;\n\t\tclgame.maxEntities = GI->max_edicts + (( cl.maxclients - 1 ) * 15 );\n\t\tclgame.maxEntities = bound( MIN_LEGACY_EDICTS, clgame.maxEntities, MAX_GOLDSRC_EDICTS );\n\t\tclgame.maxModels = 512; // ???\n\t\tQ_strncpy( clgame.maptitle, clgame.mapname, sizeof( clgame.maptitle ));\n\n\t\tHost_ValidateEngineFeatures( 0, 0 );\n\t}\n\telse\n\t{\n\t\tuint32_t mask;\n\n\t\tcl.playernum = MSG_ReadByte( msg );\n\t\tcl.maxclients = MSG_ReadByte( msg );\n\t\tclgame.maxEntities = MSG_ReadWord( msg );\n\t\tif( proto == PROTO_LEGACY )\n\t\t{\n\t\t\tclgame.maxEntities = bound( MIN_LEGACY_EDICTS, clgame.maxEntities, MAX_LEGACY_EDICTS );\n\t\t\tclgame.maxModels = 512; // ???\n\t\t\tmask = ENGINE_LEGACY_FEATURES_MASK;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tclgame.maxEntities = bound( MIN_EDICTS, clgame.maxEntities, MAX_EDICTS );\n\t\t\tclgame.maxModels = MSG_ReadWord( msg );\n\t\t\tmask = ENGINE_FEATURES_MASK;\n\t\t}\n\t\tQ_strncpy( clgame.mapname, MSG_ReadString( msg ), sizeof( clgame.mapname ));\n\t\tQ_strncpy( clgame.maptitle, MSG_ReadString( msg ), sizeof( clgame.maptitle ));\n\t\tbackground = MSG_ReadOneBit( msg );\n\t\tQ_strncpy( gamefolder, MSG_ReadString( msg ), sizeof( gamefolder ));\n\t\tHost_ValidateEngineFeatures( mask, MSG_ReadDword( msg ));\n\n\t\tif( proto != PROTO_LEGACY )\n\t\t{\n\t\t\t// receive the player hulls\n\t\t\tfor( i = 0; i < MAX_MAP_HULLS * 3; i++ )\n\t\t\t{\n\t\t\t\thost.player_mins[i/3][i%3] = MSG_ReadChar( msg );\n\t\t\t\thost.player_maxs[i/3][i%3] = MSG_ReadChar( msg );\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_snprintf( mapfile, sizeof( mapfile ), \"maps/%s.bsp\", clgame.mapname );\n\tif( CRC32_MapFile( &cl.worldmapCRC, mapfile, cl.maxclients > 1 ))\n\t{\n\t\t// validate map checksum\n\t\tif( cl.worldmapCRC != cl.checksum )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Your map [%s] differs from the server's.\\n\", clgame.mapname );\n\t\t\tCL_Disconnect_f(); // for local game, call EndGame\n\t\t\tHost_AbortCurrentFrame(); // to avoid svc_bad\n\t\t}\n\t}\n\n\tif( clgame.maxModels > MAX_MODELS )\n\t\tCon_Printf( S_WARN \"server model limit is above client model limit %i > %i\\n\", clgame.maxModels, MAX_MODELS );\n\n\tif( Con_FixedFont( ))\n\t{\n\t\t// seperate the printfs so the server message can have a color\n\t\tCon_Print( \"\\n\\35\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\37\\n\" );\n\t\tCon_Print( va( \"%c%s\\n\\n\", 2, clgame.maptitle ));\n\t}\n\n\t// multiplayer game?\n\tif( cl.maxclients > 1 )\n\t{\n\t\t// allow console in multiplayer games\n\t\thost.allow_console = true;\n\n\t\t// loading user settings\n\t\tCSCR_LoadDefaultCVars( \"user.scr\" );\n\n\t\tif( r_decals.value > mp_decals.value )\n\t\t\tCvar_DirectSet( &r_decals, mp_decals.string );\n\t}\n\telse Cvar_DirectSet( &r_decals, NULL );\n\n\t// set the background state\n\tif( cls.demoplayback && ( cls.demonum != -1 ))\n\t\tcl.background = true;\n\telse cl.background = background;\n\n\tif( cl.background )\t// tell the game parts about background state\n\t\tCvar_FullSet( \"cl_background\", \"1\", FCVAR_READ_ONLY );\n\telse Cvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\n\tif( !cls.changelevel )\n\t{\n\t\t// continue playing if we are changing level\n\t\tS_StopBackgroundTrack ();\n\t}\n\n\tif( !cls.changedemo )\n\t\tUI_SetActiveMenu( cl.background );\n\telse if( !cls.demoplayback )\n\t\tKey_SetKeyDest( key_menu );\n\n\t// don't reset cursor in background mode\n\tif( cl.background )\n\t\tIN_MouseRestorePos();\n\n\t// will be changed later\n\tcl.viewentity = cl.playernum + 1;\n\tgameui.globals->maxClients = cl.maxclients;\n\tQ_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle ));\n\n\tif( !cls.changelevel && !cls.changedemo )\n\t\tCL_InitEdicts( cl.maxclients ); // re-arrange edicts\n\n\t// get splash name\n\tif( cls.demoplayback && ( cls.demonum != -1 ))\n\t\tCvar_Set( \"cl_levelshot_name\", va( \"levelshots/%s_%s\", cls.demoname, refState.wideScreen ? \"16x9\" : \"4x3\" ));\n\telse Cvar_Set( \"cl_levelshot_name\", va( \"levelshots/%s_%s\", clgame.mapname, refState.wideScreen ? \"16x9\" : \"4x3\" ));\n\tCvar_SetValue( \"scr_loading\", 0.0f ); // reset progress bar\n\n\tif(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background )\n\t{\n\t\tif( !FS_FileExists( va( \"%s.bmp\", cl_levelshot_name.string ), true ))\n\t\t\tCvar_Set( \"cl_levelshot_name\", \"*black\" ); // render a black screen\n\t\tcls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime)\n\t}\n\n\tfor( i = 0; i < MAX_CLIENTS; i++ )\n\t\tCOM_ClearCustomizationList( &cl.players[i].customdata, true );\n\tCL_CreateCustomizationList();\n\n\t// request resources from server\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tCL_ServerCommand( true, \"sendres\" );\n\t}\n\telse if( proto != PROTO_LEGACY )\n\t{\n\t\tCL_ServerCommand( true, \"sendres %i\\n\", cl.servercount );\n\t}\n\n\tmemset( &clgame.movevars, 0, sizeof( clgame.movevars ));\n\tmemset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars ));\n\tmemset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint ));\n\tcl.video_prepped = false;\n\tcl.audio_prepped = false;\n}\n\n/*\n===================\nCL_ParseClientData\n===================\n*/\nvoid CL_ParseClientData( sizebuf_t *msg, connprotocol_t proto )\n{\n\tfloat\t\tparsecounttime;\n\tint\t\ti, j, command_ack;\n\tclientdata_t\t*from_cd, *to_cd;\n\tweapon_data_t\t*from_wd, *to_wd;\n\tweapon_data_t\tnullwd[64];\n\tclientdata_t\tnullcd;\n\tframe_t\t\t*frame;\n\tint\t\tidx;\n\n\t// This is the last movement that the server ack'd\n\tcommand_ack = cls.netchan.incoming_acknowledged;\n\n\t// this is the frame update that this message corresponds to\n\ti = cls.netchan.incoming_sequence;\n\n\t// did we drop some frames?\n\tif( i > cl.last_incoming_sequence + 1 )\n\t{\n\t\t// mark as dropped\n\t\tfor( j = cl.last_incoming_sequence + 1; j < i; j++ )\n\t\t{\n\t\t\tif( cl.frames[j & CL_UPDATE_MASK].receivedtime >= 0.0 )\n\t\t\t{\n\t\t\t\tcl.frames[j & CL_UPDATE_MASK].receivedtime = -1.0f;\n\t\t\t\tcl.frames[j & CL_UPDATE_MASK].latency = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tcl.parsecount = i;\t\t\t\t\t// ack'd incoming messages.\n\tcl.parsecountmod = cl.parsecount & CL_UPDATE_MASK;\t// index into window.\n\tframe = &cl.frames[cl.parsecountmod];\t\t\t// frame at index.\n\n\tframe->time = cl.mtime[0];\t\t\t\t// mark network received time\n\tframe->receivedtime = host.realtime;\t\t\t// time now that we are parsing.\n\n\tmemset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t ));\n\n\t// send time for that frame.\n\tparsecounttime = cl.commands[command_ack & CL_UPDATE_MASK].senttime;\n\n\t// current time that we got a response to the command packet.\n\tcl.commands[command_ack & CL_UPDATE_MASK].receivedtime = host.realtime;\n\n\tif( cl.last_command_ack != -1 )\n\t{\n\t\tint\t\tlast_predicted;\n\t\tclientdata_t\t*pcd, *ppcd;\n\t\tentity_state_t\t*ps, *pps;\n\t\tweapon_data_t\t*wd, *pwd;\n\n\t\tif( !cls.spectator )\n\t\t{\n\t\t\tlast_predicted = ( cl.last_incoming_sequence + ( command_ack - cl.last_command_ack )) & CL_UPDATE_MASK;\n\n\t\t\tpps = &cl.predicted_frames[last_predicted].playerstate;\n\t\t\tpwd = cl.predicted_frames[last_predicted].weapondata;\n\t\t\tppcd = &cl.predicted_frames[last_predicted].client;\n\n\t\t\tps = &frame->playerstate[cl.playernum];\n\t\t\twd = frame->weapondata;\n\t\t\tpcd = &frame->clientdata;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tps = &cls.spectator_state.playerstate;\n\t\t\tpps = &cls.spectator_state.playerstate;\n\t\t\tpcd = &cls.spectator_state.client;\n\t\t\tppcd = &cls.spectator_state.client;\n\t\t\twd = cls.spectator_state.weapondata;\n\t\t\tpwd = cls.spectator_state.weapondata;\n\t\t}\n\n\t\tclgame.dllFuncs.pfnTxferPredictionData( ps, pps, pcd, ppcd, wd, pwd );\n\t}\n\n\t// do this after all packets read for this frame?\n\tcl.last_command_ack = cls.netchan.incoming_acknowledged;\n\tcl.last_incoming_sequence = cls.netchan.incoming_sequence;\n\n\tif( !cls.demoplayback )\n\t{\n\t\t// calculate latency of this frame.\n\t\t// sent time is set when usercmd is sent to server in CL_Move\n\t\t// this is the # of seconds the round trip took.\n\t\tfloat\tlatency = host.realtime - parsecounttime;\n\n\t\t// fill into frame latency\n\t\tframe->latency = latency;\n\n\t\t// negative latency makes no sense.  Huge latency is a problem.\n\t\tif( latency >= 0.0f && latency <= 2.0f )\n\t\t{\n\t\t\t// drift the average latency towards the observed latency\n\t\t\t// if round trip was fastest so far, just use that for latency value\n\t\t\t// otherwise, move in 1 ms steps toward observed channel latency.\n\t\t\tif( latency < cls.latency )\n\t\t\t\tcls.latency = latency;\n\t\t\telse cls.latency += 0.001f; // drift up, so corrections are needed\n\t\t}\n\t}\n\telse\n\t{\n\t\tframe->latency = 0.0f;\n\t}\n\n\t// clientdata for spectators ends here\n\tif( cls.spectator )\n\t{\n\t\tcl.local.health = 1;\n\t\treturn;\n\t}\n\n\tto_cd = &frame->clientdata;\n\tto_wd = frame->weapondata;\n\n\t// clear to old value before delta parsing\n\tif( MSG_ReadOneBit( msg ))\n\t{\n\t\tint\tdelta_sequence = MSG_ReadByte( msg );\n\n\t\tfrom_cd = &cl.frames[delta_sequence & CL_UPDATE_MASK].clientdata;\n\t\tfrom_wd = cl.frames[delta_sequence & CL_UPDATE_MASK].weapondata;\n\t}\n\telse\n\t{\n\t\tmemset( &nullcd, 0, sizeof( nullcd ));\n\t\tmemset( nullwd, 0, sizeof( nullwd ));\n\t\tfrom_cd = &nullcd;\n\t\tfrom_wd = nullwd;\n\t}\n\n\tif( proto == PROTO_GOLDSRC )\n\t\tDelta_ReadGSFields( msg, DT_CLIENTDATA_T, from_cd, to_cd, cl.mtime[0] );\n\telse MSG_ReadClientData( msg, from_cd, to_cd, cl.mtime[0] );\n\n\tfor( i = 0; i < 64; i++ )\n\t{\n\t\t// check for end of weapondata (and clientdata_t message)\n\t\tif( !MSG_ReadOneBit( msg )) break;\n\n\t\t// read the weapon idx\n\t\tidx = MSG_ReadUBitLong( msg, proto == PROTO_LEGACY ? MAX_LEGACY_WEAPON_BITS : MAX_WEAPON_BITS );\n\n\t\tif( proto == PROTO_GOLDSRC )\n\t\t\tDelta_ReadGSFields( msg, DT_WEAPONDATA_T, &from_wd[idx], &to_wd[idx], cl.mtime[0] );\n\t\telse MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], cl.mtime[0] );\n\t}\n\n\t// make a local copy of physinfo\n\tQ_strncpy( cls.physinfo, frame->clientdata.physinfo, sizeof( cls.physinfo ));\n\n\tcl.local.maxspeed = frame->clientdata.maxspeed;\n\tcl.local.pushmsec = frame->clientdata.pushmsec;\n\tcl.local.weapons = frame->clientdata.weapons;\n\tcl.local.health = frame->clientdata.health;\n}\n\n/*\n==================\nCL_ParseBaseline\n==================\n*/\nvoid CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto )\n{\n\tconst entity_state_t nullstate = { 0 };\n\n\tDelta_InitClient ();\t// finalize client delta's\n\n\twhile( 1 )\n\t{\n\t\tcl_entity_t *ent;\n\t\tqboolean player;\n\t\tint newnum;\n\n\t\tif( proto == PROTO_LEGACY )\n\t\t{\n\t\t\tnewnum = MSG_ReadWord( msg );\n\t\t}\n\t\telse if( proto == PROTO_GOLDSRC )\n\t\t{\n\t\t\tuint value = MSG_ReadWord( msg );\n\n\t\t\tif( value == 0xffff ) break; // end of baselines\n\n\t\t\tMSG_SeekToBit( msg, -16, SEEK_CUR );\n\t\t\tnewnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnewnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\t\t\tif( newnum == LAST_EDICT ) break; // end of baselines\n\t\t}\n\n\t\tplayer = CL_IsPlayerIndex( newnum );\n\n\t\tif( newnum >= clgame.maxEntities )\n\t\t\tHost_Error( \"%s: no free edicts\\n\", __func__ );\n\n\t\tent = CL_EDICT_NUM( newnum );\n\t\tent->prevstate = nullstate;\n\t\tent->index = newnum;\n\n\t\tif( proto == PROTO_GOLDSRC )\n\t\t{\n\t\t\tint type = MSG_ReadUBitLong( msg, 2 );\n\t\t\tint delta_type;\n\n\t\t\tif( player ) delta_type = DT_ENTITY_STATE_PLAYER_T;\n\t\t\telse if( type != ENTITY_NORMAL ) delta_type = DT_CUSTOM_ENTITY_STATE_T;\n\t\t\telse delta_type = DT_ENTITY_STATE_T;\n\n\t\t\tDelta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f );\n\t\t\tent->baseline.entityType = type;\n\t\t}\n\t\telse MSG_ReadDeltaEntity( msg, &nullstate, &ent->baseline, newnum, player, 1.0f );\n\n\t\tif( proto == PROTO_LEGACY )\n\t\t\tbreak; // only one baseline allowed in legacy protocol\n\t}\n\n\tif( proto != PROTO_LEGACY )\n\t{\n\t\tint i;\n\n\t\tcl.instanced_baseline_count = MSG_ReadUBitLong( msg, 6 );\n\n\t\tfor( i = 0; i < cl.instanced_baseline_count; i++ )\n\t\t{\n\t\t\tif( proto == PROTO_GOLDSRC )\n\t\t\t\tDelta_ReadGSFields( msg, DT_ENTITY_STATE_T, &nullstate, &cl.instanced_baseline[i], 1.0f );\n\t\t\telse\n\t\t\t{\n\t\t\t\tint newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );\n\t\t\t\tMSG_ReadDeltaEntity( msg, &nullstate, &cl.instanced_baseline[i], newnum, false, 1.0f );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nCL_ParseLightStyle\n================\n*/\nvoid CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tstyle;\n\tconst char\t*s;\n\tfloat\t\tf = cl.mtime[0];\n\n\tstyle = MSG_ReadByte( msg );\n\ts = MSG_ReadString( msg );\n\tif( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE )\n\t\tf = MSG_ReadFloat( msg );\n\n\tCL_SetLightstyle( style, s, f );\n}\n\n/*\n================\nCL_ParseSetAngle\n\nset the view angle to this absolute value\n================\n*/\nvoid CL_ParseSetAngle( sizebuf_t *msg )\n{\n\tMSG_ReadVec3Angles( msg, cl.viewangles );\n}\n\n/*\n================\nCL_ParseAddAngle\n\nadd the view angle yaw\n================\n*/\nvoid CL_ParseAddAngle( sizebuf_t *msg )\n{\n\tpred_viewangle_t\t*a;\n\tfloat\t\tdelta_yaw;\n\n\tdelta_yaw = MSG_ReadBitAngle( msg, 16 );\n#if 0\n\tcl.viewangles[YAW] += delta_yaw;\n\treturn;\n#endif\n\t// update running counter\n\tcl.addangletotal += delta_yaw;\n\n\t// select entry into circular buffer\n\tcl.angle_position = (cl.angle_position + 1) & ANGLE_MASK;\n\ta = &cl.predicted_angle[cl.angle_position];\n\n\t// record update\n\ta->starttime = cl.mtime[0];\n\ta->total = cl.addangletotal;\n}\n\n/*\n================\nCL_ParseCrosshairAngle\n\noffset crosshair angles\n================\n*/\nvoid CL_ParseCrosshairAngle( sizebuf_t *msg )\n{\n\tcl.crosshairangle[0] = MSG_ReadChar( msg ) * 0.2f;\n\tcl.crosshairangle[1] = MSG_ReadChar( msg ) * 0.2f;\n\tcl.crosshairangle[2] = 0.0f; // not used for screen space\n}\n\n/*\n================\nCL_ParseRestore\n\nreading decals, etc.\n================\n*/\nvoid CL_ParseRestore( sizebuf_t *msg )\n{\n\tstring\t\tfilename;\n\tint\t\ti, mapCount;\n\tchar\t\t*pMapName;\n\n\t// mapname.HL2\n\tQ_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));\n\tmapCount = MSG_ReadByte( msg );\n\n\t// g-cont. acutally in Xash3D this does nothing.\n\t// decals already restored on a server, and correctly transferred through levels\n\t// but i'm leave this message for backward compatibility\n\tfor( i = 0; i < mapCount; i++ )\n\t{\n\t\tpMapName = MSG_ReadString( msg );\n\t\tCon_Printf( \"Loading decals from %s\\n\", pMapName );\n\t}\n}\n\n/*\n================\nCL_RegisterUserMessage\n\nregister new user message or update existing\n================\n*/\nvoid CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )\n{\n\tchar *pszName;\n\tchar szName[17];\n\tint size;\n\tint svc_num = MSG_ReadByte( msg );\n\n\tif( proto == PROTO_LEGACY || proto == PROTO_GOLDSRC )\n\t{\n\t\tsize = MSG_ReadByte( msg );\n\t\tif( size == UINT8_MAX )\n\t\t\tsize = -1;\n\t}\n\telse\n\t{\n\t\tsize = MSG_ReadWord( msg );\n\t\tif( size == UINT16_MAX )\n\t\t\tsize = -1;\n\t}\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tMSG_ReadBytes( msg, szName, sizeof( szName ) - 1 );\n\t\tszName[16] = 0;\n\t\tpszName = szName;\n\t}\n\telse pszName = MSG_ReadString( msg );\n\n\tCL_LinkUserMessage( pszName, svc_num, size );\n}\n\n/*\n================\nCL_UpdateUserinfo\n\ncollect userinfo from all players\n================\n*/\nvoid CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tslot, id;\n\tqboolean\t\tactive;\n\tplayer_info_t\t*player;\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tslot = MSG_ReadByte( msg );\n\t\tid = MSG_ReadLong( msg );\n\t\tactive = true;\n\t}\n\telse if( proto == PROTO_LEGACY )\n\t{\n\t\tslot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );\n\t\tid = 0; // bogus\n\t\tactive = MSG_ReadOneBit( msg ) ? true : false;\n\t}\n\telse\n\t{\n\t\tslot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );\n\t\tid = MSG_ReadLong( msg );\t// unique user ID\n\t\tactive = MSG_ReadOneBit( msg ) ? true : false;\n\t}\n\n\tif( slot >= MAX_CLIENTS )\n\t\tHost_Error( \"%s: svc_updateuserinfo >= MAX_CLIENTS\\n\", __func__ );\n\n\tplayer = &cl.players[slot];\n\n\tif( active )\n\t{\n\t\tQ_strncpy( player->userinfo, MSG_ReadString( msg ), sizeof( player->userinfo ));\n\t\tQ_strncpy( player->name, Info_ValueForKey( player->userinfo, \"name\" ), sizeof( player->name ));\n\t\tQ_strncpy( player->model, Info_ValueForKey( player->userinfo, \"model\" ), sizeof( player->model ));\n\t\tplayer->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, \"topcolor\" ));\n\t\tplayer->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, \"bottomcolor\" ));\n\t\tplayer->spectator = Q_atoi( Info_ValueForKey( player->userinfo, \"*hltv\" ));\n\t\tif( proto != PROTO_LEGACY )\n\t\t\tMSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));\n\n\t\tif( proto == PROTO_GOLDSRC && ( !COM_CheckStringEmpty( player->userinfo ) || !COM_CheckStringEmpty( player->name )))\n\t\t\tactive = false;\n\n\t\tif( active && slot == cl.playernum )\n\t\t\tgameui.playerinfo = *player;\n\t}\n\n\n\tif( !active )\n\t{\n\t\tCOM_ClearCustomizationList( &player->customdata, true );\n\n\t\tmemset( player, 0, sizeof( *player ));\n\t}\n\n\t// in GoldSrc userinfo might be empty but userid is always sent as separate value\n\t// thus avoids clean up even after client disconnect\n\tplayer->userid = id;\n}\n\n/*\n==============\nCL_ParseResource\n\ndownloading and precache resource in-game\n==============\n*/\nvoid CL_ParseResource( sizebuf_t *msg )\n{\n\tresource_t\t*pResource;\n\n\tpResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\tpResource->type = MSG_ReadUBitLong( msg, 4 );\n\n\tQ_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));\n\tpResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );\n\tpResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 );\n\tpResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;\n\n\tif( FBitSet( pResource->ucFlags, RES_CUSTOM ))\n\t\tMSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));\n\n\tif( MSG_ReadOneBit( msg ))\n\t\tMSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));\n\n\tif( pResource->type == t_sound && pResource->nIndex > MAX_SOUNDS )\n\t{\n\t\tMem_Free( pResource );\n\t\tHost_Error( \"bad sound index\\n\" );\n\t}\n\n\tif( pResource->type == t_model && pResource->nIndex > MAX_MODELS )\n\t{\n\t\tMem_Free( pResource );\n\t\tHost_Error( \"bad model index\\n\" );\n\t}\n\n\tif( pResource->type == t_eventscript && pResource->nIndex > MAX_EVENTS )\n\t{\n\t\tMem_Free( pResource );\n\t\tHost_Error( \"bad event index\\n\" );\n\t}\n\n\tif( pResource->type == t_generic && pResource->nIndex > MAX_CUSTOM )\n\t{\n\t\tMem_Free( pResource );\n\t\tHost_Error( \"bad file index\\n\" );\n\t}\n\n\tif( pResource->type == t_decal && pResource->nIndex > MAX_DECALS )\n\t{\n\t\tMem_Free( pResource );\n\t\tHost_Error( \"bad decal index\\n\" );\n\t}\n\n\tCL_AddToResourceList( pResource, &cl.resourcesneeded );\n}\n\n/*\n================\nCL_UpdateUserPings\n\ncollect pings and packet lossage from clients\n================\n*/\nvoid CL_UpdateUserPings( sizebuf_t *msg )\n{\n\t// a1ba: there was a MAX_PLAYERS check but it doesn't make sense\n\t// because pings message always ends by null bit\n\twhile( 1 )\n\t{\n\t\tint slot;\n\t\tplayer_info_t *player;\n\n\t\tif( !MSG_ReadOneBit( msg ))\n\t\t\tbreak; // end of message\n\n\t\tslot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );\n\n\t\tif( unlikely( slot >= MAX_CLIENTS ))\n\t\t{\n\t\t\tHost_Error( \"%s: svc_pings > MAX_CLIENTS\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\n\t\tplayer = &cl.players[slot];\n\t\tplayer->ping = MSG_ReadUBitLong( msg, 12 );\n\t\tplayer->packet_loss = MSG_ReadUBitLong( msg, 7 );\n\t}\n}\n\nstatic const char *CL_CheckTypeToString( int check_type )\n{\n\t// renamed so they have same width and look better in console output\n\tswitch( check_type )\n\t{\n\tcase force_exactfile:\n\t\treturn \"exactfile\";\n\tcase force_model_samebounds:\n\t\treturn \"samebounds\";\n\tcase force_model_specifybounds:\n\t\treturn \"specbounds\";\n\tcase force_model_specifybounds_if_avail:\n\t\treturn \"specbounds2\";\n\t}\n\treturn \"unknown\";\n}\n\nstatic void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )\n{\n\tqboolean\t\tuser_changed_diskfile;\n\tvec3_t\t\tmins, maxs;\n\tstring\t\tfilename;\n\tCRC32_t\t\tcrcFile;\n\tbyte\t\tmd5[16] = { 0 };\n\tconsistency_t\t*pc;\n\tint\t\ti, pos;\n\n\tif( !cl.need_force_consistency_response )\n\t\treturn;\n\tcl.need_force_consistency_response = false;\n\n\tMSG_BeginClientCmd( msg, clc_fileconsistency );\n\tpos = MSG_GetNumBytesWritten( msg );\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tMSG_WriteShort( msg, 0 );\n\t\tMSG_StartBitWriting( msg );\n\t}\n\n\tFS_AllowDirectPaths( true );\n\n\tfor( i = 0; i < cl.num_consistency; i++ )\n\t{\n\t\tqboolean have_file = true;\n\n\t\tpc = &cl.consistency_list[i];\n\n\t\tuser_changed_diskfile = false;\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS );\n\n\t\tif( pc->issound )\n\t\t\tQ_snprintf( filename, sizeof( filename ), DEFAULT_SOUNDPATH \"%s\", pc->filename );\n\t\telse Q_strncpy( filename, pc->filename, sizeof( filename ));\n\n\t\tCOM_FixSlashes( filename );\n\t\thave_file = FS_FileExists( filename, false );\n\n\t\tif( Q_strstr( filename, \"models/\" ) && have_file )\n\t\t{\n\t\t\tCRC32_Init( &crcFile );\n\t\t\tCRC32_File( &crcFile, filename );\n\t\t\tcrcFile = CRC32_Final( crcFile );\n\t\t\tuser_changed_diskfile = !Mod_ValidateCRC( filename, crcFile );\n\t\t}\n\n\t\tswitch( pc->check_type )\n\t\t{\n\t\tcase force_exactfile:\n\t\t\t// servers rely on md5 not being initialized after previous file\n\t\t\t// if current file doesn't exist\n\t\t\tMD5_HashFile( md5, filename, NULL );\n\t\t\tmemcpy( &pc->value, md5, sizeof( pc->value ));\n\t\t\tLittleLongSW( pc->value );\n\n\t\t\tif( user_changed_diskfile )\n\t\t\t\tMSG_WriteUBitLong( msg, 0, 32 );\n\t\t\telse MSG_WriteUBitLong( msg, pc->value, 32 );\n\t\t\tbreak;\n\n\t\tcase force_model_specifybounds_if_avail:\n\t\t\tif( have_file )\n\t\t\t{\n\t\t\t\tif( !Mod_GetStudioBounds( filename, mins, maxs ))\n\t\t\t\t\tHost_Error( \"unable to find %s\\n\", filename );\n\n\t\t\t\tif( user_changed_diskfile )\n\t\t\t\t{\n\t\t\t\t\tVectorSet( mins, -9999.9f, -9999.9f, -9999.9f );\n\t\t\t\t\tVectorSet( maxs, 9999.9f, 9999.9f, 9999.9f );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorSet( mins, -1.0f, -1.0f, -1.0f );\n\t\t\t\tVectorCopy( mins, maxs );\n\t\t\t}\n\n\t\t\tMSG_WriteBytes( msg, mins, 12 );\n\t\t\tMSG_WriteBytes( msg, maxs, 12 );\n\t\t\tbreak;\n\t\tcase force_model_samebounds:\n\t\tcase force_model_specifybounds:\n\t\t\tif( !Mod_GetStudioBounds( filename, mins, maxs ))\n\t\t\t\tHost_Error( \"unable to find %s\\n\", filename );\n\t\t\tif( user_changed_diskfile )\n\t\t\t{\n\t\t\t\tVectorSet( mins, -9999.9f, -9999.9f, -9999.9f );\n\t\t\t\tVectorSet( maxs, 9999.9f, 9999.9f, 9999.9f );\n\t\t\t}\n\t\t\tMSG_WriteBytes( msg, mins, 12 );\n\t\t\tMSG_WriteBytes( msg, maxs, 12 );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tHost_Error( \"Unknown consistency type %i\\n\", pc->check_type );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tFS_AllowDirectPaths( false );\n\n\tMSG_WriteOneBit( msg, 0 );\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tint len;\n\t\tMSG_EndBitWriting( msg );\n\n\t\tlen = MSG_GetNumBytesWritten( msg ) - pos - 2;\n\t\t*(short *)&msg->pData[pos] = len;\n\n\t\tCOM_Munge( &msg->pData[pos + 2], len, cl.servercount );\n\t}\n}\n\n/*\n==================\nCL_StartDark\n==================\n*/\nstatic void CL_StartDark( void )\n{\n\tif( v_dark.value )\n\t{\n\t\tscreenfade_t\t\t*sf = &clgame.fade;\n\t\tfloat\t\t\tfadetime = 5.0f;\n\t\tclient_textmessage_t\t*title;\n\n\t\ttitle = CL_TextMessageGet( \"GAMETITLE\" );\n\t\tif( Host_IsQuakeCompatible( ))\n\t\t\tfadetime = 1.0f;\n\n\t\tif( title )\n\t\t{\n\t\t\t// get settings from titles.txt\n\t\t\tsf->fadeEnd = title->holdtime + title->fadeout;\n\t\t\tsf->fadeReset = title->fadeout;\n\t\t}\n\t\telse sf->fadeEnd = sf->fadeReset = fadetime;\n\n\t\tsf->fadeFlags = FFADE_IN;\n\t\tsf->fader = sf->fadeg = sf->fadeb = 0;\n\t\tsf->fadealpha = 255;\n\t\tsf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset;\n\t\tsf->fadeReset += cl.time;\n\t\tsf->fadeEnd += sf->fadeReset;\n\n\t\tCvar_DirectSet( &v_dark, \"0\" );\n\t}\n}\n\n/*\n==================\nCL_RegisterResources\n\nClean up and move to next part of sequence.\n==================\n*/\nvoid CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )\n{\n\tmodel_t\t*mod;\n\tint\ti;\n\n\tif( cls.dl.custom || ( cls.signon == SIGNONS && cls.state == ca_active ) )\n\t{\n\t\tcls.dl.custom = false;\n\t\treturn;\n\t}\n\n\tif( !cls.demoplayback )\n\t\tCL_SendConsistencyInfo( msg, proto );\n\n\t// All done precaching.\n\tcl.worldmodel = CL_ModelHandle( 1 ); // get world pointer\n\n\tif( cl.worldmodel && cl.maxclients > 0 )\n\t{\n\t\tASSERT( clgame.entities != NULL );\n\t\tclgame.entities->model = cl.worldmodel;\n\n\t\tif( !cl.video_prepped && !cl.audio_prepped )\n\t\t{\n\t\t\tCon_Printf( \"Setting up renderer...\\n\" );\n\n\t\t\t// load tempent sprites (glowshell, muzzleflashes etc)\n\t\t\tCL_LoadClientSprites ();\n\n\t\t\t// invalidate all decal indexes\n\t\t\tmemset( cl.decal_index, 0, sizeof( cl.decal_index ));\n\t\t\tcl.video_prepped = true;\n\t\t\tcl.audio_prepped = true;\n\n\t\t\tCL_ClearWorld ();\n\n\t\t\t// load skybox\n\t\t\tR_SetupSky( clgame.movevars.skyName );\n\n\t\t\t// tell rendering system we have a new set of models.\n\t\t\tref.dllFuncs.R_NewMap ();\n\n\t\t\t// check if this map must start from dark screen\n\t\t\tCL_StartDark ();\n\n\t\t\tCL_SetupOverviewParams();\n\n\t\t\t// release unused SpriteTextures\n\t\t\tfor( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )\n\t\t\t{\n\t\t\t\tif( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name ))\n\t\t\t\t\tMod_FreeModel( mod );\n\t\t\t}\n\n\t\t\tMod_FreeUnused ();\n\n\t\t\tif( host_developer.value <= DEV_NONE )\n\t\t\t\tCon_ClearNotify(); // clear any lines of console text\n\n\t\t\t// done with all resources, issue prespawn command.\n\t\t\t// Include server count in case server disconnects and changes level during d/l\n\t\t\tMSG_BeginClientCmd( msg, clc_stringcmd );\n\t\t\tif( proto == PROTO_GOLDSRC )\n\t\t\t{\n\t\t\t\tint32_t crc = cl.worldmapCRC;\n\t\t\t\tCOM_Munge2((byte*)&crc, sizeof( crc ), ( 0xff - cl.servercount ) & 0xff );\n\t\t\t\tMSG_WriteStringf( msg, \"spawn %i %i\", cl.servercount, crc );\n\t\t\t}\n\t\t\telse MSG_WriteStringf( msg, \"spawn %i\", cl.servercount );\n\t\t}\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_ERROR \"client world model is NULL\\n\" );\n\t\tCL_Disconnect();\n\t}\n}\n\nstatic void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint\t\tlastcheck;\n\tint\t\tdelta;\n\tint\t\ti;\n\tint\t\tisdelta;\n\tresource_t\t*pResource;\n\tresource_t\t*skip_crc_change;\n\tint\t\tskip;\n\tconsistency_t\t*pc;\n\tbyte\t\tnullbuffer[32];\n\n\tmemset( nullbuffer, 0, 32 );\n\n\tcl.need_force_consistency_response = MSG_ReadOneBit( msg );\n\tpResource = cl.resourcesneeded.pNext;\n\n\tif( !cl.need_force_consistency_response )\n\t\treturn;\n\n\tif( !pResource )\n\t{\n\t\tHost_Error( \"%s: malformed consistency info packet (resources needed is NULL)\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tif( cl_trace_consistency.value )\n\t\tCon_Printf( \"Server wants consistency of the following resources:\\n\" );\n\n\tskip_crc_change = NULL;\n\tlastcheck = 0;\n\n\twhile( MSG_ReadOneBit( msg ))\n\t{\n\t\tisdelta = MSG_ReadOneBit( msg );\n\n\t\tif( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck;\n\t\telse delta = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_MODEL_BITS : MAX_MODEL_BITS );\n\n\t\tskip = delta - lastcheck;\n\n\t\tfor( i = 0; i < skip; i++ )\n\t\t{\n\t\t\tif( pResource != skip_crc_change && Q_strstr( pResource->szFileName, \"models/\" ))\n\t\t\t\tMod_NeedCRC( pResource->szFileName, false );\n\t\t\tpResource = pResource->pNext;\n\n\t\t\tif( !pResource )\n\t\t\t{\n\t\t\t\tHost_Error( \"%s: malformed consistency info packet (last check %d, delta %d, position %d)\\n\", __func__, lastcheck, delta, i );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif( cl.num_consistency >= MAX_MODELS )\n\t\t\tHost_Error( \"%s: MAX_MODELS limit exceeded (%d)\\n\", __func__, MAX_MODELS );\n\n\t\tpc = &cl.consistency_list[cl.num_consistency];\n\t\tcl.num_consistency++;\n\n\t\tmemset( pc, 0, sizeof( *pc ));\n\t\tpc->filename = pResource->szFileName;\n\t\tpc->issound = (pResource->type == t_sound);\n\t\tpc->orig_index = delta;\n\t\tpc->value = 0;\n\n\t\tif( pResource->type == t_model && memcmp( nullbuffer, pResource->rguc_reserved, 32 ))\n\t\t{\n\t\t\tif( proto == PROTO_GOLDSRC )\n\t\t\t\tCOM_UnMunge( pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), cl.servercount );\n\t\t\tpc->check_type = pResource->rguc_reserved[0];\n\t\t}\n\n\t\tif( cl_trace_consistency.value )\n\t\t\tCon_Printf( \"%s\\t%s\\t%s\\n\", COM_ResourceTypeFromIndex( pResource->type ), CL_CheckTypeToString( pc->check_type ), pc->filename );\n\n\t\tskip_crc_change = pResource;\n\t\tlastcheck = delta;\n\t}\n}\n\n/*\n==============\nCL_ParseResourceList\n\n==============\n*/\nvoid CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )\n{\n\tresource_t\t*pResource;\n\tint\t\ti, total;\n\n\ttotal = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS );\n\n\tfor( i = 0; i < total; i++ )\n\t{\n\t\tpResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\t\tpResource->type = MSG_ReadUBitLong( msg, 4 );\n\n\t\tQ_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));\n\t\tpResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );\n\t\tpResource->nDownloadSize = MSG_ReadBitLong( msg, 24, proto != PROTO_GOLDSRC );\n\t\tpResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;\n\n\t\tif( FBitSet( pResource->ucFlags, RES_CUSTOM ))\n\t\t\tMSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));\n\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tMSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));\n\n\t\tCL_AddToResourceList( pResource, &cl.resourcesneeded );\n\t}\n\n\tCL_ParseConsistencyInfo( msg, proto );\n\n\tCL_StartResourceDownloading( \"Verifying and downloading resources...\\n\", false );\n}\n\n/*\n==================\nCL_ParseVoiceInit\n\n==================\n*/\nvoid CL_ParseVoiceInit( sizebuf_t *msg )\n{\n\tchar *pszCodec = MSG_ReadString( msg );\n\tint quality = MSG_ReadByte( msg );\n\n\tVoice_Init( pszCodec, quality, false ); // init requested codec and the device\n}\n\n/*\n==================\nCL_ParseVoiceData\n\n==================\n*/\nvoid CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto )\n{\n\tint size, idx, frames = 0;\n\tbyte received[VOICE_MAX_DATA_SIZE];\n\n\tidx = MSG_ReadByte( msg ) + 1;\n\n\tif ( idx <= 0 || idx > cl.maxclients )\n\t\treturn;\n\n\t// must notify client.dll about server ack'ing our local client voice data\n\tif( idx == cl.playernum + 1 )\n\t\tVoice_LoopbackAck();\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tsize = MSG_ReadShort( msg );\n\t\tif( size > VOICE_MAX_GS_DATA_SIZE )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Voice data size is too large: %d bytes (max: %d)\\n\", size, VOICE_MAX_GS_DATA_SIZE );\n\t\t\treturn;\n\t\t}\n\t}\n\telse\n\t{\n\t\tframes = MSG_ReadByte( msg );\n\t\tsize = MSG_ReadShort( msg );\n\t\tif( size > VOICE_MAX_DATA_SIZE )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Voice data size is too large: %d bytes (max: %d)\\n\", size, VOICE_MAX_DATA_SIZE );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tsize = Q_min( size, sizeof( received ));\n\n\tif ( !size )\n\t\treturn;\n\n\tMSG_ReadBytes( msg, received, size );\n\n\tVoice_AddIncomingData( idx, received, size, frames );\n}\n\n/*\n==================\nCL_ParseResLocation\n\n==================\n*/\nvoid CL_ParseResLocation( sizebuf_t *msg )\n{\n\tchar *data = MSG_ReadString( msg );\n\tchar token[256];\n\n\tif( Q_strlen( data ) > 256 )\n\t{\n\t\tCon_Printf( S_ERROR \"Resource location too long!\\n\" );\n\t\treturn;\n\t}\n\n\twhile(( data = COM_ParseFile( data, token, sizeof( token ))))\n\t{\n\t\tCon_Reportf( \"Adding %s as download location\\n\", token );\n\t\tcl.http_download = true;\n\t\tHTTP_AddCustomServer( token );\n\t}\n}\n\n/*\n==============\nCL_ParseHLTV\n\nspectator message (hltv)\nsended from game.dll\n==============\n*/\nvoid CL_ParseHLTV( sizebuf_t *msg )\n{\n\tswitch( MSG_ReadByte( msg ))\n\t{\n\tcase HLTV_ACTIVE:\n\t\tcl.proxy_redirect = true;\n\t\tcls.spectator = true;\n\t\tbreak;\n\tcase HLTV_STATUS:\n\t\t\tMSG_ReadLong( msg );\n\t\t\tMSG_ReadShort( msg );\n\t\t\tMSG_ReadWord( msg );\n\t\t\tMSG_ReadLong( msg );\n\t\t\tMSG_ReadLong( msg );\n\t\t\tMSG_ReadWord( msg );\n\t\tbreak;\n\tcase HLTV_LISTEN:\n\t\tcls.signon = SIGNONS;\n#if 1\n\t\tMSG_ReadString( msg );\n#else\n\t\tNET_StringToAdr( MSG_ReadString( msg ), &cls.hltv_listen_address );\n\t\tNET_JoinGroup( cls.netchan.sock, cls.hltv_listen_address );\n#endif\n\t\tSCR_EndLoadingPlaque();\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\n/*\n==============\nCL_ParseDirector\n\nspectator message (director)\nsended from game.dll\n==============\n*/\nvoid CL_ParseDirector( sizebuf_t *msg )\n{\n\tint\tiSize = MSG_ReadByte( msg );\n\tbyte\tpbuf[256];\n\n\t// parse user message into buffer\n\tMSG_ReadBytes( msg, pbuf, iSize );\n\tclgame.dllFuncs.pfnDirectorMessage( iSize, pbuf );\n}\n\n/*\n==============\nCL_ParseScreenShake\n\nSet screen shake\n==============\n*/\nstatic void CL_ParseScreenShake( sizebuf_t *msg )\n{\n\tfloat amplitude = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 ));\n\tfloat duration  = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 ));\n\tfloat frequency = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 8 ));\n\n\t// don't overwrite larger existing shake\n\tif( amplitude > clgame.shake.amplitude )\n\t\tclgame.shake.amplitude = amplitude;\n\n\tclgame.shake.duration = duration;\n\tclgame.shake.time = cl.time + clgame.shake.duration;\n\tclgame.shake.frequency = frequency;\n\tclgame.shake.next_shake = 0.0f; // apply immediately\n}\n\n/*\n==============\nCL_ParseScreenFade\n\nSet screen fade\n==============\n*/\nstatic void CL_ParseScreenFade( sizebuf_t *msg )\n{\n\tfloat\t\tduration, holdTime;\n\tscreenfade_t\t*sf = &clgame.fade;\n\tfloat\t\tflScale;\n\n\tduration = (float)MSG_ReadWord( msg );\n\tholdTime = (float)MSG_ReadWord( msg );\n\tsf->fadeFlags = MSG_ReadShort( msg );\n\tflScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f);\n\n\tsf->fader = MSG_ReadByte( msg );\n\tsf->fadeg = MSG_ReadByte( msg );\n\tsf->fadeb = MSG_ReadByte( msg );\n\tsf->fadealpha = MSG_ReadByte( msg );\n\tsf->fadeSpeed = 0.0f;\n\tsf->fadeEnd = duration * flScale;\n\tsf->fadeReset = holdTime * flScale;\n\n\t// calc fade speed\n\tif( duration > 0 )\n\t{\n\t\tif( FBitSet( sf->fadeFlags, FFADE_OUT ))\n\t\t{\n\t\t\tif( sf->fadeEnd )\n\t\t\t{\n\t\t\t\tsf->fadeSpeed = -(float)sf->fadealpha / sf->fadeEnd;\n\t\t\t}\n\n\t\t\tsf->fadeEnd += cl.time;\n\t\t\tsf->fadeTotalEnd = sf->fadeEnd;\n\t\t\tsf->fadeReset += sf->fadeEnd;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( sf->fadeEnd )\n\t\t\t{\n\t\t\t\tsf->fadeSpeed = (float)sf->fadealpha / sf->fadeEnd;\n\t\t\t}\n\n\t\t\tsf->fadeReset += cl.time;\n\t\t\tsf->fadeEnd += sf->fadeReset;\n\t\t}\n\t}\n}\n\n/*\n==============\nCL_ParseCvarValue\n\nFind the client cvar value\nand sent it back to the server\n==============\n*/\nvoid CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext, const connprotocol_t proto )\n{\n\tconst char *cvarName, *response = NULL;\n\tconvar_t *cvar;\n\tint requestID;\n\n\tif( ext )\n\t\trequestID = MSG_ReadLong( msg );\n\n\tcvarName = MSG_ReadString( msg );\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tif( !Q_stricmp( cvarName, \"sv_version\" ))\n\t\t\tresponse = \"1.1.2.2/Stdio,48,10211\";\n\t}\n\n\tif( !response )\n\t{\n\t\tcvar = Cvar_FindVar( cvarName );\n\n\t\tif( cvar )\n\t\t{\n\t\t\tif( cvar->flags & FCVAR_PRIVILEGED )\n\t\t\t\tresponse = \"CVAR is privileged\";\n\t\t\telse if( cvar->flags & FCVAR_SERVER )\n\t\t\t\tresponse = \"CVAR is server-only\";\n\t\t\telse if( cvar->flags & FCVAR_PROTECTED )\n\t\t\t\tresponse = \"CVAR is protected\";\n\t\t\telse\n\t\t\t\tresponse = cvar->string;\n\t\t}\n\t\telse if( proto == PROTO_LEGACY )\n\t\t{\n\t\t\tresponse = \"Not Found\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\tresponse = \"Bad CVAR request\";\n\t\t}\n\t}\n\n\tif( ext )\n\t{\n\t\tint clc_msg = proto == PROTO_GOLDSRC ? clc_goldsrc_requestcvarvalue2 : clc_requestcvarvalue2;\n\n\t\tMSG_BeginClientCmd( &cls.netchan.message, clc_msg );\n\t\tMSG_WriteLong( &cls.netchan.message, requestID );\n\t\tMSG_WriteString( &cls.netchan.message, cvarName );\n\t}\n\telse\n\t{\n\t\tint clc_msg = proto == PROTO_GOLDSRC ? clc_goldsrc_requestcvarvalue : clc_requestcvarvalue;\n\n\t\tMSG_BeginClientCmd( &cls.netchan.message, clc_msg );\n\t}\n\tMSG_WriteString( &cls.netchan.message, response );\n}\n\n/*\n==============\nCL_ParseExec\n\nExec map/class specific configs\n==============\n*/\nvoid CL_ParseExec( sizebuf_t *msg )\n{\n\tqboolean is_class;\n\tint class_idx;\n\tstring mapname;\n\tconst char *class_cfgs[] = {\n\t\t\"\",\n\t\t\"exec scout.cfg\\n\",\n\t\t\"exec sniper.cfg\\n\",\n\t\t\"exec soldier.cfg\\n\",\n\t\t\"exec demoman.cfg\\n\",\n\t\t\"exec medic.cfg\\n\",\n\t\t\"exec hwguy.cfg\\n\",\n\t\t\"exec pyro.cfg\\n\",\n\t\t\"exec spy.cfg\\n\",\n\t\t\"exec engineer.cfg\\n\",\n\t\t\"\",\n\t\t\"exec civilian.cfg\\n\"\n\t};\n\n\tis_class = MSG_ReadByte( msg );\n\n\tif ( is_class )\n\t{\n\t\tclass_idx = MSG_ReadByte( msg );\n\n\t\tif ( class_idx >= 0 && class_idx <= 11 && !Q_stricmp( GI->gamefolder, \"tfc\" ) )\n\t\t\tCbuf_AddText( class_cfgs[class_idx] );\n\t}\n\telse if ( !Q_stricmp( GI->gamefolder, \"tfc\" ) )\n\t{\n\t\tCbuf_AddText( \"exec mapdefault.cfg\\n\" );\n\n\t\tCOM_FileBase( clgame.mapname, mapname, sizeof( mapname ));\n\n\t\tif ( COM_CheckString( mapname ) )\n\t\t\tCbuf_AddTextf( \"exec %s.cfg\\n\", mapname );\n\t}\n}\n\n/*\n==============\nCL_DispatchUserMessage\n\nDispatch user message by engine request\n==============\n*/\nqboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( pszName ))\n\t\treturn false;\n\n\tfor( i = 0; i < MAX_USER_MESSAGES; i++ )\n\t{\n\t\t// search for user message\n\t\tif( !Q_strcmp( clgame.msg[i].name, pszName ))\n\t\t\tbreak;\n\t}\n\n\tif( i == MAX_USER_MESSAGES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"UserMsg: bad message %s\\n\", pszName );\n\t\treturn false;\n\t}\n\n\tif( clgame.msg[i].func )\n\t{\n\t\tclgame.msg[i].func( pszName, iSize, pbuf );\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( S_ERROR \"UserMsg: No pfn %s %d\\n\", clgame.msg[i].name, clgame.msg[i].number );\n\t\tclgame.msg[i].func = CL_UserMsgStub; // throw warning only once\n\t}\n\treturn true;\n}\n\n/*\n==============\nCL_ParseUserMessage\n\nhandles all user messages\n==============\n*/\nvoid CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto )\n{\n\tbyte\tpbuf[MAX_USERMSG_LENGTH];\n\tint\ti, iSize;\n\n\t// NOTE: any user message is really parse at engine, not in client.dll\n\tif( svc_num <= svc_lastmsg || svc_num > ( MAX_USER_MESSAGES + svc_lastmsg ))\n\t{\n\t\t// out or range\n\t\tHost_Error( \"%s: illegible server message %d\\n\", __func__, svc_num );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < MAX_USER_MESSAGES; i++ )\n\t{\n\t\t// search for user message\n\t\tif( clgame.msg[i].number == svc_num )\n\t\t\tbreak;\n\t}\n\n\tif( i == MAX_USER_MESSAGES ) // probably unregistered\n\t\tHost_Error( \"%s: illegible server message %d\\n\", __func__, svc_num );\n\n\t// NOTE: some user messages handled into engine\n\tif( !Q_strcmp( clgame.msg[i].name, \"ScreenShake\" ))\n\t{\n\t\tCL_ParseScreenShake( msg );\n\t\treturn;\n\t}\n\telse if( !Q_strcmp( clgame.msg[i].name, \"ScreenFade\" ))\n\t{\n\t\tCL_ParseScreenFade( msg );\n\t\treturn;\n\t}\n\n\tiSize = clgame.msg[i].size;\n\n\t// message with variable sizes receive an actual size as first byte\n\tif( iSize == -1 )\n\t{\n\t\tif( proto == PROTO_GOLDSRC || proto == PROTO_LEGACY )\n\t\t\tiSize = MSG_ReadByte( msg );\n\t\telse iSize = MSG_ReadWord( msg );\n\t}\n\n\tif( iSize >= MAX_USERMSG_LENGTH )\n\t{\n\t\tCon_Reportf( \"%s: user message %s size limit hit (%d > %d)!\\n\", __func__, clgame.msg[i].name, iSize, MAX_USERMSG_LENGTH );\n\t\treturn;\n\t}\n\n\t// parse user message into buffer\n\tMSG_ReadBytes( msg, pbuf, iSize );\n\n\tif( cl_trace_messages.value )\n\t{\n\t\tCon_Reportf( \"^3USERMSG %s SIZE %i SVC_NUM %i\\n\",\n\t\t\tclgame.msg[i].name, iSize, clgame.msg[i].number );\n\t}\n\n\tif( clgame.msg[i].func )\n\t{\n\t\tclgame.msg[i].func( clgame.msg[i].name, iSize, pbuf );\n\n#ifdef HACKS_RELATED_HLMODS\n\t\t// run final credits for Half-Life because hl1 doesn't have call END_SECTION\n\t\tif( !Q_stricmp( clgame.msg[i].name, \"HudText\" ) && !Q_stricmp( GI->gamefolder, \"valve\" ))\n\t\t{\n\t\t\t// it's a end, so we should run credits\n\t\t\tif( !Q_strcmp( (char *)pbuf, \"END3\" ))\n\t\t\t\tHost_Credits();\n\t\t}\n#endif\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: No pfn %s %d\\n\", __func__, clgame.msg[i].name, clgame.msg[i].number );\n\t\tclgame.msg[i].func = CL_UserMsgStub; // throw warning only once\n\t}\n}\n\n/*\n=====================================================================\n\nACTION MESSAGES\n\n=====================================================================\n*/\n\n/*\n============\nCL_ParseCommonDLLMessage\n\nparse a message which structure is enforced by DLL compatibility\nit should always be the same regardless of protocol used\n============\n*/\nqboolean CL_ParseCommonDLLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset )\n{\n\tint param1, param2;\n\n\tswitch( svc_num )\n\t{\n\tcase svc_temp_entity:\n\t\tCL_ParseTempEntity( msg, proto ); // need protocol because message header differs\n\t\tcl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - startoffset;\n\t\tbreak;\n\tcase svc_intermission:\n\t\tcl.intermission = 1;\n\t\tbreak;\n\tcase svc_cdtrack:\n\t\tparam1 = MSG_ReadByte( msg );\n\t\tparam1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum\n\t\tparam2 = MSG_ReadByte( msg );\n\t\tparam2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum\n\t\tS_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false );\n\t\tbreak;\n\tcase svc_weaponanim:\n\t\tparam1 = MSG_ReadByte( msg );\t// iAnim\n\t\tparam2 = MSG_ReadByte( msg );\t// body\n\t\tCL_WeaponAnim( param1, param2 );\n\t\tbreak;\n\tcase svc_roomtype:\n\t\tparam1 = MSG_ReadShort( msg );\n\t\tCvar_SetValue( \"room_type\", param1 );\n\t\tbreak;\n\tcase svc_director:\n\t\tCL_ParseDirector( msg );\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=====================\nCL_ParseServerMessage\n\ndispatch messages\n=====================\n*/\nvoid CL_ParseServerMessage( sizebuf_t *msg )\n{\n\tsize_t\t\tbufStart, playerbytes;\n\tint\t\tcmd;\n\tint\t\told_background;\n\tconst char\t*s;\n\n\t// parse the message\n\twhile( 1 )\n\t{\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t{\n\t\t\tHost_Error( \"%s: overflow!\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\n\t\t// mark start position\n\t\tbufStart = MSG_GetNumBytesRead( msg );\n\n\t\t// end of message (align bits)\n\t\tif( MSG_GetNumBitsLeft( msg ) < 8 )\n\t\t\tbreak;\n\n\t\tcmd = MSG_ReadServerCmd( msg );\n\n\t\t// record command for debugging spew on parse problem\n\t\tCL_Parse_RecordCommand( cmd, bufStart );\n\n\t\tif( CL_ParseCommonDLLMessage( msg, PROTO_CURRENT, cmd, bufStart ))\n\t\t\tcontinue;\n\n\t\t// other commands\n\t\tswitch( cmd )\n\t\t{\n\t\tcase svc_bad:\n\t\t\tHost_Error( \"svc_bad\\n\" );\n\t\t\tbreak;\n\t\tcase svc_nop:\n\t\t\t// this does nothing\n\t\t\tbreak;\n\t\tcase svc_disconnect:\n\t\t\tCL_Drop ();\n\t\t\tHost_AbortCurrentFrame ();\n\t\t\tbreak;\n\t\tcase svc_event:\n\t\t\tCL_ParseEvent( msg, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_changing:\n\t\t\told_background = cl.background;\n\t\t\tif( MSG_ReadOneBit( msg ))\n\t\t\t{\n\t\t\t\tint maxclients = cl.maxclients;\n\n\t\t\t\tcls.changelevel = true;\n\t\t\t\tS_StopAllSounds( true );\n\n\t\t\t\tCon_Printf( \"Server changing, reconnecting\\n\" );\n\n\t\t\t\tif( cls.demoplayback )\n\t\t\t\t{\n\t\t\t\t\tSCR_BeginLoadingPlaque( cl.background );\n\t\t\t\t\tcls.changedemo = true;\n\t\t\t\t}\n\n\t\t\t\tCL_ClearState();\n\t\t\t\tCL_InitEdicts( maxclients ); // re-arrange edicts\n\t\t\t}\n\t\t\telse Con_Printf( \"Server disconnected, reconnecting\\n\" );\n\n\t\t\tif( cls.demoplayback )\n\t\t\t{\n\t\t\t\tcl.background = (cls.demonum != -1) ? true : false;\n\t\t\t\tcls.state = ca_connected;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// g-cont. local client skip the challenge\n\t\t\t\tif( SV_Active( ))\n\t\t\t\t\tcls.state = ca_disconnected;\n\t\t\t\telse cls.state = ca_connecting;\n\t\t\t\tcl.background = old_background;\n\t\t\t\tcls.connect_time = MAX_HEARTBEAT;\n\t\t\t\tcls.connect_retry = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase svc_setview:\n\t\t\tCL_ParseViewEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_sound:\n\t\t\tCL_ParseSoundPacket( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_time:\n\t\t\tCL_ParseServerTime( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_print:\n\t\t\tCon_Printf( \"%s\", MSG_ReadString( msg ));\n\t\t\tbreak;\n\t\tcase svc_stufftext:\n\t\t\ts = MSG_ReadString( msg );\n\t\t\tif( cl_trace_stufftext.value )\n\t\t\t{\n\t\t\t\tsize_t len = Q_strlen( s );\n\t\t\t\tCon_Printf( \"Stufftext: %s%c\", s, len && s[len-1] == '\\n' ? '\\0' : '\\n' );\n\t\t\t}\n\n#ifdef HACKS_RELATED_HLMODS\n\t\t\t// disable Cry Of Fear antisave protection\n\t\t\tif( !Q_strnicmp( s, \"disconnect\", 10 ) && cls.signon != SIGNONS )\n\t\t\t\tbreak; // too early\n#endif\n\n\t\t\tCbuf_AddFilteredText( s );\n\t\t\tbreak;\n\t\tcase svc_setangle:\n\t\t\tCL_ParseSetAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_serverdata:\n\t\t\tCbuf_Execute(); // make sure any stuffed commands are done\n\t\t\tCL_ParseServerData( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_lightstyle:\n\t\t\tCL_ParseLightStyle( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_updateuserinfo:\n\t\t\tCL_UpdateUserinfo( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_deltatable:\n\t\t\tDelta_ParseTableField( msg );\n\t\t\tbreak;\n\t\tcase svc_clientdata:\n\t\t\tCL_ParseClientData( msg, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_resource:\n\t\t\tCL_ParseResource( msg );\n\t\t\tbreak;\n\t\tcase svc_pings:\n\t\t\tCL_UpdateUserPings( msg );\n\t\t\tbreak;\n\t\tcase svc_particle:\n\t\t\tCL_ParseParticles( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_restoresound:\n\t\t\tCL_ParseRestoreSoundPacket( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_spawnstatic:\n\t\t\tCL_ParseStaticEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_event_reliable:\n\t\t\tCL_ParseReliableEvent( msg, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_spawnbaseline:\n\t\t\tCL_ParseBaseline( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_setpause:\n\t\t\tcl.paused = ( MSG_ReadOneBit( msg ) != 0 );\n\t\t\tbreak;\n\t\tcase svc_signonnum:\n\t\t\tCL_ParseSignon( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_centerprint:\n\t\t\tCL_CenterPrint( MSG_ReadString( msg ), 0.25f );\n\t\t\tbreak;\n\t\tcase svc_finale:\n\t\t\tCL_ParseFinaleCutscene( msg, 2 );\n\t\t\tbreak;\n\t\tcase svc_restore:\n\t\t\tCL_ParseRestore( msg );\n\t\t\tbreak;\n\t\tcase svc_cutscene:\n\t\t\tCL_ParseFinaleCutscene( msg, 3 );\n\t\t\tbreak;\n\t\tcase svc_bspdecal:\n\t\t\tCL_ParseStaticDecal( msg );\n\t\t\tbreak;\n\t\tcase svc_addangle:\n\t\t\tCL_ParseAddAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_usermessage:\n\t\t\tCL_RegisterUserMessage( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_packetentities:\n\t\t\tplayerbytes = CL_ParsePacketEntities( msg, false, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_deltapacketentities:\n\t\t\tplayerbytes = CL_ParsePacketEntities( msg, true, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_choke:\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true;\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0;\n\t\t\tbreak;\n\t\tcase svc_resourcelist:\n\t\t\tCL_ParseResourceList( msg, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_deltamovevars:\n\t\t\tCL_ParseMovevars( msg );\n\t\t\tbreak;\n\t\tcase svc_resourcerequest:\n\t\t\tCL_ParseResourceRequest( msg );\n\t\t\tbreak;\n\t\tcase svc_customization:\n\t\t\tCL_ParseCustomization( msg );\n\t\t\tbreak;\n\t\tcase svc_crosshairangle:\n\t\t\tCL_ParseCrosshairAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_soundfade:\n\t\t\tCL_ParseSoundFade( msg );\n\t\t\tbreak;\n\t\tcase svc_filetxferfailed:\n\t\t\tCL_ParseFileTransferFailed( msg );\n\t\t\tbreak;\n\t\tcase svc_hltv:\n\t\t\tCL_ParseHLTV( msg );\n\t\t\tbreak;\n\t\tcase svc_voiceinit:\n\t\t\tCL_ParseVoiceInit( msg );\n\t\t\tbreak;\n\t\tcase svc_voicedata:\n\t\t\tCL_ParseVoiceData( msg, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_resourcelocation:\n\t\t\tCL_ParseResLocation( msg );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue:\n\t\t\tCL_ParseCvarValue( msg, false, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue2:\n\t\t\tCL_ParseCvarValue( msg, true, PROTO_CURRENT );\n\t\t\tbreak;\n\t\tcase svc_exec:\n\t\t\tCL_ParseExec( msg );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCL_ParseUserMessage( msg, cmd, PROTO_CURRENT );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/client/cl_parse_48.c",
    "content": "/*\ncl_parse.c - parse a message received from the server\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"particledef.h\"\n#include \"cl_tent.h\"\n#include \"shake.h\"\n#include \"hltv.h\"\n#include \"input.h\"\n\n\n/*\n==================\nCL_ParseStaticEntity\n\nstatic client entity\n==================\n*/\nstatic void CL_LegacyParseStaticEntity( sizebuf_t *msg )\n{\n\tint\t\ti;\n\tentity_state_t state = { 0 };\n\tcl_entity_t\t*ent;\n\n\tif( !clgame.static_entities )\n\t\tclgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );\n\n\tstate.modelindex = MSG_ReadShort( msg );\n\tstate.sequence = MSG_ReadByte( msg );\n\tstate.frame = MSG_ReadByte( msg );\n\tstate.colormap = MSG_ReadWord( msg );\n\tstate.skin = MSG_ReadByte( msg );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tstate.origin[i] = MSG_ReadCoord( msg );\n\t\tstate.angles[i] = MSG_ReadBitAngle( msg, 16 );\n\t}\n\n\tstate.rendermode = MSG_ReadByte( msg );\n\n\tif( state.rendermode != kRenderNormal )\n\t{\n\t\tstate.renderamt = MSG_ReadByte( msg );\n\t\tstate.rendercolor.r = MSG_ReadByte( msg );\n\t\tstate.rendercolor.g = MSG_ReadByte( msg );\n\t\tstate.rendercolor.b = MSG_ReadByte( msg );\n\t\tstate.renderfx = MSG_ReadByte( msg );\n\t}\n\n\ti = clgame.numStatics;\n\tif( i >= MAX_STATIC_ENTITIES )\n\t{\n\t\tCon_Printf( S_ERROR \"MAX_STATIC_ENTITIES limit exceeded!\\n\" );\n\t\treturn;\n\t}\n\n\tent = &clgame.static_entities[i];\n\tclgame.numStatics++;\n\n\t// all states are same\n\tent->baseline = ent->curstate = ent->prevstate = state;\n\tent->index = 0; // static entities doesn't has the numbers\n\n\t// statics may be respawned in game e.g. for demo recording\n\tif( cls.state == ca_connected || cls.state == ca_validate )\n\t\tent->trivial_accept = INVALID_HANDLE;\n\n\t// setup the new static entity\n\tVectorCopy( ent->curstate.origin, ent->origin );\n\tVectorCopy( ent->curstate.angles, ent->angles );\n\tent->model = CL_ModelHandle( state.modelindex );\n\tent->curstate.framerate = 1.0f;\n\tCL_ResetLatchedVars( ent, true );\n\n\tif( ent->curstate.rendermode == kRenderNormal && ent->model != NULL )\n\t{\n\t\t// auto 'solid' faces\n\t\tif( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( ))\n\t\t{\n\t\t\tent->curstate.rendermode = kRenderTransAlpha;\n\t\t\tent->curstate.renderamt = 255;\n\t\t}\n\t}\n\n\tR_AddEfrags( ent );\t// add link\n}\n\nstatic void CL_LegacyParseSoundPacket( sizebuf_t *msg, qboolean is_ambient )\n{\n\tvec3_t\tpos;\n\tint \tchan, sound;\n\tfloat \tvolume, attn;\n\tint\tflags, pitch, entnum;\n\tsound_t\thandle = 0;\n\n\tflags = MSG_ReadWord( msg );\n\tif( flags & SND_LEGACY_LARGE_INDEX )\n\t{\n\t\tsound = MSG_ReadWord( msg );\n\t\tflags &= ~SND_LEGACY_LARGE_INDEX;\n\t}\n\telse\n\t\tsound = MSG_ReadByte( msg );\n\tchan = MSG_ReadByte( msg );\n\n\tif( FBitSet( flags, SND_VOLUME ))\n\t\tvolume = (float)MSG_ReadByte( msg ) / 255.0f;\n\telse volume = VOL_NORM;\n\n\tif( FBitSet( flags, SND_ATTENUATION ))\n\t\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\telse attn = ATTN_NONE;\n\n\tif( FBitSet( flags, SND_PITCH ))\n\t\tpitch = MSG_ReadByte( msg );\n\telse pitch = PITCH_NORM;\n\n\t// entity reletive\n\tentnum = MSG_ReadWord( msg );\n\n\t// positioned in space\n\tMSG_ReadVec3Coord( msg, pos );\n\n\tif( FBitSet( flags, SND_SENTENCE ))\n\t{\n\t\tchar\tsentenceName[32];\n\t\tQ_snprintf( sentenceName, sizeof( sentenceName ), \"!%i\", sound );\n\t\thandle = S_RegisterSound( sentenceName );\n\t}\n\telse handle = cl.sound_index[sound];\t// see precached sound\n\n\tif( !cl.audio_prepped )\n\t\treturn; // too early\n\n\t// g-cont. sound and ambient sound have only difference with channel\n\tif( is_ambient )\n\t{\n\t\tS_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );\n\t}\n\telse\n\t{\n\t\tS_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags );\n\t}\n}\n/*\n================\nCL_PrecacheSound\n\nprceache sound from server\n================\n*/\nstatic void CL_LegacyPrecacheSound( sizebuf_t *msg )\n{\n\tint\tsoundIndex;\n\n\tsoundIndex = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );\n\n\tif( soundIndex < 0 || soundIndex >= MAX_SOUNDS )\n\t\tHost_Error( \"%s: bad soundindex %i\\n\", __func__, soundIndex );\n\n\tQ_strncpy( cl.sound_precache[soundIndex], MSG_ReadString( msg ), sizeof( cl.sound_precache[0] ));\n\n\t// when we loading map all resources is precached sequentially\n\t//if( !cl.audio_prepped ) return;\n\n\tcl.sound_index[soundIndex] = S_RegisterSound( cl.sound_precache[soundIndex] );\n}\n\nstatic void CL_LegacyPrecacheModel( sizebuf_t *msg )\n{\n\tint\tmodelIndex;\n\tstring model;\n\n\tmodelIndex = MSG_ReadUBitLong( msg, MAX_LEGACY_MODEL_BITS );\n\n\tif( modelIndex < 0 || modelIndex >= MAX_MODELS )\n\t\tHost_Error( \"%s: bad modelindex %i\\n\", __func__, modelIndex );\n\n\tQ_strncpy( model, MSG_ReadString( msg ), sizeof( model ));\n\t//Q_strncpy( cl.model_precache[modelIndex], BF_ReadString( msg ), sizeof( cl.model_precache[0] ));\n\n\t// when we loading map all resources is precached sequentially\n\t//if( !cl.video_prepped ) return;\n\tif( modelIndex == 1 && !cl.worldmodel )\n\t{\n\t\tCL_ClearWorld ();\n\n\t\tcl.models[modelIndex] = cl.worldmodel = Mod_LoadWorld( model, true );\n\t\treturn;\n\n\t}\n\n\t//Mod_RegisterModel( cl.model_precache[modelIndex], modelIndex );\n\n\tcl.models[modelIndex] = Mod_ForName( model, false, false );\n\tcl.nummodels = Q_max( cl.nummodels, modelIndex  );\n}\n\nstatic void CL_LegacyPrecacheEvent( sizebuf_t *msg )\n{\n\tint\teventIndex;\n\n\teventIndex = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );\n\n\tif( eventIndex < 0 || eventIndex >= MAX_EVENTS )\n\t\tHost_Error( \"%s: bad eventindex %i\\n\", __func__, eventIndex );\n\n\tQ_strncpy( cl.event_precache[eventIndex], MSG_ReadString( msg ), sizeof( cl.event_precache[0] ));\n\n\t// can be set now\n\tCL_SetEventIndex( cl.event_precache[eventIndex], eventIndex );\n}\n\n#if XASH_LOW_MEMORY == 0\n#define MAX_LEGACY_RESOURCES 2048\n#elif XASH_LOW_MEMORY == 2\n#define MAX_LEGACY_RESOURCES 1\n#elif XASH_LOW_MEMORY == 1\n#define MAX_LEGACY_RESOURCES 512\n#endif\n/*\n==============\nCL_ParseResourceList\n\n==============\n*/\nstatic void CL_LegacyParseResourceList( sizebuf_t *msg )\n{\n\tint\ti = 0;\n\tstatic struct\n\t{\n\t\tint  rescount;\n\t\tint  restype[MAX_LEGACY_RESOURCES];\n\t\tchar resnames[MAX_LEGACY_RESOURCES][MAX_QPATH];\n\t} reslist;\n\n\tmemset( &reslist, 0, sizeof( reslist ));\n\treslist.rescount = MSG_ReadWord( msg ) - 1;\n\n\tif( reslist.rescount > MAX_LEGACY_RESOURCES )\n\t\tHost_Error( \"MAX_RESOURCES reached\\n\" );\n\n\tfor( i = 0; i < reslist.rescount; i++ )\n\t{\n\t\treslist.restype[i] = MSG_ReadWord( msg );\n\t\tQ_strncpy( reslist.resnames[i], MSG_ReadString( msg ), sizeof( reslist.resnames[i] ));\n\t}\n\n\tif( cls.demoplayback )\n\t\treturn;\n\n\tif( !cl_allow_download.value )\n\t{\n\t\tCon_DPrintf( \"Refusing new resource, cl_allowdownload set to 0\\n\" );\n\t\treslist.rescount = 0;\n\t}\n\n\tif( cls.state == ca_active && !cl_download_ingame.value )\n\t{\n\t\tCon_DPrintf( \"Refusing new resource, cl_download_ingame set to 0\\n\" );\n\t\treslist.rescount = 0;\n\t}\n\n\tHTTP_ResetProcessState();\n\n\thost.downloadcount = 0;\n\n\tfor( i = 0; i < reslist.rescount; i++ )\n\t{\n\t\tchar soundpath[MAX_VA_STRING];\n\t\tconst char *path;\n\n\t\tif( reslist.restype[i] == t_sound )\n\t\t{\n\t\t\tQ_snprintf( soundpath, sizeof( soundpath ), DEFAULT_SOUNDPATH \"%s\", reslist.resnames[i] );\n\n\t\t\tpath = soundpath;\n\t\t}\n\t\telse path = reslist.resnames[i];\n\n\t\tif( FS_FileExists( path, false ))\n\t\t\tcontinue;\t// already exists\n\n\t\thost.downloadcount++;\n\t\tHTTP_AddDownload( path, -1, true, NULL );\n\t}\n\n\tif( !host.downloadcount )\n\t{\n\t\tMSG_WriteByte( &cls.netchan.message, clc_stringcmd );\n\t\tMSG_WriteString( &cls.netchan.message, \"continueloading\" );\n\t}\n}\n\n/*\n=====================\nCL_ParseLegacyServerMessage\n\ndispatch messages\n=====================\n*/\nvoid CL_ParseLegacyServerMessage( sizebuf_t *msg )\n{\n\tsize_t\t\tbufStart, playerbytes;\n\tint\t\tcmd;\n\tint\t\told_background;\n\tconst char\t*s;\n\n\t// parse the message\n\twhile( 1 )\n\t{\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t{\n\t\t\tHost_Error( \"%s: overflow!\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\n\t\t// mark start position\n\t\tbufStart = MSG_GetNumBytesRead( msg );\n\n\t\t// end of message (align bits)\n\t\tif( MSG_GetNumBitsLeft( msg ) < 8 )\n\t\t\tbreak;\n\n\t\tcmd = MSG_ReadServerCmd( msg );\n\n\t\t// record command for debugging spew on parse problem\n\t\tCL_Parse_RecordCommand( cmd, bufStart );\n\n\t\tif( CL_ParseCommonDLLMessage( msg, PROTO_LEGACY, cmd, bufStart ))\n\t\t\tcontinue;\n\n\t\t// other commands\n\t\tswitch( cmd )\n\t\t{\n\t\tcase svc_bad:\n\t\t\tHost_Error( \"svc_bad\\n\" );\n\t\t\tbreak;\n\t\tcase svc_nop:\n\t\t\t// this does nothing\n\t\t\tbreak;\n\t\tcase svc_disconnect:\n\t\t\tCL_Drop ();\n\t\t\tHost_AbortCurrentFrame ();\n\t\t\tbreak;\n\t\tcase svc_legacy_event:\n\t\t\tCL_ParseEvent( msg, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_legacy_changing:\n\t\t\told_background = cl.background;\n\t\t\tif( MSG_ReadOneBit( msg ))\n\t\t\t{\n\t\t\t\tint maxclients = cl.maxclients;\n\n\t\t\t\t// we can only changelevel in singleplayer\n\t\t\t\t// and singleplayer always runs in current protocol\n\t\t\t\t// cls.changelevel = true;\n\t\t\t\tS_StopAllSounds( true );\n\n\t\t\t\tCon_Printf( \"Server changing, reconnecting\\n\" );\n\n\t\t\t\tif( cls.demoplayback )\n\t\t\t\t{\n\t\t\t\t\tSCR_BeginLoadingPlaque( cl.background );\n\t\t\t\t\tcls.changedemo = true;\n\t\t\t\t}\n\n\t\t\t\tCL_ClearState( );\n\n\t\t\t\t// a1ba: need to restore cl.maxclients because engine chooses\n\t\t\t\t// frame backups count depending on this value\n\t\t\t\t// In general, it's incorrect to call CL_InitEdicts right after\n\t\t\t\t// CL_ClearState because of this bug. Some time later this logic\n\t\t\t\t// should be re-done.\n\t\t\t\tCL_InitEdicts( maxclients ); // re-arrange edicts\n\t\t\t}\n\t\t\telse Con_Printf( \"Server disconnected, reconnecting\\n\" );\n\n\t\t\tif( cls.demoplayback )\n\t\t\t{\n\t\t\t\tcl.background = (cls.demonum != -1) ? true : false;\n\t\t\t\tcls.state = ca_connected;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// g-cont. local client skip the challenge\n\t\t\t\tif( SV_Active( ))\n\t\t\t\t\tcls.state = ca_disconnected;\n\t\t\t\telse cls.state = ca_connecting;\n\t\t\t\tcl.background = old_background;\n\t\t\t\tcls.connect_time = MAX_HEARTBEAT;\n\t\t\t\tcls.connect_retry = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase svc_setview:\n\t\t\tCL_ParseViewEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_sound:\n\t\t\tCL_LegacyParseSoundPacket( msg, false );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_legacy_ambientsound:\n\t\t\tCL_LegacyParseSoundPacket( msg, true );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\n\t\t\tbreak;\n\t\tcase svc_time:\n\t\t\tCL_ParseServerTime( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_print:\n\t\t\tCon_Printf( \"%s\", MSG_ReadString( msg ));\n\t\t\tbreak;\n\t\tcase svc_stufftext:\n\t\t\ts = MSG_ReadString( msg );\n\t\t\tif( cl_trace_stufftext.value )\n\t\t\t{\n\t\t\t\tsize_t len = Q_strlen( s );\n\t\t\t\tCon_Printf( \"Stufftext: %s%c\", s, len && s[len-1] == '\\n' ? '\\0' : '\\n' );\n\t\t\t}\n\n#ifdef HACKS_RELATED_HLMODS\n\t\t\t// disable Cry Of Fear antisave protection\n\t\t\tif( !Q_strnicmp( s, \"disconnect\", 10 ) && cls.signon != SIGNONS )\n\t\t\t\tbreak; // too early\n#endif\n\t\t\tCbuf_AddFilteredText( s );\n\t\t\tbreak;\n\t\tcase svc_setangle:\n\t\t\tCL_ParseSetAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_serverdata:\n\t\t\tCbuf_Execute(); // make sure any stuffed commands are done\n\t\t\tCL_ParseServerData( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_lightstyle:\n\t\t\tCL_ParseLightStyle( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_updateuserinfo:\n\t\t\tCL_UpdateUserinfo( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_deltatable:\n\t\t\tDelta_ParseTableField( msg );\n\t\t\tbreak;\n\t\tcase svc_clientdata:\n\t\t\tCL_ParseClientData( msg, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_resource:\n\t\t\tCL_ParseResource( msg );\n\t\t\tbreak;\n\t\tcase svc_pings:\n\t\t\tCL_UpdateUserPings( msg );\n\t\t\tbreak;\n\t\tcase svc_particle:\n\t\t\tCL_ParseParticles( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_restoresound:\n\t\t\tCon_Printf( S_ERROR \"%s: svc_restoresound: implement me!\\n\", __func__ );\n\t\t\tbreak;\n\t\tcase svc_spawnstatic:\n\t\t\tCL_LegacyParseStaticEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_event_reliable:\n\t\t\tCL_ParseReliableEvent( msg, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_spawnbaseline:\n\t\t\tCL_ParseBaseline( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_setpause:\n\t\t\tcl.paused = ( MSG_ReadOneBit( msg ) != 0 );\n\t\t\tbreak;\n\t\tcase svc_signonnum:\n\t\t\tCL_ParseSignon( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_centerprint:\n\t\t\tCL_CenterPrint( MSG_ReadString( msg ), 0.25f );\n\t\t\tbreak;\n\t\tcase svc_legacy_modelindex:\n\t\t\tCL_LegacyPrecacheModel( msg );\n\t\t\tbreak;\n\t\tcase svc_legacy_soundindex:\n\t\t\tCL_LegacyPrecacheSound( msg );\n\t\t\tbreak;\n\t\tcase svc_restore:\n\t\t\tCL_ParseRestore( msg );\n\t\t\tbreak;\n\t\tcase svc_legacy_eventindex:\n\t\t\tCL_LegacyPrecacheEvent(msg);\n\t\t\tbreak;\n\t\tcase svc_bspdecal:\n\t\t\tCL_ParseStaticDecal( msg );\n\t\t\tbreak;\n\t\tcase svc_addangle:\n\t\t\tCL_ParseAddAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_usermessage:\n\t\t\tCL_RegisterUserMessage( msg, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_packetentities:\n\t\t\tplayerbytes = CL_ParsePacketEntities( msg, false, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_deltapacketentities:\n\t\t\tplayerbytes = CL_ParsePacketEntities( msg, true, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_legacy_chokecount:\n\t\t{\n\t\t\tint i, j;\n\t\t\ti = MSG_ReadByte( msg );\n\t\t\tj = cls.netchan.incoming_acknowledged - 1;\n\t\t\tfor( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- )\n\t\t\t{\n\t\t\t\tif( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 )\n\t\t\t\t{\n\t\t\t\t\tcl.frames[j & CL_UPDATE_MASK].choked = true;\n\t\t\t\t\tcl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0;\n\t\t\t\t\ti--;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t\t//cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true;\n\t\t\t//cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0;\n\t\t\tbreak;\n\t\tcase svc_resourcelist:\n\t\t\tCL_LegacyParseResourceList( msg );\n\t\t\tbreak;\n\t\tcase svc_deltamovevars:\n\t\t\tCL_ParseMovevars( msg );\n\t\t\tbreak;\n\t\tcase svc_resourcerequest:\n\t\t\tCL_ParseResourceRequest( msg );\n\t\t\tbreak;\n\t\tcase svc_customization:\n\t\t\tCL_ParseCustomization( msg );\n\t\t\tbreak;\n\t\tcase svc_crosshairangle:\n\t\t\tCL_ParseCrosshairAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_soundfade:\n\t\t\tCL_ParseSoundFade( msg );\n\t\t\tbreak;\n\t\tcase svc_filetxferfailed:\n\t\t\tCL_ParseFileTransferFailed( msg );\n\t\t\tbreak;\n\t\tcase svc_hltv:\n\t\t\tCL_ParseHLTV( msg );\n\t\t\tbreak;\n\t\tcase svc_resourcelocation:\n\t\t\tCL_ParseResLocation( msg );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue:\n\t\t\tCL_ParseCvarValue( msg, false, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue2:\n\t\t\tCL_ParseCvarValue( msg, true, PROTO_LEGACY );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCL_ParseUserMessage( msg, cmd, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid CL_LegacyPrecache_f( void )\n{\n\tint\tspawncount, i;\n\tmodel_t *mod;\n\n\tif( cls.legacymode != PROTO_LEGACY )\n\t\treturn;\n\n\tspawncount = Q_atoi( Cmd_Argv( 1 ));\n\n\tCon_Printf( \"Setting up renderer...\\n\" );\n\n\t// load tempent sprites (glowshell, muzzleflashes etc)\n\tCL_LoadClientSprites ();\n\n\t// invalidate all decal indexes\n\tmemset( cl.decal_index, 0, sizeof( cl.decal_index ));\n\tcl.video_prepped = true;\n\tcl.audio_prepped = true;\n\tif( clgame.entities )\n\t\tclgame.entities->model = cl.worldmodel;\n\n\t// load skybox\n\tR_SetupSky( clgame.movevars.skyName );\n\n\t// tell rendering system we have a new set of models.\n\tref.dllFuncs.R_NewMap ();\n\n\tCL_SetupOverviewParams();\n\n\t// release unused SpriteTextures\n\tfor( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )\n\t{\n\t\tif( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name ))\n\t\t\tMod_FreeModel( mod );\n\t}\n\n//\tMod_FreeUnused ();\n\n\tif( host_developer.value <= DEV_NONE )\n\t\tCon_ClearNotify(); // clear any lines of console text\n\n\t// done with all resources, issue prespawn command.\n\t// Include server count in case server disconnects and changes level during d/l\n\tMSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd );\n\tMSG_WriteStringf( &cls.netchan.message, \"begin %i\", spawncount );\n\tcls.signon = SIGNONS - 1;\n}\n"
  },
  {
    "path": "engine/client/cl_parse_gs.c",
    "content": "/*\ncl_parse.c - parse a message received from the server (GoldSrc 48 protocol)\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"particledef.h\"\n#include \"cl_tent.h\"\n#include \"shake.h\"\n#include \"hltv.h\"\n#include \"input.h\"\n#include \"server.h\"\n\nstatic void CL_ParseExtraInfo( sizebuf_t *msg )\n{\n\tstring clientfallback;\n\n\tQ_strncpy( clientfallback, MSG_ReadString( msg ), sizeof( clientfallback ));\n\tif( COM_CheckStringEmpty( clientfallback ))\n\t\tCon_Reportf( S_ERROR \"%s: TODO: add fallback directory %s!\\n\", __func__, clientfallback );\n\n\tif( MSG_ReadByte( msg ))\n\t{\n\t\tCvar_FullSet( \"sv_cheats\", \"1\", FCVAR_READ_ONLY | FCVAR_SERVER );\n\t}\n\telse\n\t{\n\t\tCvar_SetCheatState();\n\t\tCvar_FullSet( \"sv_cheats\", \"0\", FCVAR_READ_ONLY | FCVAR_SERVER );\n\t}\n}\n\nstatic void CL_ParseNewMovevars( sizebuf_t *msg )\n{\n\tDelta_InitClient(); // finalize client delta's\n\n\tclgame.movevars.gravity           = MSG_ReadFloat( msg );\n\tclgame.movevars.stopspeed         = MSG_ReadFloat( msg );\n\tclgame.movevars.maxspeed          = MSG_ReadFloat( msg );\n\tclgame.movevars.spectatormaxspeed = MSG_ReadFloat( msg );\n\tclgame.movevars.accelerate        = MSG_ReadFloat( msg );\n\tclgame.movevars.airaccelerate     = MSG_ReadFloat( msg );\n\tclgame.movevars.wateraccelerate   = MSG_ReadFloat( msg );\n\tclgame.movevars.friction          = MSG_ReadFloat( msg );\n\tclgame.movevars.edgefriction      = MSG_ReadFloat( msg );\n\tclgame.movevars.waterfriction     = MSG_ReadFloat( msg );\n\tclgame.movevars.entgravity        = MSG_ReadFloat( msg );\n\tclgame.movevars.bounce            = MSG_ReadFloat( msg );\n\tclgame.movevars.stepsize          = MSG_ReadFloat( msg );\n\tclgame.movevars.maxvelocity       = MSG_ReadFloat( msg );\n\tclgame.movevars.zmax              = MSG_ReadFloat( msg );\n\tclgame.movevars.waveHeight        = MSG_ReadFloat( msg );\n\tclgame.movevars.footsteps         = MSG_ReadByte( msg );\n\tclgame.movevars.rollangle         = MSG_ReadFloat( msg );\n\tclgame.movevars.rollspeed         = MSG_ReadFloat( msg );\n\tclgame.movevars.skycolor_r        = MSG_ReadFloat( msg );\n\tclgame.movevars.skycolor_g        = MSG_ReadFloat( msg );\n\tclgame.movevars.skycolor_b        = MSG_ReadFloat( msg );\n\tclgame.movevars.skyvec_x          = MSG_ReadFloat( msg );\n\tclgame.movevars.skyvec_y          = MSG_ReadFloat( msg );\n\tclgame.movevars.skyvec_z          = MSG_ReadFloat( msg );\n\n\tQ_strncpy( clgame.movevars.skyName, MSG_ReadString( msg ), sizeof( clgame.movevars.skyName ));\n\n\t// water alpha is not allowed\n\tif( !FBitSet( world.flags, FWORLD_WATERALPHA ))\n\t\tclgame.movevars.wateralpha = 1.0f;\n\n\t// update sky if changed\n\tif( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped )\n\t\tR_SetupSky( clgame.movevars.skyName );\n\n\tclgame.oldmovevars = clgame.movevars;\n\n\t// FIXME: set world wave height when entities will be allocated\n\tif( clgame.entities )\n\t\tclgame.entities->curstate.scale = clgame.movevars.waveHeight;\n\n\t// keep features an actual!\n\tclgame.oldmovevars.features = clgame.movevars.features = host.features;\n}\n\ntypedef struct delta_header_t\n{\n\tqboolean remove;\n\tqboolean custom;\n\tqboolean instanced;\n\tuint16_t instanced_baseline_index;\n\tuint16_t offset;\n} delta_header_t;\n\nstatic int CL_ParseDeltaHeader( sizebuf_t *msg, qboolean delta, int oldnum, struct delta_header_t *hdr )\n{\n\tint entnum = oldnum;\n\tmemset( hdr, 0, sizeof( *hdr ));\n\n\tif( !delta )\n\t{\n\t\t// if we have one bit set, then it's a next entity in line\n\t\t// if we have next bit NON set, then it's a one of next 64 entities\n\t\t// if not, it's a new entity\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tentnum++;\n\t\telse if( MSG_ReadOneBit( msg ) == 0 )\n\t\t\tentnum += MSG_ReadUBitLong( msg, 6 );\n\t\telse\n\t\t\tentnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );\n\t}\n\telse\n\t{\n\t\t// does this packet encode entity deletion?\n\t\thdr->remove = MSG_ReadOneBit( msg );\n\n\t\t// same logic as above\n\t\tif( MSG_ReadOneBit( msg ) == 0 )\n\t\t\tentnum += MSG_ReadUBitLong( msg, 6 );\n\t\telse entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );\n\t}\n\n\t// if we are not removing this entity\n\tif( !hdr->remove )\n\t{\n\t\thdr->custom = MSG_ReadOneBit( msg );\n\n\t\t// do we got instanced baselines in svc_spawnbaselines?\n\t\tif( cl.instanced_baseline_count )\n\t\t{\n\t\t\thdr->instanced = MSG_ReadOneBit( msg );\n\t\t\tif( hdr->instanced )\n\t\t\t\thdr->instanced_baseline_index = MSG_ReadUBitLong( msg, 6 );\n\t\t}\n\n\t\tif( !delta && !hdr->instanced )\n\t\t{\n\t\t\tif( MSG_ReadOneBit( msg ))\n\t\t\t\thdr->offset = MSG_ReadUBitLong( msg, 6 );\n\t\t}\n\t}\n\n\treturn entnum;\n}\n\nstatic int CL_GetEntityDelta( const struct delta_header_t *hdr, int entnum )\n{\n\tif( hdr->custom )\n\t\treturn DT_CUSTOM_ENTITY_STATE_T;\n\n\tif( CL_IsPlayerIndex( entnum ))\n\t\treturn DT_ENTITY_STATE_PLAYER_T;\n\n\treturn DT_ENTITY_STATE_T;\n}\n\nstatic int CL_FlushEntityPacketGS( frame_t *frame, sizebuf_t *msg )\n{\n\tint playerbytes = 0, numbase = 0;\n\n\tframe->valid = false;\n\tcl.validsequence = 0; // can't render a frame\n\n\t// read it all but ignore it\n\twhile( 1 )\n\t{\n\t\tint newnum, bufstart;\n\t\tentity_state_t from = { 0 }, to;\n\t\tdelta_header_t hdr;\n\t\tqboolean player;\n\n\t\tif( MSG_ReadWord( msg ) != 0 )\n\t\t{\n\t\t\tMSG_SeekToBit( msg, -16, SEEK_CUR );\n\t\t\tnumbase = newnum = CL_ParseDeltaHeader( msg, true, numbase, &hdr );\n\t\t}\n\t\telse break;\n\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\t\tplayer = CL_IsPlayerIndex( newnum );\n\t\tbufstart = MSG_GetNumBytesRead( msg );\n\n\t\tif( hdr.remove )\n\t\t\tcontinue;\n\n\t\tDelta_ReadGSFields( msg, CL_GetEntityDelta( &hdr, newnum ), &from, &to, cl.mtime[0] );\n\n\t\tif( player )\n\t\t\tplayerbytes += MSG_GetNumBytesRead( msg ) - bufstart;\n\t}\n\n\tif( MSG_CheckOverflow( msg ))\n\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\treturn playerbytes;\n}\n\nstatic void CL_DeltaEntityGS( const delta_header_t *hdr, sizebuf_t *msg, frame_t *frame, int newnum, const entity_state_t *from )\n{\n\tcl_entity_t\t*ent;\n\tentity_state_t\t*to;\n\tqboolean newent = from == NULL;\n\tint pack = frame->num_entities;\n\tqboolean has_update = msg != NULL;\n\n\t// alloc next slot to store update\n\tto = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];\n\n\tif(( newnum < 0 ) || ( newnum >= clgame.maxEntities ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"CL_DeltaEntity: invalid newnum: %d\\n\", newnum );\n\t\tHost_Error( \"%s: bad delta entity number: %i\\n\", __func__, newnum );\n\t\treturn;\n\t}\n\n\tent = CL_EDICT_NUM( newnum );\n\tif( hdr->remove )\n\t{\n\t\tif( !newent )\n\t\t\tCL_KillDeadBeams( ent );\n\t\telse\n\t\t\tCon_Printf( S_WARN \"%s: entity remove on non-delta update (%d)\\n\", __func__, newnum );\n\t\treturn;\n\t}\n\n\tent->index = newnum; // enumerate entity index\n\tif( newent )\n\t{\n\t\tif( hdr->instanced )\n\t\t\tfrom = &cl.instanced_baseline[hdr->instanced_baseline_index];\n\t\telse if( hdr->offset != 0 )\n\t\t\tfrom = &cls.packet_entities[(cls.next_client_entities - hdr->offset) % cls.num_client_entities];\n\t\telse\n\t\t\tfrom = &ent->baseline;\n\t}\n\n\tif( has_update )\n\t\tDelta_ReadGSFields( msg, CL_GetEntityDelta( hdr, newnum ), from, to, cl.mtime[0] );\n\telse memcpy( to, from, sizeof( entity_state_t ));\n\n\tto->entityType = hdr->custom ? ENTITY_BEAM : ENTITY_NORMAL;\n\tto->number = newnum;\n\n\tif( newent )\n\t{\n\t\t// interpolation must be reset\n\t\tSETVISBIT( frame->flags, pack );\n\n\t\t// release beams from previous entity\n\n\t\t// a1ba: check that this entity number was never used on client\n\t\t// as beams can be transferred before this entity was sent to client\n\t\t// (for example, beam was sent over during beam entity spawn\n\t\t// but referenced start point entity hasn't been sent over due to PVS)\n\t\tif( ent->curstate.messagenum != 0 )\n\t\t\tCL_KillDeadBeams( ent );\n\t}\n\n\t// add entity to packet\n\tcls.next_client_entities++;\n\tframe->num_entities++;\n}\n\nstatic void CL_CopyPacketEntity( frame_t *frame, int num, const entity_state_t *from )\n{\n\tdelta_header_t fakehdr =\n\t{\n\t\t.custom = FBitSet( from->entityType, ENTITY_BEAM ) == ENTITY_BEAM,\n\t};\n\tCL_DeltaEntityGS( &fakehdr, NULL, frame, num, from );\n}\n\nstatic int CL_ParsePacketEntitiesGS( sizebuf_t *msg, qboolean delta )\n{\n\tframe_t *frame, *oldframe;\n\tint oldindex, oldnum, numbase = 0;\n\tentity_state_t *oldent;\n\tint count;\n\tint playerbytes = 0;\n\n\t// save first uncompressed packet as timestamp\n\tif( cls.changelevel && !delta && cls.demorecording )\n\t\tCL_WriteDemoJumpTime();\n\n\tcount = MSG_ReadWord( msg );\n\n\tframe = &cl.frames[cl.parsecountmod];\n\tmemset( frame->flags, 0, sizeof( frame->flags ));\n\tframe->first_entity = cls.next_client_entities;\n\tframe->num_entities = 0;\n\tframe->valid = true;\n\n\tif( delta )\n\t{\n\t\tuint oldpacket = MSG_ReadByte( msg );\n\t\toldframe = &cl.frames[oldpacket & CL_UPDATE_MASK];\n\n\t\tif( !CL_ValidateDeltaPacket( oldpacket, oldframe ))\n\t\t{\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_FlushEntityPacketGS( frame, msg );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\treturn playerbytes;\n\t\t}\n\t}\n\telse\n\t{\n\t\toldframe = NULL;\n\t\tcls.demowaiting = false;\n\t}\n\n\tcl.validsequence = cls.netchan.incoming_sequence;\n\n\tMSG_StartBitWriting( msg );\n\n\toldent = NULL;\n\toldindex = 0;\n\toldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent );\n\n\t// read it all but ignore it\n\twhile( 1 )\n\t{\n\t\tint bufstart, newnum;\n\t\tqboolean player;\n\t\tdelta_header_t hdr;\n\t\tint val = MSG_ReadWord( msg );\n\n\t\tif( val )\n\t\t{\n\t\t\tMSG_SeekToBit( msg, -16, SEEK_CUR );\n\t\t\tnumbase = newnum = CL_ParseDeltaHeader( msg, delta, numbase, &hdr );\n\t\t}\n\t\telse break;\n\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\t\tplayer = CL_IsPlayerIndex( newnum );\n\n\t\twhile( oldnum < newnum )\n\t\t{\n\t\t\tif( !delta )\n\t\t\t\tCon_Printf( S_WARN \"%s: old frame copy on non-delta update (%d < %d)\\n\", __func__, oldnum, newnum );\n\n\t\t\t// one or more entities from the old packet are unchanged\n\t\t\tCL_CopyPacketEntity( frame, oldnum, oldent );\n\t\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t\t}\n\n\t\tbufstart = MSG_GetNumBytesRead( msg );\n\n\t\tif( oldnum == newnum )\n\t\t{\n\t\t\tif( !delta )\n\t\t\t\tCon_Printf( S_WARN \"%s: delta entity on non-delta update (%d)\\n\", __func__, oldnum );\n\n\t\t\t// from delta\n\t\t\tCL_DeltaEntityGS( &hdr, msg, frame, newnum, oldent );\n\t\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t\t}\n\t\telse if( oldnum > newnum )\n\t\t{\n\t\t\t// from baseline\n\t\t\tCL_DeltaEntityGS( &hdr, msg, frame, newnum, NULL );\n\t\t}\n\n\t\tif( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufstart;\n\t}\n\n\tif( MSG_CheckOverflow( msg ))\n\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\n\t// any remaining entities in the old frame are copied over\n\twhile( oldnum != MAX_ENTNUMBER )\n\t{\n\t\t// one or more entities from the old packet are unchanged\n\t\tCL_CopyPacketEntity( frame, oldnum, oldent );\n\t\toldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );\n\t}\n\n\tMSG_EndBitWriting( msg );\n\n\tif( frame->num_entities != count )\n\t\tCon_Reportf( S_WARN \"CL_Parse%sPacketEntitiesGS: (%i should be %i)\\n\", delta ? \"Delta\" : \"\", frame->num_entities, count );\n\n\tif( !frame->valid )\n\t\treturn playerbytes;\n\n\tCL_ProcessPacket( frame );\n\tCL_SetSolidEntities();\n\n\t// first update, received world, remove loading plaque\n\tif( cls.signon == ( SIGNONS - 1 ))\n\t{\n\t\tcls.signon = SIGNONS;\n\t\tCL_SignonReply( PROTO_GOLDSRC );\n\t}\n\n\treturn playerbytes;\n}\n\nstatic float MSG_ReadGSBitCoord( sizebuf_t *sb )\n{\n\tfloat value = 0;\n\tint ival, fval;\n\n\tival = MSG_ReadOneBit( sb );\n\tfval = MSG_ReadOneBit( sb );\n\n\tif( ival || fval )\n\t{\n\t\tint sign = MSG_ReadOneBit( sb );\n\n\t\tif( ival )\n\t\t\tival = MSG_ReadUBitLong( sb, 12 );\n\t\tif( fval )\n\t\t\tfval = MSG_ReadUBitLong( sb, 3 );\n\n\t\tvalue = (float)( fval / 8.0 + ival );\n\t\tif( sign )\n\t\t\tvalue = -value;\n\t}\n\n\treturn value;\n}\n\nstatic void MSG_ReadGSBitVec3Coord( sizebuf_t *sb, vec3_t fa )\n{\n\tqboolean x, y, z;\n\n\tVectorClear( fa );\n\n\tx = MSG_ReadOneBit( sb );\n\ty = MSG_ReadOneBit( sb );\n\tz = MSG_ReadOneBit( sb );\n\tif( x )\n\t\tfa[0] = MSG_ReadGSBitCoord( sb );\n\tif( y )\n\t\tfa[1] = MSG_ReadGSBitCoord( sb );\n\tif( z )\n\t\tfa[2] = MSG_ReadGSBitCoord( sb );\n}\n\nstatic void CL_ParseSoundPacketGS( sizebuf_t *msg )\n{\n\tvec3_t\tpos;\n\tint \tchan, sound;\n\tfloat \tvolume, attn;\n\tint\tflags, pitch, entnum;\n\tsound_t\thandle = 0;\n\n\tMSG_StartBitWriting( msg );\n\n\tflags = MSG_ReadUBitLong( msg, 9 );\n\n\tif( FBitSet( flags, SND_VOLUME ))\n\t\tvolume = (float)MSG_ReadByte( msg ) / 255.0f;\n\telse volume = VOL_NORM;\n\n\tif( FBitSet( flags, SND_ATTENUATION ))\n\t\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\telse attn = ATTN_NONE;\n\n\tchan = MSG_ReadUBitLong( msg, 3 );\n\tentnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );\n\tif( FBitSet( flags, SND_LEGACY_LARGE_INDEX ))\n\t\tsound = MSG_ReadWord( msg );\n\telse sound = MSG_ReadByte( msg );\n\tMSG_ReadGSBitVec3Coord( msg, pos );\n\n\tif( FBitSet( flags, SND_PITCH ))\n\t\tpitch = MSG_ReadByte( msg );\n\telse pitch = PITCH_NORM;\n\n\tMSG_EndBitWriting( msg );\n\n\tClearBits( flags, SND_LEGACY_LARGE_INDEX );\n\n\tif( FBitSet( flags, SND_SENTENCE ))\n\t{\n\t\tchar\tsentenceName[32];\n\t\tQ_snprintf( sentenceName, sizeof( sentenceName ), \"!%i\", sound );\n\t\thandle = S_RegisterSound( sentenceName );\n\t}\n\telse handle = cl.sound_index[sound];\t// see precached sound\n\n\tif( !cl.audio_prepped )\n\t\treturn; // too early\n\n\t// g-cont. sound and ambient sound have only difference with channel\n\tif( chan == CHAN_STATIC )\n\t{\n\t\tS_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );\n\t}\n\telse\n\t{\n\t\tS_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags );\n\t}\n}\n\nstatic void CL_ParseSpawnStaticSound( sizebuf_t *msg )\n{\n\tvec3_t pos;\n\tint handle, entnum, pitch, flags;\n\tfloat volume, attn;\n\n\tMSG_ReadVec3Coord( msg, pos );\n\thandle = MSG_ReadShort( msg );\n\tvolume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );\n\tattn   = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );\n\tentnum = MSG_ReadShort( msg );\n\tpitch  = MSG_ReadByte( msg );\n\tflags  = MSG_ReadByte( msg );\n\n\tS_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );\n}\n\n/*\n=====================================================================\n\nACTION MESSAGES\n\n=====================================================================\n*/\n/*\n=====================\nCL_ParseGoldSrcServerMessage\n\ndispatch messages\n=====================\n*/\nvoid CL_ParseGoldSrcServerMessage( sizebuf_t *msg )\n{\n\tsize_t\t\tbufStart, playerbytes;\n\tint\t\tcmd, param1;\n\tconst char\t*s;\n\n\t// parse the message\n\twhile( 1 )\n\t{\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t{\n\t\t\tHost_Error( \"%s: overflow!\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\n\t\t// mark start position\n\t\tbufStart = MSG_GetNumBytesRead( msg );\n\n\t\t// end of message (align bits)\n\t\tif( MSG_GetNumBitsLeft( msg ) < 8 )\n\t\t\tbreak;\n\n\t\tcmd = MSG_ReadServerCmd( msg );\n\n\t\t// record command for debugging spew on parse problem\n\t\tCL_Parse_RecordCommand( cmd, bufStart );\n\n\t\tif( CL_ParseCommonDLLMessage( msg, PROTO_GOLDSRC, cmd, bufStart ))\n\t\t\tcontinue;\n\n\t\t// other commands\n\t\tswitch( cmd )\n\t\t{\n\t\tcase svc_bad:\n\t\t\tHost_Error( \"svc_bad\\n\" );\n\t\t\tbreak;\n\t\tcase svc_nop:\n\t\tcase svc_spawnstatic:\n\t\tcase svc_goldsrc_damage:\n\t\tcase svc_goldsrc_killedmonster:\n\t\tcase svc_goldsrc_foundsecret:\n\t\t\t// this does nothing\n\t\t\tbreak;\n\t\tcase svc_disconnect:\n\t\t\ts = MSG_ReadString( msg );\n\t\t\tif( COM_CheckStringEmpty( s ))\n\t\t\t\tCon_Printf( \"Server issued disconnect. Reason: %s\\n\", s );\n\t\t\tCL_Drop ();\n\t\t\tHost_AbortCurrentFrame ();\n\t\t\tbreak;\n\t\tcase svc_event:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_ParseEvent( msg, PROTO_GOLDSRC );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_goldsrc_version:\n\t\t\tparam1 = MSG_ReadLong( msg );\n\t\t\tif( param1 != PROTOCOL_GOLDSRC_VERSION )\n\t\t\t\tHost_Error( \"Server use invalid protocol (%i should be %i)\\n\", param1, PROTOCOL_GOLDSRC_VERSION );\n\t\t\tbreak;\n\t\tcase svc_setview:\n\t\t\tCL_ParseViewEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_sound:\n\t\t\tCL_ParseSoundPacketGS( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_time:\n\t\t\tCL_ParseServerTime( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_print:\n\t\t\tCon_Printf( \"%s\", MSG_ReadString( msg ));\n\t\t\tbreak;\n\t\tcase svc_stufftext:\n\t\t\ts = MSG_ReadString( msg );\n\t\t\tif( cl_trace_stufftext.value )\n\t\t\t{\n\t\t\t\tsize_t len = Q_strlen( s );\n\t\t\t\tCon_Printf( \"Stufftext: %s%c\", s, len && s[len-1] == '\\n' ? '\\0' : '\\n' );\n\t\t\t}\n\n#ifdef HACKS_RELATED_HLMODS\n\t\t\t// disable Cry Of Fear antisave protection\n\t\t\tif( !Q_strnicmp( s, \"disconnect\", 10 ) && cls.signon != SIGNONS )\n\t\t\t\tbreak; // too early\n#endif\n\t\t\tCbuf_AddFilteredText( s );\n\t\t\tbreak;\n\t\tcase svc_setangle:\n\t\t\tCL_ParseSetAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_serverdata:\n\t\t\tCbuf_Execute(); // make sure any stuffed commands are done\n\t\t\tCL_ParseServerData( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_lightstyle:\n\t\t\tCL_ParseLightStyle( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_updateuserinfo:\n\t\t\tCL_UpdateUserinfo( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_deltatable:\n\t\t\tDelta_ParseTableField_GS( msg );\n\t\t\tbreak;\n\t\tcase svc_clientdata:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_ParseClientData( msg, PROTO_GOLDSRC );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_goldsrc_stopsound:\n\t\t\tparam1 = MSG_ReadWord( msg );\n\t\t\tS_StopSound( param1 >> 3, param1 & 7, NULL );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_pings:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_UpdateUserPings( msg );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tbreak;\n\t\tcase svc_particle:\n\t\t\tCL_ParseParticles( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_event_reliable:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_ParseReliableEvent( msg, PROTO_GOLDSRC );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_spawnbaseline:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_ParseBaseline( msg, PROTO_GOLDSRC );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tbreak;\n\t\tcase svc_setpause:\n\t\t\tcl.paused = ( MSG_ReadByte( msg ) != 0 );\n\t\t\tbreak;\n\t\tcase svc_signonnum:\n\t\t\tCL_ParseSignon( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_centerprint:\n\t\t\tCL_CenterPrint( MSG_ReadString( msg ), 0.25f );\n\t\t\tbreak;\n\t\tcase svc_goldsrc_spawnstaticsound:\n\t\t\tCL_ParseSpawnStaticSound( msg );\n\t\t\tbreak;\n\t\tcase svc_finale:\n\t\t\tCL_ParseFinaleCutscene( msg, 2 );\n\t\t\tbreak;\n\t\tcase svc_restore:\n\t\t\tCL_ParseRestore( msg );\n\t\t\tbreak;\n\t\tcase svc_cutscene:\n\t\t\tCL_ParseFinaleCutscene( msg, 3 );\n\t\t\tbreak;\n\t\tcase svc_goldsrc_decalname:\n\t\t\tparam1 = MSG_ReadByte( msg );\n\t\t\ts = MSG_ReadString( msg );\n\t\t\tQ_strncpy( host.draw_decals[param1], s, sizeof( host.draw_decals[param1] ));\n\t\t\tbreak;\n\t\tcase svc_addangle:\n\t\t\tCL_ParseAddAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_usermessage:\n\t\t\tCL_RegisterUserMessage( msg, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_packetentities:\n\t\t\tplayerbytes = CL_ParsePacketEntitiesGS( msg, false );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_deltapacketentities:\n\t\t\tplayerbytes = CL_ParsePacketEntitiesGS( msg, true );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.players += playerbytes;\n\t\t\tcl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes;\n\t\t\tbreak;\n\t\tcase svc_choke:\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true;\n\t\t\tcl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0;\n\t\t\tbreak;\n\t\tcase svc_resourcelist:\n\t\t\tMSG_StartBitWriting( msg );\n\t\t\tCL_ParseResourceList( msg, PROTO_GOLDSRC );\n\t\t\tMSG_EndBitWriting( msg );\n\t\t\tbreak;\n\t\tcase svc_deltamovevars:\n\t\t\tCL_ParseNewMovevars( msg );\n\t\t\tbreak;\n\t\tcase svc_resourcerequest:\n\t\t\tCL_ParseResourceRequest( msg );\n\t\t\tbreak;\n\t\tcase svc_customization:\n\t\t\tCL_ParseCustomization( msg );\n\t\t\tbreak;\n\t\tcase svc_crosshairangle:\n\t\t\tCL_ParseCrosshairAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_soundfade:\n\t\t\tCL_ParseSoundFade( msg );\n\t\t\tbreak;\n\t\tcase svc_filetxferfailed:\n\t\t\tCL_ParseFileTransferFailed( msg );\n\t\t\tbreak;\n\t\tcase svc_hltv:\n\t\t\tCL_ParseHLTV( msg );\n\t\t\tbreak;\n\t\tcase svc_voiceinit:\n\t\t\tCL_ParseVoiceInit( msg );\n\t\t\tbreak;\n\t\tcase svc_voicedata:\n\t\t\tCL_ParseVoiceData( msg, PROTO_GOLDSRC );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_resourcelocation:\n\t\t\tCL_ParseResLocation( msg );\n\t\t\tbreak;\n\t\tcase svc_goldsrc_sendextrainfo:\n\t\t\tCL_ParseExtraInfo( msg );\n\t\t\tbreak;\n\t\tcase svc_goldsrc_timescale:\n\t\t\t// we can set sys_timescale to anything we want but in GoldSrc it's locked for\n\t\t\t// HLTV and demoplayback. Do we really want to have it then if both are out of scope?\n\t\t\tCon_Reportf( S_ERROR \"%s: svc_goldsrc_timescale: implement me!\\n\", __func__ );\n\t\t\tMSG_ReadFloat( msg );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue:\n\t\t\tCL_ParseCvarValue( msg, false, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_querycvarvalue2:\n\t\t\tCL_ParseCvarValue( msg, true, PROTO_GOLDSRC );\n\t\t\tbreak;\n\t\tcase svc_exec:\n\t\t\tCL_ParseExec( msg );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCL_ParseUserMessage( msg, cmd, PROTO_LEGACY );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/client/cl_pmove.c",
    "content": "/*\ncl_pmove.c - client-side player physic\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"const.h\"\n#include \"cl_tent.h\"\n#include \"pm_local.h\"\n#include \"particledef.h\"\n#include \"studio.h\"\n\n#define MAX_FORWARD\t\t\t6\t// forward probes for set idealpitch\n#define MIN_CORRECTION_DISTANCE\t0.25f\t// use smoothing if error is > this\n#define MIN_PREDICTION_EPSILON\t0.5f\t// complain if error is > this and we have cl_showerror set\n#define MAX_PREDICTION_ERROR\t\t64.0f\t// above this is assumed to be a teleport, don't smooth, etc.\n\n/*\n=============\nCL_PushPMStates\n\n=============\n*/\nvoid GAME_EXPORT CL_PushPMStates( void )\n{\n\tif( clgame.pushed ) return;\n\tclgame.oldphyscount = clgame.pmove->numphysent;\n\tclgame.oldviscount  = clgame.pmove->numvisent;\n\tclgame.pushed = true;\n}\n\n/*\n=============\nCL_PopPMStates\n\n=============\n*/\nvoid GAME_EXPORT CL_PopPMStates( void )\n{\n\tif( !clgame.pushed ) return;\n\tclgame.pmove->numphysent = clgame.oldphyscount;\n\tclgame.pmove->numvisent  = clgame.oldviscount;\n\tclgame.pushed = false;\n}\n\n/*\n===============\nCL_IsPredicted\n===============\n*/\nstatic qboolean CL_IsPredicted( void )\n{\n\tif( cl_nopred.value || cl.intermission )\n\t\treturn false;\n\n\t// never predict the quake demos\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t\treturn false;\n\treturn true;\n}\n\n/*\n===============\nCL_SetLastUpdate\n===============\n*/\nvoid CL_SetLastUpdate( void )\n{\n\tcls.lastupdate_sequence = cls.netchan.incoming_sequence;\n}\n\n/*\n===============\nCL_RedoPrediction\n===============\n*/\nvoid CL_RedoPrediction( void )\n{\n\tif ( cls.netchan.incoming_sequence != cls.lastupdate_sequence )\n\t{\n\t\tCL_PredictMovement( true );\n\t\tCL_CheckPredictionError();\n\t}\n}\n\n/*\n===============\nCL_SetIdealPitch\n===============\n*/\nvoid CL_SetIdealPitch( void )\n{\n\tfloat\tangleval, sinval, cosval;\n\tint\ti, j, step, dir, steps;\n\tfloat\tz[MAX_FORWARD];\n\tvec3_t\ttop, bottom;\n\tpmtrace_t\ttr;\n\n\tif( cl.local.onground == -1 )\n\t\treturn;\n\n\tangleval = cl.viewangles[YAW] * M_PI2 / 360.0f;\n\tSinCos( angleval, &sinval, &cosval );\n\n\t// Now move forward by 36, 48, 60, etc. units from the eye position and drop lines straight down\n\t// 160 or so units to see what's below\n\tfor( i = 0; i < MAX_FORWARD; i++ )\n\t{\n\t\ttop[0] = cl.simorg[0] + cosval * (i + 3.0f) * 12.0f;\n\t\ttop[1] = cl.simorg[1] + sinval * (i + 3.0f) * 12.0f;\n\t\ttop[2] = cl.simorg[2] + cl.viewheight[2];\n\n\t\tbottom[0] = top[0];\n\t\tbottom[1] = top[1];\n\t\tbottom[2] = top[2] - 160.0f;\n\n\t\t// skip any monsters (only world and brush models)\n\t\ttr = CL_TraceLine( top, bottom, PM_STUDIO_BOX );\n\t\tif( tr.allsolid ) return; // looking at a wall, leave ideal the way is was\n\n\t\tif( tr.fraction == 1.0f )\n\t\t\treturn;\t// near a dropoff\n\n\t\tz[i] = top[2] + tr.fraction * (bottom[2] - top[2]);\n\t}\n\n\tdir = 0;\n\tsteps = 0;\n\n\tfor( j = 1; j < i; j++ )\n\t{\n\t\tstep = z[j] - z[j-1];\n\t\tif( step > -ON_EPSILON && step < ON_EPSILON )\n\t\t\tcontinue;\n\n\t\tif( dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ))\n\t\t\treturn; // mixed changes\n\n\t\tsteps++;\n\t\tdir = step;\n\t}\n\n\tif( !dir )\n\t{\n\t\tcl.local.idealpitch = 0.0f;\n\t\treturn;\n\t}\n\n\tif( steps < 2 ) return;\n\tcl.local.idealpitch = -dir * cl_idealpitchscale.value;\n}\n\n/*\n==================\nCL_PlayerTeleported\n\ncheck for instant movement in case\nwe don't want interpolate this\n==================\n*/\nstatic qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to )\n{\n\tint\tlen, maxlen;\n\tvec3_t\tdelta;\n\n\tVectorSubtract( to->playerstate.origin, from->playerstate.origin, delta );\n\n\t// compute potential max movement in units per frame and compare with entity movement\n\tmaxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));\n\tlen = VectorLength( delta );\n\n\treturn (len > maxlen);\n}\n\n/*\n===================\nCL_CheckPredictionError\n===================\n*/\nvoid CL_CheckPredictionError( void )\n{\n\tint\t\tframe, cmd;\n\tstatic int\tpos = 0;\n\tvec3_t\t\tdelta;\n\tfloat\t\tdist;\n\n\tif( !CL_IsPredicted( ))\n\t\treturn;\n\n\t// calculate the last usercmd_t we sent that the server has processed\n\tframe = ( cls.netchan.incoming_acknowledged ) & CL_UPDATE_MASK;\n\tcmd = cl.parsecountmod;\n\n\t// compare what the server returned with what we had predicted it to be\n\tVectorSubtract( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame], delta );\n\tdist = VectorLength( delta );\n\n\t// save the prediction error for interpolation\n\tif( dist > MAX_PREDICTION_ERROR )\n\t{\n\t\tif( cl_showerror.value && host_developer.value )\n\t\t\tCon_NPrintf( 10 + ( ++pos & 3 ), \"^3player teleported:^7 %.3f units\\n\", dist );\n\n\t\t// a teleport or something or gamepaused\n\t\tVectorClear( cl.local.prediction_error );\n\t}\n\telse\n\t{\n\t\tif( cl_showerror.value && dist > MIN_PREDICTION_EPSILON && host_developer.value )\n\t\t\tCon_NPrintf( 10 + ( ++pos & 3 ), \"^1prediction error:^7 %.3f units\\n\", dist );\n\n\t\tVectorCopy( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame] );\n\n\t\t// save for error interpolation\n\t\tVectorCopy( delta, cl.local.prediction_error );\n\n\t\t// GoldSrc checks for singleplayer\n\t\t// we would check for local server\n\t\tif( dist > MIN_CORRECTION_DISTANCE && !SV_Active() )\n\t\t\tcls.correction_time = cl_smoothtime.value;\n\t}\n}\n\n/*\n=============\nCL_SetUpPlayerPrediction\n\nCalculate the new position of players, without other player clipping\nWe do this to set up real player prediction.\nPlayers are predicted twice, first without clipping other players,\nthen with clipping against them.\nThis sets up the first phase.\n=============\n*/\nvoid GAME_EXPORT CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient )\n{\n\tentity_state_t\t*state;\n\tpredicted_player_t\t*player;\n\tcl_entity_t\t*ent;\n\tint\t\ti;\n\n\tfor( i = 0; i < MAX_CLIENTS; i++ )\n\t{\n\t\tstate = &cl.frames[cl.parsecountmod].playerstate[i];\n\t\tplayer = &cls.predicted_players[i];\n\n\t\tplayer->active = false;\n\n\t\tif( state->messagenum != cl.parsecount )\n\t\t\tcontinue; // not present this frame\n\n\t\tif( !state->modelindex )\n\t\t\tcontinue;\n\n\t\tplayer->active = true;\n\t\tplayer->movetype = state->movetype;\n\t\tplayer->solid = state->solid;\n\t\tplayer->usehull = state->usehull;\n\n\t\tif( FBitSet( state->effects, EF_NODRAW ) && !bIncludeLocalClient && ( cl.playernum == i ))\n\t\t\tcontinue;\n\n\t\t// note that the local player is special, since he moves locally\n\t\t// we use his last predicted postition\n\t\tif( cl.playernum == i )\n\t\t{\n\t\t\tVectorCopy( state->origin, player->origin );\n\t\t\tVectorCopy( state->angles, player->angles );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent = CL_GetEntityByIndex( i + 1 );\n\n\t\t\tCL_ComputePlayerOrigin( ent );\n\n\t\t\tVectorCopy( ent->origin, player->origin );\n\t\t\tVectorCopy( ent->angles, player->angles );\n\t\t}\n\t}\n}\n\nvoid CL_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr )\n{\n\tAssert( tr != NULL );\n\n\tif( clgame.dllFuncs.pfnClipMoveToEntity != NULL )\n\t{\n\t\t// do custom sweep test\n\t\tclgame.dllFuncs.pfnClipMoveToEntity( pe, start, mins, maxs, end, tr );\n\t}\n\telse\n\t{\n\t\t// function is missed, so we didn't hit anything\n\t\ttr->allsolid = false;\n\t}\n}\n\nstatic void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboolean visent )\n{\n\tmodel_t\t*mod = CL_ModelHandle( state->modelindex );\n\n\tpe->player = 0;\n\n\tif( state->number >= 1 && state->number <= cl.maxclients )\n\t\tpe->player = state->number;\n\n\tif( pe->player )\n\t{\n\t\t// client or bot\n\t\tQ_snprintf( pe->name, sizeof( pe->name ), \"player %i\", pe->player - 1 );\n\t}\n\telse if( mod != NULL )\n\t{\n\t\t// otherwise copy the modelname\n\t\tQ_strncpy( pe->name, mod->name, sizeof( pe->name ));\n\t}\n\telse\n\t{\n\t\tQ_strncpy( pe->name, \"entity %i\", state->number );\n\t}\n\n\tpe->model = pe->studiomodel = NULL;\n\n\tVectorCopy( state->mins, pe->mins );\n\tVectorCopy( state->maxs, pe->maxs );\n\n\tif( state->solid == SOLID_BBOX )\n\t{\n\t\tif( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ))\n\t\t\tpe->studiomodel = mod;\n\t}\n\telse\n\t{\n\t\tif( pe->solid != SOLID_BSP && ( mod != NULL ) && ( mod->type == mod_studio ))\n\t\t\tpe->studiomodel = mod;\n\t\telse pe->model = mod;\n\t}\n\n\t// rare case: not solid entities in vistrace\n\tif( visent && VectorIsNull( pe->mins ) && mod != NULL )\n\t{\n\t\tVectorCopy( mod->mins, pe->mins );\n\t\tVectorCopy( mod->maxs, pe->maxs );\n\t}\n\n\tpe->info = state->number;\n\tVectorCopy( state->origin, pe->origin );\n\tVectorCopy( state->angles, pe->angles );\n\n\tpe->solid = state->solid;\n\tpe->rendermode = state->rendermode;\n\tpe->skin = state->skin;\n\tpe->frame = state->frame;\n\tpe->sequence = state->sequence;\n\n\tmemcpy( &pe->controller[0], &state->controller[0], sizeof( pe->controller ));\n\tmemcpy( &pe->blending[0], &state->blending[0], sizeof( pe->blending ));\n\n\tpe->movetype = state->movetype;\n\tpe->takedamage = (pe->player) ? DAMAGE_YES : DAMAGE_NO;\n\tpe->team = state->team;\n\tpe->classnumber = state->playerclass;\n\tpe->blooddecal = 0;\t// unused in GoldSrc\n\n\t// for mods\n\tpe->iuser1 = state->iuser1;\n\tpe->iuser2 = state->iuser2;\n\tpe->iuser3 = state->iuser3;\n\tpe->iuser4 = state->iuser4;\n\tpe->fuser1 = state->fuser1;\n\tpe->fuser2 = state->fuser2;\n\tpe->fuser3 = state->fuser3;\n\tpe->fuser4 = state->fuser4;\n\n\tVectorCopy( state->vuser1, pe->vuser1 );\n\tVectorCopy( state->vuser2, pe->vuser2 );\n\tVectorCopy( state->vuser3, pe->vuser3 );\n\tVectorCopy( state->vuser4, pe->vuser4 );\n}\n\n/*\n====================\nCL_AddLinksToPmove\n\ncollect solid entities\n====================\n*/\nstatic void CL_AddLinksToPmove( frame_t *frame )\n{\n\tentity_state_t\t*state;\n\tmodel_t\t\t*model;\n\tphysent_t\t\t*pe;\n\tint\t\ti;\n\n\tif( !frame->valid ) return;\n\n\tfor( i = 0; i < frame->num_entities; i++ )\n\t{\n\t\tstate = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];\n\n\t\tif( state->number >= 1 && state->number <= cl.maxclients )\n\t\t\tcontinue;\n\n\t\tif( !state->modelindex )\n\t\t\tcontinue;\n\n\t\tmodel = CL_ModelHandle( state->modelindex );\n\t\tif( !model ) continue;\n\n\t\tif(( state->owner != 0 ) && ( state->owner == cl.playernum + 1 ))\n\t\t\tcontinue;\n\n\t\tif(( model->hulls[1].lastclipnode || model->type == mod_studio ) && clgame.pmove->numvisent < MAX_PHYSENTS )\n\t\t{\n\t\t\tpe = &clgame.pmove->visents[clgame.pmove->numvisent];\n\t\t\tCL_CopyEntityToPhysEnt( pe, state, true );\n\t\t\tclgame.pmove->numvisent++;\n\t\t}\n\n\t\tif( state->solid == SOLID_TRIGGER || ( state->solid == SOLID_NOT && state->skin >= CONTENTS_EMPTY ))\n\t\t\tcontinue;\n\n\t\t// dead body\n\t\tif( state->mins[2] == 0.0f && state->maxs[2] == 1.0f )\n\t\t\tcontinue;\n\n\t\t// can't collide with zeroed hull\n\t\tif( VectorIsNull( state->mins ) && VectorIsNull( state->maxs ))\n\t\t\tcontinue;\n\n\t\tif( state->solid == SOLID_NOT && state->skin == CONTENTS_LADDER )\n\t\t{\n\t\t\tif( clgame.pmove->nummoveent >= MAX_MOVEENTS )\n\t\t\t\tcontinue;\n\n\t\t\tpe = &clgame.pmove->moveents[clgame.pmove->nummoveent];\n\t\t\tCL_CopyEntityToPhysEnt( pe, state, false );\n\t\t\tclgame.pmove->nummoveent++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !model->hulls[1].lastclipnode && model->type != mod_studio )\n\t\t\t\tcontinue;\n\n\t\t\t// reserve slots for all the clients\n\t\t\tif( clgame.pmove->numphysent >= ( MAX_PHYSENTS - cl.maxclients ))\n\t\t\t\tcontinue;\n\n\t\t\tpe = &clgame.pmove->physents[clgame.pmove->numphysent];\n\t\t\tCL_CopyEntityToPhysEnt( pe, state, false );\n\t\t\tclgame.pmove->numphysent++;\n\t\t}\n\t}\n}\n\n/*\n===============\nCL_SetSolidEntities\n\nBuilds all the pmove physents for the current frame\n===============\n*/\nvoid CL_SetSolidEntities( void )\n{\n\tphysent_t\t*pe = clgame.pmove->physents;\n\n\t// setup physents\n\tclgame.pmove->numvisent = 1;\n\tclgame.pmove->numphysent = 1;\n\tclgame.pmove->nummoveent = 0;\n\n\tmemset( clgame.pmove->physents, 0, sizeof( physent_t ));\n\tmemset( clgame.pmove->visents, 0, sizeof( physent_t ));\n\n\tpe->model = cl.worldmodel;\n\tif( pe->model ) Q_strncpy( pe->name, pe->model->name, sizeof( pe->name ));\n\tpe->takedamage = DAMAGE_YES;\n\tpe->solid = SOLID_BSP;\n\n\t// share to visents\n\tclgame.pmove->visents[0] = clgame.pmove->physents[0];\n\n\t// add all other entities exlucde players\n\tCL_AddLinksToPmove( &cl.frames[cl.parsecountmod] );\n}\n\n/*\n===============\nCL_SetSolidPlayers\n\nBuilds all the pmove physents for the current frame\nNote that CL_SetUpPlayerPrediction() must be called first!\npmove must be setup with world and solid entity hulls before calling\n(via CL_PredictMove)\n===============\n*/\nvoid GAME_EXPORT CL_SetSolidPlayers( int playernum )\n{\n\tentity_state_t\t*state;\n\tpredicted_player_t\t*player;\n\tphysent_t\t\t*pe;\n\tint\t\ti;\n\n\tif( !cl_solid_players.value )\n\t\treturn;\n\n\tfor( i = 0; i < MAX_CLIENTS; i++ )\n\t{\n\t\tstate = &cl.frames[cl.parsecountmod].playerstate[i];\n\t\tplayer = &cls.predicted_players[i];\n\n\t\tif( playernum == -1 )\n\t\t{\n\t\t\tif( i != cl.playernum && !player->active )\n\t\t\t\tcontinue;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !player->active )\n\t\t\t\tcontinue;\t// not present this frame\n\n\t\t\t// the player object never gets added\n\t\t\tif( playernum == i )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( player->solid == SOLID_NOT )\n\t\t\tcontinue;\t// dead body\n\n\t\tif( clgame.pmove->numphysent >= MAX_PHYSENTS )\n\t\t\tbreak;\n\n\t\tpe = &clgame.pmove->physents[clgame.pmove->numphysent];\n\t\tCL_CopyEntityToPhysEnt( pe, state, false );\n\t\tclgame.pmove->numphysent++;\n\n\t\t// some fields needs to be override from cls.predicted_players\n\t\tVectorCopy( player->origin, pe->origin );\n\t\tVectorCopy( player->angles, pe->angles );\n\t\tVectorCopy( host.player_mins[player->usehull], pe->mins );\n\t\tVectorCopy( host.player_maxs[player->usehull], pe->maxs );\n\t\tpe->movetype = player->movetype;\n\t\tpe->solid = player->solid;\n\t}\n}\n\n/*\n=============\nCL_WaterEntity\n\n=============\n*/\nint GAME_EXPORT CL_WaterEntity( const float *rgflPos )\n{\n\tphysent_t\t\t*pe;\n\thull_t\t\t*hull;\n\tvec3_t\t\ttest, offset;\n\tint\t\ti, oldhull;\n\n\tif( !rgflPos ) return -1;\n\n\toldhull = clgame.pmove->usehull;\n\n\tfor( i = 0; i < clgame.pmove->numphysent; i++ )\n\t{\n\t\tpe = &clgame.pmove->physents[i];\n\n\t\tif( pe->solid != SOLID_NOT ) // disabled ?\n\t\t\tcontinue;\n\n\t\t// only brushes can have special contents\n\t\tif( !pe->model || pe->model->type != mod_brush )\n\t\t\tcontinue;\n\n\t\t// check water brushes accuracy\n\t\tclgame.pmove->usehull = 2;\n\t\thull = PM_HullForBsp( pe, clgame.pmove, offset );\n\t\tclgame.pmove->usehull = oldhull;\n\n\t\t// offset the test point appropriately for this hull.\n\t\tVectorSubtract( rgflPos, offset, test );\n\n\t\tif( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles ))\n\t\t{\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, rgflPos, test );\n\t\t}\n\n\t\t// test hull for intersection with this model\n\t\tif( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )\n\t\t\tcontinue;\n\n\t\t// found water entity\n\t\treturn pe->info;\n\t}\n\treturn -1;\n}\n\n/*\n=============\nCL_TraceLine\n\na simple engine traceline\n=============\n*/\npmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags )\n{\n\tint\told_usehull;\n\tpmtrace_t\ttr;\n\n\told_usehull = clgame.pmove->usehull;\n\tclgame.pmove->usehull = 2;\n\ttr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numphysent, clgame.pmove->physents, -1, NULL );\n\tclgame.pmove->usehull = old_usehull;\n\n\treturn tr;\n}\n\n/*\n=============\nCL_VisTraceLine\n\ntrace by visible objects (thats can be non-solid)\n=============\n*/\npmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags )\n{\n\tint\t\told_usehull;\n\tstatic pmtrace_t\ttr;\n\n\told_usehull = clgame.pmove->usehull;\n\tclgame.pmove->usehull = 2;\n\ttr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numvisent, clgame.pmove->visents, -1, NULL );\n\tclgame.pmove->usehull = old_usehull;\n\n\treturn &tr;\n}\n\n/*\n=============\nCL_GetWaterEntity\n\nreturns water brush where inside pos\n=============\n*/\ncl_entity_t *CL_GetWaterEntity( const float *rgflPos )\n{\n\tint\tentnum;\n\n\tentnum = CL_WaterEntity( rgflPos );\n\tif( entnum <= 0 ) return NULL; // world or not water\n\n\treturn CL_GetEntityByIndex( entnum );\n}\n\nstatic int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace )\n{\n\treturn PM_TestPlayerPosition( clgame.pmove, pos, ptrace, NULL );\n}\n\nstatic void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr )\n{\n\tPM_StuckTouch( clgame.pmove, hitent, tr );\n}\n\nstatic int GAME_EXPORT pfnTruePointContents( float *p )\n{\n\treturn PM_TruePointContents( clgame.pmove, p );\n}\n\nstatic pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe )\n{\n\treturn PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );\n}\n\nstatic void *pfnHullForBsp( physent_t *pe, float *offset )\n{\n\treturn PM_HullForBsp( pe, clgame.pmove, offset );\n}\n\nstatic float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace )\n{\n\treturn PM_TraceModel( clgame.pmove, pe, start, end, trace );\n}\n\nstatic void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch )\n{\n\tif( !clgame.pmove->runfuncs )\n\t\treturn;\n\n\tS_StartSound( NULL, clgame.pmove->player_index + 1, channel, S_RegisterSound( sample ), volume, attenuation, pitch, fFlags );\n}\n\nstatic void GAME_EXPORT pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )\n{\n\tCL_PlaybackEvent( flags, NULL, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 );\n}\n\nstatic pmtrace_t GAME_EXPORT pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter )\n{\n\treturn PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter );\n}\n\nstatic int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter )\n{\n\treturn PM_TestPlayerPosition( clgame.pmove, pos, ptrace, pmFilter );\n}\n\nstatic pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter )\n{\n\treturn PM_TraceLineEx( clgame.pmove, start, end, flags, usehull, pmFilter );\n}\n\n/*\n===============\nCL_InitClientMove\n\n===============\n*/\nvoid CL_InitClientMove( void )\n{\n\tint\ti;\n\n\tPmove_Init ();\n\n\tclgame.pmove->server = false;\t// running at client\n\tclgame.pmove->movevars = &clgame.movevars;\n\tclgame.pmove->runfuncs = false;\n\n\t// enumerate client hulls\n\tfor( i = 0; i < MAX_MAP_HULLS; i++ )\n\t{\n\t\tif( clgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))\n\t\t\tCon_Reportf( \"CL: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\\n\", i,\n\t\t\thost.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2],\n\t\t\thost.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] );\n\t}\n\n\tmemcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));\n\tmemcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));\n\n\t// common utilities\n\tclgame.pmove->PM_Info_ValueForKey = Info_ValueForKey;\n\tclgame.pmove->PM_Particle = CL_Particle; // ref should be initialized here already\n\tclgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition;\n\tclgame.pmove->Con_NPrintf = Con_NPrintf;\n\tclgame.pmove->Con_DPrintf = Con_DPrintf;\n\tclgame.pmove->Con_Printf = Con_Printf;\n\tclgame.pmove->Sys_FloatTime = Sys_DoubleTime;\n\tclgame.pmove->PM_StuckTouch = pfnStuckTouch;\n\tclgame.pmove->PM_PointContents = (void*)PM_CL_PointContents;\n\tclgame.pmove->PM_TruePointContents = pfnTruePointContents;\n\tclgame.pmove->PM_HullPointContents = (void*)PM_HullPointContents;\n\tclgame.pmove->PM_PlayerTrace = pfnPlayerTrace;\n\tclgame.pmove->PM_TraceLine = PM_CL_TraceLine;\n\tclgame.pmove->RandomLong = COM_RandomLong;\n\tclgame.pmove->RandomFloat = COM_RandomFloat;\n\tclgame.pmove->PM_GetModelType = pfnGetModelType;\n\tclgame.pmove->PM_GetModelBounds = pfnGetModelBounds;\n\tclgame.pmove->PM_HullForBsp = pfnHullForBsp;\n\tclgame.pmove->PM_TraceModel = pfnTraceModel;\n\tclgame.pmove->COM_FileSize = COM_FileSize;\n\tclgame.pmove->COM_LoadFile = COM_LoadFile;\n\tclgame.pmove->COM_FreeFile = COM_FreeFile;\n\tclgame.pmove->memfgets = COM_MemFgets;\n\tclgame.pmove->PM_PlaySound = pfnPlaySound;\n\tclgame.pmove->PM_TraceTexture = PM_CL_TraceTexture;\n\tclgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull;\n\tclgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx;\n\tclgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx;\n\tclgame.pmove->PM_TraceLineEx = pfnTraceLineEx;\n\tclgame.pmove->PM_TraceSurface = pfnTraceSurface;\n\n\t// initalize pmove\n\tclgame.dllFuncs.pfnPlayerMoveInit( clgame.pmove );\n}\n\nstatic void CL_SetupPMove( playermove_t *pmove, const local_state_t *from, const usercmd_t *ucmd, qboolean runfuncs, double time )\n{\n\tconst entity_state_t\t*ps;\n\tconst clientdata_t\t*cd;\n\n\tps = &from->playerstate;\n\tcd = &from->client;\n\n\tpmove->player_index = ps->number - 1;\n\n\t// a1ba: workaround bug where the server refuse to send our local player in delta\n\t// cl.playernum, in theory, must be equal to our local player index anyway\n\t//\n\t// this might not be a real solution, since everything else will be bogus\n\t// but we need to properly run prediction and avoid potential memory\n\t// corruption\n\tif( pmove->player_index < 0 )\n\t\tpmove->player_index = bound( 0, cl.playernum, cl.maxclients - 1 );\n\n\tpmove->multiplayer = (cl.maxclients > 1);\n\tpmove->runfuncs = runfuncs;\n\tpmove->time = time * 1000.0f;\n\tpmove->frametime = ucmd->msec / 1000.0f;\n\tVectorCopy( ps->origin, pmove->origin );\n\tVectorCopy( ps->angles, pmove->angles );\n\tVectorCopy( pmove->angles, pmove->oldangles );\n\tVectorCopy( cd->velocity, pmove->velocity );\n\tVectorCopy( ps->basevelocity, pmove->basevelocity );\n\tVectorCopy( cd->view_ofs, pmove->view_ofs );\n\tVectorClear( pmove->movedir );\n\tpmove->flDuckTime = (float)cd->flDuckTime;\n\tpmove->bInDuck = cd->bInDuck;\n\tpmove->usehull = ps->usehull;\n\tpmove->flTimeStepSound = cd->flTimeStepSound;\n\tpmove->iStepLeft = ps->iStepLeft;\n\tpmove->flFallVelocity = ps->flFallVelocity;\n\tpmove->flSwimTime = (float)cd->flSwimTime;\n\tVectorCopy( cd->punchangle, pmove->punchangle );\n\tpmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code\n\tpmove->effects = ps->effects;\n\tpmove->flags = cd->flags;\n\tpmove->gravity = ps->gravity;\n\tpmove->friction = ps->friction;\n\tpmove->oldbuttons = ps->oldbuttons;\n\tpmove->waterjumptime = (float)cd->waterjumptime;\n\tpmove->dead = (cl.local.health <= 0);\n\tpmove->deadflag = cd->deadflag;\n\tpmove->spectator = (cls.spectator != 0);\n\tpmove->movetype = ps->movetype;\n\tpmove->onground = ps->onground;\n\tpmove->waterlevel = cd->waterlevel;\n\tpmove->watertype = cd->watertype;\n\tpmove->maxspeed = clgame.movevars.maxspeed;\n\tpmove->clientmaxspeed = cd->maxspeed;\n\tpmove->iuser1 = cd->iuser1;\n\tpmove->iuser2 = cd->iuser2;\n\tpmove->iuser3 = cd->iuser3;\n\tpmove->iuser4 = cd->iuser4;\n\tpmove->fuser1 = cd->fuser1;\n\tpmove->fuser2 = cd->fuser2;\n\tpmove->fuser3 = cd->fuser3;\n\tpmove->fuser4 = cd->fuser4;\n\tVectorCopy( cd->vuser1, pmove->vuser1 );\n\tVectorCopy( cd->vuser2, pmove->vuser2 );\n\tVectorCopy( cd->vuser3, pmove->vuser3 );\n\tVectorCopy( cd->vuser4, pmove->vuser4 );\n\tpmove->cmd = *ucmd;\t// copy current cmds\n\n\tQ_strncpy( pmove->physinfo, cls.physinfo, sizeof( pmove->physinfo ));\n}\n\nstatic const void CL_FinishPMove( const playermove_t *pmove, local_state_t *to )\n{\n\tentity_state_t\t*ps;\n\tclientdata_t\t*cd;\n\n\tps = &to->playerstate;\n\tcd = &to->client;\n\n\tcd->flags = pmove->flags;\n\tcd->bInDuck = pmove->bInDuck;\n\tcd->flTimeStepSound = pmove->flTimeStepSound;\n\tcd->flDuckTime = (int)pmove->flDuckTime;\n\tcd->flSwimTime = (int)pmove->flSwimTime;\n\tcd->waterjumptime = (int)pmove->waterjumptime;\n\tcd->watertype = pmove->watertype;\n\tcd->waterlevel = pmove->waterlevel;\n\tcd->maxspeed = pmove->clientmaxspeed;\n\tcd->deadflag = pmove->deadflag;\n\tVectorCopy( pmove->velocity, cd->velocity );\n\tVectorCopy( pmove->view_ofs, cd->view_ofs );\n\tVectorCopy( pmove->origin, ps->origin );\n\tVectorCopy( pmove->angles, ps->angles );\n\tVectorCopy( pmove->basevelocity, ps->basevelocity );\n\tVectorCopy( pmove->punchangle, cd->punchangle );\n\tps->oldbuttons = (uint)pmove->cmd.buttons;\n\tps->friction = pmove->friction;\n\tps->movetype = pmove->movetype;\n\tps->onground = pmove->onground;\n\tps->effects = pmove->effects;\n\tps->usehull = pmove->usehull;\n\tps->iStepLeft = pmove->iStepLeft;\n\tps->flFallVelocity = pmove->flFallVelocity;\n\tcd->iuser1 = pmove->iuser1;\n\tcd->iuser2 = pmove->iuser2;\n\tcd->iuser3 = pmove->iuser3;\n\tcd->iuser4 = pmove->iuser4;\n\tcd->fuser1 = pmove->fuser1;\n\tcd->fuser2 = pmove->fuser2;\n\tcd->fuser3 = pmove->fuser3;\n\tcd->fuser4 = pmove->fuser4;\n\tVectorCopy( pmove->vuser1, cd->vuser1 );\n\tVectorCopy( pmove->vuser2, cd->vuser2 );\n\tVectorCopy( pmove->vuser3, cd->vuser3 );\n\tVectorCopy( pmove->vuser4, cd->vuser4 );\n}\n\n/*\n=================\nCL_RunUsercmd\n\nRuns prediction code for user cmd\n=================\n*/\nstatic void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed )\n{\n\tusercmd_t\t\tcmd;\n\n\tif( u->msec > 50 )\n\t{\n\t\tlocal_state_t\ttemp;\n\t\tusercmd_t\t\tsplit;\n\n\t\tmemset( &temp, 0, sizeof( temp ));\n\n\t\tsplit = *u;\n\t\tsplit.msec /= 2;\n\t\tCL_RunUsercmd( from, &temp, &split, runfuncs, time, random_seed );\n\t\tsplit.impulse = split.weaponselect = 0;\n\t\tCL_RunUsercmd( &temp, to, &split, runfuncs, time, random_seed );\n\t\treturn;\n\t}\n\n\tcmd = *u;\t// deal with local copy\n\t*to = *from;\n\n\tif( CL_IsPredicted( ))\n\t{\n\t\t// setup playermove state\n\t\tCL_SetupPMove( clgame.pmove, from, &cmd, runfuncs, *time );\n\n\t\t// motor!\n\t\tclgame.dllFuncs.pfnPlayerMove( clgame.pmove, false );\n\n\t\t// copy results back to client\n\t\tCL_FinishPMove( clgame.pmove, to );\n\n\t\tif( clgame.pmove->onground > 0 && clgame.pmove->onground < clgame.pmove->numphysent )\n\t\t\tcl.local.lastground = clgame.pmove->physents[clgame.pmove->onground].info;\n\t\telse cl.local.lastground = clgame.pmove->onground; // world(0) or in air(-1)\n\t}\n\n\tclgame.dllFuncs.pfnPostRunCmd( from, to, &cmd, runfuncs, *time, random_seed );\n\n\t*time += (double)cmd.msec / 1000.0;\n}\n\n\n/*\n=================\nCL_MoveSpectatorCamera\n\nspectator movement code\n=================\n*/\nvoid CL_MoveSpectatorCamera( void )\n{\n\tdouble\ttime = cl.time;\n\n\tif( !cls.spectator )\n\t\treturn;\n\n\tCL_SetUpPlayerPrediction( false, true );\n\tCL_SetSolidPlayers( cl.playernum );\n\tCL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, &cl.cmd, true, &time, (uint)( time * 100.0 ));\n\n\tVectorCopy( cls.spectator_state.client.velocity, cl.simvel );\n\tVectorCopy( cls.spectator_state.client.origin, cl.simorg );\n\tVectorCopy( cls.spectator_state.client.punchangle, cl.punchangle );\n\tVectorCopy( cls.spectator_state.client.view_ofs, cl.viewheight );\n}\n\n/*\n=================\nCL_PredictMovement\n\nSets cl.predicted.origin and cl.predicted.angles\n=================\n*/\nvoid CL_PredictMovement( qboolean repredicting )\n{\n\truncmd_t\t\t*to_cmd = NULL, *from_cmd;\n\tlocal_state_t\t*from = NULL, *to = NULL;\n\tframe_t *frame = NULL;\n\tuint\t\ti, stoppoint;\n\tdouble\t\tf = 1.0;\n\tdouble\t\ttime;\n\n\tif( cls.state != ca_active || cls.spectator )\n\t\treturn;\n\n\tif( cls.demoplayback && !repredicting )\n\t\tCL_DemoInterpolateAngles();\n\n\tCL_SetUpPlayerPrediction( false, false );\n\n\tif( !cl.validsequence )\n\t\treturn;\n\n\tif(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK )\n\t\treturn;\n\n\t// this is the last frame received from the server\n\tframe = &cl.frames[cl.parsecountmod];\n\n\tif( !CL_IsPredicted( ))\n\t{\n\t\tVectorCopy( frame->clientdata.velocity, cl.simvel );\n\t\tVectorCopy( frame->clientdata.origin, cl.simorg );\n\t\tVectorCopy( frame->clientdata.punchangle, cl.punchangle );\n\t\tVectorCopy( frame->clientdata.view_ofs, cl.viewheight );\n\t\tcl.local.usehull = frame->playerstate[cl.playernum].usehull;\n\t\tcl.local.waterlevel = frame->clientdata.waterlevel;\n\n\t\tif( FBitSet( frame->clientdata.flags, FL_ONGROUND ))\n\t\t\tcl.local.onground = frame->playerstate[cl.playernum].onground;\n\t\telse cl.local.onground = -1;\n\t}\n\n\tfrom = &cl.predicted_frames[cl.parsecountmod];\n\tfrom_cmd = &cl.commands[cls.netchan.incoming_acknowledged & CL_UPDATE_MASK];\n\tmemcpy( from->weapondata, frame->weapondata, sizeof( from->weapondata ));\n\tfrom->playerstate = frame->playerstate[cl.playernum];\n\tfrom->client = frame->clientdata;\n\tif( !frame->valid ) return;\n\n\ttime = frame->time;\n\tstoppoint = ( repredicting ) ? 0 : 1;\n\tcl.local.repredicting = repredicting;\n\tcl.local.onground = -1;\n\n\t// predict forward until cl.time <= to->senttime\n\tCL_PushPMStates();\n\tCL_SetSolidPlayers( cl.playernum );\n\n\tfor( i = 1; i < CL_UPDATE_MASK && cls.netchan.incoming_acknowledged + i < cls.netchan.outgoing_sequence + stoppoint; i++ )\n\t{\n\t\tuint\t\tcurrent_command;\n\t\tuint\t\tcurrent_command_mod;\n\t\tqboolean\t\trunfuncs;\n\n\t\tcurrent_command = cls.netchan.incoming_acknowledged + i;\n\t\tcurrent_command_mod = current_command & CL_UPDATE_MASK;\n\n\t\tto = &cl.predicted_frames[(cl.parsecountmod + i) & CL_UPDATE_MASK];\n\t\tto_cmd = &cl.commands[current_command_mod];\n\t\trunfuncs = ( !repredicting && !to_cmd->processedfuncs );\n\n\t\tCL_RunUsercmd( from, to, &to_cmd->cmd, runfuncs, &time, current_command );\n\t\tVectorCopy( to->playerstate.origin, cl.local.predicted_origins[current_command_mod] );\n\t\tto_cmd->processedfuncs = true;\n\n\t\tif( to_cmd->senttime >= host.realtime )\n\t\t\tbreak;\n\n\t\tfrom = to;\n\t\tfrom_cmd = to_cmd;\n\t}\n\n\tCL_PopPMStates();\n\n\tif(( i == CL_UPDATE_MASK ) || ( !to && !repredicting ))\n\t{\n\t\tcl.local.repredicting = false;\n\t\treturn; // net hasn't deliver packets in a long time...\n\t}\n\n\tif( !to )\n\t{\n\t\tto = from;\n\t\tto_cmd = from_cmd;\n\t}\n\n\tif( !CL_IsPredicted( ))\n\t{\n\t\t// keep onground actual\n\t\tif( FBitSet( frame->clientdata.flags, FL_ONGROUND ))\n\t\t\tcl.local.onground = frame->playerstate[cl.playernum].onground;\n\t\telse cl.local.onground = -1;\n\n\t\tif( !repredicting || !cl_lw.value )\n\t\t\tcl.local.viewmodel = to->client.viewmodel;\n\t\tcl.local.repredicting = false;\n\t\tcl.local.moving = false;\n\t\treturn;\n\t}\n\n\t// now interpolate some fraction of the final frame\n\tif( to_cmd->senttime != from_cmd->senttime )\n\t\tf = bound( 0.0, (host.realtime - from_cmd->senttime) / (to_cmd->senttime - from_cmd->senttime) * 0.1, 1.0 );\n\telse f = 0.0;\n\n\tif( CL_PlayerTeleported( from, to ))\n\t{\n\t\tVectorCopy( to->client.velocity, cl.simvel );\n\t\tVectorCopy( to->playerstate.origin, cl.simorg );\n\t\tVectorCopy( to->client.punchangle, cl.punchangle );\n\t\tVectorCopy( to->client.view_ofs, cl.viewheight );\n\t}\n\telse\n\t{\n\t\tVectorLerp( from->playerstate.origin, f, to->playerstate.origin, cl.simorg );\n\t\tVectorLerp( from->client.velocity, f, to->client.velocity, cl.simvel );\n\t\tVectorLerp( from->client.punchangle, f, to->client.punchangle, cl.punchangle );\n\n\t\tif( from->playerstate.usehull == to->playerstate.usehull )\n\t\t\tVectorLerp( from->client.view_ofs, f, to->client.view_ofs, cl.viewheight );\n\t\telse VectorCopy( to->client.view_ofs, cl.viewheight );\n\t}\n\n\tcl.local.waterlevel = to->client.waterlevel;\n\tcl.local.usehull = to->playerstate.usehull;\n\tif( !repredicting || !cl_lw.value )\n\t\tcl.local.viewmodel = to->client.viewmodel;\n\n\tif( FBitSet( to->client.flags, FL_ONGROUND ))\n\t{\n\t\tcl_entity_t\t*ent = CL_GetEntityByIndex( cl.local.lastground );\n\t\tcl.local.onground = cl.local.lastground;\n\t\tcl.local.moving = false;\n\n\t\tif( ent )\n\t\t{\n\t\t\tvec3_t delta;\n\n\t\t\tdelta[0] = ent->curstate.origin[0] - ent->prevstate.origin[0];\n\t\t\tdelta[1] = ent->curstate.origin[1] - ent->prevstate.origin[1];\n\t\t\tdelta[2] = 0.0f;\n\n\t\t\tif( VectorLength( delta ) > 0.0f )\n\t\t\t{\n\t\t\t\tcls.correction_time = 0;\n\t\t\t\tcl.local.moving = true;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tcl.local.onground = -1;\n\t\tcl.local.moving = false;\n\t}\n\n\tif( cls.correction_time > 0 && !cl_nosmooth.value && cl_smoothtime.value )\n\t{\n\t\tvec3_t delta;\n\t\tfloat frac;\n\n\t\t// only decay timer once per frame\n\t\tif( !repredicting )\n\t\t\tcls.correction_time -= host.frametime;\n\n\t\t// Make sure smoothtime is postive\n\t\tif( cl_smoothtime.value <= 0.0f )\n\t\t\tCvar_DirectSet( &cl_smoothtime, \"0.1\" );\n\n\t\t// Clamp from 0 to cl_smoothtime.value\n\t\tcls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime.value );\n\n\t\t// Compute backward interpolation fraction along full correction\n\t\tfrac = 1.0f - cls.correction_time / cl_smoothtime.value;\n\n\t\t// Determine how much error we still have to make up for\n\t\tVectorSubtract( cl.simorg, cl.local.lastorigin, delta );\n\n\t\t// Scale the error by the backlerp fraction\n\t\tVectorScale( delta, frac, delta );\n\n\t\t// Go some fraction of the way\n\t\t// FIXME, Probably can't do this any more\n\t\tVectorAdd( cl.local.lastorigin, delta, cl.simorg );\n\t}\n\n\tVectorCopy( cl.simorg, cl.local.lastorigin );\n\tcl.local.repredicting = false;\n}\n"
  },
  {
    "path": "engine/client/cl_qparse.c",
    "content": "/*\ncl_qparse.c - parse a message received from the Quake demo\nCopyright (C) 2018 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"net_encode.h\"\n#include \"particledef.h\"\n#include \"cl_tent.h\"\n#include \"shake.h\"\n#include \"hltv.h\"\n#include \"input.h\"\n\nenum {\n\tSTAT_HEALTH = 0,\n\tSTAT_FRAGS,\n\tSTAT_WEAPON,\n\tSTAT_AMMO,\n\tSTAT_ARMOR,\n\tSTAT_WEAPONFRAME,\n\tSTAT_SHELLS,\n\tSTAT_NAILS,\n\tSTAT_ROCKETS,\n\tSTAT_CELLS,\n\tSTAT_ACTIVEWEAPON,\n\tSTAT_TOTALSECRETS,\n\tSTAT_TOTALMONSTERS,\n\tSTAT_SECRETS,  // bumped on client side by svc_foundsecret\n\tSTAT_MONSTERS, // bumped by svc_killedmonster\n\tMAX_STATS =\t32,\n};\n\nstatic char\tcmd_buf[8192];\nstatic char\tmsg_buf[8192];\nstatic sizebuf_t\tmsg_demo;\n\n/*\n==================\nCL_DispatchQuakeMessage\n\n==================\n*/\nstatic void CL_DispatchQuakeMessage( const char *name )\n{\n\tCL_DispatchUserMessage( name, msg_demo.iCurBit >> 3, msg_demo.pData );\n\tMSG_Clear( &msg_demo ); // don't forget to clear buffer\n}\n\n/*\n==================\nCL_ParseQuakeStats\n\nredirect to qwrap->client\n==================\n*/\nstatic void CL_ParseQuakeStats( sizebuf_t *msg )\n{\n\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\t// stat num\n\tMSG_WriteLong( &msg_demo, MSG_ReadLong( msg ));\t// stat value\n\tCL_DispatchQuakeMessage( \"Stats\" );\n}\n\n/*\n==================\nCL_EntityTeleported\n\ncheck for instant movement in case\nwe don't want interpolate this\n==================\n*/\nstatic qboolean CL_QuakeEntityTeleported( cl_entity_t *ent, entity_state_t *newstate )\n{\n\tfloat\tlen, maxlen;\n\tvec3_t\tdelta;\n\n\tVectorSubtract( newstate->origin, ent->prevstate.origin, delta );\n\n\t// compute potential max movement in units per frame and compare with entity movement\n\tmaxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));\n\tlen = VectorLength( delta );\n\n\treturn (len > maxlen);\n}\n\n/*\n==================\nCL_ParseQuakeStats\n\nredirect to qwrap->client\n==================\n*/\nstatic int CL_UpdateQuakeStats( sizebuf_t *msg, int statnum, qboolean has_update )\n{\n\tint \tvalue = 0;\n\n\tMSG_WriteByte( &msg_demo, statnum );\t// stat num\n\n\tif( has_update )\n\t{\n\t\tif( statnum == STAT_HEALTH )\n\t\t\tvalue = MSG_ReadShort( msg );\n\t\telse value = MSG_ReadByte( msg );\n\t}\n\n\tMSG_WriteLong( &msg_demo, value );\n\tCL_DispatchQuakeMessage( \"Stats\" );\n\n\treturn value;\n}\n\n/*\n==================\nCL_UpdateQuakeGameMode\n\nredirect to qwrap->client\n==================\n*/\nstatic void CL_UpdateQuakeGameMode( int gamemode )\n{\n\tMSG_WriteByte( &msg_demo, gamemode );\n\tCL_DispatchQuakeMessage( \"GameMode\" );\n}\n\n/*\n==================\nCL_ParseQuakeSound\n\n==================\n*/\nstatic void CL_ParseQuakeSound( sizebuf_t *msg )\n{\n\tint \tchannel, sound;\n\tint\tflags, entnum;\n\tfloat \tvolume, attn;\n\tsound_t\thandle;\n\tvec3_t\tpos;\n\n\tflags = MSG_ReadByte( msg );\n\n\tif( FBitSet( flags, SND_VOLUME ))\n\t\tvolume = (float)MSG_ReadByte( msg ) / 255.0f;\n\telse volume = VOL_NORM;\n\n\tif( FBitSet( flags, SND_ATTENUATION ))\n\t\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\telse attn = ATTN_NONE;\n\n\tchannel = MSG_ReadWord( msg );\n\tsound = MSG_ReadByte( msg );\t// Quake1 have max 255 precached sounds. erm\n\n\t// positioned in space\n\tMSG_ReadVec3Coord( msg, pos );\n\n\tentnum = channel >> 3;\t// entity reletive\n\tchannel &= 7;\n\n\t// see precached sound\n\thandle = cl.sound_index[sound];\n\n\tif( !cl.audio_prepped )\n\t\treturn; // too early\n\n\tS_StartSound( pos, entnum, channel, handle, volume, attn, PITCH_NORM, flags );\n}\n\n/*\n==================\nCL_ParseQuakeServerInfo\n\n==================\n*/\nstatic void CL_ParseQuakeServerInfo( sizebuf_t *msg )\n{\n\tresource_t\t*pResource;\n\tconst char\t*pResName;\n\tint\t\tgametype;\n\tint\t\ti;\n\n\tCon_Reportf( \"Serverdata packet received.\\n\" );\n\tcls.timestart = Sys_DoubleTime();\n\n\tcls.demowaiting = false;\t// server is changed\n\n\t// wipe the client_t struct\n\tif( !cls.changelevel && !cls.changedemo )\n\t\tCL_ClearState ();\n\tcl.background = (cls.demonum != -1) ? true : false;\n\tcls.state = ca_connected;\n\n\t// parse protocol version number\n\ti = MSG_ReadLong( msg );\n\n\tif( i != PROTOCOL_VERSION_QUAKE )\n\t{\n\t\tCon_Printf( \"\\n\" S_ERROR \"Server use invalid protocol (%i should be %i)\\n\", i, PROTOCOL_VERSION_QUAKE );\n\t\tCL_StopPlayback();\n\t\tHost_AbortCurrentFrame();\n\t}\n\n\tcl.maxclients = MSG_ReadByte( msg );\n\tgametype = MSG_ReadByte( msg );\n\tclgame.maxEntities = GI->max_edicts;\n\tclgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS );\n\tclgame.maxModels = MAX_MODELS;\n\tQ_strncpy( clgame.maptitle, MSG_ReadString( msg ), sizeof( clgame.maptitle ));\n\n\t// Re-init hud video, especially if we changed game directories\n\tclgame.dllFuncs.pfnVidInit();\n\n\tif( Con_FixedFont( ))\n\t{\n\t\t// seperate the printfs so the server message can have a color\n\t\tCon_Print( \"\\n\\35\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\36\\37\\n\" );\n\t\tCon_Print( va( \"%c%s\\n\\n\", 2, clgame.maptitle ));\n\t}\n\n\t// multiplayer game?\n\tif( cl.maxclients > 1 )\n\t{\n\t\t// allow console in multiplayer games\n\t\thost.allow_console = true;\n\n\t\t// loading user settings\n\t\tCSCR_LoadDefaultCVars( \"user.scr\" );\n\n\t\tif( r_decals.value > mp_decals.value )\n\t\t\tCvar_DirectSet( &r_decals, mp_decals.string );\n\t}\n\telse Cvar_DirectSet( &r_decals, NULL );\n\n\tif( cl.background )\t// tell the game parts about background state\n\t\tCvar_FullSet( \"cl_background\", \"1\", FCVAR_READ_ONLY );\n\telse Cvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\n\tS_StopBackgroundTrack ();\n\n\tif( !cls.changedemo )\n\t\tUI_SetActiveMenu( cl.background );\n\telse if( !cls.demoplayback )\n\t\tKey_SetKeyDest( key_menu );\n\n\t// don't reset cursor in background mode\n\tif( cl.background )\n\t\tIN_MouseRestorePos();\n\n\t// will be changed later\n\tcl.viewentity = cl.playernum + 1;\n\tgameui.globals->maxClients = cl.maxclients;\n\tQ_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle ));\n\n\tif( !cls.changelevel && !cls.changedemo )\n\t\tCL_InitEdicts( cl.maxclients ); // re-arrange edicts\n\n\t// Quake just have a large packet of initialization data\n\tfor( i = 1; i < MAX_MODELS; i++ )\n\t{\n\t\tpResName = MSG_ReadString( msg );\n\n\t\tif( !COM_CheckString( pResName ))\n\t\t\tbreak; // end of list\n\n\t\tpResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\t\tpResource->type = t_model;\n\n\t\tQ_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));\n\t\tif( i == 1 ) Q_strncpy( clgame.mapname, pResName, sizeof( clgame.mapname ));\n\t\tpResource->nDownloadSize = -1;\n\t\tpResource->nIndex = i;\n\n\t\tCL_AddToResourceList( pResource, &cl.resourcesneeded );\n\t}\n\n\tfor( i = 1; i < MAX_SOUNDS; i++ )\n\t{\n\t\tpResName = MSG_ReadString( msg );\n\n\t\tif( !COM_CheckString( pResName ))\n\t\t\tbreak; // end of list\n\n\t\tpResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\t\tpResource->type = t_sound;\n\n\t\tQ_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));\n\t\tpResource->nDownloadSize = -1;\n\t\tpResource->nIndex = i;\n\n\t\tCL_AddToResourceList( pResource, &cl.resourcesneeded );\n\t}\n\n\t// get splash name\n\tif( cls.demoplayback && ( cls.demonum != -1 ))\n\t\tCvar_Set( \"cl_levelshot_name\", va( \"levelshots/%s_%s\", cls.demoname, refState.wideScreen ? \"16x9\" : \"4x3\" ));\n\telse Cvar_Set( \"cl_levelshot_name\", va( \"levelshots/%s_%s\", clgame.mapname, refState.wideScreen ? \"16x9\" : \"4x3\" ));\n\tCvar_SetValue( \"scr_loading\", 0.0f ); // reset progress bar\n\n\tif(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background )\n\t{\n\t\tif( !FS_FileExists( va( \"%s.bmp\", cl_levelshot_name.string ), true ))\n\t\t\tCvar_Set( \"cl_levelshot_name\", \"*black\" ); // render a black screen\n\t\tcls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime)\n\t}\n\n\tmemset( &clgame.movevars, 0, sizeof( clgame.movevars ));\n\tmemset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars ));\n\tmemset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint ));\n\tcl.video_prepped = false;\n\tcl.audio_prepped = false;\n\n\t// GAME_COOP or GAME_DEATHMATCH\n\tCL_UpdateQuakeGameMode( gametype );\n\n\t// now we can start to precache\n\tCL_BatchResourceRequest( true );\n\n\tclgame.movevars.wateralpha = 1.0f;\n\tclgame.entities->curstate.scale = 0.0f;\n\tclgame.movevars.waveHeight = 0.0f;\n\tclgame.movevars.zmax = 14172.0f;\t// 8192 * 1.74\n\tclgame.movevars.gravity = 800.0f;\t// quake doesn't write gravity in demos\n\tclgame.movevars.maxvelocity = 2000.0f;\n\n\tclgame.oldmovevars = clgame.movevars;\n}\n\n/*\n==================\nCL_ParseQuakeClientData\n\n==================\n*/\nstatic void CL_ParseQuakeClientData( sizebuf_t *msg )\n{\n\tint\ti, bits = MSG_ReadWord( msg );\n\tframe_t\t*frame;\n\n\t// this is the frame update that this message corresponds to\n\ti = cls.netchan.incoming_sequence;\n\n\tcl.parsecount = i;\t\t\t\t\t// ack'd incoming messages.\n\tcl.parsecountmod = cl.parsecount & CL_UPDATE_MASK;\t// index into window.\n\tframe = &cl.frames[cl.parsecountmod];\t\t\t// frame at index.\n\tframe->time = cl.mtime[0];\t\t\t\t// mark network received time\n\tframe->receivedtime = host.realtime;\t\t\t// time now that we are parsing.\n\tmemset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t ));\n\tmemset( frame->flags, 0, sizeof( frame->flags ));\n\tframe->first_entity = cls.next_client_entities;\n\tframe->num_entities = 0;\n\tframe->valid = true; // assume valid\n\n\tif( FBitSet( bits, SU_VIEWHEIGHT ))\n\t\tframe->clientdata.view_ofs[2] = MSG_ReadChar( msg );\n\telse frame->clientdata.view_ofs[2] = 22.0f;\n\n\tif( FBitSet( bits, SU_IDEALPITCH ))\n\t\tcl.local.idealpitch = MSG_ReadChar( msg );\n\telse cl.local.idealpitch = 0;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( FBitSet( bits, SU_PUNCH1 << i ))\n\t\t\tframe->clientdata.punchangle[i] = (float)MSG_ReadChar( msg );\n\t\telse frame->clientdata.punchangle[i] = 0.0f;\n\n\t\tif( FBitSet( bits, ( SU_VELOCITY1 << i )))\n\t\t\tframe->clientdata.velocity[i] = MSG_ReadChar( msg ) * 16.0f;\n\t\telse frame->clientdata.velocity[i] = 0;\n\t}\n\n\tif( FBitSet( bits, SU_ONGROUND ))\n\t\tSetBits( frame->clientdata.flags, FL_ONGROUND );\n\tif( FBitSet( bits, SU_INWATER ))\n\t\tSetBits( frame->clientdata.flags, FL_INWATER );\n\n\t// [always sent]\n\tMSG_WriteLong( &msg_demo, MSG_ReadLong( msg ));\n\tCL_DispatchQuakeMessage( \"Items\" );\n\n\tif( FBitSet( bits, SU_WEAPONFRAME ))\n\t\tCL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, true );\n\telse CL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, false );\n\n\tif( FBitSet( bits, SU_ARMOR ))\n\t\tCL_UpdateQuakeStats( msg, STAT_ARMOR, true );\n\telse CL_UpdateQuakeStats( msg, STAT_ARMOR, false );\n\n\tif( FBitSet( bits, SU_WEAPON ))\n\t\tframe->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, true );\n\telse frame->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, false );\n\n\tcl.local.health = CL_UpdateQuakeStats( msg, STAT_HEALTH, true );\n\tCL_UpdateQuakeStats( msg, STAT_AMMO, true );\n\tCL_UpdateQuakeStats( msg, STAT_SHELLS, true );\n\tCL_UpdateQuakeStats( msg, STAT_NAILS, true );\n\tCL_UpdateQuakeStats( msg, STAT_ROCKETS, true );\n\tCL_UpdateQuakeStats( msg, STAT_CELLS, true );\n\tCL_UpdateQuakeStats( msg, STAT_ACTIVEWEAPON, true );\n}\n\n/*\n==================\nCL_ParseQuakeEntityData\n\nParse an entity update message from the server\nIf an entities model or origin changes from frame to frame, it must be\nrelinked.  Other attributes can change without relinking.\n==================\n*/\nstatic void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits )\n{\n\tint\t\ti, newnum, pack;\n\tqboolean\t\tforcelink;\n\tentity_state_t\t*state;\n\tframe_t\t\t*frame;\n\tcl_entity_t\t*ent;\n\n\t// first update is the final signon stage where we actually receive an entity (i.e., the world at least)\n\tif( cls.signon == ( SIGNONS - 1 ))\n\t{\n\t\t// we are done with signon sequence.\n\t\tcls.signon = SIGNONS;\n\n\t\t// Clear loading plaque.\n\t\tCL_SignonReply( PROTO_QUAKE );\n\t}\n\n\t// alloc next slot to store update\n\tstate = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];\n\tcl.validsequence = cls.netchan.incoming_sequence;\n\tframe = &cl.frames[cl.parsecountmod];\n\tpack = frame->num_entities;\n\n\tif( FBitSet( bits, U_MOREBITS ))\n\t{\n\t\ti = MSG_ReadByte( msg );\n\t\tSetBits( bits, i << 8 );\n\t}\n\n\tif( FBitSet( bits, U_LONGENTITY ))\n\t\tnewnum = MSG_ReadWord( msg );\n\telse newnum = MSG_ReadByte( msg );\n\n\tmemset( state, 0, sizeof( *state ));\n\tSetBits( state->entityType, ENTITY_NORMAL );\n\tstate->number = newnum;\n\n\t// mark all the players\n\tent = CL_EDICT_NUM( newnum );\n\tent->index = newnum; // enumerate entity index\n\tent->player = CL_IsPlayerIndex( newnum );\n\tstate->animtime = cl.mtime[0];\n\n\tif( ent->curstate.msg_time != cl.mtime[1] )\n\t\tforcelink = true;\t// no previous frame to lerp from\n\telse forcelink = false;\n\n\tif( FBitSet( bits, U_MODEL ))\n\t\tstate->modelindex = MSG_ReadByte( msg );\n\telse state->modelindex = ent->baseline.modelindex;\n\n\tif( FBitSet( bits, U_FRAME ))\n\t\tstate->frame = MSG_ReadByte( msg );\n\telse state->frame = ent->baseline.frame;\n\n\tif( FBitSet( bits, U_COLORMAP ))\n\t\tstate->colormap = MSG_ReadByte( msg );\n\telse state->colormap = ent->baseline.colormap;\n\n\tif( FBitSet( bits, U_SKIN ))\n\t\tstate->skin = MSG_ReadByte( msg );\n\telse state->skin = ent->baseline.skin;\n\n\tif( FBitSet( bits, U_EFFECTS ))\n\t\tstate->effects = MSG_ReadByte( msg );\n\telse state->effects = ent->baseline.effects;\n\n\tif( FBitSet( bits, U_ORIGIN1 ))\n\t\tstate->origin[0] = MSG_ReadCoord( msg );\n\telse state->origin[0] = ent->baseline.origin[0];\n\n\tif( FBitSet( bits, U_ANGLE1 ))\n\t\tstate->angles[0] = MSG_ReadAngle( msg );\n\telse state->angles[0] = ent->baseline.angles[0];\n\n\tif( FBitSet( bits, U_ORIGIN2 ))\n\t\tstate->origin[1] = MSG_ReadCoord( msg );\n\telse state->origin[1] = ent->baseline.origin[1];\n\n\tif( FBitSet( bits, U_ANGLE2 ))\n\t\tstate->angles[1] = MSG_ReadAngle( msg );\n\telse state->angles[1] = ent->baseline.angles[1];\n\n\tif( FBitSet( bits, U_ORIGIN3 ))\n\t\tstate->origin[2] = MSG_ReadCoord( msg );\n\telse state->origin[2] = ent->baseline.origin[2];\n\n\tif( FBitSet( bits, U_ANGLE3 ))\n\t\tstate->angles[2] = MSG_ReadAngle( msg );\n\telse state->angles[2] = ent->baseline.angles[2];\n\n\tif( FBitSet( bits, U_TRANS ))\n\t{\n\t\tint\ttemp = MSG_ReadFloat( msg );\n\t\tfloat\talpha = MSG_ReadFloat( msg );\n\n\t\tif( alpha == 0.0f ) alpha = 1.0f;\n\n\t\tif( alpha < 1.0f )\n\t\t{\n\t\t\tstate->rendermode = kRenderTransTexture;\n\t\t\tstate->renderamt = (int)(alpha * 255.0f);\n\t\t}\n\n\t\tif( temp == 2 && MSG_ReadFloat( msg ))\n\t\t\tSetBits( state->effects, EF_FULLBRIGHT );\n\t}\n\n\tif( FBitSet( bits, U_NOLERP ))\n\t\tstate->movetype = MOVETYPE_STEP;\n\telse state->movetype = MOVETYPE_NOCLIP;\n\n\tif( CL_QuakeEntityTeleported( ent, state ))\n\t{\n\t\t// remove smooth stepping\n\t\tif( cl.viewentity == ent->index )\n\t\t\tcl.skip_interp = true;\n\t\tforcelink = true;\n\t}\n\n\tif( FBitSet( state->effects, 16 ))\n\t\tSetBits( state->effects, EF_NODRAW );\n\n\tif(( newnum - 1 ) == cl.playernum )\n\t\tVectorCopy( state->origin, frame->clientdata.origin );\n\n\tif( forcelink )\n\t{\n\t\tVectorCopy( state->origin, ent->baseline.vuser1 );\n\n\t\tSetBits( state->effects, EF_NOINTERP );\n\n\t\t// interpolation must be reset\n\t\tSETVISBIT( frame->flags, pack );\n\n\t\t// release beams from previous entity\n\t\tCL_KillDeadBeams( ent );\n\t}\n\n\t// add entity to packet\n\tcls.next_client_entities++;\n\tframe->num_entities++;\n}\n\n/*\n==================\nCL_ParseQuakeParticles\n\n==================\n*/\nstatic void CL_ParseQuakeParticle( sizebuf_t *msg )\n{\n\tint\tcount, color;\n\tvec3_t\torg, dir;\n\n\tMSG_ReadVec3Coord( msg, org );\n\tdir[0] = MSG_ReadChar( msg ) * 0.0625f;\n\tdir[1] = MSG_ReadChar( msg ) * 0.0625f;\n\tdir[2] = MSG_ReadChar( msg ) * 0.0625f;\n\tcount = MSG_ReadByte( msg );\n\tcolor = MSG_ReadByte( msg );\n\tif( count == 255 ) count = 1024;\n\n\tR_RunParticleEffect( org, dir, color, count );\n}\n\n/*\n===================\nCL_ParseQuakeStaticSound\n\n===================\n*/\nstatic void CL_ParseQuakeStaticSound( sizebuf_t *msg )\n{\n\tint\tsound_num;\n\tfloat \tvol, attn;\n\tvec3_t\torg;\n\n\tMSG_ReadVec3Coord( msg, org );\n\tsound_num = MSG_ReadByte( msg );\n\tvol = (float)MSG_ReadByte( msg ) / 255.0f;\n\tattn = (float)MSG_ReadByte( msg ) / 64.0f;\n\n\tS_StartSound( org, 0, CHAN_STATIC, cl.sound_index[sound_num], vol, attn, PITCH_NORM, 0 );\n}\n\n/*\n==================\nCL_ParseQuakeDamage\n\nredirect to qwrap->client\n==================\n*/\nstatic void CL_ParseQuakeDamage( sizebuf_t *msg )\n{\n\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\t// armor\n\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\t// blood\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\t// direction\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\t// direction\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\t// direction\n\tCL_DispatchQuakeMessage( \"Damage\" );\n}\n\n/*\n===================\nCL_ParseStaticEntity\n\n===================\n*/\nstatic void CL_ParseQuakeStaticEntity( sizebuf_t *msg )\n{\n\tentity_state_t state = { 0 };\n\tcl_entity_t\t*ent;\n\tint\t\ti;\n\n\tif( !clgame.static_entities )\n\t\tclgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );\n\n\tstate.modelindex = MSG_ReadByte( msg );\n\tstate.frame = MSG_ReadByte( msg );\n\tstate.colormap = MSG_ReadByte( msg );\n\tstate.skin = MSG_ReadByte( msg );\n\tstate.origin[0] = MSG_ReadCoord( msg );\n\tstate.angles[0] = MSG_ReadAngle( msg );\n\tstate.origin[1] = MSG_ReadCoord( msg );\n\tstate.angles[1] = MSG_ReadAngle( msg );\n\tstate.origin[2] = MSG_ReadCoord( msg );\n\tstate.angles[2] = MSG_ReadAngle( msg );\n\n\ti = clgame.numStatics;\n\tif( i >= MAX_STATIC_ENTITIES )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: static entities limit exceeded!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tent = &clgame.static_entities[i];\n\tclgame.numStatics++;\n\n\tent->index = 0; // ???\n\tent->baseline = state;\n\tent->curstate = state;\n\tent->prevstate = state;\n\n\t// statics may be respawned in game e.g. for demo recording\n\tif( cls.state == ca_connected || cls.state == ca_validate )\n\t\tent->trivial_accept = INVALID_HANDLE;\n\n\t// setup the new static entity\n\tVectorCopy( ent->curstate.origin, ent->origin );\n\tVectorCopy( ent->curstate.angles, ent->angles );\n\tent->model = CL_ModelHandle( state.modelindex );\n\tent->curstate.framerate = 1.0f;\n\tCL_ResetLatchedVars( ent, true );\n\n\tif( ent->model != NULL )\n\t{\n\t\t// auto 'solid' faces\n\t\tif( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible())\n\t\t{\n\t\t\tent->curstate.rendermode = kRenderTransAlpha;\n\t\t\tent->curstate.renderamt = 255;\n\t\t}\n\t}\n\n\tR_AddEfrags( ent );\t// add link\n}\n\n/*\n===================\nCL_ParseQuakeBaseline\n\n===================\n*/\nstatic void CL_ParseQuakeBaseline( sizebuf_t *msg )\n{\n\tentity_state_t\tstate;\n\tcl_entity_t\t*ent;\n\tint\t\tnewnum;\n\n\tnewnum = MSG_ReadWord( msg ); // entnum\n\n\tif( newnum >= clgame.maxEntities )\n\t\tHost_Error( \"%s: no free edicts\\n\", __func__ );\n\n\t// parse baseline\n\tmemset( &state, 0, sizeof( state ));\n\tstate.modelindex = MSG_ReadByte( msg );\n\tstate.frame = MSG_ReadByte( msg );\n\tstate.colormap = MSG_ReadByte( msg );\n\tstate.skin = MSG_ReadByte( msg );\n\tstate.origin[0] = MSG_ReadCoord( msg );\n\tstate.angles[0] = MSG_ReadAngle( msg );\n\tstate.origin[1] = MSG_ReadCoord( msg );\n\tstate.angles[1] = MSG_ReadAngle( msg );\n\tstate.origin[2] = MSG_ReadCoord( msg );\n\tstate.angles[2] = MSG_ReadAngle( msg );\n\n\tent = CL_EDICT_NUM( newnum );\n\tent->index = newnum;\n\tent->player = CL_IsPlayerIndex( newnum );\n\tent->prevstate = ent->baseline = state;\n}\n\n/*\n===================\nCL_ParseQuakeTempEntity\n\n===================\n*/\nstatic void CL_ParseQuakeTempEntity( sizebuf_t *msg )\n{\n\tint\ttype = MSG_ReadByte( msg );\n\n\tMSG_WriteByte( &msg_demo, type );\n\n\tif( type == 17 )\n\t\tMSG_WriteString( &msg_demo, MSG_ReadString( msg ));\n\n\t// TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_LIGHTNING4\n\tif( type == 5 || type == 6 || type == 9 || type == 13 || type == 17 )\n\t\tMSG_WriteWord( &msg_demo, MSG_ReadWord( msg ));\n\n\t// all temp ents have position at beginning\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\n\t// TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_EXPLOSION3, TE_LIGHTNING4\n\tif( type == 5 || type == 6 || type == 9 || type == 13 || type == 16 || type == 17 )\n\t{\n\t\t// write endpos for beams\n\t\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\t\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\t\tMSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));\n\t}\n\n\t// TE_EXPLOSION2\n\tif( type == 12 )\n\t{\n\t\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\n\t\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\n\t}\n\n\t// TE_SMOKE (nehahra)\n\tif( type == 18 )\n\t\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\n\n\tCL_DispatchQuakeMessage( \"TempEntity\" );\n}\n\n/*\n===================\nCL_ParseQuakeSignon\n\nvery important message\n===================\n*/\nstatic void CL_ParseQuakeSignon( sizebuf_t *msg )\n{\n\tint\ti = MSG_ReadByte( msg );\n\n\tif( i == 3 ) cls.signon = SIGNONS - 1;\n\tCon_Reportf( \"%s: %d\\n\", __func__, i );\n}\n\n/*\n==================\nCL_ParseNehahraShowLMP\n\nredirect to qwrap->client\n==================\n*/\nstatic void CL_ParseNehahraShowLMP( sizebuf_t *msg )\n{\n\tMSG_WriteString( &msg_demo, MSG_ReadString( msg ));\n\tMSG_WriteString( &msg_demo, MSG_ReadString( msg ));\n\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\n\tMSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));\n\tCL_DispatchQuakeMessage( \"Stats\" );\n}\n\n/*\n==================\nCL_ParseNehahraHideLMP\n\nredirect to qwrap->client\n==================\n*/\nstatic void CL_ParseNehahraHideLMP( sizebuf_t *msg )\n{\n\tMSG_WriteString( &msg_demo, MSG_ReadString( msg ));\n\tCL_DispatchQuakeMessage( \"Stats\" );\n}\n\n/*\n==================\nCL_QuakeStuffText\n\n==================\n*/\nstatic void CL_QuakeStuffText( const char *text )\n{\n\tQ_strncat( cmd_buf, text, sizeof( cmd_buf ));\n\n\t// a1ba: didn't filtered, anyway quake protocol\n\t// only supported for demos, not network games\n\tCbuf_AddText( text );\n}\n\n/*\n==================\nCL_QuakeExecStuff\n\n==================\n*/\nstatic void CL_QuakeExecStuff( void )\n{\n\tchar\t*text = cmd_buf;\n\tchar\ttoken[256];\n\tint\targc = 0;\n\n\t// check if no commands this frame\n\tif( !COM_CheckString( text ))\n\t\treturn;\n\n\twhile( 1 )\n\t{\n\t\t// skip whitespace up to a /n\n\t\twhile( *text && ((byte)*text ) <= ' ' && *text != '\\r' && *text != '\\n' )\n\t\t\ttext++;\n\n\t\tif( *text == '\\n' || *text == '\\r' )\n\t\t{\n\t\t\t// a newline seperates commands in the buffer\n\t\t\tif( *text == '\\r' && text[1] == '\\n' )\n\t\t\t\ttext++;\n\t\t\targc = 0;\n\t\t\ttext++;\n\t\t}\n\n\t\tif( !*text ) break;\n\n\t\ttext = COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL, NULL );\n\n\t\tif( !text ) break;\n\n\t\tif( argc == 0 )\n\t\t{\n\t\t\t// debug: find all missed commands and cvars to add them into QWrap\n\t\t\tif( !Cvar_Exists( token ) && !Cmd_Exists( token ))\n\t\t\t\tCon_Printf( S_WARN \"'%s' is not exist\\n\", token );\n//\t\t\telse Msg( \"cmd: %s\\n\", token );\n\n\t\t\t// process some special commands\n\t\t\tif( !Q_stricmp( token, \"playdemo\" ))\n\t\t\t\tcls.changedemo = true;\n\t\t\targc++;\n\t\t}\n\t}\n\n\t// reset the buffer\n\tcmd_buf[0] = '\\0';\n}\n\n/*\n==================\nCL_ParseQuakeMessage\n\n==================\n*/\nvoid CL_ParseQuakeMessage( sizebuf_t *msg )\n{\n\tint\t\tcmd, param1, param2;\n\tsize_t\t\tbufStart;\n\tconst char\t*str;\n\n\t// init excise buffer\n\tMSG_Init( &msg_demo, \"UserMsg\", msg_buf, sizeof( msg_buf ));\n\n\t// parse the message\n\twhile( 1 )\n\t{\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t{\n\t\t\tHost_Error( \"%s: overflow!\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\n\t\t// mark start position\n\t\tbufStart = MSG_GetNumBytesRead( msg );\n\n\t\t// end of message (align bits)\n\t\tif( MSG_GetNumBitsLeft( msg ) < 8 )\n\t\t\tbreak;\n\n\t\tcmd = MSG_ReadServerCmd( msg );\n\n\t\t// if the high bit of the command byte is set, it is a fast update\n\t\tif( FBitSet( cmd, 128 ))\n\t\t{\n\t\t\tCL_ParseQuakeEntityData( msg, cmd & 127 );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// record command for debugging spew on parse problem\n\t\tCL_Parse_RecordCommand( cmd, bufStart );\n\n\t\t// other commands\n\t\tswitch( cmd )\n\t\t{\n\t\tcase svc_nop:\n\t\t\t// this does nothing\n\t\t\tbreak;\n\t\tcase svc_disconnect:\n\t\t\tCL_DemoCompleted ();\n\t\t\tbreak;\n\t\tcase svc_updatestat:\n\t\t\tCL_ParseQuakeStats( msg );\n\t\t\tbreak;\n\t\tcase svc_version:\n\t\t\tparam1 = MSG_ReadLong( msg );\n\t\t\tif( param1 != PROTOCOL_VERSION_QUAKE )\n\t\t\t\tHost_Error( \"Server is protocol %i instead of %i\\n\", param1, PROTOCOL_VERSION_QUAKE );\n\t\t\tbreak;\n\t\tcase svc_setview:\n\t\t\tCL_ParseViewEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_sound:\n\t\t\tCL_ParseQuakeSound( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_time:\n\t\t\tCbuf_AddText( \"\\n\" ); // new frame was started\n\t\t\tCL_ParseServerTime( msg, PROTO_QUAKE );\n\t\t\tbreak;\n\t\tcase svc_print:\n\t\t\tstr = MSG_ReadString( msg );\n\t\t\tCon_Printf( \"%s%s\", str, *str == 2 ? \"\\n\" : \"\" );\n\t\t\tbreak;\n\t\tcase svc_stufftext:\n\t\t\tCL_QuakeStuffText( MSG_ReadString( msg ));\n\t\t\tbreak;\n\t\tcase svc_setangle:\n\t\t\tcl.viewangles[0] = MSG_ReadAngle( msg );\n\t\t\tcl.viewangles[1] = MSG_ReadAngle( msg );\n\t\t\tcl.viewangles[2] = MSG_ReadAngle( msg );\n\t\t\tbreak;\n\t\tcase svc_serverdata:\n\t\t\tCbuf_Execute(); // make sure any stuffed commands are done\n\t\t\tCL_ParseQuakeServerInfo( msg );\n\t\t\tbreak;\n\t\tcase svc_lightstyle:\n\t\t\tCL_ParseLightStyle( msg, PROTO_QUAKE );\n\t\t\tbreak;\n\t\tcase svc_updatename:\n\t\t\tparam1 = MSG_ReadByte( msg );\n\t\t\tQ_strncpy( cl.players[param1].name, MSG_ReadString( msg ), sizeof( cl.players[0].name ));\n\t\t\tQ_strncpy( cl.players[param1].model, \"player\", sizeof( cl.players[0].name ));\n\t\t\tbreak;\n\t\tcase svc_updatefrags:\n\t\t\tparam1 = MSG_ReadByte( msg );\n\t\t\tparam2 = MSG_ReadShort( msg );\n\t\t\t// HACKHACK: store frags into spectator\n\t\t\tcl.players[param1].spectator = param2;\n\t\t\tbreak;\n\t\tcase svc_clientdata:\n\t\t\tCL_ParseQuakeClientData( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_stopsound:\n\t\t\tparam1 = MSG_ReadWord( msg );\n\t\t\tS_StopSound( param1 >> 3, param1 & 7, NULL );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_updatecolors:\n\t\t\tparam1 = MSG_ReadByte( msg );\n\t\t\tparam2 = MSG_ReadByte( msg );\n\t\t\tcl.players[param1].topcolor = param2 & 0xF;\n\t\t\tcl.players[param1].bottomcolor = (param2 & 0xF0) >> 4;\n\t\t\tbreak;\n\t\tcase svc_particle:\n\t\t\tCL_ParseQuakeParticle( msg );\n\t\t\tbreak;\n\t\tcase svc_damage:\n\t\t\tCL_ParseQuakeDamage( msg );\n\t\t\tbreak;\n\t\tcase svc_spawnstatic:\n\t\t\tCL_ParseQuakeStaticEntity( msg );\n\t\t\tbreak;\n\t\tcase svc_spawnbinary:\n\t\t\t// never used in Quake\n\t\t\tbreak;\n\t\tcase svc_spawnbaseline:\n\t\t\tCL_ParseQuakeBaseline( msg );\n\t\t\tbreak;\n\t\tcase svc_temp_entity:\n\t\t\tCL_ParseQuakeTempEntity( msg );\n\t\t\tcl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart;\n\t\t\tbreak;\n\t\tcase svc_setpause:\n\t\t\tcl.paused = MSG_ReadByte( msg );\n\t\t\tbreak;\n\t\tcase svc_signonnum:\n\t\t\tCL_ParseQuakeSignon( msg );\n\t\t\tbreak;\n\t\tcase svc_centerprint:\n\t\t\tstr = MSG_ReadString( msg );\n\t\t\tCL_DispatchUserMessage( \"HudText\", Q_strlen( str ), (void *)str );\n\t\t\tbreak;\n\t\tcase svc_killedmonster:\n\t\t\tCL_DispatchQuakeMessage( \"KillMonster\" ); // just an event\n\t\t\tbreak;\n\t\tcase svc_foundsecret:\n\t\t\tCL_DispatchQuakeMessage( \"FoundSecret\" ); // just an event\n\t\t\tbreak;\n\t\tcase svc_spawnstaticsound:\n\t\t\tCL_ParseQuakeStaticSound( msg );\n\t\t\tbreak;\n\t\tcase svc_intermission:\n\t\t\tcl.intermission = 1;\n\t\t\tbreak;\n\t\tcase svc_finale:\n\t\t\tCL_ParseFinaleCutscene( msg, 2 );\n\t\t\tbreak;\n\t\tcase svc_cdtrack:\n\t\t\tparam1 = MSG_ReadByte( msg );\n\t\t\tparam1 = bound( 0, param1, MAX_CDTRACKS - 1 ); // tracknum\n\t\t\tparam2 = MSG_ReadByte( msg );\n\t\t\tparam2 = bound( 0, param2, MAX_CDTRACKS - 1 ); // loopnum\n\t\t\tif(( cls.demoplayback || cls.demorecording ) && ( cls.forcetrack != -1 ))\n\t\t\t\tS_StartBackgroundTrack( clgame.cdtracks[cls.forcetrack], clgame.cdtracks[cls.forcetrack], 0, false );\n\t\t\telse S_StartBackgroundTrack( clgame.cdtracks[param1], clgame.cdtracks[param2], 0, false );\n\t\t\tbreak;\n\t\tcase svc_sellscreen:\n\t\t\tCmd_ExecuteString( \"help\" );\t// open quake menu\n\t\t\tbreak;\n\t\tcase svc_cutscene:\n\t\t\tCL_ParseFinaleCutscene( msg, 3 );\n\t\t\tbreak;\n\t\tcase svc_hidelmp:\n\t\t\tCL_ParseNehahraHideLMP( msg );\n\t\t\tbreak;\n\t\tcase svc_showlmp:\n\t\t\tCL_ParseNehahraShowLMP( msg );\n\t\t\tbreak;\n\t\tcase svc_skybox:\n\t\t\tQ_strncpy( clgame.movevars.skyName, MSG_ReadString( msg ), sizeof( clgame.movevars.skyName ));\n\t\t\tbreak;\n\t\tcase svc_skyboxsize:\n\t\t\tMSG_ReadCoord( msg ); // obsolete\n\t\t\tbreak;\n\t\tcase svc_fog:\n\t\t\tif( MSG_ReadByte( msg ))\n\t\t\t{\n\t\t\t\tfloat\tfog_settings[4];\n\t\t\t\tint\tpacked_fog[4];\n\n\t\t\t\tfog_settings[3] = MSG_ReadFloat( msg );\t// density\n\t\t\t\tfog_settings[0] = MSG_ReadByte( msg );\t// red\n\t\t\t\tfog_settings[1] = MSG_ReadByte( msg );\t// green\n\t\t\t\tfog_settings[2] = MSG_ReadByte( msg );\t// blue\n\t\t\t\tpacked_fog[0] = fog_settings[0] * 255;\n\t\t\t\tpacked_fog[1] = fog_settings[1] * 255;\n\t\t\t\tpacked_fog[2] = fog_settings[2] * 255;\n\t\t\t\tpacked_fog[3] = fog_settings[3] * 255;\n\t\t\t\tclgame.movevars.fog_settings = (packed_fog[1]<<24)|(packed_fog[2]<<16)|(packed_fog[3]<<8)|packed_fog[0];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tclgame.movevars.fog_settings = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tHost_Error( \"%s: Illegible server message\\n\", __func__ );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// now process packet.\n\tCL_ProcessPacket( &cl.frames[cl.parsecountmod] );\n\n\t// add new entities into physic lists\n\tCL_SetSolidEntities();\n\n\t// check deferred cmds\n\tCL_QuakeExecStuff();\n}\n"
  },
  {
    "path": "engine/client/cl_remap.c",
    "content": "/*\ngl_remap.c - remap model textures\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"studio.h\"\n\n/*\n====================\nCL_GetRemapInfoForEntity\n\nReturns remapinfo slot for specified entity\n====================\n*/\nremap_info_t *CL_GetRemapInfoForEntity( cl_entity_t *e )\n{\n\tif( !e ) return NULL;\n\n\tif( e == &clgame.viewent )\n\t\treturn clgame.remap_info[clgame.maxEntities];\n\n\treturn clgame.remap_info[e->curstate.number];\n}\n\n/*\n====================\nCL_CmpStudioTextures\n\nreturn true if equal\n====================\n*/\nstatic qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, remap_info_t *remap )\n{\n\tint\ti;\n\tmstudiotexture_t *p2;\n\n\tif( !p1 ) // no textures\n\t\treturn false;\n\n\tif( !remap ) // current model has no remap\n\t\treturn false;\n\n\tif( !remap->textures ) // shouldn't happen, just in case\n\t\treturn false;\n\n\tif( numtexs != remap->numtextures ) // amount of textures differs, it's a different model\n\t\treturn false;\n\n\tp2 = remap->ptexture;\n\n\tfor( i = 0; i < numtexs; i++, p1++, p2++ )\n\t{\n\t\tif( p1->flags & STUDIO_NF_COLORMAP )\n\t\t\tcontinue;\t// colormaps always has different indexes\n\n\t\tif( p1->index != p2->index )\n\t\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n====================\nCL_CreateRawTextureFromPixels\n\nConvert texture_t struct into mstudiotexture_t prototype\n====================\n*/\nstatic byte *CL_CreateRawTextureFromPixels( texture_t *tx, size_t *size, int topcolor, int bottomcolor )\n{\n\tstatic mstudiotexture_t\tpin;\n\tbyte\t\t\t*pal;\n\n\tAssert( size != NULL );\n\n\t*size = sizeof( pin ) + (tx->width * tx->height) + 768;\n\n\t// fill header\n\tif( !pin.name[0] ) Q_strncpy( pin.name, \"#raw_remap_image.mdl\", sizeof( pin.name ));\n\tpin.flags = STUDIO_NF_COLORMAP; // just in case :-)\n\t//pin.index = (int)(tx + 1); // pointer to pixels\n\t// no more pointer-to-int-to-pointer casts\n\tImage_SetMDLPointer( (byte*)((texture_t *)tx + 1) );\n\tpin.width = tx->width;\n\tpin.height = tx->height;\n\n\t// update palette\n\tpal = (byte *)(tx + 1) + (tx->width * tx->height);\n\tImage_PaletteHueReplace( pal, topcolor, tx->anim_min, tx->anim_max, 3 );\n\tImage_PaletteHueReplace( pal, bottomcolor, tx->anim_max + 1, tx->anim_total, 3 );\n\n\treturn (byte *)&pin;\n}\n\n/*\n====================\nCL_DuplicateTexture\n\nDupliacte texture with remap pixels\n====================\n*/\nstatic void CL_DuplicateTexture( cl_entity_t *entity, model_t *model, mstudiotexture_t *ptexture, int topcolor, int bottomcolor )\n{\n\tconst char *name;\n\ttexture_t\t\t*tx = NULL;\n\tchar\t\ttexname[128];\n\tint\t\ti, index;\n\tsize_t size;\n\tbyte\t\tpaletteBackup[768];\n\tbyte\t\t*raw, *pal;\n\n\t// save off the real texture index\n\tindex = ptexture->index;\n\tname = ref.dllFuncs.GL_TextureName( index );\n\tQ_snprintf( texname, sizeof( texname ), \"#%i_%s\", entity->curstate.number, name + 1 );\n\n\t// search for pixels\n\tfor( i = 0; i < model->numtextures; i++ )\n\t{\n\t\ttx = model->textures[i];\n\t\tif( tx->gl_texturenum == index )\n\t\t\tbreak; // found\n\t}\n\n\tif( !tx )\n\t\treturn;\n\n\t// backup original palette\n\tpal = (byte *)(tx + 1) + (tx->width * tx->height);\n\tmemcpy( paletteBackup, pal, 768 );\n\n\traw = CL_CreateRawTextureFromPixels( tx, &size, topcolor, bottomcolor );\n\tptexture->index = ref.dllFuncs.GL_LoadTexture( texname, raw, size, TF_FORCE_COLOR ); // do copy\n\n\t// restore original palette\n\tmemcpy( pal, paletteBackup, 768 );\n}\n\n/*\n====================\nCL_UpdateStudioTexture\n\nUpdate texture top and bottom colors\n====================\n*/\nstatic void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, int topcolor, int bottomcolor )\n{\n\trgbdata_t\t\t*pic;\n\ttexture_t\t\t*tx = NULL;\n\tchar\t\ttexname[128], name[128], mdlname[128];\n\tconst char\t*origtexname;\n\tint\t\ti, index;\n\tsize_t size;\n\tbyte\t\tpaletteBackup[768];\n\tbyte\t\t*raw, *pal;\n\n\t// save off the real texture index\n\torigtexname = ref.dllFuncs.GL_TextureName( ptexture->index );\n\n\t// build name of original texture\n\tQ_strncpy( mdlname, entity->model->name, sizeof( mdlname ));\n\tCOM_FileBase( ptexture->name, name, sizeof( name ));\n\tCOM_StripExtension( mdlname );\n\n\tQ_snprintf( texname, sizeof( texname ), \"#%s/%s.mdl\", mdlname, name );\n\tindex = ref.dllFuncs.GL_FindTexture( texname );\n\tif( !index )\n\t\treturn; // couldn't find texture\n\n\t// search for pixels\n\tfor( i = 0; i < entity->model->numtextures; i++ )\n\t{\n\t\ttx = entity->model->textures[i];\n\t\tif( tx->gl_texturenum == index )\n\t\t\tbreak; // found\n\t}\n\n\tif( !tx )\n\t\treturn; // couldn't find texture\n\n\t// backup original palette\n\tpal = (byte *)(tx + 1) + (tx->width * tx->height);\n\tmemcpy( paletteBackup, pal, 768 );\n\n\traw = CL_CreateRawTextureFromPixels( tx, &size, topcolor, bottomcolor );\n\tpic = FS_LoadImage( origtexname, raw, size );\n\tif( !pic )\n\t{\n\t\tCon_DPrintf( S_ERROR \"Couldn't update texture %s\\n\", origtexname );\n\t\treturn;\n\t}\n\n\tindex = GL_UpdateTextureInternal( origtexname, pic, 0 );\n\tFS_FreeImage( pic );\n\n\t// restore original palette\n\tmemcpy( pal, paletteBackup, 768 );\n\n\tAssert( index == ptexture->index );\n}\n\n/*\n====================\nCL_UpdateAliasTexture\n\nUpdate texture top and bottom colors\n====================\n*/\nstatic void CL_UpdateAliasTexture( cl_entity_t *entity, unsigned short *texture, int skinnum, int topcolor, int bottomcolor )\n{\n\tchar\ttexname[MAX_QPATH];\n\trgbdata_t\tskin, *pic;\n\ttexture_t\t*tx;\n\n\tif( !texture || !entity->model->textures )\n\t\treturn; // no remapinfo in model\n\n\ttx = entity->model->textures[skinnum];\n\tif( !tx ) return; // missing texture ?\n\n\tif( *texture == 0 )\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"%s:remap%i_%i\", entity->model->name, skinnum, entity->index );\n\t\tskin.width = tx->width;\n\t\tskin.height = tx->height;\n\t\tskin.depth = skin.numMips = 1;\n\t\tskin.size = tx->width * tx->height;\n\t\tskin.type = PF_INDEXED_24;\n\t\tskin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL;\n\t\tskin.encode = DXT_ENCODE_DEFAULT;\n\t\tskin.buffer = (byte *)(tx + 1);\n\t\tskin.palette = skin.buffer + skin.size;\n\t\tpic = FS_CopyImage( &skin ); // because GL_LoadTextureInternal will freed a rgbdata_t at end\n\t\t*texture = GL_LoadTextureInternal( texname, pic, TF_KEEP_SOURCE );\n\t}\n\n\t// and now we can remap with internal routines\n\tref.dllFuncs.GL_ProcessTexture( *texture, -1.0f, topcolor, bottomcolor );\n}\n\n/*\n====================\nCL_FreeRemapInfo\n\nRelease remap info per entity\n====================\n*/\nstatic void CL_FreeRemapInfo( remap_info_t *info )\n{\n\tint\ti;\n\n\tAssert( info != NULL );\n\n\t// release all colormap texture copies\n\tfor( i = 0; i < info->numtextures; i++ )\n\t{\n\t\tif( info->ptexture != NULL )\n\t\t{\n\t\t\tif( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP ))\n\t\t\t\tref.dllFuncs.GL_FreeTexture( info->ptexture[i].index );\n\t\t}\n\n\t\tif( info->textures[i] != 0 )\n\t\t\tref.dllFuncs.GL_FreeTexture( info->textures[i] );\n\t}\n\n\tMem_Free( info ); // release struct\n}\n\n/*\n====================\nCL_UpdateRemapInfo\n\nUpdate all remaps per entity\n====================\n*/\nstatic void CL_UpdateRemapInfo( cl_entity_t *entity, int topcolor, int bottomcolor )\n{\n\tremap_info_t\t*info;\n\tint\t\ti;\n\n\ti = ( entity == &clgame.viewent ) ? clgame.maxEntities : entity->curstate.number;\n\tinfo = clgame.remap_info[i];\n\tif( !info ) return; // no remap info\n\n\tif( info->topcolor == topcolor && info->bottomcolor == bottomcolor )\n\t\treturn; // values is valid\n\n\tfor( i = 0; i < info->numtextures; i++ )\n\t{\n\t\tif( info->ptexture != NULL )\n\t\t{\n\t\t\tif( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP ))\n\t\t\t\tCL_UpdateStudioTexture( entity, &info->ptexture[i], topcolor, bottomcolor );\n\t\t}\n\t\telse CL_UpdateAliasTexture( entity, &info->textures[i], i, topcolor, bottomcolor );\n\t}\n\n\tinfo->topcolor = topcolor;\n\tinfo->bottomcolor = bottomcolor;\n}\n\n/*\n====================\nCL_AllocRemapInfo\n\nAllocate new remap info per entity\nand make copy of remap textures\n====================\n*/\nstatic void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int bottomcolor )\n{\n\tremap_info_t\t*info;\n\tstudiohdr_t\t*phdr;\n\taliashdr_t\t*ahdr;\n\tmstudiotexture_t\t*src, *dst;\n\tint\t\ti, size;\n\n\tif( !entity ) return;\n\ti = ( entity == &clgame.viewent ) ? clgame.maxEntities : entity->curstate.number;\n\n\tif( !model || ( model->type != mod_alias && model->type != mod_studio ))\n\t{\n\t\t// entity has changed model by another type, release remap info\n\t\tif( clgame.remap_info[i] )\n\t\t{\n\t\t\tCL_FreeRemapInfo( clgame.remap_info[i] );\n\t\t\tclgame.remap_info[i] = NULL;\n\t\t}\n\t\treturn; // missed or hide model, ignore it\n\t}\n\n\t// model doesn't contains remap textures\n\tif( model->numtextures <= 0 )\n\t{\n\t\t// entity has changed model with no remap textures\n\t\tif( clgame.remap_info[i] )\n\t\t{\n\t\t\tCL_FreeRemapInfo( clgame.remap_info[i] );\n\t\t\tclgame.remap_info[i] = NULL;\n\t\t}\n\t\treturn;\n\t}\n\n\tif( model->type == mod_studio )\n\t{\n\t\tphdr = (studiohdr_t *)Mod_StudioExtradata( model );\n\t\tif( !phdr ) return;\t// bad model?\n\n\t\tsrc = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);\n\n\t\t// NOTE: we must copy all the structures 'mstudiotexture_t' for easy access when model is rendering\n\t\tif( !CL_CmpStudioTextures( phdr->numtextures, src, clgame.remap_info[i] ) || clgame.remap_info[i]->model != model )\n\t\t{\n\t\t\t// this code catches studiomodel change with another studiomodel with remap textures\n\t\t\t// e.g. playermodel 'barney' with playermodel 'gordon'\n\t\t\tif( clgame.remap_info[i] ) CL_FreeRemapInfo( clgame.remap_info[i] ); // free old info\n\t\t\tsize = sizeof( remap_info_t ) + ( sizeof( mstudiotexture_t ) * phdr->numtextures );\n\t\t\tinfo = clgame.remap_info[i] = Mem_Calloc( clgame.mempool, size );\n\t\t\tinfo->ptexture = (mstudiotexture_t *)(info + 1); // textures are immediately comes after remap_info\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// studiomodel is valid, nothing to change\n\t\t\treturn;\n\t\t}\n\n\t\tinfo->numtextures = phdr->numtextures;\n\t\tinfo->topcolor = topcolor;\n\t\tinfo->bottomcolor = bottomcolor;\n\n\t\tsrc = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);\n\t\tdst = info->ptexture;\n\n\t\t// copy unchanged first\n\t\tmemcpy( dst, src, sizeof( mstudiotexture_t ) * phdr->numtextures );\n\n\t\t// make local copies for remap textures\n\t\tfor( i = 0; i < info->numtextures; i++ )\n\t\t{\n\t\t\tif( dst[i].flags & STUDIO_NF_COLORMAP )\n\t\t\t\tCL_DuplicateTexture( entity, model, &dst[i], topcolor, bottomcolor );\n\t\t}\n\t}\n\telse if( model->type == mod_alias )\n\t{\n\t\tahdr = (aliashdr_t *)Mod_AliasExtradata( model );\n\t\tif( !ahdr ) return;\t// bad model?\n\n\t\t// NOTE: we must copy all the structures 'mstudiotexture_t' for easy access when model is rendering\n\t\tif( !clgame.remap_info[i] || clgame.remap_info[i]->model != model )\n\t\t{\n\t\t\t// this code catches studiomodel change with another studiomodel with remap textures\n\t\t\t// e.g. playermodel 'barney' with playermodel 'gordon'\n\t\t\tif( clgame.remap_info[i] ) CL_FreeRemapInfo( clgame.remap_info[i] ); // free old info\n\t\t\tinfo = clgame.remap_info[i] = Mem_Calloc( clgame.mempool, sizeof( remap_info_t ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// aliasmodel is valid, nothing to change\n\t\t\treturn;\n\t\t}\n\n\t\tinfo->numtextures = model->numtextures;\n\n\t\t// alias remapping is easy\n\t\tCL_UpdateRemapInfo( entity, topcolor, bottomcolor );\n\t}\n\telse\n\t{\n\t\t// only alias & studio models are supposed for remapping\n\t\treturn;\n\t}\n\n\tinfo->model = model;\n}\n\n/*\n====================\nCL_ClearAllRemaps\n\nRelease all remap infos\n====================\n*/\nvoid CL_ClearAllRemaps( void )\n{\n\tint\ti;\n\n\tif( clgame.remap_info )\n\t{\n\t\tfor( i = 0; i < clgame.maxRemapInfos; i++ )\n\t\t{\n\t\t\tif( clgame.remap_info[i] )\n\t\t\t\tCL_FreeRemapInfo( clgame.remap_info[i] );\n\t\t}\n\t\tMem_Free( clgame.remap_info );\n\t}\n\tclgame.remap_info = NULL;\n}\n\n/*\n=============\nCL_EntitySetRemapColors\n=============\n*/\nqboolean CL_EntitySetRemapColors( cl_entity_t *e, model_t *mod, int top, int bottom )\n{\n\tCL_AllocRemapInfo( e, mod, top, bottom );\n\n\tif( CL_GetRemapInfoForEntity( e ))\n\t{\n\t\tCL_UpdateRemapInfo( e, top, bottom );\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n"
  },
  {
    "path": "engine/client/cl_render.c",
    "content": "/*\ncl_render.c - RenderAPI loader & implementation\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"library.h\"\n#include \"platform/platform.h\"\n\nint R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis )\n{\n\treturn Mod_FatPVS( org, radius, visbuffer, world.visbytes, merge, fullvis, false );\n}\n\nlightstyle_t *CL_GetLightStyle( int number )\n{\n\tAssert( number >= 0 && number < MAX_LIGHTSTYLES );\n\treturn &cl.lightstyles[number];\n}\n\nconst ref_overview_t *GL_GetOverviewParms( void )\n{\n\treturn &clgame.overView;\n}\n\nstatic void *R_Mem_Alloc( size_t cb, const char *filename, const int fileline )\n{\n\treturn _Mem_Alloc( cls.mempool, cb, true, filename, fileline );\n}\n\nstatic void R_Mem_Free( void *mem, const char *filename, const int fileline )\n{\n\tif( !mem ) return;\n\t_Mem_Free( mem, filename, fileline );\n}\n\n/*\n=========\npfnGetFilesList\n\n=========\n*/\nstatic char **pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly )\n{\n\tstatic search_t\t*t = NULL;\n\n\tif( t ) Mem_Free( t ); // release prev search\n\n\tt = FS_Search( pattern, true, gamedironly );\n\n\tif( !t )\n\t{\n\t\tif( numFiles ) *numFiles = 0;\n\t\treturn NULL;\n\t}\n\n\tif( numFiles ) *numFiles = t->numfilenames;\n\treturn t->filenames;\n}\n\nstatic uint pfnFileBufferCRC32( const void *buffer, const int length )\n{\n\tuint\tmodelCRC = 0;\n\n\tif( !buffer || length <= 0 )\n\t\treturn modelCRC;\n\n\tCRC32_Init( &modelCRC );\n\tCRC32_ProcessBuffer( &modelCRC, buffer, length );\n\treturn CRC32_Final( modelCRC );\n}\n\n/*\n=================\nR_EnvShot\n\n=================\n*/\nstatic void R_EnvShot( const float *vieworg, const char *name, qboolean skyshot, int shotsize )\n{\n\tstatic vec3_t viewPoint;\n\n\tif( !COM_CheckString( name ))\n\t\treturn;\n\n\tif( cls.scrshot_action != scrshot_inactive )\n\t{\n\t\tif( cls.scrshot_action != scrshot_skyshot && cls.scrshot_action != scrshot_envshot )\n\t\t\tCon_Printf( S_ERROR \"R_%sShot: subsystem is busy, try for next frame.\\n\", skyshot ? \"Sky\" : \"Env\" );\n\t\treturn;\n\t}\n\n\tcls.envshot_vieworg = NULL; // use client view\n\tQ_strncpy( cls.shotname, name, sizeof( cls.shotname ));\n\n\tif( vieworg )\n\t{\n\t\t// make sure what viewpoint don't temporare\n\t\tVectorCopy( vieworg, viewPoint );\n\t\tcls.envshot_vieworg = viewPoint;\n\t\tcls.envshot_disable_vis = true;\n\t}\n\n\t// make request for envshot\n\tif( skyshot ) cls.scrshot_action = scrshot_skyshot;\n\telse cls.scrshot_action = scrshot_envshot;\n\n\t// catch negative values\n\tcls.envshot_viewsize = Q_max( 0, shotsize );\n}\n\n/*\n=============\nCL_GenericHandle\n\n=============\n*/\nstatic const char *CL_GenericHandle( int fileindex )\n{\n\tif( fileindex < 0 || fileindex >= MAX_CUSTOM )\n\t\treturn 0;\n\treturn cl.files_precache[fileindex];\n}\n\nintptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef )\n{\n\tswitch( parm )\n\t{\n\tcase PARM_BSP2_SUPPORTED:\n\t\treturn 1;\n\tcase PARAM_GAMEPAUSED:\n\t\treturn cl.paused;\n\tcase PARM_CLIENT_INGAME:\n\t\treturn CL_IsInGame();\n\tcase PARM_MAX_ENTITIES:\n\t\treturn clgame.maxEntities;\n\tcase PARM_FEATURES:\n\t\treturn host.features;\n\tcase PARM_MAP_HAS_DELUXE:\n\t\treturn FBitSet( world.flags, FWORLD_HAS_DELUXEMAP );\n\tcase PARM_CLIENT_ACTIVE:\n\t\treturn (cls.state == ca_active);\n\tcase PARM_DEDICATED_SERVER:\n\t\treturn (host.type == HOST_DEDICATED);\n\tcase PARM_WATER_ALPHA:\n\t\treturn FBitSet( world.flags, FWORLD_WATERALPHA );\n\tcase PARM_DELUXEDATA:\n\t\treturn (intptr_t)world.deluxedata;\n\tcase PARM_SHADOWDATA:\n\t\treturn (intptr_t)world.shadowdata;\n\tcase PARM_FULLSCREEN:\n\t\treturn refState.fullScreen;\n\tcase PARM_WIDESCREEN:\n\t\treturn refState.wideScreen;\n\tcase PARM_SCREEN_WIDTH:\n\t\treturn refState.width;\n\tcase PARM_SCREEN_HEIGHT:\n\t\treturn refState.height;\n\tcase PARM_SKY_SPHERE:\n\t\treturn FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX );\n\tcase PARM_SURF_SAMPLESIZE:\n\t\tif( arg >= 0 && arg < cl.worldmodel->numsurfaces )\n\t\t\treturn Mod_SampleSizeForFace( &cl.worldmodel->surfaces[arg] );\n\t\treturn LM_SAMPLE_SIZE;\n\tdefault:\n\t\t// indicates call from client.dll\n\t\tif( checkRef )\n\t\t{\n\t\t\treturn ref.dllFuncs.RefGetParm( parm, arg );\n\t\t}\n\t\t// call issued from ref_dll, check extensions here\n\t\telse switch( parm )\n\t\t{\n\t\tcase PARM_DEV_OVERVIEW:\n\t\t\treturn CL_IsDevOverviewMode();\n\t\tcase PARM_THIRDPERSON:\n\t\t\treturn CL_IsThirdPerson();\n\t\tcase PARM_QUAKE_COMPATIBLE:\n\t\t\treturn Host_IsQuakeCompatible();\n\t\tcase PARM_CONNSTATE:\n\t\t\treturn (int)cls.state;\n\t\tcase PARM_PLAYING_DEMO:\n\t\t\treturn cls.demoplayback;\n\t\tcase PARM_WATER_LEVEL:\n\t\t\treturn cl.local.waterlevel;\n\t\tcase PARM_LOCAL_HEALTH:\n\t\t\treturn cl.local.health;\n\t\tcase PARM_LOCAL_GAME:\n\t\t\treturn Host_IsLocalGame();\n\t\tcase PARM_NUMENTITIES:\n\t\t\treturn pfnNumberOfEntities();\n\t\tcase PARM_GET_CLIENT_PTR:\n\t\t\treturn (intptr_t)&cl.time; // with the offset\n\t\tcase PARM_GET_HOST_PTR:\n\t\t\treturn (intptr_t)&host.realtime; // with the offset\n\t\tcase PARM_GET_WORLD_PTR:\n\t\t\treturn (intptr_t)&world;\n\t\tcase PARM_GET_MOVEVARS_PTR:\n\t\t\treturn (intptr_t)&clgame.movevars;\n\t\tcase PARM_GET_PALETTE_PTR:\n\t\t\treturn (intptr_t)&clgame.palette;\n\t\tcase PARM_GET_VIEWENT_PTR:\n\t\t\treturn (intptr_t)&clgame.viewent;\n\t\tcase PARM_GET_TEXGAMMATABLE_PTR:\n\t\tcase PARM_GET_LIGHTGAMMATABLE_PTR:\n\t\tcase PARM_GET_SCREENGAMMATABLE_PTR:\n\t\tcase PARM_GET_LINEARGAMMATABLE_PTR:\n\t\t\treturn V_GetGammaPtr( parm );\n\t\tcase PARM_GET_LIGHTSTYLES_PTR:\n\t\t\treturn (intptr_t)CL_GetLightStyle( 0 );\n\t\tcase PARM_GET_DLIGHTS_PTR:\n\t\t\treturn (intptr_t)CL_GetDynamicLight( 0 );\n\t\tcase PARM_GET_ELIGHTS_PTR:\n\t\t\treturn (intptr_t)CL_GetEntityLight( 0 );\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic intptr_t pfnRenderGetParm( int parm, int arg )\n{\n\treturn CL_RenderGetParm( parm, arg, true );\n}\n\nstatic void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, float attn, float synctime )\n{\n\treturn; // stub, use AVI_SetParm and AVI_Think to stream AVI sound\n}\n\nstatic render_api_t gRenderAPI =\n{\n\tpfnRenderGetParm, // GL_RenderGetParm,\n\tNULL, // R_GetDetailScaleForTexture,\n\tNULL, // R_GetExtraParmsForTexture,\n\tCL_GetLightStyle,\n\tCL_GetDynamicLight,\n\tCL_GetEntityLight,\n\tLightToTexGamma,\n\tNULL, // R_GetFrameTime,\n\tNULL, // R_SetCurrentEntity,\n\tNULL, // R_SetCurrentModel,\n\tR_FatPVS,\n\tR_StoreEfrags,\n\tNULL, // GL_FindTexture,\n\tNULL, // GL_TextureName,\n\tNULL, // GL_TextureData,\n\tNULL, // GL_LoadTexture,\n\tNULL, // GL_CreateTexture,\n\tNULL, // GL_LoadTextureArray,\n\tNULL, // GL_CreateTextureArray,\n\tNULL, // GL_FreeTexture,\n\tNULL, // DrawSingleDecal,\n\tNULL, // R_DecalSetupVerts,\n\tNULL, // R_EntityRemoveDecals,\n\tAVI_LoadVideo,\n\tAVI_GetVideoInfo,\n\tAVI_GetVideoFrameNumber,\n\tAVI_GetVideoFrame,\n\tNULL, // R_UploadStretchRaw,\n\tAVI_FreeVideo,\n\tAVI_IsActive,\n\tpfnAVI_StreamSound,\n\tAVI_Think,\n\tAVI_SetParm,\n\tNULL, // GL_Bind,\n\tNULL, // GL_SelectTexture,\n\tNULL, // GL_LoadTexMatrixExt,\n\tNULL, // GL_LoadIdentityTexMatrix,\n\tNULL, // GL_CleanUpTextureUnits,\n\tNULL, // GL_TexGen,\n\tNULL, // GL_TextureTarget,\n\tNULL, // GL_SetTexCoordArrayMode,\n\tNULL, // GL_GetProcAddress,\n\tNULL, // GL_UpdateTexSize,\n\tNULL,\n\tNULL,\n\tNULL, // CL_DrawParticlesExternal,\n\tR_EnvShot,\n\tpfnSPR_LoadExt,\n\tNULL, // R_LightVec,\n\tNULL, // R_StudioGetTexture,\n\tGL_GetOverviewParms,\n\tCL_GenericHandle,\n\tCOM_SaveFile,\n\tNULL,\n\tR_Mem_Alloc,\n\tR_Mem_Free,\n\tpfnGetFilesList,\n\tpfnFileBufferCRC32,\n\tpfnCompareFileTime,\n\tHost_Error,\n\t(void*)CL_ModelHandle,\n\tpfnTime,\n\tCvar_Set,\n\tS_FadeMusicVolume,\n\tCOM_SetRandomSeed,\n};\n\nstatic void R_FillRenderAPIFromRef( render_api_t *to, const ref_interface_t *from )\n{\n\tto->GetDetailScaleForTexture = from->GetDetailScaleForTexture;\n\tto->GetExtraParmsForTexture  = from->GetExtraParmsForTexture;\n\tto->GetFrameTime             = from->GetFrameTime;\n\tto->R_SetCurrentEntity       = from->R_SetCurrentEntity;\n\tto->R_SetCurrentModel        = from->R_SetCurrentModel;\n\tto->GL_FindTexture           = from->GL_FindTexture;\n\tto->GL_TextureName           = from->GL_TextureName;\n\tto->GL_TextureData           = from->GL_TextureData;\n\tto->GL_LoadTexture           = from->GL_LoadTexture;\n\tto->GL_CreateTexture         = from->GL_CreateTexture;\n\tto->GL_LoadTextureArray      = from->GL_LoadTextureArray;\n\tto->GL_CreateTextureArray    = from->GL_CreateTextureArray;\n\tto->GL_FreeTexture           = from->GL_FreeTexture;\n\tto->DrawSingleDecal          = from->DrawSingleDecal;\n\tto->R_DecalSetupVerts        = from->R_DecalSetupVerts;\n\tto->R_EntityRemoveDecals     = from->R_EntityRemoveDecals;\n\tto->AVI_UploadRawFrame       = from->AVI_UploadRawFrame;\n\tto->GL_Bind                  = from->GL_Bind;\n\tto->GL_SelectTexture         = from->GL_SelectTexture;\n\tto->GL_LoadTextureMatrix     = from->GL_LoadTextureMatrix;\n\tto->GL_TexMatrixIdentity     = from->GL_TexMatrixIdentity;\n\tto->GL_CleanUpTextureUnits   = from->GL_CleanUpTextureUnits;\n\tto->GL_TexGen                = from->GL_TexGen;\n\tto->GL_TextureTarget         = from->GL_TextureTarget;\n\tto->GL_TexCoordArrayMode     = from->GL_TexCoordArrayMode;\n\tto->GL_UpdateTexSize         = from->GL_UpdateTexSize;\n\tto->GL_DrawParticles         = from->GL_DrawParticles;\n\tto->LightVec                 = from->LightVec;\n\tto->StudioGetTexture         = from->StudioGetTexture;\n\tto->GL_GetProcAddress        = from->R_GetProcAddress;\n}\n\n/*\n===============\nR_InitRenderAPI\n\nInitialize client external rendering\n===============\n*/\nqboolean R_InitRenderAPI( void )\n{\n\t// make sure what render functions is cleared\n\tmemset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));\n\n\t// fill missing functions from renderer\n\tR_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );\n\n\tif( clgame.dllFuncs.pfnGetRenderInterface )\n\t{\n\t\tif( clgame.dllFuncs.pfnGetRenderInterface( CL_RENDER_INTERFACE_VERSION, &gRenderAPI, &clgame.drawFuncs ))\n\t\t{\n\t\t\tCon_Reportf( \"%s: ^2initailized extended RenderAPI ^7ver. %i\\n\", __func__, CL_RENDER_INTERFACE_VERSION );\n\t\t\treturn true;\n\t\t}\n\n\t\t// make sure what render functions is cleared\n\t\tmemset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));\n\n\t\treturn false; // just tell user about problems\n\t}\n\n\t// render interface is missed\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/cl_scrn.c",
    "content": "/*\ncl_scrn.c - refresh screen\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"qfont.h\"\n#include \"input.h\"\n#include \"library.h\"\n\nCVAR_DEFINE_AUTO( scr_centertime, \"2.5\", 0, \"centerprint hold time\" );\nCVAR_DEFINE_AUTO( scr_loading, \"0\", 0, \"loading bar progress\" );\nCVAR_DEFINE_AUTO( scr_download, \"-1\", 0, \"downloading bar progress\" );\nCVAR_DEFINE( scr_viewsize, \"viewsize\", \"120\", FCVAR_ARCHIVE, \"screen size (quake only)\" );\nCVAR_DEFINE_AUTO( cl_testlights, \"0\", FCVAR_CHEAT, \"test dynamic lights\" );\nCVAR_DEFINE( cl_allow_levelshots, \"allow_levelshots\", \"0\", FCVAR_ARCHIVE, \"allow engine to use indivdual levelshots instead of 'loading' image\" );\nCVAR_DEFINE_AUTO( cl_levelshot_name, \"*black\", 0, \"contains path to current levelshot\" );\nstatic CVAR_DEFINE_AUTO( cl_envshot_size, \"256\", FCVAR_ARCHIVE, \"envshot size of cube side\" );\nCVAR_DEFINE_AUTO( v_dark, \"0\", 0, \"starts level from dark screen\" );\nstatic CVAR_DEFINE_AUTO( net_speeds, \"0\", FCVAR_ARCHIVE, \"show network packets\" );\nstatic CVAR_DEFINE_AUTO( cl_showfps, \"0\", FCVAR_ARCHIVE, \"show client fps\" );\nstatic CVAR_DEFINE_AUTO( cl_showpos, \"0\", FCVAR_ARCHIVE, \"show local player position and velocity\" );\nstatic CVAR_DEFINE_AUTO( cl_showents, \"0\", FCVAR_ARCHIVE | FCVAR_CHEAT, \"show entities information (largely undone)\" );\nstatic CVAR_DEFINE_AUTO( cl_showcmd, \"0\", 0, \"visualize usercmd button presses\" );\n\ntypedef struct\n{\n\tint\tx1, y1, x2, y2;\n} dirty_t;\n\nstatic dirty_t\tscr_dirty, scr_old_dirty[2];\nstatic qboolean\tscr_init = false;\n\n/*\n==============\nSCR_DrawFPS\n==============\n*/\nvoid SCR_DrawFPS( int height )\n{\n\tfloat\t\tcalc;\n\trgba_t\t\tcolor;\n\tdouble\t\tnewtime;\n\tstatic double\tnexttime = 0, lasttime = 0;\n\tstatic double\tframerate = 0;\n\tstatic int\tframecount = 0;\n\tstatic int\tminfps = 9999;\n\tstatic int\tmaxfps = 0;\n\tchar\t\tfpsstring[64];\n\tint\t\toffset;\n\n\tif( cls.state != ca_active || !cl_showfps.value || cl.background )\n\t\treturn;\n\n\tswitch( cls.scrshot_action )\n\t{\n\tcase scrshot_normal:\n\tcase scrshot_snapshot:\n\tcase scrshot_inactive:\n\t\tbreak;\n\tdefault: return;\n\t}\n\n\tnewtime = Sys_DoubleTime();\n\tif( newtime >= nexttime )\n\t{\n\t\tframerate = framecount / (newtime - lasttime);\n\t\tlasttime = newtime;\n\t\tnexttime = Q_max( nexttime + 1.0, lasttime - 1.0 );\n\t\tframecount = 0;\n\t}\n\n\tcalc = framerate;\n\tframecount++;\n\n\tif( calc < 1.0f )\n\t{\n\t\tQ_snprintf( fpsstring, sizeof( fpsstring ), \"%4i spf\", (int)(1.0f / calc + 0.5f));\n\t\tMakeRGBA( color, 255, 0, 0, 255 );\n\t}\n\telse\n\t{\n\t\tint\tcurfps = (int)(calc + 0.5f);\n\n\t\tif( curfps < minfps ) minfps = curfps;\n\t\tif( curfps > maxfps ) maxfps = curfps;\n\n\t\tif( cl_showfps.value == 2 )\n\t\t\tQ_snprintf( fpsstring, sizeof( fpsstring ), \"fps: ^1%4i min, ^3%4i cur, ^2%4i max\", minfps, curfps, maxfps );\n\t\telse Q_snprintf( fpsstring, sizeof( fpsstring ), \"%4i fps\", curfps );\n\t\tMakeRGBA( color, 255, 255, 255, 255 );\n\t}\n\n\tCon_DrawStringLen( fpsstring, &offset, NULL );\n\tCon_DrawString( refState.width - offset - 4, height, fpsstring, color );\n}\n\n/*\n==============\nSCR_DrawPos\n\nDraw local player position, angles and velocity\n==============\n*/\nvoid SCR_DrawPos( void )\n{\n\tstatic char     msg[MAX_SYSPATH];\n\tfloat speed;\n\tcl_entity_t *ent;\n\trgba_t color;\n\n\tif( cls.state != ca_active || !cl_showpos.value || cl.background )\n\t\treturn;\n\n\tent = CL_GetLocalPlayer();\n\tspeed = VectorLength( cl.simvel );\n\n\tQ_snprintf( msg, MAX_SYSPATH,\n\t\t\"pos: %.2f %.2f %.2f\\n\"\n\t\t\"ang: %.2f %.2f %.2f\\n\"\n\t\t\"velocity: %.2f\",\n\t\tcl.simorg[0], cl.simorg[1], cl.simorg[2],\n\t\t// should we use entity angles or viewangles?\n\t\t// view isn't always bound to player\n\t\tent->angles[0], ent->angles[1], ent->angles[2],\n\t\tspeed );\n\n\tMakeRGBA( color, 255, 255, 255, 255 );\n\n\tCon_DrawString( refState.width / 2, 4, msg, color );\n}\n\n/*\n==============\nSCR_DrawEnts\n==============\n*/\nvoid SCR_DrawEnts( void )\n{\n\trgba_t color = { 255, 255, 255, 255 };\n\tint i;\n\n\tif( cls.state != ca_active || !cl_showents.value || ( cl.maxclients > 1 && !cls.demoplayback ))\n\t\treturn;\n\n\t// this probably better hook CL_AddVisibleEntities\n\t// as entities might get added by client.dll\n\tfor( i = 0; i < clgame.maxEntities; i++ )\n\t{\n\t\tconst cl_entity_t *ent = &clgame.entities[i];\n\t\tstring msg;\n\t\tvec3_t screen, pos;\n\n\t\tif( ent->curstate.messagenum != cl.parsecount )\n\t\t\tcontinue;\n\n\t\tVectorCopy( ent->origin, pos );\n\n\t\tif( ent->model != NULL )\n\t\t{\n\t\t\tvec3_t v;\n\n\t\t\t// simple model type filter\n\t\t\tif( cl_showents.value > 1 )\n\t\t\t{\n\t\t\t\tif( ent->model->type != (modtype_t)( cl_showents.value - 2 ))\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tVectorAverage( ent->model->mins, ent->model->maxs, v );\n\t\t\tVectorAdd( pos, v, pos );\n\t\t}\n\n\t\tif( !ref.dllFuncs.WorldToScreen( pos, screen ))\n\t\t{\n\t\t\tQ_snprintf( msg, sizeof( msg ),\n\t\t\t\t\"entity %d\\n\"\n\t\t\t\t\"model %s\\n\"\n\t\t\t\t\"movetype %d\\n\",\n\t\t\t\tent->index,\n\t\t\t\tent->model ? ent->model->name : \"(null)\",\n\t\t\t\tent->curstate.movetype );\n\n\t\t\tscreen[0] =  0.5f * screen[0] * refState.width;\n\t\t\tscreen[1] = -0.5f * screen[1] * refState.height;\n\t\t\tscreen[0] += 0.5f * refState.width;\n\t\t\tscreen[1] += 0.5f * refState.height;\n\n\t\t\tCon_DrawString( screen[0], screen[1], msg, color );\n\t\t}\n\t}\n}\n\n/*\n==============\nSCR_DrawUserCmd\n\nanother debugging aids, shows pressed buttons\n==============\n*/\nvoid SCR_DrawUserCmd( void )\n{\n\truncmd_t *pcmd = &cl.commands[( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK];\n\tstruct\n\t{\n\t\tint mask;\n\t\tconst char *name;\n\t} buttons[16] =\n\t{\n\t{ IN_ATTACK, \"attack\" },\n\t{ IN_JUMP, \"jump\" },\n\t{ IN_DUCK, \"duck\" },\n\t{ IN_FORWARD, \"forward\" },\n\t{ IN_BACK, \"back\" },\n\t{ IN_USE, \"use\" },\n\t{ IN_CANCEL, \"cancel\" },\n\t{ IN_LEFT, \"left\" },\n\t{ IN_RIGHT, \"right\" },\n\t{ IN_MOVELEFT, \"moveleft\" },\n\t{ IN_MOVERIGHT, \"moveright\" },\n\t{ IN_ATTACK2, \"attack2\" },\n\t{ IN_RUN, \"run\" },\n\t{ IN_RELOAD, \"reload\" },\n\t{ IN_ALT1, \"alt1\" },\n\t{ IN_SCORE, \"score\" },\n\t};\n\tcl_font_t *font = Con_GetCurFont();\n\tstring msg;\n\tint i, ypos = 100;\n\n\tif( cls.state != ca_active || !cl_showcmd.value )\n\t\treturn;\n\n\tfor( i = 0; i < ARRAYSIZE( buttons ); i++ )\n\t{\n\t\trgba_t rgba;\n\n\t\trgba[0] = FBitSet( pcmd->cmd.buttons, buttons[i].mask ) ? 0 : 255;\n\t\trgba[1] = FBitSet( pcmd->cmd.buttons, buttons[i].mask ) ? 255 : 0;\n\t\trgba[2] = 0;\n\t\trgba[3] = 255;\n\n\t\tCon_DrawString( 100, ypos, buttons[i].name, rgba );\n\n\t\typos += font->charHeight;\n\t}\n\n\tQ_snprintf( msg, sizeof( msg ),\n\t\t\"F/S/U: %g %g %g\\n\"\n\t\t\"impulse: %u\\n\"\n\t\t\"msec: %u\",\n\t\tpcmd->cmd.forwardmove, pcmd->cmd.sidemove, pcmd->cmd.upmove,\n\t\tpcmd->cmd.impulse,\n\t\tpcmd->cmd.msec );\n\tCon_DrawString( 100, ypos, msg, g_color_table[7] );\n}\n\n/*\n==============\nSCR_NetSpeeds\n\nsame as r_speeds but for network channel\n==============\n*/\nvoid SCR_NetSpeeds( void )\n{\n\tstatic char\tmsg[MAX_SYSPATH];\n\tint\t\tx, y;\n\tfloat\t\ttime = cl.mtime[0];\n\tstatic int\tmin_svfps = 100;\n\tstatic int\tmax_svfps = 0;\n\tint\t\tcur_svfps = 0;\n\tstatic int\tmin_clfps = 100;\n\tstatic int\tmax_clfps = 0;\n\tint\t\tcur_clfps = 0;\n\trgba_t\t\tcolor;\n\tcl_font_t *font = Con_GetCurFont();\n\n\tif( !host.allow_console )\n\t\treturn;\n\n\tif( !net_speeds.value || cls.state != ca_active )\n\t\treturn;\n\n\t// prevent to get too big values at max\n\tif( cl_serverframetime() > 0.0001f )\n\t{\n\t\tcur_svfps = Q_rint( 1.0f / cl_serverframetime( ));\n\t\tif( cur_svfps < min_svfps ) min_svfps = cur_svfps;\n\t\tif( cur_svfps > max_svfps ) max_svfps = cur_svfps;\n\t}\n\n\t// prevent to get too big values at max\n\tif( cl_clientframetime() > 0.0001f )\n\t{\n\t\tcur_clfps = Q_rint( 1.0f / cl_clientframetime( ));\n\t\tif( cur_clfps < min_clfps ) min_clfps = cur_clfps;\n\t\tif( cur_clfps > max_clfps ) max_clfps = cur_clfps;\n\t}\n\n\tQ_snprintf( msg, sizeof( msg ),\n\t\t\"Updaterate: ^1%2i min, ^3%2i cur, ^2%2i max\\n\"\n\t\t\"Client FPS: ^1%i min, ^3%3i cur, ^2%3i max\\n\"\n\t\t\"Game Time: %02d:%02d\\n\"\n\t\t\"Total received from server: %s\\n\"\n\t\t\"Total sent to server: %s\\n\",\n\t\tmin_svfps, cur_svfps, max_svfps,\n\t\tmin_clfps, cur_clfps, max_clfps,\n\t\t(int)(time / 60.0f ), (int)fmod( time, 60.0f ),\n\t\tQ_memprint( cls.netchan.total_received ),\n\t\tQ_memprint( cls.netchan.total_sended )\n\t);\n\n\tx = refState.width - 320 * font->scale;\n\ty = 384;\n\n\tMakeRGBA( color, 255, 255, 255, 255 );\n\tCL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF );\n}\n\n/*\n================\nSCR_RSpeeds\n================\n*/\nvoid SCR_RSpeeds( void )\n{\n\tchar\tmsg[2048];\n\n\tif( !host.allow_console )\n\t\treturn;\n\n\tif( ref.dllFuncs.R_SpeedsMessage( msg, sizeof( msg )))\n\t{\n\t\tint\tx, y;\n\t\trgba_t\tcolor;\n\t\tcl_font_t *font = Con_GetCurFont();\n\n\t\tx = refState.width - 340 * font->scale;\n\t\ty = 64;\n\n\t\tMakeRGBA( color, 255, 255, 255, 255 );\n\t\tCL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF );\n\t}\n}\n\n/*\n================\nSCR_MakeLevelShot\n\ncreates levelshot at next frame\n================\n*/\nvoid SCR_MakeLevelShot( void )\n{\n\tif( cls.scrshot_request != scrshot_plaque )\n\t\treturn;\n\n\t// make levelshot at nextframe()\n\tCbuf_AddText( \"levelshot\\n\" );\n}\n\n/*\n===============\nVID_WriteOverviewScript\n\nCreate overview script file\n===============\n*/\nstatic void VID_WriteOverviewScript( void )\n{\n\tref_overview_t\t*ov = &clgame.overView;\n\tstring\t\tfilename;\n\tfile_t\t\t*f;\n\n\tQ_snprintf( filename, sizeof( filename ), \"overviews/%s.txt\", clgame.mapname );\n\n\tf = FS_Open( filename, \"w\", false );\n\tif( !f ) return;\n\n\tFS_Printf( f, \"// overview description file for %s.bsp\\n\\n\", clgame.mapname );\n\tFS_Print( f, \"global\\n{\\n\" );\n\tFS_Printf( f, \"\\tZOOM\\t%.2f\\n\", ov->flZoom );\n\tFS_Printf( f, \"\\tORIGIN\\t%.2f\\t%.2f\\t%.2f\\n\", ov->origin[0], ov->origin[1], ov->origin[2] );\n\tFS_Printf( f, \"\\tROTATED\\t%i\\n\", ov->rotated ? 1 : 0 );\n\tFS_Print( f, \"}\\n\\nlayer\\n{\\n\" );\n\tFS_Printf( f, \"\\tIMAGE\\t\\\"overviews/%s.bmp\\\"\\n\", clgame.mapname );\n\tFS_Printf( f, \"\\tHEIGHT\\t%.2f\\n\", ov->zFar );\t// ???\n\tFS_Print( f, \"}\\n\" );\n\n\tFS_Close( f );\n}\n\n/*\n================\nSCR_MakeScreenShot\n\ncreate a requested screenshot type\n================\n*/\nvoid SCR_MakeScreenShot( void )\n{\n\tqboolean\tiRet = false;\n\tint\tviewsize;\n\n\tif( cls.scrshot_action == scrshot_inactive )\n\t\treturn;\n\n\tif( cls.envshot_viewsize > 0 )\n\t\tviewsize = cls.envshot_viewsize;\n\telse viewsize = cl_envshot_size.value;\n\n\tV_CheckGamma();\n\n\tswitch( cls.scrshot_action )\n\t{\n\tcase scrshot_normal:\n\t\tiRet = ref.dllFuncs.VID_ScreenShot( cls.shotname, VID_SCREENSHOT );\n\t\tbreak;\n\tcase scrshot_snapshot:\n\t\tiRet = ref.dllFuncs.VID_ScreenShot( cls.shotname, VID_SNAPSHOT );\n\t\tbreak;\n\tcase scrshot_plaque:\n\t\tiRet = ref.dllFuncs.VID_ScreenShot( cls.shotname, VID_LEVELSHOT );\n\t\tbreak;\n\tcase scrshot_savegame:\n\t\tiRet = ref.dllFuncs.VID_ScreenShot( cls.shotname, VID_MINISHOT );\n\t\tbreak;\n\tcase scrshot_envshot:\n\t\tiRet = ref.dllFuncs.VID_CubemapShot( cls.shotname, viewsize, cls.envshot_vieworg, false );\n\t\tbreak;\n\tcase scrshot_skyshot:\n\t\tiRet = ref.dllFuncs.VID_CubemapShot( cls.shotname, viewsize, cls.envshot_vieworg, true );\n\t\tbreak;\n\tcase scrshot_mapshot:\n\t\tiRet = ref.dllFuncs.VID_ScreenShot( cls.shotname, VID_MAPSHOT );\n\t\tif( iRet )\n\t\t\tVID_WriteOverviewScript(); // store overview script too\n\t\tbreak;\n\t}\n\n\t// report\n\tif( iRet )\n\t{\n\t\t// snapshots don't writes message about image\n\t\tif( cls.scrshot_action != scrshot_snapshot )\n\t\t\tCon_Reportf( \"Write %s\\n\", cls.shotname );\n\t}\n\telse Con_Printf( S_ERROR \"Unable to write %s\\n\", cls.shotname );\n\n\tcls.envshot_vieworg = NULL;\n\tcls.scrshot_action = scrshot_inactive;\n\tcls.envshot_disable_vis = false;\n\tcls.envshot_viewsize = 0;\n\tcls.shotname[0] = '\\0';\n}\n\n/*\n================\nSCR_DrawPlaque\n================\n*/\nstatic qboolean SCR_DrawPlaque( void )\n{\n\tif(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background )\n\t{\n\t\tint levelshot = ref.dllFuncs.GL_LoadTexture( cl_levelshot_name.string, NULL, 0, TF_IMAGE );\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n\t\tref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1, levelshot );\n\t\tif( !cl.background ) CL_DrawHUD( CL_LOADING );\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n================\nSCR_BeginLoadingPlaque\n================\n*/\nvoid SCR_BeginLoadingPlaque( qboolean is_background )\n{\n\tS_StopAllSounds( true );\n\tcl.audio_prepped = false;\t\t\t// don't play ambients\n\n\tif( cls.key_dest == key_menu && !cls.changedemo && !is_background )\n\t{\n\t\tUI_SetActiveMenu( false );\n\t\tif( cls.state == ca_disconnected && !(GameState->curstate == STATE_RUNFRAME && GameState->nextstate != STATE_RUNFRAME) )\n\t\t\tSCR_UpdateScreen();\n\t}\n\n\tif( cls.state == ca_disconnected || cls.disable_screen )\n\t\treturn; // already set\n\n\tif( cls.key_dest == key_console )\n\t\treturn;\n\n\tif( is_background ) IN_MouseSavePos( );\n\tcls.draw_changelevel = !is_background;\n\tSCR_UpdateScreen();\n\n\t// set video_prepped after update screen, so engine can draw last remaining frame\n\tcl.video_prepped = false;\n\tcls.disable_screen = host.realtime;\n\tcl.background = is_background;\t\t// set right state before svc_serverdata is came\n\n//\tSNDDMA_LockSound();\n}\n\n/*\n================\nSCR_EndLoadingPlaque\n================\n*/\nvoid SCR_EndLoadingPlaque( void )\n{\n\tcls.disable_screen = 0.0f;\n\tCon_ClearNotify();\n//\tSNDDMA_UnlockSound();\n}\n\n/*\n=================\nSCR_AddDirtyPoint\n=================\n*/\nstatic void SCR_AddDirtyPoint( int x, int y )\n{\n\tif( x < scr_dirty.x1 ) scr_dirty.x1 = x;\n\tif( x > scr_dirty.x2 ) scr_dirty.x2 = x;\n\tif( y < scr_dirty.y1 ) scr_dirty.y1 = y;\n\tif( y > scr_dirty.y2 ) scr_dirty.y2 = y;\n}\n\n/*\n================\nSCR_DirtyScreen\n================\n*/\nvoid SCR_DirtyScreen( void )\n{\n\tSCR_AddDirtyPoint( 0, 0 );\n\tSCR_AddDirtyPoint( refState.width - 1, refState.height - 1 );\n}\n\nstatic void R_DrawTileClear( int texnum, int x, int y, int w, int h, float tw, float th )\n{\n\tfloat s1 = x / tw;\n\tfloat t1 = y / th;\n\tfloat s2 = ( x + w ) / tw;\n\tfloat t2 = ( y + h ) / th;\n\n\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n\tref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, texnum );\n}\n\n/*\n================\nSCR_TileClear\n================\n*/\nvoid SCR_TileClear( void )\n{\n\tint\ti, top, bottom, left, right, texnum;\n\tdirty_t\tclear;\n\tfloat tw, th;\n\n\tif( likely( scr_viewsize.value >= 120 ))\n\t\treturn; // full screen rendering\n\n\tif( !cls.tileImage )\n\t{\n\t\tcls.tileImage = ref.dllFuncs.GL_LoadTexture( \"gfx/backtile.lmp\", NULL, 0, TF_NOMIPMAP );\n\t\tif( !cls.tileImage )\n\t\t\tcls.tileImage = -1;\n\t}\n\n\tif( cls.tileImage > 0 )\n\t\ttexnum = cls.tileImage;\n\telse texnum = 0;\n\n\t// erase rect will be the union of the past three frames\n\t// so tripple buffering works properly\n\tclear = scr_dirty;\n\n\tfor( i = 0; i < 2; i++ )\n\t{\n\t\tif( scr_old_dirty[i].x1 < clear.x1 )\n\t\t\tclear.x1 = scr_old_dirty[i].x1;\n\t\tif( scr_old_dirty[i].x2 > clear.x2 )\n\t\t\tclear.x2 = scr_old_dirty[i].x2;\n\t\tif( scr_old_dirty[i].y1 < clear.y1 )\n\t\t\tclear.y1 = scr_old_dirty[i].y1;\n\t\tif( scr_old_dirty[i].y2 > clear.y2 )\n\t\t\tclear.y2 = scr_old_dirty[i].y2;\n\t}\n\n\tscr_old_dirty[1] = scr_old_dirty[0];\n\tscr_old_dirty[0] = scr_dirty;\n\n\tscr_dirty.x1 = 9999;\n\tscr_dirty.x2 = -9999;\n\tscr_dirty.y1 = 9999;\n\tscr_dirty.y2 = -9999;\n\n\tif( clear.y2 <= clear.y1 )\n\t\treturn; // nothing disturbed\n\n\ttop = clgame.viewport[1];\n\tbottom = top + clgame.viewport[3] - 1;\n\tleft = clgame.viewport[0];\n\tright = left + clgame.viewport[2] - 1;\n\n\ttw = REF_GET_PARM( PARM_TEX_SRC_WIDTH, texnum );\n\tth = REF_GET_PARM( PARM_TEX_SRC_HEIGHT, texnum );\n\n\tif( clear.y1 < top )\n\t{\n\t\t// clear above view screen\n\t\ti = clear.y2 < top-1 ? clear.y2 : top - 1;\n\t\tR_DrawTileClear( texnum, clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1 + 1, tw, th );\n\t\tclear.y1 = top;\n\t}\n\n\tif( clear.y2 > bottom )\n\t{\n\t\t// clear below view screen\n\t\ti = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1;\n\t\tR_DrawTileClear( texnum, clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i + 1, tw, th );\n\t\tclear.y2 = bottom;\n\t}\n\n\tif( clear.x1 < left )\n\t{\n\t\t// clear left of view screen\n\t\ti = clear.x2 < left - 1 ? clear.x2 : left - 1;\n\t\tR_DrawTileClear( texnum, clear.x1, clear.y1, i - clear.x1 + 1, clear.y2 - clear.y1 + 1, tw, th );\n\t\tclear.x1 = left;\n\t}\n\n\tif( clear.x2 > right )\n\t{\n\t\t// clear left of view screen\n\t\ti = clear.x1 > right + 1 ? clear.x1 : right + 1;\n\t\tR_DrawTileClear( texnum, i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1 + 1, tw, th );\n\t\tclear.x2 = right;\n\t}\n}\n\n/*\n==================\nSCR_UpdateScreen\n\nThis is called every frame, and can also be called explicitly to flush\ntext to the screen.\n==================\n*/\nvoid SCR_UpdateScreen( void )\n{\n\tqboolean screen_redraw = true; // assume screen has been redrawn\n\n\tif( !V_PreRender( )) return;\n\n\tswitch( cls.state )\n\t{\n\tcase ca_disconnected:\n\t\tCon_RunConsole ();\n\t\tbreak;\n\tcase ca_connecting:\n\tcase ca_connected:\n\tcase ca_validate:\n\t\tscreen_redraw = SCR_DrawPlaque();\n\t\tbreak;\n\tcase ca_active:\n\t\tCon_RunConsole ();\n\t\tV_RenderView();\n\t\tbreak;\n\tcase ca_cinematic:\n\t\tSCR_DrawCinematic();\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: bad cls.state\\n\", __func__ );\n\t\tbreak;\n\t}\n\n\t// during changelevel we might have a few frames when we have nothing to draw\n\t// (assuming levelshots are off) and drawing 2d on top of nothing or cleared screen\n\t// is ugly, specifically with Adreno and ImgTec GPUs\n\tif( screen_redraw || !cls.changelevel || !cls.changedemo )\n\t\tV_PostRender();\n}\n\n/*\n================\nSCR_LoadCreditsFont\n\nINTERNAL RESOURCE\n================\n*/\nvoid SCR_LoadCreditsFont( void )\n{\n\tcl_font_t *const font = &cls.creditsFont;\n\tqboolean success = false;\n\tfloat scale = hud_fontscale.value;\n\tdword crc = 0;\n\n\t// replace default gfx.wad textures by current charset's font\n\tif( !CRC32_File( &crc, \"gfx.wad\" ) || crc == 0x49eb9f16 )\n\t{\n\t\tstring charsetFnt;\n\n\t\tif( Q_snprintf( charsetFnt, sizeof( charsetFnt ),\n\t\t\t\"creditsfont_%s.fnt\", Cvar_VariableString( \"con_charset\" )) > 0 )\n\t\t{\n\t\t\tif( FS_FileExists( charsetFnt, false ))\n\t\t\t\tsuccess = Con_LoadVariableWidthFont( charsetFnt, font, scale, &hud_fontrender, TF_FONT );\n\t\t}\n\t}\n\n\tif( !success )\n\t\tsuccess = Con_LoadVariableWidthFont( \"gfx/creditsfont.fnt\", font, scale, &hud_fontrender, TF_FONT );\n\n\tif( !success )\n\t\tsuccess = Con_LoadFixedWidthFont( \"gfx/conchars\", font, scale, &hud_fontrender, TF_FONT );\n\n\t// copy font size for client.dll\n\tif( success )\n\t{\n\t\tint i;\n\n\t\tclgame.scrInfo.iCharHeight = cls.creditsFont.charHeight;\n\n\t\tfor( i = 0; i < ARRAYSIZE( cls.creditsFont.charWidths ); i++ )\n\t\t\tclgame.scrInfo.charWidths[i] = cls.creditsFont.charWidths[i];\n\t}\n\telse Con_DPrintf( S_ERROR \"failed to load HUD font\\n\" );\n}\n\n/*\n================\nSCR_InstallParticlePalette\n\nINTERNAL RESOURCE\n================\n*/\nstatic void SCR_InstallParticlePalette( void )\n{\n\trgbdata_t\t*pic;\n\tint\ti;\n\n\t// first check 'palette.lmp' then 'palette.pal'\n\tpic = FS_LoadImage( DEFAULT_INTERNAL_PALETTE, NULL, 0 );\n\tif( !pic ) pic = FS_LoadImage( DEFAULT_EXTERNAL_PALETTE, NULL, 0 );\n\n\t// NOTE: imagelib required this fakebuffer for loading internal palette\n\tif( !pic ) pic = FS_LoadImage( \"#valve.pal\", (byte *)&i, 768 );\n\n\tif( pic )\n\t{\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\tclgame.palette[i].r = pic->palette[i*4+0];\n\t\t\tclgame.palette[i].g = pic->palette[i*4+1];\n\t\t\tclgame.palette[i].b = pic->palette[i*4+2];\n\t\t}\n\t\tFS_FreeImage( pic );\n\t}\n\telse\n\t{\n\t\t// someone deleted internal palette from code...\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\tclgame.palette[i].r = i;\n\t\t\tclgame.palette[i].g = i;\n\t\t\tclgame.palette[i].b = i;\n\t\t}\n\t}\n}\n\nint SCR_LoadPauseIcon( void )\n{\n\tint texnum = 0;\n\n\tif( FS_FileExists( \"gfx/paused.lmp\", false ))\n\t\ttexnum = ref.dllFuncs.GL_LoadTexture( \"gfx/paused.lmp\", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST );\n\telse if( FS_FileExists( \"gfx/pause.lmp\", false ))\n\t\ttexnum = ref.dllFuncs.GL_LoadTexture( \"gfx/pause.lmp\", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST );\n\n\treturn texnum ? texnum : -1;\n}\n\n/*\n================\nSCR_RegisterTextures\n\nINTERNAL RESOURCE\n================\n*/\nvoid SCR_RegisterTextures( void )\n{\n\t// register gfx.wad images\n\tif( FS_FileExists( \"gfx/lambda.lmp\", false ))\n\t{\n\t\tif( cl_allow_levelshots.value )\n\t\t\tcls.loadingBar = ref.dllFuncs.GL_LoadTexture( \"gfx/lambda.lmp\", NULL, 0, TF_IMAGE|TF_LUMINANCE|TF_ALLOW_NEAREST );\n\t\telse cls.loadingBar = ref.dllFuncs.GL_LoadTexture( \"gfx/lambda.lmp\", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST );\n\t}\n\telse if( FS_FileExists( \"gfx/loading.lmp\", false ))\n\t{\n\t\tif( cl_allow_levelshots.value )\n\t\t\tcls.loadingBar = ref.dllFuncs.GL_LoadTexture( \"gfx/loading.lmp\", NULL, 0, TF_IMAGE|TF_LUMINANCE|TF_ALLOW_NEAREST );\n\t\telse cls.loadingBar = ref.dllFuncs.GL_LoadTexture( \"gfx/loading.lmp\", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST );\n\t}\n\n}\n\n/*\n=================\nSCR_SizeUp_f\n\nKeybinding command\n=================\n*/\nstatic void SCR_SizeUp_f( void )\n{\n\tCvar_SetValue( \"viewsize\", Q_min( scr_viewsize.value + 10, 120 ));\n}\n\n\n/*\n=================\nSCR_SizeDown_f\n\nKeybinding command\n=================\n*/\nstatic void SCR_SizeDown_f( void )\n{\n\tCvar_SetValue( \"viewsize\", Q_max( scr_viewsize.value - 10, 30 ));\n}\n\n/*\n==================\nSCR_VidInit\n==================\n*/\nvoid SCR_VidInit( void )\n{\n\tif( !ref.initialized ) // don't call VidInit too soon\n\t\treturn;\n\n\tmemset( &clgame.ds, 0, sizeof( clgame.ds )); // reset a draw state\n\tmemset( &gameui.ds, 0, sizeof( gameui.ds )); // reset a draw state\n\tmemset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint ));\n\n\t// update screen sizes for menu\n\tif( gameui.globals )\n\t{\n\t\tgameui.globals->scrWidth = refState.width;\n\t\tgameui.globals->scrHeight = refState.height;\n\t}\n\n\t// notify vgui about screen size change\n\tif( clgame.hInstance )\n\t{\n\t\tVGui_Startup( refState.width, refState.height );\n\t}\n\n\tCL_ClearSpriteTextures(); // now all hud sprites are invalid\n\n\t// vid_state has changed\n\tif( gameui.hInstance ) gameui.dllFuncs.pfnVidInit();\n\tif( clgame.hInstance ) clgame.dllFuncs.pfnVidInit();\n\n\t// restart console size\n\tCon_VidInit ();\n\tTouch_NotifyResize();\n}\n\n/*\n==================\nSCR_Init\n==================\n*/\nvoid SCR_Init( void )\n{\n\tif( scr_init ) return;\n\n\tCvar_RegisterVariable( &scr_centertime );\n\tCvar_RegisterVariable( &cl_levelshot_name );\n\tCvar_RegisterVariable( &cl_allow_levelshots );\n\tCvar_RegisterVariable( &scr_loading );\n\tCvar_RegisterVariable( &scr_download );\n\tCvar_RegisterVariable( &cl_testlights );\n\tCvar_RegisterVariable( &cl_envshot_size );\n\tCvar_RegisterVariable( &v_dark );\n\tCvar_RegisterVariable( &scr_viewsize );\n\tCvar_RegisterVariable( &net_speeds );\n\tCvar_RegisterVariable( &cl_showfps );\n\tCvar_RegisterVariable( &cl_showpos );\n\tCvar_RegisterVariable( &cl_showcmd );\n#ifdef _DEBUG\n\tCvar_RegisterVariable( &cl_showents );\n#endif // NDEBUG\n\n\t// register our commands\n\tCmd_AddCommand( \"skyname\", CL_SetSky_f, \"set new skybox by basename\" );\n\tCmd_AddCommand( \"loadsky\", CL_SetSky_f, \"set new skybox by basename\" );\n\tCmd_AddCommand( \"viewpos\", SCR_Viewpos_f, \"prints current player origin\" );\n\tCmd_AddCommand( \"sizeup\", SCR_SizeUp_f, \"screen size up to 10 points\" );\n\tCmd_AddCommand( \"sizedown\", SCR_SizeDown_f, \"screen size down to 10 points\" );\n\n\tif( !UI_LoadProgs( ))\n\t{\n\t\tCon_Printf( S_ERROR \"can't initialize gameui DLL: %s\\n\", COM_GetLibraryError() ); // there is non fatal for us\n\t\thost.allow_console = true; // we need console, because menu is missing\n\t}\n\n\tSCR_VidInit();\n\tSCR_LoadCreditsFont ();\n\tSCR_RegisterTextures ();\n\tSCR_InstallParticlePalette ();\n\tSCR_InitCinematic();\n\tCL_InitNetgraph();\n\n\tif( host.allow_console && Sys_CheckParm( \"-toconsole\" ))\n\t\tCbuf_AddText( \"toggleconsole\\n\" );\n\telse UI_SetActiveMenu( true );\n\n\tscr_init = true;\n}\n\nvoid SCR_Shutdown( void )\n{\n\tif( !scr_init ) return;\n\n\tCmd_RemoveCommand( \"skyname\" );\n\tCmd_RemoveCommand( \"viewpos\" );\n\tUI_SetActiveMenu( false );\n\tUI_UnloadProgs();\n\n\tscr_init = false;\n}\n"
  },
  {
    "path": "engine/client/cl_securedstub.c",
    "content": "/*\ncl_securedstub.c - secured client dll stub\nCopyright (C) 2022 FWGS\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n\ntypedef struct cldll_func_src_s\n{\n\tint\t(*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion );\n\tvoid\t(*pfnInit)( void );\n\tint\t(*pfnVidInit)( void );\n\tint\t(*pfnRedraw)( float flTime, int intermission );\n\tint\t(*pfnUpdateClientData)( client_data_t *cdata, float flTime );\n\tvoid\t(*pfnReset)( void );\n\tvoid\t(*pfnPlayerMove)( struct playermove_s *ppmove, int server );\n\tvoid\t(*pfnPlayerMoveInit)( struct playermove_s *ppmove );\n\tchar\t(*pfnPlayerMoveTexture)( char *name );\n\tvoid\t(*IN_ActivateMouse)( void );\n\tvoid\t(*IN_DeactivateMouse)( void );\n\tvoid\t(*IN_MouseEvent)( int mstate );\n\tvoid\t(*IN_ClearStates)( void );\n\tvoid\t(*IN_Accumulate)( void );\n\tvoid\t(*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active );\n\tint\t(*CL_IsThirdPerson)( void );\n\tvoid\t(*CL_CameraOffset)( float *ofs );\t// unused\n\tvoid\t*(*KB_Find)( const char *name );\n\tvoid\t(*CAM_Think)( void );\t\t// camera stuff\n\tvoid\t(*pfnCalcRefdef)( ref_params_t *pparams );\n\tint\t(*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname );\n\tvoid\t(*pfnCreateEntities)( void );\n\tvoid\t(*pfnDrawNormalTriangles)( void );\n\tvoid\t(*pfnDrawTransparentTriangles)( void );\n\tvoid\t(*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity );\n\tvoid\t(*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed );\n\tvoid\t(*pfnShutdown)( void );\n\tvoid\t(*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client );\n\tvoid\t(*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src );\n\tvoid\t(*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd );\n\tvoid\t(*pfnDemo_ReadBuffer)( int size, byte *buffer );\n\tint\t(*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size );\n\tint\t(*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs );\n\tvoid\t(*pfnFrame)( double time );\n\tint\t(*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding );\n\tvoid\t(*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ));\n\tcl_entity_t *(*pfnGetUserEntity)( int index );\n\tvoid\t(*pfnVoiceStatus)( int entindex, qboolean bTalking );\n\tvoid\t(*pfnDirectorMessage)( int iSize, void *pbuf );\n\tint\t(*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio );\n\tvoid\t(*pfnChatInputPosition)( int *x, int *y );\n\tint\t(*pfnGetPlayerTeam)( int iPlayer );\n\tvoid\t*(*pfnClientFactory)( void );\n} cldll_func_src_t;\n\ntypedef struct cldll_func_dst_s\n{\n\tvoid\t(*pfnInitialize)( cl_enginefunc_t **pEnginefuncs, int *iVersion );\n\tvoid\t(*pfnInit)( void );\n\tvoid\t(*pfnVidInit)( void );\n\tvoid\t(*pfnRedraw)( float *flTime, int *intermission );\n\tvoid\t(*pfnUpdateClientData)( client_data_t **cdata, float *flTime );\n\tvoid\t(*pfnReset)( void );\n\tvoid\t(*pfnPlayerMove)( struct playermove_s **ppmove, int *server );\n\tvoid\t(*pfnPlayerMoveInit)( struct playermove_s **ppmove );\n\tvoid\t(*pfnPlayerMoveTexture)( char **name );\n\tvoid\t(*IN_ActivateMouse)( void );\n\tvoid\t(*IN_DeactivateMouse)( void );\n\tvoid\t(*IN_MouseEvent)( int *mstate );\n\tvoid\t(*IN_ClearStates)( void );\n\tvoid\t(*IN_Accumulate)( void );\n\tvoid\t(*CL_CreateMove)( float *frametime, struct usercmd_s **cmd, int *active );\n\tvoid\t(*CL_IsThirdPerson)( void );\n\tvoid\t(*CL_CameraOffset)( float **ofs );\n\tvoid\t(*KB_Find)( const char **name );\n\tvoid\t(*CAM_Think)( void );\n\tvoid\t(*pfnCalcRefdef)( ref_params_t **pparams );\n\tvoid\t(*pfnAddEntity)( int *type, cl_entity_t **ent, const char **modelname );\n\tvoid\t(*pfnCreateEntities)( void );\n\tvoid\t(*pfnDrawNormalTriangles)( void );\n\tvoid\t(*pfnDrawTransparentTriangles)( void );\n\tvoid\t(*pfnStudioEvent)( const struct mstudioevent_s **event, const cl_entity_t **entity );\n\tvoid\t(*pfnPostRunCmd)( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed );\n\tvoid\t(*pfnShutdown)( void );\n\tvoid\t(*pfnTxferLocalOverrides)( entity_state_t **state, const clientdata_t **client );\n\tvoid\t(*pfnProcessPlayerState)( entity_state_t **dst, const entity_state_t **src );\n\tvoid\t(*pfnTxferPredictionData)( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd );\n\tvoid\t(*pfnDemo_ReadBuffer)( int *size, byte **buffer );\n\tvoid\t(*pfnConnectionlessPacket)( const struct netadr_s **net_from, const char **args, char **buffer, int **size );\n\tvoid\t(*pfnGetHullBounds)( int *hullnumber, float **mins, float **maxs );\n\tvoid\t(*pfnFrame)( double *time );\n\tvoid\t(*pfnKey_Event)( int *eventcode, int *keynum, const char **pszCurrentBinding );\n\tvoid\t(*pfnTempEntUpdate)( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ));\n\tvoid\t(*pfnGetUserEntity)( int *index );\n\tvoid\t(*pfnVoiceStatus)( int *entindex, qboolean *bTalking );\n\tvoid\t(*pfnDirectorMessage)( int *iSize, void **pbuf );\n\tvoid\t(*pfnGetStudioModelInterface)( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio );\n\tvoid\t(*pfnChatInputPosition)( int **x, int **y );\n\tvoid\t(*pfnGetPlayerTeam)( int *iPlayer );\n} cldll_func_dst_t;\n\nstruct cl_enginefunc_dst_s;\nstruct modshelpers_s;\nstruct modchelpers_s;\nstruct engdata_s;\n\ntypedef struct modfuncs_s\n{\n\tvoid\t(*m_pfnLoadMod)( char *pchModule );\n\tvoid\t(*m_pfnCloseMod)( void );\n\tint\t(*m_pfnNCall)( int ijump, int cnArg, ... );\n\tvoid\t(*m_pfnGetClDstAddrs)( cldll_func_dst_t *pcldstAddrs );\n\tvoid\t(*m_pfnGetEngDstAddrs)( struct cl_enginefunc_dst_s *pengdstAddrs );\n\tvoid\t(*m_pfnModuleLoaded)( void );\n\tvoid\t(*m_pfnProcessOutgoingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf );\n\tqboolean\t(*m_pfnProcessIncomingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf );\n\tvoid\t(*m_pfnTextureLoad)( char *pszName, int dxWidth, int dyHeight, char *pbData );\n\tvoid\t(*m_pfnModelLoad)( struct model_s *pmodel, void *pvBuf );\n\tvoid\t(*m_pfnFrameBegin)( void );\n\tvoid\t(*m_pfnFrameRender1)( void );\n\tvoid\t(*m_pfnFrameRender2)( void );\n\tvoid\t(*m_pfnSetModSHelpers)( struct modshelpers_s *pmodshelpers );\n\tvoid\t(*m_pfnSetModCHelpers)( struct modchelpers_s *pmodchelpers );\n\tvoid\t(*m_pfnSetEngData)( struct engdata_s *pengdata );\n\tint\tm_nVersion;\n\tvoid\t(*m_pfnConnectClient)( int iPlayer );\n\tvoid\t(*m_pfnRecordIP)( unsigned int pnIP );\n\tvoid\t(*m_pfnPlayerStatus)( unsigned char *pbData, int cbData );\n\tvoid\t(*m_pfnSetEngineVersion)( int nVersion );\n\tint\tm_nVoid2;\n\tint\tm_nVoid3;\n\tint\tm_nVoid4;\n\tint\tm_nVoid5;\n\tint\tm_nVoid6;\n\tint\tm_nVoid7;\n\tint\tm_nVoid8;\n\tint\tm_nVoid9;\n} modfuncs_t;\n\nstatic void DstInitialize( cl_enginefunc_t **pEnginefuncs, int *iVersion )\n{\n    // stub\n}\n\nstatic void DstInit( void )\n{\n    // stub\n}\n\nstatic void DstVidInit( void )\n{\n    // stub\n}\n\nstatic void DstRedraw( float *flTime, int *intermission )\n{\n    // stub\n}\n\nstatic void DstUpdateClientData( client_data_t **cdata, float *flTime )\n{\n    // stub\n}\n\nstatic void DstReset( void )\n{\n    // stub\n}\n\nstatic void DstPlayerMove( struct playermove_s **ppmove, int *server )\n{\n    // stub\n}\n\nstatic void DstPlayerMoveInit( struct playermove_s **ppmove )\n{\n    // stub\n}\n\nstatic void DstPlayerMoveTexture( char **name )\n{\n    // stub\n}\n\nstatic void DstIN_ActivateMouse( void )\n{\n    // stub\n}\n\nstatic void DstIN_DeactivateMouse( void )\n{\n    // stub\n}\n\nstatic void DstIN_MouseEvent( int *mstate )\n{\n    // stub\n}\n\nstatic void DstIN_ClearStates( void )\n{\n    // stub\n}\n\nstatic void DstIN_Accumulate( void )\n{\n    // stub\n}\n\nstatic void DstCL_CreateMove( float *frametime, struct usercmd_s **cmd, int *active )\n{\n    // stub\n}\n\nstatic void DstCL_IsThirdPerson( void )\n{\n    // stub\n}\n\nstatic void DstCL_CameraOffset( float **ofs )\n{\n    // stub\n}\n\nstatic void DstKB_Find( const char **name )\n{\n    // stub\n}\n\nstatic void DstCAM_Think( void )\n{\n    // stub\n}\n\nstatic void DstCalcRefdef( ref_params_t **pparams )\n{\n    // stub\n}\n\nstatic void DstAddEntity( int *type, cl_entity_t **ent, const char **modelname )\n{\n    // stub\n}\n\nstatic void DstCreateEntities( void )\n{\n    // stub\n}\n\nstatic void DstDrawNormalTriangles( void )\n{\n    // stub\n}\n\nstatic void DstDrawTransparentTriangles( void )\n{\n    // stub\n}\n\nstatic void DstStudioEvent( const struct mstudioevent_s **event, const cl_entity_t **entity )\n{\n    // stub\n}\n\nstatic void DstPostRunCmd( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed )\n{\n    // stub\n}\n\nstatic void DstShutdown( void )\n{\n    // stub\n}\n\nstatic void DstTxferLocalOverrides( entity_state_t **state, const clientdata_t **client )\n{\n    // stub\n}\n\nstatic void DstProcessPlayerState( entity_state_t **dst, const entity_state_t **src )\n{\n    // stub\n}\n\nstatic void DstTxferPredictionData( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd )\n{\n    // stub\n}\n\nstatic void DstDemo_ReadBuffer( int *size, byte **buffer )\n{\n    // stub\n}\n\nstatic void DstConnectionlessPacket( const struct netadr_s **net_from, const char **args, char **buffer, int **size )\n{\n    // stub\n}\n\nstatic void DstGetHullBounds( int *hullnumber, float **mins, float **maxs )\n{\n    // stub\n}\n\nstatic void DstFrame( double *time )\n{\n    // stub\n}\n\nstatic void DstKey_Event( int *eventcode, int *keynum, const char **pszCurrentBinding )\n{\n    // stub\n}\n\nstatic void DstTempEntUpdate( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) )\n{\n    // stub\n}\n\nstatic void DstGetUserEntity( int *index )\n{\n    // stub\n}\n\nstatic void DstVoiceStatus( int *entindex, qboolean *bTalking )\n{\n    // stub\n}\n\nstatic void DstDirectorMessage( int *iSize, void **pbuf )\n{\n    // stub\n}\n\nstatic void DstGetStudioModelInterface( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio )\n{\n    // stub\n}\n\nstatic void DstChatInputPosition( int **x, int **y )\n{\n    // stub\n}\n\nstatic void DstGetPlayerTeam( int *iPlayer )\n{\n    // stub\n}\n\nstatic cldll_func_dst_t cldllFuncDst =\n{\n\tDstInitialize,\n\tDstInit,\n\tDstVidInit,\n\tDstRedraw,\n\tDstUpdateClientData,\n\tDstReset,\n\tDstPlayerMove,\n\tDstPlayerMoveInit,\n\tDstPlayerMoveTexture,\n\tDstIN_ActivateMouse,\n\tDstIN_DeactivateMouse,\n\tDstIN_MouseEvent,\n\tDstIN_ClearStates,\n\tDstIN_Accumulate,\n\tDstCL_CreateMove,\n\tDstCL_IsThirdPerson,\n\tDstCL_CameraOffset,\n\tDstKB_Find,\n\tDstCAM_Think,\n\tDstCalcRefdef,\n\tDstAddEntity,\n\tDstCreateEntities,\n\tDstDrawNormalTriangles,\n\tDstDrawTransparentTriangles,\n\tDstStudioEvent,\n\tDstPostRunCmd,\n\tDstShutdown,\n\tDstTxferLocalOverrides,\n\tDstProcessPlayerState,\n\tDstTxferPredictionData,\n\tDstDemo_ReadBuffer,\n\tDstConnectionlessPacket,\n\tDstGetHullBounds,\n\tDstFrame,\n\tDstKey_Event,\n\tDstTempEntUpdate,\n\tDstGetUserEntity,\n\tDstVoiceStatus,\n\tDstDirectorMessage,\n\tDstGetStudioModelInterface,\n\tDstChatInputPosition,\n\tDstGetPlayerTeam,\n};\n\nvoid CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F )\n{\n\tmodfuncs_t modFuncs = { 0 };\n\n\t// secured client dlls need these\n\tcldll_func_src_t cldllFuncSrc =\n\t{\n\t\t(void *)&modFuncs,\n\t\tNULL,\n\t\t(void *)&cldllFuncDst\n\t};\n\n\t// trying to fill interface now\n\tF( &cldllFuncSrc );\n\n\t// map exports to xash's cldll_func_t\n\tclgame.dllFuncs.pfnInitialize = cldllFuncSrc.pfnInitialize;\n\tclgame.dllFuncs.pfnInit = cldllFuncSrc.pfnInit;\n\tclgame.dllFuncs.pfnVidInit = cldllFuncSrc.pfnVidInit;\n\tclgame.dllFuncs.pfnRedraw = cldllFuncSrc.pfnRedraw;\n\tclgame.dllFuncs.pfnUpdateClientData = cldllFuncSrc.pfnUpdateClientData;\n\tclgame.dllFuncs.pfnReset = cldllFuncSrc.pfnReset;\n\tclgame.dllFuncs.pfnPlayerMove = cldllFuncSrc.pfnPlayerMove;\n\tclgame.dllFuncs.pfnPlayerMoveInit = cldllFuncSrc.pfnPlayerMoveInit;\n\tclgame.dllFuncs.pfnPlayerMoveTexture = cldllFuncSrc.pfnPlayerMoveTexture;\n\tclgame.dllFuncs.IN_ActivateMouse = cldllFuncSrc.IN_ActivateMouse;\n\tclgame.dllFuncs.IN_DeactivateMouse = cldllFuncSrc.IN_DeactivateMouse;\n\tclgame.dllFuncs.IN_MouseEvent = cldllFuncSrc.IN_MouseEvent;\n\tclgame.dllFuncs.IN_ClearStates = cldllFuncSrc.IN_ClearStates;\n\tclgame.dllFuncs.IN_Accumulate = cldllFuncSrc.IN_Accumulate;\n\tclgame.dllFuncs.CL_CreateMove = cldllFuncSrc.CL_CreateMove;\n\tclgame.dllFuncs.CL_IsThirdPerson = cldllFuncSrc.CL_IsThirdPerson;\n\tclgame.dllFuncs.CL_CameraOffset = cldllFuncSrc.CL_CameraOffset;\n\tclgame.dllFuncs.KB_Find = cldllFuncSrc.KB_Find;\n\tclgame.dllFuncs.CAM_Think = cldllFuncSrc.CAM_Think;\n\tclgame.dllFuncs.pfnCalcRefdef = cldllFuncSrc.pfnCalcRefdef;\n\tclgame.dllFuncs.pfnAddEntity = cldllFuncSrc.pfnAddEntity;\n\tclgame.dllFuncs.pfnCreateEntities = cldllFuncSrc.pfnCreateEntities;\n\tclgame.dllFuncs.pfnDrawNormalTriangles = cldllFuncSrc.pfnDrawNormalTriangles;\n\tclgame.dllFuncs.pfnDrawTransparentTriangles = cldllFuncSrc.pfnDrawTransparentTriangles;\n\tclgame.dllFuncs.pfnStudioEvent = cldllFuncSrc.pfnStudioEvent;\n\tclgame.dllFuncs.pfnPostRunCmd = cldllFuncSrc.pfnPostRunCmd;\n\tclgame.dllFuncs.pfnShutdown = cldllFuncSrc.pfnShutdown;\n\tclgame.dllFuncs.pfnTxferLocalOverrides = cldllFuncSrc.pfnTxferLocalOverrides;\n\tclgame.dllFuncs.pfnProcessPlayerState = cldllFuncSrc.pfnProcessPlayerState;\n\tclgame.dllFuncs.pfnTxferPredictionData = cldllFuncSrc.pfnTxferPredictionData;\n\tclgame.dllFuncs.pfnDemo_ReadBuffer = cldllFuncSrc.pfnDemo_ReadBuffer;\n\tclgame.dllFuncs.pfnConnectionlessPacket = cldllFuncSrc.pfnConnectionlessPacket;\n\tclgame.dllFuncs.pfnGetHullBounds = cldllFuncSrc.pfnGetHullBounds;\n\tclgame.dllFuncs.pfnFrame = cldllFuncSrc.pfnFrame;\n\tclgame.dllFuncs.pfnKey_Event = cldllFuncSrc.pfnKey_Event;\n\tclgame.dllFuncs.pfnTempEntUpdate = cldllFuncSrc.pfnTempEntUpdate;\n\tclgame.dllFuncs.pfnGetUserEntity = cldllFuncSrc.pfnGetUserEntity;\n\tclgame.dllFuncs.pfnVoiceStatus = cldllFuncSrc.pfnVoiceStatus;\n\tclgame.dllFuncs.pfnDirectorMessage = cldllFuncSrc.pfnDirectorMessage;\n\tclgame.dllFuncs.pfnGetStudioModelInterface = cldllFuncSrc.pfnGetStudioModelInterface;\n\tclgame.dllFuncs.pfnChatInputPosition = cldllFuncSrc.pfnChatInputPosition;\n}\n"
  },
  {
    "path": "engine/client/cl_spray.c",
    "content": "/*\ncl_spray.c - spray conversion for GoldSrc protocol\nCopyright (C) 2025 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"imagelib.h\"\n\n#define SPRAY_MAX_SURFACE     12228\n#define SPRAY_PALETTE_SIZE    256\n#define SPRAY_PALETTE_BYTES   ( SPRAY_PALETTE_SIZE * 3 )\n#define SPRAY_ALPHA_THRESHOLD ( SPRAY_PALETTE_SIZE / 2 )\n#define SPRAY_FILENAME        \"tempdecal.wad\"\n\n// adjusts spray dimensions\nstatic void CL_AdjustSprayDimensions( int *width, int *height )\n{\n\tfloat aspect = (float)( *width ) / (float)( *height );\n\tint   h, w;\n\tfor( h = (( *height ) / 16 ) * 16; h >= 16; h -= 16 )\n\t{\n\t\tw = ((int)( h * aspect ) / 16 ) * 16;\n\t\tif( w < 16 || w > *width )\n\t\t\tcontinue;\n\n\t\tif( w * h < SPRAY_MAX_SURFACE )\n\t\t{\n\t\t\t*width = w;\n\t\t\t*height = h;\n\t\t\treturn;\n\t\t}\n\t}\n\t// fallback: minimal size\n\t*width = 16;\n\t*height = 16;\n}\n\n// loads and prepares the image\nstatic rgbdata_t *CL_LoadAndPrepareImage( const char *filename, int *width, int *height )\n{\n\trgbdata_t *image = NULL;\n\n\tImage_SetForceFlags( IL_KEEP_8BIT );\n\timage = FS_LoadImage( filename, NULL, 0 );\n\n\tif( !image )\n\t\treturn NULL;\n\n\t*width = image->width;\n\t*height = image->height;\n\tCL_AdjustSprayDimensions( width, height );\n\n\tif( *width != image->width || *height != image->height )\n\t{\n\t\tconst int bpp = PFDesc[image->type].bpp;\n\t\tconst int palette_size = 256 * ( image->type == PF_INDEXED_32 ? 4 : 3 );\n\t\trgbdata_t *scaled;\n\t\tqboolean resampled;\n\n\t\t// resample image to fit spray size constraints\n\t\tbyte *resampled_buf = Image_ResampleInternal(\n\t\t\timage->buffer, image->width, image->height,\n\t\t\t*width, *height, image->type, &resampled\n\t\t\t);\n\n\t\tif( !resampled_buf )\n\t\t{\n\t\t\tFS_FreeImage( image );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tscaled = Mem_Malloc( host.imagepool, sizeof( *scaled ));\n\t\t*scaled = *image;\n\t\tscaled->width = *width;\n\t\tscaled->height = *height;\n\t\tscaled->size = *width * *height * bpp;\n\t\tscaled->buffer = Mem_Malloc( host.imagepool, scaled->size );\n\t\tmemcpy( scaled->buffer, resampled_buf, scaled->size );\n\n\t\tif( image->palette )\n\t\t{\n\t\t\t// copy 8-bit palette for resampled bmp\n\t\t\tscaled->palette = Mem_Malloc( host.imagepool, palette_size );\n\t\t\tmemcpy( scaled->palette, image->palette, palette_size );\n\t\t}\n\n\t\tFS_FreeImage( image );\n\t\timage = scaled;\n\t}\n\n\treturn image;\n}\n\n// converts an image to WAD3 spray or miptex format\nqboolean CL_ConvertImageToWAD3( const char *filename )\n{\n\tqboolean\tis_indexed_img;\n\tint\t\t\twidth = 0, height = 0;\n\tint\t\t\ti;\n\tbyte\t\tpalette[SPRAY_PALETTE_BYTES];\n\tbyte\t\t*indexed = NULL;\n\trgbdata_t\t*image = NULL;\n\trgbdata_t\t*quant = NULL;\n\trgbdata_t\ttemp_image = {0};\n\n\timage = CL_LoadAndPrepareImage( filename, &width, &height );\n\tif( !image )\n\t\treturn false;\n\n\tis_indexed_img = image->palette != NULL;\n\tif( is_indexed_img )\n\t{\n\t\t// copy bmp palette from rgba to rgb\n\t\tfor( i = 0; i < 256; ++i )\n\t\t{\n\t\t\tpalette[i * 3 + 0] = image->palette[i * 4 + 0]; // R\n\t\t\tpalette[i * 3 + 1] = image->palette[i * 4 + 1]; // G\n\t\t\tpalette[i * 3 + 2] = image->palette[i * 4 + 2]; // B\n\t\t}\n\t\tindexed = image->buffer;\n\t}\n\telse\n\t{\n\t\tquant = Mem_Malloc( host.imagepool, sizeof( *quant ));\n\t\t*quant = *image;\n\t\tquant->buffer = Mem_Malloc( host.imagepool, quant->size );\n\t\tmemcpy( quant->buffer, image->buffer, quant->size );\n\t\tImage_Quantize( quant ); // it's so weird, it writes result to same structure as used for input data\n\n\t\tif( !quant || !quant->buffer || !quant->palette )\n\t\t\tgoto cleanup;\n\n\t\t// set index 255 for transparent pixels in rgba images\n\t\tif( image->type == PF_RGBA_32 )\n\t\t{\n\t\t\tfor( i = 0; i < width * height; ++i )\n\t\t\t{\n\t\t\t\tif( image->buffer[i * 4 + 3] <= SPRAY_ALPHA_THRESHOLD )\n\t\t\t\t\tquant->buffer[i] = 255;\n\t\t\t}\n\t\t}\n\n\t\tquant->palette[255 * 3 + 0] = 0;\n\t\tquant->palette[255 * 3 + 1] = 0;\n\t\tquant->palette[255 * 3 + 2] = 255;\n\t\tmemcpy( palette, quant->palette, SPRAY_PALETTE_BYTES );\n\t\tindexed = quant->buffer;\n\t}\n\n\ttemp_image.width = width;\n\ttemp_image.height = height;\n\ttemp_image.type = PF_INDEXED_32;\n\ttemp_image.buffer = indexed;\n\ttemp_image.size = width * height;\n\ttemp_image.palette = palette;\n\n\tif( is_indexed_img )\n\t\ttemp_image.flags |= IMAGE_GRADIENT_DECAL;\n\n\treturn FS_SaveImage( SPRAY_FILENAME, &temp_image );\n\ncleanup:\n\tif( image )\n\t\tFS_FreeImage( image );\n\tif( quant )\n\t\tFS_FreeImage( quant );\n\treturn false;\n}\n"
  },
  {
    "path": "engine/client/cl_tent.c",
    "content": "/*\ncl_tent.c - temp entity effects management\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"r_efx.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"cl_tent.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n#include \"wadfile.h\"\t// acess decal size\n#include \"sound.h\"\n\n/*\n==============================================================\n\nTEMPENTS MANAGEMENT\n\n==============================================================\n*/\n#define FLASHLIGHT_DISTANCE\t\t2000\t// in units\n#define SHARD_VOLUME\t\t12.0f\t// on shard ever n^3 units\n#define MAX_MUZZLEFLASH\t\t3\n\nstatic TEMPENTITY *cl_active_tents;\nstatic TEMPENTITY *cl_free_tents;\nstatic TEMPENTITY *cl_tempents = NULL;\t\t// entities pool\n\nstatic model_t *cl_sprite_muzzleflash[MAX_MUZZLEFLASH];\t// muzzle flashes\nstatic model_t *cl_sprite_ricochet = NULL;\nstatic model_t *cl_sprite_glow = NULL;\nmodel_t        *cl_sprite_dot = NULL;\nmodel_t        *cl_sprite_shell = NULL;\n\nstatic const char *const cl_default_sprites[] =\n{\n\t// built-in sprites\n\t\"sprites/muzzleflash1.spr\",\n\t\"sprites/muzzleflash2.spr\",\n\t\"sprites/muzzleflash3.spr\",\n\t\"sprites/dot.spr\",\n\t\"sprites/animglow01.spr\",\n\t\"sprites/richo1.spr\",\n\t\"sprites/shellchrome.spr\",\n};\n\nstatic void CL_PlayerDecal( int playerIndex, int textureIndex, int entityIndex, float *pos );\n\n/*\n================\nCL_LoadClientSprites\n\nINTERNAL RESOURCE\n================\n*/\nvoid CL_LoadClientSprites( void )\n{\n\tcl_sprite_muzzleflash[0] = CL_LoadClientSprite( cl_default_sprites[0] );\n\tcl_sprite_muzzleflash[1] = CL_LoadClientSprite( cl_default_sprites[1] );\n\tcl_sprite_muzzleflash[2] = CL_LoadClientSprite( cl_default_sprites[2] );\n\n\tcl_sprite_dot = CL_LoadClientSprite( cl_default_sprites[3] );\n\tcl_sprite_glow = CL_LoadClientSprite( cl_default_sprites[4] );\n\tcl_sprite_ricochet = CL_LoadClientSprite( cl_default_sprites[5] );\n\tcl_sprite_shell = CL_LoadClientSprite( cl_default_sprites[6] );\n}\n\n/*\n================\nCL_AddClientResource\n\nadd client-side resource to list\n================\n*/\nvoid CL_AddClientResource( const char *filename, int type )\n{\n\tresource_t\t*p, *pResource;\n\n\tfor( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )\n\t{\n\t\tif( !Q_stricmp( p->szFileName, filename ))\n\t\t\tbreak;\n\t}\n\n\tif( p != &cl.resourcesneeded )\n\t\treturn; // already in list?\n\n\tpResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));\n\n\tQ_strncpy( pResource->szFileName, filename, sizeof( pResource->szFileName ));\n\tpResource->type = type;\n\tpResource->nIndex = -1; // client resource marker\n\tpResource->nDownloadSize = 1;\n\tpResource->ucFlags |= RES_WASMISSING;\n\n\tCL_AddToResourceList( pResource, &cl.resourcesneeded );\n}\n\n/*\n================\nCL_AddClientResources\n\nclient resources not precached by server\n================\n*/\nvoid CL_AddClientResources( void )\n{\n\tconst char *snd;\n\tchar\tfilepath[MAX_QPATH];\n\tint\ti;\n\n\t// don't request resources from localhost or in quake-compatibility mode\n\tif( cl.maxclients <= 1 || Host_IsQuakeCompatible( ))\n\t\treturn;\n\n\t// check sprites first\n\tfor( i = 0; i < ARRAYSIZE( cl_default_sprites ); i++ )\n\t{\n\t\tif( !FS_FileExists( cl_default_sprites[i], false ))\n\t\t\tCL_AddClientResource( cl_default_sprites[i], t_model );\n\t}\n\n\t// then check sounds\n\tfor( i = 0; ( snd = SoundList_Get( BouncePlayerShell, i )); i++ )\n\t{\n\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", snd );\n\n\t\tif( !FS_FileExists( filepath, false ))\n\t\t\tCL_AddClientResource( snd, t_sound );\n\t}\n\n\tfor( i = 0; ( snd = SoundList_Get( BounceWeaponShell, i )); i++ )\n\t{\n\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", snd );\n\n\t\tif( !FS_FileExists( filepath, false ))\n\t\t\tCL_AddClientResource( snd, t_sound );\n\t}\n\n\tfor( i = 0; ( snd = SoundList_Get( Explode, i )); i++ )\n\t{\n\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", snd );\n\n\t\tif( !FS_FileExists( filepath, false ))\n\t\t\tCL_AddClientResource( snd, t_sound );\n\t}\n\n#if 0\t// ric sounds was precached by server-side\n\tfor( i = 0; ( snd = SoundList_Get( Ricochet, i )); i++ )\n\t{\n\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", snd );\n\n\t\tif( !FS_FileExists( filepath, false ))\n\t\t\tCL_AddClientResource( snd, t_sound );\n\t}\n#endif\n}\n\n/*\n================\nCL_ClearTempEnts\n\n================\n*/\nstatic void CL_ClearTempEnts( void )\n{\n\tint\ti;\n\n\tif( !cl_tempents ) return;\n\n\tfor( i = 0; i < GI->max_tents - 1; i++ )\n\t{\n\t\tcl_tempents[i].next = &cl_tempents[i+1];\n\t\tcl_tempents[i].entity.trivial_accept = INVALID_HANDLE;\n\t}\n\n\tcl_tempents[GI->max_tents-1].next = NULL;\n\tcl_free_tents = cl_tempents;\n\tcl_active_tents = NULL;\n}\n\n/*\n================\nCL_InitTempents\n\n================\n*/\nvoid CL_InitTempEnts( void )\n{\n\tcl_tempents = Mem_Calloc( cls.mempool, sizeof( TEMPENTITY ) * GI->max_tents );\n\tCL_ClearTempEnts();\n\n\t// load tempent sprites (glowshell, muzzleflashes etc)\n\tCL_LoadClientSprites ();\n}\n\n/*\n================\nCL_FreeTempEnts\n\n================\n*/\nvoid CL_FreeTempEnts( void )\n{\n\tif( cl_tempents )\n\t\tMem_Free( cl_tempents );\n\tcl_tempents = NULL;\n}\n\n/*\n==============\nCL_PrepareTEnt\n\nset default values\n==============\n*/\nstatic void CL_PrepareTEnt( TEMPENTITY *pTemp, model_t *pmodel )\n{\n\tint\tframeCount = 0;\n\tint\tmodelIndex = 0;\n\tint\tmodelHandle = pTemp->entity.trivial_accept;\n\n\tmemset( pTemp, 0, sizeof( *pTemp ));\n\n\t// use these to set per-frame and termination conditions / actions\n\tpTemp->entity.trivial_accept = modelHandle; // keep unchanged\n\tpTemp->flags = FTENT_NONE;\n\tpTemp->die = cl.time + 0.75f;\n\n\tif( pmodel ) frameCount = pmodel->numframes;\n\telse pTemp->flags |= FTENT_NOMODEL;\n\n\tpTemp->entity.curstate.modelindex = modelIndex;\n\tpTemp->entity.curstate.rendermode = kRenderNormal;\n\tpTemp->entity.curstate.renderfx = kRenderFxNone;\n\tpTemp->entity.curstate.rendercolor.r = 255;\n\tpTemp->entity.curstate.rendercolor.g = 255;\n\tpTemp->entity.curstate.rendercolor.b = 255;\n\tpTemp->frameMax = Q_max( 0, frameCount - 1 );\n\tpTemp->entity.curstate.renderamt = 255;\n\tpTemp->entity.curstate.body = 0;\n\tpTemp->entity.curstate.skin = 0;\n\tpTemp->entity.model = pmodel;\n\tpTemp->fadeSpeed = 0.5f;\n\tpTemp->hitSound = 0;\n\tpTemp->clientIndex = 0;\n\tpTemp->bounceFactor = 1;\n\tpTemp->entity.curstate.scale = 1.0f;\n}\n\n/*\n==============\nCL_TempEntPlaySound\n\nplay collide sound\n==============\n*/\nstatic void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )\n{\n\tfloat\tfvol;\n\tconst char\t*soundname = NULL;\n\tqboolean\tisshellcasing = false;\n\tint\tzvel;\n\n\tAssert( pTemp != NULL );\n\n\tfvol = 0.8f;\n\n\tswitch( pTemp->hitSound )\n\t{\n\tcase BOUNCE_GLASS:\n\t\tsoundname = SoundList_GetRandom( BounceGlass );\n\t\tbreak;\n\tcase BOUNCE_METAL:\n\t\tsoundname = SoundList_GetRandom( BounceMetal );\n\t\tbreak;\n\tcase BOUNCE_FLESH:\n\t\tsoundname = SoundList_GetRandom( BounceFlesh );\n\t\tbreak;\n\tcase BOUNCE_WOOD:\n\t\tsoundname = SoundList_GetRandom( BounceWood );\n\t\tbreak;\n\tcase BOUNCE_SHRAP:\n\t\tsoundname = SoundList_GetRandom( Ricochet );\n\t\tbreak;\n\tcase BOUNCE_SHOTSHELL:\n\t\tsoundname = SoundList_GetRandom( BounceWeaponShell );\n\t\tisshellcasing = true; // shell casings have different playback parameters\n\t\tfvol = 0.5f;\n\t\tbreak;\n\tcase BOUNCE_SHELL:\n\t\tsoundname = SoundList_GetRandom( BouncePlayerShell );\n\t\tisshellcasing = true; // shell casings have different playback parameters\n\t\tbreak;\n\tcase BOUNCE_CONCRETE:\n\t\tsoundname = SoundList_GetRandom( BounceConcrete );\n\t\tbreak;\n\tdefault:\t// null sound\n\t\treturn;\n\t}\n\n\tif( !soundname )\n\t\treturn;\n\n\tzvel = abs( pTemp->entity.baseline.origin[2] );\n\n\t// only play one out of every n\n\tif( isshellcasing )\n\t{\n\t\t// play first bounce, then 1 out of 3\n\t\tif( zvel < 200 && COM_RandomLong( 0, 3 ))\n\t\t\treturn;\n\t}\n\telse\n\t{\n\t\tif( COM_RandomLong( 0, 5 ))\n\t\t\treturn;\n\t}\n\n\tif( damp > 0.0f )\n\t{\n\t\tint\tpitch;\n\t\tsound_t\thandle;\n\n\t\tif( isshellcasing )\n\t\t\tfvol *= Q_min( 1.0f, ((float)zvel) / 350.0f );\n\t\telse fvol *= Q_min( 1.0f, ((float)zvel) / 450.0f );\n\n\t\tif( !COM_RandomLong( 0, 3 ) && !isshellcasing )\n\t\t\tpitch = COM_RandomLong( 95, 105 );\n\t\telse pitch = PITCH_NORM;\n\n\t\thandle = S_RegisterSound( soundname );\n\t\tS_StartSound( pTemp->entity.origin, -(pTemp - cl_tempents), CHAN_BODY, handle, fvol, ATTN_NORM, pitch, SND_STOP_LOOPING );\n\t}\n}\n\n/*\n==============\nCL_TEntAddEntity\n\nadd entity to renderlist\n==============\n*/\nstatic int CL_TempEntAddEntity( cl_entity_t *pEntity )\n{\n\tvec3_t mins, maxs;\n\n\tAssert( pEntity != NULL );\n\n\tif( !pEntity->model )\n\t\treturn 0;\n\n\tVectorAdd( pEntity->origin, pEntity->model->mins, mins );\n\tVectorAdd( pEntity->origin, pEntity->model->maxs, maxs );\n\n\t// g-cont. just use PVS from previous frame\n\tif( TriBoxInPVS( mins, maxs ))\n\t{\n\t\tVectorCopy( pEntity->angles, pEntity->curstate.angles );\n\t\tVectorCopy( pEntity->origin, pEntity->curstate.origin );\n\t\tVectorCopy( pEntity->angles, pEntity->latched.prevangles );\n\t\tVectorCopy( pEntity->origin, pEntity->latched.prevorigin );\n\n\t\t// add to list\n\t\tCL_AddVisibleEntity( pEntity, ET_TEMPENTITY );\n\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n/*\n==============\nCL_AddTempEnts\n\ntemp-entities will be added on a user-side\nsetup client callback\n==============\n*/\nvoid CL_TempEntUpdate( void )\n{\n\tdouble\tft = cl.time - cl.oldtime;\n\tfloat\tgravity = clgame.movevars.gravity;\n\n\tclgame.dllFuncs.pfnTempEntUpdate( ft, cl.time, gravity, &cl_free_tents, &cl_active_tents, CL_TempEntAddEntity, CL_TempEntPlaySound );\n}\n\n/*\n==============\nCL_TEntAddEntity\n\nfree the first low priority tempent it finds.\n==============\n*/\nstatic qboolean CL_FreeLowPriorityTempEnt( void )\n{\n\tTEMPENTITY\t*pActive = cl_active_tents;\n\tTEMPENTITY\t*pPrev = NULL;\n\n\twhile( pActive )\n\t{\n\t\tif( pActive->priority == TENTPRIORITY_LOW )\n\t\t{\n\t\t\t// remove from the active list.\n\t\t\tif( pPrev ) pPrev->next = pActive->next;\n\t\t\telse cl_active_tents = pActive->next;\n\n\t\t\t// add to the free list.\n\t\t\tpActive->next = cl_free_tents;\n\t\t\tcl_free_tents = pActive;\n\n\t\t\treturn true;\n\t\t}\n\n\t\tpPrev = pActive;\n\t\tpActive = pActive->next;\n\t}\n\n\treturn false;\n}\n\n/*\n==============\nCL_TempEntAlloc\n\nalloc normal\\low priority tempentity\n==============\n*/\nTEMPENTITY *CL_TempEntAlloc( const vec3_t org, model_t *pmodel )\n{\n\tstatic float cl_lasttimewarn;\n\tTEMPENTITY\t*pTemp;\n\n\tif( !cl_free_tents )\n\t{\n\t\tif( cl_lasttimewarn < host.realtime )\n\t\t{\n\t\t\tCon_DPrintf( \"Overflow %d temporary ents!\\n\", GI->max_tents );\n\t\t\tcl_lasttimewarn = host.realtime + 1.0f;\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tpTemp = cl_free_tents;\n\tcl_free_tents = pTemp->next;\n\n\tCL_PrepareTEnt( pTemp, pmodel );\n\n\tpTemp->priority = TENTPRIORITY_LOW;\n\tif( org ) VectorCopy( org, pTemp->entity.origin );\n\n\tpTemp->next = cl_active_tents;\n\tcl_active_tents = pTemp;\n\n\treturn pTemp;\n}\n\n/*\n==============\nCL_TempEntAllocHigh\n\nalloc high priority tempentity\n==============\n*/\nTEMPENTITY *CL_TempEntAllocHigh( const vec3_t org, model_t *pmodel )\n{\n\tTEMPENTITY\t*pTemp;\n\n\tif( !cl_free_tents )\n\t{\n\t\t// no temporary ents free, so find the first active low-priority temp ent\n\t\t// and overwrite it.\n\t\tCL_FreeLowPriorityTempEnt();\n\t}\n\n\tif( !cl_free_tents )\n\t{\n\t\t// didn't find anything? The tent list is either full of high-priority tents\n\t\t// or all tents in the list are still due to live for > 10 seconds.\n\t\tCon_DPrintf( \"Couldn't alloc a high priority TENT!\\n\" );\n\t\treturn NULL;\n\t}\n\n\t// Move out of the free list and into the active list.\n\tpTemp = cl_free_tents;\n\tcl_free_tents = pTemp->next;\n\n\tCL_PrepareTEnt( pTemp, pmodel );\n\n\tpTemp->priority = TENTPRIORITY_HIGH;\n\tif( org ) VectorCopy( org, pTemp->entity.origin );\n\n\tpTemp->next = cl_active_tents;\n\tcl_active_tents = pTemp;\n\n\treturn pTemp;\n}\n\n/*\n==============\nCL_TempEntAlloc\n\nalloc normal priority tempentity with no model\n==============\n*/\nTEMPENTITY *CL_TempEntAllocNoModel( const vec3_t org )\n{\n\treturn CL_TempEntAlloc( org, NULL );\n}\n\n/*\n==============\nCL_TempEntAlloc\n\ncustom tempentity allocation\n==============\n*/\nTEMPENTITY * GAME_EXPORT CL_TempEntAllocCustom( const vec3_t org, model_t *model, int high, void (*pfn)( TEMPENTITY*, float, float ))\n{\n\tTEMPENTITY\t*pTemp;\n\n\tif( high )\n\t{\n\t\tpTemp = CL_TempEntAllocHigh( org, model );\n\t}\n\telse\n\t{\n\t\tpTemp = CL_TempEntAlloc( org, model );\n\t}\n\n\tif( pTemp && pfn )\n\t{\n\t\tpTemp->flags |= FTENT_CLIENTCUSTOM;\n\t\tpTemp->callback = pfn;\n\t\tpTemp->die = cl.time;\n\t}\n\n\treturn pTemp;\n}\n\n/*\n==============================================================\n\n\tEFFECTS BASED ON TEMPENTS (presets)\n\n==============================================================\n*/\n/*\n==============\nR_FizzEffect\n\nCreate a fizz effect\n==============\n*/\nvoid GAME_EXPORT R_FizzEffect( cl_entity_t *ent, int modelIndex, int density )\n{\n\tconst float base_time = cl.time - 0.1f;\n\tmodel_t\t*mod = CL_ModelHandle( modelIndex );\n\tvec3_t  volume, mins, maxs;\n\tvec2_t  speed;\n\tint     i;\n\n\tif( !ent || !ent->model || !modelIndex || !mod )\n\t\treturn;\n\n\tVectorCopy( ent->model->mins, mins );\n\tVectorCopy( ent->model->maxs, maxs );\n\n\tif( ent->angles[1] != 0.0f )\n\t{\n\t\tconst float base_speed = ( ent->curstate.rendercolor.b ? -1.0f : 1.0f ) * ( ent->curstate.rendercolor.r * 256.0f + ent->curstate.rendercolor.g );\n\t\tSinCos( DEG2RAD( ent->angles[1] ), &speed[1], &speed[0] );\n\t\tspeed[0] *= base_speed;\n\t\tspeed[1] *= base_speed;\n\t}\n\telse speed[0] = speed[1] = 0.0f;\n\n\tVectorSubtract( maxs, mins, volume );\n\n\tfor( i = 0; i <= density; i++ )\n\t{\n\t\tTEMPENTITY *tent;\n\t\tvec3_t origin;\n\n\t\tVectorCopy( mins, origin );\n\t\torigin[0] += COM_RandomLong( 0, (int)volume[0] - 1 );\n\t\torigin[1] += COM_RandomLong( 0, (int)volume[1] - 1 );\n\n\t\tif( !( tent = CL_TempEntAlloc( origin, mod )))\n\t\t\treturn;\n\n\t\ttent->x = origin[0];\n\t\ttent->y = origin[1];\n\t\ttent->die = base_time;\n\t\ttent->flags |= FTENT_SINEWAVE;\n\t\ttent->entity.curstate.rendermode = kRenderTransAlpha;\n\t\tVector2Copy( speed, tent->entity.baseline.origin );\n\n\t\ttent->entity.baseline.origin[2] = COM_RandomLong( 80, 140 );\n\t\ttent->die += volume[2] / tent->entity.baseline.origin[2];\n\t\ttent->entity.curstate.frame = COM_RandomLong( 0, tent->frameMax );\n\t\ttent->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );\n\t}\n}\n\n/*\n==============\nR_Bubbles\n\nCreate bubbles\n==============\n*/\nvoid GAME_EXPORT R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed )\n{\n\tTEMPENTITY\t*pTemp;\n\tfloat\t\tsine, cosine;\n\tfloat\t\tangle, zspeed;\n\tvec3_t\t\torigin;\n\tmodel_t\t\t*mod;\n\tint\t\ti;\n\n\tif(( mod = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tfor ( i = 0; i < count; i++ )\n\t{\n\t\torigin[0] = COM_RandomLong( mins[0], maxs[0] );\n\t\torigin[1] = COM_RandomLong( mins[1], maxs[1] );\n\t\torigin[2] = COM_RandomLong( mins[2], maxs[2] );\n\t\tpTemp = CL_TempEntAlloc( origin, mod );\n\t\tif( !pTemp ) return;\n\n\t\tpTemp->flags |= FTENT_SINEWAVE;\n\n\t\tpTemp->x = origin[0];\n\t\tpTemp->y = origin[1];\n\t\tangle = COM_RandomFloat( -M_PI, M_PI );\n\t\tSinCos( angle, &sine, &cosine );\n\n\t\tzspeed = COM_RandomLong( 80, 140 );\n\t\tVectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );\n\t\tpTemp->die = cl.time + ((height - (origin[2] - mins[2])) / zspeed) - 0.1f;\n\t\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\n\t\t// Set sprite scale\n\t\tpTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );\n\t\tpTemp->entity.curstate.rendermode = kRenderTransAlpha;\n\t\tpTemp->entity.curstate.renderamt = 255;\n\t}\n}\n\n/*\n==============\nR_BubbleTrail\n\nCreate bubble trail\n==============\n*/\nvoid GAME_EXPORT R_BubbleTrail( const vec3_t start, const vec3_t end, float height, int modelIndex, int count, float speed )\n{\n\tTEMPENTITY\t*pTemp;\n\tfloat\t\tsine, cosine, zspeed;\n\tfloat\t\tdist, angle;\n\tvec3_t\t\torigin;\n\tmodel_t\t\t*mod;\n\tint\t\ti;\n\n\tif(( mod = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tdist = COM_RandomFloat( 0, 1.0 );\n\t\tVectorLerp( start, dist, end, origin );\n\t\tpTemp = CL_TempEntAlloc( origin, mod );\n\t\tif( !pTemp ) return;\n\n\t\tpTemp->flags |= FTENT_SINEWAVE;\n\n\t\tpTemp->x = origin[0];\n\t\tpTemp->y = origin[1];\n\t\tangle = COM_RandomFloat( -M_PI, M_PI );\n\t\tSinCos( angle, &sine, &cosine );\n\n\t\tzspeed = COM_RandomLong( 80, 140 );\n\t\tVectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );\n\t\tpTemp->die = cl.time + ((height - (origin[2] - start[2])) / zspeed) - 0.1f;\n\t\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\n\t\t// Set sprite scale\n\t\tpTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );\n\t\tpTemp->entity.curstate.rendermode = kRenderTransAlpha;\n\t\tpTemp->entity.curstate.renderamt = 255;\n\t}\n}\n\n/*\n==============\nR_AttachTentToPlayer\n\nAttaches entity to player\n==============\n*/\nvoid GAME_EXPORT R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life )\n{\n\tTEMPENTITY\t*pTemp;\n\tvec3_t\t\tposition;\n\tcl_entity_t\t*pClient;\n\tmodel_t\t\t*pModel;\n\n\tif( client <= 0 || client > cl.maxclients )\n\t\treturn;\n\n\tpClient = CL_GetEntityByIndex( client );\n\n\tif( !pClient || pClient->curstate.messagenum != cl.parsecount )\n\t\treturn;\n\n\tif(( pModel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tVectorCopy( pClient->origin, position );\n\tposition[2] += zoffset;\n\n\tpTemp = CL_TempEntAllocHigh( position, pModel );\n\tif( !pTemp ) return;\n\n\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\tpTemp->entity.curstate.framerate = 1;\n\n\tpTemp->clientIndex = client;\n\tpTemp->tentOffset[0] = 0;\n\tpTemp->tentOffset[1] = 0;\n\tpTemp->tentOffset[2] = zoffset;\n\tpTemp->die = cl.time + life;\n\tpTemp->flags |= FTENT_PLYRATTACHMENT|FTENT_PERSIST;\n\n\t// is the model a sprite?\n\tif( pModel->type == mod_sprite )\n\t{\n\t\tpTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP;\n\t\tpTemp->entity.curstate.framerate = 10;\n\t}\n\telse\n\t{\n\t\t// no animation support for attached clientside studio models.\n\t\tpTemp->frameMax = 0;\n\t}\n\n\tpTemp->entity.curstate.frame = 0;\n}\n\n/*\n==============\nR_KillAttachedTents\n\nDetach entity from player\n==============\n*/\nvoid GAME_EXPORT R_KillAttachedTents( int client )\n{\n\tTEMPENTITY *tent;\n\n\tif( client <= 0 || client > cl.maxclients )\n\t\treturn;\n\n\tfor( tent = cl_active_tents; tent; tent = tent->next )\n\t{\n\t\tif( FBitSet( tent->flags, FTENT_PLYRATTACHMENT ) && tent->clientIndex == client )\n\t\t\ttent->die = cl.time;\n\t}\n}\n\n/*\n==============\nR_RicochetSprite\n\nCreate ricochet sprite\n==============\n*/\nvoid GAME_EXPORT R_RicochetSprite( const vec3_t pos, model_t *pmodel, float duration, float scale )\n{\n\tTEMPENTITY\t*pTemp;\n\n\tpTemp = CL_TempEntAlloc( pos, pmodel );\n\tif( !pTemp ) return;\n\n\tpTemp->entity.curstate.rendermode = kRenderGlow;\n\tpTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 200;\n\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\tpTemp->entity.curstate.scale = scale;\n\tpTemp->die = cl.time + duration;\n\tpTemp->flags = FTENT_FADEOUT;\n\tpTemp->fadeSpeed = 8;\n\n\tpTemp->entity.curstate.frame = 0;\n\tpTemp->entity.angles[ROLL] = 45.0f * COM_RandomLong( 0, 7 );\n}\n\n/*\n==============\nR_RocketFlare\n\nCreate rocket flare\n==============\n*/\nvoid GAME_EXPORT R_RocketFlare( const vec3_t pos )\n{\n\tTEMPENTITY\t*pTemp;\n\n\tif( !cl_sprite_glow ) return;\n\n\tpTemp = CL_TempEntAlloc( pos, cl_sprite_glow );\n\tif ( !pTemp ) return;\n\n\tpTemp->entity.curstate.rendermode = kRenderGlow;\n\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\tpTemp->entity.curstate.renderamt = 200;\n\tpTemp->entity.curstate.framerate = 1.0;\n\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\tpTemp->entity.curstate.scale = 1.0;\n\tpTemp->die = cl.time + 0.01f;\t// when 100 fps die at next frame\n\tpTemp->entity.curstate.effects = EF_NOINTERP;\n}\n\n/*\n==============\nR_MuzzleFlash\n\nDo muzzleflash\n==============\n*/\nvoid GAME_EXPORT R_MuzzleFlash( const vec3_t pos, int type )\n{\n\tTEMPENTITY\t*pTemp;\n\tint\t\tindex;\n\tfloat\t\tscale;\n\n\tindex = ( type % 10 ) % MAX_MUZZLEFLASH;\n\tscale = ( type / 10 ) * 0.1f;\n\tif( scale == 0.0f ) scale = 0.5f;\n\n\tif( !cl_sprite_muzzleflash[index] )\n\t\treturn;\n\n\t// must set position for right culling on render\n\tpTemp = CL_TempEntAllocHigh( pos, cl_sprite_muzzleflash[index] );\n\tif( !pTemp ) return;\n\tpTemp->entity.curstate.rendermode = kRenderTransAdd;\n\tpTemp->entity.curstate.renderamt = 255;\n\tpTemp->entity.curstate.framerate = 10;\n\tpTemp->entity.curstate.renderfx = 0;\n\tpTemp->die = cl.time + 0.01; // die at next frame\n\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\tpTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP;\n\tpTemp->entity.curstate.scale = scale;\n\n\tif( index == 0 ) pTemp->entity.angles[2] = COM_RandomLong( 0, 20 ); // rifle flash\n\telse pTemp->entity.angles[2] = COM_RandomLong( 0, 359 );\n\n\tCL_TempEntAddEntity( &pTemp->entity );\n}\n\n/*\n==============\nR_BloodSprite\n\nCreate a high priority blood sprite\nand some blood drops. This is high-priority tent\n==============\n*/\nvoid GAME_EXPORT R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size )\n{\n\tmodel_t\t\t*pModel, *pModel2;\n\tint\t\timpactindex;\n\tint\t\tspatterindex;\n\tint\t\ti, splatter;\n\tTEMPENTITY\t*pTemp;\n\tvec3_t\t\tpos;\n\n\tcolorIndex += COM_RandomLong( 1, 3 );\n\timpactindex = colorIndex;\n\tspatterindex = colorIndex - 1;\n\n\t// validate the model first\n\tif(( pModel = CL_ModelHandle( modelIndex )) != NULL )\n\t{\n\t\tVectorCopy( org, pos );\n\t\tpos[2] += COM_RandomFloat( 2.0f, 4.0f ); // make offset from ground (snarks issues)\n\n\t\t// large, single blood sprite is a high-priority tent\n\t\tif(( pTemp = CL_TempEntAllocHigh( pos, pModel )) != NULL )\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderTransTexture;\n\t\t\tpTemp->entity.curstate.renderfx = kRenderFxClampMinScale;\n\t\t\tpTemp->entity.curstate.scale = COM_RandomFloat( size / 25.0f, size / 35.0f );\n\t\t\tpTemp->flags = FTENT_SPRANIMATE;\n\n\t\t\tpTemp->entity.curstate.rendercolor = clgame.palette[impactindex];\n\t\t\tpTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250;\n\n\t\t\tpTemp->entity.curstate.framerate = pTemp->frameMax * 4.0f; // Finish in 0.250 seconds\n\t\t\tpTemp->die = cl.time + (pTemp->frameMax / pTemp->entity.curstate.framerate ); // play the whole thing once\n\n\t\t\tpTemp->entity.curstate.frame = 0;\n\t\t\tpTemp->bounceFactor = 0;\n\t\t\tpTemp->entity.angles[2] = COM_RandomLong( 0, 360 );\n\t\t}\n\t}\n\n\t// validate the model first\n\tif(( pModel2 = CL_ModelHandle( modelIndex2 )) != NULL )\n\t{\n\t\tsplatter = size + ( COM_RandomLong( 1, 8 ) + COM_RandomLong( 1, 8 ));\n\n\t\tfor( i = 0; i < splatter; i++ )\n\t\t{\n\t\t\t// create blood drips\n\t\t\tif(( pTemp = CL_TempEntAlloc( org, pModel2 )) != NULL )\n\t\t\t{\n\t\t\t\tpTemp->entity.curstate.rendermode = kRenderTransTexture;\n\t\t\t\tpTemp->entity.curstate.renderfx = kRenderFxClampMinScale;\n\t\t\t\tpTemp->entity.curstate.scale = COM_RandomFloat( size / 15.0f, size / 25.0f );\n\t\t\t\tpTemp->flags = FTENT_ROTATE | FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD;\n\n\t\t\t\tpTemp->entity.curstate.rendercolor = clgame.palette[spatterindex];\n\t\t\t\tpTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250;\n\n\t\t\t\tpTemp->entity.baseline.origin[0] = COM_RandomFloat( -96.0f, 95.0f );\n\t\t\t\tpTemp->entity.baseline.origin[1] = COM_RandomFloat( -96.0f, 95.0f );\n\t\t\t\tpTemp->entity.baseline.origin[2] = COM_RandomFloat( -32.0f, 95.0f );\n\t\t\t\tpTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f );\n\t\t\t\tpTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f );\n\t\t\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f );\n\n\t\t\t\tpTemp->die = cl.time + COM_RandomFloat( 1.0f, 3.0f );\n\n\t\t\t\tpTemp->entity.curstate.frame = COM_RandomLong( 1, pTemp->frameMax );\n\n\t\t\t\tif( pTemp->entity.curstate.frame > 8.0f )\n\t\t\t\t\tpTemp->entity.curstate.frame = pTemp->frameMax;\n\n\t\t\t\tpTemp->entity.angles[2] = COM_RandomFloat( 0.0f, 360.0f );\n\t\t\t\tpTemp->bounceFactor\t= 0.0f;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n==============\nR_BreakModel\n\nCreate a shards\n==============\n*/\nvoid GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t dir, float random, float life, int count, int modelIndex, char flags )\n{\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*pmodel;\n\tchar\t\ttype;\n\tint\t\ti, j;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\ttype = flags & BREAK_TYPEMASK;\n\n\tif( count == 0 )\n\t{\n\t\t// assume surface (not volume)\n\t\tcount = (size[0] * size[1] + size[1] * size[2] + size[2] * size[0]) / (3 * SHARD_VOLUME * SHARD_VOLUME);\n\t}\n\n\t// limit to 100 pieces\n\tif( count > 100 ) count = 100;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tvec3_t\tvecSpot;\n\n\t\tfor( j = 0; j < 32; j++ )\n\t\t{\n\t\t\t// fill up the box with stuff\n\t\t\tvecSpot[0] = pos[0] + COM_RandomFloat( -0.5f, 0.5f ) * size[0];\n\t\t\tvecSpot[1] = pos[1] + COM_RandomFloat( -0.5f, 0.5f ) * size[1];\n\t\t\tvecSpot[2] = pos[2] + COM_RandomFloat( -0.5f, 0.5f ) * size[2];\n\n\t\t\tif( PM_CL_PointContents( vecSpot, NULL ) != CONTENTS_SOLID )\n\t\t\t\tbreak; // valid spot\n\t\t}\n\n\t\tif( j == 32 ) continue; // a piece completely stuck in the wall, ignore it\n\n\t\tpTemp = CL_TempEntAlloc( vecSpot, pmodel );\n\t\tif( !pTemp ) return;\n\n\t\t// keep track of break_type, so we know how to play sound on collision\n\t\tpTemp->hitSound = type;\n\n\t\tif( pmodel->type == mod_sprite )\n\t\t\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\t\telse if( pmodel->type == mod_studio )\n\t\t\tpTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );\n\n\t\tpTemp->flags |= FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_SLOWGRAVITY;\n\n\t\tif( COM_RandomLong( 0, 255 ) < 200 )\n\t\t{\n\t\t\tpTemp->flags |= FTENT_ROTATE;\n\t\t\tpTemp->entity.baseline.angles[0] = COM_RandomFloat( -256, 255 );\n\t\t\tpTemp->entity.baseline.angles[1] = COM_RandomFloat( -256, 255 );\n\t\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -256, 255 );\n\t\t}\n\n\t\tif (( COM_RandomLong( 0, 255 ) < 100 ) && FBitSet( flags, BREAK_SMOKE ))\n\t\t\tpTemp->flags |= FTENT_SMOKETRAIL;\n\n\t\tif(( type == BREAK_GLASS ) || FBitSet( flags, BREAK_TRANS ))\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderTransTexture;\n\t\t\tpTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 128;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderNormal;\n\t\t\tpTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 255; // set this for fadeout\n\t\t}\n\n\t\tpTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -random, random );\n\t\tpTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -random, random );\n\t\tpTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, random );\n\n\t\tpTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 1.0f ); // Add an extra 0-1 secs of life\n\t}\n}\n\n/*\n==============\nR_TempModel\n\nCreate a temp model with gravity, sounds and fadeout\n==============\n*/\nTEMPENTITY *R_TempModel( const vec3_t pos, const vec3_t dir, const vec3_t angles, float life, int modelIndex, int soundtype )\n{\n\t// alloc a new tempent\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*pmodel;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn NULL;\n\n\tpTemp = CL_TempEntAlloc( pos, pmodel );\n\tif( !pTemp ) return NULL;\n\n\tpTemp->flags = (FTENT_COLLIDEWORLD|FTENT_GRAVITY);\n\tVectorCopy( dir, pTemp->entity.baseline.origin );\n\tVectorCopy( angles, pTemp->entity.angles );\n\n\t// keep track of shell type\n\tswitch( soundtype )\n\t{\n\tcase TE_BOUNCE_SHELL:\n\t\tpTemp->hitSound = BOUNCE_SHELL;\n\t\tpTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 );\n\t\tpTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 );\n\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 );\n\t\tpTemp->flags |= FTENT_ROTATE;\n\t\tbreak;\n\tcase TE_BOUNCE_SHOTSHELL:\n\t\tpTemp->hitSound = BOUNCE_SHOTSHELL;\n\t\tpTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 );\n\t\tpTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 );\n\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 );\n\t\tpTemp->flags |= FTENT_ROTATE|FTENT_SLOWGRAVITY;\n\t\tbreak;\n\t}\n\n\tif( pmodel->type == mod_sprite )\n\t\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\telse pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );\n\n\tpTemp->die = cl.time + life;\n\n\treturn pTemp;\n}\n\n/*\n==============\nR_DefaultSprite\n\nCreate an animated sprite\n==============\n*/\nTEMPENTITY *R_DefaultSprite( const vec3_t pos, int spriteIndex, float framerate )\n{\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*psprite;\n\n\t// don't spawn while paused\n\tif( cl.time == cl.oldtime )\n\t\treturn NULL;\n\n\tif(( psprite = CL_ModelHandle( spriteIndex )) == NULL || psprite->type != mod_sprite )\n\t{\n\t\tCon_Reportf( \"No Sprite %d!\\n\", spriteIndex );\n\t\treturn NULL;\n\t}\n\n\tpTemp = CL_TempEntAlloc( pos, psprite );\n\tif( !pTemp ) return NULL;\n\n\tpTemp->entity.curstate.scale = 1.0f;\n\tpTemp->flags |= FTENT_SPRANIMATE;\n\tif( framerate == 0 ) framerate = 10;\n\n\tpTemp->entity.curstate.framerate = framerate;\n\tpTemp->die = cl.time + (float)pTemp->frameMax / framerate;\n\tpTemp->entity.curstate.frame = 0;\n\n\treturn pTemp;\n}\n\n/*\n===============\nR_SparkShower\n\nCreate an animated moving sprite\n===============\n*/\nvoid GAME_EXPORT R_SparkShower( const vec3_t pos )\n{\n\tTEMPENTITY\t*pTemp;\n\n\tpTemp = CL_TempEntAllocNoModel( pos );\n\tif( !pTemp ) return;\n\n\tpTemp->entity.baseline.origin[0] = COM_RandomFloat( -300.0f, 300.0f );\n\tpTemp->entity.baseline.origin[1] = COM_RandomFloat( -300.0f, 300.0f );\n\tpTemp->entity.baseline.origin[2] = COM_RandomFloat( -200.0f, 200.0f );\n\n\tpTemp->flags |= FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD | FTENT_SPARKSHOWER;\n\n\tpTemp->entity.curstate.framerate = COM_RandomFloat( 0.5f, 1.5f );\n\tpTemp->entity.curstate.scale = cl.time;\n\tpTemp->die = cl.time + 0.5;\n}\n\n/*\n===============\nR_TempSprite\n\nCreate an animated moving sprite\n===============\n*/\nTEMPENTITY *R_TempSprite( vec3_t pos, const vec3_t dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags )\n{\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*pmodel;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"No model %d!\\n\", modelIndex );\n\t\treturn NULL;\n\t}\n\n\tpTemp = CL_TempEntAlloc( pos, pmodel );\n\tif( !pTemp ) return NULL;\n\n\tpTemp->entity.curstate.framerate = 10;\n\tpTemp->entity.curstate.rendermode = rendermode;\n\tpTemp->entity.curstate.renderfx = renderfx;\n\tpTemp->entity.curstate.scale = scale;\n\tpTemp->entity.baseline.renderamt = a * 255;\n\tpTemp->entity.curstate.renderamt = a * 255;\n\tpTemp->flags |= flags;\n\n\tVectorCopy( dir, pTemp->entity.baseline.origin );\n\n\tif( life ) pTemp->die = cl.time + life;\n\telse pTemp->die = cl.time + ( pTemp->frameMax * 0.1f ) + 1.0f;\n\tpTemp->entity.curstate.frame = 0;\n\n\treturn pTemp;\n}\n\n/*\n===============\nR_Sprite_Explode\n\napply params for exploding sprite\n===============\n*/\nvoid GAME_EXPORT R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags )\n{\n\tqboolean noadditive, drawalpha, rotate;\n\n\tif( !pTemp )\n\t\treturn;\n\n\tnoadditive = FBitSet( flags, TE_EXPLFLAG_NOADDITIVE );\n\tdrawalpha  = FBitSet( flags, TE_EXPLFLAG_DRAWALPHA );\n\trotate     = FBitSet( flags, TE_EXPLFLAG_ROTATE );\n\n\tpTemp->entity.curstate.scale = scale;\n\tpTemp->entity.baseline.origin[2] = 8.0f;\n\tpTemp->entity.origin[2] = pTemp->entity.origin[2] + 10.0f;\n\tif( rotate )\n\t\tpTemp->entity.angles[2] = COM_RandomFloat( 0.0, 360.0f );\n\n\tpTemp->entity.curstate.rendermode = noadditive ? kRenderNormal :\n\t\tdrawalpha ? kRenderTransAlpha : kRenderTransAdd;\n\tpTemp->entity.curstate.renderamt  = noadditive ? 0xff : 0xb4;\n\tpTemp->entity.curstate.renderfx = 0;\n\tpTemp->entity.curstate.rendercolor.r = 0;\n\tpTemp->entity.curstate.rendercolor.g = 0;\n\tpTemp->entity.curstate.rendercolor.b = 0;\n}\n\n/*\n===============\nR_Sprite_Smoke\n\napply params for smoke sprite\n===============\n*/\nvoid GAME_EXPORT R_Sprite_Smoke( TEMPENTITY *pTemp, float scale )\n{\n\tint\tiColor;\n\n\tif( !pTemp ) return;\n\n\tiColor = COM_RandomLong( 20, 35 );\n\tpTemp->entity.curstate.rendermode = kRenderTransAlpha;\n\tpTemp->entity.curstate.renderfx = kRenderFxNone;\n\tpTemp->entity.baseline.origin[2] = 30;\n\tpTemp->entity.curstate.rendercolor.r = iColor;\n\tpTemp->entity.curstate.rendercolor.g = iColor;\n\tpTemp->entity.curstate.rendercolor.b = iColor;\n\tpTemp->entity.origin[2] += 20;\n\tpTemp->entity.curstate.scale = scale;\n}\n\nstatic void R_Spray_Generic( const char *func, const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread, int rendermode, qboolean sprite_spray )\n{\n\tmodel_t *mod = CL_ModelHandle( modelIndex );\n\tfloat noise = spread / 100.0f;\n\tfloat znoise = noise * 1.5f;\n\tint i;\n\n\tznoise = Q_min( 1.0f, znoise );\n\n\tif( !mod )\n\t{\n\t\tCon_Reportf( \"%s: No model %d!\\n\", func, modelIndex );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tTEMPENTITY *tent = CL_TempEntAlloc( pos, mod );\n\t\tfloat rand_speed;\n\t\tvec3_t noise_vec, vout;\n\n\t\tif( !tent )\n\t\t\tbreak;\n\n\t\ttent->flags |= FTENT_SLOWGRAVITY;\n\t\ttent->entity.curstate.rendermode = rendermode;\n\t\ttent->entity.curstate.renderamt = 255;\n\t\ttent->entity.baseline.renderamt = 255;\n\t\ttent->entity.curstate.renderfx = kRenderFxNoDissipation;\n\n\t\tnoise_vec[0] = COM_RandomFloat( -noise, noise );\n\t\tnoise_vec[1] = COM_RandomFloat( -noise, noise );\n\t\tnoise_vec[2] = COM_RandomFloat( 0.0f, znoise );\n\t\trand_speed = COM_RandomFloat( speed * 0.8f, speed * 1.2f );\n\n\t\tVectorAdd( dir, noise_vec, vout );\n\t\tVectorScale( vout, rand_speed, tent->entity.baseline.origin );\n\n\t\tif( sprite_spray ) // if TE_SPRITE_SPRAY\n\t\t{\n\t\t\ttent->flags |= FTENT_FADEOUT;\n\t\t\ttent->fadeSpeed = 2.0f;\n\n\t\t\ttent->entity.curstate.framerate = 0.5f;\n\t\t\ttent->die = cl.time + 0.35;\n\t\t\ttent->entity.curstate.frame = COM_RandomLong( 0, tent->frameMax ); // frameMax inclusive\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttent->flags |= FTENT_COLLIDEWORLD;\n\n\t\t\tif( tent->frameMax > 1 )\n\t\t\t{\n\t\t\t\ttent->flags |= FTENT_SPRANIMATE;\n\t\t\t\ttent->entity.curstate.framerate = 10.0f;\n\t\t\t\ttent->die = cl.time + tent->frameMax * 0.1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ttent->entity.curstate.framerate = 1.0f;\n\t\t\t\ttent->die = cl.time + 0.35;\n\t\t\t}\n\t\t\ttent->entity.curstate.frame = 0.0f;\n\t\t}\n\t}\n}\n\n/*\n===============\nR_Spray\n\nThrows a shower of sprites or models\n===============\n*/\nvoid GAME_EXPORT R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread, int rendermode )\n{\n\tR_Spray_Generic( __func__, pos, dir, modelIndex, count, speed, spread, rendermode, false );\n}\n\n/*\n===============\nR_Sprite_Spray\n\nSpray of alpha sprites\n===============\n*/\nvoid GAME_EXPORT R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread )\n{\n\tR_Spray_Generic( __func__, pos, dir, modelIndex, count, speed, spread, kRenderTransAlpha, true );\n}\n\n/*\n===============\nR_Sprite_Trail\n\nLine of moving glow sprites with gravity,\nfadeout, and collisions\n===============\n*/\nvoid GAME_EXPORT R_Sprite_Trail( int type, vec3_t start, vec3_t end, int modelIndex, int count, float life, float size, float amp, int renderamt, float speed )\n{\n\tTEMPENTITY\t*pTemp;\n\tvec3_t\t\tdelta, dir;\n\tmodel_t\t\t*pmodel;\n\tint\t\ti;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tVectorSubtract( end, start, delta );\n\tVectorNormalize2( delta, dir );\n\n\tamp /= 256.0f;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tvec3_t\tpos, vel;\n\n\t\t// Be careful of divide by 0 when using 'count' here...\n\t\tif( i == 0 ) VectorCopy( start, pos );\n\t\telse VectorMA( start, ( i / ( count - 1.0f )), delta, pos );\n\n\t\tpTemp = CL_TempEntAlloc( pos, pmodel );\n\t\tif( !pTemp ) return;\n\n\t\tpTemp->flags = (FTENT_COLLIDEWORLD|FTENT_SPRCYCLE|FTENT_FADEOUT|FTENT_SLOWGRAVITY);\n\n\t\tVectorScale( dir, speed, vel );\n\t\tvel[0] += COM_RandomFloat( -127.0f, 128.0f ) * amp;\n\t\tvel[1] += COM_RandomFloat( -127.0f, 128.0f ) * amp;\n\t\tvel[2] += COM_RandomFloat( -127.0f, 128.0f ) * amp;\n\t\tVectorCopy( vel, pTemp->entity.baseline.origin );\n\t\tVectorCopy( pos, pTemp->entity.origin );\n\n\t\tpTemp->entity.curstate.scale = size;\n\t\tpTemp->entity.curstate.rendermode = kRenderGlow;\n\t\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\t\tpTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = renderamt;\n\n\t\tpTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );\n\t\tpTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 4.0f );\n\t}\n}\n\n/*\n===============\nR_FunnelSprite\n\nCreate a funnel effect with custom sprite\n===============\n*/\nvoid GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse )\n{\n\tTEMPENTITY\t*pTemp;\n\tvec3_t\t\tdir, dest;\n\tfloat\t\tdist, vel;\n\tmodel_t\t\t*pmodel;\n\tint\t\ti, j;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"no model %d!\\n\", modelIndex );\n\t\treturn;\n\t}\n\n\tfor( i = -8; i < 8; i++ )\n\t{\n\t\tfor( j = -8; j < 8; j++ )\n\t\t{\n\t\t\tpTemp = CL_TempEntAlloc( org, pmodel );\n\t\t\tif( !pTemp ) return;\n\n\t\t\tdest[0] = (i * 32.0f) + org[0];\n\t\t\tdest[1] = (j * 32.0f) + org[1];\n\t\t\tdest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f );\n\n\t\t\tif( reverse )\n\t\t\t{\n\t\t\t\tVectorCopy( org, pTemp->entity.origin );\n\t\t\t\tVectorSubtract( dest, pTemp->entity.origin, dir );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy( dest, pTemp->entity.origin );\n\t\t\t\tVectorSubtract( org, pTemp->entity.origin, dir );\n\t\t\t}\n\n\t\t\tpTemp->entity.curstate.rendermode = kRenderGlow;\n\t\t\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\t\t\tpTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 200;\n\t\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -100.0f, 100.0f );\n\t\t\tpTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f );\n\t\t\tpTemp->flags = FTENT_ROTATE|FTENT_FADEOUT;\n\n\t\t\tvel = dest[2] / 8.0f;\n\t\t\tif( vel < 64.0f ) vel = 64.0f;\n\t\t\tdist = VectorNormalizeLength( dir );\n\t\t\tvel += COM_RandomFloat( 64.0f, 128.0f );\n\t\t\tVectorScale( dir, vel, pTemp->entity.baseline.origin );\n\t\t\tpTemp->die = cl.time + (dist / vel) - 0.5f;\n\t\t\tpTemp->fadeSpeed = 2.0f;\n\t\t}\n\t}\n}\n\n/*\n===============\nR_SparkEffect\n\nCreate a streaks + ricochet sprite\n===============\n*/\nvoid GAME_EXPORT R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax )\n{\n\tR_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, COM_RandomFloat( 0.5f, 1.0f ));\n\tR_SparkStreaks( pos, count, velocityMin, velocityMax );\n}\n\n/*\n==============\nR_RicochetSound\n\nMake a random ricochet sound\n==============\n*/\nstatic void R_RicochetSoundByName( const vec3_t pos, const char *name )\n{\n\tsound_t handle;\n\thandle = S_RegisterSound( name );\n\tS_StartSound( pos, 0, CHAN_AUTO, handle, VOL_NORM, 1.0, 100, 0 );\n}\n\nstatic void R_RicochetSoundByIndex( const vec3_t pos, int idx )\n{\n\tconst char *name = SoundList_Get( Ricochet, idx );\n\tif( name )\n\t\tR_RicochetSoundByName( pos, name );\n}\n\nvoid GAME_EXPORT R_RicochetSound( const vec3_t pos )\n{\n\tconst char *name = SoundList_GetRandom( Ricochet );\n\tif( name )\n\t\tR_RicochetSoundByName( pos, name );\n}\n\n/*\n==============\nR_Projectile\n\nCreate an projectile entity\n==============\n*/\nvoid GAME_EXPORT R_Projectile( const vec3_t origin, const vec3_t velocity, int modelIndex, int life, int owner, void (*hitcallback)( TEMPENTITY*, pmtrace_t* ))\n{\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*pmodel;\n\tvec3_t\t\tdir;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tpTemp = CL_TempEntAllocHigh( origin, pmodel );\n\tif( !pTemp ) return;\n\n\tVectorCopy( velocity, pTemp->entity.baseline.origin );\n\n\tif( pmodel->type == mod_sprite )\n\t{\n\t\tSetBits( pTemp->flags, FTENT_SPRANIMATE );\n\n\t\tif( pTemp->frameMax < 10 )\n\t\t{\n\t\t\tSetBits( pTemp->flags, FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP );\n\t\t\tpTemp->entity.curstate.framerate = 10;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpTemp->entity.curstate.framerate = pTemp->frameMax / life;\n\t\t}\n\t}\n\telse\n\t{\n\t\tpTemp->frameMax = 0;\n\t\tVectorNormalize2( velocity, dir );\n\t\tVectorAngles( dir, pTemp->entity.angles );\n\t}\n\n\tpTemp->flags |= FTENT_COLLIDEALL|FTENT_PERSIST|FTENT_COLLIDEKILL;\n\tpTemp->clientIndex = bound( 1, owner, cl.maxclients );\n\tpTemp->entity.baseline.renderamt = 255;\n\tpTemp->hitcallback = hitcallback;\n\tpTemp->die = cl.time + life;\n}\n\n/*\n==============\nR_TempSphereModel\n\nSpherical shower of models, picks from set\n==============\n*/\nvoid GAME_EXPORT R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex )\n{\n\tTEMPENTITY\t*pTemp;\n\tint\t\ti;\n\n\t// create temp models\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tpTemp = CL_TempEntAlloc( pos, CL_ModelHandle( modelIndex ));\n\t\tif( !pTemp ) return;\n\n\t\tpTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax );\n\n\t\tif( COM_RandomLong( 0, 255 ) < 10 )\n\t\t\tpTemp->flags |= FTENT_SLOWGRAVITY;\n\t\telse pTemp->flags |= FTENT_GRAVITY;\n\n\t\tif( COM_RandomLong( 0, 255 ) < 200 )\n\t\t{\n\t\t\tpTemp->flags |= FTENT_ROTATE;\n\t\t\tpTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f );\n\t\t\tpTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f );\n\t\t\tpTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f );\n\t\t}\n\n\t\tif( COM_RandomLong( 0, 255 ) < 100 )\n\t\t\tpTemp->flags |= FTENT_SMOKETRAIL;\n\n\t\tpTemp->flags |= FTENT_FLICKER | FTENT_COLLIDEWORLD;\n\t\tpTemp->entity.curstate.rendermode = kRenderNormal;\n\t\tpTemp->entity.curstate.effects = i & 31;\n\t\tpTemp->entity.baseline.origin[0] = COM_RandomFloat( -1.0f, 1.0f );\n\t\tpTemp->entity.baseline.origin[1] = COM_RandomFloat( -1.0f, 1.0f );\n\t\tpTemp->entity.baseline.origin[2] = COM_RandomFloat( -1.0f, 1.0f );\n\n\t\tVectorNormalize( pTemp->entity.baseline.origin );\n\t\tVectorScale( pTemp->entity.baseline.origin, speed, pTemp->entity.baseline.origin );\n\t\tpTemp->die = cl.time + life;\n\t}\n}\n\n/*\n==============\nR_Explosion\n\nCreate an explosion (scale is magnitude)\n==============\n*/\nvoid GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags )\n{\n\tsound_t\thSound;\n\n\tif( scale != 0.0f )\n\t{\n\t\t// create explosion sprite\n\t\tR_Sprite_Explode( R_DefaultSprite( pos, model, framerate ), scale, flags );\n\n\t\tif( !FBitSet( flags, TE_EXPLFLAG_NOPARTICLES ))\n\t\t\tR_FlickerParticles( pos );\n\n\t\tif( !FBitSet( flags, TE_EXPLFLAG_NODLIGHTS ))\n\t\t{\n\t\t\tdlight_t\t*dl;\n\n\t\t\t// big flash\n\t\t\tdl = CL_AllocDlight( 0 );\n\t\t\tVectorCopy( pos, dl->origin );\n\t\t\tdl->radius = 200;\n\t\t\tdl->color.r = 250;\n\t\t\tdl->color.g = 250;\n\t\t\tdl->color.b = 150;\n\t\t\tdl->die = cl.time + 0.01f;\n\t\t\tdl->decay = 800;\n\n\t\t\t// red glow\n\t\t\tdl = CL_AllocDlight( 0 );\n\t\t\tVectorCopy( pos, dl->origin );\n\t\t\tdl->radius = 150;\n\t\t\tdl->color.r = 255;\n\t\t\tdl->color.g = 190;\n\t\t\tdl->color.b = 40;\n\t\t\tdl->die = cl.time + 1.0f;\n\t\t\tdl->decay = 200;\n\t\t}\n\t}\n\n\tif( !FBitSet( flags, TE_EXPLFLAG_NOSOUND ))\n\t{\n\t\tconst char *name = SoundList_GetRandom( Explode );\n\t\tif( name )\n\t\t{\n\t\t\thSound = S_RegisterSound( name );\n\t\t\tS_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 );\n\t\t}\n\t}\n}\n\n/*\n==============\nR_PlayerSprites\n\nCreate a particle smoke around player\n==============\n*/\nvoid GAME_EXPORT R_PlayerSprites( int client, int modelIndex, int count, int size )\n{\n\tTEMPENTITY\t*pTemp;\n\tcl_entity_t\t*pEnt;\n\tvec3_t\t\tposition;\n\tvec3_t\t\tdir;\n\tfloat\t\tvel;\n\tint\t\ti;\n\n\tpEnt = CL_GetEntityByIndex( client );\n\n\tif( !pEnt || !pEnt->player )\n\t\treturn;\n\n\tvel = 128;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tVectorCopy( pEnt->origin, position );\n\t\tposition[0] += COM_RandomFloat( -10.0f, 10.0f );\n\t\tposition[1] += COM_RandomFloat( -10.0f, 10.0f );\n\t\tposition[2] += COM_RandomFloat( -20.0f, 36.0f );\n\n\t\tpTemp = CL_TempEntAlloc( position, CL_ModelHandle( modelIndex ));\n\t\tif( !pTemp ) return;\n\n\t\tVectorSubtract( pTemp->entity.origin, pEnt->origin, pTemp->tentOffset );\n\n\t\tif ( i != 0 )\n\t\t{\n\t\t\tpTemp->flags |= FTENT_PLYRATTACHMENT;\n\t\t\tpTemp->clientIndex = client;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( position, pEnt->origin, dir );\n\t\t\tVectorNormalize( dir );\n\t\t\tVectorScale( dir, 60, dir );\n\t\t\tVectorCopy( dir, pTemp->entity.baseline.origin );\n\t\t\tpTemp->entity.baseline.origin[1] = COM_RandomFloat( 20.0f, 60.0f );\n\t\t}\n\n\t\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\t\tpTemp->entity.curstate.framerate = COM_RandomFloat( 1.0f - (size / 100.0f ), 1.0f );\n\n\t\tif( pTemp->frameMax > 1 )\n\t\t{\n\t\t\tpTemp->flags |= FTENT_SPRANIMATE;\n\t\t\tpTemp->entity.curstate.framerate = 20.0f;\n\t\t\tpTemp->die = cl.time + (pTemp->frameMax * 0.05f);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpTemp->die = cl.time + 0.35f;\n\t\t}\n\t}\n}\n\n/*\n==============\nR_FireField\n\nMakes a field of fire\n==============\n*/\nvoid GAME_EXPORT R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life )\n{\n\tTEMPENTITY\t*pTemp;\n\tmodel_t\t\t*pmodel;\n\tfloat\t\ttime;\n\tvec3_t\t\tpos;\n\tint\t\ti;\n\n\tif(( pmodel = CL_ModelHandle( modelIndex )) == NULL )\n\t\treturn;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tVectorCopy( org, pos );\n\t\tpos[0] += COM_RandomFloat( -radius, radius );\n\t\tpos[1] += COM_RandomFloat( -radius, radius );\n\n\t\tif( !FBitSet( flags, TEFIRE_FLAG_PLANAR ))\n\t\t\tpos[2] += COM_RandomFloat( -radius, radius );\n\n\t\tpTemp = CL_TempEntAlloc( pos, pmodel );\n\t\tif( !pTemp ) return;\n\n\t\tif( FBitSet( flags, TEFIRE_FLAG_ALPHA ))\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderTransAlpha;\n\t\t\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\t\t\tpTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 128;\n\t\t}\n\t\telse if( FBitSet( flags, TEFIRE_FLAG_ADDITIVE ))\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderTransAdd;\n\t\t\tpTemp->entity.curstate.renderamt = 180;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpTemp->entity.curstate.rendermode = kRenderNormal;\n\t\t\tpTemp->entity.curstate.renderfx = kRenderFxNoDissipation;\n\t\t\tpTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255;\n\t\t}\n\n\t\tpTemp->entity.curstate.framerate = COM_RandomFloat( 0.75f, 1.25f );\n\t\ttime = life + COM_RandomFloat( -0.25f, 0.5f );\n\t\tpTemp->die = cl.time + time;\n\n\t\tif( pTemp->frameMax > 1 )\n\t\t{\n\t\t\tpTemp->flags |= FTENT_SPRANIMATE;\n\n\t\t\tif( FBitSet( flags, TEFIRE_FLAG_LOOP ))\n\t\t\t{\n\t\t\t\tpTemp->entity.curstate.framerate = 15.0f;\n\t\t\t\tpTemp->flags |= FTENT_SPRANIMATELOOP;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpTemp->entity.curstate.framerate = pTemp->frameMax / time;\n\t\t\t}\n\t\t}\n\n\t\tif( FBitSet( flags, TEFIRE_FLAG_ALLFLOAT ) || ( FBitSet( flags, TEFIRE_FLAG_SOMEFLOAT ) && !COM_RandomLong( 0, 1 )))\n\t\t{\n\t\t\t// drift sprite upward\n\t\t\tpTemp->entity.baseline.origin[2] = COM_RandomFloat( 10.0f, 30.0f );\n\t\t}\n\t}\n}\n\n/*\n==============\nR_MultiGunshot\n\nClient version of shotgun shot\n==============\n*/\nvoid GAME_EXPORT R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int count, int decalCount, int *decalIndices )\n{\n\tpmtrace_t\ttrace;\n\tvec3_t\tright, up;\n\tvec3_t\tvecSrc, vecDir, vecEnd;\n\tint\ti, j, decalIndex;\n\n\tVectorVectors( dir, right, up );\n\tVectorCopy( org, vecSrc );\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\t// get circular gaussian spread\n\t\tfloat x, y, z;\n\t\tdo {\n\t\t\tx = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f );\n\t\t\ty = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f );\n\t\t\tz = x * x + y * y;\n\t\t} while( z > 1.0f );\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tvecDir[j] = dir[j] + x * noise[0] * right[j] + y * noise[1] * up[j];\n\t\t\tvecEnd[j] = vecSrc[j] + 4096.0f * vecDir[j];\n\t\t}\n\n\t\ttrace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE );\n\n\t\t// paint decals\n\t\tif( trace.fraction != 1.0f )\n\t\t{\n\t\t\tphysent_t\t*pe = NULL;\n\n\t\t\tif( i & 2 ) R_RicochetSound( trace.endpos );\n\t\t\tR_BulletImpactParticles( trace.endpos );\n\n\t\t\tif( trace.ent >= 0 && trace.ent < clgame.pmove->numphysent )\n\t\t\t\tpe = &clgame.pmove->physents[trace.ent];\n\n\t\t\tif( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ))\n\t\t\t{\n\t\t\t\tcl_entity_t *e = CL_GetEntityByIndex( pe->info );\n\t\t\t\tdecalIndex = CL_DecalIndex( decalIndices[COM_RandomLong( 0, decalCount-1 )] );\n\t\t\t\tCL_DecalShoot( decalIndex, e->index, 0, trace.endpos, 0 );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n==============\nR_Sprite_WallPuff\n\nCreate a wallpuff\n==============\n*/\nvoid GAME_EXPORT R_Sprite_WallPuff( TEMPENTITY *pTemp, float scale )\n{\n\tif( !pTemp ) return;\n\n\tpTemp->entity.curstate.renderamt = 255;\n\tpTemp->entity.curstate.rendermode = kRenderTransAlpha;\n\tpTemp->entity.angles[ROLL] = COM_RandomLong( 0, 359 );\n\tpTemp->entity.baseline.origin[2] = 30;\n\tpTemp->entity.curstate.scale = scale;\n\tpTemp->die = cl.time + 0.01f;\n}\n\n\n\n/*\n==============\nCL_ParseTempEntity\n\nhandle temp-entity messages\n==============\n*/\nvoid CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto )\n{\n\tsizebuf_t\t\tbuf, *pbuf;\n\tbyte\t\tmsg_data[2048];\n\tint\t\tiSize;\n\tint\t\ttype, color, count, flags;\n\tint\t\tdecalIndex = 0, modelIndex = 0, entityIndex = 0;\n\tfloat\t\tscale, life, frameRate, vel, random;\n\tfloat\t\tbrightness, r, g, b;\n\tvec3_t\t\tpos, pos2, ang;\n\tint\t\tdecalIndices[1];\t// just stub\n\tTEMPENTITY\t*pTemp;\n\tcl_entity_t\t*pEnt;\n\tdlight_t\t\t*dl;\n\tsound_t\thSound;\n\tconst char *name;\n\n\tif( proto != PROTO_GOLDSRC )\n\t{\n\t\tif( proto == PROTO_LEGACY )\n\t\t\tiSize = MSG_ReadByte( msg );\n\t\telse iSize = MSG_ReadWord( msg );\n\n\t\t// this will probably be fatal anyway\n\t\tif( iSize > sizeof( msg_data ))\n\t\t\tCon_Printf( S_ERROR \"%s: Temp buffer overflow!\\n\", __func__ );\n\n\t\t// parse user message into buffer\n\t\tMSG_ReadBytes( msg, msg_data, iSize );\n\n\t\t// init a safe tempbuffer\n\t\tMSG_Init( &buf, \"TempEntity\", msg_data, iSize );\n\n\t\tpbuf = &buf;\n\t}\n\telse\n\t{\n\t\tpbuf = msg;\n\t}\n\n\ttype = MSG_ReadByte( pbuf );\n\n\tswitch( type )\n\t{\n\tcase TE_BEAMPOINTS:\n\tcase TE_BEAMENTPOINT:\n\tcase TE_LIGHTNING:\n\tcase TE_BEAMENTS:\n\tcase TE_BEAM:\n\tcase TE_BEAMSPRITE:\n\tcase TE_BEAMTORUS:\n\tcase TE_BEAMDISK:\n\tcase TE_BEAMCYLINDER:\n\tcase TE_BEAMFOLLOW:\n\tcase TE_BEAMRING:\n\tcase TE_BEAMHOSE:\n\tcase TE_KILLBEAM:\n\t\tCL_ParseViewBeam( pbuf, type );\n\t\tbreak;\n\tcase TE_GUNSHOT:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tR_RicochetSound( pos );\n\t\tR_RunParticleEffect( pos, vec3_origin, 0, 20 );\n\t\tbreak;\n\tcase TE_EXPLOSION:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tscale = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tframeRate = MSG_ReadByte( pbuf );\n\t\tflags = MSG_ReadByte( pbuf );\n\t\tR_Explosion( pos, modelIndex, scale, frameRate, flags );\n\t\tbreak;\n\tcase TE_TAREXPLOSION:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tR_BlobExplosion( pos );\n\n\t\tif(( name = SoundList_Get( Explode, 0 )))\n\t\t{\n\t\t\thSound = S_RegisterSound( name );\n\t\t\tS_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 );\n\t\t}\n\t\tbreak;\n\tcase TE_SMOKE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tscale = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tframeRate = MSG_ReadByte( pbuf );\n\t\tpTemp = R_DefaultSprite( pos, modelIndex, frameRate );\n\t\tR_Sprite_Smoke( pTemp, scale );\n\t\tbreak;\n\tcase TE_TRACER:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tR_TracerEffect( pos, pos2 );\n\t\tbreak;\n\tcase TE_SPARKS:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tR_SparkShower( pos );\n\t\tbreak;\n\tcase TE_LAVASPLASH:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tR_LavaSplash( pos );\n\t\tbreak;\n\tcase TE_TELEPORT:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tR_TeleportSplash( pos );\n\t\tbreak;\n\tcase TE_EXPLOSION2:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tR_ParticleExplosion2( pos, color, count );\n\n\t\tdl = CL_AllocDlight( 0 );\n\t\tVectorCopy( pos, dl->origin );\n\t\tdl->radius = 350;\n\t\tdl->die = cl.time + 0.5;\n\t\tdl->decay = 300;\n\n\t\tif(( name = SoundList_Get( Explode, 0 )))\n\t\t{\n\t\t\thSound = S_RegisterSound( name );\n\t\t\tS_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 );\n\t\t}\n\t\tbreak;\n\tcase TE_BSPDECAL:\n\tcase TE_DECAL:\n\tcase TE_WORLDDECAL:\n\tcase TE_WORLDDECALHIGH:\n\tcase TE_DECALHIGH:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tif( type == TE_BSPDECAL )\n\t\t{\n\t\t\tdecalIndex = MSG_ReadShort( pbuf );\n\t\t\tentityIndex = MSG_ReadShort( pbuf );\n\t\t\tif( entityIndex )\n\t\t\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\t\telse modelIndex = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdecalIndex = MSG_ReadByte( pbuf );\n\t\t\tif( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH )\n\t\t\t\tdecalIndex += 256;\n\n\t\t\tif( type == TE_DECALHIGH || type == TE_DECAL )\n\t\t\t\tentityIndex = MSG_ReadShort( pbuf );\n\t\t\telse entityIndex = 0;\n\n\t\t\tpEnt = CL_GetEntityByIndex( entityIndex );\n\t\t\tmodelIndex = pEnt->curstate.modelindex;\n\t\t}\n\t\tCL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, type == TE_BSPDECAL ? FDECAL_PERMANENT : 0 );\n\t\tbreak;\n\tcase TE_IMPLOSION:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tscale = MSG_ReadByte( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_Implosion( pos, scale, count, life );\n\t\tbreak;\n\tcase TE_SPRITETRAIL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tlife = (float)MSG_ReadByte( pbuf ) * 0.1f;\n\t\tscale = (float)MSG_ReadByte( pbuf );\n\t\tif( !scale ) scale = 1.0f;\n\t\telse scale *= 0.1f;\n\t\tvel = (float)MSG_ReadByte( pbuf ) * 10;\n\t\trandom = (float)MSG_ReadByte( pbuf ) * 10;\n\t\tR_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel );\n\t\tbreak;\n\tcase TE_SPRITE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tscale = (float)MSG_ReadByte( pbuf ) * 0.1f;\n\t\tbrightness = (float)MSG_ReadByte( pbuf ) / 255.0f;\n\n\t\tR_TempSprite( pos, vec3_origin, scale, modelIndex,\n\t\t\tkRenderTransAdd, kRenderFxNone, brightness, 0.0, FTENT_SPRANIMATE );\n\t\tbreak;\n\tcase TE_GLOWSPRITE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tlife = (float)MSG_ReadByte( pbuf ) * 0.1f;\n\t\tscale = (float)MSG_ReadByte( pbuf ) * 0.1f;\n\t\tbrightness = (float)MSG_ReadByte( pbuf ) / 255.0f;\n\n\t\tR_TempSprite( pos, vec3_origin, scale, modelIndex,\n\t\t\tkRenderGlow, kRenderFxNoDissipation, brightness, life, FTENT_FADEOUT );\n\t\tbreak;\n\tcase TE_STREAK_SPLASH:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tcount = MSG_ReadShort( pbuf );\n\t\tvel = (float)MSG_ReadShort( pbuf );\n\t\trandom = (float)MSG_ReadShort( pbuf );\n\t\tR_StreakSplash( pos, pos2, color, count, vel, -random, random );\n\t\tbreak;\n\tcase TE_DLIGHT:\n\t\tdl = CL_AllocDlight( 0 );\n\t\tdl->origin[0] = MSG_ReadCoord( pbuf );\n\t\tdl->origin[1] = MSG_ReadCoord( pbuf );\n\t\tdl->origin[2] = MSG_ReadCoord( pbuf );\n\t\tdl->radius = (float)(MSG_ReadByte( pbuf ) * 10.0f);\n\t\tdl->color.r = MSG_ReadByte( pbuf );\n\t\tdl->color.g = MSG_ReadByte( pbuf );\n\t\tdl->color.b = MSG_ReadByte( pbuf );\n\t\tdl->die = cl.time + (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tdl->decay = (float)(MSG_ReadByte( pbuf ) * 10.0f);\n\t\tbreak;\n\tcase TE_ELIGHT:\n\t\tdl = CL_AllocElight( MSG_ReadShort( pbuf ));\n\t\tdl->origin[0] = MSG_ReadCoord( pbuf );\n\t\tdl->origin[1] = MSG_ReadCoord( pbuf );\n\t\tdl->origin[2] = MSG_ReadCoord( pbuf );\n\t\tdl->radius = MSG_ReadCoord( pbuf );\n\t\tdl->color.r = MSG_ReadByte( pbuf );\n\t\tdl->color.g = MSG_ReadByte( pbuf );\n\t\tdl->color.b = MSG_ReadByte( pbuf );\n\t\tlife = (float)MSG_ReadByte( pbuf ) * 0.1f;\n\t\tdl->die = cl.time + life;\n\t\tdl->decay = MSG_ReadCoord( pbuf );\n\t\tif( life != 0 ) dl->decay /= life;\n\t\tbreak;\n\tcase TE_TEXTMESSAGE:\n\t\tCL_ParseTextMessage( pbuf );\n\t\tbreak;\n\tcase TE_LINE:\n\tcase TE_BOX:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tlife = (float)(MSG_ReadShort( pbuf ) * 0.1f);\n\t\tr = MSG_ReadByte( pbuf );\n\t\tg = MSG_ReadByte( pbuf );\n\t\tb = MSG_ReadByte( pbuf );\n\t\tif( type == TE_LINE ) R_ParticleLine( pos, pos2, r, g, b, life );\n\t\telse R_ParticleBox( pos, pos2, r, g, b, life );\n\t\tbreak;\n\tcase TE_LARGEFUNNEL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tflags = MSG_ReadShort( pbuf );\n\t\tR_LargeFunnel( pos, flags );\n\t\tR_FunnelSprite( pos, modelIndex, flags );\n\t\tbreak;\n\tcase TE_BLOODSTREAM:\n\tcase TE_BLOOD:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tif( type == TE_BLOOD ) R_Blood( pos, pos2, color, count );\n\t\telse R_BloodStream( pos, pos2, color, count );\n\t\tbreak;\n\tcase TE_SHOWLINE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tR_ShowLine( pos, pos2 );\n\t\tbreak;\n\tcase TE_FIZZ:\n\t\tentityIndex = MSG_ReadShort( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tscale = MSG_ReadByte( pbuf );\t// same as density\n\t\tpEnt = CL_GetEntityByIndex( entityIndex );\n\t\tR_FizzEffect( pEnt, modelIndex, scale );\n\t\tbreak;\n\tcase TE_MODEL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tang[0] = 0.0f;\n\t\tang[1] = MSG_ReadAngle( pbuf ); // yaw angle\n\t\tang[2] = 0.0f;\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tflags = MSG_ReadByte( pbuf );\t// sound flags\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_TempModel( pos, pos2, ang, life, modelIndex, flags );\n\t\tbreak;\n\tcase TE_EXPLODEMODEL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tvel = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadShort( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_TempSphereModel( pos, vel, life, count, modelIndex );\n\t\tbreak;\n\tcase TE_BREAKMODEL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tang[0] = MSG_ReadCoord( pbuf );\n\t\tang[1] = MSG_ReadCoord( pbuf );\n\t\tang[2] = MSG_ReadCoord( pbuf );\n\t\trandom = (float)MSG_ReadByte( pbuf ) * 10.0f;\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tflags = MSG_ReadByte( pbuf );\n\t\tR_BreakModel( pos, pos2, ang, random, life, count, modelIndex, (char)flags );\n\t\tbreak;\n\tcase TE_GUNSHOTDECAL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tentityIndex = MSG_ReadShort( pbuf );\n\t\tdecalIndex = MSG_ReadByte( pbuf );\n\t\tCL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 );\n\t\tR_BulletImpactParticles( pos );\n\t\tflags = COM_RandomLong( 0, 0x7fff );\n\t\tif( flags < 0x3fff && ( count = SoundList_Count( Ricochet )))\n\t\t\tR_RicochetSoundByIndex( pos, flags % count );\n\t\tbreak;\n\tcase TE_SPRAY:\n\tcase TE_SPRITE_SPRAY:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tvel = (float)MSG_ReadByte( pbuf );\n\t\trandom = (float)MSG_ReadByte( pbuf );\n\t\tif( type == TE_SPRAY )\n\t\t{\n\t\t\tflags = MSG_ReadByte( pbuf );\t// rendermode\n\t\t\tR_Spray( pos, pos2, modelIndex, count, vel, random, flags );\n\t\t}\n\t\telse R_Sprite_Spray( pos, pos2, modelIndex, count, vel * 2.0f, random );\n\t\tbreak;\n\tcase TE_ARMOR_RICOCHET:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tscale = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, scale );\n\t\tR_RicochetSound( pos );\n\t\tbreak;\n\tcase TE_PLAYERDECAL:\n\t\tcolor = MSG_ReadByte( pbuf ) - 1; // playernum\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tentityIndex = MSG_ReadShort( pbuf );\n\t\tdecalIndex = MSG_ReadByte( pbuf );\n\t\tCL_PlayerDecal( color, decalIndex, entityIndex, pos );\n\t\tbreak;\n\tcase TE_BUBBLES:\n\tcase TE_BUBBLETRAIL:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tscale = MSG_ReadCoord( pbuf );\t// water height\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tvel = MSG_ReadCoord( pbuf );\n\t\tif( type == TE_BUBBLES ) R_Bubbles( pos, pos2, scale, modelIndex, count, vel );\n\t\telse R_BubbleTrail( pos, pos2, scale, modelIndex, count, vel );\n\t\tbreak;\n\tcase TE_BLOODSPRITE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\t// sprite #1\n\t\tdecalIndex = MSG_ReadShort( pbuf );\t// sprite #2\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tscale = (float)MSG_ReadByte( pbuf );\n\t\tR_BloodSprite( pos, color, modelIndex, decalIndex, scale );\n\t\tbreak;\n\tcase TE_PROJECTILE:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tlife = MSG_ReadByte( pbuf );\n\t\tcolor = MSG_ReadByte( pbuf );\t// playernum\n\t\tR_Projectile( pos, pos2, modelIndex, life, color, NULL );\n\t\tbreak;\n\tcase TE_PLAYERSPRITES:\n\t\tcolor = MSG_ReadShort( pbuf );\t// entitynum\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\trandom = (float)MSG_ReadByte( pbuf );\n\t\tR_PlayerSprites( color, modelIndex, count, random );\n\t\tbreak;\n\tcase TE_PARTICLEBURST:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tscale = (float)MSG_ReadShort( pbuf );\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_ParticleBurst( pos, scale, color, life );\n\t\tbreak;\n\tcase TE_FIREFIELD:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tscale = (float)MSG_ReadShort( pbuf );\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tflags = MSG_ReadByte( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_FireField( pos, scale, modelIndex, count, flags, life );\n\t\tbreak;\n\tcase TE_PLAYERATTACHMENT:\n\t\tcolor = MSG_ReadByte( pbuf );\t// playernum\n\t\tscale = MSG_ReadCoord( pbuf );\t// height\n\t\tmodelIndex = MSG_ReadShort( pbuf );\n\t\tlife = (float)(MSG_ReadShort( pbuf ) * 0.1f);\n\t\tR_AttachTentToPlayer( color, modelIndex, scale, life );\n\t\tbreak;\n\tcase TE_KILLPLAYERATTACHMENTS:\n\t\tcolor = MSG_ReadByte( pbuf );\t// playernum\n\t\tR_KillAttachedTents( color );\n\t\tbreak;\n\tcase TE_MULTIGUNSHOT:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf ) * 0.1f;\n\t\tpos2[1] = MSG_ReadCoord( pbuf ) * 0.1f;\n\t\tpos2[2] = MSG_ReadCoord( pbuf ) * 0.1f;\n\t\tang[0] = MSG_ReadCoord( pbuf ) * 0.01f;\n\t\tang[1] = MSG_ReadCoord( pbuf ) * 0.01f;\n\t\tang[2] = 0.0f;\n\t\tcount = MSG_ReadByte( pbuf );\n\t\tdecalIndices[0] = MSG_ReadByte( pbuf );\n\t\tR_MultiGunshot( pos, pos2, ang, count, 1, decalIndices );\n\t\tbreak;\n\tcase TE_USERTRACER:\n\t\tpos[0] = MSG_ReadCoord( pbuf );\n\t\tpos[1] = MSG_ReadCoord( pbuf );\n\t\tpos[2] = MSG_ReadCoord( pbuf );\n\t\tpos2[0] = MSG_ReadCoord( pbuf );\n\t\tpos2[1] = MSG_ReadCoord( pbuf );\n\t\tpos2[2] = MSG_ReadCoord( pbuf );\n\t\tlife = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tcolor = MSG_ReadByte( pbuf );\n\t\tscale = (float)(MSG_ReadByte( pbuf ) * 0.1f);\n\t\tR_UserTracerParticle( pos, pos2, life, color, scale, 0, NULL );\n\t\tbreak;\n\tdefault:\n\t\tCon_DPrintf( S_ERROR \"%s: illegible TE message %i\\n\", __func__, type );\n\t\tbreak;\n\t}\n\n\t// throw warning\n\tif( MSG_CheckOverflow( pbuf ))\n\t\tCon_DPrintf( S_WARN \"%s: overflow TE message %i\\n\", __func__, type );\n}\n\n\n/*\n==============================================================\n\nLIGHT STYLE MANAGEMENT\n\n==============================================================\n*/\n#define STYLE_LERPING_THRESHOLD\t3.0f // because we wan't interpolate fast sequences (like on\\off)\n\n/*\n================\nCL_ClearLightStyles\n================\n*/\nstatic void CL_ClearLightStyles( void )\n{\n\tmemset( cl.lightstyles, 0, sizeof( cl.lightstyles ));\n}\n\nvoid CL_SetLightstyle( int style, const char *s, float f )\n{\n\tint i;\n\tlightstyle_t *ls;\n\n\tif( unlikely( style < 0 || style >= MAX_LIGHTSTYLES ))\n\t{\n\t\tCon_Printf( S_WARN \"%s: ignored invalid lightstyle id %d\\n\", __func__, style );\n\t\treturn;\n\t}\n\n\tls = &cl.lightstyles[style];\n\n\tls->length = Q_strncpy( ls->pattern, s, sizeof( ls->pattern ));\n\tls->time = f; // set local time\n\n\tfor( i = 0; i < ls->length; i++ )\n\t\tls->map[i] = (float)(s[i] - 'a');\n\n\tls->interp = (ls->length <= 1) ? false : true;\n\n\t// check for allow interpolate\n\t// NOTE: fast flickering styles looks ugly when interpolation is running\n\tfor( i = 0; i < ( ls->length - 1 ); i++ )\n\t{\n\t\tfloat val1 = ls->map[( i + 0 ) % ls->length];\n\t\tfloat val2 = ls->map[( i + 1 ) % ls->length];\n\n\t\tif( fabs( val1 - val2 ) > STYLE_LERPING_THRESHOLD )\n\t\t{\n\t\t\tls->interp = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( ls->length >= 1 )\n\t\tCon_Reportf( \"Lightstyle %i (%s), interp %s\\n\", style, ls->pattern, ls->interp ? \"Yes\" : \"No\" );\n}\n\n/*\n==============================================================\n\nDLIGHT MANAGEMENT\n\n==============================================================\n*/\nstatic dlight_t cl_dlights[MAX_DLIGHTS];\nstatic dlight_t cl_elights[MAX_ELIGHTS];\n\n/*\n================\nCL_ClearDlights\n================\n*/\nstatic void CL_ClearDlights( void )\n{\n\tmemset( cl_dlights, 0, sizeof( cl_dlights ));\n\tmemset( cl_elights, 0, sizeof( cl_elights ));\n}\n\n/*\n===============\nCL_AllocDlight\n\n===============\n*/\ndlight_t *CL_AllocDlight( int key )\n{\n\tdlight_t\t*dl;\n\tint\ti;\n\n\t// first look for an exact key match\n\tif( key )\n\t{\n\t\tfor( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ )\n\t\t{\n\t\t\tif( dl->key == key )\n\t\t\t{\n\t\t\t\t// reuse this light\n\t\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\t\tdl->key = key;\n\t\t\t\treturn dl;\n\t\t\t}\n\t\t}\n\t}\n\n\t// then look for anything else\n\tfor( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ )\n\t{\n\t\tif( dl->die < cl.time && dl->key == 0 )\n\t\t{\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\tdl->key = key;\n\t\t\treturn dl;\n\t\t}\n\t}\n\n\t// otherwise grab first dlight\n\tdl = &cl_dlights[0];\n\tmemset( dl, 0, sizeof( *dl ));\n\tdl->key = key;\n\n\treturn dl;\n}\n\n/*\n===============\nCL_AllocElight\n\n===============\n*/\ndlight_t *CL_AllocElight( int key )\n{\n\tdlight_t\t*dl;\n\tint\ti;\n\n\t// first look for an exact key match\n\tif( key )\n\t{\n\t\tfor( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ )\n\t\t{\n\t\t\tif( dl->key == key )\n\t\t\t{\n\t\t\t\t// reuse this light\n\t\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\t\tdl->key = key;\n\t\t\t\treturn dl;\n\t\t\t}\n\t\t}\n\t}\n\n\t// then look for anything else\n\tfor( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ )\n\t{\n\t\tif( dl->die < cl.time && dl->key == 0 )\n\t\t{\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\tdl->key = key;\n\t\t\treturn dl;\n\t\t}\n\t}\n\n\t// otherwise grab first dlight\n\tdl = &cl_elights[0];\n\tmemset( dl, 0, sizeof( *dl ));\n\tdl->key = key;\n\n\treturn dl;\n}\n\n/*\n===============\nCL_DecayLights\n\n===============\n*/\nvoid CL_DecayLights( void )\n{\n\tconst float time = cl.time;\n\tconst float dt = cl.time - cl.oldtime;\n\tint\ti;\n\n\tfor( i = 0; i < MAX_DLIGHTS; i++ )\n\t{\n\t\tdlight_t *dl = &cl_dlights[i];\n\n\t\tif( !dl->radius )\n\t\t\tcontinue;\n\n\t\tif( dl->die < time )\n\t\t{\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\tcontinue;\n\t\t}\n\n\t\tdl->radius -= dt * dl->decay;\n\t\tif( dl->radius <= 0 )\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t}\n\n\tfor( i = 0; i < MAX_ELIGHTS; i++ )\n\t{\n\t\tdlight_t *dl = &cl_elights[i];\n\n\t\tif( !dl->radius )\n\t\t\tcontinue;\n\n\t\tif( dl->die < time )\n\t\t{\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t\t\tcontinue;\n\t\t}\n\n\t\tdl->radius -= dt * dl->decay;\n\t\tif( dl->radius <= 0 )\n\t\t\tmemset( dl, 0, sizeof( *dl ));\n\t}\n}\n\ndlight_t *CL_GetDynamicLight( int number )\n{\n\tAssert( number >= 0 && number < MAX_DLIGHTS );\n\treturn &cl_dlights[number];\n}\n\ndlight_t *CL_GetEntityLight( int number )\n{\n\tAssert( number >= 0 && number < MAX_ELIGHTS );\n\treturn &cl_elights[number];\n}\n\n/*\n================\nCL_UpdateFlashlight\n\nupdate client flashlight\n================\n*/\nstatic void CL_UpdateFlashlight( cl_entity_t *ent )\n{\n\tvec3_t\t\tforward, view_ofs;\n\tvec3_t\t\tvecSrc, vecEnd;\n\tfloat\t\tfalloff;\n\tpmtrace_t\t\ttrace;\n\tcl_entity_t\t*hit;\n\tdlight_t\t\t*dl;\n\n\tif( ent->index == ( cl.playernum + 1 ))\n\t{\n\t\t// local player case\n\t\tAngleVectors( cl.viewangles, forward, NULL, NULL );\n\t\tVectorCopy( cl.viewheight, view_ofs );\n\t}\n\telse\t// non-local player case\n\t{\n\t\tvec3_t\tv_angle;\n\n\t\t// NOTE: pitch divided by 3.0 twice. So we need apply 3^2 = 9\n\t\tv_angle[PITCH] = ent->curstate.angles[PITCH] * 9.0f;\n\t\tv_angle[YAW] = ent->angles[YAW];\n\t\tv_angle[ROLL] = 0.0f; // roll not used\n\n\t\tAngleVectors( v_angle, forward, NULL, NULL );\n\t\tview_ofs[0] = view_ofs[1] = 0.0f;\n\n\t\t// FIXME: these values are hardcoded ...\n\t\tif( ent->curstate.usehull == 1 )\n\t\t\tview_ofs[2] = 12.0f;\t// VEC_DUCK_VIEW;\n\t\telse view_ofs[2] = 28.0f;\t\t// DEFAULT_VIEWHEIGHT\n\t}\n\n\tVectorAdd( ent->origin, view_ofs, vecSrc );\n\tVectorMA( vecSrc, FLASHLIGHT_DISTANCE, forward, vecEnd );\n\n\ttrace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );\n\n\t// update flashlight endpos\n\tdl = CL_AllocDlight( ent->index );\n#if 1\n\thit = CL_GetEntityByIndex( clgame.pmove->physents[trace.ent].info );\n\tif( hit && hit->model && ( hit->model->type == mod_alias || hit->model->type == mod_studio ))\n\t\tVectorCopy( hit->origin, dl->origin );\n\telse VectorCopy( trace.endpos, dl->origin );\n#else\n\tVectorCopy( trace->endpos, dl->origin );\n#endif\n\t// compute falloff\n\tfalloff = trace.fraction * FLASHLIGHT_DISTANCE;\n\tif( falloff < 500.0f ) falloff = 1.0f;\n\telse falloff = 500.0f / falloff;\n\tfalloff *= falloff;\n\n\t// apply brigthness to dlight\n\tdl->color.r = dl->color.g = dl->color.b = bound( 0, falloff * 255, 255 );\n\tdl->die = cl.time + 0.01f; // die on next frame\n\tdl->radius = 80;\n}\n\nstatic void R_EntityDimlight( cl_entity_t *ent, int key )\n{\n\tdlight_t *dl = CL_AllocDlight( key );\n\n\tVectorCopy( ent->origin, dl->origin );\n\tdl->color.r = dl->color.g = dl->color.b = 100;\n\tdl->radius = COM_RandomFloat( 200.0f, 231.0f );\n\tdl->die = cl.time + 0.001;\n}\n\nstatic void R_EntityLight( cl_entity_t *ent, int key )\n{\n\tdlight_t *dl = CL_AllocDlight( key );\n\n\tVectorCopy( ent->origin, dl->origin );\n\tdl->color.r = dl->color.g = dl->color.b = 100;\n\tdl->radius = 200;\n\tdl->die = cl.time + 0.001;\n\n\tR_RocketFlare( ent->origin );\n}\n\nstatic void R_EntityBrightlight( cl_entity_t *ent, int key, int radius )\n{\n\tdlight_t *dl = CL_AllocDlight( key );\n\n\tVectorCopy( ent->origin, dl->origin );\n\tdl->origin[2] += 16.0f;\n\tdl->color.r = dl->color.g = dl->color.b = 250;\n\tif( !radius )\n\t\tdl->radius = COM_RandomFloat( 400.0f, 431.0f );\n\telse dl->radius = 400;\n\tdl->die = cl.time + 0.001;\n}\n\n/*\n================\nCL_AddEntityEffects\n\napply various effects to entity origin or attachment\n================\n*/\nvoid CL_AddEntityEffects( cl_entity_t *ent )\n{\n\t// players have special set of effects, from CL_LinkPlayers\n\tif( ent->player && ent->index != cl.viewentity )\n\t{\n\t\tif( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT ))\n\t\t\tR_EntityBrightlight( ent, ent->index /* 4 in GoldSrc */, 0 );\n\n\t\tif( FBitSet( ent->curstate.effects, EF_DIMLIGHT ))\n\t\t\tR_EntityDimlight( ent, ent->index /* 4 in GoldSrc */ );\n\t}\n\telse if( RP_LOCALCLIENT( ent ))\n\t{\n\t\t// from CL_PlayerFlashlight\n\t\tif( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT ))\n\t\t\tR_EntityBrightlight( ent, ent->index /* 1 in GoldSrc */, 400 );\n\t\telse if( FBitSet( ent->curstate.effects, EF_DIMLIGHT ))\n\t\t{\n\t\t\tif( Host_IsQuakeCompatible( ))\n\t\t\t\tR_EntityDimlight( ent, ent->index );\n\t\t\telse if ( !REF_GET_PARM( PARM_MODERNFLASHLIGHT, 1))\n\t\t\t\tCL_UpdateFlashlight( ent );\n\t\t}\n\t}\n\telse\n\t{\n\t\t// from CL_LinkPacketEntities\n\t\tif( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD ))\n\t\t\tR_EntityParticles( ent );\n\n\t\tif( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT ))\n\t\t\tR_EntityBrightlight( ent, ent->index, 0 );\n\n\t\tif( FBitSet( ent->curstate.effects, EF_DIMLIGHT ))\n\t\t\tR_EntityDimlight( ent, ent->index );\n\n\t\tif( FBitSet( ent->curstate.effects, EF_LIGHT ))\n\t\t\tR_EntityLight( ent, ent->curstate.number );\n\t}\n\n\t// studio models are handle muzzleflashes difference\n\tif( FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ) && ent->model && ent->model->type == mod_alias )\n\t{\n\t\tdlight_t\t*dl = CL_AllocDlight( ent->index );\n\t\tvec3_t\tfv;\n\n\t\tClearBits( ent->curstate.effects, EF_MUZZLEFLASH );\n\t\tdl->color.r = dl->color.g = dl->color.b = 100;\n\t\tVectorCopy( ent->origin, dl->origin );\n\t\tAngleVectors( ent->angles, fv, NULL, NULL );\n\t\tdl->origin[2] += 16.0f;\n\t\tVectorMA( dl->origin, 18, fv, dl->origin );\n\t\tdl->radius = COM_RandomFloat( 200, 231 );\n\t\tdl->die = cl.time + 0.1;\n\t\tdl->minlight = 32;\n\t}\n}\n\n/*\n================\nCL_AddModelEffects\n\nthese effects will be enable by flag in model header\n================\n*/\nvoid CL_AddModelEffects( cl_entity_t *ent )\n{\n\tvec3_t\tneworigin;\n\tvec3_t\toldorigin;\n\n\tif( !ent->model || ent->player )\n\t\treturn;\n\n\tif( ent->model->type != mod_alias && ent->model->type != mod_studio )\n\t\treturn;\n\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t{\n\t\tVectorCopy( ent->baseline.vuser1, oldorigin );\n\t\tVectorCopy( ent->origin, ent->baseline.vuser1 );\n\t\tVectorCopy( ent->origin, neworigin );\n\t}\n\telse\n\t{\n\t\tVectorCopy( ent->prevstate.origin, oldorigin );\n\t\tVectorCopy( ent->curstate.origin, neworigin );\n\t}\n\n\t// NOTE: this completely over control about angles and don't broke interpolation\n\tif( FBitSet( ent->model->flags, STUDIO_ROTATE ))\n\t\tent->angles[1] = anglemod( 100.0f * cl.time );\n\n\tif( FBitSet( ent->model->flags, STUDIO_GIB ))\n\t\tR_RocketTrail( oldorigin, neworigin, 2 );\n\telse if( FBitSet( ent->model->flags, STUDIO_ZOMGIB ))\n\t\tR_RocketTrail( oldorigin, neworigin, 4 );\n\telse if( FBitSet( ent->model->flags, STUDIO_TRACER ))\n\t\tR_RocketTrail( oldorigin, neworigin, 3 );\n\telse if( FBitSet( ent->model->flags, STUDIO_TRACER2 ))\n\t\tR_RocketTrail( oldorigin, neworigin, 5 );\n\telse if( FBitSet( ent->model->flags, STUDIO_ROCKET ))\n\t{\n\t\tdlight_t\t*dl = CL_AllocDlight( ent->curstate.number );\n\n\t\tdl->color.r = dl->color.g = dl->color.b = 200;\n\t\tVectorCopy( ent->origin, dl->origin );\n\n\t\t// XASH SPECIFIC: get radius from head entity\n\t\tif( ent->curstate.rendermode != kRenderNormal )\n\t\t\tdl->radius = Q_max( 0, ent->curstate.renderamt - 55 );\n\t\telse dl->radius = 200;\n\n\t\tdl->die = cl.time + 0.01f;\n\n\t\tR_RocketTrail( oldorigin, neworigin, 0 );\n\t}\n\telse if( FBitSet( ent->model->flags, STUDIO_GRENADE ))\n\t\tR_RocketTrail( oldorigin, neworigin, 1 );\n\telse if( FBitSet( ent->model->flags, STUDIO_TRACER3 ))\n\t\tR_RocketTrail( oldorigin, neworigin, 6 );\n}\n\n/*\n================\nCL_TestLights\n\nif cl_testlights is set, create 32 lights models\n================\n*/\nvoid CL_TestLights( void )\n{\n\tint\ti, j, numLights;\n\tvec3_t\tforward, right;\n\tfloat\tf, r;\n\tdlight_t\t*dl;\n\n\tif( !cl_testlights.value )\n\t\treturn;\n\n\tnumLights = bound( 1, cl_testlights.value, MAX_DLIGHTS );\n\tAngleVectors( cl.viewangles, forward, right, NULL );\n\n\tfor( i = 0; i < numLights; i++ )\n\t{\n\t\tdl = &cl_dlights[i];\n\n\t\tr = 64 * ((i % 4) - 1.5f );\n\t\tf = 64 * ( i / 4) + 128;\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t\tdl->origin[j] = cl.simorg[j] + forward[j] * f + right[j] * r;\n\n\t\tdl->color.r = ((((i % 6) + 1) & 1)>>0) * 255;\n\t\tdl->color.g = ((((i % 6) + 1) & 2)>>1) * 255;\n\t\tdl->color.b = ((((i % 6) + 1) & 4)>>2) * 255;\n\t\tdl->radius = Q_max( 64, 200 - 5 * numLights );\n\t\tdl->die = cl.time + host.frametime;\n\t}\n}\n\n/*\n==============================================================\n\nDECAL MANAGEMENT\n\n==============================================================\n*/\n/*\n===============\nCL_FireCustomDecal\n\ncustom temporary decal\n===============\n*/\nvoid GAME_EXPORT CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale )\n{\n\tref.dllFuncs.R_DecalShoot( textureIndex, entityIndex, modelIndex, pos, flags, scale );\n}\n\n/*\n===============\nCL_DecalShoot\n\nnormal temporary decal\n===============\n*/\nvoid GAME_EXPORT CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags )\n{\n\tCL_FireCustomDecal( textureIndex, entityIndex, modelIndex, pos, flags, 1.0f );\n}\n\n/*\n===============\nCL_PlayerDecal\n\nspray custom colored decal (clan logo etc)\n===============\n*/\nstatic void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos )\n{\n\tint\t\ttextureIndex = 0;\n\tcustomization_t\t*pCust = NULL;\n\n\tif( playernum < MAX_CLIENTS )\n\t\tpCust = cl.players[playernum].customdata.pNext;\n\n\tif( pCust != NULL && pCust->pBuffer != NULL && pCust->pInfo != NULL )\n\t{\n\t\tif( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal && pCust->bTranslated )\n\t\t{\n\t\t\tif( !pCust->nUserData1 )\n\t\t\t{\n\t\t\t\tchar decalname[MAX_VA_STRING];\n\t\t\t\tint width, height;\n\n\t\t\t\tQ_snprintf( decalname, sizeof( decalname ), \"player%dlogo%d\", playernum, customIndex );\n\t\t\t\ttextureIndex = ref.dllFuncs.GL_FindTexture( decalname );\n\t\t\t\tif( textureIndex != 0 )\n\t\t\t\t\tref.dllFuncs.GL_FreeTexture( textureIndex );\n\n\t\t\t\tpCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL );\n\n\t\t\t\twidth = REF_GET_PARM( PARM_TEX_WIDTH, pCust->nUserData1 );\n\t\t\t\theight = REF_GET_PARM( PARM_TEX_HEIGHT, pCust->nUserData1 );\n\n\t\t\t\tif( width > cl_logomaxdim.value || height > cl_logomaxdim.value )\n\t\t\t\t{\n\t\t\t\t\tdouble scale = cl_logomaxdim.value / Q_max( width, height );\n\t\t\t\t\twidth = round( width * scale );\n\t\t\t\t\theight = round( height * scale );\n\t\t\t\t\tref.dllFuncs.R_OverrideTextureSourceSize( pCust->nUserData1, width, height ); // default custom decal from HL1\n\t\t\t\t}\n\t\t\t}\n\t\t\ttextureIndex = pCust->nUserData1;\n\t\t}\n\t}\n\n\tCL_DecalShoot( textureIndex, entityIndex, 0, pos, FDECAL_CUSTOM );\n}\n\n/*\n===============\nCL_DecalIndexFromName\n\nget decal global index from decalname\n===============\n*/\nint GAME_EXPORT CL_DecalIndexFromName( const char *name )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( name ))\n\t\treturn 0;\n\n\t// look through the loaded sprite name list for SpriteName\n\tfor( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( name, host.draw_decals[i] ))\n\t\t\treturn i;\n\t}\n\treturn 0; // invalid decal\n}\n\n/*\n===============\nCL_DecalIndex\n\nget texture index from decal global index\n===============\n*/\nint GAME_EXPORT CL_DecalIndex( int id )\n{\n\tid = bound( 0, id, MAX_DECALS - 1 );\n\n\tif( cl.decal_index[id] == 0 )\n\t{\n\t\tint gl_texturenum = 0;\n\n\t\tImage_SetForceFlags( IL_LOAD_DECAL );\n\n\t\tif( Mod_AllowMaterials( ))\n\t\t{\n\t\t\tstring decalname;\n\n\t\t\tif( Q_snprintf( decalname, sizeof( decalname ), \"materials/decals/%s.tga\", host.draw_decals[id] ) > 0 )\n\t\t\t{\n\t\t\t\tif( g_fsapi.FileExists( decalname, false ))\n\t\t\t\t{\n\t\t\t\t\tgl_texturenum = ref.dllFuncs.GL_LoadTexture( decalname, NULL, 0, TF_DECAL );\n\t\t\t\t\tif( host_allow_materials.value == 2.0f )\n\t\t\t\t\t\tCon_Printf( \"Looking for %s decal replacement...%s (%s)\\n\", host.draw_decals[id], gl_texturenum != 0 ? S_GREEN \"OK\" : S_RED \"FAIL\", decalname );\n\t\t\t\t}\n\t\t\t\telse if( host_allow_materials.value == 2.0f )\n\t\t\t\t\tCon_Printf( \"Looking for %s decal replacement...\" S_YELLOW \"MISS (%s)\\n\", host.draw_decals[id], decalname );\n\t\t\t}\n\t\t\telse if( host_allow_materials.value == 2.0f )\n\t\t\t\tCon_Printf( \"Looking for %s decal replacement...\" S_YELLOW \"MISS (overflow)\\n\", host.draw_decals[id] );\n\n\t\t\tif( gl_texturenum )\n\t\t\t{\n\t\t\t\tbyte *fin;\n\n\t\t\t\tQ_snprintf( decalname, sizeof( decalname ), \"decals.wad/%s\", host.draw_decals[id] );\n\n\t\t\t\tif(( fin = g_fsapi.LoadFile( decalname, NULL, false )) != NULL )\n\t\t\t\t{\n\t\t\t\t\tmip_t *mip = (mip_t *)fin;\n\t\t\t\t\tref.dllFuncs.R_OverrideTextureSourceSize( gl_texturenum, mip->width, mip->height );\n\t\t\t\t\tMem_Free( fin );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( !gl_texturenum )\n\t\t\tgl_texturenum = ref.dllFuncs.GL_LoadTexture( host.draw_decals[id], NULL, 0, TF_DECAL );\n\n\t\tcl.decal_index[id] = gl_texturenum;\n\t\tImage_ClearForceFlags();\n\t}\n\n\treturn cl.decal_index[id];\n}\n\n/*\n===============\nCL_DecalRemoveAll\n\nremove all decals with specified texture\n===============\n*/\nvoid GAME_EXPORT CL_DecalRemoveAll( int textureIndex )\n{\n\tint id = bound( 0, textureIndex, MAX_DECALS - 1 );\n\tref.dllFuncs.R_DecalRemoveAll( cl.decal_index[id] );\n}\n\n/*\n==============================================================\n\nEFRAGS MANAGEMENT\n\n==============================================================\n*/\n/*\n=======================\nR_ClearStaticEntities\n\ne.g. by demo request\n=======================\n*/\nvoid CL_ClearStaticEntities( void )\n{\n\tint\ti;\n\n\tif( host.type == HOST_DEDICATED )\n\t\treturn;\n\n\t// clear out efrags in case the level hasn't been reloaded\n\tfor( i = 0; i < cl.worldmodel->numleafs; i++ )\n\t\tcl.worldmodel->leafs[i+1].efrags = NULL;\n\n\tclgame.numStatics = 0;\n\n\tCL_ClearEfrags ();\n}\n\n/*\n==============\nCL_ClearEffects\n==============\n*/\nvoid CL_ClearEffects( void )\n{\n\tCL_ClearEfrags ();\n\tCL_ClearDlights ();\n\tCL_ClearTempEnts ();\n\tCL_ClearViewBeams ();\n\tCL_ClearParticles ();\n\tCL_ClearLightStyles ();\n}\n"
  },
  {
    "path": "engine/client/cl_tent.h",
    "content": "/*\ncl_tent.h - efx api set\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef CL_TENT_H\n#define CL_TENT_H\n\n#include \"triangleapi.h\"\n\n// EfxAPI\nstruct particle_s *R_AllocParticle( void (*callback)( struct particle_s*, float ));\nvoid R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags );\nvoid R_ParticleExplosion( const vec3_t org );\nvoid R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength );\nvoid R_Implosion( const vec3_t end, float radius, int count, float life );\nvoid R_Blood( const vec3_t org, const vec3_t dir, int pcolor, int speed );\nvoid R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed );\nvoid R_BlobExplosion( const vec3_t org );\nvoid R_EntityParticles( cl_entity_t *ent );\nvoid R_FlickerParticles( const vec3_t org );\nvoid R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count );\nvoid R_ParticleBurst( const vec3_t org, int size, int color, float life );\nvoid R_LavaSplash( const vec3_t org );\nvoid R_TeleportSplash( const vec3_t org );\nvoid R_RocketTrail( vec3_t start, vec3_t end, int type );\nshort R_LookupColor( byte r, byte g, byte b );\nvoid R_GetPackedColor( short *packed, short color );\nvoid R_TracerEffect( const vec3_t start, const vec3_t end );\nvoid R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( struct particle_s* ));\nstruct particle_s *R_TracerParticles( float *org, float *vel, float life );\nvoid R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life );\nvoid R_ParticleBox( const vec3_t mins, const vec3_t maxs, byte r, byte g, byte b, float life );\nvoid R_ShowLine( const vec3_t start, const vec3_t end );\nvoid R_BulletImpactParticles( const vec3_t pos );\nvoid R_SparkShower( const vec3_t org );\nstruct tempent_s *CL_TempEntAlloc( const vec3_t org, model_t *pmodel );\nstruct tempent_s *CL_TempEntAllocHigh( const vec3_t org, model_t *pmodel );\nstruct tempent_s *CL_TempEntAllocNoModel( const vec3_t org );\nstruct tempent_s *CL_TempEntAllocCustom( const vec3_t org, model_t *model, int high, void (*callback)( struct tempent_s*, float, float ));\nvoid R_FizzEffect( cl_entity_t *pent, int modelIndex, int density );\nvoid R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed );\nvoid R_BubbleTrail( const vec3_t start, const vec3_t end, float flWaterZ, int modelIndex, int count, float speed );\nvoid R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life );\nvoid R_KillAttachedTents( int client );\nvoid R_RicochetSprite( const vec3_t pos, model_t *pmodel, float duration, float scale );\nvoid R_RocketFlare( const vec3_t pos );\nvoid R_MuzzleFlash( const vec3_t pos, int type );\nvoid R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size );\nvoid R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t dir, float random, float life, int count, int modelIndex, char flags );\nstruct tempent_s *R_TempModel( const vec3_t pos, const vec3_t dir, const vec3_t angles, float life, int modelIndex, int soundtype );\nstruct tempent_s *R_TempSprite( vec3_t pos, const vec3_t dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags );\nstruct tempent_s *R_DefaultSprite( const vec3_t pos, int spriteIndex, float framerate );\nvoid R_Sprite_Explode( struct tempent_s *pTemp, float scale, int flags );\nvoid R_Sprite_Smoke( struct tempent_s *pTemp, float scale );\nvoid R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int iRand, int renderMode );\nvoid R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int iRand );\nvoid R_Sprite_Trail( int type, vec3_t vecStart, vec3_t vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed );\nvoid R_FunnelSprite( const vec3_t pos, int spriteIndex, int flags );\nvoid R_LargeFunnel( const vec3_t pos, int reverse );\nvoid R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax );\nvoid R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velMin, int velMax );\nvoid R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax );\nvoid R_Projectile( const vec3_t origin, const vec3_t velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s*, struct pmtrace_s* ));\nvoid R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex );\nvoid R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int count, int decalCount, int *decalIndices );\nvoid R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life );\nvoid R_PlayerSprites( int client, int modelIndex, int count, int size );\nvoid R_Sprite_WallPuff( struct tempent_s *pTemp, float scale );\nvoid R_RicochetSound( const vec3_t pos );\nstruct dlight_s *CL_AllocDlight( int key );\nstruct dlight_s *CL_AllocElight( int key );\nvoid CL_AddEntityEffects( cl_entity_t *ent );\nvoid CL_AddModelEffects( cl_entity_t *ent );\nvoid CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags );\nvoid CL_DecalRemoveAll( int textureIndex );\nint CL_DecalIndexFromName( const char *name );\nint CL_DecalIndex( int id );\n\n// RefAPI\nstruct particle_s *CL_AllocParticleFast( void );\n\n// Beams\nstruct beam_s *R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed );\nstruct beam_s *R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\nstruct beam_s *R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\nstruct beam_s *R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\nstruct beam_s *R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\nstruct beam_s *R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b );\nstruct beam_s *R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness );\nvoid R_BeamKill( int deadEntity );\n\n\n// TriAPI\nvoid TriRenderMode( int mode );\nvoid TriColor4f( float r, float g, float b, float a );\nvoid TriColor4ub( byte r, byte g, byte b, byte a );\nvoid TriBrightness( float brightness );\nvoid TriCullFace( TRICULLSTYLE mode );\nint TriWorldToScreen( const float *world, float *screen );\nint TriBoxInPVS( float *mins, float *maxs );\nvoid TriLightAtPoint( float *pos, float *value );\nvoid TriColor4fRendermode( float r, float g, float b, float a, int rendermode );\nint TriSpriteTexture( model_t *pSpriteModel, int frame );\n\nextern model_t\t*cl_sprite_dot;\nextern model_t\t*cl_sprite_shell;\n\n#endif//CL_TENT_H\n"
  },
  {
    "path": "engine/client/cl_video.c",
    "content": "/*\ncl_video.c - avi video player\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n\n/*\n=================================================================\n\nAVI PLAYING\n\n=================================================================\n*/\n\nstatic movie_state_t\t*cin_state;\n\n/*\n==================\nSCR_NextMovie\n\nCalled when a demo or cinematic finishes\nIf the \"nextmovie\" cvar is set, that command will be issued\n==================\n*/\nqboolean SCR_NextMovie( void )\n{\n\tstring\tstr;\n\n\tif( cls.movienum == -1 )\n\t{\n\t\tS_StopAllSounds( true );\n\t\tSCR_StopCinematic();\n\t\tCL_CheckStartupDemos();\n\t\treturn false; // don't play movies\n\t}\n\n\tif( !cls.movies[cls.movienum][0] || cls.movienum == MAX_MOVIES )\n\t{\n\t\tS_StopAllSounds( true );\n\t\tSCR_StopCinematic();\n\t\tcls.movienum = -1;\n\t\tCL_CheckStartupDemos();\n\t\treturn false;\n\t}\n\n\tQ_snprintf( str, MAX_STRING, \"movie %s full\\n\", cls.movies[cls.movienum] );\n\n\tCbuf_InsertText( str );\n\tcls.movienum++;\n\n\treturn true;\n}\n\nstatic void SCR_CreateStartupVids( void )\n{\n\tfile_t\t*f;\n\n\tf = FS_Open( DEFAULT_VIDEOLIST_PATH, \"w\", false );\n\tif( !f ) return;\n\n\t// make standard video playlist: sierra, valve\n\tFS_Print( f, \"media/sierra.avi\\n\" );\n\tFS_Print( f, \"media/valve.avi\\n\" );\n\tFS_Close( f );\n}\n\nvoid SCR_CheckStartupVids( void )\n{\n\tint\tc = 0;\n\tbyte *afile;\n\tchar *pfile;\n\tstring\ttoken;\n\n#if 0\n\tif( host_developer.value )\n\t{\n\t\t// don't run movies where we in developer-mode\n\t\tcls.movienum = -1;\n\t\tCL_CheckStartupDemos();\n\t\treturn;\n\t}\n#endif\n\n\tif( Sys_CheckParm( \"-nointro\" ) || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME )\n\t{\n\t\t// don't run movies where we in developer-mode\n\t\tcls.movienum = -1;\n\t\tCL_CheckStartupDemos();\n\t\treturn;\n\t}\n\n\tif( !FS_FileExists( DEFAULT_VIDEOLIST_PATH, false ))\n\t\tSCR_CreateStartupVids();\n\n\tafile = FS_LoadFile( DEFAULT_VIDEOLIST_PATH, NULL, false );\n\tif( !afile ) return; // something bad happens\n\n\tpfile = (char *)afile;\n\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tQ_strncpy( cls.movies[c], token, sizeof( cls.movies[0] ));\n\n\t\tif( ++c > MAX_MOVIES - 1 )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"too many movies (%d) specified in %s\\n\", MAX_MOVIES, DEFAULT_VIDEOLIST_PATH );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tMem_Free( afile );\n\n\t// run cinematic\n\tcls.movienum = 0;\n\tSCR_NextMovie ();\n\tCbuf_Execute();\n}\n\n/*\n==================\nSCR_RunCinematic\n==================\n*/\nvoid SCR_RunCinematic( void )\n{\n\tif( cls.state != ca_cinematic )\n\t\treturn;\n\n\tif( !AVI_IsActive( cin_state ))\n\t{\n\t\tSCR_NextMovie( );\n\t\treturn;\n\t}\n\n\tif( UI_IsVisible( ))\n\t{\n\t\t// these can happens when user set +menu_ option to cmdline\n\t\tAVI_CloseVideo( cin_state );\n\t\tcls.state = ca_disconnected;\n\t\tKey_SetKeyDest( key_menu );\n\t\tS_StopStreaming();\n\t\tcls.movienum = -1;\n\t\tcls.signon = 0;\n\t\treturn;\n\t}\n}\n\n/*\n==================\nSCR_DrawCinematic\n\nReturns true if a cinematic is active, meaning the view rendering\nshould be skipped\n==================\n*/\nqboolean SCR_DrawCinematic( void )\n{\n\tif( !ref.initialized )\n\t\treturn false;\n\n\tif( !AVI_Think( cin_state ))\n\t\treturn SCR_NextMovie();\n\n\treturn true;\n}\n\n/*\n==================\nSCR_PlayCinematic\n==================\n*/\nqboolean SCR_PlayCinematic( const char *arg )\n{\n\tint x, y, w, h;\n\tconst char\t*fullpath;\n\tdouble video_ratio, screen_ratio, scale;\n\n\tfullpath = FS_GetDiskPath( arg, false );\n\n\tif( FS_FileExists( arg, false ) && !fullpath )\n\t{\n\t\tCon_Printf( S_ERROR \"Couldn't load %s from packfile. Please extract it\\n\", arg );\n\t\treturn false;\n\t}\n\n\tAVI_OpenVideo( cin_state, fullpath, true, false );\n\tif( !AVI_IsActive( cin_state ) || !AVI_GetVideoInfo( cin_state, &w, &h, NULL ))\n\t{\n\t\tAVI_CloseVideo( cin_state );\n\t\treturn false;\n\t}\n\n\tvideo_ratio = (double)w / (double)h;\n\tscreen_ratio = (double)refState.width / (double)refState.height;\n\n\tif( video_ratio < screen_ratio )\n\t\tscale = (double)refState.height / (double)h;\n\telse\n\t\tscale = (double)refState.width / (double)w;\n\n\tw = Q_rint( w * scale );\n\th = Q_rint( h * scale );\n\n\tif( video_ratio < screen_ratio )\n\t{\n\t\tx = (refState.width - w) / 2.0;\n\t\ty = 0;\n\t}\n\telse\n\t{\n\t\tx = 0;\n\t\ty = (refState.height - h) / 2.0;\n\t}\n\n\tif( AVI_HaveAudioTrack( cin_state ))\n\t{\n\t\t// begin streaming\n\t\tS_StopAllSounds( true );\n\t\tS_StartStreaming();\n\t}\n\n\tAVI_SetParm( cin_state,\n\t\tAVI_RENDER_X, x,\n\t\tAVI_RENDER_Y, y,\n\t\tAVI_RENDER_W, w,\n\t\tAVI_RENDER_H, h,\n\t\tAVI_PARM_LAST );\n\n\tUI_SetActiveMenu( false );\n\tcls.state = ca_cinematic;\n\tCon_FastClose();\n\tcls.signon = 0;\n\n\treturn true;\n}\n\n/*\n==================\nSCR_StopCinematic\n==================\n*/\nvoid SCR_StopCinematic( void )\n{\n\tif( cls.state != ca_cinematic )\n\t\treturn;\n\n\tAVI_CloseVideo( cin_state );\n\tS_StopStreaming();\n\n\tcls.state = ca_disconnected;\n\tcls.signon = 0;\n\n\tUI_SetActiveMenu( true );\n}\n\n/*\n==================\nSCR_InitCinematic\n==================\n*/\nvoid SCR_InitCinematic( void )\n{\n\tAVI_Initailize ();\n\tcin_state = AVI_GetState( CIN_MAIN );\n}\n\n/*\n==================\nSCR_FreeCinematic\n==================\n*/\nvoid SCR_FreeCinematic( void )\n{\n\tmovie_state_t\t*cin_state;\n\n\t// release videos\n\tcin_state = AVI_GetState( CIN_LOGO );\n\tAVI_CloseVideo( cin_state );\n\n\tcin_state = AVI_GetState( CIN_MAIN );\n\tAVI_CloseVideo( cin_state );\n\n\tAVI_Shutdown();\n}\n"
  },
  {
    "path": "engine/client/cl_view.c",
    "content": "/*\ncl_view.c - player rendering positioning\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"const.h\"\n#include \"entity_types.h\"\n#include \"vgui_draw.h\"\n#include \"sound.h\"\n#include \"input.h\" // touch\n#include \"platform/platform.h\" // GL_UpdateSwapInterval\n\n/*\n===============\nV_CalcViewRect\n\ncalc frame rectangle (Quake1 style)\n===============\n*/\nstatic void V_CalcViewRect( void )\n{\n\tqboolean\tfull = false;\n\tint\tsb_lines;\n\tfloat\tsize;\n\n\tif( Host_IsQuakeCompatible( ))\n\t{\n\t\t// intermission is always full screen\n\t\tif( cl.intermission ) size = 120.0f;\n\t\telse size = scr_viewsize.value;\n\n\t\tif( size >= 120.0f )\n\t\t\tsb_lines = 0;\t\t// no status bar at all\n\t\telse if( size >= 110.0f )\n\t\t\tsb_lines = 24;\t\t// no inventory\n\t\telse sb_lines = 48;\n\n\t\tif( scr_viewsize.value >= 100.0f )\n\t\t{\n\t\t\tfull = true;\n\t\t\tsize = 100.0f;\n\t\t}\n\t\telse size = scr_viewsize.value;\n\n\t\tif( cl.intermission )\n\t\t{\n\t\t\tsize = 100.0f;\n\t\t\tsb_lines = 0;\n\t\t\tfull = true;\n\t\t}\n\t\tsize /= 100.0f;\n\t}\n\telse\n\t{\n\t\tfull = true;\n\t\tsb_lines = 0;\n\t\tsize = 1.0f;\n\t}\n\n\tclgame.viewport[2] = refState.width * size;\n\tclgame.viewport[3] = refState.height * size;\n\n\tif( clgame.viewport[3] > refState.height - sb_lines )\n\t\tclgame.viewport[3] = refState.height - sb_lines;\n\tif( clgame.viewport[3] > refState.height )\n\t\tclgame.viewport[3] = refState.height;\n\n\tclgame.viewport[0] = ( refState.width - clgame.viewport[2] ) / 2;\n\tif( full ) clgame.viewport[1] = 0;\n\telse clgame.viewport[1] = ( refState.height - sb_lines - clgame.viewport[3] ) / 2;\n\n}\n\n/*\n===============\nV_SetupViewModel\n===============\n*/\nstatic void V_SetupViewModel( void )\n{\n\tcl_entity_t\t*view = &clgame.viewent;\n\tplayer_info_t\t*info = &cl.players[cl.playernum];\n\n\tif( !cl.local.weaponstarttime )\n\t\tcl.local.weaponstarttime = cl.time;\n\n\t// setup the viewent variables\n\tview->curstate.colormap = (info->topcolor & 0xFFFF)|((info->bottomcolor << 8) & 0xFFFF);\n\tview->curstate.number = cl.playernum + 1;\n\tview->index = cl.playernum + 1;\n\tview->model = CL_ModelHandle( cl.local.viewmodel );\n\tview->curstate.modelindex = cl.local.viewmodel;\n\tview->curstate.sequence = cl.local.weaponsequence;\n\tview->curstate.rendermode = kRenderNormal;\n\n\t// alias models has another animation methods\n\tif( view->model && view->model->type == mod_studio )\n\t{\n\t\tview->curstate.animtime = cl.local.weaponstarttime;\n\t\tview->curstate.frame = 0.0f;\n\t}\n}\n\n/*\n===============\nV_SetRefParams\n===============\n*/\nstatic void V_SetRefParams( ref_params_t *fd )\n{\n\tmemset( fd, 0, sizeof( ref_params_t ));\n\n\t// probably this is not needs\n\tVectorCopy( refState.vieworg, fd->vieworg );\n\tVectorCopy( refState.viewangles, fd->viewangles );\n\n\tfd->frametime = host.frametime;\n\tfd->time = cl.time;\n\n\tfd->intermission = cl.intermission;\n\tfd->paused = (cl.paused != 0);\n\tfd->spectator = (cls.spectator != 0);\n\tfd->onground = (cl.local.onground != -1);\n\tfd->waterlevel = cl.local.waterlevel;\n\n\tVectorCopy( cl.simvel, fd->simvel );\n\tVectorCopy( cl.simorg, fd->simorg );\n\n\tVectorCopy( cl.viewheight, fd->viewheight );\n\tfd->idealpitch = cl.local.idealpitch;\n\n\tVectorCopy( cl.viewangles, fd->cl_viewangles );\n\tfd->health = cl.local.health;\n\tVectorCopy( cl.crosshairangle, fd->crosshairangle );\n\tif( Host_IsQuakeCompatible( ))\n\t\tfd->viewsize = scr_viewsize.value;\n\telse fd->viewsize = 120.0f;\n\n\tVectorCopy( cl.punchangle, fd->punchangle );\n\tfd->maxclients = cl.maxclients;\n\tfd->viewentity = cl.viewentity;\n\tfd->playernum = cl.playernum;\n\tfd->max_entities = clgame.maxEntities;\n\tfd->demoplayback = cls.demoplayback;\n\tfd->hardware = 1; // OpenGL\n\n\tif( cl.first_frame || cl.skip_interp )\n\t{\n\t\tcl.first_frame = false;\t\t// now can be unlocked\n\t\tfd->smoothing = true;\t\t// NOTE: currently this used to prevent ugly un-duck effect while level is changed\n\t}\n\telse fd->smoothing = cl.local.pushmsec;\t\t// enable smoothing in multiplayer by server request (AMX uses)\n\n\t// get pointers to movement vars and user cmd\n\tfd->movevars = &clgame.movevars;\n\tfd->cmd = &cl.cmd;\n\n\t// setup viewport\n\tfd->viewport[0] = clgame.viewport[0];\n\tfd->viewport[1] = clgame.viewport[1];\n\tfd->viewport[2] = clgame.viewport[2];\n\tfd->viewport[3] = clgame.viewport[3];\n\n\tfd->onlyClientDraw = 0;\t// reset clientdraw\n\tfd->nextView = 0;\t\t// reset nextview\n}\n\n/*\n===============\nV_MergeOverviewRefdef\n\nmerge refdef with overview settings\n===============\n*/\nstatic void V_RefApplyOverview( ref_viewpass_t *rvp )\n{\n\tref_overview_t\t*ov = &clgame.overView;\n\tfloat\t\taspect;\n\tfloat\t\tsize_x, size_y;\n\tvec2_t\t\tmins, maxs;\n\n\tif( !CL_IsDevOverviewMode( ))\n\t\treturn;\n\n\t// NOTE: Xash3D may use 16:9 or 16:10 aspects\n\taspect = (float)refState.width / (float)refState.height;\n\n\tsize_x = fabs( 8192.0f / ov->flZoom );\n\tsize_y = fabs( 8192.0f / (ov->flZoom * aspect ));\n\n\t// compute rectangle\n\tov->xLeft = -(size_x / 2);\n\tov->xRight = (size_x / 2);\n\tov->yTop = -(size_y / 2);\n\tov->yBottom = (size_y / 2);\n\n\tif( CL_IsDevOverviewMode() == 1 )\n\t{\n\t\tCon_NPrintf( 0, \" Overview: Zoom %.2f, Map Origin (%.2f, %.2f, %.2f), Z Min %.2f, Z Max %.2f, Rotated %i\\n\",\n\t\tov->flZoom, ov->origin[0], ov->origin[1], ov->origin[2], ov->zNear, ov->zFar, ov->rotated );\n\t}\n\n\tVectorCopy( ov->origin, rvp->vieworigin );\n\trvp->vieworigin[2] = ov->zFar + ov->zNear;\n\tVector2Copy( rvp->vieworigin, mins );\n\tVector2Copy( rvp->vieworigin, maxs );\n\n\tmins[!ov->rotated] += ov->xLeft;\n\tmaxs[!ov->rotated] += ov->xRight;\n\tmins[ov->rotated] += ov->yTop;\n\tmaxs[ov->rotated] += ov->yBottom;\n\n\trvp->viewangles[0] = 90.0f;\n\trvp->viewangles[1] = 90.0f;\n\trvp->viewangles[2] = (ov->rotated) ? (ov->flZoom < 0.0f) ? 180.0f : 0.0f : (ov->flZoom < 0.0f) ? -90.0f : 90.0f;\n\n\tSetBits( rvp->flags, RF_DRAW_OVERVIEW );\n\n\tref.dllFuncs.GL_OrthoBounds( mins, maxs );\n}\n\n/*\n====================\nV_CalcFov\n====================\n*/\nstatic float V_CalcFov( float *fov_x, float width, float height )\n{\n\tfloat\tx, half_fov_y;\n\n\tif( *fov_x < 1.0f || *fov_x > 179.0f )\n\t\t*fov_x = 90.0f; // default value\n\n\tx = width / tan( DEG2RAD( *fov_x ) * 0.5f );\n\thalf_fov_y = atan( height / x );\n\n\treturn RAD2DEG( half_fov_y ) * 2;\n}\n\n/*\n====================\nV_AdjustFov\n====================\n*/\nstatic void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x )\n{\n\tfloat x, y;\n\n\tif( width * 3 == 4 * height || width * 4 == height * 5 )\n\t{\n\t\t// 4:3 or 5:4 ratio\n\t\treturn;\n\t}\n\n\tif( lock_x )\n\t{\n\t\t*fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI_F / 360.0f * 0.5f )) * 360 / M_PI_F;\n\t\treturn;\n\t}\n\n\ty = V_CalcFov( fov_x, 640, 480 );\n\tx = *fov_x;\n\n\t*fov_x = V_CalcFov( &y, height, width );\n\tif( *fov_x < x ) *fov_x = x;\n\telse *fov_y = y;\n}\n\n/*\n=============\nV_GetRefParams\n=============\n*/\nstatic void V_GetRefParams( ref_params_t *fd, ref_viewpass_t *rvp )\n{\n\t// part1: deniable updates\n\tVectorCopy( fd->simvel, cl.simvel );\n\tVectorCopy( fd->simorg, cl.simorg );\n\tVectorCopy( fd->punchangle, cl.punchangle );\n\tVectorCopy( fd->viewheight, cl.viewheight );\n\n\t// part2: really used updates\n\tVectorCopy( fd->crosshairangle, cl.crosshairangle );\n\tVectorCopy( fd->cl_viewangles, cl.viewangles );\n\n\t// setup ref_viewpass\n\trvp->viewport[0] = fd->viewport[0];\n\trvp->viewport[1] = fd->viewport[1];\n\trvp->viewport[2] = fd->viewport[2];\n\trvp->viewport[3] = fd->viewport[3];\n\n\tVectorCopy( fd->vieworg, rvp->vieworigin );\n\tVectorCopy( fd->viewangles, rvp->viewangles );\n\n\trvp->viewentity = fd->viewentity;\n\n\t// calc FOV\n\trvp->fov_x = bound( 10.0f, cl.local.scr_fov, 150.0f ); // this is a final fov value\n\n\t// first we need to compute FOV and other things that needs for frustum properly work\n\trvp->fov_y = V_CalcFov( &rvp->fov_x, clgame.viewport[2], clgame.viewport[3] );\n\n\t// adjust FOV for widescreen\n\tif( refState.wideScreen && r_adjust_fov.value )\n\t\tV_AdjustFov( &rvp->fov_x, &rvp->fov_y, clgame.viewport[2], clgame.viewport[3], false );\n\n\trvp->flags = 0;\n\n\tif( fd->onlyClientDraw )\n\t\tSetBits( rvp->flags, RF_ONLY_CLIENTDRAW );\n\tSetBits( rvp->flags, RF_DRAW_WORLD );\n}\n\n/*\n==================\nV_PreRender\n\n==================\n*/\nqboolean V_PreRender( void )\n{\n\t// too early\n\tif( !ref.initialized )\n\t\treturn false;\n\n\tif( host.status == HOST_SLEEP )\n\t\treturn false;\n\n\t// if the screen is disabled (loading plaque is up)\n\tif( cls.disable_screen )\n\t{\n\t\tif(( host.realtime - cls.disable_screen ) > cl_timeout.value )\n\t\t{\n\t\t\tCon_Reportf( \"%s: loading plaque timed out\\n\", __func__ );\n\t\t\tcls.disable_screen = 0.0f;\n\t\t}\n\t\treturn false;\n\t}\n\n\tV_CheckGamma();\n\n\tref.dllFuncs.R_BeginFrame( !cl.paused && ( cls.state == ca_active ));\n\n\tGL_UpdateSwapInterval( );\n\n\treturn true;\n}\n\n//============================================================================\n\n/*\n==================\nV_RenderView\n\n==================\n*/\nvoid V_RenderView( void )\n{\n\t// HACKHACK: make ref params static\n\t// not really critical but allows client.dll to take address of refdef and don't trigger ASan\n\tstatic ref_params_t\trp;\n\tref_viewpass_t\trvp;\n\tint\t\tviewnum = 0;\n\n\tif( !cl.video_prepped || ( !ui_renderworld.value && UI_IsVisible() && !cl.background ))\n\t\treturn; // still loading\n\n\tV_CalcViewRect ();\t// compute viewport rectangle\n\tV_SetRefParams( &rp );\n\tV_SetupViewModel ();\n\tref.dllFuncs.R_Set2DMode( false );\n\tSCR_DirtyScreen();\n\tref.dllFuncs.GL_BackendStartFrame ();\n\n\tdo\n\t{\n\t\tclgame.dllFuncs.pfnCalcRefdef( &rp );\n\t\tV_GetRefParams( &rp, &rvp );\n\t\tV_RefApplyOverview( &rvp );\n\n\t\tif( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))\n\t\t{\n\t\t\tref.dllFuncs.R_ClearScreen();\n\t\t}\n\n\t\tGL_RenderFrame( &rvp );\n\t\tS_UpdateFrame( &rvp );\n\t\tviewnum++;\n\n\t} while( rp.nextView );\n\n\t// draw debug triangles on a server\n\tSV_DrawDebugTriangles ();\n\tref.dllFuncs.GL_BackendEndFrame ();\n}\n\n#define POINT_SIZE\t\t16.0f\n#define NODE_INTERVAL_X(x)\t(x * 16.0f)\n#define NODE_INTERVAL_Y(x)\t(x * 16.0f)\n\nstatic void R_DrawLeafNode( float x, float y, float scale )\n{\n\tfloat downScale = scale * 0.25f;// * POINT_SIZE;\n\n\tref.dllFuncs.R_DrawStretchPic( x - downScale * 0.5f, y - downScale * 0.5f, downScale, downScale, 0, 0, 1, 1, R_GetBuiltinTexture( REF_PARTICLE_TEXTURE ) );\n}\n\nstatic void R_DrawNodeConnection( float x, float y, float x2, float y2 )\n{\n\tref.dllFuncs.Begin( TRI_LINES );\n\t\tref.dllFuncs.Vertex3f( x, y, 0 );\n\t\tref.dllFuncs.Vertex3f( x2, y2, 0 );\n\tref.dllFuncs.End( );\n}\n\nstatic void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, mleaf_t *viewleaf )\n{\n\tfloat\tdownScale = scale * 0.8f;\n\n\tdownScale = Q_max( downScale, 1.0f );\n\n\tif( !node ) return;\n\n\tworld.recursion_level++;\n\n\tif( node->contents < 0 )\n\t{\n\t\tmleaf_t\t*leaf = (mleaf_t *)node;\n\n\t\tif( world.recursion_level > world.max_recursion )\n\t\t\tworld.max_recursion = world.recursion_level;\n\n\t\tif( shownodes == 1 )\n\t\t{\n\t\t\tif( cl.worldmodel->leafs == leaf )\n\t\t\t\tref.dllFuncs.Color4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\t\telse if( viewleaf && viewleaf == leaf )\n\t\t\t\tref.dllFuncs.Color4f( 1.0f, 0.0f, 0.0f, 1.0f );\n\t\t\telse ref.dllFuncs.Color4f( 0.0f, 1.0f, 0.0f, 1.0f );\n\t\t\tR_DrawLeafNode( x, y, scale );\n\t\t}\n\t\tworld.recursion_level--;\n\t\treturn;\n\t}\n\n\tif( shownodes == 1 )\n\t{\n\t\tref.dllFuncs.Color4f( 0.0f, 0.0f, 1.0f, 1.0f );\n\t\tR_DrawLeafNode( x, y, scale );\n\t}\n\telse if( shownodes == 2 )\n\t{\n\t\tR_DrawNodeConnection( x, y, x - scale, y + scale );\n\t\tR_DrawNodeConnection( x, y, x + scale, y + scale );\n\t}\n\n\tR_ShowTree_r( node_child( node, 1, cl.worldmodel ), x - scale, y + scale, downScale, shownodes, viewleaf );\n\tR_ShowTree_r( node_child( node, 0, cl.worldmodel ), x + scale, y + scale, downScale, shownodes, viewleaf );\n\n\tworld.recursion_level--;\n}\n\nstatic void R_ShowTree( void )\n{\n\tfloat\tx = (float)((refState.width - (int)POINT_SIZE) >> 1);\n\tfloat\ty = NODE_INTERVAL_Y(1.0f);\n\tmleaf_t *viewleaf;\n\n\tif( !cl.worldmodel || !r_showtree.value )\n\t\treturn;\n\n\tworld.recursion_level = 0;\n\tviewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );\n\n\tref.dllFuncs.TriRenderMode( kRenderTransTexture );\n\n\t//pglLineWidth( 2.0f );\n\tref.dllFuncs.Color4f( 1, 0.7f, 0, 1.0f );\n\t//pglDisable( GL_TEXTURE_2D );\n\tR_ShowTree_r( cl.worldmodel->nodes, x, y, world.max_recursion * 3.5f, 2, viewleaf );\n\t//pglEnable( GL_TEXTURE_2D );\n\t//pglLineWidth( 1.0f );\n\n\tR_ShowTree_r( cl.worldmodel->nodes, x, y, world.max_recursion * 3.5f, 1, viewleaf );\n\n\tCon_NPrintf( 0, \"max recursion %d\\n\", world.max_recursion );\n}\n\n/*\n==================\nV_PostRender\n\n==================\n*/\nvoid V_PostRender( void )\n{\n\tqboolean\t\tdraw_2d = false;\n\n\tref.dllFuncs.R_AllowFog( false );\n\tref.dllFuncs.R_Set2DMode( true );\n\n\tif( cls.state == ca_active && cls.signon == SIGNONS && cls.scrshot_action != scrshot_mapshot )\n\t{\n\t\tSCR_TileClear();\n\t\tCL_DrawHUD( CL_ACTIVE );\n\t\tVGui_Paint();\n\t}\n\n\tswitch( cls.scrshot_action )\n\t{\n\tcase scrshot_inactive:\n\tcase scrshot_normal:\n\tcase scrshot_snapshot:\n\t\tdraw_2d = true;\n\t\tbreak;\n\t}\n\n\tif( draw_2d )\n\t{\n\t\tSCR_RSpeeds();\n\t\tSCR_NetSpeeds();\n\t\tSCR_DrawPos();\n\t\tSCR_DrawEnts();\n\t\tSCR_DrawNetGraph();\n\t\tSCR_DrawUserCmd();\n\t\tSV_DrawOrthoTriangles();\n\t\tCL_DrawDemoRecording();\n\t\tCL_DrawHUD( CL_CHANGELEVEL );\n\t\tref.dllFuncs.R_ShowTextures();\n\t\tR_ShowTree();\n\t\tCon_DrawConsole();\n\t\tUI_UpdateMenu( host.realtime );\n\t\tCon_DrawVersion();\n\t\tCon_DrawDebug(); // must be last\n\t\tTouch_Draw();\n\t\tOSK_Draw();\n\n\t\tS_ExtraUpdate();\n\t}\n\n\tSCR_MakeScreenShot();\n\tref.dllFuncs.R_AllowFog( true );\n\tPlatform_SetTimer( 0.0f );\n\tref.dllFuncs.R_EndFrame();\n\n\tV_CheckGammaEnd();\n}\n"
  },
  {
    "path": "engine/client/client.h",
    "content": "/*\nclient.h - primary header for client\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef CLIENT_H\n#define CLIENT_H\n\n#include \"xash3d_types.h\"\n#include \"xash3d_mathlib.h\"\n#include \"cdll_int.h\"\n#include \"menu_int.h\"\n#include \"cl_entity.h\"\n#include \"mod_local.h\"\n#include \"pm_defs.h\"\n#include \"pm_movevars.h\"\n#include \"ref_params.h\"\n#include \"render_api.h\"\n#include \"cdll_exp.h\"\n#include \"screenfade.h\"\n#include \"protocol.h\"\n#include \"netchan.h\"\n#include \"net_api.h\"\n#include \"world.h\"\n#include \"ref_common.h\"\n#include \"voice.h\"\n\n// client sprite types\n#define SPR_CLIENT\t\t0\t// client sprite for temp-entities or user-textures\n#define SPR_HUDSPRITE\t1\t// hud sprite\n#define SPR_MAPSPRITE\t2\t// contain overview.bmp that diced into frames 128x128\n\n//=============================================================================\ntypedef struct netbandwithgraph_s\n{\n\tword\t\tclient;\n\tword\t\tplayers;\n\tword\t\tentities;\t\t// entities bytes, except for players\n\tword\t\ttentities;\t// temp entities\n\tword\t\tsound;\n\tword\t\tevent;\n\tword\t\tusr;\n\tword\t\tmsgbytes;\n\tword\t\tvoicebytes;\n} netbandwidthgraph_t;\n\ntypedef struct frame_s\n{\n\t// received from server\n\tdouble\t\treceivedtime;\t// time message was received, or -1\n\tdouble\t\tlatency;\n\tdouble\t\ttime;\t\t// server timestamp\n\tqboolean\t\tvalid;\t\t// cleared if delta parsing was invalid\n\tqboolean\t\tchoked;\n\n\tclientdata_t\tclientdata;\t// local client private data\n\tentity_state_t\tplayerstate[MAX_CLIENTS];\n\tweapon_data_t\tweapondata[MAX_LOCAL_WEAPONS];\n\tnetbandwidthgraph_t graphdata;\n\tbyte\t\tflags[MAX_VISIBLE_PACKET_VIS_BYTES];\n\tint\t\tnum_entities;\n\tint\t\tfirst_entity;\t// into the circular cl_packet_entities[]\n} frame_t;\n\ntypedef struct runcmd_s\n{\n\tdouble\t\tsenttime;\n\tdouble\t\treceivedtime;\n\tfloat\t\tframe_lerp;\n\n\tusercmd_t\t\tcmd;\n\n\tqboolean\t\tprocessedfuncs;\n\tqboolean\t\theldback;\n\tint\t\tsendsize;\n} runcmd_t;\n\n// add angles\ntypedef struct\n{\n\tfloat\t\tstarttime;\n\tfloat\t\ttotal;\n} pred_viewangle_t;\n\n#define ANGLE_BACKUP\t16\n#define ANGLE_MASK\t\t(ANGLE_BACKUP - 1)\n\n#define CL_UPDATE_MASK\t(CL_UPDATE_BACKUP - 1)\n#if XASH_LOW_MEMORY == 2\n#define CL_UPDATE_BACKUP SINGLEPLAYER_BACKUP\n#else\nextern int CL_UPDATE_BACKUP;\n#endif\n\n#define SIGNONS\t\t2\t\t// signon messages to receive before connected\n#define INVALID_HANDLE\t0xFFFF\t\t// for XashXT cache system\n\n#define MIN_UPDATERATE\t10.0f\n#define MAX_UPDATERATE\t102.0f\n\n#define MAX_EX_INTERP\t0.1f\n\n#define CL_MIN_RESEND_TIME\t1.5f\t\t// mininum time gap (in seconds) before a subsequent connection request is sent.\n#define CL_MAX_RESEND_TIME\t20.0f\t\t// max time.  The cvar cl_resend is bounded by these.\n\n#define cl_serverframetime()\t(cl.mtime[0] - cl.mtime[1])\n#define cl_clientframetime()\t(cl.time - cl.oldtime)\n\ntypedef struct\n{\n\t// got from prediction system\n\tvec3_t\t\tpredicted_origins[CMD_BACKUP];\n\tvec3_t\t\tprediction_error;\n\tvec3_t\t\tlastorigin;\n\tint\t\tlastground;\n\n\t// interp info\n\tfloat\t\tinterp_amount;\n\n\t// misc local info\n\tqboolean\t\trepredicting;\t// repredicting in progress\n\tqboolean\t\tapply_effects;\t// local player will not added but we should apply their effects: flashlight etc\n\tfloat\t\tidealpitch;\n\tint\t\tviewmodel;\n\tint\t\thealth;\t\t// client health\n\tint\t\tonground;\n\tint\t\tlight_level;\n\tint\t\twaterlevel;\n\tint\t\tusehull;\n\tqboolean\tmoving;\n\tint\t\tpushmsec;\n\tint\t\tweapons;\n\tfloat\t\tmaxspeed;\n\tfloat\t\tscr_fov;\n\n\t// weapon predict stuff\n\tint\t\tweaponsequence;\n\tfloat\t\tweaponstarttime;\n} cl_local_data_t;\n\ntypedef struct\n{\n\tqboolean\t\tbUsed;\n\tfloat\t\tfTime;\n\tint\t\tnBytesRemaining;\n} downloadtime_t;\n\ntypedef struct\n{\n\tqboolean\t\tdoneregistering;\n\tint\t\tpercent;\n\tqboolean\t\tdownloadrequested;\n\tdownloadtime_t\trgStats[8];\n\tint\t\tnCurStat;\n\tint\t\tnTotalSize;\n\tint\t\tnTotalToTransfer;\n\tint\t\tnRemainingToTransfer;\n\tfloat\t\tfLastStatusUpdate;\n\tqboolean\t\tcustom;\n} incomingtransfer_t;\n\n// the client_t structure is wiped completely\n// at every server map change\ntypedef struct\n{\n\t// ==== shared through RefAPI's ref_client_t ====\n\tdouble\t\ttime;\t\t\t// this is the time value that the client\n\t\t\t\t\t\t// is rendering at.  always <= cls.realtime\n\t\t\t\t\t\t// a lerp point for other data\n\tdouble\t\toldtime;\t\t\t// previous cl.time, time-oldtime is used\n\t\t\t\t\t\t// to decay light values and smooth step ups\n\tint\t\tviewentity;\n\n\t// server state information\n\tint\t\tplayernum;\n\tint\t\tmaxclients;\n\n\tint\t\tnummodels;\n\tmodel_t\t\t*models[MAX_MODELS+1];\t\t// precached models (plus sentinel slot)\n\n\tqboolean\tpaused;\n\n\tvec3_t\t\tsimorg;\t\t\t// predicted origin\n\t// ==== shared through RefAPI's ref_client_t ===\n\n\tint\t\tservercount;\t\t// server identification for prespawns\n\tint\t\tvalidsequence;\t\t// this is the sequence number of the last good\n\t\t\t\t\t\t// world snapshot/update we got.  If this is 0, we can't\n\t\t\t\t\t\t// render a frame yet\n\tint\t\tparsecount;\t\t// server message counter\n\tint\t\tparsecountmod;\t\t// modulo with network window\n\n\tqboolean\t\tvideo_prepped;\t\t// false if on new level or new ref dll\n\tqboolean\t\taudio_prepped;\t\t// false if on new level or new snd dll\n\n\tint\t\tdelta_sequence;\t\t// acknowledged sequence number\n\n\tdouble\t\tmtime[2];\t\t\t// the timestamp of the last two messages\n\tfloat\t\tlerpFrac;\n\n\tint\t\tlast_command_ack;\n\tint\t\tlast_incoming_sequence;\n\n\tqboolean\t\tbackground;\t\t// not real game, just a background\n\tqboolean\t\tfirst_frame;\t\t// first rendering frame\n\tqboolean\t\tproxy_redirect;\t\t// spectator stuff\n\tqboolean\t\tskip_interp;\t\t// skip interpolation this frame\n\n\tuint\t\tchecksum;\t\t\t// for catching cheater maps\n\n\tframe_t\t\tframes[MULTIPLAYER_BACKUP];\t\t// alloced on svc_serverdata\n\truncmd_t\t\tcommands[MULTIPLAYER_BACKUP];\t\t// each mesage will send several old cmds\n\tlocal_state_t\tpredicted_frames[MULTIPLAYER_BACKUP];\t// local client state\n\n\tdouble\t\ttimedelta;\t\t// floating delta between two updates\n\n\tchar\t\tserverinfo[MAX_SERVERINFO_STRING];\n\tplayer_info_t\tplayers[MAX_CLIENTS];\t// collected info about all other players include himself\n\tdouble\t\tlastresourcecheck;\n\tqboolean\t\thttp_download;\n\tevent_state_t\tevents;\n\n\t// predicting stuff but not only...\n\tcl_local_data_t\tlocal;\n\n\t// player final info\n\tusercmd_t\tcmd;\t\t\t// cl.commands[outgoing_sequence].cmd\n\tvec3_t\t\tviewangles;\n\tvec3_t\t\tviewheight;\n\tvec3_t\t\tpunchangle;\n\n\tint\t\tintermission;\t\t// don't change view angle, full screen, et\n\tvec3_t\t\tcrosshairangle;\n\n\tpred_viewangle_t\tpredicted_angle[ANGLE_BACKUP];// accumulate angles from server\n\tint\t\tangle_position;\n\tfloat\t\taddangletotal;\n\tfloat\t\tprevaddangletotal;\n\n\t// predicted velocity\n\tvec3_t\t\tsimvel;\n\n\tentity_state_t\tinstanced_baseline[MAX_CUSTOM_BASELINES];\n\tint\t\tinstanced_baseline_count;\n\n\tchar\t\tsound_precache[MAX_SOUNDS][MAX_QPATH];\n\tchar\t\tevent_precache[MAX_EVENTS][MAX_QPATH];\n\tchar\t\tfiles_precache[MAX_CUSTOM][MAX_QPATH];\n\tlightstyle_t\tlightstyles[MAX_LIGHTSTYLES];\n\tint\t\tnumfiles;\n\n\tconsistency_t\tconsistency_list[MAX_MODELS];\n\tint\t\tnum_consistency;\n\n\tqboolean\t\tneed_force_consistency_response;\n\tresource_t\tresourcesonhand;\n\tresource_t\tresourcesneeded;\n\tresource_t\tresourcelist[MAX_RESOURCES];\n\tint\t\tnum_resources;\n\n\tshort\t\tsound_index[MAX_SOUNDS];\n\tshort\t\tdecal_index[MAX_DECALS];\n\n\tmodel_t\t\t*worldmodel;\t\t\t// pointer to world\n\n\tint lostpackets;\t\t\t\t\t// count lost packets and show dialog in menu\n\n\tdouble frametime_remainder;\n\n\tuint worldmapCRC;\n} client_t;\n\n/*\n==================================================================\n\nthe client_static_t structure is persistant through an arbitrary number\nof server connections\n\n==================================================================\n*/\ntypedef enum\n{\n\tscrshot_inactive,\n\tscrshot_normal,\t// in-game screenshot\n\tscrshot_snapshot,\t// in-game snapshot\n\tscrshot_plaque,  \t// levelshot\n\tscrshot_savegame,\t// saveshot\n\tscrshot_envshot,\t// cubemap view\n\tscrshot_skyshot,\t// skybox view\n\tscrshot_mapshot\t// overview layer\n} scrshot_t;\n\n// client screen state\ntypedef enum\n{\n\tCL_LOADING = 1,\t// draw loading progress-bar\n\tCL_ACTIVE,\t// draw normal hud\n\tCL_PAUSED,\t// pause when active\n\tCL_CHANGELEVEL,\t// draw 'loading' during changelevel\n} scrstate_t;\n\ntypedef struct\n{\n\tchar\t\tname[32];\n\tint\t\tnumber;\t// svc_ number\n\tint\t\tsize;\t// if size == -1, size come from first byte after svcnum\n\tpfnUserMsgHook\tfunc;\t// user-defined function\n} cl_user_message_t;\n\ntypedef void (*pfnEventHook)( event_args_t *args );\n\ntypedef struct\n{\n\tchar\t\tname[MAX_QPATH];\n\tword\t\tindex;\t// event index\n\tpfnEventHook\tfunc;\t// user-defined function\n} cl_user_event_t;\n\n#define FONT_FIXED      0\n#define FONT_VARIABLE   1\n\n#define FONT_DRAW_HUD      BIT( 0 ) // pass to drawing function to apply hud_scale\n#define FONT_DRAW_UTF8     BIT( 1 ) // call UtfProcessChar\n#define FONT_DRAW_FORCECOL BIT( 2 ) // ignore colorcodes\n#define FONT_DRAW_NORENDERMODE BIT( 3 ) // ignore font's default rendermode\n#define FONT_DRAW_NOLF     BIT( 4 ) // ignore \\n\n#define FONT_DRAW_RESETCOLORONLF BIT( 5 ) // yet another flag to simulate consecutive Con_DrawString calls...\n\ntypedef struct\n{\n\tint      hFontTexture;    // handle to texture\n\twrect_t  fontRc[256];     // tex coords\n\tfloat    scale;           // scale factor\n\tbyte     charWidths[256]; // scaled widths\n\tint      charHeight;      // scaled height\n\tint      type;            // fixed width font or variable\n\tconvar_t *rendermode;     // user-defined default rendermode\n\tqboolean\tvalid;           // all rectangles are valid\n} cl_font_t;\n\ntypedef struct scissor_state_s\n{\n\tint x;\n\tint y;\n\tint width;\n\tint height;\n\tqboolean test;\n} scissor_state_t;\n\ntypedef struct\n{\n\t// scissor test\n\tscissor_state_t scissor;\n\n\t// temp handle\n\tconst model_t\t*pSprite;\t\t\t// pointer to current SpriteTexture\n\n\tint\t\trenderMode;\t\t// override kRenderMode from TriAPI\n\tTRICULLSTYLE\tcullMode;\t\t\t// override CULL FACE from TriAPI\n\n\t// holds text color\n\trgba_t\t\ttextColor;\n\trgba_t\t\tspriteColor;\n\tvec4_t\t\ttriRGBA;\n\n\t// crosshair members\n\tconst model_t\t*pCrosshair;\n\twrect_t\t\trcCrosshair;\n\trgba_t\t\trgbaCrosshair;\n} client_draw_t;\n\ntypedef struct cl_predicted_player_s\n{\n\tint\t\tmovetype;\n\tint\t\tsolid;\n\tint\t\tusehull;\n\tqboolean\t\tactive;\n\tvec3_t\t\torigin;\t\t// interpolated origin\n\tvec3_t\t\tangles;\n} predicted_player_t;\n\ntypedef struct\n{\n\t// scissor test\n\tscissor_state_t scissor;\n\n\tint\t\tgl_texturenum;\t// this is a real texnum\n\n\t// holds text color\n\trgba_t\t\ttextColor;\n} gameui_draw_t;\n\ntypedef struct\n{\n\tchar\t\tszListName[MAX_QPATH];\n\tclient_sprite_t\t*pList;\n\tint\t\tcount;\n} cached_spritelist_t;\n\ntypedef struct\n{\n\t// centerprint stuff\n\tfloat\t\ttime;\n\tint\t\ty, lines;\n\tchar\t\tmessage[2048];\n\tint\t\ttotalWidth;\n\tint\t\ttotalHeight;\n} center_print_t;\n\ntypedef struct\n{\n\tfloat\t\ttime;\n\tfloat\t\tduration;\n\tfloat\t\tamplitude;\n\tfloat\t\tfrequency;\n\tfloat\t\tnext_shake;\n\tvec3_t\t\toffset;\n\tfloat\t\tangle;\n\tvec3_t\t\tapplied_offset;\n\tfloat\t\tapplied_angle;\n} screen_shake_t;\n\ntypedef struct\n{\n\tnet_response_t\t\tresp;\n\tnet_api_response_func_t\tpfnFunc;\n\tdouble\t\t\ttimeout;\n\tdouble\t\t\ttimesend;\t// time when request was sended\n\tint\t\t\tflags;\t// FNETAPI_MULTIPLE_RESPONSE etc\n} net_request_t;\n\n// new versions of client dlls have a single export with all callbacks\ntypedef void (*CL_EXPORT_FUNCS)( void *pv );\n\ntypedef struct\n{\n\tvoid\t\t*hInstance;\t\t// pointer to client.dll\n\tcldll_func_t\tdllFuncs;\t\t\t// dll exported funcs\n\trender_interface_t\tdrawFuncs;\t\t// custom renderer support\n\tpoolhandle_t      mempool;\t\t\t// client edicts pool\n\tstring\t\tmapname;\t\t\t// map name\n\tstring\t\tmaptitle;\t\t\t// display map title\n\tstring\t\titemspath;\t\t// path to items description for auto-complete func\n\n\tcl_entity_t\t*entities;\t\t// dynamically allocated entity array\n\tcl_entity_t\t*static_entities;\t\t// dynamically allocated static entity array\n\tremap_info_t\t**remap_info;\t\t// store local copy of all remap textures for each entity\n\n\tint\t\tmaxEntities;\n\tint\t\tmaxRemapInfos;\t\t// maxEntities + cl.viewEnt; also used for catch entcount\n\tint\t\tnumStatics;\t\t// actual static entity count\n\tint\t\tmaxModels;\n\n\t// movement values from server\n\tmovevars_t\tmovevars;\n\tmovevars_t\toldmovevars;\n\tplayermove_t\t*pmove;\t\t\t// pmove state\n\n\tqboolean\t\tpushed;\t\t\t// used by PM_Push\\Pop state\n\tint\t\toldviscount;\t\t// used by PM_Push\\Pop state\n\tint\t\toldphyscount;\t\t// used by PM_Push\\Pop state\n\n\tcl_user_message_t\tmsg[MAX_USER_MESSAGES];\t// keep static to avoid fragment memory\n\tcl_user_event_t\t*events[MAX_EVENTS];\n\n\tstring\t\tcdtracks[MAX_CDTRACKS];\t// 32 cd-tracks read from cdaudio.txt\n\n\tmodel_t\t\tsprites[MAX_CLIENT_SPRITES];\t// hud&client spritetexturesz\n\tint\t\tviewport[4];\t\t// viewport sizes\n\n\tclient_draw_t\tds;\t\t\t// draw2d stuff (hud, weaponmenu etc)\n\tscreenfade_t\tfade;\t\t\t// screen fade\n\tscreen_shake_t\tshake;\t\t\t// screen shake\n\tcenter_print_t\tcenterPrint;\t\t// centerprint variables\n\tSCREENINFO\tscrInfo;\t\t\t// actual screen info\n\tref_overview_t\toverView;\t\t\t// overView params\n\tcolor24\t\tpalette[256];\t\t// palette used for particle colors\n\n\tcached_spritelist_t\tsprlist[MAX_CLIENT_SPRITES];\t// client list sprites\n\n\tclient_textmessage_t *titles;\t\t\t// title messages, not network messages\n\tint\t\tnumTitles;\n\n\tnet_request_t\tnet_requests[MAX_REQUESTS];\t// no reason to keep more\n\n\tcl_entity_t\tviewent;\t\t\t// viewmodel\n\n#if XASH_WIN32\n\tqboolean client_dll_uses_sdl;\n#endif\n} clgame_static_t;\n\ntypedef struct\n{\n\tvoid\t\t*hInstance;\t\t// pointer to client.dll\n\tUI_FUNCTIONS\tdllFuncs;\t\t\t// dll exported funcs\n\tUI_EXTENDED_FUNCTIONS dllFuncs2;\t// fwgs extension\n\tpoolhandle_t      mempool;\t\t\t// client edicts pool\n\n\tcl_entity_t\tplayermodel;\t\t// uiPlayerSetup drawing model\n\tplayer_info_t\tplayerinfo;\t\t// local playerinfo\n\n\tgameui_draw_t\tds;\t\t\t// draw2d stuff (menu images)\n\tgameinfo2_t\t\tgameInfo;\t// current gameInfo\n\tgameinfo2_t\t\t*modsInfo;\t// simplified gameInfo for MainUI, allocated by demand\n\tGAMEINFO\t\t**oldModsInfo;\t// simplified gameInfo for older MainUI, allocated by demand\n\n\tui_globalvars_t\t*globals;\n\n\tqboolean\t\tdrawLogo;\t\t\t// set to TRUE if logo.avi missed or corrupted\n\tint\t\tlogo_xres;\n\tint\t\tlogo_yres;\n\tfloat\t\tlogo_length;\n\n\tqboolean use_extended_api;\n} gameui_static_t;\n\ntypedef struct\n{\n\tconnstate_t\tstate;\n\tqboolean\t\tinitialized;\n\tqboolean\t\tchangelevel;\t\t// during changelevel\n\tqboolean\t\tchangedemo;\t\t// during changedemo\n\tdouble\t\ttimestart;\t\t// just for profiling\n\n\t// screen rendering information\n\tfloat\t\tdisable_screen;\t\t// showing loading plaque between levels\n\t\t\t\t\t\t// or changing rendering dlls\n\t\t\t\t\t\t// if time gets > 30 seconds ahead, break it\n\tqboolean\t\tdraw_changelevel;\t\t// draw changelevel image 'Loading...'\n\n\tkeydest_t\t\tkey_dest;\n\n\tpoolhandle_t      mempool;\t\t\t// client premamnent pool: edicts etc\n\n\tint\t\tsignon;\t\t\t// 0 to SIGNONS, for the signon sequence.\n\n\t// connection information\n\tchar\t\tservername[MAX_QPATH];\t// name of server from original connect\n\tdouble\t\tconnect_time;\t\t// for connection retransmits\n\tint\t\tmax_fragment_size;\t\t// we needs to test a real network bandwidth\n\tint\t\tconnect_retry;\t\t// how many times we send a connect packet to the server\n\tqboolean\t\tspectator;\t\t// not a real player, just spectator\n\n\tlocal_state_t\tspectator_state;\t\t// init as client startup\n\n\tchar\t\tuserinfo[MAX_INFO_STRING];\n\tchar\t\tphysinfo[MAX_INFO_STRING];\t// read-only\n\n\tsizebuf_t\t\tdatagram;\t\t\t// unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second.\n\tbyte\t\tdatagram_buf[MAX_DATAGRAM];\n\n\tnetchan_t\t\tnetchan;\n\n\tfloat\t\tpacket_loss;\n\tdouble\t\tpacket_loss_recalc_time;\n\tint\t\tstarting_count;\t\t// message num readed bits\n\n\tfloat\t\tnextcmdtime;\t\t// when can we send the next command packet?\n\tint\t\tlastoutgoingcommand;\t// sequence number of last outgoing command\n\tint\t\tlastupdate_sequence;\t// prediction stuff\n\n\tint\t\ttd_lastframe;\t\t// to meter out one message a frame\n\tint\t\ttd_startframe;\t\t// host_framecount at start\n\tdouble\t\ttd_starttime;\t\t// realtime at second frame of timedemo\n\tint\t\tforcetrack;\t\t// -1 = use normal cd track\n\n\t// game images\n\tint\t\tpauseIcon;\t\t// draw 'paused' when game in-pause\n\tint\t\ttileImage;\t\t// for draw any areas not covered by the refresh\n\tint\t\tloadingBar;\t\t// 'loading' progress bar\n\tcl_font_t\t\tcreditsFont;\t\t// shared creditsfont\n\n\tfloat\t\tlatency;\t\t\t// rolling average of frame latencey (receivedtime - senttime) values.\n\n\tint\t\tnum_client_entities;\t// cl.maxclients * CL_UPDATE_BACKUP * MAX_PACKET_ENTITIES\n\tint\t\tnext_client_entities;\t// next client_entity to use\n\tentity_state_t\t*packet_entities;\t\t// [num_client_entities]\n\n\tpredicted_player_t\tpredicted_players[MAX_CLIENTS];\n\tdouble\t\tcorrection_time;\n\n\tscrshot_t\t\tscrshot_request;\t\t// request for screen shot\n\tscrshot_t\t\tscrshot_action;\t\t// in-action\n\tconst float\t*envshot_vieworg;\t\t// envshot position\n\tint\t\tenvshot_viewsize;\t\t// override cvar\n\tqboolean\t\tenvshot_disable_vis;\t// disable VIS on server while makes an envshots\n\tstring\t\tshotname;\n\n\t// download info\n\tincomingtransfer_t\tdl;\n\n\t// demo loop control\n\tint\t\tdemonum;\t\t\t// -1 = don't play demos\n\tint\t\tolddemonum;\t\t// restore playing\n\tchar\t\tdemos[MAX_DEMOS][MAX_QPATH];\t// when not playing\n\tqboolean\t\tdemos_pending;\n\n\t// movie playlist\n\tint\t\tmovienum;\n\tstring\t\tmovies[MAX_MOVIES];\n\n\t// demo recording info must be here, so it isn't clearing on level change\n\tqboolean\t\tdemorecording;\n\tint\t\t\tdemoplayback;\n\tqboolean\t\tdemowaiting;\t\t// don't record until a non-delta message is received\n\tqboolean\t\ttimedemo;\n\tstring\t\tdemoname;\t\t\t// for demo looping\n\tdouble\t\tdemotime;\t\t\t// recording time\n\tqboolean\t\tset_lastdemo;\t\t// store name of last played demo into the cvar\n\n\tfile_t\t\t*demofile;\n\tfile_t\t\t*demoheader;\t\t// contain demo startup info in case we record a demo on this level\n\tqboolean internetservers_wait;\t// internetservers is waiting for dns request\n\tqboolean internetservers_pending; // if true, clean master server pings\n\tuint32_t internetservers_key;       // compare key to validate master server reply\n\tchar     internetservers_query[512]; // cached query\n\tuint32_t internetservers_query_len;\n\n\t// multiprotocol support\n\tconnprotocol_t legacymode;\n\tint extensions;\n\n\tnetadr_t serveradr;\n\n\t// do we accept utf8 as input\n\tqboolean accept_utf8;\n\n\t// server's build number (might be zero)\n\tint build_num;\n\tuint8_t steamid[8];\n} client_static_t;\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern client_t\t\tcl;\nextern client_static_t\tcls;\nextern clgame_static_t\tclgame;\nextern gameui_static_t\tgameui;\n\n#ifdef __cplusplus\n}\n#endif\n\n//\n// cvars\n//\nextern convar_t\tshowpause;\nextern convar_t\tmp_decals;\nextern convar_t\tcl_logomaxdim;\nextern convar_t\tcl_allow_download;\nextern convar_t\tcl_download_ingame;\nextern convar_t\tcl_nopred;\nextern convar_t\tcl_timeout;\nextern convar_t\tcl_interp;\nextern convar_t cl_nointerp;\nextern convar_t\tcl_showerror;\nextern convar_t\tcl_nosmooth;\nextern convar_t\tcl_smoothtime;\nextern convar_t\tcl_crosshair;\nextern convar_t\tcl_testlights;\nextern convar_t\tcl_cmdrate;\nextern convar_t\tcl_updaterate;\nextern convar_t\tcl_solid_players;\nextern convar_t\tcl_idealpitchscale;\nextern convar_t\tcl_allow_levelshots;\nextern convar_t\tcl_draw_particles;\nextern convar_t\tcl_draw_tracers;\nextern convar_t\tcl_levelshot_name;\nextern convar_t\tcl_draw_beams;\nextern convar_t\tcl_clockreset;\nextern convar_t\thud_fontscale;\nextern convar_t hud_fontrender;\nextern convar_t\thud_scale;\nextern convar_t hud_scale_minimal_width;\nextern convar_t\tr_showtextures;\nextern convar_t\tcl_bmodelinterp;\nextern convar_t\tcl_lw;\t\t// local weapons\nextern convar_t\tcl_charset;\nextern convar_t\tcl_trace_consistency;\nextern convar_t\tcl_trace_stufftext;\nextern convar_t\tcl_trace_messages;\nextern convar_t\tcl_trace_events;\nextern convar_t\thud_utf8;\nextern convar_t\tcl_showevents;\nextern convar_t\tscr_centertime;\nextern convar_t\tscr_viewsize;\nextern convar_t\tscr_loading;\nextern convar_t\tv_dark;\t// start from dark\nextern convar_t\tnet_graph;\nextern convar_t\trate;\nextern convar_t\tm_ignore;\nextern convar_t\tr_showtree;\nextern convar_t\tui_renderworld;\nextern convar_t cl_fixmodelinterpolationartifacts;\n\n//=============================================================================\n\nvoid CL_SetLightstyle( int style, const char* s, float f );\nvoid CL_DecayLights( void );\ndlight_t *CL_GetDynamicLight( int number );\ndlight_t *CL_GetEntityLight( int number );\n\n//=================================================\n\n//\n// cl_cmds.c\n//\nvoid CL_Quit_f( void );\nvoid CL_GenericShot_f( void );\nvoid CL_PlayCDTrack_f( void );\nvoid CL_LevelShot_f( void );\nvoid CL_SetSky_f( void );\nvoid SCR_Viewpos_f( void );\nvoid CL_WavePlayLen_f( void );\n\n//\n// cl_custom.c\n//\nqboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource );\nvoid CL_AddToResourceList( resource_t *pResource, resource_t *pList );\nvoid CL_RemoveFromResourceList( resource_t *pResource );\nvoid CL_MoveToOnHandList( resource_t *pResource );\nvoid CL_ClearResourceLists( void );\n\n//\n// cl_debug.c\n//\nvoid CL_ReplayBufferDat_f( void );\nvoid CL_Parse_Debug( qboolean enable );\nvoid CL_Parse_RecordCommand( int cmd, int startoffset );\nvoid CL_ResetFrame( frame_t *frame );\n\n//\n// cl_efx.c\n//\nvoid CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel );\n\n//\n// cl_main.c\n//\nvoid CL_Init( void );\nvoid CL_Disconnect_f( void );\nvoid CL_ProcessFile( qboolean successfully_received, const char *filename );\nvoid CL_WriteUsercmd( connprotocol_t proto, sizebuf_t *msg, int from, int to );\nvoid CL_SetupNetchanForProtocol( connprotocol_t proto );\nqboolean CL_PrecacheResources( void );\nvoid CL_SetupOverviewParams( void );\nvoid CL_UpdateFrameLerp( void );\nint CL_IsDevOverviewMode( void );\nvoid CL_SignonReply( connprotocol_t proto );\nvoid CL_ClearState( void );\n\n//\n// cl_demo.c\n//\nvoid CL_StartupDemoHeader( void );\nvoid CL_DrawDemoRecording( void );\nvoid CL_WriteDemoUserCmd( int cmdnumber );\nvoid CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg );\nvoid CL_WriteDemoUserMessage( int size, byte *buffer );\nqboolean CL_DemoReadMessage( byte *buffer, size_t *length );\nvoid CL_DemoInterpolateAngles( void );\nvoid CL_CheckStartupDemos( void );\nvoid CL_WriteDemoJumpTime( void );\nvoid CL_CloseDemoHeader( void );\nvoid CL_DemoCompleted( void );\nvoid CL_PlayDemo_f( void );\nvoid CL_TimeDemo_f( void );\nvoid CL_StartDemos_f( void );\nvoid CL_Demos_f( void );\nvoid CL_DeleteDemo_f( void );\nvoid CL_Record_f( void );\nvoid CL_Stop_f( void );\nvoid CL_ListDemo_f( void );\nint CL_GetDemoComment( const char *demoname, char *comment );\n\n//\n// cl_events.c\n//\nvoid CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseReliableEvent( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_SetEventIndex( const char *szEvName, int ev_index );\nvoid CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\nvoid CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func );\nvoid CL_ResetEvent( event_info_t *ei );\nword CL_EventIndex( const char *name );\nvoid CL_FireEvents( void );\n\n//\n// cl_font.c\n//\nqboolean CL_FixedFont( cl_font_t *font );\nqboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags );\nqboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags );\nvoid CL_FreeFont( cl_font_t *font );\nvoid CL_SetFontRendermode( cl_font_t *font );\nint CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_t *font, int flags );\nint CL_DrawString( float x, float y, const char *s, const rgba_t color, cl_font_t *font, int flags );\nvoid CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height );\nvoid CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags );\nint CL_DrawStringf( cl_font_t *font, float x, float y, const rgba_t color, int flags, const char *fmt, ... ) FORMAT_CHECK( 6 );\n\n\n//\n// cl_game.c\n//\nvoid CL_UnloadProgs( void );\nqboolean CL_LoadProgs( const char *name );\nvoid CL_LinkUserMessage( char *pszName, const int svc_num, int iSize );\nvoid CL_DrawHUD( int state );\nvoid CL_InitEdicts( int maxclients );\nvoid CL_FreeEdicts( void );\nvoid CL_ClearWorld( void );\nvoid CL_DrawCenterPrint( void );\nvoid CL_ClearSpriteTextures( void );\nvoid CL_CenterPrint( const char *text, float y );\nvoid CL_TextMessageParse( byte *pMemFile, int fileSize );\nclient_textmessage_t *CL_TextMessageGet( const char *pName );\nvoid NetAPI_CancelAllRequests( void );\nmodel_t *CL_LoadClientSprite( const char *filename );\nmodel_t *CL_LoadModel( const char *modelname, int *index );\nHSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags );\nvoid SPR_AdjustSize( float *x, float *y, float *w, float *h );\nint CL_GetScreenInfo( SCREENINFO *pscrinfo );\npmtrace_t *PM_CL_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe );\nconst char *PM_CL_TraceTexture( int ground, float *vstart, float *vend );\nint PM_CL_PointContents( const float *p, int *truecontents );\nphysent_t *pfnGetPhysent( int idx );\nstruct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend );\nvoid CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );\nvoid CL_DisableScissor( scissor_state_t *scissor );\nqboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );\n\nstatic inline cl_entity_t *CL_EDICT_NUM( int index )\n{\n\tif( !clgame.entities ) // not in game yet\n\t{\n\t\tHost_Error( \"%s: clgame.entities is NULL\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tif( index < 0 || index >= clgame.maxEntities )\n\t{\n\t\tHost_Error( \"%s: bad number %i\\n\", __func__, index );\n\t\treturn NULL;\n\t}\n\n\treturn clgame.entities + index;\n}\n\nstatic inline cl_entity_t *CL_GetEntityByIndex( int index )\n{\n\tif( !clgame.entities ) // not in game yet\n\t\treturn NULL;\n\n\tif( index < 0 || index >= clgame.maxEntities )\n\t\treturn NULL;\n\n\treturn clgame.entities + index;\n}\n\nstatic inline model_t *CL_ModelHandle( int modelindex )\n{\n\treturn modelindex >= 0 && modelindex < MAX_MODELS ? cl.models[modelindex] : NULL;\n}\n\nstatic inline qboolean CL_IsThirdPerson( void )\n{\n\treturn clgame.dllFuncs.CL_IsThirdPerson();\n}\n\nstatic inline cl_entity_t *CL_GetLocalPlayer( void )\n{\n\tcl_entity_t\t*player = CL_GetEntityByIndex( cl.playernum + 1 );\n\n\t// HACKHACK: GoldSrc doesn't do this, but some mods actually check it for null pointer\n\t// this is a lesser evil than changing semantics of HUD_VidInit and call it after entities are allocated\n\tif( !player )\n\t\tCon_Printf( S_WARN \"%s: client entities are not initialized yet! Returning NULL...\\n\", __func__ );\n\n\treturn player;\n}\n\n//\n// cl_parse.c\n//\nvoid CL_ParseSetAngle( sizebuf_t *msg );\nvoid CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseResource( sizebuf_t *msg );\nvoid CL_ParseClientData( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_UpdateUserPings( sizebuf_t *msg );\nvoid CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseRestoreSoundPacket( sizebuf_t *msg );\nvoid CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseSignon( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseRestore( sizebuf_t *msg );\nvoid CL_ParseStaticDecal( sizebuf_t *msg );\nvoid CL_ParseAddAngle( sizebuf_t *msg );\nvoid CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseMovevars( sizebuf_t *msg );\nvoid CL_ParseResourceRequest( sizebuf_t *msg );\nvoid CL_ParseCustomization( sizebuf_t *msg );\nvoid CL_ParseCrosshairAngle( sizebuf_t *msg );\nvoid CL_ParseSoundFade( sizebuf_t *msg );\nvoid CL_ParseFileTransferFailed( sizebuf_t *msg );\nvoid CL_ParseHLTV( sizebuf_t *msg );\nvoid CL_ParseDirector( sizebuf_t *msg );\nvoid CL_ParseVoiceInit( sizebuf_t *msg );\nvoid CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseResLocation( sizebuf_t *msg );\nvoid CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext, const connprotocol_t proto );\nvoid CL_ParseServerMessage( sizebuf_t *msg );\nqboolean CL_ParseCommonDLLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset );\nvoid CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto );\nqboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf );\nqboolean CL_RequestMissingResources( void );\nvoid CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseViewEntity( sizebuf_t *msg );\nvoid CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto );\nvoid CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto );\nvoid CL_ParseFinaleCutscene( sizebuf_t *msg, int level );\nvoid CL_ParseTextMessage( sizebuf_t *msg );\nvoid CL_ParseExec( sizebuf_t *msg );\nvoid CL_BatchResourceRequest( qboolean initialize );\nint CL_EstimateNeededResources( void );\n\n//\n// cl_parse_48.c\n//\nvoid CL_ParseLegacyServerMessage( sizebuf_t *msg );\nvoid CL_LegacyPrecache_f( void );\n\n//\n// cl_parse_gs.c\n//\nvoid CL_ParseGoldSrcServerMessage( sizebuf_t *msg );\n\n//\n// cl_scrn.c\n//\nvoid SCR_VidInit( void );\nvoid SCR_TileClear( void );\nvoid SCR_DirtyScreen( void );\nvoid SCR_EndLoadingPlaque( void );\nint SCR_LoadPauseIcon( void );\nvoid SCR_RegisterTextures( void );\nvoid SCR_LoadCreditsFont( void );\nvoid SCR_MakeScreenShot( void );\nvoid SCR_MakeLevelShot( void );\nvoid SCR_NetSpeeds( void );\nvoid SCR_RSpeeds( void );\nvoid SCR_DrawFPS( int height );\nvoid SCR_DrawPos( void );\nvoid SCR_DrawEnts( void );\nvoid SCR_DrawUserCmd( void );\n\n//\n// cl_netgraph.c\n//\nvoid CL_InitNetgraph( void );\nvoid SCR_DrawNetGraph( void );\n\n//\n// cl_view.c\n//\n\nvoid V_Init (void);\nvoid V_Shutdown( void );\nqboolean V_PreRender( void );\nvoid V_PostRender( void );\nvoid V_RenderView( void );\n\n//\n// cl_pmove.c\n//\nvoid CL_SetSolidEntities( void );\nvoid CL_SetSolidPlayers( int playernum );\nvoid CL_InitClientMove( void );\nvoid CL_PredictMovement( qboolean repredicting );\nvoid CL_CheckPredictionError( void );\nint CL_WaterEntity( const float *rgflPos );\ncl_entity_t *CL_GetWaterEntity( const float *rgflPos );\npmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags );\npmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags );\nvoid CL_MoveSpectatorCamera( void );\nvoid CL_SetLastUpdate( void );\nvoid CL_RedoPrediction( void );\nvoid CL_PushPMStates( void );\nvoid CL_PopPMStates( void );\nvoid CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient );\nvoid CL_SetIdealPitch( void );\n\n//\n// cl_qparse.c\n//\nvoid CL_ParseQuakeMessage( sizebuf_t *msg );\n\n//\n// cl_frame.c\n//\nstruct channel_s;\nstruct rawchan_s;\nqboolean CL_ValidateDeltaPacket( uint oldpacket, frame_t *oldframe );\nint CL_UpdateOldEntNum( int oldindex, frame_t *oldframe, entity_state_t **oldent );\nint CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto );\nqboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType );\nvoid CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset );\nqboolean CL_GetEntitySpatialization( struct channel_s *ch );\nqboolean CL_GetMovieSpatialization( struct rawchan_s *ch );\nvoid CL_ComputePlayerOrigin( cl_entity_t *clent );\nvoid CL_ProcessPacket( frame_t *frame );\nvoid CL_MoveThirdpersonCamera( void );\nvoid CL_EmitEntities( void );\n\nstatic inline qboolean CL_IsPlayerIndex( int idx )\n{\n\treturn idx >= 1 && idx <= cl.maxclients ? true : false;\n}\n\n//\n// cl_remap.c\n//\nremap_info_t *CL_GetRemapInfoForEntity( cl_entity_t *e );\nqboolean CL_EntitySetRemapColors( cl_entity_t *e, model_t *mod, int top, int bottom );\nvoid CL_ClearAllRemaps( void );\n\n//\n// cl_render.c\n//\nqboolean R_InitRenderAPI( void );\nintptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRef );\nlightstyle_t *CL_GetLightStyle( int number );\nint R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis );\nconst ref_overview_t *GL_GetOverviewParms( void );\n\n//\n// cl_spray.c\n//\nqboolean CL_ConvertImageToWAD3( const char *filename );\n\n//\n// cl_efrag.c\n//\nvoid R_StoreEfrags( efrag_t **ppefrag, int framecount );\nvoid R_AddEfrags( cl_entity_t *ent );\n\n//\n// cl_tent.c\n//\nstruct particle_s;\nvoid CL_WeaponAnim( int iAnim, int body );\nvoid CL_ClearEffects( void );\nvoid CL_ClearEfrags( void );\nvoid CL_TestLights( void );\nvoid CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale );\nvoid CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags );\nvoid R_FreeDeadParticles( struct particle_s **ppparticles );\nvoid CL_AddClientResource( const char *filename, int type );\nvoid CL_AddClientResources( void );\nvoid CL_InitParticles( void );\nvoid CL_ClearParticles( void );\nvoid CL_FreeParticles( void );\nvoid CL_InitTempEnts( void );\nvoid CL_FreeTempEnts( void );\nvoid CL_TempEntUpdate( void );\nvoid CL_InitViewBeams( void );\nvoid CL_ClearViewBeams( void );\nvoid CL_FreeViewBeams( void );\ncl_entity_t *R_BeamGetEntity( int index );\nvoid CL_KillDeadBeams( cl_entity_t *pDeadEntity );\nvoid CL_ParseViewBeam( sizebuf_t *msg, int beamType );\nvoid CL_LoadClientSprites( void );\nvoid CL_ReadPointFile_f( void );\nvoid CL_DrawEFX( float time, qboolean fTrans );\nvoid CL_ThinkParticle( double frametime, particle_t *p );\nvoid CL_ReadLineFile_f( void );\n\n//\n// console.c\n//\nextern convar_t con_fontsize;\nint Con_Visible( void );\nqboolean Con_FixedFont( void );\nvoid Con_VidInit( void );\nvoid Con_Shutdown( void );\nvoid Con_ToggleConsole_f( void );\nvoid Con_ClearNotify( void );\nvoid Con_DrawDebug( void );\nvoid Con_RunConsole( void );\nvoid Con_DrawConsole( void );\nvoid Con_DrawVersion( void );\nint Con_UtfProcessChar( int in );\nint Con_UtfProcessCharForce( int in );\nint Con_UtfMoveLeft( char *str, int pos );\nint Con_UtfMoveRight( char *str, int pos, int length );\nvoid Con_DefaultColor( int r, int g, int b, qboolean gameui );\ncl_font_t *Con_GetCurFont( void );\ncl_font_t *Con_GetFont( int num );\nint Con_DrawString( int x, int y, const char *string, const rgba_t setColor ); // legacy, use cl_font.c\nvoid GAME_EXPORT Con_DrawStringLen( const char *pText, int *length, int *height ); // legacy, use cl_font.c\nvoid Con_CharEvent( int key );\nvoid Key_Console( int key );\nvoid Key_Message( int key );\nvoid Con_FastClose( void );\nvoid Con_Bottom( void );\nvoid Con_PageDown( int lines );\nvoid Con_PageUp( int lines );\n\n//\n// s_main.c\n//\ntypedef int sound_t;\nvoid S_StartBackgroundTrack( const char *intro, const char *loop, int position, qboolean fullpath );\nvoid S_StopBackgroundTrack( void );\nvoid S_StreamSetPause( int pause );\nvoid S_StartStreaming( void );\nvoid S_StopStreaming( void );\nvoid S_BeginRegistration( void );\nsound_t S_RegisterSound( const char *sample );\nvoid S_EndRegistration( void );\nvoid S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex );\nvoid S_StartSound( const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, int pitch, int flags );\nvoid S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags );\nvoid S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds );\nvoid S_FadeMusicVolume( float fadePercent );\nvoid S_StartLocalSound( const char *name, float volume, qboolean reliable );\nvoid SND_UpdateSound( void );\nvoid S_ExtraUpdate( void );\n\n//\n// cl_gameui.c\n//\nvoid UI_UnloadProgs( void );\nqboolean UI_LoadProgs( void );\nvoid UI_UpdateMenu( float realtime );\nvoid UI_KeyEvent( int key, qboolean down );\nvoid UI_MouseMove( int x, int y );\nvoid UI_SetActiveMenu( qboolean fActive );\nvoid UI_AddServerToList( netadr_t adr, const char *info );\nvoid UI_GetCursorPos( int *pos_x, int *pos_y );\nvoid UI_SetCursorPos( int pos_x, int pos_y );\nvoid UI_ShowCursor( qboolean show );\nqboolean UI_CreditsActive( void );\nvoid UI_CharEvent( int key );\nqboolean UI_MouseInRect( void );\nqboolean UI_IsVisible( void );\nvoid UI_ResetPing( void );\nvoid UI_ShowUpdateDialog( qboolean preferStore );\nqboolean UI_ShowMessageBox( const char *text );\nvoid UI_AddTouchButtonToList( const char *name, const char *texture, const char *command, unsigned char *color, int flags );\nvoid UI_ConnectionProgress_Disconnect( void );\nvoid UI_ConnectionProgress_Download( const char *pszFileName, const char *pszServerName, const char *pszServerPath, int iCurrent, int iTotal, const char *comment );\nvoid UI_ConnectionProgress_DownloadEnd( void );\nvoid UI_ConnectionProgress_Precache( void );\nvoid UI_ConnectionProgress_Connect( const char *server );\nvoid UI_ConnectionProgress_ChangeLevel( void );\nvoid UI_ConnectionProgress_ParseServerInfo( const char *server );\n\n//\n// cl_mobile.c\n//\nqboolean Mobile_Init( void );\nvoid Mobile_Shutdown( void );\n\n//\n// cl_securedstub.c\n//\nvoid CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );\n\n//\n// cl_video.c\n//\nvoid SCR_InitCinematic( void );\nvoid SCR_FreeCinematic( void );\nqboolean SCR_PlayCinematic( const char *name );\nqboolean SCR_DrawCinematic( void );\nqboolean SCR_NextMovie( void );\nvoid SCR_RunCinematic( void );\nvoid SCR_StopCinematic( void );\nvoid CL_PlayVideo_f( void );\n\n\n//\n// keys.c\n//\nint Key_IsDown( int keynum );\nvoid Key_Event( int key, int down );\nvoid Key_Init( void );\nvoid Key_WriteBindings( file_t *f );\nconst char *Key_GetBinding( int keynum );\nvoid Key_SetBinding( int keynum, const char *binding );\nconst char *Key_LookupBinding( const char *pBinding );\nvoid Key_ClearStates( void );\nconst char *Key_KeynumToString( int keynum );\nvoid Key_EnumCmds_f( void );\nvoid Key_SetKeyDest( int key_dest );\nvoid Key_EnableTextInput( qboolean enable, qboolean force );\nint Key_ToUpper( int key );\nvoid OSK_Draw( void );\n\n//\n// identification.c\n//\nvoid ID_Init( void );\nconst char *ID_GetMD5( void );\n\nextern rgba_t g_color_table[8];\nextern triangleapi_t gTriApi;\nextern net_api_t gNetApi;\n\n#endif//CLIENT_H\n"
  },
  {
    "path": "engine/client/console.c",
    "content": "/*\nconsole.c - developer console\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"keydefs.h\"\n#include \"protocol.h\"\t\t// get the protocol version\n#include \"con_nprint.h\"\n#include \"qfont.h\"\n#include \"wadfile.h\"\n#include \"input.h\"\n#include \"utflib.h\"\n\nstatic CVAR_DEFINE_AUTO( scr_conspeed, \"600\", FCVAR_ARCHIVE, \"console moving speed\" );\nstatic CVAR_DEFINE_AUTO( con_notifytime, \"3\", FCVAR_ARCHIVE, \"notify time to live\" );\nCVAR_DEFINE_AUTO( con_fontsize, \"1\", FCVAR_ARCHIVE, \"console font number (0, 1 or 2)\" );\nstatic CVAR_DEFINE_AUTO( con_fontrender, \"2\", FCVAR_ARCHIVE, \"console font render mode (0: additive, 1: holes, 2: trans)\" );\nstatic CVAR_DEFINE_AUTO( con_charset, \"cp1251\", FCVAR_ARCHIVE, \"console font charset (only cp1251 supported now)\" );\nstatic CVAR_DEFINE_AUTO( con_fontscale, \"1.0\", FCVAR_ARCHIVE, \"scale font texture\" );\nstatic CVAR_DEFINE_AUTO( con_fontnum, \"-1\", FCVAR_ARCHIVE, \"console font number (0, 1 or 2), -1 for autoselect\" );\nstatic CVAR_DEFINE_AUTO( con_color, \"240 180 24\", FCVAR_ARCHIVE, \"set a custom console color\" );\nstatic CVAR_DEFINE_AUTO( scr_drawversion, \"1\", FCVAR_ARCHIVE, \"draw version in menu or screenshots, doesn't affect console\" );\nstatic CVAR_DEFINE_AUTO( con_oldfont, \"0\", 0, \"use legacy font from gfx.wad, might be missing or broken\" );\n\nstatic int g_codepage = 0;\n\nstatic qboolean g_messagemode_privileged = true;\n\n#define CON_TIMES\t\t4\t// notify lines\n#define CON_MAX_TIMES\t64\t// notify max lines\n#define COLOR_DEFAULT\t'7'\n#define CON_HISTORY\t\t64\n#define MAX_DBG_NOTIFY\t128\n#if XASH_LOW_MEMORY\n#define CON_NUMFONTS\t1\t\t// do not load different font textures\n#define CON_TEXTSIZE\t32768\t// max scrollback buffer characters in console (32 kb)\n#define CON_MAXLINES\t2048\t// max scrollback buffer lines in console\n#else\n#define CON_NUMFONTS\t3\t// maxfonts\n#define CON_TEXTSIZE\t1048576\t// max scrollback buffer characters in console (1 Mb)\n#define CON_MAXLINES\t16384\t// max scrollback buffer lines in console\n#endif\n#define CON_LINES( i )\t(con.lines[(con.lines_first + (i)) % con.maxlines])\n#define CON_LINES_COUNT\tcon.lines_count\n#define CON_LINES_LAST()\tCON_LINES( CON_LINES_COUNT - 1 )\n\n// console color typeing\nrgba_t g_color_table[8] =\n{\n{   0,   0,   0, 255 },\t// black\n{ 255,   0,   0, 255 },\t// red\n{   0, 255,   0, 255 },\t// green\n{ 255, 255,   0, 255 },\t// yellow\n{   0,   0, 255, 255 },\t// blue\n{   0, 255, 255, 255 },\t// cyan\n{ 255,   0, 255, 255 },\t// magenta\n{ 240, 180,  24, 255 },\t// default color (can be changed by user)\n};\n\ntypedef struct\n{\n\tstring\t\tszNotify;\n\tfloat\t\texpire;\n\trgba_t\t\tcolor;\n\tint\t\tkey_dest;\n} notify_t;\n\ntypedef struct con_lineinfo_s\n{\n\tchar\t\t*start;\n\tsize_t\t\tlength;\n\tdouble\t\taddtime;\t\t// notify stuff\n} con_lineinfo_t;\n\ntypedef struct history_line_s\n{\n\tstring buffer;\n\tint    cursor;\n\tint    scroll;\n} history_line_t;\n\ntypedef struct con_history_s\n{\n\thistory_line_t lines[CON_HISTORY];\n\thistory_line_t backup;\n\tint     line; // the line being displayed from history buffer will be <= nextHistoryLine\n\tint     next; // the last line in the history buffer, not masked\n} con_history_t;\n\ntypedef struct\n{\n\tqboolean\t\tinitialized;\n\n\t// conbuffer\n\tchar\t\t*buffer;\t\t// common buffer for all console lines\n\tint\t\tbufsize;\t\t// CON_TEXSIZE\n\tcon_lineinfo_t\t*lines;\t\t// console lines\n\tint\t\tmaxlines;\t\t// CON_MAXLINES\n\n\tint\t\tlines_first;\t// cyclic buffer\n\tint\t\tlines_count;\n\tint\t\tnum_times;\t// overlay lines count\n\n\t// console scroll\n\tint\t\tbackscroll;\t// lines up from bottom to display\n\tint \t\tlinewidth;\t// characters across screen\n\n\t// console animation\n\tfloat\t\tshowlines;\t// how many lines we should display\n\tfloat\t\tvislines;\t\t// in scanlines\n\n\t// console images\n\tint\t\tbackground;\t// console background\n\n\t// console fonts\n\tcl_font_t\t\tchars[CON_NUMFONTS];// fonts.wad/font1.fnt\n\tcl_font_t\t\t*curFont;\n\n\t// console input\n\tfield_t\t\tinput;\n\n\t// chatfiled\n\tfield_t\t\tchat;\n\tstring\t\tchat_cmd;\t\t// can be overrieded by user\n\n\t// console history\n\tcon_history_t\thistory;\n\tqboolean\t\thistoryLoaded;\n\n\tnotify_t\t\tnotify[MAX_DBG_NOTIFY]; // for Con_NXPrintf\n\tqboolean\t\tdraw_notify;\t// true if we have NXPrint message\n\n\t// console update\n\tdouble\t\tlastupdate;\n} console_t;\n\nstatic console_t\t\tcon;\n\nstatic void Con_ClearField( field_t *edit );\nstatic void Field_CharEvent( field_t *edit, int ch );\nstatic void Con_InvalidateFonts( void );\n\nstatic void Con_LoadHistory( con_history_t *self );\nstatic void Con_SaveHistory( con_history_t *self );\n\n/*\n================\nCon_Clear_f\n================\n*/\nstatic void Con_Clear_f( void )\n{\n\tcon.lines_count = 0;\n\tcon.backscroll = 0; // go to end\n}\n\n/*\n================\nCon_SetColor\n================\n*/\nstatic void Con_SetColor( void )\n{\n\tint r, g, b;\n\tint num;\n\n\tif( !FBitSet( con_color.flags, FCVAR_CHANGED ))\n\t\treturn;\n\n\tnum = sscanf( con_color.string, \"%i %i %i\", &r, &g, &b );\n\n\tswitch( num )\n\t{\n\tcase 1:\n\t\tCon_DefaultColor( r, r, r, false );\n\t\tbreak;\n\tcase 3:\n\t\tCon_DefaultColor( r, g, b, false );\n\t\tbreak;\n\tdefault:\n\t\tCvar_DirectSet( &con_color, con_color.def_string );\n\t\tbreak;\n\t}\n\n\tClearBits( con_color.flags, FCVAR_CHANGED );\n}\n\n/*\n================\nCon_ClearNotify\n================\n*/\nvoid Con_ClearNotify( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < CON_LINES_COUNT; i++ )\n\t\tCON_LINES( i ).addtime = 0.0;\n}\n\n/*\n================\nCon_ClearTyping\n================\n*/\nstatic void Con_ClearTyping( void )\n{\n\tCon_ClearField( &con.input );\n\tcon.input.widthInChars = con.linewidth;\n\n\tCmd_AutoCompleteClear();\n}\n\n/*\n================\nCon_MessageMode_f\n================\n*/\nstatic void Con_MessageMode_f( void )\n{\n\tg_messagemode_privileged = Cmd_CurrentCommandIsPrivileged();\n\n\tif( Cmd_Argc() == 2 )\n\t\tQ_strncpy( con.chat_cmd, Cmd_Argv( 1 ), sizeof( con.chat_cmd ));\n\telse Q_strncpy( con.chat_cmd, \"say\", sizeof( con.chat_cmd ));\n\n\tKey_SetKeyDest( key_message );\n}\n\n/*\n================\nCon_MessageMode2_f\n================\n*/\nstatic void Con_MessageMode2_f( void )\n{\n\tg_messagemode_privileged = Cmd_CurrentCommandIsPrivileged();\n\n\tQ_strncpy( con.chat_cmd, \"say_team\", sizeof( con.chat_cmd ));\n\tKey_SetKeyDest( key_message );\n}\n\n/*\n================\nCon_ToggleConsole_f\n================\n*/\nvoid Con_ToggleConsole_f( void )\n{\n\tif( !host.allow_console || UI_CreditsActive( ))\n\t\treturn; // disabled\n\n\tSCR_EndLoadingPlaque();\n\n\t// show console only in game or by special call from menu\n\tif( cls.state != ca_active || cls.key_dest == key_menu )\n\t\treturn;\n\n\tCon_ClearTyping();\n\tCon_ClearNotify();\n\n\tif( cls.key_dest == key_console )\n\t{\n\t\tif( Cvar_VariableInteger( \"sv_background\" ) || Cvar_VariableInteger( \"cl_background\" ))\n\t\t\tUI_SetActiveMenu( true );\n\t\telse UI_SetActiveMenu( false );\n\t}\n\telse\n\t{\n\t\tUI_SetActiveMenu( false );\n\t\tKey_SetKeyDest( key_console );\n\t}\n}\n\n/*\n================\nCon_SetTimes_f\n================\n*/\nstatic void Con_SetTimes_f( void )\n{\n\tint\tnewtimes;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"contimes <n lines>\\n\" );\n\t\treturn;\n\t}\n\n\tnewtimes = Q_atoi( Cmd_Argv( 1 ) );\n\tcon.num_times = bound( CON_TIMES, newtimes, CON_MAX_TIMES );\n}\n\n/*\n================\nCon_FixTimes\n\nNotifies the console code about the current time\n(and shifts back times of other entries when the time\nwent backwards)\n================\n*/\nstatic void Con_FixTimes( void )\n{\n\tdouble\tdiff;\n\tint\ti;\n\n\tif( con.lines_count <= 0 ) return;\n\n\tdiff = cl.time - CON_LINES_LAST().addtime;\n\tif( diff >= 0.0 ) return; // nothing to fix\n\n\tfor( i = 0; i < con.lines_count; i++ )\n\t\tCON_LINES( i ).addtime += diff;\n}\n\n/*\n================\nCon_DeleteLine\n\nDeletes the first line from the console history.\n================\n*/\nstatic void Con_DeleteLine( void )\n{\n\tif( con.lines_count == 0 )\n\t\treturn;\n\tcon.lines_count--;\n\tcon.lines_first = (con.lines_first + 1) % con.maxlines;\n}\n\n/*\n================\nCon_DeleteLastLine\n\nDeletes the last line from the console history.\n================\n*/\nstatic void Con_DeleteLastLine( void )\n{\n\tif( con.lines_count == 0 )\n\t\treturn;\n\tcon.lines_count--;\n}\n\n/*\n================\nCon_BytesLeft\n\nChecks if there is space for a line of the given length, and if yes, returns a\npointer to the start of such a space, and NULL otherwise.\n================\n*/\nstatic char *Con_BytesLeft( int length )\n{\n\tif( length > con.bufsize )\n\t\treturn NULL;\n\n\tif( con.lines_count == 0 )\n\t{\n\t\treturn con.buffer;\n\t}\n\telse\n\t{\n\t\tchar\t*firstline_start = con.lines[con.lines_first].start;\n\t\tchar\t*lastline_onepastend = CON_LINES_LAST().start + CON_LINES_LAST().length;\n\n\t\t// the buffer is cyclic, so we first have two cases...\n\t\tif( firstline_start < lastline_onepastend ) // buffer is contiguous\n\t\t{\n\t\t\t// put at end?\n\t\t\tif( length <= con.buffer + con.bufsize - lastline_onepastend )\n\t\t\t\treturn lastline_onepastend;\n\t\t\t// put at beginning?\n\t\t\telse if( length <= firstline_start - con.buffer )\n\t\t\t\treturn con.buffer;\n\n\t\t\treturn NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// buffer has a contiguous hole\n\t\t\tif( length <= firstline_start - lastline_onepastend )\n\t\t\t\treturn lastline_onepastend;\n\n\t\t\treturn NULL;\n\t\t}\n\t}\n}\n\n/*\n================\nCon_AddLine\n\nAppends a given string as a new line to the console.\n================\n*/\nstatic void Con_AddLine( const char *line, int length, qboolean newline )\n{\n\tchar\t\t*putpos;\n\tcon_lineinfo_t\t*p;\n\n\tif( !con.initialized || !con.buffer )\n\t\treturn;\n\n\tCon_FixTimes();\n\tlength++;\t// reserve space for term\n\n\tASSERT( length < CON_TEXTSIZE );\n\n\twhile( !( putpos = Con_BytesLeft( length )) || con.lines_count >= con.maxlines )\n\t\tCon_DeleteLine();\n\n\tif( newline )\n\t{\n\t\tmemcpy( putpos, line, length );\n\t\tputpos[length - 1] = '\\0';\n\t\tcon.lines_count++;\n\n\t\tp = &CON_LINES_LAST();\n\t\tp->start = putpos;\n\t\tp->length = length;\n\t\tp->addtime = cl.time;\n\t}\n\telse\n\t{\n\t\tp = &CON_LINES_LAST();\n\t\tputpos = p->start + Q_strlen( p->start );\n\t\tmemcpy( putpos, line, length - 1 );\n\t\tp->length = Q_strlen( p->start );\n\t\tputpos[p->length] = '\\0';\n\t\tp->addtime = cl.time;\n\t\tp->length++;\n\t}\n}\n\n/*\n================\nCon_CheckResize\n\nIf the line width has changed, reformat the buffer.\n================\n*/\nstatic void Con_CheckResize( void )\n{\n\tint\tcharWidth = 8;\n\tint\ti, width;\n\n\tif( con.curFont && con.curFont->hFontTexture )\n\t\tcharWidth = con.curFont->charWidths['O'] - 1;\n\n\twidth = ( refState.width / charWidth ) - 2;\n\tif( !ref.initialized ) width = (640 / 5);\n\n\tif( width == con.linewidth )\n\t\treturn;\n\n\tCon_ClearNotify();\n\tcon.linewidth = width;\n\tcon.backscroll = 0;\n\n\tcon.input.widthInChars = con.linewidth;\n}\n\n/*\n================\nCon_PageUp\n================\n*/\nvoid Con_PageUp( int lines )\n{\n\tcon.backscroll += abs( lines );\n}\n\n/*\n================\nCon_PageDown\n================\n*/\nvoid Con_PageDown( int lines )\n{\n\tcon.backscroll -= abs( lines );\n}\n\n/*\n================\nCon_Top\n================\n*/\nstatic void Con_Top( void )\n{\n\tcon.backscroll = CON_MAXLINES;\n}\n\n/*\n================\nCon_Bottom\n================\n*/\nvoid Con_Bottom( void )\n{\n\tcon.backscroll = 0;\n}\n\n/*\n================\nCon_Visible\n================\n*/\nint GAME_EXPORT Con_Visible( void )\n{\n\treturn (con.vislines > 0);\n}\n\n/*\n================\nCon_FixedFont\n================\n*/\nqboolean Con_FixedFont( void )\n{\n\treturn CL_FixedFont( con.curFont );\n}\n\n\n/*\n================\nCon_LoadConsoleFont\n\nINTERNAL RESOURCE\n================\n*/\nstatic void Con_LoadConsoleFont( int fontNumber, cl_font_t *font )\n{\n\tqboolean success = false;\n\tfloat scale = con_fontscale.value;\n\n\tif( font->valid )\n\t\treturn; // already loaded\n\n\tif( con_oldfont.value )\n\t{\n\t\tsuccess = Con_LoadVariableWidthFont( \"gfx/conchars.fnt\", font, scale, &con_fontrender, TF_FONT|TF_NEAREST );\n\t}\n\telse\n\t{\n\t\tstring path;\n\t\tdword crc = 0;\n\n\t\t// replace default fonts.wad textures by current charset's font\n\t\tif( !CRC32_File( &crc, \"fonts.wad\" ) || crc == 0x3c0a0029 )\n\t\t{\n\t\t\tif( Q_snprintf( path, sizeof( path ),\n\t\t\t\t\"font%i_%s.fnt\", fontNumber, Cvar_VariableString( \"con_charset\" )) > 0 )\n\t\t\t{\n\t\t\t\tsuccess = Con_LoadVariableWidthFont( path, font, scale, &con_fontrender, TF_FONT|TF_NEAREST );\n\t\t\t}\n\t\t}\n\n\t\tif( !success )\n\t\t{\n\t\t\tQ_snprintf( path, sizeof( path ), \"fonts/font%i\", fontNumber );\n\t\t\tsuccess = Con_LoadVariableWidthFont( path, font, scale, &con_fontrender, TF_FONT|TF_NEAREST );\n\t\t}\n\t}\n\n\tif( !success )\n\t{\n\t\t// quake fixed font as fallback\n\t\t// keep source to print directly into conback image\n\t\tif( !Con_LoadFixedWidthFont( \"gfx/conchars\", font, scale, &con_fontrender, TF_FONT|TF_NEAREST|TF_KEEP_SOURCE ))\n\t\t\tCon_DPrintf( S_ERROR \"failed to load console font\\n\" );\n\t}\n}\n\n/*\n================\nCon_LoadConchars\n================\n*/\nstatic void Con_LoadConchars( void )\n{\n\tint\ti, fontSize;\n\n\t// load all the console fonts\n\tfor( i = 0; i < CON_NUMFONTS; i++ )\n\t\tCon_LoadConsoleFont( i, con.chars + i );\n\n\t// select properly fontsize\n\tif( con_fontnum.value >= 0 && con_fontnum.value <= CON_NUMFONTS - 1 )\n\t\tfontSize = con_fontnum.value;\n\telse if( refState.width <= 640 )\n\t\tfontSize = 0;\n\telse if( refState.width >= 1280 )\n\t\tfontSize = 2;\n\telse fontSize = 1;\n\n\tif( fontSize > CON_NUMFONTS - 1 )\n\t\tfontSize = CON_NUMFONTS - 1;\n\n\t// sets the current font\n\tcon.curFont = &con.chars[fontSize];\n}\n\n/*\n============================\nCon_UtfProcessChar\n\nConvert utf char to current font's single-byte encoding\n============================\n*/\nint Con_UtfProcessCharForce( int in )\n{\n\t// TODO: get rid of global state where possible\n\tstatic utfstate_t state = { 0 };\n\n\tuint32_t ch = Q_DecodeUTF8( &state, in );\n\n\tif( g_codepage == 1251 )\n\t\treturn Q_UnicodeToCP1251( ch );\n\tif( g_codepage == 1252 )\n\t\treturn Q_UnicodeToCP1252( ch );\n\n\treturn '?'; // not implemented yet\n}\n\nint GAME_EXPORT Con_UtfProcessChar( int in )\n{\n\tif( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence\n\t\treturn in;\n\n\t// otherwise, decode it and convert to selected codepage\n\treturn Con_UtfProcessCharForce( in );\n}\n\n/*\n=================\nCon_UtfMoveLeft\n\nget position of previous printful char\n=================\n*/\nint Con_UtfMoveLeft( char *str, int pos )\n{\n\tutfstate_t state = { 0 };\n\tint k = 0;\n\tint i;\n\n\tif( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence\n\t\treturn pos - 1;\n\n\tif( pos == 1 )\n\t\treturn 0;\n\n\tfor( i = 0; i < pos - 1; i++ )\n\t{\n\t\tif( Q_DecodeUTF8( &state, (byte)str[i] ))\n\t\t\tk = i + 1;\n\t}\n\n\treturn k;\n}\n\n/*\n=================\nCon_UtfMoveRight\n\nget next of previous printful char\n=================\n*/\nint Con_UtfMoveRight( char *str, int pos, int length )\n{\n\tutfstate_t state = { 0 };\n\tint i;\n\n\tif( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence\n\t\treturn pos + 1;\n\n\tfor( i = pos; i <= length; i++ )\n\t{\n\t\tif( Q_DecodeUTF8( &state, (byte)str[i] ))\n\t\t\treturn i + 1;\n\t}\n\n\treturn pos + 1;\n}\n\nstatic void Con_DrawCharToConback( int num, const byte *conchars, byte *dest )\n{\n\tint\trow, col;\n\tconst byte\t*source;\n\tint\tdrawline;\n\tint\tx;\n\n\trow = num >> 4;\n\tcol = num & 15;\n\tsource = conchars + (row << 10) + (col << 3);\n\n\tdrawline = 8;\n\n\twhile( drawline-- )\n\t{\n\t\tfor( x = 0; x < 8; x++ )\n\t\t\tif( source[x] != 255 )\n\t\t\t\tdest[x] = 0x60 + source[x];\n\t\tsource += 128;\n\t\tdest += 320;\n\t}\n\n}\n\n/*\n====================\nCon_GetFont\n\n====================\n*/\ncl_font_t *Con_GetFont( int num )\n{\n\tnum = bound( 0, num, CON_NUMFONTS - 1 );\n\treturn &con.chars[num];\n}\n\n/*\n====================\nCon_GetCurFont\n\n====================\n*/\ncl_font_t *Con_GetCurFont( void )\n{\n\treturn con.curFont;\n}\n\n/*\n====================\nCon_DrawStringLen\n\ncompute string width and height in screen pixels\n====================\n*/\nvoid GAME_EXPORT Con_DrawStringLen( const char *pText, int *length, int *height )\n{\n\tCL_DrawStringLen( con.curFont, pText, length, height, FONT_DRAW_UTF8 );\n}\n\n/*\n====================\nCon_DrawString\n\nclient version of routine\n====================\n*/\nint Con_DrawString( int x, int y, const char *string, const rgba_t setColor )\n{\n\treturn CL_DrawString( x, y, string, setColor, con.curFont, FONT_DRAW_UTF8 );\n}\n\n/*\n================\nCon_Init\n================\n*/\nvoid Con_Init( void )\n{\n\tif( host.type == HOST_DEDICATED )\n\t\treturn; // dedicated server already have console\n\n\t// must be init before startup video subsystem\n\tCvar_RegisterVariable( &scr_conspeed );\n\tCvar_RegisterVariable( &con_notifytime );\n\tCvar_RegisterVariable( &con_fontsize );\n\tCvar_RegisterVariable( &con_charset );\n\tCvar_RegisterVariable( &con_fontscale );\n\tCvar_RegisterVariable( &con_fontrender );\n\tCvar_RegisterVariable( &con_fontnum );\n\tCvar_RegisterVariable( &con_color );\n\tCvar_RegisterVariable( &scr_drawversion );\n\tCvar_RegisterVariable( &con_oldfont );\n\n\t// init the console buffer\n\tcon.bufsize = CON_TEXTSIZE;\n\tcon.buffer = (char *)Z_Calloc( con.bufsize );\n\tcon.maxlines = CON_MAXLINES;\n\tcon.lines = (con_lineinfo_t *)Z_Calloc( con.maxlines * sizeof( *con.lines ));\n\tcon.lines_first = con.lines_count = 0;\n\tcon.num_times = CON_TIMES; // default as 4\n\n\tCon_CheckResize();\n\n\tCon_ClearField( &con.input );\n\tcon.input.widthInChars = con.linewidth;\n\n\tCon_ClearField( &con.chat );\n\tcon.chat.widthInChars = con.linewidth;\n\n\tCmd_AddCommand( \"toggleconsole\", Con_ToggleConsole_f, \"opens or closes the console\" );\n\tCmd_AddRestrictedCommand( \"clear\", Con_Clear_f, \"clear console history\" );\n\tCmd_AddCommand( \"messagemode\", Con_MessageMode_f, \"enable message mode \\\"say\\\"\" );\n\tCmd_AddCommand( \"messagemode2\", Con_MessageMode2_f, \"enable message mode \\\"say_team\\\"\" );\n\tCmd_AddCommand( \"contimes\", Con_SetTimes_f, \"change number of console overlay lines (4-64)\" );\n\tcon.initialized = true;\n\n\tCon_Printf( \"Console initialized.\\n\" );\n}\n\n/*\n================\nCon_Shutdown\n================\n*/\nvoid Con_Shutdown( void )\n{\n\tcon.initialized = false;\n\n\tif( con.buffer )\n\t\tMem_Free( con.buffer );\n\n\tif( con.lines )\n\t\tMem_Free( con.lines );\n\n\tcon.buffer = NULL;\n\tcon.lines = NULL;\n\tCon_SaveHistory( &con.history );\n}\n\n/*\n================\nCon_Print\n\nHandles cursor positioning, line wrapping, etc\nAll console printing must go through this in order to be displayed\nIf no console is visible, the notify window will pop up.\n================\n*/\nvoid Con_Print( const char *txt )\n{\n\tstatic qboolean cr_pending = false;\n\tstatic qboolean colorstring = false;\n\tstatic char buf[MAX_PRINT_MSG];\n\tstatic int  lastlength = 0;\n\tstatic int  bufpos = 0;\n\tstatic int  charpos = 0;\n\n\tqboolean norefresh = false;\n\tint\t\tc, mask = 0;\n\n\t// client not running\n\tif( !con.initialized || !con.buffer )\n\t\treturn;\n\n\tif( txt[0] == 2 )\n\t{\n\t\t// go to colored text\n\t\tif( Con_FixedFont( ))\n\t\t\tmask = 128;\n\t\ttxt++;\n\t}\n\n\tif( txt[0] == 3 )\n\t{\n\t\tnorefresh = true;\n\t\ttxt++;\n\t}\n\n\tfor( ; *txt; txt++ )\n\t{\n\t\tif( cr_pending )\n\t\t{\n\t\t\tCon_DeleteLastLine();\n\t\t\tcr_pending = false;\n\t\t}\n\t\tc = *txt;\n\n\t\tswitch( c )\n\t\t{\n\t\tcase '\\0':\n\t\t\tbreak;\n\t\tcase '\\r':\n\t\t\tif( txt[1] != '\\n' )\n\t\t\t{\n\t\t\t\tCon_AddLine( buf, bufpos, true );\n\t\t\t\tlastlength = CON_LINES_LAST().length;\n\t\t\t\tcr_pending = true;\n\t\t\t\tbufpos = 0;\n\t\t\t\tcharpos = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase '\\n':\n\t\t\tCon_AddLine( buf, bufpos, true );\n\t\t\tlastlength = CON_LINES_LAST().length;\n\t\t\tbufpos = 0;\n\t\t\tcharpos = 0;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbuf[bufpos++] = c | mask;\n\n\t\t\tif( IsColorString( txt ))\n\t\t\t{\n\t\t\t\t// first color string character\n\t\t\t\tcolorstring = true;\n\t\t\t}\n\t\t\telse if( colorstring )\n\t\t\t{\n\t\t\t\t// second color string character\n\t\t\t\tcolorstring = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// not a color string, move char counter\n\t\t\t\tcharpos++;\n\t\t\t}\n\n\t\t\tif(( bufpos >= sizeof( buf ) - 1 ) || charpos >= ( con.linewidth - 1 ))\n\t\t\t{\n\t\t\t\tCon_AddLine( buf, bufpos, true );\n\t\t\t\tlastlength = CON_LINES_LAST().length;\n\t\t\t\tbufpos = 0;\n\t\t\t\tcharpos = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( norefresh ) return;\n\n\t// custom renderer cause problems while updates screen on-loading\n\tif( SV_Active() && cls.state < ca_active && !cl.video_prepped && !cls.disable_screen )\n\t{\n\t\tif( bufpos != 0 )\n\t\t{\n\t\t\tCon_AddLine( buf, bufpos, lastlength != 0 );\n\t\t\tlastlength = 0;\n\t\t\tbufpos = 0;\n\t\t\tcharpos = 0;\n\t\t}\n\n\t\t// pump messages to avoid window hanging\n\t\tif( con.lastupdate < Sys_DoubleTime( ))\n\t\t{\n\t\t\tcon.lastupdate = Sys_DoubleTime() + 1.0;\n\t\t\tHost_InputFrame();\n\t\t}\n\n\t\t// FIXME: disable updating screen, because when texture is bound any console print\n\t\t// can re-bound it to console font texture\n#if 0\n\t\tif( !inupdate )\n\t\t{\n\t\t\tinupdate = true;\n\t\t\tSCR_UpdateScreen ();\n\t\t\tinupdate = false;\n\t\t}\n#endif\n\t}\n}\n\n/*\n================\nCon_NXPrintfv\n\nDraw a single debug line with specified height, color and time to live\n================\n*/\nstatic void Con_NXPrintfv( keydest_t key_dest, const con_nprint_t *info, const char *fmt, va_list va )\n{\n\tif( info->index < 0 || info->index >= ARRAYSIZE( con.notify ))\n\t\treturn;\n\n\tQ_vsnprintf( con.notify[info->index].szNotify, sizeof( con.notify[info->index].szNotify ), fmt, va );\n\n\t// setup values\n\tcon.notify[info->index].key_dest = key_dest;\n\tcon.notify[info->index].expire = host.realtime + info->time_to_live;\n\tMakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 );\n\tcon.draw_notify = true;\n}\n\n/*\n================\nCon_NPrint\n\nDraw a single debug line with specified height\n================\n*/\nvoid GAME_EXPORT Con_NPrintf( int idx, const char *fmt, ... )\n{\n\tva_list\targs;\n\tcon_nprint_t info =\n\t{\n\t\t.index = idx,\n\t\t.time_to_live = 4.0f,\n\t\t.color = { 1.0f, 1.0f, 1.0f },\n\t};\n\n\tva_start( args, fmt );\n\tCon_NXPrintfv( key_game, &info, fmt, args );\n\tva_end( args );\n}\n\n/*\n================\nCon_NXPrint\n\nDraw a single debug line with specified height, color and time to live\n================\n*/\nvoid GAME_EXPORT Con_NXPrintf( con_nprint_t *info, const char *fmt, ... )\n{\n\tva_list\targs;\n\n\tif( !info ) return;\n\n\tva_start( args, fmt );\n\tCon_NXPrintfv( key_game, info, fmt, args );\n\tva_end( args );\n}\n\n/*\n================\nUI_NPrint\n\nDraw a single debug line with specified height (menu version)\n================\n*/\nvoid GAME_EXPORT UI_NPrintf( int idx, const char *fmt, ... )\n{\n\tva_list\targs;\n\tcon_nprint_t info =\n\t{\n\t\t.index = idx,\n\t\t.time_to_live = 4.0f,\n\t\t.color = { 1.0f, 1.0f, 1.0f },\n\t};\n\n\tva_start( args, fmt );\n\tCon_NXPrintfv( key_menu, &info, fmt, args );\n\tva_end( args );\n}\n\n/*\n================\nUI_NXPrint\n\nDraw a single debug line with specified height, color and time to live (menu version)\n================\n*/\nvoid GAME_EXPORT UI_NXPrintf( con_nprint_t *info, const char *fmt, ... )\n{\n\tva_list\targs;\n\n\tif( !info ) return;\n\n\tva_start( args, fmt );\n\tCon_NXPrintfv( key_menu, info, fmt, args );\n\tva_end( args );\n}\n\n/*\n=============================================================================\n\nEDIT FIELDS\n\n=============================================================================\n*/\n/*\n================\nCon_ClearField\n================\n*/\nstatic void Con_ClearField( field_t *edit )\n{\n\tmemset( edit->buffer, 0, sizeof( edit->buffer ));\n\tedit->cursor = 0;\n\tedit->scroll = 0;\n}\n\n/*\n================\nField_Set\n================\n*/\nstatic void Field_Set( field_t *f, const char *string )\n{\n\tf->scroll = 0;\n\tf->cursor = Q_strncpy( f->buffer, string, sizeof( f->buffer ));\n}\n\n/*\n================\nField_Paste\n================\n*/\nstatic void Field_Paste( field_t *edit )\n{\n\tchar\t*cbd;\n\tint\ti, pasteLen;\n\n\tcbd = Sys_GetClipboardData();\n\tif( !cbd ) return;\n\n\t// send as if typed, so insert / overstrike works properly\n\tpasteLen = Q_strlen( cbd );\n\tfor( i = 0; i < pasteLen; i++ )\n\t\tField_CharEvent( edit, cbd[i] );\n}\n\n/*\n=================\nField_GoTo\n\n=================\n*/\nstatic void Field_GoTo( field_t *edit, int pos )\n{\n\tedit->cursor = pos;\n\tedit->scroll = Q_max( 0, edit->cursor - edit->widthInChars );\n}\n\n/*\n=================\nField_KeyDownEvent\n\nPerforms the basic line editing functions for the console,\nin-game talk, and menu fields\n\nKey events are used for non-printable characters, others are gotten from char events.\n=================\n*/\nstatic void Field_KeyDownEvent( field_t *edit, int key )\n{\n\tint\tlen;\n\n\t// shift-insert is paste\n\tif((( key == K_INS ) || ( key == K_KP_INS )) && Key_IsDown( K_SHIFT ))\n\t{\n\t\tField_Paste( edit );\n\t\treturn;\n\t}\n\n\tlen = Q_strlen( edit->buffer );\n\n\tif( key == K_DEL )\n\t{\n\t\tif( edit->cursor < len )\n\t\t\tmemmove( edit->buffer + edit->cursor, edit->buffer + edit->cursor + 1, len - edit->cursor );\n\t\treturn;\n\t}\n\n\tif( key == K_BACKSPACE || key == K_X_BUTTON )\n\t{\n\t\tif( edit->cursor > 0 )\n\t\t{\n\t\t\tint newcursor = Con_UtfMoveLeft( edit->buffer, edit->cursor );\n\t\t\tmemmove( edit->buffer + newcursor, edit->buffer + edit->cursor, len - edit->cursor + 1 );\n\t\t\tedit->cursor = newcursor;\n\t\t\tif( edit->scroll ) edit->scroll--;\n\t\t}\n\t\treturn;\n\t}\n\n\tif( key == K_RIGHTARROW || key == K_DPAD_RIGHT )\n\t{\n\t\tif( edit->cursor < len ) edit->cursor = Con_UtfMoveRight( edit->buffer, edit->cursor, edit->widthInChars );\n\t\tif( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len )\n\t\t\tedit->scroll++;\n\t\treturn;\n\t}\n\n\tif( key == K_LEFTARROW || key == K_DPAD_LEFT )\n\t{\n\t\tif( edit->cursor > 0 ) edit->cursor = Con_UtfMoveLeft( edit->buffer, edit->cursor );\n\t\tif( edit->cursor < edit->scroll ) edit->scroll--;\n\t\treturn;\n\t}\n\n\tif( key == K_HOME || ( Q_tolower(key) == 'a' && Key_IsDown( K_CTRL )))\n\t{\n\t\tField_GoTo( edit, 0 );\n\t\treturn;\n\t}\n\n\tif( key == K_END || ( Q_tolower(key) == 'e' && Key_IsDown( K_CTRL )))\n\t{\n\t\tField_GoTo( edit, len );\n\t\treturn;\n\t}\n\n\tif( key == K_INS )\n\t{\n\t\thost.key_overstrike = !host.key_overstrike;\n\t\treturn;\n\t}\n}\n\n/*\n==================\nField_CharEvent\n==================\n*/\nstatic void Field_CharEvent( field_t *edit, int ch )\n{\n\tint\tlen;\n\n\tif( ch == 'v' - 'a' + 1 )\n\t{\n\t\t// ctrl-v is paste\n\t\tField_Paste( edit );\n\t\treturn;\n\t}\n\n\tif( ch == 'c' - 'a' + 1 )\n\t{\n\t\t// ctrl-c clears the field\n\t\tCon_ClearField( edit );\n\t\treturn;\n\t}\n\n\tlen = Q_strlen( edit->buffer );\n\n\tif( ch == 'a' - 'a' + 1 )\n\t{\n\t\t// ctrl-a is home\n\t\tField_GoTo( edit, 0 );\n\t\treturn;\n\t}\n\n\tif( ch == 'e' - 'a' + 1 )\n\t{\n\t\t// ctrl-e is end\n\t\tField_GoTo( edit, len );\n\t\treturn;\n\t}\n\n\t// ignore any other non printable chars\n\tif( ch < 32 ) return;\n\n\tif( host.key_overstrike )\n\t{\n\t\tif ( edit->cursor == MAX_STRING - 1 ) return;\n\t\tedit->buffer[edit->cursor] = ch;\n\t\tedit->cursor++;\n\t}\n\telse\n\t{\n\t\t// insert mode\n\t\tif ( len == MAX_STRING - 1 ) return; // all full\n\t\tmemmove( edit->buffer + edit->cursor + 1, edit->buffer + edit->cursor, len + 1 - edit->cursor );\n\t\tedit->buffer[edit->cursor] = ch;\n\t\tedit->cursor++;\n\t}\n\n\tif( edit->cursor >= edit->widthInChars ) edit->scroll++;\n\tif( edit->cursor == len + 1 ) edit->buffer[edit->cursor] = 0;\n}\n\n/*\n==================\nField_DrawInputLine\n==================\n*/\nstatic void Field_DrawInputLine( int x, int y, const field_t *edit )\n{\n\tint curPos;\n\tchar str[MAX_SYSPATH];\n\tconst byte *colorDefault = g_color_table[ColorIndex( COLOR_DEFAULT )];\n\tconst int prestep = bound( 0, edit->scroll, sizeof( edit->buffer ) - 1 );\n\tconst int drawLen = bound( 0, edit->widthInChars, sizeof( str ));\n\tconst int cursorCharPos = bound( 0, edit->cursor - prestep, sizeof( str ));\n\n\tstr[0] = 0;\n\tQ_strncpy( str, edit->buffer + prestep, drawLen );\n\n\t// draw it\n\tCL_DrawString( x, y, str, colorDefault, con.curFont, FONT_DRAW_UTF8 );\n\n\t// draw the cursor\n\tif((int)( host.realtime * 4 ) & 1 ) return; // off blink\n\n\t// calc cursor position\n\tstr[cursorCharPos] = 0;\n\tCL_DrawStringLen( con.curFont, str, &curPos, NULL, FONT_DRAW_UTF8 );\n\n\tif( host.key_overstrike )\n\t{\n\t\tCL_DrawCharacter( x + curPos, y, '|', colorDefault, con.curFont, 0 );\n\t}\n\telse\n\t{\n\t\tCL_DrawCharacter( x + curPos, y, '_', colorDefault, con.curFont, 0 );\n\t}\n}\n\n/*\n=============================================================================\n\nCONSOLE HISTORY HANDLING\n\n=============================================================================\n*/\n/*\n===================\nCon_HistoryFromField\n\n===================\n*/\nstatic void Con_HistoryFromField( history_line_t *dst, const field_t *src )\n{\n\tQ_strncpy( dst->buffer, src->buffer, sizeof( dst->buffer ));\n\tdst->cursor = src->cursor;\n\tdst->scroll = src->scroll;\n}\n\n/*\n===================\nCon_HistoryToField\n\n===================\n*/\nstatic void Con_HistoryToField( field_t *dst, const history_line_t *src )\n{\n\tQ_strncpy( dst->buffer, src->buffer, sizeof( dst->buffer ));\n\tdst->cursor = src->cursor;\n\tdst->scroll = src->scroll;\n}\n\n/*\n===================\nCon_HistoryUp\n\n===================\n*/\nstatic void Con_HistoryUp( con_history_t *self, field_t *in )\n{\n\tif( self->line == self->next )\n\t\tCon_HistoryFromField( &self->backup, in );\n\telse\n\t\tCon_HistoryFromField( &self->lines[self->line % CON_HISTORY], in );\n\n\tif(( self->next - self->line ) < CON_HISTORY )\n\t\tself->line = Q_max( 0, self->line - 1 );\n\n\tCon_HistoryToField( in, &self->lines[self->line % CON_HISTORY] );\n}\n\n/*\n===================\nCon_HistoryDown\n\n===================\n*/\nstatic void Con_HistoryDown( con_history_t *self, field_t *in )\n{\n\tCon_HistoryFromField( &self->lines[self->line % CON_HISTORY], in );\n\n\tself->line = Q_min( self->next, self->line + 1 );\n\tif( self->line == self->next )\n\t\tCon_HistoryToField( in, &self->backup );\n\telse\n\t\tCon_HistoryToField( in, &self->lines[self->line % CON_HISTORY] );\n}\n\n/*\n===================\nCon_HistoryAppend\n===================\n*/\nstatic void Con_HistoryAppend( con_history_t *self, const field_t *from )\n{\n\tint prevLine = Q_max( 0, self->line - 1 );\n\tconst char *buf = from->buffer;\n\n\t// skip backslashes\n\tif( from->buffer[0] == '\\\\' || from->buffer[1] == '/' )\n\t\tbuf++;\n\n\t// only if non-empty\n\tif( !from->buffer[0] )\n\t\treturn;\n\n\t// skip empty commands\n\tif( Q_isspace( buf ))\n\t\treturn;\n\n\t// if not copy (don't ignore backslashes)\n\tif( !Q_strcmp( from->buffer, self->lines[prevLine % CON_HISTORY].buffer ))\n\t\treturn;\n\n\tCon_HistoryFromField( &self->lines[self->next % CON_HISTORY], from );\n\tself->line = ++self->next;\n}\n\nstatic void Con_LoadHistory( con_history_t *self )\n{\n\tfile_t *fd;\n\tint i;\n\n\tfd = FS_Open( \"console_history.txt\", \"rb\", true );\n\n\tif( !fd )\n\t\treturn;\n\n\twhile( !FS_Eof( fd ))\n\t{\n\t\thistory_line_t *f = &self->lines[self->next % CON_HISTORY];\n\n\t\tFS_Gets( fd, f->buffer, sizeof( f->buffer ));\n\t\tf->cursor = Q_strlen( f->buffer );\n\t\tf->scroll = 0;\n\n\t\t// skip empty lines\n\t\tif( f->cursor == 0 )\n\t\t\tcontinue;\n\n\t\t// skip repeating lines\n\t\tif( self->next > 0 )\n\t\t{\n\t\t\tconst history_line_t *prev = &self->lines[(self->next - 1) % CON_HISTORY];\n\t\t\tif( !Q_stricmp( prev->buffer, f->buffer ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tself->next++;\n\t}\n\n\tFS_Close( fd );\n\n\tfor( i = self->next; i < CON_HISTORY; i++ )\n\t{\n\t\thistory_line_t *f = &self->lines[i];\n\n\t\tmemset( f, 0, sizeof( *f ));\n\t}\n\n\tself->line = self->next;\n}\n\nstatic void Con_SaveHistory( con_history_t *self )\n{\n\tint historyStart = self->next - CON_HISTORY, i;\n\tfile_t *f;\n\n\t// do not save history if nothing was executed\n\tif( self->next == 0 )\n\t\treturn;\n\n\tif( historyStart < 0 )\n\t\thistoryStart = 0;\n\n\tf = FS_Open( \"console_history.txt\", \"wb\", true );\n\n\tfor( i = historyStart; i < self->next; i++ )\n\t{\n\t\tconst char *s = self->lines[i % CON_HISTORY].buffer;\n\n\t\t// HACKHACK: don't save lines that have something that looks like a password\n\t\tif( Q_stristr( s, \"password\" ) || Q_stristr( s, \"_pw\" ))\n\t\t\tcontinue;\n\n\t\tFS_Printf( f, \"%s\\n\", s );\n\t}\n\n\tFS_Close( f );\n}\n\n\n/*\n=============================================================================\n\nCONSOLE LINE EDITING\n\n=============================================================================\n*/\n/*\n====================\nKey_Console\n\nHandles history and console scrollback\n====================\n*/\nvoid Key_Console( int key )\n{\n\t// exit the console by pressing MINUS on NSwitch\n\t// or both Back(Select)/Start buttons for everyone else\n\tif( key == K_BACK_BUTTON || key == K_START_BUTTON || key == K_ESCAPE )\n\t{\n\t\tif( cls.state == ca_active && !cl.background )\n\t\t\tKey_SetKeyDest( key_game );\n\t\telse UI_SetActiveMenu( true );\n\t\treturn;\n\t}\n\n\t// ctrl-L clears screen\n\tif( key == 'l' && Key_IsDown( K_CTRL ))\n\t{\n\t\tCbuf_AddText( \"clear\\n\" );\n\t\treturn;\n\t}\n\n\t// enter or A finish the line\n\tif( key == K_ENTER || key == K_KP_ENTER || key == K_A_BUTTON )\n\t{\n\t\t// backslash text are commands, else chat\n\t\tif( con.input.buffer[0] == '\\\\' || con.input.buffer[0] == '/' )\n\t\t\tCbuf_AddText( con.input.buffer + 1 ); // skip backslash\n\t\telse Cbuf_AddText( con.input.buffer ); // valid command\n\t\tCbuf_AddText( \"\\n\" );\n\n\t\t// echo to console\n\t\tCon_Printf( \">%s\\n\", con.input.buffer );\n\n\t\t// copy line to history buffer\n\t\t// just in case, remove all CR and LF characters pushing it to the history\n\t\t// not sure how they get even added in the first place\n\t\tCOM_RemoveLineFeed( con.input.buffer, sizeof( con.input.buffer ));\n\t\tCon_HistoryAppend( &con.history, &con.input );\n\n\t\tCon_ClearField( &con.input );\n\t\tcon.input.widthInChars = con.linewidth;\n\t\tCon_Bottom();\n\n\t\tif( cls.state == ca_disconnected )\n\t\t{\n\t\t\t// force an update, because the command may take some time\n\t\t\tSCR_UpdateScreen ();\n\t\t}\n\t\treturn;\n\t}\n\n\t// command completion\n\tif( key == K_TAB || key == K_L2_BUTTON )\n\t{\n\t\tCon_CompleteCommand( &con.input );\n\t\tCon_Bottom();\n\t\treturn;\n\t}\n\n\t// command history (ctrl-p ctrl-n for unix style)\n\tif(( key == K_MWHEELUP && Key_IsDown( K_SHIFT )) || ( key == K_UPARROW ) || (( Q_tolower(key) == 'p' ) && Key_IsDown( K_CTRL )))\n\t{\n\t\tCon_HistoryUp( &con.history, &con.input );\n\t\treturn;\n\t}\n\n\tif(( key == K_MWHEELDOWN && Key_IsDown( K_SHIFT )) || ( key == K_DOWNARROW ) || (( Q_tolower(key) == 'n' ) && Key_IsDown( K_CTRL )))\n\t{\n\t\tCon_HistoryDown( &con.history, &con.input );\n\t\treturn;\n\t}\n\n\t// console scrolling\n\tif( key == K_PGUP || key == K_DPAD_UP )\n\t{\n\t\tCon_PageUp( 1 );\n\t\treturn;\n\t}\n\n\tif( key == K_PGDN || key == K_DPAD_DOWN )\n\t{\n\t\tCon_PageDown( 1 );\n\t\treturn;\n\t}\n\n\tif( key == K_MWHEELUP )\n\t{\n\t\tif( Key_IsDown( K_CTRL ))\n\t\t\tCon_PageUp( 8 );\n\t\telse Con_PageUp( 2 );\n\t\treturn;\n\t}\n\n\tif( key == K_MWHEELDOWN )\n\t{\n\t\tif( Key_IsDown( K_CTRL ))\n\t\t\tCon_PageDown( 8 );\n\t\telse Con_PageDown( 2 );\n\t\treturn;\n\t}\n\n\t// ctrl-home = top of console\n\tif( key == K_HOME && Key_IsDown( K_CTRL ))\n\t{\n\t\tCon_Top();\n\t\treturn;\n\t}\n\n\t// ctrl-end = bottom of console\n\tif( key == K_END && Key_IsDown( K_CTRL ))\n\t{\n\t\tCon_Bottom();\n\t\treturn;\n\t}\n\n\t// enable the OSK with button press\n\tif( key == K_Y_BUTTON )\n\t{\n\t\tKey_EnableTextInput( true, true );\n\t\treturn;\n\t}\n\n\t// pass to the normal editline routine\n\tField_KeyDownEvent( &con.input, key );\n}\n\n/*\n================\nKey_Message\n\nIn game talk message\n================\n*/\nvoid Key_Message( int key )\n{\n\tchar\tbuffer[MAX_SYSPATH];\n\n\tif( key == K_ESCAPE || key == K_BACK_BUTTON )\n\t{\n\t\tKey_SetKeyDest( key_game );\n\t\tCon_ClearField( &con.chat );\n\t\treturn;\n\t}\n\n\tif( key == K_ENTER || key == K_KP_ENTER || key == K_A_BUTTON )\n\t{\n\t\tif( con.chat.buffer[0] && cls.state == ca_active )\n\t\t{\n\t\t\tQ_snprintf( buffer, sizeof( buffer ), \"%s \\\"%s\\\"\\n\", con.chat_cmd, con.chat.buffer );\n\n\t\t\tif( g_messagemode_privileged )\n\t\t\t\tCbuf_AddText( buffer );\n\t\t\telse Cbuf_AddFilteredText( buffer );\n\t\t}\n\n\t\tKey_SetKeyDest( key_game );\n\t\tCon_ClearField( &con.chat );\n\t\treturn;\n\t}\n\n\tField_KeyDownEvent( &con.chat, key );\n}\n\n/*\n==============================================================================\n\nDRAWING\n\n==============================================================================\n*/\n/*\n================\nCon_DrawInput\n\nThe input line scrolls horizontally if typing goes beyond the right edge\n================\n*/\nstatic void Con_DrawInput( int lines )\n{\n\tint\ty;\n\n\t// don't draw anything (always draw if not active)\n\tif( cls.key_dest != key_console || !con.curFont )\n\t\treturn;\n\n\ty = lines - ( con.curFont->charHeight * 2 );\n\tCL_DrawCharacter( con.curFont->charWidths[' '], y, ']', g_color_table[7], con.curFont, 0 );\n\tField_DrawInputLine(  con.curFont->charWidths[' ']*2, y, &con.input );\n}\n\n/*\n================\nCon_DrawDebugLines\n\nCustom debug messages\n================\n*/\nstatic int Con_DrawDebugLines( void )\n{\n\tnotify_t *notify = con.notify;\n\tint\ti, count = 0;\n\tint\tdefaultX;\n\tint\ty = 20;\n\tint\tfontTall;\n\n\tif( !con.curFont || !con.curFont->valid )\n\t\treturn 0;\n\n\tdefaultX = refState.width / 4;\n\tfontTall = con.curFont->charHeight + 1;\n\n\tfor( i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ )\n\t{\n\t\tint x, len;\n\n\t\tif( host.realtime > notify->expire )\n\t\t\tcontinue;\n\n\t\tif( notify->key_dest != cls.key_dest )\n\t\t\tcontinue;\n\n\t\tCon_DrawStringLen( notify->szNotify, &len, NULL );\n\t\tx = refState.width - Q_max( defaultX, len ) - 10;\n\n\t\tif( y + fontTall > refState.height - 20 )\n\t\t\treturn count;\n\n\t\tcount++;\n\t\ty += fontTall;\n\t\tCL_DrawString( x, y, notify->szNotify, notify->color, con.curFont, FONT_DRAW_UTF8 | FONT_DRAW_NOLF );\n\t}\n\n\treturn count;\n}\n\n/*\n================\nCon_DrawDebug\n\nDraws the debug messages (not passed to console history)\n================\n*/\nvoid Con_DrawDebug( void )\n{\n\tstatic double\ttimeStart;\n\tstring\t\tdlstring;\n\tint\t\tx, y;\n\n\tif( scr_download.value != -1.0f )\n\t{\n\t\tint length;\n\t\tQ_snprintf( dlstring, sizeof( dlstring ), \"Downloading [%d remaining]: ^2%s^7 %5.1f%% time %.f secs\",\n\t\t\thost.downloadcount, host.downloadfile, scr_download.value, Sys_DoubleTime() - timeStart );\n\n\t\tCon_DrawStringLen( dlstring, &length, NULL );\n\t\tlength = Q_max( length, 300 );\n\t\tx = refState.width - length * 1.05f;\n\t\ty = con.curFont->charHeight * 1.05f;\n\t\tCon_DrawString( x, y, dlstring, g_color_table[7] );\n\t}\n\telse\n\t{\n\t\ttimeStart = host.realtime;\n\t}\n\n\tif( !host.allow_console || Cvar_VariableInteger( \"cl_background\" ) || Cvar_VariableInteger( \"sv_background\" ))\n\t\treturn;\n\n\tif( con.draw_notify && !Con_Visible( ))\n\t{\n\t\tif( Con_DrawDebugLines() == 0 )\n\t\t\tcon.draw_notify = false;\n\t}\n}\n\n/*\n================\nCon_DrawNotify\n\nDraws the last few lines of output transparently over the game top\n================\n*/\nstatic void Con_DrawNotify( void )\n{\n\tdouble\ttime = cl.time;\n\tint\ti, x, y = 0;\n\n\tif( !con.curFont ) return;\n\n\tx = con.curFont->charWidths[' ']; // offset one space at left screen side\n\n\tif( host.allow_console && ( !Cvar_VariableInteger( \"cl_background\" ) && !Cvar_VariableInteger( \"sv_background\" )))\n\t{\n\t\tfor( i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ )\n\t\t{\n\t\t\tcon_lineinfo_t\t*l = &CON_LINES( i );\n\n\t\t\tif( l->addtime < ( time - con_notifytime.value ))\n\t\t\t\tcontinue;\n\n\t\t\tCon_DrawString( x, y, l->start, g_color_table[7] );\n\t\t\ty += con.curFont->charHeight;\n\t\t}\n\t}\n\n\tif( cls.key_dest == key_message )\n\t{\n\t\tstring\tbuf;\n\t\tint\tlen;\n\n\t\t// update chatline position from client.dll\n\t\tif( clgame.dllFuncs.pfnChatInputPosition )\n\t\t\tclgame.dllFuncs.pfnChatInputPosition( &x, &y );\n\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s: \", con.chat_cmd );\n\n\t\tCon_DrawStringLen( buf, &len, NULL );\n\t\tCon_DrawString( x, y, buf, g_color_table[7] );\n\n\t\tField_DrawInputLine( x + len, y, &con.chat );\n\t}\n\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n}\n\n/*\n================\nCon_DrawConsoleLine\n\nDraws a line of the console; returns its height in lines.\nIf alpha is 0, the line is not drawn, but still wrapped and its height\nreturned.\n================\n*/\nstatic int Con_DrawConsoleLine( int y, int lineno )\n{\n\tcon_lineinfo_t\t*li = &CON_LINES( lineno );\n\n\tif( !li || !li->start || *li->start == '\\1' )\n\t\treturn 0;\t// this string will be shown only at notify\n\n\tif( y >= con.curFont->charHeight )\n\t{\n\t\tfloat x = con.curFont->charWidths[' '];\n\n\t\tCL_DrawString( x, y, li->start, g_color_table[7], con.curFont, FONT_DRAW_UTF8 );\n\t}\n\n\treturn con.curFont->charHeight;\n}\n\n/*\n================\nCon_LastVisibleLine\n\nCalculates the last visible line index and how much to show\nof it based on con.backscroll.\n================\n*/\nstatic void Con_LastVisibleLine( int *lastline )\n{\n\tint\ti, lines_seen = 0;\n\n\tcon.backscroll = Q_max( 0, con.backscroll );\n\t*lastline = 0;\n\n\t// now count until we saw con_backscroll actual lines\n\tfor( i = CON_LINES_COUNT - 1; i >= 0; i-- )\n\t{\n\t\t// line is the last visible line?\n\t\t*lastline = i;\n\n\t\tif( lines_seen + 1 > con.backscroll && lines_seen <= con.backscroll )\n\t\t\treturn;\n\n\t\tlines_seen += 1;\n\t}\n\n\t// if we get here, no line was on screen - scroll so that one line is visible then.\n\tcon.backscroll = lines_seen - 1;\n}\n\n/*\n================\nCon_DrawConsole\n\nDraws the console with the solid background\n================\n*/\nstatic void Con_DrawSolidConsole( int lines )\n{\n\tint\ti, x, y;\n\tfloat\tfraction;\n\tint\tstart;\n\tint\tstringLen, width = 0, charH;\n\tstring\tcurbuild;\n\tbyte\tcolor[4];\n\n\tif( lines <= 0 ) return;\n\n\t// draw the background\n\tref.dllFuncs.GL_SetRenderMode( kRenderNormal );\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 ); // to prevent grab color from screenfade\n\tif( refState.width * 3 / 4 < refState.height && lines >= refState.height )\n\t\tref.dllFuncs.R_DrawStretchPic( 0, lines - refState.height, refState.width, refState.height - refState.width * 3 / 4, 0, 0, 1, 1, R_GetBuiltinTexture( REF_BLACK_TEXTURE) );\n\tref.dllFuncs.R_DrawStretchPic( 0, lines - refState.width * 3 / 4, refState.width, refState.width * 3 / 4, 0, 0, 1, 1, con.background );\n\n\tif( !con.curFont || !host.allow_console )\n\t\treturn; // nothing to draw\n\n\t// draw current version\n\tmemcpy( color, g_color_table[7], sizeof( color ));\n\n\tQ_snprintf( curbuild, MAX_STRING, XASH_ENGINE_NAME \" %i/\" XASH_VERSION \" (%s-%s build %i)\", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));\n\n\tCon_DrawStringLen( curbuild, &stringLen, &charH );\n\n\tstart = refState.width - stringLen;\n\tfraction = lines / (float)refState.height;\n\tcolor[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number\n\n\tCon_DrawString( start, 0, curbuild, color );\n\n\t// draw the text\n\tif( CON_LINES_COUNT > 0 )\n\t{\n\t\tint\tymax = lines - (con.curFont->charHeight * 2.0f);\n\t\tint\tlastline;\n\n\t\tCon_LastVisibleLine( &lastline );\n\t\ty = ymax - con.curFont->charHeight;\n\n\t\tif( con.backscroll )\n\t\t{\n\t\t\tstart = con.curFont->charWidths[' ']; // offset one space at left screen side\n\n\t\t\t// draw red arrows to show the buffer is backscrolled\n\t\t\tfor( x = 0; x < con.linewidth; x += 4 )\n\t\t\t\tCL_DrawCharacter( ( x + 1 ) * start, y, '^', g_color_table[1], con.curFont, 0 );\n\t\t\ty -= con.curFont->charHeight;\n\t\t}\n\t\tx = lastline;\n\n\t\twhile( 1 )\n\t\t{\n\t\t\ty -= Con_DrawConsoleLine( y, x );\n\n\t\t\t// top of console buffer or console window\n\t\t\tif( x == 0 || y < con.curFont->charHeight )\n\t\t\t\tbreak;\n\t\t\tx--;\n\t\t}\n\t}\n\n\t// draw the input prompt, user text, and cursor if desired\n\tCon_DrawInput( lines );\n\n\ty = lines - ( con.curFont->charHeight * 1.2f );\n\tSCR_DrawFPS( Q_max( y, 4 )); // to avoid to hide fps counter\n\n\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n}\n\n/*\n==================\nCon_DrawConsole\n==================\n*/\nvoid Con_DrawConsole( void )\n{\n\t// never draw console when changelevel in-progress\n\tif( cls.state != ca_disconnected && ( cls.changelevel || cls.changedemo ))\n\t\treturn;\n\n\t// check for console width changes from a vid mode change\n\tCon_CheckResize ();\n\n\tif( cls.state == ca_connecting || cls.state == ca_connected )\n\t{\n\t\tif( !cl_allow_levelshots.value && !cls.timedemo )\n\t\t{\n\t\t\tif( cls.key_dest != key_console && ( Cvar_VariableInteger( \"cl_background\" ) || Cvar_VariableInteger( \"sv_background\" )))\n\t\t\t\tcon.vislines = con.showlines = 0;\n\t\t\telse con.vislines = con.showlines = refState.height;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcon.showlines = 0;\n\n\t\t\tif( host_developer.value >= DEV_EXTENDED && !cls.timedemo )\n\t\t\t\tCon_DrawNotify(); // draw notify lines\n\t\t}\n\t}\n\n\t// if disconnected, render console full screen\n\tswitch( cls.state )\n\t{\n\tcase ca_disconnected:\n\t\tif( cls.key_dest != key_menu )\n\t\t{\n\t\t\tCon_DrawSolidConsole( refState.height );\n\t\t\tKey_SetKeyDest( key_console );\n\t\t}\n\t\tbreak;\n\tcase ca_connecting:\n\tcase ca_connected:\n\tcase ca_validate:\n\t\t// force to show console always for -dev 3 and higher\n\t\tCon_DrawSolidConsole( con.vislines );\n\t\tbreak;\n\tcase ca_active:\n\tcase ca_cinematic:\n\t\tif( Cvar_VariableInteger( \"cl_background\" ) || Cvar_VariableInteger( \"sv_background\" ))\n\t\t{\n\t\t\tif( cls.key_dest == key_console )\n\t\t\t\tCon_DrawSolidConsole( refState.height );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( con.vislines )\n\t\t\t\tCon_DrawSolidConsole( con.vislines );\n\t\t\telse if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message ) && !cls.timedemo )\n\t\t\t\tCon_DrawNotify(); // draw notify lines\n\t\t}\n\t\tbreak;\n\t}\n\n\tif( !Con_Visible( )) SCR_DrawFPS( 4 );\n}\n\n/*\n==================\nCon_DrawVersion\n\nUsed by menu\n==================\n*/\nvoid Con_DrawVersion( void )\n{\n\t// draws the current build\n\tbyte\t*color = g_color_table[7];\n\tint\tstringLen, charH = 0;\n\tint\tstart, height = refState.height;\n\tstring\tcurbuild;\n\n\tif( !scr_drawversion.value )\n\t\treturn;\n\n\tif( cls.key_dest == key_menu )\n\t{\n\t\tQ_snprintf( curbuild, sizeof( curbuild ),\n\t\t\t\"v%i/\" XASH_VERSION \" (%s-%s build %i)\", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));\n\t}\n\telse\n\t{\n\t\tqboolean draw_version;\n\n\t\tif( CL_IsDevOverviewMode() == 2 || net_graph.value )\n\t\t\treturn;\n\n\t\tdraw_version = cls.scrshot_action == scrshot_normal\n\t\t\t|| cls.scrshot_action == scrshot_snapshot\n\t\t\t|| host.force_draw_version_time > host.realtime;\n\n\t\tif( !draw_version )\n\t\t\treturn;\n\n\t\tQ_snprintf( curbuild, sizeof( curbuild ),\n\t\t\tXASH_ENGINE_NAME \" v%i/\" XASH_VERSION \" (%s-%s build %i)\", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));\n\t}\n\n\tCon_DrawStringLen( curbuild, &stringLen, &charH );\n\tstart = refState.width - stringLen * 1.05f;\n\theight -= charH * 1.05f;\n\n\tCon_DrawString( start, height, curbuild, color );\n}\n\n/*\n==================\nCon_RunConsole\n\nScroll it up or down\n==================\n*/\nvoid Con_RunConsole( void )\n{\n\tfloat\tlines_per_frame;\n\n\tCon_SetColor( );\n\n\t// decide on the destination height of the console\n\tif( host.allow_console && cls.key_dest == key_console )\n\t{\n#if XASH_MOBILE_PLATFORM\n\t\tcon.showlines = refState.height; // always full screen on mobile devices\n#else\n\t\tif( cls.state < ca_active || cl.first_frame )\n\t\t\tcon.showlines = refState.height;\t// full screen\n\t\telse con.showlines = (refState.height >> 1);\t// half screen\n#endif\n\t}\n\telse con.showlines = 0; // none visible\n\n\tlines_per_frame = fabs( scr_conspeed.value ) * host.realframetime;\n\n\tif( con.showlines < con.vislines )\n\t{\n\t\tcon.vislines -= lines_per_frame;\n\t\tif( con.showlines > con.vislines )\n\t\t\tcon.vislines = con.showlines;\n\t}\n\telse if( con.showlines > con.vislines )\n\t{\n\t\tcon.vislines += lines_per_frame;\n\t\tif( con.showlines < con.vislines )\n\t\t\tcon.vislines = con.showlines;\n\t}\n\n\tif( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags,  FCVAR_CHANGED ))\n\t{\n\t\t// update codepage parameters\n\t\tif( !Q_stricmp( con_charset.string, \"cp1251\" ))\n\t\t{\n\t\t\tg_codepage = 1251;\n\t\t}\n\t\telse if( !Q_stricmp( con_charset.string, \"cp1252\" ))\n\t\t{\n\t\t\tg_codepage = 1252;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tg_codepage = 0;\n\t\t}\n\n\t\tcls.accept_utf8 = !Q_stricmp( cl_charset.string, \"utf-8\" );\n\t\tCon_InvalidateFonts();\n\t\tCon_LoadConchars();\n\t\tClearBits( con_charset.flags,   FCVAR_CHANGED );\n\t\tClearBits( con_fontnum.flags,   FCVAR_CHANGED );\n\t\tClearBits( con_fontscale.flags, FCVAR_CHANGED );\n\t\tClearBits( cl_charset.flags,    FCVAR_CHANGED );\n\t\tClearBits( con_oldfont.flags,   FCVAR_CHANGED );\n\t}\n}\n\n/*\n==============================================================================\n\nCONSOLE INTERFACE\n\n==============================================================================\n*/\n/*\n================\nCon_CharEvent\n\nConsole input\n================\n*/\nvoid Con_CharEvent( int key )\n{\n\t// distribute the key down event to the apropriate handler\n\tif( cls.key_dest == key_console )\n\t{\n\t\tField_CharEvent( &con.input, key );\n\t}\n\telse if( cls.key_dest == key_message )\n\t{\n\t\tField_CharEvent( &con.chat, key );\n\t}\n}\n\nstatic int Con_LoadSimpleConback( const char *name, int flags )\n{\n\tint i;\n\n\tfor( i = 0; i < 5; i++ )\n\t{\n\t\tstring path;\n\n\t\tswitch( i )\n\t\t{\n\t\tcase 0:\n\t\t\tQ_snprintf( path, sizeof( path ), \"gfx/shell/%s.dds\", name );\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tQ_snprintf( path, sizeof( path ), \"gfx/shell/%s.bmp\", name );\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tQ_snprintf( path, sizeof( path ), \"gfx/shell/%s.tga\", name );\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tQ_snprintf( path, sizeof( path ), \"cached/%s640\", name );\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tQ_snprintf( path, sizeof( path ), \"cached/%s\", name );\n\t\t\tbreak;\n\t\t}\n\n\t\tif( g_fsapi.FileExists( path, false ))\n\t\t{\n\t\t\tint gl_texturenum = ref.dllFuncs.GL_LoadTexture( path, NULL, 0, flags );\n\n\t\t\tif( gl_texturenum )\n\t\t\t\treturn gl_texturenum;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/*\n=========\nCon_VidInit\n\nINTERNAL RESOURCE\n=========\n*/\nvoid Con_VidInit( void )\n{\n\tconst uint flags = TF_IMAGE|TF_ALLOW_NEAREST;\n\n\tif( !con.historyLoaded )\n\t{\n\t\tCon_LoadHistory( &con.history );\n\t\tcon.historyLoaded = true;\n\t}\n\n\tif( Sys_CheckParm( \"-oldfont\" ))\n\t\tCvar_DirectSet( &con_oldfont, \"1\" );\n\n\tCon_LoadConchars();\n\tCon_CheckResize();\n\n#if XASH_LOW_MEMORY\n\tcon.background = R_GetBuiltinTexture( REF_GRAY_TEXTURE );\n#else\n\t// loading console image\n\tcon.background = Con_LoadSimpleConback( host.allow_console ? \"conback\" : \"loading\", flags );\n\n\tif( !con.background ) // last chance - quake conback image\n\t{\n\t\tqboolean\t\tdraw_to_console = false;\n\t\tfs_offset_t\t\tlength = 0;\n\t\tconst byte *buf;\n\n\t\t// NOTE: only these games want to draw build number into console background\n\t\tif( !Q_stricmp( FS_Gamedir(), \"id1\" ))\n\t\t\tdraw_to_console = true;\n\n\t\tif( !Q_stricmp( FS_Gamedir(), \"hipnotic\" ))\n\t\t\tdraw_to_console = true;\n\n\t\tif( !Q_stricmp( FS_Gamedir(), \"rogue\" ))\n\t\t\tdraw_to_console = true;\n\n\t\tif( draw_to_console && con.curFont &&\n\t\t\t( buf = ref.dllFuncs.R_GetTextureOriginalBuffer( con.curFont->hFontTexture )) != NULL )\n\t\t{\n\t\t\tlmp_t\t*cb = (lmp_t *)FS_LoadFile( \"gfx/conback.lmp\", &length, false );\n\t\t\tchar\tver[64];\n\t\t\tbyte\t*dest;\n\t\t\tint\tx, y, len;\n\n\t\t\tif( cb && cb->width == 320 && cb->height == 200 )\n\t\t\t{\n\t\t\t\tlen = Q_snprintf( ver, 64, \"%i\", Q_buildnum( )); // can store only buildnum\n\t\t\t\tdest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * len;\n\t\t\t\ty = len;\n\t\t\t\tfor( x = 0; x < y; x++ )\n\t\t\t\t\tCon_DrawCharToConback( ver[x], buf, dest + (x << 3));\n\t\t\t\tcon.background = ref.dllFuncs.GL_LoadTexture( \"#gfx/conback.lmp\", (byte *)cb, length, TF_IMAGE );\n\t\t\t}\n\t\t\tif( cb ) Mem_Free( cb );\n\t\t}\n\n\t\tif( !con.background ) // trying the load unmodified conback\n\t\t\tcon.background = ref.dllFuncs.GL_LoadTexture( \"gfx/conback.lmp\", NULL, 0, TF_IMAGE );\n\t}\n\n\t// missed console image will be replaced as gray background like X-Ray or Crysis\n\tif( con.background == R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ) || con.background == 0 )\n\t\tcon.background = R_GetBuiltinTexture( REF_GRAY_TEXTURE );\n#endif\n}\n\n/*\n=========\nCon_InvalidateFonts\n\n=========\n*/\nvoid Con_InvalidateFonts( void )\n{\n\tint i;\n\tfor( i = 0; i < ARRAYSIZE( con.chars ); i++ )\n\t\tCL_FreeFont( &con.chars[i] );\n\tcon.curFont = NULL;\n}\n\n/*\n=========\nCon_FastClose\n\nimmediately close the console\n=========\n*/\nvoid Con_FastClose( void )\n{\n\tCon_ClearField( &con.input );\n\tCon_ClearNotify();\n\tcon.showlines = 0;\n\tcon.vislines = 0;\n}\n\n/*\n=========\nCon_DefaultColor\n\ncalled from MainUI\n=========\n*/\nvoid Con_DefaultColor( int r, int g, int b, qboolean gameui )\n{\n\tr = bound( 0, r, 255 );\n\tg = bound( 0, g, 255 );\n\tb = bound( 0, b, 255 );\n\n\t// gameui wants to override console color... check if it's not default\n\tif( gameui && ( g_color_table[7][0] != r || g_color_table[7][1] != g || g_color_table[7][2] != b ))\n\t{\n\t\t// yes, different from default orange, disable con_color\n\t\tSetBits( con_color.flags, FCVAR_READ_ONLY );\n\t\tClearBits( con_color.flags, FCVAR_CHANGED );\n\t}\n\n\tMakeRGBA( g_color_table[7], r, g, b, 255 );\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nstatic void Test_RunConHistory( void )\n{\n\tcon_history_t hist = { 0 };\n\tfield_t input = { 0 };\n\tconst char *strs1[] = { \"map t0a0\", \"quit\", \"wtf\", \"wtf\", \"\", \"nyan\" };\n\tconst char *strs2[] = { \"nyan\", \"wtf\", \"quit\", \"map t0a0\" };\n\tconst char *testbackup = \"unfinished_edit\";\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( strs1 ); i++ )\n\t{\n\t\tField_Set( &input, strs1[i] );\n\t\tCon_HistoryAppend( &hist, &input );\n\t}\n\n\tField_Set( &input, testbackup );\n\n\tfor( i = 0; i < ARRAYSIZE( strs2 ); i++ )\n\t{\n\t\tCon_HistoryUp( &hist, &input );\n\t\tTASSERT_STR( input.buffer, strs2[i] );\n\t}\n\n\t// check for overrun\n\tCon_HistoryUp( &hist, &input );\n\n\tfor( i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- )\n\t{\n\t\tTASSERT_STR( input.buffer, strs2[i] );\n\t\tCon_HistoryDown( &hist, &input );\n\t}\n\n\tTASSERT_STR( input.buffer, testbackup );\n}\n\nvoid Test_RunCon( void )\n{\n\tTRUN( Test_RunConHistory() );\n}\n\n#endif /* XASH_ENGINE_TESTS */\n"
  },
  {
    "path": "engine/client/gamma.c",
    "content": "/*\ngamma.c - gamma routines\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"xash3d_mathlib.h\"\n#include \"enginefeatures.h\"\n\n//-----------------------------------------------------------------------------\n// Gamma conversion support\n//-----------------------------------------------------------------------------\nstatic qboolean gamma_rebuilt;\nstatic byte\ttexgammatable[256];\nstatic uint\tlightgammatable[1024];\nstatic uint\tlineargammatable[1024];\nstatic uint\tscreengammatable[1024];\nstatic CVAR_DEFINE( v_direct, \"direct\", \"0.9\", 0, \"direct studio lighting\" );\nstatic CVAR_DEFINE( v_texgamma, \"texgamma\", \"2.0\", 0, \"texgamma amount\" );\nstatic CVAR_DEFINE( v_lightgamma, \"lightgamma\", \"2.5\", 0, \"lightgamma amount\" );\nstatic CVAR_DEFINE( v_brightness, \"brightness\", \"0.0\", FCVAR_ARCHIVE, \"brightness factor\" );\nstatic CVAR_DEFINE( v_gamma, \"gamma\", \"2.5\", FCVAR_ARCHIVE, \"gamma amount\" );\n\nstatic void BuildGammaTable( const float gamma, const float brightness, const float texgamma, const float lightgamma )\n{\n\tfloat g1, g2, g3;\n\tint i;\n\n\tif( gamma != 0.0 )\n\t\tg1 = 1.0 / gamma;\n\telse g1 = 0.4;\n\n\tg2 = g1 * texgamma;\n\n\tif( brightness <= 0.0 )\n\t\tg3 = 0.125;\n\telse if( brightness <= 1.0 )\n\t\tg3 = 0.125 - brightness * brightness * 0.075;\n\telse\n\t\tg3 = 0.05;\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tdouble d = pow( i / 255.0, (double)g2 );\n\t\tint inf = d * 255.0;\n\t\ttexgammatable[i] = bound( 0, inf, 255 );\n\t}\n\n\tfor( i = 0; i < 1024; i++ )\n\t{\n\t\tdouble d;\n\t\tfloat f = pow( i / 1023.0, (double)lightgamma );\n\t\tint inf;\n\n\t\tif( brightness > 1.0 )\n\t\t\tf *= brightness;\n\n\t\tif( f <= g3 )\n\t\t\tf = ( f / g3 ) * 0.125;\n\t\telse\n\t\t\tf = (( f - g3 ) / ( 1.0 - g3 )) * 0.875 + 0.125;\n\n\t\td = pow( (double)f, (double)g1 ); // do not remove the cast, or tests fail\n\t\tinf = d * 1023.0;\n\t\tlightgammatable[i] = bound( 0, inf, 1023 );\n\n\t\t// do these calculations in the same loop...\n\t\tlineargammatable[i] = pow( i / 1023.0, (double)gamma ) * 1023.0;\n\t\tscreengammatable[i] = pow( i / 1023.0, 1.0 / gamma ) * 1023.0;\n\t}\n}\n\nstatic void V_ValidateGammaCvars( void )\n{\n\tif( v_gamma.value < 1.8f )\n\t\tCvar_DirectSet( &v_gamma, \"1.8\" );\n\telse if( v_gamma.value > 3.0f )\n\t\tCvar_DirectSet( &v_gamma, \"3\" );\n\n\tif( v_texgamma.value < 1.8f )\n\t\tCvar_DirectSet( &v_texgamma, \"1.8\" );\n\telse if( v_texgamma.value > 3.0f )\n\t\tCvar_DirectSet( &v_texgamma, \"3\" );\n\n\tif( v_lightgamma.value < 1.8f )\n\t\tCvar_DirectSet( &v_lightgamma, \"1.8\" );\n\telse if( v_lightgamma.value > 3.0f )\n\t\tCvar_DirectSet( &v_lightgamma, \"3\" );\n\n\tif( v_brightness.value < 0.0f )\n\t\tCvar_DirectSet( &v_brightness, \"0\" );\n\telse if( v_brightness.value > 3.0f )\n\t\tCvar_DirectSet( &v_brightness, \"3\" );\n}\n\nvoid V_CheckGamma( void )\n{\n\tstatic qboolean dirty = false;\n\n\t// because these cvars were defined as archive\n\t// but wasn't doing anything useful\n\t// reset them into default values\n\t// this might be removed after a while\n\tif( v_direct.value == 1.0f || v_lightgamma.value == 1.0f )\n\t{\n\t\tCvar_DirectSet( &v_direct, \"0.9\" );\n\t\tCvar_DirectSet( &v_lightgamma, \"2.5\" );\n\t}\n\n\tif( cls.scrshot_action == scrshot_envshot || cls.scrshot_action == scrshot_skyshot )\n\t{\n\t\tdirty = true; // force recalculate next normal frame\n\t\tBuildGammaTable( 1.8f, 0.0f, 2.0f, 2.5f );\n\t\tif( ref.initialized )\n\t\t\tref.dllFuncs.R_GammaChanged( true );\n\t\treturn;\n\t}\n\n\tif( dirty || FBitSet( v_texgamma.flags|v_lightgamma.flags|v_brightness.flags|v_gamma.flags, FCVAR_CHANGED ))\n\t{\n\t\tV_ValidateGammaCvars();\n\n\t\tdirty = false;\n\t\tgamma_rebuilt = true;\n\n\t\tBuildGammaTable( v_gamma.value, v_brightness.value, v_texgamma.value, v_lightgamma.value );\n\n\t\t// force refdll to recalculate lightmaps\n\t\tif( ref.initialized )\n\t\t\tref.dllFuncs.R_GammaChanged( false );\n\t}\n}\n\nvoid V_CheckGammaEnd( void )\n{\n\t// don't reset changed flag if it was set during frame\n\t// keep it for next frame\n\tif( !gamma_rebuilt )\n\t\treturn;\n\n\tgamma_rebuilt = false;\n\n\t// keep the flags until the end of frame so client.dll will catch these changes\n\tif( FBitSet( v_texgamma.flags|v_lightgamma.flags|v_brightness.flags|v_gamma.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( v_texgamma.flags, FCVAR_CHANGED );\n\t\tClearBits( v_lightgamma.flags, FCVAR_CHANGED );\n\t\tClearBits( v_brightness.flags, FCVAR_CHANGED );\n\t\tClearBits( v_gamma.flags, FCVAR_CHANGED );\n\t}\n}\n\nvoid V_Init( void )\n{\n\tCvar_RegisterVariable( &v_texgamma );\n\tCvar_RegisterVariable( &v_lightgamma );\n\tCvar_RegisterVariable( &v_brightness );\n\tCvar_RegisterVariable( &v_gamma );\n\tCvar_RegisterVariable( &v_direct );\n\n\t// force gamma init\n\tSetBits( v_gamma.flags, FCVAR_CHANGED );\n\tV_CheckGamma();\n}\n\nbyte TextureToGamma( byte b )\n{\n\tif( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE ))\n\t\treturn b;\n\n\treturn texgammatable[b];\n}\n\nbyte LightToTexGamma( byte b )\n{\n\tif( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE ))\n\t\treturn b;\n\n\t// 255 << 2 is 1020, impossible to overflow\n\treturn lightgammatable[b << 2] >> 2;\n}\n\nuint ScreenGammaTable( uint b )\n{\n\tif( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE ))\n\t\treturn b;\n\n\tif( unlikely( b >= ARRAYSIZE( screengammatable )))\n\t\treturn 0;\n\n\treturn screengammatable[b];\n}\n\nuint LinearGammaTable( uint b )\n{\n\tif( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE ))\n\t\treturn b;\n\n\tif( unlikely( b >= ARRAYSIZE( lineargammatable )))\n\t\treturn 0;\n\treturn lineargammatable[b];\n}\n\nintptr_t V_GetGammaPtr( int parm )\n{\n\tswitch( parm )\n\t{\n\tcase PARM_GET_TEXGAMMATABLE_PTR:\n\t\treturn (intptr_t)texgammatable;\n\tcase PARM_GET_LIGHTGAMMATABLE_PTR:\n\t\treturn (intptr_t)lightgammatable;\n\tcase PARM_GET_SCREENGAMMATABLE_PTR:\n\t\treturn (intptr_t)screengammatable;\n\tcase PARM_GET_LINEARGAMMATABLE_PTR:\n\t\treturn (intptr_t)lineargammatable;\n\t}\n\n\treturn 0;\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\ntypedef struct precomputed_gamma_tables_s\n{\n\tfloat gamma;\n\tfloat brightness;\n\tfloat texgamma;\n\tfloat lightgamma;\n\tbyte  texgammatable[256];\n\tint   lightgammatable[1024];\n\tint   lineargammatable[1024];\n\tint   screengammatable[1024];\n} precomputed_gamma_tables_t;\n\n// put at the end of the file, to not confuse Qt Creator's parser\nprecomputed_gamma_tables_t *Test_GetGammaTables( int i );\n\nstatic void Test_PrecomputedGammaTables( void )\n{\n\tprecomputed_gamma_tables_t *data;\n\tint i = 0;\n\n\twhile(( data = Test_GetGammaTables( i )))\n\t{\n\t\tint j;\n\n\t\tBuildGammaTable( data->gamma, data->brightness, data->texgamma, data->lightgamma );\n\n\t\tfor( j = 0; j < 1024; j++ )\n\t\t{\n\t\t\tif( j < 256 )\n\t\t\t{\n\t\t\t\tTASSERT_EQi( texgammatable[j], data->texgammatable[j] );\n\t\t\t}\n\n\t\t\tTASSERT_EQi( lightgammatable[j], data->lightgammatable[j] );\n\t\t\tTASSERT_EQi( lineargammatable[j], data->lineargammatable[j] );\n\t\t\tTASSERT_EQi( screengammatable[j], data->screengammatable[j] );\n\t\t}\n\t\ti++;\n\t}\n}\n\nvoid Test_RunGamma( void )\n{\n\tTRUN( Test_PrecomputedGammaTables() );\n}\n\nprecomputed_gamma_tables_t *Test_GetGammaTables( int i )\n{\n\tstatic precomputed_gamma_tables_t precomputed_data[] = {\n\t{\n\t\t.gamma = 2.5,\n\t\t.brightness = 0.0,\n\t\t.texgamma = 2.0,\n\t\t.lightgamma = 2.5,\n\t\t.texgammatable = {\n\t\t\t0, 3, 5, 7, 9, 10, 12, 14, 15, 17, 19, 20, 22, 23, 25, 26, 27, 29, 30, 31, 33, 34, 35, 37, 38, 39, 41, 42, 43, 44, 46, 47, 48, 49, 50, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 82, 83, 84,\n\t\t\t85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 132, 133, 134, 135, 136, 137, 138,\n\t\t\t139, 140, 141, 142, 143, 144, 145, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 165, 166, 167, 168, 169, 170, 171, 172, 172, 173, 174, 175, 176, 177, 178, 179, 180, 180, 181, 182, 183, 184, 185,\n\t\t\t186, 186, 187, 188, 189, 190, 191, 192, 192, 193, 194, 195, 196, 197, 198, 198, 199, 200, 201, 202, 203, 204, 204, 205, 206, 207, 208, 209, 209, 210, 211, 212, 213, 214, 214, 215, 216, 217, 218, 219, 219, 220, 221, 222, 223, 224, 224, 225, 226, 227, 228, 229,\n\t\t\t229, 230, 231, 232, 233, 233, 234, 235, 236, 237, 238, 238, 239, 240, 241, 242, 242, 243, 244, 245, 246, 246, 247, 248, 249, 250, 250, 251, 252, 253, 254, 255\n\t\t},\n\t\t.lightgammatable = {\n\t\t\t0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,\n\t\t\t65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,\n\t\t\t124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,\n\t\t\t176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227,\n\t\t\t228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279,\n\t\t\t280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331,\n\t\t\t332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 343, 343, 344, 345, 346, 347, 348, 349, 350, 351, 353, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 376, 376, 377, 379, 379, 380, 381, 382, 383,\n\t\t\t384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435,\n\t\t\t436, 437, 438, 439, 440, 441, 442, 443, 444, 446, 446, 447, 449, 449, 450, 451, 452, 453, 455, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 468, 469, 470, 470, 472, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487,\n\t\t\t488, 489, 490, 491, 493, 493, 494, 495, 496, 498, 498, 499, 500, 502, 502, 503, 504, 505, 507, 508, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 520, 521, 521, 522, 523, 524, 525, 526, 527, 528, 530, 530, 531, 532, 533, 534, 536, 536, 537, 538, 539,\n\t\t\t540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 552, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 564, 564, 565, 566, 567, 568, 570, 571, 571, 572, 573, 574, 576, 576, 577, 578, 579, 580, 581, 583, 583, 584, 585, 586, 587, 588, 590, 591, 591,\n\t\t\t593, 593, 594, 596, 596, 597, 598, 600, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 611, 611, 612, 613, 615, 615, 616, 618, 618, 620, 621, 621, 622, 624, 625, 626, 627, 627, 628, 629, 630, 632, 633, 633, 634, 635, 636, 638, 638, 639, 641, 642, 643, 643,\n\t\t\t644, 645, 646, 648, 648, 650, 651, 651, 652, 654, 655, 656, 656, 658, 658, 660, 660, 662, 662, 664, 664, 665, 666, 668, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 682, 682, 683, 684, 685, 686, 687, 688, 690, 690, 691, 692, 693, 695, 696,\n\t\t\t696, 697, 698, 699, 700, 701, 702, 703, 705, 706, 706, 707, 708, 709, 710, 711, 712, 713, 715, 716, 716, 717, 718, 719, 721, 721, 722, 724, 724, 725, 727, 727, 728, 729, 730, 731, 733, 733, 734, 735, 736, 737, 738, 740, 740, 741, 743, 744, 744, 745, 747, 747,\n\t\t\t748, 749, 750, 752, 753, 753, 755, 755, 757, 757, 758, 759, 760, 761, 762, 764, 764, 766, 766, 767, 769, 770, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 782, 783, 784, 784, 785, 786, 788, 789, 790, 791, 792, 792, 794, 794, 796, 796, 797, 799, 800,\n\t\t\t801, 801, 802, 803, 804, 805, 806, 807, 809, 810, 810, 811, 813, 813, 815, 815, 816, 817, 819, 819, 820, 822, 823, 823, 824, 825, 826, 827, 829, 829, 830, 832, 832, 833, 835, 835, 837, 837, 839, 840, 840, 841, 842, 843, 844, 845, 847, 847, 849, 849, 850, 851,\n\t\t\t852, 854, 854, 856, 856, 857, 859, 859, 860, 861, 862, 864, 864, 865, 867, 867, 868, 870, 870, 872, 872, 874, 875, 875, 876, 877, 879, 880, 881, 881, 882, 883, 885, 885, 886, 887, 888, 890, 891, 891, 893, 893, 894, 895, 897, 898, 899, 900, 901, 902, 902, 903,\n\t\t\t904, 905, 907, 907, 908, 910, 910, 911, 912, 914, 914, 915, 917, 918, 919, 920, 921, 922, 922, 923, 925, 925, 926, 927, 928, 930, 931, 932, 932, 933, 935, 936, 936, 937, 939, 940, 940, 942, 942, 944, 944, 946, 947, 948, 949, 949, 950, 952, 952, 953, 955, 956,\n\t\t\t957, 958, 959, 959, 960, 962, 962, 964, 965, 965, 967, 967, 969, 970, 971, 972, 973, 973, 975, 975, 976, 977, 978, 979, 980, 982, 982, 984, 984, 986, 986, 988, 988, 990, 991, 992, 993, 993, 994, 995, 996, 998, 999, 999, 1000, 1002, 1002, 1004, 1005, 1005,\n\t\t\t1006, 1007, 1008, 1010, 1011, 1011, 1013, 1014, 1015, 1016, 1017, 1018, 1018, 1019, 1020, 1021, 1023\n\t\t},\n\t\t.lineargammatable = {\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n\t\t\t2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11,\n\t\t\t11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 25, 25,\n\t\t\t25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 46, 46, 46,\n\t\t\t47, 47, 48, 48, 48, 49, 49, 50, 50, 50, 51, 51, 52, 52, 52, 53, 53, 54, 54, 55, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 60, 61, 61, 62, 62, 63, 63, 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 76,\n\t\t\t77, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 83, 84, 84, 85, 86, 86, 87, 87, 88, 88, 89, 90, 90, 91, 91, 92, 92, 93, 94, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101, 102, 102, 103, 104, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 111, 111, 112,\n\t\t\t113, 113, 114, 115, 115, 116, 117, 117, 118, 119, 119, 120, 121, 122, 122, 123, 124, 124, 125, 126, 126, 127, 128, 129, 129, 130, 131, 132, 132, 133, 134, 134, 135, 136, 137, 137, 138, 139, 140, 140, 141, 142, 143, 144, 144, 145, 146, 147, 147, 148, 149, 150,\n\t\t\t151, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 164, 164, 165, 166, 167, 168, 169, 169, 170, 171, 172, 173, 174, 175, 176, 176, 177, 178, 179, 180, 181, 182, 183, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 193, 194,\n\t\t\t195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,\n\t\t\t247, 248, 249, 250, 251, 252, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 265, 266, 267, 268, 269, 270, 271, 272, 274, 275, 276, 277, 278, 279, 280, 282, 283, 284, 285, 286, 287, 289, 290, 291, 292, 293, 294, 296, 297, 298, 299, 300, 302, 303, 304, 305,\n\t\t\t306, 308, 309, 310, 311, 312, 314, 315, 316, 317, 319, 320, 321, 322, 324, 325, 326, 327, 329, 330, 331, 333, 334, 335, 336, 338, 339, 340, 342, 343, 344, 345, 347, 348, 349, 351, 352, 353, 355, 356, 357, 359, 360, 361, 363, 364, 365, 367, 368, 369, 371, 372,\n\t\t\t373, 375, 376, 378, 379, 380, 382, 383, 384, 386, 387, 389, 390, 391, 393, 394, 396, 397, 399, 400, 401, 403, 404, 406, 407, 409, 410, 411, 413, 414, 416, 417, 419, 420, 422, 423, 425, 426, 428, 429, 431, 432, 434, 435, 437, 438, 440, 441, 443, 444, 446, 447,\n\t\t\t449, 450, 452, 453, 455, 456, 458, 459, 461, 462, 464, 466, 467, 469, 470, 472, 473, 475, 477, 478, 480, 481, 483, 485, 486, 488, 489, 491, 493, 494, 496, 497, 499, 501, 502, 504, 506, 507, 509, 511, 512, 514, 515, 517, 519, 520, 522, 524, 525, 527, 529, 531,\n\t\t\t532, 534, 536, 537, 539, 541, 542, 544, 546, 548, 549, 551, 553, 554, 556, 558, 560, 561, 563, 565, 567, 568, 570, 572, 574, 575, 577, 579, 581, 583, 584, 586, 588, 590, 592, 593, 595, 597, 599, 601, 602, 604, 606, 608, 610, 612, 613, 615, 617, 619, 621, 623,\n\t\t\t625, 626, 628, 630, 632, 634, 636, 638, 639, 641, 643, 645, 647, 649, 651, 653, 655, 657, 659, 660, 662, 664, 666, 668, 670, 672, 674, 676, 678, 680, 682, 684, 686, 688, 690, 692, 694, 696, 698, 700, 702, 704, 706, 708, 710, 712, 714, 716, 718, 720, 722, 724,\n\t\t\t726, 728, 730, 732, 734, 736, 738, 740, 742, 744, 746, 748, 750, 753, 755, 757, 759, 761, 763, 765, 767, 769, 771, 773, 776, 778, 780, 782, 784, 786, 788, 791, 793, 795, 797, 799, 801, 803, 806, 808, 810, 812, 814, 816, 819, 821, 823, 825, 827, 830, 832, 834,\n\t\t\t836, 839, 841, 843, 845, 847, 850, 852, 854, 856, 859, 861, 863, 865, 868, 870, 872, 874, 877, 879, 881, 884, 886, 888, 890, 893, 895, 897, 900, 902, 904, 907, 909, 911, 914, 916, 918, 921, 923, 925, 928, 930, 932, 935, 937, 940, 942, 944, 947, 949, 952, 954,\n\t\t\t956, 959, 961, 964, 966, 968, 971, 973, 976, 978, 981, 983, 985, 988, 990, 993, 995, 998, 1000, 1003, 1005, 1008, 1010, 1013, 1015, 1018, 1020, 1023\n\t\t},\n\t\t.screengammatable = {\n\t\t\t0, 63, 84, 99, 111, 121, 130, 139, 146, 154, 160, 166, 172, 178, 183, 188, 193, 198, 203, 207, 212, 216, 220, 224, 228, 231, 235, 239, 242, 245, 249, 252, 255, 259, 262, 265, 268, 271, 274, 276, 279, 282, 285, 287, 290, 293, 295, 298, 300, 303, 305, 308,\n\t\t\t310, 313, 315, 317, 320, 322, 324, 326, 328, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 363, 365, 367, 369, 370, 372, 374, 376, 378, 379, 381, 383, 385, 386, 388, 390, 392, 393, 395, 397, 398, 400, 401, 403, 405, 406, 408,\n\t\t\t409, 411, 413, 414, 416, 417, 419, 420, 422, 423, 425, 426, 428, 429, 431, 432, 434, 435, 436, 438, 439, 441, 442, 444, 445, 446, 448, 449, 450, 452, 453, 455, 456, 457, 459, 460, 461, 463, 464, 465, 466, 468, 469, 470, 472, 473, 474, 475, 477, 478, 479, 480,\n\t\t\t482, 483, 484, 485, 487, 488, 489, 490, 491, 493, 494, 495, 496, 497, 499, 500, 501, 502, 503, 504, 505, 507, 508, 509, 510, 511, 512, 513, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539,\n\t\t\t540, 541, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 585, 586, 587, 588, 589, 590,\n\t\t\t591, 592, 593, 594, 595, 595, 596, 597, 598, 599, 600, 601, 602, 603, 603, 604, 605, 606, 607, 608, 609, 610, 610, 611, 612, 613, 614, 615, 616, 616, 617, 618, 619, 620, 621, 622, 622, 623, 624, 625, 626, 627, 627, 628, 629, 630, 631, 632, 632, 633, 634, 635,\n\t\t\t636, 637, 637, 638, 639, 640, 641, 641, 642, 643, 644, 645, 645, 646, 647, 648, 649, 649, 650, 651, 652, 652, 653, 654, 655, 656, 656, 657, 658, 659, 659, 660, 661, 662, 663, 663, 664, 665, 666, 666, 667, 668, 669, 669, 670, 671, 672, 672, 673, 674, 675, 675,\n\t\t\t676, 677, 678, 678, 679, 680, 681, 681, 682, 683, 684, 684, 685, 686, 686, 687, 688, 689, 689, 690, 691, 692, 692, 693, 694, 694, 695, 696, 697, 697, 698, 699, 699, 700, 701, 701, 702, 703, 704, 704, 705, 706, 706, 707, 708, 708, 709, 710, 711, 711, 712, 713,\n\t\t\t713, 714, 715, 715, 716, 717, 717, 718, 719, 719, 720, 721, 721, 722, 723, 723, 724, 725, 725, 726, 727, 727, 728, 729, 729, 730, 731, 731, 732, 733, 733, 734, 735, 735, 736, 737, 737, 738, 739, 739, 740, 741, 741, 742, 743, 743, 744, 745, 745, 746, 746, 747,\n\t\t\t748, 748, 749, 750, 750, 751, 752, 752, 753, 753, 754, 755, 755, 756, 757, 757, 758, 758, 759, 760, 760, 761, 762, 762, 763, 763, 764, 765, 765, 766, 767, 767, 768, 768, 769, 770, 770, 771, 771, 772, 773, 773, 774, 774, 775, 776, 776, 777, 778, 778, 779, 779,\n\t\t\t780, 781, 781, 782, 782, 783, 784, 784, 785, 785, 786, 786, 787, 788, 788, 789, 789, 790, 791, 791, 792, 792, 793, 794, 794, 795, 795, 796, 796, 797, 798, 798, 799, 799, 800, 801, 801, 802, 802, 803, 803, 804, 805, 805, 806, 806, 807, 807, 808, 809, 809, 810,\n\t\t\t810, 811, 811, 812, 813, 813, 814, 814, 815, 815, 816, 816, 817, 818, 818, 819, 819, 820, 820, 821, 821, 822, 823, 823, 824, 824, 825, 825, 826, 826, 827, 828, 828, 829, 829, 830, 830, 831, 831, 832, 832, 833, 834, 834, 835, 835, 836, 836, 837, 837, 838, 838,\n\t\t\t839, 839, 840, 841, 841, 842, 842, 843, 843, 844, 844, 845, 845, 846, 846, 847, 848, 848, 849, 849, 850, 850, 851, 851, 852, 852, 853, 853, 854, 854, 855, 855, 856, 856, 857, 857, 858, 859, 859, 860, 860, 861, 861, 862, 862, 863, 863, 864, 864, 865, 865, 866,\n\t\t\t866, 867, 867, 868, 868, 869, 869, 870, 870, 871, 871, 872, 872, 873, 873, 874, 874, 875, 875, 876, 876, 877, 877, 878, 878, 879, 879, 880, 880, 881, 881, 882, 882, 883, 883, 884, 884, 885, 885, 886, 886, 887, 887, 888, 888, 889, 889, 890, 890, 891, 891, 892,\n\t\t\t892, 893, 893, 894, 894, 895, 895, 896, 896, 897, 897, 898, 898, 899, 899, 900, 900, 901, 901, 902, 902, 903, 903, 904, 904, 904, 905, 905, 906, 906, 907, 907, 908, 908, 909, 909, 910, 910, 911, 911, 912, 912, 913, 913, 914, 914, 915, 915, 915, 916, 916, 917,\n\t\t\t917, 918, 918, 919, 919, 920, 920, 921, 921, 922, 922, 922, 923, 923, 924, 924, 925, 925, 926, 926, 927, 927, 928, 928, 929, 929, 929, 930, 930, 931, 931, 932, 932, 933, 933, 934, 934, 935, 935, 935, 936, 936, 937, 937, 938, 938, 939, 939, 940, 940, 940, 941,\n\t\t\t941, 942, 942, 943, 943, 944, 944, 944, 945, 945, 946, 946, 947, 947, 948, 948, 949, 949, 949, 950, 950, 951, 951, 952, 952, 953, 953, 953, 954, 954, 955, 955, 956, 956, 957, 957, 957, 958, 958, 959, 959, 960, 960, 961, 961, 961, 962, 962, 963, 963, 964, 964,\n\t\t\t964, 965, 965, 966, 966, 967, 967, 968, 968, 968, 969, 969, 970, 970, 971, 971, 971, 972, 972, 973, 973, 974, 974, 974, 975, 975, 976, 976, 977, 977, 977, 978, 978, 979, 979, 980, 980, 980, 981, 981, 982, 982, 983, 983, 983, 984, 984, 985, 985, 986, 986, 986,\n\t\t\t987, 987, 988, 988, 988, 989, 989, 990, 990, 991, 991, 991, 992, 992, 993, 993, 993, 994, 994, 995, 995, 996, 996, 996, 997, 997, 998, 998, 998, 999, 999, 1000, 1000, 1001, 1001, 1001, 1002, 1002, 1003, 1003, 1003, 1004, 1004, 1005, 1005, 1005, 1006, 1006,\n\t\t\t1007, 1007, 1008, 1008, 1008, 1009, 1009, 1010, 1010, 1010, 1011, 1011, 1012, 1012, 1012, 1013, 1013, 1014, 1014, 1014, 1015, 1015, 1016, 1016, 1016, 1017, 1017, 1018, 1018, 1018, 1019, 1019, 1020, 1020, 1020, 1021, 1021, 1022, 1022, 1023\n\t\t},\n\t}, {\n\t\t.gamma = 2.2,\n\t\t.brightness = 1.0,\n\t\t.texgamma = 2.2,\n\t\t.lightgamma = 2.4,\n\t\t.texgammatable = {\n\t\t\t0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,\n\t\t\t66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,\n\t\t\t124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,\n\t\t\t176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227,\n\t\t\t228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255\n\t\t},\n\t\t.lightgammatable = {\n\t\t\t0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 45, 46, 47, 48, 50, 51, 52, 53, 55, 56, 57, 58, 60, 61, 62, 63, 65, 66, 67, 69, 70, 71, 72, 74, 75, 76,\n\t\t\t78, 79, 80, 81, 83, 84, 85, 87, 88, 89, 91, 92, 93, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 110, 112, 113, 114, 116, 117, 118, 120, 121, 122, 124, 125, 126, 128, 129, 130, 132, 133, 134, 136, 137, 138, 140, 141, 142, 144, 145, 147, 148, 149,\n\t\t\t151, 152, 153, 155, 156, 157, 159, 160, 162, 163, 164, 166, 167, 168, 170, 171, 173, 174, 175, 177, 178, 179, 181, 182, 184, 185, 186, 188, 189, 191, 192, 193, 195, 196, 198, 199, 200, 202, 203, 204, 206, 207, 209, 210, 211, 213, 214, 216, 217, 219, 220, 221,\n\t\t\t223, 224, 226, 227, 228, 230, 231, 233, 234, 235, 237, 238, 240, 241, 243, 244, 245, 247, 248, 250, 251, 252, 254, 255, 257, 258, 260, 261, 262, 264, 265, 267, 268, 270, 271, 272, 274, 275, 277, 278, 280, 281, 282, 284, 285, 287, 288, 290, 291, 293, 294, 295,\n\t\t\t297, 298, 300, 301, 303, 304, 306, 307, 308, 310, 311, 313, 314, 316, 317, 319, 320, 321, 323, 324, 326, 327, 329, 330, 332, 333, 335, 336, 337, 339, 340, 342, 343, 345, 346, 348, 349, 351, 352, 354, 355, 356, 358, 359, 361, 362, 364, 365, 367, 368, 370, 371,\n\t\t\t373, 374, 376, 377, 378, 380, 381, 383, 384, 386, 387, 389, 390, 392, 393, 395, 396, 397, 398, 398, 399, 399, 400, 401, 401, 402, 402, 403, 403, 404, 404, 405, 406, 406, 407, 407, 408, 408, 409, 410, 410, 411, 411, 412, 413, 413, 414, 414, 415, 416, 416, 417,\n\t\t\t417, 418, 419, 419, 420, 420, 421, 422, 422, 423, 423, 424, 425, 425, 426, 427, 427, 428, 428, 429, 430, 430, 431, 432, 432, 433, 433, 434, 435, 435, 436, 437, 437, 438, 439, 439, 440, 441, 441, 442, 443, 443, 444, 445, 445, 446, 447, 447, 448, 449, 449, 450,\n\t\t\t451, 451, 452, 453, 453, 454, 455, 455, 456, 457, 457, 458, 459, 459, 460, 461, 461, 462, 463, 464, 464, 465, 466, 466, 467, 468, 468, 469, 470, 471, 471, 472, 473, 473, 474, 475, 476, 476, 477, 478, 478, 479, 480, 481, 481, 482, 483, 483, 484, 485, 486, 486,\n\t\t\t487, 488, 489, 489, 490, 491, 492, 492, 493, 494, 495, 495, 496, 497, 498, 498, 499, 500, 501, 501, 502, 503, 504, 504, 505, 506, 507, 507, 508, 509, 510, 510, 511, 512, 513, 513, 514, 515, 516, 517, 517, 518, 519, 520, 520, 521, 522, 523, 524, 524, 525, 526,\n\t\t\t527, 527, 528, 529, 530, 531, 531, 532, 533, 534, 535, 535, 536, 537, 538, 538, 539, 540, 541, 542, 542, 543, 544, 545, 546, 546, 547, 548, 549, 550, 550, 551, 552, 553, 554, 555, 555, 556, 557, 558, 559, 559, 560, 561, 562, 563, 564, 564, 565, 566, 567, 568,\n\t\t\t568, 569, 570, 571, 572, 573, 573, 574, 575, 576, 577, 578, 578, 579, 580, 581, 582, 583, 583, 584, 585, 586, 587, 588, 588, 589, 590, 591, 592, 593, 593, 594, 595, 596, 597, 598, 599, 599, 600, 601, 602, 603, 604, 605, 605, 606, 607, 608, 609, 610, 611, 611,\n\t\t\t612, 613, 614, 615, 616, 617, 617, 618, 619, 620, 621, 622, 623, 623, 624, 625, 626, 627, 628, 629, 629, 630, 631, 632, 633, 634, 635, 636, 636, 637, 638, 639, 640, 641, 642, 643, 643, 644, 645, 646, 647, 648, 649, 650, 651, 651, 652, 653, 654, 655, 656, 657,\n\t\t\t658, 658, 659, 660, 661, 662, 663, 664, 665, 666, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 703,\n\t\t\t704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 749, 750, 751,\n\t\t\t752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 795, 796, 797, 798, 799, 800,\n\t\t\t801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850,\n\t\t\t851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 900,\n\t\t\t901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952,\n\t\t\t953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002,\n\t\t\t1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1023\n\t\t},\n\t\t.lineargammatable = {\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4,\n\t\t\t4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,\n\t\t\t17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36,\n\t\t\t36, 36, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 42, 42, 42, 43, 43, 44, 44, 44, 45, 45, 46, 46, 46, 47, 47, 48, 48, 48, 49, 49, 50, 50, 51, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 61, 62, 62, 63,\n\t\t\t63, 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79, 80, 80, 81, 82, 82, 83, 83, 84, 84, 85, 86, 86, 87, 87, 88, 88, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 96, 96, 97, 97, 98, 99,\n\t\t\t99, 100, 100, 101, 102, 102, 103, 104, 104, 105, 105, 106, 107, 107, 108, 109, 109, 110, 111, 111, 112, 113, 113, 114, 115, 115, 116, 117, 117, 118, 119, 119, 120, 121, 121, 122, 123, 123, 124, 125, 126, 126, 127, 128, 128, 129, 130, 131, 131, 132, 133, 133,\n\t\t\t134, 135, 136, 136, 137, 138, 139, 139, 140, 141, 142, 142, 143, 144, 145, 145, 146, 147, 148, 148, 149, 150, 151, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, 169, 170, 171, 172, 172, 173, 174,\n\t\t\t175, 176, 177, 177, 178, 179, 180, 181, 182, 183, 183, 184, 185, 186, 187, 188, 189, 190, 190, 191, 192, 193, 194, 195, 196, 197, 198, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221,\n\t\t\t222, 223, 224, 225, 226, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273,\n\t\t\t274, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 288, 289, 290, 291, 292, 293, 294, 295, 296, 298, 299, 300, 301, 302, 303, 304, 305, 307, 308, 309, 310, 311, 312, 313, 315, 316, 317, 318, 319, 320, 322, 323, 324, 325, 326, 328, 329, 330, 331, 332,\n\t\t\t333, 335, 336, 337, 338, 339, 341, 342, 343, 344, 346, 347, 348, 349, 350, 352, 353, 354, 355, 357, 358, 359, 360, 362, 363, 364, 365, 367, 368, 369, 370, 372, 373, 374, 375, 377, 378, 379, 381, 382, 383, 384, 386, 387, 388, 390, 391, 392, 393, 395, 396, 397,\n\t\t\t399, 400, 401, 403, 404, 405, 407, 408, 409, 411, 412, 413, 415, 416, 417, 419, 420, 421, 423, 424, 426, 427, 428, 430, 431, 432, 434, 435, 437, 438, 439, 441, 442, 443, 445, 446, 448, 449, 450, 452, 453, 455, 456, 458, 459, 460, 462, 463, 465, 466, 468, 469,\n\t\t\t470, 472, 473, 475, 476, 478, 479, 481, 482, 483, 485, 486, 488, 489, 491, 492, 494, 495, 497, 498, 500, 501, 503, 504, 506, 507, 509, 510, 512, 513, 515, 516, 518, 519, 521, 522, 524, 525, 527, 528, 530, 532, 533, 535, 536, 538, 539, 541, 542, 544, 545, 547,\n\t\t\t549, 550, 552, 553, 555, 556, 558, 560, 561, 563, 564, 566, 568, 569, 571, 572, 574, 576, 577, 579, 580, 582, 584, 585, 587, 589, 590, 592, 593, 595, 597, 598, 600, 602, 603, 605, 607, 608, 610, 612, 613, 615, 617, 618, 620, 622, 623, 625, 627, 628, 630, 632,\n\t\t\t633, 635, 637, 639, 640, 642, 644, 645, 647, 649, 650, 652, 654, 656, 657, 659, 661, 663, 664, 666, 668, 670, 671, 673, 675, 677, 678, 680, 682, 684, 685, 687, 689, 691, 692, 694, 696, 698, 700, 701, 703, 705, 707, 709, 710, 712, 714, 716, 718, 719, 721, 723,\n\t\t\t725, 727, 729, 730, 732, 734, 736, 738, 740, 741, 743, 745, 747, 749, 751, 753, 754, 756, 758, 760, 762, 764, 766, 767, 769, 771, 773, 775, 777, 779, 781, 783, 785, 786, 788, 790, 792, 794, 796, 798, 800, 802, 804, 806, 808, 809, 811, 813, 815, 817, 819, 821,\n\t\t\t823, 825, 827, 829, 831, 833, 835, 837, 839, 841, 843, 845, 847, 849, 851, 853, 855, 857, 859, 861, 863, 865, 867, 869, 871, 873, 875, 877, 879, 881, 883, 885, 887, 889, 891, 893, 895, 897, 899, 901, 903, 905, 907, 910, 912, 914, 916, 918, 920, 922, 924, 926,\n\t\t\t928, 930, 932, 934, 937, 939, 941, 943, 945, 947, 949, 951, 953, 956, 958, 960, 962, 964, 966, 968, 970, 973, 975, 977, 979, 981, 983, 985, 988, 990, 992, 994, 996, 998, 1001, 1003, 1005, 1007, 1009, 1012, 1014, 1016, 1018, 1020, 1023\n\t\t},\n\t\t.screengammatable = {\n\t\t\t0, 43, 60, 72, 82, 91, 98, 106, 112, 118, 124, 130, 135, 140, 145, 150, 154, 158, 163, 167, 171, 174, 178, 182, 185, 189, 192, 196, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 228, 231, 234, 237, 239, 242, 244, 247, 249, 252, 254, 257, 259, 261,\n\t\t\t264, 266, 268, 270, 273, 275, 277, 279, 281, 283, 286, 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 311, 313, 315, 317, 319, 321, 323, 324, 326, 328, 330, 331, 333, 335, 337, 338, 340, 342, 343, 345, 347, 348, 350, 352, 353, 355, 357, 358, 360,\n\t\t\t361, 363, 365, 366, 368, 369, 371, 372, 374, 375, 377, 378, 380, 381, 383, 384, 386, 387, 389, 390, 392, 393, 394, 396, 397, 399, 400, 401, 403, 404, 406, 407, 408, 410, 411, 412, 414, 415, 416, 418, 419, 420, 422, 423, 424, 426, 427, 428, 430, 431, 432, 433,\n\t\t\t435, 436, 437, 438, 440, 441, 442, 443, 445, 446, 447, 448, 450, 451, 452, 453, 454, 456, 457, 458, 459, 460, 462, 463, 464, 465, 466, 467, 469, 470, 471, 472, 473, 474, 475, 477, 478, 479, 480, 481, 482, 483, 484, 486, 487, 488, 489, 490, 491, 492, 493, 494,\n\t\t\t495, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 545, 546, 547,\n\t\t\t548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 583, 584, 585, 586, 587, 588, 589, 590, 591, 591, 592, 593, 594, 595,\n\t\t\t596, 597, 598, 598, 599, 600, 601, 602, 603, 604, 604, 605, 606, 607, 608, 609, 609, 610, 611, 612, 613, 614, 615, 615, 616, 617, 618, 619, 620, 620, 621, 622, 623, 624, 624, 625, 626, 627, 628, 629, 629, 630, 631, 632, 633, 633, 634, 635, 636, 637, 637, 638,\n\t\t\t639, 640, 641, 641, 642, 643, 644, 645, 645, 646, 647, 648, 649, 649, 650, 651, 652, 652, 653, 654, 655, 656, 656, 657, 658, 659, 659, 660, 661, 662, 663, 663, 664, 665, 666, 666, 667, 668, 669, 669, 670, 671, 672, 672, 673, 674, 675, 675, 676, 677, 678, 678,\n\t\t\t679, 680, 681, 681, 682, 683, 684, 684, 685, 686, 686, 687, 688, 689, 689, 690, 691, 692, 692, 693, 694, 694, 695, 696, 697, 697, 698, 699, 700, 700, 701, 702, 702, 703, 704, 705, 705, 706, 707, 707, 708, 709, 709, 710, 711, 712, 712, 713, 714, 714, 715, 716,\n\t\t\t716, 717, 718, 719, 719, 720, 721, 721, 722, 723, 723, 724, 725, 725, 726, 727, 728, 728, 729, 730, 730, 731, 732, 732, 733, 734, 734, 735, 736, 736, 737, 738, 738, 739, 740, 740, 741, 742, 742, 743, 744, 744, 745, 746, 746, 747, 748, 748, 749, 750, 750, 751,\n\t\t\t752, 752, 753, 754, 754, 755, 756, 756, 757, 758, 758, 759, 759, 760, 761, 761, 762, 763, 763, 764, 765, 765, 766, 767, 767, 768, 769, 769, 770, 770, 771, 772, 772, 773, 774, 774, 775, 776, 776, 777, 777, 778, 779, 779, 780, 781, 781, 782, 782, 783, 784, 784,\n\t\t\t785, 786, 786, 787, 787, 788, 789, 789, 790, 791, 791, 792, 792, 793, 794, 794, 795, 795, 796, 797, 797, 798, 799, 799, 800, 800, 801, 802, 802, 803, 803, 804, 805, 805, 806, 806, 807, 808, 808, 809, 809, 810, 811, 811, 812, 812, 813, 814, 814, 815, 815, 816,\n\t\t\t817, 817, 818, 818, 819, 820, 820, 821, 821, 822, 823, 823, 824, 824, 825, 825, 826, 827, 827, 828, 828, 829, 830, 830, 831, 831, 832, 833, 833, 834, 834, 835, 835, 836, 837, 837, 838, 838, 839, 839, 840, 841, 841, 842, 842, 843, 843, 844, 845, 845, 846, 846,\n\t\t\t847, 847, 848, 849, 849, 850, 850, 851, 851, 852, 853, 853, 854, 854, 855, 855, 856, 857, 857, 858, 858, 859, 859, 860, 860, 861, 862, 862, 863, 863, 864, 864, 865, 865, 866, 867, 867, 868, 868, 869, 869, 870, 870, 871, 872, 872, 873, 873, 874, 874, 875, 875,\n\t\t\t876, 876, 877, 878, 878, 879, 879, 880, 880, 881, 881, 882, 882, 883, 884, 884, 885, 885, 886, 886, 887, 887, 888, 888, 889, 889, 890, 891, 891, 892, 892, 893, 893, 894, 894, 895, 895, 896, 896, 897, 898, 898, 899, 899, 900, 900, 901, 901, 902, 902, 903, 903,\n\t\t\t904, 904, 905, 905, 906, 906, 907, 908, 908, 909, 909, 910, 910, 911, 911, 912, 912, 913, 913, 914, 914, 915, 915, 916, 916, 917, 917, 918, 918, 919, 920, 920, 921, 921, 922, 922, 923, 923, 924, 924, 925, 925, 926, 926, 927, 927, 928, 928, 929, 929, 930, 930,\n\t\t\t931, 931, 932, 932, 933, 933, 934, 934, 935, 935, 936, 936, 937, 937, 938, 938, 939, 939, 940, 940, 941, 941, 942, 942, 943, 943, 944, 944, 945, 945, 946, 946, 947, 947, 948, 948, 949, 949, 950, 950, 951, 951, 952, 952, 953, 953, 954, 954, 955, 955, 956, 956,\n\t\t\t957, 957, 958, 958, 959, 959, 960, 960, 961, 961, 962, 962, 963, 963, 964, 964, 965, 965, 966, 966, 967, 967, 968, 968, 969, 969, 969, 970, 970, 971, 971, 972, 972, 973, 973, 974, 974, 975, 975, 976, 976, 977, 977, 978, 978, 979, 979, 980, 980, 981, 981, 982,\n\t\t\t982, 982, 983, 983, 984, 984, 985, 985, 986, 986, 987, 987, 988, 988, 989, 989, 990, 990, 991, 991, 991, 992, 992, 993, 993, 994, 994, 995, 995, 996, 996, 997, 997, 998, 998, 999, 999, 999, 1000, 1000, 1001, 1001, 1002, 1002, 1003, 1003, 1004, 1004, 1005,\n\t\t\t1005, 1006, 1006, 1006, 1007, 1007, 1008, 1008, 1009, 1009, 1010, 1010, 1011, 1011, 1012, 1012, 1012, 1013, 1013, 1014, 1014, 1015, 1015, 1016, 1016, 1017, 1017, 1017, 1018, 1018, 1019, 1019, 1020, 1020, 1021, 1021, 1022, 1022, 1023\n\t\t},\n\t}\n\t};\n\n\tif( i < 0 || i >= ARRAYSIZE( precomputed_data ))\n\t\treturn NULL;\n\n\treturn &precomputed_data[i];\n}\n#endif\n"
  },
  {
    "path": "engine/client/identification.c",
    "content": "/*\nidentification.c - unique id generation\nCopyright (C) 2017 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <inttypes.h>\n#include \"common.h\"\n#include <fcntl.h>\n#if !XASH_WIN32\n#include <dirent.h>\n#endif\n\nstatic char id_md5[33];\n\n/*\n==========================================================\n\nsimple 64-bit one-hash-func bloom filter\nshould be enough to determine if device exist in identifier\n\n==========================================================\n*/\ntypedef uint64_t bloomfilter_t;\n\nstatic bloomfilter_t id;\n\n#define bf64_mask ((1U<<6)-1)\n\nstatic bloomfilter_t BloomFilter_Process( const char *buffer, int size )\n{\n\tdword crc32;\n\tbloomfilter_t value = 0;\n\n\tif( size <= 0 || size > 512 )\n\t\treturn 0;\n\n\tCRC32_Init( &crc32 );\n\tCRC32_ProcessBuffer( &crc32, buffer, size );\n\n\twhile( crc32 )\n\t{\n\t\tvalue |= ((uint64_t)1) << ( crc32 & bf64_mask );\n\t\tcrc32 = crc32 >> 6;\n\t}\n\n\treturn value;\n}\n\nstatic bloomfilter_t BloomFilter_ProcessStr( const char *buffer )\n{\n\treturn BloomFilter_Process( buffer, Q_strlen( buffer ) );\n}\n\nstatic uint BloomFilter_Weight( bloomfilter_t value )\n{\n\tint weight = 0;\n\n\twhile( value )\n\t{\n\t\tif( value & 1 )\n\t\t\tweight++;\n\t\tvalue = value >> 1;\n#if _MSC_VER == 1200\n\t\tvalue &= 0x7FFFFFFFFFFFFFFF;\n#endif\n\t}\n\n\treturn weight;\n}\n\nstatic qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str )\n{\n\tbloomfilter_t value = BloomFilter_ProcessStr( str );\n\n\treturn (filter & value) == value;\n}\n\n/*\n=============================================\n\nIDENTIFICATION\n\n=============================================\n*/\n#define MAXBITS_GEN 30\n#define MAXBITS_CHECK MAXBITS_GEN + 6\n\nstatic qboolean ID_ProcessFile( bloomfilter_t *value, const char *path );\n\nstatic void ID_BloomFilter_f( void )\n{\n\tbloomfilter_t value = 0;\n\tint i;\n\n\tfor( i = 1; i < Cmd_Argc(); i++ )\n\t\tvalue |= BloomFilter_ProcessStr( Cmd_Argv( i ) );\n\n\tMsg( \"%d %016\"PRIX64\"\\n\", BloomFilter_Weight( value ), value );\n\n\t// test\n\t// for( i = 1; i < Cmd_Argc(); i++ )\n\t//\tMsg( \"%s: %d\\n\", Cmd_Argv( i ), BloomFilter_ContainsString( value, Cmd_Argv( i ) ) );\n}\n\nstatic qboolean ID_VerifyHEX( const char *hex )\n{\n\tuint chars = 0;\n\tchar prev = 0;\n\tqboolean monotonic = true; // detect 11:22...\n\tint weight = 0;\n\n\twhile( *hex++ )\n\t{\n\t\tchar ch = Q_tolower( *hex );\n\n\t\tif( ( ch >= 'a' && ch <= 'f') || ( ch >= '0' && ch <= '9' ) )\n\t\t{\n\t\t\tif( prev && ( ch - prev < -1 || ch - prev > 1 ) )\n\t\t\t\tmonotonic = false;\n\n\t\t\tif( ch >= 'a' )\n\t\t\t\tchars |= 1 << (ch - 'a' + 10);\n\t\t\telse\n\t\t\t\tchars |= 1 << (ch - '0');\n\n\t\t\tprev = ch;\n\t\t}\n\t}\n\n\tif( monotonic )\n\t\treturn false;\n\n\twhile( chars )\n\t{\n\t\tif( chars & 1 )\n\t\t\tweight++;\n\n\t\tchars = chars >> 1;\n\n\t\tif( weight > 2 )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void ID_VerifyHEX_f( void )\n{\n\tif( ID_VerifyHEX( Cmd_Argv( 1 ) ) )\n\t\tMsg( \"Good\\n\" );\n\telse\n\t\tMsg( \"Bad\\n\" );\n}\n\n#if XASH_LINUX\nstatic qboolean ID_ProcessCPUInfo( bloomfilter_t *value )\n{\n\tint cpuinfofd = open( \"/proc/cpuinfo\", O_RDONLY );\n\tchar buffer[1024], *pbuf, *pbuf2;\n\tint ret;\n\n\tif( cpuinfofd < 0 )\n\t\treturn false;\n\n\tif( (ret = read( cpuinfofd, buffer, 1023 ) ) < 0 )\n\t\treturn false;\n\n\tclose( cpuinfofd );\n\n\tbuffer[ret] = 0;\n\n\tif( !ret )\n\t\treturn false;\n\n\tpbuf = Q_strstr( buffer, \"Serial\" );\n\tif( !pbuf )\n\t\treturn false;\n\tpbuf += 6;\n\n\tif( ( pbuf2 = Q_strchr( pbuf, '\\n' ) ) )\n\t\t*pbuf2 = 0;\n\telse\n\t\tpbuf2 = pbuf + Q_strlen( pbuf );\n\n\tif( !ID_VerifyHEX( pbuf ) )\n\t\treturn false;\n\n\t*value |= BloomFilter_Process( pbuf, pbuf2 - pbuf );\n\treturn true;\n}\n\nstatic qboolean ID_ValidateNetDevice( const char *dev )\n{\n\tconst char *prefix = \"/sys/class/net\";\n\tbyte *pfile;\n\tint assignType;\n\n\t// These devices are fake, their mac address is generated each boot, while assign_type is 0\n\tif( !Q_strnicmp( dev, \"ccmni\", sizeof( \"ccmni\" ) ) ||\n\t\t!Q_strnicmp( dev, \"ifb\", sizeof( \"ifb\" ) ) )\n\t\treturn false;\n\n\tpfile = FS_LoadDirectFile( va( \"%s/%s/addr_assign_type\", prefix, dev ), NULL );\n\n\t// if NULL, it may be old kernel\n\tif( pfile )\n\t{\n\t\tassignType = Q_atoi( (char*)pfile );\n\n\t\tMem_Free( pfile );\n\n\t\t// check is MAC address is constant\n\t\tif( assignType != 0 )\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic int ID_ProcessNetDevices( bloomfilter_t *value )\n{\n\tconst char *prefix = \"/sys/class/net\";\n\tDIR *dir;\n\tstruct dirent *entry;\n\tint count = 0;\n\n\tif( !( dir = opendir( prefix ) ) )\n\t\treturn 0;\n\n\twhile( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )\n\t{\n\t\tif( !Q_strcmp( entry->d_name, \".\" ) || !Q_strcmp( entry->d_name, \"..\" ) )\n\t\t\tcontinue;\n\n\t\tif( !ID_ValidateNetDevice( entry->d_name ) )\n\t\t\tcontinue;\n\n\t\tcount += ID_ProcessFile( value, va( \"%s/%s/address\", prefix, entry->d_name ) );\n\t}\n\tclosedir( dir );\n\treturn count;\n}\n\nstatic int ID_CheckNetDevices( bloomfilter_t value )\n{\n\tconst char *prefix = \"/sys/class/net\";\n\n\tDIR *dir;\n\tstruct dirent *entry;\n\tint count = 0;\n\tbloomfilter_t filter = 0;\n\n\tif( !( dir = opendir( prefix ) ) )\n\t\treturn 0;\n\n\twhile( ( entry = readdir( dir ) ) )\n\t{\n\t\tif( !Q_strcmp( entry->d_name, \".\" ) || !Q_strcmp( entry->d_name, \"..\" ) )\n\t\t\tcontinue;\n\n\t\tif( !ID_ValidateNetDevice( entry->d_name ) )\n\t\t\tcontinue;\n\n\t\tif( ID_ProcessFile( &filter, va( \"%s/%s/address\", prefix, entry->d_name ) ) )\n\t\t\tcount += ( value & filter ) == filter, filter = 0;\n\t}\n\n\tclosedir( dir );\n\treturn count;\n}\n\nstatic void ID_TestCPUInfo_f( void )\n{\n\tbloomfilter_t value = 0;\n\n\tif( ID_ProcessCPUInfo( &value ) )\n\t\tMsg( \"Got %016\"PRIX64\"\\n\", value );\n\telse\n\t\tMsg( \"Could not get serial\\n\" );\n}\n\n#endif\n\nstatic qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )\n{\n\tint fd = open( path, O_RDONLY );\n\tchar buffer[256];\n\tint ret;\n\n\tif( fd < 0 )\n\t\treturn false;\n\n\tif( (ret = read( fd, buffer, 255 ) ) < 0 )\n\t\treturn false;\n\n\tclose( fd );\n\n\tif( !ret )\n\t\treturn false;\n\n\tbuffer[ret] = 0;\n\n\tif( !ID_VerifyHEX( buffer ) )\n\t\treturn false;\n\n\t*value |= BloomFilter_Process( buffer, ret );\n\treturn true;\n}\n\n#if !XASH_WIN32\nstatic int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix )\n{\n\tDIR *dir;\n\tstruct dirent *entry;\n\tint count = 0;\n\n\tif( !( dir = opendir( prefix ) ) )\n\t    return 0;\n\n\twhile( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )\n\t{\n\t\tif( !Q_strcmp( entry->d_name, \".\" ) || !Q_strcmp( entry->d_name, \"..\" ) )\n\t\t\tcontinue;\n\n\t\tcount += ID_ProcessFile( value, va( \"%s/%s/%s\", prefix, entry->d_name, postfix ) );\n\t}\n\tclosedir( dir );\n\treturn count;\n}\n\nstatic int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix )\n{\n\tDIR *dir;\n\tstruct dirent *entry;\n\tint count = 0;\n\tbloomfilter_t filter = 0;\n\n\tif( !( dir = opendir( prefix ) ) )\n\t    return 0;\n\n\twhile( ( entry = readdir( dir ) ) )\n\t{\n\t\tif( !Q_strcmp( entry->d_name, \".\" ) || !Q_strcmp( entry->d_name, \"..\" ) )\n\t\t\tcontinue;\n\n\t\tif( ID_ProcessFile( &filter, va( \"%s/%s/%s\", prefix, entry->d_name, postfix ) ) )\n\t\t\tcount += ( value & filter ) == filter, filter = 0;\n\t}\n\n\tclosedir( dir );\n\treturn count;\n}\n#else\nstatic int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData )\n{\n\tHKEY hKey;\n\n\tif( RegOpenKeyExA( hRootKey, subKey, 0, KEY_QUERY_VALUE, &hKey ) != ERROR_SUCCESS )\n\t\treturn 0;\n\n\tif( RegQueryValueExA( hKey, value, NULL, NULL, data, &cbData ) != ERROR_SUCCESS )\n\t{\n\t\tRegCloseKey( hKey );\n\t\treturn 0;\n\t}\n\n\tRegCloseKey( hKey );\n\treturn 1;\n}\n\nstatic int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData )\n{\n\tHKEY hKey;\n\tif( RegCreateKeyA( hRootKey, subKey, &hKey ) != ERROR_SUCCESS )\n\t\treturn 0;\n\n\tif( RegSetValueExA( hKey, value, 0, dwType, data, cbData ) != ERROR_SUCCESS )\n\t{\n\t\tRegCloseKey( hKey );\n\t\treturn 0;\n\t}\n\n\tRegCloseKey( hKey );\n\treturn 1;\n}\n\n#define BUFSIZE 4096\n\nstatic int ID_RunWMIC( char *buffer, const wchar_t *cmdline )\n{\n\tHANDLE g_IN_Rd = NULL;\n\tHANDLE g_IN_Wr = NULL;\n\tHANDLE g_OUT_Rd = NULL;\n\tHANDLE g_OUT_Wr = NULL;\n\tDWORD dwRead;\n\tBOOL bSuccess = FALSE;\n\twchar_t *cmdline_copy;\n\n\tPROCESS_INFORMATION pi = { 0 };\n\tSECURITY_ATTRIBUTES saAttr =\n\t{\n\t\t.nLength = sizeof( SECURITY_ATTRIBUTES ),\n\t\t.bInheritHandle = TRUE,\n\t\t.lpSecurityDescriptor = NULL,\n\t};\n\n\tCreatePipe( &g_IN_Rd, &g_IN_Wr, &saAttr, 0 );\n\tCreatePipe( &g_OUT_Rd, &g_OUT_Wr, &saAttr, 0 );\n\tSetHandleInformation( g_IN_Wr, HANDLE_FLAG_INHERIT, 0 );\n\n\tSTARTUPINFO si =\n\t{\n\t\t.cb = sizeof( STARTUPINFO ),\n\t\t.dwFlags = STARTF_USESTDHANDLES,\n\t\t.hStdInput = g_IN_Rd,\n\t\t.hStdOutput = g_OUT_Wr,\n\t\t.hStdError = g_OUT_Wr,\n\t\t.wShowWindow = SW_HIDE,\n\t\t.dwFlags = STARTF_USESTDHANDLES,\n\t};\n\n\tcmdline_copy = malloc( wcslen( cmdline ) * sizeof( *cmdline_copy ));\n\n\tif( !CreateProcessW( NULL, cmdline_copy, NULL, NULL, true, CREATE_NO_WINDOW, NULL, NULL, &si, &pi ))\n\t\tgoto err;\n\n\tWaitForSingleObject( pi.hProcess, 500 );\n\n\tbSuccess = ReadFile( g_OUT_Rd, buffer, BUFSIZE, &dwRead, NULL );\n\tbuffer[BUFSIZE-1] = 0;\n\n\tTerminateProcess( pi.hProcess, 0 );\n\n\tCloseHandle( pi.hProcess );\n\tCloseHandle( pi.hThread );\n\nerr:\n\tCloseHandle( g_IN_Wr );\n\tCloseHandle( g_OUT_Wr );\n\tCloseHandle( g_IN_Rd );\n\tCloseHandle( g_OUT_Rd );\n\n\tfree( cmdline_copy );\n\n\treturn bSuccess;\n}\n\nstatic int ID_ProcessWMIC( bloomfilter_t *value, const wchar_t *cmdline )\n{\n\tchar buffer[BUFSIZE], token[BUFSIZE], *pbuf;\n\tint count = 0;\n\n\tif( !ID_RunWMIC( buffer, cmdline ))\n\t\treturn 0;\n\n\tpbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header\n\twhile( pbuf = COM_ParseFile( pbuf, token, sizeof( token )))\n\t{\n\t\tif( !ID_VerifyHEX( token ))\n\t\t\tcontinue;\n\n\t\t*value |= BloomFilter_ProcessStr( token );\n\t\tcount++;\n\t}\n\n\treturn count;\n}\n\nstatic int ID_CheckWMIC( bloomfilter_t value, const wchar_t *cmdline )\n{\n\tchar buffer[BUFSIZE], token[BUFSIZE], *pbuf;\n\tint count = 0;\n\n\tif( !ID_RunWMIC( buffer, cmdline ))\n\t\treturn 0;\n\n\tpbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header\n\twhile( pbuf = COM_ParseFile( pbuf, token, sizeof( token )))\n\t{\n\t\tbloomfilter_t filter;\n\n\t\tif( !ID_VerifyHEX( token ))\n\t\t\tcontinue;\n\n\t\tfilter = BloomFilter_ProcessStr( token );\n\t\tcount += ( filter & value ) == filter;\n\t}\n\n\treturn count;\n}\n#endif\n\n\n#if XASH_IOS\nchar *IOS_GetUDID( void );\n#endif\n\n#if XASH_PSVITA\nint PSVita_GetPSID( char *buf, const size_t buflen );\n#endif\n\nstatic bloomfilter_t ID_GenerateRawId( void )\n{\n\tbloomfilter_t value = 0;\n\tint count = 0;\n\n#if XASH_LINUX\n#if XASH_ANDROID && !XASH_DEDICATED\n\t{\n\t\tconst char *androidid = Android_GetAndroidID();\n\t\tif( androidid && ID_VerifyHEX( androidid ) )\n\t\t{\n\t\t\tvalue |= BloomFilter_ProcessStr( androidid );\n\t\t\tcount ++;\n\t\t}\n\t}\n#endif\n\tcount += ID_ProcessCPUInfo( &value );\n\tcount += ID_ProcessFiles( &value, \"/sys/block\", \"device/cid\" );\n\tcount += ID_ProcessNetDevices( &value );\n#endif\n#if XASH_WIN32\n\tcount += ID_ProcessWMIC( &value, L\"wmic path win32_physicalmedia get SerialNumber \" );\n\tcount += ID_ProcessWMIC( &value, L\"wmic bios get serialnumber \" );\n#endif\n#if XASH_IOS\n\t{\n\t\tvalue |= BloomFilter_ProcessStr(IOS_GetUDID());\n\t\tcount ++;\n\t}\n#endif\n#if XASH_PSVITA\n\t{\n\t\tchar data[16];\n\t\tPSVita_GetPSID( data, sizeof( data ));\n\t\tvalue |= BloomFilter_Process( data, sizeof( data ));\n\t\tcount ++;\n\t}\n#endif\n\treturn value;\n}\n\nstatic uint ID_CheckRawId( bloomfilter_t filter )\n{\n\tbloomfilter_t value = 0;\n\tint count = 0;\n\n#if XASH_LINUX\n#if XASH_ANDROID && !XASH_DEDICATED\n\t{\n\t\tconst char *androidid = Android_GetAndroidID();\n\t\tif( androidid && ID_VerifyHEX( androidid ) )\n\t\t{\n\t\t\tvalue = BloomFilter_ProcessStr( androidid );\n\t\t\tcount += (filter & value) == value;\n\t\t\tvalue = 0;\n\t\t}\n\t}\n#endif\n\tcount += ID_CheckNetDevices( filter );\n\tcount += ID_CheckFiles( filter, \"/sys/block\", \"device/cid\" );\n\tif( ID_ProcessCPUInfo( &value ) )\n\t\tcount += (filter & value) == value;\n#endif\n\n#if XASH_WIN32\n\tcount += ID_CheckWMIC( filter, L\"wmic path win32_physicalmedia get SerialNumber\" );\n\tcount += ID_CheckWMIC( filter, L\"wmic bios get serialnumber\" );\n#endif\n\n#if XASH_IOS\n\t{\n\t\tvalue = BloomFilter_ProcessStr(IOS_GetUDID());\n\t\tcount += (filter & value) == value;\n\t\tvalue = 0;\n\t}\n#endif\n#if XASH_PSVITA\n\t{\n\t\tchar data[16];\n\t\tPSVita_GetPSID( data, sizeof( data ));\n\t\tvalue = BloomFilter_Process( data, sizeof( data ));\n\t\tcount += (filter & value) == value;\n\t\tvalue = 0;\n\t}\n#endif\n#if 0\n\tMsg( \"%s: %d\\n\", __func__, count );\n#endif\n\treturn count;\n}\n\n#define SYSTEM_XOR_MASK 0x10331c2dce4c91db\n#define GAME_XOR_MASK 0x7ffc48fbac1711f1\n\nstatic void ID_Check( void )\n{\n\tuint weight = BloomFilter_Weight( id );\n\tuint mincount = weight >> 2;\n\n\tif( mincount < 1 )\n\t\tmincount = 1;\n\n\tif( weight > MAXBITS_CHECK )\n\t{\n\t\tid = 0;\n#if 0\n\t\tMsg( \"%s: fail %d\\n\", __func__, weight );\n#endif\n\t\treturn;\n\t}\n\n\tif( ID_CheckRawId( id ) < mincount )\n\t\tid = 0;\n#if 0\n\tMsg( \"%s: success %d\\n\", __func__, weight );\n#endif\n}\n\nconst char *ID_GetMD5( void )\n{\n\treturn id_md5;\n}\n\nvoid ID_Init( void )\n{\n\tMD5Context_t hash = { 0 };\n\tbyte md5[16];\n\tint i;\n\n\tCmd_AddRestrictedCommand( \"bloomfilter\", ID_BloomFilter_f, \"print bloomfilter raw value of arguments set\");\n\tCmd_AddRestrictedCommand( \"verifyhex\", ID_VerifyHEX_f, \"check if id source seems to be fake\" );\n#if XASH_LINUX\n\tCmd_AddRestrictedCommand( \"testcpuinfo\", ID_TestCPUInfo_f, \"try read cpu serial\" );\n#endif\n\n#if XASH_ANDROID && !XASH_DEDICATED\n\tsscanf( Android_LoadID(), \"%016\"PRIX64, &id );\n\tif( id )\n\t{\n\t\tid ^= SYSTEM_XOR_MASK;\n\t\tID_Check();\n\t}\n\n#elif XASH_WIN32\n\t{\n\t\tCHAR szBuf[MAX_PATH];\n\t\tID_GetKeyData( HKEY_CURRENT_USER, \"Software\\\\\"XASH_ENGINE_NAME\"\\\\\", \"xash_id\", szBuf, MAX_PATH );\n\n\t\tsscanf(szBuf, \"%016\"PRIX64, &id);\n\t\tid ^= SYSTEM_XOR_MASK;\n\t\tID_Check();\n\t}\n#else\n\t{\n\t\tconst char *home = getenv( \"HOME\" );\n\t\tif( COM_CheckString( home ) )\n\t\t{\n\t\t\tFILE *cfg = fopen( va( \"%s/.config/.xash_id\", home ), \"r\" );\n\t\t\tif( !cfg )\n\t\t\t\tcfg = fopen( va( \"%s/.local/.xash_id\", home ), \"r\" );\n\t\t\tif( !cfg )\n\t\t\t\tcfg = fopen( va( \"%s/.xash_id\", home ), \"r\" );\n\t\t\tif( cfg )\n\t\t\t{\n\t\t\t\tif( fscanf( cfg, \"%016\"PRIX64, &id ) > 0 )\n\t\t\t\t{\n\t\t\t\t\tid ^= SYSTEM_XOR_MASK;\n\t\t\t\t\tID_Check();\n\t\t\t\t}\n\t\t\t\tfclose( cfg );\n\t\t\t}\n\t\t}\n\t}\n#endif\n\tif( !id )\n\t{\n\t\tconst char *buf = (const char*) FS_LoadFile( \".xash_id\", NULL, false );\n\t\tif( buf )\n\t\t{\n\t\t\tsscanf( buf, \"%016\"PRIX64, &id );\n\t\t\tid ^= GAME_XOR_MASK;\n\t\t\tID_Check();\n\t\t}\n\t}\n\tif( !id )\n\t\tid = ID_GenerateRawId();\n\n\tMD5Init( &hash );\n\tMD5Update( &hash, (byte *)&id, sizeof( id ) );\n\tMD5Final( (byte*)md5, &hash );\n\n\tfor( i = 0; i < 16; i++ )\n\t\tQ_snprintf( &id_md5[i*2], sizeof( id_md5 ) - i * 2, \"%02hhx\", md5[i] );\n\n#if XASH_ANDROID && !XASH_DEDICATED\n\tAndroid_SaveID( va(\"%016\"PRIX64, id^SYSTEM_XOR_MASK ) );\n#elif XASH_WIN32\n\t{\n\t\tCHAR Buf[MAX_PATH];\n\t\tsprintf( Buf, \"%016\"PRIX64, id^SYSTEM_XOR_MASK );\n\t\tID_SetKeyData( HKEY_CURRENT_USER, \"Software\\\\\"XASH_ENGINE_NAME\"\\\\\", REG_SZ, \"xash_id\", Buf, Q_strlen(Buf) );\n\t}\n#else\n\t{\n\t\tconst char *home = getenv( \"HOME\" );\n\t\tif( COM_CheckString( home ) )\n\t\t{\n\t\t\tFILE *cfg = fopen( va( \"%s/.config/.xash_id\", home ), \"w\" );\n\t\t\tif( !cfg )\n\t\t\t\tcfg = fopen( va( \"%s/.local/.xash_id\", home ), \"w\" );\n\t\t\tif( !cfg )\n\t\t\t\tcfg = fopen( va( \"%s/.xash_id\", home ), \"w\" );\n\t\t\tif( cfg )\n\t\t\t{\n\t\t\t\tfprintf( cfg, \"%016\"PRIX64, id^SYSTEM_XOR_MASK );\n\t\t\t\tfclose( cfg );\n\t\t\t}\n\t\t}\n\t}\n#endif\n\tFS_WriteFile( \".xash_id\", va(\"%016\"PRIX64, id^GAME_XOR_MASK), 16 );\n#if 0\n\tMsg(\"MD5 id: %s\\nRAW id:%016\"PRIX64\"\\n\", id_md5, id );\n#endif\n}\n"
  },
  {
    "path": "engine/client/in_joy.c",
    "content": "/*\njoyinput.c - joystick common input code\n\nCopyright (C) 2016 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"input.h\"\n#include \"keydefs.h\"\n#include \"client.h\"\n#include \"platform/platform.h\"\n\n#ifndef SHRT_MAX\n#define SHRT_MAX 0x7FFF\n#endif\n\n#define MAX_AXES JOY_AXIS_NULL\n\n// index - axis num come from event\n// value - inner axis\nstatic engineAxis_t joyaxesmap[MAX_AXES] =\n{\n\tJOY_AXIS_SIDE,  // left stick, x\n\tJOY_AXIS_FWD,   // left stick, y\n\tJOY_AXIS_PITCH, // right stick, y\n\tJOY_AXIS_YAW,   // right stick, x\n\tJOY_AXIS_RT,    // right trigger\n\tJOY_AXIS_LT     // left trigger\n};\n\nstatic struct joy_axis_s\n{\n\tshort val;\n\tshort prevval;\n} joyaxis[MAX_AXES] = { 0 };\nstatic qboolean joy_initialized;\n\nstatic CVAR_DEFINE_AUTO( joy_pitch,   \"100.0\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"joystick pitch sensitivity\" );\nstatic CVAR_DEFINE_AUTO( joy_yaw,     \"100.0\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"joystick yaw sensitivity\" );\nstatic CVAR_DEFINE_AUTO( joy_side,    \"1.0\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"joystick side sensitivity. Values from -1.0 to 1.0\" );\nstatic CVAR_DEFINE_AUTO( joy_forward, \"1.0\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"joystick forward sensitivity. Values from -1.0 to 1.0\" );\nstatic CVAR_DEFINE_AUTO( joy_lt_threshold, \"16384\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"left trigger threshold. Value from 0 to 32767\");\nstatic CVAR_DEFINE_AUTO( joy_rt_threshold, \"16384\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"right trigger threshold. Value from 0 to 32767\" );\nstatic CVAR_DEFINE_AUTO( joy_side_key_threshold, \"24576\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"side axis key event emit threshold. Value from 0 to 32767\" );\nstatic CVAR_DEFINE_AUTO( joy_forward_key_threshold, \"24576\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"forward axis key event emit threshold. Value from 0 to 32767\");\nstatic CVAR_DEFINE_AUTO( joy_side_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"side axis deadzone. Value from 0 to 32767\" );\nstatic CVAR_DEFINE_AUTO( joy_forward_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"forward axis deadzone. Value from 0 to 32767\");\nstatic CVAR_DEFINE_AUTO( joy_pitch_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"pitch axis deadzone. Value from 0 to 32767\");\nstatic CVAR_DEFINE_AUTO( joy_yaw_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"yaw axis deadzone. Value from 0 to 32767\" );\nstatic CVAR_DEFINE_AUTO( joy_axis_binding, \"sfpyrl\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"axis hardware id to engine inner axis binding, \"\n\t\"s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger\" );\nCVAR_DEFINE_AUTO( joy_enable, \"1\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"enable joystick\" );\nstatic CVAR_DEFINE_AUTO( joy_have_gyro, \"0\", FCVAR_READ_ONLY, \"tells whether current active gamepad has gyroscope or not\" );\nstatic CVAR_DEFINE_AUTO( joy_calibrated, \"0\", FCVAR_READ_ONLY, \"tells whether current active gamepad gyroscope has been calibrated or not\" );\n\n/*\n============\nJoy_IsActive\n============\n*/\nqboolean Joy_IsActive( void )\n{\n\treturn joy_enable.value;\n}\n\n/*\n===========\nJoy_SetCapabilities\n===========\n*/\nvoid Joy_SetCapabilities( qboolean have_gyro )\n{\n\tCvar_FullSet( joy_have_gyro.name, have_gyro ? \"1\" : \"0\", joy_have_gyro.flags );\n}\n\n/*\n===========\nJoy_SetCalibrationState\n===========\n*/\nvoid Joy_SetCalibrationState( joy_calibration_state_t state )\n{\n\tif( (int)joy_calibrated.value == state )\n\t\treturn;\n\n\tCvar_FullSet( joy_calibrated.name, va( \"%d\", state ), joy_calibrated.flags );\n}\n\n/*\n============\nJoy_HatMotionEvent\n\nDPad events\n============\n*/\nstatic void Joy_HatMotionEvent( int value )\n{\n\tstruct\n\t{\n\t\tint mask;\n\t\tint key;\n\t} keys[] =\n\t{\n\t\t{ JOY_HAT_UP, K_UPARROW },\n\t\t{ JOY_HAT_DOWN, K_DOWNARROW },\n\t\t{ JOY_HAT_LEFT, K_LEFTARROW },\n\t\t{ JOY_HAT_RIGHT, K_RIGHTARROW },\n\t};\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( keys ); i++ )\n\t{\n\t\tif( value & keys[i].mask )\n\t\t{\n\t\t\tif( !Key_IsDown( keys[i].key ))\n\t\t\t\tKey_Event( keys[i].key, true );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( Key_IsDown( keys[i].key ))\n\t\t\t\tKey_Event( keys[i].key, false );\n\t\t}\n\t}\n}\n\n/*\n=============\nJoy_ProcessTrigger\n=============\n*/\nstatic void Joy_ProcessTrigger( const engineAxis_t engineAxis, short value )\n{\n\tint trigButton = 0, trigThreshold = 0;\n\n\tswitch( engineAxis )\n\t{\n\tcase JOY_AXIS_RT:\n\t\ttrigButton = K_JOY2;\n\t\ttrigThreshold = joy_rt_threshold.value;\n\t\tbreak;\n\tcase JOY_AXIS_LT:\n\t\ttrigButton = K_JOY1;\n\t\ttrigThreshold = joy_lt_threshold.value;\n\t\tbreak;\n\tdefault:\n\t\tCon_Reportf( S_ERROR \"%s: invalid axis = %i\\n\", __func__, engineAxis );\n\t\tbreak;\n\t}\n\n\t// update axis values\n\tjoyaxis[engineAxis].prevval = joyaxis[engineAxis].val;\n\tjoyaxis[engineAxis].val = value;\n\n\tif( joyaxis[engineAxis].val > trigThreshold &&\n\t\tjoyaxis[engineAxis].prevval <= trigThreshold ) // ignore random press\n\t{\n\t\tKey_Event( trigButton, true );\n\t}\n\telse if( joyaxis[engineAxis].val < trigThreshold &&\n\t\t\t joyaxis[engineAxis].prevval >= trigThreshold ) // we're unpressing (inverted)\n\t{\n\t\tKey_Event( trigButton, false );\n\t}\n}\n\nstatic int Joy_GetHatValueForAxis( const engineAxis_t engineAxis )\n{\n\tint threshold, negative, positive;\n\n\tswitch( engineAxis )\n\t{\n\tcase JOY_AXIS_SIDE:\n\t\tthreshold = joy_side_key_threshold.value;\n\t\tnegative = JOY_HAT_LEFT;\n\t\tpositive = JOY_HAT_RIGHT;\n\t\tbreak;\n\tcase JOY_AXIS_FWD:\n\t\tthreshold = joy_side_key_threshold.value;\n\t\tnegative = JOY_HAT_UP;\n\t\tpositive = JOY_HAT_DOWN;\n\t\tbreak;\n\tdefault:\n\t\tASSERT( false ); // only fwd/side axes can emit key events\n\t\treturn 0;\n\t}\n\n\t// similar code in Joy_ProcessTrigger\n\tif( joyaxis[engineAxis].val > threshold &&\n\t\tjoyaxis[engineAxis].prevval <= threshold ) // ignore random press\n\t{\n\t\treturn positive;\n\t}\n\tif( joyaxis[engineAxis].val < -threshold &&\n\t\tjoyaxis[engineAxis].prevval >= -threshold ) // we're unpressing (inverted)\n\t{\n\t\treturn negative;\n\t}\n\treturn 0;\n}\n\n/*\n=============\nJoy_ProcessStick\n=============\n*/\nstatic void Joy_ProcessStick( const engineAxis_t engineAxis, short value )\n{\n\tint deadzone = 0;\n\n\tswitch( engineAxis )\n\t{\n\tcase JOY_AXIS_FWD:   deadzone = joy_forward_deadzone.value; break;\n\tcase JOY_AXIS_SIDE:  deadzone = joy_side_deadzone.value; break;\n\tcase JOY_AXIS_PITCH: deadzone = joy_pitch_deadzone.value; break;\n\tcase JOY_AXIS_YAW:   deadzone = joy_yaw_deadzone.value; break;\n\tdefault:\n\t\tCon_Reportf( S_ERROR \"%s: invalid axis = %i\\n\", __func__, engineAxis );\n\t\tbreak;\n\t}\n\n\tif( value < deadzone && value > -deadzone )\n\t\tvalue = 0; // caught new event in deadzone, fill it with zero(no motion)\n\n\t// update axis values\n\tjoyaxis[engineAxis].prevval = joyaxis[engineAxis].val;\n\tjoyaxis[engineAxis].val = value;\n\n\t// fwd/side axis simulate hat movement\n\tif( ( engineAxis == JOY_AXIS_SIDE || engineAxis == JOY_AXIS_FWD ) &&\n\t\t( cls.key_dest == key_menu || cls.key_dest == key_console ))\n\t{\n\t\tint val = 0;\n\n\t\tval |= Joy_GetHatValueForAxis( JOY_AXIS_SIDE );\n\t\tval |= Joy_GetHatValueForAxis( JOY_AXIS_FWD );\n\n\t\tJoy_HatMotionEvent( val );\n\t}\n}\n\n/*\n=============\nJoy_AxisMotionEvent\n\nAxis events\n=============\n*/\nvoid Joy_AxisMotionEvent( engineAxis_t engineAxis, short value )\n{\n\tif( engineAxis >= JOY_AXIS_NULL )\n\t\treturn;\n\n\tif( value == joyaxis[engineAxis].val )\n\t\treturn; // it is not an update\n\n\tif( engineAxis >= JOY_AXIS_RT )\n\t\tJoy_ProcessTrigger( engineAxis, value );\n\telse\n\t\tJoy_ProcessStick( engineAxis, value );\n}\n\n/*\n=============\nJoy_GyroEvent\n\nGyroscope events\n=============\n*/\nvoid Joy_GyroEvent( vec3_t data )\n{\n\n}\n\n/*\n=============\nJoy_FinalizeMove\n\nAppend movement from axis. Called everyframe\n=============\n*/\nvoid Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )\n{\n\tif( !Joy_IsActive() )\n\t\treturn;\n\n\tif( FBitSet( joy_axis_binding.flags, FCVAR_CHANGED ) )\n\t{\n\t\tconst char *bind = joy_axis_binding.string;\n\t\tsize_t i;\n\n\t\tfor( i = 0; bind[i]; i++ )\n\t\t{\n\t\t\tswitch( bind[i] )\n\t\t\t{\n\t\t\tcase 's': joyaxesmap[i] = JOY_AXIS_SIDE; break;\n\t\t\tcase 'f': joyaxesmap[i] = JOY_AXIS_FWD; break;\n\t\t\tcase 'y': joyaxesmap[i] = JOY_AXIS_YAW; break;\n\t\t\tcase 'p': joyaxesmap[i] = JOY_AXIS_PITCH; break;\n\t\t\tcase 'r': joyaxesmap[i] = JOY_AXIS_RT; break;\n\t\t\tcase 'l': joyaxesmap[i] = JOY_AXIS_LT; break;\n\t\t\tdefault : joyaxesmap[i] = JOY_AXIS_NULL; break;\n\t\t\t}\n\t\t}\n\n\t\tClearBits( joy_axis_binding.flags, FCVAR_CHANGED );\n\t}\n\n\t*fw     -= joy_forward.value * (float)joyaxis[JOY_AXIS_FWD ].val/(float)SHRT_MAX;  // must be form -1.0 to 1.0\n\t*side   += joy_side.value    * (float)joyaxis[JOY_AXIS_SIDE].val/(float)SHRT_MAX;\n\t*dpitch -= joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime;\n\t*dyaw   += joy_yaw.value   * (float)joyaxis[JOY_AXIS_YAW  ].val/(float)SHRT_MAX * host.realframetime;\n}\n\nstatic void Joy_CalibrateGyro_f( void )\n{\n\tif( !joy_have_gyro.value )\n\t{\n\t\tCon_Printf( \"Current active gamepad doesn't have gyroscope\\n\" );\n\t\treturn;\n\t}\n\n\tPlatform_CalibrateGamepadGyro();\n}\n\n/*\n=============\nJoy_Init\n\nMain init procedure\n=============\n*/\nvoid Joy_Init( void )\n{\n\tCmd_AddRestrictedCommand( \"joy_calibrate_gyro\", Joy_CalibrateGyro_f, \"calibrate gamepad gyroscope. You must to put gamepad on stationary surface\" );\n\n\tCvar_RegisterVariable( &joy_pitch );\n\tCvar_RegisterVariable( &joy_yaw );\n\tCvar_RegisterVariable( &joy_side );\n\tCvar_RegisterVariable( &joy_forward );\n\n\tCvar_RegisterVariable( &joy_lt_threshold );\n\tCvar_RegisterVariable( &joy_rt_threshold );\n\n\t// emit a key event at 75% axis move\n\tCvar_RegisterVariable( &joy_side_key_threshold );\n\tCvar_RegisterVariable( &joy_forward_key_threshold );\n\n\t// by default, we rely on deadzone detection come from system, but some glitchy devices report false deadzones\n\tCvar_RegisterVariable( &joy_side_deadzone );\n\tCvar_RegisterVariable( &joy_forward_deadzone );\n\tCvar_RegisterVariable( &joy_pitch_deadzone );\n\tCvar_RegisterVariable( &joy_yaw_deadzone );\n\n\tCvar_RegisterVariable( &joy_axis_binding );\n\n\tCvar_RegisterVariable( &joy_have_gyro );\n\tCvar_RegisterVariable( &joy_calibrated );\n\tCvar_RegisterVariable( &joy_enable );\n\n\t// renamed from -nojoy to -noenginejoy to not conflict with\n\t// client.dll's joystick support\n\tif( Sys_CheckParm( \"-noenginejoy\" ))\n\t{\n\t\tCvar_FullSet( \"joy_enable\", \"0\", FCVAR_READ_ONLY );\n\t\treturn;\n\t}\n\n\tPlatform_JoyInit();\n\n\tjoy_initialized = true;\n}\n\n/*\n===========\nJoy_Shutdown\n\nShutdown joystick code\n===========\n*/\nvoid Joy_Shutdown( void )\n{\n\tPlatform_JoyShutdown();\n}\n"
  },
  {
    "path": "engine/client/in_touch.c",
    "content": "/*\ntouch.c - touchscreen support prototype\nCopyright (C) 2015-2018 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"math.h\"\n#include \"vgui_draw.h\"\n#include \"mobility_int.h\"\n\ntypedef enum\n{\n\ttouch_command, // just tap a button\n\ttouch_move,    // like a joystick stick\n\ttouch_joy,     // like a joystick stick, centered\n\ttouch_dpad,    // only two directions\n\ttouch_look,    // like a touchpad\n\ttouch_wheel    // scroll-like\n} touchButtonType;\n\ntypedef enum\n{\n\tstate_none = 0,\n\tstate_edit,\n\tstate_edit_move\n} touchState;\n\ntypedef enum\n{\n\tround_none = 0,\n\tround_grid,\n\tround_aspect\n} touchRound;\n\ntypedef struct touch_button_s\n{\n\ttouchButtonType type;\n\n\t// button coordinates\n\tfloat x1, y1, x2, y2;\n\n\tint gl_texturenum;\n\trgba_t color;\n\tchar texture[256];\n\tchar command[256];\n\tchar name[32];\n\tint finger;\n\tint flags;\n\tfloat fade;\n\tfloat fadespeed;\n\tfloat fadeend;\n\tfloat aspect;\n\n\t// Double-linked list\n\tstruct touch_button_s *next;\n\tstruct touch_button_s *prev;\n} touch_button_t;\n\ntypedef struct touchdefaultbutton_s\n{\n\tchar name[32];\n\tchar texture[256];\n\tchar command[256];\n\tfloat x1, y1, x2, y2;\n\trgba_t color;\n\ttouchRound round;\n\tfloat aspect;\n\tint flags;\n} touchdefaultbutton_t;\n\ntypedef struct touchbuttonlist_s\n{\n\ttouch_button_t *first;\n\ttouch_button_t *last;\n} touchbuttonlist_t;\n\nstatic struct touch_s\n{\n\tqboolean initialized;\n\tqboolean config_loaded;\n\ttouchbuttonlist_t list_user, list_edit;\n\tpoolhandle_t mempool;\n\ttouchState state;\n\n\tint look_finger;\n\tint move_finger;\n\tint wheel_finger;\n\n\ttouch_button_t *move_button;\n\tfloat move_start_x;\n\tfloat move_start_y;\n\n\tfloat wheel_amount;\n\tstring wheel_up;\n\tstring wheel_down;\n\tstring wheel_end;\n\tint wheel_count;\n\tqboolean wheel_horizontal;\n\n\tfloat forward;\n\tfloat side;\n\tfloat yaw;\n\tfloat pitch;\n\n\t// editing\n\ttouch_button_t *edit;\n\ttouch_button_t *selection;\n\ttouch_button_t *hidebutton;\n\tint resize_finger;\n\tqboolean showeditbuttons;\n\n\t// other features\n\tqboolean clientonly;\n\trgba_t scolor;\n\tint swidth;\n\tqboolean precision;\n\n\t// textures\n\tint whitetexture;\n\tint joytexture; // touch indicator\n\tqboolean configchanged;\n\tfloat actual_aspect_ratio; // maximum aspect ratio from launch, or aspect ratio when entering editor\n\tfloat config_aspect_ratio; // aspect ratio set by command from config or after entering editor\n} touch;\n\n// private to the engine flags\n#define TOUCH_FL_UNPRIVILEGED BIT( 10 )\n\nstatic touchdefaultbutton_t *g_DefaultButtons;\nstatic size_t g_DefaultButtonsLength;\n\nstatic CVAR_DEFINE_AUTO( touch_in_menu, \"0\", FCVAR_PRIVILEGED, \"draw touch in menu (for internal use only)\" );\nstatic CVAR_DEFINE_AUTO( touch_forwardzone, \"0.06\", FCVAR_FILTERABLE, \"forward touch zone\" );\nstatic CVAR_DEFINE_AUTO( touch_sidezone, \"0.06\", FCVAR_FILTERABLE, \"side touch zone\" );\nstatic CVAR_DEFINE_AUTO( touch_pitch, \"90\", FCVAR_FILTERABLE, \"touch pitch sensitivity\" );\nstatic CVAR_DEFINE_AUTO( touch_yaw, \"120\", FCVAR_FILTERABLE, \"touch yaw sensitivity\" );\nstatic CVAR_DEFINE_AUTO( touch_nonlinear_look, \"0\", FCVAR_FILTERABLE, \"enable nonlinear touch look\" );\nstatic CVAR_DEFINE_AUTO( touch_pow_factor, \"1.3\", FCVAR_FILTERABLE, \"set > 1 to enable\" );\nstatic CVAR_DEFINE_AUTO( touch_pow_mult, \"400.0\", FCVAR_FILTERABLE, \"power multiplier, usually 200-1000\" );\nstatic CVAR_DEFINE_AUTO( touch_exp_mult, \"0\", FCVAR_FILTERABLE, \"exponent multiplier, usually 20-200, 0 to disable\" );\nstatic CVAR_DEFINE_AUTO( touch_grid_count, \"50\", FCVAR_FILTERABLE, \"touch grid count\" );\nstatic CVAR_DEFINE_AUTO( touch_grid_enable, \"1\", FCVAR_FILTERABLE, \"enable touch grid\" );\nstatic CVAR_DEFINE_AUTO( touch_config_file, \"touch.cfg\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"current touch profile file\" );\nstatic CVAR_DEFINE_AUTO( touch_precise_amount, \"0.5\", FCVAR_FILTERABLE, \"sensitivity multiplier for precise-look\" );\nstatic CVAR_DEFINE_AUTO( touch_highlight_r, \"1.0\", 0, \"highlight r color\" );\nstatic CVAR_DEFINE_AUTO( touch_highlight_g, \"1.0\", 0, \"highlight g color\" );\nstatic CVAR_DEFINE_AUTO( touch_highlight_b, \"1.0\", 0, \"highlight b color\" );\nstatic CVAR_DEFINE_AUTO( touch_highlight_a, \"1.0\", 0, \"highlight alpha\" );\nstatic CVAR_DEFINE_AUTO( touch_dpad_radius, \"1.0\", FCVAR_FILTERABLE, \"dpad radius multiplier\" );\nstatic CVAR_DEFINE_AUTO( touch_joy_radius, \"1.0\", FCVAR_FILTERABLE, \"joy radius multiplier\" );\nstatic CVAR_DEFINE_AUTO( touch_move_indicator, \"0.0\", FCVAR_FILTERABLE, \"indicate move events (0 to disable)\" );\nstatic CVAR_DEFINE_AUTO( touch_joy_texture, \"touch_default/joy\", FCVAR_FILTERABLE, \"texture for move indicator\");\nstatic CVAR_DEFINE( touch_emulate, \"_touch_emulate\", \"0\", FCVAR_PRIVILEGED, \"emulate touch with mouse\" );\nCVAR_DEFINE_AUTO( touch_enable, DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"enable touch controls\" );\n\n// code looks smaller with it\n#define TO_SCRN_Y(x) (refState.width * (x) * Touch_AspectRatio())\n#define TO_SCRN_X(x) (refState.width * (x))\n\nstatic void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2  );\nstatic void IN_TouchEditClear( void );\nstatic void Touch_InitConfig( void );\n\nvoid Touch_NotifyResize( void )\n{\n\tif( refState.width && refState.height && ( !touch.configchanged || !touch.actual_aspect_ratio ))\n\t{\n\t\tfloat aspect_ratio = (float)refState.height / refState.width;\n\t\tif( aspect_ratio < 0.99 && aspect_ratio > touch.actual_aspect_ratio )\n\t\t\ttouch.actual_aspect_ratio = aspect_ratio;\n\t}\n}\n\nstatic inline float Touch_AspectRatio( void )\n{\n\tif( touch.config_aspect_ratio )\n\t\treturn touch.config_aspect_ratio;\n\n\tif( touch.actual_aspect_ratio )\n\t\treturn touch.actual_aspect_ratio;\n\n\tif( refState.width && refState.height )\n\t\treturn (float)refState.height / refState.width;\n\n\treturn 9.0f / 16.0f;\n}\n\nstatic void Touch_ConfigAspectRatio_f( void )\n{\n\ttouch.config_aspect_ratio = Q_atof( Cmd_Argv( 1 ));\n}\n\n\n/*\n==========================\nTouch_ExportButtonToConfig\n\nwrites button data to config\n==========================\n*/\nstatic void Touch_ExportButtonToConfig( file_t *f, const touch_button_t *button, qboolean keepAspect )\n{\n\tstring newCommand;\n\tint flags = button->flags;\n\n\tif( FBitSet( flags, TOUCH_FL_CLIENT ))\n\t\treturn; // skip temporary buttons\n\n\tif( FBitSet( flags, TOUCH_FL_DEF_SHOW ))\n\t\tClearBits( flags, TOUCH_FL_HIDE );\n\n\tif( FBitSet( flags, TOUCH_FL_DEF_HIDE ))\n\t\tSetBits( flags, TOUCH_FL_HIDE );\n\n\tCmd_Escape( newCommand, button->command, sizeof( newCommand ));\n\n\tFS_Printf( f, \"touch_addbutton \\\"%s\\\" \\\"%s\\\" \\\"%s\\\" %g %g %g %g %d %d %d %d %d\",\n\t\tbutton->name, button->texture, newCommand,\n\t\tbutton->x1, button->y1, button->x2, button->y2,\n\t\tbutton->color[0], button->color[1], button->color[2], button->color[3],\n\t\tflags );\n\n\tif( keepAspect )\n\t{\n\t\tfloat aspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( ));\n\t\tFS_Printf( f, \" %g\\n\", aspect );\n\t}\n\telse FS_Printf( f, \"\\n\" );\n}\n\n/*\n=================\nTouch_DumpConfig\n\nDump config to file\n=================\n*/\nstatic qboolean Touch_DumpConfig( const char *name, const char *profilename )\n{\n\tfile_t *f;\n\tconst touch_button_t *button;\n\n\tf = FS_Open( name, \"w\", true );\n\n\tif( !f )\n\t{\n\t\tCon_Printf( S_ERROR \"Couldn't write %s.\\n\", name );\n\t\treturn false;\n\t}\n\n\tFS_Printf( f, \"//=======================================================================\\n\");\n\tFS_Printf( f, \"//\\tGenerated by \"XASH_ENGINE_NAME\" (%i, %s, %s, %s-%s)\\n\", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());\n\tFS_Printf( f, \"//\\t\\t\\ttouchscreen config\\n\" );\n\tFS_Printf( f, \"//=======================================================================\\n\" );\n\tFS_Printf( f, \"\\ntouch_config_file \\\"%s\\\"\\n\", profilename );\n\tFS_Printf( f, \"\\n// touch cvars\\n\" );\n\tFS_Printf( f, \"\\n// sensitivity settings\\n\" );\n\tFS_Printf( f, \"touch_pitch \\\"%g\\\"\\n\", touch_pitch.value );\n\tFS_Printf( f, \"touch_yaw \\\"%g\\\"\\n\", touch_yaw.value );\n\tFS_Printf( f, \"touch_forwardzone \\\"%g\\\"\\n\", touch_forwardzone.value );\n\tFS_Printf( f, \"touch_sidezone \\\"%g\\\"\\n\", touch_sidezone.value );\n\tFS_Printf( f, \"touch_nonlinear_look \\\"%d\\\"\\n\", touch_nonlinear_look.value ? 1 : 0 );\n\tFS_Printf( f, \"touch_pow_factor \\\"%g\\\"\\n\", touch_pow_factor.value );\n\tFS_Printf( f, \"touch_pow_mult \\\"%g\\\"\\n\", touch_pow_mult.value );\n\tFS_Printf( f, \"touch_exp_mult \\\"%g\\\"\\n\", touch_exp_mult.value );\n\tFS_Printf( f, \"\\n// grid settings\\n\" );\n\tFS_Printf( f, \"touch_grid_count \\\"%d\\\"\\n\", (int)touch_grid_count.value );\n\tFS_Printf( f, \"touch_grid_enable \\\"%d\\\"\\n\", touch_grid_enable.value ? 1 : 0 );\n\tFS_Printf( f, \"\\n// global overstroke (width, r, g, b, a)\\n\" );\n\tFS_Printf( f, \"touch_set_stroke %d %d %d %d %d\\n\", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] );\n\tFS_Printf( f, \"\\n// highlight when pressed\\n\" );\n\tFS_Printf( f, \"touch_highlight_r \\\"%g\\\"\\n\", touch_highlight_r.value );\n\tFS_Printf( f, \"touch_highlight_g \\\"%g\\\"\\n\", touch_highlight_g.value );\n\tFS_Printf( f, \"touch_highlight_b \\\"%g\\\"\\n\", touch_highlight_b.value );\n\tFS_Printf( f, \"touch_highlight_a \\\"%g\\\"\\n\", touch_highlight_a.value );\n\tFS_Printf( f, \"\\n// _joy and _dpad options\\n\" );\n\tFS_Printf( f, \"touch_dpad_radius \\\"%g\\\"\\n\", touch_dpad_radius.value );\n\tFS_Printf( f, \"touch_joy_radius \\\"%g\\\"\\n\", touch_joy_radius.value );\n\tFS_Printf( f, \"\\n// how much slowdown when Precise Look button pressed\\n\" );\n\tFS_Printf( f, \"touch_precise_amount \\\"%g\\\"\\n\", touch_precise_amount.value );\n\tFS_Printf( f, \"\\n// enable/disable move indicator\\n\" );\n\tFS_Printf( f, \"touch_move_indicator \\\"%g\\\"\\n\", touch_move_indicator.value );\n\n\tFS_Printf( f, \"\\n// reset menu state when execing config\\n\" );\n\tFS_Printf( f, \"touch_setclientonly 0\\n\" );\n\tFS_Printf( f, \"\\n// touch buttons\\n\" );\n\tFS_Printf( f, \"touch_removeall\\n\" );\n\tFS_Printf( f, \"touch_aspectratio %g\\n\", Touch_AspectRatio());\n\n\tfor( button = touch.list_user.first; button; button = button->next )\n\t\tTouch_ExportButtonToConfig( f, button, false );\n\n\tFS_Close( f );\n\treturn true;\n}\n\n/*\n=================\nTouch_WriteConfig\n\nsave current touch configuration\n=================\n*/\nvoid Touch_WriteConfig( void )\n{\n\tstring newconfigfile, oldconfigfile;\n\n\tif( !touch.list_user.first )\n\t\treturn;\n\n\tif( Sys_CheckParm( \"-nowriteconfig\" ) || !touch.configchanged || !touch.config_loaded )\n\t\treturn;\n\n\tCon_DPrintf( \"%s: %s\\n\", __func__, touch_config_file.string );\n\n\tQ_snprintf( newconfigfile, sizeof( newconfigfile ), \"%s.new\", touch_config_file.string );\n\tQ_snprintf( oldconfigfile, sizeof( oldconfigfile ), \"%s.bak\", touch_config_file.string );\n\n\tif( Touch_DumpConfig( newconfigfile, touch_config_file.string ))\n\t{\n\t\tFS_Delete( oldconfigfile );\n\t\tFS_Rename( touch_config_file.string, oldconfigfile );\n\n\t\tFS_Delete( touch_config_file.string );\n\t\tFS_Rename( newconfigfile, touch_config_file.string );\n\t}\n}\n\n/*\n=================\nTouch_ExportConfig_f\n\nexport current touch configuration into profile\n=================\n*/\nstatic void Touch_ExportConfig_f( void )\n{\n\tconst char *name;\n\tstring profilename;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_exportconfig <name>\\n\" );\n\t\treturn;\n\t}\n\n\tif( !touch.list_user.first )\n\t{\n\t\tCon_Printf( \"%s: nothing to export\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tname = Cmd_Argv( 1 );\n\n\tif( Q_strstr( name, \"touch_presets/\" ))\n\t{\n\t\tstring profilebase;\n\n\t\tCOM_FileBase( name, profilebase, sizeof( profilebase ));\n\t\tQ_snprintf( profilename, sizeof( profilebase ), \"touch_profiles/%s (copy).cfg\", profilebase );\n\t}\n\telse Q_strncpy( profilename, name, sizeof( profilename ));\n\n\tCon_Reportf( \"Exporting config to \\\"%s\\\", profile name \\\"%s\\\"\\n\", name, profilename );\n\tTouch_DumpConfig( name, profilename );\n}\n\n/*\n=================\nTouch_GenerateCode_f\n\nexport current touch configuration into C code\n=================\n*/\nstatic void Touch_GenerateCode_f( void )\n{\n\tconst touch_button_t *button;\n\trgba_t c = { 0 };\n\n\tif( !touch.list_user.first )\n\t{\n\t\tCon_Printf( \"%s: nothing to export\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tfor( button = touch.list_user.first; button; button = button->next )\n\t{\n\t\tfloat aspect;\n\t\tint flags = button->flags;\n\t\tint round;\n\n\t\tif( FBitSet( flags, TOUCH_FL_CLIENT ))\n\t\t\tcontinue; // skip temporary buttons\n\n\t\tif( FBitSet( flags, TOUCH_FL_DEF_SHOW ))\n\t\t\tClearBits( flags, TOUCH_FL_HIDE );\n\n\t\tif( FBitSet( flags, TOUCH_FL_DEF_HIDE ))\n\t\t\tSetBits( flags, TOUCH_FL_HIDE );\n\n\t\taspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( ));\n\n\t\tif( memcmp( c, button->color, sizeof( c )))\n\t\t{\n\t\t\tCon_Printf( \"unsigned char color[] = { %d, %d, %d, %d };\\n\",\n\t\t\t\tbutton->color[0], button->color[1], button->color[2], button->color[3] );\n\t\t\tmemcpy( c, button->color, sizeof( c ));\n\t\t}\n\n\t\tif( button->type == touch_command )\n\t\t{\n\t\t\tif( fabs( aspect - 1.0f ) < 0.001 )\n\t\t\t\tround = round_aspect;\n\t\t\telse\n\t\t\t\tround = round_grid;\n\t\t}\n\t\telse\n\t\t\tround = round_none;\n\n\t\tCon_Printf( \"TOUCH_ADDDEFAULT( \\\"%s\\\", \\\"%s\\\", \\\"%s\\\", %gf, %gf, %gf, %gf, color, %d, %g, %d );\\n\",\n\t\t\tbutton->name, button->texture, button->command,\n\t\t\tbutton->x1, button->y1, button->x2, button->y2,\n\t\t\tround, aspect, flags );\n\t}\n}\n\nstatic void Touch_RoundAll_f( void )\n{\n\ttouch_button_t *button;\n\n\tif( !touch_grid_enable.value )\n\t\treturn;\n\n\tfor( button = touch.list_user.first; button; button = button->next )\n\t\tIN_TouchCheckCoords( &button->x1, &button->y1, &button->x2, &button->y2 );\n}\n\nstatic void Touch_ListButtons_f( void )\n{\n\ttouch_button_t *button;\n\n\tTouch_InitConfig();\n\n\tfor( button = touch.list_user.first; button; button = button->next )\n\t{\n\t\tCon_Printf( \"%s %s %s %g %g %g %g %d %d %d %d %d\\n\",\n\t\t\tbutton->name, button->texture, button->command,\n\t\t\tbutton->x1, button->y1, button->x2, button->y2,\n\t\t\tbutton->color[0], button->color[1], button->color[2], button->color[3],\n\t\t\tbutton->flags );\n\n\t\tif( FBitSet( button->flags, TOUCH_FL_CLIENT ))\n\t\t\tcontinue;\n\n\t\tUI_AddTouchButtonToList( button->name, button->texture, button->command, button->color, button->flags );\n\t}\n\ttouch.configchanged = true;\n}\n\nstatic void Touch_Stroke_f( void )\n{\n\tif( Cmd_Argc() != 6 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_set_stroke <width> <r> <g> <b> <a>\\n\");\n\t\treturn;\n\t}\n\n\ttouch.swidth = Q_atoi( Cmd_Argv( 1 ) );\n\tMakeRGBA( touch.scolor, Q_atoi( Cmd_Argv( 2 ) ), Q_atoi( Cmd_Argv( 3 ) ), Q_atoi( Cmd_Argv( 4 ) ), Q_atoi( Cmd_Argv( 5 ) ) );\n}\n\nstatic touch_button_t *Touch_FindNextNoPattern( touch_button_t *buttons, const char *name, qboolean privileged )\n{\n\ttouch_button_t *b;\n\n\tfor( b = buttons; b; b = b->next )\n\t{\n\t\tif( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\tcontinue;\n\n\t\tif( !Q_strncmp( b->name, name, sizeof( b->name )))\n\t\t\treturn b;\n\t}\n\n\treturn NULL;\n}\n\nstatic touch_button_t *Touch_FindButtonNoPattern( touchbuttonlist_t *list, const char *name, qboolean privileged )\n{\n\treturn Touch_FindNextNoPattern( list->first, name, privileged );\n}\n\nstatic touch_button_t *Touch_FindNext( touch_button_t *buttons, const char *name, qboolean privileged )\n{\n\ttouch_button_t *b;\n\tqboolean has_pattern = Q_strchr( name, '*' ) != NULL;\n\n\tif( !has_pattern )\n\t\treturn Touch_FindNextNoPattern( buttons, name, privileged );\n\n\tfor( b = buttons; b; b = b->next )\n\t{\n\t\tif( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\tcontinue;\n\n\t\tif( Q_stricmpext( name, b->name ))\n\t\t\treturn b;\n\t}\n\n\treturn NULL;\n}\n\n\nstatic touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *name, qboolean privileged )\n{\n\treturn Touch_FindNext( list->first, name, privileged );\n}\n\nvoid Touch_SetClientOnly( byte state )\n{\n\t// TODO: fix clash with vgui cursors\n\tif( touch.clientonly == state )\n\t\treturn;\n\n\ttouch.clientonly = state;\n\n\ttouch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;\n\ttouch.forward = touch.side = 0;\n\n\tif( state )\n\t{\n\t\tPlatform_SetCursorType( dc_arrow );\n\t\tIN_DeactivateMouse();\n\t}\n\telse\n\t{\n\t\tPlatform_SetCursorType( dc_none );\n\t\tIN_ActivateMouse();\n\t}\n}\n\nstatic void Touch_SetClientOnly_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_setclientonly <state>\\n\");\n\t\treturn;\n\t}\n\n\tTouch_SetClientOnly( Q_atoi( Cmd_Argv( 1 )));\n}\n\nstatic void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *name, qboolean privileged )\n{\n\ttouch_button_t *button;\n\n\tIN_TouchEditClear();\n\n\twhile(( button = Touch_FindFirst( &touch.list_user, name, privileged )))\n\t{\n\t\tif( button->prev )\n\t\t\tbutton->prev->next = button->next;\n\t\telse\n\t\t\tlist->first = button->next;\n\n\t\tif( button->next )\n\t\t\tbutton->next->prev = button->prev;\n\t\telse\n\t\t\tlist->last = button->prev;\n\n\t\tMem_Free( button );\n\t}\n}\n\nvoid Touch_RemoveButton( const char *name, qboolean privileged )\n{\n\tTouch_RemoveButtonFromList( &touch.list_user, name, privileged );\n}\n\nstatic void IN_TouchRemoveButton_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_removebutton <button>\\n\");\n\t\treturn;\n\t}\n\n\tTouch_RemoveButton( Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged( ));\n}\n\nstatic void Touch_ClearList( touchbuttonlist_t *list )\n{\n\twhile( list->first )\n\t{\n\t\ttouch_button_t *remove = list->first;\n\t\tlist->first = list->first->next;\n\t\tMem_Free( remove );\n\t}\n\tlist->first = list->last = NULL;\n}\n\nstatic void Touch_RemoveAll_f( void )\n{\n\tIN_TouchEditClear();\n\tTouch_ClearList( &touch.list_user );\n\ttouch.config_aspect_ratio = 0.0f;\n}\n\nstatic void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color, qboolean privileged )\n{\n\ttouch_button_t *b;\n\n\tfor( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))\n\t\tVector4Copy( color, b->color );\n}\n\nstatic void Touch_SetTexture( touchbuttonlist_t *list, const char *name, const char *texture, qboolean privileged )\n{\n\ttouch_button_t *button = Touch_FindButtonNoPattern( list, name, privileged );\n\n\tif( !button )\n\t\treturn;\n\n\tbutton->gl_texturenum = -1; // mark for texture load\n\tQ_strncpy( button->texture, texture, sizeof( button->texture ));\n}\n\nstatic void Touch_SetCommand( touch_button_t *button, const char *command )\n{\n\tQ_strncpy( button->command, command, sizeof( button->command ));\n\n\tif( !Q_strcmp( command, \"_look\" ))\n\t\tbutton->type = touch_look;\n\telse if( !Q_strcmp( command, \"_move\" ))\n\t\tbutton->type = touch_move;\n\telse if( !Q_strcmp( command, \"_joy\" ))\n\t\tbutton->type = touch_joy;\n\telse if( !Q_strcmp( command, \"_dpad\" ))\n\t\tbutton->type = touch_dpad;\n\telse if( !Q_strncmp( \"_wheel \", command, 7 ) || !Q_strncmp( \"_hwheel \", command, 8 ))\n\t\tbutton->type = touch_wheel;\n\telse\n\t\tbutton->type = touch_command;\n}\n\nvoid Touch_HideButtons( const char *name, byte hide, qboolean privileged )\n{\n\ttouch_button_t *b;\n\n\tfor( b = Touch_FindFirst( &touch.list_user, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))\n\t{\n\t\tif( hide )\n\t\t\tSetBits( b->flags, TOUCH_FL_HIDE );\n\t\telse\n\t\t\tClearBits( b->flags, TOUCH_FL_HIDE );\n\t}\n}\n\nstatic void Touch_ToggleSelection_f( void )\n{\n\tif( touch.selection )\n\t\ttouch.selection->flags ^= TOUCH_FL_HIDE;\n}\n\nstatic void Touch_Hide_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_hide <button>\\n\");\n\t\treturn;\n\t}\n\n\tTouch_HideButtons( Cmd_Argv( 1 ), true, Cmd_CurrentCommandIsPrivileged( ));\n}\n\nstatic void Touch_Show_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_show <button>\\n\");\n\t\treturn;\n\t}\n\n\tTouch_HideButtons( Cmd_Argv( 1 ), false, Cmd_CurrentCommandIsPrivileged( ));\n}\n\nstatic void Touch_FadeButtons( touchbuttonlist_t *list, const char *name, float speed, float end, float start, qboolean privileged )\n{\n\ttouch_button_t *b;\n\n\tfor( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))\n\t{\n\t\tif( start >= 0 )\n\t\t\tb->fade = start;\n\t\tb->fadespeed = speed;\n\t\tb->fadeend = end;\n\t}\n}\n\nstatic void Touch_Fade_f( void )\n{\n\tfloat start = -1;\n\n\tif( Cmd_Argc() == 5 )\n\t{\n\t\tstart = Q_atof( Cmd_Argv( 4 ) );\n\t}\n\telse if( Cmd_Argc() != 4 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_fade <button> <speed> <end> [start]\\n\");\n\t\treturn;\n\t}\n\n\tTouch_FadeButtons( &touch.list_user,Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), Q_atof( Cmd_Argv( 3 )),\n\t\tstart, Cmd_CurrentCommandIsPrivileged( ));\n}\n\nstatic void Touch_SetColor_f( void )\n{\n\tif( Cmd_Argc() == 6 )\n\t{\n\t\trgba_t color = { Q_atoi( Cmd_Argv( 2 )), Q_atoi( Cmd_Argv( 3 )), Q_atoi( Cmd_Argv( 4 )), Q_atoi( Cmd_Argv( 5 )) };\n\t\tTouch_SetColor( &touch.list_user, Cmd_Argv( 1 ), color, Cmd_CurrentCommandIsPrivileged( ));\n\t}\n\telse Con_Printf( S_USAGE \"touch_setcolor <pattern> <r> <g> <b> <a>\\n\" );\n}\n\nstatic void Touch_SetTexture_f( void )\n{\n\tif( Cmd_Argc() == 3 )\n\t\tTouch_SetTexture( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ), Cmd_CurrentCommandIsPrivileged( ));\n\telse Con_Printf( S_USAGE \"touch_settexture <name> <file>\\n\" );\n}\n\nstatic void Touch_SetFlags_f( void )\n{\n\tif( Cmd_Argc() == 3 )\n\t{\n\t\tqboolean privileged = Cmd_CurrentCommandIsPrivileged();\n\t\ttouch_button_t *button = Touch_FindButtonNoPattern( &touch.list_user, Cmd_Argv( 1 ), privileged );\n\n\t\tif( button )\n\t\t\tbutton->flags = ( privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT ) | Q_atoi( Cmd_Argv( 2 ));\n\t\telse\n\t\t\tCon_Printf( S_ERROR \"no such button\" );\n\t}\n\telse Con_Printf( S_USAGE \"touch_setflags <name> <file>\\n\" );\n}\n\nstatic void Touch_SetCommand_f( void )\n{\n\tif( Cmd_Argc() == 3 )\n\t{\n\t\ttouch_button_t *button = Touch_FindButtonNoPattern( &touch.list_user, Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged( ));\n\n\t\tif( button )\n\t\t\tTouch_SetCommand( button, Cmd_Argv( 2 ) );\n\t\telse\n\t\t\tCon_Printf( S_ERROR \"no such button\" );\n\t}\n\telse Con_Printf( S_USAGE \"touch_setcommand <name> <command>\\n\" );\n}\n\nstatic void Touch_LoadDefaults_f( void );\n\nstatic void Touch_ReloadConfig_f( void )\n{\n\ttouch.state = state_none;\n\tif( touch.edit )\n\t\ttouch.edit->finger = -1;\n\n\tif( touch.selection )\n\t\ttouch.selection->finger = -1;\n\n\ttouch.edit = touch.selection = NULL;\n\ttouch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;\n\n\tif( touch_in_menu.value )\n\t\tCvar_DirectSet( &touch_in_menu, \"0\" );\n\n\tif( FS_FileExists( touch_config_file.string, true ))\n\t\tCbuf_AddTextf( \"exec \\\"%s\\\"\\n\", touch_config_file.string );\n\telse\n\t{\n\t\tTouch_LoadDefaults_f();\n\t\ttouch.configchanged = true;\n\t}\n}\n\nstatic touch_button_t *Touch_AddButton( touchbuttonlist_t *list, const char *name, const char *texture, const char *command,\n\tfloat x1, float y1, float x2, float y2, byte *color, qboolean privileged )\n{\n\ttouch_button_t *b = Mem_Calloc( touch.mempool, sizeof( *b ));\n\n\tTouch_RemoveButtonFromList( list, name, privileged ); // replace if exist\n\n\tb->gl_texturenum = -1;\n\tQ_strncpy( b->texture, texture, sizeof( b->texture ));\n\tQ_strncpy( b->name, name, sizeof( b->name ));\n\tb->x1 = x1;\n\tb->y1 = y1;\n\tb->x2 = x2;\n\tb->y2 = y2;\n\tVector4Copy( color, b->color );\n\tb->fade = 1;\n\n\tif( !privileged )\n\t\tSetBits( b->flags, TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT );\n\n\tTouch_SetCommand( b, command );\n\n\tb->finger = -1;\n\tb->prev = list->last;\n\tif( b->prev )\n\t\tb->prev->next = b;\n\tlist->last = b;\n\n\tif( !list->first )\n\t\tlist->first = b;\n\n\treturn b;\n}\n\nvoid Touch_AddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )\n{\n\ttouch_button_t *button;\n\n\tif( !touch.initialized )\n\t\treturn;\n\n\tif( round )\n\t\tIN_TouchCheckCoords( &x1, &y1, &x2, &y2 );\n\n\tif( round == round_aspect )\n\t\ty2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;\n\n\tbutton = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true );\n\tSetBits( button->flags, TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT );\n\tbutton->aspect = aspect;\n}\n\nstatic void Touch_LoadDefaults_f( void )\n{\n\tint i;\n\tfor( i = 0; i < g_DefaultButtonsLength; i++ )\n\t{\n\t\ttouch_button_t *button;\n\t\tfloat x1 = g_DefaultButtons[i].x1,\n\t\t\t  y1 = g_DefaultButtons[i].y1,\n\t\t\t  x2 = g_DefaultButtons[i].x2,\n\t\t\t  y2 = g_DefaultButtons[i].y2;\n\n\t\tIN_TouchCheckCoords( &x1, &y1, &x2, &y2 );\n\n\t\tif( g_DefaultButtons[i].aspect && g_DefaultButtons[i].round == round_aspect )\n\t\t{\n\t\t\tif( g_DefaultButtons[i].texture[0] == '#' )\n\t\t\t\ty2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth * 2.0f / refState.height;\n\t\t\telse\n\t\t\t\ty2 = y1 + (( x2 - x1 ) / Touch_AspectRatio()) * g_DefaultButtons[i].aspect;\n\t\t}\n\n\t\tIN_TouchCheckCoords( &x1, &y1, &x2, &y2 );\n\n\t\tbutton = Touch_AddButton( &touch.list_user, g_DefaultButtons[i].name, g_DefaultButtons[i].texture, g_DefaultButtons[i].command, x1, y1, x2, y2, g_DefaultButtons[i].color, true );\n\t\tSetBits( button->flags, g_DefaultButtons[i].flags );\n\t\tbutton->aspect = g_DefaultButtons[i].aspect;\n\t}\n\ttouch.configchanged = true;\n}\n\n// Add default button from client\nvoid Touch_AddDefaultButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )\n{\n\ttouchdefaultbutton_t *b;\n\n\tg_DefaultButtons = Mem_Realloc( touch.mempool, g_DefaultButtons, sizeof( *g_DefaultButtons ) * ( g_DefaultButtonsLength + 1 ));\n\n\tb = &g_DefaultButtons[g_DefaultButtonsLength];\n\n\tQ_strncpy( b->name, name, sizeof( b->name ));\n\tQ_strncpy( b->texture, texture, sizeof( b->texture ));\n\tQ_strncpy( b->command, command, sizeof( b->command ));\n\tb->x1 = x1;\n\tb->y1 = y1;\n\tb->x2 = x2;\n\tb->y2 = y2;\n\tVector4Copy( color, b->color );\n\tb->round = round;\n\tb->aspect = aspect;\n\tb->flags = flags;\n\n\tg_DefaultButtonsLength++;\n}\n\n// Client may remove all default buttons from engine\nvoid Touch_ResetDefaultButtons( void )\n{\n\tg_DefaultButtonsLength = 0;\n\n\tif( g_DefaultButtons )\n\t{\n\t\tMem_Free( g_DefaultButtons );\n\t\tg_DefaultButtons = NULL;\n\t}\n}\n\nstatic void Touch_AddButton_f( void )\n{\n\trgba_t color = { 255, 255, 255, 255 };\n\ttouch_button_t *button = NULL;\n\tconst char *name, *command;\n\tfloat x1 = 0.4f, y1 = 0.4f, x2 = 0.6f, y2 = 0.6f;\n\tqboolean privileged = Cmd_CurrentCommandIsPrivileged();\n\tstring texture;\n\n\tif( Cmd_Argc( ) < 4 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_addbutton <name> <texture> <command> [<x1> <y1> <x2> <y2> [ r g b a ] ]\\n\" );\n\t\treturn;\n\t}\n\n\tname = Cmd_Argv( 1 );\n\tQ_strncpy( texture, Cmd_Argv( 2 ), sizeof( texture ));\n\tcommand = Cmd_Argv( 3 );\n\n\t// HACKHACK: old engine specifically used .tga for touch buttons\n\t// and because new engine extras.pk3 don't have .tga textures\n\t// (which instead were converted to .png) strip extension to let\n\t// to let imagelib choose better format\n\t//\n\t// Remove this when old engine migration would be done\n\tif( !Q_stricmp( COM_FileExtension( texture ), \"tga\" ))\n\t\tCOM_StripExtension( texture );\n\n\tif( Cmd_Argc( ) >= 8 )\n\t{\n\t\tx1 = Q_atof( Cmd_Argv( 4 ));\n\t\ty1 = Q_atof( Cmd_Argv( 5 ));\n\t\tx2 = Q_atof( Cmd_Argv( 6 ));\n\t\ty2 = Q_atof( Cmd_Argv( 7 ));\n\t}\n\n\tif( Cmd_Argc( ) >= 12 )\n\t{\n\t\tcolor[0] = Q_atoi( Cmd_Argv( 8 ));\n\t\tcolor[1] = Q_atoi( Cmd_Argv( 9 ));\n\t\tcolor[2] = Q_atoi( Cmd_Argv( 10 ));\n\t\tcolor[3] = Q_atoi( Cmd_Argv( 11 ));\n\t}\n\n\tbutton = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, privileged );\n\n\tif( Cmd_Argc( ) >= 13 )\n\t\tSetBits( button->flags, Q_atoi( Cmd_Argv( 12 )));\n\n\tif( Cmd_Argc( ) >= 14 )\n\t{\n\t\t// Recalculate button coordinates aspect ratio\n\t\t// This is feature for distributed configs\n\t\tfloat aspect = Q_atof( Cmd_Argv( 13 ));\n\t\tif( aspect )\n\t\t{\n\t\t\tif( button->texture[0] != '#' )\n\t\t\t\tbutton->y2 = button->y1 + (( button->x2 - button->x1 ) / Touch_AspectRatio( )) * aspect;\n\t\t\tbutton->aspect = aspect;\n\t\t}\n\t}\n}\n\nstatic void Touch_EnableEdit_f( void )\n{\n\tfloat current_ratio = (float)refState.height / refState.width;\n\n\tif( touch.state == state_none )\n\t\ttouch.state = state_edit;\n\n\ttouch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;\n\ttouch.move_button = NULL;\n\ttouch.configchanged = true;\n\n\t/* try determine the best ratio\n\t * User enters editor. Window now have correct size. Need to fix aspect ratio in some cases */\n\t// Case A: no config was loaded, touch was generated with lower height, but window was resized higher, reset it to actual size\n\tif( touch.actual_aspect_ratio > current_ratio )\n\t\ttouch.actual_aspect_ratio = current_ratio;\n\tif( !touch.config_aspect_ratio )\n\t\ttouch.config_aspect_ratio = touch.actual_aspect_ratio;\n\t// Case B: config was loaded, but window may be resized later, so keep y coordinate as is\n\ttouch.actual_aspect_ratio = current_ratio;\n\n\t// convert coordinates to actual aspect ratio after it was updated\n\tif( touch.config_aspect_ratio != touch.actual_aspect_ratio )\n\t{\n\t\ttouch_button_t *button;\n\n\t\tfor( button = touch.list_user.first; button; button = button->next )\n\t\t{\n\t\t\tbutton->y1 /= touch.actual_aspect_ratio / touch.config_aspect_ratio;\n\t\t\tbutton->y2 /= touch.actual_aspect_ratio / touch.config_aspect_ratio;\n\n\t\t\t// clamp positions to make buttons visible by user\n\t\t\tif( button->y2 > 1.0f )\n\t\t\t{\n\t\t\t\tbutton->y1 -= button->y2 - 1.0f;\n\t\t\t\tbutton->y2 -= button->y2 - 1.0f;\n\t\t\t}\n\t\t}\n\t\ttouch.config_aspect_ratio = touch.actual_aspect_ratio;\n\t}\n}\n\nstatic void Touch_DisableEdit_f( void )\n{\n\ttouch.state = state_none;\n\tif( touch.edit )\n\t\ttouch.edit->finger = -1;\n\tif( touch.selection )\n\t\ttouch.selection->finger = -1;\n\ttouch.edit = touch.selection = NULL;\n\ttouch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;\n\n\tif( touch_in_menu.value )\n\t\tCvar_DirectSet( &touch_in_menu, \"0\" );\n\telse if( cls.key_dest == key_game )\n\t\tTouch_WriteConfig();\n}\n\nstatic void Touch_DeleteProfile_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"touch_deleteprofile <name>\\n\" );\n\t\treturn;\n\t}\n\n\t// delete profile\n\tFS_Delete( va( \"touch_profiles/%s.cfg\", Cmd_Argv( 1 )));\n}\n\nstatic void Touch_InitEditor( void )\n{\n\tfloat x = 0.1f * (Touch_AspectRatio());\n\tfloat y = 0.05f;\n\ttouch_button_t *temp;\n\trgba_t color;\n\n\tMakeRGBA( color, 255, 255, 255, 255 );\n\n\tTouch_ClearList( &touch.list_edit );\n\n\ttemp = Touch_AddButton( &touch.list_edit, \"close\", \"touch_default/edit_close\", \"touch_disableedit\", 0, y, x, y + 0.1f, color, true );\n\tSetBits( temp->flags, TOUCH_FL_NOEDIT );\n\n\ttemp = Touch_AddButton( &touch.list_edit, \"close\", \"#Close and save\", \"\", x, y, x + 0.2f, y + 0.1f, color, true );\n\tSetBits( temp->flags, TOUCH_FL_NOEDIT );\n\n\ty += 0.2f;\n\n\ttemp = Touch_AddButton( &touch.list_edit, \"cancel\", \"touch_default/edit_reset\", \"touch_reloadconfig\", 0, y, x, y + 0.1f, color, true );\n\tSetBits( temp->flags, TOUCH_FL_NOEDIT );\n\n\ttemp = Touch_AddButton( &touch.list_edit, \"close\", \"#Cancel and reset\", \"\", x, y, x + 0.2f, y + 0.1f, color, true );\n\tSetBits( temp->flags, TOUCH_FL_NOEDIT );\n\n\ty += 0.2f;\n\n\ttouch.hidebutton = Touch_AddButton( &touch.list_edit, \"showhide\", \"touch_default/edit_hide\", \"touch_toggleselection\", 0, y, x, y + 0.1f, color, true );\n\tSetBits( touch.hidebutton->flags, TOUCH_FL_HIDE | TOUCH_FL_NOEDIT );\n}\n\nvoid Touch_Init( void )\n{\n\trgba_t color;\n\n\tif( touch.initialized )\n\t\treturn;\n\n\ttouch.mempool = Mem_AllocPool( \"Touch\" );\n\t//touch.first = touch.last = NULL;\n\tCon_Printf( \"%s()\\n\", __func__ );\n\ttouch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;\n\ttouch.state = state_none;\n\ttouch.showeditbuttons = true;\n\ttouch.clientonly = false;\n\ttouch.precision = false;\n\tMakeRGBA( touch.scolor, 255, 255, 255, 255 );\n\ttouch.swidth = 1;\n\tg_DefaultButtons = NULL;\n\tg_DefaultButtonsLength = 0;\n\n\ttouch.list_edit.first = touch.list_edit.last = NULL;\n\ttouch.list_user.first = touch.list_user.last = NULL;\n\n\t// fill default buttons list\n\tMakeRGBA( color, 255, 255, 255, 255 );\n\tTouch_AddDefaultButton( \"look\", \"\", \"_look\", 0.500000, 0.000000, 1.000000, 1, color, 0, 0, 0 );\n\tTouch_AddDefaultButton( \"move\", \"\", \"_move\", 0.000000, 0.000000, 0.500000, 1, color, 0, 0, 0 );\n\tTouch_AddDefaultButton( \"invnext\", \"touch_default/next_weap\", \"invnext\", 0.000000, 0.530200, 0.120000, 0.757428, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"invprev\", \"touch_default/prev_weap\", \"invprev\", 0.000000, 0.075743, 0.120000, 0.302971, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"use\", \"touch_default/use\", \"+use\", 0.880000, 0.454457, 1.000000, 0.681685, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"jump\", \"touch_default/jump\", \"+jump\", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"attack\", \"touch_default/shoot\", \"+attack\", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"attack2\", \"touch_default/shoot_alt\", \"+attack2\", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"loadquick\", \"touch_default/load\", \"loadquick\", 0.760000, 0.000000, 0.840000, 0.142222, color, 2, 1, 16 );\n\tTouch_AddDefaultButton( \"savequick\", \"touch_default/save\", \"savequick\", 0.840000, 0.000000, 0.920000, 0.142222, color, 2, 1, 16 );\n\tTouch_AddDefaultButton( \"messagemode\", \"touch_default/keyboard\", \"messagemode\", 0.840000, 0.000000, 0.920000, 0.142222, color, 2, 1, 8 );\n\tTouch_AddDefaultButton( \"reload\", \"touch_default/reload\", \"+reload\", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"flashlight\", \"touch_default/flash_light_filled\", \"impulse 100\", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"scores\", \"touch_default/map\", \"+showscores\", 0.760000, 0.000000, 0.840000, 0.142222, color, 2, 1, 8 );\n\tTouch_AddDefaultButton( \"show_numbers\", \"touch_default/show_weapons\", \"exec touch_default/numbers.cfg\", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"duck\", \"touch_default/crouch\", \"+duck\", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"tduck\", \"touch_default/tduck\", \";+duck\", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"edit\", \"touch_default/settings\", \"touch_enableedit\", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 );\n\tTouch_AddDefaultButton( \"menu\", \"touch_default/menu\", \"cancelselect\", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );\n\tTouch_AddDefaultButton( \"spray\", \"touch_default/spray\", \"impulse 201\", 0.680000, 0.000000, 0.760000, 0.142222, color, 2, 1, 8 );\n\tTouch_AddDefaultButton( \"voicechat\", \"touch_default/microphone\", \"+voicerecord\", 0.780000, 0.817778, 0.860000, 0.960000, color, 2, 1, 8 );\n\n\tCmd_AddCommand( \"touch_addbutton\", Touch_AddButton_f, \"add native touch button\" );\n\tCmd_AddCommand( \"touch_removebutton\", IN_TouchRemoveButton_f, \"remove native touch button\" );\n\tCmd_AddRestrictedCommand( \"touch_enableedit\", Touch_EnableEdit_f, \"enable button editing mode\" );\n\tCmd_AddRestrictedCommand( \"touch_disableedit\", Touch_DisableEdit_f, \"disable button editing mode\" );\n\tCmd_AddCommand( \"touch_settexture\", Touch_SetTexture_f, \"change button texture\" );\n\tCmd_AddCommand( \"touch_setcolor\", Touch_SetColor_f, \"change button color\" );\n\tCmd_AddCommand( \"touch_setcommand\", Touch_SetCommand_f, \"change button command\" );\n\tCmd_AddCommand( \"touch_setflags\", Touch_SetFlags_f, \"change button flags (be careful)\" );\n\tCmd_AddCommand( \"touch_show\", Touch_Show_f, \"show button\" );\n\tCmd_AddCommand( \"touch_hide\", Touch_Hide_f, \"hide button\" );\n\tCmd_AddRestrictedCommand( \"touch_list\", Touch_ListButtons_f, \"list buttons\" );\n\tCmd_AddRestrictedCommand( \"touch_removeall\", Touch_RemoveAll_f, \"remove all buttons\" );\n\tCmd_AddRestrictedCommand( \"touch_loaddefaults\", Touch_LoadDefaults_f, \"generate config from defaults\" );\n\tCmd_AddRestrictedCommand( \"touch_roundall\", Touch_RoundAll_f, \"round all buttons coordinates to grid\" );\n\tCmd_AddRestrictedCommand( \"touch_exportconfig\", Touch_ExportConfig_f, \"export config keeping aspect ratio\" );\n\tCmd_AddRestrictedCommand( \"touch_set_stroke\", Touch_Stroke_f, \"set global stroke width and color\" );\n\tCmd_AddRestrictedCommand( \"touch_setclientonly\", Touch_SetClientOnly_f, \"when 1, only client buttons are shown\" );\n\tCmd_AddRestrictedCommand( \"touch_reloadconfig\", Touch_ReloadConfig_f, \"load config, not saving changes\" );\n\tCmd_AddRestrictedCommand( \"touch_writeconfig\", Touch_WriteConfig, \"save current config\" );\n\tCmd_AddRestrictedCommand( \"touch_deleteprofile\", Touch_DeleteProfile_f, \"delete profile by name\" );\n\tCmd_AddRestrictedCommand( \"touch_generate_code\", Touch_GenerateCode_f, \"create code sample for mobility API\" );\n\tCmd_AddCommand( \"touch_fade\", Touch_Fade_f, \"start fade animation for selected buttons\" );\n\tCmd_AddRestrictedCommand( \"touch_toggleselection\", Touch_ToggleSelection_f, \"toggle vidibility on selected button in editor\" );\n\tCmd_AddRestrictedCommand( \"touch_aspectratio\", Touch_ConfigAspectRatio_f, \"set current aspect ratio\" );\n\n\t// not saved, just runtime state for scripting\n\tCvar_RegisterVariable( &touch_in_menu );\n\n\t// sensitivity configuration\n\tCvar_RegisterVariable( &touch_forwardzone );\n\tCvar_RegisterVariable( &touch_sidezone );\n\tCvar_RegisterVariable( &touch_pitch );\n\tCvar_RegisterVariable( &touch_yaw );\n\tCvar_RegisterVariable( &touch_nonlinear_look );\n\tCvar_RegisterVariable( &touch_pow_factor );\n\tCvar_RegisterVariable( &touch_pow_mult );\n\tCvar_RegisterVariable( &touch_exp_mult );\n\n\t// touch.cfg\n\tCvar_RegisterVariable( &touch_grid_count );\n\tCvar_RegisterVariable( &touch_grid_enable );\n\tCvar_RegisterVariable( &touch_config_file );\n\tCvar_RegisterVariable( &touch_precise_amount );\n\tCvar_RegisterVariable( &touch_highlight_r );\n\tCvar_RegisterVariable( &touch_highlight_g );\n\tCvar_RegisterVariable( &touch_highlight_b );\n\tCvar_RegisterVariable( &touch_highlight_a );\n\tCvar_RegisterVariable( &touch_dpad_radius );\n\tCvar_RegisterVariable( &touch_joy_radius );\n\tCvar_RegisterVariable( &touch_move_indicator );\n\tCvar_RegisterVariable( &touch_joy_texture );\n\n\t// input devices cvar\n\tCvar_RegisterVariable( &touch_enable );\n\tCvar_RegisterVariable( &touch_emulate );\n\n\ttouch.initialized = true;\n}\n\n//int pfnGetScreenInfo( SCREENINFO *pscrinfo );\nstatic void Touch_InitConfig( void )\n{\n\tif( !touch.initialized || !host.config_executed || touch.config_loaded )\n\t\treturn;\n\n\t/// TODO: hud font\n\t//pfnGetScreenInfo( NULL ); //HACK: update hud screen parameters like iHeight\n\tif( FS_FileExists( touch_config_file.string, true ) )\n\t{\n\t\tCbuf_AddTextf( \"exec \\\"%s\\\"\\n\", touch_config_file.string );\n\t\tCbuf_Execute();\n\t}\n\telse Touch_LoadDefaults_f();\n\n\tTouch_InitEditor();\n\ttouch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_NOMIPMAP );\n\ttouch.whitetexture = R_GetBuiltinTexture( REF_WHITE_TEXTURE );\n\ttouch.configchanged = false;\n\ttouch.config_loaded = true;\n}\n\n/*\n============================================================================\n\n                     TOUCH CONTROLS RENDERING\n\n============================================================================\n*/\n\nstatic qboolean Touch_IsVisible( touch_button_t *button )\n{\n\tif( !FBitSet( button->flags, TOUCH_FL_CLIENT ) && touch.clientonly )\n\t\treturn false; // skip nonclient buttons in clientonly mode\n\n\tif( touch.state >= state_edit )\n\t\treturn true; // draw when editor is open\n\n\tif( FBitSet( button->flags, TOUCH_FL_HIDE ))\n\t\treturn false; // skip hidden\n\n\tif( cl.maxclients == 1 )\n\t{\n\t\tif( FBitSet( button->flags, TOUCH_FL_MP ))\n\t\t\treturn false; // skip multiplayer buttons in singleplayer\n\t}\n\telse\n\t{\n\t\tif( FBitSet( button->flags, TOUCH_FL_SP ))\n\t\t\treturn false; // skip singleplayer(load, save) buttons in multiplayer\n\t}\n\n\treturn true;\n}\n\nstatic void Touch_DrawTexture( float x1, float y1, float x2, float y2, int texture, byte *color )\n{\n\tif( x1 >= x2 || y1 >= y2 )\n\t\treturn;\n\n\tref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );\n\tref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( x1 ), TO_SCRN_Y( y1 ),\n\t\tTO_SCRN_X( x2 - x1 ), TO_SCRN_Y( y2 - y1 ),\n\t\t0, 0, 1, 1, texture );\n}\n\n#define GRID_COUNT_X ((int)touch_grid_count.value )\n#define GRID_COUNT_Y (((int)touch_grid_count.value ) * Touch_AspectRatio( ))\n#define GRID_X ( 1.0f / GRID_COUNT_X )\n#define GRID_Y ( 1.0f / Touch_AspectRatio() / GRID_COUNT_X )\n#define GRID_ROUND_X( x ) ((float)round(( x ) * GRID_COUNT_X ) / GRID_COUNT_X )\n#define GRID_ROUND_Y( x ) ((float)round(( x ) * GRID_COUNT_Y ) / GRID_COUNT_Y )\n\nstatic void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2  )\n{\n\t/// TODO: grid check here\n\tif( *x2 - *x1 < GRID_X * 2 )\n\t\t*x2 = *x1 + GRID_X * 2;\n\n\tif( *y2 - *y1 < GRID_Y * 2)\n\t\t*y2 = *y1 + GRID_Y * 2;\n\n\tif( *x1 < 0 )\n\t{\n\t\t*x2 -= *x1;\n\t\t*x1 = 0;\n\t}\n\n\tif( *y1 < 0 )\n\t{\n\t\t*y2 -= *y1;\n\t\t*y1 = 0;\n\t}\n\n\tif( *y2 > 1 )\n\t{\n\t\t*y1 -= *y2 - 1;\n\t\t*y2 = 1;\n\t}\n\n\tif( *x2 > 1 )\n\t{\n\t\t*x1 -= *x2 - 1;\n\t\t*x2 = 1;\n\t}\n\n\tif( touch_grid_enable.value )\n\t{\n\t\t*x1 = GRID_ROUND_X( *x1 );\n\t\t*x2 = GRID_ROUND_X( *x2 );\n\t\t*y1 = GRID_ROUND_Y( *y1 );\n\t\t*y2 = GRID_ROUND_Y( *y2 );\n\t}\n}\n\nstatic float Touch_DrawCharacter( float x, float y, int number, float size )\n{\n\tfloat s1, s2, t1, t2, width, height;\n\tint w, h;\n\twrect_t *prc;\n\n\tif( !cls.creditsFont.valid )\n\t\treturn 0;\n\n\tnumber &= 255;\n\tnumber = Con_UtfProcessChar( number );\n\n\tif( !number )\n\t\treturn 0;\n\n\tR_GetTextureParms( &w, &h, cls.creditsFont.hFontTexture );\n\tprc = &cls.creditsFont.fontRc[number];\n\n\ts1 = prc->left / (float)w;\n\tt1 = prc->top / (float)h;\n\ts2 = prc->right / (float)w;\n\tt2 = prc->bottom / (float)h;\n\n\twidth = ( prc->right - prc->left ) / 1024.0f * size;\n\theight = ( prc->bottom - prc->top ) / 1024.0f * size;\n\n\tref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( x ), TO_SCRN_Y( y ), TO_SCRN_X( width ), TO_SCRN_X( height ),\n\t\ts1, t1, s2, t2, cls.creditsFont.hFontTexture );\n\n\treturn width;\n}\n\nstatic float Touch_DrawText( float x1, float y1, float x2, float y2, const char *s, byte *color, float size )\n{\n\tfloat x = x1;\n\tfloat maxy = y2;\n\tfloat maxx;\n\tfloat alpha = color[3] / 255.0f;\n\n\tif( x2 )\n\t\tmaxx = x2 - cls.creditsFont.charWidths['M'] / 1024.0f * size;\n\telse\n\t\tmaxx = 1;\n\n\tif( !cls.creditsFont.valid )\n\t\treturn GRID_X * 2;\n\n\tCon_UtfProcessChar( 0 );\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );\n\n\t// text is additive and alpha does not work\n\tref.dllFuncs.Color4ub( color[0] * alpha, color[1] * alpha, color[2] * alpha, 255 );\n\n\twhile( *s )\n\t{\n\t\twhile( *s && ( *s != '\\n' ) && ( *s != ';' ) && ( x1 < maxx ))\n\t\t\tx1 += Touch_DrawCharacter( x1, y1, *s++, size );\n\t\ty1 += cls.creditsFont.charHeight / 1024.f * size / Touch_AspectRatio();\n\n\t\tif( y1 >= maxy )\n\t\t\tbreak;\n\n\t\tif( *s == '\\n' || *s == ';' )\n\t\t\ts++;\n\t\tx1 = x;\n\t}\n\treturn x1;\n}\n\nstatic void Touch_DrawButtons( touchbuttonlist_t *list )\n{\n\ttouch_button_t *b;\n\n\tfor( b = list->first; b; b = b->next )\n\t{\n\t\tif( Touch_IsVisible( b ))\n\t\t{\n\t\t\trgba_t color;\n\n\t\t\tVector4Copy( b->color, color );\n\n\t\t\tif( b->fadespeed )\n\t\t\t{\n\t\t\t\tb->fade += b->fadespeed * host.frametime;\n\t\t\t\tb->fade = bound( 0, b->fade, 1 );\n\t\t\t\tif( b->fade == 0 || b->fade == 1 )\n\t\t\t\t\tb->fadespeed = 0;\n\n\t\t\t\tif(( b->fade >= b->fadeend && b->fadespeed > 0 ) || ( b->fade <= b->fadeend && b->fadespeed < 0 ))\n\t\t\t\t{\n\t\t\t\t\tb->fadespeed = 0;\n\t\t\t\t\tb->fade = b->fadeend;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( b->finger != -1 && !FBitSet( b->flags, TOUCH_FL_CLIENT ) )\n\t\t\t{\n\t\t\t\tcolor[0] = bound( 0, color[0] * touch_highlight_r.value, 255 );\n\t\t\t\tcolor[1] = bound( 0, color[1] * touch_highlight_g.value, 255 );\n\t\t\t\tcolor[2] = bound( 0, color[2] * touch_highlight_b.value, 255 );\n\t\t\t\tcolor[3] = bound( 0, color[3] * touch_highlight_a.value, 255 );\n\t\t\t}\n\n\t\t\tcolor[3] *= b->fade;\n\n\t\t\tif( b->texture[0] == '#' )\n\t\t\t{\n\t\t\t\tTouch_DrawText(\n\t\t\t\t\ttouch.swidth / (float)refState.width + b->x1,\n\t\t\t\t\ttouch.swidth / (float)refState.height + b->y1,\n\t\t\t\t\tb->x2, b->y2, b->texture + 1, color, b->aspect ? b->aspect : 1 );\n\t\t\t}\n\t\t\telse if( b->texture[0] )\n\t\t\t{\n\t\t\t\tif( b->gl_texturenum == -1 )\n\t\t\t\t\tb->gl_texturenum = ref.dllFuncs.GL_LoadTexture( b->texture, NULL, 0, TF_IMAGE );\n\n\t\t\t\tif( FBitSet( b->flags, TOUCH_FL_DRAW_ADDITIVE ))\n\t\t\t\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );\n\t\t\t\telse\n\t\t\t\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\n\t\t\t\tTouch_DrawTexture( b->x1, b->y1, b->x2, b->y2, b->gl_texturenum, color );\n\t\t\t}\n\n\t\t\tif( FBitSet( b->flags, TOUCH_FL_STROKE ))\n\t\t\t{\n\t\t\t\trgba_t scolor;\n\t\t\t\tconst float x1_ = TO_SCRN_X( b->x1 );\n\t\t\t\tconst float y1_ = TO_SCRN_Y( b->y1 );\n\t\t\t\tconst float x2_ = TO_SCRN_X( b->x2 );\n\t\t\t\tconst float y2_ = TO_SCRN_Y( b->y2 );\n\t\t\t\tconst float swidth = touch.swidth;\n\n\t\t\t\tVector4Copy( touch.scolor, scolor );\n\t\t\t\tscolor[3] *= b->fade;\n\n\t\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture,\n\t\t\t\t\tx1_, y1_,\n\t\t\t\t\tswidth, y2_ - y1_ - swidth,\n\t\t\t\t\tscolor[0], scolor[1], scolor[2], scolor[3] );\n\n\t\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture,\n\t\t\t\t\tx2_ - swidth, y1_ + swidth,\n\t\t\t\t\tswidth, y2_ - y1_ - swidth,\n\t\t\t\t\tscolor[0], scolor[1], scolor[2], scolor[3] );\n\n\t\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture,\n\t\t\t\t\tx1_ + swidth, y1_,\n\t\t\t\t\tx2_ - x1_ - swidth, swidth,\n\t\t\t\t\tscolor[0], scolor[1], scolor[2], scolor[3] );\n\n\t\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture,\n\t\t\t\t\tx1_, y2_ - swidth,\n\t\t\t\t\tx2_ - x1_ - swidth, swidth,\n\t\t\t\t\tscolor[0], scolor[1], scolor[2], scolor[3] );\n\t\t\t}\n\t\t}\n\n\t\tif( touch.state >= state_edit && !FBitSet( b->flags, TOUCH_FL_NOEDIT ))\n\t\t{\n\t\t\trgba_t color;\n\n\t\t\tif( !FBitSet( b->flags, TOUCH_FL_HIDE ))\n\t\t\t\tMakeRGBA( color, 255, 255, 0, 32 );\n\t\t\telse\n\t\t\t\tMakeRGBA( color, 128, 128, 128, 128 );\n\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture,\n\t\t\t\tTO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ),\n\t\t\t\tTO_SCRN_X( b->x2 - b->x1 ), TO_SCRN_Y( b->y2 - b->y1 ), color[0], color[1], color[2], color[3] );\n\n\t\t\tMakeRGBA( color, 255, 255, 127, 255 );\n\t\t\tCon_DrawString( TO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ), b->name, color );\n\t\t}\n\t}\n\n}\n\nvoid Touch_Draw( void )\n{\n\tif( !touch.initialized || ( !touch_enable.value && !touch.clientonly ))\n\t\treturn;\n\n\tif( cls.key_dest != key_game && !touch_in_menu.value )\n\t\treturn;\n\n\tTouch_InitConfig();\n\n\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\n\tif( touch.state >= state_edit && touch_grid_enable.value )\n\t{\n\t\tfloat x;\n\n\t\tif( touch_in_menu.value )\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 32, 32, 32, 255 );\n\t\telse\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 0, 0, 0, 112 );\n\n\t\tfor( x = 0.0f; x < 1.0f; x += GRID_X )\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( x ), 0, 1, TO_SCRN_Y( 1 ), 0, 224, 224, 112 );\n\n\t\tfor( x = 0.0f; x < 1.0f; x += GRID_Y )\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, 0, TO_SCRN_Y( x ), TO_SCRN_X( 1 ), 1, 0, 224, 224, 112 );\n\t}\n\n\tTouch_DrawButtons( &touch.list_user );\n\n\tif( touch.state >= state_edit )\n\t{\n\t\tif( touch.edit )\n\t\t{\n\t\t\tfloat x1 = touch.edit->x1, y1 = touch.edit->y1, x2 = touch.edit->x2, y2 = touch.edit->y2;\n\t\t\tIN_TouchCheckCoords( &x1, &y1, &x2, &y2 );\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( x1 ), TO_SCRN_Y( y1 ),\n\t\t\t\tTO_SCRN_X( x2 - x1 ), TO_SCRN_Y( y2 - y1 ), 0, 255, 0, 32 );\n\t\t}\n\n\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, TO_SCRN_X( GRID_X ), TO_SCRN_Y( GRID_Y ), 255, 255, 255, 64 );\n\n\t\tif( touch.showeditbuttons )\n\t\t\tTouch_DrawButtons( &touch.list_edit );\n\n\t\t/// TODO: move to mainui\n\t\tif( touch.selection )\n\t\t{\n\t\t\tchar text[MAX_VA_STRING];\n\t\t\trgba_t color = { 255, 255, 255, 255 };\n\t\t\tconst touch_button_t *b = touch.selection;\n\n\t\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ),\n\t\t\t\tTO_SCRN_X( b->x2 - b->x1 ), TO_SCRN_Y( b->y2 - b->y1 ), 255, 0, 0, 64 );\n\n\t\t\tQ_snprintf( text, sizeof( text ), \"Selection:\\nName: %s\\nTexture: %s\\nCommand: %s\", b->name, b->texture, b->command );\n\n\t\t\tCon_DrawString( 0, TO_SCRN_Y( GRID_Y * 11 ), text, color );\n\t\t}\n\t}\n\n\tif( touch.move_finger != -1 && touch.move_button && touch_move_indicator.value > 0.0f )\n\t{\n\t\tfloat width, height;\n\t\tfloat size = touch_move_indicator.value;\n\n\t\tif( FBitSet( touch_joy_texture.flags, FCVAR_CHANGED ) )\n\t\t{\n\t\t\tClearBits( touch_joy_texture.flags, FCVAR_CHANGED );\n\t\t\ttouch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_IMAGE );\n\t\t}\n\n\t\tif( touch.move_button->type == touch_move )\n\t\t{\n\t\t\twidth =  touch_sidezone.value;\n\t\t\theight = touch_forwardzone.value;\n\t\t}\n\t\telse\n\t\t{\n\t\t\twidth = (touch.move_button->x2 - touch.move_button->x1)/2;\n\t\t\theight = (touch.move_button->y2 - touch.move_button->y1)/2;\n\t\t}\n\n\t\tref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );\n\t\tref.dllFuncs.Color4ub( 255, 255, 255, 128 );\n\t\tref.dllFuncs.R_DrawStretchPic(\n\t\t\tTO_SCRN_X( touch.move_start_x - GRID_X * size ),\n\t\t\tTO_SCRN_Y( touch.move_start_y - GRID_Y * size ),\n\t\t\tTO_SCRN_X( GRID_X * 2 * size ),\n\t\t\tTO_SCRN_Y( GRID_Y * 2 * size ),\n\t\t\t0, 0, 1, 1, touch.joytexture );\n\t\tref.dllFuncs.Color4ub( 255, 255, 255, 255 );\n\t\tref.dllFuncs.R_DrawStretchPic(\n\t\t\tTO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * size ),\n\t\t\tTO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * size ),\n\t\t\tTO_SCRN_X( GRID_X * 2 * size ),\n\t\t\tTO_SCRN_Y( GRID_Y * 2 * size ),\n\t\t\t0, 0, 1, 1, touch.joytexture );\n\t}\n}\n\n// clear move and selection state\nstatic void IN_TouchEditClear( void )\n{\n\tif( touch.state < state_edit )\n\t\treturn;\n\n\ttouch.state = state_edit;\n\n\tif( touch.edit )\n\t\ttouch.edit->finger = -1;\n\n\ttouch.resize_finger = -1;\n\ttouch.edit = NULL;\n\ttouch.selection = NULL;\n}\n\nstatic void Touch_EditMove( touchEventType type, int fingerID, float x, float y, float dx, float dy )\n{\n\tif( touch.edit->finger == fingerID )\n\t{\n\t\tif( type == event_up ) // shutdown button move\n\t\t{\n\t\t\ttouch_button_t *b = touch.edit;\n\n\t\t\tIN_TouchCheckCoords( &b->x1, &b->y1, &b->x2, &b->y2 );\n\t\t\tIN_TouchEditClear();\n\n\t\t\ttouch.selection = b;\n\n\t\t\t// update \"hide\" editor button\n\t\t\ttouch.hidebutton->gl_texturenum = -1;\n\t\t\tClearBits( touch.hidebutton->flags, TOUCH_FL_HIDE );\n\n\t\t\tif( FBitSet( b->flags, TOUCH_FL_HIDE ))\n\t\t\t\tQ_strncpy( touch.hidebutton->texture, \"touch_default/edit_show\", sizeof( touch.hidebutton->texture ));\n\t\t\telse\n\t\t\t\tQ_strncpy( touch.hidebutton->texture, \"touch_default/edit_hide\", sizeof( touch.hidebutton->texture ));\n\t\t}\n\t\telse if( type == event_motion ) // shutdown button move\n\t\t{\n\t\t\ttouch.edit->y1 += dy;\n\t\t\ttouch.edit->y2 += dy;\n\t\t\ttouch.edit->x1 += dx;\n\t\t\ttouch.edit->x2 += dx;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( type == event_down ) // enable resizing\n\t\t{\n\t\t\tif( touch.resize_finger == -1 )\n\t\t\t\ttouch.resize_finger = fingerID;\n\t\t}\n\t\telse if( type == event_up ) // disable resizing\n\t\t{\n\t\t\tif( touch.resize_finger == fingerID )\n\t\t\t\ttouch.resize_finger = -1;\n\t\t}\n\t\telse if( type == event_motion ) // perform resizing\n\t\t{\n\t\t\tif( touch.resize_finger == fingerID )\n\t\t\t{\n\t\t\t\ttouch.edit->y2 += dy;\n\t\t\t\ttouch.edit->x2 += dx;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void Touch_Motion( int fingerID, float x, float y, float dx, float dy )\n{\n\t// process wheel\n\tif( fingerID == touch.wheel_finger )\n\t{\n\t\ttouch.wheel_amount += touch.wheel_horizontal ? dx : dy;\n\n\t\tif( touch.wheel_amount > 0.1f )\n\t\t{\n\t\t\tCbuf_AddText( touch.wheel_down );\n\t\t\ttouch.wheel_count++;\n\t\t\ttouch.wheel_amount = 0;\n\t\t}\n\n\t\tif( touch.wheel_amount < -0.1f )\n\t\t{\n\t\t\tCbuf_AddText( touch.wheel_up );\n\t\t\ttouch.wheel_count++;\n\t\t\ttouch.wheel_amount = 0;\n\t\t}\n\n\t\treturn;\n\t}\n\n\t// walk\n\tif( fingerID == touch.move_finger )\n\t{\n\t\tconst touch_button_t *b = touch.move_button;\n\n\t\tif( !b || b->type == touch_move )\n\t\t{\n\t\t\t// check bounds\n\t\t\tif( touch_forwardzone.value <= 0 )\n\t\t\t\tCvar_DirectSet( &touch_forwardzone, \"0.5\" );\n\n\t\t\tif( touch_sidezone.value <= 0 )\n\t\t\t\tCvar_DirectSet( &touch_sidezone, \"0.3\" );\n\n\t\t\t// move relative to touch start\n\t\t\ttouch.forward = ( touch.move_start_y - y ) / touch_forwardzone.value;\n\t\t\ttouch.side = ( x - touch.move_start_x ) / touch_sidezone.value;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// move relative to joy center\n\t\t\ttouch.forward = (( b->y2 + b->y1 ) - y * 2 ) / ( b->y2 - b->y1 );\n\t\t\ttouch.side = ( x * 2 - ( b->x2 + b->x1 )) / ( b->x2 - b->x1 );\n\n\t\t\tif( b->type == touch_joy )\n\t\t\t{\n\t\t\t\ttouch.forward *= touch_joy_radius.value;\n\t\t\t\ttouch.side *= touch_joy_radius.value;\n\t\t\t}\n\t\t\telse if( b->type == touch_dpad )\n\t\t\t{\n\t\t\t\t// like joy, but without acceleration. useful for bhop\n\t\t\t\ttouch.forward = round( touch.forward * touch_dpad_radius.value );\n\t\t\t\ttouch.side = round( touch.side * touch_dpad_radius.value );\n\t\t\t}\n\t\t}\n\n\t\ttouch.forward = bound( -1, touch.forward, 1 );\n\t\ttouch.side = bound( -1, touch.side, 1 );\n\t}\n\n\t// process look\n\tif( fingerID == touch.look_finger )\n\t{\n\t\tif( touch.precision )\n\t\t{\n\t\t\tdx *= touch_precise_amount.value;\n\t\t\tdy *= touch_precise_amount.value;\n\t\t}\n\n\t\tif( touch_nonlinear_look.value )\n\t\t{\n\t\t\tfloat dabs, dcos, dsin;\n\n\t\t\t// save angle, modify only velocity\n\t\t\tdabs = sqrt( dx * dx + dy * dy );\n\n\t\t\tif( dabs < 0.000001f )\n\t\t\t\treturn; // no motion, avoid division by zero\n\n\t\t\tdcos = dx / dabs;\n\t\t\tdsin = dy / dabs;\n\n\t\t\tif( touch_exp_mult.value > 1 )\n\t\t\t\tdabs = ( exp( dabs * touch_exp_mult.value ) - 1 ) / touch_exp_mult.value;\n\n\t\t\tif( touch_pow_mult.value > 1 && touch_pow_factor.value > 1 )\n\t\t\t\tdabs = pow( dabs * touch_pow_mult.value, touch_pow_factor.value ) / touch_pow_mult.value;\n\n\t\t\tdx = dabs * dcos;\n\t\t\tdy = dabs * dsin;\n\t\t}\n\n\t\t// prevent breaking engine/client with bad values\n\t\tif( IS_NAN( dx ) || IS_NAN( dy ))\n\t\t\treturn;\n\n\t\t// accumulate\n\t\ttouch.yaw -= dx * touch_yaw.value;\n\t\ttouch.pitch += dy * touch_pitch.value;\n\t}\n}\n\nstatic qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y )\n{\n\ttouch_button_t *button;\n\tqboolean result = false;\n\n\tif( type != event_down && type != event_up )\n\t\treturn false;\n\n\t// run from end(front) to start(back)\n\tfor( button = list->last; button; button = button->prev )\n\t{\n\t\t// skip invisible buttons\n\t\tif( !Touch_IsVisible( button ))\n\t\t\tcontinue;\n\n\t\tif( type == event_down )\n\t\t{\n\t\t\t// button bounds check\n\t\t\tif( x < button->x1 || x > button->x2 || y < button->y1 || y > button->y2 )\n\t\t\t\tcontinue;\n\n\t\t\tbutton->finger = fingerID;\n\n\t\t\tif( button->type == touch_command )\n\t\t\t{\n\t\t\t\tchar command[256];\n\n\t\t\t\t// command down: just execute command\n\t\t\t\tQ_snprintf( command, sizeof( command ), \"%s\\n\", button->command );\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\t\t\tCbuf_AddFilteredText( command );\n\t\t\t\telse Cbuf_AddText( command );\n\n\t\t\t\t// increase precision\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_PRECISION ))\n\t\t\t\t\ttouch.precision = true;\n\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t\telse if( button->type == touch_wheel )\n\t\t\t{\n\t\t\t\tstring command;\n\n\t\t\t\ttouch.wheel_finger = fingerID;\n\t\t\t\ttouch.wheel_amount = touch.wheel_count = 0;\n\n\t\t\t\tCmd_TokenizeString( button->command );\n\n\t\t\t\ttouch.wheel_horizontal = !Q_strcmp( Cmd_Argv( 0 ), \"_hwheel\" );\n\t\t\t\tQ_snprintf( touch.wheel_up, sizeof( touch.wheel_up ), \"%s\\n\", Cmd_Argv( 1 ));\n\t\t\t\tQ_snprintf( touch.wheel_down, sizeof( touch.wheel_down ), \"%s\\n\", Cmd_Argv( 2 ));\n\t\t\t\tQ_snprintf( touch.wheel_end, sizeof( touch.wheel_end ), \"%s\\n\", Cmd_Argv( 3 ));\n\t\t\t\tif( Q_snprintf( command, sizeof( command ), \"%s\\n\", Cmd_Argv( 4 )) > 1 )\n\t\t\t\t{\n\t\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\t\t\t\tCbuf_AddFilteredText( command );\n\t\t\t\t\telse Cbuf_AddText( command );\n\t\t\t\t\ttouch.wheel_count++;\n\t\t\t\t}\n\n\t\t\t\t// increase precision\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_PRECISION ))\n\t\t\t\t\ttouch.precision = true;\n\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t\t// initialize motion when player touched motion zone\n\t\t\telse if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad )\n\t\t\t{\n\t\t\t\tif( touch.move_finger !=-1 )\n\t\t\t\t{\n\t\t\t\t\t// prevent initializing move while already moving\n\t\t\t\t\t// revert finger switch, leave first finger\n\t\t\t\t\tbutton->finger = touch.move_finger;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tresult = true;\n\n\t\t\t\tif( touch.look_finger == fingerID )\n\t\t\t\t{\n\t\t\t\t\ttouch_button_t *newbutton;\n\n\t\t\t\t\t// this is an error, try recover\n\t\t\t\t\ttouch.move_finger = touch.look_finger = -1;\n\n\t\t\t\t\t// player touched touch_move with enabled look mode\n\t\t\t\t\t// and same finger id. release all move triggers\n\t\t\t\t\tfor( newbutton = list->first; newbutton; newbutton = newbutton->next )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( newbutton->type == touch_move || newbutton->type == touch_look )\n\t\t\t\t\t\t\tnewbutton->finger = -1;\n\t\t\t\t\t}\n\n\t\t\t\t\tCon_DPrintf( S_ERROR \"Touch: touch_move on look finger %d!\\n\", fingerID );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// initialize move mode\n\t\t\t\ttouch.move_finger = fingerID;\n\t\t\t\ttouch.move_button = button;\n\n\t\t\t\tif( button->type == touch_move )\n\t\t\t\t{\n\t\t\t\t\t// initial position is first touch\n\t\t\t\t\ttouch.move_start_x = x;\n\t\t\t\t\ttouch.move_start_y = y;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// initial position is button center\n\t\t\t\t\ttouch.move_start_y = ( button->y2 + button->y1 ) / 2;\n\t\t\t\t\ttouch.move_start_x = ( button->x2 + button->x1 ) / 2;\n\n\t\t\t\t\t// start move instanly\n\t\t\t\t\ttouch.forward = (( button->y2 + button->y1 ) - y * 2 ) / ( button->y2 - button->y1 );\n\t\t\t\t\ttouch.side = (x * 2 - ( button->x2 + button->x1 )) / ( button->x2 - button->x1 );\n\n\t\t\t\t\t// same as joy, but round\n\t\t\t\t\tif( button->type == touch_dpad )\n\t\t\t\t\t{\n\t\t\t\t\t\ttouch.forward = round( touch.forward );\n\t\t\t\t\t\ttouch.side = round( touch.side );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// initialize look\n\t\t\telse if( button->type == touch_look )\n\t\t\t{\n\t\t\t\tif( touch.look_finger !=-1 )\n\t\t\t\t{\n\t\t\t\t\t// prevent initializing look while already looking\n\t\t\t\t\t// revert finger switch, leave first finger\n\t\t\t\t\tbutton->finger = touch.look_finger;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tresult = true;\n\n\t\t\t\tif( touch.move_finger == fingerID )\n\t\t\t\t{\n\t\t\t\t\ttouch_button_t *newbutton;\n\n\t\t\t\t\t// this is an error, try recover\n\t\t\t\t\ttouch.move_finger = touch.look_finger = -1;\n\n\t\t\t\t\t// player touched touch_move with enabled look mode\n\t\t\t\t\t// and same finger id. release all move triggers\n\t\t\t\t\tfor( newbutton = list->first; newbutton; newbutton = newbutton->next )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( newbutton->type == touch_move || newbutton->type == touch_look )\n\t\t\t\t\t\t\tnewbutton->finger = -1;\n\t\t\t\t\t}\n\n\t\t\t\t\tCon_Printf( S_ERROR \"touch: touch_look on move finger %d!\\n\", fingerID );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttouch.look_finger = fingerID;\n\t\t\t}\n\t\t}\n\t\telse if( type == event_up )\n\t\t{\n\t\t\t// no bounds check here.\n\t\t\t// button released when finger released\n\t\t\tif( fingerID != button->finger )\n\t\t\t\tcontinue;\n\n\t\t\tbutton->finger = -1;\n\n\t\t\t// handle +command, replace by -command\n\t\t\tif( button->type == touch_command )\n\t\t\t{\n\t\t\t\tif( button->command[0] == '+' )\n\t\t\t\t{\n\t\t\t\t\tchar command[256];\n\n\t\t\t\t\tQ_snprintf( command, sizeof( command ), \"-%s\\n\", &button->command[1] );\n\t\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\t\t\t\tCbuf_AddFilteredText( command );\n\t\t\t\t\telse Cbuf_AddText( command );\n\t\t\t\t}\n\n\t\t\t\t// disable precision mode\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_PRECISION ))\n\t\t\t\t\ttouch.precision = false;\n\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t\t// handle wheel end\n\t\t\telse if( button->type == touch_wheel )\n\t\t\t{\n\t\t\t\tif( touch.wheel_count )\n\t\t\t\t{\n\t\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))\n\t\t\t\t\t\tCbuf_AddFilteredText( touch.wheel_end );\n\t\t\t\t\telse Cbuf_AddText( touch.wheel_end );\n\t\t\t\t}\n\n\t\t\t\t// disable precision mode\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_PRECISION ))\n\t\t\t\t\ttouch.precision = false;\n\n\t\t\t\ttouch.wheel_finger = -1;\n\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t\t// release motion buttons\n\t\t\telse if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad )\n\t\t\t{\n\t\t\t\ttouch.move_finger = -1;\n\t\t\t\ttouch.forward = touch.side = 0;\n\t\t\t\ttouch.move_button = NULL;\n\t\t\t}\n\t\t\t// release look buttons\n\t\t\telse if( button->type == touch_look )\n\t\t\t\ttouch.look_finger = -1;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nstatic qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y )\n{\n\ttouch_button_t *button;\n\n\t// edit buttons are on y1\n\tif( type == event_down )\n\t{\n\t\tif( x < GRID_X && y < GRID_Y )\n\t\t{\n\t\t\ttouch.showeditbuttons = !touch.showeditbuttons;\n\t\t\treturn true;\n\t\t}\n\n\t\tif( touch.showeditbuttons && Touch_ButtonPress( &touch.list_edit, type, fingerID, x, y ))\n\t\t\treturn true;\n\t}\n\n\t// run from end(front) to start(back)\n\tfor( button = touch.list_user.last; button; button = button->prev )\n\t{\n\t\tif( type == event_down )\n\t\t{\n\t\t\tif( x > button->x1 && x < button->x2 && y > button->y1 && y < button->y2 )\n\t\t\t{\n\t\t\t\tbutton->finger = fingerID;\n\n\t\t\t\t// do not edit NOEDIT buttons\n\t\t\t\tif( FBitSet( button->flags, TOUCH_FL_NOEDIT ))\n\t\t\t\t\tcontinue;\n\n\t\t\t\ttouch.edit = button;\n\t\t\t\ttouch.selection = NULL;\n\n\t\t\t\t// make button last to bring it up\n\t\t\t\tif( button->next && button->type == touch_command )\n\t\t\t\t{\n\t\t\t\t\tif( button->prev )\n\t\t\t\t\t\tbutton->prev->next = button->next;\n\t\t\t\t\telse\n\t\t\t\t\t\ttouch.list_user.first = button->next;\n\n\t\t\t\t\tbutton->next->prev = button->prev;\n\t\t\t\t\ttouch.list_user.last->next = button;\n\t\t\t\t\tbutton->prev = touch.list_user.last;\n\t\t\t\t\tbutton->next = NULL;\n\t\t\t\t\ttouch.list_user.last = button;\n\t\t\t\t}\n\t\t\t\ttouch.state = state_edit_move;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tif( type == event_up )\n\t\t{\n\t\t\tif( fingerID == button->finger )\n\t\t\t\tbutton->finger = -1;\n\t\t}\n\t}\n\n\tif( type == event_down )\n\t{\n\t\ttouch.selection = NULL;\n\t\ttouch.hidebutton->flags |= TOUCH_FL_HIDE;\n\t}\n\n\treturn false;\n}\n\nstatic int Touch_ControlsEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy )\n{\n\tif( touch.state == state_edit_move )\n\t{\n\t\tTouch_EditMove( type, fingerID, x, y, dx, dy );\n\t\treturn true;\n\t}\n\n\tif( touch.state == state_edit && Touch_ButtonEdit( type, fingerID, x, y ))\n\t\treturn true;\n\tif( Touch_ButtonPress( &touch.list_user, type, fingerID, x, y ))\n\t\treturn true;\n\tif( type == event_motion )\n\t\tTouch_Motion( fingerID, x, y, dx, dy );\n\treturn true;\n}\n\nint IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy )\n{\n//\tCon_Printf(\"%f %f\\n\", TO_SCRN_X(x), TO_SCRN_Y(y));\n\t// simulate menu mouse click\n\tif( cls.key_dest != key_game && !touch_in_menu.value )\n\t{\n\t\ttouch.move_finger = touch.resize_finger = touch.look_finger = touch.wheel_finger = -1;\n\t\t// Hack for keyboard, hope it help\n\t\t// a1ba: this is absolutely horrible\n\t\tif( cls.key_dest == key_console || cls.key_dest == key_message )\n\t\t{\n\t\t\tstatic float x1 = 0.0f;\n\t\t\tx1 += dx;\n\n\t\t\tif( type == event_up ) // don't show keyboard on every tap\n\t\t\t{\n\t\t\t\tKey_EnableTextInput( true, true );\n\t\t\t\tx1 = 0.0f;\n\t\t\t}\n\n\t\t\tif( cls.key_dest == key_console )\n\t\t\t{\n\t\t\t\tstatic float y1 = 0;\n\t\t\t\ty1 += dy;\n\t\t\t\tif( dy > 0.4f )\n\t\t\t\t\tCon_Bottom();\n\n\t\t\t\tif( y1 > 0.01f )\n\t\t\t\t{\n\t\t\t\t\tCon_PageUp( 1 );\n\t\t\t\t\ty1 = 0;\n\t\t\t\t}\n\t\t\t\tif( y1 < -0.01f )\n\t\t\t\t{\n\t\t\t\t\tCon_PageDown( 1 );\n\t\t\t\t\ty1 = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// exit of console area\n\t\t\tif( type == event_down && x < 0.1f && y > 0.9f )\n\t\t\t{\n\t\t\t\tif( cls.key_dest == key_console )\n\t\t\t\t\tKey_Console( K_ESCAPE );\n\t\t\t\telse\n\t\t\t\t\tKey_Message( K_ESCAPE );\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\t// swipe from edge to exit console/chat\n\t\t\tif(( x > 0.7f && x1 < -0.1f ) || ( x < 0.3f && x1 > 0.1f ))\n\t\t\t{\n\t\t\t\tif( cls.key_dest == key_console )\n\t\t\t\t\tKey_Console( K_ESCAPE );\n\t\t\t\telse\n\t\t\t\t\tKey_Message( K_ESCAPE );\n\t\t\t\tx1 = 0.0f;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\tUI_MouseMove( x * refState.width, y * refState.height );\n\n\t\t//MsgDev( D_NOTE, \"touch %d %d\\n\", TO_SCRN_X(x), TO_SCRN_Y(y) );\n\t\tif( type == event_down )\n\t\t\tKey_Event( K_MOUSE1, true );\n\n\t\tif( type == event_up )\n\t\t\tKey_Event( K_MOUSE1, false );\n\n\t\treturn 0;\n\t}\n\n\n\tif( VGui_IsActive() )\n\t{\n\t\tVGui_MouseMove( x * refState.width, y * refState.height );\n\n\t\tswitch( type )\n\t\t{\n\t\tcase event_down:\n\t\t\tVGui_MouseEvent( K_MOUSE1, 1 );\n\t\t\tbreak;\n\t\tcase event_up:\n\t\t\tVGui_MouseEvent( K_MOUSE1, 0 );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !touch.initialized || ( !touch_enable.value && !touch.clientonly ))\n\t\treturn false;\n\n\ty *= (float)refState.height / refState.width / Touch_AspectRatio();\n\n\tif( clgame.dllFuncs.pfnTouchEvent && clgame.dllFuncs.pfnTouchEvent( type, fingerID, x, y, dx, dy ) )\n\t\treturn true;\n\n\treturn Touch_ControlsEvent( type, fingerID, x, y, dx, dy );\n}\n\nvoid Touch_GetMove( float *forward, float *side, float *yaw, float *pitch )\n{\n\t*forward += touch.forward;\n\t*side += touch.side;\n\t*yaw += touch.yaw;\n\t*pitch += touch.pitch;\n\ttouch.yaw = touch.pitch = 0;\n}\n\nvoid Touch_KeyEvent( int key, int down )\n{\n\tstatic float lx, ly;\n\tstatic int kidNamedFinger = -1;\n\ttouchEventType event;\n\tfloat x, y;\n\tint finger, xi, yi;\n\n\tif( !Touch_WantVisibleCursor( ))\n\t\treturn;\n\n\tif( !key )\n\t{\n\t\tif( kidNamedFinger < 0 )\n\t\t\treturn;\n\n\t\tfinger = kidNamedFinger;\n\t\tevent  = event_motion;\n\t}\n\telse\n\t{\n\t\tfinger = key == K_MOUSE1 ? 0 : 1;\n\t\tif( down )\n\t\t{\n\t\t\tevent = event_down;\n\t\t\tkidNamedFinger = finger;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tevent = event_up;\n\t\t\tkidNamedFinger = -1;\n\t\t}\n\t}\n\n\tPlatform_GetMousePos( &xi, &yi );\n\n\tx = xi / (float)refState.width;\n\ty = yi / (float)refState.height;\n\n\t// Con_DPrintf( \"event %d %.2f %.2f %.2f %.2f\\n\", event, x, y, x - lx, y - ly );\n\n\tIN_TouchEvent( event, finger, x, y, x - lx, y - ly );\n\n\tlx = x;\n\tly = y;\n}\n\nqboolean Touch_WantVisibleCursor( void )\n{\n\treturn ( touch_enable.value && touch_emulate.value ) || touch.clientonly || touch_in_menu.value;\n}\n\nvoid Touch_Shutdown( void )\n{\n\tif( !touch.initialized )\n\t\treturn;\n\tTouch_RemoveAll_f();\n\tCmd_RemoveCommand( \"touch_addbutton\" );\n\tCmd_RemoveCommand( \"touch_removebutton\" );\n\tCmd_RemoveCommand( \"touch_enableedit\" );\n\tCmd_RemoveCommand( \"touch_disableedit\" );\n\tCmd_RemoveCommand( \"touch_settexture\" );\n\tCmd_RemoveCommand( \"touch_setcolor\" );\n\tCmd_RemoveCommand( \"touch_setcommand\" );\n\tCmd_RemoveCommand( \"touch_setflags\" );\n\tCmd_RemoveCommand( \"touch_show\" );\n\tCmd_RemoveCommand( \"touch_hide\" );\n\tCmd_RemoveCommand( \"touch_list\" );\n\tCmd_RemoveCommand( \"touch_removeall\" );\n\tCmd_RemoveCommand( \"touch_loaddefaults\" );\n\tCmd_RemoveCommand( \"touch_roundall\" );\n\tCmd_RemoveCommand( \"touch_exportconfig\" );\n\tCmd_RemoveCommand( \"touch_set_stroke\" );\n\tCmd_RemoveCommand( \"touch_setclientonly\" );\n\tCmd_RemoveCommand( \"touch_reloadconfig\" );\n\tCmd_RemoveCommand( \"touch_writeconfig\" );\n\tCmd_RemoveCommand( \"touch_generate_code\" );\n\n\ttouch.initialized = false;\n\tMem_FreePool( &touch.mempool );\n}\n"
  },
  {
    "path": "engine/client/input.c",
    "content": "/*\ninput.c - win32 input devices\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if XASH_SDL == 2\n#include <SDL.h>\n#elif XASH_SDL == 3\n#include <SDL3/SDL.h>\n#endif\n\n#include \"common.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"cursor_type.h\"\n#include \"platform/platform.h\"\n\nvoid*\t\tin_mousecursor;\nqboolean\tin_mouseactive;\t\t\t\t// false when not focus app\nqboolean\tin_mouseinitialized;\nqboolean\tin_mouse_suspended;\nPOINT\t\tin_lastvalidpos;\nqboolean\tin_mouse_savedpos;\nstatic int in_mstate = 0;\nstatic struct inputstate_s\n{\n\tfloat lastpitch, lastyaw;\n} inputstate;\n\nCVAR_DEFINE_AUTO( m_pitch, \"0.022\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"mouse pitch value\" );\nCVAR_DEFINE_AUTO( m_yaw, \"0.022\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"mouse yaw value\" );\nCVAR_DEFINE_AUTO( m_ignore, DEFAULT_M_IGNORE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"ignore mouse events\" );\nstatic CVAR_DEFINE_AUTO( look_filter, \"0\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"filter look events making it smoother\" );\nstatic CVAR_DEFINE_AUTO( m_rawinput, \"1\", FCVAR_ARCHIVE | FCVAR_FILTERABLE, \"enable mouse raw input\" );\n\nstatic CVAR_DEFINE_AUTO( cl_forwardspeed, \"400\", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, \"Default forward move speed\" );\nstatic CVAR_DEFINE_AUTO( cl_backspeed, \"400\", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, \"Default back move speed\"  );\nstatic CVAR_DEFINE_AUTO( cl_sidespeed, \"400\", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, \"Default side move speed\"  );\n\nstatic CVAR_DEFINE_AUTO( m_grab_debug, \"0\", FCVAR_PRIVILEGED, \"show debug messages on mouse state change\" );\n\n/*\n================\nIN_CollectInputDevices\n\nReturns a bit mask representing connected devices or, at least, enabled\n================\n*/\nuint IN_CollectInputDevices( void )\n{\n\tuint ret = 0;\n\n\tif( !m_ignore.value ) // no way to check is mouse connected, so use cvar only\n\t\tret |= INPUT_DEVICE_MOUSE;\n\n\tif( touch_enable.value )\n\t\tret |= INPUT_DEVICE_TOUCH;\n\n\tif( Joy_IsActive() ) // connected or enabled\n\t\tret |= INPUT_DEVICE_JOYSTICK;\n\n\tCon_Reportf( \"Connected devices: %s%s%s%s\\n\",\n\t\tFBitSet( ret, INPUT_DEVICE_MOUSE )    ? \"mouse \" : \"\",\n\t\tFBitSet( ret, INPUT_DEVICE_TOUCH )    ? \"touch \" : \"\",\n\t\tFBitSet( ret, INPUT_DEVICE_JOYSTICK ) ? \"joy \" : \"\",\n\t\tFBitSet( ret, INPUT_DEVICE_VR )       ? \"vr \" : \"\");\n\n\treturn ret;\n}\n\n/*\n=================\nIN_LockInputDevices\n\ntries to lock any possibilty to connect another input device after\nplayer is connected to the server\n=================\n*/\nvoid IN_LockInputDevices( qboolean lock )\n{\n\textern convar_t joy_enable; // private to input system\n\n\tif( lock )\n\t{\n\t\tSetBits( m_ignore.flags, FCVAR_READ_ONLY );\n\t\tSetBits( joy_enable.flags, FCVAR_READ_ONLY );\n\t\tSetBits( touch_enable.flags, FCVAR_READ_ONLY );\n\t}\n\telse\n\t{\n\t\tClearBits( m_ignore.flags, FCVAR_READ_ONLY );\n\t\tClearBits( joy_enable.flags, FCVAR_READ_ONLY );\n\t\tClearBits( touch_enable.flags, FCVAR_READ_ONLY );\n\t}\n}\n\n\n/*\n===========\nIN_StartupMouse\n===========\n*/\nstatic void IN_StartupMouse( void )\n{\n\tCvar_RegisterVariable( &m_ignore );\n\n\tCvar_RegisterVariable( &m_pitch );\n\tCvar_RegisterVariable( &m_yaw );\n\tCvar_RegisterVariable( &look_filter );\n\tCvar_RegisterVariable( &m_rawinput );\n\tCvar_RegisterVariable( &m_grab_debug );\n\n\t// You can use -nomouse argument to prevent using mouse from client\n\t// -noenginemouse will disable all mouse input\n\tif( Sys_CheckParm(  \"-noenginemouse\" )) return;\n\n\tin_mouseinitialized = true;\n}\n\n/*\n===========\nIN_MouseSavePos\n\nSave mouse pos before state change e.g. changelevel\n===========\n*/\nvoid IN_MouseSavePos( void )\n{\n\tif( !in_mouseactive )\n\t\treturn;\n\n\tPlatform_GetMousePos( &in_lastvalidpos.x, &in_lastvalidpos.y );\n\tin_mouse_savedpos = true;\n}\n\n/*\n===========\nIN_MouseRestorePos\n\nRestore right position for background\n===========\n*/\nvoid IN_MouseRestorePos( void )\n{\n\tif( !in_mouse_savedpos )\n\t\treturn;\n\n\tPlatform_SetMousePos( in_lastvalidpos.x, in_lastvalidpos.y );\n\n\tin_mouse_savedpos = false;\n}\n\n/*\n===========\nIN_ToggleClientMouse\n\nCalled when key_dest is changed\n===========\n*/\nvoid IN_ToggleClientMouse( int newstate, int oldstate )\n{\n\tif( newstate == oldstate )\n\t\treturn;\n\n\t// since SetCursorType controls cursor visibility\n\t// execute it first, and then check mouse grab state\n\tif( newstate == key_menu || newstate == key_console )\n\t{\n\t\tPlatform_SetCursorType( dc_arrow );\n\n#if XASH_USE_EVDEV\n\t\tEvdev_SetGrab( false );\n#endif\n\t}\n\telse\n\t{\n\t\tPlatform_SetCursorType( dc_none );\n\n#if XASH_USE_EVDEV\n\t\tEvdev_SetGrab( true );\n#endif\n\t}\n\n\t// don't leave the user without cursor if they enabled m_ignore\n\tif( m_ignore.value )\n\t\treturn;\n\n\tif( oldstate == key_game )\n\t{\n\t\tIN_DeactivateMouse();\n\t}\n\telse if( newstate == key_game )\n\t{\n\t\tIN_ActivateMouse();\n\t}\n}\n\nvoid IN_SetRelativeMouseMode( qboolean set )\n{\n\tstatic qboolean s_bRawInput;\n\tqboolean verbose = m_grab_debug.value ? true : false;\n\n\tif( set && !s_bRawInput )\n\t{\n#if XASH_SDL >= 2\n\t\tSDL_GetRelativeMouseState( NULL, NULL );\n#if XASH_SDL == 2\n\t\tSDL_SetRelativeMouseMode( SDL_TRUE );\n#else // XASH_SDL != 2\n\t\tSDL_SetWindowRelativeMouseMode( host.hWnd, true );\n#endif // XASH_SDL != 2\n#endif // XASH_SDL >= 2\n\t\ts_bRawInput = true;\n\t\tif( verbose )\n\t\t\tCon_Printf( \"%s: true\\n\", __func__ );\n\t}\n\telse if( !set && s_bRawInput )\n\t{\n#if XASH_SDL >= 2\n\t\tSDL_GetRelativeMouseState( NULL, NULL );\n#if XASH_SDL == 2\n\t\tSDL_SetRelativeMouseMode( SDL_FALSE );\n#else // XASH_SDL != 2\n\t\tSDL_SetWindowRelativeMouseMode( host.hWnd, false );\n#endif // XASH_SDL != 2\n#endif // XASH_SDL >= 2\n\n\t\ts_bRawInput = false;\n\t\tif( verbose )\n\t\t\tCon_Printf( \"%s: false\\n\", __func__ );\n\t}\n}\n\nvoid IN_SetMouseGrab( qboolean set )\n{\n\tstatic qboolean s_bMouseGrab;\n\tqboolean verbose = m_grab_debug.value ? true : false;\n\n\tif( set && !s_bMouseGrab )\n\t{\n\t\tPlatform_SetMouseGrab( true );\n\n\t\ts_bMouseGrab = true;\n\t\tif( verbose )\n\t\t\tCon_Printf( \"%s: true\\n\", __func__ );\n\t}\n\telse if( !set && s_bMouseGrab )\n\t{\n\t\tPlatform_SetMouseGrab( false );\n\n\t\ts_bMouseGrab = false;\n\t\tif( verbose )\n\t\t\tCon_Printf( \"%s: false\\n\", __func__ );\n\t}\n}\n\nstatic void IN_CheckMouseState( qboolean active )\n{\n\tqboolean use_raw_input;\n\n#if XASH_WIN32\n\tuse_raw_input = ( m_rawinput.value && clgame.client_dll_uses_sdl ) || clgame.dllFuncs.pfnLookEvent != NULL;\n#else\n\tuse_raw_input = true; // always use SDL code\n#endif\n\n\tif( m_ignore.value )\n\t\tactive = false;\n\n\tif( active && use_raw_input && !host.mouse_visible && cls.state == ca_active )\n\t\tIN_SetRelativeMouseMode( true );\n\telse\n\t\tIN_SetRelativeMouseMode( false );\n\n\tif( active && !host.mouse_visible && cls.state == ca_active )\n\t\tIN_SetMouseGrab( true );\n\telse\n\t\tIN_SetMouseGrab( false );\n}\n\n/*\n===========\nIN_ActivateMouse\n\nCalled when the window gains focus or changes in some way\n===========\n*/\nvoid IN_ActivateMouse( void )\n{\n\tif( !in_mouseinitialized )\n\t\treturn;\n\n\tIN_CheckMouseState( true );\n\tif( clgame.dllFuncs.IN_ActivateMouse )\n\t\tclgame.dllFuncs.IN_ActivateMouse();\n\tin_mouseactive = true;\n}\n\n/*\n===========\nIN_DeactivateMouse\n\nCalled when the window loses focus\n===========\n*/\nvoid IN_DeactivateMouse( void )\n{\n\tif( !in_mouseinitialized )\n\t\treturn;\n\n\tIN_CheckMouseState( false );\n\tif( clgame.dllFuncs.IN_DeactivateMouse )\n\t\tclgame.dllFuncs.IN_DeactivateMouse();\n\tin_mouseactive = false;\n}\n\n\n\n/*\n================\nIN_MouseMove\n================\n*/\nstatic void IN_MouseMove( void )\n{\n\tint x, y;\n\n\tif( !in_mouseinitialized )\n\t\treturn;\n\n\tif( Touch_WantVisibleCursor( ))\n\t{\n\t\t// touch emulation overrides all input\n\t\tTouch_KeyEvent( 0, 0 );\n\t\treturn;\n\t}\n\n\t// find mouse movement\n\tPlatform_GetMousePos( &x, &y );\n\n\tVGui_MouseMove( x, y );\n\n\t// if the menu is visible, move the menu cursor\n\tUI_MouseMove( x, y );\n}\n\n/*\n===========\nIN_MouseEvent\n===========\n*/\nvoid IN_MouseEvent( int key, int down )\n{\n\tif( !in_mouseinitialized )\n\t\treturn;\n\n\tif( down )\n\t\tSetBits( in_mstate, BIT( key ));\n\telse ClearBits( in_mstate, BIT( key ));\n\n\t// touch emulation overrides all input\n\tif( Touch_WantVisibleCursor( ))\n\t{\n\t\tTouch_KeyEvent( K_MOUSE1 + key, down );\n\t}\n\telse if( cls.key_dest == key_game )\n\t{\n\t\t// perform button actions\n\t\tVGui_MouseEvent( K_MOUSE1 + key, down );\n\n\t\t// don't do Key_Event here\n\t\t// client may override IN_MouseEvent\n\t\t// but by default it calls back to Key_Event anyway\n\t\tif( in_mouseactive )\n\t\t\tclgame.dllFuncs.IN_MouseEvent( in_mstate );\n\t}\n\telse\n\t{\n\t\t// perform button actions\n\t\tKey_Event( K_MOUSE1 + key, down );\n\t}\n}\n\n/*\n==============\nIN_MWheelEvent\n\ndirection is negative for wheel down, otherwise wheel up\n==============\n*/\nvoid IN_MWheelEvent( int y )\n{\n\tint b = y > 0 ? K_MWHEELUP : K_MWHEELDOWN;\n\n\tVGui_MWheelEvent( y );\n\n\tKey_Event( b, true );\n\tKey_Event( b, false );\n}\n\n/*\n===========\nIN_Shutdown\n===========\n*/\nvoid IN_Shutdown( void )\n{\n\tIN_DeactivateMouse( );\n\n#if XASH_USE_EVDEV\n\tEvdev_Shutdown();\n#endif\n\n\tTouch_Shutdown();\n}\n\n\n/*\n===========\nIN_Init\n===========\n*/\nvoid IN_Init( void )\n{\n\tCvar_RegisterVariable( &cl_forwardspeed );\n\tCvar_RegisterVariable( &cl_backspeed );\n\tCvar_RegisterVariable( &cl_sidespeed );\n\n\tif( !Host_IsDedicated() )\n\t{\n\t\tIN_StartupMouse( );\n\n\t\tJoy_Init(); // common joystick support init\n\n\t\tTouch_Init();\n\n#if XASH_USE_EVDEV\n\t\tEvdev_Init();\n#endif\n\t}\n}\n\n/*\n================\nIN_JoyMove\n\nCommon function for engine joystick movement\n\n\t-1 < forwardmove < 1,\t-1 < sidemove < 1\n\n================\n*/\n\n#define F (1U << 0)\t// Forward\n#define B (1U << 1)\t// Back\n#define L (1U << 2)\t// Left\n#define R (1U << 3)\t// Right\n#define T (1U << 4)\t// Forward stop\n#define S (1U << 5)\t// Side stop\nstatic void IN_JoyAppendMove( usercmd_t *cmd, float forwardmove, float sidemove )\n{\n\tstatic uint moveflags = T | S;\n\n\tif( forwardmove ) cmd->forwardmove  = forwardmove * cl_forwardspeed.value;\n\tif( sidemove ) cmd->sidemove  = sidemove * cl_sidespeed.value;\n\n\tif( forwardmove )\n\t{\n\t\tmoveflags &= ~T;\n\t}\n\telse if( !( moveflags & T ) )\n\t{\n\t\tCmd_ExecuteString( \"-back\" );\n\t\tCmd_ExecuteString( \"-forward\" );\n\t\tmoveflags |= T;\n\t}\n\n\tif( sidemove )\n\t{\n\t\tmoveflags &= ~S;\n\t}\n\telse if( !( moveflags & S ) )\n\t{\n\t\tCmd_ExecuteString( \"-moveleft\" );\n\t\tCmd_ExecuteString( \"-moveright\" );\n\t\tmoveflags |= S;\n\t}\n\n\tif ( forwardmove > 0.7f && !( moveflags & F ))\n\t{\n\t\tmoveflags |= F;\n\t\tCmd_ExecuteString( \"+forward\" );\n\t}\n\telse if ( forwardmove < 0.7f && ( moveflags & F ))\n\t{\n\t\tmoveflags &= ~F;\n\t\tCmd_ExecuteString( \"-forward\" );\n\t}\n\n\tif ( forwardmove < -0.7f && !( moveflags & B ))\n\t{\n\t\tmoveflags |= B;\n\t\tCmd_ExecuteString( \"+back\" );\n\t}\n\telse if ( forwardmove > -0.7f && ( moveflags & B ))\n\t{\n\t\tmoveflags &= ~B;\n\t\tCmd_ExecuteString( \"-back\" );\n\t}\n\n\tif ( sidemove > 0.9f && !( moveflags & R ))\n\t{\n\t\tmoveflags |= R;\n\t\tCmd_ExecuteString( \"+moveright\" );\n\t}\n\telse if ( sidemove < 0.9f && ( moveflags & R ))\n\t{\n\t\tmoveflags &= ~R;\n\t\tCmd_ExecuteString( \"-moveright\" );\n\t}\n\n\tif ( sidemove < -0.9f && !( moveflags & L ))\n\t{\n\t\tmoveflags |= L;\n\t\tCmd_ExecuteString( \"+moveleft\" );\n\t}\n\telse if ( sidemove > -0.9f && ( moveflags & L ))\n\t{\n\t\tmoveflags &= ~L;\n\t\tCmd_ExecuteString( \"-moveleft\" );\n\t}\n}\n\nstatic void IN_CollectInput( float *forward, float *side, float *pitch, float *yaw, qboolean includeMouse )\n{\n\tif( includeMouse )\n\t{\n\t\tfloat x, y;\n\t\tPlatform_MouseMove( &x, &y );\n\t\t*pitch += y * m_pitch.value;\n\t\t*yaw   -= x * m_yaw.value;\n\n#if XASH_USE_EVDEV\n\t\tIN_EvdevMove( yaw, pitch );\n#endif\n\t}\n\n\tJoy_FinalizeMove( forward, side, yaw, pitch );\n\tTouch_GetMove( forward, side, yaw, pitch );\n\n\tif( look_filter.value )\n\t{\n\t\t*pitch = ( inputstate.lastpitch + *pitch ) / 2;\n\t\t*yaw   = ( inputstate.lastyaw   + *yaw ) / 2;\n\t\tinputstate.lastpitch = *pitch;\n\t\tinputstate.lastyaw   = *yaw;\n\t}\n\n}\n\n/*\n================\nIN_EngineAppendMove\n\nCalled from cl_main.c after generating command in client\n================\n*/\nvoid IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active )\n{\n\tfloat forward, side, pitch, yaw;\n\n\tif( clgame.dllFuncs.pfnLookEvent )\n\t\treturn;\n\n\tif( cls.key_dest != key_game || cl.paused || cl.intermission )\n\t\treturn;\n\n\tforward = side = pitch = yaw = 0;\n\n\tif( active )\n\t{\n\t\tfloat sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f );\n\n\t\tIN_CollectInput( &forward, &side, &pitch, &yaw, false );\n\n\t\tIN_JoyAppendMove( cmd, forward, side );\n\n\t\tif( pitch || yaw )\n\t\t{\n\t\t\tcmd->viewangles[YAW]   += yaw * sensitivity;\n\t\t\tcmd->viewangles[PITCH] += pitch * sensitivity;\n\t\t\tcmd->viewangles[PITCH] = bound( -90, cmd->viewangles[PITCH], 90 );\n\t\t\tVectorCopy( cmd->viewangles, cl.viewangles );\n\t\t}\n\t}\n}\n\nstatic void IN_Commands( void )\n{\n#if XASH_USE_EVDEV\n\tIN_EvdevFrame();\n#endif\n\n\tif( clgame.dllFuncs.pfnLookEvent )\n\t{\n\t\tfloat forward = 0, side = 0, pitch = 0, yaw = 0;\n\n\t\tIN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !m_ignore.value );\n\n\t\tif( cls.key_dest == key_game )\n\t\t{\n\t\t\tclgame.dllFuncs.pfnLookEvent( yaw, pitch );\n\t\t\tclgame.dllFuncs.pfnMoveEvent( forward, side );\n\t\t}\n\t}\n\n\tif( !in_mouseinitialized )\n\t\treturn;\n\n\tIN_CheckMouseState( in_mouseactive );\n}\n\n/*\n==================\nHost_InputFrame\n\nCalled every frame, even if not generating commands\n==================\n*/\nvoid Host_InputFrame( void )\n{\n\tIN_Commands();\n\n\tIN_MouseMove();\n}\n"
  },
  {
    "path": "engine/client/input.h",
    "content": "/*\ninput.h - win32 input devices\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef INPUT_H\n#define INPUT_H\n\n/*\n==============================================================\n\nINPUT\n\n==============================================================\n*/\n\n#include \"keydefs.h\"\n#include \"usercmd.h\"\n\n//\n// input.c\n//\nextern qboolean\tin_mouseinitialized;\nvoid IN_Init( void );\nvoid Host_InputFrame( void );\nvoid IN_Shutdown( void );\nvoid IN_MouseEvent( int key, int down );\nvoid IN_MWheelEvent( int direction );\nvoid IN_ActivateMouse( void );\nvoid IN_DeactivateMouse( void );\nvoid IN_MouseSavePos( void );\nvoid IN_MouseRestorePos( void );\nvoid IN_ToggleClientMouse( int newstate, int oldstate );\n\nuint IN_CollectInputDevices( void );\nvoid IN_LockInputDevices( qboolean lock );\nvoid IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active );\n\nvoid IN_SetRelativeMouseMode( qboolean set );\nvoid IN_SetMouseGrab( qboolean set );\n\nextern convar_t m_yaw;\nextern convar_t m_pitch;\n//\n// in_touch.c\n//\ntypedef enum\n{\n\tevent_down = 0,\n\tevent_up,\n\tevent_motion\n} touchEventType;\n\nextern convar_t touch_enable;\n\nvoid Touch_Draw( void );\nvoid Touch_SetClientOnly( byte state );\nvoid Touch_RemoveButton( const char *name, qboolean privileged );\nvoid Touch_HideButtons( const char *name, unsigned char hide, qboolean privileged );\nvoid Touch_AddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags );\nvoid Touch_AddDefaultButton( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags );\nvoid Touch_WriteConfig( void );\nvoid Touch_Init( void );\nvoid Touch_Shutdown( void );\nvoid Touch_GetMove( float * forward, float *side, float *yaw, float *pitch );\nvoid Touch_ResetDefaultButtons( void );\nint IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy );\nvoid Touch_KeyEvent( int key, int down );\nqboolean Touch_WantVisibleCursor( void );\nvoid Touch_NotifyResize( void );\n\n//\n// in_joy.c\n//\nenum\n{\n\tJOY_HAT_CENTERED = 0,\n\tJOY_HAT_UP    = BIT(0),\n\tJOY_HAT_RIGHT = BIT(1),\n\tJOY_HAT_DOWN  = BIT(2),\n\tJOY_HAT_LEFT  = BIT(3),\n\tJOY_HAT_RIGHTUP   = JOY_HAT_RIGHT | JOY_HAT_UP,\n\tJOY_HAT_RIGHTDOWN = JOY_HAT_RIGHT | JOY_HAT_DOWN,\n\tJOY_HAT_LEFTUP    = JOY_HAT_LEFT  | JOY_HAT_UP,\n\tJOY_HAT_LEFTDOWN  = JOY_HAT_LEFT  | JOY_HAT_DOWN\n};\n\ntypedef enum engineAxis_e\n{\n\tJOY_AXIS_SIDE = 0,\n\tJOY_AXIS_FWD,\n\tJOY_AXIS_PITCH,\n\tJOY_AXIS_YAW,\n\tJOY_AXIS_RT,\n\tJOY_AXIS_LT,\n\tJOY_AXIS_NULL\n} engineAxis_t;\n\ntypedef enum joy_calibration_state_s\n{\n\tJOY_NOT_CALIBRATED = 0,\n\tJOY_CALIBRATING,\n\tJOY_FAILED_TO_CALIBRATE,\n\tJOY_CALIBRATED\n} joy_calibration_state_t;\n\nqboolean Joy_IsActive( void );\nvoid Joy_SetCapabilities( qboolean have_gyro );\nvoid Joy_SetCalibrationState( joy_calibration_state_t state );\nvoid Joy_AxisMotionEvent( engineAxis_t engineAxis, short value );\nvoid Joy_GyroEvent( vec3_t data );\nvoid Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw );\nvoid Joy_Init( void );\nvoid Joy_Shutdown( void );\n\n#endif//INPUT_H\n"
  },
  {
    "path": "engine/client/keys.c",
    "content": "/*\nkeys.c - console key events\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"platform/platform.h\"\n\ntypedef struct\n{\n\tqboolean\t\tdown;\n\tqboolean\t\tgamedown;\n\tint\t\trepeats;\t// if > 1, it is autorepeating\n\tconst char\t*binding;\n} enginekey_t;\n\ntypedef struct keyname_s\n{\n\tconst char\t*name;\t// key name\n\tint\t\tkeynum;\t// key number\n\tconst char\t*binding;\t// default bind\n} keyname_t;\n\nstatic enginekey_t\tkeys[256];\n\nstatic const keyname_t keynames[] =\n{\n{\"TAB\",\t\tK_TAB,\t\t\"\"\t\t},\n{\"ENTER\",\t\tK_ENTER,\t\t\"\"\t\t},\n{\"ESCAPE\",\tK_ESCAPE, \t\"cancelselect\"\t\t}, // hardcoded\n{\"SPACE\",\t\tK_SPACE,\t\t\"+jump\"\t\t},\n{\"BACKSPACE\",\tK_BACKSPACE,\t\"\"\t\t},\n{\"UPARROW\",\tK_UPARROW,\t\"+forward\"\t},\n{\"DOWNARROW\",\tK_DOWNARROW,\t\"+back\"\t\t},\n{\"LEFTARROW\",\tK_LEFTARROW,\t\"+left\"\t\t},\n{\"RIGHTARROW\",\tK_RIGHTARROW,\t\"+right\"\t\t},\n{\"ALT\",\t\tK_ALT,\t\t\"+strafe\"\t\t},\n{\"CTRL\",\t\tK_CTRL,\t\t\"+attack\"\t\t},\n{\"SHIFT\",\t\tK_SHIFT,\t\t\"+speed\"\t\t},\n{\"CAPSLOCK\",\tK_CAPSLOCK,\t\"\"\t\t},\n{\"SCROLLOCK\",\tK_SCROLLLOCK,\t\"\"\t\t},\n{\"F1\",\t\tK_F1,\t\t\"cmd help\"\t},\n{\"F2\",\t\tK_F2,\t\t\"menu_savegame\"\t},\n{\"F3\",\t\tK_F3,\t\t\"menu_loadgame\"\t},\n{\"F4\",\t\tK_F4,\t\t\"menu_controls\"\t},\n{\"F5\",\t\tK_F5,\t\t\"menu_creategame\"\t},\n{\"F6\",\t\tK_F6,\t\t\"savequick\"\t},\n{\"F7\",\t\tK_F7,\t\t\"loadquick\"\t},\n{\"F8\",\t\tK_F8,\t\t\"stop\"\t\t},\n{\"F9\",\t\tK_F9,\t\t\"\"\t\t},\n{\"F10\",\t\tK_F10,\t\t\"menu_main\"\t},\n{\"F11\",\t\tK_F11,\t\t\"\"\t\t},\n{\"F12\",\t\tK_F12,\t\t\"snapshot\"\t},\n{\"INS\",\t\tK_INS,\t\t\"\"\t\t},\n{\"DEL\",\t\tK_DEL,\t\t\"+lookdown\"\t},\n{\"PGDN\",\t\tK_PGDN,\t\t\"+lookup\"\t\t},\n{\"PGUP\",\t\tK_PGUP,\t\t\"\"\t\t},\n{\"HOME\",\t\tK_HOME,\t\t\"\"\t\t},\n{\"END\",\t\tK_END,\t\t\"centerview\"\t},\n\n// mouse buttouns\n{\"MOUSE1\",\tK_MOUSE1,\t\t\"+attack\"\t\t},\n{\"MOUSE2\",\tK_MOUSE2,\t\t\"+attack2\"\t},\n{\"MOUSE3\",\tK_MOUSE3,\t\t\"\"\t\t},\n{\"MOUSE4\",\tK_MOUSE4,\t\t\"\"\t\t},\n{\"MOUSE5\",\tK_MOUSE5,\t\t\"\"\t\t},\n{\"MWHEELUP\",\tK_MWHEELUP,\t\"\"\t\t},\n{\"MWHEELDOWN\",\tK_MWHEELDOWN,\t\"\"\t\t},\n\n// digital keyboard\n{\"KP_HOME\",\tK_KP_HOME,\t\"\"\t\t},\n{\"KP_UPARROW\",\tK_KP_UPARROW,\t\"+forward\"\t},\n{\"KP_PGUP\",\tK_KP_PGUP,\t\"\"\t\t},\n{\"KP_LEFTARROW\",\tK_KP_LEFTARROW,\t\"+left\"\t\t},\n{\"KP_5\",\t\tK_KP_5,\t\t\"\"\t\t},\n{\"KP_RIGHTARROW\",\tK_KP_RIGHTARROW,\t\"+right\"\t\t},\n{\"KP_END\",\tK_KP_END,\t\t\"centerview\"\t},\n{\"KP_DOWNARROW\",\tK_KP_DOWNARROW,\t\"+back\"\t\t},\n{\"KP_PGDN\",\tK_KP_PGDN,\t\"+lookup\" \t},\n{\"KP_ENTER\",\tK_KP_ENTER,\t\"\"\t\t},\n{\"KP_INS\",\tK_KP_INS,\t\t\"\"\t\t},\n{\"KP_DEL\",\tK_KP_DEL,\t\t\"+lookdown\"\t},\n{\"KP_SLASH\",\tK_KP_SLASH,\t\"\"\t\t},\n{\"KP_MINUS\",\tK_KP_MINUS,\t\"\"\t\t},\n{\"KP_PLUS\",\tK_KP_PLUS,\t\"\"\t\t},\n{\"PAUSE\",\t\tK_PAUSE,\t\t\"pause\"\t\t},\n\n// Gamepad\n// A/B X/Y names match the Xbox controller layout\n{\"A_BUTTON\", K_A_BUTTON, \"+jump\"},\n{\"B_BUTTON\", K_B_BUTTON, \"+use\"},\n{\"X_BUTTON\", K_X_BUTTON, \"+reload\"},\n{\"Y_BUTTON\", K_Y_BUTTON, \"impulse 100\"}, // Flashlight\n{\"BACK\",   K_BACK_BUTTON, \"pause\"}, // Menu\n{\"MODE\",   K_MODE_BUTTON, \"\"},\n{\"START\",  K_START_BUTTON, \"cancelselect\"},\n{\"STICK1\", K_LSTICK, \"+speed\"},\n{\"STICK2\", K_RSTICK, \"+duck\"},\n{\"L1_BUTTON\",  K_L1_BUTTON, \"+duck\"},\n{\"R1_BUTTON\",  K_R1_BUTTON, \"+attack\"},\n{\"DPAD_UP\",\tK_DPAD_UP,\t\"impulse 201\"}, // Spray\n{\"DPAD_DOWN\",\tK_DPAD_DOWN,\t\"lastinv\"},\n{\"DPAD_LEFT\",\tK_DPAD_LEFT,\t\"invprev\"},\n{\"DPAD_RIGHT\",\tK_DPAD_RIGHT,\t\"invnext\"},\n{\"L2_BUTTON\", K_L2_BUTTON, \"+speed\"},\n{\"R2_BUTTON\", K_R2_BUTTON, \"+attack2\"},\n{\"LTRIGGER\" , K_JOY1 , \"+speed\"}, // L2 in SDL2\n{\"RTRIGGER\" , K_JOY2 , \"+attack2\"}, // R2 in SDL2\n{\"JOY3\" , K_JOY3 , \"\"},\n{\"JOY4\" , K_JOY4 , \"\"},\n{\"C_BUTTON\", K_C_BUTTON, \"\"},\n{\"Z_BUTTON\", K_Z_BUTTON, \"\"},\n{\"MISC_BUTTON\", K_MISC_BUTTON, \"\"},\n{\"PADDLE1\", K_PADDLE1_BUTTON, \"\"},\n{\"PADDLE2\", K_PADDLE2_BUTTON, \"\"},\n{\"PADDLE3\", K_PADDLE3_BUTTON, \"\"},\n{\"PADDLE4\", K_PADDLE4_BUTTON, \"\"},\n{\"TOUCHPAD\", K_TOUCHPAD, \"\"},\n{\"AUX26\", K_AUX26, \"\"}, // generic\n{\"AUX27\", K_AUX27, \"\"},\n{\"AUX28\", K_AUX28, \"\"},\n{\"AUX29\", K_AUX29, \"\"},\n{\"AUX30\", K_AUX30, \"\"},\n{\"AUX31\", K_AUX31, \"\"},\n{\"AUX32\", K_AUX32, \"\"},\n\n// raw semicolon seperates commands\n{\"SEMICOLON\",\t';',\t\t\"\"\t\t},\n};\n\nstatic void OSK_EnableTextInput( qboolean enable, qboolean force );\nstatic qboolean OSK_KeyEvent( int key, int down );\nstatic CVAR_DEFINE_AUTO( osk_enable, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"enable built-in on-screen keyboard\" );\nstatic CVAR_DEFINE_AUTO( key_rotate, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"rotate arrow keys (0-3)\" );\n\n/*\n===================\nKey_IsDown\n===================\n*/\nint GAME_EXPORT Key_IsDown( int keynum )\n{\n\tif( keynum == -1 )\n\t\treturn false;\n\treturn keys[keynum].down;\n}\n\n/*\n===================\nKey_StringToKeynum\n\nReturns a key number to be used to index keys[] by looking at\nthe given string.  Single ascii characters return themselves, while\nthe K_* names are matched up.\n\n0x11 will be interpreted as raw hex, which will allow new controlers\n\nto be configured even if they don't have defined names.\n===================\n*/\nstatic int Key_StringToKeynum( const char *str )\n{\n\tint i;\n\n\tif( !str || !str[0] )\n\t\treturn -1;\n\n\tif( !str[1] )\n\t\treturn str[0];\n\n\t// check for hex code\n\tif( str[0] == '0' && str[1] == 'x' && Q_strlen( str ) == 4 )\n\t\treturn COM_Nibble( str[2] ) << 4 | COM_Nibble( str[3] );\n\n\t// scan for a text match\n\tfor( i = 0; i < ARRAYSIZE( keynames ); i++ )\n\t{\n\t\tif( !Q_stricmp( str, keynames[i].name ))\n\t\t\treturn keynames[i].keynum;\n\t}\n\n\treturn -1;\n}\n\n/*\n===================\nKey_KeynumToString\n\nReturns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the\ngiven keynum.\n===================\n*/\nconst char *Key_KeynumToString( int keynum )\n{\n\tstatic char\ttinystr[5];\n\tint\t\ti, j;\n\n\tif( keynum == -1 )\n\t\treturn \"<KEY NOT FOUND>\";\n\n\tif( keynum < 0 || keynum > 255 )\n\t\treturn \"<OUT OF RANGE>\";\n\n\t// check for printable ascii (don't use quote)\n\tif( keynum > 32 && keynum < 127 && keynum != '\"' && keynum != ';' && keynum != K_SCROLLLOCK )\n\t{\n\t\ttinystr[0] = keynum;\n\t\ttinystr[1] = 0;\n\t\treturn tinystr;\n\t}\n\n\t// check for a key string\n\tfor( i = 0; i < ARRAYSIZE( keynames ); i++ )\n\t{\n\t\tif( keynum == keynames[i].keynum )\n\t\t\treturn keynames[i].name;\n\t}\n\n\t// make a hex string\n\ti = keynum >> 4;\n\tj = keynum & 15;\n\n\ttinystr[0] = '0';\n\ttinystr[1] = 'x';\n\ttinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';\n\ttinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';\n\ttinystr[4] = 0;\n\n\treturn tinystr;\n}\n\n/*\n===================\nKey_SetBinding\n===================\n*/\nvoid GAME_EXPORT Key_SetBinding( int keynum, const char *binding )\n{\n\tif( keynum == -1 ) return;\n\n\t// free old bindings\n\tif( keys[keynum].binding )\n\t{\n\t\tMem_Free((char *)keys[keynum].binding );\n\t\tkeys[keynum].binding = NULL;\n\t}\n\n\t// allocate memory for new binding\n\tkeys[keynum].binding = copystring( binding );\n}\n\n\n/*\n===================\nKey_GetBinding\n===================\n*/\nconst char *Key_GetBinding( int keynum )\n{\n\tif( keynum == -1 ) return NULL;\n\treturn keys[keynum].binding;\n}\n\n/*\n===================\nKey_GetKey\n===================\n*/\nstatic int Key_GetKey( const char *pBinding )\n{\n\tint\t\t i, len;\n\tconst char\t*p;\n\n\tif( !pBinding ) return -1;\n\n\tlen = Q_strlen( pBinding );\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tif( !keys[i].binding )\n\t\t\tcontinue;\n\n\t\tp = keys[i].binding;\n\n\t\tif( *p == '+' )\n\t\t\tp++;\n\n\t\tif( !Q_strnicmp( p, pBinding, len ) )\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\n/*\n=============\nKey_LookupBinding\n\n=============\n*/\nconst char *Key_LookupBinding( const char *pBinding )\n{\n\treturn Key_KeynumToString( Key_GetKey( pBinding ));\n}\n\n/*\n===================\nKey_Unbind_f\n===================\n*/\nstatic void Key_Unbind_f( void )\n{\n\tint\tb;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"unbind <key> : remove commands from a key\\n\" );\n\t\treturn;\n\t}\n\n\tb = Key_StringToKeynum( Cmd_Argv( 1 ));\n\n\tif( b == -1 )\n\t{\n\t\tCon_Printf( \"\\\"%s\\\" isn't a valid key\\n\", Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\tif( b == K_ESCAPE )\n\t{\n\t\tCon_Printf( \"Can't unbind ESCAPE key\\n\" );\n\t\treturn;\n\t}\n\n\tKey_SetBinding( b, \"\" );\n}\n\n/*\n===================\nKey_Unbindall_f\n===================\n*/\nstatic void Key_Unbindall_f( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < ARRAYSIZE( keys ); i++ )\n\t{\n\t\tif( keys[i].binding )\n\t\t\tKey_SetBinding( i, \"\" );\n\t}\n\n\t// set some defaults\n\tKey_SetBinding( K_ESCAPE, \"cancelselect\" );\n\tKey_SetBinding( K_START_BUTTON, \"cancelselect\" );\n}\n\n/*\n===================\nKey_Reset_f\n===================\n*/\nstatic void Key_Reset_f( void )\n{\n\tint\ti;\n\n\t// clear all keys first\n\tfor( i = 0; i < ARRAYSIZE( keys ); i++ )\n\t{\n\t\tif( keys[i].binding )\n\t\t\tKey_SetBinding( i, \"\" );\n\t}\n\n\t// apply default values\n\tfor( i = 0; i < ARRAYSIZE( keynames ); i++ )\n\t\tKey_SetBinding( keynames[i].keynum, keynames[i].binding );\n}\n\n/*\n===================\nKey_Bind_f\n===================\n*/\nstatic void Key_Bind_f( void )\n{\n\tchar\tcmd[1024];\n\tint\ti, c, b;\n\n\tc = Cmd_Argc();\n\n\tif( c < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"bind <key> [command] : attach a command to a key\\n\" );\n\t\treturn;\n\t}\n\n\tb = Key_StringToKeynum( Cmd_Argv( 1 ));\n\n\tif( b == -1 )\n\t{\n\t\tCon_Printf( \"\\\"%s\\\" isn't a valid key\\n\", Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\tif( c == 2 )\n\t{\n\t\tif( keys[b].binding )\n\t\t\tCon_Printf( \"\\\"%s\\\" = \\\"%s\\\"\\n\", Cmd_Argv( 1 ), keys[b].binding );\n\t\telse Con_Printf( \"\\\"%s\\\" is not bound\\n\", Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\t// copy the rest of the command line\n\tcmd[0] = 0; // start out with a null string\n\n\tfor( i = 2; i < c; i++ )\n\t{\n\t\tQ_strncat( cmd, Cmd_Argv( i ), sizeof( cmd ));\n\t\tif( i != ( c - 1 )) Q_strncat( cmd, \" \", sizeof( cmd ));\n\t}\n\n\tKey_SetBinding( b, cmd );\n}\n\n/*\n============\nKey_WriteBindings\n\nWrites lines containing \"bind key value\"\n============\n*/\nvoid Key_WriteBindings( file_t *f )\n{\n\tint\ti;\n\tstring newCommand;\n\n\tif( !f ) return;\n\n\tFS_Printf( f, \"unbindall\\n\" );\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tif( !COM_CheckString( keys[i].binding ))\n\t\t\tcontinue;\n\n\t\tCmd_Escape( newCommand, keys[i].binding, sizeof( newCommand ));\n\t\tFS_Printf( f, \"bind %s \\\"%s\\\"\\n\", Key_KeynumToString( i ), newCommand );\n\t}\n}\n\n/*\n============\nKey_Bindlist_f\n\n============\n*/\nstatic void Key_Bindlist_f( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tif( !COM_CheckString( keys[i].binding ))\n\t\t\tcontinue;\n\n\t\tCon_Printf( \"%s \\\"%s\\\"\\n\", Key_KeynumToString( i ), keys[i].binding );\n\t}\n}\n\n/*\n==============================================================================\n\n\t\t\tLINE TYPING INTO THE CONSOLE\n\n==============================================================================\n*/\n/*\n===================\nKey_Init\n===================\n*/\nvoid Key_Init( void )\n{\n\tint i;\n\n\t// register our functions\n\tCmd_AddRestrictedCommand( \"bind\", Key_Bind_f, \"binds a command to the specified key in bindmap\" );\n\tCmd_AddRestrictedCommand( \"unbind\", Key_Unbind_f, \"removes a command on the specified key in bindmap\" );\n\tCmd_AddRestrictedCommand( \"unbindall\", Key_Unbindall_f, \"removes all commands from all keys in bindmap\" );\n\tCmd_AddRestrictedCommand( \"resetkeys\", Key_Reset_f, \"reset all keys to their default values\" );\n\tCmd_AddCommand( \"bindlist\", Key_Bindlist_f, \"display current key bindings\" );\n\tCmd_AddCommand( \"makehelp\", Key_EnumCmds_f, \"write help.txt that contains all console cvars and cmds\" );\n\n\t// setup default binding. \"unbindall\" from config.cfg will be reset it\n\tfor( i = 0; i < ARRAYSIZE( keynames ); i++ )\n\t\tKey_SetBinding( keynames[i].keynum, keynames[i].binding );\n\n\tCvar_RegisterVariable( &osk_enable );\n\tCvar_RegisterVariable( &key_rotate );\n\n}\n\n/*\n===================\nKey_AddKeyCommands\n===================\n*/\nstatic void Key_AddKeyCommands( int key, const char *kb, qboolean down )\n{\n\tchar\tbutton[1024];\n\tchar\t*buttonPtr;\n\tchar\tcmd[1024];\n\tint\ti;\n\n\tif( !kb ) return;\n\tbuttonPtr = button;\n\n\tfor( i = 0; ; i++ )\n\t{\n\t\tif( kb[i] == ';' || !kb[i] )\n\t\t{\n\t\t\t*buttonPtr = '\\0';\n\t\t\tif( button[0] == '+' )\n\t\t\t{\n\t\t\t\t// button commands add keynum as a parm\n\t\t\t\tif( down ) Q_snprintf( cmd, sizeof( cmd ), \"%s %i\\n\", button, key );\n\t\t\t\telse Q_snprintf( cmd, sizeof( cmd ), \"-%s %i\\n\", button + 1, key );\n\t\t\t\tCbuf_AddText( cmd );\n\t\t\t}\n\t\t\telse if( down )\n\t\t\t{\n\t\t\t\t// down-only command\n\t\t\t\tCbuf_AddText( button );\n\t\t\t\tCbuf_AddText( \"\\n\" );\n\t\t\t}\n\n\t\t\tbuttonPtr = button;\n\t\t\twhile((((byte)kb[i]) <= ' ' || kb[i] == ';' ) && kb[i] != 0 )\n\t\t\t\ti++;\n\t\t}\n\n\t\t*buttonPtr++ = kb[i];\n\t\tif( !kb[i] ) break;\n\t}\n}\n\n/*\n===================\nKey_IsAllowedAutoRepeat\n\nList of keys that allows auto-repeat\n===================\n*/\nstatic qboolean Key_IsAllowedAutoRepeat( int key )\n{\n\tif( cls.key_dest != key_game )\n\t\treturn true;\n\n\tswitch( key )\n\t{\n\tcase K_BACKSPACE:\n\tcase K_PAUSE:\n\tcase K_PGUP:\n\tcase K_KP_PGUP:\n\tcase K_PGDN:\n\tcase K_KP_PGDN:\n\t\treturn true;\n\tdefault:\n\t\treturn false;\n\t}\n}\n\nstatic int Key_Rotate( int key )\n{\n\tif( key_rotate.value == 1.0f ) // CW\n\t{\n\t\tif( key == K_UPARROW )\n\t\t\t\tkey = K_LEFTARROW;\n\t\telse if( key == K_LEFTARROW )\n\t\t\t\tkey = K_DOWNARROW;\n\t\telse if( key == K_RIGHTARROW )\n\t\t\t\tkey = K_UPARROW;\n\t\telse if( key == K_DOWNARROW )\n\t\t\t\tkey = K_RIGHTARROW;\n\t}\n\n\telse if( key_rotate.value == 3.0f ) // CCW\n\t{\n\t\tif( key == K_UPARROW )\n\t\t\t\tkey = K_RIGHTARROW;\n\t\telse if( key == K_LEFTARROW )\n\t\t\t\tkey = K_UPARROW;\n\t\telse if( key == K_RIGHTARROW )\n\t\t\t\tkey = K_DOWNARROW;\n\t\telse if( key == K_DOWNARROW )\n\t\t\t\tkey = K_LEFTARROW;\n\t}\n\n\telse if( key_rotate.value == 2.0f )\n\t{\n\t\tif( key == K_UPARROW )\n\t\t\t\tkey = K_DOWNARROW;\n\t\telse if( key == K_LEFTARROW )\n\t\t\t\tkey = K_RIGHTARROW;\n\t\telse if( key == K_RIGHTARROW )\n\t\t\t\tkey = K_LEFTARROW;\n\t\telse if( key == K_DOWNARROW )\n\t\t\t\tkey = K_UPARROW;\n\t}\n\n\treturn key;\n}\n\n\n/*\n===================\nKey_Event\n\nCalled by the system for both key up and key down events\n===================\n*/\nvoid GAME_EXPORT Key_Event( int key, int down )\n{\n\tconst char\t*kb;\n\n\tkey = Key_Rotate( key );\n\n\tif( OSK_KeyEvent( key, down ) )\n\t\treturn;\n\n\t// key was pressed before engine was run\n\tif( !keys[key].down && !down )\n\t\treturn;\n\n\tkb = keys[key].binding;\n\tkeys[key].down = down;\n\n#ifdef HACKS_RELATED_HLMODS\n\tif(( cls.key_dest == key_game ) && ( cls.state == ca_cinematic ) && ( key != K_ESCAPE || !down ))\n\t{\n\t\t// only escape passed when cinematic is playing\n\t\t// HLFX 0.6 bug: crash in vgui3.dll while press +attack during movie playback\n\t\treturn;\n\t}\n#endif\n\t// distribute the key down event to the apropriate handler\n\tif( cls.key_dest == key_game && ( down || keys[key].gamedown ))\n\t{\n\t\tif( !clgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding ))\n\t\t{\n\t\t\tif( keys[key].repeats == 0 && down )\n\t\t\t{\n\t\t\t\tkeys[key].gamedown = true;\n\t\t\t}\n\n\t\t\tif( !down )\n\t\t\t{\n\t\t\t\tkeys[key].gamedown = false;\n\t\t\t\tkeys[key].repeats = 0;\n\t\t\t}\n\t\t\treturn; // handled in client.dll\n\t\t}\n\t}\n\n\t// update auto-repeat status\n\tif( down )\n\t{\n\t\tkeys[key].repeats++;\n\n\t\tif( !Key_IsAllowedAutoRepeat( key ) && keys[key].repeats > 1 )\n\t\t{\n\t\t\t// ignore most autorepeats\n\t\t\treturn;\n\t\t}\n\n\t\tif( key >= 200 && !kb )\n\t\t\tCon_Printf( \"%s is unbound.\\n\", Key_KeynumToString( key ));\n\t}\n\telse\n\t{\n\t\tkeys[key].gamedown = false;\n\t\tkeys[key].repeats = 0;\n\t}\n\n\tVGui_KeyEvent( key, down );\n\n\t// console key is hardcoded, so the user can never unbind it\n\tif( key == '`' || key == '~' )\n\t{\n\t\t// we are in typing mode, so don't switch to console\n\t\tif( cls.key_dest == key_message || !down )\n\t\t\treturn;\n\n\t\tCon_ToggleConsole_f();\n\t\treturn;\n\t}\n\n\t// escape is always handled special\n\tif( key == K_ESCAPE && down )\n\t{\n\t\tswitch( cls.key_dest )\n\t\t{\n\t\tcase key_game:\n\t\t\tif( r_showtextures.value )\n\t\t\t{\n\t\t\t\t// close texture atlas\n\t\t\t\tCvar_DirectSet( &r_showtextures, \"0\" );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if( host.mouse_visible && cls.state != ca_cinematic )\n\t\t\t{\n\t\t\t\tclgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding );\n\t\t\t\treturn; // handled in client.dll\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( cls.key_dest == key_menu )\n\t{\n\t\t// classic Xash3D menus don't have an extension that tells engine\n\t\t// to enable text input\n\t\tif( !gameui.use_extended_api )\n\t\t{\n\t\t\t// we don't know if menu wants text input or not\n\t\t\t// enable it unconditionally\n\t\t\tKey_EnableTextInput( true, false );\n\n\t\t\t// pass this key to the menu, if printable\n\t\t\tif( !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ))\n\t\t\t{\n\t\t\t\tif( Key_IsDown( K_SHIFT ))\n\t\t\t\t\tkey += 'A' - 'a';\n\n\t\t\t\tUI_CharEvent( key );\n\t\t\t}\n\t\t}\n\n\t\tUI_KeyEvent( key, down );\n\t\treturn;\n\t}\n\n\t// key up events only perform actions if the game key binding is\n\t// a button command (leading + sign).  These will be processed even in\n\t// console mode and menu mode, to keep the character from continuing\n\t// an action started before a mode switch.\n\tif( !down )\n\t{\n\t\tKey_AddKeyCommands( key, kb, down );\n\t\treturn;\n\t}\n\n\t// distribute the key down event to the apropriate handler\n\tif( cls.key_dest == key_game )\n\t{\n\t\tKey_AddKeyCommands( key, kb, down );\n\t}\n\telse if( cls.key_dest == key_console )\n\t{\n\t\tKey_Console( key );\n\t}\n\telse if( cls.key_dest == key_message )\n\t{\n\t\tKey_Message( key );\n\t}\n}\n\n/*\n================\nKey_EnableTextInput\n\n================\n*/\nvoid Key_EnableTextInput( qboolean enable, qboolean force )\n{\n\tif( osk_enable.value )\n\t{\n\t\tOSK_EnableTextInput( enable, force );\n\t\treturn;\n\t}\n\tif( enable && ( !host.textmode || force ))\n\t\tPlatform_EnableTextInput( true );\n\telse if( !enable && ( host.textmode || force ))\n\t\tPlatform_EnableTextInput( false );\n\n\thost.textmode = enable;\n}\n\n/*\n=========\nKey_SetKeyDest\n=========\n*/\nvoid GAME_EXPORT Key_SetKeyDest( int key_dest )\n{\n\tIN_ToggleClientMouse( key_dest, cls.key_dest );\n\n\tswitch( key_dest )\n\t{\n\tcase key_game:\n\t\tKey_EnableTextInput( false, false );\n\t\tcls.key_dest = key_game;\n\t\tbreak;\n\tcase key_menu:\n\t\tKey_EnableTextInput( false, false );\n\t\tcls.key_dest = key_menu;\n\t\tbreak;\n\tcase key_console:\n#if !XASH_NSWITCH && !XASH_PSVITA // if we don't disable this, pops up the keyboard during load\n\t\tKey_EnableTextInput( true, false );\n#endif\n\t\tcls.key_dest = key_console;\n\t\tbreak;\n\tcase key_message:\n\t\tKey_EnableTextInput( true, false );\n\t\tcls.key_dest = key_message;\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: wrong destination (%i)\\n\", __func__, key_dest );\n\t\tbreak;\n\t}\n}\n\n/*\n===================\nKey_ClearStates\n===================\n*/\nvoid GAME_EXPORT Key_ClearStates( void )\n{\n\tint\ti;\n\n\t// don't clear keys during changelevel\n\tif( cls.changelevel )\n\t\treturn;\n\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tif( i >= K_MOUSE1 && i <= K_MOUSE5 )\n\t\t\tIN_MouseEvent( i - K_MOUSE1, false );\n\t\telse\n\t\t\tKey_Event( i, false );\n\n\t\tkeys[i].down = 0;\n\t\tkeys[i].repeats = 0;\n\t\tkeys[i].gamedown = 0;\n\t}\n\n\tif( clgame.hInstance )\n\t\tclgame.dllFuncs.IN_ClearStates();\n}\n\n/*\n===================\nCL_CharEvent\n\nNormal keyboard characters, already shifted / capslocked / etc\n===================\n*/\nvoid CL_CharEvent( int key )\n{\n\t// the console key should never be used as a char\n\tif( key == '`' || key == '~' ) return;\n\n\tif( cls.key_dest == key_console && !Con_Visible( ))\n\t{\n\t\tif((char)key == '`' || (char)key == '?' )\n\t\t\treturn; // don't pass '`' when we open the console\n\t}\n\n\t// distribute the key down event to the apropriate handler\n\tif( cls.key_dest == key_console || cls.key_dest == key_message )\n\t{\n\t\tCon_CharEvent( key );\n\t}\n\telse if( cls.key_dest == key_menu )\n\t{\n\t\tUI_CharEvent( key );\n\t}\n}\n\n/*\n============\nKey_ToUpper\n\nA helper function if platform input doesn't support text mode properly\n============\n*/\nint Key_ToUpper( int keynum )\n{\n\tif( keynum == '-' )\n\t\treturn '_';\n\tif( keynum == '=' )\n\t\treturn '+';\n\tif( keynum == ';' )\n\t\treturn ':';\n\tif( keynum == '\\'' )\n\t\treturn '\"';\n\n\treturn Q_toupper( keynum );\n}\n\n/* On-screen keyboard:\n *\n * 4 lines with 13 buttons each\n * Left trigger == backspace\n * Right trigger == space\n * Any button press is button press on keyboard\n *\n * Our layout:\n *  0  1  2  3  4  5  6  7  8  9  10 11 12\n * +--+--+--+--+--+--+--+--+--+--+--+--+--+\n * |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0\n * +--+--+--+--+--+--+--+--+--+--+--+--+--+\n * |q |w |e |r |t |y |u |i |o |p |[ |] |\\ | 1\n * +--+--+--+--+--+--+--+--+--+--+--+--+--+\n * |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2\n * +--+--+--+--+--+--+--+--+--+--+--+--+--+\n * |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3\n * +--+--+--+--+--+--+--+--+--+--+--+--+--+\n */\n\n#define MAX_OSK_ROWS 13\n#define MAX_OSK_LINES 4\n\nenum\n{\n\tOSK_DEFAULT = 0,\n\tOSK_UPPER, // on caps, shift\n\t/*\n\tOSK_RUSSIAN,\n\tOSK_RUSSIAN_UPPER,\n\t*/\n\tOSK_LAST\n};\n\nenum\n{\n\tOSK_TAB = 16,\n\tOSK_SHIFT,\n\tOSK_BACKSPACE,\n\tOSK_ENTER,\n\tOSK_SPECKEY_LAST\n};\nstatic const char *osk_keylayout[][4] =\n{\n\t{\n\t\t\t  \"`1234567890-=\",  // 13\n\t\t\t  \"qwertyuiop[]\\\\\", // 13\n\t\t\"\\x10\" \"asdfghjkl;'\" \"\\x12\",   // 11 + caps on a left, enter on a right\n\t\t\"\\x11\" \"zxcvbnm,./ \" \"\\x13\"     // 10 + esc on left + shift on a left/right\n\t},\n\t{\n\t\t\t  \"~!@#$%^&*()_+\",\n\t\t\t  \"QWERTYUIOP{}|\",\n\t\t\"\\x10\" \"ASDFGHJKL:\\\"\" \"\\x12\",\n\t\t\"\\x11\" \"ZXCVBNM<>? \"  \"\\x13\"\n\t}\n};\n\nstruct osk_s\n{\n\tqboolean enable;\n\tint curlayout;\n\tqboolean shift;\n\tqboolean sending;\n\tstruct {\n\t\tsigned char x;\n\t\tsigned char y;\n\t\tchar val;\n\t} curbutton;\n} osk;\n\nstatic qboolean OSK_KeyEvent( int key, int down )\n{\n\tif( !osk.enable || !osk_enable.value )\n\t\treturn false;\n\n\tif( osk.sending )\n\t{\n\t\tosk.sending = false;\n\t\treturn false;\n\t}\n\n\tif( osk.curbutton.val == 0 )\n\t{\n\t\tif( key == K_ENTER )\n\t\t{\n\t\t\tosk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\n\tswitch ( key )\n\t{\n\tcase K_ENTER:\n\t\tswitch( osk.curbutton.val )\n\t\t{\n\t\tcase OSK_ENTER:\n\t\t\tosk.sending  = true;\n\t\t\tKey_Event( K_ENTER, down );\n\t\t\t//osk_enable = false; // TODO: handle multiline\n\t\t\tbreak;\n\t\tcase OSK_SHIFT:\n\t\t\tif( !down )\n\t\t\t\tbreak;\n\n\t\t\tif( osk.curlayout & 1 )\n\t\t\t\tosk.curlayout--;\n\t\t\telse\n\t\t\t\tosk.curlayout++;\n\n\t\t\tosk.shift = true;\n\t\t\tosk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];\n\t\t\tbreak;\n\t\tcase OSK_BACKSPACE:\n\t\t\tKey_Event( K_BACKSPACE, down ); break;\n\t\tcase OSK_TAB:\n\t\t\tKey_Event( K_TAB, down ); break;\n\t\tdefault:\n\t\t\t{\n\t\t\t\tint ch;\n\n\t\t\t\tif( !down )\n\t\t\t\t{\n\t\t\t\t\tif( osk.shift && osk.curlayout & 1 )\n\t\t\t\t\t\tosk.curlayout--;\n\n\t\t\t\t\tosk.shift = false;\n\t\t\t\t\tosk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tch = (byte)osk.curbutton.val;\n\n\t\t\t\t// do not pass UTF-8 sequence into the engine, convert it here\n\t\t\t\tif( !cls.accept_utf8 )\n\t\t\t\t\tch = Con_UtfProcessCharForce( ch );\n\n\t\t\t\tif( !ch )\n\t\t\t\t\tbreak;\n\n\t\t\t\tCL_CharEvent( ch );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase K_UPARROW:\n\t\tif( down && --osk.curbutton.y < 0 )\n\t\t{\n\t\t\tosk.curbutton.y = MAX_OSK_LINES - 1;\n\t\t\tosk.curbutton.val = 0;\n\t\t\treturn true;\n\t\t}\n\t\tbreak;\n\tcase K_DOWNARROW:\n\t\tif( down && ++osk.curbutton.y >= MAX_OSK_LINES )\n\t\t{\n\t\t\tosk.curbutton.y = 0;\n\t\t\tosk.curbutton.val = 0;\n\t\t\treturn true;\n\t\t}\n\t\tbreak;\n\tcase K_LEFTARROW:\n\t\tif( down && --osk.curbutton.x < 0 )\n\t\t\tosk.curbutton.x = MAX_OSK_ROWS - 1;\n\t\tbreak;\n\tcase K_RIGHTARROW:\n\t\tif( down && ++osk.curbutton.x >= MAX_OSK_ROWS )\n\t\t\tosk.curbutton.x = 0;\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\tosk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];\n\treturn true;\n\n}\n\n/*\n=============\nOSK_EnableTextInput\n\nEnables built-in IME\n=============\n*/\nstatic void OSK_EnableTextInput( qboolean enable, qboolean force )\n{\n\tqboolean old = osk.enable;\n\n\tosk.enable = enable;\n\n\tif( osk.enable && (!old || force) )\n\t{\n\t\tosk.curlayout = 0;\n\t\tosk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];\n\t}\n}\n\n#define X_START 0.1347475f\n#define Y_START 0.567f\n#define X_STEP 0.05625\n#define Y_STEP 0.0825\n\n/*\n============\nJoy_DrawSymbolButton\n\nDraw button with symbol on it\n============\n*/\nstatic void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )\n{\n\tcl_font_t *font = Con_GetCurFont();\n\tbyte color[] = { 255, 255, 255, 255 };\n\tint x1 = x * refState.width,\n\t\ty1 = y * refState.height,\n\t\tw = width * refState.width,\n\t\th = height * refState.height;\n\n\tif( symb == osk.curbutton.val )\n\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );\n\n\tif( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) )\n\t\treturn;\n\n\tCL_DrawCharacter(\n\t\tx1 + width * 0.4 * refState.width,\n\t\ty1 + height * 0.4 * refState.height,\n\t\tsymb, color, font, 0 );\n}\n\n/*\n=============\nJoy_DrawSpecialButton\n\nDraw special button, like shift, enter or esc\n=============\n*/\nstatic void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )\n{\n\tbyte color[] = { 0, 255, 0, 255 };\n\n\tCon_DrawString(\n\t\tx * refState.width + width * 0.4 * refState.width,\n\t\ty * refState.height + height * 0.4 * refState.height,\n\t\tname,\n\t\tcolor );\n}\n\n\n/*\n=============\nJoy_DrawOnScreenKeyboard\n\nDraw on screen keyboard, if enabled\n=============\n*/\nvoid OSK_Draw( void )\n{\n\tconst char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)\n\tfloat  x, y;\n\tint i, j;\n\n\tif( !osk.enable || !osk_enable.value || !osk.curbutton.val )\n\t\treturn;\n\n\t// draw keyboard\n\tref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,\n\t\t\t\t\t  X_STEP * MAX_OSK_ROWS * refState.width,\n\t\t\t\t\t  Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );\n\n\tOSK_DrawSpecialButton( \"-]\",   X_START,               Y_START + Y_STEP * 2, X_STEP, Y_STEP );\n\tOSK_DrawSpecialButton( \"<-\",  X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );\n\n\tOSK_DrawSpecialButton( \"sh\", X_START,               Y_START + Y_STEP * 3, X_STEP, Y_STEP );\n\tOSK_DrawSpecialButton( \"en\", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );\n\n\tfor( y = Y_START,     j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )\n\t\tfor( x = X_START, i = 0; i < MAX_OSK_ROWS;  i++, x += X_STEP )\n\t\t\tOSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );\n}\n"
  },
  {
    "path": "engine/client/mod_dbghulls.c",
    "content": "/*\nmod_dbghulls.c - loading & handling world and brushmodels\nCopyright (C) 2016 Uncle Mike\nCopyright (C) 2005 Kevin Shanahan\nCopyright (C) 1996-1997 Id Software, Inc.\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"client.h\"\n#include \"mod_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"world.h\"\n#include \"eiface.h\" // offsetof\n\n#define MAX_CLIPNODE_DEPTH\t\t256\t// should never exceeds\n\n#define list_entry( ptr, type, member ) \\\n\t((type *)((char *)(ptr) - (size_t)(&((type *)0)->member)))\n\n// iterate over each entry in the list\n#define list_for_each_entry( pos, head, member )\t\t\t\\\n\tfor( pos = list_entry( (head)->next, winding_t, member );\t\\\n\t     &pos->member != (head);\t\t\t\t\\\n\t     pos = list_entry( pos->member.next, winding_t, member ))\n\n// iterate over the list, safe for removal of entries\n#define list_for_each_entry_safe( pos, n, head, member )\t\t\\\n\tfor( pos = list_entry( (head)->next, winding_t, member ),\t\\\n\t     n = list_entry( pos->member.next, winding_t, member );\t\\\n\t     &pos->member != (head);\t\t\t\t\\\n\t     pos = n, n = list_entry( n->member.next, winding_t, member ))\n\n#define LIST_HEAD_INIT( name ) { &(name), &(name) }\n\n_inline void list_add__( hullnode_t *new, hullnode_t *prev, hullnode_t *next )\n{\n\tnext->prev = new;\n\tnew->next = next;\n\tnew->prev = prev;\n\tprev->next = new;\n}\n\n// add the new entry after the give list entry\n_inline void list_add( hullnode_t *newobj, hullnode_t *head )\n{\n\tlist_add__( newobj, head, head->next );\n}\n\n// add the new entry before the given list entry (list is circular)\n_inline void list_add_tail( hullnode_t *newobj, hullnode_t *head )\n{\n\tlist_add__( newobj, head->prev, head );\n}\n\n_inline void list_del( hullnode_t *entry )\n{\n\tentry->next->prev = entry->prev;\n\tentry->prev->next = entry->next;\n}\n\nstatic winding_t * winding_alloc( uint numpoints )\n{\n\treturn (winding_t *)malloc( offsetof( winding_t, p[numpoints] ) );\n}\n\nstatic void free_winding( winding_t *w )\n{\n\t// simple sentinel by Carmack\n\tif( *(unsigned *)w == 0xDEADC0DE )\n\t\tHost_Error( \"%s: freed a freed winding\\n\", __func__ );\n\t*(unsigned *)w = 0xDEADC0DE;\n\tfree( w );\n}\n\nstatic winding_t *winding_copy( winding_t *w )\n{\n\twinding_t\t*neww;\n\n\tneww = winding_alloc( w->numpoints );\n\tmemcpy( neww, w, offsetof( winding_t, p[w->numpoints] ) );\n\n\treturn neww;\n}\n\nstatic void winding_reverse( winding_t *w )\n{\n\tvec3_t\tpoint;\n\tint\ti;\n\n\tfor( i = 0; i < w->numpoints / 2; i++ )\n\t{\n\t\tVectorCopy( w->p[i], point );\n\t\tVectorCopy( w->p[w->numpoints - i - 1], w->p[i] );\n\t\tVectorCopy( point, w->p[w->numpoints - i - 1] );\n\t}\n}\n\n/*\n * winding_shrink\n *\n * Takes an over-allocated winding and allocates a new winding with just the\n * required number of points. The input winding is freed.\n */\nstatic winding_t *winding_shrink( winding_t *w )\n{\n\twinding_t\t*neww = winding_alloc( w->numpoints );\n\tmemcpy( neww, w, offsetof( winding_t, p[w->numpoints] ));\n\tfree_winding( w );\n\n\treturn neww;\n}\n\n/*\n====================\nwinding_for_plane\n====================\n*/\nstatic winding_t *winding_for_plane( const mplane_t *p )\n{\n\tvec3_t\torg, vright, vup;\n\tint\ti, axis;\n\tvec_t\tmax, v;\n\twinding_t\t*w;\n\n\t// find the major axis\n\tmax = -BOGUS_RANGE;\n\taxis = -1;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tv = fabs( p->normal[i] );\n\t\tif( v > max )\n\t\t{\n\t\t\taxis = i;\n\t\t\tmax = v;\n\t\t}\n\t}\n\n\tVectorClear( vup );\n\tswitch( axis )\n\t{\n\tcase 0:\n\tcase 1:\n\t\tvup[2] = 1;\n\t\tbreak;\n\tcase 2:\n\t\tvup[0] = 1;\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: no axis found\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\tv = DotProduct( vup, p->normal );\n\tVectorMA( vup, -v, p->normal, vup );\n\tVectorNormalize( vup );\n\tVectorScale( p->normal, p->dist, org );\n\tCrossProduct( vup, p->normal, vright );\n\tVectorScale( vup, BOGUS_RANGE, vup );\n\tVectorScale( vright, BOGUS_RANGE, vright );\n\n\t// project a really big axis aligned box onto the plane\n\tw = winding_alloc( 4 );\n\tmemset( w->p, 0, sizeof( vec3_t ) * 4 );\n\tw->numpoints = 4;\n\tw->plane = p;\n\n\tVectorSubtract( org, vright, w->p[0] );\n\tVectorAdd( w->p[0], vup, w->p[0] );\n\tVectorAdd( org, vright, w->p[1] );\n\tVectorAdd( w->p[1], vup, w->p[1] );\n\tVectorAdd( org, vright, w->p[2] );\n\tVectorSubtract( w->p[2], vup, w->p[2] );\n\tVectorSubtract( org, vright, w->p[3] );\n\tVectorSubtract( w->p[3], vup, w->p[3] );\n\n\treturn w;\n}\n\n/*\n * ===========================\n * Helper for for the clipping functions\n *  (winding_clip, winding_split)\n * ===========================\n */\nstatic void CalcSides( const winding_t *in, const mplane_t *split, int *sides, vec_t *dists, int counts[3], vec_t epsilon )\n{\n\tconst vec_t\t*p;\n\tint\t\ti;\n\n\tcounts[0] = counts[1] = counts[2] = 0;\n\n\tswitch( split->type )\n\t{\n\tcase PLANE_X:\n\tcase PLANE_Y:\n\tcase PLANE_Z:\n\t\tp = in->p[0] + split->type;\n\t\tfor( i = 0; i < in->numpoints; i++, p += 3 )\n\t\t{\n\t\t\tconst vec_t dot = *p - split->dist;\n\n\t\t\tdists[i] = dot;\n\t\t\tif( dot > epsilon )\n\t\t\t\tsides[i] = SIDE_FRONT;\n\t\t\telse if( dot < -epsilon )\n\t\t\t\tsides[i] = SIDE_BACK;\n\t\t\telse sides[i] = SIDE_ON;\n\t\t\tcounts[sides[i]]++;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tp = in->p[0];\n\t\tfor( i = 0; i < in->numpoints; i++, p += 3 )\n\t\t{\n\t\t\tconst vec_t dot = DotProduct( split->normal, p ) - split->dist;\n\n\t\t\tdists[i] = dot;\n\t\t\tif( dot > epsilon )\n\t\t\t\tsides[i] = SIDE_FRONT;\n\t\t\telse if( dot < -epsilon )\n\t\t\t\tsides[i] = SIDE_BACK;\n\t\t\telse sides[i] = SIDE_ON;\n\t\t\tcounts[sides[i]]++;\n\t\t}\n\t\tbreak;\n\t}\n\n\tsides[i] = sides[0];\n\tdists[i] = dists[0];\n}\n\nstatic void PushToPlaneAxis( vec_t *v, const mplane_t *p )\n{\n\tconst int\tt = p->type % 3;\n\n\tv[t] = (p->dist - p->normal[(t + 1) % 3] * v[(t + 1) % 3] - p->normal[(t + 2) % 3] * v[(t + 2) % 3]) / p->normal[t];\n}\n\n/*\n==================\nwinding_clip\n\nClips the winding to the plane, returning the new winding on 'side'.\nFrees the input winding.\nIf keepon is true, an exactly on-plane winding will be saved, otherwise\n  it will be clipped away.\n==================\n*/\nstatic winding_t *winding_clip( winding_t *in, const mplane_t *split, qboolean keepon, int side, vec_t epsilon )\n{\n\tvec_t\t*dists;\n\tint\t*sides;\n\tint\tcounts[3];\n\tvec_t\tdot;\n\tint\ti, j;\n\twinding_t *neww;\n\tvec_t\t*p1, *p2, *mid;\n\tint\tmaxpts;\n\n\tdists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));\n\tsides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));\n\tCalcSides( in, split, sides, dists, counts, epsilon );\n\n\tif( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] )\n\t{\n\t\tneww = in;\n\t\tgoto out_free;\n\t}\n\n\tif( !counts[side] )\n\t{\n\t\tfree_winding( in );\n\t\tneww = NULL;\n\t\tgoto out_free;\n\t}\n\n\tif( !counts[side ^ 1] )\n\t{\n\t\tneww = in;\n\t\tgoto out_free;\n\t}\n\n\tmaxpts = in->numpoints + 4;\n\tneww = winding_alloc( maxpts );\n\tneww->numpoints = 0;\n\tneww->plane = in->plane;\n\n\tfor( i = 0; i < in->numpoints; i++ )\n\t{\n\t\tp1 = in->p[i];\n\n\t\tif( sides[i] == SIDE_ON )\n\t\t{\n\t\t\tVectorCopy( p1, neww->p[neww->numpoints] );\n\t\t\tneww->numpoints++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( sides[i] == side )\n\t\t{\n\t\t\tVectorCopy( p1, neww->p[neww->numpoints] );\n\t\t\tneww->numpoints++;\n\t\t}\n\n\t\tif( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] )\n\t\t\tcontinue;\n\n\t\t// generate a split point\n\t\tp2 = in->p[(i + 1) % in->numpoints];\n\t\tmid = neww->p[neww->numpoints++];\n\n\t\tdot = dists[i] / (dists[i] - dists[i + 1]);\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\t// avoid round off error when possible\n\t\t\tif( in->plane->normal[j] == 1.0f )\n\t\t\t\tmid[j] = in->plane->dist;\n\t\t\telse if( in->plane->normal[j] == -1.0f )\n\t\t\t\tmid[j] = -in->plane->dist;\n\t\t\telse if( split->normal[j] == 1.0f )\n\t\t\t\tmid[j] = split->dist;\n\t\t\telse if( split->normal[j] == -1.0f )\n\t\t\t\tmid[j] = -split->dist;\n\t\t\telse mid[j] = p1[j] + dot * (p2[j] - p1[j]);\n\t\t}\n\n\t\tif( in->plane->type < 3 )\n\t\t\tPushToPlaneAxis( mid, in->plane );\n\t}\n\n\t// free the original winding\n\tfree_winding( in );\n\n\t// Shrink the winding back to just what it needs...\n\tneww = winding_shrink(neww);\nout_free:\n\tfree( dists );\n\tfree( sides );\n\n\treturn neww;\n}\n\n/*\n==================\nwinding_split\n\nSplits a winding by a plane, producing one or two windings.  The\noriginal winding is not damaged or freed.  If only on one side, the\nreturned winding will be the input winding.  If on both sides, two\nnew windings will be created.\n==================\n*/\nstatic void winding_split( winding_t *in, const mplane_t *split, winding_t **pfront, winding_t **pback )\n{\n\tvec_t\t*dists;\n\tint\t*sides;\n\tint\tcounts[3];\n\tvec_t\tdot;\n\tint\ti, j;\n\twinding_t\t*front, *back;\n\tvec_t\t*p1, *p2, *mid;\n\tint\tmaxpts;\n\n\tdists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));\n\tsides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));\n\tCalcSides(in, split, sides, dists, counts, 0.04f );\n\n\tif( !counts[0] && !counts[1] )\n\t{\n\t\t// winding on the split plane - return copies on both sides\n\t\t*pfront = winding_copy( in );\n\t\t*pback = winding_copy( in );\n\t\tgoto out_free;\n\t}\n\n\tif( !counts[0] )\n\t{\n\t\t*pfront = NULL;\n\t\t*pback = in;\n\t\tgoto out_free;\n\t}\n\n\tif( !counts[1] )\n\t{\n\t\t*pfront = in;\n\t\t*pback = NULL;\n\t\tgoto out_free;\n\t}\n\n\tmaxpts = in->numpoints + 4;\n\tfront = winding_alloc( maxpts );\n\tfront->numpoints = 0;\n\tfront->plane = in->plane;\n\tback = winding_alloc( maxpts );\n\tback->numpoints = 0;\n\tback->plane = in->plane;\n\n\tfor( i = 0; i < in->numpoints; i++ )\n\t{\n\t\tp1 = in->p[i];\n\n\t\tif( sides[i] == SIDE_ON )\n\t\t{\n\t\t\tVectorCopy( p1, front->p[front->numpoints] );\n\t\t\tVectorCopy( p1, back->p[back->numpoints] );\n\t\t\tfront->numpoints++;\n\t\t\tback->numpoints++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( sides[i] == SIDE_FRONT )\n\t\t{\n\t\t\tVectorCopy( p1, front->p[front->numpoints] );\n\t\t\tfront->numpoints++;\n\t\t}\n\t\telse if( sides[i] == SIDE_BACK )\n\t\t{\n\t\t\tVectorCopy( p1, back->p[back->numpoints] );\n\t\t\tback->numpoints++;\n\t\t}\n\n\t\tif( sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i] )\n\t\t\tcontinue;\n\n\t\t// generate a split point\n\t\tp2 = in->p[(i + 1) % in->numpoints];\n\t\tmid = front->p[front->numpoints++];\n\n\t\tdot = dists[i] / (dists[i] - dists[i + 1]);\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\t// avoid round off error when possible\n\t\t\tif( in->plane->normal[j] == 1.0f )\n\t\t\t\tmid[j] = in->plane->dist;\n\t\t\telse if( in->plane->normal[j] == -1.0f )\n\t\t\t\tmid[j] = -in->plane->dist;\n\t\t\telse if( split->normal[j] == 1.0f )\n\t\t\t\tmid[j] = split->dist;\n\t\t\telse if( split->normal[j] == -1.0f )\n\t\t\t\tmid[j] = -split->dist;\n\t\t\telse mid[j] = p1[j] + dot * (p2[j] - p1[j]);\n\t\t}\n\n\t\tif( in->plane->type < 3 )\n\t\t\tPushToPlaneAxis( mid, in->plane );\n\t\tVectorCopy( mid, back->p[back->numpoints] );\n\t\tback->numpoints++;\n\t}\n\n\t*pfront = winding_shrink( front );\n\t*pback = winding_shrink( back );\nout_free:\n\tfree( dists );\n\tfree( sides );\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * This is a stack of the clipnodes we have traversed\n * \"sides\" indicates which side we went down each time\n */\nstatic int\tnode_stack[MAX_CLIPNODE_DEPTH];\nstatic int\tside_stack[MAX_CLIPNODE_DEPTH];\nstatic uint\tnode_stack_depth;\n\nstatic void push_node( int nodenum, int side )\n{\n\tif( node_stack_depth == MAX_CLIPNODE_DEPTH )\n\t\tHost_Error( \"node stack overflow\\n\" );\n\n\tnode_stack[node_stack_depth] = nodenum;\n\tside_stack[node_stack_depth] = side;\n\tnode_stack_depth++;\n}\n\nstatic void pop_node( void )\n{\n\tif( !node_stack_depth )\n\t\tHost_Error( \"node stack underflow\\n\" );\n\tnode_stack_depth--;\n}\n\nstatic void free_hull_polys( hullnode_t *hull_polys )\n{\n\twinding_t\t*w, *next;\n\n\tlist_for_each_entry_safe( w, next, hull_polys, chain )\n\t{\n\t\tlist_del( &w->chain );\n\t\tfree_winding( w );\n\t}\n}\n\nstatic void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model );\n\nstatic void do_hull_recursion( hull_t *hull, int nodenum, int side, hullnode_t *polys, hull_model_t *model )\n{\n\twinding_t\t*w, *next;\n\tint childnum;\n\n\tif( world.version == QBSP2_VERSION )\n\t\tchildnum = hull->clipnodes32[nodenum].children[side];\n\telse\n\t\tchildnum = hull->clipnodes16[nodenum].children[side];\n\n\tif( childnum >= 0 )\n\t{\n\t\tpush_node( nodenum, side );\n\t\thull_windings_r( hull, childnum, polys, model );\n\t\tpop_node();\n\t}\n\telse\n\t{\n\t\tswitch( childnum )\n\t\t{\n\t\tcase CONTENTS_EMPTY:\n\t\tcase CONTENTS_WATER:\n\t\tcase CONTENTS_SLIME:\n\t\tcase CONTENTS_LAVA:\n\t\t\tlist_for_each_entry_safe( w, next, polys, chain )\n\t\t\t{\n\t\t\t\tlist_del( &w->chain );\n\t\t\t\tlist_add( &w->chain, &model->polys );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase CONTENTS_SOLID:\n\t\tcase CONTENTS_SKY:\n\t\t\t// throw away polys...\n\t\t\tlist_for_each_entry_safe( w, next, polys, chain )\n\t\t\t{\n\t\t\t\tif( w->pair )\n\t\t\t\t\tw->pair->pair = NULL;\n\t\t\t\tlist_del( &w->chain );\n\t\t\t\tfree_winding( w );\n\t\t\t\tmodel->num_polys--;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tHost_Error( \"bad contents: %i\\n\", childnum );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model )\n{\n\tmplane_t *plane;\n\thullnode_t\tfrontlist = LIST_HEAD_INIT( frontlist );\n\thullnode_t\tbacklist = LIST_HEAD_INIT( backlist );\n\twinding_t\t\t*w, *next, *front, *back;\n\tint\ti;\n\n\tif( world.version == QBSP2_VERSION )\n\t\tplane = hull->planes + hull->clipnodes32[nodenum].planenum;\n\telse\n\t\tplane = hull->planes + hull->clipnodes16[nodenum].planenum;\n\n\tlist_for_each_entry_safe( w, next, polys, chain )\n\t{\n\t\t// PARANIOA - PAIR CHECK\n\t\tASSERT( !w->pair || w->pair->pair == w );\n\n\t\tlist_del( &w->chain );\n\t\twinding_split( w, plane, &front, &back );\n\t\tif( front ) list_add( &front->chain, &frontlist );\n\t\tif( back ) list_add( &back->chain, &backlist );\n\n\t\tif( front && back )\n\t\t{\n\t\t\tif( w->pair )\n\t\t\t{\n\t\t\t\t// split the paired poly, preserve pairing\n\t\t\t\twinding_t\t*front2, *back2;\n\n\t\t\t\twinding_split( w->pair, plane, &front2, &back2 );\n\n\t\t\t\tfront2->pair = front;\n\t\t\t\tfront->pair = front2;\n\t\t\t\tback2->pair = back;\n\t\t\t\tback->pair = back2;\n\n\t\t\t\tlist_add( &front2->chain, &w->pair->chain );\n\t\t\t\tlist_add( &back2->chain, &w->pair->chain );\n\t\t\t\tlist_del( &w->pair->chain );\n\t\t\t\tfree_winding( w->pair );\n\t\t\t\tmodel->num_polys++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfront->pair = NULL;\n\t\t\t\tback->pair = NULL;\n\t\t\t}\n\n\t\t\tmodel->num_polys++;\n\t\t\tfree_winding( w );\n\t\t}\n\t}\n\n\tw = winding_for_plane(plane);\n\n\tfor( i = 0; w && i < node_stack_depth; i++ )\n\t{\n\t\tmplane_t *p;\n\n\t\tif( world.version == QBSP2_VERSION )\n\t\t\tp = hull->planes + hull->clipnodes32[node_stack[i]].planenum;\n\t\telse\n\t\t\tp = hull->planes + hull->clipnodes16[node_stack[i]].planenum;\n\n\t\tw = winding_clip( w, p, false, side_stack[i], 0.00001 );\n\t}\n\n\tif( w )\n\t{\n\t\twinding_t *tmp = winding_copy( w );\n\t\twinding_reverse( tmp );\n\n\t\tw->pair = tmp;\n\t\ttmp->pair = w;\n\n\t\tlist_add( &w->chain, &frontlist );\n\t\tlist_add( &tmp->chain, &backlist );\n\n\t\t// PARANIOA - PAIR CHECK\n\t\tASSERT( !w->pair || w->pair->pair == w );\n\t\tmodel->num_polys += 2;\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_WARN \"new winding was clipped away!\\n\" );\n\t}\n\n\tdo_hull_recursion( hull, nodenum, 0, &frontlist, model );\n\tdo_hull_recursion( hull, nodenum, 1, &backlist, model );\n}\n\nstatic void remove_paired_polys( hull_model_t *model )\n{\n\twinding_t\t*w, *next;\n\n\tlist_for_each_entry_safe( w, next, &model->polys, chain )\n\t{\n\t\tif( w->pair )\n\t\t{\n\t\t\tlist_del( &w->chain );\n\t\t\tfree_winding( w );\n\t\t\tmodel->num_polys--;\n\t\t}\n\t}\n}\n\nstatic void make_hull_windings( hull_t *hull, hull_model_t *model )\n{\n\thullnode_t head = LIST_HEAD_INIT( head );\n\n\tCon_Reportf( \"%i clipnodes...\\n\", hull->lastclipnode - hull->firstclipnode );\n\n\tnode_stack_depth = 0;\n\tmodel->num_polys = 0;\n\n\tif( hull->planes != NULL )\n\t{\n\t\thull_windings_r( hull, hull->firstclipnode, &head, model );\n\t\tremove_paired_polys( model );\n\t}\n\tCon_Reportf( \"%i hull polys\\n\", model->num_polys );\n}\n\nstatic void Mod_InitDebugHulls( model_t *loadmodel )\n{\n\tint\ti;\n\n\tworld.hull_models = Mem_Calloc( loadmodel->mempool, sizeof( hull_model_t ) * loadmodel->numsubmodels );\n\tworld.num_hull_models = loadmodel->numsubmodels;\n\n\t// initialize list\n\tfor( i = 0; i < world.num_hull_models; i++ )\n\t{\n\t\thullnode_t *poly = &world.hull_models[i].polys;\n\t\tpoly->next = poly;\n\t\tpoly->prev = poly;\n\t}\n}\n\nstatic void Mod_CreatePolygonsForHull( int hullnum )\n{\n\tmodel_t\t*mod = cl.worldmodel;\n\tdouble\tstart, end;\n\tchar\tname[8];\n\tint\ti;\n\n\tif( hullnum < 0 || hullnum >= MAX_MAP_HULLS )\n\t\treturn;\n\n\tif( !world.num_hull_models )\n\t\tMod_InitDebugHulls( mod ); // FIXME: build hulls for separate bmodels (shells, medkits etc)\n\n\tCon_Printf( \"generating polygons for hull %u...\\n\", hullnum );\n\tstart = Sys_DoubleTime();\n\n\t// rebuild hulls list\n\tfor( i = 0; i < world.num_hull_models; i++ )\n\t{\n\t\thull_model_t *model = &world.hull_models[i];\n\t\tfree_hull_polys( &model->polys );\n\t\tmake_hull_windings( &mod->hulls[hullnum], model );\n\t\tQ_snprintf( name, sizeof( name ), \"*%i\", i + 1 );\n\t\tmod = Mod_FindName( name, false );\n\t}\n\tend = Sys_DoubleTime();\n\tCon_Printf( \"build time %.3f secs\\n\", end - start );\n}\n\nstatic void R_DrawHull( hull_model_t *hull )\n{\n\twinding_t *poly;\n\n\tref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE ));\n\tref.dllFuncs.TriRenderMode( kRenderNormal );\n\tlist_for_each_entry( poly, &hull->polys, chain )\n\t{\n\t\tint i;\n\n\t\tsrand((unsigned int)poly );\n\t\tref.dllFuncs.Color4ub( rand() & 255, rand() & 255, rand() & 255, 255 );\n\n\t\tref.dllFuncs.Begin( TRI_POLYGON );\n\t\tfor( i = 0; i < poly->numpoints; i++ )\n\t\t\tref.dllFuncs.Vertex3fv( poly->p[i] );\n\t\tref.dllFuncs.End();\n\t}\n}\n\nvoid R_DrawWorldHull( void )\n{\n\tif( r_showhull.value <= 0.0f )\n\t\treturn;\n\n\tif( FBitSet( r_showhull.flags, FCVAR_CHANGED ))\n\t{\n\t\tint val = r_showhull.value;\n\t\tif( val > 3 ) val = 0;\n\t\tMod_CreatePolygonsForHull( val );\n\t\tClearBits( r_showhull.flags, FCVAR_CHANGED );\n\t}\n\n\tR_DrawHull( &world.hull_models[0] );\n}\n\nvoid R_DrawModelHull( model_t *mod )\n{\n\tint i;\n\n\tif( r_showhull.value <= 0.0f )\n\t\treturn;\n\n\tif( !mod || mod->name[0] != '*' )\n\t\treturn;\n\n\ti = atoi( mod->name + 1 );\n\tif( i < 1 || i >= world.num_hull_models )\n\t\treturn;\n\n\tR_DrawHull( &world.hull_models[i] );\n}\n\nvoid Mod_ReleaseHullPolygons( void )\n{\n\tint\ti;\n\n\t// release ploygons\n\tfor( i = 0; i < world.num_hull_models; i++ )\n\t{\n\t\thull_model_t *model = &world.hull_models[i];\n\t\tfree_hull_polys( &model->polys );\n\t}\n\tworld.num_hull_models = 0;\n}\n"
  },
  {
    "path": "engine/client/ref_common.c",
    "content": "#include \"common.h\"\n#include \"client.h\"\n#include \"library.h\"\n#include \"cl_tent.h\"\n#include \"platform/platform.h\"\n#include \"vid_common.h\"\n\nstruct ref_state_s ref;\nref_globals_t refState;\n\nstatic const char* r_skyBoxSuffix[SKYBOX_MAX_SIDES] = { \"rt\", \"bk\", \"lf\", \"ft\", \"up\", \"dn\" };\n\nCVAR_DEFINE_AUTO( gl_vsync, \"1\", FCVAR_ARCHIVE,  \"enable vertical syncronization\" );\nCVAR_DEFINE_AUTO( r_showtextures, \"0\", FCVAR_CHEAT, \"show all uploaded textures\" );\nCVAR_DEFINE_AUTO( r_adjust_fov, \"1\", FCVAR_ARCHIVE, \"making FOV adjustment for wide-screens\" );\nCVAR_DEFINE_AUTO( r_decals, \"4096\", FCVAR_ARCHIVE, \"sets the maximum number of decals\" );\nCVAR_DEFINE_AUTO( gl_msaa_samples, \"0\", FCVAR_GLCONFIG, \"samples number for multisample anti-aliasing\" );\nCVAR_DEFINE_AUTO( gl_clear, \"0\", FCVAR_ARCHIVE, \"clearing screen after each frame\" );\nCVAR_DEFINE_AUTO( r_showtree, \"0\", FCVAR_ARCHIVE, \"build the graph of visible BSP tree\" );\nstatic CVAR_DEFINE_AUTO( r_refdll, \"\", FCVAR_RENDERINFO, \"choose renderer implementation, if supported\" );\nstatic CVAR_DEFINE_AUTO( r_refdll_loaded, \"\", FCVAR_READ_ONLY, \"currently loaded renderer\" );\n\n// there is no need to expose whole host and cl structs into the renderer\n// but we still need to update timings accurately as possible\n// this looks horrible but the only other option would be passing four\n// time pointers and then it's looks even worse with dereferences everywhere\n#define STATIC_OFFSET_CHECK( s1, s2, field, base, msg ) \\\n\tSTATIC_ASSERT( offsetof( s1, field ) == offsetof( s2, field ) - offsetof( s2, base ), msg )\n#define REF_CLIENT_CHECK( field ) \\\n\tSTATIC_OFFSET_CHECK( ref_client_t, client_t, field, time, \"broken ref_client_t offset\" ); \\\n\tSTATIC_ASSERT_( szchk_##__LINE__, sizeof(((ref_client_t *)0)->field ) == sizeof( cl.field ), \"broken ref_client_t size\" )\n#define REF_HOST_CHECK( field ) \\\n\tSTATIC_OFFSET_CHECK( ref_host_t, host_parm_t, field, realtime, \"broken ref_client_t offset\" ); \\\n\tSTATIC_ASSERT_( szchk_##__LINE__, sizeof(((ref_host_t *)0)->field ) == sizeof( host.field ), \"broken ref_client_t size\" )\n\nREF_CLIENT_CHECK( time );\nREF_CLIENT_CHECK( oldtime );\nREF_CLIENT_CHECK( viewentity );\nREF_CLIENT_CHECK( playernum );\nREF_CLIENT_CHECK( maxclients );\nREF_CLIENT_CHECK( models );\nREF_CLIENT_CHECK( paused );\nREF_CLIENT_CHECK( simorg );\nREF_HOST_CHECK( realtime );\nREF_HOST_CHECK( frametime );\nREF_HOST_CHECK( features );\n\nstatic qboolean CheckSkybox( const char *name, char out[SKYBOX_MAX_SIDES][MAX_STRING] )\n{\n\tstatic const char *skybox_ext[3] = { \"dds\", \"tga\", \"bmp\" };\n\tstatic const char *skybox_delim[2] = { \"\", \"_\" }; // no space for HL style, underscore for Q1 style\n\tint\ti;\n\n\t// search for skybox images\n\tfor( i = 0; i <\tARRAYSIZE( skybox_ext ); i++ )\n\t{\n\t\tint j;\n\n\t\tfor( j = 0; j < ARRAYSIZE( skybox_delim ); j++ )\n\t\t{\n\t\t\tint k, num_checked_sides = 0;\n\n\t\t\tfor( k = 0; k < SKYBOX_MAX_SIDES; k++ )\n\t\t\t{\n\t\t\t\tchar sidename[MAX_VA_STRING];\n\n\t\t\t\tQ_snprintf( sidename, sizeof( sidename ), \"%s%s%s.%s\", name, skybox_delim[j], r_skyBoxSuffix[k], skybox_ext[i] );\n\t\t\t\tif( g_fsapi.FileExists( sidename, false ))\n\t\t\t\t{\n\t\t\t\t\tQ_strncpy( out[k], sidename, sizeof( out[k] ));\n\t\t\t\t\tnum_checked_sides++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( num_checked_sides == SKYBOX_MAX_SIDES )\n\t\t\t\treturn true; // image exists\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid R_SetupSky( const char *name )\n{\n\tstring loadname;\n\tchar sidenames[SKYBOX_MAX_SIDES][MAX_STRING];\n\tint skyboxTextures[SKYBOX_MAX_SIDES] = { 0 };\n\tint i, len;\n\tqboolean result;\n\n\tif( !COM_CheckString( name ))\n\t{\n\t\tref.dllFuncs.R_SetupSky( NULL ); // unload skybox\n\t\treturn;\n\t}\n\n\tQ_snprintf( loadname, sizeof( loadname ), \"gfx/env/%s\", name );\n\tCOM_StripExtension( loadname );\n\n\t// kill the underline suffix to find them manually later\n\tlen = Q_strlen( loadname );\n\n\tif( loadname[len - 1] == '_' )\n\t\tloadname[len - 1] = '\\0';\n\tresult = CheckSkybox( loadname, sidenames );\n\n\t// to prevent infinite recursion if default skybox was missed\n\tif( !result && Q_stricmp( name, DEFAULT_SKYBOX_NAME ))\n\t{\n\t\tCon_Reportf( S_WARN \"missed or incomplete skybox '%s'\\n\", name );\n\t\tR_SetupSky( DEFAULT_SKYBOX_NAME ); // force to default\n\t\treturn;\n\t}\n\n\tref.dllFuncs.R_SetupSky( NULL ); // unload skybox\n\tCon_DPrintf( \"SKY:  \" );\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tskyboxTextures[i] = ref.dllFuncs.GL_LoadTexture( sidenames[i], NULL, 0, TF_CLAMP|TF_SKY );\n\n\t\tif( !skyboxTextures[i] )\n\t\t\tbreak;\n\n\t\tCon_DPrintf( \"%s%s%s\", name, r_skyBoxSuffix[i], i != 5 ? \", \" : \". \" );\n\t}\n\n\tif( i == SKYBOX_MAX_SIDES )\n\t{\n\t\tSetBits( world.flags, FWORLD_CUSTOM_SKYBOX );\n\t\tCon_DPrintf( \"done\\n\" );\n\t\tref.dllFuncs.R_SetupSky( skyboxTextures );\n\t\treturn; // loaded\n\t}\n\n\tCon_DPrintf( \"^2failed\\n\" );\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tif( skyboxTextures[i] )\n\t\t\tref.dllFuncs.GL_FreeTexture( skyboxTextures[i] );\n\t}\n}\n\nvoid GAME_EXPORT GL_FreeImage( const char *name )\n{\n\tint\ttexnum;\n\n\tif( !ref.initialized )\n\t\treturn;\n\n\tif(( texnum = ref.dllFuncs.GL_FindTexture( name )) != 0 )\n\t\t ref.dllFuncs.GL_FreeTexture( texnum );\n}\n\nvoid GL_RenderFrame( const ref_viewpass_t *rvp )\n{\n\tVectorCopy( rvp->vieworigin, refState.vieworg );\n\tVectorCopy( rvp->viewangles, refState.viewangles );\n\n\tref.dllFuncs.GL_RenderFrame( rvp );\n}\n\nstatic intptr_t pfnEngineGetParm( int parm, int arg )\n{\n\treturn CL_RenderGetParm( parm, arg, false ); // prevent recursion\n}\n\nstatic cvar_t *pfnCvar_Get( const char *szName, const char *szValue, int flags, const char *description )\n{\n\treturn (cvar_t *)Cvar_Get( szName, szValue, flags | FCVAR_REFDLL, description );\n}\n\nstatic void pfnCvar_RegisterVariable( convar_t *var )\n{\n\tSetBits( var->flags, FCVAR_REFDLL );\n\tCvar_RegisterVariable( var );\n}\n\nstatic void pfnCvar_FullSet( const char *var_name, const char *value, int flags )\n{\n\tCvar_FullSet( var_name, value, flags | FCVAR_REFDLL );\n}\n\nstatic int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description )\n{\n\treturn Cmd_AddCommandEx( cmd_name, function, description, CMD_REFDLL, __func__ );\n}\n\nstatic void pfnStudioEvent( const mstudioevent_t *event, const cl_entity_t *e )\n{\n\tclgame.dllFuncs.pfnStudioEvent( event, e );\n}\n\nstatic model_t *pfnGetDefaultSprite( enum ref_defaultsprite_e spr )\n{\n\tswitch( spr )\n\t{\n\tcase REF_DOT_SPRITE: return cl_sprite_dot;\n\tcase REF_CHROME_SPRITE: return cl_sprite_shell;\n\tdefault: Host_Error( \"%s: unknown sprite %d\\n\", __func__, spr );\n\t}\n\treturn NULL;\n}\n\nstatic void *pfnMod_Extradata( int type, model_t *m )\n{\n\tswitch( type )\n\t{\n\tcase mod_alias: return Mod_AliasExtradata( m );\n\tcase mod_studio: return Mod_StudioExtradata( m );\n\tcase mod_sprite: // fallthrough\n\tcase mod_brush: return NULL;\n\tdefault: Host_Error( \"%s: unknown type %d\\n\", __func__, type );\n\t}\n\treturn NULL;\n}\n\nstatic void CL_ExtraUpdate( void )\n{\n\tclgame.dllFuncs.IN_Accumulate();\n\tS_ExtraUpdate();\n}\n\nstatic void pfnCL_GetScreenInfo( int *width, int *height ) // clgame.scrInfo, ptrs may be NULL\n{\n\tif( width ) *width = clgame.scrInfo.iWidth;\n\tif( height ) *height = clgame.scrInfo.iHeight;\n}\n\nstatic void pfnSetLocalLightLevel( int level )\n{\n\tcl.local.light_level = level;\n}\n\n/*\n===============\npfnPlayerInfo\n\n===============\n*/\nstatic player_info_t *pfnPlayerInfo( int index )\n{\n\tif( index == -1 ) // special index for menu\n\t\treturn &gameui.playerinfo;\n\n\tif( index < 0 || index >= cl.maxclients )\n\t\treturn NULL;\n\n\treturn &cl.players[index];\n}\n\n/*\n===============\npfnGetPlayerState\n\n===============\n*/\nstatic entity_state_t *R_StudioGetPlayerState( int index )\n{\n\tif( index < 0 || index >= cl.maxclients )\n\t\treturn NULL;\n\n\treturn &cl.frames[cl.parsecountmod].playerstate[index];\n}\n\nstatic int pfnGetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )\n{\n\treturn clgame.dllFuncs.pfnGetStudioModelInterface ?\n\t\tclgame.dllFuncs.pfnGetStudioModelInterface( version, ppinterface, pstudio ) :\n\t\t0;\n}\n\nstatic const bpc_desc_t *pfnImage_GetPFDesc( int idx )\n{\n\treturn &PFDesc[idx];\n}\n\nstatic void pfnDrawNormalTriangles( void )\n{\n\tclgame.dllFuncs.pfnDrawNormalTriangles();\n}\n\nstatic void pfnDrawTransparentTriangles( void )\n{\n\tclgame.dllFuncs.pfnDrawTransparentTriangles();\n}\n\nstatic screenfade_t *pfnRefGetScreenFade( void )\n{\n\treturn &clgame.fade;\n}\n\nstatic qboolean R_Init_Video_( const int type )\n{\n\thost.apply_opengl_config = true;\n\tCbuf_AddTextf( \"exec %s.cfg\", ref.dllFuncs.R_GetConfigName());\n\tCbuf_Execute();\n\thost.apply_opengl_config = false;\n\n\treturn R_Init_Video( type );\n}\n\nstatic mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )\n{\n\t// FIXME: get rid of this on next RefAPI update\n\treturn Mod_PointInLeaf( p, node, cl.models[1] );\n}\n\nstatic const ref_api_t gEngfuncs =\n{\n\tpfnEngineGetParm,\n\n\tpfnCvar_Get,\n\t(void*)Cvar_FindVarExt,\n\tCvar_VariableValue,\n\tCvar_VariableString,\n\tCvar_SetValue,\n\tCvar_Set,\n\tpfnCvar_RegisterVariable,\n\tpfnCvar_FullSet,\n\n\tCmd_AddRefCommand,\n\tCmd_RemoveCommand,\n\tCmd_Argc,\n\tCmd_Argv,\n\tCmd_Args,\n\n\tCbuf_AddText,\n\tCbuf_InsertText,\n\tCbuf_Execute,\n\n\tCon_Printf,\n\tCon_DPrintf,\n\tCon_Reportf,\n\n\tCon_NPrintf,\n\tCon_NXPrintf,\n\tCL_CenterPrint,\n\tCon_DrawStringLen,\n\tCon_DrawString,\n\tCL_DrawCenterPrint,\n\n\tR_BeamGetEntity,\n\tCL_GetWaterEntity,\n\tCL_AddVisibleEntity,\n\n\tMod_SampleSizeForFace,\n\tMod_BoxVisible,\n\tpfnMod_PointInLeaf,\n\tR_DrawWorldHull,\n\tR_DrawModelHull,\n\n\tR_StudioGetAnim,\n\tpfnStudioEvent,\n\n\tCL_DrawEFX,\n\tCL_ThinkParticle,\n\tR_FreeDeadParticles,\n\tCL_AllocParticleFast,\n\tCL_AllocElight,\n\tpfnGetDefaultSprite,\n\tR_StoreEfrags,\n\n\tMod_ForName,\n\tpfnMod_Extradata,\n\n\tCL_EntitySetRemapColors,\n\tCL_GetRemapInfoForEntity,\n\n\tCL_ExtraUpdate,\n\tHost_Error,\n\tCOM_SetRandomSeed,\n\tCOM_RandomFloat,\n\tCOM_RandomLong,\n\tpfnRefGetScreenFade,\n\tpfnCL_GetScreenInfo,\n\tpfnSetLocalLightLevel,\n\tSys_CheckParm,\n\n\tpfnPlayerInfo,\n\tR_StudioGetPlayerState,\n\tMod_CacheCheck,\n\tMod_LoadCacheFile,\n\tMod_Calloc,\n\tpfnGetStudioModelInterface,\n\n\t_Mem_AllocPool,\n\t_Mem_FreePool,\n\t_Mem_Alloc,\n\t_Mem_Realloc,\n\t_Mem_Free,\n\n\tCOM_LoadLibrary,\n\tCOM_FreeLibrary,\n\tCOM_GetProcAddress,\n\n\tR_Init_Video_,\n\tR_Free_Video,\n\n\tGL_SetAttribute,\n\tGL_GetAttribute,\n\tGL_GetProcAddress,\n\tGL_SwapBuffers,\n\n\tSW_CreateBuffer,\n\tSW_LockBuffer,\n\tSW_UnlockBuffer,\n\n\tR_FatPVS,\n\tGL_GetOverviewParms,\n\tSys_DoubleTime,\n\n\tpfnGetPhysent,\n\tpfnTraceSurface,\n\tPM_CL_TraceLine,\n\tCL_VisTraceLine,\n\tCL_TraceLine,\n\n\tImage_AddCmdFlags,\n\tImage_SetForceFlags,\n\tImage_ClearForceFlags,\n\tImage_CustomPalette,\n\tImage_Process,\n\tFS_LoadImage,\n\tFS_SaveImage,\n\tFS_CopyImage,\n\tFS_FreeImage,\n\tImage_SetMDLPointer,\n\tpfnImage_GetPFDesc,\n\n\tpfnDrawNormalTriangles,\n\tpfnDrawTransparentTriangles,\n\t&clgame.drawFuncs,\n\n\t&g_fsapi,\n\n\tR_GetWindowHandle,\n\n\tXVK_GetInstanceExtensions,\n\tXVK_GetVkGetInstanceProcAddr,\n\tXVK_CreateSurface,\n};\n\nstatic void R_UnloadProgs( void )\n{\n\tif( !ref.hInstance ) return;\n\n\t// deinitialize renderer\n\tref.dllFuncs.R_Shutdown();\n\n\tCvar_FullSet( \"host_refloaded\", \"0\", FCVAR_READ_ONLY );\n\n\tCvar_Unlink( FCVAR_RENDERINFO | FCVAR_GLCONFIG | FCVAR_REFDLL );\n\tCmd_Unlink( CMD_REFDLL );\n\n\tCOM_FreeLibrary( ref.hInstance );\n\tref.hInstance = NULL;\n\n\tmemset( &refState, 0, sizeof( refState ));\n\tmemset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));\n}\n\nstatic void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src )\n{\n\tdst->version           = TRI_API_VERSION;\n\tdst->Begin             = src->Begin;\n\tdst->RenderMode        = TriRenderMode;\n\tdst->End               = src->End;\n\tdst->Color4f           = TriColor4f;\n\tdst->Color4ub          = TriColor4ub;\n\tdst->TexCoord2f        = src->TexCoord2f;\n\tdst->Vertex3f          = src->Vertex3f;\n\tdst->Vertex3fv         = src->Vertex3fv;\n\tdst->Brightness        = TriBrightness;\n\tdst->CullFace          = TriCullFace;\n\tdst->SpriteTexture     = TriSpriteTexture;\n\tdst->WorldToScreen     = TriWorldToScreen;\n\tdst->Fog               = src->Fog;\n\tdst->ScreenToWorld     = src->ScreenToWorld;\n\tdst->GetMatrix         = src->GetMatrix;\n\tdst->BoxInPVS          = TriBoxInPVS;\n\tdst->LightAtPoint      = TriLightAtPoint;\n\tdst->Color4fRendermode = TriColor4fRendermode;\n\tdst->FogParams         = src->FogParams;\n}\n\nstatic qboolean R_LoadProgs( const char *name )\n{\n\tstatic ref_api_t gpEngfuncs;\n\tREFAPI GetRefAPI; // single export\n\n\tif( ref.hInstance ) R_UnloadProgs();\n\n\tFS_AllowDirectPaths( true );\n\tif( !( ref.hInstance = COM_LoadLibrary( name, false, true )))\n\t{\n\t\tFS_AllowDirectPaths( false );\n\t\tCon_Reportf( \"%s: can't load renderer library %s: %s\\n\", __func__, name, COM_GetLibraryError() );\n\t\treturn false;\n\t}\n\n\tFS_AllowDirectPaths( false );\n\n\tif( !( GetRefAPI = (REFAPI)COM_GetProcAddress( ref.hInstance, GET_REF_API )))\n\t{\n\t\tCon_Reportf( \"%s: can't find GetRefAPI entry point in %s\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// make local copy of engfuncs to prevent overwrite it with user dll\n\tgpEngfuncs = gEngfuncs;\n\n\tif( GetRefAPI( REF_API_VERSION, &ref.dllFuncs, &gpEngfuncs, &refState ) != REF_API_VERSION )\n\t{\n\t\tCon_Reportf( \"%s: can't init renderer API: wrong version\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\trefState.developer = host_developer.value;\n\n\tif( !ref.dllFuncs.R_Init( ))\n\t{\n\t\tCon_Reportf( \"%s: can't init renderer!\\n\", __func__ ); //, ref.dllFuncs.R_GetInitError() );\n\t\treturn false;\n\t}\n\n\tCvar_FullSet( \"host_refloaded\", \"1\", FCVAR_READ_ONLY );\n\tref.initialized = true;\n\n\t// initialize TriAPI callbacks\n\tCL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs );\n\n\treturn true;\n}\n\nvoid R_Shutdown( void )\n{\n\tint i;\n\tmodel_t *mod;\n\n\t// release SpriteTextures\n\tfor( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )\n\t{\n\t\tif( !mod->name[0] ) continue;\n\t\tMod_FreeModel( mod );\n\t}\n\tmemset( clgame.sprites, 0, sizeof( clgame.sprites ));\n\n\t// correctly free all models before render unload\n\t// change this if need add online render changing\n\tMod_FreeAll();\n\tR_UnloadProgs();\n\tref.initialized = false;\n}\n\nstatic void R_GetRendererName( char *dest, size_t size, const char *opt )\n{\n\tif( !Q_strstr( opt, \".\" OS_LIB_EXT ))\n\t{\n#ifdef XASH_INTERNAL_GAMELIBS\n\t#define FMT1 \"%s\"\n\t#define FMT2 \"ref_%s\"\n#else\n\t#define FMT1 OS_LIB_PREFIX \"%s.\" OS_LIB_EXT\n\t#define FMT2 OS_LIB_PREFIX \"ref_%s.\" OS_LIB_EXT\n#endif\n\t\tif( !Q_strncmp( opt, \"ref_\", 4 ))\n\t\t\tQ_snprintf( dest, size, FMT1, opt );\n\t\telse\n\t\t\tQ_snprintf( dest, size, FMT2, opt );\n#undef FMT1\n#undef FMT2\n\t}\n\telse\n\t{\n\t\t// full path\n\t\tQ_strncpy( dest, opt, size );\n\t}\n}\n\nstatic qboolean R_LoadRenderer( const char *refopt, qboolean quiet )\n{\n\tstring refdll;\n\n\tR_GetRendererName( refdll, sizeof( refdll ), refopt );\n\n\tCon_Printf( \"Loading renderer: %s -> %s\\n\", refopt, refdll );\n\n\tif( !R_LoadProgs( refdll ))\n\t{\n\t\tR_Shutdown();\n\t\tif( !quiet )\n\t\t\tSys_Warn( S_ERROR \"Can't initialize %s renderer!\\n\", refdll );\n\t\treturn false;\n\t}\n\n\tCvar_FullSet( \"r_refdll_loaded\", refopt, FCVAR_READ_ONLY );\n\tCon_Reportf( \"Renderer %s initialized\\n\", refdll );\n\n\treturn true;\n}\n\nstatic void SetWidthAndHeightFromCommandLine( void )\n{\n\tint width, height;\n\n\tSys_GetIntFromCmdLine( \"-width\", &width );\n\tSys_GetIntFromCmdLine( \"-height\", &height );\n\n\tif( width < 1 || height < 1 )\n\t{\n\t\t// Not specified or invalid, so don't bother.\n\t\treturn;\n\t}\n\n\tR_SaveVideoMode( width, height, width, height, false );\n}\n\nstatic void SetFullscreenModeFromCommandLine( void )\n{\n\tif( Sys_CheckParm( \"-borderless\" ))\n\t\tCvar_DirectSet( &vid_fullscreen, \"2\" );\n\telse if( Sys_CheckParm( \"-fullscreen\" ))\n\t\tCvar_DirectSet( &vid_fullscreen, \"1\" );\n\telse if( Sys_CheckParm( \"-windowed\" ))\n\t\tCvar_DirectSet( &vid_fullscreen, \"0\" );\n}\n\nstatic void R_CollectRendererNames( void )\n{\n\t// ordering is important!\n\tstatic const char *short_names[] =\n\t{\n#if XASH_REF_GL_ENABLED\n\t\t\"gl\",\n#endif\n#if XASH_REF_NANOGL_ENABLED\n\t\t\"gles1\",\n#endif\n#if XASH_REF_GLWES_ENABLED\n\t\t\"gles2\",\n#endif\n#if XASH_REF_GL4ES_ENABLED\n\t\t\"gl4es\",\n#endif\n#if XASH_REF_GLES3COMPAT_ENABLED\n\t\t\"gles3compat\",\n#endif\n#if XASH_REF_SOFT_ENABLED\n\t\t\"soft\",\n#endif\n#if XASH_REF_VULKAN_ENABLED\n\t\t\"vk\"\n#endif\n\t};\n\n\t// ordering is important here too!\n\tstatic const char *long_names[ARRAYSIZE( short_names )] =\n\t{\n#if XASH_REF_GL_ENABLED\n\t\t\"OpenGL\",\n#endif\n#if XASH_REF_NANOGL_ENABLED\n\t\t\"GLES1 (NanoGL)\",\n#endif\n#if XASH_REF_GLWES_ENABLED\n\t\t\"GLES2 (gl-wes-v2)\",\n#endif\n#if XASH_REF_GL4ES_ENABLED\n\t\t\"GL4ES\",\n#endif\n#if XASH_REF_GLES3COMPAT_ENABLED\n\t\t\"GLES3 (gl2_shim)\",\n#endif\n#if XASH_REF_SOFT_ENABLED\n\t\t\"Software\",\n#endif\n#if XASH_REF_VULKAN_ENABLED\n\t\t\"Vulkan\"\n#endif\n\t};\n\n\tref.num_renderers = ARRAYSIZE( short_names );\n\tref.short_names = short_names;\n\tref.long_names = long_names;\n}\n\nconst ref_device_t *R_GetRenderDevice( unsigned int idx )\n{\n\tif( !Q_stricmp( r_refdll_loaded.string, \"vk\" ))\n\t{\n\t\tif( !ref.dllFuncs.pfnGetVulkanRenderDevice )\n\t\t\treturn NULL;\n\n\t\treturn ref.dllFuncs.pfnGetVulkanRenderDevice( idx );\n\t}\n\n\t// TODO: implement?\n\treturn NULL;\n}\n\nstatic const char *R_DeviceTypeToString( ref_device_type_t type )\n{\n\tswitch( type )\n\t{\n\tcase REF_DEVICE_TYPE_DISCRETE_GPU:\n\t\treturn \"^2Discrete^7\";\n\tcase REF_DEVICE_TYPE_INTERGRATED_GPU:\n\t\treturn \"^3Integrated^7\";\n\tcase REF_DEVICE_TYPE_VIRTUAL_GPU:\n\t\treturn \"^4Virtual^7\";\n\tcase REF_DEVICE_TYPE_CPU:\n\t\treturn \"^5Software^7\";\n\t}\n\n\treturn \"^6Unknown^7\";\n}\n\nstatic void R_GetRenderDevices_f( void )\n{\n\tint i = 0;\n\tconst ref_device_t *device = NULL;\n\n\tif( Q_stricmp( r_refdll_loaded.string, \"vk\" ) ||\n\t    !ref.dllFuncs.pfnGetVulkanRenderDevice )\n\t{\n\t\tCon_Printf( \"Renderer %s doesn't implement this!\\n\", r_refdll_loaded.string );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Num ID      Type    Name\\n\" );\n\tCon_Printf( \"------------------------------------------------\\n\" );\n\n\tfor( i = 0;; i++ )\n\t{\n\t\tdevice = R_GetRenderDevice( i );\n\t\tif( !device )\n\t\t\tbreak;\n\n\t\tCon_Printf( \"%-3i %04x:%04x %-10s %s\\n\",\n\t\t\ti, device->vendorID, device->deviceID,\n\t\t\tR_DeviceTypeToString( device->deviceType ), device->deviceName );\n\t}\n\n}\n\nqboolean R_Init( void )\n{\n\tqboolean success = false;\n\tstring requested_cmdline;\n\tstring requested_cvar;\n\n\tCvar_RegisterVariable( &gl_vsync );\n\tCvar_RegisterVariable( &r_showtextures );\n\tCvar_RegisterVariable( &r_adjust_fov );\n\tCvar_RegisterVariable( &r_decals );\n\tCvar_RegisterVariable( &gl_msaa_samples );\n\tCvar_RegisterVariable( &gl_clear );\n\tCvar_RegisterVariable( &r_showtree );\n\tCvar_RegisterVariable( &r_refdll );\n\tCvar_RegisterVariable( &r_refdll_loaded );\n\n\t// cvars that are expected to exist\n\tCvar_Get( \"r_speeds\", \"0\", FCVAR_ARCHIVE, \"shows renderer speeds\" );\n\tCvar_Get( \"r_fullbright\", \"0\", FCVAR_CHEAT, \"disable lightmaps, get fullbright for entities\" );\n\tCvar_Get( \"r_norefresh\", \"0\", 0, \"disable 3D rendering (use with caution)\" );\n\tCvar_Get( \"r_dynamic\", \"1\", FCVAR_ARCHIVE, \"allow dynamic lighting (dlights, lightstyles)\" );\n\tCvar_Get( \"r_lightmap\", \"0\", FCVAR_CHEAT, \"lightmap debugging tool\" );\n\tCvar_Get( \"tracerred\", \"0.8\", 0, \"tracer red component weight ( 0 - 1.0 )\" );\n\tCvar_Get( \"tracergreen\", \"0.8\", 0, \"tracer green component weight ( 0 - 1.0 )\" );\n\tCvar_Get( \"tracerblue\", \"0.4\", 0, \"tracer blue component weight ( 0 - 1.0 )\" );\n\tCvar_Get( \"traceralpha\", \"0.5\", 0, \"tracer alpha amount ( 0 - 1.0 )\" );\n\n\tCvar_Get( \"r_sprite_lerping\", \"1\", FCVAR_ARCHIVE, \"enables sprite animation lerping\" );\n\tCvar_Get( \"r_sprite_lighting\", \"1\", FCVAR_ARCHIVE, \"enables sprite lighting (blood etc)\" );\n\n\tCvar_Get( \"r_drawviewmodel\", \"1\", 0, \"draw firstperson weapon model\" );\n\tCvar_Get( \"r_glowshellfreq\", \"2.2\", 0, \"glowing shell frequency update\" );\n\n\t// cvars that are expected to exist by client.dll\n\t// refdll should just get pointer to them\n\tCvar_Get( \"r_lighting_modulate\", \"0.6\", FCVAR_ARCHIVE, \"compatibility cvar, does nothing\" );\n\tCvar_Get( \"r_drawentities\", \"1\", FCVAR_CHEAT, \"render entities\" );\n\tCvar_Get( \"cl_himodels\", \"1\", FCVAR_ARCHIVE, \"draw high-resolution player models in multiplayer\" );\n\n\tCmd_AddCommand( \"r_show_devices\", R_GetRenderDevices_f, \"print all available GPUs in the system\" );\n\n\t// cvars are created, execute video config\n\tCbuf_AddText( \"exec video.cfg\" );\n\tCbuf_Execute();\n\n\t// Set screen resolution and fullscreen mode if passed in on command line.\n\t// this is done after executing video.cfg, as the command line values should take priority.\n\tSetWidthAndHeightFromCommandLine();\n\tSetFullscreenModeFromCommandLine();\n\n\tR_CollectRendererNames();\n\n\t// Priority:\n\t// 1. Command line `-ref` argument.\n\t// 2. `ref_dll` cvar.\n\t// 3. Detected renderers in `DEFAULT_RENDERERS` order.\n\trequested_cmdline[0] = 0;\n\trequested_cvar[0] = 0;\n\n\tif( Sys_GetParmFromCmdLine( \"-ref\", requested_cmdline ))\n\t\tsuccess = R_LoadRenderer( requested_cmdline, false );\n\n\tif( !success && COM_CheckString( r_refdll.string ) && Q_stricmp( requested_cmdline, r_refdll.string ))\n\t{\n\t\tQ_strncpy( requested_cvar, r_refdll.string, sizeof( requested_cvar ));\n\n\t\t// do not show scary messages to user if renderer set in config cannot be loaded\n\t\t// as game data could be copied from one platform to another, where this renderer\n\t\t// might not be supported (ref_gl on Android for example)\n\t\tsuccess = R_LoadRenderer( requested_cvar, !host_developer.value );\n\t}\n\n\tif( !success )\n\t{\n\t\tint i;\n\n\t\tfor( i = 0; i < ref.num_renderers; i++ )\n\t\t{\n\t\t\t// skip renderer that was requested but failed to load\n\t\t\tif( !Q_strcmp( requested_cmdline, ref.short_names[i] ))\n\t\t\t\tcontinue;\n\n\t\t\tif( !Q_strcmp( requested_cvar, ref.short_names[i] ))\n\t\t\t\tcontinue;\n\n\t\t\t// do not show bruteforcing attempts, however, warn user about falling back\n\t\t\t// to software mode\n\t\t\tif( !Q_strcmp( \"soft\", ref.short_names[i] ) && !host_developer.value )\n\t\t\t\tSys_Warn( \"Can't initialize any hardware accelerated renderer. Falling back to software rendering...\\n\" );\n\n\t\t\tsuccess = R_LoadRenderer( ref.short_names[i], !host_developer.value );\n\n\t\t\tif( success )\n\t\t\t{\n\t\t\t\t// remember last valid renderer\n\t\t\t\tCvar_DirectSet( &r_refdll, ref.short_names[i] );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !success )\n\t{\n\t\tSys_Error( \"Can't initialize any renderer. Check your video drivers!\\n\" );\n\t\treturn false;\n\t}\n\n\tSCR_Init();\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/ref_common.h",
    "content": "/*\nref_common.h - Xash3D render dll API\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#if !defined REF_COMMON_H && !defined REF_DLL\n#define REF_COMMON_H\n\n#include \"ref_api.h\"\n\n#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ( cl.playernum + 1 ) && e->player )\n\nstruct ref_state_s\n{\n\tHINSTANCE       hInstance;\n\tqboolean        initialized;\n\tint             num_renderers;\n\tref_interface_t dllFuncs;\n\n\t// depends on build configuration\n\tconst char    **short_names;\n\tconst char    **long_names;\n};\n\nextern struct ref_state_s ref;\nextern ref_globals_t refState;\n\n// handy API wrappers\n#define REF_GET_PARM( parm, arg ) ref.dllFuncs.RefGetParm( (parm), (arg) )\n#define GL_LoadTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), false )\n#define GL_UpdateTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), true )\n#define R_GetBuiltinTexture( name ) ref.dllFuncs.GL_FindTexture( (name) )\n\nstatic inline void R_GetTextureParms( int *w, int *h, int texnum )\n{\n\tif( w ) *w = REF_GET_PARM( PARM_TEX_WIDTH, texnum );\n\tif( h ) *h = REF_GET_PARM( PARM_TEX_HEIGHT, texnum );\n}\n\nvoid GL_RenderFrame( const struct ref_viewpass_s *rvp );\n\nvoid R_SetupSky( const char *name );\n\n// common engine and renderer cvars\nextern convar_t r_decals;\nextern convar_t r_adjust_fov;\nextern convar_t gl_clear;\n\nqboolean R_Init( void );\nvoid R_Shutdown( void );\nconst ref_device_t *R_GetRenderDevice( unsigned int idx );\n\n#endif // REF_COMMON_H\n"
  },
  {
    "path": "engine/client/s_dsp.c",
    "content": "/*\ns_dsp.c - digital signal processing algorithms for audio FX\nCopyright (C) 2009 Uncle Mike\nCopyright (C) 2016-2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"sound.h\"\n\n#define MAX_DELAY\t\t0.4f\n#define MAX_ROOM_TYPES\tARRAYSIZE( rgsxpre )\n\n#define MONODLY\t\t0\n#define MAX_MONO_DELAY\t0.4f\n\n#define REVERBPOS\t\t1\n#define MAX_REVERB_DELAY\t0.1f\n\n#define STEREODLY\t\t3\n#define MAX_STEREO_DELAY\t0.1f\n\n#define REVERB_XFADE\t32\n\n#define MAXDLY\t\t(STEREODLY + 1)\n#define MAXLP\t\t10\n\ntypedef struct sx_preset_s\n{\n\tfloat\troom_lp;\t// lowpass\n\tfloat\troom_mod;\t// modulation\n\n\t// reverb\n\tfloat\troom_size;\n\tfloat\troom_refl;\n\tfloat\troom_rvblp;\n\n\t// delay\n\tfloat\troom_delay;\n\tfloat\troom_feedback;\n\tfloat\troom_dlylp;\n\tfloat\troom_left;\n} sx_preset_t;\n\ntypedef struct dly_s\n{\n\tsize_t\tcdelaysamplesmax;\t// delay line array size\n\n\t// delay line pointers\n\tsize_t\tidelayinput;\n\tsize_t\tidelayoutput;\n\n\t// crossfade\n\tint\tidelayoutputxf;\t// output pointer\n\tint\txfade;\t\t// value\n\n\tint\tdelaysamples;\t// delay setting\n\tint\tdelayfeedback;\t// feedback setting\n\n\t// lowpass\n\tint\tlp;\t\t// is lowpass enabled\n\tint\tlp0, lp1, lp2;\t// lowpass buffer\n\n\t// modulation\n\tint\tmod;\n\tint\tmodcur;\n\n\t// delay line\n\tint\t*lpdelayline;\n} dly_t;\n\nstatic const sx_preset_t rgsxpre[] =\n{\n//          -------reverb--------  -------delay--------\n// lp  mod  size   refl   rvblp  delay  feedback  dlylp  left\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.0,   0.0,      2.0,   0.0    }, // 0 off\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.065, 0.1,      0.0,   0.01   }, // 1 generic\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.02,  0.75,     0.0,   0.01   }, // 2 metalic\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.03,  0.78,     0.0,   0.02   }, // 3\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.06,  0.77,     0.0,   0.03   }, // 4\n{ 0.0, 0.0, 0.05,  0.85,  1.0,   0.008, 0.96,     2.0,   0.01   }, // 5 tunnel\n{ 0.0, 0.0, 0.05,  0.88,  1.0,   0.01,  0.98,     2.0,   0.02   }, // 6\n{ 0.0, 0.0, 0.05,  0.92,  1.0,   0.015, 0.995,    2.0,   0.04   }, // 7\n{ 0.0, 0.0, 0.05,  0.84,  1.0,   0.0,   0.0,      2.0,   0.012  }, // 8 chamber\n{ 0.0, 0.0, 0.05,  0.9,   1.0,   0.0,   0.0,      2.0,   0.008  }, // 9\n{ 0.0, 0.0, 0.05,  0.95,  1.0,   0.0,   0.0,      2.0,   0.004  }, // 10\n{ 0.0, 0.0, 0.05,  0.7,   0.0,   0.0,   0.0,      2.0,   0.012  }, // 11 brite\n{ 0.0, 0.0, 0.055, 0.78,  0.0,   0.0,   0.0,      2.0,   0.008  }, // 12\n{ 0.0, 0.0, 0.05,  0.86,  0.0,   0.0,   0.0,      2.0,   0.002  }, // 13\n{ 1.0, 0.0, 0.0,   0.0,   1.0,   0.0,   0.0,      2.0,   0.01   }, // 14 water\n{ 1.0, 0.0, 0.0,   0.0,   1.0,   0.06,  0.85,     2.0,   0.02   }, // 15\n{ 1.0, 0.0, 0.0,   0.0,   1.0,   0.2,   0.6,      2.0,   0.05   }, // 16\n{ 0.0, 0.0, 0.05,  0.8,   1.0,   0.0,   0.48,     2.0,   0.016  }, // 17 concrete\n{ 0.0, 0.0, 0.06,  0.9,   1.0,   0.0,   0.52,     2.0,   0.01   }, // 18\n{ 0.0, 0.0, 0.07,  0.94,  1.0,   0.3,   0.6,      2.0,   0.008  }, // 19\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.3,   0.42,     2.0,   0.0    }, // 20 outside\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.35,  0.48,     2.0,   0.0    }, // 21\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.38,  0.6,      2.0,   0.0    }, // 22\n{ 0.0, 0.0, 0.05,  0.9,   1.0,   0.2,   0.28,     0.0,   0.0    }, // 23 cavern\n{ 0.0, 0.0, 0.07,  0.9,   1.0,   0.3,   0.4,      0.0,   0.0    }, // 24\n{ 0.0, 0.0, 0.09,  0.9,   1.0,   0.35,  0.5,      0.0,   0.0    }, // 25\n{ 0.0, 1.0, 0.01,  0.9,   0.0,   0.0,   0.0,      2.0,   0.05   }, // 26 weirdo\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.009, 0.999,    2.0,   0.04   }, // 27\n{ 0.0, 0.0, 0.001, 0.999, 0.0,   0.2,   0.8,      2.0,   0.05   }  // 28\n};\n\n// 0x0045dca8 enginegl.exe\n// SHA256: 42383d32cd712e59ee2c1bd78b7ba48814e680e7026c4223e730111f34a60d66\nstatic const sx_preset_t rgsxpre_hlalpha052[] =\n{\n//          -------reverb--------  -------delay--------\n// lp  mod  size   refl   rvblp  delay  feedback  dlylp  left\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.0,   0.0,      2.0,   0.0    }, // 0 off\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.08,  0.8,      2.0,   0.0    }, // 1 generic\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.02,  0.75,     0.0,   0.001  }, // 2 metalic\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.03,  0.78,     0.0,   0.002  }, // 3\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.06,  0.77,     0.0,   0.003  }, // 4\n{ 0.0, 0.0, 0.05,  0.85,  1.0,   0.008, 0.96,     2.0,   0.01   }, // 5 tunnel\n{ 0.0, 0.0, 0.05,  0.88,  1.0,   0.01,  0.98,     2.0,   0.02   }, // 6\n{ 0.0, 0.0, 0.05,  0.92,  1.0,   0.015, 0.995,    2.0,   0.04   }, // 7\n{ 0.0, 0.0, 0.05,  0.84,  1.0,   0.0,   0.0,      2.0,   0.003  }, // 8 chamber\n{ 0.0, 0.0, 0.05,  0.9,   1.0,   0.0,   0.0,      2.0,   0.002  }, // 9\n{ 0.0, 0.0, 0.05,  0.95,  1.0,   0.0,   0.0,      2.0,   0.001  }, // 10\n{ 0.0, 0.0, 0.05,  0.7,   0.0,   0.0,   0.0,      2.0,   0.003  }, // 11 brite\n{ 0.0, 0.0, 0.055, 0.78,  0.0,   0.0,   0.0,      2.0,   0.002  }, // 12\n{ 0.0, 0.0, 0.05,  0.86,  0.0,   0.0,   0.0,      2.0,   0.001  }, // 13\n{ 1.0, 1.0, 0.0,   0.0,   1.0,   0.0,   0.0,      2.0,   0.01   }, // 14 water\n{ 1.0, 1.0, 0.0,   0.0,   1.0,   0.06,  0.85,     2.0,   0.02   }, // 15\n{ 1.0, 1.0, 0.0,   0.0,   1.0,   0.2,   0.6,      2.0,   0.05   }, // 16\n{ 0.0, 0.0, 0.05,  0.8,   1.0,   0.15,  0.48,     2.0,   0.008  }, // 17 concrete\n{ 0.0, 0.0, 0.06,  0.9,   1.0,   0.22,  0.52,     2.0,   0.005  }, // 18\n{ 0.0, 0.0, 0.07,  0.94,  1.0,   0.3,   0.6,      2.0,   0.001  }, // 19\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.3,   0.42,     2.0,   0.0    }, // 20 outside\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.35,  0.48,     2.0,   0.0    }, // 21\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.38,  0.6,      2.0,   0.0    }, // 22\n{ 0.0, 0.0, 0.05,  0.9,   1.0,   0.2,   0.28,     0.0,   0.0    }, // 23 cavern\n{ 0.0, 0.0, 0.07,  0.9,   1.0,   0.3,   0.4,      0.0,   0.0    }, // 24\n{ 0.0, 0.0, 0.09,  0.9,   1.0,   0.35,  0.5,      0.0,   0.0    }, // 25\n{ 0.0, 1.0, 0.01,  0.9,   0.0,   0.0,   0.0,      2.0,   0.05   }, // 26 weirdo\n{ 0.0, 0.0, 0.0,   0.0,   1.0,   0.009, 0.999,    2.0,   0.04   }, // 27\n{ 0.0, 0.0, 0.001, 0.999, 0.0,   0.2,   0.8,      2.0,   0.05   }, // 28\n};\n\nstatic const sx_preset_t *ptable = rgsxpre;\n\n// cvars\nstatic CVAR_DEFINE_AUTO( dsp_off, \"0\",  FCVAR_ARCHIVE, \"disable DSP processing (deprecated)\" );\nstatic CVAR_DEFINE_AUTO( room_off, \"0\", FCVAR_ARCHIVE, \"disable DSP processing (GoldSrc compatible cvar)\" );\nstatic CVAR_DEFINE_AUTO( dsp_coeff_table, \"0\", FCVAR_ARCHIVE, \"select DSP coefficient table: 0 for release or 1 for alpha 0.52\" );\nstatic CVAR_DEFINE_AUTO( room_type, \"0\",  0, \"current room type preset\" );\n\nstatic CVAR_DEFINE( roomwater_type, \"waterroom_type\", \"14\", 0, \"water room type\" );\nstatic CVAR_DEFINE( hisound, \"room_hires\", \"2\", FCVAR_ARCHIVE, \"dsp quality. 1 for 22k, 2 for 44k(recommended) and 3 for 96k\" );\n\n// underwater/special fx modulations\nstatic CVAR_DEFINE( sxmod_mod, \"room_mod\", \"0\", 0, \"stereo amptitude modulation for room\" );\nstatic CVAR_DEFINE( sxmod_lowpass, \"room_lp\", \"0\", 0, \"for water fx, lowpass for entire room\" );\n\n// stereo delay(no feedback)\nstatic CVAR_DEFINE( sxste_delay, \"room_left\", \"0\", 0, \"left channel delay time\" );\n\n// mono reverb\nstatic CVAR_DEFINE( sxrvb_lp, \"room_rvblp\", \"1\", 0, \"reverb: low pass filtering level\" );\nstatic CVAR_DEFINE( sxrvb_feedback, \"room_refl\", \"0\", 0, \"reverb: decay time\" );\nstatic CVAR_DEFINE( sxrvb_size, \"room_size\", \"0\", 0, \"reverb: initial reflection size\" );\n\n// mono delay\nstatic CVAR_DEFINE( sxdly_lp, \"room_dlylp\", \"1\", 0, \"mono delay: low pass filtering level\" );\nstatic CVAR_DEFINE( sxdly_feedback, \"room_feedback\", \"0.2\", 0, \"mono delay: decay time\" );\nstatic CVAR_DEFINE( sxdly_delay, \"room_delay\", \"0.8\", 0, \"mono delay: delay time\" );\n\nstatic int\t\t\tidsp_dma_speed;\nint\t\t\tidsp_room;\nstatic int\t\t\troom_typeprev;\n\n// routines\nstatic int\t\t\tsxamodl, sxamodr;      // amplitude modulation values\nstatic int\t\t\tsxamodlt, sxamodrt;    // modulation targets\nstatic int\t\t\tsxmod1cur, sxmod2cur;\nstatic int\t\t\tsxmod1, sxmod2;\nstatic int\t\t\tsxhires;\n\nstatic portable_samplepair_t\t*paintto = NULL;\n\nstatic dly_t\t\t\trgsxdly[MAXDLY]; // stereo is last\nstatic int\t\t\trgsxlp[MAXLP];\n\nstatic void SX_Profiling_f( void );\n\n/*\n============\nSX_ReloadRoomFX\n\n============\n*/\nstatic void SX_ReloadRoomFX( void )\n{\n\tSetBits( sxste_delay.flags, FCVAR_CHANGED );\n\tSetBits( sxrvb_feedback.flags, FCVAR_CHANGED );\n\tSetBits( sxdly_delay.flags, FCVAR_CHANGED );\n\tSetBits( room_type.flags, FCVAR_CHANGED );\n}\n\n/*\n============\nSX_Init()\n\nStarts sound crackling system\n============\n*/\nvoid SX_Init( void )\n{\n\tmemset( rgsxdly, 0, sizeof( rgsxdly ));\n\tmemset( rgsxlp,  0, sizeof( rgsxlp  ));\n\n\tsxamodr = sxamodl = sxamodrt = sxamodlt = 255;\n\tidsp_dma_speed = SOUND_11k;\n\n\tCvar_RegisterVariable( &hisound );\n\tsxhires = 2;\n\n\tsxmod1cur = sxmod1 = 350 * ( idsp_dma_speed / SOUND_11k );\n\tsxmod2cur = sxmod2 = 450 * ( idsp_dma_speed / SOUND_11k );\n\n\tCvar_RegisterVariable( &dsp_off );\n\tCvar_RegisterVariable( &room_off );\n\tCvar_RegisterVariable( &dsp_coeff_table );\n\n\tCvar_RegisterVariable( &roomwater_type );\n\tCvar_RegisterVariable( &room_type );\n\n\tCvar_RegisterVariable( &sxmod_lowpass );\n\tCvar_RegisterVariable( &sxmod_mod );\n\n\tCvar_RegisterVariable( &sxrvb_size );\n\tCvar_RegisterVariable( &sxrvb_feedback );\n\tCvar_RegisterVariable( &sxrvb_lp );\n\n\tCvar_RegisterVariable( &sxdly_delay );\n\tCvar_RegisterVariable( &sxdly_feedback );\n\tCvar_RegisterVariable( &sxdly_lp );\n\n\tCvar_RegisterVariable( &sxste_delay );\n\n\tCmd_AddCommand( \"dsp_profile\", SX_Profiling_f, \"dsp stress-test, first argument is room_type\" );\n\n\tSX_ReloadRoomFX();\n}\n\n/*\n===========\nDLY_Free\n\nFree memory allocated for DSP\n===========\n*/\nstatic void DLY_Free( int idelay )\n{\n\tAssert( idelay >= 0 && idelay < MAXDLY );\n\n\tif( rgsxdly[idelay].lpdelayline )\n\t{\n\t\tZ_Free( rgsxdly[idelay].lpdelayline );\n\t\trgsxdly[idelay].lpdelayline = NULL;\n\t}\n}\n\n/*\n==========\nSX_Shutdown\n\nStop DSP processor\n==========\n*/\nvoid SX_Free( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i <= 3; i++ )\n\t\tDLY_Free( i );\n\n\tCmd_RemoveCommand( \"dsp_profile\" );\n}\n\n\n/*\n===========\nDLY_Init\n\nInitialize dly\n===========\n*/\nstatic int DLY_Init( int idelay, float delay )\n{\n\tdly_t\t*cur;\n\n\t// DLY_Init called anytime with constants. So valid it in debug builds only.\n\tAssert( idelay >= 0 && idelay < MAXDLY );\n\tAssert( delay > 0.0f && delay <= MAX_DELAY );\n\n\tDLY_Free( idelay ); // free dly if it's allocated\n\n\tcur = &rgsxdly[idelay];\n\tcur->cdelaysamplesmax = ((int)(delay * idsp_dma_speed) << sxhires) + 1;\n\tcur->lpdelayline = (int *)Mem_Calloc( sndpool, cur->cdelaysamplesmax * sizeof( int ));\n\tcur->xfade = 0;\n\n\t// init modulation\n\tcur->mod = cur->modcur = 0;\n\n\t// init lowpass\n\tcur->lp = 1;\n\tcur->lp0 = cur->lp1 = cur->lp2 = 0;\n\n\tcur->idelayinput = 0;\n\tcur->idelayoutput = cur->cdelaysamplesmax - cur->delaysamples; // NOTE: delaysamples must be set!!!\n\n\n\treturn 1;\n}\n\n/*\n============\nDLY_MovePointer\n\nChecks overflow and moves pointer\n============\n*/\nstatic void DLY_MovePointer( dly_t *dly )\n{\n\tif( ++dly->idelayinput >= dly->cdelaysamplesmax )\n\t\tdly->idelayinput = 0;\n\n\tif( ++dly->idelayoutput >= dly->cdelaysamplesmax )\n\t\tdly->idelayoutput = 0;\n}\n\n/*\n=============\nDLY_CheckNewStereoDelayVal\n\nUpdate stereo processor settings if we are in new room\n=============\n*/\nstatic void DLY_CheckNewStereoDelayVal( void )\n{\n\tdly_t *const\tdly = &rgsxdly[STEREODLY];\n\tfloat\t\tdelay = sxste_delay.value;\n\n\tif( !FBitSet( sxste_delay.flags, FCVAR_CHANGED ))\n\t\treturn;\n\n\tif( delay == 0 )\n\t{\n\t\tDLY_Free( STEREODLY );\n\t}\n\telse\n\t{\n\t\tint\tsamples;\n\n\t\tdelay = Q_min( delay, MAX_STEREO_DELAY );\n\t\tsamples = (int)(delay * idsp_dma_speed) << sxhires;\n\n\t\t// re-init dly\n\t\tif( !dly->lpdelayline )\n\t\t{\n\t\t\tdly->delaysamples = samples;\n\t\t\tDLY_Init( STEREODLY, MAX_STEREO_DELAY );\n\t\t}\n\n\t\tif( dly->delaysamples != samples )\n\t\t{\n\t\t\tdly->xfade = 128;\n\t\t\tdly->idelayoutputxf = dly->idelayinput - samples;\n\t\t\tif( dly->idelayoutputxf < 0 )\n\t\t\t\tdly->idelayoutputxf += dly->cdelaysamplesmax;\n\t\t}\n\n\t\tdly->modcur = dly->mod = 0;\n\n\t\tif( dly->delaysamples == 0 )\n\t\t\tDLY_Free( STEREODLY );\n\t}\n}\n\n/*\n=============\nDLY_DoStereoDelay\n\nDo stereo processing\n=============\n*/\nstatic void DLY_DoStereoDelay( int count )\n{\n\tint\t\t\tdelay, samplexf;\n\tdly_t *const\t\tdly = &rgsxdly[STEREODLY];\n\tportable_samplepair_t\t*paint = paintto;\n\n\tif( !dly->lpdelayline )\n\t\treturn; // inactive\n\n\tfor( ; count; count--, paint++ )\n\t{\n\t\tif( dly->mod && --dly->modcur < 0 )\n\t\t\tdly->modcur = dly->mod;\n\n\t\tdelay = dly->lpdelayline[dly->idelayoutput];\n\n\t\t// process only if crossfading, active left value or delayline\n\t\tif( delay || paint->left || dly->xfade )\n\t\t{\n\t\t\t// set up new crossfade, if not crossfading, not modulating, but going to\n\t\t\tif( !dly->xfade && !dly->modcur && dly->mod )\n\t\t\t{\n\t\t\t\tdly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * dly->delaysamples ) >> 9 );\n\n\t\t\t\tdly->xfade = 128;\n\t\t\t}\n\n\t\t\tdly->idelayoutputxf %= dly->cdelaysamplesmax;\n\n\t\t\t// modify delay, if crossfading\n\t\t\tif( dly->xfade )\n\t\t\t{\n\t\t\t\tsamplexf = dly->lpdelayline[dly->idelayoutputxf] * (128 - dly->xfade) >> 7;\n\t\t\t\tdelay = samplexf + ((delay * dly->xfade) >> 7);\n\n\t\t\t\tif( ++dly->idelayoutputxf >= dly->cdelaysamplesmax )\n\t\t\t\t\tdly->idelayoutputxf = 0;\n\n\t\t\t\tif( --dly->xfade == 0 )\n\t\t\t\t\tdly->idelayoutput = dly->idelayoutputxf;\n\t\t\t}\n\n\t\t\t// save left value to delay line\n\t\t\tdly->lpdelayline[dly->idelayinput] = CLIP( paint->left );\n\n\t\t\t// paint new delay value\n\t\t\tpaint->left = delay;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// clear delay line\n\t\t\tdly->lpdelayline[dly->idelayinput] = 0;\n\t\t}\n\n\t\tDLY_MovePointer( dly );\n\t}\n}\n\n/*\n=============\nDLY_CheckNewDelayVal\n\nUpdate delay processor settings if we are in new room\n=============\n*/\nstatic void DLY_CheckNewDelayVal( void )\n{\n\tfloat\t\tdelay = sxdly_delay.value;\n\tdly_t *const\tdly = &rgsxdly[MONODLY];\n\n\tif( FBitSet( sxdly_delay.flags, FCVAR_CHANGED ))\n\t{\n\t\tif( delay == 0 )\n\t\t{\n\t\t\tDLY_Free( MONODLY );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdelay = Q_min( delay, MAX_MONO_DELAY );\n\t\t\tdly->delaysamples = (int)(delay * idsp_dma_speed) << sxhires;\n\n\t\t\t// init dly\n\t\t\tif( !dly->lpdelayline )\n\t\t\t\tDLY_Init( MONODLY, MAX_MONO_DELAY );\n\n\t\t\tif( dly->lpdelayline )\n\t\t\t{\n\t\t\t\tmemset( dly->lpdelayline, 0, dly->cdelaysamplesmax * sizeof( int ) );\n\t\t\t\tdly->lp0 = dly->lp1 = dly->lp2 = 0;\n\t\t\t}\n\n\t\t\tdly->idelayinput = 0;\n\t\t\tdly->idelayoutput = dly->cdelaysamplesmax - dly->delaysamples;\n\n\t\t\tif( !dly->delaysamples )\n\t\t\t\tDLY_Free( MONODLY );\n\n\t\t}\n\t}\n\n\tdly->lp = sxdly_lp.value;\n\tdly->delayfeedback = 255 * sxdly_feedback.value;\n}\n\n/*\n=============\nDLY_DoDelay\n\nDo delay processing\n=============\n*/\nstatic void DLY_DoDelay( int count )\n{\n\tdly_t *const\t\tdly = &rgsxdly[MONODLY];\n\tportable_samplepair_t\t*paint = paintto;\n\tint\t\t\tdelay;\n\n\tif( !dly->lpdelayline || !count )\n\t\treturn; // inactive\n\n\tfor( ; count; count--, paint++ )\n\t{\n\t\tdelay = dly->lpdelayline[dly->idelayoutput];\n\n\t\t// don't process if delay line and left/right samples are zero\n\t\tif( delay || paint->left || paint->right )\n\t\t{\n\t\t\t// calculate delayed value from average\n\t\t\tint val = (( paint->left + paint->right ) >> 1 ) + (( dly->delayfeedback * delay ) >> 8);\n\t\t\tval = CLIP( val );\n\n\t\t\tif( dly->lp ) // lowpass\n\t\t\t{\n\t\t\t\tval = ( dly->lp0 + dly->lp1 + val ) / 3;\n\t\t\t\tdly->lp0 = dly->lp1;\n\t\t\t\tdly->lp1 = val;\n\t\t\t}\n\n\t\t\tdly->lpdelayline[dly->idelayinput] = val;\n\n\t\t\tval >>= 2;\n\n\t\t\tpaint->left = CLIP( paint->left + val );\n\t\t\tpaint->right = CLIP( paint->right + val );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdly->lpdelayline[dly->idelayinput] = 0;\n\t\t\tdly->lp0 = dly->lp1 = dly->lp2 = 0;\n\t\t}\n\n\t\tDLY_MovePointer( dly );\n\t}\n}\n\n/*\n===========\nRVB_SetUpDly\n\nSet up dly for reverb\n===========\n*/\nstatic void RVB_SetUpDly( int pos, float delay, int kmod )\n{\n\tint\tsamples;\n\n\tdelay = Q_min( delay, MAX_REVERB_DELAY );\n\tsamples = (int)(delay * idsp_dma_speed) << sxhires;\n\n\tif( !rgsxdly[pos].lpdelayline )\n\t{\n\t\trgsxdly[pos].delaysamples = samples;\n\t\tDLY_Init( pos, MAX_REVERB_DELAY );\n\t}\n\n\trgsxdly[pos].modcur = rgsxdly[pos].mod = (int)(kmod * idsp_dma_speed / SOUND_11k) << sxhires;\n\n\t// set up crossfade, if delay has changed\n\tif( rgsxdly[pos].delaysamples != samples )\n\t{\n\t\trgsxdly[pos].idelayoutputxf = rgsxdly[pos].idelayinput - samples;\n\t\tif( rgsxdly[pos].idelayoutputxf < 0 )\n\t\t\trgsxdly[pos].idelayoutputxf += rgsxdly[pos].cdelaysamplesmax;\n\t\trgsxdly[pos].xfade = REVERB_XFADE;\n\t}\n\n\tif( !rgsxdly[pos].delaysamples )\n\t\tDLY_Free( pos );\n\n}\n\n/*\n===========\nRVB_CheckNewReverbVal\n\nUpdate reverb settings if we are in new room\n===========\n*/\nstatic void RVB_CheckNewReverbVal( void )\n{\n\tdly_t *const\tdly1 = &rgsxdly[REVERBPOS];\n\tdly_t *const\tdly2 = &rgsxdly[REVERBPOS + 1];\n\tfloat\t\tdelay = sxrvb_size.value;\n\n\tif( FBitSet( sxrvb_size.flags, FCVAR_CHANGED ))\n\t{\n\t\tif( delay == 0.0f )\n\t\t{\n\t\t\tDLY_Free( REVERBPOS );\n\t\t\tDLY_Free( REVERBPOS + 1 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tRVB_SetUpDly( REVERBPOS, sxrvb_size.value, 500 );\n\t\t\tRVB_SetUpDly( REVERBPOS+1, sxrvb_size.value * 0.71f, 700 );\n\t\t}\n\t}\n\n\tdly1->lp = dly2->lp = sxrvb_lp.value;\n\tdly1->delayfeedback = dly2->delayfeedback = (int)(255 * sxrvb_feedback.value);\n}\n\n/*\n===========\nRVB_DoReverbForOneDly\n\nDo reverberation for one dly\n===========\n*/\nstatic int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair )\n{\n\tint\tdelay;\n\tint\tsamplexf;\n\tint\tval, valt;\n\tint\tvoutm = 0;\n\n\tif( --dly->modcur < 0 )\n\t\tdly->modcur = dly->mod;\n\n\tdelay = dly->lpdelayline[dly->idelayoutput];\n\n\tif( dly->xfade || delay || samplepair->left || samplepair->right )\n\t{\n\t\t// modulate delay rate\n\t\tif( !dly->mod )\n\t\t{\n\t\t\tdly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * delay) >> 9 );\n\n\t\t\tdly->idelayoutputxf %= dly->cdelaysamplesmax;\n\n\t\t\tdly->xfade = REVERB_XFADE;\n\t\t}\n\n\t\tif( dly->xfade )\n\t\t{\n\t\t\tsamplexf = (dly->lpdelayline[dly->idelayoutputxf] * (REVERB_XFADE - dly->xfade)) / REVERB_XFADE;\n\t\t\tdelay = ((delay * dly->xfade) / REVERB_XFADE) + samplexf;\n\n\t\t\tif( ++dly->idelayoutputxf >= dly->cdelaysamplesmax )\n\t\t\t\tdly->idelayoutputxf = 0;\n\n\t\t\tif( --dly->xfade == 0 )\n\t\t\t\tdly->idelayoutput = dly->idelayoutputxf;\n\t\t}\n\n\n\t\tif( delay )\n\t\t{\n\t\t\tval = vlr + ( ( dly->delayfeedback * delay ) >> 8 );\n\t\t\tval = CLIP( val );\n\t\t}\n\t\telse\n\t\t\tval = vlr;\n\n\t\tif( dly->lp )\n\t\t{\n\t\t\tvalt = (dly->lp0 + val) >> 1;\n\t\t\tdly->lp0 = val;\n\t\t}\n\t\telse\n\t\t\tvalt = val;\n\n\t\tvoutm = dly->lpdelayline[dly->idelayinput] = valt;\n\t}\n\telse\n\t{\n\t\tvoutm = dly->lpdelayline[dly->idelayinput] = 0;\n\t\tdly->lp0 = 0;\n\t}\n\n\tDLY_MovePointer( dly );\n\n\treturn voutm;\n\n}\n\n/*\n===========\nRVB_DoReverb\n\nDo reverberation processing\n===========\n*/\nstatic void RVB_DoReverb( int count )\n{\n\tdly_t *const\t\tdly1 = &rgsxdly[REVERBPOS];\n\tdly_t *const\t\tdly2 = &rgsxdly[REVERBPOS+1];\n\tportable_samplepair_t\t*paint = paintto;\n\tint\t\t\tvlr, voutm;\n\n\tif( !dly1->lpdelayline )\n\t\treturn;\n\n\tfor( ; count; count--, paint++ )\n\t{\n\t\tvlr = ( paint->left + paint->right ) >> 1;\n\n\t\tvoutm = RVB_DoReverbForOneDly( dly1, vlr, paint );\n\t\tvoutm += RVB_DoReverbForOneDly( dly2, vlr, paint );\n\n\t\tif( dsp_coeff_table.value == 1.0f )\n\t\t\tvoutm /= 6; // alpha\n\t\telse voutm = (11 * voutm) >> 6;\n\n\t\tpaint->left = CLIP( paint->left + voutm );\n\t\tpaint->right = CLIP( paint->right + voutm );\n\t}\n}\n\n/*\n===========\nRVB_DoAMod\n\nDo amplification modulation processing\n===========\n*/\nstatic void RVB_DoAMod( int count )\n{\n\tportable_samplepair_t\t*paint = paintto;\n\n\tif( !sxmod_lowpass.value && !sxmod_mod.value )\n\t\treturn;\n\n\tfor( ; count; count--, paint++ )\n\t{\n\t\tportable_samplepair_t\tres = *paint;\n\n\t\tif( sxmod_lowpass.value )\n\t\t{\n\t\t\tres.left  = rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + res.left;\n\t\t\tres.right = rgsxlp[5] + rgsxlp[6] + rgsxlp[7] + rgsxlp[8] + rgsxlp[9] + res.right;\n\n\t\t\tres.left >>= 2;\n\t\t\tres.right >>= 2;\n\n\t\t\trgsxlp[4] = paint->left;\n\t\t\trgsxlp[9] = paint->right;\n\n\t\t\trgsxlp[0] = rgsxlp[1];\n\t\t\trgsxlp[1] = rgsxlp[2];\n\t\t\trgsxlp[2] = rgsxlp[3];\n\t\t\trgsxlp[3] = rgsxlp[4];\n\t\t\trgsxlp[4] = rgsxlp[5];\n\t\t\trgsxlp[5] = rgsxlp[6];\n\t\t\trgsxlp[6] = rgsxlp[7];\n\t\t\trgsxlp[7] = rgsxlp[8];\n\t\t\trgsxlp[8] = rgsxlp[9];\n\t\t}\n\n\t\tif( sxmod_mod.value )\n\t\t{\n\t\t\tif( --sxmod1cur < 0 )\n\t\t\t\tsxmod1cur = sxmod1;\n\n\t\t\tif( !sxmod1 )\n\t\t\t\tsxamodlt = COM_RandomLong( 32, 255 );\n\n\t\t\tif( --sxmod2cur < 0 )\n\t\t\t\tsxmod2cur = sxmod2;\n\n\t\t\tif( !sxmod2 )\n\t\t\t\tsxamodrt = COM_RandomLong( 32, 255 );\n\n\t\t\tres.left = (sxamodl * res.left) >> 8;\n\t\t\tres.right = (sxamodr * res.right) >> 8;\n\n\t\t\tif( sxamodl < sxamodlt )\n\t\t\t\tsxamodl++;\n\t\t\telse if( sxamodl > sxamodlt )\n\t\t\t\tsxamodl--;\n\n\t\t\tif( sxamodr < sxamodrt )\n\t\t\t\tsxamodr++;\n\t\t\telse if( sxamodr > sxamodrt )\n\t\t\t\tsxamodr--;\n\t\t}\n\n\t\tpaint->left = CLIP(res.left);\n\t\tpaint->right = CLIP(res.right);\n\t}\n}\n\n/*\n===========\nDSP_Process\n\n(xash dsp interface)\n===========\n*/\nvoid DSP_Process( portable_samplepair_t *pbfront, int sampleCount )\n{\n\tif( dsp_off.value || room_off.value || !sampleCount )\n\t\treturn;\n\n\t// preset is already installed by CheckNewDspPresets\n\tpaintto = pbfront;\n\n\tRVB_DoAMod( sampleCount );\n\tRVB_DoReverb( sampleCount );\n\tDLY_DoDelay( sampleCount );\n\tDLY_DoStereoDelay( sampleCount );\n}\n\n/*\n===========\nDSP_ClearState\n\n(xash dsp interface)\n===========\n*/\nvoid DSP_ClearState( void )\n{\n\tCvar_DirectSet( &room_type, \"0\" );\n\tSX_ReloadRoomFX();\n}\n\n/*\n===========\nCheckNewDspPresets\n\n(xash dsp interface)\n===========\n*/\nvoid CheckNewDspPresets( void )\n{\n\tif( dsp_off.value || room_off.value )\n\t\treturn;\n\n\tif( FBitSet( dsp_coeff_table.flags, FCVAR_CHANGED ))\n\t{\n\t\tswitch( (int)dsp_coeff_table.value )\n\t\t{\n\t\tcase 0: // release\n\t\t\tptable = rgsxpre;\n\t\t\tbreak;\n\t\tcase 1: // alpha\n\t\t\tptable = rgsxpre_hlalpha052;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tptable = rgsxpre;\n\t\t\tbreak;\n\t\t}\n\n\t\tSX_ReloadRoomFX();\n\t\troom_typeprev = -1;\n\n\t\tClearBits( dsp_coeff_table.flags, FCVAR_CHANGED );\n\t}\n\n\tif( s_listener.waterlevel > 2 )\n\t\tidsp_room = roomwater_type.value;\n\telse idsp_room = room_type.value;\n\n\t// don't pass invalid presets\n\tidsp_room = bound( 0, idsp_room, MAX_ROOM_TYPES );\n\n\tif( FBitSet( hisound.flags, FCVAR_CHANGED ))\n\t{\n\t\tsxhires = hisound.value;\n\t\tClearBits( hisound.flags, FCVAR_CHANGED );\n\t}\n\n\tif( idsp_room == room_typeprev && idsp_room == 0 )\n\t\treturn;\n\n\tif( idsp_room != room_typeprev )\n\t{\n\t\tconst sx_preset_t *cur;\n\n\t\tcur = ptable + idsp_room;\n\n\t\tCvar_DirectSetValue( &sxmod_lowpass, cur->room_lp );\n\t\tCvar_DirectSetValue( &sxmod_mod, cur->room_mod );\n\t\tCvar_DirectSetValue( &sxrvb_size, cur->room_size );\n\t\tCvar_DirectSetValue( &sxrvb_feedback, cur->room_refl );\n\t\tCvar_DirectSetValue( &sxrvb_lp, cur->room_rvblp );\n\t\tCvar_DirectSetValue( &sxdly_delay, cur->room_delay );\n\t\tCvar_DirectSetValue( &sxdly_feedback, cur->room_feedback );\n\t\tCvar_DirectSetValue( &sxdly_lp, cur->room_dlylp );\n\t\tCvar_DirectSetValue( &sxste_delay, cur->room_left );\n\t}\n\n\troom_typeprev = idsp_room;\n\n\tRVB_CheckNewReverbVal( );\n\tDLY_CheckNewDelayVal( );\n\tDLY_CheckNewStereoDelayVal();\n\n\tClearBits( sxrvb_size.flags, FCVAR_CHANGED );\n\tClearBits( sxdly_delay.flags, FCVAR_CHANGED );\n\tClearBits( sxste_delay.flags, FCVAR_CHANGED );\n}\n\nstatic void SX_Profiling_f( void )\n{\n\tportable_samplepair_t\ttestbuffer[512];\n\tfloat\t\t\toldroom = room_type.value;\n\tdouble\t\t\tstart, end;\n\tint\t\t\ti, calls;\n\n\tfor( i = 0; i < 512; i++ )\n\t{\n\t\ttestbuffer[i].left = COM_RandomLong( 0, 3000 );\n\t\ttestbuffer[i].right = COM_RandomLong( 0, 3000 );\n\t}\n\n\tif( Cmd_Argc() > 1 )\n\t{\n\t\tCvar_DirectSetValue( &room_type, Q_atof( Cmd_Argv( 1 )));\n\t\tSX_ReloadRoomFX();\n\t\tCheckNewDspPresets(); // we just need idsp_room immediately, for message below\n\t}\n\n\tCon_Printf( \"Profiling 10000 calls to DSP. Sample count is 512, room_type is %i\\n\", idsp_room );\n\n\tstart = Sys_DoubleTime();\n\tfor( calls = 10000; calls; calls-- )\n\t{\n\t\tDSP_Process( testbuffer, 512 );\n\t}\n\tend = Sys_DoubleTime();\n\n\tCon_Printf( \"----------\\nTook %g seconds.\\n\", end - start );\n\n\tif( Cmd_Argc() > 1 )\n\t{\n\t\tCvar_DirectSetValue( &room_type, oldroom );\n\t\tSX_ReloadRoomFX();\n\t\tCheckNewDspPresets();\n\t}\n}\n"
  },
  {
    "path": "engine/client/s_load.c",
    "content": "/*\ns_load.c - sounds managment\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"sound.h\"\n\n// during registration it is possible to have more sounds\n// than could actually be referenced during gameplay,\n// because we don't want to free anything until we are\n// sure we won't need it.\n#define MAX_SFX\t\t8192\n#define MAX_SFX_HASH\t(MAX_SFX/4)\n\nstatic int\ts_numSfx = 0;\nstatic sfx_t\ts_knownSfx[MAX_SFX];\nstatic sfx_t\t*s_sfxHashList[MAX_SFX_HASH];\nstatic string\ts_sentenceImmediateName;\t// keep dummy sentence name\nqboolean\t\ts_registering = false;\n\n/*\n=================\nS_SoundList_f\n=================\n*/\nvoid S_SoundList_f( void )\n{\n\tsfx_t\t\t*sfx;\n\twavdata_t\t\t*sc;\n\tint\t\ti, totalSfx = 0;\n\tint\t\ttotalSize = 0;\n\n\tfor( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )\n\t{\n\t\tif( !sfx->name[0] )\n\t\t\tcontinue;\n\n\t\tsc = sfx->cache;\n\t\tif( sc )\n\t\t{\n\t\t\ttotalSize += sc->size;\n\n\t\t\tif( FBitSet( sc->flags, SOUND_LOOPED ))\n\t\t\t\tCon_Printf( \"L\" );\n\t\t\telse\n\t\t\t\tCon_Printf( \" \" );\n\n\t\t\tif( sfx->name[0] == '*' || !Q_strncmp( sfx->name, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ))\n\t\t\t\tCon_Printf( \" (%2db) %s : %s\\n\", sc->width * 8, Q_memprint( sc->size ), sfx->name );\n\t\t\telse Con_Printf( \" (%2db) %s : \" DEFAULT_SOUNDPATH \"%s\\n\", sc->width * 8, Q_memprint( sc->size ), sfx->name );\n\t\t\ttotalSfx++;\n\t\t}\n\t}\n\n\tCon_Printf( \"-------------------------------------------\\n\" );\n\tCon_Printf( \"%i total sounds\\n\", totalSfx );\n\tCon_Printf( \"%s total memory\\n\", Q_memprint( totalSize ));\n\tCon_Printf( \"\\n\" );\n}\n\n// return true if char 'c' is one of 1st 2 characters in pch\nqboolean S_TestSoundChar( const char *pch, char c )\n{\n\tchar\t*pcht = (char *)pch;\n\tint\ti;\n\n\tif( !pch || !*pch )\n\t\treturn false;\n\n\t// check first 2 characters\n\tfor( i = 0; i < 2; i++ )\n\t{\n\t\tif( *pcht == c )\n\t\t\treturn true;\n\t\tpcht++;\n\t}\n\treturn false;\n}\n\n// return pointer to first valid character in file name\nchar *S_SkipSoundChar( const char *pch )\n{\n\tchar *pcht = (char *)pch;\n\n\t// check first character\n\tif( *pcht == '!' )\n\t\tpcht++;\n\treturn pcht;\n}\n\n/*\n=================\nS_CreateDefaultSound\n=================\n*/\nstatic wavdata_t *S_CreateDefaultSound( void )\n{\n\twavdata_t *sc;\n\tuint samples = SOUND_DMA_SPEED;\n\tuint channels = 1;\n\tuint width = 2;\n\tsize_t size = samples * width * channels;\n\n\tsc = Mem_Calloc( sndpool, sizeof( wavdata_t ) + size );\n\tsc->width = width;\n\tsc->channels = channels;\n\tsc->rate = SOUND_DMA_SPEED;\n\tsc->samples = samples;\n\tsc->size = size;\n\n\treturn sc;\n}\n\n/*\n=================\nS_LoadSound\n=================\n*/\nwavdata_t *S_LoadSound( sfx_t *sfx )\n{\n\twavdata_t\t*sc = NULL;\n\n\tif( !sfx ) return NULL;\n\n\t// see if still in memory\n\tif( sfx->cache )\n\t\treturn sfx->cache;\n\n\tif( !COM_CheckString( sfx->name ))\n\t\treturn NULL;\n\n\t// load it from disk\n\tif( Q_stricmp( sfx->name, \"*default\" ))\n\t{\n\t\t// load it from disk\n\t\tif( s_warn_late_precache.value > 0 && cls.state == ca_active )\n\t\t\tCon_Printf( S_WARN \"%s: late precache of %s\\n\", __func__, sfx->name );\n\n\t\tif( sfx->name[0] == '*' )\n\t\t\tsc = FS_LoadSound( sfx->name + 1, NULL, 0 );\n\t\telse sc = FS_LoadSound( sfx->name, NULL, 0 );\n\t}\n\n\tif( !sc ) sc = S_CreateDefaultSound();\n\n\tif( sc->rate < SOUND_11k ) // some bad sounds\n\t\tSound_Process( &sc, SOUND_11k, sc->width, sc->channels, SOUND_RESAMPLE );\n\telse if( sc->rate > SOUND_11k && sc->rate < SOUND_22k ) // some bad sounds\n\t\tSound_Process( &sc, SOUND_22k, sc->width, sc->channels, SOUND_RESAMPLE );\n\telse if( sc->rate > SOUND_22k && sc->rate != SOUND_44k ) // some bad sounds\n\t\tSound_Process( &sc, SOUND_44k, sc->width, sc->channels, SOUND_RESAMPLE );\n\n\tsfx->cache = sc;\n\n\treturn sfx->cache;\n}\n\n// =======================================================================\n// Load a sound\n// =======================================================================\n/*\n==================\nS_FindName\n\n==================\n*/\nsfx_t *S_FindName( const char *pname, int *pfInCache )\n{\n\tsfx_t\t*sfx;\n\tuint\ti, hash;\n\tstring\tname;\n\n\tif( !COM_CheckString( pname ) || !dma.initialized )\n\t\treturn NULL;\n\n\tif( Q_strlen( pname ) >= sizeof( sfx->name ))\n\t\treturn NULL;\n\n\tQ_strncpy( name, pname, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\t// see if already loaded\n\thash = COM_HashKey( name, MAX_SFX_HASH );\n\tfor( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext )\n\t{\n\t\tif( !Q_strcmp( sfx->name, name ))\n\t\t{\n\t\t\tif( pfInCache )\n\t\t\t{\n\t\t\t\t// indicate whether or not sound is currently in the cache.\n\t\t\t\t*pfInCache = ( sfx->cache != NULL ) ? true : false;\n\t\t\t}\n\t\t\t// prolonge registration\n\t\t\tsfx->servercount = cl.servercount;\n\t\t\treturn sfx;\n\t\t}\n\t}\n\n\t// find a free sfx slot spot\n\tfor( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++)\n\t\tif( !sfx->name[0] ) break; // free spot\n\n\tif( i == s_numSfx )\n\t{\n\t\tif( s_numSfx == MAX_SFX )\n\t\t\treturn NULL;\n\t\ts_numSfx++;\n\t}\n\n\tsfx = &s_knownSfx[i];\n\tmemset( sfx, 0, sizeof( *sfx ));\n\tif( pfInCache ) *pfInCache = false;\n\tQ_strncpy( sfx->name, name, sizeof( sfx->name ));\n\tsfx->servercount = cl.servercount;\n\tsfx->hashValue = COM_HashKey( sfx->name, MAX_SFX_HASH );\n\n\t// link it in\n\tsfx->hashNext = s_sfxHashList[sfx->hashValue];\n\ts_sfxHashList[sfx->hashValue] = sfx;\n\n\treturn sfx;\n}\n\n/*\n==================\nS_FreeSound\n==================\n*/\nvoid S_FreeSound( sfx_t *sfx )\n{\n\tsfx_t\t*hashSfx;\n\tsfx_t\t**prev;\n\n\tif( !sfx || !sfx->name[0] )\n\t\treturn;\n\n\t// de-link it from the hash tree\n\tprev = &s_sfxHashList[sfx->hashValue];\n\twhile( 1 )\n\t{\n\t\thashSfx = *prev;\n\t\tif( !hashSfx )\n\t\t\tbreak;\n\n\t\tif( hashSfx == sfx )\n\t\t{\n\t\t\t*prev = hashSfx->hashNext;\n\t\t\tbreak;\n\t\t}\n\t\tprev = &hashSfx->hashNext;\n\t}\n\n\tif( sfx->cache )\n\t\tFS_FreeSound( sfx->cache );\n\tmemset( sfx, 0, sizeof( *sfx ));\n}\n\n/*\n=====================\nS_BeginRegistration\n\n=====================\n*/\nvoid S_BeginRegistration( void )\n{\n\tint\ti;\n\n\tsnd_ambient = false;\n\n\t// check for automatic ambient sounds\n\tfor( i = 0; i < NUM_AMBIENTS; i++ )\n\t{\n\t\tif( !GI->ambientsound[i][0] )\n\t\t\tcontinue;\t// empty slot\n\n\t\tambient_sfx[i] = S_RegisterSound( GI->ambientsound[i] );\n\t\tif( ambient_sfx[i] ) snd_ambient = true; // allow auto-ambients\n\t}\n\n\ts_registering = true;\n}\n\n/*\n=====================\nS_EndRegistration\n\n=====================\n*/\nvoid S_EndRegistration( void )\n{\n\tsfx_t\t*sfx;\n\tint\ti;\n\n\tif( !s_registering || !dma.initialized )\n\t\treturn;\n\n\t// free any sounds not from this registration sequence\n\tfor( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )\n\t{\n\t\tif( !sfx->name[0] || !Q_stricmp( sfx->name, \"*default\" ))\n\t\t\tcontinue; // don't release default sound\n\n\t\tif( sfx->servercount != cl.servercount )\n\t\t\tS_FreeSound( sfx ); // don't need this sound\n\t}\n\n\t// load everything in\n\tfor( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )\n\t{\n\t\tif( !sfx->name[0] )\n\t\t\tcontinue;\n\t\tS_LoadSound( sfx );\n\t}\n\ts_registering = false;\n}\n\n/*\n==================\nS_RegisterSound\n\n==================\n*/\nsound_t S_RegisterSound( const char *name )\n{\n\tsfx_t\t*sfx;\n\n\tif( !COM_CheckString( name ) || !dma.initialized )\n\t\treturn -1;\n\n\tif( S_TestSoundChar( name, '!' ))\n\t{\n\t\tQ_strncpy( s_sentenceImmediateName, name, sizeof( s_sentenceImmediateName ));\n\t\treturn SENTENCE_INDEX;\n\t}\n\n\t// some stupid mappers used leading '/' or '\\' in path to models or sounds\n\tif( name[0] == '/' || name[0] == '\\\\' ) name++;\n\tif( name[0] == '/' || name[0] == '\\\\' ) name++;\n\n\tsfx = S_FindName( name, NULL );\n\tif( !sfx ) return -1;\n\n\tsfx->servercount = cl.servercount;\n\tif( !s_registering ) S_LoadSound( sfx );\n\n\treturn sfx - s_knownSfx;\n}\n\nsfx_t *S_GetSfxByHandle( sound_t handle )\n{\n\tif( !dma.initialized )\n\t\treturn NULL;\n\n\t// create new sfx\n\tif( handle == SENTENCE_INDEX )\n\t\treturn S_FindName( s_sentenceImmediateName, NULL );\n\n\tif( handle < 0 || handle >= s_numSfx )\n\t\treturn NULL;\n\n\treturn &s_knownSfx[handle];\n}\n\n/*\n=================\nS_InitSounds\n=================\n*/\nvoid S_InitSounds( void )\n{\n\t// create unused 0-entry\n\tQ_strncpy( s_knownSfx->name, \"*default\", sizeof( s_knownSfx->name ));\n\ts_knownSfx->hashValue = COM_HashKey( s_knownSfx->name, MAX_SFX_HASH );\n\ts_knownSfx->hashNext = s_sfxHashList[s_knownSfx->hashValue];\n\ts_sfxHashList[s_knownSfx->hashValue] = s_knownSfx;\n\ts_knownSfx->cache = S_CreateDefaultSound();\n\ts_numSfx = 1;\n}\n\n/*\n=================\nS_FreeSounds\n=================\n*/\nvoid S_FreeSounds( void )\n{\n\tsfx_t\t*sfx;\n\tint\ti;\n\n\tif( !dma.initialized )\n\t\treturn;\n\n\t// stop all sounds\n\tS_StopAllSounds( true );\n\n\t// free all sounds\n\tfor( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )\n\t\tS_FreeSound( sfx );\n\n\tmemset( s_knownSfx, 0, sizeof( s_knownSfx ));\n\tmemset( s_sfxHashList, 0, sizeof( s_sfxHashList ));\n\n\ts_numSfx = 0;\n}\n"
  },
  {
    "path": "engine/client/s_main.c",
    "content": "/*\ns_main.c - sound engine\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n#include \"client.h\"\n#include \"con_nprint.h\"\n#include \"pm_local.h\"\n#include \"platform/platform.h\"\n\ndma_t\t\tdma;\npoolhandle_t sndpool;\nstatic soundfade_t\tsoundfade;\nchannel_t   \tchannels[MAX_CHANNELS];\nsound_t\t\tambient_sfx[NUM_AMBIENTS];\nrawchan_t\t\t*raw_channels[MAX_RAW_CHANNELS];\nqboolean\t\tsnd_ambient = false;\nqboolean\t\tsnd_fade_sequence = false;\nlistener_t\ts_listener;\nint\t\ttotal_channels;\nint\t\tsoundtime;\t// sample PAIRS\nint   \t\tpaintedtime; \t// sample PAIRS\n\nstatic CVAR_DEFINE( s_volume, \"volume\", \"0.7\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"sound volume\" );\nCVAR_DEFINE( s_musicvolume, \"MP3Volume\", \"1.0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"background music volume\" );\nstatic CVAR_DEFINE( s_mixahead, \"_snd_mixahead\", \"0.12\", FCVAR_FILTERABLE, \"how much sound to mix ahead of time\" );\nstatic CVAR_DEFINE_AUTO( s_show, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"show playing sounds\" );\nCVAR_DEFINE_AUTO( s_lerping, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"apply interpolation to sound output\" );\nstatic CVAR_DEFINE( s_ambient_level, \"ambient_level\", \"0.3\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"volume of environment noises (water and wind)\" );\nstatic CVAR_DEFINE( s_ambient_fade, \"ambient_fade\", \"1000\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"rate of volume fading when client is moving\" );\nstatic CVAR_DEFINE_AUTO( s_combine_sounds, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"combine channels with same sounds\" );\nCVAR_DEFINE_AUTO( snd_mute_losefocus, \"1\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"silence the audio when game window loses focus\" );\nCVAR_DEFINE_AUTO( s_test, \"0\", 0, \"engine developer cvar for quick testing new features\" );\nCVAR_DEFINE_AUTO( s_samplecount, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"sample count (0 for default value)\" );\nCVAR_DEFINE_AUTO( s_warn_late_precache, \"0\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"warn about late precached sounds on client-side\" );\n\n/*\n=============================================================================\n\n\t\tSOUNDS PROCESSING\n\n=============================================================================\n*/\n/*\n=================\nS_GetMasterVolume\n=================\n*/\nfloat S_GetMasterVolume( void )\n{\n\tfloat\tscale = 1.0f;\n\n\tif( host.status == HOST_NOFOCUS && snd_mute_losefocus.value != 0.0f )\n\t{\n\t\t// we return zero volume to keep sounds running\n\t\treturn 0.0f;\n\t}\n\n\tif( !s_listener.inmenu && soundfade.percent != 0 )\n\t{\n\t\tscale = bound( 0.0f, soundfade.percent / 100.0f, 1.0f );\n\t\tscale = 1.0f - scale;\n\t}\n\treturn s_volume.value * scale;\n}\n\n/*\n=================\nS_FadeClientVolume\n=================\n*/\nvoid S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds )\n{\n\tsoundfade.starttime\t= cl.mtime[0];\n\tsoundfade.initial_percent = fadePercent;\n\tsoundfade.fadeouttime = fadeOutSeconds;\n\tsoundfade.holdtime = holdTime;\n\tsoundfade.fadeintime = fadeInSeconds;\n}\n\n/*\n=================\nS_IsClient\n=================\n*/\nstatic qboolean S_IsClient( int entnum )\n{\n\treturn ( entnum == s_listener.entnum );\n}\n\n\n// free channel so that it may be allocated by the\n// next request to play a sound.  If sound is a\n// word in a sentence, release the sentence.\n// Works for static, dynamic, sentence and stream sounds\n/*\n=================\nS_FreeChannel\n=================\n*/\nvoid S_FreeChannel( channel_t *ch )\n{\n\tch->sfx = NULL;\n\tch->name[0] = '\\0';\n\tch->use_loop = false;\n\tch->isSentence = false;\n\n\t// clear mixer\n\tmemset( &ch->pMixer, 0, sizeof( ch->pMixer ));\n\n\tSND_CloseMouth( ch );\n}\n\n/*\n=================\nS_UpdateSoundFade\n=================\n*/\nstatic void S_UpdateSoundFade( void )\n{\n\tfloat\tf, totaltime, elapsed;\n\n\t// determine current fade value.\n\t// assume no fading remains\n\tsoundfade.percent = 0;\n\n\ttotaltime = soundfade.fadeouttime + soundfade.fadeintime + soundfade.holdtime;\n\n\telapsed = cl.mtime[0] - soundfade.starttime;\n\n\t// clock wrapped or reset (BUG) or we've gone far enough\n\tif( elapsed < 0.0f || elapsed >= totaltime || totaltime <= 0.0f )\n\t\treturn;\n\n\t// We are in the fade time, so determine amount of fade.\n\tif( soundfade.fadeouttime > 0.0f && ( elapsed < soundfade.fadeouttime ))\n\t{\n\t\t// ramp up\n\t\tf = elapsed / soundfade.fadeouttime;\n\t}\n\telse if( elapsed <= ( soundfade.fadeouttime + soundfade.holdtime ))\t// Inside the hold time\n\t{\n\t\t// stay\n\t\tf = 1.0f;\n\t}\n\telse\n\t{\n\t\t// ramp down\n\t\tf = ( elapsed - ( soundfade.fadeouttime + soundfade.holdtime ) ) / soundfade.fadeintime;\n\t\tf = 1.0f - f; // backward interpolated...\n\t}\n\n\t// spline it.\n\tf = -( cos( M_PI * f ) - 1 ) / 2;\n\tf = bound( 0.0f, f, 1.0f );\n\n\tsoundfade.percent = soundfade.initial_percent * f;\n\n\tif( snd_fade_sequence )\n\t\tS_FadeMusicVolume( soundfade.percent );\n\n\tif( snd_fade_sequence && soundfade.percent == 100.0f )\n\t{\n\t\tS_StopAllSounds( false );\n\t\tS_StopBackgroundTrack();\n\t\tsnd_fade_sequence = false;\n\t}\n}\n\n/*\n=================\nSND_FStreamIsPlaying\n\nSelect a channel from the dynamic channel allocation area.  For the given entity,\noverride any other sound playing on the same channel (see code comments below for\nexceptions).\n=================\n*/\nstatic qboolean SND_FStreamIsPlaying( sfx_t *sfx )\n{\n\tint\tch_idx;\n\n\tfor( ch_idx = NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++ )\n\t{\n\t\tif( channels[ch_idx].sfx == sfx )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n=================\nSND_GetChannelTimeLeft\n\nTODO: this function needs to be removed after whole sound subsystem rewrite\n=================\n*/\nstatic int SND_GetChannelTimeLeft( const channel_t *ch )\n{\n\tint remaining;\n\n\tif( ch->pMixer.finished || !ch->sfx || !ch->sfx->cache )\n\t\treturn 0;\n\n\tif( ch->isSentence ) // sentences are special, count all remaining words\n\t{\n\t\tint i;\n\n\t\tif( !ch->currentWord )\n\t\t\treturn 0;\n\n\t\t// current word\n\t\tremaining = ch->currentWord->forcedEndSample - ch->currentWord->sample;\n\n\t\t// here we count all remaining words, stopping if no sfx or sound file is available\n\t\t// see VOX_LoadWord\n\t\tfor( i = ch->wordIndex + 1; i < ARRAYSIZE( ch->words ); i++ )\n\t\t{\n\t\t\twavdata_t *sc;\n\t\t\tint end;\n\n\t\t\t// don't continue with broken sentences\n\t\t\tif( !ch->words[i].sfx )\n\t\t\t\tbreak;\n\n\t\t\tif( !( sc = S_LoadSound( ch->words[i].sfx )))\n\t\t\t\tbreak;\n\n\t\t\tend = ch->words[i].end;\n\n\t\t\tif( end )\n\t\t\t\tremaining += sc->samples * 0.01f * end;\n\t\t\telse remaining += sc->samples;\n\t\t}\n\t}\n\telse\n\t{\n\t\tint curpos;\n\t\tint samples;\n\n\t\t// handle position looping\n\t\tsamples = ch->sfx->cache->samples;\n\t\tcurpos = S_ConvertLoopedPosition( ch->sfx->cache, ch->pMixer.sample, ch->use_loop );\n\t\tremaining = bound( 0, samples - curpos, samples );\n\t}\n\n\treturn remaining;\n}\n\n/*\n=================\nSND_PickDynamicChannel\n\nSelect a channel from the dynamic channel allocation area.  For the given entity,\noverride any other sound playing on the same channel (see code comments below for\nexceptions).\n=================\n*/\nchannel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean *ignore )\n{\n\tint\tch_idx;\n\tint\tfirst_to_die;\n\tint\tlife_left;\n\tint\ttimeleft;\n\n\t// check for replacement sound, or find the best one to replace\n\tfirst_to_die = -1;\n\tlife_left = 0x7fffffff;\n\tif( ignore ) *ignore = false;\n\n\tif( channel == CHAN_STREAM && SND_FStreamIsPlaying( sfx ))\n\t{\n\t\tif( ignore )\n\t\t\t*ignore = true;\n\t\treturn NULL;\n\t}\n\n\tfor( ch_idx = NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++ )\n\t{\n\t\tchannel_t\t*ch = &channels[ch_idx];\n\n\t\t// Never override a streaming sound that is currently playing or\n\t\t// voice over IP data that is playing or any sound on CHAN_VOICE( acting )\n\t\tif( ch->sfx && ( ch->entchannel == CHAN_STREAM ))\n\t\t\tcontinue;\n\n\t\tif( channel != CHAN_AUTO && ch->entnum == entnum && ( ch->entchannel == channel || channel == -1 ))\n\t\t{\n\t\t\t// always override sound from same entity\n\t\t\tfirst_to_die = ch_idx;\n\t\t\tbreak;\n\t\t}\n\n\t\t// don't let monster sounds override player sounds\n\t\tif( ch->sfx && S_IsClient( ch->entnum ) && !S_IsClient( entnum ))\n\t\t\tcontinue;\n\n\t\t// try to pick the sound with the least amount of data left to play\n\t\ttimeleft = SND_GetChannelTimeLeft( ch );\n\n\t\tif( timeleft < life_left )\n\t\t{\n\t\t\tlife_left = timeleft;\n\t\t\tfirst_to_die = ch_idx;\n\t\t}\n\t}\n\n\tif( first_to_die == -1 )\n\t\treturn NULL;\n\n\tif( channels[first_to_die].sfx )\n\t{\n\t\t// don't restart looping sounds for the same entity\n\t\twavdata_t\t*sc = channels[first_to_die].sfx->cache;\n\n\t\tif( sc && FBitSet( sc->flags, SOUND_LOOPED ))\n\t\t{\n\t\t\tchannel_t\t*ch = &channels[first_to_die];\n\n\t\t\tif( ch->entnum == entnum && ch->entchannel == channel && ch->sfx == sfx )\n\t\t\t{\n\t\t\t\tif( ignore ) *ignore = true;\n\t\t\t\t// same looping sound, same ent, same channel, don't restart the sound\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\t// be sure and release previous channel if sentence.\n\t\tS_FreeChannel( &( channels[first_to_die] ));\n\t}\n\n\treturn &channels[first_to_die];\n}\n\n/*\n=====================\nSND_PickStaticChannel\n\nPick an empty channel from the static sound area, or allocate a new\nchannel.  Only fails if we're at max_channels (128!!!) or if\nwe're trying to allocate a channel for a stream sound that is\nalready playing.\n=====================\n*/\nchannel_t *SND_PickStaticChannel( const vec3_t pos, sfx_t *sfx )\n{\n\tchannel_t\t*ch = NULL;\n\tint\ti;\n\n\t// check for replacement sound, or find the best one to replace\n \tfor( i = MAX_DYNAMIC_CHANNELS; i < total_channels; i++ )\n \t{\n\t\tif( channels[i].sfx == NULL )\n\t\t\tbreak;\n\n\t\tif( VectorCompare( pos, channels[i].origin ) && channels[i].sfx == sfx )\n\t\t\tbreak;\n\t}\n\n\tif( i < total_channels )\n\t{\n\t\t// reuse an empty static sound channel\n\t\tch = &channels[i];\n\t}\n\telse\n\t{\n\t\t// no empty slots, alloc a new static sound channel\n\t\tif( total_channels == MAX_CHANNELS )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: no free channels\\n\", __func__ );\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// get a channel for the static sound\n\t\tch = &channels[total_channels];\n\t\ttotal_channels++;\n\t}\n\treturn ch;\n}\n\n/*\n=================\nS_AlterChannel\n\nsearch through all channels for a channel that matches this\nsoundsource, entchannel and sfx, and perform alteration on channel\nas indicated by 'flags' parameter. If shut down request and\nsfx contains a sentence name, shut off the sentence.\nreturns TRUE if sound was altered,\nreturns FALSE if sound was not found (sound is not playing)\n=================\n*/\nstatic int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int flags )\n{\n\tchannel_t\t*ch;\n\tint\ti;\n\n\tif( S_TestSoundChar( sfx->name, '!' ))\n\t{\n\t\t// This is a sentence name.\n\t\t// For sentences: assume that the entity is only playing one sentence\n\t\t// at a time, so we can just shut off\n\t\t// any channel that has ch->isSentence >= 0 and matches the entnum.\n\n\t\tfor( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ )\n\t\t{\n\t\t\tif( ch->entnum == entnum && ch->entchannel == channel && ch->sfx && ch->isSentence )\n\t\t\t{\n\t\t\t\tif( flags & SND_CHANGE_PITCH )\n\t\t\t\t\tch->basePitch = pitch;\n\n\t\t\t\tif( flags & SND_CHANGE_VOL )\n\t\t\t\t\tch->master_vol = vol;\n\n\t\t\t\tif( flags & SND_STOP )\n\t\t\t\t\tS_FreeChannel( ch );\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\t// channel not found\n\t\treturn false;\n\n\t}\n\n\t// regular sound or streaming sound\n\tfor( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ )\n\t{\n\t\tif( ch->entnum == entnum && ch->entchannel == channel && ch->sfx == sfx )\n\t\t{\n\t\t\tif( flags & SND_CHANGE_PITCH )\n\t\t\t\tch->basePitch = pitch;\n\n\t\t\tif( flags & SND_CHANGE_VOL )\n\t\t\t\tch->master_vol = vol;\n\n\t\t\tif( flags & SND_STOP )\n\t\t\t\tS_FreeChannel( ch );\n\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/*\n=================\nS_SpatializeChannel\n=================\n*/\nstatic void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float gain, float dot, float dist )\n{\n\tfloat\tlscale, rscale, scale;\n\n\trscale = 1.0f + dot;\n\tlscale = 1.0f - dot;\n\n\t// add in distance effect\n\tscale = ( 1.0f - dist ) * rscale;\n\t*right_vol = (int)( master_vol * scale );\n\n\tscale = ( 1.0f - dist ) * lscale;\n\t*left_vol = (int)( master_vol * scale );\n\n\t*right_vol = bound( 0, *right_vol, 255 );\n\t*left_vol = bound( 0, *left_vol, 255 );\n}\n\n/*\n=================\nSND_Spatialize\n=================\n*/\nstatic void SND_Spatialize( channel_t *ch )\n{\n\tvec3_t\tsource_vec;\n\tfloat\tdist, dot, gain = 1.0f;\n\tqboolean\tlooping = false;\n\twavdata_t\t*pSource;\n\n\t// anything coming from the view entity will allways be full volume\n\tif( S_IsClient( ch->entnum ))\n\t{\n\t\tch->leftvol = ch->master_vol;\n\t\tch->rightvol = ch->master_vol;\n\t\treturn;\n\t}\n\n\tpSource = ch->sfx->cache;\n\n\tif( ch->use_loop && pSource && FBitSet( pSource->flags, SOUND_LOOPED ))\n\t\tlooping = true;\n\n\tif( !ch->staticsound )\n\t{\n\t\tif( !CL_GetEntitySpatialization( ch ))\n\t\t{\n\t\t\t// origin is null and entity not exist on client\n\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// source_vec is vector from listener to sound source\n\t// player sounds come from 1' in front of player\n\tVectorSubtract( ch->origin, s_listener.origin, source_vec );\n\n\t// normalize source_vec and get distance from listener to source\n\tdist = VectorNormalizeLength( source_vec );\n\tdot = DotProduct( s_listener.right, source_vec );\n\n\tif( !FBitSet( host.bugcomp, BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE ))\n\t{\n\t\t// don't pan sounds with no attenuation\n\t\tif( ch->dist_mult <= 0.0f ) dot = 0.0f;\n\t}\n\n\t// fill out channel volumes for single location\n\tS_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, gain, dot, dist * ch->dist_mult );\n\n\t// if playing a word, set volume\n\tVOX_SetChanVol( ch );\n}\n\n/*\n====================\nS_StartSound\n\nStart a sound effect for the given entity on the given channel (ie; voice, weapon etc).\nTry to grab a channel out of the 8 dynamic spots available.\nCurrently used for looping sounds, streaming sounds, sentences, and regular entity sounds.\nNOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.\nPitch changes playback pitch of wave by % above or below 100.  Ignored if pitch == 100\n\nNOTE: it's not a good idea to play looping sounds through StartDynamicSound, because\nif the looping sound starts out of range, or is bumped from the buffer by another sound\nit will never be restarted.  Use StartStaticSound (pass CHAN_STATIC to EMIT_SOUND or\nSV_StartSound.\n====================\n*/\nvoid S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags )\n{\n\twavdata_t\t*pSource;\n\tsfx_t\t*sfx = NULL;\n\tchannel_t\t*target_chan, *check;\n\tint\tvol, ch_idx;\n\tqboolean\tbIgnore = false;\n\n\tif( !dma.initialized ) return;\n\tsfx = S_GetSfxByHandle( handle );\n\tif( !sfx ) return;\n\n\tvol = bound( 0, fvol * 255, 255 );\n\tif( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues\n\n\tif( flags & ( SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH ))\n\t{\n\t\tif( S_AlterChannel( ent, chan, sfx, vol, pitch, flags ))\n\t\t\treturn;\n\n\t\tif( flags & SND_STOP ) return;\n\t\t// fall through - if we're not trying to stop the sound,\n\t\t// and we didn't find it (it's not playing), go ahead and start it up\n\t}\n\n\tif( !pos ) pos = refState.vieworg;\n\n\tif( chan == CHAN_STREAM )\n\t\tSetBits( flags, SND_STOP_LOOPING );\n\n\t// pick a channel to play on\n\tif( chan == CHAN_STATIC ) target_chan = SND_PickStaticChannel( pos, sfx );\n\telse target_chan = SND_PickDynamicChannel( ent, chan, sfx, &bIgnore );\n\n\tif( !target_chan )\n\t{\n\t\tif( !bIgnore )\n\t\t\tCon_DPrintf( S_ERROR \"dropped sound \\\"\" DEFAULT_SOUNDPATH \"%s\\\"\\n\", sfx->name );\n\t\treturn;\n\t}\n\n\t// spatialize\n\tmemset( target_chan, 0, sizeof( *target_chan ));\n\n\tVectorCopy( pos, target_chan->origin );\n\ttarget_chan->staticsound = ( ent == 0 ) ? true : false;\n\ttarget_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true;\n\ttarget_chan->localsound = (flags & SND_LOCALSOUND) ? true : false;\n\ttarget_chan->dist_mult = (attn / SND_CLIP_DISTANCE);\n\ttarget_chan->master_vol = vol;\n\ttarget_chan->entnum = ent;\n\ttarget_chan->entchannel = chan;\n\ttarget_chan->basePitch = pitch;\n\ttarget_chan->isSentence = false;\n\ttarget_chan->sfx = sfx;\n\n\tpSource = NULL;\n\n\tif( S_TestSoundChar( sfx->name, '!' ))\n\t{\n\t\t// this is a sentence\n\t\t// link all words and load the first word\n\t\t// NOTE: sentence names stored in the cache lookup are\n\t\t// prepended with a '!'.  Sentence names stored in the\n\t\t// sentence file do not have a leading '!'.\n\t\tVOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name ));\n\t\tQ_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name ));\n\t\tsfx = target_chan->sfx;\n\t\tif( sfx ) pSource = sfx->cache;\n\t}\n\telse\n\t{\n\t\t// regular or streamed sound fx\n\t\tpSource = S_LoadSound( sfx );\n\t\ttarget_chan->name[0] = '\\0';\n\t}\n\n\tif( !pSource )\n\t{\n\t\tS_FreeChannel( target_chan );\n\t\treturn;\n\t}\n\n\tSND_Spatialize( target_chan );\n\n\t// If a client can't hear a sound when they FIRST receive the StartSound message,\n\t// the client will never be able to hear that sound. This is so that out of\n\t// range sounds don't fill the playback buffer. For streaming sounds, we bypass this optimization.\n\tif( !target_chan->leftvol && !target_chan->rightvol )\n\t{\n\t\t// looping sounds don't use this optimization because they should stick around until they're killed.\n\t\tif( !sfx->cache || !FBitSet( sfx->cache->flags, SOUND_LOOPED ))\n\t\t{\n\t\t\t// if this is a streaming sound, play the whole thing.\n\t\t\tif( chan != CHAN_STREAM )\n\t\t\t{\n\t\t\t\tS_FreeChannel( target_chan );\n\t\t\t\treturn; // not audible at all\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init client entity mouth movement vars\n\tSND_InitMouth( ent, chan );\n}\n\n/*\n====================\nS_RestoreSound\n\nRestore a sound effect for the given entity on the given channel\n====================\n*/\nvoid S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex )\n{\n\twavdata_t\t*pSource;\n\tsfx_t\t*sfx = NULL;\n\tchannel_t\t*target_chan;\n\tqboolean\tbIgnore = false;\n\tint\tvol;\n\n\tif( !dma.initialized ) return;\n\tsfx = S_GetSfxByHandle( handle );\n\tif( !sfx ) return;\n\n\tvol = bound( 0, fvol * 255, 255 );\n\tif( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues\n\n\t// pick a channel to play on\n\tif( chan == CHAN_STATIC ) target_chan = SND_PickStaticChannel( pos, sfx );\n\telse target_chan = SND_PickDynamicChannel( ent, chan, sfx, &bIgnore );\n\n\tif( !target_chan )\n\t{\n\t\tif( !bIgnore )\n\t\t\tCon_DPrintf( S_ERROR \"dropped sound \\\"\" DEFAULT_SOUNDPATH \"%s\\\"\\n\", sfx->name );\n\t\treturn;\n\t}\n\n\t// spatialize\n\tmemset( target_chan, 0, sizeof( *target_chan ));\n\n\tVectorCopy( pos, target_chan->origin );\n\ttarget_chan->staticsound = ( ent == 0 ) ? true : false;\n\ttarget_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true;\n\ttarget_chan->localsound = (flags & SND_LOCALSOUND) ? true : false;\n\ttarget_chan->dist_mult = (attn / SND_CLIP_DISTANCE);\n\ttarget_chan->master_vol = vol;\n\ttarget_chan->entnum = ent;\n\ttarget_chan->entchannel = chan;\n\ttarget_chan->basePitch = pitch;\n\ttarget_chan->isSentence = false;\n\ttarget_chan->sfx = sfx;\n\n\tpSource = NULL;\n\n\tif( S_TestSoundChar( sfx->name, '!' ))\n\t{\n\t\t// this is a sentence\n\t\t// link all words and load the first word\n\t\t// NOTE: sentence names stored in the cache lookup are\n\t\t// prepended with a '!'.  Sentence names stored in the\n\t\t// sentence file do not have a leading '!'.\n\t\tVOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name ));\n\t\tQ_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name ));\n\n\t\t// not a first word in sentence!\n\t\tif( wordIndex != 0 )\n\t\t{\n\t\t\tVOX_FreeWord( target_chan );\t\t// release first loaded word\n\t\t\ttarget_chan->wordIndex = wordIndex;\t// restore current word\n\t\t\tVOX_LoadWord( target_chan );\n\n\t\t\tif( target_chan->currentWord )\n\t\t\t{\n\t\t\t\ttarget_chan->sfx = target_chan->words[target_chan->wordIndex].sfx;\n\t\t\t\tsfx = target_chan->sfx;\n\t\t\t\tpSource = sfx->cache;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsfx = target_chan->sfx;\n\t\t\tif( sfx ) pSource = sfx->cache;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// regular or streamed sound fx\n\t\tpSource = S_LoadSound( sfx );\n\t\ttarget_chan->name[0] = '\\0';\n\t}\n\n\tif( !pSource )\n\t{\n\t\tS_FreeChannel( target_chan );\n\t\treturn;\n\t}\n\n\tSND_Spatialize( target_chan );\n\n\t// NOTE: first spatialization may be failed because listener position is invalid at this time\n\t// so we should keep all sounds an actual and waiting for player spawn.\n\n\t// apply the sample offests\n\ttarget_chan->pMixer.sample = sample;\n\ttarget_chan->pMixer.forcedEndSample = end;\n\n\t// Init client entity mouth movement vars\n\tSND_InitMouth( ent, chan );\n}\n\n/*\n=================\nS_AmbientSound\n\nStart playback of a sound, loaded into the static portion of the channel array.\nCurrently, this should be used for looping ambient sounds, looping sounds\nthat should not be interrupted until complete, non-creature sentences,\nand one-shot ambient streaming sounds.  Can also play 'regular' sounds one-shot,\nin case designers want to trigger regular game sounds.\nPitch changes playback pitch of wave by % above or below 100.  Ignored if pitch == 100\n\nNOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.\n=================\n*/\nvoid S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags )\n{\n\tchannel_t\t*ch;\n\twavdata_t\t*pSource = NULL;\n\tsfx_t\t*sfx = NULL;\n\tint\tvol, fvox = 0;\n\n\tif( !dma.initialized ) return;\n\tsfx = S_GetSfxByHandle( handle );\n\tif( !sfx ) return;\n\n\tvol = bound( 0, fvol * 255, 255 );\n\tif( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues\n\n\tif( flags & (SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH))\n\t{\n\t\tif( S_AlterChannel( ent, CHAN_STATIC, sfx, vol, pitch, flags ))\n\t\t\treturn;\n\t\tif( flags & SND_STOP ) return;\n\t}\n\n\t// pick a channel to play on from the static area\n\tch = SND_PickStaticChannel( pos, sfx );\n\tif( !ch ) return;\n\n\tVectorCopy( pos, ch->origin );\n\tch->entnum = ent;\n\n\tCL_GetEntitySpatialization( ch );\n\n\tif( S_TestSoundChar( sfx->name, '!' ))\n\t{\n\t\t// this is a sentence. link words to play in sequence.\n\t\t// NOTE: sentence names stored in the cache lookup are\n\t\t// prepended with a '!'.  Sentence names stored in the\n\t\t// sentence file do not have a leading '!'.\n\n\t\t// link all words and load the first word\n\t\tVOX_LoadSound( ch, S_SkipSoundChar( sfx->name ));\n\t\tQ_strncpy( ch->name, sfx->name, sizeof( ch->name ));\n\t\tsfx = ch->sfx;\n\t\tif( sfx ) pSource = sfx->cache;\n\t\tfvox = 1;\n\t}\n\telse\n\t{\n\t\t// load regular or stream sound\n\t\tpSource = S_LoadSound( sfx );\n\t\tch->sfx = sfx;\n\t\tch->isSentence = false;\n\t\tch->name[0] = '\\0';\n\t}\n\n\tif( !pSource )\n\t{\n\t\tS_FreeChannel( ch );\n\t\treturn;\n\t}\n\n\tpitch *= (sys_timescale.value + 1) / 2;\n\n\t// never update positions if source entity is 0\n\tch->staticsound = ( ent == 0 ) ? true : false;\n\tch->use_loop = (flags & SND_STOP_LOOPING) ? false : true;\n\tch->localsound = (flags & SND_LOCALSOUND) ? true : false;\n\tch->master_vol = vol;\n\tch->dist_mult = (attn / SND_CLIP_DISTANCE);\n\tch->entchannel = CHAN_STATIC;\n\tch->basePitch = pitch;\n\n\tSND_Spatialize( ch );\n}\n\n/*\n==================\nS_StartLocalSound\n==================\n*/\nvoid S_StartLocalSound(  const char *name, float volume, qboolean reliable )\n{\n\tsound_t\tsfxHandle;\n\tint\tflags = (SND_LOCALSOUND|SND_STOP_LOOPING);\n\tint\tchannel = CHAN_AUTO;\n\n\tif( reliable ) channel = CHAN_STATIC;\n\n\tif( !dma.initialized ) return;\n\tsfxHandle = S_RegisterSound( name );\n\tS_StartSound( NULL, s_listener.entnum, channel, sfxHandle, volume, ATTN_NONE, PITCH_NORM, flags );\n}\n\n/*\n==================\nS_GetCurrentStaticSounds\n\ngrab all static sounds playing at current channel\n==================\n*/\nint S_GetCurrentStaticSounds( soundlist_t *pout, int size )\n{\n\tint\tsounds_left = size;\n\tint\ti;\n\n\tif( !dma.initialized )\n\t\treturn 0;\n\n\tfor( i = MAX_DYNAMIC_CHANNELS; i < total_channels && sounds_left; i++ )\n\t{\n\t\tif( channels[i].entchannel == CHAN_STATIC && channels[i].sfx && channels[i].sfx->name[0] )\n\t\t{\n\t\t\tif( channels[i].isSentence && channels[i].name[0] )\n\t\t\t\tQ_strncpy( pout->name, channels[i].name, sizeof( pout->name ));\n\t\t\telse Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));\n\t\t\tpout->entnum = channels[i].entnum;\n\t\t\tVectorCopy( channels[i].origin, pout->origin );\n\t\t\tpout->volume = (float)channels[i].master_vol / 255.0f;\n\t\t\tpout->attenuation = channels[i].dist_mult * SND_CLIP_DISTANCE;\n\t\t\tpout->looping = ( channels[i].use_loop && FBitSet( channels[i].sfx->cache->flags, SOUND_LOOPED ));\n\t\t\tpout->pitch = channels[i].basePitch;\n\t\t\tpout->channel = channels[i].entchannel;\n\t\t\tpout->wordIndex = channels[i].wordIndex;\n\t\t\tpout->samplePos = channels[i].pMixer.sample;\n\t\t\tpout->forcedEnd = channels[i].pMixer.forcedEndSample;\n\n\t\t\tsounds_left--;\n\t\t\tpout++;\n\t\t}\n\t}\n\n\treturn ( size - sounds_left );\n}\n\n/*\n==================\nS_GetCurrentStaticSounds\n\ngrab all static sounds playing at current channel\n==================\n*/\nint S_GetCurrentDynamicSounds( soundlist_t *pout, int size )\n{\n\tint\tsounds_left = size;\n\tint\ti, looped;\n\n\tif( !dma.initialized )\n\t\treturn 0;\n\n\tfor( i = 0; i < MAX_CHANNELS && sounds_left; i++ )\n\t{\n\t\tif( !channels[i].sfx || !channels[i].sfx->name[0] || !Q_stricmp( channels[i].sfx->name, \"*default\" ))\n\t\t\tcontinue;\t// don't serialize default sounds\n\n\t\tlooped = ( channels[i].use_loop && FBitSet( channels[i].sfx->cache->flags, SOUND_LOOPED ));\n\n\t\tif( channels[i].entchannel == CHAN_STATIC && looped && !Host_IsQuakeCompatible())\n\t\t\tcontinue;\t// never serialize static looped sounds. It will be restoring in game code\n\n\t\tif( channels[i].isSentence && channels[i].name[0] )\n\t\t\tQ_strncpy( pout->name, channels[i].name, sizeof( pout->name ));\n\t\telse Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name ));\n\t\tpout->entnum = (channels[i].entnum < 0) ? 0 : channels[i].entnum;\n\t\tVectorCopy( channels[i].origin, pout->origin );\n\t\tpout->volume = (float)channels[i].master_vol / 255.0f;\n\t\tpout->attenuation = channels[i].dist_mult * SND_CLIP_DISTANCE;\n\t\tpout->pitch = channels[i].basePitch;\n\t\tpout->channel = channels[i].entchannel;\n\t\tpout->wordIndex = channels[i].wordIndex;\n\t\tpout->samplePos = channels[i].pMixer.sample;\n\t\tpout->forcedEnd = channels[i].pMixer.forcedEndSample;\n\t\tpout->looping = looped;\n\n\t\tsounds_left--;\n\t\tpout++;\n\t}\n\n\treturn ( size - sounds_left );\n}\n\n/*\n===================\nS_InitAmbientChannels\n===================\n*/\nstatic void S_InitAmbientChannels( void )\n{\n\tint\tambient_channel;\n\tchannel_t\t*chan;\n\n\tfor( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ )\n\t{\n\t\tchan = &channels[ambient_channel];\n\n\t\tchan->staticsound = true;\n\t\tchan->use_loop = true;\n\t\tchan->entchannel = CHAN_STATIC;\n\t\tchan->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE);\n\t\tchan->basePitch = PITCH_NORM;\n\t}\n}\n\n/*\n===================\nS_UpdateAmbientSounds\n===================\n*/\nstatic void S_UpdateAmbientSounds( void )\n{\n\tmleaf_t\t*leaf;\n\tfloat\tvol;\n\tint\tambient_channel;\n\tchannel_t\t*chan;\n\n\tif( !snd_ambient ) return;\n\n\t// calc ambient sound levels\n\tif( !cl.worldmodel ) return;\n\n\tleaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes, cl.worldmodel );\n\n\tif( !leaf || !s_ambient_level.value )\n\t{\n\t\tfor( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ )\n\t\t\tchannels[ambient_channel].sfx = NULL;\n\t\treturn;\n\t}\n\n\tfor( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ )\n\t{\n\t\tchan = &channels[ambient_channel];\n\t\tchan->sfx = S_GetSfxByHandle( ambient_sfx[ambient_channel] );\n\n\t\t// ambient is unused\n\t\tif( !chan->sfx )\n\t\t{\n\t\t\tchan->rightvol = 0;\n\t\t\tchan->leftvol = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tvol = s_ambient_level.value * leaf->ambient_sound_level[ambient_channel];\n\t\tif( vol < 0 ) vol = 0;\n\n\t\t// don't adjust volume too fast\n\t\tif( chan->master_vol < vol )\n\t\t{\n\t\t\tchan->master_vol += s_listener.frametime * s_ambient_fade.value;\n\t\t\tif( chan->master_vol > vol ) chan->master_vol = vol;\n\t\t}\n\t\telse if( chan->master_vol > vol )\n\t\t{\n\t\t\tchan->master_vol -= s_listener.frametime * s_ambient_fade.value;\n\t\t\tif( chan->master_vol < vol ) chan->master_vol = vol;\n\t\t}\n\n\t\tchan->leftvol = chan->rightvol = chan->master_vol;\n\t}\n}\n\n/*\n=============================================================================\n\n\t\tSOUND STREAM RAW SAMPLES\n\n=============================================================================\n*/\n/*\n===================\nS_FindRawChannel\n===================\n*/\nrawchan_t *S_FindRawChannel( int entnum, qboolean create )\n{\n\tint\ti, free;\n\tint\tbest, best_time;\n\tsize_t\traw_samples = 0;\n\trawchan_t\t*ch;\n\n\tif( !sndpool ) return NULL; // sound is not active\n\n\tif( !entnum ) return NULL; // world is unused\n\n\t// check for replacement sound, or find the best one to replace\n\tbest_time = 0x7fffffff;\n\tbest = free = -1;\n\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\tch = raw_channels[i];\n\n\t\tif( free < 0 && !ch )\n\t\t{\n\t\t\tfree = i;\n\t\t}\n\t\telse if( ch )\n\t\t{\n\t\t\tint\ttime;\n\n\t\t\t// exact match\n\t\t\tif( ch->entnum == entnum )\n\t\t\t\treturn ch;\n\n\t\t\ttime = ch->s_rawend - paintedtime;\n\t\t\tif( time < best_time )\n\t\t\t{\n\t\t\t\tbest = i;\n\t\t\t\tbest_time = time;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !create ) return NULL;\n\n\tif( free >= 0 ) best = free;\n\tif( best < 0 ) return NULL; // no free slots\n\n\tif( !raw_channels[best] )\n\t{\n\t\traw_samples = MAX_RAW_SAMPLES;\n\t\traw_channels[best] = Mem_Calloc( sndpool, sizeof( *ch ) + sizeof( portable_samplepair_t ) * raw_samples );\n\t}\n\n\tch = raw_channels[best];\n\tch->max_samples = raw_samples;\n\tch->entnum = entnum;\n\tch->s_rawend = 0;\n\n\treturn ch;\n}\n\n/*\n===================\nS_RawSamplesStereo\n===================\n*/\nuint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data )\n{\n\tuint\tfracstep, samplefrac;\n\tuint\tsrc, dst;\n\n\tif( rawend < paintedtime )\n\t\trawend = paintedtime;\n\n\tfracstep = ((double) rate / (double)SOUND_DMA_SPEED) * (double)(1 << S_RAW_SAMPLES_PRECISION_BITS);\n\tsamplefrac = 0;\n\n\tif( width == 2 )\n\t{\n\t\tconst short *in = (const short *)data;\n\n\t\tif( channels == 2 )\n\t\t{\n\t\t\tfor( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))\n\t\t\t{\n\t\t\t\tdst = rawend++ & ( max_samples - 1 );\n\t\t\t\trawsamples[dst].left = in[src*2+0];\n\t\t\t\trawsamples[dst].right = in[src*2+1];\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))\n\t\t\t{\n\t\t\t\tdst = rawend++ & ( max_samples - 1 );\n\t\t\t\trawsamples[dst].left = in[src];\n\t\t\t\trawsamples[dst].right = in[src];\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( channels == 2 )\n\t\t{\n\t\t\tconst char *in = (const char *)data;\n\n\t\t\tfor( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))\n\t\t\t{\n\t\t\t\tdst = rawend++ & ( max_samples - 1 );\n\t\t\t\trawsamples[dst].left = in[src*2+0] << 8;\n\t\t\t\trawsamples[dst].right = in[src*2+1] << 8;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))\n\t\t\t{\n\t\t\t\tdst = rawend++ & ( max_samples - 1 );\n\t\t\t\trawsamples[dst].left = ( data[src] - 128 ) << 8;\n\t\t\t\trawsamples[dst].right = ( data[src] - 128 ) << 8;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn rawend;\n}\n\n/*\n===================\nS_RawEntSamples\n===================\n*/\nvoid S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol )\n{\n\trawchan_t\t*ch;\n\n\tif( snd_vol < 0 )\n\t\tsnd_vol = 0;\n\n\tif( !( ch = S_FindRawChannel( entnum, true )))\n\t\treturn;\n\n\tch->master_vol = snd_vol;\n\tch->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE);\n\tch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data );\n\tch->leftvol = ch->rightvol = snd_vol;\n}\n\n/*\n===================\nS_FreeIdleRawChannels\n\nFree raw channel that have been idling for too long.\n===================\n*/\nstatic void S_FreeIdleRawChannels( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\trawchan_t\t*ch = raw_channels[i];\n\n\t\tif( !ch ) continue;\n\n\t\tif( ch->s_rawend >= paintedtime )\n\t\t\tcontinue;\n\n\t\tif( ch->entnum > 0 )\n\t\t{\n\t\t\tSND_ForceCloseMouth( ch->entnum );\n\n\t\t\tif( ch->entnum <= MAX_CLIENTS )\n\t\t\t\tVoice_StopChannel( ch->entnum );\n\t\t}\n\n\t\tif(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC )\n\t\t{\n\t\t\traw_channels[i] = NULL;\n\t\t\tMem_Free( ch );\n\t\t}\n\t}\n}\n\n/*\n===================\nS_ClearRawChannels\n===================\n*/\nstatic void S_ClearRawChannels( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\trawchan_t\t*ch = raw_channels[i];\n\n\t\tif( !ch ) continue;\n\t\tch->s_rawend = 0;\n\t\tch->oldtime = -1;\n\t}\n}\n\n/*\n===================\nS_SpatializeRawChannels\n===================\n*/\nstatic void S_SpatializeRawChannels( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\trawchan_t\t*ch = raw_channels[i];\n\t\tvec3_t\tsource_vec;\n\t\tfloat\tdist, dot;\n\n\t\tif( !ch ) continue;\n\n\t\tif( ch->s_rawend < paintedtime )\n\t\t{\n\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// spatialization\n\t\tif( !S_IsClient( ch->entnum ) && ch->dist_mult && ch->entnum >= 0 && ch->entnum < GI->max_edicts )\n\t\t{\n\t\t\tif( !CL_GetMovieSpatialization( ch ))\n\t\t\t{\n\t\t\t\t// origin is null and entity not exist on client\n\t\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorSubtract( ch->origin, s_listener.origin, source_vec );\n\n\t\t\t\t// normalize source_vec and get distance from listener to source\n\t\t\t\tdist = VectorNormalizeLength( source_vec );\n\t\t\t\tdot = DotProduct( s_listener.right, source_vec );\n\n\t\t\t\t// don't pan sounds with no attenuation\n\t\t\t\tif( ch->dist_mult <= 0.0f ) dot = 0.0f;\n\n\t\t\t\t// fill out channel volumes for single location\n\t\t\t\tS_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, 1.0f, dot, dist * ch->dist_mult );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tch->leftvol = ch->rightvol = ch->master_vol;\n\t\t}\n\t}\n}\n\n/*\n===================\nS_FreeRawChannels\n===================\n*/\nstatic void S_FreeRawChannels( void )\n{\n\tint\ti;\n\n\t// free raw samples\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\tif( raw_channels[i] )\n\t\t\tMem_Free( raw_channels[i] );\n\t}\n\n\tmemset( raw_channels, 0, sizeof( raw_channels ));\n}\n\n//=============================================================================\n\n/*\n==================\nS_ClearBuffer\n==================\n*/\nstatic void S_ClearBuffer( void )\n{\n\tS_ClearRawChannels();\n\n\tSNDDMA_BeginPainting ();\n\tif( dma.buffer ) memset( dma.buffer, 0, dma.samples * 2 );\n\tSNDDMA_Submit ();\n\n\tMIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true );\n}\n\n/*\n==================\nS_StopSound\n\nstop all sounds for entity on a channel.\n==================\n*/\nvoid GAME_EXPORT S_StopSound( int entnum, int channel, const char *soundname )\n{\n\tsfx_t\t*sfx;\n\n\tif( !dma.initialized ) return;\n\tsfx = S_FindName( soundname, NULL );\n\tS_AlterChannel( entnum, channel, sfx, 0, 0, SND_STOP );\n}\n\n/*\n==================\nS_StopAllSounds\n==================\n*/\nvoid S_StopAllSounds( qboolean ambient )\n{\n\tint\ti;\n\n\tif( !dma.initialized ) return;\n\ttotal_channels = MAX_DYNAMIC_CHANNELS;\t// no statics\n\n\tfor( i = 0; i < MAX_CHANNELS; i++ )\n\t{\n\t\tif( !channels[i].sfx ) continue;\n\t\tS_FreeChannel( &channels[i] );\n\t}\n\n\tDSP_ClearState();\n\n\t// clear all the channels\n\tmemset( channels, 0, sizeof( channels ));\n\n\t// restart the ambient sounds\n\tif( ambient ) S_InitAmbientChannels ();\n\n\tS_ClearBuffer ();\n\n\t// clear any remaining soundfade\n\tmemset( &soundfade, 0, sizeof( soundfade ));\n}\n\n/*\n==============\nS_GetSoundtime\n\nupdate global soundtime\n\n(was part of platform code)\n===============\n*/\nstatic int S_GetSoundtime( void )\n{\n\tstatic int buffers, oldsamplepos;\n\tint samplepos, fullsamples;\n\n\tfullsamples = dma.samples / 2;\n\n\t// it is possible to miscount buffers\n\t// if it has wrapped twice between\n\t// calls to S_Update.  Oh well.\n\tsamplepos = dma.samplepos;\n\n\tif( samplepos < oldsamplepos )\n\t{\n\t\tbuffers++; // buffer wrapped\n\n\t\tif( paintedtime > 0x40000000 )\n\t\t{\n\t\t\t// time to chop things off to avoid 32 bit limits\n\t\t\tbuffers     = 0;\n\t\t\tpaintedtime = fullsamples;\n\t\t\tS_StopAllSounds( true );\n\t\t}\n\t}\n\n\toldsamplepos = samplepos;\n\n\treturn ( buffers * fullsamples + samplepos / 2 );\n}\n\n//=============================================================================\nstatic void S_UpdateChannels( void )\n{\n\tuint\tendtime;\n\tint\tsamps;\n\n\tSNDDMA_BeginPainting();\n\n\tif( !dma.buffer ) return;\n\n\t// updates DMA time\n\tsoundtime = S_GetSoundtime();\n\n\t// soundtime - total samples that have been played out to hardware at dmaspeed\n\t// paintedtime - total samples that have been mixed at speed\n\t// endtime - target for samples in mixahead buffer at speed\n\tendtime = soundtime + s_mixahead.value * SOUND_DMA_SPEED;\n\tsamps = dma.samples >> 1;\n\n\tif((int)(endtime - soundtime) > samps )\n\t\tendtime = soundtime + samps;\n\n\tif(( endtime - paintedtime ) & 0x3 )\n\t{\n\t\t// the difference between endtime and painted time should align on\n\t\t// boundaries of 4 samples. this is important when upsampling from 11khz -> 44khz.\n\t\tendtime -= ( endtime - paintedtime ) & 0x3;\n\t}\n\n\tMIX_PaintChannels( endtime );\n\n\tSNDDMA_Submit();\n}\n\n/*\n=================\nS_ExtraUpdate\n\nDon't let sound skip if going slow\n=================\n*/\nvoid S_ExtraUpdate( void )\n{\n\tif( !dma.initialized ) return;\n\tS_UpdateChannels ();\n}\n\n/*\n============\nS_UpdateFrame\n\nupdate listener position\n============\n*/\nvoid S_UpdateFrame( struct ref_viewpass_s *rvp )\n{\n\tif( !FBitSet( rvp->flags, RF_DRAW_WORLD ) || FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW ))\n\t\treturn;\n\n\tVectorCopy( rvp->vieworigin, s_listener.origin );\n\tAngleVectors( rvp->viewangles, s_listener.forward, s_listener.right, s_listener.up );\n\ts_listener.entnum = rvp->viewentity; // can be camera entity too\n}\n\n/*\n============\nSND_UpdateSound\n\nCalled once each time through the main loop\n============\n*/\nvoid SND_UpdateSound( void )\n{\n\tint\t\ti, j, total;\n\tchannel_t\t\t*ch, *combine;\n\tcon_nprint_t\tinfo;\n\n\tif( !dma.initialized ) return;\n\n\t// if the loading plaque is up, clear everything\n\t// out to make sure we aren't looping a dirty\n\t// dma buffer while loading\n\t// update any client side sound fade\n\tS_UpdateSoundFade();\n\n\t// release raw-channels that no longer used more than 10 secs\n\tS_FreeIdleRawChannels();\n\n\ts_listener.frametime = (cl.time - cl.oldtime);\n\ts_listener.waterlevel = cl.local.waterlevel;\n\ts_listener.active = CL_IsInGame();\n\ts_listener.inmenu = cls.key_dest == key_menu;\n\ts_listener.paused = cl.paused;\n\n\t// update general area ambient sound sources\n\tS_UpdateAmbientSounds();\n\n\tcombine = NULL;\n\n\t// update spatialization for static and dynamic sounds\n\tfor( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ )\n\t{\n\t\tif( !ch->sfx ) continue;\n\t\tSND_Spatialize( ch ); // respatialize channel\n\n\t\tif( !ch->leftvol && !ch->rightvol )\n\t\t\tcontinue;\n\n\t\t// try to combine static sounds with a previous channel of the same\n\t\t// sound effect so we don't mix five torches every frame\n\t\t// g-cont: perfomance option, probably kill stereo effect in most cases\n\t\tif( i >= MAX_DYNAMIC_CHANNELS && s_combine_sounds.value )\n\t\t{\n\t\t\t// see if it can just use the last one\n\t\t\tif( combine && combine->sfx == ch->sfx )\n\t\t\t{\n\t\t\t\tcombine->leftvol += ch->leftvol;\n\t\t\t\tcombine->rightvol += ch->rightvol;\n\t\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// search for one\n\t\t\tcombine = channels + MAX_DYNAMIC_CHANNELS;\n\n\t\t\tfor( j = MAX_DYNAMIC_CHANNELS; j < i; j++, combine++ )\n\t\t\t{\n\t\t\t\tif( combine->sfx == ch->sfx )\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( j == total_channels )\n\t\t\t{\n\t\t\t\tcombine = NULL;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( combine != ch )\n\t\t\t\t{\n\t\t\t\t\tcombine->leftvol += ch->leftvol;\n\t\t\t\t\tcombine->rightvol += ch->rightvol;\n\t\t\t\t\tch->leftvol = ch->rightvol = 0;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tS_SpatializeRawChannels();\n\n\t// debugging output\n\tif( s_show.value != 0.0f )\n\t{\n\t\tinfo.color[0] = 1.0f;\n\t\tinfo.color[1] = 0.6f;\n\t\tinfo.color[2] = 0.0f;\n\t\tinfo.time_to_live = 0.5f;\n\n\t\tfor( i = 0, total = 1, ch = channels; i < MAX_CHANNELS; i++, ch++ )\n\t\t{\n\t\t\tif( ch->sfx && ( ch->leftvol || ch->rightvol ))\n\t\t\t{\n\t\t\t\tinfo.index = total;\n\t\t\t\tCon_NXPrintf( &info, \"chan %i, pos (%.f %.f %.f) ent %i, lv%3i rv%3i %s\\n\",\n\t\t\t\ti, ch->origin[0], ch->origin[1], ch->origin[2], ch->entnum, ch->leftvol, ch->rightvol, ch->sfx->name );\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t}\n\n\t\tVectorSet( info.color, 1.0f, 1.0f, 1.0f );\n\t\tinfo.index = 0;\n\n\t\tCon_NXPrintf( &info, \"room_type: %i (%s) ----(%i)---- painted: %i\\n\", idsp_room, Cvar_VariableString( \"dsp_coeff_table\" ), total - 1, paintedtime );\n\t}\n\n\tS_StreamBackgroundTrack ();\n\n\t// mix some sound\n\tS_UpdateChannels ();\n}\n\n/*\n===============================================================================\n\nconsole functions\n\n===============================================================================\n*/\nstatic void S_Play_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"play <soundfile>\\n\" );\n\t\treturn;\n\t}\n\n\tS_StartLocalSound( Cmd_Argv( 1 ), VOL_NORM, false );\n}\n\nstatic void S_Play2_f( void )\n{\n\tint\ti = 1;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"play2 <soundfile>\\n\" );\n\t\treturn;\n\t}\n\n\twhile( i < Cmd_Argc( ))\n\t{\n\t\tS_StartLocalSound( Cmd_Argv( i ), VOL_NORM, true );\n\t\ti++;\n\t}\n}\n\nstatic void S_PlayVol_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"playvol <soundfile volume>\\n\" );\n\t\treturn;\n\t}\n\n\tS_StartLocalSound( Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), false );\n}\n\nstatic void S_Say( const char *name, qboolean reliable )\n{\n\tchar sentence[1024];\n\n\t// predefined vox sentence\n\tif( name[0] == '!' )\n\t{\n\t\tS_StartLocalSound( name, 1.0f, reliable );\n\t\treturn;\n\t}\n\n\tQ_snprintf( sentence, sizeof( sentence ), \"!#%s\", name );\n\tS_StartLocalSound( sentence, 1.0f, reliable );\n}\n\nstatic void S_Say_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"speak <vox sentence>\\n\" );\n\t\treturn;\n\t}\n\n\tS_Say( Cmd_Argv( 1 ), false );\n}\n\nstatic void S_SayReliable_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"spk <vox sentence>\\n\" );\n\t\treturn;\n\t}\n\n\tS_Say( Cmd_Argv( 1 ), true );\n}\n\n/*\n=================\nS_Music_f\n=================\n*/\nstatic void S_Music_f( void )\n{\n\tint\tc = Cmd_Argc();\n\n\t// run background track\n\tif( c == 1 )\n\t{\n\t\t// blank name stopped last track\n\t\tS_StopBackgroundTrack();\n\t}\n\telse if( c == 2 )\n\t{\n\t\tstring\tintro, main, track;\n\t\tconst char\t*ext[] = { \"mp3\", \"wav\" };\n\t\tint\ti;\n\n\t\tQ_strncpy( track, Cmd_Argv( 1 ), sizeof( track ));\n\t\tQ_snprintf( intro, sizeof( intro ), \"%s_intro\", Cmd_Argv( 1 ));\n\t\tQ_snprintf( main, sizeof( main ), \"%s_main\", Cmd_Argv( 1 ));\n\n\t\tfor( i = 0; i < 2; i++ )\n\t\t{\n\t\t\tchar intro_path[MAX_VA_STRING];\n\t\t\tchar main_path[MAX_VA_STRING];\n\t\t\tchar track_path[MAX_VA_STRING];\n\n\t\t\tQ_snprintf( intro_path, sizeof( intro_path ), \"media/%s.%s\", intro, ext[i] );\n\t\t\tQ_snprintf( main_path, sizeof( main_path ), \"media/%s.%s\", main, ext[i] );\n\n\t\t\tif( FS_FileExists( intro_path, false ) && FS_FileExists( main_path, false ))\n\t\t\t{\n\t\t\t\t// combined track with introduction and main loop theme\n\t\t\t\tS_StartBackgroundTrack( intro, main, 0, false );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tQ_snprintf( track_path, sizeof( track_path ), \"media/%s.%s\", track, ext[i] );\n\n\t\t\tif( FS_FileExists( track_path, false ))\n\t\t\t{\n\t\t\t\t// single non-looped theme\n\t\t\t\tS_StartBackgroundTrack( track, NULL, 0, false );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t}\n\telse if( c == 3 )\n\t{\n\t\tS_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), 0, false );\n\t}\n\telse if( c == 4 && Q_atoi( Cmd_Argv( 3 )) != 0 )\n\t{\n\t\t// restore command for singleplayer: all arguments are valid\n\t\tS_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), Q_atoi( Cmd_Argv( 3 )), false );\n\t}\n\telse Con_Printf( S_USAGE \"music <musicfile> [loopfile]\\n\" );\n}\n\n/*\n=================\nS_StopSound_f\n=================\n*/\nstatic void S_StopSound_f( void )\n{\n\tS_StopAllSounds( true );\n}\n\n/*\n=================\nS_SoundFade_f\n=================\n*/\nstatic void S_SoundFade_f( void )\n{\n\tint\tc = Cmd_Argc();\n\tfloat\tfadeTime = 5.0f;\n\n\tif( c == 2 )\n\t\tfadeTime = bound( 1.0f, atof( Cmd_Argv( 1 )), 60.0f );\n\n\tS_FadeClientVolume( 100.0f, fadeTime, 1.0f, 0.0f );\n\tsnd_fade_sequence = true;\n}\n\n/*\n=================\nS_SoundInfo_f\n=================\n*/\nvoid S_SoundInfo_f( void )\n{\n\tCon_Printf( \"Audio backend: %s\\n\", dma.backendName );\n\tCon_Printf( \"%5d channel(s)\\n\", 2 );\n\tCon_Printf( \"%5d samples\\n\", dma.samples );\n\tCon_Printf( \"%5d bits/sample\\n\", 16 );\n\tCon_Printf( \"%5d bytes/sec\\n\", SOUND_DMA_SPEED );\n\tCon_Printf( \"%5d total_channels\\n\", total_channels );\n\n\tS_PrintBackgroundTrackState ();\n}\n\n/*\n=================\nS_VoiceRecordStart_f\n=================\n*/\nstatic void S_VoiceRecordStart_f( void )\n{\n\tif( cls.state != ca_active )\n\t\treturn;\n\n\tVoice_RecordStart();\n}\n\n/*\n=================\nS_VoiceRecordStop_f\n=================\n*/\nstatic void S_VoiceRecordStop_f( void )\n{\n\tif( cls.state != ca_active || !Voice_IsRecording() )\n\t\treturn;\n\n\tCL_AddVoiceToDatagram();\n\tVoice_RecordStop();\n}\n\n/*\n================\nS_Init\n================\n*/\nqboolean S_Init( void )\n{\n\tCvar_RegisterVariable( &s_volume );\n\tCvar_RegisterVariable( &s_musicvolume );\n\tCvar_RegisterVariable( &s_mixahead );\n\tCvar_RegisterVariable( &s_show );\n\tCvar_RegisterVariable( &s_lerping );\n\tCvar_RegisterVariable( &s_ambient_level );\n\tCvar_RegisterVariable( &s_ambient_fade );\n\tCvar_RegisterVariable( &s_combine_sounds );\n\tCvar_RegisterVariable( &snd_mute_losefocus );\n\tCvar_RegisterVariable( &s_test );\n\tCvar_RegisterVariable( &s_samplecount );\n\tCvar_RegisterVariable( &s_warn_late_precache );\n\n\tif( Sys_CheckParm( \"-nosound\" ))\n\t{\n\t\tCon_Printf( \"Audio: Disabled\\n\" );\n\t\treturn false;\n\t}\n\n\tCmd_AddCommand( \"play\", S_Play_f, \"playing a specified sound file\" );\n\tCmd_AddCommand( \"play2\", S_Play2_f, \"playing a group of specified sound files\" ); // nehahra stuff\n\tCmd_AddCommand( \"playvol\", S_PlayVol_f, \"playing a specified sound file with specified volume\" );\n\tCmd_AddCommand( \"stopsound\", S_StopSound_f, \"stop all sounds\" );\n\t// HLU SDK have command with the same name\n\tCmd_AddCommandWithFlags( \"music\", S_Music_f, \"starting a background track\", CMD_OVERRIDABLE );\n\tCmd_AddCommand( \"soundlist\", S_SoundList_f, \"display loaded sounds\" );\n\tCmd_AddCommand( \"s_info\", S_SoundInfo_f, \"print sound system information\" );\n\tCmd_AddCommand( \"s_fade\", S_SoundFade_f, \"fade all sounds then stop all\" );\n\tCmd_AddCommand( \"+voicerecord\", S_VoiceRecordStart_f, \"start voice recording\" );\n\tCmd_AddCommand( \"-voicerecord\", S_VoiceRecordStop_f, \"stop voice recording\" );\n\tCmd_AddCommand( \"spk\", S_SayReliable_f, \"reliable play a specified sententce\" );\n\tCmd_AddCommand( \"speak\", S_Say_f, \"playing a specified sententce\" );\n\n\tsndpool = Mem_AllocPool( \"Sound Zone\" );\n\tdma.backendName = \"None\";\n\tif( !SNDDMA_Init( ))\n\t{\n\t\tCon_Printf( \"Audio: sound system can't be initialized\\n\" );\n\t\tMem_FreePool( &sndpool );\n\t\treturn false;\n\t}\n\n\tsoundtime = 0;\n\tpaintedtime = 0;\n\n\t// clear ambient sounds\n\tmemset( ambient_sfx, 0, sizeof( ambient_sfx ));\n\n\tMIX_InitAllPaintbuffers ();\n\tSX_Init ();\n\tS_InitScaletable ();\n\tS_StopAllSounds ( true );\n\tS_InitSounds ();\n\tVOX_Init ();\n\n\treturn true;\n}\n\n// =======================================================================\n// Shutdown sound engine\n// =======================================================================\nvoid S_Shutdown( void )\n{\n\tif( !dma.initialized ) return;\n\n\tCmd_RemoveCommand( \"play\" );\n\tCmd_RemoveCommand( \"playvol\" );\n\tCmd_RemoveCommand( \"stopsound\" );\n\tif( Cmd_Exists( \"music\" ))\n\t\tCmd_RemoveCommand( \"music\" );\n\tCmd_RemoveCommand( \"soundlist\" );\n\tCmd_RemoveCommand( \"s_info\" );\n\tCmd_RemoveCommand( \"s_fade\" );\n\tCmd_RemoveCommand( \"+voicerecord\" );\n\tCmd_RemoveCommand( \"-voicerecord\" );\n\tCmd_RemoveCommand( \"speak\" );\n\tCmd_RemoveCommand( \"spk\" );\n\n\tS_StopAllSounds (false);\n\tS_FreeRawChannels ();\n\tS_FreeSounds ();\n\tVOX_Shutdown ();\n\tSX_Free ();\n\n\tSNDDMA_Shutdown ();\n\tMIX_FreeAllPaintbuffers ();\n\tMem_FreePool( &sndpool );\n}\n"
  },
  {
    "path": "engine/client/s_mix.c",
    "content": "/*\ns_mix.c - portable code to mix sounds\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n#include \"client.h\"\n\nenum\n{\n\tIPAINTBUFFER = 0,\n\tIROOMBUFFER,\n\tISTREAMBUFFER,\n\tIVOICEBUFFER,\n\tCPAINTBUFFERS,\n};\n\nenum\n{\n\tFILTERTYPE_NONE = 0,\n\tFILTERTYPE_LINEAR,\n\tFILTERTYPE_CUBIC,\n};\n\n#define CCHANVOLUMES\t2\n\n#define SND_SCALE_BITS   7\n#define SND_SCALE_SHIFT  ( 8 - SND_SCALE_BITS )\n#define SND_SCALE_LEVELS ( 1 << SND_SCALE_BITS )\n\n// sound mixing buffer\n#define CPAINTFILTERMEM 3\n#define CPAINTFILTERS   4 // maximum number of consecutive upsample passes per paintbuffer\n\n// fixed point stuff for real-time resampling\n#define FIX_BITS             28\n#define FIX_SCALE\t\t\t ( 1 << FIX_BITS )\n#define FIX_MASK             (( 1 << FIX_BITS ) - 1 )\n#define FIX_FLOAT( a )       ((int)(( a ) * FIX_SCALE ))\n#define FIX( a )             (((int)( a )) << FIX_BITS )\n#define FIX_INTPART( a )     (((int)( a )) >> FIX_BITS )\n#define FIX_FRACPART( a )    (( a ) & FIX_MASK )\n\ntypedef struct\n{\n\tqboolean               factive; // if true, mix to this paintbuffer using flags\n\tportable_samplepair_t *pbuf;    // front stereo mix buffer, for 2 or 4 channel mixing\n\tint                    ifilter; // current filter memory buffer to use for upsampling pass\n\tportable_samplepair_t  fltmem[CPAINTFILTERS][CPAINTFILTERMEM];\n} paintbuffer_t;\n\nstatic portable_samplepair_t *g_curpaintbuffer;\nstatic portable_samplepair_t  streambuffer[(PAINTBUFFER_SIZE+1)];\nstatic portable_samplepair_t  paintbuffer[(PAINTBUFFER_SIZE+1)];\nstatic portable_samplepair_t  roombuffer[(PAINTBUFFER_SIZE+1)];\nstatic portable_samplepair_t  voicebuffer[(PAINTBUFFER_SIZE+1)];\nstatic portable_samplepair_t  temppaintbuffer[(PAINTBUFFER_SIZE+1)];\nstatic paintbuffer_t          paintbuffers[CPAINTBUFFERS];\n\nstatic int snd_scaletable[SND_SCALE_LEVELS][256];\nvoid S_InitScaletable( void )\n{\n\tint\ti, j;\n\n\tfor( i = 0; i < SND_SCALE_LEVELS; i++ )\n\t{\n\t\tfor( j = 0; j < 256; j++ )\n\t\t\tsnd_scaletable[i][j] = ((signed char)j) * i * (1<<SND_SCALE_SHIFT);\n\t}\n}\n\n/*\n===================\nS_TransferPaintBuffer\n\n===================\n*/\nstatic void S_TransferPaintBuffer( int endtime )\n{\n\tint\t*snd_p, snd_linear_count;\n\tint\tlpos, lpaintedtime;\n\tint\ti, val, sampleMask;\n\tshort\t*snd_out;\n\tdword\t*pbuf;\n\n\tpbuf = (dword *)dma.buffer;\n\tsnd_p = (int *)g_curpaintbuffer;\n\tlpaintedtime = paintedtime;\n\tsampleMask = ((dma.samples >> 1) - 1);\n\n\twhile( lpaintedtime < endtime )\n\t{\n\t\t// handle recirculating buffer issues\n\t\tlpos = lpaintedtime & sampleMask;\n\n\t\tsnd_out = (short *)pbuf + (lpos << 1);\n\n\t\tsnd_linear_count = (dma.samples>>1) - lpos;\n\t\tif( lpaintedtime + snd_linear_count > endtime )\n\t\t\tsnd_linear_count = endtime - lpaintedtime;\n\n\t\tsnd_linear_count <<= 1;\n\n\t\t// write a linear blast of samples\n\t\tfor( i = 0; i < snd_linear_count; i += 2 )\n\t\t{\n\t\t\tval = (snd_p[i+0] * 256) >> 8;\n\n\t\t\tif( val > 0x7fff ) snd_out[i+0] = 0x7fff;\n\t\t\telse if( val < (short)0x8000 )\n\t\t\t\tsnd_out[i+0] = (short)0x8000;\n\t\t\telse snd_out[i+0] = val;\n\n\t\t\tval = (snd_p[i+1] * 256) >> 8;\n\t\t\tif( val > 0x7fff ) snd_out[i+1] = 0x7fff;\n\t\t\telse if( val < (short)0x8000 )\n\t\t\t\tsnd_out[i+1] = (short)0x8000;\n\t\t\telse snd_out[i+1] = val;\n\t\t}\n\n\t\tsnd_p += snd_linear_count;\n\t\tlpaintedtime += (snd_linear_count >> 1);\n\t}\n}\n\n//===============================================================================\n// Mix buffer (paintbuffer) management routines\n//===============================================================================\n// Activate a paintbuffer.  All active paintbuffers are mixed in parallel within\n// MIX_MixChannelsToPaintbuffer, according to flags\nstatic void MIX_ActivatePaintbuffer( int ipaintbuffer )\n{\n\tAssert( ipaintbuffer < CPAINTBUFFERS );\n\tpaintbuffers[ipaintbuffer].factive = true;\n}\n\nstatic void MIX_SetCurrentPaintbuffer( int ipaintbuffer )\n{\n\tAssert( ipaintbuffer < CPAINTBUFFERS );\n\tg_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf;\n\tAssert( g_curpaintbuffer != NULL );\n}\n\nstatic int MIX_GetCurrentPaintbufferIndex( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < CPAINTBUFFERS; i++ )\n\t{\n\t\tif( g_curpaintbuffer == paintbuffers[i].pbuf )\n\t\t\treturn i;\n\t}\n\treturn 0;\n}\n\nstatic paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void )\n{\n\tint\tipaint = MIX_GetCurrentPaintbufferIndex();\n\n\tAssert( ipaint < CPAINTBUFFERS );\n\treturn &paintbuffers[ipaint];\n}\n\n// Don't mix into any paintbuffers\nstatic void MIX_DeactivateAllPaintbuffers( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < CPAINTBUFFERS; i++ )\n\t\tpaintbuffers[i].factive = false;\n}\n\n// set upsampling filter indexes back to 0\nstatic void MIX_ResetPaintbufferFilterCounters( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < CPAINTBUFFERS; i++ )\n\t\tpaintbuffers[i].ifilter = FILTERTYPE_NONE;\n}\n\n// return pointer to front paintbuffer pbuf, given index\nstatic portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer )\n{\n\tAssert( ipaintbuffer < CPAINTBUFFERS );\n\treturn paintbuffers[ipaintbuffer].pbuf;\n}\n\nstatic paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint )\n{\n\tAssert( ipaint < CPAINTBUFFERS );\n\treturn &paintbuffers[ipaint];\n}\n\nvoid MIX_FreeAllPaintbuffers( void )\n{\n\t// clear paintbuffer structs\n\tmemset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t ));\n}\n\n// Initialize paintbuffers array, set current paint buffer to main output buffer IPAINTBUFFER\nvoid MIX_InitAllPaintbuffers( void )\n{\n\t// clear paintbuffer structs\n\tmemset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t ));\n\n\tpaintbuffers[IPAINTBUFFER].pbuf = paintbuffer;\n\tpaintbuffers[IROOMBUFFER].pbuf = roombuffer;\n\tpaintbuffers[ISTREAMBUFFER].pbuf = streambuffer;\n\tpaintbuffers[IVOICEBUFFER].pbuf = voicebuffer;\n\n\tMIX_SetCurrentPaintbuffer( IPAINTBUFFER );\n}\n\n/*\n===============================================================================\n\nCHANNEL MIXING\n\n===============================================================================\n*/\nstatic void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount )\n{\n\tint\t*lscale, *rscale;\n\tint \ti, data;\n\n\tlscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];\n\trscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tdata = pData[i];\n\t\tpbuf[i].left += lscale[data];\n\t\tpbuf[i].right += rscale[data];\n\t}\n}\n\nstatic void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount )\n{\n\tint\t*lscale, *rscale;\n\tuint\tleft, right;\n\tword\t*data;\n\tint\ti;\n\n\tlscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];\n\trscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];\n\tdata = (word *)pData;\n\n\tfor( i = 0; i < outCount; i++, data++ )\n\t{\n\t\tleft = (byte)((*data & 0x00FF));\n\t\tright = (byte)((*data & 0xFF00) >> 8);\n\t\tpbuf[i].left += lscale[left];\n\t\tpbuf[i].right += rscale[right];\n\t}\n}\n\nstatic void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount )\n{\n\tint\tleft, right;\n\tint\ti, data;\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tdata = pData[i];\n\t\tleft = ( data * volume[0]) >> 8;\n\t\tright = (data * volume[1]) >> 8;\n\t\tpbuf[i].left += left;\n\t\tpbuf[i].right += right;\n\t}\n}\n\nstatic void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount )\n{\n\tuint\t*data;\n\tint\tleft, right;\n\tint\ti;\n\n\tdata = (uint *)pData;\n\n\tfor( i = 0; i < outCount; i++, data++ )\n\t{\n\t\tleft = (signed short)((*data & 0x0000FFFF));\n\t\tright = (signed short)((*data & 0xFFFF0000) >> 16);\n\n\t\tleft =  (left * volume[0]) >> 8;\n\t\tright = (right * volume[1]) >> 8;\n\n\t\tpbuf[i].left += left;\n\t\tpbuf[i].right += right;\n\t}\n}\n\nstatic void S_Mix8MonoTimeCompress( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress )\n{\n}\n\nstatic void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress )\n{\n\tint\ti, sampleIndex = 0;\n\tuint\tsampleFrac = inputOffset;\n\tint\t*lscale, *rscale;\n\n\tif( timecompress != 0 )\n\t{\n\t\tS_Mix8MonoTimeCompress( pbuf, volume, pData, inputOffset, rateScale, outCount, timecompress );\n//\t\treturn;\n\t}\n\n\t// Not using pitch shift?\n\tif( rateScale == FIX( 1 ))\n\t{\n\t\tS_PaintMonoFrom8( pbuf, volume, pData, outCount );\n\t\treturn;\n\t}\n\n\tlscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];\n\trscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tpbuf[i].left += lscale[pData[sampleIndex]];\n\t\tpbuf[i].right += rscale[pData[sampleIndex]];\n\t\tsampleFrac += rateScale;\n\t\tsampleIndex += FIX_INTPART( sampleFrac );\n\t\tsampleFrac = FIX_FRACPART( sampleFrac );\n\t}\n}\n\nstatic void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount )\n{\n\tint\ti, sampleIndex = 0;\n\tuint\tsampleFrac = inputOffset;\n\tint\t*lscale, *rscale;\n\n\t// Not using pitch shift?\n\tif( rateScale == FIX( 1 ))\n\t{\n\t\tS_PaintStereoFrom8( pbuf, volume, pData, outCount );\n\t\treturn;\n\t}\n\n\tlscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];\n\trscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tpbuf[i].left += lscale[pData[sampleIndex+0]];\n\t\tpbuf[i].right += rscale[pData[sampleIndex+1]];\n\t\tsampleFrac += rateScale;\n\t\tsampleIndex += FIX_INTPART( sampleFrac )<<1;\n\t\tsampleFrac = FIX_FRACPART( sampleFrac );\n\t}\n}\n\nstatic void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount )\n{\n\tint\ti, sampleIndex = 0;\n\tuint\tsampleFrac = inputOffset;\n\n\t// Not using pitch shift?\n\tif( rateScale == FIX( 1 ))\n\t{\n\t\tS_PaintMonoFrom16( pbuf, volume, pData, outCount );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tpbuf[i].left += (volume[0] * (int)( pData[sampleIndex] ))>>8;\n\t\tpbuf[i].right += (volume[1] * (int)( pData[sampleIndex] ))>>8;\n\t\tsampleFrac += rateScale;\n\t\tsampleIndex += FIX_INTPART( sampleFrac );\n\t\tsampleFrac = FIX_FRACPART( sampleFrac );\n\t}\n}\n\nstatic void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount )\n{\n\tint\ti, sampleIndex = 0;\n\tuint\tsampleFrac = inputOffset;\n\n\t// Not using pitch shift?\n\tif( rateScale == FIX( 1 ))\n\t{\n\t\tS_PaintStereoFrom16( pbuf, volume, pData, outCount );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < outCount; i++ )\n\t{\n\t\tpbuf[i].left += (volume[0] * (int)( pData[sampleIndex+0] ))>>8;\n\t\tpbuf[i].right += (volume[1] * (int)( pData[sampleIndex+1] ))>>8;\n\t\tsampleFrac += rateScale;\n\t\tsampleIndex += FIX_INTPART(sampleFrac)<<1;\n\t\tsampleFrac = FIX_FRACPART(sampleFrac);\n\t}\n}\n\nstatic void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount, int timecompress )\n{\n\tint\t\t\tpvol[CCHANVOLUMES];\n\tpaintbuffer_t\t\t*ppaint = MIX_GetCurrentPaintbufferPtr();\n\twavdata_t\t\t\t*pSource = pChannel->sfx->cache;\n\tportable_samplepair_t\t*pbuf;\n\n\tAssert( pSource != NULL );\n\n\tpvol[0] = bound( 0, pChannel->leftvol, 255 );\n\tpvol[1] = bound( 0, pChannel->rightvol, 255 );\n\tpbuf = ppaint->pbuf + outputOffset;\n\n\tif( pSource->channels == 1 )\n\t{\n\t\tif( pSource->width == 1 )\n\t\t\tS_Mix8Mono( pbuf, pvol, pData, inputOffset, fracRate, outCount, timecompress );\n\t\telse S_Mix16Mono( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount );\n\t}\n\telse\n\t{\n\t\tif( pSource->width == 1 )\n\t\t\tS_Mix8Stereo( pbuf, pvol, pData, inputOffset, fracRate, outCount );\n\t\telse S_Mix16Stereo( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount );\n\t}\n}\n\nint S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outRate, int outOffset, int timeCompress )\n{\n\t// save this to compute total output\n\tint\tstartingOffset = outOffset;\n\tfloat\tinputRate = ( pChannel->pitch * pChannel->sfx->cache->rate );\n\tfloat\trate = inputRate / outRate;\n\n\t// shouldn't be playing this if finished, but return if we are\n\tif( pChannel->pMixer.finished )\n\t\treturn 0;\n\n\t// If we are terminating this wave prematurely, then make sure we detect the limit\n\tif( pChannel->pMixer.forcedEndSample )\n\t{\n\t\t// how many total input samples will we need?\n\t\tint\tsamplesRequired = (int)(sampleCount * rate);\n\n\t\t// will this hit the end?\n\t\tif( pChannel->pMixer.sample + samplesRequired >= pChannel->pMixer.forcedEndSample )\n\t\t{\n\t\t\t// yes, mark finished and truncate the sample request\n\t\t\tpChannel->pMixer.finished = true;\n\t\t\tsampleCount = (int)((pChannel->pMixer.forcedEndSample - pChannel->pMixer.sample) / rate );\n\t\t}\n\t}\n\n\twhile( sampleCount > 0 )\n\t{\n\t\tint\tavailableSamples, outSampleCount;\n\t\twavdata_t\t*pSource = pChannel->sfx->cache;\n\t\tqboolean\tuse_loop = pChannel->use_loop;\n\t\tvoid\t*pData = NULL;\n\t\tdouble\tsampleFrac;\n\t\tint\ti, j;\n\n\t\t// compute number of input samples required\n\t\tdouble\tend = pChannel->pMixer.sample + rate * sampleCount;\n\t\tint\tinputSampleCount = (int)(ceil( end ) - floor( pChannel->pMixer.sample ));\n\n\t\tavailableSamples = S_GetOutputData( pSource, &pData, pChannel->pMixer.sample, inputSampleCount, use_loop );\n\n\t\t// none available, bail out\n\t\tif( !availableSamples ) break;\n\n\t\tsampleFrac = pChannel->pMixer.sample - floor( pChannel->pMixer.sample );\n\n\t\tif( availableSamples < inputSampleCount )\n\t\t{\n\t\t\t// how many samples are there given the number of input samples and the rate.\n\t\t\toutSampleCount = (int)ceil(( availableSamples - sampleFrac ) / rate );\n\t\t}\n\t\telse\n\t\t{\n\t\t\toutSampleCount = sampleCount;\n\t\t}\n\n\t\t// Verify that we won't get a buffer overrun.\n\t\tAssert( floor( sampleFrac + rate * ( outSampleCount - 1 )) <= availableSamples );\n\n\t\t// save current paintbuffer\n\t\tj = MIX_GetCurrentPaintbufferIndex();\n\n\t\tfor( i = 0; i < CPAINTBUFFERS; i++ )\n\t\t{\n\t\t\tif( !paintbuffers[i].factive )\n\t\t\t\tcontinue;\n\n\t\t\t// mix chan into all active paintbuffers\n\t\t\tMIX_SetCurrentPaintbuffer( i );\n\n\t\t\tS_MixChannel( pChannel, pData, outOffset, FIX_FLOAT( sampleFrac ), FIX_FLOAT( rate ), outSampleCount, timeCompress );\n\t\t}\n\n\t\tMIX_SetCurrentPaintbuffer( j );\n\n\t\tpChannel->pMixer.sample += outSampleCount * rate;\n\t\toutOffset += outSampleCount;\n\t\tsampleCount -= outSampleCount;\n\t}\n\n\t// Did we run out of samples? if so, mark finished\n\tif( sampleCount > 0 )\n\t{\n\t\tpChannel->pMixer.finished = true;\n\t}\n\n\t// total number of samples mixed !!! at the output clock rate !!!\n\treturn outOffset - startingOffset;\n}\n\nstatic qboolean S_ShouldContinueMixing( channel_t *ch )\n{\n\tif( ch->isSentence )\n\t{\n\t\tif( ch->currentWord )\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\treturn !ch->pMixer.finished;\n}\n\n// Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached.\n// endtime: time in 44khz samples to mix\n// rate: ignore samples which are not natively at this rate (for multipass mixing/filtering)\n// if rate == SOUND_ALL_RATES then mix all samples this pass\n// flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry'\n// outputRate: target mix rate for all samples.  Note, if outputRate = SOUND_DMA_SPEED, then\n// this routine will fill the paintbuffer to endtime.  Otherwise, fewer samples are mixed.\n// if( endtime - paintedtime ) is not aligned on boundaries of 4,\n// we'll miss data if outputRate < SOUND_DMA_SPEED!\nstatic void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate )\n{\n\tchannel_t *ch;\n\twavdata_t\t*pSource;\n\tint\ti, sampleCount;\n\tqboolean\tbZeroVolume;\n\tqboolean local = Host_IsLocalGame();\n\n\t// mix each channel into paintbuffer\n\tch = channels;\n\n\t// validate parameters\n\tAssert( outputRate <= SOUND_DMA_SPEED );\n\n\t// make sure we're not discarding data\n\tAssert( !(( endtime - paintedtime ) & 0x3 ) || ( outputRate == SOUND_DMA_SPEED ));\n\n\t// 44k: try to mix this many samples at outputRate\n\tsampleCount = ( endtime - paintedtime ) / ( SOUND_DMA_SPEED / outputRate );\n\n\tif( sampleCount <= 0 ) return;\n\n\tfor( i = 0; i < total_channels; i++, ch++ )\n\t{\n\t\tif( !ch->sfx ) continue;\n\n\t\t// NOTE: background map is allow both type sounds: menu and game\n\t\tif( !cl.background )\n\t\t{\n\t\t\tif( cls.key_dest == key_console && ch->localsound )\n\t\t\t{\n\t\t\t\t// play, playvol\n\t\t\t}\n\t\t\telse if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound && local )\n\t\t\t{\n\t\t\t\t// play only local sounds, keep pause for other\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if( !s_listener.inmenu && !s_listener.active && !ch->staticsound )\n\t\t\t{\n\t\t\t\t// play only ambient sounds, keep pause for other\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse if( cls.key_dest == key_console )\n\t\t\tcontinue;\t// silent mode in console\n\n\t\tpSource = S_LoadSound( ch->sfx );\n\n\t\t// Don't mix sound data for sounds with zero volume. If it's a non-looping sound,\n\t\t// just remove the sound when its volume goes to zero.\n\n\t\tbZeroVolume = !ch->leftvol && !ch->rightvol;\n\n\t\tif( !bZeroVolume )\n\t\t{\n\t\t\t// this values matched with GoldSrc\n\t\t\tif( ch->leftvol < 8 && ch->rightvol < 8 )\n\t\t\t\tbZeroVolume = true;\n\t\t}\n\n\t\tif( !pSource || ( bZeroVolume && !FBitSet( pSource->flags, SOUND_LOOPED )))\n\t\t{\n\t\t\tif( !pSource )\n\t\t\t{\n\t\t\t\tS_FreeChannel( ch );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\telse if( bZeroVolume )\n\t\t{\n\t\t\tcontinue;\n\t\t}\n\n\t\t// multipass mixing - only mix samples of specified sample rate\n\t\tswitch( rate )\n\t\t{\n\t\tcase SOUND_11k:\n\t\tcase SOUND_22k:\n\t\tcase SOUND_44k:\n\t\t\tif( rate != pSource->rate )\n\t\t\t\tcontinue;\n\t\t\tbreak;\n\t\tdefault:\tbreak;\n\t\t}\n\n\t\t// get playback pitch\n\t\tif( ch->isSentence )\n\t\t\tch->pitch = VOX_ModifyPitch( ch, ch->basePitch * 0.01f );\n\t\telse ch->pitch = ch->basePitch * 0.01f;\n\n\t\tch->pitch *= ( sys_timescale.value + 1 ) / 2;\n\n\t\tif( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM ))\n\t\t{\n\t\t\tif( pSource->width == 1 )\n\t\t\t\tSND_MoveMouth8( ch, pSource, sampleCount );\n\t\t\telse SND_MoveMouth16( ch, pSource, sampleCount );\n\t\t}\n\n\t\t// mix channel to all active paintbuffers.\n\t\t// NOTE: must be called once per channel only - consecutive calls retrieve additional data.\n\t\tif( ch->isSentence )\n\t\t\tVOX_MixDataToDevice( ch, sampleCount, outputRate, 0 );\n\t\telse S_MixDataToDevice( ch, sampleCount, outputRate, 0, 0 );\n\n\t\tif( !S_ShouldContinueMixing( ch ))\n\t\t{\n\t\t\tS_FreeChannel( ch );\n\t\t}\n\t}\n}\n\n// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer\nstatic portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem )\n{\n\t// The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples\n\tif( i == -1 ) return (&(pfiltermem[0]));\n\tif( i == 0 ) return (&(pfiltermem[1]));\n\tif( i == 1 ) return (&(pfiltermem[2]));\n\n\t// return from paintbuffer, where samples are doubled.\n\t// even samples are to be replaced with interpolated value.\n\treturn (&(pbuffer[(i-2) * 2 + 1]));\n}\n\n// pass forward over passed in buffer and cubic interpolate all odd samples\n// pbuffer: buffer to filter (in place)\n// prevfilter:  filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC\n// if NULL then perform no filtering.\n// count: how many samples to upsample. will become count*2 samples in buffer, in place.\n\nstatic void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count )\n{\n\n// implement cubic interpolation on 2x upsampled buffer.   Effectively delays buffer contents by 2 samples.\n// pbuffer: contains samples at 0, 2, 4, 6...\n// temppaintbuffer is temp buffer, same size as paintbuffer, used to store processed values\n// count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6...\n\n// finpos is the fractional, inpos the integer part.\n//\t\tfinpos = 0.5 for upsampling by 2x\n//\t\tinpos is the position of the sample\n\n//\t\txm1 = x [inpos - 1];\n//\t\tx0 = x [inpos + 0];\n//\t\tx1 = x [inpos + 1];\n//\t\tx2 = x [inpos + 2];\n//\t\ta = (3 * (x0-x1) - xm1 + x2) / 2;\n//\t\tb = 2*x1 + xm1 - (5*x0 + x2) / 2;\n//\t\tc = (x1 - xm1) / 2;\n//\t\ty [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0;\n\n\tint i;\n\tconst int upCount = Q_min( count << 1, PAINTBUFFER_SIZE );\n\tint a, b, c;\n\tint xm1, x0, x1, x2;\n\tportable_samplepair_t *psamp0;\n\tportable_samplepair_t *psamp1;\n\tportable_samplepair_t *psamp2;\n\tportable_samplepair_t *psamp3;\n\tint outpos = 0;\n\n\t// pfiltermem holds 6 samples from previous buffer pass\n\t// process 'count' samples\n\tfor( i = 0; i < count; i++)\n\t{\n\t\t// get source sample pointer\n\t\tpsamp0 = S_GetNextpFilter( i-1, pbuffer, pfiltermem );\n\t\tpsamp1 = S_GetNextpFilter( i+0, pbuffer, pfiltermem );\n\t\tpsamp2 = S_GetNextpFilter( i+1, pbuffer, pfiltermem );\n\t\tpsamp3 = S_GetNextpFilter( i+2, pbuffer, pfiltermem );\n\n\t\t// write out original sample to interpolation buffer\n\t\ttemppaintbuffer[outpos++] = *psamp1;\n\n\t\t// get all left samples for interpolation window\n\t\txm1 = psamp0->left;\n\t\tx0 = psamp1->left;\n\t\tx1 = psamp2->left;\n\t\tx2 = psamp3->left;\n\n\t\t// interpolate\n\t\ta = (3 * (x0-x1) - xm1 + x2) / 2;\n\t\tb = 2*x1 + xm1 - (5*x0 + x2) / 2;\n\t\tc = (x1 - xm1) / 2;\n\n\t\t// write out interpolated sample\n\t\ttemppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0;\n\n\t\t// get all right samples for window\n\t\txm1 = psamp0->right;\n\t\tx0 = psamp1->right;\n\t\tx1 = psamp2->right;\n\t\tx2 = psamp3->right;\n\n\t\t// interpolate\n\t\ta = (3 * (x0-x1) - xm1 + x2) / 2;\n\t\tb = 2*x1 + xm1 - (5*x0 + x2) / 2;\n\t\tc = (x1 - xm1) / 2;\n\n\t\t// write out interpolated sample, increment output counter\n\t\ttemppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0;\n\n\t\tif( outpos > ARRAYSIZE( temppaintbuffer ))\n\t\t\tbreak;\n\t}\n\n\tAssert( cfltmem >= 3 );\n\n\t// save last 3 samples from paintbuffer\n\tpfiltermem[0] = pbuffer[upCount - 5];\n\tpfiltermem[1] = pbuffer[upCount - 3];\n\tpfiltermem[2] = pbuffer[upCount - 1];\n\n\t// copy temppaintbuffer back into paintbuffer\n\tmemcpy( pbuffer, temppaintbuffer, sizeof( *pbuffer ) * upCount );\n}\n\n// pass forward over passed in buffer and linearly interpolate all odd samples\n// pbuffer: buffer to filter (in place)\n// prevfilter:  filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR\n// if NULL then perform no filtering.\n// count: how many samples to upsample. will become count*2 samples in buffer, in place.\nstatic void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count )\n{\n\tint\ti, upCount = count<<1;\n\n\tAssert( upCount <= PAINTBUFFER_SIZE );\n\tAssert( cfltmem >= 1 );\n\n\t// use interpolation value from previous mix\n\tpbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1;\n\tpbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1;\n\n\tfor( i = 2; i < upCount; i += 2 )\n\t{\n\t\t// use linear interpolation for upsampling\n\t\tpbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1;\n\t\tpbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1;\n\t}\n\n\t// save last value to be played out in buffer\n\t*pfiltermem = pbuffer[upCount - 1];\n}\n\n// upsample by 2x, optionally using interpolation\n// count: how many samples to upsample. will become count*2 samples in buffer, in place.\n// pbuffer: buffer to upsample into (in place)\n// pfiltermem:  filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR\n// if NULL then perform no filtering.\n// cfltmem: max number of sample pairs filter can use\n// filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc.  Must match prevfilter.\nstatic void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype )\n{\n\tint\tupCount = count<<1;\n\tint\ti, j;\n\n\t// reverse through buffer, duplicating contents for 'count' samples\n\tfor( i = upCount - 1, j = count - 1; j >= 0; i-=2, j-- )\n\t{\n\t\tpbuffer[i] = pbuffer[j];\n\t\tpbuffer[i-1] = pbuffer[j];\n\t}\n\n\t// pass forward through buffer, interpolate all even slots\n\tswitch( filtertype )\n\t{\n\tcase FILTERTYPE_LINEAR:\n\t\tS_Interpolate2xLinear( pbuffer, pfiltermem, cfltmem, count );\n\t\tbreak;\n\tcase FILTERTYPE_CUBIC:\n\t\tS_Interpolate2xCubic( pbuffer, pfiltermem, cfltmem, count );\n\t\tbreak;\n\tdefault:\t// no filter\n\t\tbreak;\n\t}\n}\n\n// zero out all paintbuffers\nvoid MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters )\n{\n\tint\tcount = Q_min( SampleCount, PAINTBUFFER_SIZE );\n\tint\ti;\n\n\t// zero out all paintbuffer data (ignore sampleCount)\n\tfor( i = 0; i < CPAINTBUFFERS; i++ )\n\t{\n\t\tif( paintbuffers[i].pbuf != NULL )\n\t\t\tmemset( paintbuffers[i].pbuf, 0, (count+1) * sizeof( portable_samplepair_t ));\n\n\t\tif( clearFilters )\n\t\t{\n\t\t\tmemset( paintbuffers[i].fltmem, 0, sizeof( paintbuffers[i].fltmem ));\n\t\t}\n\t}\n\n\tif( clearFilters )\n\t{\n\t\tMIX_ResetPaintbufferFilterCounters();\n\t}\n}\n\n// mixes pbuf1 + pbuf2 into pbuf3, count samples\n// fgain is output gain 0-1.0\n// NOTE: pbuf3 may equal pbuf1 or pbuf2!\nstatic void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain )\n{\n\tportable_samplepair_t\t*pbuf1, *pbuf2, *pbuf3;\n\tint\t\t\ti, gain;\n\n\tgain = 256 * fgain;\n\n\tAssert( count <= PAINTBUFFER_SIZE );\n\tAssert( ibuf1 < CPAINTBUFFERS );\n\tAssert( ibuf2 < CPAINTBUFFERS );\n\tAssert( ibuf3 < CPAINTBUFFERS );\n\n\tpbuf1 = paintbuffers[ibuf1].pbuf;\n\tpbuf2 = paintbuffers[ibuf2].pbuf;\n\tpbuf3 = paintbuffers[ibuf3].pbuf;\n\n\tif( !gain )\n\t{\n\t\t// do not mix buf2 into buf3, just copy\n\t\tif( pbuf1 != pbuf3 )\n\t\t\tmemcpy( pbuf3, pbuf1, sizeof( *pbuf1 ) * count );\n\t\treturn;\n\t}\n\n\t// destination buffer stereo - average n chans down to stereo\n\n\t// destination 2ch:\n\t// pb1 2ch + pb2 2ch\t\t-> pb3 2ch\n\t// pb1 2ch + pb2 (4ch->2ch)\t\t-> pb3 2ch\n\t// pb1 (4ch->2ch) + pb2 (4ch->2ch)\t-> pb3 2ch\n\n\t// mix front channels\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tpbuf3[i].left = pbuf1[i].left;\n\t\tpbuf3[i].right = pbuf1[i].right;\n\t\tpbuf3[i].left += (pbuf2[i].left * gain) >> 8;\n\t\tpbuf3[i].right += (pbuf2[i].right * gain) >> 8;\n\t}\n}\n\nstatic void MIX_CompressPaintbuffer( int ipaint, int count )\n{\n\tportable_samplepair_t\t*pbuf;\n\tpaintbuffer_t\t\t*ppaint;\n\tint\t\t\ti;\n\n\tppaint = MIX_GetPPaintFromIPaint( ipaint );\n\tpbuf = ppaint->pbuf;\n\n\tfor( i = 0; i < count; i++, pbuf++ )\n\t{\n\t\tpbuf->left = CLIP( pbuf->left );\n\t\tpbuf->right = CLIP( pbuf->right );\n\t}\n}\n\nstatic void S_MixUpsample( int sampleCount, int filtertype )\n{\n\tpaintbuffer_t\t*ppaint = MIX_GetCurrentPaintbufferPtr();\n\tint\t\tifilter = ppaint->ifilter;\n\n\tAssert( ifilter < CPAINTFILTERS );\n\n\tS_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );\n\n\t// make sure on next upsample pass for this paintbuffer, new filter memory is used\n\tppaint->ifilter++;\n}\n\nstatic void MIX_MixRawSamplesBuffer( int end )\n{\n\tportable_samplepair_t\t*pbuf, *roombuf, *streambuf, *voicebuf;\n\tuint i, j, stop;\n\n\troombuf = MIX_GetPFrontFromIPaint( IROOMBUFFER );\n\tstreambuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER );\n\tvoicebuf = MIX_GetPFrontFromIPaint( IVOICEBUFFER );\n\n\tif( s_listener.paused ) return;\n\n\t// paint in the raw channels\n\tfor( i = 0; i < MAX_RAW_CHANNELS; i++ )\n\t{\n\t\t// copy from the streaming sound source\n\t\trawchan_t *ch = raw_channels[i];\n\t\tqboolean stream;\n\t\tqboolean is_voice;\n\n\t\tif( !ch )\n\t\t\tcontinue;\n\n\t\t// not audible\n\t\tif( !ch->leftvol && !ch->rightvol )\n\t\t\tcontinue;\n\n\t\tis_voice = (ch->entnum > 0 && ch->entnum <= MAX_CLIENTS) || \n\t\t           (ch->entnum == VOICE_LOOPBACK_INDEX) ||\n\t\t           (ch->entnum == VOICE_LOCALCLIENT_INDEX);\n\n\t\tstream = ch->entnum == S_RAW_SOUND_BACKGROUNDTRACK || CL_IsPlayerIndex( ch->entnum );\n\t\t\n\t\tif( is_voice )\n\t\t\tpbuf = voicebuf;\n\t\telse if( stream )\n\t\t\tpbuf = streambuf;\n\t\telse\n\t\t\tpbuf = roombuf;\n\n\t\tstop = (end < ch->s_rawend) ? end : ch->s_rawend;\n\n\t\tfor( j = paintedtime; j < stop; j++ )\n\t\t{\n\t\t\tpbuf[j-paintedtime].left += ( ch->rawsamples[j & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8;\n\t\t\tpbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8;\n\t\t}\n\n\t\tif( ch->entnum > 0 )\n\t\t{\n\t\t\tint pos = paintedtime & ( ch->max_samples - 1 );\n\n\t\t\tSND_MoveMouthRaw( ch, &ch->rawsamples[pos], bound( 0, ch->max_samples - pos, stop - paintedtime ));\n\t\t}\n\t}\n}\n\n// upsample and mix sounds into final 44khz versions of:\n// IROOMBUFFER, IFACINGBUFFER, IFACINGAWAY\n// dsp fx are then applied to these buffers by the caller.\n// caller also remixes all into final IPAINTBUFFER output.\nstatic void MIX_UpsampleAllPaintbuffers( int end, int count )\n{\n\t// 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction\n\t// These buffers are facing, facingaway, room\n\t// These 3 mixed buffers are then each upsampled to 22khz.\n\n\t// 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction\n\t// These 3 mixed buffers are then each upsampled to 44khz.\n\n\t// 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction\n\n\tMIX_DeactivateAllPaintbuffers();\n\n\t// set paintbuffer upsample filter indices to 0\n\tMIX_ResetPaintbufferFilterCounters();\n\n\t// only mix to roombuffer if dsp fx are on KDB: perf\n\tMIX_ActivatePaintbuffer( IROOMBUFFER );\t// operates on MIX_MixChannelsToPaintbuffer\n\n\t// mix 11khz sounds:\n\tMIX_MixChannelsToPaintbuffer( end, SOUND_11k, SOUND_11k );\n\n#if SOUND_DMA_SPEED >= SOUND_22k\n\t// upsample all 11khz buffers by 2x\n\t// only upsample roombuffer if dsp fx are on KDB: perf\n\tMIX_SetCurrentPaintbuffer( IROOMBUFFER ); // operates on MixUpSample\n\tS_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_11k ), s_lerping.value );\n\n\t// mix 22khz sounds:\n\tMIX_MixChannelsToPaintbuffer( end, SOUND_22k, SOUND_22k );\n#endif\n\n#if SOUND_DMA_SPEED >= SOUND_44k\n\t// upsample all 22khz buffers by 2x\n\t// only upsample roombuffer if dsp fx are on KDB: perf\n\tMIX_SetCurrentPaintbuffer( IROOMBUFFER );\n\tS_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_22k ), s_lerping.value );\n\n\t// mix all 44khz sounds to all active paintbuffers\n\tMIX_MixChannelsToPaintbuffer( end, SOUND_44k, SOUND_DMA_SPEED );\n#endif\n\n\t// mix raw samples from the video streams\n\tMIX_MixRawSamplesBuffer( end );\n\n\tMIX_DeactivateAllPaintbuffers();\n\tMIX_SetCurrentPaintbuffer( IPAINTBUFFER );\n}\n\nvoid MIX_PaintChannels( int endtime )\n{\n\tint\tend, count;\n\n\tCheckNewDspPresets();\n\n\twhile( paintedtime < endtime )\n\t{\n\t\t// if paintbuffer is smaller than DMA buffer\n\t\tend = endtime;\n\t\tif( endtime - paintedtime > PAINTBUFFER_SIZE )\n\t\t\tend = paintedtime + PAINTBUFFER_SIZE;\n\n\t\t// number of 44khz samples to mix into paintbuffer, up to paintbuffer size\n\t\tcount = end - paintedtime;\n\n\t\t// clear the all mix buffers\n\t\tMIX_ClearAllPaintBuffers( count, false );\n\n\t\tMIX_UpsampleAllPaintbuffers( end, count );\n\n\t\t// process all sounds with DSP\n\t\tif( cls.key_dest != key_menu )\n\t\t\tDSP_Process( MIX_GetPFrontFromIPaint( IROOMBUFFER ), count );\n\n\t\t// add music or soundtrack from movie (no dsp)\n\t\tMIX_MixPaintbuffers( IPAINTBUFFER, IROOMBUFFER, IPAINTBUFFER, count, S_GetMasterVolume() );\n\n\t\t// add music or soundtrack from movie (no dsp)\n\t\tMIX_MixPaintbuffers( IPAINTBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, 1.0f );\n\n\t\t// voice chat\n\t\tMIX_MixPaintbuffers( IPAINTBUFFER, IVOICEBUFFER, IPAINTBUFFER, count, 1.0f );\n\n\t\t// clip all values > 16 bit down to 16 bit\n\t\tMIX_CompressPaintbuffer( IPAINTBUFFER, count );\n\n\t\t// transfer IPAINTBUFFER paintbuffer out to DMA buffer\n\t\tMIX_SetCurrentPaintbuffer( IPAINTBUFFER );\n\n\t\t// transfer out according to DMA format\n\t\tS_TransferPaintBuffer( end );\n\t\tpaintedtime = end;\n\t}\n}\n"
  },
  {
    "path": "engine/client/s_mouth.c",
    "content": "/*\ns_mouth.c - animate mouth\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n#include \"client.h\"\n#include \"const.h\"\n\n#define CAVGSAMPLES\t\t10\n\nvoid SND_InitMouth( int entnum, int entchannel )\n{\n\tif(( entchannel == CHAN_VOICE || entchannel == CHAN_STREAM ) && entnum > 0 )\n\t{\n\t\tSND_ForceInitMouth( entnum );\n\t}\n}\n\nvoid SND_CloseMouth( channel_t *ch )\n{\n\tif( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM )\n\t{\n\t\tSND_ForceCloseMouth( ch->entnum );\n\t}\n}\n\nvoid SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count )\n{\n\tcl_entity_t\t*clientEntity;\n\tsigned char\t\t*pdata = NULL;\n\tmouth_t\t\t*pMouth = NULL;\n\tint\t\tscount, pos = 0;\n\tint\t\tsavg, data;\n\tuint \t\ti;\n\n\tclientEntity = CL_GetEntityByIndex( ch->entnum );\n\tif( !clientEntity ) return;\n\n\tpMouth = &clientEntity->mouth;\n\n\tif( ch->isSentence )\n\t{\n\t\tif( ch->currentWord )\n\t\t\tpos = ch->currentWord->sample;\n\t}\n\telse pos = ch->pMixer.sample;\n\n\tcount = S_GetOutputData( pSource, (void**)&pdata, pos, count, ch->use_loop );\n\tif( pdata == NULL ) return;\n\n\ti = 0;\n\tscount = pMouth->sndcount;\n\tsavg = 0;\n\n\twhile( i < count && scount < CAVGSAMPLES )\n\t{\n\t\tdata = pdata[i];\n\t\tsavg += abs( data );\n\n\t\ti += 80 + ((byte)data & 0x1F);\n\t\tscount++;\n\t}\n\n\tpMouth->sndavg += savg;\n\tpMouth->sndcount = (byte)scount;\n\n\tif( pMouth->sndcount >= CAVGSAMPLES )\n\t{\n\t\tpMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES;\n\t\tpMouth->sndavg = 0;\n\t\tpMouth->sndcount = 0;\n\t}\n}\n\nvoid SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count )\n{\n\tcl_entity_t\t*clientEntity;\n\tshort\t\t*pdata = NULL;\n\tmouth_t\t\t*pMouth = NULL;\n\tint\t\tsavg, data;\n\tint\t\tscount, pos = 0;\n\tuint \t\ti;\n\n\tclientEntity = CL_GetEntityByIndex( ch->entnum );\n\tif( !clientEntity ) return;\n\n\tpMouth = &clientEntity->mouth;\n\n\tif( ch->isSentence )\n\t{\n\t\tif( ch->currentWord )\n\t\t\tpos = ch->currentWord->sample;\n\t}\n\telse pos = ch->pMixer.sample;\n\n\tcount = S_GetOutputData( pSource, (void**)&pdata, pos, count, ch->use_loop );\n\tif( pdata == NULL ) return;\n\n\ti = 0;\n\tscount = pMouth->sndcount;\n\tsavg = 0;\n\n\twhile( i < count && scount < CAVGSAMPLES )\n\t{\n\t\tdata = pdata[i];\n\t\tdata = (bound( -32767, data, 0x7ffe ) >> 8);\n\t\tsavg += abs( data );\n\n\t\ti += 80 + ((byte)data & 0x1F);\n\t\tscount++;\n\t}\n\n\tpMouth->sndavg += savg;\n\tpMouth->sndcount = (byte)scount;\n\n\tif( pMouth->sndcount >= CAVGSAMPLES )\n\t{\n\t\tpMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES;\n\t\tpMouth->sndavg = 0;\n\t\tpMouth->sndcount = 0;\n\t}\n}\n\nvoid SND_ForceInitMouth( int entnum )\n{\n\tcl_entity_t *clientEntity;\n\n\tclientEntity = CL_GetEntityByIndex( entnum );\n\n\tif( clientEntity )\n\t{\n\t\tclientEntity->mouth.mouthopen = 0;\n\t\tclientEntity->mouth.sndavg = 0;\n\t\tclientEntity->mouth.sndcount = 0;\n\t}\n}\n\nvoid SND_ForceCloseMouth( int entnum )\n{\n\tcl_entity_t *clientEntity;\n\n\tclientEntity = CL_GetEntityByIndex( entnum );\n\n\tif( clientEntity )\n\t\tclientEntity->mouth.mouthopen = 0;\n}\n\nvoid SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count )\n{\n\tcl_entity_t\t*clientEntity;\n\tmouth_t\t\t*pMouth = NULL;\n\tint\t\tsavg, data;\n\tint\t\tscount = 0;\n\tuint \t\ti;\n\n\tclientEntity = CL_GetEntityByIndex( ch->entnum );\n\tif( !clientEntity ) return;\n\n\tpMouth = &clientEntity->mouth;\n\n\tif( pData == NULL )\n\t\treturn;\n\n\ti = 0;\n\tscount = pMouth->sndcount;\n\tsavg = 0;\n\n\twhile ( i < count && scount < CAVGSAMPLES )\n\t{\n\t\tdata = pData[i].left; // mono sound anyway\n\t\tdata = ( bound( -32767, data, 0x7ffe ) >> 8 );\n\t\tsavg += abs( data );\n\n\t\ti += 80 + ( (byte)data & 0x1F );\n\t\tscount++;\n\t}\n\n\tpMouth->sndavg += savg;\n\tpMouth->sndcount = (byte)scount;\n\n\tif ( pMouth->sndcount >= CAVGSAMPLES )\n\t{\n\t\tpMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES;\n\t\tpMouth->sndavg = 0;\n\t\tpMouth->sndcount = 0;\n\t}\n}\n"
  },
  {
    "path": "engine/client/s_stream.c",
    "content": "/*\ns_stream.c - sound streaming\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n#include \"client.h\"\n#include \"soundlib.h\"\n\nstatic bg_track_t\t\ts_bgTrack;\nstatic musicfade_t\t\tmusicfade;\t// controlled by game dlls\n\n/*\n=================\nS_PrintBackgroundTrackState\n=================\n*/\nvoid S_PrintBackgroundTrackState( void )\n{\n\tCon_Printf( \"BackgroundTrack: \" );\n\n\tif( s_bgTrack.current[0] && s_bgTrack.loopName[0] )\n\t\tCon_Printf( \"intro %s, loop %s\\n\", s_bgTrack.current, s_bgTrack.loopName );\n\telse if( s_bgTrack.current[0] )\n\t\tCon_Printf( \"%s\\n\", s_bgTrack.current );\n\telse if( s_bgTrack.loopName[0] )\n\t\tCon_Printf( \"%s [loop]\\n\", s_bgTrack.loopName );\n\telse Con_Printf( \"not playing\\n\" );\n}\n\n/*\n=================\nS_FadeMusicVolume\n=================\n*/\nvoid S_FadeMusicVolume( float fadePercent )\n{\n\tmusicfade.percent = bound( 0.0f, fadePercent, 100.0f );\n}\n\n/*\n=================\nS_GetMusicVolume\n=================\n*/\nfloat S_GetMusicVolume( void )\n{\n\tfloat\tscale = 1.0f;\n\n\tif( host.status == HOST_NOFOCUS && snd_mute_losefocus.value != 0.0f )\n\t{\n\t\t// we return zero volume to keep sounds running\n\t\treturn 0.0f;\n\t}\n\n\tif( !s_listener.inmenu && musicfade.percent != 0 )\n\t{\n\t\tscale = bound( 0.0f, musicfade.percent / 100.0f, 1.0f );\n\t\tscale = 1.0f - scale;\n\t}\n\n\treturn s_musicvolume.value * scale;\n}\n\n/*\n=================\nS_StartBackgroundTrack\n=================\n*/\nvoid S_StartBackgroundTrack( const char *introTrack, const char *mainTrack, int position, qboolean fullpath )\n{\n\tS_StopBackgroundTrack();\n\n\tif( !dma.initialized ) return;\n\n\t// check for special symbols\n\tif( introTrack && *introTrack == '*' )\n\t\tintroTrack = NULL;\n\n\tif( mainTrack && *mainTrack == '*' )\n\t\tmainTrack = NULL;\n\n\tif( !COM_CheckString( introTrack ) && !COM_CheckString( mainTrack ))\n\t\treturn;\n\n\tif( !introTrack ) introTrack = mainTrack;\n\tif( !*introTrack ) return;\n\n\tif( !COM_CheckString( mainTrack ))\n\t\ts_bgTrack.loopName[0] = '\\0';\n\telse Q_strncpy( s_bgTrack.loopName, mainTrack, sizeof( s_bgTrack.loopName ));\n\n\t// open stream\n\ts_bgTrack.stream = FS_OpenStream( introTrack );\n\n\tQ_strncpy( s_bgTrack.current, introTrack, sizeof( s_bgTrack.current ));\n\tmemset( &musicfade, 0, sizeof( musicfade )); // clear any soundfade\n\ts_bgTrack.source = cls.key_dest;\n\n\tif( position != 0 )\n\t{\n\t\t// restore message, update song position\n\t\tFS_SetStreamPos( s_bgTrack.stream, position );\n\t}\n}\n\n/*\n=================\nS_StopBackgroundTrack\n=================\n*/\nvoid S_StopBackgroundTrack( void )\n{\n\ts_listener.stream_paused = false;\n\n\tif( !dma.initialized ) return;\n\tif( !s_bgTrack.stream ) return;\n\n\tFS_FreeStream( s_bgTrack.stream );\n\tmemset( &s_bgTrack, 0, sizeof( bg_track_t ));\n\tmemset( &musicfade, 0, sizeof( musicfade ));\n}\n\n/*\n=================\nS_StreamSetPause\n=================\n*/\nvoid S_StreamSetPause( int pause )\n{\n\ts_listener.stream_paused = pause;\n}\n\n/*\n=================\nS_StreamGetCurrentState\n\nsave\\restore code\n=================\n*/\nqboolean S_StreamGetCurrentState( char *currentTrack, size_t currentTrackSize, char *loopTrack, size_t loopTrackSize, int *position )\n{\n\tif( !s_bgTrack.stream )\n\t\treturn false; // not active\n\n\tif( currentTrack )\n\t{\n\t\tif( s_bgTrack.current[0] )\n\t\t\tQ_strncpy( currentTrack, s_bgTrack.current, currentTrackSize );\n\t\telse Q_strncpy( currentTrack, \"*\", currentTrackSize ); // no track\n\t}\n\n\tif( loopTrack )\n\t{\n\t\tif( s_bgTrack.loopName[0] )\n\t\t\tQ_strncpy( loopTrack, s_bgTrack.loopName, loopTrackSize );\n\t\telse Q_strncpy( loopTrack, \"*\", loopTrackSize ); // no track\n\t}\n\n\tif( position )\n\t\t*position = FS_GetStreamPos( s_bgTrack.stream );\n\n\treturn true;\n}\n\n/*\n=================\nS_StreamBackgroundTrack\n=================\n*/\nvoid S_StreamBackgroundTrack( void )\n{\n\tint\tbufferSamples;\n\tint\tfileSamples;\n\tbyte\traw[MAX_RAW_SAMPLES];\n\tint\tr, fileBytes;\n\trawchan_t\t*ch = NULL;\n\n\tif( !dma.initialized || !s_bgTrack.stream || s_listener.streaming )\n\t\treturn;\n\n\t// don't bother playing anything if musicvolume is 0\n\tif( !s_musicvolume.value || s_listener.paused || s_listener.stream_paused )\n\t\treturn;\n\n\tif( !cl.background )\n\t{\n\t\t// pause music by source type\n\t\tif( s_bgTrack.source == key_game && cls.key_dest == key_menu ) return;\n\t\tif( s_bgTrack.source == key_menu && cls.key_dest != key_menu ) return;\n\t}\n\telse if( cls.key_dest == key_console )\n\t\treturn;\n\n\tch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, true );\n\n\tAssert( ch != NULL );\n\n\t// see how many samples should be copied into the raw buffer\n\tif( ch->s_rawend < soundtime )\n\t\tch->s_rawend = soundtime;\n\n\twhile( ch->s_rawend < soundtime + ch->max_samples )\n\t{\n\t\tconst stream_t *info = s_bgTrack.stream;\n\n\t\tbufferSamples = ch->max_samples - (ch->s_rawend - soundtime);\n\n\t\t// decide how much data needs to be read from the file\n\t\tfileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED );\n\t\tif( fileSamples <= 1 ) return; // no more samples need\n\n\t\t// our max buffer size\n\t\tfileBytes = fileSamples * ( info->width * info->channels );\n\n\t\tif( fileBytes > sizeof( raw ))\n\t\t{\n\t\t\tfileBytes = sizeof( raw );\n\t\t\tfileSamples = fileBytes / ( info->width * info->channels );\n\t\t}\n\n\t\t// read\n\t\tr = FS_ReadStream( s_bgTrack.stream, fileBytes, raw );\n\n\t\tif( r < fileBytes )\n\t\t{\n\t\t\tfileBytes = r;\n\t\t\tfileSamples = r / ( info->width * info->channels );\n\t\t}\n\n\t\tif( r > 0 )\n\t\t{\n\t\t\t// add to raw buffer\n\t\t\tint music_vol = (int)(255.0f * S_GetMusicVolume());\n\t\t\tS_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// loop\n\t\t\tif( s_bgTrack.loopName[0] )\n\t\t\t{\n\t\t\t\tFS_FreeStream( s_bgTrack.stream );\n\t\t\t\ts_bgTrack.stream = FS_OpenStream( s_bgTrack.loopName );\n\t\t\t\tQ_strncpy( s_bgTrack.current, s_bgTrack.loopName, sizeof( s_bgTrack.current ));\n\n\t\t\t\tif( !s_bgTrack.stream ) return;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tS_StopBackgroundTrack();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t}\n}\n\n/*\n=================\nS_StartStreaming\n=================\n*/\nvoid S_StartStreaming( void )\n{\n\tif( !dma.initialized ) return;\n\t// begin streaming movie soundtrack\n\ts_listener.streaming = true;\n}\n\n/*\n=================\nS_StopStreaming\n=================\n*/\nvoid S_StopStreaming( void )\n{\n\tif( !dma.initialized ) return;\n\ts_listener.streaming = false;\n}\n"
  },
  {
    "path": "engine/client/s_utils.c",
    "content": "/*\ns_utils.c - common sound functions\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n\n//-----------------------------------------------------------------------------\n// Purpose: wrap the position wrt looping\n// Input  : samplePosition - absolute position\n// Output : int - looped position\n//-----------------------------------------------------------------------------\nint S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean use_loop )\n{\n\t// if the wave is looping and we're past the end of the sample\n\t// convert to a position within the loop\n\t// At the end of the loop, we return a short buffer, and subsequent call\n\t// will loop back and get the rest of the buffer\n\tif( FBitSet( pSource->flags, SOUND_LOOPED ) && samplePosition >= pSource->samples && use_loop )\n\t{\n\t\t// size of loop\n\t\tint\tloopSize = pSource->samples - pSource->loopStart;\n\n\t\t// subtract off starting bit of the wave\n\t\tsamplePosition -= pSource->loopStart;\n\n\t\tif( loopSize )\n\t\t{\n\t\t\t// \"real\" position in memory (mod off extra loops)\n\t\t\tsamplePosition = pSource->loopStart + ( samplePosition % loopSize );\n\t\t}\n\t\t// ERROR? if no loopSize\n\t}\n\n\treturn samplePosition;\n}\n\nint S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop )\n{\n\tint\ttotalSampleCount;\n\tint\tsampleSize;\n\n\t// handle position looping\n\tsamplePosition = S_ConvertLoopedPosition( pSource, samplePosition, use_loop );\n\n\t// how many samples are available (linearly not counting looping)\n\ttotalSampleCount = pSource->samples - samplePosition;\n\n\t// may be asking for a sample out of range, clip at zero\n\tif( totalSampleCount < 0 ) totalSampleCount = 0;\n\n\t// clip max output samples to max available\n\tif( sampleCount > totalSampleCount )\n\t\tsampleCount = totalSampleCount;\n\n\tsampleSize = pSource->width * pSource->channels;\n\n\t// this can never be zero -- other functions divide by this.\n\t// This should never happen, but avoid crashing\n\tif( sampleSize <= 0 ) sampleSize = 1;\n\n\t// byte offset in sample database\n\tsamplePosition *= sampleSize;\n\n\t// if we are returning some samples, store the pointer\n\tif( sampleCount )\n\t{\n\t\t*pData = pSource->buffer + samplePosition;\n\t}\n\n\treturn sampleCount;\n}\n"
  },
  {
    "path": "engine/client/s_vox.c",
    "content": "/*\ns_vox.c - npc sentences\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sound.h\"\n#include \"const.h\"\n#include <ctype.h>\n\n#define TRIM_SCAN_MAX 255\n#define TRIM_SAMPLES_BELOW_8 2\n#define TRIM_SAMPLES_BELOW_16 512 // 65k * 2 / 256\n\n#define CVOXFILESENTENCEMAX 4096\n\nstatic int cszrawsentences = 0;\nstatic char *rgpszrawsentence[CVOXFILESENTENCEMAX];\nstatic const char *voxperiod = \"_period\", *voxcomma = \"_comma\";\n\nstatic qboolean S_ShouldTrimSample8( const int8_t *buf, int channels )\n{\n\tif( abs( buf[0] ) > TRIM_SAMPLES_BELOW_8 )\n\t\treturn false;\n\n\tif( channels >= 2 && abs( buf[1] ) > TRIM_SAMPLES_BELOW_8 )\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic qboolean S_ShouldTrimSample16( const int16_t *buf, int channels )\n{\n\tif( abs( buf[0] ) > TRIM_SAMPLES_BELOW_16 )\n\t\treturn false;\n\n\tif( channels >= 2 && abs( buf[1] ) > TRIM_SAMPLES_BELOW_16 )\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic int S_TrimStart( const wavdata_t *wav, int start )\n{\n\tsize_t channels = wav->channels, width = wav->width, i;\n\n\tif( wav->type != WF_PCMDATA )\n\t\treturn start;\n\n\tif( width == 1 )\n\t{\n\t\tconst int8_t *data = (const int8_t *)&wav->buffer[channels * width * start];\n\n\t\tfor( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )\n\t\t{\n\t\t\tif( !S_ShouldTrimSample8( data, wav->channels ))\n\t\t\t\tbreak;\n\n\t\t\tstart += channels;\n\t\t\tdata += channels;\n\t\t}\n\t}\n\telse if( width == 2 )\n\t{\n\t\tconst int16_t *data = (const int16_t *)&wav->buffer[channels * width * start];\n\n\t\tfor( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )\n\t\t{\n\t\t\tif( !S_ShouldTrimSample16( data, wav->channels ))\n\t\t\t\tbreak;\n\n\t\t\tstart += channels;\n\t\t\tdata += channels;\n\t\t}\n\t}\n\n\treturn start;\n}\n\nstatic int S_TrimEnd( const wavdata_t *wav, int end )\n{\n\tsize_t channels = wav->channels, width = wav->width, i;\n\n\tif( wav->type != WF_PCMDATA )\n\t\treturn end;\n\n\tif( width == 1 )\n\t{\n\t\tconst int8_t *data = (const int8_t *)&wav->buffer[channels * width * ( end - 1 )];\n\n\t\tfor( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )\n\t\t{\n\t\t\tif( !S_ShouldTrimSample8( data, wav->channels ))\n\t\t\t\tbreak;\n\n\t\t\tend -= channels;\n\t\t\tdata -= channels;\n\t\t}\n\t}\n\telse if( width == 2 )\n\t{\n\t\tconst int16_t *data = (const int16_t *)&wav->buffer[channels * width * ( end - 1 )];\n\n\t\tfor( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )\n\t\t{\n\t\t\tif( !S_ShouldTrimSample16( data, wav->channels ))\n\t\t\t\tbreak;\n\n\t\t\tend -= channels;\n\t\t\tdata -= channels;\n\t\t}\n\t}\n\n\treturn end;\n}\n\nstatic void S_TrimStartEndTimes( channel_t *ch, wavdata_t *wav, int start, int end )\n{\n\tch->pMixer.sample = start = S_TrimStart( wav, start );\n\n\t// don't overrun the buffer while trimming end\n\tif( end == 0 )\n\t\tend = wav->samples - wav->channels;\n\n\tif( end < start )\n\t\tend = start;\n\n\tch->pMixer.forcedEndSample = S_TrimEnd( wav, end );\n}\n\n// return number of samples mixed\nint VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset )\n{\n\t// save this to compute total output\n\tint\tstartingOffset = outputOffset;\n\n\tif( !pchan->currentWord )\n\t\treturn 0;\n\n\twhile( sampleCount > 0 && pchan->currentWord )\n\t{\n\t\tint\ttimeCompress = pchan->words[pchan->wordIndex].timecompress;\n\t\tint\toutputCount = S_MixDataToDevice( pchan, sampleCount, outputRate, outputOffset, timeCompress );\n\n\t\toutputOffset += outputCount;\n\t\tsampleCount -= outputCount;\n\n\t\t// if we finished load a next word\n\t\tif( pchan->currentWord->finished )\n\t\t{\n\t\t\tVOX_FreeWord( pchan );\n\t\t\tpchan->wordIndex++;\n\t\t\tVOX_LoadWord( pchan );\n\n\t\t\tif( pchan->currentWord )\n\t\t\t{\n\t\t\t\tpchan->sfx = pchan->words[pchan->wordIndex].sfx;\n\t\t\t}\n\t\t}\n\t}\n\treturn outputOffset - startingOffset;\n}\n\nvoid VOX_LoadWord( channel_t *ch )\n{\n\tconst voxword_t *word = &ch->words[ch->wordIndex];\n\twavdata_t *data;\n\tint start, end, samples;\n\n\tif( !word->sfx )\n\t\treturn;\n\n\tdata = S_LoadSound( word->sfx );\n\n\tif( !data )\n\t\treturn;\n\n\tch->currentWord = &ch->pMixer;\n\tch->currentWord->pData = data;\n\n\tsamples = data->samples;\n\tstart   = word->start;\n\tend     = word->end;\n\n\tif( end <= start ) end = 0;\n\n\tS_TrimStartEndTimes( ch, data, start * 0.01f * samples, end * 0.01f * samples );\n}\n\nvoid VOX_FreeWord( channel_t *ch )\n{\n\tvoxword_t *word = &ch->words[ch->wordIndex];\n\n\tch->currentWord = NULL;\n\tmemset( &ch->pMixer, 0, sizeof( ch->pMixer ));\n\n\tif( !word->sfx || word->fKeepCached )\n\t\treturn;\n\n\tFS_FreeSound( word->sfx->cache );\n\tword->sfx->cache = NULL;\n\tword->sfx = NULL;\n}\n\nvoid VOX_SetChanVol( channel_t *ch )\n{\n\tvoxword_t *word;\n\tif( !ch->currentWord )\n\t\treturn;\n\n\tword = &ch->words[ch->wordIndex];\n\n\tif( word->volume == 100 )\n\t\treturn;\n\n\tch->leftvol = ch->leftvol * word->volume * 0.01f;\n\tch->rightvol = ch->rightvol * word->volume * 0.01f;\n}\n\nfloat VOX_ModifyPitch( channel_t *ch, float pitch )\n{\n\tvoxword_t *word;\n\tif( !ch->currentWord )\n\t\treturn pitch;\n\n\tword = &ch->words[ch->wordIndex];\n\n\tif( word->pitch < 0 )\n\t\treturn pitch;\n\n\tpitch += ( word->pitch - PITCH_NORM ) * 0.01f;\n\n\treturn pitch;\n}\n\nstatic const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize )\n{\n\tconst char *p;\n\tint len;\n\n\t// HACKHACK: some modders send strings like \"/fvox/_period four\"\n\t// which should get parsed as \"_period four\" said by fvox\n\t// it might be incorrect but ignore first slash here for now\n\tif( psz[0] == '/' )\n\t\tpsz++;\n\n\t// search / backwards\n\tp = Q_strrchr( psz, '/' );\n\n\tif( !p )\n\t{\n\t\tQ_strncpy( szpath, \"vox/\", nsize );\n\t\treturn psz;\n\t}\n\n\tlen = p - psz + 1;\n\n\tif( len > nsize )\n\t{\n\t\tCon_Printf( \"%s: invalid directory in: %s\\n\", __func__, psz );\n\t\treturn NULL;\n\t}\n\n\tmemcpy( szpath, psz, len );\n\tszpath[len] = 0;\n\n\treturn p + 1;\n}\n\nstatic const char *VOX_LookupString( const char *pszin )\n{\n\tint i = -1, len;\n\tconst char *c;\n\n\t// check if we are an immediate sentence\n\tif( *pszin == '#' )\n\t{\n\t\t// immediate sentence, probably coming from \"speak\" command\n\t\treturn pszin + 1;\n\t}\n\n\t// check if we received an index\n\tif( Q_isdigit( pszin ))\n\t{\n\t\ti = Q_atoi( pszin );\n\n\t\tif( i >= cszrawsentences )\n\t\t\ti = -1;\n\t}\n\n\t// last hope: find it in sentences array\n\tif( i == -1 )\n\t{\n\t\tfor( i = 0; i < cszrawsentences; i++ )\n\t\t{\n\t\t\tif( !Q_stricmp( pszin, rgpszrawsentence[i] ))\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// not found, exit\n\tif( i == cszrawsentences )\n\t\treturn NULL;\n\n\tlen = Q_strlen( rgpszrawsentence[i] );\n\n\tc = &rgpszrawsentence[i][len + 1];\n\tfor( ; *c == ' ' || *c == '\\t'; c++ );\n\n\treturn c;\n}\n\nstatic int VOX_ParseString( char *psz, char *rgpparseword[CVOXWORDMAX] )\n{\n\tint i = 0;\n\n\tif( !psz )\n\t\treturn i;\n\n\trgpparseword[i++] = psz;\n\n\twhile( i < CVOXWORDMAX )\n\t{\n\t\t// skip to next word\n\t\tfor( ; *psz &&\n\t\t\t*psz != ' ' &&\n\t\t\t*psz != '.' &&\n\t\t\t*psz != ',' &&\n\t\t\t*psz != '('; psz++ );\n\n\t\t// skip anything in between ( and )\n\t\tif( *psz == '(' )\n\t\t{\n\t\t\tfor( ; *psz && *psz != ')'; psz++ );\n\t\t\tpsz++;\n\t\t}\n\n\t\tif( !*psz )\n\t\t\treturn i;\n\n\t\t// . and , are special but if not end of string\n\t\tif(( *psz == '.' || *psz == ',' ) &&\n\t\t\tpsz[1] != '\\n' && psz[1] != '\\r' && psz[1] != '\\0' )\n\t\t{\n\t\t\tif( *psz == '.' )\n\t\t\t\trgpparseword[i++] = (char *)voxperiod;\n\t\t\telse rgpparseword[i++] = (char *)voxcomma;\n\n\t\t\tif( i >= CVOXWORDMAX )\n\t\t\t\treturn i;\n\t\t}\n\n\t\t*psz++ = 0;\n\n\t\tfor( ; *psz && ( *psz == '.' || *psz == ' ' || *psz == ',' );\n\t\t     psz++ );\n\n\t\tif( !*psz )\n\t\t\treturn i;\n\n\t\trgpparseword[i++] = psz;\n\t}\n\n\treturn i;\n}\n\nstatic qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, qboolean fFirst )\n{\n\tint len, i;\n\tchar sznum[8], *pszsave = psz;\n\tstatic voxword_t voxwordDefault;\n\n\tif( fFirst )\n\t{\n\t\tvoxwordDefault.fKeepCached = 0;\n\t\tvoxwordDefault.pitch = -1;\n\t\tvoxwordDefault.volume = 100;\n\t\tvoxwordDefault.start = 0;\n\t\tvoxwordDefault.end = 100;\n\t\tvoxwordDefault.timecompress = 0;\n\t}\n\n\t*pvoxword = voxwordDefault;\n\n\tlen = Q_strlen( psz );\n\n\tif( len == 0 )\n\t\treturn false;\n\n\t// no special params\n\tif( psz[len-1] != ')' )\n\t\treturn true;\n\n\tfor( ; *psz != '(' && *psz != ')'; psz++ );\n\n\t// invalid syntax\n\tif( *psz == ')' )\n\t\treturn false;\n\n\t// split filename and params\n\t*psz++ = '\\0';\n\n\tfor( ;; )\n\t{\n\t\tchar command;\n\n\t\t// find command\n\t\tfor( ; *psz &&\n\t\t\t*psz != 'v' &&\n\t\t\t*psz != 'p' &&\n\t\t\t*psz != 's' &&\n\t\t\t*psz != 'e' &&\n\t\t\t*psz != 't'; psz++ )\n\t\t{\n\t\t\tif( *psz == ')' )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tcommand = *psz++;\n\n\t\tif( !isdigit( *psz ))\n\t\t\tbreak;\n\n\t\tmemset( sznum, 0, sizeof( sznum ));\n\t\tfor( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ )\n\t\t\tsznum[i] = *psz;\n\n\t\ti = Q_atoi( sznum );\n\t\tswitch( command )\n\t\t{\n\t\tcase 'e': pvoxword->end = i; break;\n\t\tcase 'p': pvoxword->pitch = i; break;\n\t\tcase 's': pvoxword->start = i; break;\n\t\tcase 't': pvoxword->timecompress = i; break;\n\t\tcase 'v': pvoxword->volume = i; break;\n\t\t}\n\t}\n\n\t// no actual word but new defaults\n\tif( Q_strlen( pszsave ) == 0 )\n\t{\n\t\tvoxwordDefault = *pvoxword;\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid VOX_LoadSound( channel_t *ch, const char *pszin )\n{\n\tchar buffer[512] = { 0 }, szpath[32] = { 0 };\n\tchar *rgpparseword[CVOXWORDMAX] = { 0 };\n\tconst char *psz;\n\tint i, j;\n\n\tif( !pszin )\n\t\treturn;\n\n\tpsz = VOX_LookupString( pszin );\n\n\tif( !psz )\n\t{\n\t\t// sometimes modders remove sentences but entities continue to use them, so it's a warning, not an error\n\t\tCon_Printf( S_WARN \"%s: no sentence named %s\\n\", __func__, pszin );\n\t\treturn;\n\t}\n\n\tpsz = VOX_GetDirectory( szpath, psz, sizeof( szpath ));\n\n\tif( !psz )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: failed getting directory for %s\\n\", __func__, pszin );\n\t\treturn;\n\t}\n\n\tif( Q_strlen( psz ) >= sizeof( buffer ) )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: sentence is too long %s\\n\", __func__, psz );\n\t\treturn;\n\t}\n\n\tQ_strncpy( buffer, psz, sizeof( buffer ));\n\tVOX_ParseString( buffer, rgpparseword );\n\n\tfor( i = 0, j = 0; i < CVOXWORDMAX && rgpparseword[i]; i++ )\n\t{\n\t\tchar pathbuffer[MAX_SYSPATH];\n\n\t\tif( !VOX_ParseWordParams( rgpparseword[i], &ch->words[j], i == 0 ))\n\t\t\tcontinue;\n\n\t\tif( Q_snprintf( pathbuffer, sizeof( pathbuffer ), \"%s%s\", szpath, rgpparseword[i] ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: path to word in sentence %s is too long\\n\", __func__, pszin );\n\t\t\treturn;\n\t\t}\n\n\t\tch->words[j].sfx = S_FindName( pathbuffer, &ch->words[j].fKeepCached );\n\n\t\tj++;\n\t}\n\n\tch->words[j].sfx = NULL;\n\tch->sfx = ch->words[0].sfx;\n\tch->wordIndex = 0;\n\tch->isSentence = true;\n\n\tVOX_LoadWord( ch );\n}\n\nstatic void VOX_ReadSentenceFile_( byte *buf, fs_offset_t size )\n{\n\tchar *p, *last;\n\n\tp = (char *)buf;\n\tlast = p + size;\n\n\twhile( p < last )\n\t{\n\t\tchar *name = NULL, *value = NULL;\n\n\t\tif( cszrawsentences >= CVOXFILESENTENCEMAX )\n\t\t\tbreak;\n\n\t\tfor( ; p < last && ( *p == '\\n' || *p == '\\r' || *p == '\\t' || *p == ' ' );\n\t\t     p++ );\n\n\t\tif( *p != '/' )\n\t\t{\n\t\t\tname = p;\n\n\t\t\tfor( ; p < last && *p != ' ' && *p != '\\t' ; p++ );\n\n\t\t\tif( p < last )\n\t\t\t\t*p++ = 0;\n\n\t\t\tvalue = p;\n\t\t}\n\n\t\tfor( ; p < last && *p != '\\n' && *p != '\\r'; p++ );\n\n\t\tif( p < last )\n\t\t\t*p++ = 0;\n\n\t\tif( name )\n\t\t{\n\t\t\tint index = cszrawsentences;\n\t\t\tint size = strlen( name ) + strlen( value ) + 2;\n\n\t\t\trgpszrawsentence[index] = Mem_Malloc( sndpool, size );\n\t\t\tmemcpy( rgpszrawsentence[index], name, size );\n\t\t\trgpszrawsentence[index][size - 1] = 0;\n\t\t\tcszrawsentences++;\n\t\t}\n\t}\n}\n\nstatic void VOX_ReadSentenceFile( const char *path )\n{\n\tbyte *buf;\n\tfs_offset_t size;\n\n\tVOX_Shutdown();\n\n\tbuf = FS_LoadFile( path, &size, false );\n\tif( !buf ) return;\n\n\tVOX_ReadSentenceFile_( buf, size );\n\n\tMem_Free( buf );\n}\n\nvoid VOX_Init( void )\n{\n\tVOX_ReadSentenceFile( DEFAULT_SOUNDPATH \"sentences.txt\" );\n}\n\nvoid VOX_Shutdown( void )\n{\n\tint i;\n\n\tfor( i = 0; i < cszrawsentences; i++ )\n\t\tMem_Free( rgpszrawsentence[i] );\n\n\tcszrawsentences = 0;\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nstatic void Test_VOX_GetDirectory( void )\n{\n\tconst char *data[] =\n\t{\n\t\t\"\", \"\", \"vox/\",\n\t\t\"bark bark\", \"bark bark\", \"vox/\",\n\t\t\"barney/meow\", \"meow\", \"barney/\",\n\t\t\"/fvox/_period\", \"_period\", \"fvox/\",\n\t};\n\tint i;\n\n\tfor( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 3 )\n\t{\n\t\tstring szpath;\n\t\tconst char *p = VOX_GetDirectory( szpath, data[i+0], sizeof( szpath ));\n\n\t\tTASSERT_STR( p, data[i+1] );\n\t\tTASSERT_STR( szpath, data[i+2] );\n\t}\n}\n\nstatic void Test_VOX_LookupString( void )\n{\n\tint i;\n\tconst char *p, *data[] =\n\t{\n\t\t\"0\", \"123\",\n\t\t\"3\", \"SPAAACE\",\n\t\t\"-2\", NULL,\n\t\t\"404\", NULL,\n\t\t\"not found\", NULL,\n\t\t\"exactmatch\", \"123\",\n\t\t\"caseinsensitive\", \"456\",\n\t\t\"SentenceWithTabs\", \"789\",\n\t\t\"SentenceWithSpaces\", \"SPAAACE\",\n\t};\n\n\tVOX_Shutdown();\n\n\trgpszrawsentence[cszrawsentences++] = (char*)\"exactmatch\\000123\";\n\trgpszrawsentence[cszrawsentences++] = (char*)\"CaseInsensitive\\000456\";\n\trgpszrawsentence[cszrawsentences++] = (char*)\"SentenceWithTabs\\0\\t\\t\\t789\";\n\trgpszrawsentence[cszrawsentences++] = (char*)\"SentenceWithSpaces\\0  SPAAACE\";\n\trgpszrawsentence[cszrawsentences++] = (char*)\"SentenceWithTabsAndSpaces\\0\\t \\t\\t MEOW\";\n\n\tfor( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 )\n\t{\n\t\tp = VOX_LookupString( data[i] );\n\n\t\tTASSERT_STR( p, data[i+1] );\n\t}\n\n\tcszrawsentences = 0;\n}\n\nstatic void Test_VOX_ParseString( void )\n{\n\tchar *rgpparseword[CVOXWORDMAX];\n\tconst char *data[] =\n\t{\n\t\t\"(p100) my ass is, heavy!(p80 t20) clik.\",\n\t\t\"(p100)\", \"my\", \"ass\", \"is\", \"_comma\", \"heavy!(p80 t20)\", \"clik\", NULL,\n\t\t\"freeman...\",\n\t\t\"freeman\", \"_period\", NULL,\n\t};\n\tint i = 0;\n\n\twhile( i < sizeof( data ) / sizeof( data[0] ))\n\t{\n\t\tchar buffer[4096];\n\t\tint wordcount, j = 0;\n\t\tQ_strncpy( buffer, data[i], sizeof( buffer ));\n\t\twordcount = VOX_ParseString( buffer, rgpparseword );\n\n\t\ti++;\n\n\t\twhile( data[i] )\n\t\t{\n\t\t\tTASSERT_STR( data[i], rgpparseword[j] );\n\t\t\ti++;\n\t\t\tj++;\n\t\t}\n\n\t\tTASSERT( j == wordcount );\n\n\t\ti++;\n\t}\n}\n\nstatic void Test_VOX_ParseWordParams( void )\n{\n\tstring buffer;\n\tqboolean ret;\n\tvoxword_t word;\n\n\tQ_strncpy( buffer, \"heavy!(p80)\", sizeof( buffer ));\n\tret = VOX_ParseWordParams( buffer, &word, true );\n\tTASSERT_STR( buffer, \"heavy!\" );\n\tTASSERT( word.pitch == 80 );\n\tTASSERT( ret );\n\n\tQ_strncpy( buffer, \"(p105)\", sizeof( buffer ));\n\tret = VOX_ParseWordParams( buffer, &word, false );\n\tTASSERT_STR( buffer, \"\" );\n\tTASSERT( word.pitch == 105 );\n\tTASSERT( !ret );\n\n\tQ_strncpy( buffer, \"quiet(v50)\", sizeof( buffer ));\n\tret = VOX_ParseWordParams( buffer, &word, false );\n\tTASSERT_STR( buffer, \"quiet\" );\n\tTASSERT( word.pitch == 105 ); // defaulted\n\tTASSERT( word.volume == 50 );\n\tTASSERT( ret );\n}\n\nvoid Test_RunVOX( void )\n{\n\tTRUN( Test_VOX_GetDirectory() );\n\tTRUN( Test_VOX_LookupString() );\n\tTRUN( Test_VOX_ParseString() );\n\tTRUN( Test_VOX_ParseWordParams() );\n}\n\n#endif /* XASH_ENGINE_TESTS */\n"
  },
  {
    "path": "engine/client/sound.h",
    "content": "/*\nsound.h - sndlib main header\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SOUND_H\n#define SOUND_H\n\nextern poolhandle_t sndpool;\n\n#include \"xash3d_mathlib.h\"\n\n#define XASH_AUDIO_CD_QUALITY 1 // some platforms might need this\n\n// sound engine rate defines\n#if XASH_AUDIO_CD_QUALITY\n#define SOUND_11k       11025 // 11khz sample rate\n#define SOUND_22k       22050 // 22khz sample rate\n#define SOUND_44k       44100 // 44khz sample rate\n#else // XASH_AUDIO_CD_QUALITY\n#define SOUND_11k       12000 // 11khz sample rate\n#define SOUND_22k       24000 // 22khz sample rate\n#define SOUND_44k       48000 // 44khz sample rate\n#endif // XASH_AUDIO_CD_QUALITY\n\n#define SOUND_DMA_SPEED SOUND_44k // hardware playback rate\n\n// NOTE: clipped sound at 32760 to avoid overload\n#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x )))\n\n#define PAINTBUFFER_SIZE 1024\t// 44k: was 512\n\n#define S_RAW_SOUND_IDLE_SEC         10 // time interval for idling raw sound before it's freed\n#define S_RAW_SOUND_BACKGROUNDTRACK  -2\n#define S_RAW_SOUND_SOUNDTRACK       -1\n#define S_RAW_SAMPLES_PRECISION_BITS 14\n\ntypedef struct\n{\n\tint left;\n\tint right;\n} portable_samplepair_t;\n\ntypedef struct sfx_s\n{\n\tchar          name[MAX_QPATH];\n\twavdata_t    *cache;\n\n\tint           servercount;\n\tuint          hashValue;\n\tstruct sfx_s *hashNext;\n} sfx_t;\n\n// structure used for fading in and out client sound volume.\ntypedef struct\n{\n\tfloat initial_percent;\n\tfloat percent;     // how far to adjust client's volume down by.\n\tfloat starttime;   // GetHostTime() when we started adjusting volume\n\tfloat fadeouttime; // # of seconds to get to faded out state\n\tfloat holdtime;    // # of seconds to hold\n\tfloat fadeintime;  // # of seconds to restore\n} soundfade_t;\n\ntypedef struct\n{\n\tfloat percent;\n} musicfade_t;\n\ntypedef struct snd_format_s\n{\n\tuint speed;\n\tbyte width;\n\tbyte channels;\n} snd_format_t;\n\ntypedef struct\n{\n\tsnd_format_t format;\n\tint          samples;     // mono samples in buffer\n\tint          samplepos;   // in mono samples\n\tqboolean     initialized; // sound engine is active\n\tbyte        *buffer;\n\tconst char  *backendName;\n} dma_t;\n\n#include \"vox.h\"\n\ntypedef struct\n{\n\tdouble     sample;\n\twavdata_t *pData;\n\tdouble     forcedEndSample;\n\tqboolean   finished;\n} mixer_t;\n\ntypedef struct rawchan_s\n{\n\tint                   entnum;\n\tint                   master_vol;\n\tint                   leftvol;       // 0-255 left volume\n\tint                   rightvol;      // 0-255 right volume\n\tfloat                 dist_mult;     // distance multiplier (attenuation/clipK)\n\tvec3_t                origin;        // only use if fixed_origin is set\n\tvolatile uint         s_rawend;\n\tfloat                 oldtime;       // catch time jumps\n\tsize_t                max_samples;   // buffer length\n\tportable_samplepair_t rawsamples[]; // variable sized\n} rawchan_t;\n\ntypedef struct channel_s\n{\n\tchar     name[16];    // keep sentence name\n\tsfx_t   *sfx;         // sfx number\n\n\tint      leftvol;     // 0-255 left volume\n\tint      rightvol;    // 0-255 right volume\n\n\tint      entnum;      // entity soundsource\n\tint      entchannel;  // sound channel (CHAN_STREAM, CHAN_VOICE, etc.)\n\tvec3_t   origin;      // only use if fixed_origin is set\n\tfloat    dist_mult;   // distance multiplier (attenuation/clipK)\n\tint      master_vol;  // 0-255 master volume\n\tint      basePitch;   // base pitch percent (100% is normal pitch playback)\n\tfloat    pitch;       // real-time pitch after any modulation or shift by dynamic data\n\tqboolean use_loop;    // don't loop default and local sounds\n\tqboolean staticsound; // use origin instead of fetching entnum's origin\n\tqboolean localsound;  // it's a local menu sound (not looped, not paused)\n\tmixer_t  pMixer;\n\n\t// sentence mixer\n\tqboolean  isSentence;  // bit indicating sentence\n\tint       wordIndex;\n\tmixer_t  *currentWord; // NULL if sentence is finished\n\tvoxword_t words[CVOXWORDMAX];\n} channel_t;\n\ntypedef struct\n{\n\tvec3_t   origin;   // simorg + view_ofs\n\tvec3_t   forward;\n\tvec3_t   right;\n\tvec3_t   up;\n\n\tint      entnum;\n\tint      waterlevel;\n\tfloat    frametime;     // used for sound fade\n\tqboolean active;\n\tqboolean inmenu;        // listener in-menu ?\n\tqboolean paused;\n\tqboolean streaming;     // playing AVI-file\n\tqboolean stream_paused; // pause only background track\n} listener_t;\n\ntypedef struct\n{\n\tstring    current;  // a currently playing track\n\tstring    loopName; // may be empty\n\tstream_t *stream;\n\tint       source;   // may be game, menu, etc\n} bg_track_t;\n\ntypedef int sound_t;\n\n//====================================================================\n\n#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS)\n#define MAX_CHANNELS         (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp\n#define MAX_RAW_CHANNELS     48\n#define MAX_RAW_SAMPLES      16384\n#define SND_CLIP_DISTANCE    1000.0f\n\nextern sound_t    ambient_sfx[NUM_AMBIENTS];\nextern qboolean   snd_ambient;\nextern channel_t  channels[MAX_CHANNELS];\nextern rawchan_t *raw_channels[MAX_RAW_CHANNELS];\nextern int        total_channels;\nextern int        paintedtime;\nextern int        soundtime;\nextern listener_t s_listener;\nextern int        idsp_room;\nextern dma_t      dma;\n\nextern convar_t s_musicvolume;\nextern convar_t s_lerping;\nextern convar_t s_test;  // cvar to test new effects\nextern convar_t s_samplecount;\nextern convar_t s_warn_late_precache;\nextern convar_t snd_mute_losefocus;\n\nvoid S_InitScaletable( void );\nwavdata_t *S_LoadSound( sfx_t *sfx );\nfloat S_GetMasterVolume( void );\nfloat S_GetMusicVolume( void );\n\n//\n// s_main.c\n//\nvoid S_FreeChannel( channel_t *ch );\n\n//\n// s_mix.c\n//\nint S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, int timeCompress );\nvoid MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters );\nvoid MIX_InitAllPaintbuffers( void );\nvoid MIX_FreeAllPaintbuffers( void );\nvoid MIX_PaintChannels( int endtime );\n\n// s_load.c\nqboolean S_TestSoundChar( const char *pch, char c );\nchar *S_SkipSoundChar( const char *pch );\nsfx_t *S_FindName( const char *name, int *pfInCache );\nsound_t S_RegisterSound( const char *name );\nvoid S_FreeSound( sfx_t *sfx );\nvoid S_InitSounds( void );\n\n// s_dsp.c\nvoid SX_Init( void );\nvoid SX_Free( void );\nvoid CheckNewDspPresets( void );\nvoid DSP_Process( portable_samplepair_t *pbfront, int sampleCount );\nvoid DSP_ClearState( void );\n\nqboolean S_Init( void );\nvoid S_Shutdown( void );\nvoid S_SoundList_f( void );\nvoid S_SoundInfo_f( void );\n\nstruct ref_viewpass_s;\nchannel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean *ignore );\nchannel_t *SND_PickStaticChannel( const vec3_t pos, sfx_t *sfx );\nint S_GetCurrentStaticSounds( soundlist_t *pout, int size );\nint S_GetCurrentDynamicSounds( soundlist_t *pout, int size );\nsfx_t *S_GetSfxByHandle( sound_t handle );\nrawchan_t *S_FindRawChannel( int entnum, qboolean create );\nuint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data );\nvoid S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol );\nvoid S_StopSound( int entnum, int channel, const char *soundname );\nvoid S_UpdateFrame( struct ref_viewpass_s *rvp );\nvoid S_StopAllSounds( qboolean ambient );\nvoid S_FreeSounds( void );\n\n//\n// s_mouth.c\n//\nvoid SND_InitMouth( int entnum, int entchannel );\nvoid SND_ForceInitMouth( int entnum );\nvoid SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count );\nvoid SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count );\nvoid SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count );\nvoid SND_CloseMouth( channel_t *ch );\nvoid SND_ForceCloseMouth( int entnum );\n\n//\n// s_stream.c\n//\nvoid S_StreamBackgroundTrack( void );\nvoid S_PrintBackgroundTrackState( void );\nvoid S_FadeMusicVolume( float fadePercent );\n\n//\n// s_utils.c\n//\nint S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample );\nint S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample );\nint S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean use_loop );\nint S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop );\n\n//\n// s_vox.c\n//\nvoid VOX_Init( void );\nvoid VOX_Shutdown( void );\nvoid VOX_SetChanVol( channel_t *ch );\nvoid VOX_LoadSound( channel_t *pchan, const char *psz );\nfloat VOX_ModifyPitch( channel_t *ch, float pitch );\nint VOX_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );\n\n#endif//SOUND_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/dct36.c",
    "content": "/*\n\tThis is an optimized DCT from Jeff Tsay's maplay 1.2+ package.\n\tSaved one multiplication by doing the 'twiddle factor' stuff\n\ttogether with the window mul. (MH)\n\n\tThis uses Byeong Gi Lee's Fast Cosine Transform algorithm, but the\n\t9 point IDCT needs to be reduced further. Unfortunately, I don't\n\tknow how to do that, because 9 is not an even number. - Jeff.\n\n\tOriginal Message:\n\n\t9 Point Inverse Discrete Cosine Transform\n\n\tThis piece of code is Copyright 1997 Mikko Tommila and is freely usable\n\tby anybody. The algorithm itself is of course in the public domain.\n\n\tAgain derived heuristically from the 9-point WFTA.\n\n\tThe algorithm is optimized (?) for speed, not for small rounding errors or\n\tgood readability.\n\n\t36 additions, 11 multiplications\n\n\tAgain this is very likely sub-optimal.\n\n\tThe code is optimized to use a minimum number of temporary variables,\n\tso it should compile quite well even on 8-register Intel x86 processors.\n\tThis makes the code quite obfuscated and very difficult to understand.\n\n\tReferences:\n\t[1] S. Winograd: \"On Computing the Discrete Fourier Transform\",\n\t    Mathematics of Computation, Volume 32, Number 141, January 1978,\n\t    Pages 175-199\n*/\n\n#include \"mpg123.h\"\n#include <math.h>\n\n#define MACRO(v) { \\\n\tfloat tmpval; \\\n\ttmpval = tmp[(v)] + tmp[17-(v)]; \\\n\tout2[9+(v)] = REAL_MUL(tmpval, w[27+(v)]); \\\n\tout2[8-(v)] = REAL_MUL(tmpval, w[26-(v)]); \\\n\ttmpval = tmp[(v)] - tmp[17-(v)]; \\\n\tts[SBLIMIT*(8-(v))] = out1[8-(v)] + REAL_MUL(tmpval, w[8-(v)]); \\\n\tts[SBLIMIT*(9+(v))] = out1[9+(v)] + REAL_MUL(tmpval, w[9+(v)]); }\n\n#define DCT12_PART1 \\\n\tin5 = in[5*3];  \\\n\tin5 += (in4 = in[4*3]); \\\n\tin4 += (in3 = in[3*3]); \\\n\tin3 += (in2 = in[2*3]); \\\n\tin2 += (in1 = in[1*3]); \\\n\tin1 += (in0 = in[0*3]); \\\n\t\\\n\tin5 += in3; in3 += in1; \\\n\t\\\n\tin2 = REAL_MUL(in2, COS6_1); \\\n\tin3 = REAL_MUL(in3, COS6_1);\n\n#define DCT12_PART2 \\\n\tin0 += REAL_MUL(in4, COS6_2); \\\n\t\\\n\tin4 = in0 + in2; \\\n\tin0 -= in2;      \\\n\t\\\n\tin1 += REAL_MUL(in5, COS6_2); \\\n\t\\\n\tin5 = REAL_MUL((in1 + in3), tfcos12[0]); \\\n\tin1 = REAL_MUL((in1 - in3), tfcos12[2]); \\\n\t\\\n\tin3 = in4 + in5; \\\n\tin4 -= in5;      \\\n\t\\\n\tin2 = in0 + in1; \\\n\tin0 -= in1;\n\n// calculation of the inverse MDCT\n// used to be static without 3dnow - does that floatly matter?\nvoid dct36( float *inbuf, float *o1, float *o2, float *wintab, float *tsbuf )\n{\n\tfloat\ttmp[18];\n\n\t{\n\t\tregister float *in = inbuf;\n\n\t\tin[17] += in[16]; in[16] += in[15]; in[15] += in[14];\n\t\tin[14] += in[13]; in[13] += in[12]; in[12] += in[11];\n\t\tin[11] += in[10]; in[10] += in[9]; in[9] += in[8];\n\t\tin[8] += in[7]; in[7] += in[6]; in[6] += in[5];\n\t\tin[5] += in[4]; in[4] += in[3]; in[3] += in[2];\n\t\tin[2] += in[1]; in[1] += in[0];\n\n\t\tin[17] += in[15]; in[15] += in[13]; in[13] += in[11]; in[11] += in[9];\n\t\tin[9] += in[7]; in[7] += in[5]; in[5] += in[3]; in[3] += in[1];\n\n\t\t{\n\t\t\tfloat t3;\n\t\t\t{\n\t\t\t\tfloat t0, t1, t2;\n\n\t\t\t\tt0 = REAL_MUL(COS6_2, (in[8] + in[16] - in[4]));\n\t\t\t\tt1 = REAL_MUL(COS6_2, in[12]);\n\n\t\t\t\tt3 = in[0];\n\t\t\t\tt2 = t3 - t1 - t1;\n\t\t\t\ttmp[1] = tmp[7] = t2 - t0;\n\t\t\t\ttmp[4]          = t2 + t0 + t0;\n\t\t\t\tt3 += t1;\n\n\t\t\t\tt2 = REAL_MUL(COS6_1, (in[10] + in[14] - in[2]));\n\t\t\t\ttmp[1] -= t2;\n\t\t\t\ttmp[7] += t2;\n\t\t\t}\n\t\t\t{\n\t\t\t\tfloat t0, t1, t2;\n\n\t\t\t\tt0 = REAL_MUL(cos9[0], (in[4] + in[8] ));\n\t\t\t\tt1 = REAL_MUL(cos9[1], (in[8] - in[16]));\n\t\t\t\tt2 = REAL_MUL(cos9[2], (in[4] + in[16]));\n\n\t\t\t\ttmp[2] = tmp[6] = t3 - t0      - t2;\n\t\t\t\ttmp[0] = tmp[8] = t3 + t0 + t1;\n\t\t\t\ttmp[3] = tmp[5] = t3      - t1 + t2;\n\t\t\t}\n\t\t}\n\t\t{\n\t\t\tfloat t1, t2, t3;\n\n\t\t\tt1 = REAL_MUL(cos18[0], (in[2]  + in[10]));\n\t\t\tt2 = REAL_MUL(cos18[1], (in[10] - in[14]));\n\t\t\tt3 = REAL_MUL(COS6_1,    in[6]);\n\n\t\t\t{\n\t\t\t\tfloat t0 = t1 + t2 + t3;\n\t\t\t\ttmp[0] += t0;\n\t\t\t\ttmp[8] -= t0;\n\t\t\t}\n\n\t\t\tt2 -= t3;\n\t\t\tt1 -= t3;\n\n\t\t\tt3 = REAL_MUL(cos18[2], (in[2] + in[14]));\n\n\t\t\tt1 += t3;\n\t\t\ttmp[3] += t1;\n\t\t\ttmp[5] -= t1;\n\n\t\t\tt2 -= t3;\n\t\t\ttmp[2] += t2;\n\t\t\ttmp[6] -= t2;\n\t\t}\n\t\t{\n\t\t\tfloat t0, t1, t2, t3, t4, t5, t6, t7;\n\n\t\t\tt1 = REAL_MUL(COS6_2, in[13]);\n\t\t\tt2 = REAL_MUL(COS6_2, (in[9] + in[17] - in[5]));\n\n\t\t\tt3 = in[1] + t1;\n\t\t\tt4 = in[1] - t1 - t1;\n\t\t\tt5 = t4 - t2;\n\n\t\t\tt0 = REAL_MUL(cos9[0], (in[5] + in[9]));\n\t\t\tt1 = REAL_MUL(cos9[1], (in[9] - in[17]));\n\n\t\t\ttmp[13] = REAL_MUL((t4 + t2 + t2), tfcos36[17-13]);\n\t\t\tt2 = REAL_MUL(cos9[2], (in[5] + in[17]));\n\n\t\t\tt6 = t3 - t0 - t2;\n\t\t\tt0 += t3 + t1;\n\t\t\tt3 += t2 - t1;\n\n\t\t\tt2 = REAL_MUL(cos18[0], (in[3]  + in[11]));\n\t\t\tt4 = REAL_MUL(cos18[1], (in[11] - in[15]));\n\t\t\tt7 = REAL_MUL(COS6_1, in[7]);\n\n\t\t\tt1 = t2 + t4 + t7;\n\t\t\ttmp[17] = REAL_MUL((t0 + t1), tfcos36[17-17]);\n\t\t\ttmp[9]  = REAL_MUL((t0 - t1), tfcos36[17-9]);\n\t\t\tt1 = REAL_MUL(cos18[2], (in[3] + in[15]));\n\t\t\tt2 += t1 - t7;\n\n\t\t\ttmp[14] = REAL_MUL((t3 + t2), tfcos36[17-14]);\n\t\t\tt0 = REAL_MUL(COS6_1, (in[11] + in[15] - in[3]));\n\t\t\ttmp[12] = REAL_MUL((t3 - t2), tfcos36[17-12]);\n\n\t\t\tt4 -= t1 + t7;\n\n\t\t\ttmp[16] = REAL_MUL((t5 - t0), tfcos36[17-16]);\n\t\t\ttmp[10] = REAL_MUL((t5 + t0), tfcos36[17-10]);\n\t\t\ttmp[15] = REAL_MUL((t6 + t4), tfcos36[17-15]);\n\t\t\ttmp[11] = REAL_MUL((t6 - t4), tfcos36[17-11]);\n\t\t}\n\n\n\n\t\t{\n\t\t\tregister float *out2 = o2;\n\t\t\tregister float *w = wintab;\n\t\t\tregister float *out1 = o1;\n\t\t\tregister float *ts = tsbuf;\n\n\t\t\tMACRO(0);\n\t\t\tMACRO(1);\n\t\t\tMACRO(2);\n\t\t\tMACRO(3);\n\t\t\tMACRO(4);\n\t\t\tMACRO(5);\n\t\t\tMACRO(6);\n\t\t\tMACRO(7);\n\t\t\tMACRO(8);\n\t\t}\n\n\t}\n}\n\nvoid dct12( float *in, float *rawout1, float *rawout2, register float *wi, register float *ts )\n{\n\t{\n\t\tfloat in0,in1,in2,in3,in4,in5;\n\t\tregister float *out1 = rawout1;\n\t\tts[SBLIMIT*0] = out1[0]; ts[SBLIMIT*1] = out1[1]; ts[SBLIMIT*2] = out1[2];\n\t\tts[SBLIMIT*3] = out1[3]; ts[SBLIMIT*4] = out1[4]; ts[SBLIMIT*5] = out1[5];\n\n\t\tDCT12_PART1\n\n\t\t{\n\t\t\tfloat tmp0,tmp1 = (in0 - in4);\n\t\t\t{\n\t\t\t\tfloat tmp2 = REAL_MUL((in1 - in5), tfcos12[1]);\n\t\t\t\ttmp0 = tmp1 + tmp2;\n\t\t\t\ttmp1 -= tmp2;\n\t\t\t}\n\t\t\tts[(17-1)*SBLIMIT] = out1[17-1] + REAL_MUL(tmp0, wi[11-1]);\n\t\t\tts[(12+1)*SBLIMIT] = out1[12+1] + REAL_MUL(tmp0, wi[6+1]);\n\t\t\tts[(6 +1)*SBLIMIT] = out1[6 +1] + REAL_MUL(tmp1, wi[1]);\n\t\t\tts[(11-1)*SBLIMIT] = out1[11-1] + REAL_MUL(tmp1, wi[5-1]);\n\t\t}\n\n\t\tDCT12_PART2\n\n\t\tts[(17-0)*SBLIMIT] = out1[17-0] + REAL_MUL(in2, wi[11-0]);\n\t\tts[(12+0)*SBLIMIT] = out1[12+0] + REAL_MUL(in2, wi[6+0]);\n\t\tts[(12+2)*SBLIMIT] = out1[12+2] + REAL_MUL(in3, wi[6+2]);\n\t\tts[(17-2)*SBLIMIT] = out1[17-2] + REAL_MUL(in3, wi[11-2]);\n\n\t\tts[(6 +0)*SBLIMIT]  = out1[6+0] + REAL_MUL(in0, wi[0]);\n\t\tts[(11-0)*SBLIMIT] = out1[11-0] + REAL_MUL(in0, wi[5-0]);\n\t\tts[(6 +2)*SBLIMIT]  = out1[6+2] + REAL_MUL(in4, wi[2]);\n\t\tts[(11-2)*SBLIMIT] = out1[11-2] + REAL_MUL(in4, wi[5-2]);\n\t}\n\n\tin++;\n\n\t{\n\t\tfloat in0,in1,in2,in3,in4,in5;\n\t\tregister float *out2 = rawout2;\n\n\t\tDCT12_PART1\n\n\t\t{\n\t\t\tfloat tmp0,tmp1 = (in0 - in4);\n\t\t\t{\n\t\t\t\tfloat tmp2 = REAL_MUL((in1 - in5), tfcos12[1]);\n\t\t\t\ttmp0 = tmp1 + tmp2;\n\t\t\t\ttmp1 -= tmp2;\n\t\t\t}\n\t\t\tout2[5-1] = REAL_MUL(tmp0, wi[11-1]);\n\t\t\tout2[0+1] = REAL_MUL(tmp0, wi[6+1]);\n\t\t\tts[(12+1)*SBLIMIT] += REAL_MUL(tmp1, wi[1]);\n\t\t\tts[(17-1)*SBLIMIT] += REAL_MUL(tmp1, wi[5-1]);\n\t\t}\n\n\t\tDCT12_PART2\n\n\t\tout2[5-0] = REAL_MUL(in2, wi[11-0]);\n\t\tout2[0+0] = REAL_MUL(in2, wi[6+0]);\n\t\tout2[0+2] = REAL_MUL(in3, wi[6+2]);\n\t\tout2[5-2] = REAL_MUL(in3, wi[11-2]);\n\n\t\tts[(12+0)*SBLIMIT] += REAL_MUL(in0, wi[0]);\n\t\tts[(17-0)*SBLIMIT] += REAL_MUL(in0, wi[5-0]);\n\t\tts[(12+2)*SBLIMIT] += REAL_MUL(in4, wi[2]);\n\t\tts[(17-2)*SBLIMIT] += REAL_MUL(in4, wi[5-2]);\n\t}\n\n\tin++;\n\n\t{\n\t\tfloat in0,in1,in2,in3,in4,in5;\n\t\tregister float *out2 = rawout2;\n\t\tout2[12]=out2[13]=out2[14]=out2[15]=out2[16]=out2[17]=0.0;\n\n\t\tDCT12_PART1\n\n\t\t{\n\t\t\tfloat tmp0,tmp1 = (in0 - in4);\n\t\t\t{\n\t\t\t\tfloat tmp2 = REAL_MUL((in1 - in5), tfcos12[1]);\n\t\t\t\ttmp0 = tmp1 + tmp2;\n\t\t\t\ttmp1 -= tmp2;\n\t\t\t}\n\t\t\tout2[11-1] = REAL_MUL(tmp0, wi[11-1]);\n\t\t\tout2[6 +1] = REAL_MUL(tmp0, wi[6+1]);\n\t\t\tout2[0+1] += REAL_MUL(tmp1, wi[1]);\n\t\t\tout2[5-1] += REAL_MUL(tmp1, wi[5-1]);\n\t\t}\n\n\t\tDCT12_PART2\n\n\t\tout2[11-0] = REAL_MUL(in2, wi[11-0]);\n\t\tout2[6 +0] = REAL_MUL(in2, wi[6+0]);\n\t\tout2[6 +2] = REAL_MUL(in3, wi[6+2]);\n\t\tout2[11-2] = REAL_MUL(in3, wi[11-2]);\n\n\t\tout2[0+0] += REAL_MUL(in0, wi[0]);\n\t\tout2[5-0] += REAL_MUL(in0, wi[5-0]);\n\t\tout2[0+2] += REAL_MUL(in4, wi[2]);\n\t\tout2[5-2] += REAL_MUL(in4, wi[5-2]);\n\t}\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/dct64.c",
    "content": "/*\n\tdct64.c: DCT64, the plain C version\n\n\tcopyright ?-2006 by the mpg123 project - free software under the terms of the LGPL 2.1\n\tsee COPYING and AUTHORS files in distribution or http://mpg123.org\n\tinitially written by Michael Hipp\n*/\n\n/*\n * Discrete Cosine Transform (DCT) for subband synthesis\n *\n * -funroll-loops (for gcc) will remove the loops for better performance\n * using loops in the source-code enhances readabillity\n *\n */\n\n#include \"mpg123.h\"\n\nvoid dct64( float *out0, float *out1, float *samples )\n{\n\tfloat\tbufs[64];\n\n\t{\n\t\tregister float\t*b1, *b2, *bs;\n\t\tregister float\t*costab;\n\t\tregister int\ti, j;\n\n\t\tb1 = samples;\n\t\tbs = bufs;\n\t\tcostab = pnts[0]+16;\n\t\tb2 = b1 + 32;\n\n\t\tfor( i = 15; i >= 0; i-- )\n\t\t\t*bs++ = (*b1++ + *--b2);\n\n\t\tfor( i = 15; i >= 0; i-- )\n\t\t\t*bs++ = REAL_MUL((*--b2 - *b1++), *--costab);\n\n\t\tb1 = bufs;\n\t\tcostab = pnts[1] + 8;\n\t\tb2 = b1 + 16;\n\n\t\t{\n\t\t\tfor( i = 7; i >= 0; i-- )\n\t\t\t\t*bs++ = (*b1++ + *--b2);\n\n\t\t\tfor( i = 7; i >= 0; i-- )\n\t\t\t\t*bs++ = REAL_MUL((*--b2 - *b1++), *--costab);\n\t\t\tb2 += 32;\n\t\t\tcostab += 8;\n\n\t\t\tfor( i = 7; i >= 0; i-- )\n\t\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\tfor( i = 7; i >= 0; i-- )\n\t\t\t\t*bs++ = REAL_MUL((*b1++ - *--b2), *--costab);\n\t\t\tb2 += 32;\n\t\t}\n\n\t\tbs = bufs;\n\t\tcostab = pnts[2];\n\t\tb2 = b1 + 8;\n\n\t\tfor( j = 2; j; j-- )\n\t\t{\n\t\t\tfor( i = 3; i >= 0; i-- )\n\t\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\tfor( i = 3;i >= 0; i-- )\n\t\t\t\t*bs++ = REAL_MUL((*--b2 - *b1++), costab[i]);\n\t\t\tb2 += 16;\n\n\t\t\tfor( i = 3; i >= 0; i-- )\n\t\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\tfor( i = 3;i >= 0; i-- )\n\t\t\t\t*bs++ = REAL_MUL((*b1++ - *--b2), costab[i]);\n\t\t\tb2 += 16;\n\t\t}\n\n\t\tb1 = bufs;\n\t\tcostab = pnts[3];\n\t\tb2 = b1 + 4;\n\n\t\tfor( j = 4; j; j-- )\n\t\t{\n\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\t*bs++ = REAL_MUL((*--b2 - *b1++), costab[1]);\n\t\t\t*bs++ = REAL_MUL((*--b2 - *b1++), costab[0]);\n\t\t\tb2 += 8;\n\n\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\t*bs++ = (*b1++ + *--b2);\n\t\t\t*bs++ = REAL_MUL((*b1++ - *--b2), costab[1]);\n\t\t\t*bs++ = REAL_MUL((*b1++ - *--b2), costab[0]);\n\t\t\tb2 += 8;\n\t\t}\n\n\t\tbs = bufs;\n\t\tcostab = pnts[4];\n\n\t\tfor( j = 8; j; j-- )\n\t\t{\n\t\t\tfloat v0, v1;\n\n\t\t\tv0 = *b1++;\n\t\t\tv1 = *b1++;\n\t\t\t*bs++ = (v0 + v1);\n\t\t\t*bs++ = REAL_MUL((v0 - v1), (*costab));\n\t\t\tv0 = *b1++;\n\t\t\tv1 = *b1++;\n\t\t\t*bs++ = (v0 + v1);\n\t\t\t*bs++ = REAL_MUL((v1 - v0), (*costab));\n\t\t}\n\t}\n\n\t{\n\t\tregister float\t*b1;\n\t\tregister int\ti;\n\n\t\tfor( b1 =bufs, i = 8; i; i--, b1 += 4 )\n\t\t\tb1[2] += b1[3];\n\n\t\tfor( b1 = bufs, i = 4; i; i--, b1 += 8 )\n\t\t{\n\t\t\tb1[4] += b1[6];\n\t\t\tb1[6] += b1[5];\n\t\t\tb1[5] += b1[7];\n\t\t}\n\n\t\tfor( b1 = bufs, i = 2; i; i--, b1 += 16 )\n\t\t{\n\t\t\tb1[8]  += b1[12];\n\t\t\tb1[12] += b1[10];\n\t\t\tb1[10] += b1[14];\n\t\t\tb1[14] += b1[9];\n\t\t\tb1[9]  += b1[13];\n\t\t\tb1[13] += b1[11];\n\t\t\tb1[11] += b1[15];\n\t\t}\n\t}\n\n\tout0[0x10*16] = REAL_SCALE_DCT64( bufs[0] );\n\tout0[0x10*15] = REAL_SCALE_DCT64( bufs[16+0]  + bufs[16+8] );\n\tout0[0x10*14] = REAL_SCALE_DCT64( bufs[8] );\n\tout0[0x10*13] = REAL_SCALE_DCT64( bufs[16+8]  + bufs[16+4] );\n\tout0[0x10*12] = REAL_SCALE_DCT64( bufs[4] );\n\tout0[0x10*11] = REAL_SCALE_DCT64( bufs[16+4]  + bufs[16+12] );\n\tout0[0x10*10] = REAL_SCALE_DCT64( bufs[12] );\n\tout0[0x10* 9] = REAL_SCALE_DCT64( bufs[16+12] + bufs[16+2] );\n\tout0[0x10* 8] = REAL_SCALE_DCT64( bufs[2] );\n\tout0[0x10* 7] = REAL_SCALE_DCT64( bufs[16+2]  + bufs[16+10] );\n\tout0[0x10* 6] = REAL_SCALE_DCT64( bufs[10] );\n\tout0[0x10* 5] = REAL_SCALE_DCT64( bufs[16+10] + bufs[16+6] );\n\tout0[0x10* 4] = REAL_SCALE_DCT64( bufs[6] );\n\tout0[0x10* 3] = REAL_SCALE_DCT64( bufs[16+6]  + bufs[16+14] );\n\tout0[0x10* 2] = REAL_SCALE_DCT64( bufs[14] );\n\tout0[0x10* 1] = REAL_SCALE_DCT64( bufs[16+14] + bufs[16+1] );\n\tout0[0x10* 0] = REAL_SCALE_DCT64( bufs[1] );\n\n\tout1[0x10* 0] = REAL_SCALE_DCT64( bufs[1] );\n\tout1[0x10* 1] = REAL_SCALE_DCT64( bufs[16+1]  + bufs[16+9] );\n\tout1[0x10* 2] = REAL_SCALE_DCT64( bufs[9] );\n\tout1[0x10* 3] = REAL_SCALE_DCT64( bufs[16+9]  + bufs[16+5] );\n\tout1[0x10* 4] = REAL_SCALE_DCT64( bufs[5] );\n\tout1[0x10* 5] = REAL_SCALE_DCT64( bufs[16+5]  + bufs[16+13] );\n\tout1[0x10* 6] = REAL_SCALE_DCT64( bufs[13] );\n\tout1[0x10* 7] = REAL_SCALE_DCT64( bufs[16+13] + bufs[16+3] );\n\tout1[0x10* 8] = REAL_SCALE_DCT64( bufs[3] );\n\tout1[0x10* 9] = REAL_SCALE_DCT64( bufs[16+3]  + bufs[16+11] );\n\tout1[0x10*10] = REAL_SCALE_DCT64( bufs[11] );\n\tout1[0x10*11] = REAL_SCALE_DCT64( bufs[16+11] + bufs[16+7] );\n\tout1[0x10*12] = REAL_SCALE_DCT64( bufs[7] );\n\tout1[0x10*13] = REAL_SCALE_DCT64( bufs[16+7]  + bufs[16+15] );\n\tout1[0x10*14] = REAL_SCALE_DCT64( bufs[15] );\n\tout1[0x10*15] = REAL_SCALE_DCT64( bufs[16+15] );\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/fmt123.h",
    "content": "/*\nfmt123.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef FMT123_H\n#define FMT123_H\n\n#define MPG123_RATES\t9\n#define MPG123_ENCODINGS\t2\n\nenum mpg123_enc_enum\n{\n\t// 0000 0000 0000 1111 Some 8 bit  integer encoding. */\n\tMPG123_ENC_8      = 0x00f,\n\t// 0000 0000 0100 0000 Some 16 bit integer encoding.\n\tMPG123_ENC_16     = 0x040,\n\t// 0000 0000 1000 0000 Some signed integer encoding.\n\tMPG123_ENC_SIGNED = 0x080,\n\t// 0000 0000 1101 0000 signed 16 bit\n\tMPG123_ENC_SIGNED_16   = (MPG123_ENC_16|MPG123_ENC_SIGNED|0x10),\n\t// 0000 0000 0110 0000 unsigned 16 bit\n\tMPG123_ENC_UNSIGNED_16 = (MPG123_ENC_16|0x20),\n\t// 0000 0000 0000 0001 unsigned 8 bit\n\tMPG123_ENC_UNSIGNED_8  = 0x01,\n\t// 0000 0000 1000 0010 signed 8 bit\n\tMPG123_ENC_SIGNED_8    = (MPG123_ENC_SIGNED|0x02),\n\t// 0000 0000 0000 0100 ulaw 8 bit\n\tMPG123_ENC_ULAW_8      = 0x04,\n\t// 0000 0000 0000 1000 alaw 8 bit\n\tMPG123_ENC_ALAW_8      = 0x08,\n};\n\n#endif//FMT123_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/format.c",
    "content": "/*\nframe.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n\nenum mpg123_channelcount\n{\n\tMPG123_MONO   = 1,\n\tMPG123_STEREO = 2\n};\n\n// only the standard rates\nstatic const long my_rates[MPG123_RATES] =\n{\n\t 8000, 11025, 12000,\n\t16000, 22050, 24000,\n\t32000, 44100, 48000,\n};\n\nstatic const int my_encodings[MPG123_ENCODINGS] =\n{\n\tMPG123_ENC_SIGNED_16,\n\tMPG123_ENC_UNSIGNED_16,\n};\n\n// the list of actually possible encodings.\nstatic const int good_encodings[] =\n{\n\tMPG123_ENC_SIGNED_16,\n\tMPG123_ENC_UNSIGNED_16,\n};\n\n// check if encoding is a valid one in this build.\nstatic int good_enc( const int enc )\n{\n\tsize_t\ti;\n\n\tfor( i = 0; i < sizeof( good_encodings ) / sizeof( int ); ++i )\n\t{\n\t\tif( enc == good_encodings[i] )\n\t\t\treturn TRUE;\n\t}\n\n\treturn FALSE;\n}\n\nstatic void mpg123_rates( const long **list, size_t *number )\n{\n\tif( number != NULL ) *number = sizeof( my_rates ) / sizeof( long );\n\tif( list != NULL ) *list = my_rates;\n}\n\n// now that's a bit tricky... One build of the library knows only a subset of the encodings.\nstatic void mpg123_encodings( const int **list, size_t *number )\n{\n\tif( number != NULL ) *number = sizeof( good_encodings ) / sizeof( int );\n\tif( list != NULL ) *list = good_encodings;\n}\n\nstatic int mpg123_encsize( int encoding )\n{\n\treturn sizeof( short );\n}\n\nstatic int rate2num( long r )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MPG123_RATES; i++ )\n\t{\n\t\tif( my_rates[i] == r )\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nstatic int enc2num( int encoding )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MPG123_ENCODINGS; ++i )\n\t{\n\t\tif( my_encodings[i] == encoding )\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nstatic int cap_fit( mpg123_handle_t *fr, audioformat_t *nf, int f0, int f2)\n{\n\tint\ti;\n\tint\tc  = nf->channels - 1;\n\tint\trn = rate2num( nf->rate );\n\n\tif( rn >= 0 )\n\t{\n\t\tfor( i = f0; i <f2; i++ )\n\t\t{\n\t\t\tif( fr->p.audio_caps[c][rn][i] )\n\t\t\t{\n\t\t\t\tnf->encoding = my_encodings[i];\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int freq_fit( mpg123_handle_t *fr, audioformat_t *nf, int f0, int f2 )\n{\n\tnf->rate = frame_freq( fr ) >> fr->p.down_sample;\n\n\tif( cap_fit( fr, nf, f0, f2 ))\n\t\treturn 1;\n\n\tif( fr->p.flags & MPG123_AUTO_RESAMPLE )\n\t{\n\t\tnf->rate >>= 1;\n\t\tif( cap_fit( fr, nf, f0, f2 ))\n\t\t\treturn 1;\n\n\t\tnf->rate >>= 1;\n\t\tif( cap_fit( fr, nf, f0, f2 ))\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n// match constraints against supported audio formats, store possible setup in frame\n// return: -1: error; 0: no format change; 1: format change\nint frame_output_format( mpg123_handle_t *fr )\n{\n\tint\t\tf0 = 0;\n\tint\t\tf2 = MPG123_ENCODINGS;\n\tmpg123_parm_t\t*p = &fr->p;\n\taudioformat_t\tnf;\n\n\t// initialize new format, encoding comes later\n\tnf.channels = fr->stereo;\n\n\t// force stereo is stronger\n\tif( p->flags & MPG123_FORCE_MONO )\n\t\tnf.channels = 1;\n\n\tif( p->flags & MPG123_FORCE_STEREO )\n\t\tnf.channels = 2;\n\n\tif( freq_fit( fr, &nf, f0, 2 ))\n\t\tgoto end; // try rates with 16bit\n\n\tif( freq_fit( fr, &nf, f0 <=2 ? 2 : f0, f2 ))\n\t\tgoto end; // ... 8bit\n\n\t// try again with different stereoness\n\tif( nf.channels == 2 && !( p->flags & MPG123_FORCE_STEREO ))\n\t\tnf.channels = 1;\n\telse if( nf.channels == 1 && !( p->flags & MPG123_FORCE_MONO ))\n\t\tnf.channels = 2;\n\n\tif( freq_fit( fr, &nf, f0, 2 ))\n\t\tgoto end; // try rates with 16bit\n\tif( freq_fit( fr, &nf,  f0 <= 2 ? 2 : f0, f2 ))\n\t\tgoto end; // ... 8bit\n\n\tfr->err = MPG123_BAD_OUTFORMAT;\n\treturn -1;\nend:\n\t// here is the _good_ end.\n\t// we had a successful match, now see if there's a change\n\tif( nf.rate == fr->af.rate && nf.channels == fr->af.channels && nf.encoding == fr->af.encoding )\n\t{\n\t\treturn 0; // the same format as before\n\t}\n\telse\n\t{\t// a new format\n\t\tfr->af.rate = nf.rate;\n\t\tfr->af.channels = nf.channels;\n\t\tfr->af.encoding = nf.encoding;\n\n\t\t// cache the size of one sample in bytes, for ease of use.\n\t\tfr->af.encsize = mpg123_encsize( fr->af.encoding );\n\t\tif( fr->af.encsize < 1 )\n\t\t{\n\t\t\tfr->err = MPG123_BAD_OUTFORMAT;\n\t\t\treturn -1;\n\t\t}\n\n\t\t// set up the decoder synth format. Might differ.\n\t\t// without high-precision synths, 16 bit signed is the basis for\n\t\t// everything higher than 8 bit.\n\t\tif( fr->af.encsize > 2 )\n\t\t{\n\t\t\tfr->af.dec_enc = MPG123_ENC_SIGNED_16;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch( fr->af.encoding )\n\t\t\t{\n\t\t\tcase MPG123_ENC_UNSIGNED_16:\n\t\t\t\tfr->af.dec_enc = MPG123_ENC_SIGNED_16;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfr->af.dec_enc = fr->af.encoding;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfr->af.dec_encsize = mpg123_encsize( fr->af.dec_enc );\n\n\t\treturn 1;\n\t}\n}\n\nstatic int mpg123_fmt_none( mpg123_parm_t *mp )\n{\n\tif( mp == NULL )\n\t\treturn MPG123_BAD_PARS;\n\n\tmemset( mp->audio_caps, 0, sizeof( mp->audio_caps ));\n\treturn MPG123_OK;\n}\n\nint mpg123_fmt_all( mpg123_parm_t *mp )\n{\n\tsize_t\trate, ch, enc;\n\n\tif( mp == NULL )\n\t\treturn MPG123_BAD_PARS;\n\n\tfor( ch = 0; ch < NUM_CHANNELS; ++ch )\n\t{\n\t\tfor( rate = 0; rate < MPG123_RATES+1; ++rate )\n\t\t{\n\t\t\tfor( enc = 0; enc < MPG123_ENCODINGS; ++enc )\n\t\t\t\tmp->audio_caps[ch][rate][enc] = good_enc( my_encodings[enc] );\n\t\t}\n\t}\n\n\treturn MPG123_OK;\n}\n\nstatic int mpg123_fmt( mpg123_parm_t *mp, long rate, int channels, int encodings )\n{\n\tint\tie, ic, ratei;\n\tint\tch[2] = { 0, 1 };\n\n\tif( mp == NULL )\n\t\treturn MPG123_BAD_PARS;\n\n\tif(!( channels & ( MPG123_MONO|MPG123_STEREO )))\n\t\treturn MPG123_BAD_CHANNEL;\n\n\tif(!( channels & MPG123_STEREO ))\n\t\tch[1] = 0;\n\telse if(!( channels & MPG123_MONO ))\n\t\tch[0] = 1;\n\n\tratei = rate2num( rate );\n\tif( ratei < 0 ) return MPG123_BAD_RATE;\n\n\t// now match the encodings\n\tfor( ic = 0; ic < 2; ++ic )\n\t{\n\t\tfor( ie = 0; ie < MPG123_ENCODINGS; ++ie )\n\t\t{\n\t\t\tif( good_enc( my_encodings[ie] ) && (( my_encodings[ie] & encodings ) == my_encodings[ie] ))\n\t\t\t\tmp->audio_caps[ch[ic]][ratei][ie] = 1;\n\t\t}\n\n\t\tif( ch[0] == ch[1] )\n\t\t\tbreak; // no need to do it again\n\t}\n\n\treturn MPG123_OK;\n}\n\nstatic int mpg123_fmt_support( mpg123_parm_t *mp, long rate, int encoding )\n{\n\tint\tratei, enci;\n\tint\tch = 0;\n\n\tratei = rate2num( rate );\n\tenci = enc2num( encoding );\n\n\tif( mp == NULL || ratei < 0 || enci < 0 )\n\t\treturn 0;\n\n\tif( mp->audio_caps[0][ratei][enci] )\n\t\tch |= MPG123_MONO;\n\n\tif( mp->audio_caps[1][ratei][enci] )\n\t\tch |= MPG123_STEREO;\n\n\treturn ch;\n}\n\nint mpg123_format_none( mpg123_handle_t *mh )\n{\n\tint\tr;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tr = mpg123_fmt_none( &mh->p );\n\n\tif( r != MPG123_OK )\n\t{\n\t\tmh->err = r;\n\t\treturn MPG123_ERR;\n\t}\n\n\treturn r;\n}\n\nint mpg123_format_all( mpg123_handle_t *mh )\n{\n\tint\tr;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tr = mpg123_fmt_all( &mh->p );\n\n\tif( r != MPG123_OK )\n\t{\n\t\tmh->err = r;\n\t\treturn MPG123_ERR;\n\t}\n\n\treturn r;\n}\n\nint mpg123_format( mpg123_handle_t *mh, long rate, int channels, int encodings )\n{\n\tint\tr;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tr = mpg123_fmt( &mh->p, rate, channels, encodings );\n\n\tif( r != MPG123_OK )\n\t{\n\t\tmh->err = r;\n\t\treturn MPG123_ERR;\n\t}\n\n\treturn r;\n}\n\nstatic int mpg123_format_support( mpg123_handle_t *mh, long rate, int encoding )\n{\n\tif( mh == NULL )\n\t\treturn 0;\n\n\treturn mpg123_fmt_support( &mh->p, rate, encoding );\n}\n\n// call this one to ensure that any valid format will be something different than this.\nvoid invalidate_format( audioformat_t *af )\n{\n\taf->encoding = 0;\n\taf->channels = 0;\n\taf->rate = 0;\n}\n\n// number of bytes the decoder produces.\nmpg_off_t decoder_synth_bytes( mpg123_handle_t *fr, mpg_off_t s )\n{\n\treturn s * fr->af.dec_encsize * fr->af.channels;\n}\n\n// samples/bytes for output buffer after post-processing.\n// take into account: channels, bytes per sample -- NOT resampling!\nmpg_off_t samples_to_bytes( mpg123_handle_t *fr, mpg_off_t s )\n{\n\treturn s * fr->af.encsize * fr->af.channels;\n}\n\nmpg_off_t bytes_to_samples( mpg123_handle_t *fr, mpg_off_t b )\n{\n\treturn b / fr->af.encsize / fr->af.channels;\n}\n\n// number of bytes needed for decoding _and_ post-processing.\nmpg_off_t outblock_bytes( mpg123_handle_t *fr, mpg_off_t s )\n{\n\tint encsize = (fr->af.encsize > fr->af.dec_encsize ? fr->af.encsize : fr->af.dec_encsize);\n\treturn s * encsize * fr->af.channels;\n}\n\nstatic void conv_s16_to_u16( outbuffer_t *buf )\n{\n\tint16_t\t*ssamples = (int16_t *)buf->data;\n\tuint16_t\t*usamples = (uint16_t *)buf->data;\n\tsize_t\tcount = buf->fill / sizeof( int16_t );\n\tsize_t\ti;\n\n\tfor( i = 0; i < count; ++i )\n\t{\n\t\tlong tmp = (long)ssamples[i] + 32768;\n\t\tusamples[i] = (uint16_t)tmp;\n\t}\n}\n\nvoid postprocess_buffer( mpg123_handle_t *fr )\n{\n\tswitch( fr->af.dec_enc )\n\t{\n\tcase MPG123_ENC_SIGNED_16:\n\t\tswitch( fr->af.encoding )\n\t\t{\n\t\tcase MPG123_ENC_UNSIGNED_16:\n\t\t\tconv_s16_to_u16(&fr->buffer);\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/frame.c",
    "content": "/*\nframe.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include <math.h>\n\nstatic void *aligned_pointer( void *base, uint alignment )\n{\n\t// work in unsigned integer realm, explicitly.\n\t// tricking the compiler into integer operations like % by invoking base-NULL is dangerous:\n\t// it results into ptrdiff_t, which gets negative on big addresses. Big screw up, that.\n\t// i try to do it \"properly\" here: Casting only to size_t and no artihmethic with void*.\n\n\tsize_t\tbaseval = (size_t)(char *)base;\n\tsize_t\taoff = baseval % alignment;\n\n\tif( aoff )\n\t\treturn (char *)base + alignment - aoff;\n\treturn base;\n}\n\nstatic void frame_default_parm( mpg123_parm_t *mp )\n{\n\tmp->outscale = 1.0;\n\tmp->flags = 0;\n\tmp->flags |= MPG123_GAPLESS;\n\tmp->flags |= MPG123_AUTO_RESAMPLE;\n\tmp->down_sample = 0;\n\tmp->rva = 0;\n\tmp->halfspeed = 0;\n\tmp->doublespeed = 0;\n\tmp->verbose = 0;\n\tmp->timeout = 0;\n\tmp->resync_limit = 1024;\n\tmp->index_size = INDEX_SIZE;\n\tmp->preframes = 4;\t// that's good  for layer 3 ISO compliance bitstream.\n\tmpg123_fmt_all( mp );\n\n\t// default of keeping some 4K buffers at hand, should cover the \"usual\" use case\n\t// (using 16K pipe buffers as role model).\n\tmp->feedpool = 5;\n\tmp->feedbuffer = 4096;\n}\n\n// reset everythign except dynamic memory.\nstatic void frame_fixed_reset( mpg123_handle_t *fr )\n{\n\topen_bad( fr );\n\tfr->to_decode = FALSE;\n\tfr->to_ignore = FALSE;\n\tfr->metaflags = 0;\n\tfr->outblock = 0;\t// this will be set before decoding!\n\tfr->num = -1;\n\tfr->input_offset = -1;\n\tfr->playnum = -1;\n\tfr->state_flags = FRAME_ACCURATE;\n\tfr->silent_resync = 0;\n\tfr->audio_start = 0;\n\tfr->clip = 0;\n\tfr->oldhead = 0;\n\tfr->firsthead = 0;\n\tfr->vbr = MPG123_CBR;\n\tfr->abr_rate = 0;\n\tfr->track_frames = 0;\n\tfr->track_samples = -1;\n\tfr->framesize=0;\n\tfr->mean_frames = 0;\n\tfr->mean_framesize = 0;\n\tfr->freesize = 0;\n\tfr->lastscale = -1;\n\tfr->rva.level[0] = -1;\n\tfr->rva.level[1] = -1;\n\tfr->rva.gain[0] = 0;\n\tfr->rva.gain[1] = 0;\n\tfr->rva.peak[0] = 0;\n\tfr->rva.peak[1] = 0;\n\tfr->fsizeold = 0;\n\tfr->firstframe = 0;\n\tfr->ignoreframe = fr->firstframe - fr->p.preframes;\n\tfr->header_change = 0;\n\tfr->lastframe = -1;\n\tfr->fresh = 1;\n\tfr->new_format = 0;\n\tframe_gapless_init( fr, -1, 0, 0 );\n\tfr->lastoff = 0;\n\tfr->firstoff = 0;\n\tfr->bo = 1;\t// the usual bo\n\tfr->halfphase = 0;\t// here or indeed only on first-time init?\n\tfr->error_protection = 0;\n\tfr->freeformat_framesize = -1;\n}\n\nint frame_index_setup( mpg123_handle_t *fr )\n{\n\tint\tret = MPG123_ERR;\n\n\tif( fr->p.index_size >= 0 )\n\t{\n\t\t// simple fixed index.\n\t\tfr->index.grow_size = 0;\n\t\tret = fi_resize( &fr->index, (size_t)fr->p.index_size );\n\t}\n\telse\n\t{\n\t\t// a growing index. we give it a start, though.\n\t\tfr->index.grow_size = (size_t)(-fr->p.index_size );\n\n\t\tif( fr->index.size < fr->index.grow_size )\n\t\t\tret = fi_resize( &fr->index, fr->index.grow_size );\n\t\telse ret = MPG123_OK; // we have minimal size already... and since growing is OK...\n\t}\n\n\treturn ret;\n}\n\nvoid frame_init_par( mpg123_handle_t *fr, mpg123_parm_t *mp )\n{\n\tfr->own_buffer = TRUE;\n\tfr->buffer.data = NULL;\n\tfr->buffer.rdata = NULL;\n\tfr->buffer.fill = 0;\n\tfr->buffer.size = 0;\n\tfr->rawbuffs = NULL;\n\tfr->rawbuffss = 0;\n\tfr->rawdecwin = NULL;\n\tfr->rawdecwins = 0;\n\tfr->layerscratch = NULL;\n\tfr->xing_toc = NULL;\n\n\t// unnecessary: fr->buffer.size = fr->buffer.fill = 0;\n\t// frame_outbuffer is missing...\n\t// frame_buffers is missing... that one needs cpu opt setting!\n\t// after these... frame_reset is needed before starting full decode\n\tinvalidate_format( &fr->af );\n\tfr->rdat.r_read = NULL;\n\tfr->rdat.r_lseek = NULL;\n\tfr->rdat.iohandle = NULL;\n\tfr->rdat.r_read_handle = NULL;\n\tfr->rdat.r_lseek_handle = NULL;\n\tfr->rdat.cleanup_handle = NULL;\n\tfr->wrapperdata = NULL;\n\tfr->wrapperclean = NULL;\n\tfr->decoder_change = 1;\n\tfr->err = MPG123_OK;\n\n\tif( mp == NULL ) frame_default_parm( &fr->p );\n\telse memcpy( &fr->p, mp, sizeof( mpg123_parm_t ));\n\n\tbc_prepare( &fr->rdat.buffer, fr->p.feedpool, fr->p.feedbuffer );\n\n\tfr->down_sample = 0;\t// initialize to silence harmless errors when debugging.\n\tframe_fixed_reset( fr );\t// reset only the fixed data, dynamic buffers are not there yet!\n\tfr->synth = NULL;\n\tfr->synth_mono = NULL;\n\tfr->make_decode_tables = NULL;\n\n\tfi_init( &fr->index );\n\tframe_index_setup( fr );\t// apply the size setting.\n}\n\nstatic void frame_decode_buffers_reset( mpg123_handle_t *fr )\n{\n\tif( fr->rawbuffs ) /* memset(NULL, 0, 0) not desired */\n\t\tmemset( fr->rawbuffs, 0, fr->rawbuffss );\n}\n\nint frame_buffers( mpg123_handle_t *fr )\n{\n\tint\tbuffssize = 4352;\n\n\tbuffssize += 15; // for 16-byte alignment\n\n\tif(fr->rawbuffs != NULL && fr->rawbuffss != buffssize)\n\t{\n\t\tfree(fr->rawbuffs);\n\t\tfr->rawbuffs = NULL;\n\t}\n\n\tif( fr->rawbuffs == NULL )\n\t\tfr->rawbuffs = (byte *)malloc( buffssize );\n\n\tif( fr->rawbuffs == NULL )\n\t\treturn -1;\n\n\tfr->rawbuffss = buffssize;\n\tfr->short_buffs[0][0] = aligned_pointer( fr->rawbuffs, 16 );\n\tfr->short_buffs[0][1] = fr->short_buffs[0][0] + 0x110;\n\tfr->short_buffs[1][0] = fr->short_buffs[0][1] + 0x110;\n\tfr->short_buffs[1][1] = fr->short_buffs[1][0] + 0x110;\n\tfr->float_buffs[0][0] = aligned_pointer( fr->rawbuffs, 16 );\n\tfr->float_buffs[0][1] = fr->float_buffs[0][0] + 0x110;\n\tfr->float_buffs[1][0] = fr->float_buffs[0][1] + 0x110;\n\tfr->float_buffs[1][1] = fr->float_buffs[1][0] + 0x110;\n\n\t// now the different decwins... all of the same size, actually\n\t// the MMX ones want 32byte alignment, which I'll try to ensure manually\n\t{\n\t\tint\tdecwin_size = (512 + 32) * sizeof( float );\n\n\t\t// hm, that's basically realloc() ...\n\t\tif( fr->rawdecwin != NULL && fr->rawdecwins != decwin_size )\n\t\t{\n\t\t\tfree( fr->rawdecwin );\n\t\t\tfr->rawdecwin = NULL;\n\t\t}\n\n\t\tif( fr->rawdecwin == NULL )\n\t\t\tfr->rawdecwin = (byte *)malloc( decwin_size );\n\n\t\tif( fr->rawdecwin == NULL )\n\t\t\treturn -1;\n\n\t\tfr->rawdecwins = decwin_size;\n\t\tfr->decwin = (float *)fr->rawdecwin;\n\t}\n\n\t// layer scratch buffers are of compile-time fixed size, so allocate only once.\n\tif( fr->layerscratch == NULL )\n\t{\n\t\t// allocate specific layer3 buffers\n\t\tsize_t\tscratchsize = 0;\n\t\tfloat\t*scratcher;\n\n\t\tscratchsize += sizeof( float ) * 2 * SBLIMIT * SSLIMIT; // hybrid_in\n\t\tscratchsize += sizeof( float ) * 2 * SSLIMIT * SBLIMIT; // hybrid_out\n\n\t\tfr->layerscratch = malloc( scratchsize + 63 );\n\t\tif(fr->layerscratch == NULL) return -1;\n\n\t\t// get aligned part of the memory, then divide it up.\n\t\tscratcher = aligned_pointer( fr->layerscratch, 64 );\n\n\t\t// those funky pointer casts silence compilers...\n\t\t// One might change the code at hand to really just use 1D arrays,\n\t\t// but in practice, that would not make a (positive) difference.\n\t\tfr->layer3.hybrid_in = (float(*)[SBLIMIT][SSLIMIT])scratcher;\n\t\tscratcher += 2 * SBLIMIT * SSLIMIT;\n\t\tfr->layer3.hybrid_out = (float(*)[SSLIMIT][SBLIMIT])scratcher;\n\t\tscratcher += 2 * SSLIMIT * SBLIMIT;\n\n\t\t// note: These buffers don't need resetting here.\n\t}\n\n\t// only reset the buffers we created just now.\n\tframe_decode_buffers_reset( fr );\n\n\treturn 0;\n}\n\nint frame_buffers_reset( mpg123_handle_t *fr )\n{\n\tfr->buffer.fill = 0; // hm, reset buffer fill... did we do a flush?\n\tfr->bsnum = 0;\n\n\t// wondering: could it be actually _wanted_ to retain buffer contents over different files? (special gapless / cut stuff)\n\tfr->bsbuf = fr->bsspace[1];\n\tfr->bsbufold = fr->bsbuf;\n\tfr->bitreservoir = 0;\n\tframe_decode_buffers_reset( fr );\n\tmemset( fr->bsspace, 0, 2 * ( MAXFRAMESIZE + 512 ));\n\tmemset( fr->ssave, 0, 34 );\n\tfr->hybrid_blc[0] = fr->hybrid_blc[1] = 0;\n\tmemset( fr->hybrid_block, 0, sizeof( float ) * 2 * 2 * SBLIMIT * SSLIMIT );\n\n\treturn 0;\n}\n\nvoid frame_init( mpg123_handle_t *fr )\n{\n\tframe_init_par( fr, NULL );\n}\n\nint frame_outbuffer( mpg123_handle_t *fr )\n{\n\tsize_t\tsize = fr->outblock;\n\n\tif( !fr->own_buffer )\n\t{\n\t\tif( fr->buffer.size < size )\n\t\t{\n\t\t\tfr->err = MPG123_BAD_BUFFER;\n\t\t\treturn MPG123_ERR;\n\t\t}\n\t}\n\n\tif( fr->buffer.rdata != NULL && fr->buffer.size != size )\n\t{\n\t\tfree( fr->buffer.rdata );\n\t\tfr->buffer.rdata = NULL;\n\t}\n\n\tfr->buffer.size = size;\n\tfr->buffer.data = NULL;\n\n\t// be generous: use 16 byte alignment\n\tif( fr->buffer.rdata == NULL )\n\t\tfr->buffer.rdata = (byte *)malloc( fr->buffer.size + 15 );\n\n\tif( fr->buffer.rdata == NULL )\n\t{\n\t\tfr->err = MPG123_OUT_OF_MEM;\n\t\treturn MPG123_ERR;\n\t}\n\n\tfr->buffer.data = aligned_pointer( fr->buffer.rdata, 16 );\n\tfr->own_buffer = TRUE;\n\tfr->buffer.fill = 0;\n\n\treturn MPG123_OK;\n}\n\nstatic void frame_free_toc( mpg123_handle_t *fr )\n{\n\tif( fr->xing_toc != NULL )\n\t{\n\t\tfree( fr->xing_toc );\n\t\tfr->xing_toc = NULL;\n\t}\n}\n\n// Just copy the Xing TOC over...\nint frame_fill_toc( mpg123_handle_t *fr, byte *in )\n{\n\tif( fr->xing_toc == NULL )\n\t\tfr->xing_toc = malloc( 100 );\n\n\tif( fr->xing_toc != NULL )\n\t{\n\t\tmemcpy( fr->xing_toc, in, 100 );\n\t\treturn TRUE;\n\t}\n\n\treturn FALSE;\n}\n\n// prepare the handle for a new track.\n// reset variables, buffers...\nint frame_reset( mpg123_handle_t *fr )\n{\n\tframe_buffers_reset( fr );\n\tframe_fixed_reset( fr );\n\tframe_free_toc( fr );\n\tfi_reset( &fr->index );\n\n\treturn 0;\n}\n\nstatic void frame_free_buffers( mpg123_handle_t *fr )\n{\n\tif( fr->rawbuffs != NULL )\n\t\tfree( fr->rawbuffs );\n\tfr->rawbuffs = NULL;\n\tfr->rawbuffss = 0;\n\n\tif( fr->rawdecwin != NULL )\n\t\tfree( fr->rawdecwin );\n\tfr->rawdecwin = NULL;\n\tfr->rawdecwins = 0;\n\n\tif( fr->layerscratch != NULL )\n\t\tfree( fr->layerscratch );\n}\n\nvoid frame_exit( mpg123_handle_t *fr )\n{\n\tif( fr->buffer.rdata != NULL )\n\t\tfree( fr->buffer.rdata );\n\n\tfr->buffer.rdata = NULL;\n\tframe_free_buffers( fr );\n\tframe_free_toc( fr );\n\tfi_exit( &fr->index );\n\n\t// clean up possible mess from LFS wrapper.\n\tif( fr->wrapperclean != NULL )\n\t{\n\t\tfr->wrapperclean( fr->wrapperdata );\n\t\tfr->wrapperdata = NULL;\n\t}\n\n\tbc_cleanup( &fr->rdat.buffer );\n}\n\nstatic int mpg123_framedata( mpg123_handle_t *mh, ulong *header, byte **bodydata, size_t *bodybytes )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tif( !mh->to_decode )\n\t\treturn MPG123_ERR;\n\n\tif( header != NULL )\n\t\t*header = mh->oldhead;\n\n\tif( bodydata != NULL )\n\t\t*bodydata  = mh->bsbuf;\n\n\tif( bodybytes != NULL )\n\t\t*bodybytes = mh->framesize;\n\n\treturn MPG123_OK;\n}\n\n// Fuzzy frame offset searching (guessing).\n// When we don't have an accurate position, we may use an inaccurate one.\n// Possibilities:\n//\t- use approximate positions from Xing TOC (not yet parsed)\n//\t- guess wildly from mean framesize and offset of first frame / beginning of file.\nstatic mpg_off_t frame_fuzzy_find( mpg123_handle_t *fr, mpg_off_t want_frame, mpg_off_t *get_frame )\n{\n\tmpg_off_t\tret = fr->audio_start; // default is to go to the beginning.\n\n\t*get_frame = 0;\n\n\t// but we try to find something better.\n\t// Xing VBR TOC works with relative positions, both in terms of audio frames and stream bytes.\n\t// thus, it only works when whe know the length of things.\n\t// oh... I assume the offsets are relative to the _total_ file length.\n\tif( fr->xing_toc != NULL && fr->track_frames > 0 && fr->rdat.filelen > 0 )\n\t{\n\t\t// one could round...\n\t\tint toc_entry = (int)((double)want_frame * 100.0 / fr->track_frames );\n\n\t\t// it is an index in the 100-entry table.\n\t\tif( toc_entry < 0 ) toc_entry = 0;\n\t\tif( toc_entry > 99 ) toc_entry = 99;\n\n\t\t// now estimate back what frame we get.\n\t\t*get_frame = (mpg_off_t)((double)toc_entry / 100.0 * fr->track_frames );\n\t\tfr->state_flags &= ~FRAME_ACCURATE;\n\t\tfr->silent_resync = 1;\n\n\t\t// question: Is the TOC for whole file size (with/without ID3) or the \"real\" audio data only?\n\t\t// ID3v1 info could also matter.\n\t\tret = (mpg_off_t)((double)fr->xing_toc[toc_entry] / 256.0 * fr->rdat.filelen);\n\t}\n\telse if( fr->mean_framesize > 0 )\n\t{\n\t\t// just guess with mean framesize (may be exact with CBR files).\n\t\t// query filelen here or not?\n\t\tfr->state_flags &= ~FRAME_ACCURATE; // fuzzy!\n\t\tfr->silent_resync = 1;\n\t\t*get_frame = want_frame;\n\t\tret = (mpg_off_t)(fr->audio_start + fr->mean_framesize * want_frame);\n\t}\n\n\treturn ret;\n}\n\n// find the best frame in index just before the wanted one, seek to there\n// then step to just before wanted one with read_frame\n// do not care tabout the stuff that was in buffer but not played back\n// everything that left the decoder is counted as played\n// decide if you want low latency reaction and accurate timing info or stable long-time playback with buffer!\nmpg_off_t frame_index_find( mpg123_handle_t *fr, mpg_off_t want_frame, mpg_off_t* get_frame )\n{\n\tmpg_off_t\tgopos = 0; // default is file start if no index position\n\n\t*get_frame = 0;\n\n\t// possibly use VBRI index, too? I'd need an example for this...\n\tif( fr->index.fill )\n\t{\n\t\tsize_t\tfi; // find in index\n\n\t\t// at index fi there is frame step*fi...\n\t\tfi = want_frame / fr->index.step;\n\n\t\tif( fi >= fr->index.fill )\n\t\t{\n\t\t\t// if we are beyond the end of frame index...\n\t\t\t// when fuzzy seek is allowed, we have some limited tolerance for the frames we want to read rather then jump over.\n\t\t\tif( fr->p.flags & MPG123_FUZZY && want_frame - (fr->index.fill- 1) * fr->index.step > 10 )\n\t\t\t{\n\t\t\t\tgopos = frame_fuzzy_find( fr, want_frame, get_frame );\n\t\t\t\tif( gopos > fr->audio_start )\n\t\t\t\t\treturn gopos; // only in that case, we have a useful guess.\n\t\t\t\t// else... just continue, fuzzyness didn't help.\n\t\t\t}\n\n\t\t\t// use the last available position, slowly advancing from that one.\n\t\t\tfi = fr->index.fill - 1;\n\t\t}\n\n\t\t// we have index position, that yields frame and byte offsets.\n\t\t*get_frame = fi * fr->index.step;\n\t\tgopos = fr->index.data[fi];\n\t\tfr->state_flags |= FRAME_ACCURATE; // when using the frame index, we are accurate.\n\t}\n\telse\n\t{\n\t\tif( fr->p.flags & MPG123_FUZZY )\n\t\t\treturn frame_fuzzy_find( fr, want_frame, get_frame );\n\n\t\t// a bit hackish here... but we need to be fresh when looking for the first header again.\n\t\tfr->firsthead = 0;\n\t\tfr->oldhead = 0;\n\t}\n\n\treturn gopos;\n}\n\nstatic mpg_off_t frame_ins2outs( mpg123_handle_t *fr, mpg_off_t ins )\n{\n\tmpg_off_t\touts = 0;\n\n\tswitch( fr->down_sample )\n\t{\n\tcase 0:\n\t\touts = ins >> fr->down_sample;\n\t\tbreak;\n\tdefault:\tbreak;\n\t}\n\n\treturn outs;\n}\n\nmpg_off_t frame_outs( mpg123_handle_t *fr, mpg_off_t num )\n{\n\tmpg_off_t\touts = 0;\n\n\tswitch( fr->down_sample )\n\t{\n\tcase 0:\n\t\touts = (fr->spf >> fr->down_sample) * num;\n\t\tbreak;\n\tdefault:\tbreak;\n\t}\n\n\treturn outs;\n}\n\n// compute the number of output samples we expect from this frame.\n// this is either simple spf() or a tad more elaborate for ntom.\nmpg_off_t frame_expect_outsamples( mpg123_handle_t *fr )\n{\n\tmpg_off_t\touts = 0;\n\n\tswitch( fr->down_sample )\n\t{\n\tcase 0:\n\t\touts = fr->spf >> fr->down_sample;\n\t\tbreak;\n\tdefault:\tbreak;\n\t}\n\n\treturn outs;\n}\n\nmpg_off_t frame_offset( mpg123_handle_t *fr, mpg_off_t outs )\n{\n\tmpg_off_t\tnum = 0;\n\n\tswitch( fr->down_sample )\n\t{\n\tcase 0:\n\t\tnum = outs / (fr->spf >> fr->down_sample);\n\t\tbreak;\n\tdefault:\tbreak;\n\t}\n\n\treturn num;\n}\n\n// input in _input_ samples\nvoid frame_gapless_init( mpg123_handle_t *fr, mpg_off_t framecount, mpg_off_t bskip, mpg_off_t eskip )\n{\n\tfr->gapless_frames = framecount;\n\n\tif( fr->gapless_frames > 0 && bskip >= 0 && eskip >= 0 )\n\t{\n\t\tfr->begin_s = bskip + GAPLESS_DELAY;\n\t\tfr->end_s = framecount * fr->spf - eskip + GAPLESS_DELAY;\n\t}\n\telse fr->begin_s = fr->end_s = 0;\n\n\t// these will get proper values later, from above plus resampling info.\n\tfr->begin_os = 0;\n\tfr->end_os = 0;\n\tfr->fullend_os = 0;\n}\n\nvoid frame_gapless_realinit( mpg123_handle_t *fr )\n{\n\tfr->begin_os = frame_ins2outs( fr, fr->begin_s );\n\tfr->end_os = frame_ins2outs( fr, fr->end_s );\n\n\tif( fr->gapless_frames > 0 )\n\t\tfr->fullend_os = frame_ins2outs( fr, fr->gapless_frames * fr->spf );\n\telse fr->fullend_os = 0;\n}\n\n// at least note when there is trouble...\nvoid frame_gapless_update( mpg123_handle_t *fr, mpg_off_t total_samples )\n{\n\tmpg_off_t gapless_samples = fr->gapless_frames * fr->spf;\n\n\tif( fr->gapless_frames < 1 )\n\t\treturn;\n\n\tif( gapless_samples > total_samples )\n\t{\n\t\t// This invalidates the current position... but what should I do?\n\t\tframe_gapless_init( fr, -1, 0, 0 );\n\t\tframe_gapless_realinit( fr );\n\t\tfr->lastframe = -1;\n\t\tfr->lastoff = 0;\n\t}\n}\n\n// compute the needed frame to ignore from, for getting accurate/consistent output for intended firstframe.\nstatic mpg_off_t ignoreframe( mpg123_handle_t *fr )\n{\n\tmpg_off_t\tpreshift = fr->p.preframes;\n\n\t// layer 3 _really_ needs at least one frame before.\n\tif( fr->lay == 3 && preshift < 1 )\n\t\tpreshift = 1;\n\n\t// layer 1 & 2 really do not need more than 2.\n\tif(fr->lay != 3 && preshift > 2 )\n\t\tpreshift = 2;\n\n\treturn fr->firstframe - preshift;\n}\n\n// the frame seek... this is not simply the seek to fe * fr->spf samples in output because we think of _input_ frames here.\n// seek to frame offset 1 may be just seek to 200 samples offset in output since the beginning of first frame is delay/padding.\n// hm, is that right? OK for the padding stuff, but actually, should the decoder delay be better totally hidden or not?\n// with gapless, even the whole frame position could be advanced further than requested (since Homey don't play dat).\nvoid frame_set_frameseek( mpg123_handle_t *fr, mpg_off_t fe )\n{\n\tfr->firstframe = fe;\n\n\tif( fr->p.flags & MPG123_GAPLESS && fr->gapless_frames > 0 )\n\t{\n\t\t// take care of the beginning...\n\t\tmpg_off_t\tbeg_f = frame_offset( fr, fr->begin_os );\n\n\t\tif( fe <= beg_f )\n\t\t{\n\t\t\tfr->firstframe = beg_f;\n\t\t\tfr->firstoff = fr->begin_os - frame_outs( fr, beg_f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfr->firstoff = 0;\n\t\t}\n\n\t\t// the end is set once for a track at least, on the frame_set_frameseek called in get_next_frame()\n\t\tif( fr->end_os > 0 )\n\t\t{\n\t\t\tfr->lastframe = frame_offset( fr, fr->end_os );\n\t\t\tfr->lastoff = fr->end_os - frame_outs( fr, fr->lastframe );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfr->lastframe = -1;\n\t\t\tfr->lastoff = 0;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfr->firstoff = fr->lastoff = 0;\n\t\tfr->lastframe = -1;\n\t}\n\n\tfr->ignoreframe = ignoreframe( fr );\n}\n\nvoid frame_skip( mpg123_handle_t *fr )\n{\n\tif( fr->lay == 3 )\n\t\tset_pointer( fr, 512 );\n}\n\n// sample accurate seek prepare for decoder.\n// this gets unadjusted output samples and takes resampling into account\nvoid frame_set_seek( mpg123_handle_t *fr, mpg_off_t sp )\n{\n\tfr->firstframe = frame_offset( fr, sp );\n\tfr->ignoreframe = ignoreframe( fr );\n\tfr->firstoff = sp - frame_outs( fr, fr->firstframe );\n}\n\nstatic int get_rva( mpg123_handle_t *fr, double *peak, double *gain )\n{\n\tdouble\tp = -1;\n\tdouble\tg = 0;\n\tint\tret = 0;\n\n\tif( fr->p.rva )\n\t{\n\t\tint\trt = 0;\n\n\t\t// should one assume a zero RVA as no RVA?\n\t\tif( fr->p.rva == 2 && fr->rva.level[1] != -1 )\n\t\t\trt = 1;\n\n\t\tif( fr->rva.level[rt] != -1 )\n\t\t{\n\t\t\tp = fr->rva.peak[rt];\n\t\t\tg = fr->rva.gain[rt];\n\t\t\tret = 1; // success.\n\t\t}\n\t}\n\n\tif( peak != NULL ) *peak = p;\n\tif( gain != NULL ) *gain = g;\n\n\treturn ret;\n}\n\n// adjust the volume, taking both fr->outscale and rva values into account\nvoid do_rva( mpg123_handle_t *fr )\n{\n\tdouble\tpeak = 0;\n\tdouble\tgain = 0;\n\tdouble\tnewscale;\n\tdouble\trvafact = 1;\n\n\tif( get_rva( fr, &peak, &gain ))\n\t\trvafact = pow( 10, gain / 20 );\n\n\tnewscale = fr->p.outscale * rvafact;\n\n\t// if peak is unknown (== 0) this check won't hurt\n\tif(( peak * newscale ) > 1.0 )\n\t\tnewscale = 1.0 / peak;\n\n\t// first rva setting is forced with fr->lastscale < 0\n\tif( newscale != fr->lastscale || fr->decoder_change )\n\t{\n\t\tfr->lastscale = newscale;\n\t\t// it may be too early, actually.\n\t\tif( fr->make_decode_tables != NULL )\n\t\t\tfr->make_decode_tables( fr ); // the actual work\n\t}\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/frame.h",
    "content": "/*\nframe.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef FRAME_H\n#define FRAME_H\n\n#define MAXFRAMESIZE\t3456\t// max = 1728\n#define NUM_CHANNELS\t2\n\ntypedef struct\n{\n\tshort\t\tbits;\n\tshort\t\td;\n} al_table_t;\n\n// the output buffer, used to be pcm_sample, pcm_point and audiobufsize\ntypedef struct outbuffer_s\n{\n\tbyte\t\t*data;\t\t// main data pointer, aligned\n\tbyte\t\t*p;\t\t// read pointer\n\tsize_t\t\tfill;\t\t// fill from read pointer\n\tsize_t\t\tsize;\n\tbyte\t\t*rdata;\t\t// unaligned base pointer\n} outbuffer_t;\n\ntypedef struct audioformat_s\n{\n\tint\t\tencoding;\t\t// final encoding, after post-processing.\n\tint\t\tencsize;\t\t// size of one sample in bytes, plain int should be fine here...\n\tint\t\tdec_enc;  \t// encoding of decoder synth.\n\tint\t\tdec_encsize;\t// size of one decoder sample.\n\tint\t\tchannels;\n\tint\t\trate;\n} audioformat_t;\n\ntypedef struct mpg123_parm_s\n{\n\tint\t\tverbose;\t\t// verbose level\n\tlong\t\tflags;\t\t// combination of above\n\tint\t\tdown_sample;\n\tint\t\trva;\t\t// (which) rva to do: 0: nothing, 1: radio/mix/track 2: album/audiophile\n\tlong\t\thalfspeed;\n\tlong\t\tdoublespeed;\n\tlong\t\ttimeout;\n\n\tchar\t\taudio_caps[NUM_CHANNELS][MPG123_RATES+1][MPG123_ENCODINGS];\n\tdouble\t\toutscale;\n\tlong\t\tresync_limit;\n\tlong\t\tindex_size;\t// Long, because: negative values have a meaning.\n\tlong\t\tpreframes;\n\tlong\t\tfeedpool;\n\tlong\t\tfeedbuffer;\n} mpg123_parm_t;\n\n// generic init, does not include dynamic buffers\nvoid frame_init( mpg123_handle_t *fr );\nvoid frame_init_par( mpg123_handle_t *fr, mpg123_parm_t *mp );\nint frame_outbuffer( mpg123_handle_t *fr );\nint frame_output_format( mpg123_handle_t *fr );\nint frame_buffers( mpg123_handle_t *fr );\nint frame_reset( mpg123_handle_t *fr );\nint frame_buffers_reset( mpg123_handle_t *fr );\nvoid frame_exit( mpg123_handle_t *fr );\nint frame_index_setup( mpg123_handle_t *fr );\nmpg_off_t frame_expect_outsamples( mpg123_handle_t *fr );\nmpg_off_t frame_offset( mpg123_handle_t *fr, mpg_off_t outs );\nvoid frame_gapless_init( mpg123_handle_t *fr, mpg_off_t framecount, mpg_off_t bskip, mpg_off_t eskip );\nvoid frame_gapless_realinit( mpg123_handle_t *fr );\nvoid frame_gapless_update( mpg123_handle_t *fr, mpg_off_t total_samples );\nmpg_off_t frame_index_find( mpg123_handle_t *fr, mpg_off_t want_frame, mpg_off_t* get_frame );\nmpg_off_t frame_outs( mpg123_handle_t *fr, mpg_off_t num );\nvoid frame_set_seek( mpg123_handle_t *fr, mpg_off_t sp );\nvoid frame_set_frameseek( mpg123_handle_t *fr, mpg_off_t fe );\nint frame_fill_toc( mpg123_handle_t *fr, byte *in );\nvoid frame_skip( mpg123_handle_t *fr );\nvoid do_rva( mpg123_handle_t *fr );\n\n#endif//FRAME_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/getbits.h",
    "content": "/*\ngetbits.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GETBITS_H\n#define GETBITS_H\n\n#define backbits( fr, nob )\t((void)( \\\n\tfr->bitindex -= nob, \\\n\tfr->wordpointer += (fr->bitindex>>3), \\\n\tfr->bitindex &= 0x7 ))\n\n#define getbitoffset( fr )\t((-fr->bitindex) & 0x7)\n#define getbyte( fr )\t(*fr->wordpointer++)\n\n#define skipbits( fr, nob )\tfr->ultmp = ( \\\n\tfr->ultmp = fr->wordpointer[0], fr->ultmp <<= 8, fr->ultmp |= fr->wordpointer[1], \\\n\tfr->ultmp <<= 8, fr->ultmp |= fr->wordpointer[2], fr->ultmp <<= fr->bitindex, \\\n\tfr->ultmp &= 0xffffff, fr->bitindex += nob, \\\n\tfr->ultmp >>= (24-nob), fr->wordpointer += (fr->bitindex>>3), \\\n\tfr->bitindex &= 7 )\n\n#define getbits_fast( fr, nob )( \\\n\tfr->ultmp = (byte) (fr->wordpointer[0] << fr->bitindex), \\\n\tfr->ultmp |= ((ulong)fr->wordpointer[1] << fr->bitindex) >> 8, \\\n\tfr->ultmp <<= nob, fr->ultmp >>= 8, \\\n\tfr->bitindex += nob, fr->wordpointer += (fr->bitindex >> 3), \\\n\tfr->bitindex &= 7, fr->ultmp )\n\n#define get1bit( fr )\t( \\\n\tfr->uctmp = *fr->wordpointer << fr->bitindex, fr->bitindex++, \\\n\tfr->wordpointer += (fr->bitindex >> 3), fr->bitindex &= 7, fr->uctmp >> 7 )\n\n\nstatic uint getbits( mpg123_handle_t *fr, int number_of_bits )\n{\n\tulong\trval;\n\n\tif( (long)(fr->wordpointer-fr->bsbuf)*8\n\t    + fr->bitindex+number_of_bits > (long)fr->framesize*8 )\n\t\treturn 0;\n\n\trval = fr->wordpointer[0];\n\trval <<= 8;\n\trval |= fr->wordpointer[1];\n\trval <<= 8;\n\trval |= fr->wordpointer[2];\n\n\trval <<= fr->bitindex;\n\trval &= 0xffffff;\n\n\tfr->bitindex += number_of_bits;\n\trval >>= (24-number_of_bits);\n\n\tfr->wordpointer += (fr->bitindex>>3);\n\tfr->bitindex &= 7;\n\n\treturn rval;\n}\n\n#endif//GETBITS_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/huffman.h",
    "content": "/*\n\thuffman.h: huffman tables ... recalcualted to work with optimized decoder scheme (MH)\n\n\tcopyright ?-2006 by the mpg123 project - free software under the terms of the LGPL 2.1\n\tsee COPYING and AUTHORS files in distribution or http://mpg123.org\n\tinitially written by Michael Hipp\n\n\tprobably we could save a few bytes of memory, because the\n\tsmaller tables are often the part of a bigger table\n*/\n\n\n#ifndef HUFFMAN_H_\n#define HUFFMAN_H_\n\nstruct newhuff\n{\n\tuint\t\tlinbits;\n\tconst short\t*table;\n};\n\nstatic const short tab0[] =\n{\n   0\n};\n\nstatic const short tab1[] =\n{\n  -5,  -3,  -1,  17,   1,  16,   0\n};\n\nstatic const short tab2[] =\n{\n -15, -11,  -9,  -5,  -3,  -1,  34,   2,  18,  -1,  33,  32,  17,  -1,   1,\n  16,   0\n};\n\nstatic const short tab3[] =\n{\n -13, -11,  -9,  -5,  -3,  -1,  34,   2,  18,  -1,  33,  32,  16,  17,  -1,\n   1,   0\n};\n\nstatic const short tab5[] =\n{\n -29, -25, -23, -15,  -7,  -5,  -3,  -1,  51,  35,  50,  49,  -3,  -1,  19,\n   3,  -1,  48,  34,  -3,  -1,  18,  33,  -1,   2,  32,  17,  -1,   1,  16,\n   0\n};\n\nstatic const short tab6[] =\n{\n -25, -19, -13,  -9,  -5,  -3,  -1,  51,   3,  35,  -1,  50,  48,  -1,  19,\n  49,  -3,  -1,  34,   2,  18,  -3,  -1,  33,  32,   1,  -1,  17,  -1,  16,\n   0\n};\n\nstatic const short tab7[] =\n{\n -69, -65, -57, -39, -29, -17, -11,  -7,  -3,  -1,  85,  69,  -1,  84,  83,\n  -1,  53,  68,  -3,  -1,  37,  82,  21,  -5,  -1,  81,  -1,   5,  52,  -1,\n  80,  -1,  67,  51,  -5,  -3,  -1,  36,  66,  20,  -1,  65,  64, -11,  -7,\n  -3,  -1,   4,  35,  -1,  50,   3,  -1,  19,  49,  -3,  -1,  48,  34,  18,\n  -5,  -1,  33,  -1,   2,  32,  17,  -1,   1,  16,   0\n};\n\nstatic const short tab8[] =\n{\n -65, -63, -59, -45, -31, -19, -13,  -7,  -5,  -3,  -1,  85,  84,  69,  83,\n  -3,  -1,  53,  68,  37,  -3,  -1,  82,   5,  21,  -5,  -1,  81,  -1,  52,\n  67,  -3,  -1,  80,  51,  36,  -5,  -3,  -1,  66,  20,  65,  -3,  -1,   4,\n  64,  -1,  35,  50,  -9,  -7,  -3,  -1,  19,  49,  -1,   3,  48,  34,  -1,\n   2,  32,  -1,  18,  33,  17,  -3,  -1,   1,  16,   0\n};\n\nstatic const short tab9[] =\n{\n -63, -53, -41, -29, -19, -11,  -5,  -3,  -1,  85,  69,  53,  -1,  83,  -1,\n  84,   5,  -3,  -1,  68,  37,  -1,  82,  21,  -3,  -1,  81,  52,  -1,  67,\n  -1,  80,   4,  -7,  -3,  -1,  36,  66,  -1,  51,  64,  -1,  20,  65,  -5,\n  -3,  -1,  35,  50,  19,  -1,  49,  -1,   3,  48,  -5,  -3,  -1,  34,   2,\n  18,  -1,  33,  32,  -3,  -1,  17,   1,  -1,  16,   0\n};\n\nstatic const short tab10[] =\n{\n-125,-121,-111, -83, -55, -35, -21, -13,  -7,  -3,  -1, 119, 103,  -1, 118,\n  87,  -3,  -1, 117, 102,  71,  -3,  -1, 116,  86,  -1, 101,  55,  -9,  -3,\n  -1, 115,  70,  -3,  -1,  85,  84,  99,  -1,  39, 114, -11,  -5,  -3,  -1,\n 100,   7, 112,  -1,  98,  -1,  69,  53,  -5,  -1,   6,  -1,  83,  68,  23,\n -17,  -5,  -1, 113,  -1,  54,  38,  -5,  -3,  -1,  37,  82,  21,  -1,  81,\n  -1,  52,  67,  -3,  -1,  22,  97,  -1,  96,  -1,   5,  80, -19, -11,  -7,\n  -3,  -1,  36,  66,  -1,  51,   4,  -1,  20,  65,  -3,  -1,  64,  35,  -1,\n  50,   3,  -3,  -1,  19,  49,  -1,  48,  34,  -7,  -3,  -1,  18,  33,  -1,\n   2,  32,  17,  -1,   1,  16,   0\n};\n\nstatic const short tab11[] =\n{\n-121,-113, -89, -59, -43, -27, -17,  -7,  -3,  -1, 119, 103,  -1, 118, 117,\n  -3,  -1, 102,  71,  -1, 116,  -1,  87,  85,  -5,  -3,  -1,  86, 101,  55,\n  -1, 115,  70,  -9,  -7,  -3,  -1,  69,  84,  -1,  53,  83,  39,  -1, 114,\n  -1, 100,   7,  -5,  -1, 113,  -1,  23, 112,  -3,  -1,  54,  99,  -1,  96,\n  -1,  68,  37, -13,  -7,  -5,  -3,  -1,  82,   5,  21,  98,  -3,  -1,  38,\n   6,  22,  -5,  -1,  97,  -1,  81,  52,  -5,  -1,  80,  -1,  67,  51,  -1,\n  36,  66, -15, -11,  -7,  -3,  -1,  20,  65,  -1,   4,  64,  -1,  35,  50,\n  -1,  19,  49,  -5,  -3,  -1,   3,  48,  34,  33,  -5,  -1,  18,  -1,   2,\n  32,  17,  -3,  -1,   1,  16,   0\n};\n\nstatic const short tab12[] =\n{\n-115, -99, -73, -45, -27, -17,  -9,  -5,  -3,  -1, 119, 103, 118,  -1,  87,\n 117,  -3,  -1, 102,  71,  -1, 116, 101,  -3,  -1,  86,  55,  -3,  -1, 115,\n  85,  39,  -7,  -3,  -1, 114,  70,  -1, 100,  23,  -5,  -1, 113,  -1,   7,\n 112,  -1,  54,  99, -13,  -9,  -3,  -1,  69,  84,  -1,  68,  -1,   6,   5,\n  -1,  38,  98,  -5,  -1,  97,  -1,  22,  96,  -3,  -1,  53,  83,  -1,  37,\n  82, -17,  -7,  -3,  -1,  21,  81,  -1,  52,  67,  -5,  -3,  -1,  80,   4,\n  36,  -1,  66,  20,  -3,  -1,  51,  65,  -1,  35,  50, -11,  -7,  -5,  -3,\n  -1,  64,   3,  48,  19,  -1,  49,  34,  -1,  18,  33,  -7,  -5,  -3,  -1,\n   2,  32,   0,  17,  -1,   1,  16\n};\n\nstatic const short tab13[] =\n{\n-509,-503,-475,-405,-333,-265,-205,-153,-115, -83, -53, -35, -21, -13,  -9,\n  -7,  -5,  -3,  -1, 254, 252, 253, 237, 255,  -1, 239, 223,  -3,  -1, 238,\n 207,  -1, 222, 191,  -9,  -3,  -1, 251, 206,  -1, 220,  -1, 175, 233,  -1,\n 236, 221,  -9,  -5,  -3,  -1, 250, 205, 190,  -1, 235, 159,  -3,  -1, 249,\n 234,  -1, 189, 219, -17,  -9,  -3,  -1, 143, 248,  -1, 204,  -1, 174, 158,\n  -5,  -1, 142,  -1, 127, 126, 247,  -5,  -1, 218,  -1, 173, 188,  -3,  -1,\n 203, 246, 111, -15,  -7,  -3,  -1, 232,  95,  -1, 157, 217,  -3,  -1, 245,\n 231,  -1, 172, 187,  -9,  -3,  -1,  79, 244,  -3,  -1, 202, 230, 243,  -1,\n  63,  -1, 141, 216, -21,  -9,  -3,  -1,  47, 242,  -3,  -1, 110, 156,  15,\n  -5,  -3,  -1, 201,  94, 171,  -3,  -1, 125, 215,  78, -11,  -5,  -3,  -1,\n 200, 214,  62,  -1, 185,  -1, 155, 170,  -1,  31, 241, -23, -13,  -5,  -1,\n 240,  -1, 186, 229,  -3,  -1, 228, 140,  -1, 109, 227,  -5,  -1, 226,  -1,\n  46,  14,  -1,  30, 225, -15,  -7,  -3,  -1, 224,  93,  -1, 213, 124,  -3,\n  -1, 199,  77,  -1, 139, 184,  -7,  -3,  -1, 212, 154,  -1, 169, 108,  -1,\n 198,  61, -37, -21,  -9,  -5,  -3,  -1, 211, 123,  45,  -1, 210,  29,  -5,\n  -1, 183,  -1,  92, 197,  -3,  -1, 153, 122, 195,  -7,  -5,  -3,  -1, 167,\n 151,  75, 209,  -3,  -1,  13, 208,  -1, 138, 168, -11,  -7,  -3,  -1,  76,\n 196,  -1, 107, 182,  -1,  60,  44,  -3,  -1, 194,  91,  -3,  -1, 181, 137,\n  28, -43, -23, -11,  -5,  -1, 193,  -1, 152,  12,  -1, 192,  -1, 180, 106,\n  -5,  -3,  -1, 166, 121,  59,  -1, 179,  -1, 136,  90, -11,  -5,  -1,  43,\n  -1, 165, 105,  -1, 164,  -1, 120, 135,  -5,  -1, 148,  -1, 119, 118, 178,\n -11,  -3,  -1,  27, 177,  -3,  -1,  11, 176,  -1, 150,  74,  -7,  -3,  -1,\n  58, 163,  -1,  89, 149,  -1,  42, 162, -47, -23,  -9,  -3,  -1,  26, 161,\n  -3,  -1,  10, 104, 160,  -5,  -3,  -1, 134,  73, 147,  -3,  -1,  57,  88,\n  -1, 133, 103,  -9,  -3,  -1,  41, 146,  -3,  -1,  87, 117,  56,  -5,  -1,\n 131,  -1, 102,  71,  -3,  -1, 116,  86,  -1, 101, 115, -11,  -3,  -1,  25,\n 145,  -3,  -1,   9, 144,  -1,  72, 132,  -7,  -5,  -1, 114,  -1,  70, 100,\n  40,  -1, 130,  24, -41, -27, -11,  -5,  -3,  -1,  55,  39,  23,  -1, 113,\n  -1,  85,   7,  -7,  -3,  -1, 112,  54,  -1,  99,  69,  -3,  -1,  84,  38,\n  -1,  98,  53,  -5,  -1, 129,  -1,   8, 128,  -3,  -1,  22,  97,  -1,   6,\n  96, -13,  -9,  -5,  -3,  -1,  83,  68,  37,  -1,  82,   5,  -1,  21,  81,\n  -7,  -3,  -1,  52,  67,  -1,  80,  36,  -3,  -1,  66,  51,  20, -19, -11,\n  -5,  -1,  65,  -1,   4,  64,  -3,  -1,  35,  50,  19,  -3,  -1,  49,   3,\n  -1,  48,  34,  -3,  -1,  18,  33,  -1,   2,  32,  -3,  -1,  17,   1,  16,\n   0\n};\n\nstatic const short tab15[] =\n{\n-495,-445,-355,-263,-183,-115, -77, -43, -27, -13,  -7,  -3,  -1, 255, 239,\n  -1, 254, 223,  -1, 238,  -1, 253, 207,  -7,  -3,  -1, 252, 222,  -1, 237,\n 191,  -1, 251,  -1, 206, 236,  -7,  -3,  -1, 221, 175,  -1, 250, 190,  -3,\n  -1, 235, 205,  -1, 220, 159, -15,  -7,  -3,  -1, 249, 234,  -1, 189, 219,\n  -3,  -1, 143, 248,  -1, 204, 158,  -7,  -3,  -1, 233, 127,  -1, 247, 173,\n  -3,  -1, 218, 188,  -1, 111,  -1, 174,  15, -19, -11,  -3,  -1, 203, 246,\n  -3,  -1, 142, 232,  -1,  95, 157,  -3,  -1, 245, 126,  -1, 231, 172,  -9,\n  -3,  -1, 202, 187,  -3,  -1, 217, 141,  79,  -3,  -1, 244,  63,  -1, 243,\n 216, -33, -17,  -9,  -3,  -1, 230,  47,  -1, 242,  -1, 110, 240,  -3,  -1,\n  31, 241,  -1, 156, 201,  -7,  -3,  -1,  94, 171,  -1, 186, 229,  -3,  -1,\n 125, 215,  -1,  78, 228, -15,  -7,  -3,  -1, 140, 200,  -1,  62, 109,  -3,\n  -1, 214, 227,  -1, 155, 185,  -7,  -3,  -1,  46, 170,  -1, 226,  30,  -5,\n  -1, 225,  -1,  14, 224,  -1,  93, 213, -45, -25, -13,  -7,  -3,  -1, 124,\n 199,  -1,  77, 139,  -1, 212,  -1, 184, 154,  -7,  -3,  -1, 169, 108,  -1,\n 198,  61,  -1, 211, 210,  -9,  -5,  -3,  -1,  45,  13,  29,  -1, 123, 183,\n  -5,  -1, 209,  -1,  92, 208,  -1, 197, 138, -17,  -7,  -3,  -1, 168,  76,\n  -1, 196, 107,  -5,  -1, 182,  -1, 153,  12,  -1,  60, 195,  -9,  -3,  -1,\n 122, 167,  -1, 166,  -1, 192,  11,  -1, 194,  -1,  44,  91, -55, -29, -15,\n  -7,  -3,  -1, 181,  28,  -1, 137, 152,  -3,  -1, 193,  75,  -1, 180, 106,\n  -5,  -3,  -1,  59, 121, 179,  -3,  -1, 151, 136,  -1,  43,  90, -11,  -5,\n  -1, 178,  -1, 165,  27,  -1, 177,  -1, 176, 105,  -7,  -3,  -1, 150,  74,\n  -1, 164, 120,  -3,  -1, 135,  58, 163, -17,  -7,  -3,  -1,  89, 149,  -1,\n  42, 162,  -3,  -1,  26, 161,  -3,  -1,  10, 160, 104,  -7,  -3,  -1, 134,\n  73,  -1, 148,  57,  -5,  -1, 147,  -1, 119,   9,  -1,  88, 133, -53, -29,\n -13,  -7,  -3,  -1,  41, 103,  -1, 118, 146,  -1, 145,  -1,  25, 144,  -7,\n  -3,  -1,  72, 132,  -1,  87, 117,  -3,  -1,  56, 131,  -1, 102,  71,  -7,\n  -3,  -1,  40, 130,  -1,  24, 129,  -7,  -3,  -1, 116,   8,  -1, 128,  86,\n  -3,  -1, 101,  55,  -1, 115,  70, -17,  -7,  -3,  -1,  39, 114,  -1, 100,\n  23,  -3,  -1,  85, 113,  -3,  -1,   7, 112,  54,  -7,  -3,  -1,  99,  69,\n  -1,  84,  38,  -3,  -1,  98,  22,  -3,  -1,   6,  96,  53, -33, -19,  -9,\n  -5,  -1,  97,  -1,  83,  68,  -1,  37,  82,  -3,  -1,  21,  81,  -3,  -1,\n   5,  80,  52,  -7,  -3,  -1,  67,  36,  -1,  66,  51,  -1,  65,  -1,  20,\n   4,  -9,  -3,  -1,  35,  50,  -3,  -1,  64,   3,  19,  -3,  -1,  49,  48,\n  34,  -9,  -7,  -3,  -1,  18,  33,  -1,   2,  32,  17,  -3,  -1,   1,  16,\n   0\n};\n\nstatic const short tab16[] =\n{\n-509,-503,-461,-323,-103, -37, -27, -15,  -7,  -3,  -1, 239, 254,  -1, 223,\n 253,  -3,  -1, 207, 252,  -1, 191, 251,  -5,  -1, 175,  -1, 250, 159,  -3,\n  -1, 249, 248, 143,  -7,  -3,  -1, 127, 247,  -1, 111, 246, 255,  -9,  -5,\n  -3,  -1,  95, 245,  79,  -1, 244, 243, -53,  -1, 240,  -1,  63, -29, -19,\n -13,  -7,  -5,  -1, 206,  -1, 236, 221, 222,  -1, 233,  -1, 234, 217,  -1,\n 238,  -1, 237, 235,  -3,  -1, 190, 205,  -3,  -1, 220, 219, 174, -11,  -5,\n  -1, 204,  -1, 173, 218,  -3,  -1, 126, 172, 202,  -5,  -3,  -1, 201, 125,\n  94, 189, 242, -93,  -5,  -3,  -1,  47,  15,  31,  -1, 241, -49, -25, -13,\n  -5,  -1, 158,  -1, 188, 203,  -3,  -1, 142, 232,  -1, 157, 231,  -7,  -3,\n  -1, 187, 141,  -1, 216, 110,  -1, 230, 156, -13,  -7,  -3,  -1, 171, 186,\n  -1, 229, 215,  -1,  78,  -1, 228, 140,  -3,  -1, 200,  62,  -1, 109,  -1,\n 214, 155, -19, -11,  -5,  -3,  -1, 185, 170, 225,  -1, 212,  -1, 184, 169,\n  -5,  -1, 123,  -1, 183, 208, 227,  -7,  -3,  -1,  14, 224,  -1,  93, 213,\n  -3,  -1, 124, 199,  -1,  77, 139, -75, -45, -27, -13,  -7,  -3,  -1, 154,\n 108,  -1, 198,  61,  -3,  -1,  92, 197,  13,  -7,  -3,  -1, 138, 168,  -1,\n 153,  76,  -3,  -1, 182, 122,  60, -11,  -5,  -3,  -1,  91, 137,  28,  -1,\n 192,  -1, 152, 121,  -1, 226,  -1,  46,  30, -15,  -7,  -3,  -1, 211,  45,\n  -1, 210, 209,  -5,  -1,  59,  -1, 151, 136,  29,  -7,  -3,  -1, 196, 107,\n  -1, 195, 167,  -1,  44,  -1, 194, 181, -23, -13,  -7,  -3,  -1, 193,  12,\n  -1,  75, 180,  -3,  -1, 106, 166, 179,  -5,  -3,  -1,  90, 165,  43,  -1,\n 178,  27, -13,  -5,  -1, 177,  -1,  11, 176,  -3,  -1, 105, 150,  -1,  74,\n 164,  -5,  -3,  -1, 120, 135, 163,  -3,  -1,  58,  89,  42, -97, -57, -33,\n -19, -11,  -5,  -3,  -1, 149, 104, 161,  -3,  -1, 134, 119, 148,  -5,  -3,\n  -1,  73,  87, 103, 162,  -5,  -1,  26,  -1,  10, 160,  -3,  -1,  57, 147,\n  -1,  88, 133,  -9,  -3,  -1,  41, 146,  -3,  -1, 118,   9,  25,  -5,  -1,\n 145,  -1, 144,  72,  -3,  -1, 132, 117,  -1,  56, 131, -21, -11,  -5,  -3,\n  -1, 102,  40, 130,  -3,  -1,  71, 116,  24,  -3,  -1, 129, 128,  -3,  -1,\n   8,  86,  55,  -9,  -5,  -1, 115,  -1, 101,  70,  -1,  39, 114,  -5,  -3,\n  -1, 100,  85,   7,  23, -23, -13,  -5,  -1, 113,  -1, 112,  54,  -3,  -1,\n  99,  69,  -1,  84,  38,  -3,  -1,  98,  22,  -1,  97,  -1,   6,  96,  -9,\n  -5,  -1,  83,  -1,  53,  68,  -1,  37,  82,  -1,  81,  -1,  21,   5, -33,\n -23, -13,  -7,  -3,  -1,  52,  67,  -1,  80,  36,  -3,  -1,  66,  51,  20,\n  -5,  -1,  65,  -1,   4,  64,  -1,  35,  50,  -3,  -1,  19,  49,  -3,  -1,\n   3,  48,  34,  -3,  -1,  18,  33,  -1,   2,  32,  -3,  -1,  17,   1,  16,\n   0\n};\n\nstatic const short tab24[] =\n{\n-451,-117, -43, -25, -15,  -7,  -3,  -1, 239, 254,  -1, 223, 253,  -3,  -1,\n 207, 252,  -1, 191, 251,  -5,  -1, 250,  -1, 175, 159,  -1, 249, 248,  -9,\n  -5,  -3,  -1, 143, 127, 247,  -1, 111, 246,  -3,  -1,  95, 245,  -1,  79,\n 244, -71,  -7,  -3,  -1,  63, 243,  -1,  47, 242,  -5,  -1, 241,  -1,  31,\n 240, -25,  -9,  -1,  15,  -3,  -1, 238, 222,  -1, 237, 206,  -7,  -3,  -1,\n 236, 221,  -1, 190, 235,  -3,  -1, 205, 220,  -1, 174, 234, -15,  -7,  -3,\n  -1, 189, 219,  -1, 204, 158,  -3,  -1, 233, 173,  -1, 218, 188,  -7,  -3,\n  -1, 203, 142,  -1, 232, 157,  -3,  -1, 217, 126,  -1, 231, 172, 255,-235,\n-143, -77, -45, -25, -15,  -7,  -3,  -1, 202, 187,  -1, 141, 216,  -5,  -3,\n  -1,  14, 224,  13, 230,  -5,  -3,  -1, 110, 156, 201,  -1,  94, 186,  -9,\n  -5,  -1, 229,  -1, 171, 125,  -1, 215, 228,  -3,  -1, 140, 200,  -3,  -1,\n  78,  46,  62, -15,  -7,  -3,  -1, 109, 214,  -1, 227, 155,  -3,  -1, 185,\n 170,  -1, 226,  30,  -7,  -3,  -1, 225,  93,  -1, 213, 124,  -3,  -1, 199,\n  77,  -1, 139, 184, -31, -15,  -7,  -3,  -1, 212, 154,  -1, 169, 108,  -3,\n  -1, 198,  61,  -1, 211,  45,  -7,  -3,  -1, 210,  29,  -1, 123, 183,  -3,\n  -1, 209,  92,  -1, 197, 138, -17,  -7,  -3,  -1, 168, 153,  -1,  76, 196,\n  -3,  -1, 107, 182,  -3,  -1, 208,  12,  60,  -7,  -3,  -1, 195, 122,  -1,\n 167,  44,  -3,  -1, 194,  91,  -1, 181,  28, -57, -35, -19,  -7,  -3,  -1,\n 137, 152,  -1, 193,  75,  -5,  -3,  -1, 192,  11,  59,  -3,  -1, 176,  10,\n  26,  -5,  -1, 180,  -1, 106, 166,  -3,  -1, 121, 151,  -3,  -1, 160,   9,\n 144,  -9,  -3,  -1, 179, 136,  -3,  -1,  43,  90, 178,  -7,  -3,  -1, 165,\n  27,  -1, 177, 105,  -1, 150, 164, -17,  -9,  -5,  -3,  -1,  74, 120, 135,\n  -1,  58, 163,  -3,  -1,  89, 149,  -1,  42, 162,  -7,  -3,  -1, 161, 104,\n  -1, 134, 119,  -3,  -1,  73, 148,  -1,  57, 147, -63, -31, -15,  -7,  -3,\n  -1,  88, 133,  -1,  41, 103,  -3,  -1, 118, 146,  -1,  25, 145,  -7,  -3,\n  -1,  72, 132,  -1,  87, 117,  -3,  -1,  56, 131,  -1, 102,  40, -17,  -7,\n  -3,  -1, 130,  24,  -1,  71, 116,  -5,  -1, 129,  -1,   8, 128,  -1,  86,\n 101,  -7,  -5,  -1,  23,  -1,   7, 112, 115,  -3,  -1,  55,  39, 114, -15,\n  -7,  -3,  -1,  70, 100,  -1,  85, 113,  -3,  -1,  54,  99,  -1,  69,  84,\n  -7,  -3,  -1,  38,  98,  -1,  22,  97,  -5,  -3,  -1,   6,  96,  53,  -1,\n  83,  68, -51, -37, -23, -15,  -9,  -3,  -1,  37,  82,  -1,  21,  -1,   5,\n  80,  -1,  81,  -1,  52,  67,  -3,  -1,  36,  66,  -1,  51,  20,  -9,  -5,\n  -1,  65,  -1,   4,  64,  -1,  35,  50,  -1,  19,  49,  -7,  -5,  -3,  -1,\n   3,  48,  34,  18,  -1,  33,  -1,   2,  32,  -3,  -1,  17,   1,  -1,  16,\n   0\n};\n\nstatic const short tab_c0[] =\n{\n -29, -21, -13,  -7,  -3,  -1,  11,  15,  -1,  13,  14,  -3,  -1,   7,   5,\n   9,  -3,  -1,   6,   3,  -1,  10,  12,  -3,  -1,   2,   1,  -1,   4,   8,\n   0\n};\n\nstatic const short tab_c1[] =\n{\n -15,  -7,  -3,  -1,  15,  14,  -1,  13,  12,  -3,  -1,  11,  10,  -1,   9,\n   8,  -7,  -3,  -1,   7,   6,  -1,   5,   4,  -3,  -1,   3,   2,  -1,   1,\n   0\n};\n\nstatic const struct newhuff ht[] =\n{\n{ 0 , tab0  },\n{ 0 , tab1  },\n{ 0 , tab2  },\n{ 0 , tab3  },\n{ 0 , tab0  },\n{ 0 , tab5  },\n{ 0 , tab6  },\n{ 0 , tab7  },\n{ 0 , tab8  },\n{ 0 , tab9  },\n{ 0 , tab10 },\n{ 0 , tab11 },\n{ 0 , tab12 },\n{ 0 , tab13 },\n{ 0 , tab0  },\n{ 0 , tab15 },\n\n{ 1 , tab16 },\n{ 2 , tab16 },\n{ 3 , tab16 },\n{ 4 , tab16 },\n{ 6 , tab16 },\n{ 8 , tab16 },\n{ 10, tab16 },\n{ 13, tab16 },\n{ 4 , tab24 },\n{ 5 , tab24 },\n{ 6 , tab24 },\n{ 7 , tab24 },\n{ 8 , tab24 },\n{ 9 , tab24 },\n{ 11, tab24 },\n{ 13, tab24 }\n};\n\nstatic const struct newhuff htc[] =\n{\n{ 0 , tab_c0 },\n{ 0 , tab_c1 }\n};\n\n#endif//HUFFMAN_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/index.c",
    "content": "/*\nindex.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include \"index.h\"\n\n// the next expected frame offset, one step ahead.\nstatic mpg_off_t fi_next( frame_index_t *fi )\n{\n\treturn (mpg_off_t)fi->fill*fi->step;\n}\n\n// shrink down the used index to the half.\n// be careful with size = 1 ... there's no shrinking possible there.\nstatic void fi_shrink( frame_index_t *fi )\n{\n\tif( fi->fill < 2 )\n\t{\n\t\treturn; // won't shrink below 1.\n\t}\n\telse\n\t{\n\t\tsize_t\tc;\n\n\t\t// double the step, half the fill. Should work as well for fill%2 = 1\n\t\tfi->step *= 2;\n\t\tfi->fill /= 2;\n\n\t\t// move the data down.\n\t\tfor( c = 0; c < fi->fill; ++c )\n\t\t\tfi->data[c] = fi->data[2*c];\n\t}\n\n\tfi->next = fi_next( fi );\n}\n\nvoid fi_init( frame_index_t *fi )\n{\n\tfi->data = NULL;\n\tfi->step = 1;\n\tfi->fill = 0;\n\tfi->size = 0;\n\tfi->grow_size = 0;\n\tfi->next = fi_next( fi );\n}\n\nvoid fi_exit( frame_index_t *fi )\n{\n\tif( fi->size && fi->data != NULL )\n\t\tfree( fi->data );\n\n\tfi_init( fi ); // be prepared for further fun, still.\n}\n\nint fi_resize( frame_index_t *fi, size_t newsize )\n{\n\tmpg_off_t\t*newdata = NULL;\n\n\tif( newsize == fi->size )\n\t\treturn 0;\n\n\tif( newsize > 0 && newsize < fi->size )\n\t{\n\t\t// when we reduce buffer size a bit, shrink stuff.\n\t\twhile( fi->fill > newsize )\n\t\t\tfi_shrink( fi );\n\t}\n\n\tnewdata = realloc( fi->data, newsize * sizeof( mpg_off_t ));\n\tif( newsize == 0 || newdata != NULL )\n\t{\n\t\tfi->data = newdata;\n\t\tfi->size = newsize;\n\n\t\tif( fi->fill > fi->size )\n\t\t\tfi->fill = fi->size;\n\n\t\tfi->next = fi_next( fi );\n\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\treturn -1;\n\t}\n}\n\nvoid fi_add( frame_index_t *fi, mpg_off_t pos )\n{\n\tif( fi->fill == fi->size )\n\t{\n\t\tmpg_off_t\tframenum = fi->fill*fi->step;\n\n\t\t// index is full, we need to shrink... or grow.\n\t\t// store the current frame number to check later if we still want it.\n\n\t\t// if we want not / cannot grow, we shrink.\n\t\tif( !( fi->grow_size && fi_resize( fi, fi->size+fi->grow_size ) == 0 ))\n\t\t\tfi_shrink( fi );\n\n\t\t// now check if we still want to add this frame (could be that not, because of changed step).\n\t\tif( fi->next != framenum )\n\t\t\treturn;\n\t}\n\n\t// when we are here, we want that frame.\n\tif( fi->fill < fi->size ) // safeguard for size = 1, or just generally\n\t{\n\t\tfi->data[fi->fill] = pos;\n\t\tfi->fill++;\n\t\tfi->next = fi_next( fi );\n\t}\n}\n\nint fi_set( frame_index_t *fi, mpg_off_t *offsets, mpg_off_t step, size_t fill )\n{\n\tif( fi_resize( fi, fill ) == -1 )\n\t\treturn -1;\n\n\tfi->step = step;\n\n\tif( offsets != NULL )\n\t{\n\t\tmemcpy( fi->data, offsets, fill * sizeof( mpg_off_t ));\n\t\tfi->fill = fill;\n\t}\n\telse\n\t{\n\t\t// allocation only, no entries in index yet\n\t\tfi->fill = 0;\n\t}\n\n\tfi->next = fi_next( fi );\n\n\treturn 0;\n}\n\nvoid fi_reset( frame_index_t *fi )\n{\n\tfi->fill = 0;\n\tfi->step = 1;\n\tfi->next = fi_next( fi );\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/index.h",
    "content": "/*\nindex.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef INDEX_H\n#define INDEX_H\n\ntypedef struct frame_index_s\n{\n\tmpg_off_t\t*data;\t// actual data, the frame positions\n\tmpg_off_t\tstep;\t// advancement in frame number per index point\n\tmpg_off_t\tnext;\t// frame offset supposed to come next into the index\n\tsize_t\tsize;\t// total number of possible entries\n\tsize_t\tfill;\t// number of used entries\n\tsize_t\tgrow_size;// if > 0: index allowed to grow on need with these steps, instead of lowering resolution\n} frame_index_t;\n\n// the condition for a framenum to be appended to the index.\n#define FI_NEXT( fi, framenum )\t((fi).size && framenum == (fi).next)\n\n// initialize stuff, set things to zero and NULL...\nvoid fi_init( frame_index_t *fi );\n// deallocate/zero things.\nvoid fi_exit( frame_index_t *fi );\n// prepare a given size, preserving current fill, if possible.\nint fi_resize( frame_index_t *fi, size_t newsize );\n// append a frame position, reducing index density if needed.\nvoid fi_add( frame_index_t *fi, mpg_off_t pos );\n// replace the frame index\nint fi_set( frame_index_t *fi, mpg_off_t *offsets, mpg_off_t step, size_t fill );\n// empty the index (setting fill=0 and step=1), but keep current size.\nvoid fi_reset( frame_index_t *fi );\n\n#endif//INDEX_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/layer3.c",
    "content": "/*\nlayer3.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include \"huffman.h\"\n#include \"getbits.h\"\n#include <math.h>\n\n// static one-time calculated tables... or so\nfloat\t\tCOS6_1;\t\t// dct12 wants to use that\nfloat\t\tCOS6_2;\t\t// dct12 wants to use that\nfloat\t\tcos9[3];\t\t// dct36 wants to use that\nfloat\t\tcos18[3];\t\t// dct36 wants to use that\nfloat\t\ttfcos12[3];\t// dct12 wants to use that\nfloat\t\ttfcos36[9];\t// dct36 wants to use that\nstatic float\tispow[8207];\nstatic float\tCOS9[9];\nstatic float\taa_ca[8];\nstatic float\taa_cs[8];\nstatic float\twin[4][36];\nstatic float\twin1[4][36];\nstatic float\ttan1_1[16];\nstatic float\ttan2_1[16];\nstatic float\ttan1_2[16];\nstatic float\ttan2_2[16];\nstatic float\tpow1_1[2][32];\nstatic float\tpow2_1[2][32];\nstatic float\tpow1_2[2][32];\nstatic float\tpow2_2[2][32];\nstatic int\tmapbuf0[9][152];\nstatic int\tmapbuf1[9][156];\nstatic int\tmapbuf2[9][44];\nstatic int\t*map[9][3];\nstatic int\t*mapend[9][3];\nstatic uint\tn_slen2[512];\t// MPEG 2.0 slen for 'normal' mode\nstatic uint\ti_slen2[256];\t// MPEG 2.0 slen for intensity stereo\n\n// Decoder state data, living on the stack of do_layer3.\ntypedef struct gr_info_s\n{\n\tint\tscfsi;\n\tuint\tpart2_3_length;\n\tuint\tbig_values;\n\tuint\tscalefac_compress;\n\tuint\tblock_type;\n\tuint\tmixed_block_flag;\n\tuint\ttable_select[3];\n\n\t// making those two signed int as workaround for open64/pathscale/sun compilers,\n\t// and also for consistency, since they're worked on together with other signed variables.\n\tint\tmaxband[3];\n\tint\tmaxbandl;\n\tuint\tmaxb;\n\tuint\tregion1start;\n\tuint\tregion2start;\n\tuint\tpreflag;\n\tuint\tscalefac_scale;\n\tuint\tcount1table_select;\n\tfloat\t*full_gain[3];\n\tfloat\t*pow2gain;\n} gr_info_t;\n\ntypedef struct\n{\n\tuint\tmain_data_begin;\n\tuint\tprivate_bits;\n\n\t// hm, funny... struct inside struct...\n\tstruct\n\t{\n\t\tgr_info_t\tgr[2];\n\t} ch[2];\n} III_sideinfo;\n\ntypedef struct\n{\n\tword\tlongIdx[23];\n\tbyte\tlongDiff[22];\n\tword\tshortIdx[14];\n\tbyte\tshortDiff[13];\n} bandInfoStruct;\n\n// techy details about our friendly MPEG data. Fairly constant over the years ;-)\nstatic const bandInfoStruct bandInfo[9] =\n{\n\t{ // MPEG 1.0\n\t{0,4,8,12,16,20,24,30,36,44,52,62,74, 90,110,134,162,196,238,288,342,418,576},\n\t{4,4,4,4,4,4,6,6,8, 8,10,12,16,20,24,28,34,42,50,54, 76,158},\n\t{0,4*3,8*3,12*3,16*3,22*3,30*3,40*3,52*3,66*3, 84*3,106*3,136*3,192*3},\n\t{4,4,4,4,6,8,10,12,14,18,22,30,56}\n\t},\n\t{\n\t{0,4,8,12,16,20,24,30,36,42,50,60,72, 88,106,128,156,190,230,276,330,384,576},\n\t{4,4,4,4,4,4,6,6,6, 8,10,12,16,18,22,28,34,40,46,54, 54,192},\n\t{0,4*3,8*3,12*3,16*3,22*3,28*3,38*3,50*3,64*3, 80*3,100*3,126*3,192*3},\n\t{4,4,4,4,6,6,10,12,14,16,20,26,66}\n\t},\n\t{\n\t{0,4,8,12,16,20,24,30,36,44,54,66,82,102,126,156,194,240,296,364,448,550,576},\n\t{4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102, 26},\n\t{0,4*3,8*3,12*3,16*3,22*3,30*3,42*3,58*3,78*3,104*3,138*3,180*3,192*3},\n\t{4,4,4,4,6,8,12,16,20,26,34,42,12}\n\t},\n\t{ // MPEG 2.0\n\t{0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576},\n\t{6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54 } ,\n\t{0,4*3,8*3,12*3,18*3,24*3,32*3,42*3,56*3,74*3,100*3,132*3,174*3,192*3} ,\n\t{4,4,4,6,6,8,10,14,18,26,32,42,18 }\n\t},\n\t{ // twiddling 3 values here (not just 330->332!) fixed bug 1895025.\n\t{0,6,12,18,24,30,36,44,54,66,80,96,114,136,162,194,232,278,332,394,464,540,576},\n\t{6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36 },\n\t{0,4*3,8*3,12*3,18*3,26*3,36*3,48*3,62*3,80*3,104*3,136*3,180*3,192*3},\n\t{4,4,4,6,8,10,12,14,18,24,32,44,12 }\n\t},\n\t{\n\t{0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576},\n\t{6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54 },\n\t{0,4*3,8*3,12*3,18*3,26*3,36*3,48*3,62*3,80*3,104*3,134*3,174*3,192*3},\n\t{4,4,4,6,8,10,12,14,18,24,30,40,18 }\n\t},\n\t{ // MPEG 2.5\n\t{0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576},\n\t{6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54},\n\t{0,12,24,36,54,78,108,144,186,240,312,402,522,576},\n\t{4,4,4,6,8,10,12,14,18,24,30,40,18}\n\t},\n\t{\n\t{0,6,12,18,24,30,36,44,54,66,80,96,116,140,168,200,238,284,336,396,464,522,576},\n\t{6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54},\n\t{0,12,24,36,54,78,108,144,186,240,312,402,522,576},\n\t{4,4,4,6,8,10,12,14,18,24,30,40,18}\n\t},\n\t{\n\t{0,12,24,36,48,60,72,88,108,132,160,192,232,280,336,400,476,566,568,570,572,574,576},\n\t{12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2},\n\t{0, 24, 48, 72,108,156,216,288,372,480,486,492,498,576},\n\t{8,8,8,12,16,20,24,28,36,2,2,2,26}\n\t}\n};\n\nstatic byte pretab_choice[2][22] =\n{\n{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},\n{0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0}\n};\n\n// init tables for layer-3 ... specific with the downsampling...\nvoid init_layer3( void )\n{\n\tint\ti, j, k, l;\n\n\tfor( i = 0; i < 8207; i++ )\n\t\tispow[i] = DOUBLE_TO_REAL_POW43( pow( (double)i, (double)4.0 / 3.0 ));\n\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\tconst double Ci[8] = { -0.6, -0.535, -0.33, -0.185, -0.095, -0.041, -0.0142, -0.0037 };\n\t\tdouble sq = sqrt( 1.0 + Ci[i] * Ci[i] );\n\t\taa_cs[i] = DOUBLE_TO_REAL( 1.0 / sq );\n\t\taa_ca[i] = DOUBLE_TO_REAL( Ci[i] / sq );\n\t}\n\n\tfor( i = 0; i < 18; i++ )\n\t{\n\t\twin[0][i] = win[1][i] = DOUBLE_TO_REAL( 0.5 * sin( M_PI / 72.0 * (double)(2 * (i + 0) + 1)) / cos( M_PI * (double)(2 * (i + 0) + 19) / 72.0) );\n\t\twin[0][i+18] = win[3][i+18] = DOUBLE_TO_REAL( 0.5 * sin( M_PI/72.0 * (double)(2 * (i + 18) + 1)) / cos( M_PI * (double)(2 * (i + 18) + 19) / 72.0) );\n\t}\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\twin[1][i+18] = DOUBLE_TO_REAL( 0.5 / cos ( M_PI * (double)(2 * (i + 18) + 19) / 72.0 ));\n\t\twin[3][i+12] = DOUBLE_TO_REAL( 0.5 / cos ( M_PI * (double)(2 * (i + 12) + 19) / 72.0 ));\n\t\twin[1][i+24] = DOUBLE_TO_REAL( 0.5 * sin( M_PI / 24.0 * (double)(2 * i + 13)) / cos( M_PI * (double)(2 * (i + 24) + 19) / 72.0 ));\n\t\twin[1][i+30] = win[3][i] = DOUBLE_TO_REAL( 0.0 );\n\t\twin[3][i+6 ] = DOUBLE_TO_REAL( 0.5 * sin( M_PI / 24.0 * (double)(2 * i + 1)) / cos( M_PI * (double)(2 * (i + 6 ) + 19) / 72.0 ));\n\t}\n\n\tfor( i = 0; i < 9; i++ )\n\t\tCOS9[i] = DOUBLE_TO_REAL( cos( M_PI / 18.0 * (double)i ));\n\n\tfor( i = 0; i < 9; i++ )\n\t\ttfcos36[i] = DOUBLE_TO_REAL( 0.5 / cos( M_PI * (double)(i * 2 + 1) / 36.0 ));\n\n\tfor( i = 0; i < 3; i++ )\n\t\ttfcos12[i] = DOUBLE_TO_REAL( 0.5 / cos( M_PI * (double)(i * 2 + 1) / 12.0 ));\n\n\tCOS6_1 = DOUBLE_TO_REAL( cos( M_PI / 6.0 * (double)1 ));\n\tCOS6_2 = DOUBLE_TO_REAL( cos( M_PI / 6.0 * (double)2 ));\n\n\tcos9[0]  = DOUBLE_TO_REAL( cos( 1.0 * M_PI / 9.0));\n\tcos9[1]  = DOUBLE_TO_REAL( cos( 5.0 * M_PI / 9.0));\n\tcos9[2]  = DOUBLE_TO_REAL( cos( 7.0 * M_PI / 9.0));\n\tcos18[0] = DOUBLE_TO_REAL( cos( 1.0 * M_PI / 18.0));\n\tcos18[1] = DOUBLE_TO_REAL( cos( 11.0 * M_PI / 18.0));\n\tcos18[2] = DOUBLE_TO_REAL( cos( 13.0 * M_PI / 18.0));\n\n\tfor( i = 0; i < 12; i++ )\n\t\twin[2][i] = DOUBLE_TO_REAL( 0.5 * sin( M_PI / 24.0 * (double)(2 * i + 1) ) / cos( M_PI * (double)(2 * i + 7) / 24.0 ));\n\n\tfor( i = 0; i < 16; i++ )\n\t{\n\t\tdouble t = tan((double)i * M_PI / 12.0 );\n\t\ttan1_1[i] = DOUBLE_TO_REAL_15( t / (1.0 + t));\n\t\ttan2_1[i] = DOUBLE_TO_REAL_15( 1.0 / (1.0 + t));\n\t\ttan1_2[i] = DOUBLE_TO_REAL_15( M_SQRT2 * t / (1.0 + t));\n\t\ttan2_2[i] = DOUBLE_TO_REAL_15( M_SQRT2 / (1.0 + t));\n\t}\n\n\tfor( i = 0; i < 32; i++ )\n\t{\n\t\tfor( j = 0; j < 2; j++ )\n\t\t{\n\t\t\tdouble base = pow( 2.0, -0.25 * (j + 1.0));\n\t\t\tdouble p1 = 1.0, p2 = 1.0;\n\n\t\t\tif( i > 0 )\n\t\t\t{\n\t\t\t\tif( i & 1 ) p1 = pow( base,(i + 1.0) * 0.5);\n\t\t\t\telse p2 = pow( base, i * 0.5 );\n\t\t\t}\n\n\t\t\tpow1_1[j][i] = DOUBLE_TO_REAL_15( p1 );\n\t\t\tpow2_1[j][i] = DOUBLE_TO_REAL_15( p2 );\n\t\t\tpow1_2[j][i] = DOUBLE_TO_REAL_15( M_SQRT2 * p1 );\n\t\t\tpow2_2[j][i] = DOUBLE_TO_REAL_15( M_SQRT2 * p2 );\n\t\t}\n\t}\n\n\tfor( j = 0; j < 4; j++ )\n\t{\n\t\tconst int len[4] = { 36, 36, 12, 36 };\n\n\t\tfor( i = 0; i < len[j]; i += 2 )\n\t\t\twin1[j][i] = +win[j][i];\n\n\t\tfor( i = 1; i < len[j]; i += 2 )\n\t\t\twin1[j][i] = -win[j][i];\n\t}\n\n\tfor( j = 0; j < 9; j++ )\n\t{\n\t\tconst bandInfoStruct\t*bi = &bandInfo[j];\n\t\tint\t\t\tcb, lwin;\n\t\tconst byte\t\t*bdf;\n\t\tint\t\t\t*mp;\n\n\t\tmp = map[j][0] = mapbuf0[j];\n\t\tbdf = bi->longDiff;\n\n\t\tfor( i = 0, cb = 0; cb < 8 ; cb++, i += *bdf++ )\n\t\t{\n\t\t\t*mp++ = (*bdf) >> 1;\n\t\t\t*mp++ = i;\n\t\t\t*mp++ = 3;\n\t\t\t*mp++ = cb;\n\t\t}\n\n\t\tbdf = bi->shortDiff + 3;\n\t\tfor( cb = 3;cb < 13; cb++ )\n\t\t{\n\t\t\tint l = (*bdf++) >> 1;\n\t\t\tfor( lwin = 0; lwin < 3; lwin++ )\n\t\t\t{\n\t\t\t\t*mp++ = l;\n\t\t\t\t*mp++ = i + lwin;\n\t\t\t\t*mp++ = lwin;\n\t\t\t\t*mp++ = cb;\n\t\t\t}\n\t\t\ti += 6 * l;\n\t\t}\n\n\t\tmapend[j][0] = mp;\n\t\tmp = map[j][1] = mapbuf1[j];\n\t\tbdf = bi->shortDiff + 0;\n\n\t\tfor( i = 0, cb = 0; cb < 13; cb++ )\n\t\t{\n\t\t\tint l = (*bdf++) >> 1;\n\t\t\tfor( lwin = 0; lwin < 3; lwin++ )\n\t\t\t{\n\t\t\t\t*mp++ = l;\n\t\t\t\t*mp++ = i + lwin;\n\t\t\t\t*mp++ = lwin;\n\t\t\t\t*mp++ = cb;\n\t\t\t}\n\t\t\ti += 6 * l;\n\t\t}\n\n\t\tmapend[j][1] = mp;\n\t\tmp = map[j][2] = mapbuf2[j];\n\t\tbdf = bi->longDiff;\n\n\t\tfor( cb = 0; cb < 22; cb++ )\n\t\t{\n\t\t\t*mp++ = (*bdf++) >> 1;\n\t\t\t*mp++ = cb;\n\t\t}\n\t\tmapend[j][2] = mp;\n\t}\n\n\t// now for some serious loopings!\n\tfor( i = 0; i < 5; i++ )\n\t{\n\t\tfor( j = 0; j < 6; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 6; k++ )\n\t\t\t{\n\t\t\t\tint n = k + j * 6 + i * 36;\n\t\t\t\ti_slen2[n] = i|(j<<3)|(k<<6)|(3<<12);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\tfor( j = 0; j < 4; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 4; k++ )\n\t\t\t{\n\t\t\t\tint n = k + j * 4 + i * 16;\n\t\t\t\ti_slen2[n+180] = i|(j<<3)|(k<<6)|(4<<12);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tint n = j + i * 3;\n\t\t\ti_slen2[n+244] = i|(j<<3) | (5<<12);\n\t\t\tn_slen2[n+500] = i|(j<<3) | (2<<12) | (1<<15);\n\t\t}\n\t}\n\n\tfor( i = 0; i < 5; i++ )\n\t{\n\t\tfor( j = 0; j < 5; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 4; k++ )\n\t\t\t{\n\t\t\t\tfor( l = 0; l < 4; l++ )\n\t\t\t\t{\n\t\t\t\t\tint n = l + k * 4 + j * 16 + i * 80;\n\t\t\t\t\tn_slen2[n] = i|(j<<3)|(k<<6)|(l<<9)|(0<<12);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < 5; i++ )\n\t{\n\t\tfor( j = 0; j < 5; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 4; k++ )\n\t\t\t{\n\t\t\t\tint n = k + j * 4 + i * 20;\n\t\t\t\tn_slen2[n+400] = i|(j<<3)|(k<<6)|(1<<12);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid init_layer3_stuff( mpg123_handle_t *fr )\n{\n\tint i,j;\n\n\tfor( i = -256; i < 118 + 4; i++ )\n\t\tfr->gainpow2[i+256] = DOUBLE_TO_REAL_SCALE_LAYER3( pow((double)2.0, -0.25 * (double)(i + 210)), i + 256 );\n\n\tfor( j = 0; j < 9; j++ )\n\t{\n\t\tfor( i = 0; i < 23; i++ )\n\t\t{\n\t\t\tfr->longLimit[j][i] = (bandInfo[j].longIdx[i] - 1 + 8) / 18 + 1;\n\n\t\t\tif( fr->longLimit[j][i] > fr->down_sample_sblimit )\n\t\t\t\tfr->longLimit[j][i] = fr->down_sample_sblimit;\n\t\t}\n\n\t\tfor( i = 0; i < 14; i++ )\n\t\t{\n\t\t\tfr->shortLimit[j][i] = (bandInfo[j].shortIdx[i] - 1) / 18 + 1;\n\n\t\t\tif( fr->shortLimit[j][i] > fr->down_sample_sblimit )\n\t\t\t\tfr->shortLimit[j][i] = fr->down_sample_sblimit;\n\t\t}\n\t}\n}\n\n// read additional side information (for MPEG 1 and MPEG 2)\nstatic int III_get_side_info( mpg123_handle_t *fr, III_sideinfo *si, int stereo, int ms_stereo, long sfreq, int single )\n{\n\tint\tpowdiff = (single == SINGLE_MIX) ? 4 : 0;\n\tconst int\ttabs[2][5] = { { 2,9,5,3,4 } , { 1,8,1,2,9 } };\n\tconst int\t*tab = tabs[fr->lsf];\n\tint\tch, gr;\n\n\tsi->main_data_begin = getbits( fr, tab[1] );\n\n\tif( si->main_data_begin > fr->bitreservoir )\n\t{\n\t\t//  overwrite main_data_begin for the floatly available bit reservoir\n\t\tbackbits( fr, tab[1] );\n\n\t\tif( fr->lsf == 0 )\n\t\t{\n\t\t\tfr->wordpointer[0] = (byte)(fr->bitreservoir >> 1);\n\t\t\tfr->wordpointer[1] = (byte)((fr->bitreservoir & 1) << 7);\n\t\t}\n\t\telse fr->wordpointer[0] = (byte)fr->bitreservoir;\n\n\t\t// zero \"side-info\" data for a silence-frame\n\t\t// without touching audio data used as bit reservoir for following frame\n\t\tmemset( fr->wordpointer + 2, 0, fr->ssize - 2 );\n\n\t\t// reread the new bit reservoir offset\n\t\tsi->main_data_begin = getbits( fr, tab[1] );\n\t}\n\n\t// keep track of the available data bytes for the bit reservoir.\n\t// think: Substract the 2 crc bytes in parser already?\n\tfr->bitreservoir = fr->bitreservoir + fr->framesize - fr->ssize - (fr->error_protection ? 2 : 0);\n\t// limit the reservoir to the max for MPEG 1.0 or 2.x.\n\tif( fr->bitreservoir > (uint)(fr->lsf == 0 ? 511 : 255 ))\n\t\tfr->bitreservoir = (fr->lsf == 0 ? 511 : 255);\n\n\t// now back into less commented territory. It's code. It works.\n\n\tif( stereo == 1 ) si->private_bits = getbits_fast( fr, tab[2] );\n\telse si->private_bits = getbits_fast( fr, tab[3] );\n\n\tif( !fr->lsf )\n\t{\n\t\tfor( ch = 0; ch < stereo; ch++ )\n\t\t{\n\t\t\tsi->ch[ch].gr[0].scfsi = -1;\n\t\t\tsi->ch[ch].gr[1].scfsi = getbits_fast( fr, 4 );\n\t\t}\n\t}\n\n\tfor( gr = 0; gr < tab[0]; gr++ )\n\t{\n\t\tfor( ch = 0; ch < stereo; ch++ )\n\t\t{\n\t\t\tregister gr_info_t\t*gr_info = &( si->ch[ch].gr[gr] );\n\n\t\t\tgr_info->part2_3_length = getbits( fr, 12 );\n\t\t\tgr_info->big_values = getbits( fr, 9 );\n\n\t\t\tif( gr_info->big_values > 288 )\n\t\t\t\tgr_info->big_values = 288;\n\n\t\t\tgr_info->pow2gain = fr->gainpow2 + 256 - getbits_fast( fr, 8 ) + powdiff;\n\t\t\tif( ms_stereo ) gr_info->pow2gain += 2;\n\n\t\t\tgr_info->scalefac_compress = getbits( fr, tab[4] );\n\n\t\t\tif( get1bit( fr ))\n\t\t\t{\n\t\t\t\tint\ti;\n\n\t\t\t\t// window switch flag\n\t\t\t\tgr_info->block_type = getbits_fast( fr, 2 );\n\t\t\t\tgr_info->mixed_block_flag = get1bit( fr );\n\t\t\t\tgr_info->table_select[0] = getbits_fast( fr, 5 );\n\t\t\t\tgr_info->table_select[1] = getbits_fast( fr, 5 );\n\n\t\t\t\t// table_select[2] not needed, because there is no region2,\n\t\t\t\t// but to satisfy some verification tools we set it either.\n\t\t\t\tgr_info->table_select[2] = 0;\n\n\t\t\t\tfor( i = 0; i < 3; i++ )\n\t\t\t\t\tgr_info->full_gain[i] = gr_info->pow2gain + (getbits_fast( fr, 3 ) << 3);\n\n\t\t\t\tif( gr_info->block_type == 0 )\n\t\t\t\t\treturn 1;\n\n\t\t\t\t// region_count/start parameters are implicit in this case.\n\t\t\t\tif(( !fr->lsf || ( gr_info->block_type == 2 )) && !fr->mpeg25 )\n\t\t\t\t{\n\t\t\t\t\tgr_info->region1start = 36 >> 1;\n\t\t\t\t\tgr_info->region2start = 576 >> 1;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( fr->mpeg25 )\n\t\t\t\t\t{\n\t\t\t\t\t\tint\tr0c, r1c;\n\n\t\t\t\t\t\tif(( gr_info->block_type == 2 ) && ( !gr_info->mixed_block_flag ))\n\t\t\t\t\t\t\tr0c = 5;\n\t\t\t\t\t\telse r0c = 7;\n\n\t\t\t\t\t\t// r0c + 1 + r1c + 1 == 22, always.\n\t\t\t\t\t\tr1c = 20 - r0c;\n\t\t\t\t\t\tgr_info->region1start = bandInfo[sfreq].longIdx[r0c+1] >> 1 ;\n\t\t\t\t\t\tgr_info->region2start = bandInfo[sfreq].longIdx[r0c+1+r1c+1] >> 1;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tgr_info->region1start = 54 >> 1;\n\t\t\t\t\t\tgr_info->region2start = 576 >> 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint\ti, r0c, r1c;\n\n\t\t\t\tfor( i = 0; i < 3; i++ )\n\t\t\t\t\tgr_info->table_select[i] = getbits_fast( fr, 5 );\n\n\t\t\t\tr0c = getbits_fast( fr, 4 ); // 0 .. 15\n\t\t\t\tr1c = getbits_fast( fr, 3 ); // 0 .. 7\n\t\t\t\tgr_info->region1start = bandInfo[sfreq].longIdx[r0c+1] >> 1 ;\n\n\t\t\t\t// max( r0c + r1c + 2 ) = 15 + 7 + 2 = 24\n\t\t\t\tif( r0c + 1 + r1c + 1 > 22 )\n\t\t\t\t\tgr_info->region2start = 576 >> 1;\n\t\t\t\telse gr_info->region2start = bandInfo[sfreq].longIdx[r0c+1+r1c+1] >> 1;\n\n\t\t\t\tgr_info->block_type = 0;\n\t\t\t\tgr_info->mixed_block_flag = 0;\n\t\t\t}\n\n\t\t\tif( !fr->lsf )\n\t\t\t\tgr_info->preflag = get1bit( fr );\n\n\t\t\tgr_info->scalefac_scale = get1bit( fr );\n\t\t\tgr_info->count1table_select = get1bit( fr );\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n// read scalefactors\nstatic int III_get_scale_factors_1( mpg123_handle_t *fr, int *scf, gr_info_t *gr_info )\n{\n\tconst byte slen[2][16] =\n\t{\n\t{ 0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4 },\n\t{ 0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3 }\n\t};\n\tint\tnum0 = slen[0][gr_info->scalefac_compress];\n\tint\tnum1 = slen[1][gr_info->scalefac_compress];\n\tint\tnumbits;\n\n\tif( gr_info->block_type == 2 )\n\t{\n\t\tint i = 18;\n\t\tnumbits = (num0 + num1) * 18;\n\n\t\tif( gr_info->mixed_block_flag )\n\t\t{\n\t\t\tfor( i = 8; i; i-- )\n\t\t\t\t*scf++ = getbits_fast( fr, num0 );\n\n\t\t\ti = 9;\n\t\t\tnumbits -= num0; // num0 * 17 + num1 * 18\n\t\t}\n\n\t\tfor( ; i; i-- )\n\t\t\t*scf++ = getbits_fast( fr, num0 );\n\n\t\tfor( i = 18; i; i-- )\n\t\t\t*scf++ = getbits_fast( fr, num1 );\n\n\t\t// short[13][0..2] = 0\n\t\t*scf++ = 0;\n\t\t*scf++ = 0;\n\t\t*scf++ = 0;\n\t}\n\telse\n\t{\n\t\tint\ti, scfsi = gr_info->scfsi;\n\n\t\tif( scfsi < 0 )\n\t\t{\n\t\t\t// scfsi < 0 => granule == 0\n\t\t\tfor( i = 11; i; i-- )\n\t\t\t\t*scf++ = getbits_fast( fr, num0 );\n\n\t\t\tfor( i = 10; i; i-- )\n\t\t\t\t*scf++ = getbits_fast( fr, num1 );\n\n\t\t\tnumbits = (num0 + num1) * 10 + num0;\n\t\t\t*scf++ = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnumbits = 0;\n\n\t\t\tif(!( scfsi & 0x8 ))\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 6; i++ )\n\t\t\t\t\t*scf++ = getbits_fast( fr, num0 );\n\n\t\t\t\tnumbits += num0 * 6;\n\t\t\t}\n\t\t\telse scf += 6;\n\n\t\t\tif(!( scfsi & 0x4 ))\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 5; i++ )\n\t\t\t\t\t*scf++ = getbits_fast( fr, num0 );\n\n\t\t\t\tnumbits += num0 * 5;\n\t\t\t}\n\t\t\telse scf += 5;\n\n\t\t\tif(!( scfsi & 0x2 ))\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 5; i++ )\n\t\t\t\t\t*scf++ = getbits_fast( fr, num1 );\n\n\t\t\t\tnumbits += num1 * 5;\n\t\t\t}\n\t\t\telse scf += 5;\n\n\t\t\tif(!( scfsi & 0x1 ))\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 5; i++ )\n\t\t\t\t\t*scf++ = getbits_fast( fr, num1 );\n\n\t\t\t\tnumbits += num1 * 5;\n\t\t\t}\n\t\t\telse scf += 5;\n\n\t\t\t// no l[21] in original sources\n\t\t\t*scf++ = 0;\n\t\t}\n\t}\n\n\treturn numbits;\n}\n\nstatic int III_get_scale_factors_2( mpg123_handle_t *fr, int *scf, gr_info_t *gr_info, int i_stereo )\n{\n\tconst byte\t*pnt;\n\tint\t\ti, j, n = 0;\n\tint\t\tnumbits = 0;\n\tuint\t\tslen;\n\n\tconst byte stab[3][6][4] =\n\t{\n\t{\n\t{ 6, 5, 5,5 } , { 6, 5, 7,3 } , { 11,10,0,0},\n\t{ 7, 7, 7,0 } , { 6, 6, 6,3 } , {  8, 8,5,0}\n\t},\n\t{\n\t{ 9, 9, 9,9 } , { 9, 9,12,6 } , { 18,18,0,0},\n\t{12,12,12,0 } , {12, 9, 9,6 } , { 15,12,9,0}\n\t},\n\t{\n\t{ 6, 9, 9,9 } , { 6, 9,12,6 } , { 15,18,0,0},\n\t{ 6,15,12,0 } , { 6,12, 9,6 } , {  6,18,9,0}\n\t}\n\t};\n\n\t// i_stereo AND second channel -> do_layer3() checks this\n\tif( i_stereo ) slen = i_slen2[gr_info->scalefac_compress>>1];\n\telse slen = n_slen2[gr_info->scalefac_compress];\n\n\tgr_info->preflag = (slen >> 15) & 0x1;\n\tn = 0;\n\n\tif( gr_info->block_type == 2 )\n\t{\n\t\tif( gr_info->mixed_block_flag )\n\t\t\tn++;\n\t\tn++;\n\t}\n\n\tpnt = stab[n][(slen>>12)&0x7];\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\tint\tnum = slen & 0x7;\n\n\t\tslen >>= 3;\n\t\tif( num )\n\t\t{\n\t\t\tfor( j = 0; j < (int)(pnt[i]); j++ )\n\t\t\t\t*scf++ = getbits_fast( fr, num );\n\t\t\tnumbits += pnt[i] * num;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( j = 0; j < (int)(pnt[i]); j++ )\n\t\t\t\t*scf++ = 0;\n\t\t}\n\t}\n\n\tn = (n << 1) + 1;\n\n\tfor( i = 0; i < n; i++ )\n\t\t*scf++ = 0;\n\n\treturn numbits;\n}\n\n/* 24 is enough because tab13 has max. a 19 bit huffvector */\n/* The old code played games with shifting signed integers around in not quite */\n/* legal ways. Also, it used long where just 32 bits are required. This could */\n/* be good or bad on 64 bit architectures ... anyway, making clear that */\n/* 32 bits suffice is a benefit. */\n#if 0\n/* To reconstruct old code, use this: */\n#define MASK_STYPE long\n#define MASK_UTYPE unsigned long\n#define MASK_TYPE MASK_STYPE\n#define MSB_MASK (mask < 0)\n#else\n/* This should be more proper: */\n#define MASK_STYPE int32_t\n#define MASK_UTYPE uint32_t\n#define MASK_TYPE  MASK_UTYPE\n#define MSB_MASK ((MASK_UTYPE)mask & (MASK_UTYPE)1<<(sizeof(MASK_TYPE)*8-1))\n#endif\n\n// 24 is enough because tab13 has max. a 19 bit huffvector\n#define BITSHIFT\t((sizeof(MASK_TYPE) - 1) * 8)\n\n#define REFRESH_MASK \\\n\twhile( num < BITSHIFT ) { \\\n\t\tmask |= ((MASK_UTYPE)getbyte( fr )) << (BITSHIFT - num); \\\n\t\tnum += 8; \\\n\t\tpart2remain -= 8; }\n\nstatic int III_dequantize_sample( mpg123_handle_t *fr, float xr[SBLIMIT][SSLIMIT], int *scf, gr_info_t *gr_info, int sfreq, int part2bits )\n{\n\tint\tshift = 1 + gr_info->scalefac_scale;\n\tint\tpart2remain = gr_info->part2_3_length - part2bits;\n\tint\tregion1 = gr_info->region1start;\n\tint\tregion2 = gr_info->region2start;\n\tint\tbv = gr_info->big_values;\n\tint\tnum = getbitoffset( fr );\n\tfloat\t*xrpnt = (float *)xr;\n\tint\tl[3], l3;\n\tMASK_TYPE mask;\n\tint\t*me;\n\n\t// we must split this, because for num == 0 the shift is undefined if you do it in one step.\n\tmask  = ((MASK_UTYPE)getbits( fr, num )) << BITSHIFT;\n\tmask <<= 8 - num;\n\tpart2remain -= num;\n\n\tl3 = ((576>>1)-bv)>>1;\n\n\t// we may lose the 'odd' bit here !! check this later again\n\tif( bv <= region1 )\n\t{\n\t\tl[0] = bv;\n\t\tl[1] = 0;\n\t\tl[2] = 0;\n\t}\n\telse\n\t{\n\t\tl[0] = region1;\n\n\t\tif( bv <= region2 )\n\t\t{\n\t\t\tl[1] = bv - l[0];\n\t\t\tl[2] = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tl[1] = region2 - l[0];\n\t\t\tl[2] = bv - region2;\n\t\t}\n\t}\n\n\tif( gr_info->block_type == 2 )\n\t{\n\t\tint\t\ti, max[4];\n\t\tint\t\tstep = 0;\n\t\tint\t\tlwin = 3;\n\t\tregister float\tv = 0.0f;\n\t\tint\t\tcb = 0;\n\t\tregister int\t*m, mc;\n\t\tint\t\trmax;\n\n\t\t// decoding with short or mixed mode BandIndex table\n\t\tif( gr_info->mixed_block_flag )\n\t\t{\n\t\t\tmax[3] = -1;\n\t\t\tmax[0] = max[1] = max[2] = 2;\n\t\t\tm = map[sfreq][0];\n\t\t\tme = mapend[sfreq][0];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmax[0] = max[1] = max[2] = max[3] = -1;\n\t\t\t// max[3] not floatly needed in this case\n\t\t\tm = map[sfreq][1];\n\t\t\tme = mapend[sfreq][1];\n\t\t}\n\n\t\tmc = 0;\n\n\t\tfor( i = 0; i < 2; i++ )\n\t\t{\n\t\t\tconst struct newhuff\t*h = ht + gr_info->table_select[i];\n\t\t\tint\t\t\tlp = l[i];\n\n\t\t\tfor( ; lp; lp--, mc-- )\n\t\t\t{\n\t\t\t\tregister MASK_STYPE x, y;\n\n\t\t\t\tif( (!mc) )\n\t\t\t\t{\n\t\t\t\t\tmc = *m++;\n\t\t\t\t\txrpnt = ((float *)xr) + (*m++);\n\t\t\t\t\tlwin = *m++;\n\t\t\t\t\tcb = *m++;\n\n\t\t\t\t\tif( lwin == 3 )\n\t\t\t\t\t{\n\t\t\t\t\t\tv = gr_info->pow2gain[(*scf++) << shift];\n\t\t\t\t\t\tstep = 1;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tv = gr_info->full_gain[lwin][(*scf++) << shift];\n\t\t\t\t\t\tstep = 3;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tconst short *val = h->table;\n\t\t\t\t\tREFRESH_MASK;\n\n\t\t\t\t\twhile(( y = *val++ ) < 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( MSB_MASK )\n\t\t\t\t\t\t\tval -= y;\n\n\t\t\t\t\t\tnum--;\n\t\t\t\t\t\tmask <<= 1;\n\t\t\t\t\t}\n\t\t\t\t\tx = y >> 4;\n\t\t\t\t\ty &= 0xf;\n\t\t\t\t}\n\n\t\t\t\tif( x == 15 && h->linbits )\n\t\t\t\t{\n\t\t\t\t\tmax[lwin] = cb;\n\t\t\t\t\tREFRESH_MASK;\n\n\t\t\t\t\tx += ((MASK_UTYPE)mask) >> (BITSHIFT + 8 - h->linbits);\n\t\t\t\t\tnum -= h->linbits + 1;\n\t\t\t\t\tmask <<= h->linbits;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt = REAL_MUL_SCALE_LAYER3( -ispow[x], v );\n\t\t\t\t\telse *xrpnt = REAL_MUL_SCALE_LAYER3( ispow[x], v );\n\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse if( x )\n\t\t\t\t{\n\t\t\t\t\tmax[lwin] = cb;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt = REAL_MUL_SCALE_LAYER3( -ispow[x], v );\n\t\t\t\t\telse *xrpnt = REAL_MUL_SCALE_LAYER3( ispow[x], v );\n\n\t\t\t\t\tnum--;\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt = DOUBLE_TO_REAL(0.0);\n\n\t\t\t\txrpnt += step;\n\n\t\t\t\tif( y == 15 && h->linbits )\n\t\t\t\t{\n\t\t\t\t\tmax[lwin] = cb;\n\t\t\t\t\tREFRESH_MASK;\n\n\t\t\t\t\ty += ((MASK_UTYPE) mask) >> (BITSHIFT + 8 - h->linbits);\n\t\t\t\t\tnum -= h->linbits + 1;\n\t\t\t\t\tmask <<= h->linbits;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt = REAL_MUL_SCALE_LAYER3( -ispow[y], v );\n\t\t\t\t\telse *xrpnt = REAL_MUL_SCALE_LAYER3( ispow[y], v );\n\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse if( y )\n\t\t\t\t{\n\t\t\t\t\tmax[lwin] = cb;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt = REAL_MUL_SCALE_LAYER3( -ispow[y], v );\n\t\t\t\t\telse *xrpnt = REAL_MUL_SCALE_LAYER3( ispow[y], v );\n\n\t\t\t\t\tnum--;\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt = DOUBLE_TO_REAL(0.0);\n\n\t\t\t\txrpnt += step;\n\t\t\t}\n\t\t}\n\n\t\tfor( ; l3 && (part2remain + num > 0); l3-- )\n\t\t{\n\t\t\tconst struct newhuff\t*h;\n\t\t\tconst short\t\t*val;\n\t\t\tregister short\t\ta;\n\n\t\t\t// this is only a humble hack to prevent a special segfault.\n\t\t\t// more insight into the float workings is still needed.\n\t\t\t// especially why there are (valid?) files that make xrpnt exceed the array with 4 bytes without segfaulting\n\t\t\t// more seems to be floatly bad, though.\n\n\t\t\tif(!( xrpnt < &xr[SBLIMIT][0] + 5 ))\n\t\t\t\treturn 2;\n\n\t\t\th = htc + gr_info->count1table_select;\n\t\t\tval = h->table;\n\n\t\t\tREFRESH_MASK;\n\n\t\t\twhile(( a = *val++ ) < 0 )\n\t\t\t{\n\t\t\t\tif( MSB_MASK )\n\t\t\t\t\tval -= a;\n\n\t\t\t\tnum--;\n\t\t\t\tmask <<= 1;\n\t\t\t}\n\n\t\t\tif( part2remain + num <= 0 )\n\t\t\t{\n\t\t\t\tnum -= part2remain + num;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor( i = 0; i < 4; i++ )\n\t\t\t{\n\t\t\t\tif(!( i & 1 ))\n\t\t\t\t{\n\t\t\t\t\tif( !mc )\n\t\t\t\t\t{\n\t\t\t\t\t\tmc = *m++;\n\t\t\t\t\t\txrpnt = ((float *)xr) + (*m++);\n\t\t\t\t\t\tlwin = *m++;\n\t\t\t\t\t\tcb = *m++;\n\n\t\t\t\t\t\tif( lwin == 3 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tv = gr_info->pow2gain[(*scf++) << shift];\n\t\t\t\t\t\t\tstep = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tv = gr_info->full_gain[lwin][(*scf++) << shift];\n\t\t\t\t\t\t\tstep = 3;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmc--;\n\t\t\t\t}\n\n\t\t\t\tif(( a & ( 0x8 >> i )))\n\t\t\t\t{\n\t\t\t\t\tmax[lwin] = cb;\n\n\t\t\t\t\tif( part2remain + num <= 0 )\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt = -REAL_SCALE_LAYER3( v );\n\t\t\t\t\telse *xrpnt =  REAL_SCALE_LAYER3( v );\n\n\t\t\t\t\tnum--;\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt = DOUBLE_TO_REAL( 0.0 );\n\n\t\t\t\txrpnt += step;\n\t\t\t}\n\t\t}\n\n\t\tif( lwin < 3 )\n\t\t{\n\t\t\t// short band?\n\t\t\twhile( 1 )\n\t\t\t{\n\t\t\t\tfor( ; mc > 0; mc-- )\n\t\t\t\t{\n\t\t\t\t\t*xrpnt = DOUBLE_TO_REAL( 0.0 );\n\t\t\t\t\txrpnt += 3; // short band -> step = 3\n\t\t\t\t\t*xrpnt = DOUBLE_TO_REAL( 0.0 );\n\t\t\t\t\txrpnt += 3;\n\t\t\t\t}\n\n\t\t\t\tif( m >= me ) break;\n\n\t\t\t\tmc = *m++;\n\t\t\t\txrpnt = ((float *)xr) + *m++;\n\t\t\t\tif( *m++ == 0 ) break; // optimize: field will be set to zero at the end of the function\n\n\t\t\t\tm++; // cb\n\t\t\t}\n\t\t}\n\n\t\tgr_info->maxband[0] = max[0]+1;\n\t\tgr_info->maxband[1] = max[1]+1;\n\t\tgr_info->maxband[2] = max[2]+1;\n\t\tgr_info->maxbandl   = max[3]+1;\n\n\t\trmax = max[0] > max[1] ? max[0] : max[1];\n\t\trmax = (rmax > max[2] ? rmax : max[2]) + 1;\n\t\tgr_info->maxb = rmax ? fr->shortLimit[sfreq][rmax] : fr->longLimit[sfreq][max[3]+1];\n\t}\n\telse\n\t{\n\t\t// decoding with 'long' BandIndex table (block_type != 2)\n\t\tconst byte\t*pretab = pretab_choice[gr_info->preflag];\n\t\tint\t\t*m = map[sfreq][2];\n\t\tint\t\ti,max = -1;\n\t\tint\t\tcb = 0;\n\t\tregister float\tv = 0.0;\n\t\tint\t\tmc = 0;\n\n\t\t// long hash table values\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tconst struct newhuff\t*h = ht + gr_info->table_select[i];\n\t\t\tint\t\t\tlp = l[i];\n\n\t\t\tfor( ; lp; lp--, mc-- )\n\t\t\t{\n\t\t\t\tMASK_STYPE\tx, y;\n\n\t\t\t\tif( !mc )\n\t\t\t\t{\n\t\t\t\t\tmc = *m++;\n\t\t\t\t\tcb = *m++;\n\t\t\t\t\tv = gr_info->pow2gain[(*(scf++) + (*pretab++)) << shift];\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tconst short *val = h->table;\n\t\t\t\t\tREFRESH_MASK;\n\n\t\t\t\t\twhile(( y = *val++ ) < 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( MSB_MASK )\n\t\t\t\t\t\t\tval -= y;\n\n\t\t\t\t\t\tnum--;\n\t\t\t\t\t\tmask <<= 1;\n\t\t\t\t\t}\n\n\t\t\t\t\tx = y >> 4;\n\t\t\t\t\ty &= 0xf;\n\t\t\t\t}\n\n\t\t\t\tif( x == 15 && h->linbits )\n\t\t\t\t{\n\t\t\t\t\tmax = cb;\n\t\t\t\t\tREFRESH_MASK;\n\n\t\t\t\t\tx += ((MASK_UTYPE)mask) >> (BITSHIFT + 8 - h->linbits);\n\t\t\t\t\tnum -= h->linbits+1;\n\t\t\t\t\tmask <<= h->linbits;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt++ = REAL_MUL_SCALE_LAYER3(-ispow[x], v );\n\t\t\t\t\telse *xrpnt++ = REAL_MUL_SCALE_LAYER3( ispow[x], v );\n\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse if( x )\n\t\t\t\t{\n\t\t\t\t\tmax = cb;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt++ = REAL_MUL_SCALE_LAYER3( -ispow[x], v );\n\t\t\t\t\telse *xrpnt++ = REAL_MUL_SCALE_LAYER3( ispow[x], v );\n\t\t\t\t\tnum--;\n\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt++ = DOUBLE_TO_REAL( 0.0 );\n\n\t\t\t\tif( y == 15 && h->linbits )\n\t\t\t\t{\n\t\t\t\t\tmax = cb;\n\t\t\t\t\tREFRESH_MASK;\n\t\t\t\t\ty += ((MASK_UTYPE)mask) >> (BITSHIFT + 8 - h->linbits);\n\t\t\t\t\tnum -= h->linbits+1;\n\t\t\t\t\tmask <<= h->linbits;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt++ = REAL_MUL_SCALE_LAYER3( -ispow[y], v );\n\t\t\t\t\telse *xrpnt++ = REAL_MUL_SCALE_LAYER3( ispow[y], v );\n\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse if( y )\n\t\t\t\t{\n\t\t\t\t\tmax = cb;\n\t\t\t\t\tif( MSB_MASK ) *xrpnt++ = REAL_MUL_SCALE_LAYER3( -ispow[y], v );\n\t\t\t\t\telse *xrpnt++ = REAL_MUL_SCALE_LAYER3( ispow[y], v );\n\n\t\t\t\t\tnum--;\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt++ = DOUBLE_TO_REAL( 0.0 );\n\t\t\t}\n\t\t}\n\n\t\t// short (count1table) values\n\t\tfor( ; l3 && (part2remain + num > 0); l3-- )\n\t\t{\n\t\t\tconst struct newhuff\t*h = htc+gr_info->count1table_select;\n\t\t\tconst short\t\t*val = h->table;\n\t\t\tregister short\t\ta;\n\n\t\t\tREFRESH_MASK;\n\t\t\twhile(( a = *val++ ) < 0 )\n\t\t\t{\n\t\t\t\tif( MSB_MASK )\n\t\t\t\t\tval -= a;\n\n\t\t\t\tnum--;\n\t\t\t\tmask <<= 1;\n\t\t\t}\n\n\t\t\tif( part2remain + num <= 0 )\n\t\t\t{\n\t\t\t\tnum -= part2remain + num;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor( i = 0; i < 4; i++ )\n\t\t\t{\n\t\t\t\tif(!( i & 1 ))\n\t\t\t\t{\n\t\t\t\t\tif( !mc )\n\t\t\t\t\t{\n\t\t\t\t\t\tmc = *m++;\n\t\t\t\t\t\tcb = *m++;\n\n\t\t\t\t\t\tv = gr_info->pow2gain[((*scf++) + (*pretab++)) << shift];\n\t\t\t\t\t}\n\t\t\t\t\tmc--;\n\t\t\t\t}\n\n\t\t\t\tif(( a & (0x8 >> i)))\n\t\t\t\t{\n\t\t\t\t\tmax = cb;\n\t\t\t\t\tif( part2remain + num <= 0 )\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tif( MSB_MASK ) *xrpnt++ = -REAL_SCALE_LAYER3( v );\n\t\t\t\t\telse *xrpnt++ = REAL_SCALE_LAYER3( v );\n\n\t\t\t\t\tnum--;\n\t\t\t\t\tmask <<= 1;\n\t\t\t\t}\n\t\t\t\telse *xrpnt++ = DOUBLE_TO_REAL( 0.0 );\n\t\t\t}\n\t\t}\n\n\t\tgr_info->maxbandl = max+1;\n\t\tgr_info->maxb = fr->longLimit[sfreq][gr_info->maxbandl];\n\t}\n\n\tpart2remain += num;\n\tbackbits( fr, num );\n\tnum = 0;\n\n\twhile( xrpnt < &xr[SBLIMIT][0] )\n\t\t*xrpnt++ = DOUBLE_TO_REAL( 0.0 );\n\n\twhile( part2remain > 16 )\n\t{\n\t\tskipbits( fr, 16 ); // dismiss stuffing Bits\n\t\tpart2remain -= 16;\n\t}\n\n\tif( part2remain > 0 )\n\t{\n\t\tskipbits( fr, part2remain );\n\t}\n\telse if( part2remain < 0 )\n\t{\n\t\t// error\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n// calculate float channel values for Joint-I-Stereo-mode\nstatic void III_i_stereo( float xr_buf[2][SBLIMIT][SSLIMIT], int *scalefac, gr_info_t *gr_info, int sfreq, int ms_stereo, int lsf )\n{\n\tfloat (*xr)[SBLIMIT*SSLIMIT] = (float(*)[SBLIMIT*SSLIMIT])xr_buf;\n\tconst bandInfoStruct *bi = &bandInfo[sfreq];\n\tconst float *tab1, *tab2;\n\tint tab;\n\n\t// TODO: optimize as static\n\tconst float *tabs[3][2][2] =\n\t{\n\t{ { tan1_1,tan2_1 }       , { tan1_2,tan2_2 } },\n\t{ { pow1_1[0],pow2_1[0] } , { pow1_2[0],pow2_2[0] } },\n\t{ { pow1_1[1],pow2_1[1] } , { pow1_2[1],pow2_2[1] } }\n\t};\n\n\ttab = lsf + (gr_info->scalefac_compress & lsf);\n\ttab1 = tabs[tab][ms_stereo][0];\n\ttab2 = tabs[tab][ms_stereo][1];\n\n\tif( gr_info->block_type == 2 )\n\t{\n\t\tint\tlwin, do_l = 0;\n\n\t\tif( gr_info->mixed_block_flag )\n\t\t\tdo_l = 1;\n\n\t\tfor( lwin = 0; lwin < 3; lwin++ )\n\t\t{\n\t\t\tint\tis_p, sb, idx;\n\t\t\tint\tsfb = gr_info->maxband[lwin];  // sfb is minimal 3 for mixed mode\n\n\t\t\tif( sfb > 3 ) do_l = 0;\n\n\t\t\t// process each window\n\t\t\t// get first band with zero values\n\t\t\tfor( ; sfb < 12; sfb++ )\n\t\t\t{\n\t\t\t\tis_p = scalefac[sfb * 3 + lwin - gr_info->mixed_block_flag]; // scale: 0-15\n\n\t\t\t\tif( is_p != 7 )\n\t\t\t\t{\n\t\t\t\t\tfloat\tt1, t2;\n\n\t\t\t\t\tsb = bi->shortDiff[sfb];\n\t\t\t\t\tidx = bi->shortIdx[sfb] + lwin;\n\t\t\t\t\tt1 = tab1[is_p];\n\t\t\t\t\tt2 = tab2[is_p];\n\n\t\t\t\t\tfor( ; sb > 0; sb--, idx += 3 )\n\t\t\t\t\t{\n\t\t\t\t\t\tfloat v = xr[0][idx];\n\t\t\t\t\t\txr[0][idx] = REAL_MUL_15( v, t1 );\n\t\t\t\t\t\txr[1][idx] = REAL_MUL_15( v, t2 );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// in the original: copy 10 to 11 , here: copy 11 to 12\n\t\t\t// maybe still wrong??? (copy 12 to 13?)\n\t\t\tis_p = scalefac[11 * 3 + lwin - gr_info->mixed_block_flag]; // scale: 0-15\n\t\t\tsb = bi->shortDiff[12];\n\t\t\tidx  = bi->shortIdx[12] + lwin;\n\n\t\t\tif( is_p != 7 )\n\t\t\t{\n\t\t\t\tfloat\tt1, t2;\n\n\t\t\t\tt1 = tab1[is_p];\n\t\t\t\tt2 = tab2[is_p];\n\n\t\t\t\tfor( ; sb > 0; sb--, idx += 3 )\n\t\t\t\t{\n\t\t\t\t\tfloat v = xr[0][idx];\n\t\t\t\t\txr[0][idx] = REAL_MUL_15( v, t1 );\n\t\t\t\t\txr[1][idx] = REAL_MUL_15( v, t2 );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// also check l-part, if ALL bands in the three windows are 'empty' and mode = mixed_mode\n\t\tif( do_l )\n\t\t{\n\t\t\tint\tidx, sfb = gr_info->maxbandl;\n\n\t\t\tif( sfb > 21 ) return; // similarity fix related to CVE-2006-1655\n\n\t\t\tidx = bi->longIdx[sfb];\n\n\t\t\tfor( ; sfb < 8; sfb++ )\n\t\t\t{\n\t\t\t\tint\tsb = bi->longDiff[sfb];\n\t\t\t\tint\tis_p = scalefac[sfb]; // scale: 0-15\n\n\t\t\t\tif( is_p != 7 )\n\t\t\t\t{\n\t\t\t\t\tfloat\tt1, t2;\n\n\t\t\t\t\tt1 = tab1[is_p];\n\t\t\t\t\tt2 = tab2[is_p];\n\n\t\t\t\t\tfor( ; sb > 0; sb--, idx++ )\n\t\t\t\t\t{\n\t\t\t\t\t\tfloat v = xr[0][idx];\n\t\t\t\t\t\txr[0][idx] = REAL_MUL_15( v, t1 );\n\t\t\t\t\t\txr[1][idx] = REAL_MUL_15( v, t2 );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse idx += sb;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tint\tsfb = gr_info->maxbandl;\n\t\tint\tis_p, idx;\n\n\t\tif( sfb > 21 ) return; // tightened fix for CVE-2006-1655\n\n\t\tidx = bi->longIdx[sfb];\n\n\t\tfor( ; sfb < 21; sfb++ )\n\t\t{\n\t\t\tint\tsb = bi->longDiff[sfb];\n\n\t\t\tis_p = scalefac[sfb]; // scale: 0-15\n\n\t\t\tif( is_p != 7 )\n\t\t\t{\n\t\t\t\tfloat\tt1, t2;\n\n\t\t\t\tt1 = tab1[is_p];\n\t\t\t\tt2 = tab2[is_p];\n\n\t\t\t\tfor( ; sb > 0; sb--, idx++ )\n\t\t\t\t{\n\t\t\t\t\t float v = xr[0][idx];\n\t\t\t\t\t xr[0][idx] = REAL_MUL_15( v, t1 );\n\t\t\t\t\t xr[1][idx] = REAL_MUL_15( v, t2 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse idx += sb;\n\t\t}\n\n\t\tis_p = scalefac[20];\n\n\t\tif( is_p != 7 )\n\t\t{\n\t\t\tfloat\tt1, t2;\n\t\t\tint\tsb;\n\n\t\t\tt1 = tab1[is_p],\n\t\t\tt2 = tab2[is_p];\n\n\t\t\t// copy l-band 20 to l-band 21\n\t\t\tfor( sb = bi->longDiff[21]; sb > 0; sb--, idx++ )\n\t\t\t{\n\t\t\t\tfloat v = xr[0][idx];\n\t\t\t\txr[0][idx] = REAL_MUL_15( v, t1 );\n\t\t\t\txr[1][idx] = REAL_MUL_15( v, t2 );\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void III_antialias( float xr[SBLIMIT][SSLIMIT], gr_info_t *gr_info )\n{\n\tint\tsblim, sb;\n\tfloat\t*xr1;\n\n\tif( gr_info->block_type == 2 )\n\t{\n\t\tif( !gr_info->mixed_block_flag )\n\t\t\treturn;\n\t\tsblim = 1;\n\t}\n\telse\n\t{\n\t\tsblim = gr_info->maxb-1;\n\t}\n\n\t// 31 alias-reduction operations between each pair of sub-bands\n\t// with 8 butterflies between each pair\n\txr1 = (float *)xr[1];\n\n\tfor( sb = sblim; sb; sb--, xr1 += 10 )\n\t{\n\t\tfloat\t*cs = aa_cs;\n\t\tfloat\t*ca = aa_ca;\n\t\tfloat\t*xr2 = xr1;\n\t\tint\tss;\n\n\t\tfor( ss = 7; ss >= 0; ss-- )\n\t\t{\n\t\t\t// upper and lower butterfly inputs\n\t\t\tregister float bu = *--xr2;\n\t\t\tregister float bd = *xr1;\n\n\t\t\t*xr2 = REAL_MUL( bu, *cs ) - REAL_MUL( bd, *ca );\n\t\t\t*xr1++ = REAL_MUL( bd, *cs++ ) + REAL_MUL( bu, *ca++ );\n\t\t}\n\t}\n}\n\nstatic void III_hybrid( float fsIn[SBLIMIT][SSLIMIT], float tsOut[SSLIMIT][SBLIMIT], int ch, gr_info_t *gr_info, mpg123_handle_t *fr )\n{\n\tfloat\t(*block)[2][SBLIMIT*SSLIMIT] = fr->hybrid_block;\n\tint\t*blc = fr->hybrid_blc;\n\tfloat\t*tspnt = (float *)tsOut;\n\tfloat\t*rawout1, *rawout2;\n\tint\tbt = 0, b, i;\n\tsize_t\tsb = 0;\n\n\tb = blc[ch];\n\trawout1 = block[b][ch];\n\tb=-b + 1;\n\trawout2 = block[b][ch];\n\tblc[ch] = b;\n\n\tif( gr_info->mixed_block_flag )\n\t{\n\t\tsb = 2;\n\t\tdct36( fsIn[0], rawout1, rawout2, win[0], tspnt );\n\t\tdct36( fsIn[1], rawout1+18, rawout2+18, win1[0], tspnt + 1 );\n\t\trawout1 += 36; rawout2 += 36; tspnt += 2;\n\t}\n\n\tbt = gr_info->block_type;\n\n\tif( bt == 2 )\n\t{\n\t\tfor( ; sb < gr_info->maxb; sb += 2, tspnt += 2, rawout1 += 36, rawout2 += 36 )\n\t\t{\n\t\t\tdct12( fsIn[sb], rawout1, rawout2, win[2], tspnt );\n\t\t\tdct12( fsIn[sb+1], rawout1 + 18, rawout2 + 18, win1[2], tspnt + 1 );\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( ; sb < gr_info->maxb; sb += 2, tspnt += 2, rawout1 += 36, rawout2 += 36 )\n\t\t{\n\t\t\tdct36( fsIn[sb], rawout1, rawout2, win[bt], tspnt );\n\t\t\tdct36( fsIn[sb+1], rawout1 + 18, rawout2 + 18, win1[bt], tspnt + 1 );\n\t\t}\n\t}\n\n\tfor( ; sb < SBLIMIT; sb++, tspnt++ )\n\t{\n\t\tfor( i = 0; i < SSLIMIT; i++ )\n\t\t{\n\t\t\ttspnt[i*SBLIMIT] = *rawout1++;\n\t\t\t*rawout2++ = DOUBLE_TO_REAL( 0.0 );\n\t\t}\n\t}\n}\n\n// and at the end... the main layer3 handler\nint do_layer3( mpg123_handle_t *fr )\n{\n\tint\t\tgr, ch, ss, clip = 0;\n\tint\t\tstereo = fr->stereo;\n\tint\t\tsingle = fr->single;\n\tint\t\tms_stereo, i_stereo;\n\tint\t\tsfreq = fr->sampling_frequency;\n\tint\t\tscalefacs[2][39]; // max 39 for short[13][3] mode, mixed: 38, long: 22\n\tint\t\tstereo1, granules;\n\tIII_sideinfo\tsideinfo;\n\n\tif( stereo == 1 )\n\t{\n\t\t// stream is mono\n\t\tstereo1 = 1;\n\t\tsingle = SINGLE_LEFT;\n\t}\n\telse if( single != SINGLE_STEREO )\n\t{\n\t\t// stream is stereo, but force to mono\n\t\tstereo1 = 1;\n\t}\n\telse\n\t{\n\t\tstereo1 = 2;\n\t}\n\n\tif( fr->mode == MPG_MD_JOINT_STEREO )\n\t{\n\t\tms_stereo = (fr->mode_ext & 0x2) >> 1;\n\t\ti_stereo  = fr->mode_ext & 0x1;\n\t}\n\telse\n\t{\n\t\tms_stereo = i_stereo = 0;\n\t}\n\n\tgranules = fr->lsf ? 1 : 2;\n\n\t// quick hack to keep the music playing\n\t// after having seen this nasty test file...\n\tif( III_get_side_info( fr, &sideinfo, stereo, ms_stereo, sfreq, single ))\n\t\treturn clip;\n\n\tset_pointer( fr, sideinfo.main_data_begin );\n\n\tfor( gr = 0; gr < granules; gr++ )\n\t{\n\t\tfloat\t(*hybridIn)[SBLIMIT][SSLIMIT] = fr->layer3.hybrid_in;\t//  hybridIn[2][SBLIMIT][SSLIMIT]\n\t\tfloat\t(*hybridOut)[SSLIMIT][SBLIMIT] = fr->layer3.hybrid_out;\t//  hybridOut[2][SSLIMIT][SBLIMIT]\n\t\tgr_info_t\t*gr_info = &(sideinfo.ch[0].gr[gr]);\n\t\tlong\tpart2bits;\n\n\t\tif( fr->lsf ) part2bits = III_get_scale_factors_2( fr, scalefacs[0], gr_info, 0 );\n\t\telse part2bits = III_get_scale_factors_1( fr, scalefacs[0], gr_info );\n\n\t\tif( III_dequantize_sample( fr, hybridIn[0], scalefacs[0], gr_info, sfreq, part2bits ))\n\t\t\treturn clip;\n\n\t\tif( stereo == 2 )\n\t\t{\n\t\t\tregister float\t*in0, *in1;\n\t\t\tregister int\ti;\n\n\t\t\tgr_info = &(sideinfo.ch[1].gr[gr]);\n\n\t\t\tif( fr->lsf ) part2bits = III_get_scale_factors_2( fr, scalefacs[1], gr_info, i_stereo );\n\t\t\telse part2bits = III_get_scale_factors_1( fr, scalefacs[1], gr_info );\n\n\t\t\tif( III_dequantize_sample( fr, hybridIn[1], scalefacs[1], gr_info, sfreq, part2bits ))\n\t\t\t\treturn clip;\n\n\t\t\tif( ms_stereo )\n\t\t\t{\n\t\t\t\tuint\tmaxb = sideinfo.ch[0].gr[gr].maxb;\n\t\t\t\tint\ti;\n\n\t\t\t\tif( sideinfo.ch[1].gr[gr].maxb > maxb )\n\t\t\t\t\tmaxb = sideinfo.ch[1].gr[gr].maxb;\n\n\t\t\t\tfor( i = 0; i < SSLIMIT * (int)maxb; i++ )\n\t\t\t\t{\n\t\t\t\t\tfloat tmp0 = ((float *)hybridIn[0])[i];\n\t\t\t\t\tfloat tmp1 = ((float *)hybridIn[1])[i];\n\t\t\t\t\t((float *)hybridIn[0])[i] = tmp0 + tmp1;\n\t\t\t\t\t((float *)hybridIn[1])[i] = tmp0 - tmp1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( i_stereo )\n\t\t\t\tIII_i_stereo( hybridIn, scalefacs[1], gr_info, sfreq, ms_stereo, fr->lsf );\n\n\t\t\tif( ms_stereo || i_stereo || ( single == SINGLE_MIX ))\n\t\t\t{\n\t\t\t\tif( gr_info->maxb > sideinfo.ch[0].gr[gr].maxb )\n\t\t\t\t\tsideinfo.ch[0].gr[gr].maxb = gr_info->maxb;\n\t\t\t\telse gr_info->maxb = sideinfo.ch[0].gr[gr].maxb;\n\t\t\t}\n\n\t\t\tswitch( single )\n\t\t\t{\n\t\t\tcase SINGLE_MIX:\n\t\t\t\tin0 = (float *)hybridIn[0];\n\t\t\t\tin1 = (float *)hybridIn[1];\n\n\t\t\t\tfor( i = 0; i < SSLIMIT * (int)gr_info->maxb; i++, in0++ )\n\t\t\t\t\t*in0 = (*in0 + *in1++); // *0.5 done by pow-scale\n\t\t\t\tbreak;\n\t\t\tcase SINGLE_RIGHT:\n\t\t\t\tin0 = (float *)hybridIn[0];\n\t\t\t\tin1 = (float *)hybridIn[1];\n\n\t\t\t\tfor( i = 0; i < SSLIMIT * (int)gr_info->maxb; i++ )\n\t\t\t\t\t*in0++ = *in1++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfor( ch = 0; ch < stereo1; ch++ )\n\t\t{\n\t\t\tgr_info = &(sideinfo.ch[ch].gr[gr]);\n\t\t\tIII_antialias( hybridIn[ch], gr_info );\n\t\t\tIII_hybrid( hybridIn[ch], hybridOut[ch], ch,gr_info, fr );\n\t\t}\n\n\t\tfor( ss = 0; ss < SSLIMIT; ss++ )\n\t\t{\n\t\t\tif( single != SINGLE_STEREO )\n\t\t\t\tclip += (fr->synth_mono)(hybridOut[0][ss], fr );\n\t\t\telse clip += (fr->synth_stereo)(hybridOut[0][ss], hybridOut[1][ss], fr );\n\n\t\t}\n\t}\n\n\treturn clip;\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/libmpg.c",
    "content": "/*\nlibmpg.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include \"libmpg.h\"\n\nvoid *create_decoder( int *error )\n{\n\tvoid\t*mpg;\n\tint\tret;\n\n\tif( error ) *error = 0;\n\tmpg123_init();\n\n\tmpg = mpg123_new( &ret );\n\tif( !mpg ) return NULL;\n\n\tret = mpg123_param( mpg, MPG123_FLAGS, MPG123_FUZZY|MPG123_SEEKBUFFER|MPG123_GAPLESS );\n\tif( ret != MPG123_OK && error )\n\t\t*error = 1;\n\n\t// let the seek index auto-grow and contain an entry for every frame\n\tret = mpg123_param( mpg, MPG123_INDEX_SIZE, -1 );\n\tif( ret != MPG123_OK && error )\n\t\t*error = 1;\n\n\treturn mpg;\n}\n\nint feed_mpeg_header( void *mpg, const byte *data, long bufsize, long streamsize, wavinfo_t *sc )\n{\n\tmpg123_handle_t\t*mh = (mpg123_handle_t *)mpg;\n\tint\t\tret, no;\n\n\tif( !mh || !sc ) return 0;\n\n\tret = mpg123_open_feed( mh );\n\tif( ret != MPG123_OK )\n\t\treturn 0;\n\n\t// feed input chunk and get first chunk of decoded audio.\n\tret = mpg123_decode( mh, data, bufsize, NULL, 0, NULL );\n\n\tif( ret != MPG123_NEW_FORMAT )\n\t\treturn 0;\t// there were errors\n\n\tmpg123_getformat( mh, &sc->rate, &sc->channels, &no );\n\tmpg123_format_none( mh );\n\tmpg123_format( mh, sc->rate, sc->channels, MPG123_ENC_SIGNED_16 );\n\n\t// some hacking to get function get_songlen to working properly\n\tmh->rdat.filelen = streamsize;\n\tsc->playtime = get_songlen( mh, -1 ) * 1000;\n\n\treturn 1;\n}\n\nint feed_mpeg_stream( void *mpg, const byte *data, long bufsize, byte *outbuf, size_t *outsize )\n{\n\tswitch( mpg123_decode( mpg, data, bufsize, outbuf, OUTBUF_SIZE, outsize ))\n\t{\n\tcase MPG123_NEED_MORE:\n\t\treturn MP3_NEED_MORE;\n\tcase MPG123_OK:\n\t\treturn MP3_OK;\n\tdefault:\n\t\treturn MP3_ERR;\n\t}\n}\n\nint open_mpeg_stream( void *mpg, void *file, pfread f_read, pfseek f_seek, wavinfo_t *sc )\n{\n\tmpg123_handle_t\t*mh = (mpg123_handle_t *)mpg;\n\tint\t\tret, no;\n\n\tif( !mh || !sc ) return 0;\n\n\tret = mpg123_replace_reader_handle( mh, f_read, f_seek, NULL );\n\tif( ret != MPG123_OK )\n\t\treturn 0;\n\n\tret = mpg123_open_handle( mh, file );\n\tif( ret != MPG123_OK )\n\t\treturn 0;\n\n\tret = mpg123_getformat( mh, &sc->rate, &sc->channels, &no );\n\tif( ret != MPG123_OK )\n\t\treturn 0;\n\n\tmpg123_format_none( mh );\n\tmpg123_format( mh, sc->rate, sc->channels, MPG123_ENC_SIGNED_16 );\n\tsc->playtime = get_songlen( mh, -1 ) * 1000;\n\n\treturn 1;\n}\n\nint read_mpeg_stream( void *mpg, byte *outbuf, size_t *outsize  )\n{\n\tswitch( mpg123_read( mpg, outbuf, OUTBUF_SIZE, outsize ))\n\t{\n\tcase MPG123_OK:\n\t\treturn MP3_OK;\n\tdefault:\n\t\treturn MP3_ERR;\n\t}\n}\n\nint get_stream_pos( void *mpg )\n{\n\treturn mpg123_tell( mpg );\n}\n\nint set_stream_pos( void *mpg, int curpos )\n{\n\treturn mpg123_seek( mpg, curpos, SEEK_SET );\n}\n\nvoid close_decoder( void *mpg )\n{\n\tmpg123_delete( mpg );\n\tmpg123_exit();\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/libmpg.dsp",
    "content": "# Microsoft Developer Studio Project File - Name=\"libmpg\" - Package Owner=<4>\n# Microsoft Developer Studio Generated Build File, Format Version 6.00\n# ** DO NOT EDIT **\n\n# TARGTYPE \"Win32 (x86) Static Library\" 0x0104\n\nCFG=libmpg - Win32 Release\n!MESSAGE This is not a valid makefile. To build this project using NMAKE,\n!MESSAGE use the Export Makefile command and run\n!MESSAGE \n!MESSAGE NMAKE /f \"libmpg.mak\".\n!MESSAGE \n!MESSAGE You can specify a configuration when running NMAKE\n!MESSAGE by defining the macro CFG on the command line. For example:\n!MESSAGE \n!MESSAGE NMAKE /f \"libmpg.mak\" CFG=\"libmpg - Win32 Release\"\n!MESSAGE \n!MESSAGE Possible choices for configuration are:\n!MESSAGE \n!MESSAGE \"libmpg - Win32 Release\" (based on \"Win32 (x86) Static Library\")\n!MESSAGE \"libmpg - Win32 Debug\" (based on \"Win32 (x86) Static Library\")\n!MESSAGE \n\n# Begin Project\n# PROP AllowPerConfigDependencies 0\n# PROP Scc_ProjName \"\"\n# PROP Scc_LocalPath \"\"\nCPP=cl.exe\nRSC=rc.exe\n\n!IF  \"$(CFG)\" == \"libmpg - Win32 Release\"\n\n# PROP BASE Use_MFC 0\n# PROP BASE Use_Debug_Libraries 0\n# PROP BASE Output_Dir \"Release\"\n# PROP BASE Intermediate_Dir \"Release\"\n# PROP BASE Target_Dir \"\"\n# PROP Use_MFC 0\n# PROP Use_Debug_Libraries 0\n# PROP Output_Dir \"..\\temp\\libmpg\\!release\"\n# PROP Intermediate_Dir \"..\\temp\\libmpg\\!release\"\n# PROP Target_Dir \"\"\n# ADD BASE CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_MBCS\" /D \"_LIB\" /YX /FD /c\n# ADD CPP /nologo /MD /W3 /GX /O1 /I \"./\" /D \"WIN32\" /D \"NDEBUG\" /D \"_MBCS\" /D \"_LIB\" /FD /c\n# SUBTRACT CPP /YX /Yc /Yu\n# ADD BASE RSC /l 0x419 /d \"NDEBUG\"\n# ADD RSC /l 0x419 /d \"NDEBUG\"\nBSC32=bscmake.exe\n# ADD BASE BSC32 /nologo\n# ADD BSC32 /nologo\nLIB32=link.exe -lib\n# ADD BASE LIB32 /nologo\n# ADD LIB32 /nologo /out:\"..\\mpeg.lib\"\n\n!ELSEIF  \"$(CFG)\" == \"libmpg - Win32 Debug\"\n\n# PROP BASE Use_MFC 0\n# PROP BASE Use_Debug_Libraries 1\n# PROP BASE Output_Dir \"Debug\"\n# PROP BASE Intermediate_Dir \"Debug\"\n# PROP BASE Target_Dir \"\"\n# PROP Use_MFC 0\n# PROP Use_Debug_Libraries 1\n# PROP Output_Dir \"..\\temp\\libmpg\\!debug\"\n# PROP Intermediate_Dir \"..\\temp\\libmpg\\!debug\"\n# PROP Target_Dir \"\"\n# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_MBCS\" /D \"_LIB\" /YX /FD /GZ /c\n# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I \"./\" /D \"WIN32\" /D \"_DEBUG\" /D \"_MBCS\" /D \"_LIB\" /FAs /FR /FD /GZ /c\n# SUBTRACT CPP /YX /Yc /Yu\n# ADD BASE RSC /l 0x419 /d \"_DEBUG\"\n# ADD RSC /l 0x419 /d \"_DEBUG\"\nBSC32=bscmake.exe\n# ADD BASE BSC32 /nologo\n# ADD BSC32 /nologo\nLIB32=link.exe -lib\n# ADD BASE LIB32 /nologo\n# ADD LIB32 /nologo /out:\"..\\mpeg_dbg.lib\"\n\n!ENDIF \n\n# Begin Target\n\n# Name \"libmpg - Win32 Release\"\n# Name \"libmpg - Win32 Debug\"\n# Begin Group \"Source Files\"\n\n# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\n# Begin Source File\n\nSOURCE=.\\dct36.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\dct64.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\format.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\frame.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\index.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\layer3.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\libmpg.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\mpg123.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\parse.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\reader.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\synth.c\n# End Source File\n# Begin Source File\n\nSOURCE=.\\tabinit.c\n# End Source File\n# End Group\n# Begin Group \"Header Files\"\n\n# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\n# Begin Source File\n\nSOURCE=.\\fmt123.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\frame.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\getbits.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\huffman.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\index.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\libmpg.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\mpg123.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\reader.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\sample.h\n# End Source File\n# Begin Source File\n\nSOURCE=.\\synth.h\n# End Source File\n# End Group\n# End Target\n# End Project\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/libmpg.h",
    "content": "/*\nlibmpg.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef LIBMPG_H\n#define LIBMPG_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// error codes\n#define MP3_ERR\t\t-1\n#define MP3_OK\t\t0\n#define MP3_NEED_MORE\t1\n\n#define OUTBUF_SIZE\t\t8192\t// don't change!\n\ntypedef struct\n{\n\tint\trate;\t\t// num samples per second (e.g. 11025 - 11 khz)\n\tint\tchannels;\t\t// num channels (1 - mono, 2 - stereo)\n\tint\tplaytime;\t\t// stream size in milliseconds\n} wavinfo_t;\n\n#ifdef _MSC_VER // a1ba: MSVC6 don't have ssize_t\ntypedef long\t\tmpg_ssize_t;\n#else\ntypedef ssize_t\t\tmpg_ssize_t;\n#endif\n\n// custom stdio\ntypedef mpg_ssize_t (*pfread)( void *handle, void *buf, size_t count );\ntypedef fs_offset_t (*pfseek)( void *handle, fs_offset_t offset, int whence );\n\nextern void *create_decoder( int *error );\nextern int feed_mpeg_header( void *mpg, const byte *data, long bufsize, long streamsize, wavinfo_t *sc );\nextern int feed_mpeg_stream( void *mpg, const byte *data, long bufsize, byte *outbuf, size_t *outsize );\nextern int open_mpeg_stream( void *mpg, void *file, pfread f_read, pfseek f_seek, wavinfo_t *sc );\nextern int read_mpeg_stream(void *mpg, byte *outbuf, size_t *outsize  );\nextern int get_stream_pos( void *mpg );\nextern int set_stream_pos( void *mpg, int curpos );\nextern void close_decoder( void *mpg );\nconst char *get_error( void *mpeg );\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif//LIBMPG_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/mpeghead.h",
    "content": "/*\nmpeghead.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef MPEGHEAD_H\n#define MPEGHEAD_H\n\n#define HDR_SYNC\t\t0xffe00000\n#define HDR_SYNC_VAL(h)\t(((h) & HDR_SYNC) >> 21)\n#define HDR_VERSION\t\t0x00180000\n#define HDR_VERSION_VAL(h)\t(((h) & HDR_VERSION) >> 19)\n#define HDR_LAYER\t\t0x00060000\n#define HDR_LAYER_VAL(h)\t(((h) & HDR_LAYER) >> 17)\n#define HDR_CRC\t\t0x00010000\n#define HDR_CRC_VAL(h)\t(((h) & HDR_CRC) >> 16)\n#define HDR_BITRATE\t\t0x0000f000\n#define HDR_BITRATE_VAL(h)\t(((h) & HDR_BITRATE) >> 12)\n#define HDR_SAMPLERATE\t0x00000c00\n#define HDR_SAMPLERATE_VAL(h)\t(((h) & HDR_SAMPLERATE) >> 10)\n#define HDR_PADDING\t\t0x00000200\n#define HDR_PADDING_VAL(h)\t(((h) & HDR_PADDING) >> 9)\n#define HDR_PRIVATE\t\t0x00000100\n#define HDR_PRIVATE_VAL(h)\t(((h) & HDR_PRIVATE) >> 8)\n#define HDR_CHANNEL\t\t0x000000c0\n#define HDR_CHANNEL_VAL(h)\t(((h) & HDR_CHANNEL) >> 6)\n#define HDR_CHANEX\t\t0x00000030\n#define HDR_CHANEX_VAL(h)\t(((h) & HDR_CHANEX) >> 4)\n#define HDR_COPYRIGHT\t0x00000008\n#define HDR_COPYRIGHT_VAL(h)\t(((h) & HDR_COPYRIGHT) >> 3)\n#define HDR_ORIGINAL\t0x00000004\n#define HDR_ORIGINAL_VAL(h)\t(((h) & HDR_ORIGINAL) >> 2)\n#define HDR_EMPHASIS\t0x00000003\n#define HDR_EMPHASIS_VAL(h)\t(((h) & HDR_EMPHASIS) >> 0)\n\n\n// a generic mask for telling if a header is somewhat valid for the current stream.\n// meaning: Most basic info is not allowed to change.\n// checking of channel count needs to be done, too, though. So,\n// if channel count matches, frames are decoded the same way: frame buffers and decoding\n// routines can stay the same, especially frame buffers (think spf * channels!).\n#define HDR_CMPMASK\t\t(HDR_SYNC|HDR_VERSION|HDR_LAYER|HDR_SAMPLERATE)\n\n// A stricter mask, for matching free format headers.\n#define HDR_SAMEMASK\t(HDR_SYNC|HDR_VERSION|HDR_LAYER|HDR_BITRATE|HDR_SAMPLERATE|HDR_CHANNEL|HDR_CHANEX)\n\n// free format headers have zero bitrate value.\n#define HDR_FREE_FORMAT(head)\t(!(head & HDR_BITRATE))\n\n// a mask for changed sampling rate (version or rate bits).\n#define HDR_SAMPMASK\t(HDR_VERSION|HDR_SAMPLERATE)\n\n#endif//MPEGHEAD_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/mpg123.c",
    "content": "/*\nmpg123.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include \"sample.h\"\n#include \"libmpg.h\"\n\nstatic int\tinitialized = 0;\n\nint mpg123_init( void )\n{\n\tif(( sizeof( short ) != 2 ) || ( sizeof( long ) < 4 ))\n\t\treturn MPG123_BAD_TYPES;\n\n\tif( initialized )\n\t\treturn MPG123_OK; // no need to initialize twice\n\n\tinit_layer3();\n\tprepare_decode_tables();\n\tinitialized = 1;\n\n#ifdef IEEE_FLOAT\n\t// this is rather pointless but it eases my mind to check that we did\n\t// not enable the special rounding on a VAX or something.\n\tif( REAL_TO_SHORT_ACCURATE( 12345.67f ) != 12346 )\n\t{\n\t\treturn MPG123_ERR;\n\t}\n#endif\n\treturn MPG123_OK;\n}\n\nvoid mpg123_exit( void )\n{\n\t// nothing yet, but something later perhaps\n}\n\n// create a new handle with specified decoder, decoder can be \"\", \"auto\" or NULL for auto-detection\nmpg123_handle_t *mpg123_new( int *error )\n{\n\treturn mpg123_parnew( NULL, error );\n}\n\n// ...the full routine with optional initial parameters to override defaults.\nmpg123_handle_t *mpg123_parnew( mpg123_parm_t *mp, int *error )\n{\n\tmpg123_handle_t\t*fr = NULL;\n\tint\t\terr = MPG123_OK;\n\n\tif( initialized )\n\t\tfr = (mpg123_handle_t *)malloc( sizeof( mpg123_handle_t ));\n\telse err = MPG123_NOT_INITIALIZED;\n\n\tif( fr != NULL )\n\t{\n\t\tframe_init_par( fr, mp );\n\t\tinit_synth( fr );\n\t}\n\n\tif( fr != NULL )\n\t{\n\t\tfr->decoder_change = 1;\n\t}\n\telse if( err == MPG123_OK )\n\t{\n\t\terr = MPG123_OUT_OF_MEM;\n\t}\n\n\tif( error != NULL )\n\t\t*error = err;\n\n\treturn fr;\n}\n\nstatic int mpg123_par( mpg123_parm_t *mp, enum mpg123_parms key, long val )\n{\n\tint\tret = MPG123_OK;\n\n\tif( mp == NULL )\n\t\treturn MPG123_BAD_PARS;\n\n\tswitch( key )\n\t{\n\tcase MPG123_VERBOSE:\n\t\tmp->verbose = val;\n\t\tbreak;\n\tcase MPG123_FLAGS:\n\t\tif( ret == MPG123_OK )\n\t\t\tmp->flags = val;\n\t\tbreak;\n\tcase MPG123_ADD_FLAGS:\n\t\tmp->flags |= val;\n\t\tbreak;\n\tcase MPG123_REMOVE_FLAGS:\n\t\tmp->flags &= ~val;\n\t\tbreak;\n\tcase MPG123_FORCE_RATE: // should this trigger something\n\t\tif( val > 0 )\n\t\t\tret = MPG123_BAD_RATE;\n\t\tbreak;\n\tcase MPG123_DOWN_SAMPLE:\n\t\tif( val != 0 )\n\t\t\tret = MPG123_BAD_RATE;\n\t\tbreak;\n\tcase MPG123_RVA:\n\t\tif( val < 0 || val > MPG123_RVA_MAX )\n\t\t\tret = MPG123_BAD_RVA;\n\t\telse mp->rva = (int)val;\n\t\tbreak;\n\tcase MPG123_DOWNSPEED:\n\t\tmp->halfspeed = val < 0 ? 0 : val;\n\t\tbreak;\n\tcase MPG123_UPSPEED:\n\t\tmp->doublespeed = val < 0 ? 0 : val;\n\t\tbreak;\n\tcase MPG123_OUTSCALE:\n\t\t// choose the value that is non-zero, if any.\n\t\t// downscaling integers to 1.0.\n\t\tmp->outscale = (double)val / SHORT_SCALE;\n\t\tbreak;\n\tcase MPG123_TIMEOUT:\n\t\tif( val > 0 ) ret = MPG123_NO_TIMEOUT;\n\t\tbreak;\n\tcase MPG123_RESYNC_LIMIT:\n\t\tmp->resync_limit = val;\n\t\tbreak;\n\tcase MPG123_INDEX_SIZE:\n\t\tmp->index_size = val;\n\t\tbreak;\n\tcase MPG123_PREFRAMES:\n\t\tif( val >= 0 ) mp->preframes = val;\n\t\telse ret = MPG123_BAD_VALUE;\n\t\tbreak;\n\tcase MPG123_FEEDPOOL:\n\t\tif( val >= 0 ) mp->feedpool = val;\n\t\telse ret = MPG123_BAD_VALUE;\n\t\tbreak;\n\tcase MPG123_FEEDBUFFER:\n\t\tif( val > 0 ) mp->feedbuffer = val;\n\t\telse ret = MPG123_BAD_VALUE;\n\t\tbreak;\n\tdefault:\n\t\tret = MPG123_BAD_PARAM;\n\t}\n\n\treturn ret;\n}\n\nint mpg123_param( mpg123_handle_t *mh, enum mpg123_parms key, long val )\n{\n\tint\tr;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tr = mpg123_par(&mh->p, key, val );\n\tif( r != MPG123_OK )\n\t{\n\t\tmh->err = r;\n\t\treturn MPG123_ERR;\n\t}\n\telse\n\t{\n\t\t// special treatment for some settings.\n\t\tif( key == MPG123_INDEX_SIZE )\n\t\t{\n\t\t\t// apply frame index size and grow property on the fly.\n\t\t\tr = frame_index_setup( mh );\n\t\t\tif( r != MPG123_OK )\n\t\t\t\tmh->err = MPG123_INDEX_FAIL;\n\t\t}\n\n\t\t// feeder pool size is applied right away, reader will react to that.\n\t\tif( key == MPG123_FEEDPOOL || key == MPG123_FEEDBUFFER )\n\t\t\tbc_poolsize( &mh->rdat.buffer, mh->p.feedpool, mh->p.feedbuffer );\n\n\t\treturn r;\n\t}\n}\n\nstatic int mpg123_close( mpg123_handle_t *mh )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\t// mh->rd is never NULL!\n\tif( mh->rd->close != NULL )\n\t\tmh->rd->close( mh );\n\n\tif( mh->new_format )\n\t{\n\t\tinvalidate_format( &mh->af );\n\t\tmh->new_format = 0;\n\t}\n\n\t// always reset the frame buffers on close, so we cannot forget it in funky opening routines (wrappers, even).\n\tframe_reset( mh );\n\n\treturn MPG123_OK;\n}\n\nvoid mpg123_delete( mpg123_handle_t *mh )\n{\n\tif( mh != NULL )\n\t{\n\t\tmpg123_close( mh );\n\t\tframe_exit( mh );\t// free buffers in frame\n\t\tfree( mh );\t// free struct; cast?\n\t}\n}\n\nint mpg123_open_handle( mpg123_handle_t *mh, void *iohandle )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tmpg123_close( mh );\n\n\tif( mh->rdat.r_read_handle == NULL )\n\t{\n\t\tmh->err = MPG123_BAD_CUSTOM_IO;\n\t\treturn MPG123_ERR;\n\t}\n\n\treturn open_stream_handle( mh, iohandle );\n}\n\nint mpg123_open_feed( mpg123_handle_t *mh )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tmpg123_close( mh );\n\n\treturn open_feed( mh );\n}\n\nint mpg123_replace_reader_handle( mpg123_handle_t *mh, mpg_ssize_t (*fread)( void*, void*, size_t), mpg_off_t (*lseek)(void*, mpg_off_t, int), void(*fclose)(void*))\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tmpg123_close( mh );\n\tmh->rdat.r_read_handle = fread;\n\tmh->rdat.r_lseek_handle = lseek;\n\tmh->rdat.cleanup_handle = fclose;\n\n\treturn MPG123_OK;\n}\n\n// update decoding engine for\n// a) a new choice of decoder\n// b) a changed native format of the MPEG stream\n// ... calls are only valid after parsing some MPEG frame!\nstatic int decode_update( mpg123_handle_t *mh )\n{\n\tlong\tnative_rate;\n\tint\tb;\n\n\tif( mh->num < 0 )\n\t{\n\t\tmh->err = MPG123_BAD_DECODER_SETUP;\n\t\treturn MPG123_ERR;\n\t}\n\n\tmh->state_flags |= FRAME_FRESH_DECODER;\n\tnative_rate = frame_freq( mh );\n\n\tb = frame_output_format( mh ); // select the new output format based on given constraints.\n\tif( b < 0 ) return MPG123_ERR;\n\tif( b == 1 ) mh->new_format = 1; // store for later...\n\n\tif( mh->af.rate == native_rate )\n\t\tmh->down_sample = 0;\n\telse if( mh->af.rate == native_rate >> 1 )\n\t\tmh->down_sample = 1;\n\telse if( mh->af.rate == native_rate >> 2 )\n\t\tmh->down_sample = 2;\n\telse mh->down_sample = 3; // flexible (fixed) rate\n\n\tswitch( mh->down_sample )\n\t{\n\tcase 0:\n\tcase 1:\n\tcase 2:\n\t\tmh->down_sample_sblimit = SBLIMIT >> ( mh->down_sample );\n\t\t// with downsampling I get less samples per frame\n\t\tmh->outblock = outblock_bytes( mh, ( mh->spf >> mh->down_sample ));\n\t\tbreak;\n\t}\n\n\tif(!( mh->p.flags & MPG123_FORCE_MONO ))\n\t{\n\t\tif( mh->af.channels == 1 )\n\t\t\tmh->single = SINGLE_MIX;\n\t\telse mh->single = SINGLE_STEREO;\n\t}\n\telse mh->single = ( mh->p.flags & MPG123_FORCE_MONO ) - 1;\n\n\tif( set_synth_functions( mh ) != 0 )\n\t\treturn -1;\n\n\t// the needed size of output buffer may have changed.\n\tif( frame_outbuffer( mh ) != MPG123_OK )\n\t\treturn -1;\n\n\tdo_rva( mh );\n\n\treturn 0;\n}\n\nstatic size_t mpg123_safe_buffer( void )\n{\n\t// real is the largest possible output\n\treturn sizeof( float ) * 2 * 1152;\n}\n\nstatic size_t mpg123_outblock( mpg123_handle_t *mh )\n{\n\t// try to be helpful and never return zero output block size.\n\tif( mh != NULL && mh->outblock > 0 )\n\t\treturn mh->outblock;\n\treturn mpg123_safe_buffer();\n}\n\n// read in the next frame we actually want for decoding.\n// this includes skipping/ignoring frames, in additon to skipping junk in the parser.\nstatic int get_next_frame( mpg123_handle_t *mh )\n{\n\tint\tchange = mh->decoder_change;\n\n\t// ensure we got proper decoder for ignoring frames.\n\t// header can be changed from seeking around. But be careful: Only after at\n\t// least one frame got read, decoder update makes sense.\n\tif( mh->header_change > 1 && mh->num >= 0 )\n\t{\n\t\tchange = 1;\n\t\tmh->header_change = 0;\n\n\t\tif( decode_update( mh ) < 0 )\n\t\t\treturn MPG123_ERR;\n\t}\n\n\tdo\n\t{\n\t\tint\tb;\n\n\t\t// decode & discard some frame(s) before beginning.\n\t\tif( mh->to_ignore && mh->num < mh->firstframe && mh->num >= mh->ignoreframe )\n\t\t{\n\t\t\t// decoder structure must be current! decode_update has been called before...\n\t\t\t(mh->do_layer)( mh );\n\t\t\tmh->buffer.fill = 0;\n\t\t\tmh->to_ignore = mh->to_decode = FALSE;\n\t\t}\n\n\t\t// read new frame data; possibly breaking out here for MPG123_NEED_MORE.\n\t\tmh->to_decode = FALSE;\n\t\tb = read_frame( mh ); // that sets to_decode only if a full frame was read.\n\n\t\tif( b == MPG123_NEED_MORE )\n\t\t{\n\t\t\treturn MPG123_NEED_MORE; // need another call with data\n\t\t}\n\t\telse if( b <= 0 )\n\t\t{\n\t\t\t// more sophisticated error control?\n\t\t\tif( b == 0 || ( mh->rdat.filelen >= 0 && mh->rdat.filepos == mh->rdat.filelen ))\n\t\t\t{\n\t\t\t\t// we simply reached the end.\n\t\t\t\tmh->track_frames = mh->num + 1;\n\n\t\t\t\treturn MPG123_DONE;\n\t\t\t}\n\n\t\t\treturn MPG123_ERR; // some real error.\n\t\t}\n\n\t\t// now, there should be new data to decode ... and also possibly new stream properties\n\t\tif( mh->header_change > 1 )\n\t\t{\n\t\t\tchange = 1;\n\t\t\tmh->header_change = 0;\n\n\t\t\t// need to update decoder structure right away since frame might need to\n\t\t\t// be decoded on next loop iteration for properly ignoring its output.\n\t\t\tif( decode_update( mh ) < 0 )\n\t\t\t\treturn MPG123_ERR;\n\t\t}\n\n\t\t// now some accounting: Look at the numbers and decide if we want this frame.\n\t\tmh->playnum++;\n\n\t\t// plain skipping without decoding, only when frame is not ignored on next cycle.\n\t\tif( mh->num < mh->firstframe || ( mh->p.doublespeed && ( mh->playnum % mh->p.doublespeed )))\n\t\t{\n\t\t\tif(!( mh->to_ignore && mh->num < mh->firstframe && mh->num >= mh->ignoreframe ))\n\t\t\t\tframe_skip( mh );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// or, we are finally done and have a new frame.\n\t\t\tbreak;\n\t\t}\n\t} while( 1 );\n\n\t// if we reach this point, we got a new frame ready to be decoded.\n\t// all other situations resulted in returns from the loop.\n\tif( change )\n\t{\n\t\tmh->decoder_change = 0;\n\n\t\tif( mh->fresh )\n\t\t{\n\t\t\tint b = 0;\n\t\t\t// prepare offsets for gapless decoding.\n\t\t\tframe_gapless_realinit( mh );\n\t\t\tframe_set_frameseek( mh, mh->num );\n\t\t\tmh->fresh = 0;\n\n\t\t\t// could this possibly happen? With a real big gapless offset...\n\t\t\tif( mh->num < mh->firstframe ) b = get_next_frame( mh );\n\t\t\tif( b < 0 ) return b; // Could be error, need for more, new format...\n\t\t}\n\t}\n\n\treturn MPG123_OK;\n}\n\nstatic int init_track( mpg123_handle_t *mh )\n{\n\tif( track_need_init( mh ))\n\t{\n\t\t// fresh track, need first frame for basic info.\n\t\tint b = get_next_frame( mh );\n\t\tif( b < 0 ) return b;\n\t}\n\n\treturn 0;\n}\n\n// from internal sample number to external.\nstatic mpg_off_t sample_adjust( mpg123_handle_t *mh, mpg_off_t x )\n{\n\tmpg_off_t\ts;\n\n\tif( mh->p.flags & MPG123_GAPLESS )\n\t{\n\t\t// it's a bit tricky to do this computation for the padding samples.\n\t\t// they are not there on the outside.\n\t\tif( x > mh->end_os )\n\t\t{\n\t\t\tif( x < mh->fullend_os )\n\t\t\t\ts = mh->end_os - mh->begin_os;\n\t\t\telse s = x - (mh->fullend_os - mh->end_os + mh->begin_os);\n\t\t}\n\t\telse s = x - mh->begin_os;\n\t}\n\telse\n\t{\n\t\ts = x;\n\t}\n\n\treturn s;\n}\n\n// from external samples to internal\nstatic mpg_off_t sample_unadjust( mpg123_handle_t *mh, mpg_off_t x )\n{\n\tmpg_off_t\ts;\n\n\tif( mh->p.flags & MPG123_GAPLESS )\n\t{\n\t\ts = x + mh->begin_os;\n\t\t// there is a hole; we don't create sample positions in there.\n\t\t// jump from the end of the gapless track directly to after the padding.\n\t\tif( s >= mh->end_os ) s += mh->fullend_os - mh->end_os;\n\t}\n\telse\n\t{\n\t\ts = x;\n\t}\n\n\treturn s;\n}\n\n// take the buffer after a frame decode (strictly: it is the data from frame fr->num!) and cut samples out.\n// fr->buffer.fill may then be smaller than before...\nstatic void frame_buffercheck( mpg123_handle_t *fr )\n{\n\t// when we have no accurate position, gapless code does not make sense.\n\tif( !( fr->state_flags & FRAME_ACCURATE ))\n\t\treturn;\n\n\t// get a grip on dirty streams that start with a gapless header.\n\t// simply accept all data from frames that are too much,\n\t// they are supposedly attached to the stream after the fact.\n\tif( fr->gapless_frames > 0 && fr->num >= fr->gapless_frames )\n\t\treturn;\n\n\t// important: We first cut samples from the end, then cut from beginning (including left-shift of the buffer).\n\t// this order works also for the case where firstframe == lastframe.\n\n\t// the last interesting (planned) frame: Only use some leading samples.\n\t// note a difference from the below: The last frame and offset are unchanges by seeks.\n\t// the lastoff keeps being valid.\n\tif( fr->lastframe > -1 && fr->num >= fr->lastframe )\n\t{\n\t\t// there can be more than one frame of padding at the end, so we ignore the whole frame if we are beyond lastframe.\n\t\tmpg_off_t byteoff = ( fr->num == fr->lastframe ) ? samples_to_bytes( fr, fr->lastoff ) : 0;\n\n\t\tif((mpg_off_t)fr->buffer.fill > byteoff )\n\t\t\tfr->buffer.fill = byteoff;\n\t}\n\n\t// the first interesting frame: Skip some leading samples.\n\tif( fr->firstoff && fr->num == fr->firstframe )\n\t{\n\t\tmpg_off_t\tbyteoff = samples_to_bytes( fr, fr->firstoff );\n\t\tif((mpg_off_t)fr->buffer.fill > byteoff )\n\t\t{\n\t\t\tfr->buffer.fill -= byteoff;\n\n\t\t\tif( fr->own_buffer ) fr->buffer.p = fr->buffer.data + byteoff;\n\t\t\telse memmove( fr->buffer.data, fr->buffer.data + byteoff, fr->buffer.fill );\n\t\t}\n\t\telse fr->buffer.fill = 0;\n\n\t\t// we can only reach this frame again by seeking. And on seeking, firstoff will be recomputed.\n\t\t// so it is safe to null it here (and it makes the if() decision abort earlier).\n\t\tfr->firstoff = 0;\n\t}\n}\n\n// not part of the api. This just decodes the frame and fills missing bits with zeroes.\n// there can be frames that are broken and thus make do_layer() fail.\nstatic void decode_the_frame( mpg123_handle_t *fr )\n{\n\tsize_t\tneeded_bytes = decoder_synth_bytes( fr, frame_expect_outsamples( fr ));\n\tfr->clip += (fr->do_layer)(fr);\n\n\t// there could be less data than promised.\n\t// also, then debugging, we look out for coding errors that could result in _more_ data than expected.\n\tif( fr->buffer.fill < needed_bytes )\n\t{\n\t\t// one could do a loop with individual samples instead... but zero is zero\n\t\t// actually, that is wrong: zero is mostly a series of null bytes,\n\t\t// but we have funny 8bit formats that have a different opinion on zero...\n\t\t// unsigned 16 or 32 bit formats are handled later.\n\t\tmemset( fr->buffer.data + fr->buffer.fill, 0, needed_bytes - fr->buffer.fill );\n\n\t\tfr->buffer.fill = needed_bytes;\n\t}\n\n\tpostprocess_buffer( fr );\n}\n\nint mpg123_read( mpg123_handle_t *mh, byte *out, size_t size, size_t *done )\n{\n\treturn mpg123_decode( mh, NULL, 0, out, size, done );\n}\n\nint mpg123_feed( mpg123_handle_t *mh, const byte *in, size_t size )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tif( size > 0 )\n\t{\n\t\tif( in != NULL )\n\t\t{\n\t\t\tif( feed_more( mh, in, size ) != 0 )\n\t\t\t{\n\t\t\t\treturn MPG123_ERR;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// the need for more data might have triggered an error.\n\t\t\t\t// this one is outdated now with the new data.\n\t\t\t\tif( mh->err == MPG123_ERR_READER )\n\t\t\t\t\tmh->err = MPG123_OK;\n\t\t\t\treturn MPG123_OK;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmh->err = MPG123_NULL_BUFFER;\n\t\t\treturn MPG123_ERR;\n\t\t}\n\t}\n\n\treturn MPG123_OK;\n}\n\nint mpg123_decode( mpg123_handle_t *mh, const byte *inmemory, size_t inmemsize, byte *outmemory, size_t outmemsize, size_t *done )\n{\n\tint\tret = MPG123_OK;\n\tsize_t\tmdone = 0;\n\n\tif( done != NULL ) *done = 0;\n\tif( mh == NULL ) return MPG123_BAD_HANDLE;\n\n\tif( inmemsize > 0 && mpg123_feed( mh, inmemory, inmemsize ) != MPG123_OK )\n\t{\n\t\tret = MPG123_ERR;\n\t\tgoto decodeend;\n\t}\n\n\tif( outmemory == NULL )\n\t\toutmemsize = 0; // not just give error, give chance to get a status message.\n\n\twhile( ret == MPG123_OK )\n\t{\n\t\t// decode a frame that has been read before.\n\t\t// this only happens when buffer is empty!\n\t\tif( mh->to_decode )\n\t\t{\n\t\t\tif( mh->new_format )\n\t\t\t{\n\t\t\t\tmh->new_format = 0;\n\t\t\t\tret = MPG123_NEW_FORMAT;\n\t\t\t\tgoto decodeend;\n\t\t\t}\n\n\t\t\tif( mh->buffer.size - mh->buffer.fill < mh->outblock )\n\t\t\t{\n\t\t\t\tret = MPG123_NO_SPACE;\n\t\t\t\tgoto decodeend;\n\t\t\t}\n\n\t\t\tdecode_the_frame( mh );\n\t\t\tmh->to_decode = mh->to_ignore = FALSE;\n\t\t\tmh->buffer.p = mh->buffer.data;\n\t\t\tframe_buffercheck( mh );\n\t\t}\n\n\t\tif( mh->buffer.fill )\n\t\t{\n\t\t\tint\ta = mh->buffer.fill > (outmemsize - mdone) ? outmemsize - mdone : mh->buffer.fill;\n\n\t\t\t// copy (part of) the decoded data to the caller's buffer.\n\t\t\t// get what is needed - or just what is there\n\t\t\tmemcpy( outmemory, mh->buffer.p, a );\n\n\t\t\t// less data in frame buffer, less needed, output pointer increase, more data given...\n\t\t\tmh->buffer.fill -= a;\n\t\t\toutmemory  += a;\n\t\t\tmdone += a;\n\t\t\tmh->buffer.p += a;\n\n\t\t\tif(!( outmemsize > mdone ))\n\t\t\t\tgoto decodeend;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// if we didn't have data, get a new frame.\n\t\t\tint b = get_next_frame( mh );\n\t\t\tif (b < 0 )\n\t\t\t{\n\t\t\t\tret = b;\n\t\t\t\tgoto decodeend;\n\t\t\t}\n\t\t}\n\t}\ndecodeend:\n\tif( done != NULL )\n\t\t*done = mdone;\n\n\treturn ret;\n}\n\nint mpg123_getformat( mpg123_handle_t *mh, int *rate, int *channels, int *encoding )\n{\n\tint\tb;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\tb = init_track( mh );\n\tif( b < 0 ) return b;\n\n\tif( rate != NULL ) *rate = mh->af.rate;\n\tif( channels != NULL ) *channels = mh->af.channels;\n\tif( encoding != NULL ) *encoding = mh->af.encoding;\n\tmh->new_format = 0;\n\n\treturn MPG123_OK;\n}\n\nstatic int mpg123_scan( mpg123_handle_t *mh )\n{\n\tmpg_off_t\ttrack_frames = 0;\n\tmpg_off_t\ttrack_samples = 0;\n\tmpg_off_t\toldpos;\n\tint\tb;\n\n\tif( mh == NULL )\n\t\treturn MPG123_BAD_HANDLE;\n\n\tif(!( mh->rdat.flags & READER_SEEKABLE ))\n\t{\n\t\tmh->err = MPG123_NO_SEEK;\n\t\treturn MPG123_ERR;\n\t}\n\n\t// scan through the _whole_ file, since the current position is no count but computed assuming constant samples per frame.\n\t// also, we can just keep the current buffer and seek settings. Just operate on input frames here.\n\n\tb = init_track( mh ); // mh->num >= 0 !!\n\n\tif( b < 0 )\n\t{\n\t\tif( b == MPG123_DONE )\n\t\t\treturn MPG123_OK;\n\t\treturn MPG123_ERR; // must be error here, NEED_MORE is not for seekable streams.\n\t}\n\n\toldpos = mpg123_tell( mh );\n\tb = mh->rd->seek_frame( mh, 0 );\n\n\tif( b < 0 || mh->num != 0 )\n\t\treturn MPG123_ERR;\n\n\t// one frame must be there now.\n\ttrack_frames = 1;\n\ttrack_samples = mh->spf; // internal samples.\n\n\t// do not increment mh->track_frames in the loop as tha would confuse Frankenstein detection.\n\twhile( read_frame( mh ) == 1 )\n\t{\n\t\ttrack_samples += mh->spf;\n\t\ttrack_frames++;\n\t}\n\n\tmh->track_frames = track_frames;\n\tmh->track_samples = track_samples;\n\n\t// also, think about usefulness of that extra value track_samples ...\n\t// it could be used for consistency checking.\n\tif( mh->p.flags & MPG123_GAPLESS )\n\t\tframe_gapless_update( mh, mh->track_samples );\n\n\treturn mpg123_seek( mh, oldpos, SEEK_SET ) >= 0 ? MPG123_OK : MPG123_ERR;\n}\n\n// now, where are we? We need to know the last decoded frame... and what's left of it in buffer.\n// the current frame number can mean the last decoded frame or the to-be-decoded frame.\n// if mh->to_decode, then mh->num frames have been decoded, the frame mh->num now coming next.\n// if not, we have the possibility of mh->num+1 frames being decoded or nothing at all.\n// then, there is firstframe...when we didn't reach it yet, then the next data will come from there.\n// mh->num starts with -1\nmpg_off_t mpg123_tell( mpg123_handle_t *mh )\n{\n\tmpg_off_t\tpos = 0;\n\n\tif( mh == NULL )\n\t\treturn MPG123_ERR;\n\n\tif( track_need_init( mh ))\n\t\treturn 0;\n\n\t// now we have all the info at hand.\n\tif(( mh->num < mh->firstframe ) || ( mh->num == mh->firstframe && mh->to_decode ))\n\t{\n\t\t// we are at the beginning, expect output from firstframe on.\n\t\tpos = frame_outs( mh, mh->firstframe );\n\t\tpos += mh->firstoff;\n\t}\n\telse if( mh->to_decode )\n\t{\n\t\t// we start fresh with this frame. Buffer should be empty, but we make sure to count it in.\n\t\tpos = frame_outs(mh, mh->num) - bytes_to_samples( mh, mh->buffer.fill );\n\t}\n\telse\n\t{\n\t\t// we serve what we have in buffer and then the beginning of next frame...\n\t\tpos = frame_outs(mh, mh->num+1) - bytes_to_samples( mh, mh->buffer.fill );\n\t}\n\n\t// substract padding and delay from the beginning. */\n\tpos = sample_adjust( mh, pos );\n\n\t// negative sample offsets are not right, less than nothing is still nothing.\n\treturn pos > 0 ? pos : 0;\n}\n\nstatic int do_the_seek( mpg123_handle_t *mh )\n{\n\tmpg_off_t\tfnum = SEEKFRAME( mh );\n\tint\tb;\n\n\tmh->buffer.fill = 0;\n\n\t// If we are inside the ignoreframe - firstframe window,\n\t// we may get away without actual seeking.\n\tif( mh->num < mh->firstframe )\n\t{\n\t\tmh->to_decode = FALSE; // In any case, don't decode the current frame, perhaps ignore instead.\n\t\tif( mh->num > fnum )\n\t\t\treturn MPG123_OK;\n\t}\n\n\t// if we are already there, we are fine either for decoding or for ignoring.\n\tif( mh->num == fnum && ( mh->to_decode || fnum < mh->firstframe ))\n\t\treturn MPG123_OK;\n\n\t// we have the frame before... just go ahead as normal.\n\tif( mh->num == fnum - 1 )\n\t{\n\t\tmh->to_decode = FALSE;\n\t\treturn MPG123_OK;\n\t}\n\n\t// OK, real seeking follows... clear buffers and go for it.\n\tframe_buffers_reset( mh );\n\n\tb = mh->rd->seek_frame( mh, fnum );\n\tif( mh->header_change > 1 )\n\t{\n\t\tif( decode_update( mh ) < 0 )\n\t\t\treturn MPG123_ERR;\n\t\tmh->header_change = 0;\n\t}\n\n\tif( b < 0 ) return b;\n\n\t// Only mh->to_ignore is TRUE.\n\tif( mh->num < mh->firstframe )\n\t\tmh->to_decode = FALSE;\n\tmh->playnum = mh->num;\n\n\treturn 0;\n}\n\nmpg_off_t mpg123_seek( mpg123_handle_t *mh, mpg_off_t sampleoff, int whence )\n{\n\tmpg_off_t\tpos;\n\tint\tb;\n\n\tpos = mpg123_tell( mh ); // adjusted samples\n\n\t// pos < 0 also can mean that simply a former seek failed at the lower levels.\n\t// in that case, we only allow absolute seeks.\n\tif( pos < 0 && whence != SEEK_SET )\n\t{\n\t\t// unless we got the obvious error of NULL handle,\n\t\t// this is a special seek failure.\n\t\tif( mh != NULL )\n\t\t\tmh->err = MPG123_NO_RELSEEK;\n\t\treturn MPG123_ERR;\n\t}\n\n\tif(( b = init_track( mh )) < 0 )\n\t\treturn b;\n\n\tswitch( whence )\n\t{\n\tcase SEEK_CUR: pos += sampleoff; break;\n\tcase SEEK_SET: pos = sampleoff; break;\n\tcase SEEK_END:\n\t\t// when we do not know the end already, we can try to find it.\n\t\tif( mh->track_frames < 1 && ( mh->rdat.flags & READER_SEEKABLE ))\n\t\t\tmpg123_scan( mh );\n\t\tif( mh->track_frames > 0 )\n\t\t\tpos = sample_adjust( mh, frame_outs( mh, mh->track_frames )) - sampleoff;\n\t\telse if( mh->end_os > 0 )\n\t\t\tpos = sample_adjust( mh, mh->end_os ) - sampleoff;\n\t\telse\n\t\t{\n\t\t\tmh->err = MPG123_NO_SEEK_FROM_END;\n\t\t\treturn MPG123_ERR;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tmh->err = MPG123_BAD_WHENCE;\n\t\treturn MPG123_ERR;\n\t}\n\n\tif( pos < 0 ) pos = 0;\n\t// pos now holds the wanted sample offset in adjusted samples\n\tframe_set_seek( mh, sample_unadjust( mh, pos ));\n\tpos = do_the_seek( mh );\n\tif( pos < 0 ) return pos;\n\n\treturn mpg123_tell( mh );\n}\n\nstatic const char *mpg123_error[] =\n{\n\t\"No error... (code 0)\",\n\t\"Unable to set up output format! (code 1)\",\n\t\"Invalid channel number specified. (code 2)\",\n\t\"Invalid sample rate specified. (code 3)\",\n\t\"Unable to allocate memory for 16 to 8 converter table! (code 4)\",\n\t\"Bad parameter id! (code 5)\",\n\t\"Bad buffer given -- invalid pointer or too small size. (code 6)\",\n\t\"Out of memory -- some malloc() failed. (code 7)\",\n\t\"You didn't initialize the library! (code 8)\",\n\t\"Invalid decoder choice. (code 9)\",\n\t\"Invalid mpg123 handle. (code 10)\",\n\t\"Unable to initialize frame buffers (out of memory?)! (code 11)\",\n\t\"Invalid RVA mode. (code 12)\",\n\t\"This build doesn't support gapless decoding. (code 13)\",\n\t\"Not enough buffer space. (code 14)\",\n\t\"Incompatible numeric data types. (code 15)\",\n\t\"Bad equalizer band. (code 16)\",\n\t\"Null pointer given where valid storage address needed. (code 17)\",\n\t\"Error reading the stream. (code 18)\",\n\t\"Cannot seek from end (end is not known). (code 19)\",\n\t\"Invalid 'whence' for seek function. (code 20)\",\n\t\"Build does not support stream timeouts. (code 21)\",\n\t\"File access error. (code 22)\",\n\t\"Seek not supported by stream. (code 23)\",\n\t\"No stream opened. (code 24)\",\n\t\"Bad parameter handle. (code 25)\",\n\t\"Invalid parameter addresses for index retrieval. (code 26)\",\n\t\"Lost track in the bytestream and did not attempt resync. (code 27)\",\n\t\"Failed to find valid MPEG data within limit on resync. (code 28)\",\n\t\"No 8bit encoding possible. (code 29)\",\n\t\"Stack alignment is not good. (code 30)\",\n\t\"You gave me a NULL buffer? (code 31)\",\n\t\"File position is screwed up, please do an absolute seek (code 32)\",\n\t\"Inappropriate NULL-pointer provided.\",\n\t\"Bad key value given.\",\n\t\"There is no frame index (disabled in this build).\",\n\t\"Frame index operation failed.\",\n\t\"Decoder setup failed (invalid combination of settings?)\",\n\t\"Feature not in this build.\",\n\t\"Some bad value has been provided.\",\n\t\"Low-level seeking has failed (call to lseek(), usually).\",\n\t\"Custom I/O obviously not prepared.\",\n\t\"Overflow in LFS (large file support) conversion.\",\n\t\"Overflow in integer conversion.\",\n};\n\nconst char *mpg123_plain_strerror( int errcode )\n{\n\tif( errcode >= 0 && errcode < sizeof( mpg123_error ) / sizeof( char* ))\n\t\treturn mpg123_error[errcode];\n\n\tswitch( errcode )\n\t{\n\tcase MPG123_ERR:\n\t\treturn \"A generic mpg123 error.\";\n\tcase MPG123_DONE:\n\t\treturn \"Message: I am done with this track.\";\n\tcase MPG123_NEED_MORE:\n\t\treturn \"Message: Feed me more input data!\";\n\tcase MPG123_NEW_FORMAT:\n\t\treturn \"Message: Prepare for a changed audio format (query the new one)!\";\n\tdefault:\n\t\treturn \"I have no idea - an unknown error code!\";\n\t}\n}\n\nconst char *get_error( void *handle )\n{\n\tmpg123_handle_t *mh = handle;\n\n\tif( !mh ) return mpg123_plain_strerror( MPG123_BAD_HANDLE );\n\treturn mpg123_plain_strerror( mh->err );\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/mpg123.h",
    "content": "/*\nmpg123.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef MPG123_H\n#define MPG123_H\n\ntypedef struct mpg123_handle_s\tmpg123_handle_t;\n\n#ifdef _MSC_VER\n#pragma warning(disable : 4115)\t// named type definition in parentheses\n#pragma warning(disable : 4057)\t// differs in indirection to slightly different base types\n#pragma warning(disable : 4244)\t// conversion possible loss of data\n#pragma warning(disable : 4127)\t// conditional expression is constant\n#pragma warning(disable : 4706)\t// assignment within conditional expression\n#pragma warning(disable : 4100)\t// unreferenced formal parameter\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"fmt123.h\"\n#include STDINT_H\n#include \"xash3d_types.h\"\n\n#ifndef FALSE\n#define FALSE 0\n#endif\n\n#ifndef TRUE\n#define TRUE (!FALSE)\n#endif\n\n// configure the lib\n#define ACCURATE_ROUNDING\n//#define IEEE_FLOAT\n\n// begin used typedefs\ntypedef unsigned char\tbyte;\ntypedef unsigned short\tword;\ntypedef unsigned long\tulong;\ntypedef unsigned int\tuint;\ntypedef fs_offset_t\tmpg_off_t;\n\n#ifdef _MSC_VER // a1ba: MSVC6 don't have ssize_t\ntypedef long\t\tmpg_ssize_t;\n#else\ntypedef ssize_t\t\tmpg_ssize_t;\n#endif\n\ntypedef short\t\tint16_t;\ntypedef unsigned short\tuint16_t;\n\n#include \"synth.h\"\n#include \"index.h\"\n#include \"reader.h\"\n#include \"frame.h\"\n\n#define SEEKFRAME( mh )\t((mh)->ignoreframe < 0 ? 0 : (mh)->ignoreframe)\n#define track_need_init( mh )\t((mh)->num < 0)\n#define INDEX_SIZE\t\t1000\n#define SBLIMIT\t\t32\n#define SSLIMIT\t\t18\n#define GAPLESS_DELAY\t529\n#define SHORT_SCALE\t\t32768\n\n#define MPG_MD_STEREO\t0\n#define MPG_MD_JOINT_STEREO\t1\n#define MPG_MD_DUAL_CHANNEL\t2\n#define MPG_MD_MONO\t\t3\n\n#define SINGLE_STEREO\t-1\n#define SINGLE_LEFT\t\t0\n#define SINGLE_RIGHT\t1\n#define SINGLE_MIX\t\t3\n\n#define DOUBLE_TO_REAL( x )\t\t\t(float)(x)\n#define DOUBLE_TO_REAL_15( x )\t\t(float)(x)\n#define DOUBLE_TO_REAL_POW43( x )\t\t(float)(x)\n#define DOUBLE_TO_REAL_SCALE_LAYER12( x )\t(float)(x)\n#define DOUBLE_TO_REAL_SCALE_LAYER3( x, y )\t(float)(x)\n#define REAL_TO_DOUBLE( x )\t\t\t(x)\n\n#define REAL_MUL( x, y )\t\t\t((x) * (y))\n#define REAL_MUL_SYNTH( x, y )\t\t((x) * (y))\n#define REAL_MUL_15( x, y )\t\t\t((x) * (y))\n#define REAL_MUL_SCALE_LAYER12( x, y )\t\t((x) * (y))\n#define REAL_MUL_SCALE_LAYER3( x, y )\t\t((x) * (y))\n#define REAL_SCALE_LAYER12( x )\t\t(x)\n#define REAL_SCALE_LAYER3( x )\t\t(x)\n#define REAL_SCALE_DCT64( x )\t\t\t(x)\n\n#ifndef M_PI\n#define M_PI\t3.14159265358979323846\n#endif\n\n#ifndef M_SQRT2\n#define M_SQRT2\t1.41421356237309504880\n#endif\n\n// enumeration of the message and error codes and returned by libmpg123 functions.\nenum mpg123_errors\n{\n\tMPG123_DONE\t= -12,\t/**< Message: Track ended. Stop decoding. */\n\tMPG123_NEW_FORMAT\t= -11,\t/**< Message: Output format will be different on next call. Note that some libmpg123 versions between 1.4.3 and 1.8.0 insist on you calling mpg123_getformat() after getting this message code. Newer verisons behave like advertised: You have the chance to call mpg123_getformat(), but you can also just continue decoding and get your data. */\n\tMPG123_NEED_MORE\t= -10,\t/**< Message: For feed reader: \"Feed me more!\" (call mpg123_feed() or mpg123_decode() with some new input data). */\n\tMPG123_ERR\t= -1,\t/**< Generic Error */\n\tMPG123_OK\t\t= 0,\t/**< Success */\n\tMPG123_BAD_OUTFORMAT, \t/**< Unable to set up output format! */\n\tMPG123_BAD_CHANNEL,\t\t/**< Invalid channel number specified. */\n\tMPG123_BAD_RATE,\t\t/**< Invalid sample rate specified.  */\n\tMPG123_ERR_16TO8TABLE,\t/**< Unable to allocate memory for 16 to 8 converter table! */\n\tMPG123_BAD_PARAM,\t\t/**< Bad parameter id! */\n\tMPG123_BAD_BUFFER,\t\t/**< Bad buffer given -- invalid pointer or too small size. */\n\tMPG123_OUT_OF_MEM,\t\t/**< Out of memory -- some malloc() failed. */\n\tMPG123_NOT_INITIALIZED,\t/**< You didn't initialize the library! */\n\tMPG123_BAD_DECODER,\t\t/**< Invalid decoder choice. */\n\tMPG123_BAD_HANDLE,\t\t/**< Invalid mpg123 handle. */\n\tMPG123_NO_BUFFERS,\t\t/**< Unable to initialize frame buffers (out of memory?). */\n\tMPG123_BAD_RVA,\t\t/**< Invalid RVA mode. */\n\tMPG123_NO_GAPLESS,\t\t/**< This build doesn't support gapless decoding. */\n\tMPG123_NO_SPACE,\t\t/**< Not enough buffer space. */\n\tMPG123_BAD_TYPES,\t\t/**< Incompatible numeric data types. */\n\tMPG123_BAD_BAND,\t\t/**< Bad equalizer band. */\n\tMPG123_ERR_NULL,\t\t/**< Null pointer given where valid storage address needed. */\n\tMPG123_ERR_READER,\t\t/**< Error reading the stream. */\n\tMPG123_NO_SEEK_FROM_END,\t/**< Cannot seek from end (end is not known). */\n\tMPG123_BAD_WHENCE,\t\t/**< Invalid 'whence' for seek function.*/\n\tMPG123_NO_TIMEOUT,\t\t/**< Build does not support stream timeouts. */\n\tMPG123_BAD_FILE,\t\t/**< File access error. */\n\tMPG123_NO_SEEK,\t\t/**< Seek not supported by stream. */\n\tMPG123_NO_READER,\t\t/**< No stream opened. */\n\tMPG123_BAD_PARS,\t\t/**< Bad parameter handle. */\n\tMPG123_BAD_INDEX_PAR,\t/**< Bad parameters to mpg123_index() and mpg123_set_index() */\n\tMPG123_OUT_OF_SYNC,\t\t/**< Lost track in bytestream and did not try to resync. */\n\tMPG123_RESYNC_FAIL,\t\t/**< Resync failed to find valid MPEG data. */\n\tMPG123_NO_8BIT,\t\t/**< No 8bit encoding possible. */\n\tMPG123_BAD_ALIGN,\t\t/**< Stack aligmnent error */\n\tMPG123_NULL_BUFFER,\t\t/**< NULL input buffer with non-zero size... */\n\tMPG123_NO_RELSEEK,\t\t/**< Relative seek not possible (screwed up file offset) */\n\tMPG123_NULL_POINTER,\t/**< You gave a null pointer somewhere where you shouldn't have. */\n\tMPG123_BAD_KEY,\t\t/**< Bad key value given. */\n\tMPG123_NO_INDEX,\t\t/**< No frame index in this build. */\n\tMPG123_INDEX_FAIL,\t\t/**< Something with frame index went wrong. */\n\tMPG123_BAD_DECODER_SETUP,\t/**< Something prevents a proper decoder setup */\n\tMPG123_MISSING_FEATURE,\t/**< This feature has not been built into libmpg123. */\n\tMPG123_BAD_VALUE,\t\t/**< A bad value has been given, somewhere. */\n\tMPG123_LSEEK_FAILED,\t/**< Low-level seek failed. */\n\tMPG123_BAD_CUSTOM_IO,\t/**< Custom I/O not prepared. */\n\tMPG123_LFS_OVERFLOW,\t/**< Offset value overflow during translation of large file API calls -- your client program cannot handle that large file. */\n\tMPG123_INT_OVERFLOW\t\t/**< Some integer overflow. */\n};\n\n// enumeration of the parameters types that it is possible to set/get.\nenum mpg123_parms\n{\n\tMPG123_VERBOSE = 0,\t\t/**< set verbosity value for enabling messages to stderr, >= 0 makes sense (integer) */\n\tMPG123_FLAGS,\t\t/**< set all flags, p.ex val = MPG123_GAPLESS|MPG123_MONO_MIX (integer) */\n\tMPG123_ADD_FLAGS,\t\t/**< add some flags (integer) */\n\tMPG123_FORCE_RATE,\t\t/**< when value > 0, force output rate to that value (integer) */\n\tMPG123_DOWN_SAMPLE,\t\t/**< 0=native rate, 1=half rate, 2=quarter rate (integer) */\n\tMPG123_RVA,\t\t/**< one of the RVA choices above (integer) */\n\tMPG123_DOWNSPEED,\t\t/**< play a frame N times (integer) */\n\tMPG123_UPSPEED,\t\t/**< play every Nth frame (integer) */\n\tMPG123_START_FRAME,\t\t/**< start with this frame (skip frames before that, integer) */\n\tMPG123_DECODE_FRAMES,\t/**< decode only this number of frames (integer) */\n\tMPG123_OUTSCALE,\t\t/**< the scale for output samples (amplitude - integer according to mpg123 output format) */\n\tMPG123_TIMEOUT,\t\t/**< timeout for reading from a stream (not supported on win32, integer) */\n\tMPG123_REMOVE_FLAGS,\t/**< remove some flags (inverse of MPG123_ADD_FLAGS, integer) */\n\tMPG123_RESYNC_LIMIT,\t/**< Try resync on frame parsing for that many bytes or until end of stream (<0 ... integer). This can enlarge the limit for skipping junk on beginning, too (but not reduce it).  */\n\tMPG123_INDEX_SIZE,\t\t/**< Set the frame index size (if supported). Values <0 mean that the index is allowed to grow dynamically in these steps (in positive direction, of course) -- Use this when you really want a full index with every individual frame. */\n\tMPG123_PREFRAMES,\t\t/**< Decode/ignore that many frames in advance for layer 3. This is needed to fill bit reservoir after seeking, for example (but also at least one frame in advance is needed to have all \"normal\" data for layer 3). Give a positive integer value, please.*/\n\tMPG123_FEEDPOOL,\t\t/**< For feeder mode, keep that many buffers in a pool to avoid frequent malloc/free. The pool is allocated on mpg123_open_feed(). If you change this parameter afterwards, you can trigger growth and shrinkage during decoding. The default value could change any time. If you care about this, then set it. (integer) */\n\tMPG123_FEEDBUFFER,\t\t/**< Minimal size of one internal feeder buffer, again, the default value is subject to change. (integer) */\n};\n\n// flag bits for MPG123_FLAGS, use the usual binary or to combine.\nenum mpg123_param_flags\n{\n\tMPG123_FORCE_MONO = 0x7,\t\t/**<     0111 Force some mono mode: This is a test bitmask for seeing if any mono forcing is active. */\n\tMPG123_MONO_LEFT = 0x1,\t\t/**<     0001 Force playback of left channel only.  */\n\tMPG123_MONO_RIGHT = 0x2,\t\t/**<     0010 Force playback of right channel only. */\n\tMPG123_MONO_MIX = 0x4,\t\t/**<     0100 Force playback of mixed mono.         */\n\tMPG123_FORCE_STEREO = 0x8,\t\t/**<     1000 Force stereo output.                  */\n\tMPG123_QUIET = 0x20,\t\t/**< 00100000 Suppress any printouts (overrules verbose).                    */\n\tMPG123_GAPLESS = 0x40,\t\t/**< 01000000 Enable gapless decoding (default on if libmpg123 has support). */\n\tMPG123_NO_RESYNC = 0x80,\t\t/**< 10000000 Disable resync stream after error.                             */\n\tMPG123_SEEKBUFFER = 0x100,\t\t/**< 000100000000 Enable small buffer on non-seekable streams to allow some peek-ahead (for better MPEG sync). */\n\tMPG123_FUZZY = 0x200,\t\t/**< 001000000000 Enable fuzzy seeks (guessing byte offsets or using approximate seek points from Xing TOC) */\n\tMPG123_IGNORE_STREAMLENGTH = 0x1000,\t/**< 1000000000000 Ignore any stream length information contained in the stream, which can be contained in a 'TLEN' frame of an ID3v2 tag or a Xing tag */\n\tMPG123_IGNORE_INFOFRAME = 0x4000,\t/**< 100 0000 0000 0000 Do not parse the LAME/Xing info frame, treat it as normal MPEG data. */\n\tMPG123_AUTO_RESAMPLE = 0x8000,\t/**< 1000 0000 0000 0000 Allow automatic internal resampling of any kind (default on if supported). Especially when going lowlevel with replacing output buffer, you might want to unset this flag. Setting MPG123_DOWNSAMPLE or MPG123_FORCE_RATE will override this. */\n};\n\n// choices for MPG123_RVA\nenum mpg123_param_rva\n{\n\tMPG123_RVA_OFF   = 0,\t\t/**< RVA disabled (default).   */\n\tMPG123_RVA_MIX   = 1,\t\t/**< Use mix/track/radio gain. */\n\tMPG123_RVA_ALBUM = 2,\t\t/**< Use album/audiophile gain */\n\tMPG123_RVA_MAX   = MPG123_RVA_ALBUM,\t/**< The maximum RVA code, may increase in future. */\n};\n\nenum frame_state_flags\n{\n\tFRAME_ACCURATE = 0x1,\t\t/**<     0001 Positions are considered accurate. */\n\tFRAME_FRANKENSTEIN = 0x2,\t\t/**<     0010 This stream is concatenated. */\n\tFRAME_FRESH_DECODER = 0x4,\t\t/**<     0100 Decoder is fleshly initialized. */\n};\n\n// enumeration of the mode types of Variable Bitrate\nenum mpg123_vbr\n{\n\tMPG123_CBR = 0,\t\t\t/**< Constant Bitrate Mode (default) */\n\tMPG123_VBR,\t\t\t/**< Variable Bitrate Mode */\n\tMPG123_ABR\t\t\t/**< Average Bitrate Mode */\n};\n\n// Data structure for ID3v1 tags (the last 128 bytes of a file).\n// Don't take anything for granted (like string termination)!\n// Also note the change ID3v1.1 did: comment[28] = 0; comment[29] = track_number\n// It is your task to support ID3v1 only or ID3v1.1 ...\ntypedef struct\n{\n\tchar\ttag[3];\t\t\t/**< Always the string \"TAG\", the classic intro. */\n\tchar\ttitle[30];\t\t/**< Title string.  */\n\tchar\tartist[30];\t\t/**< Artist string. */\n\tchar\talbum[30];\t\t/**< Album string. */\n\tchar\tyear[4];\t\t\t/**< Year string. */\n\tchar\tcomment[30];\t\t/**< Comment string. */\n\tbyte\tgenre;\t\t\t/**< Genre index. */\n} mpg123_id3v1;\n\n#define MPG123_ID3\t\t0x3\t\t/**< 0011 There is some ID3 info. Also matches 0010 or NEW_ID3. */\n#define MPG123_NEW_ID3\t0x1\t\t/**< 0001 There is ID3 info that changed since last call to mpg123_id3. */\n\nstruct mpg123_handle_s\n{\n\tint\t\tfresh;\t\t// to be moved into flags\n\tint\t\tnew_format;\n\tfloat\t\thybrid_block[2][2][SBLIMIT*SSLIMIT];\n\tint\t\thybrid_blc[2];\n\n\t// the scratch vars for the decoders, sometimes float, sometimes short... sometimes int/long\n\tshort\t\t*short_buffs[2][2];\n\tfloat\t\t*float_buffs[2][2];\n\tbyte\t\t*rawbuffs;\n\tint\t\trawbuffss;\n\tint\t\tbo;\t\t// just have it always here.\n\tbyte\t\t*rawdecwin;\t// the block with all decwins\n\n\tint\t\trawdecwins;\t// size of rawdecwin memory\n\tfloat\t\t*decwin;\t\t// _the_ decode table\n\n\t// for halfspeed mode\n\tbyte\t\tssave[34];\n\tint\t\thalfphase;\n\n\t// layer3\n\tint\t\tlongLimit[9][23];\n\tint\t\tshortLimit[9][14];\n\tfloat\t\tgainpow2[256+118+4];// not floatly dynamic, just different for mmx\n\n\tsynth_t\t\tsynths;\n\tint\t\tverbose;\t\t// 0: nothing, 1: just print chosen decoder, 2: be verbose\n\n\tconst al_table_t\t*alloc;\n\n\t// the runtime-chosen decoding, based on input and output format\n\tfunc_synth\tsynth;\n\tfunc_synth_stereo\tsynth_stereo;\n\tfunc_synth_mono\tsynth_mono;\n\n\t// yes, this function is runtime-switched, too.\n\tvoid (*make_decode_tables)( mpg123_handle_t *fr ); // that is the volume control.\n\n\tint\t\tstereo;\t\t// I _think_ 1 for mono and 2 for stereo\n\tint\t\tjsbound;\n\n\tint\t\tsingle;\n\tint\t\tII_sblimit;\n\tint\t\tdown_sample_sblimit;\n\tint\t\tlsf;\t\t// 0: MPEG 1.0; 1: MPEG 2.0/2.5 -- both used as bool and array index!\n\n\t// many flags in disguise as integers... wasting bytes.\n\tint\t\tmpeg25;\n\tint\t\tdown_sample;\n\tint\t\theader_change;\n\tint\t\tlay;\n\tlong\t\tspf;\t\t// cached count of samples per frame\n\n\tint (*do_layer)( mpg123_handle_t* );\n\n\tint\t\terror_protection;\n\tint\t\tbitrate_index;\n\tint\t\tsampling_frequency;\n\tint\t\tpadding;\n\tint\t\textension;\n\tint\t\tmode;\n\tint\t\tmode_ext;\n\tint\t\tcopyright;\n\tint\t\toriginal;\n\tint\t\temphasis;\n\tint\t\tframesize;\t// computed framesize\n\tint\t\tfreesize;\t\t// free format frame size\n\tint\t\tvbr;\t\t// 1 if variable bitrate was detected\n\tmpg_off_t\t\tnum;\t\t// frame offset ...\n\tmpg_off_t\t\tinput_offset;\t// byte offset of this frame in input stream\n\tmpg_off_t\t\tplaynum;\t\t// playback offset... includes repetitions, reset at seeks\n\tmpg_off_t\t\taudio_start;\t// The byte offset in the file where audio data begins.\n\tint\t\tstate_flags;\n\tchar\t\tsilent_resync;\t// Do not complain for the next n resyncs.\n\tbyte\t\t*xing_toc;\t// The seek TOC from Xing header.\n\tint\t\tfreeformat;\n\tlong\t\tfreeformat_framesize;\n\n\t// bitstream info; bsi\n\tint\t\tbitindex;\n\tbyte\t\t*wordpointer;\n\n\t// temporary storage for getbits stuff\n\tulong\t\tultmp;\n\tbyte\t\tuctmp;\n\n\t// rva data\n\tdouble\t\tmaxoutburst;\t// the maximum amplitude in current sample represenation.\n\tdouble\t\tlastscale;\n\n\tstruct\n\t{\n\t\tint\tlevel[2];\n\t\tfloat\tgain[2];\n\t\tfloat\tpeak[2];\n\t} rva;\n\n\t// input data\n\tmpg_off_t\t\ttrack_frames;\n\tmpg_off_t\t\ttrack_samples;\n\tdouble\t\tmean_framesize;\n\tmpg_off_t\t\tmean_frames;\n\tint\t\tfsizeold;\n\tint\t\tssize;\n\n\tuint\t\tbitreservoir;\n\tbyte\t\tbsspace[2][MAXFRAMESIZE+512];\n\tbyte\t\t*bsbuf;\n\tbyte\t\t*bsbufold;\n\tint\t\tbsnum;\n\n\t// that is the header matching the last read frame body.\n\tulong\t\toldhead;\n\n\t// that is the header that is supposedly the first of the stream.\n\tulong\t\tfirsthead;\n\tint\t\tabr_rate;\n\n\tframe_index_t\tindex;\n\n\t// output data\n\toutbuffer_t\tbuffer;\n\taudioformat_t\taf;\n\n\tint\t\town_buffer;\n\tsize_t\t\toutblock;\t\t// number of bytes that this frame produces (upper bound)\n\tint\t\tto_decode;\t// this frame holds data to be decoded\n\tint\t\tto_ignore;\t// the same, somehow\n\tmpg_off_t\t\tfirstframe;\t// start decoding from here\n\tmpg_off_t\t\tlastframe;\t// last frame to decode (for gapless or num_frames limit)\n\tmpg_off_t\t\tignoreframe;\t// frames to decode but discard before firstframe\n\n\tmpg_off_t\t\tgapless_frames;\t// frame count for the gapless part\n\tmpg_off_t\t\tfirstoff;\t\t// number of samples to ignore from firstframe\n\tmpg_off_t\t\tlastoff;\t\t// number of samples to use from lastframe\n\tmpg_off_t\t\tbegin_s;\t\t// overall begin offset in samples\n\tmpg_off_t\t\tbegin_os;\n\tmpg_off_t\t\tend_s;\t\t// overall end offset in samples\n\tmpg_off_t\t\tend_os;\n\tmpg_off_t\t\tfullend_os;\t// gapless_frames translated to output samples\n\n\tuint\t\tcrc;\t\t// well, I need a safe 16bit type, actually. But wider doesn't hurt.\n\n\treader_t\t\t*rd;\t\t// pointer to the reading functions\n\treader_data_t\trdat;\t\t// reader data and state info\n\tmpg123_parm_t\tp;\n\n\tint\t\terr;\n\tint\t\tdecoder_change;\n\tint\t\tdelayed_change;\n\tlong\t\tclip;\n\n\t// the meta crap\n\tint\t\tmetaflags;\n\tbyte\t\tid3buf[128];\n\n\tfloat\t\t*layerscratch;\n\n\t// these are significant chunks of memory already...\n\tstruct\n\t{\n\t\tfloat\t(*hybrid_in)[SBLIMIT][SSLIMIT];  // ALIGNED(16) float hybridIn[2][SBLIMIT][SSLIMIT];\n\t\tfloat\t(*hybrid_out)[SSLIMIT][SBLIMIT]; // ALIGNED(16) float hybridOut[2][SSLIMIT][SBLIMIT];\n\t} layer3;\n\n\t// a place for storing additional data for the large file wrapper. this is cruft!\n\tvoid\t\t*wrapperdata;\n\n\t// a callback used to properly destruct the wrapper data.\n\tvoid (*wrapperclean)( void* );\n};\n\n//\n// parse.c\n//\nvoid set_pointer( mpg123_handle_t *fr, long backstep );\nint get_songlen( mpg123_handle_t *fr, int no );\ndouble compute_bpf( mpg123_handle_t *fr );\nlong frame_freq( mpg123_handle_t *fr );\ndouble mpg123_tpf( mpg123_handle_t *fr );\nint mpg123_spf( mpg123_handle_t *mh );\nint read_frame( mpg123_handle_t *fr );\n\n//\n// format.c\n//\nvoid invalidate_format( audioformat_t *af );\nvoid postprocess_buffer( mpg123_handle_t *fr );\nint frame_output_format( mpg123_handle_t *fr );\nint mpg123_fmt_all( mpg123_parm_t *mp );\nint mpg123_format_none( mpg123_handle_t *mh );\nint mpg123_format_all( mpg123_handle_t *mh );\nint mpg123_format( mpg123_handle_t *mh, long rate, int channels, int encodings );\nmpg_off_t decoder_synth_bytes( mpg123_handle_t *fr, mpg_off_t s );\nmpg_off_t bytes_to_samples( mpg123_handle_t *fr, mpg_off_t b );\nmpg_off_t samples_to_bytes( mpg123_handle_t *fr, mpg_off_t s );\nmpg_off_t outblock_bytes( mpg123_handle_t *fr, mpg_off_t s );\n\n//\n// layer3.c\n//\nextern float COS6_1;\nextern float COS6_2;\nextern float cos9[3];\nextern float cos18[3];\nextern float tfcos12[3];\nextern float tfcos36[9];\nvoid init_layer3( void );\nvoid init_layer3_stuff( mpg123_handle_t *fr );\nint do_layer3( mpg123_handle_t *fr );\n\n//\n// dct36.c\n//\nvoid dct36( float *inbuf, float *o1, float *o2, float *wintab, float *tsbuf );\nvoid dct12( float *in, float *rawout1, float *rawout2, register float *wi, register float *ts );\n\n//\n// dct64.c\n//\nvoid dct64( float *out0, float *out1, float *samples );\n\n//\n// tabinit.c\n//\nextern float *pnts[];\nvoid prepare_decode_tables( void );\nvoid make_decode_tables( mpg123_handle_t *fr );\n\n// begin prototypes\nmpg123_handle_t *mpg123_new( int *error );\nmpg123_handle_t *mpg123_parnew( mpg123_parm_t *mp, int *error );\nint mpg123_param( mpg123_handle_t *mh, enum mpg123_parms key, long val );\nint mpg123_open_handle( mpg123_handle_t *mh, void *iohandle );\nint mpg123_replace_reader_handle( mpg123_handle_t *mh, mpg_ssize_t (*fread)(void*, void*, size_t), mpg_off_t (*lseek)(void*, mpg_off_t, int), void(*fclose)(void*));\nint mpg123_decode( mpg123_handle_t *mh, const byte *inmemory, size_t inmemsize, byte *outmemory, size_t outmemsize, size_t *done );\nint mpg123_getformat( mpg123_handle_t *mh, int *rate, int *channels, int *encoding );\nint mpg123_read( mpg123_handle_t *mh, byte *out, size_t size, size_t *done );\nmpg_off_t mpg123_seek( mpg123_handle_t *mh, mpg_off_t sampleoff, int whence );\nint mpg123_feed( mpg123_handle_t *mh, const byte *in, size_t size );\nconst char *mpg123_plain_strerror( int errcode );\nint mpg123_open_feed( mpg123_handle_t *mh );\nvoid mpg123_delete( mpg123_handle_t *mh );\nmpg_off_t mpg123_tell( mpg123_handle_t *mh );\nint mpg123_init( void );\nvoid mpg123_exit( void );\n\n#endif//MPG123_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/parse.c",
    "content": "/*\nparse.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpeghead.h\"\n#include \"mpg123.h\"\n#include \"getbits.h\"\n#include <limits.h>\n\n#define TRACK_MAX_FRAMES\t(ULONG_MAX / 4 / 1152)\n#define FORGET_INTERVAL\t1024\t// used by callers to set forget flag each <n> bytes.\n\n// use 4 bytes from buf to construct 28bit uint value and return 1; return 0 if bytes are not synchsafe\n#define synchsafe_to_long( buf, res ) \\\n\t((((buf)[0]|(buf)[1]|(buf)[2]|(buf)[3]) & 0x80) ? \\\n\t0 : (res = (((ulong)(buf)[0]) << 21) | (((ulong)(buf)[1]) << 14)|(((ulong)(buf)[2]) << 7)|((ulong)(buf)[3]), 1 ))\n\n#define check_bytes_left( n ) if( fr->framesize < lame_offset + n ) \\\n\t\tgoto check_lame_tag_yes\n\n// PARSE_GOOD and PARSE_BAD have to be 1 and 0 (TRUE and FALSE), others can vary.\nenum parse_codes\n{\n\tPARSE_MORE = MPG123_NEED_MORE,\n\tPARSE_ERR  = MPG123_ERR,\n\tPARSE_END  = 10,\t\t/* No more audio data to find. */\n\tPARSE_GOOD = 1,\t\t/* Everything's fine. */\n\tPARSE_BAD  = 0,\t\t/* Not fine (invalid data). */\n\tPARSE_RESYNC = 2,\t\t/* Header not good, go into resync. */\n\tPARSE_AGAIN  = 3,\t\t/* Really start over, throw away and read a new header, again. */\n};\n\n// bitrates for [mpeg1/2][layer]\nstatic const int tabsel_123[2][3][16] =\n{\n{\n{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,},\n{0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,},\n{0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,}\n},\n{\n{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,},\n{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,},\n{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,}\n}\n};\n\nstatic const long freqs[9] = { 44100, 48000, 32000, 22050, 24000, 16000 , 11025 , 12000 , 8000 };\n\nvoid set_pointer( mpg123_handle_t *fr, long backstep )\n{\n\tfr->wordpointer = fr->bsbuf + fr->ssize - backstep;\n\n\tif( backstep )\n\t\tmemcpy( fr->wordpointer, fr->bsbufold + fr->fsizeold - backstep, backstep );\n\n\tfr->bitindex = 0;\n}\n\nstatic int frame_bitrate( mpg123_handle_t *fr )\n{\n\treturn tabsel_123[fr->lsf][fr->lay-1][fr->bitrate_index];\n}\n\nlong frame_freq( mpg123_handle_t *fr )\n{\n\treturn freqs[fr->sampling_frequency];\n}\n\ndouble compute_bpf( mpg123_handle_t *fr )\n{\n\tdouble\tbpf;\n\n\tswitch( fr->lay )\n\t{\n\tcase 1:\n\t\tbpf = tabsel_123[fr->lsf][0][fr->bitrate_index];\n\t\tbpf *= 12000.0 * 4.0;\n\t\tbpf /= freqs[fr->sampling_frequency] << (fr->lsf);\n\t\tbreak;\n\tcase 2:\n\tcase 3:\n\t\tbpf = tabsel_123[fr->lsf][fr->lay-1][fr->bitrate_index];\n\t\tbpf *= 144000;\n\t\tbpf /= freqs[fr->sampling_frequency] << (fr->lsf);\n\t\tbreak;\n\tdefault:\n\t\tbpf = 1.0;\n\t}\n\n\treturn bpf;\n}\n\nint mpg123_spf( mpg123_handle_t *mh )\n{\n\tif( mh == NULL )\n\t\treturn MPG123_ERR;\n\n\treturn mh->firsthead ? mh->spf : MPG123_ERR;\n}\n\ndouble mpg123_tpf( mpg123_handle_t *fr )\n{\n\tstatic int\tbs[4] = { 0,384, 1152, 1152 };\n\tdouble\t\ttpf;\n\n\tif( fr == NULL || !fr->firsthead )\n\t\treturn MPG123_ERR;\n\n\ttpf = (double)bs[fr->lay];\n\ttpf /= freqs[fr->sampling_frequency] << (fr->lsf);\n\n\treturn tpf;\n}\n\nint get_songlen( mpg123_handle_t *fr, int no )\n{\n\tdouble\ttpf;\n\n\tif( !fr ) return 0;\n\n\tif( no < 0 )\n\t{\n\t\tif( !fr->rd || fr->rdat.filelen < 0 )\n\t\t\treturn 0;\n\n\t\tno = (int)((double)fr->rdat.filelen / compute_bpf( fr ));\n\t}\n\n\ttpf = mpg123_tpf( fr );\n\treturn (int)(no * tpf);\n}\n\n// just tell if the header is some mono.\nstatic int header_mono( ulong newhead )\n{\n\treturn HDR_CHANNEL_VAL( newhead ) == MPG_MD_MONO ? TRUE : FALSE;\n}\n\nstatic int head_check(ulong head)\n{\n\tif((( head & HDR_SYNC ) != HDR_SYNC ) || (!(HDR_LAYER_VAL(head))) || (HDR_BITRATE_VAL(head) == 0xf) || (HDR_SAMPLERATE_VAL(head) == 0x3 ))\n\t\treturn FALSE;\n\n\treturn TRUE;\n}\n\n// true if the two headers will work with the same decoding routines\nstatic int head_compatible( ulong fred, ulong bret )\n{\n\treturn (( fred & HDR_CMPMASK ) == ( bret & HDR_CMPMASK ) && header_mono( fred ) == header_mono( bret ));\n}\n\n// this is moderately sized buffers. Int offset is enough.\nstatic ulong bit_read_long( byte *buf, int *offset )\n{\n\tulong val = (((ulong)buf[*offset]) << 24) | (((ulong) buf[*offset+1]) << 16) | (((ulong) buf[*offset+2]) << 8) | ((ulong)buf[*offset+3]);\n\t*offset += 4;\n\n\treturn val;\n}\n\nstatic word bit_read_short( byte *buf, int *offset )\n{\n\tword val = (((word) buf[*offset]  ) << 8) | ((word) buf[*offset+1]);\n\t*offset += 2;\n\n\treturn val;\n}\n\nstatic int check_lame_tag( mpg123_handle_t *fr )\n{\n\tint\tlame_offset = (fr->stereo == 2) ? (fr->lsf ? 17 : 32) : (fr->lsf ? 9  : 17);\n\tulong\txing_flags;\n\tulong\tlong_tmp;\n\tint\ti;\n\n\t// going to look for Xing or Info at some position after the header\n\t//\t                                   MPEG 1  MPEG 2/2.5 (LSF)\n\t//\tStereo, Joint Stereo, Dual Channel  32      17\n\t//\tMono                                17       9\n\n\tif( fr->p.flags & MPG123_IGNORE_INFOFRAME )\n\t\tgoto check_lame_tag_no;\n\n\t// note: CRC or not, that does not matter here.\n\t// but, there is any combination of Xing flags in the wild. There are headers\n\t// without the search index table! I cannot assume a reasonable minimal size\n\t// for the actual data, have to check if each byte of information is present.\n\t// but: 4 B Info/Xing + 4 B flags is bare minimum.\n\tif( fr->framesize < lame_offset + 8 )\n\t\tgoto check_lame_tag_no;\n\n\t// only search for tag when all zero before it (apart from checksum)\n\tfor( i = 2; i < lame_offset; ++i )\n\t{\n\t\tif( fr->bsbuf[i] != 0 )\n\t\t\tgoto check_lame_tag_no;\n\t}\n\n\tif((fr->bsbuf[lame_offset] == 'I') && (fr->bsbuf[lame_offset+1] == 'n') && (fr->bsbuf[lame_offset+2] == 'f') && (fr->bsbuf[lame_offset+3] == 'o'))\n\t{\n\t\t// we still have to see what there is\n\t}\n\telse if((fr->bsbuf[lame_offset] == 'X') && (fr->bsbuf[lame_offset+1] == 'i') && (fr->bsbuf[lame_offset+2] == 'n') && (fr->bsbuf[lame_offset+3] == 'g'))\n\t{\n\t\t// Xing header means always VBR\n\t\tfr->vbr = MPG123_VBR;\n\t}\n\telse goto check_lame_tag_no;\n\n\tlame_offset += 4;\n\txing_flags = bit_read_long( fr->bsbuf, &lame_offset );\n\n\t// from now on, I have to carefully check if the announced data is actually\n\t// there! I'm always returning 'yes', though.\n\tif( xing_flags & 1 )\n\t{\n\t\t// total bitstream frames\n\t\tcheck_bytes_left( 4 );\n\t\tlong_tmp = bit_read_long( fr->bsbuf, &lame_offset );\n\t\tif( fr->p.flags & MPG123_IGNORE_STREAMLENGTH )\n\t\t{\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// check for endless stream, but: TRACK_MAX_FRAMES sensible at all?\n\t\t\tfr->track_frames = long_tmp > TRACK_MAX_FRAMES ? 0 : (mpg_off_t)long_tmp;\n\n\t\t\t// all or nothing: Only if encoder delay/padding is known, we'll cut\n\t\t\t// samples for gapless.\n\t\t\tif( fr->p.flags & MPG123_GAPLESS )\n\t\t\t\tframe_gapless_init( fr, fr->track_frames, 0, 0 );\n\t\t}\n\t}\n\n\tif( xing_flags & 0x2 )\n\t{\n\t\t// total bitstream bytes\n\t\tcheck_bytes_left( 4 );\n\t\tlong_tmp = bit_read_long( fr->bsbuf, &lame_offset );\n\n\t\tif( fr->p.flags & MPG123_IGNORE_STREAMLENGTH )\n\t\t{\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// the Xing bitstream length, at least as interpreted by the Lame\n\t\t\t// encoder, encompasses all data from the Xing header frame on,\n\t\t\t// ignoring leading ID3v2 data. Trailing tags (ID3v1) seem to be\n\t\t\t// included, though.\n\t\t\tif( fr->rdat.filelen < 1 )\n\t\t\t{\n\t\t\t\tfr->rdat.filelen = (mpg_off_t)long_tmp + fr->audio_start; // Overflow?\n\t\t\t}\n\t\t}\n\t}\n\n\tif( xing_flags & 0x4 ) // TOC\n\t{\n\t\tcheck_bytes_left( 100 );\n\t\tframe_fill_toc( fr, fr->bsbuf + lame_offset );\n\t\tlame_offset += 100;\n\t}\n\n\t// VBR quality\n\tif( xing_flags & 0x8 )\n\t{\n\t\tcheck_bytes_left( 4 );\n\t\tlong_tmp = bit_read_long( fr->bsbuf, &lame_offset );\n\t}\n\n\t// either zeros/nothing, or:\n\t// 0-8: LAME3.90a\n\t// 9: revision/VBR method\n\t// 10: lowpass\n\t// 11-18: ReplayGain\n\t// 19: encoder flags\n\t// 20: ABR\n\t// 21-23: encoder delays\n\tcheck_bytes_left( 24 ); // I'm interested in 24 B of extra info.\n\n\tif( fr->bsbuf[lame_offset] != 0 )\n\t{\n\t\tbyte\tlame_vbr;\n\t\tfloat\treplay_gain[2] = { 0, 0 };\n\t\tfloat\tpeak = 0;\n\t\tfloat\tgain_offset = 0; // going to be +6 for old lame that used 83dB\n\t\tchar\tnb[10];\n\t\tmpg_off_t\tpad_in;\n\t\tmpg_off_t\tpad_out;\n\n\t\tmemcpy( nb, fr->bsbuf + lame_offset, 9 );\n\t\tnb[9] = 0;\n\n\t\tif(!strncmp( \"LAME\", nb, 4 ))\n\t\t{\n\t\t\tuint\tmajor, minor;\n\t\t\tchar\trest[6];\n\n\t\t\trest[0] = 0;\n\n\t\t\t// Lame versions before 3.95.1 used 83 dB reference level, later\n\t\t\t// versions 89 dB. We stick with 89 dB as being \"normal\", adding 6 dB.\n\t\t\tif( sscanf( nb + 4, \"%u.%u%s\", &major, &minor, rest ) >= 2 )\n\t\t\t{\n\t\t\t\t// We cannot detect LAME 3.95 reliably (same version string as\n\t\t\t\t// 3.95.1), so this is a blind spot. Everything < 3.95 is safe, though.\n\t\t\t\tif( major < 3 || ( major == 3 && minor < 95 ))\n\t\t\t\t\tgain_offset = 6;\n\t\t\t}\n\t\t}\n\n\t\tlame_offset += 9; // 9 in\n\n\t\t// the 4 big bits are tag revision, the small bits vbr method.\n\t\tlame_vbr = fr->bsbuf[lame_offset] & 15;\n\t\tlame_offset += 1; // 10 in\n\n\t\t// from rev1 proposal... not sure if all good in practice\n\t\tswitch( lame_vbr )\n\t\t{\n\t\tcase 1:\n\t\tcase 8: fr->vbr = MPG123_CBR; break;\n\t\tcase 2:\n\t\tcase 9: fr->vbr = MPG123_ABR; break;\n\t\tdefault: fr->vbr = MPG123_VBR; break;\t// 00 ==unknown is taken as VBR\n\t\t}\n\n\t\tlame_offset += 1; // 11 in, skipping lowpass filter value\n\t\tpeak = 0; // until better times arrived\n\t\tlame_offset += 4; // 15 in\n\n\t\t// ReplayGain values - lame only writes radio mode gain...\n\t\t// 16bit gain, 3 bits name, 3 bits originator, sign (1=-, 0=+),\n\t\t// dB value * 10 in 9 bits (fixed point) ignore the setting if name or\n\t\t// originator == 000!\n\t\t// radio      0 0 1 0 1 1 1 0 0 1 1 1 1 1 0 1\n\t\t// audiophile 0 1 0 0 1 0 0 0 0 0 0 1 0 1 0 0\n\t\tfor( i = 0; i < 2; ++i )\n\t\t{\n\t\t\tbyte\tgt = fr->bsbuf[lame_offset] >> 5;\n\t\t\tbyte\torigin = (fr->bsbuf[lame_offset] >> 2) & 0x7;\n\t\t\tfloat\tfactor = (fr->bsbuf[lame_offset] & 0x2) ? -0.1f : 0.1f;\n\t\t\tword\tgain  = bit_read_short( fr->bsbuf, &lame_offset ) & 0x1ff; // 19 in (2 cycles)\n\n\t\t\tif( origin == 0 || gt < 1 || gt > 2 )\n\t\t\t\tcontinue;\n\n\t\t\tgt--;\n\t\t\treplay_gain[gt] = factor * (float)gain;\n\n\t\t\t// apply gain offset for automatic origin.\n\t\t\tif( origin == 3 ) replay_gain[gt] += gain_offset;\n\t\t}\n\n\t\tfor( i = 0; i < 2; ++i )\n\t\t{\n\t\t\tif( fr->rva.level[i] <= 0 )\n\t\t\t{\n\t\t\t\tfr->rva.peak[i] = 0; // TODO: use parsed peak?\n\t\t\t\tfr->rva.gain[i] = replay_gain[i];\n\t\t\t\tfr->rva.level[i] = 0;\n\t\t\t}\n\t\t}\n\n\t\tlame_offset += 1; // 20 in, skipping encoding flags byte\n\n\t\t// ABR rate\n\t\tif( fr->vbr == MPG123_ABR )\n\t\t\tfr->abr_rate = fr->bsbuf[lame_offset];\n\t\tlame_offset += 1; // 21 in\n\n\t\t// Encoder delay and padding, two 12 bit values\n\t\t// ... lame does write them from int.\n\t\tpad_in  = ((((int) fr->bsbuf[lame_offset]) << 4) | (((int) fr->bsbuf[lame_offset+1]) >> 4));\n\t\tpad_out = ((((int) fr->bsbuf[lame_offset+1]) << 8) | ((int) fr->bsbuf[lame_offset+2])) & 0xfff;\n\t\tlame_offset += 3; // 24 in\n\n\t\tif( fr->p.flags & MPG123_GAPLESS )\n\t\t\tframe_gapless_init( fr, fr->track_frames, pad_in, pad_out );\n\t\t// final: 24 B LAME data\n\t}\n\ncheck_lame_tag_yes:\n\t// switch buffer back ...\n\tfr->bsbuf = fr->bsspace[fr->bsnum] + 512;\n\tfr->bsnum = (fr->bsnum + 1) & 1;\n\n\treturn 1;\n\ncheck_lame_tag_no:\n\treturn 0;\n}\n\n// first attempt of read ahead check to find the real first header; cannot believe what junk is out there!\nstatic int do_readahead( mpg123_handle_t *fr, ulong newhead )\n{\n\tulong\tnexthead = 0;\n\tint\thd = 0;\n\tmpg_off_t\tstart, oret;\n\tint\tret;\n\n\tif(!( !fr->firsthead && fr->rdat.flags & ( READER_SEEKABLE|READER_BUFFERED )))\n\t\treturn PARSE_GOOD;\n\n\tstart = fr->rd->tell( fr );\n\n\t// step framesize bytes forward and read next possible header\n\tif((oret = fr->rd->skip_bytes( fr, fr->framesize )) < 0 )\n\t\treturn oret == MPG123_NEED_MORE ? PARSE_MORE : PARSE_ERR;\n\n\t// read header, seek back.\n\thd = fr->rd->head_read( fr, &nexthead );\n\n\tif( fr->rd->back_bytes( fr, fr->rd->tell( fr ) - start ) < 0 )\n\t\treturn PARSE_ERR;\n\n\tif( hd == MPG123_NEED_MORE )\n\t\treturn PARSE_MORE;\n\n\tif( !hd ) return PARSE_END;\n\n\tif( !head_check( nexthead ) || !head_compatible( newhead, nexthead ))\n\t{\n\t\tfr->oldhead = 0; // start over\n\n\t\t// try next byte for valid header\n\t\tif(( ret = fr->rd->back_bytes( fr, 3 )) < 0 )\n\t\t\treturn PARSE_ERR;\n\t\treturn PARSE_AGAIN;\n\t}\n\n\treturn PARSE_GOOD;\n}\n\nstatic void halfspeed_prepare( mpg123_handle_t *fr )\n{\n\tif( fr->p.halfspeed && fr->lay == 3 )\n\t\tmemcpy( fr->ssave, fr->bsbuf, fr->ssize );\n}\n\n// if this returns 1, the next frame is the repetition.\nstatic int halfspeed_do( mpg123_handle_t *fr )\n{\n\t// speed-down hack: Play it again, Sam (the frame, I mean).\n\tif( fr->p.halfspeed )\n\t{\n\t\tif( fr->halfphase ) // repeat last frame\n\t\t{\n\t\t\tfr->to_decode = fr->to_ignore = TRUE;\n\t\t\tfr->halfphase--;\n\t\t\tfr->bitindex = 0;\n\t\t\tfr->wordpointer = (byte *)fr->bsbuf;\n\n\t\t\tif( fr->lay == 3 )\n\t\t\t\tmemcpy( fr->bsbuf, fr->ssave, fr->ssize );\n\n\t\t\tif( fr->error_protection )\n\t\t\t\tfr->crc = getbits( fr, 16 ); // skip crc\n\t\t\treturn 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfr->halfphase = fr->p.halfspeed - 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n// read ahead and find the next MPEG header, to guess framesize\n// return value: success code\n// PARSE_GOOD: found a valid frame size (stored in the handle).\n// < 0: error codes, possibly from feeder buffer (NEED_MORE)\n// PARSE_BAD: cannot get the framesize for some reason and shall silentry try the next possible header (if this is no free format stream after all...)\nstatic int guess_freeformat_framesize( mpg123_handle_t *fr, ulong oldhead )\n{\n\tulong\thead;\n\tint\tret;\n\tlong\ti;\n\n\tif(!( fr->rdat.flags & ( READER_SEEKABLE|READER_BUFFERED )))\n\t\treturn PARSE_BAD;\n\n\tif(( ret = fr->rd->head_read( fr, &head )) <= 0 )\n\t\treturn ret;\n\n\t// we are already 4 bytes into it\n\tfor( i = 4; i < MAXFRAMESIZE + 4; i++ )\n\t{\n\t\tif(( ret = fr->rd->head_shift( fr, &head )) <= 0 )\n\t\t\treturn ret;\n\n\t\t// no head_check needed, the mask contains all relevant bits.\n\t\tif(( head & HDR_SAMEMASK ) == ( oldhead & HDR_SAMEMASK ))\n\t\t{\n\t\t\tfr->rd->back_bytes( fr, i + 1 );\n\t\t\tfr->framesize = i - 3;\n\t\t\treturn PARSE_GOOD;\t// success!\n\t\t}\n\t}\n\n\tfr->rd->back_bytes( fr, i );\n\n\treturn PARSE_BAD;\n}\n\n// decode a header and write the information\n// into the frame structure\n// return values are compatible with those of read_frame, namely:\n//  1: success\n//  0: no valid header\n// <0: some error\n// you are required to do a head_check() before calling!\nstatic int decode_header( mpg123_handle_t *fr, ulong newhead, int *freeformat_count )\n{\n\t// for some reason, the layer and sampling freq settings used to be wrapped\n\t// in a weird conditional including MPG123_NO_RESYNC. what was I thinking?\n\t// this information has to be consistent.\n\tfr->lay = 4 - HDR_LAYER_VAL( newhead );\n\n\tif( HDR_VERSION_VAL( newhead ) & 0x2 )\n\t{\n\t\tfr->lsf = (HDR_VERSION_VAL( newhead ) & 0x1 ) ? 0 : 1;\n\t\tfr->sampling_frequency = HDR_SAMPLERATE_VAL( newhead ) + (fr->lsf * 3);\n\t\tfr->mpeg25 = 0;\n\t}\n\telse\n\t{\n\t\tfr->sampling_frequency = 6 + HDR_SAMPLERATE_VAL( newhead );\n\t\tfr->mpeg25 = 1;\n\t\tfr->lsf = 1;\n\t}\n\n\tfr->error_protection = HDR_CRC_VAL( newhead ) ^ 0x1;\n\tfr->bitrate_index = HDR_BITRATE_VAL( newhead );\n\tfr->padding = HDR_PADDING_VAL( newhead );\n\tfr->extension = HDR_PRIVATE_VAL( newhead );\n\tfr->mode = HDR_CHANNEL_VAL( newhead );\n\tfr->mode_ext = HDR_CHANEX_VAL( newhead );\n\tfr->copyright = HDR_COPYRIGHT_VAL( newhead );\n\tfr->original = HDR_ORIGINAL_VAL( newhead );\n\tfr->emphasis = HDR_EMPHASIS_VAL( newhead );\n\tfr->freeformat = !( newhead & HDR_BITRATE );\n\tfr->stereo = (fr->mode == MPG_MD_MONO) ? 1 : 2;\n\n\t// we can't use tabsel_123 for freeformat, so trying to guess framesize...\n\tif( fr->freeformat )\n\t{\n\t\t// when we first encounter the frame with freeformat, guess framesize\n\t\tif( fr->freeformat_framesize < 0 )\n\t\t{\n\t\t\tint\tret;\n\n\t\t\t*freeformat_count += 1;\n\t\t\tif( *freeformat_count > 5 )\n\t\t\t\treturn PARSE_BAD;\n\n\t\t\tret = guess_freeformat_framesize( fr, newhead );\n\n\t\t\tif( ret == PARSE_GOOD )\n\t\t\t{\n\t\t\t\tfr->freeformat_framesize = fr->framesize - fr->padding;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// freeformat should be CBR, so the same framesize can be used at the 2nd reading or later\n\t\t\tfr->framesize = fr->freeformat_framesize + fr->padding;\n\t\t}\n\t}\n\n\tswitch( fr->lay )\n\t{\n\tcase 3:\n\t\tfr->spf = fr->lsf ? 576 : 1152; /* MPEG 2.5 implies LSF.*/\n\t\tfr->do_layer = do_layer3;\n\t\tif( fr->lsf ) fr->ssize = (fr->stereo == 1) ? 9 : 17;\n\t\telse fr->ssize = (fr->stereo == 1) ? 17 : 32;\n\n\t\tif( fr->error_protection )\n\t\t\tfr->ssize += 2;\n\n\t\tif( !fr->freeformat )\n\t\t{\n\t\t\tfr->framesize  = (long)tabsel_123[fr->lsf][2][fr->bitrate_index] * 144000;\n\t\t\tfr->framesize /= freqs[fr->sampling_frequency]<<(fr->lsf);\n\t\t\tfr->framesize = fr->framesize + fr->padding - 4;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\treturn PARSE_BAD;\n\t}\n\n\tif( fr->framesize > MAXFRAMESIZE )\n\t\treturn PARSE_BAD;\n\n\treturn PARSE_GOOD;\n}\n\n// advance a byte in stream to get next possible header and forget\n// buffered data if possible (for feed reader).\nstatic int forget_head_shift( mpg123_handle_t *fr, ulong *newheadp, int forget )\n{\n\tint\tret;\n\n\tif(( ret = fr->rd->head_shift( fr, newheadp )) <= 0 )\n\t\treturn ret;\n\n\t// try to forget buffered data as early as possible to speed up parsing where\n\t// new data needs to be added for resync (and things would be re-parsed again\n\t// and again because of the start from beginning after hitting end).\n\tif( forget && fr->rd->forget != NULL )\n\t{\n\t\t// ensure that the last 4 bytes stay in buffers for reading the header anew.\n\t\tif(!fr->rd->back_bytes( fr, 4 ))\n\t\t{\n\t\t\tfr->rd->forget( fr );\n\t\t\tfr->rd->back_bytes( fr, -4 );\n\t\t}\n\t}\n\n\treturn ret; // No surprise here, error already triggered early return.\n}\n\n// trying to parse ID3v2.3 and ID3v2.4 tags...\n// returns:  0: bad or just unparseable tag\n//           1: good, (possibly) new tag info\n//          <0: reader error (may need more data feed, try again)\nstatic int parse_new_id3( mpg123_handle_t *fr, ulong first4bytes )\n{\n\tbyte\tbuf[6];\n\tulong\tlength=0;\n\tbyte\tflags = 0;\n\tint\tret = 1;\n\tint\tret2;\n\tbyte\tmajor = (byte)(first4bytes & 0xff);\n\n\tif( major == 0xff )\n\t\treturn 0;\n\n\tif(( ret2 = fr->rd->read_frame_body( fr, buf, 6 )) < 0 ) // read more header information\n\t\treturn ret2;\n\n\tif( buf[0] == 0xff )\n\t\treturn 0; // revision, will never be 0xff.\n\n\t// second new byte are some nice flags, if these are invalid skip the whole thing\n\tflags = buf[1];\n\n\t// length-10 or length-20 (footer present); 4 synchsafe integers == 28 bit number\n\t// we have already read 10 bytes, so left are length or length+10 bytes belonging to tag\n\tif( !synchsafe_to_long( buf + 2, length ))\n\t\treturn 0;\n\n\tif(( ret2 = fr->rd->skip_bytes( fr, length )) < 0 ) // will not store data in backbuff!\n\t\tret = ret2;\n\n\t// skip footer if present\n\tif(( ret > 0 ) && ( flags & 16 ) && (( ret2 = fr->rd->skip_bytes( fr, length )) < 0 ))\n\t\tret = ret2;\n\n\treturn ret;\n}\n\nstatic int handle_id3v2( mpg123_handle_t *fr, ulong newhead )\n{\n\tint\tret;\n\n\tfr->oldhead = 0;\t// think about that. Used to be present only for skipping of junk, not resync-style wetwork.\n\tret = parse_new_id3( fr, newhead );\n\tif( ret < 0 ) return ret;\n\n\treturn PARSE_AGAIN;\n}\n\n// watch out for junk/tags on beginning of stream by invalid header\nstatic int skip_junk( mpg123_handle_t *fr, ulong *newheadp, long *headcount )\n{\n\tint\tret;\n\tint\tfreeformat_count = 0;\n\tulong\tnewhead = *newheadp;\n\tuint\tforgetcount = 0;\n\tlong\tlimit = 65536;\n\n\t// check for id3v2; first three bytes (of 4) are \"ID3\"\n\tif(( newhead & (ulong)0xffffff00 ) == (ulong)0x49443300 )\n\t{\n\t\treturn handle_id3v2( fr, newhead );\n\t}\n\n\t// I even saw RIFF headers at the beginning of MPEG streams ;(\n\tif( newhead == ('R'<<24)+('I'<<16)+('F'<<8)+'F' )\n\t{\n\t\tif(( ret = fr->rd->head_read( fr, &newhead )) <= 0 )\n\t\t\treturn ret;\n\n\t\twhile( newhead != ('d'<<24)+('a'<<16)+('t'<<8)+'a' )\n\t\t{\n\t\t\tif( ++forgetcount > FORGET_INTERVAL )\n\t\t\t\tforgetcount = 0;\n\n\t\t\tif(( ret = forget_head_shift( fr, &newhead, !forgetcount )) <= 0 )\n\t\t\t\treturn ret;\n\t\t}\n\n\t\tif(( ret = fr->rd->head_read( fr, &newhead )) <= 0 )\n\t\t\treturn ret;\n\n\t\tfr->oldhead = 0;\n\t\t*newheadp = newhead;\n\n\t\treturn PARSE_AGAIN;\n\t}\n\n\t// unhandled junk... just continue search for a header, stepping in single bytes through next 64K.\n\t// this is rather identical to the resync loop.\n\t*newheadp = 0;\t// invalidate the external value.\n\tret = 0;\t\t// we will check the value after the loop.\n\n\t// we prepare for at least the 64K bytes as usual, unless\n\t// user explicitly wanted more (even infinity). Never less.\n\tif( fr->p.resync_limit < 0 || fr->p.resync_limit > limit )\n\t\tlimit = fr->p.resync_limit;\n\n\tdo\n\t{\n\t\t++(*headcount);\n\t\tif( limit >= 0 && *headcount >= limit )\n\t\t\tbreak;\n\n\t\tif( ++forgetcount > FORGET_INTERVAL )\n\t\t\tforgetcount = 0;\n\n\t\tif(( ret = forget_head_shift( fr, &newhead, !forgetcount )) <= 0 )\n\t\t\treturn ret;\n\n\t\tif( head_check( newhead ) && (ret = decode_header( fr, newhead, &freeformat_count )))\n\t\t\tbreak;\n\t} while( 1 );\n\n\tif( ret < 0 )\n\t\treturn ret;\n\n\tif( limit >= 0 && *headcount >= limit )\n\t\treturn PARSE_END;\n\n\t// If the new header ist good, it is already decoded.\n\t*newheadp = newhead;\n\n\treturn PARSE_GOOD;\n}\n\n// the newhead is bad, so let's check if it is something special, otherwise just resync.\nstatic int wetwork( mpg123_handle_t *fr, ulong *newheadp )\n{\n\tint\tret = PARSE_ERR;\n\tulong\tnewhead = *newheadp;\n\n\t*newheadp = 0;\n\n\t// classic ID3 tags. Read, then start parsing again.\n\tif(( newhead & 0xffffff00 ) == ( 'T'<<24 )+( 'A'<<16 )+( 'G'<<8 ))\n\t{\n\t\tfr->id3buf[0] = (byte)((newhead >> 24) & 0xff);\n\t\tfr->id3buf[1] = (byte)((newhead >> 16) & 0xff);\n\t\tfr->id3buf[2] = (byte)((newhead >> 8)  & 0xff);\n\t\tfr->id3buf[3] = (byte)( newhead        & 0xff);\n\n\t\tif(( ret = fr->rd->fullread( fr, fr->id3buf + 4, 124 )) < 0 )\n\t\t\treturn ret;\n\n\t\tfr->metaflags  |= MPG123_NEW_ID3|MPG123_ID3;\n\t\tfr->rdat.flags |= READER_ID3TAG; // that marks id3v1\n\n\t\treturn PARSE_AGAIN;\n\t}\n\n\t// this is similar to initial junk skipping code...\n\t// check for id3v2; first three bytes (of 4) are \"ID3\"\n\tif(( newhead & (ulong)0xffffff00 ) == (ulong)0x49443300 )\n\t{\n\t\treturn handle_id3v2( fr, newhead );\n\t}\n\n\t// now we got something bad at hand, try to recover.\n\tif( !( fr->p.flags & MPG123_NO_RESYNC ))\n\t{\n\t\tlong\ttry = 0;\n\t\tlong\tlimit = fr->p.resync_limit;\n\t\tuint\tforgetcount = 0;\n\n\t\t// if a resync is needed the bitreservoir of previous frames is no longer valid\n\t\tfr->bitreservoir = 0;\n\n\t\tdo\t// ... shift the header with additional single bytes until be found something that could be a header.\n\t\t{\n\t\t\ttry++;\n\n\t\t\tif( limit >= 0 && try >= limit )\n\t\t\t\tbreak;\n\n\t\t\tif( ++forgetcount > FORGET_INTERVAL )\n\t\t\t\tforgetcount = 0;\n\n\t\t\tif(( ret = forget_head_shift( fr, &newhead, !forgetcount )) <= 0 )\n\t\t\t{\n\t\t\t\t*newheadp = newhead;\n\t\t\t\treturn ret ? ret : PARSE_END;\n\t\t\t}\n\t\t} while( !head_check( newhead ));\n\n\t\t*newheadp = newhead;\n\n\t\t// now we either got something that could be a header, or we gave up.\n\t\tif( limit >= 0 && try >= limit )\n\t\t{\n\t\t\tfr->err = MPG123_RESYNC_FAIL;\n\t\t\treturn PARSE_ERR;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfr->oldhead = 0;\n\t\t\treturn PARSE_RESYNC;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfr->err = MPG123_OUT_OF_SYNC;\n\t\treturn PARSE_ERR;\n\t}\n}\n\n// that's a big one: read the next frame. 1 is success, <= 0 is some error\n// special error READER_MORE means: Please feed more data and try again.\nint read_frame( mpg123_handle_t *fr )\n{\n\t// TODO: rework this thing\n\tint\tfreeformat_count = 0;\n\tint\toldsize  = fr->framesize;\n\tint\toldphase = fr->halfphase;\n\tlong\theadcount = 0;\n\tbyte\t*newbuf;\n\tulong\tnewhead;\n\tmpg_off_t\tframepos;\n\tint\tret;\n\n\tfr->fsizeold = fr->framesize;\t// for Layer3\n\n\tif( halfspeed_do( fr ) == 1 )\n\t\treturn 1;\n\nread_again:\n\t// in case we are looping to find a valid frame, discard any buffered data before the current position.\n\t// this is essential to prevent endless looping, always going back to the beginning when feeder buffer is exhausted.\n\tif( fr->rd->forget != NULL )\n\t\tfr->rd->forget( fr );\n\n\tif(( ret = fr->rd->head_read( fr, &newhead )) <= 0 )\n\t\tgoto read_frame_bad;\ninit_resync:\n\tif( !fr->firsthead && !head_check( newhead ))\n\t{\n\t\tret = skip_junk(fr, &newhead, &headcount);\n\n\t\tif( ret < 0 )\n\t\t\tgoto read_frame_bad;\n\t\telse if( ret == PARSE_AGAIN )\n\t\t\tgoto read_again;\n\t\telse if( ret == PARSE_RESYNC )\n\t\t\tgoto init_resync;\n\t\telse if( ret == PARSE_END )\n\t\t{\n\t\t\tret = 0;\n\t\t\tgoto read_frame_bad;\n\t\t}\n\t}\n\n\tret = head_check( newhead );\n\tif( ret ) ret = decode_header( fr, newhead, &freeformat_count );\n\n\tif( ret < 0 )\n\t\tgoto read_frame_bad;\n\telse if( ret == PARSE_AGAIN )\n\t\tgoto read_again;\n\telse if( ret == PARSE_RESYNC )\n\t\tgoto init_resync;\n\telse if( ret == PARSE_END )\n\t{\n\t\tret = 0;\n\t\tgoto read_frame_bad;\n\t}\n\n\tif( ret == PARSE_BAD )\n\t{\n\t\t// header was not good.\n\t\tret = wetwork( fr, &newhead ); // Messy stuff, handle junk, resync ...\n\n\t\tif( ret < 0 )\n\t\t\tgoto read_frame_bad;\n\t\telse if( ret == PARSE_AGAIN )\n\t\t\tgoto read_again;\n\t\telse if( ret == PARSE_RESYNC )\n\t\t\tgoto init_resync;\n\t\telse if( ret == PARSE_END )\n\t\t{\n\t\t\tret = 0;\n\t\t\tgoto read_frame_bad;\n\t\t}\n\n\t\t// normally, we jumped already.\n\t\t// if for some reason everything's fine to continue, do continue.\n\t\tif( ret != PARSE_GOOD )\n\t\t\tgoto read_frame_bad;\n\t}\n\n\tif( !fr->firsthead )\n\t{\n\t\tret = do_readahead( fr, newhead );\n\n\t\t// readahead can fail mit NEED_MORE, in which case we must also make\n\t\t// the just read header available again for next go\n\t\tif( ret < 0 ) fr->rd->back_bytes( fr, 4 );\n\n\t\tif( ret < 0 )\n\t\t\tgoto read_frame_bad;\n\t\telse if( ret == PARSE_AGAIN )\n\t\t\tgoto read_again;\n\t\telse if( ret == PARSE_RESYNC )\n\t\t\tgoto init_resync;\n\t\telse if( ret == PARSE_END )\n\t\t{\n\t\t\tret = 0;\n\t\t\tgoto read_frame_bad;\n\t\t}\n\t}\n\n\t// now we should have our valid header and proceed to reading the frame.\n\n\t// if filepos is invalid, so is framepos\n\tframepos = fr->rd->tell( fr ) - 4;\n\n\t// flip/init buffer for Layer 3\n\tnewbuf = fr->bsspace[fr->bsnum] + 512;\n\n\t// read main data into memory\n\tif(( ret = fr->rd->read_frame_body( fr, newbuf, fr->framesize )) < 0 )\n\t{\n\t\t// if failed: flip back\n\t\tgoto read_frame_bad;\n\t}\n\n\tfr->bsbufold = fr->bsbuf;\n\tfr->bsbuf = newbuf;\n\tfr->bsnum = (fr->bsnum + 1) & 1;\n\n\tif( !fr->firsthead )\n\t{\n\t\tfr->firsthead = newhead; // _now_ it's time to store it... the first real header */\n\t\t// this is the first header of our current stream segment.\n\t\t// it is only the actual first header of the whole stream when fr->num is still below zero!\n\t\t// think of resyncs where firsthead has been reset for format flexibility.\n\t\tif( fr->num < 0 )\n\t\t{\n\t\t\tfr->audio_start = framepos;\n\n\t\t\t// only check for LAME tag at beginning of whole stream\n\t\t\t// ... when there indeed is one in between, it's the user's problem.\n\t\t\tif( fr->lay == 3 && check_lame_tag( fr ) == 1 )\n\t\t\t{\n\t\t\t\t// ...in practice, Xing/LAME tags are layer 3 only.\n\t\t\t\tif( fr->rd->forget != NULL )\n\t\t\t\t\tfr->rd->forget( fr );\n\n\t\t\t\tfr->oldhead = 0;\n\t\t\t\tgoto read_again;\n\t\t\t}\n\n\t\t\t// now adjust volume\n\t\t\tdo_rva( fr );\n\t\t}\n\t}\n\n\tfr->bitindex = 0;\n\tfr->wordpointer = (byte *)fr->bsbuf;\n\n\t// question: How bad does the floating point value get with repeated recomputation?\n\t// also, considering that we can play the file or parts of many times.\n\tif( ++fr->mean_frames != 0 )\n\t\tfr->mean_framesize = ((fr->mean_frames - 1) * fr->mean_framesize + compute_bpf( fr )) / fr->mean_frames ;\n\n\tfr->num++; // 0 for first frame!\n\n\tif(!( fr->state_flags & FRAME_FRANKENSTEIN ) && (( fr->track_frames > 0 && fr->num >= fr->track_frames ) || ( fr->gapless_frames > 0 && fr->num >= fr->gapless_frames )))\n\t\tfr->state_flags |= FRAME_FRANKENSTEIN;\n\n\thalfspeed_prepare( fr );\n\n\t// index the position\n\tfr->input_offset = framepos;\n\n\t// keep track of true frame positions in our frame index.\n\t// but only do so when we are sure that the frame number is accurate...\n\tif(( fr->state_flags & FRAME_ACCURATE ) && FI_NEXT( fr->index, fr->num ))\n\t\tfi_add( &fr->index, framepos );\n\n\tif( fr->silent_resync > 0 )\n\t\tfr->silent_resync--;\n\n\tif( fr->rd->forget != NULL )\n\t\tfr->rd->forget( fr );\n\n\tfr->to_decode = fr->to_ignore = TRUE;\n\tif( fr->error_protection )\n\t\tfr->crc = getbits( fr, 16 ); // skip crc\n\n\t// let's check for header change after deciding that the new one is good\n\t// and actually having read a frame.\n\t// header_change > 1: decoder structure has to be updated\n\t// preserve header_change value from previous runs if it is serious.\n\t// If we still have a big change pending, it should be dealt with outside,\n\t// fr->header_change set to zero afterwards.\n\tif( fr->header_change < 2 )\n\t{\n\t\tfr->header_change = 2;\t// output format change is possible...\n\t\tif( fr->oldhead )\t\t// check a following header for change\n\t\t{\n\t\t\tif( fr->oldhead == newhead )\n\t\t\t{\n\t\t\t\tfr->header_change = 0;\n\t\t\t}\n\t\t\telse if( head_compatible( fr->oldhead, newhead ))\n\t\t\t{\n\t\t\t\t// headers that match in this test behave the same for the outside world.\n\t\t\t\t// namely: same decoding routines, same amount of decoded data.\n\t\t\t\tfr->header_change = 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfr->state_flags |= FRAME_FRANKENSTEIN;\n\t\t\t}\n\t\t}\n\t\telse if( fr->firsthead && !head_compatible( fr->firsthead, newhead ))\n\t\t{\n\t\t\tfr->state_flags |= FRAME_FRANKENSTEIN;\n\t\t}\n\t}\n\n\tfr->oldhead = newhead;\n\n\treturn 1;\nread_frame_bad:\n\n\t// also if we searched for valid data in vein, we can forget skipped data.\n\t// otherwise, the feeder would hold every dead old byte in memory until the first valid frame!\n\tif( fr->rd->forget != NULL )\n\t\tfr->rd->forget( fr );\n\n\tfr->silent_resync = 0;\n\tif( fr->err == MPG123_OK )\n\t\tfr->err = MPG123_ERR_READER;\n\tfr->framesize = oldsize;\n\tfr->halfphase = oldphase;\n\n\t// that return code might be inherited from some feeder action, or reader error.\n\treturn ret;\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/reader.c",
    "content": "/*\nreader.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#ifdef _WIN32\n#include <io.h>\n#else\n#include <unistd.h>\n#endif\n\n#define READER_STREAM\t0\n#define READER_FEED\t\t1\n#define READER_BUF_STREAM\t2\n\nstatic int default_init( mpg123_handle_t *fr );\nstatic mpg_off_t get_fileinfo( mpg123_handle_t *fr );\n\n// methods for the buffer chain, mainly used for feed reader, but not just that.\nstatic buffy_t* buffy_new( size_t size, size_t minsize )\n{\n\tbuffy_t\t*newbuf = malloc( sizeof( buffy_t ));\n\n\tif( newbuf == NULL )\n\t\treturn NULL;\n\n\tnewbuf->realsize = size > minsize ? size : minsize;\n\tnewbuf->data = malloc( newbuf->realsize );\n\n\tif( newbuf->data == NULL )\n\t{\n\t\tfree( newbuf );\n\t\treturn NULL;\n\t}\n\n\tnewbuf->size = 0;\n\tnewbuf->next = NULL;\n\n\treturn newbuf;\n}\n\nstatic void buffy_del( buffy_t *buf )\n{\n\tif( buf )\n\t{\n\t\tfree( buf->data );\n\t\tfree( buf );\n\t}\n}\n\n// delete this buffy and all following buffies.\nstatic void buffy_del_chain( buffy_t *buf )\n{\n\twhile( buf )\n\t{\n\t\tbuffy_t *next = buf->next;\n\t\tbuffy_del( buf );\n\t\tbuf = next;\n\t}\n}\n\n// fetch a buffer from the pool (if possible) or create one.\nstatic buffy_t* bc_alloc( bufferchain_t *bc, size_t size )\n{\n\t// Easy route: Just try the first available buffer.\n\t// size does not matter, it's only a hint for creation of new buffers.\n\tif( bc->pool )\n\t{\n\t\tbuffy_t *buf = bc->pool;\n\n\t\tbc->pool = buf->next;\n\t\tbuf->next = NULL; // that shall be set to a sensible value later.\n\t\tbuf->size = 0;\n\t\tbc->pool_fill--;\n\n\t\treturn buf;\n\t}\n\n\treturn buffy_new( size, bc->bufblock );\n}\n\n// either stuff the buffer back into the pool or free it for good.\nstatic void bc_free( bufferchain_t *bc, buffy_t* buf )\n{\n\tif( !buf ) return;\n\n\tif( bc->pool_fill < bc->pool_size )\n\t{\n\t\tbuf->next = bc->pool;\n\t\tbc->pool = buf;\n\t\tbc->pool_fill++;\n\t}\n\telse buffy_del( buf );\n}\n\n// make the buffer count in the pool match the pool size.\nstatic int bc_fill_pool( bufferchain_t *bc )\n{\n\t// remove superfluous ones.\n\twhile( bc->pool_fill > bc->pool_size )\n\t{\n\t\t// lazyness: Just work on the front.\n\t\tbuffy_t *buf = bc->pool;\n\t\tbc->pool = buf->next;\n\t\tbuffy_del( buf );\n\t\tbc->pool_fill--;\n\t}\n\n\t// add missing ones.\n\twhile( bc->pool_fill < bc->pool_size )\n\t{\n\t\t// again, just work on the front.\n\t\tbuffy_t *buf = buffy_new( 0, bc->bufblock ); // use default block size.\n\t\tif( !buf ) return -1;\n\n\t\tbuf->next = bc->pool;\n\t\tbc->pool = buf;\n\t\tbc->pool_fill++;\n\t}\n\n\treturn 0;\n}\n\nstatic void bc_init( bufferchain_t *bc )\n{\n\tbc->first = NULL;\n\tbc->last = bc->first;\n\tbc->size = 0;\n\tbc->pos = 0;\n\tbc->firstpos = 0;\n\tbc->fileoff = 0;\n}\n\nstatic void bc_reset( bufferchain_t *bc )\n{\n\t// free current chain, possibly stuffing back into the pool.\n\twhile( bc->first )\n\t{\n\t\tbuffy_t *buf = bc->first;\n\t\tbc->first = buf->next;\n\t\tbc_free( bc, buf );\n\t}\n\n\tbc_fill_pool( bc );\t// ignoring an error here...\n\tbc_init( bc );\n}\n\n// create a new buffy at the end to be filled.\nstatic int bc_append( bufferchain_t *bc, mpg_ssize_t size )\n{\n\tbuffy_t\t*newbuf;\n\n\tif( size < 1 )\n\t\treturn -1;\n\n\tnewbuf = bc_alloc( bc, size );\n\tif( newbuf == NULL ) return -2;\n\n\tif( bc->last != NULL )\n\t\tbc->last->next = newbuf;\n\telse if( bc->first == NULL )\n\t\tbc->first = newbuf;\n\n\tbc->last  = newbuf;\n\n\treturn 0;\n}\n\nvoid bc_prepare( bufferchain_t *bc, size_t pool_size, size_t bufblock )\n{\n\tbc_poolsize( bc, pool_size, bufblock );\n\tbc->pool = NULL;\n\tbc->pool_fill = 0;\n\tbc_init( bc );\t// ensure that members are zeroed for read-only use.\n}\n\nsize_t bc_fill( bufferchain_t *bc )\n{\n\treturn (size_t)(bc->size - bc->pos);\n}\n\nvoid bc_poolsize( bufferchain_t *bc, size_t pool_size, size_t bufblock )\n{\n\tbc->pool_size = pool_size;\n\tbc->bufblock = bufblock;\n}\n\nvoid bc_cleanup( bufferchain_t *bc )\n{\n\tbuffy_del_chain( bc->pool );\n\tbc->pool_fill = 0;\n\tbc->pool = NULL;\n}\n\n// append a new buffer and copy content to it.\nstatic int bc_add( bufferchain_t *bc, const byte *data, mpg_ssize_t size )\n{\n\tint\tret = 0;\n\tmpg_ssize_t\tpart = 0;\n\n\twhile( size > 0 )\n\t{\n\t\t// try to fill up the last buffer block.\n\t\tif( bc->last != NULL && bc->last->size < bc->last->realsize )\n\t\t{\n\t\t\tpart = bc->last->realsize - bc->last->size;\n\t\t\tif( part > size ) part = size;\n\n\t\t\tmemcpy( bc->last->data + bc->last->size, data, part );\n\t\t\tbc->last->size += part;\n\t\t\tsize -= part;\n\t\t\tbc->size += part;\n\t\t\tdata += part;\n\t\t}\n\n\t\t// if there is still data left, put it into a new buffer block.\n\t\tif( size > 0 && ( ret = bc_append( bc, size )) != 0 )\n\t\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\n// common handler for \"You want more than I can give.\" situation.\nstatic mpg_ssize_t bc_need_more( bufferchain_t *bc )\n{\n\t// go back to firstpos, undo the previous reads\n\tbc->pos = bc->firstpos;\n\n\treturn MPG123_NEED_MORE;\n}\n\n// give some data, advancing position but not forgetting yet.\nstatic mpg_ssize_t bc_give( bufferchain_t *bc, byte *out, mpg_ssize_t size )\n{\n\tbuffy_t\t*b = bc->first;\n\tmpg_ssize_t\tgotcount = 0;\n\tmpg_ssize_t\toffset = 0;\n\n\tif( bc->size - bc->pos < size )\n\t\treturn bc_need_more( bc );\n\n\t// find the current buffer\n\twhile( b != NULL && ( offset + b->size ) <= bc->pos )\n\t{\n\t\toffset += b->size;\n\t\tb = b->next;\n\t}\n\n\t// now start copying from there\n\twhile( gotcount < size && ( b != NULL ))\n\t{\n\t\tmpg_ssize_t\tloff = bc->pos - offset;\n\t\tmpg_ssize_t\tchunk = size - gotcount; // amount of bytes to get from here...\n\n\t\tif( chunk > b->size - loff )\n\t\t\tchunk = b->size - loff;\n\n\t\tmemcpy( out + gotcount, b->data + loff, chunk );\n\t\tgotcount += chunk;\n\t\tbc->pos += chunk;\n\t\toffset += b->size;\n\t\tb = b->next;\n\t}\n\n\treturn gotcount;\n}\n\n// skip some bytes and return the new position.\n// the buffers are still there, just the read pointer is moved!\nstatic mpg_ssize_t bc_skip( bufferchain_t *bc, mpg_ssize_t count )\n{\n\tif( count >= 0 )\n\t{\n\t\tif( bc->size - bc->pos < count )\n\t\t\treturn bc_need_more( bc );\n\t\treturn bc->pos += count;\n\t}\n\n\treturn MPG123_ERR;\n}\n\nstatic mpg_ssize_t bc_seekback( bufferchain_t *bc, mpg_ssize_t count )\n{\n\tif( count >= 0 && count <= bc->pos )\n\t\treturn bc->pos -= count;\n\treturn MPG123_ERR;\n}\n\n// throw away buffies that we passed.\nstatic void bc_forget( bufferchain_t *bc )\n{\n\tbuffy_t\t*b = bc->first;\n\n\t// free all buffers that are def'n'tly outdated\n\t// we have buffers until filepos... delete all buffers fully below it\n\twhile( b != NULL && bc->pos >= b->size )\n\t{\n\t\tbuffy_t\t*n = b->next;\t// != NULL or this is indeed the end and the last cycle anyway\n\n\t\tif( n == NULL )\n\t\t\tbc->last = NULL;\t// Going to delete the last buffy...\n\t\tbc->fileoff += b->size;\n\t\tbc->pos  -= b->size;\n\t\tbc->size -= b->size;\n\t\tbc_free( bc, b );\n\t\tb = n;\n\t}\n\n\tbc->first = b;\n\tbc->firstpos = bc->pos;\n}\n\n// reader for input via manually provided buffers\nstatic int feed_init( mpg123_handle_t *fr )\n{\n\tbc_init( &fr->rdat.buffer );\n\tbc_fill_pool( &fr->rdat.buffer );\n\tfr->rdat.filelen = 0;\n\tfr->rdat.filepos = 0;\n\tfr->rdat.flags |= READER_BUFFERED;\n\n\treturn 0;\n}\n\n// externally called function, returns 0 on success, -1 on error\nint feed_more( mpg123_handle_t *fr, const byte *in, long count )\n{\n\tif( bc_add( &fr->rdat.buffer, in, count ) != 0 )\n\t\treturn MPG123_ERR;\n\n\treturn MPG123_OK;\n}\n\nstatic mpg_ssize_t feed_read( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )\n{\n\tmpg_ssize_t\tgotcount = bc_give( &fr->rdat.buffer, out, count );\n\n\tif( gotcount >= 0 && gotcount != count )\n\t\treturn MPG123_ERR;\n\n\treturn gotcount;\n}\n\n// returns reached position... negative ones are bad...\nstatic mpg_off_t feed_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )\n{\n\t// this is either the new buffer offset or some negative error value.\n\tmpg_off_t res = bc_skip( &fr->rdat.buffer, (mpg_ssize_t)len );\n\tif( res < 0 ) return res;\n\n\treturn fr->rdat.buffer.fileoff + res;\n}\n\nstatic int feed_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )\n{\n\tif( bytes >= 0 )\n\t\treturn bc_seekback(&fr->rdat.buffer, (mpg_ssize_t)bytes) >= 0 ? 0 : MPG123_ERR;\n\treturn feed_skip_bytes( fr, -bytes ) >= 0 ? 0 : MPG123_ERR;\n}\n\nstatic int feed_seek_frame( mpg123_handle_t *fr, mpg_off_t num )\n{\n\treturn MPG123_ERR;\n}\n\n// not just for feed reader, also for self-feeding buffered reader.\nstatic void buffered_forget( mpg123_handle_t *fr )\n{\n\tbc_forget( &fr->rdat.buffer );\n\tfr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;\n}\n\nmpg_off_t feed_set_pos( mpg123_handle_t *fr, mpg_off_t pos )\n{\n\tbufferchain_t\t*bc = &fr->rdat.buffer;\n\n\tif( pos >= bc->fileoff && pos - bc->fileoff < bc->size )\n\t{\n\t\t// we have the position!\n\t\tbc->pos = (mpg_ssize_t)(pos - bc->fileoff);\n\n\t\t// next input after end of buffer...\n\t\treturn bc->fileoff + bc->size;\n\t}\n\telse\n\t{\n\t\t// i expect to get the specific position on next feed. Forget what I have now.\n\t\tbc_reset( bc );\n\t\tbc->fileoff = pos;\n\n\t\t// next input from exactly that position.\n\t\treturn pos;\n\t}\n}\n\n// the specific stuff for buffered stream reader.\nstatic mpg_ssize_t buffered_fullread( mpg123_handle_t *fr, byte *out, mpg_ssize_t count )\n{\n\tbufferchain_t\t*bc = &fr->rdat.buffer;\n\tmpg_ssize_t\t\tgotcount;\n\n\tif( bc->size - bc->pos < count )\n\t{\n\t\t// add more stuff to buffer. If hitting end of file, adjust count.\n\t\tbyte\treadbuf[4096];\n\n\t\tmpg_ssize_t need = count - (bc->size - bc->pos);\n\n\t\twhile( need > 0 )\n\t\t{\n\t\t\tmpg_ssize_t\tgot = fr->rdat.fullread( fr, readbuf, sizeof( readbuf ));\n\t\t\tint\tret;\n\n\t\t\tif( got < 0 )\n\t\t\t\treturn MPG123_ERR;\n\n\t\t\tif( got > 0 && ( ret = bc_add( bc, readbuf, got )) != 0 )\n\t\t\t\treturn MPG123_ERR;\n\n\t\t\tneed -= got; // may underflow here...\n\n\t\t\tif( got < sizeof( readbuf )) // that naturally catches got == 0, too.\n\t\t\t\tbreak; // end.\n\t\t}\n\n\t\tif( bc->size - bc->pos < count )\n\t\t\tcount = bc->size - bc->pos; // we want only what we got.\n\t}\n\n\tgotcount = bc_give( bc, out, count );\n\n\tif( gotcount != count )\n\t\treturn MPG123_ERR;\n\treturn gotcount;\n}\n\n// stream based operation\nstatic mpg_ssize_t plain_fullread( mpg123_handle_t *fr, byte *buf, mpg_ssize_t count )\n{\n\tmpg_ssize_t\tret, cnt=0;\n\n\t// there used to be a check for expected file end here (length value or ID3 flag).\n\t// this is not needed:\n\t// 1. EOF is indicated by fdread returning zero bytes anyway.\n\t// 2. We get false positives of EOF for either files that grew or\n\t// 3. ... files that have ID3v1 tags in between (stream with intro).\n\twhile( cnt < count )\n\t{\n\t\tret = fr->rdat.fdread( fr, buf + cnt, count - cnt );\n\n\t\tif( ret < 0 ) return MPG123_ERR;\n\t\tif( ret == 0 ) break;\n\n\t\tif(!( fr->rdat.flags & READER_BUFFERED ))\n\t\t\tfr->rdat.filepos += ret;\n\t\tcnt += ret;\n\t}\n\n\treturn cnt;\n}\n\n// wrappers for actual reading/seeking... I'm full of wrappers here.\nstatic mpg_off_t io_seek( reader_data_t *rdat, mpg_off_t offset, int whence )\n{\n\tif( rdat->flags & READER_HANDLEIO )\n\t{\n\t\tif( rdat->r_lseek_handle != NULL )\n\t\t\treturn rdat->r_lseek_handle( rdat->iohandle, offset, whence );\n\t\treturn -1;\n\t}\n\n\treturn rdat->lseek( rdat->filept, offset, whence );\n}\n\nstatic mpg_ssize_t io_read( reader_data_t *rdat, void *buf, size_t count )\n{\n\tif( rdat->flags & READER_HANDLEIO )\n\t{\n\t\tif( rdat->r_read_handle != NULL )\n\t\t\treturn rdat->r_read_handle( rdat->iohandle, buf, count );\n\t\treturn -1;\n\t}\n\n\treturn rdat->read( rdat->filept, buf, count );\n}\n\n// A normal read and a read with timeout.\nstatic mpg_ssize_t plain_read( mpg123_handle_t *fr, void *buf, size_t count )\n{\n\treturn io_read( &fr->rdat, buf, count );\n}\n\nstatic mpg_off_t stream_lseek( mpg123_handle_t *fr, mpg_off_t pos, int whence )\n{\n\tmpg_off_t\tret;\n\n\tret = io_seek( &fr->rdat, pos, whence );\n\n\tif( ret >= 0 )\n\t{\n\t\tfr->rdat.filepos = ret;\n\t}\n\telse\n\t{\n\t\tfr->err = MPG123_LSEEK_FAILED;\n\t\tret = MPG123_ERR;\n\t}\n\n\treturn ret;\n}\n\nstatic void stream_close( mpg123_handle_t *fr )\n{\n\tif( fr->rdat.flags & READER_FD_OPENED )\n\t\tclose( fr->rdat.filept );\n\n\tfr->rdat.filept = 0;\n\n\tif( fr->rdat.flags & READER_BUFFERED )\n\t\tbc_reset( &fr->rdat.buffer );\n\n\tif( fr->rdat.flags & READER_HANDLEIO )\n\t{\n\t\tif( fr->rdat.cleanup_handle != NULL )\n\t\t\tfr->rdat.cleanup_handle( fr->rdat.iohandle );\n\t\tfr->rdat.iohandle = NULL;\n\t}\n}\n\nstatic int stream_seek_frame( mpg123_handle_t *fr, mpg_off_t newframe )\n{\n\t// seekable streams can go backwards and jump forwards.\n\t// non-seekable streams still can go forward, just not jump.\n\tif(( fr->rdat.flags & READER_SEEKABLE ) || ( newframe >= fr->num ))\n\t{\n\t\tmpg_off_t\tpreframe;\t// a leading frame we jump to\n\t\tmpg_off_t\tseek_to;\t// the byte offset we want to reach\n\t\tmpg_off_t\tto_skip;\t// bytes to skip to get there (can be negative)\n\n\t\t// now seek to nearest leading index position and read from there until newframe is reached.\n\t\t// we use skip_bytes, which handles seekable and non-seekable streams\n\t\t// (the latter only for positive offset, which we ensured before entering here).\n\t\tseek_to = frame_index_find( fr, newframe, &preframe );\n\n\t\t// no need to seek to index position if we are closer already.\n\t\t// but I am picky about fr->num == newframe, play safe by reading the frame again.\n\t\t// if you think that's stupid, don't call a seek to the current frame.\n\t\tif( fr->num >= newframe || fr->num < preframe )\n\t\t{\n\t\t\tto_skip = seek_to - fr->rd->tell( fr );\n\t\t\tif( fr->rd->skip_bytes( fr, to_skip ) != seek_to )\n\t\t\t\treturn MPG123_ERR;\n\n\t\t\tfr->num = preframe - 1; // watch out! I am going to read preframe... fr->num should indicate the frame before!\n\t\t}\n\n\t\twhile( fr->num < newframe )\n\t\t{\n\t\t\t// try to be non-fatal now... frameNum only gets advanced on success anyway\n\t\t\tif( !read_frame( fr )) break;\n\t\t}\n\n\t\t// now the wanted frame should be ready for decoding.\n\t\treturn MPG123_OK;\n\t}\n\telse\n\t{\n\t\tfr->err = MPG123_NO_SEEK;\n\t\treturn MPG123_ERR; // invalid, no seek happened\n\t}\n}\n\n// return FALSE on error, TRUE on success, READER_MORE on occasion\nstatic int generic_head_read( mpg123_handle_t *fr, ulong *newhead )\n{\n\tbyte\thbuf[4];\n\tint\tret = fr->rd->fullread( fr, hbuf, 4 );\n\n\tif( ret == MPG123_NEED_MORE )\n\t\treturn ret;\n\n\tif( ret != 4 ) return FALSE;\n\n\t*newhead = ((ulong) hbuf[0] << 24) | ((ulong) hbuf[1] << 16) | ((ulong) hbuf[2] << 8) | (ulong) hbuf[3];\n\n\treturn TRUE;\n}\n\n// return FALSE on error, TRUE on success, READER_MORE on occasion\nstatic int generic_head_shift( mpg123_handle_t *fr, ulong *head )\n{\n\tbyte\thbuf;\n\tint\tret = fr->rd->fullread( fr, &hbuf, 1 );\n\n\tif( ret == MPG123_NEED_MORE )\n\t\treturn ret;\n\n\tif( ret != 1 ) return FALSE;\n\n\t*head <<= 8;\n\t*head |= hbuf;\n\t*head &= 0xffffffff;\n\n\treturn TRUE;\n}\n\n// returns reached position... negative ones are bad...\nstatic mpg_off_t stream_skip_bytes( mpg123_handle_t *fr, mpg_off_t len )\n{\n\tif( fr->rdat.flags & READER_SEEKABLE )\n\t{\n\t\tmpg_off_t ret = stream_lseek( fr, len, SEEK_CUR );\n\t\treturn (ret < 0) ? MPG123_ERR : ret;\n\t}\n\telse if( len >= 0 )\n\t{\n\t\tbyte\tbuf[1024]; // ThOr: Compaq cxx complained and it makes sense to me... or should one do a cast? What for?\n\t\tmpg_ssize_t\tret;\n\n\t\twhile( len > 0 )\n\t\t{\n\t\t\tmpg_ssize_t num = len < (mpg_off_t)sizeof( buf ) ? (mpg_ssize_t)len : (mpg_ssize_t)sizeof( buf );\n\t\t\tret = fr->rd->fullread( fr, buf, num );\n\t\t\tif( ret < 0 ) return ret;\n\t\t\telse if( ret == 0 ) break; // EOF... an error? interface defined to tell the actual position...\n\t\t\tlen -= ret;\n\t\t}\n\n\t\treturn fr->rd->tell( fr );\n\t}\n\telse if( fr->rdat.flags & READER_BUFFERED )\n\t{\n\t\t// perhaps we _can_ go a bit back.\n\t\tif( fr->rdat.buffer.pos >= -len )\n\t\t{\n\t\t\tfr->rdat.buffer.pos += len;\n\t\t\treturn fr->rd->tell( fr );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfr->err = MPG123_NO_SEEK;\n\t\t\treturn MPG123_ERR;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfr->err = MPG123_NO_SEEK;\n\t\treturn MPG123_ERR;\n\t}\n}\n\n// return 0 on success...\nstatic int stream_back_bytes( mpg123_handle_t *fr, mpg_off_t bytes )\n{\n\tmpg_off_t\twant = fr->rd->tell( fr ) - bytes;\n\n\tif( want < 0 ) return MPG123_ERR;\n\n\tif( stream_skip_bytes( fr, -bytes ) != want )\n\t\treturn MPG123_ERR;\n\n\treturn 0;\n}\n\n\n// returns size on success...\nstatic int generic_read_frame_body( mpg123_handle_t *fr, byte *buf, int size )\n{\n\tlong\tl;\n\n\tif(( l = fr->rd->fullread( fr, buf, size )) != size )\n\t\treturn MPG123_ERR;\n\n\treturn l;\n}\n\nstatic mpg_off_t generic_tell( mpg123_handle_t *fr )\n{\n\tif( fr->rdat.flags & READER_BUFFERED )\n\t\tfr->rdat.filepos = fr->rdat.buffer.fileoff + fr->rdat.buffer.pos;\n\n\treturn fr->rdat.filepos;\n}\n\n// this does not (fully) work for non-seekable streams... You have to check for that flag, pal!\nstatic void stream_rewind( mpg123_handle_t *fr )\n{\n\tif( fr->rdat.flags & READER_SEEKABLE )\n\t{\n\t\tfr->rdat.filepos = stream_lseek( fr, 0, SEEK_SET );\n\t\tfr->rdat.buffer.fileoff = fr->rdat.filepos;\n\t}\n\n\tif( fr->rdat.flags & READER_BUFFERED )\n\t{\n\t\tfr->rdat.buffer.pos = 0;\n\t\tfr->rdat.buffer.firstpos = 0;\n\t\tfr->rdat.filepos = fr->rdat.buffer.fileoff;\n\t}\n}\n\n// returns length of a file (if filept points to a file)\n// reads the last 128 bytes information into buffer\n// ... that is not totally safe...\nstatic mpg_off_t get_fileinfo( mpg123_handle_t *fr )\n{\n\tmpg_off_t\tlen;\n\n\tif(( len = io_seek( &fr->rdat, 0, SEEK_END )) < 0 )\n\t\treturn -1;\n\n\tif( io_seek( &fr->rdat, -128, SEEK_END ) < 0 )\n\t\treturn -1;\n\n\tif( fr->rd->fullread( fr, (byte *)fr->id3buf, 128 ) != 128 )\n\t\treturn -1;\n\n\tif( !strncmp((char *)fr->id3buf, \"TAG\", 3 ))\n\t\tlen -= 128;\n\n\tif( io_seek( &fr->rdat, 0, SEEK_SET ) < 0 )\n\t\treturn -1;\n\n\tif( len <= 0 )\n\t\treturn -1;\n\n\treturn len;\n}\n\nstatic int bad_init( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic mpg_ssize_t bad_fullread( mpg123_handle_t *mh, byte *data, mpg_ssize_t count ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic int bad_head_read( mpg123_handle_t *mh, ulong *newhead ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic int bad_head_shift( mpg123_handle_t *mh, ulong *head ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic mpg_off_t bad_skip_bytes( mpg123_handle_t *mh, mpg_off_t len ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic int bad_read_frame_body( mpg123_handle_t *mh, byte *data, int size ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic int bad_back_bytes( mpg123_handle_t *mh, mpg_off_t bytes ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic int bad_seek_frame( mpg123_handle_t *mh, mpg_off_t num ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic mpg_off_t bad_tell( mpg123_handle_t *mh ) { mh->err = MPG123_NO_READER; return MPG123_ERR; }\nstatic void bad_rewind( mpg123_handle_t *mh ) { }\nstatic void bad_close( mpg123_handle_t *mh ) { }\n\nstatic reader_t bad_reader =\n{\n\tbad_init,\n\tbad_close,\n\tbad_fullread,\n\tbad_head_read,\n\tbad_head_shift,\n\tbad_skip_bytes,\n\tbad_read_frame_body,\n\tbad_back_bytes,\n\tbad_seek_frame,\n\tbad_tell,\n\tbad_rewind,\n\tNULL\n};\n\nvoid open_bad( mpg123_handle_t *mh )\n{\n\tmh->rd = &bad_reader;\n\tmh->rdat.flags = 0;\n\tbc_init( &mh->rdat.buffer );\n\tmh->rdat.filelen = -1;\n}\n\nstatic reader_t readers[] =\n{\n\t{\t// READER_STREAM\n\t\tdefault_init,\n\t\tstream_close,\n\t\tplain_fullread,\n\t\tgeneric_head_read,\n\t\tgeneric_head_shift,\n\t\tstream_skip_bytes,\n\t\tgeneric_read_frame_body,\n\t\tstream_back_bytes,\n\t\tstream_seek_frame,\n\t\tgeneric_tell,\n\t\tstream_rewind,\n\t\tNULL\n\t},\n\t{\t// READER_FEED\n\t\tfeed_init,\n\t\tstream_close,\n\t\tfeed_read,\n\t\tgeneric_head_read,\n\t\tgeneric_head_shift,\n\t\tfeed_skip_bytes,\n\t\tgeneric_read_frame_body,\n\t\tfeed_back_bytes,\n\t\tfeed_seek_frame,\n\t\tgeneric_tell,\n\t\tstream_rewind,\n\t\tbuffered_forget\n\t},\n\t{\t// READER_BUF_STREAM\n\t\tdefault_init,\n\t\tstream_close,\n\t\tbuffered_fullread,\n\t\tgeneric_head_read,\n\t\tgeneric_head_shift,\n\t\tstream_skip_bytes,\n\t\tgeneric_read_frame_body,\n\t\tstream_back_bytes,\n\t\tstream_seek_frame,\n\t\tgeneric_tell,\n\t\tstream_rewind,\n\t\tbuffered_forget\n\t}\n};\n\n// final code common to open_stream and open_stream_handle.\nstatic int open_finish( mpg123_handle_t *fr )\n{\n\tfr->rd = &readers[READER_STREAM];\n\tif( fr->rd->init( fr ) < 0 )\n\t\treturn -1;\n\n\treturn MPG123_OK;\n}\n\nint open_stream_handle( mpg123_handle_t *fr, void *iohandle )\n{\n\tfr->rdat.filelen = -1;\n\tfr->rdat.filept  = -1;\n\tfr->rdat.iohandle = iohandle;\n\tfr->rdat.flags = 0;\n\tfr->rdat.flags |= READER_HANDLEIO;\n\n\treturn open_finish( fr );\n}\n\nint open_feed( mpg123_handle_t *fr )\n{\n\tfr->rd = &readers[READER_FEED];\n\tfr->rdat.flags = 0;\n\n\tif( fr->rd->init( fr ) < 0 )\n\t\treturn -1;\n\n\treturn 0;\n}\n\nstatic mpg_ssize_t read_mpgtypes( int fd, void *buf, size_t count )\n{\n\treturn read( fd, buf, count );\n}\n\nstatic mpg_off_t lseek_mpgtypes( int fd, mpg_off_t offset, int whence )\n{\n\treturn lseek( fd, offset, whence );\n}\n\nstatic int default_init( mpg123_handle_t *fr )\n{\n\tfr->rdat.fdread = plain_read;\n\tfr->rdat.read = fr->rdat.r_read  != NULL ? fr->rdat.r_read  : read_mpgtypes;\n\tfr->rdat.lseek = fr->rdat.r_lseek != NULL ? fr->rdat.r_lseek : lseek_mpgtypes;\n\tfr->rdat.filelen = get_fileinfo( fr );\n\tfr->rdat.filepos = 0;\n\n\t// don't enable seeking on ICY streams, just plain normal files.\n\t// this check is necessary since the client can enforce ICY parsing on files that would otherwise be seekable.\n\t// it is a task for the future to make the ICY parsing safe with seeks ... or not.\n\tif( fr->rdat.filelen >= 0 )\n\t{\n\t\tfr->rdat.flags |= READER_SEEKABLE;\n\t\tif( !strncmp((char *)fr->id3buf,\"TAG\", 3 ))\n\t\t{\n\t\t\tfr->rdat.flags |= READER_ID3TAG;\n\t\t\tfr->metaflags  |= MPG123_NEW_ID3;\n\t\t}\n\t}\n\telse if( fr->p.flags & MPG123_SEEKBUFFER )\n\t{\n\t\t// switch reader to a buffered one, if allowed.\n\t\tif( fr->rd == &readers[READER_STREAM] )\n\t\t{\n\t\t\tfr->rd = &readers[READER_BUF_STREAM];\n\t\t\tfr->rdat.fullread = plain_fullread;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn -1;\n\t\t}\n\n\t\tbc_init( &fr->rdat.buffer );\n\t\tfr->rdat.filelen = 0; // we carry the offset, but never know how big the stream is.\n\t\tfr->rdat.flags |= READER_BUFFERED;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/reader.h",
    "content": "/*\nreader.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef READER_H\n#define READER_H\n\n#define READER_FD_OPENED\t0x1\n#define READER_ID3TAG\t0x2\n#define READER_SEEKABLE\t0x4\n#define READER_BUFFERED\t0x8\n#define READER_NONBLOCK\t0x20\n#define READER_HANDLEIO\t0x40\n\ntypedef struct buffy_s\n{\n\tbyte\t\t*data;\n\tmpg_ssize_t\t\tsize;\n\tmpg_ssize_t\t\trealsize;\n\tstruct buffy_s\t*next;\n} buffy_t;\n\ntypedef struct bufferchain_s\n{\n\tstruct buffy_s\t*first;\t\t// the beginning of the chain.\n\tstruct buffy_s\t*last;\t\t// the end...    of the chain.\n\tmpg_ssize_t\t\tsize;\t\t// aggregated size of all buffies.\n\n\t// these positions are relative to buffer chain beginning.\n\tmpg_ssize_t\t\tpos;\t\t// position in whole chain.\n\tmpg_ssize_t\t\tfirstpos;\t\t// the point of return on non-forget()\n\n\t// the \"real\" filepos is fileoff + pos.\n\tmpg_off_t\t\tfileoff;\t\t// beginning of chain is at this file offset.\n\tsize_t\t\tbufblock;\t\t// default (minimal) size of buffers.\n\tsize_t\t\tpool_size;\t// keep that many buffers in storage.\n\tsize_t\t\tpool_fill;\t// that many buffers are there.\n\n\t// a pool of buffers to re-use, if activated. It's a linked list that is worked on from the front.\n\tstruct buffy_s\t*pool;\n} bufferchain_t;\n\n// call this before any buffer chain use (even bc_init()).\nvoid bc_prepare( bufferchain_t*, size_t pool_size, size_t bufblock );\n// free persistent data in the buffer chain, after bc_reset().\nvoid bc_cleanup( bufferchain_t* );\n// change pool size. This does not actually allocate/free anything on itself, just instructs later operations to free less / allocate more buffers.\nvoid bc_poolsize( bufferchain_t*, size_t pool_size, size_t bufblock );\n// return available byte count in the buffer.\nsize_t bc_fill( bufferchain_t *bc );\n\ntypedef struct reader_data_s\n{\n\tmpg_off_t\t\tfilelen;\t\t// total file length or total buffer size\n\tmpg_off_t\t\tfilepos;\t\t// position in file or position in buffer chain\n\tint\t\tfilept;\n\n\t// custom opaque I/O handle from the client.\n\tvoid\t\t*iohandle;\n\tint\t\tflags;\n\tlong\t\ttimeout_sec;\n\n\tmpg_ssize_t (*fdread)( mpg123_handle_t*, void*, size_t );\n\n\t// user can replace the read and lseek functions. The r_* are the stored replacement functions or NULL.\n\tmpg_ssize_t (*r_read)( int fd, void *buf, size_t count );\n\tmpg_off_t (*r_lseek)( int fd, mpg_off_t offset, int whence );\n\n\t// These are custom I/O routines for opaque user handles.\n\t// They get picked if there's some iohandle set.\n\tmpg_ssize_t (*r_read_handle)( void *handle, void *buf, size_t count );\n\tmpg_off_t (*r_lseek_handle)( void *handle, mpg_off_t offset, int whence );\n\n\t// an optional cleaner for the handle on closing the stream.\n\tvoid (*cleanup_handle)( void *handle );\n\n\t// these two pointers are the actual workers (default map to POSIX read/lseek).\n\tmpg_ssize_t (*read)( int fd, void *buf, size_t count );\n\tmpg_off_t (*lseek)( int fd, mpg_off_t offset, int whence );\n\n\t// buffered readers want that abstracted, set internally.\n\tmpg_ssize_t (*fullread)( mpg123_handle_t*, byte*, mpg_ssize_t );\n\n\tbufferchain_t\tbuffer;\t\t// not dynamically allocated, these few struct bytes aren't worth the trouble.\n} reader_data_t;\n\n// start to use mpg_off_t to properly do LFS in future ... used to be long\ntypedef struct reader_s\n{\n\tint\t(*init)( mpg123_handle_t* );\n\tvoid\t(*close)( mpg123_handle_t* );\n\tmpg_ssize_t\t(*fullread)( mpg123_handle_t*, byte*, mpg_ssize_t);\n\tint\t(*head_read)( mpg123_handle_t*, ulong *newhead );\t\t// succ: TRUE, else <= 0 (FALSE or READER_MORE)\n\tint\t(*head_shift)( mpg123_handle_t*, ulong *head );\t\t// succ: TRUE, else <= 0 (FALSE or READER_MORE)\n\tmpg_off_t\t(*skip_bytes)( mpg123_handle_t*, mpg_off_t len );\t\t// succ: >=0, else error or READER_MORE\n\tint\t(*read_frame_body)( mpg123_handle_t*, byte*, int size );\n\tint\t(*back_bytes)( mpg123_handle_t*, mpg_off_t bytes );\n\tint\t(*seek_frame)( mpg123_handle_t*, mpg_off_t num );\n\tmpg_off_t\t(*tell)( mpg123_handle_t* );\n\tvoid\t(*rewind)( mpg123_handle_t* );\n\tvoid\t(*forget)( mpg123_handle_t* );\n} reader_t;\n\n// open a file by path or use an opened file descriptor\nint open_stream( mpg123_handle_t *fr, const char *path, int fd );\n// open an external handle.\nint open_stream_handle( mpg123_handle_t *fr, void *iohandle );\n\n// feed based operation has some specials\nint open_feed( mpg123_handle_t *fr );\n// externally called function, returns 0 on success, -1 on error\nint feed_more( mpg123_handle_t *fr, const byte *in, long count );\n// forget the data that has been read (free some buffers)\nvoid feed_forget( mpg123_handle_t *fr );\n// set position (inside available data if possible), return wanted byte offset of next feed.\nmpg_off_t feed_set_pos( mpg123_handle_t *fr, mpg_off_t pos );\n// error fallback\nvoid open_bad( mpg123_handle_t *fr );\n\n#endif//READER_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/sample.h",
    "content": "/*\nsample.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SAMPLE_H\n#define SAMPLE_H\n\n// define the accurate rounding function.\n#ifdef IEEE_FLOAT\n// rhis function is only available for IEEE754 single-precision values\n// rhis is nearly identical to proper rounding, just -+0.5 is rounded to 0\nstatic _inline int16_t ftoi16( float x )\n{\n\tunion\n\t{\n\t\tfloat\tf;\n\t\tint32_t\ti;\n\t} u_fi;\n\n\tu_fi.f = x + 12582912.0f;\t// Magic Number: 2^23 + 2^22\n\treturn (int16_t)u_fi.i;\n}\n\n#define REAL_TO_SHORT_ACCURATE( x )\tftoi16(x)\n#else\n// the \"proper\" rounding, plain C, a bit slow.\n#define REAL_TO_SHORT_ACCURATE( x )\t(short)((x) > 0.0f ? (x) + 0.5f : (x) - 0.5f)\n#endif\n\n// now define the normal rounding.\n#ifdef ACCURATE_ROUNDING\n#define REAL_TO_SHORT( x )\tREAL_TO_SHORT_ACCURATE( x )\n#else\n#define REAL_TO_SHORT( x )\t(short)( x )\n#endif\n\n#endif//SAMPLE_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/synth.c",
    "content": "/*\nsynth.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include \"sample.h\"\n\n#define BACKPEDAL\t0x10\t// we use autoincrement and thus need this re-adjustment for window/b0.\n#define BLOCK\t0x40\t// one decoding block is 64 samples.\n\n#define WRITE_SHORT_SAMPLE( samples, sum, clip ) \\\n\tif(( sum ) > 32767.0f ) { *(samples) = 0x7fff; (clip)++; } \\\n\telse if(( sum ) < -32768.0f ) { *(samples) = -0x8000; (clip)++; } \\\n\telse { *(samples) = REAL_TO_SHORT( sum ); }\n\n// main synth function, uses the plain dct64\nstatic int synth_1to1( float *bandPtr, int channel, mpg123_handle_t *fr, int final )\n{\n\tstatic const int\tstep = 2;\n\tshort\t\t*samples = (short *) (fr->buffer.data + fr->buffer.fill);\n\tfloat\t\t*b0, **buf; // (*buf)[0x110];\n\tint\t\tclip = 0;\n\tint\t\tbo1;\n\n\tif( !channel )\n\t{\n\t\tfr->bo--;\n\t\tfr->bo &= 0xf;\n\t\tbuf = fr->float_buffs[0];\n\t}\n\telse\n\t{\n\t\tsamples++;\n\t\tbuf = fr->float_buffs[1];\n\t}\n\n\tif( fr->bo & 0x1 )\n\t{\n\t\tb0 = buf[0];\n\t\tbo1 = fr->bo;\n\t\tdct64( buf[1] + ((fr->bo + 1) & 0xf ), buf[0] + fr->bo, bandPtr );\n\t}\n\telse\n\t{\n\t\tb0 = buf[1];\n\t\tbo1 = fr->bo+1;\n\t\tdct64( buf[0] + fr->bo, buf[1] + fr->bo + 1, bandPtr );\n\t}\n\n\t{\n\t\tfloat\t\t*window = fr->decwin + 16 - bo1;\n\t\tregister int\tj;\n\n\t\tfor( j = (BLOCK / 4); j; j--, b0 += 0x400 / BLOCK - BACKPEDAL, window += 0x800 / BLOCK - BACKPEDAL, samples += step )\n\t\t{\n\t\t\tfloat\tsum;\n\n\t\t\tsum  = REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum += REAL_MUL_SYNTH( *window++, *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *window++, *b0++ );\n\n\t\t\tWRITE_SHORT_SAMPLE( samples, sum, clip );\n\t\t}\n\n\t\t{\n\t\t\tfloat\tsum;\n\n\t\t\tsum  = REAL_MUL_SYNTH( window[0x0], b0[0x0] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0x2], b0[0x2] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0x4], b0[0x4] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0x6], b0[0x6] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0x8], b0[0x8] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0xA], b0[0xA] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0xC], b0[0xC] );\n\t\t\tsum += REAL_MUL_SYNTH( window[0xE], b0[0xE] );\n\n\t\t\tWRITE_SHORT_SAMPLE( samples, sum, clip );\n\t\t\tsamples += step;\n\t\t\tb0 -= 0x400 / BLOCK;\n\t\t\twindow -= 0x800 / BLOCK;\n\t\t}\n\t\twindow += bo1<<1;\n\n\t\tfor( j= (BLOCK / 4) - 1; j; j--, b0 -= 0x400 / BLOCK + BACKPEDAL, window -= 0x800 / BLOCK - BACKPEDAL, samples += step )\n\t\t{\n\t\t\tfloat\tsum;\n\n\t\t\tsum = -REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\t\t\tsum -= REAL_MUL_SYNTH( *(--window), *b0++ );\n\n\t\t\tWRITE_SHORT_SAMPLE( samples, sum, clip );\n\t\t}\n\t}\n\n\tif( final ) fr->buffer.fill += BLOCK * sizeof( short );\n\n\treturn clip;\n}\n\n// the call of left and right plain synth, wrapped.\n// this may be replaced by a direct stereo optimized synth.\nstatic int synth_stereo( float *bandPtr_l, float *bandPtr_r, mpg123_handle_t *fr )\n{\n\tint\tclip;\n\n\tclip = (fr->synth)( bandPtr_l, 0, fr, 0 );\n\tclip += (fr->synth)( bandPtr_r, 1, fr, 1 );\n\treturn clip;\n}\n\n// mono to stereo synth, wrapping over synth_1to1\nstatic int synth_1to1_m2s(float *bandPtr, mpg123_handle_t *fr )\n{\n\tbyte\t*samples = fr->buffer.data;\n\tint\ti, ret;\n\n\tret = synth_1to1( bandPtr, 0, fr, 1 );\n\tsamples += fr->buffer.fill - BLOCK * sizeof( short );\n\n\tfor( i = 0; i < (BLOCK / 2); i++ )\n\t{\n\t\t((short *)samples)[1] = ((short *)samples)[0];\n\t\tsamples += 2 * sizeof( short );\n\t}\n\n\treturn ret;\n}\n\n// mono synth, wrapping over synth_1to1\nstatic int synth_1to1_mono( float *bandPtr, mpg123_handle_t *fr )\n{\n\tshort\tsamples_tmp[BLOCK];\n\tshort\t*tmp1 = samples_tmp;\n\tbyte\t*samples = fr->buffer.data;\n\tint\tpnt = fr->buffer.fill;\n\tint\ti, ret;\n\n\t// save buffer stuff, trick samples_tmp into there, decode, restore\n\tfr->buffer.data = (byte *)samples_tmp;\n\tfr->buffer.fill = 0;\n\n\tret = synth_1to1( bandPtr, 0, fr, 0 );\t// decode into samples_tmp\n\tfr->buffer.data = samples;\t\t// restore original value\n\n\t// now append samples from samples_tmp\n\tsamples += pnt;\t\t\t// just the next mem in frame buffer\n\n\tfor( i = 0; i < (BLOCK / 2); i++ )\n\t{\n\t\t*((short *)samples) = *tmp1;\n\t\tsamples += sizeof( short );\n\t\ttmp1 += 2;\n\t}\n\n\tfr->buffer.fill = pnt + (BLOCK / 2) * sizeof( short );\n\n\treturn ret;\n}\n\nstatic const struct synth_s synth_base =\n{\n{\n{ synth_1to1 }\t// plain\n},\n{\n{ synth_stereo }\t// stereo, by default only wrappers over plain synth\n},\n{\n{ synth_1to1_m2s }\t// mono2stereo\n},\n{\n{ synth_1to1_mono }\t// mono\n}\n};\n\nvoid init_synth( mpg123_handle_t *fr )\n{\n\tfr->synths = synth_base;\n}\n\nstatic int find_synth(func_synth synth,  const func_synth synths[r_limit][f_limit])\n{\n\tenum synth_resample\tri;\n\tenum synth_format\tfi;\n\n\tfor( ri = 0; ri < r_limit; ++ri )\n\t{\n\t\tfor( fi = 0; fi < f_limit; ++fi )\n\t\t{\n\t\t\tif( synth == synths[ri][fi] )\n\t\t\t\treturn TRUE;\n                    }\n\t}\n\n\treturn FALSE;\n}\n\nenum optdec\n{\n\tautodec = 0,\n\tgeneric,\n\tnodec\n};\n\n// determine what kind of decoder is actually active\n// this depends on runtime choices which may cause fallback to i386 or generic code.\nstatic int find_dectype( mpg123_handle_t *fr )\n{\n\tenum optdec\ttype = nodec;\n\tfunc_synth\tbasic_synth = fr->synth;\n\n\tif( find_synth( basic_synth, synth_base.plain ))\n\t\ttype = generic;\n\n\tif( type != nodec )\n\t{\n\t\treturn MPG123_OK;\n\t}\n\telse\n\t{\n\t\tfr->err = MPG123_BAD_DECODER_SETUP;\n\t\treturn MPG123_ERR;\n\t}\n}\n\n\n// set synth functions for current frame\nint set_synth_functions( mpg123_handle_t *fr )\n{\n\tenum synth_resample\tresample = r_none;\n\tenum synth_format\tbasic_format = f_none; // default is always 16bit, or whatever.\n\n\tif( fr->af.dec_enc & MPG123_ENC_16 )\n\t\tbasic_format = f_16;\n\n\t// make sure the chosen format is compiled into this lib.\n\tif( basic_format == f_none )\n\t\treturn -1;\n\n\t// be explicit about downsampling variant.\n\tswitch( fr->down_sample )\n\t{\n\tcase 0: resample = r_1to1; break;\n\t}\n\n\tif( resample == r_none )\n\t\treturn -1;\n\n\t// finally selecting the synth functions for stereo / mono.\n\tfr->synth = fr->synths.plain[resample][basic_format];\n\tfr->synth_stereo = fr->synths.stereo[resample][basic_format];\n\tfr->synth_mono = fr->af.channels == 2 ? fr->synths.mono2stereo[resample][basic_format] : fr->synths.mono[resample][basic_format];\n\n\tif( find_dectype( fr ) != MPG123_OK )\n\t{\n\t\tfr->err = MPG123_BAD_DECODER_SETUP;\n\t\treturn MPG123_ERR;\n\t}\n\n\tif( frame_buffers( fr ) != 0 )\n\t{\n\t\tfr->err = MPG123_NO_BUFFERS;\n\t\treturn MPG123_ERR;\n\t}\n\n\tinit_layer3_stuff( fr );\n\tfr->make_decode_tables = make_decode_tables;\n\n\t// we allocated the table buffers just now, so (re)create the tables.\n\tfr->make_decode_tables( fr );\n\n\treturn 0;\n}\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/synth.h",
    "content": "/*\nsynth.h - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SYNTH_H\n#define SYNTH_H\n\ntypedef int (*func_synth)( float*, int, mpg123_handle_t*, int );\ntypedef int (*func_synth_mono)( float*, mpg123_handle_t* );\ntypedef int (*func_synth_stereo)( float*, float*, mpg123_handle_t* );\n\nenum synth_channel\n{\n\tc_plain = 0,\n\tc_stereo,\n\tc_m2s,\n\tc_mono,\n\tc_limit\n};\n\nenum synth_resample\n{\n\tr_none = -1,\n\tr_1to1 = 0,\n\tr_limit\n};\n\nenum synth_format\n{\n\tf_none = -1,\n\tf_16,\n\tf_limit\n};\n\ntypedef struct synth_s\n{\n\tfunc_synth\tplain[r_limit][f_limit];\n\tfunc_synth_stereo\tstereo[r_limit][f_limit];\n\tfunc_synth_mono\tmono2stereo[r_limit][f_limit];\n\tfunc_synth_mono\tmono[r_limit][f_limit];\n} synth_t;\n\nvoid init_synth( mpg123_handle_t *fr );\nint set_synth_functions( mpg123_handle_t *fr );\n\n#endif//SYNTH_H\n"
  },
  {
    "path": "engine/client/soundlib/libmpg/tabinit.c",
    "content": "/*\ntabinit.c - compact version of famous library mpg123\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"mpg123.h\"\n#include <math.h>\n\nstatic float cos64[16];\nstatic float cos32[8];\nstatic float cos16[4];\nstatic float cos8[2];\nstatic float cos4[1];\n\nstatic long intwinbase[] = {\n     0,    -1,    -1,    -1,    -1,    -1,    -1,    -2,    -2,    -2,\n    -2,    -3,    -3,    -4,    -4,    -5,    -5,    -6,    -7,    -7,\n    -8,    -9,   -10,   -11,   -13,   -14,   -16,   -17,   -19,   -21,\n   -24,   -26,   -29,   -31,   -35,   -38,   -41,   -45,   -49,   -53,\n   -58,   -63,   -68,   -73,   -79,   -85,   -91,   -97,  -104,  -111,\n  -117,  -125,  -132,  -139,  -147,  -154,  -161,  -169,  -176,  -183,\n  -190,  -196,  -202,  -208,  -213,  -218,  -222,  -225,  -227,  -228,\n  -228,  -227,  -224,  -221,  -215,  -208,  -200,  -189,  -177,  -163,\n  -146,  -127,  -106,   -83,   -57,   -29,     2,    36,    72,   111,\n   153,   197,   244,   294,   347,   401,   459,   519,   581,   645,\n   711,   779,   848,   919,   991,  1064,  1137,  1210,  1283,  1356,\n  1428,  1498,  1567,  1634,  1698,  1759,  1817,  1870,  1919,  1962,\n  2001,  2032,  2057,  2075,  2085,  2087,  2080,  2063,  2037,  2000,\n  1952,  1893,  1822,  1739,  1644,  1535,  1414,  1280,  1131,   970,\n   794,   605,   402,   185,   -45,  -288,  -545,  -814, -1095, -1388,\n -1692, -2006, -2330, -2663, -3004, -3351, -3705, -4063, -4425, -4788,\n -5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597, -7910, -8209,\n -8491, -8755, -8998, -9219, -9416, -9585, -9727, -9838, -9916, -9959,\n -9966, -9935, -9863, -9750, -9592, -9389, -9139, -8840, -8492, -8092,\n -7640, -7134, -6574, -5959, -5288, -4561, -3776, -2935, -2037, -1082,\n   -70,   998,  2122,  3300,  4533,  5818,  7154,  8540,  9975, 11455,\n 12980, 14548, 16155, 17799, 19478, 21189, 22929, 24694, 26482, 28289,\n 30112, 31947, 33791, 35640, 37489, 39336, 41176, 43006, 44821, 46617,\n 48390, 50137, 51853, 53534, 55178, 56778, 58333, 59838, 61289, 62684,\n 64019, 65290, 66494, 67629, 68692, 69679, 70590, 71420, 72169, 72835,\n 73415, 73908, 74313, 74630, 74856, 74992, 75038 };\n\nfloat *pnts[] = { cos64, cos32, cos16, cos8, cos4 };\n\nvoid prepare_decode_tables( void )\n{\n\tint\ti, k, kr, divv;\n\tfloat\t*costab;\n\n\tfor( i = 0; i < 5; i++ )\n\t{\n\t\tkr = 0x10 >> i;\n\t\tdivv = 0x40 >> i;\n\t\tcostab = pnts[i];\n\n\t\tfor( k = 0; k < kr; k++)\n\t\t\tcostab[k] = DOUBLE_TO_REAL( 1.0 / ( 2.0 * cos( M_PI * ((double)k * 2.0 + 1.0 ) / (double)divv )));\n\t}\n}\n\nvoid make_decode_tables( mpg123_handle_t *fr )\n{\n\tint\ti, j;\n\tint\tidx = 0;\n\tdouble\tscaleval;\n\n\t// scale is always based on 1.0.\n\tscaleval = -0.5 * (fr->lastscale < 0 ? fr->p.outscale : fr->lastscale);\n\n\tfor( i = 0, j = 0; i < 256; i++, j++, idx += 32 )\n\t{\n\t\tif( idx < 512 + 16 )\n\t\t\tfr->decwin[idx+16] = fr->decwin[idx] = DOUBLE_TO_REAL( (double)intwinbase[j] * scaleval );\n\n\t\tif( i % 32 == 31 )\n\t\t\tidx -= 1023;\n\n\t\tif( i % 64 == 63 )\n\t\t\tscaleval = -scaleval;\n\t}\n\n\tfor( ; i < 512; i++, j--, idx += 32 )\n\t{\n\t\tif( idx < 512 + 16 )\n\t\t\tfr->decwin[idx+16] = fr->decwin[idx] = DOUBLE_TO_REAL( (double)intwinbase[j] * scaleval );\n\n\t\tif( i % 32 == 31 )\n\t\t\tidx -= 1023;\n\t\tif( i % 64 == 63 )\n\t\t\tscaleval = -scaleval;\n\t}\n}\n"
  },
  {
    "path": "engine/client/soundlib/ogg_filestream.c",
    "content": "/*\nogg_filestream.c - helper struct for working with Ogg files\nCopyright (C) 2024 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"ogg_filestream.h\"\n#include \"soundlib.h\"\n#include <string.h>\n\nsize_t OggFilestream_Read( void *ptr, size_t blockSize, size_t nmemb, void *datasource )\n{\n\togg_filestream_t *filestream = (ogg_filestream_t *)datasource;\n\tsize_t remain = filestream->filesize - filestream->position;\n\tsize_t dataSize = blockSize * nmemb;\n\n\t// reads as many blocks as fits in remaining memory\n\tif( dataSize > remain )\n\t\tdataSize = remain - remain % blockSize;\n\n\tmemcpy( ptr, filestream->buffer + filestream->position, dataSize );\n\tfilestream->position += dataSize;\n\treturn dataSize / blockSize;\n}\n\nint OggFilestream_Seek( void *datasource, int64_t offset, int whence )\n{\n\tint64_t position;\n\togg_filestream_t *filestream = (ogg_filestream_t *)datasource;\n\n\tif( whence == SEEK_SET )\n\t\tposition = offset;\n\telse if( whence == SEEK_CUR )\n\t\tposition = offset + filestream->position;\n\telse if( whence == SEEK_END )\n\t\tposition = offset + filestream->filesize;\n\telse\n\t\treturn -1;\n\n\tif( position < 0 || position > filestream->filesize )\n\t\treturn -1;\n\n\tfilestream->position = position;\n\treturn 0;\n}\n\nlong OggFilestream_Tell( void *datasource )\n{\n\togg_filestream_t *filestream = (ogg_filestream_t *)datasource;\n\treturn filestream->position;\n}\n"
  },
  {
    "path": "engine/client/soundlib/ogg_filestream.h",
    "content": "/*\nogg_filestream.h - helper struct for working with Ogg files\nCopyright (C) 2024 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef OGG_FILESTREAM_H\n#define OGG_FILESTREAM_H\n\n#include \"xash3d_types.h\"\n\ntypedef struct ogg_filestream_s\n{\n\tconst char *name;\n\tconst byte *buffer;\n\tsize_t     filesize;\n\tsize_t     position;\n} ogg_filestream_t;\n\nsize_t OggFilestream_Read( void *ptr, size_t blockSize, size_t nmemb, void *datasource );\nint OggFilestream_Seek( void *datasource, int64_t offset, int whence );\nlong OggFilestream_Tell( void *datasource );\n\nstatic inline void OggFilestream_Init( ogg_filestream_t *filestream, const char *name, const byte *buffer, size_t filesize )\n{\n\tfilestream->name = name;\n\tfilestream->buffer = buffer;\n\tfilestream->filesize = filesize;\n\tfilestream->position = 0;\n}\n\n#endif // OGG_FILESTREAM_H\n"
  },
  {
    "path": "engine/client/soundlib/snd_main.c",
    "content": "/*\nsnd_main.c - load & save various sound formats\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"soundlib.h\"\n\nstatic void Sound_Reset( void )\n{\n\t// reset global variables\n\tsound.width = sound.rate = 0;\n\tsound.channels = sound.loopstart = 0;\n\tsound.samples = sound.flags = 0;\n\tsound.type = WF_UNKNOWN;\n\n\tsound.wav = NULL;\n\tsound.size = 0;\n}\n\nstatic MALLOC_LIKE( FS_FreeSound, 1 ) wavdata_t *SoundPack( void )\n{\n\twavdata_t *pack = Mem_Malloc( host.soundpool, sizeof( *pack ) + sound.size );\n\n\tpack->size = sound.size;\n\tpack->loopStart = sound.loopstart;\n\tpack->samples = sound.samples;\n\tpack->type = sound.type;\n\tpack->flags = sound.flags;\n\tpack->rate = sound.rate;\n\tpack->width = sound.width;\n\tpack->channels = sound.channels;\n\tmemcpy( pack->buffer, sound.wav, sound.size );\n\n\tMem_Free( sound.wav );\n\tsound.wav = NULL;\n\n\treturn pack;\n}\n\n/*\n================\nFS_LoadSound\n\nloading and unpack to wav any known sound\n================\n*/\nwavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )\n{\n\tconst char *ext = COM_FileExtension( filename );\n\tstring loadname;\n\tqboolean anyformat = true;\n\tconst loadwavfmt_t *format;\n\n\tSound_Reset(); // clear old sounddata\n\tQ_strncpy( loadname, filename, sizeof( loadname ));\n\n\tif( COM_CheckStringEmpty( ext ))\n\t{\n\t\t// we needs to compare file extension with list of supported formats\n\t\t// and be sure what is real extension, not a filename with dot\n\t\tfor( format = sound.loadformats; format && format->ext; format++ )\n\t\t{\n\t\t\tif( !Q_stricmp( format->ext, ext ))\n\t\t\t{\n\t\t\t\tCOM_StripExtension( loadname );\n\t\t\t\tanyformat = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// special mode: skip any checks, load file from buffer\n\tif( filename[0] == '#' && buffer && size )\n\t\tgoto load_internal;\n\n\t// now try all the formats in the selected list\n\tfor( format = sound.loadformats; format && format->ext; format++)\n\t{\n\t\tif( anyformat || !Q_stricmp( ext, format->ext ))\n\t\t{\n\t\t\tqboolean success = false;\n\t\t\tfs_offset_t filesize = 0;\n\t\t\tbyte *f;\n\t\t\tstring path;\n\n\t\t\tQ_snprintf( path, sizeof( path ), DEFAULT_SOUNDPATH \"%s.%s\", loadname, format->ext );\n\n\t\t\tf = FS_LoadFile( path, &filesize, false );\n\t\t\tif( f && filesize > 0 )\n\t\t\t{\n\t\t\t\tsuccess = format->loadfunc( path, f, filesize );\n\t\t\t\tMem_Free( f ); // release buffer\n\t\t\t}\n\n\t\t\tif( success )\n\t\t\t\treturn SoundPack(); // loaded\n\n\t\t\tQ_snprintf( path, sizeof( path ), \"%s.%s\", loadname, format->ext );\n\t\t\tf = FS_LoadFile( path, &filesize, false );\n\t\t\tif( f && filesize > 0 )\n\t\t\t{\n\t\t\t\tsuccess = format->loadfunc( path, f, filesize );\n\t\t\t\tMem_Free( f ); // release buffer\n\t\t\t}\n\n\t\t\tif( success )\n\t\t\t\treturn SoundPack();\n\t\t}\n\t}\n\nload_internal:\n\tfor( format = sound.loadformats; format && format->ext; format++ )\n\t{\n\t\tif( anyformat || !Q_stricmp( ext, format->ext ))\n\t\t{\n\t\t\tif( buffer && size > 0  )\n\t\t\t{\n\t\t\t\tif( format->loadfunc( loadname, buffer, size ))\n\t\t\t\t\treturn SoundPack(); // loaded\n\t\t\t}\n\t\t}\n\t}\n\n\tif( filename[0] != '#' )\n\t\tCon_DPrintf( S_WARN \"%s: couldn't load \\\"%s\\\"\\n\", __func__, loadname );\n\n\treturn NULL;\n}\n\n/*\n================\nSound_FreeSound\n\nfree WAV buffer\n================\n*/\nvoid FS_FreeSound( wavdata_t *pack )\n{\n\tif( !pack ) return;\n\tMem_Free( pack );\n}\n\n/*\n================\nFS_OpenStream\n\nopen and reading basic info from sound stream\n================\n*/\nstream_t *FS_OpenStream( const char *filename )\n{\n\tconst char\t*ext = COM_FileExtension( filename );\n\tstring\t\tloadname;\n\tqboolean\t\tanyformat = true;\n\tconst streamfmt_t\t*format;\n\tstream_t\t\t*stream = NULL;\n\n\tSound_Reset(); // clear old streaminfo\n\tQ_strncpy( loadname, filename, sizeof( loadname ));\n\n\tif( COM_CheckStringEmpty( ext ))\n\t{\n\t\t// we needs to compare file extension with list of supported formats\n\t\t// and be sure what is real extension, not a filename with dot\n\t\tfor( format = sound.streamformat; format && format->ext; format++ )\n\t\t{\n\t\t\tif( !Q_stricmp( format->ext, ext ))\n\t\t\t{\n\t\t\t\tCOM_StripExtension( loadname );\n\t\t\t\tanyformat = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// now try all the formats in the selected list\n\tfor( format = sound.streamformat; format && format->ext; format++)\n\t{\n\t\tif( anyformat || !Q_stricmp( ext, format->ext ))\n\t\t{\n\t\t\tstring path;\n\n\t\t\tQ_snprintf( path, sizeof( path ), \"%s.%s\", loadname, format->ext );\n\n\t\t\tif(( stream = format->openfunc( path )) != NULL )\n\t\t\t{\n\t\t\t\tstream->format = format;\n\t\t\t\treturn stream; // done\n\t\t\t}\n\t\t}\n\t}\n\n\t// compatibility with original Xash3D, try media/ folder\n\tif( Q_strncmp( filename, \"media/\", sizeof( \"media/\" ) - 1 ))\n\t{\n\t\tQ_snprintf( loadname, sizeof( loadname ), \"media/%s\", filename );\n\t\tstream = FS_OpenStream( loadname );\n\t}\n\telse\n\t{\n\t\tCon_Reportf( \"%s: couldn't open \\\"%s\\\" or \\\"%s\\\"\\n\", __func__, filename + 6, filename );\n\t}\n\n\treturn stream;\n}\n\n/*\n================\nFS_ReadStream\n\nextract stream as wav-data and put into buffer, move file pointer\n================\n*/\nint FS_ReadStream( stream_t *stream, int bytes, void *buffer )\n{\n\tif( !stream || !stream->format || !stream->format->readfunc )\n\t\treturn 0;\n\n\tif( bytes <= 0 || buffer == NULL )\n\t\treturn 0;\n\n\treturn stream->format->readfunc( stream, bytes, buffer );\n}\n\n/*\n================\nFS_GetStreamPos\n\nget stream position (in bytes)\n================\n*/\nint FS_GetStreamPos( stream_t *stream )\n{\n\tif( !stream || !stream->format || !stream->format->getposfunc )\n\t\treturn -1;\n\n\treturn stream->format->getposfunc( stream );\n}\n\n/*\n================\nFS_SetStreamPos\n\nset stream position (in bytes)\n================\n*/\nint FS_SetStreamPos( stream_t *stream, int newpos )\n{\n\tif( !stream || !stream->format || !stream->format->setposfunc )\n\t\treturn -1;\n\n\treturn stream->format->setposfunc( stream, newpos );\n}\n\n/*\n================\nFS_FreeStream\n\nclose sound stream\n================\n*/\nvoid FS_FreeStream( stream_t *stream )\n{\n\tif( !stream || !stream->format || !stream->format->freefunc )\n\t\treturn;\n\n\tstream->format->freefunc( stream );\n}\n\n#if XASH_ENGINE_TESTS\n#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \\\nint EXPORT export( const uint8_t *Data, size_t Size ); \\\nint EXPORT export( const uint8_t *Data, size_t Size ) \\\n{ \\\n\twavdata_t *wav; \\\n\thost.type = HOST_NORMAL; \\\n\tMemory_Init(); \\\n\tSound_Init(); \\\n\tif( target( \"#internal\", Data, Size )) \\\n\t{ \\\n\t\twav = SoundPack(); \\\n\t\tFS_FreeSound( wav ); \\\n\t} \\\n\tSound_Shutdown(); \\\n\treturn 0; \\\n} \\\n\nIMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadMPG, Sound_LoadMPG )\nIMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV )\n#endif\n"
  },
  {
    "path": "engine/client/soundlib/snd_mp3.c",
    "content": "/*\nsnd_mp3.c - mp3 format loading and streaming\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"soundlib.h\"\n#include \"libmpg/libmpg.h\"\n\n#pragma pack( push, 1 )\ntypedef struct did3v2_header_s\n{\n\tchar     ident[3];  // must be \"ID3\"\n\tuint8_t  major_ver; // must be 4\n\tuint8_t  minor_ver; // must be 0\n\tuint8_t  flags;\n\tuint32_t length; // size of extended header, padding and frames\n} did3v2_header_t;\nSTATIC_CHECK_SIZEOF( did3v2_header_t, 10, 10 );\n\ntypedef struct did3v2_extended_header_s\n{\n\tuint32_t length;\n\tuint8_t  flags_length;\n\tuint8_t  flags[1];\n} did3v2_extended_header_t;\nSTATIC_CHECK_SIZEOF( did3v2_extended_header_t, 6, 6 );\n\ntypedef struct did3v2_frame_s\n{\n\tchar     frame_id[4];\n\tuint32_t length;\n\tuint8_t  flags[2];\n} did3v2_frame_t;\nSTATIC_CHECK_SIZEOF( did3v2_frame_t, 10, 10 );\n#pragma pack( pop )\n\ntypedef enum did3v2_header_flags_e\n{\n\tID3V2_HEADER_UNSYHCHRONIZATION = BIT( 7U ),\n\tID3V2_HEADER_EXTENDED_HEADER   = BIT( 6U ),\n\tID3V2_HEADER_EXPERIMENTAL      = BIT( 5U ),\n\tID3V2_HEADER_FOOTER_PRESENT    = BIT( 4U ),\n} did3v2_header_flags_t;\n\n#define CHECK_IDENT( ident, b0, b1, b2 )        ((( ident )[0]) == ( b0 ) && (( ident )[1]) == ( b1 ) && (( ident )[2]) == ( b2 ))\n#define CHECK_FRAME_ID( ident, b0, b1, b2, b3 ) ( CHECK_IDENT( ident, b0, b1, b2 ) && (( ident )[3]) == ( b3 ))\n\nstatic uint32_t Sound_ParseSynchInteger( uint32_t v )\n{\n\tuint32_t res = 0;\n\n\t// read as big endian\n\tres |= (( v >> 24 ) & 0x7f ) << 0;\n\tres |= (( v >> 16 ) & 0x7f ) << 7;\n\tres |= (( v >> 8  ) & 0x7f ) << 14;\n\tres |= (( v >> 0  ) & 0x7f ) << 21;\n\n\treturn res;\n}\n\nstatic void Sound_HandleCustomID3Comment( const char *key, const char *value )\n{\n\tif( !Q_strcmp( key, \"LOOP_START\" ) || !Q_strcmp( key, \"LOOPSTART\" ))\n\t{\n\t\tsound.loopstart = Q_atoi( value );\n\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t}\n\t// unknown comment is not an error\n}\n\nstatic qboolean Sound_ParseID3Frame( const did3v2_frame_t *frame, const byte *buffer, size_t frame_length )\n{\n\tif( CHECK_FRAME_ID( frame->frame_id, 'T', 'X', 'X', 'X' ))\n\t{\n\t\tstring key, value;\n\t\tint32_t key_len, value_len;\n\n\t\tif( buffer[0] == 0x00 || buffer[0] == 0x03 )\n\t\t{\n\t\t\tkey_len = Q_strncpy( key, &buffer[1], sizeof( key ));\n\t\t\tvalue_len = frame_length - (1 + key_len + 1);\n\t\t\tif( value_len <= 0 || value_len >= sizeof( value ))\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"%s: invalid TXXX description, possibly broken file.\\n\", __func__ );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tmemcpy( value, &buffer[1 + key_len + 1], value_len );\n\t\t\tvalue[value_len + 1] = 0;\n\n\t\t\tSound_HandleCustomID3Comment( key, value );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( buffer[0] == 0x01 || buffer[0] == 0x02 ) // UTF-16 with BOM\n\t\t\t\tCon_Printf( S_ERROR \"%s: UTF-16 encoding is unsupported. Use UTF-8 or ISO-8859!\\n\", __func__ );\n\t\t\telse\n\t\t\t\tCon_Printf( S_ERROR \"%s: unknown TXXX tag encoding %d, possibly broken file.\\n\", __func__, buffer[0] );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nstatic qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize )\n{\n\tconst did3v2_header_t *header = (const did3v2_header_t *)buffer;\n\tconst byte *buffer_begin = buffer;\n\tuint32_t tag_length;\n\n\tif( filesize < sizeof( *header ))\n\t\t return false;\n\n\tbuffer += sizeof( *header );\n\n\t// support only id3v2\n\tif( !CHECK_IDENT( header->ident, 'I', 'D', '3' ))\n\t{\n\t\t// old id3v1 header found\n\t\tif( CHECK_IDENT( header->ident, 'T', 'A', 'G' ))\n\t\t\tCon_Printf( S_ERROR \"%s: ID3v1 is not supported! Convert to ID3v2.4!\\n\", __func__ );\n\n\t\treturn true; // missing tag header is not an error\n\t}\n\n\t// support only latest id3 v2.4\n\tif( header->major_ver != 4 || header->minor_ver == 0xff )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: invalid ID3v2 tag version 2.%d.%d. Convert to ID3v2.4!\\n\", __func__, header->major_ver, header->minor_ver );\n\t\treturn false;\n\t}\n\n\ttag_length = Sound_ParseSynchInteger( header->length );\n\tif( tag_length > filesize - sizeof( *header ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: invalid tag length %u, possibly broken file.\\n\", __func__, tag_length );\n\t\treturn false;\n\t}\n\n\t// just skip extended header\n\tif( FBitSet( header->flags, ID3V2_HEADER_EXTENDED_HEADER ))\n\t{\n\t\tconst did3v2_extended_header_t *ext_header = (const did3v2_extended_header_t *)buffer;\n\t\tuint32_t ext_length = Sound_ParseSynchInteger( ext_header->length );\n\n\t\tif( ext_length > tag_length )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: invalid extended header length %u, possibly broken file.\\n\", __func__, ext_length );\n\t\t\treturn false;\n\t\t}\n\n\t\tbuffer += ext_length;\n\t}\n\n\twhile( buffer - buffer_begin < tag_length )\n\t{\n\t\tconst did3v2_frame_t *frame = (const did3v2_frame_t *)buffer;\n\t\tuint32_t frame_length = Sound_ParseSynchInteger( frame->length );\n\n\t\tif( frame_length > tag_length )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: invalid frame length %u, possibly broken file.\\n\", __func__, frame_length );\n\t\t\treturn false;\n\t\t}\n\n\t\tbuffer += sizeof( *frame );\n\n\t\t// parse can fail, but it's ok to continue\n\t\tSound_ParseID3Frame( frame, buffer, frame_length );\n\n\t\tbuffer += frame_length;\n\t}\n\n\treturn true;\n}\n\n#if XASH_ENGINE_TESTS\nint EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size );\nint EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size )\n{\n\tmemset( &sound, 0, sizeof( sound ));\n\tSound_ParseID3Tag( Data, Size );\n\treturn 0;\n}\n#endif\n\n/*\n=================================================================\n\n\tMPEG decompression\n\n=================================================================\n*/\nqboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tvoid\t*mpeg;\n\tsize_t\tpos = 0;\n\tsize_t\tbytesWrite = 0;\n\tbyte\tout[OUTBUF_SIZE];\n\tsize_t\toutsize, padsize;\n\tint\tret;\n\twavinfo_t\tsc;\n\n\t// load the file\n\tif( !buffer || filesize < FRAME_SIZE )\n\t\treturn false;\n\n\t// couldn't create decoder\n\tif(( mpeg = create_decoder( &ret )) == NULL )\n\t\treturn false;\n\n\tif( ret ) Con_DPrintf( S_ERROR \"%s\\n\", get_error( mpeg ));\n\n\t// trying to read header\n\tif( !feed_mpeg_header( mpeg, buffer, FRAME_SIZE, filesize, &sc ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, name, get_error( mpeg ));\n\t\tclose_decoder( mpeg );\n\t\treturn false;\n\t}\n\n\tsound.channels = sc.channels;\n\tsound.rate = sc.rate;\n\tsound.width = 2; // always 16-bit PCM\n\tsound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes\n\tpadsize = sound.size % FRAME_SIZE;\n\tpos += FRAME_SIZE; // evaluate pos\n\n\tif( !Sound_ParseID3Tag( buffer, filesize ))\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: (%s) failed to extract LOOP_START tag\\n\", __func__, name );\n\t}\n\n\tif( !sound.size )\n\t{\n\t\t// bad mpeg file ?\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) is probably corrupted\\n\", __func__, name );\n\t\tclose_decoder( mpeg );\n\t\treturn false;\n\t}\n\n\t// add sentinel make sure we not overrun\n\tsound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size + padsize );\n\tsound.type = WF_PCMDATA;\n\n\t// decompress mpg into pcm wav format\n\twhile( bytesWrite < sound.size )\n\t{\n\t\tint\tsize;\n\n\t\tif( feed_mpeg_stream( mpeg, NULL, 0, out, &outsize ) != MP3_OK && outsize <= 0 )\n\t\t{\n\t\t\tconst byte *data = buffer + pos;\n\t\t\tint\tbufsize;\n\n\t\t\t// if there are no bytes remainig so we can decompress the new frame\n\t\t\tif( pos + FRAME_SIZE > filesize )\n\t\t\t\tbufsize = ( filesize - pos );\n\t\t\telse bufsize = FRAME_SIZE;\n\t\t\tpos += bufsize;\n\n\t\t\tif( feed_mpeg_stream( mpeg, data, bufsize, out, &outsize ) != MP3_OK )\n\t\t\t\tbreak; // there was end of the stream\n\t\t}\n\n\t\tif( bytesWrite + outsize > sound.size )\n\t\t\tsize = ( sound.size - bytesWrite );\n\t\telse size = outsize;\n\n\t\tmemcpy( &sound.wav[bytesWrite], out, size );\n\t\tbytesWrite += size;\n\t}\n\n\tsound.samples = bytesWrite / ( sound.width * sound.channels );\n\tclose_decoder( mpeg );\n\n\treturn true;\n}\n\nstatic fs_offset_t FS_SeekMpg( void *file, fs_offset_t offset, int whence )\n{\n\treturn g_fsapi.Seek((file_t *)file, offset, whence ) == -1 ? -1 : g_fsapi.Tell((file_t *)file );\n}\n\nstatic mpg_ssize_t FS_ReadMpg( void *file, void *buf, size_t count )\n{\n\treturn g_fsapi.Read((file_t *)file, buf, count );\n}\n\n/*\n=================\nStream_OpenMPG\n=================\n*/\nstream_t *Stream_OpenMPG( const char *filename )\n{\n\tstream_t\t*stream;\n\tvoid\t*mpeg;\n\tfile_t\t*file;\n\tint\tret;\n\twavinfo_t\tsc;\n\n\tfile = FS_Open( filename, \"rb\", false );\n\tif( !file ) return NULL;\n\n\t// at this point we have valid stream\n\tstream = Mem_Calloc( host.soundpool, sizeof( stream_t ));\n\tstream->file = file;\n\tstream->pos = 0;\n\n\t// couldn't create decoder\n\tif(( mpeg = create_decoder( &ret )) == NULL )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: couldn't create decoder: %s\\n\", __func__, get_error( mpeg ) );\n\t\tMem_Free( stream );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tif( ret ) Con_DPrintf( S_ERROR \"%s\\n\", get_error( mpeg ));\n\n\t// trying to open stream and read header\n\tif( !open_mpeg_stream( mpeg, file, FS_ReadMpg, FS_SeekMpg, &sc ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, filename, get_error( mpeg ));\n\t\tclose_decoder( mpeg );\n\t\tMem_Free( stream );\n\t\tFS_Close( file );\n\n\t\treturn NULL;\n\t}\n\n\tstream->buffsize = 0; // how many samples left from previous frame\n\tstream->channels = sc.channels;\n\tstream->rate = sc.rate;\n\tstream->width = 2;\t// always 16 bit\n\tstream->ptr = mpeg;\n\tstream->type = WF_MPGDATA;\n\n\treturn stream;\n}\n\n/*\n=================\nStream_ReadMPG\n\nassume stream is valid\n=================\n*/\nint Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer )\n{\n\t// buffer handling\n\tint\tbytesWritten = 0;\n\tvoid\t*mpg;\n\n\tmpg = stream->ptr;\n\n\twhile( 1 )\n\t{\n\t\tbyte\t*data;\n\t\tint\toutsize;\n\n\t\tif( !stream->buffsize )\n\t\t{\n\t\t\tif( read_mpeg_stream( mpg, (byte*)stream->temp, &stream->pos ) != MP3_OK )\n\t\t\t\tbreak; // there was end of the stream\n\t\t}\n\n\t\t// check remaining size\n\t\tif( bytesWritten + stream->pos > needBytes )\n\t\t\toutsize = ( needBytes - bytesWritten );\n\t\telse outsize = stream->pos;\n\n\t\t// copy raw sample to output buffer\n\t\tdata = (byte *)buffer + bytesWritten;\n\t\tmemcpy( data, &stream->temp[stream->buffsize], outsize );\n\t\tbytesWritten += outsize;\n\t\tstream->pos -= outsize;\n\t\tstream->buffsize += outsize;\n\n\t\t// continue from this sample on a next call\n\t\tif( bytesWritten >= needBytes )\n\t\t\treturn bytesWritten;\n\n\t\tstream->buffsize = 0; // no bytes remaining\n\t}\n\n\treturn 0;\n}\n\n/*\n=================\nStream_SetPosMPG\n\nassume stream is valid\n=================\n*/\nint Stream_SetPosMPG( stream_t *stream, int newpos )\n{\n\tif( set_stream_pos( stream->ptr, newpos ) != -1 )\n\t{\n\t\t// flush any previous data\n\t\tstream->buffsize = 0;\n\t\treturn true;\n\t}\n\n\t// failed to seek for some reasons\n\treturn false;\n}\n\n/*\n=================\nStream_GetPosMPG\n\nassume stream is valid\n=================\n*/\nint Stream_GetPosMPG( stream_t *stream )\n{\n\treturn get_stream_pos( stream->ptr );\n}\n\n/*\n=================\nStream_FreeMPG\n\nassume stream is valid\n=================\n*/\nvoid Stream_FreeMPG( stream_t *stream )\n{\n\tif( stream->ptr )\n\t{\n\t\tclose_decoder( stream->ptr );\n\t\tstream->ptr = NULL;\n\t}\n\n\tif( stream->file )\n\t{\n\t\tFS_Close( stream->file );\n\t\tstream->file = NULL;\n\t}\n\n\tMem_Free( stream );\n}\n"
  },
  {
    "path": "engine/client/soundlib/snd_ogg_opus.c",
    "content": "/*\nsnd_ogg_opus.c - loading and streaming of Ogg audio format with Opus codec\nCopyright (C) 2024 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <opusfile.h>\n#include <string.h>\n#include \"soundlib.h\"\n#include \"crtlib.h\"\n#include \"ogg_filestream.h\"\n\ntypedef struct opus_streaming_ctx_s\n{\n\tfile_t      *file;\n\tOggOpusFile *of;\n} opus_streaming_ctx_t;\n\nstatic int OpusCallback_Read( void *datasource, byte *ptr, int nbytes )\n{\n\treturn OggFilestream_Read( ptr, 1, nbytes, datasource );\n}\n\nstatic opus_int64 OpusCallback_Tell( void *datasource )\n{\n\treturn OggFilestream_Tell( datasource );\n}\n\nstatic int FS_ReadOggOpus( void *datasource, byte *ptr, int nbytes )\n{\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Read( ctx->file, ptr, nbytes );\n}\n\nstatic int FS_SeekOggOpus( void *datasource, int64_t offset, int whence )\n{\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Seek( ctx->file, offset, whence );\n}\n\nstatic opus_int64 FS_TellOggOpus( void *datasource )\n{\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Tell( ctx->file );\n}\n\nstatic const OpusFileCallbacks op_callbacks_membuf = {\n\tOpusCallback_Read,\n\tOggFilestream_Seek,\n\tOpusCallback_Tell,\n\tNULL\n};\n\nstatic const OpusFileCallbacks op_callbacks_fs = {\n\tFS_ReadOggOpus,\n\tFS_SeekOggOpus,\n\tFS_TellOggOpus,\n\tNULL\n};\n\n/*\n=================================================================\n\n\tOgg Opus decompression & streaming\n\n=================================================================\n*/\nstatic void Sound_ScanOpusComments( const OggOpusFile *of )\n{\n\tconst char     *value;\n\tconst OpusTags *tags = op_tags( of, -1 );\n\tif( tags )\n\t{\n\t\tif(( value = opus_tags_query( tags, \"LOOPSTART\", 0 )))\n\t\t{\n\t\t\tsound.loopstart = Q_atoi( value );\n\t\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t\t}\n\t\telse if(( value = opus_tags_query( tags, \"LOOP_START\", 0 )))\n\t\t{\n\t\t\tsound.loopstart = Q_atoi( value );\n\t\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t\t}\n\t}\n}\n\nstatic const char *Opus_GetErrorDesc( int errorCode )\n{\n\tswitch( errorCode )\n\t{\n\tcase OP_FALSE:\n\t\treturn \"request failed\";\n\tcase OP_EOF:\n\t\treturn \"end of file reached\";\n\tcase OP_HOLE:\n\t\treturn \"there was a hole in the stream\";\n\tcase OP_EREAD:\n\t\treturn \"read error occurred\";\n\tcase OP_EFAULT:\n\t\treturn \"fault issue occurred\";\n\tcase OP_EIMPL:\n\t\treturn \"feature not implemented\";\n\tcase OP_EINVAL:\n\t\treturn \"invalid argument\";\n\tcase OP_ENOTFORMAT:\n\t\treturn \"not a valid file format\";\n\tcase OP_EBADHEADER:\n\t\treturn \"bad header\";\n\tcase OP_EVERSION:\n\t\treturn \"version mismatch\";\n\tcase OP_EBADPACKET:\n\t\treturn \"bad packet\";\n\tcase OP_EBADLINK:\n\t\treturn \"bad link found\";\n\tcase OP_ENOSEEK:\n\t\treturn \"bitstream not seekable\";\n\tcase OP_EBADTIMESTAMP:\n\t\treturn \"invalid timestamp\";\n\tdefault:\n\t\treturn \"unknown error\";\n\t}\n}\n\nqboolean Sound_LoadOggOpus( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tint ret;\n\togg_filestream_t file;\n\tOggOpusFile      *of;\n\tconst OpusHead   *opusHead;\n\tsize_t written = 0;\n\n\tif( !buffer )\n\t\treturn false;\n\n\tOggFilestream_Init( &file, name, buffer, filesize );\n\tof = op_open_callbacks( &file, &op_callbacks_membuf, NULL, 0, &ret );\n\tif( !of )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, file.name, Opus_GetErrorDesc( ret ));\n\t\treturn false;\n\t}\n\n\topusHead = op_head( of, -1 );\n\tif( opusHead->channel_count < 1 || opusHead->channel_count > 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): unsuppored channels count\\n\", __func__, file.name );\n\t\treturn false;\n\t}\n\n\t// according to OggOpus specification, sound always encoded at 48kHz sample rate\n\t// but this isn't a problem, engine can do resampling\n\tsound.channels = opusHead->channel_count;\n\tsound.rate = 48000;\n\tsound.width = 2; // always 16-bit PCM\n\tsound.type = WF_PCMDATA;\n\tsound.samples = op_pcm_total( of, -1 );\n\tsound.size = sound.samples * sound.width * sound.channels;\n\tsound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size );\n\n\t// skip undesired samples before playing sound\n\tif(( ret = op_pcm_seek( of, opusHead->pre_skip )) < 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to pre-skip (%s): %s\\n\", __func__, file.name, Opus_GetErrorDesc( ret ));\n\t\treturn false;\n\t}\n\n\tSetBits( sound.flags, SOUND_RESAMPLE );\n\tSound_ScanOpusComments( of );\n\n\twhile(( ret = op_read( of, (opus_int16 *)( sound.wav + written ), ( sound.size - written ) / sound.width, NULL )) != 0 )\n\t{\n\t\tif( ret < 0 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: failed to read (%s): %s\\n\", __func__, file.name, Opus_GetErrorDesc( ret ));\n\t\t\treturn false;\n\t\t}\n\t\twritten += ret * sound.width * sound.channels;\n\t}\n\n\top_free( of );\n\treturn true;\n}\n\nstream_t *Stream_OpenOggOpus( const char *filename )\n{\n\tint ret;\n\tstream_t *stream;\n\topus_streaming_ctx_t *ctx;\n\tconst OpusHead       *opusHead;\n\n\tctx = (opus_streaming_ctx_t *)Mem_Calloc( host.soundpool, sizeof( opus_streaming_ctx_t ));\n\tctx->file = FS_Open( filename, \"rb\", false );\n\tif( !ctx->file )\n\t{\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\tstream = (stream_t *)Mem_Calloc( host.soundpool, sizeof( stream_t ));\n\tstream->file = ctx->file;\n\tstream->pos = 0;\n\n\tctx->of = op_open_callbacks( ctx, &op_callbacks_fs, NULL, 0, &ret );\n\tif( !ctx->of )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, filename, Opus_GetErrorDesc( ret ));\n\t\tFS_Close( ctx->file );\n\t\tMem_Free( stream );\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\topusHead = op_head( ctx->of, -1 );\n\tif( opusHead->channel_count < 1 || opusHead->channel_count > 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): unsuppored channels count\\n\", __func__, filename );\n\t\top_free( ctx->of );\n\t\tFS_Close( ctx->file );\n\t\tMem_Free( stream );\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\t// skip undesired samples before playing sound\n\tif(( ret = op_pcm_seek( ctx->of, opusHead->pre_skip )) < 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to pre-skip (%s): %s\\n\", __func__, filename, Opus_GetErrorDesc( ret ));\n\t\top_free( ctx->of );\n\t\tFS_Close( ctx->file );\n\t\tMem_Free( stream );\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\tstream->buffsize = 0; // how many samples left from previous frame\n\tstream->channels = opusHead->channel_count;\n\tstream->rate = 48000; // that's fixed at 48kHz for Opus format\n\tstream->width = 2;    // always 16 bit\n\tstream->ptr = ctx;\n\tstream->type = WF_OPUSDATA;\n\n\treturn stream;\n}\n\nint Stream_ReadOggOpus( stream_t *stream, int needBytes, void *buffer )\n{\n\tint bytesWritten = 0;\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr;\n\n\twhile( 1 )\n\t{\n\t\tint  ret;\n\t\tbyte *data;\n\t\tint  outsize;\n\n\t\tif( !stream->buffsize )\n\t\t{\n\t\t\tret = op_read( ctx->of, (opus_int16 *)stream->temp, OUTBUF_SIZE / stream->width, NULL );\n\t\t\tif( ret == 0 )\n\t\t\t\tbreak; // end of file\n\t\t\telse if( ret < 0 )\n\t\t\t\tCon_DPrintf( S_ERROR \"%s: error during read: %s\\n\", __func__, Opus_GetErrorDesc( ret ));\n\t\t\telse\n\t\t\t\tstream->pos = ret * stream->width * stream->channels;\n\t\t}\n\n\t\t// check remaining size\n\t\tif( bytesWritten + stream->pos > needBytes )\n\t\t\toutsize = ( needBytes - bytesWritten );\n\t\telse\n\t\t\toutsize = stream->pos;\n\n\t\t// copy raw sample to output buffer\n\t\tdata = (byte *)buffer + bytesWritten;\n\t\tmemcpy( data, &stream->temp[stream->buffsize], outsize );\n\t\tbytesWritten += outsize;\n\t\tstream->pos -= outsize;\n\t\tstream->buffsize += outsize;\n\n\t\t// continue from this sample on a next call\n\t\tif( bytesWritten >= needBytes )\n\t\t\treturn bytesWritten;\n\n\t\tstream->buffsize = 0; // no bytes remaining\n\t}\n\n\treturn 0;\n}\n\nint Stream_SetPosOggOpus( stream_t *stream, int newpos )\n{\n\tint ret;\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr;\n\tif(( ret = op_raw_seek( ctx->of, newpos )) == 0 )\n\t{\n\t\tstream->buffsize = 0; // flush any previous data\n\t\treturn true;\n\t}\n\tCon_DPrintf( S_ERROR \"%s: error during seek: %s\\n\", __func__, Opus_GetErrorDesc( ret ));\n\treturn false; // failed to seek\n}\n\nint Stream_GetPosOggOpus( stream_t *stream )\n{\n\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr;\n\treturn op_raw_tell( ctx->of );\n}\n\nvoid Stream_FreeOggOpus( stream_t *stream )\n{\n\tif( stream->ptr )\n\t{\n\t\topus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr;\n\t\top_free( ctx->of );\n\t\tMem_Free( stream->ptr );\n\t\tstream->ptr = NULL;\n\t}\n\n\tif( stream->file )\n\t{\n\t\tFS_Close( stream->file );\n\t\tstream->file = NULL;\n\t}\n\n\tMem_Free( stream );\n}\n"
  },
  {
    "path": "engine/client/soundlib/snd_ogg_vorbis.c",
    "content": "/*\nsnd_ogg_vorbis.c - loading and streaming of Ogg audio format with Vorbis codec\nCopyright (C) 2024 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <string.h>\n#include <vorbis/codec.h>\n#include <vorbis/vorbisfile.h>\n#include \"soundlib.h\"\n#include \"crtlib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ogg_filestream.h\"\n\ntypedef struct vorbis_streaming_ctx_s\n{\n\tfile_t *file;\n\tOggVorbis_File vf;\n} vorbis_streaming_ctx_t;\n\nstatic size_t FS_ReadOggVorbis( void *ptr, size_t blockSize, size_t nmemb, void *datasource )\n{\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Read( ctx->file, ptr, blockSize * nmemb );\n}\n\nstatic int FS_SeekOggVorbis( void *datasource, int64_t offset, int whence )\n{\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Seek( ctx->file, offset, whence );\n}\n\nstatic long FS_TellOggVorbis( void *datasource )\n{\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource;\n\treturn g_fsapi.Tell( ctx->file );\n}\n\nstatic const ov_callbacks ov_callbacks_membuf = {\n\tOggFilestream_Read,\n\tOggFilestream_Seek,\n\tNULL,\n\tOggFilestream_Tell\n};\n\nstatic const ov_callbacks ov_callbacks_fs = {\n\tFS_ReadOggVorbis,\n\tFS_SeekOggVorbis,\n\tNULL,\n\tFS_TellOggVorbis\n};\n\n/*\n=================================================================\n\n\tOgg Vorbis decompression & streaming\n\n=================================================================\n*/\nstatic const char *Vorbis_GetErrorDesc( int errorCode )\n{\n\tswitch( errorCode )\n\t{\n\tcase OV_EOF:\n\t\treturn \"end of file\";\n\tcase OV_HOLE:\n\t\treturn \"compressed data sync lost\";\n\tcase OV_EBADHEADER:\n\t\treturn \"invalid header\";\n\tcase OV_EINVAL:\n\t\treturn \"invalid argument\";\n\tcase OV_ENOTVORBIS:\n\t\treturn \"not a Vorbis data\";\n\tcase OV_EBADLINK:\n\t\treturn \"link corrupted\";\n\tcase OV_EFAULT:\n\t\treturn \"internal error\";\n\tcase OV_EIMPL:\n\t\treturn \"not implemented\";\n\tcase OV_EBADPACKET:\n\t\treturn \"invalid packet\";\n\tcase OV_EVERSION:\n\t\treturn \"version mismatch\";\n\tcase OV_ENOSEEK:\n\t\treturn \"bitstream not seekable\";\n\tcase OV_ENOTAUDIO:\n\t\treturn \"not an audio data\";\n\tcase OV_EREAD:\n\t\treturn \"read error\";\n\tdefault:\n\t\treturn \"unknown error\";\n\t}\n}\n\nstatic void Sound_ScanVorbisComments( OggVorbis_File *vf )\n{\n\tconst char     *value;\n\tvorbis_comment *vc = ov_comment( vf, -1 );\n\tif( vc )\n\t{\n\t\tif(( value = vorbis_comment_query( vc, \"LOOPSTART\", 0 )))\n\t\t{\n\t\t\tsound.loopstart = Q_atoi( value );\n\t\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t\t}\n\t\telse if(( value = vorbis_comment_query( vc, \"LOOP_START\", 0 )))\n\t\t{\n\t\t\tsound.loopstart = Q_atoi( value );\n\t\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t\t}\n\t}\n}\n\nqboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tlong   ret;\n\tint    section;\n\tsize_t written = 0;\n\tvorbis_info      *info;\n\togg_filestream_t file;\n\tOggVorbis_File   vorbisFile;\n\n\tif( !buffer )\n\t\treturn false;\n\n\tOggFilestream_Init( &file, name, buffer, filesize );\n\tif(( ret = ov_open_callbacks( &file, &vorbisFile, NULL, 0, ov_callbacks_membuf )) < 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, file.name, Vorbis_GetErrorDesc( ret ));\n\t\treturn false;\n\t}\n\n\tinfo = ov_info( &vorbisFile, -1 );\n\tif( info->channels < 1 || info->channels > 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): unsuppored channels count\\n\", __func__, file.name );\n\t\treturn false;\n\t}\n\n\tsound.channels = info->channels;\n\tsound.rate = info->rate;\n\tsound.width = 2; // always 16-bit PCM\n\tsound.type = WF_PCMDATA;\n\tsound.samples = ov_pcm_total( &vorbisFile, -1 );\n\tsound.size = sound.samples * sound.width * sound.channels;\n\tsound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size );\n\n\tSetBits( sound.flags, SOUND_RESAMPLE );\n\tSound_ScanVorbisComments( &vorbisFile );\n\n\twhile(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, 0, sound.width, 1, &section )) != 0 )\n\t{\n\t\tif( ret < 0 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, file.name, Vorbis_GetErrorDesc( ret ));\n\t\t\treturn false;\n\t\t}\n\t\twritten += ret;\n\t}\n\n\tov_clear( &vorbisFile );\n\treturn true;\n}\n\nstream_t *Stream_OpenOggVorbis( const char *filename )\n{\n\tint ret;\n\tstream_t    *stream;\n\tvorbis_info *info;\n\tvorbis_streaming_ctx_t *ctx;\n\n\tctx = (vorbis_streaming_ctx_t *)Mem_Calloc( host.soundpool, sizeof( vorbis_streaming_ctx_t ));\n\tctx->file = FS_Open( filename, \"rb\", false );\n\tif( !ctx->file )\n\t{\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\tstream = (stream_t *)Mem_Calloc( host.soundpool, sizeof( stream_t ));\n\tstream->file = ctx->file;\n\tstream->pos = 0;\n\n\tif(( ret = ov_open_callbacks( ctx, &ctx->vf, NULL, 0, ov_callbacks_fs )) < 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): %s\\n\", __func__, filename, Vorbis_GetErrorDesc( ret ));\n\t\tFS_Close( ctx->file );\n\t\tMem_Free( stream );\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\tinfo = ov_info( &ctx->vf, -1 );\n\tif( info->channels < 1 || info->channels > 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: failed to load (%s): unsuppored channels count\\n\", __func__, filename );\n\t\tFS_Close( ctx->file );\n\t\tMem_Free( stream );\n\t\tMem_Free( ctx );\n\t\treturn NULL;\n\t}\n\n\tstream->buffsize = 0; // how many samples left from previous frame\n\tstream->channels = info->channels;\n\tstream->rate = info->rate;\n\tstream->width = 2; // always 16 bit\n\tstream->ptr = ctx;\n\tstream->type = WF_VORBISDATA;\n\n\treturn stream;\n}\n\nint Stream_ReadOggVorbis( stream_t *stream, int needBytes, void *buffer )\n{\n\tint section;\n\tint bytesWritten = 0;\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr;\n\n\twhile( 1 )\n\t{\n\t\tbyte *data;\n\t\tint  outsize;\n\n\t\tif( !stream->buffsize )\n\t\t{\n\t\t\tstream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, 0, stream->width, 1, &section );\n\t\t\tif( stream->pos == 0 )\n\t\t\t{\n\t\t\t\tbreak; // end of file\n\t\t\t}\n\t\t\telse if( stream->pos < 0 )\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_ERROR \"%s: error during read: %s\\n\", __func__, Vorbis_GetErrorDesc( stream->pos ));\n\t\t\t}\n\t\t}\n\n\t\t// check remaining size\n\t\tif( bytesWritten + stream->pos > needBytes )\n\t\t\toutsize = ( needBytes - bytesWritten );\n\t\telse\n\t\t\toutsize = stream->pos;\n\n\t\t// copy raw sample to output buffer\n\t\tdata = (byte *)buffer + bytesWritten;\n\t\tmemcpy( data, &stream->temp[stream->buffsize], outsize );\n\t\tbytesWritten += outsize;\n\t\tstream->pos -= outsize;\n\t\tstream->buffsize += outsize;\n\n\t\t// continue from this sample on a next call\n\t\tif( bytesWritten >= needBytes )\n\t\t\treturn bytesWritten;\n\n\t\tstream->buffsize = 0; // no bytes remaining\n\t}\n\n\treturn 0;\n}\n\nint Stream_SetPosOggVorbis( stream_t *stream, int newpos )\n{\n\tint ret;\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr;\n\tif(( ret = ov_raw_seek_lap( &ctx->vf, newpos )) == 0 )\n\t{\n\t\tstream->buffsize = 0; // flush any previous data\n\t\treturn true;\n\t}\n\tCon_DPrintf( S_ERROR \"%s: error during seek: %s\\n\", __func__, Vorbis_GetErrorDesc( ret ));\n\treturn false; // failed to seek\n}\n\nint Stream_GetPosOggVorbis( stream_t *stream )\n{\n\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr;\n\treturn ov_raw_tell( &ctx->vf );\n}\n\nvoid Stream_FreeOggVorbis( stream_t *stream )\n{\n\tif( stream->ptr )\n\t{\n\t\tvorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr;\n\t\tov_clear( &ctx->vf );\n\t\tMem_Free( stream->ptr );\n\t\tstream->ptr = NULL;\n\t}\n\n\tif( stream->file )\n\t{\n\t\tFS_Close( stream->file );\n\t\tstream->file = NULL;\n\t}\n\n\tMem_Free( stream );\n}\n"
  },
  {
    "path": "engine/client/soundlib/snd_wav.c",
    "content": "/*\nsnd_wav.c - wav format load & save\nCopyright (C) 2010 Uncle Mike\nCopyright (C) 2023 FTEQW developers\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stddef.h>\n#include \"soundlib.h\"\n\nstatic const byte *iff_data;\nstatic const byte *iff_dataPtr;\nstatic const byte *iff_end;\nstatic const byte *iff_lastChunk;\nstatic int iff_chunkLen;\n\nstatic int IsFourCC( const void *ptr, const void *fourcc )\n{\n\treturn 0 == memcmp( ptr, fourcc, 4 );\n}\n\n/*\n=================\nGetLittleShort\n=================\n*/\nstatic short GetLittleShort( void )\n{\n\tshort\tval = 0;\n\n\tval += (*(iff_dataPtr+0) << 0);\n\tval += (*(iff_dataPtr+1) << 8);\n\tiff_dataPtr += 2;\n\n\treturn val;\n}\n\n/*\n=================\nGetLittleLong\n=================\n*/\nstatic int GetLittleLong( void )\n{\n\tint\tval = 0;\n\n\tval += (*(iff_dataPtr+0) << 0);\n\tval += (*(iff_dataPtr+1) << 8);\n\tval += (*(iff_dataPtr+2) <<16);\n\tval += (*(iff_dataPtr+3) <<24);\n\tiff_dataPtr += 4;\n\n\treturn val;\n}\n\n/*\n=================\nFindNextChunk\n=================\n*/\nstatic void FindNextChunk( const char *filename, const char *name )\n{\n\twhile( 1 )\n\t{\n\t\tptrdiff_t remaining = iff_end - iff_lastChunk;\n\n\t\tif( remaining < 8 )\n\t\t{\n\t\t\tiff_dataPtr = NULL;\n\t\t\treturn;\n\t\t}\n\n\t\tiff_dataPtr = iff_lastChunk + 4;\n\t\tremaining -= 8;\n\n\t\tiff_chunkLen = GetLittleLong();\n\t\tif( iff_chunkLen < 0 )\n\t\t{\n\t\t\tiff_dataPtr = NULL;\n\t\t\treturn;\n\t\t}\n\n\t\tif( iff_chunkLen > remaining )\n\t\t{\n\t\t\t// only print this warning if selected chunk is truncated\n\t\t\t//\n\t\t\t// otherwise this warning becomes misleading because some\n\t\t\t// idiot programs like CoolEdit (i.e. Adobe Audition) don't always\n\t\t\t// respect pad byte. The file isn't actually truncated, it just\n\t\t\t// can't be reliably parsed as a whole\n\n\t\t\tif( IsFourCC( iff_lastChunk, \"RIFF\" )\n\t\t\t\t|| IsFourCC( iff_lastChunk, \"fmt \" )\n\t\t\t\t|| IsFourCC( iff_lastChunk, \"cue \" )\n\t\t\t\t|| IsFourCC( iff_lastChunk, \"LIST\" )\n\t\t\t\t|| IsFourCC( iff_lastChunk, \"data\" ))\n\t\t\t{\n\t\t\t\tCon_DPrintf( \"%s: '%s' truncated by %zi bytes\\n\", __func__, filename, iff_chunkLen - remaining );\n\t\t\t}\n\t\t\tiff_chunkLen = remaining;\n\t\t}\n\n\t\tremaining -= iff_chunkLen;\n\t\tiff_dataPtr -= 8;\n\n\t\tiff_lastChunk = iff_dataPtr + 8 + iff_chunkLen;\n\t\tif(( iff_chunkLen & 1 ) && remaining )\n\t\t\tiff_lastChunk++;\n\t\tif( IsFourCC( iff_dataPtr, name ))\n\t\t\treturn;\n\t}\n}\n\n/*\n=================\nFindChunk\n=================\n*/\nstatic void FindChunk( const char *filename, const char *name )\n{\n\tiff_lastChunk = iff_data;\n\tFindNextChunk( filename, name );\n}\n\n/*\n============\nStreamFindNextChunk\n============\n*/\nstatic qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_chunk )\n{\n\tchar\tchunkName[4];\n\tint\tiff_chunk_len;\n\n\twhile( 1 )\n\t{\n\t\tFS_Seek( file, *last_chunk, SEEK_SET );\n\n\t\tif( FS_Eof( file ))\n\t\t\treturn false;\t// didn't find the chunk\n\n\t\tFS_Seek( file, 4, SEEK_CUR );\n\t\tif( FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )) != sizeof( iff_chunk_len ))\n\t\t\treturn false;\n\n\t\tif( iff_chunk_len < 0 )\n\t\t\treturn false;\t// didn't find the chunk\n\n\t\tFS_Seek( file, -8, SEEK_CUR );\n\t\t*last_chunk = FS_Tell( file ) + 8 + (( iff_chunk_len + 1 ) & ~1 );\n\t\tif( FS_Read( file, chunkName, sizeof( chunkName )) != sizeof( chunkName ))\n\t\t\treturn false;\n\n\t\tif( IsFourCC( chunkName, name ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n=============\nSound_LoadWAV\n=============\n*/\nqboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tint\tsamples, fmt;\n\tqboolean\tmpeg_stream = false;\n\n\tif( !buffer || filesize <= 0 )\n\t\treturn false;\n\n\tiff_data = buffer;\n\tiff_end = buffer + filesize;\n\n\t// find \"RIFF\" chunk\n\tFindChunk( name, \"RIFF\" );\n\n\tif( !iff_dataPtr || !IsFourCC( iff_dataPtr + 8, \"WAVE\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'RIFF/WAVE' chunks\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// get \"fmt \" chunk\n\tiff_data = iff_dataPtr + 12;\n\tFindChunk( name, \"fmt \" );\n\n\tif( !iff_dataPtr )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'fmt ' chunk\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tiff_dataPtr += 8;\n\tfmt = GetLittleShort();\n\n\tif( fmt != 1 )\n\t{\n\t\tif( fmt != 85 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: %s not a microsoft PCM format\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// mpeg stream in wav container\n\t\t\tmpeg_stream = true;\n\t\t}\n\t}\n\n\tsound.channels = GetLittleShort();\n\tif( sound.channels != 1 && sound.channels != 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: only mono and stereo WAV files supported (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tsound.rate = GetLittleLong();\n\tiff_dataPtr += 6;\n\n\tsound.width = GetLittleShort() / 8;\n\tif( mpeg_stream ) sound.width = 2; // mp3 always 16bit\n\n\tif( sound.width != 1 && sound.width != 2 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: only 8 and 16 bit WAV files supported (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// get cue chunk\n\tFindChunk( name, \"cue \" );\n\n\tif( iff_dataPtr && iff_end - iff_dataPtr >= 36 )\n\t{\n\t\tiff_dataPtr += 32;\n\t\tsound.loopstart = GetLittleLong();\n\t\tSetBits( sound.flags, SOUND_LOOPED );\n\t\tFindNextChunk( name, \"LIST\" ); // if the next chunk is a LIST chunk, look for a cue length marker\n\n\t\tif( iff_dataPtr && iff_end - iff_dataPtr >= 32 )\n\t\t{\n\t\t\tif( IsFourCC( iff_dataPtr + 28, \"mark\" ))\n\t\t\t{\n\t\t\t\t// this is not a proper parse, but it works with CoolEdit...\n\t\t\t\tiff_dataPtr += 24;\n\t\t\t\tsound.samples = sound.loopstart + GetLittleLong(); // samples in loop\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tsound.loopstart = 0;\n\t\tsound.samples = 0;\n\t}\n\n\t// find data chunk\n\tFindChunk( name, \"data\" );\n\n\tif( !iff_dataPtr )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'data' chunk\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tiff_dataPtr += 4;\n\tsamples = GetLittleLong() / sound.width;\n\n\tif( sound.samples )\n\t{\n\t\tif( samples < sound.samples )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: %s has a bad loop length\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t}\n\telse sound.samples = samples;\n\n\tif( sound.samples <= 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: file with %i samples (%s)\\n\", __func__, sound.samples, name );\n\t\treturn false;\n\t}\n\n\tsound.type = WF_PCMDATA;\n\tsound.samples /= sound.channels;\n\n\t// g-cont. get support for mp3 streams packed in wav container\n\t// e.g. CAd menu sounds\n\tif( mpeg_stream )\n\t{\n\t\tint\thdr_size = (iff_dataPtr - buffer);\n\n\t\tif(( filesize - hdr_size ) < FRAME_SIZE )\n\t\t{\n\t\t\tsound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, FRAME_SIZE );\n\t\t\tmemcpy( sound.tempbuffer, buffer + (iff_dataPtr - buffer), filesize - hdr_size );\n\t\t\treturn Sound_LoadMPG( name, sound.tempbuffer, FRAME_SIZE );\n\t\t}\n\n\t\treturn Sound_LoadMPG( name, buffer + hdr_size, filesize - hdr_size );\n\t}\n\n\t// Load the data\n\tsound.size = sound.samples * sound.width * sound.channels;\n\tsound.wav = Mem_Malloc( host.soundpool, sound.size );\n\n\tmemcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size );\n\n\t// now convert 8-bit sounds to signed\n\tif( sound.width == 1 )\n\t{\n\t\tint\ti, j;\n\t\tsigned char\t*pData = (signed char *)sound.wav;\n\n\t\tfor( i = 0; i < sound.samples; i++ )\n\t\t{\n\t\t\tfor( j = 0; j < sound.channels; j++ )\n\t\t\t{\n\t\t\t\t*pData = (byte)((int)((byte)*pData) - 128 );\n\t\t\t\tpData++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nStream_OpenWAV\n=================\n*/\nstream_t *Stream_OpenWAV( const char *filename )\n{\n\tstream_t\t*stream;\n\tint \tlast_chunk = 0;\n\tchar\tchunkName[4];\n\tint\tiff_data;\n\tfile_t\t*file;\n\tshort\tt;\n\n\tif( !filename || !*filename )\n\t\treturn NULL;\n\n\t// open\n\tfile = FS_Open( filename, \"rb\", false );\n\tif( !file ) return NULL;\n\n\t// find \"RIFF\" chunk\n\tif( !StreamFindNextChunk( file, \"RIFF\", &last_chunk ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing RIFF chunk\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tFS_Seek( file, 4, SEEK_CUR );\n\n\tif( FS_Read( file, chunkName, 4 ) != 4 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing WAVE chunk, truncated\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn false;\n\t}\n\n\tif( !IsFourCC( chunkName, \"WAVE\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing WAVE chunk\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\t// get \"fmt \" chunk\n\tiff_data = FS_Tell( file );\n\tlast_chunk = iff_data;\n\tif( !StreamFindNextChunk( file, \"fmt \", &last_chunk ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'fmt ' chunk\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tFS_Read( file, chunkName, 4 );\n\n\tFS_Read( file, &t, sizeof( t ));\n\tif( t != 1 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s not a microsoft PCM format\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tFS_Read( file, &t, sizeof( t ));\n\tsound.channels = t;\n\n\tFS_Read( file, &sound.rate, sizeof( int ));\n\n\tFS_Seek( file, 6, SEEK_CUR );\n\n\tFS_Read( file, &t, sizeof( t ));\n\tsound.width = t / 8;\n\n\tsound.loopstart = 0;\n\n\t// find data chunk\n\tlast_chunk = iff_data;\n\tif( !StreamFindNextChunk( file, \"data\", &last_chunk ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'data' chunk\\n\", __func__, filename );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tFS_Read( file, &sound.samples, sizeof( int ));\n\tsound.samples = ( sound.samples / sound.width ) / sound.channels;\n\n\t// at this point we have valid stream\n\tstream = Mem_Calloc( host.soundpool, sizeof( stream_t ));\n\tstream->file = file;\n\tstream->size = sound.samples * sound.width * sound.channels;\n\tstream->buffsize = FS_Tell( file ); // header length\n\tstream->channels = sound.channels;\n\tstream->width = sound.width;\n\tstream->rate = sound.rate;\n\tstream->type = WF_PCMDATA;\n\n\treturn stream;\n}\n\n/*\n=================\nStream_ReadWAV\n\nassume stream is valid\n=================\n*/\nint Stream_ReadWAV( stream_t *stream, int bytes, void *buffer )\n{\n\tint\tremaining;\n\n\tif( !stream->file ) return 0;\t// invalid file\n\n\tremaining = stream->size - stream->pos;\n\tif( remaining <= 0 ) return 0;\n\tif( bytes > remaining ) bytes = remaining;\n\n\tstream->pos += bytes;\n\tFS_Read( stream->file, buffer, bytes );\n\n\treturn bytes;\n}\n\n/*\n=================\nStream_SetPosWAV\n\nassume stream is valid\n=================\n*/\nint Stream_SetPosWAV( stream_t *stream, int newpos )\n{\n\t// NOTE: stream->pos it's real file position without header size\n\tif( FS_Seek( stream->file, stream->buffsize + newpos, SEEK_SET ) != -1 )\n\t{\n\t\tstream->pos = newpos;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n=================\nStream_GetPosWAV\n\nassume stream is valid\n=================\n*/\nint Stream_GetPosWAV( stream_t *stream )\n{\n\treturn stream->pos;\n}\n\n/*\n=================\nStream_FreeWAV\n\nassume stream is valid\n=================\n*/\nvoid Stream_FreeWAV( stream_t *stream )\n{\n\tif( stream->file )\n\t\tFS_Close( stream->file );\n\tMem_Free( stream );\n}\n"
  },
  {
    "path": "engine/client/titles.c",
    "content": "/*\ntitles.c - implementation of titles.txt parser\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n\n#define MAX_MESSAGES\t2048\n\n#define MSGFILE_NAME\t0\n#define MSGFILE_TEXT\t1\n\nclient_textmessage_t\tgMessageParms;\n\n// the string \"pText\" is assumed to have all whitespace from both ends cut out\nstatic int IsComment( const char *pText )\n{\n\tif( pText )\n\t{\n\t\tint length = Q_strlen( pText );\n\n\t\tif( length >= 2 && pText[0] == '/' && pText[1] == '/' )\n\t\t\treturn 1;\n\n\t\t// no text?\n\t\tif( length > 0 )\n\t\t\treturn 0;\n\t}\n\n\t// no text is a comment too\n\treturn 1;\n}\n\n// the string \"pText\" is assumed to have all whitespace from both ends cut out\nstatic int IsStartOfText( const char *pText )\n{\n\tif( pText )\n\t{\n\t\tif( pText[0] == '{' )\n\t\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n// the string \"pText\" is assumed to have all whitespace from both ends cut out\nstatic int IsEndOfText( const char *pText )\n{\n\tif( pText )\n\t{\n\t\tif( pText[0] == '}' )\n\t\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int IsWhiteSpace( char space )\n{\n\tif( space == ' ' || space == '\\t' || space == '\\r' || space == '\\n' )\n\t\treturn 1;\n\treturn 0;\n}\n\nstatic const char *SkipSpace( const char *pText )\n{\n\tif( pText )\n\t{\n\t\tint pos = 0;\n\t\twhile( pText[pos] && IsWhiteSpace( pText[pos] ))\n\t\t\tpos++;\n\t\treturn pText + pos;\n\t}\n\treturn NULL;\n}\n\nstatic const char *SkipText( const char *pText )\n{\n\tif( pText )\n\t{\n\t\tint pos = 0;\n\t\twhile( pText[pos] && !IsWhiteSpace( pText[pos] ))\n\t\t\tpos++;\n\t\treturn pText + pos;\n\t}\n\treturn NULL;\n}\n\nstatic int ParseFloats( const char *pText, float *pFloat, int count )\n{\n\tconst char *pTemp = pText;\n\tint index = 0;\n\n\twhile( pTemp && count > 0 )\n\t{\n\t\t// skip current token / float\n\t\tpTemp = SkipText( pTemp );\n\t\t// skip any whitespace in between\n\t\tpTemp = SkipSpace( pTemp );\n\n\t\tif( pTemp )\n\t\t{\n\t\t\t// parse a float\n\t\t\tpFloat[index] = Q_atof( pTemp );\n\t\t\tcount--;\n\t\t\tindex++;\n\t\t}\n\t}\n\n\tif( count == 0 )\n\t\treturn 1;\n\treturn 0;\n}\n\nstatic int IsToken( const char *pText, const char *pTokenName )\n{\n\tif( !pText || !pTokenName )\n\t\treturn 0;\n\n\tif( !Q_strnicmp( pText+1, pTokenName, Q_strlen( pTokenName )))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic int ParseDirective( const char *pText )\n{\n\tif( pText && pText[0] == '$' )\n\t{\n\t\tfloat\ttempFloat[8];\n\n\t\tif( IsToken( pText, \"position\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 2 ))\n\t\t\t{\n\t\t\t\tgMessageParms.x = tempFloat[0];\n\t\t\t\tgMessageParms.y = tempFloat[1];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"effect\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 1 ))\n\t\t\t{\n\t\t\t\tgMessageParms.effect = (int)tempFloat[0];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"fxtime\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 1 ))\n\t\t\t{\n\t\t\t\tgMessageParms.fxtime = tempFloat[0];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"color2\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 3 ))\n\t\t\t{\n\t\t\t\tgMessageParms.r2 = (int)tempFloat[0];\n\t\t\t\tgMessageParms.g2 = (int)tempFloat[1];\n\t\t\t\tgMessageParms.b2 = (int)tempFloat[2];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"color\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 3 ))\n\t\t\t{\n\t\t\t\tgMessageParms.r1 = (int)tempFloat[0];\n\t\t\t\tgMessageParms.g1 = (int)tempFloat[1];\n\t\t\t\tgMessageParms.b1 = (int)tempFloat[2];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"fadein\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 1 ))\n\t\t\t{\n\t\t\t\tgMessageParms.fadein = tempFloat[0];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"fadeout\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 3 ))\n\t\t\t{\n\t\t\t\tgMessageParms.fadeout = tempFloat[0];\n\t\t\t}\n\t\t}\n\t\telse if( IsToken( pText, \"holdtime\" ))\n\t\t{\n\t\t\tif( ParseFloats( pText, tempFloat, 3 ))\n\t\t\t{\n\t\t\t\tgMessageParms.holdtime = tempFloat[0];\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"unknown token: %s\\n\", pText );\n\t\t}\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nvoid CL_TextMessageParse( byte *pMemFile, int fileSize )\n{\n\tchar\t\t\tbuf[512], trim[512], currentName[512];\n\tchar\t\t\t*pCurrentText = NULL, *pNameHeap;\n\tchar\t\t\tnameHeap[32768]; // g-cont. i will scale up heap to handle all TFC messages\n\tint\t\t\tmode = MSGFILE_NAME; // searching for a message name\n\tint\t\t\tlineNumber, filePos, lastLinePos;\n\tclient_textmessage_t\ttextMessages[MAX_MESSAGES];\n\tint\t\t\ti, nameHeapSize, textHeapSize, messageSize, nameOffset;\n\tint\t\t\tmessageCount, lastNamePos;\n\tsize_t\t\ttextHeapSizeRemaining;\n\n\tlastNamePos = 0;\n\tlineNumber = 0;\n\tfilePos = 0;\n\tlastLinePos = 0;\n\tmessageCount = 0;\n\n\twhile( COM_MemFgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL )\n\t{\n\t\tCOM_TrimSpace( buf, trim );\n\n\t\tswitch( mode )\n\t\t{\n\t\tcase MSGFILE_NAME:\n\t\t\t// skip comment lines\n\t\t\tif( IsComment( trim ))\n\t\t\t\tbreak;\n\n\t\t\t// Is this a directive \"$command\"?, if so parse it and break\n\t\t\tif( ParseDirective( trim ))\n\t\t\t\tbreak;\n\n\t\t\tif( IsStartOfText( trim ))\n\t\t\t{\n\t\t\t\tmode = MSGFILE_TEXT;\n\t\t\t\tpCurrentText = (char*)(pMemFile + filePos);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( IsEndOfText( trim ))\n\t\t\t{\n\t\t\t\tCon_Reportf( \"%s: unexpected '}' found, line %d\\n\", __func__, lineNumber );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tQ_strncpy( currentName, trim, sizeof( currentName ));\n\t\t\tbreak;\n\t\tcase MSGFILE_TEXT:\n\t\t\tif( IsEndOfText( trim ))\n\t\t\t{\n\t\t\t\tint length = Q_strlen( currentName );\n\n\t\t\t\t// save name on name heap\n\t\t\t\tif( lastNamePos + length > 32768 )\n\t\t\t\t{\n\t\t\t\t\tCon_Reportf( \"%s: error while parsing!\\n\", __func__ );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tQ_strncpy( nameHeap + lastNamePos, currentName, sizeof( nameHeap ) - lastNamePos );\n\n\t\t\t\t// terminate text in-place in the memory file\n\t\t\t\t// (it's temporary memory that will be deleted)\n\t\t\t\tpMemFile[lastLinePos-1] = 0;\n\n\t\t\t\t// Save name/text on heap\n\t\t\t\ttextMessages[messageCount] = gMessageParms;\n\t\t\t\ttextMessages[messageCount].pName = nameHeap + lastNamePos;\n\t\t\t\tlastNamePos += length + 1;\n\t\t\t\ttextMessages[messageCount].pMessage = pCurrentText;\n\t\t\t\tmessageCount++;\n\n\t\t\t\t// reset parser to search for names\n\t\t\t\tmode = MSGFILE_NAME;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif( IsStartOfText( trim ))\n\t\t\t{\n\t\t\t\tCon_Reportf( \"%s: unexpected '{' found, line %d\\n\", __func__, lineNumber );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tlineNumber++;\n\t\tlastLinePos = filePos;\n\n\t\tif( messageCount >= MAX_MESSAGES )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"Too many messages in titles.txt, max is %d\\n\", MAX_MESSAGES );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tCon_Reportf( \"%s: parsed %d text messages\\n\", __func__, messageCount );\n\tnameHeapSize = lastNamePos;\n\ttextHeapSize = 0;\n\n\tfor( i = 0; i < messageCount; i++ )\n\t\ttextHeapSize += Q_strlen( textMessages[i].pMessage ) + 1;\n\tmessageSize = ( messageCount * sizeof( client_textmessage_t ));\n\n\tif(( textHeapSize + nameHeapSize + messageSize ) <= 0 )\n\t{\n\t\tclgame.titles = NULL;\n\t\tclgame.numTitles = 0;\n\t\treturn;\n\t}\n\n\t// must malloc because we need to be able to clear it after initialization\n\tclgame.titles = (client_textmessage_t *)Mem_Calloc( cls.mempool, textHeapSize + nameHeapSize + messageSize );\n\n\t// copy table over\n\tmemcpy( clgame.titles, textMessages, messageSize );\n\n\t// copy Name heap\n\tpNameHeap = ((char *)clgame.titles) + messageSize;\n\tmemcpy( pNameHeap, nameHeap, nameHeapSize );\n\t//nameOffset = pNameHeap - clgame.titles[0].pName; //undefined on amd64\n\n\n\t// copy text & fixup pointers\n\ttextHeapSizeRemaining = textHeapSize;\n\tpCurrentText = pNameHeap + nameHeapSize;\n\n\tfor( i = 0; i < messageCount; i++ )\n\t{\n\t\tsize_t currentTextSize = Q_strlen( clgame.titles[i].pMessage ) + 1;\n\n\t\tclgame.titles[i].pName = pNameHeap;\t\t\t// adjust name pointer (parallel buffer)\n\t\tQ_strncpy( pCurrentText, clgame.titles[i].pMessage, textHeapSizeRemaining );\t// copy text over\n\t\tclgame.titles[i].pMessage = pCurrentText;\n\n\t\tpNameHeap += Q_strlen( pNameHeap ) + 1;\n\t\tpCurrentText += currentTextSize;\n\t\ttextHeapSizeRemaining -= currentTextSize;\n\t}\n\n\tif(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize ))\n\t\tCon_DPrintf( S_ERROR \"%s: overflow text message buffer!\\n\", __func__ );\n\n\tclgame.numTitles = messageCount;\n}\n"
  },
  {
    "path": "engine/client/vgui/vgui_draw.c",
    "content": "/*\nvgui_draw.c - vgui draw methods\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <string.h>\n#include \"common.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"vgui_api.h\"\n#include \"library.h\"\n#include \"keydefs.h\"\n#include \"ref_common.h\"\n#include \"input.h\"\n#include \"platform/platform.h\"\n\n#define VGUI_MAX_TEXTURES 1024\n\ntypedef struct vgui_reusable_texture_s\n{\n\tint gl_texturenum;\n\tbyte hash[16];\n} vgui_reusable_texture_t;\n\ntypedef struct vgui_static_s\n{\n\tqboolean initialized;\n\tVGUI_DefaultCursor cursor;\n\tvguiapi_t dllFuncs;\n\n\tvgui_reusable_texture_t *textures;\n\tint texture_id;\n\tint max_textures;\n\tint bound_texture;\n\tbyte color[4];\n\tqboolean enable_texture;\n\n\tHINSTANCE hInstance;\n\n\tpoolhandle_t mempool;\n\n\tenum VGUI_KeyCode virtualKeyTrans[256];\n} vgui_static_t;\n\nstatic vgui_static_t vgui = {\n\tfalse, -1\n};\nstatic CVAR_DEFINE_AUTO( vgui_utf8, \"0\", FCVAR_ARCHIVE, \"enable utf-8 support for vgui text\" );\n\nstatic void GAME_EXPORT VGUI_DrawInit( void )\n{\n\tif( vgui.mempool )\n\t\tMem_EmptyPool( vgui.mempool );\n\telse vgui.mempool = Mem_AllocPool( \"VGui Support Pool\" );\n\n\tvgui.textures = NULL;\n\n\tmemset( vgui.color, 0, sizeof( vgui.color ));\n\tvgui.texture_id = 0;\n\tvgui.bound_texture = 0;\n\tvgui.max_textures = 0;\n\tvgui.enable_texture = true;\n}\n\nstatic void GAME_EXPORT VGUI_DrawShutdown( void )\n{\n\tint i;\n\n\tfor( i = 1; i < vgui.texture_id; i++ )\n\t\tref.dllFuncs.GL_FreeTexture( vgui.textures[i].gl_texturenum );\n\n\tMem_FreePool( &vgui.mempool );\n\tvgui.textures = NULL;\n\n\tmemset( vgui.color, 0, sizeof( vgui.color ));\n\tvgui.texture_id = 0;\n\tvgui.bound_texture = 0;\n\tvgui.max_textures = 0;\n}\n\nstatic int GAME_EXPORT VGUI_GenerateTexture( void )\n{\n\t// allocate new\n\tif( vgui.texture_id + 1 >= vgui.max_textures )\n\t{\n\t\tif( vgui.max_textures + VGUI_MAX_TEXTURES >= VGUI_MAX_TEXTURES * VGUI_MAX_TEXTURES )\n\t\t{\n\t\t\t// in theory it might look up texture that hasn't been bound for a while and\n\t\t\t// reuse that but it will eventually overwrite some important textures anyway\n\t\t\tCon_Printf( S_ERROR \"%s: Refusing resizing VGUI textures array due to memory leak\\n\", __func__ );\n\t\t\treturn vgui.texture_id;\n\t\t}\n\n\t\tvgui.max_textures += VGUI_MAX_TEXTURES;\n\n\t\t// this potentially might leak memory if VGUI is used incorrectly!\n\t\t// (like in Cry of Fear)\n\t\tvgui.textures = Mem_Realloc( vgui.mempool, vgui.textures, sizeof( *vgui.textures ) * vgui.max_textures );\n\n\t\t// warn mod developer\n\t\tif( vgui.max_textures >= VGUI_MAX_TEXTURES * 4 )\n\t\t\tCon_Printf( S_ERROR \"%s: Potential memory leak in VGUI code is detected!\\n\", __func__ );\n\t}\n\n\treturn ++vgui.texture_id;\n}\n\nstatic void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height )\n{\n\trgbdata_t r_image = { 0 };\n\tchar texName[32];\n\tMD5Context_t ctx;\n\tbyte hash[16];\n\n\tif( id <= 0 || id >= vgui.max_textures || width <= 0 || height <= 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: bad texture %i. Ignored\\n\", __func__, id );\n\t\treturn;\n\t}\n\n\t// need to do this as some mods tend to upload same texture over and over\n\t// exhausing engine-wide limit on textures and leaking vram\n\tMD5Init( &ctx );\n\tMD5Update( &ctx, buffer, width * height * 4 );\n\tMD5Final( hash, &ctx );\n\n\t// it's a new texture, try to find a copy\n\tif( vgui.textures[id].gl_texturenum == 0 )\n\t{\n\t\tint i;\n\n\t\tfor( i = 1; i < vgui.texture_id; i++ )\n\t\t{\n\t\t\tif( vgui.textures[i].gl_texturenum != 0 && !memcmp( vgui.textures[i].hash, hash, sizeof( hash )))\n\t\t\t{\n\t\t\t\t// copy data to new texture id\n\t\t\t\tvgui.textures[id] = vgui.textures[i];\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_snprintf( texName, sizeof( texName ), \"*vgui%i\", id );\n\n\tr_image.width = width;\n\tr_image.height = height;\n\tr_image.type = PF_RGBA_32;\n\tr_image.size = width * height * 4;\n\tr_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;\n\tr_image.buffer = (byte*)buffer;\n\n\tvgui.textures[id].gl_texturenum = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );\n\tmemcpy( vgui.textures[id].hash, hash, sizeof( hash ));\n}\n\nstatic void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height )\n{\n\t// nothing uses it, it can be removed\n\tHost_Error( \"%s: deprecated\\n\", __func__ );\n\n}\n\nstatic void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )\n{\n\t// nothing uses it, it can be removed\n\tHost_Error( \"%s: deprecated\\n\", __func__ );\n}\n\nstatic void GAME_EXPORT VGUI_BindTexture( int id )\n{\n\tif( id <= 0 || id >= vgui.max_textures || !vgui.textures[id].gl_texturenum )\n\t\tid = 1; // NOTE: same as bogus index 2700 in GoldSrc\n\n\tref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id].gl_texturenum );\n\tvgui.bound_texture = id;\n}\n\nstatic void GAME_EXPORT VGUI_GetTextureSizes( int *w, int *h )\n{\n\tint texnum;\n\n\tif( vgui.bound_texture )\n\t\ttexnum = vgui.textures[vgui.bound_texture].gl_texturenum;\n\telse\n\t\ttexnum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );\n\n\tR_GetTextureParms( w, h, texnum );\n}\n\nstatic void GAME_EXPORT VGUI_SetupDrawingRect( int *pColor )\n{\n\tref.dllFuncs.VGUI_SetupDrawing( true );\n\tVector4Set( vgui.color, pColor[0], pColor[1], pColor[2], 255 - pColor[3] );\n}\n\nstatic void GAME_EXPORT VGUI_SetupDrawingText( int *pColor )\n{\n\tref.dllFuncs.VGUI_SetupDrawing( false );\n\tVector4Set( vgui.color, pColor[0], pColor[1], pColor[2], 255 - pColor[3] );\n}\n\nstatic void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr )\n{\n\tfloat x, y, w, h;\n\n\tif( !ul || !lr )\n\t\treturn;\n\n\tx = ul->point[0];\n\ty = ul->point[1];\n\tw = lr->point[0] - x;\n\th = lr->point[1] - y;\n\n\tSPR_AdjustSize( &x, &y, &w, &h );\n\n\tif( vgui.enable_texture )\n\t{\n\t\tfloat s1, s2, t1, t2;\n\n\t\ts1 = ul->coord[0];\n\t\tt1 = ul->coord[1];\n\t\ts2 = lr->coord[0];\n\t\tt2 = lr->coord[1];\n\n\t\tref.dllFuncs.Color4ub( vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] );\n\t\tref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture].gl_texturenum );\n\t}\n\telse\n\t{\n\t\tref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, w, h, vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] );\n\t}\n}\n\nstatic void GAME_EXPORT VGUI_EnableTexture( qboolean enable )\n{\n\tvgui.enable_texture = enable;\n}\n\nstatic void GAME_EXPORT *VGUI_EngineMalloc( size_t size )\n{\n\treturn Z_Malloc( size );\n}\n\nstatic qboolean GAME_EXPORT VGUI_IsInGame( void )\n{\n\treturn cls.state == ca_active && cls.key_dest == key_game;\n}\n\nstatic void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y )\n{\n\tfloat xscale = (float)refState.width / (float)clgame.scrInfo.iWidth;\n\tfloat yscale = (float)refState.height / (float)clgame.scrInfo.iHeight;\n\tint x, y;\n\n\tPlatform_GetMousePos( &x, &y );\n\t*_x = x / xscale;\n\t*_y = y / yscale;\n}\n\nstatic void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor )\n{\n\tif( vgui.cursor != cursor )\n\t\tPlatform_SetCursorType( cursor );\n}\n\nstatic byte GAME_EXPORT VGUI_GetColor( int i, int j )\n{\n\treturn g_color_table[i][j];\n}\n\nstatic int GAME_EXPORT VGUI_UtfProcessChar( int in )\n{\n\tif( vgui_utf8.value )\n\t\treturn Con_UtfProcessCharForce( in );\n\treturn in;\n}\n\nqboolean VGui_IsActive( void )\n{\n\treturn vgui.initialized;\n}\n\nvoid VGui_RegisterCvars( void )\n{\n\tCvar_RegisterVariable( &vgui_utf8 );\n}\n\nstatic const vguiapi_t gEngfuncs =\n{\n\tfalse, // Not initialized yet\n\tVGUI_DrawInit, // VGUI_DrawInit,\n\tVGUI_DrawShutdown, // VGUI_DrawShutdown,\n\tVGUI_SetupDrawingText, // VGUI_SetupDrawingText,\n\tVGUI_SetupDrawingRect, // VGUI_SetupDrawingRect,\n\tVGUI_SetupDrawingText, // VGUI_SetupDrawingImage, (same as text)\n\tVGUI_BindTexture, // VGUI_BindTexture,\n\tVGUI_EnableTexture, // VGUI_EnableTexture,\n\tVGUI_CreateTexture, // VGUI_CreateTexture,\n\tVGUI_UploadTexture, // VGUI_UploadTexture,\n\tVGUI_UploadTextureBlock, // VGUI_UploadTextureBlock,\n\tVGUI_DrawQuad, // VGUI_DrawQuad,\n\tVGUI_GetTextureSizes, // VGUI_GetTextureSizes,\n\tVGUI_GenerateTexture, // VGUI_GenerateTexture,\n\tVGUI_EngineMalloc,\n\tVGUI_CursorSelect,\n\tVGUI_GetColor,\n\tVGUI_IsInGame,\n\tKey_EnableTextInput,\n\tVGUI_GetMousePos,\n\tVGUI_UtfProcessChar,\n\tPlatform_GetClipboardText,\n\tPlatform_SetClipboardText,\n\tPlatform_GetKeyModifiers,\n};\n\nqboolean VGui_LoadProgs( HINSTANCE hInstance )\n{\n\tvoid (*F)( vguiapi_t* );\n\tqboolean client = hInstance != NULL;\n\n\tvgui.dllFuncs = gEngfuncs;\n\n\t// not loading interface from client.dll, load vgui_support.dll instead\n\tif( !client )\n\t{\n\t\tstring vguiloader, vguilib;\n\n\t\t// HACKHACK: try to load path from custom path\n\t\t// to support having different versions of VGUI\n\t\tif( Sys_GetParmFromCmdLine( \"-vguilib\", vguilib ) && !COM_LoadLibrary( vguilib, false, false ))\n\t\t{\n\t\t\tCon_Reportf( S_WARN \"VGUI preloading failed. Default library will be used! Reason: %s\", COM_GetLibraryError());\n\t\t}\n\n\t\tif( !Sys_GetParmFromCmdLine( \"-vguiloader\", vguiloader ))\n\t\t{\n\t\t\tQ_strncpy( vguiloader, VGUI_SUPPORT_DLL, sizeof( vguiloader ));\n\t\t}\n\n\t\thInstance = vgui.hInstance = COM_LoadLibrary( vguiloader, false, false );\n\n\t\tif( !vgui.hInstance )\n\t\t{\n\t\t\tif( FS_FileExists( vguiloader, false ))\n\t\t\t\tCon_Reportf( S_ERROR \"Failed to load vgui_support library: %s\\n\", COM_GetLibraryError() );\n\t\t\telse Con_Reportf( \"%s: not found\\n\", __func__ );\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// try legacy API first\n\tF = COM_GetProcAddress( hInstance, client ? \"InitVGUISupportAPI\" : \"InitAPI\" );\n\n\tif( F )\n\t{\n\t\tF( &vgui.dllFuncs );\n\n\t\tvgui.initialized = vgui.dllFuncs.initialized = true;\n\t\tCon_Reportf( \"%s: initialized legacy API in %s module\\n\", __func__, client ? \"client\" : \"support\" );\n\n\t\treturn true;\n\t}\n\n\tCon_Reportf( S_ERROR \"%s: Failed to find VGUI support API entry point in %s module\\n\", __func__, client ? \"client\" : \"support\" );\n\treturn false;\n}\n\n/*\n================\nVGui_Startup\n\n================\n*/\nvoid VGui_Startup( int width, int height )\n{\n\t// vgui not initialized from both support and client modules, skip\n\tif( !vgui.initialized )\n\t\treturn;\n\n\theight = Q_max( 480, height );\n\n\tif( width <= 640 ) width = 640;\n\telse if( width <= 800 ) width = 800;\n\telse if( width <= 1024 ) width = 1024;\n\telse if( width <= 1152 ) width = 1152;\n\telse if( width <= 1280 ) width = 1280;\n\telse if( width <= 1600 ) width = 1600;\n\n\tif( vgui.dllFuncs.Startup )\n\t\tvgui.dllFuncs.Startup( width, height );\n}\n\n\n\n/*\n================\nVGui_Shutdown\n\nUnload vgui_support library and call VGui_Shutdown\n================\n*/\nvoid VGui_Shutdown( void )\n{\n\tif( vgui.dllFuncs.Shutdown )\n\t\tvgui.dllFuncs.Shutdown();\n\n\tif( vgui.hInstance )\n\t\tCOM_FreeLibrary( vgui.hInstance );\n\n\t// drop pointers to now unloaded vgui_support\n\tvgui.dllFuncs = gEngfuncs;\n\tvgui.hInstance = NULL;\n}\n\n\nstatic void VGUI_InitKeyTranslationTable( void )\n{\n\tstatic qboolean initialized = false;\n\n\tif( initialized ) return;\n\n\tinitialized = true;\n\n\t// set virtual key translation table\n\tmemset( vgui.virtualKeyTrans, -1, sizeof( vgui.virtualKeyTrans ) );\n\n\t// TODO: engine keys are not enough here!\n\t// make crossplatform way to pass SDL keys here\n\n\tvgui.virtualKeyTrans['0'] = KEY_0;\n\tvgui.virtualKeyTrans['1'] = KEY_1;\n\tvgui.virtualKeyTrans['2'] = KEY_2;\n\tvgui.virtualKeyTrans['3'] = KEY_3;\n\tvgui.virtualKeyTrans['4'] = KEY_4;\n\tvgui.virtualKeyTrans['5'] = KEY_5;\n\tvgui.virtualKeyTrans['6'] = KEY_6;\n\tvgui.virtualKeyTrans['7'] = KEY_7;\n\tvgui.virtualKeyTrans['8'] = KEY_8;\n\tvgui.virtualKeyTrans['9'] = KEY_9;\n\tvgui.virtualKeyTrans['A'] = vgui.virtualKeyTrans['a'] = KEY_A;\n\tvgui.virtualKeyTrans['B'] = vgui.virtualKeyTrans['b'] = KEY_B;\n\tvgui.virtualKeyTrans['C'] = vgui.virtualKeyTrans['c'] = KEY_C;\n\tvgui.virtualKeyTrans['D'] = vgui.virtualKeyTrans['d'] = KEY_D;\n\tvgui.virtualKeyTrans['E'] = vgui.virtualKeyTrans['e'] = KEY_E;\n\tvgui.virtualKeyTrans['F'] = vgui.virtualKeyTrans['f'] = KEY_F;\n\tvgui.virtualKeyTrans['G'] = vgui.virtualKeyTrans['g'] = KEY_G;\n\tvgui.virtualKeyTrans['H'] = vgui.virtualKeyTrans['h'] = KEY_H;\n\tvgui.virtualKeyTrans['I'] = vgui.virtualKeyTrans['i'] = KEY_I;\n\tvgui.virtualKeyTrans['J'] = vgui.virtualKeyTrans['j'] = KEY_J;\n\tvgui.virtualKeyTrans['K'] = vgui.virtualKeyTrans['k'] = KEY_K;\n\tvgui.virtualKeyTrans['L'] = vgui.virtualKeyTrans['l'] = KEY_L;\n\tvgui.virtualKeyTrans['M'] = vgui.virtualKeyTrans['m'] = KEY_M;\n\tvgui.virtualKeyTrans['N'] = vgui.virtualKeyTrans['n'] = KEY_N;\n\tvgui.virtualKeyTrans['O'] = vgui.virtualKeyTrans['o'] = KEY_O;\n\tvgui.virtualKeyTrans['P'] = vgui.virtualKeyTrans['p'] = KEY_P;\n\tvgui.virtualKeyTrans['Q'] = vgui.virtualKeyTrans['q'] = KEY_Q;\n\tvgui.virtualKeyTrans['R'] = vgui.virtualKeyTrans['r'] = KEY_R;\n\tvgui.virtualKeyTrans['S'] = vgui.virtualKeyTrans['s'] = KEY_S;\n\tvgui.virtualKeyTrans['T'] = vgui.virtualKeyTrans['t'] = KEY_T;\n\tvgui.virtualKeyTrans['U'] = vgui.virtualKeyTrans['u'] = KEY_U;\n\tvgui.virtualKeyTrans['V'] = vgui.virtualKeyTrans['v'] = KEY_V;\n\tvgui.virtualKeyTrans['W'] = vgui.virtualKeyTrans['w'] = KEY_W;\n\tvgui.virtualKeyTrans['X'] = vgui.virtualKeyTrans['x'] = KEY_X;\n\tvgui.virtualKeyTrans['Y'] = vgui.virtualKeyTrans['y'] = KEY_Y;\n\tvgui.virtualKeyTrans['Z'] = vgui.virtualKeyTrans['z'] = KEY_Z;\n\n\tvgui.virtualKeyTrans[K_KP_5 - 5] = KEY_PAD_0;\n\tvgui.virtualKeyTrans[K_KP_5 - 4] = KEY_PAD_1;\n\tvgui.virtualKeyTrans[K_KP_5 - 3] = KEY_PAD_2;\n\tvgui.virtualKeyTrans[K_KP_5 - 2] = KEY_PAD_3;\n\tvgui.virtualKeyTrans[K_KP_5 - 1] = KEY_PAD_4;\n\tvgui.virtualKeyTrans[K_KP_5 - 0] = KEY_PAD_5;\n\tvgui.virtualKeyTrans[K_KP_5 + 1] = KEY_PAD_6;\n\tvgui.virtualKeyTrans[K_KP_5 + 2] = KEY_PAD_7;\n\tvgui.virtualKeyTrans[K_KP_5 + 3] = KEY_PAD_8;\n\tvgui.virtualKeyTrans[K_KP_5 + 4] = KEY_PAD_9;\n\tvgui.virtualKeyTrans[K_KP_SLASH] = KEY_PAD_DIVIDE;\n\tvgui.virtualKeyTrans['*']        = KEY_PAD_MULTIPLY;\n\tvgui.virtualKeyTrans[K_KP_MINUS] = KEY_PAD_MINUS;\n\tvgui.virtualKeyTrans[K_KP_PLUS]  = KEY_PAD_PLUS;\n\tvgui.virtualKeyTrans[K_KP_ENTER] = KEY_PAD_ENTER;\n\tvgui.virtualKeyTrans[K_KP_NUMLOCK] = KEY_NUMLOCK;\n\tvgui.virtualKeyTrans['['] = KEY_LBRACKET;\n\tvgui.virtualKeyTrans[']'] = KEY_RBRACKET;\n\tvgui.virtualKeyTrans[';'] = KEY_SEMICOLON;\n\tvgui.virtualKeyTrans['`'] = KEY_BACKQUOTE;\n\tvgui.virtualKeyTrans[','] = KEY_COMMA;\n\tvgui.virtualKeyTrans['.'] = KEY_PERIOD;\n\tvgui.virtualKeyTrans['-'] = KEY_MINUS;\n\tvgui.virtualKeyTrans['='] = KEY_EQUAL;\n\tvgui.virtualKeyTrans['/'] = KEY_SLASH;\n\tvgui.virtualKeyTrans['\\\\'] = KEY_BACKSLASH;\n\tvgui.virtualKeyTrans['\\''] = KEY_APOSTROPHE;\n\tvgui.virtualKeyTrans[K_TAB] = KEY_TAB;\n\tvgui.virtualKeyTrans[K_ENTER] = KEY_ENTER;\n\tvgui.virtualKeyTrans[K_SPACE] = KEY_SPACE;\n\tvgui.virtualKeyTrans[K_CAPSLOCK] = KEY_CAPSLOCK;\n\tvgui.virtualKeyTrans[K_BACKSPACE] = KEY_BACKSPACE;\n\tvgui.virtualKeyTrans[K_ESCAPE]\t= KEY_ESCAPE;\n\tvgui.virtualKeyTrans[K_INS] = KEY_INSERT;\n\tvgui.virtualKeyTrans[K_DEL] = KEY_DELETE;\n\tvgui.virtualKeyTrans[K_HOME] = KEY_HOME;\n\tvgui.virtualKeyTrans[K_END] = KEY_END;\n\tvgui.virtualKeyTrans[K_PGUP] = KEY_PAGEUP;\n\tvgui.virtualKeyTrans[K_PGDN] = KEY_PAGEDOWN;\n\tvgui.virtualKeyTrans[K_PAUSE] = KEY_BREAK;\n\tvgui.virtualKeyTrans[K_SHIFT] = KEY_LSHIFT;\t// SHIFT -> left SHIFT\n\tvgui.virtualKeyTrans[K_ALT] = KEY_LALT;\t\t// ALT -> left ALT\n\tvgui.virtualKeyTrans[K_CTRL] = KEY_LCONTROL;\t// CTRL -> left CTRL\n\tvgui.virtualKeyTrans[K_WIN] = KEY_LWIN;\n\tvgui.virtualKeyTrans[K_UPARROW] = KEY_UP;\n\tvgui.virtualKeyTrans[K_LEFTARROW] = KEY_LEFT;\n\tvgui.virtualKeyTrans[K_DOWNARROW] = KEY_DOWN;\n\tvgui.virtualKeyTrans[K_RIGHTARROW] = KEY_RIGHT;\n\tvgui.virtualKeyTrans[K_F1] = KEY_F1;\n\tvgui.virtualKeyTrans[K_F2] = KEY_F2;\n\tvgui.virtualKeyTrans[K_F3] = KEY_F3;\n\tvgui.virtualKeyTrans[K_F4] = KEY_F4;\n\tvgui.virtualKeyTrans[K_F5] = KEY_F5;\n\tvgui.virtualKeyTrans[K_F6] = KEY_F6;\n\tvgui.virtualKeyTrans[K_F7] = KEY_F7;\n\tvgui.virtualKeyTrans[K_F8] = KEY_F8;\n\tvgui.virtualKeyTrans[K_F9] = KEY_F9;\n\tvgui.virtualKeyTrans[K_F10] = KEY_F10;\n\tvgui.virtualKeyTrans[K_F11] = KEY_F11;\n\tvgui.virtualKeyTrans[K_F12] = KEY_F12;\n}\n\nstatic enum VGUI_KeyCode VGUI_MapKey( int keyCode )\n{\n\tVGUI_InitKeyTranslationTable();\n\n\tif( keyCode >= 0 && keyCode < ARRAYSIZE( vgui.virtualKeyTrans ))\n\t\treturn vgui.virtualKeyTrans[keyCode];\n\n\treturn (enum VGUI_KeyCode)-1;\n}\n\nvoid VGui_MouseEvent( int key, int clicks )\n{\n\tenum VGUI_MouseAction mact;\n\tenum VGUI_MouseCode   code;\n\n\tif( !vgui.dllFuncs.Mouse )\n\t\treturn;\n\n\tswitch( key )\n\t{\n\tcase K_MOUSE1: code = MOUSE_LEFT; break;\n\tcase K_MOUSE2: code = MOUSE_RIGHT; break;\n\tcase K_MOUSE3: code = MOUSE_MIDDLE; break;\n\tdefault: return;\n\t}\n\n\tif( clicks >= 2 )\n\t\tmact = MA_DOUBLE;\n\telse if( clicks == 1 )\n\t\tmact = MA_PRESSED;\n\telse\n\t\tmact = MA_RELEASED;\n\n\tvgui.dllFuncs.Mouse( mact, code );\n}\n\nvoid VGui_MWheelEvent( int y )\n{\n\tif( !vgui.dllFuncs.Mouse )\n\t\treturn;\n\n\tvgui.dllFuncs.Mouse( MA_WHEEL, y );\n}\n\nvoid VGui_KeyEvent( int key, int down )\n{\n\tenum VGUI_KeyCode code;\n\n\tif( !vgui.dllFuncs.Key )\n\t\treturn;\n\n\tif(( code = VGUI_MapKey( key )) < 0 )\n\t\treturn;\n\n\tif( down )\n\t{\n\t\tvgui.dllFuncs.Key( KA_PRESSED, code );\n\t\tvgui.dllFuncs.Key( KA_TYPED, code );\n\t}\n\telse vgui.dllFuncs.Key( KA_RELEASED, code );\n}\n\nvoid VGui_MouseMove( int x, int y )\n{\n\tif( vgui.dllFuncs.MouseMove )\n\t{\n\t\tfloat xscale = (float)refState.width / (float)clgame.scrInfo.iWidth;\n\t\tfloat yscale = (float)refState.height / (float)clgame.scrInfo.iHeight;\n\t\tvgui.dllFuncs.MouseMove( x / xscale, y / yscale );\n\t}\n}\n\nvoid VGui_Paint( void )\n{\n\tif( vgui.dllFuncs.Paint )\n\t\tvgui.dllFuncs.Paint();\n}\n\nvoid VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType )\n{\n\tvgui.cursor = cursorType;\n}\n\nvoid *GAME_EXPORT VGui_GetPanel( void )\n{\n\tif( vgui.dllFuncs.GetPanel )\n\t\treturn vgui.dllFuncs.GetPanel();\n\treturn NULL;\n}\n\nvoid VGui_ReportTextInput( const char *text )\n{\n\tif( vgui.dllFuncs.TextInput )\n\t\tvgui.dllFuncs.TextInput( text );\n}\n\n"
  },
  {
    "path": "engine/client/vgui/vgui_draw.h",
    "content": "/*\nvgui_draw.h - vgui draw methods\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef VGUI_DRAW_H\n#define VGUI_DRAW_H\n\n//\n// vgui_draw.c\n//\nvoid VGui_RegisterCvars( void );\nqboolean VGui_LoadProgs( HINSTANCE hInstance );\nvoid VGui_Startup( int width, int height );\nvoid VGui_Shutdown( void );\nvoid VGui_Paint( void );\nvoid VGui_RunFrame( void );\nvoid VGui_MouseEvent( int key, int clicks );\nvoid VGui_MWheelEvent( int y );\nvoid VGui_KeyEvent( int key, int down );\nvoid VGui_MouseMove( int x, int y );\nqboolean VGui_IsActive( void );\nvoid *VGui_GetPanel( void );\nvoid VGui_ReportTextInput( const char *text );\nvoid VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType );\n\n#endif // VGUI_DRAW_H\n"
  },
  {
    "path": "engine/client/vid_common.c",
    "content": "/*\nvid_common.c - common vid component\nCopyright (C) 2018 a1batross, Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"mod_local.h\"\n#include \"input.h\"\n#include \"vid_common.h\"\n#include \"platform/platform.h\"\n\nstatic CVAR_DEFINE_AUTO( vid_mode, \"0\", FCVAR_RENDERINFO, \"current video mode index (used only for storage)\" );\nstatic CVAR_DEFINE_AUTO( vid_rotate, \"0\", FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"screen rotation (0-3)\" );\nstatic CVAR_DEFINE_AUTO( vid_scale, \"1.0\", FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"pixel scale\" );\n\nCVAR_DEFINE_AUTO( vid_highdpi, \"1\",  FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"enable High-DPI mode\" );\nCVAR_DEFINE_AUTO( vid_maximized, \"0\", FCVAR_RENDERINFO, \"window maximized state, read-only\" );\nCVAR_DEFINE( vid_fullscreen, \"fullscreen\", DEFAULT_FULLSCREEN, FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"fullscreen state (0 windowed, 1 fullscreen, 2 borderless)\" );\nCVAR_DEFINE( window_width, \"width\", \"0\", FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"screen width\" );\nCVAR_DEFINE( window_height, \"height\", \"0\", FCVAR_RENDERINFO|FCVAR_VIDRESTART, \"screen height\" );\nCVAR_DEFINE( window_xpos, \"_window_xpos\", \"-1\", FCVAR_RENDERINFO, \"window position by horizontal\" );\nCVAR_DEFINE( window_ypos, \"_window_ypos\", \"-1\", FCVAR_RENDERINFO, \"window position by vertical\" );\n\nglwstate_t\tglw_state;\n\n/*\n=================\nVID_InitDefaultResolution\n=================\n*/\nvoid VID_InitDefaultResolution( void )\n{\n\t// we need to have something valid here\n\t// until video subsystem initialized\n\trefState.width = 640;\n\trefState.height = 480;\n}\n\n/*\n=================\nR_SaveVideoMode\n=================\n*/\nvoid R_SaveVideoMode( int w, int h, int render_w, int render_h, qboolean maximized )\n{\n\tif( !w || !h || !render_w || !render_h )\n\t{\n\t\thost.renderinfo_changed = false;\n\t\treturn;\n\t}\n\n\thost.window_center_x = w / 2;\n\thost.window_center_y = h / 2;\n\n\tCvar_SetValue( \"width\", w );\n\tCvar_SetValue( \"height\", h );\n\tCvar_DirectSet( &vid_maximized, maximized ? \"1\" : \"0\" );\n\t\n\t// immediately drop changed state or we may trigger\n\t// video subsystem to reapply settings\n\thost.renderinfo_changed = false;\n\n\tif( refState.width == render_w && refState.height == render_h )\n\t\treturn;\n\n\trefState.width = render_w;\n\trefState.height = render_h;\n\n\t// check for 4:3 or 5:4\n\tif( render_w * 3 != render_h * 4 && render_w * 4 != render_h * 5 )\n\t\trefState.wideScreen = true;\n\telse refState.wideScreen = false;\n\n\tSCR_VidInit(); // tell client.dll that vid_mode has changed\n}\n\n/*\n=================\nVID_GetModeString\n=================\n*/\nconst char *VID_GetModeString( int vid_mode )\n{\n\tvidmode_t *vidmode;\n\tif( vid_mode < 0 || vid_mode >= R_MaxVideoModes() )\n\t\treturn NULL;\n\n\tif( !( vidmode = R_GetVideoMode( vid_mode ) ) )\n\t\treturn NULL;\n\n\treturn vidmode->desc;\n}\n\n/*\n==================\nVID_CheckChanges\n\ncheck vid modes and fullscreen\n==================\n*/\nvoid VID_CheckChanges( void )\n{\n\tif( FBitSet( cl_allow_levelshots.flags, FCVAR_CHANGED ))\n\t{\n\t\t//GL_FreeTexture( cls.loadingBar );\n\t\tSCR_RegisterTextures(); // reload 'lambda' image\n\t\tClearBits( cl_allow_levelshots.flags, FCVAR_CHANGED );\n\t}\n\n\tif( host.renderinfo_changed )\n\t{\n\t\tif( VID_SetMode( ))\n\t\t{\n\t\t\tSCR_VidInit(); // tell the client.dll what vid_mode has changed\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSys_Error( \"Can't re-initialize video subsystem\\n\" );\n\t\t}\n\t\thost.renderinfo_changed = false;\n\t}\n}\n\n/*\n===============\nVID_SetDisplayTransform\n\nnotify ref dll about screen transformations\n===============\n*/\nvoid VID_SetDisplayTransform( int *render_w, int *render_h )\n{\n\tuint rotate = vid_rotate.value;\n\n\tif( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale.value, vid_scale.value ))\n\t{\n\t\tif( rotate & 1 )\n\t\t{\n\t\t\tint swap = *render_w;\n\n\t\t\t*render_w = *render_h;\n\t\t\t*render_h = swap;\n\t\t}\n\n\t\t*render_h /= vid_scale.value;\n\t\t*render_w /= vid_scale.value;\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_WARN \"failed to setup screen transform\\n\" );\n\t}\n}\n\nstatic void VID_Mode_f( void )\n{\n\tint w, h;\n\n\tswitch( Cmd_Argc() )\n\t{\n\tcase 2:\n\t{\n\t\tvidmode_t *vidmode;\n\n\t\tvidmode = R_GetVideoMode( Q_atoi( Cmd_Argv( 1 )) );\n\t\tif( !vidmode )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"unable to set mode, backend returned null\" );\n\t\t\treturn;\n\t\t}\n\n\t\tw = vidmode->width;\n\t\th = vidmode->height;\n\t\tbreak;\n\t}\n\tcase 3:\n\t{\n\t\tw = Q_atoi( Cmd_Argv( 1 ));\n\t\th = Q_atoi( Cmd_Argv( 2 ));\n\t\tbreak;\n\t}\n\tdefault:\n\t\tMsg( S_USAGE \"vid_mode <modenum>|<width height>\\n\" );\n\t\treturn;\n\t}\n\n\tR_ChangeDisplaySettings( w, h, bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 ));\n}\n\nvoid VID_Init( void )\n{\n\t// system screen width and height (don't suppose for change from console at all)\n\tCvar_RegisterVariable( &window_width );\n\tCvar_RegisterVariable( &window_height );\n\n\tCvar_RegisterVariable( &vid_mode );\n\tCvar_RegisterVariable( &vid_highdpi );\n\tCvar_RegisterVariable( &vid_rotate );\n\tCvar_RegisterVariable( &vid_scale );\n\tCvar_RegisterVariable( &vid_fullscreen );\n\tCvar_RegisterVariable( &vid_maximized );\n\tCvar_RegisterVariable( &window_xpos );\n\tCvar_RegisterVariable( &window_ypos );\n\n\t// a1ba: planned to be named vid_mode for compability\n\t// but supported mode list is filled by backends, so numbers are not portable any more\n\tCmd_AddRestrictedCommand( \"vid_setmode\", VID_Mode_f, \"display video mode\" );\n\n\tV_Init(); // init gamma\n\tR_Init(); // init renderer\n}\n"
  },
  {
    "path": "engine/client/vid_common.h",
    "content": "#pragma once\n#ifndef VID_COMMON\n#define VID_COMMON\n\ntypedef struct vidmode_s\n{\n\tconst char\t*desc;\n\tint\t\t\twidth;\n\tint\t\t\theight;\n} vidmode_t;\n\ntypedef enum window_mode_e\n{\n\tWINDOW_MODE_WINDOWED = 0,\n\tWINDOW_MODE_FULLSCREEN,\n\tWINDOW_MODE_BORDERLESS,\n\tWINDOW_MODE_COUNT,\n} window_mode_t;\n\ntypedef struct\n{\n\tvoid*\tcontext; // handle to GL rendering context\n\tint\t\tsafe;\n\n\tint\t\tdesktopBitsPixel;\n\tint\t\tdesktopHeight;\n\n\tqboolean\t\tinitialized;\t// OpenGL subsystem started\n\tqboolean\t\textended;\t\t// extended context allows to GL_Debug\n\tint context_type; // REF_SOFTWARE / REF_GL / REF_VULKAN\n} glwstate_t;\n\nextern glwstate_t glw_state;\n\n#define VID_MIN_HEIGHT 200\n#define VID_MIN_WIDTH 320\n\nextern convar_t\tvid_fullscreen;\nextern convar_t vid_maximized;\nextern convar_t\tvid_highdpi;\nextern convar_t window_width;\nextern convar_t window_height;\nextern convar_t window_xpos;\nextern convar_t window_ypos;\nextern convar_t\tgl_msaa_samples;\n\nvoid R_SaveVideoMode( int w, int h, int render_w, int render_h, qboolean maximized );\nvoid VID_SetDisplayTransform( int *render_w, int *render_h );\nvoid VID_CheckChanges( void );\nconst char *VID_GetModeString( int vid_mode );\n\n#endif // VID_COMMON\n"
  },
  {
    "path": "engine/client/voice.c",
    "content": "/*\nvoice.c - voice chat implementation\nCopyright (C) 2022 Velaron\nCopyright (C) 2022 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#define CUSTOM_MODES 1 // required to correctly link with Opus Custom\n#include <opus_custom.h>\n#include <opus.h>\n#include \"common.h\"\n#include \"client.h\"\n#include \"voice.h\"\n#include \"crclib.h\"\n\nvoice_state_t voice = { 0 };\n\nstatic CVAR_DEFINE_AUTO( voice_enable, \"1\", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, \"enable voice chat\" );\nCVAR_DEFINE_AUTO( voice_loopback, \"0\", FCVAR_PRIVILEGED, \"loopback voice back to the speaker\" );\nstatic CVAR_DEFINE_AUTO( voice_scale, \"1.0\", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, \"incoming voice volume scale\" );\nstatic CVAR_DEFINE_AUTO( voice_transmit_scale, \"1.0\", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, \"outcoming voice volume scale\" );\nstatic CVAR_DEFINE_AUTO( voice_avggain, \"0.5\", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, \"automatic voice gain control (average)\" );\nstatic CVAR_DEFINE_AUTO( voice_maxgain, \"5.0\", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, \"automatic voice gain control (maximum)\" );\nstatic CVAR_DEFINE_AUTO( voice_inputfromfile, \"0\", FCVAR_PRIVILEGED, \"input voice from voice_input.wav\" );\n\nstatic void Voice_StartChannel( uint samples, byte *data, int entnum );\n\n/*\n===============================================================================\n\n\tUTILITY FUNCTIONS\n\n===============================================================================\n*/\n\n/*\n=========================\nVoice_IsGoldSrcMode\n\nCheck if codec is GoldSrc mode\n=========================\n*/\nstatic qboolean Voice_IsGoldSrcMode( const char *codec )\n{\n\t// check for an empty codec string\n\t// if the server does not use ReVoice or VTC\n\t// it will send an empty codec\n\t// however, decoding is still possible\n\t// if the voice data contains Opus\n\treturn( COM_CheckString( codec ) == 0 || Q_strstr( codec, \"voice_speex\" ) != NULL );\n}\n\n/*\n=========================\nVoice_IsOpusCustomMode\n\nCheck if codec is Opus Custom mode\n=========================\n*/\nstatic qboolean Voice_IsOpusCustomMode( const char *codec )\n{\n\treturn Q_strcmp( codec, VOICE_OPUS_CUSTOM_CODEC ) == 0;\n}\n\n/*\n=========================\nVoice_GetBitrateForQuality\n\nGet bitrate for given quality level\n=========================\n*/\nstatic int Voice_GetBitrateForQuality( int quality, qboolean goldsrc )\n{\n\tswitch( quality )\n\t{\n\tcase 1: return 6000;   // 6 kbps\n\tcase 2: return 12000;  // 12 kbps\n\tcase 3: return 24000;  // 24 kbps\n\tcase 4: return 36000;  // 36 kbps\n\tcase 5: return 48000;  // 48 kbps\n\tdefault: return 36000; // default\n\t}\n}\n\n/*\n===============================================================================\n\n\tOPUS INTEGRATION\n\n===============================================================================\n*/\n\n/*\n=========================\nVoice_InitCustomMode\n\nInitialize Opus Custom mode\n=========================\n*/\nstatic qboolean Voice_InitCustomMode( void )\n{\n\tint err = 0;\n\n\tvoice.width = sizeof( int16_t );\n\tvoice.samplerate = VOICE_OPUS_CUSTOM_SAMPLERATE;\n\tvoice.frame_size = VOICE_OPUS_CUSTOM_FRAME_SIZE;\n\n\tvoice.custom_mode = opus_custom_mode_create( SOUND_44k, voice.frame_size, &err );\n\n\tif( !voice.custom_mode )\n\t{\n\t\tCon_Printf( S_ERROR \"Can't create Opus Custom mode: %s\\n\", opus_strerror( err ));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=========================\nVoice_InitOpusDecoder\n\nInitialize Opus decoders for clients\n=========================\n*/\nstatic qboolean Voice_InitOpusDecoder( void )\n{\n\tint err = 0;\n\n\tfor( int i = 0; i < cl.maxclients; i++ )\n\t{\n\t\tif( voice.goldsrc )\n\t\t{\n\t\t\tvoice.gs_decoders[i] = opus_decoder_create( GS_DEFAULT_SAMPLE_RATE, VOICE_PCM_CHANNELS, &err );\n\t\t\tif( !voice.gs_decoders[i] )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"Can't create GoldSrc Opus decoder for %i: %s\\n\", i, opus_strerror( err ));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvoice.decoders[i] = opus_custom_decoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err );\n\t\t\tif( !voice.decoders[i] )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"Can't create Custom Opus decoder for %i: %s\\n\", i, opus_strerror( err ));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=========================\nVoice_InitOpusEncoder\n\nInitialize Opus encoder with quality settings\n=========================\n*/\nstatic qboolean Voice_InitOpusEncoder( int quality )\n{\n\tint err = 0;\n\tint bitrate = Voice_GetBitrateForQuality( quality, voice.goldsrc );\n\n\tif( voice.goldsrc )\n\t{\n\t\tvoice.gs_encoder = opus_encoder_create( GS_DEFAULT_SAMPLE_RATE, VOICE_PCM_CHANNELS, OPUS_APPLICATION_VOIP, &err );\n\t\tif( !voice.gs_encoder )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Can't create GoldSrc Opus encoder: %s\\n\", opus_strerror( err ));\n\t\t\treturn false;\n\t\t}\n\t\topus_encoder_ctl( voice.gs_encoder, OPUS_SET_DTX( 1 ));\n\t\topus_encoder_ctl( voice.gs_encoder, OPUS_SET_BITRATE( bitrate ));\n\t}\n\telse\n\t{\n\t\tvoice.encoder = opus_custom_encoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err );\n\t\tif( !voice.encoder )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Can't create Opus encoder: %s\\n\", opus_strerror( err ));\n\t\t\treturn false;\n\t\t}\n\t\topus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( bitrate ));\n\t}\n\n\treturn true;\n}\n\n/*\n=========================\nVoice_ShutdownOpusDecoder\n\nCleanup Opus decoders\n=========================\n*/\nstatic void Voice_ShutdownOpusDecoder( void )\n{\n\tfor( int i = 0; i < MAX_CLIENTS; i++ )\n\t{\n\t\tif( voice.goldsrc )\n\t\t{\n\t\t\tif( voice.gs_decoders[i] )\n\t\t\t{\n\t\t\t\topus_decoder_destroy( voice.gs_decoders[i] );\n\t\t\t\tvoice.gs_decoders[i] = NULL;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( voice.decoders[i] )\n\t\t\t{\n\t\t\t\topus_custom_decoder_destroy( voice.decoders[i] );\n\t\t\t\tvoice.decoders[i] = NULL;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n=========================\nVoice_ShutdownOpusEncoder\n\nCleanup Opus encoder\n=========================\n*/\nstatic void Voice_ShutdownOpusEncoder( void )\n{\n\tif( voice.goldsrc )\n\t{\n\t\tif( voice.gs_encoder )\n\t\t{\n\t\t\topus_encoder_destroy( voice.gs_encoder );\n\t\t\tvoice.gs_encoder = NULL;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( voice.encoder )\n\t\t{\n\t\t\topus_custom_encoder_destroy( voice.encoder );\n\t\t\tvoice.encoder = NULL;\n\t\t}\n\t}\n}\n\n/*\n=========================\nVoice_ShutdownCustomMode\n\nCleanup Opus Custom mode\n=========================\n*/\nstatic void Voice_ShutdownCustomMode( void )\n{\n\tif( voice.custom_mode )\n\t{\n\t\topus_custom_mode_destroy( voice.custom_mode );\n\t\tvoice.custom_mode = NULL;\n\t}\n}\n\n/*\n=========================\nVoice_InitGoldSrcMode\n\nInitialize GoldSrc voice mode\n=========================\n*/\nstatic qboolean Voice_InitGoldSrcMode( int quality )\n{\n\tvoice.goldsrc = true;\n\tvoice.autogain.block_size = 128;\n\tvoice.width = sizeof( int16_t );\n\tvoice.samplerate = GS_DEFAULT_SAMPLE_RATE;\n\tvoice.frame_size = GS_DEFAULT_FRAME_SIZE;\n\n\tif( !Voice_InitOpusDecoder())\n\t{\n\t\tCon_Printf( S_ERROR \"Can't create GoldSrc decoders, voice chat is disabled.\\n\" );\n\t\treturn false;\n\t}\n\n\tif( !Voice_InitOpusEncoder( quality ))\n\t{\n\t\tCon_Printf( S_WARN \"Other players will not be able to hear you.\\n\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=========================\nVoice_InitOpusCustomMode\n\nInitialize Opus Custom voice mode\n=========================\n*/\nstatic qboolean Voice_InitOpusCustomMode( int quality )\n{\n\tvoice.goldsrc = false;\n\tvoice.autogain.block_size = 128;\n\tvoice.samplerate = VOICE_OPUS_CUSTOM_SAMPLERATE;\n\tvoice.frame_size = VOICE_OPUS_CUSTOM_FRAME_SIZE;\n\tvoice.width = sizeof( int16_t );\n\n\tif( !Voice_InitCustomMode() || !Voice_InitOpusDecoder())\n\t{\n\t\tCon_Printf( S_ERROR \"Can't create Opus Custom decoders, voice chat is disabled.\\n\" );\n\t\treturn false;\n\t}\n\n\tif( !Voice_InitOpusEncoder( quality ))\n\t{\n\t\tCon_Printf( S_WARN \"Other players will not be able to hear you.\\n\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=========================\nVoice_ShutdownGoldSrcMode\n\nCleanup GoldSrc mode\n=========================\n*/\nstatic void Voice_ShutdownGoldSrcMode( void )\n{\n\tVoice_ShutdownOpusDecoder();\n\tVoice_ShutdownOpusEncoder();\n}\n\n/*\n=========================\nVoice_ShutdownOpusCustomMode\n\nCleanup Opus Custom mode\n=========================\n*/\nstatic void Voice_ShutdownOpusCustomMode( void )\n{\n\tVoice_ShutdownOpusDecoder();\n\tVoice_ShutdownOpusEncoder();\n\tVoice_ShutdownCustomMode();\n}\n\n/*\n===============================================================================\n\n\tVOICE PROCESSING\n\n===============================================================================\n*/\n\n/*\n=========================\nVoice_ApplyGainAdjust\n\nApply automatic gain control to voice samples\n=========================\n*/\nstatic void Voice_ApplyGainAdjust( int16_t *samples, int count, float scale )\n{\n\tfloat gain, modifiedMax;\n\tint   average, blockOffset = 0;\n\n\tfor( ;; )\n\t{\n\t\tint i, localMax = 0, localSum = 0;\n\t\tint blockSize = Q_min( count - blockOffset, voice.autogain.block_size );\n\n\t\tif( blockSize < 1 )\n\t\t\tbreak;\n\n\t\tfor( i = 0; i < blockSize; ++i )\n\t\t{\n\t\t\tint sample = samples[blockOffset + i];\n\t\t\tint absSample = abs( sample );\n\n\t\t\tif( absSample > localMax )\n\t\t\t\tlocalMax = absSample;\n\n\t\t\tlocalSum += absSample;\n\t\t\tgain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier;\n\t\t\tsamples[blockOffset + i] = bound( SHRT_MIN, (int)( sample * gain ), SHRT_MAX );\n\t\t}\n\n\t\tif( blockOffset % voice.autogain.block_size == 0 )\n\t\t{\n\t\t\taverage = localSum / blockSize;\n\t\t\tmodifiedMax = average + ( localMax - average ) * voice_avggain.value;\n\n\t\t\tvoice.autogain.current_gain = voice.autogain.next_gain * scale;\n\t\t\tvoice.autogain.next_gain = Q_min((float)SHRT_MAX / ( modifiedMax > 1 ? modifiedMax : 1 ), voice_maxgain.value ) * scale;\n\t\t\tif( blockSize > 1 )\n\t\t\t\tvoice.autogain.gain_multiplier = ( voice.autogain.next_gain - voice.autogain.current_gain ) / ( blockSize - 1 );\n\t\t\telse\n\t\t\t\tvoice.autogain.gain_multiplier = 0.0f;\n\t\t}\n\t\tblockOffset += blockSize;\n\t}\n}\n\n/*\n=========================\nVoice_GetOpusCompressedData\n\nGet compressed voice data for Opus Custom mode\n=========================\n*/\nstatic uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames )\n{\n\tuint ofs = 0, size = 0;\n\tuint frame_size_bytes = voice.frame_size * voice.width;\n\n\tif( voice.input_file )\n\t{\n\t\tuint   numbytes;\n\t\tdouble updateInterval, curtime = Sys_DoubleTime();\n\n\t\tupdateInterval = curtime - voice.start_time;\n\t\tvoice.start_time = curtime;\n\n\t\tnumbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;\n\t\tnumbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos );\n\t\tnumbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos );\n\n\t\tmemcpy( voice.input_buffer + voice.input_buffer_pos, voice.input_file->buffer + voice.input_file_pos, numbytes );\n\t\tvoice.input_buffer_pos += numbytes;\n\t\tvoice.input_file_pos += numbytes;\n\t}\n\n\tif( !voice.input_file )\n\t\tVoiceCapture_Lock( true );\n\n\tfor( ofs = 0; voice.input_buffer_pos - ofs >= frame_size_bytes && ofs <= voice.input_buffer_pos; ofs += frame_size_bytes )\n\t{\n\t\tint bytes;\n\n\t\tif( !voice.input_file )\n\t\t\tVoice_ApplyGainAdjust((int16_t *)( voice.input_buffer + ofs ), voice.frame_size, voice_transmit_scale.value );\n\n\t\tbytes = opus_custom_encode( voice.encoder, (const int16_t *)( voice.input_buffer + ofs ),\n\t\t\t\t\t    voice.frame_size, out + size + sizeof( uint16_t ), maxsize );\n\n\t\tif( bytes > 0 )\n\t\t{\n\t\t\t// write compressed frame size\n\t\t\t*((uint16_t *)&out[size] ) = LittleShort( bytes );\n\n\t\t\tsize += bytes + sizeof( uint16_t );\n\t\t\tmaxsize -= bytes + sizeof( uint16_t );\n\n\t\t\t( *frames )++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: failed to encode frame: %s\\n\", __func__, opus_strerror( bytes ));\n\t\t}\n\t}\n\n\t// did we compress anything? update counters\n\tif( ofs )\n\t{\n\t\tfs_offset_t remaining = voice.input_buffer_pos - ofs;\n\t\t// move remaining samples to the beginning of buffer\n\t\tmemmove( voice.input_buffer, voice.input_buffer + ofs, remaining );\n\t\tvoice.input_buffer_pos = remaining;\n\t}\n\n\tif( !voice.input_file )\n\t\tVoiceCapture_Lock( false );\n\n\treturn size;\n}\n\n/*\n=========================\nVoice_GetGSCompressedData\n\nGet compressed voice data for GoldSrc mode\n=========================\n*/\nstatic uint Voice_GetGSCompressedData( byte *out, uint maxsize, uint *frames )\n{\n\tuint ofs = 0, size = 0;\n\tconst uint      frame_size_samples = GS_DEFAULT_FRAME_SIZE;\n\tconst uint      frame_size_bytes = GS_DEFAULT_FRAME_SIZE * voice.width;\n\tstatic uint16_t sequence = 0;\n\n\tif( voice.input_file )\n\t{\n\t\tuint   numbytes;\n\t\tdouble updateInterval, curtime = Sys_DoubleTime();\n\n\t\tupdateInterval = curtime - voice.start_time;\n\t\tvoice.start_time = curtime;\n\n\t\tnumbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;\n\t\tnumbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos );\n\t\tnumbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos );\n\n\t\tmemcpy( voice.input_buffer + voice.input_buffer_pos, voice.input_file->buffer + voice.input_file_pos, numbytes );\n\t\tvoice.input_buffer_pos += numbytes;\n\t\tvoice.input_file_pos += numbytes;\n\t}\n\n\tif( !voice.input_file )\n\t\tVoiceCapture_Lock( true );\n\n\t*frames = 0;\n\n\twhile( voice.input_buffer_pos - ofs >= frame_size_bytes )\n\t{\n\t\tint     bytes;\n\t\tint     is_silence = 1;\n\t\tint16_t *samples = (int16_t *)( voice.input_buffer + ofs );\n\n\t\tfor( uint i = 0; i < frame_size_samples; ++i )\n\t\t{\n\t\t\tif( samples[i] != 0 )\n\t\t\t{\n\t\t\t\tis_silence = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif( is_silence )\n\t\t{\n\t\t\tofs += frame_size_bytes;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( !voice.input_file )\n\t\t\tVoice_ApplyGainAdjust( samples, frame_size_samples, voice_transmit_scale.value );\n\n\t\tbytes = opus_encode( voice.gs_encoder, samples,\n\t\t\t\t     frame_size_samples, out + size + 4, maxsize - 4 );\n\n\t\tif( bytes > 0 )\n\t\t{\n\t\t\t*(uint16_t *)( out + size ) = LittleShort( bytes );\n\t\t\t*(uint16_t *)( out + size + 2 ) = LittleShort( sequence++ );\n\n\t\t\tsize += bytes + sizeof( uint32_t );\n\t\t\tmaxsize -= bytes + sizeof( uint32_t );\n\n\t\t\t( *frames )++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: failed to encode frame: %s\\n\", __func__, opus_strerror( bytes ));\n\t\t}\n\n\t\tofs += frame_size_bytes;\n\t}\n\n\tif( ofs )\n\t{\n\t\tfs_offset_t remaining = voice.input_buffer_pos - ofs;\n\t\tmemmove( voice.input_buffer, voice.input_buffer + ofs, remaining );\n\t\tvoice.input_buffer_pos = remaining;\n\t}\n\n\tif( !voice.input_file )\n\t\tVoiceCapture_Lock( false );\n\n\treturn size;\n}\n\n/*\n=========================\nVoice_ProcessGSData\n\nProcess GoldSrc voice data and return number of samples\n=========================\n*/\nstatic int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )\n{\n\tuint32_t    crc_in_packet;\n\tuint32_t    crc;\n\tsize_t      offset;\n\tint16_t     pcm[GS_MAX_DECOMPRESSED_SAMPLES];\n\tsize_t      output_samples;\n\tuint16_t    sample_rate;\n\tuint8_t     vpc_type;\n\tuint16_t    data_len;\n\tOpusDecoder *decoder;\n\tsize_t      opus_offset;\n\tint decoded;\n\tsize_t      silence_samples;\n\tuint16_t    frame_size;\n\n\tif( !data || ent <= 0 || ent > cl.maxclients )\n\t\treturn 0;\n\n\tcrc_in_packet = LittleLong( *(uint32_t *)( data + size - sizeof( uint32_t )));\n\tcrc = CRC32_INIT_VALUE;\n\tCRC32_ProcessBuffer( &crc, data, size - sizeof( uint32_t ));\n\tcrc = CRC32_Final( crc );\n\n\tif( crc != crc_in_packet )\n\t{\n\t\tCon_Printf( S_WARN \"Voice packet CRC32 mismatch\\n\" );\n\t\treturn 0;\n\t}\n\n\toffset = sizeof( uint64_t );\n\toutput_samples = 0;\n\n\tif( offset >= size - sizeof( uint32_t ) || data[offset] != GS_VPC_SETSAMPLERATE )\n\t{\n\t\tCon_Printf( S_WARN \"Invalid voice packet type: %d\\n\", data[offset] );\n\t\treturn 0;\n\t}\n\toffset++;\n\n\tif( offset + sizeof( uint32_t ) > size - sizeof( uint32_t ))\n\t\treturn 0;\n\n\tsample_rate = LittleShort( *(uint16_t *)( data + offset ));\n\toffset += sizeof( uint16_t );\n\n\tvpc_type = data[offset++];\n\tdata_len = LittleShort( *(uint16_t *)( data + offset ));\n\toffset += sizeof( uint16_t );\n\n\tif( offset + data_len > size - sizeof( uint32_t ))\n\t{\n\t\tCon_Printf( S_WARN \"Voice packet data_len out of bounds\\n\" );\n\t\treturn 0;\n\t}\n\n\tif( vpc_type == GS_VPC_VDATA_OPUS_PLC )\n\t{\n\t\tdecoder = voice.gs_decoders[ent - 1];\n\t\tif( !decoder )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"No decoder available for entity %d\\n\", ent );\n\t\t\treturn 0;\n\t\t}\n\t\topus_offset = 0;\n\t\twhile( opus_offset + sizeof( uint32_t ) <= data_len )\n\t\t{\n\t\t\tframe_size = LittleShort( *(uint16_t *)( data + offset + opus_offset ));\n\t\t\topus_offset += sizeof( uint32_t );\n\t\t\t// if frame size is 0, it means silence\n\t\t\tif( frame_size == 0 )\n\t\t\t{\n\t\t\t\tif( output_samples + VOICE_DEFAULT_SILENCE_FRAME_SIZE > GS_MAX_DECOMPRESSED_SAMPLES )\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( S_WARN \"Voice buffer overflow\\n\" );\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tmemset( pcm + output_samples, 0, VOICE_DEFAULT_SILENCE_FRAME_SIZE * sizeof( int16_t ));\n\t\t\t\toutput_samples += VOICE_DEFAULT_SILENCE_FRAME_SIZE;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif( frame_size == 0xFFFF )\n\t\t\t{\n\t\t\t\topus_decoder_ctl( decoder, OPUS_RESET_STATE );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif( opus_offset + frame_size > data_len )\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"Opus frame size exceeds data length\\n\" );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tdecoded = opus_decode( decoder, data + offset + opus_offset, frame_size,\n\t\t\t\t\t       pcm + output_samples, voice.frame_size, 0 );\n\t\t\tif( decoded < 0 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"Opus decode error: %s\\n\", opus_strerror( decoded ));\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\toutput_samples += decoded;\n\t\t\topus_offset += frame_size;\n\t\t}\n\t}\n\telse if( vpc_type == GS_VPC_VDATA_SILENCE )\n\t{\n\t\tsilence_samples = data_len / 2;\n\t\tif( silence_samples > GS_MAX_DECOMPRESSED_SAMPLES )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"Silence data too large\\n\" );\n\t\t\treturn 0;\n\t\t}\n\t\tmemset( pcm, 0, silence_samples * sizeof( int16_t ));\n\t\toutput_samples = silence_samples;\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_WARN \"Unsupported voice data type: %d\\n\", vpc_type );\n\t\treturn 0;\n\t}\n\n\tif( output_samples > 0 )\n\t\tVoice_StartChannel( output_samples, (byte *)pcm, ent );\n\n\treturn output_samples;\n}\n\n/*\n=========================\nVoice_CreateGSVoicePacket\n\nCreate GoldSrc voice packet\n=========================\n*/\nstatic uint Voice_CreateGSVoicePacket( byte *out, const byte *voice_data, uint voice_size )\n{\n\tuint     offset = 0;\n\tuint32_t crc;\n\n\tif( !out || !voice_data || voice_size == 0 )\n\t{\n\t\tCon_Printf( S_WARN \"Invalid voice data for packet creation\\n\" );\n\t\treturn 0;\n\t}\n\n\tif( cls.steamid )\n\t{\n\t\tmemcpy( out + offset, cls.steamid, 8 );\n\t}\n\telse\n\t{\n\t\tmemset( out + offset, 0, 8 ); // fallback: 0\n\t}\n\n\toffset += sizeof( uint64_t );\n\n\tout[offset] = GS_VPC_SETSAMPLERATE;\n\toffset++;\n\n\t*(uint16_t *)( out + offset ) = LittleShort( GS_DEFAULT_SAMPLE_RATE );\n\toffset += sizeof( uint16_t );\n\n\tout[offset] = GS_VPC_VDATA_OPUS_PLC;\n\toffset++;\n\n\t*(uint16_t *)( out + offset ) = LittleShort( voice_size );\n\toffset += sizeof( uint16_t );\n\n\tmemcpy( out + offset, voice_data, voice_size );\n\toffset += voice_size;\n\n\tcrc = CRC32_INIT_VALUE;\n\tCRC32_ProcessBuffer( &crc, out, offset );\n\tcrc = CRC32_Final( crc );\n\t*(uint32_t *)( out + offset ) = LittleLong( crc );\n\toffset += sizeof( uint32_t );\n\n\treturn offset;\n}\n\n/*\n===============================================================================\n\n\tVOICE CHAT INTEGRATION\n\n===============================================================================\n*/\n\n/*\n=========================\nVoice_Status\n\nNotify user dll about voice transmission\n=========================\n*/\nstatic void Voice_Status( int entindex, qboolean bTalking )\n{\n\tif( cls.state == ca_active && clgame.dllFuncs.pfnVoiceStatus )\n\t\tclgame.dllFuncs.pfnVoiceStatus( entindex, bTalking );\n}\n\n/*\n=========================\nVoice_StatusTimeout\n\nWaits few milliseconds and if there was no\nvoice transmission, sends notification\n=========================\n*/\nstatic void Voice_StatusTimeout( voice_status_t *status, int entindex, double frametime )\n{\n\tif( status->talking_ack )\n\t{\n\t\tstatus->talking_timeout += frametime;\n\t\tif( status->talking_timeout > 0.2 )\n\t\t{\n\t\t\tstatus->talking_ack = false;\n\t\t\tVoice_Status( entindex, false );\n\t\t}\n\t}\n}\n\n/*\n=========================\nVoice_StatusAck\n\nSends notification to user dll and\nzeroes timeouts for this client\n=========================\n*/\nstatic void Voice_StatusAck( voice_status_t *status, int playerIndex )\n{\n\tif( !status->talking_ack )\n\t\tVoice_Status( playerIndex, true );\n\n\tstatus->talking_ack = true;\n\tstatus->talking_timeout = 0.0;\n}\n\n/*\n=========================\nVoice_IsRecording\n\nCheck if voice is currently recording\n=========================\n*/\nqboolean Voice_IsRecording( void )\n{\n\treturn voice.is_recording;\n}\n\n/*\n=========================\nVoice_RecordStop\n\nStop voice recording\n=========================\n*/\nvoid Voice_RecordStop( void )\n{\n\tif( voice.input_file )\n\t{\n\t\tFS_FreeSound( voice.input_file );\n\t\tvoice.input_file = NULL;\n\t}\n\n\tVoiceCapture_Activate( false );\n\tvoice.is_recording = false;\n\n\tVoice_Status( VOICE_LOCALCLIENT_INDEX, false );\n\n\tvoice.input_buffer_pos = 0;\n\tmemset( voice.input_buffer, 0, sizeof( voice.input_buffer ));\n}\n\n/*\n=========================\nVoice_RecordStart\n\nStart voice recording\n=========================\n*/\nvoid Voice_RecordStart( void )\n{\n\tVoice_RecordStop();\n\n\tif( !voice.initialized )\n\t\treturn;\n\n\tif( voice_inputfromfile.value )\n\t{\n\t\tvoice.input_file = FS_LoadSound( \"voice_input.wav\", NULL, 0 );\n\n\t\tif( voice.input_file )\n\t\t{\n\t\t\tSound_Process( &voice.input_file, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, SOUND_RESAMPLE );\n\t\t\tvoice.input_file_pos = 0;\n\n\t\t\tvoice.start_time = Sys_DoubleTime();\n\t\t\tvoice.is_recording = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tFS_FreeSound( voice.input_file );\n\t\t\tvoice.input_file = NULL;\n\t\t}\n\t}\n\n\tif( !Voice_IsRecording( ) && voice.device_opened )\n\t\tvoice.is_recording = VoiceCapture_Activate( true );\n\n\tif( Voice_IsRecording())\n\t\tVoice_Status( VOICE_LOCALCLIENT_INDEX, true );\n}\n\n/*\n=========================\nVoice_Disconnect\n\nWe're disconnected from server\nstop recording and notify user dlls\n=========================\n*/\nvoid Voice_Disconnect( void )\n{\n\tint i;\n\n\tVoice_RecordStop();\n\n\tif( voice.local.talking_ack )\n\t{\n\t\tVoice_Status( VOICE_LOOPBACK_INDEX, false );\n\t\tvoice.local.talking_ack = false;\n\t}\n\n\tfor( i = 1; i <= MAX_CLIENTS; i++ )\n\t\tVoice_Status( i, false );\n\n\tVoiceCapture_Shutdown();\n\tvoice.device_opened = false;\n\n\n\tVoice_ShutdownOpusDecoder();\n}\n\n/*\n=========================\nVoice_StartChannel\n\nFeed the decoded data to engine sound subsystem\n=========================\n*/\nstatic void Voice_StartChannel( uint samples, byte *data, int entnum )\n{\n\tSND_ForceInitMouth( entnum );\n\tS_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ));\n\tVoice_Status( entnum, true );\n}\n\n/*\n=========================\nVoice_StopChannel\n\nCalled by mixer when channel idles\n=========================\n*/\nvoid Voice_StopChannel( int entnum )\n{\n\tVoice_Status( entnum, false );\n}\n\n\n/*\n=========================\nVoice_AddIncomingData\n\nReceived encoded voice data, decode it\n=========================\n*/\nvoid Voice_LoopbackAck( void )\n{\n\tVoice_StatusAck( &voice.local, VOICE_LOOPBACK_INDEX );\n}\n\n/*\n=========================\nVoice_AddIncomingData\n\nReceived encoded voice data, decode it\n=========================\n*/\nvoid Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames )\n{\n\tconst int playernum = ent - 1;\n\tint samples = 0;\n\tint ofs = 0;\n\tvoice_status_t *status = NULL;\n\n\tif( !voice.initialized || !voice_enable.value )\n\t\treturn;\n\n\tif( voice.goldsrc )\n\t{\n\t\t// Voice_ProcessGSData handles Voice_StartChannel internally\n\t\tVoice_ProcessGSData( ent, (const uint8_t *)data, size );\n\t}\n\telse\n\t{\n\t\t// Validate player index and decoder\n\t\tif( playernum < 0 || playernum >= cl.maxclients || !voice.decoders[playernum] )\n\t\t\treturn;\n\n\t\t// decode frame by frame\n\t\tfor( ;; )\n\t\t{\n\t\t\tint      frame_samples;\n\t\t\tuint16_t compressed_size;\n\n\t\t\t// no compressed size mark\n\t\t\tif( ofs + sizeof( uint16_t ) > size )\n\t\t\t\tbreak;\n\n\t\t\tcompressed_size = *(const uint16_t *)( data + ofs );\n\t\t\tofs += sizeof( uint16_t );\n\n\t\t\t// no frame data\n\t\t\tif( ofs + compressed_size > size )\n\t\t\t\tbreak;\n\n\t\t\tframe_samples = opus_custom_decode( voice.decoders[playernum], data + ofs, compressed_size,\n\t\t\t\t\t\t\t    (int16_t *)voice.decompress_buffer + samples, voice.frame_size );\n\n\t\t\tofs += compressed_size;\n\t\t\tsamples += frame_samples;\n\t\t}\n\n\t\tif( samples > 0 )\n\t\t\tVoice_StartChannel( samples, voice.decompress_buffer, ent );\n\t}\n}\n\n/*\n=========================\nCL_AddVoiceToDatagram\n\nEncode our voice data and send it to server\n=========================\n*/\nvoid CL_AddVoiceToDatagram( void )\n{\n\tbyte buffer[VOICE_MAX_DATA_SIZE];\n\tuint size, frames;\n\n\tif( cls.state != ca_active || !voice.device_opened || !Voice_IsRecording())\n\t\treturn;\n\n\tif( voice.goldsrc )\n\t{\n\t\tif( !voice.gs_encoder )\n\t\t\treturn;\n\n\t\tsize = Voice_GetGSCompressedData( buffer, sizeof( buffer ), &frames );\n\t\tif( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 )\n\t\t{\n\t\t\tuint packet_size = Voice_CreateGSVoicePacket( voice.compress_buffer, buffer, size );\n\t\t\tMSG_BeginClientCmd( &cls.datagram, clc_voicedata );\n\t\t\tMSG_WriteShort( &cls.datagram, packet_size );\n\t\t\tMSG_WriteBytes( &cls.datagram, voice.compress_buffer, packet_size );\n\t\t}\n\t\treturn;\n\t}\n\n\tif( !voice.encoder )\n\t\treturn;\n\n\tsize = Voice_GetOpusCompressedData( voice.compress_buffer, sizeof( voice.compress_buffer ), &frames );\n\tif( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 )\n\t{\n\t\tMSG_BeginClientCmd( &cls.datagram, clc_voicedata );\n\t\tMSG_WriteByte( &cls.datagram, voice_loopback.value != 0 );\n\t\tMSG_WriteByte( &cls.datagram, frames );\n\t\tMSG_WriteShort( &cls.datagram, size );\n\t\tMSG_WriteBytes( &cls.datagram, voice.compress_buffer, size );\n\t}\n}\n\n/*\n=========================\nVoice_RegisterCvars\n\nRegister voice related cvars and commands\n=========================\n*/\nvoid Voice_RegisterCvars( void )\n{\n\tCvar_RegisterVariable( &voice_enable );\n\tCvar_RegisterVariable( &voice_loopback );\n\tCvar_RegisterVariable( &voice_scale );\n\tCvar_RegisterVariable( &voice_transmit_scale );\n\tCvar_RegisterVariable( &voice_avggain );\n\tCvar_RegisterVariable( &voice_maxgain );\n\tCvar_RegisterVariable( &voice_inputfromfile );\n}\n\n/*\n=========================\nVoice_Shutdown\n\nCompletely shutdown the voice subsystem\n=========================\n*/\nstatic void Voice_Shutdown( void )\n{\n\tint i;\n\n\tVoice_RecordStop();\n\n\tif( voice.goldsrc )\n\t\tVoice_ShutdownGoldSrcMode();\n\telse\n\t\tVoice_ShutdownOpusCustomMode();\n\n\tVoiceCapture_Shutdown();\n\n\tif( voice.local.talking_ack )\n\t\tVoice_Status( VOICE_LOOPBACK_INDEX, false );\n\n\tfor( i = 1; i <= MAX_CLIENTS; i++ )\n\t\tVoice_Status( i, false );\n\n\tvoice.initialized = false;\n\tvoice.is_recording = false;\n\tvoice.device_opened = false;\n\tvoice.goldsrc = false;\n\tvoice.start_time = 0.0;\n\tvoice.samplerate = 0;\n\tvoice.frame_size = 0;\n\tvoice.width = 0;\n\n\tvoice.input_buffer_pos = 0;\n\tvoice.input_file_pos = 0;\n\tmemset( voice.input_buffer, 0, sizeof( voice.input_buffer ));\n\tmemset( voice.compress_buffer, 0, sizeof( voice.compress_buffer ));\n\tmemset( voice.decompress_buffer, 0, sizeof( voice.decompress_buffer ));\n\tmemset( &voice.local, 0, sizeof( voice.local ));\n\tmemset( &voice.autogain, 0, sizeof( voice.autogain ));\n}\n\n/*\n=========================\nVoice_Idle\n\nRun timeout for clients\n=========================\n*/\nvoid Voice_Idle( double frametime )\n{\n\tint i;\n\n\tif( FBitSet( voice_enable.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( voice_enable.flags, FCVAR_CHANGED );\n\n\t\tif( voice_enable.value )\n\t\t{\n\t\t\tif( cls.state == ca_active )\n\t\t\t\tVoice_Init( voice.codec, voice.quality, false );\n\t\t}\n\t\telse\n\t\t\tVoice_Shutdown();\n\t}\n\n\t// update local player status first\n\tVoice_StatusTimeout( &voice.local, VOICE_LOOPBACK_INDEX, frametime );\n}\n\n/*\n=========================\nVoice_Init\n\nInitialize the voice subsystem\n=========================\n*/\nqboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit )\n{\n\tQ_strncpy( voice.codec, pszCodecName, sizeof( voice.codec ));\n\tvoice.quality = quality;\n\n\tif( !voice_enable.value )\n\t\treturn false;\n\n\tif( preinit )\n\t\treturn true;\n\n\tif( Voice_IsGoldSrcMode( pszCodecName ))\n\t{\n\t\tif( !Voice_InitGoldSrcMode( quality ))\n\t\t{\n\t\t\tVoice_Shutdown();\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if( Voice_IsOpusCustomMode( pszCodecName ))\n\t{\n\t\tif( !Voice_InitOpusCustomMode( quality ))\n\t\t{\n\t\t\tVoice_Shutdown();\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// unsupported codec\n\t\tCon_Printf( S_WARN \"Server requested unsupported voice codec: %s\\n\", pszCodecName );\n\t\tVoice_Shutdown();\n\t\treturn false;\n\t}\n\n\tvoice.device_opened = VoiceCapture_Init();\n\n\tif( !voice.device_opened )\n\t\tCon_Printf( S_WARN \"No microphone is available.\\n\" );\n\n\tvoice.initialized = true;\n\treturn true;\n}\n"
  },
  {
    "path": "engine/client/voice.h",
    "content": "/*\nvoice.h - voice chat implementation\nCopyright (C) 2022 Velaron\nCopyright (C) 2022 SNMetamorph\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef VOICE_H\n#define VOICE_H\n\n#include \"common.h\"\n#include \"protocol.h\" // MAX_CLIENTS\n#include \"sound.h\"\n\ntypedef struct OpusCustomEncoder OpusCustomEncoder;\ntypedef struct OpusCustomDecoder OpusCustomDecoder;\ntypedef struct OpusCustomMode OpusCustomMode;\ntypedef struct OpusEncoder OpusEncoder;\ntypedef struct OpusDecoder OpusDecoder;\n\n#define VOICE_LOOPBACK_INDEX    ( -2 )\n#define VOICE_LOCALCLIENT_INDEX ( -1 )\n\n#define VOICE_PCM_CHANNELS     1 // always mono\n#define VOICE_MAX_DATA_SIZE    8192\n#define VOICE_MAX_GS_DATA_SIZE 4096\n\n// never change these parameters when using opuscustom\n#define VOICE_OPUS_CUSTOM_SAMPLERATE 44100\n// must follow opus custom requirements\n// also be divisible with MAX_RAW_SAMPLES\n#define VOICE_OPUS_CUSTOM_FRAME_SIZE 1024\n#define VOICE_OPUS_CUSTOM_CODEC      \"opus_custom_44k_512\"\n\n// a1ba: do not change, we don't have any re-encoding support now\n#define VOICE_DEFAULT_CODEC              VOICE_OPUS_CUSTOM_CODEC\n#define VOICE_DEFAULT_SILENCE_FRAME_SIZE 160\n\n// GoldSrc voice configuration\n#define GS_MAX_DECOMPRESSED_SAMPLES 32768\n#define GS_DEFAULT_SAMPLE_RATE      24000\n#define GS_DEFAULT_FRAME_SIZE       480\n\n// VPC (Voice Packet Control) types\nenum gs_vpc_type\n{\n\tGS_VPC_VDATA_SILENCE  = 0,\n\tGS_VPC_VDATA_MILES    = 1,\n\tGS_VPC_VDATA_SPEEX    = 2,\n\tGS_VPC_VDATA_RAW      = 3,\n\tGS_VPC_VDATA_SILK     = 4,\n\tGS_VPC_VDATA_OPUS_PLC = 6,\n\tGS_VPC_SETSAMPLERATE  = 11,\n\tGS_VPC_UNKNOWN        = 10\n};\n\ntypedef struct voice_status_s\n{\n\tqboolean talking_ack;\n\tdouble   talking_timeout;\n} voice_status_t;\n\ntypedef struct voice_autogain_s\n{\n\tint   block_size;\n\tfloat current_gain;\n\tfloat next_gain;\n\tfloat gain_multiplier;\n} voice_autogain_t;\n\ntypedef struct voice_state_s\n{\n\tstring   codec;\n\tint      quality;\n\tqboolean goldsrc;\n\n\tqboolean initialized;\n\tqboolean is_recording;\n\tqboolean device_opened;\n\tdouble   start_time;\n\n\tvoice_status_t    local;\n\n\t// opus stuff\n\tOpusCustomMode    *custom_mode;\n\tOpusCustomEncoder *encoder;\n\tOpusCustomDecoder *decoders[MAX_CLIENTS];\n\n\tOpusEncoder *gs_encoder;\n\tOpusDecoder *gs_decoders[MAX_CLIENTS];\n\n\t// audio info\n\tuint width;\n\tuint samplerate;\n\tuint frame_size; // in samples\n\n\t// buffers\n\tbyte input_buffer[MAX_RAW_SAMPLES];\n\tbyte compress_buffer[MAX_RAW_SAMPLES];\n\tbyte decompress_buffer[MAX_RAW_SAMPLES];\n\tfs_offset_t      input_buffer_pos; // in bytes\n\n\t// input from file\n\twavdata_t        *input_file;\n\tfs_offset_t      input_file_pos; // in bytes\n\n\tvoice_autogain_t autogain;\n} voice_state_t;\n\nextern voice_state_t voice;\n\nextern convar_t voice_loopback;\n\nvoid CL_AddVoiceToDatagram( void );\nvoid Voice_RegisterCvars( void );\nqboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit );\nvoid Voice_Idle( double frametime );\nqboolean Voice_IsRecording( void );\nvoid Voice_RecordStop( void );\nvoid Voice_RecordStart( void );\nvoid Voice_Disconnect( void );\nvoid Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames );\nvoid Voice_StopChannel( int entnum );\nvoid Voice_LoopbackAck( void ); // sends VOICE_LOOPBACK_INDEX to client, gets disabled on timeout\n\n#endif // VOICE_H\n"
  },
  {
    "path": "engine/client/vox.h",
    "content": "/*\nvox.h - sentences vox private header\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef VOX_H\n#define VOX_H\n\n#define CVOXWORDMAX    64\n#define SENTENCE_INDEX -99999 // unique sentence index\n\ntypedef struct voxword_s\n{\n\tint    volume;       // increase percent, ie: 125 = 125% increase\n\tint    pitch;        // pitch shift up percent\n\tint    start;        // offset start of wave percent\n\tint    end;          // offset end of wave percent\n\tint    cbtrim;       // end of wave after being trimmed to 'end'\n\tint    fKeepCached;  // 1 if this word was already in cache before sentence referenced it\n\tint    samplefrac;   // if pitch shifting, this is position into wav * 256\n\tint    timecompress; // % of wave to skip during playback (causes no pitch shift)\n\tsfx_t *sfx;          // name and cache pointer\n} voxword_t;\n\nstruct channel_s;\nvoid VOX_LoadWord( struct channel_s *pchan );\nvoid VOX_FreeWord( struct channel_s *pchan );\n\n#endif\n"
  },
  {
    "path": "engine/common/base_cmd.c",
    "content": "/*\nbase_cmd.c - command & cvar hashmap. Insipred by Doom III\nCopyright (C) 2016 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"base_cmd.h\"\n#include \"cdll_int.h\"\n\n#define HASH_SIZE 64 // 64 * 4 * 4 == 1024 bytes\n\ntypedef struct base_command_hashmap_s base_command_hashmap_t;\n\nstruct base_command_hashmap_s\n{\n\tbase_command_t         *basecmd; // base command: cvar, alias or command\n\tbase_command_hashmap_t *next;\n\tbase_command_type_e     type;    // type for faster searching\n\tchar                    name[]; // key for searching\n};\n\nstatic base_command_hashmap_t *hashed_cmds[HASH_SIZE];\nstatic poolhandle_t basecmd_pool;\n\n#define BaseCmd_HashKey( x ) COM_HashKey( name, HASH_SIZE )\n\n/*\n============\nBaseCmd_FindInBucket\n\nFind base command in bucket\n============\n*/\nstatic base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name )\n{\n\tbase_command_hashmap_t *i;\n\n\tfor( i = bucket; i != NULL; i = i->next )\n\t{\n\t\tint cmp;\n\n\t\tif( i->type != type )\n\t\t\tcontinue;\n\n\t\tcmp = Q_stricmp( i->name, name );\n\n\t\tif( cmp < 0 )\n\t\t\tcontinue;\n\n\t\tif( cmp > 0 )\n\t\t\tbreak;\n\n\t\treturn i;\n\t}\n\n\treturn NULL;\n}\n\n/*\n============\nBaseCmd_GetBucket\n\nGet bucket which contain basecmd by given name\n============\n*/\nstatic base_command_hashmap_t *BaseCmd_GetBucket( const char *name )\n{\n\treturn hashed_cmds[ BaseCmd_HashKey( name ) ];\n}\n\n/*\n============\nBaseCmd_Find\n\nFind base command in hashmap\n============\n*/\nbase_command_t *BaseCmd_Find( base_command_type_e type, const char *name )\n{\n\tbase_command_hashmap_t *base = BaseCmd_GetBucket( name );\n\tbase_command_hashmap_t *found = BaseCmd_FindInBucket( base, type, name );\n\n\tif( found )\n\t\treturn found->basecmd;\n\treturn NULL;\n}\n\n/*\n============\nBaseCmd_Find\n\nFind every type of base command and write into arguments\n============\n*/\nvoid BaseCmd_FindAll( const char *name, cmd_t **cmd, cmdalias_t **alias, convar_t **cvar )\n{\n\tbase_command_hashmap_t *base = BaseCmd_GetBucket( name );\n\tbase_command_hashmap_t *i = base;\n\n\t*cmd = NULL;\n\t*alias = NULL;\n\t*cvar = NULL;\n\n\tfor( ; i; i = i->next )\n\t{\n\t\tint cmp = Q_stricmp( i->name, name );\n\n\t\tif( cmp < 0 )\n\t\t\tcontinue;\n\n\t\tif( cmp > 0 )\n\t\t\tbreak;\n\n\t\tswitch( i->type )\n\t\t{\n\t\tcase HM_CMD:\n\t\t\t*cmd = (cmd_t *)i->basecmd;\n\t\t\tbreak;\n\t\tcase HM_CMDALIAS:\n\t\t\t*alias = (cmdalias_t *)i->basecmd;\n\t\t\tbreak;\n\t\tcase HM_CVAR:\n\t\t\t*cvar = (convar_t *)i->basecmd;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n============\nBaseCmd_Insert\n\nAdd new typed base command to hashmap\n============\n*/\nvoid BaseCmd_Insert( base_command_type_e type, base_command_t *basecmd, const char *name )\n{\n\tbase_command_hashmap_t *elem, *cur, *find;\n\tuint hash = BaseCmd_HashKey( name );\n\tsize_t len = Q_strlen( name );\n\n\telem = Mem_Malloc( basecmd_pool, sizeof( base_command_hashmap_t ) + len + 1 );\n\telem->basecmd = basecmd;\n\telem->type = type;\n\tQ_strncpy( elem->name, name, len + 1 );\n\n\t// link the variable in alphanumerical order\n\tfor( cur = NULL, find = hashed_cmds[hash];\n\t\t  find && Q_stricmp( find->name, elem->name ) < 0;\n\t\t  cur = find, find = find->next );\n\n\tif( cur ) cur->next = elem;\n\telse hashed_cmds[hash] = elem;\n\n\telem->next = find;\n}\n\n/*\n============\nBaseCmd_Remove\n\nRemove base command from hashmap\n============\n*/\nvoid BaseCmd_Remove( base_command_type_e type, const char *name )\n{\n\tuint hash = BaseCmd_HashKey( name );\n\tbase_command_hashmap_t *i, *prev;\n\n\tfor( prev = NULL, i = hashed_cmds[hash]; i != NULL; prev = i, i = i->next )\n\t{\n\t\tint cmp;\n\n\t\tif( i->type != type )\n\t\t\tcontinue;\n\n\t\tcmp = Q_stricmp( i->name, name );\n\n\t\tif( cmp < 0 )\n\t\t\tcontinue;\n\n\t\tif( cmp > 0 )\n\t\t\ti = NULL;\n\n\t\tbreak;\n\t}\n\n\tif( !i )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: Couldn't find %s in buckets\\n\", __func__, name );\n\t\treturn;\n\t}\n\n\tif( prev )\n\t\tprev->next = i->next;\n\telse\n\t\thashed_cmds[hash] = i->next;\n\n\tZ_Free( i );\n}\n\n/*\n============\nBaseCmd_Init\n\ninitialize base command hashmap system\n============\n*/\nvoid BaseCmd_Init( void )\n{\n\tbasecmd_pool = Mem_AllocPool( \"BaseCmd\" );\n\tmemset( hashed_cmds, 0, sizeof( hashed_cmds ) );\n}\n\nvoid BaseCmd_Shutdown( void )\n{\n\tMem_FreePool( &basecmd_pool );\n}\n\n/*\n============\nBaseCmd_Stats_f\n\n============\n*/\nvoid BaseCmd_Stats_f( void )\n{\n\tint i, minsize = 99999, maxsize = -1, empty = 0;\n\n\tfor( i = 0; i < HASH_SIZE; i++ )\n\t{\n\t\tbase_command_hashmap_t *hm;\n\t\tint len = 0;\n\n\t\t// count bucket length\n\t\tfor( hm = hashed_cmds[i]; hm; hm = hm->next, len++ );\n\n\t\tif( len == 0 )\n\t\t{\n\t\t\tempty++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( len < minsize )\n\t\t\tminsize = len;\n\n\t\tif( len > maxsize )\n\t\t\tmaxsize = len;\n\n\t}\n\n\tCon_Printf( \"min length: %d, max length: %d, empty: %d\\n\", minsize, maxsize, empty );\n}\n\ntypedef struct\n{\n\tqboolean valid;\n\tint lookups;\n} basecmd_test_stats_t;\n\nstatic void BaseCmd_CheckCvars( const char *key, const char *value, const void *unused, void *ptr )\n{\n\tbasecmd_test_stats_t *stats = ptr;\n\n\tstats->lookups++;\n\tif( !BaseCmd_Find( HM_CVAR, key ))\n\t{\n\t\tCon_Printf( \"Cvar %s is missing in basecmd\\n\", key );\n\t\tstats->valid = false;\n\t}\n}\n\n/*\n============\nBaseCmd_Stats_f\n\ntesting order matches cbuf execute\n============\n*/\nvoid BaseCmd_Test_f( void )\n{\n\tbasecmd_test_stats_t stats;\n\tdouble start, end, dt;\n\tint i;\n\n\tstats.valid = true;\n\tstats.lookups = 0;\n\n\tstart = Sys_DoubleTime() * 1000;\n\n\tfor( i = 0; i < 1000; i++ )\n\t{\n\t\tcmdalias_t *a;\n\t\tvoid *cmd;\n\n\t\t// Cmd_LookupCmds don't allows to check alias, so just iterate\n\t\tfor( a = Cmd_AliasGetList(); a; a = a->next, stats.lookups++ )\n\t\t{\n\t\t\tif( !BaseCmd_Find( HM_CMDALIAS, a->name ))\n\t\t\t{\n\t\t\t\tCon_Printf( \"Alias %s is missing in basecmd\\n\", a->name );\n\t\t\t\tstats.valid = false;\n\t\t\t}\n\t\t}\n\n\t\tfor( cmd = Cmd_GetFirstFunctionHandle(); cmd;\n\t\t\t cmd = Cmd_GetNextFunctionHandle( cmd ), stats.lookups++ )\n\t\t{\n\t\t\tif( !BaseCmd_Find( HM_CMD, Cmd_GetName( cmd )))\n\t\t\t{\n\t\t\t\tCon_Printf( \"Command %s is missing in basecmd\\n\", Cmd_GetName( cmd ));\n\t\t\t\tstats.valid = false;\n\t\t\t}\n\t\t}\n\n\t\tCvar_LookupVars( 0, NULL, &stats.valid, (setpair_t)BaseCmd_CheckCvars );\n\t}\n\n\tend = Sys_DoubleTime() * 1000;\n\n\tdt = end - start;\n\n\tif( !stats.valid )\n\t\tCon_Printf( \"BaseCmd is valid\\n\" );\n\n\tCon_Printf( \"Test took %.3f ms, %d lookups, %.3f us/lookup\\n\", dt, stats.lookups, dt / stats.lookups * 1000 );\n\n\tBaseCmd_Stats_f();\n}\n"
  },
  {
    "path": "engine/common/base_cmd.h",
    "content": "/*\nbase_cmd.h - command & cvar hashmap. Insipred by Doom III\nCopyright (C) 2016 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef BASE_CMD_H\n#define BASE_CMD_H\n\n#define XASH_HASHED_VARS\n\n#ifdef XASH_HASHED_VARS\n\n#include \"common.h\"\n#include \"cdll_int.h\"\n\ntypedef enum base_command_type\n{\n\tHM_DONTCARE = 0,\n\tHM_CVAR,\n\tHM_CMD,\n\tHM_CMDALIAS\n} base_command_type_e;\n\ntypedef void base_command_t;\n\nvoid BaseCmd_Init( void );\nvoid BaseCmd_Shutdown( void );\nbase_command_t *BaseCmd_Find( base_command_type_e type, const char *name );\nvoid BaseCmd_FindAll( const char *name, cmd_t **cmd, cmdalias_t **alias, convar_t **cvar );\nvoid BaseCmd_Insert ( base_command_type_e type, base_command_t *basecmd, const char *name );\nvoid BaseCmd_Remove ( base_command_type_e type, const char *name );\nvoid BaseCmd_Stats_f( void ); // to be registered later\nvoid BaseCmd_Test_f( void ); // to be registered later\n\n#endif // XASH_HASHED_VARS\n\n#endif // BASE_CMD_H\n"
  },
  {
    "path": "engine/common/cfgscript.c",
    "content": "/*\ncfgscript.c - \"Valve script\" parsing routines\nCopyright (C) 2016 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n\ntypedef enum\n{\n\tT_NONE = 0,\n\tT_BOOL,\n\tT_NUMBER,\n\tT_LIST,\n\tT_STRING,\n\tT_COUNT\n} cvartype_t;\n\nstatic const char *const cvartypes[] = { NULL, \"BOOL\", \"NUMBER\", \"LIST\", \"STRING\" };\n\ntypedef struct parserstate_s\n{\n\tchar\t\t*buf;\n\tchar\t\ttoken[MAX_STRING];\n\tconst char\t*filename;\n} parserstate_t;\n\ntypedef struct scrvardef_s\n{\n\tchar\t\tname[MAX_STRING];\n\tchar\t\tvalue[MAX_STRING];\n\tchar\t\tdesc[MAX_STRING];\n\tfloat\t\tfMin, fMax;\n\tcvartype_t\ttype;\n\tint\t\tflags;\n\tqboolean\t\tfHandled;\n} scrvardef_t;\n\n/*\n===================\nCSCR_ExpectString\n\nReturn true if next token is pExpext and skip it\n===================\n*/\nstatic qboolean CSCR_ExpectString( parserstate_t *ps, const char *pExpect, qboolean skip, qboolean error )\n{\n\tchar\t*tmp = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token ));\n\n\tif( !Q_stricmp( ps->token, pExpect ) )\n\t{\n\t\tps->buf = tmp;\n\t\treturn true;\n\t}\n\n\tif( skip ) ps->buf = tmp;\n\tif( error ) Con_DPrintf( S_ERROR \"Syntax error in %s: got \\\"%s\\\" instead of \\\"%s\\\"\\n\", ps->filename, ps->token, pExpect );\n\n\treturn false;\n}\n\n/*\n===================\nCSCR_ParseType\n\nDetermine script variable type\n===================\n*/\nstatic cvartype_t CSCR_ParseType( parserstate_t *ps )\n{\n\tint\ti;\n\n\tfor( i = 1; i < T_COUNT; i++ )\n\t{\n\t\tif( CSCR_ExpectString( ps, cvartypes[i], false, false ))\n\t\t\treturn i;\n\t}\n\n\tCon_DPrintf( S_ERROR \"Cannot parse %s: Bad type %s\\n\", ps->filename, ps->token );\n\treturn T_NONE;\n}\n\n\n\n/*\n=========================\nCSCR_ParseSingleCvar\n=========================\n*/\nstatic qboolean CSCR_ParseSingleCvar( parserstate_t *ps, scrvardef_t *result )\n{\n\t// read the name\n\tps->buf = COM_ParseFile( ps->buf, result->name, sizeof( result->name ));\n\n\tif( !CSCR_ExpectString( ps, \"{\", false, true ))\n\t\treturn false;\n\n\t// read description\n\tps->buf = COM_ParseFile( ps->buf, result->desc, sizeof( result->desc ));\n\n\tif( !CSCR_ExpectString( ps, \"{\", false, true ))\n\t\treturn false;\n\n\tresult->type = CSCR_ParseType( ps );\n\n\tswitch( result->type )\n\t{\n\tcase T_BOOL:\n\t\t// bool only has description\n\t\tif( !CSCR_ExpectString( ps, \"}\", false, true ))\n\t\t\treturn false;\n\t\tbreak;\n\tcase T_NUMBER:\n\t\t// min\n\t\tps->buf = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token ));\n\t\tresult->fMin = Q_atof( ps->token );\n\n\t\t// max\n\t\tps->buf = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token ));\n\t\tresult->fMax = Q_atof( ps->token );\n\n\t\tif( !CSCR_ExpectString( ps, \"}\", false, true ))\n\t\t\treturn false;\n\t\tbreak;\n\tcase T_STRING:\n\t\tif( !CSCR_ExpectString( ps, \"}\", false, true ))\n\t\t\treturn false;\n\t\tbreak;\n\tcase T_LIST:\n\t\twhile( !CSCR_ExpectString( ps, \"}\", true, false ))\n\t\t{\n\t\t\t// read token for each item here\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\tif( !CSCR_ExpectString( ps, \"{\", false, true ))\n\t\treturn false;\n\n\t// default value\n\tps->buf = COM_ParseFile( ps->buf, result->value, sizeof( result->value ));\n\n\tif( !CSCR_ExpectString( ps, \"}\", false, true ))\n\t\treturn false;\n\n\tif( CSCR_ExpectString( ps, \"SetInfo\", false, false ))\n\t\tresult->flags |= FCVAR_USERINFO;\n\n\tif( !CSCR_ExpectString( ps, \"}\", false, true ))\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n======================\nCSCR_ParseHeader\n\nCheck version and seek to first cvar name\n======================\n*/\nstatic qboolean CSCR_ParseHeader( parserstate_t *ps )\n{\n\tif( !CSCR_ExpectString( ps, \"VERSION\", false, true ))\n\t\treturn false;\n\n\t// Parse in the version #\n\t// Get the first token.\n\tps->buf = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token ));\n\n\tif( Q_atof( ps->token ) != 1 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"File %s has wrong version %s!\\n\", ps->filename, ps->token );\n\t\treturn false;\n\t}\n\n\tif( !CSCR_ExpectString( ps, \"DESCRIPTION\", false, true ))\n\t\treturn false;\n\n\tps->buf = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token ));\n\n\tif( Q_stricmp( ps->token, \"INFO_OPTIONS\") && Q_stricmp( ps->token, \"SERVER_OPTIONS\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"DESCRIPTION must be INFO_OPTIONS or SERVER_OPTIONS\\n\");\n\t\treturn false;\n\t}\n\n\tif( !CSCR_ExpectString( ps, \"{\", false, true ))\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n==============\nCSCR_ParseFile\n\ngeneric scr parser\nwill callback on each scrvardef_t\n==============\n*/\nstatic int CSCR_ParseFile( const char *scriptfilename,\n\tvoid (*callback)( scrvardef_t *var, void * ), void *userdata )\n{\n\tparserstate_t\tstate = { 0 };\n\tqboolean\t\tsuccess = false;\n\tint\t\tcount = 0;\n\tfs_offset_t\t\tlength = 0;\n\tchar\t\t*start;\n\n\tstate.filename = scriptfilename;\n\tstate.buf = start = (char *)FS_LoadFile( scriptfilename, &length, true );\n\n\tif( !state.buf || !length )\n\t\treturn 0;\n\n\tCon_DPrintf( \"Reading config script file %s\\n\", scriptfilename );\n\n\tif( !CSCR_ParseHeader( &state ))\n\t\tgoto finish;\n\n\twhile( !CSCR_ExpectString( &state, \"}\", false, false ))\n\t{\n\t\tscrvardef_t\tvar = { 0 };\n\n\t\t// Create a new object\n\t\tif( CSCR_ParseSingleCvar( &state, &var ) )\n\t\t{\n\t\t\tcallback( &var, userdata );\n\t\t\tcount++;\n\t\t}\n\t\telse\n\t\t\tbreak;\n\n\t\tif( count > 1024 )\n\t\t\tbreak;\n\t}\n\n\tif( COM_ParseFile( state.buf, state.token, sizeof( state.token )))\n\t\tCon_DPrintf( S_ERROR \"Got extra tokens!\\n\" );\n\telse success = true;\nfinish:\n\tif( !success )\n\t{\n\t\tstate.token[sizeof( state.token ) - 1] = 0;\n\t\tif( start && state.buf )\n\t\t\tCon_DPrintf( S_ERROR \"Parse error in %s, byte %d, token %s\\n\", scriptfilename, (int)( state.buf - start ), state.token );\n\t\telse Con_DPrintf( S_ERROR \"Parse error in %s, token %s\\n\", scriptfilename, state.token );\n\t}\n\n\tif( start ) Mem_Free( start );\n\n\treturn count;\n}\n\nstatic void CSCR_WriteVariableToFile( scrvardef_t *var, void *file )\n{\n\tfile_t   *cfg  = (file_t*)file;\n\tconvar_t *cvar = Cvar_FindVar( var->name );\n\n\tif( cvar && !FBitSet( cvar->flags, FCVAR_SERVER|FCVAR_ARCHIVE ))\n\t{\n\t\t// cvars will be placed in game.cfg and restored on map start\n\t\tif( var->flags & FCVAR_USERINFO )\n\t\t\tFS_Printf( cfg, \"setinfo %s \\\"%s\\\"\\n\", var->name, cvar->string );\n\t\telse FS_Printf( cfg, \"%s \\\"%s\\\"\\n\", var->name, cvar->string );\n\t}\n}\n\n/*\n======================\nCSCR_WriteGameCVars\n\nPrint all cvars declared in script to game.cfg file\n======================\n*/\nint CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename )\n{\n\treturn CSCR_ParseFile( scriptfilename, CSCR_WriteVariableToFile, cfg );\n}\n\nstatic void CSCR_RegisterVariable( scrvardef_t *var, void *unused )\n{\n\tif( !Cvar_FindVar( var->name ))\n\t\tCvar_Get( var->name, var->value, var->flags|FCVAR_TEMPORARY, var->desc );\n}\n\n/*\n======================\nCSCR_LoadDefaultCVars\n\nRegister all cvars declared in config file and set default values\n======================\n*/\nint CSCR_LoadDefaultCVars( const char *scriptfilename )\n{\n\treturn CSCR_ParseFile( scriptfilename, CSCR_RegisterVariable, NULL );\n}\n"
  },
  {
    "path": "engine/common/cmd.c",
    "content": "/*\ncmd.c - script command processing module\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"server.h\"\n#include \"base_cmd.h\"\n\n#define MAX_CMD_BUFFER\t32768\n#define MAX_CMD_LINE\t2048\n#define MAX_ALIAS_NAME\t32\n\ntypedef struct\n{\n\tbyte *const data;\n\tconst int maxsize;\n\tint cursize;\n} cmdbuf_t;\n\nstatic qboolean cmd_wait;\nstatic byte     cmd_text_buf[MAX_CMD_BUFFER];\nstatic byte     filteredcmd_text_buf[MAX_CMD_BUFFER];\nstatic cmdbuf_t cmd_text =\n{\n\t.data = cmd_text_buf,\n\t.maxsize = ARRAYSIZE( cmd_text_buf ),\n};\nstatic cmdbuf_t filteredcmd_text =\n{\n\t.data = filteredcmd_text_buf,\n\t.maxsize = ARRAYSIZE( filteredcmd_text_buf ),\n};\nstatic cmdalias_t *cmd_alias;\nstatic uint cmd_condition;\nstatic int  cmd_condlevel;\nstatic qboolean cmd_currentCommandIsPrivileged;\nstatic poolhandle_t cmd_pool;\n\nstatic void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPrivileged );\n\n/*\n=============================================================================\n\n\t\t\tCOMMAND BUFFER\n\n=============================================================================\n*/\n\n/*\n============\nCbuf_Clear\n============\n*/\nvoid Cbuf_Clear( void )\n{\n\tmemset( cmd_text.data, 0, cmd_text.maxsize );\n\tmemset( filteredcmd_text.data, 0, filteredcmd_text.maxsize );\n\tcmd_text.cursize = filteredcmd_text.cursize = 0;\n}\n\n/*\n============\nCbuf_GetSpace\n============\n*/\nstatic void *Cbuf_GetSpace( cmdbuf_t *buf, int length )\n{\n\tvoid    *data;\n\n\tif(( buf->cursize + length ) > buf->maxsize )\n\t{\n\t\tbuf->cursize = 0;\n\t\tHost_Error( \"%s: overflow\\n\", __func__ );\n\t}\n\n\tdata = buf->data + buf->cursize;\n\tbuf->cursize += length;\n\n\treturn data;\n}\n\nstatic void Cbuf_AddTextToBuffer( cmdbuf_t *buf, const char *text )\n{\n\tint l = Q_strlen( text );\n\n\tif(( buf->cursize + l ) >= buf->maxsize )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: overflow\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tmemcpy( Cbuf_GetSpace( buf, l ), text, l );\n}\n\n/*\n============\nCbuf_AddText\n\nAdds command text at the end of the buffer\n============\n*/\nvoid Cbuf_AddText( const char *text )\n{\n\tCbuf_AddTextToBuffer( &cmd_text, text );\n}\n\nvoid Cbuf_AddTextf( const char *fmt, ... )\n{\n\tva_list va;\n\tchar buf[MAX_VA_STRING];\n\n\tva_start( va, fmt );\n\tQ_vsnprintf( buf, sizeof( buf ), fmt, va );\n\tva_end( va );\n\n\tCbuf_AddText( buf );\n}\n\n/*\n============\nCbuf_AddFilteredText\n============\n*/\nvoid Cbuf_AddFilteredText( const char *text )\n{\n\tCbuf_AddTextToBuffer( &filteredcmd_text, text );\n}\n\n/*\n============\nCbuf_InsertText\n\nAdds command text immediately after the current command\n============\n*/\nstatic void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text, size_t len, size_t requested_len )\n{\n\tif(( buf->cursize + requested_len ) >= buf->maxsize )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: overflow\\n\", __func__ );\n\t}\n\telse\n\t{\n\t\tmemmove( buf->data + len, buf->data, buf->cursize );\n\t\tmemcpy( buf->data, text, len );\n\t\tbuf->cursize += len;\n\t}\n}\n\nvoid Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len )\n{\n\t// sometimes we need to insert more data than we have\n\t// but also prevent overflow\n\tCbuf_InsertTextToBuffer( &cmd_text, text, len, requested_len );\n}\n\nvoid Cbuf_InsertText( const char *text )\n{\n\tsize_t l = Q_strlen( text );\n\tCbuf_InsertTextToBuffer( &cmd_text, text, l, l );\n}\n\n/*\n============\nCbuf_Execute\n============\n*/\nstatic void Cbuf_ExecuteCommandsFromBuffer( cmdbuf_t *buf, qboolean isPrivileged, int cmdsToExecute )\n{\n\tchar\t*text;\n\tchar\tline[MAX_CMD_LINE];\n\tint\ti, quotes;\n\tchar\t*comment;\n\n\twhile( buf->cursize )\n\t{\n\t\tif( cmd_wait > 0 )\n\t\t{\n\t\t\t// skip out while text still remains in buffer,\n\t\t\t// leaving it for next frame\n\t\t\tcmd_wait--;\n\t\t\tbreak;\n\t\t}\n\n\t\t// limit amount of commands that can be issued\n\t\tif( cmdsToExecute >= 0 )\n\t\t{\n\t\t\tif( !cmdsToExecute-- )\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// find a \\n or ; line break\n\t\ttext = (char *)buf->data;\n\n\t\tquotes = false;\n\t\tcomment = NULL;\n\n\t\tfor( i = 0; i < buf->cursize; i++ )\n\t\t{\n\t\t\tif( !comment )\n\t\t\t{\n\t\t\t\tif( text[i] == '\"' ) quotes = !quotes;\n\n\t\t\t\tif( quotes )\n\t\t\t\t{\n\t\t\t\t\t// make sure i doesn't get > cursize which causes a negative size in memmove, which is fatal --blub\n\t\t\t\t\tif( i < ( buf->cursize - 1 ) && ( text[i+0] == '\\\\' && (text[i+1] == '\"' || text[i+1] == '\\\\')))\n\t\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( text[i+0] == '/' && text[i+1] == '/' && ( i == 0 || (byte)text[i - 1] <= ' ' ))\n\t\t\t\t\t\tcomment = &text[i];\n\t\t\t\t\tif( text[i] == ';' ) break; // don't break if inside a quoted string or comment\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( text[i] == '\\n' || text[i] == '\\r' )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( i >= ( MAX_CMD_LINE - 1 ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: command string owerflow\\n\", __func__ );\n\t\t\tline[0] = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmemcpy( line, text, comment ? (comment - text) : i );\n\t\t\tline[comment ? (comment - text) : i] = 0;\n\t\t}\n\n\t\t// delete the text from the command buffer and move remaining commands down\n\t\t// this is necessary because commands (exec) can insert data at the\n\t\t// beginning of the text buffer\n\t\tif( i == buf->cursize )\n\t\t{\n\t\t\tbuf->cursize = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti++;\n\t\t\tbuf->cursize -= i;\n\t\t\tmemmove( buf->data, text + i, buf->cursize );\n\t\t}\n\n\t\t// execute the command line\n\t\tCmd_ExecuteStringWithPrivilegeCheck( line, isPrivileged );\n\t}\n}\n\n/*\n============\nCbuf_Execute\n============\n*/\nvoid Cbuf_Execute( void )\n{\n\tCbuf_ExecuteCommandsFromBuffer( &cmd_text, true, -1 );\n\n\t// a1ba: goldsrc limits unprivileged commands per frame to 1 here\n\t// I don't see any sense in restricting that at this moment\n\t// but in future we may limit this\n\n\t// a1ba: there is little to no sense limit privileged commands in\n\t// local game, as client runs server code anyway\n\t// do this for singleplayer only though, to make it easier to catch\n\t// possible bugs during local multiplayer testing\n\tCbuf_ExecuteCommandsFromBuffer( &filteredcmd_text, SV_Active() && SV_GetMaxClients() == 1, -1 );\n}\n\n/*\n===============\nCbuf_ExecStuffCmds\n\nexecute commandline\n===============\n*/\nvoid Cbuf_ExecStuffCmds( void )\n{\n\tchar\tbuild[MAX_CMD_LINE]; // this is for all commandline options combined (and is bounds checked)\n\tint\ti, j, l = 0;\n\n\t// no reason to run the commandline arguments twice\n\tif( !host.stuffcmds_pending )\n\t\treturn;\n\tbuild[0] = 0;\n\n\tfor( i = 0; i < host.argc; i++ )\n\t{\n\t\tif( host.argv[i] && host.argv[i][0] == '+' && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ) && l + Q_strlen( host.argv[i] ) - 1 <= sizeof( build ) - 1 )\n\t\t{\n\t\t\tj = 1;\n\n\t\t\twhile( host.argv[i][j] )\n\t\t\t\tbuild[l++] = host.argv[i][j++];\n\n\t\t\tfor( i++; i < host.argc; i++ )\n\t\t\t{\n\t\t\t\tif( !host.argv[i] ) continue;\n\t\t\t\tif(( host.argv[i][0] == '+' || host.argv[i][0] == '-' ) && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ))\n\t\t\t\t\tbreak;\n\t\t\t\tif( l + Q_strlen( host.argv[i] ) + 4 > sizeof( build ) - 1 )\n\t\t\t\t\tbreak;\n\t\t\t\tbuild[l++] = ' ';\n\n\t\t\t\tif( Q_strchr( host.argv[i], ' ' ))\n\t\t\t\t\tbuild[l++] = '\\\"';\n\n\t\t\t\tfor( j = 0; host.argv[i][j]; j++ )\n\t\t\t\t\tbuild[l++] = host.argv[i][j];\n\n\t\t\t\tif( Q_strchr( host.argv[i], ' ' ))\n\t\t\t\t\tbuild[l++] = '\\\"';\n\t\t\t}\n\t\t\tbuild[l++] = '\\n';\n\t\t\ti--;\n\t\t}\n\t}\n\n\t// now terminate the combined string and prepend it to the command buffer\n\t// we already reserved space for the terminator\n\tbuild[l++] = 0;\n\tCbuf_InsertText( build );\n\tCbuf_Execute(); // apply now\n\n\t// this command can be called only from .rc\n\tCmd_RemoveCommand( \"stuffcmds\" );\n\thost.stuffcmds_pending = false;\n}\n\n/*\n==============================================================================\n\n\t\t\tSCRIPT COMMANDS\n\n==============================================================================\n*/\nqboolean Cmd_CurrentCommandIsPrivileged( void )\n{\n\treturn cmd_currentCommandIsPrivileged;\n}\n\n/*\n===============\nCmd_StuffCmds_f\n\nAdds command line parameters as script statements\nCommands lead with a +, and continue until a - or another +\nhl.exe -dev 3 +map c1a0d\nhl.exe -nosound -game bshift\n===============\n*/\nstatic void Cmd_StuffCmds_f( void )\n{\n\thost.stuffcmds_pending = true;\n}\n\n/*\n============\nCmd_Wait_f\n\nCauses execution of the remainder of the command buffer to be delayed until\nnext frame.  This allows commands like:\nbind g \"cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster\"\n============\n*/\nstatic void Cmd_Wait_f( void )\n{\n\tif ( Cmd_Argc() > 1 )\n\t{\n\t\tconst char *arg = Cmd_Argv( 1 );\n\t\tcmd_wait = atoi( arg );\n\t}\n\n\tcmd_wait = Q_max( cmd_wait, 1 );\n}\n\n/*\n===============\nCmd_Echo_f\n\nJust prints the rest of the line to the console\n===============\n*/\nstatic void Cmd_Echo_f( void )\n{\n\tint\ti;\n\n\tfor( i = 1; i < Cmd_Argc(); i++ )\n\t\tCon_Printf( \"%s \", Cmd_Argv( i ));\n\tCon_Printf( \"\\n\" );\n}\n\n/*\n===============\nCmd_Alias_f\n\nCreates a new command that executes a command string (possibly ; seperated)\n===============\n*/\nstatic void Cmd_Alias_f( void )\n{\n\tcmdalias_t\t*a;\n\tchar\t\tcmd[MAX_CMD_LINE];\n\tint\t\ti, c;\n\tconst char\t\t*s;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( \"Current alias commands:\\n\" );\n\t\tfor( a = cmd_alias; a; a = a->next )\n\t\t\tCon_Printf( \"^2%s^7 : ^3%s^7\\n\", a->name, a->value );\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv( 1 );\n\n\tif( Q_strlen( s ) >= MAX_ALIAS_NAME )\n\t{\n\t\tCon_Printf( \"Alias name is too long\\n\" );\n\t\treturn;\n\t}\n\n\t// if the alias already exists, reuse it\n\tfor( a = cmd_alias; a; a = a->next )\n\t{\n\t\tif( !Q_strcmp( s, a->name ))\n\t\t{\n\t\t\tMem_Free( a->value );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !a )\n\t{\n\t\tcmdalias_t\t*cur, *prev;\n\n\t\ta = Mem_Malloc( cmd_pool, sizeof( cmdalias_t ));\n\n\t\tQ_strncpy( a->name, s, sizeof( a->name ));\n\n\t\t// insert it at the right alphanumeric position\n\t\tfor( prev = NULL, cur = cmd_alias; cur && Q_strcmp( cur->name, a->name ) < 0; prev = cur, cur = cur->next );\n\n\t\tif( prev ) prev->next = a;\n\t\telse cmd_alias = a;\n\t\ta->next = cur;\n\n#if defined( XASH_HASHED_VARS )\n\t\tBaseCmd_Insert( HM_CMDALIAS, a, a->name );\n#endif\n\t}\n\n\t// copy the rest of the command line\n\tcmd[0] = 0; // start out with a null string\n\n\tc = Cmd_Argc();\n\n\tfor( i = 2; i < c; i++ )\n\t{\n\t\tif( i != 2 ) Q_strncat( cmd, \" \", sizeof( cmd ));\n\t\tQ_strncat( cmd, Cmd_Argv( i ), sizeof( cmd ));\n\t}\n\n\tQ_strncat( cmd, \"\\n\", sizeof( cmd ));\n\ta->value = copystringpool( cmd_pool, cmd );\n}\n\n/*\n===============\nCmd_UnAlias_f\n\nRemove existing aliases.\n===============\n*/\nstatic void Cmd_UnAlias_f ( void )\n{\n\tcmdalias_t\t*a, *p;\n\tconst char\t*s;\n\tint\t\ti;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"unalias alias1 [alias2 ...]\\n\" );\n\t\treturn;\n\t}\n\n\tfor( i = 1; i < Cmd_Argc(); i++ )\n\t{\n\t\ts = Cmd_Argv( i );\n\t\tp = NULL;\n\n\t\tfor( a = cmd_alias; a; p = a, a = a->next )\n\t\t{\n\t\t\tif( !Q_strcmp( s, a->name ))\n\t\t\t{\n#if defined( XASH_HASHED_VARS )\n\t\t\t\tBaseCmd_Remove( HM_CMDALIAS, a->name );\n#endif\n\t\t\t\tif( a == cmd_alias )\n\t\t\t\t\tcmd_alias = a->next;\n\t\t\t\tif( p ) p->next = a->next;\n\t\t\t\tMem_Free( a->value );\n\t\t\t\tMem_Free( a );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !a ) Con_Printf( \"%s not found\\n\", s );\n\t}\n}\n\n/*\n=============================================================================\n\n\t\t\tCOMMAND EXECUTION\n\n=============================================================================\n*/\nstruct cmd_s\n{\n\tcmd_t      *next;\n\tchar       *name;\n\txcommand_t  function;\n\tint         flags;\n\tchar        desc[];\n};\n\nint           cmd_argc;\nconst char   *cmd_args = NULL;\nchar         *cmd_argv[MAX_CMD_TOKENS];\nstatic cmd_t *cmd_functions;\t\t\t// possible commands to execute\n/*\n===========================\n\nClient exports\n\n===========================\n*/\n/*\n============\nCmd_AliasGetList\n============\n*/\ncmdalias_t *GAME_EXPORT Cmd_AliasGetList( void )\n{\n\treturn cmd_alias;\n}\n\n/*\n============\nCmd_GetList\n============\n*/\ncmd_t *GAME_EXPORT Cmd_GetFirstFunctionHandle( void )\n{\n\treturn cmd_functions;\n}\n\n/*\n============\nCmd_GetNext\n============\n*/\ncmd_t *GAME_EXPORT Cmd_GetNextFunctionHandle( cmd_t *cmd )\n{\n\treturn (cmd) ? cmd->next : NULL;\n}\n\n/*\n============\nCmd_GetName\n============\n*/\nconst char *GAME_EXPORT Cmd_GetName( cmd_t *cmd )\n{\n\treturn cmd->name;\n}\n\n/*\n============\nCmd_TokenizeString\n\nParses the given string into command line tokens.\nThe text is copied to a seperate buffer and 0 characters\nare inserted in the apropriate place, The argv array\nwill point into this temporary buffer.\n============\n*/\nvoid Cmd_TokenizeString( const char *text )\n{\n\tchar\tcmd_token[MAX_CMD_BUFFER];\n\tint\ti;\n\n\t// clear the args from the last string\n\tfor( i = 0; i < cmd_argc; i++ )\n\t\tMem_Free( cmd_argv[i] );\n\n\tcmd_argc = 0; // clear previous args\n\tcmd_args = NULL;\n\n\tif( !text ) return;\n\n\twhile( 1 )\n\t{\n\t\t// skip whitespace up to a /n\n\t\twhile( *text && ((byte)*text ) <= ' ' && *text != '\\r' && *text != '\\n' )\n\t\t\ttext++;\n\n\t\tif( *text == '\\n' || *text == '\\r' )\n\t\t{\n\t\t\t// a newline seperates commands in the buffer\n\t\t\tif( *text == '\\r' && text[1] == '\\n' )\n\t\t\t\ttext++;\n\t\t\ttext++;\n\t\t\tbreak;\n\t\t}\n\n\t\tif( !*text )\n\t\t\treturn;\n\n\t\tif( cmd_argc == 1 )\n\t\t\t cmd_args = text;\n\n\t\ttext = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL );\n\n\t\tif( !text ) return;\n\n\t\tif( cmd_argc < MAX_CMD_TOKENS )\n\t\t{\n\t\t\tcmd_argv[cmd_argc] = copystringpool( cmd_pool, cmd_token );\n\t\t\tcmd_argc++;\n\t\t}\n\t}\n}\n\n/*\n============\nCmd_AddCommandEx\n============\n*/\nint Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd_desc, int iFlags, const char *funcname )\n{\n\tcmd_t  *cmd, *cur, *prev;\n\tsize_t desc_len;\n\n\tif( !COM_CheckString( cmd_name ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: NULL name\\n\", funcname );\n\t\treturn 0;\n\t}\n\n\t// fail if the command is a variable name\n\tif( Cvar_FindVar( cmd_name ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s already defined as a var\\n\", funcname, cmd_name );\n\t\treturn 0;\n\t}\n\n\t// fail if the command already exists and cannot be overriden\n\tcmd = Cmd_Exists( cmd_name );\n\tif( cmd )\n\t{\n\t\t// some mods register commands that share the name with some engine's commands\n\t\t// when they aren't critical to keep engine running, we can let mods to override them\n\t\t// unfortunately, we lose original command this way\n\t\tif( FBitSet( cmd->flags, CMD_OVERRIDABLE ))\n\t\t{\n\t\t\tdesc_len = Q_strlen( cmd->desc ) + 1;\n\t\t\tQ_strncpy( cmd->desc, cmd_desc, desc_len );\n\t\t\tcmd->function = function;\n\t\t\tcmd->flags = iFlags;\n\n\t\t\tCon_DPrintf( S_WARN \"%s: %s already defined but is allowed to be overriden\\n\", funcname, cmd_name );\n\t\t\treturn 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_DPrintf( \"%s%s: %s already defined\\n\", cmd->function == function ? S_WARN : S_ERROR, funcname, cmd_name );\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\t// use a small malloc to avoid zone fragmentation\n\tdesc_len = Q_strlen( cmd_desc ) + 1;\n\tcmd = Mem_Malloc( cmd_pool, sizeof( cmd_t ) + desc_len );\n\tcmd->name = copystringpool( cmd_pool, cmd_name );\n\tQ_strncpy( cmd->desc, cmd_desc, desc_len );\n\tcmd->function = function;\n\tcmd->flags = iFlags;\n\n\t// insert it at the right alphanumeric position\n\tfor( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next );\n\n\tif( prev ) prev->next = cmd;\n\telse cmd_functions = cmd;\n\tcmd->next = cur;\n\n#if defined(XASH_HASHED_VARS)\n\tBaseCmd_Insert( HM_CMD, cmd, cmd->name );\n#endif\n\n\treturn 1;\n}\n\n/*\n============\nCmd_RemoveCommand\n============\n*/\nvoid GAME_EXPORT Cmd_RemoveCommand( const char *cmd_name )\n{\n\tcmd_t\t*cmd, **back;\n\n\tif( !cmd_name || !*cmd_name )\n\t\treturn;\n\n\tback = &cmd_functions;\n\twhile( 1 )\n\t{\n\t\tcmd = *back;\n\t\tif( !cmd ) return;\n\n\t\tif( !Q_strcmp( cmd_name, cmd->name ))\n\t\t{\n#if defined(XASH_HASHED_VARS)\n\t\t\tBaseCmd_Remove( HM_CMD, cmd->name );\n#endif\n\n\t\t\t*back = cmd->next;\n\n\t\t\tif( cmd->name )\n\t\t\t\tMem_Free( cmd->name );\n\n\t\t\tMem_Free( cmd );\n\t\t\treturn;\n\t\t}\n\t\tback = &cmd->next;\n\t}\n}\n\n/*\n============\nCmd_LookupCmds\n============\n*/\nvoid Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback )\n{\n\tcmd_t\t*cmd;\n\tcmdalias_t\t*alias;\n\n\t// nothing to process ?\n\tif( !callback ) return;\n\n\tfor( cmd = cmd_functions; cmd; cmd = cmd->next )\n\t{\n\t\tif( !buffer ) callback( cmd->name, (char *)cmd->function, cmd->desc, ptr );\n\t\telse callback( cmd->name, (char *)cmd->function, buffer, ptr );\n\t}\n\n\t// lookup an aliases too\n\tfor( alias = cmd_alias; alias; alias = alias->next )\n\t\tcallback( alias->name, alias->value, buffer, ptr );\n}\n\n/*\n============\nCmd_Exists\n============\n*/\ncmd_t *Cmd_Exists( const char *cmd_name )\n{\n#if defined(XASH_HASHED_VARS)\n\treturn BaseCmd_Find( HM_CMD, cmd_name );\n#else\n\tcmd_t\t*cmd;\n\tfor( cmd = cmd_functions; cmd; cmd = cmd->next )\n\t{\n\t\tif( !Q_strcmp( cmd_name, cmd->name ))\n\t\t\treturn cmd;\n\t}\n\treturn NULL;\n#endif\n}\n\n/*\n============\nCmd_If_f\n\nCompare and et condition bit if true\n============\n*/\nstatic void Cmd_If_f( void )\n{\n\t// reset bit first\n\tcmd_condition &= ~BIT( cmd_condlevel );\n\n\t// usage\n\tif( cmd_argc == 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"if <op1> [ <operator> <op2> ]\\n\");\n\t\tCon_Printf( \":<action1>\\n\" );\n\t\tCon_Printf( \":<action2>\\n\" );\n\t\tCon_Printf( \"else\\n\" );\n\t\tCon_Printf( \":<action3>\\n\" );\n\t\tCon_Printf( \"operands are string or float values\\n\" );\n\t\tCon_Printf( \"and substituted cvars like '$cl_lw'\\n\" );\n\t\tCon_Printf( \"operator is '='', '==', '>', '<', '>=', '<=' or '!='\\n\" );\n\t\treturn;\n\t}\n\n\t// one argument - check if nonzero\n\tif( cmd_argc == 2 )\n\t{\n\t\tif( Q_atof( cmd_argv[1] ))\n\t\t\tcmd_condition |= BIT( cmd_condlevel );\n\t}\n\telse if( cmd_argc == 4 )\n\t{\n\t\t// simple compare\n\t\tfloat\tf1 = Q_atof( cmd_argv[1] );\n\t\tfloat\tf2 = Q_atof( cmd_argv[3] );\n\n\t\tif( !cmd_argv[2][0] ) // this is wrong\n\t\t\treturn;\n\n\t\tif(( cmd_argv[2][0] == '=' ) || ( cmd_argv[2][1] == '=' )) // =, ==, >=, <=\n\t\t{\n\t\t\tif( !Q_strcmp( cmd_argv[1], cmd_argv[3] ) || (( f1 || f2 ) && ( f1 == f2 )))\n\t\t\t\tcmd_condition |= BIT( cmd_condlevel );\n\t\t}\n\n\t\tif( cmd_argv[2][0] == '!' ) \t\t\t\t\t// !=\n\t\t{\n\t\t\tcmd_condition ^= BIT( cmd_condlevel );\n\t\t\treturn;\n\t\t}\n\n\t\tif(( cmd_argv[2][0] == '>' ) && ( f1 > f2 )) // >, >=\n\t\t\tcmd_condition |= BIT( cmd_condlevel );\n\n\t\tif(( cmd_argv[2][0] == '<' ) && ( f1 < f2 )) // <, <=\n\t\t\tcmd_condition |= BIT( cmd_condlevel );\n\t}\n}\n\n/*\n============\nCmd_Else_f\n\nInvert condition bit\n============\n*/\nstatic void Cmd_Else_f( void )\n{\n\tcmd_condition ^= BIT( cmd_condlevel );\n}\n\nstatic qboolean Cmd_ShouldAllowCommand( cmd_t *cmd, qboolean isPrivileged )\n{\n\tconst char *prefixes[] = { \"cl_\", \"gl_\", \"r_\", \"m_\", \"hud_\", \"joy_\", \"con_\", \"scr_\" };\n\tint i;\n\n\t// always allow local commands\n\tif( isPrivileged )\n\t\treturn true;\n\n\t// never allow local only commands from remote\n\tif( FBitSet( cmd->flags, CMD_PRIVILEGED ))\n\t\treturn false;\n\n\t// allow engine commands if user don't mind\n\tif( cl_filterstuffcmd.value <= 0.0f )\n\t\treturn true;\n\n\tif( FBitSet( cmd->flags, CMD_FILTERABLE ))\n\t\treturn false;\n\n\tfor( i = 0; i < ARRAYSIZE( prefixes ); i++ )\n\t{\n\t\tif( !Q_strnicmp( cmd->name, prefixes[i], Q_strlen( prefixes[i] )))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nCmd_ExecuteString\n\nA complete command line has been parsed, so try to execute it\n============\n*/\nstatic void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPrivileged )\n{\n\tcmd_t\t*cmd = NULL;\n\tcmdalias_t\t*a = NULL;\n\tconvar_t *cvar = NULL;\n\tchar\t\tcommand[MAX_CMD_LINE];\n\tchar\t\t*pcmd = command;\n\tint\t\tlen = 0;\n\n\tcmd_condlevel = 0;\n\n\t// cvar value substitution\n\tif( cmd_scripting.value && isPrivileged )\n\t{\n\t\twhile( *text )\n\t\t{\n\t\t\t// check for escape\n\t\t\tif(( *text == '\\\\' || *text == '$' ) && (*( text + 1 ) == '$' ))\n\t\t\t{\n\t\t\t\ttext ++;\n\t\t\t}\n\t\t\telse if( *text == '$' )\n\t\t\t{\n\t\t\t\tchar\ttoken[MAX_CMD_LINE];\n\t\t\t\tchar\t*ptoken = token;\n\n\t\t\t\t// check for correct cvar name\n\t\t\t\ttext++;\n\t\t\t\twhile(( *text >= '0' && *text <= '9' ) || ( *text >= 'A' && *text <= 'Z' ) || ( *text >= 'a' && *text <= 'z' ) || ( *text == '_' ))\n\t\t\t\t\t*ptoken++ = *text++;\n\t\t\t\t*ptoken = 0;\n\n\t\t\t\tlen += Q_strncpy( pcmd, Cvar_VariableString( token ), sizeof( token ) - len );\n\t\t\t\tpcmd = command + len;\n\n\t\t\t\tif( !*text ) break;\n\t\t\t}\n\n\t\t\t*pcmd++ = *text++;\n\t\t\tlen++;\n\t\t}\n\n\t\t*pcmd = 0;\n\t\ttext = command;\n\n\t\twhile( *text == ':' )\n\t\t{\n\t\t\tif( !FBitSet( cmd_condition, BIT( cmd_condlevel )))\n\t\t\t\treturn;\n\t\t\tcmd_condlevel++;\n\t\t\ttext++;\n\t\t}\n\t}\n\n\t// execute the command line\n\tCmd_TokenizeString( text );\n\n\tif( !Cmd_Argc( )) return; // no tokens\n\n#if defined( XASH_HASHED_VARS )\n\tBaseCmd_FindAll( cmd_argv[0], &cmd, &a, &cvar );\n#endif\n\n\tif( !host.apply_game_config )\n\t{\n#if !defined( XASH_HASHED_VARS )\n\t\t// check aliases\n\t\tfor( a = cmd_alias; a; a = a->next )\n\t\t{\n\t\t\tif( !Q_stricmp( cmd_argv[0], a->name ))\n\t\t\t\tbreak;\n\t\t}\n#endif\n\n\t\tif( a )\n\t\t{\n\t\t\tsize_t len = Q_strlen( a->value );\n\t\t\tCbuf_InsertTextToBuffer(\n\t\t\t\tisPrivileged ? &cmd_text : &filteredcmd_text,\n\t\t\t\ta->value, len, len );\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// special mode for restore game.dll archived cvars\n\tif( !host.apply_game_config || !Q_strcmp( cmd_argv[0], \"exec\" ))\n\t{\n#if !defined( XASH_HASHED_VARS )\n\t\tfor( cmd = cmd_functions; cmd; cmd = cmd->next )\n\t\t{\n\t\t\tif( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function )\n\t\t\t\tbreak;\n\t\t}\n#endif\n\n\t\t// check functions\n\t\tif( cmd && cmd->function )\n\t\t{\n\t\t\tif( Cmd_ShouldAllowCommand( cmd, isPrivileged ))\n\t\t\t{\n\t\t\t\tcmd_currentCommandIsPrivileged = isPrivileged;\n\t\t\t\tcmd->function();\n\t\t\t\tcmd_currentCommandIsPrivileged = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"Could not execute privileged command %s\\n\", cmd->name );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// check cvars\n\tif( Cvar_CommandWithPrivilegeCheck( cvar, isPrivileged )) return;\n\n\tif( host.apply_game_config )\n\t\treturn; // don't send nothing to server: we are a server!\n\n\t// forward the command line to the server, so the entity DLL can parse it\n\tif( host.type == HOST_NORMAL )\n\t{\n#if !XASH_DEDICATED\n\t\tif( cls.state >= ca_connected )\n\t\t{\n\t\t\tCmd_ForwardToServer();\n\t\t}\n\t\telse\n#endif // XASH_DEDICATED\n\t\tif( Cvar_VariableInteger( \"host_gameloaded\" ))\n\t\t{\n\t\t\tCon_Printf( S_WARN \"Unknown command \\\"%s\\\"\\n\", Cmd_Argv( 0 ) );\n\t\t}\n\t}\n}\n\nvoid Cmd_ExecuteString( const char *text )\n{\n\tCmd_ExecuteStringWithPrivilegeCheck( text, true );\n}\n\n/*\n===================\nCmd_ForwardToServer\n\nadds the current command line as a clc_stringcmd to the client message.\nthings like godmode, noclip, etc, are commands directed to the server,\nso when they are typed in at the console, they will need to be forwarded.\n===================\n*/\n#if !XASH_DEDICATED\nvoid Cmd_ForwardToServer( void )\n{\n\tchar\tstr[MAX_CMD_BUFFER];\n\n\tif( cls.demoplayback )\n\t{\n\t\tif( !Q_stricmp( Cmd_Argv( 0 ), \"pause\" ))\n\t\t\tcl.paused ^= 1;\n\t\treturn;\n\t}\n\n\tif( cls.state < ca_connected || cls.state > ca_active )\n\t{\n\t\tif( Q_stricmp( Cmd_Argv( 0 ), \"setinfo\" ))\n\t\t\tCon_Printf( \"Can't \\\"%s\\\", not connected\\n\", Cmd_Argv( 0 ));\n\t\treturn; // not connected\n\t}\n\n\tMSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd );\n\n\tstr[0] = 0;\n\tif( Q_stricmp( Cmd_Argv( 0 ), \"cmd\" ))\n\t{\n\t\tQ_strncat( str, Cmd_Argv( 0 ), sizeof( str ));\n\t\tQ_strncat( str, \" \", sizeof( str ));\n\t}\n\n\tif( Cmd_Argc() > 1 )\n\t\tQ_strncat( str, Cmd_Args( ), sizeof( str ));\n\telse Q_strncat( str, \"\\n\", sizeof( str ));\n\n\tMSG_WriteString( &cls.netchan.message, str );\n}\n#endif // XASH_DEDICATED\n\n/*\n============\nCmd_List_f\n============\n*/\nstatic void Cmd_List_f( void )\n{\n\tcmd_t\t*cmd;\n\tint\ti = 0;\n\tsize_t\tmatchlen = 0;\n\tconst char *match = NULL;\n\n\tif( Cmd_Argc() > 1 )\n\t{\n\t\tmatch = Cmd_Argv( 1 );\n\t\tmatchlen = Q_strlen( match );\n\t}\n\n\tfor( cmd = cmd_functions; cmd; cmd = cmd->next )\n\t{\n\t\tif( cmd->name[0] == '@' )\n\t\t\tcontinue;\t// never show system cmds\n\n\t\tif( match && !Q_strnicmpext( match, cmd->name, matchlen ))\n\t\t\tcontinue;\n\n\t\tCon_Printf( \" %-*s ^3%s^7\\n\", 32, cmd->name, cmd->desc );\n\t\ti++;\n\t}\n\n\tCon_Printf( \"%i commands\\n\", i );\n}\n\n/*\n============\nCmd_Unlink\n\nunlink all commands with specified flag\n============\n*/\nvoid Cmd_Unlink( int group )\n{\n\tcmd_t\t*cmd;\n\tcmd_t\t**prev;\n\tint\tcount = 0;\n\n\tif( FBitSet( group, CMD_SERVERDLL ) && Cvar_VariableInteger( \"host_gameloaded\" ))\n\t\treturn;\n\n\tif( FBitSet( group, CMD_CLIENTDLL ) && Cvar_VariableInteger( \"host_clientloaded\" ))\n\t\treturn;\n\n\tif( FBitSet( group, CMD_GAMEUIDLL ) && Cvar_VariableInteger( \"host_gameuiloaded\" ))\n\t\treturn;\n\n\tprev = &cmd_functions;\n\n\twhile( 1 )\n\t{\n\t\tcmd = *prev;\n\t\tif( !cmd ) break;\n\n\t\t// do filter by specified group\n\t\tif( group && !FBitSet( cmd->flags, group ))\n\t\t{\n\t\t\tprev = &cmd->next;\n\t\t\tcontinue;\n\t\t}\n\n#if defined(XASH_HASHED_VARS)\n\t\tBaseCmd_Remove( HM_CMD, cmd->name );\n#endif\n\n\t\t*prev = cmd->next;\n\n\t\tif( cmd->name ) Mem_Free( cmd->name );\n\n\t\tMem_Free( cmd );\n\t\tcount++;\n\t}\n\n\tCon_Reportf( \"unlink %i commands\\n\", count );\n}\n\nstatic void Cmd_Apropos_f( void )\n{\n\tcmd_t *cmd;\n\tconvar_t *var;\n\tcmdalias_t *alias;\n\tconst char *partial;\n\tint count = 0;\n\tchar buf[MAX_VA_STRING];\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tMsg( \"apropos what?\\n\" );\n\t\treturn;\n\t}\n\n\tpartial = Cmd_Args();\n\n\tif( !Q_strpbrk( partial, \"*?\" ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"*%s*\", partial );\n\t\tpartial = buf;\n\t}\n\n\tfor( var = (convar_t*)Cvar_GetList(); var; var = var->next )\n\t{\n\t\tif( !matchpattern_with_separator( var->name, partial, true, \"\", false ) )\n\t\t{\n\t\t\tconst char *desc;\n\n\t\t\tif( var->flags & FCVAR_EXTENDED )\n\t\t\t\tdesc = var->desc;\n\t\t\telse desc = \"game cvar\";\n\n\t\t\tif( !desc )\n\t\t\t\tdesc = \"user cvar\";\n\n\t\t\tif( !matchpattern_with_separator( desc, partial, true, \"\", false ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// TODO: maybe add flags output like cvarlist, also\n\t\t// fix inconsistencies in output from different commands\n\t\tMsg( \"cvar ^3%s^7 is \\\"%s\\\" [\\\"%s\\\"] %s\\n\",\n\t\t\tvar->name, var->string,\n\t\t\t( var->flags & FCVAR_EXTENDED ) ? var->def_string : \"\",\n\t\t\t( var->flags & FCVAR_EXTENDED ) ? var->desc : \"game cvar\");\n\t\tcount++;\n\t}\n\n\tfor( cmd = Cmd_GetFirstFunctionHandle(); cmd; cmd = Cmd_GetNextFunctionHandle( cmd ) )\n\t{\n\t\tif( cmd->name[0] == '@' )\n\t\t\tcontinue;\t// never show system cmds\n\n\t\tif( !matchpattern_with_separator( cmd->name, partial, true, \"\", false ) &&\n\t\t\t!matchpattern_with_separator( cmd->desc, partial, true, \"\", false ))\n\t\t\tcontinue;\n\n\t\tMsg( \"command ^2%s^7: %s\\n\", cmd->name, cmd->desc );\n\t\tcount++;\n\t}\n\n\tfor( alias = Cmd_AliasGetList(); alias; alias = alias->next )\n\t{\n\t\t// proceed a bit differently here as an alias value always got a final \\n\n\t\tif( !matchpattern_with_separator( alias->name, partial, true, \"\", false ) &&\n\t\t\t!matchpattern_with_separator( alias->value, partial, true, \"\\n\", false )) // when \\n is a separator, wildcards don't match it //-V666\n\t\t\tcontinue;\n\n\t\tMsg( \"alias ^5%s^7: %s\", alias->name, alias->value ); // do not print an extra \\n\n\t\tcount++;\n\t}\n\n\tMsg( \"\\n%i result%s\\n\\n\", count, (count > 1) ? \"s\" : \"\" );\n}\n\n\n/*\n============\nCmd_Null_f\n\nnull function for some cmd stubs\n============\n*/\nvoid Cmd_Null_f( void )\n{\n}\n\n/*\n=============\nCmd_MakePrivileged_f\n=============\n*/\nstatic void Cmd_MakePrivileged_f( void )\n{\n\tconst char *s = Cmd_Argv( 1 );\n\tconvar_t *cv;\n\tcmd_t *cmd;\n\tcmdalias_t *alias;\n\n\tif( Cmd_Argc( ) != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"make_privileged <cvar or command>\\n\" );\n\t\treturn;\n\t}\n\n#if defined( XASH_HASHED_VARS )\n\tBaseCmd_FindAll( s, &cmd, &alias, &cv );\n#else\n\tcmd = Cmd_Exists( s );\n\tcv = Cvar_FindVar( s );\n#endif\n\n\tif( !cv && !cmd )\n\t{\n\t\tCon_Printf( \"Nothing was found.\\n\" );\n\t\treturn;\n\t}\n\n\tif( cv )\n\t{\n\t\tSetBits( cv->flags, FCVAR_PRIVILEGED );\n\t\tCon_Printf( \"Cvar %s set to be privileged\\n\", cv->name );\n\t}\n\n\tif( cmd )\n\t{\n\t\tSetBits( cmd->flags, CMD_PRIVILEGED );\n\t\tCon_Printf( \"Command %s set to be privileged\\n\", cmd->name );\n\t}\n}\n\n/*\n==========\nCmd_Escape\n\ninserts escape sequences\n==========\n*/\nvoid Cmd_Escape( char *newCommand, const char *oldCommand, int len )\n{\n\tint c;\n\tint scripting = cmd_scripting.value;\n\n\twhile( (c = *oldCommand++) && len > 1 )\n\t{\n\t\tif( c == '\"' )\n\t\t{\n\t\t\t*newCommand++ = '\\\\';\n\t\t\tlen--;\n\t\t}\n\n\t\tif( scripting && c == '$')\n\t\t{\n\t\t\t*newCommand++ = '$';\n\t\t\tlen--;\n\t\t}\n\n\t\t*newCommand++ = c; len--;\n\t}\n\n\t*newCommand++ = 0;\n}\n\n\n/*\n============\nCmd_Init\n\n============\n*/\nvoid Cmd_Init( void )\n{\n\tcmd_pool = Mem_AllocPool( \"Console Commands\" );\n\tcmd_functions = NULL;\n\tcmd_condition = 0;\n\tcmd_alias = NULL;\n\tcmd_args = NULL;\n\tcmd_argc = 0;\n\n\t// register our commands\n\tCmd_AddCommand( \"echo\", Cmd_Echo_f, \"print a message to the console (useful in scripts)\" );\n\tCmd_AddCommand( \"wait\", Cmd_Wait_f, \"make script execution wait for some rendered frames\" );\n\tCmd_AddCommand( \"cmdlist\", Cmd_List_f, \"display all console commands beginning with the specified prefix\" );\n\tCmd_AddRestrictedCommand( \"stuffcmds\", Cmd_StuffCmds_f, \"execute commandline parameters (must be present in .rc script)\" );\n\tCmd_AddCommand( \"apropos\", Cmd_Apropos_f, \"lists all console variables/commands/aliases containing the specified string in the name or description\" );\n#if !XASH_DEDICATED\n\tCmd_AddCommand( \"cmd\", Cmd_ForwardToServer, \"send a console commandline to the server\" );\n#endif // XASH_DEDICATED\n\tCmd_AddRestrictedCommand( \"alias\", Cmd_Alias_f, \"create a script function. Without arguments show the list of all alias\" );\n\tCmd_AddRestrictedCommand( \"unalias\", Cmd_UnAlias_f, \"remove a script function\" );\n\tCmd_AddRestrictedCommand( \"if\", Cmd_If_f, \"compare and set condition bits\" );\n\tCmd_AddRestrictedCommand( \"else\", Cmd_Else_f, \"invert condition bit\" );\n\n\tCmd_AddRestrictedCommand( \"make_privileged\", Cmd_MakePrivileged_f, \"makes command or variable privileged (protected from access attempts from server)\" );\n\n#if defined(XASH_HASHED_VARS)\n\tCmd_AddCommand( \"basecmd_stats\", BaseCmd_Stats_f, \"print info about basecmd usage\" );\n\tCmd_AddCommand( \"basecmd_test\", BaseCmd_Test_f, \"test basecmd\" );\n#endif\n}\n\nvoid Cmd_Shutdown( void )\n{\n\tMem_FreePool( &cmd_pool );\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nenum\n{\n\tNO_CALL = 0,\n\tPRIV = 1,\n\tUNPRIV = 2\n};\n\nstatic int test_flags[3] = { NO_CALL, NO_CALL, NO_CALL };\n\nstatic void Test_PrivilegedCommand_f( void )\n{\n\ttest_flags[0] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;\n}\n\nstatic void Test_UnprivilegedCommand_f( void )\n{\n\ttest_flags[1] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;\n}\n\nstatic void Test_FilteredCommand_f( void )\n{\n\ttest_flags[2] = Cmd_CurrentCommandIsPrivileged() ? PRIV : UNPRIV;\n}\n\nvoid Test_RunCmd( void )\n{\n\tCmd_AddCommand( \"test_privileged\", Test_PrivilegedCommand_f, \"bark bark\" );\n\tCmd_AddRestrictedCommand( \"test_unprivileged\", Test_UnprivilegedCommand_f, \"meow meow\" );\n\tCmd_AddCommand( \"hud_filtered\", Test_FilteredCommand_f, \"dummy description\" );\n\n\tCbuf_AddText( \"test_privileged; test_unprivileged; hud_filtered\\n\" );\n\tCbuf_Execute();\n\tTASSERT( test_flags[0] == PRIV );\n\tTASSERT( test_flags[1] == PRIV );\n\tTASSERT( test_flags[2] == PRIV );\n\n\tVectorSet( test_flags, NO_CALL, NO_CALL, NO_CALL );\n\tCvar_DirectSet( &cl_filterstuffcmd, \"0\" );\n\tCbuf_AddFilteredText( \"test_privileged; test_unprivileged; hud_filtered\\n\" );\n\tCbuf_Execute();\n\tTASSERT( test_flags[0] == UNPRIV );\n\tTASSERT( test_flags[1] == NO_CALL );\n\tTASSERT( test_flags[2] == UNPRIV );\n\n\tVectorSet( test_flags, NO_CALL, NO_CALL, NO_CALL );\n\tCvar_DirectSet( &cl_filterstuffcmd, \"1\" );\n\tCbuf_AddFilteredText( \"test_privileged; test_unprivileged; hud_filtered\\n\" );\n\tCbuf_Execute();\n\tTASSERT( test_flags[0] == UNPRIV );\n\tTASSERT( test_flags[1] == NO_CALL );\n\tTASSERT( test_flags[2] == NO_CALL );\n\n\tCmd_RemoveCommand( \"hud_filtered\" );\n\tCmd_RemoveCommand( \"test_unprivileged\" );\n\tCmd_RemoveCommand( \"test_privileged\" );\n}\n#endif\n"
  },
  {
    "path": "engine/common/com_strings.h",
    "content": "/*\ncom_strings.h - all paths to external resources that hardcoded into engine\nCopyright (C) 2018 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef COM_STRINGS_H\n#define COM_STRINGS_H\n\n// default colored message headers\n#define S_BLACK   \"^0\"\n#define S_RED     \"^1\"\n#define S_GREEN   \"^2\"\n#define S_YELLOW  \"^3\"\n#define S_BLUE    \"^4\"\n#define S_CYAN    \"^5\"\n#define S_MAGENTA \"^6\"\n#define S_DEFAULT \"^7\"\n\n#define S_NOTE         S_GREEN  \"Note: \" S_DEFAULT\n#define S_WARN         S_YELLOW \"Warning: \" S_DEFAULT\n#define S_ERROR        S_RED    \"Error: \" S_DEFAULT\n#define S_USAGE        \"Usage: \"\n#define S_USAGE_INDENT \"\\t\"\n\n#define S_OPENGL_NOTE  S_GREEN  \"OpenGL Note: \" S_DEFAULT\n#define S_OPENGL_WARN  S_YELLOW \"OpenGL Warning: \" S_DEFAULT\n#define S_OPENGL_ERROR S_RED    \"OpenGL Error: \" S_DEFAULT\n\n// end game final default message\n#define DEFAULT_ENDGAME_MESSAGE\t\"The End\"\n\n// path to default playermodel in GoldSrc\n#define DEFAULT_PLAYER_PATH_HALFLIFE\t\"models/player.mdl\"\n\n// path to default playermodel in Quake\n#define DEFAULT_PLAYER_PATH_QUAKE\t\"progs/player.mdl\"\n\n// debug beams\n#define DEFAULT_LASERBEAM_PATH\t\"sprites/laserbeam.spr\"\n\n#define DEFAULT_INTERNAL_PALETTE\t\"gfx/palette.lmp\"\n\n#define DEFAULT_EXTERNAL_PALETTE\t\"gfx/palette.pal\"\n\n// path to sound files\n#define DEFAULT_SOUNDPATH\t\t\"sound/\"\n\n// path to saved games\n#define DEFAULT_SAVE_DIRECTORY\t\"save/\"\n\n// path to download games\n#define DEFAULT_DOWNLOADED_DIRECTORY \"downloaded/\"\n\n// path to user mod directory\n#define DEFAULT_CUSTOM_DIRECTORY \"custom/\"\n\n// fallback to this skybox\n#define DEFAULT_SKYBOX_NAME     \"desert\"\n\n// playlist for startup videos\n#define DEFAULT_VIDEOLIST_PATH\t\"media/StartupVids.txt\"\n\n#define CVAR_GLCONFIG_DESCRIPTION\t\"enable or disable %s\"\n\n#define DEFAULT_UPDATE_PAGE \"https://github.com/FWGS/xash3d-fwgs/releases/latest\"\n\n#define XASH_ENGINE_NAME \"Xash3D FWGS\"\n#define XASH_DEDICATED_SERVER_NAME \"XashDS\"\n#define XASH_VERSION        \"0.21\" // engine current version\n#define XASH_COMPAT_VERSION \"0.99\" // version we are based on\n\n#endif//COM_STRINGS_H\n"
  },
  {
    "path": "engine/common/common.c",
    "content": "/*\ncommon.c - misc functions used by dlls'\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if defined( ALLOCA_H )\n#include ALLOCA_H\n#endif\n#include \"common.h\"\n#include \"studio.h\"\n#include \"xash3d_mathlib.h\"\n#include \"const.h\"\n#include \"client.h\"\n#include \"library.h\"\n\nstatic const char *const file_exts[] =\n{\n\t// ban text files that don't make sense as resource\n\t\"cfg\", \"lst\", \"ini\", \"log\",\n\n\t// ban Windows code\n\t\"exe\", \"vbs\", \"com\", \"bat\",\n\t\"dll\", \"sys\", \"ps1\",\n\n\t// ban common unix code\n\t// NOTE: in unix anything can be executed as long it has access flag\n\t\"so\", \"sh\", \"dylib\",\n\n\t// ban mobile archives\n\t\"apk\", \"ipa\",\n};\n\n#ifdef _DEBUG\nvoid DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage )\n{\n\tif( fExpr ) return;\n\n\tif( szMessage != NULL )\n\t\tCon_DPrintf( S_ERROR \"ASSERT FAILED:\\n %s \\n(%s@%d)\\n%s\\n\", szExpr, szFile, szLine, szMessage );\n\telse Con_DPrintf( S_ERROR \"ASSERT FAILED:\\n %s \\n(%s@%d)\\n\", szExpr, szFile, szLine );\n}\n#endif\t// DEBUG\n\nstatic int idum = 0;\n\n#define MAX_RANDOM_RANGE\t0x7FFFFFFFUL\n#define IA\t\t16807\n#define IM\t\t2147483647\n#define IQ\t\t127773\n#define IR\t\t2836\n#define NTAB\t\t32\n#define EPS\t\t1.2e-7\n#define NDIV\t\t(1 + (IM - 1) / NTAB)\n#define AM\t\t(1.0 / IM)\n#define RNMX\t\t(1.0 - EPS)\n\nstatic int lran1( void )\n{\n\tstatic int\tiy = 0;\n\tstatic int\tiv[NTAB];\n\tint\t\tj;\n\tint\t\tk;\n\n\tif( idum <= 0 || !iy )\n\t{\n\t\tif( -(idum) < 1 ) idum = 1;\n\t\telse idum = -(idum);\n\n\t\tfor( j = NTAB + 7; j >= 0; j-- )\n\t\t{\n\t\t\tk = (idum) / IQ;\n\t\t\tidum = IA * (idum - k * IQ) - IR * k;\n\t\t\tif( idum < 0 ) idum += IM;\n\t\t\tif( j < NTAB ) iv[j] = idum;\n\t\t}\n\n\t\tiy = iv[0];\n\t}\n\n\tk = (idum) / IQ;\n\tidum = IA * (idum - k * IQ) - IR * k;\n\tif( idum < 0 ) idum += IM;\n\tj = iy / NDIV;\n\tiy = iv[j];\n\tiv[j] = idum;\n\n\treturn iy;\n}\n\n// fran1 -- return a random floating-point number on the interval [0,1]\nstatic float fran1( void )\n{\n\tfloat temp = (float)AM * lran1();\n\tif( temp > RNMX )\n\t\treturn (float)RNMX;\n\treturn temp;\n}\n\nvoid GAME_EXPORT COM_SetRandomSeed( int lSeed )\n{\n\tif( lSeed ) idum = lSeed;\n\telse idum = -time( NULL );\n\n\tif( 1000 < idum )\n\t\tidum = -idum;\n\telse if( -1000 < idum )\n\t\tidum -= 22261048;\n}\n\nfloat GAME_EXPORT COM_RandomFloat( float flLow, float flHigh )\n{\n\tfloat\tfl;\n\n\tif( idum == 0 ) COM_SetRandomSeed( 0 );\n\n\tfl = fran1(); // float in [0,1]\n\treturn (fl * (flHigh - flLow)) + flLow; // float in [low, high)\n}\n\nint GAME_EXPORT COM_RandomLong( int lLow, int lHigh )\n{\n\tdword\tmaxAcceptable;\n\tdword\tn, x = lHigh - lLow + 1;\n\n\tif( idum == 0 ) COM_SetRandomSeed( 0 );\n\n\tif( x <= 0 || MAX_RANDOM_RANGE < x - 1 )\n\t\treturn lLow;\n\n\t// The following maps a uniform distribution on the interval [0, MAX_RANDOM_RANGE]\n\t// to a smaller, client-specified range of [0,x-1] in a way that doesn't bias\n\t// the uniform distribution unfavorably. Even for a worst case x, the loop is\n\t// guaranteed to be taken no more than half the time, so for that worst case x,\n\t// the average number of times through the loop is 2. For cases where x is\n\t// much smaller than MAX_RANDOM_RANGE, the average number of times through the\n\t// loop is very close to 1.\n\tmaxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE + 1) % x );\n\tdo\n\t{\n\t\tn = lran1();\n\t} while( n > maxAcceptable );\n\n\treturn lLow + (n % x);\n}\n\n/*\n============\nva\n\ndoes a varargs printf into a temp buffer,\nso I don't need to have varargs versions\nof all text functions.\n============\n*/\nchar *va( const char *format, ... )\n{\n\tva_list\t\targptr;\n\tstatic char\tstring[16][MAX_VA_STRING], *s;\n\tstatic int\tstringindex = 0;\n\n\ts = string[stringindex];\n\tstringindex = (stringindex + 1) & 15;\n\tva_start( argptr, format );\n\tQ_vsnprintf( s, sizeof( string[0] ), format, argptr );\n\tva_end( argptr );\n\n\treturn s;\n}\n\n/*\n===============================================================================\n\n\tLZSS Compression\n\n===============================================================================\n*/\n#define LZSS_ID\t\t(('S'<<24)|('S'<<16)|('Z'<<8)|('L'))\n#define LZSS_LOOKSHIFT\t4\n#define LZSS_WINDOW_SIZE\t4096\n#define LZSS_LOOKAHEAD\tBIT( LZSS_LOOKSHIFT )\n\n\ntypedef struct\n{\n\tunsigned int\tid;\n\tunsigned int\tsize;\n} lzss_header_t;\n\n// expected to be sixteen bytes\ntypedef struct lzss_node_s\n{\n\tconst byte\t*data;\n\tstruct lzss_node_s\t*prev;\n\tstruct lzss_node_s\t*next;\n\tchar\t\tpad[4];\n} lzss_node_t;\n\ntypedef struct\n{\n\tlzss_node_t\t*start;\n\tlzss_node_t\t*end;\n} lzss_list_t;\n\ntypedef struct\n{\n\tlzss_list_t\t*hash_table;\n\tlzss_node_t\t*hash_node;\n\tint\t\twindow_size;\n} lzss_state_t;\n\nqboolean LZSS_IsCompressed( const byte *source, size_t input_len )\n{\n\tconst lzss_header_t *phdr;\n\n\tif( input_len <= sizeof( lzss_header_t ))\n\t\treturn 0;\n\n\tphdr = (const lzss_header_t *)source;\n\n\tif( phdr && phdr->id == LZSS_ID )\n\t\treturn true;\n\treturn false;\n}\n\nuint LZSS_GetActualSize( const byte *source, size_t input_len )\n{\n\tconst lzss_header_t *phdr;\n\n\tif( input_len <= sizeof( lzss_header_t ))\n\t\treturn 0;\n\n\tphdr = (const lzss_header_t *)source;\n\n\tif( phdr && phdr->id == LZSS_ID )\n\t\treturn phdr->size;\n\n\treturn 0;\n}\n\nstatic void LZSS_BuildHash( lzss_state_t *state, const byte *source )\n{\n\tlzss_list_t\t*list;\n\tlzss_node_t\t*node;\n\tunsigned int\ttargetindex = (uintptr_t)source & ( state->window_size - 1 );\n\n\tnode = &state->hash_node[targetindex];\n\n\tif( node->data )\n\t{\n\t\tlist = &state->hash_table[*node->data];\n\t\tif( node->prev )\n\t\t{\n\t\t\tlist->end = node->prev;\n\t\t\tnode->prev->next = NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlist->start = NULL;\n\t\t\tlist->end = NULL;\n\t\t}\n\t}\n\n\tlist = &state->hash_table[*source];\n\tnode->data = source;\n\tnode->prev = NULL;\n\tnode->next = list->start;\n\tif( list->start )\n\t\tlist->start->prev = node;\n\telse list->end = node;\n\tlist->start = node;\n}\n\nstatic byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *pOutputBuf, uint *pOutputSize )\n{\n\tbyte\t\t*pStart = pOutputBuf; // allocate the output buffer, compressed buffer is expected to be less, caller will free\n\tbyte\t\t*pEnd = pStart + input_length - sizeof( lzss_header_t ) - 8; // prevent compression failure\n\tlzss_header_t\t*header = (lzss_header_t *)pStart;\n\tbyte\t\t*pOutput = pStart + sizeof( lzss_header_t );\n\tconst byte\t*pEncodedPosition = NULL;\n\tbyte\t\t*pLookAhead = pInput;\n\tbyte\t\t*pWindow = pInput;\n\tint\t\ti, putCmdByte = 0;\n\tbyte\t\t*pCmdByte = NULL;\n\n\tif( input_length <= sizeof( lzss_header_t ) + 8 )\n\t\treturn NULL;\n\n\t// set LZSS header\n\theader->id = LZSS_ID;\n\theader->size = input_length;\n\n\t// create the compression work buffers, small enough (~64K) for stack\n\tstate->hash_table = (lzss_list_t *)alloca( 256 * sizeof( lzss_list_t ));\n\tmemset( state->hash_table, 0, 256 * sizeof( lzss_list_t ));\n\tstate->hash_node = (lzss_node_t *)alloca( state->window_size * sizeof( lzss_node_t ));\n\tmemset( state->hash_node, 0, state->window_size * sizeof( lzss_node_t ));\n\n\twhile( input_length > 0 )\n\t{\n\t\tint\t\tlookAheadLength = input_length < LZSS_LOOKAHEAD ? input_length : LZSS_LOOKAHEAD;\n\t\tlzss_node_t\t*hash = state->hash_table[pLookAhead[0]].start;\n\t\tint\t\tencoded_length = 0;\n\n\t\tpWindow = pLookAhead - state->window_size;\n\n\t\tif( pWindow < pInput )\n\t\t\tpWindow = pInput;\n\n\t\tif( !putCmdByte )\n\t\t{\n\t\t\tpCmdByte = pOutput++;\n\t\t\t*pCmdByte = 0;\n\t\t}\n\n\t\tputCmdByte = ( putCmdByte + 1 ) & 0x07;\n\n\t\twhile( hash != NULL )\n\t\t{\n\t\t\tint\tlength = lookAheadLength;\n\t\t\tint\tmatch_length = 0;\n\n\t\t\twhile( length-- && hash->data[match_length] == pLookAhead[match_length] )\n\t\t\t\tmatch_length++;\n\n\t\t\tif( match_length > encoded_length )\n\t\t\t{\n\t\t\t\tencoded_length = match_length;\n\t\t\t\tpEncodedPosition = hash->data;\n\t\t\t}\n\n\t\t\tif( match_length == lookAheadLength )\n\t\t\t\tbreak;\n\n\t\t\thash = hash->next;\n\t\t}\n\n\t\tif ( encoded_length >= 3 )\n\t\t{\n\t\t\t*pCmdByte = (*pCmdByte >> 1) | 0x80;\n\t\t\t*pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) >> LZSS_LOOKSHIFT );\n\t\t\t*pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) << LZSS_LOOKSHIFT ) | ( encoded_length - 1 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t*pCmdByte = ( *pCmdByte >> 1 );\n\t\t\t*pOutput++ = *pLookAhead;\n\t\t\tencoded_length = 1;\n\t\t}\n\n\t\tfor( i = 0; i < encoded_length; i++ )\n\t\t{\n\t\t\tLZSS_BuildHash( state, pLookAhead++ );\n\t\t}\n\n\t\tinput_length -= encoded_length;\n\n\t\tif( pOutput >= pEnd )\n\t\t{\n\t\t\t// compression is worse, abandon\n\t\t\tstate->hash_table = NULL;\n\t\t\tstate->hash_node = NULL;\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif( input_length != 0 )\n\t{\n\t\t// unexpected failure\n\t\tAssert( 0 );\n\t\tstate->hash_table = NULL;\n\t\tstate->hash_node = NULL;\n\t\treturn NULL;\n\t}\n\n\tif( !putCmdByte )\n\t{\n\t\tpCmdByte = pOutput++;\n\t\t*pCmdByte = 0x01;\n\t}\n\telse\n\t{\n\t\t*pCmdByte = (( *pCmdByte >> 1 ) | 0x80 ) >> ( 7 - putCmdByte );\n\t}\n\n\t// put two ints at end of buffer\n\t*pOutput++ = 0;\n\t*pOutput++ = 0;\n\n\tif( pOutputSize )\n\t\t*pOutputSize = pOutput - pStart;\n\n\treturn pStart;\n}\n\nbyte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize )\n{\n\tbyte *pStart = (byte *)malloc( inputLength );\n\tbyte *pFinal = NULL;\n\tlzss_state_t state = { .window_size = LZSS_WINDOW_SIZE };\n\n\tif( !pStart )\n\t\treturn NULL;\n\n\tpFinal = LZSS_CompressNoAlloc( &state, pInput, inputLength, pStart, pOutputSize );\n\n\tif( !pFinal )\n\t{\n\t\tfree( pStart );\n\t\treturn NULL;\n\t}\n\n\treturn pStart;\n}\n\nuint LZSS_Decompress( const byte *pInput, byte *pOutput, size_t input_len, size_t output_len )\n{\n\tuint\ttotalBytes = 0;\n\tint\tgetCmdByte = 0;\n\tint\tcmdByte = 0;\n\tuint\tactualSize;\n\tconst byte *pInputEnd = pInput + input_len - 1; // thanks to nillerusr for the fix!\n\tbyte *pOrigOutput = pOutput;\n\n\tif( input_len <= sizeof( lzss_header_t ))\n\t\treturn 0;\n\n\tactualSize = LZSS_GetActualSize( pInput, input_len );\n\n\tif( !actualSize || actualSize > output_len )\n\t\treturn 0;\n\n\tpInput += sizeof( lzss_header_t );\n\n\twhile( 1 )\n\t{\n\t\tif( !getCmdByte )\n\t\t{\n\t\t\tif( pInput > pInputEnd )\n\t\t\t\treturn 0;\n\n\t\t\tcmdByte = *pInput++;\n\t\t}\n\t\tgetCmdByte = ( getCmdByte + 1 ) & 0x07;\n\n\t\tif( cmdByte & 0x01 )\n\t\t{\n\t\t\tint\tposition;\n\t\t\tint\ti, count;\n\t\t\tbyte\t*pSource;\n\n\t\t\tif( pInput > pInputEnd )\n\t\t\t\treturn 0;\n\n\t\t\tposition = *pInput++ << LZSS_LOOKSHIFT;\n\t\t\tposition |= ( *pInput >> LZSS_LOOKSHIFT );\n\t\t\tcount = ( *pInput++ & 0x0F ) + 1;\n\n\t\t\tif( count == 1 )\n\t\t\t\tbreak;\n\n\t\t\tpSource = pOutput - position - 1;\n\n\t\t\tif( totalBytes + count > output_len || pSource < pOrigOutput )\n\t\t\t\treturn 0;\n\n\t\t\tfor( i = 0; i < count; i++ )\n\t\t\t\t*pOutput++ = *pSource++;\n\t\t\ttotalBytes += count;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( totalBytes + 1 > output_len || pInput > pInputEnd )\n\t\t\t\treturn 0;\n\n\t\t\t*pOutput++ = *pInput++;\n\t\t\ttotalBytes++;\n\t\t}\n\t\tcmdByte = cmdByte >> 1;\n\t}\n\n\tif( totalBytes != actualSize )\n\t{\n\t\tAssert( 0 );\n\t\treturn 0;\n\t}\n\treturn totalBytes;\n}\n\n/*\n==============\nCOM_IsWhiteSpace\n\ninterpret symbol as whitespace\n==============\n*/\n\nstatic int COM_IsWhiteSpace( char space )\n{\n\tif( space == ' ' || space == '\\t' || space == '\\r' || space == '\\n' )\n\t\treturn 1;\n\treturn 0;\n}\n\n/*\n================\nCOM_ParseVector\n\n================\n*/\nqboolean COM_ParseVector( char **pfile, float *v, size_t size )\n{\n\tstring\ttoken;\n\tqboolean\tbracket = false;\n\tchar\t*saved;\n\tuint\ti;\n\n\tif( v == NULL || size == 0 )\n\t\treturn false;\n\n\tmemset( v, 0, sizeof( *v ) * size );\n\n\tif( size == 1 )\n\t{\n\t\t*pfile = COM_ParseFile( *pfile, token, sizeof( token ));\n\t\tv[0] = Q_atof( token );\n\t\treturn true;\n\t}\n\n\tsaved = *pfile;\n\n\tif(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )\n\t\treturn false;\n\n\tif( token[0] == '(' )\n\t\tbracket = true;\n\telse *pfile = saved; // restore token to right get it again\n\n\tfor( i = 0; i < size; i++ )\n\t{\n\t\t*pfile = COM_ParseFile( *pfile, token, sizeof( token ));\n\t\tv[i] = Q_atof( token );\n\t}\n\n\tif( !bracket ) return true;\t// done\n\n\tif(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )\n\t\treturn false;\n\n\tif( token[0] == ')' )\n\t\treturn true;\n\treturn false;\n}\n\n/*\n=============\nCOM_FileSize\n\n=============\n*/\nint GAME_EXPORT COM_FileSize( const char *filename )\n{\n\treturn FS_FileSize( filename, false );\n}\n\n/*\n=============\nCOM_TrimSpace\n\ntrims all whitespace from the front\nand end of a string\n=============\n*/\nvoid COM_TrimSpace( const char *source, char *dest )\n{\n\tint\tstart, end, length;\n\n\tstart = 0;\n\tend = Q_strlen( source );\n\n\twhile( source[start] && COM_IsWhiteSpace( source[start] ))\n\t\tstart++;\n\tend--;\n\n\twhile( end > 0 && COM_IsWhiteSpace( source[end] ))\n\t\tend--;\n\tend++;\n\n\tlength = end - start;\n\n\tif( length > 0 )\n\t\tmemcpy( dest, source + start, length );\n\telse length = 0;\n\n\t// terminate the dest string\n\tdest[length] = 0;\n}\n\n/*\n==================\nCOM_Nibble\n\nReturns the 4 bit nibble for a hex character\n==================\n*/\nbyte COM_Nibble( char c )\n{\n\tif(( c >= '0' ) && ( c <= '9' ))\n\t{\n\t\t return (byte)(c - '0');\n\t}\n\n\tif(( c >= 'A' ) && ( c <= 'F' ))\n\t{\n\t\t return (byte)(c - 'A' + 0x0a);\n\t}\n\n\tif(( c >= 'a' ) && ( c <= 'f' ))\n\t{\n\t\t return (byte)(c - 'a' + 0x0a);\n\t}\n\n\treturn '0';\n}\n\n/*\n==================\nCOM_HexConvert\n\nConverts pszInput Hex string to nInputLength/2 binary\n==================\n*/\nvoid COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput )\n{\n\tconst char\t*pIn;\n\tbyte\t\t*p = pOutput;\n\tint\t\ti;\n\n\n\tfor( i = 0; i < nInputLength; i += 2 )\n\t{\n\t\tpIn = &pszInput[i];\n\t\t*p = COM_Nibble( pIn[0] ) << 4 | COM_Nibble( pIn[1] );\n\t\tp++;\n\t}\n}\n\n/*\n=============\nCOM_MemFgets\n\n=============\n*/\nchar *GAME_EXPORT COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize )\n{\n\tint\ti, last, stop;\n\n\tif( !pMemFile || !pBuffer || !filePos )\n\t\treturn NULL;\n\n\tif( *filePos >= fileSize )\n\t\treturn NULL;\n\n\ti = *filePos;\n\tlast = fileSize;\n\n\t// fgets always NULL terminates, so only read bufferSize-1 characters\n\tif( last - *filePos > ( bufferSize - 1 ))\n\t\tlast = *filePos + ( bufferSize - 1);\n\n\tstop = 0;\n\n\t// stop at the next newline (inclusive) or end of buffer\n\twhile( i < last && !stop )\n\t{\n\t\tif( pMemFile[i] == '\\n' )\n\t\t\tstop = 1;\n\t\ti++;\n\t}\n\n\t// if we actually advanced the pointer, copy it over\n\tif( i != *filePos )\n\t{\n\t\t// we read in size bytes\n\t\tint\tsize = i - *filePos;\n\n\t\t// copy it out\n\t\tmemcpy( pBuffer, pMemFile + *filePos, size );\n\n\t\t// If the buffer isn't full, terminate (this is always true)\n\t\tif( size < bufferSize ) pBuffer[size] = 0;\n\n\t\t// update file pointer\n\t\t*filePos = i;\n\t\treturn pBuffer;\n\t}\n\n\treturn NULL;\n}\n\n/*\n====================\nCache_Check\n\nconsistency check\n====================\n*/\nvoid *GAME_EXPORT Cache_Check( poolhandle_t mempool, cache_user_t *c )\n{\n\tif( !c->data )\n\t\treturn NULL;\n\n\tif( !Mem_IsAllocatedExt( mempool, c->data ))\n\t\treturn NULL;\n\n\treturn c->data;\n}\n\n/*\n=============\nCOM_LoadFileForMe\n\n=============\n*/\nbyte *GAME_EXPORT COM_LoadFileForMe( const char *filename, int *pLength )\n{\n\tstring\tname;\n\tbyte\t*pfile;\n\tfs_offset_t\tiLength;\n\n\tif( !COM_CheckString( filename ))\n\t{\n\t\tif( pLength )\n\t\t\t*pLength = 0;\n\t\treturn NULL;\n\t}\n\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tpfile = g_fsapi.LoadFileMalloc( name, &iLength, false );\n\tif( pLength ) *pLength = (int)iLength;\n\n\treturn pfile;\n}\n\n/*\n=============\nCOM_LoadFile\n\n=============\n*/\nbyte *GAME_EXPORT COM_LoadFile( const char *filename, int usehunk, int *pLength )\n{\n\treturn COM_LoadFileForMe( filename, pLength );\n}\n\n/*\n=============\nCOM_SaveFile\n\n=============\n*/\nint GAME_EXPORT COM_SaveFile( const char *filename, const void *data, int len )\n{\n\t// check for empty filename\n\tif( !COM_CheckString( filename ))\n\t\treturn false;\n\n\t// check for null data\n\tif( !data || len <= 0 )\n\t\treturn false;\n\n\treturn FS_WriteFile( filename, data, len );\n}\n\n/*\n=============\nCOM_FreeFile\n\n=============\n*/\nvoid GAME_EXPORT COM_FreeFile( void *buffer )\n{\n\tfree( buffer );\n}\n\n/*\n=============\npfnGetModelType\n\n=============\n*/\nint GAME_EXPORT pfnGetModelType( model_t *mod )\n{\n\tif( !mod ) return mod_bad;\n\treturn mod->type;\n}\n\n/*\n=============\npfnGetModelBounds\n\n=============\n*/\nvoid GAME_EXPORT pfnGetModelBounds( model_t *mod, float *mins, float *maxs )\n{\n\tif( mod )\n\t{\n\t\tif( mins ) VectorCopy( mod->mins, mins );\n\t\tif( maxs ) VectorCopy( mod->maxs, maxs );\n\t}\n\telse\n\t{\n\t\tif( mins ) VectorClear( mins );\n\t\tif( maxs ) VectorClear( maxs );\n\t}\n}\n\n/*\n=============\npfnCVarGetPointer\n\ncan return NULL\n=============\n*/\ncvar_t *GAME_EXPORT pfnCVarGetPointer( const char *szVarName )\n{\n\treturn (cvar_t *)Cvar_FindVar( szVarName );\n}\n\n/*\n=============\npfnCompareFileTime\n\n=============\n*/\nint GAME_EXPORT pfnCompareFileTime( const char *path1, const char *path2, int *retval )\n{\n\tint t1, t2;\n\t*retval = 0;\n\n\tif( !path1 || !path2 )\n\t\treturn 0;\n\n\tif(( t1 = g_fsapi.FileTime( path1, false )) == -1 )\n\t\treturn 0;\n\n\tif(( t2 = g_fsapi.FileTime( path2, false )) == -1 )\n\t\treturn 0;\n\n\tif( t1 < t2 )\n\t\t*retval = -1;\n\telse if( t1 > t2 )\n\t\t*retval = 1;\n\n\treturn 1;\n}\n\n/*\n=============\nCOM_CheckParm\n\n=============\n*/\nint GAME_EXPORT COM_CheckParm( char *parm, char **ppnext )\n{\n\tint\ti = Sys_CheckParm( parm );\n\n\tif( ppnext )\n\t{\n\t\tif( i != 0 && i < host.argc - 1 )\n\t\t\t*ppnext = (char *)host.argv[i + 1];\n\t\telse *ppnext = NULL;\n\t}\n\n\treturn i;\n}\n\n/*\n=============\npfnTime\n\n=============\n*/\nfloat GAME_EXPORT pfnTime( void )\n{\n\treturn (float)Sys_DoubleTime();\n}\n\nqboolean COM_IsSafeFileToDownload( const char *filename )\n{\n\tchar\t\tlwrfilename[4096];\n\tconst char\t*last;\n\tconst char\t*ext;\n\tsize_t\tlen;\n\tint\t\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn false;\n\n\text = COM_FileExtension( filename );\n\tlen = Q_strlen( filename );\n\n\t// only allow extensionless files that start with !MD5\n\tif( !Q_strncmp( filename, \"!MD5\", 4 ))\n\t{\n\t\tif( COM_CheckStringEmpty( ext ))\n\t\t\treturn false;\n\n\t\tlen = Q_strlen( filename );\n\n\t\tif( len != 36 )\n\t\t\treturn false;\n\n\t\tfor( i = 4; i < len; i++ )\n\t\t{\n\t\t\tif(( filename[i] >= '0' && filename[i] <= '9' ) ||\n\t\t\t\t( filename[i] >= 'A' && filename[i] <= 'F' ))\n\t\t\t\tcontinue;\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tfor( i = 0; i < len; i++ )\n\t{\n\t\tif( !isprint( filename[i] ))\n\t\t\treturn false;\n\t}\n\n\tQ_strnlwr( filename, lwrfilename, sizeof( lwrfilename ));\n\text = COM_FileExtension( lwrfilename );\n\n\tif( Q_strpbrk( lwrfilename, \"\\\\:~\" ) || Q_strstr( lwrfilename, \"..\" ))\n\t\treturn false;\n\n\tif( lwrfilename[0] == '/' )\n\t\treturn false;\n\n\tlast = Q_strrchr( lwrfilename, '.' );\n\n\tif( last == NULL )\n\t\treturn false;\n\n\tif( Q_strlen( last ) != 4 )\n\t\treturn false;\n\n\tfor( i = 0; i < ARRAYSIZE( file_exts ); i++ )\n\t{\n\t\tif( !Q_stricmp( ext, file_exts[i] ))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nchar *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline )\n{\n\tsize_t\tsize;\n\tchar\t*b;\n\n\tif( !s ) return NULL;\n\tif( !mempool ) mempool = host.mempool;\n\n\tsize = Q_strlen( s ) + 1;\n\tb = _Mem_Alloc( mempool, size, false, filename, fileline );\n\tQ_strncpy( b, s, size );\n\n\treturn b;\n}\n\n/*\n======================\n\nCOMMON EXPORT STUBS\n\n======================\n*/\n\n\n/*\n=============\npfnSequenceGet\n\nused by CS:CZ\n=============\n*/\nvoid *GAME_EXPORT pfnSequenceGet( const char *fileName, const char *entryName )\n{\n\tMsg( \"%s: file %s, entry %s\\n\", __func__, fileName, entryName );\n\n\treturn NULL;\n}\n\n/*\n=============\npfnSequencePickSentence\n\nused by CS:CZ\n=============\n*/\nvoid *GAME_EXPORT pfnSequencePickSentence( const char *groupName, int pickMethod, int *picked )\n{\n\tMsg( \"%s: group %s, pickMethod %i\\n\", __func__, groupName, pickMethod );\n\n\treturn NULL;\n\n}\n\n/*\n=============\npfnIsCareerMatch\n\nused by CS:CZ (client stub)\n=============\n*/\nint GAME_EXPORT pfnIsCareerMatch( void )\n{\n\treturn 0;\n}\n\n/*\n=============\npfnProcessTutorMessageDecayBuffer\n\nonly exists in PlayStation version\n=============\n*/\nvoid GAME_EXPORT pfnProcessTutorMessageDecayBuffer( int *buffer, int bufferLength )\n{\n}\n\n/*\n=============\npfnConstructTutorMessageDecayBuffer\n\nonly exists in PlayStation version\n=============\n*/\nvoid GAME_EXPORT pfnConstructTutorMessageDecayBuffer( int *buffer, int bufferLength )\n{\n}\n\n/*\n=============\npfnResetTutorMessageDecayData\n\nonly exists in PlayStation version\n=============\n*/\nvoid GAME_EXPORT pfnResetTutorMessageDecayData( void )\n{\n}\n\n#if XASH_ENGINE_TESTS\n\n#include \"tests.h\"\n\n#ifdef USE_ASAN\n#include <sanitizer/asan_interface.h>\n#endif\n\nstatic void Test_LZSS( void )\n{\n\tchar poison1[8192];\n\tbyte in[256];\n\tchar poison2[8192];\n\tbyte out[256];\n\tchar poison3[8192];\n\n\tlzss_header_t *hdr = (lzss_header_t *)in;\n\tuint result;\n\n\tconst byte compressed[] =\n\t{\n\t\t0x4c, 0x5a, 0x53, 0x53, 0x1a, 0x00, 0x00, 0x00, 0x00,\n\t\t0x44, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6c, 0x00,\n\t\t0x69, 0x6b, 0x65, 0x20, 0x77, 0x68, 0x61, 0x74, 0x41,\n\t\t0x00, 0xd4, 0x73, 0x65, 0x65, 0x3f, 0x00, 0x00, 0x00,\n\t};\n\tconst char decompressed[] = \"Do you like what you see?\";\n\n#ifdef USING_ASAN\n\tASAN_POISON_MEMORY_REGION( poison1, sizeof( poison1 ));\n\tASAN_POISON_MEMORY_REGION( poison2, sizeof( poison2 ));\n\tASAN_POISON_MEMORY_REGION( poison3, sizeof( poison3 ));\n#endif\n\n\thdr->size = sizeof( in ) - sizeof( *hdr );\n\thdr->id = LZSS_ID;\n\n\tmemset( in + sizeof( *hdr ), 0xff, sizeof( in ) - sizeof( *hdr ));\n\tresult = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));\n\tTASSERT_EQi( result, 0 );\n\n\tmemset( in + sizeof( *hdr ), 0x00, sizeof( in ) - sizeof( *hdr ));\n\tresult = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));\n\tTASSERT_EQi( result, 0 );\n\n\thdr->size = 1;\n\thdr->id = LZSS_ID;\n\tresult = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));\n\tTASSERT_EQi( result, 0 );\n\n\thdr->size = 999;\n\thdr->id = LZSS_ID;\n\tresult = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));\n\tTASSERT_EQi( result, 0 );\n\n\thdr->size = sizeof( in ) - sizeof( *hdr );\n\thdr->id = 0xa1ba;\n\tresult = LZSS_Decompress( in, out, sizeof( in ), sizeof( out ));\n\tTASSERT_EQi( result, 0 );\n\n\tresult = LZSS_Decompress( compressed, out, sizeof( compressed ), sizeof( out ));\n\tTASSERT_EQi( result, 26 );\n\tTASSERT_STR( out, decompressed );\n}\n\nvoid Test_RunCommon( void )\n{\n\tMsg( \"Checking COM_IsSafeFileToDownload...\\n\" );\n\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"models/bsg_props/[hl-lab.ru]bush.mdl\" ), true );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"!MD5AAB5E8B307672DA86FBD10AC302BC732\" ), true );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"!MD56f1ffd8c96bd64c9c27955309f6ecfe6\" ), false );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"!MD5AAB5E8B307672DA86FBD10AC302B.exe\" ), false );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"!MD5/../../valve/resource/GameMenu.res\" ), false );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"not-a-virus-trust-me.bat\" ), false );\n\tTASSERT_EQi( COM_IsSafeFileToDownload( \"a-texture.png\" ), true );\n\n\tMsg( \"Checking LZSS_Decompress...\\n\" );\n\tTest_LZSS();\n}\n#endif\n"
  },
  {
    "path": "engine/common/common.h",
    "content": "/*\ncommon.h - definitions common between client and server\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef COMMON_H\n#define COMMON_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n===================================================================================================================================\nLegend:\n\nINTERNAL RESOURCE\t\t\t- function contain hardcoded path to resource that engine required (optional in most cases)\nOBSOLETE, UNUSED\t\t\t- this function no longer used and leaved here for keep binary compatibility\nTODO\t\t\t\t- some functionality not impemented but planned\nFIXME\t\t\t\t- code doesn't working properly in some rare cases\nHACKHACK\t\t\t\t- unexpected behavior on some input params (or something like)\nBUGBUG\t\t\t\t- code doesn't working properly in most cases!\nTESTTEST\t\t\t\t- this code may be unstable and needs to be more tested\ng-cont:\t\t\t\t- notes from engine author\nXASH SPECIFIC\t\t\t- sort of hack that works only in Xash3D not in GoldSrc\n===================================================================================================================================\n*/\n\n#include \"port.h\"\n\n#include \"backends.h\"\n#include \"defaults.h\"\n\n#include <stdio.h>\n#include <stdlib.h> // rand, adbs\n#include <stdarg.h> // va\n\n#if !XASH_WIN32\n#include <stddef.h> // size_t\n#else\n#include <sys/types.h> // off_t\n#endif\n\n// configuration\n\n//\n// check if selected backend not allowed\n//\n#if XASH_TIMER == TIMER_NULL\n\t#error \"Please select timer backend\"\n#endif\n\n#if !XASH_DEDICATED\n\t#if XASH_VIDEO == VIDEO_NULL\n\t\t#error \"Please select video backend\"\n\t#endif\n#endif\n\n#ifndef XASH_SDL\n\n#if XASH_TIMER == TIMER_SDL || XASH_VIDEO == VIDEO_SDL || XASH_SOUND == SOUND_SDL || XASH_INPUT == INPUT_SDL\n#error \"SDL backends without XASH_SDL not allowed\"\n#endif\n\n#endif\n\n#define HACKS_RELATED_HLMODS\t\t// some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time\n\nenum dev_level_e\n{\n\tDEV_NONE = 0,\n\tDEV_NORMAL,\n\tDEV_EXTENDED\n};\n\ntypedef enum instance_e\n{\n\tHOST_NORMAL,\t// listen server, singleplayer\n\tHOST_DEDICATED,\n} instance_t;\n\n#ifdef XASH_DEDICATED\n#define Host_IsDedicated() ( true )\n#else\n#define Host_IsDedicated() ( host.type == HOST_DEDICATED )\n#endif\n\n#include \"system.h\"\n#include \"com_model.h\"\n#include \"com_strings.h\"\n#include \"crtlib.h\"\n#define FSCALLBACK_OVERRIDE_OPEN\n#define FSCALLBACK_OVERRIDE_LOADFILE\n#define FSCALLBACK_OVERRIDE_MALLOC_LIKE\n#include \"fscallback.h\"\n#include \"cvar.h\"\n#include \"con_nprint.h\"\n#include \"crclib.h\"\n#include \"ref_api.h\"\n\n// PERFORMANCE INFO\n#define MIN_FPS         20.0f    // host minimum fps value for maxfps.\n#define MAX_FPS_SOFT    200.0f   // soft limit for maxfps.\n#define MAX_FPS_HARD    1000.0f  // multiplayer hard limit for maxfps.\n#define HOST_FPS\t\t100.0f\t\t// multiplayer games typical fps\n\n#define MAX_FRAMETIME\t0.25f\n#define MIN_FRAMETIME\t0.0001f\n#define GAME_FPS\t\t20.0f\n\n#define MAX_CMD_TOKENS\t80\t\t// cmd tokens\n#define MAX_ENTNUMBER\t99999\t\t// for server and client parsing\n#define MAX_HEARTBEAT\t-99999\t\t// connection time\n#define QCHAR_WIDTH\t\t16\t\t// font width\n\n#define CIN_MAIN\t\t0\n#define CIN_LOGO\t\t1\n\n#if XASH_LOW_MEMORY == 0\n#define MAX_DECALS\t\t512\t// touching TE_DECAL messages, etc\n#define MAX_STATIC_ENTITIES\t3096\t// static entities that moved on the client when level is spawn\n#elif XASH_LOW_MEMORY == 1\n#define MAX_DECALS\t\t512\t// touching TE_DECAL messages, etc\n#define MAX_STATIC_ENTITIES\t128\t// static entities that moved on the client when level is spawn\n#elif XASH_LOW_MEMORY == 2\n#define MAX_DECALS\t\t256\t// touching TE_DECAL messages, etc\n#define MAX_STATIC_ENTITIES\t32\t// static entities that moved on the client when level is spawn\n#endif\n\n#define MAX_SERVERINFO_STRING 512  // server handles too many settings. expand to 1024?\n#define MAX_PRINT_MSG         8192 // how many symbols can handle single call of Con_Printf or Con_DPrintf\n#define MAX_TOKEN             2048 // parse token length\n#define MAX_USERMSG_LENGTH    2048 // don't modify it's relies on a client-side definitions\n\n#define GameState\t\t(&host.game)\n\n#define FORCE_DRAW_VERSION_TIME 5.0 // draw version for 5 seconds\n\n#ifdef _DEBUG\nvoid DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage );\n#define Assert( f )\t\tDBG_AssertFunction( f, #f, __FILE__, __LINE__, NULL )\n#else\n#define Assert( f )\n#endif\n\nextern convar_t\tgl_vsync;\nextern convar_t\tscr_loading;\nextern convar_t\tscr_download;\nextern convar_t\tcmd_scripting;\nextern convar_t\thost_allow_materials;\nextern convar_t\thost_developer;\nextern convar_t\thost_limitlocal;\nextern convar_t\thost_maxfps;\nextern convar_t\tfps_override;\nextern convar_t\tsys_timescale;\nextern convar_t\tcl_filterstuffcmd;\nextern convar_t\trcon_password;\nextern convar_t\thpk_custom_file;\nextern convar_t\tcon_gamemaps;\n\n#define Mod_AllowMaterials() ( host_allow_materials.value != 0.0f && !FBitSet( host.features, ENGINE_DISABLE_HDTEXTURES ))\n\n/*\n==============================================================\n\nHOST INTERFACE\n\n==============================================================\n*/\n/*\n========================================================================\n\nGAMEINFO stuff\n\ninternal shared gameinfo structure (readonly for engine parts)\n========================================================================\n*/\ntypedef enum host_status_e\n{\n\tHOST_INIT = 0,\t// initalize operations\n\tHOST_FRAME,\t// host running\n\tHOST_SHUTDOWN,\t// shutdown operations\n\tHOST_ERR_FATAL,\t// sys error\n\tHOST_SLEEP,\t// sleeped by different reason, e.g. minimize window\n\tHOST_NOFOCUS,\t// same as HOST_FRAME, but disable mouse\n\tHOST_CRASHED\t// an exception handler called\n} host_status_t;\n\ntypedef enum host_state_e\n{\n\tSTATE_RUNFRAME = 0,\n\tSTATE_LOAD_LEVEL,\n\tSTATE_LOAD_GAME,\n\tSTATE_CHANGELEVEL,\n\tSTATE_GAME_SHUTDOWN,\n} host_state_t;\n\ntypedef struct game_status_e\n{\n\thost_state_t\tcurstate;\n\thost_state_t\tnextstate;\n\tchar\t\tlevelName[MAX_QPATH];\n\tchar\t\tlandmarkName[MAX_QPATH];\n\tqboolean\t\tbackgroundMap;\n\tqboolean\t\tloadGame;\n\tqboolean\t\tnewGame;\t\t// unload the server.dll before start a new map\n} game_status_t;\n\ntypedef enum keydest_e\n{\n\tkey_console = 0,\n\tkey_game,\n\tkey_menu,\n\tkey_message\n} keydest_t;\n\ntypedef enum rdtype_e\n{\n\tRD_NONE = 0,\n\tRD_CLIENT,\n\tRD_PACKET\n} rdtype_t;\n\n#include \"net_ws.h\"\n\n// console field\ntypedef struct field_e\n{\n\tstring\t\tbuffer;\n\tint\t\tcursor;\n\tint\t\tscroll;\n\tint\t\twidthInChars;\n} field_t;\n\ntypedef struct host_redirect_s\n{\n\trdtype_t target;\n\tchar     *buffer;\n\tsize_t   buffersize;\n\tnetadr_t address;\n\tvoid     (*flush)( netadr_t adr, rdtype_t target, char *buffer );\n\tint      lines;\n} host_redirect_t;\n\ntypedef struct soundlist_e\n{\n\tchar\t\tname[MAX_QPATH];\n\tshort\t\tentnum;\n\tvec3_t\t\torigin;\n\tfloat\t\tvolume;\n\tfloat\t\tattenuation;\n\tqboolean\t\tlooping;\n\tbyte\t\tchannel;\n\tbyte\t\tpitch;\n\tbyte\t\twordIndex;\t// current playing word in sentence\n\tdouble\t\tsamplePos;\n\tdouble\t\tforcedEnd;\n} soundlist_t;\n\ntypedef enum bugcomp_e\n{\n\t// reverts fix for pfnPEntityOfEntIndex for bug compatibility with GoldSrc\n\tBUGCOMP_PENTITYOFENTINDEX_FLAG = BIT( 0 ),\n\n\t// rewrites mod's attempts to write GoldSrc-specific messages into Xash protocol\n\t// (new wrappers are added by request)\n\tBUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG = BIT( 1 ),\n\n\t// makes sound with no attenuation spatialized, like in GoldSrc\n\tBUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE = BIT( 2 ),\n\n\t// returns full path to the game directory in server's pfnGetGameDir call\n\tBUGCOMP_GET_GAME_DIR_FULL_PATH = BIT( 3 ),\n} bugcomp_t;\n\ntypedef struct host_parm_s\n{\n\t// ==== shared through RefAPI's ref_host_t\n\tdouble realtime;    // host.curtime\n\tdouble frametime;   // time between engine frames\n\tuint   features;    // custom features that enables by mod-maker request\n\t// ==== shared through RefAPI's ref_host_t\n\n\thost_status_t status;           // global host state\n\tgame_status_t game;             // game manager\n\tinstance_t    type;             // running at\n\tpoolhandle_t  mempool;          // static mempool for misc allocations\n\tpoolhandle_t  imagepool;        // imagelib mempool\n\tpoolhandle_t  soundpool;        // soundlib mempool\n\tstring        downloadfile;     // filename to be downloading\n\tint           downloadcount;    // how many files remain to downloading\n\tchar          deferred_cmd[128];// deferred commands\n\n\thost_redirect_t rd; // remote console\n\n\tvoid   *hWnd;          // main window\n\n\t// command line parms\n\tchar **argv;\n\tint\t argc;\n\n\tuint     framecount;     // global framecount\n\tuint     errorframe;     // to prevent multiple host error\n\tuint32_t bugcomp; // bug compatibility level, for very \"special\" games\n\tdouble   realframetime;  // for some system events, e.g. console animations\n\tdouble   starttime;      // measure time to first frame\n\tdouble   pureframetime;  // count of sleeps can be inserted between frames\n\tdouble   force_draw_version_time;\n\n\tchar   draw_decals[MAX_DECALS][MAX_QPATH]; // list of unique decal indexes\n\tvec3_t player_mins[MAX_MAP_HULLS];         // 4 hulls allowed\n\tvec3_t player_maxs[MAX_MAP_HULLS];         // 4 hulls allowed\n\n\t// for CL_{Push,Pop}TraceBounds\n\tvec3_t player_mins_backup[MAX_MAP_HULLS];\n\tvec3_t player_maxs_backup[MAX_MAP_HULLS];\n\tqboolean trace_bounds_pushed;\n\n\tqboolean allow_console;       // allow console in dev-mode or multiplayer game\n\tqboolean allow_console_init;  // initial value to allow the console\n\tqboolean key_overstrike;      // key overstrike mode\n\tqboolean stuffcmds_pending;   // should execute stuff commands\n\tqboolean allow_cheats;        // this host will allow cheating\n\tqboolean change_game;         // initialize when game is changed\n\tqboolean mouse_visible;       // vgui override cursor control (never change outside Platform_SetCursorType!)\n\tqboolean shutdown_issued;     // engine is shutting down\n\tqboolean apply_game_config;   // when true apply only to game cvars and ignore all other commands\n\tqboolean apply_opengl_config; // when true apply only to opengl cvars and ignore all other commands\n\tqboolean config_executed;     // a bit who indicated was config.cfg already executed e.g. from valve.rc\n#if XASH_DLL_LOADER\n\tqboolean enabledll;\n#endif\n\tqboolean textmode;\n\n\t// some settings were changed and needs to global update\n\tqboolean userinfo_changed;\n\tqboolean movevars_changed;\n\tqboolean renderinfo_changed;\n\n\t// for IN_MouseMove() easy access\n\tint      window_center_x;\n\tint      window_center_y;\n\tstring   gamedll;\n\tstring   clientlib;\n\tstring   menulib;\n} host_parm_t;\n\nextern host_parm_t\thost;\n\n#define CMD_SERVERDLL   BIT( 0 ) // added by server.dll\n#define CMD_CLIENTDLL   BIT( 1 ) // added by client.dll\n#define CMD_GAMEUIDLL   BIT( 2 ) // added by GameUI.dll\n#define CMD_PRIVILEGED  BIT( 3 ) // only available in privileged mode\n#define CMD_FILTERABLE  BIT( 4 ) // filtered in unprivileged mode if cl_filterstuffcmd is 1\n#define CMD_REFDLL      BIT( 5 ) // added by ref.dll\n#define CMD_OVERRIDABLE BIT( 6 ) // can be removed by DLLs if name matches\n\ntypedef void (*xcommand_t)( void );\n\n//\n// zone.c\n//\nvoid Memory_Init( void );\nvoid _Mem_Free( void *data, const char *filename, int fileline );\nvoid *_Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline )\n\tALLOC_CHECK( 3 ) WARN_UNUSED_RESULT;\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n\tALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\npoolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline )\n\tWARN_UNUSED_RESULT;\nvoid _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline );\nvoid _Mem_EmptyPool( poolhandle_t poolptr, const char *filename, int fileline );\nvoid _Mem_Check( const char *filename, int fileline );\nqboolean Mem_IsAllocatedExt( poolhandle_t poolptr, void *data );\nvoid Mem_PrintList( size_t minallocationsize );\nvoid Mem_PrintStats( void );\n\n#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ )\n#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ )\n#define Mem_Realloc( pool, ptr, size ) _Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ )\n#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ )\n#define Mem_AllocPool( name ) _Mem_AllocPool( name, __FILE__, __LINE__ )\n#define Mem_FreePool( pool ) _Mem_FreePool( pool, __FILE__, __LINE__ )\n#define Mem_EmptyPool( pool ) _Mem_EmptyPool( pool, __FILE__, __LINE__ )\n#define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem )\n#define Mem_Check() _Mem_Check( __FILE__, __LINE__ )\n\n//\n// filesystem_engine.c\n//\nvoid FS_Init( const char *basedir );\nvoid FS_Shutdown( void );\nvoid *FS_GetNativeObject( const char *obj );\nint FS_Close( file_t *file );\nsearch_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nfile_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly )\n\tMALLOC_LIKE( FS_Close, 1 ) WARN_UNUSED_RESULT;\nbyte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nbyte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nvoid FS_Rescan_f( void );\nvoid FS_LoadGameInfo( void );\nvoid FS_SaveVFSConfig( void );\n\n//\n// cmd.c\n//\ntypedef struct cmd_s cmd_t;\n\nstatic inline int GAME_EXPORT Cmd_Argc( void )\n{\n\textern int cmd_argc;\n\treturn cmd_argc;\n}\n\nstatic inline const char *GAME_EXPORT RETURNS_NONNULL Cmd_Argv( int arg )\n{\n\textern int cmd_argc;\n\textern char *cmd_argv[MAX_CMD_TOKENS];\n\n\tif((uint)arg >= cmd_argc )\n\t\treturn \"\";\n\treturn cmd_argv[arg];\n}\n\nstatic inline const char *GAME_EXPORT RETURNS_NONNULL Cmd_Args( void )\n{\n\textern const char *cmd_args;\n\n\treturn cmd_args;\n}\n\nvoid Cbuf_Clear( void );\nvoid Cbuf_AddText( const char *text );\nvoid Cbuf_AddTextf( const char *text, ... ) FORMAT_CHECK( 1 );\nvoid Cbuf_AddFilteredText( const char *text );\nvoid Cbuf_InsertText( const char *text );\nvoid Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len );\nvoid Cbuf_ExecStuffCmds( void );\nvoid Cbuf_Execute (void);\nqboolean Cmd_CurrentCommandIsPrivileged( void );\nvoid Cmd_Init( void );\nvoid Cmd_Shutdown( void );\nvoid Cmd_Unlink( int group );\nint Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd_desc, int flags, const char *funcname );\n\nstatic inline int Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc )\n{\n\treturn Cmd_AddCommandEx( cmd_name, function, cmd_desc, 0, __func__ );\n}\n\nstatic inline int Cmd_AddRestrictedCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc )\n{\n\treturn Cmd_AddCommandEx( cmd_name, function, cmd_desc, CMD_PRIVILEGED, __func__ );\n}\n\nstatic inline int Cmd_AddCommandWithFlags( const char *cmd_name, xcommand_t function, const char *cmd_desc, int flags )\n{\n\treturn Cmd_AddCommandEx( cmd_name, function, cmd_desc, flags, __func__ );\n}\n\nvoid Cmd_RemoveCommand( const char *cmd_name );\ncmd_t *Cmd_Exists( const char *cmd_name );\nvoid Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback );\nint Cmd_ListMaps( search_t *t , char *lastmapname, size_t len );\nvoid Cmd_TokenizeString( const char *text );\nvoid Cmd_ExecuteString( const char *text );\nvoid Cmd_ForwardToServer( void );\nvoid Cmd_Escape( char *newCommand, const char *oldCommand, int len );\n\n\n//\n// imagelib\n//\n#include \"com_image.h\"\n\nvoid Image_Setup( void );\nvoid Image_Init( void );\nvoid Image_Shutdown( void );\nvoid Image_AddCmdFlags( uint flags );\nvoid FS_FreeImage( rgbdata_t *pack );\nrgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) MALLOC_LIKE( FS_FreeImage, 1 ) WARN_UNUSED_RESULT;\nqboolean FS_SaveImage( const char *filename, rgbdata_t *pix );\nrgbdata_t *FS_CopyImage( rgbdata_t *in ) MALLOC_LIKE( FS_FreeImage, 1 ) WARN_UNUSED_RESULT;\nextern const bpc_desc_t PFDesc[];\t// image get pixelformat\nqboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, float reserved );\nvoid Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int pal_size );\nvoid Image_SetForceFlags( uint flags );\t// set image force flags on loading\nqboolean Image_CustomPalette( void );\nvoid Image_ClearForceFlags( void );\nvoid Image_SetMDLPointer( byte *p );\nvoid Image_CheckPaletteQ1( void );\n\n/*\n========================================================================\n\ninternal sound format\n\ntypically expanded to wav buffer\n========================================================================\n*/\ntypedef enum sndformat_e\n{\n\tWF_UNKNOWN = 0,\n\tWF_PCMDATA,\n\tWF_MPGDATA,\n\tWF_VORBISDATA,\n\tWF_OPUSDATA,\n\tWF_TOTALCOUNT,\t// must be last\n} sndformat_t;\n\n// wavdata output flags\ntypedef enum sndFlags_e\n{\n\t// wavdata->flags\n\tSOUND_LOOPED\t= BIT( 0 ),\t// this is looped sound (contain cue markers)\n\tSOUND_STREAM\t= BIT( 1 ),\t// this is a streaminfo, not a real sound\n\n\t// Sound_Process manipulation flags\n\tSOUND_RESAMPLE\t= BIT( 12 ),\t// resample sound to specified rate\n} sndFlags_t;\n\ntypedef struct wavdata_s\n{\n\tsize_t  size;      // for bounds checking\n\tuint    loopStart; // offset at this point sound will be looping while playing more than only once\n\tuint    samples;   // total samplecount in wav\n\tuint    type;      // compression type\n\tuint    flags;     // misc sound flags\n\tword    rate;      // num samples per second (e.g. 11025 - 11 khz)\n\tbyte    width;     // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2)\n\tbyte    channels;  // num channels (1 - mono, 2 - stereo)\n\tbyte    buffer[];  // sound buffer\n} wavdata_t;\n\n//\n// soundlib\n//\ntypedef struct stream_s stream_t;\nvoid Sound_Init( void );\nvoid Sound_Shutdown( void );\nvoid FS_FreeSound( wavdata_t *pack );\nvoid FS_FreeStream( stream_t *stream );\nwavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) MALLOC_LIKE( FS_FreeSound, 1 ) WARN_UNUSED_RESULT;\nstream_t *FS_OpenStream( const char *filename ) MALLOC_LIKE( FS_FreeStream, 1 ) WARN_UNUSED_RESULT;\nint FS_ReadStream( stream_t *stream, int bytes, void *buffer );\nint FS_SetStreamPos( stream_t *stream, int newpos );\nint FS_GetStreamPos( stream_t *stream );\nqboolean Sound_Process( wavdata_t **wav, int rate, int width, int channels, uint flags );\nuint Sound_GetApproxWavePlayLen( const char *filepath );\nqboolean Sound_SupportedFileFormat( const char *fileext );\n\n//\n// host.c\n//\ntypedef void( *pfnChangeGame )( const char *progname );\n\nqboolean Host_IsQuakeCompatible( void );\nvoid Host_ShutdownWithReason( const char *reason );\nint EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func );\nvoid Host_EndGame( qboolean abort, const char *message, ... ) FORMAT_CHECK( 2 );\nvoid Host_AbortCurrentFrame( void ) NORETURN;\nvoid Host_WriteServerConfig( const char *name );\nvoid Host_WriteOpenGLConfig( void );\nvoid Host_WriteVideoConfig( void );\nvoid Host_WriteConfig( void );\nvoid Host_Error( const char *error, ... ) FORMAT_CHECK( 1 );\nvoid Host_ValidateEngineFeatures( uint32_t mask, uint32_t features );\nvoid Host_Frame( double time );\nvoid Host_Credits( void );\nvoid Host_ExitInMain( void );\n\n//\n// host_state.c\n//\nvoid COM_InitHostState( void );\nvoid COM_NewGame( char const *pMapName );\nvoid COM_LoadLevel( char const *pMapName, qboolean background );\nvoid COM_LoadGame( char const *pSaveFileName );\nvoid COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background );\nvoid COM_Frame( double time );\n\n/*\n==============================================================\n\nCLIENT / SERVER SYSTEMS\n\n==============================================================\n*/\n#if !XASH_DEDICATED\nvoid CL_Init( void );\nvoid CL_Shutdown( void );\nvoid Host_ClientBegin( void );\nvoid Host_ClientFrame( void );\nint CL_Active( void );\n#else\nstatic inline void CL_Init( void ) { }\nstatic inline void CL_Shutdown( void ) { }\nstatic inline void Host_ClientBegin( void ) { Cbuf_Execute(); }\nstatic inline void Host_ClientFrame( void ) { }\nstatic inline int CL_Active( void ) { return 0; }\n#endif\n\nvoid SV_Init( void );\nvoid SV_Shutdown( const char *finalmsg );\nvoid SV_ShutdownFilter( void );\nvoid Host_ServerFrame( void );\nqboolean SV_Active( void );\n\n/*\n==============================================================\n\n\tSHARED ENGFUNCS\n\n==============================================================\n*/\nchar *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize );\nvoid COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput );\nbyte COM_Nibble( char c );\nint COM_SaveFile( const char *filename, const void *data, int len );\nbyte *COM_LoadFileForMe( const char *filename, int *pLength ) MALLOC_LIKE( free, 1 );\nqboolean COM_IsSafeFileToDownload( const char *filename );\ncvar_t *pfnCVarGetPointer( const char *szVarName );\nint pfnDrawConsoleString( int x, int y, char *string );\nvoid pfnDrawSetTextColor( float r, float g, float b );\nvoid pfnDrawConsoleStringLen( const char *pText, int *length, int *height );\nvoid *Cache_Check( poolhandle_t mempool, struct cache_user_s *c );\nvoid COM_TrimSpace( const char *source, char *dest );\nvoid pfnGetModelBounds( model_t *mod, float *mins, float *maxs );\nint COM_CheckParm( char *parm, char **ppnext );\nint pfnGetModelType( model_t *mod );\nint pfnIsMapValid( char *filename );\nvoid Con_Reportf( const char *szFmt, ... ) FORMAT_CHECK( 1 );\nvoid Con_DPrintf( const char *fmt, ... ) FORMAT_CHECK( 1 );\nvoid Con_Printf( const char *szFmt, ... ) FORMAT_CHECK( 1 );\nint pfnNumberOfEntities( void );\nint pfnIsInGame( void );\nfloat pfnTime( void );\n#define copystring( s ) _copystring( host.mempool, s, __FILE__, __LINE__ )\n#define copystringpool( pool, s ) _copystring( pool, s, __FILE__, __LINE__ )\n#define SV_CopyString( s ) _copystring( svgame.stringspool, s, __FILE__, __LINE__ )\n#define freestring( s ) if( s != NULL ) { Mem_Free( s ); s = NULL; }\nchar *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline );\n\n// CS:CS engfuncs (stubs)\nvoid *pfnSequenceGet( const char *fileName, const char *entryName );\nvoid *pfnSequencePickSentence( const char *groupName, int pickMethod, int *picked );\nint pfnIsCareerMatch( void );\n\n// Decay engfuncs (stubs)\nvoid pfnConstructTutorMessageDecayBuffer( int *buffer, int buflen );\nvoid pfnProcessTutorMessageDecayBuffer( int *buffer, int bufferLength );\nvoid pfnResetTutorMessageDecayData( void );\n\n\n/*\n==============================================================\n\n\tMISC COMMON FUNCTIONS\n\n==============================================================\n*/\n#define Z_Malloc( size )\t\tMem_Malloc( host.mempool, size )\n#define Z_Calloc( size )\t\tMem_Calloc( host.mempool, size )\n#define Z_Realloc( ptr, size )\tMem_Realloc( host.mempool, ptr, size )\n#define Z_Free( ptr )\t\tif( ptr != NULL ) Mem_Free( ptr )\n\n//\n// con_utils.c\n//\nvoid Con_CompleteCommand( field_t *field );\nvoid Cmd_AutoComplete( char *complete_string );\nvoid Cmd_AutoCompleteClear( void );\nvoid Host_InitializeConfig( file_t *f, const char *config, const char *description );\nvoid Host_FinalizeConfig( file_t *f, const char *config );\n\n//\n// custom.c\n//\nvoid COM_ClearCustomizationList( customization_t *pHead, qboolean bCleanDecals );\nqboolean COM_CreateCustomization( customization_t *pHead, resource_t *pRes, int playernum, int flags, customization_t **pCust, int *nLumps );\nint COM_SizeofResourceList( resource_t *pList, resourceinfo_t *ri );\n\n//\n// cfgscript.c\n//\nint CSCR_LoadDefaultCVars( const char *scriptfilename );\nint CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename );\n\n//\n// hpak.c\n//\nconst char *COM_ResourceTypeFromIndex( int index );\nvoid HPAK_Init( void );\nqboolean HPAK_GetDataPointer( const char *filename, struct resource_s *pRes, byte **buffer, int *size );\nqboolean HPAK_ResourceForHash( const char *filename, byte *hash, struct resource_s *pRes );\nvoid HPAK_AddLump( qboolean queue, const char *filename, struct resource_s *pRes, byte *data, file_t *f );\nvoid HPAK_RemoveLump( const char *name, resource_t *resource );\nvoid HPAK_CheckIntegrity( const char *filename );\nvoid HPAK_CheckSize( const char *filename );\nvoid HPAK_FlushHostQueue( void );\n\n#include \"avi/avi.h\"\n\n//\n// input.c\n//\n\n#define INPUT_DEVICE_MOUSE (1<<0)\n#define INPUT_DEVICE_TOUCH (1<<1)\n#define INPUT_DEVICE_JOYSTICK (1<<2)\n#define INPUT_DEVICE_VR (1<<3)\n\n\ntypedef enum connprotocol_e\n{\n\tPROTO_CURRENT = 0, // Xash3D 49\n\tPROTO_LEGACY, // Xash3D 48\n\tPROTO_QUAKE, // Quake 15\n\tPROTO_GOLDSRC, // GoldSrc 48\n} connprotocol_t;\n\n// shared calls\nstruct physent_s;\nstruct sv_client_s;\ntypedef struct sizebuf_s sizebuf_t;\nint SV_GetMaxClients( void );\n\n#if !XASH_DEDICATED\nqboolean CL_Initialized( void );\nqboolean CL_IsInGame( void );\nqboolean CL_IsInConsole( void );\nqboolean CL_IsIntermission( void );\nqboolean CL_DisableVisibility( void );\nqboolean CL_IsRecordDemo( void );\nqboolean CL_IsPlaybackDemo( void );\nqboolean UI_CreditsActive( void );\nint CL_GetMaxClients( void );\n#else\nstatic inline qboolean CL_Initialized( void ) { return false; }\nstatic inline qboolean CL_IsInGame( void ) { return true; } // always true for dedicated\nstatic inline qboolean CL_IsInConsole( void ) { return false; }\nstatic inline qboolean CL_IsIntermission( void ) { return false; }\nstatic inline qboolean CL_DisableVisibility( void ) { return false; }\nstatic inline qboolean CL_IsRecordDemo( void ) { return false; }\nstatic inline qboolean CL_IsPlaybackDemo( void ) { return false; }\nstatic inline qboolean UI_CreditsActive( void ) { return false; }\nstatic inline int CL_GetMaxClients( void ) { return SV_GetMaxClients(); }\n#endif\n\nchar *CL_Userinfo( void );\nvoid CL_CharEvent( int key );\nbyte *COM_LoadFile( const char *filename, int usehunk, int *pLength ) MALLOC_LIKE( free, 1 );\nstruct cmd_s *Cmd_GetFirstFunctionHandle( void );\nstruct cmd_s *Cmd_GetNextFunctionHandle( struct cmd_s *cmd );\nstruct cmdalias_s *Cmd_AliasGetList( void );\nconst char *Cmd_GetName( struct cmd_s *cmd );\nvoid Log_Printf( const char *fmt, ... ) FORMAT_CHECK( 1 );\nvoid SV_BroadcastCommand( const char *fmt, ... ) FORMAT_CHECK( 1 );\nvoid SV_BroadcastPrintf( struct sv_client_s *ignore, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid CL_ClearStaticEntities( void );\nqboolean S_StreamGetCurrentState( char *currentTrack, size_t currentTrackSize, char *loopTrack, size_t loopTrackSize, int *position );\nvoid CL_ServerCommand( qboolean reliable, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid CL_UpdateInfo( const char *key, const char *value );\nvoid CL_HudMessage( const char *pMessage );\nconst char *CL_MsgInfo( int cmd );\nvoid SV_DrawDebugTriangles( void );\nvoid SV_DrawOrthoTriangles( void );\ndouble CL_GetDemoFramerate( void );\nvoid CL_StopPlayback( void );\nqboolean SV_Initialized( void );\nvoid CL_ProcessFile( qboolean successfully_received, const char *filename );\nint SV_GetSaveComment( const char *savename, char *comment );\nvoid SV_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr );\nvoid CL_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr );\nvoid SV_SysError( const char *error_string );\nvoid SV_ShutdownGame( void );\nvoid SV_ExecLoadLevel( void );\nvoid SV_ExecLoadGame( void );\nvoid SV_ExecChangeLevel( void );\nvoid CL_WriteMessageHistory( void );\nvoid CL_Disconnect( void );\nvoid CL_ClearEdicts( void );\nvoid CL_Crashed( void );\nchar *SV_Serverinfo( void );\nvoid CL_Drop( void );\nvoid Con_Init( void );\nvoid SCR_Init( void );\nvoid SCR_UpdateScreen( void );\nvoid SCR_BeginLoadingPlaque( qboolean is_background );\nvoid SCR_CheckStartupVids( void );\nvoid SCR_Shutdown( void );\nvoid Con_Print( const char *txt );\nvoid Con_NPrintf( int idx, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid Con_NXPrintf( con_nprint_t *info, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid UI_NPrintf( int idx, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid UI_NXPrintf( con_nprint_t *info, const char *fmt, ... ) FORMAT_CHECK( 2 );\nconst char *Info_ValueForKey( const char *s, const char *key ) RETURNS_NONNULL NONNULL;\nvoid Info_RemovePrefixedKeys( char *start, char prefix );\nqboolean Info_RemoveKey( char *s, const char *key );\nqboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize );\nqboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... ) FORMAT_CHECK( 4 );\nqboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize );\nqboolean Info_IsValid( const char *s );\nvoid Info_WriteVars( file_t *f );\nvoid Info_Print( const char *s );\nint Cmd_CheckMapsList( int fRefresh );\nvoid COM_SetRandomSeed( int lSeed );\nint COM_RandomLong( int lMin, int lMax );\nfloat COM_RandomFloat( float fMin, float fMax );\nqboolean LZSS_IsCompressed( const byte *source, size_t input_len );\nuint LZSS_GetActualSize( const byte *source, size_t input_len );\nbyte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize );\nuint LZSS_Decompress( const byte *pInput, byte *pOutput, size_t input_len, size_t output_len );\nvoid GL_FreeImage( const char *name );\nvoid VID_InitDefaultResolution( void );\nvoid VID_Init( void );\nvoid UI_SetActiveMenu( qboolean fActive );\nvoid UI_ShowConnectionWarning( void );\nvoid Cmd_Null_f( void );\nvoid Rcon_Print( host_redirect_t *rd, const char *pMsg );\nqboolean COM_ParseVector( char **pfile, float *v, size_t size );\nint COM_FileSize( const char *filename );\nvoid COM_FreeFile( void *buffer );\nint pfnCompareFileTime( const char *path1, const char *path2, int *retval );\nchar *va( const char *format, ... ) FORMAT_CHECK( 1 ) RETURNS_NONNULL;\nqboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer );\n\nstatic inline void COM_NormalizeAngles( vec3_t angles )\n{\n\tint i;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( angles[i] > 180.0f )\n\t\t\tangles[i] -= 360.0f;\n\t\telse if( angles[i] < -180.0f )\n\t\t\tangles[i] += 360.0f;\n\t}\n}\n\n#if !XASH_DEDICATED\nconnprotocol_t CL_Protocol( void );\n#else\nstatic inline connprotocol_t CL_Protocol( void )\n{\n\treturn PROTO_CURRENT;\n}\n#endif\n\nstatic inline qboolean Host_IsLocalGame( void )\n{\n\tif( SV_Active( ))\n\t\treturn SV_GetMaxClients() == 1 ? true : false;\n\treturn CL_GetMaxClients() == 1 ? true : false;\n}\n\nstatic inline qboolean Host_IsLocalClient( void )\n{\n\treturn CL_Initialized( ) && SV_Initialized( ) ? true : false;\n}\n\n// soundlib shared exports\nqboolean S_Init( void );\nvoid S_Shutdown( void );\nvoid S_StopSound( int entnum, int channel, const char *soundname );\nint S_GetCurrentStaticSounds( soundlist_t *pout, int size );\nvoid S_StopBackgroundTrack( void );\nvoid S_StopAllSounds( qboolean ambient );\n\n// gamma routines\nbyte LightToTexGamma( byte b );\nbyte TextureToGamma( byte );\nuint ScreenGammaTable( uint );\nuint LinearGammaTable( uint );\nvoid V_Init( void );\nvoid V_CheckGamma( void );\nvoid V_CheckGammaEnd( void );\nintptr_t V_GetGammaPtr( int parm );\n\n//\n// masterlist.c\n//\nvoid NET_InitMasters( void );\nvoid NET_SaveMasters( void );\nqboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data );\nqboolean NET_IsMasterAdr( netadr_t adr );\nvoid NET_MasterHeartbeat( void );\nvoid NET_MasterClear( void );\nvoid NET_MasterShutdown( void );\nqboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat );\n\n//\n// munge.c\n//\nvoid COM_Munge( byte *data, size_t len, int seq );\nvoid COM_UnMunge( byte *data, size_t len, int seq );\nvoid COM_Munge2( byte *data, size_t len, int seq );\nvoid COM_UnMunge2( byte *data, size_t len, int seq );\nvoid COM_Munge3( byte *data, size_t len, int seq );\nvoid COM_UnMunge3( byte *data, size_t len, int seq );\n\n//\n// sounds.c\n//\ntypedef enum soundlst_group_e\n{\n\tBouncePlayerShell = 0,\n\tBounceWeaponShell,\n\tBounceConcrete,\n\tBounceGlass,\n\tBounceMetal,\n\tBounceFlesh,\n\tBounceWood,\n\tRicochet,\n\tExplode,\n\tPlayerWaterEnter,\n\tPlayerWaterExit,\n\tEntityWaterEnter,\n\tEntityWaterExit,\n\n\tSoundList_Groups // must be last\n} soundlst_group_t;\n\nint SoundList_Count( soundlst_group_t group );\nconst char *SoundList_GetRandom( soundlst_group_t group );\nconst char *SoundList_Get( soundlst_group_t group, int idx );\nvoid SoundList_Init( void );\nvoid SoundList_Shutdown( void );\n\n#ifdef REF_DLL\n#error \"common.h in ref_dll\"\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n#endif//COMMON_H\n"
  },
  {
    "path": "engine/common/con_utils.c",
    "content": "/*\ncon_utils.c - console helpers\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\"\n#include \"const.h\"\n#include \"kbutton.h\"\n\n#define CON_MAXCMDS\t\t4096\t// auto-complete intermediate list\n\ntypedef struct autocomplete_list_s\n{\n\tconst char *name;\n\tint arg; // argument number to handle\n\tqboolean (*func)( const char *s, char *name, int length );\n} autocomplete_list_t;\n\ntypedef struct\n{\n\t// console auto-complete\n\tstring\t\tshortestMatch;\n\tfield_t\t\t*completionField;\t// con.input or dedicated server fake field-line\n\tconst char\t*completionString;\n\tconst char\t*completionBuffer;\n\tchar\t\t*cmds[CON_MAXCMDS];\n\tint\t\tmatchCount;\n} con_autocomplete_t;\n\nstatic con_autocomplete_t\t\tcon;\n\n/*\n=======================================================================\n\n\t\t\tFILENAME AUTOCOMPLETION\n\n=======================================================================\n*/\n/*\n=====================================\nCmd_ListMaps\n\n=====================================\n*/\nint Cmd_ListMaps( search_t *t, char *lastmapname, size_t len )\n{\n\tbyte   buf[MAX_SYSPATH]; // 1 kb\n\tfile_t *f;\n\tint i, nummaps;\n\tstring mapname, message, compiler, generator;\n\n\tfor( i = 0, nummaps = 0; i < t->numfilenames; i++ )\n\t{\n\t\tchar\t\tentfilename[MAX_QPATH];\n\t\tconst char\t*ext = COM_FileExtension( t->filenames[i] );\n\t\tint\t\tver = -1, lumpofs = 0, lumplen = 0;\n\t\tchar\t\t*ents = NULL, *pfile;\n\t\tint\t\tversion = 0;\n\t\tstring\tversion_description;\n\n\t\tif( Q_stricmp( ext, \"bsp\" )) continue;\n\t\tQ_strncpy( message, \"^1error^7\", sizeof( message ));\n\t\tcompiler[0] = '\\0';\n\t\tgenerator[0] = '\\0';\n\n\t\tf = FS_Open( t->filenames[i], \"rb\", con_gamemaps.value );\n\n\t\tif( f )\n\t\t{\n\t\t\tdheader_t *header;\n\t\t\tdextrahdr_t\t*hdrext;\n\t\t\tdlump_t entities;\n\n\t\t\tmemset( buf, 0, sizeof( buf ));\n\t\t\tFS_Read( f, buf, sizeof( buf ));\n\t\t\theader = (dheader_t *)buf;\n\t\t\tver = header->version;\n\n\t\t\t// check all the lumps and some other errors\n\t\t\tif( Mod_TestBmodelLumps( f, t->filenames[i], buf, true, &entities ))\n\t\t\t{\n\t\t\t\tlumpofs = entities.fileofs;\n\t\t\t\tlumplen = entities.filelen;\n\t\t\t\tver = header->version;\n\t\t\t}\n\n\t\t\thdrext = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t ));\n\t\t\tif( hdrext->id == IDEXTRAHEADER ) version = hdrext->version;\n\n\t\t\tQ_strncpy( entfilename, t->filenames[i], sizeof( entfilename ));\n\t\t\tCOM_ReplaceExtension( entfilename, \".ent\", sizeof( entfilename ));\n\t\t\tents = (char *)FS_LoadFile( entfilename, NULL, true );\n\n\t\t\tif( !ents && lumplen >= 10 )\n\t\t\t{\n\t\t\t\tFS_Seek( f, lumpofs, SEEK_SET );\n\t\t\t\tents = (char *)Mem_Calloc( host.mempool, lumplen + 1 );\n\t\t\t\tFS_Read( f, ents, lumplen );\n\t\t\t}\n\n\t\t\tif( ents )\n\t\t\t{\n\t\t\t\t// if there are entities to parse, a missing message key just\n\t\t\t\t// means there is no title, so clear the message string now\n\t\t\t\tchar\ttoken[2048];\n\n\t\t\t\tmessage[0] = 0; // remove 'error'\n\t\t\t\tpfile = ents;\n\n\t\t\t\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t\t\t\t{\n\t\t\t\t\tif( !Q_strcmp( token, \"{\" )) continue;\n\t\t\t\t\telse if( !Q_strcmp( token, \"}\" )) break;\n\t\t\t\t\telse if( !Q_strcmp( token, \"message\" ))\n\t\t\t\t\t{\n\t\t\t\t\t\t// get the message contents\n\t\t\t\t\t\tpfile = COM_ParseFile( pfile, message, sizeof( message ));\n\t\t\t\t\t}\n\t\t\t\t\telse if( !Q_strcmp( token, \"compiler\" ) || !Q_strcmp( token, \"_compiler\" ))\n\t\t\t\t\t{\n\t\t\t\t\t\t// get the message contents\n\t\t\t\t\t\tpfile = COM_ParseFile( pfile, compiler, sizeof( compiler ));\n\t\t\t\t\t}\n\t\t\t\t\telse if( !Q_strcmp( token, \"generator\" ) || !Q_strcmp( token, \"_generator\" ))\n\t\t\t\t\t{\n\t\t\t\t\t\t// get the message contents\n\t\t\t\t\t\tpfile = COM_ParseFile( pfile, generator, sizeof( generator ));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tMem_Free( ents );\n\t\t\t}\n\t\t}\n\n\t\tif( f ) FS_Close(f);\n\t\tCOM_FileBase( t->filenames[i], mapname, sizeof( mapname ));\n\n\t\tswitch( ver )\n\t\t{\n\t\tcase Q1BSP_VERSION:\n\t\t\tQ_strncpy( version_description, \"Quake\", sizeof( version_description ));\n\t\t\tbreak;\n\t\tcase QBSP2_VERSION:\n\t\t\tQ_strncpy( version_description, \"Darkplaces BSP2\", sizeof( version_description ));\n\t\t\tbreak;\n\t\tcase HLBSP_VERSION:\n\t\t\tswitch( version )\n\t\t\t{\n\t\t\tcase 1: Q_strncpy( version_description, \"XashXT old format\", sizeof( version_description )); break;\n\t\t\tcase 2: Q_strncpy( version_description, \"Paranoia 2: Savior\", sizeof( version_description )); break;\n\t\t\tcase 4: Q_strncpy( version_description, \"Half-Life extended\", sizeof( version_description )); break;\n\t\t\tdefault: Q_strncpy( version_description, \"Half-Life\", sizeof( version_description )); break;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\tQ_strncpy( version_description, \"??\", sizeof( version_description )); break;\n\t\t}\n\n\t\tCon_Printf( \"%16s (%s) ^3%s^7 ^2%s %s^7\\n\", mapname, version_description, message, compiler, generator );\n\t\tnummaps++;\n\t}\n\n\tif( lastmapname && len )\n\t\tQ_strncpy( lastmapname, mapname, len );\n\n\treturn nummaps;\n}\n\n/*\n=====================================\nCmd_GetMapList\n\nPrints or complete map filename\n=====================================\n*/\nstatic qboolean Cmd_GetMapList( const char *s, char *completedname, int length )\n{\n\tsearch_t *t;\n\tstring   matchbuf;\n\tint\t i, nummaps;\n\n\tt = FS_Search( va( \"maps/%s*.bsp\", s ), true, con_gamemaps.value );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tnummaps = Cmd_ListMaps( t, matchbuf, sizeof( matchbuf ));\n\n\tCon_Printf( \"\\n^3 %d maps found.\\n\", nummaps );\n\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tfor( i = 0; matchbuf[i]; i++ )\n\t{\n\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\tcompletedname[i] = 0;\n\t}\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetDemoList\n\nPrints or complete demo filename\n=====================================\n*/\nstatic qboolean Cmd_GetDemoList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numdems;\n\n\t// lookup only in gamedir\n\tt = FS_Search( va( \"%s*.dem\", s ), true, true );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor( i = 0, numdems = 0; i < t->numfilenames; i++ )\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"dem\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumdems++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i demos found.\\n\", numdems );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetMovieList\n\nPrints or complete movie filename\n=====================================\n*/\nstatic qboolean Cmd_GetMovieList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, nummovies;\n\n\tt = FS_Search( va( \"media/%s*.avi\", s ), true, false );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor(i = 0, nummovies = 0; i < t->numfilenames; i++)\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"avi\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnummovies++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i movies found.\\n\", nummovies );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetMusicList\n\nPrints or complete background track filename\n=====================================\n*/\nstatic qboolean Cmd_GetMusicList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numtracks;\n\n\tt = FS_Search( va( \"media/%s*.*\", s ), true, false );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor(i = 0, numtracks = 0; i < t->numfilenames; i++)\n\t{\n\t\tconst char *ext = COM_FileExtension( t->filenames[i] );\n\n\t\tif( Q_stricmp( ext, \"wav\" ) && Q_stricmp( ext, \"mp3\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumtracks++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i soundtracks found.\\n\", numtracks );\n\tMem_Free(t);\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetSavesList\n\nPrints or complete savegame filename\n=====================================\n*/\nstatic qboolean Cmd_GetSavesList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numsaves;\n\n\tt = FS_Search( va( DEFAULT_SAVE_DIRECTORY \"%s*.sav\", s ), true, true );\t// lookup only in gamedir\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor( i = 0, numsaves = 0; i < t->numfilenames; i++ )\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"sav\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumsaves++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i saves found.\\n\", numsaves );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetConfigList\n\nPrints or complete .cfg filename\n=====================================\n*/\nstatic qboolean Cmd_GetConfigList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numconfigs;\n\n\tt = FS_Search( va( \"%s*.cfg\", s ), true, false );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor( i = 0, numconfigs = 0; i < t->numfilenames; i++ )\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"cfg\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumconfigs++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i configs found.\\n\", numconfigs );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetSoundList\n\nPrints or complete sound filename\n=====================================\n*/\nstatic qboolean Cmd_GetSoundList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numsounds;\n\n\tt = FS_Search( va( \"%s%s*.*\", DEFAULT_SOUNDPATH, s ), true, false );\n\tif( !t ) return false;\n\n\tQ_strncpy( matchbuf, t->filenames[0] + sizeof( DEFAULT_SOUNDPATH ) - 1, sizeof( matchbuf ));\n\tCOM_StripExtension( matchbuf );\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor(i = 0, numsounds = 0; i < t->numfilenames; i++)\n\t{\n\t\tconst char *ext = COM_FileExtension( t->filenames[i] );\n\n\t\tif( Q_stricmp( ext, \"wav\" ) && Q_stricmp( ext, \"mp3\" ))\n\t\t\tcontinue;\n\n\t\tQ_strncpy( matchbuf, t->filenames[i] + sizeof( DEFAULT_SOUNDPATH ) - 1, sizeof( matchbuf ));\n\t\tCOM_StripExtension( matchbuf );\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumsounds++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i sounds found.\\n\", numsounds );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetItemsList\n\nPrints or complete item classname (weapons only)\n=====================================\n*/\nstatic qboolean Cmd_GetItemsList( const char *s, char *completedname, int length )\n{\n#if !XASH_DEDICATED\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numitems;\n\n\tif( !clgame.itemspath[0] ) return false; // not in game yet\n\tt = FS_Search( va( \"%s/%s*.txt\", clgame.itemspath, s ), true, false );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor( i = 0, numitems = 0; i < t->numfilenames; i++ )\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"txt\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumitems++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i items found.\\n\", numitems );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n#endif // !XASH_DEDICATED\n\treturn false;\n}\n\n/*\n=====================================\nCmd_GetKeysList\n\nAutocomplete for bind command\n=====================================\n*/\nstatic qboolean Cmd_GetKeysList( const char *s, char *completedname, int length )\n{\n#if !XASH_DEDICATED\n\tsize_t i, numkeys;\n\tstring keys[256];\n\tstring matchbuf;\n\tint len;\n\n\t// compare keys list with current keyword\n\tlen = Q_strlen( s );\n\n\tfor( i = 0, numkeys = 0; i < 255; i++ )\n\t{\n\t\tconst char *keyname = Key_KeynumToString( i );\n\n\t\tif(( *s == '*' ) || !Q_strnicmp( keyname, s, len))\n\t\t\tQ_strncpy( keys[numkeys++], keyname, sizeof( keys[0] ));\n\t}\n\n\tif( !numkeys ) return false;\n\tQ_strncpy( matchbuf, keys[0], sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( numkeys == 1 ) return true;\n\n\tfor( i = 0; i < numkeys; i++ )\n\t{\n\t\tQ_strncpy( matchbuf, keys[i], sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t}\n\n\tCon_Printf( \"\\n^3 %zu keys found.\\n\", numkeys );\n\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\treturn true;\n#endif // !XASH_DEDICATED\n\treturn false;\n}\n\n/*\n===============\nCon_AddCommandToList\n\n===============\n*/\nstatic void Con_AddCommandToList( const char *s, const char *value, const void *ptoggle, void *_autocompleteList )\n{\n\tcon_autocomplete_t *list = (con_autocomplete_t*)_autocompleteList;\n\tqboolean toggle = ptoggle != NULL && *(qboolean *)ptoggle;\n\n\tif( *s == '@' ) return; // never show system cvars or cmds\n\tif( list->matchCount >= CON_MAXCMDS ) return; // list is full\n\n\tif( toggle )\n\t{\n\t\tif( Q_strcmp( value, \"0\" ) && Q_strcmp( value, \"1\" ))\n\t\t\treturn; // exclude non-toggable cvars\n\t}\n\n\tif( Q_strnicmp( s, list->completionString, Q_strlen( list->completionString ) ) )\n\t\treturn; // no match\n\n\tlist->cmds[list->matchCount++] = copystring( s );\n}\n\n/*\n=================\nCon_SortCmds\n=================\n*/\nstatic int Con_SortCmds( const void *arg1, const void *arg2 )\n{\n\treturn Q_stricmp( *(const char **)arg1, *(const char **)arg2 );\n}\n\n/*\n=====================================\nCmd_GetCommandsList\n\nAutocomplete for bind command\n=====================================\n*/\nstatic qboolean Cmd_GetCommandsAndCvarsList( const char *s, char *completedname, int length, qboolean cmds, qboolean cvars, qboolean toggle )\n{\n\tsize_t i;\n\tstring matchbuf;\n\tcon_autocomplete_t list = { 0 }; // local autocomplete list\n\n\tlist.completionString = s;\n\n\t// skip backslash\n\twhile( *list.completionString && (*list.completionString == '\\\\' || *list.completionString == '/') )\n\t\tlist.completionString++;\n\n\tif( !COM_CheckStringEmpty( list.completionString ) )\n\t\treturn false;\n\n\t// find matching commands and variables\n\tif( cvars )\n\t{\n\t\tCvar_LookupVars( 0, &toggle, &list, (setpair_t)Con_AddCommandToList );\n\t}\n\n\tif( cmds )\n\t{\n\t\ttoggle = false;\n\t\tCmd_LookupCmds( &toggle, &list, (setpair_t)Con_AddCommandToList );\n\t}\n\n\tif( !list.matchCount ) return false;\n\tQ_strncpy( matchbuf, list.cmds[0], sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( list.matchCount == 1 ) return true;\n\n\tqsort( list.cmds, list.matchCount, sizeof( char* ), Con_SortCmds );\n\n\tfor( i = 0; i < list.matchCount; i++ )\n\t{\n\t\tQ_strncpy( matchbuf, list.cmds[i], sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t}\n\n\tCon_Printf( \"\\n^3 %i %s found.\\n\", list.matchCount, cmds ? \"commands\" : \"variables\" );\n\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\n\tfor( i = 0; i < list.matchCount; i++ )\n\t{\n\t\tif( list.cmds[i] != NULL )\n\t\t{\n\t\t\tMem_Free( list.cmds[i] );\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetCommandsList\n\nAutocomplete for bind command\n=====================================\n*/\nstatic qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length )\n{\n\treturn Cmd_GetCommandsAndCvarsList( s, completedname, length, true, true, false );\n}\n\n/*\n=====================================\nCmd_GetCvarList\n\nAutocomplete for bind command\n=====================================\n*/\nstatic qboolean Cmd_GetCvarsList( const char *s, char *completedname, int length )\n{\n\tqboolean toggle = !Q_stricmp( Cmd_Argv( 0 ), \"toggle\" );\n\treturn Cmd_GetCommandsAndCvarsList( s, completedname, length, false, true, toggle );\n}\n\n/*\n=====================================\nCmd_GetCustomList\n\nPrints or complete .HPK filenames\n=====================================\n*/\nstatic qboolean Cmd_GetCustomList( const char *s, char *completedname, int length )\n{\n\tsearch_t\t\t*t;\n\tstring\t\tmatchbuf;\n\tint\t\ti, numitems;\n\n\tt = FS_Search( va( \"%s*.hpk\", s ), true, false );\n\tif( !t ) return false;\n\n\tCOM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( t->numfilenames == 1 ) return true;\n\n\tfor(i = 0, numitems = 0; i < t->numfilenames; i++)\n\t{\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"hpk\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t\tnumitems++;\n\t}\n\n\tCon_Printf( \"\\n^3 %i items found.\\n\", numitems );\n\tMem_Free( t );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetGameList\n\nPrints or complete gamedir name\n=====================================\n*/\nstatic qboolean Cmd_GetGamesList( const char *s, char *completedname, int length )\n{\n\tint\ti, numgamedirs;\n\tstring\tgamedirs[MAX_MODS];\n\tstring\tmatchbuf;\n\tint\tlen;\n\n\t// compare gamelist with current keyword\n\tlen = Q_strlen( s );\n\n\tfor( i = 0, numgamedirs = 0; i < FI->numgames; i++ )\n\t{\n\t\tif(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len))\n\t\t\tQ_strncpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder, sizeof( gamedirs[0] ));\n\t}\n\n\tif( !numgamedirs ) return false;\n\tQ_strncpy( matchbuf, gamedirs[0], sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( numgamedirs == 1 ) return true;\n\n\tfor( i = 0; i < numgamedirs; i++ )\n\t{\n\t\tQ_strncpy( matchbuf, gamedirs[i], sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t}\n\n\tCon_Printf( \"\\n^3 %i games found.\\n\", numgamedirs );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n}\n\n/*\n=====================================\nCmd_GetCDList\n\nPrints or complete CD command name\n=====================================\n*/\nstatic qboolean Cmd_GetCDList( const char *s, char *completedname, int length )\n{\n\tint i, numcdcommands;\n\tstring\tcdcommands[8];\n\tstring\tmatchbuf;\n\tint\tlen;\n\n\tconst char *cd_command[] =\n\t{\n\t\"info\",\n\t\"loop\",\n\t\"off\",\n\t\"on\",\n\t\"pause\",\n\t\"play\",\n\t\"resume\",\n\t\"stop\",\n\t};\n\n\t// compare CD command list with current keyword\n\tlen = Q_strlen( s );\n\n\tfor( i = 0, numcdcommands = 0; i < 8; i++ )\n\t{\n\t\tif(( *s == '*' ) || !Q_strnicmp( cd_command[i], s, len))\n\t\t\tQ_strncpy( cdcommands[numcdcommands++], cd_command[i], sizeof( cdcommands[0] ));\n\t}\n\n\tif( !numcdcommands ) return false;\n\tQ_strncpy( matchbuf, cdcommands[0], sizeof( matchbuf ));\n\tif( completedname && length )\n\t\tQ_strncpy( completedname, matchbuf, length );\n\tif( numcdcommands == 1 ) return true;\n\n\tfor( i = 0; i < numcdcommands; i++ )\n\t{\n\t\tQ_strncpy( matchbuf, cdcommands[i], sizeof( matchbuf ));\n\t\tCon_Printf( \"%16s\\n\", matchbuf );\n\t}\n\n\tCon_Printf( \"\\n^3 %i commands found.\\n\", numcdcommands );\n\n\t// cut shortestMatch to the amount common with s\n\tif( completedname && length )\n\t{\n\t\tfor( i = 0; matchbuf[i]; i++ )\n\t\t{\n\t\t\tif( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] ))\n\t\t\t\tcompletedname[i] = 0;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir )\n{\n\tqboolean\tuse_filter = false;\n\tbyte\tbuf[MAX_SYSPATH];\n\tstring\tmpfilter;\n\tchar\t*buffer;\n\tsize_t\tbuffersize;\n\tstring\tresult;\n\tint\ti, size;\n\tsearch_t\t*t;\n\tfile_t\t*f;\n\n\tif( FS_FileSize( \"maps.lst\", onlyingamedir ) > 0 && !fRefresh )\n\t\treturn true; // exist\n\n\t// setup mpfilter\n\tQ_snprintf( mpfilter, sizeof( mpfilter ), \"maps/%s\", GI->mp_filter );\n\tt = FS_Search( \"maps/*.bsp\", false, onlyingamedir );\n\n\tif( !t )\n\t{\n\t\tif( onlyingamedir )\n\t\t{\n\t\t\t// mod doesn't contain any maps (probably this is a bot)\n\t\t\treturn Cmd_CheckMapsList_R( fRefresh, false );\n\t\t}\n\t\treturn false;\n\t}\n\n\tbuffersize = t->numfilenames * 2 * sizeof( result );\n\tbuffer = Mem_Calloc( host.mempool, buffersize );\n\tuse_filter = COM_CheckStringEmpty( GI->mp_filter ) ? true : false;\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t{\n\t\tchar\t\t*ents = NULL, *pfile;\n\t\tint\t\tlumpofs = 0, lumplen = 0;\n\t\tstring\t\tmapname, message, entfilename;\n\n\t\tif( Q_stricmp( COM_FileExtension( t->filenames[i] ), \"bsp\" ))\n\t\t\tcontinue;\n\n\t\tif( use_filter && Q_stristr( t->filenames[i], mpfilter ))\n\t\t\tcontinue;\n\n\t\tf = FS_Open( t->filenames[i], \"rb\", onlyingamedir );\n\t\tCOM_FileBase( t->filenames[i], mapname, sizeof( mapname ));\n\n\t\tif( f )\n\t\t{\n\t\t\tqboolean  have_spawnpoints = false;\n\t\t\tdlump_t   entities;\n\n\t\t\tmemset( buf, 0, MAX_SYSPATH );\n\t\t\tFS_Read( f, buf, MAX_SYSPATH );\n\n\t\t\t// check all the lumps and some other errors\n\t\t\tif( !Mod_TestBmodelLumps( f, t->filenames[i], buf, true, &entities ))\n\t\t\t{\n\t\t\t\tFS_Close( f );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// after call Mod_TestBmodelLumps we gurantee what map is valid\n\t\t\tlumpofs = entities.fileofs;\n\t\t\tlumplen = entities.filelen;\n\n\t\t\tQ_strncpy( entfilename, t->filenames[i], sizeof( entfilename ));\n\t\t\tCOM_ReplaceExtension( entfilename, \".ent\", sizeof( entfilename ));\n\t\t\tents = (char *)FS_LoadFile( entfilename, NULL, true );\n\n\t\t\tif( !ents && lumplen >= 10 )\n\t\t\t{\n\t\t\t\tFS_Seek( f, lumpofs, SEEK_SET );\n\t\t\t\tents = Z_Calloc( lumplen + 1 );\n\t\t\t\tFS_Read( f, ents, lumplen );\n\t\t\t}\n\n\t\t\tif( ents )\n\t\t\t{\n\t\t\t\t// if there are entities to parse, a missing message key just\n\t\t\t\t// means there is no title, so clear the message string now\n\t\t\t\tchar\ttoken[MAX_TOKEN];\n\t\t\t\tqboolean\tworldspawn = true;\n\n\t\t\t\tQ_strncpy( message, \"No Title\", sizeof( message ));\n\t\t\t\tpfile = ents;\n\n\t\t\t\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t\t\t\t{\n\t\t\t\t\tif( token[0] == '}' && worldspawn )\n\t\t\t\t\t{\n\t\t\t\t\t\tworldspawn = false;\n\n\t\t\t\t\t\t// if mod has mp_filter set up, then it's a mod that\n\t\t\t\t\t\t// might not have valid mp_entity set in GI\n\t\t\t\t\t\t// if mod is multiplayer only, assume all maps are valid\n\t\t\t\t\t\tif( use_filter || GI->gamemode == GAME_MULTIPLAYER_ONLY )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thave_spawnpoints = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse if( !Q_strcmp( token, \"message\" ) && worldspawn )\n\t\t\t\t\t{\n\t\t\t\t\t\t// get the message contents\n\t\t\t\t\t\tpfile = COM_ParseFile( pfile, message, sizeof( message ));\n\t\t\t\t\t}\n\t\t\t\t\telse if( !Q_strcmp( token, \"classname\" ))\n\t\t\t\t\t{\n\t\t\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\n\t\t\t\t\t\tif( !Q_strcmp( token, GI->mp_entity ))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thave_spawnpoints = true;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif( have_spawnpoints )\n\t\t\t\t\t\tbreak; // valid map\n\t\t\t\t}\n\t\t\t\tMem_Free( ents );\n\t\t\t}\n\n\t\t\tif( f ) FS_Close( f );\n\n\t\t\tif( have_spawnpoints )\n\t\t\t{\n\t\t\t\t// format: mapname \"maptitle\"\\n\n\t\t\t\tQ_snprintf( result, sizeof( result ), \"%s \\\"%s\\\"\\n\", mapname, message );\n\t\t\t\tQ_strncat( buffer, result, buffersize ); // add new string\n\t\t\t}\n\t\t}\n\t}\n\n\tif( t ) Mem_Free( t ); // free search result\n\tsize = Q_strlen( buffer );\n\n\tif( !size )\n\t{\n\t\tif( buffer ) Mem_Free( buffer );\n\n\t\tif( onlyingamedir )\n\t\t\treturn Cmd_CheckMapsList_R( fRefresh, false );\n\t\treturn false;\n\t}\n\n\t// write generated maps.lst\n\tif( FS_WriteFile( \"maps.lst\", buffer, size))\n\t{\n\t\tif( buffer ) Mem_Free( buffer );\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nint GAME_EXPORT Cmd_CheckMapsList( int fRefresh )\n{\n\treturn Cmd_CheckMapsList_R( fRefresh, true );\n}\n\n// keep this sorted\nstatic const autocomplete_list_t cmd_list[] =\n{\n{ \"bind\", 1, Cmd_GetKeysList },\n{ \"bind\", 2, Cmd_GetCommandsList },\n{ \"cd\", 1, Cmd_GetCDList },\n{ \"changelevel2\", 1, Cmd_GetMapList },\n{ \"changelevel\", 1, Cmd_GetMapList },\n{ \"drop\", 1, Cmd_GetItemsList },\n{ \"entpatch\", 1, Cmd_GetMapList },\n{ \"exec\", 1, Cmd_GetConfigList },\n{ \"game\", 1, Cmd_GetGamesList },\n{ \"give\", 1, Cmd_GetItemsList },\n{ \"hpkextract\", 1, Cmd_GetCustomList },\n{ \"hpklist\", 1, Cmd_GetCustomList },\n{ \"hpkval\", 1, Cmd_GetCustomList },\n{ \"listdemo\", 1, Cmd_GetDemoList, },\n{ \"load\", 1, Cmd_GetSavesList },\n{ \"map\", 1, Cmd_GetMapList },\n{ \"map_background\", 1, Cmd_GetMapList },\n{ \"movie\", 1, Cmd_GetMovieList },\n{ \"mp3\", 1, Cmd_GetCDList },\n{ \"music\", 1, Cmd_GetMusicList, },\n{ \"play\", 1, Cmd_GetSoundList },\n{ \"playdemo\", 1, Cmd_GetDemoList, },\n{ \"playvol\", 1, Cmd_GetSoundList },\n{ \"reset\", 1, Cmd_GetCvarsList },\n{ \"save\", 1, Cmd_GetSavesList },\n{ \"set\", 1, Cmd_GetCvarsList },\n{ \"timedemo\", 1, Cmd_GetDemoList },\n{ \"toggle\", 1, Cmd_GetCvarsList },\n{ \"unbind\", 1, Cmd_GetKeysList },\n};\n\n/*\n===============\nCmd_CheckName\n\ncompare first argument with string\n===============\n*/\nstatic qboolean Cmd_CheckName( const char *name )\n{\n\tconst char *p = Cmd_Argv( 0 );\n\n\tif( !Q_stricmp( p, name ))\n\t\treturn true;\n\n\tif( p[0] == '\\\\' && !Q_stricmp( &p[1], name ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n============\nCmd_AutocompleteName\n\nAutocomplete filename\nfor various cmds\n============\n*/\nstatic qboolean Cmd_AutocompleteName( const char *source, int arg, char *buffer, size_t bufsize )\n{\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( cmd_list ); i++  )\n\t{\n\t\tif( cmd_list[i].arg == arg && Cmd_CheckName( cmd_list[i].name ))\n\t\t\treturn cmd_list[i].func( source, buffer, bufsize );\n\t}\n\n\treturn false;\n}\n\n/*\n===============\nCon_PrintCmdMatches\n===============\n*/\nstatic void Con_PrintCmdMatches( const char *s, const char *unused1, const char *m, void *unused2 )\n{\n\tif( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ) ) )\n\t{\n\t\tif( COM_CheckString( m ) ) Con_Printf( \"    %s ^3\\\"%s\\\"\\n\", s, m );\n\t\telse Con_Printf( \"    %s\\n\", s ); // variable or command without description\n\t}\n}\n\n/*\n===============\nCon_PrintCvarMatches\n===============\n*/\nstatic void Con_PrintCvarMatches( const char *s, const char *value, const char *m, void *unused2 )\n{\n\tif( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ) ) )\n\t{\n\t\tif( COM_CheckString( m ) ) Con_Printf( \"    %s (%s)   ^3\\\"%s\\\"\\n\", s, value, m );\n\t\telse Con_Printf( \"    %s  (%s)\\n\", s, value ); // variable or command without description\n\t}\n}\n\n/*\n===============\nCon_ConcatRemaining\n===============\n*/\nstatic void Con_ConcatRemaining( const char *src, const char *start )\n{\n\tconst char\t*arg;\n\tint\ti;\n\n\targ = Q_strstr( src, start );\n\n\tif( !arg )\n\t{\n\t\tfor( i = 1; i < Cmd_Argc(); i++ )\n\t\t{\n\t\t\tQ_strncat( con.completionField->buffer, \" \", sizeof( con.completionField->buffer ) );\n\t\t\targ = Cmd_Argv( i );\n\t\t\twhile( *arg )\n\t\t\t{\n\t\t\t\tif( *arg == ' ' )\n\t\t\t\t{\n\t\t\t\t\tQ_strncat( con.completionField->buffer, \"\\\"\", sizeof( con.completionField->buffer ) );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\targ++;\n\t\t\t}\n\n\t\t\tQ_strncat( con.completionField->buffer, Cmd_Argv( i ), sizeof( con.completionField->buffer ) );\n\t\t\tif( *arg == ' ' ) Q_strncat( con.completionField->buffer, \"\\\"\", sizeof( con.completionField->buffer ) );\n\t\t}\n\t\treturn;\n\t}\n\n\targ += Q_strlen( start );\n\tQ_strncat( con.completionField->buffer, arg, sizeof( con.completionField->buffer ) );\n}\n\n/*\n===============\nCon_CompleteCommand\n\nperform Tab expansion\n===============\n*/\nvoid Con_CompleteCommand( field_t *field )\n{\n\tfield_t\ttemp;\n\tstring\tfilename;\n\tqboolean toggle = false;\n\tqboolean\tnextcmd;\n\tint\ti;\n\n\t// setup the completion field\n\tcon.completionField = field;\n\n\t// only look at the first token for completion purposes\n\tCmd_TokenizeString( con.completionField->buffer );\n\n\tnextcmd = (con.completionField->buffer[Q_strlen( con.completionField->buffer ) - 1] == ' ') ? true : false;\n\n\tcon.completionString = Cmd_Argv( 0 );\n\n\t// skip backslash\n\twhile( *con.completionString && (*con.completionString == '\\\\' || *con.completionString == '/') )\n\t\tcon.completionString++;\n\n\tif( !COM_CheckStringEmpty( con.completionString ) )\n\t\treturn;\n\n\t// free the old autocomplete list\n\tfor( i = 0; i < con.matchCount; i++ )\n\t{\n\t\tif( con.cmds[i] != NULL )\n\t\t{\n\t\t\tMem_Free( con.cmds[i] );\n\t\t\tcon.cmds[i] = NULL;\n\t\t}\n\t}\n\n\tcon.matchCount = 0;\n\tcon.shortestMatch[0] = 0;\n\n\t// find matching commands and variables\n\tCmd_LookupCmds( &toggle, &con, (setpair_t)Con_AddCommandToList );\n\tCvar_LookupVars( 0, &toggle, &con, (setpair_t)Con_AddCommandToList );\n\n\tif( !con.matchCount ) return; // no matches\n\n\ttemp = *con.completionField;\n\n\t// autocomplete second arg\n\tif( (Cmd_Argc() >= 2) || ((Cmd_Argc() == 1) && nextcmd) )\n\t{\n\t\tcon.completionBuffer = Cmd_Argv( Cmd_Argc() - 1 );\n\n\t\t// skip backslash\n\t\twhile( *con.completionBuffer && (*con.completionBuffer == '\\\\' || *con.completionBuffer == '/') )\n\t\t\tcon.completionBuffer++;\n\n\t\tif( !COM_CheckStringEmpty( con.completionBuffer ) )\n\t\t\treturn;\n\n\t\tif( Cmd_AutocompleteName( con.completionBuffer, Cmd_Argc() - 1, filename, sizeof( filename ) ) )\n\t\t{\n\t\t\tcon.completionField->buffer[0] = 0;\n\n\t\t\tfor( i = 0; i < Cmd_Argc() - 1; i++ )\n\t\t\t{\n\t\t\t\tQ_strncat( con.completionField->buffer, Cmd_Argv( i ), sizeof( con.completionField->buffer ));\n\t\t\t\tQ_strncat( con.completionField->buffer, \" \", sizeof( con.completionField->buffer ));\n\t\t\t}\n\t\t\tQ_strncat( con.completionField->buffer, filename, sizeof( con.completionField->buffer ));\n\t\t\tcon.completionField->cursor = Q_strlen( con.completionField->buffer );\n\t\t}\n\n\t\t// don't adjusting cursor pos if we nothing found\n\t\treturn;\n\t}\n\n\tif( con.matchCount == 1 )\n\t{\n\t\tQ_strncpy( con.completionField->buffer, con.cmds[0], sizeof( con.completionField->buffer ));\n\t\tif( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, \" \", sizeof( con.completionField->buffer ) );\n\t\telse Con_ConcatRemaining( temp.buffer, con.completionString );\n\t\tcon.completionField->cursor = Q_strlen( con.completionField->buffer );\n\t}\n\telse\n\t{\n\t\tchar\t*first, *last;\n\t\tint\tlen = 0;\n\n\t\tqsort( con.cmds, con.matchCount, sizeof( char* ), Con_SortCmds );\n\n\t\t// find the number of matching characters between the first and\n\t\t// the last element in the list and copy it\n\t\tfirst = con.cmds[0];\n\t\tlast = con.cmds[con.matchCount - 1];\n\n\t\twhile( *first && *last && Q_tolower( *first ) == Q_tolower( *last ) )\n\t\t{\n\t\t\tfirst++;\n\t\t\tlast++;\n\n\t\t\tcon.shortestMatch[len] = con.cmds[0][len];\n\t\t\tlen++;\n\t\t}\n\t\tcon.shortestMatch[len] = 0;\n\n\t\t// multiple matches, complete to shortest\n\t\tQ_strncpy( con.completionField->buffer, con.shortestMatch, sizeof( con.completionField->buffer ));\n\t\tcon.completionField->cursor = Q_strlen( con.completionField->buffer );\n\t\tCon_ConcatRemaining( temp.buffer, con.completionString );\n\n\t\tCon_Printf( \"]%s\\n\", con.completionField->buffer );\n\n\t\t// run through again, printing matches\n\t\tCmd_LookupCmds( NULL, NULL, (setpair_t)Con_PrintCmdMatches );\n\t\tCvar_LookupVars( 0, NULL, NULL, (setpair_t)Con_PrintCvarMatches );\n\t}\n}\n\n/*\n=========\nCmd_AutoComplete\n\nNOTE: input string must be equal or longer than MAX_STRING\n=========\n*/\nvoid Cmd_AutoComplete( char *complete_string )\n{\n\tfield_t\tinput;\n\n\tif( !complete_string || !*complete_string )\n\t\treturn;\n\n\t// setup input\n\tQ_strncpy( input.buffer, complete_string, sizeof( input.buffer ) );\n\tinput.cursor = input.scroll = 0;\n\n\tCon_CompleteCommand( &input );\n\n\t// setup output\n\tif( input.buffer[0] == '\\\\' || input.buffer[0] == '/' )\n\t\tQ_strncpy( complete_string, input.buffer + 1, sizeof( input.buffer ) );\n\telse Q_strncpy( complete_string, input.buffer, sizeof( input.buffer ) );\n}\n\n/*\n============\nCmd_AutoCompleteClear\n\n============\n*/\nvoid Cmd_AutoCompleteClear( void )\n{\n\tint i;\n\n\t// free the old autocomplete list\n\tfor( i = 0; i < con.matchCount; i++ )\n\t{\n\t\tif( con.cmds[i] != NULL )\n\t\t{\n\t\t\tMem_Free( con.cmds[i] );\n\t\t\tcon.cmds[i] = NULL;\n\t\t}\n\t}\n\n\tcon.matchCount = 0;\n}\n\n/*\n============\nCmd_WriteVariables\n\nAppends lines containing \"set variable value\" for all variables\nwith the archive flag set to true.\n============\n*/\nstatic void Cmd_WriteOpenGLCvar( const char *name, const char *string, const char *desc, void *f )\n{\n\tif( !COM_CheckString( desc ))\n\t\treturn; // ignore cvars without description (fantom variables)\n\tFS_Printf( f, \"%s \\\"%s\\\"\\n\", name, string );\n}\n\nstatic void Cmd_WriteHelp(const char *name, const char *unused, const char *desc, void *f )\n{\n\tint\tlength;\n\n\tif( !COM_CheckString( desc ))\n\t\treturn; // ignore fantom cmds\n\n\tif( name[0] == '+' || name[0] == '-' )\n\t\treturn; // key bindings\n\n\tlength = 3 - (Q_strlen( name ) / 10); // Asm_Ed default tab stop is 10\n\n\tif( length == 3 ) FS_Printf( f, \"%s\\t\\t\\t\\\"%s\\\"\\n\", name, desc );\n\tif( length == 2 ) FS_Printf( f, \"%s\\t\\t\\\"%s\\\"\\n\", name, desc );\n\tif( length == 1 ) FS_Printf( f, \"%s\\t\\\"%s\\\"\\n\", name, desc );\n\tif( length == 0 ) FS_Printf( f, \"%s \\\"%s\\\"\\n\", name, desc );\n}\n\nstatic void Cmd_WriteOpenGLVariables( file_t *f )\n{\n\tCvar_LookupVars( FCVAR_GLCONFIG, NULL, f, (setpair_t)Cmd_WriteOpenGLCvar );\n}\n\nvoid Host_InitializeConfig( file_t *f, const char *config, const char *description )\n{\n\tFS_Printf( f, \"//=======================================================================\\n\");\n\tFS_Printf( f, \"//\\tGenerated by \"XASH_ENGINE_NAME\" (%i, %s, %s, %s-%s)\\n\", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());\n\tFS_Printf( f, \"//\\t\\t%s - %s\\n\", config, description );\n\tFS_Printf( f, \"//=======================================================================\\n\" );\n}\n\nvoid Host_FinalizeConfig( file_t *f, const char *config )\n{\n\tstring backup, newcfg;\n\n\tQ_snprintf( backup, sizeof( backup ), \"%s.bak\", config );\n\tQ_snprintf( newcfg, sizeof( newcfg ), \"%s.new\", config );\n\n\tFS_Printf( f, \"// end of %s\\n\", config );\n\tFS_Close( f );\n\tFS_Delete( backup );\n\tFS_Rename( config, backup );\n\tFS_Rename( newcfg, config );\n}\n\n\n#if !XASH_DEDICATED\n/*\n===============\nHost_WriteConfig\n\nWrites key bindings and archived cvars to config.cfg\n===============\n*/\nvoid Host_WriteConfig( void )\n{\n\tkbutton_t\t*mlook = NULL;\n\tkbutton_t\t*jlook = NULL;\n\tfile_t\t*f;\n\n\tif( !clgame.hInstance || Sys_CheckParm( \"-nowriteconfig\" ) ) return;\n\n\n\tf = FS_Open( \"config.cfg.new\", \"w\", false );\n\tif( f )\n\t{\n\t\tCon_Reportf( \"%s()\\n\", __func__ );\n\t\tHost_InitializeConfig( f, \"config.cfg\", \"archive of cvars\" );\n\t\tKey_WriteBindings( f );\n\t\tCvar_WriteVariables( f, FCVAR_ARCHIVE );\n\t\tInfo_WriteVars( f );\n\n\t\tif( clgame.hInstance )\n\t\t{\n\t\t\tmlook = (kbutton_t *)clgame.dllFuncs.KB_Find( \"in_mlook\" );\n\t\t\tjlook = (kbutton_t *)clgame.dllFuncs.KB_Find( \"in_jlook\" );\n\t\t}\n\n\t\tif( mlook && ( mlook->state & 1 ))\n\t\t\tFS_Printf( f, \"+mlook\\n\" );\n\n\t\tif( jlook && ( jlook->state & 1 ))\n\t\t\tFS_Printf( f, \"+jlook\\n\" );\n\n\t\tFS_Printf( f, \"exec userconfig.cfg\\n\" );\n\n\t\tHost_FinalizeConfig( f, \"config.cfg\" );\n\t}\n\telse Con_DPrintf( S_ERROR \"Couldn't write config.cfg.\\n\" );\n\n\tNET_SaveMasters();\n\n\tFS_SaveVFSConfig();\n}\n\n/*\n===============\nHost_WriteServerConfig\n\nsave serverinfo variables into server.cfg (using for dedicated server too)\n===============\n*/\nvoid GAME_EXPORT Host_WriteServerConfig( const char *name )\n{\n\tfile_t\t*f;\n\tstring newconfigfile;\n\n\tQ_snprintf( newconfigfile, MAX_STRING, \"%s.new\", name );\n\n\t// FIXME: move this out until menu parser is done\n\tCSCR_LoadDefaultCVars( \"settings.scr\" );\n\n\tif(( f = FS_Open( newconfigfile, \"w\", false )) != NULL )\n\t{\n\t\tHost_InitializeConfig( f, \"game.cfg\", \"multiplayer server temporary config\" );\n\n\t\tCvar_WriteVariables( f, FCVAR_SERVER );\n\t\tCSCR_WriteGameCVars( f, \"settings.scr\" );\n\n\t\tHost_FinalizeConfig( f, name );\n\t}\n\telse Con_DPrintf( S_ERROR \"Couldn't write %s.\\n\", name );\n}\n\n/*\n===============\nHost_WriteOpenGLConfig\n\nsave opengl variables into opengl.cfg\n===============\n*/\nvoid Host_WriteOpenGLConfig( void )\n{\n\tstring name;\n\tfile_t\t*f;\n\n\tif( Sys_CheckParm( \"-nowriteconfig\" ) )\n\t\treturn;\n\n\tQ_snprintf( name, sizeof( name ), \"%s.cfg\", ref.dllFuncs.R_GetConfigName() );\n\n\n\tf = FS_Open( va( \"%s.new\", name ), \"w\", false );\n\tif( f )\n\t{\n\t\tCon_Reportf( \"%s()\\n\", __func__ );\n\t\tHost_InitializeConfig( f, name, \"archive of renderer implementation cvars\" );\n\t\tCmd_WriteOpenGLVariables( f );\n\n\t\tHost_FinalizeConfig( f, name );\n\t}\n\telse Con_DPrintf( S_ERROR \"can't update %s.\\n\", name );\n}\n\n/*\n===============\nHost_WriteVideoConfig\n\nsave render variables into video.cfg\n===============\n*/\nvoid Host_WriteVideoConfig( void )\n{\n\tfile_t\t*f;\n\n\tif( Sys_CheckParm( \"-nowriteconfig\" ) )\n\t\treturn;\n\n\tf = FS_Open( \"video.cfg.new\", \"w\", false );\n\tif( f )\n\t{\n\t\tCon_Reportf( \"%s()\\n\", __func__ );\n\t\tHost_InitializeConfig( f, \"video.cfg\", \"archive of renderer variables\" );\n\t\tCvar_WriteVariables( f, FCVAR_RENDERINFO );\n\t\tHost_FinalizeConfig( f, \"video.cfg\" );\n\t}\n\telse Con_DPrintf( S_ERROR \"can't update video.cfg.\\n\" );\n}\n#endif // XASH_DEDICATED\n\nvoid Key_EnumCmds_f( void )\n{\n\tfile_t\t*f;\n\n\tFS_AllowDirectPaths( true );\n\tif( FS_FileExists( \"../help.txt\", false ))\n\t{\n\t\tCon_Printf( \"help.txt already exist\\n\" );\n\t\tFS_AllowDirectPaths( false );\n\t\treturn;\n\t}\n\n\tf = FS_Open( \"../help.txt\", \"w\", false );\n\tif( f )\n\t{\n\t\tHost_InitializeConfig( f, \"help.txt\", \"xash commands and console variables\" );\n\t\tFS_Printf( f, \"\\n\\n\\t\\t\\tconsole variables\\n\\n\");\n\t\tCvar_LookupVars( 0, NULL, f, (setpair_t)Cmd_WriteHelp );\n\t\tFS_Printf( f, \"\\n\\n\\t\\t\\tconsole commands\\n\\n\");\n\t\tCmd_LookupCmds( NULL, f, (setpair_t)Cmd_WriteHelp );\n  \t\tFS_Printf( f, \"\\n\\n\");\n\t\tFS_Close( f );\n\t\tCon_Printf( \"help.txt created\\n\" );\n\t}\n\telse Con_Printf( S_ERROR \"couldn't write help.txt.\\n\");\n\tFS_AllowDirectPaths( false );\n}\n"
  },
  {
    "path": "engine/common/custom.c",
    "content": "/*\ncustom.c - customization routines\nCopyright (C) 2018 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"custom.h\"\n#include \"ref_common.h\"\n#include \"hpak.h\" // be aware of HPK limits\n\nstatic rgbdata_t *CustomDecal_LoadImage( const char *path, void *raw, int size )\n{\n\tconst char *testname;\n\n\t// this way we limit file types\n\tif( !Q_stricmp( COM_FileExtension( path ), \"png\" ))\n\t\ttestname = \"#logo.png\";\n\telse if( !Q_stricmp( COM_FileExtension( path ), \"wad\" ))\n\t\ttestname = \"#logo.wad\";\n\telse testname = \"#logo.bmp\";\n\n\tImage_SetForceFlags( IL_LOAD_PLAYER_DECAL );\n\n\treturn FS_LoadImage( testname, raw, size );\n}\n\nstatic qboolean CustomDecal_Validate( const char *path, void *raw, int nFileSize )\n{\n\trgbdata_t *test = CustomDecal_LoadImage( path, raw, nFileSize );\n\n\tif( test )\n\t{\n\t\t// all's ok, logo is valid\n\t\tFS_FreeImage( test );\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid COM_ClearCustomizationList( customization_t *pHead, qboolean bCleanDecals )\n{\n\tcustomization_t\t*pCurrent;\n\tcustomization_t\t*pNext;\n\n\tfor( pCurrent = pHead->pNext; pCurrent != NULL; pCurrent = pNext )\n\t{\n\t\tpNext = pCurrent->pNext;\n\n\t\tif( pCurrent->bInUse && pCurrent->pBuffer )\n\t\t\tMem_Free( pCurrent->pBuffer );\n\n\t\tif( pCurrent->bInUse && pCurrent->pInfo )\n\t\t{\n#if !XASH_DEDICATED\n\t\t\tif( pCurrent->resource.type == t_decal )\n\t\t\t{\n\t\t\t\tif( bCleanDecals && CL_Active( ))\n\t\t\t\t\tref.dllFuncs.R_DecalRemoveAll( pCurrent->nUserData1 );\n\t\t\t}\n#endif\n\n\t\t\tFS_FreeImage( pCurrent->pInfo );\n\t\t}\n\t\tMem_Free( pCurrent );\n\t}\n\n\tpHead->pNext = NULL;\n}\n\nqboolean COM_CreateCustomization( customization_t *pListHead, resource_t *pResource, int playernumber, int flags, customization_t **pOut, int *nLumps )\n{\n\tqboolean\t\tbError = false;\n\tfs_offset_t\t\tchecksize = 0;\n\tcustomization_t\t*pCust;\n\n\tif( pOut ) *pOut = NULL;\n\n\tpCust = Z_Calloc( sizeof( customization_t ));\n\tpCust->resource = *pResource;\n\n\tif( pResource->nDownloadSize <= 0 )\n\t\tgoto CustomizationError;\n\n\tpCust->bInUse = true;\n\n\tif( FBitSet( flags, FCUST_FROMHPAK ))\n\t{\n\t\tif( !HPAK_GetDataPointer( hpk_custom_file.string, pResource, (byte **)&pCust->pBuffer, NULL ))\n\t\t\tbError = true;\n\t}\n\telse\n\t{\n\n\t\tpCust->pBuffer = FS_LoadFile( pResource->szFileName, &checksize, true );\n\t\tif( (int)checksize != pCust->resource.nDownloadSize )\n\t\t\tbError = true;\n\t}\n\n\tif( bError )\n\t\tgoto CustomizationError;\n\n\tif( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal )\n\t{\n\t\tpCust->resource.playernum = playernumber;\n\n\t\tif( CustomDecal_Validate( pResource->szFileName, pCust->pBuffer, pResource->nDownloadSize ))\n\t\t{\n\t\t\tif( !FBitSet( flags, FCUST_IGNOREINIT ))\n\t\t\t{\n\t\t\t\tif( pResource->nDownloadSize >= HPAK_ENTRY_MIN_SIZE && pResource->nDownloadSize <= HPAK_ENTRY_MAX_SIZE )\n\t\t\t\t{\n\t\t\t\t\tpCust->bTranslated = true;\n\t\t\t\t\tpCust->nUserData1 = 0;\n\t\t\t\t\tpCust->nUserData2 = 7;\n\n\t\t\t\t\tif( !FBitSet( flags, FCUST_WIPEDATA ))\n\t\t\t\t\t\tpCust->pInfo = CustomDecal_LoadImage( pResource->szFileName, pCust->pBuffer, pCust->resource.nDownloadSize );\n\t\t\t\t\telse pCust->pInfo = NULL;\n\t\t\t\t\tif( nLumps ) *nLumps = 1;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( S_WARN \"Ignoring custom decal \\\"%s\\\": wrong size (%i bytes)\\n\", pResource->szFileName, pResource->nDownloadSize );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif( pOut ) *pOut = pCust;\n\tpCust->pNext = pListHead->pNext;\n\tpListHead->pNext = pCust;\n\n\treturn true;\n\nCustomizationError:\n\tif( pCust->pBuffer )\n\t\tMem_Free( pCust->pBuffer );\n\n\tif( pCust->pInfo )\n\t\tMem_Free( pCust->pInfo );\n\tMem_Free( pCust );\n\n\treturn false;\n}\n\nint COM_SizeofResourceList( resource_t *pList, resourceinfo_t *ri )\n{\n\tint\t\tnSize = 0;\n\tresource_t\t*p;\n\n\tmemset( ri, 0, sizeof( *ri ));\n\n\tfor( p = pList->pNext; p != pList; p = p->pNext )\n\t{\n\t\tnSize += p->nDownloadSize;\n\n\t\tif( p->type == t_model && p->nIndex == 1 )\n\t\t\tri->info[t_world].size += p->nDownloadSize;\n\t\telse ri->info[p->type].size += p->nDownloadSize;\n\t}\n\n\treturn nSize;\n}\n"
  },
  {
    "path": "engine/common/cvar.c",
    "content": "/*\ncvar.c - dynamic variable tracking\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <math.h>\t// fabs...\n#include \"common.h\"\n#include \"base_cmd.h\"\n#include \"eiface.h\" // ARRAYSIZE\n\nstatic convar_t\t*cvar_vars = NULL; // head of list\nstatic poolhandle_t cvar_pool;\nCVAR_DEFINE_AUTO( cmd_scripting, \"0\", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, \"enable simple condition checking and variable operations\" );\n\ntypedef struct cvar_filter_quirks_s\n{\n\tconst char *gamedir; // gamedir to enable for\n\tconst char *cvars; // list of cvars should be excluded from filter\n} cvar_filter_quirks_t;\n\n#ifdef HACKS_RELATED_HLMODS\nstatic const cvar_filter_quirks_t cvar_filter_quirks[] =\n{\n\t// EXAMPLE:\n\t//{\n\t//\t\"valve\",\n\t//\t\"test;test1;test100\"\n\t//},\n\t{\n\t\t\"ricochet\",\n\t\t\"r_drawviewmodel\",\n\t},\n\t{\n\t\t\"dod\",\n\t\t\"cl_dodmusic\" // Day of Defeat Beta 1.3 cvar\n\t},\n};\n#endif\n\nstatic const cvar_filter_quirks_t *cvar_active_filter_quirks = NULL;\n\nCVAR_DEFINE_AUTO( cl_filterstuffcmd, \"1\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"filter commands coming from server\" );\n\n/*\n============\nCvar_GetList\n============\n*/\ncvar_t *GAME_EXPORT Cvar_GetList( void )\n{\n\treturn (cvar_t *)cvar_vars;\n}\n\n\n/*\n============\nCvar_FindVar\n\nfind the specified variable by name\n============\n*/\nconvar_t *Cvar_FindVarExt( const char *var_name, int ignore_group )\n{\n\tconvar_t *var;\n\n\tif( !var_name )\n\t\treturn NULL;\n\n#if defined(XASH_HASHED_VARS) // TODO: ignore_group\n\tvar = BaseCmd_Find( HM_CVAR, var_name );\n#else\n\tfor( var = cvar_vars; var; var = var->next )\n\t{\n\t\tif( ignore_group && FBitSet( ignore_group, var->flags ))\n\t\t\tcontinue;\n\n\t\tif( !Q_stricmp( var_name, var->name ))\n\t\t\treturn var;\n\t}\n#endif\n\n\t// HACKHACK: HL25 compatibility\n\tif( !var && !Q_stricmp( var_name, \"gl_widescreen_yfov\" ))\n\t\tvar = Cvar_FindVarExt( \"r_adjust_fov\", ignore_group );\n\n\treturn var;\n}\n\n/*\n============\nCvar_BuildAutoDescription\n\nbuild cvar auto description that based on the setup flags\n============\n*/\nconst char *Cvar_BuildAutoDescription( const char *szName, int flags )\n{\n\tstatic char\tdesc[256];\n\n\tif( FBitSet( flags, FCVAR_GLCONFIG ))\n\t{\n\t\tQ_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, szName );\n\t\treturn desc;\n\t}\n\n\tdesc[0] = '\\0';\n\n\tif( FBitSet( flags, FCVAR_EXTDLL ))\n\t\tQ_strncpy( desc, \"game \", sizeof( desc ));\n\telse if( FBitSet( flags, FCVAR_CLIENTDLL ))\n\t\tQ_strncpy( desc, \"client \", sizeof( desc ));\n\telse if( FBitSet( flags, FCVAR_GAMEUIDLL ))\n\t\tQ_strncpy( desc, \"GameUI \", sizeof( desc ));\n\n\tif( FBitSet( flags, FCVAR_SERVER ))\n\t\tQ_strncat( desc, \"server \", sizeof( desc ));\n\n\tif( FBitSet( flags, FCVAR_USERINFO ))\n\t\tQ_strncat( desc, \"user \", sizeof( desc ));\n\n\tif( FBitSet( flags, FCVAR_ARCHIVE ))\n\t\tQ_strncat( desc, \"archived \", sizeof( desc ));\n\n\tif( FBitSet( flags, FCVAR_PROTECTED ))\n\t\tQ_strncat( desc, \"protected \", sizeof( desc ));\n\n\tif( FBitSet( flags, FCVAR_PRIVILEGED ))\n\t\tQ_strncat( desc, \"privileged \", sizeof( desc ));\n\n\tQ_strncat( desc, \"cvar\", sizeof( desc ));\n\n\treturn desc;\n}\n\n/*\n============\nCvar_UpdateInfo\n\ndeal with userinfo etc\n============\n*/\nstatic qboolean Cvar_UpdateInfo( convar_t *var, const char *value, qboolean notify )\n{\n\tif( FBitSet( var->flags, FCVAR_USERINFO ))\n\t{\n\t\tif( Host_IsDedicated() )\n\t\t{\n\t\t\t// g-cont. this is a very strange behavior...\n\t\t\tchar *info = SV_Serverinfo();\n\n\t\t\tInfo_SetValueForKey( info, var->name, value, MAX_SERVERINFO_STRING ),\n\t\t\tSV_BroadcastCommand( \"fullserverinfo \\\"%s\\\"\\n\", info );\n\t\t}\n#if !XASH_DEDICATED\n\t\telse\n\t\t{\n\t\t\tif( !Info_SetValueForKey( CL_Userinfo(), var->name, value, MAX_INFO_STRING ))\n\t\t\t\treturn false; // failed to change value\n\n\t\t\t// time to update server copy of userinfo\n\t\t\tCL_UpdateInfo( var->name, value );\n\t\t}\n#endif\n\t}\n\n\tif( FBitSet( var->flags, FCVAR_SERVER ) && notify )\n\t{\n\t\tif( !FBitSet( var->flags, FCVAR_UNLOGGED ))\n\t\t{\n\t\t\tif( FBitSet( var->flags, FCVAR_PROTECTED ))\n\t\t\t{\n\t\t\t\tLog_Printf( \"Server cvar \\\"%s\\\" = \\\"%s\\\"\\n\", var->name, \"***PROTECTED***\" );\n\t\t\t\tSV_BroadcastPrintf( NULL, \"\\\"%s\\\" changed to \\\"%s\\\"\\n\", var->name, \"***PROTECTED***\" );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tLog_Printf( \"Server cvar \\\"%s\\\" = \\\"%s\\\"\\n\", var->name, value );\n\t\t\t\tSV_BroadcastPrintf( NULL, \"\\\"%s\\\" changed to \\\"%s\\\"\\n\", var->name, value );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n============\nCvar_ValidateString\n\ndeal with userinfo etc\n============\n*/\nstatic const char *Cvar_ValidateString( convar_t *var, const char *value )\n{\n\tconst char\t*pszValue;\n\tstatic char\tszNew[MAX_STRING];\n\n\tpszValue = value;\n\tszNew[0] = 0;\n\n\t// this cvar's string must only contain printable characters.\n\t// strip out any other crap. we'll fill in \"empty\" if nothing is left\n\tif( FBitSet( var->flags, FCVAR_PRINTABLEONLY ))\n\t{\n\t\tchar\t*szVal = szNew;\n\t\tint\tlen = 0;\n\n\t\t// step through the string, only copying back in characters that are printable\n\t\twhile( *pszValue && len < ( MAX_STRING - 1 ))\n\t\t{\n\t\t\tif( ((byte)*pszValue) < 32 )\n\t\t\t{\n\t\t\t\tpszValue++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t*szVal++ = *pszValue++;\n\t\t\tlen++;\n\t\t}\n\n\t\t*szVal = '\\0';\n\t\tpszValue = szNew;\n\n\t\t// g-cont. is this even need?\n\t\tif( !COM_CheckStringEmpty( szNew ) ) Q_strncpy( szNew, \"empty\", sizeof( szNew ));\n\t}\n\n\tif( FBitSet( var->flags, FCVAR_NOEXTRAWHITESPACE ))\n\t{\n\t\tchar\t*szVal = szNew;\n\t\tint\tlen = 0;\n\n\t\t// step through the string, only copying back in characters that are printable\n\t\twhile( *pszValue && len < MAX_STRING )\n\t\t{\n\t\t\tif( *pszValue == ' ' )\n\t\t\t{\n\t\t\t\tpszValue++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t*szVal++ = *pszValue++;\n\t\t\tlen++;\n\t\t}\n\n\t\t*szVal = '\\0';\n\t\tpszValue = szNew;\n\t}\n\n\treturn pszValue;\n}\n\n/*\n============\nCvar_ValidateVarName\n============\n*/\nstatic qboolean Cvar_ValidateVarName( const char *s, qboolean isvalue )\n{\n\tif( !s )\n\t\treturn false;\n\tif( Q_strchr( s, '\\\\' ) && !isvalue )\n\t\treturn false;\n\tif( Q_strchr( s, '\\\"' ))\n\t\treturn false;\n\tif( Q_strchr( s, ';' ) && !isvalue )\n\t\treturn false;\n\treturn true;\n}\n\nstatic void Cvar_Free( convar_t *var )\n{\n\tfreestring( var->name );\n\tfreestring( var->string );\n\tfreestring( var->def_string );\n\tfreestring( var->desc );\n\tMem_Free( var );\n}\n\n/*\n============\nCvar_UnlinkVar\n\nunlink the variable\n============\n*/\nstatic int Cvar_UnlinkVar( const char *var_name, int group )\n{\n\tint\tcount = 0;\n\tconvar_t\t**prev;\n\tconvar_t\t*var;\n\n\tprev = &cvar_vars;\n\n\twhile( 1 )\n\t{\n\t\tvar = *prev;\n\t\tif( !var ) break;\n\n\t\t// do filter by name\n\t\tif( var_name && Q_strcmp( var->name, var_name ))\n\t\t{\n\t\t\tprev = &var->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// do filter by specified group\n\t\tif( group && !FBitSet( var->flags, group ))\n\t\t{\n\t\t\tprev = &var->next;\n\t\t\tcontinue;\n\t\t}\n\n#if defined(XASH_HASHED_VARS)\n\t\tBaseCmd_Remove( HM_CVAR, var->name );\n#endif\n\n\t\t// unlink variable from list\n\t\t*prev = var->next;\n\n\t\t// only allocated cvars can throw these fields\n\t\tif( FBitSet( var->flags, FCVAR_ALLOCATED ))\n\t\t\tCvar_Free( var );\n\t\telse\n\t\t\tfreestring( var->string );\n\t\tcount++;\n\t}\n\n\treturn count;\n}\n\n/*\n============\nCvar_Changed\n\nTell the engine parts about cvar changing\n============\n*/\nstatic void Cvar_Changed( convar_t *var )\n{\n\tAssert( var != NULL );\n\n\t// tell about changes\n\tSetBits( var->flags, FCVAR_CHANGED );\n\n\t// tell the engine parts with global state\n\tif( FBitSet( var->flags, FCVAR_USERINFO ))\n\t\thost.userinfo_changed = true;\n\n\tif( FBitSet( var->flags, FCVAR_MOVEVARS ))\n\t\thost.movevars_changed = true;\n\n\tif( FBitSet( var->flags, FCVAR_VIDRESTART ))\n\t\thost.renderinfo_changed = true;\n\n\tif( !Q_strcmp( var->name, \"sv_cheats\" ))\n\t\thost.allow_cheats = Q_atoi( var->string );\n}\n\n/*\n============\nCvar_LookupVars\n============\n*/\nvoid Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback )\n{\n\tconvar_t\t*var;\n\n\t// nothing to process ?\n\tif( !callback ) return;\n\n\t// force checkbit to 0 for lookup all cvars\n\tfor( var = cvar_vars; var; var = var->next )\n\t{\n\t\tif( checkbit && !FBitSet( var->flags, checkbit ))\n\t\t\tcontinue;\n\n\t\tif( buffer )\n\t\t{\n\t\t\tcallback( var->name, var->string, buffer, ptr );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// NOTE: dlls cvars doesn't have description\n\t\t\tif( FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED ))\n\t\t\t\tcallback( var->name, var->string, var->desc, ptr );\n\t\t\telse callback( var->name, var->string, \"\", ptr );\n\t\t}\n\t}\n}\n\n/*\n============\nCvar_Get\n\nIf the variable already exists, the value will not be set\nThe flags will be or'ed in if the variable exists.\n============\n*/\nconvar_t *Cvar_Get( const char *name, const char *value, int flags, const char *var_desc )\n{\n\tconvar_t\t*cur, *find, *var;\n\n\tASSERT( name && *name );\n\n\t// check for command coexisting\n\tif( Cmd_Exists( name ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"can't register variable '%s', is already defined as command\\n\", name );\n\t\treturn NULL;\n\t}\n\n\tvar = Cvar_FindVar( name );\n\n\tif( var )\n\t{\n\t\t// already existed?\n\t\tif( FBitSet( flags, FCVAR_GLCONFIG ))\n\t\t{\n\t\t\t// NOTE: cvars without description produced by Cvar_FullSet\n\t\t\t// which executed from the config file. So we don't need to\n\t\t\t// change value here: we *already* have actual value from config.\n\t\t\t// in other cases we need to rewrite them\n\t\t\tif( COM_CheckStringEmpty( var->desc ))\n\t\t\t{\n\t\t\t\t// directly set value\n\t\t\t\tsize_t len = Q_strlen( value ) + 1;\n\t\t\t\tvar->string = Mem_Realloc( cvar_pool, var->string, len );\n\t\t\t\tQ_strncpy( var->string, value, len );\n\t\t\t\tvar->value = Q_atof( var->string );\n\t\t\t\tSetBits( var->flags, flags );\n\n\t\t\t\t// tell engine about changes\n\t\t\t\tCvar_Changed( var );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSetBits( var->flags, flags );\n\t\t\tCvar_DirectSet( var, value );\n\t\t}\n\n\t\tif( FBitSet( var->flags, FCVAR_ALLOCATED ) && Q_strcmp( var_desc, var->desc ))\n\t\t{\n\t\t\tsize_t len = Q_strlen( var_desc ) + 1;\n\n\t\t\tif( !FBitSet( flags, FCVAR_GLCONFIG ))\n\t\t\t\tCon_Reportf( \"%s change description from %s to %s\\n\", var->name, var->desc, var_desc );\n\n\t\t\t// update description if needs\n\t\t\tvar->desc = Mem_Realloc( cvar_pool, var->desc, len );\n\t\t\tQ_strncpy( var->desc, var_desc, len );\n\t\t}\n\n\t\treturn var;\n\t}\n\n\t// allocate a new cvar\n\tvar = Mem_Malloc( cvar_pool, sizeof( *var ));\n\tvar->name = copystringpool( cvar_pool, name );\n\tvar->string = copystringpool( cvar_pool, value );\n\tvar->def_string = copystringpool( cvar_pool, value );\n\tvar->desc = copystringpool( cvar_pool, var_desc );\n\tvar->value = Q_atof( var->string );\n\tvar->flags = flags|FCVAR_ALLOCATED;\n\n\t// link the variable in alphanumerical order\n\tfor( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next );\n\n\tif( cur ) cur->next = var;\n\telse cvar_vars = var;\n\tvar->next = find;\n\n\t// fill it cls.userinfo, svs.serverinfo\n\tCvar_UpdateInfo( var, var->string, false );\n\n\t// tell engine about changes\n\tCvar_Changed( var );\n\n#if defined(XASH_HASHED_VARS)\n\t// add to map\n\tBaseCmd_Insert( HM_CVAR, var, var->name );\n#endif\n\n\treturn var;\n}\n\n/*\n============\nCvar_Getf\n============\n*/\nconvar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... )\n{\n\tchar value[MAX_VA_STRING];\n\tva_list args;\n\n\tva_start( args, format );\n\tQ_vsnprintf( value, sizeof( value ), format, args );\n\tva_end( args );\n\n\treturn Cvar_Get( var_name, value, flags, description );\n}\n\n/*\n============\nCvar_RegisterVariable\n\nAdds a freestanding variable to the variable list.\n============\n*/\nvoid Cvar_RegisterVariable( convar_t *var )\n{\n\tconvar_t\t*cur, *find, *dup;\n\n\tASSERT( var != NULL );\n\n\t// first check to see if it has allready been defined\n\tdup = Cvar_FindVar( var->name );\n\n\tif( dup )\n\t{\n\t\tif( !FBitSet( dup->flags, FCVAR_TEMPORARY ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"can't register variable '%s', is already defined\\n\", var->name );\n\t\t\treturn;\n\t\t}\n\n\t\t// time to replace temp variable with real\n\t\tCvar_UnlinkVar( var->name, FCVAR_TEMPORARY );\n\t}\n\n\t// check for overlap with a command\n\tif( Cmd_Exists( var->name ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"can't register variable '%s', is already defined as command\\n\", var->name );\n\t\treturn;\n\t}\n\n\t// NOTE: all the 'long' engine cvars have an special setntinel on static declaration\n\t// (all the engine cvars should be declared through CVAR_DEFINE macros or they shouldn't working properly anyway)\n\t// so we can determine long version 'convar_t' and short version 'cvar_t' more reliable than by FCVAR_EXTDLL flag\n\tif( CVAR_CHECK_SENTINEL( var )) SetBits( var->flags, FCVAR_EXTENDED );\n\n\t// copy the value off, because future sets will free it\n\tif( FBitSet( var->flags, FCVAR_EXTENDED ))\n\t\tvar->def_string = var->string; // just swap pointers\n\n\tvar->string = copystringpool( cvar_pool, var->string );\n\tvar->value = Q_atof( var->string );\n\n\t// find the supposed position in chain (alphanumerical order)\n\tfor( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next );\n\n\t// now link variable\n\tif( cur ) cur->next = var;\n\telse cvar_vars = var;\n\tvar->next = find;\n\n\t// fill it cls.userinfo, svs.serverinfo\n\tCvar_UpdateInfo( var, var->string, false );\n\n\t// tell engine about changes\n\tCvar_Changed( var );\n\n#if defined(XASH_HASHED_VARS)\n\t// add to map\n\tBaseCmd_Insert( HM_CVAR, var, var->name );\n#endif\n}\n\nstatic qboolean Cvar_CanSet( const convar_t *cv )\n{\n\tif( FBitSet( cv->flags, FCVAR_READ_ONLY ))\n\t{\n\t\tCon_Printf( \"%s is read-only.\\n\", cv->name );\n\t\treturn false;\n\t}\n\n\tif( FBitSet( cv->flags, FCVAR_CHEAT ) && !host.allow_cheats )\n\t{\n\t\tCon_Printf( \"%s is cheat protected.\\n\", cv->name );\n\t\treturn false;\n\t}\n\n\t// just tell user about deferred changes\n\tif( FBitSet( cv->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( )))\n\t\tCon_Printf( \"%s will be changed upon restarting.\\n\", cv->name );\n\n\treturn true;\n}\n\n/*\n============\nCvar_Set2\n============\n*/\nstatic convar_t *Cvar_Set2( const char *var_name, const char *value )\n{\n\tconvar_t\t*var;\n\tqboolean\tdll_variable = false;\n\tqboolean\tforce = false;\n\tconst char *fixed_string;\n\tsize_t fixed_string_len;\n\n\tif( !Cvar_ValidateVarName( var_name, false ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"Invalid cvar name string: %s\\n\", var_name );\n\t\treturn NULL;\n\t}\n\n\tvar = Cvar_FindVar( var_name );\n\tif( !var )\n\t{\n\t\t// if cvar not found, create it\n\t\treturn Cvar_Get( var_name, value, FCVAR_USER_CREATED, NULL );\n\t}\n\telse\n\t{\n\t\tif( !Cmd_CurrentCommandIsPrivileged( ))\n\t\t{\n\t\t\tif( FBitSet( var->flags, FCVAR_PRIVILEGED ))\n\t\t\t{\n\t\t\t\tCon_Printf( \"%s is priveleged.\\n\", var->name );\n\t\t\t\treturn var;\n\t\t\t}\n\n\t\t\tif( cl_filterstuffcmd.value > 0.0f && FBitSet( var->flags, FCVAR_FILTERABLE ))\n\t\t\t{\n\t\t\t\tCon_Printf( \"%s is filterable.\\n\", var->name );\n\t\t\t\treturn var;\n\t\t\t}\n\t\t}\n\t}\n\n\t// use this check to prevent acessing for unexisting fields\n\t// for cvar_t: latched_string, description, etc\n\tdll_variable = FBitSet( var->flags, FCVAR_EXTDLL );\n\n\t// check value\n\tif( !value )\n\t{\n\t\tif( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))\n\t\t{\n\t\t\tCon_Printf( \"%s has no default value and can't be reset.\\n\", var->name );\n\t\t\treturn var;\n\t\t}\n\n\t\tif( dll_variable )\n\t\t\tvalue = \"0\";\n\t\telse\n\t\t\tvalue = var->def_string; // reset to default value\n\t}\n\n\tif( !Q_strcmp( value, var->string ))\n\t\treturn var;\n\n\t// any latched values not allowed for game cvars\n\tif( dll_variable )\n\t\tforce = true;\n\n\tif( !force )\n\t{\n\t\tif( !Cvar_CanSet( var ))\n\t\t\treturn var;\n\t}\n\n\tfixed_string = Cvar_ValidateString( var, value );\n\n\t// nothing to change\n\tif( !Q_strcmp( fixed_string, var->string ))\n\t\treturn var;\n\n\t// fill it cls.userinfo, svs.serverinfo\n\tif( !Cvar_UpdateInfo( var, fixed_string, true ))\n\t\treturn var;\n\n\t// and finally change the cvar itself\n\tfixed_string_len = Q_strlen( fixed_string ) + 1;\n\tvar->string = Mem_Realloc( cvar_pool, var->string, fixed_string_len );\n\tQ_strncpy( var->string, fixed_string, fixed_string_len );\n\tvar->value = Q_atof( var->string );\n\n\t// tell engine about changes\n\tCvar_Changed( var );\n\treturn var;\n}\n\n/*\n============\nCvar_DirectSet\n\nway to change value for many cvars\n============\n*/\nvoid GAME_EXPORT Cvar_DirectSet( convar_t *var, const char *value )\n{\n\tconst char *fixed_string;\n\tsize_t fixed_string_len;\n\n\tif( unlikely( !var )) return;\t// ???\n\n\t// lookup for registration\n\tif( unlikely( CVAR_CHECK_SENTINEL( var ) || ( var->next == NULL && !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))))\n\t{\n\t\t// need to registering cvar fisrt\n\t\tCvar_RegisterVariable( var );\t// ok, register it\n\n\t\t// lookup for registration again\n\t\tif( var != Cvar_FindVar( var->name ))\n\t\t\treturn; // how this possible?\n\t}\n\n\tif( !Cvar_CanSet( var ))\n\t\treturn;\n\n\t// check value\n\tif( !value )\n\t{\n\t\tif( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))\n\t\t{\n\t\t\tCon_Printf( \"%s has no default value and can't be reset.\\n\", var->name );\n\t\t\treturn;\n\t\t}\n\n\t\tvalue = var->def_string; // reset to default value\n\t}\n\n\tfixed_string = Cvar_ValidateString( var, value );\n\n\t// nothing to change\n\tif( !Q_strcmp( fixed_string, var->string ))\n\t\treturn;\n\n\t// fill it cls.userinfo, svs.serverinfo\n\tif( !Cvar_UpdateInfo( var, fixed_string, true ))\n\t\treturn;\n\n\t// and finally change the cvar itself\n\tfixed_string_len = Q_strlen( fixed_string ) + 1;\n\tvar->string = Mem_Realloc( cvar_pool, var->string, fixed_string_len );\n\tQ_strncpy( var->string, fixed_string, fixed_string_len );\n\tvar->value = Q_atof( var->string );\n\n\t// tell engine about changes\n\tCvar_Changed( var );\n}\n\n/*\n============\nCvar_DirectSetValue\n\nfunctionally is the same as Cvar_SetValue but for direct cvar access\n============\n*/\nvoid Cvar_DirectSetValue( convar_t *var, float value )\n{\n\tchar\tval[32];\n\n\tif( fabs( value - (int)value ) < 0.000001 )\n\t\tQ_snprintf( val, sizeof( val ), \"%d\", (int)value );\n\telse Q_snprintf( val, sizeof( val ), \"%f\", value );\n\n\tCvar_DirectSet( var, val );\n}\n\n/*\n============\nCvar_FullSet\n\ncan set any protected cvars\n============\n*/\nvoid Cvar_FullSet( const char *var_name, const char *value, int flags )\n{\n\tconvar_t *var = Cvar_FindVar( var_name );\n\tsize_t len = Q_strlen( value ) + 1;\n\n\tif( !var )\n\t{\n\t\tCvar_Get( var_name, value, flags, \"\" );\n\t\treturn;\n\t}\n\n\tvar->string = Mem_Realloc( cvar_pool, var->string, len );\n\tQ_strncpy( var->string, value, len );\n\tvar->value = Q_atof( var->string );\n\tSetBits( var->flags, flags );\n\n\t// tell engine about changes\n\tCvar_Changed( var );\n}\n\n/*\n============\nCvar_Set\n============\n*/\nvoid GAME_EXPORT Cvar_Set( const char *var_name, const char *value )\n{\n\tconvar_t\t*var;\n\n\tif( !var_name )\n\t{\n\t\t// there is an error in C code if this happens\n\t\tCon_Printf( \"%s: passed NULL variable name\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tvar = Cvar_FindVar( var_name );\n\n\tif( !var )\n\t{\n\t\t// there is an error in C code if this happens\n\t\tCon_Printf( \"%s: variable '%s' not found\\n\", __func__, var_name );\n\t\treturn;\n\t}\n\n\tCvar_DirectSet( var, value );\n}\n\n/*\n============\nCvar_SetValue\n============\n*/\nvoid GAME_EXPORT Cvar_SetValue( const char *var_name, float value )\n{\n\tchar\tval[32];\n\n\tif( fabs( value - (int)value ) < 0.000001 )\n\t\tQ_snprintf( val, sizeof( val ), \"%d\", (int)value );\n\telse Q_snprintf( val, sizeof( val ), \"%f\", value );\n\n\tCvar_Set( var_name, val );\n}\n\n/*\n============\nCvar_Reset\n============\n*/\nvoid Cvar_Reset( const char *var_name )\n{\n\tCvar_Set( var_name, NULL );\n}\n\n/*\n============\nCvar_VariableValue\n============\n*/\nfloat GAME_EXPORT Cvar_VariableValue( const char *var_name )\n{\n\tconvar_t\t*var;\n\n\tif( !var_name )\n\t{\n\t\t// there is an error in C code if this happens\n\t\tCon_Printf( \"%s: passed NULL variable name\\n\", __func__ );\n\t\treturn 0.0f;\n\t}\n\n\tvar = Cvar_FindVar( var_name );\n\tif( !var ) return 0.0f;\n\n\treturn Q_atof( var->string );\n}\n\n/*\n============\nCvar_VariableInteger\n============\n*/\nint Cvar_VariableInteger( const char *var_name )\n{\n\tconvar_t\t*var;\n\n\tvar = Cvar_FindVar( var_name );\n\tif( !var ) return 0;\n\n\treturn Q_atoi( var->string );\n}\n\n/*\n============\nCvar_VariableString\n============\n*/\nconst char *Cvar_VariableString( const char *var_name )\n{\n\tconvar_t\t*var;\n\n\tif( !var_name )\n\t{\n\t\t// there is an error in C code if this happens\n\t\tCon_Printf( \"%s: passed NULL variable name\\n\", __func__ );\n\t\treturn \"\";\n\t}\n\n\tvar = Cvar_FindVar( var_name );\n\tif( !var ) return \"\";\n\n\treturn var->string;\n}\n\n/*\n============\nCvar_Exists\n============\n*/\nqboolean Cvar_Exists( const char *var_name )\n{\n\tif( Cvar_FindVar( var_name ))\n\t\treturn true;\n\treturn false;\n}\n\n/*\n============\nCvar_SetCheatState\n\nAny testing variables will be reset to the safe values\n============\n*/\nvoid Cvar_SetCheatState( void )\n{\n\tconvar_t\t*var;\n\n\t// set all default vars to the safe value\n\tfor( var = cvar_vars; var; var = var->next )\n\t{\n\t\t// can't process dll cvars - missed def_string\n\t\tif( !FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED ))\n\t\t\tcontinue;\n\n\t\tif( FBitSet( var->flags, FCVAR_CHEAT ))\n\t\t{\n\t\t\tif( Q_strcmp( var->def_string, var->string ))\n\t\t\t\tCvar_DirectSet( var, var->def_string );\n\t\t}\n\t}\n}\n\n/*\n============\nCvar_SetGL\n\nAs Cvar_Set, but also flags it as glconfig\n============\n*/\nstatic void Cvar_SetGL( const char *name, const char *value )\n{\n\tconvar_t *var = Cvar_FindVar( name );\n\n\tif( var && !FBitSet( var->flags, FCVAR_GLCONFIG ))\n\t{\n\t\tCon_Reportf( S_ERROR \"Can't set non-GL cvar %s to %s\\n\", name, value );\n\t\treturn;\n\t}\n\n\tCvar_FullSet( name, value, FCVAR_GLCONFIG );\n}\n\nstatic int ShouldSetCvar_splitstr_handler( char *prev, char *next, void *userdata )\n{\n\tsize_t len = next - prev;\n\n\tif( !Q_strnicmp( prev, userdata, len ))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic qboolean Cvar_ShouldSetCvar( convar_t *v, qboolean isPrivileged )\n{\n\tconst char *prefixes[] = { \"cl_\", \"gl_\", \"m_\", \"r_\", \"hud_\", \"joy_\", \"con_\", \"scr_\" };\n\tint i;\n\n\tif( isPrivileged )\n\t\treturn true;\n\n\tif( FBitSet( v->flags, FCVAR_PRIVILEGED ))\n\t\treturn false;\n\n\tif( cl_filterstuffcmd.value <= 0.0f )\n\t\treturn true;\n\n\t// check if game-specific filter exceptions should be applied\n\t// TODO: for cmd exceptions, make generic function\n\tif( cvar_active_filter_quirks )\n\t{\n\t\tif( Q_splitstr((char *)cvar_active_filter_quirks->cvars, ';', v->name, ShouldSetCvar_splitstr_handler ))\n\t\t\treturn true;\n\t}\n\n\tif( FBitSet( v->flags, FCVAR_FILTERABLE ))\n\t\treturn false;\n\n\tfor( i = 0; i < ARRAYSIZE( prefixes ); i++ )\n\t{\n\t\tif( !Q_strnicmp( v->name, prefixes[i], Q_strlen( prefixes[i] )))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nCvar_Command\n\nHandles variable inspection and changing from the console\n============\n*/\nqboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged )\n{\n\t// special case for setup opengl configuration\n\tif( host.apply_opengl_config )\n\t{\n\t\tCvar_SetGL( Cmd_Argv( 0 ), Cmd_Argv( 1 ) );\n\t\treturn true;\n\t}\n\n#if !defined( XASH_HASHED_VARS )\n\t// check variables\n\tv = Cvar_FindVar( Cmd_Argv( 0 ));\n#endif\n\n\tif( !v )\n\t\treturn false;\n\n\t// perform a variable print or set\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tif( FBitSet( v->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED ))\n\t\t\tCon_Printf( \"\\\"%s\\\" is \\\"%s\\\" ( ^3\\\"%s\\\"^7 )\\n\", v->name, v->string, v->def_string );\n\t\telse Con_Printf( \"\\\"%s\\\" is \\\"%s\\\"\\n\", v->name, v->string );\n\n\t\treturn true;\n\t}\n\n\tif( host.apply_game_config )\n\t{\n\t\tif( !FBitSet( v->flags, FCVAR_EXTDLL ))\n\t\t\treturn true; // only game.dll cvars passed\n\t}\n\n\tif( FBitSet( v->flags, FCVAR_SPONLY ) && CL_GetMaxClients() > 1 )\n\t{\n\t\tCon_Printf( \"can't set \\\"%s\\\" in multiplayer\\n\", v->name );\n\t\treturn false;\n\t}\n\telse if( !Cvar_ShouldSetCvar( v, isPrivileged ))\n\t{\n\t\tCon_Printf( \"%s is a privileged variable\\n\", v->name );\n\t\treturn true;\n\t}\n\telse\n\t{\n\t\tCvar_DirectSet( v, Cmd_Argv( 1 ));\n\t\treturn true;\n\t}\n}\n\n/*\n============\nCvar_WriteVariables\n\nWrites lines containing \"variable value\" for all variables\nwith the specified flag set to true.\n============\n*/\nvoid Cvar_WriteVariables( file_t *f, int group )\n{\n\tconvar_t\t*var;\n\n\tfor( var = cvar_vars; var; var = var->next )\n\t{\n\t\tif( FBitSet( var->flags, group ))\n\t\t\tFS_Printf( f, \"%s \\\"%s\\\"\\n\", var->name, var->string );\n\t}\n}\n\n/*\n============\nCvar_Toggle_f\n\nToggles a cvar for easy single key binding\n============\n*/\nstatic void Cvar_Toggle_f( void )\n{\n\tint\tv;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"toggle <variable>\\n\" );\n\t\treturn;\n\t}\n\n\tv = !Cvar_VariableInteger( Cmd_Argv( 1 ));\n\n\tCvar_Set( Cmd_Argv( 1 ), v ? \"1\" : \"0\" );\n}\n\n/*\n============\nCvar_Set_f\n\nAllows setting and defining of arbitrary cvars from console, even if they\nweren't declared in C code.\n============\n*/\nstatic void Cvar_Set_f( void )\n{\n\tint\ti, c, l = 0, len;\n\tchar\tcombined[MAX_CMD_TOKENS];\n\n\tc = Cmd_Argc();\n\tif( c < 3 )\n\t{\n\t\tMsg( S_USAGE \"set <variable> <value>\\n\" );\n\t\treturn;\n\t}\n\tcombined[0] = 0;\n\n\tfor( i = 2; i < c; i++ )\n\t{\n\t\tlen = Q_strlen( Cmd_Argv(i) + 1 );\n\t\tif( l + len >= MAX_CMD_TOKENS - 2 )\n\t\t\tbreak;\n\t\tQ_strncat( combined, Cmd_Argv( i ), sizeof( combined ));\n\t\tif( i != c-1 ) Q_strncat( combined, \" \", sizeof( combined ));\n\t\tl += len;\n\t}\n\n\tCvar_Set2( Cmd_Argv( 1 ), combined );\n}\n\n/*\n============\nCvar_SetGL_f\n\nAs Cvar_Set, but also flags it as glconfig\n============\n*/\nstatic void Cvar_SetGL_f( void )\n{\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"setgl <variable> <value>\\n\" );\n\t\treturn;\n\t}\n\n\tCvar_SetGL( Cmd_Argv( 1 ), Cmd_Argv( 2 ) );\n}\n\n/*\n============\nCvar_Reset_f\n============\n*/\nstatic void Cvar_Reset_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"reset <variable>\\n\" );\n\t\treturn;\n\t}\n\n\tCvar_Reset( Cmd_Argv( 1 ));\n}\n\n/*\n============\nCvar_List_f\n============\n*/\nstatic void Cvar_List_f( void )\n{\n\tconvar_t\t*var;\n\tconst char\t*match = NULL;\n\tint\tcount = 0;\n\tsize_t\tmatchlen = 0;\n\n\tif( Cmd_Argc() > 1 )\n\t{\n\t\tmatch = Cmd_Argv( 1 );\n\t\tmatchlen = Q_strlen( match );\n\t}\n\n\tfor( var = cvar_vars; var; var = var->next )\n\t{\n\t\tchar value[MAX_VA_STRING];\n\t\tchar *p;\n\n\t\tif( var->name[0] == '@' )\n\t\t\tcontinue;\t// never shows system cvars\n\n\t\tif( match && !Q_strnicmpext( match, var->name, matchlen ))\n\t\t\tcontinue;\n\n\t\tp = Q_strchr( var->string, '^' );\n\n\t\tif( IsColorString( p ))\n\t\t\tQ_snprintf( value, sizeof( value ), \"\\\"%s\\\"\", var->string );\n\t\telse Q_snprintf( value, sizeof( value ), \"\\\"^2%s^7\\\"\", var->string );\n\n\t\tif( FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))\n\t\t\tCon_Printf( \" %-*s %s ^3%s^7\\n\", 32, var->name, value, var->desc );\n\t\telse Con_Printf( \" %-*s %s ^3%s^7\\n\", 32, var->name, value, Cvar_BuildAutoDescription( var->name, var->flags ));\n\n\t\tcount++;\n\t}\n\n\tCon_Printf( \"\\n%i cvars\\n\", count );\n}\n\nstatic qboolean Cvar_ValidateUnlinkGroup( int group )\n{\n\tif( FBitSet( group, FCVAR_EXTDLL ) && !Cvar_VariableInteger( \"host_gameloaded\" ))\n\t\treturn false;\n\n\tif( FBitSet( group, FCVAR_CLIENTDLL ) && !Cvar_VariableInteger( \"host_clientloaded\" ))\n\t\treturn false;\n\n\tif( FBitSet( group, FCVAR_GAMEUIDLL ) && !Cvar_VariableInteger( \"host_gameuiloaded\" ))\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n============\nCvar_Unlink\n\nunlink all cvars with specified flag\n============\n*/\nvoid Cvar_Unlink( int group )\n{\n\tint\tcount;\n\n\tif( !Cvar_ValidateUnlinkGroup( group ))\n\t\treturn;\n\n\tcount = Cvar_UnlinkVar( NULL, group );\n\tCon_Reportf( \"unlink %i cvars\\n\", count );\n}\n\npending_cvar_t *Cvar_PrepareToUnlink( int group )\n{\n\tpending_cvar_t *list = NULL;\n\tpending_cvar_t *tail = NULL;\n\tconvar_t *cv;\n\n\tfor( cv = cvar_vars; cv != NULL; cv = cv->next )\n\t{\n\t\tsize_t namelen;\n\t\tpending_cvar_t *p;\n\n\t\tif( !FBitSet( cv->flags, group ))\n\t\t\tcontinue;\n\n\t\tnamelen = Q_strlen( cv->name ) + 1;\n\t\tp = Mem_Malloc( cvar_pool, sizeof( *list ) + namelen );\n\t\tp->next = NULL;\n\t\tp->cv_cur = cv;\n\t\tp->cv_next = cv->next;\n\t\tp->cv_allocated = FBitSet( cv->flags, FCVAR_ALLOCATED ) ? true : false;\n\t\tQ_strncpy( p->cv_name, cv->name, namelen );\n\n\t\tif( list == NULL )\n\t\t\tlist = p;\n\t\telse\n\t\t\ttail->next = p;\n\n\t\ttail = p;\n\t}\n\n\treturn list;\n}\n\nvoid Cvar_UnlinkPendingCvars( pending_cvar_t *list )\n{\n\tint count = 0;\n\n\twhile( list != NULL )\n\t{\n\t\tpending_cvar_t *next = list->next;\n\t\tconvar_t *cv_prev, *cv;\n\n\t\tfor( cv_prev = NULL, cv = cvar_vars; cv != NULL; cv_prev = cv, cv = cv->next )\n\t\t{\n\t\t\tif( cv == list->cv_cur )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( cv == NULL )\n\t\t{\n\t\t\tCon_Reportf( \"%s: can't find %s in variable list\\n\", __func__, list->cv_name );\n\t\t\tMem_Free( list );\n\t\t\tlist = next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// unlink cvar from list\n\t\tBaseCmd_Remove( HM_CVAR, list->cv_name );\n\t\tif( cv_prev != NULL )\n\t\t\tcv_prev->next = list->cv_next;\n\t\telse cvar_vars = list->cv_next;\n\n\t\tif( list->cv_allocated )\n\t\t\tCvar_Free( list->cv_cur );\n\t\telse\n\t\t{\n\t\t\t// TODO: can't free cvar string here because\n\t\t\t// it's not safe to access cv_cur and\n\t\t\t// can't save string pointer because it could've been changed\n\t\t\t// and pointer to it is already lost\n\t\t\t// freestring( list->cv_string );\n\t\t}\n\n\t\t// now free pending cvar\n\t\tMem_Free( list );\n\t\tlist = next;\n\t\tcount++;\n\t}\n\n\tCon_Reportf( \"unlink %i cvars\\n\", count );\n}\n\n/*\n============\nCvar_Init\n\nReads in all archived cvars\n============\n*/\nvoid Cvar_Init( void )\n{\n\tcvar_pool = Mem_AllocPool( \"Console Variables\" );\n\tcvar_vars = NULL;\n\tcvar_active_filter_quirks = NULL;\n\tCvar_RegisterVariable( &cmd_scripting );\n\tCvar_RegisterVariable( &host_developer ); // early registering for dev\n\tCvar_RegisterVariable( &cl_filterstuffcmd );\n\tCmd_AddRestrictedCommand( \"setgl\", Cvar_SetGL_f, \"change the value of a opengl variable\" );\t// OBSOLETE\n\tCmd_AddRestrictedCommand( \"toggle\", Cvar_Toggle_f, \"toggles a console variable's values (use for more info)\" );\n\tCmd_AddRestrictedCommand( \"reset\", Cvar_Reset_f, \"reset any type variable to initial value\" );\n\tCmd_AddCommand( \"set\", Cvar_Set_f, \"create or change the value of a console variable\" );\n\tCmd_AddCommand( \"cvarlist\", Cvar_List_f, \"display all console variables beginning with the specified prefix\" );\n}\n\nvoid Cvar_Shutdown( void )\n{\n\tMem_FreePool( &cvar_pool );\n}\n\n/*\n============\nCvar_PostFSInit\n\n============\n*/\nvoid Cvar_PostFSInit( void )\n{\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( cvar_filter_quirks ); i++ )\n\t{\n\t\tif( !Q_stricmp( cvar_filter_quirks[i].gamedir, GI->gamefolder ))\n\t\t{\n\t\t\tcvar_active_filter_quirks = &cvar_filter_quirks[i];\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nvoid Test_RunCvar( void )\n{\n\tconvar_t *test_privileged = Cvar_Get( \"test_privileged\", \"0\", FCVAR_PRIVILEGED, \"bark bark\" );\n\tconvar_t *test_unprivileged = Cvar_Get( \"test_unprivileged\", \"0\", 0, \"meow meow\" );\n\tconvar_t *hud_filtered = Cvar_Get( \"hud_filtered\", \"0\", 0, \"dummy description\" );\n\tconvar_t *filtered2 = Cvar_Get( \"filtered2\", \"0\", FCVAR_FILTERABLE, \"filtered2\" );\n\n\tCbuf_AddText( \"test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\\n\" );\n\tCbuf_Execute();\n\tTASSERT( test_privileged->value   != 0.0f );\n\tTASSERT( test_unprivileged->value != 0.0f );\n\tTASSERT( hud_filtered->value      != 0.0f );\n\tTASSERT( filtered2->value         != 0.0f );\n\n\tCvar_DirectSet( test_privileged,   \"0\" );\n\tCvar_DirectSet( test_unprivileged, \"0\" );\n\tCvar_DirectSet( hud_filtered,      \"0\" );\n\tCvar_DirectSet( filtered2,         \"0\" );\n\tCvar_DirectSet( &cl_filterstuffcmd, \"0\" );\n\tCbuf_AddFilteredText( \"test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\\n\" );\n\tCbuf_Execute();\n\tCbuf_Execute();\n\tCbuf_Execute();\n\tTASSERT( test_privileged->value   == 0.0f );\n\tTASSERT( test_unprivileged->value != 0.0f );\n\tTASSERT( hud_filtered->value      != 0.0f );\n\tTASSERT( filtered2->value         != 0.0f );\n\n\tCvar_DirectSet( test_privileged,   \"0\" );\n\tCvar_DirectSet( test_unprivileged, \"0\" );\n\tCvar_DirectSet( hud_filtered,      \"0\" );\n\tCvar_DirectSet( filtered2,         \"0\" );\n\tCvar_DirectSet( &cl_filterstuffcmd, \"1\" );\n\tCbuf_AddFilteredText( \"test_privileged 1; test_unprivileged 1; hud_filtered 1; filtered2 1\\n\" );\n\tCbuf_Execute();\n\tCbuf_Execute();\n\tCbuf_Execute();\n\tTASSERT( test_privileged->value   == 0.0f );\n\tTASSERT( test_unprivileged->value != 0.0f );\n\tTASSERT( hud_filtered->value      == 0.0f );\n\tTASSERT( filtered2->value         == 0.0f );\n}\n#endif\n"
  },
  {
    "path": "engine/common/cvar.h",
    "content": "/*\ncvar.h - dynamic variable tracking\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef CVAR_H\n#define CVAR_H\n\n#include \"cvardef.h\"\n\n// As some mods dynamically allocate cvars and free them without notifying the engine\n// let's construct a list of cvars that must be removed\ntypedef struct pending_cvar_s\n{\n\tstruct pending_cvar_s *next;\n\n\tconvar_t *cv_cur; // preserve the data that might get freed\n\tconvar_t *cv_next;\n\tqboolean  cv_allocated; // if it's allocated by us, it's safe to access cv_cur\n\tchar      cv_name[];\n} pending_cvar_t;\n\ntypedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs );\n\ncvar_t *Cvar_GetList( void );\n#define Cvar_FindVar( name )\tCvar_FindVarExt( name, 0 )\nconvar_t *Cvar_FindVarExt( const char *var_name, int ignore_group );\nvoid Cvar_RegisterVariable( convar_t *var );\nconvar_t *Cvar_Get( const char *var_name, const char *value, int flags, const char *description );\nconvar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) FORMAT_CHECK( 4 );\nvoid Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback );\nvoid Cvar_FullSet( const char *var_name, const char *value, int flags );\nvoid Cvar_DirectSet( convar_t *var, const char *value );\nvoid Cvar_DirectSetValue( convar_t *var, float value );\nvoid Cvar_Set( const char *var_name, const char *value );\nvoid Cvar_SetValue( const char *var_name, float value );\nconst char *Cvar_BuildAutoDescription( const char *szName, int flags ) RETURNS_NONNULL;\nfloat Cvar_VariableValue( const char *var_name );\nint Cvar_VariableInteger( const char *var_name );\nconst char *Cvar_VariableString( const char *var_name ) RETURNS_NONNULL;\nvoid Cvar_WriteVariables( file_t *f, int group );\nqboolean Cvar_Exists( const char *var_name );\nvoid Cvar_Reset( const char *var_name );\nvoid Cvar_SetCheatState( void );\nqboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged );\nvoid Cvar_Init( void );\nvoid Cvar_Shutdown( void );\nvoid Cvar_PostFSInit( void );\nvoid Cvar_Unlink( int group );\n\npending_cvar_t *Cvar_PrepareToUnlink( int group );\nvoid Cvar_UnlinkPendingCvars( pending_cvar_t *pending_cvars );\n\n#endif//CVAR_H\n"
  },
  {
    "path": "engine/common/dedicated.c",
    "content": "/*\ndedicated.c - stubs for dedicated server\nCopyright (C) 2018 a1batross, mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#if XASH_DEDICATED\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_api.h\"\n#include \"server.h\"\n\nref_globals_t refState;\n\nconst char *CL_MsgInfo( int cmd )\n{\n\tstatic string\tsz;\n\n\tQ_strncpy( sz, \"???\", sizeof( sz ));\n\n\tif( cmd >= 0 && cmd <= svc_lastmsg )\n\t{\n\t\t// get engine message name\n\t\tconst char *svc_string = svc_strings[cmd];\n\n\t\tQ_strncpy( sz, svc_string, sizeof( sz ));\n\t}\n\telse if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES ))\n\t{\n\t\tint\ti;\n\n\t\tfor( i = 0; i < MAX_USER_MESSAGES; i++ )\n\t\t{\n\t\t\tif( svgame.msg[i].number == cmd )\n\t\t\t{\n\t\t\t\tQ_strncpy( sz, svgame.msg[i].name, sizeof( sz ));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn sz;\n}\n\nvoid Key_Init( void )\n{\n\n}\n\nvoid IN_Init( void )\n{\n\n}\n\nvoid CL_Drop( void )\n{\n\n}\n\nvoid CL_ClearEdicts( void )\n{\n\n}\n\nvoid GAME_EXPORT Key_SetKeyDest(int key_dest)\n{\n\n}\n\nvoid UI_SetActiveMenu( qboolean fActive )\n{\n\n}\n\nvoid CL_WriteMessageHistory( void )\n{\n\n}\n\nvoid Host_InputFrame( void )\n{\n}\n\nvoid VID_InitDefaultResolution( void )\n{\n\n}\n\nvoid Con_Init( void )\n{\n\n}\n\nvoid GAME_EXPORT S_StopSound(int entnum, int channel, const char *soundname)\n{\n\n}\n\nvoid IN_TouchInitConfig( void )\n{\n\n}\n\nvoid CL_Disconnect( void )\n{\n\n}\n\nvoid R_ClearStaticEntities( void )\n{\n\n}\n\nvoid Host_Credits( void )\n{\n\n}\n\nvoid S_StopBackgroundTrack( void )\n{\n\n}\n\nvoid SCR_BeginLoadingPlaque( qboolean is_background )\n{\n\n}\n\nvoid S_StopAllSounds( qboolean ambient )\n{\n\n}\n\nvoid GAME_EXPORT Con_NPrintf( int idx, const char *fmt, ... )\n{\n\n}\n\nvoid GAME_EXPORT Con_NXPrintf( struct  con_nprint_s *info, const char *fmt, ... )\n{\n\n}\n\nvoid SCR_CheckStartupVids( void )\n{\n\n}\n\nvoid CL_StopPlayback( void )\n{\n\n}\n\nvoid CL_ClearStaticEntities( void )\n{\n\n}\n\nvoid UI_ShowConnectionWarning( void )\n{\n\n}\n\nvoid CL_Crashed( void )\n{\n}\n\nvoid CL_HudMessage( const char *pMessage )\n{\n\n}\n\nbyte TextureToGamma( byte b )\n{\n\treturn b;\n}\n\nbyte LightToTexGamma( byte b )\n{\n\treturn b;\n}\n\n#endif // XASH_DEDICATED\n"
  },
  {
    "path": "engine/common/filesystem_engine.c",
    "content": "/*\nfilesystem.c - game filesystem based on DP fs\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if XASH_SDL == 2\n#include <SDL.h> // SDL_GetBasePath\n#elif XASH_SDL == 3\n#include <SDL3/SDL.h>\n#endif\n\n#include <errno.h>\n#include \"common.h\"\n#include \"library.h\"\n#include \"platform/platform.h\"\n\nstatic CVAR_DEFINE_AUTO( fs_mount_hd, \"0\", FCVAR_PRIVILEGED, \"mount high definition content folder\" );\nstatic CVAR_DEFINE_AUTO( fs_mount_lv, \"0\", FCVAR_PRIVILEGED, \"mount low violence models content folder\" );\nstatic CVAR_DEFINE_AUTO( fs_mount_addon, \"0\", FCVAR_PRIVILEGED, \"mount addon content folder\" );\nstatic CVAR_DEFINE_AUTO( fs_mount_l10n, \"0\", FCVAR_PRIVILEGED, \"mount localization content folder\" );\nstatic CVAR_DEFINE_AUTO( ui_language, \"english\", FCVAR_PRIVILEGED, \"selected game language\" );\n\nfs_api_t g_fsapi;\nfs_globals_t *FI;\n\nstatic pfnCreateInterface_t fs_pfnCreateInterface;\nstatic HINSTANCE fs_hInstance;\n\nsearch_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly )\n{\n\treturn g_fsapi.Search( pattern, caseinsensitive, gamedironly );\n}\n\nint FS_Close( file_t *file )\n{\n\treturn g_fsapi.Close( file );\n}\n\nfile_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly )\n{\n\treturn g_fsapi.Open( filepath, mode, gamedironly );\n}\n\nbyte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n{\n\treturn g_fsapi.LoadFile( path, filesizeptr, gamedironly );\n}\n\nbyte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr )\n{\n\treturn g_fsapi.LoadDirectFile( path, filesizeptr );\n}\n\nstatic void COM_StripDirectorySlash( char *pname )\n{\n\tsize_t len;\n\n\tlen = Q_strlen( pname );\n\tif( len > 0 && pname[len - 1] == '/' )\n\t\tpname[len - 1] = 0;\n}\n\nvoid *FS_GetNativeObject( const char *obj )\n{\n\tif( fs_pfnCreateInterface )\n\t\treturn fs_pfnCreateInterface( obj, NULL );\n\n\treturn NULL;\n}\n\nstatic uint32_t FS_MountFlags( void )\n{\n\tuint32_t flags = 0;\n\n\t// FIXME: VFS shouldn't care about this, allow engine to mount gamedirs\n\tif( fs_mount_lv.value ) SetBits( flags, FS_MOUNT_LV );\n\tif( fs_mount_hd.value ) SetBits( flags, FS_MOUNT_HD );\n\tif( fs_mount_addon.value ) SetBits( flags, FS_MOUNT_ADDON );\n\tif( fs_mount_l10n.value ) SetBits( flags, FS_MOUNT_L10N );\n\n\treturn flags;\n}\n\nvoid FS_Rescan_f( void )\n{\n\tg_fsapi.Rescan( FS_MountFlags(), ui_language.string );\n}\n\nstatic void FS_LoadVFSConfig( const char *gamedir )\n{\n\tstring parm;\n\n\tif( Host_IsDedicated( ))\n\t\treturn;\n\n\tCbuf_AddTextf( \"exec %s/vfs.cfg\\n\", gamedir );\n\tCbuf_Execute();\n\n\tif( Sys_GetParmFromCmdLine( \"-language\", parm ))\n\t{\n\t\tCvar_DirectSet( &ui_language, parm );\n\t\tCvar_DirectSet( &fs_mount_l10n, \"1\" );\n\t}\n\n\tClearBits( fs_mount_hd.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_lv.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_l10n.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_addon.flags, FCVAR_CHANGED );\n\tClearBits( ui_language.flags, FCVAR_CHANGED );\n}\n\nvoid FS_SaveVFSConfig( void )\n{\n\tfile_t *f;\n\n\tif( !FBitSet( fs_mount_hd.flags|fs_mount_lv.flags|fs_mount_l10n.flags|fs_mount_addon.flags|ui_language.flags, FCVAR_CHANGED ))\n\t{\n\t\tCon_Reportf( \"%s: no need to save vfs.cfg\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"%s()\\n\", __func__ );\n\n\tf = FS_Open( \"vfs.cfg.new\", \"w\", true );\n\tif( !f )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: couldn't open vfs.cfg for write\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tFS_Printf( f, \"%s \\\"%d\\\"\\n\", fs_mount_hd.name, (int)fs_mount_hd.value );\n\tFS_Printf( f, \"%s \\\"%d\\\"\\n\", fs_mount_lv.name, (int)fs_mount_lv.value );\n\tFS_Printf( f, \"%s \\\"%d\\\"\\n\", fs_mount_l10n.name, (int)fs_mount_l10n.value );\n\tFS_Printf( f, \"%s \\\"%d\\\"\\n\", fs_mount_addon.name, (int)fs_mount_addon.value );\n\tFS_Printf( f, \"%s \\\"%s\\\"\\n\", ui_language.name, ui_language.string );\n\n\tHost_FinalizeConfig( f, \"vfs.cfg\" );\n\n\tClearBits( fs_mount_hd.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_lv.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_l10n.flags, FCVAR_CHANGED );\n\tClearBits( fs_mount_addon.flags, FCVAR_CHANGED );\n\tClearBits( ui_language.flags, FCVAR_CHANGED );\n}\n\nvoid FS_LoadGameInfo( void )\n{\n\tFS_LoadVFSConfig( g_fsapi.Gamedir( ));\n\n\tg_fsapi.LoadGameInfo( FS_MountFlags(), ui_language.string );\n}\n\nstatic void FS_ClearPaths_f( void )\n{\n\tFS_ClearSearchPath();\n}\n\nstatic void FS_Path_f_( void )\n{\n\tFS_Path_f();\n}\n\nstatic void FS_MakeGameInfo_f( void )\n{\n\tg_fsapi.MakeGameInfo();\n}\n\nstatic const fs_interface_t fs_memfuncs =\n{\n\tCon_Printf,\n\tCon_DPrintf,\n\tCon_Reportf,\n\tSys_Error,\n\n\t_Mem_AllocPool,\n\t_Mem_FreePool,\n\t_Mem_Alloc,\n\t_Mem_Realloc,\n\t_Mem_Free,\n\n\tSys_GetNativeObject,\n};\n\nstatic void FS_UnloadProgs( void )\n{\n\tif( fs_hInstance )\n\t{\n\t\tCOM_FreeLibrary( fs_hInstance );\n\t\tfs_hInstance = 0;\n\t}\n}\n\n#ifdef XASH_INTERNAL_GAMELIBS\n#define FILESYSTEM_STDIO_DLL \"filesystem_stdio\"\n#elif XASH_ANDROID\n#define FILESYSTEM_STDIO_DLL \"libfilesystem_stdio.so\"\n#else\n#define FILESYSTEM_STDIO_DLL \"filesystem_stdio.\" OS_LIB_EXT\n#endif\n\nstatic qboolean FS_LoadProgs( void )\n{\n\tconst char *name = FILESYSTEM_STDIO_DLL;\n\tFSAPI GetFSAPI;\n\n\tfs_hInstance = COM_LoadLibrary( name, false, true );\n\n\tif( !fs_hInstance )\n\t{\n\t\tSys_Error( \"%s: can't load filesystem library %s: %s\\n\", __func__, name, COM_GetLibraryError() );\n\t\treturn false;\n\t}\n\n\tif( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API )))\n\t{\n\t\tFS_UnloadProgs();\n\t\tSys_Error( \"%s: can't find GetFSAPI entry point in %s\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif( GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs ) != FS_API_VERSION )\n\t{\n\t\tFS_UnloadProgs();\n\t\tSys_Error( \"%s: can't initialize filesystem API: wrong version\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tif( !( fs_pfnCreateInterface = (pfnCreateInterface_t)COM_GetProcAddress( fs_hInstance, \"CreateInterface\" )))\n\t{\n\t\tFS_UnloadProgs();\n\t\tSys_Error( \"%s: can't find CreateInterface entry point in %s\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tCon_DPrintf( \"%s: filesystem_stdio successfully loaded\\n\", __func__ );\n\treturn true;\n}\n\nstatic qboolean FS_DetermineRootDirectory( char *out, size_t size )\n{\n\tconst char *path = getenv( \"XASH3D_BASEDIR\" );\n\n\tif( COM_CheckString( path ))\n\t{\n\t\tQ_strncpy( out, path, size );\n\t\treturn true;\n\t}\n\n#if TARGET_OS_IOS\n\tQ_strncpy( out, IOS_GetDocsDir(), size );\n\treturn true;\n#elif XASH_ANDROID && XASH_SDL\n\tpath = SDL_AndroidGetExternalStoragePath();\n\tif( path != NULL )\n\t{\n\t\tQ_strncpy( out, path, size );\n\t\treturn true;\n\t}\n\tSys_Error( \"couldn't determine Android external storage path: %s\", SDL_GetError( ));\n\treturn false;\n#elif XASH_PSVITA\n\tif( PSVita_GetBasePath( out, size ))\n\t\treturn true;\n\tSys_Error( \"couldn't find %s data directory\", XASH_ENGINE_NAME );\n\treturn false;\n#elif ( XASH_SDL >= 2 ) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2\n\tpath = SDL_GetBasePath();\n\n#if XASH_APPLE\n\tif( path != NULL && Q_stristr( path, \".app\" ))\n\t{\n\t\tSDL_free((void *)path );\n\t\tpath = SDL_GetPrefPath( NULL, XASH_ENGINE_NAME );\n\t}\n#endif\n\n\tif( path != NULL )\n\t{\n\t\tQ_strncpy( out, path, size );\n\t\tSDL_free((void *)path );\n\t\treturn true;\n\t}\n\n#if XASH_POSIX || XASH_WIN32\n\tif( getcwd( out, size ))\n\t\treturn true;\n\tSys_Error( \"couldn't determine current directory: %s, getcwd: %s\", SDL_GetError(), strerror( errno ));\n#else // !( XASH_POSIX || XASH_WIN32 )\n\tSys_Error( \"couldn't determine current directory: %s\", SDL_GetError( ));\n#endif // !( XASH_POSIX || XASH_WIN32 )\n\treturn false;\n#else // generic case\n\tif( getcwd( out, size ))\n\t\treturn true;\n\n\tSys_Error( \"couldn't determine current directory: %s\", strerror( errno ));\n\treturn false;\n#endif // generic case\n}\n\nstatic qboolean FS_DetermineReadOnlyRootDirectory( char *out, size_t size )\n{\n\tconst char *env_rodir = getenv( \"XASH3D_RODIR\" );\n\n\tif( _Sys_GetParmFromCmdLine( \"-rodir\", out, size ))\n\t\treturn true;\n\n\tif( COM_CheckString( env_rodir ))\n\t{\n\t\tQ_strncpy( out, env_rodir, size );\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n================\nFS_Init\n================\n*/\nvoid FS_Init( const char *basedir )\n{\n\tstring gamedir;\n\tchar rodir[MAX_OSPATH], rootdir[MAX_OSPATH];\n\trodir[0] = rootdir[0] = 0;\n\n\tif( !FS_DetermineRootDirectory( rootdir, sizeof( rootdir )) || !COM_CheckStringEmpty( rootdir ))\n\t{\n\t\tSys_Error( \"couldn't determine current directory (empty string)\" );\n\t\treturn;\n\t}\n\tCOM_FixSlashes( rootdir );\n\tCOM_StripDirectorySlash( rootdir );\n\n\tFS_DetermineReadOnlyRootDirectory( rodir, sizeof( rodir ));\n\tCOM_FixSlashes( rodir );\n\tCOM_StripDirectorySlash( rodir );\n\n\tif( !Sys_GetParmFromCmdLine( \"-game\", gamedir ))\n\t{\n\t\tchar *env = getenv( \"XASH3D_GAME\" );\n\t\tif( env )\n\t\t\tQ_strncpy( gamedir, env, sizeof( gamedir ));\n\t\telse\n\t\t\tQ_strncpy( gamedir, basedir, sizeof( gamedir )); // gamedir == basedir\n\t}\n\n\tFS_LoadProgs();\n\n\t// TODO: this function will cause engine to stop in case of fail\n\t// when it will have an option to return string error, restore Sys_Error\n\t// FIXME: why do we call this function before InitStdio?\n\t// because InitStdio immediately scans all available game directories\n\t// and this better be reworked at some point\n\tg_fsapi.SetCurrentDirectory( rootdir );\n\n\tif( !g_fsapi.InitStdio( true, rootdir, basedir, gamedir, rodir ))\n\t{\n\t\tSys_Error( \"Can't init filesystem_stdio!\\n\" );\n\t\treturn;\n\t}\n\n\tCmd_AddRestrictedCommand( \"fs_rescan\", FS_Rescan_f, \"rescan filesystem search pathes\" );\n\tCmd_AddRestrictedCommand( \"fs_path\", FS_Path_f_, \"show filesystem search pathes\" );\n\tCmd_AddRestrictedCommand( \"fs_clearpaths\", FS_ClearPaths_f, \"clear filesystem search pathes\" );\n\tCmd_AddRestrictedCommand( \"fs_make_gameinfo\", FS_MakeGameInfo_f, \"create gameinfo.txt for current running game\" );\n\n\tCvar_RegisterVariable( &fs_mount_hd );\n\tCvar_RegisterVariable( &fs_mount_lv );\n\tCvar_RegisterVariable( &fs_mount_addon );\n\tCvar_RegisterVariable( &fs_mount_l10n );\n\tCvar_RegisterVariable( &ui_language );\n\n\tif( !Sys_GetParmFromCmdLine( \"-dll\", host.gamedll ))\n\t\thost.gamedll[0] = 0;\n\n\tif( !Sys_GetParmFromCmdLine( \"-clientlib\", host.clientlib ))\n\t\thost.clientlib[0] = 0;\n\n\tif( !Sys_GetParmFromCmdLine( \"-menulib\", host.menulib ))\n\t\thost.menulib[0] = 0;\n}\n\n/*\n================\nFS_Shutdown\n================\n*/\nvoid FS_Shutdown( void )\n{\n\tif( g_fsapi.ShutdownStdio )\n\t\tg_fsapi.ShutdownStdio();\n\n\tFS_UnloadProgs();\n}\n"
  },
  {
    "path": "engine/common/host.c",
    "content": "/*\nhost.c - dedicated and normal host\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"build.h\"\n#include <stdarg.h>  // va_args\n#if !XASH_WIN32\n#include <unistd.h> // fork\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#endif\n#if XASH_EMSCRIPTEN\n#include <emscripten/emscripten.h>\n#endif\n#include \"common.h\"\n#include \"base_cmd.h\"\n#include \"client.h\"\n#include \"server.h\"\n#include \"netchan.h\"\n#include \"protocol.h\"\n#include \"mod_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"input.h\"\n#include \"enginefeatures.h\"\n#include \"render_api.h\"\t// decallist_t\n#include \"tests.h\"\n\nstatic pfnChangeGame\tpChangeGame = NULL;\nhost_parm_t\t\thost;\t// host parms\n\n#if XASH_ANDROID\nstatic jmp_buf return_from_main_buf;\n\n/*\n===============\nHost_ExitInMain\n\nOn some platforms (e.g. Android) we can't exit with exit(3) as calling it would\nkill wrapper process (e.g. app_process) too early, before all resources would\nbe freed, contexts released, files closed, etc, etc...\n\nTo fix this, we create jmp_buf in Host_Main function, when jumping into with\nnon-zero value will immediately return from it with `error_on_exit`.\n===============\n*/\nvoid Host_ExitInMain( void )\n{\n\tlongjmp( return_from_main_buf, 1 );\n}\n#endif // XASH_ANDROID\n\n#ifdef XASH_ENGINE_TESTS\nstruct tests_stats_s tests_stats;\n#endif\n\nCVAR_DEFINE( host_developer, \"developer\", \"0\", FCVAR_FILTERABLE, \"engine is in development-mode\" );\nCVAR_DEFINE_AUTO( sys_timescale, \"1.0\", FCVAR_FILTERABLE, \"scale frame time\" );\n\nstatic CVAR_DEFINE_AUTO( sys_ticrate, \"100\", FCVAR_SERVER, \"framerate in dedicated mode\" );\nstatic CVAR_DEFINE_AUTO( host_serverstate, \"0\", FCVAR_READ_ONLY, \"displays current server state\" );\nstatic CVAR_DEFINE_AUTO( host_gameloaded, \"0\", FCVAR_READ_ONLY, \"inidcates a loaded game.dll\" );\nstatic CVAR_DEFINE_AUTO( host_clientloaded, \"0\", FCVAR_READ_ONLY, \"inidcates a loaded client.dll\" );\nCVAR_DEFINE_AUTO( host_limitlocal, \"0\", 0, \"apply cl_cmdrate and rate to loopback connection\" );\nCVAR_DEFINE( host_maxfps, \"fps_max\", \"72\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"host fps upper limit\" );\nCVAR_DEFINE_AUTO( fps_override, \"0\", FCVAR_FILTERABLE, \"unlock higher framerate values, not supported\" );\nstatic CVAR_DEFINE_AUTO( host_framerate, \"0\", FCVAR_FILTERABLE, \"locks frame timing to this value in seconds\" );\nstatic CVAR_DEFINE( host_sleeptime, \"sleeptime\", \"1\", FCVAR_ARCHIVE|FCVAR_FILTERABLE, \"milliseconds to sleep for each frame. higher values reduce fps accuracy\" );\nstatic CVAR_DEFINE_AUTO( host_sleeptime_debug, \"0\", 0, \"print sleeps between frames\" );\nCVAR_DEFINE_AUTO( host_allow_materials, \"0\", FCVAR_LATCH|FCVAR_ARCHIVE, \"allow texture replacements from materials/ folder\" );\nCVAR_DEFINE( con_gamemaps, \"con_mapfilter\", \"1\", FCVAR_ARCHIVE, \"when true show only maps in game folder\" );\n\ntypedef struct feature_message_s\n{\n\tuint32_t mask;\n\tconst char *msg;\n\tconst char *arg;\n} feature_message_t;\n\nstatic const feature_message_t bugcomp_features[] =\n{\n{ BUGCOMP_PENTITYOFENTINDEX_FLAG, \"pfnPEntityOfEntIndex bugfix revert\", \"peoei\" },\n{ BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG, \"GoldSrc Message Rewrite Facility\", \"gsmrf\" },\n{ BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE, \"spatialize sounds with zero attenuation\", \"sp_attn_none\" },\n{ BUGCOMP_GET_GAME_DIR_FULL_PATH, \"Return full path in GET_GAME_DIR()\", \"get_game_dir_full\" }\n};\n\nstatic const feature_message_t engine_features[] =\n{\n{ ENGINE_WRITE_LARGE_COORD, \"Big World Support\" },\n{ ENGINE_QUAKE_COMPATIBLE, \"Quake Compatibility\" },\n{ ENGINE_LOAD_DELUXEDATA, \"Deluxemap Support\" },\n{ ENGINE_PHYSICS_PUSHER_EXT, \"Improved MOVETYPE_PUSH\" },\n{ ENGINE_LARGE_LIGHTMAPS, \"Large Lightmaps\" },\n{ ENGINE_COMPENSATE_QUAKE_BUG, \"Stupid Quake Bug Compensation\" },\n{ ENGINE_IMPROVED_LINETRACE, \"Improved Trace Line\" },\n{ ENGINE_COMPUTE_STUDIO_LERP, \"Studio MOVETYPE_STEP Lerping\" },\n{ ENGINE_LINEAR_GAMMA_SPACE, \"Linear Gamma Space\" },\n{ ENGINE_STEP_POSHISTORY_LERP, \"MOVETYPE_STEP Position History Based Lerping\" },\n};\n\nstatic void Sys_MakeVersionString( char *out, size_t len )\n{\n\tQ_snprintf( out, len, XASH_ENGINE_NAME \" %i/\" XASH_VERSION \" (%s-%s build %i)\", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));\n}\n\nstatic void Sys_PrintUsage( const char *exename )\n{\n\tstring version_str;\n\tconst char *usage_str;\n\n\tSys_MakeVersionString( version_str, sizeof( version_str ));\n\n#if XASH_MESSAGEBOX != MSGBOX_STDERR\n\t#if XASH_WIN32\n\t\t#define XASH_EXE \"(xash).exe\"\n\t#else\n\t\t#define XASH_EXE \"(xash)\"\n\t#endif\n#else\n\t#define XASH_EXE \"%s\"\n#endif\n#define O( x, y ) \"  \"x\"  \"y\"\\n\"\n\n\tusage_str = S_USAGE XASH_EXE \" [options] [+command] [+command2 arg] ...\\n\"\n\n\"\\nCommon options:\\n\"\n\tO(\"-dev [level]       \", \"set log verbosity 0-2\")\n\tO(\"-log [file name]   \", \"write log to \\\"engine.log\\\" or [file name] if specified\")\n\tO(\"-logtime           \", \"enable writing timestamps to the log file\")\n\tO(\"-nowriteconfig     \", \"disable config save\")\n\tO(\"-noch              \", \"disable crashhandler\")\n#if XASH_WIN32 // !!!!\n\tO(\"-minidumps         \", \"enable writing minidumps when game is crashed\")\n#endif\n\tO(\"-rodir <path>      \", \"set read-only base directory\")\n\tO(\"-bugcomp [opts]    \", \"enable precise bug compatibility\")\n\tO(\"                   \", \"will break games that don't require it\")\n\tO(\"                   \", \"refer to engine documentation for more info\")\n\tO(\"-language <lang>   \", \"mount localization game directory\")\n\tO(\"-disablehelp       \", \"disable this message\")\n#if !XASH_DEDICATED\n\tO(\"-dedicated         \", \"run engine in dedicated mode\")\n#endif\n\n\"\\nNetworking options:\\n\"\n\tO(\"-noip              \", \"disable IPv4\")\n\tO(\"-ip <ip>           \", \"set IPv4 address\")\n\tO(\"-port <port>       \", \"set IPv4 port\")\n#if !XASH_DEDICATED\n\tO(\"-clientport <port> \", \"set IPv4 client port\")\n#endif\n\tO(\"-noip6             \", \"disable IPv6\")\n\tO(\"-ip6 <ip>          \", \"set IPv6 address\")\n\tO(\"-port6 <port>      \", \"set IPv6 port\")\n#if !XASH_DEDICATED\n\tO(\"-clientport6 <port>\", \"set IPv6 client port\")\n#endif\n\tO(\"-clockwindow <cw>  \", \"adjust clockwindow used to ignore client commands\")\n\tO(\"                   \", \"to prevent speed hacks\")\n\n\"\\nGame options:\\n\"\n\tO(\"-game <directory>  \", \"set game directory to start engine with\")\n\tO(\"-dll <path>        \", \"override server DLL path\")\n#if !XASH_DEDICATED\n\tO(\"-clientlib <path>  \", \"override client DLL path\")\n\tO(\"-menulib <path>    \", \"override menu DLL path\")\n\tO(\"-console           \", \"run engine with console enabled\")\n\tO(\"-toconsole         \", \"run engine witn console open\")\n\tO(\"-oldfont           \", \"enable unused Quake font in Half-Life\")\n\tO(\"-width <n>         \", \"set window width\")\n\tO(\"-height <n>        \", \"set window height\")\n\tO(\"-borderless        \", \"run engine in fullscreen borderless mode\")\n\tO(\"-fullscreen        \", \"run engine in fullscreen mode\")\n\tO(\"-windowed          \", \"run engine in windowed mode\")\n\tO(\"-ref <name>        \", \"use selected renderer dll\")\n\tO(\"-gldebug           \", \"enable OpenGL debug log\")\n#if XASH_WIN32\n\tO(\"-noavi             \", \"disable AVI support\")\n\tO(\"-nointro           \", \"disable intro video\")\n#endif\n\tO(\"-noenginejoy       \", \"disable engine builtin joystick support\")\n\tO(\"-noenginemouse     \", \"disable engine builtin mouse support\")\n\tO(\"-nosound           \", \"disable sound output\")\n\tO(\"-timedemo          \", \"run timedemo and exit\")\n#endif\n\n\"\\nPlatform-specific options:\\n\"\n#if !XASH_MOBILE_PLATFORM\n\tO(\"-daemonize         \", \"run engine as a daemon\")\n#endif\n#if XASH_SDL == 2\n\tO(\"-sdl_renderer <n>  \",\"use alternative SDL_Renderer for software\")\n#endif // XASH_SDL\n#if XASH_ANDROID && !XASH_SDL\n\tO(\"-nativeegl         \",\"use native egl implementation. Use if screen does not update or black\")\n#endif // XASH_ANDROID\n#if XASH_DOS\n\tO(\"-novesa            \",\"disable vesa\")\n#endif // XASH_DOS\n#if XASH_VIDEO == VIDEO_FBDEV\n\tO(\"-fbdev <path>      \",\"open selected framebuffer\")\n\tO(\"-ttygfx            \",\"set graphics mode in tty\")\n\tO(\"-doublebuffer      \",\"enable doublebuffering\")\n#endif // XASH_VIDEO == VIDEO_FBDEV\n#if XASH_SOUND == SOUND_ALSA\n\tO(\"-alsadev <dev>     \",\"open selected ALSA device\")\n#endif // XASH_SOUND == SOUND_ALSA\n\t;\n#undef O\n#undef XASH_EXE\n\n\t// HACKHACK: pretty output in dedicated\n#if XASH_MESSAGEBOX != MSGBOX_STDERR\n\tPlatform_MessageBox( version_str, usage_str, false );\n#else\n\tfprintf( stderr, \"%s\\n\", version_str );\n\tfprintf( stderr, usage_str, exename );\n#endif\n\n\tSys_Quit( NULL );\n}\n\nstatic void Sys_PrintBugcompUsage( const char *exename )\n{\n\tstring version_str;\n\tchar usage_str[4096];\n\tchar *p = usage_str;\n\tint i;\n\n\tSys_MakeVersionString( version_str, sizeof( version_str ));\n\n\tp += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), \"Known bugcomp flags are:\\n\" );\n\tfor( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ )\n\t\tp += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), \"   %s: %s\\n\", bugcomp_features[i].arg, bugcomp_features[i].msg );\n\tp += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), \"\\nIt is possible to combine multiple flags with '+' characters.\\nExample: -bugcomp flag1+flag2+flag3...\\n\" );\n\n\t// HACKHACK: pretty output in dedicated\n#if XASH_MESSAGEBOX != MSGBOX_STDERR\n\tPlatform_MessageBox( version_str, usage_str, false );\n#else\n\tfprintf( stderr, \"%s\\n\", version_str );\n\tfprintf( stderr, usage_str, exename );\n#endif\n\n\tSys_Quit( NULL );\n}\n\n/*\n================\nHost_PrintEngineFeatures\n================\n*/\nstatic void Host_PrintFeatures( uint32_t flags, const char *s, const feature_message_t *features, size_t size )\n{\n\tsize_t i;\n\n\tfor( i = 0; i < size; i++ )\n\t{\n\t\tif( FBitSet( flags, features[i].mask ))\n\t\t\tCon_Printf( \"^3%s:^7 %s is enabled\\n\", s, features[i].msg );\n\t}\n}\n\n/*\n==============\nHost_ValidateEngineFeatures\n\nvalidate features bits and set host.features\n==============\n*/\nvoid Host_ValidateEngineFeatures( uint32_t mask, uint32_t features )\n{\n\t// don't allow unsupported bits\n\tfeatures &= mask;\n\n\t// force bits for some games\n\tif( !Q_stricmp( GI->gamefolder, \"cstrike\" ) || !Q_stricmp( GI->gamefolder, \"czero\" ))\n\t\tSetBits( features, ENGINE_STEP_POSHISTORY_LERP );\n\n\t// print requested first\n\tHost_PrintFeatures( features, \"EXT\", engine_features, ARRAYSIZE( engine_features ));\n\n\t// now warn about incompatible bits\n\tif( FBitSet( features, ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP ) == ( ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP ))\n\t\tCon_Printf( S_WARN \"%s: incompatible ENGINE_STEP_POSHISTORY_LERP and ENGINE_COMPUTE_STUDIO_LERP are enabled!\\n\", __func__ );\n\n\t// finally set global variable\n\thost.features = features;\n}\n\n/*\n==============\nHost_IsQuakeCompatible\n\n==============\n*/\nqboolean Host_IsQuakeCompatible( void )\n{\n\t// feature set\n\tif( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\treturn true;\n\n#if !XASH_DEDICATED\n\t// quake demo playing\n\tif( cls.demoplayback == DEMO_QUAKE1 )\n\t\treturn true;\n#endif // XASH_DEDICATED\n\n\treturn false;\n}\n\n/*\n================\nHost_EndGame\n================\n*/\nvoid Host_EndGame( qboolean abort, const char *message, ... )\n{\n\tva_list\t\targptr;\n\tstatic char\tstring[MAX_SYSPATH];\n\n\tva_start( argptr, message );\n\tQ_vsnprintf( string, sizeof( string ), message, argptr );\n\tva_end( argptr );\n\n\tCon_Printf( \"Host_EndGame: %s\\n\", string );\n\n\tSV_Shutdown( \"\\n\" );\n#if !XASH_DEDICATED\n\tCL_Disconnect();\n\n\t// recreate world if needs\n\tCL_ClearEdicts ();\n#endif\n\n\t// release all models\n\tMod_FreeAll();\n\n\tif( abort ) Host_AbortCurrentFrame ();\n}\n\n/*\n==================\nHost_CalcSleep\n==================\n*/\nstatic int Host_CalcSleep( void )\n{\n\tif( Host_IsDedicated( ))\n\t{\n\t\t// let the dedicated server some sleep\n\t\treturn host_sleeptime.value;\n\t}\n\n\tswitch( host.status )\n\t{\n\tcase HOST_NOFOCUS:\n\t\tif( SV_Active() && CL_IsInGame())\n\t\t\treturn host_sleeptime.value;\n\t\t// fallthrough\n\tcase HOST_SLEEP:\n\t\treturn 20;\n\t}\n\n\treturn host_sleeptime.value;\n}\n\nstatic void Host_NewInstance( const char *name, const char *finalmsg )\n{\n\tif( !pChangeGame ) return;\n\n\thost.change_game = true;\n\n\tif( !Sys_NewInstance( name, finalmsg ))\n\t\tpChangeGame( name ); // call from hl.exe\n}\n\n/*\n=================\nHost_ChangeGame_f\n\nChange game modification\n=================\n*/\nstatic void Host_ChangeGame_f( void )\n{\n\tint\ti;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"game <directory>\\n\" );\n\t\treturn;\n\t}\n\n\t// validate gamedir\n\tfor( i = 0; i < FI->numgames; i++ )\n\t{\n\t\tif( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 )))\n\t\t\tbreak;\n\t}\n\n\tif( i == FI->numgames )\n\t{\n\t\tCon_Printf( \"%s not exist\\n\", Cmd_Argv( 1 ));\n\t}\n\telse if( !Q_stricmp( GI->gamefolder, Cmd_Argv( 1 )))\n\t{\n\t\tCon_Printf( \"%s already active\\n\", Cmd_Argv( 1 ));\n\t}\n\telse\n\t{\n\t\tchar finalmsg[MAX_VA_STRING];\n\n\t\tQ_snprintf( finalmsg, sizeof( finalmsg ), \"change game to '%s'\", FI->games[i]->title );\n\t\tHost_NewInstance( Cmd_Argv( 1 ), finalmsg );\n\t}\n}\n\n/*\n===============\nHost_Exec_f\n===============\n*/\nstatic void Host_Exec_f( void )\n{\n\tstring cfgpath;\n\tbyte *f;\n\tfs_offset_t len;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"exec <filename>\\n\" );\n\t\treturn;\n\t}\n\n\tQ_strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath ));\n\tCOM_DefaultExtension( cfgpath, \".cfg\", sizeof( cfgpath )); // append as default\n\n#ifndef XASH_DEDICATED\n\tif( !Cmd_CurrentCommandIsPrivileged() )\n\t{\n\t\tconst char *unprivilegedWhitelist[] =\n\t\t{\n\t\t\tNULL, \"mapdefault.cfg\", \"scout.cfg\", \"sniper.cfg\",\n\t\t\t\"soldier.cfg\", \"demoman.cfg\", \"medic.cfg\", \"hwguy.cfg\",\n\t\t\t\"pyro.cfg\", \"spy.cfg\", \"engineer.cfg\", \"civilian.cfg\"\n\t\t};\n\t\tint i;\n\t\tchar temp[MAX_VA_STRING];\n\t\tqboolean allow = false;\n\n\t\tQ_snprintf( temp, sizeof( temp ), \"%s.cfg\", clgame.mapname );\n\t\tunprivilegedWhitelist[0] = temp;\n\n\t\tfor( i = 0; i < ARRAYSIZE( unprivilegedWhitelist ); i++ )\n\t\t{\n\t\t\tif( !Q_strcmp( cfgpath, unprivilegedWhitelist[i] ))\n\t\t\t{\n\t\t\t\tallow = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !allow )\n\t\t{\n\t\t\tCon_Printf( \"exec %s: not privileged or in whitelist\\n\", cfgpath );\n\t\t\treturn;\n\t\t}\n\t}\n#endif // XASH_DEDICATED\n\n\t// don't execute game.cfg in singleplayer\n\tif( SV_GetMaxClients() == 1 && !Q_stricmp( \"game.cfg\", cfgpath ))\n\t\treturn;\n\n\tf = FS_LoadFile( cfgpath, &len, false );\n\tif( !f )\n\t{\n\t\tCon_Reportf( \"couldn't exec %s\\n\", Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\t// len is fs_offset_t, which can be larger than size_t\n\tif( len >= SIZE_MAX )\n\t{\n\t\tCon_Reportf( \"%s: %s is too long\\n\", __func__, Cmd_Argv( 1 ));\n\t\treturn;\n\t}\n\n\tif( !Q_stricmp( \"config.cfg\", cfgpath ))\n\t\thost.config_executed = true;\n\n\tif( !host.apply_game_config )\n\t\tCon_Printf( \"execing %s\\n\", Cmd_Argv( 1 ));\n\n\t// adds \\n at end of the file\n\t// FS_LoadFile always null terminates\n\tif( f[len - 1] != '\\n' )\n\t{\n\t\tCbuf_InsertTextLen( f, len, len + 1 );\n\t\tCbuf_InsertTextLen( \"\\n\", 1, 1 );\n\t}\n\telse Cbuf_InsertTextLen( f, len, len );\n\n\tMem_Free( f );\n}\n\n/*\n===============\nHost_MemStats_f\n===============\n*/\nstatic void Host_MemStats_f( void )\n{\n\tswitch( Cmd_Argc( ))\n\t{\n\tcase 1:\n\t\tMem_PrintList( 1<<30 );\n\t\tMem_PrintStats();\n\t\tbreak;\n\tcase 2:\n\t\tMem_PrintList( Q_atoi( Cmd_Argv( 1 )) * 1024 );\n\t\tMem_PrintStats();\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( S_USAGE \"memlist <all>\\n\" );\n\t\tbreak;\n\t}\n}\n\n/*\n=================\nHost_RegisterDecal\n=================\n*/\nstatic qboolean Host_RegisterDecal( const char *name, int *count )\n{\n\tchar\tshortname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( name ))\n\t\treturn 0;\n\n\tCOM_FileBase( name, shortname, sizeof( shortname ));\n\n\tfor( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( host.draw_decals[i], shortname ))\n\t\t\treturn true;\n\t}\n\n\tif( i == MAX_DECALS )\n\t{\n\t\tCon_DPrintf( S_ERROR \"MAX_DECALS limit exceeded (%d)\\n\", MAX_DECALS );\n\t\treturn false;\n\t}\n\n\t// register new decal\n\tQ_strncpy( host.draw_decals[i], shortname, sizeof( host.draw_decals[i] ));\n\t*count += 1;\n\n\treturn true;\n}\n\n/*\n=================\nHost_InitDecals\n=================\n*/\nstatic void Host_InitDecals( void )\n{\n\tint\ti, num_decals = 0;\n\tsearch_t\t*t;\n\n\tmemset( host.draw_decals, 0, sizeof( host.draw_decals ));\n\n\t// lookup all the decals in decals.wad (basedir, gamedir, falldir)\n\tt = FS_Search( \"decals.wad/*.*\", true, false );\n\n\tfor( i = 0; t && i < t->numfilenames; i++ )\n\t{\n\t\tif( !Host_RegisterDecal( t->filenames[i], &num_decals ))\n\t\t\tbreak;\n\t}\n\n\tif( t ) Mem_Free( t );\n\tCon_Reportf( \"%s: %i decals\\n\", __func__, num_decals );\n}\n\n/*\n===================\nHost_GetCommands\n\nAdd them exactly as if they had been typed at the console\n===================\n*/\nstatic void Host_GetCommands( void )\n{\n\tchar\t*cmd;\n\n\twhile( ( cmd = Platform_Input() ) )\n\t{\n\t\tCbuf_AddText( cmd );\n\t\tCbuf_Execute();\n\t}\n}\n\n/*\n===================\nHost_CalcFPS\n\ncompute actual FPS for various modes\n===================\n*/\nstatic double Host_CalcFPS( void )\n{\n\tdouble\tfps = 0.0;\n\n\tif( Host_IsDedicated( ))\n\t{\n\t\tfps = sys_ticrate.value;\n\t}\n#if !XASH_DEDICATED\n\telse if( CL_IsPlaybackDemo() || CL_IsRecordDemo( )) // NOTE: we should play demos with same fps as it was recorded\n\t{\n\t\tfps = CL_GetDemoFramerate();\n\t}\n\telse if( Host_IsLocalGame( ))\n\t{\n\t\tif( !gl_vsync.value )\n\t\t\tfps = host_maxfps.value;\n\t}\n\telse if( !SV_Active() && CL_Protocol() == PROTO_GOLDSRC && cls.state != ca_disconnected && cls.state < ca_validate )\n\t{\n\t\treturn 31.0;\n\t}\n\telse\n\t{\n\t\tif( !gl_vsync.value )\n\t\t{\n\t\t\tdouble max_fps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT;\n\n\t\t\tfps = host_maxfps.value;\n\t\t\tif( fps == 0.0 ) fps = max_fps;\n\t\t\tfps = bound( MIN_FPS, fps, max_fps );\n\t\t}\n\t}\n#endif\n\n\treturn fps;\n}\n\nstatic qboolean Host_Autosleep( double dt, double scale )\n{\n\tdouble targetframetime, fps;\n\tint sleep;\n\n\tfps = Host_CalcFPS();\n\n\tif( fps <= 0 )\n\t\treturn true;\n\n\t// limit fps to withing tolerable range\n\tfps = bound( MIN_FPS, fps, MAX_FPS_HARD );\n\n\tif( Host_IsDedicated( ))\n\t\ttargetframetime = ( 1.0 / ( fps + 1.0 ));\n\telse targetframetime = ( 1.0 / fps );\n\n\tsleep = Host_CalcSleep();\n\tif( sleep == 0 ) // no sleeps between frames, much simpler code\n\t{\n\t\tif( dt < targetframetime * scale )\n\t\t\treturn false;\n\t}\n\telse\n\t{\n\t\tstatic double timewindow; // allocate a time window for sleeps\n\t\tstatic int counter; // for debug\n\t\tstatic double realsleeptime;\n\t\tconst double sleeptime = sleep * 0.000001;\n\n\t\tif( dt < targetframetime * scale )\n\t\t{\n\t\t\t// if we have allocated time window, try to sleep\n\t\t\tif( timewindow > realsleeptime )\n\t\t\t{\n\t\t\t\t// Platform_Sleep isn't guaranteed to sleep an exact amount of microseconds\n\t\t\t\t// so we measure the real sleep time and use it to decrease the window\n\t\t\t\tdouble t1 = Sys_DoubleTime(), t2;\n\t\t\t\tPlatform_NanoSleep( sleep * 1000 ); // in usec!\n\t\t\t\tt2 = Sys_DoubleTime();\n\t\t\t\trealsleeptime = t2 - t1;\n\n\t\t\t\ttimewindow -= realsleeptime;\n\n\t\t\t\tif( host_sleeptime_debug.value )\n\t\t\t\t{\n\t\t\t\t\tcounter++;\n\n\t\t\t\t\tCon_NPrintf( counter, \"%d: %.4f %.4f\", counter, timewindow, realsleeptime );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// if we exhausted this time window, allocate a new one after new frame\n\t\tif( timewindow <= realsleeptime )\n\t\t{\n\t\t\tdouble targetsleeptime = targetframetime - host.pureframetime * 2;\n\n\t\t\tif( targetsleeptime > 0 )\n\t\t\t\ttimewindow = targetsleeptime;\n\t\t\telse timewindow = 0;\n\n\t\t\trealsleeptime = sleeptime; // reset in case CPU was too busy\n\n\t\t\tif( host_sleeptime_debug.value )\n\t\t\t{\n\t\t\t\tcounter = 0;\n\n\t\t\t\tCon_NPrintf( 0, \"tgt = %.4f, pft = %.4f, wnd = %.4f\", targetframetime, host.pureframetime, timewindow );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n===================\nHost_FilterTime\n\nReturns false if the time is too short to run a frame\n===================\n*/\nstatic qboolean Host_FilterTime( double time )\n{\n\tstatic double\toldtime;\n\tdouble dt;\n\tdouble scale = sys_timescale.value;\n\n\thost.realtime += time * scale;\n\tdt = host.realtime - oldtime;\n\n\t// clamp the fps in multiplayer games\n\tif( !Host_Autosleep( dt, scale ))\n\t\treturn false;\n\n\thost.frametime = host.realtime - oldtime;\n\thost.realframetime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );\n\toldtime = host.realtime;\n\n\t// NOTE: allow only in singleplayer while demos are not active\n\tif( host_framerate.value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( ))\n\t\thost.frametime = bound( MIN_FRAMETIME, host_framerate.value * scale, MAX_FRAMETIME );\n\telse host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME );\n\n\treturn true;\n}\n\n/*\n=================\nHost_Frame\n=================\n*/\nvoid Host_Frame( double time )\n{\n\tdouble t1;\n\n\t// decide the simulation time\n\tif( !Host_FilterTime( time ))\n\t\treturn;\n\n\tt1 = Sys_DoubleTime();\n\n\tif( host.framecount == 0 )\n\t\tCon_DPrintf( \"Time to first frame: %.3f seconds\\n\", t1 - host.starttime );\n\n\tHost_InputFrame ();  // input frame\n\tHost_ClientBegin (); // begin client\n\tHost_GetCommands (); // dedicated in\n\tHost_ServerFrame (); // server frame\n\tHost_ClientFrame (); // client frame\n\tHTTP_Run();\t\t\t // both server and client\n\n\thost.framecount++;\n\thost.pureframetime = Sys_DoubleTime() - t1;\n}\n\n/*\n=================\nHost_Error\n=================\n*/\nvoid GAME_EXPORT Host_Error( const char *error, ... )\n{\n\tstatic char\thosterror1[MAX_SYSPATH];\n\tstatic char\thosterror2[MAX_SYSPATH];\n\tstatic qboolean\trecursive = false;\n\tva_list\t\targptr;\n\n\tva_start( argptr, error );\n\tQ_vsnprintf( hosterror1, sizeof( hosterror1 ), error, argptr );\n\tva_end( argptr );\n\n\tCL_WriteMessageHistory (); // before Q_error call\n\n\tif( host.framecount < 3 )\n\t{\n\t\tSys_Error( \"%sInit: %s\", __func__, hosterror1 );\n\t}\n\telse if( host.framecount == host.errorframe )\n\t{\n\t\tSys_Error( \"%sMulti: %s\", __func__, hosterror2 );\n\t}\n\telse\n\t{\n\t\tCon_Printf( \"%s: %s\", __func__, hosterror1 );\n\t\tif( host.allow_console )\n\t\t{\n\t\t\tUI_SetActiveMenu( false );\n\t\t\tKey_SetKeyDest( key_console );\n\t\t}\n\t\telse Platform_MessageBox( \"Host Error\", hosterror1, true );\n\t}\n\n\t// host is shutting down. don't invoke infinite loop\n\tif( host.status == HOST_SHUTDOWN ) return;\n\n\tif( recursive )\n\t{\n\t\tCon_Printf( \"%sRecursive: %s\", __func__, hosterror2 );\n\t\tSys_Error( \"%s\", hosterror1 );\n\t}\n\n\trecursive = true;\n\tQ_strncpy( hosterror2, hosterror1, sizeof( hosterror2 ));\n\thost.errorframe = host.framecount; // to avoid multply calls per frame\n\n\t// clearing cmd buffer to prevent execute any commands\n\tCOM_InitHostState();\n\tCbuf_Clear();\n\n\tSV_Shutdown( \"Server was killed due to an error\\n\" );\n\tCL_Drop(); // drop clients\n\n\t// recreate world if needs\n\tCL_ClearEdicts ();\n\n\t// release all models\n\tMod_FreeAll();\n\n\trecursive = false;\n\tHost_AbortCurrentFrame();\n}\n\nstatic void Host_Error_f( void )\n{\n\tconst char *error = Cmd_Argv( 1 );\n\n\tif( !*error ) error = \"Invoked host error\";\n\tHost_Error( \"%s\\n\", error );\n}\n\nstatic void Sys_Error_f( void )\n{\n\tconst char *error = Cmd_Argv( 1 );\n\n\tif( !*error ) error = \"Invoked sys error\";\n\tSys_Error( \"%s\\n\", error );\n}\n\n/*\n=================\nHost_Crash_f\n=================\n*/\nstatic void Host_Crash_f( void )\n{\n\t*(volatile int *)0 = 0xffffffff;\n}\n\n/*\n=================\nHost_Userconfigd_f\n=================\n*/\nstatic void Host_Userconfigd_f( void )\n{\n\tsearch_t *t;\n\tint i;\n\n\tt = FS_Search( \"userconfig.d/*.cfg\", true, false );\n\tif( !t ) return;\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t{\n\t\tCbuf_AddTextf( \"exec %s\\n\", t->filenames[i] );\n\t}\n\n\tMem_Free( t );\n}\n\n#if XASH_ENGINE_TESTS\nstatic void Host_RunTests( int stage )\n{\n\tswitch( stage )\n\t{\n\tcase 0: // early engine load\n\t\tmemset( &tests_stats, 0, sizeof( tests_stats ));\n\t\tTEST_LIST_0;\n#if !XASH_DEDICATED\n\t\tTEST_LIST_0_CLIENT;\n#endif /* XASH_DEDICATED */\n\t\tbreak;\n\tcase 1: // after FS load\n\t\tTEST_LIST_1;\n#if !XASH_DEDICATED\n\t\tTEST_LIST_1_CLIENT;\n#endif\n\t\tMsg( \"Done! %d passed, %d failed\\n\", tests_stats.passed, tests_stats.failed );\n\t\terror_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;\n\t\tSys_Quit( NULL );\n\t}\n}\n#endif\n\nstatic int Host_CheckBugcomp_splitstr_handler( char *prev, char *next, void *userdata )\n{\n\tsize_t i;\n\tuint32_t *flags = userdata;\n\n\t*next = '\\0';\n\n\tif( !COM_CheckStringEmpty( prev ))\n\t\treturn 0;\n\n\tfor( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ )\n\t{\n\t\tif( !Q_stricmp( bugcomp_features[i].arg, prev ))\n\t\t{\n\t\t\tSetBits( *flags, bugcomp_features[i].mask );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( i == ARRAYSIZE( bugcomp_features ))\n\t{\n\t\tCon_Printf( S_ERROR \"Unknown bugcomp flag %s\\n\", prev );\n\t\tCon_Printf( \"Valid flags are:\\n\" );\n\t\tfor( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ )\n\t\t\tCon_Printf( \"\\t%s: %s\\n\", bugcomp_features[i].arg, bugcomp_features[i].msg );\n\t}\n\n\treturn 0;\n}\n\nstatic uint32_t Host_CheckBugcomp( void )\n{\n\tuint32_t flags = 0;\n\tstring args;\n\n\tif( !Sys_CheckParm( \"-bugcomp\" ))\n\t\treturn 0;\n\n\tif( Sys_GetParmFromCmdLine( \"-bugcomp\", args ) && isalpha( args[0] ))\n\t{\n\t\tQ_splitstr( args, '+', &flags, Host_CheckBugcomp_splitstr_handler );\n\t}\n\telse\n\t{\n\t\t// no argument specified -bugcomp just enables everything\n\t\tflags = -1;\n\t}\n\n\tHost_PrintFeatures( flags, \"BUGCOMP\", bugcomp_features, ARRAYSIZE( bugcomp_features ));\n\n\treturn flags;\n}\n\nstatic void Host_DetermineExecutableName( char *out, size_t size )\n{\n#if XASH_WIN32\n\tchar temp[MAX_SYSPATH];\n\n\tif( GetModuleFileName( NULL, temp, sizeof( temp )))\n\t\tCOM_FileBase( temp, out, size );\n#else\n\tif( host.argc > 0 )\n\t\tCOM_FileBase( host.argv[0], out, size );\n\telse\n\t\tQ_strncpy( out, \"xash\", size );\n#endif\n}\n\n/*\n=================\nHost_InitCommon\n=================\n*/\nstatic void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame, char *exename, size_t exename_size )\n{\n\tconst char *basedir = progname[0] == '#' ? progname + 1 : progname;\n\tchar dev_level[4], ticrate[16];\n\tint developer = DEFAULT_DEV;\n\n\t// some commands may turn engine into infinite loop,\n\t// e.g. xash.exe +game xash -game xash\n\t// so we clear all cmd_args, but leave dbg states as well\n\tSys_ParseCommandLine( argc, argv );\n\tHost_DetermineExecutableName( exename, exename_size );\n\n\tif( !Sys_CheckParm( \"-disablehelp\" ))\n\t{\n\t\tstring arg;\n\n\t\tif( Sys_CheckParm( \"-help\" ) || Sys_CheckParm( \"-h\" ) || Sys_CheckParm( \"--help\" ))\n\t\t\tSys_PrintUsage( exename );\n\n\t\tif( Sys_GetParmFromCmdLine( \"-bugcomp\", arg ) && !Q_stricmp( arg, \"help\" ))\n\t\t\tSys_PrintBugcompUsage( exename );\n\t}\n\n\tif( !Sys_CheckParm( \"-noch\" ))\n\t\tSys_SetupCrashHandler( argv[0] );\n\n#if XASH_DLL_LOADER\n\thost.enabledll = !Sys_CheckParm( \"-nodll\" );\n#endif\n\n\thost.change_game = bChangeGame || Sys_CheckParm( \"-changegame\" );\n\thost.config_executed = false;\n\thost.status = HOST_INIT; // initialzation started\n\thost.type = HOST_DEDICATED; // predict state\n#ifndef XASH_DEDICATED\n\tif( !Sys_CheckParm( \"-dedicated\" ))\n\t\thost.type = HOST_NORMAL;\n#endif\n\n\tMemory_Init(); // init memory subsystem\n\n\thost.mempool = Mem_AllocPool( \"Zone Engine\" );\n\n\thost.allow_console = DEFAULT_ALLOWCONSOLE || DEFAULT_DEV > 0;\n\n\tif( Sys_CheckParm( \"-dev\" ))\n\t{\n\t\thost.allow_console = true;\n\t\tdeveloper = DEV_NORMAL;\n\n\t\tif( Sys_GetParmFromCmdLine( \"-dev\", dev_level ))\n\t\t{\n\t\t\tif( Q_isdigit( dev_level ))\n\t\t\t\tdeveloper = bound( DEV_NONE, abs( Q_atoi( dev_level )), DEV_EXTENDED );\n\t\t}\n\t}\n\n#if XASH_ENGINE_TESTS\n\tif( Sys_CheckParm( \"-runtests\" ))\n\t{\n\t\thost.allow_console = true;\n\t\tdeveloper = DEV_EXTENDED;\n\t}\n#endif\n\n\t// always enable console for Quake and dedicated\n\tif( !host.allow_console && ( Host_IsDedicated() || Sys_CheckParm( \"-console\" ) || !Q_strnicmp( exename, \"quake\", 5 )))\n\t\thost.allow_console = true;\n\n\t// member console allowing\n\thost.allow_console_init = host.allow_console;\n\n\t// timeBeginPeriod( 1 ); // a1ba: Do we need this?\n\n\t// NOTE: this message couldn't be passed into game console but it doesn't matter\n//\tCon_Reportf( \"Sys_LoadLibrary: Loading xash.dll - ok\\n\" );\n\n\t// get default screen res\n\tVID_InitDefaultResolution();\n\n\t// init host state machine\n\tCOM_InitHostState();\n\n\t// init hashed commands\n\tBaseCmd_Init();\n\n\t// startup cmds and cvars subsystem\n\tCmd_Init();\n\tCvar_Init();\n\n\t// share developer level across all dlls\n\tQ_snprintf( dev_level, sizeof( dev_level ), \"%i\", developer );\n\tCvar_DirectSet( &host_developer, dev_level );\n\tCvar_RegisterVariable( &sys_ticrate );\n\n\tif( Sys_GetParmFromCmdLine( \"-sys_ticrate\", ticrate ))\n\t{\n\t\tdouble fps = bound( MIN_FPS, atof( ticrate ), MAX_FPS_HARD );\n\t\tCvar_SetValue( \"sys_ticrate\", fps );\n\t}\n\n\tCon_Init(); // early console running to catch all the messages\n\n#if XASH_ENGINE_TESTS\n\tif( Sys_CheckParm( \"-runtests\" ))\n\t\tHost_RunTests( 0 );\n#endif\n\n#if XASH_DEDICATED\n\tPlatform_SetupSigtermHandling();\n#endif\n\tPlatform_Init( Host_IsDedicated( ) || developer >= DEV_EXTENDED, basedir );\n\tFS_Init( basedir );\n\n\tSys_InitLog();\n\n\t// print current developer level to simplify processing users feedback\n\tif( developer > 0 )\n\t{\n\t\tint i;\n\n\t\tCon_Printf( \"Program args: \" S_YELLOW );\n\t\tfor( i = 0; i < host.argc; i++ )\n\t\t\tCon_Printf( \"%s \", host.argv[i] );\n\t\tCon_Printf( S_DEFAULT \"\\n\" );\n\n\t\tCon_Printf( \"Developer level: \" S_YELLOW \"%i\" S_DEFAULT \"\\n\", developer );\n\t}\n\n\thost.bugcomp = Host_CheckBugcomp();\n\n\tCmd_AddCommand( \"exec\", Host_Exec_f, \"execute a script file\" );\n\tCmd_AddCommand( \"memlist\", Host_MemStats_f, \"prints memory pool information\" );\n\tCmd_AddRestrictedCommand( \"userconfigd\", Host_Userconfigd_f, \"execute all scripts from userconfig.d\" );\n\n#if !XASH_DEDICATED\n\tCmd_AddRestrictedCommand( \"host_writeconfig\", Host_WriteConfig, \"save current configuration\" );\n#endif\n\n\tImage_Init();\n\tSound_Init();\n\n#if XASH_ENGINE_TESTS\n\tif( Sys_CheckParm( \"-runtests\" ))\n\t\tHost_RunTests( 1 );\n#endif\n\n\tFS_LoadGameInfo();\n\tCvar_PostFSInit();\n\n\tImage_CheckPaletteQ1 ();\n\n\t// NOTE: only once resource without which engine can't continue work\n\tif( !FS_FileExists( \"gfx/conchars\", false ))\n\t\tSys_Error( \"%s: couldn't load gfx.wad\\n\", __func__ );\n\n\tHost_InitDecals ();\t// reload decals\n\n\tHPAK_Init();\n\n\tIN_Init();\n\tKey_Init();\n}\n\nstatic void Host_FreeCommon( void )\n{\n\tImage_Shutdown();\n\tSound_Shutdown();\n\tNetchan_Shutdown();\n\tHPAK_FlushHostQueue();\n\tFS_Shutdown();\n}\n\nstatic void Sys_Quit_f( void )\n{\n\tSys_Quit( \"command\" );\n}\n\nstatic void Host_MainLoop( void *userdata )\n{\n\tdouble *poldtime = (double *)userdata;\n\tdouble newtime = Sys_DoubleTime();\n\tCOM_Frame( newtime - *poldtime );\n\t*poldtime = newtime;\n}\n\n/*\n=================\nHost_Main\n=================\n*/\nint EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func )\n{\n\tstatic double oldtime;\n\tstring demoname, exename;\n\n\thost.starttime = Sys_DoubleTime();\n\n\tpChangeGame = func;\t// may be NULL\n\n\tHost_InitCommon( argc, argv, progname, bChangeGame, exename, sizeof( exename ));\n\n\t// init commands and vars\n\tif( host_developer.value >= DEV_EXTENDED )\n\t{\n\t\tCmd_AddRestrictedCommand ( \"sys_error\", Sys_Error_f, \"just throw a fatal error to test shutdown procedures\");\n\t\tCmd_AddRestrictedCommand ( \"host_error\", Host_Error_f, \"just throw a host error to test shutdown procedures\");\n\t\tCmd_AddRestrictedCommand ( \"crash\", Host_Crash_f, \"a way to force a bus error for development reasons\");\n\t}\n\n\tCvar_RegisterVariable( &host_allow_materials );\n\tCvar_RegisterVariable( &host_serverstate );\n\tCvar_RegisterVariable( &host_maxfps );\n\tCvar_RegisterVariable( &fps_override );\n\tCvar_RegisterVariable( &host_framerate );\n\tCvar_RegisterVariable( &host_sleeptime );\n\tCvar_RegisterVariable( &host_sleeptime_debug );\n\tCvar_RegisterVariable( &host_gameloaded );\n\tCvar_RegisterVariable( &host_clientloaded );\n\tCvar_RegisterVariable( &host_limitlocal );\n\tCvar_RegisterVariable( &con_gamemaps );\n\tCvar_RegisterVariable( &sys_timescale );\n\n\tCvar_Getf( \"buildnum\", FCVAR_READ_ONLY, \"returns a current build number\", \"%i\", Q_buildnum_compat());\n\tCvar_Getf( \"ver\", FCVAR_READ_ONLY, \"shows an engine version\", \"%i/%s (hw build %i)\", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat());\n\tCvar_Getf( \"host_ver\", FCVAR_READ_ONLY, \"detailed info about this build\", \"%i \" XASH_VERSION \" %s %s %s\", Q_buildnum(), Q_buildos(), Q_buildarch(), g_buildcommit);\n\tCvar_Getf( \"host_lowmemorymode\", FCVAR_READ_ONLY, \"indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)\", \"%i\", XASH_LOW_MEMORY );\n\n\tCvar_Get( \"host_hl25_extended_structs\",\n#if SUPPORT_HL25_EXTENDED_STRUCTS\n\t\t\"1\",\n#else\n\t\t\"0\",\n#endif\n\t\tFCVAR_READ_ONLY, \"indicates if engine was compiled with extended msurface_t struct\" );\n\n\tMod_Init();\n\tNET_Init();\n\tNET_InitMasters();\n\tNetchan_Init();\n\n\t// allow to change game from the console\n\tif( pChangeGame != NULL )\n\t{\n\t\tCmd_AddRestrictedCommand( \"game\", Host_ChangeGame_f, \"change game\" );\n\t\tCvar_Get( \"host_allow_changegame\", \"1\", FCVAR_READ_ONLY, \"allows to change games\" );\n\t}\n\telse\n\t{\n\t\tCvar_Get( \"host_allow_changegame\", \"0\", FCVAR_READ_ONLY, \"allows to change games\" );\n\t}\n\n\tSV_Init();\n\tCL_Init();\n\n\tHTTP_Init();\n\tSoundList_Init();\n\n\tif( Host_IsDedicated( ))\n\t{\n#ifdef _WIN32\n\t\tWcon_InitConsoleCommands ();\n#endif\n\n\t\t// disable texture replacements for dedicated\n\t\tCvar_FullSet( \"host_allow_materials\", \"0\", FCVAR_READ_ONLY );\n\n\t\tCmd_AddRestrictedCommand( \"quit\", Sys_Quit_f, \"quit the game\" );\n\t\tCmd_AddRestrictedCommand( \"exit\", Sys_Quit_f, \"quit the game\" );\n\t}\n\telse Cmd_AddRestrictedCommand( \"minimize\", Platform_Minimize_f, \"minimize main window to tray\" );\n\n\thost.errorframe = 0;\n\n\tif( progname[0] == '#' )\n\t\tprogname++;\n\n\t// post initializations\n\tswitch( host.type )\n\t{\n\tcase HOST_NORMAL:\n#ifdef _WIN32\n\t\tWcon_ShowConsole( false ); // hide console\n#endif\n\t\t// execute startup config and cmdline\n\t\tif( FS_FileExists( va( \"%s.rc\", progname ), false )) // e.g. valve.rc\n\t\t\tCbuf_AddTextf( \"exec %s.rc\", progname );\n\t\telse if( FS_FileExists( va( \"%s.rc\", exename ), false )) // e.g. quake.rc\n\t\t\tCbuf_AddTextf( \"exec %s.rc\", exename );\n\t\telse if( FS_FileExists( va( \"%s.rc\", GI->gamefolder ), false )) // e.g. game.rc (ran from default launcher)\n\t\t\tCbuf_AddTextf( \"exec %s.rc\", GI->gamefolder );\n\t\tCbuf_Execute();\n\n\t\tif( !host.config_executed )\n\t\t{\n\t\t\tCbuf_AddText( \"exec config.cfg\\n\" );\n\t\t\tCbuf_Execute();\n\t\t}\n\t\t// exec all files from userconfig.d\n\t\tHost_Userconfigd_f();\n\t\tbreak;\n\tcase HOST_DEDICATED:\n\t\t// allways parse commandline in dedicated-mode\n\t\thost.stuffcmds_pending = true;\n\t\tbreak;\n\t}\n\n\thost.change_game = false;\t// done\n\tCmd_RemoveCommand( \"setgl\" );\n\tCbuf_ExecStuffCmds();\t// execute stuffcmds (commandline)\n\tSCR_CheckStartupVids();\t// must be last\n\n\tif( Sys_GetParmFromCmdLine( \"-timedemo\", demoname ))\n\t\tCbuf_AddTextf( \"timedemo %s\\n\", demoname );\n\n\toldtime = Sys_DoubleTime() - 0.1;\n\n\tif( Host_IsDedicated( ))\n\t{\n\t\t// in dedicated server input system can't set HOST_FRAME status\n\t\t// so set it here as we're finished initializing\n\t\thost.status = HOST_FRAME;\n\n\t\tif( GameState->nextstate == STATE_RUNFRAME )\n#if XASH_WIN32 // FIXME: implement autocomplete on *nix\n\t\t\tCon_Printf( \"Type 'map <mapname>' to start game... (TAB-autocomplete is working too)\\n\" );\n#else // !XASH_WIN32\n\t\t\tCon_Printf( \"Type 'map <mapname>' to start game...\\n\" );\n#endif // !XASH_WIN32\n\n\t\t// execute server.cfg after commandline\n\t\t// so we have a chance to set servercfgfile\n\t\tCbuf_AddTextf( \"exec %s\\n\", Cvar_VariableString( \"servercfgfile\" ));\n\t\tCbuf_Execute();\n\t}\n\n\t// check after all configs were executed\n\tHPAK_CheckIntegrity( hpk_custom_file.string );\n\n#if XASH_ANDROID\n\tif( setjmp( return_from_main_buf ))\n\t\treturn error_on_exit;\n#endif // XASH_ANDROID\n\n#if !XASH_EMSCRIPTEN\n\t// main window message loop\n\twhile( host.status != HOST_CRASHED )\n\t\tHost_MainLoop( &oldtime );\n#else // XASH_EMSCRIPTEN\n\temscripten_set_main_loop_arg( Host_MainLoop, &oldtime, 0, false );\n#endif // XASH_EMSCRIPTEN\n\n\treturn 0;\n}\n\nvoid EXPORT Host_Shutdown( void );\nvoid EXPORT Host_Shutdown( void )\n{\n\tHost_ShutdownWithReason( \"launcher shutdown\" );\n}\n\n/*\n=================\nHost_Shutdown\n=================\n*/\nvoid Host_ShutdownWithReason( const char *reason )\n{\n\tqboolean error = host.status == HOST_ERR_FATAL;\n\n\tif( host.shutdown_issued )\n\t\treturn;\n\n\thost.shutdown_issued = true;\n\n\tif( reason != NULL )\n\t\tCon_Printf( S_NOTE \"Issuing host shutdown due to reason \\\"%s\\\"\\n\", reason );\n\n\tif( host.status != HOST_ERR_FATAL )\n\t\thost.status = HOST_SHUTDOWN; // prepare host to normal shutdown\n\n#if !XASH_DEDICATED\n\tif( host.type == HOST_NORMAL && !error )\n\t\tHost_WriteConfig();\n#endif\n\n\tSV_Shutdown( \"Server shutdown\\n\" );\n\tSV_UnloadProgs();\n\tSV_ShutdownFilter();\n\tCL_Shutdown();\n\n\tSoundList_Shutdown();\n\tMod_Shutdown();\n\tNET_Shutdown();\n\tHTTP_Shutdown();\n\tHost_FreeCommon();\n\tPlatform_Shutdown();\n\n\tBaseCmd_Shutdown();\n\tCmd_Shutdown();\n\tCvar_Shutdown();\n\n\t// must be last, console uses this\n\tMem_FreePool( &host.mempool );\n\n\t// restore filter\n\tSys_RestoreCrashHandler();\n\tSys_CloseLog( reason );\n}\n"
  },
  {
    "path": "engine/common/host_state.c",
    "content": "/*\nhost_cmd.c - dedicated and normal host\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"platform/platform.h\"\n\nstatic jmp_buf g_abortframe;\n\nvoid COM_InitHostState( void )\n{\n\tmemset( GameState, 0, sizeof( game_status_t ));\n}\n\nstatic void Host_SetState( host_state_t newState, qboolean clearNext )\n{\n\tif( clearNext )\n\t\tGameState->nextstate = newState;\n\tGameState->curstate = newState;\n\n\tif( clearNext && newState == STATE_RUNFRAME )\n\t{\n\t\t// states finished here\n\t\tGameState->backgroundMap = false;\n\t\tGameState->loadGame = false;\n\t\tGameState->newGame = false;\n\t}\n}\n\nstatic void Host_SetNextState( host_state_t nextState )\n{\n\tASSERT( GameState->curstate == STATE_RUNFRAME );\n\tGameState->nextstate = nextState;\n}\n\nvoid COM_NewGame( char const *pMapName )\n{\n\tif( GameState->nextstate != STATE_RUNFRAME )\n\t\treturn;\n\n\tif( UI_CreditsActive( ))\n\t\treturn;\n\n\tQ_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName ));\n\tHost_SetNextState( STATE_LOAD_LEVEL );\n\n\tGameState->backgroundMap = false;\n\tGameState->landmarkName[0] = 0;\n\tGameState->loadGame = false;\n\tGameState->newGame = true;\n\n\tif( !SV_Active( ))\n\t\tCL_Disconnect( ); // disconnect from current online game\n\n\tSV_ShutdownGame(); // exit from current game\n}\n\nvoid COM_LoadLevel( char const *pMapName, qboolean background )\n{\n\tif( GameState->nextstate != STATE_RUNFRAME )\n\t\treturn;\n\n\tif( UI_CreditsActive( ))\n\t\treturn;\n\n\tQ_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName ));\n\tHost_SetNextState( STATE_LOAD_LEVEL );\n\n\tGameState->backgroundMap = background;\n\tGameState->landmarkName[0] = 0;\n\tGameState->loadGame = false;\n\tGameState->newGame = false;\n\n\tif( !SV_Active( ))\n\t\tCL_Disconnect( ); // disconnect from current online game\n\n\tSV_ShutdownGame(); // exit from current game\n}\n\nvoid COM_LoadGame( char const *pMapName )\n{\n\tif( GameState->nextstate != STATE_RUNFRAME )\n\t\treturn;\n\n\tif( UI_CreditsActive( ))\n\t\treturn;\n\n\tQ_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName ));\n\tHost_SetNextState( STATE_LOAD_GAME );\n\tGameState->backgroundMap = false;\n\tGameState->newGame = false;\n\tGameState->loadGame = true;\n\n\tif( !SV_Active( ))\n\t\tCL_Disconnect( ); // disconnect from current online game\n}\n\nvoid COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background )\n{\n\tif( GameState->nextstate != STATE_RUNFRAME )\n\t\treturn;\n\n\tif( UI_CreditsActive( ))\n\t\treturn;\n\n\tQ_strncpy( GameState->levelName, pNewLevel, sizeof( GameState->levelName ));\n\tGameState->backgroundMap = background;\n\n\tif( COM_CheckString( pLandmarkName ))\n\t{\n\t\tQ_strncpy( GameState->landmarkName, pLandmarkName, sizeof( GameState->landmarkName ));\n\t\tGameState->loadGame = true;\n\t}\n\telse\n\t{\n\t\tGameState->landmarkName[0] = 0;\n\t\tGameState->loadGame = false;\n\t}\n\n\tHost_SetNextState( STATE_CHANGELEVEL );\n\tGameState->newGame = false;\n}\n\nstatic void Host_ShutdownGame( void )\n{\n\tSV_ShutdownGame();\n\n\tswitch( GameState->nextstate )\n\t{\n\tcase STATE_LOAD_GAME:\n\tcase STATE_LOAD_LEVEL:\n\t\tHost_SetState( GameState->nextstate, true );\n\t\tbreak;\n\tdefault:\n\t\tHost_SetState( STATE_RUNFRAME, true );\n\t\tbreak;\n\t}\n}\n\nstatic void Host_RunFrame( double time )\n{\n\t// at this time, we don't need to get events from OS on dedicated\n#if !XASH_DEDICATED\n\tPlatform_RunEvents();\n#endif // XASH_DEDICATED\n\n\t// engine main frame\n\tHost_Frame( time );\n\n\tswitch( GameState->nextstate )\n\t{\n\tcase STATE_RUNFRAME:\n\t\tbreak;\n\tcase STATE_LOAD_GAME:\n\tcase STATE_LOAD_LEVEL:\n\t\tSCR_BeginLoadingPlaque( GameState->backgroundMap );\n\t\t// intentionally fallthrough\n\tcase STATE_GAME_SHUTDOWN:\n\t\tHost_SetState( STATE_GAME_SHUTDOWN, false );\n\t\tbreak;\n\tcase STATE_CHANGELEVEL:\n\t\tSCR_BeginLoadingPlaque( GameState->backgroundMap );\n\t\tHost_SetState( GameState->nextstate, true );\n\t\tbreak;\n\tdefault:\n\t\tHost_SetState( STATE_RUNFRAME, true );\n\t\tbreak;\n\t}\n}\n\n/*\n================\nHost_AbortCurrentFrame\n\naborts the current host frame and goes on with the next one\n================\n*/\nvoid Host_AbortCurrentFrame( void )\n{\n\tlongjmp( g_abortframe, 1 );\n}\n\nvoid COM_Frame( double time )\n{\n\tint\tloopCount = 0;\n\n\tif( setjmp( g_abortframe ))\n\t\treturn;\n\n\twhile( 1 )\n\t{\n\t\tint\toldState = GameState->curstate;\n\n\t\t// execute the current state (and transition to the next state if not in STATE_RUNFRAME)\n\t\tswitch( GameState->curstate )\n\t\t{\n\t\tcase STATE_LOAD_LEVEL:\n\t\t\tSV_ExecLoadLevel();\n\t\t\tHost_SetState( STATE_RUNFRAME, true );\n\t\t\tbreak;\n\t\tcase STATE_LOAD_GAME:\n\t\t\tSV_ExecLoadGame();\n\t\t\tHost_SetState( STATE_RUNFRAME, true );\n\t\t\tbreak;\n\t\tcase STATE_CHANGELEVEL:\n\t\t\tSV_ExecChangeLevel();\n\t\t\tHost_SetState( STATE_RUNFRAME, true );\n\t\t\tbreak;\n\t\tcase STATE_RUNFRAME:\n\t\t\tHost_RunFrame( time );\n\t\t\tbreak;\n\t\tcase STATE_GAME_SHUTDOWN:\n\t\t\tHost_ShutdownGame();\n\t\t\tbreak;\n\t\t}\n\n\t\tif( oldState == STATE_RUNFRAME )\n\t\t\tbreak;\n\n\t\tif(( GameState->curstate == oldState ) || ( ++loopCount > 8 ))\n\t\t\tSys_Error( \"state infinity loop!\\n\" );\n\t}\n}\n"
  },
  {
    "path": "engine/common/hpak.c",
    "content": "/*\nhpak.c - custom user package to send other clients\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"hpak.h\"\n\n#define HPAK_MAX_ENTRIES\t0x8000\n\ntypedef struct hash_pack_queue_s\n{\n\tchar\t\t\t*name;\n\tresource_t\t\tresource;\n\tsize_t\t\t\tsize;\n\tvoid\t\t\t*data;\n\tstruct hash_pack_queue_s\t*next;\n} hash_pack_queue_t;\n\nstatic CVAR_DEFINE( hpk_maxsize, \"hpk_max_size\", \"64\", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, \"set limit by size for all HPK-files in megabytes ( 0 - unlimited )\" );\nCVAR_DEFINE_AUTO( hpk_custom_file, \"custom.hpk\", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, \"set custom path for players customizations cache file\" );\nstatic hash_pack_queue_t\t*gp_hpak_queue = NULL;\nstatic hpak_header_t\thash_pack_header;\nstatic hpak_info_t\thash_pack_info;\n\nstatic void HPAK_MaxSize_f( void )\n{\n\tCon_Printf( S_ERROR \"hpk_maxsize is deprecated, use hpk_max_size\\n\" );\n}\n\nconst char *COM_ResourceTypeFromIndex( int type )\n{\n\tswitch( type )\n\t{\n\tcase t_sound: return \"decal\";\n\tcase t_skin: return \"skin\";\n\tcase t_model: return \"model\";\n\tcase t_decal: return \"decal\";\n\tcase t_generic: return \"generic\";\n\tcase t_eventscript: return \"event\";\n\tcase t_world: return \"map\";\n\t}\n\treturn \"?\";\n}\n\nstatic inline void HPAK_ResourceToCompat( dresource_t *dest, resource_t *src )\n{\n\tmemcpy( dest, src, sizeof( *dest ));\n\tdest->pNext = dest->pPrev = 0xDEADBEEF;\n}\n\nstatic inline void HPAK_ResourceFromCompat( resource_t *dest, dresource_t *src )\n{\n\tmemcpy( dest, src, sizeof( *src ));\n\tdest->pNext = dest->pPrev = (void*)0xDEADBEEF;\n}\n\nstatic void HPAK_AddToQueue( const char *name, resource_t *pResource, void *data, file_t *f )\n{\n\thash_pack_queue_t\t*p;\n\n\tp = Z_Malloc( sizeof( hash_pack_queue_t ));\n\tp->name = copystring( name );\n\tp->resource = *pResource;\n\tp->size = pResource->nDownloadSize;\n\tp->data = Z_Malloc( p->size );\n\n\tif( data != NULL ) memcpy( p->data, data, p->size );\n\telse if( f != NULL ) FS_Read( f, p->data, p->size );\n\telse Host_Error( \"%s: data == NULL.\\n\", __func__ );\n\n\tp->next = gp_hpak_queue;\n\tgp_hpak_queue = p;\n}\n\nvoid HPAK_FlushHostQueue( void )\n{\n\thash_pack_queue_t\t*p;\n\n\tfor( p = gp_hpak_queue; p != NULL; p = gp_hpak_queue )\n\t{\n\t\tgp_hpak_queue = p->next;\n\t\tHPAK_AddLump( false, p->name, &p->resource, p->data, NULL );\n\t\tfreestring( p->name );\n\t\tMem_Free( p->data );\n\t\tMem_Free( p );\n\t}\n\tgp_hpak_queue = NULL;\n}\n\nstatic void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, file_t *fin )\n{\n\tint\t\tfilelocation;\n\tstring\t\tpakname;\n\tbyte\t\tmd5[16];\n\tfile_t\t\t*fout;\n\tMD5Context_t\tctx = { 0 };\n\n\tif( !COM_CheckString( filename ))\n\t\treturn;\n\n\tif(( fin != NULL && pData != NULL ) || ( fin == NULL && pData == NULL ))\n\t\treturn;\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tCon_Printf( \"creating HPAK %s.\\n\", pakname );\n\n\tfout = FS_Open( pakname, \"wb\", true );\n\tif( !fout )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: can't write %s.\\n\", __func__, pakname );\n\t\treturn;\n\t}\n\n\t// let's hash it.\n\tMD5Init( &ctx );\n\n\tif( pData == NULL )\n\t{\n\t\tbyte *temp;\n\n\t\t// there are better ways\n\t\tfilelocation = FS_Tell( fin );\n\t\ttemp = Z_Malloc( pResource->nDownloadSize );\n\t\tFS_Read( fin, temp, pResource->nDownloadSize );\n\t\tFS_Seek( fin, filelocation, SEEK_SET );\n\t\tMD5Update( &ctx, temp, pResource->nDownloadSize );\n\t\tMem_Free( temp );\n\t}\n\telse\n\t{\n\t\tMD5Update( &ctx, pData, pResource->nDownloadSize );\n\t}\n\n\tMD5Final( md5, &ctx );\n\n\tif( memcmp( md5, pResource->rgucMD5_hash, 16 ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: bad checksum for %s. Ignored\\n\", __func__, pakname );\n\t\treturn;\n\t}\n\n\thash_pack_header.ident = IDHPAKHEADER;\n\thash_pack_header.version = IDHPAK_VERSION;\n\thash_pack_header.infotableofs = 0;\n\n\tFS_Write( fout, &hash_pack_header, sizeof( hash_pack_header ));\n\n\thash_pack_info.count = 1;\n\thash_pack_info.entries = Z_Malloc( sizeof( hpak_lump_t ));\n\tHPAK_ResourceToCompat( &hash_pack_info.entries[0].resource, pResource );\n\thash_pack_info.entries[0].filepos = FS_Tell( fout );\n\thash_pack_info.entries[0].disksize = pResource->nDownloadSize;\n\n\tif( pData == NULL )\n\t{\n\t\tFS_FileCopy( fout, fin, hash_pack_info.entries[0].disksize );\n\t}\n\telse\n\t{\n\t\tFS_Write( fout, pData, hash_pack_info.entries[0].disksize );\n\t}\n\n\tfilelocation = FS_Tell( fout );\n\tFS_Write( fout, &hash_pack_info.count, sizeof( hash_pack_info.count ));\n\tFS_Write( fout, &hash_pack_info.entries[0], sizeof( hpak_lump_t ));\n\n\tif( hash_pack_info.entries )\n\t\tMem_Free( hash_pack_info.entries );\n\tmemset( &hash_pack_info, 0, sizeof( hpak_info_t ));\n\n\thash_pack_header.infotableofs = filelocation;\n\tFS_Seek( fout, 0, SEEK_SET );\n\tFS_Write( fout, &hash_pack_header, sizeof( hpak_header_t ));\n\tFS_Close( fout );\n}\n\nstatic qboolean HPAK_FindResource( hpak_info_t *hpk, byte *hash, resource_t *pResource )\n{\n\tint\ti;\n\n\tfor( i = 0; i < hpk->count; i++ )\n\t{\n\t\tif( !memcmp( hpk->entries[i].resource.rgucMD5_hash, hash, 16 ))\n\t\t{\n\t\t\tif( pResource )\n\t\t\t\tHPAK_ResourceFromCompat( pResource, &hpk->entries[i].resource );\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, byte *pData, file_t *pFile )\n{\n\tint\t\ti, j, position, length;\n\thpak_lump_t\t*pCurrentEntry = NULL;\n\tstring\t\tsrcname, dstname;\n\thpak_info_t\tsrcpak, dstpak;\n\tfile_t\t\t*file_src;\n\tfile_t\t\t*file_dst;\n\tbyte\t\tmd5[16];\n\tMD5Context_t\tctx = { 0 };\n\n\tif( pData == NULL && pFile == NULL )\n\t\treturn;\n\n\tif( pResource->nDownloadSize < HPAK_ENTRY_MIN_SIZE || pResource->nDownloadSize > HPAK_ENTRY_MAX_SIZE )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: invalid size %s\\n\", name, Q_memprint( pResource->nDownloadSize ));\n\t\treturn;\n\t}\n\n\t// hash it\n\tMD5Init( &ctx );\n\n\tif( !pData )\n\t{\n\t\tbyte\t\t*temp;\n\n\t\t// there are better ways\n\t\tposition = FS_Tell( pFile );\n\t\ttemp = Z_Malloc( pResource->nDownloadSize );\n\t\tFS_Read( pFile, temp, pResource->nDownloadSize );\n\t\tFS_Seek( pFile, position, SEEK_SET );\n\t\tMD5Update( &ctx, temp, pResource->nDownloadSize );\n\t\tMem_Free( temp );\n\t}\n\telse\n\t{\n\t\tMD5Update( &ctx, pData, pResource->nDownloadSize );\n\t}\n\n\tMD5Final( md5, &ctx );\n\n\tif( memcmp( md5, pResource->rgucMD5_hash, 16 ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: bad checksum for %s. Ignored\\n\", __func__, pResource->szFileName );\n\t\treturn;\n\t}\n\n\tif( bUseQueue )\n\t{\n\t\tHPAK_AddToQueue( name, pResource, pData, pFile );\n\t\treturn;\n\t}\n\n\tQ_strncpy( srcname, name, sizeof( srcname ));\n\tCOM_ReplaceExtension( srcname, \".hpk\", sizeof( srcname ));\n\n\tfile_src = FS_Open( srcname, \"rb\", true );\n\n\tif( !file_src )\n\t{\n\t\t// just create new pack\n\t\tHPAK_CreatePak( name, pResource, pData, pFile );\n\t\treturn;\n\t}\n\n\tQ_strncpy( dstname, srcname, sizeof( dstname ));\n\tCOM_ReplaceExtension( dstname, \".hp2\", sizeof( dstname ));\n\n\tfile_dst = FS_Open( dstname, \"wb\", true );\n\n\tif( !file_dst )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: couldn't open %s.\\n\", __func__, srcname );\n\t\tFS_Close( file_src );\n\t\treturn;\n\t}\n\n\t// load headers\n\tFS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t ));\n\n\tif( hash_pack_header.version != IDHPAK_VERSION )\n\t{\n\t\t// we don't check the HPAK bit for some reason.\n\t\tCon_DPrintf( S_ERROR \"%s: %s does not have a valid header.\\n\", __func__, srcname );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\treturn;\n\t}\n\n\tlength = FS_FileLength( file_src );\n\tFS_Seek( file_src, 0, SEEK_SET ); // rewind to start of file\n\tFS_FileCopy( file_dst, file_src, length );\n\n\tFS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET );\n\tFS_Read( file_src, &srcpak.count, sizeof( srcpak.count ));\n\n\tif( srcpak.count < 1 || srcpak.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s contain too many lumps.\\n\", __func__, srcname );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\treturn;\n\t}\n\n\t// load the data\n\tsrcpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * srcpak.count );\n\tFS_Read( file_src, srcpak.entries, sizeof( hpak_lump_t ) * srcpak.count );\n\n\t// check if already exists\n\tif( HPAK_FindResource( &srcpak, pResource->rgucMD5_hash, NULL ))\n\t{\n\t\tZ_Free( srcpak.entries );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\tFS_Delete( dstname );\n\t\treturn;\n\t}\n\n\t// make a new container\n\tdstpak.count = srcpak.count + 1;\n\tdstpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * dstpak.count );\n\tmemcpy( dstpak.entries, srcpak.entries, sizeof( hpak_lump_t ) * srcpak.count );\n\n\t// check is there are entry with same hash\n\tfor( i = 0; i < srcpak.count; i++ )\n\t{\n\t\tif( memcmp( md5, srcpak.entries[i].resource.rgucMD5_hash, 16 ) == 0 )\n\t\t{\n\t\t\tpCurrentEntry = &dstpak.entries[i];\n\n\t\t\tfor( j = i; j < srcpak.count; j++ )\n\t\t\t\tdstpak.entries[j + 1] = srcpak.entries[j];\n\t\t}\n\t}\n\n\tif( !pCurrentEntry )\n\t\tpCurrentEntry = &dstpak.entries[dstpak.count-1];\n\n\tmemset( pCurrentEntry, 0, sizeof( hpak_lump_t ));\n\tFS_Seek( file_dst, hash_pack_header.infotableofs, SEEK_SET );\n\tHPAK_ResourceToCompat( &pCurrentEntry->resource, pResource );\n\tpCurrentEntry->filepos = FS_Tell( file_dst );\n\tpCurrentEntry->disksize = pResource->nDownloadSize;\n\n\tif( !pData )\n\t\tFS_FileCopy( file_dst, pFile, pCurrentEntry->disksize );\n\telse\n\t\tFS_Write( file_dst, pData, pCurrentEntry->disksize );\n\n\thash_pack_header.infotableofs = FS_Tell( file_dst );\n\tFS_Write( file_dst, &dstpak.count, sizeof( dstpak.count ));\n\n\tfor( i = 0; i < dstpak.count; i++ )\n\t{\n\t\tFS_Write( file_dst, &dstpak.entries[i], sizeof( hpak_lump_t ));\n\t}\n\n\t// finalize\n\tif( srcpak.entries )\n\t\tMem_Free( srcpak.entries );\n\tif( dstpak.entries )\n\t\tMem_Free( dstpak.entries );\n\n\tFS_Seek( file_dst, 0, SEEK_SET );\n\tFS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t ));\n\n\tFS_Close( file_src );\n\tFS_Close( file_dst );\n\n\tFS_Delete( srcname );\n\tFS_Rename( dstname, srcname );\n}\n\nstatic qboolean HPAK_Validate( const char *filename, qboolean quiet, qboolean delete )\n{\n\tfile_t\t\t*f;\n\thpak_lump_t\t*dataDir;\n\thpak_header_t\thdr;\n\tbyte\t\t*dataPak;\n\tint\t\ti, num_lumps;\n\tMD5Context_t\tMD5_Hash;\n\tstring\t\tpakname;\n\tdresource_t\t*pRes;\n\tbyte\t\tmd5[16];\n\n\tif( quiet ) HPAK_FlushHostQueue();\n\n\t// not an error - just flush queue\n\tif( !COM_CheckString( filename ) )\n\t\treturn true;\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f )\n\t{\n\t\tif( !quiet )\n\t\t\tCon_DPrintf( S_ERROR \"Couldn't find %s.\\n\", pakname );\n\t\treturn true;\n\t}\n\n\tif( !quiet ) Con_Printf( \"Validating %s\\n\", pakname );\n\n\tFS_Read( f, &hdr, sizeof( hdr ));\n\tif( hdr.ident != IDHPAKHEADER || hdr.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s does not have a valid HPAK header.\\n\", __func__, pakname );\n\t\tFS_Close( f );\n\t\tif( delete ) FS_Delete( pakname );\n\t\treturn false;\n\t}\n\n\tFS_Seek( f, hdr.infotableofs, SEEK_SET );\n\tFS_Read( f, &num_lumps, sizeof( num_lumps ));\n\n\tif( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s has too many lumps %u.\\n\", __func__, pakname, num_lumps );\n\t\tFS_Close( f );\n\t\tif( delete ) FS_Delete( pakname );\n\t\treturn false;\n\t}\n\n\tif( !quiet ) Con_Printf( \"# of Entries:  %i\\n\", num_lumps );\n\n\tdataDir = Z_Malloc( sizeof( hpak_lump_t ) * num_lumps );\n\tFS_Read( f, dataDir, sizeof( hpak_lump_t ) * num_lumps );\n\n\tif( !quiet ) Con_Printf( \"# Type Size FileName : MD5 Hash\\n\" );\n\n\tfor( i = 0; i < num_lumps; i++ )\n\t{\n\t\tif( dataDir[i].disksize < HPAK_ENTRY_MIN_SIZE || dataDir[i].disksize > HPAK_ENTRY_MAX_SIZE )\n\t\t{\n\t\t\t// odd max size\n\t\t\tCon_DPrintf( S_ERROR \"%s: lump %i has invalid size %s\\n\", __func__, i, Q_memprint( dataDir[i].disksize ));\n\t\t\tMem_Free( dataDir );\n\t\t\tFS_Close( f );\n\t\t\tif( delete ) FS_Delete( pakname );\n\t\t\treturn false;\n\t\t}\n\n\t\tdataPak = Z_Malloc( dataDir[i].disksize );\n\t\tFS_Seek( f, dataDir[i].filepos, SEEK_SET );\n\t\tFS_Read( f, dataPak, dataDir[i].disksize );\n\n\t\tmemset( &MD5_Hash, 0, sizeof( MD5Context_t ));\n\t\tMD5Init( &MD5_Hash );\n\t\tMD5Update( &MD5_Hash, dataPak, dataDir[i].disksize );\n\t\tMD5Final( md5, &MD5_Hash );\n\n\t\tpRes = &dataDir[i].resource;\n\n\t\tif( !quiet )\n\t\t{\n\t\t\tCon_Printf( \"%i:      %s %s %s:   \", i, COM_ResourceTypeFromIndex( pRes->type ),\n\t\t\t\tQ_memprint( pRes->nDownloadSize ), pRes->szFileName );\n\t\t}\n\n\t\tif( memcmp( md5, pRes->rgucMD5_hash, 0x10 ))\n\t\t{\n\t\t\tif( quiet )\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_ERROR \"%s: %s has invalid checksum.\\n\", __func__, pakname );\n\t\t\t\tMem_Free( dataPak );\n\t\t\t\tMem_Free( dataDir );\n\t\t\t\tFS_Close( f );\n\t\t\t\tif( delete ) FS_Delete( pakname );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse Con_DPrintf( S_ERROR \"failed\\n\" );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !quiet ) Con_Printf( \"OK\\n\" );\n\t\t}\n\n\t\t// at this point, it's passed our checks.\n\t\tMem_Free( dataPak );\n\t}\n\n\tMem_Free( dataDir );\n\tFS_Close( f );\n\treturn true;\n}\n\nvoid HPAK_CheckIntegrity( const char *filename )\n{\n\tstring\tpakname;\n\n\tif( !COM_CheckString( filename ) )\n\t\treturn;\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tHPAK_Validate( pakname, true, true );\n}\n\nvoid HPAK_CheckSize( const char *filename )\n{\n\tstring\tpakname;\n\tint\tmaxsize;\n\n\tmaxsize = hpk_maxsize.value;\n\tif( maxsize <= 0 ) return;\n\n\tif( !COM_CheckString( filename ) )\n\t\treturn;\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tif( FS_FileSize( pakname, false ) > ( maxsize * 1024 * 1024 ))\n\t{\n\t\tCon_Printf( \"Server: Size of %s > %f MB, deleting.\\n\", filename, hpk_maxsize.value );\n\t\tLog_Printf( \"Server: Size of %s > %f MB, deleting.\\n\", filename, hpk_maxsize.value );\n\t\tFS_Delete( filename );\n\t}\n}\n\nqboolean HPAK_ResourceForHash( const char *filename, byte *hash, resource_t *pResource )\n{\n\thpak_info_t\tdirectory;\n\thpak_header_t\theader;\n\tstring\t\tpakname;\n\tqboolean\t\tbFound;\n\tfile_t\t\t*f;\n\thash_pack_queue_t\t*p;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn false;\n\n\tfor( p = gp_hpak_queue; p != NULL; p = p->next )\n\t{\n\t\tif( !Q_stricmp( p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, hash, 16 ))\n\t\t{\n\t\t\tif( pResource != NULL )\n\t\t\t\t*pResource = p->resource;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f ) return false;\n\n\tFS_Read( f, &header, sizeof( header ));\n\n\tif( header.ident != IDHPAKHEADER )\n\t{\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tif( header.version != IDHPAK_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tFS_Seek( f, header.infotableofs, SEEK_SET );\n\tFS_Read( f, &directory.count, sizeof( directory.count ));\n\n\tif( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tdirectory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count );\n\tFS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count );\n\tbFound = HPAK_FindResource( &directory, hash, pResource );\n\tMem_Free( directory.entries );\n\tFS_Close( f );\n\n\treturn bFound;\n}\n\nstatic qboolean HPAK_ResourceForIndex( const char *filename, int index, resource_t *pResource )\n{\n\thpak_header_t\theader;\n\thpak_info_t\tdirectory;\n\tstring\t\tpakname;\n\tfile_t\t\t*f;\n\n\tif( !COM_CheckString( filename ) )\n\t\treturn false;\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f )\n\t{\n\t\tCon_DPrintf( S_ERROR \"couldn't open %s.\\n\", pakname );\n\t\treturn false;\n\t}\n\n\tFS_Read( f, &header, sizeof( header ));\n\tif( header.ident != IDHPAKHEADER )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s is not an HPAK file\\n\", pakname );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tif( header.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid version (%i should be %i).\\n\", pakname, header.version, IDHPAK_VERSION );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tFS_Seek( f, header.infotableofs, SEEK_SET );\n\tFS_Read( f, &directory.count, sizeof( directory.count ));\n\n\tif( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has too many lumps %u.\\n\", pakname, directory.count );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tif( index < 1 || index > directory.count )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s, lump with index %i doesn't exist.\\n\", pakname, index );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tdirectory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count );\n\tFS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count );\n\tHPAK_ResourceFromCompat( pResource, &directory.entries[index-1].resource );\n\tZ_Free( directory.entries );\n\tFS_Close( f );\n\n\treturn true;\n}\n\nqboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte **buffer, int *bufsize )\n{\n\tbyte\t\t*tmpbuf;\n\tstring\t\tpakname;\n\thpak_header_t\theader;\n\thpak_info_t\tdirectory;\n\thpak_lump_t\t*entry;\n\thash_pack_queue_t\t*p;\n\tfile_t\t\t*f;\n\tint\t\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn false;\n\n\tif( buffer ) *buffer = NULL;\n\tif( bufsize ) *bufsize = 0;\n\n\tfor( p = gp_hpak_queue; p != NULL; p = p->next )\n\t{\n\t\tif( !Q_stricmp( p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tif( buffer )\n\t\t\t{\n\t\t\t\ttmpbuf = Z_Malloc( p->size );\n\t\t\t\tmemcpy( tmpbuf, p->data, p->size );\n\t\t\t\t*buffer = tmpbuf;\n\t\t\t}\n\n\t\t\tif( bufsize )\n\t\t\t\t*bufsize = p->size;\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tQ_strncpy( pakname, filename, sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f ) return false;\n\n\tFS_Read( f, &header, sizeof( header ));\n\n\tif( header.ident != IDHPAKHEADER )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s it's not a HPK file.\\n\", pakname );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tif( header.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid version (%i should be %i).\\n\", pakname, header.version, IDHPAK_VERSION );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tFS_Seek( f, header.infotableofs, SEEK_SET );\n\tFS_Read( f, &directory.count, sizeof( directory.count ));\n\n\tif( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s has too many lumps %u.\\n\", __func__, filename, directory.count );\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tdirectory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count );\n\tFS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count );\n\n\tfor( i = 0; i < directory.count; i++ )\n\t{\n\t\tentry = &directory.entries[i];\n\n\t\tif( entry->filepos > 0 &&\n\t\t\tentry->disksize > 0 &&\n\t\t\t!memcmp( entry->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tFS_Seek( f, entry->filepos, SEEK_SET );\n\n\t\t\tif( buffer )\n\t\t\t{\n\t\t\t\ttmpbuf = Z_Malloc( entry->disksize );\n\t\t\t\tFS_Read( f, tmpbuf, entry->disksize );\n\t\t\t\t*buffer = tmpbuf;\n\t\t\t}\n\n\t\t\tif( bufsize )\n\t\t\t\t*bufsize = entry->disksize;\n\n\t\t\tMem_Free( directory.entries );\n\t\t\tFS_Close( f );\n\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tMem_Free( directory.entries );\n\tFS_Close( f );\n\n\treturn false;\n}\n\nvoid HPAK_RemoveLump( const char *name, resource_t *pResource )\n{\n\tstring\t\tread_path;\n\tstring\t\tsave_path;\n\tfile_t\t\t*file_src;\n\tfile_t\t\t*file_dst;\n\thpak_info_t\thpak_read;\n\thpak_info_t\thpak_save;\n\tint\t\ti, j;\n\n\tif( !COM_CheckString( name ) || !pResource )\n\t\treturn;\n\n\tHPAK_FlushHostQueue();\n\n\tQ_strncpy( read_path, name, sizeof( read_path ));\n\tCOM_ReplaceExtension( read_path, \".hpk\", sizeof( read_path ));\n\n\tfile_src = FS_Open( read_path, \"rb\", true );\n\tif( !file_src )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s couldn't open.\\n\", read_path );\n\t\treturn;\n\t}\n\n\tQ_strncpy( save_path, read_path, sizeof( save_path ));\n\tCOM_ReplaceExtension( save_path, \".hp2\", sizeof( save_path ));\n\tfile_dst = FS_Open( save_path, \"wb\", true );\n\n\tif( !file_dst )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s couldn't open.\\n\", save_path );\n\t\tFS_Close( file_src );\n\t\treturn;\n\t}\n\n\tFS_Seek( file_src, 0, SEEK_SET );\n\tFS_Seek( file_dst, 0, SEEK_SET );\n\n\t// header copy\n\tFS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t ));\n\tFS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t ));\n\n\tif( hash_pack_header.ident != IDHPAKHEADER || hash_pack_header.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid header.\\n\", read_path );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\tFS_Delete( save_path ); // delete temp file\n\t\treturn;\n\t}\n\n\tFS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET );\n\tFS_Read( file_src, &hpak_read.count, sizeof( hpak_read.count ));\n\n\tif( hpak_read.count < 1 || hpak_read.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid number of lumps.\\n\", read_path );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\tFS_Delete( save_path ); // delete temp file\n\t\treturn;\n\t}\n\n\tif( hpak_read.count == 1 )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s only has one element, so HPAK will be removed\\n\", read_path );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\tFS_Delete( read_path );\n\t\tFS_Delete( save_path );\n\t\treturn;\n\t}\n\n\thpak_save.count = hpak_read.count - 1;\n\thpak_read.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_read.count );\n\thpak_save.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_save.count );\n\n\tFS_Read( file_src, hpak_read.entries, sizeof( hpak_lump_t ) * hpak_read.count );\n\n\tif( !HPAK_FindResource( &hpak_read, pResource->rgucMD5_hash, NULL ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"HPAK %s doesn't contain specified lump: %s\\n\", read_path, pResource->szFileName );\n\t\tMem_Free( hpak_read.entries );\n\t\tMem_Free( hpak_save.entries );\n\t\tFS_Close( file_src );\n\t\tFS_Close( file_dst );\n\t\tFS_Delete( save_path );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"Removing %s from HPAK %s.\\n\", pResource->szFileName, read_path );\n\n\t// If there's a collision, we've just corrupted this hpak.\n\tfor( i = 0, j = 0; i < hpak_read.count; i++ )\n\t{\n\t\tif( !memcmp( hpak_read.entries[i].resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 ))\n\t\t\tcontinue;\n\n\t\thpak_save.entries[j] = hpak_read.entries[i];\n\t\thpak_save.entries[j].filepos = FS_Tell( file_dst );\n\t\tFS_Seek( file_src, hpak_read.entries[j].filepos, SEEK_SET );\n\t\tFS_FileCopy( file_dst, file_src, hpak_save.entries[j].disksize );\n\t\tj++;\n\t}\n\n\thash_pack_header.infotableofs = FS_Tell( file_dst );\n\tFS_Write( file_dst, &hpak_save.count, sizeof( hpak_save.count ));\n\n\tfor( i = 0; i < hpak_save.count; i++ )\n\t\tFS_Write( file_dst, &hpak_save.entries[i], sizeof( hpak_lump_t ));\n\n\tFS_Seek( file_dst, 0, SEEK_SET );\n\tFS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t ));\n\n\tMem_Free( hpak_read.entries );\n\tMem_Free( hpak_save.entries );\n\tFS_Close( file_src );\n\tFS_Close( file_dst );\n\n\tFS_Delete( read_path );\n\tFS_Rename( save_path, read_path );\n}\n\nstatic void HPAK_List_f( void )\n{\n\tint\t\tnCurrent;\n\thpak_header_t\theader;\n\thpak_info_t\tdirectory;\n\thpak_lump_t\t*entry;\n\tstring\t\tlumpname;\n\tstring\t\tpakname;\n\tconst char\t*type;\n\tconst char\t*size;\n\tfile_t\t\t*f;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"hpklist <hpk>\\n\" );\n\t\treturn;\n\t}\n\n\tHPAK_FlushHostQueue();\n\n\tQ_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\tCon_Printf( \"Contents for %s.\\n\", pakname );\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f )\n\t{\n\t\tCon_DPrintf( S_ERROR \"couldn't open %s.\\n\", pakname );\n\t\treturn;\n\t}\n\n\tFS_Read( f, &header, sizeof( hpak_header_t ));\n\n\tif( header.ident != IDHPAKHEADER )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s is not an HPAK file\\n\", pakname );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tif( header.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid version (%i should be %i).\\n\", pakname, header.version, IDHPAK_VERSION );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tFS_Seek( f, header.infotableofs, SEEK_SET );\n\tFS_Read( f, &directory.count, sizeof( directory.count ));\n\n\tif( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has too many lumps %u.\\n\", pakname, directory.count );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"# of Entries:  %i\\n\", directory.count );\n\tCon_Printf( \"# Type Size FileName : MD5 Hash\\n\" );\n\n\tdirectory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t ));\n\tFS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t ));\n\n\tfor( nCurrent = 0; nCurrent < directory.count; nCurrent++ )\n\t{\n\t\tentry = &directory.entries[nCurrent];\n\t\tCOM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname ));\n\t\ttype = COM_ResourceTypeFromIndex( entry->resource.type );\n\t\tsize = Q_memprint( entry->resource.nDownloadSize );\n\n\t\tCon_Printf( \"%i: %10s %s %s\\n  :  %s\\n\", nCurrent + 1, type, size, lumpname, MD5_Print( entry->resource.rgucMD5_hash ));\n\t}\n\n\tif( directory.entries )\n\t\tMem_Free( directory.entries );\n\tFS_Close( f );\n}\n\nstatic void HPAK_Extract_f( void )\n{\n\tint\t\tnCurrent;\n\thpak_header_t\theader;\n\thpak_info_t\tdirectory;\n\thpak_lump_t\t*entry;\n\tstring\t\tlumpname;\n\tstring\t\tpakname;\n\tstring\t\tszFileOut;\n\tint\t\tnIndex;\n\tbyte\t\t*pData;\n\tint\t\tnDataSize;\n\tconst char\t*type;\n\tconst char\t*size;\n\tfile_t\t\t*f;\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"hpkextract hpkname [all | single index]\\n\" );\n\t\treturn;\n\t}\n\n\tif( !Q_stricmp( Cmd_Argv( 2 ), \"all\" ))\n\t{\n\t\tnIndex = -1;\n\t}\n\telse\n\t{\n\t\tnIndex = Q_atoi( Cmd_Argv( 2 ) );\n\t}\n\n\tHPAK_FlushHostQueue();\n\n\tQ_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname ));\n\tCOM_ReplaceExtension( pakname, \".hpk\", sizeof( pakname ));\n\tCon_Printf( \"Contents for %s.\\n\", pakname );\n\n\tf = FS_Open( pakname, \"rb\", true );\n\tif( !f )\n\t{\n\t\tCon_DPrintf( S_ERROR \"couldn't open %s.\\n\", pakname );\n\t\treturn;\n\t}\n\n\tFS_Read( f, &header, sizeof( hpak_header_t ));\n\n\tif( header.ident != IDHPAKHEADER )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s is not an HPAK file\\n\", pakname );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tif( header.version != IDHPAK_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has invalid version (%i should be %i).\\n\", pakname, header.version, IDHPAK_VERSION );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tFS_Seek( f, header.infotableofs, SEEK_SET );\n\tFS_Read( f, &directory.count, sizeof( directory.count ));\n\n\tif( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has too many lumps %u.\\n\", pakname, directory.count );\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tif( nIndex == -1 ) Con_Printf( \"Extracting all lumps from %s.\\n\", pakname );\n\telse Con_Printf( \"Extracting lump %i from %s\\n\", nIndex, pakname );\n\n\tdirectory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t ));\n\tFS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t ));\n\n\tfor( nCurrent = 0; nCurrent < directory.count; nCurrent++ )\n\t{\n\t\tentry = &directory.entries[nCurrent];\n\n\t\tif( nIndex != -1 && nIndex != nCurrent )\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname ) );\n\t\ttype = COM_ResourceTypeFromIndex( entry->resource.type );\n\t\tsize = Q_memprint( entry->resource.nDownloadSize );\n\n\t\tCon_Printf( \"Extracting %i: %10s %s %s\\n\", nCurrent + 1, type, size, lumpname );\n\n\t\tif( entry->disksize < HPAK_ENTRY_MIN_SIZE || entry->disksize > HPAK_ENTRY_MAX_SIZE )\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"Unable to extract data, size invalid:  %s\\n\", Q_memprint( entry->disksize ));\n\t\t\tcontinue;\n\t\t}\n\n\t\tnDataSize = entry->disksize;\n\t\tpData = Z_Malloc( nDataSize + 1 );\n\t\tFS_Seek( f, entry->filepos, SEEK_SET );\n\t\tFS_Read( f, pData, nDataSize );\n\n\t\tQ_snprintf( szFileOut, sizeof( szFileOut ), \"hpklmps/lmp%04i.bmp\", nCurrent );\n\t\tFS_WriteFile( szFileOut, pData, nDataSize );\n\t\tif( pData ) Mem_Free( pData );\n\t}\n\n\tif( directory.entries )\n\t\tMem_Free( directory.entries );\n\n\tFS_Close( f );\n}\n\nstatic void HPAK_Remove_f( void )\n{\n\tresource_t\tresource;\n\n\tHPAK_FlushHostQueue();\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"hpkremove <hpk> <index>\\n\" );\n\t\treturn;\n\t}\n\n\tif( HPAK_ResourceForIndex( Cmd_Argv( 1 ), Q_atoi( Cmd_Argv( 2 )), &resource ))\n\t{\n\t\tHPAK_RemoveLump( Cmd_Argv( 1 ), &resource );\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( S_ERROR \"Could not locate resource %i in %s\\n\", Q_atoi( Cmd_Argv( 2 )), Cmd_Argv( 1 ));\n\t}\n}\n\nstatic void HPAK_Validate_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"hpkval <filename>\\n\" );\n\t\treturn;\n\t}\n\n\tHPAK_Validate( Cmd_Argv( 1 ), false, false );\n}\n\nvoid HPAK_Init( void )\n{\n\tCmd_AddRestrictedCommand( \"hpklist\", HPAK_List_f, \"list all files in specified HPK-file\" );\n\tCmd_AddRestrictedCommand( \"hpkremove\", HPAK_Remove_f, \"remove specified file from HPK-file\" );\n\tCmd_AddRestrictedCommand( \"hpkval\", HPAK_Validate_f, \"validate specified HPK-file\" );\n\tCmd_AddRestrictedCommand( \"hpkextract\", HPAK_Extract_f, \"extract all lumps from specified HPK-file\" );\n\tCmd_AddRestrictedCommand( \"hpk_maxsize\", HPAK_MaxSize_f, \"deprecation notice for hpk_maxsize\" );\n\tCvar_RegisterVariable( &hpk_maxsize );\n\tCvar_RegisterVariable( &hpk_custom_file );\n\n\tgp_hpak_queue = NULL;\n}\n"
  },
  {
    "path": "engine/common/hpak.h",
    "content": "/*\nhpak.c - custom user package to send other clients\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef HPAK_H\n#define HPAK_H\n\n#include \"custom.h\"\n\n#define HPAK_ENTRY_MIN_SIZE\t(512)\n#define HPAK_ENTRY_MAX_SIZE\t(256 * 1024)\n\n/*\n========================================================================\n.HPK archive format\t(Hash PAK - HPK)\n\nList of compressed files, that can be identify only by TYPE_*\n\n<format>\nheader:\tdwadinfo_t[dwadinfo_t]\nfile_1:\tbyte[dwadinfo_t[num]->disksize]\nfile_2:\tbyte[dwadinfo_t[num]->disksize]\nfile_3:\tbyte[dwadinfo_t[num]->disksize]\n...\nfile_n:\tbyte[dwadinfo_t[num]->disksize]\ninfotable\tdlumpinfo_t[dwadinfo_t->numlumps]\n========================================================================\n*/\n\n#define IDHPAKHEADER\t(('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian \"HPAK\"\n#define IDHPAK_VERSION\t1\n\n// a1ba: because Valve for some reason writes resource_t to file\n// I had to make it crossplatform version\n#pragma pack( push, 8 )\ntypedef struct dresource_s\n{\n\tchar                       szFileName[64];       /*     0    64 */\n\t/* --- cacheline 1 boundary (64 bytes) --- */\n\tresourcetype_t             type;                 /*    64     4 */\n\tint                        nIndex;               /*    68     4 */\n\tint                        nDownloadSize;        /*    72     4 */\n\tunsigned char              ucFlags;              /*    76     1 */\n\tunsigned char              rgucMD5_hash[16];     /*    77    16 */\n\tunsigned char              playernum;            /*    93     1 */\n\tunsigned char              rguc_reserved[32];    /*    94    32 */\n\n\t/* XXX 2 bytes hole, try to pack */\n\n\t/* --- cacheline 2 boundary (128 bytes) --- */\n\tuint32_t                   pNext;                /*   128     4 */\n\tuint32_t                   pPrev;                /*   132     4 */\n\n\t/* size: 136, cachelines: 3, members: 10 */\n\t/* sum members: 134, holes: 1, sum holes: 2 */\n\t/* last cacheline: 8 bytes */\n} dresource_t;\n#pragma pack( pop )\n\nSTATIC_CHECK_SIZEOF( dresource_t, 136, 136 );\n\ntypedef struct\n{\n\tint             ident;          // should be equal HPAK\n\tint             version;\n\tint             infotableofs;\n} hpak_header_t;\n\nSTATIC_CHECK_SIZEOF( hpak_header_t, 12, 12 );\n\ntypedef struct\n{\n\tdresource_t     resource;\n\tint             filepos;\n\tint             disksize;\n} hpak_lump_t;\n\nSTATIC_CHECK_SIZEOF( hpak_lump_t, 144, 144 );\n\ntypedef struct\n{\n\tint             count;\n\thpak_lump_t     *entries;\t\t// variable sized.\n} hpak_info_t;\n\n#endif // HPAK_H\n"
  },
  {
    "path": "engine/common/imagelib/imagelib.h",
    "content": "/*\nimagelib.h - engine image lib\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef IMAGELIB_H\n#define IMAGELIB_H\n\n#include \"common.h\"\n\n// skyorder_q2[6] = { 2, 3, 1, 0, 4, 5, }; // Quake, Half-Life skybox ordering\n// skyorder_ms[6] = { 4, 5, 1, 0, 2, 3  }; // Microsoft DDS ordering (reverse)\n\n// cubemap hints\ntypedef enum\n{\n\tCB_HINT_NO = 0,\n\n\t// dds cubemap hints ( Microsoft sides order )\n\tCB_HINT_POSX,\n\tCB_HINT_NEGX,\n\tCB_HINT_POSZ,\n\tCB_HINT_NEGZ,\n\tCB_HINT_POSY,\n\tCB_HINT_NEGY,\n\tCB_FACECOUNT,\n} side_hint_t;\n\ntypedef enum\n{\n\tIL_HINT_NO = 0,\n\tIL_HINT_Q1,\t// palette choosing\n\tIL_HINT_HL,\n} image_hint_t;\n\ntypedef struct loadformat_s\n{\n\tconst char *ext;\n\tqboolean (*loadfunc)( const char *name, const byte *buffer, fs_offset_t filesize );\n\timage_hint_t hint;\n} loadpixformat_t;\n\ntypedef struct saveformat_s\n{\n\tconst char *ext;\n\tqboolean (*savefunc)( const char *name, rgbdata_t *pix );\n} savepixformat_t;\n\ntypedef struct imglib_s\n{\n\tconst loadpixformat_t\t*loadformats;\n\tconst savepixformat_t\t*saveformats;\n\n\t// current 2d image state\n\tword\t\t\twidth;\n\tword\t\t\theight;\n\tword\t\t\tdepth;\n\tbyte\t\t\tnum_mips;\t\t// mipmap count\n\tword\t\t\tencode;\t\t// custom encode type\n\tuint\t\t\ttype;\t\t// main type switcher\n\tuint\t\t\tflags;\t\t// additional image flags\n\tsize_t\t\t\tsize;\t\t// image rgba size (for bounds checking)\n\tuint\t\t\tptr;\t\t// safe image pointer\n\tint\t\t\tbpp;\t\t// PFDesc[type].bpp\n\tbyte\t\t\t*rgba;\t\t// image pointer (see image_type for details)\n\n\t// current cubemap state\n\tint\t\t\tsource_width;\t// locked cubemap dims (all wrong sides will be automatically resampled)\n\tint\t\t\tsource_height;\n\tuint\t\t\tsource_type;\t// shared image type for all mipmaps or cubemap sides\n\tint\t\t\tnum_sides;\t// how much sides is loaded\n\tbyte\t\t\t*cubemap;\t\t// cubemap pack\n\n\t// indexed images state\n\tuint\t\t\t*d_currentpal;\t// installed version of internal palette\n\tint\t\t\td_rendermode;\t// palette rendermode\n\tbyte\t\t\t*palette;\t\t// palette pointer\n\n\t// global parms\n\trgba_t\t\t\tfogParams;\t// some water textures has info about underwater fog\n\n\tint\t\t\thint;\t\t// hint for some loaders\n\tbyte\t\t\t*tempbuffer;\t// for convert operations\n\tint\t\t\tcmd_flags;\t// global imglib flags\n\tint\t\t\tforce_flags;\t// override cmd_flags\n\tqboolean\t\t\tcustom_palette;\t// custom palette was installed\n} imglib_t;\n\n// imagelib definitions\n#define IMAGE_MAXWIDTH\t8192\n#define IMAGE_MAXHEIGHT\t8192\n#define LUMP_MAXWIDTH\t1024\t// WorldCraft limits\n#define LUMP_MAXHEIGHT\t1024\n#define PLDECAL_MAXWIDTH  768 // total of ~2mb uncompressed rgba data\n#define PLDECAL_MAXHEIGHT 768\n#define IMAGE_GRADIENT_DECAL (1<<10) // TYP_PALETTE lump in WAD\n\nenum\n{\n\tLUMP_NORMAL = 0, // no alpha\n\tLUMP_MASKED,     // 1-bit alpha channel masked texture\n\tLUMP_GRADIENT,   // gradient image (decals)\n\tLUMP_EXTENDED,   // bmp images have extened palette with alpha-channel\n\tLUMP_HALFLIFE,   // get predefined half-life palette\n\tLUMP_QUAKE1,     // get predefined quake palette\n\tLUMP_TEXGAMMA,   // apply texgamma on top of palette, for half-life mips\n};\n\nenum\n{\n\tPAL_INVALID = -1,\n\tPAL_CUSTOM = 0,\n\tPAL_QUAKE1,\n\tPAL_HALFLIFE\n};\n\nextern imglib_t image;\n\nbyte *Image_ResampleInternal( const void *indata, int in_w, int in_h, int out_w, int out_h, int intype, qboolean *done );\nbyte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int type, int flags );\nrgbdata_t *Image_Load(const char *filename, const byte *buffer, size_t buffsize );\nqboolean Image_Copy8bitRGBA( const byte *in, byte *out, int pixels );\nqboolean Image_AddIndexedImageToPack( const byte *in, int width, int height );\nqboolean Image_AddRGBAImageToPack( uint imageSize, const void* data );\nvoid Image_Save( const char *filename, rgbdata_t *pix );\nvoid Image_GetPaletteLMP( const byte *pal, int rendermode );\nvoid Image_GetPaletteBMP( const byte *pal );\nint Image_ComparePalette( const byte *pal );\nvoid Image_FreeImage( rgbdata_t *pack );\nvoid Image_CopyPalette24bit( void );\nvoid Image_CopyPalette32bit( void );\nvoid Image_SetPixelFormat( void );\nvoid Image_GetPaletteQ1( void );\nvoid Image_GetPaletteHL( void );\nsize_t Image_ComputeSize( int type, int width, int height, int depth );\nvoid Image_GenerateMipmaps( const byte *source, int width, int height, byte *mip1, byte *mip2, byte *mip3 );\n\n//\n// formats load\n//\nqboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadMDL( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize );\n\n//\n// formats save\n//\nqboolean Image_SaveTGA( const char *name, rgbdata_t *pix );\nqboolean Image_SaveBMP( const char *name, rgbdata_t *pix );\nqboolean Image_SavePNG( const char *name, rgbdata_t *pix );\nqboolean Image_SaveWAD( const char *name, rgbdata_t *pix );\n\n//\n// img_quant.c\n//\nrgbdata_t *Image_Quantize( rgbdata_t *pic );\n\n//\n// img_utils.c\n//\nvoid Image_Reset( void );\nbyte *Image_Copy( size_t size );\nvoid Image_CopyParms( rgbdata_t *src );\nqboolean Image_ValidSize( const char *name );\nqboolean Image_LumpValidSize( const char *name );\nqboolean Image_CheckFlag( int bit );\n\n#endif//IMAGELIB_H\n"
  },
  {
    "path": "engine/common/imagelib/img_bmp.c",
    "content": "/*\nimg_bmp.c - bmp format load & save\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"img_bmp.h\"\n\n/*\n=============\nImage_LoadBMP\n=============\n*/\nqboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tbyte\t*buf_p, *pixbuf;\n\trgba_t\tpalette[256] = { 0 };\n\tint\ti, columns, column, rows, row, bpp = 1;\n\tint\tcbPalBytes = 0, padSize = 0, bps = 0;\n\tuint\treflectivity[3] = { 0, 0, 0 };\n\tqboolean\tload_qfont = false;\n\tbmp_t\tbhdr;\n\tfs_offset_t estimatedSize;\n\n\tif( filesize < sizeof( bhdr ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s have incorrect file size %li should be greater than %zu (header)\\n\", __func__, name, (long)filesize, sizeof( bhdr ));\n\t\treturn false;\n\t}\n\n\tbuf_p = (byte *)buffer;\n\tmemcpy( &bhdr, buf_p, sizeof( bmp_t ));\n\tbuf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize;\n\n\t// bogus file header check\n\tif( bhdr.reserved0 != 0 ) return false;\n\tif( bhdr.planes != 1 ) return false;\n\n\tif( memcmp( bhdr.id, \"BM\", 2 ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: only Windows-style BMP files supported (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif(!( bhdr.bitmapHeaderSize == 40 || bhdr.bitmapHeaderSize == 108 || bhdr.bitmapHeaderSize == 124 ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s have non-standard header size %i\\n\", __func__, name, bhdr.bitmapHeaderSize );\n\t\treturn false;\n\t}\n\n\t// bogus info header check\n\tif( bhdr.fileSize != filesize )\n\t{\n\t\t// Sweet Half-Life issues. splash.bmp have bogus filesize\n\t\tCon_Reportf( S_WARN \"%s: %s have incorrect file size %li should be %i\\n\", __func__, name, (long)filesize, bhdr.fileSize );\n\t}\n\n\t// bogus compression?  Only non-compressed supported.\n\tif( bhdr.compression != BI_RGB )\n\t{\n\t\tif( bhdr.bitsPerPixel != 32 || bhdr.compression != BI_BITFIELDS )\n\t\t{ \n\t\t\tCon_DPrintf( S_ERROR \"%s: only uncompressed BMP files supported (%s)\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\timage.width = columns = bhdr.width;\n\timage.height = rows = abs( bhdr.height );\n\n\tif( !Image_ValidSize( name ))\n\t\treturn false;\n\n\t// special case for loading qfont (menu font)\n\tif( !Q_strncmp( name, \"#XASH_SYSTEMFONT_001\", 20 ))\n\t{\n\t\t// NOTE: same as system font we can use 4-bit bmps only\n\t\t// step1: move main layer into alpha-channel (give grayscale from RED channel)\n\t\t// step2: fill main layer with 255 255 255 color (white)\n\t\t// step3: ????\n\t\t// step4: PROFIT!!! (economy up to 150 kb for menu.dll final size)\n\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\tload_qfont = true;\n\t}\n\n\tif( bhdr.bitsPerPixel <= 8 )\n\t{\n\t\t// figure out how many entries are actually in the table\n\t\tif( bhdr.colors == 0 )\n\t\t{\n\t\t\tbhdr.colors = 256;\n\t\t\tcbPalBytes = ( 1 << bhdr.bitsPerPixel ) * sizeof( rgba_t );\n\t\t}\n\t\telse cbPalBytes = bhdr.colors * sizeof( rgba_t );\n\t}\n\n\testimatedSize = ( buf_p - buffer ) + cbPalBytes;\n\tif( filesize < estimatedSize )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s have incorrect file size %li should be greater than %li (palette)\\n\", __func__, name, (long)filesize, (long)estimatedSize );\n\t\treturn false;\n\t}\n\n\tmemcpy( palette, buf_p, cbPalBytes );\n\n\t// setup gradient alpha for player decal\n\tif( !Q_strncmp( name, \"#logo\", 5 ))\n\t{\n\t\tfor( i = 0; i < bhdr.colors; i++ )\n\t\t\tpalette[i][3] = i;\n\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t}\n\n\tif( Image_CheckFlag( IL_OVERVIEW ) && bhdr.bitsPerPixel == 8 )\n\t{\n\t\t// convert green background into alpha-layer, make opacity for all other entries\n\t\tfor( i = 0; i < bhdr.colors; i++ )\n\t\t{\n\t\t\tif( palette[i][0] == 0 && palette[i][1] == 255 && palette[i][2] == 0 )\n\t\t\t{\n\t\t\t\tpalette[i][0] = palette[i][1] = palette[i][2] = palette[i][3] = 0;\n\t\t\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\t\t}\n\t\t\telse palette[i][3] = 255;\n\t\t}\n\t}\n\n\tif( Image_CheckFlag( IL_KEEP_8BIT ) && bhdr.bitsPerPixel == 8 )\n\t{\n\t\tpixbuf = image.palette = Mem_Malloc( host.imagepool, 1024 );\n\n\t\t// bmp have a reversed palette colors\n\t\tfor( i = 0; i < bhdr.colors; i++ )\n\t\t{\n\t\t\t*pixbuf++ = palette[i][2];\n\t\t\t*pixbuf++ = palette[i][1];\n\t\t\t*pixbuf++ = palette[i][0];\n\t\t\t*pixbuf++ = palette[i][3];\n\t\t}\n\t\timage.type = PF_INDEXED_32; // 32 bit palette\n\t}\n\telse\n\t{\n\t\timage.palette = NULL;\n\t\timage.type = PF_RGBA_32;\n\t\tbpp = 4;\n\t}\n\n\tbuf_p += cbPalBytes;\n\tbps = image.width * (bhdr.bitsPerPixel >> 3);\n\n\tswitch( bhdr.bitsPerPixel )\n\t{\n\tcase 1:\n\t\tpadSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4;\n\t\tbreak;\n\tcase 4:\n\t\tpadSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4;\n\t\tbreak;\n\tcase 16:\n\t\tpadSize = ( 4 - ( image.width * 2 % 4 )) % 4;\n\t\tbreak;\n\tcase 8:\n\tcase 24:\n\t\tpadSize = ( 4 - ( bps % 4 )) % 4;\n\t\tbreak;\n\t}\n\n\testimatedSize = ( buf_p - buffer ) + image.width * image.height * ( bhdr.bitsPerPixel >> 3 );\n\tif( filesize < estimatedSize )\n\t{\n\t\tif( image.palette )\n\t\t{\n\t\t\tMem_Free( image.palette );\n\t\t\timage.palette = NULL;\n\t\t}\n\n\t\tCon_Reportf( S_ERROR \"%s: %s have incorrect file size %li should be greater than %li (pixels)\\n\", __func__, name, (long)filesize, (long)estimatedSize );\n\t\treturn false;\n\t}\n\n\timage.depth = 1;\n\timage.size = image.width * image.height * bpp;\n\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\n\tfor( row = rows - 1; row >= 0; row-- )\n\t{\n\t\tpixbuf = image.rgba + (row * columns * bpp);\n\n\t\tfor( column = 0; column < columns; column++ )\n\t\t{\n\t\t\tbyte\tred, green, blue, alpha;\n\t\t\tword\tshortPixel;\n\t\t\tint\tc, k, palIndex;\n\n\t\t\tswitch( bhdr.bitsPerPixel )\n\t\t\t{\n\t\t\tcase 1:\n\t\t\t\talpha = *buf_p++;\n\t\t\t\tcolumn--;\t// ingnore main iterations\n\t\t\t\tfor( c = 0, k = 128; c < 8; c++, k >>= 1 )\n\t\t\t\t{\n\t\t\t\t\tred = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00);\n\t\t\t\t\t*pixbuf++ = red;\n\t\t\t\t\t*pixbuf++ = green;\n\t\t\t\t\t*pixbuf++ = blue;\n\t\t\t\t\t*pixbuf++ = 0x00;\n\t\t\t\t\tif( ++column == columns )\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\talpha = *buf_p++;\n\t\t\t\tpalIndex = alpha >> 4;\n\t\t\t\tif( load_qfont )\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = red = 255;\n\t\t\t\t\t*pixbuf++ = green = 255;\n\t\t\t\t\t*pixbuf++ = blue = 255;\n\t\t\t\t\t*pixbuf++ = palette[palIndex][2];\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = red = palette[palIndex][2];\n\t\t\t\t\t*pixbuf++ = green = palette[palIndex][1];\n\t\t\t\t\t*pixbuf++ = blue = palette[palIndex][0];\n\t\t\t\t\t*pixbuf++ = palette[palIndex][3];\n\t\t\t\t}\n\t\t\t\tif( ++column == columns ) break;\n\t\t\t\tpalIndex = alpha & 0x0F;\n\t\t\t\tif( load_qfont )\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = red = 255;\n\t\t\t\t\t*pixbuf++ = green = 255;\n\t\t\t\t\t*pixbuf++ = blue = 255;\n\t\t\t\t\t*pixbuf++ = palette[palIndex][2];\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = red = palette[palIndex][2];\n\t\t\t\t\t*pixbuf++ = green = palette[palIndex][1];\n\t\t\t\t\t*pixbuf++ = blue = palette[palIndex][0];\n\t\t\t\t\t*pixbuf++ = palette[palIndex][3];\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 8:\n\t\t\t\tpalIndex = *buf_p++;\n\t\t\t\tred = palette[palIndex][2];\n\t\t\t\tgreen = palette[palIndex][1];\n\t\t\t\tblue = palette[palIndex][0];\n\t\t\t\talpha = palette[palIndex][3];\n\n\t\t\t\tif( Image_CheckFlag( IL_KEEP_8BIT ))\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = palIndex;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t*pixbuf++ = red;\n\t\t\t\t\t*pixbuf++ = green;\n\t\t\t\t\t*pixbuf++ = blue;\n\t\t\t\t\t*pixbuf++ = alpha;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 16:\n\t\t\t\tshortPixel = *(word *)buf_p, buf_p += 2;\n\t\t\t\t*pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7;\n\t\t\t\t*pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2;\n\t\t\t\t*pixbuf++ = red = (shortPixel & ( 31 )) << 3;\n\t\t\t\t*pixbuf++ = 0xff;\n\t\t\t\tbreak;\n\t\t\tcase 24:\n\t\t\t\tblue = *buf_p++;\n\t\t\t\tgreen = *buf_p++;\n\t\t\t\tred = *buf_p++;\n\t\t\t\t*pixbuf++ = red;\n\t\t\t\t*pixbuf++ = green;\n\t\t\t\t*pixbuf++ = blue;\n\t\t\t\t*pixbuf++ = 0xFF;\n\t\t\t\tbreak;\n\t\t\tcase 32:\n\t\t\t\tblue = *buf_p++;\n\t\t\t\tgreen = *buf_p++;\n\t\t\t\tred = *buf_p++;\n\t\t\t\talpha = *buf_p++;\n\t\t\t\t*pixbuf++ = red;\n\t\t\t\t*pixbuf++ = green;\n\t\t\t\t*pixbuf++ = blue;\n\t\t\t\t*pixbuf++ = alpha;\n\t\t\t\tif( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tMem_Free( image.palette );\n\t\t\t\tMem_Free( image.rgba );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif( red != green || green != blue )\n\t\t\t\timage.flags |= IMAGE_HAS_COLOR;\n\n\t\t\treflectivity[0] += red;\n\t\t\treflectivity[1] += green;\n\t\t\treflectivity[2] += blue;\n\t\t}\n\t\tbuf_p += padSize;\t// actual only for 4-bit bmps\n\t}\n\n\tVectorDivide( reflectivity, ( image.width * image.height ), image.fogParams );\n\tif( image.palette )\n\t\tImage_GetPaletteBMP( image.palette );\n\n\treturn true;\n}\n\nqboolean Image_SaveBMP( const char *name, rgbdata_t *pix )\n{\n\tfile_t\t\t*pfile = NULL;\n\tsize_t\t\ttotal_size, cur_size;\n\trgba_t\t\trgrgbPalette[256];\n\tdword\t\tcbBmpBits;\n\tbyte\t\t*clipbuf = NULL;\n\tbyte\t\t*pb, *pbBmpBits;\n\tdword\t\tcbPalBytes;\n\tdword\t\tbiTrueWidth;\n\tint\t\tpixel_size;\n\tint\t\ti, x, y;\n\tbmp_t\thdr;\n\n\tif( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE ) )\n\t\treturn false; // already existed\n\n\t// bogus parameter check\n\tif( !pix->buffer )\n\t\treturn false;\n\n\t// get image description\n\tswitch( pix->type )\n\t{\n\tcase PF_INDEXED_24:\n\tcase PF_INDEXED_32:\n\t\tpixel_size = 1;\n\t\tbreak;\n\tcase PF_RGB_24:\n\t\tpixel_size = 3;\n\t\tbreak;\n\tcase PF_RGBA_32:\n\t\tpixel_size = 4;\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\tpfile = FS_Open( name, \"wb\", false );\n\tif( !pfile ) return false;\n\n\t// NOTE: align transparency column will sucessfully removed\n\t// after create sprite or lump image, it's just standard requiriments\n\tbiTrueWidth = ((pix->width + 3) & ~3);\n\tcbBmpBits = biTrueWidth * pix->height * pixel_size;\n\tcbPalBytes = ( pixel_size == 1 ) ? 256 * sizeof( rgba_t ) : 0;\n\n\t// Bogus file header check\n\thdr.id[0] = 'B';\n\thdr.id[1] = 'M';\n\thdr.fileSize =  sizeof( hdr ) + cbBmpBits + cbPalBytes;\n\thdr.reserved0 = 0;\n\thdr.bitmapDataOffset = sizeof( hdr ) + cbPalBytes;\n\thdr.bitmapHeaderSize = BI_SIZE;\n\thdr.width = biTrueWidth;\n\thdr.height = pix->height;\n\thdr.planes = 1;\n\thdr.bitsPerPixel = pixel_size * 8;\n\thdr.compression = BI_RGB;\n\thdr.bitmapDataSize = cbBmpBits;\n\thdr.hRes = 0;\n\thdr.vRes = 0;\n\thdr.colors = ( pixel_size == 1 ) ? 256 : 0;\n\thdr.importantColors = 0;\n\n\tFS_Write( pfile, &hdr, sizeof( bmp_t ));\n\n\tpbBmpBits = Mem_Malloc( host.imagepool, cbBmpBits );\n\n\tif( pixel_size == 1 )\n\t{\n\t\tpb = pix->palette;\n\n\t\t// copy over used entries\n\t\tfor( i = 0; i < (int)hdr.colors; i++ )\n\t\t{\n\t\t\trgrgbPalette[i][2] = *pb++;\n\t\t\trgrgbPalette[i][1] = *pb++;\n\t\t\trgrgbPalette[i][0] = *pb++;\n\n\t\t\t// bmp feature - can store 32-bit palette if present\n\t\t\t// some viewers e.g. fimg.exe can show alpha-chanell for it\n\t\t\tif( pix->type == PF_INDEXED_32 )\n\t\t\t\trgrgbPalette[i][3] = *pb++;\n\t\t\telse rgrgbPalette[i][3] = 0;\n\t\t}\n\n\t\t// write palette\n\t\tFS_Write( pfile, rgrgbPalette, cbPalBytes );\n\t}\n\n\tpb = pix->buffer;\n\n\tfor( y = 0; y < hdr.height; y++ )\n\t{\n\t\ti = (hdr.height - 1 - y ) * (hdr.width);\n\n\t\tfor( x = 0; x < pix->width; x++ )\n\t\t{\n\t\t\tif( pixel_size == 1 )\n\t\t\t{\n\t\t\t\t// 8-bit\n\t\t\t\tpbBmpBits[i] = pb[x];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// 24 bit\n\t\t\t\tpbBmpBits[i*pixel_size+0] = pb[x*pixel_size+2];\n\t\t\t\tpbBmpBits[i*pixel_size+1] = pb[x*pixel_size+1];\n\t\t\t\tpbBmpBits[i*pixel_size+2] = pb[x*pixel_size+0];\n\t\t\t}\n\n\t\t\tif( pixel_size == 4 ) // write alpha channel\n\t\t\t\tpbBmpBits[i*pixel_size+3] = pb[x*pixel_size+3];\n\t\t\ti++;\n\t\t}\n\n\t\tpb += pix->width * pixel_size;\n\t}\n\n\t// write bitmap bits (remainder of file)\n\tFS_Write( pfile, pbBmpBits, cbBmpBits );\n\tFS_Close( pfile );\n\n\tMem_Free( pbBmpBits );\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_bmp.h",
    "content": "/*\nimg_bmp.h - bmp format reference\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef IMG_BMP_H\n#define IMG_BMP_H\n/*\n========================================================================\n\n.BMP image format\n\n========================================================================\n*/\n\n#define BI_FILE_HEADER_SIZE 14\n#define BI_SIZE 40 // size of bitmap info header.\n#if !defined(BI_RGB)\n#define BI_RGB\t\t  0\t// uncompressed RGB bitmap (defined in wingdi.h)\n#define BI_RLE8       1\n#define BI_RLE4       2\n#define BI_BITFIELDS  3\n#define BI_JPEG       4\n#define BI_PNG        5\n#endif\n\n#pragma pack( push, 1 )\ntypedef struct\n{\n\tint8_t   id[2];            // bmfh.bfType\n\tuint32_t fileSize;         // bmfh.bfSize\n\tuint32_t reserved0;        // bmfh.bfReserved1 + bmfh.bfReserved2\n\tuint32_t bitmapDataOffset; // bmfh.bfOffBits\n\tuint32_t bitmapHeaderSize; // bmih.biSize\n\tint32_t  width;            // bmih.biWidth\n\tint32_t  height;           // bmih.biHeight\n\tuint16_t planes;           // bmih.biPlanes\n\tuint16_t bitsPerPixel;     // bmih.biBitCount\n\tuint32_t compression;      // bmih.biCompression\n\tuint32_t bitmapDataSize;   // bmih.biSizeImage\n\tuint32_t hRes;             // bmih.biXPelsPerMeter\n\tuint32_t vRes;             // bmih.biYPelsPerMeter\n\tuint32_t colors;           // bmih.biClrUsed\n\tuint32_t importantColors;  // bmih.biClrImportant\n} bmp_t;\n#pragma pack( pop )\n#endif // IMG_BMP_H\n\n"
  },
  {
    "path": "engine/common/imagelib/img_dds.c",
    "content": "/*\nimg_dds.c - dds format load\nCopyright (C) 2015 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"img_dds.h\"\n\nstatic qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin )\n{\n\tword\tsAlpha;\n\tbyte\t*alpha;\n\tint\tx, y, i, j;\n\n\tfor( y = 0; y < hdr->dwHeight; y += 4 )\n\t{\n\t\tfor( x = 0; x < hdr->dwWidth; x += 4 )\n\t\t{\n\t\t\talpha = fin + 8;\n\t\t\tfin += 16;\n\n\t\t\tfor( j = 0; j < 4; j++ )\n\t\t\t{\n\t\t\t\tsAlpha = alpha[2*j] + 256 * alpha[2*j+1];\n\n\t\t\t\tfor( i = 0; i < 4; i++ )\n\t\t\t\t{\n\t\t\t\t\tif((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight ))\n\t\t\t\t\t{\n\t\t\t\t\t\tif( sAlpha == 0 )\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tsAlpha >>= 4;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin )\n{\n\tuint\tbits, bitmask;\n\tbyte\t*alphamask;\n\tint\tx, y, i, j;\n\n\tfor( y = 0; y < hdr->dwHeight; y += 4 )\n\t{\n\t\tfor( x = 0; x < hdr->dwWidth; x += 4 )\n\t\t{\n\t\t\tif( y >= hdr->dwHeight || x >= hdr->dwWidth )\n\t\t\t\tbreak;\n\n\t\t\talphamask = fin + 2;\n\t\t\tfin += 8;\n\n\t\t\tbitmask = ((uint *)fin)[1];\n\t\t\tfin += 8;\n\n\t\t\t// last three bytes\n\t\t\tbits = (alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16);\n\n\t\t\tfor( j = 2; j < 4; j++ )\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 4; i++ )\n\t\t\t\t{\n\t\t\t\t\t// only put pixels out < width or height\n\t\t\t\t\tif((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight ))\n\t\t\t\t\t{\n\t\t\t\t\t\tif( bits & 0x07 )\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\tbits >>= 3;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt )\n{\n\tuint bits = hdr->dsPixelFormat.dwRGBBitCount;\n\n\tif( !FBitSet( hdr->dsCaps.dwCaps2, DDS_VOLUME ))\n\t\thdr->dwDepth = 1;\n\n\tif( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC ))\n\t{\n\t\tif( hdr->dsPixelFormat.dwFourCC == TYPE_DX10 )\n\t\t{\n\t\t\tswitch( headerExt->dxgiFormat )\n\t\t\t{\n\t\t\tcase DXGI_FORMAT_BC4_TYPELESS:\n\t\t\tcase DXGI_FORMAT_BC4_UNORM:\n\t\t\t\timage.type = PF_BC4_UNSIGNED;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC4_SNORM:\n\t\t\t\timage.type = PF_BC4_SIGNED;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC6H_SF16:\n\t\t\t\timage.type = PF_BC6H_SIGNED;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC6H_UF16:\n\t\t\tcase DXGI_FORMAT_BC6H_TYPELESS:\n\t\t\t\timage.type = PF_BC6H_UNSIGNED;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC7_UNORM:\n\t\t\tcase DXGI_FORMAT_BC7_TYPELESS:\n\t\t\t\timage.type = PF_BC7_UNORM;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC7_UNORM_SRGB:\n\t\t\t\timage.type = PF_BC7_SRGB;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC5_TYPELESS:\n\t\t\t\timage.type = PF_ATI2;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC5_UNORM:\n\t\t\t\timage.type = PF_BC5_UNSIGNED;\n\t\t\t\tbreak;\n\t\t\tcase DXGI_FORMAT_BC5_SNORM:\n\t\t\t\timage.type = PF_BC5_SIGNED;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\timage.type = PF_UNKNOWN;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch( hdr->dsPixelFormat.dwFourCC )\n\t\t\t{\n\t\t\tcase TYPE_DXT1:\n\t\t\t\timage.type = PF_DXT1;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_DXT2:\n\t\t\t\timage.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color\n\t\t\t\t// intentionally fallthrough\n\t\t\tcase TYPE_DXT3:\n\t\t\t\timage.type = PF_DXT3;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_DXT4:\n\t\t\t\timage.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color\n\t\t\t\t// intentionally fallthrough\n\t\t\tcase TYPE_DXT5:\n\t\t\t\timage.type = PF_DXT5;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_ATI2:\n\t\t\t\timage.type = PF_ATI2;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_BC5S:\n\t\t\t\timage.type = PF_BC5_SIGNED;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_BC4S:\n\t\t\t\timage.type = PF_BC4_SIGNED;\n\t\t\t\tbreak;\n\t\t\tcase TYPE_BC4U:\n\t\t\t\timage.type = PF_BC4_UNSIGNED;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\timage.type = PF_UNKNOWN; // assume error\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\t// this dds texture isn't compressed so write out ARGB or luminance format\n\t\tif( hdr->dsPixelFormat.dwFlags & DDS_DUDV )\n\t\t{\n\t\t\timage.type = PF_UNKNOWN; // assume error\n\t\t}\n\t\telse if( hdr->dsPixelFormat.dwFlags & DDS_LUMINANCE )\n\t\t{\n\t\t\timage.type = PF_UNKNOWN; // assume error\n\t\t}\n\t\telse\n\t\t{\n\t\t\tswitch( bits )\n\t\t\t{\n\t\t\tcase 32:\n\t\t\t\timage.type = PF_BGRA_32;\n\t\t\t\tbreak;\n\t\t\tcase 24:\n\t\t\t\timage.type = PF_BGR_24;\n\t\t\t\tbreak;\n\t\t\tcase 8:\n\t\t\t\timage.type = PF_LUMINANCE;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\timage.type = PF_UNKNOWN;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// setup additional flags\n\tif( hdr->dsCaps.dwCaps1 & DDS_COMPLEX && hdr->dsCaps.dwCaps2 & DDS_CUBEMAP )\n\t\timage.flags |= IMAGE_CUBEMAP;\n\n\tif( hdr->dwFlags & DDS_MIPMAPCOUNT )\n\t\timage.num_mips = hdr->dwMipMapCount; // get actual mip count\n}\n\nstatic size_t Image_DXTCalcMipmapSize( dds_t *hdr )\n{\n\tsize_t\tbuffsize = 0;\n\tint\ti, width, height;\n\n\t// now correct buffer size\n\tfor( i = 0; i < Q_max( 1, ( hdr->dwMipMapCount )); i++ )\n\t{\n\t\twidth = Q_max( 1, ( hdr->dwWidth >> i ));\n\t\theight = Q_max( 1, ( hdr->dwHeight >> i ));\n\t\tbuffsize += Image_ComputeSize( image.type, width, height, image.depth );\n\t}\n\n\treturn buffsize;\n}\n\nstatic uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize )\n{\n\tsize_t buffsize = 0;\n\tint w = image.width;\n\tint h = image.height;\n\tint d = image.depth;\n\n\tif( hdr->dsCaps.dwCaps2 & DDS_CUBEMAP )\n\t{\n\t\t// cubemap w*h always match for all sides\n\t\tbuffsize = Image_DXTCalcMipmapSize( hdr ) * 6;\n\t}\n\telse if( hdr->dwFlags & DDS_MIPMAPCOUNT )\n\t{\n\t\t// if mipcount > 1\n\t\tbuffsize = Image_DXTCalcMipmapSize( hdr );\n\t}\n\telse if( hdr->dwFlags & ( DDS_LINEARSIZE|DDS_PITCH ))\n\t{\n\t\t// just in case (no need, really)\n\t\tbuffsize = hdr->dwLinearSize;\n\t}\n\telse\n\t{\n\t\t// pretty solution for microsoft bug\n\t\tbuffsize = Image_DXTCalcMipmapSize( hdr );\n\t}\n\n\tif( filesize != buffsize ) // main check\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: (%s) probably corrupted (%zu should be %zu)\\n\", __func__, name, buffsize, filesize );\n\t\tif( buffsize > filesize )\n\t\t\treturn false;\n\t}\n\n\treturn buffsize;\n}\n\nstatic void Image_DXTAdjustVolume( dds_t *hdr )\n{\n\tif( hdr->dwDepth <= 1 )\n\t\treturn;\n\n\thdr->dwLinearSize = Image_ComputeSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth );\n\thdr->dwFlags |= DDS_LINEARSIZE;\n}\n\n/*\n=============\nImage_LoadDDS\n=============\n*/\nqboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tdds_t\theader;\n\tbyte\t*fin;\n\tint\t\theadersOffset;\n\tdds_header_dxt10_t header2;\n\n\tif( filesize < sizeof( header ))\n\t\treturn false;\n\n\tmemcpy( &header, buffer, sizeof( header ));\n\n\tif( header.dwIdent != DDSHEADER )\n\t\treturn false; // it's not a dds file, just skip it\n\n\tif( header.dwSize != sizeof( header ) - sizeof( uint )) // size of the structure (minus MagicNum)\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) have corrupted header\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) have corrupt pixelformat header\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\theadersOffset = sizeof( header );\n\tif( header.dsPixelFormat.dwFourCC == TYPE_DX10 )\n\t{\n\t\tmemcpy( &header2, buffer + sizeof( header ), sizeof( header2 ));\n\t\theadersOffset += sizeof( header2 );\n\t}\n\n\timage.width = header.dwWidth;\n\timage.height = header.dwHeight;\n\n\tif( header.dwFlags & DDS_DEPTH )\n\t\timage.depth = header.dwDepth;\n\telse image.depth = 1;\n\n\tif( !Image_ValidSize( name )) return false;\n\n\tImage_DXTGetPixelFormat( &header, &header2 ); // and image type too :)\n\tImage_DXTAdjustVolume( &header );\n\n\tif( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type ))\n\t\treturn false; // silently rejected\n\n\tif( image.type == PF_UNKNOWN )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) has unrecognized type\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\timage.size = Image_DXTCalcSize( name, &header, filesize - headersOffset );\n\tif( image.size == 0 ) return false; // just in case\n\tfin = (byte *)( buffer + headersOffset );\n\n\t// copy an encode method\n\timage.encode = (word)header.dwReserved1[0];\n\n\tswitch( image.encode )\n\t{\n\tcase DXT_ENCODE_COLOR_YCoCg:\n\t\tSetBits( image.flags, IMAGE_HAS_COLOR );\n\t\tbreak;\n\tcase DXT_ENCODE_NORMAL_AG_ORTHO:\n\tcase DXT_ENCODE_NORMAL_AG_STEREO:\n\tcase DXT_ENCODE_NORMAL_AG_PARABOLOID:\n\tcase DXT_ENCODE_NORMAL_AG_QUARTIC:\n\tcase DXT_ENCODE_NORMAL_AG_AZIMUTHAL:\n\t\tSetBits( image.flags, IMAGE_HAS_COLOR );\n\t\tbreak;\n\tdefault:\t// check for real alpha-pixels\n\t\tif( image.type == PF_DXT3 && Image_CheckDXT3Alpha( &header, fin ))\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\telse if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin ))\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\telse if( image.type == PF_BC5_SIGNED || image.type == PF_BC5_UNSIGNED )\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\telse if( image.type == PF_BC7_UNORM || image.type == PF_BC7_SRGB )\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\tif( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE ))\n\t\t\tSetBits( image.flags, IMAGE_HAS_COLOR );\n\t\tbreak;\n\t}\n\n\tif( image.type == PF_LUMINANCE )\n\t\tClearBits( image.flags, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA );\n\n\tif( header.dwReserved1[1] != 0 )\n\t{\n\t\t// store texture reflectivity\n\t\timage.fogParams[0] = ((header.dwReserved1[1] & 0x000000FF) >> 0 );\n\t\timage.fogParams[1] = ((header.dwReserved1[1] & 0x0000FF00) >> 8 );\n\t\timage.fogParams[2] = ((header.dwReserved1[1] & 0x00FF0000) >> 16);\n\t\timage.fogParams[3] = ((header.dwReserved1[1] & 0xFF000000) >> 24);\n\t}\n\n\t// dds files will be uncompressed on a render. requires minimal of info for set this\n\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\tmemcpy( image.rgba, fin, image.size );\n\tSetBits( image.flags, IMAGE_DDS_FORMAT );\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_dds.h",
    "content": "/*\nimg_dds.h - dds format reference\nCopyright (C) 2015 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef IMG_DDS_H\n#define IMG_DDS_H\n/*\n========================================================================\n\n.DDS image format\n\n========================================================================\n*/\n#define DDSHEADER\t((' '<<24)+('S'<<16)+('D'<<8)+'D') // little-endian \"DDS \"\n\n// various four-cc types\n#define TYPE_DXT1\t(('1'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian \"DXT1\"\n#define TYPE_DXT2\t(('2'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian \"DXT2\"\n#define TYPE_DXT3\t(('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian \"DXT3\"\n#define TYPE_DXT4\t(('4'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian \"DXT4\"\n#define TYPE_DXT5\t(('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian \"DXT5\"\n#define TYPE_DX10\t(('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian \"DX10\"\n#define TYPE_ATI1\t(('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian \"ATI1\"\n#define TYPE_ATI2\t(('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian \"ATI2\"\n#define TYPE_BC5S\t(('S'<<24)+('5'<<16)+('C'<<8)+'B') // little-endian \"BC5S\"\n#define TYPE_BC4S\t(('S'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian \"BC4S\"\n#define TYPE_BC4U\t(('U'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian \"BC4U\"\n#define TYPE_RXGB\t(('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian \"RXGB\" doom3 normalmaps\n#define TYPE_$\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'$') // little-endian \"$\"\n#define TYPE_o\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'o') // little-endian \"o\"\n#define TYPE_p\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'p') // little-endian \"p\"\n#define TYPE_q\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'q') // little-endian \"q\"\n#define TYPE_r\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'r') // little-endian \"r\"\n#define TYPE_s\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'s') // little-endian \"s\"\n#define TYPE_t\t(('\\0'<<24)+('\\0'<<16)+('\\0'<<8)+'t') // little-endian \"t\"\n\n// dwFlags1\n#define DDS_CAPS\t\t\t\t0x00000001L\n#define DDS_HEIGHT\t\t\t\t0x00000002L\n#define DDS_WIDTH\t\t\t\t0x00000004L\n#define DDS_PITCH\t\t\t\t0x00000008L\n#define DDS_PIXELFORMAT\t\t\t0x00001000L\n#define DDS_MIPMAPCOUNT\t\t\t0x00020000L\n#define DDS_LINEARSIZE\t\t\t0x00080000L\n#define DDS_DEPTH\t\t\t\t0x00800000L\n\n// dwFlags2\n#define DDS_ALPHAPIXELS\t\t\t0x00000001L\n#define DDS_ALPHA\t\t\t\t0x00000002L\n#define DDS_FOURCC\t\t\t\t0x00000004L\n#define DDS_RGB\t\t\t\t0x00000040L\n#define DDS_RGBA\t\t\t\t0x00000041L\t// (DDS_RGB|DDS_ALPHAPIXELS)\n#define DDS_LUMINANCE\t\t\t0x00020000L\n#define DDS_DUDV\t\t\t\t0x00080000L\n\n// dwCaps1\n#define DDS_COMPLEX\t\t\t\t0x00000008L\n#define DDS_TEXTURE\t\t\t\t0x00001000L\n#define DDS_MIPMAP\t\t\t\t0x00400000L\n\n// dwCaps2\n#define DDS_CUBEMAP\t\t\t\t0x00000200L\n#define DDS_CUBEMAP_POSITIVEX\t\t\t0x00000400L\n#define DDS_CUBEMAP_NEGATIVEX\t\t\t0x00000800L\n#define DDS_CUBEMAP_POSITIVEY\t\t\t0x00001000L\n#define DDS_CUBEMAP_NEGATIVEY\t\t\t0x00002000L\n#define DDS_CUBEMAP_POSITIVEZ\t\t\t0x00004000L\n#define DDS_CUBEMAP_NEGATIVEZ\t\t\t0x00008000L\n#define DDS_CUBEMAP_ALL_SIDES\t\t\t0x0000FC00L\n#define DDS_VOLUME\t\t\t\t0x00200000L\n\ntypedef enum\n{\n\tDXGI_FORMAT_UNKNOWN = 0,\n\tDXGI_FORMAT_R32G32B32A32_TYPELESS = 1,\n\tDXGI_FORMAT_R32G32B32A32_FLOAT = 2,\n\tDXGI_FORMAT_R32G32B32A32_UINT = 3,\n\tDXGI_FORMAT_R32G32B32A32_SINT = 4,\n\tDXGI_FORMAT_R32G32B32_TYPELESS = 5,\n\tDXGI_FORMAT_R32G32B32_FLOAT = 6,\n\tDXGI_FORMAT_R32G32B32_UINT = 7,\n\tDXGI_FORMAT_R32G32B32_SINT = 8,\n\tDXGI_FORMAT_R16G16B16A16_TYPELESS = 9,\n\tDXGI_FORMAT_R16G16B16A16_FLOAT = 10,\n\tDXGI_FORMAT_R16G16B16A16_UNORM = 11,\n\tDXGI_FORMAT_R16G16B16A16_UINT = 12,\n\tDXGI_FORMAT_R16G16B16A16_SNORM = 13,\n\tDXGI_FORMAT_R16G16B16A16_SINT = 14,\n\tDXGI_FORMAT_R32G32_TYPELESS = 15,\n\tDXGI_FORMAT_R32G32_FLOAT = 16,\n\tDXGI_FORMAT_R32G32_UINT = 17,\n\tDXGI_FORMAT_R32G32_SINT = 18,\n\tDXGI_FORMAT_R32G8X24_TYPELESS = 19,\n\tDXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20,\n\tDXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21,\n\tDXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22,\n\tDXGI_FORMAT_R10G10B10A2_TYPELESS = 23,\n\tDXGI_FORMAT_R10G10B10A2_UNORM = 24,\n\tDXGI_FORMAT_R10G10B10A2_UINT = 25,\n\tDXGI_FORMAT_R11G11B10_FLOAT = 26,\n\tDXGI_FORMAT_R8G8B8A8_TYPELESS = 27,\n\tDXGI_FORMAT_R8G8B8A8_UNORM = 28,\n\tDXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29,\n\tDXGI_FORMAT_R8G8B8A8_UINT = 30,\n\tDXGI_FORMAT_R8G8B8A8_SNORM = 31,\n\tDXGI_FORMAT_R8G8B8A8_SINT = 32,\n\tDXGI_FORMAT_R16G16_TYPELESS = 33,\n\tDXGI_FORMAT_R16G16_FLOAT = 34,\n\tDXGI_FORMAT_R16G16_UNORM = 35,\n\tDXGI_FORMAT_R16G16_UINT = 36,\n\tDXGI_FORMAT_R16G16_SNORM = 37,\n\tDXGI_FORMAT_R16G16_SINT = 38,\n\tDXGI_FORMAT_R32_TYPELESS = 39,\n\tDXGI_FORMAT_D32_FLOAT = 40,\n\tDXGI_FORMAT_R32_FLOAT = 41,\n\tDXGI_FORMAT_R32_UINT = 42,\n\tDXGI_FORMAT_R32_SINT = 43,\n\tDXGI_FORMAT_R24G8_TYPELESS = 44,\n\tDXGI_FORMAT_D24_UNORM_S8_UINT = 45,\n\tDXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46,\n\tDXGI_FORMAT_X24_TYPELESS_G8_UINT = 47,\n\tDXGI_FORMAT_R8G8_TYPELESS = 48,\n\tDXGI_FORMAT_R8G8_UNORM = 49,\n\tDXGI_FORMAT_R8G8_UINT = 50,\n\tDXGI_FORMAT_R8G8_SNORM = 51,\n\tDXGI_FORMAT_R8G8_SINT = 52,\n\tDXGI_FORMAT_R16_TYPELESS = 53,\n\tDXGI_FORMAT_R16_FLOAT = 54,\n\tDXGI_FORMAT_D16_UNORM = 55,\n\tDXGI_FORMAT_R16_UNORM = 56,\n\tDXGI_FORMAT_R16_UINT = 57,\n\tDXGI_FORMAT_R16_SNORM = 58,\n\tDXGI_FORMAT_R16_SINT = 59,\n\tDXGI_FORMAT_R8_TYPELESS = 60,\n\tDXGI_FORMAT_R8_UNORM = 61,\n\tDXGI_FORMAT_R8_UINT = 62,\n\tDXGI_FORMAT_R8_SNORM = 63,\n\tDXGI_FORMAT_R8_SINT = 64,\n\tDXGI_FORMAT_A8_UNORM = 65,\n\tDXGI_FORMAT_R1_UNORM = 66,\n\tDXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67,\n\tDXGI_FORMAT_R8G8_B8G8_UNORM = 68,\n\tDXGI_FORMAT_G8R8_G8B8_UNORM = 69,\n\tDXGI_FORMAT_BC1_TYPELESS = 70,\n\tDXGI_FORMAT_BC1_UNORM = 71,\n\tDXGI_FORMAT_BC1_UNORM_SRGB = 72,\n\tDXGI_FORMAT_BC2_TYPELESS = 73,\n\tDXGI_FORMAT_BC2_UNORM = 74,\n\tDXGI_FORMAT_BC2_UNORM_SRGB = 75,\n\tDXGI_FORMAT_BC3_TYPELESS = 76,\n\tDXGI_FORMAT_BC3_UNORM = 77,\n\tDXGI_FORMAT_BC3_UNORM_SRGB = 78,\n\tDXGI_FORMAT_BC4_TYPELESS = 79,\n\tDXGI_FORMAT_BC4_UNORM = 80,\n\tDXGI_FORMAT_BC4_SNORM = 81,\n\tDXGI_FORMAT_BC5_TYPELESS = 82,\n\tDXGI_FORMAT_BC5_UNORM = 83,\n\tDXGI_FORMAT_BC5_SNORM = 84,\n\tDXGI_FORMAT_B5G6R5_UNORM = 85,\n\tDXGI_FORMAT_B5G5R5A1_UNORM = 86,\n\tDXGI_FORMAT_B8G8R8A8_UNORM = 87,\n\tDXGI_FORMAT_B8G8R8X8_UNORM = 88,\n\tDXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89,\n\tDXGI_FORMAT_B8G8R8A8_TYPELESS = 90,\n\tDXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91,\n\tDXGI_FORMAT_B8G8R8X8_TYPELESS = 92,\n\tDXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93,\n\tDXGI_FORMAT_BC6H_TYPELESS = 94,\n\tDXGI_FORMAT_BC6H_UF16 = 95,\n\tDXGI_FORMAT_BC6H_SF16 = 96,\n\tDXGI_FORMAT_BC7_TYPELESS = 97,\n\tDXGI_FORMAT_BC7_UNORM = 98,\n\tDXGI_FORMAT_BC7_UNORM_SRGB = 99,\n\tDXGI_FORMAT_AYUV = 100,\n\tDXGI_FORMAT_Y410 = 101,\n\tDXGI_FORMAT_Y416 = 102,\n\tDXGI_FORMAT_NV12 = 103,\n\tDXGI_FORMAT_P010 = 104,\n\tDXGI_FORMAT_P016 = 105,\n\tDXGI_FORMAT_420_OPAQUE = 106,\n\tDXGI_FORMAT_YUY2 = 107,\n\tDXGI_FORMAT_Y210 = 108,\n\tDXGI_FORMAT_Y216 = 109,\n\tDXGI_FORMAT_NV11 = 110,\n\tDXGI_FORMAT_AI44 = 111,\n\tDXGI_FORMAT_IA44 = 112,\n\tDXGI_FORMAT_P8 = 113,\n\tDXGI_FORMAT_A8P8 = 114,\n\tDXGI_FORMAT_B4G4R4A4_UNORM = 115,\n\tDXGI_FORMAT_P208 = 130,\n\tDXGI_FORMAT_V208 = 131,\n\tDXGI_FORMAT_V408 = 132,\n\tDXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE,\n\tDXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE,\n\tDXGI_FORMAT_FORCE_UINT = 0xffffffff\n} dxgi_format_t;\n\ntypedef enum\n{\n\tD3D10_RESOURCE_DIMENSION_UNKNOWN = 0,\n\tD3D10_RESOURCE_DIMENSION_BUFFER = 1,\n\tD3D10_RESOURCE_DIMENSION_TEXTURE1D = 2,\n\tD3D10_RESOURCE_DIMENSION_TEXTURE2D = 3,\n\tD3D10_RESOURCE_DIMENSION_TEXTURE3D = 4\n} dds_resource_dimension_t;\n\ntypedef struct dds_pf_s\n{\n\tuint32_t    dwSize;\n\tuint32_t    dwFlags;\n\tuint32_t    dwFourCC;\n\tuint32_t    dwRGBBitCount;\n\tuint32_t    dwRBitMask;\n\tuint32_t    dwGBitMask;\n\tuint32_t    dwBBitMask;\n\tuint32_t    dwABitMask;\n} dds_pixf_t;\n\n//  DDCAPS2\ntypedef struct dds_caps_s\n{\n\tuint32_t    dwCaps1;\n\tuint32_t    dwCaps2;\n\tuint32_t    dwCaps3;\t\t\t// currently unused\n\tuint32_t    dwCaps4;\t\t\t// currently unused\n} dds_caps_t;\n\ntypedef struct dds_header_dxt10_s\n{\n\tdxgi_format_t dxgiFormat;\n\tdds_resource_dimension_t resourceDimension;\n\tuint32_t    miscFlag;\n\tuint32_t    arraySize;\n\tuint32_t    miscFlags2;\n} dds_header_dxt10_t;\n\ntypedef struct dds_s\n{\n\tuint32_t    dwIdent;\t\t// must matched with DDSHEADER\n\tuint32_t    dwSize;\n\tuint32_t    dwFlags;\t\t// determines what fields are valid\n\tuint32_t    dwHeight;\n\tuint32_t    dwWidth;\n\tuint32_t    dwLinearSize;\t// Formless late-allocated optimized surface size\n\tuint32_t    dwDepth;\t\t// depth if a volume texture\n\tuint32_t    dwMipMapCount;\t// number of mip-map levels requested\n\tuint32_t    dwAlphaBitDepth;\t// depth of alpha buffer requested\n\tuint32_t    dwReserved1[10];\t// reserved for future expansions\n\tdds_pixf_t  dsPixelFormat;\n\tdds_caps_t  dsCaps;\n\tuint32_t    dwTextureStage;\n} dds_t;\n#endif // IMG_DDS_H\n\n"
  },
  {
    "path": "engine/common/imagelib/img_ktx2.c",
    "content": "/*\nimg_ktx2.c - ktx2 format load\nCopyright (C) 2023 Provod\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"img_ktx2.h\"\n\nstatic void Image_KTX2Format( uint32_t ktx2_format )\n{\n\tswitch( ktx2_format )\n\t{\n\t\tcase KTX2_FORMAT_BC4_UNORM_BLOCK:\n\t\t\timage.type = PF_BC4_UNSIGNED;\n\t\t\t// 1 component for ref_gl\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC4_SNORM_BLOCK:\n\t\t\timage.type = PF_BC4_SIGNED;\n\t\t\t// 1 component for ref_gl\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC5_UNORM_BLOCK:\n\t\t\timage.type = PF_BC5_UNSIGNED;\n\t\t\t// 2 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC5_SNORM_BLOCK:\n\t\t\timage.type = PF_BC5_SIGNED;\n\t\t\t// 2 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC6H_UFLOAT_BLOCK:\n\t\t\timage.type = PF_BC6H_UNSIGNED;\n\t\t\t// 3 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_COLOR );\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC6H_SFLOAT_BLOCK:\n\t\t\timage.type = PF_BC6H_SIGNED;\n\t\t\t// 3 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_COLOR );\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC7_UNORM_BLOCK:\n\t\t\timage.type = PF_BC7_UNORM;\n\t\t\t// 4 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA );\n\t\t\tbreak;\n\t\tcase KTX2_FORMAT_BC7_SRGB_BLOCK:\n\t\t\timage.type = PF_BC7_SRGB;\n\t\t\t// 4 components for ref_gl\n\t\t\tSetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\timage.type = PF_UNKNOWN;\n\t\t\tbreak;\n\t}\n}\n\nstatic const int g_remap_cube_layer[6] = {\n/* Face order\n 0   1   2   3   4   5  -- index\nft, bk, up, dn, rt, lf  -- xash\n+x, -x, +y, -y, +z, -z  -- KTX2\nrt, lf, bk, ft, up, dn  -- ref_vk\n\ntexture[face] = ktx2[map[face]], e.g.:\ntexture[rt] = ktx2[+z = 4]\ntexture[lf] = ktx2[-z = 5]\ntexture[bk] = ktx2[-x = 1]\n...\n*/\n\t4, 5, 1, 0, 2, 3\n};\n\nstatic qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer, fs_offset_t filesize )\n{\n\tktx2_index_t index;\n\tsize_t total_size = 0;\n\tsize_t max_offset = 0;\n\tint mip;\n\tconst byte *const levels_begin = buffer + KTX2_LEVELS_OFFSET;\n\n\t// Sets image.type and image.flags\n\tImage_KTX2Format( header->vkFormat );\n\n\tif( image.type == PF_UNKNOWN )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unsupported KTX2 format %d\\n\", __func__, header->vkFormat );\n\t\treturn false;\n\t}\n\n\tif( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type ))\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: has compressed format, but support is not advertized\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tif( header->levelCount == 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: file has no mip levels\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tif( header->pixelDepth > 1 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unsupported KTX2 pixelDepth %d\\n\", __func__, header->pixelDepth );\n\t\treturn false;\n\t}\n\n\tif( header->faceCount != 1 && header->faceCount != 6 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unsupported KTX2 faceCount %d\\n\", __func__, header->faceCount );\n\t\treturn false;\n\t}\n\n\tif( header->layerCount > 1 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unsupported KTX2 layerCount %d\\n\", __func__, header->layerCount );\n\t\treturn false;\n\t}\n\n\tif( header->supercompressionScheme != 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unsupported KTX2 supercompressionScheme %d\\n\", __func__, header->supercompressionScheme );\n\t\treturn false;\n\t}\n\n\tif( header->levelCount * sizeof( ktx2_level_t ) + KTX2_LEVELS_OFFSET > filesize )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: file abruptly ends\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tmemcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index ));\n\n\tfor( mip = 0; mip < header->levelCount; ++mip )\n\t{\n\t\tconst uint32_t width = Q_max( 1, ( header->pixelWidth >> mip ));\n\t\tconst uint32_t height = Q_max( 1, ( header->pixelHeight >> mip ));\n\t\tconst uint32_t mip_size = Image_ComputeSize( image.type, width, height, image.depth );\n\n\t\tktx2_level_t level;\n\t\tmemcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));\n\n\t\tif( mip_size * header->faceCount != level.byteLength )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: mip=%d size mismatch read=%d, but computed=%d(mip=%d * faces=%d)\\n\",\n\t\t\t\t__func__, mip, (int)level.byteLength, mip_size * header->faceCount, mip_size, header->faceCount );\n\t\t\treturn false;\n\t\t}\n\n\t\ttotal_size += level.byteLength;\n\t\tmax_offset = Q_max( max_offset, level.byteLength + level.byteOffset );\n\t}\n\n\tif( max_offset > filesize ) {\n\t\tCon_DPrintf( S_ERROR \"%s: size to read %d exceeds file size %d\\n\",\n\t\t\t__FUNCTION__, (int)max_offset, (int)filesize );\n\t\treturn false;\n\t}\n\n\timage.size = total_size;\n\timage.num_mips = header->levelCount;\n\n\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\tmemcpy( image.rgba, buffer, image.size );\n\n\tfor( mip = 0; mip < header->levelCount; ++mip )\n\t{\n\t\tint cursors[6] = {0};\n\t\tif ( header->faceCount == 6 ) {\n\t\t\timage.flags |= IMAGE_CUBEMAP;\n\n\t\t\tfor ( int face = 0; face < header->faceCount; ++face )\n\t\t\t\tcursors[face] = g_remap_cube_layer[face] * total_size / header->faceCount;\n\t\t}\n\n\t\tfor( int mip = 0; mip < header->levelCount; ++mip )\n\t\t{\n\t\t\tktx2_level_t level;\n\t\t\tint face_size = 0;\n\n\t\t\tmemcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));\n\t\t\tface_size = level.byteLength / header->faceCount;\n\n\t\t\tfor ( int face = 0; face < header->faceCount; ++face )\n\t\t\t{\n\t\t\t\tmemcpy( image.rgba + cursors[face], buffer + level.byteOffset + face * face_size, face_size );\n\t\t\t\tcursors[face] += face_size;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nqboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tktx2_header_t header;\n\n\tif( filesize < KTX2_MINIMAL_HEADER_SIZE )\n\t\treturn false;\n\n\tif( memcmp( buffer, KTX2_IDENTIFIER, KTX2_IDENTIFIER_SIZE ) != 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) has invalid identifier\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tmemcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header ));\n\n\timage.width = header.pixelWidth;\n\timage.height = header.pixelHeight;\n\timage.depth = Q_max( 1, header.pixelDepth );\n\timage.num_mips = 1;\n\n\tClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA | IMAGE_CUBEMAP );\n\n\tif( !Image_KTX2Parse( &header, buffer, filesize ))\n\t{\n\t\tif( !Image_CheckFlag( IL_KTX2_RAW ))\n\t\t\treturn false;\n\n\t\t// If KTX2 to imagelib conversion failed, try passing the file as raw data.\n\t\t// This is useful for ref_vk which can directly support hundreds of formats which we don't convert to pixformat_t here\n\n\t\tCon_DPrintf( S_WARN \"%s: (%s) could not be converted to supported imagelib format, passing as raw KTX2 data\\n\", __func__, name );\n\t\t// This is a catch-all for ref_vk, which can do this format directly and natively\n\t\timage.type = PF_KTX2_RAW;\n\n\t\timage.size = filesize;\n\t\t//image.encode = TODO custom encode type?\n\n\t\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\t\tmemcpy( image.rgba, buffer, image.size );\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_ktx2.h",
    "content": "/*\nimg_ktx2.h - ktx2 format reference\nCopyright (C) 2023 Provod\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef IMG_KTX2_H\n#define IMG_KTX2_H\n\n#include <stdint.h>\n\n#define KTX2_IDENTIFIER_SIZE 12\n#define KTX2_IDENTIFIER \"\\xABKTX 20\\xBB\\r\\n\\x1A\\n\"\n\n/*\nstatic const char k_ktx2_identifier[KTX2_IDENTIFIER_SIZE] =\n{\n  '\\xAB', 'K', 'T', 'X', ' ', '2', '0', '\\xBB', '\\r', '\\n', '\\x1A', '\\n'\n};\n*/\n\ntypedef struct\n{\n\tuint32_t vkFormat;\n\tuint32_t typeSize;\n\tuint32_t pixelWidth;\n\tuint32_t pixelHeight;\n\tuint32_t pixelDepth;\n\tuint32_t layerCount;\n\tuint32_t faceCount;\n\tuint32_t levelCount;\n\tuint32_t supercompressionScheme;\n} ktx2_header_t;\n\ntypedef struct\n{\n\tuint32_t dfdByteOffset;\n\tuint32_t dfdByteLength;\n\tuint32_t kvdByteOffset;\n\tuint32_t kvdByteLength;\n\tuint64_t sgdByteOffset;\n\tuint64_t sgdByteLength;\n} ktx2_index_t;\n\ntypedef struct\n{\n\tuint64_t byteOffset;\n\tuint64_t byteLength;\n\tuint64_t uncompressedByteLength;\n} ktx2_level_t;\n\n#define KTX2_LEVELS_OFFSET ( KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ) + sizeof( ktx2_index_t ))\n\n#define KTX2_MINIMAL_HEADER_SIZE ( KTX2_LEVELS_OFFSET + sizeof( ktx2_level_t ))\n\n// These have the same values as enum VkFormat in vulkan_core.h\n// There are hundreds of formats which can be contained in KTX2.\n// Below are listed the ones which are supported here. This list can be extended.\ntypedef enum\n{\n\tKTX2_FORMAT_BC4_UNORM_BLOCK = 139,\n\tKTX2_FORMAT_BC4_SNORM_BLOCK = 140,\n\tKTX2_FORMAT_BC5_UNORM_BLOCK = 141,\n\tKTX2_FORMAT_BC5_SNORM_BLOCK = 142,\n\tKTX2_FORMAT_BC6H_UFLOAT_BLOCK = 143,\n\tKTX2_FORMAT_BC6H_SFLOAT_BLOCK = 144,\n\tKTX2_FORMAT_BC7_UNORM_BLOCK = 145,\n\tKTX2_FORMAT_BC7_SRGB_BLOCK = 146,\n} ktx2_format_t;\n\n#endif // IMG_KTX2_H\n"
  },
  {
    "path": "engine/common/imagelib/img_main.c",
    "content": "/*\nimg_main.c - load & save various image formats\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <math.h>\n#include \"imagelib.h\"\n#include \"eiface.h\" // ARRAYSIZE\n\n#define DEBUG_LOOKUPS_COUNT 0\n#define USE_FS_SEARCH_FOR_LOOKUPS 1\n\n// global image variables\nimglib_t\timage;\n\ntypedef struct suffix_s\n{\n\tconst char\t*suf;\n\tuint\t\tflags;\n\tside_hint_t\thint;\n} suffix_t;\n\nstatic const suffix_t skybox_qv1[6] =\n{\n{ \"ft\", IMAGE_FLIP_X, CB_HINT_POSX },\n{ \"bk\", IMAGE_FLIP_Y, CB_HINT_NEGX },\n{ \"up\", IMAGE_ROT_90, CB_HINT_POSZ },\n{ \"dn\", IMAGE_ROT_90, CB_HINT_NEGZ },\n{ \"rt\", IMAGE_ROT_90, CB_HINT_POSY },\n{ \"lf\", IMAGE_ROT270, CB_HINT_NEGY },\n};\n\nstatic const suffix_t skybox_qv2[6] =\n{\n{ \"_ft\", IMAGE_FLIP_X, CB_HINT_POSX },\n{ \"_bk\", IMAGE_FLIP_Y, CB_HINT_NEGX },\n{ \"_up\", IMAGE_ROT_90, CB_HINT_POSZ },\n{ \"_dn\", IMAGE_ROT_90, CB_HINT_NEGZ },\n{ \"_rt\", IMAGE_ROT_90, CB_HINT_POSY },\n{ \"_lf\", IMAGE_ROT270, CB_HINT_NEGY },\n};\n\nstatic const suffix_t cubemap_v1[6] =\n{\n{ \"px\", 0, CB_HINT_POSX },\n{ \"nx\", 0, CB_HINT_NEGX },\n{ \"py\", 0, CB_HINT_POSY },\n{ \"ny\", 0, CB_HINT_NEGY },\n{ \"pz\", 0, CB_HINT_POSZ },\n{ \"nz\", 0, CB_HINT_NEGZ },\n};\n\ntypedef struct cubepack_s\n{\n\tconst char\t*name;\t// just for debug\n\tconst suffix_t\t*type;\n} cubepack_t;\n\nstatic const cubepack_t load_cubemap[] =\n{\n{ \"3Ds Sky1\", skybox_qv1 },\n{ \"3Ds Sky2\", skybox_qv2 },\n{ \"3Ds Cube\", cubemap_v1 },\n};\n\n// soul of ImageLib - table of image format constants\nconst bpc_desc_t PFDesc[] =\n{\n{ PF_UNKNOWN,\t\"raw\",\t0x1908, 0 },\n{ PF_INDEXED_24,\t\"pal 24\",\t0x1908, 1 },\n{ PF_INDEXED_32,\t\"pal 32\",\t0x1908, 1 },\n{ PF_RGBA_32,\t\"RGBA 32\",0x1908, 4 },\n{ PF_BGRA_32,\t\"BGRA 32\",0x80E1, 4 },\n{ PF_RGB_24,\t\"RGB 24\",\t0x1908, 3 },\n{ PF_BGR_24,\t\"BGR 24\",\t0x80E0, 3 },\n{ PF_LUMINANCE,\t\"LUM 8\",\t0x1909, 1 },\n{ PF_DXT1,\t\"DXT 1\",\t0x83F1, 4 },\n{ PF_DXT3,\t\"DXT 3\",\t0x83F2, 4 },\n{ PF_DXT5,\t\"DXT 5\",\t0x83F3, 4 },\n{ PF_ATI2,\t\"ATI 2\",\t0x8837, 4 },\n};\n\n#if DEBUG_LOOKUPS_COUNT\nstatic int g_lookups = 0;\nstatic int g_lookups_total = 0;\nstatic double g_lookup_start = 0.0f;\nstatic double g_lookup_time = 0.0f;\nstatic double g_lookup_time_total = 0.0f;\n\nstatic void Image_ReportLookupsCount( const char *name )\n{\n\tif( name[0] != '#' )\n\t{\n\t\tCon_Reportf( \"Performed %i lookups in %fs (of total %i for %fs) while loading %s\\n\",\n\t\t\tg_lookups, g_lookup_time, g_lookups_total, g_lookup_time_total, name );\n\t}\n}\n\nstatic void Image_IncrementLookupTime( void )\n{\n\tdouble t = Sys_DoubleTime();\n\tdouble dt = t - g_lookup_start;\n\n\tg_lookup_time += dt;\n\tg_lookup_time_total += dt;\n\tg_lookups++;\n\tg_lookups_total++;\n\n\tg_lookup_start = Sys_DoubleTime();\n}\n#else\nstatic void Image_ReportLookupsCount( const char *name )\n{\n\t(void)name;\n}\nstatic void Image_IncrementLookupTime( void )\n{\n\n}\n#endif\n\nvoid Image_Reset( void )\n{\n\t// reset global variables\n\timage.width = image.height = image.depth = 0;\n\timage.source_width = image.source_height = 0;\n\timage.source_type = image.num_mips = 0;\n\timage.num_sides = image.flags = 0;\n\timage.encode = DXT_ENCODE_DEFAULT;\n\timage.type = PF_UNKNOWN;\n\timage.fogParams[0] = 0;\n\timage.fogParams[1] = 0;\n\timage.fogParams[2] = 0;\n\timage.fogParams[3] = 0;\n\n\t// pointers will be saved with prevoius picture struct\n\t// don't care about it\n\timage.palette = NULL;\n\timage.cubemap = NULL;\n\timage.rgba = NULL;\n\timage.ptr = 0;\n\timage.size = 0;\n\n#if DEBUG_LOOKUPS_COUNT\n\tg_lookups = 0;\n\tg_lookup_time = 0.0f;\n\tg_lookup_start = Sys_DoubleTime();\n#endif // DEBUG_LOOKUPS_COUNT\n}\n\nstatic MALLOC_LIKE( FS_FreeImage, 1 ) rgbdata_t *ImagePack( const char *name )\n{\n\trgbdata_t\t*pack;\n\n\tImage_ReportLookupsCount( name );\n\n\t// clear any force flags\n\timage.force_flags = 0;\n\n\tif( image.cubemap && image.num_sides != 6 )\n\t{\n\t\t// this never can happen, just in case\n\t\treturn NULL;\n\t}\n\n\tpack = Mem_Calloc( host.imagepool, sizeof( *pack ));\n\n\tif( image.cubemap )\n\t{\n\t\timage.flags |= IMAGE_CUBEMAP;\n\t\tpack->buffer = image.cubemap;\n\t\tpack->width = image.source_width;\n\t\tpack->height = image.source_height;\n\t\tpack->type = image.source_type;\n\t\tpack->size = image.size * image.num_sides;\n\t}\n\telse\n\t{\n\t\tpack->buffer = image.rgba;\n\t\tpack->width = image.width;\n\t\tpack->height = image.height;\n\t\tpack->depth = image.depth;\n\t\tpack->type = image.type;\n\t\tpack->size = image.size;\n\t}\n\n\t// copy fog params\n\tpack->fogParams[0] = image.fogParams[0];\n\tpack->fogParams[1] = image.fogParams[1];\n\tpack->fogParams[2] = image.fogParams[2];\n\tpack->fogParams[3] = image.fogParams[3];\n\n\tpack->flags = image.flags;\n\tpack->numMips = image.num_mips;\n\tpack->palette = image.palette;\n\tpack->encode = image.encode;\n\n\treturn pack;\n}\n\n/*\n================\nFS_AddSideToPack\n\n================\n*/\nstatic qboolean FS_AddSideToPack( int adjust_flags )\n{\n\tbyte\t*out, *flipped;\n\tqboolean\tresampled = false;\n\n\t// first side set average size for all cubemap sides!\n\tif( !image.cubemap )\n\t{\n\t\timage.source_width = image.width;\n\t\timage.source_height = image.height;\n\t\timage.source_type = image.type;\n\t}\n\n\t// keep constant size, render.dll expecting it\n\t// NOTE: This is super incorrect for compressed images.\n\t// No idea why it was needed\n\t// image.size = image.source_width * image.source_height * 4;\n\n\t// mixing dds format with any existing ?\n\tif( image.type != image.source_type )\n\t\treturn false;\n\n\t// flip image if needed\n\tflipped = Image_FlipInternal( image.rgba, &image.width, &image.height, image.source_type, adjust_flags );\n\tif( !flipped ) return false; // try to reasmple dxt?\n\tif( flipped != image.rgba ) image.rgba = Image_Copy( image.size );\n\n\t// resampling image if needed\n\tout = Image_ResampleInternal((uint *)image.rgba, image.width, image.height, image.source_width, image.source_height, image.source_type, &resampled );\n\tif( !out ) return false; // try to reasmple dxt?\n\tif( resampled ) image.rgba = Image_Copy( image.size );\n\n\timage.cubemap = Mem_Realloc( host.imagepool, image.cubemap, image.ptr + image.size );\n\tmemcpy( image.cubemap + image.ptr, image.rgba, image.size ); // add new side\n\n\tMem_Free( image.rgba );\t// release source buffer\n\timage.ptr += image.size; \t// move to next\n\timage.num_sides++;\t\t// bump sides count\n\n\treturn true;\n}\n\nstatic const loadpixformat_t *Image_GetLoadFormatForExtension( const char *ext )\n{\n\tconst loadpixformat_t *format;\n\n\tif( !COM_CheckStringEmpty( ext ))\n\t\treturn NULL;\n\n\tfor( format = image.loadformats; format->ext; format++ )\n\t{\n\t\tif( !Q_stricmp( ext, format->ext ))\n\t\t\treturn format;\n\t}\n\n\treturn NULL;\n}\n\nstatic qboolean Image_ProbeLoadBuffer_( const loadpixformat_t *fmt, const char *name, const byte *buf, size_t size, int override_hint )\n{\n\tif( override_hint > 0 )\n\t\timage.hint = override_hint;\n\telse image.hint = fmt->hint;\n\n\treturn fmt->loadfunc( name, buf, size );\n}\n\nstatic qboolean Image_ProbeLoadBuffer( const loadpixformat_t *fmt, const char *name, const byte *buf, size_t size, int override_hint )\n{\n\tif( unlikely( size <= 0 ))\n\t\treturn false;\n\n\t// bruteforce all loaders\n\tif( !fmt )\n\t{\n\t\tfor( fmt = image.loadformats; fmt->ext; fmt++ )\n\t\t{\n\t\t\tif( Image_ProbeLoadBuffer_( fmt, name, buf, size, override_hint ))\n\t\t\t\t return true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\treturn Image_ProbeLoadBuffer_( fmt, name, buf, size, override_hint );\n}\n\nstatic qboolean Image_ProbeLoad_( const loadpixformat_t *fmt, const char *name, const char *suffix, int override_hint )\n{\n\tqboolean success = false;\n\tfs_offset_t filesize;\n\tstring path;\n\tbyte *f;\n\n\tQ_snprintf( path, sizeof( path ), \"%s%s.%s\", name, suffix, fmt->ext );\n\tf = FS_LoadFile( path, &filesize, false );\n\n\tImage_IncrementLookupTime();\n\n\tif( f && filesize >= 0 )\n\t{\n\t\tsuccess = Image_ProbeLoadBuffer_( fmt, path, f, filesize, override_hint );\n\n\t\tMem_Free( f );\n\t}\n\n\treturn success;\n}\n\nstatic qboolean Image_ProbeLoad2( const char *name, const char *suffix, int override_hint )\n{\n\tconst loadpixformat_t *fmt;\n\tsearch_t *t;\n\tstring pattern;\n\tint i;\n\n\tQ_snprintf( pattern, sizeof( pattern ), \"%s%s.*\", name, suffix );\n\n\tt = FS_Search( pattern, true, false );\n\n\tif( !t )\n\t\treturn false;\n\n\t// we now have to check every extension\n\t// to keep the loading order\n\tfor( fmt = image.loadformats; fmt->ext; fmt++ )\n\t{\n\t\tfs_offset_t filesize;\n\t\tbyte *data;\n\n\t\tfor( i = 0; i < t->numfilenames; i++ )\n\t\t{\n\t\t\tconst char *ext = COM_FileExtension( t->filenames[i] );\n\n\t\t\tif( !Q_stricmp( ext, fmt->ext ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// try next...\n\t\tif( i == t->numfilenames )\n\t\t\tcontinue;\n\n\t\tdata = FS_LoadFile( t->filenames[i], &filesize, false );\n\t\tImage_IncrementLookupTime();\n\n\t\t// can't load file, ignore\n\t\tif( unlikely( !data || filesize <= 0 ))\n\t\t\tcontinue;\n\n\t\tif( Image_ProbeLoadBuffer_( fmt, t->filenames[i], data, filesize, override_hint ))\n\t\t{\n\t\t\tMem_Free( data );\n\t\t\tMem_Free( t );\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tMem_Free( t );\n\treturn false;\n}\n\nstatic qboolean Image_ProbeLoad( const loadpixformat_t *fmt, const char *name, const char *suffix, int override_hint )\n{\n\tif( !fmt )\n\t{\n#if USE_FS_SEARCH_FOR_LOOKUPS\n\t\treturn Image_ProbeLoad2( name, suffix, override_hint );\n#else\n\t\t// bruteforce all formats to allow implicit extension\n\t\tfor( fmt = image.loadformats; fmt->ext; fmt++ )\n\t\t{\n\t\t\tif( Image_ProbeLoad_( fmt, name, suffix, override_hint ))\n\t\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n#endif\n\t}\n\n\treturn Image_ProbeLoad_( fmt, name, suffix, override_hint );\n}\n\n/*\n================\nFS_LoadImage\n\nloading and unpack to rgba any known image\n================\n*/\nrgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size )\n{\n\tconst char\t*ext = COM_FileExtension( filename );\n\tstring\t\tloadname;\n\tint\t\ti, j;\n\tconst loadpixformat_t *extfmt;\n\n\tQ_strncpy( loadname, filename, sizeof( loadname ));\n\n\t// we needs to compare file extension with list of supported formats\n\t// and be sure what is real extension, not a filename with dot\n\tif(( extfmt = Image_GetLoadFormatForExtension( ext )))\n\t\tCOM_StripExtension( loadname );\n\n\tImage_Reset(); // clear old image\n\n\t// special mode: skip any checks, load file from buffer\n\tif( filename[0] == '#' && buffer && size )\n\t\tgoto load_internal;\n\n\tif( Image_ProbeLoad( extfmt, loadname, \"\", -1 ))\n\t\treturn ImagePack( filename );\n\n\t// check all cubemap sides with package suffix\n\tfor( j = 0; j < ARRAYSIZE( load_cubemap ); j++ )\n\t{\n\t\tconst cubepack_t\t*cmap = &load_cubemap[j];\n\n\t\tfor( i = 0; i < 6; i++ )\n\t\t{\n\t\t\tif( Image_ProbeLoad( extfmt, loadname, cmap->type[i].suf, cmap->type[i].hint ))\n\t\t\t{\n\t\t\t\tFS_AddSideToPack( cmap->type[i].flags );\n\t\t\t}\n\n\t\t\tif( image.num_sides != i + 1 ) // check side\n\t\t\t{\n\t\t\t\t// first side not found, probably it's not cubemap\n\t\t\t\t// it contain info about image_type and dimensions, don't generate black cubemaps\n\t\t\t\tif( !image.cubemap ) break;\n\t\t\t\t// Mem_Alloc already filled memblock with 0x00, no need to do it again\n\t\t\t\timage.cubemap = Mem_Realloc( host.imagepool, image.cubemap, image.ptr + image.size );\n\t\t\t\timage.ptr += image.size; // move to next\n\t\t\t\timage.num_sides++; // merge counter\n\t\t\t}\n\t\t}\n\n\t\t// make sure that all sides is loaded\n\t\tif( image.num_sides != 6 )\n\t\t{\n\t\t\t// unexpected errors ?\n\t\t\tif( image.cubemap )\n\t\t\t\tMem_Free( image.cubemap );\n\t\t\tImage_Reset();\n\t\t}\n\t\telse break;\n\t}\n\n\tif( image.cubemap )\n\t\treturn ImagePack( filename ); // all done\n\nload_internal:\n\tif( buffer && size )\n\t{\n\t\tif( Image_ProbeLoadBuffer( extfmt, loadname, buffer, size, -1 ))\n\t\t\treturn ImagePack( filename );\n\t}\n\n\tif( loadname[0] != '#' )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: couldn't load \\\"%s\\\"\\n\", __func__, loadname );\n\t\tImage_ReportLookupsCount( filename );\n\t}\n\n\t// clear any force flags\n\timage.force_flags = 0;\n\n\treturn NULL;\n}\n\n\n\n/*\n================\nImage_Save\n\nwrites image as any known format\n================\n*/\nqboolean FS_SaveImage( const char *filename, rgbdata_t *pix )\n{\n\tconst char\t*ext = COM_FileExtension( filename );\n\tqboolean\t\tanyformat = !COM_CheckStringEmpty( ext );\n\tstring\t\tpath, savename;\n\tconst savepixformat_t *format;\n\n\tif( !pix || !pix->buffer || anyformat )\n\t{\n\t\t// clear any force flags\n\t\timage.force_flags = 0;\n\t\treturn false;\n\t}\n\n\tQ_strncpy( savename, filename, sizeof( savename ));\n\tCOM_StripExtension( savename ); // remove extension if needed\n\n\tif( pix->flags & (IMAGE_CUBEMAP|IMAGE_SKYBOX))\n\t{\n\t\tsize_t\t\trealSize = pix->size; // keep real pic size\n\t\tbyte\t\t*picBuffer; // to avoid corrupt memory on free data\n\t\tconst suffix_t\t*box;\n\t\tint\t\ti;\n\n\t\tif( pix->flags & IMAGE_SKYBOX )\n\t\t\tbox = skybox_qv1;\n\t\telse if( pix->flags & IMAGE_CUBEMAP )\n\t\t\tbox = cubemap_v1;\n\t\telse\n\t\t{\n\t\t\t// clear any force flags\n\t\t\timage.force_flags = 0;\n\t\t\treturn false;\t// do not happens\n\t\t}\n\n\t\tpix->size /= 6; // now set as side size\n\t\tpicBuffer = pix->buffer;\n\n\t\t// save all sides seperately\n\t\tfor( format = image.saveformats; format && format->ext; format++ )\n\t\t{\n\t\t\tif( !Q_stricmp( ext, format->ext ))\n\t\t\t{\n\t\t\t\tfor( i = 0; i < 6; i++ )\n\t\t\t\t{\n\t\t\t\t\tQ_snprintf( path, sizeof( path ), \"%s%s.%s\", savename, box[i].suf, format->ext );\n\t\t\t\t\tif( !format->savefunc( path, pix )) break; // there were errors\n\t\t\t\t\tpix->buffer += pix->size; // move pointer\n\t\t\t\t}\n\n\t\t\t\t// restore pointers\n\t\t\t\tpix->size = realSize;\n\t\t\t\tpix->buffer = picBuffer;\n\n\t\t\t\t// clear any force flags\n\t\t\t\timage.force_flags = 0;\n\n\t\t\t\treturn ( i == 6 );\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( format = image.saveformats; format && format->ext; format++ )\n\t\t{\n\t\t\tif( !Q_stricmp( ext, format->ext ))\n\t\t\t{\n\t\t\t\tQ_snprintf( path, sizeof( path ), \"%s.%s\", savename, format->ext );\n\t\t\t\tif( format->savefunc( path, pix ))\n\t\t\t\t{\n\t\t\t\t\t// clear any force flags\n\t\t\t\t\timage.force_flags = 0;\n\t\t\t\t\treturn true; // saved\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// clear any force flags\n\timage.force_flags = 0;\n\n\treturn false;\n}\n\n/*\n================\nImage_FreeImage\n\nfree RGBA buffer\n================\n*/\nvoid FS_FreeImage( rgbdata_t *pack )\n{\n\tif( !pack ) return;\n\tif( pack->buffer ) Mem_Free( pack->buffer );\n\tif( pack->palette ) Mem_Free( pack->palette );\n\tMem_Free( pack );\n}\n\n/*\n================\nFS_CopyImage\n\nmake an image copy\n================\n*/\nrgbdata_t *FS_CopyImage( rgbdata_t *in )\n{\n\trgbdata_t\t*out;\n\tint\tpalSize = 0;\n\n\tif( !in ) return NULL;\n\n\tout = Mem_Malloc( host.imagepool, sizeof( rgbdata_t ));\n\t*out = *in;\n\n\tswitch( in->type )\n\t{\n\tcase PF_INDEXED_24:\n\t\tpalSize = 768;\n\t\tbreak;\n\tcase PF_INDEXED_32:\n\t\tpalSize = 1024;\n\t\tbreak;\n\t}\n\n\tif( palSize )\n\t{\n\t\tout->palette = Mem_Malloc( host.imagepool, palSize );\n\t\tmemcpy( out->palette, in->palette, palSize );\n\t}\n\n\tif( in->size )\n\t{\n\t\tout->buffer = Mem_Malloc( host.imagepool, in->size );\n\t\tmemcpy( out->buffer, in->buffer, in->size );\n\t}\n\n\treturn out;\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nstatic void GeneratePixel( byte *pix, uint i, uint j, uint w, uint h, qboolean genAlpha )\n{\n\tdouble x = ( j / (double)w ) - 0.5;\n\tdouble y = ( i / (double)h ) - 0.5;\n\tdouble d = sqrt( x * x + y * y );\n\tpix[0] = (byte)(( sin( d * 30.0 ) + 1.0 ) * 126 );\n\tpix[1] = (byte)(( sin( d * 27.723 ) + 1.0 ) * 126 );\n\tpix[2] = (byte)(( sin( d * 42.41 ) + 1.0 ) * 126 );\n\tpix[3] = genAlpha ? (byte)(( cos( d * 2.0 ) + 1.0 ) * 126 ) : 255;\n}\n\nstatic void Test_CheckImage( const char *name, rgbdata_t *rgb )\n{\n\trgbdata_t *load;\n\n\t// test reading\n\tload = FS_LoadImage( name, NULL, 0 );\n\tTASSERT( load->width == rgb->width )\n\tTASSERT( load->height == rgb->height )\n\tTASSERT( load->type == rgb->type )\n\tTASSERT( ( load->flags & rgb->flags ) != 0 )\n\tTASSERT( load->size == rgb->size )\n\tTASSERT( memcmp(load->buffer, rgb->buffer, rgb->size ) == 0 )\n\n\tFS_FreeImage( load );\n}\n\nvoid Test_RunImagelib( void )\n{\n\trgbdata_t rgb = { 0 };\n\tbyte *buf;\n\tconst char *extensions[] = { \"tga\", \"png\", \"bmp\" };\n\tuint i, j;\n\n\tImage_Setup();\n\n\t// generate image\n\trgb.width = 256;\n\trgb.height = 512;\n\trgb.type = PF_RGBA_32;\n\trgb.flags = IMAGE_HAS_ALPHA;\n\trgb.size = rgb.width * rgb.height * 4;\n\tbuf = rgb.buffer = Z_Malloc( rgb.size );\n\n\tfor( i = 0; i < rgb.height; i++ )\n\t{\n\t\tfor( j = 0; j < rgb.width; j++ )\n\t\t{\n\t\t\tGeneratePixel( buf, i, j, rgb.width, rgb.height, true );\n\t\t\tbuf += 4;\n\t\t}\n\t}\n\n\tfor( i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++ )\n\t{\n\t\tqboolean ret;\n\t\tchar name[MAX_VA_STRING];\n\n\t\tQ_snprintf( name, sizeof( name ), \"test_gen.%s\", extensions[i] );\n\n\t\t// test saving\n\t\tret = FS_SaveImage( name, &rgb );\n\t\tCon_Printf( \"Checking if we can save images in '%s' format...\\n\", extensions[i] );\n\t\tASSERT(ret == true);\n\n\t\t// test reading\n\t\tCon_Printf( \"Checking if we can read images in '%s' format...\\n\", extensions[i] );\n\t\tTest_CheckImage( name, &rgb );\n\t}\n\n\tZ_Free( rgb.buffer );\n}\n\n#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \\\nint export( const uint8_t *Data, size_t Size ); \\\nint EXPORT export( const uint8_t *Data, size_t Size ) \\\n{ \\\n\trgbdata_t *rgb; \\\n\thost.type = HOST_NORMAL; \\\n\tMemory_Init(); \\\n\tImage_Init(); \\\n\tif( target( \"#internal\", Data, Size )) \\\n\t{ \\\n\t\trgb = ImagePack( \"#internal\" ); \\\n\t\tFS_FreeImage( rgb ); \\\n\t} \\\n\tImage_Shutdown(); \\\n\treturn 0; \\\n} \\\n\nIMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadBMP, Image_LoadBMP )\nIMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadPNG, Image_LoadPNG )\nIMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadDDS, Image_LoadDDS )\nIMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA )\n\n#endif /* XASH_ENGINE_TESTS */\n"
  },
  {
    "path": "engine/common/imagelib/img_png.c",
    "content": "/*\nimg_png.c - png format load & save\nCopyright (C) 2019 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"miniz.h\"\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"img_png.h\"\n\n#if defined(XASH_NO_NETWORK)\n\t#include \"platform/stub/net_stub.h\"\n#elif XASH_NSWITCH\n\t// our ntohl is here\n\t#include <arpa/inet.h>\n#elif !XASH_WIN32\n\t#include <netinet/in.h>\n#endif\n\nstatic const char png_sign[] = {0x89, 'P', 'N', 'G', '\\r', '\\n', 0x1a, '\\n'};\nstatic const char ihdr_sign[] = {'I', 'H', 'D', 'R'};\nstatic const char trns_sign[] = {'t', 'R', 'N', 'S'};\nstatic const char plte_sign[] = {'P', 'L', 'T', 'E'};\nstatic const char idat_sign[] = {'I', 'D', 'A', 'T'};\nstatic const char iend_sign[] = {'I', 'E', 'N', 'D'};\nstatic const int  iend_crc32 = 0xAE426082;\n\n/*\n=============\nImage_LoadPNG\n=============\n*/\nqboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tint\t\tret;\n\tshort\t\tp, a, b, c, pa, pb, pc;\n\tbyte\t\t*buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL;\n\tbyte\t\t*pallete = NULL, *trns = NULL;\n\tuint\t \tchunk_len, trns_len = 0, plte_len = 0, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize;\n\tuint\t\tuncompressed_size, pixel_size, pixel_count, i, y, filter_type, chunk_sign, r_alpha, g_alpha, b_alpha;\n\tqboolean \thas_iend_chunk = false;\n\tz_stream \tstream = {0};\n\tpng_t\t\tpng_hdr;\n\n\tif( filesize < sizeof( png_hdr ) )\n\t\treturn false;\n\n\tbuf_p = (byte *)buffer;\n\n\t// get png header\n\tmemcpy( &png_hdr, buffer, sizeof( png_t ) );\n\n\t// check png signature\n\tif( memcmp( png_hdr.sign, png_sign, sizeof( png_sign ) ) )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Invalid PNG signature (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// convert IHDR chunk length to little endian\n\tpng_hdr.ihdr_len = ntohl( png_hdr.ihdr_len );\n\n\t// check IHDR chunk length (valid value - 13)\n\tif( png_hdr.ihdr_len != sizeof( png_ihdr_t ) )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Invalid IHDR chunk size %u (%s)\\n\", __func__, png_hdr.ihdr_len, name );\n\t\treturn false;\n\t}\n\n\t// check IHDR chunk signature\n\tif( memcmp( png_hdr.ihdr_sign, ihdr_sign, sizeof( ihdr_sign ) ) )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IHDR chunk corrupted (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// convert image width and height to little endian\n\timage.height = png_hdr.ihdr_chunk.height = ntohl( png_hdr.ihdr_chunk.height );\n\timage.width  = png_hdr.ihdr_chunk.width  = ntohl( png_hdr.ihdr_chunk.width );\n\n\tif( png_hdr.ihdr_chunk.height == 0 || png_hdr.ihdr_chunk.width == 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Invalid image size %ux%u (%s)\\n\", __func__, png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name );\n\t\treturn false;\n\t}\n\n\tif( !Image_ValidSize( name ))\n\t\treturn false;\n\n\tif( png_hdr.ihdr_chunk.bitdepth != 8 )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: Only 8-bit images is supported (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif( !( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB\n\t    || png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA\n\t    || png_hdr.ihdr_chunk.colortype == PNG_CT_GREY\n\t    || png_hdr.ihdr_chunk.colortype == PNG_CT_ALPHA\n\t    || png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE ) )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: Unknown color type %u (%s)\\n\", __func__, png_hdr.ihdr_chunk.colortype, name );\n\t\treturn false;\n\t}\n\n\tif( png_hdr.ihdr_chunk.compression > 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Unknown compression method %u (%s)\\n\", __func__, png_hdr.ihdr_chunk.compression, name );\n\t\treturn false;\n\t}\n\n\tif( png_hdr.ihdr_chunk.filter > 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Unknown filter type %u (%s)\\n\", __func__, png_hdr.ihdr_chunk.filter, name );\n\t\treturn false;\n\t}\n\n\tif( png_hdr.ihdr_chunk.interlace == 1 )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: Adam7 Interlacing not supported (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif( png_hdr.ihdr_chunk.interlace > 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Unknown interlacing type %u (%s)\\n\", __func__, png_hdr.ihdr_chunk.interlace, name );\n\t\treturn false;\n\t}\n\n\t// calculate IHDR chunk CRC\n\tCRC32_Init( &crc32_check );\n\tCRC32_ProcessBuffer( &crc32_check, buf_p + sizeof( png_hdr.sign ) + sizeof( png_hdr.ihdr_len ), png_hdr.ihdr_len + sizeof( png_hdr.ihdr_sign ) );\n\tcrc32_check = CRC32_Final( crc32_check );\n\n\t// check IHDR chunk CRC\n\tif( ntohl( png_hdr.ihdr_crc32 ) != crc32_check )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IHDR chunk has wrong CRC32 sum (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\t// move pointer\n\tbuf_p += sizeof( png_hdr );\n\n\t// find all critical chunks\n\twhile( !has_iend_chunk && ( buf_p - buffer ) < filesize )\n\t{\n\t\t// get chunk length\n\t\tmemcpy( &chunk_len, buf_p, sizeof( chunk_len ) );\n\n\t\t// convert chunk length to little endian\n\t\tchunk_len = ntohl( chunk_len );\n\n\t\tif( chunk_len > INT_MAX )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: Found chunk with wrong size (%s)\\n\", __func__, name );\n\t\t\tif( idat_buf ) Mem_Free( idat_buf );\n\t\t\treturn false;\n\t\t}\n\n\t\tif( chunk_len > filesize - ( buf_p - buffer ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: Found chunk with size past file size (%s)\\n\", __func__, name );\n\t\t\tif( idat_buf ) Mem_Free( idat_buf );\n\t\t\treturn false;\n\t\t}\n\n\t\t// move pointer\n\t\tbuf_p += sizeof( chunk_len );\n\n\t\t// find transparency\n\t\tif( !memcmp( buf_p, trns_sign, sizeof( trns_sign ) ) )\n\t\t{\n\t\t\ttrns = buf_p + sizeof( trns_sign );\n\t\t\ttrns_len = chunk_len;\n\t\t}\n\t\t// find pallete for indexed image\n\t\telse if( !memcmp( buf_p, plte_sign, sizeof( plte_sign ) ) )\n\t\t{\n\t\t\tpallete = buf_p + sizeof( plte_sign );\n\t\t\tplte_len = chunk_len / 3;\n\t\t}\n\t\t// get all IDAT chunks data\n\t\telse if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) )\n\t\t{\n\t\t\tnewsize = oldsize + chunk_len;\n\t\t\tidat_buf = (byte *)Mem_Realloc( host.imagepool, idat_buf, newsize );\n\t\t\tmemcpy( idat_buf + oldsize, buf_p + sizeof( idat_sign ), chunk_len );\n\t\t\toldsize = newsize;\n\t\t}\n\t\telse if( !memcmp( buf_p, iend_sign, sizeof( iend_sign ) ) )\n\t\t\thas_iend_chunk = true;\n\n\t\t// calculate chunk CRC\n\t\tCRC32_Init( &crc32_check );\n\t\tCRC32_ProcessBuffer( &crc32_check, buf_p, chunk_len + sizeof( idat_sign ) );\n\t\tcrc32_check = CRC32_Final( crc32_check );\n\n\t\t// move pointer\n\t\tbuf_p += sizeof( chunk_sign );\n\t\tbuf_p += chunk_len;\n\n\t\t// get real chunk CRC\n\t\tmemcpy( &crc32, buf_p, sizeof( crc32 ) );\n\n\t\t// check chunk CRC\n\t\tif( ntohl( crc32 ) != crc32_check )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: Found chunk with wrong CRC32 sum (%s)\\n\", __func__, name );\n\t\t\tif( idat_buf ) Mem_Free( idat_buf );\n\t\t\treturn false;\n\t\t}\n\n\t\t// move pointer\n\t\tbuf_p += sizeof( crc32 );\n\t}\n\n\tif( oldsize == 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: Couldn't find IDAT chunks (%s)\\n\", __func__, name );\n\t\treturn false;\n\t}\n\n\tif( png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE && !pallete )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: PLTE chunk not found (%s)\\n\", __func__, name );\n\t\tMem_Free( idat_buf );\n\t\treturn false;\n\t}\n\n\tif( !has_iend_chunk )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IEND chunk not found (%s)\\n\", __func__, name );\n\t\tMem_Free( idat_buf );\n\t\treturn false;\n\t}\n\n\tif( chunk_len != 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IEND chunk has wrong size %u (%s)\\n\", __func__, chunk_len, name );\n\t\tMem_Free( idat_buf );\n\t\treturn false;\n\t}\n\n\tswitch( png_hdr.ihdr_chunk.colortype )\n\t{\n\tcase PNG_CT_GREY:\n\tcase PNG_CT_PALLETE:\n\t\tpixel_size = 1;\n\t\tbreak;\n\tcase PNG_CT_ALPHA:\n\t\tpixel_size = 2;\n\t\tbreak;\n\tcase PNG_CT_RGB:\n\t\tpixel_size = 3;\n\t\tbreak;\n\tcase PNG_CT_RGBA:\n\t\tpixel_size = 4;\n\t\tbreak;\n\tdefault:\n\t\tpixel_size = 0; // make compiler happy\n\t\tASSERT( false );\n\t\tbreak;\n\t}\n\n\timage.type = PF_RGBA_32; // always exctracted to 32-bit buffer\n\tpixel_count = image.height * image.width;\n\timage.size = pixel_count * 4;\n\n\tif( png_hdr.ihdr_chunk.colortype & PNG_CT_RGB )\n\t\timage.flags |= IMAGE_HAS_COLOR;\n\n\tif( trns || ( png_hdr.ihdr_chunk.colortype & PNG_CT_ALPHA ) )\n\t\timage.flags |= IMAGE_HAS_ALPHA;\n\n\timage.depth = 1;\n\n\trowsize = pixel_size * image.width;\n\n\tuncompressed_size = image.height * ( rowsize + 1 ); // +1 for filter\n\tuncompressed_buffer = Mem_Malloc( host.imagepool, uncompressed_size );\n\n\tstream.next_in = idat_buf;\n\tstream.total_in = stream.avail_in = newsize;\n\tstream.next_out = uncompressed_buffer;\n\tstream.total_out = stream.avail_out = uncompressed_size;\n\n\t// uncompress image\n\tif( inflateInit2( &stream, MAX_WBITS ) != Z_OK )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IDAT chunk decompression failed (%s)\\n\", __func__, name );\n\t\tMem_Free( uncompressed_buffer );\n\t\tMem_Free( idat_buf );\n\t\treturn false;\n\t}\n\n\tret = inflate( &stream, Z_NO_FLUSH );\n\tinflateEnd( &stream );\n\n\tMem_Free( idat_buf );\n\n\tif( ret != Z_OK && ret != Z_STREAM_END )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IDAT chunk decompression failed (%s)\\n\", __func__, name );\n\t\tMem_Free( uncompressed_buffer );\n\t\treturn false;\n\t}\n\n\tprior = pixbuf = image.rgba = Mem_Malloc( host.imagepool, image.size );\n\n\ti = 0;\n\n\traw = uncompressed_buffer;\n\n\tif( png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA )\n\t\tprior = pixbuf = raw;\n\n\tfilter_type = *raw++;\n\n\t// decode adaptive filter\n\tswitch( filter_type )\n\t{\n\tcase PNG_F_NONE:\n\tcase PNG_F_UP:\n\t\tfor( ; i < rowsize; i++ )\n\t\t\tpixbuf[i] = raw[i];\n\t\tbreak;\n\tcase PNG_F_SUB:\n\tcase PNG_F_PAETH:\n\t\tfor( ; i < pixel_size; i++ )\n\t\t\tpixbuf[i] = raw[i];\n\n\t\tfor( ; i < rowsize; i++ )\n\t\t\tpixbuf[i] = raw[i] + pixbuf[i - pixel_size];\n\t\tbreak;\n\tcase PNG_F_AVERAGE:\n\t\tfor( ; i < pixel_size; i++ )\n\t\t\tpixbuf[i] = raw[i];\n\n\t\tfor( ; i < rowsize; i++ )\n\t\t\tpixbuf[i] = raw[i] + ( pixbuf[i - pixel_size] >> 1 );\n\t\tbreak;\n\tdefault:\n\t\tCon_DPrintf( S_ERROR \"%s: Found unknown filter type (%s)\\n\", __func__, name );\n\t\tMem_Free( uncompressed_buffer );\n\t\tMem_Free( image.rgba );\n\t\treturn false;\n\t}\n\n\tfor( y = 1; y < image.height; y++ )\n\t{\n\t\ti = 0;\n\n\t\tpixbuf += rowsize;\n\t\traw += rowsize;\n\n\t\tfilter_type = *raw++;\n\n\t\tswitch( filter_type )\n\t\t{\n\t\tcase PNG_F_NONE:\n\t\t\tfor( ; i < rowsize; i++ )\n\t\t\t\tpixbuf[i] = raw[i];\n\t\t\tbreak;\n\t\tcase PNG_F_SUB:\n\t\t\tfor( ; i < pixel_size; i++ )\n\t\t\t\tpixbuf[i] = raw[i];\n\n\t\t\tfor( ; i < rowsize; i++ )\n\t\t\t\tpixbuf[i] = raw[i] + pixbuf[i - pixel_size];\n\t\t\tbreak;\n\t\tcase PNG_F_UP:\n\t\t\tfor( ; i < rowsize; i++ )\n\t\t\t\tpixbuf[i] = raw[i] + prior[i];\n\t\t\tbreak;\n\t\tcase PNG_F_AVERAGE:\n\t\t\tfor( ; i < pixel_size; i++ )\n\t\t\t\tpixbuf[i] = raw[i] + ( prior[i] >> 1 );\n\n\t\t\tfor( ; i < rowsize; i++ )\n\t\t\t\tpixbuf[i] = raw[i] + ( ( pixbuf[i - pixel_size] + prior[i] ) >> 1 );\n\t\t\tbreak;\n\t\tcase PNG_F_PAETH:\n\t\t\tfor( ; i < pixel_size; i++ )\n\t\t\t\tpixbuf[i] = raw[i] + prior[i];\n\n\t\t\tfor( ; i < rowsize; i++ )\n\t\t\t{\n\t\t\t\ta = pixbuf[i - pixel_size];\n\t\t\t\tb = prior[i];\n\t\t\t\tc = prior[i - pixel_size];\n\t\t\t\tp = a + b - c;\n\t\t\t\tpa = abs( p - a );\n\t\t\t\tpb = abs( p - b );\n\t\t\t\tpc = abs( p - c );\n\n\t\t\t\tpixbuf[i] = raw[i];\n\n\t\t\t\tif( pc < pa && pc < pb )\n\t\t\t\t\tpixbuf[i] += c;\n\t\t\t\telse if( pb < pa )\n\t\t\t\t\tpixbuf[i] += b;\n\t\t\t\telse\n\t\t\t\t\tpixbuf[i] += a;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCon_DPrintf( S_ERROR \"%s: Found unknown filter type (%s)\\n\", __func__, name );\n\t\t\tMem_Free( uncompressed_buffer );\n\t\t\tMem_Free( image.rgba );\n\t\t\treturn false;\n\t\t}\n\n\t\tprior = pixbuf;\n\t}\n\n\tpixbuf = image.rgba;\n\traw = uncompressed_buffer;\n\n\tswitch( png_hdr.ihdr_chunk.colortype )\n\t{\n\tcase PNG_CT_RGB:\n\t\tif( trns )\n\t\t{\n\t\t\tr_alpha = trns[0] << 8 | trns[1];\n\t\t\tg_alpha = trns[2] << 8 | trns[3];\n\t\t\tb_alpha = trns[4] << 8 | trns[5];\n\t\t}\n\n\t\tfor( y = 0; y < pixel_count; y++, raw += pixel_size )\n\t\t{\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[1];\n\t\t\t*pixbuf++ = raw[2];\n\n\t\t\tif( trns && r_alpha == raw[0]\n\t\t\t    && g_alpha == raw[1]\n\t\t\t    && b_alpha == raw[2] )\n\t\t\t\t*pixbuf++ = 0;\n\t\t\telse\n\t\t\t\t*pixbuf++ = 0xFF;\n\t\t}\n\t\tbreak;\n\tcase PNG_CT_GREY:\n\t\tif( trns )\n\t\t\tr_alpha = trns[0] << 8 | trns[1];\n\n\t\tfor( y = 0; y < pixel_count; y++, raw += pixel_size )\n\t\t{\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[0];\n\n\t\t\tif( trns && r_alpha == raw[0] )\n\t\t\t\t*pixbuf++ = 0;\n\t\t\telse\n\t\t\t\t*pixbuf++ = 0xFF;\n\t\t}\n\t\tbreak;\n\tcase PNG_CT_ALPHA:\n\t\tfor( y = 0; y < pixel_count; y++, raw += pixel_size )\n\t\t{\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[0];\n\t\t\t*pixbuf++ = raw[1];\n\t\t}\n\t\tbreak;\n\tcase PNG_CT_PALLETE:\n\t\tfor( y = 0; y < pixel_count; y++, raw += pixel_size )\n\t\t{\n\t\t\tif( raw[0] < plte_len )\n\t\t\t{\n\t\t\t\t*pixbuf++ = pallete[3 * raw[0] + 0];\n\t\t\t\t*pixbuf++ = pallete[3 * raw[0] + 1];\n\t\t\t\t*pixbuf++ = pallete[3 * raw[0] + 2];\n\n\t\t\t\tif( trns && raw[0] < trns_len )\n\t\t\t\t\t*pixbuf++ = trns[raw[0]];\n\t\t\t\telse\n\t\t\t\t\t*pixbuf++ = 0xFF;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*pixbuf++ = 0;\n\t\t\t\t*pixbuf++ = 0;\n\t\t\t\t*pixbuf++ = 0;\n\t\t\t\t*pixbuf++ = 0xFF;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n\n\tMem_Free( uncompressed_buffer );\n\n\treturn true;\n}\n\n/*\n=============\nImage_SavePNG\n=============\n*/\nqboolean Image_SavePNG( const char *name, rgbdata_t *pix )\n{\n\tint\t\t ret;\n\tuint\t\t y, outsize, pixel_size, filtered_size, idat_len;\n\tuint\t\t ihdr_len, crc32, rowsize, big_idat_len;\n\tbyte\t\t*in, *buffer, *out, *filtered_buffer, *rowend;\n\tz_stream \t stream = {0};\n\tpng_t\t\t png_hdr;\n\tpng_footer_t\t png_ftr;\n\n\tif( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE ))\n\t\treturn false; // already existed\n\n\t// bogus parameter check\n\tif( !pix->buffer )\n\t\treturn false;\n\n\t// get image description\n\tswitch( pix->type )\n\t{\n\tcase PF_BGR_24:\n\tcase PF_RGB_24: pixel_size = 3; break;\n\tcase PF_BGRA_32:\n\tcase PF_RGBA_32: pixel_size = 4; break;\n\tdefault:\n\t\treturn false;\n\t}\n\n\trowsize = pix->width * pixel_size;\n\n\t// get filtered image size\n\tfiltered_size = ( rowsize + 1 ) * pix->height;\n\n\tout = filtered_buffer = Mem_Malloc( host.imagepool, filtered_size );\n\n\t// apply adaptive filter to image\n\tswitch( pix->type )\n\t{\n\tcase PF_RGB_24:\n\tcase PF_RGBA_32:\n\t\tfor( y = 0; y < pix->height; y++ )\n\t\t{\n\t\t\tin = pix->buffer + y * pix->width * pixel_size;\n\t\t\t*out++ = PNG_F_NONE;\n\t\t\trowend = in + rowsize;\n\t\t\tfor( ; in < rowend; in += pixel_size )\n\t\t\t{\n\t\t\t\t*out++ = in[0];\n\t\t\t\t*out++ = in[1];\n\t\t\t\t*out++ = in[2];\n\t\t\t\tif( pix->flags & IMAGE_HAS_ALPHA )\n\t\t\t\t\t*out++ = in[3];\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase PF_BGR_24:\n\tcase PF_BGRA_32:\n\t\tfor( y = 0; y < pix->height; y++ )\n\t\t{\n\t\t\tin = pix->buffer + y * pix->width * pixel_size;\n\t\t\t*out++ = PNG_F_NONE;\n\t\t\trowend = in + rowsize;\n\t\t\tfor( ; in < rowend; in += pixel_size )\n\t\t\t{\n\t\t\t\t*out++ = in[2];\n\t\t\t\t*out++ = in[1];\n\t\t\t\t*out++ = in[0];\n\t\t\t\tif( pix->flags & IMAGE_HAS_ALPHA )\n\t\t\t\t\t*out++ = in[3];\n\t\t\t}\n\t\t}\n\t\tbreak;\n\t}\n\n\n\n\t// get IHDR chunk length\n\tihdr_len = sizeof( png_ihdr_t );\n\n\t// predict IDAT chunk length\n\tidat_len = deflateBound( NULL, filtered_size );\n\n\t// calculate PNG filesize\n\toutsize = sizeof( png_t );\n\toutsize += sizeof( idat_len );\n\toutsize += sizeof( idat_sign );\n\toutsize += idat_len;\n\toutsize += sizeof( png_footer_t );\n\n\t// write PNG header\n\tmemcpy( png_hdr.sign, png_sign, sizeof( png_sign ) );\n\n\t// write IHDR chunk length\n\tpng_hdr.ihdr_len = htonl( ihdr_len );\n\n\t// write IHDR chunk signature\n\tmemcpy( png_hdr.ihdr_sign, ihdr_sign, sizeof( ihdr_sign ) );\n\n\t// write image width\n\tpng_hdr.ihdr_chunk.width = htonl( pix->width );\n\n\t// write image height\n\tpng_hdr.ihdr_chunk.height = htonl( pix->height );\n\n\t// write image bitdepth\n\tpng_hdr.ihdr_chunk.bitdepth = 8;\n\n\t// write image colortype\n\tpng_hdr.ihdr_chunk.colortype = ( pix->flags & IMAGE_HAS_ALPHA ) ? PNG_CT_RGBA : PNG_CT_RGB; // 8 bits of alpha\n\n\t// write image comression method\n\tpng_hdr.ihdr_chunk.compression = 0;\n\n\t// write image filter type\n\tpng_hdr.ihdr_chunk.filter = 0;\n\n\t// write image interlacing\n\tpng_hdr.ihdr_chunk.interlace = 0;\n\n\t// get IHDR chunk CRC\n\tCRC32_Init( &crc32 );\n\tCRC32_ProcessBuffer( &crc32, &png_hdr.ihdr_sign, ihdr_len + sizeof( ihdr_sign ) );\n\tcrc32 = CRC32_Final( crc32 );\n\n\t// write IHDR chunk CRC\n\tpng_hdr.ihdr_crc32 = htonl( crc32 );\n\n\tout = buffer = (byte *)Mem_Malloc( host.imagepool, outsize );\n\n\tstream.next_in = filtered_buffer;\n\tstream.avail_in = filtered_size;\n\tstream.next_out = buffer + sizeof( png_hdr ) + sizeof( idat_len ) + sizeof( idat_sign );\n\tstream.avail_out = idat_len;\n\n\t// compress image\n\tif( deflateInit( &stream, Z_BEST_COMPRESSION ) != Z_OK )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: deflateInit failed (%s)\\n\", __func__, name );\n\t\tMem_Free( filtered_buffer );\n\t\tMem_Free( buffer );\n\t\treturn false;\n\t}\n\n\tret = deflate( &stream, Z_FINISH );\n\tdeflateEnd( &stream );\n\n\tMem_Free( filtered_buffer );\n\n\tif( ret != Z_OK && ret != Z_STREAM_END )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: IDAT chunk compression failed (%s)\\n\", __func__, name );\n\t\tMem_Free( buffer );\n\t\treturn false;\n\t}\n\n\t// get final filesize\n\toutsize -= idat_len;\n\tidat_len = stream.total_out;\n\toutsize += idat_len;\n\n\tmemcpy( out, &png_hdr, sizeof( png_t ) );\n\n\tout += sizeof( png_t );\n\n\t// convert IDAT chunk length to big endian\n\tbig_idat_len = htonl( idat_len );\n\n\t// write IDAT chunk length\n\tmemcpy( out, &big_idat_len, sizeof( idat_len ) );\n\n\tout += sizeof( idat_len );\n\n\t// write IDAT chunk signature\n\tmemcpy( out, idat_sign, sizeof( idat_sign ) );\n\n\t// calculate IDAT chunk CRC\n\tCRC32_Init( &crc32 );\n\tCRC32_ProcessBuffer( &crc32, out, idat_len + sizeof( idat_sign ) );\n\tcrc32 = CRC32_Final( crc32 );\n\n\tout += sizeof( idat_sign );\n\tout += idat_len;\n\n\t// write IDAT chunk CRC\n\tpng_ftr.idat_crc32 = htonl( crc32 );\n\n\t// write IEND chunk length\n\tpng_ftr.iend_len = 0;\n\n\t// write IEND chunk signature\n\tmemcpy( png_ftr.iend_sign, iend_sign, sizeof( iend_sign ) );\n\n\t// write IEND chunk CRC\n\tpng_ftr.iend_crc32 = htonl( iend_crc32 );\n\n\t// write PNG footer to buffer\n\tmemcpy( out, &png_ftr, sizeof( png_ftr ) );\n\n\tFS_WriteFile( name, buffer, outsize );\n\n\tMem_Free( buffer );\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_png.h",
    "content": "/*\nimg_png.h - png format reference\nCopyright (C) 2019 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef IMG_PNG_H\n#define IMG_PNG_H\n/*\n========================================================================\n\n.PNG image format\t(Portable Network Graphics)\n\n========================================================================\n*/\n\nenum png_colortype\n{\n\tPNG_CT_GREY,\n\tPNG_CT_RGB = BIT(1),\n\tPNG_CT_PALLETE = (PNG_CT_RGB|BIT(0)),\n\tPNG_CT_ALPHA = BIT(2),\n\tPNG_CT_RGBA = (PNG_CT_RGB|PNG_CT_ALPHA)\n};\n\nenum png_filter\n{\n\tPNG_F_NONE,\n\tPNG_F_SUB,\n\tPNG_F_UP,\n\tPNG_F_AVERAGE,\n\tPNG_F_PAETH\n};\n\n#pragma pack( push, 1 )\ntypedef struct png_ihdr_s\n{\n\tuint32_t    width;\n\tuint32_t    height;\n\tuint8_t     bitdepth;\n\tuint8_t     colortype;\n\tuint8_t     compression;\n\tuint8_t     filter;\n\tuint8_t     interlace;\n} png_ihdr_t;\n\ntypedef struct png_s\n{\n\tuint8_t     sign[8];\n\tuint32_t    ihdr_len;\n\tuint8_t     ihdr_sign[4];\n\tpng_ihdr_t  ihdr_chunk;\n\tuint32_t    ihdr_crc32;\n} png_t;\n\ntypedef struct png_footer_s\n{\n\tuint32_t    idat_crc32;\n\tuint32_t    iend_len;\n\tuint8_t     iend_sign[4];\n\tuint32_t    iend_crc32;\n} png_footer_t;\n#pragma pack( pop )\n#endif // IMG_PNG_H\n\n"
  },
  {
    "path": "engine/common/imagelib/img_quant.c",
    "content": "/*\nimg_quant.c - image quantizer. based on Antony Dekker original code\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n\n#define palettesize 256\n#define netsize     255 // number of colours used\n\n#define prime1      499\n#define prime2      491\n#define prime3      487\n#define prime4      503\n\n#define minpicturebytes\t(3*prime4)\t\t// minimum size for input image\n\n#define maxnetpos\t\t(netsize-1)\n#define netbiasshift\t4\t\t\t// bias for colour values\n#define ncycles\t\t100\t\t\t// no. of learning cycles\n\n// defs for freq and bias\n#define intbiasshift\t16\t\t\t// bias for fractions\n#define intbias\t\t(1<<intbiasshift)\n#define gammashift  \t10\t\t\t// gamma = 1024\n#define gamma\t\t(1<<gammashift)\n#define betashift\t\t10\n#define beta\t\t(intbias>>betashift)\t// beta = 1 / 1024\n#define betagamma\t\t(intbias<<(gammashift - betashift))\n\n// defs for decreasing radius factor\n#define initrad\t\t(netsize>>3)\t\t// for 256 cols, radius starts\n#define radiusbiasshift\t6\t\t\t// at 32.0 biased by 6 bits\n#define radiusbias\t\t(1<<radiusbiasshift)\n#define initradius\t\t(initrad * radiusbias)\t// and decreases by a\n#define radiusdec\t\t30\t\t\t// factor of 1/30 each cycle\n\n// defs for decreasing alpha factor\n#define alphabiasshift\t10\t\t\t// alpha starts at 1.0\n#define initalpha\t\t(1<<alphabiasshift)\nint\t\t\talphadec;\t\t\t// biased by 10 bits\n\n// radbias and alpharadbias used for radpower calculation\n#define radbiasshift\t8\n#define radbias\t\t(1<<radbiasshift)\n#define alpharadbshift\t(alphabiasshift+radbiasshift)\n#define alpharadbias\t(1<<alpharadbshift)\n\n// types and global variables\nstatic byte\t\t*thepicture;\t\t// the input image itself\nstatic int\t\tlengthcount;\t\t// lengthcount = H*W*3\nstatic int\t\tsamplefac;\t\t// sampling factor 1..30\nstatic int\t\tnetwork[netsize][4];\t// the network itself\nstatic int\t\tnetindex[256];\t\t// for network lookup - really 256\nstatic int\t\tbias[netsize];\t\t// bias and freq arrays for learning\nstatic int\t\tfreq[netsize];\nstatic int\t\tradpower[initrad];\t\t// radpower for precomputation\n\nstatic void initnet( byte *thepic, int len, int sample )\n{\n\tregister int\ti, *p;\n\n\tthepicture = thepic;\n\tlengthcount = len;\n\tsamplefac = sample;\n\n\tfor( i = 0; i < netsize; i++ )\n\t{\n\t\tp = network[i];\n\t\tp[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;\n\t\tfreq[i] = intbias / netsize;\t// 1 / netsize\n\t\tbias[i] = 0;\n\t}\n}\n\n// Unbias network to give byte values 0..255 and record position i to prepare for sort\nstatic void unbiasnet( void )\n{\n\tint\ti, j, temp;\n\n\tfor( i = 0; i < netsize; i++ )\n\t{\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\t// OLD CODE: network[i][j] >>= netbiasshift;\n\t\t\t// Fix based on bug report by Juergen Weigert jw@suse.de\n\t\t\ttemp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;\n\t\t\tif( temp > 255 ) temp = 255;\n\t\t\tnetwork[i][j] = temp;\n\t\t}\n\n\t\tnetwork[i][3] = i; // record colour num\n\t}\n}\n\n// Insertion sort of network and building of netindex[0..255] (to do after unbias)\nstatic void inxbuild( void )\n{\n\tregister int\t*p, *q;\n\tregister int\ti, j, smallpos, smallval;\n\tint\t\tpreviouscol, startpos;\n\n\tpreviouscol = 0;\n\tstartpos = 0;\n\n\tfor( i = 0; i < netsize; i++ )\n\t{\n\t\tp = network[i];\n\t\tsmallpos = i;\n\t\tsmallval = p[1];\t\t\t// index on g\n\n\t\t// find smallest in i..netsize-1\n\t\tfor( j = i + 1; j < netsize; j++ )\n\t\t{\n\t\t\tq = network[j];\n\t\t\tif( q[1] < smallval )\n\t\t\t{\n\t\t\t\t// index on g\n\t\t\t\tsmallpos = j;\n\t\t\t\tsmallval = q[1];\t// index on g\n\t\t\t}\n\t\t}\n\n\t\tq = network[smallpos];\n\n\t\t// swap p (i) and q (smallpos) entries\n\t\tif( i != smallpos )\n\t\t{\n\t\t\tj = q[0];   q[0] = p[0];   p[0] = j;\n\t\t\tj = q[1];   q[1] = p[1];   p[1] = j;\n\t\t\tj = q[2];   q[2] = p[2];   p[2] = j;\n\t\t\tj = q[3];   q[3] = p[3];   p[3] = j;\n\t\t}\n\n\t\t// smallval entry is now in position i\n\t\tif( smallval != previouscol )\n\t\t{\n\t\t\tnetindex[previouscol] = (startpos+i) >> 1;\n\n\t\t\tfor( j = previouscol + 1; j < smallval; j++ )\n\t\t\t\tnetindex[j] = i;\n\n\t\t\tpreviouscol = smallval;\n\t\t\tstartpos = i;\n\t\t}\n\t}\n\n\tnetindex[previouscol] = (startpos + maxnetpos)>>1;\n\n\tfor( j = previouscol + 1; j < 256; j++ )\n\t\tnetindex[j] = maxnetpos; // really 256\n}\n\n\n// Search for BGR values 0..255 (after net is unbiased) and return colour index\nstatic int inxsearch( int r, int g, int b )\n{\n\tregister int\ti, j, dist, a, bestd;\n\tregister int\t*p;\n\tint\t\tbest;\n\n\tbestd = 1000;\t// biggest possible dist is 256 * 3\n\tbest = -1;\n\ti = netindex[g];\t// index on g\n\tj = i - 1;\t// start at netindex[g] and work outwards\n\n\twhile(( i < netsize ) || ( j >= 0 ))\n\t{\n\t\tif( i < netsize )\n\t\t{\n\t\t\tp = network[i];\n\t\t\tdist = p[1] - g;\t\t// inx key\n\n\t\t\tif( dist >= bestd )\n\t\t\t{\n\t\t\t\ti = netsize;\t// stop iter\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ti++;\n\t\t\t\tif( dist < 0 ) dist = -dist;\n\t\t\t\ta = p[2] - b;\n\t\t\t\tif( a < 0 ) a = -a;\n\t\t\t\tdist += a;\n\n\t\t\t\tif( dist < bestd )\n\t\t\t\t{\n\t\t\t\t\ta = p[0] - r;\n\t\t\t\t\tif( a < 0 ) a = -a;\n\t\t\t\t\tdist += a;\n\n\t\t\t\t\tif( dist < bestd )\n\t\t\t\t\t{\n\t\t\t\t\t\tbestd = dist;\n\t\t\t\t\t\tbest = p[3];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( j >= 0 )\n\t\t{\n\t\t\tp = network[j];\n\t\t\tdist = g - p[1]; // inx key - reverse dif\n\n\t\t\tif( dist >= bestd )\n\t\t\t{\n\t\t\t\tj = -1; // stop iter\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tj--;\n\t\t\t\tif( dist < 0 ) dist = -dist;\n\t\t\t\ta = p[2] - b;\n\t\t\t\tif( a < 0 ) a = -a;\n\t\t\t\tdist += a;\n\n\t\t\t\tif( dist < bestd )\n\t\t\t\t{\n\t\t\t\t\ta = p[0] - r;\n\t\t\t\t\tif( a < 0 ) a = -a;\n\t\t\t\t\tdist += a;\n\t\t\t\t\tif( dist < bestd )\n\t\t\t\t\t{\n\t\t\t\t\t\tbestd = dist;\n\t\t\t\t\t\tbest = p[3];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn best;\n}\n\n// Search for biased BGR values\nstatic int contest( int r, int g, int b )\n{\n\tregister int\t*p, *f, *n;\n\tregister int\ti, dist, a, biasdist, betafreq;\n\tint\t\tbestpos, bestbiaspos, bestd, bestbiasd;\n\n\t// finds closest neuron (min dist) and updates freq\n\t// finds best neuron (min dist-bias) and returns position\n\t// for frequently chosen neurons, freq[i] is high and bias[i] is negative\n\t// bias[i] = gamma * ((1 / netsize) - freq[i])\n\tbestd = INT_MAX;\n\tbestbiasd = bestd;\n\tbestpos = -1;\n\tbestbiaspos = bestpos;\n\tp = bias;\n\tf = freq;\n\n\tfor( i = 0; i < netsize; i++ )\n\t{\n\t\tn = network[i];\n\t\tdist = n[2] - b;\n\t\tif( dist < 0 ) dist = -dist;\n\t\ta = n[1] - g;\n\t\tif( a < 0 ) a = -a;\n\t\tdist += a;\n\t\ta = n[0] - r;\n\t\tif( a < 0 ) a = -a;\n\t\tdist += a;\n\n\t\tif( dist < bestd )\n\t\t{\n\t\t\tbestd = dist;\n\t\t\tbestpos = i;\n\t\t}\n\n\t\tbiasdist = dist - ((*p) >> (intbiasshift - netbiasshift));\n\n\t\tif( biasdist < bestbiasd )\n\t\t{\n\t\t\tbestbiasd = biasdist;\n\t\t\tbestbiaspos = i;\n\t\t}\n\n\t\tbetafreq = (*f >> betashift);\n\t\t*f++ -= betafreq;\n\t\t*p++ += (betafreq << gammashift);\n\t}\n\n\tfreq[bestpos] += beta;\n\tbias[bestpos] -= betagamma;\n\n\treturn bestbiaspos;\n}\n\n// Move neuron i towards biased (b,g,r) by factor alpha\nstatic void altersingle( int alpha, int i, int r, int g, int b )\n{\n\tregister int\t*n;\n\n\tn = network[i];\t// alter hit neuron\n\t*n -= (alpha * (*n - r)) / initalpha;\n\tn++;\n\t*n -= (alpha * (*n - g)) / initalpha;\n\tn++;\n\t*n -= (alpha * (*n - b)) / initalpha;\n}\n\n// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]\nstatic void alterneigh( int rad, int i, int r, int g, int b )\n{\n\tregister int\tj, k, lo, hi, a;\n\tregister int\t*p, *q;\n\n\tlo = i - rad;\n\tif( lo < -1 ) lo = -1;\n\thi = i + rad;\n\tif( hi > netsize ) hi = netsize;\n\n\tj = i + 1;\n\tk = i - 1;\n\tq = radpower;\n\n\twhile(( j < hi ) || ( k > lo ))\n\t{\n\t\ta = (*(++q));\n\n\t\tif( j < hi )\n\t\t{\n\t\t\tp = network[j];\n\t\t\t*p -= (a * (*p - r)) / alpharadbias;\n\t\t\tp++;\n\t\t\t*p -= (a * (*p - g)) / alpharadbias;\n\t\t\tp++;\n\t\t\t*p -= (a * (*p - b)) / alpharadbias;\n\t\t\tj++;\n\t\t}\n\n\t\tif( k > lo )\n\t\t{\n\t\t\tp = network[k];\n\t\t\t*p -= (a * (*p - r)) / alpharadbias;\n\t\t\tp++;\n\t\t\t*p -= (a * (*p - g)) / alpharadbias;\n\t\t\tp++;\n\t\t\t*p -= (a * (*p - b)) / alpharadbias;\n\t\t\tk--;\n\t\t}\n\t}\n}\n\n// Main Learning Loop\nstatic void learn( void )\n{\n\tregister byte\t*p;\n\tregister int\ti, j, r, g, b;\n\tint\t\tradius, rad, alpha, step;\n\tint\t\tdelta, samplepixels;\n\tbyte\t\t*lim;\n\n\talphadec = 30 + ((samplefac - 1) / 3);\n\tp = thepicture;\n\tlim = thepicture + lengthcount;\n\tsamplepixels = lengthcount / (image.bpp * samplefac);\n\tdelta = samplepixels / ncycles;\n\talpha = initalpha;\n\tradius = initradius;\n\n\trad = radius >> radiusbiasshift;\n\tif( rad <= 1 ) rad = 0;\n\n\tfor( i = 0; i < rad; i++ )\n\t\tradpower[i] = alpha * ((( rad * rad - i * i ) * radbias ) / ( rad * rad ));\n\n\tif( delta <= 0 ) return;\n\n\tif(( lengthcount % prime1 ) != 0 )\n\t{\n\t\tstep = prime1 * image.bpp;\n\t}\n\telse if(( lengthcount % prime2 ) != 0 )\n\t{\n\t\tstep = prime2 * image.bpp;\n\t}\n\telse if(( lengthcount % prime3 ) != 0 )\n\t{\n\t\tstep = prime3 * image.bpp;\n\t}\n\telse\n\t{\n\t\tstep = prime4 * image.bpp;\n\t}\n\n\ti = 0;\n\n\twhile( i < samplepixels )\n\t{\n\t\tr = p[0] << netbiasshift;\n\t\tg = p[1] << netbiasshift;\n\t\tb = p[2] << netbiasshift;\n\t\tj = contest( r, g, b );\n\n\t\taltersingle( alpha, j, r, g, b );\n\t\tif( rad ) alterneigh( rad, j, r, g, b );   // alter neighbours\n\n\t\tp += step;\n\t\twhile( p >= lim ) p -= lengthcount;\n\n\t\ti++;\n\n\t\tif( i % delta == 0 )\n\t\t{\n\t\t\talpha -= alpha / alphadec;\n\t\t\tradius -= radius / radiusdec;\n\t\t\trad = radius >> radiusbiasshift;\n\t\t\tif( rad <= 1 ) rad = 0;\n\n\t\t\tfor( j = 0; j < rad; j++ )\n\t\t\t\tradpower[j] = alpha * ((( rad * rad - j * j ) * radbias ) / ( rad * rad ));\n\t\t}\n\t}\n}\n\n// returns the actual number of palette entries.\nrgbdata_t *Image_Quantize( rgbdata_t *pic )\n{\n\tint\ti;\n\n\t// quick case to reject unneeded conversions\n\tif( pic->type == PF_INDEXED_24 || pic->type ==  PF_INDEXED_32 )\n\t\treturn pic;\n\n\tImage_CopyParms( pic );\n\timage.size = image.width * image.height;\n\timage.bpp = PFDesc[pic->type].bpp;\n\timage.ptr = 0;\n\n\t// allocate 8-bit buffer\n\timage.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, image.size );\n\n\tinitnet( pic->buffer, pic->size, 10 );\n\tlearn();\n\tunbiasnet();\n\n\tpic->palette = Mem_Malloc( host.imagepool, palettesize * 3 );\n\n\tfor( i = 0; i < netsize; i++ )\n\t{\n\t\tpic->palette[i*3+0] = network[i][0];\t// red\n\t\tpic->palette[i*3+1] = network[i][1];\t// green\n\t\tpic->palette[i*3+2] = network[i][2];\t// blue\n\t}\n\n\tfor( ; i < palettesize; i++ )\n\t{\n\t\tpic->palette[i*3+0] = 0;\n\t\tpic->palette[i*3+1] = 0;\n\t\tpic->palette[i*3+2] = 0;\n\t}\n\n\tinxbuild();\n\n\tfor( i = 0; i < image.width * image.height; i++ )\n\t{\n\t\timage.tempbuffer[i] = inxsearch( pic->buffer[i*image.bpp+0], pic->buffer[i*image.bpp+1], pic->buffer[i*image.bpp+2] );\n\t}\n\n\tpic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size );\n\tmemcpy( pic->buffer, image.tempbuffer, image.size );\n\tpic->type = PF_INDEXED_24;\n\tpic->size = image.size;\n\n\treturn pic;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_tga.c",
    "content": "/*\nimg_tga.c - tga format load & save\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"img_tga.h\"\n\n/*\n=============\nImage_LoadTGA\n=============\n*/\nqboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tint\ti, columns, rows, row_inc, row, col;\n\tbyte\t*buf_p, *pixbuf, *targa_rgba;\n\trgba_t\tpalette[256];\n\tbyte\tred = 0, green = 0, blue = 0, alpha = 0;\n\tint\treadpixelcount, pixelcount;\n\tuint\treflectivity[3] = { 0, 0, 0 };\n\tqboolean\tcompressed;\n\ttga_t\ttarga_header;\n\n\tif( filesize < sizeof( tga_t ))\n\t\treturn false;\n\n\tbuf_p = (byte *)buffer;\n\ttarga_header.id_length = *buf_p++;\n\ttarga_header.colormap_type = *buf_p++;\n\ttarga_header.image_type = *buf_p++;\n\n\ttarga_header.colormap_index = buf_p[0] + buf_p[1] * 256;\t\tbuf_p += 2;\n\ttarga_header.colormap_length = buf_p[0] + buf_p[1] * 256;\t\tbuf_p += 2;\n\ttarga_header.colormap_size = *buf_p;\t\t\t\tbuf_p += 1;\n\ttarga_header.x_origin = *(short *)buf_p;\t\t\tbuf_p += 2;\n\ttarga_header.y_origin = *(short *)buf_p;\t\t\tbuf_p += 2;\n\ttarga_header.width = image.width = *(short *)buf_p;\t\tbuf_p += 2;\n\ttarga_header.height = image.height = *(short *)buf_p;\t\tbuf_p += 2;\n\ttarga_header.pixel_size = *buf_p++;\n\ttarga_header.attributes = *buf_p++;\n\tif( targa_header.id_length != 0 ) buf_p += targa_header.id_length;\t// skip TARGA image comment\n\n\t// check for tga file\n\tif( !Image_ValidSize( name )) return false;\n\n\timage.type = PF_RGBA_32; // always exctracted to 32-bit buffer\n\n\tif( targa_header.image_type == 1 || targa_header.image_type == 9 )\n\t{\n\t\t// uncompressed colormapped image\n\t\tif( targa_header.pixel_size != 8 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) Only 8 bit images supported for type 1 and 9\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t\tif( targa_header.colormap_length != 256 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) Only 8 bit colormaps are supported for type 1 and 9\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t\tif( targa_header.colormap_index )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) colormap_index is not supported for type 1 and 9\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t\tif( targa_header.colormap_size == 24 )\n\t\t{\n\t\t\tfor( i = 0; i < targa_header.colormap_length; i++ )\n\t\t\t{\n\t\t\t\tpalette[i][2] = *buf_p++;\n\t\t\t\tpalette[i][1] = *buf_p++;\n\t\t\t\tpalette[i][0] = *buf_p++;\n\t\t\t\tpalette[i][3] = 255;\n\t\t\t}\n\t\t}\n\t\telse if( targa_header.colormap_size == 32 )\n\t\t{\n\t\t\tfor( i = 0; i < targa_header.colormap_length; i++ )\n\t\t\t{\n\t\t\t\tpalette[i][2] = *buf_p++;\n\t\t\t\tpalette[i][1] = *buf_p++;\n\t\t\t\tpalette[i][0] = *buf_p++;\n\t\t\t\tpalette[i][3] = *buf_p++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if( targa_header.image_type == 2 || targa_header.image_type == 10 )\n\t{\n\t\t// uncompressed or RLE compressed RGB\n\t\tif( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) Only 32 or 24 bit images supported for type 2 and 10\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if( targa_header.image_type == 3 || targa_header.image_type == 11 )\n\t{\n\t\t// uncompressed greyscale\n\t\tif( targa_header.pixel_size != 8 && targa_header.pixel_size != 16 )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: (%s) Only 8 bit images supported for type 3 and 11\\n\", __func__, name );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tcolumns = targa_header.width;\n\trows = targa_header.height;\n\n\timage.size = image.width * image.height * 4;\n\ttarga_rgba = image.rgba = Mem_Malloc( host.imagepool, image.size );\n\n\t// if bit 5 of attributes isn't set, the image has been stored from bottom to top\n\tif( !Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 )\n\t{\n\t\tpixbuf = targa_rgba;\n\t\trow_inc = 0;\n\t}\n\telse\n\t{\n\t\tpixbuf = targa_rgba + ( rows - 1 ) * columns * 4;\n\t\trow_inc = -columns * 4 * 2;\n\t}\n\n\tcompressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 );\n\tfor( row = col = 0; row < rows; )\n\t{\n\t\tpixelcount = 0x10000;\n\t\treadpixelcount = 0x10000;\n\n\t\tif( compressed )\n\t\t{\n\t\t\tpixelcount = *buf_p++;\n\t\t\tif( pixelcount & 0x80 )  // run-length packet\n\t\t\t\treadpixelcount = 1;\n\t\t\tpixelcount = 1 + ( pixelcount & 0x7f );\n\t\t}\n\n\t\twhile( pixelcount-- && ( row < rows ) )\n\t\t{\n\t\t\tif( readpixelcount-- > 0 )\n\t\t\t{\n\t\t\t\tswitch( targa_header.image_type )\n\t\t\t\t{\n\t\t\t\tcase 1:\n\t\t\t\tcase 9:\n\t\t\t\t\t// colormapped image\n\t\t\t\t\tblue = *buf_p++;\n\t\t\t\t\tif( blue < targa_header.colormap_length )\n\t\t\t\t\t{\n\t\t\t\t\t\tred = palette[blue][0];\n\t\t\t\t\t\tgreen = palette[blue][1];\n\t\t\t\t\t\talpha = palette[blue][3];\n\t\t\t\t\t\tblue = palette[blue][2];\n\t\t\t\t\t\tif( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\tcase 10:\n\t\t\t\t\t// 24 or 32 bit image\n\t\t\t\t\tblue = *buf_p++;\n\t\t\t\t\tgreen = *buf_p++;\n\t\t\t\t\tred = *buf_p++;\n\t\t\t\t\talpha = 255;\n\t\t\t\t\tif( targa_header.pixel_size == 32 )\n\t\t\t\t\t{\n\t\t\t\t\t\talpha = *buf_p++;\n\t\t\t\t\t\tif( alpha != 255 )\n\t\t\t\t\t\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\tcase 11:\n\t\t\t\t\t// greyscale image\n\t\t\t\t\tblue = green = red = *buf_p++;\n\t\t\t\t\tif( targa_header.pixel_size == 16 )\n\t\t\t\t\t{\n\t\t\t\t\t\talpha = *buf_p++;\n\t\t\t\t\t\tif( alpha != 255 )\n\t\t\t\t\t\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\talpha = 255;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( red != green || green != blue )\n\t\t\t\timage.flags |= IMAGE_HAS_COLOR;\n\n\t\t\treflectivity[0] += red;\n\t\t\treflectivity[1] += green;\n\t\t\treflectivity[2] += blue;\n\n\t\t\t*pixbuf++ = red;\n\t\t\t*pixbuf++ = green;\n\t\t\t*pixbuf++ = blue;\n\t\t\t*pixbuf++ = alpha;\n\t\t\tif( ++col == columns )\n\t\t\t{\n\t\t\t\t// run spans across rows\n\t\t\t\trow++;\n\t\t\t\tcol = 0;\n\t\t\t\tpixbuf += row_inc;\n\t\t\t}\n\t\t}\n\t}\n\n\tVectorDivide( reflectivity, ( image.width * image.height ), image.fogParams );\n\timage.depth = 1;\n\n\treturn true;\n}\n\n/*\n=============\nImage_SaveTGA\n=============\n*/\nqboolean Image_SaveTGA( const char *name, rgbdata_t *pix )\n{\n\tint\t\ty, outsize, pixel_size;\n\tconst uint8_t\t*bufend, *in;\n\tuint8_t\t\t*buffer, *out;\n\ttga_t\t\ttarga_header = {0};\n\tconst char\tcomment[] = \"Generated by Xash ImageLib\";\n\n\tif( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE ))\n\t\treturn false; // already existed\n\n\t// bogus parameter check\n\tif( !pix->buffer )\n\t\treturn false;\n\n\t// get image description\n\tswitch( pix->type )\n\t{\n\tcase PF_RGB_24:\n\tcase PF_BGR_24: pixel_size = 3; break;\n\tcase PF_RGBA_32:\n\tcase PF_BGRA_32: pixel_size = 4; break;\n\tdefault:\n\t\treturn false;\n\t}\n\n\toutsize = pix->width * pix->height * pixel_size;\n\toutsize += sizeof( tga_t );\n\toutsize += sizeof( comment ) - 1;\n\n\tbuffer = (uint8_t *)Mem_Malloc( host.imagepool, outsize );\n\n\t// prepare header\n\ttarga_header.id_length = sizeof( comment ) - 1; // tga comment length\n\ttarga_header.image_type = 2; // uncompressed type\n\ttarga_header.width = pix->width;\n\ttarga_header.height = pix->height;\n\n\tif( pix->flags & IMAGE_HAS_ALPHA )\n\t{\n\t\ttarga_header.pixel_size = 32;\n\t\ttarga_header.attributes = 8; // 8 bits of alpha\n\t}\n\telse\n\t{\n\t\ttarga_header.pixel_size = 24;\n\t\ttarga_header.attributes = 0;\n\t}\n\n\tout = buffer;\n\n\tmemcpy( out, &targa_header, sizeof( tga_t ) );\n\tout += sizeof( tga_t );\n\n\tmemcpy( out, comment, sizeof( comment ) - 1 );\n\tout += sizeof( comment ) - 1;\n\n\tswitch( pix->type )\n\t{\n\tcase PF_RGB_24:\n\tcase PF_RGBA_32:\n\t\t// swap rgba to bgra and flip upside down\n\t\tfor( y = pix->height - 1; y >= 0; y-- )\n\t\t{\n\t\t\tin = pix->buffer + y * pix->width * pixel_size;\n\t\t\tbufend = in + pix->width * pixel_size;\n\t\t\tfor( ; in < bufend; in += pixel_size )\n\t\t\t{\n\t\t\t\t*out++ = in[2];\n\t\t\t\t*out++ = in[1];\n\t\t\t\t*out++ = in[0];\n\t\t\t\tif( pix->flags & IMAGE_HAS_ALPHA )\n\t\t\t\t\t*out++ = in[3];\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase PF_BGR_24:\n\tcase PF_BGRA_32:\n\t\t// flip upside down\n\t\tfor( y = pix->height - 1; y >= 0; y-- )\n\t\t{\n\t\t\tin = pix->buffer + y * pix->width * pixel_size;\n\t\t\tbufend = in + pix->width * pixel_size;\n\t\t\tfor( ; in < bufend; in += pixel_size )\n\t\t\t{\n\t\t\t\t*out++ = in[0];\n\t\t\t\t*out++ = in[1];\n\t\t\t\t*out++ = in[2];\n\t\t\t\tif( pix->flags & IMAGE_HAS_ALPHA )\n\t\t\t\t\t*out++ = in[3];\n\t\t\t}\n\t\t}\n\t\tbreak;\n\t}\n\n\tFS_WriteFile( name, buffer, outsize );\n\n\tMem_Free( buffer );\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_tga.h",
    "content": "/*\nimg_tga.h - tga format reference\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef IMG_TGA_H\n#define IMG_TGA_H\n/*\n========================================================================\n\n.TGA image format\t(Truevision Targa)\n\n========================================================================\n*/\n#pragma pack( push, 1 )\ntypedef struct tga_s\n{\n\tuint8_t  id_length;\n\tuint8_t  colormap_type;\n\tuint8_t  image_type;\n\tuint16_t colormap_index;\n\tuint16_t colormap_length;\n\tuint8_t  colormap_size;\n\tuint16_t x_origin;\n\tuint16_t y_origin;\n\tuint16_t width;\n\tuint16_t height;\n\tuint8_t  pixel_size;\n\tuint8_t  attributes;\n} tga_t;\n#pragma pack( pop )\n#endif // IMG_TGA_H\n\n"
  },
  {
    "path": "engine/common/imagelib/img_utils.c",
    "content": "/*\nimg_utils.c - image common tools\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"mod_local.h\"\n\n#define LERPBYTE( i )\tr = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r )\n#define FILTER_SIZE\t\t5\n\nuint d_8toQ1table[256];\nuint d_8toHLtable[256];\nuint d_8to24table[256];\n\nqboolean q1palette_init = false;\nqboolean hlpalette_init = false;\n\nstatic byte palette_q1[768] =\n{\n0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,\n171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,\n47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,\n27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123,\n123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,\n7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,\n0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7,\n95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,\n87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,\n243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,\n95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,\n83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,\n143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,\n19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,\n107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,\n95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,\n243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,\n0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47,\n47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,\n7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,\n59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,\n243,147,255,247,199,255,255,255,159,91,83\n};\n\n// this is used only for particle colors\nstatic byte palette_hl[768] =\n{\n0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171,\n171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63,\n47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27,\n27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123,\n123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71,\n7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79,\n0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7,\n95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19,\n87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255,\n243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123,\n95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119,\n83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107,\n143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35,\n19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83,\n107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107,\n95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255,\n243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55,\n0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47,\n47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,\n7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,\n59,183,155,55,199,195,55,231,227,87,0,255,0,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243,\n147,255,247,199,255,255,255,159,91,83\n};\n\n/*\n=============================================================================\n\n\tXASH3D LOAD IMAGE FORMATS\n\n=============================================================================\n*/\n// stub\nstatic const loadpixformat_t load_null[] =\n{\n{ NULL, NULL, IL_HINT_NO }\n};\n\nstatic const loadpixformat_t load_game[] =\n{\n{ \"dds\", Image_LoadDDS, IL_HINT_NO },   // dds for world and studio models\n{ \"bmp\", Image_LoadBMP, IL_HINT_NO },   // WON menu images\n{ \"tga\", Image_LoadTGA, IL_HINT_NO },   // hl vgui menus\n{ \"png\", Image_LoadPNG, IL_HINT_NO },   // NightFire 007 menus\n{ \"wad\", Image_LoadWAD, IL_HINT_NO },   // hl wad files\n{ \"mip\", Image_LoadMIP, IL_HINT_NO },   // hl textures from wad or buffer\n{ \"mdl\", Image_LoadMDL, IL_HINT_HL },   // hl studio model skins\n{ \"spr\", Image_LoadSPR, IL_HINT_HL },   // hl sprite frames\n{ \"lmp\", Image_LoadLMP, IL_HINT_NO },   // hl menu images (cached.wad etc)\n{ \"fnt\", Image_LoadFNT, IL_HINT_HL },   // hl console font (fonts.wad etc)\n{ \"pal\", Image_LoadPAL, IL_HINT_NO },   // install studio\\sprite palette\n{ \"ktx2\", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models\n{ NULL, NULL, IL_HINT_NO }\n};\n\n/*\n=============================================================================\n\n\tXASH3D SAVE IMAGE FORMATS\n\n=============================================================================\n*/\n// stub\nstatic const savepixformat_t save_null[] =\n{\n{ NULL, NULL }\n};\n\n// Xash3D normal instance\nstatic const savepixformat_t save_game[] =\n{\n{ \"tga\", Image_SaveTGA }, // tga screenshots\n{ \"bmp\", Image_SaveBMP }, // bmp levelshots or screenshots\n{ \"png\", Image_SavePNG }, // png screenshots\n{ \"wad\", Image_SaveWAD }, // player logo in tempdecal.wad\n{ NULL, NULL }\n};\n\nvoid Image_Setup( void )\n{\n\timage.cmd_flags = IL_USE_LERPING|IL_ALLOW_OVERWRITE;\n\timage.loadformats = load_game;\n\timage.saveformats = save_game;\n}\n\nvoid Image_Init( void )\n{\n\t// init pools\n\thost.imagepool = Mem_AllocPool( \"ImageLib Pool\" );\n\n\t// install image formats (can be re-install later by Image_Setup)\n\tswitch( host.type )\n\t{\n\tcase HOST_NORMAL:\n\t\tImage_Setup( );\n\t\tbreak;\n\tcase HOST_DEDICATED:\n\t\timage.cmd_flags = 0;\n\t\timage.loadformats = load_game;\n\t\timage.saveformats = save_null;\n\t\tbreak;\n\tdefault:\t// all other instances not using imagelib\n\t\timage.cmd_flags = 0;\n\t\timage.loadformats = load_null;\n\t\timage.saveformats = save_null;\n\t\tbreak;\n\t}\n\n\timage.tempbuffer = NULL;\n}\n\nvoid Image_Shutdown( void )\n{\n\tMem_Check(); // check for leaks\n\tMem_FreePool( &host.imagepool );\n}\n\nbyte *Image_Copy( size_t size )\n{\n\tbyte\t*out;\n\n\tout = Mem_Realloc( host.imagepool, image.tempbuffer, size );\n\timage.tempbuffer = NULL;\n\n\treturn out;\n}\n\n/*\n=================\nImage_CustomPalette\n=================\n*/\nqboolean Image_CustomPalette( void )\n{\n\treturn image.custom_palette;\n}\n\n/*\n=================\nImage_CheckFlag\n=================\n*/\nqboolean Image_CheckFlag( int bit )\n{\n\tif( FBitSet( image.force_flags, bit ))\n\t\treturn true;\n\n\tif( FBitSet( image.cmd_flags, bit ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n=================\nImage_SetForceFlags\n=================\n*/\nvoid Image_SetForceFlags( uint flags )\n{\n\tSetBits( image.force_flags, flags );\n}\n\n/*\n=================\nImage_ClearForceFlags\n=================\n*/\nvoid Image_ClearForceFlags( void )\n{\n\timage.force_flags = 0;\n}\n\n/*\n=================\nImage_AddCmdFlags\n=================\n*/\nvoid Image_AddCmdFlags( uint flags )\n{\n\tSetBits( image.cmd_flags, flags );\n}\n\nqboolean Image_ValidSize( const char *name )\n{\n\tint max_width = IMAGE_MAXWIDTH;\n\tint max_height = IMAGE_MAXHEIGHT;\n\n\tif( Image_CheckFlag( IL_LOAD_PLAYER_DECAL ))\n\t{\n\t\tmax_width = PLDECAL_MAXWIDTH;\n\t\tmax_height = PLDECAL_MAXHEIGHT;\n\t}\n\n\tif( image.width > max_width || image.height > max_height || image.width <= 0 || image.height <= 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"Image: (%s) dims out of range [%dx%d]\\n\", name, image.width, image.height );\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nqboolean Image_LumpValidSize( const char *name )\n{\n\tif( image.width > LUMP_MAXWIDTH || image.height > LUMP_MAXHEIGHT || image.width <= 0 || image.height <= 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"Image: (%s) dims out of range [%dx%d]\\n\", name, image.width,image.height );\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n=============\nImage_ComparePalette\n=============\n*/\nint Image_ComparePalette( const byte *pal )\n{\n\tif( pal == NULL )\n\t\treturn PAL_INVALID;\n\telse if( !memcmp( palette_q1, pal, 765 )) // last color was changed\n\t\treturn PAL_QUAKE1;\n\telse if( !memcmp( palette_hl, pal, 765 ))\n\t\treturn PAL_HALFLIFE;\n\treturn PAL_CUSTOM;\n}\n\nstatic void Image_SetPalette( const byte *pal, uint *d_table )\n{\n\tbyte\trgba[4];\n\tuint uirgba; // TODO: palette looks byte-swapped on big-endian\n\tint\ti;\n\n\t// setup palette\n\tswitch( image.d_rendermode )\n\t{\n\tcase LUMP_NORMAL:\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\tmemcpy( rgba, &pal[i * 3], 3 );\n\t\t\trgba[3] = 0xFF;\n\t\t\tmemcpy( &uirgba, rgba, sizeof( uirgba ));\n\t\t\td_table[i] = uirgba;\n\t\t}\n\t\tbreak;\n\tcase LUMP_TEXGAMMA:\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\trgba[0] = TextureToGamma( pal[i * 3 + 0] );\n\t\t\trgba[1] = TextureToGamma( pal[i * 3 + 1] );\n\t\t\trgba[2] = TextureToGamma( pal[i * 3 + 2] );\n\t\t\trgba[3] = 0xFF;\n\t\t\tmemcpy( &uirgba, rgba, sizeof( uirgba ));\n\t\t\td_table[i] = uirgba;\n\t\t}\n\t\tbreak;\n\tcase LUMP_GRADIENT:\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\trgba[0] = pal[765];\n\t\t\trgba[1] = pal[766];\n\t\t\trgba[2] = pal[767];\n\t\t\trgba[3] = i;\n\t\t\tmemcpy( &uirgba, rgba, sizeof( uirgba ));\n\t\t\td_table[i] = uirgba;\n\t\t}\n\t\tbreak;\n\tcase LUMP_MASKED:\n\t\tfor( i = 0; i < 255; i++ )\n\t\t{\n\t\t\trgba[0] = pal[i*3+0];\n\t\t\trgba[1] = pal[i*3+1];\n\t\t\trgba[2] = pal[i*3+2];\n\t\t\trgba[3] = 0xFF;\n\t\t\tmemcpy( &uirgba, rgba, sizeof( uirgba ));\n\t\t\td_table[i] = uirgba;\n\t\t}\n\t\td_table[255] = 0;\n\t\tbreak;\n\tcase LUMP_EXTENDED:\n\t\tfor( i = 0; i < 256; i++ )\n\t\t{\n\t\t\trgba[0] = pal[i*4+0];\n\t\t\trgba[1] = pal[i*4+1];\n\t\t\trgba[2] = pal[i*4+2];\n\t\t\trgba[3] = pal[i*4+3];\n\t\t\tmemcpy( &uirgba, rgba, sizeof( uirgba ));\n\t\t\td_table[i] = uirgba;\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic void Image_ConvertPalTo24bit( rgbdata_t *pic )\n{\n\tbyte\t*pal32, *pal24;\n\tbyte\t*converted;\n\tint\ti;\n\n\tif( pic->type == PF_INDEXED_24 )\n\t\treturn; // does nothing\n\n\tpal24 = converted = Mem_Malloc( host.imagepool, 768 );\n\tpal32 = pic->palette;\n\n\tfor( i = 0; i < 256; i++, pal24 += 3, pal32 += 4 )\n\t{\n\t\tpal24[0] = pal32[0];\n\t\tpal24[1] = pal32[1];\n\t\tpal24[2] = pal32[2];\n\t}\n\n\tMem_Free( pic->palette );\n\tpic->palette = converted;\n\tpic->type = PF_INDEXED_24;\n}\n\nvoid Image_CopyPalette32bit( void )\n{\n\tif( image.palette ) return; // already created ?\n\timage.palette = Mem_Malloc( host.imagepool, 1024 );\n\tmemcpy( image.palette, image.d_currentpal, 1024 );\n}\n\nvoid Image_CheckPaletteQ1( void )\n{\n\trgbdata_t\t*pic = FS_LoadImage( DEFAULT_INTERNAL_PALETTE, NULL, 0 );\n\n\tif( pic && pic->size == 1024 )\n\t{\n\t\tImage_ConvertPalTo24bit( pic );\n\t\tif( Image_ComparePalette( pic->palette ) == PAL_CUSTOM )\n\t\t{\n\t\t\timage.d_rendermode = LUMP_NORMAL;\n\t\t\tCon_DPrintf( \"custom quake palette detected\\n\" );\n\t\t\tImage_SetPalette( pic->palette, d_8toQ1table );\n\t\t\td_8toQ1table[255] = 0; // 255 is transparent\n\t\t\timage.custom_palette = true;\n\t\t\tq1palette_init = true;\n\t\t}\n\t}\n\n\tif( pic ) FS_FreeImage( pic );\n}\n\nvoid Image_GetPaletteQ1( void )\n{\n\tif( !q1palette_init )\n\t{\n\t\timage.d_rendermode = LUMP_NORMAL;\n\t\tImage_SetPalette( palette_q1, d_8toQ1table );\n\t\td_8toQ1table[255] = 0; // 255 is transparent\n\t\tq1palette_init = true;\n\t}\n\n\timage.d_rendermode = LUMP_QUAKE1;\n\timage.d_currentpal = d_8toQ1table;\n}\n\nvoid Image_GetPaletteHL( void )\n{\n\tif( !hlpalette_init )\n\t{\n\t\timage.d_rendermode = LUMP_NORMAL;\n\t\tImage_SetPalette( palette_hl, d_8toHLtable );\n\t\thlpalette_init = true;\n\t}\n\n\timage.d_rendermode = LUMP_HALFLIFE;\n\timage.d_currentpal = d_8toHLtable;\n}\n\nvoid Image_GetPaletteBMP( const byte *pal )\n{\n\timage.d_rendermode = LUMP_EXTENDED;\n\n\tif( pal )\n\t{\n\t\tImage_SetPalette( pal, d_8to24table );\n\t\timage.d_currentpal = d_8to24table;\n\t}\n}\n\nvoid Image_GetPaletteLMP( const byte *pal, int rendermode )\n{\n\timage.d_rendermode = rendermode;\n\n\tif( pal )\n\t{\n\t\tImage_SetPalette( pal, d_8to24table );\n\t\timage.d_currentpal = d_8to24table;\n\t}\n\telse\n\t{\n\t\tswitch( rendermode )\n\t\t{\n\t\tcase LUMP_QUAKE1:\n\t\t\tImage_GetPaletteQ1();\n\t\t\tbreak;\n\t\tcase LUMP_HALFLIFE:\n\t\t\tImage_GetPaletteHL();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// defaulting to half-life palette\n\t\t\tImage_GetPaletteHL();\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int pal_size )\n{\n\tfloat\tr, g, b;\n\tfloat\tmaxcol, mincol;\n\tfloat\thue, val, sat;\n\tint\ti;\n\n\thue = (float)(newHue * ( 360.0f / 255 ));\n\tpal_size = bound( 3, pal_size, 4 );\n\n\tfor( i = start; i <= end; i++ )\n\t{\n\t\tr = palSrc[i*pal_size+0];\n\t\tg = palSrc[i*pal_size+1];\n\t\tb = palSrc[i*pal_size+2];\n\n\t\tmaxcol = Q_max( Q_max( r, g ), b ) / 255.0f;\n\t\tmincol = Q_min( Q_min( r, g ), b ) / 255.0f;\n\n\t\tif( maxcol == 0 ) continue;\n\n\t\tval = maxcol;\n\t\tsat = (maxcol - mincol) / maxcol;\n\n\t\tmincol = val * (1.0f - sat);\n\n\t\tif( hue <= 120.0f )\n\t\t{\n\t\t\tb = mincol;\n\t\t\tif( hue < 60 )\n\t\t\t{\n\t\t\t\tr = val;\n\t\t\t\tg = mincol + hue * (val - mincol) / (120.0f - hue);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tg = val;\n\t\t\t\tr = mincol + (120.0f - hue) * (val - mincol) / hue;\n\t\t\t}\n\t\t}\n\t\telse if( hue <= 240.0f )\n\t\t{\n\t\t\tr = mincol;\n\t\t\tif( hue < 180.0f )\n\t\t\t{\n\t\t\t\tg = val;\n\t\t\t\tb = mincol + (hue - 120.0f) * (val - mincol) / (240.0f - hue);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tb = val;\n\t\t\t\tg = mincol + (240.0f - hue) * (val - mincol) / (hue - 120.0f);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tg = mincol;\n\t\t\tif( hue < 300.0f )\n\t\t\t{\n\t\t\t\tb = val;\n\t\t\t\tr = mincol + (hue - 240.0f) * (val - mincol) / (360.0f - hue);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tr = val;\n\t\t\t\tb = mincol + (360.0f - hue) * (val - mincol) / (hue - 240.0f);\n\t\t\t}\n\t\t}\n\n\t\tpalSrc[i*pal_size+0] = (byte)(r * 255);\n\t\tpalSrc[i*pal_size+1] = (byte)(g * 255);\n\t\tpalSrc[i*pal_size+2] = (byte)(b * 255);\n\t}\n}\n\nstatic void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size )\n{\n\tbyte\tdst[256], src[256];\n\tint\ti;\n\n\tpal_size = bound( 3, pal_size, 4 );\n\tfor( i = 0; i < 256; i++ )\n\t\tsrc[i] = i;\n\tmemcpy( dst, src, 256 );\n\n\tif( top < 128 )\n\t{\n\t\t// the artists made some backwards ranges. sigh.\n\t\tmemcpy( dst + SHIRT_HUE_START, src + top, 16 );\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < 16; i++ )\n\t\t\tdst[SHIRT_HUE_START+i] = src[top + 15 - i];\n\t}\n\n\tif( bottom < 128 )\n\t{\n\t\tmemcpy( dst + PANTS_HUE_START, src + bottom, 16 );\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < 16; i++ )\n\t\t\tdst[PANTS_HUE_START + i] = src[bottom + 15 - i];\n\t}\n\n\t// last color isn't changed\n\tfor( i = 0; i < 255; i++ )\n\t{\n\t\tpalSrc[i*pal_size+0] = palette_q1[dst[i]*3+0];\n\t\tpalSrc[i*pal_size+1] = palette_q1[dst[i]*3+1];\n\t\tpalSrc[i*pal_size+2] = palette_q1[dst[i]*3+2];\n\t}\n}\n\nvoid Image_CopyParms( rgbdata_t *src )\n{\n\tImage_Reset();\n\n\timage.width = src->width;\n\timage.height = src->height;\n\timage.type = src->type;\n\timage.flags = src->flags;\n\timage.size = src->size;\n\timage.palette = src->palette;\t// may be NULL\n\n\tmemcpy( image.fogParams, src->fogParams, sizeof( image.fogParams ));\n}\n\n/*\n============\nImage_Copy8bitRGBA\n\nNOTE: must call Image_GetPaletteXXX before used\n============\n*/\nqboolean Image_Copy8bitRGBA( const byte *in, byte *out, int pixels )\n{\n\tint\t*iout = (int *)out;\n\tbyte\t*fin = (byte *)in;\n\tbyte\t*col;\n\tint\ti;\n\n\tif( !in || !image.d_currentpal )\n\t\treturn false;\n\n\t// this is a base image with luma - clear luma pixels\n\tif( image.flags & IMAGE_HAS_LUMA )\n\t{\n\t\tfor( i = 0; i < image.width * image.height; i++ )\n\t\t\tfin[i] = fin[i] < 224 ? fin[i] : 0;\n\t}\n\n\t// check for color\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tcol = (byte *)&image.d_currentpal[i];\n\t\tif( col[0] != col[1] || col[1] != col[2] )\n\t\t{\n\t\t\timage.flags |= IMAGE_HAS_COLOR;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\twhile( pixels >= 8 )\n\t{\n\t\tiout[0] = image.d_currentpal[in[0]];\n\t\tiout[1] = image.d_currentpal[in[1]];\n\t\tiout[2] = image.d_currentpal[in[2]];\n\t\tiout[3] = image.d_currentpal[in[3]];\n\t\tiout[4] = image.d_currentpal[in[4]];\n\t\tiout[5] = image.d_currentpal[in[5]];\n\t\tiout[6] = image.d_currentpal[in[6]];\n\t\tiout[7] = image.d_currentpal[in[7]];\n\n\t\tin += 8;\n\t\tiout += 8;\n\t\tpixels -= 8;\n\t}\n\n\tif( pixels & 4 )\n\t{\n\t\tiout[0] = image.d_currentpal[in[0]];\n\t\tiout[1] = image.d_currentpal[in[1]];\n\t\tiout[2] = image.d_currentpal[in[2]];\n\t\tiout[3] = image.d_currentpal[in[3]];\n\t\tin += 4;\n\t\tiout += 4;\n\t}\n\n\tif( pixels & 2 )\n\t{\n\t\tiout[0] = image.d_currentpal[in[0]];\n\t\tiout[1] = image.d_currentpal[in[1]];\n\t\tin += 2;\n\t\tiout += 2;\n\t}\n\n\tif( pixels & 1 ) // last byte\n\t\tiout[0] = image.d_currentpal[in[0]];\n\timage.type = PF_RGBA_32;\t// update image type;\n\n\treturn true;\n}\n\nstatic void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth )\n{\n\tint\tj, xi, oldx = 0, f, fstep, endx, lerp;\n\n\tfstep = (int)(inwidth * 65536.0f / outwidth);\n\tendx = (inwidth-1);\n\n\tfor( j = 0, f = 0; j < outwidth; j++, f += fstep )\n\t{\n\t\txi = f>>16;\n\t\tif( xi != oldx )\n\t\t{\n\t\t\tin += (xi - oldx) * 4;\n\t\t\toldx = xi;\n\t\t}\n\t\tif( xi < endx )\n\t\t{\n\t\t\tlerp = f & 0xFFFF;\n\t\t\t*out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]);\n\t\t\t*out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]);\n\t\t\t*out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]);\n\t\t\t*out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]);\n\t\t}\n\t\telse // last pixel of the line has no pixel to lerp to\n\t\t{\n\t\t\t*out++ = in[0];\n\t\t\t*out++ = in[1];\n\t\t\t*out++ = in[2];\n\t\t\t*out++ = in[3];\n\t\t}\n\t}\n}\n\nstatic void Image_Resample24LerpLine( const byte *in, byte *out, int inwidth, int outwidth )\n{\n\tint\tj, xi, oldx = 0, f, fstep, endx, lerp;\n\n\tfstep = (int)(inwidth * 65536.0f / outwidth);\n\tendx = (inwidth-1);\n\n\tfor( j = 0, f = 0; j < outwidth; j++, f += fstep )\n\t{\n\t\txi = f>>16;\n\n\t\tif( xi != oldx )\n\t\t{\n\t\t\tin += (xi - oldx) * 3;\n\t\t\toldx = xi;\n\t\t}\n\n\t\tif( xi < endx )\n\t\t{\n\t\t\tlerp = f & 0xFFFF;\n\t\t\t*out++ = (byte)((((in[3] - in[0]) * lerp)>>16) + in[0]);\n\t\t\t*out++ = (byte)((((in[4] - in[1]) * lerp)>>16) + in[1]);\n\t\t\t*out++ = (byte)((((in[5] - in[2]) * lerp)>>16) + in[2]);\n\t\t}\n\t\telse // last pixel of the line has no pixel to lerp to\n\t\t{\n\t\t\t*out++ = in[0];\n\t\t\t*out++ = in[1];\n\t\t\t*out++ = in[2];\n\t\t}\n\t}\n}\n\nstatic void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )\n{\n\tconst byte *inrow;\n\tint\ti, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1);\n\tint\tinwidth4 = inwidth * 4;\n\tint\toutwidth4 = outwidth * 4;\n\tbyte\t*out = (byte *)outdata;\n\tbyte\t*resamplerow1;\n\tbyte\t*resamplerow2;\n\n\tfstep = (int)(inheight * 65536.0f / outheight);\n\n\tresamplerow1 = (byte *)Mem_Malloc( host.imagepool, outwidth * 4 * 2);\n\tresamplerow2 = resamplerow1 + outwidth * 4;\n\n\tinrow = (const byte *)indata;\n\n\tImage_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth );\n\tImage_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth );\n\n\tfor( i = 0, f = 0; i < outheight; i++, f += fstep )\n\t{\n\t\tyi = f>>16;\n\n\t\tif( yi < endy )\n\t\t{\n\t\t\tlerp = f & 0xFFFF;\n\t\t\tif( yi != oldy )\n\t\t\t{\n\t\t\t\tinrow = (byte *)indata + inwidth4 * yi;\n\t\t\t\tif( yi == oldy + 1 ) memcpy( resamplerow1, resamplerow2, outwidth4 );\n\t\t\t\telse Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth );\n\t\t\t\tImage_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth );\n\t\t\t\toldy = yi;\n\t\t\t}\n\n\t\t\tj = outwidth - 4;\n\n\t\t\twhile( j >= 0 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tLERPBYTE( 3);\n\t\t\t\tLERPBYTE( 4);\n\t\t\t\tLERPBYTE( 5);\n\t\t\t\tLERPBYTE( 6);\n\t\t\t\tLERPBYTE( 7);\n\t\t\t\tLERPBYTE( 8);\n\t\t\t\tLERPBYTE( 9);\n\t\t\t\tLERPBYTE(10);\n\t\t\t\tLERPBYTE(11);\n\t\t\t\tLERPBYTE(12);\n\t\t\t\tLERPBYTE(13);\n\t\t\t\tLERPBYTE(14);\n\t\t\t\tLERPBYTE(15);\n\t\t\t\tout += 16;\n\t\t\t\tresamplerow1 += 16;\n\t\t\t\tresamplerow2 += 16;\n\t\t\t\tj -= 4;\n\t\t\t}\n\n\t\t\tif( j & 2 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tLERPBYTE( 3);\n\t\t\t\tLERPBYTE( 4);\n\t\t\t\tLERPBYTE( 5);\n\t\t\t\tLERPBYTE( 6);\n\t\t\t\tLERPBYTE( 7);\n\t\t\t\tout += 8;\n\t\t\t\tresamplerow1 += 8;\n\t\t\t\tresamplerow2 += 8;\n\t\t\t}\n\n\t\t\tif( j & 1 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tLERPBYTE( 3);\n\t\t\t\tout += 4;\n\t\t\t\tresamplerow1 += 4;\n\t\t\t\tresamplerow2 += 4;\n\t\t\t}\n\n\t\t\tresamplerow1 -= outwidth4;\n\t\t\tresamplerow2 -= outwidth4;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( yi != oldy )\n\t\t\t{\n\t\t\t\tinrow = (byte *)indata + inwidth4 * yi;\n\t\t\t\tif( yi == oldy + 1 ) memcpy( resamplerow1, resamplerow2, outwidth4 );\n\t\t\t\telse Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth);\n\t\t\t\toldy = yi;\n\t\t\t}\n\n\t\t\tmemcpy( out, resamplerow1, outwidth4 );\n\t\t}\n\t}\n\n\tMem_Free( resamplerow1 );\n}\n\nstatic void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )\n{\n\tint\ti, j;\n\tuint\tfrac, fracstep;\n\tint\t*inrow, *out = (int *)outdata; // relies on int being 4 bytes\n\n\tfracstep = inwidth * 0x10000 / outwidth;\n\n\tfor( i = 0; i < outheight; i++)\n\t{\n\t\tinrow = (int *)indata + inwidth * (i * inheight / outheight);\n\t\tfrac = fracstep>>1;\n\t\tj = outwidth - 4;\n\n\t\twhile( j >= 0 )\n\t\t{\n\t\t\tout[0] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout[1] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout[2] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout[3] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout += 4;\n\t\t\tj -= 4;\n\t\t}\n\n\t\tif( j & 2 )\n\t\t{\n\t\t\tout[0] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout[1] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout += 2;\n\t\t}\n\n\t\tif( j & 1 )\n\t\t{\n\t\t\tout[0] = inrow[frac >> 16];frac += fracstep;\n\t\t\tout += 1;\n\t\t}\n\t}\n}\n\nstatic void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )\n{\n\tconst byte *inrow;\n\tint\ti, j, r, yi, oldy, f, fstep, lerp, endy = (inheight - 1);\n\tint\tinwidth3 = inwidth * 3;\n\tint\toutwidth3 = outwidth * 3;\n\tbyte\t*out = (byte *)outdata;\n\tbyte\t*resamplerow1;\n\tbyte\t*resamplerow2;\n\n\tfstep = (int)(inheight * 65536.0f / outheight);\n\n\tresamplerow1 = (byte *)Mem_Malloc( host.imagepool, outwidth * 3 * 2 );\n\tresamplerow2 = resamplerow1 + outwidth*3;\n\n\tinrow = (const byte *)indata;\n\toldy = 0;\n\tImage_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );\n\tImage_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth );\n\n\tfor( i = 0, f = 0; i < outheight; i++, f += fstep )\n\t{\n\t\tyi = f>>16;\n\n\t\tif( yi < endy )\n\t\t{\n\t\t\tlerp = f & 0xFFFF;\n\t\t\tif( yi != oldy )\n\t\t\t{\n\t\t\t\tinrow = (byte *)indata + inwidth3 * yi;\n\t\t\t\tif( yi == oldy + 1) memcpy( resamplerow1, resamplerow2, outwidth3 );\n\t\t\t\telse Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );\n\t\t\t\tImage_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth );\n\t\t\t\toldy = yi;\n\t\t\t}\n\n\t\t\tj = outwidth - 4;\n\n\t\t\twhile( j >= 0 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tLERPBYTE( 3);\n\t\t\t\tLERPBYTE( 4);\n\t\t\t\tLERPBYTE( 5);\n\t\t\t\tLERPBYTE( 6);\n\t\t\t\tLERPBYTE( 7);\n\t\t\t\tLERPBYTE( 8);\n\t\t\t\tLERPBYTE( 9);\n\t\t\t\tLERPBYTE(10);\n\t\t\t\tLERPBYTE(11);\n\t\t\t\tout += 12;\n\t\t\t\tresamplerow1 += 12;\n\t\t\t\tresamplerow2 += 12;\n\t\t\t\tj -= 4;\n\t\t\t}\n\n\t\t\tif( j & 2 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tLERPBYTE( 3);\n\t\t\t\tLERPBYTE( 4);\n\t\t\t\tLERPBYTE( 5);\n\t\t\t\tout += 6;\n\t\t\t\tresamplerow1 += 6;\n\t\t\t\tresamplerow2 += 6;\n\t\t\t}\n\n\t\t\tif( j & 1 )\n\t\t\t{\n\t\t\t\tLERPBYTE( 0);\n\t\t\t\tLERPBYTE( 1);\n\t\t\t\tLERPBYTE( 2);\n\t\t\t\tout += 3;\n\t\t\t\tresamplerow1 += 3;\n\t\t\t\tresamplerow2 += 3;\n\t\t\t}\n\n\t\t\tresamplerow1 -= outwidth3;\n\t\t\tresamplerow2 -= outwidth3;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( yi != oldy )\n\t\t\t{\n\t\t\t\tinrow = (byte *)indata + inwidth3*yi;\n\t\t\t\tif( yi == oldy + 1) memcpy( resamplerow1, resamplerow2, outwidth3 );\n\t\t\t\telse Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth );\n\t\t\t\toldy = yi;\n\t\t\t}\n\n\t\t\tmemcpy( out, resamplerow1, outwidth3 );\n\t\t}\n\t}\n\n\tMem_Free( resamplerow1 );\n}\n\nstatic void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )\n{\n\tuint\tfrac, fracstep;\n\tint\ti, j, f, inwidth3 = inwidth * 3;\n\tbyte\t*inrow, *out = (byte *)outdata;\n\n\tfracstep = inwidth * 0x10000 / outwidth;\n\n\tfor( i = 0; i < outheight; i++)\n\t{\n\t\tinrow = (byte *)indata + inwidth3 * (i * inheight / outheight);\n\t\tfrac = fracstep>>1;\n\t\tj = outwidth - 4;\n\n\t\twhile( j >= 0 )\n\t\t{\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tj -= 4;\n\t\t}\n\n\t\tif( j & 2 )\n\t\t{\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tout += 2;\n\t\t}\n\n\t\tif( j & 1 )\n\t\t{\n\t\t\tf = (frac >> 16)*3;\n\t\t\t*out++ = inrow[f+0];\n\t\t\t*out++ = inrow[f+1];\n\t\t\t*out++ = inrow[f+2];\n\t\t\tfrac += fracstep;\n\t\t\tout += 1;\n\t\t}\n\t}\n}\n\nstatic void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )\n{\n\tint\ti, j;\n\tbyte\t*in, *inrow;\n\tuint\tfrac, fracstep;\n\tbyte\t*out = (byte *)outdata;\n\n\tin = (byte *)indata;\n\tfracstep = inwidth * 0x10000 / outwidth;\n\n\tfor( i = 0; i < outheight; i++, out += outwidth )\n\t{\n\t\tinrow = in + inwidth*(i*inheight/outheight);\n\t\tfrac = fracstep>>1;\n\n\t\tfor( j = 0; j < outwidth; j++ )\n\t\t{\n\t\t\tout[j] = inrow[frac>>16];\n\t\t\tfrac += fracstep;\n\t\t}\n\t}\n}\n\n/*\n================\nImage_Resample\n================\n*/\nbyte *Image_ResampleInternal( const void *indata, int inwidth, int inheight, int outwidth, int outheight, int type, qboolean *resampled )\n{\n\tqboolean\tquality = Image_CheckFlag( IL_USE_LERPING );\n\n\t// nothing to resample ?\n\tif( inwidth == outwidth && inheight == outheight )\n\t{\n\t\t*resampled = false;\n\t\treturn (byte *)indata;\n\t}\n\n\t// alloc new buffer\n\tswitch( type )\n\t{\n\tcase PF_INDEXED_24:\n\tcase PF_INDEXED_32:\n\t\timage.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight );\n\t\tImage_Resample8Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );\n\t\tbreak;\n\tcase PF_RGB_24:\n\tcase PF_BGR_24:\n\t\timage.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 3 );\n\t\tif( quality ) Image_Resample24Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );\n\t\telse Image_Resample24Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );\n\t\tbreak;\n\tcase PF_RGBA_32:\n\tcase PF_BGRA_32:\n\t\timage.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 4 );\n\t\tif( quality ) Image_Resample32Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );\n\t\telse Image_Resample32Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight );\n\t\tbreak;\n\tdefault:\n\t\t*resampled = false;\n\t\treturn (byte *)indata;\n\t}\n\n\t*resampled = true;\n\treturn image.tempbuffer;\n}\n\n/*\n================\nImage_Flip\n================\n*/\nbyte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int type, int flags )\n{\n\tint\ti, x, y;\n\tword\twidth = *srcwidth;\n\tword\theight = *srcheight;\n\tint\tsamples = PFDesc[type].bpp;\n\tqboolean\tflip_x = FBitSet( flags, IMAGE_FLIP_X ) ? true : false;\n\tqboolean\tflip_y = FBitSet( flags, IMAGE_FLIP_Y ) ? true : false;\n\tqboolean\tflip_i = FBitSet( flags, IMAGE_ROT_90 ) ? true : false;\n\tint\trow_inc = ( flip_y ? -samples : samples ) * width;\n\tint\tcol_inc = ( flip_x ? -samples : samples );\n\tint\trow_ofs = ( flip_y ? ( height - 1 ) * width * samples : 0 );\n\tint\tcol_ofs = ( flip_x ? ( width - 1 ) * samples : 0 );\n\tconst byte *p, *line;\n\tbyte\t*out;\n\n\t// nothing to process\n\tif( !FBitSet( flags, IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90 ))\n\t\treturn (byte *)in;\n\n\tswitch( type )\n\t{\n\tcase PF_INDEXED_24:\n\tcase PF_INDEXED_32:\n\tcase PF_RGB_24:\n\tcase PF_BGR_24:\n\tcase PF_RGBA_32:\n\tcase PF_BGRA_32:\n\t\timage.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height * samples );\n\t\tbreak;\n\tdefault:\n\t\treturn (byte *)in;\n\t}\n\n\tout = image.tempbuffer;\n\n\tif( flip_i )\n\t{\n\t\tfor( x = 0, line = in + col_ofs; x < width; x++, line += col_inc )\n\t\t\tfor( y = 0, p = line + row_ofs; y < height; y++, p += row_inc, out += samples )\n\t\t\t\tfor( i = 0; i < samples; i++ )\n\t\t\t\t\tout[i] = p[i];\n\t}\n\telse\n\t{\n\t\tfor( y = 0, line = in + row_ofs; y < height; y++, line += row_inc )\n\t\t\tfor( x = 0, p = line + col_ofs; x < width; x++, p += col_inc, out += samples )\n\t\t\t\tfor( i = 0; i < samples; i++ )\n\t\t\t\t\tout[i] = p[i];\n\t}\n\n\t// update dims\n\tif( FBitSet( flags, IMAGE_ROT_90 ))\n\t{\n\t\t*srcwidth = height;\n\t\t*srcheight = width;\n\t}\n\telse\n\t{\n\t\t*srcwidth = width;\n\t\t*srcheight = height;\n\t}\n\n\treturn image.tempbuffer;\n}\n\nstatic byte *Image_MakeLuma( byte *fin, int width, int height, int type, int flags )\n{\n\tbyte\t*out;\n\tint\ti;\n\n\tif( !FBitSet( flags, IMAGE_HAS_LUMA ))\n\t\treturn (byte *)fin;\n\n\tswitch( type )\n\t{\n\tcase PF_INDEXED_24:\n\tcase PF_INDEXED_32:\n\t\tout = image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height );\n\t\tfor( i = 0; i < width * height; i++ )\n\t\t\t*out++ = fin[i] >= 224 ? fin[i] : 0;\n\t\tbreak;\n\tdefault:\n\t\t// another formats does ugly result :(\n\t\tCon_Printf( S_ERROR \"%s: unsupported format %s\\n\", __func__, PFDesc[type].name );\n\t\treturn (byte *)fin;\n\t}\n\n\treturn image.tempbuffer;\n}\n\nqboolean Image_AddIndexedImageToPack( const byte *in, int width, int height )\n{\n\tint\tmipsize = width * height;\n\tqboolean\texpand_to_rgba = true;\n\n\tif( Image_CheckFlag( IL_KEEP_8BIT ))\n\t\texpand_to_rgba = false;\n\telse if( FBitSet( image.flags, IMAGE_HAS_LUMA|IMAGE_QUAKESKY ))\n\t\texpand_to_rgba = false;\n\n\timage.size = mipsize;\n\n\tif( expand_to_rgba ) image.size *= 4;\n\telse Image_CopyPalette32bit();\n\n\t// reallocate image buffer\n\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\tif( !expand_to_rgba ) memcpy( image.rgba, in, image.size );\n\telse if( !Image_Copy8bitRGBA( in, image.rgba, mipsize ))\n\t\treturn false; // probably pallette not installed\n\n\treturn true;\n}\n\n/*\n=============\nImage_Decompress\n\nforce to unpack any image to 32-bit buffer\n=============\n*/\nstatic qboolean Image_Decompress( const byte *data )\n{\n\tbyte\t*fin, *fout;\n\tint\ti, size;\n\n\tif( !data ) return false;\n\tfin = (byte *)data;\n\n\tsize = image.width * image.height * 4;\n\timage.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, size );\n\tfout = image.tempbuffer;\n\n\tswitch( PFDesc[image.type].format )\n\t{\n\tcase PF_INDEXED_24:\n\t\tif( image.flags & IMAGE_HAS_ALPHA )\n\t\t{\n\t\t\tif( image.flags & IMAGE_COLORINDEX )\n\t\t\t\tImage_GetPaletteLMP( image.palette, LUMP_GRADIENT );\n\t\t\telse Image_GetPaletteLMP( image.palette, LUMP_MASKED );\n\t\t}\n\t\telse Image_GetPaletteLMP( image.palette, LUMP_NORMAL );\n\t\t// intentionally fallthrough\n\tcase PF_INDEXED_32:\n\t\tif( !image.d_currentpal ) image.d_currentpal = (uint *)image.palette;\n\t\tif( !Image_Copy8bitRGBA( fin, fout, image.width * image.height ))\n\t\t\treturn false;\n\t\tbreak;\n\tcase PF_BGR_24:\n\t\tfor (i = 0; i < image.width * image.height; i++ )\n\t\t{\n\t\t\tfout[(i<<2)+0] = fin[i*3+2];\n\t\t\tfout[(i<<2)+1] = fin[i*3+1];\n\t\t\tfout[(i<<2)+2] = fin[i*3+0];\n\t\t\tfout[(i<<2)+3] = 255;\n\t\t}\n\t\tbreak;\n\tcase PF_RGB_24:\n\t\tfor (i = 0; i < image.width * image.height; i++ )\n\t\t{\n\t\t\tfout[(i<<2)+0] = fin[i*3+0];\n\t\t\tfout[(i<<2)+1] = fin[i*3+1];\n\t\t\tfout[(i<<2)+2] = fin[i*3+2];\n\t\t\tfout[(i<<2)+3] = 255;\n\t\t}\n\t\tbreak;\n\tcase PF_BGRA_32:\n\t\tfor( i = 0; i < image.width * image.height; i++ )\n\t\t{\n\t\t\tfout[i*4+0] = fin[i*4+2];\n\t\t\tfout[i*4+1] = fin[i*4+1];\n\t\t\tfout[i*4+2] = fin[i*4+0];\n\t\t\tfout[i*4+3] = fin[i*4+3];\n\t\t}\n\t\tbreak;\n\tcase PF_RGBA_32:\n\t\t// fast default case\n\t\tmemcpy( fout, fin, size );\n\t\tbreak;\n\tdefault: return false;\n\t}\n\n\t// set new size\n\timage.size = size;\n\n\treturn true;\n}\n\nstatic rgbdata_t *Image_DecompressInternal( rgbdata_t *pic )\n{\n\t// quick case to reject unneeded conversions\n\tif( pic->type == PF_RGBA_32 )\n\t\treturn pic;\n\n\tImage_CopyParms( pic );\n\timage.size = image.ptr = 0;\n\n\tImage_Decompress( pic->buffer );\n\n\t// now we can change type to RGBA\n\tpic->type = PF_RGBA_32;\n\n\tpic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size );\n\tmemcpy( pic->buffer, image.tempbuffer, image.size );\n\tif( pic->palette ) Mem_Free( pic->palette );\n\tpic->flags = image.flags;\n\tpic->palette = NULL;\n\n\treturn pic;\n}\n\nstatic rgbdata_t *Image_LightGamma( rgbdata_t *pic )\n{\n\tbyte\t*in = (byte *)pic->buffer;\n\tint\ti;\n\n\tif( pic->type != PF_RGBA_32 )\n\t\treturn pic;\n\n\tfor( i = 0; i < pic->width * pic->height; i++, in += 4 )\n\t{\n\t\tin[0] = LightToTexGamma( in[0] );\n\t\tin[1] = LightToTexGamma( in[1] );\n\t\tin[2] = LightToTexGamma( in[2] );\n\t}\n\n\treturn pic;\n}\n\nstatic qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor )\n{\n\tif( !pic->palette )\n\t\treturn false;\n\n\tswitch( pic->type )\n\t{\n\tcase PF_INDEXED_24:\n\t\tbreak;\n\tcase PF_INDEXED_32:\n\t\tImage_ConvertPalTo24bit( pic );\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\tif( Image_ComparePalette( pic->palette ) == PAL_QUAKE1 )\n\t{\n\t\tImage_PaletteTranslate( pic->palette, topColor * 16, bottomColor * 16, 3 );\n\t}\n\telse\n\t{\n\t\t// g-cont. preview images has a swapped top and bottom colors. I don't know why.\n\t\tImage_PaletteHueReplace( pic->palette, topColor, SUIT_HUE_START, SUIT_HUE_END, 3 );\n\t\tImage_PaletteHueReplace( pic->palette, bottomColor, PLATE_HUE_START, PLATE_HUE_END, 3 );\n\t}\n\n\treturn true;\n}\n\nqboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, float reserved )\n{\n\trgbdata_t\t*pic = *pix;\n\tqboolean\tresult = true;\n\tbyte\t*out;\n\n\t// check for buffers\n\tif( unlikely( !pic || !pic->buffer ))\n\t{\n\t\timage.force_flags = 0;\n\t\treturn false;\n\t}\n\n\tif( unlikely( !flags ))\n\t{\n\t\t// clear any force flags\n\t\timage.force_flags = 0;\n\t\treturn false; // no operation specfied\n\t}\n\n\tif( FBitSet( flags, IMAGE_MAKE_LUMA ))\n\t{\n\t\tout = Image_MakeLuma( pic->buffer, pic->width, pic->height, pic->type, pic->flags );\n\t\tif( pic->buffer != out ) memcpy( pic->buffer, image.tempbuffer, pic->size );\n\t\tClearBits( pic->flags, IMAGE_HAS_LUMA );\n\t}\n\n\tif( FBitSet( flags, IMAGE_REMAP ))\n\t{\n\t\t// NOTE: user should keep copy of indexed image manually for new changes\n\t\tif( Image_RemapInternal( pic, width, height ))\n\t\t\tpic = Image_DecompressInternal( pic );\n\t}\n\n\t// update format to RGBA if any\n\tif( FBitSet( flags, IMAGE_FORCE_RGBA ))\n\t\tpic = Image_DecompressInternal( pic );\n\n\tif( FBitSet( flags, IMAGE_LIGHTGAMMA ))\n\t\tpic = Image_LightGamma( pic );\n\n\tout = Image_FlipInternal( pic->buffer, &pic->width, &pic->height, pic->type, flags );\n\tif( pic->buffer != out ) memcpy( pic->buffer, image.tempbuffer, pic->size );\n\n\tif( FBitSet( flags, IMAGE_RESAMPLE ) && width > 0 && height > 0 )\n\t{\n\t\tint\tw = bound( 1, width, IMAGE_MAXWIDTH );\t// 1 - 4096\n\t\tint\th = bound( 1, height, IMAGE_MAXHEIGHT);\t// 1 - 4096\n\t\tqboolean\tresampled = false;\n\n\t\tout = Image_ResampleInternal((uint *)pic->buffer, pic->width, pic->height, w, h, pic->type, &resampled );\n\n\t\tif( resampled ) // resampled or filled\n\t\t{\n\t\t\tCon_Reportf( \"Image_Resample: from[%d x %d] to [%d x %d]\\n\", pic->width, pic->height, w, h );\n\t\t\tpic->width = w, pic->height = h;\n\t\t\tpic->size = w * h * PFDesc[pic->type].bpp;\n\t\t\tMem_Free( pic->buffer );\t\t// free original image buffer\n\t\t\tpic->buffer = Image_Copy( pic->size );\t// unzone buffer\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// not a resampled or filled\n\t\t\tresult = false;\n\t\t}\n\t}\n\n\t// quantize image\n\tif( FBitSet( flags, IMAGE_QUANTIZE ))\n\t\tpic = Image_Quantize( pic );\n\n\t*pix = pic;\n\n\t// clear any force flags\n\timage.force_flags = 0;\n\n\treturn result;\n}\n\n// This codebase has too many copies of this function:\n// - ref_gl has one\n// - ref_vk has one\n// - ref_soft has one\n// - many more places probably have one too\n// TODO figure out how to make it available for ref_*\nsize_t Image_ComputeSize( int type, int width, int height, int depth )\n{\n\tswitch( type )\n\t{\n\tcase PF_DXT1:\n\tcase PF_BC4_SIGNED:\n\tcase PF_BC4_UNSIGNED:\n\t\treturn ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 );\n\tcase PF_DXT3:\n\tcase PF_DXT5:\n\tcase PF_ATI2:\n\tcase PF_BC5_UNSIGNED:\n\tcase PF_BC5_SIGNED:\n\tcase PF_BC6H_SIGNED:\n\tcase PF_BC6H_UNSIGNED:\n\tcase PF_BC7_UNORM:\n\tcase PF_BC7_SRGB: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 );\n\tcase PF_LUMINANCE: return ( width * height * depth );\n\tcase PF_BGR_24:\n\tcase PF_RGB_24: return ( width * height * depth * 3 );\n\tcase PF_BGRA_32:\n\tcase PF_RGBA_32: return ( width * height * depth * 4 );\n\t}\n\n\treturn 0;\n}\n\n/*\n============\nImage_GenerateMipmaps\n============\n*/\nvoid Image_GenerateMipmaps( const byte *source, int width, int height, byte *mip1, byte *mip2, byte *mip3 )\n{\n\tconst int sizes[3][2] = {\n\t\t{ width / 2, height / 2 },\n\t\t{ width / 4, height / 4 },\n\t\t{ width / 8, height / 8 }\n\t};\n\tbyte *mipmaps[3] = { mip1, mip2, mip3 };\n\tint m;\n\n\tfor( m = 0; m < 3; ++m )\n\t{\n\t\tint mw, mh, step, y;\n\n\t\tif( !mipmaps[m] )\n\t\t\tcontinue;\n\t\tmw = sizes[m][0];\n\t\tmh = sizes[m][1];\n\t\tstep = 1 << ( m + 1 );\n\t\tfor( y = 0; y < mh; ++y )\n\t\t{\n\t\t\tint x;\n\n\t\t\tfor( x = 0; x < mw; ++x )\n\t\t\t{\n\t\t\t\tmipmaps[m][y * mw + x] = source[( y * step ) * width + ( x * step )];\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/common/imagelib/img_wad.c",
    "content": "/*\nimg_mip.c - hl1 and q1 image mips\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"imagelib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"wadfile.h\"\n#include \"studio.h\"\n#include \"sprite.h\"\n#include \"qfont.h\"\n\n/*\n============\nImage_LoadPAL\n============\n*/\nqboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tint\trendermode = LUMP_NORMAL;\n\tbyte pal[768];\n\n\tif( filesize > sizeof( pal ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: (%s) have invalid size (%li should be less or equal than %zu)\\n\", __func__, name, (long)filesize, sizeof( pal ));\n\t\treturn false;\n\t}\n\telse if( filesize < sizeof( pal ) && buffer != NULL )\n\t{\n\t\t// palette might be truncated, fill it with zeros\n\t\tmemset( pal, 0, sizeof( pal ));\n\t\tmemcpy( pal, buffer, filesize );\n\t\tbuffer = pal;\n\t}\n\n\tif( name[0] == '#' )\n\t{\n\t\t// using palette name as rendermode\n\t\tif( Q_stristr( name, \"normal\" ))\n\t\t\trendermode = LUMP_NORMAL;\n\t\telse if( Q_stristr( name, \"masked\" ))\n\t\t\trendermode = LUMP_MASKED;\n\t\telse if( Q_stristr( name, \"gradient\" ))\n\t\t\trendermode = LUMP_GRADIENT;\n\t\telse if( Q_stristr( name, \"texgamma\" ))\n\t\t\trendermode = LUMP_TEXGAMMA;\n\t\telse if( Q_stristr( name, \"valve\" ))\n\t\t{\n\t\t\trendermode = LUMP_HALFLIFE;\n\t\t\tbuffer = NULL; // force to get HL palette\n\t\t}\n\t\telse if( Q_stristr( name, \"id\" ))\n\t\t{\n\t\t\trendermode = LUMP_QUAKE1;\n\t\t\tbuffer = NULL; // force to get Q1 palette\n\t\t}\n\t}\n\n\t// NOTE: image.d_currentpal not cleared with Image_Reset()\n\t// and stay valid any time before new call of Image_SetPalette\n\tImage_GetPaletteLMP( buffer, rendermode );\n\tImage_CopyPalette32bit();\n\n\timage.rgba = NULL;\t// only palette, not real image\n\timage.size = 1024;\t// expanded palette\n\timage.width = image.height = 0;\n\timage.depth = 1;\n\n\treturn true;\n}\n\n/*\n============\nImage_LoadFNT\n============\n*/\nqboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tqfont_t\t\tfont;\n\tconst byte\t*pal, *fin;\n\tsize_t\t\tsize;\n\tint\t\tnumcolors;\n\n\tif( image.hint == IL_HINT_Q1 )\n\t\treturn false; // Quake1 doesn't have qfonts\n\n\tif( filesize < sizeof( font ))\n\t\treturn false;\n\n\tmemcpy( &font, buffer, sizeof( font ));\n\n\t// last sixty four bytes - what the hell ????\n\tsize = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64;\n\n\tif( size != filesize )\n\t{\n\t\t// oldstyle font: \"conchars\" or \"creditsfont\"\n\t\timage.width = 256;\t\t// hardcoded\n\t\timage.height = font.height;\n\t}\n\telse\n\t{\n\t\t// Half-Life 1.1.0.0 font style (qfont_t)\n\t\timage.width = font.width * QCHAR_WIDTH;\n\t\timage.height = font.height;\n\t}\n\n\tif( !Image_LumpValidSize( name ))\n\t\treturn false;\n\n\tfin = buffer + sizeof( font ) - 4;\n\tpal = fin + (image.width * image.height);\n\tnumcolors = *(short *)pal, pal += sizeof( short );\n\n\tif( numcolors == 768 || numcolors == 256 )\n\t{\n\t\t// g-cont. make sure that is didn't hit anything\n\t\tImage_GetPaletteLMP( pal, LUMP_MASKED );\n\t\timage.flags |= IMAGE_HAS_ALPHA; // fonts always have transparency\n\t}\n\telse\n\t{\n\t\treturn false;\n\t}\n\n\timage.type = PF_INDEXED_32;\t// 32-bit palette\n\timage.depth = 1;\n\n\treturn Image_AddIndexedImageToPack( fin, image.width, image.height );\n}\n\n/*\n======================\nImage_SetMDLPointer\n\nTransfer buffer pointer before Image_LoadMDL\n======================\n*/\nstatic void *g_mdltexdata;\nvoid Image_SetMDLPointer( byte *p )\n{\n\tg_mdltexdata = p;\n}\n\n/*\n============\nImage_LoadMDL\n============\n*/\nqboolean Image_LoadMDL( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tbyte\t\t*fin;\n\tsize_t\t\tpixels;\n\tmstudiotexture_t\t*pin;\n\tint\t\tflags;\n\n\tpin = (mstudiotexture_t *)buffer;\n\tflags = pin->flags;\n\n\timage.width = pin->width;\n\timage.height = pin->height;\n\tpixels = image.width * image.height;\n\tfin = (byte *)g_mdltexdata;\n\tASSERT(fin);\n\tg_mdltexdata = NULL;\n\n\tif( !Image_ValidSize( name ))\n\t\treturn false;\n\n\tif( image.hint == IL_HINT_HL )\n\t{\n\t\tif( filesize < ( sizeof( *pin ) + pixels + 768 ))\n\t\t\treturn false;\n\n\t\tif( FBitSet( flags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\tbyte\t*pal = fin + pixels;\n\n\t\t\tImage_GetPaletteLMP( pal, LUMP_MASKED );\n\t\t\timage.flags |= IMAGE_HAS_ALPHA|IMAGE_ONEBIT_ALPHA;\n\t\t}\n\t\telse Image_GetPaletteLMP( fin + pixels, LUMP_TEXGAMMA );\n\t}\n\telse\n\t{\n\t\treturn false; // unknown or unsupported mode rejected\n\t}\n\n\timage.type = PF_INDEXED_32;\t// 32-bit palete\n\timage.depth = 1;\n\n\treturn Image_AddIndexedImageToPack( fin, image.width, image.height );\n}\n\n/*\n============\nImage_LoadSPR\n============\n*/\nqboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tdspriteframe_t\tpin;\t// identical for q1\\hl sprites\n\tqboolean\t\ttruecolor = false;\n\tbyte *fin;\n\n\tif( image.hint == IL_HINT_HL )\n\t{\n\t\tif( !image.d_currentpal )\n\t\t\treturn false;\n\t}\n\telse if( image.hint == IL_HINT_Q1 )\n\t{\n\t\tImage_GetPaletteQ1();\n\t}\n\telse\n\t{\n\t\t// unknown mode rejected\n\t\treturn false;\n\t}\n\n\tmemcpy( &pin, buffer, sizeof( dspriteframe_t ));\n\timage.width = pin.width;\n\timage.height = pin.height;\n\n\tif( filesize < image.width * image.height )\n\t\treturn false;\n\n\tif( filesize == ( image.width * image.height * 4 ))\n\t\ttruecolor = true;\n\n\t// sorry, can't validate palette rendermode\n\tif( !Image_LumpValidSize( name )) return false;\n\timage.type = (truecolor) ? PF_RGBA_32 : PF_INDEXED_32;\t// 32-bit palete\n\timage.depth = 1;\n\n\t// detect alpha-channel by palette type\n\tswitch( image.d_rendermode )\n\t{\n\tcase LUMP_MASKED:\n\t\tSetBits( image.flags, IMAGE_ONEBIT_ALPHA );\n\t\t// intentionally fallthrough\n\tcase LUMP_GRADIENT:\n\tcase LUMP_QUAKE1:\n\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\tbreak;\n\t}\n\n\tfin =  (byte *)(buffer + sizeof(dspriteframe_t));\n\n\tif( truecolor )\n\t{\n\t\t// spr32 support\n\t\timage.size = image.width * image.height * 4;\n\t\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\t\tmemcpy( image.rgba, fin, image.size );\n\t\tSetBits( image.flags, IMAGE_HAS_COLOR ); // Color. True Color!\n\t\treturn true;\n\t}\n\treturn Image_AddIndexedImageToPack( fin, image.width, image.height );\n}\n\n/*\n============\nImage_LoadLMP\n============\n*/\nqboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tlmp_t\tlmp;\n\tbyte\t*fin, *pal;\n\tint\trendermode;\n\tint\ti, pixels;\n\n\tif( filesize < sizeof( lmp ))\n\t\treturn false;\n\n\t// valve software trick (particle palette)\n\tif( Q_stristr( name, \"palette.lmp\" ))\n\t\treturn Image_LoadPAL( name, buffer, filesize );\n\n\t// id software trick (image without header)\n\tif( Q_stristr( name, \"conchars\" ) && filesize == 16384 )\n\t{\n\t\timage.width = image.height = 128;\n\t\trendermode = LUMP_QUAKE1;\n\t\tfilesize += sizeof( lmp );\n\t\tfin = (byte *)buffer;\n\n\t\t// need to remap transparent color from first to last entry\n\t\tfor( i = 0; i < 16384; i++ ) if( !fin[i] ) fin[i] = 0xFF;\n\t}\n\telse\n\t{\n\t\tfin = (byte *)buffer;\n\t\tmemcpy( &lmp, fin, sizeof( lmp ));\n\t\timage.width = lmp.width;\n\t\timage.height = lmp.height;\n\t\trendermode = LUMP_NORMAL;\n\t\tfin += sizeof( lmp );\n\t}\n\n\tpixels = image.width * image.height;\n\n\tif( filesize < sizeof( lmp ) + pixels )\n\t\treturn false;\n\n\tif( !Image_ValidSize( name ))\n\t\treturn false;\n\n\tif( image.hint != IL_HINT_Q1 && filesize > (int)sizeof(lmp) + pixels )\n\t{\n\t\tint\tnumcolors;\n\n\t\t// HACKHACK: console background image shouldn't be transparent\n\t\tif( !Q_stristr( name, \"conback\" ))\n\t\t{\n\t\t\tfor( i = 0; i < pixels; i++ )\n\t\t\t{\n\t\t\t\tif( fin[i] == 255 )\n\t\t\t\t{\n\t\t\t\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\t\trendermode = LUMP_MASKED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpal = fin + pixels;\n\t\tnumcolors = *(short *)pal;\n\t\tif( numcolors != 256 ) pal = NULL; // corrupted lump ?\n\t\telse pal += sizeof( short );\n\t}\n\telse if( image.hint != IL_HINT_HL )\n\t{\n\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\trendermode = LUMP_QUAKE1;\n\t\tpal = NULL;\n\t}\n\telse\n\t{\n\t\t// unknown mode rejected\n\t\treturn false;\n\t}\n\n\tImage_GetPaletteLMP( pal, rendermode );\n\timage.type = PF_INDEXED_32; // 32-bit palete\n\timage.depth = 1;\n\n\treturn Image_AddIndexedImageToPack( fin, image.width, image.height );\n}\n\n/*\n=============\nImage_LoadMIP\n=============\n*/\nqboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tmip_t\tmip;\n\tqboolean\thl_texture;\n\tbyte\t*fin, *pal;\n\tint\tofs[4], rendermode;\n\tint\ti, pixels, numcolors;\n\tuint\treflectivity[3] = { 0, 0, 0 };\n\n\tif( filesize < sizeof( mip ))\n\t\treturn false;\n\n\tmemcpy( &mip, buffer, sizeof( mip ));\n\timage.width = mip.width;\n\timage.height = mip.height;\n\n\tif( !Image_ValidSize( name ))\n\t\treturn false;\n\n\tmemcpy( ofs, mip.offsets, sizeof( ofs ));\n\tpixels = image.width * image.height;\n\n\tif( image.hint != IL_HINT_Q1 && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6) + sizeof(short) + 768)\n\t{\n\t\t// half-life 1.0.0.1 mip version with palette\n\t\tfin = (byte *)buffer + mip.offsets[0];\n\t\tpal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6);\n\t\tnumcolors = *(short *)pal;\n\t\tif( numcolors != 256 ) pal = NULL; // corrupted mip ?\n\t\telse pal += sizeof( short ); // skip colorsize\n\n\t\thl_texture = true;\n\n\t\t// setup rendermode\n\t\tif( Q_strrchr( name, '{' ))\n\t\t{\n\t\t\t// NOTE: decals with 'blue base' can be interpret as colored decals\n\t\t\tif( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal && pal[765] == 0 && pal[766] == 0 && pal[767] == 255 ))\n\t\t\t{\n\t\t\t\tSetBits( image.flags, IMAGE_ONEBIT_ALPHA );\n\t\t\t\trendermode = LUMP_MASKED;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// classic gradient decals\n\t\t\t\tSetBits( image.flags, IMAGE_COLORINDEX );\n\t\t\t\trendermode = LUMP_GRADIENT;\n\t\t\t}\n\n\t\t\tSetBits( image.flags, IMAGE_HAS_ALPHA );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint\tpal_type;\n\n\t\t\t// NOTE: we can have luma-pixels if quake1 texture\n\t\t\t// converted into the hl texture but palette leave unchanged\n\t\t\t// this is a good reason for using fullbright pixels\n\t\t\tpal_type = Image_ComparePalette( pal );\n\n\t\t\t// check for luma pixels (but ignore liquid textures because they have no lightmap)\n\t\t\tif( mip.name[0] != '*' && mip.name[0] != '!' && pal_type == PAL_QUAKE1 )\n\t\t\t{\n\t\t\t\tfor( i = 0; i < image.width * image.height; i++ )\n\t\t\t\t{\n\t\t\t\t\tif( fin[i] > 224 )\n\t\t\t\t\t{\n\t\t\t\t\t\timage.flags |= IMAGE_HAS_LUMA;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( pal_type == PAL_QUAKE1 )\n\t\t\t{\n\t\t\t\tSetBits( image.flags, IMAGE_QUAKEPAL );\n\n\t\t\t\t// if texture was converted from quake to half-life with no palette changes\n\t\t\t\t// then applying texgamma might make it too dark or even outright broken\n\t\t\t\trendermode = LUMP_NORMAL;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// half-life mips need texgamma applied\n\t\t\t\trendermode = LUMP_TEXGAMMA;\n\t\t\t}\n\t\t}\n\n\t\tImage_GetPaletteLMP( pal, rendermode );\n\t\timage.d_currentpal[255] &= 0xFFFFFF;\n\t}\n\telse if( image.hint != IL_HINT_HL && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6))\n\t{\n\t\t// quake1 1.01 mip version without palette\n\t\tfin = (byte *)buffer + mip.offsets[0];\n\t\tpal = NULL; // clear palette\n\t\trendermode = LUMP_NORMAL;\n\n\t\thl_texture = false;\n\n\t\t// check for luma and alpha pixels\n\t\tif( !image.custom_palette )\n\t\t{\n\t\t\tfor( i = 0; i < image.width * image.height; i++ )\n\t\t\t{\n\t\t\t\tif( fin[i] > 224 && fin[i] != 255 )\n\t\t\t\t{\n\t\t\t\t\t// don't apply luma to water surfaces because they have no lightmap\n\t\t\t\t\tif( mip.name[0] != '*' && mip.name[0] != '!' )\n\t\t\t\t\t\timage.flags |= IMAGE_HAS_LUMA;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Arcane Dimensions has the transparent textures\n\t\tif( Q_strrchr( name, '{' ))\n\t\t{\n\t\t\tfor( i = 0; i < image.width * image.height; i++ )\n\t\t\t{\n\t\t\t\tif( fin[i] == 255 )\n\t\t\t\t{\n\t\t\t\t\t// don't set ONEBIT_ALPHA flag for some reasons\n\t\t\t\t\timage.flags |= IMAGE_HAS_ALPHA;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tSetBits( image.flags, IMAGE_QUAKEPAL );\n\t\tImage_GetPaletteQ1();\n\t}\n\telse\n\t{\n\t\treturn false; // unknown or unsupported mode rejected\n\t}\n\n\t// check for quake-sky texture\n\tif( !Q_strncmp( mip.name, \"sky\", 3 ) && image.width == ( image.height * 2 ))\n\t{\n\t\t// g-cont: we need to run additional checks for palette type and colors ?\n\t\timage.flags |= IMAGE_QUAKESKY;\n\t}\n\n\t// check for half-life water texture\n\tif( pal != NULL )\n\t{\n\t\tif( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, \"water\", 5 )))\n\t\t{\n\t\t\t// grab the fog color\n\t\t\timage.fogParams[0] = pal[3*3+0];\n\t\t\timage.fogParams[1] = pal[3*3+1];\n\t\t\timage.fogParams[2] = pal[3*3+2];\n\n\t\t\t// grab the fog density\n\t\t\timage.fogParams[3] = pal[4*3+0];\n\t\t}\n\t\telse if( hl_texture && ( rendermode == LUMP_GRADIENT ))\n\t\t{\n\t\t\t// grab the decal color\n\t\t\timage.fogParams[0] = pal[255*3+0];\n\t\t\timage.fogParams[1] = pal[255*3+1];\n\t\t\timage.fogParams[2] = pal[255*3+2];\n\n\t\t\t// calc the decal reflectivity\n\t\t\timage.fogParams[3] = VectorAvg( image.fogParams );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// calc texture reflectivity\n\t\t\tfor( i = 0; i < 256; i++ )\n\t\t\t{\n\t\t\t\treflectivity[0] += pal[i*3+0];\n\t\t\t\treflectivity[1] += pal[i*3+1];\n\t\t\t\treflectivity[2] += pal[i*3+2];\n\t\t\t}\n\n\t\t\tVectorDivide( reflectivity, 256, image.fogParams );\n\t\t}\n\t}\n\n\timage.type = PF_INDEXED_32;\t// 32-bit palete\n\timage.depth = 1;\n\n\treturn Image_AddIndexedImageToPack( fin, image.width, image.height );\n}\n\n/*\n============\nImage_LoadWAD\n============\n*/\nqboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize )\n{\n\tconst dwadinfo_t    *header;\n\tconst dlumpinfo_t   *lumps;\n\tconst unsigned char *mipdata;\n\tint i, j;\n\n\tif( !buffer || filesize < sizeof( dwadinfo_t ))\n\t\treturn false;\n\n\theader = (const dwadinfo_t *)buffer;\n\tif( header->numlumps <= 0 || header->infotableofs <= 0 || header->infotableofs >= (int)filesize )\n\t\treturn false;\n\n\tlumps = (const dlumpinfo_t *)((const unsigned char *)buffer + header->infotableofs );\n\n\tfor( i = 0; i < header->numlumps; ++i )\n\t{\n\t\tconst unsigned char *pixels, *palette, *use_palette;\n\t\tunsigned char grad_palette[256 * 3];\n\t\tint mip_size;\n\t\tmip_t mip;\n\t\tuint32_t      width, height, offset0;\n\t\tuint32_t      m0size, m1size, m2size, m3size;\n\t\tqboolean      alpha_mode = false;\n\t\tunsigned char frontR = 0, frontG = 0, frontB = 0;\n\t\tfloat t;\n\t\tbyte  idx;\n\n\t\tif( lumps[i].type != TYP_MIPTEX && lumps[i].type != TYP_PALETTE )\n\t\t\tcontinue;\n\n\t\t// get lump data and validate\n\t\tmipdata = (const unsigned char *)buffer + lumps[i].filepos;\n\t\tmip_size = lumps[i].disksize;\n\t\tif( lumps[i].filepos < 0 || lumps[i].filepos + mip_size > (int)filesize )\n\t\t\tcontinue;\n\n\t\tmemcpy( &mip, mipdata, sizeof( mip ));\n\t\twidth = mip.width;\n\t\theight = mip.height;\n\n\t\tif( width <= 0 || height <= 0 || width > 256 || height > 256 )\n\t\t\tcontinue;\n\n\t\toffset0 = mip.offsets[0];\n\t\tif( offset0 == 0 || offset0 + width * height > (uint32_t)mip_size )\n\t\t\tcontinue;\n\n\t\tpixels = mipdata + offset0;\n\t\tm0size = width * height;\n\t\tm1size = m0size / 4;\n\t\tm2size = m0size / 16;\n\t\tm3size = m0size / 64;\n\t\tpalette = mipdata + 0x28 + m0size + m1size + m2size + m3size + 2;\n\t\tuse_palette = palette;\n\n\t\t// handle gradient palette\n\t\tif( lumps[i].type == TYP_PALETTE )\n\t\t{\n\t\t\t// gradient palette\n\t\t\tconst unsigned char *frontColorPtr = palette + 255 * 3;\n\t\t\tfrontR = frontColorPtr[0];\n\t\t\tfrontG = frontColorPtr[1];\n\t\t\tfrontB = frontColorPtr[2];\n\t\t\tfor( j = 0; j < 256; ++j )\n\t\t\t{\n\t\t\t\tt = j / 255.0f;\n\t\t\t\tgrad_palette[j * 3 + 0] = (unsigned char)( frontR * t );\n\t\t\t\tgrad_palette[j * 3 + 1] = (unsigned char)( frontG * t );\n\t\t\t\tgrad_palette[j * 3 + 2] = (unsigned char)( frontB * t );\n\t\t\t}\n\t\t\tuse_palette = grad_palette;\n\t\t\talpha_mode = true; // gradient\n\t\t}\n\n\t\t// prepare image structure\n\t\tImage_Reset();\n\t\timage.width = width;\n\t\timage.height = height;\n\t\timage.type = PF_RGBA_32;\n\t\timage.flags = IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA;\n\t\timage.size = width * height * 4;\n\t\timage.rgba = Mem_Malloc( host.imagepool, image.size );\n\t\timage.palette = NULL;\n\n\t\t// convert indexed pixels to RGBA\n\t\tfor( j = 0; j < (int)( width * height ); ++j )\n\t\t{\n\t\t\tidx = pixels[j];\n\t\t\timage.rgba[j * 4 + 0] = use_palette[idx * 3 + 0];\n\t\t\timage.rgba[j * 4 + 1] = use_palette[idx * 3 + 1];\n\t\t\timage.rgba[j * 4 + 2] = use_palette[idx * 3 + 2];\n\t\t\tif( alpha_mode )\n\t\t\t\timage.rgba[j * 4 + 3] = idx; // soft transparency\n\t\t\telse\n\t\t\t\timage.rgba[j * 4 + 3] = ( idx == 255 ) ? 0 : 255; // classic\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n============\nImage_SaveWAD\n============\n*/\nqboolean Image_SaveWAD( const char *name, rgbdata_t *pix )\n{\n\tint         m0size, m1size, m2size, m3size;\n\tbyte        *mip1_data = NULL, *mip2_data = NULL, *mip3_data = NULL;\n\tconst byte  *palette;\n\tbyte        grad_palette[256 * 3];\n\tfile_t      *f;\n\tdwadinfo_t  header;\n\tmip_t       miptex;\n\tlong        infotableofs;\n\tdlumpinfo_t lump;\n\tfs_offset_t pad;\n\tint         i;\n\tqboolean    result = false;\n\tint         lump_type = ( pix->flags & IMAGE_GRADIENT_DECAL ) ? TYP_PALETTE : TYP_MIPTEX;\n\tshort       palette_size = 256;\n\tint         infotableofs32 = 0;\n\n\tif( !pix || !pix->buffer )\n\t\treturn false;\n\n\tpalette = pix->palette ? pix->palette : (const byte *)image.palette;\n\n\tm0size = pix->width * pix->height;\n\tm1size = m0size / 4;\n\tm2size = m0size / 16;\n\tm3size = m0size / 64;\n\n\tmip1_data = (byte *)Mem_Malloc( host.imagepool, m1size );\n\tmip2_data = (byte *)Mem_Malloc( host.imagepool, m2size );\n\tmip3_data = (byte *)Mem_Malloc( host.imagepool, m3size );\n\tif( !mip1_data || !mip2_data || !mip3_data )\n\t\tgoto cleanup;\n\n\tImage_GenerateMipmaps( pix->buffer, pix->width, pix->height, mip1_data, mip2_data, mip3_data );\n\n\tmemset( &miptex, 0, sizeof( mip_t ));\n\tQ_strncpy( miptex.name, \"{LOGO\", sizeof( miptex.name ));\n\tmiptex.width = pix->width;\n\tmiptex.height = pix->height;\n\tmiptex.offsets[0] = sizeof( mip_t );\n\tmiptex.offsets[1] = miptex.offsets[0] + m0size;\n\tmiptex.offsets[2] = miptex.offsets[1] + m1size;\n\tmiptex.offsets[3] = miptex.offsets[2] + m2size;\n\n\tf = FS_Open( name, \"wb\", false );\n\tif( !f )\n\t\tgoto cleanup;\n\n\tmemset( &header, 0, sizeof( header ));\n\theader.ident = IDWAD3HEADER;\n\theader.numlumps = 1;\n\n\tFS_Write( f, &header, sizeof( header ));\n\tFS_Write( f, &miptex, sizeof( mip_t ));\n\tFS_Write( f, pix->buffer, m0size );\n\tFS_Write( f, mip1_data, m1size );\n\tFS_Write( f, mip2_data, m2size );\n\tFS_Write( f, mip3_data, m3size );\n\tFS_Write( f, &palette_size, sizeof( short ));\n\n\tif( lump_type == TYP_PALETTE )\n\t{\n\t\tconst byte *frontColorPtr = palette + 255 * 3;\n\t\tfor( i = 0; i < 256; ++i )\n\t\t{\n\t\t\tfloat t = i / 255.0f;\n\t\t\tgrad_palette[i * 3 + 0] = (byte)( frontColorPtr[0] * t );\n\t\t\tgrad_palette[i * 3 + 1] = (byte)( frontColorPtr[1] * t );\n\t\t\tgrad_palette[i * 3 + 2] = (byte)( frontColorPtr[2] * t );\n\t\t}\n\t\tFS_Write( f, grad_palette, 256 * 3 );\n\t}\n\telse\n\t{\n\t\tFS_Write( f, palette, 256 * 3 );\n\t}\n\n\t// padding up to a multiple of 4\n\tpad = (( FS_Tell( f ) + 3 ) & ~3 ) - FS_Tell( f );\n\tfor( i = 0; i < pad; ++i )\n\t\tFS_Write( f, (const void *)&(char){0}, 1 );\n\n\tinfotableofs = FS_Tell( f );\n\tmemset( &lump, 0, sizeof( lump ));\n\tlump.filepos = sizeof( dwadinfo_t );\n\tlump.disksize = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 );\n\tlump.size = lump.disksize;\n\tlump.type = (char)lump_type;\n\tlump.attribs = 0;\n\tQ_strncpy( lump.name, \"tempdecal\", sizeof( lump.name ));\n\tFS_Write( f, &lump, sizeof( lump ));\n\n\tFS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET );\n\tinfotableofs32 = (int)infotableofs;\n\tFS_Write( f, &infotableofs32, sizeof( int ));\n\n\tFS_Close( f );\n\tresult = true;\n\ncleanup:\n\tif( mip1_data )\n\t\tMem_Free( mip1_data );\n\tif( mip2_data )\n\t\tMem_Free( mip2_data );\n\tif( mip3_data )\n\t\tMem_Free( mip3_data );\n\treturn result;\n}\n"
  },
  {
    "path": "engine/common/infostring.c",
    "content": "/*\ninfostring.c - network info strings\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n\n#define MAX_KV_SIZE\t\t128\n\n/*\n=======================================================================\n\n\t\t\tINFOSTRING STUFF\n\n=======================================================================\n*/\n/*\n===============\nInfo_Print\n\nprinting current key-value pair\n===============\n*/\nvoid Info_Print( const char *s )\n{\n\tchar\tkey[MAX_KV_SIZE];\n\tchar\tvalue[MAX_KV_SIZE];\n\tint\tl, count;\n\tchar\t*o;\n\n\tif( *s == '\\\\' ) s++;\n\n\twhile( *s )\n\t{\n\t\tcount = 0;\n\t\to = key;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\n\t\tl = o - key;\n\t\tif( l < 20 )\n\t\t{\n\t\t\tmemset( o, ' ', 20 - l );\n\t\t\tkey[20] = 0;\n\t\t}\n\t\telse *o = 0;\n\n\t\tCon_Printf( \"%s\", key );\n\n\t\tif( !*s )\n\t\t{\n\t\t\tCon_Printf( \"(null)\\n\" );\n\t\t\treturn;\n\t\t}\n\n\t\tcount = 0;\n\t\to = value;\n\t\ts++;\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( *s ) s++;\n\t\tCon_Printf( \"%s\\n\", value );\n\t}\n}\n\n/*\n==============\nInfo_IsValid\n\ncheck infostring for potential problems\n==============\n*/\nqboolean Info_IsValid( const char *s )\n{\n\tchar\tkey[MAX_KV_SIZE];\n\tchar\tvalue[MAX_KV_SIZE];\n\tint\tcount;\n\tchar\t*o;\n\n\tif( *s == '\\\\' ) s++;\n\n\twhile( *s )\n\t{\n\t\tcount = 0;\n\t\to = key;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( !*s ) return false;\n\n\t\tcount = 0;\n\t\to = value;\n\t\ts++;\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( !COM_CheckStringEmpty( value ) )\n\t\t\treturn false;\n\n\t\tif( *s ) s++;\n\t}\n\n\treturn true;\n}\n\n#if !XASH_DEDICATED\n/*\n==============\nInfo_WriteVars\n\n==============\n*/\nvoid Info_WriteVars( file_t *f )\n{\n\tchar\t*s = CL_Userinfo();\n\tchar\tpkey[MAX_SERVERINFO_STRING];\n\tstatic\tchar value[4][MAX_SERVERINFO_STRING]; // use two buffers so compares work without stomping on each other\n\tstatic\tint valueindex;\n\tconvar_t\t*pcvar;\n\tchar\t*o;\n\n\tvalueindex = (valueindex + 1) % 4;\n\tif( *s == '\\\\' ) s++;\n\n\twhile( 1 )\n\t{\n\t\to = pkey;\n\t\twhile( *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\to = value[valueindex];\n\n\t\twhile( *s != '\\\\' && *s )\n\t\t{\n\t\t\tif( !*s ) return;\n\t\t\t*o++ = *s++;\n\t\t}\n\t\t*o = 0;\n\n\t\tpcvar = Cvar_FindVar( pkey );\n\n\t\tif( !pcvar && pkey[0] != '*' )  // don't store out star keys\n\t\t\tFS_Printf( f, \"setinfo \\\"%s\\\" \\\"%s\\\"\\n\", pkey, value[valueindex] );\n\n\t\tif( !*s ) return;\n\t\ts++;\n\t}\n}\n#endif // XASH_DEDICATED\n\n/*\n===============\nInfo_ValueForKey\n\nSearches the string for the given\nkey and returns the associated value, or an empty string.\n===============\n*/\nconst char *GAME_EXPORT Info_ValueForKey( const char *s, const char *key )\n{\n\tchar\tpkey[MAX_KV_SIZE];\n\tstatic\tchar value[4][MAX_KV_SIZE]; // use two buffers so compares work without stomping on each other\n\tstatic\tint valueindex;\n\tint\tcount;\n\tchar\t*o;\n\n\tvalueindex = (valueindex + 1) % 4;\n\tif( *s == '\\\\' ) s++;\n\n\twhile( 1 )\n\t{\n\t\tcount = 0;\n\t\to = pkey;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return \"\";\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\n\t\t*o = 0;\n\t\ts++;\n\n\t\to = value[valueindex];\n\t\tcount = 0;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return \"\";\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( !Q_strcmp( key, pkey ))\n\t\t\treturn value[valueindex];\n\t\tif( !*s ) return \"\";\n\t\ts++;\n\t}\n}\n\nqboolean GAME_EXPORT Info_RemoveKey( char *s, const char *key )\n{\n\tchar\t*start;\n\tchar\tpkey[MAX_KV_SIZE];\n\tchar\tvalue[MAX_KV_SIZE];\n\tint\tcmpsize = Q_strlen( key );\n\tint\tcount;\n\tchar\t*o;\n\n\tif( cmpsize > ( MAX_KV_SIZE - 1 ))\n\t\tcmpsize = MAX_KV_SIZE - 1;\n\n\tif( Q_strchr( key, '\\\\' ))\n\t\treturn false;\n\n\twhile( 1 )\n\t{\n\t\tstart = s;\n\t\tif( *s == '\\\\' ) s++;\n\t\tcount = 0;\n\t\to = pkey;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return false;\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\tcount = 0;\n\t\to = value;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s != '\\\\' && *s )\n\t\t{\n\t\t\tif( !*s ) return false;\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( !Q_strncmp( key, pkey, cmpsize ))\n\t\t{\n\t\t\tsize_t size = Q_strlen( s ) + 1;\n\n\t\t\tmemmove( start, s, size ); // remove this part\n\t\t\treturn true;\n\t\t}\n\n\t\tif( !*s ) return false;\n\t}\n}\n\nvoid Info_RemovePrefixedKeys( char *start, char prefix )\n{\n\tchar\t*s, *o;\n\tchar\tpkey[MAX_KV_SIZE];\n\tchar\tvalue[MAX_KV_SIZE];\n\tint\tcount;\n\n\ts = start;\n\n\twhile( 1 )\n\t{\n\t\tif( *s == '\\\\' ) s++;\n\n\t\tcount = 0;\n\t\to = pkey;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return;\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\t\ts++;\n\n\t\tcount = 0;\n\t\to = value;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\tif( !*s ) return;\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( pkey[0] == prefix )\n\t\t{\n\t\t\tInfo_RemoveKey( start, pkey );\n\t\t\ts = start;\n\t\t}\n\n\t\tif( !*s ) return;\n\t}\n}\n\nstatic qboolean Info_IsKeyImportant( const char *key )\n{\n\tif( key[0] == '*' )\n\t\treturn true;\n\tif( !Q_strcmp( key, \"name\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"model\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"rate\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"topcolor\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"bottomcolor\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"cl_updaterate\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"cl_lw\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"cl_lc\" ))\n\t\treturn true;\n\tif( !Q_strcmp( key, \"cl_nopred\" ))\n\t\treturn true;\n\treturn false;\n}\n\nstatic char *Info_FindLargestKey( char *s )\n{\n\tchar\tkey[MAX_KV_SIZE];\n\tchar\tvalue[MAX_KV_SIZE];\n\tstatic\tchar largest_key[128];\n\tint\tlargest_size = 0;\n\tint\tl, count;\n\tchar\t*o;\n\n\t*largest_key = 0;\n\n\tif( *s == '\\\\' ) s++;\n\n\twhile( *s )\n\t{\n\t\tint\tsize = 0;\n\n\t\tcount = 0;\n\t\to = key;\n\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\n\t\tl = o - key;\n\t\t*o = 0;\n\t\tsize = Q_strlen( key );\n\n\t\tif( !*s ) return largest_key;\n\n\t\tcount = 0;\n\t\to = value;\n\t\ts++;\n\t\twhile( count < (MAX_KV_SIZE - 1) && *s && *s != '\\\\' )\n\t\t{\n\t\t\t*o++ = *s++;\n\t\t\tcount++;\n\t\t}\n\t\t*o = 0;\n\n\t\tif( *s ) s++;\n\n\t\tsize += Q_strlen( value );\n\n\t\tif(( size > largest_size ) && !Info_IsKeyImportant( key ))\n\t\t{\n\t\t\tQ_strncpy( largest_key, key, sizeof( largest_key ));\n\t\t\tlargest_size = size;\n\t\t}\n\t}\n\n\treturn largest_key;\n}\n\nqboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize )\n{\n\tchar\tnew[1024], *v;\n\tint\tc, team;\n\n\tif( Q_strchr( key, '\\\\' ) || Q_strchr( value, '\\\\' ))\n\t{\n\t\tCon_Printf( S_ERROR \"SetValueForKey: can't use keys or values with a \\\\\\n\" );\n\t\treturn false;\n\t}\n\n\tif( Q_strstr( key, \"..\" ) || Q_strstr( value, \"..\" ))\n\t\treturn false;\n\n\tif( Q_strchr( key, '\\\"' ) || Q_strchr( value, '\\\"' ))\n\t{\n\t\tCon_Printf( S_ERROR \"SetValueForKey: can't use keys or values with a \\\"\\n\" );\n\t\treturn false;\n\t}\n\n\tif( Q_strlen( key ) > ( MAX_KV_SIZE - 1 ) || Q_strlen( value ) > ( MAX_KV_SIZE - 1 ))\n\t\treturn false;\n\n\tInfo_RemoveKey( s, key );\n\n\tif( !COM_CheckString( value ) )\n\t\treturn true; // just clear variable\n\n\tQ_snprintf( new, sizeof( new ), \"\\\\%s\\\\%s\", key, value );\n\tif( Q_strlen( new ) + Q_strlen( s ) > maxsize )\n\t{\n\t\t// no more room in buffer to add key/value\n\t\tif( Info_IsKeyImportant( key ))\n\t\t{\n\t\t\t// keep removing the largest key/values until we have room\n\t\t\tchar\t*largekey;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tlargekey = Info_FindLargestKey( s );\n\t\t\t\tInfo_RemoveKey( s, largekey );\n\t\t\t} while((( Q_strlen( new ) + Q_strlen( s )) >= maxsize ) && *largekey != 0 );\n\n\t\t\tif( largekey[0] == 0 )\n\t\t\t{\n\t\t\t\t// no room to add setting\n\t\t\t\treturn true; // info changed, new value can't saved\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// no room to add setting\n\t\t\treturn true; // info changed, new value can't saved\n\t\t}\n\t}\n\n\t// only copy ascii values\n\ts += Q_strlen( s );\n\tv = new;\n\n\tteam = ( Q_stricmp( key, \"team\" ) == 0 ) ? true : false;\n\n\twhile( *v )\n\t{\n\t\tc = (byte)*v++;\n\t\tif( team ) c = Q_tolower( c );\n\t\tif( c > 13 ) *s++ = c;\n\t}\n\t*s = 0;\n\n\t// all done\n\treturn true;\n}\n\nqboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize )\n{\n\tif( key[0] == '*' )\n\t{\n\t\tCon_Printf( S_ERROR \"Can't set *keys\\n\" );\n\t\treturn false;\n\t}\n\n\treturn Info_SetValueForStarKey( s, key, value, maxsize );\n}\n\nqboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... )\n{\n\tchar value[MAX_VA_STRING];\n\tva_list args;\n\n\tva_start( args, format );\n\tQ_vsnprintf( value, sizeof( value ), format, args );\n\tva_end( args );\n\n\treturn Info_SetValueForKey( s, key, value, maxsize );\n}\n\n"
  },
  {
    "path": "engine/common/ipv6text.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include \"ipv6text.h\"\n#include \"xash3d_types.h\"\n\n#ifdef _WIN32\n#ifndef snprintf\n#define snprintf _snprintf\n#endif\n#endif\n\nvoid IPv6IPToString( char *pszOutText, const unsigned char *ip )\n{\n\t// Find the longest run of consecutive zero quads.\n\t// If there's a tie, we want the leftmost one.\n\tint idxLongestRunStart = -1;\n\tint nLongestRun = 1; // It must be at least 2 quads in a row, a single 0 must not be compressed\n\tint nCurrentRun = 0, idxQuad;\n\tchar *p;\n\tqboolean bNeedColon;\n\n\tfor ( idxQuad = 0 ; idxQuad < 8 ; ++idxQuad )\n\t{\n\t\t// Zero\n\t\tif ( ip[idxQuad*2] || ip[idxQuad*2 + 1] )\n\t\t{\n\t\t\t// Terminate run\n\t\t\tnCurrentRun = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Extend (or begin) run\n\t\t\t++nCurrentRun;\n\n\t\t\t// Longer than previously found run?\n\t\t\tif ( nCurrentRun > nLongestRun )\n\t\t\t{\n\t\t\t\tnLongestRun = nCurrentRun;\n\t\t\t\tidxLongestRunStart = idxQuad - nCurrentRun + 1;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Print the quads\n\tp = pszOutText;\n\tidxQuad = 0;\n\tbNeedColon = false;\n\twhile ( idxQuad < 8 )\n\t{\n\t\t// Run of compressed zeros?\n\t\tif ( idxQuad == idxLongestRunStart )\n\t\t{\n\t\t\t*(p++) = ':';\n\t\t\t*(p++) = ':';\n\t\t\tbNeedColon = false;\n\t\t\tidxQuad += nLongestRun;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Lowercase hex digits, with leading zeros omitted\n\t\t\tstatic const char hexdigits[] = \"0123456789abcdef\";\n\t\t\tunsigned quad;\n\n\t\t\t// Colon to separate from previous, unless\n\t\t\t// we are first or immediately follow compressed zero \"::\"\n\t\t\tif ( bNeedColon )\n\t\t\t\t*(p++) = ':';\n\n\t\t\t// Next quad should should print a separator\n\t\t\tbNeedColon = true;\n\n\t\t\t// Assemble 16-bit quad value from the two bytes\n\t\t\tquad = ( (unsigned)ip[idxQuad*2] << 8U ) | ip[idxQuad*2 + 1];\n\n\t\t\t// Manually do the hex number formatting.\n\t\t\tif ( quad >= 0x0010 )\n\t\t\t{\n\t\t\t\tif ( quad >= 0x0100 )\n\t\t\t\t{\n\t\t\t\t\tif ( quad >= 0x1000 )\n\t\t\t\t\t\t*(p++) = hexdigits[ quad >> 12U ];\n\t\t\t\t\t*(p++) = hexdigits[ ( quad >> 8U ) & 0xf ];\n\t\t\t\t}\n\t\t\t\t*(p++) = hexdigits[ ( quad >> 4U ) & 0xf ];\n\t\t\t}\n\n\t\t\t// Least significant digit, which is always printed\n\t\t\t*(p++) = hexdigits[ quad & 0xf ];\n\n\t\t\t// On to the next one\n\t\t\t++idxQuad;\n\t\t}\n\t}\n\n\t// String terminator\n\t*p = '\\0';\n}\n\nvoid IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope )\n{\n\tchar *p = pszOutText;\n\n\t// Open bracket\n\t*(p++) = '[';\n\n\t// Print in the IP\n\tIPv6IPToString( p, ip );\n\n\t// Find the end of the string\n\twhile (*p)\n\t\t++p;\n\n\tif ( scope )\n\t{\n\t\t// And now the scope.  Max 32-digit scope number is 10 digits\n\t\tsnprintf( p, 12, \"%%%d\", scope );\n\n\t\t// Find the end of the string\n\t\twhile (*p)\n\t\t\t++p;\n\t}\n\n\t// And now the rest.  Max 16-digit port number is 6 digits\n\tsnprintf( p, 8, \"]:%u\", (unsigned int)port );\n}\n\nstatic inline int ParseIPv6Addr_HexDigitVal( char c )\n{\n\tif ( c >= '0' && c <= '9' ) return c - '0';\n\tif ( c >= 'a' && c <= 'f' ) return c - ('a' - 0xa);\n\tif ( c >= 'A' && c <= 'F' ) return c - ('A' - 0xa);\n\treturn -1;\n}\nstatic inline int ParseIPv6Addr_DecimalDigitVal( char c )\n{\n\tif ( c >= '0' && c <= '9' ) return c - '0';\n\treturn -1;\n}\nstatic bool ParseIPv6Addr_IsSpace( char c )\n{\n\t// Newlines don't count, intentionally\n\treturn c == ' ' || c == '\\t';\n}\nbool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope )\n{\n\tunsigned char *d, *pZeroFill, *pEndIP;\n\tconst char *s;\n\tqboolean bQuadMustFollow;\n\tint nPort;\n\n\twhile ( ParseIPv6Addr_IsSpace( *pszText ) )\n\t\t++pszText;\n\ts = pszText;\n\n\t// Skip opening bracket, if present\n\tif ( *s == '[' )\n\t{\n\t\t++s;\n\t\twhile ( ParseIPv6Addr_IsSpace( *s ) )\n\t\t\t++s;\n\t}\n\n\t// Special case for leading \"::\"\n\tbQuadMustFollow = true;\n\td = pOutIP;\n\tpZeroFill = NULL;\n\tpEndIP = pOutIP + 16;\n\tif ( s[0] == ':' && s[1] == ':' )\n\t{\n\t\tpZeroFill = d;\n\t\ts += 2;\n\t\tbQuadMustFollow = false;\n\t}\n\n\t// Parse quads until we get to the end\n\tfor (;;)\n\t{\n\t\t// Next thing must be a quad, or end of input.  Is it a quad?\n\t\tint quadDigit = ParseIPv6Addr_HexDigitVal( *s );\n\t\tconst char *pszStartQuad;\n\t\tint quad;\n\n\t\tif ( quadDigit < 0 )\n\t\t{\n\t\t\tif ( bQuadMustFollow )\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\t}\n\n\t\t// No room for more quads?\n\t\tif ( d >= pEndIP )\n\t\t\treturn false;\n\n\t\tpszStartQuad = s;\n\t\t++s;\n\t\tquad = quadDigit;\n\n\t\t// Now parse up to three additional characters\n\t\tquadDigit = ParseIPv6Addr_HexDigitVal( *s );\n\t\tif ( quadDigit >= 0 )\n\t\t{\n\t\t\tquad = ( quad << 4 ) | quadDigit;\n\t\t\t++s;\n\n\t\t\tquadDigit = ParseIPv6Addr_HexDigitVal( *s );\n\t\t\tif ( quadDigit >= 0 )\n\t\t\t{\n\t\t\t\tquad = ( quad << 4 ) | quadDigit;\n\t\t\t\t++s;\n\n\t\t\t\tquadDigit = ParseIPv6Addr_HexDigitVal( *s );\n\t\t\t\tif ( quadDigit >= 0 )\n\t\t\t\t{\n\t\t\t\t\tquad = ( quad << 4 ) | quadDigit;\n\t\t\t\t\t++s;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if we hit a period, which would happen if we\n\t\t// have an IPv4 dotted decimal.  For example, \"::ffff:192.168.1.210\"\n\t\tif ( *s == '.' )\n\t\t{\n\t\t\t// Make sure we would have room to store four more bytes.\n\t\t\tunsigned char *pEndDottedDecimal = d+4;\n\t\t\tif ( pEndDottedDecimal > pEndIP )\n\t\t\t\treturn false;\n\n\t\t\t// Parse 4 octets\n\t\t\ts = pszStartQuad;\n\t\t\tfor (;;)\n\t\t\t{\n\n\t\t\t\t// Parse 1-3 decimal digits\n\t\t\t\tint octet = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\t\t\tint dig;\n\n\t\t\t\tif ( octet < 0 )\n\t\t\t\t\treturn false;\n\t\t\t\t++s;\n\t\t\t\tdig = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\t\t\tif ( dig >= 0 )\n\t\t\t\t{\n\t\t\t\t\t++s;\n\t\t\t\t\toctet = octet*10 + dig;\n\t\t\t\t\tdig = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\t\t\t\tif ( dig >= 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\t++s;\n\t\t\t\t\t\toctet = octet*10 + dig;\n\n\t\t\t\t\t\t// Make sure value is in range.\n\t\t\t\t\t\tif ( octet > 255 )\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t*(d++) = (unsigned char)octet;\n\n\t\t\t\t// All done?\n\t\t\t\tif ( d >= pEndDottedDecimal )\n\t\t\t\t\tbreak;\n\n\t\t\t\t// Next thing must be dot dot separator\n\t\t\t\tif ( *s != '.' )\n\t\t\t\t\treturn false;\n\n\t\t\t\t// Eat dot\n\t\t\t\t++s;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\t// Stash it in the next slot, ignoring for now the issue\n\t\t// of compressed zeros\n\t\t*(d++) = (unsigned char)( quad >> 8 );\n\t\t*(d++) = (unsigned char)quad;\n\n\t\t// Only valid character for the IP portion is a colon.\n\t\t// Anything else ends the IP portion\n\t\tif ( *s != ':' )\n\t\t\tbreak;\n\n\t\t// Compressed zeros?\n\t\tif ( s[1] == ':' )\n\t\t{\n\n\t\t\t// Eat '::'\n\t\t\ts += 2;\n\n\t\t\t// Can only have one range of compressed zeros\n\t\t\tif ( pZeroFill )\n\t\t\t\treturn false;\n\n\t\t\t// Remember where to insert the compressed zeros\n\t\t\tpZeroFill = d;\n\n\t\t\t// An IP can end with '::'\n\t\t\tbQuadMustFollow = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// If they have filed the entire IP with no compressed zeros,\n\t\t\t// then this is unambiguously a port number.  That's not\n\t\t\t// necessarily the best style, but it *is* unambiguous\n\t\t\t// what it should mean, so let's allow it.  If there\n\t\t\t// are compressed zeros, then this is ambiguous, and we will\n\t\t\t// always interpret it as a quad.\n\t\t\tif ( !pZeroFill && d >= pEndIP )\n\t\t\t\tbreak; // leave ':' as next character, for below\n\n\t\t\t// Eat ':'\n\t\t\t++s;\n\n\t\t\t// A single colon must be followed by another quad\n\t\t\tbQuadMustFollow = true;\n\t\t}\n\t}\n\n\t// End of the IP.  Do we have compressed zeros?\n\tif ( pZeroFill )\n\t{\n\t\t// How many zeros do we need to fill?\n\t\tintptr_t nZeros = pEndIP - d;\n\t\tif ( nZeros <= 0 )\n\t\t\treturn false;\n\n\t\t// Shift the quads after the bytes to the end\n\t\tmemmove( pZeroFill+nZeros, pZeroFill, d-pZeroFill );\n\n\t\t// And now fill the zeros\n\t\tmemset( pZeroFill, 0, nZeros );\n\t}\n\telse\n\t{\n\t\t// No compressed zeros.  Just make sure we filled the IP exactly\n\t\tif ( d != pEndIP )\n\t\t\treturn false;\n\t}\n\n\tif ( *s == '%' )\n\t{\n\t\t// Parse scope number\n\t\tuint32_t unScope = 0;\n\t\tint nScopeDigit;\n\n\t\t++s;\n\n\t\tnScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\tif ( nScopeDigit < 0 )\n\t\t\treturn false;\n\t\tunScope = (uint32_t)nScopeDigit;\n\t\tfor (;;)\n\t\t{\n\t\t\t++s;\n\t\t\tif ( *s == '\\0' || *s == ']' || ParseIPv6Addr_IsSpace( *s ) )\n\t\t\t\tbreak;\n\t\t\tnScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\t\tif ( nScopeDigit < 0 )\n\t\t\t\treturn false;\n\t\t\tunScope = unScope * 10 + nScopeDigit;\n\t\t}\n\n\t\tif ( pOutScope )\n\t\t\t*pOutScope = unScope;\n\t}\n\telse\n\t{\n\t\tif ( pOutScope )\n\t\t\t*pOutScope = 0;\n\t}\n\n\t// If we started with a bracket, then the next character MUST be a bracket.\n\t// (And this is the only circumstance in which a closing bracket would be legal)\n\tif ( *pszText == '[' )\n\t{\n\t\twhile ( ParseIPv6Addr_IsSpace( *s ) )\n\t\t\t++s;\n\t\tif ( *s != ']' )\n\t\t\treturn false;\n\t\t++s;\n\t}\n\n\t// Now we are definitely at the end of the IP.  Do we have a port?\n\t// We support all of the syntaxes mentioned in RFC5952 section 6 other\n\t// than the ambiguous case\n\tif ( *s == ':' || *s == '#' || *s == '.' || *s == 'p' || *s == 'P' )\n\t{\n\t\t++s;\n\t}\n\telse\n\t{\n\t\twhile ( ParseIPv6Addr_IsSpace( *s ) )\n\t\t\t++s;\n\t\tif ( *s == '\\0' )\n\t\t{\n\t\t\t// Parsed IP without port OK\n\t\t\tif ( pOutPort )\n\t\t\t\t*pOutPort = -1;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( strncmp( s, \"port\", 4 ) == 0 )\n\t\t{\n\t\t\ts += 4;\n\t\t\twhile ( ParseIPv6Addr_IsSpace( *s ) )\n\t\t\t\t++s;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Extra stuff after the IP which isn't whitespace or a port\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// We have a port.  If they didn't ask for it, that's considered a parse failure.\n\tif ( !pOutPort )\n\t\treturn false;\n\n\t// Parse port number\n\tnPort = ParseIPv6Addr_DecimalDigitVal( *s );\n\tif ( nPort < 0 )\n\t\treturn false;\n\tfor (;;)\n\t{\n\t\tint portDigit;\n\n\t\t++s;\n\t\tif ( *s == '\\0' || ParseIPv6Addr_IsSpace( *s ) )\n\t\t\tbreak;\n\t\tportDigit = ParseIPv6Addr_DecimalDigitVal( *s );\n\t\tif ( portDigit < 0 )\n\t\t\treturn false;\n\t\tnPort = nPort * 10 + portDigit;\n\t\tif ( nPort > 0xffff )\n\t\t\treturn false;\n\t}\n\n\t// Consume trailing whitespace; confirm nothing else in the input\n\twhile ( ParseIPv6Addr_IsSpace( *s ) )\n\t\t++s;\n\tif ( *s != '\\0' )\n\t\treturn false;\n\n\t*pOutPort = nPort;\n\treturn true;\n}\n\n"
  },
  {
    "path": "engine/common/ipv6text.h",
    "content": "/// Standalone plain C utilities for parsing and printing IPv6 addresses\n#pragma once\n\n#include <stdint.h>\n#include <stdbool.h>\n\n/// Max length of an IPv6 string, with scope, WITHOUT port number, including \\0':\n/// 0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295\n#define k_ncchMaxIPV6AddrStringWithoutPort 51\n\n/// Max number of bytes output by IPv6AddrToString, including '\\0':\n/// [0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295]:12345\n/// There are other strings that are acceptable to ParseIPv6Addr\n/// that are longer than this, but this is the longest canonical\n/// string.\n#define k_ncchMaxIPV6AddrStringWithPort 59\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/// Format an IPv6 address to the canonical form according to RFC5952.\n/// The address should be 16 bytes (e.g. same as in6_addr::s6_addr).\n/// Your buffer MUST be at least k_ncchMaxIPV6AddrStringWithoutPort bytes.\nextern void IPv6IPToString( char *pszOutText, const unsigned char *ip );\n\n/// Format IPv6 IP and port to string.  This uses the recommended\n/// bracket notation, eg [1234::1]:12345.  Your buffer MUST be\n/// at least k_ncchMaxIPV6AddrStringWithPort bytes.\n///\n/// If the scope is zero, it is not printed.\nextern void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope );\n\n/// Parse IPv6 address string.  Returns true if parsed OK.  Returns false\n/// if input cannot be parsed, or if input specifies a port but pOutPort is NULL.\n/// If input does not specify a port, and pOutPort is non-NULL, then *pOutPort is\n/// set to -1.\n///\n/// Parsing is tolerant of any unambiguous IPv6 representation, the input\n/// need not be the canonical RFC5952 representation.\n///\n/// IPv6 zones are not supported.\n///\n/// Leading and trailing whitespace is OK around the entire string,\n/// but not internal whitespace.  The different methods for separating the\n/// port in RFC5952 section 6 are supported, except the ambiguous case\n/// of a colon to separate the port, when the IP contains a double-colon.\n/// Brackets around an IP are OK, even if there is no port.\n///\n/// Address must point to a 16-byte buffer (e.g. same as in6_addr::s6_addr)\n/// Port is returned in host byte order.\nextern bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope );\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "engine/common/launcher.c",
    "content": "/*\nlauncher.c - direct xash3d launcher\nCopyright (C) 2015 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if XASH_ENABLE_MAIN\n#include \"build.h\"\n#include \"common.h\"\n\n#if XASH_SDLMAIN\n#include <SDL.h>\n#endif\n\n#if XASH_EMSCRIPTEN\n#include <emscripten.h>\n#endif\n\n#ifndef XASH_GAMEDIR\n#define XASH_GAMEDIR \"valve\" // !!! Replace with your default (base) game directory !!!\n#endif\n\n#if XASH_WIN32\n#error \"Single-binary or dedicated builds aren't supported for Windows!\"\n#endif\n\nstatic char        szGameDir[128]; // safe place to keep gamedir\nstatic int         szArgc;\nstatic char        **szArgv;\n\nstatic void Sys_ChangeGame( const char *progname )\n{\n\t// stub\n}\n\nstatic int Sys_Start( void )\n{\n\tQ_strncpy( szGameDir, XASH_GAMEDIR, sizeof( szGameDir ));\n\n#if XASH_EMSCRIPTEN\n#if !XASH_DEDICATED\n\tEM_ASM( try {\n\t\tFS.mkdir( '/rwdir' );\n\t\tFS.mount( IDBFS, { root: '.' }, '/rwdir' );\n\t} catch( e ) { };);\n#else // XASH_DEDICATED\n\tEM_ASM(try {\n\t\tFS.mkdir( '/xash' );\n\t\tFS.mount( NODEFS, { root: '.'}, '/xash' );\n\t} catch( e ) { };);\n#endif // XASH_DEDICATED\n#endif // XASH_EMSCRIPTEN\n\n#if XASH_IOS\n\tIOS_LaunchDialog();\n#endif // XASH_IOS\n\n\treturn Host_Main( szArgc, szArgv, szGameDir, 0, Sys_ChangeGame );\n}\n\nint main( int argc, char **argv )\n{\n#if XASH_PSVITA\n\t// inject -dev -console into args if required\n\tszArgc = PSVita_GetArgv( argc, argv, &szArgv );\n#else\n\tszArgc = argc;\n\tszArgv = argv;\n#endif // XASH_PSVITA\n\treturn Sys_Start();\n}\n#endif // XASH_ENABLE_MAIN\n"
  },
  {
    "path": "engine/common/lib_common.c",
    "content": "/*\nlib_common.c - common dynamic library code\nCopyright (C) 2018 Flying With Gauss\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"library.h\"\n#include \"filesystem.h\"\n#include \"server.h\"\n#include <ctype.h>\n\nstatic char s_szLastError[1024] = \"\";\n\nconst char *COM_GetLibraryError( void )\n{\n\treturn s_szLastError;\n}\n\nvoid COM_ResetLibraryError( void )\n{\n\ts_szLastError[0] = 0;\n}\n\nvoid COM_PushLibraryError( const char *error )\n{\n\tif( s_szLastError[0] )\n\t\tQ_strncat( s_szLastError, \"\\n\", sizeof( s_szLastError ) );\n\tQ_strncat( s_szLastError, error, sizeof( s_szLastError ) );\n}\n\nvoid *COM_FunctionFromName_SR( void *hInstance, const char *pName )\n{\n\tchar **funcs = NULL;\n\tsize_t numfuncs, i;\n\tvoid *f = NULL;\n\tconst char *func = NULL;\n\n#ifdef XASH_ALLOW_SAVERESTORE_OFFSETS\n\tif( !memcmp( pName, \"ofs:\", 4 ))\n\t\treturn (byte*)svgame.dllFuncs.pfnGameInit + Q_atoi( pName + 4 );\n#endif\n\n#if XASH_POSIX\n\tfuncs = COM_ConvertToLocalPlatform( MANGLE_ITANIUM, pName, &numfuncs );\n\n\tif( funcs )\n\t{\n\t\tfor( i = 0; i < numfuncs; i++ )\n\t\t{\n\t\t\tif( !f )\n\t\t\t\tf = COM_FunctionFromName( hInstance, funcs[i] );\n\t\t\tZ_Free( funcs[i] );\n\t\t}\n\t\tZ_Free( funcs );\n\n\t\tif( f ) return f;\n\t}\n#elif _MSC_VER\n\t// TODO: COM_ConvertToLocalPlatform doesn't support MSVC yet\n\t// also custom loader strips always MSVC mangling, so Win32\n\t// platforms already use platform-neutral names\n\tfunc = COM_GetPlatformNeutralName( pName );\n\n\tif( func )\n\t\treturn COM_FunctionFromName( hInstance, func );\n#endif\n\n\treturn COM_FunctionFromName( hInstance, pName );\n}\n\nconst char *COM_OffsetNameForFunction( void *function )\n{\n\tstatic string sname;\n\tQ_snprintf( sname, MAX_STRING, \"ofs:%zu\", ((byte*)function - (byte*)svgame.dllFuncs.pfnGameInit) );\n\tCon_Reportf( \"%s: %s\\n\", __func__, sname );\n\treturn sname;\n}\n\ndll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath )\n{\n\tdll_user_t *p;\n\tfs_dllinfo_t dllInfo;\n\n\t// no fs loaded yet, but let engine find fs\n\tif( !g_fsapi.FindLibrary )\n\t{\n\t\tp = Mem_Calloc( host.mempool, sizeof( dll_user_t ));\n\t\tQ_strncpy( p->shortPath, dllname, sizeof( p->shortPath ));\n\t\tQ_strncpy( p->fullPath, dllname, sizeof( p->fullPath ));\n\t\tQ_strncpy( p->dllName, dllname, sizeof( p->dllName ));\n\n\t\treturn p;\n\t}\n\n\t// fs can't find library\n\tif( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo ))\n\t\treturn NULL;\n\n\t// NOTE: for libraries we not fail even if search is NULL\n\t// let the OS find library himself\n\tp = Mem_Calloc( host.mempool, sizeof( dll_user_t ));\n\tQ_strncpy( p->shortPath, dllInfo.shortPath, sizeof( p->shortPath ));\n\tQ_strncpy( p->fullPath, dllInfo.fullPath, sizeof( p->fullPath ));\n\tQ_strncpy( p->dllName, dllname, sizeof( p->dllName ));\n\tp->custom_loader = dllInfo.custom_loader;\n\tp->encrypted = dllInfo.encrypted;\n\n\treturn p;\n}\n\n/*\n=============================================================================\n\n\tLIBRARY NAMING(see Documentation/library-naming.md for more info)\n\n=============================================================================\n*/\n\nstatic void COM_GenerateCommonLibraryName( const char *name, const char *ext, char *out, size_t size )\n{\n#if ( XASH_WIN32 || ( XASH_LINUX && !XASH_ANDROID ) || XASH_APPLE ) && XASH_X86\n\tQ_snprintf( out, size, \"%s.%s\", name, ext );\n#elif XASH_WIN32 || ( XASH_LINUX && !XASH_ANDROID ) || XASH_APPLE\n\tQ_snprintf( out, size, \"%s_%s.%s\", name, Q_buildarch(), ext );\n#else\n\tQ_snprintf( out, size, \"%s_%s_%s.%s\", name, Q_buildos(), Q_buildarch(), ext );\n#endif\n}\n\n/*\n==============\nCOM_GenerateClientLibraryPath\n\nGenerates platform-unique and compatible name for client libraries\n==============\n*/\nstatic void COM_GenerateClientLibraryPath( const char *name, char *out, size_t size )\n{\n#ifdef XASH_INTERNAL_GAMELIBS // assuming library loader knows where to get libraries\n\tQ_strncpy( out, name, size );\n#else\n\tstring dllpath;\n\n#if XASH_ANDROID\n\tQ_snprintf( dllpath, sizeof( dllpath ), \"%s/lib%s\", GI->dll_path, name );\n#else\n\tQ_snprintf( dllpath, sizeof( dllpath ), \"%s/%s\", GI->dll_path, name );\n#endif\n\n\tCOM_GenerateCommonLibraryName( dllpath, OS_LIB_EXT, out, size );\n#endif\n}\n\n/*\n==============\nCOM_StripIntelSuffix\n\nSome modders use _i?86 suffix in game library name\nSo strip it to follow library naming for non-Intel CPUs\n==============\n*/\nstatic inline void COM_StripIntelSuffix( char *out )\n{\n\tchar *suffix = Q_strrchr( out, '_' );\n\n\tif( suffix && Q_stricmpext( \"_i?86\", suffix ))\n\t\t*suffix = 0;\n}\n\n/*\n==============\nCOM_GenerateServerLibraryPath\n\nGenerates platform-unique and compatible name for server library\n==============\n*/\nstatic void COM_GenerateServerLibraryPath( const char *alt_dllname, char *out, size_t size )\n{\n#ifdef XASH_INTERNAL_GAMELIBS // assuming library loader knows where to get libraries\n\tQ_strncpy( out, \"server\", size );\n#elif XASH_X86 && XASH_WIN32\n\tQ_strncpy( out, GI->game_dll, size );\n#elif XASH_X86 && XASH_APPLE\n\tQ_strncpy( out, GI->game_dll_osx, size );\n#elif XASH_X86 && XASH_LINUX && !XASH_ANDROID\n\tQ_strncpy( out, GI->game_dll_linux, size );\n\tCOM_StripExtension( out );\n\n\t// GoldSrc actually strips everything after '_', causing issues for mods that have '_' in the DLL name on Linux\n\t// e.g. delta_particles.so becomes delta.so. We're gonna be smarter and just drop the _i?86 if it matches...\n\t// ... until somebody complains :)\n\tCOM_StripIntelSuffix( out );\n\tCOM_DefaultExtension( out, \".\" OS_LIB_EXT, size );\n#else\n\tstring temp, dir, dllpath, ext;\n\tconst char *dllname;\n\n#if XASH_WIN32\n\tQ_strncpy( temp, GI->game_dll, sizeof( temp ));\n#elif XASH_APPLE\n\tQ_strncpy( temp, GI->game_dll_osx, sizeof( temp ));\n#else\n\tQ_strncpy( temp, GI->game_dll_linux, sizeof( temp ));\n#endif\n\n\t// path to the dll directory\n\tCOM_ExtractFilePath( temp, dir );\n\n\tif( alt_dllname )\n\t{\n\t\tdllname = alt_dllname;\n\t\tQ_strncpy( ext, OS_LIB_EXT, sizeof( ext ));\n\t}\n\telse\n\t{\n\t\t// cleaned up dll name\n\t\tQ_strncpy( ext, COM_FileExtension( temp ), sizeof( ext ));\n\t\tCOM_StripExtension( temp );\n\t\tCOM_StripIntelSuffix( temp );\n\t\tdllname = COM_FileWithoutPath( temp );\n\t}\n\n\t// add `lib` prefix if required by platform\n#if XASH_ANDROID\n\tQ_snprintf( dllpath, sizeof( dllpath ), \"%s/lib%s\", dir, dllname );\n#else\n\tQ_snprintf( dllpath, sizeof( dllpath ), \"%s/%s\", dir, dllname );\n#endif\n\n\t// and finally add platform suffix\n\tCOM_GenerateCommonLibraryName( dllpath, ext, out, size );\n#endif\n}\n\n\n/*\n==============\nCOM_GetCommonLibraryPath\n\nGenerates platform-unique and compatible name for server library\n==============\n*/\nvoid COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t size )\n{\n\tswitch( eLibType )\n\t{\n\tcase LIBRARY_GAMEUI:\n\t\tif( COM_CheckStringEmpty( host.menulib ))\n\t\t{\n\t\t\tif( host.menulib[0] == '@' )\n\t\t\t\tCOM_GenerateClientLibraryPath( host.menulib + 1, out, size );\n\t\t\telse Q_strncpy( out, host.menulib, size );\n\t\t}\n\t\telse COM_GenerateClientLibraryPath( \"menu\", out, size );\n\t\tbreak;\n\tcase LIBRARY_CLIENT:\n\t\tif( COM_CheckStringEmpty( host.clientlib ))\n\t\t{\n\t\t\tif( host.clientlib[0] == '@' )\n\t\t\t\tCOM_GenerateClientLibraryPath( host.clientlib + 1, out, size );\n\t\t\telse Q_strncpy( out, host.clientlib, size );\n\t\t}\n\t\telse COM_GenerateClientLibraryPath( \"client\", out, size );\n\t\tbreak;\n\tcase LIBRARY_SERVER:\n\t\tif( COM_CheckStringEmpty( host.gamedll ))\n\t\t{\n\t\t\tif( host.gamedll[0] == '@' )\n\t\t\t\tCOM_GenerateServerLibraryPath( host.gamedll + 1, out, size );\n\t\t\telse Q_strncpy( out, host.gamedll, size );\n\t\t}\n\t\telse COM_GenerateServerLibraryPath( NULL, out, size );\n\t\tbreak;\n\tdefault:\n\t\tASSERT( 0 );\n\t\tout[0] = 0;\n\t\tbreak;\n\t}\n}\n\n/*\n=============================================================================\n\n\tC++ MANGLE CONVERSION\n\n=============================================================================\n*/\n#define MAX_NESTED_NAMESPACES 16 /* MSVC limit */\n\nstatic EFunctionMangleType COM_DetectMangleType( const char *str )\n{\n\t// Itanium C++ ABI mangling always start with _Z\n\t// namespaces start with N, therefore _ZN\n\tif( !Q_strncmp( str, \"_ZN\", 3 ) )\n\t\treturn MANGLE_ITANIUM;\n\n\t// MSVC C++ mangling always start with ? and have\n\tif( str[0] == '?' && Q_strstr( str, \"@@\" ))\n\t\treturn MANGLE_MSVC;\n\n\t// allow offsets, we just silently ignore them on conversion\n\tif( !Q_strncmp( str, \"ofs:\", 4 ))\n\t\treturn MANGLE_OFFSET;\n\n\t// don't get confused by MSVC C mangling\n\tif( str[0] != '@' && Q_strchr( str, '@' ))\n\t\treturn MANGLE_VALVE;\n\n\t// not technically an error\n\treturn MANGLE_UNKNOWN;\n}\n\nchar *COM_GetMSVCName( const char *in_name )\n{\n\tstatic string   out_name;\n\tchar            *pos;\n\n\tif( in_name[0] == '?' )  // is this a MSVC C++ mangled name?\n\t{\n\t\tif(( pos = Q_strstr( in_name, \"@@\" )) != NULL )\n\t\t{\n\t\t\tptrdiff_t len = pos - in_name;\n\n\t\t\t// strip off the leading '?'\n\t\t\tQ_strncpy( out_name, in_name + 1, sizeof( out_name ));\n\t\t\tout_name[len-1] = 0; // terminate string at the \"@@\"\n\t\t\treturn out_name;\n\t\t}\n\t}\n\n\tQ_strncpy( out_name, in_name, sizeof( out_name ));\n\n\treturn out_name;\n}\n\nstatic char *COM_GetItaniumName( const char * const in_name )\n{\n\tstatic string out_name;\n\tconst char *f = in_name;\n\tstring symbols[16];\n\tuint len = 0;\n\tint i;\n\tint remaining;\n\n\tremaining = Q_strlen( f );\n\n\tif( remaining < 3 )\n\t\tgoto invalid_format;\n\n\tout_name[0] = 0;\n\n\t// skip _ZN\n\tf += 3;\n\tremaining -= 3;\n\n\tfor( i = 0; i < MAX_NESTED_NAMESPACES; i++ )\n\t{\n\t\t// parse symbol length marker\n\t\tlen = 0;\n\t\tfor( ; isdigit( *f ) && remaining > 0; f++, remaining-- )\n\t\t\tlen = len * 10 + ( *f - '0' );\n\n\t\t// sane value\n\t\tlen = Q_min( remaining, len );\n\n\t\tif( len == 0 )\n\t\t\tgoto invalid_format;\n\n\t\tQ_strncpy( symbols[i], f, Q_min( len + 1, sizeof( out_name )));\n\t\tf += len;\n\t\tremaining -= len;\n\n\t\t// end marker\n\t\tif( *f == 'E' )\n\t\t\tbreak;\n\n\t\tif( !isdigit( *f ) || remaining <= 0 )\n\t\t\tgoto invalid_format;\n\t}\n\n\tif( i == MAX_NESTED_NAMESPACES )\n\t{\n\t\tCon_DPrintf( \"%s: too much nested namespaces: %s\\n\", __func__, in_name );\n\t\treturn NULL;\n\t}\n\n\tfor( ; i >= 0; i-- )\n\t{\n\t\tQ_strncat( out_name, symbols[i], sizeof( out_name ));\n\t\tif( i > 0 )\n\t\t\tQ_strncat( out_name, \"@\", sizeof( out_name ));\n\t}\n\n\treturn out_name;\n\ninvalid_format:\n\tCon_DPrintf( \"%s: invalid format: %s\\n\", __func__, in_name );\n\treturn NULL;\n}\n\nchar **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, size_t *numfuncs )\n{\n\tstring symbols[MAX_NESTED_NAMESPACES], temp, temp2;\n\tconst char *prev;\n\tconst char *postfix[3];\n\tint i = 0;\n\tchar **ret;\n\n\t// TODO:\n\tif( to == MANGLE_MSVC )\n\t\treturn NULL;\n\n\tswitch( to )\n\t{\n\tcase MANGLE_ITANIUM:\n\t\tpostfix[0] = \"Ev\";\n\t\tpostfix[1] = \"EP11CBaseEntity\";\n\t\tpostfix[2] = \"EP11CBaseEntityS1_8USE_TYPEf\";\n\t\tbreak;\n\tdefault:\n\t\tASSERT( 0 );\n\t\treturn NULL;\n\t}\n\n\tprev = from;\n\n\tfor( i = 0; i < MAX_NESTED_NAMESPACES; i++ )\n\t{\n\t\tconst char *at = Q_strchr( prev, '@' );\n\t\tuint len;\n\n\t\tif( at ) len = (uint)( at - prev );\n\t\telse len = (uint)Q_strlen( prev );\n\n\t\tQ_strncpy( symbols[i], prev, Q_min( len + 1, sizeof( symbols[i] )));\n\n\t\tif( !at )\n\t\t\tbreak;\n\n\t\tprev = at + 1;\n\t}\n\n\tif( i == MAX_NESTED_NAMESPACES )\n\t{\n\t\tCon_DPrintf( \"%s: too much nested namespaces: %s\\n\", __func__, from );\n\t\treturn NULL;\n\t}\n\n\t// only three possible variations\n\t*numfuncs = ARRAYSIZE( postfix );\n\tret = Z_Malloc( sizeof( char * ) * ARRAYSIZE( postfix ) );\n\n\tQ_strncpy( temp, \"_ZN\", sizeof( temp ));\n\n\tfor( ; i >= 0; i-- )\n\t{\n\t\tQ_snprintf( temp2, sizeof( temp2 ), \"%u%s\", (uint)Q_strlen( symbols[i] ), symbols[i] );\n\t\tQ_strncat( temp, temp2, sizeof( temp ));\n\t}\n\n\tfor( i = 0; i < ARRAYSIZE( postfix ); i++ )\n\t{\n\t\tQ_snprintf( temp2, sizeof( temp2 ), \"%s%s\", temp, postfix[i] );\n\t\tret[i] = copystring( temp2 );\n\t}\n\n\treturn ret;\n}\n\nconst char *COM_GetPlatformNeutralName( const char *in_name )\n{\n\tEFunctionMangleType type = COM_DetectMangleType( in_name );\n\n\tswitch( type )\n\t{\n\tcase MANGLE_ITANIUM: return COM_GetItaniumName( in_name );\n\tcase MANGLE_MSVC: return COM_GetMSVCName( in_name );\n\tdefault: return in_name;\n\t}\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nstatic void Test_DetectMangleType( void )\n{\n\tTASSERT(COM_DetectMangleType( \"asdf\" ) == MANGLE_UNKNOWN );\n\tTASSERT(COM_DetectMangleType( \"012345\" ) == MANGLE_UNKNOWN );\n\tTASSERT(COM_DetectMangleType( \"?asdf\" ) == MANGLE_UNKNOWN );\n\tTASSERT(COM_DetectMangleType( \"_Zasdf\" ) == MANGLE_UNKNOWN );\n\n\tTASSERT(COM_DetectMangleType( \"ofs:1234\" ) == MANGLE_OFFSET );\n\tTASSERT(COM_DetectMangleType( \"ofs:asdf\" ) == MANGLE_OFFSET );\n\tTASSERT(COM_DetectMangleType( \"ofs:\" ) == MANGLE_OFFSET );\n\n\tTASSERT(COM_DetectMangleType( \"_ZN1f1fEv\" ) == MANGLE_ITANIUM );\n\tTASSERT(COM_DetectMangleType( \"_ZN3foo3barEv\" ) == MANGLE_ITANIUM );\n\n\tTASSERT(COM_DetectMangleType( \"?f@f@@msvcsucks\" ) == MANGLE_MSVC );\n\tTASSERT(COM_DetectMangleType( \"?foo@bar@@IHATEMSVC\" ) == MANGLE_MSVC );\n\n\tTASSERT(COM_DetectMangleType( \"f@f\" ) == MANGLE_VALVE );\n\tTASSERT(COM_DetectMangleType( \"foo@bar\" ) == MANGLE_VALVE );\n\n\t// Xash3D FWGS extensions test\n\tTASSERT(COM_DetectMangleType( \"_ZN1f1f1fEv\" ) == MANGLE_ITANIUM );\n\tTASSERT(COM_DetectMangleType( \"_ZN3foo3bar3bazEv\" ) == MANGLE_ITANIUM );\n\n\tTASSERT(COM_DetectMangleType( \"?f@f@f@@msvcsucks\" ) == MANGLE_MSVC );\n\tTASSERT(COM_DetectMangleType( \"?foo@bar@@IHATEMSVC\" ) == MANGLE_MSVC );\n\n\tTASSERT(COM_DetectMangleType( \"f@f@f\" ) == MANGLE_VALVE );\n\tTASSERT(COM_DetectMangleType( \"foo@bar@baz\" ) == MANGLE_VALVE );\n}\n\nstatic void Test_GetMSVCName( void )\n{\n\tconst char *symbols[] =\n\t{\n\t\t\"\", \"\",\n\t\t\"?f@f@@XYZA\", \"f@f\",\n\t\t\"?foo@bar@@QAEXXZ\", \"foo@bar\",\n\t\t\"foo\", \"foo\",\n\t\t\"?foo\", \"?foo\",\n\t\t\"?foo@@\", \"foo\", // not an error?\n\t\t\"?foo@bar@baz@@gotstrippedanyway\",\"foo@bar@baz\"\n\t};\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( symbols ); i += 2 )\n\t{\n\t\tMsg( \"Checking if MSVC '%s' converts to '%s'...\\n\", symbols[i], symbols[i+1] );\n\n\t\tTASSERT( !Q_strcmp( COM_GetMSVCName( symbols[i] ), symbols[i+1] ));\n\t}\n}\n\nstatic void Test_GetItaniumName( void )\n{\n\tconst char *symbols[] =\n\t{\n\t\t\"\", NULL,\n\t\t\"_\", NULL,\n\t\t\"_Z\", NULL,\n\t\t\"_ZN\", NULL,\n\t\t\"_ZNv\", NULL,\n\t\t\"_ZN4barr3foo\", NULL,\n\t\t\"_ZN3bar3foov\", NULL,\n\t\t\"_ZN4bar3fooEv\", NULL,\n\t\t\"_ZN3bar3fooEv\", \"foo@bar\",\n\t\t\"_Z3foov\", NULL,\n\t\t\"_ZN3fooEv\", \"foo\", // not possible?\n\t\t\"_ZN3baz3bar3fooEdontcare\", \"foo@bar@baz\",\n\t};\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( symbols ); i += 2 )\n\t{\n\t\tMsg( \"Checking if Itanium '%s' converts to '%s'...\\n\", symbols[i], symbols[i+1] );\n\n\t\tTASSERT( !Q_strcmp( COM_GetItaniumName( symbols[i] ), symbols[i+1] ));\n\t}\n}\n\nstatic void Test_ConvertFromValveToLocal( void )\n{\n\tconst char *symbols[] =\n\t{\n\t\t\"\", \"_ZN\",\n\t\t\"foo\", \"_ZN3foo\",\n\t\t\"xash3d@fwgs\", \"_ZN4fwgs6xash3d\",\n\t\t\"foo@bar@bazz\", \"_ZN4bazz3bar3foo\"\n\t};\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( symbols ); i += 2 )\n\t{\n\t\tchar **ret;\n\t\tsize_t numfuncs;\n\t\tsize_t symlen = Q_strlen( symbols[i + 1] );\n\n\t\tMsg( \"Checking if Valve '%s' converts to Itanium '%s'...\\n\", symbols[i], symbols[i+1] );\n\n\t\tret = COM_ConvertToLocalPlatform( MANGLE_ITANIUM, symbols[i], &numfuncs );\n\n\t\tTASSERT( numfuncs == 3 );\n\t\tTASSERT( !Q_strncmp( ret[0], symbols[i+1], symlen ));\n\t\tTASSERT( !Q_strncmp( ret[1], symbols[i+1], symlen ));\n\t\tTASSERT( !Q_strncmp( ret[2], symbols[i+1], symlen ));\n\t}\n}\n\nvoid Test_RunLibCommon( void )\n{\n\tTRUN( Test_DetectMangleType() );\n\tTRUN( Test_GetMSVCName() );\n\tTRUN( Test_GetItaniumName() );\n\tTRUN( Test_ConvertFromValveToLocal() );\n}\n#endif /* XASH_ENGINE_TESTS */\n"
  },
  {
    "path": "engine/common/library.h",
    "content": "/*\nlibrary.h - custom dlls loader\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef LIBRARY_H\n#define LIBRARY_H\n\n#define MAX_LIBRARY_EXPORTS\t\t4096\n\ntypedef struct dll_user_s\n{\n\tvoid\t*hInstance;\t\t// instance handle\n\tqboolean\tcustom_loader;\t\t// a bit who indicated loader type\n\tqboolean\tencrypted;\t\t// dll is crypted (some client.dll in HL, CS etc)\n\tchar\tdllName[32];\t\t// for debug messages\n\tchar fullPath[2048];\n\tstring shortPath;\t// actual dll paths\n\n\t// ordinals stuff, valid only on Win32\n\tword\t*ordinals;\n\tdword\t*funcs;\n\tchar\t*names[MAX_LIBRARY_EXPORTS];\t// max 4096 exports supported\n\tint\tnum_ordinals;\t\t// actual exports count\n\tuintptr_t\tfuncBase;\t\t\t// base offset\n} dll_user_t;\n\ndll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath );\nvoid *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath );\nvoid *COM_GetProcAddress( void *hInstance, const char *name );\nconst char *COM_NameForFunction( void *hInstance, void *function );\nvoid *COM_FunctionFromName_SR( void *hInstance, const char *pName ); // Save/Restore version\nvoid *COM_FunctionFromName( void *hInstance, const char *pName );\nvoid COM_FreeLibrary( void *hInstance );\nconst char *COM_GetLibraryError( void );\nqboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath );\n\n// TODO: Move to internal?\nvoid COM_ResetLibraryError( void );\nvoid COM_PushLibraryError( const char *error );\nconst char *COM_OffsetNameForFunction( void *function );\n\ntypedef enum\n{\n\tLIBRARY_CLIENT,\n\tLIBRARY_SERVER,\n\tLIBRARY_GAMEUI\n} ECommonLibraryType;\n\nvoid COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t size );\n\ntypedef enum\n{\n\tMANGLE_UNKNOWN = 0,\n\n\t/* binary offset, when NameForFunction isn't implemented */\n\tMANGLE_OFFSET,\n\n\t/* Itanium C++ ABI mangling, native for most operating systems */\n\tMANGLE_ITANIUM,\n\n\t/* MSVC \"decoration\" */\n\tMANGLE_MSVC,\n\n\t/* Valve's silly mangle for crossplatform saves */\n\tMANGLE_VALVE,\n} EFunctionMangleType;\n\n// converts to MANGLE_VALVE if possible\nconst char *COM_GetPlatformNeutralName( const char *in_name );\n\n// converts to native mangling, result must be freed\nchar **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, size_t *numfuncs );\n\n// used by lib_win.c\nchar *COM_GetMSVCName( const char *in_name );\n\n\n#endif//LIBRARY_H\n"
  },
  {
    "path": "engine/common/masterlist.c",
    "content": "/*\nmasterlist.c - multi-master list\nCopyright (C) 2018 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"netchan.h\"\n#include \"server.h\"\n\ntypedef struct master_s\n{\n\tstruct master_s *next;\n\tqboolean sent; // TODO: get rid of this internal state\n\tqboolean save;\n\tqboolean v6only;\n\tstring address;\n\tnetadr_t adr; // temporary, rewritten after each send\n\n\tuint heartbeat_challenge;\n\tdouble last_heartbeat;\n} master_t;\n\nstatic struct masterlist_s\n{\n\tmaster_t *list;\n\tqboolean modified;\n} ml;\n\nstatic CVAR_DEFINE_AUTO( sv_verbose_heartbeats, \"0\", 0, \"print every heartbeat to console\" );\n\n#define HEARTBEAT_SECONDS\t((sv_nat.value > 0.0f) ? 60.0f : 300.0f)  \t// 1 or 5 minutes\n\n/*\n========================\nNET_GetMasterHostByName\n========================\n*/\nstatic net_gai_state_t NET_GetMasterHostByName( master_t *m )\n{\n\tnet_gai_state_t res = NET_StringToAdrNB( m->address, &m->adr, m->v6only );\n\n\tif( res == NET_EAI_OK )\n\t\treturn res;\n\n\tm->adr.type = 0;\n\tif( res == NET_EAI_NONAME )\n\t\tCon_Reportf( \"Can't resolve adr: %s\\n\", m->address );\n\n\treturn res;\n}\n\n/*\n========================\nNET_SendToMasters\n\nSend request to all masterservers list\nreturn true if would block\n========================\n*/\nqboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data )\n{\n\tmaster_t *list;\n\tqboolean wait = false;\n\n\tfor( list = ml.list; list; list = list->next )\n\t{\n\t\tif( list->sent )\n\t\t\tcontinue;\n\n\t\tswitch( NET_GetMasterHostByName( list ))\n\t\t{\n\t\tcase NET_EAI_AGAIN:\n\t\t\tlist->sent = false;\n\t\t\twait = true;\n\t\t\tbreak;\n\t\tcase NET_EAI_NONAME:\n\t\t\tlist->sent = true;\n\t\t\tbreak;\n\t\tcase NET_EAI_OK:\n\t\t\tlist->sent = true;\n\t\t\tNET_SendPacket( sock, len, data, list->adr );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !wait )\n\t{\n\t\t// reset sent state\n\t\tfor( list = ml.list; list; list = list->next )\n\t\t\tlist->sent = false;\n\t}\n\n\treturn wait;\n}\n\n/*\n========================\nNET_AnnounceToMaster\n\n========================\n*/\nstatic void NET_AnnounceToMaster( master_t *m )\n{\n\tsizebuf_t msg;\n\tchar buf[16];\n\n\tm->heartbeat_challenge = COM_RandomLong( 0, INT_MAX );\n\n\tMSG_Init( &msg, \"Master Join\", buf, sizeof( buf ));\n\tMSG_WriteBytes( &msg, \"q\\xFF\", 2 );\n\tMSG_WriteDword( &msg, m->heartbeat_challenge );\n\n\tNET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ), m->adr );\n\n\tif( sv_verbose_heartbeats.value )\n\t{\n\t\tCon_Printf( S_NOTE \"sent heartbeat to %s (%s, 0x%x)\\n\",\n\t\t\tm->address, NET_AdrToString( m->adr ), m->heartbeat_challenge );\n\t}\n}\n\n/*\n========================\nNET_AnnounceToMaster\n\n========================\n*/\nvoid NET_MasterClear( void )\n{\n\tmaster_t *m;\n\n\tfor( m = ml.list; m; m = m->next )\n\t\tm->last_heartbeat = MAX_HEARTBEAT;\n}\n\n/*\n========================\nNET_MasterHeartbeat\n\n========================\n*/\nvoid NET_MasterHeartbeat( void )\n{\n\tmaster_t *m;\n\n\tif(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 )\n\t\treturn; // only public servers send heartbeats\n\n\tfor( m = ml.list; m; m = m->next )\n\t{\n\t\tif( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS )\n\t\t\tcontinue;\n\n\t\tswitch( NET_GetMasterHostByName( m ))\n\t\t{\n\t\tcase NET_EAI_AGAIN:\n\t\t\tm->last_heartbeat = MAX_HEARTBEAT; // retry on next frame\n\t\t\tif( sv_verbose_heartbeats.value )\n\t\t\t\tCon_Printf( S_NOTE \"delay heartbeat to next frame until %s resolves\\n\", m->address );\n\n\t\t\tbreak;\n\t\tcase NET_EAI_NONAME:\n\t\t\tm->last_heartbeat = host.realtime; // try to resolve again on next heartbeat\n\t\t\tbreak;\n\t\tcase NET_EAI_OK:\n\t\t\tm->last_heartbeat = host.realtime;\n\t\t\tNET_AnnounceToMaster( m );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n=================\nNET_MasterShutdown\n\nInforms all masters that this server is going down\n(ignored by master servers in current implementation)\n=================\n*/\nvoid NET_MasterShutdown( void )\n{\n\tNET_Config( true, false ); // allow remote\n\twhile( NET_SendToMasters( NS_SERVER, 2, \"\\x62\\x0A\" ));\n}\n\n\n/*\n========================\nNET_GetMasterFromAdr\n\n========================\n*/\nstatic master_t *NET_GetMasterFromAdr( netadr_t adr )\n{\n\tmaster_t *master;\n\n\tfor( master = ml.list; master; master = master->next )\n\t{\n\t\tif( NET_CompareAdr( adr, master->adr ))\n\t\t\treturn master;\n\t}\n\n\treturn NULL;\n}\n\n\n/*\n========================\nNET_GetMaster\n========================\n*/\nqboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat )\n{\n\tmaster_t *m;\n\n\tm = NET_GetMasterFromAdr( from );\n\n\tif( m )\n\t{\n\t\t*challenge = m->heartbeat_challenge;\n\t\t*last_heartbeat = m->last_heartbeat;\n\t}\n\n\treturn m != NULL;\n}\n\n/*\n========================\nNET_IsMasterAdr\n\n========================\n*/\nqboolean NET_IsMasterAdr( netadr_t adr )\n{\n\treturn NET_GetMasterFromAdr( adr ) != NULL;\n}\n\n/*\n========================\nNET_AddMaster\n\nAdd master to the list\n========================\n*/\nstatic void NET_AddMaster( const char *addr, qboolean save, qboolean v6only )\n{\n\tmaster_t *master, *last;\n\n\tfor( last = ml.list; last && last->next; last = last->next )\n\t{\n\t\tif( !Q_strcmp( last->address, addr ) ) // already exists\n\t\t\treturn;\n\t}\n\n\tmaster = Mem_Malloc( host.mempool, sizeof( *master ) );\n\tQ_strncpy( master->address, addr, sizeof( master->address ));\n\tmaster->sent = false;\n\tmaster->save = save;\n\tmaster->v6only = v6only;\n\tmaster->next = NULL;\n\tmaster->adr.type = 0;\n\n\t// link in\n\tif( last )\n\t\tlast->next = master;\n\telse\n\t\tml.list = master;\n}\n\nstatic void NET_AddMaster_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tMsg( S_USAGE \"addmaster <address>\\n\");\n\t\treturn;\n\t}\n\n\tNET_AddMaster( Cmd_Argv( 1 ), true, false ); // save them into config\n\tml.modified = true; // save config\n}\n\n/*\n========================\nNET_ClearMasters\n\nClear master list\n========================\n*/\nstatic void NET_ClearMasters_f( void )\n{\n\twhile( ml.list )\n\t{\n\t\tmaster_t *prev = ml.list;\n\t\tml.list = ml.list->next;\n\t\tMem_Free( prev );\n\t}\n}\n\n/*\n========================\nNET_ListMasters_f\n\nDisplay current master linked list\n========================\n*/\nstatic void NET_ListMasters_f( void )\n{\n\tmaster_t *list;\n\tint i;\n\n\tCon_Printf( \"Master servers:\\n\" );\n\n\tfor( i = 1, list = ml.list; list; i++, list = list->next )\n\t{\n\t\tCon_Printf( \"%d\\t%s\", i, list->address );\n\t\tif( list->adr.type != 0 )\n\t\t\tCon_Printf( \"\\t%s\\n\", NET_AdrToString( list->adr ));\n\t\telse Con_Printf( \"\\n\" );\n\t}\n}\n\n/*\n========================\nNET_LoadMasters\n\nLoad master server list from xashcomm.lst\n========================\n*/\nstatic void NET_LoadMasters( void )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tchar token[MAX_TOKEN];\n\n\tafile = FS_LoadFile( \"xashcomm.lst\", NULL, true );\n\n\tif( !afile ) // file doesn't exist yet\n\t{\n\t\tCon_Reportf( \"Cannot load xashcomm.lst\\n\" );\n\t\treturn;\n\t}\n\n\tpfile = (char*)afile;\n\n\t// format: master <addr>\\n\n\twhile( ( pfile = COM_ParseFile( pfile, token, sizeof( token ) ) ) )\n\t{\n\t\tif( !Q_strcmp( token, \"master\" )) // load addr\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ) );\n\t\t\tNET_AddMaster( token, true, false );\n\t\t}\n\t\telse if( !Q_strcmp( token, \"master6\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ) );\n\t\t\tNET_AddMaster( token, true, true );\n\t\t}\n\t}\n\n\tMem_Free( afile );\n\n\tml.modified = false;\n}\n\n/*\n========================\nNET_SaveMasters\n\nSave master server list to xashcomm.lst, except for default\n========================\n*/\nvoid NET_SaveMasters( void )\n{\n\tfile_t *f;\n\tmaster_t *m;\n\n\tif( !ml.modified )\n\t\treturn;\n\n\tf = FS_Open( \"xashcomm.lst\", \"w\", true );\n\n\tif( !f )\n\t{\n\t\tCon_Reportf( S_ERROR \"Couldn't write xashcomm.lst\\n\" );\n\t\treturn;\n\t}\n\n\tfor( m = ml.list; m; m = m->next )\n\t{\n\t\tif( m->save )\n\t\t\tFS_Printf( f, \"%s %s\\n\", m->v6only ? \"master6\" : \"master\", m->address );\n\t}\n\n\tFS_Close( f );\n}\n\n/*\n========================\nNET_InitMasters\n\nInitialize master server list\n========================\n*/\nvoid NET_InitMasters( void )\n{\n\tCmd_AddRestrictedCommand( \"addmaster\", NET_AddMaster_f, \"add address to masterserver list\" );\n\tCmd_AddRestrictedCommand( \"clearmasters\", NET_ClearMasters_f, \"clear masterserver list\" );\n\tCmd_AddCommand( \"listmasters\", NET_ListMasters_f, \"list masterservers\" );\n\n\tCvar_RegisterVariable( &sv_verbose_heartbeats );\n\n\t{ // IPv4-only\n\t\tNET_AddMaster( \"mentality.rip:27010\", false, false );\n\t\tNET_AddMaster( \"ms2.mentality.rip:27010\", false, false );\n\t\tNET_AddMaster( \"ms3.mentality.rip:27010\", false, false );\n\t}\n\n\t{ // IPv6-only\n\t\tNET_AddMaster( \"aaaa.mentality.rip:27010\", false, true );\n\t\tNET_AddMaster( \"aaaa.ms2.mentality.rip:27010\", false, true );\n\t}\n\n\t{ // testing servers, might be offline\n\t\tNET_AddMaster( \"mentality.rip:27011\", false, false );\n\t\tNET_AddMaster( \"aaaa.mentality.rip:27011\", false, true );\n\t}\n\n\tNET_LoadMasters( );\n}\n"
  },
  {
    "path": "engine/common/mod_alias.c",
    "content": "/*\nmod_alias.c - alias model loading\nCopyright (C) 2010 Uncle Mike\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"alias.h\"\n#if !XASH_DEDICATED\n#include \"ref_common.h\"\n#endif // XASH_DEDICATED\n#include \"mod_local.h\"\n#include \"xash3d_mathlib.h\"\n\nstatic int g_posenum = 0;\nstatic const trivertex_t *g_poseverts[MAXALIASFRAMES];\n\nstatic const void *Mod_LoadAliasFrame( const daliasframe_t *pdaliasframe, maliasframedesc_t *frame, const aliashdr_t *aliashdr )\n{\n\tconst trivertex_t *pinframe;\n\tint i;\n\n\tQ_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name ));\n\tframe->firstpose = g_posenum;\n\tframe->numposes = 1;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tframe->bboxmin.v[i] = pdaliasframe->bboxmin.v[i];\n\t\tframe->bboxmax.v[i] = pdaliasframe->bboxmax.v[i];\n\t}\n\n\tpinframe = (const trivertex_t *)(pdaliasframe + 1);\n\n\tg_poseverts[g_posenum] = pinframe;\n\tg_posenum++;\n\n\tpinframe += aliashdr->numverts;\n\n\treturn (void *)pinframe;\n}\n\nstatic const void *Mod_LoadAliasGroup( const daliasgroup_t *pingroup, maliasframedesc_t *frame, const aliashdr_t *aliashdr )\n{\n\tconst daliasinterval_t *pin_intervals;\n\tconst void *ptemp;\n\tint i, numframes;\n\n\tframe->firstpose = g_posenum;\n\tframe->numposes = numframes = pingroup->numframes;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tframe->bboxmin.v[i] = pingroup->bboxmin.v[i];\n\t\tframe->bboxmax.v[i] = pingroup->bboxmax.v[i];\n\t}\n\n\tpin_intervals = (const daliasinterval_t *)(pingroup + 1);\n\n\t// all the intervals are always equal 0.1 so we don't care about them\n\tframe->interval = pin_intervals->interval;\n\tpin_intervals += numframes;\n\tptemp = (void *)pin_intervals;\n\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\tg_poseverts[g_posenum] = (const trivertex_t *)((const daliasframe_t *)ptemp + 1);\n\t\tptemp = g_poseverts[g_posenum] + aliashdr->numverts;\n\t\tg_posenum++;\n\t}\n\n\treturn ptemp;\n}\n\nstatic void Mod_CalcAliasBounds( model_t *mod, const aliashdr_t *aliashdr )\n{\n\tint\ti, j, k;\n\tfloat\tradius;\n\tfloat\tdist;\n\tvec3_t\tv;\n\n\tClearBounds( mod->mins, mod->maxs );\n\tradius = 0.0f;\n\n\t// process verts\n\tfor( i = 0; i < aliashdr->numposes; i++ )\n\t{\n\t\tfor( j = 0; j < aliashdr->numverts; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < 3; k++ )\n\t\t\t\tv[k] = g_poseverts[i][j].v[k] * aliashdr->scale[k] + aliashdr->scale_origin[k];\n\n\t\t\tAddPointToBounds( v, mod->mins, mod->maxs );\n\t\t\tdist = DotProduct( v, v );\n\n\t\t\tif( radius < dist )\n\t\t\t\tradius = dist;\n\t\t}\n\t}\n\n\tmod->radius = sqrt( radius );\n}\n\nstatic const void *Mod_LoadAllSkins( model_t *mod, int numskins, const daliasskintype_t *pskintype, const aliashdr_t *aliashdr )\n{\n\tint\ti, size;\n\n\tif(( numskins < 1 ) || ( numskins > MAX_SKINS ))\n\t\tHost_Error( \"%s: Invalid # of skins: %d\\n\", __func__, numskins );\n\n\tsize = aliashdr->skinwidth * aliashdr->skinheight;\n\n\t// just skipping textures, renderer will take care of them later\n\tfor( i = 0; i < numskins; i++ )\n\t{\n\t\tif( pskintype->type == ALIAS_SKIN_SINGLE )\n\t\t{\n\t\t\tconst byte *ptexture = (const byte *)&pskintype[1];\n\n\t\t\tpskintype = (const daliasskintype_t *)( ptexture + size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tconst daliasskingroup_t *pinskingroup = (const daliasskingroup_t *)&pskintype[1];\n\t\t\tconst daliasskininterval_t *pinskinintervals = (const daliasskininterval_t *)&pinskingroup[1];\n\t\t\tconst byte *ptexture = (const byte *)&pinskinintervals[pinskingroup->numskins];\n\n\t\t\tpskintype = (const daliasskintype_t *)( ptexture + size );\n\t\t}\n\t}\n\n\treturn pskintype;\n}\n\n\n/*\n====================\nMod_LoadAliasModel\n\nload alias model\n====================\n*/\nvoid Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded )\n{\n\tconst daliasframetype_t *pframetype;\n\tconst daliasskintype_t *pskintype;\n\tconst dtriangle_t *pintriangles;\n\tconst daliashdr_t *pinmodel;\n\tconst stvert_t *pinstverts;\n\taliashdr_t *m_pAliasHeader;\n\tsize_t size;\n\tchar poolname[MAX_VA_STRING];\n\tint i;\n\n\tif( loaded ) *loaded = false;\n\tpinmodel = (const daliashdr_t *)buffer;\n\ti = pinmodel->version;\n\n\tif( i != ALIAS_VERSION )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has wrong version number (%i should be %i)\\n\", mod->name, i, ALIAS_VERSION );\n\t\treturn;\n\t}\n\n\tif( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 )\n\t\treturn; // how is it possible to make that?\n\n\tQ_snprintf( poolname, sizeof( poolname ), \"^2%s^7\", mod->name );\n\tmod->mempool = Mem_AllocPool( poolname );\n\n\tsize = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t );\n\tmod->cache.data = m_pAliasHeader = Mem_Calloc( mod->mempool, size );\n\n\t// endian-adjust and copy the data, starting with the alias model header\n\tm_pAliasHeader->numverts = pinmodel->numverts;\n\n\tif( m_pAliasHeader->numverts > MAXALIASVERTS )\n\t{\n\t\tCon_DPrintf( S_ERROR \"model %s has too many vertices\\n\", mod->name );\n\t\treturn;\n\t}\n\n\tmod->flags = pinmodel->flags; // share effects flags\n\tm_pAliasHeader->boundingradius = pinmodel->boundingradius;\n\tm_pAliasHeader->numskins = pinmodel->numskins;\n\tm_pAliasHeader->skinwidth = pinmodel->skinwidth;\n\tm_pAliasHeader->skinheight = pinmodel->skinheight;\n\tm_pAliasHeader->numtris = pinmodel->numtris;\n\tmod->numframes = m_pAliasHeader->numframes = pinmodel->numframes;\n\tm_pAliasHeader->size = pinmodel->size;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tm_pAliasHeader->scale[i] = pinmodel->scale[i];\n\t\tm_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i];\n\t\tm_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i];\n\t}\n\n\t// load the skins\n\tpskintype = (const daliasskintype_t *)&pinmodel[1];\n\tpskintype = Mod_LoadAllSkins( mod, m_pAliasHeader->numskins, pskintype, m_pAliasHeader );\n\t// will be done at renderer side...\n\n\t// load base s and t vertices\n\tpinstverts = (const stvert_t *)pskintype;\n\t// will be done at renderer side...\n\n\t// load triangle lists\n\tpintriangles = (const dtriangle_t *)&pinstverts[m_pAliasHeader->numverts];\n\t// will be done at renderer side\n\n\t// load the frames\n\tpframetype = (const daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris];\n\tm_pAliasHeader->pposeverts = g_poseverts; // store the pointer to be accessed by renderer\n\tg_posenum = 0;\n\n\tfor( i = 0; i < m_pAliasHeader->numframes; i++ )\n\t{\n\t\taliasframetype_t frametype = pframetype->type;\n\n\t\tif( frametype == ALIAS_SINGLE )\n\t\t\tpframetype = (const daliasframetype_t *)Mod_LoadAliasFrame((const daliasframe_t *)&pframetype[1], &m_pAliasHeader->frames[i], m_pAliasHeader );\n\t\telse pframetype = (const daliasframetype_t *)Mod_LoadAliasGroup(( const daliasgroup_t *)&pframetype[1], &m_pAliasHeader->frames[i], m_pAliasHeader );\n\t}\n\n\tm_pAliasHeader->numposes = g_posenum;\n\tMod_CalcAliasBounds( mod, m_pAliasHeader );\n\tmod->type = mod_alias;\n\n\tif( loaded ) *loaded = true;\t// done\n}\n"
  },
  {
    "path": "engine/common/mod_bmodel.c",
    "content": "/*\nmod_bmodel.c - loading & handling world and brushmodels\nCopyright (C) 2016 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"mod_local.h\"\n#include \"sprite.h\"\n#include \"xash3d_mathlib.h\"\n#include \"alias.h\"\n#include \"studio.h\"\n#include \"wadfile.h\"\n#include \"world.h\"\n#include \"enginefeatures.h\"\n#include \"client.h\"\n#include \"server.h\"\t\t\t// LUMP_ error codes\n#include \"ref_common.h\"\n#if defined( HAVE_OPENMP )\n#include <omp.h>\n#endif // HAVE_OPENMP\n\n#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 )\n\ntypedef struct leaflist_s\n{\n\tint\t\t\tcount;\n\tint\t\t\tmaxcount;\n\tqboolean\t\t\toverflowed;\n\tint\t\t\t*list;\n\tvec3_t\t\t\tmins, maxs;\n\tint\t\t\ttopnode;\t\t// for overflows where each leaf can't be stored individually\n} leaflist_t;\n\ntypedef struct\n{\n\t// generic lumps\n\tdmodel_t\t\t\t*submodels;\n\tsize_t\t\t\tnumsubmodels;\n\n\tdvertex_t\t\t\t*vertexes;\n\tsize_t\t\t\tnumvertexes;\n\n\tdplane_t\t\t\t*planes;\n\tsize_t\t\t\tnumplanes;\n\n\tunion\n\t{\n\t\tdnode_t\t\t*nodes;\n\t\tdnode32_t\t\t*nodes32;\n\t};\n\tsize_t\t\t\tnumnodes;\n\n\tunion\n\t{\n\t\tdleaf_t\t\t*leafs;\n\t\tdleaf32_t\t\t*leafs32;\n\t};\n\tsize_t\t\t\tnumleafs;\n\n\tunion\n\t{\n\t\tdclipnode_t\t*clipnodes;\n\t\tdclipnode32_t\t*clipnodes32;\n\t};\n\tsize_t\t\t\tnumclipnodes;\n\n\tdtexinfo_t\t\t*texinfo;\n\tsize_t\t\t\tnumtexinfo;\n\n\tunion\n\t{\n\t\tdmarkface_t\t*markfaces;\n\t\tdmarkface32_t\t*markfaces32;\n\t};\n\tsize_t\t\t\tnummarkfaces;\n\n\tdsurfedge_t\t\t*surfedges;\n\tsize_t\t\t\tnumsurfedges;\n\n\tunion\n\t{\n\t\tdedge_t\t\t*edges;\n\t\tdedge32_t\t\t*edges32;\n\t};\n\tsize_t\t\t\tnumedges;\n\n\tunion\n\t{\n\t\tdface_t\t\t*surfaces;\n\t\tdface32_t\t\t*surfaces32;\n\t};\n\tsize_t\t\t\tnumsurfaces;\n\n\tdfaceinfo_t\t\t*faceinfo;\n\tsize_t\t\t\tnumfaceinfo;\n\n\t// array lumps\n\tbyte\t\t\t*visdata;\n\tsize_t\t\t\tvisdatasize;\n\n\tbyte\t\t\t*lightdata;\n\tsize_t\t\t\tlightdatasize;\n\n\tbyte\t\t\t*deluxdata;\n\tsize_t\t\t\tdeluxdatasize;\n\n\tbyte\t\t\t*shadowdata;\n\tsize_t\t\t\tshadowdatasize;\n\n\tbyte\t\t\t*entdata;\n\tsize_t\t\t\tentdatasize;\n\n\t// lumps that required personal handler\n\tdmiptexlump_t\t\t*textures;\n\tsize_t\t\t\ttexdatasize;\n\n\t// intermediate arrays (pointers will lost after loading, but keep the data)\n\tcolor24\t\t\t*deluxedata_out;\t// deluxemap data pointer\n\tbyte\t\t\t*shadowdata_out;\t// occlusion data pointer\n\tdclipnode32_t\t\t*clipnodes_out;\t// temporary 32-bit array to hold clipnodes\n\n\t// misc stuff\n\tint       lightmap_samples;\t// samples per lightmap (1 or 3)\n\tint       version;\t\t// model version\n\tqboolean  isworld;\n\tqboolean  isbsp30ext;\n} dbspmodel_t;\n\ntypedef struct\n{\n\tconst char\t*lumpname;\n\tsize_t\t\tentrysize;\n\tsize_t\t\tmaxcount;\n\tsize_t\t\tcount;\n} mlumpstat_t;\n\ntypedef struct\n{\n\tchar\t\tname[64];\t\t// just for debug\n\n\t// count errors and warnings\n\tint\t\tnumerrors;\n\tint\t\tnumwarnings;\n} loadstat_t;\n\n#define CHECK_OVERFLOW\tBIT( 0 )\t\t// if some of lumps will be overflowed this non fatal for us. But some lumps are critical. mark them\n#define USE_EXTRAHEADER\tBIT( 1 )\n\n#define LUMP_SAVESTATS\tBIT( 0 )\n#define LUMP_TESTONLY\tBIT( 1 )\n#define LUMP_SILENT\t\tBIT( 2 )\n#define LUMP_BSP30EXT   BIT( 3 ) // extra marker for Mod_LoadLump\n\ntypedef struct\n{\n\tint          lumpnumber;\n\tint          flags;\n\tconst size_t mincount;\n\tconst size_t maxcount;\n\tconst int    entrysize;\n\tconst int    entrysize32;\t// alternative (-1 by default)\n\tconst char  *loadname;\n\tconst void **dataptr;\n\tsize_t      *count;\n} mlumpinfo_t;\n\nworld_static_t\t\tworld;\nstatic dbspmodel_t\t\tsrcmodel;\nstatic loadstat_t\t\tloadstat;\nstatic model_t\t\t*worldmodel;\nstatic byte\t\tg_visdata[(MAX_MAP_LEAFS+7)/8];\t// intermediate buffer\nstatic mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS];\nstatic mlumpinfo_t srclumps[HEADER_LUMPS] =\n{\n\t{\n\t\t.lumpnumber = LUMP_ENTITIES,\n\t\t.mincount = 32,\n\t\t.maxcount = MAX_MAP_ENTSTRING,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"entities\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.entdata,\n\t\t.count = &srcmodel.entdatasize,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_PLANES,\n\t\t.mincount = 1,\n\t\t.maxcount = MAX_MAP_PLANES,\n\t\t.entrysize = sizeof( dplane_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"planes\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.planes,\n\t\t.count = &srcmodel.numplanes,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_TEXTURES,\n\t\t.mincount = 1,\n\t\t.maxcount = MAX_MAP_MIPTEX,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"textures\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.textures,\n\t\t.count = &srcmodel.texdatasize,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_VERTEXES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_VERTS,\n\t\t.entrysize = sizeof( dvertex_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"vertexes\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.vertexes,\n\t\t.count = &srcmodel.numvertexes,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_VISIBILITY,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_VISIBILITY,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"visibility\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.visdata,\n\t\t.count = &srcmodel.visdatasize,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_NODES,\n\t\t.mincount = 1,\n\t\t.maxcount = MAX_MAP_NODES,\n\t\t.entrysize = sizeof( dnode_t ),\n\t\t.entrysize32 = sizeof( dnode32_t ),\n\t\t.loadname = \"nodes\",\n\t\t.flags = CHECK_OVERFLOW,\n\t\t.dataptr = (const void **)&srcmodel.nodes,\n\t\t.count = &srcmodel.numnodes,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_TEXINFO,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_TEXINFO,\n\t\t.entrysize = sizeof( dtexinfo_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"texinfo\",\n\t\t.flags = CHECK_OVERFLOW,\n\t\t.dataptr = (const void **)&srcmodel.texinfo,\n\t\t.count = &srcmodel.numtexinfo,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_FACES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_FACES,\n\t\t.entrysize = sizeof( dface_t ),\n\t\t.entrysize32 = sizeof( dface32_t ),\n\t\t.loadname = \"faces\",\n\t\t.flags = CHECK_OVERFLOW,\n\t\t.dataptr = (const void **)&srcmodel.surfaces,\n\t\t.count = &srcmodel.numsurfaces,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_LIGHTING,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_LIGHTING,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"lightmaps\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.lightdata,\n\t\t.count = &srcmodel.lightdatasize,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_CLIPNODES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_CLIPNODES,\n\t\t.entrysize = sizeof( dclipnode_t ),\n\t\t.entrysize32 = sizeof( dclipnode32_t ),\n\t\t.loadname = \"clipnodes\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.clipnodes,\n\t\t.count = &srcmodel.numclipnodes,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_LEAFS,\n\t\t.mincount = 1,\n\t\t.maxcount = MAX_MAP_LEAFS,\n\t\t.entrysize = sizeof( dleaf_t ),\n\t\t.entrysize32 = sizeof( dleaf32_t ),\n\t\t.loadname = \"leafs\",\n\t\t.flags = CHECK_OVERFLOW,\n\t\t.dataptr = (const void **)&srcmodel.leafs,\n\t\t.count = &srcmodel.numleafs,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_MARKSURFACES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_MARKSURFACES,\n\t\t.entrysize = sizeof( dmarkface_t ),\n\t\t.entrysize32 = sizeof( dmarkface32_t ),\n\t\t.loadname = \"markfaces\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.markfaces,\n\t\t.count = &srcmodel.nummarkfaces,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_EDGES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_EDGES,\n\t\t.entrysize = sizeof( dedge_t ),\n\t\t.entrysize32 = sizeof( dedge32_t ),\n\t\t.loadname = \"edges\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.edges,\n\t\t.count = &srcmodel.numedges,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_SURFEDGES,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_SURFEDGES,\n\t\t.entrysize = sizeof( dsurfedge_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"surfedges\",\n\t\t.flags = 0,\n\t\t.dataptr = (const void **)&srcmodel.surfedges,\n\t\t.count = &srcmodel.numsurfedges,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_MODELS,\n\t\t.mincount = 1,\n\t\t.maxcount = MAX_MAP_MODELS,\n\t\t.entrysize = sizeof( dmodel_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"models\",\n\t\t.flags = CHECK_OVERFLOW,\n\t\t.dataptr = (const void **)&srcmodel.submodels,\n\t\t.count = &srcmodel.numsubmodels,\n\t},\n};\n\nstatic const mlumpinfo_t extlumps[EXTRA_LUMPS] =\n{\n\t{\n\t\t.lumpnumber = LUMP_LIGHTVECS,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_LIGHTING,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"deluxmaps\",\n\t\t.flags = USE_EXTRAHEADER,\n\t\t.dataptr = (const void **)&srcmodel.deluxdata,\n\t\t.count = &srcmodel.deluxdatasize,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_FACEINFO,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_FACEINFO,\n\t\t.entrysize = sizeof( dfaceinfo_t ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"faceinfos\",\n\t\t.flags = CHECK_OVERFLOW|USE_EXTRAHEADER,\n\t\t.dataptr = (const void **)&srcmodel.faceinfo,\n\t\t.count = &srcmodel.numfaceinfo,\n\t},\n\t{\n\t\t.lumpnumber = LUMP_SHADOWMAP,\n\t\t.mincount = 0,\n\t\t.maxcount = MAX_MAP_LIGHTING / 3,\n\t\t.entrysize = sizeof( byte ),\n\t\t.entrysize32 = -1,\n\t\t.loadname = \"shadowmap\",\n\t\t.flags = USE_EXTRAHEADER,\n\t\t.dataptr = (const void **)&srcmodel.shadowdata,\n\t\t.count = &srcmodel.shadowdatasize,\n\t},\n};\n\n#define BOX_CLIPNODES_INITIALIZER \\\n\t{ \\\n\t\t.planenum = 0, \\\n\t\t.children = { CONTENTS_EMPTY, 1 }, \\\n\t}, \\\n\t{ \\\n\t\t.planenum = 1, \\\n\t\t.children = { 2, CONTENTS_EMPTY }, \\\n\t}, \\\n\t{ \\\n\t\t.planenum = 2, \\\n\t\t.children = { CONTENTS_EMPTY, 3 }, \\\n\t}, \\\n\t{ \\\n\t\t.planenum = 3, \\\n\t\t.children = { 4, CONTENTS_EMPTY }, \\\n\t}, \\\n\t{ \\\n\t\t.planenum = 4, \\\n\t\t.children = { CONTENTS_EMPTY, 5 }, \\\n\t}, \\\n\t{ \\\n\t\t.planenum = 5, \\\n\t\t.children = { CONTENTS_SOLID, CONTENTS_EMPTY }, \\\n\t}, \\\n\nconst mclipnode16_t box_clipnodes16[6] = { BOX_CLIPNODES_INITIALIZER };\nconst mclipnode32_t box_clipnodes32[6] = { BOX_CLIPNODES_INITIALIZER };\n\n/*\n===============================================================================\n\n\t\t\tStatic helper functions\n\n===============================================================================\n*/\n\nstatic mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i )\n{\n\tif( i < 0 || i >= bmod->textures->nummiptex )\n\t\treturn NULL;\n\n\tif( bmod->textures->dataofs[i] == -1 )\n\t\treturn NULL;\n\n\treturn (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] );\n}\n\n// Returns index of WAD that texture was found in, or -1 if not found.\nstatic int Mod_LoadTextureFromWadList( wadlist_t *list, const char *name, rgbdata_t **pic, char *texpath, size_t texpathlen )\n{\n\tint i;\n\n\tif( !list || !COM_CheckString( name ))\n\t\treturn -1;\n\n\t// check wads in reverse order\n\tfor( i = list->count - 1; i >= 0; i-- )\n\t{\n\t\tsearchpath_t *sp = NULL;\n\n\t\twhile(( sp = g_fsapi.GetArchiveByName( list->wadnames[i], sp )))\n\t\t{\n\t\t\tfs_offset_t len;\n\t\t\tbyte *buf;\n\t\t\tchar file[MAX_VA_STRING];\n\t\t\tint pack_ind;\n\n\t\t\tQ_snprintf( file, sizeof( file ), \"%s.mip\", name );\n\t\t\tpack_ind = g_fsapi.FindFileInArchive( sp, file, NULL, 0 );\n\n\t\t\tif( pack_ind < 0 )\n\t\t\t\tcontinue;\n\n\t\t\tif( texpath != NULL )\n\t\t\t\tQ_snprintf( texpath, texpathlen, \"%s/%s.mip\", list->wadnames[i], name );\n\n\t\t\tif( pic == NULL )\n\t\t\t\treturn i; // dedicated server don't want to load the textures (why?)\n\n\t\t\tif( !( buf = g_fsapi.LoadFileFromArchive( sp, file, pack_ind, &len, false )))\n\t\t\t{\n\t\t\t\t*pic = NULL;\n\t\t\t\treturn i; // corrupted file, don't ignore it\n\t\t\t}\n\n\t\t\t// tell imagelib to directly load this texture to save time\n\t\t\tQ_snprintf( file, sizeof( file ), \"#%s/%s.mip\", list->wadnames[i], name );\n\t\t\t*pic = FS_LoadImage( file, buf, len );\n\t\t\tMem_Free( buf );\n\t\t\treturn i; // if file is corrupted, it's fine, we want to tell the user about it\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nstatic fs_offset_t Mod_CalculateMipTexSize( const mip_t *mt, qboolean palette )\n{\n\tif( !mt )\n\t\treturn 0;\n\n\treturn sizeof( *mt ) + (( mt->width * mt->height * 85 ) >> 6 ) +\n\t\t( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 );\n}\n\nstatic qboolean Mod_CalcMipTexUsesCustomPalette( model_t *mod, dbspmodel_t *bmod, int textureIndex )\n{\n\tint nextTextureIndex = 0;\n\tmip_t *mipTex;\n\tfs_offset_t size, remainingBytes;\n\n\tmipTex = Mod_GetMipTexForTexture( bmod, textureIndex );\n\n\tif( !mipTex || mipTex->offsets[0] <= 0 )\n\t\treturn false;\n\n\t// Calculate the size assuming we are not using a custom palette.\n\tsize = Mod_CalculateMipTexSize( mipTex, false );\n\n\t// Compute next data offset to determine allocated miptex space\n\tfor( nextTextureIndex = textureIndex + 1; nextTextureIndex < mod->numtextures; nextTextureIndex++ )\n\t{\n\t\tint nextOffset = bmod->textures->dataofs[nextTextureIndex];\n\n\t\tif( nextOffset != -1 )\n\t\t{\n\t\t\tremainingBytes = nextOffset - ( bmod->textures->dataofs[textureIndex] + size );\n\t\t\treturn remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;\n\t\t}\n\t}\n\n\t// There was no other miptex after this one.\n\t// See if there is enough space between the end and our offset.\n\tremainingBytes = bmod->texdatasize - ( bmod->textures->dataofs[textureIndex] + size );\n\treturn remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;\n}\n\nstatic qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex )\n{\n\tif( !tex )\n\t\treturn false;\n\n\t// Not an animated texture name\n\tif( tex->name[0] != '-' && tex->name[0] != '+' )\n\t\treturn false;\n\n\t// Name implies texture is animated - check second character is valid.\n\tif( !( tex->name[1] >= '0' && tex->name[1] <= '9' ) &&\n\t\t!( tex->name[1] >= 'a' && tex->name[1] <= 'j' ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: animating texture \\\"%s\\\" has invalid name\\n\", __func__, tex->name );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic void Mod_CreateDefaultTexture( model_t *mod, texture_t **texture )\n{\n\ttexture_t *tex;\n\n\t// Pointer must be valid, and value pointed to must be null.\n\tif( !texture || *texture != NULL )\n\t\treturn;\n\n\t*texture = tex = Mem_Calloc( mod->mempool, sizeof( *tex ));\n\tQ_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name ));\n\n#if !XASH_DEDICATED\n\tif( !Host_IsDedicated( ))\n\t{\n\t\ttex->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );\n\t\ttex->width = 16;\n\t\ttex->height = 16;\n\t}\n#endif // XASH_DEDICATED\n}\n\n/*\n===============================================================================\n\n\t\t\tMAP PROCESSING\n\n===============================================================================\n*/\n/*\n=================\nMod_LoadLump\n\ngeneric loader\n=================\n*/\nstatic void Mod_LoadLump( const byte *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags )\n{\n\tint\tversion = ((dheader_t *)in)->version;\n\tsize_t\tnumelems, real_entrysize;\n\tchar\tmsg1[32], msg2[32];\n\tdlump_t\t*l = NULL;\n\n\tif( FBitSet( info->flags, USE_EXTRAHEADER ))\n\t{\n\t\tdextrahdr_t *header = (dextrahdr_t *)((byte *)in + sizeof( dheader_t ));\n\t\tif( header->id != IDEXTRAHEADER || header->version != EXTRA_VERSION )\n\t\t\treturn;\n\t\tl = &header->lumps[info->lumpnumber];\n\t}\n\telse\n\t{\n\t\tdheader_t\t*header = (dheader_t *)in;\n\t\tl = &header->lumps[info->lumpnumber];\n\t}\n\n\t// lump is unused by engine for some reasons ?\n\tif( !l || info->entrysize <= 0 || info->maxcount <= 0 )\n\t\treturn;\n\n\treal_entrysize = info->entrysize; // default\n\n\t// analyze real entrysize\n\tif( version == QBSP2_VERSION && info->entrysize32 > 0 )\n\t{\n\t\t// always use alternate entrysize for BSP2\n\t\treal_entrysize = info->entrysize32;\n\t}\n\telse if( version == HLBSP_VERSION && FBitSet( flags, LUMP_BSP30EXT ) && info->lumpnumber == LUMP_CLIPNODES )\n\t{\n\t\t// if this map is bsp30ext, try to guess extended clipnodes\n\t\tif((( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize32 ) >= MAX_MAP_CLIPNODES_HLBSP ))\n\t\t{\n\t\t\treal_entrysize = info->entrysize32;\n\t\t}\n\t}\n\n\t// bmodels not required the visibility\n\tif( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY )\n\t\tSetBits( flags, LUMP_SILENT ); // shut up warning\n\n\t// fill the stats for world\n\tif( FBitSet( flags, LUMP_SAVESTATS ))\n\t{\n\t\tstat->lumpname = info->loadname;\n\t\tstat->entrysize = real_entrysize;\n\t\tstat->maxcount = info->maxcount;\n\t\tif( real_entrysize != 0 )\n\t\t\tstat->count = l->filelen / real_entrysize;\n\t}\n\n\tQ_strncpy( msg1, info->loadname, sizeof( msg1 ));\n\tQ_strncpy( msg2, info->loadname, sizeof( msg2 ));\n\tmsg2[0] = Q_toupper( msg2[0] ); // first letter in cap\n\n\t// lump is not present\n\tif( l->filelen <= 0 )\n\t{\n\t\t// don't warn about extra lumps - it's optional\n\t\tif( !FBitSet( info->flags, USE_EXTRAHEADER ))\n\t\t{\n\t\t\t// some data array that may be optional\n\t\t\tif( real_entrysize == sizeof( byte ))\n\t\t\t{\n\t\t\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\t\t{\n\t\t\t\t\tCon_DPrintf( S_WARN \"map ^2%s^7 has no %s\\n\", loadstat.name, msg1 );\n\t\t\t\t\tloadstat.numwarnings++;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if( info->mincount > 0 )\n\t\t\t{\n\t\t\t\t// it has the mincount and the lump is completely missed!\n\t\t\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\t\t\tCon_DPrintf( S_ERROR \"map ^2%s^7 has no %s\\n\", loadstat.name, msg1 );\n\t\t\t\tloadstat.numerrors++;\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tif( l->filelen % real_entrysize )\n\t{\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_DPrintf( S_ERROR \"Mod_Load%s: Lump size %d was not a multiple of %zu bytes\\n\", msg2, l->filelen, real_entrysize );\n\t\tloadstat.numerrors++;\n\t\treturn;\n\t}\n\n\tnumelems = l->filelen / real_entrysize;\n\n\tif( numelems < info->mincount )\n\t{\n\t\t// it has the mincount and it's smaller than this limit\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_DPrintf( S_ERROR \"map ^2%s^7 has no %s\\n\", loadstat.name, msg1 );\n\t\tloadstat.numerrors++;\n\t\treturn;\n\t}\n\n\tif( numelems > info->maxcount )\n\t{\n\t\t// it has the maxcount and it's overflowed\n\t\tif( FBitSet( info->flags, CHECK_OVERFLOW ))\n\t\t{\n\t\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\t\tCon_DPrintf( S_ERROR \"map ^2%s^7 has too many %s\\n\", loadstat.name, msg1 );\n\t\t\tloadstat.numerrors++;\n\t\t\treturn;\n\t\t}\n\t\telse if( !FBitSet( flags, LUMP_SILENT ))\n\t\t{\n\t\t\t// just throw warning\n\t\t\tCon_DPrintf( S_WARN \"map ^2%s^7 has too many %s\\n\", loadstat.name, msg1 );\n\t\t\tloadstat.numwarnings++;\n\t\t}\n\t}\n\n\tif( FBitSet( flags, LUMP_TESTONLY ))\n\t\treturn; // don't fill the intermediate struct\n\n\t// all checks are passed, store pointers\n\tif( info->dataptr ) *info->dataptr = (void *)(in + l->fileofs);\n\tif( info->count ) *info->count = numelems;\n}\n\n/*\n================\nMod_ArrayUsage\n================\n*/\nstatic int Mod_ArrayUsage( const char *szItem, int items, int maxitems, int itemsize )\n{\n\tfloat\tpercentage = maxitems ? (items * 100.0f / maxitems) : 0.0f;\n\n\tCon_Printf( \"%-12s  %7i/%-7i  %8i/%-8i  (%4.1f%%) \", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage );\n\n\tif( percentage > 99.99f )\n\t\tCon_Printf( \"^1SIZE OVERFLOW!!!^7\\n\" );\n\telse if( percentage > 95.0f )\n\t\tCon_Printf( \"^3SIZE DANGER!^7\\n\" );\n\telse if( percentage > 80.0f )\n\t\tCon_Printf( \"^2VERY FULL!^7\\n\" );\n\telse Con_Printf( \"\\n\" );\n\n\treturn items * itemsize;\n}\n\n/*\n================\nMod_GlobUsage\n================\n*/\nstatic int Mod_GlobUsage( const char *szItem, int itemstorage, int maxstorage )\n{\n\tfloat\tpercentage = maxstorage ? (itemstorage * 100.0f / maxstorage) : 0.0f;\n\n\tCon_Printf( \"%-15s  %-12s  %8i/%-8i  (%4.1f%%) \", szItem, \"[variable]\", itemstorage, maxstorage, percentage );\n\n\tif( percentage > 99.99f )\n\t\tCon_Printf( \"^1SIZE OVERFLOW!!!^7\\n\" );\n\telse if( percentage > 95.0f )\n\t\tCon_Printf( \"^3SIZE DANGER!^7\\n\" );\n\telse if( percentage > 80.0f )\n\t\tCon_Printf( \"^2VERY FULL!^7\\n\" );\n\telse Con_Printf( \"\\n\" );\n\n\treturn itemstorage;\n}\n\n/*\n=============\nMod_PrintWorldStats_f\n\nDumps info about world\n=============\n*/\nvoid Mod_PrintWorldStats_f( void )\n{\n\tint\ti, totalmemory = 0;\n\tmodel_t\t*w = worldmodel;\n\n\tif( !w || !w->numsubmodels )\n\t{\n\t\tCon_Printf( \"No map loaded\\n\" );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"\\n\" );\n\tCon_Printf( \"Object names  Objects/Maxobjs  Memory / Maxmem  Fullness\\n\" );\n\tCon_Printf( \"------------  ---------------  ---------------  --------\\n\" );\n\n\tfor( i = 0; i < ARRAYSIZE( worldstats ); i++ )\n\t{\n\t\tmlumpstat_t *stat = &worldstats[i];\n\n\t\tif( !stat->lumpname || !stat->maxcount || !stat->count )\n\t\t\tcontinue; // unused or lump is empty\n\n\t\tif( stat->entrysize == sizeof( byte ))\n\t\t\ttotalmemory += Mod_GlobUsage( stat->lumpname, stat->count, stat->maxcount );\n\t\telse totalmemory += Mod_ArrayUsage( stat->lumpname, stat->count, stat->maxcount, stat->entrysize );\n\t}\n\n\tCon_Printf( \"=== Total BSP file data space used: %s ===\\n\", Q_memprint( totalmemory ));\n\tCon_Printf( \"World size ( %g %g %g ) units\\n\", world.size[0], world.size[1], world.size[2] );\n\tCon_Printf( \"Supports transparency world water: %s\\n\", FBitSet( world.flags, FWORLD_WATERALPHA ) ? \"Yes\" : \"No\" );\n\tCon_Printf( \"Lighting: %s\\n\", FBitSet( w->flags, MODEL_COLORED_LIGHTING ) ? \"colored\" : \"monochrome\" );\n\tCon_Printf( \"World total leafs: %d\\n\", worldmodel->numleafs + 1 );\n\tCon_Printf( \"original name: ^1%s\\n\", worldmodel->name );\n\tCon_Printf( \"internal name: ^2%s\\n\", world.message[0] ? world.message : \"none\" );\n\tCon_Printf( \"map compiler: ^3%s\\n\", world.compiler[0] ? world.compiler : \"unknown\" );\n\tCon_Printf( \"map editor: ^2%s\\n\", world.generator[0] ? world.generator : \"unknown\" );\n}\n\n/*\n===============================================================================\n\n\t\t\tCOMMON ROUTINES\n\n===============================================================================\n*/\n/*\n===================\nMod_DecompressPVS\n\nTODO: replace all Mod_DecompressPVS calls by this\n===================\n*/\nstatic void Mod_DecompressPVSTo( byte *const out, const byte *in, size_t visbytes )\n{\n\tbyte *dst = out;\n\n\tif( !in ) // no visinfo, make all visible\n\t{\n\t\tmemset( out, 0xFF, visbytes );\n\t\treturn;\n\t}\n\n\twhile( dst < out + visbytes )\n\t{\n\t\tif( *in ) // uncompressed\n\t\t{\n\t\t\t*dst++ = *in++;\n\t\t}\n\t\telse // zero repeated `c` times\n\t\t{\n\t\t\tsize_t c = in[1];\n\t\t\tif( c > out + visbytes - dst )\n\t\t\t\tc = out + visbytes - dst;\n\n\t\t\tmemset( dst, 0, c );\n\t\t\tin += 2;\n\t\t\tdst += c;\n\t\t}\n\t}\n}\n\n/*\n===================\nMod_DecompressPVS\n===================\n*/\nstatic byte *Mod_DecompressPVS( const byte *in, int visbytes )\n{\n\tMod_DecompressPVSTo( g_visdata, in, visbytes );\n\treturn g_visdata;\n}\n\nstatic size_t Mod_CompressPVS( byte *const out, const byte *in, size_t inbytes )\n{\n\tsize_t i;\n\tbyte *dst = out;\n\n\tfor( i = 0; i < inbytes; i++ )\n\t{\n\t\tsize_t j = i + 1, rep = 1;\n\n\t\t*dst++ = in[i];\n\n\t\t// only compress zeros\n\t\tif( in[i] )\n\t\t\tcontinue;\n\n\t\tfor( ; j < inbytes && rep != 255; j++, rep++ )\n\t\t{\n\t\t\tif( in[j] )\n\t\t\t\tbreak;\n\t\t}\n\n\t\t*dst++ = rep;\n\t\ti = j - 1;\n\t}\n\n\treturn dst - out;\n}\n\n/*\n==================\nMod_PointInLeaf\n\n==================\n*/\nmleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod )\n{\n\tAssert( node != NULL );\n\n\twhile( 1 )\n\t{\n\t\tif( node->contents < 0 )\n\t\t\treturn (mleaf_t *)node;\n\t\tnode = node_child( node, PlaneDiff( p, node->plane ) <= 0, mod );\n\t}\n\n\t// never reached\n\treturn NULL;\n}\n\n/*\n==================\nMod_GetPVSForPoint\n\nReturns PVS data for a given point\nNOTE: can return NULL\n==================\n*/\nbyte *Mod_GetPVSForPoint( const vec3_t p )\n{\n\tmleaf_t\t*leaf;\n\n\tASSERT( worldmodel != NULL );\n\n\tleaf = Mod_PointInLeaf( p, worldmodel->nodes, worldmodel );\n\n\tif( leaf && leaf->cluster >= 0 )\n\t\treturn Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );\n\treturn NULL;\n}\n\n/*\n==================\nMod_FatPVS_RecursiveBSPNode\n\n==================\n*/\nstatic void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node, qboolean phs )\n{\n\twhile( node->contents >= 0 )\n\t{\n\t\tfloat d = PlaneDiff( org, node->plane );\n\n\t\tif( d > radius )\n\t\t\tnode = node_child( node, 0, worldmodel );\n\t\telse if( d < -radius )\n\t\t\tnode = node_child( node, 1, worldmodel );\n\t\telse\n\t\t{\n\t\t\t// go down both sides\n\t\t\tMod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node_child( node, 0, worldmodel ), phs );\n\t\t\tnode = node_child( node, 1, worldmodel );\n\t\t}\n\t}\n\n\t// if this leaf is in a cluster, accumulate the vis bits\n\tif(((mleaf_t *)node)->cluster >= 0 )\n\t{\n\t\tbyte *vis;\n\n\t\tif( phs )\n\t\t{\n\t\t\tint i = ((mleaf_t *)node)->cluster + 1;\n\t\t\tvis = Mod_DecompressPVS( &world.compressed_phs[world.phsofs[i]], world.visbytes );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes );\n\t\t}\n\n\t\tQ_memor( visbuffer, vis, visbytes );\n\t}\n}\n\n/*\n==================\nMod_FatPVS_RecursiveBSPNode\n\nCalculates a PVS that is the inclusive or of all leafs\nwithin radius pixels of the given point.\n==================\n*/\nint Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean phs )\n{\n\tint\tbytes = world.visbytes;\n\tmleaf_t\t*leaf = NULL;\n\n\tASSERT( worldmodel != NULL );\n\n\tleaf = Mod_PointInLeaf( org, worldmodel->nodes, worldmodel );\n\tbytes = Q_min( bytes, visbytes );\n\n\t// enable full visibility for some reasons\n\tif( fullvis || !worldmodel->visdata || !leaf || leaf->cluster < 0 )\n\t{\n\t\tmemset( visbuffer, 0xFF, bytes );\n\t\treturn bytes;\n\t}\n\n\t// requested PHS but we don't have PHS for some reason\n\t// enable full visibility\n\tif( phs && !( world.compressed_phs && world.phsofs ))\n\t{\n\t\tmemset( visbuffer, 0xFF, bytes );\n\t\treturn bytes;\n\t}\n\n\tif( !merge ) memset( visbuffer, 0x00, bytes );\n\n\tMod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes, phs );\n\n\treturn bytes;\n}\n\n/*\n======================================================================\n\nLEAF LISTING\n\n======================================================================\n*/\nstatic void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node )\n{\n\tint\tsides;\n\n\twhile( 1 )\n\t{\n\t\tif( node->contents == CONTENTS_SOLID )\n\t\t\treturn;\n\n\t\tif( node->contents < 0 )\n\t\t{\n\t\t\tmleaf_t\t*leaf = (mleaf_t *)node;\n\n\t\t\t// it's a leaf!\n\t\t\tif( ll->count >= ll->maxcount )\n\t\t\t{\n\t\t\t\tll->overflowed = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tll->list[ll->count++] = leaf->cluster;\n\t\t\treturn;\n\t\t}\n\n\t\tsides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane );\n\n\t\tif( sides == 1 )\n\t\t\tnode = node_child( node, 0, worldmodel );\n\t\telse if( sides == 2 )\n\t\t\tnode = node_child( node, 1, worldmodel );\n\t\telse\n\t\t{\n\t\t\t// go down both\n\t\t\tif( ll->topnode == -1 )\n\t\t\t\tll->topnode = node - worldmodel->nodes;\n\t\t\tMod_BoxLeafnums_r( ll, node_child( node, 0, worldmodel ));\n\t\t\tnode = node_child( node, 1, worldmodel );\n\t\t}\n\t}\n}\n\n/*\n==================\nMod_BoxLeafnums\n==================\n*/\nstatic int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *topnode )\n{\n\tleaflist_t\tll;\n\n\tif( !worldmodel ) return 0;\n\n\tVectorCopy( mins, ll.mins );\n\tVectorCopy( maxs, ll.maxs );\n\n\tll.maxcount = listsize;\n\tll.overflowed = false;\n\tll.topnode = -1;\n\tll.list = list;\n\tll.count = 0;\n\n\tMod_BoxLeafnums_r( &ll, worldmodel->nodes );\n\n\tif( topnode ) *topnode = ll.topnode;\n\treturn ll.count;\n}\n\n/*\n=============\nMod_BoxVisible\n\nReturns true if any leaf in boxspace\nis potentially visible\n=============\n*/\nqboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits )\n{\n\tint\tleafList[MAX_BOX_LEAFS];\n\tint\ti, count;\n\n\tif( !visbits || !mins || !maxs )\n\t\treturn true;\n\n\tcount = Mod_BoxLeafnums( mins, maxs, leafList, MAX_BOX_LEAFS, NULL );\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tif( CHECKVISBIT( visbits, leafList[i] ))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n=================\nMod_FindModelOrigin\n\nroutine to detect bmodels with origin-brush\n=================\n*/\nstatic void Mod_FindModelOrigin( const char *entities, const char *modelname, vec3_t origin )\n{\n\tchar\t*pfile;\n\tstring\tkeyname;\n\tchar\ttoken[2048];\n\tqboolean\tmodel_found;\n\tqboolean\torigin_found;\n\n\tif( !entities || !COM_CheckString( modelname ))\n\t\treturn;\n\n\tif( !origin || !VectorIsNull( origin ))\n\t\treturn;\n\n\tpfile = (char *)entities;\n\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tif( token[0] != '{' )\n\t\t\tHost_Error( \"%s: found %s when expecting {\\n\", __func__, token );\n\n\t\tmodel_found = origin_found = false;\n\t\tVectorClear( origin );\n\n\t\twhile( 1 )\n\t\t{\n\t\t\t// parse key\n\t\t\tif(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )\n\t\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\t\t\tif( token[0] == '}' ) break; // end of desc\n\n\t\t\tQ_strncpy( keyname, token, sizeof( keyname ));\n\n\t\t\t// parse value\n\t\t\tif(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )\n\t\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\n\t\t\tif( token[0] == '}' )\n\t\t\t\tHost_Error( \"%s: closing brace without data\\n\", __func__ );\n\n\t\t\tif( !Q_stricmp( keyname, \"model\" ) && !Q_stricmp( modelname, token ))\n\t\t\t\tmodel_found = true;\n\n\t\t\tif( !Q_stricmp( keyname, \"origin\" ))\n\t\t\t{\n\t\t\t\tQ_atov( origin, token, 3 );\n\t\t\t\torigin_found = true;\n\t\t\t}\n\t\t}\n\n\t\tif( model_found ) break;\n\t}\n}\n\n/*\n==================\nMod_CheckWaterAlphaSupport\n\nconverted maps potential may don't\nsupport water transparency\n==================\n*/\nstatic qboolean Mod_CheckWaterAlphaSupport( model_t *mod, dbspmodel_t *bmod )\n{\n\tmleaf_t\t\t*leaf;\n\tint\t\ti, j;\n\tconst byte\t*pvs;\n\n\tif( bmod->visdatasize <= 0 )\n\t\treturn true;\n\n\t// check all liquid leafs to see if they can see into empty leafs, if any\n\t// can we can assume this map supports r_wateralpha\n\tfor( i = 0, leaf = mod->leafs; i < mod->numleafs; i++, leaf++ )\n\t{\n\t\tif(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 )\n\t\t{\n\t\t\tpvs = Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );\n\n\t\t\tfor( j = 0; j < mod->numleafs; j++ )\n\t\t\t{\n\t\t\t\tif( CHECKVISBIT( pvs, mod->leafs[j].cluster ) && mod->leafs[j].contents == CONTENTS_EMPTY )\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n==================\nMod_SampleSizeForFace\n\nreturn the current lightmap resolution per face\n==================\n*/\nint Mod_SampleSizeForFace( const msurface_t *surf )\n{\n\tif( !surf || !surf->texinfo )\n\t\treturn LM_SAMPLE_SIZE;\n\n\t// world luxels has more priority\n\tif( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS ))\n\t\treturn 1;\n\n\tif( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))\n\t\treturn LM_SAMPLE_EXTRASIZE;\n\n\tif( surf->texinfo->faceinfo )\n\t\treturn surf->texinfo->faceinfo->texture_step;\n\n\treturn LM_SAMPLE_SIZE;\n}\n\n/*\n==================\nMod_GetFaceContents\n\ndetermine face contents by name\n==================\n*/\nstatic int Mod_GetFaceContents( const char *name )\n{\n\tif( !Q_strnicmp( name, \"SKY\", 3 ))\n\t\treturn CONTENTS_SKY;\n\n\tif( name[0] == '!' || name[0] == '*' )\n\t{\n\t\tif( !Q_strnicmp( name + 1, \"lava\", 4 ))\n\t\t\treturn CONTENTS_LAVA;\n\t\telse if( !Q_strnicmp( name + 1, \"slime\", 5 ))\n\t\t\treturn CONTENTS_SLIME;\n\t\treturn CONTENTS_WATER; // otherwise it's water\n\t}\n\n\tif( !Q_strnicmp( name, \"water\", 5 ))\n\t\treturn CONTENTS_WATER;\n\n\treturn CONTENTS_SOLID;\n}\n\n/*\n==================\nMod_GetFaceContents\n\ndetermine face contents by name\n==================\n*/\nstatic mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge, const dbspmodel_t *bmod )\n{\n\tint\tlindex = mod->surfedges[surfedge];\n\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tif( lindex > 0 )\n\t\t{\n\t\t\tmedge32_t *edge = &mod->edges32[lindex];\n\t\t\treturn &mod->vertexes[edge->v[0]];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmedge32_t *edge = &mod->edges32[-lindex];\n\t\t\treturn &mod->vertexes[edge->v[1]];\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( lindex > 0 )\n\t\t{\n\t\t\tmedge16_t *edge = &mod->edges16[lindex];\n\t\t\treturn &mod->vertexes[edge->v[0]];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmedge16_t *edge = &mod->edges16[-lindex];\n\t\t\treturn &mod->vertexes[edge->v[1]];\n\t\t}\n\t}\n}\n\n/*\n==================\nMod_MakeNormalAxial\n\nremove jitter from near-axial normals\n==================\n*/\nstatic void Mod_MakeNormalAxial( vec3_t normal )\n{\n\tint\ti, type;\n\n\tfor( type = 0; type < 3; type++ )\n\t{\n\t\tif( fabs( normal[type] ) > 0.9999f )\n\t\t\tbreak;\n\t}\n\n\t// make positive and pure axial\n\tfor( i = 0; i < 3 && type != 3; i++ )\n\t{\n\t\tif( i == type )\n\t\t\tnormal[i] = 1.0f;\n\t\telse normal[i] = 0.0f;\n\t}\n}\n\n/*\n==================\nMod_LightMatrixFromTexMatrix\n\ncompute lightmap matrix based on texture matrix\n==================\n*/\nstatic void Mod_LightMatrixFromTexMatrix( const mtexinfo_t *tx, float lmvecs[2][4] )\n{\n\tfloat\tlmscale = LM_SAMPLE_SIZE;\n\tint\ti, j;\n\n\t// this is can't be possible but who knews\n\tif( FBitSet( tx->flags, TEX_EXTRA_LIGHTMAP ))\n\t\tlmscale = LM_SAMPLE_EXTRASIZE;\n\n\tif( tx->faceinfo )\n\t\tlmscale = tx->faceinfo->texture_step;\n\n\t// copy texmatrix into lightmap matrix fisrt\n\tfor( i = 0; i < 2; i++ )\n\t{\n\t\tfor( j = 0; j < 4; j++ )\n\t\t{\n\t\t\tlmvecs[i][j] = tx->vecs[i][j];\n\t\t}\n\t}\n\n\tif( !FBitSet( tx->flags, TEX_WORLD_LUXELS ))\n\t\treturn; // just use texmatrix\n\n\tVectorNormalize( lmvecs[0] );\n\tVectorNormalize( lmvecs[1] );\n\n\tif( FBitSet( tx->flags, TEX_AXIAL_LUXELS ))\n\t{\n\t\tMod_MakeNormalAxial( lmvecs[0] );\n\t\tMod_MakeNormalAxial( lmvecs[1] );\n\t}\n\n\t// put the lighting origin at center the of poly\n\tVectorScale( lmvecs[0], (1.0f / lmscale), lmvecs[0] );\n\tVectorScale( lmvecs[1], -(1.0f / lmscale), lmvecs[1] );\n\n\tlmvecs[0][3] = lmscale * 0.5f;\n\tlmvecs[1][3] = -lmscale * 0.5f;\n}\n\n/*\n=================\nMod_CalcSurfaceExtents\n\nFills in surf->texturemins[] and surf->extents[]\n=================\n*/\nstatic void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )\n{\n\t// this place is VERY critical to precision\n\t// keep it as float, don't use double, because it causes issues with lightmap\n\tfloat\t\tmins[2], maxs[2], val;\n\tfloat\t\tlmmins[2], lmmaxs[2];\n\tint\t\tbmins[2], bmaxs[2];\n\tint\t\ti, j, e, sample_size;\n\tmextrasurf_t\t*info = surf->info;\n\tmtexinfo_t\t*tex;\n\tmvertex_t\t\t*v;\n\n\tsample_size = Mod_SampleSizeForFace( surf );\n\ttex = surf->texinfo;\n\n\tMod_LightMatrixFromTexMatrix( tex, info->lmvecs );\n\n\tmins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999;\n\tmaxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[1] =-999999;\n\n\tfor( i = 0; i < surf->numedges; i++ )\n\t{\n\t\te = mod->surfedges[surf->firstedge + i];\n\n\t\tif( e >= mod->numedges || e <= -mod->numedges )\n\t\t\tHost_Error( \"%s: bad edge\\n\", __func__ );\n\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t{\n\t\t\tif( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]];\n\t\t\telse v = &mod->vertexes[mod->edges32[-e].v[1]];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]];\n\t\t\telse v = &mod->vertexes[mod->edges16[-e].v[1]];\n\t\t}\n\n\t\tfor( j = 0; j < 2; j++ )\n\t\t{\n\t\t\tval = DotProductPrecise( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3];\n\t\t\tmins[j] = Q_min( val, mins[j] );\n\t\t\tmaxs[j] = Q_max( val, maxs[j] );\n\t\t}\n\n\t\tfor( j = 0; j < 2; j++ )\n\t\t{\n\t\t\tval = DotProductPrecise( v->position, info->lmvecs[j] ) + info->lmvecs[j][3];\n\t\t\tlmmins[j] = Q_min( val, lmmins[j] );\n\t\t\tlmmaxs[j] = Q_max( val, lmmaxs[j] );\n\t\t}\n\t}\n\n\tfor( i = 0; i < 2; i++ )\n\t{\n\t\tbmins[i] = floor( mins[i] / sample_size );\n\t\tbmaxs[i] = ceil( maxs[i] / sample_size );\n\n\t\tsurf->texturemins[i] = bmins[i] * sample_size;\n\t\tsurf->extents[i] = (bmaxs[i] - bmins[i]) * sample_size;\n\n\t\tif( FBitSet( tex->flags, TEX_WORLD_LUXELS ))\n\t\t{\n\t\t\tlmmins[i] = floor( lmmins[i] );\n\t\t\tlmmaxs[i] = ceil( lmmaxs[i] );\n\n\t\t\tinfo->lightmapmins[i] = lmmins[i];\n\t\t\tinfo->lightextents[i] = (lmmaxs[i] - lmmins[i]);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// just copy texturemins\n\t\t\tinfo->lightmapmins[i] = surf->texturemins[i];\n\t\t\tinfo->lightextents[i] = surf->extents[i];\n\t\t}\n\n#if !XASH_DEDICATED && 0 // REFTODO:\n\t\tif( !FBitSet( tex->flags, TEX_SPECIAL ) && ( surf->extents[i] > 16384 ) && ( tr.block_size == BLOCK_SIZE_DEFAULT ))\n\t\t\tCon_Reportf( S_ERROR \"Bad surface extents %i\\n\", surf->extents[i] );\n#endif // XASH_DEDICATED\n\t}\n}\n\n/*\n=================\nMod_CalcSurfaceBounds\n\nfills in surf->mins and surf->maxs\n=================\n*/\nstatic void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )\n{\n\tint\ti, e;\n\tmvertex_t\t*v;\n\n\tClearBounds( surf->info->mins, surf->info->maxs );\n\n\tfor( i = 0; i < surf->numedges; i++ )\n\t{\n\t\te = mod->surfedges[surf->firstedge + i];\n\n\t\tif( e >= mod->numedges || e <= -mod->numedges )\n\t\t\tHost_Error( \"%s: bad edge\\n\", __func__ );\n\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t{\n\t\t\tif( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]];\n\t\t\telse v = &mod->vertexes[mod->edges32[-e].v[1]];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]];\n\t\t\telse v = &mod->vertexes[mod->edges16[-e].v[1]];\n\t\t}\n\t\tAddPointToBounds( v->position, surf->info->mins, surf->info->maxs );\n\t}\n\n\tVectorAverage( surf->info->mins, surf->info->maxs, surf->info->origin );\n}\n\n/*\n=================\nMod_CreateFaceBevels\n=================\n*/\nstatic void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )\n{\n\tvec3_t\t\tdelta, edgevec;\n\tbyte\t\t*facebevel;\n\tvec3_t\t\tfaceNormal;\n\tmvertex_t\t\t*v0, *v1;\n\tint\t\tcontents;\n\tint\t\ti, size;\n\tvec_t\t\tradius;\n\tmfacebevel_t\t*fb;\n\n\tif( surf->texinfo && surf->texinfo->texture )\n\t\tcontents = Mod_GetFaceContents( surf->texinfo->texture->name );\n\telse contents = CONTENTS_SOLID;\n\n\tsize = sizeof( mfacebevel_t ) + surf->numedges * sizeof( mplane_t );\n\tfacebevel = (byte *)Mem_Calloc( mod->mempool, size );\n\tfb = (mfacebevel_t *)facebevel;\n\tfacebevel += sizeof( mfacebevel_t );\n\tfb->edges = (mplane_t *)facebevel;\n\tfb->numedges = surf->numedges;\n\tfb->contents = contents;\n\tsurf->info->bevel = fb;\n\n\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\tVectorNegate( surf->plane->normal, faceNormal );\n\telse VectorCopy( surf->plane->normal, faceNormal );\n\n\t// compute face origin and plane edges\n\tfor( i = 0; i < surf->numedges; i++ )\n\t{\n\t\tmplane_t\t*dest = &fb->edges[i];\n\n\t\tv0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod );\n\t\tv1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges, bmod );\n\t\tVectorSubtract( v1->position, v0->position, edgevec );\n\t\tCrossProduct( faceNormal, edgevec, dest->normal );\n\t\tVectorNormalize( dest->normal );\n\t\tdest->dist = DotProduct( dest->normal, v0->position );\n\t\tdest->type = PlaneTypeForNormal( dest->normal );\n\t\tVectorAdd( fb->origin, v0->position, fb->origin );\n\t}\n\n\tVectorScale( fb->origin, 1.0f / surf->numedges, fb->origin );\n\n\t// compute face radius\n\tfor( i = 0; i < surf->numedges; i++ )\n\t{\n\t\tv0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod );\n\t\tVectorSubtract( v0->position, fb->origin, delta );\n\t\tradius = DotProduct( delta, delta );\n\t\tfb->radius = Q_max( radius, fb->radius );\n\t}\n}\n\n/*\n=================\nMod_SetParent\n=================\n*/\nstatic void Mod_SetParent( model_t *mod, mnode_t *node, mnode_t *parent )\n{\n\tnode->parent = parent;\n\n\tif( node->contents < 0 )\n\t\treturn; // it's leaf\n\n\tMod_SetParent( mod, node_child( node, 0, mod ), node );\n\tMod_SetParent( mod, node_child( node, 1, mod ), node );\n}\n\n/*\n==================\nCountClipNodes_r\n==================\n*/\nstatic void CountClipNodes16_r( mclipnode16_t *src, hull_t *hull, int nodenum )\n{\n\t// leaf?\n\tif( nodenum < 0 ) return;\n\n\tif( hull->lastclipnode == MAX_MAP_CLIPNODES_HLBSP )\n\t\tHost_Error( \"%s: MAX_MAP_CLIPNODES_HLBSP limit exceeded\\n\", __func__ );\n\thull->lastclipnode++;\n\n\tCountClipNodes16_r( src, hull, src[nodenum].children[0] );\n\tCountClipNodes16_r( src, hull, src[nodenum].children[1] );\n}\n\nstatic void CountClipNodes32_r( mclipnode32_t *src, hull_t *hull, int nodenum )\n{\n\t// leaf?\n\tif( nodenum < 0 ) return;\n\n\tif( hull->lastclipnode == MAX_MAP_CLIPNODES_BSP2 )\n\t\tHost_Error( \"%s: MAX_MAP_CLIPNODES_BSP2 limit exceeded\\n\", __func__ );\n\thull->lastclipnode++;\n\n\tCountClipNodes32_r( src, hull, src[nodenum].children[0] );\n\tCountClipNodes32_r( src, hull, src[nodenum].children[1] );\n}\n\nstatic void CountDClipNodes_r( dclipnode32_t *src, hull_t *hull, int nodenum, const int max_clipnodes )\n{\n\t// leaf?\n\tif( nodenum < 0 ) return;\n\n\tif( hull->lastclipnode == max_clipnodes )\n\t\tHost_Error( \"%s: MAX_MAP_CLIPNODES (%d) limit exceeded\\n\", __func__, max_clipnodes );\n\thull->lastclipnode++;\n\n\tCountDClipNodes_r( src, hull, src[nodenum].children[0], max_clipnodes );\n\tCountDClipNodes_r( src, hull, src[nodenum].children[1], max_clipnodes );\n}\n\n/*\n==================\nRemapClipNodes_r\n==================\n*/\nstatic int RemapClipNodes_r( dbspmodel_t *bmod, dclipnode32_t *srcnodes, hull_t *hull, int nodenum )\n{\n\tdclipnode32_t *src;\n\tint\t\ti, c;\n\n\t// leaf?\n\tif( nodenum < 0 )\n\t\treturn nodenum;\n\n\t// emit a clipnode\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tif( hull->lastclipnode == MAX_MAP_CLIPNODES_BSP2 )\n\t\t\tHost_Error( \"%s: MAX_MAP_CLIPNODES_BSP2 limit exceeded\\n\", __func__ );\n\t}\n\telse\n\t{\n\t\tif( hull->lastclipnode == MAX_MAP_CLIPNODES_HLBSP )\n\t\t\tHost_Error( \"%s: MAX_MAP_CLIPNODES_HLBSP limit exceeded\\n\", __func__ );\n\t}\n\n\tsrc = srcnodes + nodenum;\n\n\tc = hull->lastclipnode;\n\thull->lastclipnode++;\n\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tmclipnode32_t *out = &hull->clipnodes32[c];\n\t\tout->planenum = src->planenum;\n\t\tfor( i = 0; i < 2; i++ )\n\t\t\tout->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] );\n\t}\n\telse\n\t{\n\t\tmclipnode16_t *out = &hull->clipnodes16[c];\n\t\tout->planenum = src->planenum;\n\t\tfor( i = 0; i < 2; i++ )\n\t\t\tout->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] );\n\t}\n\n\treturn c;\n}\n\n/*\n=================\nMod_MakeHull0\n\nDuplicate the drawing hull structure as a clipping hull\n=================\n*/\nstatic void Mod_MakeHull0( model_t *mod, const dbspmodel_t *bmod )\n{\n\thull_t *hull = &mod->hulls[0];\n\tint i;\n\n\thull->firstclipnode = 0;\n\thull->lastclipnode = mod->numnodes - 1;\n\thull->planes = mod->planes;\n\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tmclipnode32_t *out;\n\t\tmnode_t *in = mod->nodes;\n\n\t\thull->clipnodes32 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes32 ));\n\n\t\tfor( i = 0; i < mod->numnodes; i++, out++, in++ )\n\t\t{\n\t\t\tint j;\n\n\t\t\tout->planenum = in->plane - mod->planes;\n\n\t\t\tfor( j = 0; j < 2; j++ )\n\t\t\t{\n\t\t\t\tmnode_t *child = node_child( in, j, mod );\n\n\t\t\t\tif( child->contents < 0 )\n\t\t\t\t\tout->children[j] = child->contents;\n\t\t\t\telse\n\t\t\t\t\tout->children[j] = child - mod->nodes;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tmclipnode16_t *out;\n\t\tmnode_t *in = mod->nodes;\n\n\t\thull->clipnodes16 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes16 ));\n\n\t\tfor( i = 0; i < mod->numnodes; i++, out++, in++ )\n\t\t{\n\t\t\tint j;\n\n\t\t\tout->planenum = in->plane - mod->planes;\n\n\t\t\tfor( j = 0; j < 2; j++ )\n\t\t\t{\n\t\t\t\tmnode_t *child = node_child( in, j, mod );\n\n\t\t\t\tif( child->contents < 0 )\n\t\t\t\t\tout->children[j] = child->contents;\n\t\t\t\telse\n\t\t\t\t\tout->children[j] = child - mod->nodes;\n\t\t\t}\n\t\t}\n\t}\n\n}\n\n/*\n=================\nMod_SetupHull\n=================\n*/\nstatic void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, int headnode, int hullnum, model_t *world )\n{\n\thull_t *hull = &mod->hulls[hullnum];\n\n\tswitch( hullnum )\n\t{\n\tcase 1:\n\t\tVectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull\n\t\tVectorCopy( host.player_maxs[0], hull->clip_maxs );\n\t\tbreak;\n\tcase 2:\n\t\tVectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull\n\t\tVectorCopy( host.player_maxs[3], hull->clip_maxs );\n\t\tbreak;\n\tcase 3:\n\t\tVectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull\n\t\tVectorCopy( host.player_maxs[1], hull->clip_maxs );\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: bad hull number %i\\n\", __func__, hullnum );\n\t\tbreak;\n\t}\n\n\tif( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs ))\n\t\treturn;\t// no hull specified\n\n\t// assume no hull\n\thull->firstclipnode = hull->lastclipnode = 0;\n\thull->planes = NULL; // hull is missed\n\n\tif( headnode >= mod->numclipnodes )\n\t\treturn;\t// ZHLT weird empty hulls\n\n\t// bsp30ext allows for extended total amount of clipnodes, but the limit is still 16-bit per submodel\n\t// therefore we need to remap them\n\t// take a simpler route if we don't need clipnodes remapping\n\tif( !bmod->isbsp30ext )\n\t{\n\t\thull->planes = mod->planes;\n\n\t\t// some map \"optimizers\" (you know who you are!) put -1 here\n\t\t// ... and it's purposefully? encode CONTENTS_EMPTY sometimes\n\t\t// but might cause out of bounds reads\n\t\thull->firstclipnode = headnode;\n\t\thull->lastclipnode = mod->numclipnodes - 1;\n\n\t\t// only allocate clipnodes array for the base model, only for first hull\n\t\tif( mod == world && hullnum == 1 )\n\t\t{\n\t\t\tint i;\n\n\t\t\tif( bmod->version == QBSP2_VERSION )\n\t\t\t{\n\t\t\t\thull->clipnodes32 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes32 ) * mod->numclipnodes );\n\n\t\t\t\tfor( i = 0; i < mod->numclipnodes; i++ )\n\t\t\t\t{\n\t\t\t\t\thull->clipnodes32[i].planenum = bmod->clipnodes_out[i].planenum;\n\t\t\t\t\thull->clipnodes32[i].children[0] = bmod->clipnodes_out[i].children[0];\n\t\t\t\t\thull->clipnodes32[i].children[1] = bmod->clipnodes_out[i].children[1];\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\thull->clipnodes16 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes16 ) * mod->numclipnodes );\n\n\t\t\t\tfor( i = 0; i < mod->numclipnodes; i++ )\n\t\t\t\t{\n\t\t\t\t\thull->clipnodes16[i].planenum = bmod->clipnodes_out[i].planenum;\n\t\t\t\t\thull->clipnodes16[i].children[0] = bmod->clipnodes_out[i].children[0];\n\t\t\t\t\thull->clipnodes16[i].children[1] = bmod->clipnodes_out[i].children[1];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( bmod->version == QBSP2_VERSION )\n\t\t\t\thull->clipnodes32 = world->hulls[1].clipnodes32;\n\t\t\telse\n\t\t\t\thull->clipnodes16 = world->hulls[1].clipnodes16;\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 ))\n\t\treturn; // hull missed\n\n\t// fit array to real count\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tCountDClipNodes_r( bmod->clipnodes_out, hull, headnode, MAX_MAP_CLIPNODES_BSP2 );\n\t\thull->clipnodes32 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes32 ) * hull->lastclipnode );\n\t}\n\telse\n\t{\n\t\tCountDClipNodes_r( bmod->clipnodes_out, hull, headnode, MAX_MAP_CLIPNODES_HLBSP );\n\t\thull->clipnodes16 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes16 ) * hull->lastclipnode );\n\t}\n\n\thull->planes = mod->planes; // share planes\n\thull->lastclipnode = 0; // restart counting\n\n\tRemapClipNodes_r( bmod, bmod->clipnodes_out, hull, headnode ); // remap clipnodes to 16-bit indexes\n}\n\nstatic qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_size, color24 **out, size_t *outsize )\n{\n\tchar        modelname[64], path[64];\n\tint         iCompare;\n\tfs_offset_t datasize;\n\tfile_t      *f;\n\tuint        hdr[2];\n\n\tCOM_FileBase( mod->name, modelname, sizeof( modelname ));\n\tQ_snprintf( path, sizeof( path ), \"maps/%s.%s\", modelname, ext );\n\n\tif( !pfnCompareFileTime( path, mod->name, &iCompare ))\n\t\treturn false;\n\n\tif( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg\n\t\tCon_Printf( S_WARN \"%s probably is out of date\\n\", path );\n\n\tf = FS_Open( path, \"rb\", false );\n\n\tif( !f )\n\t{\n\t\tCon_Printf( S_ERROR \"couldn't load %s\\n\", path );\n\t\treturn false;\n\t}\n\n\tdatasize = FS_FileLength( f );\n\n\t// skip header bytes\n\tdatasize -= 8;\n\n\tif( datasize != expected_size )\n\t{\n\t\tCon_Printf( S_ERROR \"%s has mismatched size (%li should be %zu)\\n\", path, (long)datasize, expected_size );\n\t\tgoto cleanup_and_error;\n\t}\n\n\tif( FS_Read( f, hdr, sizeof( hdr )) != sizeof( hdr ))\n\t{\n\t\tCon_Printf( S_ERROR \"failed reading header from %s\\n\", path );\n\t\tgoto cleanup_and_error;\n\t}\n\n\tif( LittleLong( hdr[0] ) != IDDELUXEMAPHEADER )\n\t{\n\t\tCon_Printf( S_ERROR \"%s is corrupted\\n\", path );\n\t\tgoto cleanup_and_error;\n\t}\n\n\tif( LittleLong( hdr[1] ) != DELUXEMAP_VERSION )\n\t{\n\t\tCon_Printf( S_ERROR \"has %s mismatched version (%u should be %u)\\n\", path, LittleLong( hdr[1] ), DELUXEMAP_VERSION );\n\t\tgoto cleanup_and_error;\n\t}\n\n\t*out = Mem_Malloc( mod->mempool, datasize );\n\t*outsize = datasize;\n\n\tFS_Read( f, *out, datasize );\n\tFS_Close( f );\n\treturn true;\n\ncleanup_and_error:\n\tFS_Close( f );\n\treturn false;\n}\n\n/*\n=================\nMod_SetupSubmodels\n\nduplicate the basic information\nfor embedded submodels\n=================\n*/\nstatic void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )\n{\n\tconst qboolean colored = FBitSet( mod->flags, MODEL_COLORED_LIGHTING ) ? true : false;\n\tconst qboolean qbsp2 = FBitSet( mod->flags, MODEL_QBSP2 ) ? true : false;\n\tconst char *name = mod->name;\n\tmodel_t *world = mod; // submodels might want to share hulls\n\tint\ti;\n\n\tmod->numframes = 2;\t// regular and alternate animation\n\n\t// set up the submodels\n\tfor( i = 0; i < mod->numsubmodels; i++ )\n\t{\n\t\tdmodel_t *bm = &mod->submodels[i];\n\t\tint j;\n\n\t\t// hull 0 is just shared across all bmodels\n\t\tmod->hulls[0].firstclipnode = bm->headnode[0];\n\t\tmod->hulls[0].lastclipnode = bm->headnode[0]; // need to be real count\n\n\t\t// counting a real number of clipnodes per each submodel\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t\tCountClipNodes32_r( mod->hulls[0].clipnodes32, &mod->hulls[0], bm->headnode[0] );\n\t\telse\n\t\t\tCountClipNodes16_r( mod->hulls[0].clipnodes16, &mod->hulls[0], bm->headnode[0] );\n\n\t\t// but hulls1-3 is build individually for a each given submodel\n\t\tfor( j = 1; j < MAX_MAP_HULLS; j++ )\n\t\t\tMod_SetupHull( bmod, mod, bm->headnode[j], j, world );\n\n\t\tmod->firstmodelsurface = bm->firstface;\n\t\tmod->nummodelsurfaces = bm->numfaces;\n\n\t\tVectorCopy( bm->mins, mod->mins );\n\t\tVectorCopy( bm->maxs, mod->maxs );\n\n\t\tmod->radius = RadiusFromBounds( mod->mins, mod->maxs );\n\t\tmod->numleafs = bm->visleafs;\n\t\tmod->flags = 0;\n\n\t\t// this bit will be shared between all the submodels include worldmodel\n\t\tif( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING );\n\t\tif( qbsp2 ) SetBits( mod->flags, MODEL_QBSP2 );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\tchar temp[MAX_VA_STRING];\n\n\t\t\tQ_snprintf( temp, sizeof( temp ), \"*%i\", i );\n\t\t\tMod_FindModelOrigin( world->entities, temp, bm->origin );\n\n\t\t\t// mark models that have origin brushes\n\t\t\tif( !VectorIsNull( bm->origin ))\n\t\t\t\tSetBits( mod->flags, MODEL_HAS_ORIGIN );\n#ifdef HACKS_RELATED_HLMODS\n\t\t\t// c2a1 doesn't have origin brush it's just placed at center of the level\n\t\t\tif( !Q_stricmp( name, \"maps/c2a1.bsp\" ) && ( i == 11 ))\n\t\t\t\tSetBits( mod->flags, MODEL_HAS_ORIGIN );\n#endif\n\t\t}\n\n\t\t// sets the model flags\n\t\tfor( j = 0; i != 0 && j < mod->nummodelsurfaces; j++ )\n\t\t{\n\t\t\tmsurface_t *surf = mod->surfaces + mod->firstmodelsurface + j;\n\n\t\t\tif( FBitSet( surf->flags, SURF_CONVEYOR ))\n\t\t\t\tSetBits( mod->flags, MODEL_CONVEYOR );\n\n\t\t\tif( FBitSet( surf->flags, SURF_TRANSPARENT ))\n\t\t\t\tSetBits( mod->flags, MODEL_TRANSPARENT );\n\n\t\t\tif( FBitSet( surf->flags, SURF_DRAWTURB ))\n\t\t\t\tSetBits( mod->flags, MODEL_LIQUID );\n\t\t}\n\n\t\tif( i < mod->numsubmodels - 1 )\n\t\t{\n\t\t\tchar\tname[8];\n\t\t\tmodel_t *submod;\n\n\t\t\t// duplicate the basic information\n\t\t\tQ_snprintf( name, sizeof( name ), \"*%i\", i + 1 );\n\t\t\tsubmod = Mod_FindName( name, true );\n\t\t\t*submod = *mod;\n\t\t\tQ_strncpy( submod->name, name, sizeof( submod->name ));\n\t\t\tsubmod->mempool = 0;\n\t\t\tmod = submod;\n\t\t}\n\t}\n\n\tif( bmod->clipnodes_out != NULL )\n\t\tMem_Free( bmod->clipnodes_out );\n}\n\n/*\n===============================================================================\n\n\t\t\tMAP LOADING\n\n===============================================================================\n*/\n/*\n=================\nMod_LoadSubmodels\n=================\n*/\nstatic void Mod_LoadSubmodels( model_t *mod, dbspmodel_t *bmod )\n{\n\tdmodel_t\t*in, *out;\n\tint\toldmaxfaces;\n\tint\ti, j;\n\n\t// allocate extradata for each dmodel_t\n\tout = Mem_Malloc( mod->mempool, bmod->numsubmodels * sizeof( *out ));\n\n\tmod->numsubmodels = bmod->numsubmodels;\n\tmod->submodels = out;\n\tin = bmod->submodels;\n\n\tif( bmod->isworld )\n\t\trefState.max_surfaces = 0;\n\toldmaxfaces = refState.max_surfaces;\n\n\tfor( i = 0; i < bmod->numsubmodels; i++, in++, out++ )\n\t{\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\t// reset empty bounds to prevent error\n\t\t\tif( in->mins[j] == 999999.0f )\n\t\t\t\tin->mins[j] = 0.0f;\n\t\t\tif( in->maxs[j] == -999999.0f)\n\t\t\t\tin->maxs[j] = 0.0f;\n\n\t\t\t// spread the mins / maxs by a unit\n\t\t\tout->mins[j] = in->mins[j] - 1.0f;\n\t\t\tout->maxs[j] = in->maxs[j] + 1.0f;\n\t\t\tout->origin[j] = in->origin[j];\n\t\t}\n\n\t\tfor( j = 0; j < MAX_MAP_HULLS; j++ )\n\t\t\tout->headnode[j] = in->headnode[j];\n\n\t\tout->visleafs = in->visleafs;\n\t\tout->firstface = in->firstface;\n\t\tout->numfaces = in->numfaces;\n\n\t\tif( i == 0 && bmod->isworld )\n\t\t\tcontinue; // skip the world to save mem\n\t\toldmaxfaces = Q_max( oldmaxfaces, out->numfaces );\n\t}\n\n\t// these array used to sort translucent faces in bmodels\n\tif( oldmaxfaces > refState.max_surfaces )\n\t{\n\t\trefState.draw_surfaces = (sortedface_t *)Z_Realloc( refState.draw_surfaces, oldmaxfaces * sizeof( sortedface_t ));\n\t\trefState.max_surfaces = oldmaxfaces;\n\t}\n}\n\nstatic int Mod_LoadEntities_splitstr_handler( char *prev, char *next, void *userdata )\n{\n\tconst char *wad;\n\twadlist_t *wadlist = userdata;\n\n\t*next = '\\0';\n\n\tif( !COM_CheckStringEmpty( prev ))\n\t\treturn 0;\n\n\tCOM_FixSlashes( prev );\n\twad = COM_FileWithoutPath( prev );\n\n\tif( Q_stricmp( COM_FileExtension( wad ), \"wad\" ))\n\t\treturn 0;\n\n\t// make sure that wad is really exist\n\tif( FS_FileExists( wad, false ))\n\t{\n\t\tint num = wadlist->count++;\n\t\tQ_strncpy( wadlist->wadnames[num], wad, sizeof( wadlist->wadnames[0] ));\n\t\twadlist->wadusage[num] = 0;\n\t}\n\n\tif( wadlist->count >= ARRAYSIZE( wadlist->wadnames ))\n\t\treturn 1;\n\n\treturn 0;\n}\n\n/*\n=================\nMod_LoadEntities\n=================\n*/\nstatic void Mod_LoadEntities( model_t *mod, dbspmodel_t *bmod )\n{\n\tbyte   *entpatch = NULL;\n\tchar   token[MAX_TOKEN];\n\tstring keyname;\n\tchar   *pfile;\n\n\tif( bmod->isworld )\n\t{\n\t\tchar        entfilename[MAX_QPATH];\n\t\tfs_offset_t\tentpatchsize;\n\t\tint         ft1, ft2;\n\n\t\t// if world check for entfile too\n\t\tQ_strncpy( entfilename, mod->name, sizeof( entfilename ));\n\t\tCOM_ReplaceExtension( entfilename, \".ent\", sizeof( entfilename ));\n\n\t\t// make sure that entity patch is never than bsp\n\t\tft1 = FS_FileTime( mod->name, false );\n\t\tft2 = FS_FileTime( entfilename, true );\n\n\t\tif( ft2 != -1 )\n\t\t{\n\t\t\tif( ft1 > ft2 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"Entity patch is older than bsp. Ignored.\\n\" );\n\t\t\t}\n\t\t\telse if(( entpatch = FS_LoadFile( entfilename, &entpatchsize, true )) != NULL )\n\t\t\t{\n\t\t\t\tCon_Printf( \"^2Read entity patch:^7 %s\\n\", entfilename );\n\t\t\t\tbmod->entdatasize = entpatchsize;\n\t\t\t\tbmod->entdata = entpatch;\n\t\t\t}\n\t\t}\n\t}\n\n\t// make sure that we really have null terminator\n\tmod->entities = Mem_Malloc( mod->mempool, bmod->entdatasize + 1 );\n\tmemcpy( mod->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool\n\tmod->entities[bmod->entdatasize] = 0;\n\n\tif( entpatch )\n\t{\n\t\tMem_Free( entpatch ); // release entpatch if present\n\t\tentpatch = NULL;\n\t}\n\n\tif( !bmod->isworld )\n\t\treturn;\n\n\tpfile = (char *)mod->entities;\n\tworld.generator[0] = '\\0';\n\tworld.compiler[0] = '\\0';\n\tworld.message[0] = '\\0';\n\tworld.wadlist.count = 0;\n\n\t// parse all the wads for loading textures in right ordering\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tif( token[0] != '{' )\n\t\t\tHost_Error( \"%s: found %s when expecting {\\n\", __func__, token );\n\n\t\twhile( 1 )\n\t\t{\n\t\t\t// parse key\n\t\t\tif(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )\n\t\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\n\t\t\tif( token[0] == '}' )\n\t\t\t\tbreak; // end of desc\n\n\t\t\tQ_strncpy( keyname, token, sizeof( keyname ));\n\n\t\t\t// parse value\n\t\t\tif(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )\n\t\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\n\t\t\tif( token[0] == '}' )\n\t\t\t\tHost_Error( \"%s: closing brace without data\\n\", __func__ );\n\n\t\t\tif( !Q_stricmp( keyname, \"wad\" ))\n\t\t\t\tQ_splitstr( token, ';', &world.wadlist, Mod_LoadEntities_splitstr_handler );\n\t\t\telse if( !Q_stricmp( keyname, \"message\" ))\n\t\t\t\tQ_strncpy( world.message, token, sizeof( world.message ));\n\t\t\telse if( !Q_stricmp( keyname, \"compiler\" ) || !Q_stricmp( keyname, \"_compiler\" ))\n\t\t\t\tQ_strncpy( world.compiler, token, sizeof( world.compiler ));\n\t\t\telse if( !Q_stricmp( keyname, \"generator\" ) || !Q_stricmp( keyname, \"_generator\" ))\n\t\t\t\tQ_strncpy( world.generator, token, sizeof( world.generator ));\n\t\t}\n\t\treturn;\t// all done\n\t}\n}\n\n/*\n=================\nMod_LoadPlanes\n=================\n*/\nstatic void Mod_LoadPlanes( model_t *mod, dbspmodel_t *bmod )\n{\n\tdplane_t\t*in;\n\tmplane_t\t*out;\n\tint\ti, j;\n\n\tin = bmod->planes;\n\tmod->planes = out = Mem_Malloc( mod->mempool, bmod->numplanes * sizeof( *out ));\n\tmod->numplanes = bmod->numplanes;\n\n\tfor( i = 0; i < bmod->numplanes; i++, in++, out++ )\n\t{\n\t\tout->signbits = 0;\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tout->normal[j] = in->normal[j];\n\n\t\t\tif( out->normal[j] < 0.0f )\n\t\t\t\tSetBits( out->signbits, BIT( j ));\n\t\t}\n\n\t\tif( VectorLength( out->normal ) < 0.5f )\n\t\t\tCon_Printf( S_ERROR \"bad normal for plane #%i\\n\", i );\n\n\t\tout->dist = in->dist;\n\t\tout->type = in->type;\n\t}\n}\n\n/*\n=================\nMod_LoadVertexes\n=================\n*/\nstatic void Mod_LoadVertexes( model_t *mod, dbspmodel_t *bmod )\n{\n\tdvertex_t\t*in;\n\tmvertex_t\t*out;\n\tint\ti;\n\n\tin = bmod->vertexes;\n\tout = mod->vertexes = Mem_Malloc( mod->mempool, bmod->numvertexes * sizeof( mvertex_t ));\n\tmod->numvertexes = bmod->numvertexes;\n\n\tif( bmod->isworld ) ClearBounds( world.mins, world.maxs );\n\n\tfor( i = 0; i < bmod->numvertexes; i++, in++, out++ )\n\t{\n\t\tif( bmod->isworld )\n\t\t\tAddPointToBounds( in->point, world.mins, world.maxs );\n\t\tVectorCopy( in->point, out->position );\n\t}\n\n\tif( !bmod->isworld ) return;\n\n\tVectorSubtract( world.maxs, world.mins, world.size );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\t// spread the mins / maxs by a pixel\n\t\tworld.mins[i] -= 1.0f;\n\t\tworld.maxs[i] += 1.0f;\n\t}\n}\n\n/*\n=================\nMod_LoadEdges\n=================\n*/\nstatic void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod )\n{\n\tint\ti;\n\n\tmod->numedges = bmod->numedges;\n\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tdedge32_t *in = bmod->edges32;\n\t\tmedge32_t *out;\n\t\tmod->edges32 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out ));\n\n\t\tfor( i = 0; i < bmod->numedges; i++, in++, out++ )\n\t\t{\n\t\t\tout->v[0] = in->v[0];\n\t\t\tout->v[1] = in->v[1];\n\t\t}\n\t}\n\telse\n\t{\n\t\tdedge_t\t*in = bmod->edges;\n\t\tmedge16_t *out;\n\t\tmod->edges16 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out ));\n\n\t\tfor( i = 0; i < bmod->numedges; i++, in++, out++ )\n\t\t{\n\t\t\tout->v[0] = (word)in->v[0];\n\t\t\tout->v[1] = (word)in->v[1];\n\t\t}\n\t}\n}\n\n/*\n=================\nMod_LoadSurfEdges\n=================\n*/\nstatic void Mod_LoadSurfEdges( model_t *mod, dbspmodel_t *bmod )\n{\n\tmod->surfedges = Mem_Malloc( mod->mempool, bmod->numsurfedges * sizeof( dsurfedge_t ));\n\tmemcpy( mod->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t ));\n\tmod->numsurfedges = bmod->numsurfedges;\n}\n\n/*\n=================\nMod_LoadMarkSurfaces\n=================\n*/\nstatic void Mod_LoadMarkSurfaces( model_t *mod, dbspmodel_t *bmod )\n{\n\tmsurface_t\t**out;\n\tint\t\ti;\n\n\tmod->marksurfaces = out = Mem_Malloc( mod->mempool, bmod->nummarkfaces * sizeof( *out ));\n\tmod->nummarksurfaces = bmod->nummarkfaces;\n\n\tif( bmod->version == QBSP2_VERSION )\n\t{\n\t\tconst dmarkface32_t *in = bmod->markfaces32;\n\n\t\tfor( i = 0; i < bmod->nummarkfaces; i++ )\n\t\t{\n\t\t\tif( in[i] < 0 || in[i] >= mod->numsurfaces )\n\t\t\t\tHost_Error( \"%s: bad surface number %i at %i (max %i) in '%s'\\n\", __func__, in[i], i, mod->numsurfaces, mod->name );\n\t\t\tout[i] = mod->surfaces + in[i];\n\t\t}\n\t}\n\telse\n\t{\n\t\tconst dmarkface_t *in = bmod->markfaces;\n\n\t\tfor( i = 0; i < bmod->nummarkfaces; i++ )\n\t\t{\n\t\t\t// NOTE: some of the buggy compilers have written a broken BSP file\n\t\t\t// with marksurface pointing at negative surface, for example darkf6.bsp\n\t\t\t// and darkf26.bsp in darkfuture mod. GoldSrc straight up writes\n\t\t\t// invalid pointer to a surface. Try to fix up these cases...\n\t\t\tif( mod->numsurfaces <= INT16_MAX && (int16_t)in[i] < 0 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_WARN \"%s: fixing up bad surface number %i at %i (max %i) in '%s'\\n\", __func__, in[i], i, mod->numsurfaces, mod->name );\n\t\t\t\tout[i] = mod->surfaces;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif( in[i] < 0 || in[i] >= mod->numsurfaces )\n\t\t\t\tHost_Error( \"%s: bad surface number %i at %i (max %i) in '%s'\\n\", __func__, in[i], i, mod->numsurfaces, mod->name );\n\t\t\tout[i] = mod->surfaces + in[i];\n\t\t}\n\t}\n}\n\nstatic qboolean Mod_LooksLikeWaterTexture( const char *name )\n{\n\tif(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' )\n\t\treturn true;\n\n\tif( !Host_IsQuakeCompatible( ))\n\t{\n\t\tif( !Q_strncmp( name, \"water\", 5 ) || !Q_strnicmp( name, \"laser\", 5 ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void Mod_TextureReplacementReport( const char *modelname, const char *texname, const char *type, int gl_texturenum, const char *foundpath )\n{\n\tif( host_allow_materials.value != 2.0f )\n\t\treturn;\n\n\tif( gl_texturenum > 0 ) // found and loaded successfully\n\t\tCon_Printf( \"Looking for %s:%s%s tex replacement...\" S_GREEN \"OK (%s)\\n\", modelname, texname, type, foundpath );\n\telse if( gl_texturenum < 0 ) // not found\n\t\tCon_Printf( \"Looking for %s:%s%s tex replacement...\" S_YELLOW \"MISS (%s)\\n\", modelname, texname, type, foundpath );\n\telse // found but not loaded\n\t\tCon_Printf( \"Looking for %s:%s%s tex replacement...\" S_RED \"FAIL (%s)\\n\", modelname, texname, type, foundpath );\n}\n\nstatic qboolean Mod_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *texname, const char *type )\n{\n\tconst char *subdirs[] = { modelname, \"common\" };\n\tint i;\n\n\tfor( i = 0; i < ARRAYSIZE( subdirs ); i++ )\n\t{\n\t\tif( Q_snprintf( out, size, \"materials/%s/%s%s.tga\", subdirs[i], texname, type ) < 0 )\n\t\t\tcontinue; // truncated name\n\n\t\tif( g_fsapi.FileExists( out, false ))\n\t\t\treturn true; // found, load it\n\t}\n\n\tMod_TextureReplacementReport( modelname, texname, type, -1, \"not found\" );\n\n\treturn false;\n}\n\nstatic void Mod_InitSkyClouds( model_t *mod, const mip_t *mt, texture_t *tx, qboolean custom_palette )\n{\n#if !XASH_DEDICATED\n\trgbdata_t\tr_temp, *r_sky;\n\tuint\t*trans, *rgba;\n\tuint\ttranspix;\n\tint\tr, g, b;\n\tint\ti, j, p;\n\tstring\ttexname;\n\tint solidskyTexture = 0, alphaskyTexture = 0;\n\n\tif( !ref.initialized )\n\t\treturn;\n\n\tif( Mod_AllowMaterials( ))\n\t{\n\t\trgbdata_t *pic;\n\n\t\tif( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, \"_solid\" ))\n\t\t{\n\t\t\tpic = FS_LoadImage( texname, NULL, 0 );\n\t\t\tif( pic )\n\t\t\t{\n\t\t\t\t// need to do rename texture to properly cleanup these textures on reload\n\t\t\t\tsolidskyTexture = GL_LoadTextureInternal( \"solid_sky\", pic, TF_NOMIPMAP );\n\t\t\t\tMod_TextureReplacementReport( mod->name, mt->name, \"_solid\", solidskyTexture, texname );\n\t\t\t\tFS_FreeImage( pic );\n\t\t\t}\n\t\t}\n\n\t\tif( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, \"_alpha\" ))\n\t\t{\n\t\t\tpic = FS_LoadImage( texname, NULL, 0 );\n\t\t\tif( pic )\n\t\t\t{\n\t\t\t\talphaskyTexture = GL_LoadTextureInternal( \"alpha_sky\", pic, TF_NOMIPMAP );\n\t\t\t\tMod_TextureReplacementReport( mod->name, mt->name, \"_alpha\", alphaskyTexture, texname );\n\t\t\t\tFS_FreeImage( pic );\n\t\t\t}\n\t\t}\n\n\t\tif( !solidskyTexture || !alphaskyTexture )\n\t\t{\n\t\t\tref.dllFuncs.GL_FreeTexture( solidskyTexture );\n\t\t\tref.dllFuncs.GL_FreeTexture( alphaskyTexture );\n\t\t}\n\t\telse goto done; // replacements found, notify the renderer and exit\n\t}\n\n\tQ_snprintf( texname, sizeof( texname ), \"%s%s.mip\", ( mt->offsets[0] > 0 ) ? \"#\" : \"\", tx->name );\n\n\tif( mt->offsets[0] > 0 )\n\t{\n\t\tsize_t size = sizeof( mip_t ) + (( mt->width * mt->height * 85 ) >> 6 );\n\n\t\tif( custom_palette )\n\t\t\tsize += sizeof( short ) + 768;\n\n\t\tr_sky = FS_LoadImage( texname, (byte *)mt, size );\n\t}\n\telse\n\t{\n\t\t// okay loading it from wad\n\t\tr_sky = FS_LoadImage( texname, NULL, 0 );\n\t}\n\n\tif( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: unable to load sky texture %s\\n\", __func__, tx->name );\n\n\t\tif( r_sky )\n\t\t\tFS_FreeImage( r_sky );\n\n\t\treturn;\n\t}\n\n\t// make an average value for the back to avoid\n\t// a fringe on the top level\n\ttrans = Mem_Malloc( host.mempool, r_sky->height * r_sky->height * sizeof( *trans ));\n\tr = g = b = 0;\n\n\tfor( i = 0; i < r_sky->width >> 1; i++ )\n\t{\n\t\tfor( j = 0; j < r_sky->height; j++ )\n\t\t{\n\t\t\tp = r_sky->buffer[i * r_sky->width + j + r_sky->height];\n\t\t\trgba = (uint *)r_sky->palette + p;\n\t\t\ttrans[(i * r_sky->height) + j] = *rgba;\n\t\t\tr += ((byte *)rgba)[0];\n\t\t\tg += ((byte *)rgba)[1];\n\t\t\tb += ((byte *)rgba)[2];\n\t\t}\n\t}\n\n\t((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height );\n\t((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height );\n\t((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height );\n\t((byte *)&transpix)[3] = 0;\n\n\t// build a temporary image\n\tr_temp = *r_sky;\n\tr_temp.width = r_sky->width >> 1;\n\tr_temp.height = r_sky->height;\n\tr_temp.type = PF_RGBA_32;\n\tr_temp.flags = IMAGE_HAS_COLOR;\n\tr_temp.size = r_temp.width * r_temp.height * 4;\n\tr_temp.buffer = (byte *)trans;\n\tr_temp.palette = NULL;\n\n\t// load it in\n\tsolidskyTexture = GL_LoadTextureInternal( \"solid_sky\", &r_temp, TF_NOMIPMAP );\n\n\tfor( i = 0; i < r_sky->width >> 1; i++ )\n\t{\n\t\tfor( j = 0; j < r_sky->height; j++ )\n\t\t{\n\t\t\tp = r_sky->buffer[i * r_sky->width + j];\n\n\t\t\tif( p == 0 )\n\t\t\t{\n\t\t\t\ttrans[(i * r_sky->height) + j] = transpix;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\trgba = (uint *)r_sky->palette + p;\n\t\t\t\ttrans[(i * r_sky->height) + j] = *rgba;\n\t\t\t}\n\t\t}\n\t}\n\n\tr_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;\n\n\t// load it in\n\talphaskyTexture = GL_LoadTextureInternal( \"alpha_sky\", &r_temp, TF_NOMIPMAP );\n\n\t// clean up\n\tFS_FreeImage( r_sky );\n\tMem_Free( trans );\n\n\tif( !solidskyTexture || !alphaskyTexture )\n\t{\n\t\tref.dllFuncs.GL_FreeTexture( solidskyTexture );\n\t\tref.dllFuncs.GL_FreeTexture( alphaskyTexture );\n\t\treturn;\n\t}\n\ndone:\n\t// notify the renderer\n\tref.dllFuncs.R_SetSkyCloudsTextures( solidskyTexture, alphaskyTexture );\n\n\tif( solidskyTexture && alphaskyTexture )\n\t\tSetBits( world.flags, FWORLD_SKYSPHERE );\n#endif // !XASH_DEDICATED\n}\n\nstatic void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureIndex )\n{\n\tuint32_t txFlags = 0;\n\tchar texpath[MAX_VA_STRING];\n\tchar safemtname[16]; // only for external textures\n\tqboolean load_external = false;\n\n\t// don't load texture data on dedicated server, as there is no renderer.\n\t// but count the wadusage for automatic precache\n\ttexture_t *texture = mod->textures[textureIndex];\n\tconst mip_t *mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );\n\tconst qboolean usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( mod, bmod, textureIndex );\n\tconst qboolean iswater = Mod_LooksLikeWaterTexture( mipTex->name );\n\n\t// check for multi-layered sky texture (quake1 specific)\n\tif( bmod->isworld && Q_strncmp( mipTex->name, \"sky\", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 )\n\t{\n\t\tMod_InitSkyClouds( mod, mipTex, texture, usesCustomPalette ); // load quake sky\n\t\treturn;\n\t}\n\n\t// FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too\n\t// but there is no facility for this yet\n\tif( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' )\n\t\tSetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing\n\n\t// check if this is water to keep the source texture and expand it to RGBA (so ripple effect works)\n\tif( iswater )\n\t\tSetBits( txFlags, TF_KEEP_SOURCE | TF_EXPAND_SOURCE );\n\n\t// Texture loading order:\n\t// 1. HQ from disk\n\t// 2. From WAD\n\t// 3. Internal from map\n\n\ttexture->gl_texturenum = 0;\n\tQ_strncpy( safemtname, mipTex->name, sizeof( safemtname ));\n\tif( safemtname[0] == '*' )\n\t\tsafemtname[0] = '!'; // replace unexpected symbol\n\n\tif( Mod_AllowMaterials( ))\n\t{\n#if !XASH_DEDICATED\n\t\tif( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, \"\" ))\n\t\t{\n\t\t\ttexture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags );\n\t\t\tload_external = texture->gl_texturenum != 0;\n\t\t\tMod_TextureReplacementReport( mod->name, safemtname, \"\", texture->gl_texturenum, texpath );\n\t\t}\n#endif // !XASH_DEDICATED\n\t}\n\n\t// Try WAD texture (force while r_wadtextures is 1)\n\tif( !texture->gl_texturenum && (( r_wadtextures.value && world.wadlist.count > 0 ) || mipTex->offsets[0] <= 0 ))\n\t{\n\t\trgbdata_t *pic = NULL;\n\t\tint wadIndex = Mod_LoadTextureFromWadList( &world.wadlist, mipTex->name, Host_IsDedicated() ? NULL : &pic, texpath, sizeof( texpath ));\n\n\t\tif( wadIndex >= 0 )\n\t\t{\n#if !XASH_DEDICATED\n\t\t\tif( !Host_IsDedicated( ) && pic != NULL )\n\t\t\t{\n\t\t\t\ttexture->gl_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texpath, pic, txFlags, false );\n\t\t\t\tFS_FreeImage( pic );\n\t\t\t}\n#endif // !XASH_DEDICATED\n\t\t\tworld.wadlist.wadusage[wadIndex]++;\n\t\t}\n\t}\n\n#if !XASH_DEDICATED\n\tif( Host_IsDedicated( ))\n\t\treturn;\n\n\t// WAD failed, so use internal texture (if present)\n\tif( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 )\n\t{\n\t\tchar texName[64];\n\t\tconst size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );\n\n\t\tQ_snprintf( texName, sizeof( texName ), \"#%s:%s.mip\", loadstat.name, mipTex->name );\n\t\ttexture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );\n\t}\n\n\t// If texture is completely missed:\n\tif( texture->gl_texturenum == 0 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"Unable to find %s.mip\\n\", mipTex->name );\n\t\ttexture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );\n\t}\n\n\ttexture->fb_texturenum = 0;\n\n\t// Check for luma texture\n\t// a1ba: ignore for water because fb_texturenum will be used to store ripple texture\n\tif( iswater )\n\t\treturn;\n\n\tif( load_external ) // external textures will not have TF_HAS_LUMA flag because it set only from WAD images loader\n\t{\n\t\tif( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, \"_luma\" ))\n\t\t{\n\t\t\ttexture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, TF_MAKELUMA );\n\t\t\tMod_TextureReplacementReport( mod->name, safemtname, \"_luma\", texture->fb_texturenum, texpath );\n\t\t}\n\t}\n\n\tif( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, texture->gl_texturenum ), TF_HAS_LUMA ) && !texture->fb_texturenum )\n\t{\n\t\tchar texName[64];\n\n\t\tQ_snprintf( texName, sizeof( texName ), \"#%s:%s_luma.mip\", loadstat.name, mipTex->name );\n\n\t\tif( mipTex->offsets[0] > 0 )\n\t\t{\n\t\t\tconst size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );\n\t\t\ttexture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint wadIndex;\n\t\t\trgbdata_t *pic = NULL;\n\n\t\t\t// NOTE: We can't load the _luma texture from the WAD as normal because it\n\t\t\t// doesn't exist there. The original texture is already loaded, but cannot be modified.\n\t\t\t// Instead, load the original texture again and convert it to luma.\n\t\t\twadIndex = Mod_LoadTextureFromWadList( &world.wadlist, texture->name, &pic, NULL, 0 );\n\n\t\t\tif( wadIndex >= 0 && pic != NULL )\n\t\t\t{\n\t\t\t\t// OK, loading it from wad or hi-res(??) version\n\t\t\t\ttexture->fb_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texName, pic, TF_MAKELUMA, false );\n\t\t\t\tFS_FreeImage( pic );\n\t\t\t\tworld.wadlist.wadusage[wadIndex]++;\n\t\t\t}\n\t\t}\n\t}\n#endif // !XASH_DEDICATED\n}\n\nstatic void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex )\n{\n\ttexture_t *texture;\n\tmip_t *mipTex;\n\n\tif( textureIndex < 0 || textureIndex >= mod->numtextures )\n\t\treturn;\n\n\tmipTex = Mod_GetMipTexForTexture( bmod, textureIndex );\n\n\tif( !mipTex )\n\t{\n\t\t// No data for this texture.\n\t\t// Create default texture (some mods require this).\n\t\tMod_CreateDefaultTexture( mod, &mod->textures[textureIndex] );\n\t\treturn;\n\t}\n\n\tif( mipTex->name[0] == '\\0' )\n\t\tQ_snprintf( mipTex->name, sizeof( mipTex->name ), \"miptex_%i\", textureIndex );\n\n\ttexture = (texture_t *)Mem_Calloc( mod->mempool, sizeof( *texture ));\n\tmod->textures[textureIndex] = texture;\n\n\t// Ensure texture name is lowercase.\n\tQ_strnlwr( mipTex->name, texture->name, sizeof( texture->name ));\n\n\ttexture->width = mipTex->width;\n\ttexture->height = mipTex->height;\n\n\tMod_LoadTextureData( mod, bmod, textureIndex );\n}\n\nstatic void Mod_LoadAllTextures( model_t *mod, dbspmodel_t *bmod )\n{\n\tint i;\n\n\tfor( i = 0; i < mod->numtextures; i++ )\n\t\tMod_LoadTexture( mod, bmod, i );\n}\n\nstatic void Mod_SequenceAnimatedTexture( model_t *mod, int baseTextureIndex )\n{\n\ttexture_t *anims[10];\n\ttexture_t *altanims[10];\n\ttexture_t *baseTexture;\n\tint max = 0;\n\tint altmax = 0;\n\tint candidateIndex;\n\n\tif( baseTextureIndex < 0 || baseTextureIndex >= mod->numtextures )\n\t\treturn;\n\n\tbaseTexture = mod->textures[baseTextureIndex];\n\n\tif( !Mod_NameImpliesTextureIsAnimated( baseTexture ))\n\t\treturn;\n\n\t// Already sequenced\n\tif( baseTexture->anim_next )\n\t\treturn;\n\n\t// find the number of frames in the animation\n\tmemset( anims, 0, sizeof( anims ));\n\tmemset( altanims, 0, sizeof( altanims ));\n\n\tif( baseTexture->name[1] >= '0' && baseTexture->name[1] <= '9' )\n\t{\n\t\t// This texture is a standard animation frame.\n\t\tint frameIndex = (int)baseTexture->name[1] - (int)'0';\n\n\t\tanims[frameIndex] = baseTexture;\n\t\tmax = frameIndex + 1;\n\t}\n\telse\n\t{\n\t\t// This texture is an alternate animation frame.\n\t\tint frameIndex = (int)baseTexture->name[1] - (int)'a';\n\n\t\taltanims[frameIndex] = baseTexture;\n\t\taltmax = frameIndex + 1;\n\t}\n\n\t// Now search the rest of the textures to find all other frames.\n\tfor( candidateIndex = baseTextureIndex + 1; candidateIndex < mod->numtextures; candidateIndex++ )\n\t{\n\t\ttexture_t *altTexture = mod->textures[candidateIndex];\n\n\t\tif( !Mod_NameImpliesTextureIsAnimated( altTexture ))\n\t\t\tcontinue;\n\n\t\t// This texture is animated, but is it part of the same group as\n\t\t// the original texture we encountered? Check that the rest of\n\t\t// the name matches the original (both will be valid for at least\n\t\t// string index 2).\n\t\tif( Q_strcmp( altTexture->name + 2, baseTexture->name + 2 ) != 0 )\n\t\t\tcontinue;\n\n\t\tif( altTexture->name[1] >= '0' && altTexture->name[1] <= '9' )\n\t\t{\n\t\t\t// This texture is a standard frame.\n\t\t\tint frameIndex = (int)altTexture->name[1] - (int)'0';\n\t\t\tanims[frameIndex] = altTexture;\n\n\t\t\tif( frameIndex >= max )\n\t\t\t\tmax = frameIndex + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// This texture is an alternate frame.\n\t\t\tint frameIndex = (int)altTexture->name[1] - (int)'a';\n\t\t\taltanims[frameIndex] = altTexture;\n\n\t\t\tif( frameIndex >= altmax )\n\t\t\t\taltmax = frameIndex + 1;\n\t\t}\n\t}\n\n\t// Link all standard animated frames together.\n\tfor( candidateIndex = 0; candidateIndex < max; candidateIndex++ )\n\t{\n\t\ttexture_t *tex = anims[candidateIndex];\n\n\t\tif( !tex )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: missing frame %i of animated texture \\\"%s\\\"\\n\",\n\t\t\t\t__func__,\n\t\t\t\tcandidateIndex,\n\t\t\t\tbaseTexture->name );\n\n\t\t\tbaseTexture->anim_total = 0;\n\t\t\tbreak;\n\t\t}\n\n\t\ttex->anim_total = max * ANIM_CYCLE;\n\t\ttex->anim_min = candidateIndex * ANIM_CYCLE;\n\t\ttex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;\n\t\ttex->anim_next = anims[( candidateIndex + 1 ) % max];\n\n\t\tif( altmax > 0 )\n\t\t\ttex->alternate_anims = altanims[0];\n\t}\n\n\t// Link all alternate animated frames together.\n\tfor( candidateIndex = 0; candidateIndex < altmax; candidateIndex++ )\n\t{\n\t\ttexture_t *tex = altanims[candidateIndex];\n\n\t\tif( !tex )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: missing alternate frame %i of animated texture \\\"%s\\\"\\n\",\n\t\t\t\t__func__,\n\t\t\t\tcandidateIndex,\n\t\t\t\tbaseTexture->name );\n\n\t\t\tbaseTexture->anim_total = 0;\n\t\t\tbreak;\n\t\t}\n\n\t\ttex->anim_total = altmax * ANIM_CYCLE;\n\t\ttex->anim_min = candidateIndex * ANIM_CYCLE;\n\t\ttex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;\n\t\ttex->anim_next = altanims[( candidateIndex + 1 ) % altmax];\n\n\t\tif( max > 0 )\n\t\t\ttex->alternate_anims = anims[0];\n\t}\n}\n\nstatic void Mod_SequenceAllAnimatedTextures( model_t *mod )\n{\n\tint i;\n\n\tfor( i = 0; i < mod->numtextures; i++ )\n\t\tMod_SequenceAnimatedTexture( mod, i );\n}\n\n/*\n=================\nMod_LoadTextures\n=================\n*/\nstatic void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )\n{\n\tdmiptexlump_t *lump;\n\n#if !XASH_DEDICATED\n\t// release old sky layers first\n\tif( !Host_IsDedicated() && bmod->isworld )\n\t{\n\t\tref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( \"alpha_sky\" ));\n\t\tref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( \"solid_sky\" ));\n\t}\n#endif\n\n\tlump = bmod->textures;\n\n\tif( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )\n\t{\n\t\t// no textures\n\t\tmod->textures = NULL;\n\t\treturn;\n\t}\n\n\tmod->textures = (texture_t **)Mem_Calloc( mod->mempool, lump->nummiptex * sizeof( texture_t * ));\n\tmod->numtextures = lump->nummiptex;\n\n\tMod_LoadAllTextures( mod, bmod );\n\tMod_SequenceAllAnimatedTextures( mod );\n}\n\n/*\n=================\nMod_LoadTexInfo\n=================\n*/\nstatic void Mod_LoadTexInfo( model_t *mod, dbspmodel_t *bmod )\n{\n\tmfaceinfo_t\t*fout, *faceinfo;\n\tint\t\ti, j, k, miptex;\n\tdfaceinfo_t\t*fin;\n\tmtexinfo_t\t*out;\n\tdtexinfo_t\t*in;\n\n\t// trying to load faceinfo\n\tfaceinfo = fout = Mem_Calloc( mod->mempool, bmod->numfaceinfo * sizeof( *fout ));\n\tfin = bmod->faceinfo;\n\n\tfor( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ )\n\t{\n\t\tQ_strncpy( fout->landname, fin->landname, sizeof( fout->landname ));\n\t\tfout->texture_step = fin->texture_step;\n\t\tfout->max_extent = fin->max_extent;\n\t\tfout->groupid = fin->groupid;\n\t}\n\n\tmod->texinfo = out = Mem_Calloc( mod->mempool, bmod->numtexinfo * sizeof( *out ));\n\tmod->numtexinfo = bmod->numtexinfo;\n\tin = bmod->texinfo;\n\n\tfor( i = 0; i < bmod->numtexinfo; i++, in++, out++ )\n\t{\n\t\tfor( j = 0; j < 2; j++ )\n\t\t\tfor( k = 0; k < 4; k++ )\n\t\t\t\tout->vecs[j][k] = in->vecs[j][k];\n\n\t\tmiptex = in->miptex;\n\t\tif( miptex < 0 || miptex >= mod->numtextures )\n\t\t\tmiptex = 0; // this is possible?\n\t\tout->texture = mod->textures[miptex];\n\t\tout->flags = in->flags;\n\n\t\t// make sure what faceinfo is really exist\n\t\tif( faceinfo != NULL && in->faceinfo != -1 && in->faceinfo < bmod->numfaceinfo )\n\t\t\tout->faceinfo = &faceinfo[in->faceinfo];\n\t}\n}\n\n/*\n=================\nMod_LoadSurfaces\n=================\n*/\nstatic void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod )\n{\n\tint\t\ttest_lightsize = -1;\n\tint\t\tnext_lightofs = -1;\n\tint\t\tprev_lightofs = -1;\n\tint\t\ti, j, lightofs;\n\tmextrasurf_t\t*info;\n\tmsurface_t\t*out;\n\n\tmod->surfaces = out = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( msurface_t ));\n\tinfo = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( mextrasurf_t ));\n\tmod->numsurfaces = bmod->numsurfaces;\n\n\t// predict samplecount based on bspversion\n\tif( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION )\n\t\tbmod->lightmap_samples = 1;\n\telse bmod->lightmap_samples = 3;\n\n\tfor( i = 0; i < bmod->numsurfaces; i++, out++, info++ )\n\t{\n\t\ttexture_t\t*tex;\n\n\t\t// setup crosslinks between two parts of msurface_t\n\t\tout->info = info;\n\t\tinfo->surf = out;\n\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t{\n\t\t\tdface32_t\t*in = &bmod->surfaces32[i];\n\n\t\t\tif(( in->firstedge + in->numedges ) > mod->numsurfedges )\n\t\t\t\tcontinue;\t// corrupted level?\n\t\t\tout->firstedge = in->firstedge;\n\t\t\tout->numedges = in->numedges;\n\t\t\tif( in->side ) SetBits( out->flags, SURF_PLANEBACK );\n\t\t\tout->plane = mod->planes + in->planenum;\n\t\t\tout->texinfo = mod->texinfo + in->texinfo;\n\n\t\t\tfor( j = 0; j < MAXLIGHTMAPS; j++ )\n\t\t\t\tout->styles[j] = in->styles[j];\n\t\t\tlightofs = in->lightofs;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdface_t\t*in = &bmod->surfaces[i];\n\n\t\t\tif(( in->firstedge + in->numedges ) > mod->numsurfedges )\n\t\t\t{\n\t\t\t\tCon_Reportf( S_ERROR \"bad surface %i from %zu\\n\", i, bmod->numsurfaces );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tout->firstedge = in->firstedge;\n\t\t\tout->numedges = in->numedges;\n\t\t\tif( in->side ) SetBits( out->flags, SURF_PLANEBACK );\n\t\t\tout->plane = mod->planes + in->planenum;\n\t\t\tout->texinfo = mod->texinfo + in->texinfo;\n\n\t\t\tfor( j = 0; j < MAXLIGHTMAPS; j++ )\n\t\t\t\tout->styles[j] = in->styles[j];\n\t\t\tlightofs = in->lightofs;\n\t\t}\n\n\t\ttex = out->texinfo->texture;\n\n\t\tif( !Q_strncmp( tex->name, \"sky\", 3 ))\n\t\t\tSetBits( out->flags, SURF_DRAWSKY );\n\n\t\tif( Mod_LooksLikeWaterTexture( tex->name ))\n\t\t\tSetBits( out->flags, SURF_DRAWTURB );\n\n\t\tif( !Q_strncmp( tex->name, \"scroll\", 6 ))\n\t\t\tSetBits( out->flags, SURF_CONVEYOR );\n\n\t\tif( FBitSet( out->texinfo->flags, TEX_SCROLL ))\n\t\t\tSetBits( out->flags, SURF_CONVEYOR );\n\n\t\t// g-cont. added a combined conveyor-transparent\n\t\tif( !Q_strncmp( tex->name, \"{scroll\", 7 ))\n\t\t\tSetBits( out->flags, SURF_CONVEYOR|SURF_TRANSPARENT );\n\n\t\tif( tex->name[0] == '{' )\n\t\t\tSetBits( out->flags, SURF_TRANSPARENT );\n\n\t\tif( FBitSet( out->texinfo->flags, TEX_SPECIAL ))\n\t\t\tSetBits( out->flags, SURF_DRAWTILED );\n\n\t\tMod_CalcSurfaceBounds( mod, out, bmod );\n\t\tMod_CalcSurfaceExtents( mod, out, bmod );\n\t\tMod_CreateFaceBevels( mod, out, bmod );\n\n\t\t// grab the second sample to detect colored lighting\n\t\tif( test_lightsize > 0 && lightofs != -1 )\n\t\t{\n\t\t\tif( lightofs > prev_lightofs && lightofs < next_lightofs )\n\t\t\t\tnext_lightofs = lightofs;\n\t\t}\n\n\t\t// grab the first sample to determine lightmap size\n\t\tif( lightofs != -1 && test_lightsize == -1 )\n\t\t{\n\t\t\tint\tsample_size = Mod_SampleSizeForFace( out );\n\t\t\tint\tsmax = (info->lightextents[0] / sample_size) + 1;\n\t\t\tint\ttmax = (info->lightextents[1] / sample_size) + 1;\n\t\t\tint\tlightstyles = 0;\n\n\t\t\ttest_lightsize = smax * tmax;\n\t\t\t// count styles to right compute test_lightsize\n\t\t\tfor( j = 0; j < MAXLIGHTMAPS && out->styles[j] != 255; j++ )\n\t\t\t\tlightstyles++;\n\n\t\t\ttest_lightsize *= lightstyles;\n\t\t\tprev_lightofs = lightofs;\n\t\t\tnext_lightofs = 99999999;\n\t\t}\n\n#if !XASH_DEDICATED // TODO: Do we need subdivide on server?\n\t\tif( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() )\n\t\t\tref.dllFuncs.GL_SubdivideSurface( mod, out ); // cut up polygon for warps\n#endif\n\t}\n\n\t// now we have enough data to trying determine samplecount per lightmap pixel\n\tif( test_lightsize > 0 && prev_lightofs != -1 && next_lightofs != -1 && next_lightofs != 99999999 )\n\t{\n\t\tfloat\tsamples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;\n\n\t\tif( samples != (int)samples )\n\t\t{\n\t\t\ttest_lightsize = (test_lightsize + 3) & ~3; // align datasize and try again\n\t\t\tsamples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;\n\t\t}\n\n\t\tif( samples == 1 || samples == 3 )\n\t\t{\n\t\t\tbmod->lightmap_samples = (int)samples;\n\t\t\tbmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero\n\t\t}\n\t\telse Con_DPrintf( S_WARN \"lighting invalid samplecount: %g, defaulting to %i\\n\", samples, bmod->lightmap_samples );\n\t}\n}\n\n/*\n=================\nMod_LoadNodes\n=================\n*/\nstatic void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod )\n{\n\tmnode_t\t*out;\n\tint\ti, j, p;\n\n\tmod->nodes = out = (mnode_t *)Mem_Calloc( mod->mempool, bmod->numnodes * sizeof( *out ));\n\tmod->numnodes = bmod->numnodes;\n\n\tfor( i = 0; i < mod->numnodes; i++, out++ )\n\t{\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t{\n\t\t\tdnode32_t\t*in = &bmod->nodes32[i];\n\n\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t{\n\t\t\t\tout->minmaxs[j+0] = in->mins[j];\n\t\t\t\tout->minmaxs[j+3] = in->maxs[j];\n\t\t\t}\n\n#if !XASH_64BIT\n\t\t\tif( in->firstface >= BIT( 24 ))\n\t\t\t{\n\t\t\t\tHost_Error( \"%s: face index limit exceeded on node %i\\n\", __func__, i );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif( in->numfaces >= BIT( 24 ))\n\t\t\t{\n\t\t\t\tHost_Error( \"%s: face count limit exceeded on node %i\\n\", __func__, i );\n\t\t\t\treturn;\n\t\t\t}\n#endif\n\n\t\t\tp = in->planenum;\n\t\t\tout->plane = mod->planes + p;\n\t\t\tout->firstsurface_0 = in->firstface & 0xFFFF;\n\t\t\tout->numsurfaces_0  = in->numfaces  & 0xFFFF;\n\n\t\t\tout->firstsurface_1 = in->firstface >> 16;\n\t\t\tout->numsurfaces_1  = in->numfaces >> 16;\n\n\t\t\tfor( j = 0; j < 2; j++ )\n\t\t\t{\n\t\t\t\tp = in->children[j];\n#if XASH_64BIT\n\t\t\t\tif( p >= 0 ) out->children_[j] = mod->nodes + p;\n\t\t\t\telse out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p ));\n#else\n\t\t\t\tif( j == 0 )\n\t\t\t\t{\n\t\t\t\t\tif( p >= 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\tout->child_0_leaf = 0;\n\t\t\t\t\t\tout->child_0_off  = p;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tout->child_0_leaf = 1;\n\t\t\t\t\t\tout->child_0_off = -1 - p;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( p >= 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\tout->child_1_leaf = 0;\n\t\t\t\t\t\tout->child_1_off  = p;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tout->child_1_leaf = 1;\n\t\t\t\t\t\tout->child_1_off = -1 - p;\n\t\t\t\t\t}\n\t\t\t\t}\n#endif\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdnode_t\t*in = &bmod->nodes[i];\n\n\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t{\n\t\t\t\tout->minmaxs[j+0] = in->mins[j];\n\t\t\t\tout->minmaxs[j+3] = in->maxs[j];\n\t\t\t}\n\n\t\t\tp = in->planenum;\n\t\t\tout->plane = mod->planes + p;\n\t\t\tout->firstsurface_0 = in->firstface;\n\t\t\tout->numsurfaces_0 = in->numfaces;\n\n\t\t\tfor( j = 0; j < 2; j++ )\n\t\t\t{\n\t\t\t\tp = in->children[j];\n\t\t\t\tif( p >= 0 ) out->children_[j] = mod->nodes + p;\n\t\t\t\telse out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p ));\n\t\t\t}\n\t\t}\n\t}\n\n\t// sets nodes and leafs\n\tMod_SetParent( mod, mod->nodes, NULL );\n}\n\n/*\n=================\nMod_LoadLeafs\n=================\n*/\nstatic void Mod_LoadLeafs( model_t *mod, dbspmodel_t *bmod )\n{\n\tmleaf_t\t*out;\n\tint\ti, j, p;\n\tint\tvisclusters = 0;\n\n\tmod->leafs = out = (mleaf_t *)Mem_Calloc( mod->mempool, bmod->numleafs * sizeof( *out ));\n\tmod->numleafs = bmod->numleafs;\n\n\tif( bmod->isworld )\n\t{\n\t\tvisclusters = mod->submodels[0].visleafs;\n\t\tworld.visbytes = (visclusters + 7) >> 3;\n\t\tworld.fatbytes = (visclusters + 31) >> 3;\n\t\trefState.visbytes = world.visbytes;\n\t}\n\n\tfor( i = 0; i < bmod->numleafs; i++, out++ )\n\t{\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t{\n\t\t\tdleaf32_t\t*in = &bmod->leafs32[i];\n\n\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t{\n\t\t\t\tout->minmaxs[j+0] = in->mins[j];\n\t\t\t\tout->minmaxs[j+3] = in->maxs[j];\n\t\t\t}\n\n\t\t\tout->contents = in->contents;\n\t\t\tp = in->visofs;\n\n\t\t\tfor( j = 0; j < 4; j++ )\n\t\t\t\tout->ambient_sound_level[j] = in->ambient_level[j];\n\n\t\t\tout->firstmarksurface = mod->marksurfaces + in->firstmarksurface;\n\t\t\tout->nummarksurfaces = in->nummarksurfaces;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdleaf_t\t*in = &bmod->leafs[i];\n\n\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t{\n\t\t\t\tout->minmaxs[j+0] = in->mins[j];\n\t\t\t\tout->minmaxs[j+3] = in->maxs[j];\n\t\t\t}\n\n\t\t\tout->contents = in->contents;\n\t\t\tp = in->visofs;\n\n\t\t\tfor( j = 0; j < 4; j++ )\n\t\t\t\tout->ambient_sound_level[j] = in->ambient_level[j];\n\n\t\t\tout->firstmarksurface = mod->marksurfaces + in->firstmarksurface;\n\t\t\tout->nummarksurfaces = in->nummarksurfaces;\n\t\t}\n\n\t\tif( bmod->isworld )\n\t\t{\n\t\t\tout->cluster = ( i - 1 ); // solid leaf 0 has no visdata\n\n\t\t\tif( out->cluster >= visclusters )\n\t\t\t\tout->cluster = -1;\n\n\t\t\t// ignore visofs errors on leaf 0 (solid)\n\t\t\tif( p >= 0 && out->cluster >= 0 && mod->visdata )\n\t\t\t{\n\t\t\t\tif( p < bmod->visdatasize )\n\t\t\t\t\tout->compressed_vis = mod->visdata + p;\n\t\t\t\telse Con_Reportf( S_WARN \"Mod_LoadLeafs: invalid visofs for leaf #%i\\n\", i );\n\t\t\t}\n\t\t}\n\t\telse out->cluster = -1; // no visclusters on bmodels\n\n\t\tif( p == -1 ) out->compressed_vis = NULL;\n\t\telse out->compressed_vis = mod->visdata + p;\n\n\t\t// gl underwater warp\n\t\tif( out->contents != CONTENTS_EMPTY )\n\t\t{\n\t\t\tfor( j = 0; j < out->nummarksurfaces; j++ )\n\t\t\t{\n\t\t\t\t// mark underwater surfaces\n\t\t\t\tSetBits( out->firstmarksurface[j]->flags, SURF_UNDERWATER );\n\t\t\t}\n\t\t}\n\t}\n\n\tif( bmod->isworld && mod->leafs[0].contents != CONTENTS_SOLID )\n\t\tHost_Error( \"%s: Map %s has leaf 0 is not CONTENTS_SOLID\\n\", __func__, mod->name );\n\n\t// do some final things for world\n\tif( bmod->isworld && Mod_CheckWaterAlphaSupport( mod, bmod ))\n\t\tSetBits( world.flags, FWORLD_WATERALPHA );\n}\n\n/*\n===========\nMod_CalcPHS\n\nTo be called while loading world for multiplayer game server\n===========\n*/\nstatic void Mod_CalcPHS( model_t *mod )\n{\n\tconst qboolean vis_stats = host_developer.value >= DEV_EXTENDED;\n\tconst size_t rowbytes = ALIGN( world.visbytes, 4 ); // force align rows by 32-bit boundary\n\tconst size_t count = mod->numleafs + 1; // same as mod->submodels[0].visleafs + 1\n\tdouble t1;\n\tdouble t2;\n\tsize_t total_compressed_size = 0;\n\tsize_t hcount = 0;\n\tsize_t vcount = 0;\n\tint i;\n\tbyte *uncompressed_pvs;\n\tbyte *uncompressed_phs;\n\n\tif( !mod->visdata )\n\t\treturn;\n\n#if defined( HAVE_OPENMP )\n\tCon_Reportf( \"Building PHS in %d threads...\\n\", omp_get_max_threads( ));\n#else\n\tCon_Reportf( \"Building PHS...\\n\" );\n#endif\n\n\tuncompressed_pvs = Mem_Calloc( mod->mempool, rowbytes * count * 2 );\n\tuncompressed_phs = &uncompressed_pvs[rowbytes * count];\n\n\tworld.phsofs = Mem_Calloc( mod->mempool, sizeof( size_t ) * count );\n\tworld.compressed_phs = NULL;\n\n\tt1 = Platform_DoubleTime();\n\n#pragma omp parallel\n\t{\n\t\t// uncompress pvs first\n#pragma omp for schedule( static, 256 ) // there might be thousands of leafs, split by 256\n\t\tfor( i = 0; i < count; i++ )\n\t\t\tMod_DecompressPVSTo( &uncompressed_pvs[rowbytes * i], mod->leafs[i].compressed_vis, world.visbytes );\n\n\t\t// now create phs\n#pragma omp for schedule( static, 256 ) reduction( + : vcount, hcount )\n\t\tfor( i = 0; i < count; i++ )\n\t\t{\n\t\t\tconst byte *scan = &uncompressed_pvs[rowbytes * i];\n\t\t\tbyte *dst = &uncompressed_phs[rowbytes * i]; // rowbytes, not rowwords!\n\t\t\tsize_t j;\n\n\t\t\tmemcpy( dst, scan, rowbytes );\n\n\t\t\tfor( j = 0; j < rowbytes; j++ )\n\t\t\t{\n\t\t\t\tsize_t k;\n\t\t\t\tuint bitbyte = scan[j];\n\n\t\t\t\tif( bitbyte == 0 )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tfor( k = 0; k < 8; k++ )\n\t\t\t\t{\n\t\t\t\t\tsize_t index;\n\n\t\t\t\t\tif( !FBitSet( bitbyte, BIT( k )))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// OR this pvs row into the phs\n\t\t\t\t\t// +1 because pvs is 1 based\n\t\t\t\t\tindex = (( j * 8 ) + k + 1 );\n\t\t\t\t\tif( index >= count )\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tQ_memor( dst, &uncompressed_pvs[rowbytes * index], rowbytes );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( vis_stats && i != 0 )\n\t\t\t{\n\t\t\t\tsize_t j;\n\n\t\t\t\tfor( j = 0; j < count; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( CHECKVISBIT( scan, j ))\n\t\t\t\t\t\tvcount++;\n\n\t\t\t\t\tif( CHECKVISBIT( dst, j ))\n\t\t\t\t\t\thcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// since I can't predict at which spot compressed array\n\t// should be put, this loop is single threaded\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tconst byte *src = &uncompressed_phs[rowbytes * i];\n\t\tbyte temp_compressed_row[(MAX_MAP_LEAFS+1)/4]; // compression for this row might be ineffective\n\t\tsize_t compressed_size;\n\n\t\tcompressed_size = Mod_CompressPVS( temp_compressed_row, src, rowbytes );\n\n\t\tworld.compressed_phs = Mem_Realloc( mod->mempool, world.compressed_phs, total_compressed_size + compressed_size );\n\t\tmemcpy( &world.compressed_phs[total_compressed_size], temp_compressed_row, compressed_size );\n\t\tworld.phsofs[i]\t= total_compressed_size;\n\n\t\ttotal_compressed_size += compressed_size;\n\t}\n\n\tt2 = Platform_DoubleTime();\n\n\tif( vis_stats )\n\t\tCon_Reportf( \"Average leaves visible / audible / total: %zu / %zu / %zu\\n\", vcount / count, hcount / count, count );\n\tCon_Reportf( \"Uncompressed PHS size: %s\\n\", Q_memprint( rowbytes * count ));\n\tCon_Reportf( \"Compressed PHS size: %s\\n\", Q_memprint( total_compressed_size + sizeof( *world.phsofs ) * count ));\n\tCon_Reportf( \"PHS building time: %.2f ms\\n\", ( t2 - t1 ) * 1000.0f );\n\n\t// TODO: rewrite this into a unit test\n\t// NOTE: how to get GoldSrc fat PHS and PVS data\n\t// start a multiplayer server with some op4_bootcamp (for example)\n\t// attach to process with GDB:\n\t// (gdb) p gPAS[0]\n\t// $0 = (byte *) ...\n\t// (gdb) p gPAS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]\n\t// $1 = (byte *) ...\n\t// (gdb) dump binary memory op4_bootcamp_gs.phs $0 $1\n\t// (gdb) p gPVS[0]\n\t// $2 = (byte *) ...\n\t// (gdb) p gPVS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]\n\t// $3 = (byte *) ...\n\t// (gdb) dump binary memory op4_bootcamp_gs.pvs $0 $1\n\t//\n\t// NOTE: as of writing, uncompressed PVS and PHS data do match! hooray!\n\t//\n\t// FS_WriteFile( \"op4_bootcamp.pvs\", uncompressed_pvs, rowbytes * count );\n\t// FS_WriteFile( \"op4_bootcamp.phs\", uncompressed_phs, rowbytes * count );\n\n\t// release uncompressed data\n\tMem_Free( uncompressed_pvs );\n\n\t// TODO: cache the PHS somewhere, it might take a long time on giant maps\n}\n\n/*\n=================\nMod_LoadClipnodes\n=================\n*/\nstatic void Mod_LoadClipnodes( model_t *mod, dbspmodel_t *bmod )\n{\n\tdclipnode32_t\t*out;\n\tint\t\ti;\n\n\tbmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( mod->mempool, bmod->numclipnodes * sizeof( *out ));\n\n\tif(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->isbsp30ext && bmod->numclipnodes >= MAX_MAP_CLIPNODES_HLBSP ))\n\t{\n\t\tdclipnode32_t *in = bmod->clipnodes32;\n\n\t\tfor( i = 0; i < bmod->numclipnodes; i++, out++, in++ )\n\t\t{\n\t\t\tout->planenum = in->planenum;\n\t\t\tout->children[0] = in->children[0];\n\t\t\tout->children[1] = in->children[1];\n\t\t}\n\t}\n\telse\n\t{\n\t\tdclipnode_t\t*in = bmod->clipnodes;\n\n\t\tfor( i = 0; i < bmod->numclipnodes; i++, out++, in++ )\n\t\t{\n\t\t\tout->planenum = in->planenum;\n\n\t\t\tout->children[0] = (unsigned short)in->children[0];\n\t\t\tout->children[1] = (unsigned short)in->children[1];\n\n\t\t\t// aguirRe QBSP 'broken' clipnodes\n\t\t\tif( out->children[0] >= bmod->numclipnodes )\n\t\t\t\tout->children[0] -= 65536;\n\t\t\tif( out->children[1] >= bmod->numclipnodes )\n\t\t\t\tout->children[1] -= 65536;\n\t\t}\n\t}\n\n\t// FIXME: fill mod->clipnodes?\n\tmod->numclipnodes = bmod->numclipnodes;\n}\n\n/*\n=================\nMod_LoadVisibility\n=================\n*/\nstatic void Mod_LoadVisibility( model_t *mod, dbspmodel_t *bmod )\n{\n\tmod->visdata = Mem_Malloc( mod->mempool, bmod->visdatasize );\n\tmemcpy( mod->visdata, bmod->visdata, bmod->visdatasize );\n}\n\n/*\n=================\nMod_LoadLightVecs\n=================\n*/\nstatic void Mod_LoadLightVecs( model_t *mod, dbspmodel_t *bmod )\n{\n\tif( bmod->deluxdatasize != bmod->lightdatasize )\n\t{\n\t\tif( bmod->deluxdatasize > 0 )\n\t\t\tCon_Printf( S_ERROR \"%s: has mismatched size (%zu should be %zu)\\n\", __func__, bmod->deluxdatasize, bmod->lightdatasize );\n\t\telse\n\t\t\tMod_LoadLitfile( mod, \"dlit\", bmod->lightdatasize, &bmod->deluxedata_out, &bmod->deluxdatasize ); // old method\n\t\treturn;\n\t}\n\n\tbmod->deluxedata_out = Mem_Malloc( mod->mempool, bmod->deluxdatasize );\n\tmemcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize );\n}\n\n/*\n=================\nMod_LoadShadowmap\n=================\n*/\nstatic void Mod_LoadShadowmap( model_t *mod, dbspmodel_t *bmod )\n{\n\tif( bmod->shadowdatasize != ( bmod->lightdatasize / 3 ))\n\t{\n\t\tif( bmod->shadowdatasize > 0 )\n\t\t\tCon_Printf( S_ERROR \"%s: has mismatched size (%zu should be %zu)\\n\", __func__, bmod->shadowdatasize, bmod->lightdatasize / 3 );\n\t\treturn;\n\t}\n\n\tbmod->shadowdata_out = Mem_Malloc( mod->mempool, bmod->shadowdatasize );\n\tmemcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize );\n}\n\n/*\n=================\nMod_LoadLighting\n=================\n*/\nstatic void Mod_LoadLighting( model_t *mod, dbspmodel_t *bmod )\n{\n\tint     i;\n\n\tif( !bmod->lightdatasize )\n\t\treturn;\n\n\tswitch( bmod->lightmap_samples )\n\t{\n\tcase 1:\n\t\tif( !Mod_LoadLitfile( mod, \"lit\", bmod->lightdatasize * 3, &mod->lightdata, &bmod->lightdatasize ))\n\t\t{\n\t\t\tmod->lightdata = (color24 *)Mem_Malloc( mod->mempool, bmod->lightdatasize * sizeof( color24 ));\n\n\t\t\t// expand the white lighting data\n\t\t\tfor( i = 0; i < bmod->lightdatasize; i++ )\n\t\t\t\tmod->lightdata[i].r = mod->lightdata[i].g = mod->lightdata[i].b = bmod->lightdata[i];\n\t\t}\n\t\telse SetBits( mod->flags, MODEL_COLORED_LIGHTING );\n\t\tbreak;\n\tcase 3:\t// load colored lighting\n\t\tmod->lightdata = Mem_Malloc( mod->mempool, bmod->lightdatasize );\n\t\tmemcpy( mod->lightdata, bmod->lightdata, bmod->lightdatasize );\n\t\tSetBits( mod->flags, MODEL_COLORED_LIGHTING );\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: bad lightmap sample count %i\\n\", __func__, bmod->lightmap_samples );\n\t\tbreak;\n\t}\n\n\tCon_Reportf( \"lighting: %s\\n\", FBitSet( mod->flags, MODEL_COLORED_LIGHTING ) ? \"colored\" : \"monochrome\" );\n\n\t// not supposed to be load ?\n\tif( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))\n\t{\n\t\tMod_LoadLightVecs( mod, bmod );\n\t\tMod_LoadShadowmap( mod, bmod );\n\n\t\tif( bmod->isworld && bmod->deluxdatasize )\n\t\t\tSetBits( world.flags, FWORLD_HAS_DELUXEMAP );\n\t}\n\n\t// setup lightdata pointers\n\tif( !mod->lightdata )\n\t\treturn;\n\n\tfor( i = 0; i < mod->numsurfaces; i++ )\n\t{\n\t\tint lightofs;\n\n\t\tif( bmod->version == QBSP2_VERSION )\n\t\t\tlightofs = bmod->surfaces32[i].lightofs;\n\t\telse\n\t\t\tlightofs = bmod->surfaces[i].lightofs;\n\n\t\tif( lightofs != -1 )\n\t\t{\n\t\t\tint offset = lightofs / bmod->lightmap_samples;\n\n\t\t\t// NOTE: we divide offset by three because lighting and deluxemap keep their pointers\n\t\t\t// into three-bytes structs and shadowmap just monochrome\n\t\t\tmod->surfaces[i].samples = mod->lightdata + offset;\n\n\t\t\t// if deluxemap is present setup it too\n\t\t\tif( bmod->deluxedata_out )\n\t\t\t\tmod->surfaces[i].info->deluxemap = bmod->deluxedata_out + offset;\n\n\t\t\t// will be used by mods\n\t\t\tif( bmod->shadowdata_out )\n\t\t\t\tmod->surfaces[i].info->shadowmap = bmod->shadowdata_out + offset;\n\t\t}\n\t}\n}\n\n/*\n=================\nMod_LumpLooksLikeEntities\n\n=================\n*/\nstatic int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen )\n{\n\t// look for \"classname\" string\n\treturn Q_memmem( lump, lumplen, \"\\\"classname\\\"\", sizeof( \"\\\"classname\\\"\" ) - 1 ) != NULL ? 1 : 0;\n}\n\n/*\n=================\nMod_LoadBmodelLumps\n\nloading and processing bmodel\n=================\n*/\nstatic qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolean isworld )\n{\n\tconst dheader_t *header = (const dheader_t *)mod_base;\n\tconst dextrahdr_t\t*extrahdr = (const dextrahdr_t *)(mod_base + sizeof( dheader_t ));\n\tdbspmodel_t\t*bmod = &srcmodel;\n\tchar\t\twadvalue[2048];\n\tsize_t\t\tlen = 0;\n\tint\t\ti, ret, flags = 0;\n\tqboolean wadlist_warn = false;\n\n\t// always reset the intermediate struct\n\tmemset( bmod, 0, sizeof( dbspmodel_t ));\n\tmemset( &loadstat, 0, sizeof( loadstat_t ));\n\n\tQ_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));\n\twadvalue[0] = '\\0';\n\n\tswitch( header->version )\n\t{\n\tcase HLBSP_VERSION:\n\t\tif( extrahdr->id == IDEXTRAHEADER )\n\t\t{\n\t\t\tSetBits( flags, LUMP_BSP30EXT );\n\t\t}\n\t\t// only relevant for half-life maps\n\t\telse if( !Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_ENTITIES].fileofs, header->lumps[LUMP_ENTITIES].filelen ) &&\n\t\t\t Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))\n\t\t{\n\t\t\t// blue-shift swapped lumps\n\t\t\tsrclumps[0].lumpnumber = LUMP_PLANES;\n\t\t\tsrclumps[1].lumpnumber = LUMP_ENTITIES;\n\t\t\tbreak;\n\t\t}\n\t\t// intended fallthrough\n\tcase Q1BSP_VERSION:\n\tcase QBSP2_VERSION:\n\t\t// everything else\n\t\tsrclumps[0].lumpnumber = LUMP_ENTITIES;\n\t\tsrclumps[1].lumpnumber = LUMP_PLANES;\n\n\t\tif( header->version == QBSP2_VERSION )\n\t\t\tSetBits( mod->flags, MODEL_QBSP2 );\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( S_ERROR \"%s has wrong version number (%i should be %i)\\n\", mod->name, header->version, HLBSP_VERSION );\n\t\tloadstat.numerrors++;\n\t\treturn false;\n\t}\n\n\tbmod->version = header->version;\t// share up global\n\tif( isworld )\n\t{\n\t\tworld.flags = 0;\t// clear world settings\n\t\tSetBits( flags, LUMP_SAVESTATS|LUMP_SILENT );\n\t}\n\tbmod->isworld = isworld;\n\tbmod->isbsp30ext = FBitSet( flags, LUMP_BSP30EXT );\n\n\t// loading base lumps\n\tfor( i = 0; i < ARRAYSIZE( srclumps ); i++ )\n\t\tMod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );\n\n\t// loading extralumps\n\tfor( i = 0; i < ARRAYSIZE( extlumps ); i++ )\n\t\tMod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );\n\n\tif( !bmod->isworld && loadstat.numerrors )\n\t{\n\t\tCon_DPrintf( \"Mod_Load%s: %i error(s), %i warning(s)\\n\", isworld ? \"World\" : \"Brush\", loadstat.numerrors, loadstat.numwarnings );\n\t\treturn false; // there were errors, we can't load this map\n\t}\n\telse if( !bmod->isworld && loadstat.numwarnings )\n\t\tCon_DPrintf( \"Mod_Load%s: %i warning(s)\\n\", isworld ? \"World\" : \"Brush\", loadstat.numwarnings );\n\n\t// load into heap\n\tMod_LoadEntities( mod, bmod );\n\tMod_LoadPlanes( mod, bmod );\n\tMod_LoadSubmodels( mod, bmod );\n\tMod_LoadVertexes( mod, bmod );\n\tMod_LoadEdges( mod, bmod );\n\tMod_LoadSurfEdges( mod, bmod );\n\tMod_LoadTextures( mod, bmod );\n\tMod_LoadVisibility( mod, bmod );\n\tMod_LoadTexInfo( mod, bmod );\n\tMod_LoadSurfaces( mod, bmod );\n\tMod_LoadLighting( mod, bmod );\n\tMod_LoadMarkSurfaces( mod, bmod );\n\tMod_LoadLeafs( mod, bmod );\n\tMod_LoadNodes( mod, bmod );\n\tMod_LoadClipnodes( mod, bmod );\n\n\t// preform some post-initalization\n\tMod_MakeHull0( mod, bmod );\n\tMod_SetupSubmodels( mod, bmod );\n\n\tif( isworld )\n\t{\n\t\tworld.version = bmod->version;\n#if !XASH_DEDICATED\n\t\tworld.deluxedata = bmod->deluxedata_out;\t// deluxemap data pointer\n\t\tworld.shadowdata = bmod->shadowdata_out;\t// occlusion data pointer\n#endif // XASH_DEDICATED\n\n\t\tif( SV_Active() && svs.maxclients > 1 )\n\t\t\tMod_CalcPHS( mod );\n\t}\n\n\tfor( i = 0; i < world.wadlist.count; i++ )\n\t{\n\t\tif( !world.wadlist.wadusage[i] )\n\t\t\tcontinue;\n\n\t\tif( !wadlist_warn )\n\t\t{\n\t\t\tret = Q_snprintf( &wadvalue[len], sizeof( wadvalue ), \"%s; \", world.wadlist.wadnames[i] );\n\t\t\tif( ret == -1 )\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_WARN \"Too many wad files for output!\\n\" );\n\t\t\t\twadlist_warn = true;\n\t\t\t}\n\t\t\tlen += ret;\n\t\t}\n\t}\n\n\tif( COM_CheckString( wadvalue ))\n\t{\n\t\twadvalue[Q_strlen( wadvalue ) - 2] = '\\0'; // kill the last semicolon\n\t\tCon_Reportf( \"Wad files required to run the map: \\\"%s\\\"\\n\", wadvalue );\n\t}\n\n\treturn true;\n}\n\nstatic int Mod_LumpLooksLikeEntitiesFile( file_t *f, const dlump_t *l, int flags, const char *msg )\n{\n\tchar *buf;\n\tint ret;\n\n\tif( FS_Seek( f, l->fileofs, SEEK_SET ) < 0 )\n\t{\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_DPrintf( S_ERROR \"map ^2%s^7 %s lump past end of file\\n\", loadstat.name, msg );\n\t\treturn -1;\n\t}\n\n\tbuf = Z_Malloc( l->filelen + 1 );\n\tif( FS_Read( f, buf, l->filelen ) != l->filelen )\n\t{\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_DPrintf( S_ERROR \"can't read %s lump of map ^2%s^7\", msg, loadstat.name );\n\t\tZ_Free( buf );\n\t\treturn -1;\n\t}\n\n\tret = Mod_LumpLooksLikeEntities( buf, l->filelen );\n\n\tZ_Free( buf );\n\treturn ret;\n}\n\n/*\n=================\nMod_TestBmodelLumps\n\ncheck for possible errors\nreturn real entities lump (for bshift swapped lumps)\n=================\n*/\nqboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities )\n{\n\tconst dheader_t\t*header = (const dheader_t *)mod_base;\n\tconst dextrahdr_t *extrahdr = (const dextrahdr_t *)( mod_base + sizeof( dheader_t ));\n\tint\ti, flags = LUMP_TESTONLY;\n\n\t// always reset the intermediate struct\n\tmemset( &loadstat, 0, sizeof( loadstat_t ));\n\n\t// store the name to correct show errors and warnings\n\tQ_strncpy( loadstat.name, name, sizeof( loadstat.name ));\n\tif( silent )\n\t\tSetBits( flags, LUMP_SILENT );\n\n\tswitch( header->version )\n\t{\n\tcase HLBSP_VERSION:\n\t\tif( extrahdr->id == IDEXTRAHEADER )\n\t\t{\n\t\t\tSetBits( flags, LUMP_BSP30EXT );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// only relevant for half-life maps\n\t\t\tint ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_ENTITIES], flags, \"entities\" );\n\t\t\tif( ret < 0 ) return false;\n\t\t\tif( !ret )\n\t\t\t{\n\t\t\t\tret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_PLANES], flags, \"planes\" );\n\t\t\t\tif( ret < 0 ) return false;\n\t\t\t\tif( ret )\n\t\t\t\t{\n\t\t\t\t\t// blue-shift swapped lumps\n\t\t\t\t\t*entities = header->lumps[LUMP_PLANES];\n\n\t\t\t\t\tsrclumps[0].lumpnumber = LUMP_PLANES;\n\t\t\t\t\tsrclumps[1].lumpnumber = LUMP_ENTITIES;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// intended fallthrough\n\tcase Q1BSP_VERSION:\n\tcase QBSP2_VERSION:\n\t\t// everything else\n\t\t*entities = header->lumps[LUMP_ENTITIES];\n\n\t\tsrclumps[0].lumpnumber = LUMP_ENTITIES;\n\t\tsrclumps[1].lumpnumber = LUMP_PLANES;\n\t\tbreak;\n\tdefault:\n\t\t// don't early out: let me analyze errors\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_Printf( S_ERROR \"%s has wrong version number (%i should be %i)\\n\", name, header->version, HLBSP_VERSION );\n\t\tloadstat.numerrors++;\n\t\tbreak;\n\t}\n\n\t// loading base lumps\n\tfor( i = 0; i < ARRAYSIZE( srclumps ); i++ )\n\t\tMod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );\n\n\t// loading extralumps\n\tfor( i = 0; i < ARRAYSIZE( extlumps ); i++ )\n\t\tMod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );\n\n\tif( loadstat.numerrors )\n\t{\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_Printf( \"%s: %i error(s), %i warning(s)\\n\", __func__, loadstat.numerrors, loadstat.numwarnings );\n\t\treturn false; // there were errors, we can't load this map\n\t}\n\telse if( loadstat.numwarnings )\n\t{\n\t\tif( !FBitSet( flags, LUMP_SILENT ))\n\t\t\tCon_Printf( \"%s: %i warning(s)\\n\", __func__, loadstat.numwarnings );\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nMod_LoadBrushModel\n=================\n*/\nvoid Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded )\n{\n\tchar poolname[MAX_VA_STRING];\n\n\tQ_snprintf( poolname, sizeof( poolname ), \"^2%s^7\", mod->name );\n\n\tif( loaded ) *loaded = false;\n\n\tmod->mempool = Mem_AllocPool( poolname );\n\tmod->type = mod_brush;\n\n\t// loading all the lumps into heap\n\tif( !Mod_LoadBmodelLumps( mod, buffer, world.loading ))\n\t\treturn; // there were errors\n\n\tif( world.loading ) worldmodel = mod;\n\n\tif( loaded ) *loaded = true;\t// all done\n}\n\n/*\n==================\nMod_CheckLump\n\ncheck lump for existing\n==================\n*/\nint GAME_EXPORT Mod_CheckLump( const char *filename, const int lump, int *lumpsize )\n{\n\tfile_t\t\t*f = FS_Open( filename, \"rb\", false );\n\tbyte\t\tbuffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];\n\tsize_t\t\tprefetch_size = sizeof( buffer );\n\tdextrahdr_t\t*extrahdr;\n\tdheader_t\t\t*header;\n\n\tif( !f ) return LUMP_LOAD_COULDNT_OPEN;\n\n\tif( FS_Read( f, buffer, prefetch_size ) != prefetch_size )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_BAD_HEADER;\n\t}\n\n\theader = (dheader_t *)buffer;\n\n\tif( header->version != HLBSP_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_BAD_VERSION;\n\t}\n\n\textrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));\n\n\tif( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_NO_EXTRADATA;\n\t}\n\n\tif( lump < 0 || lump >= EXTRA_LUMPS )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_INVALID_NUM;\n\t}\n\n\tif( extrahdr->lumps[lump].filelen <= 0 )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_NOT_EXIST;\n\t}\n\n\tif( lumpsize )\n\t\t*lumpsize = extrahdr->lumps[lump].filelen;\n\n\tFS_Close( f );\n\n\treturn LUMP_LOAD_OK;\n}\n\n/*\n==================\nMod_ReadLump\n\nreading random lump by user request\n==================\n*/\nint GAME_EXPORT Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize )\n{\n\tfile_t\t\t*f = FS_Open( filename, \"rb\", false );\n\tbyte\t\tbuffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];\n\tsize_t\t\tprefetch_size = sizeof( buffer );\n\tdextrahdr_t\t*extrahdr;\n\tdheader_t\t\t*header;\n\tbyte\t\t*data;\n\tint\t\tlength;\n\n\tif( !f ) return LUMP_LOAD_COULDNT_OPEN;\n\n\tif( FS_Read( f, buffer, prefetch_size ) != prefetch_size )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_BAD_HEADER;\n\t}\n\n\theader = (dheader_t *)buffer;\n\n\tif( header->version != HLBSP_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_BAD_VERSION;\n\t}\n\n\textrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));\n\n\tif( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_NO_EXTRADATA;\n\t}\n\n\tif( lump < 0 || lump >= EXTRA_LUMPS )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_INVALID_NUM;\n\t}\n\n\tif( extrahdr->lumps[lump].filelen <= 0 )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_NOT_EXIST;\n\t}\n\n\tdata = malloc( extrahdr->lumps[lump].filelen + 1 );\n\tlength = extrahdr->lumps[lump].filelen;\n\n\tif( !data )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_MEM_FAILED;\n\t}\n\n\tFS_Seek( f, extrahdr->lumps[lump].fileofs, SEEK_SET );\n\n\tif( FS_Read( f, data, length ) != length )\n\t{\n\t\tfree( data );\n\t\tFS_Close( f );\n\t\treturn LUMP_LOAD_CORRUPTED;\n\t}\n\n\tdata[length] = 0; // write term\n\tFS_Close( f );\n\n\tif( lumpsize )\n\t\t*lumpsize = length;\n\t*lumpdata = data;\n\n\treturn LUMP_LOAD_OK;\n}\n\n/*\n==================\nMod_SaveLump\n\nwriting lump by user request\nonly empty lumps is allows\n==================\n*/\nint GAME_EXPORT Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize )\n{\n\tbyte\t\tbuffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];\n\tsize_t\t\tprefetch_size = sizeof( buffer );\n\tint\t\tresult, dummy = lumpsize;\n\tdextrahdr_t\t*extrahdr;\n\tdheader_t\t\t*header;\n\tfile_t\t\t*f;\n\n\tif( !lumpdata || lumpsize <= 0 )\n\t\treturn LUMP_SAVE_NO_DATA;\n\n\t// make sure what .bsp is placed into gamedir and not in pak\n\tif( !FS_GetDiskPath( filename, true ))\n\t\treturn LUMP_SAVE_COULDNT_OPEN;\n\n\t// first we should sure what we allow to rewrite this .bsp\n\tresult = Mod_CheckLump( filename, lump, &dummy );\n\n\tif( result != LUMP_LOAD_NOT_EXIST )\n\t\treturn result;\n\n\tf = FS_Open( filename, \"e+b\", true );\n\n\tif( !f ) return LUMP_SAVE_COULDNT_OPEN;\n\n\tif( FS_Read( f, buffer, prefetch_size ) != prefetch_size )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_BAD_HEADER;\n\t}\n\n\theader = (dheader_t *)buffer;\n\n\t// these checks below are redundant\n\tif( header->version != HLBSP_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_BAD_VERSION;\n\t}\n\n\textrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));\n\n\tif( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_NO_EXTRADATA;\n\t}\n\n\tif( lump < 0 || lump >= EXTRA_LUMPS )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_INVALID_NUM;\n\t}\n\n\tif( extrahdr->lumps[lump].filelen != 0 )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_ALREADY_EXIST;\n\t}\n\n\tFS_Seek( f, 0, SEEK_END );\n\n\t// will be saved later\n\textrahdr->lumps[lump].fileofs = FS_Tell( f );\n\textrahdr->lumps[lump].filelen = lumpsize;\n\n\tif( FS_Write( f, lumpdata, lumpsize ) != lumpsize )\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_CORRUPTED;\n\t}\n\n\t// update the header\n\tFS_Seek( f, sizeof( dheader_t ), SEEK_SET );\n\n\tif( FS_Write( f, extrahdr, sizeof( dextrahdr_t )) != sizeof( dextrahdr_t ))\n\t{\n\t\tFS_Close( f );\n\t\treturn LUMP_SAVE_CORRUPTED;\n\t}\n\n\tFS_Close( f );\n\treturn LUMP_SAVE_OK;\n}\n"
  },
  {
    "path": "engine/common/mod_local.h",
    "content": "/*\nmod_local.h - model loader\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef MOD_LOCAL_H\n#define MOD_LOCAL_H\n\n//#include \"common.h\"\n#include \"edict.h\"\n#include \"eiface.h\"\n#include \"ref_api.h\"\n#include \"studio.h\"\n\n#define LM_SAMPLE_SIZE\t\t16\n#define LM_SAMPLE_EXTRASIZE\t\t8\n\n#define MAX_MAP_WADS\t\t256\t// max wads that can be referenced per one map\n\n#define CHECKVISBIT( vis, b )\t\t((b) >= 0 ? (byte)((vis)[(b) >> 3] & (1 << ((b) & 7))) : (byte)false )\n#define SETVISBIT( vis, b )( void )\t((b) >= 0 ? (byte)((vis)[(b) >> 3] |= (1 << ((b) & 7))) : (byte)false )\n#define CLEARVISBIT( vis, b )( void )\t((b) >= 0 ? (byte)((vis)[(b) >> 3] &= ~(1 << ((b) & 7))) : (byte)false )\n\n#define REFPVS_RADIUS\t\t2.0f\t// radius for rendering\n#define FATPVS_RADIUS\t\t8.0f\t// FatPVS use radius smaller than the FatPHS\n#define FATPHS_RADIUS\t\t8.0f\t// see SV_AddToFatPAS in GoldSrc\n\n#define WORLD_INDEX\t\t\t(1)\t// world index is always 1\n\ntypedef struct consistency_s\n{\n\tconst char\t*filename;\n\tint\t\torig_index;\n\tint\t\tcheck_type;\n\tqboolean\t\tissound;\n\tint\t\tvalue;\n\tvec3_t\t\tmins;\n\tvec3_t\t\tmaxs;\n} consistency_t;\n\n#define FCRC_SHOULD_CHECKSUM\tBIT( 0 )\n#define FCRC_CHECKSUM_DONE\tBIT( 1 )\n\ntypedef struct\n{\n\tint\t\tflags;\n\tCRC32_t\t\tinitialCRC;\n} model_info_t;\n\n// values for model_t's needload\n#define NL_UNREFERENCED\t0\t\t// this model can be freed after sequence precaching is done\n#define NL_NEEDS_LOADED\t1\n#define NL_PRESENT\t\t2\n\ntypedef struct hullnode_s\n{\n\tstruct hullnode_s\t*next;\n\tstruct hullnode_s\t*prev;\n} hullnode_t;\n\ntypedef struct winding_s\n{\n\tconst mplane_t\t*plane;\n\tstruct winding_s\t*pair;\n\thullnode_t\tchain;\n\tint\t\tnumpoints;\n\tvec3_t\t\tp[];\t\t// variable sized\n} winding_t;\n\ntypedef struct\n{\n\thullnode_t\tpolys;\n\tuint\t\tnum_polys;\n} hull_model_t;\n\ntypedef struct wadlist_s\n{\n\tchar wadnames[MAX_MAP_WADS][36]; // including .wad extension\n\tint  wadusage[MAX_MAP_WADS];\n\tint  count;\n} wadlist_t;\n\ntypedef struct world_static_s\n{\n\tqboolean\t\tloading;\t\t// true if worldmodel is loading\n\tint\t\tflags;\t\t// misc flags\n\n\t// mapstats info\n\tchar\t\tmessage[2048];\t// just for debug\n\tchar\t\tcompiler[256];\t// map compiler\n\tchar\t\tgenerator[256];\t// map editor\n\n\thull_model_t\t*hull_models;\n\tint\t\tnum_hull_models;\n\n\t// out pointers to light data\n\tcolor24\t\t*deluxedata;\t// deluxemap data pointer\n\tbyte\t\t*shadowdata;\t// occlusion data pointer\n\n\t// visibility info\n\tsize_t\t\tvisbytes;\t\t// cluster size\n\tsize_t\t\tfatbytes;\t\t// fatpvs size\n\n\t// world bounds\n\tvec3_t\t\tmins;\t\t// real accuracy world bounds\n\tvec3_t\t\tmaxs;\n\tvec3_t\t\tsize;\n\n\t// tree visualization stuff\n\tint\t\trecursion_level;\n\tint\t\tmax_recursion;\n\n\tuint32_t version; // BSP version\n\n\t// Potentially Hearable Set\n\tbyte   *compressed_phs;\n\tsize_t *phsofs;\n\n\twadlist_t wadlist;\n} world_static_t;\n\n#ifndef REF_DLL\nextern world_static_t\tworld;\nextern poolhandle_t     com_studiocache;\nextern convar_t\t\tmod_studiocache;\nextern convar_t\t\tr_wadtextures;\nextern convar_t\t\tr_showhull;\nextern const mclipnode16_t box_clipnodes16[6];\nextern const mclipnode32_t box_clipnodes32[6];\n\n//\n// model.c\n//\nvoid Mod_Init( void );\nvoid Mod_FreeModel( model_t *mod );\nvoid Mod_FreeAll( void );\nvoid Mod_Shutdown( void );\nvoid Mod_ClearUserData( void );\nmodel_t *Mod_LoadWorld( const char *name, qboolean preload );\nvoid *Mod_Calloc( int number, size_t size );\nvoid *Mod_CacheCheck( struct cache_user_s *c );\nvoid Mod_LoadCacheFile( const char *path, struct cache_user_s *cu );\nvoid *Mod_AliasExtradata( model_t *mod );\nvoid *Mod_StudioExtradata( model_t *mod );\nmodel_t *Mod_FindName( const char *name, qboolean trackCRC );\nmodel_t *Mod_LoadModel( model_t *mod, qboolean crash );\nmodel_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC );\nqboolean Mod_ValidateCRC( const char *name, CRC32_t crc );\nvoid Mod_NeedCRC( const char *name, qboolean needCRC );\nvoid Mod_FreeUnused( void );\n\n//\n// mod_alias.c\n//\nvoid Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded );\n\n//\n// mod_bmodel.c\n//\nvoid Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded );\nqboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities );\nint Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean phs );\nqboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits );\nint Mod_CheckLump( const char *filename, const int lump, int *lumpsize );\nint Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize );\nint Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize );\nmleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod );\nint Mod_SampleSizeForFace( const msurface_t *surf );\nbyte *Mod_GetPVSForPoint( const vec3_t p );\nvoid Mod_UnloadBrushModel( model_t *mod );\nvoid Mod_PrintWorldStats_f( void );\n\n//\n// mod_dbghulls.c\n//\nvoid R_DrawWorldHull( void );\nvoid R_DrawModelHull( model_t *mod );\nvoid Mod_ReleaseHullPolygons( void );\n\n//\n// mod_studio.c\n//\nvoid Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded );\nvoid Mod_UnloadStudioModel( model_t *mod );\nvoid Mod_InitStudioAPI( void );\nvoid Mod_InitStudioHull( void );\nvoid Mod_ResetStudioAPI( void );\nconst char *Mod_StudioTexName( const char *modname );\nqboolean Mod_GetStudioBounds( const char *name, vec3_t mins, vec3_t maxs );\nvoid Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *org, float *ang );\nvoid Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang );\nhull_t *Mod_HullForStudio( model_t *m, float frame, int seq, vec3_t ang, vec3_t org, vec3_t size, byte *pcnt, byte *pbl, int *hitboxes, edict_t *ed );\nvoid *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc );\nvoid Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean ignore_sequences );\nint Mod_HitgroupForStudioHull( int index );\nvoid Mod_ClearStudioCache( void );\n\n//\n// mod_sprite.c\n//\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded );\n#endif\n\n#endif//MOD_LOCAL_H\n"
  },
  {
    "path": "engine/common/mod_sprite.c",
    "content": "/*\nmod_sprite.c - sprite loading\nCopyright (C) 2010 Uncle Mike\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"sprite.h\"\n#include \"studio.h\"\n#if !XASH_DEDICATED\n#include \"ref_common.h\"\n#endif // XASH_DEDICATED\n#include \"mod_local.h\"\n\n\n/*\n====================\nMod_LoadSpriteModel\n\nload sprite model\n====================\n*/\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded )\n{\n\tdsprite_q1_t\t*pinq1;\n\tdsprite_hl_t\t*pinhl;\n\tdsprite_t\t\t*pin;\n\tmsprite_t\t\t*psprite;\n\tchar\t\tpoolname[MAX_VA_STRING];\n\tint\t\ti, size;\n\n\tif( loaded ) *loaded = false;\n\tpin = (dsprite_t *)buffer;\n\tmod->type = mod_sprite;\n\ti = pin->version;\n\n\tif( pin->ident != IDSPRITEHEADER )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has wrong id (%x should be %x)\\n\", mod->name, pin->ident, IDSPRITEHEADER );\n\t\treturn;\n\t}\n\n\tif( i != SPRITE_VERSION_Q1 && i != SPRITE_VERSION_HL && i != SPRITE_VERSION_32 )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s has wrong version number (%i should be %i or %i)\\n\", mod->name, i, SPRITE_VERSION_Q1, SPRITE_VERSION_HL );\n\t\treturn;\n\t}\n\n\tQ_snprintf( poolname, sizeof( poolname ), \"^2%s^7\", mod->name );\n\tmod->mempool = Mem_AllocPool( poolname );\n\n\tif( i == SPRITE_VERSION_Q1 || i == SPRITE_VERSION_32 )\n\t{\n\t\tpinq1 = (dsprite_q1_t *)buffer;\n\t\tsize = sizeof( msprite_t ) + ( pinq1->numframes - 1 ) * sizeof( psprite->frames );\n\t\tpsprite = Mem_Calloc( mod->mempool, size );\n\t\tmod->cache.data = psprite;\t// make link to extradata\n\n\t\tpsprite->type = pinq1->type;\n\t\tpsprite->texFormat = SPR_ADDITIVE;\t//SPR_ALPHTEST;\n\t\tpsprite->numframes = mod->numframes = pinq1->numframes;\n\t\tpsprite->facecull = SPR_CULL_FRONT;\n\t\tpsprite->radius = pinq1->boundingradius;\n\t\tpsprite->synctype = pinq1->synctype;\n\n\t\t// LordHavoc: hack to allow sprites to be non-fullbright\n\t\tfor( i = 0; i < MAX_QPATH && mod->name[i]; i++ )\n\t\t\tif( mod->name[i] == '!' )\n\t\t\t\tpsprite->texFormat = SPR_ALPHTEST;\n\n\t\tmod->mins[0] = mod->mins[1] = -pinq1->bounds[0] * 0.5f;\n\t\tmod->maxs[0] = mod->maxs[1] = pinq1->bounds[0] * 0.5f;\n\t\tmod->mins[2] = -pinq1->bounds[1] * 0.5f;\n\t\tmod->maxs[2] = pinq1->bounds[1] * 0.5f;\n\t}\n\telse // if( i == SPRITE_VERSION_HL )\n\t{\n\t\tpinhl = (dsprite_hl_t *)buffer;\n\t\tsize = sizeof( msprite_t ) + ( pinhl->numframes - 1 ) * sizeof( psprite->frames );\n\t\tpsprite = Mem_Calloc( mod->mempool, size );\n\t\tmod->cache.data = psprite;\t// make link to extradata\n\n\t\tpsprite->type = pinhl->type;\n\t\tpsprite->texFormat = pinhl->texFormat;\n\t\tpsprite->numframes = mod->numframes = pinhl->numframes;\n\t\tpsprite->facecull = pinhl->facetype;\n\t\tpsprite->radius = pinhl->boundingradius;\n\t\tpsprite->synctype = pinhl->synctype;\n\n\t\tmod->mins[0] = mod->mins[1] = -pinhl->bounds[0] * 0.5f;\n\t\tmod->maxs[0] = mod->maxs[1] = pinhl->bounds[0] * 0.5f;\n\t\tmod->mins[2] = -pinhl->bounds[1] * 0.5f;\n\t\tmod->maxs[2] = pinhl->bounds[1] * 0.5f;\n\t}\n\tif( loaded ) *loaded = true;\t// done\n\n\tif( Host_IsDedicated() )\n\t{\n\t\t// skip frames loading\n\t\tpsprite->numframes = 0;\n\t\treturn;\n\t}\n}\n"
  },
  {
    "path": "engine/common/mod_studio.c",
    "content": "/*\nsv_studio.c - server studio utilities\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"studio.h\"\n#include \"r_studioint.h\"\n#include \"library.h\"\n#include \"ref_common.h\"\n\ntypedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*,  float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] );\n\ntypedef struct mstudiocache_s\n{\n\tmodel_t *model;\n\tfloat   frame;\n\tint     sequence;\n\tvec3_t  angles;\n\tvec3_t  origin;\n\tvec3_t  size;\n\tbyte    controller[4];\n\tbyte    blending[2];\n\tuint    current_hull;\n\tuint    current_plane;\n\tuint    numhitboxes;\n} mstudiocache_t;\n\n#define STUDIO_CACHESIZE\t\t16\n#define STUDIO_CACHEMASK\t\t(STUDIO_CACHESIZE - 1)\n\n// trace global variables\nstatic sv_blending_interface_t\t*pBlendAPI = NULL;\nstatic studiohdr_t\t\t\t*mod_studiohdr;\nstatic matrix3x4\t\t\tstudio_transform;\nstatic hull_t\t\t\tcache_hull[MAXSTUDIOBONES];\nstatic hull_t\t\t\tstudio_hull[MAXSTUDIOBONES];\nstatic matrix3x4\t\t\tstudio_bones[MAXSTUDIOBONES];\nstatic uint\t\t\tstudio_hull_hitgroup[MAXSTUDIOBONES];\nstatic uint\t\t\tcache_hull_hitgroup[MAXSTUDIOBONES];\nstatic mstudiocache_t\t\tcache_studio[STUDIO_CACHESIZE];\nstatic mplane_t\t\t\tstudio_planes[MAXSTUDIOBONES * 6];\nstatic mplane_t\t\t\tcache_planes[MAXSTUDIOBONES * 6];\n\n// current cache state\nstatic int\t\t\tcache_current;\nstatic int\t\t\tcache_current_hull;\nstatic int\t\t\tcache_current_plane;\n\n/*\n====================\nMod_InitStudioHull\n====================\n*/\nvoid Mod_InitStudioHull( void )\n{\n\tint\ti;\n\n\tif( studio_hull[0].planes != NULL )\n\t\treturn;\t// already initailized\n\n\tfor( i = 0; i < MAXSTUDIOBONES; i++ )\n\t{\n\t\tstudio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\t\tstudio_hull[i].planes = &studio_planes[i*6];\n\t\tstudio_hull[i].firstclipnode = 0;\n\t\tstudio_hull[i].lastclipnode = 5;\n\t}\n}\n\n/*\n===============================================================================\n\n\tSTUDIO MODELS CACHE\n\n===============================================================================\n*/\n/*\n====================\nClearStudioCache\n====================\n*/\nvoid Mod_ClearStudioCache( void )\n{\n\tmemset( cache_studio, 0, sizeof( cache_studio ));\n\tcache_current_hull = cache_current_plane = 0;\n\n\tcache_current = 0;\n}\n\n/*\n====================\nAddToStudioCache\n====================\n*/\nstatic void Mod_AddToStudioCache( float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *pcontroller, byte *pblending, model_t *model, hull_t *hull, int numhitboxes )\n{\n\tmstudiocache_t *pCache;\n\n\tif( numhitboxes + cache_current_hull >= MAXSTUDIOBONES )\n\t\tMod_ClearStudioCache();\n\n\tcache_current++;\n\tpCache = &cache_studio[cache_current & STUDIO_CACHEMASK];\n\n\tpCache->frame = frame;\n\tpCache->sequence = sequence;\n\tVectorCopy( angles, pCache->angles );\n\tVectorCopy( origin, pCache->origin );\n\tVectorCopy( size, pCache->size );\n\n\tmemcpy( pCache->controller, pcontroller, 4 );\n\tmemcpy( pCache->blending, pblending, 2 );\n\n\tpCache->model = model;\n\tpCache->current_hull = cache_current_hull;\n\tpCache->current_plane = cache_current_plane;\n\n\tmemcpy( &cache_hull[cache_current_hull], hull, numhitboxes * sizeof( hull_t ));\n\tmemcpy( &cache_planes[cache_current_plane], studio_planes, numhitboxes * sizeof( mplane_t ) * 6 );\n\tmemcpy( &cache_hull_hitgroup[cache_current_hull], studio_hull_hitgroup, numhitboxes * sizeof( uint ));\n\n\tcache_current_hull += numhitboxes;\n\tcache_current_plane += numhitboxes * 6;\n\tpCache->numhitboxes = numhitboxes;\n}\n\n/*\n====================\nCheckStudioCache\n====================\n*/\nstatic mstudiocache_t *Mod_CheckStudioCache( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *controller, byte *blending )\n{\n\tmstudiocache_t\t*pCached;\n\tint\t\ti;\n\n\tfor( i = 0; i < STUDIO_CACHESIZE; i++ )\n\t{\n\t\tpCached = &cache_studio[(cache_current - i) & STUDIO_CACHEMASK];\n\n\t\tif( pCached->model != model )\n\t\t\tcontinue;\n\n\t\tif( pCached->frame != frame )\n\t\t\tcontinue;\n\n\t\tif( pCached->sequence != sequence )\n\t\t\tcontinue;\n\n\t\tif( !VectorCompare( pCached->angles, angles ))\n\t\t\tcontinue;\n\n\t\tif( !VectorCompare( pCached->origin, origin ))\n\t\t\tcontinue;\n\n\t\tif( !VectorCompare( pCached->size, size ))\n\t\t\tcontinue;\n\n\t\tif( memcmp( pCached->controller, controller, 4 ) != 0 )\n\t\t\tcontinue;\n\n\t\tif( memcmp( pCached->blending, blending, 2 ) != 0 )\n\t\t\tcontinue;\n\n\t\treturn pCached;\n\t}\n\n\treturn NULL;\n}\n\n/*\n===============================================================================\n\n\tSTUDIO MODELS TRACING\n\n===============================================================================\n*/\n/*\n====================\nSetStudioHullPlane\n====================\n*/\nstatic void Mod_SetStudioHullPlane( int planenum, int bone, int axis, float offset, const vec3_t size )\n{\n\tmplane_t\t*pl = &studio_planes[planenum];\n\n\tpl->type = 5;\n\n\tpl->normal[0] = studio_bones[bone][0][axis];\n\tpl->normal[1] = studio_bones[bone][1][axis];\n\tpl->normal[2] = studio_bones[bone][2][axis];\n\n\tpl->dist = (pl->normal[0] * studio_bones[bone][0][3]) + (pl->normal[1] * studio_bones[bone][1][3]) + (pl->normal[2] * studio_bones[bone][2][3]) + offset;\n\n\tif( planenum & 1 ) pl->dist -= DotProductFabs( pl->normal, size );\n\telse pl->dist += DotProductFabs( pl->normal, size );\n\n}\n\n/*\n====================\nHullForStudio\n\nNOTE: pEdict may be NULL\n====================\n*/\nhull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *pcontroller, byte *pblending, int *numhitboxes, edict_t *pEdict )\n{\n\tvec3_t\t\tangles2;\n\tmstudiocache_t\t*bonecache;\n\tmstudiobbox_t\t*phitbox;\n\tqboolean\t\tbSkipShield;\n\tint\t\ti, j;\n\n\tbSkipShield = false;\n\t*numhitboxes = 0; // assume error\n\n\tif( mod_studiocache.value )\n\t{\n\t\tbonecache = Mod_CheckStudioCache( model, frame, sequence, angles, origin, size, pcontroller, pblending );\n\n\t\tif( bonecache != NULL )\n\t\t{\n\t\t\tmemcpy( studio_planes, &cache_planes[bonecache->current_plane], bonecache->numhitboxes * sizeof( mplane_t ) * 6 );\n\t\t\tmemcpy( studio_hull_hitgroup, &cache_hull_hitgroup[bonecache->current_hull], bonecache->numhitboxes * sizeof( uint ));\n\t\t\tmemcpy( studio_hull, &cache_hull[bonecache->current_hull], bonecache->numhitboxes * sizeof( hull_t ));\n\n\t\t\t*numhitboxes = bonecache->numhitboxes;\n\t\t\treturn studio_hull;\n\t\t}\n\t}\n\n\tmod_studiohdr = Mod_StudioExtradata( model );\n\tif( !mod_studiohdr ) return NULL; // probably not a studiomodel\n\n\tVectorCopy( angles, angles2 );\n\n\tif( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\tangles2[PITCH] = -angles2[PITCH]; // stupid quake bug\n\n\tpBlendAPI->SV_StudioSetupBones( model, frame, sequence, angles2, origin, pcontroller, pblending, -1, pEdict );\n\tphitbox = (mstudiobbox_t *)((byte *)mod_studiohdr + mod_studiohdr->hitboxindex);\n\n\tif( SV_IsValidEdict( pEdict ) && pEdict->v.gamestate == 1 )\n\t\tbSkipShield = 1;\n\n\tfor( i = j = 0; i < mod_studiohdr->numhitboxes; i++, j += 6 )\n\t{\n\t\tif( world.version == QBSP2_VERSION )\n\t\t\tstudio_hull[i].clipnodes32 = (mclipnode32_t *)box_clipnodes32;\n\t\telse\n\t\t\tstudio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\n\t\tif( bSkipShield && i == 21 )\n\t\t\tcontinue;\t// CS stuff\n\n\t\tstudio_hull_hitgroup[i] = phitbox[i].group;\n\n\t\tMod_SetStudioHullPlane( j + 0, phitbox[i].bone, 0, phitbox[i].bbmax[0], size );\n\t\tMod_SetStudioHullPlane( j + 1, phitbox[i].bone, 0, phitbox[i].bbmin[0], size );\n\t\tMod_SetStudioHullPlane( j + 2, phitbox[i].bone, 1, phitbox[i].bbmax[1], size );\n\t\tMod_SetStudioHullPlane( j + 3, phitbox[i].bone, 1, phitbox[i].bbmin[1], size );\n\t\tMod_SetStudioHullPlane( j + 4, phitbox[i].bone, 2, phitbox[i].bbmax[2], size );\n\t\tMod_SetStudioHullPlane( j + 5, phitbox[i].bone, 2, phitbox[i].bbmin[2], size );\n\t}\n\n\t// tell trace code about hitbox count\n\t*numhitboxes = (bSkipShield) ? (mod_studiohdr->numhitboxes - 1) : (mod_studiohdr->numhitboxes);\n\n\tif( mod_studiocache.value )\n\t\tMod_AddToStudioCache( frame, sequence, angles, origin, size, pcontroller, pblending, model, studio_hull, *numhitboxes );\n\n\treturn studio_hull;\n}\n\n/*\n===============================================================================\n\n\tSTUDIO MODELS SETUP BONES\n\n===============================================================================\n*/\n/*\n====================\nStudioCalcBoneAdj\n\n====================\n*/\nstatic void Mod_StudioCalcBoneAdj( float *adj, const byte *pcontroller )\n{\n\tint\t\t\ti, j;\n\tfloat\t\t\tvalue;\n\tmstudiobonecontroller_t\t*pbonecontroller;\n\n\tpbonecontroller = (mstudiobonecontroller_t *)((byte *)mod_studiohdr + mod_studiohdr->bonecontrollerindex);\n\n\tfor( j = 0; j < mod_studiohdr->numbonecontrollers; j++ )\n\t{\n\t\ti = pbonecontroller[j].index;\n\n\t\tif( i == STUDIO_MOUTH )\n\t\t\tcontinue; // ignore mouth\n\n\t\tif( i >= MAXSTUDIOCONTROLLERS )\n\t\t\tcontinue;\n\n\t\t// check for 360% wrapping\n\t\tif( pbonecontroller[j].type & STUDIO_RLOOP )\n\t\t{\n\t\t\tvalue = pcontroller[i] * (360.0f / 256.0f) + pbonecontroller[j].start;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvalue = pcontroller[i] / 255.0f;\n\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\tvalue = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t}\n\n\t\tswitch( pbonecontroller[j].type & STUDIO_TYPES )\n\t\t{\n\t\tcase STUDIO_XR:\n\t\tcase STUDIO_YR:\n\t\tcase STUDIO_ZR:\n\t\t\tadj[j] = value * (M_PI_F / 180.0f);\n\t\t\tbreak;\n\t\tcase STUDIO_X:\n\t\tcase STUDIO_Y:\n\t\tcase STUDIO_Z:\n\t\t\tadj[j] = value;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioCalcRotations\n\n====================\n*/\nstatic void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *pcontroller, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f )\n{\n\tint\t\ti, j, frame;\n\tmstudiobone_t\t*pbone;\n\tfloat\t\tadj[MAXSTUDIOCONTROLLERS];\n\tfloat\t\ts;\n\n\t// bah, fix this bug with changing sequences too fast\n\tif( f > pseqdesc->numframes - 1 )\n\t{\n\t\tf = 0.0f;\n\t}\n\telse if( f < -0.01f )\n\t{\n\t\t// BUG ( somewhere else ) but this code should validate this data.\n\t\t// This could cause a crash if the frame # is negative, so we'll go ahead\n\t\t// and clamp it here\n\t\tf = -0.01f;\n\t}\n\n\tframe = (int)f;\n\ts = (f - frame);\n\n\t// add in programtic controllers\n\tpbone = (mstudiobone_t *)((byte *)mod_studiohdr + mod_studiohdr->boneindex);\n\n\tmemset( adj, 0, sizeof( adj ));\n\tMod_StudioCalcBoneAdj( adj, pcontroller );\n\n\tfor( j = numbones - 1; j >= 0; j-- )\n\t{\n\t\ti = boneused[j];\n\t\tR_StudioCalcBones( frame, s, &pbone[i], &panim[i], adj, pos[i], q[i] );\n\t}\n\n\tif( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f;\n}\n\n\n/*\n====================\nStudioGetAnim\n\n====================\n*/\nvoid *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc )\n{\n\tmstudioseqgroup_t\t*pseqgroup;\n\tcache_user_t\t*paSequences;\n\tfs_offset_t\tfilesize;\n\tbyte\t\t*buf;\n\n\tpseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup;\n\tif( pseqdesc->seqgroup == 0 )\n\t\treturn ((byte *)m_pStudioHeader + pseqdesc->animindex);\n\n\tpaSequences = (cache_user_t *)m_pSubModel->submodels;\n\n\tif( paSequences == NULL )\n\t{\n\t\tpaSequences = (cache_user_t *)Mem_Calloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t ));\n\t\tm_pSubModel->submodels = (void *)paSequences;\n\t}\n\n\t// check for already loaded\n\tif( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] )))\n\t{\n\t\tstring\tfilepath, modelname, modelpath;\n\n\t\tCOM_FileBase( m_pSubModel->name, modelname, sizeof( modelname ));\n\t\tCOM_ExtractFilePath( m_pSubModel->name, modelpath );\n\n\t\t// NOTE: here we build real sub-animation filename because stupid user may rename model without recompile\n\t\tQ_snprintf( filepath, sizeof( filepath ), \"%s/%s%i%i.mdl\", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 );\n\n\t\tbuf = FS_LoadFile( filepath, &filesize, false );\n\t\tif( !buf || !filesize ) Host_Error( \"%s: can't load %s\\n\", __func__, filepath );\n\t\tif( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( \"%s: %s is corrupted\\n\", __func__, filepath );\n\n\t\tCon_Printf( \"loading: %s\\n\", filepath );\n\n\t\tpaSequences[pseqdesc->seqgroup].data = Mem_Calloc( com_studiocache, filesize );\n\t\tmemcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize );\n\t\tMem_Free( buf );\n\t}\n\n\treturn ((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex);\n}\n\n/*\n====================\nStudioSetupBones\n\nNOTE: pEdict is unused\n====================\n*/\nstatic void SV_StudioSetupBones( model_t *pModel,\tfloat frame, int sequence, const vec3_t angles, const vec3_t origin,\n\tconst byte *pcontroller, const byte *pblending, int iBone, const edict_t *pEdict )\n{\n\tint\t\ti, j, numbones = 0;\n\tint\t\tboneused[MAXSTUDIOBONES];\n\tfloat\t\tf = 0.0;\n\n\tmstudiobone_t\t*pbones;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioanim_t\t*panim;\n\n\tstatic float\tpos[MAXSTUDIOBONES][3];\n\tstatic vec4_t\tq[MAXSTUDIOBONES];\n\tmatrix3x4\t\tbonematrix;\n\n\tstatic float\tpos2[MAXSTUDIOBONES][3];\n\tstatic vec4_t\tq2[MAXSTUDIOBONES];\n\tstatic float\tpos3[MAXSTUDIOBONES][3];\n\tstatic vec4_t\tq3[MAXSTUDIOBONES];\n\tstatic float\tpos4[MAXSTUDIOBONES][3];\n\tstatic vec4_t\tq4[MAXSTUDIOBONES];\n\n\tif( sequence < 0 || sequence >= mod_studiohdr->numseq )\n\t{\n\t\t// only show warn if sequence that out of range was specified intentionally\n\t\tif( sequence > mod_studiohdr->numseq )\n\t\t\tCon_Reportf( S_WARN \"%s: sequence %i/%i out of range for model %s\\n\", __func__, sequence, mod_studiohdr->numseq, pModel->name );\n\t\tsequence = 0;\n\t}\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)mod_studiohdr + mod_studiohdr->seqindex) + sequence;\n\tpbones = (mstudiobone_t *)((byte *)mod_studiohdr + mod_studiohdr->boneindex);\n\tpanim = R_StudioGetAnim( mod_studiohdr, pModel, pseqdesc );\n\n\tif( iBone < -1 || iBone >= mod_studiohdr->numbones )\n\t\tiBone = 0;\n\n\tif( iBone == -1 )\n\t{\n\t\tnumbones = mod_studiohdr->numbones;\n\t\tfor( i = 0; i < mod_studiohdr->numbones; i++ )\n\t\t\tboneused[(numbones - i) - 1] = i;\n\t}\n\telse\n\t{\n\t\t// only the parent bones\n\t\tfor( i = iBone; i != -1; i = pbones[i].parent )\n\t\t\tboneused[numbones++] = i;\n\t}\n\n\tif( pseqdesc->numframes > 1 )\n\t\tf = ( frame * ( pseqdesc->numframes - 1 )) / 256.0f;\n\n\tMod_StudioCalcRotations( boneused, numbones, pcontroller, pos, q, pseqdesc, panim, f );\n\n\tif( pseqdesc->numblends > 1 )\n\t{\n\t\tfloat\ts;\n\n\t\tpanim += mod_studiohdr->numbones;\n\t\tMod_StudioCalcRotations( boneused, numbones, pcontroller, pos2, q2, pseqdesc, panim, f );\n\n\t\ts = (float)pblending[0] / 255.0f;\n\n\t\tR_StudioSlerpBones( mod_studiohdr->numbones, q, pos, q2, pos2, s );\n\n\t\tif( pseqdesc->numblends == 4 )\n\t\t{\n\t\t\tpanim += mod_studiohdr->numbones;\n\t\t\tMod_StudioCalcRotations( boneused, numbones, pcontroller, pos3, q3, pseqdesc, panim, f );\n\n\t\t\tpanim += mod_studiohdr->numbones;\n\t\t\tMod_StudioCalcRotations( boneused, numbones, pcontroller, pos4, q4, pseqdesc, panim, f );\n\n\t\t\ts = (float)pblending[0] / 255.0f;\n\t\t\tR_StudioSlerpBones( mod_studiohdr->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\ts = (float)pblending[1] / 255.0f;\n\t\t\tR_StudioSlerpBones( mod_studiohdr->numbones, q, pos, q3, pos3, s );\n\t\t}\n\t}\n\n\tMatrix3x4_CreateFromEntity( studio_transform, angles, origin, 1.0f );\n\n\tfor( j = numbones - 1; j >= 0; j-- )\n\t{\n\t\ti = boneused[j];\n\n\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\t\tif( pbones[i].parent == -1 )\n\t\t\tMatrix3x4_ConcatTransforms( studio_bones[i], studio_transform, bonematrix );\n\t\telse Matrix3x4_ConcatTransforms( studio_bones[i], studio_bones[pbones[i].parent], bonematrix );\n\t}\n}\n\n/*\n====================\nStudioGetAttachment\n====================\n*/\nvoid Mod_StudioGetAttachment( const edict_t *e, int iAtt, float *origin, float *angles )\n{\n\tmstudioattachment_t\t\t*pAtt;\n\tvec3_t\t\t\tangles2;\n\tmatrix3x4\t\t\tlocalPose;\n\tmatrix3x4\t\t\tworldPose;\n\tmodel_t\t\t\t*mod;\n\n\tmod = SV_ModelHandle( e->v.modelindex );\n\tmod_studiohdr = (studiohdr_t *)Mod_StudioExtradata( mod );\n\tif( !mod_studiohdr ) return;\n\n\tif( mod_studiohdr->numattachments <= 0 )\n\t{\n\t\tif( origin ) VectorCopy( e->v.origin, origin );\n\n\t\tif( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ) && angles )\n\t\t\tVectorCopy( e->v.angles, angles );\n\t\treturn;\n\t}\n\n\tiAtt = bound( 0, iAtt, mod_studiohdr->numattachments - 1 );\n\n\t// calculate attachment origin and angles\n\tpAtt = (mstudioattachment_t *)((byte *)mod_studiohdr + mod_studiohdr->attachmentindex) + iAtt;\n\n\tVectorCopy( e->v.angles, angles2 );\n\n\tif( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\tangles2[PITCH] = -angles2[PITCH];\n\n\tpBlendAPI->SV_StudioSetupBones( mod, e->v.frame, e->v.sequence, angles2, e->v.origin, e->v.controller, e->v.blending, pAtt->bone, e );\n\n\tMatrix3x4_LoadIdentity( localPose );\n\tMatrix3x4_SetOrigin( localPose, pAtt->org[0], pAtt->org[1], pAtt->org[2] );\n\tMatrix3x4_ConcatTransforms( worldPose, studio_bones[pAtt->bone], localPose );\n\n\tif( origin != NULL ) // origin is used always\n\t\tMatrix3x4_OriginFromMatrix( worldPose, origin );\n\n\tif( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ) && angles != NULL )\n\t\tMatrix3x4_AnglesFromMatrix( worldPose, angles );\n}\n\n/*\n====================\nGetBonePosition\n====================\n*/\nvoid Mod_GetBonePosition( const edict_t *e, int iBone, float *origin, float *angles )\n{\n\tmodel_t\t*mod;\n\n\tmod = SV_ModelHandle( e->v.modelindex );\n\tmod_studiohdr = (studiohdr_t *)Mod_StudioExtradata( mod );\n\tif( !mod_studiohdr ) return;\n\n\tpBlendAPI->SV_StudioSetupBones( mod, e->v.frame, e->v.sequence, e->v.angles, e->v.origin, e->v.controller, e->v.blending, iBone, e );\n\n\tif( origin ) Matrix3x4_OriginFromMatrix( studio_bones[iBone], origin );\n\tif( angles ) Matrix3x4_AnglesFromMatrix( studio_bones[iBone], angles );\n}\n\n/*\n====================\nHitgroupForStudioHull\n====================\n*/\nint Mod_HitgroupForStudioHull( int index )\n{\n\treturn studio_hull_hitgroup[index];\n}\n\n/*\n====================\nStudioBoundVertex\n====================\n*/\nstatic void Mod_StudioBoundVertex( vec3_t mins, vec3_t maxs, int *numverts, const vec3_t vertex )\n{\n\tif((*numverts) == 0 )\n\t\tClearBounds( mins, maxs );\n\n\tAddPointToBounds( vertex, mins, maxs );\n\t(*numverts)++;\n}\n\n/*\n====================\nStudioAccumulateBoneVerts\n====================\n*/\nstatic void Mod_StudioAccumulateBoneVerts( vec3_t mins, vec3_t maxs, int *numverts, vec3_t bone_mins, vec3_t bone_maxs, int *numbones )\n{\n\tvec3_t\tdelta;\n\tvec3_t\tpoint;\n\n\tif( *numbones <= 0 )\n\t\treturn;\n\n\t// calculate the midpoint of the second vertex,\n\tVectorSubtract( bone_maxs, bone_mins, delta );\n\n\tVectorScale( delta, 0.5f, point );\n\tMod_StudioBoundVertex( mins, maxs, numverts, point );\n\n\tVectorClear( bone_mins );\n\tVectorClear( bone_maxs );\n\t*numbones = 0;\n}\n\n/*\n====================\nStudioComputeBounds\n====================\n*/\nvoid Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean ignore_sequences )\n{\n\tint\t\ti, j, k, numseq;\n\tstudiohdr_t\t*pstudiohdr;\n\tmstudiobodyparts_t\t*pbodypart;\n\tmstudiomodel_t\t*m_pSubModel;\n\tmstudioseqgroup_t\t*pseqgroup;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudiobone_t\t*pbones;\n\tmstudioanim_t\t*panim;\n\tvec3_t\t\tbone_mins, bone_maxs;\n\tvec3_t\t\tvert_mins, vert_maxs;\n\tint\t\tvert_count, bone_count;\n\tint\t\tbodyCount = 0;\n\tvec3_t\t\tpos, *pverts;\n\n\tvert_count = bone_count = 0;\n\tVectorClear( bone_mins );\n\tVectorClear( bone_maxs );\n\tVectorClear( vert_mins );\n\tVectorClear( vert_maxs );\n\n\t// Get the body part portion of the model\n\tpstudiohdr = (studiohdr_t *)buffer;\n\tpbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex);\n\n\t// each body part has nummodels variations so there are as many total variations as there\n\t// are in a matrix of each part by each other part\n\tfor( i = 0; i < pstudiohdr->numbodyparts; i++ )\n\t\tbodyCount += pbodypart[i].nummodels;\n\n\t// The studio models we want are vec3_t mins, vec3_t maxsight after the bodyparts (still need to\n\t// find a detailed breakdown of the mdl format).  Move pointer there.\n\tm_pSubModel = (mstudiomodel_t *)(&pbodypart[pstudiohdr->numbodyparts]);\n\n\tfor( i = 0; i < bodyCount; i++ )\n\t{\n\t\tpverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[i].vertindex);\n\n\t\tfor( j = 0; j < m_pSubModel[i].numverts; j++ )\n\t\t\tMod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[j] );\n\t}\n\n\tpbones = (mstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex);\n\tnumseq = (ignore_sequences) ? 1 : pstudiohdr->numseq;\n\n\tfor( i = 0; i < numseq; i++ )\n\t{\n\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + i;\n\t\tpseqgroup = (mstudioseqgroup_t *)((byte *)pstudiohdr + pstudiohdr->seqgroupindex) + pseqdesc->seqgroup;\n\n\t\tif( pseqdesc->seqgroup == 0 )\n\t\t\tpanim = (mstudioanim_t *)((byte *)pstudiohdr + pseqdesc->animindex);\n\t\telse continue;\n\n\t\tfor( j = 0; j < pstudiohdr->numbones; j++ )\n\t\t{\n\t\t\tfor( k = 0; k < pseqdesc->numframes; k++ )\n\t\t\t{\n\t\t\t\tR_StudioCalcBones( k, 0, &pbones[j], panim, NULL, pos, NULL );\n\t\t\t\tMod_StudioBoundVertex( vert_mins, vert_maxs, &bone_count, pos );\n\t\t\t}\n\t\t}\n\n\t\tMod_StudioAccumulateBoneVerts( bone_mins, bone_maxs, &vert_count, vert_mins, vert_maxs, &bone_count );\n\t}\n\n\tVectorCopy( bone_mins, mins );\n\tVectorCopy( bone_maxs, maxs );\n}\n\n/*\n====================\nMod_GetStudioBounds\n====================\n*/\nqboolean Mod_GetStudioBounds( const char *name, vec3_t mins, vec3_t maxs )\n{\n\tint\tresult = false;\n\tbyte\t*f;\n\n\tif( !Q_strstr( name, \"models\" ) || !Q_strstr( name, \".mdl\" ))\n\t\treturn false;\n\n\tf = FS_LoadFile( name, NULL, false );\n\tif( !f ) return false;\n\n\tif( *(uint *)f == IDSTUDIOHEADER )\n\t{\n\t\tVectorClear( mins );\n\t\tVectorClear( maxs );\n\t\tMod_StudioComputeBounds( f, mins, maxs, false );\n\t\tresult = true;\n\t}\n\tMem_Free( f );\n\n\treturn result;\n}\n\n/*\n===============\nMod_StudioTexName\n\nextract texture filename from modelname\n===============\n*/\nconst char *Mod_StudioTexName( const char *modname )\n{\n\tstatic char\ttexname[MAX_QPATH+1];\n\n\tQ_strncpy( texname, modname, sizeof( texname ));\n\tCOM_StripExtension( texname );\n\tQ_strncat( texname, \"T.mdl\", sizeof( texname ));\n\n\treturn texname;\n}\n\n/*\n================\nMod_StudioBodyVariations\n\ncalc studio body variations\n================\n*/\nstatic int Mod_StudioBodyVariations( model_t *mod )\n{\n\tstudiohdr_t\t*pstudiohdr;\n\tmstudiobodyparts_t\t*pbodypart;\n\tint\t\ti, count = 1;\n\n\tpstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod );\n\tif( !pstudiohdr ) return 0;\n\n\tpbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex);\n\n\t// each body part has nummodels variations so there are as many total variations as there\n\t// are in a matrix of each part by each other part\n\tfor( i = 0; i < pstudiohdr->numbodyparts; i++ )\n\t\tcount = count * pbodypart[i].nummodels;\n\n\treturn count;\n}\n\n/*\n=================\nR_StudioLoadHeader\n=================\n*/\nstatic studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer )\n{\n\tbyte\t\t*pin;\n\tstudiohdr_t\t*phdr;\n\tint\t\ti;\n\n\tif( !buffer ) return NULL;\n\n\tpin = (byte *)buffer;\n\tphdr = (studiohdr_t *)pin;\n\ti = phdr->version;\n\n\tif( i != STUDIO_VERSION )\n\t{\n\t\tCon_Printf( S_ERROR \"%s has wrong version number (%i should be %i)\\n\", mod->name, i, STUDIO_VERSION );\n\t\treturn NULL;\n\t}\n\n\treturn (studiohdr_t *)buffer;\n}\n\nstatic studiohdr_t *Mod_MaybeTruncateStudioTextureData( model_t *mod )\n{\n#if XASH_LOW_MEMORY\n\tstudiohdr_t *phdr = (studiohdr_t *)mod->cache.data;\n\n\tmod->cache.data = Mem_Realloc( mod->mempool, mod->cache.data, phdr->texturedataindex );\n\tphdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr\n\tphdr->length = phdr->texturedataindex; // update model size\n\n\treturn phdr;\n#else\n\t// NOTE: some mods potentially might require full studio model data\n\treturn (studiohdr_t *)mod->cache.data;\n#endif\n}\n\n/*\n=================\nMod_LoadStudioModel\n=================\n*/\nvoid Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )\n{\n\tchar poolname[MAX_VA_STRING];\n\tstudiohdr_t\t*phdr;\n\tqboolean textures_loaded = false;\n\n\tQ_snprintf( poolname, sizeof( poolname ), \"^2%s^7\", mod->name );\n\n\tif( loaded ) *loaded = false;\n\tmod->mempool = Mem_AllocPool( poolname );\n\tmod->type = mod_studio;\n\n\tphdr = R_StudioLoadHeader( mod, buffer );\n\tif( !phdr || phdr->length < sizeof( studiohdr_t )) // garbage value in length\n\t\treturn;\t// bad model\n\n#if !XASH_DEDICATED\n\tif( !Host_IsDedicated( ) && phdr->numtextures == 0 )\n\t{\n\t\tstudiohdr_t *thdr;\n\t\tvoid *buffer2;\n\n\t\tbuffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false );\n\t\tthdr = R_StudioLoadHeader( mod, buffer2 );\n\n\t\tif( thdr != NULL )\n\t\t{\n\t\t\tbyte *in, *out;\n\t\t\tsize_t size1, size2;\n\n\t\t\t// TODO: Mod_StudioLoadTextures will crash if passed a merged studio model!\n\t\t\tref.dllFuncs.Mod_StudioLoadTextures( mod, thdr );\n\t\t\ttextures_loaded = true;\n\n\t\t\t// give space for textures and skinrefs\n\t\t\tsize1 = thdr->numtextures * sizeof( mstudiotexture_t );\n\t\t\tsize2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short );\n\t\t\tmod->cache.data = Mem_Calloc( mod->mempool, phdr->length + size1 + size2 );\n\t\t\tmemcpy( mod->cache.data, buffer, phdr->length ); // copy main mdl buffer\n\t\t\tphdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr\n\t\t\tphdr->numskinfamilies = thdr->numskinfamilies;\n\t\t\tphdr->numtextures = thdr->numtextures;\n\t\t\tphdr->numskinref = thdr->numskinref;\n\t\t\tphdr->textureindex = phdr->length;\n\t\t\tphdr->skinindex = phdr->textureindex + size1;\n\n\t\t\tin = (byte *)thdr + thdr->textureindex;\n\t\t\tout = (byte *)phdr + phdr->textureindex;\n\t\t\tmemcpy( out, in, size1 + size2 );\t// copy textures + skinrefs\n\t\t\tphdr->length += size1 + size2;\n\t\t}\n\t\telse Con_Printf( S_WARN \"%s: %s missing textures file\\n\", __func__, mod->name );\n\n\t\tif( buffer2 )\n\t\t\tMem_Free( buffer2 ); // release T.mdl\n\t}\n#endif\n\n\tif( !textures_loaded )\n\t{\n\t\t// NOTE: don't modify source buffer because it's used for CRC computing\n\t\tmod->cache.data = Mem_Calloc( mod->mempool, phdr->length );\n\t\tmemcpy( mod->cache.data, buffer, phdr->length );\n\t\tphdr = mod->cache.data;\n\n#if !XASH_DEDICATED\n\t\tif( !Host_IsDedicated( ))\n\t\t\tref.dllFuncs.Mod_StudioLoadTextures( mod, phdr );\n#endif\n\t}\n\n\t// NOTE: we may not want to keep raw textures in memory. just cutoff model pointer above texture base\n\tphdr = Mod_MaybeTruncateStudioTextureData( mod );\n\n\t// setup bounding box\n\tif( !VectorCompare( vec3_origin, phdr->bbmin ))\n\t{\n\t\t// clipping bounding box\n\t\tVectorCopy( phdr->bbmin, mod->mins );\n\t\tVectorCopy( phdr->bbmax, mod->maxs );\n\t}\n\telse if( !VectorCompare( vec3_origin, phdr->min ))\n\t{\n\t\t// movement bounding box\n\t\tVectorCopy( phdr->min, mod->mins );\n\t\tVectorCopy( phdr->max, mod->maxs );\n\t}\n\telse\n\t{\n\t\t// well compute bounds from vertices and round to nearest even values\n\t\tMod_StudioComputeBounds( phdr, mod->mins, mod->maxs, true );\n\t\tRoundUpHullSize( mod->mins );\n\t\tRoundUpHullSize( mod->maxs );\n\t}\n\n\tmod->numframes = Mod_StudioBodyVariations( mod );\n\tmod->radius = RadiusFromBounds( mod->mins, mod->maxs );\n\tmod->flags = phdr->flags; // copy header flags\n\n\tif( loaded ) *loaded = true;\n}\n\nstatic sv_blending_interface_t gBlendAPI =\n{\n\tSV_BLENDING_INTERFACE_VERSION,\n\tSV_StudioSetupBones,\n};\n\nstatic server_studio_api_t gStudioAPI =\n{\n\tMod_Calloc,\n\tMod_CacheCheck,\n\tMod_LoadCacheFile,\n\tMod_StudioExtradata,\n};\n\n/*\n===============\nMod_InitStudioAPI\n\nInitialize server studio (blending interface)\n===============\n*/\nvoid Mod_InitStudioAPI( void )\n{\n\tstatic STUDIOAPI\tpBlendIface;\n\n\tpBlendAPI = &gBlendAPI;\n\n\tpBlendIface = (STUDIOAPI)COM_GetProcAddress( svgame.hInstance, \"Server_GetBlendingInterface\" );\n\tif( pBlendIface && pBlendIface( SV_BLENDING_INTERFACE_VERSION, &pBlendAPI, &gStudioAPI, &studio_transform, &studio_bones ))\n\t{\n\t\tCon_Reportf( \"%s: ^2initailized Server Blending interface ^7ver. %i\\n\", __func__, SV_BLENDING_INTERFACE_VERSION );\n\t\treturn;\n\t}\n\n\t// just restore pointer to builtin function\n\tpBlendAPI = &gBlendAPI;\n}\n\n/*\n===============\nMod_ResetStudioAPI\n\nReturns to default callbacks\n===============\n*/\nvoid Mod_ResetStudioAPI( void )\n{\n\tpBlendAPI = &gBlendAPI;\n}\n"
  },
  {
    "path": "engine/common/model.c",
    "content": "/*\nmodel.c - modelloader\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"mod_local.h\"\n#include \"sprite.h\"\n#include \"xash3d_mathlib.h\"\n#include \"alias.h\"\n#include \"studio.h\"\n#include \"wadfile.h\"\n#include \"world.h\"\n#include \"enginefeatures.h\"\n#include \"client.h\"\n#include \"server.h\"\n\nstatic model_info_t\tmod_crcinfo[MAX_MODELS];\nstatic model_t\tmod_known[MAX_MODELS];\nstatic int\tmod_numknown = 0;\npoolhandle_t      com_studiocache;\t\t// cache for submodels\nCVAR_DEFINE( mod_studiocache, \"r_studiocache\", \"1\", FCVAR_ARCHIVE, \"enables studio cache for speedup tracing hitboxes\" );\nCVAR_DEFINE_AUTO( r_wadtextures, \"0\", 0, \"completely ignore textures in the bsp-file if enabled\" );\nCVAR_DEFINE_AUTO( r_showhull, \"0\", 0, \"draw collision hulls 1-3\" );\n\n/*\n===============================================================================\n\n\t\t\tMOD COMMON UTILS\n\n===============================================================================\n*/\n/*\n================\nMod_Modellist_f\n================\n*/\nstatic void Mod_Modellist_f( void )\n{\n\tint\ti, nummodels;\n\tmodel_t\t*mod;\n\n\tCon_Printf( \"\\n\" );\n\tCon_Printf( \"-----------------------------------\\n\" );\n\n\tfor( i = nummodels = 0, mod = mod_known; i < mod_numknown; i++, mod++ )\n\t{\n\t\tif( !COM_CheckStringEmpty( mod->name ) )\n\t\t\tcontinue; // free slot\n\t\tCon_Printf( \"%s\\n\", mod->name );\n\t\tnummodels++;\n\t}\n\n\tCon_Printf( \"-----------------------------------\\n\" );\n\tCon_Printf( \"%i total models\\n\", nummodels );\n\tCon_Printf( \"\\n\" );\n}\n\n/*\n================\nMod_FreeUserData\n================\n*/\nstatic void Mod_FreeUserData( model_t *mod )\n{\n\t// ignore submodels and freed models\n\tif( !COM_CheckStringEmpty( mod->name ) || mod->name[0] == '*' )\n\t\treturn;\n\n\tif( Host_IsDedicated() )\n\t{\n\t\tif( svgame.physFuncs.Mod_ProcessUserData != NULL )\n\t\t{\n\t\t\t// let the server.dll free custom data\n\t\t\tsvgame.physFuncs.Mod_ProcessUserData( mod, false, NULL );\n\t\t}\n\t}\n#if !XASH_DEDICATED\n\telse\n\t{\n\t\tref.dllFuncs.Mod_ProcessRenderData( mod, false, NULL );\n\t}\n#endif\n}\n\n/*\n================\nMod_FreeModel\n================\n*/\nvoid Mod_FreeModel( model_t *mod )\n{\n\t// already freed?\n\tif( !mod || !COM_CheckStringEmpty( mod->name ) )\n\t\treturn;\n\n\tif( mod->type != mod_brush || mod->name[0] != '*' )\n\t{\n\t\tMod_FreeUserData( mod );\n\t\tMem_FreePool( &mod->mempool );\n\t}\n\n\tif( mod->type == mod_brush && FBitSet( mod->flags, MODEL_WORLD ) )\n\t{\n\t\tworld.version = 0;\n\t\tworld.shadowdata = NULL;\n\t\tworld.deluxedata = NULL;\n\n\t\t// data already freed by Mem_FreePool above\n\t\tworld.hull_models = NULL;\n\t\tworld.compressed_phs = NULL;\n\t\tworld.phsofs = NULL;\n\t}\n\n\tmemset( mod, 0, sizeof( *mod ));\n}\n\n/*\n===============================================================================\n\n\t\t\tMODEL INITIALIZE\\SHUTDOWN\n\n===============================================================================\n*/\n/*\n================\nMod_Init\n================\n*/\nvoid Mod_Init( void )\n{\n\tcom_studiocache = Mem_AllocPool( \"Studio Cache\" );\n\tCvar_RegisterVariable( &mod_studiocache );\n\tCvar_RegisterVariable( &r_wadtextures );\n\tCvar_RegisterVariable( &r_showhull );\n\n\tCmd_AddCommand( \"mapstats\", Mod_PrintWorldStats_f, \"show stats for currently loaded map\" );\n\tCmd_AddCommand( \"modellist\", Mod_Modellist_f, \"display loaded models list\" );\n\n\tMod_ResetStudioAPI ();\n\tMod_InitStudioHull ();\n}\n\n/*\n================\nMod_FreeAll\n================\n*/\nvoid Mod_FreeAll( void )\n{\n\tint\ti;\n\n#if !XASH_DEDICATED\n\tMod_ReleaseHullPolygons();\n#endif\n\tfor( i = 0; i < mod_numknown; i++ )\n\t\tMod_FreeModel( &mod_known[i] );\n\tmod_numknown = 0;\n}\n\n/*\n================\nMod_ClearUserData\n================\n*/\nvoid Mod_ClearUserData( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < mod_numknown; i++ )\n\t\tMod_FreeUserData( &mod_known[i] );\n}\n\n/*\n================\nMod_Shutdown\n================\n*/\nvoid Mod_Shutdown( void )\n{\n\tMod_FreeAll();\n\tMem_FreePool( &com_studiocache );\n}\n\n/*\n===============================================================================\n\n\t\t\tMODELS MANAGEMENT\n\n===============================================================================\n*/\n/*\n==================\nMod_FindName\n\nnever return NULL\n==================\n*/\nmodel_t *Mod_FindName( const char *filename, qboolean trackCRC )\n{\n\tchar\tmodname[MAX_QPATH];\n\tmodel_t\t*mod;\n\tint\ti;\n\n\tQ_strncpy( modname, filename, sizeof( modname ));\n\n\t// search the currently loaded models\n\tfor( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )\n\t{\n\t\tif( !Q_stricmp( mod->name, modname ))\n\t\t{\n\t\t\tif( mod->mempool || mod->name[0] == '*' )\n\t\t\t\tmod->needload = NL_PRESENT;\n\t\t\telse mod->needload = NL_NEEDS_LOADED;\n\n\t\t\treturn mod;\n\t\t}\n\t}\n\n\t// find a free model slot spot\n\tfor( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ )\n\t\tif( !COM_CheckStringEmpty( mod->name ) ) break; // this is a valid spot\n\n\tif( i == mod_numknown )\n\t{\n\t\tif( mod_numknown == MAX_MODELS )\n\t\t\tHost_Error( \"MAX_MODELS limit exceeded (%d)\\n\", MAX_MODELS );\n\t\tmod_numknown++;\n\t}\n\n\t// copy name, so model loader can find model file\n\tQ_strncpy( mod->name, modname, sizeof( mod->name ));\n\tif( trackCRC ) mod_crcinfo[i].flags = FCRC_SHOULD_CHECKSUM;\n\telse mod_crcinfo[i].flags = 0;\n\tmod->needload = NL_NEEDS_LOADED;\n\tmod_crcinfo[i].initialCRC = 0;\n\n\treturn mod;\n}\n\n/*\n==================\nMod_LoadModel\n\nLoads a model into the cache\n==================\n*/\nmodel_t *Mod_LoadModel( model_t *mod, qboolean crash )\n{\n\tchar\t\ttempname[MAX_QPATH];\n\tfs_offset_t\t\tlength = 0;\n\tqboolean\t\tloaded, loaded2 = false;\n\tbyte\t\t*buf;\n\tmodel_info_t\t*p;\n\n\tASSERT( mod != NULL );\n\n\t// check if already loaded (or inline bmodel)\n\tif( mod->mempool || mod->name[0] == '*' )\n\t{\n\t\tmod->needload = NL_PRESENT;\n\t\treturn mod;\n\t}\n\n\tASSERT( mod->needload == NL_NEEDS_LOADED );\n\n\t// store modelname to show error\n\tQ_strncpy( tempname, mod->name, sizeof( tempname ));\n\tCOM_FixSlashes( tempname );\n\n\tbuf = FS_LoadFile( tempname, &length, false );\n\n\tif( !buf || length < sizeof( uint ))\n\t{\n\t\tmemset( mod, 0, sizeof( model_t ));\n\n\t\tif( crash ) Host_Error( \"Could not load model %s from disk\\n\", tempname );\n\t\telse Con_Printf( S_ERROR \"Could not load model %s from disk\\n\", tempname );\n\n\t\treturn NULL;\n\t}\n\n\tCon_Reportf( \"loading %s\\n\", mod->name );\n\tmod->needload = NL_PRESENT;\n\tmod->type = mod_bad;\n\n\t// call the apropriate loader\n\tswitch( *(uint *)buf )\n\t{\n\tcase IDSTUDIOHEADER:\n\t\tMod_LoadStudioModel( mod, buf, &loaded );\n\t\tbreak;\n\tcase IDSPRITEHEADER:\n\t\tMod_LoadSpriteModel( mod, buf, &loaded );\n\t\tbreak;\n\tcase IDALIASHEADER:\n\t\tMod_LoadAliasModel( mod, buf, &loaded );\n\t\tbreak;\n\tcase Q1BSP_VERSION:\n\tcase HLBSP_VERSION:\n\tcase QBSP2_VERSION:\n\t\tMod_LoadBrushModel( mod, buf, &loaded );\n\t\tbreak;\n\tdefault:\n\t\tMem_Free( buf );\n\t\tif( crash ) Host_Error( \"%s has unknown format\\n\", tempname );\n\t\telse Con_Printf( S_ERROR \"%s has unknown format\\n\", tempname );\n\t\treturn NULL;\n\t}\n\n\tif( loaded )\n\t{\n\t\tif( world.loading )\n\t\t\tSetBits( mod->flags, MODEL_WORLD ); // mark worldmodel\n\n\t\tif( Host_IsDedicated() )\n\t\t{\n\t\t\tif( svgame.physFuncs.Mod_ProcessUserData != NULL )\n\t\t\t{\n\t\t\t\t// let the server.dll load custom data\n\t\t\t\tsvgame.physFuncs.Mod_ProcessUserData( mod, true, buf );\n\t\t\t}\n\t\t\tloaded2 = true;\n\t\t}\n#if !XASH_DEDICATED\n\t\telse\n\t\t{\n\t\t\tloaded2 = ref.dllFuncs.Mod_ProcessRenderData( mod, true, buf );\n\t\t}\n#endif\n\t}\n\n\tif( mod->type == mod_alias )\n\t{\n\t\taliashdr_t *hdr = mod->cache.data;\n\t\tif( hdr ) // clean up temporary pointer after passing the alias model to the renderer\n\t\t\thdr->pposeverts = NULL;\n\t}\n\n\tif( !loaded || !loaded2 )\n\t{\n\t\tMod_FreeModel( mod );\n\t\tMem_Free( buf );\n\n\t\tif( crash ) Host_Error( \"Could not load model %s\\n\", tempname );\n\t\telse Con_Printf( S_ERROR \"Could not load model %s\\n\", tempname );\n\n\t\treturn NULL;\n\t}\n\n\tp = &mod_crcinfo[mod - mod_known];\n\tmod->needload = NL_PRESENT;\n\n\tif( FBitSet( p->flags, FCRC_SHOULD_CHECKSUM ))\n\t{\n\t\tCRC32_t\tcurrentCRC;\n\n\t\tCRC32_Init( &currentCRC );\n\t\tCRC32_ProcessBuffer( &currentCRC, buf, length );\n\t\tcurrentCRC = CRC32_Final( currentCRC );\n\n\t\tif( FBitSet( p->flags, FCRC_CHECKSUM_DONE ))\n\t\t{\n\t\t\tif( currentCRC != p->initialCRC )\n\t\t\t\tHost_Error( \"%s has a bad checksum\\n\", tempname );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSetBits( p->flags, FCRC_CHECKSUM_DONE );\n\t\t\tp->initialCRC = currentCRC;\n\t\t}\n\t}\n\tMem_Free( buf );\n\n\treturn mod;\n}\n\n/*\n==================\nMod_ForName\n\nLoads in a model for the given name\n==================\n*/\nmodel_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC )\n{\n\tmodel_t\t*mod;\n\n\tif( !COM_CheckString( name ))\n\t\treturn NULL;\n\n\tmod = Mod_FindName( name, trackCRC );\n\treturn Mod_LoadModel( mod, crash );\n}\n\n/*\n==================\nMod_PurgeStudioCache\n\nfree studio cache on change level\n==================\n*/\nstatic void Mod_PurgeStudioCache( void )\n{\n\tint\ti;\n\n\t// refresh hull data\n\tSetBits( r_showhull.flags, FCVAR_CHANGED );\n#if !XASH_DEDICATED\n\tMod_ReleaseHullPolygons();\n#endif\n\t// release previois map\n\tMod_FreeModel( mod_known );\t// world is stuck on slot #0 always\n\n\t// we should release all the world submodels\n\t// and clear studio sequences\n\tfor( i = 1; i < mod_numknown; i++ )\n\t{\n\t\tif( mod_known[i].type == mod_studio )\n\t\t\tmod_known[i].submodels = NULL;\n\t\tif( mod_known[i].name[0] == '*' )\n\t\t\tMod_FreeModel( &mod_known[i] );\n\t\tmod_known[i].needload = NL_UNREFERENCED;\n\t}\n\n\tMem_EmptyPool( com_studiocache );\n\tMod_ClearStudioCache();\n}\n\n/*\n==================\nMod_LoadWorld\n\nLoads in the map and all submodels\n==================\n*/\nmodel_t *Mod_LoadWorld( const char *name, qboolean preload )\n{\n\tmodel_t\t*pworld;\n\n\t// already loaded?\n\tif( !Q_stricmp( mod_known->name, name ))\n\t\treturn mod_known;\n\n\t// free sequence files on studiomodels\n\tMod_PurgeStudioCache();\n\n\t// load the newmap\n\tworld.loading = true;\n\tpworld = Mod_FindName( name, false );\n\tif( preload ) Mod_LoadModel( pworld, true );\n\tworld.loading = false;\n\n\tASSERT( pworld == mod_known );\n\n\treturn pworld;\n}\n\n/*\n==================\nMod_FreeUnused\n\nPurge all unused models\n==================\n*/\nvoid Mod_FreeUnused( void )\n{\n\tmodel_t\t*mod;\n\tint\ti;\n\n\t// never tries to release worldmodel\n\tfor( i = 1, mod = &mod_known[1]; i < mod_numknown; i++, mod++ )\n\t{\n\t\tif( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name ))\n\t\t\tMod_FreeModel( mod );\n\t}\n}\n\n/*\n===============================================================================\n\n\t\t\tMODEL ROUTINES\n\n===============================================================================\n*/\n/*\n===============\nMod_Calloc\n\n===============\n*/\nvoid *Mod_Calloc( int number, size_t size )\n{\n\tcache_user_t\t*cu;\n\n\tif( number <= 0 || size <= 0 ) return NULL;\n\tcu = (cache_user_t *)Mem_Calloc( com_studiocache, sizeof( cache_user_t ) + number * size );\n\tcu->data = (void *)cu; // make sure what cu->data is not NULL\n\n\treturn cu;\n}\n\n/*\n===============\nMod_CacheCheck\n\n===============\n*/\nvoid *Mod_CacheCheck( cache_user_t *c )\n{\n\treturn Cache_Check( com_studiocache, c );\n}\n\n/*\n===============\nMod_LoadCacheFile\n\n===============\n*/\nvoid Mod_LoadCacheFile( const char *filename, cache_user_t *cu )\n{\n\tchar\tmodname[MAX_QPATH];\n\tfs_offset_t\tsize;\n\tbyte\t*buf;\n\n\tAssert( cu != NULL );\n\n\tif( !COM_CheckString( filename ))\n\t\treturn;\n\n\tQ_strncpy( modname, filename, sizeof( modname ));\n\tCOM_FixSlashes( modname );\n\n\tbuf = FS_LoadFile( modname, &size, false );\n\tif( !buf || !size ) Host_Error( \"LoadCacheFile: ^1can't load %s^7\\n\", filename );\n\tcu->data = Mem_Malloc( com_studiocache, size );\n\tmemcpy( cu->data, buf, size );\n\tMem_Free( buf );\n}\n\n/*\n===============\nMod_AliasExtradata\n\n===============\n*/\nvoid *Mod_AliasExtradata( model_t *mod )\n{\n\tif( mod && mod->type == mod_alias )\n\t\treturn mod->cache.data;\n\treturn NULL;\n}\n\n/*\n===============\nMod_StudioExtradata\n\n===============\n*/\nvoid *Mod_StudioExtradata( model_t *mod )\n{\n\tif( mod && mod->type == mod_studio )\n\t\treturn mod->cache.data;\n\treturn NULL;\n}\n\n/*\n==================\nMod_ValidateCRC\n\n==================\n*/\nqboolean Mod_ValidateCRC( const char *name, CRC32_t crc )\n{\n\tmodel_info_t\t*p;\n\tmodel_t\t\t*mod;\n\n\tmod = Mod_FindName( name, true );\n\tp = &mod_crcinfo[mod - mod_known];\n\n\tif( !FBitSet( p->flags, FCRC_CHECKSUM_DONE ))\n\t\treturn true;\n\tif( p->initialCRC == crc )\n\t\treturn true;\n\treturn false;\n}\n\n/*\n==================\nMod_NeedCRC\n\n==================\n*/\nvoid Mod_NeedCRC( const char *name, qboolean needCRC )\n{\n\tmodel_t\t\t*mod;\n\tmodel_info_t\t*p;\n\n\tmod = Mod_FindName( name, true );\n\tp = &mod_crcinfo[mod - mod_known];\n\n\tif( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM );\n\telse ClearBits( p->flags, FCRC_SHOULD_CHECKSUM );\n}\n\n#if XASH_ENGINE_TESTS\n\nstatic const uint8_t *fuzz_data;\nstatic size_t fuzz_size;\n\nstatic byte *Fuzz_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n{\n\tbyte *buf = Mem_Malloc( host.mempool, fuzz_size );\n\tmemcpy( buf, fuzz_data, fuzz_size );\n\t*filesizeptr = fuzz_size;\n\treturn buf;\n}\n\nint EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size );\nint EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size )\n{\n\tmodel_t mod = { .name = \"test\", .needload = NL_NEEDS_LOADED };\n\n\tMemory_Init();\n\n\thost.type = HOST_DEDICATED;\n\thost.mempool = Mem_AllocPool( \"fuzzing pool\" );\n\tfuzz_data = Data;\n\tfuzz_size = Size;\n\trefState.draw_surfaces = NULL;\n\n\tg_fsapi.LoadFile = Fuzz_LoadFile;\n\n\tif( Mod_LoadModel( &mod, false ) && mod.mempool )\n\t\tMem_FreePool( &mod.mempool );\n\n\tMem_FreePool( &host.mempool );\n\n\treturn 0;\n}\n\n#endif // XASH_ENGINE_TESTS\n"
  },
  {
    "path": "engine/common/munge.c",
    "content": "/*\nmunge.c - protocol mangling for GoldSrc\nCopyright (C) ReHLDS developers\nCopyright (C) 2023 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n\nstatic const byte mungify_table[] =\n{\n\t0x7A, 0x64, 0x05, 0xF1, 0x1B, 0x9B, 0xA0, 0xB5,\n\t0xCA, 0xED, 0x61, 0x0D, 0x4A, 0xDF, 0x8E, 0xC7,\n};\n\nstatic const byte mungify_table2[] =\n{\n\t0x05, 0x61, 0x7A, 0xED,\t0x1B, 0xCA, 0x0D, 0x9B,\n\t0x4A, 0xF1, 0x64, 0xC7,\t0xB5, 0x8E, 0xDF, 0xA0,\n};\n\nstatic const byte mungify_table3[] =\n{\n\t0x20, 0x07, 0x13, 0x61,\t0x03, 0x45, 0x17, 0x72,\n\t0x0A, 0x2D, 0x48, 0x0C,\t0x4A, 0x12, 0xA9, 0xB5,\n};\n\nstatic uint COM_SwapLong( uint c )\n{\n\treturn ((( c >> 0  ) & 0xFF) << 24 ) +\n\t\t   ((( c >> 8  ) & 0xFF) << 16 ) +\n\t\t   ((( c >> 16 ) & 0xFF) << 8  ) +\n\t\t   ((( c >> 24 ) & 0xFF) << 0  );\n}\n\nstatic void COM_GenericMunge( byte *data, const size_t len, const int seq, const byte *table, const qboolean reverse )\n{\n\tconst size_t mungelen = len / 4;\n\tint i;\n\n\tfor( i = 0; i < mungelen; i++ )\n\t{\n\t\tuint32_t c;\n\t\tvoid *pc = &data[i * 4];\n\t\tbyte *p;\n\t\tint j;\n\n\t\tmemcpy( &c, pc, sizeof( c ));\n\t\tc ^= seq;\n\t\tif( !reverse )\n\t\t\tc = COM_SwapLong( c );\n\n\t\tp = (byte *)&c;\n\t\tfor( j = 0; j < 4; j++ )\n\t\t\t*p++ ^= (0xa5 | (j << j) | j | table[(i + j) & 0x0f]);\n\n\t\tif( reverse )\n\t\t\tc = COM_SwapLong( c );\n\t\tc ^= ~seq;\n\t\tmemcpy( pc, &c, sizeof( c ));\n\t}\n}\n\n// Anti-proxy/aimbot obfuscation code\n// COM_UnMunge should reversably fixup the data\nvoid COM_Munge( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table, false );\n}\n\nvoid COM_UnMunge( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table, true );\n}\n\nvoid COM_Munge2( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table2, false );\n}\n\nvoid COM_UnMunge2( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table2, true );\n}\n\nvoid COM_Munge3( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table3, false );\n}\n\nvoid COM_UnMunge3( byte *data, size_t len, int seq )\n{\n\tCOM_GenericMunge( data, len, seq, mungify_table3, true );\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nvoid Test_RunMunge( void )\n{\n\tconst char *msg = \"0123456789qwertyuiopasdfghjklzxcvbnmaa\";\n\tconst char *expected[][3] =\n\t{\n\t{\n\t\t\"\\x33\\x2a\\x61\\x30\\x2d\\x6e\\x35\\x74\\x2d\\x79\\x79\\x78\\x73\\x34\\x32\\x25\\x30\\x2f\\x39\\x35\\x26\\x3c\\x33\\x61\\x31\\x22\\x78\\x67\\x29\\x68\\x6a\\x6c\\x7d\\x7e\\x72\\x36\\x61\\x61\",\n\t\t\"\\x69\\x2a\\x31\\x30\\x2d\\x36\\x25\\x74\\x77\\x61\\x79\\x38\\x6b\\x34\\x62\\x25\\x30\\x7f\\x39\\x35\\x76\\x34\\x33\\x61\\x39\\x2a\\x78\\x67\\x23\\x68\\x7a\\x6c\\x7d\\x66\\x72\\x76\\x61\\x61\",\n\t\t\"\\x69\\x6a\\x71\\x30\\x6f\\x7e\\x25\\x74\\x3f\\x69\\x69\\x38\\x63\\x2c\\x62\\x25\\x28\\x77\\x29\\x75\\x7c\\x2c\\x73\\x21\\x23\\x62\\x38\\x27\\x6b\\x28\\x2a\\x6c\\x3d\\x3e\\x72\\x36\\x61\\x61\",\n\t}, {\n\t\t\"\\x32\\x2a\\x61\\x31\\x2c\\x6e\\x35\\x75\\x2c\\x79\\x79\\x79\\x72\\x34\\x32\\x24\\x31\\x2f\\x39\\x34\\x27\\x3c\\x33\\x60\\x30\\x22\\x78\\x66\\x28\\x68\\x6a\\x6d\\x7c\\x7e\\x72\\x37\\x61\\x61\",\n\t\t\"\\x68\\x2a\\x31\\x31\\x2c\\x36\\x25\\x75\\x76\\x61\\x79\\x39\\x6a\\x34\\x62\\x24\\x31\\x7f\\x39\\x34\\x77\\x34\\x33\\x60\\x38\\x2a\\x78\\x66\\x22\\x68\\x7a\\x6d\\x7c\\x66\\x72\\x77\\x61\\x61\",\n\t\t\"\\x68\\x6a\\x71\\x31\\x6e\\x7e\\x25\\x75\\x3e\\x69\\x69\\x39\\x62\\x2c\\x62\\x24\\x29\\x77\\x29\\x74\\x7d\\x2c\\x73\\x20\\x22\\x62\\x38\\x26\\x6a\\x28\\x2a\\x6d\\x3c\\x3e\\x72\\x37\\x61\\x61\",\n\t}\n\t};\n\tstring buf;\n\tsize_t msglen = Q_strlen( msg ) + 1;\n\tint i;\n\n\tQ_strncpy( buf, msg, msglen );\n\n\tfor( i = 0; i < 0xFF; i++ )\n\t{\n\t\tCOM_Munge( buf, msglen, i );\n\t\tif( i < sizeof( expected ) / sizeof( expected[0] ))\n\t\t{\n\t\t\t//for( int j = 0; j < msglen; j++ )\n\t\t\t//\tprintf( \"\\\\x%02x\", buf[j] );\n\t\t\t//printf( \"\\n\" );\n\t\t\tTASSERT( !memcmp( buf, expected[i][0], msglen ));\n\t\t}\n\t\tCOM_UnMunge( buf, msglen, i );\n\n\t\tTASSERT( !Q_strcmp( buf, msg ));\n\n\t\tCOM_Munge2( buf, msglen, i );\n\t\tif( i < sizeof( expected ) / sizeof( expected[0] ))\n\t\t{\n\t\t\tTASSERT( !memcmp( buf, expected[i][1], msglen ));\n\t\t}\n\t\tCOM_UnMunge2( buf, msglen, i );\n\n\t\tTASSERT( !Q_strcmp( buf, msg ));\n\n\t\tCOM_Munge3( buf, msglen, i );\n\t\tif( i < sizeof( expected ) / sizeof( expected[0] ))\n\t\t{\n\t\t\tTASSERT( !memcmp( buf, expected[i][2], msglen ));\n\t\t}\n\t\tCOM_UnMunge3( buf, msglen, i );\n\n\t\tTASSERT( !Q_strcmp( buf, msg ));\n\t}\n}\n#endif\n"
  },
  {
    "path": "engine/common/net_buffer.c",
    "content": "/*\nnet_buffer.c - network bitbuffer io functions\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"protocol.h\"\n#include \"net_buffer.h\"\n#include \"xash3d_mathlib.h\"\n\nstatic const uint32_t BitWriteMasks[32][32] =\n{ {\n\t0xfffffffe, 0xfffffffc, 0xfffffff8, 0xfffffff0, 0xffffffe0, 0xffffffc0, 0xffffff80, 0xffffff00,\n\t0xfffffe00, 0xfffffc00, 0xfffff800, 0xfffff000, 0xffffe000, 0xffffc000, 0xffff8000, 0xffff0000,\n\t0xfffe0000, 0xfffc0000, 0xfff80000, 0xfff00000, 0xffe00000, 0xffc00000, 0xff800000, 0xff000000,\n\t0xfe000000, 0xfc000000, 0xf8000000, 0xf0000000, 0xe0000000, 0xc0000000, 0x80000000, 0x00000000,\n}, {\n\t0xfffffffd, 0xfffffff9, 0xfffffff1, 0xffffffe1, 0xffffffc1, 0xffffff81, 0xffffff01, 0xfffffe01,\n\t0xfffffc01, 0xfffff801, 0xfffff001, 0xffffe001, 0xffffc001, 0xffff8001, 0xffff0001, 0xfffe0001,\n\t0xfffc0001, 0xfff80001, 0xfff00001, 0xffe00001, 0xffc00001, 0xff800001, 0xff000001, 0xfe000001,\n\t0xfc000001, 0xf8000001, 0xf0000001, 0xe0000001, 0xc0000001, 0x80000001, 0x00000001, 0x00000001,\n}, {\n\t0xfffffffb, 0xfffffff3, 0xffffffe3, 0xffffffc3, 0xffffff83, 0xffffff03, 0xfffffe03, 0xfffffc03,\n\t0xfffff803, 0xfffff003, 0xffffe003, 0xffffc003, 0xffff8003, 0xffff0003, 0xfffe0003, 0xfffc0003,\n\t0xfff80003, 0xfff00003, 0xffe00003, 0xffc00003, 0xff800003, 0xff000003, 0xfe000003, 0xfc000003,\n\t0xf8000003, 0xf0000003, 0xe0000003, 0xc0000003, 0x80000003, 0x00000003, 0x00000003, 0x00000003,\n}, {\n\t0xfffffff7, 0xffffffe7, 0xffffffc7, 0xffffff87, 0xffffff07, 0xfffffe07, 0xfffffc07, 0xfffff807,\n\t0xfffff007, 0xffffe007, 0xffffc007, 0xffff8007, 0xffff0007, 0xfffe0007, 0xfffc0007, 0xfff80007,\n\t0xfff00007, 0xffe00007, 0xffc00007, 0xff800007, 0xff000007, 0xfe000007, 0xfc000007, 0xf8000007,\n\t0xf0000007, 0xe0000007, 0xc0000007, 0x80000007, 0x00000007, 0x00000007, 0x00000007, 0x00000007,\n}, {\n\t0xffffffef, 0xffffffcf, 0xffffff8f, 0xffffff0f, 0xfffffe0f, 0xfffffc0f, 0xfffff80f, 0xfffff00f,\n\t0xffffe00f, 0xffffc00f, 0xffff800f, 0xffff000f, 0xfffe000f, 0xfffc000f, 0xfff8000f, 0xfff0000f,\n\t0xffe0000f, 0xffc0000f, 0xff80000f, 0xff00000f, 0xfe00000f, 0xfc00000f, 0xf800000f, 0xf000000f,\n\t0xe000000f, 0xc000000f, 0x8000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f,\n}, {\n\t0xffffffdf, 0xffffff9f, 0xffffff1f, 0xfffffe1f, 0xfffffc1f, 0xfffff81f, 0xfffff01f, 0xffffe01f,\n\t0xffffc01f, 0xffff801f, 0xffff001f, 0xfffe001f, 0xfffc001f, 0xfff8001f, 0xfff0001f, 0xffe0001f,\n\t0xffc0001f, 0xff80001f, 0xff00001f, 0xfe00001f, 0xfc00001f, 0xf800001f, 0xf000001f, 0xe000001f,\n\t0xc000001f, 0x8000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f,\n}, {\n\t0xffffffbf, 0xffffff3f, 0xfffffe3f, 0xfffffc3f, 0xfffff83f, 0xfffff03f, 0xffffe03f, 0xffffc03f,\n\t0xffff803f, 0xffff003f, 0xfffe003f, 0xfffc003f, 0xfff8003f, 0xfff0003f, 0xffe0003f, 0xffc0003f,\n\t0xff80003f, 0xff00003f, 0xfe00003f, 0xfc00003f, 0xf800003f, 0xf000003f, 0xe000003f, 0xc000003f,\n\t0x8000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f,\n}, {\n\t0xffffff7f, 0xfffffe7f, 0xfffffc7f, 0xfffff87f, 0xfffff07f, 0xffffe07f, 0xffffc07f, 0xffff807f,\n\t0xffff007f, 0xfffe007f, 0xfffc007f, 0xfff8007f, 0xfff0007f, 0xffe0007f, 0xffc0007f, 0xff80007f,\n\t0xff00007f, 0xfe00007f, 0xfc00007f, 0xf800007f, 0xf000007f, 0xe000007f, 0xc000007f, 0x8000007f,\n\t0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f,\n}, {\n\t0xfffffeff, 0xfffffcff, 0xfffff8ff, 0xfffff0ff, 0xffffe0ff, 0xffffc0ff, 0xffff80ff, 0xffff00ff,\n\t0xfffe00ff, 0xfffc00ff, 0xfff800ff, 0xfff000ff, 0xffe000ff, 0xffc000ff, 0xff8000ff, 0xff0000ff,\n\t0xfe0000ff, 0xfc0000ff, 0xf80000ff, 0xf00000ff, 0xe00000ff, 0xc00000ff, 0x800000ff, 0x000000ff,\n\t0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,\n}, {\n\t0xfffffdff, 0xfffff9ff, 0xfffff1ff, 0xffffe1ff, 0xffffc1ff, 0xffff81ff, 0xffff01ff, 0xfffe01ff,\n\t0xfffc01ff, 0xfff801ff, 0xfff001ff, 0xffe001ff, 0xffc001ff, 0xff8001ff, 0xff0001ff, 0xfe0001ff,\n\t0xfc0001ff, 0xf80001ff, 0xf00001ff, 0xe00001ff, 0xc00001ff, 0x800001ff, 0x000001ff, 0x000001ff,\n\t0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff,\n}, {\n\t0xfffffbff, 0xfffff3ff, 0xffffe3ff, 0xffffc3ff, 0xffff83ff, 0xffff03ff, 0xfffe03ff, 0xfffc03ff,\n\t0xfff803ff, 0xfff003ff, 0xffe003ff, 0xffc003ff, 0xff8003ff, 0xff0003ff, 0xfe0003ff, 0xfc0003ff,\n\t0xf80003ff, 0xf00003ff, 0xe00003ff, 0xc00003ff, 0x800003ff, 0x000003ff, 0x000003ff, 0x000003ff,\n\t0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff,\n}, {\n\t0xfffff7ff, 0xffffe7ff, 0xffffc7ff, 0xffff87ff, 0xffff07ff, 0xfffe07ff, 0xfffc07ff, 0xfff807ff,\n\t0xfff007ff, 0xffe007ff, 0xffc007ff, 0xff8007ff, 0xff0007ff, 0xfe0007ff, 0xfc0007ff, 0xf80007ff,\n\t0xf00007ff, 0xe00007ff, 0xc00007ff, 0x800007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff,\n\t0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff,\n}, {\n\t0xffffefff, 0xffffcfff, 0xffff8fff, 0xffff0fff, 0xfffe0fff, 0xfffc0fff, 0xfff80fff, 0xfff00fff,\n\t0xffe00fff, 0xffc00fff, 0xff800fff, 0xff000fff, 0xfe000fff, 0xfc000fff, 0xf8000fff, 0xf0000fff,\n\t0xe0000fff, 0xc0000fff, 0x80000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff,\n\t0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff,\n}, {\n\t0xffffdfff, 0xffff9fff, 0xffff1fff, 0xfffe1fff, 0xfffc1fff, 0xfff81fff, 0xfff01fff, 0xffe01fff,\n\t0xffc01fff, 0xff801fff, 0xff001fff, 0xfe001fff, 0xfc001fff, 0xf8001fff, 0xf0001fff, 0xe0001fff,\n\t0xc0001fff, 0x80001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff,\n\t0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff,\n}, {\n\t0xffffbfff, 0xffff3fff, 0xfffe3fff, 0xfffc3fff, 0xfff83fff, 0xfff03fff, 0xffe03fff, 0xffc03fff,\n\t0xff803fff, 0xff003fff, 0xfe003fff, 0xfc003fff, 0xf8003fff, 0xf0003fff, 0xe0003fff, 0xc0003fff,\n\t0x80003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff,\n\t0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff,\n}, {\n\t0xffff7fff, 0xfffe7fff, 0xfffc7fff, 0xfff87fff, 0xfff07fff, 0xffe07fff, 0xffc07fff, 0xff807fff,\n\t0xff007fff, 0xfe007fff, 0xfc007fff, 0xf8007fff, 0xf0007fff, 0xe0007fff, 0xc0007fff, 0x80007fff,\n\t0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff,\n\t0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff,\n}, {\n\t0xfffeffff, 0xfffcffff, 0xfff8ffff, 0xfff0ffff, 0xffe0ffff, 0xffc0ffff, 0xff80ffff, 0xff00ffff,\n\t0xfe00ffff, 0xfc00ffff, 0xf800ffff, 0xf000ffff, 0xe000ffff, 0xc000ffff, 0x8000ffff, 0x0000ffff,\n\t0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,\n\t0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,\n}, {\n\t0xfffdffff, 0xfff9ffff, 0xfff1ffff, 0xffe1ffff, 0xffc1ffff, 0xff81ffff, 0xff01ffff, 0xfe01ffff,\n\t0xfc01ffff, 0xf801ffff, 0xf001ffff, 0xe001ffff, 0xc001ffff, 0x8001ffff, 0x0001ffff, 0x0001ffff,\n\t0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff,\n\t0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff,\n}, {\n\t0xfffbffff, 0xfff3ffff, 0xffe3ffff, 0xffc3ffff, 0xff83ffff, 0xff03ffff, 0xfe03ffff, 0xfc03ffff,\n\t0xf803ffff, 0xf003ffff, 0xe003ffff, 0xc003ffff, 0x8003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff,\n\t0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff,\n\t0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff,\n}, {\n\t0xfff7ffff, 0xffe7ffff, 0xffc7ffff, 0xff87ffff, 0xff07ffff, 0xfe07ffff, 0xfc07ffff, 0xf807ffff,\n\t0xf007ffff, 0xe007ffff, 0xc007ffff, 0x8007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff,\n\t0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff,\n\t0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff,\n}, {\n\t0xffefffff, 0xffcfffff, 0xff8fffff, 0xff0fffff, 0xfe0fffff, 0xfc0fffff, 0xf80fffff, 0xf00fffff,\n\t0xe00fffff, 0xc00fffff, 0x800fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff,\n\t0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff,\n\t0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff,\n}, {\n\t0xffdfffff, 0xff9fffff, 0xff1fffff, 0xfe1fffff, 0xfc1fffff, 0xf81fffff, 0xf01fffff, 0xe01fffff,\n\t0xc01fffff, 0x801fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff,\n\t0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff,\n\t0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff,\n}, {\n\t0xffbfffff, 0xff3fffff, 0xfe3fffff, 0xfc3fffff, 0xf83fffff, 0xf03fffff, 0xe03fffff, 0xc03fffff,\n\t0x803fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff,\n\t0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff,\n\t0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff,\n}, {\n\t0xff7fffff, 0xfe7fffff, 0xfc7fffff, 0xf87fffff, 0xf07fffff, 0xe07fffff, 0xc07fffff, 0x807fffff,\n\t0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff,\n\t0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff,\n\t0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff,\n}, {\n\t0xfeffffff, 0xfcffffff, 0xf8ffffff, 0xf0ffffff, 0xe0ffffff, 0xc0ffffff, 0x80ffffff, 0x00ffffff,\n\t0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,\n\t0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,\n\t0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,\n}, {\n\t0xfdffffff, 0xf9ffffff, 0xf1ffffff, 0xe1ffffff, 0xc1ffffff, 0x81ffffff, 0x01ffffff, 0x01ffffff,\n\t0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff,\n\t0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff,\n\t0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff,\n}, {\n\t0xfbffffff, 0xf3ffffff, 0xe3ffffff, 0xc3ffffff, 0x83ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff,\n\t0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff,\n\t0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff,\n\t0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff,\n}, {\n\t0xf7ffffff, 0xe7ffffff, 0xc7ffffff, 0x87ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff,\n\t0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff,\n\t0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff,\n\t0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff,\n}, {\n\t0xefffffff, 0xcfffffff, 0x8fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff,\n\t0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff,\n\t0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff,\n\t0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff,\n}, {\n\t0xdfffffff, 0x9fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,\n\t0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,\n\t0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,\n\t0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,\n}, {\n\t0xbfffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff,\n\t0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff,\n\t0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff,\n\t0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff,\n}, {\n\t0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,\n\t0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,\n\t0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,\n\t0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,\n} };\n\nstatic const uint32_t ExtraMasks[32] =\n{\n\t0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f,\n\t0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff,\n\t0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,\n\t0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff,\n};\n\nconst char *const svc_strings[svc_lastmsg+1] =\n{\n\t\"svc_bad\",\n\t\"svc_nop\",\n\t\"svc_disconnect\",\n\t\"svc_event\",\n\t\"svc_changing\",\n\t\"svc_setview\",\n\t\"svc_sound\",\n\t\"svc_time\",\n\t\"svc_print\",\n\t\"svc_stufftext\",\n\t\"svc_setangle\",\n\t\"svc_serverdata\",\n\t\"svc_lightstyle\",\n\t\"svc_updateuserinfo\",\n\t\"svc_deltatable\",\n\t\"svc_clientdata\",\n\t\"svc_resource\",\n\t\"svc_pings\",\n\t\"svc_particle\",\n\t\"svc_restoresound\",\n\t\"svc_spawnstatic\",\n\t\"svc_event_reliable\",\n\t\"svc_spawnbaseline\",\n\t\"svc_temp_entity\",\n\t\"svc_setpause\",\n\t\"svc_signonnum\",\n\t\"svc_centerprint\",\n\t\"svc_unused27\",\n\t\"svc_unused28\",\n\t\"svc_unused29\",\n\t\"svc_intermission\",\n\t\"svc_finale\",\n\t\"svc_cdtrack\",\n\t\"svc_restore\",\n\t\"svc_cutscene\",\n\t\"svc_weaponanim\",\n\t\"svc_bspdecal\",\n\t\"svc_roomtype\",\n\t\"svc_addangle\",\n\t\"svc_usermessage\",\n\t\"svc_packetentities\",\n\t\"svc_deltapacketentities\",\n\t\"svc_choke\",\n\t\"svc_resourcelist\",\n\t\"svc_deltamovevars\",\n\t\"svc_resourcerequest\",\n\t\"svc_customization\",\n\t\"svc_crosshairangle\",\n\t\"svc_soundfade\",\n\t\"svc_filetxferfailed\",\n\t\"svc_hltv\",\n\t\"svc_director\",\n\t\"svc_voiceinit\",\n\t\"svc_voicedata\",\n\t\"svc_unused54\",\n\t\"svc_unused55\",\n\t\"svc_resourcelocation\",\n\t\"svc_querycvarvalue\",\n\t\"svc_querycvarvalue2\",\n\t\"svc_exec\",\n};\n\nconst char *const svc_legacy_strings[svc_lastmsg+1] =\n{\n\t[svc_legacy_changing] = \"svc_legacy_changing\",\n\t[svc_legacy_ambientsound] = \"svc_legacy_ambientsound\",\n\t[svc_legacy_soundindex] = \"svc_legacy_soundindex\",\n\t[svc_legacy_ambientsound] = \"svc_legacy_ambientsound\",\n\t[svc_legacy_modelindex] = \"svc_legacy_modelindex\",\n\t[svc_legacy_eventindex] = \"svc_legacy_eventindex\",\n\t[svc_legacy_chokecount] = \"svc_legacy_chokecount\",\n};\n\nconst char *const svc_goldsrc_strings[svc_lastmsg+1] =\n{\n\t[svc_goldsrc_version] = \"svc_goldsrc_version\",\n\t[svc_goldsrc_stopsound] = \"svc_goldsrc_stopsound\",\n\t[svc_goldsrc_damage] = \"svc_goldsrc_damage\",\n\t[svc_goldsrc_killedmonster] = \"svc_goldsrc_killedmonster\",\n\t[svc_goldsrc_foundsecret] = \"svc_goldsrc_foundsecret\",\n\t[svc_goldsrc_spawnstaticsound] = \"svc_goldsrc_spawnstaticsound\",\n\t[svc_goldsrc_decalname] = \"svc_goldsrc_decalname\",\n\t[svc_goldsrc_sendextrainfo] = \"svc_goldsrc_sendextrainfo\",\n\t[svc_goldsrc_timescale] = \"svc_goldsrc_timescale\",\n};\n\nconst char *const svc_quake_strings[svc_lastmsg+1] =\n{\n\t[svc_updatestat] = \"svc_quake_updatestat\",\n\t[svc_version] = \"svc_quake_version\",\n\t[svc_updatename] = \"svc_quake_updatename\",\n\t[svc_updatefrags] = \"svc_quake_updatefrags\",\n\t[svc_stopsound] = \"svc_quake_stopsound\",\n\t[svc_updatecolors] = \"svc_quake_updatecolors\",\n\t[svc_damage] = \"svc_quake_damage\",\n\t[svc_spawnbinary] = \"svc_quake_spawnbinary\",\n\t[svc_killedmonster] = \"svc_quake_killedmonster\",\n\t[svc_foundsecret] = \"svc_quake_foundsecret\",\n\t[svc_spawnstaticsound] = \"svc_quake_spawnstaticsound\",\n\t[svc_sellscreen] = \"svc_quake_sellscreen\",\n\t[svc_showlmp] = \"svc_quake_showlmp\",\n\t[svc_hidelmp] = \"svc_quake_hidelmp\",\n\t[svc_skybox] = \"svc_quake_skybox\",\n\t[svc_skyboxsize] = \"svc_quake_skyboxsize\",\n\t[svc_fog] = \"svc_quake_fog\",\n};\n\nvoid MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits )\n{\n\tint\tnBitsLeft = numbits;\n\tint\tiCurBit = sb->iCurBit;\n\tuint\tiDWord = iCurBit >> 5;\t// Mask in a dword.\n\tuint32_t\tiCurBitMasked;\n\tint\tnBitsWritten;\n\n\tAssert( numbits >= 1 && numbits <= 32 );\n\n\t// bounds checking..\n\tif( MSG_Overflow( sb, numbits ))\n\t{\n\t\tsb->iCurBit = sb->nDataBits;\n\t\treturn;\n\t}\n\n\tiCurBitMasked = iCurBit & 31;\n\t((uint32_t *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];\n\t((uint32_t *)sb->pData)[iDWord] |= curData << iCurBitMasked;\n\n\t// did it span a dword?\n\tnBitsWritten = 32 - iCurBitMasked;\n\n\tif( nBitsWritten < nBitsLeft )\n\t{\n\t\tnBitsLeft -= nBitsWritten;\n\t\tiCurBit += nBitsWritten;\n\t\tcurData >>= nBitsWritten;\n\n\t\tiCurBitMasked = iCurBit & 31;\n\t\t((uint32_t *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1];\n\t\t((uint32_t *)sb->pData)[iDWord+1] |= curData << iCurBitMasked;\n\t}\n\tsb->iCurBit += numbits;\n}\n\n/*\n=======================\nMSG_WriteSBitLong\n\nsign bit comes first\n=======================\n*/\nvoid MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits )\n{\n\t// do we have a valid # of bits to encode with?\n\tAssert( numbits >= 1 && numbits <= 32 );\n\n\tif( sb->iAlternateSign )\n\t{\n\t\tMSG_WriteOneBit( sb, data < 0 ? 1 : 0 );\n\t\tMSG_WriteUBitLong( sb, (uint)abs( data ), numbits - 1 );\n\t}\n\telse\n\t{\n\t\tif( data < 0 )\n\t\t{\n\t\t\tMSG_WriteUBitLong( sb, (uint)( 0x80000000 + data ), numbits - 1 );\n\t\t\tMSG_WriteOneBit( sb, 1 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_WriteUBitLong( sb, (uint)data, numbits - 1 );\n\t\t\tMSG_WriteOneBit( sb, 0 );\n\t\t}\n\t}\n}\n\nvoid MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned )\n{\n\tif( bSigned )\n\t\tMSG_WriteSBitLong( sb, (int)data, numbits );\n\telse MSG_WriteUBitLong( sb, data, numbits );\n}\n\nqboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits )\n{\n\tbyte\t*pOut = (byte *)pData;\n\tint\tnBitsLeft = nBits;\n\n\t// get output dword-aligned.\n\twhile((( uintptr_t )pOut & 3 ) != 0 && nBitsLeft >= 8 )\n\t{\n\t\tMSG_WriteUBitLong( sb, *pOut, 8 );\n\n\t\tnBitsLeft -= 8;\n\t\t++pOut;\n\t}\n\n\t// read dwords.\n\twhile( nBitsLeft >= 32 )\n\t{\n\t\tMSG_WriteUBitLong( sb, *(( uint32_t *)pOut ), 32 );\n\n\t\tpOut += sizeof( uint32_t );\n\t\tnBitsLeft -= 32;\n\t}\n\n\t// read the remaining bytes.\n\twhile( nBitsLeft >= 8 )\n\t{\n\t\tMSG_WriteUBitLong( sb, *pOut, 8 );\n\n\t\tnBitsLeft -= 8;\n\t\t++pOut;\n\t}\n\n\t// Read the remaining bits.\n\tif( nBitsLeft )\n\t{\n\t\tMSG_WriteUBitLong( sb, *pOut, nBitsLeft );\n\t}\n\n\treturn !sb->bOverflow;\n}\n\nvoid MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits )\n{\n\tconst uint shift = ( 1 << numbits );\n\tconst uint mask = shift - 1;\n\tint\td;\n\n\t// clamp the angle before receiving\n\tfAngle = fmod( fAngle, 360.0f );\n\tif( fAngle < 0 ) fAngle += 360.0f;\n\n\td = (int)(( fAngle * shift ) / 360.0f );\n\td &= mask;\n\n\tMSG_WriteUBitLong( sb, (uint)d, numbits );\n}\n\nvoid MSG_WriteCoord( sizebuf_t *sb, float val )\n{\n\t// g-cont. we loose precision here but keep old size of coord variable!\n\tif( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))\n\t\tMSG_WriteShort( sb, Q_rint( val ));\n\telse MSG_WriteShort( sb, (int)( val * 8.0f ));\n}\n\nvoid MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa )\n{\n\tMSG_WriteCoord( sb, fa[0] );\n\tMSG_WriteCoord( sb, fa[1] );\n\tMSG_WriteCoord( sb, fa[2] );\n}\n\nvoid MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa )\n{\n\tMSG_WriteBitAngle( sb, fa[0], 16 );\n\tMSG_WriteBitAngle( sb, fa[1], 16 );\n\tMSG_WriteBitAngle( sb, fa[2], 16 );\n}\n\nvoid MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name )\n{\n\tif( unlikely( net_send_debug.value ))\n\t{\n\t\tif( name != NULL )\n\t\t{\n\t\t\t// get custom name\n\t\t\tCon_Printf( \"^1sv^7 (%d) write: %s\\n\", sb->iCurBit, name );\n\t\t}\n\t\telse if( type == NS_SERVER )\n\t\t{\n\t\t\tif( cmd >= 0 && cmd <= svc_lastmsg )\n\t\t\t{\n\t\t\t\t// get engine message name\n\t\t\t\tCon_Printf( \"^1sv^7 (%d) write: %s\\n\", sb->iCurBit, svc_strings[cmd] );\n\t\t\t}\n\t\t}\n\t\telse if( type == NS_CLIENT )\n\t\t{\n\t\t\tif( cmd >= 0 && cmd <= clc_lastmsg && cmd != clc_nop )\n\t\t\t{\n\t\t\t\tCon_Printf( \"^1cl^7 (%d) write: %s\\n\", sb->iCurBit, clc_strings[cmd] );\n\t\t\t}\n\t\t}\n\t}\n\n\tMSG_WriteUBitLong( sb, cmd, sizeof( uint8_t ) << 3 );\n}\n\nvoid MSG_WriteChar( sizebuf_t *sb, int val )\n{\n\tMSG_WriteSBitLong( sb, val, sizeof( int8_t ) << 3 );\n}\n\nvoid MSG_WriteByte( sizebuf_t *sb, int val )\n{\n\tMSG_WriteUBitLong( sb, val, sizeof( uint8_t ) << 3 );\n}\n\nvoid MSG_WriteShort( sizebuf_t *sb, int val )\n{\n\tMSG_WriteSBitLong( sb, val, sizeof( int16_t ) << 3 );\n}\n\nvoid MSG_WriteWord( sizebuf_t *sb, int val )\n{\n\tMSG_WriteUBitLong( sb, val, sizeof( uint16_t ) << 3 );\n}\n\nvoid MSG_WriteLong( sizebuf_t *sb, int val )\n{\n\tMSG_WriteSBitLong( sb, val, sizeof( int32_t ) << 3 );\n}\n\nvoid MSG_WriteDword( sizebuf_t *sb, uint val )\n{\n\tMSG_WriteUBitLong( sb, val, sizeof( uint32_t ) << 3 );\n}\n\nvoid MSG_WriteFloat( sizebuf_t *sb, float val )\n{\n\tMSG_WriteBits( sb, &val, sizeof( val ) << 3 );\n}\n\nqboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes )\n{\n\treturn MSG_WriteBits( sb, pBuf, nBytes << 3 );\n}\n\nqboolean MSG_WriteString( sizebuf_t *sb, const char *pStr )\n{\n\tint len = Q_strlen( pStr );\n\n\tif( len )\n\t\tMSG_WriteBytes( sb, pStr, len + 1 );\n\telse MSG_WriteByte( sb, 0 );\n\n\treturn !sb->bOverflow;\n}\n\nqboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... )\n{\n\tva_list va;\n\tint len;\n\tchar buf[MAX_VA_STRING];\n\n\tva_start( va, format );\n\tlen = Q_vsnprintf( buf, sizeof( buf ), format, va );\n\tva_end( va );\n\n\tif( len < 0 )\n\t{\n\t\tHost_Error( \"%s: snprintf overflow!\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tMSG_WriteBytes( sb, buf, len + 1 );\n\n\treturn !sb->bOverflow;\n}\n\nint MSG_ReadOneBit( sizebuf_t *sb )\n{\n\tif( !MSG_Overflow( sb, 1 ))\n\t{\n\t\tint value = sb->pData[sb->iCurBit >> 3] & (1 << ( sb->iCurBit & 7 ));\n\t\tsb->iCurBit++;\n\t\treturn !!value;\n\t}\n\treturn 0;\n}\n\nuint MSG_ReadUBitLong( sizebuf_t *sb, int numbits )\n{\n\tint\tidword1;\n\tuint\tdword1, ret;\n\n\tif( numbits == 8 )\n\t{\n\t\tint leftBits = MSG_GetNumBitsLeft( sb );\n\n\t\tif( leftBits >= 0 && leftBits < 8 )\n\t\t\treturn 0;\t// end of message\n\t}\n\n\tif( MSG_Overflow( sb, numbits ))\n\t{\n\t\tsb->iCurBit = sb->nDataBits;\n\t\treturn 0;\n\t}\n\n\tAssert( numbits > 0 && numbits <= 32 );\n\n\t// Read the current dword.\n\tidword1 = sb->iCurBit >> 5;\n\tdword1 = ((uint *)sb->pData)[idword1];\n\tdword1 >>= ( sb->iCurBit & 31 );\t// get the bits we're interested in.\n\n\tsb->iCurBit += numbits;\n\tret = dword1;\n\n\t// Does it span this dword?\n\tif(( sb->iCurBit - 1 ) >> 5 == idword1 )\n\t{\n\t\tif( numbits != 32 )\n\t\t\tret &= ExtraMasks[numbits];\n\t}\n\telse\n\t{\n\t\tint\tnExtraBits = sb->iCurBit & 31;\n\t\tuint\tdword2 = ((uint *)sb->pData)[idword1+1] & ExtraMasks[nExtraBits];\n\n\t\t// no need to mask since we hit the end of the dword.\n\t\t// shift the second dword's part into the high bits.\n\t\tret |= (dword2 << ( numbits - nExtraBits ));\n\t}\n\treturn ret;\n}\n\nqboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits )\n{\n\tbyte\t*pOut = (byte *)pOutData;\n\tint\tnBitsLeft = nBits;\n\n\t// get output dword-aligned.\n\twhile((( uintptr_t )pOut & 3) != 0 && nBitsLeft >= 8 )\n\t{\n\t\t*pOut = (byte)MSG_ReadUBitLong( sb, 8 );\n\t\t++pOut;\n\t\tnBitsLeft -= 8;\n\t}\n\n\t// read dwords.\n\twhile( nBitsLeft >= 32 )\n\t{\n\t\t*((uint32_t *)pOut) = MSG_ReadUBitLong( sb, 32 );\n\t\tpOut += sizeof( uint32_t );\n\t\tnBitsLeft -= 32;\n\t}\n\n\t// read the remaining bytes.\n\twhile( nBitsLeft >= 8 )\n\t{\n\t\t*pOut = MSG_ReadUBitLong( sb, 8 );\n\t\t++pOut;\n\t\tnBitsLeft -= 8;\n\t}\n\n\t// read the remaining bits.\n\tif( nBitsLeft )\n\t{\n\t\t*pOut = MSG_ReadUBitLong( sb, nBitsLeft );\n\t}\n\n\treturn !sb->bOverflow;\n}\n\nfloat MSG_ReadBitAngle( sizebuf_t *sb, int numbits )\n{\n\tfloat shift = (float)( 1 << numbits );\n\tint i = MSG_ReadUBitLong( sb, numbits );\n\tfloat fReturn = (float)i * ( 360.0f / shift );\n\n\t// clamp the finale angle\n\tif( fReturn < -180.0f ) fReturn += 360.0f;\n\telse if( fReturn > 180.0f ) fReturn -= 360.0f;\n\n\treturn fReturn;\n}\n\n// Append numbits least significant bits from data to the current bit stream\nint MSG_ReadSBitLong( sizebuf_t *sb, int numbits )\n{\n\tint r;\n\n\tif( sb->iAlternateSign )\n\t{\n\t\tint sign = MSG_ReadOneBit( sb );\n\t\tr = MSG_ReadUBitLong( sb, numbits - 1 );\n\n\t\tif( sign )\n\t\t\tr = -r;\n\t}\n\telse\n\t{\n\t\tr = MSG_ReadUBitLong( sb, numbits - 1 );\n\t\tif( MSG_ReadOneBit( sb ))\n\t\t\tr = -( BIT( numbits - 1 ) - r );\n\t}\n\n\treturn r;\n}\n\nuint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned )\n{\n\tif( bSigned )\n\t\treturn (uint)MSG_ReadSBitLong( sb, numbits );\n\treturn MSG_ReadUBitLong( sb, numbits );\n}\n\nint MSG_ReadCmd( sizebuf_t *sb, netsrc_t type )\n{\n\tint\tcmd = MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 );\n\n\tif( unlikely( net_recv_debug.value ))\n\t{\n\t\tif( type == NS_SERVER )\n\t\t{\n\t\t\tif( cmd != svc_nop )\n\t\t\t\tCon_Printf( \"^1cl^7 read: %s\\n\", CL_MsgInfo( cmd ));\n\t\t}\n\t\telse if( cmd >= 0 && cmd <= clc_lastmsg )\n\t\t{\n\t\t\tCon_Printf( \"^1sv^7 read: %s\\n\", clc_strings[cmd] );\n\t\t}\n\t}\n\n\treturn cmd;\n}\n\nint MSG_ReadChar( sizebuf_t *sb )\n{\n\tint alt = sb->iAlternateSign, ret;\n\n\tsb->iAlternateSign = 0;\n\tret = MSG_ReadSBitLong( sb, sizeof( int8_t ) << 3 );\n\tsb->iAlternateSign = alt;\n\n\treturn ret;\n}\n\nint MSG_ReadByte( sizebuf_t *sb )\n{\n\treturn MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 );\n}\n\nint MSG_ReadShort( sizebuf_t *sb )\n{\n\tint alt = sb->iAlternateSign, ret;\n\n\tsb->iAlternateSign = 0;\n\tret = MSG_ReadSBitLong( sb, sizeof( int16_t ) << 3 );\n\tsb->iAlternateSign = alt;\n\n\treturn ret;\n}\n\nint MSG_ReadWord( sizebuf_t *sb )\n{\n\treturn MSG_ReadUBitLong( sb, sizeof( uint16_t ) << 3 );\n}\n\nfloat MSG_ReadCoord( sizebuf_t *sb )\n{\n\t// g-cont. we loose precision here but keep old size of coord variable!\n\tif( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))\n\t\treturn (float)(MSG_ReadShort( sb ));\n\treturn (float)(MSG_ReadShort( sb ) * ( 1.0f / 8.0f ));\n}\n\nvoid MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa )\n{\n\tfa[0] = MSG_ReadCoord( sb );\n\tfa[1] = MSG_ReadCoord( sb );\n\tfa[2] = MSG_ReadCoord( sb );\n}\n\nvoid MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa )\n{\n\tfa[0] = MSG_ReadBitAngle( sb, 16 );\n\tfa[1] = MSG_ReadBitAngle( sb, 16 );\n\tfa[2] = MSG_ReadBitAngle( sb, 16 );\n}\n\nint MSG_ReadLong( sizebuf_t *sb )\n{\n\tint alt = sb->iAlternateSign, ret;\n\n\tsb->iAlternateSign = 0;\n\tret = MSG_ReadSBitLong( sb, sizeof( int32_t ) << 3 );\n\tsb->iAlternateSign = alt;\n\n\treturn ret;\n}\n\nuint MSG_ReadDword( sizebuf_t *sb )\n{\n\treturn MSG_ReadUBitLong( sb, sizeof( uint32_t ) << 3 );\n}\n\nfloat MSG_ReadFloat( sizebuf_t *sb )\n{\n\tfloat\tret;\n\n\tMSG_ReadBits( sb, &ret, sizeof( ret ) << 3 );\n\n\treturn ret;\n}\n\nqboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes )\n{\n\treturn MSG_ReadBits( sb, pOut, nBytes << 3 );\n}\n\nstatic char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine )\n{\n\tstatic char\tstring[4096];\n\tint\t\tl = 0;\n\n\tdo\n\t{\n\t\t// use MSG_ReadByte so -1 is out of bounds\n\t\tint c = MSG_ReadByte( sb );\n\n\t\tif( c == 0 ) break;\n\t\telse if( bLine && c == '\\n' )\n\t\t\tbreak;\n\n\t\t// translate all fmt spec to avoid crash bugs\n\t\t// NOTE: but game strings leave unchanged. see pfnWriteString for details\n\t\tif( c == '%' ) c = '.';\n\n\t\tstring[l] = c;\n\t\tl++;\n\t} while( l < sizeof( string ) - 1 );\n\tstring[l] = 0; // terminator\n\n\treturn string;\n}\n\nchar *MSG_ReadString( sizebuf_t *sb )\n{\n\treturn MSG_ReadStringExt( sb, false );\n}\n\nchar *MSG_ReadStringLine( sizebuf_t *sb )\n{\n\treturn MSG_ReadStringExt( sb, true );\n}\n\nvoid MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove )\n{\n\tint\ti, endbit = startbit + bitstoremove;\n\tint\tremaining_to_end = sb->nDataBits - endbit;\n\tsizebuf_t\ttemp;\n\n\tMSG_StartWriting( &temp, sb->pData, MSG_GetMaxBytes( sb ), startbit, -1 );\n\tMSG_SeekToBit( sb, endbit, SEEK_SET );\n\n\tfor( i = 0; i < remaining_to_end; i++ )\n\t{\n\t\tMSG_WriteOneBit( &temp, MSG_ReadOneBit( sb ));\n\t}\n\n\tMSG_SeekToBit( sb, startbit, SEEK_SET );\n\tsb->nDataBits -= bitstoremove;\n}\n\n#ifdef XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nstatic const void *g_testbuf = \"asdf\\xba\\xa1\\xba\\xa1\\xed\\xc8\\x15\\x7a\";\nstatic const size_t g_testbuf_bits = (( 4 + 4 + 2 + 1 ) << 3 ) + 4;\n\nstatic void Test_Buffer_BitByte( void )\n{\n\tTASSERT_EQi( BitByte( 0 ), 0 );\n\tTASSERT_EQi( BitByte( 1 ), 1 );\n\tTASSERT_EQi( BitByte( 8 ), 1 );\n\tTASSERT_EQi( BitByte( 9 ), 2 );\n}\n\nstatic void Test_Buffer_Write( void )\n{\n\tsizebuf_t sb;\n\tchar testdata[0x100] = { 0 };\n\n\tMSG_Init( &sb, __func__, testdata, sizeof( testdata ));\n\tTASSERT_EQi( sb.iCurBit, 0 );\n\tTASSERT_EQi( sb.nDataBits, sizeof( testdata ) << 3 );\n\tTASSERT_EQp( sb.pData, (void *)testdata );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tMSG_WriteBytes( &sb, \"asdf\", 4 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 32 );\n\n\tMSG_WriteDword( &sb, 0xa1baa1ba );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 64 );\n\n\tMSG_WriteShort( &sb, -0x3713 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 80 );\n\n\tMSG_WriteOneBit( &sb, 1 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 81 );\n\n\tMSG_WriteOneBit( &sb, 0 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 82 );\n\n\tMSG_WriteOneBit( &sb, 1 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 83 );\n\n\tMSG_WriteOneBit( &sb, 0 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 84 );\n\n\tMSG_WriteByte( &sb, 0xa1 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\tTASSERT_EQi( sb.iCurBit, 92 );\n\n\tTASSERT_EQi( MSG_GetNumBitsWritten( &sb ), g_testbuf_bits );\n\tTASSERT_EQi( MSG_GetNumBytesWritten( &sb ), BitByte( g_testbuf_bits ));\n\tTASSERT_EQi( MSG_GetRealBytesWritten( &sb ), g_testbuf_bits >> 3 );\n\n\t// if tests fails here on big endian, it's possible due to endian issues\n\tTASSERT( !memcmp( sb.pData, g_testbuf, g_testbuf_bits >> 3 ));\n\n\t// must check last 4 bits separately because we never care about uninitialized bits\n\tMSG_SeekToBit( &sb, g_testbuf_bits & ~7, SEEK_SET );\n\tTASSERT_EQi( sb.iCurBit, 88 );\n\tTASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa );\n}\n\nstatic void Test_Buffer_Read( void )\n{\n\tsizebuf_t sb;\n\tchar buf[4];\n\n\tMSG_StartReading( &sb, (void *)g_testbuf, -1, 0, g_testbuf_bits );\n\tTASSERT_EQi( sb.iCurBit, 0 );\n\tTASSERT_EQi( sb.nDataBits, g_testbuf_bits );\n\tTASSERT_EQp( sb.pData, (void *)g_testbuf );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tMSG_ReadBytes( &sb, buf, 4 );\n\tTASSERT( !memcmp( buf, \"asdf\", 4 ));\n\tTASSERT_EQi( sb.iCurBit, 32 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadWord( &sb ), 0xa1ba );\n\tTASSERT_EQi( sb.iCurBit, 48 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadDword( &sb ), 0xc8eda1baU );\n\tTASSERT_EQi( sb.iCurBit, 80 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadOneBit( &sb ), 1 );\n\tTASSERT_EQi( sb.iCurBit, 81 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadOneBit( &sb ), 0 );\n\tTASSERT_EQi( sb.iCurBit, 82 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadOneBit( &sb ), 1 );\n\tTASSERT_EQi( sb.iCurBit, 83 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadOneBit( &sb ), 0 );\n\tTASSERT_EQi( sb.iCurBit, 84 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_ReadByte( &sb ), 0xa1 );\n\tTASSERT_EQi( sb.iCurBit, 92 );\n\tTASSERT_EQi( sb.bOverflow, false );\n\n\tTASSERT_EQi( MSG_Overflow( &sb, 1 ), true );\n\tTASSERT_EQi( sb.bOverflow, true );\n}\n\nstatic void Test_Buffer_ExciseBits( void )\n{\n\tsizebuf_t sb;\n\tchar testdata[0x100];\n\n\tmemcpy( testdata, g_testbuf, BitByte( g_testbuf_bits ));\n\n\tMSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits );\n\tMSG_ExciseBits( &sb, 8, 28 );\n\n\tTASSERT_EQi( MSG_CheckOverflow( &sb ), false );\n\tTASSERT_EQi( MSG_GetMaxBits( &sb ), 64 );\n\tTASSERT( !memcmp( MSG_GetData( &sb ), \"a\\x1b\\xaa\\x1b\\xda\\x8e\\x5c\\xa1\", 8 ));\n\n\tmemcpy( testdata, g_testbuf, BitByte( g_testbuf_bits ));\n\n\tMSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits );\n\tMSG_ExciseBits( &sb, 16, 32 );\n\n\tTASSERT_EQi( MSG_CheckOverflow( &sb ), false );\n\tTASSERT_EQi( MSG_GetMaxBits( &sb ), g_testbuf_bits - 32 );\n\tTASSERT( !memcmp( MSG_GetData( &sb ), \"as\\xba\\xa1\\xed\\xc8\\x15\", 7 ));\n\n\tMSG_SeekToBit( &sb, 7 << 3, SEEK_SET );\n\tTASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa );\n}\n\nvoid Test_RunBuffer( void )\n{\n\tTRUN( Test_Buffer_BitByte( ));\n\tTRUN( Test_Buffer_Write( ));\n\tTRUN( Test_Buffer_Read( ));\n\tTRUN( Test_Buffer_ExciseBits( ));\n}\n\n#endif // XASH_ENGINE_TESTS\n"
  },
  {
    "path": "engine/common/net_buffer.h",
    "content": "/*\nnet_buffer.h - network message io functions\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef NET_BUFFER_H\n#define NET_BUFFER_H\n\n#include \"enginefeatures.h\"\n\n/*\n==============================================================================\n\n\t\t\tMESSAGE IO FUNCTIONS\n\t       Handles byte ordering and avoids alignment errors\n==============================================================================\n*/\n\n// Pad a number so it lies on an N byte boundary.\n// So PAD_NUMBER(0,4) is 0 and PAD_NUMBER(1,4) is 4\n#define PAD_NUMBER( num, boundary )\t((( num ) + (( boundary ) - 1 )) / ( boundary )) * ( boundary )\n\nstatic inline int BitByte( int bits )\n{\n\treturn PAD_NUMBER( bits, 8 ) >> 3;\n}\n\nstruct sizebuf_s\n{\n\tbyte        *pData;\n\tqboolean    bOverflow;   // overflow reading or writing\n\tint         iCurBit;\n\tint         nDataBits;\n\tconst char\t*pDebugName; // buffer name (pointer to const name)\n\n\t// to support GoldSrc broken signed integers\n\tint iAlternateSign;\n};\n\n#define MSG_StartReading     MSG_StartWriting\n#define MSG_GetNumBytesRead  MSG_GetNumBytesWritten\n#define MSG_GetRealBytesRead MSG_GetRealBytesWritten\n#define MSG_GetNumBitsRead   MSG_GetNumBitsWritten\n#define MSG_ReadBitAngles    MSG_ReadBitVec3Coord\n#define MSG_ReadAngle( sb )               (float)( MSG_ReadChar( sb ) * ( 360.0f / 256.0f ))\n#define MSG_Init( sb, name, data, bytes ) MSG_InitExt( sb, name, data, bytes, -1 )\n#define MSG_CheckOverflow( sb )           MSG_Overflow( sb, 0 )\n\n// common functions\nstatic inline void MSG_Clear( sizebuf_t *sb )\n{\n\tsb->bOverflow = false;\n\tsb->iCurBit = 0;\n}\n\nstatic inline void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nBits )\n{\n\tsb->pData = pData;\n\tMSG_Clear( sb );\n\n\tif( nBits < 0 )\n\t\tsb->nDataBits = nBytes << 3;\n\telse\n\t\tsb->nDataBits = nBits;\n\n\tsb->pDebugName = pDebugName;\n\tsb->iAlternateSign = 0;\n}\n\nstatic inline void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits )\n{\n\tMSG_InitExt( sb, \"Unnamed\", pData, nBytes, nBits );\n\tsb->iCurBit = iStartBit;\n}\n\nstatic inline int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence )\n{\n\t// compute the file offset\n\tswitch( whence )\n\t{\n\tcase SEEK_CUR:\n\t\tbitPos += sb->iCurBit;\n\t\tbreak;\n\tcase SEEK_SET:\n\t\tbreak;\n\tcase SEEK_END:\n\t\tbitPos += sb->nDataBits;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tif( unlikely( bitPos < 0 || bitPos > sb->nDataBits ))\n\t\treturn -1;\n\n\tsb->iCurBit = bitPos;\n\n\treturn 0;\n}\n\nstatic inline int MSG_TellBit( sizebuf_t *sb )\n{\n\treturn sb->iCurBit;\n}\n\nstatic inline const char *MSG_GetName( sizebuf_t *sb )\n{\n\treturn sb->pDebugName;\n}\n\nstatic inline int MSG_GetNumBytesWritten( sizebuf_t *sb )\n{\n\treturn BitByte( sb->iCurBit );\n}\n\nstatic inline int MSG_GetRealBytesWritten( sizebuf_t *sb )\n{\n\treturn sb->iCurBit >> 3; // unpadded\n}\nstatic inline int MSG_GetNumBitsWritten( sizebuf_t *sb )\n{\n\treturn sb->iCurBit;\n}\n\nstatic inline int MSG_GetMaxBits( sizebuf_t *sb )\n{\n\treturn sb->nDataBits;\n}\n\nstatic inline int MSG_GetMaxBytes( sizebuf_t *sb )\n{\n\treturn sb->nDataBits >> 3;\n}\n\nstatic inline int MSG_GetNumBitsLeft( sizebuf_t *sb )\n{\n\treturn sb->nDataBits - sb->iCurBit;\n}\n\nstatic inline int MSG_GetNumBytesLeft( sizebuf_t *sb )\n{\n\treturn MSG_GetNumBitsLeft( sb ) >> 3;\n}\n\nstatic inline byte *MSG_GetData( sizebuf_t *sb )\n{\n\treturn sb->pData;\n}\n\n#if XASH_BIG_ENDIAN\n#define MSG_BigShort( x ) ( x )\n#else\nstatic inline uint16_t MSG_BigShort( const uint16_t x )\n{\n\treturn (x >> 8) | (x << 8);\n}\n#endif\n\nstatic inline qboolean MSG_Overflow( sizebuf_t *sb, int nBits )\n{\n\tif( sb->iCurBit + nBits > sb->nDataBits )\n\t\tsb->bOverflow = true;\n\treturn sb->bOverflow;\n}\n\nstatic inline void MSG_EndBitWriting( sizebuf_t *sb )\n{\n\tsb->iAlternateSign--;\n\n\tif( sb->iAlternateSign < 0 )\n\t{\n\t\tCon_Printf( \"%s: non-even MSG_Start/EndBitWriting\\n\", __func__ );\n\t\tsb->iAlternateSign = 0;\n\t}\n\n\t// we have native bit ops here, just pad to closest byte\n\tif(( sb->iCurBit & 7 ) != 0 )\n\t\tMSG_SeekToBit( sb, 8 - ( sb->iCurBit & 7 ), SEEK_CUR );\n}\n\nstatic inline void MSG_StartBitWriting( sizebuf_t *sb )\n{\n\tsb->iAlternateSign++;\n}\n\nvoid MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove );\n\n// Bit-write functions\nstatic inline void MSG_WriteOneBit( sizebuf_t *sb, int nValue )\n{\n\tif( !MSG_Overflow( sb, 1 ))\n\t{\n\t\tif( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 );\n\t\telse sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 );\n\n\t\tsb->iCurBit++;\n\t}\n}\n\nNO_ASAN void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits );\nvoid MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits );\nvoid MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned );\nqboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits );\nvoid MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits );\n\n// Byte-write functions\n#define MSG_BeginServerCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_SERVER, NULL )\n#define MSG_BeginClientCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_CLIENT, NULL )\nvoid MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name );\t\t// message marker\nvoid MSG_WriteChar( sizebuf_t *sb, int val );\nvoid MSG_WriteByte( sizebuf_t *sb, int val );\nvoid MSG_WriteShort( sizebuf_t *sb, int val );\nvoid MSG_WriteWord( sizebuf_t *sb, int val );\nvoid MSG_WriteLong( sizebuf_t *sb, int val );\nvoid MSG_WriteDword( sizebuf_t *sb, uint val );\nvoid MSG_WriteCoord( sizebuf_t *sb, float val );\nvoid MSG_WriteFloat( sizebuf_t *sb, float val );\nvoid MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa );\nvoid MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa );\nqboolean MSG_WriteString( sizebuf_t *sb, const char *pStr );\t\t// returns false if it overflows the buffer.\nqboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... ) FORMAT_CHECK( 2 );\nqboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes );\n\n// helper functions\n\n// Bit-read functions\nint MSG_ReadOneBit( sizebuf_t *sb );\nqboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits );\nfloat MSG_ReadBitAngle( sizebuf_t *sb, int numbits );\nint MSG_ReadSBitLong( sizebuf_t *sb, int numbits );\nuint MSG_ReadUBitLong( sizebuf_t *sb, int numbits );\nuint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned );\n\n// Byte-read functions\n#define MSG_ReadServerCmd( sb ) MSG_ReadCmd( sb, NS_SERVER )\n#define MSG_ReadClientCmd( sb ) MSG_ReadCmd( sb, NS_CLIENT )\nint MSG_ReadCmd( sizebuf_t *sb, netsrc_t type );\t\t// message marker\nint MSG_ReadChar( sizebuf_t *sb );\nint MSG_ReadByte( sizebuf_t *sb );\nint MSG_ReadShort( sizebuf_t *sb );\nint MSG_ReadWord( sizebuf_t *sb );\nint MSG_ReadLong( sizebuf_t *sb );\nuint MSG_ReadDword( sizebuf_t *sb );\nfloat MSG_ReadCoord( sizebuf_t *sb );\nfloat MSG_ReadFloat( sizebuf_t *sb );\nvoid MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa );\nvoid MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa );\nchar *MSG_ReadString( sizebuf_t *sb ) RETURNS_NONNULL;\nchar *MSG_ReadStringLine( sizebuf_t *sb ) RETURNS_NONNULL;\nqboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes );\n\n#endif//NET_BUFFER_H\n"
  },
  {
    "path": "engine/common/net_chan.c",
    "content": "/*\nnet_chan.c - network channel\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"netchan.h\"\n#include \"xash3d_mathlib.h\"\n#include \"net_encode.h\"\n#include \"protocol.h\"\n#if !XASH_DEDICATED\n#include <bzlib.h>\n#endif // !XASH_DEDICATED\n\n#define MAKE_FRAGID( id, count )\t((( id & 0xffff ) << 16 ) | ( count & 0xffff ))\n#define FRAG_GETID( fragid )\t\t(( fragid >> 16 ) & 0xffff )\n#define FRAG_GETCOUNT( fragid )\t( fragid & 0xffff )\n\n#define UDP_HEADER_SIZE\t\t28\n\n#define FLOW_AVG\t\t\t( 2.0f / 3.0f )\t// how fast to converge flow estimates\n#define FLOW_INTERVAL\t\t0.1\t\t// don't compute more often than this\n#define MAX_RELIABLE_PAYLOAD\t\t1400\t\t// biggest packet that has frag and or reliable data\n\n// forward declarations\nvoid Netchan_FlushIncoming( netchan_t *chan, int stream );\nvoid Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf );\n\n/*\npacket header ( size in bits )\n-------------\n31\tsequence\n1\tdoes this message contain a reliable payload\n31\tacknowledge sequence\n1\tacknowledge receipt of even/odd message\n16\tqport\n\nThe remote connection never knows if it missed a reliable message, the\nlocal side detects that it has been dropped by seeing a sequence acknowledge\nhigher thatn the last reliable sequence, but without the correct evon/odd\nbit for the reliable set.\n\nIf the sender notices that a reliable message has been dropped, it will be\nretransmitted.  It will not be retransmitted again until a message after\nthe retransmit has been acknowledged and the reliable still failed to get there.\n\nif the sequence number is -1, the packet should be handled without a netcon\n\nThe reliable message can be added to at any time by doing\nMSG_Write* (&netchan->message, <data>).\n\nIf the message buffer is overflowed, either by a single message, or by\nmultiple frames worth piling up while the last reliable transmit goes\nunacknowledged, the netchan signals a fatal error.\n\nReliable messages are allways placed first in a packet, then the unreliable\nmessage is included if there is sufficient room.\n\nTo the receiver, there is no distinction between the reliable and unreliable\nparts of the message, they are just processed out as a single larger message.\n\nIllogical packet sequence numbers cause the packet to be dropped, but do\nnot kill the connection.  This, combined with the tight window of valid\nreliable acknowledgement numbers provides protection against malicious\naddress spoofing.\n\nThe qport field is a workaround for bad address translating routers that\nsometimes remap the client's source port on a packet during gameplay.\n\nIf the base part of the net address matches and the qport matches, then the\nchannel matches even if the IP port differs.  The IP port should be updated\nto the new value before sending out any replies.\n\n\nIf there is no information that needs to be transfered on a given frame,\nsuch as during the connection stage while waiting for the client to load,\nthen a packet only needs to be delivered if there is something in the\nunacknowledged reliable\n*/\nCVAR_DEFINE_AUTO( net_showpackets, \"0\", FCVAR_PRIVILEGED, \"show network packets\" );\nstatic CVAR_DEFINE_AUTO( net_chokeloop, \"0\", 0, \"apply bandwidth choke to loopback packets\" );\nstatic CVAR_DEFINE_AUTO( net_showdrop, \"0\", 0, \"show packets that are dropped\" );\nstatic CVAR_DEFINE_AUTO( net_qport, \"0\", FCVAR_READ_ONLY, \"current quake netport\" );\nCVAR_DEFINE_AUTO( net_send_debug, \"0\", FCVAR_PRIVILEGED, \"enable debugging output for outgoing messages\" );\nCVAR_DEFINE_AUTO( net_recv_debug, \"0\", FCVAR_PRIVILEGED, \"enable debugging output for incoming messages\" );\n\nint\tnet_drop;\nnetadr_t\tnet_from;\nsizebuf_t\tnet_message;\nstatic poolhandle_t net_mempool;\nbyte\tnet_message_buffer[NET_MAX_MESSAGE];\n\nstatic const char *const ns_strings[NS_COUNT] =\n{\n\t\"Client\",\n\t\"Server\",\n};\n\n#if !XASH_DEDICATED\nvoid bz_internal_error( int errcode );\nvoid bz_internal_error( int errcode )\n{\n\tCon_Printf( S_ERROR \"bzip2/libbzip2: internal error number %d.\\n\"\n\t\t\t\"This is a bug in bzip2/libbzip2, %s.\\n\"\n\t\t\t\"Please report it at: https://gitlab.com/bzip2/bzip2/-/issues\\n\"\n\t\t\t\"If this happened when you were using some program which uses\\n\"\n\t\t\t\"libbzip2 as a component, you should also report this bug to\\n\"\n\t\t\t\"the author(s) of that program.\\n\"\n\t\t\t\"Please make an effort to report this bug;\\n\"\n\t\t\t\"timely and accurate bug reports eventually lead to higher\\n\"\n\t\t\t\"quality software.  Thanks.\\n\\n\",\n\t\t\terrcode, BZ2_bzlibVersion( ));\n\n\tif (errcode == 1007) {\n\t\tCon_Printf(\n\t\t\t\"\\n*** A special note about internal error number 1007 ***\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"Experience suggests that a common cause of i.e. 1007\\n\"\n\t\t\t\"is unreliable memory or other hardware.  The 1007 assertion\\n\"\n\t\t\t\"just happens to cross-check the results of huge numbers of\\n\"\n\t\t\t\"memory reads/writes, and so acts (unintendedly) as a stress\\n\"\n\t\t\t\"test of your memory system.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"I suggest the following: try compressing the file again,\\n\"\n\t\t\t\"possibly monitoring progress in detail with the -vv flag.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"* If the error cannot be reproduced, and/or happens at different\\n\"\n\t\t\t\"  points in compression, you may have a flaky memory system.\\n\"\n\t\t\t\"  Try a memory-test program.  I have used Memtest86\\n\"\n\t\t\t\"  (www.memtest86.com).  At the time of writing it is free (GPLd).\\n\"\n\t\t\t\"  Memtest86 tests memory much more thorougly than your BIOSs\\n\"\n\t\t\t\"  power-on test, and may find failures that the BIOS doesn't.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"* If the error can be repeatably reproduced, this is a bug in\\n\"\n\t\t\t\"  bzip2, and I would very much like to hear about it.  Please\\n\"\n\t\t\t\"  let me know, and, ideally, save a copy of the file causing the\\n\"\n\t\t\t\"  problem -- without which I will be unable to investigate it.\\n\"\n\t\t\t\"\\n\"\n\t\t);\n\t}\n\n\tSys_Error( \"bzip2/libbzip2: internal error number %d\\n\", errcode );\n}\n#endif // XASH_DEDICATED\n\n/*\n=================================\n\nNETWORK PACKET SPLIT\n\n=================================\n*/\n\n/*\n======================\nNetSplit_GetLong\n\nCollect fragmrnts with signature 0xFFFFFFFE to single packet\nreturn true when got full packet\n======================\n*/\nqboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *length )\n{\n\tnetsplit_packet_t *packet = (netsplit_packet_t*)data;\n\tnetsplit_chain_packet_t * p;\n\n\t//ASSERT( *length > NETSPLIT_HEADER_SIZE );\n\tif( *length <= NETSPLIT_HEADER_SIZE ) return false;\n\n\tLittleLongSW(packet->id);\n\tLittleLongSW(packet->length);\n\tLittleLongSW(packet->part);\n\n\tp = &ns->packets[packet->id & NETSPLIT_BACKUP_MASK];\n\t// Con_Reportf( S_NOTE \"%s: packet from %s, id %d, index %d length %d\\n\", __func__, NET_AdrToString( *from ), (int)packet->id, (int)packet->index, (int)*length );\n\n\t// no packets with this id received\n\tif( packet->id != p->id )\n\t{\n\t\t// warn if previous packet not received\n\t\tif( p->received < p->count )\n\t\t{\n\t\t\tUI_ShowConnectionWarning();\n\t\t\tCon_Reportf( S_WARN \"%s: lost packet %d\\n\", __func__, p->id );\n\t\t}\n\n\t\tp->id = packet->id;\n\t\tp->count = packet->count;\n\t\tp->received = 0;\n\t\tmemset( p->recieved_v, 0, 32 );\n\t}\n\n\t// use bool vector to detect dup packets\n\tif( p->recieved_v[packet->index >> 5 ] & ( 1 << ( packet->index & 31 ) ) )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: dup packet from %s\\n\", __func__, NET_AdrToString( *from ) );\n\t\treturn false;\n\t}\n\n\tp->received++;\n\n\t// mark as received\n\tp->recieved_v[packet->index >> 5] |= 1 << ( packet->index & 31 );\n\n\t// prevent overflow\n\tif( packet->part * packet->index > NET_MAX_PAYLOAD )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: packet out fo bounds from %s (part %d index %d)\\n\", __func__, NET_AdrToString( *from ), packet->part, packet->index );\n\t\treturn false;\n\t}\n\n\tif( packet->length > NET_MAX_PAYLOAD )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: packet out fo bounds from %s (length %d)\\n\", __func__, NET_AdrToString( *from ), packet->length );\n\t\treturn false;\n\t}\n\n\tmemcpy( p->data + packet->part * packet->index, packet->data, *length - 18 );\n\n\t// rewrite results of NET_GetPacket\n\tif( p->received == packet->count )\n\t{\n\t\t//ASSERT( packet->length % packet->part == (*length - NETSPLIT_HEADER_SIZE) % packet->part );\n\t\tsize_t len = packet->length;\n\n\t\tns->total_received += len;\n\n\t\tns->total_received_uncompressed += len;\n\t\t*length = len;\n\n\t\t// Con_Reportf( S_NOTE \"%s: packet from %s, id %d received %d length %d\\n\", __func__, NET_AdrToString( *from ), (int)packet->id, (int)p->received, (int)packet->length );\n\t\tmemcpy( data, p->data, len );\n\t\treturn true;\n\t}\n\telse\n\t\t*length = NETSPLIT_HEADER_SIZE + packet->part;\n\n\n\treturn false;\n}\n\n/*\n===============\nNetchan_Init\n===============\n*/\nvoid Netchan_Init( void )\n{\n\tchar buf[32];\n\tint\tport;\n\n\t// pick a port value that should be nice and random\n\tport = COM_RandomLong( 1, 65535 );\n\tQ_snprintf( buf, sizeof( buf ), \"%i\", port );\n\n\tCvar_RegisterVariable( &net_showpackets );\n\tCvar_RegisterVariable( &net_chokeloop );\n\tCvar_RegisterVariable( &net_showdrop );\n\tCvar_RegisterVariable( &net_qport );\n\tCvar_RegisterVariable( &net_send_debug );\n\tCvar_RegisterVariable( &net_recv_debug );\n\tCvar_FullSet( net_qport.name, buf, net_qport.flags );\n\n\tnet_mempool = Mem_AllocPool( \"Network Pool\" );\n}\n\nvoid Netchan_Shutdown( void )\n{\n\tMem_FreePool( &net_mempool );\n}\n\nvoid Netchan_ReportFlow( netchan_t *chan )\n{\n\tchar\tincoming[64];\n\tchar\toutgoing[64];\n\n\tif( CL_IsPlaybackDemo( ))\n\t\treturn;\n\n\tAssert( chan != NULL );\n\n\tQ_strncpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 ), sizeof( incoming ));\n\tQ_strncpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 ), sizeof( outgoing ));\n\n\tCon_DPrintf( \"Signon network traffic:  %s from server, %s to server\\n\", incoming, outgoing );\n}\n\n/*\n==============\nNetchan_IsLocal\n\ndetect a loopback message\n==============\n*/\nqboolean Netchan_IsLocal( netchan_t *chan )\n{\n\tif( !NET_IsActive() || NET_IsLocalAddress( chan->remote_address ))\n\t\treturn true;\n\treturn false;\n}\n\n/*\n==============\nNetchan_Setup\n\ncalled to open a channel to a remote system\n==============\n*/\nvoid Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags )\n{\n\tNetchan_Clear( chan );\n\n\tmemset( chan, 0, sizeof( *chan ));\n\n\tif( pfnBlockSize == NULL )\n\t{\n\t\tHost_Error( \"%s: pfnBlockSize == NULL\", __func__ );\n\t\treturn;\n\t}\n\n\tchan->sock = sock;\n\tchan->remote_address = adr;\n\tchan->last_received = host.realtime;\n\tchan->connect_time = host.realtime;\n\tchan->incoming_sequence = 0;\n\tchan->outgoing_sequence = 1;\n\tchan->rate = DEFAULT_RATE;\n\tchan->qport = qport;\n\tchan->client = client;\n\tchan->pfnBlockSize = pfnBlockSize;\n\tchan->split = FBitSet( flags, NETCHAN_USE_LEGACY_SPLIT ) ? true : false;\n\tchan->use_munge = FBitSet( flags, NETCHAN_USE_MUNGE ) ? true : false;\n\tchan->use_bz2 = FBitSet( flags, NETCHAN_USE_BZIP2 ) ? true : false;\n\tchan->use_lzss = FBitSet( flags, NETCHAN_USE_LZSS ) ? true : false;\n\tchan->gs_netchan = FBitSet( flags, NETCHAN_GOLDSRC ) ? true : false;\n\n\tMSG_Init( &chan->message, \"NetData\", chan->message_buf, sizeof( chan->message_buf ));\n}\n\n/*\n==============================\nNetchan_IncomingReady\n\n==============================\n*/\nqboolean Netchan_IncomingReady( netchan_t *chan )\n{\n\tint\ti;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\tif( chan->incomingready[i] )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n===============\nNetchan_CanPacket\n\nReturns true if the bandwidth choke isn't active\n================\n*/\nqboolean Netchan_CanPacket( netchan_t *chan, qboolean choke )\n{\n\t// never choke loopback packets.\n\tif( !choke || ( !net_chokeloop.value && NET_IsLocalAddress( chan->remote_address ) ))\n\t{\n\t\tchan->cleartime = host.realtime;\n\t\treturn true;\n\t}\n\n\treturn chan->cleartime < host.realtime ? true : false;\n}\n\n/*\n==============================\nNetchan_UnlinkFragment\n\n==============================\n*/\nstatic void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list )\n{\n\tfragbuf_t\t*search;\n\n\tif( !list ) return;\n\n\t// at head of list\n\tif( buf == *list )\n\t{\n\t\t// remove first element\n\t\t*list = buf->next;\n\n\t\t// destroy remnant\n\t\tMem_Free( buf );\n\t\treturn;\n\t}\n\n\tsearch = *list;\n\twhile( search->next )\n\t{\n\t\tif( search->next == buf )\n\t\t{\n\t\t\tsearch->next = buf->next;\n\n\t\t\t// destroy remnant\n\t\t\tMem_Free( buf );\n\t\t\treturn;\n\t\t}\n\t\tsearch = search->next;\n\t}\n}\n\n/*\n==============================\nNetchan_ClearFragbufs\n\n==============================\n*/\nstatic void Netchan_ClearFragbufs( fragbuf_t **ppbuf )\n{\n\tfragbuf_t\t*buf, *n;\n\n\tif( !ppbuf ) return;\n\n\t// Throw away any that are sitting around\n\tbuf = *ppbuf;\n\n\twhile( buf )\n\t{\n\t\tn = buf->next;\n\t\tMem_Free( buf );\n\t\tbuf = n;\n\t}\n\n\t*ppbuf = NULL;\n}\n\n/*\n==============================\nNetchan_ClearFragments\n\n==============================\n*/\nstatic void Netchan_ClearFragments( netchan_t *chan )\n{\n\tfragbufwaiting_t\t*wait, *next;\n\tint\t\ti;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\twait = chan->waitlist[i];\n\n\t\twhile( wait )\n\t\t{\n\t\t\tnext = wait->next;\n\t\t\tNetchan_ClearFragbufs( &wait->fragbufs );\n\t\t\tMem_Free( wait );\n\t\t\twait = next;\n\t\t}\n\t\tchan->waitlist[i] = NULL;\n\n\t\tNetchan_ClearFragbufs( &chan->fragbufs[i] );\n\t\tNetchan_FlushIncoming( chan, i );\n\t}\n}\n\n/*\n==============================\nNetchan_Clear\n\n==============================\n*/\nvoid Netchan_Clear( netchan_t *chan )\n{\n\tint\ti;\n\n\tNetchan_ClearFragments( chan );\n\n\tchan->cleartime = 0.0;\n\tchan->reliable_length = 0;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\tchan->reliable_fragid[i] = 0;\n\t\tchan->reliable_fragment[i] = 0;\n\t\tchan->fragbufcount[i] = 0;\n\t\tchan->frag_startpos[i] = 0;\n\t\tchan->frag_length[i] = 0;\n\t\tchan->incomingready[i] = false;\n\t}\n\n\tif( chan->tempbuffer )\n\t{\n\t\tMem_Free( chan->tempbuffer );\n\t\tchan->tempbuffer = NULL;\n\t}\n\tchan->tempbuffersize = 0;\n\n\tmemset( chan->flow, 0, sizeof( chan->flow ));\n}\n\n/*\n===============\nNetchan_OutOfBand\n\nSends an out-of-band datagram\n================\n*/\nvoid Netchan_OutOfBand( int net_socket, netadr_t adr, int len, const byte *data )\n{\n\tbyte buf[MAX_PRINT_MSG + 4] = { 0xff, 0xff, 0xff, 0xff };\n\n\tif( CL_IsPlaybackDemo( ))\n\t\treturn;\n\n\tif( len > sizeof( buf ) - 4 )\n\t{\n\t\tHost_Error( \"%s: overflow!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tmemcpy( &buf[4], data, len );\n\tNET_SendPacket( net_socket, len + 4, buf, adr );\n}\n\n/*\n===============\nNetchan_OutOfBandPrint\n\nSends a text message in an out-of-band datagram\n================\n*/\nvoid Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *fmt, ... )\n{\n\tva_list\tva;\n\tbyte buf[MAX_PRINT_MSG + 4] = { 0xff, 0xff, 0xff, 0xff };\n\tint len;\n\n\tif( CL_IsPlaybackDemo( ))\n\t\treturn;\n\n\tva_start( va, fmt );\n\tlen = Q_vsnprintf( &buf[4], sizeof( buf ) - 4, fmt, va );\n\tva_end( va );\n\n\tif( len < 0 )\n\t{\n\t\tHost_Error( \"%s: snprintf overflow!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tNET_SendPacket( net_socket, len + 4, buf, adr );\n}\n\n/*\n==============================\nNetchan_AllocFragbuf\n\n==============================\n*/\nstatic fragbuf_t *Netchan_AllocFragbuf( int fragment_size )\n{\n\tfragbuf_t\t*buf;\n\n\tbuf = (fragbuf_t *)Mem_Calloc( net_mempool, sizeof( fragbuf_t ) + fragment_size );\n\tMSG_Init( &buf->frag_message, \"Frag Message\", buf->frag_message_buf, fragment_size );\n\n\treturn buf;\n}\n\n/*\n==============================\nNetchan_AddFragbufToTail\n\n==============================\n*/\nstatic void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf )\n{\n\tfragbuf_t *p;\n\n\tbuf->next = NULL;\n\twait->fragbufcount++;\n\tp = wait->fragbufs;\n\n\tif( p )\n\t{\n\t\twhile( p->next )\n\t\t\tp = p->next;\n\t\tp->next = buf;\n\t}\n\telse wait->fragbufs = buf;\n}\n\n/*\n==============================\nNetchan_UpdateFlow\n\n==============================\n*/\nstatic void Netchan_UpdateFlow( netchan_t *chan )\n{\n\tfloat\tfaccumulatedtime = 0.0;\n\tint\ti, bytes = 0;\n\tint\tflow, start;\n\n\tif( !chan ) return;\n\n\tfor( flow = 0; flow < 2; flow++ )\n\t{\n\t\tflow_t\t*pflow = &chan->flow[flow];\n\n\t\tif(( host.realtime - pflow->nextcompute ) < FLOW_INTERVAL )\n\t\t\tcontinue;\n\n\t\tpflow->nextcompute = host.realtime + FLOW_INTERVAL;\n\t\tstart = pflow->current - 1;\n\n\t\t// compute data flow rate\n\t\tfor( i = 0; i < MASK_LATENT; i++ )\n\t\t{\n\t\t\tflowstats_t *pprev = &pflow->stats[(start - i) & MASK_LATENT];\n\t\t\tflowstats_t *pstat = &pflow->stats[(start - i - 1) & MASK_LATENT];\n\n\t\t\tfaccumulatedtime += ( pprev->time - pstat->time );\n\t\t\tbytes += pstat->size;\n\t\t}\n\n\t\tpflow->kbytespersec = (faccumulatedtime == 0.0f) ? 0.0f : bytes / faccumulatedtime / 1024.0f;\n\t\tpflow->avgkbytespersec = pflow->avgkbytespersec * FLOW_AVG + pflow->kbytespersec * (1.0f - FLOW_AVG);\n\t}\n}\n\n/*\n==============================\nNetchan_FragSend\n\nFragmentation buffer is full and user is prepared to send\n==============================\n*/\nvoid Netchan_FragSend( netchan_t *chan )\n{\n\tfragbufwaiting_t\t*wait;\n\tint\t\ti;\n\n\tif( !chan ) return;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\t// already something queued up, just leave in waitlist\n\t\tif( chan->fragbufs[i] ) continue;\n\n\t\twait = chan->waitlist[i];\n\n\t\t// nothing to queue?\n\t\tif( !wait ) continue;\n\n\t\tchan->waitlist[i] = wait->next;\n\n\t\twait->next = NULL;\n\n\t\t// copy in to fragbuf\n\t\tchan->fragbufs[i] = wait->fragbufs;\n\t\tchan->fragbufcount[i] = wait->fragbufcount;\n\n\t\t// throw away wait list\n\t\tMem_Free( wait );\n\t}\n}\n\n/*\n==============================\nNetchan_AddBufferToList\n\n==============================\n*/\nvoid Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf )\n{\n\t// Find best slot\n\tfragbuf_t\t*pprev, *n;\n\tint\tid1, id2;\n\n\tpbuf->next = NULL;\n\n\tif( !pplist )\n\t\treturn;\n\n\tif( !*pplist )\n\t{\n\t\tpbuf->next = *pplist;\n\t\t*pplist = pbuf;\n\t\treturn;\n\t}\n\n\tpprev = *pplist;\n\twhile( pprev->next )\n\t{\n\t\tn = pprev->next; // next item in list\n\t\tid1 = FRAG_GETID( n->bufferid );\n\t\tid2 = FRAG_GETID( pbuf->bufferid );\n\n\t\tif( id1 > id2 )\n\t\t{\n\t\t\t// insert here\n\t\t\tpbuf->next = n->next;\n\t\t\tpprev->next = pbuf;\n\t\t\treturn;\n\t\t}\n\t\tpprev = pprev->next;\n\t}\n\n\t// insert at end\n\tpprev->next = pbuf;\n}\n\n/*\n==============================\nNetchan_CreateFragments_\n\n==============================\n*/\nstatic void Netchan_CreateFragments_( netchan_t *chan, sizebuf_t *msg )\n{\n\tfragbuf_t\t\t*buf;\n\tint\t\tchunksize;\n\tint\t\tremaining;\n\tint\t\tbytes, pos;\n\tint\t\tbufferid = 1;\n\tfragbufwaiting_t\t*wait, *p;\n\n\tif( MSG_GetNumBytesWritten( msg ) == 0 )\n\t\treturn;\n\n\tchunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG );\n\n\twait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t ));\n\n\tif( chan->use_bz2 && memcmp( MSG_GetData( msg ), \"BZ2\", 4 ))\n\t{\n#if !XASH_DEDICATED\n\t\tbyte pbOut[0x10000];\n\t\tuint uSourceSize = MSG_GetNumBytesWritten( msg );\n\t\tuint uCompressedSize = MSG_GetNumBytesWritten( msg ) - 4;\n\t\tif( BZ2_bzBuffToBuffCompress( pbOut, &uCompressedSize, MSG_GetData( msg ), uSourceSize, 9, 0, 30 ) == BZ_OK )\n\t\t{\n\t\t\tif( uCompressedSize < uSourceSize )\n\t\t\t{\n\t\t\t\tCon_Reportf( \"Compressing split packet with BZip2 (%d -> %d bytes)\\n\", uSourceSize, uCompressedSize );\n\t\t\t\tmemcpy( msg->pData, \"BZ2\", 4 );\n\t\t\t\tmemcpy( msg->pData + 4, pbOut, uCompressedSize );\n\t\t\t\tMSG_SeekToBit( msg, ( uCompressedSize + 4 ) << 3, SEEK_SET );\n\t\t\t}\n\t\t}\n#else\n\t\tHost_Error( \"%s: BZ2 compression is not supported for server\", __func__ );\n#endif\n\t}\n\telse if( chan->use_lzss && !LZSS_IsCompressed( MSG_GetData( msg ), MSG_GetMaxBytes( msg )))\n\t{\n\t\tuint uCompressedSize = 0;\n\t\tuint uSourceSize = MSG_GetNumBytesWritten( msg );\n\t\tbyte *pbOut = LZSS_Compress( msg->pData, uSourceSize, &uCompressedSize );\n\n\t\tif( pbOut && uCompressedSize > 0 && uCompressedSize < uSourceSize )\n\t\t{\n\t\t\tCon_Reportf( \"Compressing split packet with LZSS (%d -> %d bytes)\\n\", uSourceSize, uCompressedSize );\n\t\t\tmemcpy( msg->pData, pbOut, uCompressedSize );\n\t\t\tMSG_SeekToBit( msg, uCompressedSize << 3, SEEK_SET );\n\t\t}\n\t\tif( pbOut ) free( pbOut );\n\t}\n\n\tremaining = MSG_GetNumBytesWritten( msg );\n\tpos = 0;\t// current position in bytes\n\n\twhile( remaining > 0 )\n\t{\n\t\tbytes = Q_min( remaining, chunksize );\n\t\tremaining -= bytes;\n\n\t\tbuf = Netchan_AllocFragbuf( bytes );\n\t\tbuf->bufferid = bufferid++;\n\n\t\t// Copy in data\n\t\tMSG_Clear( &buf->frag_message );\n\t\tMSG_WriteBits( &buf->frag_message, &msg->pData[pos], bytes << 3 );\n\n\t\tNetchan_AddFragbufToTail( wait, buf );\n\t\tpos += bytes;\n\t}\n\n\t// now add waiting list item to end of buffer queue\n\tif( !chan->waitlist[FRAG_NORMAL_STREAM] )\n\t{\n\t\tchan->waitlist[FRAG_NORMAL_STREAM] = wait;\n\t}\n\telse\n\t{\n\t\tp = chan->waitlist[FRAG_NORMAL_STREAM];\n\n\t\twhile( p->next )\n\t\t\tp = p->next;\n\t\tp->next = wait;\n\t}\n}\n\n/*\n==============================\nNetchan_CreateFragments\n\n==============================\n*/\nvoid Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg )\n{\n\t// always queue any pending reliable data ahead of the fragmentation buffer\n\tif( MSG_GetNumBytesWritten( &chan->message ) > 0 )\n\t{\n\t\tNetchan_CreateFragments_( chan, &chan->message );\n\t\tMSG_Clear( &chan->message );\n\t}\n\n\tNetchan_CreateFragments_( chan, msg );\n}\n\n/*\n==============================\nNetchan_FindBufferById\n\n==============================\n*/\nstatic fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate )\n{\n\tfragbuf_t\t*list = *pplist;\n\tfragbuf_t\t*pnewbuf;\n\n\twhile( list )\n\t{\n\t\tif( list->bufferid == id )\n\t\t\treturn list;\n\n\t\tlist = list->next;\n\t}\n\n\tif( !allocate )\n\t\treturn NULL;\n\n\t// create new entry\n\tpnewbuf = Netchan_AllocFragbuf( NET_MAX_FRAGMENT );\n\tpnewbuf->bufferid = id;\n\tNetchan_AddBufferToList( pplist, pnewbuf );\n\n\treturn pnewbuf;\n}\n\n/*\n==============================\nNetchan_CheckForCompletion\n\n==============================\n*/\nstatic void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers )\n{\n\tint\tc, id;\n\tint\tsize;\n\tfragbuf_t\t*p;\n\n\tsize = 0;\n\tc = 0;\n\n\tp = chan->incomingbufs[stream];\n\tif( !p ) return;\n\n\twhile( p )\n\t{\n\t\tsize += MSG_GetNumBytesWritten( &p->frag_message );\n\t\tc++;\n\n\t\tid = FRAG_GETID( p->bufferid );\n\t\tif( id != c )\n\t\t{\n\t\t\tif( chan->sock == NS_CLIENT )\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_ERROR \"Lost/dropped fragment would cause stall, retrying connection\\n\" );\n\t\t\t\tCbuf_AddText( \"reconnect\\n\" );\n\t\t\t}\n\t\t}\n\t\tp = p->next;\n\t}\n\n\t// received final message\n\tif( c == intotalbuffers )\n\t\tchan->incomingready[stream] = true;\n}\n\n/*\n==============================\nNetchan_CreateFileFragmentsFromBuffer\n\n==============================\n*/\nvoid Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size )\n{\n\tint        chunksize;\n\tint        send, pos;\n\tint        remaining;\n\tint        bufferid = 1;\n\tqboolean   firstfragment = true;\n\tfragbufwaiting_t *wait, *p;\n\tfragbuf_t  *buf;\n\tuint       originalSize = size;\n\tconst char *compressor = \"\";\n\n\tif( !size )\n\t\treturn;\n\n\tchunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG );\n\n\tif( chan->gs_netchan )\n\t{\n#if !XASH_DEDICATED\n\t\tuint uCompressedSize = size + 600;\n\t\tbyte *pbOut = Mem_Malloc( net_mempool, uCompressedSize );\n\t\tif( BZ2_bzBuffToBuffCompress( pbOut, &uCompressedSize, pbuf, size, 9, 0, 30 ) == BZ_OK && uCompressedSize < size )\n\t\t{\n\t\t\tCon_DPrintf( \"Compressing filebuffer (%s -> %s)\\n\", Q_memprint( size ), Q_memprint( uCompressedSize ));\n\t\t\tmemcpy( pbuf, pbOut, uCompressedSize );\n\t\t\tsize = uCompressedSize;\n\t\t\tcompressor = \"bz2\";\n\t\t}\n\t\tMem_Free( pbOut );\n#else\n\t\tHost_Error( \"%s: BZ2 compression is not supported for server\\n\", __func__ );\n#endif\n\t}\n\telse\n\t{\n\t\tuint uCompressedSize = 0;\n\t\tbyte *pbOut = LZSS_Compress( pbuf, size, &uCompressedSize );\n\t\tif( pbOut && uCompressedSize > 0 && uCompressedSize < size )\n\t\t{\n\t\t\tCon_DPrintf( \"Compressing filebuffer (%s -> %s)\\n\", Q_memprint( size ), Q_memprint( uCompressedSize ));\n\t\t\tmemcpy( pbuf, pbOut, uCompressedSize );\n\t\t\tsize = uCompressedSize;\n\t\t\tcompressor = \"lzss\";\n\t\t}\n\t\tif( pbOut )\n\t\t\tfree( pbOut );\n\t}\n\n\twait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t ));\n\tremaining = size;\n\tpos = 0;\n\n\twhile( remaining > 0 )\n\t{\n\t\tsend = Q_min( remaining, chunksize );\n\n\t\tbuf = Netchan_AllocFragbuf( send );\n\t\tbuf->bufferid = bufferid++;\n\n\t\t// copy in data\n\t\tMSG_Clear( &buf->frag_message );\n\n\t\tif( firstfragment )\n\t\t{\n\t\t\t// write filename\n\t\t\tMSG_WriteString( &buf->frag_message, filename );\n\t\t\t\n\t\t\t// write compressor name and uncompressed size\n\t\t\tif( chan->gs_netchan )\n\t\t\t{\n\t\t\t\tMSG_WriteString( &buf->frag_message, compressor );\n\t\t\t\tMSG_WriteLong( &buf->frag_message, originalSize );\n\t\t\t}\n\n\t\t\t// send a bit less on first package\n\t\t\tsend -= MSG_GetNumBytesWritten( &buf->frag_message );\n\n\t\t\tfirstfragment = false;\n\t\t}\n\n\t\tbuf->isbuffer = true;\n\t\tbuf->isfile = true;\n\t\tbuf->size = send;\n\t\tbuf->foffset = pos;\n\n\t\tMSG_WriteBits( &buf->frag_message, pbuf + pos, send << 3 );\n\n\t\tremaining -= send;\n\t\tpos += send;\n\n\t\tNetchan_AddFragbufToTail( wait, buf );\n\t}\n\n\t// now add waiting list item to end of buffer queue\n\tif( !chan->waitlist[FRAG_FILE_STREAM] )\n\t{\n\t\tchan->waitlist[FRAG_FILE_STREAM] = wait;\n\t}\n\telse\n\t{\n\t\tp = chan->waitlist[FRAG_FILE_STREAM];\n\n\t\twhile( p->next )\n\t\t\tp = p->next;\n\t\tp->next = wait;\n\t}\n}\n\n/*\n==============================\nNetchan_CreateFileFragments\n\n==============================\n*/\nint Netchan_CreateFileFragments( netchan_t *chan, const char *filename )\n{\n\tint         chunksize;\n\tint         send, pos;\n\tint         remaining;\n\tint         bufferid = 1;\n\tfs_offset_t filesize = 0;\n\tfs_offset_t originalSize = 0;\n\tint         compressedFileTime;\n\tint         fileTime;\n\tqboolean    firstfragment = true;\n\tqboolean    bCompressed = false;\n\tfragbufwaiting_t *wait, *p;\n\tfragbuf_t   *buf;\n\tchar        compressedfilename[sizeof( buf->filename ) + 5];\n\tconst char  *compressor = \"\";\n\tuint        uCompressedSize = 0;\n\tbyte        *compressed = NULL;\n\tbyte        *uncompressed = NULL;\n\n\t// shouldn't be critical, but just in case\n\tif( Q_strlen( filename ) > sizeof( buf->filename ) - 1 )\n\t{\n\t\tCon_Printf( S_WARN \"Unable to transfer %s due to path length overflow\\n\", filename );\n\t\treturn 0;\n\t}\n\n\tif(( filesize = FS_FileSize( filename, false )) <= 0 )\n\t{\n\t\tCon_Printf( S_WARN \"Unable to open %s for transfer\\n\", filename );\n\t\treturn 0;\n\t}\n\n\toriginalSize = filesize;\n\tchunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG );\n\n\tQ_snprintf( compressedfilename, sizeof( compressedfilename ), \"%s.ztmp\", filename );\n\tcompressedFileTime = FS_FileTime( compressedfilename, false );\n\tfileTime = FS_FileTime( filename, false );\n\n\tif( compressedFileTime >= fileTime )\n\t{\n\t\t// if compressed file already created and newer than source\n\t\tfs_offset_t compressedSize = FS_FileSize( compressedfilename, false );\n\t\tif( compressedSize != -1 )\n\t\t{\n\t\t\tbCompressed = true;\n\t\t\tfilesize = compressedSize;\n\t\t}\n\t}\n\telse\n\t{\n\t\tuncompressed = FS_LoadFile( filename, &filesize, false );\n\t\tif( chan->gs_netchan )\n\t\t{\n#if !XASH_DEDICATED\n\t\t\tcompressed = Mem_Malloc( net_mempool, filesize + 600 );\n\t\t\tuCompressedSize = filesize + 600;\n\t\t\tif( BZ2_bzBuffToBuffCompress( compressed, &uCompressedSize, uncompressed, filesize, 9, 0, 30 ) == BZ_OK && uCompressedSize < filesize )\n\t\t\t{\n\t\t\t\tCon_DPrintf( \"compressed file %s (%s -> %s)\\n\", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize ));\n\t\t\t\tFS_WriteFile( compressedfilename, compressed, uCompressedSize );\n\t\t\t\tfilesize = uCompressedSize;\n\t\t\t\tbCompressed = true;\n\t\t\t\tcompressor = \"bz2\";\n\t\t\t}\n\t\t\tMem_Free( compressed );\n#else\n\t\t\tHost_Error( \"%s: BZ2 compression is not supported for server\\n\", __func__ );\n#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcompressed = LZSS_Compress( uncompressed, filesize, &uCompressedSize );\n\t\t\tif( compressed && uCompressedSize > 0 && uCompressedSize < filesize )\n\t\t\t{\n\t\t\t\tCon_DPrintf( \"compressed file %s (%s -> %s)\\n\", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize ));\n\t\t\t\tFS_WriteFile( compressedfilename, compressed, uCompressedSize );\n\t\t\t\tfilesize = uCompressedSize;\n\t\t\t\tbCompressed = true;\n\t\t\t\tcompressor = \"lzss\";\n\t\t\t\tfree( compressed );\n\t\t\t}\n\t\t}\n\t\tMem_Free( uncompressed );\n\t}\n\n\twait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t ));\n\tremaining = filesize;\n\tpos = 0;\n\n\twhile( remaining > 0 )\n\t{\n\t\tsend = Q_min( remaining, chunksize );\n\n\t\tbuf = Netchan_AllocFragbuf( send );\n\t\tbuf->bufferid = bufferid++;\n\n\t\t// copy in data\n\t\tMSG_Clear( &buf->frag_message );\n\n\t\tif( firstfragment )\n\t\t{\n\t\t\t// Write filename\n\t\t\tMSG_WriteString( &buf->frag_message, filename );\n\n\t\t\t// write compressor name and uncompressed size\n\t\t\tif( chan->gs_netchan )\n\t\t\t{\n\t\t\t\tMSG_WriteString( &buf->frag_message, compressor );\n\t\t\t\tMSG_WriteLong( &buf->frag_message, (uint)originalSize );\n\t\t\t}\n\n\t\t\t// Send a bit less on first package\n\t\t\tsend -= MSG_GetNumBytesWritten( &buf->frag_message );\n\n\t\t\tfirstfragment = false;\n\t\t}\n\n\t\tbuf->isfile = true;\n\t\tbuf->size = send;\n\t\tbuf->foffset = pos;\n\t\tbuf->iscompressed = bCompressed;\n\t\tQ_strncpy( buf->filename, filename, sizeof( buf->filename ));\n\n\t\tpos += send;\n\t\tremaining -= send;\n\n\t\tNetchan_AddFragbufToTail( wait, buf );\n\t}\n\n\t// now add waiting list item to end of buffer queue\n\tif( !chan->waitlist[FRAG_FILE_STREAM] )\n\t{\n\t\tchan->waitlist[FRAG_FILE_STREAM] = wait;\n\t}\n\telse\n\t{\n\t\tp = chan->waitlist[FRAG_FILE_STREAM];\n\t\twhile( p->next )\n\t\t\tp = p->next;\n\t\tp->next = wait;\n\t}\n\n\treturn 1;\n}\n\n/*\n==============================\nNetchan_FlushIncoming\n\n==============================\n*/\nvoid Netchan_FlushIncoming( netchan_t *chan, int stream )\n{\n\tfragbuf_t\t*p, *n;\n\n\tMSG_Clear( &net_message );\n\n\tp = chan->incomingbufs[ stream ];\n\n\twhile( p )\n\t{\n\t\tn = p->next;\n\t\tMem_Free( p );\n\t\tp = n;\n\t}\n\tchan->incomingbufs[stream] = NULL;\n\tchan->incomingready[stream] = false;\n}\n\n/*\n==============================\nNetchan_CopyNormalFragments\n\n==============================\n*/\nqboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length )\n{\n\tsize_t\tsize = 0;\n\tfragbuf_t\t*p, *n;\n\n\tif( !chan->incomingready[FRAG_NORMAL_STREAM] )\n\t\treturn false;\n\n\tif( !chan->incomingbufs[FRAG_NORMAL_STREAM] )\n\t{\n\t\tchan->incomingready[FRAG_NORMAL_STREAM] = false;\n\t\treturn false;\n\t}\n\n\tp = chan->incomingbufs[FRAG_NORMAL_STREAM];\n\n\tMSG_Init( msg, \"NetMessage\", net_message_buffer, sizeof( net_message_buffer ));\n\n\twhile( p )\n\t{\n\t\tn = p->next;\n\n\t\t// copy it in\n\t\tMSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message ));\n\t\tsize += MSG_GetNumBytesWritten( &p->frag_message );\n\n\t\tMem_Free( p );\n\t\tp = n;\n\t}\n\n\tif( chan->use_bz2 && !memcmp( MSG_GetData( msg ), \"BZ2\", 4 ))\n\t{\n#if !XASH_DEDICATED\n\t\tbyte buf[0x10000];\n\t\tuint uDecompressedLen = sizeof( buf );\n\t\tint bz2_err = BZ2_bzBuffToBuffDecompress( buf, &uDecompressedLen, MSG_GetData( msg ) + 4, MSG_GetNumBytesWritten( msg ) - 4, 1, 0 );\n\n\t\tif( bz2_err == BZ_OK )\n\t\t{\n\t\t\tsize = uDecompressedLen;\n\t\t\tmemcpy( msg->pData, buf, size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: BZ2 decompression failed (%d)\\n\", __func__, bz2_err );\n\t\t\treturn false;\n\t\t}\n#else\n\t\tHost_Error( \"%s: BZ2 compression is not supported for server\\n\", __func__ );\n#endif\n\t}\n\telse if( chan->use_lzss && LZSS_IsCompressed( MSG_GetData( msg ), size ))\n\t{\n\t\tuint\tuDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg ), size );\n\t\tbyte\tbuf[NET_MAX_MESSAGE];\n\n\t\tif( uDecompressedLen <= sizeof( buf ))\n\t\t{\n\t\t\tsize = LZSS_Decompress( MSG_GetData( msg ), buf, size, sizeof( buf ));\n\t\t\tmemcpy( msg->pData, buf, size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// g-cont. this should not happens\n\t\t\tCon_Printf( S_ERROR \"buffer to small to decompress message\\n\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tchan->incomingbufs[FRAG_NORMAL_STREAM] = NULL;\n\n\t// reset flag\n\tchan->incomingready[FRAG_NORMAL_STREAM] = false;\n\n\t// tell about message size\n\tif( length ) *length = size;\n\n\treturn true;\n}\n\n/*\n==============================\nNetchan_CopyFileFragments\n\n==============================\n*/\nqboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg )\n{\n\tchar\tfilename[MAX_OSPATH], compressor[32];\n\tuint\tuncompressedSize;\n\tint\tnsize, pos;\n\tbyte\t*buffer;\n\tfragbuf_t\t*p, *n;\n\n\tif( !chan->incomingready[FRAG_FILE_STREAM] )\n\t\treturn false;\n\n\tif( !chan->incomingbufs[FRAG_FILE_STREAM] )\n\t{\n\t\tchan->incomingready[FRAG_FILE_STREAM] = false;\n\t\treturn false;\n\t}\n\n\tp = chan->incomingbufs[FRAG_FILE_STREAM];\n\n\tMSG_Init( msg, \"NetMessage\", net_message_buffer, sizeof( net_message_buffer ));\n\n\t// copy in first chunk so we can get filename out\n\tMSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message ));\n\tMSG_Clear( msg );\n\n\tQ_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));\n\tcompressor[0] = 0;\n\tif( chan->gs_netchan )\n\t{\n\t\tQ_strncpy( compressor, MSG_ReadString( msg ), sizeof( compressor ));\n\t\tuncompressedSize = MSG_ReadLong( msg );\n\t}\n\n\tif( !COM_CheckString( filename ))\n\t{\n\t\tCon_Printf( S_ERROR \"file fragment received with no filename\\nFlushing input queue\\n\" );\n\t\tNetchan_FlushIncoming( chan, FRAG_FILE_STREAM );\n\t\treturn false;\n\t}\n\telse if( filename[0] != '!' && !COM_IsSafeFileToDownload( filename ))\n\t{\n\t\tCon_Printf( S_ERROR \"file fragment received with bad path, ignoring\\n\" );\n\t\tNetchan_FlushIncoming( chan, FRAG_FILE_STREAM );\n\t\treturn false;\n\t}\n\n\tif( filename[0] != '!' )\n\t{\n\t\tstring temp_filename;\n\t\tQ_snprintf( temp_filename, sizeof( temp_filename ), DEFAULT_DOWNLOADED_DIRECTORY \"%s\", filename );\n\t\tQ_strncpy( filename, temp_filename, sizeof( filename ));\n\t}\n\n\tQ_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename ));\n\n\tif( filename[0] != '!' && FS_FileExists( filename, false ))\n\t{\n\t\tCon_Printf( S_ERROR \"can't download %s, already exists\\n\", filename );\n\t\tNetchan_FlushIncoming( chan, FRAG_FILE_STREAM );\n\t\treturn true;\n\t}\n\n\t// create file from buffers\n\tnsize = 0;\n\twhile ( p )\n\t{\n\t\tnsize += MSG_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well\n\t\tif( p == chan->incomingbufs[FRAG_FILE_STREAM] )\n\t\t\tnsize -= MSG_GetNumBytesRead( msg );\n\t\tp = p->next;\n\t}\n\n\tbuffer = Mem_Calloc( net_mempool, nsize + 1 );\n\tp = chan->incomingbufs[FRAG_FILE_STREAM];\n\tpos = 0;\n\n\twhile( p )\n\t{\n\t\tint\tcursize;\n\n\t\tn = p->next;\n\n\t\tcursize = MSG_GetNumBytesWritten( &p->frag_message );\n\n\t\t// first message has the file name, don't write that into the data stream,\n\t\t// just write the rest of the actual data\n\t\tif( p == chan->incomingbufs[FRAG_FILE_STREAM] )\n\t\t{\n\t\t\t// copy it in\n\t\t\tcursize -= MSG_GetNumBytesRead( msg );\n\t\t\tmemcpy( &buffer[pos], &p->frag_message.pData[MSG_GetNumBytesRead( msg )], cursize );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmemcpy( &buffer[pos], p->frag_message.pData, cursize );\n\t\t}\n\n\t\tpos += cursize;\n\t\tMem_Free( p );\n\t\tp = n;\n\t}\n\n\tif( chan->gs_netchan && chan->use_bz2 && !Q_stricmp( compressor, \"bz2\" ))\n\t{\n#if !XASH_DEDICATED\n\t\tbyte *uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize );\n\n\t\tCon_DPrintf( \"Decompressing file %s (%d -> %d bytes)\\n\", filename, nsize, uncompressedSize );\n\t\tif( BZ2_bzBuffToBuffDecompress( uncompressedBuffer, &uncompressedSize, buffer, nsize, 1, 0 ) != BZ_OK )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"BZ2 decompression failed for %s\\n\", filename );\n\t\t\tMem_Free( buffer );\n\t\t\tMem_Free( uncompressedBuffer );\n\t\t\tNetchan_FlushIncoming( chan, FRAG_FILE_STREAM );\n\t\t\treturn false;\n\t\t}\n\t\tMem_Free( buffer );\n\t\tnsize = uncompressedSize;\n\t\tbuffer = uncompressedBuffer;\n#else\n\t\tHost_Error( \"%s: BZ2 compression is not supported for server\", __func__ );\n#endif\n\t}\n\telse if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize + 1 ))\n\t{\n\t\tbyte *uncompressedBuffer;\n\n\t\tuncompressedSize = LZSS_GetActualSize( buffer, nsize + 1 ) + 1;\n\t\tuncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize );\n\n\t\tnsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize + 1, uncompressedSize );\n\t\tMem_Free( buffer );\n\t\tbuffer = uncompressedBuffer;\n\t}\n\n\t// customization files goes int tempbuffer\n\tif( filename[0] == '!' )\n\t{\n\t\tif( chan->tempbuffer )\n\t\t\tMem_Free( chan->tempbuffer );\n\t\tchan->tempbuffer = buffer;\n\t\tchan->tempbuffersize = nsize;\n\t}\n\telse\n\t{\n\t\t// g-cont. it's will be stored downloaded files directly into game folder\n\t\tFS_WriteFile( filename, buffer, nsize );\n\t\tMem_Free( buffer );\n\t}\n\n\t// clear remnants\n\tMSG_Clear( msg );\n\n\tchan->incomingbufs[FRAG_FILE_STREAM] = NULL;\n\tchan->incomingready[FRAG_FILE_STREAM] = false;\n\n\treturn true;\n}\n\nstatic qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length )\n{\n\tint\ti, buffer, offset;\n\tint\tcount, length;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\tif( !frag_message[i] )\n\t\t\tcontinue;\n\n\t\tbuffer = FRAG_GETID( fragid[i] );\n\t\tcount = FRAG_GETCOUNT( fragid[i] );\n\t\toffset = BitByte( frag_offset[i] );\n\t\tlength = BitByte( frag_length[i] );\n\n\t\tif( buffer < 0 || buffer > NET_MAX_BUFFER_ID )\n\t\t\treturn false;\n\n\t\tif( count < 0 || count > NET_MAX_BUFFERS_COUNT )\n\t\t\treturn false;\n\n\t\tif( length < 0 || length > ( FRAGMENT_MAX_SIZE << 3 ))\n\t\t\treturn false;\n\n\t\tif( offset < 0 || offset > ( FRAGMENT_MAX_SIZE << 3 ))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n==============================\nNetchan_UpdateProgress\n\n==============================\n*/\nvoid Netchan_UpdateProgress( netchan_t *chan )\n{\n#if !XASH_DEDICATED\n\tfragbuf_t *p;\n\tint\ti, c = 0;\n\tint\ttotal = 0;\n\tfloat\tbestpercent = 0.0;\n\n\tif( host.downloadcount == 0 )\n\t{\n\t\tscr_download.value = -1.0f;\n\t\thost.downloadfile[0] = '\\0';\n\t}\n\n\t// do show slider for file downloads.\n\tif( !chan->incomingbufs[FRAG_FILE_STREAM] )\n\t\treturn;\n\n\tfor( i = MAX_STREAMS - 1; i >= 0; i-- )\n\t{\n\t\t// receiving data\n\t\tif( chan->incomingbufs[i] )\n\t\t{\n\t\t\tp = chan->incomingbufs[i];\n\n\t\t\ttotal = FRAG_GETCOUNT( p->bufferid );\n\n\t\t\twhile( p )\n\t\t\t{\n\t\t\t\tc++;\n\t\t\t\tp = p->next;\n\t\t\t}\n\n\t\t\tif( total )\n\t\t\t{\n\t\t\t\tfloat\tpercent = 100.0f * (float)c / (float)total;\n\n\t\t\t\tif( percent > bestpercent )\n\t\t\t\t\tbestpercent = percent;\n\t\t\t}\n\n\t\t\tp = chan->incomingbufs[i];\n\n\t\t\tif( i == FRAG_FILE_STREAM )\n\t\t\t{\n\t\t\t\tchar\tsz[MAX_SYSPATH];\n\t\t\t\tchar\t*in, *out;\n\t\t\t\tint\tlen = 0;\n\n\t\t\t\tin = (char *)MSG_GetData( &p->frag_message );\n\t\t\t\tout = sz;\n\n\t\t\t\twhile( *in )\n\t\t\t\t{\n\t\t\t\t\t*out++ = *in++;\n\t\t\t\t\tlen++;\n\t\t\t\t\tif( len > 128 )\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t*out = '\\0';\n\n\t\t\t\tif( COM_CheckStringEmpty( sz ) && sz[0] != '!' )\n\t\t\t\t\tQ_strncpy( host.downloadfile, sz, sizeof( host.downloadfile ));\n\t\t\t}\n\t\t}\n\t\telse if( chan->fragbufs[i] )\t// Sending data\n\t\t{\n\t\t\tif( chan->fragbufcount[i] )\n\t\t\t{\n\t\t\t\tfloat\tpercent = 100.0f * (float)chan->fragbufs[i]->bufferid / (float)chan->fragbufcount[i];\n\n\t\t\t\tif( percent > bestpercent )\n\t\t\t\t\tbestpercent = percent;\n\t\t\t}\n\t\t}\n\n\t}\n\n\tscr_download.value = bestpercent;\n#endif // XASH_DEDICATED\n}\n\n/*\n===============\nNetchan_TransmitBits\n\ntries to send an unreliable message to a connection, and handles the\ntransmition / retransmition of the reliable messages.\n\nA 0 length will still generate a packet and deal with the reliable messages.\n================\n*/\nvoid Netchan_TransmitBits( netchan_t *chan, int length, const byte *data )\n{\n\tbyte\tsend_buf[NET_MAX_MESSAGE];\n\tqboolean\tsend_reliable_fragment;\n\tuint\tw1, w2, statId;\n\tqboolean\tsend_reliable;\n\tsizebuf_t\tsend;\n\tint\ti, j;\n\tfloat\tfRate;\n\n\t// check for message overflow\n\tif( MSG_CheckOverflow( &chan->message ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s:outgoing message overflow\\n\", NET_AdrToString( chan->remote_address ));\n\t\treturn;\n\t}\n\n\tif( chan->pfnBlockSize == NULL ) // not initialized\n\t\treturn;\n\n\t// if the remote side dropped the last reliable message, resend it\n\tsend_reliable = false;\n\n\tif( chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence )\n\t\tsend_reliable = true;\n\n\t// A packet can have \"reliable payload + frag payload + unreliable payload\n\t// frag payload can be a file chunk, if so, it needs to be parsed on the receiving end and reliable payload + unreliable payload need\n\t// to be passed on to the message queue.  The processing routine needs to be able to handle the case where a message comes in and a file\n\t// transfer completes\n\n\t// if the reliable transmit buffer is empty, copy the current message out\n\tif( !chan->reliable_length )\n\t{\n\t\tqboolean\tsend_frag = false;\n\t\tfragbuf_t\t*pbuf;\n\n\t\t// will be true if we are active and should let chan->message get some bandwidth\n\t\tint\tsend_from_frag[MAX_STREAMS] = { 0, 0 };\n\t\tint\tsend_from_regular = 0;\n\n\t\t// if we have data in the waiting list(s) and we have cleared the current queue(s), then\n\t\t// push the waitlist(s) into the current queue(s)\n\t\tNetchan_FragSend( chan );\n\n\t\t// sending regular payload\n\t\tsend_from_regular = MSG_GetNumBytesWritten( &chan->message ) ? 1 : 0;\n\n\t\t// check to see if we are sending a frag payload\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tif( chan->fragbufs[i] )\n\t\t\t\tsend_from_frag[i] = 1;\n\t\t}\n\n\t\t// stall reliable payloads if sending from frag buffer\n\t\tif( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] ))\n\t\t{\n\t\t\tint maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT );\n\t\t\tsend_from_regular = false;\n\n\t\t\tif( maxsize == 0 )\n\t\t\t\tmaxsize = MAX_RELIABLE_PAYLOAD;\n\n\t\t\t// if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer\n\t\t\tif( MSG_GetNumBytesWritten( &chan->message ) + (((uint)length) >> 3) > maxsize )\n\t\t\t{\n\t\t\t\tNetchan_CreateFragments_( chan, &chan->message );\n\t\t\t\tMSG_Clear( &chan->message );\n\t\t\t}\n\t\t}\n\n\t\t// startpos will be zero if there is no regular payload\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tchan->frag_startpos[i] = 0;\n\n\t\t\t// assume no fragment is being sent\n\t\t\tchan->reliable_fragment[i] = 0;\n\t\t\tchan->reliable_fragid[i] = 0;\n\t\t\tchan->frag_length[i] = 0;\n\n\t\t\tif( send_from_frag[i] )\n\t\t\t{\n\t\t\t\tsend_frag = true;\n\t\t\t}\n\t\t}\n\n\t\tif( send_from_regular || send_frag )\n\t\t{\n\t\t\tchan->reliable_sequence ^= 1;\n\t\t\tsend_reliable = true;\n\t\t}\n\n\t\tif( send_from_regular )\n\t\t{\n\t\t\tmemcpy( chan->reliable_buf, chan->message_buf, MSG_GetNumBytesWritten( &chan->message ));\n\t\t\tchan->reliable_length = MSG_GetNumBitsWritten( &chan->message );\n\t\t\tMSG_Clear( &chan->message );\n\n\t\t\t// if we send fragments, this is where they'll start\n\t\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t\t\tchan->frag_startpos[i] = chan->reliable_length;\n\t\t}\n\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tint\tnewpayloadsize;\n\t\t\tint\tfragment_size;\n\n\t\t\t// is there someting in the fragbuf?\n\t\t\tpbuf = chan->fragbufs[i];\n\t\t\tfragment_size = 0;\n\n\t\t\tif( pbuf )\n\t\t\t{\n\t\t\t\tfragment_size = MSG_GetNumBytesWritten( &pbuf->frag_message );\n\n\t\t\t\t// files set size a bit differently.\n\t\t\t\tif( pbuf->isfile && !pbuf->isbuffer )\n\t\t\t\t{\n\t\t\t\t\tfragment_size = pbuf->size;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tnewpayloadsize = (( chan->reliable_length + ( fragment_size << 3 )) + 7 ) >> 3;\n\n\t\t\t// make sure we have enought space left\n\t\t\tif( send_from_frag[i] && pbuf && newpayloadsize < NET_MAX_FRAGMENT )\n\t\t\t{\n\t\t\t\tsizebuf_t\ttemp;\n\n\t\t\t\t// which buffer are we sending ?\n\t\t\t\tchan->reliable_fragid[i] = MAKE_FRAGID( pbuf->bufferid, chan->fragbufcount[i] );\n\n\t\t\t\t// if it's not in-memory, then we'll need to copy it in frame the file handle.\n\t\t\t\tif( pbuf->isfile && !pbuf->isbuffer )\n\t\t\t\t{\n\t\t\t\t\tbyte\tfilebuffer[NET_MAX_FRAGMENT];\n\t\t\t\t\tfile_t\t*file;\n\n\t\t\t\t\tif( pbuf->iscompressed )\n\t\t\t\t\t{\n\t\t\t\t\t\tchar\tcompressedfilename[sizeof( pbuf->filename ) + 5];\n\n\t\t\t\t\t\tQ_snprintf( compressedfilename, sizeof( compressedfilename ), \"%s.ztmp\", pbuf->filename );\n\t\t\t\t\t\tfile = FS_Open( compressedfilename, \"rb\", false );\n\t\t\t\t\t}\n\t\t\t\t\telse file = FS_Open( pbuf->filename, \"rb\", false );\n\n\t\t\t\t\tFS_Seek( file, pbuf->foffset, SEEK_SET );\n\t\t\t\t\tFS_Read( file, filebuffer, pbuf->size );\n\n\t\t\t\t\tMSG_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 );\n\t\t\t\t\tFS_Close( file );\n\t\t\t\t}\n\n\t\t\t\t// copy frag stuff on top of current buffer\n\t\t\t\tMSG_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 );\n\t\t\t\tMSG_WriteBits( &temp, MSG_GetData( &pbuf->frag_message ), MSG_GetNumBitsWritten( &pbuf->frag_message ));\n\t\t\t\tchan->reliable_length += MSG_GetNumBitsWritten( &pbuf->frag_message );\n\t\t\t\tchan->frag_length[i] = MSG_GetNumBitsWritten( &pbuf->frag_message );\n\n\t\t\t\t// unlink pbuf\n\t\t\t\tNetchan_UnlinkFragment( pbuf, &chan->fragbufs[i] );\n\n\t\t\t\tchan->reliable_fragment[i] = 1;\n\n\t\t\t\t// offset the rest of the starting positions\n\t\t\t\tfor( j = i + 1; j < MAX_STREAMS; j++ )\n\t\t\t\t\tchan->frag_startpos[j] += chan->frag_length[i];\n\t\t\t}\n\t\t}\n\t}\n\n\tmemset( send_buf, 0, sizeof( send_buf ));\n\tMSG_Init( &send, \"NetSend\", send_buf, sizeof( send_buf ));\n\n\t// prepare the packet header\n\tw1 = chan->outgoing_sequence | (send_reliable << 31);\n\tw2 = chan->incoming_sequence | (chan->incoming_reliable_sequence << 31);\n\n\tsend_reliable_fragment = false;\n\n\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t{\n\t\tif( chan->reliable_fragment[i] )\n\t\t{\n\t\t\tsend_reliable_fragment = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( send_reliable && send_reliable_fragment )\n\t\tSetBits( w1, BIT( 30 ));\n\n\tchan->outgoing_sequence++;\n\n\tMSG_WriteLong( &send, w1 );\n\tMSG_WriteLong( &send, w2 );\n\n\t// send the qport if we are a client\n\tif( chan->sock == NS_CLIENT && !chan->gs_netchan )\n\t{\n\t\tMSG_WriteWord( &send, (int)net_qport.value );\n\t}\n\n\tif( send_reliable && send_reliable_fragment )\n\t{\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tif( chan->reliable_fragment[i] )\n\t\t\t{\n\t\t\t\tMSG_WriteByte( &send, 1 );\n\t\t\t\tMSG_WriteLong( &send, chan->reliable_fragid[i] );\n\t\t\t\tif( chan->gs_netchan )\n\t\t\t\t{\n\t\t\t\t\tMSG_WriteShort( &send, chan->frag_startpos[i] >> 3 );\n\t\t\t\t\tMSG_WriteShort( &send, chan->frag_length[i] >> 3 );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMSG_WriteLong( &send, chan->frag_startpos[i] );\n\t\t\t\t\tMSG_WriteLong( &send, chan->frag_length[i] );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMSG_WriteByte( &send, 0 );\n\t\t\t}\n\t\t}\n\t}\n\n\t// copy the reliable message to the packet first\n\tif( send_reliable )\n\t{\n\t\tMSG_WriteBits( &send, chan->reliable_buf, chan->reliable_length );\n\t\tchan->last_reliable_sequence = chan->outgoing_sequence - 1;\n\t}\n\n\tif( length )\n\t{\n\t\tint maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_UNRELIABLE );\n\n\t\tif( (( MSG_GetNumBytesWritten( &send ) + length ) >> 3) <= maxsize )\n\t\t\tMSG_WriteBits( &send, data, length );\n\t\telse Con_Printf( S_WARN \"%s: unreliable message overflow: %d\\n\", __func__, MSG_GetNumBytesWritten( &send ) );\n\t}\n\n\t// deal with packets that are too small for some networks\n\tif( MSG_GetNumBytesWritten( &send ) < 16 && !NET_IsLocalAddress( chan->remote_address )) // packet too small for some networks\n\t{\n\t\t// go ahead and pad a full 16 extra bytes -- this only happens during authentication / signon\n\t\tfor( i = MSG_GetNumBytesWritten( &send ); i < 16; i++ )\n\t\t{\n\t\t\tif( chan->sock == NS_CLIENT )\n\t\t\t\tMSG_BeginClientCmd( &send, clc_nop );\n\t\t\telse if( chan->sock == NS_SERVER )\n\t\t\t\tMSG_BeginServerCmd( &send, svc_nop );\n\t\t\telse break;\n\t\t}\n\t}\n\n\tstatId = chan->flow[FLOW_OUTGOING].current & MASK_LATENT;\n\tchan->flow[FLOW_OUTGOING].stats[statId].size = MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE;\n\tchan->flow[FLOW_OUTGOING].stats[statId].time = host.realtime;\n\tchan->flow[FLOW_OUTGOING].totalbytes += chan->flow[FLOW_OUTGOING].stats[statId].size;\n\tchan->flow[FLOW_OUTGOING].current++;\n\n\tNetchan_UpdateFlow( chan );\n\n\tchan->total_sended += MSG_GetNumBytesWritten( &send );\n\n\t// send the datagram\n\tif( !CL_IsPlaybackDemo( ))\n\t{\n\t\tint splitsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT );\n\n\t\tif( chan->use_munge )\n\t\t\tCOM_Munge2( send.pData + 8, MSG_GetNumBytesWritten( &send ) - 8, (byte)( chan->outgoing_sequence - 1 ));\n\n\t\tNET_SendPacketEx( chan->sock, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), chan->remote_address, splitsize );\n\t}\n\n\tif( SV_Active() && sv_lan.value && sv_lan_rate.value > 1000.0f )\n\t\tfRate = 1.0f / sv_lan_rate.value;\n\telse fRate = 1.0f / chan->rate;\n\n\tif( chan->cleartime < host.realtime )\n\t\tchan->cleartime = host.realtime;\n\n\tchan->cleartime += ( MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ) * fRate;\n\n\tif( net_showpackets.value && net_showpackets.value != 2.0f )\n\t{\n\t\tCon_Printf( \" %s --> sz=%i seq=%i ack=%i rel=%i tm=%f\\n\"\n\t\t\t, ns_strings[chan->sock]\n\t\t\t, MSG_GetNumBytesWritten( &send )\n\t\t\t, ( chan->outgoing_sequence - 1 ) & 63\n\t\t\t, chan->incoming_sequence & 63\n\t\t\t, send_reliable ? 1 : 0\n\t\t\t, (float)host.realtime );\n\t}\n}\n\n/*\n=================\nNetchan_Process\n\ncalled when the current net_message is from remote_address\nmodifies net_message so that it points to the packet payload\n=================\n*/\nqboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )\n{\n\tuint\tsequence, sequence_ack;\n\tuint\treliable_ack, reliable_message;\n\tuint\tfragid[MAX_STREAMS] = { 0, 0 };\n\tqboolean\tfrag_message[MAX_STREAMS] = { false, false };\n\tint\tfrag_offset[MAX_STREAMS] = { 0, 0 };\n\tint\tfrag_length[MAX_STREAMS] = { 0, 0 };\n\tqboolean\tmessage_contains_fragments;\n\tint\ti, qport, statId;\n\n\t// get sequence numbers\n\tMSG_Clear( msg );\n\tsequence = MSG_ReadLong( msg );\n\tsequence_ack = MSG_ReadLong( msg );\n\n\tif( chan->use_munge )\n\t\tCOM_UnMunge2( msg->pData + 8, ( msg->nDataBits >> 3 ) - 8, sequence & 0xFF );\n\n\t// read the qport if we are a server\n\tif( chan->sock == NS_SERVER )\n\t\tqport = MSG_ReadShort( msg );\n\n\treliable_message = sequence >> 31;\n\treliable_ack = sequence_ack >> 31;\n\n\tmessage_contains_fragments = FBitSet( sequence, BIT( 30 )) ? true : false;\n\n\tif( message_contains_fragments )\n\t{\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tif( MSG_ReadByte( msg ))\n\t\t\t{\n\t\t\t\tfrag_message[i] = true;\n\t\t\t\tfragid[i] = MSG_ReadLong( msg );\n\t\t\t\tif( chan->gs_netchan )\n\t\t\t\t{\n\t\t\t\t\tfrag_offset[i] = MSG_ReadShort( msg ) << 3;\n\t\t\t\t\tfrag_length[i] = MSG_ReadShort( msg ) << 3;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfrag_offset[i] = MSG_ReadLong( msg );\n\t\t\t\t\tfrag_length[i] = MSG_ReadLong( msg );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( !Netchan_Validate( chan, msg, frag_message, fragid, frag_offset, frag_length ))\n\t\t\treturn false;\n\t}\n\n\tsequence &= ~BIT( 31 );\n\tsequence &= ~BIT( 30 );\n\tsequence_ack &= ~BIT( 30 );\n\tsequence_ack &= ~BIT( 31 );\n\n\tif( net_showpackets.value && net_showpackets.value != 3.0f )\n\t{\n\t\tCon_Printf( \" %s <-- sz=%i seq=%i ack=%i rel=%i tm=%f\\n\"\n\t\t\t, ns_strings[chan->sock]\n\t\t\t, MSG_GetMaxBytes( msg )\n\t\t\t, sequence & 63\n\t\t\t, sequence_ack & 63\n\t\t\t, reliable_message\n\t\t\t, host.realtime );\n\t}\n\n\t// discard stale or duplicated packets\n\tif( sequence <= (uint)chan->incoming_sequence )\n\t{\n\t\tif( net_showdrop.value )\n\t\t{\n\t\t\tconst char *adr = NET_AdrToString( chan->remote_address );\n\n\t\t\tif( sequence == (uint)chan->incoming_sequence )\n\t\t\t\tCon_Printf( \"%s:duplicate packet %i at %i\\n\", adr, sequence, chan->incoming_sequence );\n\t\t\telse Con_Printf( \"%s:out of order packet %i at %i\\n\", adr, sequence, chan->incoming_sequence );\n\t\t}\n\t\treturn false;\n\t}\n\n\t// dropped packets don't keep the message from being used\n\tnet_drop = sequence - ( chan->incoming_sequence + 1 );\n\tif( net_drop > 0 && net_showdrop.value )\n\t\tCon_Printf( \"%s:dropped %i packets at %i\\n\", NET_AdrToString( chan->remote_address ), net_drop, sequence );\n\n\t// if the current outgoing reliable message has been acknowledged\n\t// clear the buffer to make way for the next\n\tif( reliable_ack == (uint)chan->reliable_sequence )\n\t{\n\t\t// make sure we actually could have ack'd this message\n\t\tif( sequence_ack >= (uint)chan->last_reliable_sequence )\n\t\t{\n\t\t\tchan->reliable_length = 0;\t// it has been received\n\t\t}\n\t}\n\n\t// if this message contains a reliable message, bump incoming_reliable_sequence\n\tchan->incoming_sequence = sequence;\n\tchan->incoming_acknowledged = sequence_ack;\n\tchan->incoming_reliable_acknowledged = reliable_ack;\n\tif( reliable_message )\n\t{\n\t\tchan->incoming_reliable_sequence ^= 1;\n\t}\n\n\tchan->last_received = host.realtime;\n\n\t// Update data flow stats\n\tstatId = chan->flow[FLOW_INCOMING].current & MASK_LATENT;\n\tchan->flow[FLOW_INCOMING].stats[statId].size = MSG_GetMaxBytes( msg ) + UDP_HEADER_SIZE;\n\tchan->flow[FLOW_INCOMING].stats[statId].time = host.realtime;\n\tchan->flow[FLOW_INCOMING].totalbytes += chan->flow[FLOW_INCOMING].stats[statId].size;\n\tchan->flow[FLOW_INCOMING].current++;\n\n\tNetchan_UpdateFlow( chan );\n\n\tchan->total_received += MSG_GetMaxBytes( msg );\n\n\tif( message_contains_fragments )\n\t{\n\t\tfor( i = 0; i < MAX_STREAMS; i++ )\n\t\t{\n\t\t\tint\tj, inbufferid;\n\t\t\tint\tintotalbuffers;\n\t\t\tint\toldpos, curbit;\n\t\t\tint\tnumbitstoremove;\n\t\t\tfragbuf_t\t*pbuf;\n\n\t\t\tif( !frag_message[i] )\n\t\t\t\tcontinue;\n\n\t\t\tinbufferid = FRAG_GETID( fragid[i] );\n\t\t\tintotalbuffers = FRAG_GETCOUNT( fragid[i] );\n\n\t\t\tif( fragid[i] != 0 )\n\t\t\t{\n\t\t\t\tpbuf = Netchan_FindBufferById( &chan->incomingbufs[i], fragid[i], true );\n\n\t\t\t\tif( pbuf )\n\t\t\t\t{\n\t\t\t\t\tbyte\tbuffer[NET_MAX_FRAGMENT];\n\t\t\t\t\tint\tbits, size;\n\t\t\t\t\tsizebuf_t\ttemp;\n\n\t\t\t\t\tsize = MSG_GetNumBitsRead( msg ) + frag_offset[i];\n\t\t\t\t\tbits = frag_length[i];\n\n\t\t\t\t\t// copy in data\n\t\t\t\t\tMSG_Clear( &pbuf->frag_message );\n\n\t\t\t\t\tMSG_StartReading( &temp, msg->pData, MSG_GetMaxBytes( msg ), size, -1 );\n\t\t\t\t\tMSG_ReadBits( &temp, buffer, bits );\n\t\t\t\t\tMSG_WriteBits( &pbuf->frag_message, buffer, bits );\n\t\t\t\t}\n\n\t\t\t\t// count # of incoming bufs we've queued? are we done?\n\t\t\t\tNetchan_CheckForCompletion( chan, i, intotalbuffers );\n\t\t\t}\n\n\t\t\t// rearrange incoming data to not have the frag stuff in the middle of it\n\t\t\toldpos = MSG_GetNumBitsRead( msg );\n\t\t\tcurbit = MSG_GetNumBitsRead( msg ) + frag_offset[i];\n\t\t\tnumbitstoremove = frag_length[i];\n\n\t\t\tMSG_ExciseBits( msg, curbit, numbitstoremove );\n\t\t\tMSG_SeekToBit( msg, oldpos, SEEK_SET );\n\n\t\t\tfor( j = i + 1; j < MAX_STREAMS; j++ )\n\t\t\t\tfrag_offset[j] -= frag_length[i];\n\t\t}\n\n\t\t// is there anything left to process?\n\t\tif( MSG_GetNumBitsLeft( msg ) <= 0 )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/common/net_encode.c",
    "content": "/*\nnet_encode.c - encode network messages\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"netchan.h\"\n#include \"xash3d_mathlib.h\"\n#include \"net_encode.h\"\n#include \"event_api.h\"\n#include \"usercmd.h\"\n#include \"pm_movevars.h\"\n#include \"entity_state.h\"\n#include \"weaponinfo.h\"\n#include \"event_args.h\"\n#include \"protocol.h\"\n#include \"client.h\"\n\n#define DELTA_PATH\t\t\"delta.lst\"\n\n#define DT_BYTE\t\tBIT( 0 )\t// A byte\n#define DT_SHORT\t\tBIT( 1 ) \t// 2 byte field\n#define DT_FLOAT\t\tBIT( 2 )\t// A floating point field\n#define DT_INTEGER\t\tBIT( 3 )\t// 4 byte integer\n#define DT_ANGLE\t\tBIT( 4 )\t// A floating point angle ( will get masked correctly )\n#define DT_TIMEWINDOW_8\tBIT( 5 )\t// A floating point timestamp, relative to sv.time\n#define DT_TIMEWINDOW_BIG\tBIT( 6 )\t// and re-encoded on the client relative to the client's clock\n#define DT_STRING\t\tBIT( 7 )\t// A null terminated string, sent as 8 byte chars\n#define DT_SIGNED\t\tBIT( 8 )\t// sign modificator\n#define DT_SIGNED_GS\tBIT( 31 ) // GoldSrc-specific sign modificator\n\n// helper macroses\n#define ENTS_DEF( x )\t#x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x )\n#define UCMD_DEF( x )\t#x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x )\n#define EVNT_DEF( x )\t#x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x )\n#define PHYS_DEF( x )\t#x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x )\n#define CLDT_DEF( x )\t#x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x )\n#define WPDT_DEF( x )\t#x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x )\n#define DESC_DEF( x )\t#x, offsetof( goldsrc_delta_t, x ), sizeof( ((goldsrc_delta_t *)0)->x )\n\nstatic qboolean\t\tdelta_init = false;\n\n// list of all the struct names\nstatic const delta_field_t cmd_fields[] =\n{\n{ UCMD_DEF( lerp_msec )\t\t},\n{ UCMD_DEF( msec )\t\t\t},\n{ UCMD_DEF( viewangles[0] )\t\t},\n{ UCMD_DEF( viewangles[1] )\t\t},\n{ UCMD_DEF( viewangles[2] )\t\t},\n{ UCMD_DEF( forwardmove )\t\t},\n{ UCMD_DEF( sidemove )\t\t},\n{ UCMD_DEF( upmove )\t\t},\n{ UCMD_DEF( lightlevel )\t\t},\n{ UCMD_DEF( buttons )\t\t},\n{ UCMD_DEF( impulse )\t\t},\n{ UCMD_DEF( weaponselect )\t\t},\n{ UCMD_DEF( impact_index )\t\t},\n{ UCMD_DEF( impact_position[0] )\t},\n{ UCMD_DEF( impact_position[1] )\t},\n{ UCMD_DEF( impact_position[2] )\t},\n};\n\nstatic const delta_field_t pm_fields[] =\n{\n{ PHYS_DEF( gravity )\t\t},\n{ PHYS_DEF( stopspeed )\t\t},\n{ PHYS_DEF( maxspeed )\t\t},\n{ PHYS_DEF( spectatormaxspeed )\t},\n{ PHYS_DEF( accelerate )\t\t},\n{ PHYS_DEF( airaccelerate )\t\t},\n{ PHYS_DEF( wateraccelerate )\t\t},\n{ PHYS_DEF( friction )\t\t},\n{ PHYS_DEF( edgefriction )\t\t},\n{ PHYS_DEF( waterfriction )\t\t},\n{ PHYS_DEF( bounce )\t\t},\n{ PHYS_DEF( stepsize )\t\t},\n{ PHYS_DEF( maxvelocity )\t\t},\n{ PHYS_DEF( zmax )\t\t\t},\n{ PHYS_DEF( waveHeight )\t\t},\n{ PHYS_DEF( footsteps )\t\t},\n{ PHYS_DEF( skyName )\t\t},\n{ PHYS_DEF( rollangle )\t\t},\n{ PHYS_DEF( rollspeed )\t\t},\n{ PHYS_DEF( skycolor_r )\t\t},\n{ PHYS_DEF( skycolor_g )\t\t},\n{ PHYS_DEF( skycolor_b )\t\t},\n{ PHYS_DEF( skyvec_x )\t\t},\n{ PHYS_DEF( skyvec_y )\t\t},\n{ PHYS_DEF( skyvec_z )\t\t},\n{ PHYS_DEF( fog_settings )\t\t},\n{ PHYS_DEF( wateralpha )\t\t},\n{ PHYS_DEF( skydir_x )\t\t},\n{ PHYS_DEF( skydir_y )\t\t},\n{ PHYS_DEF( skydir_z )\t\t},\n{ PHYS_DEF( skyangle )\t\t},\n};\n\nstatic const delta_field_t ev_fields[] =\n{\n{ EVNT_DEF( flags )\t\t},\n{ EVNT_DEF( entindex )\t},\n{ EVNT_DEF( origin[0] )\t},\n{ EVNT_DEF( origin[1] )\t},\n{ EVNT_DEF( origin[2] )\t},\n{ EVNT_DEF( angles[0] )\t},\n{ EVNT_DEF( angles[1] )\t},\n{ EVNT_DEF( angles[2] )\t},\n{ EVNT_DEF( velocity[0] )\t},\n{ EVNT_DEF( velocity[1] )\t},\n{ EVNT_DEF( velocity[2] )\t},\n{ EVNT_DEF( ducking )\t},\n{ EVNT_DEF( fparam1 )\t},\n{ EVNT_DEF( fparam2 )\t},\n{ EVNT_DEF( iparam1 )\t},\n{ EVNT_DEF( iparam2 )\t},\n{ EVNT_DEF( bparam1 )\t},\n{ EVNT_DEF( bparam2 )\t},\n};\n\nstatic const delta_field_t wd_fields[] =\n{\n{ WPDT_DEF( m_iId )\t\t\t},\n{ WPDT_DEF( m_iClip )\t\t},\n{ WPDT_DEF( m_flNextPrimaryAttack )\t},\n{ WPDT_DEF( m_flNextSecondaryAttack )\t},\n{ WPDT_DEF( m_flTimeWeaponIdle )\t},\n{ WPDT_DEF( m_fInReload )\t\t},\n{ WPDT_DEF( m_fInSpecialReload )\t},\n{ WPDT_DEF( m_flNextReload )\t\t},\n{ WPDT_DEF( m_flPumpTime )\t\t},\n{ WPDT_DEF( m_fReloadTime )\t\t},\n{ WPDT_DEF( m_fAimedDamage )\t\t},\n{ WPDT_DEF( m_fNextAimBonus )\t\t},\n{ WPDT_DEF( m_fInZoom )\t\t},\n{ WPDT_DEF( m_iWeaponState )\t\t},\n{ WPDT_DEF( iuser1 )\t\t},\n{ WPDT_DEF( iuser2 )\t\t},\n{ WPDT_DEF( iuser3 )\t\t},\n{ WPDT_DEF( iuser4 )\t\t},\n{ WPDT_DEF( fuser1 )\t\t},\n{ WPDT_DEF( fuser2 )\t\t},\n{ WPDT_DEF( fuser3 )\t\t},\n{ WPDT_DEF( fuser4 )\t\t},\n};\n\nstatic const delta_field_t cd_fields[] =\n{\n{ CLDT_DEF( origin[0] )\t},\n{ CLDT_DEF( origin[1] )\t},\n{ CLDT_DEF( origin[2] )\t},\n{ CLDT_DEF( velocity[0] )\t},\n{ CLDT_DEF( velocity[1] )\t},\n{ CLDT_DEF( velocity[2] )\t},\n{ CLDT_DEF( viewmodel )\t},\n{ CLDT_DEF( punchangle[0] )\t},\n{ CLDT_DEF( punchangle[1] )\t},\n{ CLDT_DEF( punchangle[2] )\t},\n{ CLDT_DEF( flags )\t\t},\n{ CLDT_DEF( waterlevel )\t},\n{ CLDT_DEF( watertype )\t},\n{ CLDT_DEF( view_ofs[0] )\t},\n{ CLDT_DEF( view_ofs[1] )\t},\n{ CLDT_DEF( view_ofs[2] )\t},\n{ CLDT_DEF( health )\t},\n{ CLDT_DEF( bInDuck )\t},\n{ CLDT_DEF( weapons )\t},\n{ CLDT_DEF( flTimeStepSound )\t},\n{ CLDT_DEF( flDuckTime )\t},\n{ CLDT_DEF( flSwimTime )\t},\n{ CLDT_DEF( waterjumptime )\t},\n{ CLDT_DEF( maxspeed )\t},\n{ CLDT_DEF( fov )\t\t},\n{ CLDT_DEF( weaponanim )\t},\n{ CLDT_DEF( m_iId )\t\t},\n{ CLDT_DEF( ammo_shells )\t},\n{ CLDT_DEF( ammo_nails )\t},\n{ CLDT_DEF( ammo_cells )\t},\n{ CLDT_DEF( ammo_rockets )\t},\n{ CLDT_DEF( m_flNextAttack )\t},\n{ CLDT_DEF( tfstate )\t},\n{ CLDT_DEF( pushmsec )\t},\n{ CLDT_DEF( deadflag )\t},\n{ CLDT_DEF( physinfo )\t},\n{ CLDT_DEF( iuser1 )\t},\n{ CLDT_DEF( iuser2 )\t},\n{ CLDT_DEF( iuser3 )\t},\n{ CLDT_DEF( iuser4 )\t},\n{ CLDT_DEF( fuser1 )\t},\n{ CLDT_DEF( fuser2 )\t},\n{ CLDT_DEF( fuser3 )\t},\n{ CLDT_DEF( fuser4 )\t},\n{ CLDT_DEF( vuser1[0] )\t},\n{ CLDT_DEF( vuser1[1] )\t},\n{ CLDT_DEF( vuser1[2] )\t},\n{ CLDT_DEF( vuser2[0] )\t},\n{ CLDT_DEF( vuser2[1] )\t},\n{ CLDT_DEF( vuser2[2] )\t},\n{ CLDT_DEF( vuser3[0] )\t},\n{ CLDT_DEF( vuser3[1] )\t},\n{ CLDT_DEF( vuser3[2] )\t},\n{ CLDT_DEF( vuser4[0] )\t},\n{ CLDT_DEF( vuser4[1] )\t},\n{ CLDT_DEF( vuser4[2] )\t},\n};\n\nstatic const delta_field_t ent_fields[] =\n{\n{ ENTS_DEF( entityType )\t},\n{ ENTS_DEF( origin[0] )\t},\n{ ENTS_DEF( origin[1] )\t},\n{ ENTS_DEF( origin[2] )\t},\n{ ENTS_DEF( angles[0] )\t},\n{ ENTS_DEF( angles[1] )\t},\n{ ENTS_DEF( angles[2] )\t},\n{ ENTS_DEF( modelindex )\t},\n{ ENTS_DEF( sequence )\t},\n{ ENTS_DEF( frame )\t\t},\n{ ENTS_DEF( colormap )\t},\n{ ENTS_DEF( skin )\t\t},\n{ ENTS_DEF( solid )\t\t},\n{ ENTS_DEF( effects )\t},\n{ ENTS_DEF( scale )\t\t},\n{ ENTS_DEF( eflags )\t},\n{ ENTS_DEF( rendermode )\t},\n{ ENTS_DEF( renderamt )\t},\n{ ENTS_DEF( rendercolor.r )\t},\n{ ENTS_DEF( rendercolor.g )\t},\n{ ENTS_DEF( rendercolor.b )\t},\n{ ENTS_DEF( renderfx )\t},\n{ ENTS_DEF( movetype )\t},\n{ ENTS_DEF( animtime )\t},\n{ ENTS_DEF( framerate )\t},\n{ ENTS_DEF( body )\t\t},\n{ ENTS_DEF( controller[0] )\t},\n{ ENTS_DEF( controller[1] )\t},\n{ ENTS_DEF( controller[2] )\t},\n{ ENTS_DEF( controller[3] )\t},\n{ ENTS_DEF( blending[0] )\t},\n{ ENTS_DEF( blending[1] )\t},\n{ ENTS_DEF( blending[2] )\t},\n{ ENTS_DEF( blending[3] )\t},\n{ ENTS_DEF( velocity[0] )\t},\n{ ENTS_DEF( velocity[1] )\t},\n{ ENTS_DEF( velocity[2] )\t},\n{ ENTS_DEF( mins[0] )\t},\n{ ENTS_DEF( mins[1] )\t},\n{ ENTS_DEF( mins[2] )\t},\n{ ENTS_DEF( maxs[0] )\t},\n{ ENTS_DEF( maxs[1] )\t},\n{ ENTS_DEF( maxs[2] )\t},\n{ ENTS_DEF( aiment )\t},\n{ ENTS_DEF( owner )\t\t},\n{ ENTS_DEF( friction )\t},\n{ ENTS_DEF( gravity )\t},\n{ ENTS_DEF( team )\t\t},\n{ ENTS_DEF( playerclass )\t},\n{ ENTS_DEF( health )\t},\n{ ENTS_DEF( spectator )\t},\n{ ENTS_DEF( weaponmodel )\t},\n{ ENTS_DEF( gaitsequence )\t},\n{ ENTS_DEF( basevelocity[0] )\t},\n{ ENTS_DEF( basevelocity[1] )\t},\n{ ENTS_DEF( basevelocity[2] )\t},\n{ ENTS_DEF( usehull )\t},\n{ ENTS_DEF( oldbuttons )\t},\t// probably never transmitted\n{ ENTS_DEF( onground )\t},\n{ ENTS_DEF( iStepLeft )\t},\n{ ENTS_DEF( flFallVelocity )\t},\n{ ENTS_DEF( fov )\t\t},\n{ ENTS_DEF( weaponanim )\t},\n{ ENTS_DEF( startpos[0] )\t},\n{ ENTS_DEF( startpos[1] )\t},\n{ ENTS_DEF( startpos[2] )\t},\n{ ENTS_DEF( endpos[0] )\t},\n{ ENTS_DEF( endpos[1] )\t},\n{ ENTS_DEF( endpos[2] )\t},\n{ ENTS_DEF( impacttime )\t},\n{ ENTS_DEF( starttime )\t},\n{ ENTS_DEF( iuser1 )\t},\n{ ENTS_DEF( iuser2 )\t},\n{ ENTS_DEF( iuser3 )\t},\n{ ENTS_DEF( iuser4 )\t},\n{ ENTS_DEF( fuser1 )\t},\n{ ENTS_DEF( fuser2 )\t},\n{ ENTS_DEF( fuser3 )\t},\n{ ENTS_DEF( fuser4 )\t},\n{ ENTS_DEF( vuser1[0] )\t},\n{ ENTS_DEF( vuser1[1] )\t},\n{ ENTS_DEF( vuser1[2] )\t},\n{ ENTS_DEF( vuser2[0] )\t},\n{ ENTS_DEF( vuser2[1] )\t},\n{ ENTS_DEF( vuser2[2] )\t},\n{ ENTS_DEF( vuser3[0] )\t},\n{ ENTS_DEF( vuser3[1] )\t},\n{ ENTS_DEF( vuser3[2] )\t},\n{ ENTS_DEF( vuser4[0] )\t},\n{ ENTS_DEF( vuser4[1] )\t},\n{ ENTS_DEF( vuser4[2] )\t},\n};\n\nstatic const delta_field_t meta_fields[] =\n{\n{ DESC_DEF( fieldType ), },\n{ DESC_DEF( fieldName ), },\n{ DESC_DEF( fieldOffset ), },\n{ DESC_DEF( fieldSize ), },\n{ DESC_DEF( significant_bits ), },\n{ DESC_DEF( premultiply ), },\n{ DESC_DEF( postmultiply ), },\n};\n\n#if XASH_ENGINE_TESTS\ntypedef struct delta_test_struct_t\n{\n\tchar     dt_string[128];    // always signed\n\tfloat    dt_timewindow_big; // always signed\n\tfloat    dt_timewindow_8;   // always signed\n\tfloat\t dt_angle;          // always_signed\n\tfloat    dt_float_signed;\n\tfloat    dt_float_unsigned;\n\tint32_t  dt_integer_signed;\n\tuint32_t dt_integer_unsigned;\n\tint16_t  dt_short_signed;\n\tuint16_t dt_short_unsigned;\n\tint8_t   dt_byte_signed;\n\tuint8_t  dt_byte_unsigned;\n} delta_test_struct_t;\n\n#define TEST_DEF( x )\t#x, offsetof( delta_test_struct_t, x ), sizeof( ((delta_test_struct_t *)0)->x )\n\nstatic const delta_field_t test_fields[] =\n{\n{ TEST_DEF( dt_string ) },\n{ TEST_DEF( dt_timewindow_big )},\n{ TEST_DEF( dt_timewindow_8 )},\n{ TEST_DEF( dt_angle ) },\n{ TEST_DEF( dt_float_signed ) },\n{ TEST_DEF( dt_float_unsigned ) },\n{ TEST_DEF( dt_integer_signed ) },\n{ TEST_DEF( dt_integer_unsigned ) },\n{ TEST_DEF( dt_short_signed ) },\n{ TEST_DEF( dt_short_unsigned ) },\n{ TEST_DEF( dt_byte_signed ) },\n{ TEST_DEF( dt_byte_unsigned ) },\n};\n#endif\n\nstatic delta_info_t dt_info[] =\n{\n[DT_EVENT_T]               = { \"event_t\", ev_fields, ARRAYSIZE( ev_fields ) },\n[DT_MOVEVARS_T]            = { \"movevars_t\", pm_fields, ARRAYSIZE( pm_fields ) },\n[DT_USERCMD_T]             = { \"usercmd_t\", cmd_fields, ARRAYSIZE( cmd_fields ) },\n[DT_CLIENTDATA_T]          = { \"clientdata_t\", cd_fields, ARRAYSIZE( cd_fields ) },\n[DT_WEAPONDATA_T]          = { \"weapon_data_t\", wd_fields, ARRAYSIZE( wd_fields ) },\n[DT_ENTITY_STATE_T]        = { \"entity_state_t\", ent_fields, ARRAYSIZE( ent_fields ) },\n[DT_ENTITY_STATE_PLAYER_T] = { \"entity_state_player_t\", ent_fields, ARRAYSIZE( ent_fields ) },\n[DT_CUSTOM_ENTITY_STATE_T] = { \"custom_entity_state_t\", ent_fields, ARRAYSIZE( ent_fields ) },\n#if XASH_ENGINE_TESTS\n[DT_DELTA_TEST_STRUCT_T]   = { \"delta_test_struct_t\", test_fields, ARRAYSIZE( test_fields ) },\n#endif\n};\n\n// meta description is special, it cannot be overriden\nstatic const delta_info_t dt_goldsrc_meta =\n{\n\t.pName = \"goldsrc_delta_t\",\n\t.pInfo = meta_fields,\n\t.maxFields = ARRAYSIZE( meta_fields ),\n\t.numFields = ARRAYSIZE( meta_fields ),\n\t.pFields = (delta_t[ARRAYSIZE( meta_fields )])\n\t{\n\t\t{\n\t\t\tDESC_DEF( fieldType ),\n\t\t\t.flags = DT_INTEGER,\n\t\t\t.multiplier = 1.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 32,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( fieldName ),\n\t\t\t.flags = DT_STRING,\n\t\t\t.multiplier = 1.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 1,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( fieldOffset ),\n\t\t\t.flags = DT_INTEGER,\n\t\t\t.multiplier = 1.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 16,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( fieldSize ),\n\t\t\t.flags = DT_INTEGER,\n\t\t\t.multiplier = 1.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 8,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( significant_bits ),\n\t\t\t.flags = DT_INTEGER,\n\t\t\t.multiplier = 1.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 8,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( premultiply ),\n\t\t\t.flags = DT_FLOAT,\n\t\t\t.multiplier = 4000.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 32,\n\t\t},\n\t\t{\n\t\t\tDESC_DEF( postmultiply ),\n\t\t\t.flags = DT_FLOAT,\n\t\t\t.multiplier = 4000.0f,\n\t\t\t.post_multiplier = 1.0f,\n\t\t\t.bits = 32,\n\t\t},\n\t},\n\t.bInitialized = true\n};\n\nstatic delta_info_t *Delta_FindStruct( const char *name )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( name ))\n\t\treturn NULL;\n\n\tfor( i = 0; i < ARRAYSIZE( dt_info ); i++ )\n\t{\n\t\tif( !Q_stricmp( dt_info[i].pName, name ))\n\t\t\treturn &dt_info[i];\n\t}\n\n\tCon_DPrintf( S_WARN \"Struct %s not found in delta_info\\n\", name );\n\n\t// found nothing\n\treturn NULL;\n}\n\nstatic int Delta_NumTables( void )\n{\n\treturn ARRAYSIZE( dt_info );\n}\n\nstatic delta_info_t *Delta_FindStructByIndex( int index )\n{\n\treturn &dt_info[index];\n}\n\nstatic delta_info_t *Delta_FindStructByEncoder( const char *encoderName )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( encoderName ) )\n\t\treturn NULL;\n\n\tfor( i = 0; i < ARRAYSIZE( dt_info ); i++ )\n\t{\n\t\tif( !Q_stricmp( dt_info[i].funcName, encoderName ))\n\t\t\treturn &dt_info[i];\n\t}\n\t// found nothing\n\treturn NULL;\n}\n\nstatic delta_info_t *Delta_FindStructByDelta( const delta_t *pFields )\n{\n\tint\ti;\n\n\tif( !pFields ) return NULL;\n\n\tfor( i = 0; i < ARRAYSIZE( dt_info ); i++ )\n\t{\n\t\tif( dt_info[i].pFields == pFields )\n\t\t\treturn &dt_info[i];\n\t}\n\t// found nothing\n\treturn NULL;\n}\n\nstatic void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to )\n{\n\tint\ti;\n\n\tAssert( dt != NULL );\n\n\t// set all fields is active by default\n\tfor( i = 0; i < dt->numFields; i++ )\n\t\tdt->pFields[i].bInactive = false;\n\n\tif( dt->userCallback )\n\t\tdt->userCallback( dt->pFields, from, to );\n}\n\nstatic const delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName, int maxFields )\n{\n\tint i;\n\n\tif( !fieldName || !*fieldName )\n\t\treturn NULL;\n\n\tfor( i = 0; i < maxFields; i++ )\n\t{\n\t\tif( !Q_strcmp( pInfo[i].name, fieldName ))\n\t\t\treturn &pInfo[i];\n\t}\n\n\treturn NULL;\n}\n\nstatic int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName, int maxFields )\n{\n\tint\ti;\n\n\tif( !fieldName || !*fieldName )\n\t\treturn -1;\n\n\tfor( i = 0; i < maxFields; i++ )\n\t{\n\t\tif( !Q_strcmp( pInfo[i].name, fieldName ))\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nstatic qboolean Delta_AddField( delta_info_t *dt, const char *pName, int flags, int bits, float mul, float post_mul )\n{\n\tconst delta_field_t *pFieldInfo;\n\tdelta_t\t\t*pField;\n\tint\t\ti;\n\n\t// check for coexisting field\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields && pField; i++, pField++ )\n\t{\n\t\tif( !Q_strcmp( pField->name, pName ))\n\t\t{\n\t\t\t// update existed field\n\t\t\tpField->flags = flags;\n\t\t\tpField->bits = bits;\n\t\t\tpField->multiplier = mul;\n\t\t\tpField->post_multiplier = post_mul;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// find field description\n\tpFieldInfo = Delta_FindFieldInfo( dt->pInfo, pName, dt->maxFields );\n\tif( !pFieldInfo )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: couldn't find description for %s->%s\\n\", __func__, dt->pName, pName );\n\t\treturn false;\n\t}\n\n\tif( dt->numFields + 1 > dt->maxFields )\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: can't add %s->%s encoder list is full\\n\", __func__, dt->pName, pName );\n\t\treturn false; // too many fields specified (duplicated ?)\n\t}\n\n\t// allocate a new one\n\tdt->pFields = Z_Realloc( dt->pFields, (dt->numFields + 1) * sizeof( delta_t ));\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ );\n\n\t// copy info to new field\n\tpField->name = pFieldInfo->name;\n\tpField->offset = pFieldInfo->offset;\n\tpField->size = pFieldInfo->size;\n\tpField->flags = flags;\n\tpField->bits = bits;\n\tpField->multiplier = mul;\n\tpField->post_multiplier = post_mul;\n\tdt->numFields++;\n\n\treturn true;\n}\n\nstatic void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField )\n{\n\tint\tnameIndex;\n\tdelta_info_t *dt;\n\n\tAssert( pField != NULL );\n\n\tif( !COM_CheckString( pField->name ))\n\t\treturn;// not initialized ?\n\n\tdt = Delta_FindStructByIndex( tableIndex );\n\tAssert( dt && dt->bInitialized );\n\n\tnameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name, dt->maxFields );\n\tAssert( nameIndex >= 0 && nameIndex < dt->maxFields );\n\n\tMSG_BeginServerCmd( msg, svc_deltatable );\n\tMSG_WriteUBitLong( msg, tableIndex, 4 ); // assume we support 16 network tables\n\tMSG_WriteUBitLong( msg, nameIndex, 8 ); // 255 fields by struct should be enough\n\tMSG_WriteUBitLong( msg, pField->flags, 10 ); // flags are indicated various input types\n\tMSG_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit)\n\n\t// multipliers is null-compressed\n\tif( !Q_equal( pField->multiplier, 1.0f ))\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteFloat( msg, pField->multiplier );\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n\n\tif( !Q_equal( pField->post_multiplier, 1.0f ))\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteFloat( msg, pField->post_multiplier );\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n}\n\nvoid Delta_ParseTableField( sizebuf_t *msg )\n{\n\tint\t\ttableIndex, nameIndex;\n\tfloat\t\tmul = 1.0f, post_mul = 1.0f;\n\tint\t\tflags, bits;\n\tconst char\t*pName;\n\tqboolean ignore = false;\n\tdelta_info_t\t*dt;\n\n\ttableIndex = MSG_ReadUBitLong( msg, 4 );\n\tdt = Delta_FindStructByIndex( tableIndex );\n\tif( !dt )\n\t\tHost_Error( \"%s: not initialized\", __func__ );\n\n\tnameIndex = MSG_ReadUBitLong( msg, 8 );\t// read field name index\n\tif( ( nameIndex >= 0 && nameIndex < dt->maxFields ) )\n\t{\n\t\tpName = dt->pInfo[nameIndex].name;\n\t}\n\telse\n\t{\n\t\tignore = true;\n\t\tCon_Reportf( \"%s: wrong nameIndex %d for table %s, ignoring\\n\", __func__, nameIndex,  dt->pName );\n\t}\n\n\tflags = MSG_ReadUBitLong( msg, 10 );\n\tbits = MSG_ReadUBitLong( msg, 5 ) + 1;\n\n\t// read the multipliers\n\tif( MSG_ReadOneBit( msg ))\n\t\tmul = MSG_ReadFloat( msg );\n\n\tif( MSG_ReadOneBit( msg ))\n\t\tpost_mul = MSG_ReadFloat( msg );\n\n\tif( ignore )\n\t\treturn;\n\n\t// delta encoders it's already initialized on this machine (local game)\n\tif( delta_init )\n\t\tDelta_Shutdown();\n\n\t// add field to table\n\tDelta_AddField( dt, pName, flags, bits, mul, post_mul );\n}\n\nstatic qboolean Delta_ParseField( char **delta_script, const delta_info_t *dt, delta_t *pField, qboolean bPost )\n{\n\tconst delta_field_t *pFieldInfo;\n\tstring\t\ttoken;\n\tchar\t\t*oldpos;\n\n\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\tif( Q_strcmp( token, \"(\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: expected '(', found '%s' instead\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t// read the variable name\n\tif(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: missing field name\\n\", __func__ );\n\t\treturn false;\n\t}\n\n\tpFieldInfo = Delta_FindFieldInfo( dt->pInfo, token, dt->maxFields );\n\tif( !pFieldInfo )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: unable to find field %s\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\tif( Q_strcmp( token, \",\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: expected ',', found '%s' instead\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t// copy base info to new field\n\tpField->name = pFieldInfo->name;\n\tpField->offset = pFieldInfo->offset;\n\tpField->size = pFieldInfo->size;\n\tpField->flags = 0;\n\n\t// read delta-flags\n\twhile(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) != NULL )\n\t{\n\t\tif( !Q_strcmp( token, \",\" ))\n\t\t\tbreak;\t// end of flags argument\n\n\t\tif( !Q_strcmp( token, \"|\" ))\n\t\t\tcontinue;\n\n\t\tif( !Q_strcmp( token, \"DT_BYTE\" ))\n\t\t\tpField->flags |= DT_BYTE;\n\t\telse if( !Q_strcmp( token, \"DT_SHORT\" ))\n\t\t\tpField->flags |= DT_SHORT;\n\t\telse if( !Q_strcmp( token, \"DT_FLOAT\" ))\n\t\t\tpField->flags |= DT_FLOAT;\n\t\telse if( !Q_strcmp( token, \"DT_INTEGER\" ))\n\t\t\tpField->flags |= DT_INTEGER;\n\t\telse if( !Q_strcmp( token, \"DT_ANGLE\" ))\n\t\t\tpField->flags |= DT_ANGLE;\n\t\telse if( !Q_strcmp( token, \"DT_TIMEWINDOW_8\" ))\n\t\t\tpField->flags |= DT_TIMEWINDOW_8;\n\t\telse if( !Q_strcmp( token, \"DT_TIMEWINDOW_BIG\" ))\n\t\t\tpField->flags |= DT_TIMEWINDOW_BIG;\n\t\telse if( !Q_strcmp( token, \"DT_STRING\" ))\n\t\t\tpField->flags |= DT_STRING;\n\t\telse if( !Q_strcmp( token, \"DT_SIGNED\" ))\n\t\t\tpField->flags |= DT_SIGNED;\n\t}\n\n\tif( Q_strcmp( token, \",\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: expected ',', found '%s' instead\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t// read delta-bits\n\tif(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s field bits argument is missing\\n\", __func__, pField->name );\n\t\treturn false;\n\t}\n\n\tpField->bits = Q_atoi( token );\n\n\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\tif( Q_strcmp( token, \",\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: expected ',', found '%s' instead\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t// read delta-multiplier\n\tif(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'multiplier' argument\\n\", __func__, pField->name );\n\t\treturn false;\n\t}\n\n\tpField->multiplier = Q_atof( token );\n\n\tif( bPost )\n\t{\n\t\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\t\tif( Q_strcmp( token, \",\" ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: expected ',', found '%s' instead\\n\", __func__, token );\n\t\t\treturn false;\n\t\t}\n\n\t\t// read delta-postmultiplier\n\t\tif(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: %s missing 'post_multiply' argument\\n\", __func__, pField->name );\n\t\t\treturn false;\n\t\t}\n\n\t\tpField->post_multiplier = Q_atof( token );\n\t}\n\telse\n\t{\n\t\t// to avoid division by zero\n\t\tpField->post_multiplier = 1.0f;\n\t}\n\n\t// closing brace...\n\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\tif( Q_strcmp( token, \")\" ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: expected ')', found '%s' instead\\n\", __func__, token );\n\t\treturn false;\n\t}\n\n\t// ... and trying to parse optional ',' post-symbol\n\toldpos = *delta_script;\n\t*delta_script = COM_ParseFile( *delta_script, token, sizeof( token ));\n\tif( token[0] != ',' ) *delta_script = oldpos; // not a ','\n\n\treturn true;\n}\n\nstatic void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc )\n{\n\tstring\t\ttoken;\n\tdelta_t\t\t*pField;\n\n\t// allocate the delta-structures\n\tif( !dt->pFields ) dt->pFields = (delta_t *)Z_Calloc( dt->maxFields * sizeof( delta_t ));\n\n\tpField = dt->pFields;\n\tdt->numFields = 0;\n\n\t// assume we have handled '{'\n\twhile(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) != NULL )\n\t{\n\t\tAssert( dt->numFields <= dt->maxFields );\n\n\t\tif( !Q_strcmp( token, \"DEFINE_DELTA\" ))\n\t\t{\n\t\t\tif( Delta_ParseField( delta_script, dt, &pField[dt->numFields], false ))\n\t\t\t\tdt->numFields++;\n\t\t}\n\t\telse if( !Q_strcmp( token, \"DEFINE_DELTA_POST\" ))\n\t\t{\n\t\t\tif( Delta_ParseField( delta_script, dt, &pField[dt->numFields], true ))\n\t\t\t\tdt->numFields++;\n\t\t}\n\t\telse if( token[0] == '}' )\n\t\t{\n\t\t\t// end of the section\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// copy function name\n\tQ_strncpy( dt->funcName, encodeFunc, sizeof( dt->funcName ));\n\n\tif( !Q_stricmp( encodeDll, \"none\" ))\n\t\tdt->customEncode = CUSTOM_NONE;\n\telse if( !Q_stricmp( encodeDll, \"gamedll\" ))\n\t\tdt->customEncode = CUSTOM_SERVER_ENCODE;\n\telse if( !Q_stricmp( encodeDll, \"clientdll\" ))\n\t\tdt->customEncode = CUSTOM_CLIENT_ENCODE;\n\n\t// adjust to fit memory size\n\tif( dt->numFields < dt->maxFields )\n\t{\n\t\tdt->pFields = Z_Realloc( dt->pFields, dt->numFields * sizeof( delta_t ));\n\t}\n\n\tdt->bInitialized = true; // table is ok\n}\n\nstatic void Delta_InitFields( void )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tstring\t\tencodeDll, encodeFunc, token;\n\tdelta_info_t\t*dt;\n\n\tafile = FS_LoadFile( DELTA_PATH, NULL, false );\n\tif( !afile ) Sys_Error( \"%s: couldn't load file %s\\n\", __func__, DELTA_PATH );\n\n\tpfile = (char *)afile;\n\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tdt = Delta_FindStruct( token );\n\n\t\tif( dt == NULL )\n\t\t{\n\t\t\tSys_Error( \"%s: unknown struct %s\\n\", DELTA_PATH, token );\n\t\t}\n\n\t\tpfile = COM_ParseFile( pfile, encodeDll, sizeof( encodeDll ));\n\n\t\tif( !Q_stricmp( encodeDll, \"none\" ))\n\t\t\tQ_strncpy( encodeFunc, \"null\", sizeof( encodeFunc ));\n\t\telse pfile = COM_ParseFile( pfile, encodeFunc, sizeof( encodeFunc ));\n\n\t\t// jump to '{'\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\n\t\tif( token[0] != '{' )\n\t\t{\n\t\t\tSys_Error( \"%s: missing '{' in section %s\\n\", DELTA_PATH, dt->pName );\n\t\t}\n\n\t\tDelta_ParseTable( &pfile, dt, encodeDll, encodeFunc );\n\t}\n\n\tMem_Free( afile );\n}\n\nvoid Delta_Init( void )\n{\n\tdelta_info_t\t*dt;\n\n\t// shutdown it first\n\tif( delta_init ) Delta_Shutdown ();\n\n\tDelta_InitFields ();\t// initialize fields\n\tdelta_init = true;\n\n\tdt = Delta_FindStructByIndex( DT_MOVEVARS_T );\n\n\tAssert( dt != NULL );\n\tif( dt->bInitialized ) return;\t// \"movevars_t\" already specified by user\n\n\t// create movevars_t delta internal\n\tDelta_AddField( dt, \"gravity\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"stopspeed\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"maxspeed\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"spectatormaxspeed\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"accelerate\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"airaccelerate\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"wateraccelerate\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"friction\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"edgefriction\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"waterfriction\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"bounce\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"stepsize\", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f );\n\tDelta_AddField( dt, \"maxvelocity\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\n\tif( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD ))\n\t\tDelta_AddField( dt, \"zmax\", DT_FLOAT|DT_SIGNED, 18, 1.0f, 1.0f );\n\telse Delta_AddField( dt, \"zmax\", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f );\n\n\tDelta_AddField( dt, \"waveHeight\", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f );\n\tDelta_AddField( dt, \"skyName\", DT_STRING, 1, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"footsteps\", DT_INTEGER, 1, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"rollangle\", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f );\n\tDelta_AddField( dt, \"rollspeed\", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f );\n\tDelta_AddField( dt, \"skycolor_r\", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); // 0 - 264\n\tDelta_AddField( dt, \"skycolor_g\", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"skycolor_b\", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"skyvec_x\", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // 0 - 1\n\tDelta_AddField( dt, \"skyvec_y\", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f );\n\tDelta_AddField( dt, \"skyvec_z\", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f );\n\tDelta_AddField( dt, \"wateralpha\", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f );\n\tDelta_AddField( dt, \"fog_settings\", DT_INTEGER, 32, 1.0f, 1.0f );\n\tdt->numFields = ARRAYSIZE( pm_fields ) - 4;\n\n\t// now done\n\tdt->bInitialized = true;\n}\n\nvoid Delta_InitClient( void )\n{\n\tint\ti, numActive = 0;\n\n\t// already initalized\n\tif( delta_init ) return;\n\n\tfor( i = 0; i < ARRAYSIZE( dt_info ); i++ )\n\t{\n\t\tif( dt_info[i].numFields > 0 )\n\t\t{\n\t\t\tdt_info[i].bInitialized = true;\n\t\t\tnumActive++;\n\t\t}\n\t}\n\n\tif( numActive ) delta_init = true;\n}\n\nvoid Delta_Shutdown( void )\n{\n\tint\ti;\n\n\tif( !delta_init ) return;\n\n\tfor( i = 0; i < ARRAYSIZE( dt_info ); i++ )\n\t{\n\t\tdt_info[i].numFields = 0;\n\t\tdt_info[i].customEncode = CUSTOM_NONE;\n\t\tdt_info[i].userCallback = NULL;\n\t\tdt_info[i].funcName[0] = '\\0';\n\n\t\tif( dt_info[i].pFields )\n\t\t{\n\t\t\tZ_Free( dt_info[i].pFields );\n\t\t\tdt_info[i].pFields = NULL;\n\t\t}\n\n\t\tdt_info[i].bInitialized = false;\n\t}\n\n\tdelta_init = false;\n}\n\n/*\n=====================\nDelta_ClampIntegerField\n\nprevent data to out of range\n=====================\n*/\nstatic int Delta_ClampIntegerField( delta_t *pField, int iValue, int signbit, int numbits )\n{\n#ifdef _DEBUG\n\tif( numbits < 32 && abs( iValue ) >= (uint)BIT( numbits ))\n\t\tCon_Reportf( S_WARN \"Delta_ClampIntegerField: field %s = %d overflowed %d\\n\", pField->name, abs( iValue ), (uint)BIT( numbits ));\n#endif\n\tif( numbits < 32 )\n\t{\n\t\tint signbits = numbits - signbit;\n\t\tint maxnum = BIT( signbits ) - 1;\n\n\t\tif( iValue > maxnum )\n\t\t\tiValue = maxnum;\n\t\telse if( signbit && iValue < -maxnum - 1 )\n\t\t\tiValue = -maxnum - 1;\n\t}\n\n\treturn iValue; // clamped;\n}\n\n/*\n=====================\nDelta_CompareField\n\ncompare fields by offsets\nassume from and to is valid\n=====================\n*/\nstatic qboolean Delta_CompareField( delta_t *pField, const void *from, const void *to )\n{\n\tint\t\tsignbit = ( pField->flags & DT_SIGNED ) ? 1 : 0;\n\tfloat\tval_a, val_b;\n\tint\tfromF, toF;\n\n\tAssert( pField != NULL );\n\tAssert( from != NULL );\n\tAssert( to != NULL );\n\n\tif( pField->bInactive )\n\t\treturn true;\n\n\tfromF = toF = 0;\n\n\tif( pField->flags & DT_BYTE )\n\t{\n\t\tif( signbit )\n\t\t{\n\t\t\tfromF = *(int8_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(int8_t *)((int8_t *)to + pField->offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfromF = *(uint8_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(uint8_t *)((int8_t *)to + pField->offset );\n\t\t}\n\n\t\tif( !Q_equal(pField->multiplier, 1.0f ))\n\t\t{\n\t\t\tfromF *= pField->multiplier;\n\t\t\ttoF *= pField->multiplier;\n\t\t}\n\n\t\tfromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );\n\t\ttoF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );\n\t}\n\telse if( pField->flags & DT_SHORT )\n\t{\n\t\tif( signbit )\n\t\t{\n\t\t\tfromF = *(int16_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(int16_t *)((int8_t *)to + pField->offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfromF = *(uint16_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(uint16_t *)((int8_t *)to + pField->offset );\n\t\t}\n\n\t\tif( !Q_equal(pField->multiplier, 1.0f ))\n\t\t{\n\t\t\tfromF *= pField->multiplier;\n\t\t\ttoF *= pField->multiplier;\n\t\t}\n\n\t\tfromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );\n\t\ttoF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );\n\t}\n\telse if( pField->flags & DT_INTEGER )\n\t{\n\t\tif( signbit )\n\t\t{\n\t\t\tfromF = *(int32_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(int32_t *)((int8_t *)to + pField->offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfromF = *(uint32_t *)((int8_t *)from + pField->offset );\n\t\t\ttoF = *(uint32_t *)((int8_t *)to + pField->offset );\n\t\t}\n\n\t\tif( !Q_equal(pField->multiplier, 1.0f ))\n\t\t{\n\t\t\tfromF *= pField->multiplier;\n\t\t\ttoF *= pField->multiplier;\n\t\t}\n\n\t\tfromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );\n\t\ttoF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );\n\t}\n\telse if( pField->flags & ( DT_ANGLE|DT_FLOAT ))\n\t{\n\t\t// don't convert floats to integers\n\t\tfromF = *((int *)((byte *)from + pField->offset ));\n\t\ttoF = *((int *)((byte *)to + pField->offset ));\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_8 )\n\t{\n\t\tval_a = *(float *)((byte *)from + pField->offset );\n\t\tval_b = *(float *)((byte *)to + pField->offset );\n\t\tfromF = Q_rint( val_a * 100.0 );\n\t\ttoF = Q_rint( val_b * 100.0 );\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_BIG )\n\t{\n\t\tval_a = *(float *)((byte *)from + pField->offset );\n\t\tval_b = *(float *)((byte *)to + pField->offset );\n\t\tfromF = Q_rint( val_a * pField->multiplier );\n\t\ttoF = Q_rint( val_b * pField->multiplier );\n\t}\n\telse if( pField->flags & DT_STRING )\n\t{\n\t\t// compare strings\n\t\tchar\t*s1 = (char *)((byte *)from + pField->offset );\n\t\tchar\t*s2 = (char *)((byte *)to + pField->offset );\n\n\t\t// 0 is equal, otherwise not equal\n\t\ttoF = Q_strcmp( s1, s2 );\n\t}\n\n\treturn fromF == toF;\n}\n\n/*\n=====================\nDelta_TestBaseline\n\ncompare baselines to find optimal\n=====================\n*/\nint Delta_TestBaseline( const entity_state_t *from, const entity_state_t *to, qboolean player, double timebase )\n{\n\tdelta_info_t\t*dt = NULL;\n\tdelta_t\t\t*pField;\n\tint\t\ti, countBits;\n\n\tcountBits = MAX_ENTITY_BITS + 2;\n\n\tif( to == NULL )\n\t{\n\t\tif( from == NULL ) return 0;\n\t\treturn countBits;\n\t}\n\n\tif( FBitSet( to->entityType, ENTITY_BEAM ))\n\t\tdt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T );\n\telse if( player )\n\t\tdt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T );\n\telse dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T );\n\n\tAssert( dt && dt->bInitialized );\n\n\tcountBits++; // entityType flag\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\t// flag about field change (sets always)\n\t\tcountBits++;\n\n\t\tif( !Delta_CompareField( pField, from, to ))\n\t\t{\n\t\t\t// strings are handled differently\n\t\t\tif( FBitSet( pField->flags, DT_STRING ))\n\t\t\t\tcountBits += Q_strlen((char *)((byte *)to + pField->offset )) * 8;\n\t\t\telse countBits += pField->bits;\n\t\t}\n\t}\n\n\t// g-cont. compare bitcount directly no reason to call BitByte here\n\treturn countBits;\n}\n\n/*\n=====================\nDelta_WriteField\n\nwrite fields by offsets\nassume from and to is valid\n=====================\n*/\nstatic void Delta_WriteField_( sizebuf_t *msg, delta_t *pField, const void *from, const void *to, double timebase )\n{\n\tint\t\tsignbit = FBitSet( pField->flags, DT_SIGNED ) ? 1 : 0;\n\tfloat\t\tflValue, flAngle;\n\tuint\t\tiValue;\n\tint dt;\n\tconst char\t*pStr;\n\n\tif( pField->flags & DT_BYTE )\n\t{\n\t\tif( signbit )\n\t\t\tiValue = *(int8_t *)((int8_t *)to + pField->offset );\n\t\telse\n\t\t\tiValue = *(uint8_t *)((int8_t *)to + pField->offset );\n\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue *= pField->multiplier;\n\n\t\tiValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );\n\t\tMSG_WriteBitLong( msg, iValue, pField->bits, signbit );\n\t}\n\telse if( pField->flags & DT_SHORT )\n\t{\n\t\tif( signbit )\n\t\t\tiValue = *(int16_t *)((int8_t *)to + pField->offset );\n\t\telse\n\t\t\tiValue = *(uint16_t *)((int8_t *)to + pField->offset );\n\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue *= pField->multiplier;\n\n\t\tiValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );\n\t\tMSG_WriteBitLong( msg, iValue, pField->bits, signbit );\n\t}\n\telse if( pField->flags & DT_INTEGER )\n\t{\n\t\tif( signbit )\n\t\t\tiValue = *(int32_t *)((int8_t *)to + pField->offset );\n\t\telse\n\t\t\tiValue = *(uint32_t *)((int8_t *)to + pField->offset );\n\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue *= pField->multiplier;\n\n\t\tiValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );\n\t\tMSG_WriteBitLong( msg, iValue, pField->bits, signbit );\n\t}\n\telse if( pField->flags & DT_FLOAT )\n\t{\n\t\tflValue = *(float *)((byte *)to + pField->offset );\n\t\tiValue = (int)((double)flValue * pField->multiplier);\n\t\tiValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );\n\t\tMSG_WriteBitLong( msg, iValue, pField->bits, signbit );\n\t}\n\telse if( pField->flags & DT_ANGLE )\n\t{\n\t\tflAngle = *(float *)((byte *)to + pField->offset );\n\n\t\t// NOTE: never applies multipliers to angle because\n\t\t// result may be wrong on client-side\n\t\tMSG_WriteBitAngle( msg, flAngle, pField->bits );\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_8 )\n\t{\n\t\tflValue = *(float *)((byte *)to + pField->offset );\n\t\tdt = Q_rint(( timebase - flValue ) * 100.0 );\n\t\tdt = Delta_ClampIntegerField( pField, dt, 1, pField->bits );\n\t\tMSG_WriteSBitLong( msg, dt, pField->bits );\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_BIG )\n\t{\n\t\tflValue = *(float *)((byte *)to + pField->offset );\n\t\tdt = Q_rint(( timebase - flValue ) * pField->multiplier );\n\t\tdt = Delta_ClampIntegerField( pField, dt, 1, pField->bits );\n\t\tMSG_WriteSBitLong( msg, dt, pField->bits );\n\t}\n\telse if( pField->flags & DT_STRING )\n\t{\n\t\tpStr = (char *)((byte *)to + pField->offset );\n\t\tMSG_WriteString( msg, pStr );\n\t}\n}\n\nstatic qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, const void *from, const void *to, double timebase )\n{\n\tif( Delta_CompareField( pField, from, to ))\n\t{\n\t\tMSG_WriteOneBit( msg, 0 );\t// unchanged\n\t\treturn false;\n\t}\n\n\tMSG_WriteOneBit( msg, 1 );\t// changed\n\n\tDelta_WriteField_( msg, pField, from, to, timebase );\n\n\treturn true;\n}\n\n/*\n====================\nDelta_CopyField\n\n====================\n*/\nstatic void Delta_CopyField( delta_t *pField, const void *from, void *to, double timebase )\n{\n\tqboolean bSigned = FBitSet( pField->flags, DT_SIGNED );\n\tuint8_t *to_field = (uint8_t *)to + pField->offset;\n\tuint8_t *from_field = (uint8_t *)from + pField->offset;\n\n\tif( FBitSet( pField->flags, DT_BYTE ))\n\t{\n\t\tif( bSigned )\n\t\t\t*(int8_t *)( to_field ) = *(int8_t *)( from_field );\n\t\telse\n\t\t\t*(uint8_t *)( to_field ) = *(uint8_t *)( from_field );\n\t}\n\telse if( FBitSet( pField->flags, DT_SHORT ))\n\t{\n\t\tif( bSigned )\n\t\t\t*(int16_t *)( to_field ) = *(int16_t *)( from_field );\n\t\telse\n\t\t\t*(uint16_t *)( to_field ) = *(uint16_t *)( from_field );\n\t}\n\telse if( FBitSet( pField->flags, DT_INTEGER ))\n\t{\n\t\tif( bSigned )\n\t\t\t*(int32_t *)( to_field ) = *(int32_t *)( from_field );\n\t\telse\n\t\t\t*(uint32_t *)( to_field ) = *(uint32_t *)( from_field );\n\t}\n\telse if( FBitSet( pField->flags, DT_FLOAT|DT_ANGLE|DT_TIMEWINDOW_8|DT_TIMEWINDOW_BIG ))\n\t{\n\t\t*(float *)( to_field ) = *(float *)( from_field );\n\t}\n\telse if( FBitSet( pField->flags, DT_STRING ))\n\t{\n\t\tQ_strncpy( to_field, from_field, pField->size );\n\t}\n\telse\n\t{\n\t\tAssert( 0 );\n\t}\n}\n\n/*\n=====================\nDelta_ReadField\n\nread fields by offsets\nassume 'from' and 'to' is valid\n=====================\n*/\nstatic void Delta_ReadField_( sizebuf_t *msg, delta_t *pField, void *to, double timebase )\n{\n\tqboolean\t\tbSigned = ( pField->flags & DT_SIGNED ) ? true : false;\n\tfloat\t\tflValue, flAngle, flTime;\n\tuint\t\tiValue;\n\tconst char\t*pStr;\n\tchar\t\t*pOut;\n\n\tAssert( pField->multiplier != 0.0f );\n\n\tif( pField->flags & DT_BYTE )\n\t{\n\t\tiValue = MSG_ReadBitLong( msg, pField->bits, bSigned );\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue /= pField->multiplier;\n\n\t\tif( !Q_equal( pField->post_multiplier, 1.0 ))\n\t\t\tiValue *= pField->post_multiplier;\n\n\t\tif( bSigned )\n\t\t\t*(int8_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t\telse\n\t\t\t*(uint8_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t}\n\telse if( pField->flags & DT_SHORT )\n\t{\n\t\tiValue = MSG_ReadBitLong( msg, pField->bits, bSigned );\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue /= pField->multiplier;\n\n\t\tif( !Q_equal( pField->post_multiplier, 1.0 ))\n\t\t\tiValue *= pField->post_multiplier;\n\n\t\tif( bSigned )\n\t\t\t*(int16_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t\telse\n\t\t\t*(uint16_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t}\n\telse if( pField->flags & DT_INTEGER )\n\t{\n\t\tiValue = MSG_ReadBitLong( msg, pField->bits, bSigned );\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tiValue /= pField->multiplier;\n\n\t\tif( !Q_equal( pField->post_multiplier, 1.0 ))\n\t\t\tiValue *= pField->post_multiplier;\n\n\t\tif( bSigned )\n\t\t\t*(int32_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t\telse\n\t\t\t*(uint32_t *)((uint8_t *)to + pField->offset ) = iValue;\n\t}\n\telse if( pField->flags & DT_FLOAT )\n\t{\n\t\tiValue = MSG_ReadBitLong( msg, pField->bits, bSigned );\n\t\tif( bSigned )\n\t\t\tflValue = (int)iValue;\n\t\telse\n\t\t\tflValue = iValue;\n\n\t\tif( !Q_equal( pField->multiplier, 1.0 ))\n\t\t\tflValue /= pField->multiplier;\n\n\t\tif( !Q_equal( pField->post_multiplier, 1.0 ))\n\t\t\tflValue *= pField->post_multiplier;\n\n\t\t*(float *)((byte *)to + pField->offset ) = flValue;\n\t}\n\telse if( pField->flags & DT_ANGLE )\n\t{\n\t\tflAngle = MSG_ReadBitAngle( msg, pField->bits );\n\t\t*(float *)((byte *)to + pField->offset ) = flAngle;\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_8 )\n\t{\n\t\tiValue = MSG_ReadSBitLong( msg, pField->bits );\n\t\tflTime = ( timebase * 100.0 - (int)iValue ) / 100.0;\n\t\t*(float *)((byte *)to + pField->offset ) = flTime;\n\t}\n\telse if( pField->flags & DT_TIMEWINDOW_BIG )\n\t{\n\t\tiValue = MSG_ReadSBitLong( msg, pField->bits );\n\t\tflTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier;\n\t\t*(float *)((byte *)to + pField->offset ) = flTime;\n\t}\n\telse if( pField->flags & DT_STRING )\n\t{\n\t\tpStr = MSG_ReadString( msg );\n\t\tpOut = (char *)((byte *)to + pField->offset );\n\t\tQ_strncpy( pOut, pStr, pField->size );\n\t}\n}\n\nstatic qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, const void *from, void *to, double timebase )\n{\n\tif( !MSG_ReadOneBit( msg ))\n\t{\n\t\tDelta_CopyField( pField, from, to, timebase );\n\t\treturn false;\n\t}\n\n\tDelta_ReadField_( msg, pField, to, timebase );\n\treturn true;\n}\n\nstatic void Delta_ParseGSFields( sizebuf_t *msg, const delta_info_t *dt, const void *from, void *to, double timebase )\n{\n\tuint8_t bits[8] = { 0 };\n\tdelta_t *pField;\n\tbyte c;\n\tint i;\n\n\tc = MSG_ReadUBitLong( msg, 3 );\n\n\tfor( i = 0; i < c; i++ )\n\t\tbits[i] = MSG_ReadByte( msg );\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tint b = i >> 3;\n\t\tint n = 1 << ( i & 7 );\n\n\t\tif( FBitSet( bits[b], n ))\n\t\t\tDelta_ReadField_( msg, pField, to, timebase );\n\t\telse Delta_CopyField( pField, from, to, timebase );\n\t}\n}\n\nvoid Delta_ReadGSFields( sizebuf_t *msg, int index, const void *from, void *to, double timebase )\n{\n\tconst delta_info_t *dt = Delta_FindStructByIndex( index );\n\tDelta_ParseGSFields( msg, dt, from, to, timebase );\n}\n\nvoid Delta_WriteGSFields( sizebuf_t *msg, int index, const void *from, const void *to, double timebase )\n{\n\tdelta_info_t *dt = Delta_FindStructByIndex( index );\n\tdelta_t *pField;\n\tuint8_t bits[8] = { 0 };\n\tuint c = 0;\n\tint i;\n\n\tDelta_CustomEncode( dt, from, to );\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( !Delta_CompareField( pField, from, to ))\n\t\t{\n\t\t\tint b = i >> 3;\n\t\t\tint n = 1 << ( i & 7 );\n\n\t\t\tSetBits( bits[b], n );\n\t\t\tc = b + 1;\n\t\t}\n\t}\n\n\tMSG_WriteUBitLong( msg, c, 3 );\n\tfor( i = 0; i < c; i++ )\n\t\tMSG_WriteByte( msg, bits[i] );\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tint b = i >> 3;\n\t\tint n = 1 << ( i & 7 );\n\n\t\tif( FBitSet( bits[b], n ))\n\t\t\tDelta_WriteField_( msg, pField, from, to, timebase );\n\t}\n}\n\n/*\n=============================================================================\n\nusercmd_t communication\n\n=============================================================================\n*/\n/*\n=====================\nMSG_WriteDeltaUsercmd\n=====================\n*/\nvoid MSG_WriteDeltaUsercmd( sizebuf_t *msg, const usercmd_t *from, const usercmd_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_USERCMD_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_WriteField( msg, pField, from, to, 0.0f );\n\t}\n}\n\n/*\n=====================\nMSG_ReadDeltaUsercmd\n=====================\n*/\nvoid MSG_ReadDeltaUsercmd( sizebuf_t *msg, const usercmd_t *from, usercmd_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_USERCMD_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t*to = *from;\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_ReadField( msg, pField, from, to, 0.0f );\n\t}\n\n\tCOM_NormalizeAngles( to->viewangles );\n}\n\n/*\n============================================================================\n\nevent_args_t communication\n\n============================================================================\n*/\n/*\n=====================\nMSG_WriteDeltaEvent\n=====================\n*/\nvoid MSG_WriteDeltaEvent( sizebuf_t *msg, const event_args_t *from, const event_args_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_EVENT_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_WriteField( msg, pField, from, to, 0.0f );\n\t}\n}\n\n/*\n=====================\nMSG_ReadDeltaEvent\n=====================\n*/\nvoid MSG_ReadDeltaEvent( sizebuf_t *msg, const event_args_t *from, event_args_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_EVENT_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t*to = *from;\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_ReadField( msg, pField, from, to, 0.0f );\n\t}\n}\n\n/*\n=============================================================================\n\nmovevars_t communication\n\n=============================================================================\n*/\nqboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, const movevars_t *from, const movevars_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti, startBit;\n\tint\t\tnumChanges = 0;\n\n\tdt = Delta_FindStructByIndex( DT_MOVEVARS_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\tstartBit = msg->iCurBit;\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\tMSG_BeginServerCmd( msg, svc_deltamovevars );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( Delta_WriteField( msg, pField, from, to, 0.0f ))\n\t\t\tnumChanges++;\n\t}\n\n\t// if we have no changes - kill the message\n\tif( !numChanges )\n\t{\n\t\tMSG_SeekToBit( msg, startBit, SEEK_SET );\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nvoid MSG_ReadDeltaMovevars( sizebuf_t *msg, const movevars_t *from, movevars_t *to )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_MOVEVARS_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t*to = *from;\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_ReadField( msg, pField, from, to, 0.0f );\n\t}\n}\n\n/*\n=============================================================================\n\nclientdata_t communication\n\n=============================================================================\n*/\n/*\n==================\nMSG_WriteClientData\n\nWrites current client data only for local client\nOther clients can grab the client state from entity_state_t\n==================\n*/\nvoid MSG_WriteClientData( sizebuf_t *msg, const clientdata_t *from, const clientdata_t *to, double timebase )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti, startBit;\n\tint\t\tnumChanges = 0;\n\n\tdt = Delta_FindStructByIndex( DT_CLIENTDATA_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\tstartBit = msg->iCurBit;\n\n\tMSG_WriteOneBit( msg, 1 ); // have clientdata\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( Delta_WriteField( msg, pField, from, to, timebase ))\n\t\t\tnumChanges++;\n\t}\n\n\tif( numChanges ) return; // we have updates\n\n\tMSG_SeekToBit( msg, startBit, SEEK_SET );\n\tMSG_WriteOneBit( msg, 0 ); // no changes\n}\n\n/*\n==================\nMSG_ReadClientData\n\nRead the clientdata\n==================\n*/\nvoid MSG_ReadClientData( sizebuf_t *msg, const clientdata_t *from, clientdata_t *to, double timebase )\n{\n#if !XASH_DEDICATED\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\tqboolean noChanges;\n\n\tdt = Delta_FindStructByIndex( DT_CLIENTDATA_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\tnoChanges = !cls.legacymode && !MSG_ReadOneBit( msg );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( noChanges )\n\t\t\tDelta_CopyField( pField, from, to, timebase );\n\t\telse Delta_ReadField( msg, pField, from, to, timebase );\n\t}\n#endif\n}\n\n/*\n=============================================================================\n\nweapon_data_t communication\n\n=============================================================================\n*/\n/*\n==================\nMSG_WriteWeaponData\n\nWrites current client data only for local client\nOther clients can grab the client state from entity_state_t\n==================\n*/\nvoid MSG_WriteWeaponData( sizebuf_t *msg, const weapon_data_t *from, const weapon_data_t *to, double timebase, int index )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti, startBit;\n\tint\t\tnumChanges = 0;\n\n\tdt = Delta_FindStructByIndex( DT_WEAPONDATA_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// activate fields and call custom encode func\n\tDelta_CustomEncode( dt, from, to );\n\n\tstartBit = msg->iCurBit;\n\n\tMSG_WriteOneBit( msg, 1 );\n\tMSG_WriteUBitLong( msg, index, MAX_WEAPON_BITS );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( Delta_WriteField( msg, pField, from, to, timebase ))\n\t\t\tnumChanges++;\n\t}\n\n\t// if we have no changes - kill the message\n\tif( !numChanges ) MSG_SeekToBit( msg, startBit, SEEK_SET );\n}\n\n/*\n==================\nMSG_ReadWeaponData\n\nRead the clientdata\n==================\n*/\nvoid MSG_ReadWeaponData( sizebuf_t *msg, const weapon_data_t *from, weapon_data_t *to, double timebase )\n{\n\tdelta_t\t\t*pField;\n\tdelta_info_t\t*dt;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByIndex( DT_WEAPONDATA_T );\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_ReadField( msg, pField, from, to, timebase );\n\t}\n}\n\n/*\n=============================================================================\n\nentity_state_t communication\n\n=============================================================================\n*/\n/*\n==================\nMSG_WriteDeltaEntity\n\nWrites part of a packetentities message, including the entity number.\nCan delta from either a baseline or a previous packet_entity\nIf to is NULL, a remove entity update will be sent\nIf force is not set, then nothing at all will be generated if the entity is\nidentical, under the assumption that the in-order delta code will catch it.\n==================\n*/\nvoid MSG_WriteDeltaEntity( const entity_state_t *from, const entity_state_t *to, sizebuf_t *msg, qboolean force, int delta_type, double timebase, int baseline )\n{\n\tdelta_info_t\t*dt = NULL;\n\tdelta_t\t\t*pField;\n\tint\t\ti, startBit;\n\tint\t\tnumChanges = 0;\n\n\tif( to == NULL )\n\t{\n\t\tint\tfRemoveType;\n\n\t\tif( from == NULL ) return;\n\n\t\t// a NULL to is a delta remove message\n\t\tMSG_WriteUBitLong( msg, from->number, MAX_ENTITY_BITS );\n\n\t\t// fRemoveType:\n\t\t// 0 - keep alive, has delta-update\n\t\t// 1 - remove from delta message (but keep states)\n\t\t// 2 - completely remove from server\n\t\tif( force ) fRemoveType = 2;\n\t\telse fRemoveType = 1;\n\n\t\tMSG_WriteUBitLong( msg, fRemoveType, 2 );\n\t\treturn;\n\t}\n\n\tstartBit = msg->iCurBit;\n\n\tif( to->number < 0 || to->number >= GI->max_edicts )\n\t\tHost_Error( \"%s: Bad entity number: %i\\n\", __func__, to->number );\n\n\tMSG_WriteUBitLong( msg, to->number, MAX_ENTITY_BITS );\n\tMSG_WriteUBitLong( msg, 0, 2 ); // alive\n\n\tif( baseline != 0 )\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteSBitLong( msg, baseline, 7 );\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n\n\tif( force || ( to->entityType != from->entityType ))\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteUBitLong( msg, to->entityType, 2 );\n\t\tnumChanges++;\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n\n\tif( FBitSet( to->entityType, ENTITY_BEAM ))\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T );\n\t}\n\telse if( delta_type == DELTA_PLAYER )\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T );\n\t}\n\telse\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_ENTITY_STATE_T );\n\t}\n\n\tAssert( dt && dt->bInitialized );\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\tif( delta_type == DELTA_STATIC )\n\t{\n\t\t// static entities won't to be custom encoded\n\t\tfor( i = 0; i < dt->numFields; i++ )\n\t\t\tdt->pFields[i].bInactive = false;\n\t}\n\telse\n\t{\n\t\t// activate fields and call custom encode func\n\t\tDelta_CustomEncode( dt, from, to );\n\t}\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( Delta_WriteField( msg, pField, from, to, timebase ))\n\t\t\tnumChanges++;\n\t}\n\n\t// if we have no changes - kill the message\n\tif( !numChanges && !force ) MSG_SeekToBit( msg, startBit, SEEK_SET );\n}\n\n/*\n==================\nMSG_ReadDeltaEntity\n\nThe entity number has already been read from the message, which\nis how the from state is identified.\n\nIf the delta removes the entity, entity_state_t->number will be set to MAX_EDICTS\nCan go from either a baseline or a previous packet_entity\n==================\n*/\nqboolean MSG_ReadDeltaEntity( sizebuf_t *msg, const entity_state_t *from, entity_state_t *to, int number, int delta_type, double timebase )\n{\n#if !XASH_DEDICATED\n\tdelta_info_t\t*dt = NULL;\n\tdelta_t\t\t*pField;\n\tint\t\ti, fRemoveType;\n\tint\t\tbaseline_offset = 0;\n\n\tif( number < 0 || number >= clgame.maxEntities )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: bad delta entity number: %i\\n\", __func__, number );\n\t\treturn false;\n\t}\n\n\tfRemoveType = MSG_ReadUBitLong( msg, 2 );\n\n\tif( fRemoveType )\n\t{\n\t\t// check for a remove\n\t\tmemset( to, 0, sizeof( *to ));\n\n\t\tif( fRemoveType & 1 )\n\t\t{\n\t\t\t// removed from delta-message\n\t\t\treturn false;\n\t\t}\n\n\t\tif( fRemoveType & 2 )\n\t\t{\n\t\t\t// entity was removed from server\n\t\t\tto->number = -1;\n\t\t\treturn false;\n\t\t}\n\n\t\tCon_Printf( S_ERROR \"%s: unknown update type %i\\n\", __func__, fRemoveType );\n\t\treturn false;\n\t}\n\n\tif( !cls.legacymode )\n\t{\n\t\tif( MSG_ReadOneBit( msg ))\n\t\t\tbaseline_offset = MSG_ReadSBitLong( msg, 7 );\n\n\t\tif( baseline_offset != 0 )\n\t\t{\n\t\t\tif( delta_type == DELTA_STATIC )\n\t\t\t{\n\t\t\t\tint backup = Q_max( 0, clgame.numStatics - abs( baseline_offset ));\n\t\t\t\tfrom = &clgame.static_entities[backup].baseline;\n\t\t\t}\n\t\t\telse if( baseline_offset > 0 )\n\t\t\t{\n\t\t\t\tint backup = cls.next_client_entities - baseline_offset;\n\t\t\t\tfrom = &cls.packet_entities[backup % cls.num_client_entities];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tbaseline_offset = abs( baseline_offset + 1 );\n\t\t\t\tif( baseline_offset < cl.instanced_baseline_count )\n\t\t\t\t\tfrom = &cl.instanced_baseline[baseline_offset];\n\t\t\t}\n\t\t}\n\t}\n\t// g-cont. probably is redundant\n\t*to = *from;\n\n\tif( MSG_ReadOneBit( msg ))\n\t\tto->entityType = MSG_ReadUBitLong( msg, 2 );\n\tto->number = number;\n\n\tif( cls.legacymode ? ( to->entityType == ENTITY_BEAM ) : FBitSet( to->entityType, ENTITY_BEAM ))\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T );\n\t}\n\telse if( delta_type == DELTA_PLAYER )\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T );\n\t}\n\telse\n\t{\n\t\tdt = Delta_FindStructByIndex( DT_ENTITY_STATE_T );\n\t}\n\n\tif( !dt || !dt->bInitialized )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: broken delta\\n\", __func__ );\n\t\treturn true;\n\t}\n\n\tpField = dt->pFields;\n\tAssert( pField != NULL );\n\n\t// process fields\n\tfor( i = 0; i < dt->numFields; i++, pField++ )\n\t{\n\t\tDelta_ReadField( msg, pField, from, to, timebase );\n\t}\n#endif // XASH_DEDICATED\n\t// message parsed\n\treturn true;\n}\n\nvoid Delta_ParseTableField_GS( sizebuf_t *msg )\n{\n\tconst char *s = MSG_ReadString( msg );\n\tdelta_info_t *dt = Delta_FindStruct( s );\n\tgoldsrc_delta_t null = { 0 };\n\tint i, num_fields;\n\n\t// delta encoders it's already initialized on this machine (local game)\n\tif( delta_init )\n\t\tDelta_Shutdown();\n\n\tif( !dt )\n\t\tHost_Error( \"%s: not initialized\", __func__ );\n\n\tnum_fields = MSG_ReadShort( msg );\n\tif( num_fields > dt->maxFields )\n\t\tHost_Error( \"%s: numFields > maxFields\", __func__ );\n\n\tMSG_StartBitWriting( msg );\n\n\tfor( i = 0; i < num_fields; i++ )\n\t{\n\t\tgoldsrc_delta_t to;\n\n\t\tDelta_ParseGSFields( msg, &dt_goldsrc_meta, &null, &to, 0.0f );\n\n\t\t// patch our DT_SIGNED flag\n\t\tif( FBitSet( to.fieldType, DT_SIGNED_GS ))\n\t\t{\n\t\t\tClearBits( to.fieldType, DT_SIGNED_GS );\n\t\t\tSetBits( to.fieldType, DT_SIGNED );\n\t\t}\n\t\tDelta_AddField( dt, to.fieldName, to.fieldType, to.significant_bits, to.premultiply, to.postmultiply );\n\t}\n\n\tMSG_EndBitWriting( msg );\n}\n\n/*\n==================\nDelta_WriteDescriptionToClient\n\nsend delta communication encoding\n==================\n*/\nvoid Delta_WriteDescriptionToClient( sizebuf_t *msg )\n{\n\tint\ttableIndex;\n\tint\tfieldIndex;\n\n\tfor( tableIndex = 0; tableIndex < Delta_NumTables(); tableIndex++ )\n\t{\n\t\tdelta_info_t\t*dt = Delta_FindStructByIndex( tableIndex );\n\n\t\tfor( fieldIndex = 0; fieldIndex < dt->numFields; fieldIndex++ )\n\t\t\tDelta_WriteTableField( msg, tableIndex, &dt->pFields[fieldIndex] );\n\t}\n}\n\n/*\n=============================================================================\n\n\tgame.dll interface\n\n=============================================================================\n*/\nvoid GAME_EXPORT Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc )\n{\n\tdelta_info_t\t*dt;\n\n\tdt = Delta_FindStructByEncoder( name );\n\n\tif( !dt || !dt->bInitialized )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: couldn't find delta with specified custom encode %s\\n\", __func__, name );\n\t\treturn;\n\t}\n\n\tif( dt->customEncode == CUSTOM_NONE )\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: %s not supposed for custom encoding\\n\", __func__, dt->pName );\n\t\treturn;\n\t}\n\n\t// register new encode func\n\tdt->userCallback = encodeFunc;\n}\n\nint GAME_EXPORT Delta_FindField( delta_t *pFields, const char *fieldname )\n{\n\tdelta_info_t\t*dt;\n\tdelta_t\t\t*pField;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByDelta( pFields );\n\tif( dt == NULL || !fieldname || !fieldname[0] )\n\t\treturn -1;\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( !Q_strcmp( pField->name, fieldname ))\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nvoid GAME_EXPORT Delta_SetField( delta_t *pFields, const char *fieldname )\n{\n\tdelta_info_t\t*dt;\n\tdelta_t\t\t*pField;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByDelta( pFields );\n\tif( dt == NULL || !fieldname || !fieldname[0] )\n\t\treturn;\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( !Q_strcmp( pField->name, fieldname ))\n\t\t{\n\t\t\tpField->bInactive = false;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nvoid GAME_EXPORT Delta_UnsetField( delta_t *pFields, const char *fieldname )\n{\n\tdelta_info_t\t*dt;\n\tdelta_t\t\t*pField;\n\tint\t\ti;\n\n\tdt = Delta_FindStructByDelta( pFields );\n\tif( dt == NULL || !fieldname || !fieldname[0] )\n\t\treturn;\n\n\tfor( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ )\n\t{\n\t\tif( !Q_strcmp( pField->name, fieldname ))\n\t\t{\n\t\t\tpField->bInactive = true;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nvoid GAME_EXPORT Delta_SetFieldByIndex( delta_t *pFields, int fieldNumber )\n{\n\tdelta_info_t\t*dt;\n\n\tdt = Delta_FindStructByDelta( pFields );\n\tif( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields )\n\t\treturn;\n\n\tdt->pFields[fieldNumber].bInactive = false;\n}\n\nvoid GAME_EXPORT Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber )\n{\n\tdelta_info_t\t*dt;\n\n\tdt = Delta_FindStructByDelta( pFields );\n\tif( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields )\n\t\treturn;\n\n\tdt->pFields[fieldNumber].bInactive = true;\n}\n\n#if XASH_ENGINE_TESTS\n#include \"tests.h\"\n\nvoid Test_RunDelta( void )\n{\n\tdelta_info_t *dt = &dt_info[DT_DELTA_TEST_STRUCT_T];\n\tdelta_test_struct_t from, to = { 0 };\n\tdelta_test_struct_t null = { 0 };\n\tsizebuf_t msg;\n\tint i;\n\tchar buffer[4096] = { 0 };\n\tconst double timebase = 123.123;\n\n\tDelta_AddField( dt, \"dt_string\", DT_STRING, 1, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_timewindow_big\", DT_TIMEWINDOW_BIG, 24, 1000.f, 1.0f );\n\tDelta_AddField( dt, \"dt_timewindow_8\", DT_TIMEWINDOW_8, 8, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_angle\", DT_ANGLE, 16, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_float_signed\", DT_FLOAT | DT_SIGNED, 22, 100.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_float_unsigned\", DT_FLOAT, 24, 10000.0f, 0.1f );\n\tDelta_AddField( dt, \"dt_integer_signed\", DT_INTEGER | DT_SIGNED, 24, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_integer_unsigned\", DT_INTEGER, 24, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_short_signed\", DT_SHORT | DT_SIGNED, 16, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_short_unsigned\", DT_SHORT, 15, 0.125f, 1.0f );\n\tDelta_AddField( dt, \"dt_byte_signed\", DT_BYTE | DT_SIGNED, 6, 1.0f, 1.0f );\n\tDelta_AddField( dt, \"dt_byte_unsigned\", DT_BYTE, 8, 1.0f, 1.0f );\n\n\tQ_strncpy( from.dt_string, \"test data check it's the same\", sizeof( from.dt_string ));\n\tfrom.dt_timewindow_big = timebase + 2.3456;\n\tfrom.dt_timewindow_8 = timebase + 0.0234;\n\tfrom.dt_angle = 160.245f;\n\tfrom.dt_float_signed = -15.123f;\n\tfrom.dt_float_unsigned = 1235.321f;\n\tfrom.dt_integer_signed = -412784;\n\tfrom.dt_integer_unsigned = 123453;\n\tfrom.dt_short_signed = -12343;\n\tfrom.dt_short_unsigned = 32131;\n\tfrom.dt_byte_signed = 16;\n\tfrom.dt_byte_unsigned = 218;\n\n\tMSG_Init( &msg, \"test message\", buffer, sizeof( buffer ));\n\n\tfor( i = 0; i < dt->numFields; i++ )\n\t\tDelta_WriteField( &msg, &dt->pFields[i], &null, &from, timebase );\n\n\tMSG_SeekToBit( &msg, 0, SEEK_SET );\n\n\tfor( i = 0; i < dt->numFields; i++ )\n\t\tDelta_ReadField( &msg, &dt->pFields[i], &null, &to, timebase );\n\n\tCon_Printf( \"struct as encoded to delta:\\n\" );\n\tTASSERT_STR( from.dt_string, to.dt_string );\n\n\t// the epsilon value is derived from multiplier value\n\tTASSERT( Q_equal_e( from.dt_timewindow_big, to.dt_timewindow_big, 0.001f ));\n\n\t// dt_timewindow_8 type has multiplier locked at 100.0f\n\tTASSERT( Q_equal_e( from.dt_timewindow_8, to.dt_timewindow_8, 0.01f ));\n\tTASSERT( Q_equal_e( from.dt_angle, to.dt_angle, 0.1f ));\n\tTASSERT( Q_equal_e( from.dt_float_signed, to.dt_float_signed, 0.01f ));\n\n\t// dt_float_unsigned has post-multiplier that doesn't affect network data\n\t// and therefore should be reverted back when comparing\n\tTASSERT( Q_equal_e( from.dt_float_unsigned, to.dt_float_unsigned * 10.f , 0.01f ));\n\n\tTASSERT_EQi( from.dt_integer_signed, to.dt_integer_signed );\n\tTASSERT_EQi( from.dt_integer_unsigned, to.dt_integer_unsigned );\n\tTASSERT_EQi( from.dt_short_signed, to.dt_short_signed );\n\tTASSERT(( from.dt_short_unsigned & ( 0xffff << 3 )) == to.dt_short_unsigned );\n\tTASSERT_EQi( from.dt_byte_signed, to.dt_byte_signed );\n\tTASSERT_EQi( from.dt_byte_unsigned, to.dt_byte_unsigned );\n\n\tCon_Printf( \"from.dt_timewindow_big = %f\\n\", from.dt_timewindow_big );\n\tCon_Printf( \"to.dt_timewindow_big   = %f\\n\", to.dt_timewindow_big );\n\tCon_Printf( \"from.dt_timewindow_8 = %f\\n\", from.dt_timewindow_8 );\n\tCon_Printf( \"to.dt_timewindow_8   = %f\\n\", to.dt_timewindow_8 );\n\tCon_Printf( \"from.dt_angle = %f\\n\", from.dt_angle );\n\tCon_Printf( \"to.dt_angle   = %f\\n\", to.dt_angle );\n\tCon_Printf( \"from.dt_float_signed = %f\\n\", from.dt_float_signed );\n\tCon_Printf( \"to.dt_float_signed   = %f\\n\", to.dt_float_signed );\n\tCon_Printf( \"from.dt_float_unsigned = %f\\n\", from.dt_float_unsigned );\n\tCon_Printf( \"to.dt_float_unsigned   = %f\\n\", to.dt_float_unsigned );\n\tCon_Printf( \"from.dt_integer_signed = %i\\n\", from.dt_integer_signed );\n\tCon_Printf( \"to.dt_integer_signed   = %i\\n\", to.dt_integer_signed );\n\tCon_Printf( \"from.dt_integer_unsigned = %i\\n\", from.dt_integer_unsigned );\n\tCon_Printf( \"to.dt_integer_unsigned   = %i\\n\", to.dt_integer_unsigned );\n\tCon_Printf( \"from.dt_short_signed = %i\\n\", from.dt_short_signed );\n\tCon_Printf( \"to.dt_short_signed   = %i\\n\", to.dt_short_signed );\n\tCon_Printf( \"from.dt_short_unsigned = %i\\n\", from.dt_short_unsigned );\n\tCon_Printf( \"to.dt_short_unsigned   = %i\\n\", to.dt_short_unsigned );\n\tCon_Printf( \"from.dt_byte_signed = %i\\n\", from.dt_byte_signed );\n\tCon_Printf( \"to.dt_byte_signed   = %i\\n\", to.dt_byte_signed );\n\tCon_Printf( \"from.dt_byte_unsigned = %i\\n\", from.dt_byte_unsigned );\n\tCon_Printf( \"to.dt_byte_unsigned   = %i\\n\", to.dt_byte_unsigned );\n}\n#endif // XASH_ENGINE_TESTS\n"
  },
  {
    "path": "engine/common/net_encode.h",
    "content": "/*\nnet_encode.h - delta encode routines\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef NET_ENCODE_H\n#define NET_ENCODE_H\n\n#include \"eiface.h\"\n\nenum\n{\n\tCUSTOM_NONE = 0,\n\tCUSTOM_SERVER_ENCODE,\t// known as \"gamedll\"\n\tCUSTOM_CLIENT_ENCODE,\t// known as \"client\"\n};\n\n// don't change order!\nenum\n{\n\tDELTA_ENTITY = 0,\n\tDELTA_PLAYER,\n\tDELTA_STATIC,\n};\n\nenum\n{\n\tDT_EVENT_T = 0,\n\tDT_MOVEVARS_T,\n\tDT_USERCMD_T,\n\tDT_CLIENTDATA_T,\n\tDT_WEAPONDATA_T,\n\tDT_ENTITY_STATE_T,\n\tDT_ENTITY_STATE_PLAYER_T,\n\tDT_CUSTOM_ENTITY_STATE_T,\n#if XASH_ENGINE_TESTS\n\tDT_DELTA_TEST_STRUCT_T,\n#endif\n};\n\n// struct info (filled by engine)\ntypedef struct\n{\n\tconst char\t*name;\n\tconst int\t\toffset;\n\tconst int\t\tsize;\n} delta_field_t;\n\n// one field\nstruct delta_s\n{\n\tconst char\t*name;\n\tint\t\toffset;\t\t// in bytes\n\tint\t\tsize;\t\t// used for bounds checking in DT_STRING\n\tint\t\tflags;\t\t// DT_INTEGER, DT_FLOAT etc\n\tfloat\t\tmultiplier;\n\tfloat\t\tpost_multiplier;\t// for DEFINE_DELTA_POST\n\tint\t\tbits;\t\t// how many bits we send\\receive\n\tqboolean\t\tbInactive;\t// unsetted by user request\n};\n\ntypedef struct goldsrc_delta_s\n{\n\tint   fieldType;\n\tchar  fieldName[32];\n\tint   fieldOffset;\n\tshort fieldSize;\n\tint   significant_bits;\n\tfloat premultiply;\n\tfloat postmultiply;\n} goldsrc_delta_t;\n\ntypedef void (*pfnDeltaEncode)( struct delta_s *pFields, const byte *from, const byte *to );\n\ntypedef struct\n{\n\tconst char\t*pName;\n\tconst delta_field_t\t*pInfo;\n\tconst int\t\tmaxFields;\t// maximum number of fields in struct\n\tint\t\tnumFields;\t// may be merged during initialization\n\tdelta_t\t\t*pFields;\n\n\t// added these for custom entity encode\n\tint\t\tcustomEncode;\n\tchar\t\tfuncName[32];\n\tpfnDeltaEncode\tuserCallback;\n\tqboolean\t\tbInitialized;\n} delta_info_t;\n\n//\n// net_encode.c\n//\nvoid Delta_Init( void );\nvoid Delta_InitClient( void );\nvoid Delta_Shutdown( void );\nvoid Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc );\nint Delta_FindField( delta_t *pFields, const char *fieldname );\nvoid Delta_SetField( delta_t *pFields, const char *fieldname );\nvoid Delta_UnsetField( delta_t *pFields, const char *fieldname );\nvoid Delta_SetFieldByIndex( delta_t *pFields, int fieldNumber );\nvoid Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber );\n\n// send table over network\nvoid Delta_WriteDescriptionToClient( sizebuf_t *msg );\nvoid Delta_ParseTableField( sizebuf_t *msg );\nvoid Delta_ParseTableField_GS( sizebuf_t *msg );\n\n\n// encode routines\nstruct entity_state_s;\nstruct usercmd_s;\nstruct event_args_s;\nstruct movevars_s;\nstruct clientdata_s;\nstruct weapon_data_s;\nvoid MSG_WriteDeltaUsercmd( sizebuf_t *msg, const struct usercmd_s *from, const struct usercmd_s *to );\nvoid MSG_ReadDeltaUsercmd( sizebuf_t *msg, const struct usercmd_s *from, struct usercmd_s *to );\nvoid MSG_WriteDeltaEvent( sizebuf_t *msg, const struct event_args_s *from, const struct event_args_s *to );\nvoid MSG_ReadDeltaEvent( sizebuf_t *msg, const struct event_args_s *from, struct event_args_s *to );\nqboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, const struct movevars_s *from, const struct movevars_s *to );\nvoid MSG_ReadDeltaMovevars( sizebuf_t *msg, const struct movevars_s *from, struct movevars_s *to );\nvoid MSG_WriteClientData( sizebuf_t *msg, const struct clientdata_s *from, const struct clientdata_s *to, double timebase );\nvoid MSG_ReadClientData( sizebuf_t *msg, const struct clientdata_s *from, struct clientdata_s *to, double timebase );\nvoid MSG_WriteWeaponData( sizebuf_t *msg, const struct weapon_data_s *from, const struct weapon_data_s *to, double timebase, int index );\nvoid MSG_ReadWeaponData( sizebuf_t *msg, const struct weapon_data_s *from, struct weapon_data_s *to, double timebase );\nvoid MSG_WriteDeltaEntity( const struct entity_state_s *from, const struct entity_state_s *to, sizebuf_t *msg, qboolean force, int type, double timebase, int ofs );\nqboolean MSG_ReadDeltaEntity( sizebuf_t *msg, const struct entity_state_s *from, struct entity_state_s *to, int num, int type, double timebase );\nint Delta_TestBaseline( const struct entity_state_s *from, const struct entity_state_s *to, qboolean player, double timebase );\nvoid Delta_ReadGSFields( sizebuf_t *msg, int index, const void *from, void *to, double timebase );\nvoid Delta_WriteGSFields( sizebuf_t *msg, int index, const void *from, const void *to, double timebase );\n\n#endif//NET_ENCODE_H\n"
  },
  {
    "path": "engine/common/net_http.c",
    "content": "/*\nnet_http.c - HTTP client implementation\nCopyright (C) 2024 mittorn\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\" // ConnectionProgress\n#include \"netchan.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ipv6text.h\"\n#include \"net_ws_private.h\"\n#include \"miniz.h\"\n\n/*\n=================================================\n\nHTTP downloader\n\n=================================================\n*/\n\n#define MAX_HTTP_BUFFER_SIZE (BIT( 16 ))\n\ntypedef struct httpserver_s\n{\n\tchar host[256];\n\tint port;\n\tchar path[MAX_SYSPATH];\n\tstruct httpserver_s *next;\n} httpserver_t;\n\ntypedef struct httpfile_s httpfile_t;\ntypedef int (*http_process_fn_t)( httpfile_t *file );\n\ntypedef struct httpfile_s\n{\n\tstruct httpfile_s *next;\n\thttpserver_t *server;\n\tchar path[MAX_SYSPATH];\n\tfile_t *file;\n\tint socket;\n\tint size;\n\tint reported_size;\n\tint downloaded;\n\tint lastchecksize;\n\tfloat checktime;\n\tfloat blocktime;\n\tconst char *blockreason;\n\tqboolean process;\n\tqboolean got_response;\n\tqboolean success;\n\tqboolean compressed;\n\tqboolean chunked;\n\tint chunksize;\n\tresource_t *resource;\n\thttp_process_fn_t pfn_process;\n\tstruct sockaddr_storage addr;\n\n\tchar query_backup[1024];\n\n\t// query or response\n\tchar buf[MAX_HTTP_BUFFER_SIZE+1];\n\tint header_size, query_length, bytes_sent;\n} httpfile_t;\n\nstatic struct http_static_s\n{\n\t// file and server lists\n\thttpfile_t *first_file;\n\thttpserver_t *first_server;\n\n\tint active_count, progress_count;\n\tfloat progress;\n\tqboolean resolving;\n} http;\n\n\nstatic CVAR_DEFINE_AUTO( http_useragent, \"\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"User-Agent string\" );\nstatic CVAR_DEFINE_AUTO( http_autoremove, \"1\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"remove broken files\" );\nstatic CVAR_DEFINE_AUTO( http_timeout, \"45\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"timeout for http downloader\" );\nstatic CVAR_DEFINE_AUTO( http_maxconnections, \"2\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"maximum http connection number\" );\nstatic CVAR_DEFINE_AUTO( http_show_headers, \"0\", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, \"show HTTP headers (request and response)\" );\n\nstatic int HTTP_FileFree( httpfile_t *file );\nstatic int HTTP_FileConnect( httpfile_t *file );\nstatic int HTTP_FileCreateSocket( httpfile_t *file );\nstatic int HTTP_FileProcessStream( httpfile_t *file );\nstatic int HTTP_FileQueue( httpfile_t *file );\nstatic int HTTP_FileResolveNS( httpfile_t *file );\nstatic int HTTP_FileSendRequest( httpfile_t *file );\nstatic int HTTP_FileDecompress( httpfile_t *file );\n\n/*\n==============\nHTTP_FreeFile\n\nSkip to next server/file\n==============\n*/\nstatic void HTTP_FreeFile( httpfile_t *file, qboolean error )\n{\n\tchar incname[MAX_SYSPATH + 64]; // plus downloaded/ plus .incomplete\n\tqboolean was_open = false;\n\n\tfile->blocktime = 0;\n\n\t// Allways close file and socket\n\tif( file->file )\n\t{\n\t\tFS_Close( file->file );\n\t\twas_open = true;\n\t}\n\n\tfile->file = NULL;\n\n\tif( file->socket != -1 )\n\t{\n\t\tclosesocket( file->socket );\n\t\thttp.active_count--;\n\t}\n\n\tfile->socket = -1;\n\n\tQ_snprintf( incname, sizeof( incname ), DEFAULT_DOWNLOADED_DIRECTORY \"%s.incomplete\", file->path );\n\n\tif( error )\n\t{\n\t\t// switch to next fastdl server if present\n\t\tif( file->server && was_open )\n\t\t{\n\t\t\tfile->server = file->server->next;\n\n\t\t\tfile->pfn_process = HTTP_FileQueue; // Reset download state, HTTP_Run() will open file again\n\t\t\treturn;\n\t\t}\n\n\t\t// Called because there was no servers to download, free file now\n\t\tif( http_autoremove.value == 1 ) // remove broken file\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"no servers to download %s\\n\", file->path );\n\t\t\tFS_Delete( incname );\n\t\t}\n\t\telse // autoremove disabled, keep file\n\t\t{\n\t\t\t// warn about trash file\n\t\t\tCon_Printf( S_ERROR \"no servers to download %s. You may remove %s now\\n\", file->path, incname );\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( file->compressed )\n\t\t{\n\t\t\tFS_Delete( incname );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Success, rename and process file\n\t\t\tchar name[MAX_SYSPATH];\n\t\t\tQ_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY \"%s\", file->path );\n\t\t\tFS_Rename( incname, name );\n\t\t}\n\t}\n\n\tfile->pfn_process = HTTP_FileFree;\n\tfile->success = !error;\n}\n\nstatic int HTTP_FileFree( httpfile_t *file )\n{\n\treturn 0; // do nothing, wait for memory clean up\n}\n\nstatic int HTTP_FileQueue( httpfile_t *file )\n{\n\tchar name[MAX_SYSPATH];\n\n\tif( http.active_count > http_maxconnections.value )\n\t\treturn 0;\n\n\tif( !file->server )\n\t{\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tCon_Reportf( \"HTTP: Starting download %s from %s:%d\\n\", file->path, file->server->host, file->server->port );\n\tQ_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY \"%s.incomplete\", file->path );\n\n\tif( !( file->file = FS_Open( name, \"wb+\", true )))\n\t{\n\t\tCon_Printf( S_ERROR \"HTTP: cannot open %s!\\n\", name );\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tfile->pfn_process = HTTP_FileResolveNS;\n\tfile->blocktime = file->downloaded = file->lastchecksize = file->checktime = 0;\n\treturn 1;\n}\n\nstatic int HTTP_FileResolveNS( httpfile_t *file )\n{\n\tnet_gai_state_t res;\n\n\tif( http.resolving )\n\t\treturn 0;\n\n\tmemset( &file->addr, 0, sizeof( file->addr ));\n\n\tres = NET_StringToSockaddr( file->server->host, &file->addr, true, AF_UNSPEC );\n\n\tswitch( file->addr.ss_family )\n\t{\n\tcase AF_INET:\n\t\t((struct sockaddr_in *)&file->addr)->sin_port = MSG_BigShort( file->server->port );\n\t\tbreak;\n\tcase AF_INET6:\n\t\t((struct sockaddr_in6 *)&file->addr)->sin6_port = MSG_BigShort( file->server->port );\n\t\tbreak;\n\t}\n\n\tif( res == NET_EAI_AGAIN )\n\t{\n\t\thttp.resolving = true;\n\t\treturn 0;\n\t}\n\n\tif( res == NET_EAI_NONAME )\n\t{\n\t\tCon_Printf( S_ERROR \"failed to resolve server address for %s!\\n\", file->server->host );\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tfile->pfn_process = HTTP_FileCreateSocket;\n\treturn 1;\n}\n\nstatic int HTTP_FileCreateSocket( httpfile_t *file )\n{\n\tuint mode = 1;\n\tint res;\n\n\tfile->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP );\n\n\tif( file->socket < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: socket() returned %s\\n\", __func__, NET_ErrorString());\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tif( ioctlsocket( file->socket, FIONBIO, (void *)&mode ) < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: ioctl() returned %s\\n\", __func__, NET_ErrorString());\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n#if XASH_LINUX\n\n\tres = fcntl( file->socket, F_GETFL, 0 );\n\n\tif( res < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: fcntl( F_GETFL ) returned %s\\n\", __func__, NET_ErrorString());\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\t// SOCK_NONBLOCK is not portable, so use fcntl\n\tif( fcntl( file->socket, F_SETFL, res | O_NONBLOCK ) < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: fcntl( F_SETFL ) returned %s\\n\", __func__, NET_ErrorString());\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n#endif\n\n\thttp.active_count++;\n\tfile->pfn_process = HTTP_FileConnect;\n\treturn 1;\n}\n\nstatic int HTTP_FileConnect( httpfile_t *file )\n{\n\tstring useragent;\n\tint res = connect( file->socket, (struct sockaddr *)&file->addr, NET_SockAddrLen( &file->addr ));\n\n\tif( res < 0 )\n\t{\n\t\tint err = WSAGetLastError();\n\n\t\tswitch( err )\n\t\t{\n\t\tcase WSAEISCONN:\n\t\t\t// we're connected, proceed\n\t\t\tbreak;\n\t\tcase WSAEWOULDBLOCK:\n\t\tcase WSAEINPROGRESS:\n\t\tcase WSAEALREADY:\n\t\t\t// add to the timeout\n\t\t\tfile->blocktime += host.frametime;\n\t\t\tfile->blockreason = \"request send\";\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\t// error, exit\n\t\t\tCon_Printf( S_ERROR \"cannot connect to server: %s\\n\", NET_ErrorString( ));\n\t\t\tHTTP_FreeFile( file, true );\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tfile->blocktime = 0;\n\n\tif( !COM_CheckStringEmpty( http_useragent.string ) || !Q_strcmp( http_useragent.string, \"xash3d\" ))\n\t{\n\t\tQ_snprintf( useragent, sizeof( useragent ), \"%s/%s (%s-%s; build %d; %s)\",\n\t\t\tXASH_ENGINE_NAME, XASH_VERSION, Q_buildos( ), Q_buildarch( ), Q_buildnum( ), g_buildcommit );\n\t}\n\telse Q_strncpy( useragent, http_useragent.string, sizeof( useragent ));\n\n\tfile->query_length = Q_snprintf( file->buf, sizeof( file->buf ),\n\t\t\"GET %s%s HTTP/1.1\\r\\n\"\n\t\t\"Host: %s:%d\\r\\n\"\n\t\t\"User-Agent: %s\\r\\n\"\n\t\t\"Accept-Encoding: gzip, deflate\\r\\n\"\n\t\t\"Accept: */*\\r\\n\\r\\n\",\n\t\tfile->server->path, file->path,\n\t\tfile->server->host, file->server->port,\n\t\tuseragent );\n\tQ_strncpy( file->query_backup, file->buf, sizeof( file->query_backup ));\n\tfile->bytes_sent = 0;\n\tfile->header_size = 0;\n\tfile->pfn_process = HTTP_FileSendRequest;\n\treturn 1;\n}\n\nstatic int HTTP_FileSendRequest( httpfile_t *file )\n{\n\tint res = -1;\n\n\tres = send( file->socket, file->buf + file->bytes_sent, file->query_length - file->bytes_sent, 0 );\n\n\tif( res >= 0 )\n\t{\n\t\tfile->bytes_sent += res;\n\t\tfile->blocktime = 0;\n\n\t\tif( file->bytes_sent >= file->query_length )\n\t\t{\n\t\t\tif( http_show_headers.value )\n\t\t\t\tCon_Reportf( \"HTTP: Request sent! (size %d data %s)\\n\", file->bytes_sent, file->buf );\n\t\t\telse\n\t\t\t\tCon_Reportf( \"HTTP: Request sent!\\n\" );\n\t\t\tmemset( file->buf, 0, sizeof( file->buf ));\n\t\t\tfile->pfn_process = HTTP_FileProcessStream;\n\t\t\treturn 1;\n\t\t}\n\t}\n\telse\n\t{\n\t\tint err = WSAGetLastError();\n\t\tif( err != WSAEWOULDBLOCK && err != WSAENOTCONN )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"failed to send request: %s\\n\", NET_ErrorString( ));\n\t\t\tHTTP_FreeFile( file, true );\n\t\t\treturn 0;\n\t\t}\n\n\t\tfile->blocktime += host.frametime;\n\t\tfile->blockreason = \"request send\";\n\t}\n\n\treturn 0;\n}\n\nstatic int HTTP_FileDecompress( httpfile_t *file )\n{\n\tfs_offset_t len;\n#pragma pack( push, 1 )\n\tstruct\n\t{\n\t\tbyte magic[2];\n\t\tbyte method;\n\t\tbyte flags;\n\t\tuint32_t mtime; // ignored\n\t\tbyte xfl;\n\t\tbyte os; // can be ignored\n\t} hdr;\n#pragma pack( pop )\n\n\tenum\n\t{\n\t\tGZFLG_FTEXT = BIT( 0 ), // can be ignored\n\t\tGZFLG_FHCRC = BIT( 1 ),\n\t\tGZFLG_FEXTRA = BIT( 2 ),\n\t\tGZFLG_FNAME = BIT( 3 ),\n\t\tGZFLG_FCOMMENT = BIT( 4 )\n\t};\n\n\tz_stream decompress_stream;\n\tchar name[MAX_SYSPATH];\n\tfs_offset_t deflate_pos;\n\tsize_t compressed_len, decompressed_len;\n\tbyte *data_in, *data_out;\n\tint zlib_result;\n\n\tg_fsapi.Seek( file->file, 0, SEEK_END );\n\tlen = g_fsapi.Tell( file->file );\n\n\tg_fsapi.Seek( file->file, 0, SEEK_SET );\n\tif( g_fsapi.Read( file->file, &hdr, sizeof( hdr )) != sizeof( hdr ))\n\t{\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tif( hdr.magic[0] != 0x1f && hdr.magic[1] != 0x8b && hdr.method != 0x08 )\n\t{\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tif( FBitSet( hdr.flags, GZFLG_FEXTRA ))\n\t{\n\t\tbyte res[2];\n\t\tuint16_t xlen;\n\n\t\tg_fsapi.Read( file->file, res, sizeof( res ));\n\t\txlen = res[0] | res[1] << 16;\n\t\tg_fsapi.Seek( file->file, xlen, SEEK_CUR );\n\t}\n\n\tif( FBitSet( hdr.flags, GZFLG_FNAME ))\n\t{\n\t\tbyte ch;\n\t\tdo\n\t\t{\n\t\t\tg_fsapi.Read( file->file, &ch, sizeof( ch ));\n\t\t} while( ch != 0 );\n\t}\n\n\tif( FBitSet( hdr.flags, GZFLG_FCOMMENT ))\n\t{\n\t\tbyte ch;\n\t\tdo\n\t\t{\n\t\t\tg_fsapi.Read( file->file, &ch, sizeof( ch ));\n\t\t} while( ch != 0 );\n\t}\n\n\tif( FBitSet( hdr.flags, GZFLG_FHCRC ))\n\t\tg_fsapi.Seek( file->file, 2, SEEK_CUR );\n\n\tdeflate_pos = g_fsapi.Tell( file->file );\n\tcompressed_len = len - deflate_pos;\n\n\t{\n\t\tbyte data[4];\n\n\t\tg_fsapi.Seek( file->file, -4, SEEK_END );\n\t\tg_fsapi.Read( file->file, data, sizeof( data ));\n\n\t\t// FIXME: this isn't correct, as this size might be modulo 2^32\n\t\t// but we probably won't decompress files this big\n\t\tdecompressed_len = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;\n\t}\n\n\tdata_in = Mem_Malloc( host.mempool, compressed_len + 1 );\n\tdata_out = Mem_Malloc( host.mempool, decompressed_len + 1 );\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY \"%s\", file->path );\n\n\tmemset( &decompress_stream, 0, sizeof( decompress_stream ));\n\tdecompress_stream.total_in = decompress_stream.avail_in = compressed_len;\n\tdecompress_stream.next_in = data_in;\n\tdecompress_stream.total_out = decompress_stream.avail_out = decompressed_len;\n\tdecompress_stream.next_out = data_out;\n\n\tg_fsapi.Seek( file->file, deflate_pos, SEEK_SET );\n\tg_fsapi.Read( file->file, data_in, compressed_len );\n\n\tif( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: inflateInit2 failed\\n\", __func__ );\n\t\tMem_Free( data_in );\n\t\tMem_Free( data_out );\n\t\tHTTP_FreeFile( file, true );\n\t\treturn 0;\n\t}\n\n\tzlib_result = inflate( &decompress_stream, Z_NO_FLUSH );\n\tinflateEnd( &decompress_stream );\n\n\tif( zlib_result == Z_OK || zlib_result == Z_STREAM_END )\n\t{\n\t\tg_fsapi.WriteFile( name, data_out, decompressed_len );\n\t\tHTTP_FreeFile( file, false );\n\t}\n\telse HTTP_FreeFile( file, true );\n\n\tMem_Free( data_in );\n\tMem_Free( data_out );\n\n\treturn 1;\n}\n\n/*\n========================\nHTTP_ClearCustomServers\n========================\n*/\nvoid HTTP_ClearCustomServers( void )\n{\n\tif( http.first_file )\n\t\treturn; // may be referenced\n\n\twhile( http.first_server )\n\t{\n\t\thttpserver_t *tmp = http.first_server;\n\n\t\thttp.first_server = http.first_server->next;\n\t\tMem_Free( tmp );\n\t}\n}\n\n\n/*\n===================\nHTTP_AutoClean\n\nremove files with HTTP_FREE state from list\n===================\n*/\nstatic void HTTP_AutoClean( void )\n{\n\tchar buf[1024];\n\thttpfile_t *cur, **prev = &http.first_file;\n\tsizebuf_t msg;\n\n\tMSG_Init( &msg, \"DlFile\", buf, sizeof( buf ));\n\n\t// clean all files marked to free\n\twhile( 1 )\n\t{\n\t\tcur = *prev;\n\n\t\tif( !cur )\n\t\t\tbreak;\n\n\t\tif( cur->pfn_process != HTTP_FileFree )\n\t\t{\n\t\t\tprev = &cur->next;\n\t\t\tcontinue;\n\t\t}\n\n#if !XASH_DEDICATED\n\t\tif( cur->process )\n\t\t{\n\t\t\tif( cur->resource && !cur->success )\n\t\t\t{\n\t\t\t\tMSG_BeginClientCmd( &msg, clc_stringcmd );\n\t\t\t\tMSG_WriteStringf( &msg, \"dlfile %s\", cur->path );\n\t\t\t}\n\t\t\telse CL_ProcessFile( cur->success, cur->path );\n\t\t}\n\t\telse\n#endif\n\t\t{\n\t\t\tif( cur->success )\n\t\t\t\tCon_Printf( \"successfully downloaded %s!\\n\", cur->path );\n\t\t}\n\n\t\t*prev = cur->next;\n\t\tMem_Free( cur );\n\t}\n\n#if !XASH_DEDICATED\n\tif( MSG_GetNumBytesWritten( &msg ) > 0 )\n\t{\n\t\t// it's expected to be on fragments channel\n\t\tNetchan_CreateFragments( &cls.netchan, &msg );\n\t\tNetchan_FragSend( &cls.netchan );\n\t}\n#endif\n}\n\nstatic int HTTP_FileSaveReceivedData( httpfile_t *file, int pos, int length )\n{\n\twhile( length > 0 )\n\t{\n\t\tint oldpos = pos;\n\t\tint ret;\n\t\tint len_to_write;\n\n\t\tif( file->chunked && file->chunksize <= 0 )\n\t\t{\n\t\t\tchar *begin = &file->buf[pos];\n\n\t\t\tif( begin[0] == '\\r' && begin[1] == '\\r' )\n\t\t\t\tbegin += 2;\n\n\t\t\tfile->chunksize = Q_atoi_hex( 1, begin );\n\n\t\t\tif( !file->chunksize && begin[0] == '0' ) // actually an end, not Q_atoi being stupid\n\t\t\t{\n\t\t\t\tif( file->compressed )\n\t\t\t\t{\n\t\t\t\t\tfile->blocktime = 0;\n\t\t\t\t\tfile->pfn_process = HTTP_FileDecompress;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tfs_offset_t filelen = FS_FileLength( file->file );\n\n\t\t\t\t\tif( filelen != file->reported_size )\n\t\t\t\t\t{\n\t\t\t\t\t\tCon_Printf( S_ERROR \"downloaded file %s size doesn't match reported size. Got %ld bytes, expected %d bytes\\n\", file->path, (long)filelen, file->reported_size );\n\t\t\t\t\t\tHTTP_FreeFile( file, true );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tHTTP_FreeFile( file, false ); // success\n\t\t\t\t\t}\n\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbegin = Q_strstr( begin, \"\\r\\n\" );\n\t\t\tif( !begin )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"can't parse chunked transfer encoding header for %s\\n\", file->path );\n\t\t\t\tif( http_show_headers.value )\n\t\t\t\t\tCon_Reportf( \"Request headers: %s\", file->query_backup );\n\t\t\t\tHTTP_FreeFile( file, true );\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tpos = ( begin + 2 ) - file->buf;\n\t\t\tlength -= pos - oldpos;\n\n\t\t\tif( length < 0 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"can't parse chunked transfer encoding header 2 for %s\\n\", file->path );\n\t\t\t\tif( http_show_headers.value )\n\t\t\t\t\tCon_Reportf( \"Request headers: %s\", file->query_backup );\n\t\t\t\tHTTP_FreeFile( file, true );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tif( file->chunked )\n\t\t\tlen_to_write = Q_min( length, file->chunksize );\n\t\telse len_to_write = length;\n\n\t\tret = FS_Write( file->file, &file->buf[pos], len_to_write );\n\t\tif( ret != len_to_write )\n\t\t{\n\t\t\t// close it and go to next\n\t\t\tCon_Printf( S_ERROR \"write failed for %s!\\n\", file->path );\n\t\t\tHTTP_FreeFile( file, true );\n\t\t\treturn 0;\n\t\t}\n\n\t\tlength -= len_to_write;\n\t\tfile->chunksize -= len_to_write;\n\n\t\tpos += ret;\n\t\tfile->downloaded += ret;\n\t\tfile->lastchecksize += ret;\n\t}\n\n\treturn 1;\n}\n\n/*\n===================\nHTTP_ProcessStream\n\nprocess incoming data\n===================\n*/\nstatic int HTTP_FileProcessStream( httpfile_t *curfile )\n{\n\tchar buf[sizeof( curfile->buf )];\n\tchar *begin = 0;\n\tint res;\n\n\t// if we got there, we are receiving data\n\twhile(( res = recv( curfile->socket, buf, sizeof( buf ) - curfile->header_size - 1, 0 )) > 0 )\n\t{\n\t\tcurfile->blocktime = 0;\n\n\t\tif( !curfile->got_response ) // Response still not received\n\t\t{\n\t\t\tif( curfile->header_size + res + 1 > sizeof( buf ))\n\t\t\t{\n\t\t\t\tCon_Reportf( S_ERROR \"Header too big, the size is %d\\n\", curfile->header_size );\n\t\t\t\tHTTP_FreeFile( curfile, true );\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tmemcpy( curfile->buf + curfile->header_size, buf, res );\n\t\t\tcurfile->buf[curfile->header_size + res] = 0;\n\t\t\tbegin = Q_strstr( curfile->buf, \"\\r\\n\\r\\n\" );\n\n\t\t\tif( begin ) // Got full header\n\t\t\t{\n\t\t\t\tchar *content_length;\n\t\t\t\tchar *content_encoding;\n\t\t\t\tchar *transfer_encoding;\n\n\t\t\t\t*begin = 0; // cut string to print out response\n\n\t\t\t\tif( !Q_strstr( curfile->buf, \"200 OK\" ))\n\t\t\t\t{\n\t\t\t\t\tchar *p;\n\n\t\t\t\t\tint num = -1;\n\n\t\t\t\t\tp = Q_strchr( curfile->buf, '\\r' );\n\t\t\t\t\tif( !p ) p = Q_strchr( curfile->buf, '\\n' );\n\t\t\t\t\tif( p ) *p = 0;\n\n\t\t\t\t\t// extract the error code, don't assume the response is valid HTTP\n\t\t\t\t\tif( !Q_strncmp( curfile->buf, \"HTTP/1.\", 7 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tchar tmp[4];\n\n\t\t\t\t\t\tQ_strncpy( tmp, curfile->buf + 9, sizeof( tmp ));\n\t\t\t\t\t\tif( Q_isdigit( tmp ))\n\t\t\t\t\t\t\tnum = Q_atoi( tmp );\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch( num )\n\t\t\t\t\t{\n\t\t\t\t\t// TODO: handle redirects\n\t\t\t\t\tcase 404:\n\t\t\t\t\t\tCon_Printf( S_ERROR \"%s: file not found\\n\", curfile->path );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tCon_Printf( S_ERROR \"%s: bad response: %s\\n\", curfile->path, curfile->buf );\n\t\t\t\t\t\tif( http_show_headers.value )\n\t\t\t\t\t\t\tCon_Printf( \"Request headers: %s\", curfile->query_backup );\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tHTTP_FreeFile( curfile, true );\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tcontent_encoding = Q_stristr( curfile->buf, \"Content-Encoding\" );\n\t\t\t\tif( content_encoding ) // fetch compressed status\n\t\t\t\t{\n\t\t\t\t\tcontent_encoding += sizeof( \"Content-Encoding: \" ) - 1;\n\n\t\t\t\t\tif( !Q_strnicmp( content_encoding, \"gzip\", 4 ) && ( content_encoding[4] == '\\0' || content_encoding[4] == '\\n' || content_encoding[4] == '\\r' ))\n\t\t\t\t\t\tcurfile->compressed = true;\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tCon_Printf( S_ERROR \"%s: bad Content-Encoding: %s\\n\", curfile->path, content_encoding );\n\t\t\t\t\t\tif( http_show_headers.value )\n\t\t\t\t\t\t\tCon_Printf( \"Request headers: %s\", curfile->query_backup );\n\t\t\t\t\t\tHTTP_FreeFile( curfile, true );\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif(( transfer_encoding = Q_stristr( curfile->buf, \"Transfer-Encoding: chunked\" )))\n\t\t\t\t{\n\t\t\t\t\tcurfile->size = -1;\n\t\t\t\t\tcurfile->chunked = true;\n\n\t\t\t\t\tCon_Reportf( \"HTTP: Got 200 OK! Chunked transfer encoding%s\\n\", curfile->compressed ? \", compressed\" : \"\" );\n\t\t\t\t}\n\t\t\t\telse if(( content_length = Q_stristr( curfile->buf, \"Content-Length: \" ) ))\n\t\t\t\t{\n\t\t\t\t\tint size;\n\n\t\t\t\t\tcontent_length += sizeof( \"Content-Length: \" ) - 1;\n\t\t\t\t\tsize = Q_atoi( content_length );\n\n\t\t\t\t\tCon_Reportf( \"HTTP: Got 200 OK! File size is %d%s\\n\", curfile->size, curfile->compressed ? \", compressed\" : \"\" );\n\n\t\t\t\t\tif( !curfile->compressed )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( ( curfile->size != -1 ) && ( curfile->size != size )) // check size if specified, not used\n\t\t\t\t\t\t\tCon_Reportf( S_WARN \"Server reports wrong file size for %s!\\n\", curfile->path );\n\t\t\t\t\t}\n\n\t\t\t\t\tcurfile->size = size;\n\t\t\t\t\tcurfile->header_size = 0;\n\t\t\t\t}\n\n\t\t\t\tif( curfile->size == -1 && !curfile->chunked )\n\t\t\t\t{\n\t\t\t\t\t// Usually fastdl's reports file size if link is correct\n\t\t\t\t\tCon_Printf( S_ERROR \"file size is unknown, refusing download!\\n\" );\n\t\t\t\t\tHTTP_FreeFile( curfile, true );\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tif( http_show_headers.value )\n\t\t\t\t\tCon_Reportf( \"Response headers: %s\\n\", curfile->buf );\n\n\t\t\t\tcurfile->got_response = true; // got response, let's start download\n\t\t\t\tbegin += 4;\n\n\t\t\t\tif( res - ( begin - curfile->buf ) > 0 )\n\t\t\t\t{\n\t\t\t\t\tif( !HTTP_FileSaveReceivedData( curfile, begin - curfile->buf, res - ( begin - curfile->buf )))\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tcurfile->header_size += res;\n\t\t}\n\t\telse if( res > 0 )\n\t\t{\n\t\t\tmemcpy( curfile->buf, buf, res );\n\n\t\t\t// data download\n\t\t\tif( !HTTP_FileSaveReceivedData( curfile, 0, res ))\n\t\t\t\treturn 0;\n\n\t\t\t// as after it will run in same frame\n\t\t\tif( curfile->checktime > 5 )\n\t\t\t{\n\t\t\t\tfloat speed = (float)curfile->lastchecksize / ( 5.0f * 1024 );\n\n\t\t\t\tcurfile->checktime = 0;\n\t\t\t\tCon_Reportf( \"download speed %f KB/s\\n\", speed );\n\t\t\t\tcurfile->lastchecksize = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( curfile->size > 0 )\n\t{\n\t\thttp.progress += (float)curfile->downloaded / curfile->size;\n\t\thttp.progress_count++;\n\n\t\tif( curfile->downloaded >= curfile->size )\n\t\t{\n\t\t\t// chunked files are finalized in FileSaveReceivedData\n\t\t\tif( curfile->compressed && !curfile->chunked )\n\t\t\t{\n\t\t\t\tcurfile->pfn_process = HTTP_FileDecompress;\n\t\t\t\tcurfile->success = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tHTTP_FreeFile( curfile, false ); // success\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif( res == 0 )\n\t{\n\t\tcurfile->blocktime += host.frametime;\n\t\tcurfile->blockreason = \"waiting for data\";\n\t}\n\n\tif( res < 0 )\n\t{\n\t\tint err = WSAGetLastError();\n\n\t\tif( err != WSAEWOULDBLOCK && err != WSAEINPROGRESS )\n\t\t{\n\t\t\tCon_Reportf( \"problem downloading %s: %s\\n\", curfile->path, NET_ErrorString( ));\n\t\t\tHTTP_FreeFile( curfile, true );\n\t\t\treturn 0;\n\t\t}\n\n\t\tcurfile->blocktime += host.frametime;\n\n\t\tif( !curfile->got_response )\n\t\t\tcurfile->blockreason = \"receiving header\";\n\t\telse curfile->blockreason = \"receiving data\";\n\t\treturn 0;\n\t}\n\n\tcurfile->checktime += host.frametime;\n\treturn 0; // don't block\n}\n\n/*\n==============\nHTTP_Run\n\nDownload next file block of each active file\nCall every frame\n==============\n*/\nvoid HTTP_Run( void )\n{\n\thttpfile_t *curfile;\n\n\thttp.resolving = false;\n\thttp.progress_count = 0;\n\thttp.progress = 0;\n\n\tfor( curfile = http.first_file; curfile; curfile = curfile->next )\n\t{\n\t\tint move_next = 1;\n\n\t\twhile( move_next > 0 )\n\t\t\tmove_next = curfile->pfn_process( curfile );\n\n\t\tif( curfile->blocktime > http_timeout.value )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"timeout on %s (file: %s)\\n\", curfile->blockreason, curfile->path );\n\t\t\tHTTP_FreeFile( curfile, true );\n\t\t}\n\t}\n\n\t// update progress\n\tif( !Host_IsDedicated() && http.progress_count != 0 )\n\t\tCvar_SetValue( \"scr_download\", http.progress/http.progress_count * 100 );\n\n\tHTTP_AutoClean();\n}\n\n/*\n===================\nHTTP_AddDownload\n\nAdd new download to end of queue\n===================\n*/\nvoid HTTP_AddDownload( const char *path, int size, qboolean process, resource_t *res )\n{\n\thttpfile_t *httpfile;\n\n\tif( !http.first_server )\n\t{\n\t\tCon_Printf( S_ERROR \"no servers to download %s\\n\", path );\n\t\treturn;\n\t}\n\n\thttpfile = Z_Calloc( sizeof( *httpfile ));\n\n\tCon_Reportf( \"File %s queued to download\\n\", path );\n\n\thttpfile->resource = res;\n\thttpfile->size = size;\n\thttpfile->reported_size = size;\n\thttpfile->socket = -1;\n\tQ_strncpy( httpfile->path, path, sizeof( httpfile->path ));\n\n\thttpfile->pfn_process = HTTP_FileQueue;\n\thttpfile->server = http.first_server;\n\thttpfile->process = process;\n\n\thttpfile->next = http.first_file;\n\thttp.first_file = httpfile;\n}\n\n/*\n===============\nHTTP_Download_f\n\nConsole wrapper\n===============\n*/\nstatic void HTTP_Download_f( void )\n{\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"download <gamedir_path>\\n\");\n\t\treturn;\n\t}\n\n\tHTTP_AddDownload( Cmd_Argv( 1 ), -1, false, NULL );\n}\n\n/*\n==============\nHTTP_ParseURL\n==============\n*/\nstatic httpserver_t *HTTP_ParseURL( const char *url )\n{\n\thttpserver_t *server;\n\tint i;\n\n\turl = Q_strstr( url, \"http://\" );\n\n\tif( !url )\n\t\treturn NULL;\n\n\turl += 7;\n\tserver = Z_Calloc( sizeof( httpserver_t ));\n\ti = 0;\n\n\twhile( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\\r' ) && ( *url != '\\n' ))\n\t{\n\t\tif( i > sizeof( server->host ))\n\t\t\treturn NULL;\n\n\t\tserver->host[i++] = *url++;\n\t}\n\n\tserver->host[i] = 0;\n\n\tif( *url == ':' )\n\t{\n\t\tserver->port = Q_atoi( ++url );\n\n\t\twhile( *url && ( *url != '/' ) && ( *url != '\\r' ) && ( *url != '\\n' ))\n\t\t\turl++;\n\t}\n\telse\n\t\tserver->port = 80;\n\n\ti = 0;\n\n\twhile( *url && ( *url != '\\r' ) && ( *url != '\\n' ))\n\t{\n\t\tif( i > sizeof( server->path ) - 1 )\n\t\t\treturn NULL;\n\n\t\tserver->path[i++] = *url++;\n\t}\n\n\tif( i == 0 || server->path[i-1] != '/' )\n\t\tserver->path[i++] = '/';\n\tserver->path[i] = 0;\n\tserver->next = NULL;\n\n\treturn server;\n}\n\n/*\n=======================\nHTTP_AddCustomServer\n=======================\n*/\nvoid HTTP_AddCustomServer( const char *url )\n{\n\thttpserver_t *server = HTTP_ParseURL( url );\n\n\tif( !server )\n\t{\n\t\tCon_Printf( S_ERROR \"\\\"%s\\\" is not valid url!\\n\", url );\n\t\treturn;\n\t}\n\n\tserver->next = http.first_server;\n\thttp.first_server = server;\n}\n\n/*\n=======================\nHTTP_AddCustomServer_f\n=======================\n*/\nstatic void HTTP_AddCustomServer_f( void )\n{\n\tif( Cmd_Argc() == 2 )\n\t{\n\t\tHTTP_AddCustomServer( Cmd_Argv( 1 ));\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_USAGE \"http_addcustomserver <url>\\n\" );\n\t}\n}\n\n/*\n============\nHTTP_Clear_f\n\nClear all queue\n============\n*/\nstatic void HTTP_Clear_f( void )\n{\n\twhile( http.first_file )\n\t{\n\t\thttpfile_t *file = http.first_file;\n\n\t\thttp.first_file = http.first_file->next;\n\n\t\tif( file->file )\n\t\t\tFS_Close( file->file );\n\n\t\tif( file->socket != -1 )\n\t\t\tclosesocket( file->socket );\n\n\t\tMem_Free( file );\n\t}\n}\n\n/*\n==============\nHTTP_Cancel_f\n\nStop current download, skip to next file\n==============\n*/\nstatic void HTTP_Cancel_f( void )\n{\n\tif( !http.first_file )\n\t\treturn;\n\n\thttp.first_file->server = NULL;\n\tHTTP_FreeFile( http.first_file, true );\n}\n\n/*\n=============\nHTTP_Skip_f\n\nStop current download, skip to next server\n=============\n*/\nstatic void HTTP_Skip_f( void )\n{\n\tif( http.first_file )\n\t\tHTTP_FreeFile( http.first_file, true );\n}\n\n/*\n=============\nHTTP_List_f\n\nPrint all pending downloads to console\n=============\n*/\nstatic void HTTP_List_f( void )\n{\n\tint i = 0;\n\thttpfile_t *file;\n\n\tif( !http.first_file )\n\t\tCon_Printf( \"no downloads queued\\n\" );\n\n\tfor( file = http.first_file; file; file = file->next )\n\t{\n\t\tCon_Printf( \"%d. %s (%d of %d)\\n\", i++, file->path, file->downloaded, file->size );\n\n\t\tif( file->server )\n\t\t{\n\t\t\thttpserver_t *server;\n\t\t\tfor( server = file->server; server; server = server->next )\n\t\t\t{\n\t\t\t\tCon_Printf( \"\\thttp://%s:%d/%s%s\\n\", file->server->host, file->server->port,\n\t\t\t\t\tfile->server->path, file->path );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nHTTP_ResetProcessState\n\nWhen connected to new server, all old files should not increase counter\n================\n*/\nvoid HTTP_ResetProcessState( void )\n{\n\thttpfile_t *file;\n\n\tfor( file = http.first_file; file; file = file->next )\n\t\tfile->process = false;\n}\n\n/*\n=============\nHTTP_Init\n=============\n*/\nvoid HTTP_Init( void )\n{\n\thttp.first_file = NULL;\n\n\tCmd_AddRestrictedCommand( \"http_download\", HTTP_Download_f, \"add file to download queue\" );\n\tCmd_AddRestrictedCommand( \"http_skip\", HTTP_Skip_f, \"skip current download server\" );\n\tCmd_AddRestrictedCommand( \"http_cancel\", HTTP_Cancel_f, \"cancel current download\" );\n\tCmd_AddRestrictedCommand( \"http_clear\", HTTP_Clear_f, \"cancel all downloads\" );\n\tCmd_AddRestrictedCommand( \"http_list\", HTTP_List_f, \"list all queued downloads\" );\n\tCmd_AddCommand( \"http_addcustomserver\", HTTP_AddCustomServer_f, \"add custom fastdl server\");\n\n\tCvar_RegisterVariable( &http_useragent );\n\tCvar_RegisterVariable( &http_autoremove );\n\tCvar_RegisterVariable( &http_timeout );\n\tCvar_RegisterVariable( &http_maxconnections );\n\tCvar_RegisterVariable( &http_show_headers );\n}\n\n/*\n====================\nHTTP_Shutdown\n====================\n*/\nvoid HTTP_Shutdown( void )\n{\n\tHTTP_Clear_f();\n\n\twhile( http.first_server )\n\t{\n\t\thttpserver_t *tmp = http.first_server;\n\n\t\thttp.first_server = http.first_server->next;\n\t\tMem_Free( tmp );\n\t}\n}\n"
  },
  {
    "path": "engine/common/net_ws.c",
    "content": "/*\nnet_ws.c - win network interface\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"client.h\" // ConnectionProgress\n#include \"netchan.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ipv6text.h\"\n#include \"net_ws_private.h\"\n\n#if XASH_SDL == 2\n#include <SDL_thread.h>\n#endif\n\n#define NET_USE_FRAGMENTS\n\n#define MAX_LOOPBACK\t\t4\n#define MASK_LOOPBACK\t\t(MAX_LOOPBACK - 1)\n\n#define MAX_ROUTEABLE_PACKET      1400\n#define SPLITPACKET_MIN_SIZE      508   // RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header)\n#define SPLITPACKET_MAX_SIZE      64000\n#define NET_MAX_FRAGMENTS         ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET )))\n#define NET_MAX_GOLDSRC_FRAGMENTS 5 // magic number\n\n// ff02:1\nstatic const uint8_t k_ipv6Bytes_LinkLocalAllNodes[16] =\n{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };\n\ntypedef struct\n{\n\tbyte\t\tdata[NET_MAX_MESSAGE];\n\tint\t\tdatalen;\n} net_loopmsg_t;\n\ntypedef struct\n{\n\tnet_loopmsg_t\tmsgs[MAX_LOOPBACK];\n\tint\t\tget, send;\n} net_loopback_t;\n\ntypedef struct packetlag_s\n{\n\tbyte\t\t*data;\t// Raw stream data is stored.\n\tint\t\tsize;\n\tnetadr_t\t\tfrom;\n\tfloat\t\treceivedtime;\n\tstruct packetlag_s\t*next;\n\tstruct packetlag_s\t*prev;\n} packetlag_t;\n\n// split long packets. Anything over 1460 is failing on some routers.\ntypedef struct\n{\n\tint\t\tcurrent_sequence;\n\tint\t\tsplit_count;\n\tint\t\ttotal_size;\n\tchar\t\tbuffer[NET_MAX_FRAGMENT];\n} LONGPACKET;\n\n// use this to pick apart the network stream, must be packed\n#pragma pack(push, 1)\ntypedef struct\n{\n\tint\t\tnet_id;\n\tint\t\tsequence_number;\n\tshort\t\tpacket_id;\n} SPLITPACKET;\n\ntypedef struct\n{\n\tint\t\tnet_id;\n\tint\t\tsequence_number;\n\tunsigned char\tpacket_id;\n} SPLITPACKETGS;\n#pragma pack(pop)\n\ntypedef struct\n{\n\tnet_loopback_t\tloopbacks[NS_COUNT];\n\tpacketlag_t\tlagdata[NS_COUNT];\n\tint\t\tlosscount[NS_COUNT];\n\tfloat\t\tfakelag;\t\t\t// cached fakelag value\n\tLONGPACKET\tsplit;\n\tint\t\tsplit_flags[NET_MAX_FRAGMENTS];\n\tint\t\tsequence_number;\n\tint\t\tip_sockets[NS_COUNT];\n\tint\t\tip6_sockets[NS_COUNT];\n\tqboolean\t\tinitialized;\n\tqboolean\t\tthreads_initialized;\n\tqboolean\t\tconfigured;\n\tqboolean\t\tallow_ip;\n\tqboolean\t\tallow_ip6;\n#if XASH_WIN32\n\tWSADATA\t\twinsockdata;\n#endif\n} net_state_t;\n\nstatic net_state_t\t\tnet;\nstatic CVAR_DEFINE_AUTO( net_address, \"0\", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, \"contain local address of current client\" );\nstatic CVAR_DEFINE( net_ipname, \"ip\", \"localhost\", FCVAR_PRIVILEGED, \"network ip address\" );\nstatic CVAR_DEFINE( net_iphostport, \"ip_hostport\", \"0\", FCVAR_READ_ONLY, \"network ip host port\" );\nstatic CVAR_DEFINE( net_hostport, \"hostport\", \"0\", FCVAR_READ_ONLY, \"network default host port\" );\nstatic CVAR_DEFINE( net_ipclientport, \"ip_clientport\", \"0\", FCVAR_READ_ONLY, \"network ip client port\" );\nstatic CVAR_DEFINE( net_clientport, \"clientport\", \"0\", FCVAR_READ_ONLY, \"network default client port\" );\nstatic CVAR_DEFINE( net_fakelag, \"fakelag\", \"0\", FCVAR_PRIVILEGED, \"lag all incoming network data (including loopback) by xxx ms.\" );\nstatic CVAR_DEFINE( net_fakeloss, \"fakeloss\", \"0\", FCVAR_PRIVILEGED, \"act like we dropped the packet this % of the time.\" );\nstatic CVAR_DEFINE_AUTO( net_resolve_debug, \"0\", FCVAR_PRIVILEGED, \"print resolve thread debug messages\" );\nCVAR_DEFINE( net_clockwindow, \"clockwindow\", \"0.5\", FCVAR_PRIVILEGED, \"timewindow to execute client moves\" );\n\nnetadr_t\t\t\tnet_local;\nstatic netadr_t\t\tnet6_local;\n\n// cvars equivalents for IPv6\nstatic CVAR_DEFINE( net_ip6name, \"ip6\", \"localhost\", FCVAR_PRIVILEGED, \"network ip6 address\" );\nstatic CVAR_DEFINE( net_ip6hostport, \"ip6_hostport\", \"0\", FCVAR_READ_ONLY, \"network ip6 host port\" );\nstatic CVAR_DEFINE( net_ip6clientport, \"ip6_clientport\", \"0\", FCVAR_READ_ONLY, \"network ip6 client port\" );\nstatic CVAR_DEFINE_AUTO( net6_address, \"0\", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, \"contain local IPv6 address of current client\" );\n\nstatic void NET_ClearLagData( qboolean bClient, qboolean bServer );\n\nstatic inline qboolean NET_IsSocketError( int retval )\n{\n#if XASH_WIN32 || XASH_DOS4GW\n\treturn retval == SOCKET_ERROR ? true : false;\n#else\n\treturn retval < 0 ? true : false;\n#endif\n}\n\nstatic inline qboolean NET_IsSocketValid( int socket )\n{\n#if XASH_WIN32 || XASH_DOS4GW\n\treturn socket != INVALID_SOCKET;\n#else\n\treturn socket >= 0;\n#endif\n}\n\nvoid NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr )\n{\n\tmemcpy( &ip6[0], adr->ip6_0, 2 );\n\tmemcpy( &ip6[2], adr->ip6_1, 14 );\n}\n\nvoid NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 )\n{\n\tmemcpy( adr->ip6_0, &ip6[0], 2 );\n\tmemcpy( adr->ip6_1, &ip6[2], 14 );\n}\n\nstatic int NET_NetadrIP6Compare( const netadr_t *a, const netadr_t *b )\n{\n\tuint8_t ip6_a[16], ip6_b[16];\n\n\tNET_NetadrToIP6Bytes( ip6_a, a );\n\tNET_NetadrToIP6Bytes( ip6_b, b );\n\n\treturn memcmp( ip6_a, ip6_b, sizeof( ip6_a ));\n}\n\n/*\n====================\nNET_NetadrToSockadr\n====================\n*/\nstatic void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s )\n{\n\tnetadrtype_t type = NET_NetadrType( a );\n\n\tmemset( s, 0, sizeof( *s ));\n\n\tif( type == NA_BROADCAST )\n\t{\n\t\ts->ss_family = AF_INET;\n\t\t((struct sockaddr_in *)s)->sin_port = a->port;\n\t\t((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST;\n\t}\n\telse if( type == NA_IP )\n\t{\n\t\ts->ss_family = AF_INET;\n\t\t((struct sockaddr_in *)s)->sin_port = a->port;\n\t\t((struct sockaddr_in *)s)->sin_addr.s_addr = a->ip4;\n\t}\n\telse if( type == NA_IP6 )\n\t{\n\t\ts->ss_family = AF_INET6;\n\t\t((struct sockaddr_in6 *)s)->sin6_port = a->port;\n\t\tNET_NetadrToIP6Bytes(((struct sockaddr_in6 *)s)->sin6_addr.s6_addr, a );\n\t}\n\telse if( type == NA_MULTICAST_IP6 )\n\t{\n\t\ts->ss_family = AF_INET6;\n\t\t((struct sockaddr_in6 *)s)->sin6_port = a->port;\n\t\tmemcpy(((struct sockaddr_in6 *)s)->sin6_addr.s6_addr, k_ipv6Bytes_LinkLocalAllNodes, sizeof( struct in6_addr ));\n\t}\n}\n\n/*\n====================\nNET_SockadrToNetAdr\n====================\n*/\nstatic void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a )\n{\n\tif( s->ss_family == AF_INET )\n\t{\n\t\tNET_NetadrSetType( a, NA_IP );\n\t\ta->ip4 = ((struct sockaddr_in *)s)->sin_addr.s_addr;\n\t\ta->port = ((struct sockaddr_in *)s)->sin_port;\n\t}\n\telse if( s->ss_family == AF_INET6 )\n\t{\n\t\tNET_NetadrSetType( a, NA_IP6 );\n\t\tNET_IP6BytesToNetadr( a, ((struct sockaddr_in6 *)s)->sin6_addr.s6_addr );\n\t\ta->port = ((struct sockaddr_in6 *)s)->sin6_port;\n\t}\n}\n\n/*\n============\nNET_GetHostByName\n============\n*/\nstatic qboolean NET_GetHostByName( const char *hostname, int family, struct sockaddr_storage *addr )\n{\n#if defined HAVE_GETADDRINFO\n\tstruct addrinfo *ai = NULL, *cur;\n\tstruct addrinfo hints;\n\tqboolean ret = false;\n\n#if XASH_NO_IPV6_RESOLVE\n\tif( family == AF_INET6 )\n\t\treturn false;\n#endif\n\n\tmemset( &hints, 0, sizeof( hints ));\n\thints.ai_family = family;\n\n\tif( !getaddrinfo( hostname, NULL, &hints, &ai ))\n\t{\n\t\tfor( cur = ai; cur; cur = cur->ai_next )\n\t\t{\n\t\t\tif( family == AF_UNSPEC || cur->ai_family == family )\n\t\t\t{\n\t\t\t\tmemcpy( addr, cur->ai_addr, cur->ai_addrlen );\n\t\t\t\tret = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( ai )\n\t\t\tfreeaddrinfo( ai );\n\t}\n\n\treturn ret;\n#else\n\tstruct hostent *h;\n\n#if XASH_NO_IPV6_RESOLVE\n\tif( family == AF_INET6 )\n\t\treturn false;\n#endif\n\n\tif(!( h = gethostbyname( hostname )))\n\t\treturn false;\n\n\t((struct sockaddr_in *)addr)->sin_family = AF_INET;\n\t((struct sockaddr_in *)addr)->sin_addr = *(struct in_addr *)h->h_addr_list[0];\n\n\treturn true;\n#endif\n}\n\n#if !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE\n#define CAN_ASYNC_NS_RESOLVE\n#endif // !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE\n\n#ifdef CAN_ASYNC_NS_RESOLVE\nstatic void NET_ResolveThread( void );\n\n#if XASH_SDL == 2\n#define mutex_create( x )    (( x ) = SDL_CreateMutex() )\n#define mutex_destroy( x )   SDL_DestroyMutex(( x ))\n#define mutex_lock( x )      SDL_LockMutex(( x ))\n#define mutex_unlock( x )    SDL_UnlockMutex(( x ))\n#define create_thread( thread, pfn ) (( thread ) = SDL_CreateThread(( pfn ), \"DNS resolver thread\", NULL ))\n#define detach_thread( x )   SDL_DetachThread(( x ))\ntypedef SDL_mutex *mutex_t;\ntypedef SDL_Thread *thread_t;\nstatic int NET_ThreadStart( void *ununsed )\n{\n\tNET_ResolveThread();\n\treturn 0;\n}\n#elif !XASH_WIN32\n#include <pthread.h>\n#define mutex_create( x )     pthread_mutex_init( &( x ), NULL )\n#define mutex_destroy( x )    pthread_mutex_destroy( &( x ))\n#define mutex_lock( x )       pthread_mutex_lock( &( x ))\n#define mutex_unlock( x )     pthread_mutex_unlock( &( x ))\n#define create_thread( thread, pfn ) !pthread_create( &( thread ), NULL, ( pfn ), NULL )\n#define detach_thread( x )    pthread_detach( x )\ntypedef pthread_mutex_t mutex_t;\ntypedef pthread_t thread_t;\nstatic void *NET_ThreadStart( void *unused )\n{\n\tNET_ResolveThread();\n\treturn NULL;\n}\n#else // WIN32\n#define mutex_create( x )   InitializeCriticalSection( &( x ))\n#define mutex_destroy( x )  DeleteCriticalSection( &( x ))\n#define mutex_lock( x )     EnterCriticalSection( &( x ))\n#define mutex_unlock( x )   LeaveCriticalSection( &( x ))\n#define create_thread( thread, pfn ) (( thread ) = CreateThread( NULL, 0, ( pfn ), NULL, 0, NULL ))\n#define detach_thread( x )   CloseHandle(( x ))\ntypedef CRITICAL_SECTION mutex_t;\ntypedef HANDLE thread_t;\nDWORD WINAPI NET_ThreadStart( LPVOID unused )\n{\n\tNET_ResolveThread();\n\tExitThread( 0 );\n\treturn 0;\n}\n#endif // !_WIN32\n\n#define RESOLVE_DBG( x ) do { if( net_resolve_debug.value ) Sys_PrintLog(( x )); } while( 0 )\n\nstatic struct nsthread_s\n{\n\tmutex_t  mutexns;\n\tmutex_t  mutexres;\n\tthread_t thread;\n\tint      result;\n\tstring   hostname;\n\tint      family;\n\tstruct sockaddr_storage addr;\n\tqboolean busy;\n} nsthread;\n\nstatic void NET_InitializeCriticalSections( void )\n{\n\tnet.threads_initialized = true;\n\n\tmutex_create( nsthread.mutexns );\n\tmutex_create( nsthread.mutexres );\n}\n\nstatic void NET_DeleteCriticalSections( void )\n{\n\tif( net.threads_initialized )\n\t{\n\t\tmutex_destroy( nsthread.mutexns );\n\t\tmutex_destroy( nsthread.mutexres );\n\n\t\tnet.threads_initialized = false;\n\t}\n\n\tmemset( &nsthread, 0, sizeof( nsthread ));\n}\n\nstatic void NET_ResolveThread( void )\n{\n\tstruct sockaddr_storage addr;\n\tqboolean res;\n\n\tRESOLVE_DBG( \"[resolve thread] starting resolve for \" );\n\tRESOLVE_DBG( nsthread.hostname );\n#ifdef HAVE_GETADDRINFO\n\tRESOLVE_DBG( \" with getaddrinfo\\n\" );\n#else\n\tRESOLVE_DBG( \" with gethostbyname\\n\" );\n#endif\n\n\tif(( res = NET_GetHostByName( nsthread.hostname, nsthread.family, &addr )))\n\t\tRESOLVE_DBG( \"[resolve thread] success\\n\" );\n\telse\n\t\tRESOLVE_DBG( \"[resolve thread] failed\\n\" );\n\tmutex_lock( nsthread.mutexres );\n\tnsthread.addr = addr;\n\tnsthread.busy = false;\n\tnsthread.result = res ? NET_EAI_OK : NET_EAI_NONAME;\n\tRESOLVE_DBG( \"[resolve thread] returning result\\n\" );\n\tmutex_unlock( nsthread.mutexres );\n\tRESOLVE_DBG( \"[resolve thread] exiting thread\\n\" );\n}\n#endif // CAN_ASYNC_NS_RESOLVE\n\n\n/*\n=============\nNET_StringToAdr\n\nlocalhost\nidnewt\nidnewt:28000\n192.246.40.70\n192.246.40.70:28000\n=============\n*/\nnet_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family )\n{\n\tint ret = 0, port;\n\tchar\t*colon;\n\tchar\tcopy[128];\n\tbyte ip6[16];\n\tstruct sockaddr_storage temp;\n\n\tif( !net.initialized )\n\t\treturn NET_EAI_NONAME;\n\n\tmemset( sadr, 0, sizeof( *sadr ));\n\n\t// try to parse it as IPv6 first\n\tif(( family == AF_UNSPEC || family == AF_INET6 ) && ParseIPv6Addr( s, ip6, &port, NULL ))\n\t{\n\t\t((struct sockaddr_in6 *)sadr)->sin6_family = AF_INET6;\n\t\t((struct sockaddr_in6 *)sadr)->sin6_port = htons((short)port);\n\t\tmemcpy(((struct sockaddr_in6 *)sadr)->sin6_addr.s6_addr, ip6, sizeof( struct in6_addr ));\n\n\t\treturn NET_EAI_OK;\n\t}\n\n\tQ_strncpy( copy, s, sizeof( copy ));\n\n\t// strip off a trailing :port if present\n\t((struct sockaddr_in *)sadr)->sin_port = 0;\n\tfor( colon = copy; *colon; colon++ )\n\t{\n\t\tif( *colon == ':' )\n\t\t{\n\t\t\t*colon = 0;\n\t\t\t((struct sockaddr_in *)sadr)->sin_port = htons((short)Q_atoi( colon + 1 ));\n\t\t}\n\t}\n\n\tif( copy[0] >= '0' && copy[0] <= '9' )\n\t{\n\t\t((struct sockaddr_in *)sadr)->sin_family = AF_INET;\n\t\t((struct sockaddr_in *)sadr)->sin_addr.s_addr = inet_addr( copy );\n\t}\n\telse\n\t{\n\t\tqboolean asyncfailed = true;\n\n#ifdef CAN_ASYNC_NS_RESOLVE\n\t\tif( net.threads_initialized && nonblocking )\n\t\t{\n\t\t\tmutex_lock( nsthread.mutexres );\n\n\t\t\tif( nsthread.busy )\n\t\t\t{\n\t\t\t\tmutex_unlock( nsthread.mutexres );\n\t\t\t\treturn NET_EAI_AGAIN;\n\t\t\t}\n\n\t\t\tif( !Q_strcmp( copy, nsthread.hostname ))\n\t\t\t{\n\t\t\t\tret = nsthread.result;\n\n\t\t\t\tnsthread.hostname[0] = '\\0';\n\t\t\t\tnsthread.family = AF_UNSPEC;\n\t\t\t\ttemp = nsthread.addr;\n\t\t\t\tmemset( &nsthread.addr, 0, sizeof( nsthread.addr ));\n\n\t\t\t\tdetach_thread( nsthread.thread );\n\t\t\t\tasyncfailed = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tQ_strncpy( nsthread.hostname, copy, sizeof( nsthread.hostname ));\n\t\t\t\tnsthread.family = family;\n\t\t\t\tnsthread.busy = true;\n\t\t\t\tmutex_unlock( nsthread.mutexres );\n\n\t\t\t\tif( create_thread( nsthread.thread, NET_ThreadStart ))\n\t\t\t\t{\n\t\t\t\t\tasyncfailed = false;\n\t\t\t\t\treturn NET_EAI_AGAIN;\n\t\t\t\t}\n\n\t\t\t\tCon_Reportf( S_ERROR \"%s: failed to create thread!\\n\", __func__ );\n\t\t\t\tnsthread.busy = false;\n\t\t\t}\n\n\t\t\tmutex_unlock( nsthread.mutexres );\n\t\t}\n#endif // CAN_ASYNC_NS_RESOLVE\n\n\t\tif( asyncfailed )\n\t\t\tret = NET_GetHostByName( copy, family, &temp );\n\n\t\tif( !ret )\n\t\t{\n\t\t\tif( family == AF_INET6 )\n\t\t\t\tsadr->ss_family = AF_INET6;\n\t\t\telse sadr->ss_family = AF_INET;\n\n\t\t\treturn NET_EAI_NONAME;\n\t\t}\n\n\t\tsadr->ss_family = temp.ss_family;\n\n\t\tif( temp.ss_family == AF_INET )\n\t\t\t((struct sockaddr_in *)sadr)->sin_addr = ((struct sockaddr_in*)&temp)->sin_addr;\n\t\telse if( temp.ss_family == AF_INET6 )\n\t\t\t((struct sockaddr_in6 *)sadr)->sin6_addr = ((struct sockaddr_in6*)&temp)->sin6_addr;\n\t}\n\n\treturn NET_EAI_OK;\n}\n\n/*\n====================\nNET_StringToFilterAdr\n\n====================\n*/\nqboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen )\n{\n\tchar copy[128], *temp;\n\tqboolean hasCIDR = false;\n\tbyte ip6[16];\n\tuint len;\n\n\tif( !COM_CheckStringEmpty( s ))\n\t\treturn false;\n\n\tmemset( adr, 0, sizeof( *adr ));\n\n\t// copy the string and remove CIDR prefix\n\tQ_strncpy( copy, s, sizeof( copy ));\n\ttemp = Q_strrchr( copy, '/' );\n\n\tif( temp )\n\t{\n\t\t*temp = 0;\n\t\tif( Q_isdigit( temp + 1 ))\n\t\t{\n\t\t\tlen = Q_atoi( temp + 1 );\n\t\t\thasCIDR = len != 0;\n\t\t}\n\t}\n\n\t// try to parse as IPv6 first\n\tif( ParseIPv6Addr( copy, ip6, NULL, NULL ))\n\t{\n\t\tNET_NetadrSetType( adr, NA_IP6 );\n\t\tNET_IP6BytesToNetadr( adr, ip6 );\n\n\t\tif( !hasCIDR )\n\t\t\t*prefixlen = 128;\n\t\telse\n\t\t\t*prefixlen = len;\n\t}\n\telse\n\t{\n\t\tint num = 0;\n\t\tint octet = 0;\n\n\t\t// parse as ipv4 but we don't need to allow all forms here\n\t\tfor( temp = copy; *temp; temp++ )\n\t\t{\n\t\t\tchar c = *temp;\n\n\t\t\tif( c >= '0' && c <= '9' )\n\t\t\t{\n\t\t\t\tnum *= 10;\n\t\t\t\tnum += c - '0';\n\t\t\t}\n\t\t\telse if( c == '.' )\n\t\t\t{\n\t\t\t\tif( num > 255 )\n\t\t\t\t\treturn false;\n\n\t\t\t\tadr->ip[octet++] = num;\n\t\t\t\tnum = 0;\n\n\t\t\t\tif( octet > 3 )\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif( num > 255 )\n\t\t\treturn false;\n\n\t\tadr->ip[octet++] = num;\n\n\t\tif( !hasCIDR )\n\t\t{\n\t\t\tint i;\n\n\t\t\t*prefixlen = 32;\n\n\t\t\tfor( i = 3; i >= 0; i-- )\n\t\t\t{\n\t\t\t\tif( !adr->ip[i] )\n\t\t\t\t\t*prefixlen -= 8;\n\t\t\t\telse\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tuint32_t mask;\n\n\t\t\tlen = bound( 0, len, 32 );\n\t\t\t*prefixlen = len;\n\n\t\t\t// drop unneeded bits\n\t\t\tmask = htonl( adr->ip4 ) & ( 0xFFFFFFFF << ( 32 - len ));\n\t\t\tadr->ip4 = ntohl( mask );\n\t\t}\n\n\t\tNET_NetadrSetType( adr, NA_IP );\n\t}\n\n\treturn true;\n}\n\n/*\n====================\nNET_AdrToString\n====================\n*/\nconst char *NET_AdrToString( const netadr_t a )\n{\n\tstatic char s[64];\n\tnetadrtype_t type = NET_NetadrType( &a );\n\n\tif( type == NA_LOOPBACK )\n\t\treturn \"loopback\";\n\tif( type == NA_IP6 || type == NA_MULTICAST_IP6 )\n\t{\n\t\tuint8_t ip6[16];\n\n\t\tNET_NetadrToIP6Bytes( ip6, &a );\n\t\tIPv6AddrToString( s, ip6, ntohs( a.port ), 0 );\n\n\t\treturn s;\n\t}\n\n\tQ_snprintf( s, sizeof( s ),\n\t\t\"%i.%i.%i.%i:%i\", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port ));\n\n\treturn s;\n}\n\n/*\n====================\nNET_BaseAdrToString\n====================\n*/\nconst char *NET_BaseAdrToString( const netadr_t a )\n{\n\tstatic char s[64];\n\tnetadrtype_t type = NET_NetadrType( &a );\n\n\tif( type == NA_LOOPBACK )\n\t\treturn \"loopback\";\n\tif( type == NA_IP6 || type == NA_MULTICAST_IP6 )\n\t{\n\t\tuint8_t ip6[16];\n\n\t\tNET_NetadrToIP6Bytes( ip6, &a );\n\t\tIPv6IPToString( s, ip6 );\n\n\t\treturn s;\n\t}\n\n\tQ_snprintf( s, sizeof( s ),\n\t\t\"%i.%i.%i.%i\", a.ip[0], a.ip[1], a.ip[2], a.ip[3] );\n\n\treturn s;\n}\n\n/*\n===================\nNET_CompareBaseAdr\n\nCompares without the port\n===================\n*/\nqboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b )\n{\n\tnetadrtype_t type_a = NET_NetadrType( &a );\n\tnetadrtype_t type_b = NET_NetadrType( &b );\n\n\tif( type_a != type_b )\n\t\treturn false;\n\n\tif( type_a == NA_LOOPBACK )\n\t\treturn true;\n\n\tif( type_a == NA_IP )\n\t\treturn a.ip4 == b.ip4;\n\n\tif( type_a == NA_IP6 )\n\t{\n\t\tif( !NET_NetadrIP6Compare( &a, &b ))\n\t\t    return true;\n\t}\n\n\treturn false;\n}\n\n/*\n====================\nNET_CompareAdrByMask\n\nChecks if adr is a part of subnet\n====================\n*/\nqboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen )\n{\n\tnetadrtype_t type_a = NET_NetadrType( &a );\n\tnetadrtype_t type_b = NET_NetadrType( &b );\n\n\tif( type_a != type_b || type_a == NA_LOOPBACK )\n\t\treturn false;\n\n\tif( type_a == NA_IP )\n\t{\n\t\tuint32_t ipa = htonl( a.ip4 );\n\t\tuint32_t ipb = htonl( b.ip4 );\n\n\t\tif(( ipa & (( 0xFFFFFFFFU ) << ( 32 - prefixlen ))) == ipb )\n\t\t\treturn true;\n\t}\n\telse if( type_a == NA_IP6 )\n\t{\n\t\tuint16_t a_[8], b_[8];\n\t\tsize_t check     = prefixlen / 16;\n\t\tsize_t remaining = prefixlen % 16;\n\n\t\t// convert to 16-bit pieces first\n\t\tNET_NetadrToIP6Bytes( (uint8_t*)a_, &a );\n\t\tNET_NetadrToIP6Bytes( (uint8_t*)b_, &b );\n\n\t\t// check complete hextets first, if not equal, then it's different subnets\n\t\tif( check && memcmp( a_, b_, check * sizeof( uint16_t )))\n\t\t\treturn false;\n\n\t\t// check by bits now, similar to v4 check but with 16-bit type\n\t\tif( remaining )\n\t\t{\n\t\t\tuint16_t hexa, hexb, mask = 0xFFFFU << ( 16 - remaining );\n\n\t\t\thexa = htons( a_[check] );\n\t\t\thexb = htons( b_[check] );\n\n\t\t\tif(( hexa & mask ) == ( hexb & mask ))\n\t\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n====================\nNET_IsReservedAdr\n\nCheck for reserved ip's\n====================\n*/\nqboolean NET_IsReservedAdr( netadr_t a )\n{\n\tnetadrtype_t type_a = NET_NetadrType( &a );\n\n\tif( type_a == NA_LOOPBACK )\n\t\treturn true;\n\n\t// Following checks was imported from GameNetworkingSockets library\n\tif( type_a == NA_IP )\n\t{\n\t\tif(( a.ip[0] == 10 ) || // 10.x.x.x is reserved\n\t\t\t( a.ip[0] == 127 ) || // 127.x.x.x\n\t\t\t( a.ip[0] == 169 && a.ip[1] == 254 ) || // 169.254.x.x is link-local ipv4\n\t\t\t( a.ip[0] == 172 && a.ip[1] >= 16 && a.ip[1] <= 31 ) || // 172.16.x.x  - 172.31.x.x\n\t\t\t( a.ip[0] == 192 && a.ip[1] >= 168 )) // 192.168.x.x\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif( type_a == NA_IP6 )\n\t{\n\t\tuint8_t ip6[16];\n\n\t\tNET_NetadrToIP6Bytes( ip6, &a );\n\n\t\t// Private addresses, fc00::/7\n\t\t// Range is fc00:: to fdff:ffff:etc\n\t\tif( ip6[0] >= 0xFC && ip6[1] <= 0xFD )\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Link-local fe80::/10\n\t\t// Range is fe80:: to febf::\n\t\tif( ip6[0] == 0xFE && ( ip6[1] >= 0x80 && ip6[1] <= 0xBF ))\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n====================\nNET_CompareAdr\n\nCompare full address\n====================\n*/\nqboolean NET_CompareAdr( const netadr_t a, const netadr_t b )\n{\n\tnetadrtype_t type_a = NET_NetadrType( &a );\n\tnetadrtype_t type_b = NET_NetadrType( &b );\n\n\tif( type_a != type_b )\n\t\treturn false;\n\n\tif( type_a == NA_LOOPBACK )\n\t\treturn true;\n\n\tif( type_a == NA_IP )\n\t{\n\t\tif( a.ip4 == b.ip4 && a.port == b.port )\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\tif( type_a == NA_IP6 )\n\t{\n\t\tif( a.port == b.port && !NET_NetadrIP6Compare( &a, &b ))\n\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\tCon_DPrintf( S_ERROR \"%s: bad address type\\n\", __func__ );\n\treturn false;\n}\n\n/*\n====================\nNET_CompareAdrSort\n\nNetwork address sorting comparator\nguaranteed to return -1, 0 or 1\n====================\n*/\nint NET_CompareAdrSort( const void *_a, const void *_b )\n{\n\tconst netadr_t *a = _a, *b = _b;\n\tint porta, portb, portdiff, addrdiff;\n\tnetadrtype_t type_a, type_b;\n\n\ttype_a = NET_NetadrType( a );\n\ttype_b = NET_NetadrType( b );\n\n\tif( type_a != type_b )\n\t\treturn bound( -1, (int)type_a - (int)type_b, 1 );\n\n\tporta = ntohs( a->port );\n\tportb = ntohs( b->port );\n\tif( porta < portb )\n\t\tportdiff = -1;\n\telse if( porta > portb )\n\t\tportdiff = 1;\n\telse\n\t\tportdiff = 0;\n\n\tswitch( type_a )\n\t{\n\tcase NA_IP6:\n\t\tif(( addrdiff = NET_NetadrIP6Compare( a, b )))\n\t\t\treturn addrdiff;\n\t\t// fallthrough\n\tcase NA_MULTICAST_IP6:\n\t\treturn portdiff;\n\n\tcase NA_IP:\n\t\tif(( addrdiff = memcmp( a->ip, b->ip, sizeof( a->ipx ))))\n\t\t\treturn addrdiff;\n\t\t// fallthrough\n\tcase NA_BROADCAST:\n\t\treturn portdiff;\n\n\tcase NA_IPX:\n\t\tif(( addrdiff = memcmp( a->ipx, b->ipx, sizeof( a->ipx ))))\n\t\t\treturn addrdiff;\n\t\t// fallthrough\n\tcase NA_BROADCAST_IPX:\n\t\treturn portdiff;\n\t}\n\n\treturn 0;\n}\n\n/*\n=============\nNET_StringToAdr\n\nidnewt\n192.246.40.70\n=============\n*/\nstatic qboolean NET_StringToAdrEx( const char *string, netadr_t *adr, int family )\n{\n\tstruct sockaddr_storage s;\n\n\tmemset( adr, 0, sizeof( netadr_t ));\n\n\tif( !Q_stricmp( string, \"localhost\" ) || !Q_stricmp( string, \"loopback\" ))\n\t{\n\t\tNET_NetadrSetType( adr, NA_LOOPBACK );\n\t\treturn true;\n\t}\n\n\tif( NET_StringToSockaddr( string, &s, false, family ) != NET_EAI_OK )\n\t\treturn false;\n\tNET_SockadrToNetadr( &s, adr );\n\treturn true;\n}\n\n\nqboolean NET_StringToAdr( const char *string, netadr_t *adr )\n{\n\treturn NET_StringToAdrEx( string, adr, AF_UNSPEC );\n}\n\nnet_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr, qboolean v6only )\n{\n\tstruct sockaddr_storage s;\n\tnet_gai_state_t res;\n\n\tmemset( adr, 0, sizeof( netadr_t ));\n\n\tif( !Q_stricmp( string, \"localhost\" ) || !Q_stricmp( string, \"loopback\" ))\n\t{\n\t\tNET_NetadrSetType( adr, NA_LOOPBACK );\n\t\treturn NET_EAI_OK;\n\t}\n\n\tres = NET_StringToSockaddr( string, &s, true, v6only ? AF_INET6 : AF_UNSPEC );\n\n\tif( res == NET_EAI_OK )\n\t\tNET_SockadrToNetadr( &s, adr );\n\n\treturn res;\n}\n\n/*\n=============================================================================\n\nLOOPBACK BUFFERS FOR LOCAL PLAYER\n\n=============================================================================\n*/\n/*\n====================\nNET_GetLoopPacket\n====================\n*/\nstatic qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )\n{\n\tnet_loopback_t\t*loop;\n\tint\t\ti;\n\n\tif( !data || !length )\n\t\treturn false;\n\n\tloop = &net.loopbacks[sock];\n\n\tif( loop->send - loop->get > MAX_LOOPBACK )\n\t\tloop->get = loop->send - MAX_LOOPBACK;\n\n\tif( loop->get >= loop->send )\n\t\treturn false;\n\ti = loop->get & MASK_LOOPBACK;\n\tloop->get++;\n\n\tmemcpy( data, loop->msgs[i].data, loop->msgs[i].datalen );\n\t*length = loop->msgs[i].datalen;\n\n\tmemset( from, 0, sizeof( *from ));\n\tNET_NetadrSetType( from, NA_LOOPBACK );\n\n\treturn true;\n}\n\n/*\n====================\nNET_SendLoopPacket\n====================\n*/\nstatic void NET_SendLoopPacket( netsrc_t sock, size_t length, const void *data, netadr_t to )\n{\n\tnet_loopback_t\t*loop;\n\tint\t\ti;\n\n\tloop = &net.loopbacks[sock^1];\n\n\ti = loop->send & MASK_LOOPBACK;\n\tloop->send++;\n\n\tmemcpy( loop->msgs[i].data, data, length );\n\tloop->msgs[i].datalen = length;\n}\n\n/*\n====================\nNET_ClearLoopback\n====================\n*/\nstatic void NET_ClearLoopback( void )\n{\n\tnet.loopbacks[0].send = net.loopbacks[0].get = 0;\n\tnet.loopbacks[1].send = net.loopbacks[1].get = 0;\n}\n\n/*\n=============================================================================\n\nLAG & LOSS SIMULATION SYSTEM (network debugging)\n\n=============================================================================\n*/\n/*\n==================\nNET_RemoveFromPacketList\n\ndouble linked list remove entry\n==================\n*/\nstatic void NET_RemoveFromPacketList( packetlag_t *p )\n{\n\tp->prev->next = p->next;\n\tp->next->prev = p->prev;\n\tp->prev = NULL;\n\tp->next = NULL;\n}\n\n/*\n==================\nNET_ClearLaggedList\n\ndouble linked list remove queue\n==================\n*/\nstatic void NET_ClearLaggedList( packetlag_t *list )\n{\n\tpacketlag_t\t*p, *n;\n\n\tp = list->next;\n\twhile( p && p != list )\n\t{\n\t\tn = p->next;\n\n\t\tNET_RemoveFromPacketList( p );\n\n\t\tif( p->data )\n\t\t{\n\t\t\tMem_Free( p->data );\n\t\t\tp->data = NULL;\n\t\t}\n\n\t\tMem_Free( p );\n\t\tp = n;\n\t}\n\n\tlist->prev = list;\n\tlist->next = list;\n}\n\n/*\n==================\nNET_AddToLagged\n\nadd lagged packet to stream\n==================\n*/\nstatic void NET_AddToLagged( netsrc_t sock, packetlag_t *list, packetlag_t *packet, netadr_t *from, size_t length, const void *data, float timestamp )\n{\n\tbyte\t*pStart;\n\n\tif( packet->prev || packet->next )\n\t\treturn;\n\n\tpacket->prev = list->prev;\n\tlist->prev->next = packet;\n\tlist->prev = packet;\n\tpacket->next = list;\n\n\tpStart = (byte *)Z_Malloc( length );\n\tmemcpy( pStart, data, length );\n\tpacket->data = pStart;\n\tpacket->size = length;\n\tpacket->receivedtime = timestamp;\n\tpacket->from = *from;\n}\n\n/*\n==================\nNET_AdjustLag\n\nadjust time to next fake lag\n==================\n*/\nstatic void NET_AdjustLag( void )\n{\n\tstatic double\tlasttime = 0.0;\n\tfloat\t\tdiff, converge;\n\tdouble\t\tdt;\n\n\tdt = host.realtime - lasttime;\n\tdt = bound( 0.0, dt, 0.1 );\n\tlasttime = host.realtime;\n\n\tif( host_developer.value || !net_fakelag.value )\n\t{\n\t\tif( net_fakelag.value != net.fakelag )\n\t\t{\n\t\t\tdiff = net_fakelag.value - net.fakelag;\n\t\t\tconverge = dt * 200.0f;\n\t\t\tif( fabs( diff ) < converge )\n\t\t\t\tconverge = fabs( diff );\n\t\t\tif( diff < 0.0f )\n\t\t\t\tconverge = -converge;\n\t\t\tnet.fakelag += converge;\n\t\t}\n\t}\n\telse\n\t{\n\t\tCon_Printf( \"Server must enable dev-mode to activate fakelag\\n\" );\n\t\tCvar_SetValue( \"fakelag\", 0.0 );\n\t\tnet.fakelag = 0.0f;\n\t}\n}\n\n/*\n==================\nNET_LagPacket\n\nadd fake lagged packet into rececived message\n==================\n*/\nstatic qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, size_t *length, void *data )\n{\n\tpacketlag_t\t*pNewPacketLag;\n\tpacketlag_t\t*pPacket;\n\tint\t\tninterval;\n\tfloat\t\tcurtime;\n\n\tif( net.fakelag <= 0.0f )\n\t{\n\t\tNET_ClearLagData( true, true );\n\t\treturn newdata;\n\t}\n\n\tcurtime = host.realtime;\n\n\tif( newdata )\n\t{\n\t\tif( net_fakeloss.value != 0.0f )\n\t\t{\n\t\t\tif( host_developer.value )\n\t\t\t{\n\t\t\t\tnet.losscount[sock]++;\n\t\t\t\tif( net_fakeloss.value <= 0.0f )\n\t\t\t\t{\n\t\t\t\t\tninterval = fabs( net_fakeloss.value );\n\t\t\t\t\tif( ninterval < 2 ) ninterval = 2;\n\n\t\t\t\t\tif(( net.losscount[sock] % ninterval ) == 0 )\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( COM_RandomLong( 0, 100 ) <= net_fakeloss.value )\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tCvar_SetValue( \"fakeloss\", 0.0 );\n\t\t\t}\n\t\t}\n\n\t\tpNewPacketLag = (packetlag_t *)Z_Malloc( sizeof( packetlag_t ));\n\t\t// queue packet to simulate fake lag\n\t\tNET_AddToLagged( sock, &net.lagdata[sock], pNewPacketLag, from, *length, data, curtime );\n\t}\n\n\tpPacket = net.lagdata[sock].next;\n\n\twhile( pPacket != &net.lagdata[sock] )\n\t{\n\t\tif( pPacket->receivedtime <= curtime - ( net.fakelag / 1000.0f ))\n\t\t\tbreak;\n\n\t\tpPacket = pPacket->next;\n\t}\n\n\tif( pPacket == &net.lagdata[sock] )\n\t\treturn false;\n\n\tNET_RemoveFromPacketList( pPacket );\n\n\t// delivery packet from fake lag queue\n\tmemcpy( data, pPacket->data, pPacket->size );\n\tnet_from = pPacket->from;\n\t*length = pPacket->size;\n\n\tif( pPacket->data )\n\t\tMem_Free( pPacket->data );\n\n\tMem_Free( pPacket );\n\n\treturn true;\n}\n\n/*\n==================\nNET_GetLong\n\nreceive long packet from network\n==================\n*/\nstatic qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize, connprotocol_t proto )\n{\n\tint\t\ti, sequence_number, offset;\n\tint\t\tpacket_number;\n\tint\t\tpacket_count;\n\tshort\t\tpacket_id;\n\tsize_t header_size = proto == PROTO_GOLDSRC ? sizeof( SPLITPACKETGS ) : sizeof( SPLITPACKET );\n\tint body_size = splitsize - header_size;\n\tint max_splits;\n\n\tif( body_size < 0 )\n\t\treturn false;\n\n\tif( size < header_size )\n\t{\n\t\tCon_Printf( S_ERROR \"invalid split packet length %i\\n\", size );\n\t\treturn false;\n\t}\n\n\tif( proto == PROTO_GOLDSRC )\n\t{\n\t\tSPLITPACKETGS *pHeader = (SPLITPACKETGS *)pData;\n\n\t\tsequence_number = pHeader->sequence_number;\n\t\tpacket_id = pHeader->packet_id;\n\t\tpacket_count = ( packet_id & 0xF );\n\t\tpacket_number = ( packet_id >> 4 );\n\n\t\tmax_splits = NET_MAX_GOLDSRC_FRAGMENTS;\n\t}\n\telse\n\t{\n\t\tSPLITPACKET *pHeader = (SPLITPACKET *)pData;\n\n\t\tsequence_number = pHeader->sequence_number;\n\t\tpacket_id = pHeader->packet_id;\n\t\tpacket_count = ( packet_id & 0xFF );\n\t\tpacket_number = ( packet_id >> 8 );\n\n\t\tmax_splits = ARRAYSIZE( net.split_flags );\n\t}\n\n\tif( packet_number >= max_splits || packet_count > max_splits )\n\t{\n\t\tCon_Printf( S_ERROR \"malformed packet number (%i/%i)\\n\", packet_number + 1, packet_count );\n\t\treturn false;\n\t}\n\n\tif( net.split.current_sequence == -1 || sequence_number != net.split.current_sequence )\n\t{\n\t\tnet.split.current_sequence = sequence_number;\n\t\tnet.split.split_count = packet_count;\n\t\tnet.split.total_size = 0;\n\n\t\t// clear part's sequence\n\t\tfor( i = 0; i < ARRAYSIZE( net.split_flags ); i++ )\n\t\t\tnet.split_flags[i] = -1;\n\n\t\tif( net_showpackets.value == 4.0f )\n\t\t\tCon_Printf( \"<-- Split packet restart %i count %i seq\\n\", net.split.split_count, sequence_number );\n\t}\n\n\tsize -= header_size;\n\n\tif( net.split_flags[packet_number] != sequence_number )\n\t{\n\t\tif( packet_number == ( packet_count - 1 ))\n\t\t\tnet.split.total_size = size + body_size * ( packet_count - 1 );\n\n\t\tnet.split.split_count--;\n\t\tnet.split_flags[packet_number] = sequence_number;\n\n\t\tif( net_showpackets.value == 4.0f )\n\t\t\tCon_Printf( \"<-- Split packet %i of %i, %i bytes %i seq\\n\", packet_number + 1, packet_count, size, sequence_number );\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( \"%s: Ignoring duplicated split packet %i of %i ( %i bytes )\\n\", __func__, packet_number + 1, packet_count, size );\n\t}\n\n\toffset = (packet_number * body_size);\n\tmemcpy( net.split.buffer + offset, pData + header_size, size );\n\n\t// have we received all of the pieces to the packet?\n\tif( net.split.split_count <= 0 )\n\t{\n\t\tnet.split.current_sequence = -1; // Clear packet\n\n\t\tif( net.split.total_size > sizeof( net.split.buffer ))\n\t\t{\n\t\t\tCon_Printf( \"Split packet too large! %d bytes\\n\", net.split.total_size );\n\t\t\treturn false;\n\t\t}\n\n\t\tmemcpy( pData, net.split.buffer, net.split.total_size );\n\t\t*outSize = net.split.total_size;\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n==================\nNET_QueuePacket\n\nqueue normal and lagged packets\n==================\n*/\nstatic qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )\n{\n\tbyte\t\tbuf[NET_MAX_FRAGMENT];\n\tint\t\tret, protocol;\n\tint\t\tnet_socket;\n\tWSAsize_t\taddr_len;\n\tstruct sockaddr_storage\taddr = { 0 };\n\n\t*length = 0;\n\n\tfor( protocol = 0; protocol < 2; protocol++ )\n\t{\n\t\tswitch( protocol )\n\t\t{\n\t\tcase 0: net_socket = net.ip_sockets[sock]; break;\n\t\tcase 1: net_socket = net.ip6_sockets[sock]; break;\n\t\t}\n\n\t\tif( !NET_IsSocketValid( net_socket ))\n\t\t\tcontinue;\n\n\t\taddr_len = sizeof( addr );\n\t\tret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len );\n\n\t\tNET_SockadrToNetadr( &addr, from );\n\n\t\tif( !NET_IsSocketError( ret ))\n\t\t{\n\t\t\tif( ret < NET_MAX_FRAGMENT )\n\t\t\t{\n\t\t\t\t// Transfer data\n\t\t\t\tmemcpy( data, buf, ret );\n\t\t\t\t*length = ret;\n#if !XASH_DEDICATED\n\t\t\t\t{\n\t\t\t\t\tconnprotocol_t proto = CL_Protocol();\n\n\t\t\t\t\tif( proto == PROTO_LEGACY )\n\t\t\t\t\t\treturn NET_LagPacket( true, sock, from, length, data );\n\n\t\t\t\t\t// check for split message\n\t\t\t\t\tif( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET )\n\t\t\t\t\t\treturn NET_GetLong( data, ret, length, CL_GetSplitSize( ), proto );\n\t\t\t\t}\n#endif\n\t\t\t\t// lag the packet, if needed\n\t\t\t\treturn NET_LagPacket( true, sock, from, length, data );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tCon_Reportf( \"%s: oversize packet from %s\\n\", __func__, NET_AdrToString( *from ));\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint\terr = WSAGetLastError();\n\n\t\t\tswitch( err )\n\t\t\t{\n\t\t\tcase WSAEWOULDBLOCK:\n\t\t\tcase WSAECONNRESET:\n\t\t\tcase WSAECONNREFUSED:\n\t\t\tcase WSAEMSGSIZE:\n\t\t\tcase WSAETIMEDOUT:\n\t\t\t\tbreak;\n\t\t\tdefault:\t// let's continue even after errors\n\t\t\t\tCon_DPrintf( S_ERROR \"%s: %s from %s\\n\", __func__, NET_ErrorString(), NET_AdrToString( *from ));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NET_LagPacket( false, sock, from, length, data );\n}\n\n/*\n==================\nNET_GetPacket\n\nNever called by the game logic, just the system event queing\n==================\n*/\nqboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length )\n{\n\tif( !data || !length )\n\t\treturn false;\n\n\tNET_AdjustLag();\n\n\tif( NET_GetLoopPacket( sock, from, data, length ))\n\t{\n\t\treturn NET_LagPacket( true, sock, from, length, data );\n\t}\n\telse\n\t{\n\t\treturn NET_QueuePacket( sock, from, data, length );\n\t}\n}\n\n/*\n==================\nNET_SendLong\n\nFragment long packets, send short directly\n==================\n*/\nstatic int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, size_t len, int flags, const struct sockaddr_storage *to, size_t tolen, size_t splitsize )\n{\n#ifdef NET_USE_FRAGMENTS\n\t// do we need to break this packet up?\n\tif( splitsize > sizeof( SPLITPACKET ) && sock == NS_SERVER && len > splitsize )\n\t{\n\t\tchar\t\tpacket[SPLITPACKET_MAX_SIZE];\n\t\tint\t\ttotal_sent, size, packet_count;\n\t\tint\t\tret, packet_number;\n\t\tint body_size = splitsize - sizeof( SPLITPACKET );\n\t\tSPLITPACKET\t*pPacket;\n\n\t\tnet.sequence_number++;\n\t\tif( net.sequence_number <= 0 )\n\t\t\tnet.sequence_number = 1;\n\n\t\tpPacket = (SPLITPACKET *)packet;\n\t\tpPacket->sequence_number = net.sequence_number;\n\t\tpPacket->net_id = NET_HEADER_SPLITPACKET;\n\t\tpacket_number = 0;\n\t\ttotal_sent = 0;\n\t\tpacket_count = (len + body_size - 1) / body_size;\n\n\t\twhile( len > 0 )\n\t\t{\n\t\t\tsize = Q_min( body_size, len );\n\t\t\tpPacket->packet_id = (packet_number << 8) + packet_count;\n\t\t\tmemcpy( packet + sizeof( SPLITPACKET ), buf + ( packet_number * body_size ), size );\n\n\t\t\tif( net_showpackets.value == 3.0f )\n\t\t\t{\n\t\t\t\tnetadr_t\tadr;\n\n\t\t\t\tmemset( &adr, 0, sizeof( adr ));\n\t\t\t\tNET_SockadrToNetadr( to, &adr );\n\n\t\t\t\tCon_Printf( \"Sending split %i of %i with %i bytes and seq %i to %s\\n\",\n\t\t\t\t\tpacket_number + 1, packet_count, size, net.sequence_number, NET_AdrToString( adr ));\n\t\t\t}\n\n\t\t\tret = sendto( net_socket, packet, size + sizeof( SPLITPACKET ), flags, (const struct sockaddr *)to, tolen );\n\t\t\tif( ret < 0 ) return ret; // error\n\n\t\t\tif( ret >= size )\n\t\t\t\ttotal_sent += size;\n\t\t\tlen -= size;\n\t\t\tpacket_number++;\n\t\t\tPlatform_NanoSleep( 100 * 1000 );\n\t\t}\n\n\t\treturn total_sent;\n\t}\n\telse\n#endif\n\t{\n\t\t// no fragmenantion for client connection\n\t\treturn sendto( net_socket, buf, len, flags, (const struct sockaddr *)to, tolen );\n\t}\n}\n\n/*\n==================\nNET_SendPacketEx\n==================\n*/\nvoid NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize )\n{\n\tint\t\tret;\n\tstruct sockaddr_storage\taddr = { 0 };\n\tSOCKET\t\tnet_socket = 0;\n\tnetadrtype_t type = NET_NetadrType( &to );\n\n\tif( !net.initialized || type == NA_LOOPBACK )\n\t{\n\t\tNET_SendLoopPacket( sock, length, data, to );\n\t\treturn;\n\t}\n\telse if( type == NA_BROADCAST || type == NA_IP )\n\t{\n\t\tnet_socket = net.ip_sockets[sock];\n\t\tif( !NET_IsSocketValid( net_socket ))\n\t\t\treturn;\n\t}\n\telse if( type == NA_MULTICAST_IP6 || type == NA_IP6 )\n\t{\n\t\tnet_socket = net.ip6_sockets[sock];\n\t\tif( !NET_IsSocketValid( net_socket ))\n\t\t\treturn;\n\t}\n\telse\n\t{\n\t\tHost_Error( \"%s: bad address type %i (%i, %i)\\n\", __func__, to.type, to.ip6_0[0], to.ip6_0[1] );\n\t}\n\n\tNET_NetadrToSockadr( &to, &addr );\n\n\tret = NET_SendLong( sock, net_socket, data, length, 0, &addr, NET_SockAddrLen( &addr ), splitsize );\n\n\tif( NET_IsSocketError( ret ))\n\t{\n\t\tint err = WSAGetLastError();\n\n\t\t// WSAEWOULDBLOCK is silent\n\t\tif( err == WSAEWOULDBLOCK )\n\t\t\treturn;\n\n\t\t// some PPP links don't allow broadcasts\n\t\tif( err == WSAEADDRNOTAVAIL && ( type == NA_BROADCAST || type == NA_MULTICAST_IP6 ))\n\t\t\treturn;\n\n\t\tif( Host_IsDedicated( ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: %s to %s\\n\", __func__, NET_ErrorString(), NET_AdrToString( to ));\n\t\t}\n\t\telse if( err == WSAEADDRNOTAVAIL || err == WSAENOBUFS )\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"%s: %s to %s\\n\", __func__, NET_ErrorString(), NET_AdrToString( to ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: %s to %s\\n\", __func__, NET_ErrorString(), NET_AdrToString( to ));\n\t\t}\n\t}\n\n}\n\n/*\n==================\nNET_SendPacket\n==================\n*/\nvoid NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to )\n{\n\tNET_SendPacketEx( sock, length, data, to, 0 );\n}\n\n/*\n====================\nNET_IPSocket\n====================\n*/\nstatic int NET_IPSocket( const char *net_iface, int port, int family )\n{\n\tstruct sockaddr_storage\taddr = { 0 };\n\tint\t\terr, net_socket;\n\tuint\t\toptval = 1;\n\tdword\t\t_true = 1;\n\tint pfamily = PF_INET;\n\n\tif( family == AF_INET6 )\n\t\tpfamily = PF_INET6;\n\n\tif( NET_IsSocketError(( net_socket = socket( pfamily, SOCK_DGRAM, IPPROTO_UDP ))))\n\t{\n\t\terr = WSAGetLastError();\n\t\tif( err != WSAEAFNOSUPPORT )\n\t\t\tCon_DPrintf( S_WARN \"%s: port: %d socket: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\treturn INVALID_SOCKET;\n\t}\n\n\tif( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true )))\n\t{\n\t\tstruct timeval timeout;\n\n\t\tCon_DPrintf( S_WARN \"%s: port: %d ioctl FIONBIO: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t// try timeout instead of NBIO\n\t\ttimeout.tv_sec = timeout.tv_usec = 0;\n\t\tsetsockopt( net_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));\n\t}\n\n\t// make it broadcast capable\n\tif( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ))))\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: port: %d setsockopt SO_BROADCAST: %s\\n\", __func__, port, NET_ErrorString( ));\n\t}\n\n\tif( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval ))))\n\t{\n\t\tCon_DPrintf( S_WARN \"%s: port: %d setsockopt SO_REUSEADDR: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\tclosesocket( net_socket );\n\t\treturn INVALID_SOCKET;\n\t}\n\n\taddr.ss_family = family;\n\n\tif( family == AF_INET6 )\n\t{\n\t\tif( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof( _true ))))\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"%s: port: %d setsockopt IPV6_V6ONLY: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t\tclosesocket( net_socket );\n\t\t\treturn INVALID_SOCKET;\n\t\t}\n\n\t\tif( Sys_CheckParm( \"-loopback\" ))\n\t\t{\n\t\t\tif( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&_true, sizeof( _true ))))\n\t\t\t\tCon_DPrintf( S_WARN \"%s: port %d setsockopt IPV6_MULTICAST_LOOP: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t}\n\n\t\tif( COM_CheckStringEmpty( net_iface ) && Q_stricmp( net_iface, \"localhost\" ))\n\t\t\tNET_StringToSockaddr( net_iface, &addr, false, AF_INET6 );\n\t\telse ((struct sockaddr_in6 *)&addr)->sin6_addr = in6addr_any;\n\n\t\tif( port == PORT_ANY ) ((struct sockaddr_in6 *)&addr)->sin6_port = 0;\n\t\telse ((struct sockaddr_in6 *)&addr)->sin6_port = htons((short)port);\n\n\t\tif( NET_IsSocketError( bind( net_socket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in6 ))))\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"%s: port: %d bind6: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t\tclosesocket( net_socket );\n\t\t\treturn INVALID_SOCKET;\n\t\t}\n\t}\n\telse if( family == AF_INET )\n\t{\n\t\tif( Sys_CheckParm( \"-tos\" ))\n\t\t{\n\t\t\toptval = 0x10; // IPTOS_LOWDELAY\n\t\t\tCon_Printf( \"Enabling LOWDELAY TOS option\\n\" );\n\n\t\t\tif( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_TOS, (const char *)&optval, sizeof( optval ))))\n\t\t\t{\n\t\t\t\terr = WSAGetLastError();\n\t\t\t\tif( err != WSAENOPROTOOPT )\n\t\t\t\t\tCon_Printf( S_WARN \"%s: port: %d  setsockopt IP_TOS: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t\t\tclosesocket( net_socket );\n\t\t\t\treturn INVALID_SOCKET;\n\t\t\t}\n\t\t}\n\n\t\tif( Sys_CheckParm( \"-loopback\" ))\n\t\t{\n\t\t    if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&_true, sizeof( _true ))))\n\t\t\t\tCon_DPrintf( S_WARN \"%s: port %d setsockopt IP_MULTICAST_LOOP: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t}\n\n\t\tif( COM_CheckStringEmpty( net_iface ) && Q_stricmp( net_iface, \"localhost\" ))\n\t\t\tNET_StringToSockaddr( net_iface, &addr, false, AF_INET );\n\t\telse ((struct sockaddr_in *)&addr)->sin_addr.s_addr = INADDR_ANY;\n\n\t\tif( port == PORT_ANY ) ((struct sockaddr_in *)&addr)->sin_port = 0;\n\t\telse ((struct sockaddr_in *)&addr)->sin_port = htons((short)port);\n\n\t\tif( NET_IsSocketError( bind( net_socket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in ))))\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"%s: port: %d bind: %s\\n\", __func__, port, NET_ErrorString( ));\n\t\t\tclosesocket( net_socket );\n\t\t\treturn INVALID_SOCKET;\n\t\t}\n\t}\n\n\treturn net_socket;\n}\n\n/*\n====================\nNET_OpenIP\n====================\n*/\nstatic void NET_OpenIP( qboolean change_port, int *sockets, const char *net_iface, int hostport, int clientport, int family )\n{\n\tint port;\n\tqboolean sv_nat = Cvar_VariableInteger( \"sv_nat\" );\n\tqboolean cl_nat = Cvar_VariableInteger( \"cl_nat\" );\n\n\tif( change_port && ( FBitSet( net_hostport.flags, FCVAR_CHANGED ) || sv_nat ))\n\t{\n\t\t// reopen socket to set random port\n\t\tif( NET_IsSocketValid( sockets[NS_SERVER] ))\n\t\t\tclosesocket( sockets[NS_SERVER] );\n\n\t\tsockets[NS_SERVER] = INVALID_SOCKET;\n\t\tClearBits( net_hostport.flags, FCVAR_CHANGED );\n\t}\n\n\tif( !NET_IsSocketValid( sockets[NS_SERVER] ))\n\t{\n\t\tport = hostport;\n\t\tif( !port )\n\t\t{\n\t\t\tport = sv_nat ? PORT_ANY : net_hostport.value;\n\n\t\t\tif( !port )\n\t\t\t\tport = PORT_SERVER; // forcing to default\n\t\t}\n\t\tsockets[NS_SERVER] = NET_IPSocket( net_iface, port, family );\n\n\t\tif( !NET_IsSocketValid( sockets[NS_SERVER] ) && Host_IsDedicated( ))\n\t\t\treturn;\n\t}\n\n\t// dedicated servers don't need client ports\n\tif( Host_IsDedicated( )) return;\n\n\tif( change_port && ( FBitSet( net_clientport.flags, FCVAR_CHANGED ) || cl_nat ))\n\t{\n\t\t// reopen socket to set random port\n\t\tif( NET_IsSocketValid( sockets[NS_CLIENT] ))\n\t\t\tclosesocket( sockets[NS_CLIENT] );\n\n\t\tsockets[NS_CLIENT] = INVALID_SOCKET;\n\t\tClearBits( net_clientport.flags, FCVAR_CHANGED );\n\t}\n\n\tif( !NET_IsSocketValid( sockets[NS_CLIENT] ))\n\t{\n\t\tport = clientport;\n\t\tif( !port )\n\t\t{\n\t\t\tport = cl_nat ? PORT_ANY : net_clientport.value;\n\n\t\t\tif( !port )\n\t\t\t\tport = PORT_ANY; // forcing to default\n\t\t}\n\t\tsockets[NS_CLIENT] = NET_IPSocket( net_iface, port, family );\n\n\t\tif( !NET_IsSocketValid( sockets[NS_CLIENT] ))\n\t\t\tsockets[NS_CLIENT] = NET_IPSocket( net_ipname.string, PORT_ANY, family );\n\t}\n\n\treturn;\n}\n\n/*\n================\nNET_DetermineLocalAddress\n\nReturns the servers' ip address as a string.\n================\n*/\nstatic void NET_DetermineLocalAddress( void )\n{\n\tchar\t\thostname[512];\n\tchar\t\tbuff[512];\n\tstruct sockaddr_storage\taddress;\n\tWSAsize_t\t\tnamelen;\n\tconst char\t\t*net_addr_string;\n\n\tmemset( &net_local, 0, sizeof( netadr_t ));\n\tmemset( &net6_local, 0, sizeof( netadr_t ));\n\n\tif( !net.allow_ip && !net.allow_ip6 )\n\t{\n\t\tCon_Printf( \"TCP/IP Disabled.\\n\" );\n\t\treturn;\n\t}\n\n\tgethostname( hostname, sizeof( hostname ));\n\thostname[sizeof(hostname) - 1] = 0;\n\n\tif( net.allow_ip )\n\t{\n\t\t// If we have changed the ip var from the command line, use that instead.\n\t\tif( Q_stricmp( net_ipname.string, \"localhost\" ))\n\t\t\tQ_strncpy( buff, net_ipname.string, sizeof( buff ));\n\t\telse Q_strncpy( buff, hostname, sizeof( buff ));\n\n\t\tif( NET_StringToAdrEx( buff, &net_local, AF_INET ))\n\t\t{\n\t\t\tnamelen = sizeof( struct sockaddr_in );\n\n\t\t\tif( !NET_IsSocketError( getsockname( net.ip_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen )))\n\t\t\t{\n\t\t\t\tnet_local.port = ((struct sockaddr_in *)&address)->sin_port;\n\t\t\t\tnet_addr_string = NET_AdrToString( net_local );\n\t\t\t\tCon_Printf( \"Server IPv4 address %s\\n\", net_addr_string );\n\t\t\t\tCvar_FullSet( \"net_address\", net_addr_string, net_address.flags );\n\t\t\t}\n\t\t\telse Con_DPrintf( S_ERROR \"Could not get TCP/IPv4 address. Reason: %s\\n\", NET_ErrorString( ));\n\t\t}\n\t\telse Con_DPrintf( S_ERROR \"Could not get TCP/IPv4 address, Invalid hostname: '%s'\\n\", buff );\n\t}\n\n\tif( net.allow_ip6 )\n\t{\n\t\t// If we have changed the ip var from the command line, use that instead.\n\t\tif( Q_stricmp( net_ip6name.string, \"localhost\" ))\n\t\t\tQ_strncpy( buff, net_ip6name.string, sizeof( buff ));\n\t\telse Q_strncpy( buff, hostname, sizeof( buff ));\n\n\t\tif( NET_StringToAdrEx( buff, &net6_local, AF_INET6 ))\n\t\t{\n\t\t\tnamelen = sizeof( struct sockaddr_in6 );\n\n\t\t\tif( !NET_IsSocketError( getsockname( net.ip6_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen )))\n\t\t\t{\n\t\t\t\tnet6_local.port = ((struct sockaddr_in6 *)&address)->sin6_port;\n\t\t\t\tnet_addr_string = NET_AdrToString( net6_local );\n\t\t\t\tCon_Printf( \"Server IPv6 address %s\\n\", net_addr_string );\n\t\t\t\tCvar_FullSet( \"net6_address\", net_addr_string, net6_address.flags );\n\t\t\t}\n\t\t\telse Con_DPrintf( S_ERROR \"Could not get TCP/IPv6 address. Reason: %s\\n\", NET_ErrorString( ));\n\t\t}\n\t\telse Con_DPrintf( S_ERROR \"Could not get TCP/IPv6 address, Invalid hostname: '%s'\\n\", buff );\n\t}\n}\n\n/*\n====================\nNET_Config\n\nA single player game will only use the loopback code\n====================\n*/\nvoid NET_Config( qboolean multiplayer, qboolean changeport )\n{\n\tstatic qboolean\tbFirst = true;\n\tstatic qboolean\told_config;\n\n\tif( !net.initialized )\n\t\treturn;\n\n\tif( old_config == multiplayer )\n\t\treturn;\n\n\told_config = multiplayer;\n\n\tif( multiplayer )\n\t{\n\t\t// open sockets\n\t\tif( net.allow_ip )\n\t\t\tNET_OpenIP( changeport, net.ip_sockets, net_ipname.string, net_iphostport.value, net_ipclientport.value, AF_INET );\n\n\t\tif( net.allow_ip6 )\n\t\t\tNET_OpenIP( changeport, net.ip6_sockets, net_ip6name.string, net_ip6hostport.value, net_ip6clientport.value, AF_INET6 );\n\n\t\t// validate sockets for dedicated\n\t\tif( Host_IsDedicated( ))\n\t\t{\n\t\t\tqboolean nov4, nov6;\n\t\t\tnov4 = net.allow_ip  && NET_IsSocketError( net.ip_sockets[NS_SERVER] );\n\t\t\tnov6 = net.allow_ip6 && NET_IsSocketError( net.ip6_sockets[NS_SERVER] );\n\n\t\t\tif( nov4 && nov6 )\n\t\t\t\tHost_Error( \"Couldn't allocate IPv4 and IPv6 server ports.\\n\" );\n\t\t\telse if( nov4 && !nov6 )\n\t\t\t\tCon_Printf( S_ERROR \"Couldn't allocate IPv4 server port\\n\" );\n\t\t\telse if( !nov4 && nov6 )\n\t\t\t\tCon_Printf( S_ERROR \"Couldn't allocate IPv6 server_port\\n\" );\n\t\t}\n\n\t\t// get our local address, if possible\n\t\tif( bFirst )\n\t\t{\n\t\t\tNET_DetermineLocalAddress();\n\t\t\tbFirst = false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tint\ti;\n\n\t\t// shut down any existing sockets\n\t\tfor( i = 0; i < NS_COUNT; i++ )\n\t\t{\n\t\t\tif( NET_IsSocketValid( net.ip_sockets[i] ))\n\t\t\t{\n\t\t\t\tclosesocket( net.ip_sockets[i] );\n\t\t\t\tnet.ip_sockets[i] = INVALID_SOCKET;\n\t\t\t}\n\n\t\t\tif( NET_IsSocketValid( net.ip6_sockets[i] ))\n\t\t\t{\n\t\t\t\tclosesocket( net.ip6_sockets[i] );\n\t\t\t\tnet.ip6_sockets[i] = INVALID_SOCKET;\n\t\t\t}\n\t\t}\n\t}\n\n\tNET_ClearLoopback ();\n\n\tnet.configured = multiplayer ? true : false;\n}\n\n/*\n====================\nNET_IsConfigured\n\nIs winsock ip initialized?\n====================\n*/\nqboolean NET_IsConfigured( void )\n{\n\treturn net.configured;\n}\n\n/*\n====================\nNET_IsActive\n====================\n*/\nqboolean NET_IsActive( void )\n{\n\treturn net.initialized;\n}\n\n/*\n====================\nNET_Sleep\n\nsleeps msec or until net socket is ready\n====================\n*/\nvoid NET_Sleep( int msec )\n{\n#ifndef XASH_NO_NETWORK\n\tstruct timeval\ttimeout;\n\tfd_set\t\tfdset;\n\tint\t\ti = 0;\n\n\tif( !net.initialized || host.type == HOST_NORMAL )\n\t\treturn; // we're not a dedicated server, just run full speed\n\n\tFD_ZERO( &fdset );\n\n\tif( net.ip_sockets[NS_SERVER] != INVALID_SOCKET )\n\t{\n\t\tFD_SET( net.ip_sockets[NS_SERVER], &fdset ); // network socket\n\t\ti = net.ip_sockets[NS_SERVER];\n\t}\n\n\ttimeout.tv_sec = msec / 1000;\n\ttimeout.tv_usec = (msec % 1000) * 1000;\n\tselect( i+1, &fdset, NULL, NULL, &timeout );\n#endif\n}\n\n/*\n====================\nNET_ClearLagData\n\nclear fakelag list\n====================\n*/\nstatic void NET_ClearLagData( qboolean bClient, qboolean bServer )\n{\n\tif( bClient ) NET_ClearLaggedList( &net.lagdata[NS_CLIENT] );\n\tif( bServer ) NET_ClearLaggedList( &net.lagdata[NS_SERVER] );\n}\n\n/*\n====================\nNET_GetLocalAddress\n\nget local server addresses\n====================\n*/\nvoid NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 )\n{\n\tif( ip4 )\n\t{\n\t\tif( net.allow_ip )\n\t\t\t*ip4 = net_local;\n\t\telse\n\t\t\tmemset( ip4, 0, sizeof( *ip4 ));\n\t}\n\n\tif( ip6 )\n\t{\n\t\tif( net.allow_ip6 )\n\t\t\t*ip6 = net6_local;\n\t\telse\n\t\t\tmemset( ip6, 0, sizeof( *ip6 ));\n\t}\n}\n\n/*\n====================\nNET_Init\n====================\n*/\nvoid NET_Init( void )\n{\n\tchar\tcmd[64];\n\tint\ti = 1;\n\n\tif( net.initialized ) return;\n\n\tCvar_RegisterVariable( &net_address );\n\tCvar_RegisterVariable( &net_ipname );\n\tCvar_RegisterVariable( &net_iphostport );\n\tCvar_RegisterVariable( &net_hostport );\n\tCvar_RegisterVariable( &net_ipclientport );\n\tCvar_RegisterVariable( &net_clientport );\n\tCvar_RegisterVariable( &net_fakelag );\n\tCvar_RegisterVariable( &net_fakeloss );\n\tCvar_RegisterVariable( &net_resolve_debug );\n\tCvar_RegisterVariable( &net_clockwindow );\n\n\tQ_snprintf( cmd, sizeof( cmd ), \"%i\", PORT_SERVER );\n\tCvar_FullSet( \"hostport\", cmd, FCVAR_READ_ONLY );\n\n\t// cvar equivalents for IPv6\n\tCvar_RegisterVariable( &net_ip6name );\n\tCvar_RegisterVariable( &net_ip6hostport );\n\tCvar_RegisterVariable( &net_ip6clientport );\n\tCvar_RegisterVariable( &net6_address );\n\n\t// prepare some network data\n\tfor( i = 0; i < NS_COUNT; i++ )\n\t{\n\t\tnet.lagdata[i].prev = &net.lagdata[i];\n\t\tnet.lagdata[i].next = &net.lagdata[i];\n\t\tnet.ip_sockets[i]  = INVALID_SOCKET;\n\t\tnet.ip6_sockets[i] = INVALID_SOCKET;\n\t}\n\n#if XASH_WIN32\n\tif( WSAStartup( MAKEWORD( 2, 0 ), &net.winsockdata ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"network initialization failed.\\n\" );\n\t\treturn;\n\t}\n#endif\n\n#ifdef CAN_ASYNC_NS_RESOLVE\n\tNET_InitializeCriticalSections();\n#endif\n\n\tnet.allow_ip = !Sys_CheckParm( \"-noip\" );\n\tnet.allow_ip6 = !Sys_CheckParm( \"-noip6\" );\n\n\t// specify custom host port\n\tif( Sys_GetParmFromCmdLine( \"-port\", cmd ) && Q_isdigit( cmd ))\n\t\tCvar_FullSet( net_hostport.name, cmd, net_hostport.flags );\n\n\t// specify custom IPv6 host port\n\tif( Sys_GetParmFromCmdLine( \"-port6\", cmd ) && Q_isdigit( cmd ))\n\t\tCvar_FullSet( net_ip6hostport.name, cmd, net_ip6hostport.flags );\n\n\t// specify custom client port\n\tif( Sys_GetParmFromCmdLine( \"-clientport\", cmd ) && Q_isdigit( cmd ))\n\t\tCvar_FullSet( net_clientport.name, cmd, net_clientport.flags );\n\n\t// specify custom IPv6 client port\n\tif( Sys_GetParmFromCmdLine( \"-clientport6\", cmd ) && Q_isdigit( cmd ))\n\t\tCvar_FullSet( net_ip6clientport.name, cmd, net_ip6clientport.flags );\n\n\t// specify custom ip\n\tif( Sys_GetParmFromCmdLine( \"-ip\", cmd ))\n\t\tCvar_DirectSet( &net_ipname, cmd );\n\n\t// specify custom ip6\n\tif( Sys_GetParmFromCmdLine( \"-ip6\", cmd ))\n\t\tCvar_DirectSet( &net_ip6name, cmd );\n\n\t// adjust clockwindow\n\tif( Sys_GetParmFromCmdLine( \"-clockwindow\", cmd ))\n\t\tCvar_DirectSetValue( &net_clockwindow, Q_atof( cmd ));\n\n\tnet.sequence_number = 1;\n\tnet.initialized = true;\n\tCon_Reportf( \"Base networking initialized.\\n\" );\n}\n\n\n/*\n====================\nNET_Shutdown\n====================\n*/\nvoid NET_Shutdown( void )\n{\n\tif( !net.initialized )\n\t\treturn;\n\n\tNET_ClearLagData( true, true );\n\n\tNET_Config( false, false );\n\n#ifdef CAN_ASYNC_NS_RESOLVE\n\tNET_DeleteCriticalSections();\n#endif\n\n#if XASH_WIN32\n\tWSACleanup();\n#endif\n\tnet.initialized = false;\n}\n\n\n"
  },
  {
    "path": "engine/common/net_ws.h",
    "content": "/*\nnet_ws.h - network shared functions\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef NET_WS_H\n#define NET_WS_H\n\ntypedef enum\n{\n\tNS_CLIENT,\n\tNS_SERVER,\n\tNS_COUNT\n} netsrc_t;\n\ntypedef enum\n{\n\tNET_EAI_NONAME = 0,\n\tNET_EAI_OK     = 1,\n\tNET_EAI_AGAIN  = 2\n} net_gai_state_t;\n\n// Max length of unreliable message\n#define MAX_DATAGRAM\t\t16384\n\n// Max length of a multicast message\n#define MAX_MULTICAST\t\t8192\t// some mods spamming for rain effect\n\n\n#if !XASH_LOW_MEMORY\n#define MAX_INIT_MSG\t\t0x30000\t// max length of possible message\n#else\n#define MAX_INIT_MSG\t\t0x8000\n#endif\n// net packets type\n#define NET_HEADER_OUTOFBANDPACKET\t-1\n#define NET_HEADER_SPLITPACKET\t-2\n#define NET_HEADER_COMPRESSEDPACKET\t-3\n\n\n#include \"netadr.h\"\n\nextern convar_t net_showpackets;\nextern convar_t net_clockwindow;\nextern convar_t net_send_debug;\nextern convar_t net_recv_debug;\n\nvoid NET_Init( void );\nvoid NET_Shutdown( void );\nvoid NET_Sleep( int msec );\nqboolean NET_IsActive( void );\nqboolean NET_IsConfigured( void );\nvoid NET_Config( qboolean net_enable, qboolean changeport );\nconst char *NET_AdrToString( const netadr_t a ) RETURNS_NONNULL;\nconst char *NET_BaseAdrToString( const netadr_t a ) RETURNS_NONNULL;\nqboolean NET_IsReservedAdr( netadr_t a );\nqboolean NET_StringToAdr( const char *string, netadr_t *adr );\nqboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen );\nnet_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr, qboolean v6only );\nint NET_CompareAdrSort( const void *_a, const void *_b );\nqboolean NET_CompareAdr( const netadr_t a, const netadr_t b );\nqboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b );\nqboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen );\nqboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length );\nvoid NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to );\nvoid NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize );\nvoid NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 );\nvoid NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr );\n\nstatic inline qboolean NET_IsLocalAddress( netadr_t adr )\n{\n\treturn NET_NetadrType( &adr ) == NA_LOOPBACK;\n}\n\nvoid NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 );\n\n#if !XASH_DEDICATED\nint CL_GetSplitSize( void );\n#endif\n\nvoid HTTP_AddCustomServer( const char *url );\nvoid HTTP_AddDownload( const char *path, int size, qboolean process, resource_t *res );\nvoid HTTP_ClearCustomServers( void );\nvoid HTTP_Shutdown( void );\nvoid HTTP_ResetProcessState( void );\nvoid HTTP_Init( void );\nvoid HTTP_Run( void );\n\n#endif//NET_WS_H\n"
  },
  {
    "path": "engine/common/net_ws_private.h",
    "content": "/*\nnet_ws_private.h - networking private definitions\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"net_ws.h\"\n#if XASH_NO_NETWORK\n#include \"platform/stub/net_stub.h\"\n#elif XASH_WIN32\n#include \"platform/win32/net.h\"\n#elif XASH_PSVITA\n#include \"platform/psvita/net_psvita.h\"\nstatic const struct in6_addr in6addr_any;\n#else\n#include \"platform/posix/net.h\"\n#endif\n\n\n//\n// net_ws.c\n//\nstatic inline const char *NET_ErrorString( void )\n{\n#if XASH_WIN32\n\tint err = WSAGetLastError();\n\tswitch( err )\n\t{\n\tcase WSAEINTR: return \"WSAEINTR\";\n\tcase WSAEBADF: return \"WSAEBADF\";\n\tcase WSAEACCES: return \"WSAEACCES\";\n\tcase WSAEFAULT: return \"WSAEFAULT\";\n\tcase WSAEINVAL: return \"WSAEINVAL\";\n\tcase WSAEMFILE: return \"WSAEMFILE\";\n\tcase WSAEWOULDBLOCK: return \"WSAEWOULDBLOCK\";\n\tcase WSAEINPROGRESS: return \"WSAEINPROGRESS\";\n\tcase WSAEALREADY: return \"WSAEALREADY\";\n\tcase WSAENOTSOCK: return \"WSAENOTSOCK\";\n\tcase WSAEDESTADDRREQ: return \"WSAEDESTADDRREQ\";\n\tcase WSAEMSGSIZE: return \"WSAEMSGSIZE\";\n\tcase WSAEPROTOTYPE: return \"WSAEPROTOTYPE\";\n\tcase WSAENOPROTOOPT: return \"WSAENOPROTOOPT\";\n\tcase WSAEPROTONOSUPPORT: return \"WSAEPROTONOSUPPORT\";\n\tcase WSAESOCKTNOSUPPORT: return \"WSAESOCKTNOSUPPORT\";\n\tcase WSAEOPNOTSUPP: return \"WSAEOPNOTSUPP\";\n\tcase WSAEPFNOSUPPORT: return \"WSAEPFNOSUPPORT\";\n\tcase WSAEAFNOSUPPORT: return \"WSAEAFNOSUPPORT\";\n\tcase WSAEADDRINUSE: return \"WSAEADDRINUSE\";\n\tcase WSAEADDRNOTAVAIL: return \"WSAEADDRNOTAVAIL\";\n\tcase WSAENETDOWN: return \"WSAENETDOWN\";\n\tcase WSAENETUNREACH: return \"WSAENETUNREACH\";\n\tcase WSAENETRESET: return \"WSAENETRESET\";\n\tcase WSAECONNABORTED: return \"WSWSAECONNABORTEDAEINTR\";\n\tcase WSAECONNRESET: return \"WSAECONNRESET\";\n\tcase WSAENOBUFS: return \"WSAENOBUFS\";\n\tcase WSAEISCONN: return \"WSAEISCONN\";\n\tcase WSAENOTCONN: return \"WSAENOTCONN\";\n\tcase WSAESHUTDOWN: return \"WSAESHUTDOWN\";\n\tcase WSAETOOMANYREFS: return \"WSAETOOMANYREFS\";\n\tcase WSAETIMEDOUT: return \"WSAETIMEDOUT\";\n\tcase WSAECONNREFUSED: return \"WSAECONNREFUSED\";\n\tcase WSAELOOP: return \"WSAELOOP\";\n\tcase WSAENAMETOOLONG: return \"WSAENAMETOOLONG\";\n\tcase WSAEHOSTDOWN: return \"WSAEHOSTDOWN\";\n\tcase WSAEDISCON: return \"WSAEDISCON\";\n\tcase WSASYSNOTREADY: return \"WSASYSNOTREADY\";\n\tcase WSAVERNOTSUPPORTED: return \"WSAVERNOTSUPPORTED\";\n\tcase WSANOTINITIALISED: return \"WSANOTINITIALISED\";\n\tcase WSAHOST_NOT_FOUND: return \"WSAHOST_NOT_FOUND\";\n\tcase WSATRY_AGAIN: return \"WSATRY_AGAIN\";\n\tcase WSANO_RECOVERY: return \"WSANO_RECOVERY\";\n\tcase WSANO_DATA: return \"WSANO_DATA\";\n\t}\n\n\treturn \"NO ERROR\";\n#else\n\treturn strerror( errno );\n#endif\n}\n\nstatic inline socklen_t NET_SockAddrLen( const struct sockaddr_storage *addr )\n{\n\tswitch ( addr->ss_family )\n\t{\n\tcase AF_INET:\n\t\treturn sizeof( struct sockaddr_in );\n\tcase AF_INET6:\n\t\treturn sizeof( struct sockaddr_in6 );\n\tdefault:\n\t\treturn sizeof( *addr ); // what the fuck is this?\n\t}\n}\n\nnet_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family );\n"
  },
  {
    "path": "engine/common/netchan.h",
    "content": "/*\nnetchan.h - net channel abstraction layer\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef NET_MSG_H\n#define NET_MSG_H\n\n/*\n==========================================================\n\n  ELEMENTS COMMUNICATED ACROSS THE NET\n\n==========================================================\n*/\n#include \"crtlib.h\"\n#include \"net_buffer.h\"\n\n// 0 == regular, 1 == file stream\n#define MAX_STREAMS\t\t\t2\n\n// flow control bytes per second limits\n#define MAX_RATE\t\t\t100000.0f\n#define MIN_RATE\t\t\t1000.0f\n\n// default data rate\n#define DEFAULT_RATE\t\t(9999.0f)\n\n// NETWORKING INFO\n\n// This is the packet payload without any header bytes (which are attached for actual sending)\n#define NET_MAX_PAYLOAD\t\tMAX_INIT_MSG\n\n// Theoretically maximum size of UDP-packet without header and hardware-specific data\n#define NET_MAX_FRAGMENT\t\t65535\n\n// because encoded as highpart of uint32\n#define NET_MAX_BUFFER_ID\t\t32767\n\n// because encoded as lowpart of uint32\n#define NET_MAX_BUFFERS_COUNT\t\t32767\n\n// This is the payload plus any header info (excluding UDP header)\n\n// Packet header is:\n//  4 bytes of outgoing seq\n//  4 bytes of incoming seq\n//  and for each stream\n// {\n//  byte (on/off)\n//  int (fragment id)\n//  int (startpos)\n//  int (length)\n// }\n#define HEADER_BYTES\t\t( 8 + MAX_STREAMS * 13 )\n\n// Pad this to next higher 16 byte boundary\n// This is the largest packet that can come in/out over the wire, before processing the header\n//  bytes will be stripped by the networking channel layer\n#define NET_MAX_MESSAGE\t\tPAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 )\n\n#define MS_SCAN_REQUEST\t\t\"1\\xFF\" \"0.0.0.0:0\\0\"\n\n#define PORT_MASTER\t\t\t27010\n#define PORT_SERVER\t\t\t27015\n\n#define MULTIPLAYER_BACKUP\t\t64\t// how many data slots to use when in multiplayer (must be power of 2)\n#define SINGLEPLAYER_BACKUP\t\t16\t// same for single player\n#define CMD_BACKUP\t\t\t64\t// allow a lot of command backups for very fast systems\n#define CMD_MASK\t\t\t(CMD_BACKUP - 1)\n#define NUM_PACKET_ENTITIES\t\t256\t// 170 Mb for multiplayer with 32 players\n#define MAX_CUSTOM_BASELINES\t\t64\n#define NET_LEGACY_EXT_SPLIT\t\t(1U<<1)\n#define NETSPLIT_BACKUP 8\n#define NETSPLIT_BACKUP_MASK (NETSPLIT_BACKUP - 1)\n#define NETSPLIT_HEADER_SIZE 18\n\n#if XASH_LOW_MEMORY == 2\n\t#undef MULTIPLAYER_BACKUP\n\t#undef SINGLEPLAYER_BACKUP\n\t#undef NUM_PACKET_ENTITIES\n\t#undef MAX_CUSTOM_BASELINES\n\t#undef NET_MAX_FRAGMENT\n\t#define MULTIPLAYER_BACKUP\t\t4\t// breaks protocol in legacy mode, new protocol status unknown\n\t#define SINGLEPLAYER_BACKUP\t\t4\n\t#define NUM_PACKET_ENTITIES\t\t32\n\t#define MAX_CUSTOM_BASELINES\t\t8\n\t#define NET_MAX_FRAGMENT\t\t32768\n#elif XASH_LOW_MEMORY == 1\n\t#undef SINGLEPLAYER_BACKUP\n\t#undef NUM_PACKET_ENTITIES\n\t#undef MAX_CUSTOM_BASELINES\n\t#undef NET_MAX_FRAGMENT\n\t#define SINGLEPLAYER_BACKUP\t\t4\n\t#define NUM_PACKET_ENTITIES\t\t64\n\t#define MAX_CUSTOM_BASELINES\t\t8\n\t#define NET_MAX_FRAGMENT\t\t32768\n#endif\n\ntypedef struct netsplit_chain_packet_s\n{\n\t// bool vector\n\tuint32_t recieved_v[8];\n\t// serial number\n\tuint32_t id;\n\tbyte data[NET_MAX_PAYLOAD];\n\tbyte received;\n\tbyte count;\n} netsplit_chain_packet_t;\n\n// raw packet format\ntypedef struct netsplit_packet_s\n{\n\tuint32_t signature; // 0xFFFFFFFE\n\tuint32_t length;\n\tuint32_t part;\n\tuint32_t id;\n\t// max 256 parts\n\tbyte count;\n\tbyte index;\n\tbyte data[NET_MAX_PAYLOAD - NETSPLIT_HEADER_SIZE];\n} netsplit_packet_t;\n\n\ntypedef struct netsplit_s\n{\n\tnetsplit_chain_packet_t packets[NETSPLIT_BACKUP];\n\tuint64_t total_received;\n\tuint64_t total_received_uncompressed;\n} netsplit_t;\n\n// packet splitting\nqboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *length );\n\n\n/*\n==============================================================\n\nNET\n\n==============================================================\n*/\n#define MAX_FLOWS\t\t\t2\n\n#define FLOW_OUTGOING\t\t0\n#define FLOW_INCOMING\t\t1\n#define MAX_LATENT\t\t\t32\n#define MASK_LATENT\t\t\t( MAX_LATENT - 1 )\n\n#define FRAG_NORMAL_STREAM\t\t0\n#define FRAG_FILE_STREAM\t\t1\n\n// message data\ntypedef struct\n{\n\tint\t\tsize;\t\t// size of message sent/received\n\tdouble\t\ttime;\t\t// time that message was sent/received\n} flowstats_t;\n\ntypedef struct\n{\n\tflowstats_t\tstats[MAX_LATENT];\t// data for last MAX_LATENT messages\n\tint\t\tcurrent;\t\t// current message position\n\tdouble\t\tnextcompute; \t// time when we should recompute k/sec data\n\tfloat\t\tkbytespersec;\t// average data\n\tfloat\t\tavgkbytespersec;\n\tint\t\ttotalbytes;\n} flow_t;\n\n// generic fragment structure\ntypedef struct fragbuf_s\n{\n\tstruct fragbuf_s\t*next;\t\t\t\t// next buffer in chain\n\tint\t\tbufferid;\t\t\t\t// id of this buffer\n\tsizebuf_t\t\tfrag_message;\t\t\t// message buffer where raw data is stored\n\tqboolean\t\tisfile;\t\t\t\t// is this a file buffer?\n\tqboolean\t\tisbuffer;\t\t\t\t// is this file buffer from memory ( custom decal, etc. ).\n\tqboolean\t\tiscompressed;\t\t\t// is compressed file, we should using filename.ztmp\n\tchar\t\tfilename[MAX_OSPATH];\t\t// name of the file to save out on remote host\n\tint\t\tfoffset;\t\t\t\t// offset in file from which to read data\n\tint\t\tsize;\t\t\t\t// size of data to read at that offset\n\tbyte frag_message_buf[]; // the actual data sits here (flexible)\n} fragbuf_t;\n\n// Waiting list of fragbuf chains\ntypedef struct fbufqueue_s\n{\n\tstruct fbufqueue_s\t*next;\t\t// next chain in waiting list\n\tint\t\tfragbufcount;\t// number of buffers in this chain\n\tfragbuf_t\t\t*fragbufs;\t// the actual buffers\n} fragbufwaiting_t;\n\ntypedef enum fragsize_e\n{\n\tFRAGSIZE_FRAG,\n\tFRAGSIZE_SPLIT,\n\tFRAGSIZE_UNRELIABLE\n} fragsize_t;\n\ntypedef enum netchan_flags_e\n{\n\tNETCHAN_USE_LEGACY_SPLIT = BIT( 0 ),\n\tNETCHAN_USE_MUNGE = BIT( 1 ),\n\tNETCHAN_USE_BZIP2 = BIT( 2 ),\n\tNETCHAN_GOLDSRC = BIT( 3 ),\n\tNETCHAN_USE_LZSS = BIT( 4 ), // mutually exclusive with bzip2\n} netchan_flags_t;\n\n// Network Connection Channel\ntypedef struct netchan_s\n{\n\tnetsrc_t\t\tsock;\t\t// NS_SERVER or NS_CLIENT, depending on channel.\n\tnetadr_t\t\tremote_address;\t// address this channel is talking to.\n\tint\t\tqport;\t\t// qport value to write when transmitting\n\n\tdouble\t\tlast_received;\t// for timeouts\n\tdouble\t\tconnect_time;\t// Usage: host.realtime - netchan.connect_time\n\tdouble\t\trate;\t\t// bandwidth choke. bytes per second\n\tdouble\t\tcleartime;\t// if realtime > cleartime, free to send next packet\n\n\t// Sequencing variables\n\tunsigned int\t\tincoming_sequence;\t\t\t// increasing count of sequence numbers\n\tunsigned int\t\tincoming_acknowledged;\t\t// # of last outgoing message that has been ack'd.\n\tunsigned int\t\tincoming_reliable_acknowledged;\t// toggles T/F as reliable messages are received.\n\tunsigned int\t\tincoming_reliable_sequence;\t\t// single bit, maintained local\n\tunsigned int\t\toutgoing_sequence;\t\t\t// message we are sending to remote\n\tunsigned int\t\treliable_sequence;\t\t\t// whether the message contains reliable payload, single bit\n\tunsigned int\t\tlast_reliable_sequence;\t\t// outgoing sequence number of last send that had reliable data\n\n\t// callback to get actual framgment size\n\tvoid\t\t*client;\n\tint (*pfnBlockSize)( void *cl, fragsize_t mode );\n\n\t// staging and holding areas\n\tsizebuf_t\t\tmessage;\n\tbyte\t\tmessage_buf[NET_MAX_MESSAGE];\n\n\t// reliable message buffer.\n\t// we keep adding to it until reliable is acknowledged.  Then we clear it.\n\tint\t\treliable_length;\n\tbyte\t\treliable_buf[NET_MAX_MESSAGE];\t// unacked reliable message (max size for loopback connection)\n\n\t// Waiting list of buffered fragments to go onto queue.\n\t// Multiple outgoing buffers can be queued in succession\n\tfragbufwaiting_t\t*waitlist[MAX_STREAMS];\n\n\tint\t\treliable_fragment[MAX_STREAMS];\t// is reliable waiting buf a fragment?\n\tuint\t\treliable_fragid[MAX_STREAMS];\t\t// buffer id for each waiting fragment\n\n\tfragbuf_t\t\t*fragbufs[MAX_STREAMS];\t// the current fragment being set\n\tint\t\tfragbufcount[MAX_STREAMS];\t// the total number of fragments in this stream\n\n\tint\t\tfrag_startpos[MAX_STREAMS];\t// position in outgoing buffer where frag data starts\n\tint\t\tfrag_length[MAX_STREAMS];\t// length of frag data in the buffer\n\n\tfragbuf_t\t\t*incomingbufs[MAX_STREAMS];\t// incoming fragments are stored here\n\tqboolean\t\tincomingready[MAX_STREAMS];\t// set to true when incoming data is ready\n\n\t// Only referenced by the FRAG_FILE_STREAM component\n\tchar\t\tincomingfilename[MAX_OSPATH];\t// Name of file being downloaded\n\n\tvoid\t\t*tempbuffer;\t\t// download file buffer\n\tint\t\ttempbuffersize;\t\t// current size\n\n\t// incoming and outgoing flow metrics\n\tflow_t\t\tflow[MAX_FLOWS];\n\n\t// added for net_speeds\n\tsize_t\t\ttotal_sended;\n\tsize_t\t\ttotal_received;\n\tunsigned int\tmaxpacket;\n\tunsigned int\tsplitid;\n\tnetsplit_t\tnetsplit;\n\n\tqboolean\tsplit;\n\tqboolean\tuse_munge;\n\tqboolean\tuse_bz2;\n\tqboolean\tuse_lzss;\n\tqboolean\tgs_netchan;\n} netchan_t;\n\nextern netadr_t\t\tnet_from;\nextern sizebuf_t\t\tnet_message;\nextern byte\t\tnet_message_buffer[NET_MAX_MESSAGE];\nextern convar_t\t\tsv_lan;\nextern convar_t\t\tsv_lan_rate;\nextern int\t\tnet_drop;\n\nvoid Netchan_Init( void );\nvoid Netchan_Shutdown( void );\nvoid Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags );\nvoid Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size );\nqboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length );\nqboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg );\nvoid Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg );\nint Netchan_CreateFileFragments( netchan_t *chan, const char *filename );\nvoid Netchan_TransmitBits( netchan_t *chan, int lengthInBits, const byte *data );\nvoid Netchan_OutOfBand( int net_socket, netadr_t adr, int length, const byte *data );\nvoid Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) FORMAT_CHECK( 3 );\nqboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg );\nvoid Netchan_UpdateProgress( netchan_t *chan );\nqboolean Netchan_IncomingReady( netchan_t *chan );\nqboolean Netchan_CanPacket( netchan_t *chan, qboolean choke );\nqboolean Netchan_IsLocal( netchan_t *chan );\nvoid Netchan_ReportFlow( netchan_t *chan );\nvoid Netchan_FragSend( netchan_t *chan );\nvoid Netchan_Clear( netchan_t *chan );\n\n#endif//NET_MSG_H\n"
  },
  {
    "path": "engine/common/pm_local.h",
    "content": "/*\npm_local.h - player move interface\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef PM_LOCAL_H\n#define PM_LOCAL_H\n\n#include \"pm_defs.h\"\n#include \"xash3d_mathlib.h\"\n\n#include <string.h> // memset\n\ntypedef int (*pfnIgnore)( physent_t *pe );\t// custom trace filter\n\n//\n// pm_trace.c\n//\nvoid Pmove_Init( void );\nvoid PM_InitBoxHull( void );\nhull_t *PM_HullForBsp( physent_t *pe, playermove_t *pmove, float *offset );\nqboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace );\npmtrace_t PM_PlayerTraceExt( playermove_t *pm, vec3_t p1, vec3_t p2, int flags, int numents, physent_t *ents, int ignore_pe, pfnIgnore pmFilter );\nint PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, pfnIgnore pmFilter );\nint PM_HullPointContents( hull_t *hull, int num, const vec3_t p );\nint PM_TruePointContents( playermove_t *pmove, const vec3_t p );\nint PM_PointContents( playermove_t *pmove, const vec3_t p );\nfloat PM_TraceModel( playermove_t *pmove, physent_t *pe, float *start, float *end, trace_t *trace );\npmtrace_t *PM_TraceLine( playermove_t *pmove, float *start, float *end, int flags, int usehull, int ignore_pe );\npmtrace_t *PM_TraceLineEx( playermove_t *pmove, float *start, float *end, int flags, int usehull, pfnIgnore pmFilter );\nstruct msurface_s *PM_TraceSurfacePmove( playermove_t *pmove, int ground, float *vstart, float *vend );\nconst char *PM_TraceTexture( playermove_t *pmove, int ground, float *vstart, float *vend );\nint PM_PointContentsPmove( playermove_t *pmove, const float *p, int *truecontents );\nvoid PM_StuckTouch( playermove_t *pmove, int hitent, pmtrace_t *tr );\n\nstatic inline void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent )\n{\n\tout->allsolid = in->allsolid;\n\tout->startsolid = in->startsolid;\n\tout->inopen = in->inopen;\n\tout->inwater = in->inwater;\n\tout->fraction = in->fraction;\n\tout->plane.dist = in->plane.dist;\n\tout->hitgroup = in->hitgroup;\n\tout->ent = ent;\n\n\tVectorCopy( in->endpos, out->endpos );\n\tVectorCopy( in->plane.normal, out->plane.normal );\n}\n\nstatic inline void PM_ClearPhysEnts( playermove_t *pmove )\n{\n\tpmove->nummoveent = 0;\n\tpmove->numphysent = 0;\n\tpmove->numvisent = 0;\n\tpmove->numtouch = 0;\n}\n\nstatic inline void PM_InitTrace( trace_t *trace, const vec3_t end )\n{\n\tmemset( trace, 0, sizeof( *trace ));\n\tVectorCopy( end, trace->endpos );\n\ttrace->allsolid = true;\n\ttrace->fraction = 1.0f;\n}\n\nstatic inline void PM_InitPMTrace( pmtrace_t *trace, const vec3_t end )\n{\n\tmemset( trace, 0, sizeof( *trace ));\n\tVectorCopy( end, trace->endpos );\n\ttrace->allsolid = true;\n\ttrace->fraction = 1.0f;\n}\n\n//\n// pm_surface.c\n//\nmsurface_t *PM_RecursiveSurfCheck( model_t *model, mnode_t *node, vec3_t p1, vec3_t p2 );\nmsurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end );\nint PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags );\n\n#endif//PM_LOCAL_H\n"
  },
  {
    "path": "engine/common/pm_surface.c",
    "content": "/*\npm_surface.c - surface tracing\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n#include \"pm_local.h\"\n#include \"ref_common.h\"\n\n#undef FRAC_EPSILON\n#define FRAC_EPSILON\t(1.0f / 32.0f)\n\ntypedef struct\n{\n\tfloat\t\tfraction;\n\tint\t\tcontents;\n\tmsurface_t\t*surface;\n} linetrace_t;\n\n/*\n==============\nfix_coord\n\nconverts the reletive tex coords to absolute\n==============\n*/\nstatic uint fix_coord( vec_t in, uint width )\n{\n\tif( in > 0 ) return (uint)in % width;\n\treturn width - ((uint)fabs( in ) % width);\n}\n\n/*\n=============\nSampleMiptex\n\nfence texture testing\n=============\n*/\nstatic int PM_SampleMiptex( const msurface_t *surf, const vec3_t point )\n{\n\tmextrasurf_t\t*info = surf->info;\n\tmfacebevel_t\t*fb = info->bevel;\n\tint\t\tcontents;\n\tvec_t\t\tds, dt;\n\tint\t\tx, y;\n\tmtexinfo_t\t*tx;\n\ttexture_t\t\t*mt;\n\n\t// fill the default contents\n\tif( fb ) contents = fb->contents;\n\telse contents = CONTENTS_SOLID;\n\n\tif( !surf->texinfo || !surf->texinfo->texture )\n\t\treturn contents;\n\n\ttx = surf->texinfo;\n\tmt = tx->texture;\n\n\tif( mt->name[0] != '{' )\n\t\treturn contents;\n\n\t// TODO: this won't work under dedicated\n\t// should we bring up imagelib and keep original buffers?\n#if !XASH_DEDICATED\n\tif( !Host_IsDedicated() )\n\t{\n\t\tconst byte\t\t*data;\n\n\t\tdata = ref.dllFuncs.R_GetTextureOriginalBuffer( mt->gl_texturenum );\n\n\t\tif( !data ) return contents; // original doesn't kept\n\n\t\tds = DotProduct( point, tx->vecs[0] ) + tx->vecs[0][3];\n\t\tdt = DotProduct( point, tx->vecs[1] ) + tx->vecs[1][3];\n\n\t\t// convert ST to real pixels position\n\t\tx = fix_coord( ds, mt->width - 1 );\n\t\ty = fix_coord( dt, mt->height - 1 );\n\n\t\tASSERT( x >= 0 && y >= 0 );\n\n\t\tif( data[(mt->width * y) + x] == 255 )\n\t\t\treturn CONTENTS_EMPTY;\n\t\treturn CONTENTS_SOLID;\n\t}\n#endif // !XASH_DEDICATED\n\n\treturn contents;\n}\n\n/*\n==================\nPM_RecursiveSurfCheck\n\n==================\n*/\nmsurface_t *PM_RecursiveSurfCheck( model_t *mod, mnode_t *node, vec3_t p1, vec3_t p2 )\n{\n\tfloat\t\tt1, t2, frac;\n\tint\t\ti, side;\n\tmsurface_t\t*surf;\n\tvec3_t\t\tmid;\n\tmnode_t *children[2];\n\tint numsurfaces, firstsurface;\n\nloc0:\n\tif( node->contents < 0 )\n\t\treturn NULL;\n\n\tt1 = PlaneDiff( p1, node->plane );\n\tt2 = PlaneDiff( p2, node->plane );\n\n\tnode_children( children, node, mod );\n\n\tif( t1 >= -FRAC_EPSILON && t2 >= -FRAC_EPSILON )\n\t{\n\t\tnode = children[0];\n\t\tgoto loc0;\n\t}\n\n\tif( t1 < FRAC_EPSILON && t2 < FRAC_EPSILON )\n\t{\n\t\tnode = children[1];\n\t\tgoto loc0;\n\t}\n\n\tside = (t1 < 0.0f);\n\tfrac = t1 / ( t1 - t2 );\n\tfrac = bound( 0.0f, frac, 1.0f );\n\n\tVectorLerp( p1, frac, p2, mid );\n\n\tif(( surf = PM_RecursiveSurfCheck( mod, children[side], p1, mid )) != NULL )\n\t\treturn surf;\n\n\t// walk through real faces\n\tnumsurfaces = node_numsurfaces( node, mod );\n\tfirstsurface = node_firstsurface( node, mod );\n\tfor( i = 0; i < numsurfaces; i++ )\n\t{\n\t\tmsurface_t\t*surf = &mod->surfaces[firstsurface + i];\n\t\tmextrasurf_t\t*info = surf->info;\n\t\tmfacebevel_t\t*fb = info->bevel;\n\t\tint\t\tj, contents;\n\t\tvec3_t\t\tdelta;\n\n\t\tif( !fb ) continue;\t// ???\n\n\t\tVectorSubtract( mid, fb->origin, delta );\n\t\tif( DotProduct( delta, delta ) >= fb->radius )\n\t\t\tcontinue;\t// no intersection\n\n\t\tfor( j = 0; j < fb->numedges; j++ )\n\t\t{\n\t\t\tif( PlaneDiff( mid, &fb->edges[j] ) > FRAC_EPSILON )\n\t\t\t\tbreak; // outside the bounds\n\t\t}\n\n\t\tif( j != fb->numedges )\n\t\t\tcontinue; // we are outside the bounds of the facet\n\n\t\t// hit the surface\n\t\tcontents = PM_SampleMiptex( surf, mid );\n\n\t\tif( contents != CONTENTS_EMPTY )\n\t\t\treturn surf;\n\t\treturn NULL; // through the fence\n\t}\n\n\treturn PM_RecursiveSurfCheck( mod, children[side^1], mid, p2 );\n}\n\n/*\n==================\nPM_TraceTexture\n\nfind the face where the traceline hit\nassume physentity is valid\n==================\n*/\nmsurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end )\n{\n\tmatrix4x4\t\tmatrix;\n\tmodel_t\t\t*bmodel;\n\thull_t\t\t*hull;\n\tvec3_t\t\tstart_l, end_l;\n\tvec3_t\t\toffset;\n\n\tbmodel = pe->model;\n\n\tif( !bmodel || bmodel->type != mod_brush )\n\t\treturn NULL;\n\n\thull = &pe->model->hulls[0];\n\tVectorSubtract( hull->clip_mins, vec3_origin, offset );\n\tVectorAdd( offset, pe->origin, offset );\n\n\tVectorSubtract( start, offset, start_l );\n\tVectorSubtract( end, offset, end_l );\n\n\t// rotate start and end into the models frame of reference\n\tif( !VectorIsNull( pe->angles ))\n\t{\n\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t}\n\n\treturn PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l );\n}\n\n/*\n==================\nPM_TestLine_r\n\noptimized trace for light gathering\n==================\n*/\nstatic int PM_TestLine_r( model_t *mod, mnode_t *node, vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t stop, linetrace_t *trace )\n{\n\tfloat\tfront, back;\n\tfloat\tfrac, midf;\n\tint\ti, r, side;\n\tvec3_t\tmid;\n\tmnode_t *children[2];\n\tint numsurfaces, firstsurface;\n\nloc0:\n\tif( node->contents < 0 )\n\t{\n\t\t// water, slime or lava interpret as empty\n\t\tif( node->contents == CONTENTS_SOLID )\n\t\t\treturn CONTENTS_SOLID;\n\t\tif( node->contents == CONTENTS_SKY )\n\t\t\treturn CONTENTS_SKY;\n\t\ttrace->fraction = 1.0f;\n\t\treturn CONTENTS_EMPTY;\n\t}\n\n\tfront = PlaneDiff( start, node->plane );\n\tback = PlaneDiff( stop, node->plane );\n\n\tnode_children( children, node, mod );\n\n\tif( front >= -FRAC_EPSILON && back >= -FRAC_EPSILON )\n\t{\n\t\tnode = children[0];\n\t\tgoto loc0;\n\t}\n\n\tif( front < FRAC_EPSILON && back < FRAC_EPSILON )\n\t{\n\t\tnode = children[1];\n\t\tgoto loc0;\n\t}\n\n\tside = (front < 0);\n\tfrac = front / (front - back);\n\tfrac = bound( 0.0f, frac, 1.0f );\n\n\tVectorLerp( start, frac, stop, mid );\n\tmidf = p1f + ( p2f - p1f ) * frac;\n\n\tr = PM_TestLine_r( mod, children[side], p1f, midf, start, mid, trace );\n\n\tif( r != CONTENTS_EMPTY )\n\t{\n\t\tif( trace->surface == NULL )\n\t\t\ttrace->fraction = midf;\n\t\ttrace->contents = r;\n\t\treturn r;\n\t}\n\n\t// walk through real faces\n\tnumsurfaces = node_numsurfaces( node, mod );\n\tfirstsurface = node_firstsurface( node, mod );\n\tfor( i = 0; i < numsurfaces; i++ )\n\t{\n\t\tmsurface_t\t*surf = &mod->surfaces[firstsurface + i];\n\t\tmextrasurf_t\t*info = surf->info;\n\t\tmfacebevel_t\t*fb = info->bevel;\n\t\tint\t\tj, contents;\n\t\tvec3_t\t\tdelta;\n\n\t\tif( !fb ) continue;\n\n\t\tVectorSubtract( mid, fb->origin, delta );\n\t\tif( DotProduct( delta, delta ) >= fb->radius )\n\t\t\tcontinue;\t// no intersection\n\n\t\tfor( j = 0; j < fb->numedges; j++ )\n\t\t{\n\t\t\tif( PlaneDiff( mid, &fb->edges[j] ) > FRAC_EPSILON )\n\t\t\t\tbreak; // outside the bounds\n\t\t}\n\n\t\tif( j != fb->numedges )\n\t\t\tcontinue; // we are outside the bounds of the facet\n\n\t\t// hit the surface\n\t\tcontents = PM_SampleMiptex( surf, mid );\n\n\t\t// fill the trace and out\n\t\ttrace->contents = contents;\n\t\ttrace->fraction = midf;\n\n\t\tif( contents != CONTENTS_EMPTY )\n\t\t\ttrace->surface = surf;\n\n\t\treturn contents;\n\t}\n\n\treturn PM_TestLine_r( mod, children[!side], midf, p2f, mid, stop, trace );\n}\n\nint PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags )\n{\n\tlinetrace_t\ttrace, trace_bbox;\n\tmatrix4x4\t\tmatrix;\n\thull_t\t\t*hull = NULL;\n\tvec3_t\t\toffset, start_l, end_l;\n\tqboolean\t\trotated;\n\tphysent_t\t\t*pe;\n\tint\t\ti;\n\n\ttrace.contents = CONTENTS_EMPTY;\n\ttrace.fraction = 1.0f;\n\ttrace.surface = NULL;\n\n\tfor( i = 0; i < numents; i++ )\n\t{\n\t\tpe = &ents[i];\n\n\t\tif( i != 0 && FBitSet( flags, PM_WORLD_ONLY ))\n\t\t\tbreak;\n\n\t\tif( !pe->model || pe->model->type != mod_brush || pe->solid != SOLID_BSP )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( flags, PM_GLASS_IGNORE ) && pe->rendermode != kRenderNormal )\n\t\t\tcontinue;\n\n\t\thull = &pe->model->hulls[0];\n\n\t\thull = PM_HullForBsp( pe, pmove, offset );\n\n\t\tif( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))\n\t\t\trotated = true;\n\t\telse rotated = false;\n\n\t\tif( rotated )\n\t\t{\n\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( start, pe->origin, start_l );\n\t\t\tVectorSubtract( end, pe->origin, end_l );\n\t\t}\n\n\t\ttrace_bbox.contents = CONTENTS_EMPTY;\n\t\ttrace_bbox.fraction = 1.0f;\n\t\ttrace_bbox.surface = NULL;\n\n\t\tPM_TestLine_r( pe->model, &pe->model->nodes[hull->firstclipnode], 0.0f, 1.0f, start_l, end_l, &trace_bbox );\n\n\t\tif( trace_bbox.contents != CONTENTS_EMPTY || trace_bbox.fraction < trace.fraction )\n\t\t{\n\t\t\ttrace = trace_bbox;\n\t\t}\n\t}\n\n\treturn trace.contents;\n}\n"
  },
  {
    "path": "engine/common/pm_trace.c",
    "content": "/*\npm_trace.c - pmove player trace code\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n#include \"mod_local.h\"\n#include \"pm_local.h\"\n#include \"pm_movevars.h\"\n#include \"enginefeatures.h\"\n#include \"studio.h\"\n#include \"world.h\"\n\n#define PM_AllowHitBoxTrace( model, hull ) ( model && model->type == mod_studio && ( FBitSet( model->flags, STUDIO_TRACE_HITBOX ) || hull == 2 ))\n\nstatic mplane_t\tpm_boxplanes[6];\nstatic hull_t pm_boxhull;\n\n// default hullmins\nstatic const vec3_t pm_hullmins[MAX_MAP_HULLS] =\n{\n{ -16, -16, -36 },\n{ -16, -16, -18 },\n{   0,   0,   0 },\n{ -32, -32, -32 },\n};\n\n// defualt hullmaxs\nstatic const vec3_t pm_hullmaxs[MAX_MAP_HULLS] =\n{\n{  16,  16,  36 },\n{  16,  16,  18 },\n{   0,   0,   0 },\n{  32,  32,  32 },\n};\n\nvoid Pmove_Init( void )\n{\n\tPM_InitBoxHull ();\n\n\t// init default hull sizes\n\tmemcpy( host.player_mins, pm_hullmins, sizeof( pm_hullmins ));\n\tmemcpy( host.player_maxs, pm_hullmaxs, sizeof( pm_hullmaxs ));\n}\n\n/*\n===================\nPM_InitBoxHull\n\nSet up the planes and clipnodes so that the six floats of a bounding box\ncan just be stored out and get a proper hull_t structure.\n===================\n*/\nvoid PM_InitBoxHull( void )\n{\n\tint\ti;\n\n\tpm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\tpm_boxhull.planes = pm_boxplanes;\n\tpm_boxhull.firstclipnode = 0;\n\tpm_boxhull.lastclipnode = 5;\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tpm_boxplanes[i].type = i>>1;\n\t\tpm_boxplanes[i].normal[i>>1] = 1.0f;\n\t\tpm_boxplanes[i].signbits = 0;\n\t}\n\n}\n\n/*\n===================\nPM_HullForBox\n\nTo keep everything totally uniform, bounding boxes are turned into small\nBSP trees instead of being compared directly.\n===================\n*/\nstatic hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs )\n{\n\tpm_boxplanes[0].dist = maxs[0];\n\tpm_boxplanes[1].dist = mins[0];\n\tpm_boxplanes[2].dist = maxs[1];\n\tpm_boxplanes[3].dist = mins[1];\n\tpm_boxplanes[4].dist = maxs[2];\n\tpm_boxplanes[5].dist = mins[2];\n\n\tif( world.version == QBSP2_VERSION )\n\t\tpm_boxhull.clipnodes32 = (mclipnode32_t *)box_clipnodes32;\n\telse\n\t\tpm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\n\treturn &pm_boxhull;\n}\n\n/*\n==================\nPM_HullPointContents\n\n==================\n*/\nint GAME_EXPORT PM_HullPointContents( hull_t *hull, int num, const vec3_t p )\n{\n\tmplane_t\t\t*plane;\n\n\tif( !hull || !hull->planes )\t// fantom bmodels?\n\t\treturn CONTENTS_NONE;\n\n\tif( world.version == QBSP2_VERSION )\n\t{\n\t\twhile( num >= 0 )\n\t\t{\n\t\t\tplane = &hull->planes[hull->clipnodes32[num].planenum];\n\t\t\tnum = hull->clipnodes32[num].children[PlaneDiff( p, plane ) < 0];\n\t\t}\n\t}\n\telse\n\t{\n\t\twhile( num >= 0 )\n\t\t{\n\t\t\tplane = &hull->planes[hull->clipnodes16[num].planenum];\n\t\t\tnum = hull->clipnodes16[num].children[PlaneDiff( p, plane ) < 0];\n\t\t}\n\t}\n\treturn num;\n}\n\n/*\n==================\nPM_HullForBsp\n\nassume physent is valid\n==================\n*/\nhull_t *PM_HullForBsp( physent_t *pe, playermove_t *pmove, float *offset )\n{\n\thull_t\t*hull;\n\n\tAssert( pe != NULL );\n\tAssert( pe->model != NULL );\n\n\tswitch( pmove->usehull )\n\t{\n\tcase 1:\n\t\thull = &pe->model->hulls[3];\n\t\tbreak;\n\tcase 2:\n\t\thull = &pe->model->hulls[0];\n\t\tbreak;\n\tcase 3:\n\t\thull = &pe->model->hulls[2];\n\t\tbreak;\n\tdefault:\n\t\thull = &pe->model->hulls[1];\n\t\tbreak;\n\t}\n\n\tAssert( hull != NULL );\n\n\t// calculate an offset value to center the origin\n\tVectorSubtract( hull->clip_mins, host.player_mins[pmove->usehull], offset );\n\tVectorAdd( offset, pe->origin, offset );\n\n\treturn hull;\n}\n\n/*\n==================\nPM_HullForStudio\n\ngenerate multiple hulls as hitboxes\n==================\n*/\nstatic hull_t *PM_HullForStudio( physent_t *pe, playermove_t *pmove, int *numhitboxes )\n{\n\tvec3_t\tsize;\n\n\tVectorSubtract( host.player_maxs[pmove->usehull], host.player_mins[pmove->usehull], size );\n\tVectorScale( size, 0.5f, size );\n\n\treturn Mod_HullForStudio( pe->studiomodel, pe->frame, pe->sequence, pe->angles, pe->origin, size, pe->controller, pe->blending, numhitboxes, NULL );\n}\n\n/*\n==================\nPM_RecursiveHullCheck\n==================\n*/\nqboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace )\n{\n\tint children[2];\n\tmplane_t\t\t*plane;\n\tfloat\t\tt1, t2;\n\tfloat\t\tfrac, midf;\n\tint\t\tside;\n\tvec3_t\t\tmid;\nloc0:\n\t// check for empty\n\tif( num < 0 )\n\t{\n\t\tif( num != CONTENTS_SOLID )\n\t\t{\n\t\t\ttrace->allsolid = false;\n\t\t\tif( num == CONTENTS_EMPTY )\n\t\t\t\ttrace->inopen = true;\n\t\t\telse trace->inwater = true;\n\t\t}\n\t\telse trace->startsolid = true;\n\t\treturn true; // empty\n\t}\n\n\tif( hull->firstclipnode >= hull->lastclipnode )\n\t{\n\t\t// empty hull?\n\t\ttrace->allsolid = false;\n\t\ttrace->inopen = true;\n\t\treturn true;\n\t}\n\n\tif( num < hull->firstclipnode || num > hull->lastclipnode )\n\t\tHost_Error( \"%s: bad node number %i\\n\", __func__, num );\n\n\t// find the point distances\n\tif( world.version == QBSP2_VERSION )\n\t{\n\t\tchildren[0] = hull->clipnodes32[num].children[0];\n\t\tchildren[1] = hull->clipnodes32[num].children[1];\n\t\tplane = hull->planes + hull->clipnodes32[num].planenum;\n\t}\n\telse\n\t{\n\t\tchildren[0] = hull->clipnodes16[num].children[0];\n\t\tchildren[1] = hull->clipnodes16[num].children[1];\n\t\tplane = hull->planes + hull->clipnodes16[num].planenum;\n\t}\n\n\tt1 = PlaneDiff( p1, plane );\n\tt2 = PlaneDiff( p2, plane );\n\n\tif( t1 >= 0.0f && t2 >= 0.0f )\n\t{\n\t\tnum = children[0];\n\t\tgoto loc0;\n\t}\n\n\tif( t1 < 0.0f && t2 < 0.0f )\n\t{\n\t\tnum = children[1];\n\t\tgoto loc0;\n\t}\n\n\t// put the crosspoint DIST_EPSILON pixels on the near side\n\tside = (t1 < 0.0f);\n\n\tif( side ) frac = ( t1 + DIST_EPSILON ) / ( t1 - t2 );\n\telse frac = ( t1 - DIST_EPSILON ) / ( t1 - t2 );\n\n\tif( frac < 0.0f ) frac = 0.0f;\n\tif( frac > 1.0f ) frac = 1.0f;\n\n\tmidf = p1f + ( p2f - p1f ) * frac;\n\tVectorLerp( p1, frac, p2, mid );\n\n\t// move up to the node\n\tif( !PM_RecursiveHullCheck( hull, children[side], p1f, midf, p1, mid, trace ))\n\t\treturn false;\n\n\t// this recursion can not be optimized because mid would need to be duplicated on a stack\n\tif( PM_HullPointContents( hull, children[side^1], mid ) != CONTENTS_SOLID )\n\t{\n\t\t// go past the node\n\t\treturn PM_RecursiveHullCheck( hull, children[side^1], midf, p2f, mid, p2, trace );\n\t}\n\n\t// never got out of the solid area\n\tif( trace->allsolid )\n\t\treturn false;\n\n\t// the other side of the node is solid, this is the impact point\n\tif( !side )\n\t{\n\t\tVectorCopy( plane->normal, trace->plane.normal );\n\t\ttrace->plane.dist = plane->dist;\n\t}\n\telse\n\t{\n\t\tVectorNegate( plane->normal, trace->plane.normal );\n\t\ttrace->plane.dist = -plane->dist;\n\t}\n\n\twhile( PM_HullPointContents( hull, hull->firstclipnode, mid ) == CONTENTS_SOLID )\n\t{\n\t\t// shouldn't really happen, but does occasionally\n\t\tfrac -= 0.1f;\n\n\t\tif( frac < 0.0f )\n\t\t{\n\t\t\ttrace->fraction = midf;\n\t\t\tVectorCopy( mid, trace->endpos );\n\t\t\tCon_Reportf( S_WARN \"trace backed up past 0.0\\n\" );\n\t\t\treturn false;\n\t\t}\n\n\t\tmidf = p1f + ( p2f - p1f ) * frac;\n\t\tVectorLerp( p1, frac, p2, mid );\n\t}\n\n\ttrace->fraction = midf;\n\tVectorCopy( mid, trace->endpos );\n\n\treturn false;\n}\n\npmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int flags, int numents, physent_t *ents, int ignore_pe, pfnIgnore pmFilter )\n{\n\tphysent_t\t*pe;\n\tmatrix4x4\tmatrix;\n\tpmtrace_t\ttrace_bbox;\n\tpmtrace_t\ttrace_hitbox;\n\tpmtrace_t\ttrace_total;\n\tvec3_t\toffset, start_l, end_l;\n\tvec3_t\ttemp, mins, maxs;\n\tint\ti, j, hullcount;\n\tqboolean\trotated, transform_bbox;\n\thull_t\t*hull = NULL;\n\n\tmemset( &trace_total, 0, sizeof( trace_total ));\n\tVectorCopy( end, trace_total.endpos );\n\ttrace_total.fraction = 1.0f;\n\ttrace_total.ent = -1;\n\n\tfor( i = 0; i < numents; i++ )\n\t{\n\t\tpe = &ents[i];\n\n\t\tif( i != 0 && ( flags & PM_WORLD_ONLY ))\n\t\t\tbreak;\n\n\t\t// run custom user filter\n\t\tif( pmFilter != NULL )\n\t\t{\n\t\t\tif( pmFilter( pe ))\n\t\t\t\tcontinue;\n\t\t}\n\t\telse if( ignore_pe != -1 )\n\t\t{\n\t\t\tif( i == ignore_pe )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( pe->model != NULL && pe->solid == SOLID_NOT && pe->skin != CONTENTS_NONE )\n\t\t\tcontinue;\n\n\t\tif(( flags & PM_GLASS_IGNORE ) && pe->rendermode != kRenderNormal )\n\t\t\tcontinue;\n\n\t\tif(( flags & PM_CUSTOM_IGNORE ) && pe->solid == SOLID_CUSTOM )\n\t\t\tcontinue;\n\n\t\thullcount = 1;\n\n\t\tif( pe->solid == SOLID_CUSTOM )\n\t\t{\n\t\t\tVectorCopy( host.player_mins[pmove->usehull], mins );\n\t\t\tVectorCopy( host.player_maxs[pmove->usehull], maxs );\n\t\t\tVectorClear( offset );\n\t\t}\n\t\telse if( pe->model )\n\t\t{\n\t\t\thull = PM_HullForBsp( pe, pmove, offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( pe->studiomodel )\n\t\t\t{\n\t\t\t\tif( FBitSet( flags, PM_STUDIO_IGNORE ))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( PM_AllowHitBoxTrace( pe->studiomodel, pmove->usehull ) && !FBitSet( flags, PM_STUDIO_BOX ))\n\t\t\t\t{\n\t\t\t\t\thull = PM_HullForStudio( pe, pmove, &hullcount );\n\t\t\t\t\tVectorClear( offset );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tVectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins );\n\t\t\t\t\tVectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs );\n\n\t\t\t\t\thull = PM_HullForBox( mins, maxs );\n\t\t\t\t\tVectorCopy( pe->origin, offset );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins );\n\t\t\t\tVectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs );\n\n\t\t\t\thull = PM_HullForBox( mins, maxs );\n\t\t\t\tVectorCopy( pe->origin, offset );\n\t\t\t}\n\n\t\t}\n\n\t\tif( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))\n\t\t\trotated = true;\n\t\telse rotated = false;\n\n\t\tif( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))\n\t\t{\n\t\t\tif(( check_angles( pe->angles[0] ) || check_angles( pe->angles[2] )) && pmove->usehull != 2 )\n\t\t\t\ttransform_bbox = true;\n\t\t\telse transform_bbox = false;\n\t\t}\n\t\telse transform_bbox = false;\n\n\t\tif( rotated )\n\t\t{\n\t\t\tif( transform_bbox )\n\t\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, pe->origin, 1.0f );\n\t\t\telse Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\n\t\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\n\t\t\tif( transform_bbox )\n\t\t\t{\n\t\t\t\tWorld_TransformAABB( matrix, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], mins, maxs );\n\t\t\t\tVectorSubtract( hull->clip_mins, mins, offset );\t// calc new local offset\n\n\t\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( start_l[j] >= 0.0f )\n\t\t\t\t\t\tstart_l[j] -= offset[j];\n\t\t\t\t\telse start_l[j] += offset[j];\n\t\t\t\t\tif( end_l[j] >= 0.0f )\n\t\t\t\t\t\tend_l[j] -= offset[j];\n\t\t\t\t\telse end_l[j] += offset[j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( start, offset, start_l );\n\t\t\tVectorSubtract( end, offset, end_l );\n\t\t}\n\n\t\tPM_InitPMTrace( &trace_bbox, end );\n\n\t\tif( hullcount < 1 )\n\t\t{\n\t\t\t// g-cont. probably this never happens\n\t\t\ttrace_bbox.allsolid = false;\n\t\t}\n\t\telse if( pe->solid == SOLID_CUSTOM )\n\t\t{\n\t\t\t// run custom sweep callback\n\t\t\tif( pmove->server || Host_IsLocalClient( ))\n\t\t\t\tSV_ClipPMoveToEntity( pe, start, mins, maxs, end, &trace_bbox );\n#if !XASH_DEDICATED\n\t\t\telse CL_ClipPMoveToEntity( pe, start, mins, maxs, end, &trace_bbox );\n#endif\n\t\t}\n\t\telse if( hullcount == 1 )\n\t\t{\n\t\t\tPM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace_bbox );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint\tlast_hitgroup;\n\n\t\t\tfor( last_hitgroup = 0, j = 0; j < hullcount; j++ )\n\t\t\t{\n\t\t\t\tPM_InitPMTrace( &trace_hitbox, end );\n\n\t\t\t\tPM_RecursiveHullCheck( &hull[j], hull[j].firstclipnode, 0, 1, start_l, end_l, &trace_hitbox );\n\n\t\t\t\tif( j == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace_bbox.fraction )\n\t\t\t\t{\n\t\t\t\t\tif( trace_bbox.startsolid )\n\t\t\t\t\t{\n\t\t\t\t\t\ttrace_bbox = trace_hitbox;\n\t\t\t\t\t\ttrace_bbox.startsolid = true;\n\t\t\t\t\t}\n\t\t\t\t\telse trace_bbox = trace_hitbox;\n\n\t\t\t\t\tlast_hitgroup = j;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttrace_bbox.hitgroup = Mod_HitgroupForStudioHull( last_hitgroup );\n\t\t}\n\n\t\tif( trace_bbox.allsolid )\n\t\t\ttrace_bbox.startsolid = true;\n\n\t\tif( trace_bbox.startsolid )\n\t\t\ttrace_bbox.fraction = 0.0f;\n\n\t\tif( !trace_bbox.startsolid )\n\t\t{\n\t\t\tVectorLerp( start, trace_bbox.fraction, end, trace_bbox.endpos );\n\n\t\t\tif( rotated )\n\t\t\t{\n\t\t\t\tVectorCopy( trace_bbox.plane.normal, temp );\n\t\t\t\tMatrix4x4_TransformPositivePlane( matrix, temp, trace_bbox.plane.dist, trace_bbox.plane.normal, &trace_bbox.plane.dist );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ttrace_bbox.plane.dist = DotProduct( trace_bbox.endpos, trace_bbox.plane.normal );\n\t\t\t}\n\t\t}\n\n\t\tif( trace_bbox.fraction < trace_total.fraction )\n\t\t{\n\t\t\ttrace_total = trace_bbox;\n\t\t\ttrace_total.ent = i;\n\t\t}\n\t}\n\n\treturn trace_total;\n}\n\nint PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, pfnIgnore pmFilter )\n{\n\tint\ti, j, hullcount;\n\tvec3_t\tpos_l, offset;\n\thull_t\t*hull = NULL;\n\tvec3_t\tmins, maxs;\n\tpmtrace_t trace;\n\tphysent_t *pe;\n\n\ttrace = PM_PlayerTraceExt( pmove, pmove->origin, pmove->origin, 0, pmove->numphysent, pmove->physents, -1, pmFilter );\n\tif( ptrace ) *ptrace = trace;\n\n\tfor( i = 0; i < pmove->numphysent; i++ )\n\t{\n\t\tpe = &pmove->physents[i];\n\n\t\t// run custom user filter\n\t\tif( pmFilter != NULL )\n\t\t{\n\t\t\tif( pmFilter( pe ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( pe->model != NULL && pe->solid == SOLID_NOT && pe->skin != CONTENTS_NONE )\n\t\t\tcontinue;\n\n\t\thullcount = 1;\n\n\t\tif( pe->solid == SOLID_CUSTOM )\n\t\t{\n\t\t\tVectorCopy( host.player_mins[pmove->usehull], mins );\n\t\t\tVectorCopy( host.player_maxs[pmove->usehull], maxs );\n\t\t\tVectorClear( offset );\n\t\t}\n\t\telse if( pe->model )\n\t\t{\n\t\t\thull = PM_HullForBsp( pe, pmove, offset );\n\t\t}\n\t\telse if( PM_AllowHitBoxTrace( pe->studiomodel, pmove->usehull ))\n\t\t{\n\t\t\thull = PM_HullForStudio( pe, pmove, &hullcount );\n\t\t\tVectorClear( offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins );\n\t\t\tVectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs );\n\n\t\t\thull = PM_HullForBox( mins, maxs );\n\t\t\tVectorCopy( pe->origin, offset );\n\t\t}\n\n\t\t// CM_TransformedPointContents :-)\n\t\tif( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))\n\t\t{\n\t\t\tqboolean\ttransform_bbox = false;\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tif( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))\n\t\t\t{\n\t\t\t\tif(( check_angles( pe->angles[0] ) || check_angles( pe->angles[2] )) && pmove->usehull != 2 )\n\t\t\t\t\ttransform_bbox = true;\n\t\t\t}\n\n\t\t\tif( transform_bbox )\n\t\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, pe->origin, 1.0f );\n\t\t\telse Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\n\t\t\tMatrix4x4_VectorITransform( matrix, pos, pos_l );\n\n\t\t\tif( transform_bbox )\n\t\t\t{\n\t\t\t\tWorld_TransformAABB( matrix, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], mins, maxs );\n\t\t\t\tVectorSubtract( hull->clip_mins, mins, offset );\t// calc new local offset\n\n\t\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( pos_l[j] >= 0.0f )\n\t\t\t\t\t\tpos_l[j] -= offset[j];\n\t\t\t\t\telse pos_l[j] += offset[j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// offset the test point appropriately for this hull.\n\t\t\tVectorSubtract( pos, offset, pos_l );\n\t\t}\n\n\t\tif( pe->solid == SOLID_CUSTOM )\n\t\t{\n\t\t\tpmtrace_t\ttrace;\n\n\t\t\tPM_InitPMTrace( &trace, pos );\n\n\t\t\t// run custom sweep callback\n\t\t\tif( pmove->server || Host_IsLocalClient( ))\n\t\t\t\tSV_ClipPMoveToEntity( pe, pos, mins, maxs, pos, &trace );\n#if !XASH_DEDICATED\n\t\t\telse CL_ClipPMoveToEntity( pe, pos, mins, maxs, pos, &trace );\n#endif\n\n\t\t\t// if we inside the custom hull\n\t\t\tif( trace.allsolid )\n\t\t\t\treturn i;\n\t\t}\n\t\telse if( hullcount == 1 )\n\t\t{\n\t\t\tif( PM_HullPointContents( hull, hull->firstclipnode, pos_l ) == CONTENTS_SOLID )\n\t\t\t\treturn i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( j = 0; j < hullcount; j++ )\n\t\t\t{\n\t\t\t\tif( PM_HullPointContents( &hull[j], hull[j].firstclipnode, pos_l ) == CONTENTS_SOLID )\n\t\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn -1; // didn't hit anything\n}\n\n/*\n=============\nPM_TruePointContents\n\n=============\n*/\nint PM_TruePointContents( playermove_t *pmove, const vec3_t p )\n{\n\thull_t\t*hull = &pmove->physents[0].model->hulls[0];\n\n\tif( hull )\n\t{\n\t\treturn PM_HullPointContents( hull, hull->firstclipnode, p );\n\t}\n\telse\n\t{\n\t\treturn CONTENTS_EMPTY;\n\t}\n}\n\n/*\n=============\nPM_PointContents\n\n=============\n*/\nint PM_PointContents( playermove_t *pmove, const vec3_t p )\n{\n\tint\ti, contents;\n\thull_t\t*hull;\n\tvec3_t\ttest;\n\tphysent_t\t*pe;\n\n\t// sanity check\n\tif( !p || !pmove->physents[0].model )\n\t\treturn CONTENTS_NONE;\n\n\t// get base contents from world\n\tcontents = PM_HullPointContents( &pmove->physents[0].model->hulls[0], 0, p );\n\n\tfor( i = 1; i < pmove->numphysent; i++ )\n\t{\n\t\tpe = &pmove->physents[i];\n\n\t\tif( pe->solid != SOLID_NOT ) // disabled ?\n\t\t\tcontinue;\n\n\t\t// only brushes can have special contents\n\t\tif( !pe->model ) continue;\n\n\t\t// check water brushes accuracy\n\t\thull = &pe->model->hulls[0];\n\n\t\tif( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles ))\n\t\t{\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, pe->origin, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, p, test );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// offset the test point appropriately for this hull.\n\t\t\tVectorSubtract( p, pe->origin, test );\n\t\t}\n\n\t\t// test hull for intersection with this model\n\t\tif( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )\n\t\t\tcontinue;\n\n\t\t// compare contents ranking\n\t\tif( RankForContents( pe->skin ) > RankForContents( contents ))\n\t\t\tcontents = pe->skin; // new content has more priority\n\t}\n\n\treturn contents;\n}\n\n/*\n=============\nPM_TraceModel\n\n=============\n*/\nfloat PM_TraceModel( playermove_t *pmove, physent_t *pe, float *start, float *end, trace_t *trace )\n{\n\tint\told_usehull;\n\tvec3_t\tstart_l, end_l;\n\tvec3_t\toffset, temp;\n\tqboolean\trotated;\n\tmatrix4x4\tmatrix;\n\thull_t\t*hull;\n\n\tPM_InitTrace( trace, end );\n\n\told_usehull = pmove->usehull;\n\tpmove->usehull = 2;\n\n\thull = PM_HullForBsp( pe, pmove, offset );\n\n\tpmove->usehull = old_usehull;\n\n\tif( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles ))\n\t\trotated = true;\n\telse rotated = false;\n\n\tif( rotated )\n\t{\n\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t}\n\telse\n\t{\n\t\tVectorSubtract( start, offset, start_l );\n\t\tVectorSubtract( end, offset, end_l );\n\t}\n\n\tPM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace );\n\ttrace->ent = NULL;\n\n\tif( rotated )\n\t{\n\t\tVectorCopy( trace->plane.normal, temp );\n\t\tMatrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist );\n\t}\n\n\tVectorLerp( start, trace->fraction, end, trace->endpos );\n\n\treturn trace->fraction;\n}\n\npmtrace_t *PM_TraceLine( playermove_t *pmove, float *start, float *end, int flags, int usehull, int ignore_pe )\n{\n\tstatic pmtrace_t\ttr;\n\tint\t\told_usehull;\n\n\told_usehull = pmove->usehull;\n\tpmove->usehull = usehull;\n\n\tswitch( flags )\n\t{\n\tcase PM_TRACELINE_PHYSENTSONLY:\n\t\ttr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numphysent, pmove->physents, ignore_pe, NULL );\n\t\tbreak;\n\tcase PM_TRACELINE_ANYVISIBLE:\n\t\ttr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numvisent, pmove->visents, ignore_pe, NULL );\n\t\tbreak;\n\t}\n\n\tpmove->usehull = old_usehull;\n\n\treturn &tr;\n}\n\npmtrace_t *PM_TraceLineEx( playermove_t *pmove, float *start, float *end, int flags, int usehull, pfnIgnore pmFilter )\n{\n\tstatic pmtrace_t\ttr;\n\tint\t\told_usehull;\n\n\told_usehull = pmove->usehull;\n\tpmove->usehull = usehull;\n\n\tswitch( flags )\n\t{\n\tcase PM_TRACELINE_PHYSENTSONLY:\n\t\ttr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numphysent, pmove->physents, -1, pmFilter );\n\t\tbreak;\n\tcase PM_TRACELINE_ANYVISIBLE:\n\t\ttr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numvisent, pmove->visents, -1, pmFilter );\n\t\tbreak;\n\t}\n\n\tpmove->usehull = old_usehull;\n\n\treturn &tr;\n}\n\nstruct msurface_s *PM_TraceSurfacePmove( playermove_t *pmove, int ground, float *vstart, float *vend )\n{\n\tif( ground < 0 || ground >= pmove->numphysent )\n\t\treturn NULL; // bad ground\n\n\treturn PM_TraceSurface( &pmove->physents[ground], vstart, vend );\n}\n\nconst char *PM_TraceTexture( playermove_t *pmove, int ground, float *vstart, float *vend )\n{\n\tmsurface_t *surf;\n\n\tif( ground < 0 || ground >= pmove->numphysent )\n\t\treturn NULL; // bad ground\n\n\tsurf = PM_TraceSurface( &pmove->physents[ground], vstart, vend );\n\n\tif( !surf || !surf->texinfo || !surf->texinfo->texture )\n\t\treturn NULL;\n\n\treturn surf->texinfo->texture->name;\n}\n\nint PM_PointContentsPmove( playermove_t *pmove, const float *p, int *truecontents )\n{\n\tint\tcont, truecont;\n\n\ttruecont = cont = PM_PointContents( pmove, p );\n\tif( truecontents ) *truecontents = truecont;\n\n\tif( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )\n\t\tcont = CONTENTS_WATER;\n\treturn cont;\n}\n\nvoid PM_StuckTouch( playermove_t *pmove, int hitent, pmtrace_t *tr )\n{\n\tint\ti;\n\n\tfor( i = 0; i < pmove->numtouch; i++ )\n\t{\n\t\tif( pmove->touchindex[i].ent == hitent )\n\t\t\treturn;\n\t}\n\n\tif( pmove->numtouch >= MAX_PHYSENTS )\n\t\treturn;\n\n\tVectorCopy( pmove->velocity, tr->deltavelocity );\n\ttr->ent = hitent;\n\n\tpmove->touchindex[pmove->numtouch++] = *tr;\n}\n"
  },
  {
    "path": "engine/common/protocol.h",
    "content": "/*\nprotocol.h - communications protocols\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef NET_PROTOCOL_H\n#define NET_PROTOCOL_H\n\n#define PROTOCOL_VERSION\t\t49\n\n// server to client\n#define svc_bad\t\t\t0\t// immediately crash client when received\n#define svc_nop\t\t\t1\t// does nothing\n#define svc_disconnect\t\t2\t// kick client from server\n#define svc_event\t\t\t3\t// playback event queue\n#define svc_changing\t\t4\t// changelevel by server request\n#define svc_setview\t\t\t5\t// [short] entity number\n#define svc_sound\t\t\t6\t// <see code>\n#define svc_time\t\t\t7\t// [float] server time\n#define svc_print\t\t\t8\t// [byte] id [string] null terminated string\n#define svc_stufftext\t\t9\t// [string] stuffed into client's console buffer\n#define svc_setangle\t\t10\t// [angle angle angle] set the view angle to this absolute value\n#define svc_serverdata\t\t11\t// [int] protocol ...\n#define svc_lightstyle\t\t12\t// [index][pattern][float]\n#define svc_updateuserinfo\t\t13\t// [byte] playernum, [string] userinfo\n#define svc_deltatable\t\t14\t// [table header][...]\n#define svc_clientdata\t\t15\t// [...]\n#define svc_resource\t\t16\t// [...] late-precached resource will be download in-game\n#define svc_pings\t\t\t17\t// [bit][idx][ping][packet_loss]\n#define svc_particle\t\t18\t// [float*3][char*3][byte][byte]\n#define svc_restoresound\t\t19\t// <see code>\n#define svc_spawnstatic\t\t20\t// creates a static client entity\n#define svc_event_reliable\t\t21\t// playback event directly from message, not queue\n#define svc_spawnbaseline\t\t22\t// <see code>\n#define svc_temp_entity\t\t23\t// <variable sized>\n#define svc_setpause\t\t24\t// [byte] 0 = unpaused, 1 = paused\n#define svc_signonnum\t\t25\t// [byte] used for the signon sequence\n#define svc_centerprint\t\t26\t// [string] to put in center of the screen\n// reserved\n// reserved\n// reserved\n#define svc_intermission\t\t30\t// empty message (event)\n#define svc_finale\t\t\t31\t// empty message (event)\n#define svc_cdtrack\t\t\t32\t// [string] trackname\n#define svc_restore\t\t\t33\t// [string] savename\n#define svc_cutscene\t\t34\t// empty message (event)\n#define svc_weaponanim\t\t35\t// [byte]iAnim [byte]body\n#define svc_bspdecal\t\t36\t// [float*3][short][short][short]\n#define svc_roomtype\t\t37\t// [short] room type\n#define svc_addangle\t\t38\t// [angle] add angles when client turn on mover\n#define svc_usermessage\t\t39\t// [byte][byte][string] REG_USER_MSG stuff\n#define svc_packetentities\t\t40\t// [short][...]\n#define svc_deltapacketentities\t41\t// [short][byte][...]\n#define svc_choke\t\t\t42\t// just event\n#define svc_resourcelist\t\t43\t// [short][...]\n#define svc_deltamovevars\t\t44\t// [movevars_t]\n#define svc_resourcerequest\t\t45\t// <see code>\n#define svc_customization\t\t46\n#define svc_crosshairangle\t\t47\t// [byte][byte]\n#define svc_soundfade\t\t48\t// [float*4] sound fade parms\n#define svc_filetxferfailed\t\t49\t// [string]\n#define svc_hltv\t\t\t50\t// sending from the game.dll\n#define svc_director\t\t51\t// <variable sized>\n#define svc_voiceinit\t\t52\t// <see code>\n#define svc_voicedata\t\t53\t// [byte][short][...]\n// reserved\n// reserved\n#define svc_resourcelocation\t\t56\t// [string]\n#define svc_querycvarvalue\t\t57\t// [string]\n#define svc_querycvarvalue2\t\t58\t// [string][int] (context)\n#define svc_exec\t\t\t\t59\t// [byte][...]\n#define svc_lastmsg\t\t\t59\t// start user messages at this point\n\n// client to server\n#define clc_bad\t\t\t0\t// immediately drop client when received\n#define clc_nop\t\t\t1\n#define clc_move\t\t\t2\t// [[usercmd_t]\n#define clc_stringcmd\t\t3\t// [string] message\n#define clc_delta\t\t\t4\t// [byte] sequence number, requests delta compression of message\n#define clc_resourcelist\t\t5\n// reserved\n#define clc_fileconsistency\t\t7\n#define clc_voicedata\t\t8\n#define clc_requestcvarvalue\t\t9\n#define clc_requestcvarvalue2\t\t10\n#define clc_lastmsg\t\t\t11\t// end client messages (11 is GoldSrc message)\n\n#define MAX_VISIBLE_PACKET_BITS\t11\t// 2048 visible entities per frame (hl1 has 256)\n#define MAX_VISIBLE_PACKET\t\t(1<<MAX_VISIBLE_PACKET_BITS)\n#define MAX_VISIBLE_PACKET_VIS_BYTES\t((MAX_VISIBLE_PACKET + 7) / 8)\n\n// additional protocol data\n#define MAX_CLIENT_BITS\t\t5\n#define MAX_CLIENTS\t\t\t(1<<MAX_CLIENT_BITS)// 5 bits == 32 clients ( int32 limit )\n\n#define MAX_WEAPON_BITS\t\t6\n#define MAX_WEAPONS\t\t\t(1<<MAX_WEAPON_BITS)// 6 bits == 64 predictable weapons\n\n#define MAX_EVENT_BITS\t\t10\n#define MAX_EVENTS\t\t\t(1<<MAX_EVENT_BITS)\t// 10 bits == 1024 events (the original Half-Life limit)\n\n#define MAX_MODEL_BITS\t\t12\t\t// 12 bits == 4096 models\n#define MAX_MODELS\t\t\t(1<<MAX_MODEL_BITS)\n\n#define MAX_SOUND_BITS\t\t11\n#define MAX_SOUNDS\t\t\t(1<<MAX_SOUND_BITS)\t// 11 bits == 2048 sounds\n#define MAX_SOUNDS_NONSENTENCE MAX_SOUNDS\n\n#define MAX_ENTITY_BITS\t\t13\t\t// 13 bits = 8192 edicts\n#define MAX_EDICTS\t\t\t(1<<MAX_ENTITY_BITS)\n#define MAX_EDICTS_BYTES\t\t((MAX_EDICTS + 7) / 8)\n#define LAST_EDICT\t\t\t(MAX_EDICTS - 1)\n\n#define MIN_EDICTS\t\t\t64\n\n#define MAX_CUSTOM_BITS\t\t10\n#define MAX_CUSTOM\t\t\t(1<<MAX_CUSTOM_BITS)// 10 bits == 1024 generic file\n#define MAX_USER_MESSAGES\t\t197\t\t// another 58 messages reserved for engine routines\n#define MAX_DLIGHTS\t\t\t32\t\t// dynamic lights (rendered per one frame)\n#define MAX_ELIGHTS\t\t\t128\t\t// a1ba: increased from 64 to 128, entity only point lights\n#define MAX_LIGHTSTYLES\t\t256\t\t// a1ba: increased from 64 to 256, protocol limit\n#define MAX_RENDER_DECALS\t\t4096\t\t// max rendering decals per a level\n\n// sound proto\n#define MAX_SND_FLAGS_BITS\t\t14\n#define MAX_SND_CHAN_BITS\t\t4\n\n// sound flags\n#define SND_VOLUME\t\t\t(1<<0)\t// a scaled byte\n#define SND_ATTENUATION\t\t(1<<1)\t// a byte\n#define SND_SEQUENCE\t\t(1<<2)\t// get sentence from a script\n#define SND_PITCH\t\t\t(1<<3)\t// a byte\n#define SND_SENTENCE\t\t(1<<4)\t// set if sound num is actually a sentence num\n#define SND_STOP\t\t\t(1<<5)\t// stop the sound\n#define SND_CHANGE_VOL\t\t(1<<6)\t// change sound vol\n#define SND_CHANGE_PITCH\t\t(1<<7)\t// change sound pitch\n#define SND_SPAWNING\t\t(1<<8)\t// we're spawning, used in some cases for ambients (not sent across network)\n#define SND_LOCALSOUND\t\t(1<<9)\t// not paused, not looped, for internal use\n#define SND_STOP_LOOPING\t\t(1<<10)\t// stop all looping sounds on the entity.\n#define SND_FILTER_CLIENT\t\t(1<<11)\t// don't send sound from local player if prediction was enabled\n#define SND_RESTORE_POSITION\t\t(1<<12)\t// passed playing position and the forced end\n\n// decal flags\n#define FDECAL_PERMANENT\t\t0x01\t// This decal should not be removed in favor of any new decals\n#define FDECAL_USE_LANDMARK\t\t0x02\t// This is a decal applied on a bmodel without origin-brush so we done in absoulute pos\n#define FDECAL_CUSTOM\t\t0x04\t// This is a custom clan logo and should not be saved/restored\n// reserved\n// reserved\n#define FDECAL_DONTSAVE\t\t0x20\t// Decal was loaded from adjacent level, don't save it for this level\n#define FDECAL_STUDIO\t\t0x40\t// Indicates a studio decal\n#define FDECAL_LOCAL_SPACE\t\t0x80\t// decal is in local space (any decal after serialization)\n\n// game type\n#define GAME_SINGLEPLAYER\t\t0\n#define GAME_DEATHMATCH\t\t1\n#define GAME_COOP\t\t\t2\n#define GAME_TEAMPLAY\t\t4\n\n// Max number of history commands to send ( 2 by default ) in case of dropped packets\n#define NUM_BACKUP_COMMAND_BITS 4\n#define MAX_BACKUP_COMMANDS     BIT( NUM_BACKUP_COMMAND_BITS )\n#define MAX_TOTAL_CMDS          32\n\n#define MAX_RESOURCES\t\t(MAX_MODELS+MAX_SOUNDS+MAX_CUSTOM+MAX_EVENTS)\n#define MAX_RESOURCE_BITS\t\t13\t// 13 bits 8192 resource (4096 models + 2048 sounds + 1024 events + 1024 files)\n#define\tFRAGMENT_MIN_SIZE\t\t\t508\t\t// RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header)\n#define FRAGMENT_DEFAULT_SIZE\t\t1200\t\t// default MTU\n#define FRAGMENT_MAX_SIZE\t\t64000\t\t// maximal fragment size\n#define FRAGMENT_LOCAL_SIZE\t\tFRAGMENT_MAX_SIZE\t// local connection\n\n#if XASH_LOW_MEMORY == 2\n#undef MAX_VISIBLE_PACKET\n#undef MAX_VISIBLE_PACKET_VIS_BYTES\n#undef MAX_EVENTS\n#undef MAX_MODELS\n#undef MAX_SOUNDS\n#undef MAX_CUSTOM\n#undef MAX_DLIGHTS\n#undef MAX_ELIGHTS\n#undef MAX_RENDER_DECALS\n#undef MAX_RESOURCES\n// memory reduced protocol, not for use in multiplayer (but still compatible)\n#define MAX_VISIBLE_PACKET\t\t128\n#define MAX_VISIBLE_PACKET_VIS_BYTES\t((MAX_VISIBLE_PACKET + 7) / 8)\n#define MAX_EVENTS\t\t\t128\n#define MAX_MODELS\t\t\t512\n#define MAX_SOUNDS\t\t\t512\n#define MAX_CUSTOM\t\t\t32\n#define MAX_DLIGHTS\t\t\t16\t\t// dynamic lights (rendered per one frame)\n#define MAX_ELIGHTS\t\t\t32\t\t// entity only point lights\n#define MAX_RENDER_DECALS\t\t64\t\t// max rendering decals per a level\n#define MAX_RESOURCES\t\t1024\n#elif XASH_LOW_MEMORY == 1\n#undef MAX_VISIBLE_PACKET\n#undef MAX_VISIBLE_PACKET_VIS_BYTES\n#undef MAX_EVENTS\n#undef MAX_MODELS\n#undef MAX_CUSTOM\n#undef MAX_RENDER_DECALS\n#undef MAX_RESOURCES\n#define MAX_VISIBLE_PACKET\t\t256\n#define MAX_VISIBLE_PACKET_VIS_BYTES\t((MAX_VISIBLE_PACKET + 7) / 8)\n#define MAX_EVENTS\t\t\t128\n#define MAX_MODELS\t\t\t1024\n#define MAX_CUSTOM\t\t\t512\n#define MAX_RENDER_DECALS\t128\n#define MAX_RESOURCES\t\t1024\n#endif\n\n// Quake1 Protocol\n#define PROTOCOL_VERSION_QUAKE\t15\n\n// listed only unmatched ops\n#define svc_updatestat\t\t3\t// [byte] [int]\t\t\t(svc_event)\n#define svc_version\t\t\t4\t// [int] server version\t\t(svc_changing)\n#define svc_updatename\t\t13\t// [byte] [string]\t\t\t(svc_updateuserinfo)\n#define svc_updatefrags\t\t14\t// [byte] [short]\t\t\t(svc_deltatable)\n#define svc_stopsound\t\t16\t// <see code>\t\t\t(svc_resource)\n#define svc_updatecolors\t\t17\t// [byte] [byte]\t\t\t(svc_pings)\n#define svc_damage\t\t\t19\t//\t\t\t\t(svc_restoresound)\n#define svc_spawnbinary\t\t21\t//\t\t\t\t(svc_event_reliable)\n#define svc_killedmonster\t\t27\n#define svc_foundsecret\t\t28\n#define svc_spawnstaticsound\t\t29\t// [coord3] [byte] samp [byte] vol [byte] aten\n#define svc_sellscreen\t\t33\t//\t\t\t\t(svc_restore)\n// Nehahra added\n#define svc_showlmp\t\t\t35\t// [string] slotname [string] lmpfilename [coord] x [coord] y\n#define svc_hidelmp\t\t\t36\t// [string] slotname\n#define svc_skybox\t\t\t37\t// [string] skyname\n#define svc_skyboxsize\t\t50\t// [coord] size (default is 4096)\n#define svc_fog\t\t\t51\t// [byte] enable <optional past this point, only included if enable is true>\n\t\t\t\t\t// [float] density [byte] red [byte] green [byte] blue\n\n// if the high bit of the servercmd is set, the low bits are fast update flags:\n#define U_MOREBITS\t\t(1<<0)\n#define U_ORIGIN1\t\t(1<<1)\n#define U_ORIGIN2\t\t(1<<2)\n#define U_ORIGIN3\t\t(1<<3)\n#define U_ANGLE2\t\t(1<<4)\n#define U_NOLERP\t\t(1<<5)\t\t// don't interpolate movement\n#define U_FRAME\t\t(1<<6)\n#define U_SIGNAL\t\t(1<<7)\t\t// just differentiates from other updates\n\n// svc_update can pass all of the fast update bits, plus more\n#define U_ANGLE1\t\t(1<<8)\n#define U_ANGLE3\t\t(1<<9)\n#define U_MODEL\t\t(1<<10)\n#define U_COLORMAP\t\t(1<<11)\n#define U_SKIN\t\t(1<<12)\n#define U_EFFECTS\t\t(1<<13)\n#define U_LONGENTITY\t(1<<14)\n#define U_TRANS\t\t(1<<15)\t\t// nehahra\n\n// clientdata flags\n#define SU_VIEWHEIGHT\t(1<<0)\n#define SU_IDEALPITCH\t(1<<1)\n#define SU_PUNCH1\t\t(1<<2)\n#define SU_PUNCH2\t\t(1<<3)\n#define SU_PUNCH3\t\t(1<<4)\n#define SU_VELOCITY1\t(1<<5)\n#define SU_VELOCITY2\t(1<<6)\n#define SU_VELOCITY3\t(1<<7)\n//define\tSU_AIMENT\t\t(1<<8)  AVAILABLE BIT\n#define SU_ITEMS\t\t(1<<9)\n#define SU_ONGROUND\t\t(1<<10)\t\t// no data follows, the bit is it\n#define SU_INWATER\t\t(1<<11)\t\t// no data follows, the bit is it\n#define SU_WEAPONFRAME\t(1<<12)\n#define SU_ARMOR\t\t(1<<13)\n#define SU_WEAPON\t\t(1<<14)\n\nextern const char *const svc_strings[svc_lastmsg+1];\nextern const char *const svc_legacy_strings[svc_lastmsg+1];\nextern const char *const svc_quake_strings[svc_lastmsg+1];\nextern const char *const svc_goldsrc_strings[svc_lastmsg+1];\nextern const char *const clc_strings[clc_lastmsg+1];\n\n// FWGS extensions\n#define NET_EXT_SPLITSIZE (1U<<0) // set splitsize by cl_dlmax\n\n// legacy protocol definitons\n#define PROTOCOL_LEGACY_VERSION\t\t48\n#define svc_legacy_modelindex\t\t31\t// [index][modelpath]\n#define svc_legacy_soundindex\t\t28\t// [index][soundpath]\n#define svc_legacy_eventindex\t\t34\t// [index][eventname]\n#define svc_legacy_ambientsound\t\t29\n#define svc_legacy_chokecount 42\t\t// old client specified count, new just sends svc_choke\n#define svc_legacy_event\t\t\t27\t// playback event queue\n#define svc_legacy_changing\t\t\t3\t// changelevel by server request\n\n#define clc_legacy_userinfo\t\t6\t// [[userinfo string]\n\n#define SND_LEGACY_LARGE_INDEX\t\t(1<<2)\t// a send sound as short\n#define MAX_LEGACY_ENTITY_BITS\t\t12\n#define MAX_LEGACY_WEAPON_BITS\t\t5\n#define MAX_LEGACY_MODEL_BITS  11\n#define MAX_LEGACY_TOTAL_CMDS  16 // 28 - 16 = 12 real legacy max backup\n#define MAX_LEGACY_BACKUP_CMDS 12\n\n#define MAX_LEGACY_EDICTS (1 << MAX_LEGACY_ENTITY_BITS) // 4096 edicts\n#define MIN_LEGACY_EDICTS 30\n\n// legacy engine features that can be implemented through currently supported features\n#define ENGINE_LEGACY_FEATURES_MASK   \\\n\t( ENGINE_WRITE_LARGE_COORD    \\\n\t| ENGINE_LOAD_DELUXEDATA      \\\n\t| ENGINE_LARGE_LIGHTMAPS      \\\n\t| ENGINE_COMPENSATE_QUAKE_BUG \\\n\t| ENGINE_COMPUTE_STUDIO_LERP  )\n\n// Master Server protocol\n#define MS_SCAN_REQUEST \"1\\xFF\" \"0.0.0.0:0\\0\" // TODO: implement IP filter\n\n// GoldSrc protocol definitions\n#define PROTOCOL_GOLDSRC_VERSION 48\n\n#define svc_goldsrc_version           svc_changing\n#define svc_goldsrc_stopsound         svc_resource\n#define svc_goldsrc_damage            svc_restoresound\n#define svc_goldsrc_killedmonster     27\n#define svc_goldsrc_foundsecret       28\n#define svc_goldsrc_spawnstaticsound  29\n#define svc_goldsrc_decalname         svc_bspdecal\n#define svc_goldsrc_sendextrainfo     54\n#define svc_goldsrc_timescale         55\n\n#define clc_goldsrc_hltv              clc_requestcvarvalue  // 9\n#define clc_goldsrc_requestcvarvalue  clc_requestcvarvalue2 // 10\n#define clc_goldsrc_requestcvarvalue2 11\n#define clc_goldsrc_lastmsg           11\n\n#define MAX_GOLDSRC_BACKUP_CMDS   8\n#define MAX_GOLDSRC_TOTAL_CMDS    16\n#define MAX_GOLDSRC_EXTENDED_TOTAL_CMDS 62\n#define MAX_GOLDSRC_MODEL_BITS    10\n#define MAX_GOLDSRC_RESOURCE_BITS 12\n#define MAX_GOLDSRC_ENTITY_BITS   11\n// #define MAX_GOLDSRC_EDICTS        BIT( MAX_ENTITY_BITS )\n#define MAX_GOLDSRC_EDICTS        ( BIT( MAX_ENTITY_BITS ) + ( MAX_CLIENTS * 15 ))\n#define LAST_GOLDSRC_EDICT        ( BIT( MAX_ENTITY_BITS ) - 1 )\n\n\n// from any to any (must be handled on both server and client)\n\n#define A2A_PING         \"ping\" // reply with A2A_ACK\n#define A2A_ACK          \"ack\" // no-op\n#define A2A_INFO         \"info\" // different format for client and server, see code\n#define A2A_NETINFO      \"netinfo\" // different format for client and server, see code\n#define A2A_GOLDSRC_PING \"i\" // reply with A2A_GOLDSRC_ACK\n#define A2A_GOLDSRC_ACK  \"j\" // no-op\n\n// from any to server\n#define A2S_GOLDSRC_INFO    \"TSource Engine Query\"\n#define A2S_GOLDSRC_RULES   'V'\n#define A2S_GOLDSRC_PLAYERS 'U'\n\n// from server to any\n#define S2A_GOLDSRC_INFO    'I'\n#define S2A_GOLDSRC_RULES   'E'\n#define S2A_GOLDSRC_PLAYERS 'D'\n\n// from master to server\n#define M2S_CHALLENGE     \"s\"\n#define M2S_NAT_CONNECT   \"c\"\n\n// from server to master\n#define S2M_INFO          \"0\\n\"\n\n// from client to server\n#define C2S_BANDWIDTHTEST \"bandwidth\"\n#define C2S_GETCHALLENGE  \"getchallenge\"\n#define C2S_CONNECT       \"connect\"\n#define C2S_RCON          \"rcon\"\n\n// from server to client\n#define S2C_BANDWIDTHTEST              \"testpacket\"\n#define S2C_CHALLENGE                  \"challenge\"\n#define S2C_CONNECTION                 \"client_connect\"\n#define S2C_ERRORMSG                   \"errormsg\"\n#define S2C_REJECT                     \"disconnect\"\n#define S2C_GOLDSRC_REJECT_BADPASSWORD '8'\n#define S2C_GOLDSRC_REJECT             '9'\n#define S2C_GOLDSRC_CHALLENGE          \"A00000000\"\n#define S2C_GOLDSRC_CONNECTION         \"B\"\n\n// from any to client\n#define A2C_PRINT           \"print\"\n#define A2C_GOLDSRC_PRINT   'l'\n\n// from master to client\n#define M2A_SERVERSLIST \"f\"\n\n#endif//NET_PROTOCOL_H\n"
  },
  {
    "path": "engine/common/soundlib/snd_utils.c",
    "content": "/*\nsnd_utils.c - sound common tools\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"soundlib.h\"\n\n// global sound variables\nsndlib_t\tsound;\n\n/*\n=============================================================================\n\n\tXASH3D LOAD SOUND FORMATS\n\n=============================================================================\n*/\nstatic const loadwavfmt_t load_game[] =\n{\n#ifndef XASH_DEDICATED\n{ \"wav\", Sound_LoadWAV },\n{ \"mp3\", Sound_LoadMPG },\n{ \"ogg\", Sound_LoadOggVorbis },\n{ \"opus\", Sound_LoadOggOpus },\n#else // we only need extensions\n{ \"wav\" },\n{ \"mp3\" },\n{ \"ogg\" },\n{ \"opus\" },\n#endif\n{ NULL },\n};\n\n/*\n=============================================================================\n\n\tXASH3D PROCESS STREAM FORMATS\n\n=============================================================================\n*/\nstatic const streamfmt_t stream_game[] =\n{\n#ifndef XASH_DEDICATED\n{ \"mp3\", Stream_OpenMPG, Stream_ReadMPG, Stream_SetPosMPG, Stream_GetPosMPG, Stream_FreeMPG },\n{ \"wav\", Stream_OpenWAV, Stream_ReadWAV, Stream_SetPosWAV, Stream_GetPosWAV, Stream_FreeWAV },\n{ \"ogg\", Stream_OpenOggVorbis, Stream_ReadOggVorbis, Stream_SetPosOggVorbis, Stream_GetPosOggVorbis, Stream_FreeOggVorbis },\n{ \"opus\", Stream_OpenOggOpus, Stream_ReadOggOpus, Stream_SetPosOggOpus, Stream_GetPosOggOpus, Stream_FreeOggOpus },\n#else // we only need extensions\n{ \"mp3\" },\n{ \"wav\" },\n{ \"ogg\" },\n{ \"opus\" },\n#endif\n{ NULL },\n};\n\nvoid Sound_Init( void )\n{\n\t// init pools\n\thost.soundpool = Mem_AllocPool( \"SoundLib Pool\" );\n\n\t// install sound formats\n\tsound.loadformats = load_game;\n\tsound.streamformat = stream_game;\n\n\tsound.tempbuffer = NULL;\n}\n\nvoid Sound_Shutdown( void )\n{\n\tMem_Check(); // check for leaks\n\tMem_FreePool( &host.soundpool );\n}\n\nuint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath )\n{\n\tstring    name;\n\tfile_t    *f;\n\twavehdr_t wav;\n\tsize_t    filesize;\n\tuint      msecs;\n\n\tQ_strncpy( name, filepath, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tf = FS_Open( name, \"rb\", false );\n\tif( !f )\n\t\treturn 0;\n\n\tif( FS_Read( f, &wav, sizeof( wav )) != sizeof( wav ))\n\t{\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tfilesize = FS_FileLength( f );\n\tfilesize -= 128; // magic number from GoldSrc, seems to be header size\n\n\tFS_Close( f );\n\n\t// is real wav file ?\n\tif( wav.riff_id != RIFFHEADER || wav.wave_id != WAVEHEADER || wav.fmt_id != FORMHEADER )\n\t\treturn 0;\n\n\tif( wav.nAvgBytesPerSec >= 1000 )\n\t\tmsecs = (uint)((float)filesize / ((float)wav.nAvgBytesPerSec / 1000.0f));\n\telse msecs = (uint)(((float)filesize / (float)wav.nAvgBytesPerSec) * 1000.0f);\n\n\treturn msecs;\n}\n\n#define SOUND_FORMATCONVERT_BOILERPLATE( resamplemacro ) \\\n\tif( inwidth == 1 ) \\\n\t{ \\\n\t\tconst int8_t *data = (const int8_t *)sc->buffer; \\\n\t\tif( outwidth == 1 ) \\\n\t\t{ \\\n\t\t\tint8_t *outdata = (int8_t *)sound.tempbuffer; \\\n\t\t\tresamplemacro( 1 ) \\\n\t\t} \\\n\t\telse if( outwidth == 2 ) \\\n\t\t{ \\\n\t\t\tint16_t *outdata = (int16_t *)sound.tempbuffer; \\\n\t\t\tresamplemacro( 256 ) \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse if( inwidth == 2 ) \\\n\t{ \\\n\t\tconst int16_t *data = (const int16_t *)sc->buffer; \\\n\t\tif( outwidth == 1 ) \\\n\t\t{ \\\n\t\t\tint8_t *outdata = (int8_t *)sound.tempbuffer; \\\n\t\t\tresamplemacro( 1 / 256.0 ) \\\n\t\t} \\\n\t\telse if( outwidth == 2 ) \\\n\t\t{ \\\n\t\t\tint16_t *outdata = (int16_t *)sound.tempbuffer; \\\n\t\t\tresamplemacro( 1 ) \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse \\\n\t\treturn false; \\\n\n\n#define SOUND_CONVERTNORESAMPLE_BOILERPLATE( multiplier ) \\\n\tif( inchannels == 1 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount * outchannels; i++ ) \\\n\t\t\t\toutdata[i] = data[i] * ( multiplier ); \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\toutdata[i * 2 + 0] = data[i] * ( multiplier ); \\\n\t\t\t\toutdata[i * 2 + 1] = data[i] * ( multiplier ); \\\n\t\t\t} \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse if( inchannels == 2 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t\toutdata[i] = ( data[i * 2 + 0] + data[i * 2 + 1] ) * ( multiplier ) / 2; \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount * outchannels; i++ ) \\\n\t\t\t\toutdata[i] = data[i] * ( multiplier ); \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse \\\n\t\treturn false; \\\n\n#define SOUND_CONVERTDOWNSAMPLE_BOILERPLATE( multiplier ) \\\n\tif( inchannels == 1 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i] = data[(int)j] * ( multiplier ); \\\n\t\t\t} \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i;\\\n\t\t\t\toutdata[i * 2 + 0] = data[(int)j] * ( multiplier ); \\\n\t\t\t\toutdata[i * 2 + 1] = data[(int)j] * ( multiplier ); \\\n\t\t\t} \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse if( inchannels == 2 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i] = ( data[((int)j) * 2 + 0] + data[((int)j) * 2 + 1] ) * ( multiplier ) / 2; \\\n\t\t\t} \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i * 2 + 0] = data[((int)j) * 2 + 0] * ( multiplier ); \\\n\t\t\t\toutdata[i * 2 + 1] = data[((int)j) * 2 + 1] * ( multiplier ); \\\n\t\t\t} \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse \\\n\t\treturn false; \\\n\n\n#define SOUND_CONVERTUPSAMPLE_BOILERPLATE( multiplier ) \\\n\tif( inchannels == 1 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i] = data[(int)j] * ( multiplier ); \\\n\t\t\t\tif( j != (int)j && j < incount ) \\\n\t\t\t\t{ \\\n\t\t\t\t\tdouble frac = ( j - (int)j ) * ( multiplier ); \\\n\t\t\t\t\toutdata[i] += (data[(int)j+1] - data[(int)j]) * frac; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i * 2 + 0] = data[(int)j] * ( multiplier ); \\\n\t\t\t\tif( j != (int)j && j < incount ) \\\n\t\t\t\t{ \\\n\t\t\t\t\tdouble frac = ( j - (int)j ) * ( multiplier ); \\\n\t\t\t\t\toutdata[i * 2 + 0] += (data[(int)j+1] - data[(int)j]) * frac; \\\n\t\t\t\t} \\\n\t\t\t\toutdata[i * 2 + 1] = outdata[i * 2 + 0]; \\\n\t\t\t} \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse if( inchannels == 2 ) \\\n\t{ \\\n\t\tif( outchannels == 1 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i] = ( data[((int)j) * 2 + 0] + data[((int)j) * 2 + 1] ) * ( multiplier ) / 2; \\\n\t\t\t\tif( j != (int)j && j < incount ) \\\n\t\t\t\t{ \\\n\t\t\t\t\tdouble frac = ( j - (int)j ) * ( multiplier ) / 2; \\\n\t\t\t\t\toutdata[i] += (data[((int)j + 1 ) * 2 + 0] - data[((int)j) * 2 + 0]) * frac; \\\n\t\t\t\t\toutdata[i] += (data[((int)j + 1 ) * 2 + 1] - data[((int)j) * 2 + 1]) * frac; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\telse if( outchannels == 2 ) \\\n\t\t{ \\\n\t\t\tfor( i = 0; i < outcount; i++ ) \\\n\t\t\t{ \\\n\t\t\t\tdouble j = stepscale * i; \\\n\t\t\t\toutdata[i*2+0] = data[((int)j)*2+0] * ( multiplier ); \\\n\t\t\t\toutdata[i*2+1] = data[((int)j)*2+1] * ( multiplier ); \\\n\t\t\t\tif( j != (int)j && j < incount ) \\\n\t\t\t\t{ \\\n\t\t\t\t\tdouble frac = ( j - (int)j ) * ( multiplier ); \\\n\t\t\t\t\toutdata[i*2+0] += (data[((int)j+1)*2+0] - data[((int)j)*2+0]) * frac; \\\n\t\t\t\t\toutdata[i*2+1] += (data[((int)j+1)*2+1] - data[((int)j)*2+1]) * frac; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\telse \\\n\t\t\treturn false; \\\n\t} \\\n\telse \\\n\t\treturn false; \\\n\nstatic qboolean Sound_ConvertNoResample( wavdata_t *sc, int inwidth, int inchannels, int outwidth, int outchannels, int outcount )\n{\n\tsize_t i;\n\n\t// I could write a generic case here but I also want to make it easier for compiler\n\t// to unroll these for-loops\n\n\tSOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTNORESAMPLE_BOILERPLATE )\n\n\treturn true;\n}\n\nstatic qboolean Sound_ConvertDownsample( wavdata_t *sc, int inwidth, int inchannels, int outwidth, int outchannels, int outcount, double stepscale )\n{\n\tsize_t i;\n\n\tSOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTDOWNSAMPLE_BOILERPLATE )\n\n\treturn true;\n}\n\nstatic qboolean Sound_ConvertUpsample( wavdata_t *sc, int inwidth, int inchannels, int incount, int outwidth, int outchannels, int outcount, double stepscale )\n{\n\tsize_t i;\n\n\tincount--; // to not go past last sample while interpolating\n\n\tSOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTUPSAMPLE_BOILERPLATE )\n\n\treturn true;\n}\n\n#undef SOUND_FORMATCONVERT_BOILERPLATE\n#undef SOUND_CONVERTNORESAMPLE_BOILERPLATE\n#undef SOUND_CONVERTDOWNSAMPLE_BOILERPLATE\n#undef SOUND_CONVERTUPSAMPLE_BOILERPLATE\n\n/*\n================\nSound_ResampleInternal\n\n================\n*/\nstatic qboolean Sound_ResampleInternal( wavdata_t *sc, int outrate, int outwidth, int outchannels )\n{\n\tconst int inrate = sc->rate;\n\tconst int inwidth = sc->width;\n\tconst int inchannels = sc->channels;\n\tconst int incount = sc->samples;\n\tconst int insize = sc->size;\n\tqboolean handled = false;\n\tdouble stepscale;\n\tdouble t1, t2;\n\tint\toutcount;\n\n\tif( inrate == outrate && inwidth == outwidth && inchannels == outchannels )\n\t\treturn false;\n\n\tt1 = Sys_DoubleTime();\n\n\tstepscale = (double)inrate / outrate;\t// this is usually 0.5, 1, or 2\n\toutcount = sc->samples / stepscale;\n\tsc->size = outcount * outwidth * outchannels;\n\tsc->channels = outchannels;\n\n\tsc->samples = outcount;\n\tif( FBitSet( sc->flags, SOUND_LOOPED ))\n\t\tsc->loopStart = sc->loopStart / stepscale;\n\n\tsound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, sc->size );\n\n\tif( inrate == outrate ) // no resampling, just copy data\n\t\thandled = Sound_ConvertNoResample( sc, inwidth, inchannels, outwidth, outchannels, outcount );\n\telse if( inrate > outrate ) // fast case, usually downsample but is also ok for upsampling\n\t\thandled = Sound_ConvertDownsample( sc, inwidth, inchannels, outwidth, outchannels, outcount, stepscale );\n\telse // upsample case, w/ interpolation\n\t\thandled = Sound_ConvertUpsample( sc, inwidth, inchannels, incount, outwidth, outchannels, outcount, stepscale );\n\n\tif( !handled )\n\t{\n\t\t// restore previously changed data\n\t\tsc->rate = inrate;\n\t\tsc->width = inwidth;\n\t\tsc->channels = inchannels;\n\t\tsc->samples = incount;\n\t\tsc->size = insize;\n\n\t\tCon_Printf( S_ERROR \"%s: unsupported from [%d bit %d Hz %dch] to [%d bit %d Hz %dch]\\n\", __func__, inwidth * 8, inrate, inchannels, outwidth * 8, outrate, outchannels );\n\n\t\treturn false;\n\t}\n\n\tt2 = Sys_DoubleTime();\n\tsc->rate = outrate;\n\tsc->width = outwidth;\n\n\tif( t2 - t1 > 0.01f ) // critical, report to mod developer\n\t\tCon_Printf( S_WARN \"%s: from [%d bit %d Hz %dch] to [%d bit %d Hz %dch] (took %.3fs)\\n\", __func__, inwidth * 8, inrate, inchannels, outwidth * 8, outrate, outchannels, t2 - t1 );\n\telse\n\t\tCon_Reportf( \"%s: from [%d bit %d Hz %dch] to [%d bit %d Hz %dch] (took %.3fs)\\n\", __func__, inwidth * 8, inrate, inchannels, outwidth * 8, outrate, outchannels, t2 - t1 );\n\n\treturn true;\n}\n\nqboolean Sound_Process( wavdata_t **wav, int rate, int width, int channels, uint flags )\n{\n\twavdata_t\t*snd = *wav;\n\tqboolean\tresult = true;\n\n\t// check for buffers\n\tif( unlikely( !snd || !snd->buffer ))\n\t\treturn false;\n\n\tif( likely( FBitSet( flags, SOUND_RESAMPLE ) && ( width > 0 || rate > 0\t|| channels > 0 )))\n\t{\n\t\tresult = Sound_ResampleInternal( snd, rate, width, channels );\n\n\t\tif( result )\n\t\t{\n\t\t\tsnd = Mem_Realloc( host.soundpool, snd, sizeof( *snd ) + snd->size );\n\t\t\tmemcpy( snd->buffer, sound.tempbuffer, snd->size );\n\n\t\t\tMem_Free( sound.tempbuffer );\n\t\t\tsound.tempbuffer = NULL;\n\t\t}\n\t}\n\n\t*wav = snd;\n\n\treturn result;\n}\n\nqboolean Sound_SupportedFileFormat( const char *fileext )\n{\n\tconst loadwavfmt_t *format;\n\tif( COM_CheckStringEmpty( fileext ))\n\t{\n\t\tfor( format = sound.loadformats; format && format->ext; format++ )\n\t\t{\n\t\t\tif( !Q_stricmp( format->ext, fileext ))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n"
  },
  {
    "path": "engine/common/soundlib/soundlib.h",
    "content": "/*\nsoundlib.h - engine sound lib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SOUNDLIB_H\n#define SOUNDLIB_H\n\n#include \"common.h\"\n\n#define FRAME_SIZE\t\t32768\t// must match with mp3 frame size\n#define OUTBUF_SIZE\t\t8192\t// don't change!\n\ntypedef struct loadwavfmt_s\n{\n\tconst char *ext;\n\tqboolean (*loadfunc)( const char *name, const byte *buffer, fs_offset_t filesize );\n} loadwavfmt_t;\n\ntypedef struct streamfmt_s\n{\n\tconst char *ext;\n\n\tstream_t *(*openfunc)( const char *filename );\n\tint (*readfunc)( stream_t *stream, int bytes, void *buffer );\n\tint (*setposfunc)( stream_t *stream, int newpos );\n\tint (*getposfunc)( stream_t *stream );\n\tvoid (*freefunc)( stream_t *stream );\n} streamfmt_t;\n\ntypedef struct sndlib_s\n{\n\tconst loadwavfmt_t\t*loadformats;\n\tconst streamfmt_t\t*streamformat;\t// music stream\n\n\t// current sound state\n\tint\t\ttype;\t\t// sound type\n\tint\t\trate;\t\t// num samples per second (e.g. 11025 - 11 khz)\n\tint\t\twidth;\t\t// resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2)\n\tint\t\tchannels;\t\t// num channels (1 - mono, 2 - stereo)\n\tuint\t\tloopstart;\t// start looping from\n\tuint\t\tsamples;\t\t// total samplecount in sound\n\tuint\t\tflags;\t\t// additional sound flags\n\tsize_t\t\tsize;\t\t// sound unpacked size (for bounds checking)\n\tbyte\t\t*wav;\t\t// sound pointer (see sound_type for details)\n\n\tbyte\t\t*tempbuffer;\t// for convert operations\n\tint\t\tcmd_flags;\n} sndlib_t;\n\nstruct stream_s\n{\n\tconst streamfmt_t\t*format;\t// streamformat to operate\n\n\t// stream info\n\tfile_t\t\t*file;\t// stream file\n\tint\t\twidth;\t// resolution - num bits divided by 8 (8 bit is 1, 16 bit is 2)\n\tint\t\trate;\t// stream rate\n\tint\t\tchannels;\t// stream channels\n\tint\t\ttype;\t// wavtype\n\tsize_t\t\tsize;\t// total stream size\n\n\t// current stream state\n\tvoid\t\t*ptr;\t// internal decoder state\n\tchar\t\ttemp[OUTBUF_SIZE]; // mpeg decoder stuff\n\tsize_t\t\tpos;\t// actual track position (or actual buffer remains)\n\tint\t\tbuffsize;\t// cached buffer size\n};\n\n/*\n========================================================================\n\n.WAV sound format\n\n========================================================================\n*/\n\n#define RIFFHEADER\t\t(('F'<<24)+('F'<<16)+('I'<<8)+'R') // little-endian \"RIFF\"\n#define WAVEHEADER\t\t(('E'<<24)+('V'<<16)+('A'<<8)+'W') // little-endian \"WAVE\"\n#define FORMHEADER\t\t((' '<<24)+('t'<<16)+('m'<<8)+'f') // little-endian \"fmt \"\n#define DATAHEADER\t\t(('a'<<24)+('t'<<16)+('a'<<8)+'d') // little-endian \"data\"\n\ntypedef struct\n{\n\tint32_t\triff_id;\t\t// 'RIFF'\n\tint32_t\trLen;\n\tint32_t\twave_id;\t\t// 'WAVE'\n\tint32_t\tfmt_id;\t\t// 'fmt '\n\tint32_t\tpcm_header_len;\t// varies...\n\tint16_t\twFormatTag;\n\tint16_t\tnChannels;\t// 1,2 for stereo data is (l,r) pairs\n\tint32_t\tnSamplesPerSec;\n\tint32_t\tnAvgBytesPerSec;\n\tint16_t\tnBlockAlign;\n\tint16_t\tnBitsPerSample;\n} wavehdr_t;\n\ntypedef struct\n{\n\tint32_t\tdata_id;\t\t// 'data' or 'fact'\n\tint32_t\tdLen;\n} chunkhdr_t;\n\nextern sndlib_t sound;\n//\n// formats load\n//\nqboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t filesize );\nqboolean Sound_LoadOggOpus( const char *name, const byte *buffer, fs_offset_t filesize );\n\n//\n// stream operate\n//\nstream_t *Stream_OpenWAV( const char *filename );\nint Stream_ReadWAV( stream_t *stream, int bytes, void *buffer );\nint Stream_SetPosWAV( stream_t *stream, int newpos );\nint Stream_GetPosWAV( stream_t *stream );\nvoid Stream_FreeWAV( stream_t *stream );\nstream_t *Stream_OpenMPG( const char *filename );\nint Stream_ReadMPG( stream_t *stream, int bytes, void *buffer );\nint Stream_SetPosMPG( stream_t *stream, int newpos );\nint Stream_GetPosMPG( stream_t *stream );\nvoid Stream_FreeMPG( stream_t *stream );\nstream_t *Stream_OpenOggVorbis( const char *filename );\nint Stream_ReadOggVorbis( stream_t *stream, int bytes, void *buffer );\nint Stream_SetPosOggVorbis( stream_t *stream, int newpos );\nint Stream_GetPosOggVorbis( stream_t *stream );\nvoid Stream_FreeOggVorbis( stream_t *stream );\nstream_t *Stream_OpenOggOpus( const char *filename );\nint Stream_ReadOggOpus( stream_t *stream, int bytes, void *buffer );\nint Stream_SetPosOggOpus( stream_t *stream, int newpos );\nint Stream_GetPosOggOpus( stream_t *stream );\nvoid Stream_FreeOggOpus( stream_t *stream );\n\n#endif//SOUNDLIB_H\n"
  },
  {
    "path": "engine/common/sounds.c",
    "content": "/*\nsounds.c - sounds.lst parser\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n\nenum soundlst_type_e\n{\n\tSoundList_None,\n\tSoundList_Range,\n\tSoundList_List\n};\n\nstatic const char *const soundlst_groups[SoundList_Groups] =\n{\n\t\"BouncePlayerShell\",\n\t\"BounceWeaponShell\",\n\t\"BounceConcrete\",\n\t\"BounceGlass\",\n\t\"BounceMetal\",\n\t\"BounceFlesh\",\n\t\"BounceWood\",\n\t\"Ricochet\",\n\t\"Explode\",\n\t\"PlayerWaterEnter\",\n\t\"PlayerWaterExit\",\n\t\"EntityWaterEnter\",\n\t\"EntityWaterExit\",\n};\n\ntypedef struct soundlst_s\n{\n\tenum soundlst_type_e type;\n\tchar *snd;\n\tint min; // the string length if type is group\n\tint max; // the string count if type is group\n} soundlst_t;\n\nstatic soundlst_t soundlst[SoundList_Groups];\n\nstatic void SoundList_Print_f( void );\nstatic void SoundList_Free( soundlst_t *lst )\n{\n\tif( lst->snd )\n\t\tMem_Free( lst->snd );\n\n\tmemset( lst, 0, sizeof( *lst ));\n}\n\nvoid SoundList_Shutdown( void )\n{\n\tint i;\n\n\tfor( i = 0; i < SoundList_Groups; i++ )\n\t\tSoundList_Free( &soundlst[i] );\n}\n\nint SoundList_Count( enum soundlst_group_e group )\n{\n\tsoundlst_t *lst = &soundlst[group];\n\n\tswitch( lst->type )\n\t{\n\tcase SoundList_Range:\n\t\treturn lst->max - lst->min + 1;\n\tcase SoundList_List:\n\t\treturn lst->max;\n\t}\n\n\treturn 0;\n}\n\nconst char *SoundList_Get( enum soundlst_group_e group, int i )\n{\n\tstatic string temp;\n\tsoundlst_t *lst = &soundlst[group];\n\n\tif( i < 0 || i >= SoundList_Count( group ))\n\t\treturn NULL;\n\n\tswitch( lst->type )\n\t{\n\tcase SoundList_Range:\n\t\tQ_snprintf( temp, sizeof( temp ), lst->snd, lst->min + i );\n\t\treturn temp;\n\tcase SoundList_List:\n\t\treturn &lst->snd[i * lst->min];\n\t}\n\n\treturn NULL;\n}\n\nconst char *SoundList_GetRandom( enum soundlst_group_e group )\n{\n\tint count = SoundList_Count( group );\n\tint idx = COM_RandomLong( 0, count - 1 );\n\n\t// Con_Printf( \"%s: %s %d %d\\n\", __func__, soundlst_groups[group], count, idx );\n\n\treturn SoundList_Get( group, idx );\n}\n\nstatic qboolean SoundList_ParseGroup( soundlst_t *lst, char **file )\n{\n\tstring token;\n\tint count = 0, slen = 0, i;\n\tchar *p;\n\n\tp = *file;\n\n\twhile(( p = COM_ParseFile( p, token, sizeof( token ))))\n\t{\n\t\tint len;\n\n\t\tif( !Q_strcmp( token, \"}\" ))\n\t\t\tbreak;\n\n\t\tif( !Q_strcmp( token, \"{\" ))\n\t\t{\n\t\t\tCon_Printf( \"%s: expected '}' but got '{' during group list parse\\n\", __func__ );\n\t\t\treturn false;\n\t\t}\n\t\telse if( !COM_CheckStringEmpty( token ))\n\t\t{\n\t\t\tCon_Printf( \"%s: expected '}' but got EOF during group list parse\\n\", __func__ );\n\t\t\treturn false;\n\t\t}\n\n\t\tlen = Q_strlen( token ) + 1;\n\t\tif( slen < len )\n\t\t\tslen = len;\n\n\t\tcount++;\n\t}\n\n\tif( count == 0 ) // deactivate group\n\t{\n\t\tlst->type = SoundList_None;\n\t\tlst->snd = NULL;\n\t\tlst->min = lst->max = 0;\n\t\treturn true;\n\t}\n\n\tlst->type = SoundList_List;\n\tlst->min = slen;\n\tlst->max = count;\n\tlst->snd = Mem_Malloc( host.mempool, count * slen ); // allocate single buffer for the whole group\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\t*file = COM_ParseFile( *file, token, sizeof( token ));\n\n\t\tQ_strncpy( &lst->snd[i * slen], token, slen );\n\t}\n\n\treturn true;\n}\n\nstatic qboolean SoundList_ParseRange( soundlst_t *lst, char **file )\n{\n\tstring token, snd;\n\tchar *p;\n\tint i = 0;\n\n\tlst->type = SoundList_Range;\n\t*file = COM_ParseFile( *file, snd, sizeof( snd ));\n\n\t// validate format string, count all % characters\n\tp = snd;\n\ti = 0;\n\twhile(( p = Q_strchr( p, '%' )))\n\t{\n\t\t// only decimal\n\t\tif( p[1] != 'd' && p[1] != 'i' && p[1] != 'u' )\n\t\t{\n\t\t\tCon_Printf( \"%s: invalid range string %s, only decimal numbers are allowed\", __func__, snd );\n\t\t\treturn false;\n\t\t}\n\n\t\ti++;\n\t\tp++;\n\t}\n\n\t// if more than one %, then it's an invalid format string\n\tif( i != 1 )\n\t{\n\t\tCon_Printf( \"%s: invalid range string %s, only single specifier is allowed\\n\", __func__, snd );\n\t\treturn false;\n\t}\n\n\t*file = COM_ParseFile( *file, token, sizeof( token ));\n\tif( !Q_isdigit( token ))\n\t{\n\t\tCon_Printf( \"%s: %s must be a digit\\n\", __func__, token );\n\t\treturn false;\n\t}\n\tlst->min = Q_atoi( token );\n\n\t*file = COM_ParseFile( *file, token, sizeof( token ));\n\tif( !Q_isdigit( token ))\n\t{\n\t\tCon_Printf( \"%s: %s must be a digit\\n\", __func__, token );\n\t\treturn false;\n\t}\n\tlst->max = Q_atoi( token );\n\tlst->snd = copystring( snd );\n\n\treturn true;\n}\n\nstatic qboolean SoundList_Parse( char *file )\n{\n\tstring token;\n\tint i;\n\n\twhile(( file = COM_ParseFile( file, token, sizeof( token ))))\n\t{\n\t\tsoundlst_t *lst = NULL;\n\t\tchar *p;\n\n\t\tfor( i = 0; i < SoundList_Groups; i++ )\n\t\t{\n\t\t\tif( !Q_strcmp( token, soundlst_groups[i] ))\n\t\t\t{\n\t\t\t\tlst = &soundlst[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !lst )\n\t\t{\n\t\t\tCon_Printf( \"%s: unexpected token %s, must be group name\\n\", __func__, token );\n\t\t\tgoto cleanup;\n\t\t}\n\n\t\tp = COM_ParseFile( file, token, sizeof( token ));\n\n\t\t// group is a range\n\t\tif( !Q_strcmp( token, \"{\" ))\n\t\t{\n\t\t\tfile = p; // advance pointer\n\n\t\t\tif( !SoundList_ParseGroup( lst, &file ))\n\t\t\t\tgoto cleanup;\n\n\t\t\tfile = COM_ParseFile( file, token, sizeof( token ));\n\t\t\tif( Q_strcmp( token, \"}\" ))\n\t\t\t{\n\t\t\t\tCon_Printf( \"%s: unexpected token %s, must be }\\n\", __func__, token );\n\t\t\t\tgoto cleanup;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// ranges are more simple, but need to rewind pointer for them\n\t\t\tif( !SoundList_ParseRange( lst, &file ))\n\t\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\tif( host_developer.value >= 2 )\n\t\tSoundList_Print_f();\n\n\treturn true;\n\ncleanup:\n\tSoundList_Shutdown();\n\treturn false;\n}\n\nstatic void SoundList_Print_f( void )\n{\n\tint i;\n\n\tfor( i = 0; i < SoundList_Groups; i++ )\n\t{\n\t\tsoundlst_t *lst = &soundlst[i];\n\n\t\tswitch( lst->type )\n\t\t{\n\t\tcase SoundList_Range:\n\t\t\tCon_Reportf( \"%-16s\\t\" S_CYAN \"Range\" S_DEFAULT \" %s [%d; %d]\\n\",\n\t\t\t\tsoundlst_groups[i], lst->snd, lst->min, lst->max );\n\t\t\tbreak;\n\t\tcase SoundList_List:\n\t\t{\n\t\t\tint j;\n\n\t\t\tCon_Reportf( \"%-16s\\t\" S_MAGENTA \"List\" S_DEFAULT \" [\", soundlst_groups[i] );\n\t\t\tfor( j = 0; j < lst->max; j++ )\n\t\t\t\tCon_Reportf( \"%s%s\", &lst->snd[j * lst->min], j + 1 == lst->max ? \"\" : \", \" );\n\t\t\tCon_Reportf( \"]\\n\" );\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tCon_Reportf( \"%-16s\\t\" S_RED \"inactive\" S_DEFAULT \"\\n\", soundlst_groups[i] );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// I wish we had #embed already\nstatic const char default_sounds_lst[] =\n\"BouncePlayerShell \\\"player/pl_shell%d.wav\\\" 1 3\\n\"\n\"BounceWeaponShell \\\"weapons/sshell%d.wav\\\" 1 3\\n\"\n\"BounceConcrete \\\"debris/concrete%d.wav\\\" 1 3\\n\"\n\"BounceGlass \\\"debris/glass%d.wav\\\" 1 4\\n\"\n\"BounceMetal \\\"debris/metal%d.wav\\\" 1 6\\n\"\n\"BounceFlesh \\\"debris/flesh%d.wav\\\" 1 7\\n\"\n\"BounceWood \\\"debris/wood%d.wav\\\" 1 4\\n\"\n\"Ricochet \\\"weapons/ric%d.wav\\\" 1 5\\n\"\n\"Explode \\\"weapons/explode%d.wav\\\" 3 5\\n\"\n\"EntityWaterEnter \\\"player/pl_wade%d.wav\\\" 1 4\\n\"\n\"EntityWaterExit \\\"player/pl_wade%d.wav\\\" 1 4\\n\"\n\"PlayerWaterEnter\\n\"\n\"{\\n\"\n\"\t\\\"player/pl_wade1.wav\\\"\\n\"\n\"}\\n\"\n\"\\n\"\n\"PlayerWaterExit\\n\"\n\"{\\n\"\n\"\t\\\"player/pl_wade2.wav\\\"\\n\"\n\"}\\n\";\n\nstatic void SoundList_Reload_f( void )\n{\n\tqboolean load_internal = false;\n\tchar *pfile = FS_LoadFile( \"scripts/sounds.lst\", NULL, false );\n\n\tif( pfile )\n\t{\n\t\tif( !SoundList_Parse( pfile ))\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"can't parse sounds.lst file, loading internal...\\n\" );\n\t\t\tload_internal = true;\n\t\t}\n\n\t\tMem_Free( pfile );\n\t}\n\telse load_internal = true;\n\n\tif( load_internal )\n\t\tSoundList_Parse( (char *)default_sounds_lst );\n}\n\nvoid SoundList_Init( void )\n{\n\tCmd_AddRestrictedCommand( \"host_soundlist_print\", SoundList_Print_f, \"print current sound list\" );\n\tCmd_AddRestrictedCommand( \"host_soundlist_reload\", SoundList_Reload_f, \"reload sound list\" );\n\n\tSoundList_Reload_f ();\n}\n"
  },
  {
    "path": "engine/common/sys_con.c",
    "content": "/*\nsys_con.c - stdout and log\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#if XASH_ANDROID\n#include <android/log.h>\n#endif\n#include <string.h>\n#include <errno.h>\n#if XASH_IRIX\n#include <sys/time.h>\n#endif\n#include \"xash3d_mathlib.h\"\n\n// do not waste precious CPU cycles on mobiles or low memory devices\n#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY && !XASH_EMSCRIPTEN\n#define XASH_COLORIZE_CONSOLE 1\n#else\n#define XASH_COLORIZE_CONSOLE 0\n#endif\n\nstatic struct logdata_s {\n\tchar     title[64];\n\tqboolean log_active;\n\tqboolean log_time;\n\tchar     log_path[MAX_SYSPATH];\n\tFILE     *logfile;\n\tint      logfileno;\n} s_ld;\n\nvoid Sys_DestroyConsole( void )\n{\n\t// last text message into console or log\n\tCon_Reportf( \"%s: Exiting!\\n\", __func__ );\n#if XASH_WIN32\n\tWcon_DestroyConsole();\n#endif\n}\n\n/*\n===============================================================================\n\nSYSTEM LOG\n\n===============================================================================\n*/\nint Sys_LogFileNo( void )\n{\n\treturn s_ld.logfileno;\n}\n\nstatic void Sys_FlushStdout( void )\n{\n\t// never printing anything to stdout on mobiles\n#if !XASH_MOBILE_PLATFORM\n\tfflush( stdout );\n#endif\n}\n\nstatic void Sys_FlushLogfile( void )\n{\n\tif( s_ld.logfile )\n\t\tfflush( s_ld.logfile );\n}\n\nvoid Sys_InitLog( void )\n{\n\tconst char *mode;\n\n\tif( Sys_CheckParm( \"-log\" ))\n\t{\n\t\tif( !Sys_GetParmFromCmdLine( \"-log\", s_ld.log_path ) || !isalnum( s_ld.log_path[0] ))\n\t\t\tQ_strncpy( s_ld.log_path, \"engine.log\", sizeof( s_ld.log_path ));\n\n\t\tCOM_DefaultExtension( s_ld.log_path, \".log\", sizeof( s_ld.log_path ));\n\t\ts_ld.log_active = true;\n\t}\n\n\ts_ld.log_time = Sys_CheckParm( \"-logtime\" );\n\n\tif( host.change_game && host.type != HOST_DEDICATED )\n\t\tmode = \"a\";\n\telse mode = \"w\";\n\n\tif( Host_IsDedicated( ))\n\t\tQ_strncpy( s_ld.title, XASH_DEDICATED_SERVER_NAME \" \" XASH_VERSION, sizeof( s_ld.title ));\n\telse Q_strncpy( s_ld.title, XASH_ENGINE_NAME \" \" XASH_VERSION, sizeof( s_ld.title ));\n\n\t// create log if needed\n\tif( s_ld.log_active )\n\t{\n\t\ts_ld.logfile = fopen( s_ld.log_path, mode );\n\n\t\tif ( !s_ld.logfile )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: can't create log file %s: %s\\n\", __func__, s_ld.log_path, strerror( errno ));\n\t\t\treturn;\n\t\t}\n\n\t\ts_ld.logfileno = fileno( s_ld.logfile );\n\n\t\t// fit to 80 columns for easier read on standard terminal\n\t\tfputs( \"================================================================================\\n\", s_ld.logfile );\n\t\tfprintf( s_ld.logfile, \"%s (%i, %s, %s, %s-%s)\\n\", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());\n\t\tfprintf( s_ld.logfile, \"Game started at %s\\n\", Q_timestamp( TIME_FULL ));\n\t\tfputs( \"================================================================================\\n\", s_ld.logfile );\n\t\tfflush( s_ld.logfile );\n\t}\n}\n\nvoid Sys_CloseLog( const char *finalmsg )\n{\n\tSys_FlushStdout(); // flush to stdout to ensure all data was written\n\n\tif( !s_ld.logfile )\n\t\treturn;\n\n\t// continue logged\n\tif( !finalmsg )\n\t{\n\t\tswitch( host.status )\n\t\t{\n\t\tcase HOST_CRASHED:\n\t\t\tfinalmsg = \"crashed\";\n\t\t\tbreak;\n\t\tcase HOST_ERR_FATAL:\n\t\t\tfinalmsg = \"stopped with error\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfinalmsg = \"stopped\";\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfputc( '\\n', s_ld.logfile );\n\tfputs( \"================================================================================\\n\", s_ld.logfile );\n\tfprintf( s_ld.logfile, \"%s (%i, %s, %s, %s-%s)\\n\", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());\n\tfprintf( s_ld.logfile, \"Stopped with reason \\\"%s\\\" at %s\\n\", finalmsg, Q_timestamp( TIME_FULL ));\n\tfputs( \"================================================================================\\n\", s_ld.logfile );\n\tfclose( s_ld.logfile );\n\ts_ld.logfile = NULL;\n}\n\n#if XASH_COLORIZE_CONSOLE\nstatic qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c )\n{\n\tstatic const char *q3ToAnsi[ 8 ] =\n\t{\n\t\t\"\\033[1;30m\", // COLOR_BLACK\n\t\t\"\\033[1;31m\", // COLOR_RED\n\t\t\"\\033[1;32m\", // COLOR_GREEN\n\t\t\"\\033[1;33m\", // COLOR_YELLOW\n\t\t\"\\033[1;34m\", // COLOR_BLUE\n\t\t\"\\033[1;36m\", // COLOR_CYAN\n\t\t\"\\033[1;35m\", // COLOR_MAGENTA\n\t\t\"\\033[0m\", // COLOR_WHITE\n\t};\n\tconst char *esc = q3ToAnsi[c];\n\n\treturn write( fd, esc, c == 7 ? 4 : 7 ) < 0 ? false : true;\n}\n#else\nstatic qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c )\n{\n\treturn true;\n}\n#endif\n\nstatic void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_len, const char *msg, const int colorize )\n{\n\tconst char *p = msg;\n\n\tif( logtime_len != 0 )\n\t{\n\t\tif( write( fd, logtime, logtime_len ) < 0 )\n\t\t{\n\t\t\t// not critical for us\n\t\t}\n\t}\n\n\twhile( p && *p )\n\t{\n\t\tp = Q_strchr( msg, '^' );\n\n\t\tif( p == NULL )\n\t\t{\n\t\t\tif( write( fd, msg, Q_strlen( msg )) < 0 )\n\t\t\t{\n\t\t\t\t// don't call engine Msg, might cause recursion\n\t\t\t\tfprintf( stderr, \"%s: write failed: %s\\n\", __func__, strerror( errno ));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\telse if( IsColorString( p ))\n\t\t{\n\t\t\tif( p != msg )\n\t\t\t{\n\t\t\t\tif( write( fd, msg, p - msg ) < 0 )\n\t\t\t\t\tfprintf( stderr, \"%s: write failed: %s\\n\", __func__, strerror( errno ));\n\t\t\t}\n\t\t\tmsg = p + 2;\n\n\t\t\tif( colorize )\n\t\t\t\tSys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( write( fd, msg, p - msg + 1 ) < 0 )\n\t\t\t\tfprintf( stderr, \"%s: write failed: %s\\n\", __func__, strerror( errno ));\n\t\t\tmsg = p + 1;\n\t\t}\n\t}\n\n\t// flush the color\n\tif( colorize )\n\t\tSys_WriteEscapeSequenceForColorcode( fd, 7 );\n}\n\nstatic void Sys_PrintStdout( const char *logtime, size_t logtime_len, const char *msg )\n{\n#if XASH_MOBILE_PLATFORM\n\tstatic char buf[MAX_PRINT_MSG];\n\n\t// strip color codes\n\tCOM_StripColors( msg, buf );\n\n\t// platform-specific output\n#if XASH_ANDROID && !XASH_DEDICATED\n\t__android_log_write( ANDROID_LOG_INFO, \"Xash\", buf );\n#endif // XASH_ANDROID && !XASH_DEDICATED\n\n#if TARGET_OS_IOS\n\tvoid IOS_Log( const char * );\n\tIOS_Log( buf );\n#endif // TARGET_OS_IOS\n\n#if XASH_NSWITCH && NSWITCH_DEBUG\n\t// just spew it to stderr normally in debug mode\n\tfprintf( stderr, \"%s %s\", logtime, buf );\n#endif // XASH_NSWITCH && NSWITCH_DEBUG\n\n#if XASH_PSVITA\n\t// spew to stderr only in developer mode\n\tif( host_developer.value )\n\t{\n\t\tfprintf( stderr, \"%s %s\", logtime, buf );\n\t}\n#endif\n\n#elif !XASH_WIN32 // Wcon does the job\n\tSys_PrintLogfile( STDOUT_FILENO, logtime, logtime_len, msg, XASH_COLORIZE_CONSOLE );\n\tSys_FlushStdout();\n#endif\n}\n\nvoid Sys_PrintLog( const char *pMsg )\n{\n\ttime_t crt_time;\n\tconst struct tm\t*crt_tm;\n\tchar logtime[32] = \"\";\n\tstatic char lastchar;\n\tqboolean print_time = false;\n\tsize_t len, logtime_len = 0;\n\n\tif( !lastchar || lastchar == '\\n' )\n\t{\n\t\tif( time( &crt_time ) >= 0 )\n\t\t{\n\t\t\tcrt_tm = localtime( &crt_time );\n\t\t\tprint_time = crt_tm != NULL;\n\t\t}\n\t}\n\n\tif( print_time )\n\t{\n\t\tlogtime_len = strftime( logtime, sizeof( logtime ), \"[%H:%M:%S] \", crt_tm ); // short time\n\t\tlogtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case\n\t}\n\n\t// spew to stdout\n\tSys_PrintStdout( logtime, logtime_len, pMsg );\n\n\tlen = Q_strlen( pMsg );\n\n\t// save last char to detect when line was not ended\n\tlastchar = len > 0 ? pMsg[len - 1] : 0;\n\n\t// spew to engine.log\n\tif( s_ld.logfile )\n\t{\n\t\tif( s_ld.log_time && print_time )\n\t\t{\n\t\t\tlogtime_len = strftime( logtime, sizeof( logtime ), \"[%Y:%m:%d|%H:%M:%S] \", crt_tm ); //full time\n\t\t\tlogtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlogtime[0] = '\\0';\n\t\t\tlogtime_len = 0;\n\t\t}\n\n\t\tSys_PrintLogfile( s_ld.logfileno, logtime, logtime_len, pMsg, false );\n\t\tSys_FlushLogfile();\n\t}\n}\n\n/*\n=============================================================================\n\nCONSOLE PRINT\n\n=============================================================================\n*/\nstatic void Con_Printfv( qboolean debug, const char *szFmt, va_list args )\n{\n\tstatic char buffer[MAX_PRINT_MSG];\n\tqboolean add_newline;\n\n\tadd_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0;\n\n\tif( debug && !Q_strcmp( buffer, \"0\\n\" ))\n\t\treturn; // hlrally spam\n\n\tSys_Print( buffer );\n\tif( add_newline )\n\t\tSys_Print( \"\\n\" );\n}\n\n/*\n=============\nCon_Printf\n\n=============\n*/\nvoid GAME_EXPORT Con_Printf( const char *szFmt, ... )\n{\n\tva_list args;\n\n\tif( !host.allow_console )\n\t\treturn;\n\n\tva_start( args, szFmt );\n\tCon_Printfv( false, szFmt, args );\n\tva_end( args );\n}\n\n/*\n=============\nCon_DPrintf\n\n=============\n*/\nvoid GAME_EXPORT Con_DPrintf( const char *szFmt, ... )\n{\n\tva_list args;\n\n\tif( host_developer.value < DEV_NORMAL )\n\t\treturn;\n\n\tva_start( args, szFmt );\n\tCon_Printfv( true, szFmt, args );\n\tva_end( args );\n}\n\n/*\n=============\nCon_Reportf\n\n=============\n*/\nvoid Con_Reportf( const char *szFmt, ... )\n{\n\tva_list args;\n\n\tif( host_developer.value < DEV_EXTENDED )\n\t\treturn;\n\n\tva_start( args, szFmt );\n\tCon_Printfv( false, szFmt, args );\n\tva_end( args );\n}\n\n\n#if XASH_MESSAGEBOX == MSGBOX_STDERR\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )\n{\n\tfprintf( stderr,\n\t\t \"======================================\\n\"\n\t\t \"%s: %s\\n\"\n\t\t \"======================================\\n\", title, message );\n}\n#endif\n\n"
  },
  {
    "path": "engine/common/system.c",
    "content": "/*\nsys_win.c - platform dependent code (which haven't moved to platform dir yet)\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n#include \"platform/platform.h\"\n#include <stdlib.h>\n#include <errno.h>\n\n#if _MSC_VER\n#include <intrin.h>\n#endif\n\n#if XASH_SDL == 2\n#include <SDL.h>\n#elif XASH_SDL == 3\n#include <SDL3/SDL.h>\n#endif\n\n#if XASH_POSIX\n#include <unistd.h>\n#include <signal.h>\n\n#if !XASH_ANDROID\n#include <pwd.h>\n#endif\n#endif\n\n#if XASH_WIN32\n#include <process.h>\n#endif\n\n#if XASH_NSWITCH\n#include <switch.h>\n#endif\n\n#if XASH_PSVITA\n#include <vitasdk.h>\n#endif\n\n#include \"menu_int.h\" // _UPDATE_PAGE macro\n\n#include \"library.h\"\n#include \"whereami.h\"\n\nint error_on_exit = 0;\t// arg for exit();\n\n/*\n================\nSys_DoubleTime\n================\n*/\ndouble GAME_EXPORT Sys_DoubleTime( void )\n{\n\treturn Platform_DoubleTime();\n}\n\n/*\n================\nSys_DebugBreak\n================\n*/\nvoid Sys_DebugBreak( void )\n{\n\tqboolean was_grabbed = false;\n\n\tif( !Sys_DebuggerPresent( ))\n\t\treturn;\n\n\tif( host.hWnd ) // so annoying\n\t{\n\t\twas_grabbed = Platform_GetMouseGrab();\n\t\tPlatform_SetMouseGrab( false );\n\t}\n\n#if _MSC_VER\n\t__debugbreak();\n#else // !_MSC_VER\n\tINLINE_RAISE( SIGINT );\n\tINLINE_NANOSLEEP1(); // sometimes signal comes with delay, let it interrupt nanosleep\n#endif // !_MSC_VER\n\n\tif( was_grabbed )\n\t\tPlatform_SetMouseGrab( true );\n}\n\n#if !XASH_DEDICATED\n/*\n================\nSys_GetClipboardData\n\ncreate buffer, that contain clipboard\n================\n*/\nchar *Sys_GetClipboardData( void )\n{\n\tstatic char data[1024];\n\n\tdata[0] = '\\0';\n\n\tPlatform_GetClipboardText( data, sizeof( data ));\n\n\treturn data;\n}\n#endif // XASH_DEDICATED\n\n/*\n================\nSys_GetCurrentUser\n\nreturns username for current profile\n================\n*/\nconst char *Sys_GetCurrentUser( void )\n{\n\t// TODO: move to platform\n#if XASH_WIN32\n\tstatic wchar_t sw_userName[MAX_STRING];\n\tDWORD size = ARRAYSIZE( sw_userName );\n\n\tif( GetUserNameW( sw_userName, &size ) && sw_userName[0] != 0 )\n\t{\n\t\tstatic char s_userName[MAX_STRING * 4];\n\n\t\t// set length to -1, so it will null terminate\n\t\tWideCharToMultiByte( CP_UTF8, 0, sw_userName, -1, s_userName, sizeof( s_userName ), NULL, NULL );\n\t\treturn s_userName;\n\t}\n#elif XASH_PSVITA\n\tstatic string username;\n\tsceAppUtilSystemParamGetString( SCE_SYSTEM_PARAM_ID_USERNAME, username, sizeof( username ) - 1 );\n\tif( COM_CheckStringEmpty( username ))\n\t\treturn username;\n#elif XASH_POSIX && !XASH_ANDROID && !XASH_NSWITCH\n\tstatic string username;\n\tstruct passwd *pw = getpwuid( geteuid( ));\n\n\t// POSIX standard says pw _might_ point to static area, so let's make a copy\n\tif( pw && COM_CheckString( pw->pw_name ))\n\t{\n\t\tQ_strncpy( username, pw->pw_name, sizeof( username ));\n\t\treturn username;\n\t}\n#endif\n\treturn \"Player\";\n}\n\n/*\n==================\nSys_ParseCommandLine\n\n==================\n*/\nvoid Sys_ParseCommandLine( int argc, char** argv )\n{\n\tconst char\t*blank = \"censored\";\n\tint\t\ti;\n\n\thost.argc = argc;\n\thost.argv = argv;\n\n\tif( !host.change_game ) return;\n\n\tfor( i = 0; i < host.argc; i++ )\n\t{\n\t\t// we don't want to return to first game\n\t\t\t if( !Q_stricmp( \"-game\", host.argv[i] )) host.argv[i] = (char *)blank;\n\t\t// probably it's timewaster, because engine rejected second change\n\t\telse if( !Q_stricmp( \"+game\", host.argv[i] )) host.argv[i] = (char *)blank;\n\t\t// you sure that map exists in new game?\n\t\telse if( !Q_stricmp( \"+map\", host.argv[i] )) host.argv[i] = (char *)blank;\n\t\t// just stupid action\n\t\telse if( !Q_stricmp( \"+load\", host.argv[i] )) host.argv[i] = (char *)blank;\n\t\t// changelevel beetwen games? wow it's great idea!\n\t\telse if( !Q_stricmp( \"+changelevel\", host.argv[i] )) host.argv[i] = (char *)blank;\n\t}\n}\n\n/*\n================\nSys_CheckParm\n\nReturns the position (1 to argc-1) in the program's argument list\nwhere the given parameter apears, or 0 if not present\n================\n*/\nint Sys_CheckParm( const char *parm )\n{\n\tint\ti;\n\n\tfor( i = 1; i < host.argc; i++ )\n\t{\n\t\tif( !host.argv[i] )\n\t\t\tcontinue;\n\n\t\tif( !Q_stricmp( parm, host.argv[i] ))\n\t\t\treturn i;\n\t}\n\treturn 0;\n}\n\n/*\n================\nSys_GetParmFromCmdLine\n\nReturns the argument for specified parm\n================\n*/\nqboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size )\n{\n\tint\targc = Sys_CheckParm( parm );\n\n\tif( !argc || !out || !host.argv[argc + 1] )\n\t\treturn false;\n\n\tQ_strncpy( out, host.argv[argc+1], size );\n\n\treturn true;\n}\n\nqboolean Sys_GetIntFromCmdLine( const char* argName, int *out )\n{\n\tint argIndex = Sys_CheckParm( argName );\n\n\tif( argIndex < 1 || argIndex + 1 >= host.argc || !host.argv[argIndex + 1] )\n\t{\n\t\t*out = 0;\n\t\treturn false;\n\t}\n\n\t*out = Q_atoi( host.argv[argIndex + 1] );\n\treturn true;\n}\n\n//=======================================================================\n//\t\t\tDLL'S MANAGER SYSTEM\n//=======================================================================\nqboolean Sys_LoadLibrary( dll_info_t *dll )\n{\n\tsize_t i;\n\tstring\t\terrorstring;\n\n\t// check errors\n\tif( !dll ) return false;\t// invalid desc\n\tif( dll->link ) return true;\t// already loaded\n\n\tif( !dll->name || !*dll->name )\n\t\treturn false; // nothing to load\n\n\tCon_Reportf( \"%s: Loading %s\", __func__, dll->name );\n\n\tif( dll->fcts ) // lookup export table\n\t\tClearExports( dll->fcts, dll->num_fcts );\n\n\tif( !dll->link )\n\t\tdll->link = COM_LoadLibrary( dll->name, false, true ); // environment pathes\n\n\t// no DLL found\n\tif( !dll->link )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"Sys_LoadLibrary: couldn't load %s\\n\", dll->name );\n\t\tgoto error;\n\t}\n\n\t// Get the function adresses\n\tfor( i = 0; i < dll->num_fcts; i++ )\n\t{\n\t\tconst dllfunc_t *func = &dll->fcts[i];\n\t\tif( !( *func->func = COM_GetProcAddress( dll->link, func->name )))\n\t\t{\n\t\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"Sys_LoadLibrary: %s missing or invalid function (%s)\\n\", dll->name, func->name );\n\t\t\tgoto error;\n\t\t}\n\t}\n\tCon_Reportf( \" - ok\\n\" );\n\n\treturn true;\nerror:\n\tCon_Reportf( \" - failed\\n\" );\n\tSys_FreeLibrary( dll ); // trying to free\n\tif( dll->crash ) Sys_Error( \"%s\", errorstring );\n\telse Con_Reportf( S_ERROR \"%s\", errorstring );\n\n\treturn false;\n}\n\nqboolean Sys_FreeLibrary( dll_info_t *dll )\n{\n\t// invalid desc or alredy freed\n\tif( !dll || !dll->link )\n\t\treturn false;\n\n\tif( host.status == HOST_CRASHED )\n\t{\n\t\t// we need to hold down all modules, while MSVC can find error\n\t\tCon_Reportf( \"%s: hold %s for debugging\\n\", __func__, dll->name );\n\t\treturn false;\n\t}\n\telse Con_Reportf( \"%s: Unloading %s\\n\", __func__, dll->name );\n\n\tCOM_FreeLibrary( dll->link );\n\tdll->link = NULL;\n\n\tClearExports( dll->fcts, dll->num_fcts );\n\n\treturn true;\n}\n\n/*\n================\nSys_WaitForQuit\n\nwait for 'Esc' key will be hit\n================\n*/\nstatic void Sys_WaitForQuit( void )\n{\n#if XASH_WIN32\n\tMSG\tmsg;\n\tmsg.message = 0;\n\n\t// wait for the user to quit\n\twhile( msg.message != WM_QUIT )\n\t{\n\t\tif( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))\n\t\t{\n\t\t\tTranslateMessage( &msg );\n\t\t\tDispatchMessage( &msg );\n\t\t}\n\t\telse Platform_Sleep( 20 );\n\t}\n#endif\n}\n\n/*\n================\nSys_Warn\n\nJust messagebox\n================\n*/\nvoid Sys_Warn( const char *format, ... )\n{\n\tva_list\targptr;\n\tchar\ttext[MAX_PRINT_MSG];\n\n\tva_start( argptr, format );\n\tQ_vsnprintf( text, MAX_PRINT_MSG, format, argptr );\n\tva_end( argptr );\n\n\tSys_DebugBreak();\n\n\tMsg( \"%s: %s\\n\", __func__, text );\n\n\tif( !Host_IsDedicated() ) // dedicated server should not hang on messagebox\n\t\tPlatform_MessageBox( \"Xash Warning\", text, true );\n}\n\n/*\n================\nSys_Error\n\nNOTE: we must prepare engine to shutdown\nbefore call this\n================\n*/\nvoid Sys_Error( const char *error, ... )\n{\n\tva_list\targptr;\n\tchar\ttext[MAX_PRINT_MSG];\n\n\t// enable cursor before debugger call\n\tif( !Host_IsDedicated( ))\n\t\tPlatform_SetCursorType( dc_arrow );\n\n\tif( host.status == HOST_ERR_FATAL )\n\t\treturn; // don't multiple executes\n\n\t// make sure that console received last message\n\tif( host.change_game ) Platform_Sleep( 200 );\n\n\terror_on_exit = 1;\n\thost.status = HOST_ERR_FATAL;\n\tva_start( argptr, error );\n\tQ_vsnprintf( text, MAX_PRINT_MSG, error, argptr );\n\tva_end( argptr );\n\n\tSys_DebugBreak();\n\n\tSV_SysError( text );\n\n\tif( !Host_IsDedicated() )\n\t{\n#if XASH_SDL >= 2\n\t\tif( host.hWnd ) SDL_HideWindow( host.hWnd );\n#endif\n#if XASH_WIN32\n\t\tWcon_ShowConsole( false );\n#endif\n\t\tSys_Print( text );\n\t\tPlatform_MessageBox( \"Xash Error\", text, true );\n\t}\n\telse\n\t{\n#if XASH_WIN32\n\t\tWcon_ShowConsole( true );\n\t\tWcon_DisableInput();\t// disable input line for dedicated server\n#endif\n\t\tSys_Print( text );\t// print error message\n\t\tSys_WaitForQuit();\n\t}\n\n\tSys_Quit( \"caught an error\" );\n}\n\n#if XASH_EMSCRIPTEN\n#include <emscripten.h>\n#define exit my_exit\nvoid my_exit( int ret )\n{\n\temscripten_cancel_main_loop();\n\temscripten_force_exit( ret );\n}\n#endif\n\n/*\n================\nSys_Quit\n================\n*/\nvoid Sys_Quit( const char *reason )\n{\n\tHost_ShutdownWithReason( reason );\n#if XASH_ANDROID\n\tHost_ExitInMain();\n#else\n\texit( error_on_exit );\n#endif\n}\n\n/*\n================\nSys_Print\n\nprint into window console\n================\n*/\nvoid Sys_Print( const char *pMsg )\n{\n#if !XASH_DEDICATED\n\tif( !Host_IsDedicated() )\n\t{\n\t\tCon_Print( pMsg );\n\t}\n#endif\n\n#if XASH_WIN32\n\t{\n\t\tconst char\t*msg;\n\t\tstatic char\tbuffer[MAX_PRINT_MSG];\n\t\tstatic char\tlogbuf[MAX_PRINT_MSG];\n\t\tchar\t\t*b = buffer;\n\t\tchar\t\t*c = logbuf;\n\t\tint\t\ti = 0;\n\n\t\t// if the message is REALLY long, use just the last portion of it\n\t\tif( Q_strlen( pMsg ) > sizeof( buffer ) - 1 )\n\t\t\tmsg = pMsg + Q_strlen( pMsg ) - sizeof( buffer ) + 1;\n\t\telse msg = pMsg;\n\n\t\t// copy into an intermediate buffer\n\t\twhile( msg[i] && (( b - buffer ) < sizeof( buffer ) - 1 ))\n\t\t{\n\t\t\tif( msg[i] == '\\n' && msg[i+1] == '\\r' )\n\t\t\t{\n\t\t\t\tb[0] = '\\r';\n\t\t\t\tb[1] = c[0] = '\\n';\n\t\t\t\tb += 2, c++;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\telse if( msg[i] == '\\r' )\n\t\t\t{\n\t\t\t\tb[0] = c[0] = '\\r';\n\t\t\t\tb[1] = '\\n';\n\t\t\t\tb += 2, c++;\n\t\t\t}\n\t\t\telse if( msg[i] == '\\n' )\n\t\t\t{\n\t\t\t\tb[0] = '\\r';\n\t\t\t\tb[1] = c[0] = '\\n';\n\t\t\t\tb += 2, c++;\n\t\t\t}\n\t\t\telse if( msg[i] == '\\35' || msg[i] == '\\36' || msg[i] == '\\37' )\n\t\t\t{\n\t\t\t\ti++; // skip console pseudo graph\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( msg[i] == '\\1' || msg[i] == '\\2' || msg[i] == '\\3' )\n\t\t\t\t\ti++;\n\t\t\t\t*b = *c = msg[i];\n\t\t\t\tb++, c++;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\n\t\t*b = *c = 0; // terminator\n\n\t\tWcon_WinPrint( buffer );\n\t}\n#endif\n\n\tSys_PrintLog( pMsg );\n\n\tRcon_Print( &host.rd, pMsg );\n}\n\n/*\n==================\nSys_ChangeGame\n\nThis is a special function\n\nHere we restart engine with new -game parameter\nbut since engine will be unloaded during this call\nit explicitly doesn't use internal allocation or string copy utils\n==================\n*/\nqboolean Sys_NewInstance( const char *gamedir, const char *finalmsg )\n{\n#if XASH_NSWITCH\n\tchar newargs[4096];\n\tconst char *exe = host.argv[0]; // arg 0 is always the full NRO path\n\n\t// TODO: carry over the old args (assuming you can even pass any)\n\tQ_snprintf( newargs, sizeof( newargs ), \"%s -game %s\", exe, gamedir );\n\t// just restart the entire thing\n\tprintf( \"envSetNextLoad exe: `%s`\\n\", exe );\n\tprintf( \"envSetNextLoad argv:\\n`%s`\\n\", newargs );\n\tHost_ShutdownWithReason( finalmsg );\n\tenvSetNextLoad( exe, newargs );\n\texit( 0 );\n#else\n\tint i = 0;\n\tqboolean replacedArg = false;\n\tsize_t exelen;\n\tchar *exe, **newargs;\n\n\t// don't use engine allocation utils here\n\t// they will be freed after Host_Shutdown\n\tnewargs = calloc( host.argc + 4, sizeof( *newargs ));\n\twhile( i < host.argc )\n\t{\n\t\tnewargs[i] = strdup( host.argv[i] );\n\n\t\t// replace existing -game argument\n\t\tif( !Q_stricmp( newargs[i], \"-game\" ))\n\t\t{\n\t\t\tnewargs[i + 1] = strdup( gamedir );\n\t\t\treplacedArg = true;\n\t\t\ti += 2;\n\t\t}\n\t\telse i++;\n\t}\n\n\tif( !replacedArg )\n\t{\n\t\tnewargs[i++] = strdup( \"-game\" );\n\t\tnewargs[i++] = strdup( gamedir );\n\t}\n\n\tnewargs[i++] = strdup( \"-changegame\" );\n\tnewargs[i] = NULL;\n\n#if XASH_PSVITA\n\t// under normal circumstances it's always going to be the same path\n\texe = strdup( \"app0:/eboot.bin\" );\n\tHost_ShutdownWithReason( finalmsg );\n\tsceAppMgrLoadExec( exe, newargs, NULL );\n#else\n\texelen = wai_getExecutablePath( NULL, 0, NULL );\n\texe = malloc( exelen + 1 );\n\twai_getExecutablePath( exe, exelen, NULL );\n\texe[exelen] = 0;\n\n\tHost_ShutdownWithReason( finalmsg );\n\n\texecv( exe, newargs );\n#endif\n\n\t// if execv returned, it's probably an error\n\tprintf( \"execv failed: %s\", strerror( errno ));\n\n\tfor( ; i >= 0; i-- )\n\t\tfree( newargs[i] );\n\tfree( newargs );\n\tfree( exe );\n#endif\n\n\treturn false;\n}\n\n\n/*\n==================\nSys_GetNativeObject\n\nGet platform-specific native object\n==================\n*/\nvoid *Sys_GetNativeObject( const char *obj )\n{\n\tvoid *ptr;\n\n\tif( !COM_CheckString( obj ))\n\t\treturn NULL;\n\n\tptr = FS_GetNativeObject( obj );\n\n\tif( ptr )\n\t\treturn ptr;\n\n\t// Backend should consider that obj is case-sensitive\n#if XASH_ANDROID\n\tptr = Android_GetNativeObject( obj );\n#endif // XASH_ANDROID\n\n\treturn ptr;\n}\n"
  },
  {
    "path": "engine/common/system.h",
    "content": "/*\nsystem.h - platform dependent code\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SYSTEM_H\n#define SYSTEM_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"port.h\"\n\n#include <setjmp.h>\n#include <stdio.h>\n#include <time.h>\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"crtlib.h\"\n#include \"platform/platform.h\"\n\n#define ASSERT( exp )\tif(!( exp )) Sys_Error( \"assert failed at %s:%i\\n\", __FILE__, __LINE__ )\n\n/*\n========================================================================\ninternal dll's loader\n\ntwo main types - native dlls and other win32 libraries will be recognized automatically\nNOTE: never change this structure because all dll descriptions in xash code\nwrites into struct by offsets not names\n========================================================================\n*/\ntypedef struct dll_info_s\n{\n\tconst char      *name;\t// name of library\n\tconst dllfunc_t *fcts;\t// list of dll exports\n\tconst size_t    num_fcts;\n\tqboolean        crash;\t// crash if dll not found\n\tvoid            *link;\t// hinstance of loading library\n} dll_info_t;\n\nextern int error_on_exit;\ndouble Sys_DoubleTime( void );\nchar *Sys_GetClipboardData( void );\nconst char *Sys_GetCurrentUser( void );\nint Sys_CheckParm( const char *parm );\nvoid Sys_Warn( const char *format, ... ) FORMAT_CHECK( 1 );\nvoid Sys_Error( const char *error, ... ) FORMAT_CHECK( 1 );\nqboolean Sys_LoadLibrary( dll_info_t *dll );\nqboolean Sys_FreeLibrary( dll_info_t *dll );\nvoid Sys_ParseCommandLine( int argc, char **argv );\nvoid Sys_DebugBreak( void );\n#define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out ))\nqboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size );\nqboolean Sys_GetIntFromCmdLine( const char *parm, int *out );\nvoid Sys_Print( const char *pMsg );\nvoid Sys_Quit( const char *reason ) NORETURN;\nqboolean Sys_NewInstance( const char *gamedir, const char *finalmsg );\nvoid *Sys_GetNativeObject( const char *obj );\n\n//\n// sys_con.c\n//\nchar *Sys_Input( void );\nvoid Sys_DestroyConsole( void );\nvoid Sys_CloseLog( const char *finalmsg );\nvoid Sys_InitLog( void );\nvoid Sys_PrintLog( const char *pMsg );\nint Sys_LogFileNo( void );\n\n// text messages\n#define Msg\tCon_Printf\n\n#ifdef __cplusplus\n}\n#endif\n#endif//SYSTEM_H\n"
  },
  {
    "path": "engine/common/tests.h",
    "content": "#ifndef TESTS_H\n#define TESTS_H\n\n#if XASH_ENGINE_TESTS\n\nstruct tests_stats_s\n{\n\tuint passed;\n\tuint failed;\n};\n\nextern struct tests_stats_s tests_stats;\n\n#define TRUN( x ) Msg( \"Starting \" #x \"\\n\" ); \\\n\tx; \\\n\tMsg( \"Finished \" #x \"\\n\" )\n\n#define _TASSERT( exp, msg ) \\\n\tif( exp ) \\\n\t{ \\\n\t\ttests_stats.failed++; \\\n\t\tmsg; \\\n\t} \\\n\telse tests_stats.passed++;\n\n#define TASSERT( exp ) \\\n\t_TASSERT( !(exp), Msg( S_ERROR \"assert failed at %s:%i\\n\", __FILE__, __LINE__ ) )\n#define TASSERT_EQi( val1, val2 ) \\\n\t_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR \"assert failed at %s:%i, \\\"%d\\\" != \\\"%d\\\"\\n\", __FILE__, __LINE__, val1, val2 ))\n#define TASSERT_EQp( val1, val2 ) \\\n\t_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR \"assert failed at %s:%i, \\\"%p\\\" != \\\"%p\\\"\\n\", __FILE__, __LINE__, val1, val2 ))\n#define TASSERT_STR( str1, str2 ) \\\n\t_TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR \"assert failed at %s:%i, \\\"%s\\\" != \\\"%s\\\"\\n\", __FILE__, __LINE__, ( str1 ), ( str2 )))\n\nvoid Test_RunImagelib( void );\nvoid Test_RunLibCommon( void );\nvoid Test_RunCommon( void );\nvoid Test_RunCmd( void );\nvoid Test_RunCvar( void );\nvoid Test_RunCon( void );\nvoid Test_RunVOX( void );\nvoid Test_RunIPFilter( void );\nvoid Test_RunGamma( void );\nvoid Test_RunDelta( void );\nvoid Test_RunBuffer( void );\nvoid Test_RunMunge( void );\n\n#define TEST_LIST_0 \\\n\tTest_RunLibCommon(); \\\n\tTest_RunCommon(); \\\n\tTest_RunCmd(); \\\n\tTest_RunCvar(); \\\n\tTest_RunIPFilter(); \\\n\tTest_RunBuffer(); \\\n\tTest_RunDelta(); \\\n\tTest_RunMunge();\n\n#define TEST_LIST_0_CLIENT \\\n\tTest_RunCon(); \\\n\tTest_RunGamma();\n\n#define TEST_LIST_1 \\\n\tTest_RunImagelib();\n\n#define TEST_LIST_1_CLIENT \\\n\tTest_RunVOX();\n\n#endif\n\n#endif /* TESTS_H */\n"
  },
  {
    "path": "engine/common/whereami.c",
    "content": "// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses\n//   without any warranty.\n//   by Gregory Pakosz (@gpakosz)\n// https://github.com/gpakosz/whereami\n\n// in case you want to #include \"whereami.c\" in a larger compilation unit\n#if !defined(WHEREAMI_H)\n#include <whereami.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(__linux__) || defined(__CYGWIN__)\n#undef _DEFAULT_SOURCE\n#define _DEFAULT_SOURCE\n#elif defined(__APPLE__)\n#undef _DARWIN_C_SOURCE\n#define _DARWIN_C_SOURCE\n#define _DARWIN_BETTER_REALPATH\n#endif\n\n#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC)\n#include <stdlib.h>\n#endif\n\n#if !defined(WAI_MALLOC)\n#define WAI_MALLOC(size) malloc(size)\n#endif\n\n#if !defined(WAI_FREE)\n#define WAI_FREE(p) free(p)\n#endif\n\n#if !defined(WAI_REALLOC)\n#define WAI_REALLOC(p, size) realloc(p, size)\n#endif\n\n#ifndef WAI_NOINLINE\n#if defined(_MSC_VER)\n#define WAI_NOINLINE __declspec(noinline)\n#elif defined(__GNUC__)\n#define WAI_NOINLINE __attribute__((noinline))\n#else\n#error unsupported compiler\n#endif\n#endif\n\n#if defined(_MSC_VER)\n#define WAI_RETURN_ADDRESS() _ReturnAddress()\n#elif defined(__GNUC__)\n#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0))\n#else\n#error unsupported compiler\n#endif\n\n#if defined(_WIN32)\n\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#if defined(_MSC_VER)\n#pragma warning(push, 3)\n#endif\n#include <windows.h>\n#include <intrin.h>\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n#if (_MSC_VER >= 1900)\n#include <stdbool.h>\n#else\n#define bool int\n#define false 0\n#define true 1\n#endif\n\nstatic int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length)\n{\n  wchar_t buffer1[MAX_PATH];\n  wchar_t buffer2[MAX_PATH];\n  wchar_t* path = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    DWORD size;\n    int length_, length__;\n\n    size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0]));\n\n    if (size == 0)\n      break;\n    else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0])))\n    {\n      DWORD size_ = size;\n      do\n      {\n        wchar_t* path_;\n\n        path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2);\n        if (!path_)\n          break;\n        size_ *= 2;\n        path = path_;\n        size = GetModuleFileNameW(module, path, size_);\n      }\n      while (size == size_);\n\n      if (size == size_)\n        break;\n    }\n    else\n      path = buffer1;\n\n    if (!_wfullpath(buffer2, path, MAX_PATH))\n      break;\n    length_ = (int)wcslen(buffer2);\n    length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL);\n\n    if (length__ == 0)\n      length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL);\n    if (length__ == 0)\n      break;\n\n    if (length__ <= capacity && dirname_length)\n    {\n      int i;\n\n      for (i = length__ - 1; i >= 0; --i)\n      {\n        if (out[i] == '\\\\')\n        {\n          *dirname_length = i;\n          break;\n        }\n      }\n    }\n\n    length = length__;\n  }\n\n  if (path != buffer1)\n    WAI_FREE(path);\n\n  return ok ? length : -1;\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length);\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  HMODULE module;\n  int length = -1;\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable: 4054)\n#endif\n  if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module))\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n  {\n    length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length);\n  }\n\n  return length;\n}\n\n#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(__serenity__) || defined(__gnu_hurd__) || defined(WAI_USE_PROC_SELF_EXE)\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#if defined(__linux__)\n#include <linux/limits.h>\n#else\n#include <limits.h>\n#endif\n#ifndef __STDC_FORMAT_MACROS\n#define __STDC_FORMAT_MACROS\n#endif\n#include <inttypes.h>\n#include <stdbool.h>\n\n#if defined(__gnu_hurd__) && !defined(PATH_MAX)\n#define PATH_MAX 4096\n#endif // defined(__gnu_hurd__) && !defined(PATH_MAX)\n\n#if !defined(WAI_PROC_SELF_EXE)\n#if defined(__sun)\n#define WAI_PROC_SELF_EXE \"/proc/self/path/a.out\"\n#else\n#define WAI_PROC_SELF_EXE \"/proc/self/exe\"\n#endif\n#endif\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    resolved = realpath(WAI_PROC_SELF_EXE, buffer);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  return ok ? length : -1;\n}\n\n#if !defined(WAI_PROC_SELF_MAPS_RETRY)\n#define WAI_PROC_SELF_MAPS_RETRY 5\n#endif\n\n#if !defined(WAI_PROC_SELF_MAPS)\n#if defined(__sun)\n#define WAI_PROC_SELF_MAPS \"/proc/self/map\"\n#else\n#define WAI_PROC_SELF_MAPS \"/proc/self/maps\"\n#endif\n#endif\n\n#if !defined(WAI_STRINGIZE)\n#define WAI_STRINGIZE(s)\n#define WAI_STRINGIZE_(s) #s\n#endif\n\n#if defined(__ANDROID__) || defined(ANDROID)\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#endif\n#include <stdbool.h>\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  int length = -1;\n  FILE* maps = NULL;\n  int r;\n\n  for (r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r)\n  {\n    maps = fopen(WAI_PROC_SELF_MAPS, \"r\");\n    if (!maps)\n      break;\n\n    for (;;)\n    {\n      char buffer[128 + PATH_MAX];\n      uintptr_t low, high;\n      char perms[5];\n      uint64_t offset;\n      uint32_t major, minor, inode;\n      char path[PATH_MAX + 1];\n\n      if (!fgets(buffer, sizeof(buffer), maps))\n        break;\n\n      if (sscanf(buffer, \"%\" SCNxPTR \"-%\" SCNxPTR \" %s %\" SCNx64 \" %x:%x %u %\" WAI_STRINGIZE(PATH_MAX) \"[^\\n]\\n\", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8)\n      {\n        void* _addr = WAI_RETURN_ADDRESS();\n        uintptr_t addr = (uintptr_t)_addr;\n        if (low <= addr && addr <= high)\n        {\n          char* resolved;\n\n          resolved = realpath(path, buffer);\n          if (!resolved)\n            break;\n\n          length = (int)strlen(resolved);\n#if defined(__ANDROID__) || defined(ANDROID)\n          if (length > 4\n              &&buffer[length - 1] == 'k'\n              &&buffer[length - 2] == 'p'\n              &&buffer[length - 3] == 'a'\n              &&buffer[length - 4] == '.')\n          {\n            char *begin, *p;\n            int fd = open(path, O_RDONLY);\n            if (fd == -1)\n            {\n              length = -1; // retry\n              break;\n            }\n\n            begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0);\n            if (begin == MAP_FAILED)\n            {\n              close(fd);\n              length = -1; // retry\n              break;\n            }\n\n            p = begin + offset - 30; // minimum size of local file header\n            while (p >= begin) // scan backwards\n            {\n              if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found\n              {\n                uint16_t length_ = *((uint16_t*)(p + 26));\n\n                if (length + 2 + length_ < (int)sizeof(buffer))\n                {\n                  memcpy(&buffer[length], \"!/\", 2);\n                  memcpy(&buffer[length + 2], p + 30, length_);\n                  length += 2 + length_;\n                }\n\n                break;\n              }\n\n              --p;\n            }\n\n            munmap(begin, offset);\n            close(fd);\n          }\n#endif\n          if (length <= capacity)\n          {\n            memcpy(out, resolved, length);\n\n            if (dirname_length)\n            {\n              int i;\n\n              for (i = length - 1; i >= 0; --i)\n              {\n                if (out[i] == '/')\n                {\n                  *dirname_length = i;\n                  break;\n                }\n              }\n            }\n          }\n\n          break;\n        }\n      }\n    }\n\n    fclose(maps);\n    maps = NULL;\n\n    if (length != -1)\n      break;\n  }\n\n  return length;\n}\n\n#elif defined(__APPLE__)\n\n#include <mach-o/dyld.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* path = buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    uint32_t size = (uint32_t)sizeof(buffer1);\n    if (_NSGetExecutablePath(path, &size) == -1)\n    {\n      path = (char*)WAI_MALLOC(size);\n      if (!_NSGetExecutablePath(path, &size))\n        break;\n    }\n\n    resolved = realpath(path, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  if (path != buffer1)\n    WAI_FREE(path);\n\n  return ok ? length : -1;\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#elif defined(__QNXNTO__)\n\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\n#if !defined(WAI_PROC_SELF_EXE)\n#define WAI_PROC_SELF_EXE \"/proc/self/exefile\"\n#endif\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* resolved = NULL;\n  FILE* self_exe = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    self_exe = fopen(WAI_PROC_SELF_EXE, \"r\");\n    if (!self_exe)\n      break;\n\n    if (!fgets(buffer1, sizeof(buffer1), self_exe))\n      break;\n\n    resolved = realpath(buffer1, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  fclose(self_exe);\n\n  return ok ? length : -1;\n}\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#elif defined(__DragonFly__) || defined(__FreeBSD__) || \\\n      defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)\n\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\n#if defined(__OpenBSD__)\n\n#include <unistd.h>\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[4096];\n  char buffer2[PATH_MAX];\n  char buffer3[PATH_MAX];\n  char** argv = (char**)buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };\n    size_t size;\n\n    if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0)\n        break;\n\n    if (size > sizeof(buffer1))\n    {\n      argv = (char**)WAI_MALLOC(size);\n      if (!argv)\n        break;\n    }\n\n    if (sysctl(mib, 4, argv, &size, NULL, 0) != 0)\n        break;\n\n    if (strchr(argv[0], '/'))\n    {\n      resolved = realpath(argv[0], buffer2);\n      if (!resolved)\n        break;\n    }\n    else\n    {\n      const char* PATH = getenv(\"PATH\");\n      const char* begin;\n      size_t argv0_length;\n      if (!PATH)\n        break;\n\n      argv0_length = strlen(argv[0]);\n\n      begin = PATH;\n      while (1)\n      {\n        const char* separator = strchr(begin, ':');\n        const char* end = separator ? separator : begin + strlen(begin);\n\n        if (end - begin > 0)\n        {\n          if (*(end -1) == '/')\n            --end;\n\n          if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2))\n          {\n            memcpy(buffer2, begin, end - begin);\n            buffer2[end - begin] = '/';\n            memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1);\n\n            resolved = realpath(buffer2, buffer3);\n            if (resolved)\n              break;\n          }\n        }\n\n        if (!separator)\n          break;\n\n        begin = ++separator;\n      }\n\n      if (!resolved)\n        break;\n    }\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  if (argv != (char**)buffer1)\n    WAI_FREE(argv);\n\n  return ok ? length : -1;\n}\n\n#else\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* path = buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n#if defined(__NetBSD__)\n    int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };\n#else\n    int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };\n#endif\n    size_t size = sizeof(buffer1);\n\n    if (sysctl(mib, 4, path, &size, NULL, 0) != 0)\n        break;\n\n    resolved = realpath(path, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  return ok ? length : -1;\n}\n\n#endif\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#elif defined(__sgi)\n\n/*\n * These functions are stubbed for now to get the code compiling.\n * In the future it may be possible to get these working in some way.\n * Current ideas are checking the working directory for a binary with\n * the same executed name and reading links, or worst case just searching\n * through the entirety of the filesystem that's readable by the user.\n *\n * I'm not sure it's actually possible to find the absolute path via a\n * direct method on IRIX. Its implementation of /proc is a fairly barebones\n * SVR4 implementation. Other UNIXes (e.g. Solaris) have extensions to /proc\n * that make finding the absolute path possible but these don't exist on IRIX.\n */\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  return -1;\n}\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  return -1;\n}\n\n#elif defined(__SWITCH__) || defined(__vita__) || defined(__EMSCRIPTEN__)\n\n/* Not possible on this platform */\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  return -1;\n}\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  return -1;\n}\n\n#else\n\n#error unsupported platform\n\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "engine/common/whereami.h",
    "content": "// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses\n//   without any warranty.\n//   by Gregory Pakosz (@gpakosz)\n// https://github.com/gpakosz/whereami\n\n#ifndef WHEREAMI_H\n#define WHEREAMI_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef WAI_FUNCSPEC\n  #define WAI_FUNCSPEC\n#endif\n#ifndef WAI_PREFIX\n#define WAI_PREFIX(function) wai_##function\n#endif\n\n/**\n * Returns the path to the current executable.\n *\n * Usage:\n *  - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to\n *    retrieve the length of the path\n *  - allocate the destination buffer with `path = (char*)malloc(length + 1);`\n *  - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the\n *    path\n *  - add a terminal NUL character with `path[length] = '\\0';`\n *\n * @param out destination buffer, optional\n * @param capacity destination buffer capacity\n * @param dirname_length optional recipient for the length of the dirname part\n *   of the path. Available only when `capacity` is large enough to retrieve the\n *   path.\n *\n * @return the length of the executable path on success (without a terminal NUL\n * character), otherwise `-1`\n */\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length);\n\n/**\n * Returns the path to the current module\n *\n * Usage:\n *  - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve\n *    the length  of the path\n *  - allocate the destination buffer with `path = (char*)malloc(length + 1);`\n *  - call `wai_getModulePath(path, length, NULL)` again to retrieve the path\n *  - add a terminal NUL character with `path[length] = '\\0';`\n *\n * @param out destination buffer, optional\n * @param capacity destination buffer capacity\n * @param dirname_length optional recipient for the length of the dirname part\n *   of the path. Available only when `capacity` is large enough to retrieve the\n *   path.\n *\n * @return the length of the module path on success (without a terminal NUL\n * character), otherwise `-1`\n */\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // #ifndef WHEREAMI_H\n"
  },
  {
    "path": "engine/common/world.c",
    "content": "/*\nworld.c - common worldtrace routines\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"world.h\"\n#include \"pm_defs.h\"\n#include \"mod_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"studio.h\"\n\n/*\n==================\nWorld_TransformAABB\n==================\n*/\nvoid World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t maxs, vec3_t outmins, vec3_t outmaxs )\n{\n\tvec3_t\tp1, p2;\n\tmatrix4x4\titransform;\n\tint\ti;\n\n\tif( !outmins || !outmaxs ) return;\n\n\tMatrix4x4_Invert_Simple( itransform, transform );\n\tClearBounds( outmins, outmaxs );\n\n\t// compute a full bounding box\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\tp1[0] = ( i & 1 ) ? mins[0] : maxs[0];\n\t\tp1[1] = ( i & 2 ) ? mins[1] : maxs[1];\n\t\tp1[2] = ( i & 4 ) ? mins[2] : maxs[2];\n\n\t\tp2[0] = DotProduct( p1, itransform[0] );\n\t\tp2[1] = DotProduct( p1, itransform[1] );\n\t\tp2[2] = DotProduct( p1, itransform[2] );\n\n\t\tif( p2[0] < outmins[0] ) outmins[0] = p2[0];\n\t\tif( p2[0] > outmaxs[0] ) outmaxs[0] = p2[0];\n\t\tif( p2[1] < outmins[1] ) outmins[1] = p2[1];\n\t\tif( p2[1] > outmaxs[1] ) outmaxs[1] = p2[1];\n\t\tif( p2[2] < outmins[2] ) outmins[2] = p2[2];\n\t\tif( p2[2] > outmaxs[2] ) outmaxs[2] = p2[2];\n\t}\n\n\t// sanity check\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( outmins[i] > outmaxs[i] )\n\t\t{\n\t\t\tVectorClear( outmins );\n\t\t\tVectorClear( outmaxs );\n\t\t\treturn;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/common/world.h",
    "content": "/*\nworld.h - shared world routines\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef WORLD_H\n#define WORLD_H\n\n#define MOVE_NORMAL\t\t0\t// normal trace\n#define MOVE_NOMONSTERS\t1\t// ignore monsters (edicts with flags (FL_MONSTER|FL_FAKECLIENT|FL_CLIENT) set)\n#define MOVE_MISSILE\t2\t// extra size for monsters\n\n#define CONTENTS_NONE\t0\t// no custom contents specified\n\n/*\n===============================================================================\n\nENTITY AREA CHECKING\n\n===============================================================================\n*/\n#define MAX_TOTAL_ENT_LEAFS\t\t128\n#define AREA_NODES\t\t\t32\n#define AREA_DEPTH\t\t\t4\n\n#include \"lightstyle.h\"\n\n// trace common\nstatic inline void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs )\n{\n\tint\ti;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( end[i] > start[i] )\n\t\t{\n\t\t\tboxmins[i] = start[i] + mins[i] - 1.0f;\n\t\t\tboxmaxs[i] = end[i] + maxs[i] + 1.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tboxmins[i] = end[i] + mins[i] - 1.0f;\n\t\t\tboxmaxs[i] = start[i] + maxs[i] + 1.0f;\n\t\t}\n\t}\n}\n\nstatic inline trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch )\n{\n\tif( trace->allsolid || trace->startsolid || trace->fraction < cliptrace->fraction )\n\t{\n\t\ttrace->ent = touch;\n\n\t\tif( cliptrace->startsolid )\n\t\t{\n\t\t\t*cliptrace = *trace;\n\t\t\tcliptrace->startsolid = true;\n\t\t}\n\t\telse *cliptrace = *trace;\n\t}\n\n\treturn *cliptrace;\n}\n\n/*\n==================\nRankForContents\n\nUsed for determine contents priority\n==================\n*/\nstatic inline int RankForContents( int contents )\n{\n\tswitch( contents )\n\t{\n\tcase CONTENTS_EMPTY:\treturn 0;\n\tcase CONTENTS_WATER:\treturn 1;\n\tcase CONTENTS_TRANSLUCENT:\treturn 2;\n\tcase CONTENTS_CURRENT_0:\treturn 3;\n\tcase CONTENTS_CURRENT_90:\treturn 4;\n\tcase CONTENTS_CURRENT_180:\treturn 5;\n\tcase CONTENTS_CURRENT_270:\treturn 6;\n\tcase CONTENTS_CURRENT_UP:\treturn 7;\n\tcase CONTENTS_CURRENT_DOWN:\treturn 8;\n\tcase CONTENTS_SLIME:\treturn 9;\n\tcase CONTENTS_LAVA:\t\treturn 10;\n\tcase CONTENTS_SKY:\t\treturn 11;\n\tcase CONTENTS_SOLID:\treturn 12;\n\tdefault:\t\t\treturn 13; // any user contents has more priority than default\n\t}\n}\n\nvoid World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t maxs, vec3_t outmins, vec3_t outmaxs );\n\n#define check_angles( x )\t( (int)x == 90 || (int)x == 180 || (int)x == 270 || (int)x == -90 || (int)x == -180 || (int)x == -270 )\n\n/*\n===============================================================================\n\n\tEVENTS QUEUE (hl1 events code)\n\n===============================================================================\n*/\n#include \"event_api.h\"\n#include \"event_args.h\"\n\n#define MAX_EVENT_QUEUE\t64\t\t// 16 simultaneous events, max\n\ntypedef struct event_info_s\n{\n\tword\t\tindex;\t\t// 0 implies not in use\n\tshort\t\tpacket_index;\t// Use data from state info for entity in delta_packet .\n\t\t\t\t\t// -1 implies separate info based on event\n\t\t\t\t\t// parameter signature\n\tshort\t\tentity_index;\t// The edict this event is associated with\n\tfloat\t\tfire_time;\t// if non-zero, the time when the event should be fired\n\t\t\t\t\t// ( fixed up on the client )\n\tevent_args_t\targs;\n\tint\t\tflags;\t\t// reliable or not, etc. ( CLIENT ONLY )\n} event_info_t;\n\ntypedef struct event_state_s\n{\n\tevent_info_t\tei[MAX_EVENT_QUEUE];\n} event_state_t;\n\n#endif//WORLD_H\n"
  },
  {
    "path": "engine/common/zone.c",
    "content": "/*\nzone.c - zone memory allocation from DarkPlaces\nCopyright (C) 1996-1997 Id Software, Inc.\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n\n#define MEMHEADER_SENTINEL1\t0xA1BAU\n#define MEMHEADER_SENTINEL2\t0xDFU\n\n#ifdef XASH_CUSTOM_SWAP\n#include \"platform/swap/swap.h\"\n#define Q_malloc SWAP_Malloc\n#define Q_free SWAP_Free\n\nstatic void *Q_realloc( void *mem, size_t size )\n{\n\tvoid *newmem;\n\n\tif( mem && size == 0 )\n\t{\n\t\tQ_free( mem );\n\t\treturn NULL;\n\t}\n\n\tnewmem = Q_malloc( size );\n\tif( mem && newmem )\n\t{\n\t\tmemcpy( newmem, mem, size );\n\t\tQ_free( mem );\n\t}\n\n\treturn newmem;\n}\n#else\n#define Q_malloc malloc\n#define Q_free free\n#define Q_realloc realloc\n#endif\n\n// keep this structure as compact as possible while keeping it aligned\n// on ILP32 it's 24 bytes, which is aligned to 8 byte boundary\n// on LP64 it's 40 bytes, which is also aligned to 8 byte boundary\ntypedef struct memheader_s\n{\n\tstruct memheader_s *next, *prev; // next and previous memheaders in chain belonging to pool\n\tconst char         *filename;    // file name and line where Mem_Alloc was called\n\tsize_t             size;         // size of the memory after the header (excluding header and sentinel2)\n\tpoolhandle_t       poolptr;      // pool this memheader belongs to\n\tuint16_t           fileline;\n\tuint16_t           sentinel1;    // must be equal to MEMHEADER_SENTINEL1\n\t// immediately followed by data, which is followed by a MEMHEADER_SENTINEL2 byte\n} memheader_t;\n\nSTATIC_CHECK_SIZEOF( memheader_t, 24, 40 );\n\ntypedef struct mempool_s\n{\n\tstruct memheader_s *chain;        // chain of individual memory allocations\n\tsize_t             totalsize;     // total memory allocated in this pool (inside memheaders)\n\tsize_t             realsize;      // total memory allocated in this pool (actual malloc total)\n\tsize_t             lastchecksize; // updated each time the pool is displayed by memlist\n\tconst char         *filename;     // file name and line where Mem_AllocPool was called\n\tint                fileline;\n\tchar               name[64];      // name of the pool\n} mempool_t;\n\nstatic mempool_t *poolchain = NULL; // critical stuff\nstatic size_t poolcount = 0;\n\n// a1ba: due to mempool being passed with the model through reused 32-bit field\n// which makes engine incompatible with 64-bit pointers I changed mempool type\n// from pointer to 32-bit handle, thankfully mempool structure is private\nstatic mempool_t *Mem_FindPool( poolhandle_t poolptr )\n{\n\tif( likely( poolptr > 0 && poolptr <= poolcount ))\n\t\treturn &poolchain[poolptr - 1];\n\n\tSys_Error( \"%s: not allocated or double freed pool %d\", __func__, poolptr );\n\treturn NULL;\n}\n\nstatic poolhandle_t Mem_PoolIndex( mempool_t *mempool )\n{\n\treturn (poolhandle_t)(mempool - poolchain) + 1;\n}\n\nstatic inline void Mem_PoolAdd( mempool_t *pool, size_t size )\n{\n\tpool->totalsize += size;\n\tpool->realsize += sizeof( memheader_t ) + size + sizeof( byte );\n}\n\nstatic inline void Mem_PoolSubtract( mempool_t *pool, size_t size )\n{\n\tpool->totalsize -= size;\n\tpool->realsize -= sizeof( memheader_t ) + size + sizeof( byte );\n}\n\nstatic inline void Mem_PoolLinkAlloc( mempool_t *pool, memheader_t *mem )\n{\n\tmem->next = pool->chain;\n\tif( mem->next ) mem->next->prev = mem;\n\tpool->chain = mem;\n\tmem->prev = NULL;\n\tmem->poolptr = Mem_PoolIndex( pool );\n}\n\nstatic inline void Mem_PoolUnlinkAlloc( mempool_t *pool, memheader_t *mem )\n{\n\tif( mem->next ) mem->next->prev = mem->prev;\n\tif( mem->prev ) mem->prev->next = mem->next;\n\telse pool->chain = mem->next;\n\tmem->poolptr = 0;\n}\n\nstatic inline void Mem_InitAlloc( memheader_t *mem, size_t size, const char *filename, int fileline )\n{\n\tmem->size = size;\n\tmem->filename = filename;\n\tmem->fileline = fileline;\n\tmem->sentinel1 = MEMHEADER_SENTINEL1;\n\t*((byte *)mem + sizeof( memheader_t ) + mem->size ) = MEMHEADER_SENTINEL2;\n}\n\nstatic const char *Mem_CheckFilename( const char *filename )\n{\n\tstatic const char *dummy = \"<corrupted>\\0\";\n\n\tif( !COM_CheckString( filename ))\n\t\treturn dummy;\n\n\tif( memchr( filename, '\\0', MAX_OSPATH ) != NULL )\n\t\treturn filename;\n\n\treturn dummy;\n}\n\nstatic qboolean Mem_CheckAllocHeader( const char *func, const memheader_t *mem, const char *filename, int fileline )\n{\n\tconst char *memfilename;\n\n\tif( mem->sentinel1 != MEMHEADER_SENTINEL1 )\n\t{\n\t\tmemfilename = Mem_CheckFilename( mem->filename );\n\t\tSys_Error( \"%s: trashed header sentinel 1 (alloc at %s:%i, check at %s:%i)\\n\", func, memfilename, mem->fileline, filename, fileline );\n\t\treturn false;\n\t}\n\n\tif( *((byte *)mem + sizeof( memheader_t ) + mem->size ) != MEMHEADER_SENTINEL2 )\n\t{\n\t\tmemfilename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args\n\t\tSys_Error( \"%s: trashed header sentinel 2 (alloc at %s:%i, check at %s:%i)\\n\", func, memfilename, mem->fileline, filename, fileline );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\tmemheader_t *mem;\n\tmempool_t   *pool;\n\n\tif( size <= 0 )\n\t\treturn NULL;\n\n\tif( unlikely( !poolptr ))\n\t{\n\t\tSys_Error( \"%s: pool == NULL (alloc at %s:%i)\\n\", __func__, filename, fileline );\n\t\treturn NULL;\n\t}\n\n\tpool = Mem_FindPool( poolptr );\n\tif( !pool )\n\t\treturn NULL;\n\n\tmem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( byte ));\n\tif( mem == NULL )\n\t{\n\t\tSys_Error( \"%s: out of memory (alloc size %s at %s:%i)\\n\", __func__, Q_memprint( size ), filename, fileline );\n\t\treturn NULL;\n\t}\n\n\tMem_InitAlloc( mem, size, filename, fileline );\n\n\tMem_PoolAdd( pool, size );\n\tMem_PoolLinkAlloc( pool, mem );\n\n\tif( clear )\n\t\tmemset((void *)((byte *)mem + sizeof( memheader_t )), 0, mem->size );\n\n\treturn (void *)((byte *)mem + sizeof( memheader_t ));\n}\n\nstatic void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline )\n{\n\tmempool_t\t\t*pool;\n\n\tif( !Mem_CheckAllocHeader( __func__, mem, filename, fileline ))\n\t\treturn;\n\n\tpool = Mem_FindPool( mem->poolptr );\n\tif( !pool )\n\t\treturn;\n\n\t// unlink memheader from doubly linked list\n\tif(( mem->prev ? mem->prev->next != mem : pool->chain != mem ) || ( mem->next && mem->next->prev != mem ))\n\t{\n\t\tSys_Error( \"%s: not allocated or double freed (free at %s:%i)\\n\", __func__, filename, fileline );\n\t\treturn;\n\t}\n\n\tMem_PoolSubtract( pool, mem->size );\n\tMem_PoolUnlinkAlloc( pool, mem );\n\n\tQ_free( mem );\n}\n\nvoid _Mem_Free( void *data, const char *filename, int fileline )\n{\n\tif( data == NULL )\n\t\treturn;\n\n\tMem_FreeBlock((memheader_t *)((byte *)data - sizeof( memheader_t )), filename, fileline );\n}\n\nstatic void Mem_MigratePool( poolhandle_t newpoolptr, memheader_t *mem, const char *filename, int fileline )\n{\n\tmempool_t *oldpool = Mem_FindPool( mem->poolptr );\n\tmempool_t *newpool = Mem_FindPool( newpoolptr );\n\n\t// dettach allocation from one pool and reattach it to new pool\n\t// might be made into public function at some point\n\n\tMem_PoolUnlinkAlloc( oldpool, mem );\n\tMem_PoolSubtract( oldpool, mem->size );\n\n\tMem_PoolLinkAlloc( newpool, mem );\n\tMem_PoolAdd( newpool, mem->size );\n}\n\nvoid *_Mem_Realloc( poolhandle_t poolptr, void *data, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\tmemheader_t *mem;\n\tuintptr_t oldmem;\n\tmempool_t *pool;\n\tsize_t oldsize;\n\n\tif( size <= 0 )\n\t\treturn data; // no need to reallocate\n\n\tif( unlikely( !poolptr ))\n\t{\n\t\tSys_Error( \"%s: pool == NULL (alloc at %s:%i)\\n\", __func__, filename, fileline );\n\t\treturn NULL;\n\t}\n\n\tif( !data )\n\t\treturn _Mem_Alloc( poolptr, size, clear, filename, fileline );\n\n\tmem = (memheader_t *)((byte *)data - sizeof( memheader_t ));\n\n\tif( !Mem_CheckAllocHeader( __func__, mem, filename, fileline ))\n\t\treturn NULL;\n\n\t// migrate pool if requested, even if no reallocation needed\n\tif( mem->poolptr != poolptr )\n\t\tMem_MigratePool( poolptr, mem, filename, fileline );\n\n\toldsize = mem->size;\n\tif( size == oldsize )\n\t\treturn data;\n\n\tpool = Mem_FindPool( poolptr );\n\n\toldmem = (uintptr_t)mem;\n\tmem = Q_realloc( mem, sizeof( memheader_t ) + size + sizeof( byte ));\n\n\tif( mem == NULL )\n\t{\n\t\tSys_Error( \"%s: out of memory (alloc size %s at %s:%i)\\n\", __func__, Q_memprint( size ), filename, fileline );\n\t\treturn NULL;\n\t}\n\n\t// Con_Printf( S_NOTE \"%s: mem %s oldmem, size before %zu now %zu (alloc at %s:%i)\\n\",\n\t// __func__, (uintptr_t)mem != oldmem ? \"!=\" : \"==\", oldsize, size, filename, fileline );\n\n\tMem_InitAlloc( mem, size, filename, fileline );\n\n\tif( size > oldsize )\n\t{\n\t\tMem_PoolAdd( pool, size - oldsize );\n\n\t\tif( clear )\n\t\t\tmemset((byte *)mem + sizeof( memheader_t ) + oldsize, 0, size - oldsize );\n\t}\n\telse Mem_PoolSubtract( pool, oldsize - size );\n\n\tif( oldmem != (uintptr_t)mem ) // just relink pointers\n\t{\n\t\tif( mem->next ) mem->next->prev = mem;\n\t\tif( mem->prev ) mem->prev->next = mem;\n\t\telse pool->chain = mem;\n\t}\n\n\treturn (void *)((byte *)mem + sizeof( memheader_t ));\n}\n\nstatic poolhandle_t Mem_InitPool( mempool_t *pool, const char *name, const char *filename, int fileline )\n{\n\tmemset( pool, 0, sizeof( *pool ));\n\n\t// fill header\n\tpool->filename = filename;\n\tpool->fileline = fileline;\n\tpool->realsize = sizeof( mempool_t );\n\tQ_strncpy( pool->name, name, sizeof( pool->name ));\n\n\treturn Mem_PoolIndex( pool );\n}\n\npoolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline )\n{\n\tmempool_t *pool;\n\tsize_t i;\n\n\tfor( i = 0, pool = poolchain; i < poolcount; i++, pool++ )\n\t{\n\t\tif( pool->filename == NULL )\n\t\t\treturn Mem_InitPool( pool, name, filename, fileline );\n\t}\n\n\tpool = (mempool_t *)Q_realloc( poolchain, sizeof( *poolchain ) * ( poolcount + 1 ));\n\tif( pool == NULL )\n\t{\n\t\tSys_Error( \"%s: out of memory (allocpool at %s:%i)\\n\", __func__, filename, fileline );\n\t\treturn 0;\n\t}\n\n\tpoolchain = pool;\n\tpool = &poolchain[poolcount++];\n\treturn Mem_InitPool( pool, name, filename, fileline );\n}\n\nvoid _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline )\n{\n\tmempool_t\t*pool;\n\n\tif( *poolptr && ( pool = Mem_FindPool( *poolptr )))\n\t{\n\t\tif( !pool->filename )\n\t\t{\n\t\t\tSys_Error( \"%s: pool already free (freepool at %s:%i)\\n\", __func__, filename, fileline );\n\t\t\t*poolptr = 0;\n\t\t\treturn;\n\t\t}\n\n\t\t// free memory owned by the pool\n\t\twhile( pool->chain )\n\t\t\tMem_FreeBlock( pool->chain, filename, fileline );\n\n\t\t// free the pool itself\n\t\tmemset( pool, 0xBF, sizeof( mempool_t ));\n\t\tpool->chain = NULL;\n\t\tpool->filename = NULL; // mark as reusable\n\t\t*poolptr = 0;\n\t}\n}\n\nvoid _Mem_EmptyPool( poolhandle_t poolptr, const char *filename, int fileline )\n{\n\tmempool_t *pool;\n\tif( unlikely( !poolptr ))\n\t{\n\t\tSys_Error( \"%s: pool == NULL (emptypool at %s:%i)\\n\", __func__, filename, fileline );\n\t\treturn;\n\t}\n\n\tpool = Mem_FindPool( poolptr );\n\tif( !pool )\n\t\treturn;\n\n\t// free memory owned by the pool\n\twhile( pool->chain ) Mem_FreeBlock( pool->chain, filename, fileline );\n}\n\nstatic qboolean Mem_CheckAlloc( mempool_t *pool, void *data )\n{\n\tmemheader_t *header, *target;\n\n\tif( pool )\n\t{\n\t\t// search only one pool\n\t\ttarget = (memheader_t *)((byte *)data - sizeof( memheader_t ));\n\t\tfor( header = pool->chain; header; header = header->next )\n\t\t{\n\t\t\tif( header == target )\n\t\t\t\treturn true;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// search all pools\n\t\tsize_t i;\n\t\tfor( i = 0, pool = poolchain; i < poolcount; i++, pool++ )\n\t\t{\n\t\t\tif( Mem_CheckAlloc( pool, data ))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/*\n========================\nCheck pointer for memory\n========================\n*/\nqboolean Mem_IsAllocatedExt( poolhandle_t poolptr, void *data )\n{\n\tmempool_t\t*pool = NULL;\n\n\tif( poolptr )\n\t\tpool = Mem_FindPool( poolptr );\n\n\treturn Mem_CheckAlloc( pool, data );\n}\n\nvoid _Mem_Check( const char *filename, int fileline )\n{\n\tmemheader_t *mem;\n\tmempool_t   *pool;\n\tsize_t i;\n\n\tfor( i = 0, pool = poolchain; i < poolcount; i++, pool++ )\n\t\tfor( mem = pool->chain; mem; mem = mem->next )\n\t\t\tMem_CheckAllocHeader( __func__, mem, filename, fileline );\n}\n\nvoid Mem_PrintStats( void )\n{\n\tsize_t    count = 0, size = 0, realsize = 0, i;\n\tmempool_t *pool;\n\n\tMem_Check();\n\tfor( i = 0, pool = poolchain; i < poolcount; i++, pool++ )\n\t{\n\t\tif( !pool->filename )\n\t\t\tcontinue;\n\n\t\tcount++;\n\t\tsize += pool->totalsize;\n\t\trealsize += pool->realsize;\n\t}\n\n\tCon_Printf( \"^3%zu^7 memory pools, totalling: ^1%s\\n\", count, Q_memprint( size ));\n\tCon_Printf( \"total allocated size: ^1%s\\n\", Q_memprint( realsize ));\n}\n\nvoid Mem_PrintList( size_t minallocationsize )\n{\n\tmempool_t\t\t*pool;\n\tmemheader_t\t*mem;\n\tsize_t i;\n\n\tMem_Check();\n\n\tCon_Printf( \"memory pool list:\\n\" );\n\tCon_Printf( \"\\t^3size\\t\\t\\t\\tname\\n\");\n\tfor( i = 0, pool = poolchain; i < poolcount; i++, pool++ )\n\t{\n\t\tlong\tchanged_size = (long)pool->totalsize - (long)pool->lastchecksize;\n\n\t\tif( !pool->filename )\n\t\t\tcontinue;\n\n\t\t// poolnames can contain color symbols, make sure what color is reset\n\t\tif( pool->lastchecksize != 0 && changed_size != 0 )\n\t\t{\n\t\t\tchar\tsign = (changed_size < 0) ? '-' : '+';\n\n\t\t\tCon_Printf( \"%10s (%10s real)\\t%s (^7%c%s change)\\n\", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ),\n\t\t\t\tpool->name, sign, Q_memprint( abs( changed_size )));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( \"%10s (%10s real)\\t%s\\n\", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), pool->name );\n\t\t}\n\n\t\tpool->lastchecksize = pool->totalsize;\n\t\tfor( mem = pool->chain; mem; mem = mem->next )\n\t\t{\n\t\t\tif( mem->size >= minallocationsize )\n\t\t\t\tCon_Printf( \"%10s allocated at %s:%i\\n\", Q_memprint( mem->size ), mem->filename, mem->fileline );\n\t\t}\n\t}\n}\n\n/*\n========================\nMemory_Init\n========================\n*/\nvoid Memory_Init( void )\n{\n\tpoolchain = NULL; // init mem chain\n\tpoolcount = 0;\n}\n"
  },
  {
    "path": "engine/cursor_type.h",
    "content": "/*\ncursor_type.h - enumeration of possible mouse cursor types\nCopyright (C) 2022 FWGS Team\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef CURSOR_TYPE_H\n#define CURSOR_TYPE_H\n\ntypedef enum\n{\n\tdc_user,\n\tdc_none,\n\tdc_arrow,\n\tdc_ibeam,\n\tdc_hourglass,\n\tdc_crosshair,\n\tdc_up,\n\tdc_sizenwse,\n\tdc_sizenesw,\n\tdc_sizewe,\n\tdc_sizens,\n\tdc_sizeall,\n\tdc_no,\n\tdc_hand,\n\tdc_last\n} VGUI_DefaultCursor;\n\n#endif\n"
  },
  {
    "path": "engine/custom.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef CUSTOM_H\n#define CUSTOM_H\n\n#include \"const.h\"\n\n/////////////////\n// Customization\n// passed to pfnPlayerCustomization\n// For automatic downloading.\n\ntypedef enum\n{\n\tt_sound = 0,\n\tt_skin,\n\tt_model,\n\tt_decal,\n\tt_generic,\n\tt_eventscript,\n\tt_world,\t\t\t// Fake type for world, is really t_model\n} resourcetype_t;\n\ntypedef struct\n{\n\tint\t\tsize;\n} _resourceinfo_t;\n\ntypedef struct resourceinfo_s\n{\n\t_resourceinfo_t\tinfo[8];\n} resourceinfo_t;\n\n#define RES_FATALIFMISSING\t(1<<0)\t// Disconnect if we can't get this file.\n#define RES_WASMISSING\t(1<<1)\t// Do we have the file locally, did we get it ok?\n#define RES_CUSTOM\t\t(1<<2)\t// Is this resource one that corresponds to another player's customization\n\t\t\t\t// or is it a server startup resource.\n#define RES_REQUESTED\t(1<<3)\t// Already requested a download of this one\n#define RES_PRECACHED\t(1<<4)\t// Already precached\n#define RES_ALWAYS\t\t(1<<5)\t// Download always even if available on client\n#define RES_CHECKFILE\t(1<<7)\t// Check file on client\n\n// this archive was already mounted after rescan\n// only makes sense for archives and on client\n#define RES_EXTRA_ARCHIVE_CHECKED BIT( 0 )\n\ntypedef struct resource_s\n{\n\tchar\t\t\tszFileName[64];\t// File name to download/precache.\n\tresourcetype_t\t\ttype;\t\t// t_sound, t_skin, t_model, t_decal.\n\tint\t\t\tnIndex;\t\t// For t_decals\n\tint\t\t\tnDownloadSize;\t// Size in Bytes if this must be downloaded.\n\tunsigned char\t\tucFlags;\n\n\t// for handling client to client resource propagation\n\tunsigned char\t\trgucMD5_hash[16];\t// To determine if we already have it.\n\tunsigned char\t\tplayernum;\t// Which player index this resource is associated with,\n\t\t\t\t\t\t// if it's a custom resource.\n\n\tunsigned char\t\trguc_reserved[32];\t// For future expansion\n\tunsigned short\t\tucExtraFlags; // fwgs extension, doesn't change the size of struct because of compiler padding\n\tstruct resource_s\t\t*pNext;\t\t// Next in chain.\n\tstruct resource_s\t\t*pPrev;\n} resource_t;\n\ntypedef struct customization_s\n{\n\tqboolean\t\t\tbInUse;\t\t// Is this customization in use;\n\tresource_t\t\tresource;\t\t// The resource_t for this customization\n\tqboolean\t\t\tbTranslated;\t// Has the raw data been translated into a useable format?\n\t\t\t\t\t\t// (e.g., raw decal .wad make into texture_t *)\n\tint\t\t\tnUserData1;\t// Customization specific data\n\tint\t\t\tnUserData2;\t// Customization specific data\n\tvoid\t\t\t*pInfo;\t\t// Buffer that holds the data structure that references\n\t\t\t\t\t\t// the data (e.g., the cachewad_t)\n\tvoid\t\t\t*pBuffer;\t\t// Buffer that holds the data for the customization\n\t\t\t\t\t\t// (the raw .wad data)\n\tstruct customization_s\t*pNext;\t\t// Next in chain\n} customization_t;\n\n#define FCUST_FROMHPAK\t\t( 1<<0 )\n#define FCUST_WIPEDATA\t\t( 1<<1 )\n#define FCUST_IGNOREINIT\t\t( 1<<2 )\n\nSTATIC_CHECK_SIZEOF( customization_t, 164, 192 );\nSTATIC_CHECK_SIZEOF( resource_t, 136, 144 );\n\n#endif // CUSTOM_H\n"
  },
  {
    "path": "engine/customentity.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef CUSTOMENTITY_H\n#define CUSTOMENTITY_H\n\n// Custom Entities\n\n// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12)\n#define BEAMENT_ENTITY( x )\t\t((x) & 0xFFF)\n#define BEAMENT_ATTACHMENT( x )\t(((x)>>12) & 0xF)\n\n// Beam types, encoded as a byte\nenum\n{\n\tBEAM_POINTS = 0,\n\tBEAM_ENTPOINT,\n\tBEAM_ENTS,\n\tBEAM_HOSE,\n};\n\n#define BEAM_FSINE\t\t0x10\n#define BEAM_FSOLID\t\t0x20\n#define BEAM_FSHADEIN\t0x40\n#define BEAM_FSHADEOUT\t0x80\n\n#endif//CUSTOMENTITY_H\n"
  },
  {
    "path": "engine/edict.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef EDICT_H\n#define EDICT_H\n\n#define MAX_ENT_LEAFS_32\t24\t\t// Orignally was 16\n#define MAX_ENT_LEAFS_16\t48\n\n#include \"progdefs.h\"\n\nstruct edict_s\n{\n\tqboolean\t\tfree;\n\tint\t\tserialnumber;\n\n\tlink_t\t\tarea;\t\t// linked to a division node or leaf\n\tint\t\theadnode;\t\t// -1 to use normal leaf check\n\n\tint\t\tnum_leafs;\n\tunion\n\t{\n\t\tint   leafnums32[MAX_ENT_LEAFS_32];\n\t\tshort leafnums16[MAX_ENT_LEAFS_16];\n\t};\n\n\tfloat\t\tfreetime;\t\t// sv.time when the object was freed\n\n\tvoid*\t\tpvPrivateData;\t// Alloced and freed by engine, used by DLLs\n\tentvars_t\t\tv;\t\t// C exported fields from progs\n\n\t// other fields from progs come immediately after\n};\n\n#endif//EDICT_H\n"
  },
  {
    "path": "engine/eiface.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef EIFACE_H\n#define EIFACE_H\n\n#ifdef HLDEMO_BUILD\n#define INTERFACE_VERSION       \t001\n#else  // !HLDEMO_BUILD, i.e., regular version of HL\n#define INTERFACE_VERSION\t\t140\n#endif // !HLDEMO_BUILD\n\n#include <stdio.h>\n#include \"custom.h\"\n#include \"cvardef.h\"\n//\n// Defines entity interface between engine and DLLs.\n// This header file included by engine files and DLL files.\n//\n// Before including this header, DLLs must:\n//\t\tinclude progdefs.h\n// This is conveniently done for them in extdll.h\n//\n\n#ifdef _WIN32\n#define DLLEXPORT __stdcall\n#else\n#define DLLEXPORT /* */\n#endif\n\ntypedef enum\n{\n\tat_notice,\n\tat_console,\t// same as at_notice, but forces a ConPrintf, not a message box\n\tat_aiconsole,\t// same as at_console, but only shown if developer level is 2!\n\tat_warning,\n\tat_error,\n\tat_logged\t\t// Server print to console ( only in multiplayer games ).\n} ALERT_TYPE;\n\n// 4-22-98  JOHN: added for use in pfnClientPrintf\ntypedef enum\n{\n\tprint_console,\n\tprint_center,\n\tprint_chat,\n} PRINT_TYPE;\n\n// For integrity checking of content on clients\ntypedef enum\n{\n\tforce_exactfile,\t\t\t// File on client must exactly match server's file\n\tforce_model_samebounds,\t\t// For model files only, the geometry must fit in the same bbox\n\tforce_model_specifybounds,\t\t// For model files only, the geometry must fit in the specified bbox\n\tforce_model_specifybounds_if_avail,\n} FORCE_TYPE;\n\n// Returned by TraceLine\ntypedef struct\n{\n\tint\tfAllSolid;\t\t// if true, plane is not valid\n\tint\tfStartSolid;\t\t// if true, the initial point was in a solid area\n\tint\tfInOpen;\n\tint\tfInWater;\n\tfloat\tflFraction;\t\t// time completed, 1.0 = didn't hit anything\n\tvec3_t\tvecEndPos;\t\t// final position\n\tfloat\tflPlaneDist;\n\tvec3_t\tvecPlaneNormal;\t\t// surface normal at impact\n\tedict_t\t*pHit;\t\t\t// entity the surface is on\n\tint\tiHitgroup;\t\t// 0 == generic, non zero is specific body part\n} TraceResult;\n\n// CD audio status\ntypedef struct\n{\n\tint\tfPlaying;// is sound playing right now?\n\tint\tfWasPlaying;// if not, CD is paused if WasPlaying is true.\n\tint\tfInitialized;\n\tint\tfEnabled;\n\tint\tfPlayLooping;\n\tfloat\tcdvolume;\n\tint\tfCDRom;\n\tint\tfPlayTrack;\n} CDStatus;\n\ntypedef unsigned int\tCRC32_t;\n\ntypedef struct delta_s delta_t;\nstruct entity_state_s;\n\n// Engine hands this to DLLs for functionality callbacks\ntypedef struct enginefuncs_s\n{\n\tint\t(*pfnPrecacheModel)( const char* s );\n\tint\t(*pfnPrecacheSound)( const char* s );\n\tvoid\t(*pfnSetModel)( edict_t *e, const char *m );\n\tint\t(*pfnModelIndex)( const char *m );\n\tint\t(*pfnModelFrames)( int modelIndex );\n\tvoid\t(*pfnSetSize)( edict_t *e, const float *rgflMin, const float *rgflMax );\n\tvoid\t(*pfnChangeLevel)( const char* s1, const char* s2 );\n\tvoid\t(*pfnGetSpawnParms)( edict_t *ent );\n\tvoid\t(*pfnSaveSpawnParms)( edict_t *ent );\n\tfloat\t(*pfnVecToYaw)( const float *rgflVector );\n\tvoid\t(*pfnVecToAngles)( const float *rgflVectorIn, float *rgflVectorOut );\n\tvoid\t(*pfnMoveToOrigin)( edict_t *ent, const float *pflGoal, float dist, int iMoveType );\n\tvoid\t(*pfnChangeYaw)( edict_t* ent );\n\tvoid\t(*pfnChangePitch)( edict_t* ent );\n\tedict_t*\t(*pfnFindEntityByString)( edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue );\n\tint\t(*pfnGetEntityIllum)( edict_t* pEnt );\n\tedict_t*\t(*pfnFindEntityInSphere)( edict_t *pEdictStartSearchAfter, const float *org, float rad );\n\tedict_t*\t(*pfnFindClientInPVS)( edict_t *pEdict );\n\tedict_t*\t(*pfnEntitiesInPVS)( edict_t *pplayer );\n\tvoid\t(*pfnMakeVectors)( const float *rgflVector );\n\tvoid\t(*pfnAngleVectors)( const float *rgflVector, float *forward, float *right, float *up );\n\tedict_t*\t(*pfnCreateEntity)( void );\n\tvoid\t(*pfnRemoveEntity)( edict_t* e );\n\tedict_t*\t(*pfnCreateNamedEntity)( int className );\n\tvoid\t(*pfnMakeStatic)( edict_t *ent );\n\tint\t(*pfnEntIsOnFloor)( edict_t *e );\n\tint\t(*pfnDropToFloor)( edict_t* e );\n\tint\t(*pfnWalkMove)( edict_t *ent, float yaw, float dist, int iMode );\n\tvoid\t(*pfnSetOrigin)( edict_t *e, const float *rgflOrigin );\n\tvoid\t(*pfnEmitSound)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch );\n\tvoid\t(*pfnEmitAmbientSound)( edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch );\n\tvoid\t(*pfnTraceLine)( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr );\n\tvoid\t(*pfnTraceToss)( edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr );\n\tint\t(*pfnTraceMonsterHull)( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr );\n\tvoid\t(*pfnTraceHull)( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr );\n\tvoid\t(*pfnTraceModel)( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr );\n\tconst char *(*pfnTraceTexture)( edict_t *pTextureEntity, const float *v1, const float *v2 );\n\tvoid\t(*pfnTraceSphere)( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr );\n\tvoid\t(*pfnGetAimVector)( edict_t* ent, float speed, float *rgflReturn );\n\tvoid\t(*pfnServerCommand)( const char* str );\n\tvoid\t(*pfnServerExecute)( void );\n\tvoid\t(*pfnClientCommand)( edict_t* pEdict, char* szFmt, ... );\n\tvoid\t(*pfnParticleEffect)( const float *org, const float *dir, float color, float count );\n\tvoid\t(*pfnLightStyle)( int style, const char* val );\n\tint\t(*pfnDecalIndex)( const char *name );\n\tint\t(*pfnPointContents)( const float *rgflVector );\n\tvoid\t(*pfnMessageBegin)( int msg_dest, int msg_type, const float *pOrigin, edict_t *ed );\n\tvoid\t(*pfnMessageEnd)( void );\n\tvoid\t(*pfnWriteByte)( int iValue );\n\tvoid\t(*pfnWriteChar)( int iValue );\n\tvoid\t(*pfnWriteShort)( int iValue );\n\tvoid\t(*pfnWriteLong)( int iValue );\n\tvoid\t(*pfnWriteAngle)( float flValue );\n\tvoid\t(*pfnWriteCoord)( float flValue );\n\tvoid\t(*pfnWriteString)( const char *sz );\n\tvoid\t(*pfnWriteEntity)( int iValue );\n\tvoid\t(*pfnCVarRegister)( cvar_t *pCvar );\n\tfloat\t(*pfnCVarGetFloat)( const char *szVarName );\n\tconst char* (*pfnCVarGetString)( const char *szVarName );\n\tvoid\t(*pfnCVarSetFloat)( const char *szVarName, float flValue );\n\tvoid\t(*pfnCVarSetString)( const char *szVarName, const char *szValue );\n\tvoid\t(*pfnAlertMessage)( ALERT_TYPE atype, char *szFmt, ... );\n\tvoid\t(*pfnEngineFprintf)( FILE *pfile, char *szFmt, ... );\n\tvoid*\t(*pfnPvAllocEntPrivateData)( edict_t *pEdict, long cb );\n\tvoid*\t(*pfnPvEntPrivateData)( edict_t *pEdict );\n\tvoid\t(*pfnFreeEntPrivateData)( edict_t *pEdict );\n\tconst char *(*pfnSzFromIndex)( int iString );\n\tint\t(*pfnAllocString)( const char *szValue );\n\tstruct entvars_s *(*pfnGetVarsOfEnt)( edict_t *pEdict );\n\tedict_t*\t(*pfnPEntityOfEntOffset)( int iEntOffset );\n\tint\t(*pfnEntOffsetOfPEntity)( const edict_t *pEdict );\n\tint\t(*pfnIndexOfEdict)( const edict_t *pEdict );\n\tedict_t*\t(*pfnPEntityOfEntIndex)( int iEntIndex );\n\tedict_t*\t(*pfnFindEntityByVars)( struct entvars_s* pvars );\n\tvoid*\t(*pfnGetModelPtr)( edict_t* pEdict );\n\tint\t(*pfnRegUserMsg)( const char *pszName, int iSize );\n\tvoid\t(*pfnAnimationAutomove)( const edict_t* pEdict, float flTime );\n\tvoid\t(*pfnGetBonePosition)( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles );\n\tunsigned long (*pfnFunctionFromName)( const char *pName );\n\tconst char *(*pfnNameForFunction)( unsigned long function );\n\tvoid\t(*pfnClientPrintf)( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients\n\tvoid\t(*pfnServerPrint)( const char *szMsg );\n\tconst char *(*pfnCmd_Args)( void );\t\t// these 3 added\n\tconst char *(*pfnCmd_Argv)( int argc );\t\t// so game DLL can easily\n\tint\t(*pfnCmd_Argc)( void );\t\t// access client 'cmd' strings\n\tvoid\t(*pfnGetAttachment)( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles );\n\tvoid\t(*pfnCRC32_Init)( CRC32_t *pulCRC );\n\tvoid\t(*pfnCRC32_ProcessBuffer)( CRC32_t *pulCRC, const void *p, int len );\n\tvoid\t(*pfnCRC32_ProcessByte)( CRC32_t *pulCRC, unsigned char ch );\n\tCRC32_t\t(*pfnCRC32_Final)( CRC32_t pulCRC );\n\tint\t\t(*pfnRandomLong)( int lLow, int lHigh );\n\tfloat\t(*pfnRandomFloat)( float flLow, float flHigh );\n\tvoid\t(*pfnSetView)( const edict_t *pClient, const edict_t *pViewent );\n\tfloat\t(*pfnTime)( void );\n\tvoid\t(*pfnCrosshairAngle)( const edict_t *pClient, float pitch, float yaw );\n\tbyte*\t(*pfnLoadFileForMe)( const char *filename, int *pLength );\n\tvoid\t(*pfnFreeFile)( void *buffer );\n\tvoid\t(*pfnEndSection)( const char *pszSectionName ); // trigger_endsection\n\tint\t(*pfnCompareFileTime)( const char *filename1, const char *filename2, int *iCompare );\n\tvoid\t(*pfnGetGameDir)( char *szGetGameDir );\n\tvoid\t(*pfnCvar_RegisterVariable)( cvar_t *variable );\n\tvoid\t(*pfnFadeClientVolume)( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds );\n\tvoid\t(*pfnSetClientMaxspeed)( const edict_t *pEdict, float fNewMaxspeed );\n\tedict_t\t*(*pfnCreateFakeClient)( const char *netname ); // returns NULL if fake client can't be created\n\tvoid\t(*pfnRunPlayerMove)( edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec );\n\tint\t(*pfnNumberOfEntities)( void );\n\tchar*\t(*pfnGetInfoKeyBuffer)( edict_t *e );\t\t\t// passing in NULL gets the serverinfo\n\tconst char*\t(*pfnInfoKeyValue)( const char *infobuffer, const char *key );\n\tvoid\t(*pfnSetKeyValue)( char *infobuffer, char *key, char *value );\n\tvoid\t(*pfnSetClientKeyValue)( int clientIndex, char *infobuffer, char *key, char *value );\n\tint\t(*pfnIsMapValid)( char *filename );\n\tvoid\t(*pfnStaticDecal)( const float *origin, int decalIndex, int entityIndex, int modelIndex );\n\tint\t(*pfnPrecacheGeneric)( const char *s );\n\tint\t(*pfnGetPlayerUserId)( edict_t *e ); // returns the server assigned userid for this player.  useful for logging frags, etc.  returns -1 if the edict couldn't be found in the list of clients\n\tvoid\t(*pfnBuildSoundMsg)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed );\n\tint\t(*pfnIsDedicatedServer)( void );\t\t\t// is this a dedicated server?\n\tcvar_t\t*(*pfnCVarGetPointer)( const char *szVarName );\n\tunsigned int (*pfnGetPlayerWONId)( edict_t *e ); // returns the server assigned WONid for this player.  useful for logging frags, etc.  returns -1 if the edict couldn't be found in the list of clients\n\n\t// YWB 8/1/99 TFF Physics additions\n\tvoid\t(*pfnInfo_RemoveKey)( char *s, const char *key );\n\tconst char *(*pfnGetPhysicsKeyValue)( const edict_t *pClient, const char *key );\n\tvoid\t(*pfnSetPhysicsKeyValue)( const edict_t *pClient, const char *key, const char *value );\n\tconst char *(*pfnGetPhysicsInfoString)( const edict_t *pClient );\n\tunsigned short (*pfnPrecacheEvent)( int type, const char*psz );\n\tvoid\t(*pfnPlaybackEvent)( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\n\n\tunsigned char *(*pfnSetFatPVS)( const float *org );\n\tunsigned char *(*pfnSetFatPAS)( const float *org );\n\n\tint\t(*pfnCheckVisibility )( const edict_t *entity, unsigned char *pset );\n\n\tvoid\t(*pfnDeltaSetField)\t( struct delta_s *pFields, const char *fieldname );\n\tvoid\t(*pfnDeltaUnsetField)( struct delta_s *pFields, const char *fieldname );\n\tvoid\t(*pfnDeltaAddEncoder)( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) );\n\tint\t(*pfnGetCurrentPlayer)( void );\n\tint\t(*pfnCanSkipPlayer)( const edict_t *player );\n\tint\t(*pfnDeltaFindField)( struct delta_s *pFields, const char *fieldname );\n\tvoid\t(*pfnDeltaSetFieldByIndex)( struct delta_s *pFields, int fieldNumber );\n\tvoid\t(*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber );\n\tvoid\t(*pfnSetGroupMask)( int mask, int op );\n\tint\t(*pfnCreateInstancedBaseline)( int classname, struct entity_state_s *baseline );\n\tvoid\t(*pfnCvar_DirectSet)( struct cvar_s *var, const char *value );\n\n\t// Forces the client and server to be running with the same version of the specified file\n\t//  ( e.g., a player model ).\n\t// Calling this has no effect in single player\n\tvoid\t(*pfnForceUnmodified)( FORCE_TYPE type, float *mins, float *maxs, const char *filename );\n\n\tvoid\t(*pfnGetPlayerStats)( const edict_t *pClient, int *ping, int *packet_loss );\n\n\tvoid\t(*pfnAddServerCommand)( const char *cmd_name, void (*function) (void) );\n\n\t// For voice communications, set which clients hear eachother.\n\t// NOTE: these functions take player entity indices (starting at 1).\n\tqboolean\t(*pfnVoice_GetClientListening)(int iReceiver, int iSender);\n\tqboolean\t(*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen);\n\n\tconst char *(*pfnGetPlayerAuthId)\t\t( edict_t *e );\n\n\tvoid*\t(*pfnSequenceGet)\t\t\t\t( const char* fileName, const char* entryName );\n\tvoid*\t(*pfnSequencePickSentence)\t\t( const char* groupName, int pickMethod, int *picked );\n\tint\t\t\t(*pfnGetFileSize)\t\t\t\t\t\t( const char *filename );\n\tunsigned int (*pfnGetApproxWavePlayLen)\t\t\t\t(const char *filepath);\n\tint\t\t\t(*pfnIsCareerMatch)\t\t\t\t\t\t( void );\n\tint\t\t\t(*pfnGetLocalizedStringLength)\t\t\t(const char *label);\n\tvoid\t\t(*pfnRegisterTutorMessageShown)\t\t\t(int mid);\n\tint\t\t\t(*pfnGetTimesTutorMessageShown)\t\t\t(int mid);\n\tvoid\t\t(*pfnProcessTutorMessageDecayBuffer)\t(int *buffer, int bufferLength);\n\tvoid\t\t(*pfnConstructTutorMessageDecayBuffer)\t(int *buffer, int bufferLength);\n\tvoid\t\t(*pfnResetTutorMessageDecayData)\t\t( void );\n\n\t// three useable funcs\n\tvoid\t(*pfnQueryClientCvarValue)( const edict_t *player, const char *cvarName );\n\tvoid\t(*pfnQueryClientCvarValue2)( const edict_t *player, const char *cvarName, int requestID );\n\tint\t(*pfnCheckParm)( char *parm, char **ppnext );\n\n\t// added in 8279\n\tedict_t* (*pfnPEntityOfEntIndexAllEntities)( int iEntIndex );\n} enginefuncs_t;\n// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT.  INTERFACE VERSION IS FROZEN AT 138\n\n// Passed to pfnKeyValue\ntypedef struct KeyValueData_s\n{\n\tchar\t*szClassName;\t// in: entity classname\n\tchar\t*szKeyName;\t// in: name of key\n\tchar\t*szValue;\t\t// in: value of key\n\tint\tfHandled;\t\t// out: DLL sets to true if key-value pair was understood\n} KeyValueData;\n\n\ntypedef struct\n{\n\tchar\tmapName[32];\n\tchar\tlandmarkName[32];\n\tedict_t\t*pentLandmark;\n\tvec3_t\tvecLandmarkOrigin;\n} LEVELLIST;\n\ntypedef struct\n{\n\tint\tid;\t\t// Ordinal ID of this entity (used for entity <--> pointer conversions)\n\tedict_t\t*pent;\t\t// Pointer to the in-game entity\n\n\tint\tlocation;\t\t// Offset from the base data of this entity\n\tint\tsize;\t\t// Byte size of this entity's data\n\tint\tflags;\t\t// This could be a short -- bit mask of transitions that this entity is in the PVS of\n\tstring_t\tclassname;\t// entity class name\n\n} ENTITYTABLE;\n\n#define MAX_LEVEL_CONNECTIONS\t\t16\t\t// These are encoded in the lower 16bits of ENTITYTABLE->flags\n\n#define FENTTABLE_PLAYER\t\t0x80000000\n#define FENTTABLE_REMOVED\t\t0x40000000\n#define FENTTABLE_MOVEABLE\t\t0x20000000\n#define FENTTABLE_GLOBAL\t\t0x10000000\n\ntypedef struct saverestore_s\n{\n\tchar\t\t*pBaseData;\t\t// Start of all entity save data\n\tchar\t\t*pCurrentData;\t\t// Current buffer pointer for sequential access\n\tint\t\tsize;\t\t\t// Current data size\n\tint\t\tbufferSize;\t\t// Total space for data\n\tint\t\ttokenSize;\t\t// Size of the linear list of tokens\n\tint\t\ttokenCount;\t\t// Number of elements in the pTokens table\n\tchar\t\t**pTokens;\t\t// Hash table of entity strings (sparse)\n\tint\t\tcurrentIndex;\t\t// Holds a global entity table ID\n\tint\t\ttableCount;\t\t// Number of elements in the entity table\n\tint\t\tconnectionCount;\t\t// Number of elements in the levelList[]\n\tENTITYTABLE\t*pTable;\t\t\t// Array of ENTITYTABLE elements (1 for each entity)\n\tLEVELLIST\t\tlevelList[MAX_LEVEL_CONNECTIONS];\t// List of connections from this level\n\n\t// smooth transition\n\tint\t\tfUseLandmark;\n\tchar\t\tszLandmarkName[20];\t\t// landmark we'll spawn near in next level\n\tvec3_t\t\tvecLandmarkOffset;\t\t// for landmark transitions\n\tfloat\t\ttime;\n\tchar\t\tszCurrentMapName[32];\t// To check global entities\n} SAVERESTOREDATA;\n\ntypedef enum _fieldtypes\n{\n\tFIELD_FLOAT = 0,\t\t// Any floating point value\n\tFIELD_STRING,\t\t// A string ID (return from ALLOC_STRING)\n\tFIELD_ENTITY,\t\t// An entity offset (EOFFSET)\n\tFIELD_CLASSPTR,\t\t// CBaseEntity *\n\tFIELD_EHANDLE,\t\t// Entity handle\n\tFIELD_EVARS,\t\t// EVARS *\n\tFIELD_EDICT,\t\t// edict_t *, or edict_t *  (same thing)\n\tFIELD_VECTOR,\t\t// Any vector\n\tFIELD_POSITION_VECTOR,\t// A world coordinate (these are fixed up across level transitions automagically)\n\tFIELD_POINTER,\t\t// Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER\n\tFIELD_INTEGER,\t\t// Any integer or enum\n\tFIELD_FUNCTION,\t\t// A class function pointer (Think, Use, etc)\n\tFIELD_BOOLEAN,\t\t// boolean, implemented as an int, I may use this as a hint for compression\n\tFIELD_SHORT,\t\t// 2 byte integer\n\tFIELD_CHARACTER,\t\t// a byte\n\tFIELD_TIME,\t\t// a floating point time (these are fixed up automatically too!)\n\tFIELD_MODELNAME,\t\t// Engine string that is a model name (needs precache)\n\tFIELD_SOUNDNAME,\t\t// Engine string that is a sound name (needs precache)\n\n\tFIELD_TYPECOUNT,\t\t// MUST BE LAST\n} FIELDTYPE;\n\n#ifndef offsetof\n#ifdef __GNUC__\n#define offsetof(s,m) __builtin_offsetof(s,m)\n#else\n#define offsetof(s,m) (size_t)&(((s *)0)->m)\n#endif\n#endif\n\n#define _FIELD(type,name,fieldtype,count,flags)\t\t{ fieldtype, #name, offsetof(type, name), count, flags }\n#define DEFINE_FIELD(type,name,fieldtype)\t\t_FIELD(type, name, fieldtype, 1, 0)\n#define DEFINE_ARRAY(type,name,fieldtype,count)\t\t_FIELD(type, name, fieldtype, count, 0)\n#define DEFINE_ENTITY_FIELD(name,fieldtype)\t\t_FIELD(entvars_t, name, fieldtype, 1, 0 )\n#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype)\t_FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL )\n#define DEFINE_GLOBAL_FIELD(type,name,fieldtype)\t\t_FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL )\n\n#define FTYPEDESC_GLOBAL\t\t0x0001\t\t// This field is masked for global entity save/restore\n#define FTYPEDESC_SAVE\t\t0x0002\t\t// This field is saved to disk\n#define FTYPEDESC_KEY\t\t0x0004\t\t// This field can be requested and written to by string name at load time\n#define FTYPEDESC_FUNCTIONTABLE\t0x0008\t\t// This is a table entry for a member function pointer\n\ntypedef struct\n{\n\tFIELDTYPE\t\tfieldType;\n\tconst char\t\t*fieldName;\n\tint\t\tfieldOffset;\n\tshort\t\tfieldSize;\n\tshort\t\tflags;\n} TYPEDESCRIPTION;\n\n#undef ARRAYSIZE\n#define ARRAYSIZE(p)\t(sizeof(p)/sizeof(p[0]))\n\nstruct weapon_data_s;\nstruct playermove_s;\nstruct clientdata_s;\nstruct usercmd_s;\nstruct edict_s;\nstruct netadr_s;\n\ntypedef struct\n{\n\t// Initialize/shutdown the game (one-time call after loading of game .dll )\n\tvoid\t(*pfnGameInit)( void );\n\tint\t(*pfnSpawn)( edict_t *pent );\n\tvoid\t(*pfnThink)( edict_t *pent );\n\tvoid\t(*pfnUse)( edict_t *pentUsed, edict_t *pentOther );\n\tvoid\t(*pfnTouch)( edict_t *pentTouched, edict_t *pentOther );\n\tvoid\t(*pfnBlocked)( edict_t *pentBlocked, edict_t *pentOther );\n\tvoid\t(*pfnKeyValue)( edict_t *pentKeyvalue, KeyValueData *pkvd );\n\tvoid\t(*pfnSave)( edict_t *pent, SAVERESTOREDATA *pSaveData );\n\tint \t(*pfnRestore)( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity );\n\tvoid\t(*pfnSetAbsBox)( edict_t *pent );\n\n\tvoid\t(*pfnSaveWriteFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int );\n\tvoid\t(*pfnSaveReadFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int );\n\tvoid\t(*pfnSaveGlobalState)( SAVERESTOREDATA * );\n\tvoid\t(*pfnRestoreGlobalState)( SAVERESTOREDATA * );\n\tvoid\t(*pfnResetGlobalState)( void );\n\n\tqboolean\t(*pfnClientConnect)( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128] );\n\n\tvoid\t(*pfnClientDisconnect)( edict_t *pEntity );\n\tvoid\t(*pfnClientKill)( edict_t *pEntity );\n\tvoid\t(*pfnClientPutInServer)( edict_t *pEntity );\n\tvoid\t(*pfnClientCommand)( edict_t *pEntity );\n\tvoid\t(*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer );\n\tvoid\t(*pfnServerActivate)( edict_t *pEdictList, int edictCount, int clientMax );\n\tvoid\t(*pfnServerDeactivate)( void );\n\tvoid\t(*pfnPlayerPreThink)( edict_t *pEntity );\n\tvoid\t(*pfnPlayerPostThink)( edict_t *pEntity );\n\n\tvoid\t(*pfnStartFrame)( void );\n\tvoid\t(*pfnParmsNewLevel)( void );\n\tvoid\t(*pfnParmsChangeLevel)( void );\n\n\t // Returns string describing current .dll.  E.g., TeamFotrress 2, Half-Life\n\tconst char     *(*pfnGetGameDescription)( void );\n\n\t// Notify dll about a player customization.\n\tvoid\t(*pfnPlayerCustomization)( edict_t *pEntity, customization_t *pCustom );\n\n\t// Spectator funcs\n\tvoid\t(*pfnSpectatorConnect)( edict_t *pEntity );\n\tvoid\t(*pfnSpectatorDisconnect)( edict_t *pEntity );\n\tvoid\t(*pfnSpectatorThink)( edict_t *pEntity );\n\n\t// Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint.\n\tvoid\t(*pfnSys_Error)( const char *error_string );\n\n\tvoid\t(*pfnPM_Move)( struct playermove_s *ppmove, qboolean server );\n\tvoid\t(*pfnPM_Init)( struct playermove_s *ppmove );\n\tchar\t(*pfnPM_FindTextureType)( char *name );\n\tvoid\t(*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas );\n\tvoid\t(*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd );\n\tint\t(*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet );\n\tvoid\t(*pfnCreateBaseline)( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs );\n\tvoid\t(*pfnRegisterEncoders)( void );\n\tint\t(*pfnGetWeaponData)( struct edict_s *player, struct weapon_data_s *info );\n\n\tvoid\t(*pfnCmdStart)( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed );\n\tvoid\t(*pfnCmdEnd)( const edict_t *player );\n\n\t// Return 1 if the packet is valid.  Set response_buffer_size if you want to send a response packet.  Incoming, it holds the max\n\t//  size of the response_buffer, so you must zero it out if you choose not to respond.\n\tint\t(*pfnConnectionlessPacket )( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size );\n\n\t// Enumerates player hulls.  Returns 0 if the hull number doesn't exist, 1 otherwise\n\tint\t(*pfnGetHullBounds)\t( int hullnumber, float *mins, float *maxs );\n\n\t// Create baselines for certain \"unplaced\" items.\n\tvoid\t(*pfnCreateInstancedBaselines) ( void );\n\n\t// One of the pfnForceUnmodified files failed the consistency check for the specified player\n\t// Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters )\n\tint\t(*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message );\n\n\t// The game .dll should return 1 if lag compensation should be allowed ( could also just set\n\t//  the sv_unlag cvar.\n\t// Most games right now should return 0, until client-side weapon prediction code is written\n\t//  and tested for them.\n\tint\t(*pfnAllowLagCompensation)( void );\n} DLL_FUNCTIONS;\n\nextern DLL_FUNCTIONS\t\tgEntityInterface;\n\n// Current version.\n#define NEW_DLL_FUNCTIONS_VERSION\t1\n\ntypedef struct\n{\n\t// Called right before the object's memory is freed.\n\t// Calls its destructor.\n\tvoid\t(*pfnOnFreeEntPrivateData)( edict_t *pEnt );\n\tvoid\t(*pfnGameShutdown)(void);\n\tint\t(*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther );\n\tvoid\t(*pfnCvarValue)( const edict_t *pEnt, const char *value );\n\tvoid\t(*pfnCvarValue2)( const edict_t *pEnt, int requestID, const char *cvarName, const char *value );\n} NEW_DLL_FUNCTIONS;\ntypedef int\t(*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion );\n\n// Pointers will be null if the game DLL doesn't support this API.\nextern NEW_DLL_FUNCTIONS\t\tgNewDLLFunctions;\n\ntypedef int\t(*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion );\ntypedef int\t(*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion );\n\n#endif//EIFACE_H\n"
  },
  {
    "path": "engine/key_modifiers.h",
    "content": "/*\nkey_modifiers.h - enumeration of possible key modifiers\nCopyright (C) 2022 FWGS Team\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef KEY_MODIFIERS_H\n#define KEY_MODIFIERS_H\n\ntypedef enum\n{\n\tKeyModifier_None = 0,\n\tKeyModifier_LeftShift = (1 << 0),\n\tKeyModifier_RightShift = (1 << 1),\n\tKeyModifier_LeftCtrl = (1 << 2),\n\tKeyModifier_RightCtrl = (1 << 3),\n\tKeyModifier_LeftAlt = (1 << 4),\n\tKeyModifier_RightAlt = (1 << 5),\n\tKeyModifier_LeftSuper = (1 << 6),\n\tKeyModifier_RightSuper = (1 << 7),\n\tKeyModifier_NumLock = (1 << 8),\n\tKeyModifier_CapsLock = (1 << 9)\n} key_modifier_t;\n\n#endif\n"
  },
  {
    "path": "engine/keydefs.h",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n// taken from Quake-2 source code and modified for GoldSrc compatibility\n\n//\n// these are the key numbers that should be passed to Key_Event\n// normal keys should be passed as lowercased ascii\n//\n#define K_TAB        9\n#define K_ENTER      13\n#define K_ESCAPE     27\n#define K_SPACE      32\n#define K_SCROLLLOCK 70\n#define K_BACKSPACE  127\n#define K_UPARROW    128\n#define K_DOWNARROW  129\n#define K_LEFTARROW  130\n#define K_RIGHTARROW 131\n#define K_ALT        132\n#define K_CTRL       133\n#define K_SHIFT      134\n#define K_F1         135\n#define K_F2         136\n#define K_F3         137\n#define K_F4         138\n#define K_F5         139\n#define K_F6         140\n#define K_F7         141\n#define K_F8         142\n#define K_F9         143\n#define K_F10        144\n#define K_F11        145\n#define K_F12        146\n#define K_INS        147\n#define K_DEL        148\n#define K_PGDN       149\n#define K_PGUP       150\n#define K_HOME       151\n#define K_END        152\n\n#define K_KP_HOME       160\n#define K_KP_UPARROW    161\n#define K_KP_PGUP       162\n#define K_KP_LEFTARROW  163\n#define K_KP_5          164\n#define K_KP_RIGHTARROW\t165\n#define K_KP_END        166\n#define K_KP_DOWNARROW  167\n#define K_KP_PGDN       168\n#define K_KP_ENTER      169\n#define K_KP_INS        170\n#define K_KP_DEL        171\n#define K_KP_SLASH      172\n#define K_KP_MINUS      173\n#define K_KP_PLUS       174\n#define K_CAPSLOCK      175\n#define K_KP_MUL        176\n#define K_WIN           177\n#define K_KP_NUMLOCK    178\n\n//\n// joystick buttons\n//\n#define K_JOY1 203\n#define K_JOY2 204\n#define K_JOY3 205\n#define K_JOY4 206\n\n//\n// aux keys are for multi-buttoned joysticks to generate so they can use\n// the normal binding process\n//\n#define K_AUX1  207\n#define K_AUX2  208\n#define K_AUX3  209\n#define K_AUX4  210\n#define K_AUX5  211\n#define K_AUX6  212\n#define K_AUX7  213\n#define K_AUX8  214\n#define K_AUX9  215\n#define K_AUX10 216\n#define K_AUX11 217\n#define K_AUX12 218\n#define K_AUX13 219\n#define K_AUX14 220\n#define K_AUX15 221\n#define K_AUX16 222\n#define K_AUX17 223\n#define K_AUX18 224\n#define K_AUX19 225\n#define K_AUX20 226\n#define K_AUX21 227\n#define K_AUX22 228\n#define K_AUX23 229\n#define K_AUX24 230\n#define K_AUX25 231\n#define K_AUX26 232\n#define K_AUX27 233\n#define K_AUX28 234\n#define K_AUX29 235\n#define K_AUX30 236\n#define K_AUX31 237\n#define K_AUX32 238\n\n//\n// button names for game pads\n//\n#define K_A_BUTTON       K_AUX1\n#define K_B_BUTTON       K_AUX2\n#define K_X_BUTTON       K_AUX3\n#define K_Y_BUTTON       K_AUX4\n#define K_L1_BUTTON      K_AUX5\n#define K_R1_BUTTON      K_AUX6\n#define K_BACK_BUTTON    K_AUX7\n#define K_MODE_BUTTON    K_AUX8\n#define K_START_BUTTON   K_AUX9\n#define K_LSTICK         K_AUX10\n#define K_RSTICK         K_AUX11\n#define K_L2_BUTTON      K_AUX12\n#define K_R2_BUTTON      K_AUX13\n#define K_C_BUTTON       K_AUX14\n#define K_Z_BUTTON       K_AUX15\n#define K_DPAD_UP        K_AUX16\n#define K_DPAD_DOWN      K_AUX17\n#define K_DPAD_LEFT      K_AUX18\n#define K_DPAD_RIGHT     K_AUX19\n#define K_MISC_BUTTON    K_AUX20\n#define K_PADDLE1_BUTTON K_AUX21\n#define K_PADDLE2_BUTTON K_AUX22\n#define K_PADDLE3_BUTTON K_AUX23\n#define K_PADDLE4_BUTTON K_AUX24\n#define K_TOUCHPAD       K_AUX25\n\n//\n// mouse buttons generate virtual keys\n//\n#define K_MWHEELDOWN 239\n#define K_MWHEELUP   240\n#define K_MOUSE1     241\n#define K_MOUSE2     242\n#define K_MOUSE3     243\n#define K_MOUSE4     244\n#define K_MOUSE5     245\n\n#define K_PAUSE 255\n"
  },
  {
    "path": "engine/menu_int.h",
    "content": "/*\nmenu_int.h - interface between engine and menu\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef MENU_INT_H\n#define MENU_INT_H\n\n#include \"cvardef.h\"\n#include \"gameinfo.h\"\n#include \"wrect.h\"\n#include \"ref_device.h\"\n#include \"net_api.h\"\n\n// a macro for mainui_cpp, indicating that mainui should be compiled for\n// Xash3D 1.0 interface\n#define NEW_ENGINE_INTERFACE\n\ntypedef int\t\tHIMAGE;\t\t// handle to a graphic\n\n// flags for PIC_Load\n#define PIC_NEAREST\t\t(1<<0)\t\t// disable texfilter\n#define PIC_KEEP_SOURCE\t(1<<1)\t\t// some images keep source\n#define PIC_NOFLIP_TGA\t(1<<2)\t\t// Steam background completely ignore tga attribute 0x20\n#define PIC_EXPAND_SOURCE (1<<3)\t\t// don't keep as 8-bit source, expand to RGBA\n\n// flags for COM_ParseFileSafe\n#define PFILE_IGNOREBRACKET (1<<0)\n#define PFILE_HANDLECOLON   (1<<1)\n#define PFILE_IGNOREHASHCMT (1<<2)\n\ntypedef struct ui_globalvars_s\n{\n\tfloat\t\ttime;\t\t// unclamped host.realtime\n\tfloat\t\tframetime;\n\n\tint\t\tscrWidth;\t\t// actual values\n\tint\t\tscrHeight;\n\n\tint\t\tmaxClients;\n\tint\t\tdeveloper; // boolean, changed from allow_console to make mainui_cpp compile for both engines\n\tint\t\tdemoplayback;\n\tint\t\tdemorecording;\n\tchar\t\tdemoname[64];\t// name of currently playing demo\n\tchar\t\tmaptitle[64];\t// title of active map\n} ui_globalvars_t;\n\nstruct ref_viewpass_s;\n\ntypedef struct ui_enginefuncs_s\n{\n\t// image handlers\n\tHIMAGE\t(*pfnPIC_Load)( const char *szPicName, const byte *ucRawImage, int ulRawImageSize, int flags );\n\tvoid\t(*pfnPIC_Free)( const char *szPicName );\n\tint\t(*pfnPIC_Width)( HIMAGE hPic );\n\tint\t(*pfnPIC_Height)( HIMAGE hPic );\n\tvoid\t(*pfnPIC_Set)( HIMAGE hPic, int r, int g, int b, int a );\n\tvoid\t(*pfnPIC_Draw)( int x, int y, int width, int height, const wrect_t *prc );\n\tvoid\t(*pfnPIC_DrawHoles)( int x, int y, int width, int height, const wrect_t *prc );\n\tvoid\t(*pfnPIC_DrawTrans)( int x, int y, int width, int height, const wrect_t *prc );\n\tvoid\t(*pfnPIC_DrawAdditive)( int x, int y, int width, int height, const wrect_t *prc );\n\tvoid\t(*pfnPIC_EnableScissor)( int x, int y, int width, int height );\n\tvoid\t(*pfnPIC_DisableScissor)( void );\n\n\t// screen handlers\n\tvoid\t(*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a );\n\n\t// cvar handlers\n\tcvar_t*\t(*pfnRegisterVariable)( const char *szName, const char *szValue, int flags );\n\tfloat\t(*pfnGetCvarFloat)( const char *szName );\n\tconst char*\t(*pfnGetCvarString)( const char *szName ) PFN_RETURNS_NONNULL;\n\tvoid\t(*pfnCvarSetString)( const char *szName, const char *szValue );\n\tvoid\t(*pfnCvarSetValue)( const char *szName, float flValue );\n\n\t// command handlers\n\tint\t(*pfnAddCommand)( const char *cmd_name, void (*function)(void) );\n\tvoid\t(*pfnClientCmd)( int execute_now, const char *szCmdString );\n\tvoid\t(*pfnDelCommand)( const char *cmd_name );\n\tint (*pfnCmdArgc)( void );\n\tconst char*\t(*pfnCmdArgv)( int argc ) PFN_RETURNS_NONNULL;\n\tconst char*\t(*pfnCmd_Args)( void ) PFN_RETURNS_NONNULL;\n\n\t// debug messages (in-menu shows only notify)\n\tvoid\t(*Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 );\n\tvoid\t(*Con_DPrintf)( const char *fmt, ... )  FORMAT_CHECK( 1 );\n\tvoid\t(*Con_NPrintf)( int pos, const char *fmt, ... )  FORMAT_CHECK( 2 );\n\tvoid\t(*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) FORMAT_CHECK( 2 );\n\n\t// sound handlers\n\tvoid\t(*pfnPlayLocalSound)( const char *szSound );\n\n\t// cinematic handlers\n\tvoid\t(*pfnDrawLogo)( const char *filename, float x, float y, float width, float height );\n\tint\t(*pfnGetLogoWidth)( void );\n\tint\t(*pfnGetLogoHeight)( void );\n\tfloat\t(*pfnGetLogoLength)( void );\t// cinematic duration in seconds\n\n\t// text message system\n\tvoid\t(*pfnDrawCharacter)( int x, int y, int width, int height, int ch, int ulRGBA, HIMAGE hFont );\n\tint\t(*pfnDrawConsoleString)( int x, int y, const char *string );\n\tvoid\t(*pfnDrawSetTextColor)( int r, int g, int b, int alpha );\n\tvoid\t(*pfnDrawConsoleStringLen)(  const char *string, int *length, int *height );\n\tvoid\t(*pfnSetConsoleDefaultColor)( int r, int g, int b ); // color must came from colors.lst\n\n\t// custom rendering (for playermodel preview)\n\tstruct cl_entity_s* (*pfnGetPlayerModel)( void );\t// for drawing playermodel previews\n\tvoid\t(*pfnSetModel)( struct cl_entity_s *ed, const char *path );\n\tvoid\t(*pfnClearScene)( void );\n\tvoid\t(*pfnRenderScene)( const struct ref_viewpass_s *rvp );\n\tint\t(*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent );\n\n\t// misc handlers\n\tvoid\t(*pfnHostError)( const char *szFmt, ... ) FORMAT_CHECK( 1 );\n\tint\t(*pfnFileExists)( const char *filename, int gamedironly );\n\tvoid\t(*pfnGetGameDir)( char *szGetGameDir );\n\n\t// gameinfo handlers\n\tint\t(*pfnCreateMapsList)( int fRefresh );\n\tint\t(*pfnClientInGame)( void );\n\tvoid\t(*pfnClientJoin)( const struct netadr_s adr );\n\n\t// parse txt files\n\tbyte*\t(*COM_LoadFile)( const char *filename, int *pLength );\n\tchar*\t(*COM_ParseFile)( char *data, char *token );\n\tvoid\t(*COM_FreeFile)( void *buffer );\n\n\t// keyfuncs\n\tvoid\t(*pfnKeyClearStates)( void );\t\t\t\t// call when menu open or close\n\tvoid\t(*pfnSetKeyDest)( int dest );\n\tconst char *(*pfnKeynumToString)( int keynum );\n\tconst char *(*pfnKeyGetBinding)( int keynum );\n\tvoid\t(*pfnKeySetBinding)( int keynum, const char *binding );\n\tint\t(*pfnKeyIsDown)( int keynum );\n\tint\t(*pfnKeyGetOverstrikeMode)( void );\n\tvoid\t(*pfnKeySetOverstrikeMode)( int fActive );\n\tvoid\t*(*pfnKeyGetState)( const char *name );\t\t\t// for mlook, klook etc\n\n\t// engine memory manager\n\tvoid*\t(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 );\n\tvoid\t(*pfnMemFree)( void *mem, const char *filename, const int fileline );\n\n\t// collect info from engine\n\tint\t(*pfnGetGameInfo)( GAMEINFO *pgameinfo );\n\tGAMEINFO\t**(*pfnGetGamesList)( int *numGames );\t\t\t// collect info about all mods\n\tchar \t**(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly );\t// find in files\n\tint (*pfnGetSaveComment)( const char *savename, char *comment );\n\tint\t(*pfnGetDemoComment)( const char *demoname, char *comment );\n\tint\t(*pfnCheckGameDll)( void );\t\t\t\t// returns false if hl.dll is missed or invalid\n\tchar\t*(*pfnGetClipboardData)( void );\n\n\t// engine launcher\n\tvoid\t(*pfnShellExecute)( const char *name, const char *args, int closeEngine );\n\tvoid\t(*pfnWriteServerConfig)( const char *name );\n\tvoid\t(*pfnChangeInstance)( const char *newInstance, const char *szFinalMessage );\n\tvoid\t(*pfnPlayBackgroundTrack)( const char *introName, const char *loopName );\n\tvoid\t(*pfnHostEndGame)( const char *szFinalMessage );\n\n\t// menu interface is freezed at version 0.75\n\t// new functions starts here\n\tfloat\t(*pfnRandomFloat)( float flLow, float flHigh );\n\tint\t\t(*pfnRandomLong)( int lLow, int lHigh );\n\n\tvoid\t(*pfnSetCursor)( void *hCursor );\t\t\t// change cursor\n\tint\t(*pfnIsMapValid)( char *filename );\n\tvoid\t(*pfnProcessImage)( int texnum, float gamma, int topColor, int bottomColor );\n\tint\t(*pfnCompareFileTime)( const char *filename1, const char *filename2, int *iCompare );\n\n\tconst char *(*pfnGetModeString)( int vid_mode );\n\tint\t(*COM_SaveFile)( const char *filename, const void *data, int len );\n\tint\t(*COM_RemoveFile)( const char *filepath );\n} ui_enginefuncs_t;\n\ntypedef struct\n{\n\tint\t(*pfnVidInit)( void );\n\tvoid\t(*pfnInit)( void );\n\tvoid\t(*pfnShutdown)( void );\n\tvoid\t(*pfnRedraw)( float flTime );\n\tvoid\t(*pfnKeyEvent)( int key, int down );\n\tvoid\t(*pfnMouseMove)( int x, int y );\n\tvoid\t(*pfnSetActiveMenu)( int active );\n\tvoid\t(*pfnAddServerToList)( struct netadr_s adr, const char *info );\n\tvoid\t(*pfnGetCursorPos)( int *pos_x, int *pos_y );\n\tvoid\t(*pfnSetCursorPos)( int pos_x, int pos_y );\n\tvoid\t(*pfnShowCursor)( int show );\n\tvoid\t(*pfnCharEvent)( int key );\n\tint\t(*pfnMouseInRect)( void );\t// mouse entering\\leave game window\n\tint\t(*pfnIsVisible)( void );\n\tint\t(*pfnCreditsActive)( void );\t// unused\n\tvoid\t(*pfnFinalCredits)( void );\t// show credits + game end\n} UI_FUNCTIONS;\n\n#define MENU_EXTENDED_API_VERSION 1\n\ntypedef struct ui_extendedfuncs_s {\n\t// text functions, frozen\n\tvoid (*pfnEnableTextInput)( int enable );\n\tint (*pfnUtfProcessChar) ( int ch );\n\tint (*pfnUtfMoveLeft) ( char *str, int pos );\n\tint (*pfnUtfMoveRight) ( char *str, int pos, int length );\n\n\t// new engine extended api start here\n\t// returns 1 if there are more in list, otherwise 0\n\tint (*pfnGetRenderers)( unsigned int num, char *short_name, size_t size1, char *long_name, size_t size2 );\n\tdouble (*pfnDoubleTime)( void );\n\tchar *(*pfnParseFile)( char *data, char *buf, const int size, unsigned int flags, int *len );\n\n\t// network address funcs\n\tconst char *(*pfnAdrToString)( const struct netadr_s a ) PFN_RETURNS_NONNULL;\n\tint (*pfnCompareAdr)( const void *a, const void *b ); // netadr_t\n\n\tvoid *(*pfnGetNativeObject)( const char *name );\n\tstruct net_api_s *pNetAPI;\n\n\t// new mods info\n\tgameinfo2_t *(*pfnGetGameInfo)( int gi_version ); // might return NULL if gi_version is unsupported\n\tgameinfo2_t *(*pfnGetModInfo)( int gi_version, int mod_index ); // continiously call it until it returns null\n\n\t// returns 1 if cvar has read-only flag\n\t// or -1 if cvar not found\n\tint (*pfnIsCvarReadOnly)( const char *name );\n\n\tconst ref_device_t *(*pfnGetRenderDevice)( unsigned int idx );\n} ui_extendedfuncs_t;\n\n// deprecated export from old engine\ntypedef void (*ADDTOUCHBUTTONTOLIST)( const char *name, const char *texture, const char *command, unsigned char *color, int flags );\n\ntypedef struct\n{\n\tADDTOUCHBUTTONTOLIST pfnAddTouchButtonToList;\n\tvoid (*pfnResetPing)( void );\n\tvoid (*pfnShowConnectionWarning)( void );\n\tvoid (*pfnShowUpdateDialog)( int preferStore );\n\tvoid (*pfnShowMessageBox)( const char *text );\n\tvoid (*pfnConnectionProgress_Disconnect)( void );\n\tvoid (*pfnConnectionProgress_Download)( const char *pszFileName, const char *pszServerName, int iCurrent, int iTotal, const char *comment );\n\tvoid (*pfnConnectionProgress_DownloadEnd)( void );\n\tvoid (*pfnConnectionProgress_Precache)( void );\n\tvoid (*pfnConnectionProgress_Connect)( const char *server ); // NULL for local server\n\tvoid (*pfnConnectionProgress_ChangeLevel)( void );\n\tvoid (*pfnConnectionProgress_ParseServerInfo)( const char *server );\n} UI_EXTENDED_FUNCTIONS;\n\ntypedef int (*MENUAPI)( UI_FUNCTIONS *pFunctionTable, ui_enginefuncs_t* engfuncs, ui_globalvars_t *pGlobals );\n\ntypedef int (*UIEXTENEDEDAPI)( int version, UI_EXTENDED_FUNCTIONS *pFunctionTable, ui_extendedfuncs_t *engfuncs );\n\n// deprecated interface from old engine\ntypedef int (*UITEXTAPI)( ui_extendedfuncs_t* engfuncs );\n\n#define PLATFORM_UPDATE_PAGE \"PlatformUpdatePage\"\n#define GENERIC_UPDATE_PAGE \"GenericUpdatePage\"\n\n#endif//MENU_INT_H\n"
  },
  {
    "path": "engine/mobility_int.h",
    "content": "/*\nmobility_int.h - interface between engine and client for mobile platforms\nCopyright (C) 2015 a1batross\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>\n*/\n\n#pragma once\n#ifndef MOBILITY_INT_H\n#define MOBILITY_INT_H\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define MOBILITY_API_VERSION 2\n#define MOBILITY_CLIENT_EXPORT \"HUD_MobilityInterface\"\n\n#define VIBRATE_NORMAL (1U << 0) // just vibrate for given \"life\"\n\n#define TOUCH_FL_HIDE\t\t\t(1U << 0)\n#define TOUCH_FL_NOEDIT\t\t\t(1U << 1)\n#define TOUCH_FL_CLIENT\t\t\t(1U << 2)\n#define TOUCH_FL_MP\t\t\t\t(1U << 3)\n#define TOUCH_FL_SP\t\t\t\t(1U << 4)\n#define TOUCH_FL_DEF_SHOW\t\t(1U << 5)\n#define TOUCH_FL_DEF_HIDE\t\t(1U << 6)\n#define TOUCH_FL_DRAW_ADDITIVE\t(1U << 7)\n#define TOUCH_FL_STROKE\t\t\t(1U << 8)\n#define TOUCH_FL_PRECISION\t\t(1U << 9)\n\n// flags for COM_ParseFileSafe\n#define PFILE_IGNOREBRACKET (1<<0)\n#define PFILE_HANDLECOLON   (1<<1)\n#define PFILE_IGNOREHASHCMT (1<<2)\n\ntypedef struct mobile_engfuncs_s\n{\n\t// indicates version of API. Should be equal to MOBILITY_API_VERSION\n\t// version changes when existing functions are changes\n\tint version;\n\n\t// vibration control\n\t// life -- time to vibrate in ms\n\tvoid (*pfnVibrate)( float life, char flags );\n\n\t// enable text input\n\tvoid (*pfnEnableTextInput)( int enable );\n\n\t// add temporaty button, edit will be disabled\n\tvoid (*pfnTouchAddClientButton)( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, unsigned char *color, int round, float aspect, int flags );\n\n\t// add button to defaults list. Will be loaded on config generation\n\tvoid (*pfnTouchAddDefaultButton)( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, unsigned char *color, int round, float aspect, int flags );\n\n\t// hide/show buttons by pattern\n\tvoid (*pfnTouchHideButtons)( const char *name, unsigned char hide );\n\n\t// remove button with given name\n\tvoid (*pfnTouchRemoveButton)( const char *name );\n\n\t// when enabled, only client buttons shown\n\tvoid (*pfnTouchSetClientOnly)( unsigned char state );\n\n\t// Clean defaults list\n\tvoid (*pfnTouchResetDefaultButtons)( void );\n\n\t// Draw scaled font for client\n\tint (*pfnDrawScaledCharacter)( int x, int y, int number, int r, int g, int b, float scale );\n\n\tvoid (*pfnSys_Warn)( const char *format, ... );\n\n\t// Get native object for current platform.\n\t// Pass NULL to arguments to receive an array of available objects or NULL if nothing\n\tvoid *(*pfnGetNativeObject)( const char *obj );\n\n\tvoid (*pfnSetCustomClientID)( const char *id );\n\n\t// COM_ParseFile but with buffer size limit, len reports written size or -1 on overflow\n\tchar* (*pfnParseFile)( char *data, char *buf, const int size, unsigned int flags, int *len );\n\t// To be continued...\n} mobile_engfuncs_t;\n\n// function exported from client\n// returns 0 on no error otherwise error\ntypedef int (*pfnMobilityInterface)( mobile_engfuncs_t *gMobileEngfuncs );\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n"
  },
  {
    "path": "engine/physint.h",
    "content": "/*\nphysint.h - Server Physics Interface\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef PHYSINT_H\n#define PHYSINT_H\n\n#include \"eiface.h\" // offsetof\n\n#define SV_PHYSICS_INTERFACE_VERSION\t6\n\n#define STRUCT_FROM_LINK( l, t, m )\t((t *)((byte *)l - offsetof(t, m)))\n#define EDICT_FROM_AREA( l )\t\tSTRUCT_FROM_LINK( l, edict_t, area )\n\n// values that can be returned with pfnServerState\n#define SERVER_DEAD\t\t\t0\n#define SERVER_LOADING\t\t1\n#define SERVER_ACTIVE\t\t2\n\n// LUMP reading errors\n#define LUMP_LOAD_OK\t\t0\n#define LUMP_LOAD_COULDNT_OPEN\t1\n#define LUMP_LOAD_BAD_HEADER\t\t2\n#define LUMP_LOAD_BAD_VERSION\t\t3\n#define LUMP_LOAD_NO_EXTRADATA\t4\n#define LUMP_LOAD_INVALID_NUM\t\t5\n#define LUMP_LOAD_NOT_EXIST\t\t6\n#define LUMP_LOAD_MEM_FAILED\t\t7\n#define LUMP_LOAD_CORRUPTED\t\t8\n\n// LUMP saving errors\n#define LUMP_SAVE_OK\t\t0\n#define LUMP_SAVE_COULDNT_OPEN\t1\n#define LUMP_SAVE_BAD_HEADER\t\t2\n#define LUMP_SAVE_BAD_VERSION\t\t3\n#define LUMP_SAVE_NO_EXTRADATA\t4\n#define LUMP_SAVE_INVALID_NUM\t\t5\n#define LUMP_SAVE_ALREADY_EXIST\t6\n#define LUMP_SAVE_NO_DATA\t\t7\n#define LUMP_SAVE_CORRUPTED\t\t8\n\n#ifndef ALLOC_CHECK\n#define ALLOC_CHECK( x )\n#endif\n\ntypedef struct areanode_s\n{\n\tint\t\taxis;\t\t// -1 = leaf node\n\tfloat\t\tdist;\n\tstruct areanode_s\t*children[2];\n\tlink_t\t\ttrigger_edicts;\n\tlink_t\t\tsolid_edicts;\n\tlink_t\t\tportal_edicts;\n} areanode_t;\n\ntypedef struct server_physics_api_s\n{\n\t// unlink edict from old position and link onto new\n\tvoid\t\t( *pfnLinkEdict) ( edict_t *ent, qboolean touch_triggers );\n\tdouble\t\t( *pfnGetServerTime )( void ); // unclamped\n\tdouble\t\t( *pfnGetFrameTime )( void );\t// unclamped\n\tvoid*\t\t( *pfnGetModel )( int modelindex );\n\tareanode_t*\t( *pfnGetHeadnode )( void ); // AABB tree for all physic entities\n\tint\t\t( *pfnServerState )( void );\n\tvoid\t\t( *pfnHost_Error )( const char *error, ... );\t// cause Host Error\n// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT.  INTERFACE VERSION IS FROZEN AT 6\n\tstruct triangleapi_s *pTriAPI;\t// draw coliisions etc. Only for local system\n\n\t// draw debug messages (must be called from DrawOrthoTriangles). Only for local system\n\tint\t\t( *pfnDrawConsoleString )( int x, int y, char *string );\n\tvoid\t\t( *pfnDrawSetTextColor )( float r, float g, float b );\n\tvoid\t\t( *pfnDrawConsoleStringLen )( const char *string, int *length, int *height );\n\tvoid\t\t( *Con_NPrintf )( int pos, const char *fmt, ... );\n\tvoid\t\t( *Con_NXPrintf )( struct con_nprint_s *info, const char *fmt, ... );\n\tconst char\t*( *pfnGetLightStyle )( int style ); // read custom appreance for selected lightstyle\n\tvoid\t\t( *pfnUpdateFogSettings )( unsigned int packed_fog );\n\tchar\t\t**(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly );\n\tstruct msurface_s\t*(*pfnTraceSurface)( edict_t *pTextureEntity, const float *v1, const float *v2 );\n\tconst byte\t*(*pfnGetTextureData)( unsigned int texnum );\n\n\t// static allocations\n\tvoid\t\t*(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 );\n\tvoid\t\t(*pfnMemFree)( void *mem, const char *filename, const int fileline );\n\n\t// trace & contents\n\tint\t\t(*pfnMaskPointContents)( const float *pos, int groupmask );\n\ttrace_t\t\t(*pfnTrace)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e );\n\ttrace_t\t\t(*pfnTraceNoEnts)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e );\n\tint\t\t(*pfnBoxInPVS)( const float *org, const float *boxmins, const float *boxmaxs );\n\n\t// message handler (missed function to write raw bytes)\n\tvoid\t\t(*pfnWriteBytes)( const byte *bytes, int count );\n\n\t// BSP lump management\n\tint\t\t(*pfnCheckLump)( const char *filename, const int lump, int *lumpsize );\n\tint\t\t(*pfnReadLump)( const char *filename, const int lump, void **lumpdata, int *lumpsize );\n\tint\t\t(*pfnSaveLump)( const char *filename, const int lump, void *lumpdata, int lumpsize );\n\n\t// FS tools\n\tint\t\t(*pfnSaveFile)( const char *filename, const void *data, int len );\n\tconst byte\t*(*pfnLoadImagePixels)( const char *filename, int *width, int *height );\n\n\tconst char *(*pfnGetModelName)( int modelindex );\n\n\t// FWGS extension\n\tvoid       *(*pfnGetNativeObject)( const char *object );\n} server_physics_api_t;\n\n// physic callbacks\ntypedef struct physics_interface_s\n{\n\tint\t\tversion;\n\t// passed through pfnCreate (0 is attempt to create, -1 is reject)\n\tint\t\t( *SV_CreateEntity )( edict_t *pent, const char *szName );\n\t// run custom physics for each entity (return 0 to use built-in engine physic)\n\tint\t\t( *SV_PhysicsEntity\t)( edict_t *pEntity );\n\t// spawn entities with internal mod function e.g. for re-arrange spawn order (0 - use engine parser, 1 - use mod parser)\n\tint\t\t( *SV_LoadEntities )( const char *mapname, char *entities );\n\t// update conveyor belt for clients\n\tvoid\t\t( *SV_UpdatePlayerBaseVelocity )( edict_t *ent );\n\t// The game .dll should return 1 if save game should be allowed\n\tint\t\t( *SV_AllowSaveGame )( void );\n// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT.  INTERFACE VERSION IS FROZEN AT 6\n\t// override trigger area checking and touching\n\tint\t\t( *SV_TriggerTouch )( edict_t *pent, edict_t *trigger );\n\t// some engine features can be enabled only through this function\n\tunsigned int\t( *SV_CheckFeatures )( void );\n\t// used for draw debug collisions for custom physic engine etc\n\tvoid\t\t( *DrawDebugTriangles )( void );\n\t// used for draw debug overlay (textured)\n\tvoid\t\t( *DrawNormalTriangles )( void );\n\t// used for draw debug messages (2d mode)\n\tvoid\t\t( *DrawOrthoTriangles )( void );\n\t// tracing entities with SOLID_CUSTOM mode on a server (not used by pmove code)\n\tvoid\t\t( *ClipMoveToEntity)( edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace );\n\t// tracing entities with SOLID_CUSTOM mode on a server (only used by pmove code)\n\tvoid\t\t( *ClipPMoveToEntity)( struct physent_s *pe, const float *start, float *mins, float *maxs, const float *end, struct pmtrace_s *tr );\n\t// called at end the frame of SV_Physics call\n\tvoid\t\t( *SV_EndFrame )( void );\n\t// obsolete\n\tvoid\t\t(*pfnPrepWorldFrame)( void );\n\t// called through save\\restore process\n\tvoid\t\t(*pfnCreateEntitiesInRestoreList)( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world );\n\t// allocate custom string (e.g. using user implementation of stringtable, not engine strings)\n\tstring_t\t\t(*pfnAllocString)( const char *szValue );\n\t// make custom string (e.g. using user implementation of stringtable, not engine strings)\n\tstring_t\t\t(*pfnMakeString)( const char *szValue );\n\t// read custom string (e.g. using user implementation of stringtable, not engine strings)\n\tconst char*\t(*pfnGetString)( string_t iString );\n\t// helper for restore custom decals that have custom message (e.g. Paranoia)\n\tint\t\t(*pfnRestoreDecal)( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent );\n\t// handle custom trigger touching for player\n\tvoid\t\t(*PM_PlayerTouch)( struct playermove_s *ppmove, edict_t *client );\n\t// alloc or destroy model custom data (called only for dedicated servers, otherwise using an client version)\n\tvoid\t\t(*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer );\n\t// select BSP-hull for trace with specified mins\\maxs\n\tvoid\t\t*(*SV_HullForBsp)( edict_t *ent, const float *mins, const float *maxs, float *offset );\n\t// handle player custom think function\n\tint\t\t(*SV_PlayerThink)( edict_t *ent, float frametime, double time );\n} physics_interface_t;\n\n#endif//PHYSINT_H\n"
  },
  {
    "path": "engine/platform/android/android.c",
    "content": "/*\nandroid_nosdl.c - android backend\nCopyright (C) 2016-2019 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"platform/platform.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"sound.h\"\n#include \"errno.h\"\n#include <pthread.h>\n#include <sys/prctl.h>\n\n#include <android/log.h>\n#include <jni.h>\n#if XASH_SDL\n#include <SDL.h>\n#endif // XASH_SDL\n\nstruct jnimethods_s\n{\n\tJNIEnv *env;\n\tjobject activity;\n\tjclass actcls;\n\tjmethodID loadAndroidID;\n\tjmethodID getAndroidID;\n\tjmethodID saveAndroidID;\n} jni;\n\nvoid Android_Init( void )\n{\n\tmemset( &jni, 0, sizeof( jni ));\n\n#if XASH_SDL\n\tjni.env = (JNIEnv *)SDL_AndroidGetJNIEnv();\n\tjni.activity = (jobject)SDL_AndroidGetActivity();\n\tjni.actcls = (*jni.env)->GetObjectClass( jni.env, jni.activity );\n\tjni.loadAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, \"loadAndroidID\", \"()Ljava/lang/String;\" );\n\tjni.getAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, \"getAndroidID\", \"()Ljava/lang/String;\" );\n\tjni.saveAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, \"saveAndroidID\", \"(Ljava/lang/String;)V\" );\n\n\tSDL_SetHint( SDL_HINT_ORIENTATIONS, \"LandscapeLeft LandscapeRight\" );\n\tSDL_SetHint( SDL_HINT_JOYSTICK_HIDAPI_STEAM, \"1\" );\n\tSDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE, \"0\" );\n\tSDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, \"0\" );\n\tSDL_SetHint( SDL_HINT_ANDROID_TRAP_BACK_BUTTON, \"1\" );\n#endif // !XASH_SDL\n}\n\n/*\n========================\nAndroid_GetNativeObject\n========================\n*/\n\nvoid *Android_GetNativeObject( const char *name )\n{\n\tif( !strcasecmp( name, \"JNIEnv\" ) )\n\t{\n\t\treturn (void *)jni.env;\n\t}\n\telse if( !strcasecmp( name, \"ActivityClass\" ) )\n\t{\n\t\treturn (void *)jni.actcls;\n\t}\n\n\treturn NULL;\n}\n\n/*\n========================\nAndroid_GetAndroidID\n========================\n*/\nconst char *Android_GetAndroidID( void )\n{\n\tstatic char id[32];\n\tjstring resultJNIStr;\n\tconst char *resultCStr;\n\n\tif( COM_CheckString( id ) ) return id;\n\n\tresultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAndroidID );\n\tresultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL );\n\tQ_strncpy( id, resultCStr, sizeof( id ) );\n\t(*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr );\n\n\treturn id;\n}\n\n/*\n========================\nAndroid_LoadID\n========================\n*/\nconst char *Android_LoadID( void )\n{\n\tstatic char id[32];\n\tjstring resultJNIStr;\n\tconst char *resultCStr;\n\n\tresultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.loadAndroidID );\n\tresultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL );\n\tQ_strncpy( id, resultCStr, sizeof( id ) );\n\t(*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr );\n\n\treturn id;\n}\n\n/*\n========================\nAndroid_SaveID\n========================\n*/\nvoid Android_SaveID( const char *id )\n{\n\t(*jni.env)->CallVoidMethod( jni.env, jni.activity, jni.saveAndroidID, (*jni.env)->NewStringUTF( jni.env, id ) );\n}\n\n/*\n========================\nAndroid_ShellExecute\n========================\n*/\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n#if XASH_SDL\n\tSDL_OpenURL( path );\n#endif // XASH_SDL\n}\n"
  },
  {
    "path": "engine/platform/android/dlsym-weak.c",
    "content": "/*\n * Copyright (C) 2008, 2009 The Android Open Source Project\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *  * Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *  * Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\n * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#include \"build.h\"\n#if XASH_ANDROID && !XASH_64BIT\n#include <string.h>\n#include <android/log.h>\n#include \"linker.h\"\n\nstatic const Elf_Sym *soinfo_elf_lookup( const struct soinfo *si, unsigned hash, const char *name )\n{\n\tconst Elf_Sym *symtab = si->symtab;\n\tconst char    *strtab = si->strtab;\n\tunsigned      n;\n\n\tif( si->nbucket == 0 )\n\t\treturn NULL;\n\n\tfor( n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n] )\n\t{\n\t\tconst Elf_Sym *s = symtab + n;\n\n\t\tif( strcmp( strtab + s->st_name, name ))\n\t\t\tcontinue;\n\n\t\t/* only concern ourselves with global and weak symbol definitions */\n\t\tswitch( ELF_ST_BIND( s->st_info ))\n\t\t{\n\t\tcase STB_GLOBAL:\n\t\tcase STB_WEAK:\n\t\t\tif( s->st_shndx == SHN_UNDEF )\n\t\t\t\tcontinue;\n\t\t\treturn s;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic unsigned elfhash( const unsigned char *name )\n{\n\tunsigned h = 0;\n\n\twhile( *name )\n\t{\n\t\tunsigned g;\n\n\t\th = ( h << 4 ) + *name++;\n\t\tg = h & 0xf0000000;\n\t\th ^= g;\n\t\th ^= g >> 24;\n\t}\n\treturn h;\n}\n\n/* This is used by dlsym(3).  It performs symbol lookup only within the\n   specified soinfo object and not in any of its dependencies.\n\n   TODO: Only looking in the specified soinfo seems wrong. dlsym(3) says\n   that it should do a breadth first search through the dependency\n   tree. This agrees with the ELF spec (aka System V Application\n   Binary Interface) where in Chapter 5 it discuss resolving \"Shared\n   Object Dependencies\" in breadth first search order.\n */\nstatic const Elf_Sym *dlsym_handle_lookup( const struct soinfo *si, const char *name )\n{\n\treturn soinfo_elf_lookup( si, elfhash((const unsigned char *)name ), name );\n}\n\nvoid *dlsym_weak( void *handle, const char *symbol )\n{\n\tconst struct soinfo *found = (struct soinfo *)handle;\n\tconst Elf_Sym *sym = dlsym_handle_lookup( found, symbol );\n\n\tif( sym != NULL )\n\t\treturn (void *)( sym->st_value + found->base /*load_bias*/ );\n\n\t__android_log_print( ANDROID_LOG_ERROR, \"dlsym-weak\", \"Failed when looking up %s\\n\", symbol );\n\treturn NULL;\n}\n#endif // XASH_ANDROID && !XASH_64BIT\n"
  },
  {
    "path": "engine/platform/android/dlsym-weak.h",
    "content": "/*\ndlsym-weak.h -- custom dlsym() function to override bionic libc bug on Android <5.0\nCopyright (C) 2015-2017 Flying With Gauss\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef DLSYM_WEAH_H\n#define DLSYM_WEAK_H\n\n// ------------ dlsym-weak.cpp ------------ //\nvoid* dlsym_weak(void* handle, const char* symbol);\n\n#endif\n"
  },
  {
    "path": "engine/platform/android/lib_android.c",
    "content": "/*\nandroid_lib.c - dynamic library code for Android OS\nCopyright (C) 2018 Flying With Gauss\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n#include \"library.h\"\n#include \"filesystem.h\"\n#include \"server.h\"\n#include \"platform/android/lib_android.h\"\n#include \"platform/android/dlsym-weak.h\" // Android < 5.0\n\nvoid *ANDROID_LoadLibrary( const char *path )\n{\n\tconst char *libdir[2], *name = COM_FileWithoutPath( path );\n\tchar fullpath[MAX_SYSPATH];\n\tvoid *handle;\n\n\tlibdir[0] = getenv( \"XASH3D_GAMELIBDIR\" ); // TODO: remove this once distributing games from APKs will be deprecated\n\tlibdir[1] = NULL; // TODO: put here data directory where libraries will be downloaded to\n\n\tfor( int i = 0; i < ARRAYSIZE( libdir ); i++ )\n\t{\n\t\t// this is an APK directory, get base path\n\t\tconst char *p = i == 0 ? name : path;\n\n\t\tif( !libdir[i] )\n\t\t\tcontinue;\n\n\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s/%s\", libdir[i], p );\n\n\t\thandle = dlopen( fullpath, RTLD_NOW );\n\n\t\tif( handle )\n\t\t{\n\t\t\tCon_Reportf( \"%s: loading library %s successful\\n\", __func__, fullpath );\n\t\t\treturn handle;\n\t\t}\n\n\t\tCOM_PushLibraryError( dlerror() );\n\t}\n\n\t// find in system search path, that includes our APK\n\thandle = dlopen( name, RTLD_NOW );\n\tif( handle )\n\t{\n\t\tCon_Reportf( \"%s: loading library %s from LD_LIBRARY_PATH successful\\n\", __func__, name );\n\t\treturn handle;\n\t}\n\tCOM_PushLibraryError( dlerror() );\n\n\treturn NULL;\n}\n\nvoid *ANDROID_GetProcAddress( void *hInstance, const char *name )\n{\n\tvoid *p = dlsym( hInstance, name );\n\n#ifndef XASH_64BIT\n\tif( p ) return p;\n\n\tp = dlsym_weak( hInstance, name );\n#endif // XASH_64BIT\n\n\treturn p;\n}\n"
  },
  {
    "path": "engine/platform/android/lib_android.h",
    "content": "/*\nandroid_lib.h - dynamic library code for Android OS\nCopyright (C) 2018 Flying With Gauss\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#if XASH_ANDROID\n#ifndef ANDROID_LIB_H\n#define ANDROID_LIB_H\n\n#if !XASH_TERMUX\n#define Platform_POSIX_LoadLibrary( x ) ANDROID_LoadLibrary(( x ))\n#endif // !XASH_TERMUX\n\n#define Platform_POSIX_GetProcAddress( x, y ) ANDROID_GetProcAddress(( x ), ( y ))\n\nvoid *ANDROID_LoadLibrary( const char *dllname );\nvoid *ANDROID_GetProcAddress( void *hInstance, const char *name );\n\n#endif // ANDROID_LIB_H\n#endif // XASH_ANDROID\n"
  },
  {
    "path": "engine/platform/android/linker.h",
    "content": "/*\n * Copyright (C) 2008 The Android Open Source Project\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *  * Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n *  * Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in\n *    the documentation and/or other materials provided with the\n *    distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\n * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\n * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#ifndef _LINKER_H_\n#define _LINKER_H_\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <stdbool.h>\n#include <linux/elf.h>\n\n// a1ba: we don't really need custom linker on Android 64, because\n// it's only intended to workaround bug which persist on Android < 4.4\n#define Elf_Ehdr  Elf32_Ehdr\n#define Elf_Phdr  Elf32_Phdr\n#define Elf_Shdr  Elf32_Shdr\n#define Elf_Sym   Elf32_Sym\n#define Elf_Rel   Elf32_Rel\n#define Elf_RelA  Elf32_Rela\n#define Elf_Dyn   Elf32_Dyn\n#define Elf_Half  Elf32_Half\n#define Elf_Word  Elf32_Word\n#define Elf_Sword Elf32_Sword\n#define Elf_Addr  Elf32_Addr\n#define Elf_Off   Elf32_Off\n#define Elf_Nhdr  Elf32_Nhdr\n#define Elf_Note  Elf32_Note\n\n// Returns the address of the page containing address 'x'.\n#define PAGE_START( x ) (( x )&PAGE_MASK )\n\n// Returns the offset of address 'x' in its page.\n#define PAGE_OFFSET( x ) (( x ) & ~PAGE_MASK )\n\n// Returns the address of the next page after address 'x', unless 'x' is\n// itself at the start of a page.\n#define PAGE_END( x ) PAGE_START(( x ) + ( PAGE_SIZE - 1 ))\n\n// Magic shared structures that GDB knows about.\n\nstruct link_map_t {\n\tuintptr_t  l_addr;\n\tchar       *l_name;\n\tuintptr_t  l_ld;\n\tstruct link_map_t *l_next;\n\tstruct link_map_t *l_prev;\n};\n\n// Values for r_debug->state\nenum {\n\tRT_CONSISTENT,\n\tRT_ADD,\n\tRT_DELETE\n};\n#define FLAG_LINKED 0x00000001\n#define FLAG_EXE    0x00000004     // The main executable\n#define FLAG_LINKER 0x00000010     // The linker itself\n\n#define SOINFO_NAME_LEN 128\n\ntypedef void (*linker_function_t)( void );\n\n// Android uses REL for 32-bit but only uses RELA for 64-bit.\n#if defined( __LP64__ )\n#define USE_RELA 1\n#endif\n\nstruct soinfo {\n\tchar           name[SOINFO_NAME_LEN];\n\tconst Elf_Phdr *phdr;\n\tsize_t         phnum;\n\tElf_Addr       entry;\n\tElf_Addr       base;\n\tunsigned       size;\n\n#ifndef __LP64__\n\tuint32_t       unused1; // DO NOT USE, maintained for compatibility.\n#endif\n\n\tElf_Dyn        *dynamic;\n\n#ifndef __LP64__\n\tuint32_t       unused2; // DO NOT USE, maintained for compatibility\n\tuint32_t       unused3; // DO NOT USE, maintained for compatibility\n#endif\n\n\tstruct soinfo  *next;\n\tunsigned       flags;\n\n\tconst char     *strtab;\n\tElf_Sym        *symtab;\n\n\tsize_t         nbucket;\n\tsize_t         nchain;\n\tunsigned       *bucket;\n\tunsigned       *chain;\n\n#if !defined( __LP64__ )\n\t// This is only used by 32-bit MIPS, but needs to be here for\n\t// all 32-bit architectures to preserve binary compatibility.\n\tunsigned *plt_got;\n#endif\n\n#if defined( USE_RELA )\n\tElf_RelA          *plt_rela;\n\tsize_t            plt_rela_count;\n\n\tElf_RelA          *rela;\n\tsize_t            rela_count;\n#else\n\tElf_Rel           *plt_rel;\n\tsize_t            plt_rel_count;\n\n\tElf_Rel           *rel;\n\tsize_t            rel_count;\n#endif\n\n\tlinker_function_t *preinit_array;\n\tsize_t            preinit_array_count;\n\n\tlinker_function_t *init_array;\n\tsize_t            init_array_count;\n\tlinker_function_t *fini_array;\n\tsize_t            fini_array_count;\n\n\tlinker_function_t init_func;\n\tlinker_function_t fini_func;\n\n#if defined( __arm__ )\n\t// ARM EABI section used for stack unwinding.\n\tunsigned   *ARM_exidx;\n\tsize_t     ARM_exidx_count;\n#elif defined( __mips__ )\n\tunsigned   mips_symtabno;\n\tunsigned   mips_local_gotno;\n\tunsigned   mips_gotsym;\n#endif\n\n\tsize_t            ref_count;\n\tstruct link_map_t link_map;\n\n\tbool       constructors_called;\n\n\t// When you read a virtual address from the ELF file, add this\n\t// value to get the corresponding address in the process' address space.\n\tElf_Addr load_bias;\n\n#if !defined( __LP64__ )\n\tbool     has_text_relocations;\n#endif\n\tbool     has_DT_SYMBOLIC;\n};\n\n#endif\n"
  },
  {
    "path": "engine/platform/apple/lib_ios.c",
    "content": "/*\nios_lib.c - dynamic library code for iOS\nCopyright (C) 2017-2018 mittorn\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#if TARGET_OS_IPHONE\n#include <SDL.h>\n#include \"common.h\"\n#include \"library.h\"\n#include \"filesystem.h\"\n#include \"server.h\"\n#include \"platform/apple/ios_lib.h\"\n\nstatic void *IOS_LoadLibraryInternal( const char *dllname )\n{\n\tvoid *pHandle;\n\tstring errorstring = \"\";\n\tchar path[MAX_SYSPATH];\n\n\t// load frameworks from Documents directory\n\t// frameworks should be signed with same key with application\n\t// Useful for debug to prevent rebuilding app on every library update\n\t// NOTE: Apple polices forbids loading code from shared places\n#ifdef ENABLE_FRAMEWORK_SIDELOAD\n\tQ_snprintf( path, MAX_SYSPATH, \"%s.framework/lib\", dllname );\n\tif( pHandle = dlopen( path, RTLD_LAZY ) )\n\t\treturn pHandle;\n\tQ_snprintf( errorstring, MAX_STRING, dlerror() );\n#endif\n\n#ifdef DLOPEN_FRAMEWORKS\n\t// load frameworks as it should be located in Xcode builds\n\tQ_snprintf( path, MAX_SYSPATH, \"%s%s.framework/lib\", SDL_GetBasePath(), dllname );\n#else\n\t// load libraries from app root to allow re-signing ipa with custom utilities\n\tQ_snprintf( path, MAX_SYSPATH, \"%s%s\", SDL_GetBasePath(), dllname );\n#endif\n\tpHandle = dlopen( path, RTLD_LAZY );\n\tif( !pHandle )\n\t{\n\t\tCOM_PushLibraryError(errorstring);\n\t\tCOM_PushLibraryError(dlerror());\n\t}\n\treturn pHandle;\n}\nextern char *g_szLibrarySuffix;\nstatic void *IOS_LoadLibrary( const char *dllname )\n{\n\n\tstring name;\n\tchar *postfix = g_szLibrarySuffix;\n\tchar *pHandle;\n\n\tif( !postfix ) postfix = GI->gamefolder;\n\n\tQ_snprintf( name, MAX_STRING, \"%s_%s\", dllname, postfix );\n\tpHandle = IOS_LoadLibraryInternal( name );\n\tif( pHandle )\n\t\treturn pHandle;\n\treturn IOS_LoadLibraryInternal( dllname );\n}\n#endif // TARGET_OS_IPHONE\n"
  },
  {
    "path": "engine/platform/apple/lib_ios.h",
    "content": "/*\nios_lib.h - dynamic library code for iOS\nCopyright (C) 2017-2018 mittorn\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#if TARGET_OS_IPHONE\n#ifndef IOS_LIB_H\n#define IOS_LIB_H\n\n#define Platform_POSIX_LoadLibrary( x ) IOS_LoadLibrary(( x ))\n\nvoid *IOS_LoadLibrary( const char *dllname );\n\n#endif // IOS_LIB_H\n#endif // TARGET_OS_IPHONE\n"
  },
  {
    "path": "engine/platform/dos/in_dos.c",
    "content": "/*\nin_dos.c - DOS input\nCopyright (C) 2020 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"platform/platform.h\"\n#include \"input.h\"\n#include <dos.h>\n\nstatic struct key_s\n{\nbyte buf[256];\nbyte buf_head;\nqboolean shift;\nqboolean chars;\n} keystate;\n\n//=============================================================================\n\nbyte        scantokey[128] =\n\t\t\t\t\t{\n//  0           1       2       3       4       5       6       7\n//  8           9       A       B       C       D       E       F\n\t0  ,    27,     '1',    '2',    '3',    '4',    '5',    '6',\n\t'7',    '8',    '9',    '0',    '-',    '=',    K_BACKSPACE, 9, // 0\n\t'q',    'w',    'e',    'r',    't',    'y',    'u',    'i',\n\t'o',    'p',    '[',    ']',    13 ,    K_CTRL,'a',  's',      // 1\n\t'd',    'f',    'g',    'h',    'j',    'k',    'l',    ';',\n\t'\\'' ,    '`',    K_SHIFT,'\\\\',  'z',    'x',    'c',    'v',      // 2\n\t'b',    'n',    'm',    ',',    '.',    '/',    K_SHIFT,'*',\n\tK_ALT,' ',   0  ,    K_F1, K_F2, K_F3, K_F4, K_F5,   // 3\n\tK_F6, K_F7, K_F8, K_F9, K_F10,0  ,    0  , K_HOME,\n\tK_UPARROW,K_PGUP,'-',K_LEFTARROW,'5',K_RIGHTARROW,'+',K_END, //4\n\tK_DOWNARROW,K_PGDN,K_INS,K_DEL,0,0,             0,              K_F11,\n\tK_F12,0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0,        // 5\n\t0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0,\n\t0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0,        // 6\n\t0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0,\n\t0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0  ,    0         // 7\n\t\t\t\t\t};\n\n\n// will be implemented later\nvoid Platform_RunEvents( void )\n{\n\tint i;\n\n\tfor( i = 0; i < keystate.buf_head;i++ )\n\t{\n\t\tint k = keystate.buf[i];\n\t\tint key = scantokey[k&0x7f];\n\t\tint value = !(k & 0x80);\n\n\t\tKey_Event( key , value );\n\n\t\tif( keystate.chars && value )\n\t\t{\n\t\t\tif( key >= 32 && key < 127 )\n\t\t\t{\n\t\t\t\tif( keystate.shift )\n\t\t\t\t{\n\t\t\t\t\tkey = Key_ToUpper( key );\n\t\t\t\t}\n\t\t\t\tCL_CharEvent( key );\n\t\t\t}\n\t\t}\n\t\tif( key == K_SHIFT )\n\t\t\tkeystate.shift = value;\n\t}\n\tkeystate.buf_head = 0;\n\n}\n\nvoid Platform_EnableTextInput( qboolean enable )\n{\n\tkeystate.chars = enable;\n}\n\nvoid Platform_MouseMove( float *x, float *y )\n{\n\tstatic int lx,ly;\n\tunion REGS regs;\n\n\tregs.w.ax = regs.w.bx = regs.w.cx = regs.w.dx = 0;\n\n\tregs.w.ax = 11;\n\tint386(0x33,&regs,&regs);\n\t*x = (short)regs.w.cx, *y = (short)regs.w.dx;\n\tregs.w.ax = regs.w.bx = regs.w.cx = regs.w.dx = 0;\n\tint386(0x33,&regs,&regs);// reset\n}\n\nvoid __interrupt __far keyhandler( void )\n{\n\tkeystate.buf[keystate.buf_head++] = inp(0x60);\n\toutp(0x20, 0x20);\n}\n"
  },
  {
    "path": "engine/platform/dos/ld.sh",
    "content": "#!/bin/sh\nout1=$2\nshift 2\nout=$1\nshift 1\n\necho $* > $out\nrm -r $out.files\nmkdir $out.files\ncp --parents $* $out.files/\necho cp --parents $* $out.files/\n\n#exit 0"
  },
  {
    "path": "engine/platform/dos/objcopy.sh",
    "content": "#!/bin/sh\ncd $3.files\nobjxdef $(cat $3) |grep -v _lib_> syms.tmp\nobjchg -m=$2_* -s=syms.tmp $(cat $3)\nwlib $3.a $(cat $3)\ncp $3.a $4\ncd .."
  },
  {
    "path": "engine/platform/dos/sys_dos.c",
    "content": "/*\nsys_dos.c - dos timer\nCopyright (C) 2020 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include <dos.h>\n\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n}\n\nvolatile int ticks=0;\n\n#if XASH_TIMER == TIMER_DOS\ndouble Platform_DoubleTime( void )\n{\n\treturn 0.005*ticks;\n}\n#endif // XASH_TIMER == TIMER_DOS\n\n#define PIT_FREQUENCY  0x1234DDL\n#define frequency      140\n#define counter        PIT_FREQUENCY/frequency\n\nvolatile int BIOS_ticks=0;\nvolatile int second_ticks=0;\nvolatile int second_flag=0;\n\n\nstatic void (__interrupt __far *orig_int_1c)();\nstatic void (__interrupt __far *orig_int_09)();\n\n\nstatic void __interrupt __far timerhandler()\n{\n\tBIOS_ticks += counter;\n\tsecond_ticks++, ticks++;\n\n\tif( BIOS_ticks >= 0x10000 )\n\t{\n\t\tBIOS_ticks = 0;\n\t\t_chain_intr( orig_int_1c );\n\t}\n\n\tif( second_ticks>=frequency )\n\t{\n\t\tsecond_flag = 1;\n\t\tsecond_ticks = 0;\n\t}\n\n\toutp( 0x20, 0x20 );\n}\n\n// in_dos.c\nextern void __interrupt __far keyhandler( void );\n\nvoid DOS_Init( void )\n{\n\t// save original vectors\n\torig_int_1c = _dos_getvect( 0x1c );\n\torig_int_09 = _dos_getvect( 0x09 );\n\t_dos_setvect( 0x1c, timerhandler );\n\t_dos_setvect( 0x09, keyhandler );\n\n\t// set clock freq\n\toutp( 0x43, 0x34 );\n\toutp( 0x40, (char)(counter%256) );\n\toutp( 0x40, (char)(counter/256) );\n}\n\n\nvoid DOS_Shutdown( void )\n{\n\t// restore freq\n\toutp( 0x43, 0x34 );\n\toutp( 0x40, 0 );\n\toutp( 0x40, 0 );\n\n\t//restore vectors\n\t_dos_setvect( 0x1c, orig_int_1c );\n\t_dos_setvect( 0x09, orig_int_09 );\n}\n"
  },
  {
    "path": "engine/platform/dos/vid_dos.c",
    "content": "/*\nvid_dos.c - DOS VGA/VESA driver\nCopyright (C) 2020 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#if XASH_VIDEO == VIDEO_DOS\n#include \"input.h\"\n#include \"client.h\"\n#include \"filesystem.h\"\n#include \"vid_common.h\"\n#include <fcntl.h>\n#include <errno.h>\n#include <dos.h>\n#include <i86.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\nvoid Platform_Minimize_f( void )\n{\n\t// stub\n}\n\n/*\n========================\nAndroid_SwapBuffers\n\nUpdate screen. Use native EGL if possible\n========================\n*/\nvoid GL_SwapBuffers( void )\n{\n}\n\nstatic void DOS_GetScreenRes( int *x, int *y )\n{\n\t*x = 320;\n\t*y = 200;\n}\n\nqboolean  R_Init_Video( const int type )\n{\n\tqboolean retval;\n\n\tif( type != REF_SOFTWARE )\n\t\treturn false; /// glide???\n\n\tif( !(retval = VID_SetMode()) )\n\t{\n\t\treturn retval;\n\t}\n\n\thost.renderinfo_changed = false;\n\n\treturn true;\n}\n\nvoid R_Free_Video( void )\n{\n\t// restore text mode\n\tunion REGS regs;\n\tregs.w.ax = 3;\n\tint386(0x10,&regs,&regs);\n\n\tref.dllFuncs.GL_ClearExtensions();\n}\n\n\nqboolean VID_SetMode( void )\n{\n\tR_ChangeDisplaySettings( 0, 0, WINDOW_MODE_FULLSCREEN ); // width and height are ignored anyway\n\n\treturn true;\n}\n\nrserr_t   R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )\n{\n\tint render_w, render_h;\n\tuint rotate = vid_rotate->value;\n\n\tDOS_GetScreenRes( &width, &height );\n\n\trender_w = width;\n\trender_h = height;\n\n\tCon_Reportf( \"%s: forced resolution to %dx%d)\\n\", __func__, width, height );\n\n\tif( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) )\n\t{\n\t\tif( rotate & 1 )\n\t\t{\n\t\t\tint swap = render_w;\n\n\t\t\trender_w = render_h;\n\t\t\trender_h = swap;\n\t\t}\n\n\t\trender_h /= vid_scale->value;\n\t\trender_w /= vid_scale->value;\n\t}\n\telse\n\t{\n\t\tCon_Printf( S_WARN \"failed to setup screen transform\\n\" );\n\t}\n\tR_SaveVideoMode( width, height, render_w, render_h );\n\n\treturn rserr_ok;\n}\n\nint GL_SetAttribute( int attr, int val )\n{\n\treturn 0;\n}\n\nint GL_GetAttribute( int attr, int *val )\n{\n\treturn 0;\n}\n\nint R_MaxVideoModes( void )\n{\n\treturn 0;\n}\n\nvidmode_t* R_GetVideoMode( int num )\n{\n\treturn NULL;\n}\n\nvoid* GL_GetProcAddress( const char *name ) // RenderAPI requirement\n{\n\treturn NULL;\n}\n\nstatic qboolean vsync;\n\nvoid GL_UpdateSwapInterval( void )\n{\n\tif( FBitSet( gl_vsync.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( gl_vsync.flags, FCVAR_CHANGED );\n\t\tvsync = gl_vsync.value;\n\t}\n}\n\n/*\n================================\n\nVESA structures\n\n================================\n*/\n\ntypedef struct RealPointer\n{\n\tshort\t\t\tSegment;\t//The real mode segment (offset is 0).\n\tshort\t\t\tSelector;\t//In protected mode, you need to chuck this dude into a segment register and use an offset 0.\n} RealPointer;\n\ntypedef struct VesaInfo\n{\n\tchar\t\tSignature[4];\n\tshort\t\tVersion;\n\tshort\t\tOEMNameOffset;\n\tshort\t\tOEMNameSegment;\t\t\t//Pointer to OEM name?\n\tchar\t\tCapabilities[4];\n\tshort\t\tSupportedModesOffset;\n\tshort\t\tSupportedModesSegment;\t\t//Pointer to list of supported VESA and OEM modes (terminated with 0xffff).\n\tchar\t\tReserved[238];\n} VesaInfo;\n\ntypedef struct VesaModeData\n{\n\tshort\t\tModeAttributes;\n\tchar\t\tWindowAAttributes;\n\tchar\t\tWindowBAttributes;\n\tshort\t\tWindowGranularity;\n\tshort\t\tWindowSize;\n\tshort\t\tStartSegmentWindowA;\n\tshort\t\tStartSegmentWindowB;\n\tvoid\t\t(*WindowPositioningFunction)(int page);\n\tshort\t\tBytesPerScanLine;\n\n\t//Remainder of this structure is optional for VESA modes in v1.0/1.1, needed for OEM modes.\n\n\tshort\t\tPixelWidth;\n\tshort\t\tPixelHeight;\n\tchar\t\tCharacterCellPixelWidth;\n\tchar\t\tCharacterCellPixelHeight;\n\tchar\t\tNumberOfMemoryPlanes;\n\tchar\t\tBitsPerPixel;\n\tchar\t\tNumberOfBanks;\n\tchar\t\tMemoryModelType;\n\tchar\t\tSizeOfBank;\n\tchar\t\tNumberOfImagePages;\n\tchar\t\tReserved1;\n\n\t//VBE v1.2+\n\n\tchar\t\tRedMaskSize;\n\tchar\t\tRedFieldPosition;\n\tchar\t\tGreenMaskSize;\n\tchar\t\tGreenFieldPosition;\n\tchar\t\tBlueMaskSize;\n\tchar\t\tBlueFieldPosition;\n\tchar\t\tReservedMaskSize;\n\tchar\t\tReservedFieldPosition;\n\tchar\t\tDirectColourModeInfo;\n\tchar\t\tReserved2[216];\n} VesaModeData;\n\ntypedef struct RMREGS\n{\n\tint\t\tedi;\n\tint\t\tesi;\n\tint\t\tebp;\n\tint\t\treserved;\n\tint\t\tebx;\n\tint\t\tedx;\n\tint\t\tecx;\n\tint\t\teax;\n\tshort\t\tflags;\n\tshort\t\tes,ds,fs,gs,ip,cs,sp,ss;\n} RMREGS;\n\nstatic VesaInfo\tfar\t*vesa_info\t\t= NULL;\nstatic VesaModeData\tfar\t*vesa_mode_data\t\t= NULL;\nstatic int\t\t\tvesa_granularity;\nstatic int\t\t\tvesa_page;\nstatic int\t\t\tvesa_width;\nstatic int\t\t\tvesa_height;\nstatic int\t\t\tvesa_bits_per_pixel;\n\nstatic RealPointer\t\t\t\tvesa_info_rp;\nstatic RealPointer\t\t\t\tvesa_mode_data_rp;\n\n\nstatic void far *allocate_dos_memory( RealPointer * rp,int bytes_to_allocate )\n{\n\tvoid\tfar\t*ptr\t= NULL;\n\tunion\tREGS\t\tregs;\n\n\tbytes_to_allocate = ((bytes_to_allocate + 15) & 0xfffffff0);\t//Round up to nearest paragraph.\n\tmemset(&regs,0,sizeof(regs));\n\tregs.w.ax = 0x100;\n\tregs.w.bx = (short)(bytes_to_allocate >> 4);\t\t//Allocate dos memory in pages, so convert bytes to pages.\n\tint386(0x31,&regs,&regs);\n\tif(regs.x.cflag == 0)\n\t{\t//Everything OK.\n\t\trp->Segment = regs.w.ax;\n\t\trp->Selector = regs.w.dx;\n\t\tptr = MK_FP(regs.w.dx,0);\n\t}\n\treturn(ptr);\n}\n\n/****************************************************************************/\n/* Free an area of DOS memory.\t\t\t\t\t\t\t\t\t\t\t\t*/\n/****************************************************************************/\n\nstatic void free_dos_memory( RealPointer * rp )\n{\n\tunion\tREGS\t\tregs;\n\n\tregs.w.ax = 0x101;\n\tregs.w.dx = rp->Selector;\n\tint386(0x31,&regs,&regs);\n}\n\n\n/****************************************************************************/\n/* Get information about the VESA driver.\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t*/\n/* Returns:\t\t\t\t\t\t\t\t\t*/\n/*\tTRUE\t\t-\tVESA driver present.  vesa_info set to point to a\t*/\n/*\t\t\t\t\ta structure containing capability info etc.\t*/\n/*\tFALSE\t\t-\tNo VESA driver.\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t*/\n/****************************************************************************/\n\nstatic int vesa_get_info( void )\n{\n    union\t\tREGS\tregs;\n    struct\t\tSREGS\tsregs;\n    RMREGS\trmregs;\n    int ok = 0;\n\n    if((vesa_info = (VesaInfo far *)allocate_dos_memory(&vesa_info_rp,sizeof(VesaInfo))) != NULL)\n    {\n\tmemset(&rmregs,0,sizeof(rmregs));\n\tmemset(&regs,0,sizeof(regs));\n\tmemset(&sregs,0,sizeof(sregs));\n\tsegread(&sregs);\n\trmregs.eax = 0x4f00;\n\trmregs.es = vesa_info_rp.Segment;\n\trmregs.ds = vesa_info_rp.Segment;\n\tregs.x.eax = 0x300;\n\tregs.x.ebx = 0x10;\n\tregs.x.ecx = 0;\n\tsregs.es = FP_SEG(&rmregs);\n\tregs.x.edi = FP_OFF(&rmregs);\n\tint386x(0x31,&regs,&regs,&sregs);\t//Get vesa info.\n\n\tif(rmregs.eax == 0x4f) ok=1;\n    }\n    return(ok);\n}\n\n/****************************************************************************/\n/* Free the information allocated with vesa_get_info()\t\t\t\t\t\t*/\n/****************************************************************************/\n\nstatic int vesa_free_info( void )\n{\n\tfree_dos_memory( &vesa_info_rp );\n}\n\n/****************************************************************************/\n/* Get VESA mode information.  Information is loaded into vesa_mode_data.\t*/\n/* vesa_get_info() must be called before calling this function.\t\t\t\t*/\n/* Should error check this really.\t\t\t\t\t\t\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/* Inputs:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\tvmode\t-\tThe mode that you wish to get information about.\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/* Returns:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\tTRUE\t-\tvesa_mode_data points to a filled VesaModeData structure.\t*/\n/*\tFALSE\t-\tvesa_mode_data probably invalid.  Mode probably not\t\t\t*/\n/*\t\t\t\tsupported.\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/****************************************************************************/\n\nstatic int vesa_get_mode_info( short vmode )\n{\n\tunion\tREGS\tregs;\n\tstruct\tSREGS\tsregs;\n\tRMREGS\t\t\trmregs;\n\tint ok=0;\n\n\tif((vesa_mode_data = (VesaModeData far *)allocate_dos_memory(&vesa_mode_data_rp,sizeof(VesaModeData))) != NULL)\n\t{\n\t\tmemset(&rmregs,0,sizeof(rmregs));\n\t\trmregs.es = vesa_mode_data_rp.Segment;\n\t\trmregs.ds = vesa_mode_data_rp.Segment;\n\t\trmregs.edi = 0;\n\t\trmregs.eax = 0x4f01;\n\t\trmregs.ecx = vmode;\n\t\tmemset(&regs,0,sizeof(regs));\n\t\tmemset(&sregs,0,sizeof(sregs));\n\t\tsegread(&sregs);\n\t\tregs.x.eax = 0x300;\n\t\tregs.x.ebx = 0x10;\n\t\tregs.x.edi = (int)&rmregs;\n\t\tint386x(0x31,&regs,&regs,&sregs);\n\t\tif(regs.h.al == 0)\n\t\t{\n\n\t\t\t//cache a few important items in protected mode memory area.\n\n\t\t\tvesa_granularity\t= vesa_mode_data->WindowGranularity;\n\t\t\tvesa_width\t\t\t= vesa_mode_data->PixelWidth;\n\t\t\tvesa_height\t\t\t= vesa_mode_data->PixelHeight;\n\t\t\tvesa_bits_per_pixel\t= vesa_mode_data->BitsPerPixel;\n\t\t\tok = 1;\n\t\t}\n\t}\n\treturn(ok);\n}\n\n/****************************************************************************/\n/* Set the current vesa screen page.\t\t\t\t\t\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/* Inputs:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\tvpage\t-\tPage number to set.\t\t\t\t\t\t\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/****************************************************************************/\n\nstatic void vesa_set_page( int vpage )\n{\n\tunion\tREGS\tregs;\n\n\tvesa_page = vpage;\n\tregs.w.ax = 0x4f05;\n\tregs.w.bx = 0;\n\tregs.w.cx = 0;\n\tregs.w.dx = (short)(vpage == 0 ? 0 : (short)((vpage * 64) / vesa_granularity));\n\tint386(0x10,&regs,&regs);\n\tregs.w.ax = 0x4f05;\n\tregs.w.bx = 1;\n\tregs.w.cx = 0;\n\tregs.w.dx = (short)(vpage == 0 ? 0 : (short)((vpage * 64) / vesa_granularity));\n\tint386(0x10,&regs,&regs);\n}\n\n/****************************************************************************/\n/* Set VESA video mode.  You MUST have previously called vesa_get_info().\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/* Inputs:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\tvmode\t-\tThe VESA mode that you want. e.g. 0x101 is 640x480 256\t\t*/\n/*\t\t\t\tcolours.\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/* Returns:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/*\tTRUE\t-\tThe mode has been set.\t\t\t\t\t\t\t\t\t\t*/\n/*\tFALSE\t-\tThe mode has not been set, probably not supported.\t\t\t*/\n/*\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t*/\n/****************************************************************************/\n\nint vesa_set_mode( short vmode )\n{\n\tunion\tREGS\tregs;\n\tint ok=0;\n\n\tmemset(&regs,0,sizeof(regs));\n\tregs.w.ax = 0x4f02;\n\tregs.w.bx = vmode;\n\tint386(0x10,&regs,&regs);\n\tif(regs.w.ax == 0x004f)\n\t{\n\t\tvesa_get_mode_info(vmode);\n\t\tvesa_set_page(0);\t\t\t//Probably not needed, but here for completeness.\n\t\tok = 1;\n\t}\n\treturn(ok);\n}\n\n/****************************************************************************/\n/* Free the info previously allocated with vesa_get_info()\t\t\t\t\t*/\n/****************************************************************************/\n\nvoid vesa_free_mode_info( void )\n{\n\tfree_dos_memory(&vesa_mode_data_rp);\n}\n\nvoid init_13h( void )\n{\n\tint r, g, b;\n\tunion\tREGS\tregs;\n\n\t// set 13h vga mode\n\tmemset(&regs,0,sizeof(regs));\n\tregs.w.ax = 0x13;\n\tint386(0x10,&regs,&regs);\n\n\t// set 332 palette\n\toutp(0x3c8, 0);\n\tfor (r=0; r<8; r++)\n\t\tfor (b=0; b<4; b++)\n\t\t\tfor (g=0; g<8; g++)\n\t\t\t{\n\t\t\t\toutp(0x3c9, (b<<4)+8);\n\t\t\t\toutp(0x3c9, (g<<3)+4);\n\t\t\t\toutp(0x3c9, (r<<3)+4);\n\t\t\t}\n}\n\n\n\nvoid waitvbl()\n{\n    while (!(inp (0x3da) & 8)) {}\n    while (inp (0x3da) & 8) {}\n}\n\n\nstatic byte *fb;\n\nvoid *SW_LockBuffer( void )\n{\n\treturn fb;\n}\n\nvoid SW_UnlockBuffer( void )\n{\n\n\tint left = 320*200*2;\n\tint done = 0;\n\tint vpage = 0;\n\n\tif( vsync )\n\t\twaitvbl();\n\n\t// no separate fb\n\tif( fb == 0xa0000 )\n\t    return;\n\n\twhile( left )\n\t{\n\t\tint flip = left;\n\n\t\tif( flip > 65536 )\n\t\t\tflip = 65536;\n\n\t\tvesa_set_page(vpage);\n\t\tmemcpy((void *)0xa0000, fb+done, flip);\n\n\t\tdone += flip;\n\t\tleft -= flip;\n\t\tvpage++;\n\t}\n}\n\n#define FB_BF_TO_MASK(x) (((1 << x.length) - 1) << (x.offset))\n\nqboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tint i;\n\n\t*stride = 320;\n\n\tvesa_get_info();\n\n\tif( !Sys_CheckParm(\"-novesa\") && vesa_set_mode( 0x10E ) )\n\t{\n\t\tfb = malloc( 320*200*2 );\n\t\t*bpp = 2;\n\t\t*b = 31;\n\t\t*g = 63 << 5;\n\t\t*r = 31 << 11;\n\t}\n\telse\n\t{\n\t\t// fallback to 256 color\n\t\t*b = 2 << 6;\n\t\t*g = 7 << 0;\n\t\t*r = 7 << 3;\n\t\t*bpp = 1;\n\t\tinit_13h();\n\t\tfb = 0xa0000;\n\t}\n\n\treturn true;\n}\n\nref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type )\n{\n\treturn REF_WINDOW_TYPE_NULL;\n}\n\n#endif\n"
  },
  {
    "path": "engine/platform/irix/dladdr.c",
    "content": "/*\ndladdr.c - dladdr implementation for SGI IRIX\nCopyright (C) 2022 Xav101\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n/*\n * From SGI IRIX's 'man dladdr'\n *\n * <dlfcn.h> does not contain a prototype for dladdr or definition of\n * Dl_info. The #include <dlfcn.h> in the SYNOPSIS line is traditional,\n * but contains no dladdr prototype and no IRIX library contains an\n * implementation. Write your own declaration based on the code below.\n *\n * The following code is dependent on internal interfaces that are not\n * part of the IRIX compatibility guarantee; however, there is no future\n * intention to change this interface, so on a practical level, the code\n * below is safe to use on IRIX.\n *\n * \n *\n * The following code has been reproduced from the manpage.\n */ \n\n#include \"dladdr.h\"\n\nint dladdr(void *address, Dl_info* dl)\n{\n\tvoid *v;\n\tv = _rld_new_interface(_RLD_DLADDR, address, dl);\n\treturn (int)v;\n}\n"
  },
  {
    "path": "engine/platform/irix/dladdr.h",
    "content": "/*\ndladdr.h - dladdr prototypes for SGI IRIX\nCopyright (C) 2022 Xav101\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n/* See engine/platform/irix/dladdr.c for the requirement for this implementation */\n\n#ifndef DLADDR_IRIX_H\n#define DLADDR_IRIX_H\n\n#include <rld_interface.h>\n#ifndef _RLD_INTERFACE_DLFCN_H_DLADDR\n#define _RLD_INTERFACE_DLFCN_H_DLADDR\ntypedef struct Dl_info {\n\tconst char * dli_fname;\n\tvoid       * dli_fbase;\n\tconst char * dli_saddr;\n\tint          dli_version;\n\tint          dli_reserved1;\n\tlong         dli_reserved[4];\n} Dl_info;\n#endif\n#define _RLD_DLADDR 14\n\nint dladdr(void *address, Dl_info* dl);\n\n#endif\n"
  },
  {
    "path": "engine/platform/irix/xash-exec",
    "content": "#!/usr/sgug/bin/bash\n\n# Build path\nexport LD_LIBRARYN32_PATH=$PWD/filesystem:$LD_LIBRARYN32_PATH\n\n# Install path\nexport LD_LIBRARYN32_PATH=$PWD:$LD_LIBRARYN32_PATH\n\nexec $PWD/build/engine/xash\n"
  },
  {
    "path": "engine/platform/linux/in_evdev.c",
    "content": "/*\nin_evdev.c - linux evdev interface support\nCopyright (C) 2015-2018 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"platform/platform.h\"\n#if XASH_USE_EVDEV\n\n\n#include \"common.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include <fcntl.h>\n#include <errno.h>\n#include <linux/input.h>\n#include <dirent.h>\n#define MAX_EVDEV_DEVICES 5\n\nstruct evdev_s\n{\n\tint initialized, devices;\n\tint fds[MAX_EVDEV_DEVICES];\n\tstring paths[MAX_EVDEV_DEVICES];\n\tqboolean grab;\n\tdouble grabtime;\n\tfloat x, y;\n\tqboolean chars;\n\tqboolean shift;\n} evdev;\n\nstatic CVAR_DEFINE_AUTO( evdev_keydebug, \"0\", 0, \"print key events to console\" );\n\nstatic int KeycodeFromEvdev(int keycode, int value)\n{\n\tswitch (keycode) {\n\n\tcase KEY_0:          return '0';\n\tcase KEY_1:          return '1';\n\tcase KEY_2:          return '2';\n\tcase KEY_3:          return '3';\n\tcase KEY_4:          return '4';\n\tcase KEY_5:          return '5';\n\tcase KEY_6:          return '6';\n\tcase KEY_7:          return '7';\n\tcase KEY_8:          return '8';\n\tcase KEY_9:          return '9';\n\tcase KEY_BACKSPACE:  return K_BACKSPACE;\n\tcase KEY_ENTER:      return K_ENTER;\n\tcase KEY_ESC:        return K_ESCAPE;\n\tcase KEY_KP0:        return K_KP_INS;\n\tcase KEY_KP1:        return K_KP_END;\n\tcase KEY_KP2:        return K_KP_DOWNARROW;\n\tcase KEY_KP3:        return K_KP_PGDN;\n\tcase KEY_KP4:        return K_KP_LEFTARROW;\n\tcase KEY_KP5:        return K_KP_5;\n\tcase KEY_KP6:        return K_KP_RIGHTARROW;\n\tcase KEY_KP7:        return K_KP_HOME;\n\tcase KEY_KP8:        return K_KP_UPARROW;\n\tcase KEY_KP9:        return K_KP_PGUP;\n\tcase KEY_KPDOT:      return K_KP_DEL;\n\tcase KEY_KPENTER:    return K_KP_ENTER;\n\tcase KEY_Q: return 'q';\n\tcase KEY_W: return 'w';\n\tcase KEY_E: return 'e';\n\tcase KEY_R: return 'r';\n\tcase KEY_T: return 't';\n\tcase KEY_Y: return 'y';\n\tcase KEY_U: return 'u';\n\tcase KEY_I: return 'i';\n\tcase KEY_O: return 'o';\n\tcase KEY_P: return 'p';\n\tcase KEY_A: return 'a';\n\tcase KEY_S: return 's';\n\tcase KEY_D: return 'd';\n\tcase KEY_F: return 'f';\n\tcase KEY_G: return 'g';\n\tcase KEY_H: return 'h';\n\tcase KEY_J: return 'j';\n\tcase KEY_K: return 'k';\n\tcase KEY_L: return 'l';\n\tcase KEY_Z: return 'z';\n\tcase KEY_X: return 'x';\n\tcase KEY_C: return 'c';\n\tcase KEY_V: return 'v';\n\tcase KEY_B: return 'b';\n\tcase KEY_N: return 'n';\n\tcase KEY_M: return 'm';\n\tcase KEY_LEFTBRACE: return '[';\n\tcase KEY_RIGHTBRACE: return ']';\n\tcase KEY_MINUS: return '-';\n\tcase KEY_EQUAL: return '=';\n\tcase KEY_TAB: return K_TAB;\n\tcase KEY_SEMICOLON: return ';';\n\tcase KEY_APOSTROPHE: return '\\'';\n\tcase KEY_GRAVE: return '`';\n\tcase KEY_BACKSLASH: return '\\\\';\n\tcase KEY_COMMA: return ',';\n\tcase KEY_DOT: return '.';\n\tcase KEY_SLASH: return '/';\n\tcase KEY_SPACE: return K_SPACE;\n\tcase KEY_KPASTERISK: return '*';\n\tcase KEY_RIGHTCTRL:\n\tcase KEY_LEFTCTRL:\n\t\treturn K_CTRL;\n\tcase KEY_RIGHTSHIFT:\n\tcase KEY_LEFTSHIFT:\n\t\treturn K_SHIFT;\n\tcase KEY_LEFT: return K_LEFTARROW;\n\tcase KEY_RIGHT: return K_RIGHTARROW;\n\tcase KEY_UP: return K_UPARROW;\n\tcase KEY_DOWN: return K_DOWNARROW;\n\tcase BTN_LEFT: return K_MOUSE1;\n\tcase BTN_RIGHT: return K_MOUSE2;\n\tcase BTN_MIDDLE: return K_MOUSE3;\n\tcase KEY_POWER:\treturn K_ESCAPE;\n\tcase KEY_VOLUMEDOWN: return K_PGDN;\n\tcase KEY_VOLUMEUP: return K_PGUP;\n\tcase KEY_PLAYPAUSE: return K_ENTER;\n\tdefault:\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\nstatic void Evdev_CheckPermissions( void )\n{\n#if XASH_ANDROID\n\tsystem( \"su 0 chmod 664 /dev/input/event*\" );\n#endif\n}\n\nvoid Evdev_Setup( void )\n{\n\tif( evdev.initialized )\n\t\treturn;\n#if XASH_ANDROID\n\tsystem( \"su 0 supolicy --live \\\"allow appdomain input_device dir { ioctl read getattr search open }\\\" \\\"allow appdomain input_device chr_file { ioctl read write getattr lock append open }\\\"\" );\n\tsystem( \"su 0 setenforce permissive\" );\n#endif\n\tevdev.initialized = true;\n}\n\n#define EV_HASBIT( x, y ) ( x[y / 32] & (1 << y % 32) )\n\nvoid Evdev_Autodetect_f( void )\n{\n\tint i;\n\tDIR *dir;\n\tstruct dirent *entry;\n\n\tEvdev_Setup();\n\n\tEvdev_CheckPermissions();\n\n\tif( !( dir = opendir( \"/dev/input\" ) ) )\n\t    return;\n\n\twhile( ( entry = readdir( dir ) ) )\n\t{\n\t\tint fd;\n\t\tstring path;\n\t\tuint types[EV_MAX] = {0};\n\t\tuint codes[( KEY_MAX - 1 ) / 32 + 1] = {0};\n\t\tqboolean hasbtnmouse;\n\n\t\tif( evdev.devices >= MAX_EVDEV_DEVICES )\n\t\t\tcontinue;\n\n\t\tQ_snprintf( path, sizeof( path ), \"/dev/input/%s\", entry->d_name );\n\n\t\tfor( i = 0; i < evdev.devices; i++ )\n\t\t{\n\t\t\tif( !Q_strncmp( evdev.paths[i], path, sizeof( evdev.paths[i] )))\n\t\t\t\tgoto next;\n\t\t}\n\n\t\tif( Q_strncmp( entry->d_name, \"event\", 5 ) )\n\t\t\tcontinue;\n\n\t\tfd = open( path, O_RDONLY | O_NONBLOCK );\n\n\t\tif( fd == -1 )\n\t\t\tcontinue;\n\n\t\tioctl( fd, EVIOCGBIT( 0, EV_MAX ), types );\n\n\t\tif( !EV_HASBIT( types, EV_KEY ) )\n\t\t\tgoto close;\n\n\t\tioctl( fd, EVIOCGBIT( EV_KEY, KEY_MAX ), codes );\n\n\t\tif( EV_HASBIT( codes, KEY_LEFTCTRL ) && EV_HASBIT( codes, KEY_SPACE ) )\n\t\t\tgoto open;\n\n\t\tif( !EV_HASBIT( codes, BTN_MOUSE ) )\n\t\t\tgoto close;\n\n\t\tif( EV_HASBIT( types, EV_REL ) )\n\t\t{\n\t\t\tmemset( codes, 0, sizeof( codes ) );\n\t\t\tioctl( fd, EVIOCGBIT( EV_REL, KEY_MAX ), codes );\n\n\t\t\tif( !EV_HASBIT( codes, REL_X ) )\n\t\t\t\tgoto close;\n\n\t\t\tif( !EV_HASBIT( codes, REL_Y ) )\n\t\t\t\tgoto close;\n\n\t\t\tif( !EV_HASBIT( codes, REL_WHEEL ) )\n\t\t\t\tgoto close;\n\n\t\t\tgoto open;\n\t\t}\n\t\tgoto close;\nopen:\n\t\tQ_strncpy( evdev.paths[evdev.devices], path, sizeof( evdev.paths[0] ));\n\t\tevdev.fds[evdev.devices++] = fd;\n\t\tCon_Printf( \"Opened device %s\\n\", path );\n#if XASH_INPUT == INPUT_EVDEV\n\t\tif( Sys_CheckParm( \"-grab\" ) )\n\t\t\tioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );\n#endif\n\t\tgoto next;\nclose:\n\t\tclose( fd );\nnext:\n\t\tcontinue;\n\t}\n\tclosedir( dir );\n\n}\n\n/*\n===========\nEvdev_OpenDevice\n\nFor shitty systems that cannot provide relative mouse axes\n===========\n*/\nvoid Evdev_OpenDevice ( const char *path )\n{\n\tint ret, i;\n\n\tif ( evdev.devices >= MAX_EVDEV_DEVICES )\n\t{\n\t\tCon_Printf( \"Only %d devices supported!\\n\", MAX_EVDEV_DEVICES );\n\t\treturn;\n\t}\n\n\tEvdev_Setup();\n\n\tEvdev_CheckPermissions(); // use root to grant access to evdev\n\n\tfor( i = 0; i < evdev.devices; i++ )\n\t{\n\t\tif( !Q_strncmp( evdev.paths[i], path, sizeof( evdev.paths[i] )))\n\t\t{\n\t\t\tCon_Printf( \"device %s already open!\\n\", path );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tret = open( path, O_RDONLY | O_NONBLOCK );\n\tif( ret < 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"Could not open input device %s: %s\\n\", path, strerror( errno ) );\n\t\treturn;\n\t}\n\tCon_Printf( \"Input device #%d: %s opened sucessfully\\n\", evdev.devices, path );\n\tevdev.fds[evdev.devices] = ret;\n\tQ_strncpy( evdev.paths[evdev.devices++], path, sizeof( evdev.paths[0] ));\n\n#if XASH_INPUT == INPUT_EVDEV\n\t\tif( Sys_CheckParm( \"-grab\" ) )\n\t\t\tioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );\n#endif\n}\n\nvoid Evdev_OpenDevice_f( void )\n{\n\tif( Cmd_Argc() < 2 )\n\t\tCon_Printf( S_USAGE \"evdev_opendevice <path>\\n\" );\n\n\tEvdev_OpenDevice( Cmd_Argv( 1 ) );\n}\n\n/*\n===========\nEvdev_CloseDevice_f\n===========\n*/\nvoid Evdev_CloseDevice_f ( void )\n{\n\tuint i;\n\tconst char *arg;\n\n\tif( Cmd_Argc() < 2 )\n\t\treturn;\n\n\targ = Cmd_Argv( 1 );\n\n\tif( Q_isdigit( arg ) )\n\t\ti = Q_atoi( arg );\n\telse for( i = 0; i < evdev.devices; i++ )\n\t\tif( !Q_strncmp( evdev.paths[i], arg, sizeof( evdev.paths[i] )))\n\t\t\tbreak;\n\n\tif( i >= evdev.devices )\n\t{\n\t\tCon_Printf( \"Device %s is not open\\n\", arg );\n\t\treturn;\n\t}\n\n\tclose( evdev.fds[i] );\n\tevdev.devices--;\n\tCon_Printf( \"Device %s closed successfully\\n\", evdev.paths[i] );\n\n\tfor( ; i < evdev.devices; i++ )\n\t{\n\t\tQ_strncpy( evdev.paths[i], evdev.paths[i+1], sizeof( evdev.paths[i] ));\n\t\tevdev.fds[i] = evdev.fds[i+1];\n\t}\n}\n\nvoid IN_EvdevFrame ( void )\n{\n\tint dx = 0, dy = 0, i;\n\n\tfor( i = 0; i < evdev.devices; i++ )\n\t{\n\t\tstruct input_event ev;\n\n\t\twhile ( read( evdev.fds[i], &ev, 16) == 16 )\n\t\t{\n\t\t\tif ( ev.type == EV_REL )\n\t\t\t{\n\t\t\t\tswitch ( ev.code )\n\t\t\t\t{\n\t\t\t\tcase REL_X:\n\t\t\t\t\tdx += ev.value;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase REL_Y:\n\t\t\t\t\tdy += ev.value;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase REL_WHEEL:\n\t\t\t\t\tIN_MWheelEvent( ev.value );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if ( ( ev.type == EV_KEY ) && (cls.key_dest == key_game || XASH_INPUT == INPUT_EVDEV ) )\n\t\t\t{\n\t\t\t\tint key = KeycodeFromEvdev( ev.code, ev.value );\n\n\t\t\t\tif( evdev_keydebug.value )\n\t\t\t\t\tCon_Printf( \"key %d %d %d\\n\", ev.code, key, ev.value );\n\n\t\t\t\tKey_Event( key , ev.value );\n\n\t\t\t\tif( evdev.chars && ev.value )\n\t\t\t\t{\n\t\t\t\t\tif( key >= 32 && key < 127 )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( evdev.shift )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tkey = Key_ToUpper( key );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tCL_CharEvent( key );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif( key == K_SHIFT )\n\t\t\t\t\tevdev.shift = ev.value;\n\t\t\t}\n\t\t}\n\n\t\tif( evdev.grab && evdev.grabtime <= host.realtime )\n\t\t{\n\t\t\tioctl( evdev.fds[i], EVIOCGRAB, (void*) 1 );\n\t\t\tKey_ClearStates();\n\t\t}\n\n\t\tif( m_ignore.value )\n\t\t\tcontinue;\n\n\t\tevdev.x += -dx * m_yaw.value;\n\t\tevdev.y += dy * m_pitch.value;\n\t}\n\tif( evdev.grabtime <= host.realtime )\n\t\tevdev.grab = false;\n}\n\nvoid Evdev_SetGrab( qboolean grab )\n{\n\t// grab only if evdev is secondary input source\n#if XASH_INPUT != INPUT_EVDEV\n\tint i;\n\n\tif( grab )\n\t{\n\t\tKey_Event( K_ESCAPE, 0 ); //Do not leave ESC down\n\t\tevdev.grabtime = host.realtime + 0.5;\n\t\tKey_ClearStates();\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < evdev.devices; i++ )\n\t\t\tioctl( evdev.fds[i], EVIOCGRAB, (void*) 0 );\n\t}\n\tevdev.grab = grab;\n#endif\n}\n\nvoid IN_EvdevMove( float *yaw, float *pitch )\n{\n\t*yaw += evdev.x;\n\t*pitch += evdev.y;\n\tevdev.x = evdev.y = 0.0f;\n}\n\n#if XASH_INPUT == INPUT_EVDEV\n\nvoid Platform_EnableTextInput( qboolean enable )\n{\n\tevdev.chars = enable;\n\tevdev.shift = false;\n}\n\nvoid Platfrom_MouseMove( float *yaw, float *pitch )\n{\n\t// already catched in IN_EvdevMove\n}\n\n#endif\n\nvoid Evdev_Init( void )\n{\n\tCmd_AddRestrictedCommand (\"evdev_open\", Evdev_OpenDevice_f, \"Open event device\");\n\tCmd_AddRestrictedCommand (\"evdev_close\", Evdev_CloseDevice_f, \"Close event device\");\n\tCmd_AddRestrictedCommand (\"evdev_autodetect\", Evdev_Autodetect_f, \"Automaticly open mouses and keyboards\");\n#if XASH_INPUT == INPUT_EVDEV\n\tEvdev_Autodetect_f();\n#endif\n}\n\nvoid Evdev_Shutdown( void )\n{\n\tint i;\n\n\tCmd_RemoveCommand( \"evdev_open\" );\n\tCmd_RemoveCommand( \"evdev_close\" );\n\tCmd_RemoveCommand( \"evdev_autodetect\" );\n\tCvar_RegisterVariable( &evdev_keydebug );\n\n\tfor( i = 0; i < evdev.devices; i++ )\n\t{\n\t\tioctl( evdev.fds[i], EVIOCGRAB, (void*) 0 );\n\t\tclose( evdev.fds[i] );\n\t\tevdev.fds[i] = -1;\n\t}\n\tevdev.devices = 0;\n}\n\n#endif // XASH_USE_EVDEV\n"
  },
  {
    "path": "engine/platform/linux/s_alsa.c",
    "content": "/*\ns_alsa.c - alsa sound hardware output\nCopyright (C) 2019 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#if XASH_SOUND == SOUND_ALSA\n#include <alsa/asoundlib.h>\n#include \"sound.h\"\n\n#define PERIOD_SIZE_DEFAULT 2048\n\nstatic struct s_alsa_t\n{\n\tsnd_pcm_t *pcm_handle;\n\tsnd_pcm_hw_params_t *hw_params;\n\n\tstruct sndinfo * si;\n\n\tint period_size;\n\tqboolean period_npot;\n\tqboolean paused;\n} s_alsa;\n\nvoid SND_Pause_f( void )\n{\n\ts_alsa.paused = Q_atoi( Cmd_Argv( 1 ) ) ;\n\n\tif( !s_alsa.paused )\n\t{\n\t\tsnd_pcm_start( s_alsa.pcm_handle );\n\t\tsnd_pcm_prepare( s_alsa.pcm_handle );\n\t}\n\telse\n\t{\n\t\tsnd_pcm_drain( s_alsa.pcm_handle );\n\t\tsnd_pcm_drop( s_alsa.pcm_handle );\n\t}\n}\n\n\n/*\n==================\nSNDDMA_Init\n\nInitialize ALSA pcm device, and bind it to sndinfo.\n==================\n*/\nqboolean SNDDMA_Init( void )\n{\n\tint err, dir = 0;\n\tunsigned int r;\n\tsnd_pcm_uframes_t p = 0;\n\tstring device = \"default\";\n\tint samples;\n\n\tSys_GetParmFromCmdLine( \"-alsadev\", device );\n\n\tCmd_AddRestrictedCommand(\"pcm_pause\", SND_Pause_f, \"set pcm pause (debug)\" );\n\n\tif( ( err = snd_pcm_open( &s_alsa.pcm_handle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK ) ) < 0)\n\t{\n\t\tCon_Printf( \"ALSA: cannot open device %s(%s)\\n\", device, snd_strerror( err ) );\n\t\treturn false;\n\t}\n\n\tif( ( err = snd_pcm_hw_params_malloc( &s_alsa.hw_params )) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot allocate hw params(%s)\\n\", snd_strerror( err ) );\n\t\tsnd_pcm_close(s_alsa.pcm_handle);\n\t\treturn false;\n\t}\n\n\tif( ( err = snd_pcm_hw_params_any( s_alsa.pcm_handle, s_alsa.hw_params ) ) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot init hw params(%s)\\n\", snd_strerror( err ) );\n\t\tsnd_pcm_hw_params_free(\ts_alsa.hw_params );\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\n\tif( ( err = snd_pcm_hw_params_set_access( s_alsa.pcm_handle, s_alsa.hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot set access(%s)\\n\", snd_strerror( err ) );\n\t\tsnd_pcm_hw_params_free( s_alsa.hw_params );\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\n\tif( ( err = snd_pcm_hw_params_set_format( s_alsa.pcm_handle, s_alsa.hw_params, SND_PCM_FORMAT_S16 ) ) < 0 )\n\t{\n\t\tCon_Printf(\"ALSA: 16 bit not supported\\n\");\n\t\tsnd_pcm_hw_params_free( s_alsa.hw_params );\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\n\tdma.format.speed = SOUND_DMA_SPEED;\n\tr = dma.format.speed;\n\n\tif( ( err = snd_pcm_hw_params_set_rate_near( s_alsa.pcm_handle, s_alsa.hw_params, &r, &dir ) ) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot set rate %d(%s)\\n\", r, snd_strerror( err ) );\n\t\tsnd_pcm_hw_params_free(s_alsa.hw_params);\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\telse\n\t{\n\t\t// rate succeeded, but is perhaps slightly different\n\t\tif( dir != 0 )\n\t\t{\n\t\t\tCon_Printf( \"ALSA: rate %d not supported, using %d\\n\", SOUND_DMA_SPEED, r );\n\t\t\tdma.format.speed = r;\n\t\t\tdir = 0;\n\t\t}\n\t}\n\n\tdma.format.channels = 2;\n\n\tif( ( err = snd_pcm_hw_params_set_channels(s_alsa.pcm_handle, s_alsa.hw_params, dma.format.channels ) ) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot set channels %d(%s)\\n\", 2, snd_strerror( err ) );\n\t\tsnd_pcm_hw_params_free( s_alsa.hw_params );\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\n\tp = PERIOD_SIZE_DEFAULT;\n\tif( ( err = snd_pcm_hw_params_set_period_size_near( s_alsa.pcm_handle, s_alsa.hw_params, &p, &dir ) ) < 0 )\n\t{\n\t\tCon_Printf(\"ALSA: cannot set period size (%s)\\n\", snd_strerror(err));\n\t\tsnd_pcm_hw_params_free(s_alsa.hw_params);\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\telse\n\t{\n\t\t// period succeeded, but is perhaps slightly different\n\t\tif( dir != 0 )\n\t\t{\n\t\t\tsnd_pcm_hw_params_get_period_size(s_alsa.hw_params, &p, NULL);\n\t\t\tCon_Printf( \"ALSA: period %d not supported, using %lu\\n\", PERIOD_SIZE_DEFAULT, p );\n\t\t\tdir = 0;\n\t\t}\n\t}\n\n\tif( 0 ) //( p & (p - 1) ) == 0 ) // power of two\n\t{\n\t\t// normally, set samples to period * 2 to minimize latency\n\t\tsamples = p * 2;\n\t}\n\telse\n\t{\n\t\t// if period is NPOT it cannot be used as dma.samples in Xash3D\n\t\t// and need more space to send buffer partially\n\t\tsamples = 1;\n\t\twhile( samples < p * 4 )\n\t\t\tsamples <<= 1;\n\t\ts_alsa.period_npot = true;\n\t}\n\ts_alsa.period_size = p;\n\n\tp = samples;\n\tsnd_pcm_hw_params_set_buffer_size_near( s_alsa.pcm_handle, s_alsa.hw_params, &p );\n\tCon_Printf( \"ALSA: buffer size %lu\\n\", p );\n\n\t// set params\n\tif( ( err = snd_pcm_hw_params( s_alsa.pcm_handle, s_alsa.hw_params ) ) < 0 )\n\t{\n\t\tCon_Printf( \"ALSA: cannot set params(%s)\\n\", snd_strerror( err ) );\n\t\tsnd_pcm_hw_params_free(s_alsa.hw_params);\n\t\tsnd_pcm_close( s_alsa.pcm_handle );\n\t\treturn false;\n\t}\n\n\tdma.buffer = Mem_Calloc( sndpool, samples * 2 );  //allocate pcm frame buffer\n\tdma.samplepos = 0;\n\tdma.samples = samples;\n\tdma.format.width = 2;\n\tdma.initialized = 1;\n\tdma.backendName = \"ALSA\";\n\n\tsnd_pcm_prepare( s_alsa.pcm_handle );\n\tsnd_pcm_writei( s_alsa.pcm_handle, dma.buffer, 2 * s_alsa.period_size );\n\tsnd_pcm_start( s_alsa.pcm_handle );\n\n\treturn true;\n}\n\n/*\n==============\nSNDDMA_Shutdown\n\nCloses the ALSA pcm device and frees the dma buffer.\n===============\n*/\nvoid SNDDMA_Shutdown(void)\n{\n\tCon_Printf( \"Shutting down audio.\\n\" );\n\tdma.initialized = false;\n\n\tif( dma.buffer )\n\t{\n\t  snd_pcm_drop( s_alsa.pcm_handle );\n\t  snd_pcm_close( s_alsa.pcm_handle );\n\t  Mem_Free( dma.buffer );\n\t  dma.buffer = NULL;\n\t}\n}\n\n/*\n==============\nSNDDMA_Submit\n\nSend sound to device if buffer isn't really the dma buffer\n===============\n*/\nvoid SNDDMA_Submit( void )\n{\n\tif( s_alsa.paused )\n\t\treturn;\n\n\tif( s_alsa.period_npot )\n\t{\n\t\tint avail = snd_pcm_avail_update( s_alsa.pcm_handle );\n\n\t\tif( avail < 0 )\n\t\t\tsnd_pcm_prepare( s_alsa.pcm_handle );\n\n\t\twhile( avail >= s_alsa.period_size )\n\t\t{\n\t\t\tint size    = dma.samples << 1;\n\t\t\tint pos     = dma.samplepos << 1;\n\t\t\tunsigned long  len = s_alsa.period_size * 4;\n\t\t\tint wrapped = pos + len - size;\n\t\t\tint  w;\n\n\t\t\tif( wrapped < 0 )\n\t\t\t{\n\t\t\t\tw = snd_pcm_writei( s_alsa.pcm_handle, dma.buffer + pos, len / 4 );\n\t\t\t\tif( w < 0 )\n\t\t\t\t{\n\t\t\t\t\tsnd_pcm_prepare(s_alsa.pcm_handle);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdma.samplepos += len >> 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tint remaining = size - pos;\n\t\t\t\tw = snd_pcm_writei( s_alsa.pcm_handle, dma.buffer + pos, remaining / 4 );\n\t\t\t\tif( w < 0 )\n\t\t\t\t{\n\t\t\t\t\tsnd_pcm_prepare(s_alsa.pcm_handle);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tw = snd_pcm_writei( s_alsa.pcm_handle, dma.buffer, wrapped / 4 );\n\t\t\t\tif( w < 0 )\n\t\t\t\t{\n\t\t\t\t\tsnd_pcm_prepare(s_alsa.pcm_handle);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdma.samplepos = wrapped >> 1;\n\t\t\t}\n\n\t\t\tavail = snd_pcm_avail_update( s_alsa.pcm_handle );\n\t\t}\n\t}\n\telse // period is 1/2 of samples\n\t{\n\t\tint s, w, frames;\n\t\tvoid *start;\n\n\t\tif( !dma.buffer )\n\t\t\treturn;\n\n\t\ts = dma.samplepos * 2;\n\t\tstart = (void *)&dma.buffer[s];\n\t\tframes = s_alsa.period_size / 2;\n\t\t// write to card\n\t\tif( ( w = snd_pcm_writei( s_alsa.pcm_handle, start, frames ) ) < 0)\n\t\t{\n\t\t\t// xrun occured\n\t\t\tsnd_pcm_prepare( s_alsa.pcm_handle );\n\t\t\treturn;\n\t\t}\n\n\t\tdma.samplepos += w * 2;  // mark progress\n\n\t\tif(dma.samplepos >= dma.samples)\n\t\t  dma.samplepos = 0;  // wrap buffer\n\t}\n}\n\n/*\n==============\nSNDDMA_BeginPainting\n\nCallback provided by the engine in case we need it.  We don't.\n==============\n*/\nvoid SNDDMA_BeginPainting(void)\n{\n}\n\n/*\n===========\nSNDDMA_Activate\nCalled when the main window gains or loses focus.\nThe window have been destroyed and recreated\nbetween a deactivate and an activate.\n===========\n*/\nvoid SNDDMA_Activate( qboolean active )\n{\n\tif( !dma.initialized )\n\t\treturn;\n\n\ts_alsa.paused = !active;\n\n\tif( !s_alsa.paused )\n\t{\n\t\tsnd_pcm_start( s_alsa.pcm_handle );\n\t\tsnd_pcm_prepare( s_alsa.pcm_handle );\n\t}\n\telse\n\t{\n\t\tsnd_pcm_drain( s_alsa.pcm_handle );\n\t\tsnd_pcm_drop( s_alsa.pcm_handle );\n\t}\n}\n\nqboolean VoiceCapture_Init( void )\n{\n\treturn false;\n}\n\nqboolean VoiceCapture_Activate( qboolean activate )\n{\n\treturn false;\n}\n\nqboolean VoiceCapture_Lock( qboolean lock )\n{\n\treturn false;\n}\n\nvoid VoiceCapture_Shutdown( void )\n{\n\n}\n\n#endif\n"
  },
  {
    "path": "engine/platform/linux/sys_linux.c",
    "content": "/*\nsys_linux.c - Linux system utils\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include <time.h>\n#include <stdlib.h>\n#include <fcntl.h>\n#include <dlfcn.h>\n#include <signal.h>\n#include <ucontext.h>\n#include <time.h>\n#include <unistd.h>\n#include <math.h>\n#include <sys/syscall.h>\n#include \"platform/platform.h\"\n#include <sys/types.h>\n\n// Glibc misses this macro in POSIX headers (bits/types/sigevent_t.h)\n// but it does present in Linux headers (asm-generic/siginfo.h) and musl\n#if !defined( sigev_notify_thread_id )\n#define sigev_notify_thread_id _sigev_un._tid\n#endif // !defined( sigev_notify_thread_id )\n\nstatic void *g_hsystemd;\nstatic int (*g_pfn_sd_notify)( int unset_environment, const char *state );\n\nint Linux_GetProcessID( void )\n{\n\treturn syscall( __NR_gettid );\n}\n\nqboolean Platform_DebuggerPresent( void )\n{\n\tchar buf[4096];\n\tssize_t num_read;\n\tint status_fd;\n\n\tstatus_fd = open( \"/proc/self/status\", O_RDONLY );\n\tif ( status_fd == -1 )\n\t\treturn 0;\n\n\tnum_read = read( status_fd, buf, sizeof( buf ) );\n\tclose( status_fd );\n\n\tif ( num_read > 0 )\n\t{\n\t\tstatic const char TracerPid[] = \"TracerPid:\";\n\t\tconst byte *tracer_pid;\n\n\t\tbuf[num_read] = 0;\n\t\ttracer_pid    = (const byte*)Q_strstr( buf, TracerPid );\n\t\tif( !tracer_pid )\n\t\t\treturn false;\n\t\t//printf( \"%s\\n\", tracer_pid );\n\t\twhile( *tracer_pid < '0' || *tracer_pid > '9'  )\n\t\t\tif( *tracer_pid++ == '\\n' )\n\t\t\t\treturn false;\n\t\t//printf( \"%s\\n\", tracer_pid );\n\t\treturn !!Q_atoi( (const char*)tracer_pid );\n\t}\n\n\treturn false;\n}\n\nvoid Platform_SetStatus( const char *status )\n{\n\tstring notify_str;\n\n\tif( !g_pfn_sd_notify )\n\t\treturn;\n\n\t// TODO: report STOPPING=1\n\tQ_snprintf( notify_str, sizeof( notify_str ),\n\t\t\"READY=1\\n\"\n\t\t\"WATCHDOG=1\\n\"\n\t\t\"STATUS=%s\\n\", status );\n\n\t// Quote: In order to support both service managers that implement this\n\t// scheme and those which do not, it is generally recommended to ignore\n\t// the return value of this call\n\t// a1ba: ok, you asked for no error handling :)\n\tg_pfn_sd_notify( false, notify_str );\n}\n\nvoid Linux_Init( void )\n{\n\t// sd_notify only for dedicated targets, don't waste time on full client\n\tif( !Host_IsDedicated( ))\n\t\treturn;\n\n\t// manpage says sd_notify will send messages to socket in NOTIFY_SOCKET\n\t// environment variable. Check if it's available.\n\tif( getenv( \"NOTIFY_SOCKET\" ) == NULL )\n\t\treturn;\n\n\tif(( g_hsystemd = dlopen( \"libsystemd.so.0\", RTLD_LAZY )) == NULL )\n\t\treturn;\n\n\tif(( g_pfn_sd_notify = dlsym( g_hsystemd, \"sd_notify\" )) == NULL )\n\t{\n\t\tdlclose( g_hsystemd );\n\t\tg_hsystemd = NULL;\n\t}\n\n\tCon_Reportf( \"%s: sd_notify found, will report status to systemd\\n\", __func__ );\n}\n\nvoid Linux_Shutdown( void )\n{\n\tg_pfn_sd_notify = NULL;\n\n\tif( g_hsystemd )\n\t{\n\t\tdlclose( g_hsystemd );\n\t\tg_hsystemd = NULL;\n\t}\n}\n\nstatic void Linux_TimerHandler( int sig, siginfo_t *si, void *uc )\n{\n\ttimer_t  *tidp = si->si_value.sival_ptr;\n\tint overrun = timer_getoverrun( *tidp );\n\tCon_Printf( \"Frame too long (overrun %d)!\\n\", overrun );\n}\n\n#define DEBUG_TIMER_SIGNAL SIGRTMIN\n\nvoid Linux_SetTimer( float tm )\n{\n\tstatic timer_t timerid;\n\n\tif( !timerid && tm )\n\t{\n\t\tstruct sigevent    sev = { 0 };\n\t\tstruct sigaction   sa = { 0 };\n\n\t\tsa.sa_flags = SA_SIGINFO;\n\t\tsa.sa_sigaction = Linux_TimerHandler;\n\t\tsigaction( DEBUG_TIMER_SIGNAL, &sa, NULL );\n\t\t// this path availiable in POSIX, but may signal wrong thread...\n\t\t// sev.sigev_notify = SIGEV_SIGNAL;\n\t\tsev.sigev_notify = SIGEV_THREAD_ID;\n\t\tsev.sigev_notify_thread_id = Linux_GetProcessID();\n\t\tsev.sigev_signo = DEBUG_TIMER_SIGNAL;\n\t\tsev.sigev_value.sival_ptr = &timerid;\n\t\ttimer_create( CLOCK_REALTIME, &sev, &timerid );\n\t}\n\n\tif( timerid )\n\t{\n\t\tstruct itimerspec  its = {0};\n\t\tits.it_value.tv_sec = tm;\n\t\tits.it_value.tv_nsec = 1000000000ULL * fmod( tm, 1.0f );\n\t\tits.it_interval.tv_sec = 0;\n\t\tits.it_interval.tv_nsec = 0;\n\t\ttimer_settime( timerid, 0, &its, NULL );\n\t}\n}\n"
  },
  {
    "path": "engine/platform/linux/vid_fbdev.c",
    "content": "#include \"platform/platform.h\"\n#if XASH_VIDEO == VIDEO_FBDEV\n#include \"input.h\"\n#include \"client.h\"\n#include \"filesystem.h\"\n#include \"vid_common.h\"\n#include <fcntl.h>\n#include <errno.h>\n\n#include <linux/fb.h>\n#include <sys/mman.h>\n#include <sys/ioctl.h>\n#if XASH_ANDROID\n#include <linux/kd.h>\n#else\n#include <sys/kd.h>\n#endif\n\nstruct fb_s\n{\n\tint fd, tty_fd;\n\tvoid *map;\n\tstruct fb_var_screeninfo vinfo;\n\tstruct fb_fix_screeninfo finfo;\n\tqboolean vsync;\n\tint doublebuffer;\n} fb;\n\n#define DEFAULT_FBDEV \"/dev/fb0\"\n\nvoid Platform_Minimize_f( void )\n{\n\t// stub\n}\n\n/*\n========================\nAndroid_SwapBuffers\n\nUpdate screen. Use native EGL if possible\n========================\n*/\nvoid GL_SwapBuffers( void )\n{\n}\n\nvoid FB_GetScreenRes( int *x, int *y )\n{\n\t*x = fb.vinfo.xres;\n\t*y = fb.vinfo.yres;\n}\n\nqboolean  R_Init_Video( const int type )\n{\n\tqboolean retval;\n\tstring fbdev = DEFAULT_FBDEV;\n\tfb.fd = -1;\n\n\tif( type != REF_SOFTWARE )\n\t\treturn false;\n\n\tSys_GetParmFromCmdLine( \"-fbdev\", fbdev );\n\n\tfb.fd = open( fbdev, O_RDWR );\n\n\tif( fb.fd < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"failed to open framebuffer device: %s\\n\", strerror(errno));\n\t}\n\n\tif( Sys_CheckParm( \"-ttygfx\" ) )\n\t\tfb.tty_fd = open( \"/dev/tty\", O_RDWR ); // only need this to set graphics mode, optional\n\n\tioctl(fb.fd, FBIOGET_FSCREENINFO, &fb.finfo);\n\tioctl(fb.fd, FBIOGET_VSCREENINFO, &fb.vinfo);\n\n\tif( !(retval = VID_SetMode()) )\n\t{\n\t\treturn retval;\n\t}\n\n\thost.renderinfo_changed = false;\n\n\treturn true;\n}\n\nvoid R_Free_Video( void )\n{\n\t// VID_DestroyWindow ();\n\n\t// R_FreeVideoModes();\n\tif( fb.doublebuffer )\n\t{\n\t\tfb.vinfo.yoffset = 0;\n\t\tfb.vinfo.yres_virtual >>= 1;\n\t\tioctl( fb.fd, FBIOPAN_DISPLAY, &fb.vinfo );\n\t}\n\tif( fb.map )\n\t\tmunmap( fb.map, fb.finfo.smem_len );\n\tclose( fb.fd );\n\n\tfb.fd = -1;\n\tfb.map = NULL;\n\n\tif( fb.tty_fd >= 0 )\n\t{\n\t\tioctl( fb.tty_fd, KDSETMODE, KD_TEXT );\n\t\tclose( fb.tty_fd );\n\t\tfb.tty_fd = -1;\n\t}\n\n\tref.dllFuncs.GL_ClearExtensions();\n}\n\n\nqboolean VID_SetMode( void )\n{\n\tif( fb.tty_fd > 0 )\n\t\tioctl( fb.tty_fd, KDSETMODE, KD_GRAPHICS );\n\tR_ChangeDisplaySettings( 0, 0, WINDOW_MODE_FULLSCREEN ); // width and height are ignored anyway\n\n\treturn true;\n}\n\nrserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )\n{\n\tint render_w, render_h;\n\n\tFB_GetScreenRes( &width, &height );\n\n\trender_w = width;\n\trender_h = height;\n\n\tCon_Reportf( \"%s: forced resolution to %dx%d)\\n\", __func__, width, height );\n\n\tVID_SetDisplayTransform( &render_w, &render_h );\n\tR_SaveVideoMode( width, height, render_w, render_h, false );\n\n\treturn rserr_ok;\n}\n\nint GL_SetAttribute( int attr, int val )\n{\n\treturn 0;\n}\n\nint GL_GetAttribute( int attr, int *val )\n{\n\treturn 0;\n}\n\nint R_MaxVideoModes( void )\n{\n\treturn 0;\n}\n\nvidmode_t* R_GetVideoMode( int num )\n{\n\treturn NULL;\n}\n\nvoid* GL_GetProcAddress( const char *name ) // RenderAPI requirement\n{\n\treturn NULL;\n}\n\nvoid GL_UpdateSwapInterval( void )\n{\n\tif( FBitSet( gl_vsync.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( gl_vsync.flags, FCVAR_CHANGED );\n\t\tfb.vsync = gl_vsync.value;\n\t}\n}\n\nvoid *SW_LockBuffer( void )\n{\n\tif( fb.vsync )\n\t{\n\t\t int stub = 0;\n\t\t ioctl(fb.fd, FBIO_WAITFORVSYNC, &stub);\n\t}\n\tif( fb.doublebuffer )\n\t{\n\t\tstatic int page = 0;\n\t\tpage = !page;\n\t\tfb.vinfo.yoffset = page * fb.vinfo.yres;\n\t\treturn fb.map + page * fb.doublebuffer;\n\t}\n\telse\n\t\treturn fb.map;\n}\n\nvoid SW_UnlockBuffer( void )\n{\n\t// some single-buffer fb devices need this too\n\tioctl(fb.fd, FBIOPAN_DISPLAY, &fb.vinfo);\n}\n\n#define FB_BF_TO_MASK(x) (((1 << x.length) - 1) << (x.offset))\n\nqboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tif( width > fb.vinfo.xres_virtual || height > fb.vinfo.yres_virtual )\n\t{\n\t\tCon_Printf( S_ERROR \"requested size %dx%d not fit to framebuffer size %dx%d\\n\",\n\t\t\t\t\twidth, height, fb.vinfo.xres_virtual, fb.vinfo.yres_virtual );\n\t\treturn false;\n\t}\n\n\t*bpp = fb.vinfo.bits_per_pixel >> 3;\n\t*stride = fb.vinfo.xres_virtual;\n\t*r = FB_BF_TO_MASK(fb.vinfo.red);\n\t*g = FB_BF_TO_MASK(fb.vinfo.green);\n\t*b = FB_BF_TO_MASK(fb.vinfo.blue);\n\n\tif( Sys_CheckParm(\"-doublebuffer\") )\n\t{\n\t\tfb.doublebuffer = *bpp * *stride * fb.vinfo.yres;\n\t\tfb.vinfo.yres_virtual = fb.vinfo.yres * 2;\n\t\tif(ioctl (fb.fd, FBIOPUT_VSCREENINFO, &fb.vinfo ))\n\t\t{\n\t\t\tfb.vinfo.transp.length = fb.vinfo.transp.offset = 0;\n\t\t\tif( ioctl (fb.fd, FBIOPUT_VSCREENINFO, &fb.vinfo ) )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"failed to enable double buffering!\\n\" );\n\t\t\t}\n\t\t}\n\n\t\tioctl( fb.fd, FBIOGET_FSCREENINFO, &fb.finfo );\n\t\tioctl( fb.fd, FBIOGET_VSCREENINFO, &fb.vinfo );\n\t\tioctl( fb.fd, FBIOPAN_DISPLAY, &fb.vinfo );\n\n\t\tif( fb.finfo.smem_len < fb.doublebuffer * 2 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"not enough memory for double buffering, disabling!\\n\" );\n\t\t\tfb.doublebuffer = 0;\n\t\t}\n\t}\n\tif( fb.map )\n\t\tmunmap(fb.map, fb.finfo.smem_len);\n\tfb.map = mmap(0, fb.finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fb.fd, 0);\n\n\tif( !fb.map )\n\t\treturn false;\n\treturn true;\n}\n\nref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type )\n{\n\treturn REF_WINDOW_TYPE_NULL;\n}\n\n#endif\n"
  },
  {
    "path": "engine/platform/misc/kmalloc.c",
    "content": "/*\t$NetBSD: malloc.c,v 1.8 1997/04/07 03:12:14 christos Exp $\t*/\n\n/*\n * Copyright (c) 1983 Regents of the University of California.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. All advertising materials mentioning features or use of this software\n *    must display the following acknowledgement:\n *\tThis product includes software developed by the University of\n *\tCalifornia, Berkeley and its contributors.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n#if defined(LIBC_SCCS) && !defined(lint)\n#if 0\nstatic char *sccsid = \"from: @(#)malloc.c\t5.11 (Berkeley) 2/23/91\";\n#else\nstatic char *rcsid = \"$NetBSD: malloc.c,v 1.8 1997/04/07 03:12:14 christos Exp $\";\n#endif\n#endif /* LIBC_SCCS and not lint */\n\n/*\n * malloc.c (Caltech) 2/21/82\n * Chris Kingsley, kingsley@cit-20.\n *\n * This is a very fast storage allocator.  It allocates blocks of a small\n * number of different sizes, and keeps free lists of each size.  Blocks that\n * don't exactly fit are passed up to the next larger size.  In this\n * implementation, the available sizes are 2^n-4 (or 2^n-10) bytes long.\n * This is designed for use in a virtual memory environment.\n */\n\n#include  <sys/types.h>\n#include  <stdlib.h>\n#include  <string.h>\n#include \"platform/swap/swap.h\"\n/* #define MSTATS 1 */\n\n#ifndef _WIN32\n#include  <unistd.h>\n#define\tNULL 0\n#else\n\n#define u_char unsigned char\n#define u_long unsigned long\n#define getpagesize() 4096\n#define caddr_t size_t\n#define bcopy(a,b,c) memcpy(b,a,c)\n\n#endif\n\n\nstatic void morecore(int);\n/*\n * The overhead on a block is at least 4 bytes.  When free, this space\n * contains a pointer to the next free block, and the bottom two bits must\n * be zero.  When in use, the first byte is set to MAGIC, and the second\n * byte is the size index.  The remaining bytes are for alignment.\n * If range checking is enabled then a second word holds the size of the\n * requested block, less 1, rounded up to a multiple of sizeof(RMAGIC).\n * The order of elements is critical: ov_magic must overlay the low order\n * bits of ov_next, and ov_magic can not be a valid ov_next bit pattern.\n */\nunion\toverhead {\n\tunion\toverhead *ov_next;\t/* when free */\n\tstruct {\n\t\tu_char\tovu_magic;\t/* magic number */\n\t\tu_char\tovu_index;\t/* bucket # */\n#ifdef RCHECK\n\t\tu_short\tovu_rmagic;\t/* range magic number */\n\t\tu_long\tovu_size;\t/* actual block size */\n#endif\n\t} ovu;\n#define\tov_magic\tovu.ovu_magic\n#define\tov_index\tovu.ovu_index\n#define\tov_rmagic\tovu.ovu_rmagic\n#define\tov_size\t\tovu.ovu_size\n};\n\nstatic int findbucket(\tunion overhead *freep,\nint srchlen);\n#define\tMAGIC\t\t0xef\t\t/* magic # on accounting info */\n#define RMAGIC\t\t0x5555\t\t/* magic # on range info */\n\n#ifdef RCHECK\n#define\tRSLOP\t\tsizeof (u_short)\n#else\n#define\tRSLOP\t\t0\n#endif\n\n/*\n * nextf[i] is the pointer to the next free block of size 2^(i+3).  The\n * smallest allocatable block is 8 bytes.  The overhead information\n * precedes the data area returned to the user.\n */\n#define\tNBUCKETS 30\nstatic\tunion overhead *nextf[NBUCKETS];\n/* extern\tchar *sbrk(); */\n\nstatic\tint pagesz;\t\t\t/* page size */\nstatic\tint pagebucket;\t\t\t/* page size bucket */\n\n#ifdef MSTATS\n/*\n * nmalloc[i] is the difference between the number of mallocs and frees\n * for a given block size.\n */\nstatic\tunsigned int nmalloc[NBUCKETS];\n#include  <stdio.h>\n#endif\n\n#if defined(DEBUG) || defined(RCHECK)\n#define\tASSERT(p)   if (!(p)) botch(__STRING(p))\n#include  /* <stdio.h> */\nstatic\nbotch(s)\n\tchar *s;\n{\n\tfprintf(stderr, \"\\r\\nassertion botched: %s\\r\\n\", s);\n \t(void) fflush(stderr);\t\t/* just in case user buffered it */\n\tabort();\n}\n#else\n#define\tASSERT(p)\n#endif\n\n#include <stdio.h>\n\n/*  */\n/* malloc */\n/* malloc */\nvoid * SWAP_Malloc( size_t nbytes )\n{\n  \tregister union overhead *op;\n\tregister int bucket;\n  \tregister long n;\n\tregister unsigned amt;\n\n\t/*\n\t * First time malloc is called, setup page size and\n\t * align break pointer so all data will be page aligned.\n\t */\n\tif (pagesz == 0) {\n\t\tpagesz = n = getpagesize();\n\t\top = (union overhead *)SWAP_Sbrk( 0 );\n  \t\tn = n - sizeof (*op) - ((long)op & (n - 1));\n\t\tif (n < 0)\n\t\t\tn += pagesz;\n  \t\tif (n) {\n\t\t\tif (SWAP_Sbrk(n) == (char *)-1) {\n\t\t\t\treturn (NULL);\n\t\t\t}\n\t\t}\n\t\tbucket = 0;\n\t\tamt = 8;\n\t\twhile (pagesz > amt) {\n\t\t\tamt <<= 1;\n\t\t\tbucket++;\n\t\t}\n\t\tpagebucket = bucket;\n\t}\n\t/*\n\t * Convert amount of memory requested into closest block size\n\t * stored in hash buckets which satisfies request.\n\t * Account for space used per block for accounting.\n\t */\n\tif (nbytes <= (n = pagesz - sizeof (*op) - RSLOP)) {\n#ifndef RCHECK\n\t\tamt = 8;\t/* size of first bucket */\n\t\tbucket = 0;\n#else\n\t\tamt = 16;\t/* size of first bucket */\n\t\tbucket = 1;\n#endif\n\t\tn = -((long)sizeof (*op) + RSLOP);\n\t} else {\n\t\tamt = pagesz;\n\t\tbucket = pagebucket;\n\t}\n\twhile (nbytes > amt + n) {\n\t\tamt <<= 1;\n\t\tif (amt == 0) {\n\t\t\treturn (NULL);\n\t\t}\n\t\tbucket++;\n\t}\n\t/*\n\t * If nothing in hash bucket right now,\n\t * request more memory from the system.\n\t */\n  \tif ((op = nextf[bucket]) == NULL) {\n  \t\tmorecore(bucket);\n\t\tif ((op = nextf[bucket]) == NULL) {\n  \t\t\treturn (NULL);\n\t\t}\n\t}\n\t/* remove from linked list */\n  \tnextf[bucket] = op->ov_next;\n\top->ov_magic = MAGIC;\n\top->ov_index = bucket;\n#ifdef MSTATS\n  \tnmalloc[bucket]++;\n#endif\n#ifdef RCHECK\n\t/*\n\t * Record allocated size of block and\n\t * bound space with magic numbers.\n\t */\n\top->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1);\n\top->ov_rmagic = RMAGIC;\n  \t*(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC;\n#endif\n  \treturn ((char *)(op + 1));\n}\n\nvoid * SWAP_Calloc(size_t nelem, size_t elsize) {\n  void * ptr = SWAP_Malloc (nelem * elsize);\n  // Zero out the malloc'd block.\n  memset (ptr, 0, nelem * elsize);\n  return ptr;\n}\n\n\n/*\n * Allocate more memory to the indicated bucket.\n */\nstatic void\nmorecore(int bucket)\n{\n  \tregister union overhead *op;\n\tregister long sz;\t\t/* size of desired block */\n  \tlong amt;\t\t\t/* amount to allocate */\n  \tint nblks;\t\t\t/* how many blocks we get */\n\n\t/*\n\t * sbrk_size <= 0 only for big, FLUFFY, requests (about\n\t * 2^30 bytes on a VAX, I think) or for a negative arg.\n\t */\n\tsz = 1 << (bucket + 3);\n#ifdef DEBUG\n\tASSERT(sz > 0);\n#else\n\tif (sz <= 0)\n\t\treturn;\n#endif\n\tif (sz < pagesz) {\n\t\tamt = pagesz;\n  \t\tnblks = amt / sz;\n\t} else {\n\t\tamt = sz + pagesz;\n\t\tnblks = 1;\n\t}\n\top = (union overhead *)SWAP_Sbrk(amt);\n\t/* no more room! */\n  \tif ((long)op == -1)\n  \t\treturn;\n\t/*\n\t * Add new memory allocated to that on\n\t * free list for this hash bucket.\n\t */\n  \tnextf[bucket] = op;\n  \twhile (--nblks > 0) {\n\t\top->ov_next = (union overhead *)((caddr_t)op + sz);\n\t\top = (union overhead *)((caddr_t)op + sz);\n  \t}\n}\n/*  */\n/* free */\n/* free */\nvoid SWAP_Free(\tvoid *cp)\n\n{\n  \tregister long size;\n\tregister union overhead *op;\n\n  \tif (cp == NULL)\n  \t\treturn;\n\top = (union overhead *)((caddr_t)cp - sizeof (union overhead));\n#ifdef DEBUG\n  \tASSERT(op->ov_magic == MAGIC);\t\t/* make sure it was in use */\n#else\n\tif (op->ov_magic != MAGIC)\n\t\treturn;\t\t\t\t/* sanity */\n#endif\n#ifdef RCHECK\n  \tASSERT(op->ov_rmagic == RMAGIC);\n\tASSERT(*(u_short *)((caddr_t)(op + 1) + op->ov_size) == RMAGIC);\n#endif\n  \tsize = op->ov_index;\n  \tASSERT(size < NBUCKETS);\n\top->ov_next = nextf[size];\t/* also clobbers ov_magic */\n  \tnextf[size] = op;\n#ifdef MSTATS\n  \tnmalloc[size]--;\n#endif\n}\n\n\n/* EDB: added size lookup */\n\nsize_t SWAP_MallocUsableSize(void * cp)\n{\n\tregister long size;\n\tregister union overhead *op;\n\n  \tif (cp == NULL)\n  \t\treturn 0;\n\top = (union overhead *)((caddr_t)cp - sizeof (union overhead));\n#ifdef DEBUG\n  \tASSERT(op->ov_magic == MAGIC);\t\t/* make sure it was in use */\n#else\n\tif (op->ov_magic != MAGIC)\n\t\treturn 0;\t\t\t\t/* sanity */\n#endif\n#ifdef RCHECK\n  \tASSERT(op->ov_rmagic == RMAGIC);\n\tASSERT(*(u_short *)((caddr_t)(op + 1) + op->ov_size) == RMAGIC);\n#endif\n  \treturn op->ov_index;\n}\n/* End EDB */\n\n\n/*\n * When a program attempts \"storage compaction\" as mentioned in the\n * old malloc man page, it realloc's an already freed block.  Usually\n * this is the last block it freed; occasionally it might be farther\n * back.  We have to search all the free lists for the block in order\n * to determine its bucket: 1st we make one pass thru the lists\n * checking only the first block in each; if that fails we search\n * ``realloc_srchlen'' blocks in each list for a match (the variable\n * is extern so the caller can modify it).  If that fails we just copy\n * however many bytes was given to realloc() and hope it's not huge.\n */\nint realloc_srchlen = 4;\t/* 4 should be plenty, -1 =>'s whole list */\n\n/*  */\n/* realloc */\n/* realloc */\nvoid *\nSWAP_Realloc(\tvoid *cp,\nsize_t nbytes)\n\n{\n  \tregister u_long onb;\n\tregister long i;\n\tunion overhead *op;\n  \tchar *res;\n\tint was_alloced = 0;\n\n  \tif (cp == NULL)\n\t\treturn (SWAP_Malloc(nbytes));\n\tif (nbytes == 0) {\n\t\tSWAP_Free (cp);\n\t\treturn NULL;\n\t}\n\top = (union overhead *)((caddr_t)cp - sizeof (union overhead));\n\tif (op->ov_magic == MAGIC) {\n\t\twas_alloced++;\n\t\ti = op->ov_index;\n\t} else {\n\t\t/*\n\t\t * Already free, doing \"compaction\".\n\t\t *\n\t\t * Search for the old block of memory on the\n\t\t * free list.  First, check the most common\n\t\t * case (last element free'd), then (this failing)\n\t\t * the last ``realloc_srchlen'' items free'd.\n\t\t * If all lookups fail, then assume the size of\n\t\t * the memory block being realloc'd is the\n\t\t * largest possible (so that all \"nbytes\" of new\n\t\t * memory are copied into).  Note that this could cause\n\t\t * a memory fault if the old area was tiny, and the moon\n\t\t * is gibbous.  However, that is very unlikely.\n\t\t */\n\t\tif ((i = findbucket(op, 1)) < 0 &&\n\t\t    (i = findbucket(op, realloc_srchlen)) < 0)\n\t\t\ti = NBUCKETS;\n\t}\n\tonb = 1 << (i + 3);\n\tif (onb < pagesz)\n\t\tonb -= sizeof (*op) + RSLOP;\n\telse\n\t\tonb += pagesz - sizeof (*op) - RSLOP;\n\t/* avoid the copy if same size block */\n\tif (was_alloced) {\n\t\tif (i) {\n\t\t\ti = 1 << (i + 2);\n\t\t\tif (i < pagesz)\n\t\t\t\ti -= sizeof (*op) + RSLOP;\n\t\t\telse\n\t\t\t\ti += pagesz - sizeof (*op) - RSLOP;\n\t\t}\n\t\tif (nbytes <= onb && nbytes > i) {\n#ifdef RCHECK\n\t\t\top->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1);\n\t\t\t*(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC;\n#endif\n\t\t\treturn(cp);\n\t\t} else\n\t\t\tSWAP_Free(cp);\n\t}\n\tif ((res = SWAP_Malloc(nbytes)) == NULL)\n  \t\treturn (NULL);\n  \tif (cp != res)\t\t/* common optimization if \"compacting\" */\n\t\tbcopy(cp, res, (nbytes < onb) ? nbytes : onb);\n  \treturn (res);\n}\n\n/*\n * Search ``srchlen'' elements of each free list for a block whose\n * header starts at ``freep''.  If srchlen is -1 search the whole list.\n * Return bucket number, or -1 if not found.\n */\nstatic int\nfindbucket(\tunion overhead *freep,\nint srchlen)\n\n{\n\tregister union overhead *p;\n\tregister int i, j;\n\n\tfor (i = 0; i < NBUCKETS; i++) {\n\t\tj = 0;\n\t\tfor (p = nextf[i]; p && j != srchlen; p = p->ov_next) {\n\t\t\tif (p == freep)\n\t\t\t\treturn (i);\n\t\t\tj++;\n\t\t}\n\t}\n\treturn (-1);\n}\n\n#ifdef MSTATS\n/*\n * mstats - print out statistics about malloc\n *\n * Prints two lines of numbers, one showing the length of the free list\n * for each size category, the second showing the number of mallocs -\n * frees for each size category.\n */\n/*  */\nmstats(void)\n{\n  \tregister int i, j;\n  \tregister union overhead *p;\n  \tint totfree = 0,\n  \ttotused = 0;\n\n  \tfprintf(stderr, \"Memory allocation statistics\\nfree:\\t\");\n  \tfor (i = 0; i < NBUCKETS; i++) {\n  \t\tfor (j = 0, p = nextf[i]; p; p = p->ov_next, j++)\n  \t\t\t;\n  \t\tfprintf(stderr, \" %d\", j);\n  \t\ttotfree += j * (1 << (i + 3));\n  \t}\n  \tfprintf(stderr, \"\\nused:\\t\");\n  \tfor (i = 0; i < NBUCKETS; i++) {\n  \t\tfprintf(stderr, \" %d\", nmalloc[i]);\n  \t\ttotused += nmalloc[i] * (1 << (i + 3));\n  \t}\n  \tfprintf(stderr, \"\\n\\tTotal in use: %d, total free: %d, total allocated: %d\\n\",\n\t    totused, totfree, totused + totfree);\n}\n#endif\n\n"
  },
  {
    "path": "engine/platform/misc/lib_static.c",
    "content": "/*\nlib_static.c - static linking support\nCopyright (C) 2018 Flying With Gauss\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include \"library.h\"\n#if XASH_LIB == LIB_STATIC\n#ifdef XASH_NO_LIBDL\n\nvoid *dlsym(void *handle, const char *symbol )\n{\n\tCon_DPrintf( \"%s( %p, \\\"%s\\\" ): stub\\n\", __func__, handle, symbol );\n\treturn NULL;\n}\n\nvoid *dlopen(const char *name, int flag )\n{\n\tCon_DPrintf( \"%s( \\\"%s\\\", %d ): stub\\n\", __func__, name, flag );\n\treturn NULL;\n}\n\nint dlclose(void *handle)\n{\n\tCon_DPrintf( \"%s( %p ): stub\\n\", __func__, handle );\n\treturn 0;\n}\n\nchar *dlerror( void )\n{\n\treturn \"Loading ELF libraries not supported in this build!\\n\";\n}\n\nint dladdr( const void *addr, void *info )\n{\n\treturn 0;\n}\n#endif // XASH_NO_LIBDL\n\n\ntypedef struct table_s\n{\n\tconst char *name;\n\tvoid *pointer;\n} table_t;\n\n\n#include \"generated_library_tables.h\"\n\nstatic void *Lib_Find(table_t *tbl, const char *name )\n{\n\twhile( tbl->name )\n\t{\n\t\tif( !Q_strcmp( tbl->name, name) )\n\t\t\treturn tbl->pointer;\n\t\ttbl++;\n\t}\n\n\treturn 0;\n}\n\nvoid *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath )\n{\n\treturn Lib_Find((table_t*)libs, dllname);\n}\n\nvoid COM_FreeLibrary( void *hInstance )\n{\n\t// impossible\n}\n\nvoid *COM_GetProcAddress( void *hInstance, const char *name )\n{\n\treturn Lib_Find( hInstance, name );\n}\n\nvoid *COM_FunctionFromName( void *hInstance, const char *pName )\n{\n\treturn Lib_Find( hInstance, pName );\n}\n\n\nconst char *COM_NameForFunction( void *hInstance, void *function )\n{\n#ifdef XASH_ALLOW_SAVERESTORE_OFFSETS\n\treturn COM_OffsetNameForFunction( function );\n#else\n\treturn NULL;\n#endif\n}\n#endif\n"
  },
  {
    "path": "engine/platform/misc/sbrk.c",
    "content": "/*\nsbrk.c - swap memory allocation\nCopyright (C) 2019 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#define _GNU_SOURCE\n#include <dlfcn.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <signal.h>\n#define _GNU_SOURCE\n#include <dlfcn.h>\n#include \"platform/swap/swap.h\"\n#include \"string.h\"\n\n#ifndef XASH_DEFAULT_SWAP_PATH\n#define XASH_DEFAULT_SWAP_PATH  \"/tmp/xash3d-swap\"\n#endif\n#define PAGE_SIZE 4096\nstatic struct sbrk_state_s\n{\n\tvoid *top;\n\tint fd;\n\tsize_t size;\n\tsize_t prealloc;\n\tpid_t pid;\n} s;\n\nstatic void SWAP_Initialize(void)\n{\n\tchar *path;\n\tchar *prealloc = getenv(\"SWAP_SIZE\");\n\tint fd;\n\n\tif( s.top )\n\t\treturn;\n\n\tpath = getenv(\"SWAP_PATH\");\n\tif( !path )\n\t\tpath = XASH_DEFAULT_SWAP_PATH;\n\tfd = open( path, O_CREAT|O_RDWR, 0600 );\n\n\tif( prealloc ) s.prealloc = atoi(prealloc);\n\telse s.prealloc = 128*1024*1024;\n\ts.prealloc &= ~(PAGE_SIZE - 1);\n\n\ts.fd = fd;\n\tftruncate( fd, s.prealloc );\n\ts.top = mmap( 0, s.prealloc, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );\n\n\t// space will be freed on exit\n\t//unlink(path);\n}\n\nvoid *SWAP_Sbrk(size_t size)\n{\n\tchar buf[64];\n\tSWAP_Initialize();\n\n\tif( size == 0 )\n\t\treturn s.top;\n\telse if( size > 0 )\n\t{\n\t\tvoid *res;\n\n\t\t//write(1, buf, snprintf(buf, 32, \"allocating %d\\n\", size) );\n\t\tres = s.top;\n\t\ts.size += size;\n\t\ts.top = res + size;\n\t\tif( s.size + size > s.prealloc )\n\t\t\treturn (void*)-1;\n\n\t\tmemset( res, 0, size );\n\t\treturn res;\n\n\t}\n\telse\n\t{\n\t\tvoid *res = s.top;\n\n\t\tif( -size > s.size )\n\t\t\tres = (void*)-1;\n\t\telse\n\t\t{\n\t\t\ts.top += size;\n\t\t\ts.size += size;\n\t\t\t//write(1, buf, snprintf(buf, 32, \"freed %d\\n\", -size) );\n\t\t}\n\n\t\treturn res;\n\t}\n}\n\n\n"
  },
  {
    "path": "engine/platform/misc/swap.h",
    "content": "/*\nsbrk_swap.h - swap memory allocation\nCopyright (C) 2019 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n//#include <stdint.h>\nvoid *SWAP_Sbrk( size_t size );\nvoid *SWAP_Malloc( size_t size );\nvoid *SWAP_Calloc( size_t nelem, size_t size );\nvoid SWAP_Free( void *cp );\nvoid *SWAP_Realloc( void *cp, size_t size );\nsize_t SWAP_MallocUsableSize( void * cp );\n"
  },
  {
    "path": "engine/platform/nswitch/sys_nswitch.c",
    "content": "/*\nswitch.c - switch backend\nCopyright (C) 2021-2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <switch.h>\n#include <solder.h>\n#include <SDL.h>\n\nstatic int nxlink_sock = -1;\n\n/* HACKHACK: force-export stuff required by the dynamic libs */\n\n// this is required by some std::filesystem crap in libstdc++\n// we don't have it defined in our libc\nlong pathconf( const char *path, int name ) { return -1; }\n\n// part of libunwind; required by any dynamic lib that uses C++ exceptions\nextern void *_Unwind_Resume;\nextern void *_Unwind_GetIPInfo;\n\n// these are macros in our libc, so we need to wrap them\nstatic int tolower_fn( int c ) { return tolower( c ); }\nstatic int toupper_fn( int c ) { return toupper( c ); }\nstatic int isalnum_fn( int c ) { return isalnum( c ); }\nstatic int isalpha_fn( int c ) { return isalpha( c ); }\n\nstatic const solder_export_t aux_exports[] =\n{\n\tSOLDER_EXPORT( \"tolower\", tolower_fn ),\n\tSOLDER_EXPORT( \"toupper\", toupper_fn ),\n\tSOLDER_EXPORT( \"isalnum\", isalnum_fn ),\n\tSOLDER_EXPORT( \"isalpha\", isalpha_fn ),\n\tSOLDER_EXPORT_SYMBOL( mkdir ),\n\tSOLDER_EXPORT_SYMBOL( remove ),\n\tSOLDER_EXPORT_SYMBOL( rename ),\n\tSOLDER_EXPORT_SYMBOL( pathconf ),\n\tSOLDER_EXPORT_SYMBOL( fsync ),\n\tSOLDER_EXPORT_SYMBOL( strchrnul ),\n\tSOLDER_EXPORT_SYMBOL( stpcpy ),\n\tSOLDER_EXPORT_SYMBOL( _Unwind_Resume ),\n\tSOLDER_EXPORT_SYMBOL( _Unwind_GetIPInfo ),\n};\n\nconst solder_export_t *__solder_aux_exports = aux_exports;\nconst size_t __solder_num_aux_exports = sizeof( aux_exports ) / sizeof( *aux_exports );\n\n/* end of export crap */\n\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n\tCon_Reportf( S_WARN \"Tried to shell execute ;%s; -- not supported\\n\", path );\n}\n\n#if XASH_MESSAGEBOX == MSGBOX_NSWITCH\nvoid Platform_MessageBox( const char *title, const char *message, qboolean unused )\n{\n\t// TODO: maybe figure out how to show an actual messagebox or an on-screen console\n\t//       without murdering the renderer\n\t// assume this is a fatal error\n\tFILE *f = fopen( \"fatal.log\", \"w\" );\n\tif ( f )\n\t{\n\t\tfprintf( f, \"%s:\\n%s\\n\", title, message );\n\t\tfclose( f );\n\t}\n\t// dump to nxlink as well\n\tfprintf( stderr, \"%s:\\n%s\\n\", title, message );\n}\n#endif // XASH_MESSAGEBOX == MSGBOX_NSWITCH\n\n// this gets executed before main(), do not delete\nvoid userAppInit( void )\n{\n\tsocketInitializeDefault( );\n#ifdef NSWITCH_DEBUG\n\tnxlink_sock = nxlinkStdio( );\n#endif\n\tif ( solder_init( 0 ) < 0 )\n\t{\n\t\tfprintf( stderr, \"solder_init() failed: %s\\n\", solder_dlerror() );\n\t\tfflush( stderr );\n\t\texit( 1 );\n\t}\n}\n\n// this gets executed on exit(), do not delete\nvoid userAppExit( void )\n{\n\tsolder_quit( );\n\tif ( nxlink_sock >= 0 )\n\t{\n\t\tclose( nxlink_sock );\n\t\tnxlink_sock = -1;\n\t}\n\tsocketExit( );\n}\n\nvoid NSwitch_Init( void )\n{\n\tprintf( \"%s\\n\", __func__ );\n}\n\nvoid NSwitch_Shutdown( void )\n{\n\tprintf( \"%s\\n\", __func__ );\n}\n"
  },
  {
    "path": "engine/platform/platform.h",
    "content": "/*\nplatform.h - common platform-dependent function defines\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef PLATFORM_H\n#define PLATFORM_H\n\n#include <errno.h>\n#include \"common.h\"\n#include \"system.h\"\n#include \"defaults.h\"\n#include \"cursor_type.h\"\n#include \"key_modifiers.h\"\n#include \"ref_vulkan.h\"\n\n/*\n==============================================================================\n\n                       SYSTEM UTILS\n\n==============================================================================\n*/\ndouble Platform_DoubleTime( void );\nvoid Platform_Sleep( int msec );\nvoid Platform_ShellExecute( const char *path, const char *parms );\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow );\nvoid Platform_SetStatus( const char *status );\nqboolean Platform_DebuggerPresent( void );\n\n// legacy iOS port functions\n#if TARGET_OS_IOS\nconst char *IOS_GetDocsDir( void );\nvoid IOS_LaunchDialog( void );\n#endif // TARGET_OS_IOS\n\n#if XASH_WIN32 || XASH_LINUX\n#define XASH_PLATFORM_HAVE_STATUS 1\n#else\n#undef XASH_PLATFORM_HAVE_STATUS\n#endif\n\n#if XASH_POSIX\nvoid Posix_Daemonize( void );\nvoid Posix_SetupSigtermHandling( void );\nchar *Posix_Input( void );\n#endif\n\n#if XASH_SDL\nvoid SDLash_Init( const char *basedir );\nvoid SDLash_Shutdown( void );\nvoid SDLash_NanoSleep( int nsec );\n#endif\n\n#if XASH_ANDROID\nconst char *Android_GetAndroidID( void );\nconst char *Android_LoadID( void );\nvoid Android_SaveID( const char *id );\nvoid Android_Init( void );\nvoid *Android_GetNativeObject( const char *name );\nint Android_GetKeyboardHeight( void );\nvoid Android_Shutdown( void );\n#endif\n\n#if XASH_WIN32\nvoid Win32_Init( qboolean con_showalways );\nvoid Win32_Shutdown( void );\nqboolean Win32_NanoSleep( int nsec );\nvoid Wcon_CreateConsole( qboolean con_showalways );\nvoid Wcon_DestroyConsole( void );\nvoid Wcon_InitConsoleCommands( void );\nvoid Wcon_ShowConsole( qboolean show );\nvoid Wcon_DisableInput( void );\nchar *Wcon_Input( void );\nvoid Wcon_WinPrint( const char *pMsg );\n#endif\n\n#if XASH_NSWITCH\nvoid NSwitch_Init( void );\nvoid NSwitch_Shutdown( void );\n#endif\n\n#if XASH_PSVITA\nvoid PSVita_Init( void );\nvoid PSVita_Shutdown( void );\nqboolean PSVita_GetBasePath( char *buf, const size_t buflen );\nint PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv );\nvoid PSVita_InputUpdate( void );\n#endif\n\n#if XASH_DOS\nvoid DOS_Init( void );\nvoid DOS_Shutdown( void );\n#endif\n\n#if XASH_LINUX\nvoid Linux_Init( void );\nvoid Linux_Shutdown( void );\nvoid Linux_SetTimer( float time );\nint Linux_GetProcessID( void );\n#endif\n\nstatic inline void Platform_Init( qboolean con_showalways, const char *basedir )\n{\n#if XASH_POSIX\n\t// daemonize as early as possible, because we need to close our file descriptors\n\tPosix_Daemonize( );\n#endif\n\n#if XASH_SDL\n\tSDLash_Init( basedir );\n#endif\n\n#if XASH_ANDROID\n\tAndroid_Init( );\n#elif XASH_NSWITCH\n\tNSwitch_Init( );\n#elif XASH_PSVITA\n\tPSVita_Init( );\n#elif XASH_DOS\n\tDOS_Init( );\n#elif XASH_WIN32\n\tWin32_Init( con_showalways );\n#elif XASH_LINUX\n\tLinux_Init( );\n#endif\n}\n\nstatic inline void Platform_Shutdown( void )\n{\n#if XASH_NSWITCH\n\tNSwitch_Shutdown( );\n#elif XASH_PSVITA\n\tPSVita_Shutdown( );\n#elif XASH_DOS\n\tDOS_Shutdown( );\n#elif XASH_WIN32\n\tWin32_Shutdown( );\n#elif XASH_LINUX\n\tLinux_Shutdown( );\n#endif\n\n#if XASH_SDL\n\tSDLash_Shutdown( );\n#endif\n}\n\nstatic inline qboolean Sys_DebuggerPresent( void )\n{\n#if XASH_LINUX || XASH_WIN32\n\treturn Platform_DebuggerPresent();\n#else\n\treturn false;\n#endif\n}\n\nstatic inline void Platform_SetupSigtermHandling( void )\n{\n#if XASH_POSIX\n\tPosix_SetupSigtermHandling( );\n#endif\n}\n\nstatic inline qboolean Platform_NanoSleep( int nsec )\n{\n#if XASH_SDL == 3\n\tSDLash_NanoSleep( nsec );\n\treturn true;\n\t// SDL2 doesn't have nanosleep, so use low-level functions here\n#elif XASH_POSIX\n\tstruct timespec ts = {\n\t\t.tv_sec = 0,\n\t\t.tv_nsec = nsec, // just don't put large numbers here\n\t};\n\tint ret = nanosleep( &ts, NULL );\n\tif( ret < 0 )\n\t\treturn errno == EINTR; // ignore EINTR error, it just means sleep was interrupted\n\treturn true;\n#elif XASH_WIN32\n\treturn Win32_NanoSleep( nsec );\n#else\n\treturn false;\n#endif\n}\n\n#if XASH_WIN32 || XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE\nvoid Sys_SetupCrashHandler( const char *argv0 );\nvoid Sys_RestoreCrashHandler( void );\n#else\nstatic inline void Sys_SetupCrashHandler( const char *argv0 )\n{\n}\n\nstatic inline void Sys_RestoreCrashHandler( void )\n{\n}\n#endif\n\n\n/*\n==============================================================================\n\n\t\t\tMOBILE API\n\n==============================================================================\n*/\n#if XASH_SDL >= 2\nvoid Platform_Vibrate( float life, char flags ); // left for compatibility\nvoid Platform_Vibrate2( float time, int low_freq, int high_freq, uint flags );\n#else\nstatic inline void Platform_Vibrate( float life, char flags ) {}\nstatic inline void Platform_Vibrate2( float time, int low_freq, int high_freq, uint flags ) {}\n#endif\n\n/*\n==============================================================================\n\n\t\t\tINPUT\n\n==============================================================================\n*/\n#if XASH_SDL // only SDL based backends implements these functions\nvoid Platform_PreCreateMove( void );\nvoid GAME_EXPORT Platform_GetMousePos( int *x, int *y );\nvoid GAME_EXPORT Platform_SetMousePos( int x, int y );\nqboolean Platform_GetMouseGrab( void );\nvoid Platform_SetMouseGrab( qboolean enable );\nvoid Platform_SetCursorType( VGUI_DefaultCursor type );\nint Platform_GetClipboardText( char *buffer, size_t size );\nvoid Platform_SetClipboardText( const char *buffer );\n#else\nstatic inline void Platform_PreCreateMove( void ) { }\nstatic inline void GAME_EXPORT Platform_SetMousePos( int x, int y ) { }\nstatic inline void Platform_SetMouseGrab( qboolean enable ) { }\nstatic inline void Platform_SetCursorType( VGUI_DefaultCursor type ) { }\nstatic inline int Platform_GetClipboardText( char *buffer, size_t size ) { return 0; }\nstatic inline void Platform_SetClipboardText( const char *buffer ) { }\nstatic inline qboolean Platform_GetMouseGrab( void ) { return false; }\nstatic inline void GAME_EXPORT Platform_GetMousePos( int *x, int *y )\n{\n\tif( x ) *x = 0;\n\tif( y ) *y = 0;\n}\n#endif\n\n#if XASH_SDL || XASH_DOS\nvoid Platform_RunEvents( void );\nvoid Platform_MouseMove( float *x, float *y );\n#else\nstatic inline void Platform_RunEvents( void ) { }\nstatic inline void Platform_MouseMove( float *x, float *y )\n{\n\tif( x ) *x = 0.0f;\n\tif( y ) *y = 0.0f;\n}\n#endif\n\n#if XASH_SDL >= 2 || XASH_PSVITA || XASH_DOS || XASH_USE_EVDEV\nvoid Platform_EnableTextInput( qboolean enable );\n#else\nstatic inline void Platform_EnableTextInput( qboolean enable ) { }\n#endif\n\n#if XASH_SDL >= 2\nint Platform_JoyInit( void ); // returns number of connected gamepads, negative if error\nvoid Platform_JoyShutdown( void );\nvoid Platform_CalibrateGamepadGyro( void );\nkey_modifier_t Platform_GetKeyModifiers( void );\n#else\nstatic inline int Platform_JoyInit( void ) { return 0; }\nstatic inline void Platform_JoyShutdown( void ) { }\nstatic inline void Platform_CalibrateGamepadGyro( void ) { }\nstatic inline key_modifier_t Platform_GetKeyModifiers( void ) { return KeyModifier_None; }\n#endif\n\nstatic inline void Platform_SetTimer( float time )\n{\n#if XASH_LINUX\n\tLinux_SetTimer( time );\n#endif\n}\n\nstatic inline char *Platform_Input( void )\n{\n#if XASH_WIN32\n\treturn Wcon_Input();\n#elif XASH_POSIX && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY\n\treturn Posix_Input();\n#else\n\treturn NULL;\n#endif\n}\n\n/*\n==============================================================================\n\n\t\t\tWINDOW MANAGEMENT\n\n==============================================================================\n*/\ntypedef enum\n{\n\trserr_ok,\n\trserr_invalid_fullscreen,\n\trserr_invalid_mode,\n\trserr_unknown\n} rserr_t;\n\nstruct vidmode_s;\ntypedef enum window_mode_e window_mode_t;\ntypedef enum ref_window_type_e ref_window_type_t;\n\n// Window\nqboolean  R_Init_Video( const int type );\nvoid      R_Free_Video( void );\nqboolean  VID_SetMode( void );\nrserr_t   R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode );\nint       R_MaxVideoModes( void );\nstruct vidmode_s *R_GetVideoMode( int num );\nvoid*     GL_GetProcAddress( const char *name ); // RenderAPI requirement\nvoid      GL_UpdateSwapInterval( void );\nint GL_SetAttribute( int attr, int val );\nint GL_GetAttribute( int attr, int *val );\nvoid GL_SwapBuffers( void );\nvoid *SW_LockBuffer( void );\nvoid SW_UnlockBuffer( void );\nqboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b );\nvoid Platform_Minimize_f( void );\nref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type );\n\n//\n// in_evdev.c\n//\n#if XASH_USE_EVDEV\nvoid Evdev_SetGrab( qboolean grab );\nvoid Evdev_Shutdown( void );\nvoid Evdev_Init( void );\nvoid IN_EvdevMove( float *yaw, float *pitch );\nvoid IN_EvdevFrame ( void );\n#endif // XASH_USE_EVDEV\n/*\n==============================================================================\n\n\t\t\tAUDIO INPUT/OUTPUT\n\n==============================================================================\n*/\n// initializes cycling through a DMA buffer and returns information on it\nqboolean SNDDMA_Init( void );\nvoid SNDDMA_Shutdown( void );\nvoid SNDDMA_BeginPainting( void );\nvoid SNDDMA_Submit( void );\nvoid SNDDMA_Activate( qboolean active ); // pause audio\n// void SNDDMA_PrintDeviceName( void ); // unused\n// void SNDDMA_LockSound( void ); // unused\n// void SNDDMA_UnlockSound( void ); // unused\n\nqboolean VoiceCapture_Init( void );\nvoid VoiceCapture_Shutdown( void );\nqboolean VoiceCapture_Activate( qboolean activate );\nqboolean VoiceCapture_Lock( qboolean lock );\n\n// this allows to make break in current line, without entering libc code\n// libc built with -fomit-frame-pointer may just eat stack frame (hello, glibc), making entering libc even more useless\n// calling syscalls directly allows to make break like if it was asm(\"int $3\") on x86\n#if XASH_LINUX && XASH_X86\n\t#define INLINE_RAISE(x) asm volatile( \"int $3;\" );\n\t#define INLINE_NANOSLEEP1() // nothing!\n#elif XASH_LINUX && XASH_ARM && !XASH_64BIT\n\t#include <sys/syscall.h>\n\t#include <sys/types.h>\n\t#define INLINE_RAISE(x) do \\\n\t\t{ \\\n\t\t\tint raise_pid = getpid(); \\\n\t\t\tpid_t raise_tid = Linux_GetProcessID(); \\\n\t\t\tint raise_sig = (x); \\\n\t\t\t__asm__ volatile (  \\\n\t\t\t\t\"mov r7,#268\\n\\t\" \\\n\t\t\t\t\"mov r0,%0\\n\\t\" \\\n\t\t\t\t\"mov r1,%1\\n\\t\" \\\n\t\t\t\t\"mov r2,%2\\n\\t\" \\\n\t\t\t\t\"svc 0\\n\\t\" \\\n\t\t\t\t: \\\n\t\t\t\t: \"r\"(raise_pid), \"r\"(raise_tid), \"r\"(raise_sig) \\\n\t\t\t\t: \"r0\", \"r1\", \"r2\", \"r7\", \"memory\" \\\n\t\t\t); \\\n\t\t} while( 0 )\n\t#define INLINE_NANOSLEEP1() do \\\n\t\t{ \\\n\t\t\tstruct timespec ns_t1 = {1, 0}; \\\n\t\t\tstruct timespec ns_t2 = {0, 0}; \\\n\t\t\t__asm__ volatile ( \\\n\t\t\t\t\"mov r7,#162\\n\\t\" \\\n\t\t\t\t\"mov r0,%0\\n\\t\" \\\n\t\t\t\t\"mov r1,%1\\n\\t\" \\\n\t\t\t\t\"svc 0\\n\\t\" \\\n\t\t\t\t: \\\n\t\t\t\t: \"r\"(&ns_t1), \"r\"(&ns_t2) \\\n\t\t\t\t: \"r0\", \"r1\", \"r7\", \"memory\" \\\n\t\t\t); \\\n\t\t} while( 0 )\n#elif XASH_LINUX && XASH_ARM && XASH_64BIT\n\t#include <sys/syscall.h>\n\t#include <sys/types.h>\n\t#define INLINE_RAISE(x) do \\\n\t\t{ \\\n\t\t\tint raise_pid = getpid(); \\\n\t\t\tpid_t raise_tid = Linux_GetProcessID(); \\\n\t\t\tint raise_sig = (x); \\\n\t\t\t__asm__ volatile ( \\\n\t\t\t\t\"mov x8,#131\\n\\t\" \\\n\t\t\t\t\"mov x0,%0\\n\\t\" \\\n\t\t\t\t\"mov x1,%1\\n\\t\" \\\n\t\t\t\t\"mov x2,%2\\n\\t\" \\\n\t\t\t\t\"svc 0\\n\\t\" \\\n\t\t\t\t: \\\n\t\t\t\t: \"r\"(raise_pid), \"r\"(raise_tid), \"r\"(raise_sig) \\\n\t\t\t\t: \"x0\", \"x1\", \"x2\", \"x8\", \"memory\", \"cc\" \\\n\t\t\t); \\\n\t\t} while( 0 )\n\t#define INLINE_NANOSLEEP1() do \\\n\t\t{ \\\n\t\t\tstruct timespec ns_t1 = {1, 0}; \\\n\t\t\tstruct timespec ns_t2 = {0, 0}; \\\n\t\t\t__asm__ volatile ( \\\n\t\t\t\t\"mov x8,#101\\n\\t\" \\\n\t\t\t\t\"mov x0,%0\\n\\t\" \\\n\t\t\t\t\"mov x1,%1\\n\\t\" \\\n\t\t\t\t\"svc 0\\n\\t\" \\\n\t\t\t\t: \\\n\t\t\t\t: \"r\"(&ns_t1), \"r\"(&ns_t2) \\\n\t\t\t\t: \"x0\", \"x1\", \"x8\", \"memory\", \"cc\" \\\n\t\t\t); \\\n\t\t} while( 0 )\n#elif XASH_LINUX\n\t#if defined( __NR_tgkill )\n\t\t#define INLINE_RAISE(x) syscall( __NR_tgkill, getpid(), Linux_GetProcessID(), x )\n\t#else // __NR_tgkill\n\t\t#define INLINE_RAISE(x) raise(x)\n\t#endif // __NR_tgkill\n\t#define INLINE_NANOSLEEP1() do \\\n\t\t{ \\\n\t\t\tstruct timespec ns_t1 = {1, 0}; \\\n\t\t\tstruct timespec ns_t2 = {0, 0}; \\\n\t\t\tnanosleep( &ns_t1, &ns_t2 ); \\\n\t\t} while( 0 )\n#else // generic\n\t#define INLINE_RAISE(x) raise(x)\n\t#define INLINE_NANOSLEEP1() sleep(1)\n#endif // generic\n\n#endif // PLATFORM_H\n"
  },
  {
    "path": "engine/platform/posix/con_posix.c",
    "content": "/*\ncon_posix.c - reading from stdin\nCopyright (C) 2024 Flying With Gauss\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n\n#if !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY\n// use with caution, running engine in Qt Creator may cause a freeze in read() call\n// I have never encountered this bug anywhere else, so still enable by default\n#include <sys/select.h>\n\nchar *Posix_Input( void )\n{\n\tfd_set rfds;\n\tstatic char line[1024];\n\tstatic int len;\n\tstruct timeval tv = { 0 };\n\n\tif( !Host_IsDedicated( ))\n\t\treturn NULL;\n\n\tFD_ZERO( &rfds );\n\tFD_SET( 0, &rfds); // stdin\n\twhile( select( 1, &rfds, NULL, NULL, &tv ) > 0 )\n\t{\n\t\tif( read( 0, &line[len], 1 ) != 1 )\n\t\t\tbreak;\n\t\tif( line[len] == '\\n' || len > ( sizeof( line ) - 2 ))\n\t\t{\n\t\t\tline[ ++len ] = 0;\n\t\t\tlen = 0;\n\t\t\treturn line;\n\t\t}\n\t\tlen++;\n\t\ttv.tv_sec = 0;\n\t\ttv.tv_usec = 0;\n\t}\n\n\treturn NULL;\n}\n#endif // !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY\n"
  },
  {
    "path": "engine/platform/posix/crash.h",
    "content": "/*\ncrash.h - advanced crashhandler\nCopyright (C) 2016 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n//\n// crash_libbacktrace.c\n//\nint Sys_CrashDetailsLibbacktrace( int logfd, char *message, int len, size_t max_len );\nqboolean Sys_SetupLibbacktrace( const char *argv0 );\n\n//\n// crash_glibc.c\n//\nint Sys_CrashDetailsExecinfo( int logfd, char *message, int len, size_t max_len );\n"
  },
  {
    "path": "engine/platform/posix/crash_glibc.c",
    "content": "/*\ncrash_glibc.c - advanced crashhandler based on glibc's execinfo API\nCopyright (C) 2016 Mittorn\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n// on Glibc (which potentially might not be only Linux) systems we\n// have backtrace() and backtrace_symbols() calls, which replace for us\n// platform-specific code\n#if HAVE_EXECINFO\n#include <execinfo.h>\n#include <signal.h>\n#include \"common.h\"\n#include \"input.h\"\n#include \"crash.h\"\n\nint Sys_CrashDetailsExecinfo( int logfd, char *message, int len, size_t max_len )\n{\n\tvoid *addrs[16];\n\tint size = backtrace( addrs, sizeof( addrs ) / sizeof( addrs[0] ));\n\tchar **syms = backtrace_symbols( addrs, size );\n\n\tfor( int i = 0; i < size && syms; i++ )\n\t{\n\t\tsize_t symlen = Q_strlen( syms[i] );\n\t\tchar ch = '\\n';\n\n\t\twrite( logfd, syms[i], symlen );\n\t\twrite( logfd, &ch, 1 );\n\n\t\twrite( STDERR_FILENO, syms[i], symlen );\n\t\twrite( STDERR_FILENO, &ch, 1 );\n\n\t\tlen += Q_snprintf( message + len, max_len - len, \"%2d: %s\\n\", i, syms[i] );\n\t}\n\n\treturn len;\n}\n#endif // HAVE_EXECINFO\n"
  },
  {
    "path": "engine/platform/posix/crash_libbacktrace.c",
    "content": "/*\ncrash_libbacktrace.c - advanced crashhandler based on libbacktrace\nCopyright (C) 2016 Mittorn\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#if HAVE_LIBBACKTRACE\n#include <signal.h>\n#include \"common.h\"\n#include \"backtrace.h\"\n#include \"input.h\"\n#include \"crash.h\"\n\nstatic struct backtrace_state *g_bt_state;\nstatic qboolean enable_libbacktrace;\n\nstatic void Sys_BacktraceError( void *data, const char *msg, int errnum )\n{\n\tif( errnum < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"no symbol info, no libbacktrace\\n\" );\n\t\treturn;\n\t}\n\n\tCon_Printf( S_ERROR \"libbacktrace error: %s (%d)\\n\", msg, errnum );\n\n\tenable_libbacktrace = false;\n}\n\nstruct print_data\n{\n\tchar *message;\n\tsize_t message_size;\n\tint len;\n\tint idx;\n\tint logfd;\n};\n\nstatic void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... )\n{\n\tva_list va;\n\tint len;\n\n\tva_start( va, fmt );\n\tlen = Q_vsnprintf( pd->message, pd->message_size, fmt, va );\n\tva_end( va );\n\n\tif( len > 0 )\n\t{\n\t\tchar ch = '\\n';\n\n\t\twrite( pd->logfd, pd->message, len );\n\t\twrite( pd->logfd, &ch, 1 );\n\n\t\twrite( STDERR_FILENO, pd->message, len );\n\t\twrite( STDERR_FILENO, &ch, 1 );\n\n\t\tpd->message += len;\n\t\tpd->len += len;\n\t\tpd->message_size -= len;\n\t}\n}\n\nstatic void Sys_BacktracePrintError( void *data, const char *msg, int errnum )\n{\n\tstruct print_data *pd = data;\n\tSys_AppendPrint( pd, \"%2d: error: %s (%d)\\n\", pd->idx++, msg, errnum );\n}\n\nstatic void Sys_BacktracePrintSyminfo( void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize )\n{\n\tstruct print_data *pd = data;\n\tDl_info dlinfo = { 0 };\n\tconst char *module_name;\n\n\tif( dladdr((void *)pc, &dlinfo ))\n\t\tmodule_name = dlinfo.dli_fname;\n\telse module_name = NULL;\n\n\tif( symname )\n\t{\n\t\tif( module_name )\n\t\t\tSys_AppendPrint( pd, \"%2d: <%s+%d> (%s)\\n\", pd->idx++, symname, pc - symval, module_name );\n\t\telse\n\t\t\tSys_AppendPrint( pd, \"%2d: <%s+%d>\\n\", pd->idx++, symname, pc - symval );\n\t}\n\telse\n\t{\n\t\tif( module_name )\n\t\t\tSys_AppendPrint( pd, \"%2d: %p (%s)\\n\", pd->idx++, pc, module_name );\n\t\telse\n\t\t\tSys_AppendPrint( pd, \"%2d: %p\\n\", pd->idx++, pc );\n\t}\n}\n\nstatic int Sys_BacktracePrintFull( void *data, uintptr_t pc, const char *filename, int lineno, const char *function )\n{\n\tstruct print_data *pd = data;\n\tDl_info dlinfo = { 0 };\n\tconst char *module_name;\n\n\tif( dladdr((void *)pc, &dlinfo ))\n\t\tmodule_name = dlinfo.dli_fname;\n\telse module_name = NULL;\n\n\tif( filename && lineno >= 0 && function )\n\t{\n\t\tfilename = COM_FileWithoutPath( filename );\n\n\t\tif( module_name )\n\t\t\tSys_AppendPrint( pd, \"%2d: %s (%s:%d) (%s)\\n\", pd->idx++, function, filename, lineno, module_name );\n\t\telse\n\t\t\tSys_AppendPrint( pd, \"%2d: %s (%s:%d)\\n\", pd->idx++, function, filename, lineno );\n\t}\n\telse\n\t{\n\t\tbacktrace_syminfo( g_bt_state, pc, Sys_BacktracePrintSyminfo, Sys_BacktraceError, data );\n\t}\n\n\treturn 0;\n}\n\nint Sys_CrashDetailsLibbacktrace( int logfd, char *message, int len, size_t max_len )\n{\n\tstruct print_data pd =\n\t{\n\t\t.message = message + len,\n\t\t.message_size = sizeof( message ) - len,\n\t\t.logfd = logfd,\n\t\t.len = len,\n\t};\n\n\tbacktrace_full( g_bt_state, 1, Sys_BacktracePrintFull, Sys_BacktracePrintError, &pd );\n\n\treturn pd.len;\n}\n\nqboolean Sys_SetupLibbacktrace( const char *argv0 )\n{\n\tenable_libbacktrace = true;\n\tg_bt_state = backtrace_create_state( argv0, true, Sys_BacktraceError, NULL );\n\treturn g_bt_state != NULL && enable_libbacktrace;\n}\n\n#endif // HAVE_EXECINFO\n"
  },
  {
    "path": "engine/platform/posix/crash_posix.c",
    "content": "/*\ncrash_posix.c - advanced crashhandler\nCopyright (C) 2016 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n\n// while this is mostly POSIX-compatible functions\n// the contents of ucontext_t is platform-dependent\n// before adding new OS here, check platform.h\n#define _XOPEN_SOURCE 1 // required for ucontext\n#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE\n#ifndef XASH_OPENBSD\n\t#include <ucontext.h>\n#endif\n#include <signal.h>\n#include <sys/mman.h>\n#include \"library.h\"\n#include \"input.h\"\n#include \"crash.h\"\n\nstatic qboolean have_libbacktrace = false;\n\nstatic struct sigaction oldFilter;\n\nstatic void Sys_Crash( int signal, siginfo_t *si, void *context )\n{\n\tchar message[8192];\n\tint len, logfd, i = 0;\n\tqboolean detailed_message = false;\n\n\t// flush buffers before writing directly to descriptors\n\tfflush( stdout );\n\tfflush( stderr );\n\n\t// safe actions first, stack and memory may be corrupted\n\tlen = Q_snprintf( message, sizeof( message ), \"Ver: \" XASH_ENGINE_NAME \" \" XASH_VERSION \" (build %i-%s-%s, %s-%s)\\n\",\n\t\t\t\t\t  Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch() );\n\n#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD && !XASH_APPLE\n\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"Crash: signal %d errno %d with code %d at %p %p\\n\", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr );\n#else\n\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"Crash: signal %d errno %d with code %d at %p\\n\", signal, si->si_errno, si->si_code, si->si_addr );\n#endif\n\n\twrite( STDERR_FILENO, message, len );\n\n\t// now get log fd and write trace directly to log\n\tlogfd = Sys_LogFileNo();\n\twrite( logfd, message, len );\n\n#if HAVE_LIBBACKTRACE\n\tif( have_libbacktrace && !detailed_message )\n\t{\n\t\tlen = Sys_CrashDetailsLibbacktrace( logfd, message, len, sizeof( message ));\n\t\tdetailed_message = true;\n\t}\n#endif // HAVE_LIBBACKTRACE\n\n#if HAVE_EXECINFO\n\tif( !detailed_message )\n\t{\n\t\tlen = Sys_CrashDetailsExecinfo( logfd, message, len, sizeof( message ));\n\t\tdetailed_message = true;\n\t}\n#endif // HAVE_EXECINFO\n\n\t// put MessageBox as Sys_Error\n\tMsg( \"%s\\n\", message );\n#if !XASH_DEDICATED\n\tIN_SetMouseGrab( false );\n#endif\n\thost.status = HOST_CRASHED;\n\tPlatform_MessageBox( \"Xash Error\", message, false );\n\n\t// log saved, now we can try to save configs and close log correctly, it may crash\n\tif( host.type == HOST_NORMAL )\n\t\tCL_Crashed();\n\n\tSys_Quit( \"crashed\" );\n}\n\nvoid Sys_SetupCrashHandler( const char *argv0 )\n{\n\tstruct sigaction act =\n\t{\n\t\t.sa_sigaction = Sys_Crash,\n\t\t.sa_flags = SA_SIGINFO | SA_ONSTACK,\n\t};\n\n#if HAVE_LIBBACKTRACE\n\thave_libbacktrace = Sys_SetupLibbacktrace( argv0 );\n#endif // HAVE_LIBBACKTRACE\n\n\tsigaction( SIGSEGV, &act, &oldFilter );\n\tsigaction( SIGABRT, &act, &oldFilter );\n\tsigaction( SIGBUS,  &act, &oldFilter );\n\tsigaction( SIGILL,  &act, &oldFilter );\n\n}\n\nvoid Sys_RestoreCrashHandler( void )\n{\n\tsigaction( SIGSEGV, &oldFilter, NULL );\n\tsigaction( SIGABRT, &oldFilter, NULL );\n\tsigaction( SIGBUS,  &oldFilter, NULL );\n\tsigaction( SIGILL,  &oldFilter, NULL );\n}\n\n#endif // XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX\n"
  },
  {
    "path": "engine/platform/posix/lib_posix.c",
    "content": "/*\nlib_posix.c - dynamic library code for POSIX systems\nCopyright (C) 2018 Flying With Gauss\n\nThis program is free software: you can redistribute it and/sor modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n#include \"platform/platform.h\"\n#if XASH_LIB == LIB_POSIX\n#ifdef XASH_IRIX\n#include \"platform/irix/dladdr.h\"\n#endif\n#include \"common.h\"\n#include \"library.h\"\n#include \"filesystem.h\"\n#include \"server.h\"\n#include \"platform/android/lib_android.h\"\n#include \"platform/apple/lib_ios.h\"\n\n#ifdef XASH_DLL_LOADER // wine-based dll loader\nvoid * Loader_LoadLibrary (const char *name);\nvoid * Loader_GetProcAddress (void *hndl, const char *name);\nvoid Loader_FreeLibrary(void *hndl);\nvoid *Loader_GetDllHandle( void *hndl );\nconst char * Loader_GetFuncName( void *hndl, void *func);\nconst char * Loader_GetFuncName_int( void *wm , void *func);\n#endif\n\n\n#ifdef XASH_NO_LIBDL\n#ifndef XASH_DLL_LOADER\n#error Enable at least one dll backend!!!\n#endif // XASH_DLL_LOADER\n\nvoid *dlsym(void *handle, const char *symbol )\n{\n\tCon_DPrintf( \"%s( %p, \\\"%s\\\" ): stub\\n\", __func__, handle, symbol );\n\treturn NULL;\n}\n\nvoid *dlopen(const char *name, int flag )\n{\n\tCon_DPrintf( \"%s( \\\"%s\\\", %d ): stub\\n\", __func__, name, flag );\n\treturn NULL;\n}\n\nint dlclose(void *handle)\n{\n\tCon_DPrintf( \"%s( %p ): stub\\n\", __func__, handle );\n\treturn 0;\n}\n\nchar *dlerror( void )\n{\n\treturn \"Loading ELF libraries not supported in this build!\\n\";\n}\n\nint dladdr( const void *addr, Dl_info *info )\n{\n\treturn 0;\n}\n#endif // XASH_NO_LIBDL\n\nqboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath )\n{\n\t// TODO: implement\n\treturn true;\n}\n\nvoid *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath )\n{\n\tdll_user_t *hInst = NULL;\n\tvoid *pHandle = NULL;\n\tchar buf[MAX_VA_STRING];\n\n\tCOM_ResetLibraryError();\n\n\t// platforms where gameinfo mechanism is impossible\n#ifdef Platform_POSIX_LoadLibrary\n\treturn Platform_POSIX_LoadLibrary( dllname );\n#endif\n\n\t// platforms where gameinfo mechanism is working goes here\n\t// and use FS_FindLibrary\n\thInst = FS_FindLibrary( dllname, directpath );\n\tif( !hInst )\n\t{\n\t\t// HACKHACK: direct load dll\n#ifdef XASH_DLL_LOADER\n\t\tif( host.enabledll && ( pHandle = Loader_LoadLibrary(dllname)) )\n\t\t{\n\t\t\treturn pHandle;\n\t\t}\n#endif\n\n\t\t// try to find by linker(LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, LD_32_LIBRARY_PATH and so on...)\n\t\tif( !pHandle )\n\t\t{\n\t\t\tpHandle = dlopen( dllname, RTLD_NOW );\n\t\t\tif( pHandle )\n\t\t\t\treturn pHandle;\n\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"Failed to find library %s\", dllname );\n\t\t\tCOM_PushLibraryError( buf );\n\t\t\tCOM_PushLibraryError( dlerror() );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif( hInst->custom_loader )\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"Custom library loader is not available. Extract library %s and fix gameinfo.txt!\", hInst->fullPath );\n\t\tCOM_PushLibraryError( buf );\n\t\tMem_Free( hInst );\n\t\treturn NULL;\n\t}\n\n#ifdef XASH_DLL_LOADER\n\tif( host.enabledll && ( !Q_stricmp( COM_FileExtension( hInst->shortPath ), \"dll\" ) ) )\n\t{\n\t\tif( hInst->encrypted )\n\t\t{\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"Library %s is encrypted. Cannot load\", hInst->shortPath );\n\t\t\tCOM_PushLibraryError( buf );\n\t\t\tMem_Free( hInst );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif( !( hInst->hInstance = Loader_LoadLibrary( hInst->fullPath ) ) )\n\t\t{\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"Failed to load DLL with DLL loader: %s\", hInst->shortPath );\n\t\t\tCOM_PushLibraryError( buf );\n\t\t\tMem_Free( hInst );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\telse\n#endif\n\t{\n\t\tif( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_NOW ) ) )\n\t\t{\n\t\t\tCOM_PushLibraryError( dlerror() );\n\t\t\tMem_Free( hInst );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tpHandle = hInst->hInstance;\n\n\tMem_Free( hInst );\n\n\treturn pHandle;\n}\n\nvoid COM_FreeLibrary( void *hInstance )\n{\n#ifdef XASH_DLL_LOADER\n\tvoid *wm;\n\tif( host.enabledll && (wm = Loader_GetDllHandle( hInstance )) )\n\t\treturn Loader_FreeLibrary( hInstance );\n\telse\n#endif\n\t{\n#ifdef Platform_POSIX_FreeLibrary\n\t\tPlatform_POSIX_FreeLibrary( hInstance );\n#else\n\t\tdlclose( hInstance );\n#endif\n\t}\n}\n\nvoid *COM_GetProcAddress( void *hInstance, const char *name )\n{\n#ifdef XASH_DLL_LOADER\n\tvoid *wm;\n\tif( host.enabledll && (wm = Loader_GetDllHandle( hInstance )) )\n\t\treturn Loader_GetProcAddress(hInstance, name);\n\telse\n#endif\n#if Platform_POSIX_GetProcAddress\n\treturn Platform_POSIX_GetProcAddress( hInstance, name );\n#else\n\treturn dlsym( hInstance, name );\n#endif\n}\n\nvoid *COM_FunctionFromName( void *hInstance, const char *pName )\n{\n\treturn COM_GetProcAddress( hInstance, pName );\n}\n\nconst char *COM_NameForFunction( void *hInstance, void *function )\n{\n#ifdef XASH_DLL_LOADER\n\tvoid *wm;\n\tif( host.enabledll && (wm = Loader_GetDllHandle( hInstance )) )\n#error ConvertMangledName\n\t\treturn Loader_GetFuncName_int(wm, function);\n\telse\n#endif\n\t// NOTE: dladdr() is a glibc extension\n\t{\n\t\tDl_info info = {0};\n\t\tint ret = dladdr( (void*)function, &info );\n\t\tif( ret && info.dli_sname )\n\t\t\treturn COM_GetPlatformNeutralName( info.dli_sname );\n\t}\n#ifdef XASH_ALLOW_SAVERESTORE_OFFSETS\n\treturn COM_OffsetNameForFunction( function );\n#else\n\treturn NULL;\n#endif\n}\n\n#endif // _WIN32\n"
  },
  {
    "path": "engine/platform/posix/net.h",
    "content": "/*\nnet.h - WinSock to BSD sockets wrap\nCopyright (C) 2022 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef NET_H\n#define NET_H\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#if !XASH_PSVITA\n#include <sys/ioctl.h>\n#endif\n#if XASH_SUNOS // TODO: figure out if we need this header on other systems\n#include <sys/filio.h>\n#endif\n#include <sys/select.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <errno.h>\n#include <fcntl.h>\n#if XASH_IRIX\n#include <sys/time.h>\n#endif\n\n#define WSAGetLastError()  errno\n#define WSAEINTR           EINTR\n#define WSAEBADF           EBADF\n#define WSAEACCES          EACCES\n#define WSAEFAULT          EFAULT\n#define WSAEINVAL          EINVAL\n#define WSAEMFILE          EMFILE\n#define WSAEWOULDBLOCK     EWOULDBLOCK\n#define WSAEINPROGRESS     EINPROGRESS\n#define WSAEALREADY        EALREADY\n#define WSAENOTSOCK        ENOTSOCK\n#define WSAEDESTADDRREQ    EDESTADDRREQ\n#define WSAEMSGSIZE        EMSGSIZE\n#define WSAEPROTOTYPE      EPROTOTYPE\n#define WSAENOPROTOOPT     ENOPROTOOPT\n#define WSAEPROTONOSUPPORT EPROTONOSUPPORT\n#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT\n#define WSAEOPNOTSUPP      EOPNOTSUPP\n#define WSAEPFNOSUPPORT    EPFNOSUPPORT\n#define WSAEAFNOSUPPORT    EAFNOSUPPORT\n#define WSAEADDRINUSE      EADDRINUSE\n#define WSAEADDRNOTAVAIL   EADDRNOTAVAIL\n#define WSAENETDOWN        ENETDOWN\n#define WSAENETUNREACH     ENETUNREACH\n#define WSAENETRESET       ENETRESET\n#define WSAECONNABORTED    ECONNABORTED\n#define WSAECONNRESET      ECONNRESET\n#define WSAENOBUFS         ENOBUFS\n#define WSAEISCONN         EISCONN\n#define WSAENOTCONN        ENOTCONN\n#define WSAESHUTDOWN       ESHUTDOWN\n#define WSAETOOMANYREFS    ETOOMANYREFS\n#define WSAETIMEDOUT       ETIMEDOUT\n#define WSAECONNREFUSED    ECONNREFUSED\n#define WSAELOOP           ELOOP\n#define WSAENAMETOOLONG    ENAMETOOLONG\n#define WSAEHOSTDOWN       EHOSTDOWN\n\n\n#ifndef XASH_DOS4GW\n#define HAVE_GETADDRINFO\n#define INVALID_SOCKET -1\n#define SOCKET_ERROR -1\n\n#if XASH_EMSCRIPTEN\n/* All socket operations are non-blocking already */\nstatic int ioctl_stub( int d, unsigned long r, ... )\n{\n\treturn 0;\n}\n#define ioctlsocket ioctl_stub\n#elif !XASH_PSVITA // XASH_EMSCRIPTEN\n#define ioctlsocket ioctl\n#endif // XASH_EMSCRIPTEN\n#define closesocket close\n#endif\n#define SOCKET int\ntypedef int WSAsize_t;\n\n#endif // NET_H\n"
  },
  {
    "path": "engine/platform/posix/sys_posix.c",
    "content": "/*\nsys_win.c - posix system utils\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <unistd.h> // fork\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <signal.h>\n#include \"platform/platform.h\"\n#include \"menu_int.h\"\n\nstatic qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size )\n{\n\tchar *envPath;\n\tchar *part;\n\tsize_t length;\n\tsize_t baseNameLength;\n\tsize_t needTrailingSlash;\n\n\tif( !baseName || !baseName[0] )\n\t\treturn false;\n\n\tenvPath = getenv( \"PATH\" );\n\tif( !COM_CheckString( envPath ) )\n\t\treturn false;\n\n\tbaseNameLength = Q_strlen( baseName );\n\twhile( *envPath )\n\t{\n\t\tpart = Q_strchr( envPath, ':' );\n\t\tif( part )\n\t\t\tlength = part - envPath;\n\t\telse\n\t\t\tlength = Q_strlen( envPath );\n\n\t\tif( length > 0 )\n\t\t{\n\t\t\tneedTrailingSlash = ( envPath[length - 1] == '/' ) ? 0 : 1;\n\t\t\tif( length + baseNameLength + needTrailingSlash < size )\n\t\t\t{\n\t\t\t\tstring temp;\n\n\t\t\t\tQ_strncpy( temp, envPath, length + 1 );\n\t\t\t\tQ_snprintf( buf, size, \"%s%s%s\",\n\t\t\t\t\ttemp, needTrailingSlash ? \"/\" : \"\", baseName );\n\n\t\t\t\tif( access( buf, X_OK ) == 0 )\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tenvPath += length;\n\t\tif( *envPath == ':' )\n\t\t\tenvPath++;\n\t}\n\treturn false;\n}\n\n#if !XASH_ANDROID && !XASH_NSWITCH && !XASH_PSVITA\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n\tchar xdgOpen[128];\n\n\tif( !Q_strcmp( path, GENERIC_UPDATE_PAGE ) || !Q_strcmp( path, PLATFORM_UPDATE_PAGE ))\n\t\tpath = DEFAULT_UPDATE_PAGE;\n\n\tif( Sys_FindExecutable( OPEN_COMMAND, xdgOpen, sizeof( xdgOpen ) ) )\n\t{\n\t\tconst char *argv[] = { xdgOpen, path, NULL };\n\t\tpid_t id = fork( );\n\t\tif( id == 0 )\n\t\t{\n\t\t\texecv( xdgOpen, (char **)argv );\n\t\t\tfprintf( stderr, \"error opening %s %s\", xdgOpen, path );\n\t\t\t_exit( 1 );\n\t\t}\n\t}\n\telse\n\t{\n\t\tCon_Reportf( S_WARN \"Could not find \"OPEN_COMMAND\" utility\\n\" );\n\t}\n}\n#endif // XASH_ANDROID\n\nvoid Posix_Daemonize( void )\n{\n\tif( Sys_CheckParm( \"-daemonize\" ))\n\t{\n#if XASH_POSIX && defined(_POSIX_VERSION) && !defined(XASH_MOBILE_PLATFORM)\n\t\tpid_t daemon;\n\n\t\tdaemon = fork();\n\n\t\tif( daemon < 0 )\n\t\t{\n\t\t\tHost_Error( \"fork() failed: %s\\n\", strerror( errno ) );\n\t\t}\n\n\t\tif( daemon > 0 )\n\t\t{\n\t\t\t// parent\n\t\t\tCon_Reportf( \"Child pid: %i\\n\", daemon );\n\t\t\texit( 0 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// don't be closed by parent\n\t\t\tif( setsid() < 0 )\n\t\t\t{\n\t\t\t\tHost_Error( \"setsid() failed: %s\\n\", strerror( errno ) );\n\t\t\t}\n\n\t\t\t// set permissions\n\t\t\tumask( 0 );\n\n\t\t\t// engine will still use stdin/stdout,\n\t\t\t// so just redirect them to /dev/null\n\t\t\tclose( STDIN_FILENO );\n\t\t\tclose( STDOUT_FILENO );\n\t\t\tclose( STDERR_FILENO );\n\t\t\topen(\"/dev/null\", O_RDONLY); // becomes stdin\n\t\t\topen(\"/dev/null\", O_RDWR); // stdout\n\t\t\topen(\"/dev/null\", O_RDWR); // stderr\n\n\t\t\t// fallthrough\n\t\t}\n#elif defined(XASH_MOBILE_PLATFORM)\n\t\tSys_Error( \"Can't run in background on mobile platforms!\" );\n#else\n\t\tSys_Error( \"Daemonize not supported on this platform!\" );\n#endif\n\t}\n\n}\n\nstatic void Posix_SigtermCallback( int signal )\n{\n\tstring reason;\n\tQ_snprintf( reason, sizeof( reason ), \"caught signal %d\", signal );\n\tSys_Quit( reason );\n}\n\nvoid Posix_SetupSigtermHandling( void )\n{\n#if !XASH_PSVITA\n\tstruct sigaction act = { 0 };\n\tact.sa_handler = Posix_SigtermCallback;\n\tact.sa_flags = 0;\n\tsigaction( SIGTERM, &act, NULL );\n#endif\n}\n\n#if XASH_TIMER == TIMER_POSIX\ndouble Platform_DoubleTime( void )\n{\n\tstruct timespec ts;\n#if XASH_IRIX\n\tclock_gettime( CLOCK_SGI_CYCLE, &ts );\n#else\n\tclock_gettime( CLOCK_MONOTONIC, &ts );\n#endif\n\treturn (double) ts.tv_sec + (double) ts.tv_nsec/1000000000.0;\n}\n\nvoid Platform_Sleep( int msec )\n{\n\tusleep( msec * 1000 );\n}\n#endif // XASH_TIMER == TIMER_POSIX\n\n"
  },
  {
    "path": "engine/platform/psvita/in_psvita.c",
    "content": "/*\nin_psvita.h - psvita-specific input code\nCopyright (C) 2021-2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n/* \nthe SDL fork that we use sometimes fails to call sceImeUpdate,\nwhich leads to input being consumed forever without ever showing the keyboard,\nso we just reimplement the whole thing here using SceCommonDialog\n*/\n\n#include \"platform/platform.h\"\n#include <vitasdk.h>\n#include <SDL.h>\n\nextern int SDL_SendKeyboardText( const char *text );\n\nstatic qboolean ime_enabled;\nstatic SceWChar16 ime_string[SCE_IME_MAX_TEXT_LENGTH + 1];\n\n/* this is dumb but will probably work fine enough */\nstatic inline void utf2ascii( char *dst, const SceWChar16 *src, unsigned dstsize )\n{\n\tif ( !src || !dst || !dstsize )\n\t\treturn;\n\twhile ( *src && ( dstsize-- > 0 ) )\n\t\t*(dst++) = (*(src++)) & 0xFF;\n\t*dst = 0x00;\n}\n\nstatic void IME_Open( void )\n{\n\tSceInt32 res;\n\tSceImeDialogParam param;\n\n\tmemset( ime_string, 0, sizeof( ime_string ) );\n\n\tsceImeDialogParamInit( &param );\n\tparam.supportedLanguages = SCE_IME_LANGUAGE_ENGLISH;\n\tparam.languagesForced = SCE_TRUE;\n\tparam.type = SCE_IME_TYPE_BASIC_LATIN;\n\tparam.title = u\"Input text\";\n\tparam.maxTextLength = SCE_IME_MAX_TEXT_LENGTH;\n\tparam.initialText = (SceWChar16 *)u\"\";\n\tparam.inputTextBuffer = ime_string;\n\n\tres = sceImeDialogInit( &param );\n\tif ( res < 0 )\n\t{\n\t\tCon_Reportf( S_WARN \"Could not open IME keyboard: %d\\n\", res );\n\t}\n\telse\n\t{\n\t\time_enabled = true;\n\t}\n}\n\nstatic void IME_Close( void )\n{\n\tif ( ime_enabled )\n\t{\n\t\time_enabled = false;\n\t\tsceImeDialogTerm( );\n\t}\n}\n\nstatic void IME_Update( void )\n{\n\tchar ascii_string[SCE_IME_MAX_TEXT_LENGTH + 2] = { 0 };\n\tSceImeDialogResult result;\n\tSceCommonDialogStatus status = sceImeDialogGetStatus( );\n\tif( status == 2 )\n\t{\n\t\tmemset( &result, 0, sizeof( SceImeDialogResult ) );\n\t\tsceImeDialogGetResult( &result );\n\t\tif( result.button == SCE_IME_DIALOG_BUTTON_ENTER )\n\t\t{\n\t\t\tutf2ascii( ascii_string, ime_string, SCE_IME_MAX_TEXT_LENGTH );\n\t\t\tSDL_SendKeyboardText( ascii_string );\n\t\t}\n\t\tIME_Close();\n\t}\n}\n\nvoid Platform_EnableTextInput( qboolean enable )\n{\n\tif ( enable )\n\t{\n\t\tif ( ime_enabled )\n\t\t\tIME_Close();\n\t\tIME_Open();\n\t\tSDL_EventState( SDL_TEXTINPUT, SDL_ENABLE );\n\t}\n\telse\n\t{\n\t\tSDL_EventState( SDL_TEXTINPUT, SDL_DISABLE );\n\t\tIME_Close();\n\t}\n}\n\nvoid PSVita_InputUpdate( void )\n{\n\tif ( ime_enabled )\n\t\tIME_Update();\n}\n"
  },
  {
    "path": "engine/platform/psvita/net_psvita.h",
    "content": "/*\nnet_psvita.h - psvita network stubs\nCopyright (C) 2021-2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef NET_PSVITA_H\n#define NET_PSVITA_H\n\n#include <vitasdk.h>\n#include \"platform/posix/net.h\"\n\n/* we're missing IPv6 support; define some trash */\n#define XASH_NO_IPV6_RESOLVE 1\n\n#ifndef IN6_IS_ADDR_V4MAPPED\n#define IN6_IS_ADDR_V4MAPPED( p ) ( 0 )\n#endif\n\n#ifndef IPPROTO_IPV6\n#define IPPROTO_IPV6 41\n#endif\n\n#ifndef IPV6_MULTICAST_LOOP\n#define IPV6_MULTICAST_LOOP 19\n#endif\n\n#ifndef IPV6_V6ONLY\n#define IPV6_V6ONLY 26\n#endif\n\n#ifndef FIONBIO\n#define FIONBIO SO_NONBLOCK\n#endif\n\n/* ioctlsocket() is only used to set non-blocking on sockets */\n\nstatic inline int ioctl_psvita( int fd, int req, unsigned int *arg )\n{\n\tif ( req == FIONBIO )\n\t{\n\t\treturn setsockopt( fd, SOL_SOCKET, SO_NONBLOCK, arg, sizeof( *arg ) );\n\t}\n\treturn -ENOSYS;\n}\n\n#define ioctlsocket ioctl_psvita\n\n#endif // NET_PSVITA_H\n"
  },
  {
    "path": "engine/platform/psvita/sce_sys/livearea/contents/template.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<livearea style=\"psmobile\" format-ver=\"01.03\" content-rev=\"1\">\n\t<livearea-background>\n\t\t<image>bg.png</image>\n\t</livearea-background>\n\n\t<gate>\n\t\t<startup-image>startup.png</startup-image>\n\t</gate>\n\n\t<frame id=\"frame3\">\n\t\t<liveitem>\n\t\t\t<target>psla:dev</target>\n\t\t\t<text>\n\t\t\t\t<str>Developer mode</str>\n\t\t\t</text>\n\t\t</liveitem>\n\t</frame>\n</livearea>\n"
  },
  {
    "path": "engine/platform/psvita/sys_psvita.c",
    "content": "/*\nsys_psvita.c - psvita backend\nCopyright (C) 2021-2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include \"xash3d_mathlib.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n#include <ctype.h>\n#include <vitasdk.h>\n#include <vitaGL.h>\n#include <vrtld.h>\n#include <sys/reent.h>\n\n#define DATA_PATH \"data/xash3d\"\n#define MAX_ARGV 5 // \"\" -log -dev X NULL\n\n// 200MB libc heap, 512K main thread stack, 40MB for loading game DLLs\n// the rest goes to vitaGL\nSceUInt32 sceUserMainThreadStackSize = 512 * 1024;\nunsigned int _pthread_stack_default_user = 512 * 1024;\nunsigned int _newlib_heap_size_user = 200 * 1024 * 1024;\n#define VGL_MEM_THRESHOLD ( 40 * 1024 * 1024 )\n\n// HACKHACK: create some slack at the end of the RX segment of the ELF\n// for vita-elf-create to put the generated symbol table into\nconst char vitaelf_slack __attribute__ ((aligned (0x20000))) = 0xFF;\n\n/* HACKHACK: force-export stuff required by the dynamic libs */\n\nextern void *__aeabi_idiv;\nextern void *__aeabi_uidiv;\nextern void *__aeabi_idivmod;\nextern void *__aeabi_uidivmod;\nextern void *__aeabi_d2ulz;\nextern void *__aeabi_ul2d;\nextern void *__aeabi_ul2f;\n\nstatic const vrtld_export_t aux_exports[] =\n{\n\tVRTLD_EXPORT_SYMBOL( __aeabi_d2ulz ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_idiv ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_idivmod ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_uidivmod ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_uidiv ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_ul2d ),\n\tVRTLD_EXPORT_SYMBOL( __aeabi_ul2f ),\n\tVRTLD_EXPORT_SYMBOL( _impure_ptr ),\n\tVRTLD_EXPORT_SYMBOL( ctime ),\n\tVRTLD_EXPORT_SYMBOL( vasprintf ),\n\tVRTLD_EXPORT_SYMBOL( vsprintf ),\n\tVRTLD_EXPORT_SYMBOL( vprintf ),\n\tVRTLD_EXPORT_SYMBOL( printf ),\n\tVRTLD_EXPORT_SYMBOL( putchar ),\n\tVRTLD_EXPORT_SYMBOL( puts ),\n\tVRTLD_EXPORT_SYMBOL( tolower ),\n\tVRTLD_EXPORT_SYMBOL( toupper ),\n\tVRTLD_EXPORT_SYMBOL( isalnum ),\n\tVRTLD_EXPORT_SYMBOL( isalpha ),\n\tVRTLD_EXPORT_SYMBOL( strchrnul ),\n\tVRTLD_EXPORT_SYMBOL( strtok ),\n\tVRTLD_EXPORT_SYMBOL( stpcpy ),\n\tVRTLD_EXPORT_SYMBOL( rand ),\n\tVRTLD_EXPORT_SYMBOL( srand ),\n\tVRTLD_EXPORT_SYMBOL( rintf ),\n\tVRTLD_EXPORT_SYMBOL( sceGxmMapMemory ), // needed by vgl_shim\n\tVRTLD_EXPORT( \"dlopen\", vrtld_dlopen ),\n\tVRTLD_EXPORT( \"dlclose\", vrtld_dlclose ),\n\tVRTLD_EXPORT( \"dlsym\", vrtld_dlsym ),\n};\n\nconst vrtld_export_t *__vrtld_exports = aux_exports;\nconst size_t __vrtld_num_exports = sizeof( aux_exports ) / sizeof( *aux_exports );\n\n/* end of export crap */\n\nstatic const char *PSVita_GetLaunchParameter( char *outbuf )\n{\n\tSceAppUtilAppEventParam param;\n\tmemset( &param, 0, sizeof( param ) );\n\tsceAppUtilReceiveAppEvent( &param );\n\tif( param.type == 0x05 )\n\t{\n\t\tsceAppUtilAppEventParseLiveArea( &param, outbuf );\n\t\treturn outbuf;\n\t}\n\treturn NULL;\n}\n\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n\tCon_Reportf( S_WARN \"Tried to shell execute ;%s; -- not supported\\n\", path );\n}\n\n/*\n===========\nPSVita_GetArgv\n\nOn the PS Vita under normal circumstances argv is empty, so we'll construct our own\nbased on which button the user pressed in the LiveArea launcher.\n===========\n*/\nint PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv )\n{\n\tstatic const char *fake_argv[MAX_ARGV] = { \"app0:/eboot.bin\", NULL };\n\tint fake_argc = 1;\n\tchar tmp[2048] = { 0 };\n\tSceAppUtilInitParam initParam = { 0 };\n\tSceAppUtilBootParam bootParam = { 0 };\n\n\t// on the Vita under normal circumstances argv is empty, unless we're launching from Change Game\n\tsceAppUtilInit( &initParam, &bootParam );\n\n\tif( in_argc > 1 )\n\t{\n\t\t// probably coming from Change Game, in which case we just need to keep the old args\n\t\t*out_argv = in_argv;\n\t\treturn in_argc;\n\t}\n\n\t// got empty args, which means that we're probably coming from LiveArea\n\t// construct argv based on which button the user pressed in the LiveArea launcher\n\tif( PSVita_GetLaunchParameter( tmp ))\n\t{\n\t\tif( !Q_strcmp( tmp, \"dev\" ))\n\t\t{\n\t\t\t// user hit the \"Developer Mode\" button, inject \"-log\" and \"-dev\" arguments\n\t\t\tfake_argv[fake_argc++] = \"-log\";\n\t\t\tfake_argv[fake_argc++] = \"-dev\";\n\t\t\tfake_argv[fake_argc++] = \"2\";\n\t\t}\n\t}\n\n\t*out_argv = (char **)fake_argv;\n\treturn fake_argc;\n}\n\nvoid PSVita_Init( void )\n{\n\tchar xashdir[1024] = { 0 };\n\n\t// cd to the base dir immediately for library loading to work\n\tif( PSVita_GetBasePath( xashdir, sizeof( xashdir )))\n\t{\n\t\tchdir( xashdir );\n\t}\n\n\tsceTouchSetSamplingState( SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_STOP );\n\tscePowerSetArmClockFrequency( 444 );\n\tscePowerSetBusClockFrequency( 222 );\n\tscePowerSetGpuClockFrequency( 222 );\n\tscePowerSetGpuXbarClockFrequency( 166 );\n\tsceSysmoduleLoadModule( SCE_SYSMODULE_NET );\n\n\tif( vrtld_init( 0 ) < 0 )\n\t{\n\t\tSys_Error( \"Could not init vrtld:\\n%s\\n\", vrtld_dlerror() );\n\t}\n\n\t// init vitaGL, leaving some memory for DLL mapping\n\t// TODO: we don't need to do this for ref_soft\n\tvglUseVram( GL_TRUE );\n\tvglUseExtraMem( GL_TRUE );\n\tvglInitExtended( 0, 960, 544, VGL_MEM_THRESHOLD, 0 );\n}\n\nvoid PSVita_Shutdown( void )\n{\n\tvrtld_quit( );\n}\n\nqboolean PSVita_GetBasePath( char *buf, const size_t buflen )\n{\n\t// check if a xash3d folder exists on one of these drives\n\t// default to the last one (ux0)\n\tstatic const char *drives[] = { \"uma0\", \"imc0\", \"ux0\" };\n\tSceUID dir;\n\tsize_t i;\n\n\tfor ( i = 0; i < sizeof( drives ) / sizeof( *drives ); ++i )\n\t{\n\t\tQ_snprintf( buf, buflen, \"%s:\" DATA_PATH, drives[i] );\n\t\tdir = sceIoDopen( buf );\n\t\tif ( dir >= 0 )\n\t\t{\n\t\t\tsceIoDclose( dir );\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nint PSVita_GetPSID( char *buf, const size_t buflen )\n{\n\tSceKernelOpenPsId id;\n\tconst int datasize = Q_min( buflen, sizeof( id ));\n\n\tif( sceKernelGetOpenPsId( &id ) < 0 )\n\t{\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tmemcpy( buf, &id.id[0], datasize );\n\t\treturn datasize;\n\t}\n}\n"
  },
  {
    "path": "engine/platform/sdl1/host_sdl1.c",
    "content": "/*\nhost_sdl1.c - SDL event system handlers\nCopyright (C) 2015-2025 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n#include <ctype.h>\n\n#include \"common.h\"\n#include \"keydefs.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"platform_sdl1.h\"\n#include \"sound.h\"\n#include \"vid_common.h\"\n\n#define SDL_SCANCODE_A SDLK_a\n#define SDL_SCANCODE_Z SDLK_z\n#define SDL_SCANCODE_1 SDLK_1\n#define SDL_SCANCODE_9 SDLK_9\n#define SDL_SCANCODE_F1 SDLK_F1\n#define SDL_SCANCODE_F12 SDLK_F12\n#define SDL_SCANCODE_GRAVE SDLK_BACKQUOTE\n#define SDL_SCANCODE_0 SDLK_0\n#define SDL_SCANCODE_BACKSLASH SDLK_BACKSLASH\n#define SDL_SCANCODE_LEFTBRACKET SDLK_LEFTBRACKET\n#define SDL_SCANCODE_RIGHTBRACKET SDLK_RIGHTBRACKET\n#define SDL_SCANCODE_EQUALS SDLK_EQUALS\n#define SDL_SCANCODE_MINUS SDLK_MINUS\n#define SDL_SCANCODE_TAB SDLK_TAB\n#define SDL_SCANCODE_RETURN SDLK_RETURN\n#define SDL_SCANCODE_ESCAPE SDLK_ESCAPE\n#define SDL_SCANCODE_SPACE SDLK_SPACE\n#define SDL_SCANCODE_BACKSPACE SDLK_BACKSPACE\n#define SDL_SCANCODE_UP SDLK_UP\n#define SDL_SCANCODE_LEFT SDLK_LEFT\n#define SDL_SCANCODE_DOWN SDLK_DOWN\n#define SDL_SCANCODE_RIGHT SDLK_RIGHT\n#define SDL_SCANCODE_LALT SDLK_LALT\n#define SDL_SCANCODE_RALT SDLK_RALT\n#define SDL_SCANCODE_LCTRL SDLK_LCTRL\n#define SDL_SCANCODE_RCTRL SDLK_RCTRL\n#define SDL_SCANCODE_LSHIFT SDLK_LSHIFT\n#define SDL_SCANCODE_RSHIFT SDLK_RSHIFT\n#define SDL_SCANCODE_LGUI SDLK_LMETA\n#define SDL_SCANCODE_RGUI SDLK_RMETA\n#define SDL_SCANCODE_INSERT SDLK_INSERT\n#define SDL_SCANCODE_DELETE SDLK_DELETE\n#define SDL_SCANCODE_PAGEDOWN SDLK_PAGEDOWN\n#define SDL_SCANCODE_PAGEUP SDLK_PAGEUP\n#define SDL_SCANCODE_HOME SDLK_HOME\n#define SDL_SCANCODE_END SDLK_END\n#define SDL_SCANCODE_KP_1 SDLK_KP1\n#define SDL_SCANCODE_KP_2 SDLK_KP2\n#define SDL_SCANCODE_KP_3 SDLK_KP3\n#define SDL_SCANCODE_KP_4 SDLK_KP4\n#define SDL_SCANCODE_KP_5 SDLK_KP5\n#define SDL_SCANCODE_KP_6 SDLK_KP6\n#define SDL_SCANCODE_KP_7 SDLK_KP7\n#define SDL_SCANCODE_KP_8 SDLK_KP8\n#define SDL_SCANCODE_KP_9 SDLK_KP9\n#define SDL_SCANCODE_KP_0 SDLK_KP0\n#define SDL_SCANCODE_KP_PERIOD SDLK_KP_PERIOD\n#define SDL_SCANCODE_KP_ENTER SDLK_KP_ENTER\n#define SDL_SCANCODE_KP_PLUS SDLK_KP_PLUS\n#define SDL_SCANCODE_KP_MINUS SDLK_KP_MINUS\n#define SDL_SCANCODE_KP_DIVIDE SDLK_KP_DIVIDE\n#define SDL_SCANCODE_KP_MULTIPLY SDLK_KP_MULTIPLY\n#define SDL_SCANCODE_NUMLOCKCLEAR SDLK_NUMLOCK\n#define SDL_SCANCODE_CAPSLOCK SDLK_CAPSLOCK\n#define SDL_SCANCODE_SLASH SDLK_SLASH\n#define SDL_SCANCODE_PERIOD SDLK_PERIOD\n#define SDL_SCANCODE_SEMICOLON SDLK_SEMICOLON\n#define SDL_SCANCODE_APOSTROPHE SDLK_QUOTE\n#define SDL_SCANCODE_COMMA SDLK_COMMA\n#define SDL_SCANCODE_PRINTSCREEN SDLK_PRINT\n#define SDL_SCANCODE_UNKNOWN SDLK_UNKNOWN\n#define SDL_GetScancodeName( x ) \"unknown\"\n\n/*\n=============\nSDLash_KeyEvent\n\n=============\n*/\nstatic void SDLash_KeyEvent( SDL_KeyboardEvent key )\n{\n\tint down = key.state != SDL_RELEASED;\n\tint keynum = key.keysym.sym;\n\n\tif( host.textmode && down )\n\t{\n\t\t// this is how engine understands ctrl+c, ctrl+v and other hotkeys\n\t\tif( cls.key_dest != key_game && FBitSet( SDL_GetModState(), KMOD_CTRL ))\n\t\t{\n\t\t\tif( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z )\n\t\t\t{\n\t\t\t\tkeynum = keynum - SDL_SCANCODE_A + 1;\n\t\t\t\tCL_CharEvent( keynum );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif( keynum >= SDLK_KP0 && keynum <= SDLK_KP9 )\n\t\t\tkeynum -= SDLK_KP0 + '0';\n\n\t\tif( isprint( keynum ))\n\t\t{\n\t\t\tif( FBitSet( SDL_GetModState(), KMOD_SHIFT ))\n\t\t\t\tkeynum = Key_ToUpper( keynum );\n\n\t\t\tCL_CharEvent( keynum );\n\t\t}\n\t}\n\n#define DECLARE_KEY_RANGE( min, max, repl ) \\\n\tif( keynum >= (min) && keynum <= (max) ) \\\n\t{ \\\n\t\tkeynum = keynum - (min) + (repl); \\\n\t}\n\n\tDECLARE_KEY_RANGE( SDL_SCANCODE_A, SDL_SCANCODE_Z, 'a' )\n\telse DECLARE_KEY_RANGE( SDL_SCANCODE_1, SDL_SCANCODE_9, '1' )\n\telse DECLARE_KEY_RANGE( SDL_SCANCODE_F1, SDL_SCANCODE_F12, K_F1 )\n\telse\n\t{\n\t\tqboolean numLock = FBitSet( SDL_GetModState(), KMOD_NUM );\n\n\t\tswitch( keynum )\n\t\t{\n\t\tcase SDL_SCANCODE_GRAVE: keynum = '`'; break;\n\t\tcase SDL_SCANCODE_0: keynum = '0'; break;\n\t\tcase SDL_SCANCODE_BACKSLASH: keynum = '\\\\'; break;\n\t\tcase SDL_SCANCODE_LEFTBRACKET: keynum = '['; break;\n\t\tcase SDL_SCANCODE_RIGHTBRACKET: keynum = ']'; break;\n\t\tcase SDL_SCANCODE_EQUALS: keynum = '='; break;\n\t\tcase SDL_SCANCODE_MINUS: keynum = '-'; break;\n\t\tcase SDL_SCANCODE_TAB: keynum = K_TAB; break;\n\t\tcase SDL_SCANCODE_RETURN: keynum = K_ENTER; break;\n\t\tcase SDL_SCANCODE_ESCAPE: keynum = K_ESCAPE; break;\n\t\tcase SDL_SCANCODE_SPACE: keynum = K_SPACE; break;\n\t\tcase SDL_SCANCODE_BACKSPACE: keynum = K_BACKSPACE; break;\n\t\tcase SDL_SCANCODE_UP: keynum = K_UPARROW; break;\n\t\tcase SDL_SCANCODE_LEFT: keynum = K_LEFTARROW; break;\n\t\tcase SDL_SCANCODE_DOWN: keynum = K_DOWNARROW; break;\n\t\tcase SDL_SCANCODE_RIGHT: keynum = K_RIGHTARROW; break;\n\t\tcase SDL_SCANCODE_LALT:\n\t\tcase SDL_SCANCODE_RALT: keynum = K_ALT; break;\n\t\tcase SDL_SCANCODE_LCTRL:\n\t\tcase SDL_SCANCODE_RCTRL: keynum = K_CTRL; break;\n\t\tcase SDL_SCANCODE_LSHIFT:\n\t\tcase SDL_SCANCODE_RSHIFT: keynum = K_SHIFT; break;\n\t\tcase SDL_SCANCODE_LGUI:\n\t\tcase SDL_SCANCODE_RGUI: keynum = K_WIN; break;\n\t\tcase SDL_SCANCODE_INSERT: keynum = K_INS; break;\n\t\tcase SDL_SCANCODE_DELETE: keynum = K_DEL; break;\n\t\tcase SDL_SCANCODE_PAGEDOWN: keynum = K_PGDN; break;\n\t\tcase SDL_SCANCODE_PAGEUP: keynum = K_PGUP; break;\n\t\tcase SDL_SCANCODE_HOME: keynum = K_HOME; break;\n\t\tcase SDL_SCANCODE_END: keynum = K_END; break;\n\t\tcase SDL_SCANCODE_KP_1: keynum = numLock ? '1' : K_KP_END; break;\n\t\tcase SDL_SCANCODE_KP_2: keynum = numLock ? '2' : K_KP_DOWNARROW; break;\n\t\tcase SDL_SCANCODE_KP_3: keynum = numLock ? '3' : K_KP_PGDN; break;\n\t\tcase SDL_SCANCODE_KP_4: keynum = numLock ? '4' : K_KP_LEFTARROW; break;\n\t\tcase SDL_SCANCODE_KP_5: keynum = numLock ? '5' : K_KP_5; break;\n\t\tcase SDL_SCANCODE_KP_6: keynum = numLock ? '6' : K_KP_RIGHTARROW; break;\n\t\tcase SDL_SCANCODE_KP_7: keynum = numLock ? '7' : K_KP_HOME; break;\n\t\tcase SDL_SCANCODE_KP_8: keynum = numLock ? '8' : K_KP_UPARROW; break;\n\t\tcase SDL_SCANCODE_KP_9: keynum = numLock ? '9' : K_KP_PGUP; break;\n\t\tcase SDL_SCANCODE_KP_0: keynum = numLock ? '0' : K_KP_INS; break;\n\t\tcase SDL_SCANCODE_KP_PERIOD: keynum = K_KP_DEL; break;\n\t\tcase SDL_SCANCODE_KP_ENTER: keynum = K_KP_ENTER; break;\n\t\tcase SDL_SCANCODE_KP_PLUS: keynum = K_KP_PLUS; break;\n\t\tcase SDL_SCANCODE_KP_MINUS: keynum = K_KP_MINUS; break;\n\t\tcase SDL_SCANCODE_KP_DIVIDE: keynum = K_KP_SLASH; break;\n\t\tcase SDL_SCANCODE_KP_MULTIPLY: keynum = '*'; break;\n\t\tcase SDL_SCANCODE_NUMLOCKCLEAR: keynum = K_KP_NUMLOCK; break;\n\t\tcase SDL_SCANCODE_CAPSLOCK: keynum = K_CAPSLOCK; break;\n\t\tcase SDL_SCANCODE_SLASH: keynum = '/'; break;\n\t\tcase SDL_SCANCODE_PERIOD: keynum = '.'; break;\n\t\tcase SDL_SCANCODE_SEMICOLON: keynum = ';'; break;\n\t\tcase SDL_SCANCODE_APOSTROPHE: keynum = '\\''; break;\n\t\tcase SDL_SCANCODE_COMMA: keynum = ','; break;\n\t\tcase SDL_SCANCODE_PRINTSCREEN:\n\t\t{\n\t\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t\t\tbreak;\n\t\t}\n\t\tcase SDL_SCANCODE_UNKNOWN:\n\t\t{\n\t\t\tif( down ) Con_Reportf( \"%s: Unknown scancode\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\t\tdefault:\n\t\t\tif( down ) Con_Reportf( \"%s: Unknown key: %s = %i\\n\", __func__, SDL_GetScancodeName( keynum ), keynum );\n\t\t\treturn;\n\t\t}\n\t}\n\n#undef DECLARE_KEY_RANGE\n\n\tKey_Event( keynum, down );\n}\n\n/*\n=============\nSDLash_MouseEvent\n\n=============\n*/\nstatic void SDLash_MouseEvent( SDL_MouseButtonEvent button )\n{\n\tint down;\n\n\tif( button.state == SDL_RELEASED )\n\t\tdown = 0;\n\telse\n\t\tdown = 1;\n\n\tswitch( button.button )\n\t{\n\tcase SDL_BUTTON_LEFT:\n\t\tIN_MouseEvent( 0, down );\n\t\tbreak;\n\tcase SDL_BUTTON_RIGHT:\n\t\tIN_MouseEvent( 1, down );\n\t\tbreak;\n\tcase SDL_BUTTON_MIDDLE:\n\t\tIN_MouseEvent( 2, down );\n\t\tbreak;\n\tcase SDL_BUTTON_X1:\n\t\tIN_MouseEvent( 3, down );\n\t\tbreak;\n\tcase SDL_BUTTON_X2:\n\t\tIN_MouseEvent( 4, down );\n\t\tbreak;\n\tcase SDL_BUTTON_WHEELUP:\n\t\tIN_MWheelEvent( -1 );\n\t\tbreak;\n\tcase SDL_BUTTON_WHEELDOWN:\n\t\tIN_MWheelEvent( 1 );\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( \"Unknown mouse button ID: %d\\n\", button.button );\n\t}\n}\n\nstatic void SDLash_ActiveEvent( int gain )\n{\n\tif( gain )\n\t{\n\t\thost.status = HOST_FRAME;\n\t\tif( cls.key_dest == key_game )\n\t\t\tIN_ActivateMouse( );\n\n\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t\tif( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN )\n\t\t\tVID_SetMode();\n\t}\n\telse\n\t{\n\t\thost.status = HOST_NOFOCUS;\n\n\t\tif( cls.key_dest == key_game )\n\t\t{\n\t\t\tKey_ClearStates();\n\t\t\tIN_DeactivateMouse();\n\t\t}\n\n\t\thost.force_draw_version_time = host.realtime + 2.0;\n\t\tVID_RestoreScreenResolution();\n\t}\n}\n\n/*\n=============\nSDLash_EventFilter\n\n=============\n*/\nstatic void SDLash_EventHandler( SDL_Event *event )\n{\n\tswitch ( event->type )\n\t{\n\t/* Mouse events */\n\tcase SDL_MOUSEMOTION:\n\t\tif( host.mouse_visible )\n\t\t\tSDL_GetRelativeMouseState( NULL, NULL );\n\t\tbreak;\n\n\tcase SDL_MOUSEBUTTONUP:\n\tcase SDL_MOUSEBUTTONDOWN:\n\t\tSDLash_MouseEvent( event->button );\n\t\tbreak;\n\n\t/* Keyboard events */\n\tcase SDL_KEYDOWN:\n\tcase SDL_KEYUP:\n\t\tSDLash_KeyEvent( event->key );\n\t\tbreak;\n\n\tcase SDL_QUIT:\n\t\tSys_Quit( \"caught SDL_QUIT\" );\n\t\tbreak;\n\tcase SDL_VIDEORESIZE:\n\t\tVID_SaveWindowSize( event->resize.w, event->resize.h, false );\n\t\tbreak;\n\tcase SDL_ACTIVEEVENT:\n\t\tSDLash_ActiveEvent( event->active.gain );\n\t\tbreak;\n\t}\n}\n\n/*\n=============\nSDLash_RunEvents\n\n=============\n*/\nvoid Platform_RunEvents( void )\n{\n\tSDL_Event event;\n\n\twhile( host.status != HOST_CRASHED && !host.shutdown_issued && SDL_PollEvent( &event ) )\n\t\tSDLash_EventHandler( &event );\n}\n\n/*\n========================\nPlatform_PreCreateMove\n\nthis should disable mouse look on client when m_ignore enabled\nTODO: kill mouse in win32 clients too\n========================\n*/\nvoid Platform_PreCreateMove( void )\n{\n\tif( m_ignore.value )\n\t{\n\t\tSDL_GetRelativeMouseState( NULL, NULL );\n\t\tSDL_ShowCursor( SDL_TRUE );\n\t}\n}\n"
  },
  {
    "path": "engine/platform/sdl1/in_sdl1.c",
    "content": "/*\nin_sdl1.c - SDL input component\nCopyright (C) 2018-2025 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n\n#include \"common.h\"\n#include \"keydefs.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"platform_sdl1.h\"\n#include \"sound.h\"\n#include \"vid_common.h\"\n\n#define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) )\n\n/*\n=============\nPlatform_GetMousePos\n\n=============\n*/\nvoid GAME_EXPORT Platform_GetMousePos( int *x, int *y )\n{\n\tSDL_GetMouseState( x, y );\n\n\tif( x && window_width.value && window_width.value != refState.width )\n\t{\n\t\tfloat factor = refState.width / window_width.value;\n\t\t*x = *x * factor;\n\t}\n\n\tif( y && window_height.value && window_height.value != refState.height )\n\t{\n\t\tfloat factor = refState.height / window_height.value;\n\t\t*y = *y * factor;\n\t}\n}\n\n/*\n=============\nPlatform_SetMousePos\n\n============\n*/\nvoid GAME_EXPORT Platform_SetMousePos( int x, int y )\n{\n\tSDL_WarpMouseInWindow( host.hWnd, x, y );\n}\n\n/*\n========================\nPlatform_MouseMove\n\n========================\n*/\nvoid Platform_MouseMove( float *x, float *y )\n{\n\tint m_x, m_y;\n\tSDL_GetRelativeMouseState( &m_x, &m_y );\n\t*x = (float)m_x;\n\t*y = (float)m_y;\n}\n\n/*\n=============\nPlatform_GetClipobardText\n\n=============\n*/\nint Platform_GetClipboardText( char *buffer, size_t size )\n{\n\tbuffer[0] = 0;\n\treturn 0;\n}\n\n/*\n=============\nPlatform_SetClipobardText\n\n=============\n*/\nvoid Platform_SetClipboardText( const char *buffer )\n{\n}\n\n/*\n========================\nPlatform_SetCursorType\n\n========================\n*/\nvoid Platform_SetCursorType( VGUI_DefaultCursor type )\n{\n\tqboolean visible;\n\n\tswitch( type )\n\t{\n\t\tcase dc_user:\n\t\tcase dc_none:\n\t\t\tvisible = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tvisible = true;\n\t\t\tbreak;\n\t}\n\n\t// never disable cursor in touch emulation mode\n\tif( !visible && Touch_WantVisibleCursor( ))\n\t\treturn;\n\n\thost.mouse_visible = visible;\n\tVGui_UpdateInternalCursorState( type );\n\n\tif( host.mouse_visible )\n\t{\n\t\tSDL_ShowCursor( true );\n\t}\n\telse\n\t{\n\t\tSDL_ShowCursor( false );\n\t}\n}\n\n/*\n========================\nPlatform_GetMouseGrab\n========================\n*/\nqboolean Platform_GetMouseGrab( void )\n{\n\treturn SDL_WM_GrabInput( SDL_GRAB_QUERY ) != SDL_GRAB_OFF;\n}\n\n/*\n========================\nPlatform_SetMouseGrab\n========================\n*/\nvoid Platform_SetMouseGrab( qboolean enable )\n{\n\tSDL_WM_GrabInput( enable ? SDL_GRAB_ON : SDL_GRAB_OFF );\n}\n"
  },
  {
    "path": "engine/platform/sdl1/platform_sdl1.h",
    "content": "/*\nplatform_sdl1.h - SDL backend internal header\nCopyright (C) 2015-2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef KEYWRAPPER_H\n#define KEYWRAPPER_H\n#ifdef  XASH_SDL\n\n#include \"platform.h\"\n\n// window management\nvoid VID_RestoreScreenResolution( void );\nqboolean  VID_CreateWindow( int width, int height, window_mode_t window_mode );\nvoid      VID_DestroyWindow( void );\nvoid GL_InitExtensions( void );\nqboolean GL_DeleteContext( void );\nvoid VID_SaveWindowSize( int width, int height, qboolean maximized );\n\n//\n// joy_sdl.c\n//\nvoid SDLash_HandleGameControllerEvent( SDL_Event *ev );\n\n#endif // XASH_SDL\n#endif // KEYWRAPPER_H\n"
  },
  {
    "path": "engine/platform/sdl1/s_sdl1.c",
    "content": "/*\ns_sdl1.c - sound hardware output\nCopyright (C) 2009 Uncle Mike\nCopyright (C) 2025 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"platform.h\"\n#if XASH_SOUND == SOUND_SDL\n\n#include \"sound.h\"\n#include \"voice.h\"\n\n#include <SDL.h>\n#include <stdlib.h>\n\n#define SAMPLE_16BIT_SHIFT 1\n#define SECONDARY_BUFFER_SIZE 0x10000\n\n#define SDL_GetCurrentAudioDriver() \"legacysdl\"\n#define SDL_OpenAudioDevice( a, b, c, d, e ) SDL_OpenAudio( ( c ), ( d ) )\n#define SDL_CloseAudioDevice( a ) SDL_CloseAudio()\n#define SDL_PauseAudioDevice( a, b ) SDL_PauseAudio( ( b ) )\n#define SDL_LockAudioDevice( x ) SDL_LockAudio()\n#define SDL_UnlockAudioDevice( x ) SDL_UnlockAudio()\n#define SDLash_IsAudioError( x ) (( x ) != 0)\n\n/*\n=======================================================================\nGlobal variables. Must be visible to window-procedure function\nso it can unlock and free the data block after it has been played.\n=======================================================================\n*/\nstatic int sdl_dev;\nstatic char sdl_backend_name[32];\n\nstatic void SDL_SoundCallback( void *userdata, Uint8 *stream, int len )\n{\n\tconst int size = dma.samples << 1;\n\tint pos;\n\tint wrapped;\n\n\tif( !dma.buffer )\n\t{\n\t\tmemset( stream, 0, len );\n\t\treturn;\n\t}\n\n\tpos = dma.samplepos << 1;\n\tif( pos >= size )\n\t\tpos = dma.samplepos = 0;\n\n\twrapped = pos + len - size;\n\n\tif( wrapped < 0 )\n\t{\n\t\tmemcpy( stream, dma.buffer + pos, len );\n\t\tdma.samplepos += len >> 1;\n\t}\n\telse\n\t{\n\t\tint remaining = size - pos;\n\n\t\tmemcpy( stream, dma.buffer + pos, remaining );\n\t\tmemcpy( stream + remaining, dma.buffer, wrapped );\n\t\tdma.samplepos = wrapped >> 1;\n\t}\n\n\tif( dma.samplepos >= size )\n\t\tdma.samplepos = 0;\n}\n\n/*\n==================\nSNDDMA_Init\n\nTry to find a sound device to mix for.\nReturns false if nothing is found.\n==================\n*/\nqboolean SNDDMA_Init( void )\n{\n\tSDL_AudioSpec desired, obtained;\n\tint samplecount;\n\tconst char *driver = NULL;\n\n\t// reinitialize SDL with our driver just in case\n\tif( SDL_WasInit( SDL_INIT_AUDIO ))\n\t\tSDL_QuitSubSystem( SDL_INIT_AUDIO );\n\n\tif( SDL_InitSubSystem( SDL_INIT_AUDIO ))\n\t{\n\t\tCon_Reportf( S_ERROR \"Audio: SDL: %s \\n\", SDL_GetError( ) );\n\t\treturn false;\n\t}\n\n\tmemset( &desired, 0, sizeof( desired ) );\n\tdesired.freq     = SOUND_DMA_SPEED;\n\tdesired.format   = AUDIO_S16LSB;\n\tdesired.samples  = 1024;\n\tdesired.channels = 2;\n\tdesired.callback = SDL_SoundCallback;\n\n\tsdl_dev = SDL_OpenAudioDevice( NULL, 0, &desired, &obtained, 0 );\n\n\tif( SDLash_IsAudioError( sdl_dev ))\n\t{\n\t\tCon_Printf( \"Couldn't open SDL audio: %s\\n\", SDL_GetError( ) );\n\t\treturn false;\n\t}\n\n\tif( obtained.format != AUDIO_S16LSB )\n\t{\n\t\tCon_Printf( \"SDL audio format %d unsupported.\\n\", obtained.format );\n\t\tgoto fail;\n\t}\n\n\tif( obtained.channels != 1 && obtained.channels != 2 )\n\t{\n\t\tCon_Printf( \"SDL audio channels %d unsupported.\\n\", obtained.channels );\n\t\tgoto fail;\n\t}\n\n\tdma.format.speed    = obtained.freq;\n\tdma.format.channels = obtained.channels;\n\tdma.format.width    = 2;\n\tsamplecount = s_samplecount.value;\n\tif( !samplecount )\n\t\tsamplecount = 0x8000;\n\tdma.samples         = samplecount * obtained.channels;\n\tdma.buffer          = Mem_Calloc( sndpool, dma.samples * 2 );\n\tdma.samplepos       = 0;\n\n\tCon_Printf( \"Using SDL audio driver: %s @ %d Hz\\n\", SDL_GetCurrentAudioDriver( ), obtained.freq );\n\tQ_snprintf( sdl_backend_name, sizeof( sdl_backend_name ), \"SDL\" );\n\tdma.initialized = true;\n\tdma.backendName = sdl_backend_name;\n\n\tSNDDMA_Activate( true );\n\n\treturn true;\n\nfail:\n\tSNDDMA_Shutdown( );\n\treturn false;\n}\n\n\n/*\n==============\nSNDDMA_BeginPainting\n\nMakes sure dma.buffer is valid\n===============\n*/\nvoid SNDDMA_BeginPainting( void )\n{\n\tSDL_LockAudioDevice( sdl_dev );\n}\n\n/*\n==============\nSNDDMA_Submit\n\nSend sound to device if buffer isn't really the dma buffer\nAlso unlocks the dsound buffer\n===============\n*/\nvoid SNDDMA_Submit( void )\n{\n\tSDL_UnlockAudioDevice( sdl_dev );\n}\n\n/*\n==============\nSNDDMA_Shutdown\n\nReset the sound device for exiting\n===============\n*/\nvoid SNDDMA_Shutdown( void )\n{\n\tCon_Printf( \"Shutting down audio.\\n\" );\n\tdma.initialized = false;\n\n\tif( sdl_dev )\n\t{\n\t\tSNDDMA_Activate( false );\n\n\t\tSDL_CloseAudioDevice( sdl_dev );\n\t}\n\n\tif( SDL_WasInit( SDL_INIT_AUDIO ))\n\t\tSDL_QuitSubSystem( SDL_INIT_AUDIO );\n\n\tif( dma.buffer )\n\t{\n\t\tMem_Free( dma.buffer );\n\t\tdma.buffer = NULL;\n\t}\n}\n\n/*\n===========\nSNDDMA_Activate\nCalled when the main window gains or loses focus.\nThe window have been destroyed and recreated\nbetween a deactivate and an activate.\n===========\n*/\nvoid SNDDMA_Activate( qboolean active )\n{\n\tif( !dma.initialized )\n\t\treturn;\n\n\tSDL_PauseAudioDevice( sdl_dev, !active );\n}\n\n/*\n===========\nVoiceCapture_Init\n===========\n*/\nqboolean VoiceCapture_Init( void )\n{\n\treturn false;\n}\n\n/*\n===========\nVoiceCapture_Activate\n===========\n*/\nqboolean VoiceCapture_Activate( qboolean activate )\n{\n\treturn false;\n}\n\n/*\n===========\nVoiceCapture_Lock\n===========\n*/\nqboolean VoiceCapture_Lock( qboolean lock )\n{\n\treturn false;\n}\n\n/*\n==========\nVoiceCapture_Shutdown\n==========\n*/\nvoid VoiceCapture_Shutdown( void )\n{\n}\n\n#endif // XASH_SOUND == SOUND_SDL\n"
  },
  {
    "path": "engine/platform/sdl1/sys_sdl1.c",
    "content": "/*\nsys_sdl1.c - SDL1 system utils\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <SDL.h>\n#include \"platform.h\"\n#include \"platform_sdl1.h\"\n\n#if XASH_TIMER == TIMER_SDL\ndouble Platform_DoubleTime( void )\n{\n\tstatic Uint64 g_PerformanceFrequency;\n\tstatic Uint64 g_ClockStart;\n\tUint64 CurrentTime;\n\n\tif( !g_PerformanceFrequency )\n\t{\n\t\tg_PerformanceFrequency = SDL_GetPerformanceFrequency();\n\t\tg_ClockStart = SDL_GetPerformanceCounter();\n\t}\n\tCurrentTime = SDL_GetPerformanceCounter();\n\treturn (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency );\n}\n\nvoid Platform_Sleep( int msec )\n{\n\tSDL_Delay( msec );\n}\n#endif // XASH_TIMER == TIMER_SDL\n\n#if XASH_MESSAGEBOX == MSGBOX_SDL\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )\n{\n\tSDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL );\n}\n#endif // XASH_MESSAGEBOX == MSGBOX_SDL\n\nvoid SDLash_Init( const char *basedir )\n{\n\tif( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO ))\n\t{\n\t\tSys_Warn( \"SDL_Init failed: %s\", SDL_GetError() );\n\t\thost.type = HOST_DEDICATED;\n\t}\n}\n\nvoid SDLash_Shutdown( void )\n{\n\tSDL_Quit();\n}\n"
  },
  {
    "path": "engine/platform/sdl1/vid_sdl1.c",
    "content": "/*\nvid_sdl1.c - SDL vid component\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n#include \"common.h\"\n#include \"client.h\"\n#include \"vid_common.h\"\n#include \"platform_sdl1.h\"\n\nstatic vidmode_t *vidmodes = NULL;\nstatic int num_vidmodes = 0;\nstatic void GL_SetupAttributes( void );\nstruct\n{\n\tint prev_width, prev_height;\n} sdlState = { 640, 480 };\n\nstruct\n{\n\tint width, height;\n\tSDL_Surface *surf;\n\tSDL_Surface *win;\n} sw;\n\nvoid Platform_Minimize_f( void )\n{\n\tSDL_WM_IconifyWindow();\n}\n\nqboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tsw.width = width;\n\tsw.height = height;\n\tsw.win = SDL_GetVideoSurface();\n\n\t// sdl will create renderer if hw framebuffer unavailiable, so cannot fallback here\n\t// if it is failed, it is not possible to draw with SDL in REF_SOFTWARE mode\n\tif( !sw.win )\n\t{\n\t\tSys_Warn( \"failed to initialize software output, try running with -glblit flag\" );\n\t\treturn false;\n\t}\n\n\t*bpp = sw.win->format->BytesPerPixel;\n\t*r = sw.win->format->Rmask;\n\t*g = sw.win->format->Gmask;\n\t*b = sw.win->format->Bmask;\n\t*stride = sw.win->pitch / sw.win->format->BytesPerPixel;\n\n\treturn true;\n}\n\nvoid *SW_LockBuffer( void )\n{\n\tsw.win = SDL_GetVideoSurface();\n\n\t// prevent buffer overrun\n\tif( !sw.win || sw.win->w < sw.width || sw.win->h < sw.height  )\n\t\treturn NULL;\n\n\t// real window pixels (x11 shm region, dma buffer, etc)\n\t// or SDL_Renderer texture if not supported\n\tSDL_LockSurface( sw.win );\n\treturn sw.win->pixels;\n}\n\nvoid SW_UnlockBuffer( void )\n{\n\t// already blitted\n\tSDL_UnlockSurface( sw.win );\n\n\tSDL_Flip( host.hWnd );\n}\n\nint R_MaxVideoModes( void )\n{\n\treturn num_vidmodes;\n}\n\nvidmode_t *R_GetVideoMode( int num )\n{\n\tif( !vidmodes || num < 0 || num >= R_MaxVideoModes() )\n\t{\n\t\treturn NULL;\n\t}\n\n\treturn vidmodes + num;\n}\n\nstatic void R_InitVideoModes( void )\n{\n\tchar buf[MAX_VA_STRING];\n\tSDL_Rect **modes;\n\tint len = 0, i = 0, j;\n\n\tmodes = SDL_ListModes( NULL, SDL_FULLSCREEN );\n\n\tif( !modes || modes == (void*)-1 )\n\t\treturn;\n\n\tfor( len = 0; modes[len]; len++ );\n\n\tvidmodes = Mem_Malloc( host.mempool, len * sizeof( vidmode_t ) );\n\n\t// from smallest to largest\n\tfor( ; i < len; i++ )\n\t{\n\t\tSDL_Rect *mode = modes[len - i - 1];\n\n\t\tfor( j = 0; j < num_vidmodes; j++ )\n\t\t{\n\t\t\tif( mode->w == vidmodes[j].width &&\n\t\t\t\tmode->h == vidmodes[j].height )\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif( j != num_vidmodes )\n\t\t\tcontinue;\n\n\t\tvidmodes[num_vidmodes].width = mode->w;\n\t\tvidmodes[num_vidmodes].height = mode->h;\n\t\tQ_snprintf( buf, sizeof( buf ), \"%ix%i\", mode->w, mode->h );\n\t\tvidmodes[num_vidmodes].desc = copystring( buf );\n\n\t\tnum_vidmodes++;\n\t}\n}\n\nstatic void R_FreeVideoModes( void )\n{\n\tint i;\n\n\tif( !vidmodes )\n\t\treturn;\n\n\tfor( i = 0; i < num_vidmodes; i++ )\n\t\tMem_Free( (char*)vidmodes[i].desc );\n\tMem_Free( vidmodes );\n\n\tvidmodes = NULL;\n}\n\n/*\n=================\nGL_GetProcAddress\n=================\n*/\nvoid *GL_GetProcAddress( const char *name )\n{\n\tvoid *func = SDL_GL_GetProcAddress( name );\n\n\tif( !func )\n\t\tCon_Reportf( S_ERROR \"%s failed for %s\\n\", __func__, name );\n\n\treturn func;\n}\n\n/*\n===============\nGL_UpdateSwapInterval\n===============\n*/\nvoid GL_UpdateSwapInterval( void )\n{\n}\n\n/*\n=================\nGL_DeleteContext\n\nalways return false\n=================\n*/\nqboolean GL_DeleteContext( void )\n{\n\treturn false;\n}\n\n/*\n=================\nGL_CreateContext\n=================\n*/\nstatic qboolean GL_CreateContext( void )\n{\n\treturn true;\n}\n\n/*\n=================\nGL_UpdateContext\n=================\n*/\nstatic qboolean GL_UpdateContext( void )\n{\n\treturn true;\n}\n\nvoid VID_SaveWindowSize( int width, int height, qboolean maximized )\n{\n\tint render_w = width, render_h = height;\n\n\tVID_SetDisplayTransform( &render_w, &render_h );\n\tR_SaveVideoMode( width, height, render_w, render_h, maximized );\n}\n\nstatic qboolean VID_SetScreenResolution( int width, int height, window_mode_t window_mode )\n{\n\tVID_SaveWindowSize( width, height, true );\n\treturn true;\n}\n\nvoid VID_RestoreScreenResolution( void )\n{\n}\n\nstatic qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags )\n{\n\twhile( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST )\n\t{\n\t\thost.hWnd = sw.surf = SDL_SetVideoMode( w, h, 16, flags );\n\t\t// we have window, exit loop\n\t\tif( host.hWnd )\n\t\t\tbreak;\n\n\t\tCon_Reportf( S_ERROR \"%s: couldn't create '%s' with safegl level %d: %s\\n\", __func__, wndname, glw_state.safe, SDL_GetError());\n\n\t\tglw_state.safe++;\n\n\t\tif( !gl_msaa_samples.value && glw_state.safe == SAFE_NOMSAA )\n\t\t\tglw_state.safe++; // no need to skip msaa, if we already disabled it\n\n\t\tGL_SetupAttributes(); // re-choose attributes\n\n\t\t// try again create window\n\t}\n\n\t// window creation has failed...\n\tif( glw_state.safe >= SAFE_LAST )\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic qboolean RectFitsInDisplay( const SDL_Rect *rect, const SDL_Rect *display )\n{\n\treturn rect->x >= display->x\n\t\t&& rect->y >= display->y\n\t\t&& rect->x + rect->w <= display->x + display->w\n\t\t&& rect->y + rect->h <= display->y + display->h;\n}\n// Function to check if the rectangle fits in any display\nstatic qboolean RectFitsInAnyDisplay( const SDL_Rect *rect, const SDL_Rect *display_rects, int num_displays )\n{\n\tfor( int i = 0; i < num_displays; i++ )\n\t{\n\t\tif( RectFitsInDisplay( rect, &display_rects[i] ))\n\t\t\treturn true; // Rectangle fits in this display\n\t}\n\treturn false; // Rectangle does not fit in any display\n}\n\n/*\n=================\nVID_CreateWindow\n=================\n*/\nqboolean VID_CreateWindow( int width, int height, window_mode_t window_mode )\n{\n\tstring wndname;\n\tUint32 flags = 0;\n\n\tQ_strncpy( wndname, GI->title, sizeof( wndname ));\n\n\tif( window_mode != WINDOW_MODE_WINDOWED )\n\t\tSetBits( flags, SDL_FULLSCREEN|SDL_HWSURFACE );\n\n\tif( !glw_state.software )\n\t\tSetBits( flags, SDL_OPENGL );\n\n\tif( !VID_CreateWindowWithSafeGL( wndname, 0, 0, width, height, flags ))\n\t\treturn false;\n\n\tVID_SaveWindowSize( width, height, false );\n\n\treturn true;\n}\n\n/*\n=================\nVID_DestroyWindow\n=================\n*/\nvoid VID_DestroyWindow( void )\n{\n\tGL_DeleteContext();\n\n\tVID_RestoreScreenResolution();\n\tif( host.hWnd )\n\t\thost.hWnd = NULL;\n\n\tif( refState.fullScreen )\n\t\trefState.fullScreen = false;\n}\n\n/*\n==================\nGL_SetupAttributes\n==================\n*/\nstatic void GL_SetupAttributes( void )\n{\n\tref.dllFuncs.GL_SetupAttributes( glw_state.safe );\n}\n\nvoid GL_SwapBuffers( void )\n{\n\tSDL_Flip( host.hWnd );\n}\n\nint GL_SetAttribute( int attr, int val )\n{\n\tswitch( attr )\n\t{\n#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );\n#undef MAP_REF_API_ATTRIBUTE_TO_SDL\n\t}\n\n\treturn -1;\n}\n\nint GL_GetAttribute( int attr, int *val )\n{\n\tswitch( attr )\n\t{\n#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );\n#undef MAP_REF_API_ATTRIBUTE_TO_SDL\n\t}\n\n\treturn 0;\n}\n\n/*\n==================\nR_Init_Video\n==================\n*/\nqboolean R_Init_Video( const int type )\n{\n\tstring safe;\n\tqboolean retval;\n\n\trefState.desktopBitsPixel = 16;\n\n\tswitch( type )\n\t{\n\tcase REF_SOFTWARE:\n\t\tglw_state.software = true;\n\t\tbreak;\n\tcase REF_GL:\n\t\tif( !glw_state.safe && Sys_GetParmFromCmdLine( \"-safegl\", safe ) )\n\t\t\tglw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE );\n\n\t\t// refdll can request some attributes\n\t\tGL_SetupAttributes( );\n\n\t\tif( SDL_GL_LoadLibrary( NULL ) < 0 )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR  \"Couldn't initialize OpenGL: %s\\n\", SDL_GetError());\n\t\t\treturn false;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"Can't initialize unknown context type %d!\\n\", type );\n\t\tbreak;\n\t}\n\n\tif( !(retval = VID_SetMode()) )\n\t{\n\t\treturn retval;\n\t}\n\n\tswitch( type )\n\t{\n\tcase REF_GL:\n\t\t// refdll also can check extensions\n\t\tref.dllFuncs.GL_InitExtensions();\n\t\tbreak;\n\tcase REF_SOFTWARE:\n\tdefault:\n\t\tbreak;\n\t}\n\n\tR_InitVideoModes();\n\n\thost.renderinfo_changed = false;\n\n\treturn true;\n}\n\nrserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )\n{\n\trefState.fullScreen = window_mode != WINDOW_MODE_WINDOWED;\n\tCon_Reportf( \"%s: Setting video mode to %dx%d %s\\n\", __func__, width, height, refState.fullScreen ? \"fullscreen\" : \"windowed\" );\n\n\tif( !host.hWnd )\n\t{\n\t\tif( !VID_CreateWindow( width, height, window_mode ))\n\t\t\treturn rserr_invalid_mode;\n\t}\n\telse if( refState.fullScreen )\n\t{\n\t\tif( !VID_SetScreenResolution( width, height, window_mode ))\n\t\t\treturn rserr_invalid_fullscreen;\n\t}\n\telse\n\t{\n\t\tVID_RestoreScreenResolution();\n\t\tVID_SaveWindowSize( width, height, true );\n\t}\n\n\treturn rserr_ok;\n}\n\n/*\n==================\nVID_SetMode\n\nSet the described video mode\n==================\n*/\nqboolean VID_SetMode( void )\n{\n\tint iScreenWidth, iScreenHeight;\n\trserr_t\terr;\n\twindow_mode_t window_mode;\n\n\tiScreenWidth = Cvar_VariableInteger( \"width\" );\n\tiScreenHeight = Cvar_VariableInteger( \"height\" );\n\n\tif( iScreenWidth < VID_MIN_WIDTH ||\n\t\tiScreenHeight < VID_MIN_HEIGHT )\t// trying to get resolution automatically by default\n\t{\n\t\tiScreenWidth = 320;\n\t\tiScreenHeight = 240;\n\t}\n\n\twindow_mode = bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 );\n\tSetBits( gl_vsync.flags, FCVAR_CHANGED );\n\n\tif(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, window_mode )) == rserr_ok )\n\t{\n\t\tsdlState.prev_width = iScreenWidth;\n\t\tsdlState.prev_height = iScreenHeight;\n\t}\n\telse\n\t{\n\t\tif( err == rserr_invalid_fullscreen )\n\t\t{\n\t\t\tCvar_DirectSet( &vid_fullscreen, \"0\" );\n\t\t\tCon_Reportf( S_ERROR \"%s: fullscreen unavailable in this mode\\n\", __func__ );\n\t\t\tSys_Warn( \"fullscreen unavailable in this mode!\" );\n\t\t\tif(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, WINDOW_MODE_WINDOWED )) == rserr_ok )\n\t\t\t\treturn true;\n\t\t}\n\t\telse if( err == rserr_invalid_mode )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: invalid mode\\n\", __func__ );\n\t\t\tSys_Warn( \"invalid mode, engine will run in %dx%d\", sdlState.prev_width, sdlState.prev_height );\n\t\t}\n\n\t\t// try setting it back to something safe\n\t\tif(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, WINDOW_MODE_WINDOWED )) != rserr_ok )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: could not revert to safe mode\\n\", __func__ );\n\t\t\tSys_Warn( \"could not revert to safe mode!\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type )\n{\n\treturn REF_WINDOW_TYPE_NULL;\n}\n\n/*\n==================\nR_Free_Video\n==================\n*/\nvoid R_Free_Video( void )\n{\n\tGL_DeleteContext ();\n\n\tVID_DestroyWindow ();\n\n\tR_FreeVideoModes();\n\n\tref.dllFuncs.GL_ClearExtensions();\n}\n"
  },
  {
    "path": "engine/platform/sdl2/host_sdl2.c",
    "content": "/*\nhost_sdl2.c - SDL event system handlers\nCopyright (C) 2015-2025 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n#include <ctype.h>\n\n#include \"common.h\"\n#include \"keydefs.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"platform_sdl2.h\"\n#include \"sound.h\"\n#include \"vid_common.h\"\n\n/*\n=============\nSDLash_KeyEvent\n\n=============\n*/\nstatic void SDLash_KeyEvent( SDL_KeyboardEvent key )\n{\n\tint down = key.state != SDL_RELEASED;\n\tint keynum = key.keysym.scancode;\n\n#if XASH_ANDROID\n\tif( keynum == SDL_SCANCODE_VOLUMEUP || keynum == SDL_SCANCODE_VOLUMEDOWN )\n\t{\n\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t}\n#endif\n\n\tif( SDL_IsTextInputActive( ) && down )\n\t{\n\t\t// this is how engine understands ctrl+c, ctrl+v and other hotkeys\n\t\tif( cls.key_dest != key_game && FBitSet( SDL_GetModState(), KMOD_CTRL ))\n\t\t{\n\t\t\tif( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z )\n\t\t\t{\n\t\t\t\tkeynum = keynum - SDL_SCANCODE_A + 1;\n\t\t\t\tCL_CharEvent( keynum );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// ignore printable keys, they are coming through SDL_TEXTINPUT\n\t\tif(( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z )\n\t\t\t|| ( keynum >= SDL_SCANCODE_1 && keynum <= SDL_SCANCODE_0 )\n\t\t\t|| ( keynum >= SDL_SCANCODE_KP_1 && keynum <= SDL_SCANCODE_KP_0 ))\n\t\t\treturn;\n\t}\n\n#define DECLARE_KEY_RANGE( min, max, repl ) \\\n\tif( keynum >= (min) && keynum <= (max) ) \\\n\t{ \\\n\t\tkeynum = keynum - (min) + (repl); \\\n\t}\n\n\tDECLARE_KEY_RANGE( SDL_SCANCODE_A, SDL_SCANCODE_Z, 'a' )\n\telse DECLARE_KEY_RANGE( SDL_SCANCODE_1, SDL_SCANCODE_9, '1' )\n\telse DECLARE_KEY_RANGE( SDL_SCANCODE_F1, SDL_SCANCODE_F12, K_F1 )\n\telse\n\t{\n\t\tqboolean numLock = FBitSet( SDL_GetModState(), KMOD_NUM );\n\n\t\tswitch( keynum )\n\t\t{\n\t\tcase SDL_SCANCODE_GRAVE: keynum = '`'; break;\n\t\tcase SDL_SCANCODE_0: keynum = '0'; break;\n\t\tcase SDL_SCANCODE_BACKSLASH: keynum = '\\\\'; break;\n\t\tcase SDL_SCANCODE_LEFTBRACKET: keynum = '['; break;\n\t\tcase SDL_SCANCODE_RIGHTBRACKET: keynum = ']'; break;\n\t\tcase SDL_SCANCODE_EQUALS: keynum = '='; break;\n\t\tcase SDL_SCANCODE_MINUS: keynum = '-'; break;\n\t\tcase SDL_SCANCODE_TAB: keynum = K_TAB; break;\n\t\tcase SDL_SCANCODE_RETURN: keynum = K_ENTER; break;\n\t\tcase SDL_SCANCODE_AC_BACK:\n\t\tcase SDL_SCANCODE_ESCAPE: keynum = K_ESCAPE; break;\n\t\tcase SDL_SCANCODE_SPACE: keynum = K_SPACE; break;\n\t\tcase SDL_SCANCODE_BACKSPACE: keynum = K_BACKSPACE; break;\n\t\tcase SDL_SCANCODE_UP: keynum = K_UPARROW; break;\n\t\tcase SDL_SCANCODE_LEFT: keynum = K_LEFTARROW; break;\n\t\tcase SDL_SCANCODE_DOWN: keynum = K_DOWNARROW; break;\n\t\tcase SDL_SCANCODE_RIGHT: keynum = K_RIGHTARROW; break;\n\t\tcase SDL_SCANCODE_LALT:\n\t\tcase SDL_SCANCODE_RALT: keynum = K_ALT; break;\n\t\tcase SDL_SCANCODE_LCTRL:\n\t\tcase SDL_SCANCODE_RCTRL: keynum = K_CTRL; break;\n\t\tcase SDL_SCANCODE_LSHIFT:\n\t\tcase SDL_SCANCODE_RSHIFT: keynum = K_SHIFT; break;\n\t\tcase SDL_SCANCODE_LGUI:\n\t\tcase SDL_SCANCODE_RGUI: keynum = K_WIN; break;\n\t\tcase SDL_SCANCODE_INSERT: keynum = K_INS; break;\n\t\tcase SDL_SCANCODE_DELETE: keynum = K_DEL; break;\n\t\tcase SDL_SCANCODE_PAGEDOWN: keynum = K_PGDN; break;\n\t\tcase SDL_SCANCODE_PAGEUP: keynum = K_PGUP; break;\n\t\tcase SDL_SCANCODE_HOME: keynum = K_HOME; break;\n\t\tcase SDL_SCANCODE_END: keynum = K_END; break;\n\t\tcase SDL_SCANCODE_KP_1: keynum = numLock ? '1' : K_KP_END; break;\n\t\tcase SDL_SCANCODE_KP_2: keynum = numLock ? '2' : K_KP_DOWNARROW; break;\n\t\tcase SDL_SCANCODE_KP_3: keynum = numLock ? '3' : K_KP_PGDN; break;\n\t\tcase SDL_SCANCODE_KP_4: keynum = numLock ? '4' : K_KP_LEFTARROW; break;\n\t\tcase SDL_SCANCODE_KP_5: keynum = numLock ? '5' : K_KP_5; break;\n\t\tcase SDL_SCANCODE_KP_6: keynum = numLock ? '6' : K_KP_RIGHTARROW; break;\n\t\tcase SDL_SCANCODE_KP_7: keynum = numLock ? '7' : K_KP_HOME; break;\n\t\tcase SDL_SCANCODE_KP_8: keynum = numLock ? '8' : K_KP_UPARROW; break;\n\t\tcase SDL_SCANCODE_KP_9: keynum = numLock ? '9' : K_KP_PGUP; break;\n\t\tcase SDL_SCANCODE_KP_0: keynum = numLock ? '0' : K_KP_INS; break;\n\t\tcase SDL_SCANCODE_KP_PERIOD: keynum = K_KP_DEL; break;\n\t\tcase SDL_SCANCODE_KP_ENTER: keynum = K_KP_ENTER; break;\n\t\tcase SDL_SCANCODE_KP_PLUS: keynum = K_KP_PLUS; break;\n\t\tcase SDL_SCANCODE_KP_MINUS: keynum = K_KP_MINUS; break;\n\t\tcase SDL_SCANCODE_KP_DIVIDE: keynum = K_KP_SLASH; break;\n\t\tcase SDL_SCANCODE_KP_MULTIPLY: keynum = '*'; break;\n\t\tcase SDL_SCANCODE_NUMLOCKCLEAR: keynum = K_KP_NUMLOCK; break;\n\t\tcase SDL_SCANCODE_CAPSLOCK: keynum = K_CAPSLOCK; break;\n\t\tcase SDL_SCANCODE_SLASH: keynum = '/'; break;\n\t\tcase SDL_SCANCODE_PERIOD: keynum = '.'; break;\n\t\tcase SDL_SCANCODE_SEMICOLON: keynum = ';'; break;\n\t\tcase SDL_SCANCODE_APOSTROPHE: keynum = '\\''; break;\n\t\tcase SDL_SCANCODE_COMMA: keynum = ','; break;\n\t\tcase SDL_SCANCODE_PRINTSCREEN:\n\t\t{\n\t\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t\t\tbreak;\n\t\t}\n\t\tcase SDL_SCANCODE_PAUSE: keynum = K_PAUSE; break;\n\t\tcase SDL_SCANCODE_SCROLLLOCK: keynum = K_SCROLLLOCK; break;\n\t\tcase SDL_SCANCODE_APPLICATION: keynum = K_WIN; break; // (compose key) ???\n\t\tcase SDL_SCANCODE_VOLUMEUP: keynum = K_AUX32; break;\n\t\tcase SDL_SCANCODE_VOLUMEDOWN: keynum = K_AUX31; break;\n\t\t// don't console spam on known functional buttons, not used in engine\n\t\tcase SDL_SCANCODE_MUTE:\n\t\tcase SDL_SCANCODE_BRIGHTNESSDOWN:\n\t\tcase SDL_SCANCODE_BRIGHTNESSUP:\n\t\tcase SDL_SCANCODE_SELECT:\n\t\t\treturn;\n\t\tcase SDL_SCANCODE_UNKNOWN:\n\t\t{\n\t\t\tif( down ) Con_Reportf( \"%s: Unknown scancode\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\t\tdefault:\n\t\t\tif( down ) Con_Reportf( \"%s: Unknown key: %s = %i\\n\", __func__, SDL_GetScancodeName( keynum ), keynum );\n\t\t\treturn;\n\t\t}\n\t}\n\n#undef DECLARE_KEY_RANGE\n\n\tKey_Event( keynum, down );\n}\n\n/*\n=============\nSDLash_MouseEvent\n\n=============\n*/\nstatic void SDLash_MouseEvent( SDL_MouseButtonEvent button )\n{\n\tint down;\n\n\tif( button.which == SDL_TOUCH_MOUSEID )\n\t\treturn;\n\n\tif( button.state == SDL_RELEASED )\n\t\tdown = 0;\n\telse if( button.clicks >= 2 )\n\t\tdown = 2; // special state for double-click in UI\n\telse\n\t\tdown = 1;\n\n\tswitch( button.button )\n\t{\n\tcase SDL_BUTTON_LEFT:\n\t\tIN_MouseEvent( 0, down );\n\t\tbreak;\n\tcase SDL_BUTTON_RIGHT:\n\t\tIN_MouseEvent( 1, down );\n\t\tbreak;\n\tcase SDL_BUTTON_MIDDLE:\n\t\tIN_MouseEvent( 2, down );\n\t\tbreak;\n\tcase SDL_BUTTON_X1:\n\t\tIN_MouseEvent( 3, down );\n\t\tbreak;\n\tcase SDL_BUTTON_X2:\n\t\tIN_MouseEvent( 4, down );\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( \"Unknown mouse button ID: %d\\n\", button.button );\n\t}\n}\n\n/*\n=============\nSDLash_InputEvent\n\n=============\n*/\nstatic void SDLash_InputEvent( SDL_TextInputEvent input )\n{\n\tconst char *text;\n\n\tVGui_ReportTextInput( input.text );\n\n\tfor( text = input.text; *text; text++ )\n\t{\n\t\tint ch = (byte)*text;\n\n\t\t// do not pass UTF-8 sequence into the engine, convert it here\n\t\tif( !cls.accept_utf8 )\n\t\t\tch = Con_UtfProcessCharForce( ch );\n\n\t\tif( !ch )\n\t\t\tcontinue;\n\n\t\tCL_CharEvent( ch );\n\t}\n}\n\nstatic void SDLash_ActiveEvent( int gain )\n{\n\tif( gain )\n\t{\n\t\thost.status = HOST_FRAME;\n\t\tif( cls.key_dest == key_game )\n\t\t\tIN_ActivateMouse( );\n\n\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t\tif( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN )\n\t\t\tVID_SetMode();\n\t}\n\telse\n\t{\n#if TARGET_OS_IPHONE\n\t\t{\n\t\t\t// Keep running if ftp server enabled\n\t\t\tvoid IOS_StartBackgroundTask( void );\n\t\t\tIOS_StartBackgroundTask();\n\t\t}\n#endif\n\t\thost.status = HOST_NOFOCUS;\n\n\t\tif( cls.key_dest == key_game )\n\t\t{\n\t\t\tKey_ClearStates();\n\t\t\tIN_DeactivateMouse();\n\t\t}\n\n\t\thost.force_draw_version_time = host.realtime + 2.0;\n\t\tVID_RestoreScreenResolution();\n\t}\n}\n\n/*\n=============\nSDLash_EventFilter\n\n=============\n*/\nstatic void SDLash_EventHandler( SDL_Event *event )\n{\n\tswitch ( event->type )\n\t{\n\t/* Mouse events */\n\tcase SDL_MOUSEMOTION:\n\t\tif( host.mouse_visible )\n\t\t\tSDL_GetRelativeMouseState( NULL, NULL );\n\t\tbreak;\n\n\tcase SDL_MOUSEBUTTONUP:\n\tcase SDL_MOUSEBUTTONDOWN:\n\t\tSDLash_MouseEvent( event->button );\n\t\tbreak;\n\n\t/* Keyboard events */\n\tcase SDL_KEYDOWN:\n\tcase SDL_KEYUP:\n\t\tSDLash_KeyEvent( event->key );\n\t\tbreak;\n\n\tcase SDL_QUIT:\n\t\tSys_Quit( \"caught SDL_QUIT\" );\n\t\tbreak;\n\tcase SDL_MOUSEWHEEL:\n\t\tIN_MWheelEvent( event->wheel.y );\n\t\tbreak;\n\n\t/* Touch events */\n\tcase SDL_FINGERDOWN:\n\tcase SDL_FINGERUP:\n\tcase SDL_FINGERMOTION:\n\t{\n\t\tstatic int scale = 0;\n\t\ttouchEventType type;\n\t\tfloat x, y, dx, dy;\n\n\t\tif( event->type == SDL_FINGERDOWN )\n\t\t\ttype = event_down;\n\t\telse if( event->type == SDL_FINGERUP )\n\t\t\ttype = event_up ;\n\t\telse if( event->type == SDL_FINGERMOTION )\n\t\t\ttype = event_motion;\n\t\telse break;\n\n\t\t/*\n\t\tSDL sends coordinates in [0..width],[0..height] values\n\t\ton some devices\n\t\t*/\n\t\tif( !scale )\n\t\t{\n\t\t\tif( ( event->tfinger.x > 0 ) && ( event->tfinger.y > 0 ) )\n\t\t\t{\n\t\t\t\tif( ( event->tfinger.x > 2 ) && ( event->tfinger.y > 2 ) )\n\t\t\t\t{\n\t\t\t\t\tscale = 2;\n\t\t\t\t\tCon_Reportf( \"SDL reports screen coordinates, workaround enabled!\\n\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tscale = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tx = event->tfinger.x;\n\t\ty = event->tfinger.y;\n\t\tdx = event->tfinger.dx;\n\t\tdy = event->tfinger.dy;\n\n\t\tif( scale == 2 )\n\t\t{\n\t\t\tx /= (float)refState.width;\n\t\t\ty /= (float)refState.height;\n\t\t\tdx /= (float)refState.width;\n\t\t\tdy /= (float)refState.height;\n\t\t}\n\n\t\tIN_TouchEvent( type, event->tfinger.fingerId, x, y, dx, dy );\n\t\tbreak;\n\t}\n\n\t/* IME */\n\tcase SDL_TEXTINPUT:\n\t\tSDLash_InputEvent( event->text );\n\t\tbreak;\n\n\t/* GameController API */\n\tcase SDL_CONTROLLERAXISMOTION:\n\tcase SDL_CONTROLLERBUTTONDOWN:\n\tcase SDL_CONTROLLERBUTTONUP:\n\tcase SDL_CONTROLLERDEVICEADDED:\n\tcase SDL_CONTROLLERDEVICEREMOVED:\n#if SDL_VERSION_ATLEAST( 2, 0, 14 )\n\tcase SDL_CONTROLLERTOUCHPADDOWN:\n\tcase SDL_CONTROLLERTOUCHPADMOTION:\n\tcase SDL_CONTROLLERTOUCHPADUP:\n\tcase SDL_CONTROLLERSENSORUPDATE:\n#endif\n\t\tSDLash_HandleGameControllerEvent( event );\n\t\tbreak;\n\n\tcase SDL_WINDOWEVENT:\n\t\tif( event->window.windowID != SDL_GetWindowID( host.hWnd ) )\n\t\t\treturn;\n\n\t\tif( host.status == HOST_SHUTDOWN || Host_IsDedicated() )\n\t\t\tbreak; // no need to activate\n\n\t\tswitch( event->window.event )\n\t\t{\n\t\tcase SDL_WINDOWEVENT_MOVED:\n\t\t{\n\t\t\tchar val[32];\n\n\t\t\tQ_snprintf( val, sizeof( val ), \"%d\", event->window.data1 );\n\t\t\tCvar_DirectSet( &window_xpos, val );\n\n\t\t\tQ_snprintf( val, sizeof( val ), \"%d\", event->window.data2 );\n\t\t\tCvar_DirectSet( &window_ypos, val );\n\n\t\t\tif ( vid_fullscreen.value == WINDOW_MODE_WINDOWED )\n\t\t\t\tCvar_DirectSet( &vid_maximized, \"0\" );\n\t\t\tbreak;\n\t\t}\n\t\tcase SDL_WINDOWEVENT_MINIMIZED:\n\t\t\thost.status = HOST_SLEEP;\n\t\t\tCvar_DirectSet( &vid_maximized, \"0\" );\n\t\t\tVID_RestoreScreenResolution( );\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT_RESTORED:\n\t\t\thost.status = HOST_FRAME;\n\t\t\thost.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME;\n\t\t\tCvar_DirectSet( &vid_maximized, \"0\" );\n\t\t\tif( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN )\n\t\t\t\tVID_SetMode();\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT_FOCUS_GAINED:\n\t\t\tSDLash_ActiveEvent( true );\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT_FOCUS_LOST:\n\t\t\tSDLash_ActiveEvent( false );\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT_RESIZED:\n#if !XASH_MOBILE_PLATFORM\n\t\t\tif( vid_fullscreen.value == WINDOW_MODE_WINDOWED )\n#endif\n\t\t\t{\n\t\t\t\tSDL_Window *wnd = SDL_GetWindowFromID( event->window.windowID );\n\t\t\t\tVID_SaveWindowSize( event->window.data1, event->window.data2,\n\t\t\t\t\tFBitSet( SDL_GetWindowFlags( wnd ), SDL_WINDOW_MAXIMIZED ) != 0 );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SDL_WINDOWEVENT_MAXIMIZED:\n\t\t\tCvar_DirectSet( &vid_maximized, \"1\" );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n=============\nSDLash_RunEvents\n\n=============\n*/\nvoid Platform_RunEvents( void )\n{\n\tSDL_Event event;\n\n\twhile( host.status != HOST_CRASHED && !host.shutdown_issued && SDL_PollEvent( &event ) )\n\t\tSDLash_EventHandler( &event );\n\n#if XASH_PSVITA\n\tPSVita_InputUpdate();\n#endif\n}\n\n/*\n========================\nPlatform_PreCreateMove\n\nthis should disable mouse look on client when m_ignore enabled\nTODO: kill mouse in win32 clients too\n========================\n*/\nvoid Platform_PreCreateMove( void )\n{\n\tif( m_ignore.value )\n\t{\n\t\tSDL_GetRelativeMouseState( NULL, NULL );\n\t\tSDL_ShowCursor( SDL_TRUE );\n\t}\n}\n"
  },
  {
    "path": "engine/platform/sdl2/in_sdl2.c",
    "content": "/*\nin_sdl.c - SDL input component\nCopyright (C) 2018-2025 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n\n#include \"common.h\"\n#include \"keydefs.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"vgui_draw.h\"\n#include \"platform_sdl2.h\"\n#include \"sound.h\"\n#include \"vid_common.h\"\n\nstatic struct\n{\n\tqboolean initialized;\n\tSDL_Cursor *cursors[dc_last];\n} cursors;\n\nstatic struct\n{\n\tint x, y;\n\tqboolean pushed;\n} in_visible_cursor_pos;\n\n/*\n=============\nPlatform_GetMousePos\n\n=============\n*/\nvoid GAME_EXPORT Platform_GetMousePos( int *x, int *y )\n{\n\tSDL_GetMouseState( x, y );\n\n\tif( x && window_width.value && window_width.value != refState.width )\n\t{\n\t\tfloat factor = refState.width / window_width.value;\n\t\t*x = *x * factor;\n\t}\n\n\tif( y && window_height.value && window_height.value != refState.height )\n\t{\n\t\tfloat factor = refState.height / window_height.value;\n\t\t*y = *y * factor;\n\t}\n}\n\n/*\n=============\nPlatform_SetMousePos\n\n============\n*/\nvoid GAME_EXPORT Platform_SetMousePos( int x, int y )\n{\n\tSDL_WarpMouseInWindow( host.hWnd, x, y );\n}\n\n/*\n========================\nPlatform_MouseMove\n\n========================\n*/\nvoid Platform_MouseMove( float *x, float *y )\n{\n\tint m_x, m_y;\n\tSDL_GetRelativeMouseState( &m_x, &m_y );\n\t*x = (float)m_x;\n\t*y = (float)m_y;\n}\n\n/*\n=============\nPlatform_GetClipobardText\n\n=============\n*/\nint Platform_GetClipboardText( char *buffer, size_t size )\n{\n\tint textLength;\n\tchar *sdlbuffer = SDL_GetClipboardText();\n\n\tif( !sdlbuffer )\n\t\treturn 0;\n\n\tif (buffer && size > 0)\n\t{\n\t\ttextLength = Q_strncpy( buffer, sdlbuffer, size );\n\t}\n\telse {\n\t\ttextLength = Q_strlen( sdlbuffer );\n\t}\n\tSDL_free( sdlbuffer );\n\treturn textLength;\n}\n\n/*\n=============\nPlatform_SetClipobardText\n\n=============\n*/\nvoid Platform_SetClipboardText( const char *buffer )\n{\n\tSDL_SetClipboardText( buffer );\n}\n\n#if !XASH_PSVITA\n\n/*\n=============\nSDLash_EnableTextInput\n\n=============\n*/\nvoid Platform_EnableTextInput( qboolean enable )\n{\n\tenable ? SDL_StartTextInput() : SDL_StopTextInput();\n}\n\n#endif // !XASH_PSVITA\n\n/*\n========================\nSDLash_InitCursors\n\n========================\n*/\nvoid SDLash_InitCursors( void )\n{\n\tif( cursors.initialized )\n\t\tSDLash_FreeCursors();\n\n\t// load up all default cursors\n\tcursors.cursors[dc_none] = NULL;\n\tcursors.cursors[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);\n\tcursors.cursors[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);\n\tcursors.cursors[dc_hourglass] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);\n\tcursors.cursors[dc_crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);\n\tcursors.cursors[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);\n\tcursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);\n\tcursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);\n\tcursors.cursors[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);\n\tcursors.cursors[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);\n\tcursors.cursors[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);\n\tcursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);\n\tcursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);\n\tcursors.initialized = true;\n}\n\n/*\n========================\nSDLash_FreeCursors\n\n========================\n*/\nvoid SDLash_FreeCursors( void )\n{\n\tint i = 0;\n\n\tfor( ; i < ARRAYSIZE( cursors.cursors ); i++ )\n\t{\n\t\tif( cursors.cursors[i] )\n\t\t\tSDL_FreeCursor( cursors.cursors[i] );\n\t\tcursors.cursors[i] = NULL;\n\t}\n\n\tcursors.initialized = false;\n}\n\n/*\n========================\nPlatform_SetCursorType\n\n========================\n*/\nvoid Platform_SetCursorType( VGUI_DefaultCursor type )\n{\n\tqboolean visible;\n\n\tswitch( type )\n\t{\n\t\tcase dc_user:\n\t\tcase dc_none:\n\t\t\tvisible = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tvisible = true;\n\t\t\tbreak;\n\t}\n\n\t// never disable cursor in touch emulation mode\n\tif( !visible && Touch_WantVisibleCursor( ))\n\t\treturn;\n\n\thost.mouse_visible = visible;\n\tVGui_UpdateInternalCursorState( type );\n\n\tif( host.mouse_visible )\n\t{\n\t\tif( cursors.initialized )\n\t\t\tSDL_SetCursor( cursors.cursors[type] );\n\n\t\tSDL_ShowCursor( true );\n\n\t\t// restore the last mouse position\n\t\tif( in_visible_cursor_pos.pushed )\n\t\t{\n\t\t\tSDL_WarpMouseInWindow( host.hWnd, in_visible_cursor_pos.x, in_visible_cursor_pos.y );\n\t\t\tin_visible_cursor_pos.pushed = false;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// save last mouse position and warp it to the center\n\t\tif( !in_visible_cursor_pos.pushed )\n\t\t{\n\t\t\tSDL_GetMouseState( &in_visible_cursor_pos.x, &in_visible_cursor_pos.y );\n\t\t\tSDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y );\n\t\t\tin_visible_cursor_pos.pushed = true;\n\t\t}\n\n\t\tSDL_ShowCursor( false );\n\t}\n}\n\n/*\n========================\nPlatform_GetMouseGrab\n========================\n*/\nqboolean Platform_GetMouseGrab( void )\n{\n\treturn SDL_GetWindowGrab( host.hWnd );\n}\n\n/*\n========================\nPlatform_SetMouseGrab\n========================\n*/\nvoid Platform_SetMouseGrab( qboolean enable )\n{\n\tSDL_SetWindowGrab( host.hWnd, enable );\n}\n\n/*\n========================\nPlatform_GetKeyModifiers\n\n========================\n*/\nkey_modifier_t Platform_GetKeyModifiers( void )\n{\n\tSDL_Keymod modFlags;\n\tkey_modifier_t resultFlags;\n\n\tresultFlags = KeyModifier_None;\n\tmodFlags = SDL_GetModState();\n\tif( FBitSet( modFlags, KMOD_LCTRL ))\n\t\tSetBits( resultFlags, KeyModifier_LeftCtrl );\n\tif( FBitSet( modFlags, KMOD_RCTRL ))\n\t\tSetBits( resultFlags, KeyModifier_RightCtrl );\n\tif( FBitSet( modFlags, KMOD_RSHIFT ))\n\t\tSetBits( resultFlags, KeyModifier_RightShift );\n\tif( FBitSet( modFlags, KMOD_LSHIFT ))\n\t\tSetBits( resultFlags, KeyModifier_LeftShift );\n\tif( FBitSet( modFlags, KMOD_LALT ))\n\t\tSetBits( resultFlags, KeyModifier_LeftAlt );\n\tif( FBitSet( modFlags, KMOD_RALT ))\n\t\tSetBits( resultFlags, KeyModifier_RightAlt );\n\tif( FBitSet( modFlags, KMOD_NUM ))\n\t\tSetBits( resultFlags, KeyModifier_NumLock );\n\tif( FBitSet( modFlags, KMOD_CAPS ))\n\t\tSetBits( resultFlags, KeyModifier_CapsLock );\n\tif( FBitSet( modFlags, KMOD_RGUI ))\n\t\tSetBits( resultFlags, KeyModifier_RightSuper );\n\tif( FBitSet( modFlags, KMOD_LGUI ))\n\t\tSetBits( resultFlags, KeyModifier_LeftSuper );\n\n\treturn resultFlags;\n}\n"
  },
  {
    "path": "engine/platform/sdl2/joy_sdl2.c",
    "content": "/*\njoy_sdl.c - SDL gamepads\nCopyright (C) 2018-2025 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n\n#include \"common.h\"\n#include \"keydefs.h\"\n#include \"input.h\"\n#include \"client.h\"\n#include \"platform_sdl2.h\"\n\nstatic const int g_button_mapping[] =\n{\n#if XASH_NSWITCH // devkitPro/SDL has inverted Nintendo layout for SDL_GameController\n\tK_B_BUTTON, K_A_BUTTON, K_Y_BUTTON, K_X_BUTTON,\n#else\n\tK_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON,\n#endif\n\tK_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON,\n\tK_LSTICK, K_RSTICK,\n\tK_L1_BUTTON, K_R1_BUTTON,\n\tK_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT,\n\tK_MISC_BUTTON,\n\tK_PADDLE1_BUTTON, K_PADDLE2_BUTTON, K_PADDLE3_BUTTON, K_PADDLE4_BUTTON,\n\tK_TOUCHPAD,\n};\n\n// Swap axis to follow default axis binding:\nstatic const engineAxis_t g_axis_mapping[] =\n{\n\tJOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX,\n\tJOY_AXIS_FWD, // SDL_CONTROLLER_AXIS_LEFTY,\n\tJOY_AXIS_PITCH, // SDL_CONTROLLER_AXIS_RIGHTX,\n\tJOY_AXIS_YAW, // SDL_CONTROLLER_AXIS_RIGHTY,\n\tJOY_AXIS_LT, // SDL_CONTROLLER_AXIS_TRIGGERLEFT,\n\tJOY_AXIS_RT, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT,\n};\n\nstatic SDL_JoystickID g_current_gamepad_id = -1; // used to send rumble to\nstatic SDL_GameController *g_current_gamepad;\nstatic SDL_GameController **g_gamepads;\nstatic size_t g_num_gamepads;\n\n#define CALIBRATION_TIME 10.0f\n\nstatic struct\n{\n\tfloat  time;\n\tvec3_t data;\n\tvec3_t calibrated_values;\n\tint    samples;\n} gyrocal;\n\nstatic void SDLash_RestartCalibration( void )\n{\n\tJoy_SetCalibrationState( JOY_NOT_CALIBRATED );\n\n\tmemset( &gyrocal, 0, sizeof( gyrocal ));\n\n\tgyrocal.time = host.realtime + CALIBRATION_TIME;\n\n\tCon_Printf( \"Starting gyroscope calibration...\\n\" );\n}\n\nstatic void SDLash_FinalizeCalibration( void )\n{\n\tfloat data_rate = 10.0f; // let's say we're polling at 10Hz?\n\tint min_samples;\n\n#if SDL_VERSION_ATLEAST( 2, 0, 16 )\n\tdata_rate = SDL_GameControllerGetSensorDataRate( g_current_gamepad, SDL_SENSOR_GYRO );\n\tif( !data_rate )\n\t\tdata_rate = 10.0f;\n#endif\n\n\tmin_samples = Q_rint( CALIBRATION_TIME * data_rate * 0.75f );\n\n\t// we waited for few seconds and got too few samples\n\tif( gyrocal.samples <= min_samples )\n\t{\n\t\tJoy_SetCalibrationState( JOY_FAILED_TO_CALIBRATE );\n\t\treturn;\n\t}\n\n\tVectorScale( gyrocal.data, 1.0f / gyrocal.samples, gyrocal.calibrated_values );\n\tJoy_SetCalibrationState( JOY_CALIBRATED );\n\n\tCon_Printf( \"Calibration done. Result: %f %f %f\\n\", gyrocal.calibrated_values[0], gyrocal.calibrated_values[1], gyrocal.calibrated_values[2] );\n\n\tgyrocal.time = 0.0f;\n}\n\nstatic void SDLash_GameControllerAddMappings( const char *name )\n{\n\tfs_offset_t len = 0;\n\tbyte *p = FS_LoadFile( name, &len, false );\n\n\tif( !p )\n\t\treturn;\n\n\tif( len > 0 && len < INT32_MAX ) // function accepts int, SDL3 fixes this\n\t{\n\t\tSDL_RWops *rwops = SDL_RWFromConstMem( p, (int)len );\n\t\tSDL_GameControllerAddMappingsFromRW( rwops, true );\n\t}\n\n\tMem_Free( p );\n}\n\nstatic void SDLash_SetActiveGameController( SDL_JoystickID id )\n{\n\tif( g_current_gamepad_id == id )\n\t\treturn;\n\n#if SDL_VERSION_ATLEAST( 2, 0, 14 )\n\t// going to change active controller, disable gyro events in old\n\tSDL_GameControllerSetSensorEnabled( g_current_gamepad, SDL_SENSOR_GYRO, SDL_FALSE );\n#endif // SDL_VERSION_ATLEAST( 2, 0, 14 )\n\n\tg_current_gamepad_id = id;\n\n\tif( id < 0 )\n\t{\n\t\tg_current_gamepad = NULL;\n\t\tJoy_SetCapabilities( false );\n\t\tJoy_SetCalibrationState( JOY_NOT_CALIBRATED );\n\t}\n\telse\n\t{\n\t\tqboolean have_gyro = false;\n\n\t\tg_current_gamepad = SDL_GameControllerFromInstanceID( id );\n\n#if SDL_VERSION_ATLEAST( 2, 0, 14 )\n\t\thave_gyro = SDL_GameControllerHasSensor( g_current_gamepad, SDL_SENSOR_GYRO );\n\n\t\tif( have_gyro )\n\t\t{\n\t\t\tSDL_GameControllerSetSensorEnabled( g_current_gamepad, SDL_SENSOR_GYRO, SDL_TRUE );\n\t\t\tSDLash_RestartCalibration();\n\t\t}\n#endif // SDL_VERSION_ATLEAST( 2, 0, 14 )\n\n\t\tJoy_SetCapabilities( have_gyro );\n\t}\n}\n\nstatic void SDLash_GameControllerAdded( int device_index )\n{\n\tSDL_GameController *gc;\n\tSDL_GameController **list;\n\n\tgc = SDL_GameControllerOpen( device_index );\n\tif( !gc )\n\t{\n\t\tCon_Printf( S_ERROR \"SDL_GameControllerOpen: %s\\n\", SDL_GetError( ));\n\t\treturn;\n\t}\n\n\t// this \"game controller\" only exists on Android within emulator and tries to map\n\t// keyboard events into game controller events, which as you can expect, doesn't\n\t// work and I don't understand the intention here. When debugging Xash in Android\n\t// Studio emulator, just enable hardware input passthrough.\n#if XASH_ANDROID\n\tif( !Q_strcmp( SDL_GameControllerName( gc ), \"qwerty2\" ))\n\t{\n\t\tSDL_GameControllerClose( gc );\n\t\treturn;\n\t}\n#endif // XASH_ANDROID\n\n\tlist = Mem_Realloc( host.mempool, g_gamepads, sizeof( *list ) * ( g_num_gamepads + 1 ));\n\tlist[g_num_gamepads++] = gc;\n\n\tg_gamepads = list;\n\n\t// set as current device if none other set\n\tif( g_current_gamepad_id < 0 )\n\t{\n\t\tSDL_Joystick *joy = SDL_GameControllerGetJoystick( gc );\n\n\t\tif( joy )\n\t\t\tSDLash_SetActiveGameController( SDL_JoystickInstanceID( joy ));\n\t}\n\n\tCon_Printf( \"Detected \\\"%s\\\" game controller.\\nMapping string: %s\\n\", SDL_GameControllerName( gc ), SDL_GameControllerMapping( gc ));\n}\n\nstatic void SDLash_GameControllerRemoved( SDL_JoystickID id )\n{\n\tsize_t i;\n\n\tif( id == g_current_gamepad_id )\n\t\tSDLash_SetActiveGameController( -1 );\n\n\t// now close the device\n\tfor( i = 0; i < g_num_gamepads; i++ )\n\t{\n\t\tSDL_GameController *gc = g_gamepads[i];\n\t\tSDL_Joystick *joy;\n\n\t\tif( !gc )\n\t\t\tcontinue;\n\n\t\tjoy = SDL_GameControllerGetJoystick( gc );\n\n\t\tif( !joy )\n\t\t\tcontinue;\n\n\t\tif( SDL_JoystickInstanceID( joy ) == id )\n\t\t{\n\t\t\tCon_Printf( \"Game controller \\\"%s\\\" was disconnected\\n\", SDL_GameControllerName( gc ));\n\n\t\t\tSDL_GameControllerClose( gc );\n\t\t\tg_gamepads[i] = NULL;\n\t\t}\n\t}\n}\n\n#if SDL_VERSION_ATLEAST( 2, 0, 14 )\nstatic void SDLash_GameControllerSensorUpdate( SDL_ControllerSensorEvent sensor )\n{\n\tvec3_t data;\n\n\tif( sensor.which != g_current_gamepad_id )\n\t\treturn;\n\n\tif( sensor.sensor != SDL_SENSOR_GYRO )\n\t\treturn;\n\n\tif( gyrocal.time != 0.0f )\n\t{\n\t\tif( host.realtime > gyrocal.time )\n\t\t\tSDLash_FinalizeCalibration();\n\n\t\tVectorAdd( gyrocal.data, sensor.data, gyrocal.data );\n\t\tgyrocal.samples++;\n\n\t\tJoy_SetCalibrationState( JOY_CALIBRATING );\n\t\treturn;\n\t}\n\n\tVectorSubtract( sensor.data, gyrocal.calibrated_values, data );\n\tJoy_GyroEvent( data );\n}\n#endif\n\nvoid SDLash_HandleGameControllerEvent( SDL_Event *ev )\n{\n\tint x;\n\n\tswitch( ev->type )\n\t{\n\tcase SDL_CONTROLLERAXISMOTION:\n\t\tSDLash_SetActiveGameController( ev->caxis.which );\n\t\tx = ev->caxis.axis;\n\t\tif( x >= 0 && x < ARRAYSIZE( g_axis_mapping ))\n\t\t\tJoy_AxisMotionEvent( g_axis_mapping[x], ev->caxis.value );\n\t\tbreak;\n\tcase SDL_CONTROLLERBUTTONDOWN:\n\tcase SDL_CONTROLLERBUTTONUP:\n\t\tSDLash_SetActiveGameController( ev->cbutton.which );\n\t\tx = ev->cbutton.button;\n\t\tif( x >= 0 && x < ARRAYSIZE( g_button_mapping ))\n\t\t\tKey_Event( g_button_mapping[x], ev->cbutton.state );\n\t\tbreak;\n\tcase SDL_CONTROLLERDEVICEREMOVED:\n\t\tSDLash_GameControllerRemoved( ev->cdevice.which );\n\t\tbreak;\n\tcase SDL_CONTROLLERDEVICEADDED:\n\t\tSDLash_GameControllerAdded( ev->cdevice.which );\n\t\tbreak;\n#if SDL_VERSION_ATLEAST( 2, 0, 14 )\n\tcase SDL_CONTROLLERSENSORUPDATE:\n\t\tSDLash_GameControllerSensorUpdate( ev->csensor );\n\t\tbreak;\n#endif\n\t}\n}\n\nvoid Platform_CalibrateGamepadGyro( void )\n{\n\tSDLash_RestartCalibration();\n}\n\nvoid Platform_Vibrate2( float time, int val1, int val2, uint flags )\n{\n#if SDL_VERSION_ATLEAST( 2, 0, 9 )\n\tSDL_GameController *gc = g_current_gamepad;\n\tUint32 ms;\n\n\tif( g_current_gamepad_id < 0 || !gc )\n\t\treturn;\n\n\tif( val1 < 0 )\n\t\tval1 = COM_RandomLong( 0x7FFF, 0xFFFF );\n\n\tif( val2 < 0 )\n\t\tval2 = COM_RandomLong( 0x7FFF, 0xFFFF );\n\n\tms = (Uint32)ceil( time );\n\tSDL_GameControllerRumble( gc, val1, val2, ms );\n#endif // SDL_VERSION_ATLEAST( 2, 0, 9 )\n}\n\n/*\n=============\nPlatform_Vibrate\n\n=============\n*/\nvoid Platform_Vibrate( float time, char flags )\n{\n\tPlatform_Vibrate2( time, -1, -1, flags );\n}\n\n/*\n=============\nPlatform_JoyInit\n\n=============\n*/\nint Platform_JoyInit( void )\n{\n\tint count, numJoysticks, i;\n\n\tCon_Reportf( \"Joystick: SDL GameController API\\n\" );\n\tif( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER &&\n\t\tSDL_InitSubSystem( SDL_INIT_GAMECONTROLLER ))\n\t{\n\t\tCon_Reportf( \"Failed to initialize SDL GameController API: %s\\n\", SDL_GetError( ));\n\t\treturn 0;\n\t}\n\n\tSDLash_GameControllerAddMappings( \"gamecontrollerdb.txt\" ); // shipped in extras.pk3\n\tSDLash_GameControllerAddMappings( \"controllermappings.txt\" );\n\n\tcount = 0;\n\tnumJoysticks = SDL_NumJoysticks();\n\tfor ( i = 0; i < numJoysticks; i++ )\n\t{\n\t\tif( SDL_IsGameController( i ))\n\t\t\t++count;\n\t}\n\n\treturn count;\n}\n\n/*\n=============\nPlatform_JoyShutdown\n\n=============\n*/\nvoid Platform_JoyShutdown( void )\n{\n\tsize_t i;\n\n\tSDLash_SetActiveGameController( -1 );\n\n\tfor( i = 0; i < g_num_gamepads; i++ )\n\t{\n\t\tif( !g_gamepads[i] )\n\t\t\tcontinue;\n\n\t\tSDL_GameControllerClose( g_gamepads[i] );\n\t\tg_gamepads[i] = NULL;\n\t}\n\n\tMem_Free( g_gamepads );\n\tg_gamepads = NULL;\n\tg_num_gamepads = 0;\n\n\tSDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER );\n}\n"
  },
  {
    "path": "engine/platform/sdl2/platform_sdl2.h",
    "content": "/*\nplatform_sdl2.h - SDL backend internal header\nCopyright (C) 2015-2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n#ifndef KEYWRAPPER_H\n#define KEYWRAPPER_H\n#ifdef  XASH_SDL\n\n#include \"platform.h\"\n\n// window management\nvoid VID_RestoreScreenResolution( void );\nqboolean  VID_CreateWindow( int width, int height, window_mode_t window_mode );\nvoid      VID_DestroyWindow( void );\nvoid GL_InitExtensions( void );\nqboolean GL_DeleteContext( void );\nvoid VID_SaveWindowSize( int width, int height, qboolean maximized );\n\n//\n// in_sdl.c\n//\nvoid SDLash_InitCursors( void );\nvoid SDLash_FreeCursors( void );\n\n//\n// joy_sdl.c\n//\nvoid SDLash_HandleGameControllerEvent( SDL_Event *ev );\n\n#endif // XASH_SDL\n#endif // KEYWRAPPER_H\n"
  },
  {
    "path": "engine/platform/sdl2/s_sdl2.c",
    "content": "/*\ns_backend.c - sound hardware output\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"platform.h\"\n#if XASH_SOUND == SOUND_SDL\n\n#include \"sound.h\"\n#include \"voice.h\"\n\n#include <SDL.h>\n#include <stdlib.h>\n\n#define SAMPLE_16BIT_SHIFT 1\n#define SECONDARY_BUFFER_SIZE 0x10000\n\n#define SDLash_IsAudioError( x ) (( x ) == 0)\n\n/*\n=======================================================================\nGlobal variables. Must be visible to window-procedure function\nso it can unlock and free the data block after it has been played.\n=======================================================================\n*/\nstatic int sdl_dev;\nstatic SDL_AudioDeviceID in_dev = 0;\nstatic SDL_AudioFormat sdl_format;\nstatic char sdl_backend_name[32];\n\nstatic void SDL_SoundCallback( void *userdata, Uint8 *stream, int len )\n{\n\tconst int size = dma.samples << 1;\n\tint pos;\n\tint wrapped;\n\n\tpos = dma.samplepos << 1;\n\tif( pos >= size )\n\t\tpos = dma.samplepos = 0;\n\n\twrapped = pos + len - size;\n\n\tif( wrapped < 0 )\n\t{\n\t\tmemcpy( stream, dma.buffer + pos, len );\n\t\tdma.samplepos += len >> 1;\n\t}\n\telse\n\t{\n\t\tint remaining = size - pos;\n\n\t\tmemcpy( stream, dma.buffer + pos, remaining );\n\t\tmemcpy( stream + remaining, dma.buffer, wrapped );\n\t\tdma.samplepos = wrapped >> 1;\n\t}\n\n\tif( dma.samplepos >= size )\n\t\tdma.samplepos = 0;\n}\n\n/*\n==================\nSNDDMA_Init\n\nTry to find a sound device to mix for.\nReturns false if nothing is found.\n==================\n*/\nqboolean SNDDMA_Init( void )\n{\n\tSDL_AudioSpec desired, obtained;\n\tint samplecount;\n\tconst char *driver = NULL;\n\n\t// Modders often tend to use proprietary crappy solutions\n\t// like FMOD to play music, sometimes even with versions outdated by a few decades!\n\t//\n\t// As these bullshit sound engines prefer to use DirectSound, we ask SDL2 to do\n\t// the same. Why you might ask? If SDL2 uses another audio API, like WASAPI on\n\t// more modern versions of Windows, it breaks the logic inside Windows, and the whole\n\t// application could hang in WaitFor{Single,Multiple}Object function, either called by\n\t// SDL2 if FMOD was shut down first, or deep in dsound.dll->fmod.dll if SDL2 audio\n\t// was shut down first.\n\t//\n\t// I honestly don't know who is the real culprit here: FMOD, HL modders, Windows, SDL2\n\t// or us.\n\t//\n\t// Also, fun note, GoldSrc seems doesn't use SDL2 for sound stuff at all, as nothing\n\t// reference SDL audio functions there. It's probably has DirectSound backend, that's\n\t// why modders never stumble upon this bug.\n#if XASH_WIN32\n\tdriver = \"directsound\";\n\n\tif( SDL_getenv( \"SDL_AUDIODRIVER\" ))\n\t\tdriver = NULL; // let SDL2 and user decide\n\n\tSDL_SetHint( SDL_HINT_AUDIODRIVER, driver );\n#endif // XASH_WIN32\n\n\t// even if we don't have PA\n\t// we still can safely set env variables\n\tSDL_setenv( \"PULSE_PROP_application.name\", GI->title, 1 );\n\tSDL_setenv( \"PULSE_PROP_media.role\", \"game\", 1 );\n\n\t// reinitialize SDL with our driver just in case\n\tif( SDL_WasInit( SDL_INIT_AUDIO ))\n\t\tSDL_QuitSubSystem( SDL_INIT_AUDIO );\n\n\tif( SDL_InitSubSystem( SDL_INIT_AUDIO ))\n\t{\n\t\tCon_Reportf( S_ERROR \"Audio: SDL: %s \\n\", SDL_GetError( ) );\n\t\treturn false;\n\t}\n\n\tmemset( &desired, 0, sizeof( desired ) );\n\tdesired.freq     = SOUND_DMA_SPEED;\n\tdesired.format   = AUDIO_S16LSB;\n\tdesired.samples  = 1024;\n\tdesired.channels = 2;\n\tdesired.callback = SDL_SoundCallback;\n\n\tsdl_dev = SDL_OpenAudioDevice( NULL, 0, &desired, &obtained, 0 );\n\n\tif( SDLash_IsAudioError( sdl_dev ))\n\t{\n\t\tCon_Printf( \"Couldn't open SDL audio: %s\\n\", SDL_GetError( ) );\n\t\treturn false;\n\t}\n\n\tif( obtained.format != AUDIO_S16LSB )\n\t{\n\t\tCon_Printf( \"SDL audio format %d unsupported.\\n\", obtained.format );\n\t\tgoto fail;\n\t}\n\n\tif( obtained.channels != 1 && obtained.channels != 2 )\n\t{\n\t\tCon_Printf( \"SDL audio channels %d unsupported.\\n\", obtained.channels );\n\t\tgoto fail;\n\t}\n\n\tdma.format.speed    = obtained.freq;\n\tdma.format.channels = obtained.channels;\n\tdma.format.width    = 2;\n\tsamplecount = s_samplecount.value;\n\tif( !samplecount )\n\t\tsamplecount = 0x8000;\n\tdma.samples         = samplecount * obtained.channels;\n\tdma.buffer          = Mem_Calloc( sndpool, dma.samples * 2 );\n\tdma.samplepos       = 0;\n\n\tsdl_format = obtained.format;\n\n\tCon_Printf( \"Using SDL audio driver: %s @ %d Hz\\n\", SDL_GetCurrentAudioDriver( ), obtained.freq );\n\tQ_snprintf( sdl_backend_name, sizeof( sdl_backend_name ), \"SDL (%s)\", SDL_GetCurrentAudioDriver( ));\n\tdma.initialized = true;\n\tdma.backendName = sdl_backend_name;\n\n\tSNDDMA_Activate( true );\n\n\treturn true;\n\nfail:\n\tSNDDMA_Shutdown( );\n\treturn false;\n}\n\n\n/*\n==============\nSNDDMA_BeginPainting\n\nMakes sure dma.buffer is valid\n===============\n*/\nvoid SNDDMA_BeginPainting( void )\n{\n\tSDL_LockAudioDevice( sdl_dev );\n}\n\n/*\n==============\nSNDDMA_Submit\n\nSend sound to device if buffer isn't really the dma buffer\nAlso unlocks the dsound buffer\n===============\n*/\nvoid SNDDMA_Submit( void )\n{\n\tSDL_UnlockAudioDevice( sdl_dev );\n}\n\n/*\n==============\nSNDDMA_Shutdown\n\nReset the sound device for exiting\n===============\n*/\nvoid SNDDMA_Shutdown( void )\n{\n\tCon_Printf( \"Shutting down audio.\\n\" );\n\tdma.initialized = false;\n\n\tif( sdl_dev )\n\t{\n\t\tSNDDMA_Activate( false );\n\n#if !XASH_EMSCRIPTEN\n\t\tSDL_CloseAudioDevice( sdl_dev );\n#endif\n\t}\n\n#if !XASH_EMSCRIPTEN\n\tif( SDL_WasInit( SDL_INIT_AUDIO ))\n\t\tSDL_QuitSubSystem( SDL_INIT_AUDIO );\n#endif\n\n\tif( dma.buffer )\n\t{\n\t\tMem_Free( dma.buffer );\n\t\tdma.buffer = NULL;\n\t}\n}\n\n/*\n===========\nSNDDMA_Activate\nCalled when the main window gains or loses focus.\nThe window have been destroyed and recreated\nbetween a deactivate and an activate.\n===========\n*/\nvoid SNDDMA_Activate( qboolean active )\n{\n\tif( !dma.initialized )\n\t\treturn;\n\n\tSDL_PauseAudioDevice( sdl_dev, !active );\n}\n\n/*\n===========\nSDL_SoundInputCallback\n===========\n*/\nstatic void SDL_SoundInputCallback( void *userdata, Uint8 *stream, int len )\n{\n\tint size = Q_min( len, sizeof( voice.input_buffer ) - voice.input_buffer_pos );\n\n\t// engine can't keep up, skip audio\n\tif( !size )\n\t\treturn;\n\n\tmemcpy( voice.input_buffer + voice.input_buffer_pos, stream, size );\n\tvoice.input_buffer_pos += size;\n}\n\n/*\n===========\nVoiceCapture_Init\n===========\n*/\nqboolean VoiceCapture_Init( void )\n{\n\tSDL_AudioSpec wanted, spec;\n\n\tif( !SDLash_IsAudioError( in_dev ))\n\t{\n\t\tVoiceCapture_Shutdown();\n\t}\n\n\tSDL_zero( wanted );\n\twanted.freq = voice.samplerate;\n\twanted.format = AUDIO_S16LSB;\n\twanted.channels = VOICE_PCM_CHANNELS;\n\twanted.samples = voice.frame_size;\n\twanted.callback = SDL_SoundInputCallback;\n\n\tin_dev = SDL_OpenAudioDevice( NULL, SDL_TRUE, &wanted, &spec, 0 );\n\n\tif( SDLash_IsAudioError( in_dev ))\n\t{\n\t\tCon_Printf( \"%s: error creating capture device (%s)\\n\", __func__, SDL_GetError() );\n\t\treturn false;\n\t}\n\t\t\n\tCon_Printf( S_NOTE \"%s: capture device creation success (%i: %s)\\n\", __func__, in_dev, SDL_GetAudioDeviceName( in_dev, SDL_TRUE ) );\n\treturn true;\n}\n\n/*\n===========\nVoiceCapture_Activate\n===========\n*/\nqboolean VoiceCapture_Activate( qboolean activate )\n{\n\tif( SDLash_IsAudioError( in_dev ))\n\t\treturn false;\n\n\tSDL_PauseAudioDevice( in_dev, activate ? SDL_FALSE : SDL_TRUE );\n\treturn true;\n}\n\n/*\n===========\nVoiceCapture_Lock\n===========\n*/\nqboolean VoiceCapture_Lock( qboolean lock )\n{\n\tif( SDLash_IsAudioError( in_dev ))\n\t\treturn false;\n\n\tif( lock ) SDL_LockAudioDevice( in_dev );\n\telse SDL_UnlockAudioDevice( in_dev );\n\n\treturn true;\n}\n\n/*\n==========\nVoiceCapture_Shutdown\n==========\n*/\nvoid VoiceCapture_Shutdown( void )\n{\n\tif( SDLash_IsAudioError( in_dev ))\n\t\treturn;\n\n\tSDL_CloseAudioDevice( in_dev );\n\tin_dev = 0;\n}\n\n#endif // XASH_SOUND == SOUND_SDL\n"
  },
  {
    "path": "engine/platform/sdl2/sys_sdl2.c",
    "content": "/*\nsys_sdl.c - SDL2 system utils\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <SDL.h>\n#include \"platform.h\"\n#include \"platform_sdl2.h\"\n\n#if XASH_TIMER == TIMER_SDL\ndouble Platform_DoubleTime( void )\n{\n\tstatic Uint64 g_PerformanceFrequency;\n\tstatic Uint64 g_ClockStart;\n\tUint64 CurrentTime;\n\n\tif( !g_PerformanceFrequency )\n\t{\n\t\tg_PerformanceFrequency = SDL_GetPerformanceFrequency();\n\t\tg_ClockStart = SDL_GetPerformanceCounter();\n\t}\n\tCurrentTime = SDL_GetPerformanceCounter();\n\treturn (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency );\n}\n\nvoid Platform_Sleep( int msec )\n{\n\tSDL_Delay( msec );\n}\n#endif // XASH_TIMER == TIMER_SDL\n\n#if XASH_MESSAGEBOX == MSGBOX_SDL\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )\n{\n\tSDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL );\n}\n#endif // XASH_MESSAGEBOX == MSGBOX_SDL\n\nstatic const char *SDLash_CategoryToString( int category )\n{\n\tswitch( category )\n\t{\n\tcase SDL_LOG_CATEGORY_APPLICATION: return \"App\";\n\tcase SDL_LOG_CATEGORY_ERROR: return \"Error\";\n\tcase SDL_LOG_CATEGORY_ASSERT: return \"Assert\";\n\tcase SDL_LOG_CATEGORY_SYSTEM: return \"System\";\n\tcase SDL_LOG_CATEGORY_AUDIO: return \"Audio\";\n\tcase SDL_LOG_CATEGORY_VIDEO: return \"Video\";\n\tcase SDL_LOG_CATEGORY_RENDER: return \"Render\";\n\tcase SDL_LOG_CATEGORY_INPUT: return \"Input\";\n\tcase SDL_LOG_CATEGORY_TEST: return \"Test\";\n\tdefault: return \"Unknown\";\n\t}\n}\n\nstatic void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message )\n{\n\tswitch( priority )\n\t{\n\tcase SDL_LOG_PRIORITY_CRITICAL:\n\tcase SDL_LOG_PRIORITY_ERROR:\n\t\tCon_Printf( S_ERROR S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tcase SDL_LOG_PRIORITY_WARN:\n\t\tCon_DPrintf( S_WARN S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tcase SDL_LOG_PRIORITY_INFO:\n\t\tCon_Reportf( S_NOTE S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tdefault:\n\t\tCon_Reportf( S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\t}\n}\n\nvoid SDLash_Init( const char *basedir )\n{\n#if XASH_APPLE\n\tchar *path = SDL_GetBasePath();\n\tif( path != NULL )\n\t{\n\t\tchar buf[MAX_VA_STRING];\n\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s%s/extras.pk3\", path, basedir );\n\t\tsetenv( \"XASH3D_EXTRAS_PAK1\", buf, true );\n\t}\n#endif\n\n\tSDL_LogSetOutputFunction( SDLash_LogOutputFunction, NULL );\n\n\tif( host_developer.value >= 2 )\n\t\tSDL_LogSetAllPriority( SDL_LOG_PRIORITY_VERBOSE );\n\telse if( host_developer.value >= 1 )\n\t\tSDL_LogSetAllPriority( SDL_LOG_PRIORITY_WARN );\n\telse\n\t\tSDL_LogSetAllPriority( SDL_LOG_PRIORITY_ERROR );\n\n#ifndef SDL_INIT_EVENTS\n#define SDL_INIT_EVENTS 0\n#endif\n\tif( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS ) )\n\t{\n\t\tSys_Warn( \"SDL_Init failed: %s\", SDL_GetError() );\n\t\thost.type = HOST_DEDICATED;\n\t}\n\n#if SDL_MAJOR_VERSION >= 2\n\tSDL_SetHint( SDL_HINT_ACCELEROMETER_AS_JOYSTICK, \"0\" );\n\n#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS\n\tSDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, \"0\" );\n#endif // SDL_HINT_MOUSE_TOUCH_EVENTS\n\tSDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\" );\n\n\tSDL_StopTextInput();\n#endif // XASH_SDL == 2\n\n\tSDLash_InitCursors();\n}\n\nvoid SDLash_Shutdown( void )\n{\n\tSDLash_FreeCursors();\n\n\tSDL_Quit();\n}\n"
  },
  {
    "path": "engine/platform/sdl2/vid_sdl2.c",
    "content": "/*\nvid_sdl.c - SDL vid component\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include <SDL.h>\n#include <SDL_config.h>\n#include <SDL_vulkan.h>\n#include \"common.h\"\n#include \"client.h\"\n#include \"vid_common.h\"\n#include \"platform_sdl2.h\"\n\n// include it after because it breaks definitions in net_api.h wtf\n#include <SDL_syswm.h>\n\nstatic vidmode_t *vidmodes = NULL;\nstatic int num_vidmodes = 0;\nstatic void GL_SetupAttributes( void );\nstruct\n{\n\tint prev_width, prev_height;\n} sdlState = { 640, 480 };\n\nstruct\n{\n\tSDL_Renderer *renderer;\n\tSDL_Texture *tex;\n\tint width, height;\n\tSDL_Surface *surf;\n\tSDL_Surface *win;\n} sw;\n\nvoid Platform_Minimize_f( void )\n{\n\tif( host.hWnd )\n\t\tSDL_MinimizeWindow( host.hWnd );\n}\n\nqboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tsw.width = width;\n\tsw.height = height;\n\n\tif( sw.renderer )\n\t{\n\t\tunsigned int format = SDL_GetWindowPixelFormat( host.hWnd );\n\t\tSDL_RenderSetLogicalSize( sw.renderer, refState.width, refState.height );\n\n\t\tif( sw.tex )\n\t\t{\n\t\t\tSDL_DestroyTexture( sw.tex );\n\t\t\tsw.tex = NULL;\n\t\t}\n\n\t\t// guess\n\t\tif( format == SDL_PIXELFORMAT_UNKNOWN )\n\t\t{\n\t\t\tif( refState.desktopBitsPixel == 16 )\n\t\t\t\tformat = SDL_PIXELFORMAT_RGB565;\n\t\t\telse\n\t\t\t\tformat = SDL_PIXELFORMAT_RGBA8888;\n\t\t}\n\n\t\t// we can only copy fast 16 or 32 bits\n\t\t// SDL_Renderer does not allow zero-copy, so 24 bits will be ineffective\n\t\tif( SDL_BYTESPERPIXEL( format ) != 2 && SDL_BYTESPERPIXEL( format ) != 4 )\n\t\t\tformat = SDL_PIXELFORMAT_RGBA8888;\n\n\t\tsw.tex = SDL_CreateTexture( sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height );\n\n\t\t// fallback\n\t\tif( !sw.tex && format != SDL_PIXELFORMAT_RGBA8888 )\n\t\t{\n\t\t\tformat = SDL_PIXELFORMAT_RGBA8888;\n\t\t\tsw.tex = SDL_CreateTexture( sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height );\n\t\t}\n\n\t\tif( !sw.tex )\n\t\t{\n\t\t\tSDL_DestroyRenderer( sw.renderer );\n\t\t\tsw.renderer = NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvoid *pixels;\n\t\t\tint pitch;\n\n\t\t\tif( !SDL_LockTexture( sw.tex, NULL, &pixels, &pitch ))\n\t\t\t{\n\t\t\t\tint bits;\n\t\t\t\tuint amask;\n\n\t\t\t\t// lock successfull, release\n\t\t\t\tSDL_UnlockTexture( sw.tex );\n\n\t\t\t\t// enough for building blitter tables\n\t\t\t\tSDL_PixelFormatEnumToMasks( format, &bits, r, g, b, &amask );\n\t\t\t\t*bpp = SDL_BYTESPERPIXEL( format );\n\t\t\t\t*stride = pitch / *bpp;\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// fallback to surf\n\t\t\tSDL_DestroyTexture( sw.tex );\n\t\t\tsw.tex = NULL;\n\t\t\tSDL_DestroyRenderer( sw.renderer );\n\t\t\tsw.renderer = NULL;\n\t\t}\n\t}\n\n\tif( !sw.renderer )\n\t{\n\t\tsw.win = SDL_GetWindowSurface( host.hWnd );\n\n\t\t// sdl will create renderer if hw framebuffer unavailiable, so cannot fallback here\n\t\t// if it is failed, it is not possible to draw with SDL in REF_SOFTWARE mode\n\t\tif( !sw.win )\n\t\t{\n\t\t\tSys_Warn( \"failed to initialize software output, try running with -glblit flag\" );\n\t\t\treturn false;\n\t\t}\n\n\t\t*bpp = sw.win->format->BytesPerPixel;\n\t\t*r = sw.win->format->Rmask;\n\t\t*g = sw.win->format->Gmask;\n\t\t*b = sw.win->format->Bmask;\n\t\t*stride = sw.win->pitch / sw.win->format->BytesPerPixel;\n\n\t\t/// TODO: check somehow if ref_soft can handle native format\n#if 0\n\t\t{\n\t\t\tsw.surf = SDL_CreateRGBSurfaceWithFormat( 0, width, height, 16, SDL_PIXELFORMAT_RGB565 );\n\t\t\tif( !sw.surf )\n\t\t\t\tSys_Error( \"%s: %s\", __func__, SDL_GetError( ));\n\t\t}\n#endif\n\t\treturn true;\n\t}\n\n\t// we can't create ref_soft buffer\n\treturn false;\n}\n\nvoid *SW_LockBuffer( void )\n{\n\tif( sw.renderer )\n\t{\n\t\tvoid *pixels;\n\t\tint stride;\n\n\t\tif( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) < 0 )\n\t\t\tSys_Error( \"%s: %s\", __func__, SDL_GetError( ));\n\t\treturn pixels;\n\t}\n\n\t// ensure it not changed (do we really need this?)\n\tsw.win = SDL_GetWindowSurface( host.hWnd );\n\t//if( !sw.win )\n\t\t//SDL_GetWindowSurface( host.hWnd );\n\n\t// prevent buffer overrun\n\tif( !sw.win || sw.win->w < sw.width || sw.win->h < sw.height  )\n\t\treturn NULL;\n\n\tif( sw.surf )\n\t{\n\t\tSDL_LockSurface( sw.surf );\n\t\treturn sw.surf->pixels;\n\t}\n\telse\n\t{\n\t\t// real window pixels (x11 shm region, dma buffer, etc)\n\t\t// or SDL_Renderer texture if not supported\n\t\tSDL_LockSurface( sw.win );\n\t\treturn sw.win->pixels;\n\t}\n}\n\nvoid SW_UnlockBuffer( void )\n{\n\tif( sw.renderer )\n\t{\n\t\tSDL_Rect src, dst;\n\t\tsrc.x = src.y = 0;\n\t\tsrc.w = sw.width;\n\t\tsrc.h = sw.height;\n\t\tdst = src;\n\t\tSDL_UnlockTexture(sw.tex);\n\n\t\tSDL_SetTextureBlendMode(sw.tex, SDL_BLENDMODE_NONE);\n\n\n\t\tSDL_RenderCopy(sw.renderer, sw.tex, &src, &dst);\n\t\tSDL_RenderPresent(sw.renderer);\n\n\t\treturn;\n\t\t//Con_Printf(\"%s\\n\", SDL_GetError());\n\t}\n\n\t// blit if blitting surface availiable\n\tif( sw.surf )\n\t{\n\t\tSDL_Rect src, dst;\n\t\tsrc.x = src.y = 0;\n\t\tsrc.w = sw.width;\n\t\tsrc.h = sw.height;\n\t\tdst = src;\n\t\tSDL_UnlockSurface( sw.surf );\n\t\tSDL_BlitSurface( sw.surf, &src, sw.win, &dst );\n\t\treturn;\n\t}\n\n\t// already blitted\n\tSDL_UnlockSurface( sw.win );\n\n\tSDL_UpdateWindowSurface( host.hWnd );\n}\n\nint R_MaxVideoModes( void )\n{\n\treturn num_vidmodes;\n}\n\nvidmode_t *R_GetVideoMode( int num )\n{\n\tif( !vidmodes || num < 0 || num >= R_MaxVideoModes() )\n\t{\n\t\treturn NULL;\n\t}\n\n\treturn vidmodes + num;\n}\n\nstatic void R_InitVideoModes( void )\n{\n\tchar buf[MAX_VA_STRING];\n#if SDL_VERSION_ATLEAST( 2, 24, 0 )\n\tSDL_Point point = { window_xpos.value, window_ypos.value };\n\tint displayIndex = SDL_GetPointDisplayIndex( &point );\n#else\n\tint displayIndex = 0;\n#endif\n\tint i, modes;\n\n\tnum_vidmodes = 0;\n\tmodes = SDL_GetNumDisplayModes( displayIndex );\n\n\tif( !modes )\n\t\treturn;\n\n\tvidmodes = Mem_Malloc( host.mempool, modes * sizeof( vidmode_t ) );\n\n\tfor( i = 0; i < modes; i++ )\n\t{\n\t\tint j;\n\t\tSDL_DisplayMode mode;\n\n\t\tif( SDL_GetDisplayMode( displayIndex, i, &mode ) < 0 )\n\t\t{\n\t\t\tMsg( \"SDL_GetDisplayMode: %s\\n\", SDL_GetError() );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( mode.w < VID_MIN_WIDTH || mode.h < VID_MIN_HEIGHT )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < num_vidmodes; j++ )\n\t\t{\n\t\t\tif( mode.w == vidmodes[j].width &&\n\t\t\t\tmode.h == vidmodes[j].height )\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif( j != num_vidmodes )\n\t\t\tcontinue;\n\n\t\tvidmodes[num_vidmodes].width = mode.w;\n\t\tvidmodes[num_vidmodes].height = mode.h;\n\t\tQ_snprintf( buf, sizeof( buf ), \"%ix%i\", mode.w, mode.h );\n\t\tvidmodes[num_vidmodes].desc = copystring( buf );\n\n\t\tnum_vidmodes++;\n\t}\n\n}\n\nstatic void R_FreeVideoModes( void )\n{\n\tint i;\n\n\tif( !vidmodes )\n\t\treturn;\n\n\tfor( i = 0; i < num_vidmodes; i++ )\n\t\tMem_Free( (char*)vidmodes[i].desc );\n\tMem_Free( vidmodes );\n\n\tvidmodes = NULL;\n}\n\n#if XASH_WIN32\ntypedef enum _XASH_DPI_AWARENESS\n{\n\tXASH_DPI_UNAWARE = 0,\n\tXASH_SYSTEM_DPI_AWARE = 1,\n\tXASH_PER_MONITOR_DPI_AWARE = 2\n} XASH_DPI_AWARENESS;\n\nstatic void WIN_SetDPIAwareness( void )\n{\n\tHMODULE hModule;\n\tHRESULT ( __stdcall *pSetProcessDpiAwareness )( XASH_DPI_AWARENESS );\n\tBOOL ( __stdcall *pSetProcessDPIAware )( void );\n\tBOOL bSuccess = FALSE;\n\n\tif( ( hModule = LoadLibraryW( L\"shcore.dll\" ) ) )\n\t{\n\t\tif( ( pSetProcessDpiAwareness = (void*)GetProcAddress( hModule, \"SetProcessDpiAwareness\" ) ) )\n\t\t{\n\t\t\t// I hope SDL don't handle WM_DPICHANGED message\n\t\t\tHRESULT hResult = pSetProcessDpiAwareness( XASH_SYSTEM_DPI_AWARE );\n\n\t\t\tif( hResult == S_OK )\n\t\t\t{\n\t\t\t\tCon_Reportf( \"%s: Success\\n\", __func__ );\n\t\t\t\tbSuccess = TRUE;\n\t\t\t}\n\t\t\telse if( hResult == E_INVALIDARG ) Con_Reportf( \"%s: Invalid argument\\n\", __func__ );\n\t\t\telse if( hResult == E_ACCESSDENIED ) Con_Reportf( \"%s: Access Denied\\n\", __func__ );\n\t\t}\n\t\telse Con_Reportf( \"%s: Can't get SetProcessDpiAwareness\\n\", __func__ );\n\t\tFreeLibrary( hModule );\n\t}\n\telse Con_Reportf( \"%s: Can't load shcore.dll\\n\", __func__ );\n\n\n\tif( !bSuccess )\n\t{\n\t\tCon_Reportf( \"%s: Trying SetProcessDPIAware...\\n\", __func__ );\n\n\t\tif( ( hModule = LoadLibraryW( L\"user32.dll\" ) ) )\n\t\t{\n\t\t\tif( ( pSetProcessDPIAware = ( void* )GetProcAddress( hModule, \"SetProcessDPIAware\" ) ) )\n\t\t\t{\n\t\t\t\t// I hope SDL don't handle WM_DPICHANGED message\n\t\t\t\tBOOL hResult = pSetProcessDPIAware();\n\n\t\t\t\tif( hResult )\n\t\t\t\t{\n\t\t\t\t\tCon_Reportf( \"%s: Success\\n\", __func__ );\n\t\t\t\t\tbSuccess = TRUE;\n\t\t\t\t}\n\t\t\t\telse Con_Reportf( \"%s: fail\\n\", __func__ );\n\t\t\t}\n\t\t\telse Con_Reportf( \"%s: Can't get SetProcessDPIAware\\n\", __func__ );\n\t\t\tFreeLibrary( hModule );\n\t\t}\n\t\telse Con_Reportf( \"%s: Can't load user32.dll\\n\", __func__ );\n\t}\n}\n\nstatic qboolean WIN_SetWindowIcon( HICON ico )\n{\n\tSDL_SysWMinfo wminfo;\n\n\tSDL_VERSION( &wminfo.version );\n\n\tif( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) == SDL_TRUE && wminfo.subsystem == SDL_SYSWM_WINDOWS )\n\t{\n\t\tSendMessage( wminfo.info.win.window, WM_SETICON, ICON_SMALL, (LONG_PTR)ico );\n\t\tSendMessage( wminfo.info.win.window, WM_SETICON, ICON_BIG, (LONG_PTR)ico );\n\t\treturn true;\n\t}\n\n\tCon_Reportf( S_ERROR \"%s: %s\", __func__, SDL_GetError( ));\n\treturn false;\n}\n#endif\n\n\n/*\n=================\nGL_GetProcAddress\n=================\n*/\nvoid *GL_GetProcAddress( const char *name )\n{\n\tvoid *func = SDL_GL_GetProcAddress( name );\n#if !SDL_VERSION_ATLEAST( 2, 0, 6 ) && XASH_POSIX\n\tif( !func && Sys_CheckParm( \"-egl\" ))\n\t{\n\t\t/*\n\t\t * SDL2 has broken SDL_GL_GetProcAddress until this commit if using egl:\n\t\t * https://github.com/libsdl-org/SDL/commit/466ba57d42d244e80357e9ad3011c50af30ed225\n\t\t * so call eglGetProcAddress directly\n\t\t * */\n\t\tstatic void *(*peglGetProcAddress)( const char * );\n\t\tif( !peglGetProcAddress )\n\t\t{\n\t\t\tvoid *lib = dlopen( \"libEGL.so\", RTLD_NOW );\n\t\t\tif( lib )\n\t\t\t\t*(void**)&peglGetProcAddress = dlsym( lib, \"eglGetProcAddress\" );\n\t\t}\n\t\tif( peglGetProcAddress )\n\t\t\tfunc = peglGetProcAddress( name );\n\t}\n#endif\n\n#if XASH_PSVITA\n\t// try to find in main module\n\tif( !func )\n\t{\n\t\tfunc = dlsym( NULL, name );\n\t}\n#endif\n\n\tif( !func )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s failed for %s\\n\", __func__, name );\n\t}\n\n\treturn func;\n}\n\n/*\n===============\nGL_UpdateSwapInterval\n===============\n*/\nvoid GL_UpdateSwapInterval( void )\n{\n\tif (glw_state.context_type != REF_GL)\n\t\treturn;\n\n\tif( FBitSet( gl_vsync.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( gl_vsync.flags, FCVAR_CHANGED );\n\n\t\tif( SDL_GL_SetSwapInterval( gl_vsync.value ) < 0 )\n\t\t\tCon_Reportf( S_ERROR  \"SDL_GL_SetSwapInterval: %s\\n\", SDL_GetError( ));\n\t}\n}\n\n/*\n=================\nGL_DeleteContext\n\nalways return false\n=================\n*/\nqboolean GL_DeleteContext( void )\n{\n\tif( glw_state.context )\n\t{\n\t\tSDL_GL_DeleteContext(glw_state.context);\n\t\tglw_state.context = NULL;\n\t}\n\treturn false;\n}\n\n/*\n=================\nGL_CreateContext\n=================\n*/\nstatic qboolean GL_CreateContext( void )\n{\n\tif( ( glw_state.context = SDL_GL_CreateContext( host.hWnd ) ) == NULL)\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s\\n\", __func__, SDL_GetError( ));\n\t\treturn GL_DeleteContext();\n\t}\n\treturn true;\n}\n\n/*\n=================\nGL_UpdateContext\n=================\n*/\nstatic qboolean GL_UpdateContext( void )\n{\n\tif( SDL_GL_MakeCurrent( host.hWnd, glw_state.context ) < 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s\\n\", __func__, SDL_GetError( ));\n\t\treturn GL_DeleteContext();\n\t}\n\treturn true;\n}\n\nvoid VID_SaveWindowSize( int width, int height, qboolean maximized )\n{\n\tint render_w = width, render_h = height;\n\n\tif( glw_state.context_type == REF_GL )\n\t\tSDL_GL_GetDrawableSize( host.hWnd, &render_w, &render_h );\n\telse\n\t\tSDL_RenderSetLogicalSize( sw.renderer, width, height );\n\n\tVID_SetDisplayTransform( &render_w, &render_h );\n\tR_SaveVideoMode( width, height, render_w, render_h, maximized );\n}\n\nstatic qboolean VID_SetScreenResolution( int width, int height, window_mode_t window_mode )\n{\n\tSDL_DisplayMode got;\n\tUint32 wndFlags = 0;\n\n\tif( vid_highdpi.value )\n\t\tSetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI );\n\n\tSDL_SetWindowBordered( host.hWnd, SDL_FALSE );\n\n\tif( window_mode == WINDOW_MODE_BORDERLESS )\n\t{\n\t\tif( SDL_GetDesktopDisplayMode( 0, &got ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: SDL_GetDesktopDisplayMode: %s\", __func__, SDL_GetError( ));\n\t\t\treturn false;\n\t\t}\n\n\t\tif( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN_DESKTOP ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: SDL_SetWindowFullscreen (borderless): %s\", __func__, SDL_GetError( ));\n\t\t\treturn false;\n\t\t}\n\t}\n\telse if( window_mode == WINDOW_MODE_FULLSCREEN )\n\t{\n\t\tSDL_DisplayMode want = { 0 };\n\t\twant.w = width;\n\t\twant.h = height;\n\n\t\tif( SDL_GetClosestDisplayMode( 0, &want, &got ) == NULL )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: SDL_GetClosestDisplayMode: %s\", __func__, SDL_GetError( ));\n\t\t\treturn false;\n\t\t}\n\n\t\tif( got.w != want.w || got.h != want.h )\n\t\t\tCon_Reportf( S_NOTE \"Got closest display mode: %ix%i@%i\\n\", got.w, got.h, got.refresh_rate );\n\n\t\tif( SDL_SetWindowDisplayMode( host.hWnd, &got ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: SDL_SetWindowDisplayMode: %s\", __func__, SDL_GetError( ));\n\t\t\treturn false;\n\t\t}\n\n\t\tif( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: SDL_SetWindowFullscreen (fullscreen): %s\", __func__, SDL_GetError( ));\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tSDL_SetWindowSize( host.hWnd, got.w, got.h );\n\tVID_SaveWindowSize( got.w, got.h, true );\n\treturn true;\n}\n\nvoid VID_RestoreScreenResolution( void )\n{\n\t// on mobile platform fullscreen is designed to be always on\n\t// and code below minimizes our window if we're in full screen\n\t// don't do that on mobile devices\n#if !XASH_MOBILE_PLATFORM\n\tswitch((window_mode_t)vid_fullscreen.value )\n\t{\n\tcase WINDOW_MODE_WINDOWED:\n\t\t// TODO: this line is from very old SDL video backend\n\t\t// figure out why we need it, because in windowed mode we\n\t\t// always have borders\n\t\tSDL_SetWindowBordered( host.hWnd, SDL_TRUE );\n\t\tbreak;\n\tcase WINDOW_MODE_BORDERLESS:\n\t\t// in borderless fullscreen we don't change screen resolution, so no-op\n\t\tbreak;\n\tcase WINDOW_MODE_FULLSCREEN:\n\t\t// TODO: we might want to not minimize window if current desktop mode\n\t\t// and window mode are the same\n\t\tSDL_MinimizeWindow( host.hWnd );\n\t\tSDL_SetWindowFullscreen( host.hWnd, 0 );\n\t\tbreak;\n\t}\n#endif // !XASH_MOBILE_PLATFORM\n}\n\nstatic void VID_SetWindowIcon( SDL_Window *hWnd )\n{\n\trgbdata_t *icon = NULL;\n\tchar iconpath[MAX_STRING];\n#if XASH_WIN32 // ICO support only for Win32\n\tconst char *disk_iconpath = FS_GetDiskPath( GI->iconpath, true );\n\n\tif( disk_iconpath )\n\t{\n\t\tint len = MultiByteToWideChar( CP_UTF8, 0, disk_iconpath, -1, NULL, 0 );\n\n\t\tif( len >= 0 )\n\t\t{\n\t\t\twchar_t *path = malloc( len * sizeof( *path ));\n\t\t\tHICON ico;\n\n\t\t\tMultiByteToWideChar( CP_UTF8, 0, disk_iconpath, -1, path, len );\n\t\t\tico = (HICON)LoadImageW( NULL, path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE );\n\n\t\t\tfree( path );\n\n\t\t\tif( ico && WIN_SetWindowIcon( ico ))\n\t\t\t\treturn;\n\t\t}\n\t}\n#endif // XASH_WIN32\n\n\tQ_strncpy( iconpath, GI->iconpath, sizeof( iconpath ));\n\tCOM_ReplaceExtension( iconpath, \".tga\", sizeof( iconpath ));\n\ticon = FS_LoadImage( iconpath, NULL, 0 );\n\n\tif( icon )\n\t{\n\t\tSDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer,\n\t\t\ticon->width, icon->height, 32, 4 * icon->width,\n\t\t\t0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 );\n\n\t\tFS_FreeImage( icon );\n\n\t\tif( surface )\n\t\t{\n\t\t\tSDL_SetWindowIcon( host.hWnd, surface );\n\t\t\tSDL_FreeSurface( surface );\n\t\t\treturn;\n\t\t}\n\t}\n\n#if XASH_WIN32 // ICO support only for Win32\n\tWIN_SetWindowIcon( LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( 101 )));\n#endif\n}\n\nstatic qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags )\n{\n\twhile( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST )\n\t{\n\t\thost.hWnd = SDL_CreateWindow( wndname, xpos, ypos, w, h, flags );\n\n\t\t// we have window, exit loop\n\t\tif( host.hWnd )\n\t\t\tbreak;\n\n\t\tCon_Reportf( S_ERROR \"%s: couldn't create '%s' with safegl level %d: %s\\n\", __func__, wndname, glw_state.safe, SDL_GetError());\n\n\t\tglw_state.safe++;\n\n\t\tif( !gl_msaa_samples.value && glw_state.safe == SAFE_NOMSAA )\n\t\t\tglw_state.safe++; // no need to skip msaa, if we already disabled it\n\n\t\tGL_SetupAttributes(); // re-choose attributes\n\n\t\t// try again create window\n\t}\n\n\t// window creation has failed...\n\tif( glw_state.safe >= SAFE_LAST )\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic qboolean RectFitsInDisplay( const SDL_Rect *rect, const SDL_Rect *display )\n{\n\treturn rect->x >= display->x\n\t\t&& rect->y >= display->y\n\t\t&& rect->x + rect->w <= display->x + display->w\n\t\t&& rect->y + rect->h <= display->y + display->h;\n}\n// Function to check if the rectangle fits in any display\nstatic qboolean RectFitsInAnyDisplay( const SDL_Rect *rect, const SDL_Rect *display_rects, int num_displays )\n{\n\tfor( int i = 0; i < num_displays; i++ )\n\t{\n\t\tif( RectFitsInDisplay( rect, &display_rects[i] ))\n\t\t\treturn true; // Rectangle fits in this display\n\t}\n\treturn false; // Rectangle does not fit in any display\n}\n\n/*\n=================\nVID_CreateWindow\n=================\n*/\nqboolean VID_CreateWindow( int width, int height, window_mode_t window_mode )\n{\n\tstring wndname;\n\tqboolean maximized = vid_maximized.value != 0.0f;\n\tUint32 wndFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_MOUSE_FOCUS;\n\tint xpos, ypos;\n\tint num_displays = SDL_GetNumVideoDisplays();\n\tSDL_Rect rect = { window_xpos.value, window_ypos.value, width, height };\n\n\tQ_strncpy( wndname, GI->title, sizeof( wndname ));\n\n\tif( vid_highdpi.value )\n\t\tSetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI );\n\n\tswitch (glw_state.context_type)\n\t{\n\t\tcase REF_GL:\n\t\t\twndFlags |= SDL_WINDOW_OPENGL;\n\t\t\tbreak;\n\t\tcase REF_VULKAN:\n\t\t\twndFlags |= SDL_WINDOW_VULKAN;\n\t\t\tbreak;\n\t}\n\n\tif( window_mode == WINDOW_MODE_WINDOWED )\n\t{\n\t\tSDL_Rect *display_rects = ( SDL_Rect * )malloc( num_displays * sizeof( SDL_Rect ));\n\n\t\tSetBits( wndFlags, SDL_WINDOW_RESIZABLE );\n\t\tif( maximized )\n\t\t\tSetBits( wndFlags, SDL_WINDOW_MAXIMIZED );\n\n\t\tif( !display_rects )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Failed to allocate memory for display rects!\\n\" );\n\t\t\txpos = SDL_WINDOWPOS_UNDEFINED;\n\t\t\typos = SDL_WINDOWPOS_UNDEFINED;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( int i = 0; i < num_displays; i++ )\n\t\t\t{\n\t\t\t\tif( SDL_GetDisplayBounds( i, &display_rects[i] ) != 0 )\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( S_ERROR \"Failed to get bounds for display %d! SDL_Error: %s\\n\", i, SDL_GetError());\n\t\t\t\t\tdisplay_rects[i] = ( SDL_Rect ){ 0, 0, 0, 0 };\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Check if the rectangle fits in any display\n\t\t\tif( !RectFitsInAnyDisplay( &rect, display_rects, num_displays ))\n\t\t\t{\n\t\t\t\t// Rectangle doesn't fit in any display, center it\n\t\t\t\txpos = SDL_WINDOWPOS_CENTERED;\n\t\t\t\typos = SDL_WINDOWPOS_CENTERED;\n\t\t\t\tCon_Printf( S_ERROR \"Rectangle does not fit in any display. Centering window.\\n\" );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\txpos = rect.x;\n\t\t\t\typos = rect.y;\n\t\t\t}\n\t\t}\n\t\tfree( display_rects );\n\t}\n\telse\n\t{\n\t\tif( window_mode == WINDOW_MODE_FULLSCREEN )\n\t\t\t// need input grab only in true fullscreen mode\n\t\t\tSetBits( wndFlags, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_INPUT_GRABBED );\n\t\telse\n\t\t\tSetBits( wndFlags, SDL_WINDOW_FULLSCREEN_DESKTOP );\n\t\tSetBits( wndFlags, SDL_WINDOW_BORDERLESS );\n\t\tif ( window_xpos.value < 0 || window_ypos.value < 0 )\n\t\t{\n\t\t\txpos = SDL_WINDOWPOS_UNDEFINED;\n\t\t\typos = SDL_WINDOWPOS_UNDEFINED;\n\t\t}\n\t\telse\n\t\t{\n\t\t\txpos = window_xpos.value;\n\t\t\typos = window_ypos.value;\n\t\t}\n\t}\n\n\tif( !VID_CreateWindowWithSafeGL( wndname, xpos, ypos, width, height, wndFlags ))\n\t\treturn false;\n\n\t// update window size if it was maximized, just in case\n\tif( FBitSet( SDL_GetWindowFlags( host.hWnd ), SDL_WINDOW_MAXIMIZED|SDL_WINDOW_FULLSCREEN_DESKTOP ) != 0 )\n\t\tSDL_GetWindowSize( host.hWnd, &width, &height );\n\n\tif( window_mode != WINDOW_MODE_WINDOWED )\n\t{\n\t\tif( !VID_SetScreenResolution( width, height, window_mode ))\n\t\t\treturn false;\n\t}\n\telse VID_RestoreScreenResolution();\n\n\tVID_SetWindowIcon( host.hWnd );\n\tSDL_ShowWindow( host.hWnd );\n\n\tif( glw_state.context_type == REF_SOFTWARE )\n\t{\n\t\tint sdl_renderer = -2;\n\t\tchar cmd[64];\n\n\t\tif( Sys_GetParmFromCmdLine(\"-sdl_renderer\", cmd ) )\n\t\t\tsdl_renderer = Q_atoi( cmd );\n\n\t\tif( sdl_renderer >= -1 )\n\t\t{\n\t\t\tsw.renderer = SDL_CreateRenderer( host.hWnd, sdl_renderer, 0 );\n\t\t\tif( !sw.renderer )\n\t\t\t\tCon_Printf( S_ERROR \"failed to create SDL renderer: %s\\n\", SDL_GetError() );\n\t\t\telse\n\t\t\t{\n\t\t\t\tSDL_RendererInfo info;\n\t\t\t\tSDL_GetRendererInfo( sw.renderer, &info );\n\t\t\t\tCon_Printf( \"SDL_Renderer %s initialized\\n\", info.name );\n\t\t\t}\n\t\t}\n\t}\n\telse if( glw_state.context_type == REF_GL )\n\t{\n\t\tif( !glw_state.initialized )\n\t\t{\n\t\t\twhile( !GL_CreateContext( ))\n\t\t\t{\n\t\t\t\tglw_state.safe++;\n\t\t\t\tif(glw_state.safe > SAFE_DONTCARE)\n\t\t\t\t\treturn false;\n\t\t\t\tGL_SetupAttributes(); // re-choose attributes\n\t\t\t}\n\t\t}\n\n\t\tif( !GL_UpdateContext( ))\n\t\t\treturn false;\n\t}\n\n\tVID_SaveWindowSize( width, height, maximized );\n\n\treturn true;\n}\n\n/*\n=================\nVID_DestroyWindow\n=================\n*/\nvoid VID_DestroyWindow( void )\n{\n\tGL_DeleteContext();\n\n\tVID_RestoreScreenResolution();\n\tif( host.hWnd )\n\t{\n\t\tSDL_DestroyWindow ( host.hWnd );\n\t\thost.hWnd = NULL;\n\t}\n\n\tif( refState.fullScreen )\n\t{\n\t\trefState.fullScreen = false;\n\t}\n}\n\n/*\n==================\nGL_SetupAttributes\n==================\n*/\nstatic void GL_SetupAttributes( void )\n{\n\tSDL_GL_ResetAttributes();\n\n\tref.dllFuncs.GL_SetupAttributes( glw_state.safe );\n}\n\nvoid GL_SwapBuffers( void )\n{\n\tSDL_GL_SwapWindow( host.hWnd );\n}\n\nint GL_SetAttribute( int attr, int val )\n{\n\tswitch( attr )\n\t{\n#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );\n\tcase REF_GL_CONTEXT_PROFILE_MASK:\n#if !XASH_WIN32\n#ifdef SDL_HINT_OPENGL_ES_DRIVER\n\t\tif( val == REF_GL_CONTEXT_PROFILE_ES )\n\t\t{\n\t\t\tSDL_SetHint( SDL_HINT_OPENGL_ES_DRIVER, \"1\" );\n\t\t\tSDL_SetHint( \"SDL_VIDEO_X11_FORCE_EGL\", \"1\" );\n\t\t}\n#endif // SDL_HINT_OPENGL_ES_DRIVER\n#endif\n\t\treturn SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, val );\n#if SDL_VERSION_ATLEAST( 2, 0, 4 )\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );\n#endif\n#if SDL_VERSION_ATLEAST( 2, 0, 6 )\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );\n#endif\n#undef MAP_REF_API_ATTRIBUTE_TO_SDL\n\t}\n\n\treturn -1;\n}\n\nint GL_GetAttribute( int attr, int *val )\n{\n\tswitch( attr )\n\t{\n#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK );\n#if SDL_VERSION_ATLEAST( 2, 0, 4 )\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR );\n#endif\n#if SDL_VERSION_ATLEAST( 2, 0, 6 )\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION );\n\t\tMAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR );\n#endif\n#undef MAP_REF_API_ATTRIBUTE_TO_SDL\n\t}\n\n\treturn 0;\n}\n\n#ifndef EGL_LIB\n#define EGL_LIB NULL\n#endif\n\nint XVK_GetInstanceExtensions( unsigned int count, const char **pNames )\n{\n\tif (!SDL_Vulkan_GetInstanceExtensions(host.hWnd, &count, pNames))\n\t{\n\t\tCon_Reportf( S_ERROR  \"Couldn't get Vulkan extensions: %s\\n\", SDL_GetError());\n\t\treturn -1;\n\t}\n\n\treturn (int)count;\n}\n\nvoid *XVK_GetVkGetInstanceProcAddr( void )\n{\n\treturn SDL_Vulkan_GetVkGetInstanceProcAddr();\n}\n\nVkSurfaceKHR XVK_CreateSurface( VkInstance instance )\n{\n\tVkSurfaceKHR surface;\n\n\tif (!SDL_Vulkan_CreateSurface(host.hWnd, instance, &surface))\n\t{\n\t\tCon_Reportf( S_ERROR  \"Couldn't create Vulkan surface: %s\\n\", SDL_GetError());\n\t\treturn 0;\n\t}\n\n\treturn surface;\n}\n\n/*\n==================\nR_Init_Video\n==================\n*/\nqboolean R_Init_Video( const int type )\n{\n\tstring safe;\n\tqboolean retval;\n\tSDL_DisplayMode displayMode;\n#if SDL_VERSION_ATLEAST( 2, 24, 0 )\n\tSDL_Point point = { window_xpos.value, window_ypos.value };\n\tint displayIndex = SDL_GetPointDisplayIndex( &point );\n#else\n\tint displayIndex = 0;\n#endif\n\tSDL_GetCurrentDisplayMode( displayIndex, &displayMode );\n\n\trefState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format );\n\n#ifdef SDL_HINT_QTWAYLAND_WINDOW_FLAGS\n\tSDL_SetHint( SDL_HINT_QTWAYLAND_WINDOW_FLAGS, \"OverridesSystemGestures\" );\n#endif\n#ifdef SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION\n\tSDL_SetHint( SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION, \"landscape\" );\n#endif\n\n\tif( Sys_CheckParm( \"-egl\" ) )\n#if XASH_WIN32\n\t\tSDL_SetHint( \"SDL_OPENGL_ES_DRIVER\", \"1\" );\n#else\n\t\tSDL_SetHint( \"SDL_VIDEO_X11_FORCE_EGL\", \"1\" );\n\n\tSDL_SetHint( \"SDL_VIDEO_X11_XRANDR\", \"1\" );\n\tSDL_SetHint( \"SDL_VIDEO_X11_XVIDMODE\", \"1\" );\n#endif // !XASH_WIN32\n\n\t// must be initialized before creating window\n#if XASH_WIN32\n\tWIN_SetDPIAwareness();\n#endif\n\n\tglw_state.context_type = type;\n\tswitch( type )\n\t{\n\tcase REF_SOFTWARE:\n\t\tbreak;\n\tcase REF_GL:\n\t\tif( !glw_state.safe && Sys_GetParmFromCmdLine( \"-safegl\", safe ) )\n\t\t\tglw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE );\n\n\t\t// refdll can request some attributes\n\t\tGL_SetupAttributes( );\n\n\t\tif( SDL_GL_LoadLibrary( EGL_LIB ) < 0 )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR  \"Couldn't initialize OpenGL: %s\\n\", SDL_GetError());\n\t\t\treturn false;\n\t\t}\n\t\tbreak;\n\tcase REF_VULKAN:\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"Can't initialize unknown context type %d!\\n\", type );\n\t\tbreak;\n\t}\n\n\tif( !(retval = VID_SetMode()) )\n\t{\n\t\treturn retval;\n\t}\n\n\tswitch( type )\n\t{\n\tcase REF_GL:\n\t\t// refdll also can check extensions\n\t\tref.dllFuncs.GL_InitExtensions();\n\t\tbreak;\n\tcase REF_SOFTWARE:\n\tdefault:\n\t\tbreak;\n\t}\n\n\tR_InitVideoModes();\n\n\thost.renderinfo_changed = false;\n\n\treturn true;\n}\n\nrserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode )\n{\n\tSDL_DisplayMode displayMode;\n\n\tif( SDL_GetCurrentDisplayMode( 0, &displayMode ) < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"SDL_GetCurrentDisplayMode: %s\\n\", SDL_GetError( ));\n\t\treturn rserr_invalid_mode;\n\t}\n\n\t// check our desktop attributes\n\trefState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format );\n\tif( window_mode == WINDOW_MODE_BORDERLESS )\n\t{\n\t\twidth = displayMode.w;\n\t\theight = displayMode.h;\n\t}\n\n\trefState.fullScreen = window_mode != WINDOW_MODE_WINDOWED;\n\tCon_Reportf( \"%s: Setting video mode to %dx%d %s\\n\", __func__, width, height, refState.fullScreen ? \"fullscreen\" : \"windowed\" );\n\n\tif( !host.hWnd )\n\t{\n\t\tif( !VID_CreateWindow( width, height, window_mode ))\n\t\t\treturn rserr_invalid_mode;\n\t}\n\telse if( refState.fullScreen )\n\t{\n\t\tif( !VID_SetScreenResolution( width, height, window_mode ))\n\t\t\treturn rserr_invalid_fullscreen;\n\t}\n\telse\n\t{\n\t\tVID_RestoreScreenResolution();\n\n\t\tif( SDL_SetWindowFullscreen( host.hWnd, 0 ) < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"SDL_SetWindowFullscreen: %s\", SDL_GetError( ));\n\t\t\treturn rserr_invalid_fullscreen;\n\t\t}\n#if SDL_VERSION_ATLEAST( 2, 0, 5 )\n\t\tSDL_SetWindowResizable( host.hWnd, SDL_TRUE );\n#endif\n\t\tSDL_SetWindowBordered( host.hWnd, SDL_TRUE );\n\t\tSDL_SetWindowSize( host.hWnd, width, height );\n\n\t\tVID_SaveWindowSize( width, height, true );\n\t}\n\n\treturn rserr_ok;\n}\n\n/*\n==================\nVID_SetMode\n\nSet the described video mode\n==================\n*/\nqboolean VID_SetMode( void )\n{\n\tint iScreenWidth, iScreenHeight;\n\trserr_t\terr;\n\twindow_mode_t window_mode;\n\n\tiScreenWidth = Cvar_VariableInteger( \"width\" );\n\tiScreenHeight = Cvar_VariableInteger( \"height\" );\n\n\tif( iScreenWidth < VID_MIN_WIDTH ||\n\t\tiScreenHeight < VID_MIN_HEIGHT )\t// trying to get resolution automatically by default\n\t{\n#if !defined( DEFAULT_MODE_WIDTH ) || !defined( DEFAULT_MODE_HEIGHT )\n\t\tSDL_DisplayMode mode;\n\n\t\tSDL_GetDesktopDisplayMode( 0, &mode );\n\n\t\tiScreenWidth = mode.w;\n\t\tiScreenHeight = mode.h;\n#else\n\t\tiScreenWidth = DEFAULT_MODE_WIDTH;\n\t\tiScreenHeight = DEFAULT_MODE_HEIGHT;\n#endif\n\t}\n\n#if XASH_MOBILE_PLATFORM\n\tif( Q_strcmp( vid_fullscreen.string, DEFAULT_FULLSCREEN ))\n\t{\n\t\tCvar_DirectSet( &vid_fullscreen, DEFAULT_FULLSCREEN );\n\t\tCon_Reportf( S_ERROR \"%s: windowed unavailable on this platform\\n\", __func__ );\n\t}\n#endif\n\n\twindow_mode = bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 );\n\tSetBits( gl_vsync.flags, FCVAR_CHANGED );\n\n\tif(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, window_mode )) == rserr_ok )\n\t{\n\t\tsdlState.prev_width = iScreenWidth;\n\t\tsdlState.prev_height = iScreenHeight;\n\t}\n\telse\n\t{\n\t\tif( err == rserr_invalid_fullscreen )\n\t\t{\n\t\t\tCvar_DirectSet( &vid_fullscreen, \"0\" );\n\t\t\tCon_Reportf( S_ERROR \"%s: fullscreen unavailable in this mode\\n\", __func__ );\n\t\t\tSys_Warn( \"fullscreen unavailable in this mode!\" );\n\t\t\tif(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, WINDOW_MODE_WINDOWED )) == rserr_ok )\n\t\t\t\treturn true;\n\t\t}\n\t\telse if( err == rserr_invalid_mode )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: invalid mode\\n\", __func__ );\n\t\t\tSys_Warn( \"invalid mode, engine will run in %dx%d\", sdlState.prev_width, sdlState.prev_height );\n\t\t}\n\n\t\t// try setting it back to something safe\n\t\tif(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, WINDOW_MODE_WINDOWED )) != rserr_ok )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: could not revert to safe mode\\n\", __func__ );\n\t\t\tSys_Warn( \"could not revert to safe mode!\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type )\n{\n\tSDL_SysWMinfo wmInfo;\n\n\tif( type == REF_WINDOW_TYPE_SDL )\n\t{\n\t\tif( handle )\n\t\t\t*handle = (void *)host.hWnd;\n\t\treturn REF_WINDOW_TYPE_SDL;\n\t}\n\n\tSDL_VERSION( &wmInfo.version );\n\n\tif( SDL_GetWindowWMInfo( host.hWnd, &wmInfo ))\n\t\treturn REF_WINDOW_TYPE_NULL;\n\n\tswitch( wmInfo.subsystem )\n\t{\n\tcase SDL_SYSWM_WINDOWS:\n\t\tif( !type || type == REF_WINDOW_TYPE_WIN32 )\n\t\t{\n#ifdef SDL_VIDEO_DRIVER_WINDOWS\n\t\t\tif( handle )\n\t\t\t\t*handle = (void *)wmInfo.info.win.window;\n\t\t\treturn REF_WINDOW_TYPE_WIN32;\n#endif // SDL_VIDEO_DRIVER_WINDOWS\n\t\t}\n\t\tbreak;\n\tcase SDL_SYSWM_X11:\n\t\tif( !type || type == REF_WINDOW_TYPE_X11 )\n\t\t{\n#ifdef SDL_VIDEO_DRIVER_X11\n\t\t\tif( handle )\n\t\t\t\t*handle = (void *)(uintptr_t)wmInfo.info.x11.window;\n\t\t\treturn REF_WINDOW_TYPE_X11;\n#endif // SDL_VIDEO_DRIVER_X11\n\t\t}\n\t\tbreak;\n\tcase SDL_SYSWM_COCOA:\n\t\tif( !type || type == REF_WINDOW_TYPE_MACOS )\n\t\t{\n#ifdef SDL_VIDEO_DRIVER_COCOA\n\t\t\tif( handle )\n\t\t\t\t*handle = (void *)wmInfo.info.cocoa.window;\n\t\t\treturn REF_WINDOW_TYPE_MACOS;\n#endif // SDL_VIDEO_DRIVER_COCOA\n\t\t}\n\t\tbreak;\n\tcase SDL_SYSWM_WAYLAND:\n\t\tif( !type || type == REF_WINDOW_TYPE_WAYLAND )\n\t\t{\n#ifdef SDL_VIDEO_DRIVER_WAYLAND\n\t\t\tif( handle )\n\t\t\t\t*handle = (void *)wmInfo.info.wl.surface;\n\t\t\treturn REF_WINDOW_TYPE_WAYLAND;\n#endif // SDL_VIDEO_DRIVER_WAYLAND\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn REF_WINDOW_TYPE_NULL;\n}\n\n/*\n==================\nR_Free_Video\n==================\n*/\nvoid R_Free_Video( void )\n{\n\tGL_DeleteContext ();\n\n\tVID_DestroyWindow ();\n\n\tR_FreeVideoModes();\n\n\tref.dllFuncs.GL_ClearExtensions();\n}\n"
  },
  {
    "path": "engine/platform/sdl3/in_sdl3.c",
    "content": "/*\nplatform_sdl3.h - SDL3 platform definitions\nCopyright (C) 2025 Er2off\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"platform_sdl3.h\"\n#include \"eiface.h\" // ARRAYSIZE\n#include \"vid_common.h\" // window_{width,height}\n#include \"client.h\" // refState\n#include \"input.h\" // Touch_WantVisibleCursor\n#include \"vgui_draw.h\" // VGui_UpdateInternalCursorState\n\nstatic struct\n{\n\tqboolean initialized;\n\tSDL_Cursor *cursors[dc_last];\n} cursors;\n\nstatic struct\n{\n\tfloat x, y;\n\tqboolean pushed;\n} in_visible_cursor_pos;\n\nvoid SDLash_InitCursors( void )\n{\n\tif( cursors.initialized )\n\t\tSDLash_FreeCursors();\n\n\t// load up all default cursors\n\tcursors.cursors[dc_none] = NULL;\n\tcursors.cursors[dc_arrow] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT );\n\tcursors.cursors[dc_ibeam] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_TEXT );\n\tcursors.cursors[dc_hourglass] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_WAIT );\n\tcursors.cursors[dc_crosshair] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_CROSSHAIR );\n\tcursors.cursors[dc_up] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT );\n\tcursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NWSE_RESIZE );\n\tcursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NESW_RESIZE );\n\tcursors.cursors[dc_sizewe] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_EW_RESIZE );\n\tcursors.cursors[dc_sizens] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NS_RESIZE );\n\tcursors.cursors[dc_sizeall] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_MOVE );\n\tcursors.cursors[dc_no] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NOT_ALLOWED );\n\tcursors.cursors[dc_hand] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_POINTER );\n\tcursors.initialized = true;\n}\n\nvoid SDLash_FreeCursors( void )\n{\n\tif( !cursors.initialized )\n\t\treturn;\n\n\tfor( int i = 0; i < ARRAYSIZE( cursors.cursors ); i++ )\n\t{\n\t\tif( cursors.cursors[i] )\n\t\t{\n\t\t\tSDL_DestroyCursor( cursors.cursors[i] );\n\t\t\tcursors.cursors[i] = NULL;\n\t\t}\n\t}\n\n\tcursors.initialized = false;\n}\n\nqboolean Platform_GetMouseGrab( void )\n{\n\treturn SDL_GetWindowMouseGrab( host.hWnd );\n}\n\nvoid Platform_SetMouseGrab( qboolean enable )\n{\n\tSDL_SetWindowMouseGrab( host.hWnd, enable );\n}\n\nvoid Platform_MouseMove( float *x, float *y )\n{\n\tSDL_GetRelativeMouseState( x, y );\n}\n\nvoid GAME_EXPORT Platform_SetMousePos( int x, int y )\n{\n\tSDL_WarpMouseInWindow( host.hWnd, x, y );\n}\n\nvoid GAME_EXPORT Platform_GetMousePos( int *x, int *y )\n{\n\tfloat m_x = 0.0f, m_y = 0.0f;\n\n\tSDL_GetMouseState( &m_x, &m_y );\n\n\tif( x )\n\t{\n\t\tif( window_width.value && window_width.value != refState.width )\n\t\t{\n\t\t\tfloat factor = refState.width / window_width.value;\n\t\t\t*x = m_x * factor;\n\t\t}\n\t\telse *x = m_x;\n\t}\n\n\tif( y )\n\t{\n\t\tif( window_height.value && window_height.value != refState.height )\n\t\t{\n\t\t\tfloat factor = refState.height / window_height.value;\n\t\t\t*y = m_y * factor;\n\t\t}\n\t\telse *y = m_y;\n\t}\n}\n\nvoid Platform_SetCursorType( VGUI_DefaultCursor type )\n{\n\tqboolean visible = type != dc_user && type != dc_none;\n\n\t// never disable cursor in touch emulation mode\n\tif( !visible && Touch_WantVisibleCursor( ))\n\t\treturn;\n\n\thost.mouse_visible = visible;\n\tVGui_UpdateInternalCursorState( type );\n\n\tif( visible )\n\t{\n\t\tif( cursors.initialized )\n\t\t\tSDL_SetCursor( cursors.cursors[type] );\n\n\t\tSDL_ShowCursor();\n\n\t\t// restore the last mouse position\n\t\tif( in_visible_cursor_pos.pushed )\n\t\t{\n\t\t\tSDL_WarpMouseInWindow( host.hWnd, in_visible_cursor_pos.x, in_visible_cursor_pos.y );\n\t\t\tin_visible_cursor_pos.pushed = false;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// save last mouse position and warp it to the center\n\t\tif( !in_visible_cursor_pos.pushed )\n\t\t{\n\t\t\tSDL_GetMouseState( &in_visible_cursor_pos.x, &in_visible_cursor_pos.y );\n\t\t\tSDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y );\n\t\t\tin_visible_cursor_pos.pushed = true;\n\t\t}\n\n\t\tSDL_HideCursor();\n\t}\n}\n\nvoid Platform_EnableTextInput( qboolean enable )\n{\n\tenable ? SDL_StartTextInput( host.hWnd ) : SDL_StopTextInput( host.hWnd );\n}\n\nint Platform_GetClipboardText( char *buffer, size_t size )\n{\n\tint len;\n\tchar *text = SDL_GetClipboardText();\n\n\tif( !text )\n\t\treturn 0;\n\n\tif( buffer && size > 0 )\n\t\tlen = Q_strncpy( buffer, text, size );\n\telse\n\t\tlen = Q_strlen( text );\n\n\tSDL_free( text );\n\n\treturn len;\n}\n\nvoid Platform_SetClipboardText( const char *buffer )\n{\n\tSDL_SetClipboardText( buffer );\n}\n\nkey_modifier_t Platform_GetKeyModifiers( void )\n{\n\tSDL_Keymod mod_flags = SDL_GetModState();\n\tkey_modifier_t result_flags = KeyModifier_None;\n\n\tif( FBitSet( mod_flags, SDL_KMOD_LCTRL ))\n\t\tSetBits( result_flags, KeyModifier_LeftCtrl );\n\tif( FBitSet( mod_flags, SDL_KMOD_RCTRL ))\n\t\tSetBits( result_flags, KeyModifier_RightCtrl );\n\tif( FBitSet( mod_flags, SDL_KMOD_RSHIFT ))\n\t\tSetBits( result_flags, KeyModifier_RightShift );\n\tif( FBitSet( mod_flags, SDL_KMOD_LSHIFT ))\n\t\tSetBits( result_flags, KeyModifier_LeftShift );\n\tif( FBitSet( mod_flags, SDL_KMOD_LALT ))\n\t\tSetBits( result_flags, KeyModifier_LeftAlt );\n\tif( FBitSet( mod_flags, SDL_KMOD_RALT ))\n\t\tSetBits( result_flags, KeyModifier_RightAlt );\n\tif( FBitSet( mod_flags, SDL_KMOD_NUM ))\n\t\tSetBits( result_flags, KeyModifier_NumLock );\n\tif( FBitSet( mod_flags, SDL_KMOD_CAPS ))\n\t\tSetBits( result_flags, KeyModifier_CapsLock );\n\tif( FBitSet( mod_flags, SDL_KMOD_RGUI ))\n\t\tSetBits( result_flags, KeyModifier_RightSuper );\n\tif( FBitSet( mod_flags, SDL_KMOD_LGUI ))\n\t\tSetBits( result_flags, KeyModifier_LeftSuper );\n\n\treturn result_flags;\n}\n"
  },
  {
    "path": "engine/platform/sdl3/platform_sdl3.h",
    "content": "/*\nplatform_sdl3.h - SDL3 platform definitions\nCopyright (C) 2025 Er2off\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef PLATFORM_SDL3_H\n#define PLATFORM_SDL3_H\n\n#include <SDL3/SDL.h>\n#include \"platform/platform.h\"\n\n//\n// in_sdl3.c\n//\nvoid SDLash_InitCursors( void );\nvoid SDLash_FreeCursors( void );\n\n#endif // PLATFORM_SDL3_H\n"
  },
  {
    "path": "engine/platform/sdl3/sys_sdl3.c",
    "content": "/*\nplatform_sdl3.h - SDL3 platform definitions\nCopyright (C) 2025 Er2off\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"platform_sdl3.h\"\n#include \"common.h\"\n\n#if XASH_TIMER == TIMER_SDL\ndouble Platform_DoubleTime( void )\n{\n\tstatic Uint64 g_PerformanceFrequency;\n\tstatic Uint64 g_ClockStart;\n\tUint64 CurrentTime;\n\n\tif( !g_PerformanceFrequency )\n\t{\n\t\tg_PerformanceFrequency = SDL_GetPerformanceFrequency();\n\t\tg_ClockStart = SDL_GetPerformanceCounter();\n\n\t\treturn 0.0;\n\t}\n\n\tCurrentTime = SDL_GetPerformanceCounter();\n\treturn (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency );\n}\n\nvoid Platform_Sleep( int msec )\n{\n\tSDL_Delay( msec );\n}\n#endif // XASH_TIMER == TIMER_SDL\n\n#if XASH_MESSAGEBOX == MSGBOX_SDL\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )\n{\n\tSDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL );\n}\n#endif\n\nvoid SDLash_NanoSleep( int nsec )\n{\n\tSDL_DelayNS( nsec );\n}\n\nstatic const char *SDLash_CategoryToString( int category )\n{\n\tswitch( category )\n\t{\n\tcase SDL_LOG_CATEGORY_APPLICATION: return \"App\";\n\tcase SDL_LOG_CATEGORY_ERROR: return \"Error\";\n\tcase SDL_LOG_CATEGORY_ASSERT: return \"Assert\";\n\tcase SDL_LOG_CATEGORY_SYSTEM: return \"System\";\n\tcase SDL_LOG_CATEGORY_AUDIO: return \"Audio\";\n\tcase SDL_LOG_CATEGORY_VIDEO: return \"Video\";\n\tcase SDL_LOG_CATEGORY_RENDER: return \"Render\";\n\tcase SDL_LOG_CATEGORY_INPUT: return \"Input\";\n\tcase SDL_LOG_CATEGORY_TEST: return \"Test\";\n\tcase SDL_LOG_CATEGORY_GPU: return \"GPU\";\n\tdefault: return \"Unknown\";\n\t}\n}\n\nstatic void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message )\n{\n\tswitch( priority )\n\t{\n\tcase SDL_LOG_PRIORITY_CRITICAL:\n\tcase SDL_LOG_PRIORITY_ERROR:\n\t\tCon_Printf( S_ERROR S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tcase SDL_LOG_PRIORITY_WARN:\n\t\tCon_DPrintf( S_WARN S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tcase SDL_LOG_PRIORITY_INFO:\n\t\tCon_Reportf( S_NOTE S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\tdefault:\n\t\tCon_Reportf( S_BLUE \"SDL\" S_DEFAULT \": [%s] %s\\n\", SDLash_CategoryToString( category ), message );\n\t\tbreak;\n\t}\n}\n\nvoid SDLash_Init( const char *basedir )\n{\n\tSDL_SetLogOutputFunction( SDLash_LogOutputFunction, NULL );\n\n\tif( host_developer.value >= 2 )\n\t\tSDL_SetLogPriorities( SDL_LOG_PRIORITY_VERBOSE );\n\telse if( host_developer.value >= 1 )\n\t\tSDL_SetLogPriorities( SDL_LOG_PRIORITY_WARN );\n\telse\n\t\tSDL_SetLogPriorities( SDL_LOG_PRIORITY_ERROR );\n\n\tif( !SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS ))\n\t{\n\t\tSys_Warn( \"SDL_Init failed: %s\", SDL_GetError( ));\n\t\thost.type = HOST_DEDICATED;\n\t}\n\n\tSDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, \"0\" );\n\tSDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, \"0\" );\n\n\tSDLash_InitCursors();\n}\n\nvoid SDLash_Shutdown( void )\n{\n\tSDLash_FreeCursors();\n\tSDL_Quit();\n}\n"
  },
  {
    "path": "engine/platform/stub/net_stub.h",
    "content": "/*\nnet_stub.h - stub BSD sockets\nCopyright (C) 2020 mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef NET_STUB_H\n#define NET_STUB_H\n\n#define INVALID_SOCKET (-1)\n#define SOCKET_ERROR (-1)\n#define SOCKET int\ntypedef int WSAsize_t;\nstruct in_addr {unsigned long s_addr;};\nstruct sockaddr_in{ short sin_family;unsigned short sin_port;struct in_addr sin_addr;};\nstruct sockaddr {short sa_family;int stub[32];};\nstruct hostent {int h_addr_list[1];};\nstruct timeval {long tv_sec;long tv_usec;};\n\n#define AF_INET 0\n#define INADDR_BROADCAST 0\n#define INADDR_ANY 0\n//! Network to host conversion for a word.\n#define ntohs(n) ( (((n) & 0xFF00) >> 8) | (((n) & 0x00FF) << 8) )\n//! Host to network conversion for a word.\n#define htons(n) ( (((n) & 0xFF00) >> 8) | (((n) & 0x00FF) << 8) )\n//! Network to host conversion for a double word.\n#define ntohl(n) ( (((n) & 0xFF000000) >> 24) | (((n) & 0x00FF0000) >> 8) \\\n\t | (((n) & 0x0000FF00) << 8) | (((n) & 0x000000FF) << 24) )\n//! Host to network conversion for a double word.\n#define htonl(n) ( (((n) & 0xFF000000) >> 24) | (((n) & 0x00FF0000) >> 8) \\\n\t | (((n) & 0x0000FF00) << 8) | (((n) & 0x000000FF) << 24) )\n\n#define gethostbyname(...) NULL\n#define inet_addr(...) (-1)\n#define recvfrom(...) (-1)\n#define sendto(...) (-1)\n#define socket(...) (-1)\n#define ioctlsocket(...) (-1)\n#define setsockopt(...) (-1)\n#define bind(...) (-1)\n#define gethostname(...) (-1)\n#define getsockname(...) (-1)\n#define connect(...) (-1)\n#define send(...) (-1)\n#define recv(...) (-1)\n#define bind(...) (-1)\n#define closesocket(...) (-1)\n#define select(...) (-1)\n\n#define WSAGetLastError() (22) //ENETDOWN\n#define WSAEINTR           1 //EINTR\n#define WSAEBADF           2 //EBADF\n#define WSAEACCES          3 //EACCES\n#define WSAEFAULT          4 //EFAULT\n#define WSAEINVAL          5 //EINVAL\n#define WSAEMFILE          6 //EMFILE\n#define WSAEWOULDBLOCK     7 //EWOULDBLOCK\n#define WSAEINPROGRESS     8 //EINPROGRESS\n#define WSAEALREADY        9 //EALREADY\n#define WSAENOTSOCK        10 //ENOTSOCK\n#define WSAEDESTADDRREQ    11 //EDESTADDRREQ\n#define WSAEMSGSIZE        12 //EMSGSIZE\n#define WSAEPROTOTYPE      13 //EPROTOTYPE\n#define WSAENOPROTOOPT     14 //ENOPROTOOPT\n#define WSAEPROTONOSUPPORT 15 //EPROTONOSUPPORT\n#define WSAESOCKTNOSUPPORT 16 //ESOCKTNOSUPPORT\n#define WSAEOPNOTSUPP      17 //EOPNOTSUPP\n#define WSAEPFNOSUPPORT    18 //EPFNOSUPPORT\n#define WSAEAFNOSUPPORT    19 //EAFNOSUPPORT\n#define WSAEADDRINUSE      20 //EADDRINUSE\n#define WSAEADDRNOTAVAIL   21 //EADDRNOTAVAIL\n#define WSAENETDOWN        22 //ENETDOWN\n#define WSAENETUNREACH     23 //ENETUNREACH\n#define WSAENETRESET       24 //ENETRESET\n#define WSAECONNABORTED    25 //ECONNABORTED\n#define WSAECONNRESET      26 //ECONNRESET\n#define WSAENOBUFS         27 //ENOBUFS\n#define WSAEISCONN         28 //EISCONN\n#define WSAENOTCONN        29 //ENOTCONN\n#define WSAESHUTDOWN       30 //ESHUTDOWN\n#define WSAETOOMANYREFS    31 //ETOOMANYREFS\n#define WSAETIMEDOUT       32 //ETIMEDOUT\n#define WSAECONNREFUSED    33 //ECONNREFUSED\n#define WSAELOOP           34 //ELOOP\n#define WSAENAMETOOLONG    35 //ENAMETOOLONG\n#define WSAEHOSTDOWN       36 //EHOSTDOWN\n\n#endif // NET_STUB_H\n"
  },
  {
    "path": "engine/platform/stub/s_stub.c",
    "content": "/*\ns_backend.c - sound hardware output\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n\n#ifndef XASH_DEDICATED\n#include \"common.h\"\n#if XASH_SOUND == SOUND_NULL\n\n#include \"sound.h\"\n\n#define SAMPLE_16BIT_SHIFT\t\t1\n#define SECONDARY_BUFFER_SIZE\t\t0x10000\n\n/*\n=======================================================================\nGlobal variables. Must be visible to window-procedure function\nso it can unlock and free the data block after it has been played.\n=======================================================================\n*/\n\ndma_t\t\t\tdma;\n\nvoid S_Activate( qboolean active )\n{\n}\n\n\n/*\n==================\nSNDDMA_Init\n\nTry to find a sound device to mix for.\nReturns false if nothing is found.\n==================\n*/\nqboolean SNDDMA_Init( void )\n{\n\tMsg( \"Audio is not enabled\\n\" );\n\treturn false;\n}\n\n/*\n==============\nSNDDMA_BeginPainting\n\nMakes sure dma.buffer is valid\n===============\n*/\nvoid SNDDMA_BeginPainting( void )\n{\n\n}\n\n/*\n==============\nSNDDMA_Submit\n\nSend sound to device if buffer isn't really the dma buffer\nAlso unlocks the dsound buffer\n===============\n*/\nvoid SNDDMA_Submit( void )\n{\n\n}\n\n/*\n==============\nSNDDMA_Shutdown\n\nReset the sound device for exiting\n===============\n*/\nvoid SNDDMA_Shutdown( void )\n{\n\tCon_Printf(\"Shutting down audio.\\n\");\n\tdma.initialized = false;\n\n\tif (dma.buffer) {\n\t\t Z_Free(dma.buffer);\n\t\t dma.buffer = NULL;\n\t}\n}\n\nqboolean VoiceCapture_Init( void )\n{\n\treturn false;\n}\n\nqboolean VoiceCapture_Activate( qboolean activate )\n{\n\treturn false;\n}\n\nqboolean VoiceCapture_Lock( qboolean lock )\n{\n\treturn false;\n}\n\nvoid VoiceCapture_Shutdown( void )\n{\n\n}\n\n#endif\n#endif\n"
  },
  {
    "path": "engine/platform/win32/con_win.c",
    "content": "/*\nsys_con.c - win32 dedicated and developer console\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n\n/*\n===============================================================================\n\nWIN32 CONSOLE\n\n===============================================================================\n*/\n\n// console defines\n#define COMMAND_HISTORY\t64\t// system console keep more commands than game console\n\ntypedef struct\n{\n\tstring\t\ttitle;\n\tstring\t\tpreviousTitle;\n\tUINT\t\tpreviousCodePage;\n\tUINT\t\tpreviousOutputCodePage;\n\tHWND\t\thWnd;\n\tHANDLE\t\thInput;\n\tHANDLE\t\thOutput;\n\tint\t\t\tinputLine;\n\tint\t\t\tbrowseLine;\n\tint\t\t\tcursorPosition;\n\tint\t\t\ttotalLines;\n\tint\t\t\tsavedConsoleTextLen;\n\tint\t\t\tconsoleTextLen;\n\tstring\t\tconsoleText;\n\tstring\t\tsavedConsoleText;\n\tstring\t\tstatusLine;\n\tstring\t\tlineBuffer[COMMAND_HISTORY];\n\tqboolean\tinputEnabled;\n\tqboolean\tconsoleVisible;\n\tqboolean\tattached;\n} WinConData;\n\nstatic WinConData\ts_wcd;\nstatic WORD g_color_table[8] =\n{\nFOREGROUND_INTENSITY,\t\t\t\t\t\t\t\t\t// black\nFOREGROUND_RED,\t\t\t\t\t\t\t\t\t\t\t// red\nFOREGROUND_GREEN,\t\t\t\t\t\t\t\t\t\t// green\nFOREGROUND_RED | FOREGROUND_GREEN,\t\t\t\t\t\t// yellow\nFOREGROUND_BLUE | FOREGROUND_INTENSITY,\t\t\t\t\t// blue\nFOREGROUND_GREEN | FOREGROUND_BLUE,\t\t\t\t\t\t// cyan\nFOREGROUND_RED | FOREGROUND_BLUE,\t\t\t\t\t\t// magenta\nFOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,\t// default color (white)\n};\n\nstatic BOOL WINAPI Wcon_HandleConsole(DWORD CtrlType)\n{\n\treturn TRUE;\n}\n\nstatic void Wcon_PrintInternal( const char *msg, int length )\n{\n\tchar *pTemp;\n\tDWORD cbWritten;\n\tconst char *pMsgString;\n\tstatic char tmpBuf[2048];\n\tstatic char szOutput[2048];\n\n\tQ_strncpy( szOutput, msg, length ? (length + 1) : ( sizeof( szOutput ) - 1 ));\n\tif( length )\n\t\tszOutput[length + 1] = '\\0';\n\telse\n\t\tszOutput[sizeof( szOutput ) - 1] = '\\0';\n\n\tpTemp = tmpBuf;\n\tpMsgString = szOutput;\n\twhile( pMsgString && *pMsgString )\n\t{\n\t\tif( IsColorString( pMsgString ))\n\t\t{\n\t\t\tif (( pTemp - tmpBuf ) > 0 )\n\t\t\t{\n\t\t\t\t// dump accumulated text before change color\n\t\t\t\t*pTemp = 0; // terminate string\n\t\t\t\tWriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );\n\t\t\t\tpTemp = tmpBuf;\n\t\t\t}\n\n\t\t\t// set new color\n\t\t\tSetConsoleTextAttribute( s_wcd.hOutput, g_color_table[ColorIndex(*(pMsgString + 1))] );\n\t\t\tpMsgString += 2; // skip color info\n\t\t}\n\t\telse if(( pTemp - tmpBuf ) < sizeof( tmpBuf ) - 1 )\n\t\t{\n\t\t\t*pTemp++ = *pMsgString++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// temp buffer is full, dump it now\n\t\t\t*pTemp = 0; // terminate string\n\t\t\tWriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );\n\t\t\tpTemp = tmpBuf;\n\t\t}\n\t}\n\n\t// check for last portion\n\tif (( pTemp - tmpBuf ) > 0 )\n\t{\n\t\t// dump accumulated text\n\t\t*pTemp = 0; // terminate string\n\t\tWriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );\n\t\tpTemp = tmpBuf;\n\t}\n\n\t// restore white color\n\tSetConsoleTextAttribute( s_wcd.hOutput, g_color_table[7] );\n}\n\nvoid Wcon_ShowConsole( qboolean show )\n{\n\tif( !s_wcd.hWnd || show == s_wcd.consoleVisible || s_wcd.attached )\n\t\treturn;\n\n\ts_wcd.consoleVisible = show;\n\tif( show )\n\t\tShowWindow( s_wcd.hWnd, SW_SHOW );\n\telse\n\t\tShowWindow( s_wcd.hWnd, SW_HIDE );\n}\n\nvoid Wcon_DisableInput( void )\n{\n\tif( host.type != HOST_DEDICATED || !s_wcd.hWnd )\n\t\treturn;\n\n\ts_wcd.inputEnabled = false;\n}\n\nstatic void Wcon_SetInputText( const char *inputText )\n{\n\tif( host.type != HOST_DEDICATED )\n\t\treturn;\n\n\twhile( s_wcd.consoleTextLen-- )\n\t{\n\t\tWcon_PrintInternal( \"\\b \\b\", 0 );\n\t}\n\tWcon_PrintInternal( inputText, 0 );\n\tQ_strncpy( s_wcd.consoleText, inputText, sizeof(s_wcd.consoleText) - 1 );\n\ts_wcd.consoleTextLen = Q_strlen( inputText );\n\ts_wcd.cursorPosition = s_wcd.consoleTextLen;\n\ts_wcd.browseLine = s_wcd.inputLine;\n}\n\nstatic void Wcon_Clear_f( void )\n{\n\tCONSOLE_SCREEN_BUFFER_INFO csbi;\n\tSMALL_RECT scrollRect;\n\tCOORD scrollTarget;\n\tCHAR_INFO fill;\n\n\tif( host.type != HOST_DEDICATED )\n\t\treturn;\n\n\tif( !GetConsoleScreenBufferInfo( s_wcd.hOutput, &csbi ))\n\t{\n\t\treturn;\n\t}\n\n\tscrollRect.Left = 0;\n\tscrollRect.Top = 0;\n\tscrollRect.Right = csbi.dwSize.X;\n\tscrollRect.Bottom = csbi.dwSize.Y;\n\tscrollTarget.X = 0;\n\tscrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);\n\tfill.Char.UnicodeChar = TEXT(' ');\n\tfill.Attributes = csbi.wAttributes;\n\tScrollConsoleScreenBuffer( s_wcd.hOutput, &scrollRect, NULL, scrollTarget, &fill );\n\n\tcsbi.dwCursorPosition.X = 0;\n\tcsbi.dwCursorPosition.Y = 0;\n\tSetConsoleCursorPosition( s_wcd.hOutput, csbi.dwCursorPosition );\n\n\ts_wcd.consoleText[0] = '\\0';\n\ts_wcd.consoleTextLen = 0;\n\ts_wcd.cursorPosition = 0;\n\ts_wcd.inputLine = 0;\n\ts_wcd.browseLine = 0;\n\ts_wcd.totalLines = 0;\n\tWcon_PrintInternal( \"\\n\", 0 );\n}\n\nstatic void Wcon_EventUpArrow()\n{\n\tint nLastCommandInHistory = s_wcd.inputLine + 1;\n\tif( nLastCommandInHistory > s_wcd.totalLines )\n\t\tnLastCommandInHistory = 0;\n\n\tif( s_wcd.browseLine == nLastCommandInHistory )\n\t\treturn;\n\n\tif( s_wcd.browseLine == s_wcd.inputLine )\n\t{\n\t\tif( s_wcd.consoleTextLen > 0 )\n\t\t{\n\t\t\tQ_strncpy( s_wcd.savedConsoleText, s_wcd.consoleText, s_wcd.consoleTextLen );\n\t\t}\n\t\ts_wcd.savedConsoleTextLen = s_wcd.consoleTextLen;\n\t}\n\n\ts_wcd.browseLine--;\n\tif( s_wcd.browseLine < 0 )\n\t{\n\t\ts_wcd.browseLine = s_wcd.totalLines - 1;\n\t}\n\n\twhile( s_wcd.consoleTextLen-- )\n\t{\n\t\tWcon_PrintInternal( \"\\b \\b\", 0 );\n\t}\n\n\tWcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 );\n\tQ_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText ));\n\ts_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] );\n\ts_wcd.cursorPosition = s_wcd.consoleTextLen;\n}\n\nstatic void Wcon_EventDownArrow()\n{\n\tif( s_wcd.browseLine == s_wcd.inputLine )\n\t\treturn;\n\n\tif( ++s_wcd.browseLine > s_wcd.totalLines )\n\t\ts_wcd.browseLine = 0;\n\n\twhile( s_wcd.consoleTextLen-- )\n\t{\n\t\tWcon_PrintInternal( \"\\b \\b\", 0 );\n\t}\n\n\tif( s_wcd.browseLine == s_wcd.inputLine )\n\t{\n\t\tif( s_wcd.savedConsoleTextLen > 0 )\n\t\t{\n\t\t\tQ_strncpy( s_wcd.consoleText, s_wcd.savedConsoleText, s_wcd.savedConsoleTextLen );\n\t\t\tWcon_PrintInternal( s_wcd.consoleText, s_wcd.savedConsoleTextLen );\n\t\t}\n\t\ts_wcd.consoleTextLen = s_wcd.savedConsoleTextLen;\n\t}\n\telse\n\t{\n\t\tWcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 );\n\t\tQ_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText ));\n\t\ts_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] );\n\t}\n\ts_wcd.cursorPosition = s_wcd.consoleTextLen;\n}\n\nstatic void Wcon_EventLeftArrow()\n{\n\tif( s_wcd.cursorPosition == 0 )\n\t\treturn;\n\n\tWcon_PrintInternal( \"\\b\", 0 );\n\ts_wcd.cursorPosition--;\n}\n\nstatic void Wcon_EventRightArrow()\n{\n\tif( s_wcd.cursorPosition == s_wcd.consoleTextLen )\n\t\treturn;\n\n\tWcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, 1 );\n\ts_wcd.cursorPosition++;\n}\n\nstatic int Wcon_EventNewline()\n{\n\tint nLen;\n\n\tnLen = 0;\n\tWcon_PrintInternal( \"\\n\", 0 );\n\tif( s_wcd.consoleTextLen )\n\t{\n\t\tnLen = s_wcd.consoleTextLen;\n\n\t\ts_wcd.consoleText[s_wcd.consoleTextLen] = '\\0';\n\t\ts_wcd.consoleTextLen = 0;\n\t\ts_wcd.cursorPosition = 0;\n\n\t\tif (( s_wcd.inputLine == 0 ) || ( Q_strcmp( s_wcd.lineBuffer[s_wcd.inputLine - 1], s_wcd.consoleText )))\n\t\t{\n\t\t\tQ_strncpy( s_wcd.lineBuffer[s_wcd.inputLine], s_wcd.consoleText, sizeof( s_wcd.consoleText ));\n\t\t\ts_wcd.inputLine++;\n\n\t\t\tif( s_wcd.inputLine > s_wcd.totalLines )\n\t\t\t\ts_wcd.totalLines = s_wcd.inputLine;\n\n\t\t\tif( s_wcd.inputLine >= COMMAND_HISTORY )\n\t\t\t\ts_wcd.inputLine = 0;\n\t\t}\n\t\ts_wcd.browseLine = s_wcd.inputLine;\n\t}\n\treturn nLen;\n}\n\nstatic void Wcon_EventBackspace()\n{\n\tint nCount;\n\n\tif( s_wcd.cursorPosition < 1 )\n\t{\n\t\treturn;\n\t}\n\n\ts_wcd.consoleTextLen--;\n\ts_wcd.cursorPosition--;\n\n\tWcon_PrintInternal( \"\\b\", 0 );\n\n\tfor( nCount = s_wcd.cursorPosition; nCount < s_wcd.consoleTextLen; ++nCount )\n\t{\n\t\ts_wcd.consoleText[nCount] = s_wcd.consoleText[nCount + 1];\n\t\tWcon_PrintInternal( s_wcd.consoleText + nCount, 1 );\n\t}\n\n\tWcon_PrintInternal( \" \", 0 );\n\n\tnCount = s_wcd.consoleTextLen;\n\twhile( nCount >= s_wcd.cursorPosition )\n\t{\n\t\tWcon_PrintInternal( \"\\b\", 0 );\n\t\tnCount--;\n\t}\n\n\ts_wcd.browseLine = s_wcd.inputLine;\n}\n\nstatic void Wcon_EventTab()\n{\n\ts_wcd.consoleText[s_wcd.consoleTextLen] = '\\0';\n\tCmd_AutoComplete( s_wcd.consoleText );\n\tWcon_SetInputText( s_wcd.consoleText );\n}\n\nstatic void Wcon_EventCharacter(char c)\n{\n\tint nCount;\n\n\tif( s_wcd.consoleTextLen >= ( sizeof( s_wcd.consoleText ) - 2 ))\n\t{\n\t\treturn;\n\t}\n\n\tnCount = s_wcd.consoleTextLen;\n\twhile( nCount > s_wcd.cursorPosition )\n\t{\n\t\ts_wcd.consoleText[nCount] = s_wcd.consoleText[nCount - 1];\n\t\tnCount--;\n\t}\n\n\ts_wcd.consoleText[s_wcd.cursorPosition] = c;\n\tWcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, s_wcd.consoleTextLen - s_wcd.cursorPosition + 1 );\n\ts_wcd.consoleTextLen++;\n\ts_wcd.cursorPosition++;\n\n\tnCount = s_wcd.consoleTextLen;\n\twhile( nCount > s_wcd.cursorPosition )\n\t{\n\t\tWcon_PrintInternal( \"\\b\", 0 );\n\t\tnCount--;\n\t}\n\n\ts_wcd.browseLine = s_wcd.inputLine;\n}\n\nstatic void Wcon_UpdateStatusLine()\n{\n\tCOORD coord;\n\tWORD wAttrib;\n\tDWORD dwWritten;\n\n\tcoord.X = 0;\n\tcoord.Y = 0;\n\twAttrib = g_color_table[5] | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;\n\n\tFillConsoleOutputCharacter( s_wcd.hOutput, ' ', 80, coord, &dwWritten );\n\tFillConsoleOutputAttribute( s_wcd.hOutput, wAttrib, 80, coord, &dwWritten );\n\tWriteConsoleOutputCharacter( s_wcd.hOutput, s_wcd.statusLine, Q_strlen(s_wcd.statusLine), coord, &dwWritten );\n}\n\nstatic char *Wcon_KeyEvent( int key, WCHAR character )\n{\n\tint nLen;\n\tchar inputBuffer[1024];\n\n\tswitch( key )\n\t{\n\tcase VK_UP:\n\t\tWcon_EventUpArrow();\n\t\treturn NULL;\n\tcase VK_DOWN:\n\t\tWcon_EventDownArrow();\n\t\treturn NULL;\n\tcase VK_LEFT:\n\t\tWcon_EventLeftArrow();\n\t\treturn NULL;\n\tcase VK_RIGHT:\n\t\tWcon_EventRightArrow();\n\t\treturn NULL;\n\t}\n\n\tswitch( character )\n\t{\n\t\tcase '\\r':\t// Enter\n\t\t\tnLen = Wcon_EventNewline();\n\t\t\tif (nLen)\n\t\t\t{\n\t\t\t\treturn s_wcd.consoleText;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase '\\b':\t// Backspace\n\t\t\tWcon_EventBackspace();\n\t\t\tbreak;\n\t\tcase '\\t':\t// TAB\n\t\t\tWcon_EventTab();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// TODO implement converting wide chars to UTF-8 and properly handling it\n\t\t\tif (( character >= ' ' ) && ( character <= '~' ))\n\t\t\t{\n\t\t\t\tWcon_EventCharacter(character);\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn NULL;\n}\n\n/*\n===============================================================================\n\nWIN32 IO\n\n===============================================================================\n*/\n\n/*\n================\nCon_WinPrint\n\nprint into window console\n================\n*/\nvoid Wcon_WinPrint( const char *pMsg )\n{\n\tif( !s_wcd.hWnd )\n\t\treturn;\n\n\tint nLen;\n\tif( s_wcd.consoleTextLen )\n\t{\n\t\tnLen = s_wcd.consoleTextLen;\n\t\twhile (nLen--)\n\t\t{\n\t\t\tWcon_PrintInternal( \"\\b \\b\", 0 );\n\t\t}\n\t}\n\n\tWcon_PrintInternal( pMsg, 0 );\n\n\tif( s_wcd.consoleTextLen )\n\t{\n\t\tWcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen );\n\t}\n\n\tif( !s_wcd.attached ) \n\t\tWcon_UpdateStatusLine();\n}\n\n/*\n================\nCon_CreateConsole\n\ncreate win32 console\n================\n*/\nvoid Wcon_CreateConsole( qboolean con_showalways )\n{\n\tif( host.type == HOST_NORMAL )\n\t{\n\t\tQ_strncpy( s_wcd.title, XASH_ENGINE_NAME \" \" XASH_VERSION, sizeof( s_wcd.title ));\n\t}\n\telse // dedicated console\n\t{\n\t\tQ_strncpy( s_wcd.title, XASH_DEDICATED_SERVER_NAME \" \" XASH_VERSION, sizeof( s_wcd.title ));\n\t}\n\n\ts_wcd.attached = ( AttachConsole( ATTACH_PARENT_PROCESS ) != 0 );\n\tif( s_wcd.attached )\n\t{\n\t\tGetConsoleTitle( &s_wcd.previousTitle, sizeof( s_wcd.previousTitle ));\n\t\ts_wcd.previousCodePage = GetConsoleCP();\n\t\ts_wcd.previousOutputCodePage = GetConsoleOutputCP();\n\t}\n\telse\n\t{\n\t\tif( host.type != HOST_DEDICATED && host_developer.value == DEV_NONE )\n\t\t\treturn; // don't initialize console in case of regular game startup, it's useless anyway\n\t\telse\n\t\t\tAllocConsole();\n\t}\n\n\tSetConsoleTitle( s_wcd.title );\n\tSetConsoleCP( CP_UTF8 );\n\tSetConsoleOutputCP( CP_UTF8 );\n\n\ts_wcd.hWnd = GetConsoleWindow();\n\ts_wcd.hInput = GetStdHandle( STD_INPUT_HANDLE );\n\ts_wcd.hOutput = GetStdHandle( STD_OUTPUT_HANDLE );\n\ts_wcd.inputEnabled = true;\n\t\n\tif( !SetConsoleCtrlHandler( &Wcon_HandleConsole, TRUE ))\n\t{\n\t\tCon_Reportf( S_ERROR \"Couldn't attach console handler function\\n\" );\n\t\treturn;\n\t}\n\n\tif( !s_wcd.attached )\n\t{ \n\t\tSetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW );\n\n\t\t// show console if needed\n\t\tif( con_showalways )\n\t\t{\n\t\t\t// make console visible\n\t\t\tShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT );\n\t\t\tUpdateWindow( s_wcd.hWnd );\n\t\t\tSetForegroundWindow( s_wcd.hWnd );\n\t\t\tSetFocus( s_wcd.hWnd );\n\t\t\ts_wcd.consoleVisible = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ts_wcd.consoleVisible = false;\n\t\t\tShowWindow( s_wcd.hWnd, SW_HIDE );\n\t\t}\n\t}\n}\n\n/*\n================\nCon_InitConsoleCommands\n\nregister console commands (dedicated only)\n================\n*/\nvoid Wcon_InitConsoleCommands( void )\n{\n\tif( host.type != HOST_DEDICATED || !s_wcd.hWnd )\n\t\treturn;\n\n\tCmd_AddCommand( \"clear\", Wcon_Clear_f, \"clear console history\" );\n}\n\n/*\n================\nCon_DestroyConsole\n\ndestroy win32 console\n================\n*/\nvoid Wcon_DestroyConsole( void )\n{\n\t// last text message into console or log\n\tCon_Reportf( \"%s: Unloading xash.dll\\n\", __func__ );\n\n\tif( !s_wcd.attached )\n\t{\n\t\tif( s_wcd.hWnd )\n\t\t{\n\t\t\tShowWindow( s_wcd.hWnd, SW_HIDE );\n\t\t\ts_wcd.hWnd = 0;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// reverts title & code page for console window that was before starting Xash3D\n\t\tSetConsoleCP( s_wcd.previousCodePage );\n\t\tSetConsoleOutputCP( s_wcd.previousOutputCodePage );\n\t\tSetConsoleTitle( &s_wcd.previousTitle );\n\t\tCon_Printf( \"Press Enter to continue...\\n\" );\n\t}\n\n\tFreeConsole();\n}\n\n/*\n================\nCon_Input\n\nreturned input text\n================\n*/\nchar *Wcon_Input( void )\n{\n\tDWORD i;\n\tDWORD eventsCount;\n\tstatic INPUT_RECORD events[1024];\n\t\n\tif( !s_wcd.inputEnabled || !s_wcd.hWnd )\n\t\treturn NULL;\n\n\twhile( true )\n\t{\n\t\tif( !GetNumberOfConsoleInputEvents( s_wcd.hInput, &eventsCount ))\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif( eventsCount <= 0 )\n\t\t\tbreak;\n\n\t\tif( !ReadConsoleInputW( s_wcd.hInput, events, ARRAYSIZE( events ), &eventsCount ))\n\t\t{\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif( eventsCount == 0 )\n\t\t\treturn NULL;\n\n\t\tfor( i = 0; i < eventsCount; i++ )\n\t\t{\n\t\t\tINPUT_RECORD *pRec = &events[i];\n\t\t\tif( pRec->EventType != KEY_EVENT )\n\t\t\t\tcontinue;\n\n\t\t\tif( pRec->Event.KeyEvent.bKeyDown )\n\t\t\t\treturn Wcon_KeyEvent( pRec->Event.KeyEvent.wVirtualKeyCode, pRec->Event.KeyEvent.uChar.UnicodeChar );\n\t\t}\n\t}\n\treturn NULL;\n}\n\n/*\n================\nPlatform_SetStatus\n\nset server status string in console\n================\n*/\nvoid Platform_SetStatus( const char *pStatus )\n{\n\tif( s_wcd.attached || !s_wcd.hWnd )\n\t\treturn;\n\n\tQ_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 );\n\ts_wcd.statusLine[sizeof( s_wcd.statusLine ) - 2] = '\\0';\n\tWcon_UpdateStatusLine();\n}\n"
  },
  {
    "path": "engine/platform/win32/crash_win.c",
    "content": "/*\ncrashhandler.c - advanced crashhandler\nCopyright (C) 2016 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include \"input.h\"\n\n#define DBGHELP 1 // we always enable dbghelp.dll on Windows targets\n\n#if XASH_SDL\n#include <SDL.h>\n#endif // XASH_SDL\n\n#if DBGHELP\n#include <winnt.h>\n#include <dbghelp.h>\n#include <psapi.h>\n#include <time.h>\n\n#ifndef XASH_SDL\ntypedef ULONG_PTR DWORD_PTR, *PDWORD_PTR;\n#endif\n\nstatic int Sys_ModuleName( HANDLE process, char *name, void *address, int len )\n{\n\tstatic HMODULE     *moduleArray;\n\tstatic unsigned int moduleCount;\n\tDWORD       bytesRequired;\n\n\tif( len < 3 )\n\t\treturn 0;\n\n\tif( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ))\n\t{\n\t\tif( bytesRequired )\n\t\t{\n\t\t\tLPBYTE moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired );\n\n\t\t\tif( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ))\n\t\t\t{\n\t\t\t\tmoduleCount = bytesRequired / sizeof( HMODULE );\n\t\t\t\tmoduleArray = (HMODULE *)moduleArrayBytes;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( int i = 0; i < moduleCount; i++ )\n\t{\n\t\tMODULEINFO info;\n\t\tGetModuleInformation( process, moduleArray[i], &info, sizeof( MODULEINFO ));\n\n\t\tif( address > info.lpBaseOfDll && (DWORD64)address < (DWORD64)info.lpBaseOfDll + (DWORD64)info.SizeOfImage )\n\t\t\treturn GetModuleBaseName( process, moduleArray[i], name, len );\n\t}\n\n\treturn Q_snprintf( name, len, \"???\" );\n}\n\nstatic void Sys_StackTrace( PEXCEPTION_POINTERS pInfo )\n{\n\tchar    message[8192]; // match *nix Sys_Crash\n\tHANDLE  process = GetCurrentProcess();\n\tHANDLE  thread = GetCurrentThread();\n\tDWORD   dline = 0;\n\tDWORD   options = SymGetOptions();\n\tCONTEXT context = *pInfo->ContextRecord;\n\tIMAGEHLP_LINE64 line = { .SizeOfStruct = sizeof( IMAGEHLP_LINE64 ), };\n\n\tSetBits( options, SYMOPT_DEBUG | SYMOPT_LOAD_LINES );\n\tSymSetOptions( options );\n\n\tSymInitialize( process, NULL, TRUE );\n\n#ifdef _M_IX86\n\tDWORD image = IMAGE_FILE_MACHINE_I386;\n\tSTACKFRAME64 stackframe =\n\t{\n\t\t.AddrPC.Offset = context.Eip,\n\t\t.AddrPC.Mode = AddrModeFlat,\n\t\t.AddrFrame.Offset = context.Ebp,\n\t\t.AddrFrame.Mode = AddrModeFlat,\n\t\t.AddrStack.Offset = context.Esp,\n\t\t.AddrStack.Mode = AddrModeFlat,\n\t};\n#elif _M_X64\n\tDWORD image = IMAGE_FILE_MACHINE_AMD64;\n\tSTACKFRAME64 stackframe =\n\t{\n\t\t.AddrPC.Offset = context.Rip,\n\t\t.AddrPC.Mode = AddrModeFlat,\n\t\t.AddrFrame.Offset = context.Rsp,\n\t\t.AddrFrame.Mode = AddrModeFlat,\n\t\t.AddrStack.Offset = context.Rsp,\n\t\t.AddrStack.Mode = AddrModeFlat,\n\t};\n#elif _M_IA64\n\tDWORD image = IMAGE_FILE_MACHINE_IA64;\n\tSTACKFRAME64 stackframe =\n\t{\n\t\t.AddrPC.Offset = context.StIIP,\n\t\t.AddrPC.Mode = AddrModeFlat,\n\t\t.AddrFrame.Offset = context.IntSp,\n\t\t.AddrFrame.Mode = AddrModeFlat,\n\t\t.AddrBStore.Offset = context.RsBSP,\n\t\t.AddrBStore.Mode = AddrModeFlat,\n\t\t.AddrStack.Offset = context.IntSp,\n\t\t.AddrStack.Mode = AddrModeFlat,\n\t};\n#elif _M_ARM\n\tDWORD image = IMAGE_FILE_MACHINE_ARMNT;\n\tSTACKFRAME64 stackframe =\n\t{\n\t\t.AddrPC.Offset = context.Pc,\n\t\t.AddrPC.Mode = AddrModeFlat,\n\t\t.AddrFrame.Offset = context.R11,\n\t\t.AddrFrame.Mode = AddrModeFlat,\n\t\t.AddrStack.Offset = context.Sp,\n\t\t.AddrStack.Mode = AddrModeFlat,\n\t};\n#elif _M_ARM64\n\tDWORD image = IMAGE_FILE_MACHINE_ARM64;\n\tSTACKFRAME64 stackframe =\n\t{\n\t\t.AddrPC.Offset = context.Pc,\n\t\t.AddrPC.Mode = AddrModeFlat,\n\t\t.AddrFrame.Offset = context.Fp,\n\t\t.AddrFrame.Mode = AddrModeFlat,\n\t\t.AddrStack.Offset = context.Sp,\n\t\t.AddrStack.Mode = AddrModeFlat,\n\t};\n#elif\n#error\n#endif\n\n\tint len = Q_snprintf( message, sizeof( message ), \"Ver: \" XASH_ENGINE_NAME \" \" XASH_VERSION \" (build %i-%s-%s, %s-%s)\\n\",\n\t\tQ_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());\n\n\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"Crash: address %p, code %p\\n\",\n\t\tpInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode );\n\n\tif( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ))\n\t{\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"Exception: %s:%d:%d\\n\",\n\t\t\tCOM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline );\n\t}\n\n\tif( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ))\n\t{\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"PC: %s:%d:%d\\n\",\n\t\t\tCOM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline );\n\t}\n\n\tif( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ))\n\t{\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"Frame: %s:%d:%d\\n\",\n\t\t\tCOM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline );\n\t}\n\n\tfor( size_t i = 0; i < 25; i++ )\n\t{\n\t\tchar buffer[sizeof( SYMBOL_INFO ) + MAX_SYM_NAME * sizeof( TCHAR )];\n\t\tPSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;\n\t\tBOOL result = StackWalk64( image, process, thread,\n\t\t\t&stackframe, &context, NULL,\n\t\t\tSymFunctionTableAccess64, SymGetModuleBase64, NULL );\n\t\tDWORD64 displacement = 0;\n\n\t\tif( !result )\n\t\t\tbreak;\n\n\t\tsymbol->SizeOfStruct = sizeof( SYMBOL_INFO );\n\t\tsymbol->MaxNameLen = MAX_SYM_NAME;\n\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"%2d: %p\",\n\t\t\ti, (void*)stackframe.AddrPC.Offset );\n\t\tif( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ))\n\t\t{\n\t\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \" %s \", symbol->Name );\n\t\t}\n\n\t\tif( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ))\n\t\t{\n\t\t\tlen += Q_snprintf( message + len, sizeof( message ) - len,\"(%s:%d:%d) \",\n\t\t\t\tCOM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline );\n\t\t}\n\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \"(\" );\n\t\tlen += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, sizeof( message ) - len );\n\t\tlen += Q_snprintf( message + len, sizeof( message ) - len, \")\\n\" );\n\t}\n\n#if XASH_SDL == 2\n\tif( host.type != HOST_DEDICATED ) // let system to restart server automaticly\n\t\tSDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, \"Sys_Crash\", message, NULL );\n#endif\n\n\tSys_PrintLog( message );\n\n\tSymCleanup( process );\n}\n\nstatic void Sys_GetProcessName( char *processName, size_t bufferSize )\n{\n\tchar fullpath[MAX_PATH];\n\n\tGetModuleBaseName( GetCurrentProcess(), NULL, fullpath, sizeof( fullpath ) - 1 );\n\tCOM_FileBase( fullpath, processName, bufferSize );\n}\n\nstatic void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName, size_t bufferSize )\n{\n\ttime_t currentUtcTime = time( NULL );\n\tstruct tm *currentLocalTime = localtime( &currentUtcTime );\n\n\tQ_snprintf( mdmpFileName, bufferSize, \"%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp\",\n\t\tprocessName,\n\t\tg_buildcommit,\n\t\tcurrentLocalTime->tm_year + 1900,\n\t\tcurrentLocalTime->tm_mon + 1,\n\t\tcurrentLocalTime->tm_mday,\n\t\tcurrentLocalTime->tm_hour,\n\t\tcurrentLocalTime->tm_min,\n\t\tcurrentLocalTime->tm_sec );\n}\n\nstatic qboolean Sys_WriteMinidump( PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType )\n{\n\tstring processName;\n\tstring mdmpFileName;\n\n\tSys_GetProcessName( processName, sizeof( processName ));\n\tSys_GetMinidumpFileName( processName, mdmpFileName, sizeof( mdmpFileName ));\n\n\tSetLastError( NOERROR );\n\tHANDLE fileHandle = CreateFile( mdmpFileName, GENERIC_WRITE, FILE_SHARE_WRITE,\n\t\tNULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );\n\n\tHRESULT errorCode = HRESULT_FROM_WIN32( GetLastError( ));\n\tif( !SUCCEEDED( errorCode ))\n\t{\n\t\tCloseHandle( fileHandle );\n\t\treturn false;\n\t}\n\n\tMINIDUMP_EXCEPTION_INFORMATION minidumpInfo =\n\t{\n\t\t.ThreadId = GetCurrentThreadId(),\n\t\t.ExceptionPointers = exceptionInfo,\n\t\t.ClientPointers = FALSE\n\t};\n\n\tqboolean status = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), fileHandle,\n\t\tminidumpType, &minidumpInfo, NULL, NULL);\n\n\tCloseHandle( fileHandle );\n\treturn status;\n}\n\n#endif /* DBGHELP */\n\nstatic LPTOP_LEVEL_EXCEPTION_FILTER  oldFilter;\n\nstatic long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo )\n{\n\t// save config\n\tif( host.status != HOST_CRASHED )\n\t{\n\t\t// check to avoid recursive call\n\t\thost.status = HOST_CRASHED;\n\n#if !XASH_DEDICATED\n\t\tIN_SetMouseGrab( false );\n#endif // XASH_SDL\n\n#if DBGHELP\n\t\tif( Sys_CheckParm( \"-minidumps\" ))\n\t\t{\n\t\t\tint minidumpFlags = MiniDumpWithDataSegs |\n\t\t\t\tMiniDumpWithCodeSegs |\n\t\t\t\tMiniDumpWithHandleData |\n\t\t\t\tMiniDumpWithFullMemory |\n\t\t\t\tMiniDumpWithFullMemoryInfo |\n\t\t\t\tMiniDumpWithIndirectlyReferencedMemory |\n\t\t\t\tMiniDumpWithThreadInfo |\n\t\t\t\tMiniDumpWithModuleHeaders;\n\n\t\t\tif( !Sys_WriteMinidump( pInfo, (MINIDUMP_TYPE)minidumpFlags ))\n\t\t\t{\n\t\t\t\t// fallback method, create minidump with minimal info in it\n\t\t\t\tSys_WriteMinidump( pInfo, MiniDumpWithDataSegs );\n\t\t\t}\n\t\t}\n\n\t\tSys_StackTrace( pInfo );\n#else\n\t\tSys_Warn( \"Sys_Crash: call %p at address %p\", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode );\n#endif\n\n\t\tif( host.type == HOST_NORMAL )\n\t\t\tCL_Crashed(); // tell client about crash\n\n\t\tif( host_developer.value <= 0 )\n\t\t{\n\t\t\t// no reason to call debugger in release build - just exit\n\t\t\tSys_Quit( \"crashed\" );\n\t\t\treturn EXCEPTION_CONTINUE_EXECUTION;\n\t\t}\n\n\t\t// all other states keep unchanged to let debugger find bug\n\t\tSys_DestroyConsole();\n\t}\n\n\tif( oldFilter )\n\t\treturn oldFilter( pInfo );\n\treturn EXCEPTION_CONTINUE_EXECUTION;\n}\n\nvoid Sys_SetupCrashHandler( const char *argv0 )\n{\n\tSetErrorMode( SEM_FAILCRITICALERRORS );\t// no abort/retry/fail errors\n\toldFilter = SetUnhandledExceptionFilter( Sys_Crash );\n}\n\nvoid Sys_RestoreCrashHandler( void )\n{\n\t// restore filter\n\tif( oldFilter ) SetUnhandledExceptionFilter( oldFilter );\n}\n"
  },
  {
    "path": "engine/platform/win32/lib_custom_win.c",
    "content": "/*\nlib_custom_win.c - win32 custom dlls loader\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"common.h\"\n\n#if XASH_LIB == LIB_WIN32 && XASH_X86\n#include \"lib_win.h\"\n\n#define NUMBER_OF_DIRECTORY_ENTRIES\t16\n#ifndef IMAGE_SIZEOF_BASE_RELOCATION\n#define IMAGE_SIZEOF_BASE_RELOCATION\t( sizeof( IMAGE_BASE_RELOCATION ))\n#endif\n\ntypedef struct\n{\n\tPIMAGE_NT_HEADERS\theaders;\n\tbyte\t\t*codeBase;\n\tvoid\t\t**modules;\n\tint\t\tnumModules;\n\tint\t\tinitialized;\n} MEMORYMODULE, *PMEMORYMODULE;\n\n// Protection flags for memory pages (Executable, Readable, Writeable)\nstatic int ProtectionFlags[2][2][2] =\n{\n{\n{ PAGE_NOACCESS, PAGE_WRITECOPY },\t\t// not executable\n{ PAGE_READONLY, PAGE_READWRITE },\n},\n{\n{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY },\t// executable\n{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE },\n},\n};\n\ntypedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved );\n\n#define GET_HEADER_DICTIONARY( module, idx )\t&(module)->headers->OptionalHeader.DataDirectory[idx]\n\nstatic void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module )\n{\n\tPIMAGE_SECTION_HEADER\tsection = IMAGE_FIRST_SECTION( module->headers );\n\tbyte\t\t\t*codeBase = module->codeBase;\n\tint\t\t\ti, size;\n\tbyte\t\t\t*dest;\n\n\tfor( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ )\n\t{\n\t\tif( section->SizeOfRawData == 0 )\n\t\t{\n\t\t\t// section doesn't contain data in the dll itself, but may define\n\t\t\t// uninitialized data\n\t\t\tsize = old_headers->OptionalHeader.SectionAlignment;\n\n\t\t\tif( size > 0 )\n\t\t\t{\n\t\t\t\tdest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE );\n\t\t\t\tsection->Misc.PhysicalAddress = (DWORD)dest;\n\t\t\t\tmemset( dest, 0, size );\n\t\t\t}\n\t\t\t// section is empty\n\t\t\tcontinue;\n\t\t}\n\n\t\t// commit memory block and copy data from dll\n\t\tdest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE );\n\t\tmemcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData );\n\t\tsection->Misc.PhysicalAddress = (DWORD)dest;\n\t}\n}\n\nstatic void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module )\n{\n\tPIMAGE_SECTION_HEADER\tsection = IMAGE_FIRST_SECTION(module->headers);\n\tbyte\t\t\t*codeBase = module->codeBase;\n\tint\t\t\ti, size;\n\n\tfor( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ )\n\t{\n\t\tif( section->SizeOfRawData == 0 )\n\t\t{\n\t\t\tsize = old_headers->OptionalHeader.SectionAlignment;\n\t\t\tif( size > 0 )\n\t\t\t{\n\t\t\t\tVirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT );\n\t\t\t\tsection->Misc.PhysicalAddress = 0;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tVirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT );\n\t\tsection->Misc.PhysicalAddress = 0;\n\t}\n}\n\nstatic void FinalizeSections( MEMORYMODULE *module )\n{\n\tPIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers );\n\tint\ti;\n\n\t// loop through all sections and change access flags\n\tfor( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ )\n\t{\n\t\tDWORD\tprotect, oldProtect, size;\n\t\tint\texecutable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;\n\t\tint\treadable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0;\n\t\tint\twriteable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;\n\n\t\tif( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE )\n\t\t{\n\t\t\t// section is not needed any more and can safely be freed\n\t\t\tVirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// determine protection flags based on characteristics\n\t\tprotect = ProtectionFlags[executable][readable][writeable];\n\t\tif( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED )\n\t\t\tprotect |= PAGE_NOCACHE;\n\n\t\t// determine size of region\n\t\tsize = section->SizeOfRawData;\n\n\t\tif( size == 0 )\n\t\t{\n\t\t\tif( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA )\n\t\t\t\tsize = module->headers->OptionalHeader.SizeOfInitializedData;\n\t\t\telse if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA )\n\t\t\t\tsize = module->headers->OptionalHeader.SizeOfUninitializedData;\n\t\t}\n\n\t\tif( size > 0 )\n\t\t{\n\t\t\t// change memory access flags\n\t\t\tif( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect ))\n\t\t\t\tSys_Error( \"error protecting memory page\\n\" );\n\t\t}\n\t}\n}\n\nstatic void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta )\n{\n\tPIMAGE_DATA_DIRECTORY\tdirectory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC );\n\tbyte\t\t\t*codeBase = module->codeBase;\n\tDWORD\t\t\ti;\n\n\tif( directory->Size > 0 )\n\t{\n\t\tPIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress );\n\t\tfor( ; relocation->VirtualAddress > 0; )\n\t\t{\n\t\t\tbyte\t*dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress );\n\t\t\tword\t*relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION );\n\n\t\t\tfor( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ )\n\t\t\t{\n\t\t\t\tDWORD\t*patchAddrHL;\n\t\t\t\tint\ttype, offset;\n\n\t\t\t\t// the upper 4 bits define the type of relocation\n\t\t\t\ttype = *relInfo >> 12;\n\t\t\t\t// the lower 12 bits define the offset\n\t\t\t\toffset = *relInfo & 0xfff;\n\n\t\t\t\tswitch( type )\n\t\t\t\t{\n\t\t\t\tcase IMAGE_REL_BASED_ABSOLUTE:\n\t\t\t\t\t// skip relocation\n\t\t\t\t\tbreak;\n\t\t\t\tcase IMAGE_REL_BASED_HIGHLOW:\n\t\t\t\t\t// change complete 32 bit address\n\t\t\t\t\tpatchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset );\n\t\t\t\t\t*patchAddrHL += delta;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tCon_Reportf( S_ERROR \"PerformBaseRelocation: unknown relocation: %d\\n\", type );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// advance to next relocation block\n\t\t\trelocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock );\n\t\t}\n\t}\n}\n\nFARPROC MemoryGetProcAddress( void *module, const char *name )\n{\n\tPIMAGE_DATA_DIRECTORY\tdirectory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT );\n\tbyte\t\t\t*codeBase = ((PMEMORYMODULE)module)->codeBase;\n\tPIMAGE_EXPORT_DIRECTORY\texports;\n\tint\t\t\tidx = -1;\n\tDWORD\t\t\ti, *nameRef;\n\tWORD\t\t\t*ordinal;\n\n\tif( directory->Size == 0 )\n\t{\n\t\t// no export table found\n\t\treturn NULL;\n\t}\n\n\texports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress );\n\n\tif( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 )\n\t{\n\t\t// DLL doesn't export anything\n\t\treturn NULL;\n\t}\n\n\t// search function name in list of exported names\n\tnameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames );\n\tordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals );\n\n\tfor( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ )\n\t{\n\t\t// GetProcAddress case insensative ?????\n\t\tif( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef )))\n\t\t{\n\t\t\tidx = *ordinal;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( idx == -1 )\n\t{\n\t\t// exported symbol not found\n\t\treturn NULL;\n\t}\n\n\tif((DWORD)idx > exports->NumberOfFunctions )\n\t{\n\t\t// name <-> ordinal number don't match\n\t\treturn NULL;\n\t}\n\n\t// addressOfFunctions contains the RVAs to the \"real\" functions\n\treturn (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4)));\n}\n\nstatic int BuildImportTable( MEMORYMODULE *module )\n{\n\tPIMAGE_DATA_DIRECTORY\tdirectory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT );\n\tbyte\t\t\t*codeBase = module->codeBase;\n\tint\t\t\tresult = 1;\n\n\tif( directory->Size > 0 )\n\t{\n\t\tPIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress );\n\n\t\tfor( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ )\n\t\t{\n\t\t\tDWORD\t*thunkRef, *funcRef;\n\t\t\tLPCSTR\tlibname;\n\t\t\tvoid\t*handle;\n\n\t\t\tlibname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name );\n\t\t\thandle = COM_LoadLibrary( libname, false, true );\n\n\t\t\tif( handle == NULL )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"couldn't load library %s\\n\", libname );\n\t\t\t\tresult = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tmodule->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* )));\n\t\t\tmodule->modules[module->numModules++] = handle;\n\n\t\t\tif( importDesc->OriginalFirstThunk )\n\t\t\t{\n\t\t\t\tthunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk );\n\t\t\t\tfuncRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// no hint table\n\t\t\t\tthunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk );\n\t\t\t\tfuncRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk );\n\t\t\t}\n\n\t\t\tfor( ; *thunkRef; thunkRef++, funcRef++ )\n\t\t\t{\n\t\t\t\tLPCSTR\tfuncName;\n\n\t\t\t\tif( IMAGE_SNAP_BY_ORDINAL( *thunkRef ))\n\t\t\t\t{\n\t\t\t\t\tfuncName = (LPCSTR)IMAGE_ORDINAL( *thunkRef );\n\t\t\t\t\t*funcRef = (DWORD)COM_GetProcAddress( handle, funcName );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tPIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef );\n\t\t\t\t\tfuncName = (LPCSTR)&thunkData->Name;\n\t\t\t\t\t*funcRef = (DWORD)COM_GetProcAddress( handle, funcName );\n\t\t\t\t}\n\n\t\t\t\tif( *funcRef == 0 )\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( S_ERROR \"%s unable to find address: %s\\n\", libname, funcName );\n\t\t\t\t\tresult = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif( !result ) break;\n\t\t}\n\t}\n\treturn result;\n}\n\nvoid MemoryFreeLibrary( void *hInstance )\n{\n\tMEMORYMODULE\t*module = (MEMORYMODULE *)hInstance;\n\n\tif( module != NULL )\n\t{\n\t\tint\ti;\n\n\t\tif( module->initialized != 0 )\n\t\t{\n\t\t\t// notify library about detaching from process\n\t\t\tDllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint );\n\t\t\t(*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 );\n\t\t\tmodule->initialized = 0;\n\t\t}\n\n\t\tif( module->modules != NULL )\n\t\t{\n\t\t\t// free previously opened libraries\n\t\t\tfor( i = 0; i < module->numModules; i++ )\n\t\t\t{\n\t\t\t\tif( module->modules[i] != NULL )\n\t\t\t\t\tCOM_FreeLibrary( module->modules[i] );\n\t\t\t}\n\t\t\tMem_Free( module->modules ); // Mem_Realloc end\n\t\t}\n\n\t\tFreeSections( module->headers, module );\n\n\t\tif( module->codeBase != NULL )\n\t\t{\n\t\t\t// release memory of library\n\t\t\tVirtualFree( module->codeBase, 0, MEM_RELEASE );\n\t\t}\n\n\t\tHeapFree( GetProcessHeap(), 0, module );\n\t}\n}\n\nvoid *MemoryLoadLibrary( const char *name )\n{\n\tMEMORYMODULE\t*result = NULL;\n\tPIMAGE_DOS_HEADER\tdos_header;\n\tPIMAGE_NT_HEADERS\told_header;\n\tbyte\t\t*code, *headers;\n\tDWORD\t\tlocationDelta;\n\tDllEntryProc\tDllEntry;\n\tstring\t\terrorstring;\n\tqboolean\t\tsuccessfull;\n\tvoid\t\t*data = NULL;\n\n\tdata = FS_LoadFile( name, NULL, false );\n\n\tif( !data )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"couldn't load %s\", name );\n\t\tgoto library_error;\n\t}\n\n\tdos_header = (PIMAGE_DOS_HEADER)data;\n\tif( dos_header->e_magic != IMAGE_DOS_SIGNATURE )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s it's not a valid executable file\", name );\n\t\tgoto library_error;\n\t}\n\n\told_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew];\n\tif( old_header->Signature != IMAGE_NT_SIGNATURE )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s missing PE header\", name );\n\t\tgoto library_error;\n\t}\n\n\t// reserve memory for image of library\n\tcode = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE );\n\n\tif( code == NULL )\n\t{\n\t\t// try to allocate memory at arbitrary position\n\t\tcode = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE );\n\t}\n\n\tif( code == NULL )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s can't reserve memory\", name );\n\t\tgoto library_error;\n\t}\n\n\tresult = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE ));\n\tresult->codeBase = code;\n\tresult->numModules = 0;\n\tresult->modules = NULL;\n\tresult->initialized = 0;\n\n\t// XXX: is it correct to commit the complete memory region at once?\n\t// calling DllEntry raises an exception if we don't...\n\tVirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE );\n\n\t// commit memory for headers\n\theaders = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE );\n\n\t// copy PE header to code\n\tmemcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders );\n\tresult->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew];\n\n\t// update position\n\tresult->headers->OptionalHeader.ImageBase = (DWORD)code;\n\n\t// copy sections from DLL file block to new memory location\n\tCopySections( data, old_header, result );\n\n\t// adjust base address of imported data\n\tlocationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase);\n\tif( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta );\n\n\t// load required dlls and adjust function table of imports\n\tif( !BuildImportTable( result ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s failed to build import table\", name );\n\t\tgoto library_error;\n\t}\n\n\t// mark memory pages depending on section headers and release\n\t// sections that are marked as \"discardable\"\n\tFinalizeSections( result );\n\n\t// get entry point of loaded library\n\tif( result->headers->OptionalHeader.AddressOfEntryPoint != 0 )\n\t{\n\t\tDllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint );\n\t\tif( DllEntry == 0 )\n\t\t{\n\t\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s has no entry point\", name );\n\t\t\tgoto library_error;\n\t\t}\n\n\t\t// notify library about attaching to process\n\t\tsuccessfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 );\n\t\tif( !successfull )\n\t\t{\n\t\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"can't attach library %s\", name );\n\t\t\tgoto library_error;\n\t\t}\n\t\tresult->initialized = 1;\n\t}\n\n\tMem_Free( data ); // release memory\n\treturn (void *)result;\nlibrary_error:\n\t// cleanup\n\tif( data ) Mem_Free( data );\n\tMemoryFreeLibrary( result );\n\tCon_Printf( S_ERROR \"LoadLibrary: %s\\n\", errorstring );\n\n\treturn NULL;\n}\n\n#endif // XASH_LIB == LIB_WIN32 && XASH_X86\n"
  },
  {
    "path": "engine/platform/win32/lib_win.c",
    "content": "/*\nlib_win.c - win32 dynamic library loading\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n\n#if XASH_LIB == LIB_WIN32\n#include \"lib_win.h\"\n\nstatic const wchar_t *FS_PathToWideChar( const char *path )\n{\n\tstatic wchar_t pathBuffer[MAX_PATH];\n\tMultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH );\n\treturn pathBuffer;\n}\n\n\nstatic DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header )\n{\n\tint i = 0;\n\tPIMAGE_SECTION_HEADER sect_header = IMAGE_FIRST_SECTION( nt_header );\n\n\tif (!rva)\n\t\treturn rva;\n\n\tfor( i = 0; i < nt_header->FileHeader.NumberOfSections; i++, sect_header++)\n\t{\n\t\tif( rva >= sect_header->VirtualAddress && rva < sect_header->VirtualAddress + sect_header->Misc.VirtualSize )\n\t\t\tbreak;\n\t}\n\treturn (rva - sect_header->VirtualAddress + sect_header->PointerToRawData);\n}\n\n/*\n---------------------------------------------------------------\n\n\t\tName for function stuff\n\n---------------------------------------------------------------\n*/\nstatic void FsGetString( file_t *f, char *str )\n{\n\tchar\tch;\n\n\twhile(( ch = FS_Getc( f )) != EOF )\n\t{\n\t\t*str++ = ch;\n\t\tif( !ch ) break;\n\t}\n}\n\nstatic void FreeNameFuncGlobals( dll_user_t *hInst )\n{\n\tint\ti;\n\n\tif( !hInst ) return;\n\n\tif( hInst->ordinals ) Mem_Free( hInst->ordinals );\n\tif( hInst->funcs ) Mem_Free( hInst->funcs );\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t{\n\t\tif( hInst->names[i] )\n\t\t\tMem_Free( hInst->names[i] );\n\t}\n\n\thInst->num_ordinals = 0;\n\thInst->ordinals = NULL;\n\thInst->funcs = NULL;\n}\n\nqboolean LibraryLoadSymbols( dll_user_t *hInst )\n{\n\tfile_t\t\t*f;\n\tstring\t\terrorstring;\n\tIMAGE_DOS_HEADER\tdos_header;\n\tLONG\t\tnt_signature;\n\tIMAGE_FILE_HEADER   pe_header;\n\tIMAGE_SECTION_HEADER\tsection_header;\n\tqboolean\t\trdata_found;\n\tIMAGE_OPTIONAL_HEADER   optional_header;\n\tlong\t\trdata_delta = 0;\n\tIMAGE_EXPORT_DIRECTORY  export_directory;\n\tlong\t\tname_offset;\n\tlong\t\texports_offset;\n\tlong\t\tordinal_offset;\n\tlong\t\tfunction_offset;\n\tstring\t\tfunction_name;\n\tdword\t\t*p_Names = NULL;\n\tint\t\ti, index;\n\n\t// can only be done for loaded libraries\n\tif( !hInst ) return false;\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t\thInst->names[i] = NULL;\n\n\tf = FS_Open( hInst->shortPath, \"rb\", false );\n\tif( !f )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"couldn't load %s\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Read( f, &dos_header, sizeof( dos_header )) != sizeof( dos_header ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s has corrupted EXE header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( dos_header.e_magic != IMAGE_DOS_SIGNATURE )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid dll signature\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Seek( f, dos_header.e_lfanew, SEEK_SET ) == -1 )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error seeking for new exe header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Read( f, &nt_signature, sizeof( nt_signature )) != sizeof( nt_signature ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s has corrupted NT header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( nt_signature != IMAGE_NT_SIGNATURE )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid NT signature\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Read( f, &pe_header, sizeof( pe_header )) != sizeof( pe_header ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid PE header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( !pe_header.SizeOfOptionalHeader )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have an optional header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Read( f, &optional_header, sizeof( optional_header )) != sizeof( optional_header ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s optional header probably corrupted\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\trdata_found = false;\n\n\tfor( i = 0; i < pe_header.NumberOfSections; i++ )\n\t{\n\t\tif( FS_Read( f, &section_header, sizeof( section_header )) != sizeof( section_header ))\n\t\t{\n\t\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error during reading section header\", hInst->shortPath );\n\t\t\tgoto table_error;\n\t\t}\n\n\t\tif((( optional_header.DataDirectory[0].VirtualAddress >= section_header.VirtualAddress ) &&\n\t\t\t(optional_header.DataDirectory[0].VirtualAddress < (section_header.VirtualAddress + section_header.Misc.VirtualSize))))\n\t\t{\n\t\t\trdata_found = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( rdata_found )\n\t{\n\t\trdata_delta = section_header.VirtualAddress - section_header.PointerToRawData;\n\t}\n\n\texports_offset = optional_header.DataDirectory[0].VirtualAddress - rdata_delta;\n\n\tif( FS_Seek( f, exports_offset, SEEK_SET ) == -1 )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid exports section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tif( FS_Read( f, &export_directory, sizeof( export_directory )) != sizeof( export_directory ))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid optional header\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\thInst->num_ordinals = export_directory.NumberOfNames;\t// also number of ordinals\n\n\tif( hInst->num_ordinals > MAX_LIBRARY_EXPORTS )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s too many exports %i\", hInst->shortPath, hInst->num_ordinals );\n\t\thInst->num_ordinals = 0;\n\t\tgoto table_error;\n\t}\n\n\tordinal_offset = export_directory.AddressOfNameOrdinals - rdata_delta;\n\n\tif( FS_Seek( f, ordinal_offset, SEEK_SET ) == -1 )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid ordinals section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\thInst->ordinals = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( word ));\n\n\tif( FS_Read( f, hInst->ordinals, hInst->num_ordinals * sizeof( word )) != (hInst->num_ordinals * sizeof( word )))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error during reading ordinals table\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tfunction_offset = export_directory.AddressOfFunctions - rdata_delta;\n\n\tif( FS_Seek( f, function_offset, SEEK_SET ) == -1 )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s does not have a valid export address section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\thInst->funcs = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( dword ));\n\n\tif( FS_Read( f, hInst->funcs, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword )))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error during reading export address section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tname_offset = export_directory.AddressOfNames - rdata_delta;\n\n\tif( FS_Seek( f, name_offset, SEEK_SET ) == -1 )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s file does not have a valid names section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tp_Names = Mem_Malloc( host.mempool, hInst->num_ordinals * sizeof( dword ));\n\n\tif( FS_Read( f, p_Names, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword )))\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error during reading names table\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t{\n\t\tname_offset = p_Names[i] - rdata_delta;\n\n\t\tif( name_offset != 0 )\n\t\t{\n\t\t\tif( FS_Seek( f, name_offset, SEEK_SET ) != -1 )\n\t\t\t{\n\t\t\t\tFsGetString( f, function_name );\n\t\t\t\thInst->names[i] = copystring( COM_GetMSVCName( function_name ));\n\t\t\t}\n\t\t\telse break;\n\t\t}\n\t}\n\n\tif( i != hInst->num_ordinals )\n\t{\n\t\tQ_snprintf( errorstring, sizeof( errorstring ), \"%s error during loading names section\", hInst->shortPath );\n\t\tgoto table_error;\n\t}\n\tFS_Close( f );\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t{\n\t\tif( !Q_strcmp( \"GiveFnptrsToDll\", hInst->names[i] ))\t// main entry point for user dlls\n\t\t{\n\t\t\tvoid\t*fn_offset;\n\n\t\t\tindex = hInst->ordinals[i];\n\t\t\tfn_offset = COM_GetProcAddress( hInst, \"GiveFnptrsToDll\" );\n\t\t\thInst->funcBase = (uintptr_t)(fn_offset) - hInst->funcs[index];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( p_Names ) Mem_Free( p_Names );\n\treturn true;\ntable_error:\n\t// cleanup\n\tif( f ) FS_Close( f );\n\tif( p_Names ) Mem_Free( p_Names );\n\tFreeNameFuncGlobals( hInst );\n\tCon_Printf( S_ERROR \"%s: %s\\n\", __func__, errorstring );\n\n\treturn false;\n}\n\nstatic const char *GetLastErrorAsString( void )\n{\n\tconst DWORD fm_flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK;\n\tDWORD errorcode;\n\twchar_t wide_errormessage[256];\n\tstatic string errormessage;\n\n\terrorcode = GetLastError();\n\tif ( !errorcode )\n\t\treturn \"\";\n\n\tFormatMessageW( fm_flags, NULL, errorcode, 0, wide_errormessage, ARRAYSIZE( wide_errormessage ), NULL );\n\tQ_UTF16ToUTF8( errormessage, sizeof( errormessage ), wide_errormessage, ARRAYSIZE( wide_errormessage ));\n\n\treturn errormessage;\n}\n\nstatic PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *data, PIMAGE_NT_HEADERS *peheader )\n{\n\tPIMAGE_DOS_HEADER dosHeader;\n\tPIMAGE_NT_HEADERS peHeader;\n\tPIMAGE_DATA_DIRECTORY importDir;\n\tPIMAGE_IMPORT_DESCRIPTOR importDesc;\n\n\tif ( !data )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: couldn't load %s\\n\", __func__, name );\n\t\treturn NULL;\n\t}\n\n\tdosHeader = (PIMAGE_DOS_HEADER)data;\n\tif ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s is not a valid executable file\\n\", __func__, name );\n\t\treturn NULL;\n\t}\n\n\tpeHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew );\n\tif ( peHeader->Signature != IMAGE_NT_SIGNATURE )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s is missing a PE header\\n\", __func__, name );\n\t\treturn NULL;\n\t}\n\n\timportDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];\n\tif( importDir->Size <= 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s has no dependencies\\n\", __func__, name );\n\t\treturn NULL;\n\t}\n\n\t*peheader = peHeader;\n\timportDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) );\n\n\treturn importDesc;\n}\n\nstatic void ListMissingModules( dll_user_t *hInst )\n{\n\tPIMAGE_NT_HEADERS peHeader;\n\tPIMAGE_IMPORT_DESCRIPTOR importDesc;\n\tbyte *data;\n\tchar\tbuf[MAX_VA_STRING];\n\n\tif( !hInst || !g_fsapi.LoadFile ) return;\n\n\tdata = g_fsapi.LoadFile( hInst->dllName, NULL, false );\n\tif( !data ) return;\n\n\timportDesc = GetImportDescriptor( hInst->dllName, data, &peHeader );\n\tif( !importDesc )\n\t{\n\t\tMem_Free( data );\n\t\treturn;\n\t}\n\n\tfor( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ )\n\t{\n\t\tHMODULE hMod;\n\t\tconst char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) );\n\n\t\thMod = LoadLibraryExW( FS_PathToWideChar( importName ), NULL, LOAD_LIBRARY_AS_DATAFILE );\n\t\tif ( !hMod )\n\t\t{\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"%s not found!\", importName );\n\t\t\tCOM_PushLibraryError( buf );\n\t\t}\n\t\telse\n\t\t\tFreeLibrary( hMod );\n\t}\n\n\tMem_Free( data );\n\treturn;\n}\n\nqboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath )\n{\n\tPIMAGE_NT_HEADERS peHeader;\n\tPIMAGE_IMPORT_DESCRIPTOR importDesc;\n\tbyte *data;\n\tdll_user_t *hInst;\n\tqboolean ret = FALSE;\n\n\thInst = FS_FindLibrary( name, directpath );\n\tif ( !hInst ) return FALSE;\n\n\tdata = FS_LoadFile( name, NULL, false );\n\tif ( !data )\n\t{\n\t\tCOM_FreeLibrary( hInst );\n\t\treturn FALSE;\n\t}\n\n\timportDesc = GetImportDescriptor( name, data, &peHeader );\n\tif ( !importDesc )\n\t{\n\t\tCOM_FreeLibrary( hInst );\n\t\tMem_Free( data );\n\t\treturn FALSE;\n\t}\n\n\tfor( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ )\n\t{\n\t\tconst char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) );\n\n\t\tif ( !Q_stricmp( importName, depname ) )\n\t\t{\n\t\t\tCOM_FreeLibrary( hInst );\n\t\t\tMem_Free( data );\n\t\t\treturn TRUE;\n\t\t}\n\t}\n\n\tCOM_FreeLibrary( hInst );\n\tMem_Free( data );\n\treturn FALSE;\n}\n\n/*\n================\nCOM_LoadLibrary\n\nsmart dll loader - can loading dlls from pack or wad files\n================\n*/\nvoid *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath )\n{\n\tdll_user_t *hInst;\n\tchar buf[MAX_VA_STRING];\n\n\tCOM_ResetLibraryError();\n\n\thInst = FS_FindLibrary( dllname, directpath );\n\tif( !hInst )\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"Failed to find library %s\", dllname );\n\t\tCOM_PushLibraryError( buf );\n\t\treturn NULL;\n\t}\n\n\tif( hInst->encrypted )\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"Library %s is encrypted, cannot load\", hInst->shortPath );\n\t\tCOM_PushLibraryError( buf );\n\t\tCOM_FreeLibrary( hInst );\n\t\treturn NULL;\n\t}\n\n#if XASH_X86\n\tif( hInst->custom_loader )\n\t{\n\t\thInst->hInstance = MemoryLoadLibrary( hInst->fullPath );\n\t}\n\telse\n#endif\n\t{\n\t\thInst->hInstance = LoadLibraryW( FS_PathToWideChar( hInst->fullPath ));\n\t}\n\n\tif( !hInst->hInstance )\n\t{\n\t\tCOM_PushLibraryError( GetLastErrorAsString() );\n\n\t\tif ( GetLastError() == ERROR_MOD_NOT_FOUND )\n\t\t\tListMissingModules( hInst );\n\n\t\tCOM_FreeLibrary( hInst );\n\t\treturn NULL;\n\t}\n\n\t// if not set - FunctionFromName and NameForFunction will not working\n\tif( build_ordinals_table )\n\t{\n\t\tif( !LibraryLoadSymbols( hInst ))\n\t\t{\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"Failed to load library %s\", dllname );\n\t\t\tCOM_PushLibraryError( buf );\n\t\t\tCOM_FreeLibrary( hInst );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn hInst;\n}\n\nvoid *COM_GetProcAddress( void *hInstance, const char *name )\n{\n\tdll_user_t *hInst = (dll_user_t *)hInstance;\n\n\tif( !hInst || !hInst->hInstance )\n\t\treturn NULL;\n\n#if XASH_X86\n\tif( hInst->custom_loader )\n\t\treturn (void *)MemoryGetProcAddress( hInst->hInstance, name );\n#endif\n\treturn (void *)GetProcAddress( hInst->hInstance, name );\n}\n\nvoid COM_FreeLibrary( void *hInstance )\n{\n\tdll_user_t *hInst = (dll_user_t *)hInstance;\n\n\tif( !hInst || !hInst->hInstance )\n\t\treturn; // already freed\n\n\tif( host.status == HOST_CRASHED )\n\t{\n\t\t// we need to hold down all modules, while MSVC can find error\n\t\tCon_Reportf( \"%s: hold %s for debugging\\n\", __func__, hInst->dllName );\n\t\treturn;\n\t}\n\telse Con_Reportf( \"%s: Unloading %s\\n\", __func__, hInst->dllName );\n\n#if XASH_X86\n\tif( hInst->custom_loader )\n\t{\n\t\tMemoryFreeLibrary( hInst->hInstance );\n\t}\n\telse\n#endif\n\t{\n\t\tFreeLibrary( hInst->hInstance );\n\t}\n\n\thInst->hInstance = NULL;\n\n\tif( hInst->num_ordinals )\n\t\tFreeNameFuncGlobals( hInst );\n\tMem_Free( hInst );\t// done\n}\n\nvoid *COM_FunctionFromName( void *hInstance, const char *pName )\n{\n\tdll_user_t\t*hInst = (dll_user_t *)hInstance;\n\tint\t\ti, index;\n\n\tif( !hInst || !hInst->hInstance )\n\t\treturn 0;\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t{\n\t\tif( !Q_strcmp( pName, hInst->names[i] ))\n\t\t{\n\t\t\tindex = hInst->ordinals[i];\n\t\t\treturn (void *)( hInst->funcs[index] + hInst->funcBase );\n\t\t}\n\t}\n\n\t// couldn't find the function name to return address\n\tCon_Printf( \"Can't find proc: %s\\n\", pName );\n\n\treturn 0;\n}\n\nconst char *COM_NameForFunction( void *hInstance, void *function )\n{\n\tdll_user_t\t*hInst = (dll_user_t *)hInstance;\n\tint\t\ti, index;\n\n\tif( !hInst || !hInst->hInstance )\n\t\treturn NULL;\n\n\tfor( i = 0; i < hInst->num_ordinals; i++ )\n\t{\n\t\tindex = hInst->ordinals[i];\n\n\t\tif(( (char*)function - (char*)hInst->funcBase ) == hInst->funcs[index] )\n\t\t\treturn hInst->names[i];\n\t}\n\n\t// couldn't find the function address to return name\n\tCon_Printf( \"Can't find address: %08lx\\n\", function );\n\n\treturn NULL;\n}\n#endif // _WIN32\n"
  },
  {
    "path": "engine/platform/win32/lib_win.h",
    "content": "/*\nlib_win.h - common win32 dll definitions\nCopyright (C) 2022 Flying With Gauss\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"library.h\"\n#include <winnt.h>\n#include <psapi.h>\n#include STDINT_H\n\n#define CALCULATE_ADDRESS( base, offset ) ((uint8_t *)( base ) + (uintptr_t)( offset ))\n\nFARPROC MemoryGetProcAddress( void *module, const char *name );\nvoid MemoryFreeLibrary( void *hInstance );\nvoid *MemoryLoadLibrary( const char *name );\n"
  },
  {
    "path": "engine/platform/win32/net.h",
    "content": "/*\nnet.h - WinSock to BSD sockets wrap\nCopyright (C) 2022 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef NET_H\n#define NET_H\n\n#include <WS2tcpip.h>\ntypedef int WSAsize_t;\n\n#define HAVE_GETADDRINFO\n\n#endif // NET_H\n"
  },
  {
    "path": "engine/platform/win32/sys_win.c",
    "content": "/*\nsys_win.c - win32 system utils\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"platform/platform.h\"\n#include \"menu_int.h\"\n#include \"server.h\"\n#include <shellapi.h>\n\nHANDLE g_waitable_timer;\n\n#if XASH_TIMER == TIMER_WIN32\ndouble Platform_DoubleTime( void )\n{\n\tstatic LARGE_INTEGER\tg_PerformanceFrequency;\n\tstatic LARGE_INTEGER\tg_ClockStart;\n\tLARGE_INTEGER\t\tCurrentTime;\n\n\tif( !g_PerformanceFrequency.QuadPart )\n\t{\n\t\tQueryPerformanceFrequency( &g_PerformanceFrequency );\n\t\tQueryPerformanceCounter( &g_ClockStart );\n\t}\n\tQueryPerformanceCounter( &CurrentTime );\n\n\treturn (double)( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / (double)( g_PerformanceFrequency.QuadPart );\n}\n\nvoid Platform_Sleep( int msec )\n{\n\tSleep( msec );\n}\n#endif // XASH_TIMER == TIMER_WIN32\n\nvoid Win32_Init( qboolean con_showalways )\n{\n\tHMODULE hModule = LoadLibraryW( L\"kernel32.dll\" );\n\tif( hModule )\n\t{\n\t\tHANDLE ( __stdcall *pfnCreateWaitableTimerExW)( LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCWSTR lpTimerName, DWORD dwFlags, DWORD dwDesiredAccess );\n\n\t\tif(( pfnCreateWaitableTimerExW = (void *)GetProcAddress( hModule, \"CreateWaitableTimerExW\" )))\n\t\t{\n\t\t\tg_waitable_timer = pfnCreateWaitableTimerExW(\n\t\t\t\tNULL,\n\t\t\t\tNULL,\n\t\t\t\t0x1 /* CREATE_WAITABLE_TIMER_MANUAL_RESET */ | 0x2 /* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION */,\n\t\t\t\t0x0002 /* TIMER_MODIFY_STATE */ | SYNCHRONIZE | DELETE\n\t\t\t);\n\t\t}\n\n\t\tFreeLibrary( hModule );\n\t}\n\n#if 0 // FIXME: creates object but doesn't wait for specific time for me on Windows 10, with the code above commented\n\tif( !g_waitable_timer )\n\t\tg_waitable_timer = CreateWaitableTimer( NULL, TRUE, NULL );\n#endif\n\n\tWcon_CreateConsole( con_showalways );\n}\n\nvoid Win32_Shutdown( void )\n{\n\tWcon_DestroyConsole( );\n\n\tif( g_waitable_timer )\n\t{\n\t\tCloseHandle( g_waitable_timer );\n\t\tg_waitable_timer = 0;\n\t}\n}\n\nqboolean Win32_NanoSleep( int nsec )\n{\n\tLARGE_INTEGER ts;\n\n\tif( !g_waitable_timer )\n\t\treturn false;\n\n\tts.QuadPart = -nsec / 100;\n\n\tif( !SetWaitableTimer( g_waitable_timer, &ts, 0, NULL, NULL, FALSE ))\n\t{\n\t\tCloseHandle( g_waitable_timer );\n\t\tg_waitable_timer = 0;\n\t\treturn false;\n\t}\n\n\tif( WaitForSingleObject( g_waitable_timer, Q_max( 1, nsec / 1000000 )) != WAIT_OBJECT_0 )\n\t\treturn false;\n\n\treturn true;\n}\n\nqboolean Platform_DebuggerPresent( void )\n{\n\treturn IsDebuggerPresent();\n}\n\nvoid Platform_ShellExecute( const char *path, const char *parms )\n{\n\tif( !Q_strcmp( path, GENERIC_UPDATE_PAGE ) || !Q_strcmp( path, PLATFORM_UPDATE_PAGE ))\n\t\tpath = DEFAULT_UPDATE_PAGE;\n\n\tShellExecuteA( NULL, \"open\", path, parms, NULL, SW_SHOW );\n}\n\n#if XASH_MESSAGEBOX == MSGBOX_WIN32\nvoid Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow )\n{\n\tMessageBoxA( parentMainWindow ? host.hWnd : NULL, message, title, MB_OK|MB_SETFOREGROUND|MB_ICONSTOP );\n}\n#endif // XASH_MESSAGEBOX == MSGBOX_WIN32\n\n"
  },
  {
    "path": "engine/progdefs.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef PROGDEFS_H\n#define PROGDEFS_H\n\ntypedef struct\n{\n\tfloat\t\ttime;\n\tfloat\t\tframetime;\n\tfloat\t\tforce_retouch;\n\tstring_t\t\tmapname;\n\tstring_t\t\tstartspot;\n\tfloat\t\tdeathmatch;\n\tfloat\t\tcoop;\n\tfloat\t\tteamplay;\n\tfloat\t\tserverflags;\n\tfloat\t\tfound_secrets;\n\tvec3_t\t\tv_forward;\n\tvec3_t\t\tv_up;\n\tvec3_t\t\tv_right;\n\tfloat\t\ttrace_allsolid;\n\tfloat\t\ttrace_startsolid;\n\tfloat\t\ttrace_fraction;\n\tvec3_t\t\ttrace_endpos;\n\tvec3_t\t\ttrace_plane_normal;\n\tfloat\t\ttrace_plane_dist;\n\tedict_t\t\t*trace_ent;\n\tfloat\t\ttrace_inopen;\n\tfloat\t\ttrace_inwater;\n\tint\t\ttrace_hitgroup;\n\tint\t\ttrace_flags;\n\tint\t\tchangelevel;\t// transition in progress when true (was msg_entity)\n\tint\t\tcdAudioTrack;\n\tint\t\tmaxClients;\n\tint\t\tmaxEntities;\n\tconst char\t*pStringBase;\n\n\tvoid\t\t*pSaveData;\t// (SAVERESTOREDATA *) pointer\n\tvec3_t\t\tvecLandmarkOffset;\n} globalvars_t;\n\ntypedef struct entvars_s\n{\n\tstring_t\t\tclassname;\n\tstring_t\t\tglobalname;\n\n\tvec3_t\t\torigin;\n\tvec3_t\t\toldorigin;\n\tvec3_t\t\tvelocity;\n\tvec3_t\t\tbasevelocity;\n\tvec3_t\t\tclbasevelocity;\t// Base velocity that was passed in to server physics so\n\t\t\t\t\t// client can predict conveyors correctly. Server zeroes it, so we need to store here, too.\n\tvec3_t\t\tmovedir;\n\n\tvec3_t\t\tangles;\t\t// Model angles\n\tvec3_t\t\tavelocity;\t// angle velocity (degrees per second)\n\tvec3_t\t\tpunchangle;\t// auto-decaying view angle adjustment\n\tvec3_t\t\tv_angle;\t\t// Viewing angle (player only)\n\n\t// For parametric entities\n\tvec3_t\t\tendpos;\n\tvec3_t\t\tstartpos;\n\tfloat\t\timpacttime;\n\tfloat\t\tstarttime;\n\n\tint\t\tfixangle;\t\t// 0:nothing, 1:force view angles, 2:add avelocity\n\tfloat\t\tidealpitch;\n\tfloat\t\tpitch_speed;\n\tfloat\t\tideal_yaw;\n\tfloat\t\tyaw_speed;\n\n\tint\t\tmodelindex;\n\n\tstring_t\t\tmodel;\n\tint\t\tviewmodel;\t// player's viewmodel\n\tint\t\tweaponmodel;\t// what other players see\n\n\tvec3_t\t\tabsmin;\t\t// BB max translated to world coord\n\tvec3_t\t\tabsmax;\t\t// BB max translated to world coord\n\tvec3_t\t\tmins;\t\t// local BB min\n\tvec3_t\t\tmaxs;\t\t// local BB max\n\tvec3_t\t\tsize;\t\t// maxs - mins\n\n\tfloat\t\tltime;\n\tfloat\t\tnextthink;\n\n\tint\t\tmovetype;\n\tint\t\tsolid;\n\n\tint\t\tskin;\n\tint\t\tbody;\t\t// sub-model selection for studiomodels\n\tint \t\teffects;\n\tfloat\t\tgravity;\t\t// % of \"normal\" gravity\n\tfloat\t\tfriction;\t\t// inverse elasticity of MOVETYPE_BOUNCE\n\n\tint\t\tlight_level;\n\n\tint\t\tsequence;\t\t// animation sequence\n\tint\t\tgaitsequence;\t// movement animation sequence for player (0 for none)\n\tfloat\t\tframe;\t\t// % playback position in animation sequences (0..255)\n\tfloat\t\tanimtime;\t\t// world time when frame was set\n\tfloat\t\tframerate;\t// animation playback rate (-8x to 8x)\n\tbyte\t\tcontroller[4];\t// bone controller setting (0..255)\n\tbyte\t\tblending[2];\t// blending amount between sub-sequences (0..255)\n\n\tfloat\t\tscale;\t\t// sprites and models rendering scale (0..255)\n\tint\t\trendermode;\n\tfloat\t\trenderamt;\n\tvec3_t\t\trendercolor;\n\tint\t\trenderfx;\n\n\tfloat\t\thealth;\n\tfloat\t\tfrags;\n\tint\t\tweapons;\t\t// bit mask for available weapons\n\tfloat\t\ttakedamage;\n\n\tint\t\tdeadflag;\n\tvec3_t\t\tview_ofs;\t\t// eye position\n\n\tint\t\tbutton;\n\tint\t\timpulse;\n\n\tedict_t\t\t*chain;\t\t// Entity pointer when linked into a linked list\n\tedict_t\t\t*dmg_inflictor;\n\tedict_t\t\t*enemy;\n\tedict_t\t\t*aiment;\t\t// entity pointer when MOVETYPE_FOLLOW\n\tedict_t\t\t*owner;\n\tedict_t\t\t*groundentity;\n\n\tint\t\tspawnflags;\n\tint\t\tflags;\n\n\tint\t\tcolormap;\t\t// lowbyte topcolor, highbyte bottomcolor\n\tint\t\tteam;\n\n\tfloat\t\tmax_health;\n\tfloat\t\tteleport_time;\n\tfloat\t\tarmortype;\n\tfloat\t\tarmorvalue;\n\tint\t\twaterlevel;\n\tint\t\twatertype;\n\n\tstring_t\t\ttarget;\n\tstring_t\t\ttargetname;\n\tstring_t\t\tnetname;\n\tstring_t\t\tmessage;\n\n\tfloat\t\tdmg_take;\n\tfloat\t\tdmg_save;\n\tfloat\t\tdmg;\n\tfloat\t\tdmgtime;\n\n\tstring_t\t\tnoise;\n\tstring_t\t\tnoise1;\n\tstring_t\t\tnoise2;\n\tstring_t\t\tnoise3;\n\n\tfloat\t\tspeed;\n\tfloat\t\tair_finished;\n\tfloat\t\tpain_finished;\n\tfloat\t\tradsuit_finished;\n\n\tedict_t\t\t*pContainingEntity;\n\n\tint\t\tplayerclass;\n\tfloat\t\tmaxspeed;\n\n\tfloat\t\tfov;\n\tint\t\tweaponanim;\n\n\tint\t\tpushmsec;\n\n\tint\t\tbInDuck;\n\tint\t\tflTimeStepSound;\n\tint\t\tflSwimTime;\n\tint\t\tflDuckTime;\n\tint\t\tiStepLeft;\n\tfloat\t\tflFallVelocity;\n\n\tint\t\tgamestate;\n\n\tint\t\toldbuttons;\n\n\tint\t\tgroupinfo;\n\n\t// For mods\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n\tvec3_t\t\tvuser1;\n\tvec3_t\t\tvuser2;\n\tvec3_t\t\tvuser3;\n\tvec3_t\t\tvuser4;\n\tedict_t\t\t*euser1;\n\tedict_t\t\t*euser2;\n\tedict_t\t\t*euser3;\n\tedict_t\t\t*euser4;\n} entvars_t;\n\n#endif//PROGDEFS_H\n"
  },
  {
    "path": "engine/ref_api.h",
    "content": "/*\nref_api.h - Xash3D render dll API\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef REF_API\n#define REF_API\n\n#include <stdarg.h>\n#include \"com_image.h\"\n#include \"vgui_api.h\"\n#include \"render_api.h\"\n#include \"triangleapi.h\"\n#include \"const.h\"\n#include \"cl_entity.h\"\n#include \"com_model.h\"\n#include \"studio.h\"\n#include \"r_efx.h\"\n#include \"com_image.h\"\n#include \"filesystem.h\"\n#include \"ref_vulkan.h\"\n#include \"ref_device.h\"\n#include \"common/protocol.h\"\n\n// RefAPI changelog:\n// 1. Initial release\n// 2. FS functions are removed, instead we have full fs_api_t\n// 3. SlerpBones, CalcBonePosition/Quaternion calls were moved to libpublic/mathlib\n// 4. R_StudioEstimateFrame now has time argument\n// 5. Removed GetSomethingByIndex calls, renderers are supposed to cache pointer values\n//    Removed previously unused calls\n//    Simplified remapping calls\n//    GetRefAPI is now expected to return REF_API_VERSION\n// 6. Removed timing from ref_globals_t.\n//    Renderers are supposed to migrate to ref_client_t/ref_host_t using PARM_GET_CLIENT_PTR and PARM_GET_HOST_PTR\n//    Removed functions to get internal engine structions. Use PARM_GET_*_PTR instead.\n// 7. Gamma fixes.\n// 8. Moved common code to engine.\n//    Removed REF_{SOLID,ALPHA}SKY_TEXTURE. Replaced R_InitSkyClouds by R_SetSkyCloudsTextures.\n//    Skybox loading is now done at engine side.\n//    R_SetupSky callback accepts a pointer to an array of 6 integers representing box side textures.\n//    Restored texture replacement from old Xash3D.\n//    PARM_SKY_SPHERE and PARM_SURF_SAMPLESIZE are now handled at engine side.\n//    VGUI rendering code is mostly moved back to engine.\n//    Implemented texture replacement.\n// 9. Removed gamma functions. Renderer is supposed to get them through PARM_GET_*_PTR.\n//    Move hulls rendering back to engine\n//    Removed lightstyle, dynamic and entity light functions. Renderer is supposed to get them through PARM_GET_*_PTR.\n//    CL_RunLightStyles now accepts lightstyles array.\n//    Removed R_DrawTileClear and Mod_LoadMapSprite, as they're implemented on engine side\n//    Removed FillRGBABlend. Now FillRGBA accepts rendermode parameter.\n// 10. Added R_GetWindowHandle to retrieve platform-specific window object.\n#define REF_API_VERSION 10\n\n#define TF_SKY\t\t(TF_SKYSIDE|TF_NOMIPMAP|TF_ALLOW_NEAREST)\n#define TF_FONT\t\t(TF_NOMIPMAP|TF_CLAMP|TF_ALLOW_NEAREST)\n#define TF_IMAGE\t\t(TF_NOMIPMAP|TF_CLAMP)\n#define TF_DECAL\t\t(TF_CLAMP)\n\n#define FCONTEXT_CORE_PROFILE\t\tBIT( 0 )\n#define FCONTEXT_DEBUG_ARB\t\tBIT( 1 )\n\n// screenshot types\n#define VID_SCREENSHOT\t0\n#define VID_LEVELSHOT\t1\n#define VID_MINISHOT\t2\n#define VID_MAPSHOT\t\t3\t// special case for overview layer\n#define VID_SNAPSHOT\t4\t// save screenshot into root dir and no gamma correction\n\n// model flags (stored in model_t->flags)\n#define MODEL_CONVEYOR\t\tBIT( 0 )\n#define MODEL_HAS_ORIGIN\t\tBIT( 1 )\n#define MODEL_LIQUID\t\tBIT( 2 )\t// model has only point hull\n#define MODEL_TRANSPARENT\t\tBIT( 3 )\t// have transparent surfaces\n#define MODEL_COLORED_LIGHTING\tBIT( 4 )\t// lightmaps stored as RGB\n\n#define MODEL_WORLD\t\t\tBIT( 29 )\t// it's a worldmodel\n#define MODEL_CLIENT\t\tBIT( 30 )\t// client sprite\n\n// goes into world.flags\n#define FWORLD_SKYSPHERE\t\tBIT( 0 )\n#define FWORLD_CUSTOM_SKYBOX\t\tBIT( 1 )\n#define FWORLD_WATERALPHA\t\tBIT( 2 )\n#define FWORLD_HAS_DELUXEMAP\t\tBIT( 3 )\n\n// special rendermode for screenfade modulate\n// (probably will be expanded at some point)\n#define kRenderScreenFadeModulate 0x1000\n\n#define SKYBOX_MAX_SIDES 6 // a box can only have 6 sides\n\ntypedef enum\n{\n\tDEMO_INACTIVE = 0,\n\tDEMO_XASH3D,\n\tDEMO_QUAKE1\n} demo_mode;\n\ntypedef enum ref_window_type_e\n{\n\tREF_WINDOW_TYPE_NULL = 0,\n\tREF_WINDOW_TYPE_WIN32, // HWND\n\tREF_WINDOW_TYPE_X11, // Display*\n\tREF_WINDOW_TYPE_WAYLAND, // wl_display*\n\tREF_WINDOW_TYPE_MACOS, // NSWindow*\n\tREF_WINDOW_TYPE_SDL, // SDL_Window*\n} ref_window_type_t;\n\ntypedef struct\n{\n\tmsurface_t\t*surf;\n\tint\t\tcull;\n} sortedface_t;\n\ntypedef struct ref_globals_s\n{\n\tqboolean developer;\n\n\t// viewport width and height\n\tint      width;\n\tint      height;\n\n\tqboolean fullScreen;\n\tqboolean wideScreen;\n\n\tvec3_t vieworg;\n\tvec3_t viewangles;\n\n\t// todo: fill this without engine help\n\t// move to local\n\n\t// translucent sorted array\n\tsortedface_t\t*draw_surfaces;\t// used for sorting translucent surfaces\n\tint\t\tmax_surfaces;\t// max surfaces per submodel (for all models)\n\tsize_t\t\tvisbytes;\t\t// cluster size\n\n\tint desktopBitsPixel;\n} ref_globals_t;\n\ntypedef struct ref_client_s\n{\n\tdouble   time;\n\tdouble   oldtime;\n\tint      viewentity;\n\tint      playernum;\n\tint      maxclients;\n\tint      nummodels;\n\tmodel_t *models[MAX_MODELS+1];\n\tqboolean paused;\n\tvec3_t   simorg;\n} ref_client_t;\n\ntypedef struct ref_host_s\n{\n\tdouble realtime;\n\tdouble frametime;\n\tint    features;\n} ref_host_t;\n\nenum\n{\n\tGL_KEEP_UNIT = -1,\n\tXASH_TEXTURE0 = 0,\n\tXASH_TEXTURE1,\n\tXASH_TEXTURE2,\n\tXASH_TEXTURE3,\t\t// g-cont. 4 units should be enough\n\tMAX_TEXTURE_UNITS = 32\t// can't access to all over units without GLSL or cg\n};\n\nenum // r_speeds counters\n{\n\tRS_ACTIVE_TENTS = 0,\n};\n\n// refdll must expose this default textures using this names\n#define REF_DEFAULT_TEXTURE  \"*default\"\n#define REF_GRAY_TEXTURE     \"*gray\"\n#define REF_WHITE_TEXTURE    \"*white\"\n#define REF_BLACK_TEXTURE    \"*black\"\n#define REF_PARTICLE_TEXTURE \"*particle\"\n\ntypedef enum connstate_e\n{\n\tca_disconnected = 0,// not talking to a server\n\tca_connecting,\t// sending request packets to the server\n\tca_connected,\t// netchan_t established, waiting for svc_serverdata\n\tca_validate,\t// download resources, validating, auth on server\n\tca_active,\t// game views should be displayed\n\tca_cinematic,\t// playing a cinematic, not connected to a server\n} connstate_t;\n\nenum ref_defaultsprite_e\n{\n\tREF_DOT_SPRITE, // cl_sprite_dot\n\tREF_CHROME_SPRITE // cl_sprite_shell\n};\n\n// the order of first three is important!\n// so you can use this value in IEngineStudio.StudioIsHardware\nenum ref_graphic_apis_e\n{\n\tREF_SOFTWARE,\t// hypothetical: just make a surface to draw on, in software\n\tREF_GL,\t\t// create GL context\n\tREF_D3D,\t// Direct3D\n\tREF_VULKAN, // Vulkan\n};\n\ntypedef enum\n{\n\tSAFE_NO = 0,\n\tSAFE_NOMSAA,      // skip msaa\n\tSAFE_NOACC,       // don't set acceleration flag\n\tSAFE_NOSTENCIL,   // don't set stencil bits\n\tSAFE_NOALPHA,     // don't set alpha bits\n\tSAFE_NODEPTH,     // don't set depth bits\n\tSAFE_NOCOLOR,     // don't set color bits\n\tSAFE_DONTCARE,    // ignore everything, let SDL/EGL decide\n\tSAFE_LAST,        // must be last\n} ref_safegl_context_t;\n\nenum // OpenGL configuration attributes\n{\n\tREF_GL_RED_SIZE,\n\tREF_GL_GREEN_SIZE,\n\tREF_GL_BLUE_SIZE,\n\tREF_GL_ALPHA_SIZE,\n\tREF_GL_DOUBLEBUFFER,\n\tREF_GL_DEPTH_SIZE,\n\tREF_GL_STENCIL_SIZE,\n\tREF_GL_MULTISAMPLEBUFFERS,\n\tREF_GL_MULTISAMPLESAMPLES,\n\tREF_GL_ACCELERATED_VISUAL,\n\tREF_GL_CONTEXT_MAJOR_VERSION,\n\tREF_GL_CONTEXT_MINOR_VERSION,\n\tREF_GL_CONTEXT_EGL,\n\tREF_GL_CONTEXT_FLAGS,\n\tREF_GL_CONTEXT_PROFILE_MASK,\n\tREF_GL_SHARE_WITH_CURRENT_CONTEXT,\n\tREF_GL_FRAMEBUFFER_SRGB_CAPABLE,\n\tREF_GL_CONTEXT_RELEASE_BEHAVIOR,\n\tREF_GL_CONTEXT_RESET_NOTIFICATION,\n\tREF_GL_CONTEXT_NO_ERROR,\n\tREF_GL_ATTRIBUTES_COUNT,\n};\n\nenum\n{\n\tREF_GL_CONTEXT_PROFILE_CORE           = 0x0001,\n\tREF_GL_CONTEXT_PROFILE_COMPATIBILITY  = 0x0002,\n\tREF_GL_CONTEXT_PROFILE_ES             = 0x0004 /**< GLX_CONTEXT_ES2_PROFILE_BIT_EXT */\n};\n\n// binary compatible with SDL and EGL_KHR_create_context(0x0007 mask)\nenum\n{\n\tREF_GL_CONTEXT_DEBUG_FLAG              = 0x0001,\n\tREF_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG = 0x0002,\n\tREF_GL_CONTEXT_ROBUST_ACCESS_FLAG      = 0x0004,\n\tREF_GL_CONTEXT_RESET_ISOLATION_FLAG    = 0x0008\n};\n\ntypedef enum ref_screen_rotation_e\n{\n\tREF_ROTATE_NONE = 0,\n\tREF_ROTATE_CW = 1,\n\tREF_ROTATE_UD = 2,\n\tREF_ROTATE_CCW = 3,\n} ref_screen_rotation_t;\n\ntypedef struct remap_info_s\n{\n\tunsigned short\ttextures[MAX_SKINS];// alias textures\n\tstruct mstudiotex_s\t*ptexture;\t// array of textures with local copy of remapped textures\n\tshort\t\tnumtextures;\t// textures count\n\tshort\t\ttopcolor;\t\t// cached value\n\tshort\t\tbottomcolor;\t// cached value\n\tmodel_t\t\t*model;\t\t// for catch model changes\n} remap_info_t;\n\ntypedef struct convar_s convar_t;\nstruct con_nprint_s;\nstruct engine_studio_api_s;\nstruct r_studio_interface_s;\n\ntypedef enum\n{\n\tPARM_DEV_OVERVIEW      = -1,\n\tPARM_THIRDPERSON       = -2,\n\tPARM_QUAKE_COMPATIBLE  = -3,\n\tPARM_GET_CLIENT_PTR    = -4, // ref_client_t\n\tPARM_GET_HOST_PTR      = -5, // ref_host_t\n\tPARM_CONNSTATE         = -6, // cls.state\n\tPARM_PLAYING_DEMO      = -7, // cls.demoplayback\n\tPARM_WATER_LEVEL       = -8, // cl.local.water_level\n\tPARM_GET_WORLD_PTR     = -9, // world\n\tPARM_LOCAL_HEALTH      = -10, // cl.local.health\n\tPARM_LOCAL_GAME        = -11,\n\tPARM_NUMENTITIES       = -12, // local game only\n\tPARM_GET_MOVEVARS_PTR  = -13, // clgame.movevars\n\tPARM_GET_PALETTE_PTR   = -14, // clgame.palette\n\tPARM_GET_VIEWENT_PTR   = -15, // clgame.viewent\n\n\tPARM_GET_TEXGAMMATABLE_PTR = -16,\n\tPARM_GET_LIGHTGAMMATABLE_PTR = -17,\n\tPARM_GET_SCREENGAMMATABLE_PTR = -18,\n\tPARM_GET_LINEARGAMMATABLE_PTR = -19,\n\n\tPARM_GET_LIGHTSTYLES_PTR = -20,\n\tPARM_GET_DLIGHTS_PTR = -21,\n\tPARM_GET_ELIGHTS_PTR = -22,\n\n\t// implemented by ref_dll\n\n\t// returns non-null integer if filtering is enabled for texture\n\t// pass -1 to query global filtering settings\n\tPARM_TEX_FILTERING     = -0x10000,\n} ref_parm_e;\n\ntypedef struct ref_api_s\n{\n\tintptr_t (*EngineGetParm)( int parm, int arg );\t// generic\n\n\t// cvar handlers\n\tcvar_t   *(*Cvar_Get)( const char *szName, const char *szValue, int flags, const char *description );\n\tcvar_t   *(*pfnGetCvarPointer)( const char *name, int ignore_flags );\n\tfloat       (*pfnGetCvarFloat)( const char *szName );\n\tconst char *(*pfnGetCvarString)( const char *szName ) PFN_RETURNS_NONNULL;\n\tvoid        (*Cvar_SetValue)( const char *name, float value );\n\tvoid        (*Cvar_Set)( const char *name, const char *value );\n\tvoid (*Cvar_RegisterVariable)( convar_t *var );\n\tvoid (*Cvar_FullSet)( const char *var_name, const char *value, int flags );\n\n\t// command handlers\n\tint         (*Cmd_AddCommand)( const char *cmd_name, void (*function)(void), const char *description );\n\tvoid        (*Cmd_RemoveCommand)( const char *cmd_name );\n\tint         (*Cmd_Argc)( void );\n\tconst char *(*Cmd_Argv)( int arg ) PFN_RETURNS_NONNULL;\n\tconst char *(*Cmd_Args)( void ) PFN_RETURNS_NONNULL;\n\n\t// cbuf\n\tvoid (*Cbuf_AddText)( const char *commands );\n\tvoid (*Cbuf_InsertText)( const char *commands );\n\tvoid (*Cbuf_Execute)( void );\n\n\t// logging\n\tvoid\t(*Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // typical console allowed messages\n\tvoid\t(*Con_DPrintf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 1\n\tvoid\t(*Con_Reportf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 2\n\n\t// debug print\n\tvoid\t(*Con_NPrintf)( int pos, const char *fmt, ... ) FORMAT_CHECK( 2 );\n\tvoid\t(*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) FORMAT_CHECK( 2 );\n\tvoid\t(*CL_CenterPrint)( const char *s, float y );\n\tvoid (*Con_DrawStringLen)( const char *pText, int *length, int *height );\n\tint (*Con_DrawString)( int x, int y, const char *string, const rgba_t setColor );\n\tvoid\t(*CL_DrawCenterPrint)( void );\n\n\t// entity management\n\tstruct cl_entity_s *(*R_BeamGetEntity)( int index );\n\tstruct cl_entity_s *(*CL_GetWaterEntity)( const vec3_t p );\n\tqboolean (*CL_AddVisibleEntity)( cl_entity_t *ent, int entityType );\n\n\t// brushes\n\tint (*Mod_SampleSizeForFace)( const struct msurface_s *surf );\n\tqboolean (*Mod_BoxVisible)( const vec3_t mins, const vec3_t maxs, const byte *visbits );\n\tmleaf_t *(*Mod_PointInLeaf)( const vec3_t p, mnode_t *node );\n\tvoid (*R_DrawWorldHull)( void );\n\tvoid (*R_DrawModelHull)( model_t *mod );\n\n\t// studio models\n\tvoid *(*R_StudioGetAnim)( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc );\n\tvoid\t(*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity );\n\n\t// efx\n\tvoid (*CL_DrawEFX)( float time, qboolean fTrans );\n\tvoid (*CL_ThinkParticle)( double frametime, particle_t *p );\n\tvoid (*R_FreeDeadParticles)( particle_t **ppparticles );\n\tparticle_t *(*CL_AllocParticleFast)( void ); // unconditionally give new particle pointer from cl_free_particles\n\tstruct dlight_s *(*CL_AllocElight)( int key );\n\tstruct model_s *(*GetDefaultSprite)( enum ref_defaultsprite_e spr );\n\tvoid\t\t(*R_StoreEfrags)( struct efrag_s **ppefrag, int framecount );// store efrags for static entities\n\n\t// model management\n\tmodel_t *(*Mod_ForName)( const char *name, qboolean crash, qboolean trackCRC );\n\tvoid *(*Mod_Extradata)( int type, model_t *model );\n\n\t// remap\n\tqboolean (*CL_EntitySetRemapColors)( cl_entity_t *e, model_t *mod, int top, int bottom );\n\tstruct remap_info_s *(*CL_GetRemapInfoForEntity)( cl_entity_t *e );\n\n\t// utils\n\tvoid  (*CL_ExtraUpdate)( void );\n\tvoid  (*Host_Error)( const char *fmt, ... ) FORMAT_CHECK( 1 );\n\tvoid  (*COM_SetRandomSeed)( int lSeed );\n\tfloat (*COM_RandomFloat)( float rmin, float rmax );\n\tint   (*COM_RandomLong)( int rmin, int rmax );\n\tstruct screenfade_s *(*GetScreenFade)( void );\n\tvoid (*CL_GetScreenInfo)( int *width, int *height ); // clgame.scrInfo, ptrs may be NULL\n\tvoid (*SetLocalLightLevel)( int level ); // cl.local.light_level\n\tint (*Sys_CheckParm)( const char *flag );\n\n\t// studio interface\n\tplayer_info_t *(*pfnPlayerInfo)( int index );\n\tentity_state_t *(*pfnGetPlayerState)( int index );\n\tvoid *(*Mod_CacheCheck)( struct cache_user_s *c );\n\tvoid (*Mod_LoadCacheFile)( const char *path, struct cache_user_s *cu );\n\tvoid *(*Mod_Calloc)( int number, size_t size );\n\tint\t(*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio );\n\n\t// memory\n\tpoolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline )\n\t\tWARN_UNUSED_RESULT;\n\tvoid  (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline );\n\tvoid *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n\t\tALLOC_CHECK( 2 ) WARN_UNUSED_RESULT;\n\tvoid *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline )\n\t\tALLOC_CHECK( 3 ) WARN_UNUSED_RESULT;\n\tvoid  (*_Mem_Free)( void *data, const char *filename, int fileline );\n\n\t// library management\n\tvoid *(*COM_LoadLibrary)( const char *name, int build_ordinals_table, qboolean directpath );\n\tvoid  (*COM_FreeLibrary)( void *handle );\n\tvoid *(*COM_GetProcAddress)( void *handle, const char *name );\n\n\t// video init\n\t// try to create window\n\t// will call GL_SetupAttributes in case of REF_GL\n\tqboolean  (*R_Init_Video)( int type ); // will also load and execute renderer config(see R_GetConfigName)\n\tvoid (*R_Free_Video)( void );\n\n\t// GL\n\tint   (*GL_SetAttribute)( int attr, int value );\n\tint   (*GL_GetAttribute)( int attr, int *value );\n\tvoid *(*GL_GetProcAddress)( const char *name );\n\tvoid (*GL_SwapBuffers)( void );\n\n\t// SW\n\tqboolean (*SW_CreateBuffer)( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b );\n\tvoid *(*SW_LockBuffer)( void );\n\tvoid (*SW_UnlockBuffer)( void );\n\n\t// renderapi\n\tint\t\t(*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis );\n\tconst struct ref_overview_s *( *GetOverviewParms )( void );\n\tdouble\t\t(*pfnTime)( void );\t\t\t\t// Sys_DoubleTime\n\n\t// event api\n\tstruct physent_s *(*EV_GetPhysent)( int idx );\n\tstruct msurface_s *( *EV_TraceSurface )( int ground, float *vstart, float *vend );\n\tstruct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe );\n\tstruct pmtrace_s *(*EV_VisTraceLine )( float *start, float *end, int flags );\n\tstruct pmtrace_s (*CL_TraceLine)( vec3_t start, vec3_t end, int flags );\n\n\t// imagelib\n\tvoid (*Image_AddCmdFlags)( uint flags ); // used to check if hardware dxt is supported\n\tvoid (*Image_SetForceFlags)( uint flags );\n\tvoid (*Image_ClearForceFlags)( void );\n\tqboolean (*Image_CustomPalette)( void );\n\tqboolean (*Image_Process)( rgbdata_t **pix, int width, int height, uint flags, float reserved );\n\trgbdata_t *(*FS_LoadImage)( const char *filename, const byte *buffer, size_t size );\n\tqboolean (*FS_SaveImage)( const char *filename, rgbdata_t *pix );\n\trgbdata_t *(*FS_CopyImage)( rgbdata_t *in );\n\tvoid (*FS_FreeImage)( rgbdata_t *pack );\n\tvoid (*Image_SetMDLPointer)( byte *p );\n\tconst struct bpc_desc_s *(*Image_GetPFDesc)( int idx );\n\n\t// client exports\n\tvoid\t(*pfnDrawNormalTriangles)( void );\n\tvoid\t(*pfnDrawTransparentTriangles)( void );\n\trender_interface_t\t*drawFuncs;\n\n\t// filesystem exports\n\tfs_api_t\t*fsapi;\n\n\t// for abstracting the engine's rendering\n\tref_window_type_t (*R_GetWindowHandle)( void **handle, ref_window_type_t type );\n\n\tint (*XVK_GetInstanceExtensions)( unsigned int count, const char **pNames );\n\tvoid *(*XVK_GetVkGetInstanceProcAddr)( void );\n\tVkSurfaceKHR (*XVK_CreateSurface)( VkInstance instance );\n} ref_api_t;\n\nstruct mip_s;\n\n// render callbacks\ntypedef struct ref_interface_s\n{\n\t// construct, destruct\n\tqboolean (*R_Init)( void ); // context is true if you need context management\n\t// const char *(*R_GetInitError)( void );\n\tvoid (*R_Shutdown)( void );\n\tconst char *(*R_GetConfigName)( void ); // returns config name without extension\n\tqboolean (*R_SetDisplayTransform)( ref_screen_rotation_t rotate, int x, int y, float scale_x, float scale_y );\n\n\t// only called for GL contexts\n\tvoid (*GL_SetupAttributes)( int safegl );\n\tvoid (*GL_InitExtensions)( void );\n\tvoid (*GL_ClearExtensions)( void );\n\n\t// scene rendering\n\tvoid (*R_GammaChanged)( qboolean do_reset_gamma );\n\tvoid (*R_BeginFrame)( qboolean clearScene );\n\tvoid (*R_RenderScene)( void );\n\tvoid (*R_EndFrame)( void );\n\tvoid (*R_PushScene)( void );\n\tvoid (*R_PopScene)( void );\n\tvoid (*GL_BackendStartFrame)( void );\n\tvoid (*GL_BackendEndFrame)( void );\n\n\tvoid (*R_ClearScreen)( void ); // clears color buffer on GL\n\tvoid (*R_AllowFog)( qboolean allow );\n\tvoid (*GL_SetRenderMode)( int renderMode );\n\n\tqboolean (*R_AddEntity)( struct cl_entity_s *clent, int type );\n\tvoid (*CL_AddCustomBeam)( cl_entity_t *pEnvBeam );\n\tvoid (*R_ProcessEntData)( qboolean allocate, cl_entity_t *entities, unsigned int max_entities );\n\tvoid (*R_Flush)( unsigned int flush_flags );\n\n\t// debug\n\tvoid (*R_ShowTextures)( void );\n\n\t// texture management\n\tconst byte *(*R_GetTextureOriginalBuffer)( unsigned int idx ); // not always available\n\tint (*GL_LoadTextureFromBuffer)( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update );\n\tvoid (*GL_ProcessTexture)( int texnum, float gamma, int topColor, int bottomColor );\n\tvoid (*R_SetupSky)( int *skyboxTextures );\n\n\t// 2D\n\tvoid (*R_Set2DMode)( qboolean enable );\n\tvoid (*R_DrawStretchRaw)( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty );\n\tvoid (*R_DrawStretchPic)( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum );\n\tvoid (*FillRGBA)( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a ); // in screen space\n\tint  (*WorldToScreen)( const vec3_t world, vec3_t screen );  // Returns 1 if it's z clipped\n\n\t// screenshot, cubemapshot\n\tqboolean (*VID_ScreenShot)( const char *filename, int shot_type );\n\tqboolean (*VID_CubemapShot)( const char *base, uint size, const float *vieworg, qboolean skyshot );\n\n\t// light\n\tcolorVec (*R_LightPoint)( const float *p );\n\n\t// decals\n\t// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords\n\tvoid (*R_DecalShoot)( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale );\n\tvoid (*R_DecalRemoveAll)( int texture );\n\tint (*R_CreateDecalList)( struct decallist_s *pList );\n\tvoid (*R_ClearAllDecals)( void );\n\n\t// studio interface\n\tfloat (*R_StudioEstimateFrame)( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time );\n\tvoid (*R_StudioLerpMovement)( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\n\tvoid (*CL_InitStudioAPI)( void );\n\n\t// bmodel\n\tvoid (*R_SetSkyCloudsTextures)( int solidskyTexture, int alphaskyTexture );\n\tvoid (*GL_SubdivideSurface)( model_t *mod, msurface_t *fa );\n\tvoid (*CL_RunLightStyles)( lightstyle_t *ls );\n\n\t// sprites\n\tvoid (*R_GetSpriteParms)( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite );\n\tint (*R_GetSpriteTexture)( const model_t *m_pSpriteModel, int frame );\n\n\t// model management\n\t// flags ignored for everything except spritemodels\n\tqboolean (*Mod_ProcessRenderData)( model_t *mod, qboolean create, const byte *buffer );\n\tvoid (*Mod_StudioLoadTextures)( model_t *mod, void *data );\n\n\t// efx implementation\n\tvoid (*CL_DrawParticles)( double frametime, particle_t *particles, float partsize );\n\tvoid (*CL_DrawTracers)( double frametime, particle_t *tracers );\n\tvoid (*CL_DrawBeams)( int fTrans , BEAM *beams );\n\tqboolean (*R_BeamCull)( const vec3_t start, const vec3_t end, qboolean pvsOnly );\n\n\t// Xash3D Render Interface\n\t// Get renderer info (doesn't changes engine state at all)\n\tint\t\t\t(*RefGetParm)( int parm, int arg );\t// generic\n\tvoid\t\t(*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale );\n\tvoid\t\t(*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha );\n\tfloat\t\t(*GetFrameTime)( void );\n\n\t// Set renderer info (tell engine about changes)\n\tvoid\t\t(*R_SetCurrentEntity)( struct cl_entity_s *ent ); // tell engine about both currententity and currentmodel\n\tvoid\t\t(*R_SetCurrentModel)( struct model_s *mod );\t// change currentmodel but leave currententity unchanged\n\n\t// Texture tools\n\tint\t\t(*GL_FindTexture)( const char *name );\n\tconst char*\t(*GL_TextureName)( unsigned int texnum );\n\tconst byte*\t(*GL_TextureData)( unsigned int texnum ); // may be NULL\n\tint\t\t(*GL_LoadTexture)( const char *name, const byte *buf, size_t size, int flags );\n\tint\t\t(*GL_CreateTexture)( const char *name, int width, int height, const void *buffer, texFlags_t flags );\n\tint\t\t(*GL_LoadTextureArray)( const char **names, int flags );\n\tint\t\t(*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags );\n\tvoid\t\t(*GL_FreeTexture)( unsigned int texnum );\n\tvoid\t(*R_OverrideTextureSourceSize)( unsigned int texnum, unsigned int srcWidth, unsigned int srcHeight ); // used to override decal size for texture replacement\n\n\t// Decals manipulating (draw & remove)\n\tvoid\t\t(*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa );\n\tfloat\t\t*(*R_DecalSetupVerts)( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount );\n\tvoid\t\t(*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only)\n\n\t// AVI\n\tvoid\t\t(*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data );\n\n\t// glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client)\n\tvoid\t\t(*GL_Bind)( int tmu, unsigned int texnum );\n\tvoid\t\t(*GL_SelectTexture)( int tmu );\n\tvoid\t\t(*GL_LoadTextureMatrix)( const float *glmatrix );\n\tvoid\t\t(*GL_TexMatrixIdentity)( void );\n\tvoid\t\t(*GL_CleanUpTextureUnits)( int last );\t// pass 0 for clear all the texture units\n\tvoid\t\t(*GL_TexGen)( unsigned int coord, unsigned int mode );\n\tvoid\t\t(*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture\n\tvoid\t\t(*GL_TexCoordArrayMode)( unsigned int texmode );\n\tvoid\t\t(*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics\n\tvoid\t\t(*GL_Reserved0)( void );\t// for potential interface expansion without broken compatibility\n\tvoid\t\t(*GL_Reserved1)( void );\n\n\t// Misc renderer functions\n\tvoid\t\t(*GL_DrawParticles)( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime );\n\tcolorVec\t\t(*LightVec)( const float *start, const float *end, float *lightspot, float *lightvec );\n\tstruct mstudiotex_s *( *StudioGetTexture )( struct cl_entity_s *e );\n\n\t// passed through R_RenderFrame (0 - use engine renderer, 1 - use custom client renderer)\n\tvoid\t\t(*GL_RenderFrame)( const struct ref_viewpass_s *rvp );\n\t// setup map bounds for ortho-projection when we in dev_overview mode\n\tvoid\t\t(*GL_OrthoBounds)( const float *mins, const float *maxs );\n\t// grab r_speeds message\n\tqboolean\t(*R_SpeedsMessage)( char *out, size_t size );\n\t// get visdata for current frame from custom renderer\n\tbyte*\t\t(*Mod_GetCurrentVis)( void );\n\t// tell the renderer what new map is started\n\tvoid\t\t(*R_NewMap)( void );\n\t// clear the render entities before each frame\n\tvoid\t\t(*R_ClearScene)( void );\n\t// GL_GetProcAddress for client renderer\n\tvoid*\t\t(*R_GetProcAddress)( const char *name );\n\n\t// TriAPI Interface\n\t// NOTE: implementation isn't required to be compatible\n\tvoid\t(*TriRenderMode)( int mode );\n\tvoid\t(*Begin)( int primitiveCode );\n\tvoid\t(*End)( void );\n\tvoid\t(*Color4f)( float r, float g, float b, float a ); // real glColor4f\n\tvoid\t(*Color4ub)( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); // real glColor4ub\n\tvoid\t(*TexCoord2f)( float u, float v );\n\tvoid\t(*Vertex3fv)( const float *worldPnt );\n\tvoid\t(*Vertex3f)( float x, float y, float z );\n\tvoid\t(*Fog)( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b.\n\tvoid\t(*ScreenToWorld)( const float *screen, float *world  );\n\tvoid\t(*GetMatrix)( const int pname, float *matrix );\n\tvoid\t(*FogParams)( float flDensity, int iFogSkybox );\n\tvoid    (*CullFace)( TRICULLSTYLE mode );\n\n\t// vgui drawing implementation\n\tvoid\t(*VGUI_SetupDrawing)( qboolean rect );\n\tvoid\t(*VGUI_UploadTextureBlock)( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );\n\n\t// only Vulkan manages devices in renderer code\n\tconst ref_device_t *(*pfnGetVulkanRenderDevice)( unsigned int idx );\n} ref_interface_t;\n\ntypedef int (*REFAPI)( int version, ref_interface_t *pFunctionTable, ref_api_t* engfuncs, ref_globals_t *pGlobals );\n#define GET_REF_API \"GetRefAPI\"\n\n#ifdef REF_DLL\n#define DEFINE_ENGINE_SHARED_CVAR( x, y ) cvar_t *x = NULL;\n#define DECLARE_ENGINE_SHARED_CVAR( x, y ) extern cvar_t *x;\n#define RETRIEVE_ENGINE_SHARED_CVAR( x, y ) \\\n\tif(!( x = gEngfuncs.pfnGetCvarPointer( #y, 0 ) )) \\\n\t\tgEngfuncs.Host_Error( S_ERROR \"engine didn't gave us %s cvar pointer\\n\", #y );\n#define ENGINE_SHARED_CVAR_NAME( f, x, y ) f( x, y )\n#define ENGINE_SHARED_CVAR( f, x ) ENGINE_SHARED_CVAR_NAME( f, x, x )\n\n// cvars that's logic is shared between renderer and engine\n// actually, they are just created on engine side for convinience\n// and must be retrieved by renderer side\n// sometimes it's done to standartize cvars to make it easier for users\n#define ENGINE_SHARED_CVAR_LIST( f ) \\\n\tENGINE_SHARED_CVAR_NAME( f, vid_gamma, gamma ) \\\n\tENGINE_SHARED_CVAR_NAME( f, vid_brightness, brightness ) \\\n\tENGINE_SHARED_CVAR_NAME( f, v_lightgamma, lightgamma ) \\\n\tENGINE_SHARED_CVAR_NAME( f, v_direct, direct ) \\\n\tENGINE_SHARED_CVAR( f, r_showtextures ) \\\n\tENGINE_SHARED_CVAR( f, r_speeds ) \\\n\tENGINE_SHARED_CVAR( f, r_fullbright ) \\\n\tENGINE_SHARED_CVAR( f, r_norefresh ) \\\n\tENGINE_SHARED_CVAR( f, r_lightmap ) \\\n\tENGINE_SHARED_CVAR( f, r_dynamic ) \\\n\tENGINE_SHARED_CVAR( f, r_drawentities ) \\\n\tENGINE_SHARED_CVAR( f, r_decals ) \\\n\tENGINE_SHARED_CVAR( f, r_showhull ) \\\n\tENGINE_SHARED_CVAR( f, gl_vsync ) \\\n\tENGINE_SHARED_CVAR( f, gl_clear ) \\\n\tENGINE_SHARED_CVAR( f, cl_himodels ) \\\n\tENGINE_SHARED_CVAR( f, cl_lightstyle_lerping ) \\\n\tENGINE_SHARED_CVAR( f, tracerred ) \\\n\tENGINE_SHARED_CVAR( f, tracergreen ) \\\n\tENGINE_SHARED_CVAR( f, tracerblue ) \\\n\tENGINE_SHARED_CVAR( f, traceralpha ) \\\n\tENGINE_SHARED_CVAR( f, r_sprite_lerping ) \\\n\tENGINE_SHARED_CVAR( f, r_sprite_lighting ) \\\n\tENGINE_SHARED_CVAR( f, r_drawviewmodel ) \\\n\tENGINE_SHARED_CVAR( f, r_glowshellfreq ) \\\n\tENGINE_SHARED_CVAR( f, host_allow_materials ) \\\n\n#define DECLARE_ENGINE_SHARED_CVAR_LIST() \\\n\tENGINE_SHARED_CVAR_LIST( DECLARE_ENGINE_SHARED_CVAR )\n\n#define DEFINE_ENGINE_SHARED_CVAR_LIST() \\\n\tENGINE_SHARED_CVAR_LIST( DEFINE_ENGINE_SHARED_CVAR )\n\n#define RETRIEVE_ENGINE_SHARED_CVAR_LIST() \\\n\tENGINE_SHARED_CVAR_LIST( RETRIEVE_ENGINE_SHARED_CVAR )\n#endif\n\n#endif // REF_API\n"
  },
  {
    "path": "engine/ref_vulkan.h",
    "content": "#ifndef REF_VULKAN_H\n#define REF_VULKAN_H\n\n// Define Vulkan handles without depending on vulkan.h\n#ifndef VULKAN_H_\n#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;\n#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)\n#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;\n#else\n#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;\n#endif\n\nVK_DEFINE_HANDLE(VkInstance)\nVK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)\n\n#undef VK_DEFINE_HANDLE\n#undef VK_DEFINE_NON_DISPATCHABLE_HANDLE\n#endif // ifndef VULKAN_H_\n\nint XVK_GetInstanceExtensions( unsigned int count, const char **pNames );\nvoid *XVK_GetVkGetInstanceProcAddr( void );\nVkSurfaceKHR XVK_CreateSurface( VkInstance instance );\n\n#endif /* REF_VULKAN_H */\n"
  },
  {
    "path": "engine/server/server.h",
    "content": "/*\nserver.h - primary header for server\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef SERVER_H\n#define SERVER_H\n\n#include \"xash3d_mathlib.h\"\n#include \"edict.h\"\n#include \"eiface.h\"\n#include \"physint.h\"\t// physics interface\n#include \"mod_local.h\"\n#include \"pm_defs.h\"\n#include \"pm_movevars.h\"\n#include \"entity_state.h\"\n#include \"protocol.h\"\n#include \"netchan.h\"\n#include \"custom.h\"\n#include \"world.h\"\n\n//=============================================================================\n\n#define SV_UPDATE_MASK\t(SV_UPDATE_BACKUP - 1)\n#if XASH_LOW_MEMORY == 2\n#define SV_UPDATE_BACKUP SINGLEPLAYER_BACKUP\n#else\nextern int SV_UPDATE_BACKUP;\n#endif\n\n// hostflags\n#define SVF_SKIPLOCALHOST\tBIT( 0 )\n#define SVF_MERGE_VISIBILITY\tBIT( 1 )\t// we are do portal pass\n\n// mapvalid flags\n#define MAP_IS_EXIST        BIT( 0 )\n#define MAP_HAS_LANDMARK    BIT( 2 )\n#define MAP_INVALID_VERSION BIT( 3 )\n\n#define SV_SPAWN_TIME\t0.1\n\n// group flags\n#define GROUP_OP_AND\t0\n#define GROUP_OP_NAND\t1\n\n#ifdef NDEBUG\n#define SV_IsValidEdict( e )\t( e && !e->free )\n#else\n#define SV_IsValidEdict( e )\tSV_CheckEdict( e, __FILE__, __LINE__ )\n#endif\n#define NUM_FOR_EDICT(e)\t((int)((edict_t *)(e) - svgame.edicts))\n#define EDICT_NUM( num )\tSV_EdictNum( num )\n#define STRING( offset )\tSV_GetString( offset )\n#define ALLOC_STRING(str)\tSV_AllocString( str )\n#define MAKE_STRING(str)\tSV_MakeString( str )\n\n#define MAX_PUSHED_ENTS\t256\n#define MAX_VIEWENTS\t128\n#define MAX_LOCALINFO_STRING\t32768\t// localinfo used on server and not sended to the clients\n\n#define MAX_ENT_LEAFS( ext ) (( ext ) ? MAX_ENT_LEAFS_32 : MAX_ENT_LEAFS_16 )\n\n#define FCL_RESEND_USERINFO\tBIT( 0 )\n#define FCL_RESEND_MOVEVARS\tBIT( 1 )\n#define FCL_SKIP_NET_MESSAGE\tBIT( 2 )\n#define FCL_SEND_NET_MESSAGE\tBIT( 3 )\n#define FCL_PREDICT_MOVEMENT\tBIT( 4 )\t// movement prediction is enabled\n#define FCL_LOCAL_WEAPONS\tBIT( 5 )\t// weapon prediction is enabled\n#define FCL_LAG_COMPENSATION\tBIT( 6 )\t// lag compensation is enabled\n#define FCL_FAKECLIENT\tBIT( 7 )\t// this client is a fake player controlled by the game DLL\n#define FCL_HLTV_PROXY\tBIT( 8 )\t// this is a proxy for a HLTV client (spectator)\n#define FCL_SEND_RESOURCES\tBIT( 9 )\n#define FCL_FORCE_UNMODIFIED\tBIT( 10 )\n\ntypedef enum\n{\n\tss_dead,\t\t// no map loaded\n\tss_loading,\t// spawning level edicts\n\tss_active\t\t// actively running\n} sv_state_t;\n\ntypedef enum\n{\n\tcs_free = 0,\t// can be reused for a new connection\n\tcs_zombie,\t// client has been disconnected, but don't reuse connection for a couple seconds\n\tcs_connected,\t// has been assigned to a sv_client_t, but not in game yet\n\tcs_spawning,\t// put in game, but not spawned yet\n\tcs_spawned\t// client is fully in game\n} cl_state_t;\n\ntypedef enum\n{\n\tus_inactive = 0,\n\tus_processing,\n\tus_complete,\n} cl_upload_t;\n\n// instanced baselines container\ntypedef struct\n{\n\tconst char\t*classname;\n\tentity_state_t\tbaseline;\n} sv_baseline_t;\n\ntypedef struct\n{\n\tqboolean\t\tactive;\n\tqboolean\t\tnet_log;\n\tnetadr_t\t\tnet_address;\n\tfile_t\t\t*file;\n} server_log_t;\n\ntypedef struct server_s\n{\n\tsv_state_t\tstate;\t\t// precache commands are only valid during load\n\n\tqboolean\t\tbackground;\t// this is background map\n\tqboolean\t\tloadgame;\t\t// client begins should reuse existing entity\n\tdouble\t\ttime;\t\t// sv.time += sv.frametime\n\tdouble\t\ttime_residual;\t// unclamped\n\tfloat\t\tframetime;\t// 1.0 / sv_fps->value\n\tint\t\tframecount;\t// count physic frames\n\tstruct sv_client_s\t*current_client;\t// current client who network message sending on\n\n\tint\t\thostflags;\t// misc server flags: predicting etc\n\tCRC32_t\t\tworldmapCRC;\t// check crc for catch cheater maps\n\tint\t\tprogsCRC;\t\t// this is used with feature ENGINE_QUAKE_COMPATIBLE\n\n\tchar\t\tname[MAX_QPATH];\t// map name\n\tchar\t\tstartspot[MAX_QPATH];\n\n\tdouble\t\tlastchecktime;\n\tint\t\tlastcheck;\t// number of last checked client\n\n\tchar\t\tmodel_precache[MAX_MODELS][MAX_QPATH];\n\tchar\t\tsound_precache[MAX_SOUNDS][MAX_QPATH];\n\tchar\t\tfiles_precache[MAX_CUSTOM][MAX_QPATH];\n\tchar\t\tevent_precache[MAX_EVENTS][MAX_QPATH];\n\tbyte\t\tmodel_precache_flags[MAX_MODELS];\n\tmodel_t\t\t*models[MAX_MODELS];\n\tint\t\tnum_static_entities;\n\n\t// run local lightstyles to let SV_LightPoint grab the actual information\n\tlightstyle_t\tlightstyles[MAX_LIGHTSTYLES];\n\n\tconsistency_t\tconsistency_list[MAX_MODELS];\n\tresource_t\tresources[MAX_RESOURCES];\n\tint\t\tnum_consistency;\t// typically check model bounds on this\n\tint\t\tnum_resources;\n\n\tsv_baseline_t\tinstanced[MAX_CUSTOM_BASELINES];\t// instanced baselines\n\tint\t\tlast_valid_baseline;// all the entities with number more than that was created in-game and doesn't have the baseline\n\tint\t\tnum_instanced;\n\n\t// unreliable data to send to clients.\n\tsizebuf_t\t\tdatagram;\n\tbyte\t\tdatagram_buf[MAX_DATAGRAM];\n\n\t// reliable data to send to clients.\n\tsizebuf_t\t\treliable_datagram;\t// copied to all clients at end of frame\n\tbyte\t\treliable_datagram_buf[MAX_DATAGRAM];\n\n\t// the multicast buffer is used to send a message to a set of clients\n\tsizebuf_t\t\tmulticast;\n\tbyte\t\tmulticast_buf[MAX_MULTICAST];\n\n\tsizebuf_t\t\tsignon;\n\tbyte\t\tsignon_buf[MAX_INIT_MSG];\t// need a get to maximum size\n\n\tsizebuf_t\t\tspec_datagram;\n\tbyte\t\tspectator_buf[MAX_MULTICAST];\n\n\tmodel_t\t\t*worldmodel;\t// pointer to world\n\n\tqboolean\t\tplayersonly;\n\tqboolean\t\tsimulating;\t// physics is running\n\tqboolean\t\tpaused;\n\n\t// statistics\n\tint\t\tignored_static_ents;\n\tint\t\tignored_world_decals;\n\tint\t\tstatic_ents_overflow;\n} server_t;\n\ntypedef struct\n{\n\tdouble\t\tsenttime;\n\tfloat\t\tping_time;\n\n\tclientdata_t\tclientdata;\n\tweapon_data_t\tweapondata[MAX_LOCAL_WEAPONS];\n\n\tint  \t\tnum_entities;\n\tint  \t\tfirst_entity;\t\t// into the circular sv_packet_entities[]\n} client_frame_t;\n\ntypedef struct sv_client_s\n{\n\tcl_state_t  state;\n\tcl_upload_t upstate;    // uploading state\n\tchar        name[32];   // extracted from userinfo, color string allowed\n\tuint        flags;      // client flags, some info\n\tuint        extensions; // protocol extensions\n\n\tchar hashedcdkey[34];            // MD5 hash is 32 hex #'s, plus trailing 0\n\tchar userinfo[MAX_INFO_STRING];  // name, etc (received from client)\n\tchar physinfo[MAX_INFO_STRING];  // set on server (transmit to client)\n\tchar useragent[MAX_INFO_STRING];\n\n\tbyte ignorecmdtime_warned; // did we warn our server operator in the log for this batch of commands?\n\tuint listeners;   // which other clients does this guy's voice stream go to?\n\n\tint ignorecmdtime_warns; // how many times client time was faster than server during this session\n\tint userid;              // identifying number on server\n\n\tnetchan_t netchan;\n\tsizebuf_t datagram; // the datagram is written to by sound calls, prints, temp ents, etc.\n\tbyte      datagram_buf[MAX_DATAGRAM]; // it can be harmlessly overflowed.\n\n\tint chokecount;     // number of messages rate supressed\n\tint delta_sequence; // -1 = no compression.\n\n\tdouble next_messagetime;   // time when we should send next world state update\n\tdouble next_checkpingtime; // time to send all players pings to client\n\tdouble next_sendinfotime;  // time to send info about all players\n\tdouble cl_updaterate;      // client requested updaterate\n\tdouble timebase;           // client timebase\n\tdouble connection_started;\n\n\tcustomization_t customdata;      // player customization linked list\n\tresource_t      resourcesonhand;\n\tresource_t      resourcesneeded; // <mapname.res> from client (server downloading)\n\tusercmd_t       lastcmd;         // for filling in big drops\n\n\tint    packet_loss;\n\tdouble connecttime;\n\tdouble cmdtime;\n\tdouble ignorecmdtime;\n\tfloat  latency;\n\n\tint     ignored_ents; // if visibility list is full we should know how many entities will be ignored\n\tedict_t *edict;       // EDICT_NUM(clientnum+1)\n\tedict_t *pViewEntity; // svc_setview member\n\tedict_t *viewentity[MAX_VIEWENTS]; // list of portal cameras in player PVS\n\tint     num_viewents; // num of portal cameras that can merge PVS\n\n\tint    userinfo_change_attempts;\n\tdouble fullupdate_next_calltime;\n\tdouble userinfo_next_changetime;\n\tdouble userinfo_penalty;\n\n\tdouble overflow_warn_time;\n\n\tclient_frame_t *frames; // updates can be delta'd from here\n\tevent_state_t  events;  // delta-updated events cycle\n} sv_client_t;\n\n/*\n=============================================================================\n a client can leave the server in one of four ways:\n dropping properly by quiting or disconnecting\n timing out if no valid messages are received for sv_timeout.value seconds\n getting kicked off by the server operator\n a program error, like an overflowed reliable buffer\n=============================================================================\n*/\ntypedef struct\n{\n\tchar\t\tname[32];\t// in GoldSrc max name length is 12\n\tint\t\tnumber;\t// svc_ number\n\tint\t\tsize;\t// if size == -1, size come from first byte after svcnum\n} sv_user_message_t;\n\ntypedef struct\n{\n\tedict_t\t\t*ent;\n\tvec3_t\t\torigin;\n\tvec3_t\t\tangles;\n\tint\t\tfixangle;\n} sv_pushed_t;\n\ntypedef struct\n{\n\tqboolean\t\tactive;\n\tqboolean\t\tmoving;\n\tqboolean\t\tfirstframe;\n\tqboolean\t\tnointerp;\n\n\tvec3_t\t\tmins;\n\tvec3_t\t\tmaxs;\n\n\tvec3_t\t\tcurpos;\n\tvec3_t\t\toldpos;\n\tvec3_t\t\tnewpos;\n\tvec3_t\t\tfinalpos;\n} sv_interp_t;\n\ntypedef struct\n{\n\t// user messages stuff\n\tconst char\t*msg_name;\t\t// just for debug\n\tsv_user_message_t\tmsg[MAX_USER_MESSAGES];\t// user messages array\n\tint\t\tmsg_size_index;\t\t// write message size at this pos in bitbuf\n\tint\t\tmsg_realsize;\t\t// left in bytes\n\tint\t\tmsg_index;\t\t// for debug messages\n\tint\t\tmsg_dest;\t\t\t// msg destination ( MSG_ONE, MSG_ALL etc )\n\tint     msg_rewrite_index;\n\tint     msg_rewrite_pos;\n\tqboolean\t\tmsg_started;\t\t// to avoid recursive included messages\n\tedict_t\t\t*msg_ent;\t\t\t// user message member entity\n\tvec3_t\t\tmsg_org;\t\t\t// user message member origin\n\tqboolean\tmsg_trace;\t\t// trace this message\n\n\tvoid\t\t*hInstance;\t\t// pointer to game.dll\n\n\tedict_t\t\t*edicts;\t\t\t// solid array of server entities\n\tint\t\tnumEntities;\t\t// actual entities count\n\n\tmovevars_t\tmovevars;\t\t\t// movement variables curstate\n\tmovevars_t\toldmovevars;\t\t// movement variables oldstate\n\tplayermove_t\t*pmove;\t\t\t// pmove state\n\tsv_interp_t\tinterp[MAX_CLIENTS];\t// interpolate clients\n\tsv_pushed_t\tpushed[MAX_PUSHED_ENTS];\t// no reason to keep array for all edicts\n\t\t\t\t\t\t// 256 it should be enough for any game situation\n\n\tglobalvars_t\t*globals;\t\t\t// server globals\n\n\tDLL_FUNCTIONS\tdllFuncs;\t\t\t// dll exported funcs\n\tNEW_DLL_FUNCTIONS\tdllFuncs2;\t\t// new dll exported funcs (may be NULL)\n\tphysics_interface_t\tphysFuncs;\t\t// physics interface functions (Xash3D extension)\n\n\tpoolhandle_t mempool;\t\t\t// server premamnent pool: edicts etc\n\tpoolhandle_t stringspool;\t\t// for engine strings\n} svgame_static_t;\n\ntypedef struct\n{\n\tqboolean\t\tinitialized;\t\t// sv_init has completed\n\tdouble\t\ttimestart;\t\t// just for profiling\n\n\tint\t\tmaxclients;\t\t// server max clients\n\n\tint\t\tgroupmask;\n\tint\t\tgroupop;\n\n\tserver_log_t\tlog;\n\n\tchar\t\tserverinfo[MAX_SERVERINFO_STRING];\n\tchar\t\tlocalinfo[MAX_LOCALINFO_STRING];\n\n\tint\t\tspawncount;\t\t// incremented each server start\n\t\t\t\t\t\t// used to check late spawns\n\tsv_client_t\t*clients;\t\t\t// [svs.maxclients]\n\tint\t\tnum_client_entities;\t// svs.maxclients*UPDATE_BACKUP*MAX_PACKET_ENTITIES\n\tint\t\tnext_client_entities;\t// next client_entity to use\n\tentity_state_t\t*packet_entities;\t\t// [num_client_entities]\n\tentity_state_t\t*baselines;\t\t// [GI->max_edicts]\n\tentity_state_t\t*static_entities;\t\t// [MAX_STATIC_ENTITIES];\n\n\tuint32_t  challenge_salt[16]; // pregenerated random numbers for generating challenged based on IP's MD5 address\n\n\tsizebuf_t testpacket;         // pregenerataed testpacket, only needs CRC32 patching\n\tbyte      *testpacket_buf;    // check for NULL if testpacket is available\n\tbyte      *testpacket_crcpos; // pointer to write pregenerated crc (unaligned!!!)\n\tuint32_t  *testpacket_crcs;   // checksums lookup table\n\tint       testpacket_filepos; // file position (need to calculate lookup table pos)\n\tint       testpacket_filelen; // file and lookup table length\n} server_static_t;\n\n//=============================================================================\n\nextern server_static_t svs RENAME_SYMBOL( \"svs_\" ); // persistant server info\nextern server_t        sv RENAME_SYMBOL( \"sv_\" );   // local server\nextern svgame_static_t svgame;                      // persistant game info\nextern areanode_t      sv_areanodes[];              // AABB dynamic tree\n\nextern convar_t\t\tmp_logecho;\nextern convar_t\t\tmp_logfile;\nextern convar_t\t\tsv_log_onefile;\nextern convar_t\t\tsv_log_singleplayer;\nextern convar_t\t\tsv_unlag;\nextern convar_t\t\tsv_maxunlag;\nextern convar_t\t\tsv_unlagpush;\nextern convar_t\t\tsv_unlagsamples;\nextern convar_t\t\trcon_enable;\nextern convar_t\t\tsv_instancedbaseline;\nextern convar_t\t\tsv_background_freeze;\nextern convar_t\t\tsv_minupdaterate;\nextern convar_t\t\tsv_maxupdaterate;\nextern convar_t\t\tsv_minrate;\nextern convar_t\t\tsv_maxrate;\nextern convar_t\t\tsv_downloadurl;\nextern convar_t\t\tsv_newunit;\nextern convar_t\t\tsv_clienttrace;\nextern convar_t\t\tsv_failuretime;\nextern convar_t\t\tsv_send_resources;\nextern convar_t\t\tsv_send_logos;\nextern convar_t\t\tsv_allow_upload;\nextern convar_t\t\tsv_allow_download;\nextern convar_t\t\tsv_friction;\nextern convar_t\t\tsv_gravity;\nextern convar_t\t\tsv_stopspeed;\nextern convar_t\t\tsv_wateralpha;\nextern convar_t\t\tsv_wateramp;\nextern convar_t\t\tsv_voiceenable;\nextern convar_t\t\tsv_voicequality;\nextern convar_t\t\tsv_maxvelocity;\nextern convar_t\t\tsv_stepsize;\nextern convar_t\t\tsv_skyname;\nextern convar_t\t\tsv_skycolor_r;\nextern convar_t\t\tsv_skycolor_g;\nextern convar_t\t\tsv_skycolor_b;\nextern convar_t\t\tsv_skyvec_x;\nextern convar_t\t\tsv_skyvec_y;\nextern convar_t\t\tsv_skyvec_z;\nextern convar_t\t\tsv_consistency;\nextern convar_t\t\tsv_password;\nextern convar_t\t\tsv_uploadmax;\nextern convar_t\t\tsv_trace_messages;\nextern convar_t\t\tsv_enttools_enable;\nextern convar_t\t\tsv_enttools_maxfire;\nextern convar_t\t\tsv_autosave;\nextern convar_t\t\tdeathmatch;\nextern convar_t\t\thostname;\nextern convar_t\t\tskill;\nextern convar_t\t\tcoop;\nextern convar_t\t\tsv_cheats;\nextern convar_t\t\tpublic_server;\nextern convar_t\t\tsv_nat;\nextern convar_t\t\tsv_speedhack_kick;\nextern convar_t\t\tsv_pausable;\t\t// allows pause in multiplayer\nextern convar_t\t\tsv_check_errors;\nextern convar_t\t\tsv_lighting_modulate;\nextern convar_t\t\tsv_novis;\nextern convar_t\t\tsv_hostmap;\nextern convar_t\t\tsv_validate_changelevel;\nextern convar_t\t\tsv_maxclients;\nextern convar_t\t\tsv_userinfo_enable_penalty;\nextern convar_t\t\tsv_userinfo_penalty_time;\nextern convar_t\t\tsv_userinfo_penalty_multiplier;\nextern convar_t\t\tsv_userinfo_penalty_attempts;\nextern convar_t\t\tsv_fullupdate_penalty_time;\nextern convar_t\t\tsv_log_outofband;\nextern convar_t\t\tsv_allow_autoaim;\nextern convar_t\t\tsv_aim;\nextern convar_t\t\tsv_allow_testpacket;\nextern convar_t\t\tsv_expose_player_list;\n\n//===========================================================\n//\n// sv_main.c\n//\nvoid SV_FinalMessage( const char *message, qboolean reconnect );\nvoid SV_KickPlayer( sv_client_t *cl, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid SV_DropClient( sv_client_t *cl, qboolean crash ) RENAME_SYMBOL( \"SV_DropClient_\" );\nvoid SV_UpdateMovevars( qboolean initialize );\nint SV_ModelIndex( const char *name );\nint SV_SoundIndex( const char *name );\nint SV_EventIndex( const char *name );\nint SV_GenericIndex( const char *name );\nvoid SV_InitOperatorCommands( void );\nvoid SV_KillOperatorCommands( void );\nvoid SV_RemoteCommand( netadr_t from, sizebuf_t *msg );\nvoid SV_SendResource( resource_t *pResource, sizebuf_t *msg );\nvoid SV_AddToMaster( netadr_t from, sizebuf_t *msg );\nqboolean SV_ProcessUserAgent( netadr_t from, const char *useragent );\n\n//\n// sv_init.c\n//\nqboolean SV_InitGame( void );\nvoid SV_ActivateServer( int runPhysics );\nqboolean SV_SpawnServer( const char *server, const char *startspot, qboolean background );\nvoid SV_DeactivateServer( void );\nvoid SV_FreeTestPacket( void );\n\n/*\n================\nSV_ModelHandle\n\nget model by handle\n================\n*/\nstatic inline model_t *GAME_EXPORT SV_ModelHandle( int modelindex )\n{\n\tif( unlikely( modelindex < 0 || modelindex >= MAX_MODELS ))\n\t\treturn NULL;\n\treturn sv.models[modelindex];\n}\n\n\n//\n// sv_phys.c\n//\nvoid SV_Physics( void );\nqboolean SV_InitPhysicsAPI( void );\nvoid SV_CheckVelocity( edict_t *ent );\nqboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time );\nvoid SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace );\nvoid SV_FreeOldEntities( void );\n\n//\n// sv_move.c\n//\nqboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink );\nqboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink );\nvoid SV_MoveToOrigin( edict_t *ed, const vec3_t goal, float dist, int iMode );\nqboolean SV_CheckBottom( edict_t *ent, int iMode );\nfloat SV_VecToYaw( const vec3_t src );\nvoid SV_WaterMove( edict_t *ent );\n\n//\n// sv_send.c\n//\nvoid SV_SendClientMessages( void );\nvoid SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... ) FORMAT_CHECK( 2 );\n\n//\n// sv_client.c\n//\nvoid SV_RefreshUserinfo( void );\nvoid SV_TogglePause( const char *msg );\nqboolean SV_ShouldUpdatePing( sv_client_t *cl );\nconst char *SV_GetClientIDString( sv_client_t *cl );\nsv_client_t *SV_ClientById( int id );\nsv_client_t *SV_ClientByName( const char *name );\nvoid SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg );\nvoid SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg );\nvoid SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss );\nvoid SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl );\nvoid SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg );\nvoid SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg );\nedict_t *SV_FakeConnect( const char *netname );\nvoid SV_BuildReconnect( sizebuf_t *msg );\nint SV_CalcPing( const sv_client_t *cl );\nvoid SV_UpdateServerInfo( void );\nvoid SV_EndRedirect( host_redirect_t *rd );\nvoid SV_RejectConnection( netadr_t from, const char *fmt, ... ) FORMAT_CHECK( 2 );\nvoid SV_GetPlayerCount( int *clients, int *bots );\n\nstatic inline qboolean SV_HavePassword( void )\n{\n\tif( COM_CheckStringEmpty( sv_password.string ) && Q_stricmp( sv_password.string, \"none\" ))\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic inline qboolean SV_IsPlayerIndex( int idx )\n{\n\treturn idx > 0 && idx <= svs.maxclients ? true : false;\n}\n\n//\n// sv_cmds.c\n//\nvoid SV_InitHostCommands( void );\n\n//\n// sv_custom.c\n//\nvoid SV_AddToResourceList( resource_t *pResource, resource_t *pList );\nvoid SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource );\nvoid SV_RemoveFromResourceList( resource_t *pResource );\nvoid SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg );\nint SV_EstimateNeededResources( sv_client_t *cl );\nvoid SV_ClearResourceList( resource_t *pList );\nvoid SV_BatchUploadRequest( sv_client_t *cl );\nvoid SV_SendResources( sv_client_t *cl, sizebuf_t *msg );\nvoid SV_ClearResourceLists( sv_client_t *cl );\nvoid SV_TransferConsistencyInfo( void );\nvoid SV_RequestMissingResources( void );\n\n//\n// sv_filter.c\n//\nvoid SV_InitFilter( void );\nqboolean SV_CheckIP( netadr_t *adr );\nqboolean SV_CheckID( const char *id );\n\n//\n// sv_frame.c\n//\nvoid SV_InactivateClients( void );\nint SV_FindBestBaseline( int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player );\nvoid SV_SkipUpdates( void );\n\n//\n// sv_game.c\n//\nqboolean SV_LoadProgs( const char *name );\nvoid SV_UnloadProgs( void );\nvoid SV_FreeEdicts( void );\nedict_t *SV_AllocEdict( void );\nvoid SV_FreeEdict( edict_t *pEdict );\nvoid SV_InitEdict( edict_t *pEdict );\nconst char *SV_ClassName( const edict_t *e );\nvoid SV_CopyTraceToGlobal( trace_t *trace );\nqboolean SV_CheckEdict( const edict_t *e, const char *file, const int line );\nvoid SV_SetMinMaxSize( edict_t *e, const float *min, const float *max, qboolean relink );\nvoid SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\nint SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos );\nqboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax );\nvoid SV_QueueChangeLevel( const char *level, const char *landname );\nvoid SV_WriteEntityPatch( const char *filename );\nvoid SV_SpawnEntities( const char *mapname );\nedict_t* SV_CreateNamedEntity( edict_t *ent, string_t className );\nstring_t SV_AllocString( const char *szValue );\nstring_t SV_MakeString( const char *szValue );\nconst char *SV_GetString( string_t iString );\nvoid SV_SetStringArrayMode( qboolean dynamic );\nvoid SV_EmptyStringPool( qboolean clear_stats );\nvoid SV_PrintStr64Stats_f( void );\nsv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only );\nuint SV_MapIsValid( const char *filename, const char *landmark_name );\nvoid SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch );\nedict_t *SV_FindGlobalEntity( string_t classname, string_t globalname );\nqboolean SV_CreateStaticEntity( struct sizebuf_s *msg, int index );\nvoid SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user );\nint pfnIndexOfEdict( const edict_t *pEdict );\nvoid SV_UpdateBaseVelocity( edict_t *ent );\nvoid SV_RestartAmbientSounds( void );\nvoid SV_RestartDecals( void );\nvoid SV_RestartStaticEnts( void );\nint pfnDropToFloor( edict_t* e );\nvoid SV_SetModel( edict_t *ent, const char *name );\nint pfnDecalIndex( const char *m );\nvoid SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale );\nqboolean SV_RestoreCustomDecal( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent );\n\nstatic inline edict_t *SV_EdictNum( int n )\n{\n\tif( likely( n >= 0 && n < GI->max_edicts ))\n\t\treturn &svgame.edicts[n];\n\treturn NULL;\n}\n\n//\n// sv_log.c\n//\nvoid Log_Close( void );\nvoid Log_Open( void );\nvoid Log_PrintServerVars( void );\nvoid SV_ServerLog_f( void );\nvoid SV_SetLogAddress_f( void );\n\n//\n// sv_save.c\n//\nqboolean SV_SaveGame( const char *pName );\nqboolean SV_LoadGame( const char *pName );\nint SV_LoadGameState( char const *level );\nvoid SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char *start, qboolean background );\nconst char *SV_GetLatestSave( void );\nvoid SV_InitSaveRestore( void );\nvoid SV_ClearGameState( void );\n\n//\n// sv_pmove.c\n//\nvoid SV_InitClientMove( void );\nvoid SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed );\n\n//\n// sv_world.c\n//\nvoid SV_ClearWorld( void );\nvoid SV_UnlinkEdict( edict_t *ent );\nvoid SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace );\nvoid SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace );\ntrace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip );\ntrace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e );\nconst char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end );\nmsurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end );\ntrace_t SV_MoveToss( edict_t *tossent, edict_t *ignore );\nvoid SV_LinkEdict( edict_t *ent, qboolean touch_triggers );\nint SV_TruePointContents( const vec3_t p );\nint SV_PointContents( const vec3_t p );\nvoid SV_SetLightStyle( int style, const char* s, float f );\nint SV_LightForEntity( edict_t *pEdict );\n\n//\n// sv_query.c\n//\nvoid SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from );\n\n#endif//SERVER_H\n"
  },
  {
    "path": "engine/server/sv_client.c",
    "content": "/*\nsv_client.c - client interactions\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"const.h\"\n#include \"server.h\"\n#include \"net_encode.h\"\n#include \"net_api.h\"\n\nconst char *const clc_strings[clc_lastmsg+1] =\n{\n\t\"clc_bad\",\n\t\"clc_nop\",\n\t\"clc_move\",\n\t\"clc_stringcmd\",\n\t\"clc_delta\",\n\t\"clc_resourcelist\",\n\t\"clc_legacy_userinfo\",\n\t\"clc_fileconsistency\",\n\t\"clc_voicedata\",\n\t\"clc_cvarvalue/clc_goldsrc_hltv\",\n\t\"clc_cvarvalue2/clc_goldsrc_requestcvarvalue\",\n\t\"clc_goldsrc_requestcvarvalue2\",\n};\n\ntypedef struct ucmd_s\n{\n\tconst char\t*name;\n\tqboolean\t\t(*func)( sv_client_t *cl );\n} ucmd_t;\n\nstatic int\tg_userid = 1;\n\nstatic void SV_UserinfoChanged( sv_client_t *cl );\nstatic void SV_ExecuteClientCommand( sv_client_t *cl, const char *s );\n\n/*\n=================\nSV_GetPlayerCount\n\n=================\n*/\nvoid SV_GetPlayerCount( int *players, int *bots )\n{\n\tint i;\n\n\t*players = 0;\n\t*bots = 0;\n\n\tif( !svs.clients )\n\t\treturn;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tif( svs.clients[i].state >= cs_connected )\n\t\t{\n\t\t\tif( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT ))\n\t\t\t\t(*bots)++;\n\t\t\telse\n\t\t\t\t(*players)++;\n\t\t}\n\n\t}\n}\n\n/*\n=================\nSV_GetChallenge\n\nReturns a challenge number that can be used\nin a subsequent client_connect command.\nWe do this to prevent denial of service attacks that\nflood the server with invalid connection IPs.  With a\nchallenge, they must give a valid IP address.\n=================\n*/\nstatic int SV_GetChallenge( netadr_t from, qboolean *error )\n{\n\tconst netadrtype_t type = NET_NetadrType( &from );\n\tMD5Context_t ctx;\n\tbyte digest[16];\n\n\t*error = false;\n\n\tMD5Init( &ctx );\n\n\tswitch( type )\n\t{\n\tcase NA_IP:\n\t\tMD5Update( &ctx, from.ip, sizeof( from.ip ));\n\t\tbreak;\n\tcase NA_IPX:\n\t\tMD5Update( &ctx, from.ipx, sizeof( from.ipx ));\n\t\tbreak;\n\tcase NA_IP6:\n\t{\n\t\tbyte ip6[16];\n\t\tNET_NetadrToIP6Bytes( ip6, &from );\n\t\tMD5Update( &ctx, ip6, sizeof( ip6 ));\n\t\tbreak;\n\t}\n\tcase NA_LOOPBACK:\n\t\treturn 0;\n\tdefault:\n\t\t*error = true;\n\t\treturn 0;\n\t}\n\n\tMD5Update( &ctx, (byte *)svs.challenge_salt, sizeof( svs.challenge_salt ));\n\tMD5Final( digest, &ctx );\n\n\treturn digest[0] | digest[1] << 8 | digest[2] << 16 | digest[3] << 24;\n}\n\nstatic void SV_SendChallenge( netadr_t from )\n{\n\tqboolean error = false;\n\tint challenge = SV_GetChallenge( from, &error );\n\n\tif( error )\n\t\treturn;\n\n\t// send it back\n\tNetchan_OutOfBandPrint( NS_SERVER, from, S2C_CHALLENGE\" %i\", challenge );\n}\n\nstatic int SV_GetFragmentSize( void *pcl, fragsize_t mode )\n{\n\tsv_client_t *cl = (sv_client_t*)pcl;\n\tint\tcl_frag_size;\n\n\tif( Netchan_IsLocal( &cl->netchan ))\n\t\treturn FRAGMENT_LOCAL_SIZE;\n\n\tif( mode == FRAGSIZE_UNRELIABLE )\n\t{\n\t\t// allow setting unreliable limit with \"setinfo cl_urmax\"\n\t\tcl_frag_size = Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_urmax\" ));\n\t\tif( cl_frag_size == 0 )\n\t\t\treturn NET_MAX_MESSAGE;\n\t\treturn bound( FRAGMENT_MAX_SIZE, cl_frag_size, NET_MAX_MESSAGE );\n\t}\n\n\tcl_frag_size = Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_dlmax\" ));\n\tcl_frag_size = bound( FRAGMENT_MIN_SIZE, cl_frag_size, FRAGMENT_MAX_SIZE );\n\n\tif( mode != FRAGSIZE_FRAG )\n\t{\n\t\tif( cl->extensions & NET_EXT_SPLITSIZE )\n\t\t\treturn cl_frag_size;\n\t\telse\n\t\t\treturn 0; // original engine behaviour\n\t}\n\n\t// get in-game fragmentation size\n\tif( cl->state == cs_spawned )\n\t{\n\t\t// allow setting in-game fragsize with \"setinfo cl_frmax\"\n\t\tint frmax = Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_frmax\" ));\n\n\t\tif( frmax < FRAGMENT_MIN_SIZE || frmax > FRAGMENT_MAX_SIZE )\n\t\t\tcl_frag_size /= 2; // add window for unreliable\n\t\telse\n\t\t\tcl_frag_size = frmax;\n\t}\n\n\treturn cl_frag_size - HEADER_BYTES;\n}\n\n/*\n================\nSV_RejectConnection\n\nRejects connection request and sends back a message\n================\n*/\nvoid SV_RejectConnection( netadr_t from, const char *fmt, ... )\n{\n\tchar\ttext[1024];\n\tva_list\targptr;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( text, sizeof( text ), fmt, argptr );\n\tva_end( argptr );\n\n\tCon_Reportf( \"%s connection refused. Reason: %s\\n\", NET_AdrToString( from ), text );\n\tNetchan_OutOfBandPrint( NS_SERVER, from, S2C_ERRORMSG\"\\n^1Server was reject the connection:^7 %s\", text );\n\tNetchan_OutOfBandPrint( NS_SERVER, from, A2C_PRINT\"\\n^1Server was reject the connection:^7 %s\", text );\n\tNetchan_OutOfBandPrint( NS_SERVER, from, S2C_REJECT\"\\n\" );\n}\n\n/*\n================\nSV_FailDownload\n\nfor some reasons file can't be downloaded\ntell the client about this problem\n================\n*/\nstatic void SV_FailDownload( sv_client_t *cl, const char *filename )\n{\n\tif( !COM_CheckString( filename ))\n\t\treturn;\n\n\tMSG_BeginServerCmd( &cl->netchan.message, svc_filetxferfailed );\n\tMSG_WriteString( &cl->netchan.message, filename );\n}\n\n/*\n================\nSV_CheckChallenge\n\nMake sure connecting client is not spoofing\n================\n*/\nstatic int SV_CheckChallenge( netadr_t from, int challenge )\n{\n\tqboolean error = false;\n\tint challenge2 = SV_GetChallenge( from, &error );\n\n\tif( error || challenge2 != challenge )\n\t{\n\t\tSV_RejectConnection( from, \"no challenge for your address\\n\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n================\nSV_CheckIPRestrictions\n\nDetermine if client is outside appropriate address range\n================\n*/\nstatic int SV_CheckIPRestrictions( netadr_t from )\n{\n\tif( sv_lan.value )\n\t{\n\t\tif( !NET_IsReservedAdr( from ))\n\t\t\treturn 0;\n\t}\n\treturn 1;\n}\n\n/*\n================\nSV_FindEmptySlot\n\nGet slot # and set client_t pointer for player, if possible\nWe don't do this search on a \"reconnect, we just reuse the slot\n================\n*/\nstatic sv_client_t *SV_FindEmptySlot( void )\n{\n\tint i;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tif( svs.clients[i].state == cs_free )\n\t\t\treturn &svs.clients[i];\n\t}\n\n\treturn NULL;\n}\n\nstatic void SV_MaybeNotifyPlayerCountChange( const sv_client_t *cl, const char *address )\n{\n\tint i, count = 0;\n\n\t// if this was the first client on the server, or the last client\n\t// the server can hold, send a heartbeat to the master.\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tif( svs.clients[i].state >= cs_connected )\n\t\t\tcount++;\n\t}\n\n\tif( count == 1 || count == svs.maxclients )\n\t\tNET_MasterClear();\n\n\tLog_Printf( \"\\\"%s<%i><%i><>\\\" connected, address \\\"%s\\\"\\n\",\n\t\tcl->name, cl->userid, (int)( cl - svs.clients ), address );\n}\n\n/*\n==================\nSV_ConnectClient\n\nA connection request that did not come from the master\n==================\n*/\nstatic void SV_ConnectClient( netadr_t from )\n{\n\tchar userinfo[MAX_INFO_STRING];\n\tchar protinfo[MAX_INFO_STRING];\n\tclient_frame_t *frames;\n\tsv_client_t *newcl = NULL;\n\tint qport, version;\n\tint i;\n\tint challenge;\n\tconst char *s;\n\tint extensions;\n\tuint netchan_flags = 0;\n\n\tif( Cmd_Argc() < 5 )\n\t{\n\t\tSV_RejectConnection( from, \"insufficient connection info\\n\" );\n\t\treturn;\n\t}\n\n\tversion = Q_atoi( Cmd_Argv( 1 ));\n\n\tif( version != PROTOCOL_VERSION )\n\t{\n\t\tSV_RejectConnection( from, \"unsupported protocol (%i should be %i)\\n\", version, PROTOCOL_VERSION );\n\t\treturn;\n\t}\n\n\t// LAN servers restrict to class b IP addresses\n\tif( !SV_CheckIPRestrictions( from ))\n\t{\n\t\tSV_RejectConnection( from, \"LAN servers are restricted to local clients (class C)\\n\" );\n\t\treturn;\n\t}\n\n\tchallenge = Q_atoi( Cmd_Argv( 2 )); // get challenge\n\n\t// see if the challenge is valid (local clients don't need to challenge)\n\tif( !SV_CheckChallenge( from, challenge ))\n\t\treturn;\n\n\ts = Cmd_Argv( 3 );\n\tif( Q_strlen( s ) > sizeof( protinfo ) || !Info_IsValid( s ))\n\t{\n\t\tSV_RejectConnection( from, \"invalid protinfo in connect command\\n\" );\n\t\treturn;\n\t}\n\n\tQ_strncpy( protinfo, s, sizeof( protinfo )); // protocol info\n\n\tif( !SV_ProcessUserAgent( from, protinfo ))\n\t\treturn;\n\n\t// extract qport from protocol info\n\tqport = Q_atoi( Info_ValueForKey( protinfo, \"qport\" ));\n\textensions = Q_atoi( Info_ValueForKey( protinfo, \"ext\" ));\n\n\ts = Cmd_Argv( 4 );\t// user info\n\n\tif( Q_strlen( s ) > sizeof( userinfo ) || !Info_IsValid( s ))\n\t{\n\t\tSV_RejectConnection( from, \"invalid userinfo in connect command\\n\" );\n\t\treturn;\n\t}\n\n\tQ_strncpy( userinfo, s, sizeof( userinfo ));\n\n\t// check connection password (don't verify local client)\n\tif( !NET_IsLocalAddress( from ) && SV_HavePassword( ))\n\t{\n\t\tif( Q_stricmp( sv_password.string, Info_ValueForKey( userinfo, \"password\" )))\n\t\t{\n\t\t\tSV_RejectConnection( from, \"invalid password\\n\" );\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// if there is already a slot for this ip, reuse it\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tsv_client_t *cl = &svs.clients[i];\n\n\t\tif( cl->state == cs_free || cl->state == cs_zombie )\n\t\t\tcontinue;\n\n\t\tif( NET_CompareBaseAdr( from, cl->netchan.remote_address ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remote_address.port ))\n\t\t{\n\t\t\tnewcl = cl;\n\t\t\tCon_Reportf( S_NOTE \"%s:reconnect\\n\", NET_AdrToString( from ));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// A reconnecting client will re-use the slot found above when checking for reconnection.\n\t// the slot will be wiped clean.\n\tif( !newcl )\n\t{\n\t\t// connect the client if there are empty slots.\n\t\tnewcl = SV_FindEmptySlot();\n\n\t\tif( !newcl )\n\t\t{\n\t\t\tSV_RejectConnection( from, \"server is full\\n\" );\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// build a new connection\n\t// accept the new client\n\tsv.current_client = newcl;\n\tframes = Mem_Realloc( host.mempool, newcl->frames, sizeof( client_frame_t ) * SV_UPDATE_BACKUP );\n\tmemset( frames, 0, sizeof( client_frame_t ) * SV_UPDATE_BACKUP );\n\tSV_ClearResourceLists( newcl );\n\n\t// a1ba: preserve physinfo and viewent as it's set by game logic before client connect!\n\t{\n\t\tchar physinfo[MAX_INFO_STRING];\n\t\tedict_t *viewent = newcl->pViewEntity;\n\n\t\tmemcpy( physinfo, newcl->physinfo, sizeof( physinfo ));\n\n\t\tmemset( newcl, 0, sizeof( *newcl ));\n\n\t\tmemcpy( newcl->physinfo, physinfo, sizeof( newcl->physinfo ));\n\t\tnewcl->pViewEntity = viewent;\n\t}\n\n\tnewcl->edict = EDICT_NUM(( newcl - svs.clients ) + 1 );\n\tnewcl->frames = frames;\n\tnewcl->userid = g_userid++;\t// create unique userid\n\tnewcl->state = cs_connected;\t// now expect \"spawn\" command\n\tnewcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE );\n\tQ_strncpy( newcl->useragent, protinfo, sizeof( newcl->useragent ));\n\n\t// HACKHACK: can hear all players by default to avoid issues\n\t// with server.dll without voice game manager\n\tnewcl->listeners = -1;\n\n\t// initailize netchan\n\tif( !Host_IsLocalClient( ))\n\t\tSetBits( netchan_flags, NETCHAN_USE_LZSS );\n\tNetchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize, netchan_flags );\n\tMSG_Init( &newcl->datagram, \"Datagram\", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf\n\n\tQ_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, \"uuid\" ), 32 );\n\tnewcl->hashedcdkey[32] = '\\0';\n\n\t// build protinfo answer\n\tprotinfo[0] = '\\0';\n\tInfo_SetValueForKeyf( protinfo, \"ext\", sizeof( protinfo ), \"%d\", newcl->extensions );\n\n\t// send the connect packet to the client\n\tNetchan_OutOfBandPrint( NS_SERVER, from, S2C_CONNECTION\" %s\", protinfo );\n\n\tnewcl->upstate = us_inactive;\n\tnewcl->connection_started = host.realtime;\n\tnewcl->cl_updaterate = 0.05;\t// 20 fps as default\n\tnewcl->delta_sequence = -1;\n\n\t// parse some info from the info strings (this can override cl_updaterate)\n\tQ_strncpy( newcl->userinfo, userinfo, sizeof( newcl->userinfo ));\n\n\tSV_UserinfoChanged( newcl );\n\n\tnewcl->next_messagetime = host.realtime + newcl->cl_updaterate;\n\n\t// reset stats\n\tnewcl->next_checkpingtime = -1.0;\n\n\tSV_MaybeNotifyPlayerCountChange( newcl, NET_AdrToString( newcl->netchan.remote_address ));\n}\n\n/*\n==================\nSV_FakeConnect\n\nA connection request that came from the game module\n==================\n*/\nedict_t *GAME_EXPORT SV_FakeConnect( const char *netname )\n{\n\tchar userinfo[MAX_INFO_STRING];\n\tint i, count = 0;\n\tsv_client_t *cl;\n\n\t// find a client slot\n\tcl = SV_FindEmptySlot();\n\n\tif( !cl )\n\t\treturn NULL; // server is full\n\n\tuserinfo[0] = '\\0';\n\n\tif( !COM_CheckString( netname ))\n\t\tnetname = \"Bot\";\n\n\t// setup fake client params\n\tInfo_SetValueForKey( userinfo, \"name\", netname, sizeof( userinfo ));\n\tInfo_SetValueForKey( userinfo, \"model\", \"gordon\", sizeof( userinfo ));\n\tInfo_SetValueForKey( userinfo, \"topcolor\", \"1\", sizeof( userinfo ));\n\tInfo_SetValueForKey( userinfo, \"bottomcolor\", \"1\", sizeof( userinfo ));\n\n\t// build a new connection\n\t// accept the new client\n\tsv.current_client = cl;\n\tif( cl->frames )\n\t\tMem_Free( cl->frames );\t// fakeclients doesn't have frames\n\tSV_ClearResourceLists( cl );\n\n\tmemset( cl, 0, sizeof( *cl ));\n\n\tcl->state = cs_spawned;\n\tcl->edict = EDICT_NUM(( cl - svs.clients ) + 1 );\n\tcl->userid = g_userid++; // create unique userid\n\tSetBits( cl->flags, FCL_FAKECLIENT );\n\n\t// parse some info from the info strings\n\tQ_strncpy( cl->userinfo, userinfo, sizeof( cl->userinfo ));\n\n\tSV_UserinfoChanged( cl );\n\tSetBits( cl->flags, FCL_RESEND_USERINFO );\n\tSetBits( cl->edict->v.flags, FL_CLIENT|FL_FAKECLIENT );\t// mark it as fakeclient\n\tcl->connection_started = host.realtime;\n\n\tSV_MaybeNotifyPlayerCountChange( cl, \"local\" );\n\n\treturn cl->edict;\n}\n\n/*\n==================\nSV_Kick_f\n\nKick a user off of the server\n==================\n*/\nvoid SV_KickPlayer( sv_client_t *cl, const char *fmt, ... )\n{\n\tconst char *clientId;\n\tva_list va;\n\tchar buf[MAX_VA_STRING];\n\n\tif( NET_IsLocalAddress( cl->netchan.remote_address ))\n\t{\n\t\tCon_Printf( \"The local player cannot be kicked!\\n\" );\n\t\treturn;\n\t}\n\n\tclientId = SV_GetClientIDString( cl );\n\n\tva_start( va, fmt );\n\tQ_vsnprintf( buf, sizeof( buf ), fmt, va );\n\tva_end( va );\n\n\tif( buf[0] )\n\t{\n\t\tLog_Printf( \"Kick: \\\"%s<%i><%s><>\\\" was kicked by \\\"Console\\\" (message \\\"%s\\\")\\n\", cl->name, cl->userid, clientId, buf );\n\t\tSV_BroadcastPrintf( cl, \"%s was kicked with message: \\\"%s\\\"\\n\", cl->name, buf );\n\t\tSV_ClientPrintf( cl, \"You were kicked from the game with message: \\\"%s\\\"\\n\", buf );\n\t\tif( cl->useragent[0] )\n\t\t\tNetchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, S2C_ERRORMSG\"\\nKicked with message:\\n%s\\n\", buf );\n\t}\n\telse\n\t{\n\t\tLog_Printf( \"Kick: \\\"%s<%i><%s><>\\\" was kicked by \\\"Console\\\"\\n\", cl->name, cl->userid, clientId );\n\t\tSV_BroadcastPrintf( cl, \"%s was kicked\\n\", cl->name );\n\t\tSV_ClientPrintf( cl, \"You were kicked from the game\\n\" );\n\t\tif( cl->useragent[0] )\n\t\t\tNetchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, S2C_ERRORMSG\"\\nYou were kicked from the game\\n\" );\n\t}\n\n\tSV_DropClient( cl, false );\n}\n\n/*\n=====================\nSV_DropClient\n\nCalled when the player is totally leaving the server, either willingly\nor unwillingly.  This is NOT called if the entire server is quiting\nor crashing.\n=====================\n*/\nvoid SV_DropClient( sv_client_t *cl, qboolean crash )\n{\n\tint\ti;\n\n\tif( cl->state == cs_zombie )\n\t\treturn;\t// already dropped\n\n\tif( !crash )\n\t{\n\t\t// add the disconnect\n\t\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ) )\n\t\t{\n\t\t\tMSG_BeginServerCmd( &cl->netchan.message, svc_disconnect );\n\t\t}\n\n\t\tif( cl->edict && cl->state == cs_spawned )\n\t\t{\n\t\t\tsvgame.dllFuncs.pfnClientDisconnect( cl->edict );\n\t\t}\n\n\t\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ) )\n\t\t{\n\t\t\tNetchan_TransmitBits( &cl->netchan, 0, NULL );\n\t\t}\n\t}\n\n\tClearBits( cl->flags, FCL_FAKECLIENT );\n\tClearBits( cl->flags, FCL_HLTV_PROXY );\n\tcl->state = cs_zombie; // become free in a few seconds\n\tcl->name[0] = 0;\n\n\tif( cl->frames )\n\t\tMem_Free( cl->frames ); // release delta\n\tcl->frames = NULL;\n\n\tif( NET_CompareBaseAdr( cl->netchan.remote_address, host.rd.address ))\n\t\tSV_EndRedirect( &host.rd );\n\n\t// throw away any residual garbage in the channel.\n\tNetchan_Clear( &cl->netchan );\n\n\t// clean client data on disconnect\n\tmemset( cl->userinfo, 0, MAX_INFO_STRING );\n\tmemset( cl->physinfo, 0, MAX_INFO_STRING );\n\tCOM_ClearCustomizationList( &cl->customdata, false );\n\n\t// don't send to other clients\n\tcl->edict = NULL;\n\n\t// send notification to all other clients\n\tSV_FullClientUpdate( cl, &sv.reliable_datagram );\n\n\t// if this was the last client on the server, send a heartbeat\n\t// to the master so it is known the server is empty\n\t// send a heartbeat now so the master will get up to date info\n\t// if there is already a slot for this ip, reuse it\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tif( svs.clients[i].state >= cs_connected )\n\t\t\tbreak;\n\t}\n\n\tif( i == svs.maxclients )\n\t\tNET_MasterClear();\n}\n\n/*\n==============================================================================\n\nSVC COMMAND REDIRECT\n\n==============================================================================\n*/\nstatic void SV_BeginRedirect( host_redirect_t *rd, netadr_t adr, rdtype_t target, char *buffer, size_t buffersize, void (*flush))\n{\n\trd->target = target;\n\trd->buffer = buffer;\n\trd->buffersize = buffersize;\n\trd->flush = flush;\n\trd->address = adr;\n\trd->buffer[0] = 0;\n\tif( rd->lines == 0 )\n\t\trd->lines = -1;\n}\n\nstatic void SV_FlushRedirect( netadr_t adr, int dest, char *buf )\n{\n\tswitch( dest )\n\t{\n\tcase RD_PACKET:\n\t\tNetchan_OutOfBandPrint( NS_SERVER, adr, A2C_PRINT\"\\n%s\", buf );\n\t\tbreak;\n\tcase RD_CLIENT:\n\t\tif( !sv.current_client || !FBitSet( sv.current_client->flags, FCL_FAKECLIENT ))\n\t\t\treturn; // client not set\n\t\tMSG_BeginServerCmd( &sv.current_client->netchan.message, svc_print );\n\t\tMSG_WriteString( &sv.current_client->netchan.message, buf );\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( S_ERROR \"%s: %s: invalid destination\\n\", __func__, NET_AdrToString( adr ));\n\t\tbreak;\n\t}\n}\n\nvoid SV_EndRedirect( host_redirect_t *rd )\n{\n\tif( rd->lines > 0 )\n\t\treturn;\n\n\tif( rd->flush )\n\t\trd->flush( rd->address, rd->target, rd->buffer );\n\n\trd->target = RD_NONE;\n\trd->buffer = NULL;\n\trd->buffersize = 0;\n\trd->flush = NULL;\n}\n\n/*\n================\nRcon_Print\n\nPrint message to rcon buffer and send to rcon redirect target\n================\n*/\nvoid Rcon_Print( host_redirect_t *rd, const char *pMsg )\n{\n\tsize_t len;\n\n\tif( !rd->target || !rd->lines || !rd->flush || !rd->buffer )\n\t\treturn;\n\n\tlen = Q_strncat( rd->buffer, pMsg, rd->buffersize );\n\n\tif( len && rd->buffer[len - 1] == '\\n' )\n\t{\n\t\trd->flush( rd->address, rd->target, rd->buffer );\n\n\t\tif( rd->lines > 0 )\n\t\t\trd->lines--;\n\n\t\trd->buffer[0] = 0;\n\n\t\tif( !rd->lines )\n\t\t\tMsg( \"End of redirection!\\n\" );\n\t}\n}\n\n/*\n===============\nSV_GetClientIDString\n\nReturns a pointer to a static char for most likely only printing.\n===============\n*/\nconst char *SV_GetClientIDString( sv_client_t *cl )\n{\n\tstatic char result[MAX_QPATH];\n\n\tif( !cl )\n\t\treturn \"\";\n\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn \"ID_BOT\";\n\n\tif( NET_IsLocalAddress( cl->netchan.remote_address ))\n\t\treturn \"ID_LOOPBACK\";\n\n\tif( sv_lan.value )\n\t\treturn \"ID_LAN\";\n\n\tQ_snprintf( result, sizeof( result ), \"ID_%s\", cl->hashedcdkey );\n\treturn result;\n}\n\nsv_client_t *SV_ClientById( int id )\n{\n\tsv_client_t *cl;\n\tint i;\n\n\tASSERT( id >= 0 );\n\n\tfor( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ )\n\t{\n\t\tif( !cl->state )\n\t\t\tcontinue;\n\n\t\tif( cl->userid == id )\n\t\t\treturn cl;\n\t}\n\n\treturn NULL;\n}\n\nsv_client_t *SV_ClientByName( const char *name )\n{\n\tsv_client_t *cl;\n\tint i;\n\n\tif( !COM_CheckString( name ))\n\t\treturn NULL;\n\n\tfor( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ )\n\t{\n\t\tif( !cl->state )\n\t\t\tcontinue;\n\n\t\tif( !Q_strcmp( cl->name, name ) )\n\t\t\treturn cl;\n\t}\n\n\treturn NULL;\n}\n\n/*\n================\nSV_TestBandWidth\n\n================\n*/\nstatic void SV_TestBandWidth( netadr_t from )\n{\n\tconst int version = Q_atoi( Cmd_Argv( 1 ));\n\tconst int packetsize = Q_atoi( Cmd_Argv( 2 ));\n\tuint32_t crc;\n\tint ofs;\n\n\t// don't waste time of protocol mismatched\n\tif( version != PROTOCOL_VERSION )\n\t{\n\t\tSV_RejectConnection( from, \"unsupported protocol (%i should be %i)\\n\", version, PROTOCOL_VERSION );\n\t\treturn;\n\t}\n\n\t// quickly reject invalid packets\n\tif( !sv_allow_testpacket.value || !svs.testpacket_buf ||\n\t\t( packetsize <= FRAGMENT_MIN_SIZE ) ||\n\t\t( packetsize > FRAGMENT_MAX_SIZE ))\n\t{\n\t\t// skip the test and just get challenge\n\t\tSV_SendChallenge( from );\n\t\treturn;\n\t}\n\n\t// don't go out of bounds\n\tofs = packetsize - svs.testpacket_filepos - 1;\n\tif(( ofs < 0 ) || ( ofs > svs.testpacket_filelen ))\n\t{\n\t\tSV_SendChallenge( from );\n\t\treturn;\n\t}\n\n\tcrc = svs.testpacket_crcs[ofs];\n\tmemcpy( svs.testpacket_crcpos, &crc, sizeof( crc ));\n\n\t// send the datagram\n\tNET_SendPacket( NS_SERVER, packetsize, MSG_GetData( &svs.testpacket ), from );\n}\n\n/*\n================\nSV_Ack\n\n================\n*/\nstatic void SV_Ack( netadr_t from )\n{\n\tCon_Printf( \"ping %s\\n\", NET_AdrToString( from ));\n}\n\n/*\n================\nSV_Info\n\nResponds with short info for broadcast scans\nThe second parameter should be the current protocol version number.\n================\n*/\nstatic void SV_Info( netadr_t from, int protocolVersion )\n{\n\tchar s[512];\n\n\t// ignore in single player\n\tif( svs.maxclients == 1 || !svs.initialized )\n\t\treturn;\n\n\ts[0] = '\\0';\n\n\tif( protocolVersion != PROTOCOL_VERSION )\n\t{\n\t\tQ_snprintf( s, sizeof( s ), \"%s: wrong version\\n\", hostname.string );\n\t}\n\telse\n\t{\n\t\tint count;\n\t\tint bots;\n\t\tint remaining;\n\t\tchar temp[sizeof( s )];\n\n\t\tSV_GetPlayerCount( &count, &bots );\n\n\t\t// a1ba: send protocol version to distinguish old engine and new\n\t\tInfo_SetValueForKeyf( s, \"p\", sizeof( s ), \"%i\", PROTOCOL_VERSION );\n\t\tInfo_SetValueForKey( s, \"map\", sv.name, sizeof( s ));\n\t\tInfo_SetValueForKey( s, \"dm\", svgame.globals->deathmatch ? \"1\" : \"0\", sizeof( s ));\n\t\tInfo_SetValueForKey( s, \"team\", svgame.globals->teamplay ? \"1\" : \"0\", sizeof( s ));\n\t\tInfo_SetValueForKey( s, \"coop\", svgame.globals->coop ? \"1\" : \"0\", sizeof( s ));\n\t\tInfo_SetValueForKeyf( s, \"numcl\", sizeof( s ), \"%i\", count );\n\t\tInfo_SetValueForKeyf( s, \"maxcl\", sizeof( s ), \"%i\", svs.maxclients );\n\t\tInfo_SetValueForKey( s, \"gamedir\", GI->gamefolder, sizeof( s ));\n\t\tInfo_SetValueForKey( s, \"password\", SV_HavePassword() ? \"1\" : \"0\", sizeof( s ));\n\n\t\t// write host last so we can try to cut off too long hostnames\n\t\t// TODO: value size limit for infostrings\n\t\tremaining = sizeof( s ) - Q_strlen( s ) - sizeof( \"\\\\host\\\\\" ) - 1;\n\t\tif( remaining < 0 )\n\t\t{\n\t\t\t// should never happen?\n\t\t\tCon_Printf( S_ERROR \"%s: infostring overflow!\\n\", __func__ );\n\t\t\treturn;\n\t\t}\n\t\tQ_strncpy( temp, hostname.string, remaining );\n\t\tInfo_SetValueForKey( s, \"host\", temp, sizeof( s ));\n\t}\n\n\tNetchan_OutOfBandPrint( NS_SERVER, from, A2A_INFO\"\\n%s\", s );\n}\n\nstatic void SV_ConnectNatClient( netadr_t from )\n{\n\tnetadr_t to;\n\n\tif( !sv_nat.value || !NET_IsMasterAdr( from ))\n\t\treturn;\n\n\tif( !NET_StringToAdr( Cmd_Argv( 1 ), &to ))\n\t\treturn;\n\n\tif( NET_IsReservedAdr( to ))\n\t\treturn;\n\n\tSV_Info( to, PROTOCOL_VERSION );\n}\n\n/*\n================\nSV_BuildNetAnswer\n\nResponds with long info for local and broadcast requests\n================\n*/\nstatic void SV_BuildNetAnswer( netadr_t from )\n{\n\tconst cvar_t *cv;\n\tchar string[4096];\n\tint  version;\n\tint  context;\n\tint  type;\n\tint  count = 0;\n\tint  i;\n\n\t// ignore in single player\n\tif( svs.maxclients == 1 || !svs.initialized )\n\t\treturn;\n\n\tversion = Q_atoi( Cmd_Argv( 1 ));\n\tcontext = Q_atoi( Cmd_Argv( 2 ));\n\ttype = Q_atoi( Cmd_Argv( 3 ));\n\n\tstring[0] = 0;\n\n\tif( version != PROTOCOL_VERSION )\n\t{\n\t\t// send error unsupported protocol\n\t\tInfo_SetValueForKey( string, \"neterror\", \"protocol\", sizeof( string ));\n\t\tNetchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO\" %i %i %s\\n\", context, type, string );\n\t\treturn;\n\t}\n\n\tswitch( type )\n\t{\n\tcase NETAPI_REQUEST_PING:\n\t\tbreak;\n\tcase NETAPI_REQUEST_RULES:\n\t\tfor( cv = Cvar_GetList( ); cv; cv = cv->next )\n\t\t{\n\t\t\tif( !FBitSet( cv->flags, FCVAR_SERVER ))\n\t\t\t\tcontinue;\n\n\t\t\tif( FBitSet( cv->flags, FCVAR_PROTECTED ))\n\t\t\t{\n\t\t\t\tif( COM_CheckStringEmpty( cv->string ) && Q_stricmp( cv->string, \"none\" ))\n\t\t\t\t\tInfo_SetValueForKey( string, cv->name, \"1\", sizeof( string ));\n\t\t\t\telse Info_SetValueForKey( string, cv->name, \"0\", sizeof( string ));\n\t\t\t}\n\t\t\telse Info_SetValueForKey( string, cv->name, cv->string, sizeof( string ));\n\n\t\t\tcount++;\n\t\t}\n\n\t\tInfo_SetValueForKeyf( string, \"rules\", sizeof( string ), \"%i\", count );\n\t\tbreak;\n\tcase NETAPI_REQUEST_PLAYERS:\n\t\tif( !sv_expose_player_list.value || SV_HavePassword( ))\n\t\t{\n\t\t\tInfo_SetValueForKey( string, \"neterror\", \"forbidden\", sizeof( string ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( i = 0; i < svs.maxclients; i++ )\n\t\t\t{\n\t\t\t\tconst sv_client_t *cl = &svs.clients[i];\n\n\t\t\t\tif( cl->state < cs_connected )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tInfo_SetValueForKey( string, va( \"p%iname\", count ), cl->name, sizeof( string ));\n\t\t\t\tInfo_SetValueForKeyf( string, va( \"p%ifrags\", count ), sizeof( string ), \"%i\", (int)cl->edict->v.frags );\n\t\t\t\tInfo_SetValueForKeyf( string, va( \"p%itime\", count ), sizeof( string ), \"%f\", host.realtime - cl->connection_started );\n\n\t\t\t\tcount++;\n\t\t\t}\n\n\t\t\tInfo_SetValueForKeyf( string, \"players\", sizeof( string ), \"%i\", count );\n\t\t}\n\t\tbreak;\n\tcase NETAPI_REQUEST_DETAILS:\n\t\tfor( i = 0; i < svs.maxclients; i++ )\n\t\t{\n\t\t\tif( svs.clients[i].state >= cs_connected )\n\t\t\t\tcount++;\n\t\t}\n\n\t\t// should match SV_SourceQuery_Details\n\t\tInfo_SetValueForKey( string, \"hostname\", hostname.string, sizeof( string ));\n\t\tInfo_SetValueForKey( string, \"gamedir\", GI->gamefolder, sizeof( string ));\n\t\tInfo_SetValueForKeyf( string, \"current\", sizeof( string ), \"%i\", count );\n\t\tInfo_SetValueForKeyf( string, \"max\", sizeof( string ), \"%i\", svs.maxclients );\n\t\tInfo_SetValueForKey( string, \"map\", sv.name, sizeof( string ));\n\t\tbreak;\n\tdefault:\n\t\t// send error undefined request type\n\t\tInfo_SetValueForKey( string, \"neterror\", \"undefined\", sizeof( string ));\n\t\tbreak;\n\t}\n\n\tNetchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO\" %i %i %s\\n\", context, type, string );\n}\n\n/*\n================\nRcon_Validate\n================\n*/\nstatic qboolean Rcon_Validate( void )\n{\n\tif( !COM_CheckString( rcon_password.string ))\n\t\treturn false;\n\tif( Q_strcmp( Cmd_Argv( 1 ), rcon_password.string ))\n\t\treturn false;\n\treturn true;\n}\n\n/*\n===============\nSV_RemoteCommand\n\nA client issued an rcon command.\nShift down the remaining args\nRedirect all printfs\n===============\n*/\nvoid SV_RemoteCommand( netadr_t from, sizebuf_t *msg )\n{\n\tconst char\t*adr;\n\tint\t\ti;\n\n\tif( !rcon_enable.value || !COM_CheckStringEmpty( rcon_password.string ))\n\t\treturn;\n\n\tadr = NET_AdrToString( from );\n\n\tCon_Printf( \"Rcon from %s:\\n%s\\n\", adr, MSG_GetData( msg ) + 4 );\n\tLog_Printf( \"Rcon: \\\"%s\\\" from \\\"%s\\\"\\n\", MSG_GetData( msg ) + 4, adr );\n\n\tif( Rcon_Validate( ))\n\t{\n\t\tstatic char\toutputbuf[2048];\n\t\tchar remaining[1024];\n\t\tchar *p = remaining;\n\n\t\tremaining[0] = 0;\n\t\tfor( i = 2; i < Cmd_Argc(); i++ )\n\t\t{\n\t\t\tp += Q_strncpy( p, \"\\\"\", sizeof( remaining ) - ( p - remaining ));\n\t\t\tp += Q_strncpy( p, Cmd_Argv( i ), sizeof( remaining ) - ( p - remaining ));\n\t\t\tp += Q_strncpy( p, \"\\\" \", sizeof( remaining ) - ( p - remaining ));\n\t\t}\n\n\t\tSV_BeginRedirect( &host.rd, from, RD_PACKET, outputbuf, sizeof( outputbuf ) - 16, SV_FlushRedirect );\n\t\tCmd_ExecuteString( remaining );\n\t\tSV_EndRedirect( &host.rd );\n\t}\n\telse Con_Printf( S_ERROR \"Bad rcon_password.\\n\" );\n}\n\n/*\n===================\nSV_CalcPing\n\nrecalc ping on current client\n===================\n*/\nint SV_CalcPing( const sv_client_t *cl )\n{\n\tfloat\t\tping = 0;\n\tint\t\ti, count;\n\tint\t\tidx, back;\n\tclient_frame_t\t*frame;\n\n\t// bots don't have a real ping\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ) || !cl->frames )\n\t\treturn 0;\n\n\tif( SV_UPDATE_BACKUP <= 31 )\n\t{\n\t\tback = SV_UPDATE_BACKUP / 2;\n\t\tif( back <= 0 ) return 0;\n\t}\n\telse back = 16;\n\n\tcount = 0;\n\n\tfor( i = 0; i < back; i++ )\n\t{\n\t\tidx = cl->netchan.incoming_acknowledged + ~i;\n\t\tframe = &cl->frames[idx & SV_UPDATE_MASK];\n\n\t\tif( frame->ping_time > 0.0f )\n\t\t{\n\t\t\tping += frame->ping_time;\n\t\t\tcount++;\n\t\t}\n\t}\n\n\tif( count > 0 )\n\t\treturn (( ping / count ) * 1000.0f );\n\treturn 0;\n}\n\n/*\n===================\nSV_EstablishTimeBase\n\nFinangles latency and the like.\n===================\n*/\nstatic void SV_EstablishTimeBase( sv_client_t *cl, const usercmd_t *cmds, int dropped, int numbackup, int numcmds )\n{\n\tdouble\truncmd_time = 0.0;\n\tint\ti, cmdnum = dropped;\n\n\tif( dropped < 24 )\n\t{\n\t\twhile( dropped > numbackup )\n\t\t{\n\t\t\truncmd_time = (double)cl->lastcmd.msec / 1000.0;\n\t\t\tdropped--;\n\t\t}\n\n\t\twhile( dropped > 0 )\n\t\t{\n\t\t\tcmdnum = dropped + numcmds - 1;\n\t\t\truncmd_time += (double)cmds[cmdnum].msec / 1000.0;\n\t\t\tdropped--;\n\t\t}\n\t}\n\n\tfor( i = numcmds - 1; i >= 0; i-- )\n\t\truncmd_time += cmds[i].msec / 1000.0;\n\n\tcl->timebase = sv.time + sv.frametime - runcmd_time;\n}\n\n/*\n===================\nSV_CalcClientTime\n\ncompute latency for client\n===================\n*/\nstatic float SV_CalcClientTime( sv_client_t *cl )\n{\n\tfloat\tminping, maxping;\n\tfloat\tping = 0.0f;\n\tint\ti, count = 0;\n\tint\tbacktrack;\n\n\tbacktrack = (int)sv_unlagsamples.value;\n\tif( backtrack < 1 ) backtrack = 1;\n\n\tif( backtrack >= (SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 ))\n\t\tbacktrack = ( SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 );\n\n\tif( backtrack <= 0 )\n\t\treturn 0.0f;\n\n\tfor( i = 0; i < backtrack; i++ )\n\t{\n\t\tclient_frame_t\t*frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)];\n\t\tif( frame->ping_time <= 0.0f )\n\t\t\tcontinue;\n\n\t\tping += frame->ping_time;\n\t\tcount++;\n\t}\n\n\tif( !count ) return 0.0f;\n\n\tminping =  9999.0f;\n\tmaxping = -9999.0f;\n\tping /= count;\n\n\tfor( i = 0; i < ( SV_UPDATE_BACKUP <= 4 ? SV_UPDATE_BACKUP : 4 ); i++ )\n\t{\n\t\tclient_frame_t\t*frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)];\n\t\tif( frame->ping_time <= 0.0f )\n\t\t\tcontinue;\n\n\t\tif( frame->ping_time < minping )\n\t\t\tminping = frame->ping_time;\n\n\t\tif( frame->ping_time > maxping )\n\t\t\tmaxping = frame->ping_time;\n\t}\n\n\tif( maxping < minping || fabs( maxping - minping ) <= 0.2f )\n\t\treturn ping;\n\n\treturn 0.0f;\n}\n\n/*\n===================\nSV_FullClientUpdate\n\nWrites all update values to a bitbuf\n===================\n*/\nvoid SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg )\n{\n\tchar\t\tinfo[MAX_INFO_STRING];\n\tchar\t\tdigest[16];\n\tMD5Context_t\tctx;\n\tint\t\ti;\n\n\t// process userinfo before updating\n\tSV_UserinfoChanged( cl );\n\n\ti = cl - svs.clients;\n\n\tMSG_BeginServerCmd( msg, svc_updateuserinfo );\n\tMSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS );\n\tMSG_WriteLong( msg, cl->userid );\n\n\tif( cl->name[0] )\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\n\t\tQ_strncpy( info, cl->userinfo, sizeof( info ));\n\n\t\t// remove server passwords, etc.\n\t\tInfo_RemovePrefixedKeys( info, '_' );\n\t\tMSG_WriteString( msg, info );\n\n\t\tMD5Init( &ctx );\n\t\tMD5Update( &ctx, (byte *)cl->hashedcdkey, sizeof( cl->hashedcdkey ));\n\t\tMD5Final( digest, &ctx );\n\n\t\tMSG_WriteBytes( msg, digest, sizeof( digest ));\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n}\n\n/*\n===================\nSV_RefreshUserinfo\n\n===================\n*/\nvoid SV_RefreshUserinfo( void )\n{\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state >= cs_connected )\n\t\t\tSetBits( cl->flags, FCL_RESEND_USERINFO );\n\t}\n}\n\n/*\n===================\nSV_FullUpdateMovevars\n\nthis is send all movevars values when client connected\notherwise see code SV_UpdateMovevars()\n===================\n*/\nvoid SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg )\n{\n\tconst movevars_t nullmovevars = { 0 };\n\n\tMSG_WriteDeltaMovevars( msg, &nullmovevars, &svgame.movevars );\n}\n\n/*\n===================\nSV_ShouldUpdatePing\n\ndetermine should we recalculate\nping times now\n===================\n*/\nqboolean SV_ShouldUpdatePing( sv_client_t *cl )\n{\n\tif( FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t{\n\t\tif( host.realtime < cl->next_checkpingtime )\n\t\t\treturn false;\n\n\t\tcl->next_checkpingtime = host.realtime + 2.0;\n\t\treturn true;\n\t}\n\n\t// they are viewing the scoreboard.  Send them pings.\n\treturn FBitSet( cl->lastcmd.buttons, IN_SCORE ) ? true : false;\n}\n\n/*\n===================\nSV_GetPlayerStats\n\nThis function and its static vars track some of the networking\nconditions.  I haven't bothered to trace it beyond that, because\nthis fucntion sucks pretty badly.\n===================\n*/\nvoid SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss )\n{\n\tstatic int\tlast_ping[MAX_CLIENTS];\n\tstatic int\tlast_loss[MAX_CLIENTS];\n\tint\t\ti;\n\n\ti = cl - svs.clients;\n\n\tif( host.realtime >= cl->next_checkpingtime )\n\t{\n\t\tcl->next_checkpingtime = host.realtime + 2.0;\n\t\tlast_ping[i] = SV_CalcPing( cl );\n\t\tlast_loss[i] = cl->packet_loss;\n\t}\n\n\tif( ping ) *ping = last_ping[i];\n\tif( packet_loss ) *packet_loss = last_loss[i];\n}\n\n/*\n===========\nPutClientInServer\n\nCalled when a player connects to a server or respawns in\na deathmatch.\n============\n*/\nstatic void SV_PutClientInServer( sv_client_t *cl )\n{\n\tstatic byte    \tmsg_buf[MAX_INIT_MSG + 0x200];\t// MAX_INIT_MSG + some space\n\tedict_t\t\t*ent = cl->edict;\n\tsizebuf_t\t\tmsg;\n\n\tMSG_Init( &msg, \"Spawn\", msg_buf, sizeof( msg_buf ));\n\n\tif( sv.loadgame )\n\t{\n\t\t// NOTE: we needs to setup angles on restore here\n\t\tif( ent->v.fixangle == 1 )\n\t\t{\n\t\t\tMSG_BeginServerCmd( &msg, svc_setangle );\n\t\t\tMSG_WriteVec3Angles( &msg, ent->v.angles );\n\t\t\tent->v.fixangle = 0;\n\t\t}\n\n\t\tif( svgame.dllFuncs.pfnParmsChangeLevel )\n\t\t{\n\t\t\tSAVERESTOREDATA\tlevelData;\n\t\t\tstring\t\tname;\n\t\t\tint\t\ti;\n\n\t\t\tmemset( &levelData, 0, sizeof( levelData ));\n\t\t\tsvgame.globals->pSaveData = &levelData;\n\t\t\tsvgame.dllFuncs.pfnParmsChangeLevel();\n\n\t\t\tMSG_BeginServerCmd( &msg, svc_restore );\n\t\t\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL2\", sv.name );\n\t\t\tCOM_FixSlashes( name );\n\t\t\tMSG_WriteString( &msg, name );\n\t\t\tMSG_WriteByte( &msg, levelData.connectionCount );\n\n\t\t\tfor( i = 0; i < levelData.connectionCount; i++ )\n\t\t\t\tMSG_WriteString( &msg, levelData.levelList[i].mapName );\n\n\t\t\tsvgame.globals->pSaveData = NULL;\n\t\t}\n\n\t\t// reset weaponanim\n\t\tMSG_BeginServerCmd( &msg, svc_weaponanim );\n\t\tMSG_WriteByte( &msg, 0 );\n\t\tMSG_WriteByte( &msg, 0 );\n\n\t\tsv.loadgame = false;\n\t\tsv.paused = false;\n\t}\n\telse\n\t{\n\t\tif( Q_atoi( Info_ValueForKey( cl->userinfo, \"hltv\" )))\n\t\t\tSetBits( cl->flags, FCL_HLTV_PROXY );\n\n\t\t// need to realloc private data for client\n\t\tSV_InitEdict( ent );\n\n\t\tif( FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t\t\tSetBits( ent->v.flags, FL_PROXY );\n\t\telse ent->v.flags = 0;\n\n\t\tent->v.netname = MAKE_STRING( cl->name );\n\t\tent->v.colormap = NUM_FOR_EDICT( ent );\t// ???\n\n\t\t// fisrt entering\n\t\tsvgame.globals->time = sv.time;\n\t\tsvgame.dllFuncs.pfnClientPutInServer( ent );\n\n\t\tif( sv.background )\t// don't attack player in background mode\n\t\t\tSetBits( ent->v.flags, FL_GODMODE|FL_NOTARGET );\n\n\t\tcl->pViewEntity = NULL; // reset pViewEntity\n\t}\n\n\tif( svgame.globals->cdAudioTrack )\n\t{\n\t\tMSG_BeginServerCmd( &msg, svc_stufftext );\n\t\tMSG_WriteStringf( &msg, \"cd loop %3d\\n\", svgame.globals->cdAudioTrack );\n\t\tsvgame.globals->cdAudioTrack = 0;\n\t}\n\n#ifdef HACKS_RELATED_HLMODS\n\t// enable dev-mode to prevent crash cheat-protecting from Invasion mod\n\tif( FBitSet( ent->v.flags, FL_GODMODE|FL_NOTARGET ) && !Q_stricmp( GI->gamefolder, \"invasion\" ))\n\t\tSV_ExecuteClientCommand( cl, \"test\\n\" );\n#endif\n\t// refresh the userinfo and movevars\n\t// NOTE: because movevars can be changed during the connection process\n\tSetBits( cl->flags, FCL_RESEND_USERINFO|FCL_RESEND_MOVEVARS );\n\n\t// reset client times\n\tcl->connecttime = 0.0;\n\tcl->ignorecmdtime = 0.0;\n\tcl->cmdtime = 0.0;\n\n\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t{\n\t\tint\tviewEnt;\n\n\t\t// NOTE: it's will be fragmented automatically in right ordering\n\t\tMSG_WriteBits( &msg, MSG_GetData( &sv.signon ), MSG_GetNumBitsWritten( &sv.signon ));\n\n\t\tif( cl->pViewEntity )\n\t\t\tviewEnt = NUM_FOR_EDICT( cl->pViewEntity );\n\t\telse viewEnt = NUM_FOR_EDICT( cl->edict );\n\n\t\tMSG_BeginServerCmd( &msg, svc_setview );\n\t\tMSG_WriteWord( &msg, viewEnt );\n\n\t\tMSG_BeginServerCmd( &msg, svc_signonnum );\n\t\tMSG_WriteByte( &msg, 1 );\n\n\t\tif( MSG_CheckOverflow( &msg ))\n\t\t{\n\t\t\tif( svs.maxclients == 1 )\n\t\t\t\tHost_Error( \"spawn player: overflowed\\n\" );\n\t\t\telse SV_DropClient( cl, false );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// send initialization data\n\t\t\tNetchan_CreateFragments( &cl->netchan, &msg );\n\t\t\tNetchan_FragSend( &cl->netchan );\n\t\t}\n\t}\n}\n\n/*\n===========\nSV_UpdateClientView\n\nResend the client viewentity (used for demos)\n============\n*/\nstatic void SV_UpdateClientView( sv_client_t *cl )\n{\n\tint\tviewEnt;\n\n\tif( cl->pViewEntity )\n\t\tviewEnt = NUM_FOR_EDICT( cl->pViewEntity );\n\telse viewEnt = NUM_FOR_EDICT( cl->edict );\n\n\tMSG_BeginServerCmd( &cl->netchan.message, svc_setview );\n\tMSG_WriteWord( &cl->netchan.message, viewEnt );\n}\n\n/*\n==================\nSV_TogglePause\n==================\n*/\nvoid SV_TogglePause( const char *msg )\n{\n\tif( sv.background ) return;\n\n\tsv.paused ^= 1;\n\n\tif( COM_CheckString( msg ))\n\t\tSV_BroadcastPrintf( NULL, \"%s\", msg );\n\n\t// send notification to all clients\n\tMSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause );\n\tMSG_WriteOneBit( &sv.reliable_datagram, sv.paused );\n}\n\n/*\n================\nSV_SendReconnect\n\nTell all the clients that the server is changing levels\n================\n*/\nvoid SV_BuildReconnect( sizebuf_t *msg )\n{\n\tMSG_BeginServerCmd( msg, svc_stufftext );\n\tMSG_WriteString( msg, \"reconnect\\n\" );\n}\n\n/*\n================\nSV_SendServerdata\n\nSends the first message from the server to a connected client.\nThis will be sent on the initial connection and upon each server load.\n================\n*/\nvoid SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl )\n{\n\tstring\tmessage;\n\tint\ti;\n\n\t// Only send this message to developer console, or multiplayer clients.\n\tif(( host_developer.value ) || ( svs.maxclients > 1 ))\n\t{\n\t\tMSG_BeginServerCmd( msg, svc_print );\n\t\tQ_snprintf( message, sizeof( message ), \"\\n^3BUILD %d SERVER (%i CRC)\\nServer #%i\\n\", Q_buildnum(), sv.progsCRC, svs.spawncount );\n\t\tMSG_WriteString( msg, message );\n\t}\n\n\t// send the serverdata\n\tMSG_BeginServerCmd( msg, svc_serverdata );\n\tMSG_WriteLong( msg, PROTOCOL_VERSION );\n\tMSG_WriteLong( msg, svs.spawncount );\n\tMSG_WriteLong( msg, sv.worldmapCRC );\n\tMSG_WriteByte( msg, cl - svs.clients );\n\tMSG_WriteByte( msg, svs.maxclients );\n\tMSG_WriteWord( msg, GI->max_edicts );\n\tMSG_WriteWord( msg, MAX_MODELS );\n\tMSG_WriteString( msg, sv.name );\n\tMSG_WriteString( msg, STRING( svgame.edicts->v.message )); // Map Message\n\tMSG_WriteOneBit( msg, sv.background ); // tell client about background map\n\tMSG_WriteString( msg, GI->gamefolder );\n\tMSG_WriteLong( msg, host.features );\n\n\t// send the player hulls\n\tfor( i = 0; i < MAX_MAP_HULLS * 3; i++ )\n\t{\n\t\tMSG_WriteChar( msg, host.player_mins[i/3][i%3] );\n\t\tMSG_WriteChar( msg, host.player_maxs[i/3][i%3] );\n\t}\n\n\t// send delta-encoding\n\tDelta_WriteDescriptionToClient( msg );\n\n\t// now client know delta and can reading encoded messages\n\tSV_FullUpdateMovevars( cl, msg );\n\n\t// send the user messages registration\n\tfor( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ )\n\t\tSV_SendUserReg( msg, &svgame.msg[i] );\n\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tif( !sv.lightstyles[i].pattern[0] )\n\t\t\tcontinue;\t// unused style\n\n\t\tMSG_BeginServerCmd( msg, svc_lightstyle );\n\t\tMSG_WriteByte( msg, i ); // stylenum\n\t\tMSG_WriteString( msg, sv.lightstyles[i].pattern );\n\t\tMSG_WriteFloat( msg, sv.lightstyles[i].time );\n\t}\n}\n\n/*\n============================================================\n\nCLIENT COMMAND EXECUTION\n\n============================================================\n*/\n/*\n================\nSV_New_f\n\nSends the first message from the server to a connected client.\nThis will be sent on the initial connection and upon each server load.\n================\n*/\nstatic qboolean SV_New_f( sv_client_t *cl )\n{\n\tbyte\t\tmsg_buf[MAX_INIT_MSG];\n\tchar\t\tszRejectReason[128];\n\tchar\t\tszAddress[128];\n\tchar\t\tszName[32];\n\tsv_client_t\t*cur;\n\tsizebuf_t\t\tmsg;\n\tint\t\ti;\n\n\tmemset( msg_buf, 0, sizeof( msg_buf ));\n\tMSG_Init( &msg, \"New\", msg_buf, sizeof( msg_buf ));\n\n\tif( cl->state != cs_connected )\n\t\treturn false;\n\n\t// send the serverdata\n\tSV_SendServerdata( &msg, cl );\n\n\t// if the client was connected, tell the game .dll to disconnect him/her.\n\tif(( cl->state == cs_spawned ) && cl->edict )\n\t\tsvgame.dllFuncs.pfnClientDisconnect( cl->edict );\n\n\tQ_strncpy( szName, cl->name, sizeof( szName ) );\n\tQ_strncpy( szAddress, NET_AdrToString( cl->netchan.remote_address ), sizeof( szAddress ) );\n\tQ_strncpy( szRejectReason, \"Connection rejected by game\\n\", sizeof( szRejectReason ) );\n\n\t// Allow the game dll to reject this client.\n\tif( !svgame.dllFuncs.pfnClientConnect( cl->edict, szName, szAddress, szRejectReason ))\n\t{\n\t\t// reject the connection and drop the client.\n\t\tSV_RejectConnection( cl->netchan.remote_address, \"%s\\n\", szRejectReason );\n\t\tSV_DropClient( cl, false );\n\t\treturn true;\n\t}\n\n\t// server info string\n\tMSG_BeginServerCmd( &msg, svc_stufftext );\n\tMSG_WriteStringf( &msg, \"fullserverinfo \\\"%s\\\"\\n\", svs.serverinfo );\n\n\t// collect the info about all the players and send to me\n\tfor( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ )\n\t{\n\t\tif( !cur->edict || cur->state != cs_spawned )\n\t\t\tcontinue;\t// not in game yet\n\t\tSV_FullClientUpdate( cur, &msg );\n\t}\n\n\t// g-cont. why this is there?\n\tmemset( &cl->lastcmd, 0, sizeof( cl->lastcmd ));\n\n\tNetchan_CreateFragments( &cl->netchan, &msg );\n\tNetchan_FragSend( &cl->netchan );\n\n\treturn true;\n}\n\n/*\n=================\nSV_Disconnect_f\n\nThe client is going to disconnect, so remove the connection immediately\n=================\n*/\nstatic qboolean SV_Disconnect_f( sv_client_t *cl )\n{\n\tSV_DropClient( cl, false );\n\treturn true;\n}\n\n/*\n==================\nSV_ShowServerinfo_f\n\nDumps the serverinfo info string\n==================\n*/\nstatic qboolean SV_ShowServerinfo_f( sv_client_t *cl )\n{\n\tInfo_Print( svs.serverinfo );\n\treturn true;\n}\n\n/*\n==================\nSV_Pause_f\n==================\n*/\nstatic qboolean SV_Pause_f( sv_client_t *cl )\n{\n\tstring\tmessage;\n\n\tif( UI_CreditsActive( ))\n\t\treturn true;\n\n\tif( !sv_pausable.value )\n\t{\n\t\tSV_ClientPrintf( cl, \"Pause not allowed.\\n\" );\n\t\treturn true;\n\t}\n\n\tif( FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t{\n\t\tSV_ClientPrintf( cl, \"Spectators can not pause.\\n\" );\n\t\treturn true;\n\t}\n\n\tif( !sv.paused ) Q_snprintf( message, MAX_STRING, \"^2%s^7 paused the game\\n\", cl->name );\n\telse Q_snprintf( message, MAX_STRING, \"^2%s^7 unpaused the game\\n\", cl->name );\n\n\tSV_TogglePause( message );\n\n\treturn true;\n}\n\nstatic qboolean SV_ShouldUpdateUserinfo( sv_client_t *cl )\n{\n\tqboolean allow = true; // predict state\n\n\tif( !sv_userinfo_enable_penalty.value )\n\t\treturn allow;\n\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn allow;\n\n\tif( Host_IsLocalGame( ))\n\t\treturn allow;\n\n\t// start from 1 second\n\tif( !cl->userinfo_penalty )\n\t\tcl->userinfo_penalty = sv_userinfo_penalty_time.value;\n\n\t// player changes userinfo after limit time window, but before\n\t// next timewindow\n\t// he seems to be spammer, so just increase change attempts\n\tif( host.realtime < cl->userinfo_next_changetime + cl->userinfo_penalty * sv_userinfo_penalty_multiplier.value )\n\t{\n\t\t// player changes userinfo too quick! ignore!\n\t\tif( host.realtime < cl->userinfo_next_changetime && cl->userinfo_change_attempts > 0 )\n\t\t{\n\t\t\tCon_Reportf( \"%s: ignore userinfo update for %s: penalty %f, attempts %i\\n\",\n\t\t\t\t__func__, cl->name, cl->userinfo_penalty, cl->userinfo_change_attempts );\n\t\t\tallow = false;\n\t\t}\n\n\t\tcl->userinfo_change_attempts++;\n\t}\n\n\t// they spammed too fast, increase penalty\n\tif( cl->userinfo_change_attempts >= (int)sv_userinfo_penalty_attempts.value )\n\t{\n\t\tcl->userinfo_penalty *= sv_userinfo_penalty_multiplier.value;\n\t\tcl->userinfo_change_attempts = 0;\n\n\t\tCon_Reportf( \"%s: penalty set %f for %s\\n\", __func__, cl->userinfo_penalty, cl->name );\n\t}\n\n\tcl->userinfo_next_changetime = host.realtime + cl->userinfo_penalty * sv_userinfo_penalty_multiplier.value;\n\n\treturn allow;\n}\n\n/*\n=================\nSV_UserinfoChanged\n\nPull specific info from a newly changed userinfo string\ninto a more C freindly form.\n=================\n*/\nstatic void SV_UserinfoChanged( sv_client_t *cl )\n{\n\tint\t\ti, dupc = 1;\n\tedict_t\t\t*ent = cl->edict;\n\tstring\t\tname1, name2;\n\tsv_client_t\t*current;\n\tconst char\t\t*val;\n\n\tif( !COM_CheckString( cl->userinfo ))\n\t\treturn;\n\n\tif( !SV_ShouldUpdateUserinfo( cl ))\n\t\treturn;\n\n\tif( !Info_IsValid( cl->userinfo ))\n\t\treturn;\n\n\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\tQ_strncpy( name2, val, sizeof( name2 ));\n\tCOM_TrimSpace( name2, name1 );\n\n\tif( !Q_stricmp( name1, \"console\" ))\n\t{\n\t\tInfo_SetValueForKey( cl->userinfo, \"name\", \"unnamed\", MAX_INFO_STRING );\n\t\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\t}\n\telse if( Q_strcmp( name1, val ))\n\t{\n\t\tInfo_SetValueForKey( cl->userinfo, \"name\", name1, MAX_INFO_STRING );\n\t\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\t}\n\n\tif( !COM_CheckStringEmpty( name1 ) )\n\t{\n\t\tInfo_SetValueForKey( cl->userinfo, \"name\", \"unnamed\", MAX_INFO_STRING );\n\t\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\t\tQ_strncpy( name2, \"unnamed\", sizeof( name2 ));\n\t\tQ_strncpy( name1, \"unnamed\", sizeof( name1 ));\n\t}\n\n\t// check to see if another user by the same name exists\n\twhile( 1 )\n\t{\n\t\tfor( i = 0, current = svs.clients; i < svs.maxclients; i++, current++ )\n\t\t{\n\t\t\tif( current == cl || current->state != cs_spawned )\n\t\t\t\tcontinue;\n\n\t\t\tif( !Q_stricmp( current->name, val ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( i != svs.maxclients )\n\t\t{\n\t\t\t// dup name\n\t\t\tQ_snprintf( name2, sizeof( name2 ), \"%s (%u)\", name1, dupc++ );\n\t\t\tInfo_SetValueForKey( cl->userinfo, \"name\", name2, MAX_INFO_STRING );\n\t\t\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\t\t\tQ_strncpy( cl->name, name2, sizeof( cl->name ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( dupc == 1 ) // unchanged\n\t\t\t\tQ_strncpy( cl->name, name1, sizeof( cl->name ));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// rate command\n\tval = Info_ValueForKey( cl->userinfo, \"rate\" );\n\tif( COM_CheckString( val ) )\n\t\tcl->netchan.rate = bound( sv_minrate.value, Q_atoi( val ), sv_maxrate.value );\n\telse cl->netchan.rate = DEFAULT_RATE;\n\n\t// movement prediction\n\tif( Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_nopred\" )))\n\t\tClearBits( cl->flags, FCL_PREDICT_MOVEMENT );\n\telse SetBits( cl->flags, FCL_PREDICT_MOVEMENT );\n\n\t// lag compensation\n\tif( Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_lc\" )))\n\t\tSetBits( cl->flags, FCL_LAG_COMPENSATION );\n\telse ClearBits( cl->flags, FCL_LAG_COMPENSATION );\n\n\t// weapon perdiction\n\tif( Q_atoi( Info_ValueForKey( cl->userinfo, \"cl_lw\" )))\n\t\tSetBits( cl->flags, FCL_LOCAL_WEAPONS );\n\telse ClearBits( cl->flags, FCL_LOCAL_WEAPONS );\n\n\tval = Info_ValueForKey( cl->userinfo, \"cl_updaterate\" );\n\n\tif( COM_CheckString( val ))\n\t{\n\t\tfloat rate = Q_atoi( val );\n\t\tcl->cl_updaterate = 1.0 / bound( sv_minupdaterate.value, rate, sv_maxupdaterate.value );\n\t}\n\n\t// call prog code to allow overrides\n\tsvgame.dllFuncs.pfnClientUserInfoChanged( cl->edict, cl->userinfo );\n\n\tval = Info_ValueForKey( cl->userinfo, \"name\" );\n\tQ_strncpy( cl->name, val, sizeof( cl->name ));\n\tent->v.netname = MAKE_STRING( cl->name );\n}\n\n/*\n==================\nSV_SetInfo_f\n==================\n*/\nstatic qboolean SV_SetInfo_f( sv_client_t *cl )\n{\n\tInfo_SetValueForKey( cl->userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_INFO_STRING );\n\n\tif( cl->state >= cs_connected )\n\t\tSetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info\n\treturn true;\n}\n\n/*\n==================\nSV_Noclip_f\n==================\n*/\nstatic qboolean SV_Noclip_f( sv_client_t *cl )\n{\n\tedict_t\t*pEntity = cl->edict;\n\n\tif( sv.background || !Cvar_VariableInteger( \"sv_cheats\" ))\n\t\treturn true;\n\n\tif( pEntity->v.movetype != MOVETYPE_NOCLIP )\n\t{\n\t\tSV_ClientPrintf( cl, \"noclip ON\\n\" );\n\t\tpEntity->v.movetype = MOVETYPE_NOCLIP;\n\t}\n\telse\n\t{\n\t\tSV_ClientPrintf( cl, \"noclip OFF\\n\" );\n\t\tpEntity->v.movetype =  MOVETYPE_WALK;\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nSV_Godmode_f\n==================\n*/\nstatic qboolean SV_Godmode_f( sv_client_t *cl )\n{\n\tedict_t\t*pEntity = cl->edict;\n\n\tif( sv.background || !Cvar_VariableInteger( \"sv_cheats\" ))\n\t\treturn true;\n\n\tpEntity->v.flags = pEntity->v.flags ^ FL_GODMODE;\n\n\tif( !FBitSet( pEntity->v.flags, FL_GODMODE ))\n\t\tSV_ClientPrintf( cl, \"godmode OFF\\n\" );\n\telse SV_ClientPrintf( cl, \"godmode ON\\n\" );\n\n\treturn true;\n}\n\n/*\n==================\nSV_Notarget_f\n==================\n*/\nstatic qboolean SV_Notarget_f( sv_client_t *cl )\n{\n\tedict_t\t*pEntity = cl->edict;\n\n\tif( sv.background || !Cvar_VariableInteger( \"sv_cheats\" ))\n\t\treturn true;\n\n\tpEntity->v.flags = pEntity->v.flags ^ FL_NOTARGET;\n\n\tif( !FBitSet( pEntity->v.flags, FL_NOTARGET ))\n\t\tSV_ClientPrintf( cl, \"notarget OFF\\n\" );\n\telse SV_ClientPrintf( cl, \"notarget ON\\n\" );\n\n\treturn true;\n}\n\n/*\n==================\nSV_Kill_f\n==================\n*/\nstatic qboolean SV_Kill_f( sv_client_t *cl )\n{\n\tif( !SV_IsValidEdict( cl->edict ))\n\t\treturn true;\n\n\tif( cl->state != cs_spawned )\n\t{\n\t\tSV_ClientPrintf( cl, \"Can't suicide - not connected!\\n\" );\n\t\treturn true;\n\t}\n\n\tif( cl->edict->v.health <= 0.0f )\n\t{\n\t\tSV_ClientPrintf( cl, \"Can't suicide - already dead!\\n\");\n\t\treturn true;\n\t}\n\n\tsvgame.dllFuncs.pfnClientKill( cl->edict );\n\n\treturn true;\n}\n\n/*\n==================\nSV_SendRes_f\n==================\n*/\nstatic qboolean SV_SendRes_f( sv_client_t *cl )\n{\n\tbyte\tbuffer[MAX_INIT_MSG];\n\tsizebuf_t\tmsg;\n\n\tif( cl->state != cs_connected )\n\t\treturn false;\n\n\tmemset( buffer, 0, sizeof( buffer ));\n\tMSG_Init( &msg, \"SendResources\", buffer, sizeof( buffer ));\n\n\tif( svs.maxclients > 1 && FBitSet( cl->flags, FCL_SEND_RESOURCES ))\n\t\treturn true;\n\n\tSetBits( cl->flags, FCL_SEND_RESOURCES );\n\tSV_SendResources( cl, &msg );\n\n\tNetchan_CreateFragments( &cl->netchan, &msg );\n\tNetchan_FragSend( &cl->netchan );\n\n\treturn true;\n}\n\n/*\n==================\nSV_DownloadFile_f\n==================\n*/\nstatic qboolean SV_DownloadFile_f( sv_client_t *cl )\n{\n\tconst char\t*name;\n\n\tif( Cmd_Argc() < 2 )\n\t\treturn true;\n\n\tname = Cmd_Argv( 1 );\n\n\tif( !COM_CheckString( name ))\n\t\treturn true;\n\n\tif( !COM_IsSafeFileToDownload( name ) || !sv_allow_download.value )\n\t{\n\t\tSV_FailDownload( cl, name );\n\t\treturn true;\n\t}\n\n\t// g-cont. now we supports hot precache\n\tif( name[0] != '!' )\n\t{\n\t\tif( sv_send_resources.value )\n\t\t{\n\t\t\tint i;\n\n\t\t\t// security: allow download only precached resources\n\t\t\tfor( i = 0; i < sv.num_resources; i++ )\n\t\t\t{\n\t\t\t\tconst char *cmpname = name;\n\n\t\t\t\tif( sv.resources[i].type == t_sound )\n\t\t\t\t\tcmpname += sizeof( DEFAULT_SOUNDPATH ) - 1; // cut \"sound/\" off\n\n\t\t\t\tif( !Q_strncmp( sv.resources[i].szFileName, cmpname, 64 ) )\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( i == sv.num_resources )\n\t\t\t{\n\t\t\t\tSV_FailDownload( cl, name );\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// also check the model textures\n\t\t\tif( !Q_stricmp( COM_FileExtension( name ), \"mdl\" ))\n\t\t\t{\n\t\t\t\tif( FS_FileExists( Mod_StudioTexName( name ), false ) > 0 )\n\t\t\t\t\tNetchan_CreateFileFragments( &cl->netchan, Mod_StudioTexName( name ));\n\t\t\t}\n\n\t\t\tif( Netchan_CreateFileFragments( &cl->netchan, name ))\n\t\t\t{\n\t\t\t\tNetchan_FragSend( &cl->netchan );\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tSV_FailDownload( cl, name );\n\t\treturn true;\n\t}\n\n\tif( Q_strlen( name ) == 36 && !Q_strnicmp( name, \"!MD5\", 4 ) && sv_send_logos.value )\n\t{\n\t\tresource_t\tcustResource;\n\t\tbyte\t\tmd5[32];\n\t\tbyte\t\t*pbuf;\n\t\tint\t\tsize;\n\n\t\tmemset( &custResource, 0, sizeof( custResource ) );\n\t\tCOM_HexConvert( name + 4, 32, md5 );\n\n\t\tif( HPAK_ResourceForHash( hpk_custom_file.string, md5, &custResource ))\n\t\t{\n\t\t\tif( HPAK_GetDataPointer( hpk_custom_file.string, &custResource, &pbuf, &size ))\n\t\t\t{\n\t\t\t\tif( size )\n\t\t\t\t{\n\t\t\t\t\tNetchan_CreateFileFragmentsFromBuffer( &cl->netchan, name, pbuf, size );\n\t\t\t\t\tNetchan_FragSend( &cl->netchan );\n\t\t\t\t\tMem_Free( pbuf );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tSV_FailDownload( cl, name );\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nSV_Spawn_f\n==================\n*/\nstatic qboolean SV_Spawn_f( sv_client_t *cl )\n{\n\tif( cl->state != cs_connected )\n\t\treturn false;\n\n\t// handle the case of a level changing while a client was connecting\n\tif( Q_atoi( Cmd_Argv( 1 )) != svs.spawncount )\n\t{\n\t\tSV_New_f( cl );\n\t\treturn true;\n\t}\n\n\tSV_PutClientInServer( cl );\n\n\tcl->state = cs_spawning;\n\n\t// if we are paused, tell the clients\n\tif( sv.paused )\n\t{\n\t\tMSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause );\n\t\tMSG_WriteByte( &sv.reliable_datagram, sv.paused );\n\t\tSV_ClientPrintf( cl, \"Server is paused.\\n\" );\n\t}\n\treturn true;\n}\n\n/*\n==================\nSV_Begin_f\n==================\n*/\nstatic qboolean SV_Begin_f( sv_client_t *cl )\n{\n\t// make sure client has passed connection process correctly\n\tif( cl->state != cs_spawning )\n\t\treturn false;\n\n\t// now client is spawned\n\tcl->state = cs_spawned;\n\tcl->connecttime = host.realtime;\n\n\treturn true;\n}\n\n/*\n==================\nSV_SendBuildInfo_f\n==================\n*/\nstatic qboolean SV_SendBuildInfo_f( sv_client_t *cl )\n{\n\tif( cl->state != cs_spawned )\n\t\treturn false;\n\n\tSV_ClientPrintf( cl, \"Server running \" XASH_ENGINE_NAME \" \" XASH_VERSION \" (build %i-%s, %s-%s)\\n\",\n\t\tQ_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() );\n\treturn true;\n}\n\n/*\n==================\nSV_ClientStatus_f\n==================\n*/\nstatic qboolean SV_ClientStatus_f( sv_client_t *cl )\n{\n\tnetadr_t ip4, ip6;\n\tvec3_t origin = { 0 };\n\tint clients, bots, i;\n\n\tif( cl->state != cs_spawned )\n\t\treturn false;\n\n\tNET_GetLocalAddress( &ip4, &ip6 );\n\tif( cl->edict )\n\t\tVectorCopy( cl->edict->v.origin, origin );\n\tSV_GetPlayerCount( &clients, &bots );\n\n\tSV_ClientPrintf( cl,\n\t\t\"hostname: %s\\n\"\n\t\t\"version: %i/%s %d\\n\",\n\t\thostname.string,\n\t\tPROTOCOL_VERSION, XASH_VERSION, Q_buildnum( ));\n\n\tif( ip4.type == NA_IP )\n\t\tSV_ClientPrintf( cl, \"tcp/ip: %s\\n\", NET_AdrToString( ip4 ));\n\tif( ip6.type == NA_IP6 )\n\t\tSV_ClientPrintf( cl, \"tcp/ipv6: %s\\n\", NET_AdrToString( ip6 ));\n\n\tSV_ClientPrintf( cl,\n\t\t\"map:\\t%s at %d x, %d y, %d z\\n\"\n\t\t\"players: %i active (%i max)\\n\"\n\t\t\"# score ping dev  playtime name\\n\",\n\t\tsv.name, (int)origin[0], (int)origin[1], (int)origin[2],\n\t\tclients, svs.maxclients );\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tconst sv_client_t *pcl = &svs.clients[i];\n\t\tint j = 0;\n\t\tint input_devices;\n\t\tconst char *s;\n\t\tchar devices[8];\n\n\t\tif( pcl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( pcl->flags, FCL_FAKECLIENT ))\n\t\t\ts = \"Bot \";\n\t\telse\n\t\t\ts = va( \"%i\", SV_CalcPing( pcl ));\n\n\t\tinput_devices = Q_atoi( Info_ValueForKey( pcl->useragent, \"d\" ));\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_MOUSE ))\n\t\t\tdevices[j++] = 'm';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_TOUCH ))\n\t\t\tdevices[j++] = 't';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_JOYSTICK ))\n\t\t\tdevices[j++] = 'j';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_VR ))\n\t\t\tdevices[j++] = 'v';\n\n\t\tif( j == 0 )\n\t\t\tQ_strncpy( devices, \"n/a\", sizeof( devices ));\n\t\telse\n\t\t\tdevices[j++] = 0;\n\n\t\tSV_ClientPrintf( cl,\n\t\t\t\"%2i %5i %4s %4s %g %s\\n\",\n\t\t\ti, (int)pcl->edict->v.frags, s, devices, host.realtime - pcl->netchan.connect_time, pcl->name );\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nSV_GetCrossEnt\n==================\n*/\nstatic edict_t *SV_GetCrossEnt( edict_t *player )\n{\n\tedict_t *ent = EDICT_NUM(1);\n\tedict_t *closest = NULL;\n\tfloat flMaxDot = 0.94;\n\tvec3_t forward;\n\tvec3_t viewPos;\n\tint i;\n\tfloat maxLen = 1000;\n\n\tAngleVectors( player->v.v_angle, forward, NULL, NULL );\n\tVectorAdd( player->v.origin, player->v.view_ofs, viewPos );\n\n\t// find bmodels by trace\n\t{\n\t\ttrace_t trace;\n\t\tvec3_t target;\n\n\t\tVectorMA( viewPos, 1000, forward, target );\n\t\ttrace = SV_Move( viewPos, vec3_origin, vec3_origin, target, 0, player, false );\n\t\tclosest = trace.ent;\n\t\tVectorSubtract( viewPos, trace.endpos, target );\n\t\tmaxLen = VectorLength(target) + 30;\n\t}\n\n\t// check untraceable entities\n\tfor ( i = 1; i < svgame.numEntities; i++, ent++ )\n\t{\n\t\tvec3_t vecLOS;\n\t\tvec3_t vecOrigin;\n\t\tfloat flDot, traceLen;\n\t\tvec3_t boxSize;\n\t\ttrace_t trace;\n\t\tvec3_t vecTrace;\n\n\t\tif( ent->free )\n\t\t\tcontinue;\n\n\t\tif( ent->v.solid == SOLID_BSP || ent->v.movetype == MOVETYPE_PUSHSTEP )\n\t\t\tcontinue; // bsp models will be found by trace later\n\n\t\t// do not touch following weapons\n\t\tif( ent->v.movetype == MOVETYPE_FOLLOW )\n\t\t\tcontinue;\n\n\t\tif( ent == player )\n\t\t\tcontinue;\n\n\t\tVectorAdd( ent->v.absmin, ent->v.absmax, vecOrigin );\n\t\tVectorScale( vecOrigin, 0.5, vecOrigin );\n\n\t\tVectorSubtract( vecOrigin, viewPos, vecLOS );\n\t\ttraceLen = VectorLength(vecLOS);\n\n\t\tif( traceLen > maxLen )\n\t\t\tcontinue;\n\n\t\tVectorCopy( ent->v.size, boxSize);\n\t\tVectorScale( boxSize, 0.5, boxSize );\n\n\t\tif ( vecLOS[0] > boxSize[0] )\n\t\t\tvecLOS[0] -= boxSize[0];\n\t\telse if ( vecLOS[0] < -boxSize[0] )\n\t\t\tvecLOS[0] += boxSize[0];\n\t\telse\n\t\t\tvecLOS[0] = 0;\n\n\t\tif ( vecLOS[1] > boxSize[1] )\n\t\t\tvecLOS[1] -= boxSize[1];\n\t\telse if ( vecLOS[1] < -boxSize[1] )\n\t\t\tvecLOS[1] += boxSize[1];\n\t\telse\n\t\t\tvecLOS[1] = 0;\n\n\t\tif ( vecLOS[2] > boxSize[2] )\n\t\t\tvecLOS[2] -= boxSize[2];\n\t\telse if ( vecLOS[2] < -boxSize[2] )\n\t\t\tvecLOS[2] += boxSize[2];\n\t\telse\n\t\t\tvecLOS[2] = 0;\n\t\tVectorNormalize( vecLOS );\n\n\t\tflDot = DotProduct (vecLOS , forward);\n\t\tif ( flDot <= flMaxDot )\n\t\t\tcontinue;\n\n\t\ttrace = SV_Move( viewPos, vec3_origin, vec3_origin, vecOrigin, 0, player, false );\n\t\tVectorSubtract( trace.endpos, viewPos, vecTrace );\n\t\tif( VectorLength( vecTrace ) + 30 < traceLen )\n\t\t\tcontinue;\n\t\tclosest = ent, flMaxDot = flDot;\n\t}\n\n\treturn closest;\n}\n\n/*\n==================\nSV_EntFindSingle\n==================\n*/\nstatic edict_t *SV_EntFindSingle( sv_client_t *cl, const char *pattern )\n{\n\tedict_t\t*ent = NULL;\n\tint\ti = 0;\n\n\tif( Q_isdigit( pattern ) )\n\t{\n\t\ti = Q_atoi( pattern );\n\n\t\tif( i >= svgame.numEntities )\n\t\t\treturn NULL;\n\t}\n\telse if( !Q_stricmp( pattern, \"!cross\" ) )\n\t{\n\t\tent = SV_GetCrossEnt( cl->edict );\n\n\t\tif( !SV_IsValidEdict( ent ) )\n\t\t\treturn NULL;\n\n\t\ti = NUM_FOR_EDICT( ent );\n\t}\n\telse if( pattern[0] == '!' ) // check for correct instance with !(num)_(serial)\n\t{\n\t\tconst char *p = pattern + 1;\n\t\ti = Q_atoi( p );\n\n\t\twhile( Q_isdigit( p )) p++;\n\n\t\tif( *p++ != '_' )\n\t\t\treturn NULL;\n\n\t\tif( i >= svgame.numEntities )\n\t\t\treturn NULL;\n\n\t\tent = EDICT_NUM( i );\n\n\t\tif( ent->serialnumber != Q_atoi( p ) )\n\t\t\treturn NULL;\n\t}\n\telse\n\t{\n\t\tfor( i = svgame.globals->maxClients + 1; i < svgame.numEntities; i++ )\n\t\t{\n\t\t\tent = EDICT_NUM( i );\n\n\t\t\tif( !SV_IsValidEdict( ent ) )\n\t\t\t\tcontinue;\n\n\t\t\tif( Q_stricmpext( pattern, STRING( ent->v.targetname ) ) )\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tent = EDICT_NUM( i );\n\n\tif( !SV_IsValidEdict( ent ) )\n\t\treturn NULL;\n\n\treturn ent;\n}\n\n/*\n===============\nSV_EntList_f\n\nPrint list of entities to client\n===============\n*/\nstatic qboolean SV_EntList_f( sv_client_t *cl )\n{\n\tvec3_t borigin;\n\tedict_t\t*ent = NULL;\n\tint\ti;\n\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\t\tif( !SV_IsValidEdict( ent ))\n\t\t\tcontinue;\n\n\t\t// filter by string\n\t\tif( Cmd_Argc() > 1 )\n\t\t{\n\t\t\tif( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tVectorAdd( ent->v.absmin, ent->v.absmax, borigin );\n\t\tVectorScale( borigin, 0.5, borigin );\n\n\t\tSV_ClientPrintf( cl, \"%5i origin: %.f %.f %.f\", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );\n\t\tSV_ClientPrintf( cl, \"%5i borigin: %.f %.f %.f\", i, borigin[0], borigin[1], borigin[2] );\n\n\t\tif( ent->v.classname )\n\t\t\tSV_ClientPrintf( cl, \", class: %s\", STRING( ent->v.classname ));\n\n\t\tif( ent->v.globalname )\n\t\t\tSV_ClientPrintf( cl, \", global: %s\", STRING( ent->v.globalname ));\n\n\t\tif( ent->v.targetname )\n\t\t\tSV_ClientPrintf( cl, \", name: %s\", STRING( ent->v.targetname ));\n\n\t\tif( ent->v.target )\n\t\t\tSV_ClientPrintf( cl, \", target: %s\", STRING( ent->v.target ));\n\n\t\tif( ent->v.model )\n\t\t\tSV_ClientPrintf( cl, \", model: %s\", STRING( ent->v.model ));\n\n\t\tSV_ClientPrintf( cl, \"\\n\" );\n\t}\n\treturn true;\n}\n\n/*\n===============\nSV_EntInfo_f\n\nPrint specified entity information to client\n===============\n*/\nstatic qboolean SV_EntInfo_f( sv_client_t *cl )\n{\n\tedict_t\t*ent = NULL;\n\tvec3_t borigin;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tSV_ClientPrintf( cl, \"Use ent_info <index|name|inst>\\n\" );\n\t\treturn false;\n\t}\n\n\tent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );\n\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn false;\n\n\tVectorAdd( ent->v.absmin, ent->v.absmax, borigin );\n\tVectorScale( borigin, 0.5, borigin );\n\n\tSV_ClientPrintf( cl, \"origin: %.f %.f %.f\\n\", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );\n\tSV_ClientPrintf( cl, \"angles: %.f %.f %.f\\n\", ent->v.angles[0], ent->v.angles[1], ent->v.angles[2] );\n\tSV_ClientPrintf( cl, \"borigin: %.f %.f %.f\\n\", borigin[0], borigin[1], borigin[2] );\n\n\tif( ent->v.classname )\n\t\tSV_ClientPrintf( cl, \"class: %s\\n\", STRING( ent->v.classname ));\n\n\tif( ent->v.globalname )\n\t\tSV_ClientPrintf( cl, \"global: %s\\n\", STRING( ent->v.globalname ));\n\n\tif( ent->v.targetname )\n\t\tSV_ClientPrintf( cl, \"name: %s\\n\", STRING( ent->v.targetname ));\n\n\tif( ent->v.target )\n\t\tSV_ClientPrintf( cl, \"target: %s\\n\", STRING( ent->v.target ));\n\n\tif( ent->v.model )\n\t\tSV_ClientPrintf( cl, \"model: %s\\n\", STRING( ent->v.model ));\n\n\tSV_ClientPrintf( cl, \"health: %.f\\n\", ent->v.health );\n\n\tif( ent->v.gravity != 1.0f )\n\t\tSV_ClientPrintf( cl, \"gravity: %.2f\\n\", ent->v.gravity );\n\n\tSV_ClientPrintf( cl, \"movetype: %d\\n\", ent->v.movetype );\n\tSV_ClientPrintf( cl, \"rendermode: %d\\n\", ent->v.rendermode );\n\tSV_ClientPrintf( cl, \"renderfx: %d\\n\", ent->v.renderfx );\n\tSV_ClientPrintf( cl, \"renderamt: %f\\n\", ent->v.renderamt );\n\tSV_ClientPrintf( cl, \"rendercolor: %f %f %f\\n\", ent->v.rendercolor[0], ent->v.rendercolor[1], ent->v.rendercolor[2] );\n\tSV_ClientPrintf( cl, \"maxspeed: %f\\n\", ent->v.maxspeed );\n\n\tif( ent->v.solid )\n\t\tSV_ClientPrintf( cl, \"solid: %d\\n\", ent->v.solid );\n\n\tSV_ClientPrintf( cl, \"flags: 0x%x\\n\", ent->v.flags );\n\tSV_ClientPrintf( cl, \"spawnflags: 0x%x\\n\", ent->v.spawnflags );\n\treturn true;\n}\n\n/*\n===============\nSV_EntFire_f\n\nPerform some actions\n===============\n*/\nstatic qboolean SV_EntFire_f( sv_client_t *cl )\n{\n\tedict_t\t*ent = NULL;\n\tint\ti = 1, count = 0;\n\tqboolean single; // true if user specified something that match single entity\n\n\tif( Cmd_Argc() < 3 )\n\t{\n\t\tSV_ClientPrintf( cl, \"Use ent_fire <index||pattern> <command> [<values>]\\n\"\n\t\t\t\"Use ent_fire 0 help to get command list\\n\" );\n\t\treturn false;\n\t}\n\n\tif( ( single = Q_isdigit( Cmd_Argv( 1 ) ) ) )\n\t{\n\t\ti = Q_atoi( Cmd_Argv( 1 ) );\n\n\t\tif( i < 0 || i >= svgame.numEntities )\n\t\t\treturn false;\n\n\t\tent = EDICT_NUM( i );\n\t}\n\telse if( ( single = !Q_stricmp( Cmd_Argv( 1 ), \"!cross\" ) ) )\n\t{\n\t\tent = SV_GetCrossEnt( cl->edict );\n\n\t\tif (!SV_IsValidEdict(ent))\n\t\t\treturn false;\n\n\t\ti = NUM_FOR_EDICT( ent );\n\t}\n\telse if( ( single = ( Cmd_Argv( 1 )[0] == '!') ) ) // check for correct instance with !(num)_(serial)\n\t{\n\t\tconst char *cmd = Cmd_Argv( 1 ) + 1;\n\t\ti = Q_atoi( cmd );\n\n\t\twhile( Q_isdigit( cmd )) cmd++;\n\n\t\tif( *cmd++ != '_' )\n\t\t\treturn false;\n\n\t\tif( i < 0 || i >= svgame.numEntities )\n\t\t\treturn false;\n\n\t\tent = EDICT_NUM( i );\n\t\tif( ent->serialnumber != Q_atoi( cmd ) )\n\t\t\treturn false;\n\t}\n\telse\n\t{\n\t\ti = svgame.globals->maxClients + 1;\n\t}\n\n\tfor( ; ( i <  svgame.numEntities ) && ( count < sv_enttools_maxfire.value ); i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\t\tif( !SV_IsValidEdict( ent ))\n\t\t{\n\t\t\t// SV_ClientPrintf( cl, PRINT_LOW, \"Got invalid entity\\n\" );\n\t\t\tif( single )\n\t\t\t\tbreak;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// if user specified not a number, try find such entity\n\t\tif( !single )\n\t\t{\n\t\t\tif( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tSV_ClientPrintf( cl, \"entity %i\\n\", i );\n\n\t\tcount++;\n\n\t\tif( !Q_stricmp( Cmd_Argv( 2 ), \"health\" ) )\n\t\t\tent->v.health = Q_atoi( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"gravity\" ) )\n\t\t\tent->v.gravity = Q_atof( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"movetype\" ) )\n\t\t\tent->v.movetype = Q_atoi( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"solid\" ) )\n\t\t\tent->v.solid = Q_atoi( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"rename\" ) )\n\t\t\tent->v.targetname = ALLOC_STRING( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"settarget\" ) )\n\t\t\tent->v.target = ALLOC_STRING( Cmd_Argv ( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"setmodel\" ) )\n\t\t\tSV_SetModel( ent, Cmd_Argv( 3 ) );\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"set\" ) )\n\t\t{\n\t\t\tstring keyname;\n\t\t\tstring value;\n\t\t\tKeyValueData\tpkvd;\n\t\t\tif( Cmd_Argc() != 5 )\n\t\t\t\treturn false;\n\n\t\t\tpkvd.szClassName = (char*)STRING( ent->v.classname );\n\t\t\tQ_strncpy( keyname, Cmd_Argv( 3 ), sizeof( keyname ));\n\t\t\tQ_strncpy( value, Cmd_Argv( 4 ), sizeof( value ));\n\t\t\tpkvd.szKeyName = keyname;\n\t\t\tpkvd.szValue = value;\n\t\t\tpkvd.fHandled = false;\n\t\t\tsvgame.dllFuncs.pfnKeyValue( ent, &pkvd );\n\n\t\t\tif( pkvd.fHandled )\n\t\t\t\tSV_ClientPrintf( cl, \"value set successfully!\\n\" );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"touch\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() == 4 )\n\t\t\t{\n\t\t\t\tedict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );\n\t\t\t\tif( other && other->pvPrivateData )\n\t\t\t\t\tsvgame.dllFuncs.pfnTouch( ent, other  );\n\t\t\t}\n\t\t\telse\n\t\t\t\tsvgame.dllFuncs.pfnTouch( ent, cl->edict );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"use\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() == 4 )\n\t\t\t{\n\t\t\t\tedict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );\n\t\t\t\tif( other && other->pvPrivateData )\n\t\t\t\t\tsvgame.dllFuncs.pfnUse( ent, other );\n\t\t\t}\n\t\t\telse\n\t\t\t\tsvgame.dllFuncs.pfnUse( ent, cl->edict );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"movehere\" ) )\n\t\t{\n\t\t\tent->v.origin[2] = cl->edict->v.origin[2] + 25;\n\t\t\tent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );\n\t\t\tent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );\n\t\t\tSV_LinkEdict( ent, true );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"drop2floor\" ) )\n\t\t{\n\t\t\tpfnDropToFloor( ent );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"moveup\" ) )\n\t\t{\n\t\t\tfloat dist = 25;\n\t\t\tif( Cmd_Argc() >= 4 )\n\t\t\t\tdist = Q_atof( Cmd_Argv( 3 ) );\n\t\t\tent->v.origin[2] +=  dist;\n\t\t\tif( Cmd_Argc() >= 5 )\n\t\t\t{\n\t\t\t\tdist = Q_atof( Cmd_Argv( 4 ) );\n\t\t\t\tent->v.origin[0] += dist * cos( DEG2RAD( cl->edict->v.angles[1] ) );\n\t\t\t\tent->v.origin[1] += dist * sin( DEG2RAD( cl->edict->v.angles[1] ) );\n\t\t\t}\n\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"becomeowner\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() == 4 )\n\t\t\t\tent->v.owner = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );\n\t\t\telse\n\t\t\t\tent->v.owner = cl->edict;\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"becomeenemy\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() == 4 )\n\t\t\t\tent->v.enemy = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );\n\t\t\telse\n\t\t\t\tent->v.enemy = cl->edict;\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"becomeaiment\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() == 4 )\n\t\t\t\tent->v.aiment= SV_EntFindSingle( cl, Cmd_Argv( 3 ) );\n\t\t\telse\n\t\t\t\tent->v.aiment = cl->edict;\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"hullmin\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() != 6 )\n\t\t\t\treturn false;\n\t\t\tent->v.mins[0] = Q_atof( Cmd_Argv( 3 ) );\n\t\t\tent->v.mins[1] = Q_atof( Cmd_Argv( 4 ) );\n\t\t\tent->v.mins[2] = Q_atof( Cmd_Argv( 5 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"hullmax\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() != 6 )\n\t\t\t\treturn false;\n\t\t\tent->v.maxs[0] = Q_atof( Cmd_Argv( 3 ) );\n\t\t\tent->v.maxs[1] = Q_atof( Cmd_Argv( 4 ) );\n\t\t\tent->v.maxs[2] = Q_atof( Cmd_Argv( 5 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"rendercolor\" ) )\n\t\t{\n\t\t\tif( Cmd_Argc() != 6 )\n\t\t\t\treturn false;\n\t\t\tent->v.rendercolor[0] = Q_atof( Cmd_Argv( 3 ) );\n\t\t\tent->v.rendercolor[1] = Q_atof( Cmd_Argv( 4 ) );\n\t\t\tent->v.rendercolor[2] = Q_atof( Cmd_Argv( 5 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"renderamt\" ) )\n\t\t{\n\t\t\tent->v.renderamt = Q_atof( Cmd_Argv( 3 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"renderfx\" ) )\n\t\t{\n\t\t\tent->v.renderfx = Q_atoi( Cmd_Argv( 3 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"rendermode\" ) )\n\t\t{\n\t\t\tent->v.rendermode = Q_atoi( Cmd_Argv( 3 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"angles\" ) )\n\t\t{\n\t\t\tent->v.angles[0] = Q_atof( Cmd_Argv( 3 ) );\n\t\t\tent->v.angles[1] = Q_atof( Cmd_Argv( 4 ) );\n\t\t\tent->v.angles[2] = Q_atof( Cmd_Argv( 5 ) );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"setflag\" ) )\n\t\t{\n\t\t\tent->v.flags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );\n\t\t\tSV_ClientPrintf( cl, \"flags set to 0x%x\\n\", ent->v.flags );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"clearflag\" ) )\n\t\t{\n\t\t\tent->v.flags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );\n\t\t\tSV_ClientPrintf( cl, \"flags set to 0x%x\\n\", ent->v.flags );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"setspawnflag\" ) )\n\t\t{\n\t\t\tent->v.spawnflags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );\n\t\t\tSV_ClientPrintf( cl, \"spawnflags set to 0x%x\\n\", ent->v.spawnflags );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"clearspawnflag\" ) )\n\t\t{\n\t\t\tent->v.spawnflags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );\n\t\t\tSV_ClientPrintf( cl, \"spawnflags set to 0x%x\\n\", ent->v.flags );\n\t\t}\n\t\telse if( !Q_stricmp( Cmd_Argv( 2 ), \"help\" ) )\n\t\t{\n\t\t\tSV_ClientPrintf( cl, \"Available commands:\\n\"\n\t\t\t\t\"Set fields:\\n\"\n\t\t\t\t\"        (Only set entity field, does not call any functions)\\n\"\n\t\t\t\t\"    health\\n\"\n\t\t\t\t\"    gravity\\n\"\n\t\t\t\t\"    movetype\\n\"\n\t\t\t\t\"    solid\\n\"\n\t\t\t\t\"    rendermode\\n\"\n\t\t\t\t\"    rendercolor (vector)\\n\"\n\t\t\t\t\"    renderfx\\n\"\n\t\t\t\t\"    renderamt\\n\"\n\t\t\t\t\"    hullmin (vector)\\n\"\n\t\t\t\t\"    hullmax (vector)\\n\"\n\t\t\t\t\"Actions\\n\"\n\t\t\t\t\"    rename: set entity targetname\\n\"\n\t\t\t\t\"    settarget: set entity target (only targetnames)\\n\"\n\t\t\t\t\"    setmodel: set entity model\\n\"\n\t\t\t\t\"    set: set <key> <value> by server library\\n\"\n\t\t\t\t\"        See game FGD to get list.\\n\"\n\t\t\t\t\"        command takes two arguments\\n\"\n\t\t\t\t\"    touch: touch entity by current player.\\n\"\n\t\t\t\t\"    use: use entity by current player.\\n\"\n\t\t\t\t\"    movehere: place entity in player fov.\\n\"\n\t\t\t\t\"    drop2floor: place entity to nearest floor surface\\n\"\n\t\t\t\t\"    moveup: move entity to 25 units up\\n\"\n\t\t\t\t\"Flags:\\n\"\n\t\t\t\t\"        (Set/clear specified flag bit, arg is bit number)\\n\"\n\t\t\t\t\"    setflag\\n\"\n\t\t\t\t\"    clearflag\\n\"\n\t\t\t\t\"    setspawnflag\\n\"\n\t\t\t\t\"    clearspawnflag\\n\"\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSV_ClientPrintf( cl, \"Unknown command %s!\\nUse \\\"ent_fire 0 help\\\" to list commands.\\n\", Cmd_Argv( 2 ) );\n\t\t\treturn false;\n\t\t}\n\t\tif( single )\n\t\t\tbreak;\n\t}\n\treturn true;\n}\n\n/*\n===============\nSV_EntSendVars\n===============\n*/\nstatic void SV_EntSendVars( sv_client_t *cl, edict_t *ent )\n{\n\tif( !ent )\n\t\treturn;\n\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteStringf( &cl->netchan.message, \"set ent_last_name \\\"%s\\\"\\n\", STRING( ent->v.targetname ));\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteStringf( &cl->netchan.message, \"set ent_last_num %i\\n\", NUM_FOR_EDICT( ent ));\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteStringf( &cl->netchan.message, \"set ent_last_inst !%i_%i\\n\", NUM_FOR_EDICT( ent ), ent->serialnumber );\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteStringf( &cl->netchan.message, \"set ent_last_origin \\\"%f %f %f\\\"\\n\", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteStringf( &cl->netchan.message, \"set ent_last_class \\\"%s\\\"\\n\", STRING( ent->v.classname ));\n\tMSG_WriteByte( &cl->netchan.message, svc_stufftext );\n\tMSG_WriteString( &cl->netchan.message, \"ent_getvars_cb\\n\" ); // why do we need this?\n}\n\n/*\n===============\nSV_EntCreate_f\n\nCreate new entity with specified name.\n===============\n*/\nstatic qboolean SV_EntCreate_f( sv_client_t *cl )\n{\n\tedict_t\t*ent = NULL;\n\tint\ti = 0;\n\tstring_t classname;\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tSV_ClientPrintf( cl, \"Use ent_create <classname> <key1> <value1> <key2> <value2> ...\\n\" );\n\t\treturn false;\n\t}\n\n\tclassname = ALLOC_STRING( Cmd_Argv( 1 ) );\n\n\tent = SV_CreateNamedEntity( 0, classname );\n\n\t// Xash3D extension\n\tif( !ent && svgame.physFuncs.SV_CreateEntity )\n\t{\n\t\tent = SV_AllocEdict();\n\t\tent->v.classname = classname;\n\t\tif( svgame.physFuncs.SV_CreateEntity( ent, (char*)STRING( classname ) ) == -1 )\n\t\t{\n\t\t\tif( ent && !ent->free )\n\t\t\t\tSV_FreeEdict( ent );\n\t\t\tent = NULL;\n\t\t}\n\t}\n\n\t// XashXT does not implement SV_CreateEntity, use saverestore export\n\tif( !ent && svgame.physFuncs.pfnCreateEntitiesInRestoreList )\n\t{\n\t\tENTITYTABLE table = {\n\t\t\t.classname = classname,\n\t\t\t.id = -1,\n\t\t\t.size = 1,\n\t\t\t.flags = 1337,\n\t\t};\n\n\t\tSAVERESTOREDATA data = {\n\t\t\t.tableCount = 1,\n\t\t\t.pTable = &table\n\t\t};\n\n\t\tsvgame.physFuncs.pfnCreateEntitiesInRestoreList( &data, table.flags, false );\n\n\t\tent = table.pent;\n\t}\n\n\tif( !ent )\n\t{\n\t\tSV_ClientPrintf( cl, \"Invalid entity!\\n\" );\n\t\treturn false;\n\t}\n\n\t// choose default origin\n\tent->v.origin[2] = cl->edict->v.origin[2] + 25;\n\tent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );\n\tent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );\n\n\tSV_LinkEdict( ent, false );\n\n\t// apply keyvalues if supported\n\tif( svgame.dllFuncs.pfnKeyValue )\n\t{\n\t\tfor( i = 2; i < Cmd_Argc() - 1; i++ )\n\t\t{\n\t\t\tstring keyname;\n\t\t\tstring value;\n\t\t\tKeyValueData pkvd;\n\n\t\t\t// allow split keyvalues to prespawn and postspawn\n\t\t\tif( !Q_strcmp( Cmd_Argv( i ), \"|\" ) )\n\t\t\t\tbreak;\n\n\t\t\tQ_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));\n\t\t\tQ_strncpy( value, Cmd_Argv( i ), sizeof( value ));\n\t\t\tpkvd.fHandled = false;\n\t\t\tpkvd.szClassName = (char*)STRING( ent->v.classname );\n\t\t\tpkvd.szKeyName = keyname;\n\t\t\tpkvd.szValue = value;\n\t\t\tsvgame.dllFuncs.pfnKeyValue( ent, &pkvd );\n\n\t\t\tif( pkvd.fHandled )\n\t\t\t\tSV_ClientPrintf( cl, \"value \\\"%s\\\" set to \\\"%s\\\"!\\n\", pkvd.szKeyName, pkvd.szValue );\n\t\t}\n\t}\n\n\t// set default targetname\n\tif( !ent->v.targetname )\n\t{\n\t\tstring newname, clientname;\n\t\tint j;\n\n\t\tfor( j = 0; j < sizeof( cl->name ); j++ )\n\t\t{\n\t\t\tchar c = Q_tolower( cl->name[j] );\n\t\t\tif( c < 'a' || c > 'z' )\n\t\t\t\tc = '_';\n\t\t\tif( !cl->name[j] )\n\t\t\t{\n\t\t\t\tclientname[j] = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tclientname[j] = c;\n\t\t}\n\n\t\t// generate name based on nick name and index\n\t\tQ_snprintf( newname, sizeof( newname ), \"%s_%i_e%i\", clientname, cl->userid, NUM_FOR_EDICT( ent ));\n\n\t\t// i know, it may break strict aliasing rules\n\t\t// but we will not lose anything in this case.\n\t\tQ_strnlwr( newname, newname, sizeof( newname ));\n\t\tent->v.targetname = ALLOC_STRING( newname );\n\t\tSV_EntSendVars( cl, ent );\n\t}\n\n\tSV_ClientPrintf( cl, \"Created %i: %s, targetname %s\\n\", NUM_FOR_EDICT( ent ), Cmd_Argv( 1 ), STRING( ent->v.targetname ) );\n\n\tif( svgame.dllFuncs.pfnSpawn )\n\t\tsvgame.dllFuncs.pfnSpawn( ent );\n\n\t// now drop entity to floor.\n\tpfnDropToFloor( ent );\n\n\t// force think. Otherwise given weapon may crash server if player touch it before.\n\tsvgame.dllFuncs.pfnThink( ent );\n\tpfnDropToFloor( ent );\n\n\t// apply postspawn keyvales if supported\n\tif( svgame.dllFuncs.pfnKeyValue )\n\t{\n\t\tfor( i = i + 1; i < Cmd_Argc() - 1; i++ )\n\t\t{\n\t\t\tstring keyname;\n\t\t\tstring value;\n\t\t\tKeyValueData pkvd;\n\n\t\t\tQ_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));\n\t\t\tQ_strncpy( value, Cmd_Argv( i ), sizeof( value ));\n\t\t\tpkvd.fHandled = false;\n\t\t\tpkvd.szClassName = (char*)STRING( ent->v.classname );\n\t\t\tpkvd.szKeyName = keyname;\n\t\t\tpkvd.szValue = value;\n\t\t\tsvgame.dllFuncs.pfnKeyValue( ent, &pkvd );\n\n\t\t\tif( pkvd.fHandled )\n\t\t\t\tSV_ClientPrintf( cl, \"value \\\"%s\\\" set to \\\"%s\\\"!\\n\", pkvd.szKeyName, pkvd.szValue );\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic qboolean SV_EntGetVars_f( sv_client_t *cl )\n{\n\tedict_t *ent = NULL;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tSV_ClientPrintf( cl, \"Use ent_getvars <index|name|inst>\\n\" );\n\t\treturn false;\n\t}\n\n\tent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );\n\tif( Cmd_Argc() )\n\t{\n\t\tif( !SV_IsValidEdict( ent ))\n\t\t\treturn false;\n\t}\n\n\tSV_EntSendVars( cl, ent );\n\treturn true;\n}\n\n// keep it sorted\nstatic const ucmd_t ucmds[] =\n{\n{ \"_sv_build_info\", SV_SendBuildInfo_f },\n{ \"begin\", SV_Begin_f },\n{ \"disconnect\", SV_Disconnect_f },\n{ \"dlfile\", SV_DownloadFile_f },\n{ \"god\", SV_Godmode_f },\n{ \"info\", SV_ShowServerinfo_f },\n{ \"kill\", SV_Kill_f },\n{ \"new\", SV_New_f },\n{ \"noclip\", SV_Noclip_f },\n{ \"notarget\", SV_Notarget_f },\n{ \"pause\", SV_Pause_f },\n{ \"sendres\", SV_SendRes_f },\n{ \"setinfo\", SV_SetInfo_f },\n{ \"spawn\", SV_Spawn_f },\n{ \"status\", SV_ClientStatus_f },\n};\n\nstatic const ucmd_t enttoolscmds[] =\n{\n{ \"ent_create\", SV_EntCreate_f },\n{ \"ent_fire\", SV_EntFire_f },\n{ \"ent_getvars\", SV_EntGetVars_f },\n{ \"ent_info\", SV_EntInfo_f },\n{ \"ent_list\", SV_EntList_f },\n};\n\n/*\n==================\nSV_ExecuteUserCommand\n==================\n*/\nstatic void SV_ExecuteClientCommand( sv_client_t *cl, const char *s )\n{\n\tint i;\n\n\tCmd_TokenizeString( s );\n\n\tfor( i = 0; i < ARRAYSIZE( ucmds ); i++ )\n\t{\n\t\tif( !Q_strcmp( Cmd_Argv( 0 ), ucmds[i].name ))\n\t\t{\n\t\t\tif( !ucmds[i].func( cl ))\n\t\t\t\tCon_Printf( \"'%s' is not valid from the console\\n\", ucmds[i].name );\n\t\t\telse\n\t\t\t\tCon_Reportf( \"ucmd->%s()\\n\", ucmds[i].name );\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif( sv.state == ss_active )\n\t{\n\t\tqboolean fullupdate;\n\n\t\tif( cl->state == cs_spawned && sv_enttools_enable.value > 0.0f && !sv.background )\n\t\t{\n\t\t\tfor( i = 0; i < ARRAYSIZE( enttoolscmds ); i++ )\n\t\t\t{\n\t\t\t\tif( !Q_strcmp( Cmd_Argv( 0 ), enttoolscmds[i].name ))\n\t\t\t\t{\n\t\t\t\t\tCon_Reportf( \"enttools->%s(): %s\\n\", enttoolscmds[i].name, s );\n\t\t\t\t\tLog_Printf( \"\\\"%s<%i><%s><>\\\" performed: %s\\n\", Info_ValueForKey( cl->userinfo, \"name\" ),\n\t\t\t\t\t\tcl->userid, SV_GetClientIDString( cl ), s );\n\t\t\t\t\tenttoolscmds[i].func( cl );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfullupdate = !Q_strcmp( Cmd_Argv( 0 ), \"fullupdate\" );\n\n\t\tif( fullupdate )\n\t\t{\n\t\t\tif( sv_fullupdate_penalty_time.value && host.realtime < cl->fullupdate_next_calltime )\n\t\t\t\treturn;\n\t\t}\n\n\t\t// custom client commands\n\t\tsvgame.dllFuncs.pfnClientCommand( cl->edict );\n\n\t\tif( fullupdate )\n\t\t{\n\t\t\t// resend the ambient sounds for demo recording\n\t\t\tSV_RestartAmbientSounds();\n\t\t\t// resend all the decals for demo recording\n\t\t\tSV_RestartDecals();\n\t\t\t// resend all the static ents for demo recording\n\t\t\tSV_RestartStaticEnts();\n\t\t\t// resend the viewentity\n\t\t\tSV_UpdateClientView( cl );\n\n\t\t\tif( sv_fullupdate_penalty_time.value )\n\t\t\t\tcl->fullupdate_next_calltime = host.realtime + sv_fullupdate_penalty_time.value;\n\t\t}\n\t}\n}\n\n/*\n=================\nSV_ConnectionlessPacket\n\nA connectionless packet has four leading 0xff\ncharacters to distinguish it from a game channel.\nClients that are in the game can still send\nconnectionless packets.\n=================\n*/\nvoid SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )\n{\n\tconst char *pcmd, *args;\n\n\t// prevent flooding from banned address\n\tif( SV_CheckIP( &from ))\n\t\treturn;\n\n\tMSG_Clear( msg );\n\tMSG_SeekToBit( msg, sizeof( uint32_t ) << 3, SEEK_CUR ); // skip the -1 marker\n\n\targs = MSG_ReadStringLine( msg );\n\tCmd_TokenizeString( args );\n\n\tpcmd = Cmd_Argv( 0 );\n\n\tif( sv_log_outofband.value )\n\t\tCon_Reportf( \"%s: %s : %s\\n\", __func__, NET_AdrToString( from ), pcmd );\n\n\tif( !svs.initialized )\n\t{\n\t\t// only process rcon if server not initialized\n\t\tif( !Q_strcmp( pcmd, C2S_RCON ))\n\t\t\tSV_RemoteCommand( net_from, &net_message );\n\n\t\treturn;\n\t}\n\n\tif( NET_IsMasterAdr( from ))\n\t{\n\t\tif( !Q_strcmp( pcmd, M2S_CHALLENGE ))\n\t\t{\n\t\t\tSV_AddToMaster( from, msg );\n\t\t}\n\t\telse if( !Q_strcmp( pcmd, M2S_NAT_CONNECT ))\n\t\t{\n\t\t\tSV_ConnectNatClient( from );\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif( !Q_strcmp( pcmd, A2S_GOLDSRC_INFO ) || pcmd[0] == A2S_GOLDSRC_PLAYERS || pcmd[0] == A2S_GOLDSRC_RULES )\n\t{\n\t\tSV_SourceQuery_HandleConnnectionlessPacket( pcmd, from );\n\t}\n\telse if( !Q_strcmp( pcmd, A2A_NETINFO ))\n\t{\n\t\tSV_BuildNetAnswer( from );\n\t}\n\telse if( !Q_strcmp( pcmd, A2A_INFO ))\n\t{\n\t\tSV_Info( from, Q_atoi( Cmd_Argv( 1 )));\n\t}\n\telse if( !Q_strcmp( pcmd, C2S_BANDWIDTHTEST ))\n\t{\n\t\tSV_TestBandWidth( from );\n\t}\n\telse if( !Q_strcmp( pcmd, C2S_GETCHALLENGE ))\n\t{\n\t\tSV_SendChallenge( from );\n\t}\n\telse if( !Q_strcmp( pcmd, C2S_CONNECT ))\n\t{\n\t\tSV_ConnectClient( from );\n\t}\n\telse if( !Q_strcmp( pcmd, A2A_PING ))\n\t{\n\t\tNetchan_OutOfBandPrint( NS_SERVER, from, A2A_ACK );\n\t}\n\telse if( !Q_strcmp( pcmd, A2A_GOLDSRC_PING ))\n\t{\n\t\tNetchan_OutOfBandPrint( NS_SERVER, from, A2A_GOLDSRC_ACK );\n\t}\n\telse if( !Q_strcmp( pcmd, C2S_RCON ))\n\t{\n\t\tSV_RemoteCommand( from, msg );\n\t}\n\telse if( !Q_strcmp( pcmd, A2A_ACK ) || !Q_strcmp( pcmd, A2A_GOLDSRC_ACK ))\n\t{\n\t\tSV_Ack( from );\n\t}\n\telse\n\t{\n\t\tchar buf[MAX_SYSPATH];\n\t\tint\tlen = sizeof( buf );\n\n\t\tif( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len ))\n\t\t{\n\t\t\t// user out of band message (must be handled in CL_ConnectionlessPacket)\n\t\t\tif( len > 0 )\n\t\t\t\tNetchan_OutOfBand( NS_SERVER, from, len, (byte*)buf );\n\t\t}\n\t\telse if( sv_log_outofband.value )\n\t\t\tCon_DPrintf( S_ERROR \"bad connectionless packet from %s:\\n%s\\n\", NET_AdrToString( from ), args );\n\t}\n}\n\nstatic qboolean SV_PlayerIsFrozen( const edict_t *pClient )\n{\n\tif( sv_background_freeze.value && sv.background )\n\t\treturn true;\n\n\tif( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\treturn false;\n\n\tif( FBitSet( pClient->v.flags, FL_FROZEN ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n==================\nSV_ParseClientMove\n\nThe message usually contains all the movement commands\nthat were in the last three packets, so that the information\nin dropped packets can be recovered.\n\nOn very fast clients, there may be multiple usercmd packed into\neach of the backup packets.\n==================\n*/\nstatic void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )\n{\n\tconst usercmd_t\tnullcmd = { 0 }, *from = &nullcmd; // first cmd are starting from null-compressed usercmd_t\n\tclient_frame_t  *frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];\n\tusercmd_t       cmds[CMD_BACKUP] = { 0 }, *to;\n\tedict_t         *player = cl->edict;\n\tmodel_t         *model;\n\n\tint key = MSG_GetRealBytesRead( msg );\n\tint checksum1 = MSG_ReadByte( msg );\n\tint packet_loss = MSG_ReadByte( msg );\n\tint numbackup = MSG_ReadByte( msg );\n\tint numcmds = MSG_ReadByte( msg );\n\tint totalcmds = numcmds + numbackup;\n\tint i;\n\n\tnet_drop -= (numcmds - 1);\n\n\tif( totalcmds < 0 || totalcmds >= CMD_MASK )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s sending too many commands %i\\n\", __func__, cl->name, totalcmds );\n\t\tSV_DropClient( cl, false );\n\t\treturn;\n\t}\n\n\tfor( i = totalcmds - 1; i >= 0; i-- )\n\t{\n\t\tto = &cmds[i];\n\t\tMSG_ReadDeltaUsercmd( msg, from, to );\n\t\tfrom = to; // get new baseline\n\t}\n\n\tif( cl->state != cs_spawned )\n\t\treturn;\n\n\tif( !Host_IsLocalClient( ))\n\t{\n\t\t// if the checksum fails, ignore the rest of the packet\n\t\tint size = MSG_GetRealBytesRead( msg ) - key - 1;\n\t\tint checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence );\n\n\t\tif( checksum2 != checksum1 )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: failed command checksum for %s (%d != %d)\\n\", __func__, cl->name, checksum2, checksum1 );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tcl->packet_loss = packet_loss;\n\n\t// freeze player for some reasons if loadgame was executed\n\tif( GameState->loadGame )\n\t\treturn;\n\n\t// check for pause or frozen\n\tif( sv.paused || !CL_IsInGame() || SV_PlayerIsFrozen( player ))\n\t{\n\t\tfor( i = 0; i < numcmds; i++ )\n\t\t{\n\t\t\tcmds[i].msec = 0;\n\t\t\tcmds[i].forwardmove = 0;\n\t\t\tcmds[i].sidemove = 0;\n\t\t\tcmds[i].upmove = 0;\n\t\t\tcmds[i].buttons = 0;\n\n\t\t\tif( SV_PlayerIsFrozen( player ))\n\t\t\t\tcmds[i].impulse = 0;\n\n\t\t\tVectorCopy( cmds[i].viewangles, player->v.v_angle );\n\t\t}\n\t\tnet_drop = 0;\n\t}\n\telse\n\t{\n\t\tif( !player->v.fixangle )\n\t\t\tVectorCopy( cmds[0].viewangles, player->v.v_angle );\n\t}\n\n\tSV_EstablishTimeBase( cl, cmds, net_drop, numbackup, numcmds );\n\n\tif( net_drop < 24 )\n\t{\n\t\twhile( net_drop > numbackup )\n\t\t{\n\t\t\tSV_RunCmd( cl, &cl->lastcmd, 0 );\n\t\t\tnet_drop--;\n\t\t}\n\n\t\twhile( net_drop > 0 )\n\t\t{\n\t\t\ti = numcmds + net_drop - 1;\n\t\t\tSV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );\n\t\t\tnet_drop--;\n\t\t}\n\t}\n\n\tfor( i = numcmds - 1; i >= 0; i-- )\n\t{\n\t\tSV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );\n\t}\n\n\t// was player kicked? stop here\n\tif( cl->state <= cs_zombie )\n\t\treturn;\n\n\tcl->lastcmd = cmds[0];\n\n\t// adjust latency time by 1/2 last client frame since\n\t// the message probably arrived 1/2 through client's frame loop\n\tframe->ping_time -= ( cl->lastcmd.msec * 0.5f ) / 1000.0f;\n\tframe->ping_time = Q_max( 0.0f, frame->ping_time );\n\tmodel = SV_ModelHandle( player->v.modelindex );\n\n\tif( model && model->type == mod_studio )\n\t{\n\t\t// g-cont. yes we using svgame.globals->time instead of sv.time\n\t\tif( player->v.animtime > svgame.globals->time + sv.frametime )\n\t\t\tplayer->v.animtime = svgame.globals->time + sv.frametime;\n\t}\n}\n\n/*\n===================\nSV_ParseResourceList\n\nParse resource list\n===================\n*/\nstatic void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg )\n{\n\tint\t\ttotalsize;\n\tresource_t\t*resource;\n\tint\t\ti, total;\n\tresourceinfo_t\tri;\n\n\ttotal = MSG_ReadShort( msg );\n\n\tSV_ClearResourceList( &cl->resourcesneeded );\n\tSV_ClearResourceList( &cl->resourcesonhand );\n\n\tfor( i = 0; i < total; i++ )\n\t{\n\t\tresource = Z_Calloc( sizeof( resource_t ) );\n\t\tQ_strncpy( resource->szFileName, MSG_ReadString( msg ), sizeof( resource->szFileName ));\n\t\tresource->type = MSG_ReadByte( msg );\n\t\tresource->nIndex = MSG_ReadShort( msg );\n\t\tresource->nDownloadSize = MSG_ReadLong( msg );\n\t\tresource->ucFlags = MSG_ReadByte( msg );\n\t\tresource->pNext = NULL;\n\t\tresource->pPrev = NULL;\n\t\tClearBits( resource->ucFlags, RES_WASMISSING );\n\n\t\tif( FBitSet( resource->ucFlags, RES_CUSTOM ))\n\t\t\tMSG_ReadBytes( msg, resource->rgucMD5_hash, 16 );\n\n\t\tif( resource->type > t_world || resource->nDownloadSize > 1024 * 1024 * 1024 )\n\t\t{\n\t\t\tSV_ClearResourceList( &cl->resourcesneeded );\n\t\t\tSV_ClearResourceList( &cl->resourcesonhand );\n\t\t\treturn;\n\t\t}\n\t\tSV_AddToResourceList( resource, &cl->resourcesneeded );\n\t}\n\n\ttotalsize = COM_SizeofResourceList( &cl->resourcesneeded, &ri );\n\n\tif( totalsize != 0 && sv_allow_upload.value )\n\t{\n\t\tCon_DPrintf( \"Verifying and uploading resources...\\n\" );\n\n\t\tif( totalsize != 0 )\n\t\t{\n\t\t\tCon_DPrintf( \"Custom resources total %.2fK\\n\", totalsize / 1024.0 );\n\n\t\t\tif ( ri.info[t_model].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Models:  %.2fK\\n\", ri.info[t_model].size / 1024.0 );\n\n\t\t\tif ( ri.info[t_sound].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Sounds:  %.2fK\\n\", ri.info[t_sound].size / 1024.0 );\n\n\t\t\tif ( ri.info[t_decal].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Decals:  %.2fK\\n\", ri.info[t_decal].size / 1024.0 );\n\n\t\t\tif ( ri.info[t_skin].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Skins :  %.2fK\\n\", ri.info[t_skin].size / 1024.0 );\n\n\t\t\tif ( ri.info[t_generic].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Generic :  %.2fK\\n\", ri.info[t_generic].size / 1024.0 );\n\n\t\t\tif ( ri.info[t_eventscript].size != 0 )\n\t\t\t\tCon_DPrintf( \"  Events  :  %.2fK\\n\", ri.info[t_eventscript].size / 1024.0 );\n\n\t\t\tCon_DPrintf( \"----------------------\\n\" );\n\t\t}\n\n\t\ttotalsize = SV_EstimateNeededResources( cl );\n\n\t\tif( totalsize > sv_uploadmax.value * 1024 * 1024 )\n\t\t{\n\t\t\tSV_ClearResourceList( &cl->resourcesneeded );\n\t\t\tSV_ClearResourceList( &cl->resourcesonhand );\n\t\t\treturn;\n\t\t}\n\t\tCon_DPrintf( \"resources to request: %s\\n\", Q_memprint( totalsize ));\n\t}\n\n\tcl->upstate = us_processing;\n\tSV_BatchUploadRequest( cl );\n}\n\n/*\n===================\nSV_ParseCvarValue\n\nParse a requested value from client cvar\n===================\n*/\nstatic void SV_ParseCvarValue( sv_client_t *cl, sizebuf_t *msg )\n{\n\tconst char *value = MSG_ReadString( msg );\n\n\tif( svgame.dllFuncs2.pfnCvarValue != NULL )\n\t\tsvgame.dllFuncs2.pfnCvarValue( cl->edict, value );\n\tCon_Reportf( \"Cvar query response: name:%s, value:%s\\n\", cl->name, value );\n}\n\n/*\n===================\nSV_ParseCvarValue2\n\nParse a requested value from client cvar\n===================\n*/\nstatic void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg )\n{\n\tstring\tname, value;\n\tint\trequestID = MSG_ReadLong( msg );\n\n\tQ_strncpy( name, MSG_ReadString( msg ), sizeof( name ));\n\tQ_strncpy( value, MSG_ReadString( msg ), sizeof( value ));\n\n\tif( svgame.dllFuncs2.pfnCvarValue2 != NULL )\n\t\tsvgame.dllFuncs2.pfnCvarValue2( cl->edict, requestID, name, value );\n\tCon_Reportf( \"Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\\n\", cl->name, requestID, name, value );\n}\n\n/*\n===================\nSV_ParseVoiceData\n===================\n*/\nstatic void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg )\n{\n\tchar received[4096];\n\tint i;\n\n\tconst qboolean loopback = !!MSG_ReadByte( msg );\n\tconst uint frames = MSG_ReadByte( msg );\n\tconst uint size = MSG_ReadShort( msg );\n\tconst int client = cl - svs.clients;\n\n\tif( size > sizeof( received ))\n\t{\n\t\tCon_DPrintf( \"%s: invalid incoming packet.\\n\", __func__ );\n\t\tSV_DropClient( cl, false );\n\t\treturn;\n\t}\n\n\tMSG_ReadBytes( msg, received, size );\n\n\tif( !sv_voiceenable.value || svs.maxclients <= 1 || cl->state != cs_spawned )\n\t\treturn;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tsv_client_t *cur = &svs.clients[i];\n\t\tconst qboolean local = cl == cur;\n\t\tuint length = size;\n\n\t\tif( !local )\n\t\t{\n\t\t\tif( cur->state < cs_connected )\n\t\t\t\tcontinue;\n\n\t\t\tif( !FBitSet( cl->listeners, BIT( i )))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// 6 is a number of bytes for other parts of message\n\t\tif( MSG_GetNumBytesLeft( &cur->datagram ) < length + 6 )\n\t\t\tcontinue;\n\n\t\tif( cl == cur && !loopback )\n\t\t\tlength = 0;\n\n\t\tMSG_BeginServerCmd( &cur->datagram, svc_voicedata );\n\t\tMSG_WriteByte( &cur->datagram, client );\n\t\tMSG_WriteByte( &cur->datagram, frames );\n\t\tMSG_WriteShort( &cur->datagram, length );\n\t\tMSG_WriteBytes( &cur->datagram, received, length );\n\t}\n}\n\n/*\n===================\nSV_ExecuteClientMessage\n\nParse a client packet\n===================\n*/\nvoid SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )\n{\n\tqboolean\t\tmove_issued = false;\n\tclient_frame_t\t*frame;\n\tint\t\tc;\n\n\tASSERT( cl->frames != NULL );\n\n\t// calc ping time\n\tframe = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];\n\n\t// ping time doesn't factor in message interval, either\n\tframe->ping_time = host.realtime - frame->senttime - cl->cl_updaterate;\n\n\t// on first frame ( no senttime ) don't skew ping\n\tif( frame->senttime == 0.0f ) frame->ping_time = 0.0f;\n\n\t// don't skew ping based on signon stuff either\n\tif(( host.realtime - cl->connection_started ) < 2.0f && ( frame->ping_time > 0.0f ))\n\t\tframe->ping_time = 0.0f;\n\n\tcl->latency = SV_CalcClientTime( cl );\n\tcl->delta_sequence = -1; // no delta unless requested\n\n\t// read optional clientCommand strings\n\twhile( cl->state != cs_zombie )\n\t{\n\t\tif( MSG_CheckOverflow( msg ))\n\t\t{\n\t\t\tCon_DPrintf( S_ERROR \"incoming overflow for %s\\n\", cl->name );\n\t\t\tSV_DropClient( cl, false );\n\t\t\treturn;\n\t\t}\n\n\t\t// end of message\n\t\tif( MSG_GetNumBitsLeft( msg ) < 8 )\n\t\t\tbreak;\n\n\t\tc = MSG_ReadClientCmd( msg );\n\n\t\tswitch( c )\n\t\t{\n\t\tcase clc_nop:\n\t\t\tbreak;\n\t\tcase clc_delta:\n\t\t\tcl->delta_sequence = MSG_ReadByte( msg );\n\t\t\tbreak;\n\t\tcase clc_move:\n\t\t\tif( move_issued ) return; // someone is trying to cheat...\n\t\t\tmove_issued = true;\n\t\t\tSV_ParseClientMove( cl, msg );\n\t\t\tbreak;\n\t\tcase clc_stringcmd:\n\t\t\tSV_ExecuteClientCommand( cl, MSG_ReadString( msg ));\n\t\t\tif( cl->state == cs_zombie )\n\t\t\t\treturn; // disconnect command\n\t\t\tbreak;\n\t\tcase clc_resourcelist:\n\t\t\tSV_ParseResourceList( cl, msg );\n\t\t\tbreak;\n\t\tcase clc_fileconsistency:\n\t\t\tSV_ParseConsistencyResponse( cl, msg );\n\t\t\tbreak;\n\t\tcase clc_voicedata:\n\t\t\tSV_ParseVoiceData( cl, msg );\n\t\t\tbreak;\n\t\tcase clc_requestcvarvalue:\n\t\t\tSV_ParseCvarValue( cl, msg );\n\t\t\tbreak;\n\t\tcase clc_requestcvarvalue2:\n\t\t\tSV_ParseCvarValue2( cl, msg );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tCon_DPrintf( S_ERROR \"%s: clc_bad\\n\", cl->name );\n\t\t\tSV_DropClient( cl, false );\n\t\t\treturn;\n\t\t}\n\t}\n }\n"
  },
  {
    "path": "engine/server/sv_cmds.c",
    "content": "/*\nsv_cmds.c - server console commands\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n\n/*\n=================\nSV_ClientPrintf\n\nSends text across to be displayed if the level passes\n=================\n*/\nvoid SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... )\n{\n\tchar\tstring[MAX_SYSPATH];\n\tva_list\targptr;\n\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( string, sizeof( string ), fmt, argptr );\n\tva_end( argptr );\n\n\tMSG_BeginServerCmd( &cl->netchan.message, svc_print );\n\tMSG_WriteString( &cl->netchan.message, string );\n}\n\n/*\n=================\nSV_BroadcastPrintf\n\nSends text to all active clients\n=================\n*/\nvoid SV_BroadcastPrintf( sv_client_t *ignore, const char *fmt, ... )\n{\n\tchar\t\tstring[MAX_SYSPATH];\n\tva_list\t\targptr;\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( string, sizeof( string ), fmt, argptr );\n\tva_end( argptr );\n\n\tif( sv.state == ss_active )\n\t{\n\t\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t\t{\n\t\t\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\t\tcontinue;\n\n\t\t\tif( cl == ignore || cl->state != cs_spawned )\n\t\t\t\tcontinue;\n\n\t\t\tMSG_BeginServerCmd( &cl->netchan.message, svc_print );\n\t\t\tMSG_WriteString( &cl->netchan.message, string );\n\t\t}\n\t}\n\n\tif( Host_IsDedicated() )\n\t{\n\t\t// echo to console\n\t\tCon_DPrintf( \"%s\", string );\n\t}\n}\n\n/*\n=================\nSV_BroadcastCommand\n\nSends text to all active clients\n=================\n*/\nvoid SV_BroadcastCommand( const char *fmt, ... )\n{\n\tchar\tstring[MAX_SYSPATH];\n\tva_list\targptr;\n\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( string, sizeof( string ), fmt, argptr );\n\tva_end( argptr );\n\n\tMSG_BeginServerCmd( &sv.reliable_datagram, svc_stufftext );\n\tMSG_WriteString( &sv.reliable_datagram, string );\n}\n\n/*\n==================\nSV_SetPlayer\n\nSets sv_client and sv_player to the player with idnum Cmd_Argv(1)\n==================\n*/\nstatic sv_client_t *SV_SetPlayer( void )\n{\n\tconst char\t*s;\n\tsv_client_t\t*cl;\n\tint\t\ti, idnum;\n\n\tif( !svs.clients || sv.background )\n\t\treturn NULL;\n\n\tif( svs.maxclients == 1 || Cmd_Argc() < 2 )\n\t{\n\t\t// special case for local client\n\t\treturn svs.clients;\n\t}\n\n\ts = Cmd_Argv( 1 );\n\n\t// numeric values are just slot numbers\n\tif( Q_isdigit( s ) || (s[0] == '-' && Q_isdigit( s + 1 )))\n\t{\n\t\tidnum = Q_atoi( s );\n\n\t\tif( idnum < 0 || idnum >= svs.maxclients )\n\t\t{\n\t\t\tCon_Printf( \"Bad client slot: %i\\n\", idnum );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tcl = &svs.clients[idnum];\n\n\t\tif( !cl->state )\n\t\t{\n\t\t\tCon_Printf( \"Client %i is not active\\n\", idnum );\n\t\t\treturn NULL;\n\t\t}\n\t\treturn cl;\n\t}\n\n\t// check for a name match\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( !cl->state ) continue;\n\n\t\tif( !Q_strcmp( cl->name, s ))\n\t\t\treturn cl;\n\t}\n\n\tCon_Printf( \"Userid %s is not on the server\\n\", s );\n\treturn NULL;\n}\n\n/*\n==================\nSV_ValidateMap\n\ncheck map for typically errors\n==================\n*/\nstatic qboolean SV_ValidateMap( const char *pMapName )\n{\n\tint\tflags;\n\n\tflags = SV_MapIsValid( pMapName, NULL );\n\n\tif( FBitSet( flags, MAP_INVALID_VERSION ))\n\t{\n\t\tCon_Printf( S_ERROR \"map %s is invalid or not supported\\n\", pMapName );\n\t\treturn false;\n\t}\n\n\tif( !FBitSet( flags, MAP_IS_EXIST ))\n\t{\n\t\tCon_Printf( S_ERROR \"map %s doesn't exist\\n\", pMapName );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nSV_Map_f\n\nGoes directly to a given map without any savegame archiving.\nFor development work\n==================\n*/\nstatic void SV_Map_f( void )\n{\n\tchar\tmapname[MAX_QPATH];\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"map <mapname>\\n\" );\n\t\treturn;\n\t}\n\n\t// hold mapname to other place\n\tQ_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname ));\n\tCOM_StripExtension( mapname );\n\n\tif( !SV_ValidateMap( mapname ))\n\t\treturn;\n\n\tCvar_DirectSet( &sv_hostmap, mapname );\n\tCOM_LoadLevel( mapname, false );\n}\n\n/*\n==================\nSV_Maps_f\n\nLists maps according to given substring.\n\nTODO: Make it more convenient. (Timestamp check, temporary file, ...)\n==================\n*/\nstatic void SV_Maps_f( void )\n{\n\tconst char *separator = \"-------------------\";\n\tconst char *argStr = Cmd_Argv( 1 ); // Substr\n\tint nummaps;\n\tsearch_t *mapList;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tMsg( S_USAGE \"maps <substring>\\nmaps * for full listing\\n\" );\n\t\treturn;\n\t}\n\n\tmapList = FS_Search( va( \"maps/*%s*.bsp\", argStr ), true, true );\n\n\tif( !mapList )\n\t{\n\t\tMsg( \"No related map found in \\\"%s/maps\\\"\\n\", GI->gamefolder );\n\t\treturn;\n\t}\n\n\tnummaps = Cmd_ListMaps( mapList, NULL, 0 );\n\n\tMem_Free( mapList );\n\n\tMsg( \"%s\\nDirectory: \\\"%s/maps\\\" - Maps listed: %d\\n\", separator, GI->gamefolder, nummaps );\n}\n\n/*\n==================\nSV_MapBackground_f\n\nSet background map (enable physics in menu)\n==================\n*/\nstatic void SV_MapBackground_f( void )\n{\n\tchar\tmapname[MAX_QPATH];\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"map_background <mapname>\\n\" );\n\t\treturn;\n\t}\n\n\tif( SV_Active() && !sv.background )\n\t{\n\t\tif( GameState->nextstate == STATE_RUNFRAME )\n\t\t\tCon_Printf( S_ERROR \"can't set background map while game is active\\n\" );\n\t\treturn;\n\t}\n\n\t// hold mapname to other place\n\tQ_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname ));\n\tCOM_StripExtension( mapname );\n\n\tif( !SV_ValidateMap( mapname ))\n\t\treturn;\n\n\t// background map is always run as singleplayer\n\tCvar_FullSet( \"maxplayers\", \"1\", FCVAR_LATCH );\n\tCvar_FullSet( \"deathmatch\", \"0\", FCVAR_LATCH|FCVAR_SERVER );\n\tCvar_FullSet( \"coop\", \"0\", FCVAR_LATCH|FCVAR_SERVER );\n\n\tCOM_LoadLevel( mapname, true );\n}\n\n/*\n==================\nSV_NextMap_f\n\nChange map for next in alpha-bethical ordering\nFor development work\n==================\n*/\nstatic void SV_NextMap_f( void )\n{\n\tchar\tnextmap[MAX_QPATH];\n\tint\ti, next;\n\tsearch_t\t*t;\n\n\tt = FS_Search( \"maps\\\\*.bsp\", true, con_gamemaps.value ); // only in gamedir\n\tif( !t ) t = FS_Search( \"maps/*.bsp\", true, con_gamemaps.value ); // only in gamedir\n\n\tif( !t )\n\t{\n\t\tCon_Printf( \"next map can't be found\\n\" );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t{\n\t\tconst char *ext = COM_FileExtension( t->filenames[i] );\n\n\t\tif( Q_stricmp( ext, \"bsp\" ))\n\t\t\tcontinue;\n\n\t\tCOM_FileBase( t->filenames[i], nextmap, sizeof( nextmap ));\n\t\tif( Q_stricmp( sv_hostmap.string, nextmap ))\n\t\t\tcontinue;\n\n\t\tnext = ( i + 1 ) % t->numfilenames;\n\t\tCOM_FileBase( t->filenames[next], nextmap, sizeof( nextmap ));\n\t\tCvar_DirectSet( &sv_hostmap, nextmap );\n\n\t\t// found current point, check for valid\n\t\tif( SV_ValidateMap( nextmap ))\n\t\t{\n\t\t\t// found and valid\n\t\t\tCOM_LoadLevel( nextmap, false );\n\t\t\tMem_Free( t );\n\t\t\treturn;\n\t\t}\n\t\t// jump to next map\n\t}\n\n\tCon_Printf( \"failed to load next map\\n\" );\n\tMem_Free( t );\n}\n\n/*\n==============\nSV_NewGame_f\n\n==============\n*/\nstatic void SV_NewGame_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t\tCOM_NewGame( GI->startmap );\n\telse if( Cmd_Argc() == 2 )\n\t\tCOM_NewGame( Cmd_Argv( 1 ));\n\telse\n\t\tCon_Printf( S_USAGE \"newgame\\n\" );\n}\n\n/*\n==============\nSV_HazardCourse_f\n\n==============\n*/\nstatic void SV_HazardCourse_f( void )\n{\n\tif( Cmd_Argc() != 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"hazardcourse\\n\" );\n\t\treturn;\n\t}\n\n\t// special case for Gunman Chronicles: playing avi-file\n\tif( FS_FileExists( va( \"media/%s.avi\", GI->trainmap ), false ))\n\t{\n\t\tCbuf_AddTextf( \"wait; movie %s\\n\", GI->trainmap );\n\t\tHost_EndGame( true, DEFAULT_ENDGAME_MESSAGE );\n\t}\n\telse COM_NewGame( GI->trainmap );\n}\n\n/*\n==============\nSV_Load_f\n\n==============\n*/\nstatic void SV_Load_f( void )\n{\n\tchar\tpath[MAX_QPATH];\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"load <savename>\\n\" );\n\t\treturn;\n\t}\n\n\tQ_snprintf( path, sizeof( path ), DEFAULT_SAVE_DIRECTORY \"%s.sav\", Cmd_Argv( 1 ));\n\tSV_LoadGame( path );\n}\n\n/*\n==============\nSV_QuickLoad_f\n\n==============\n*/\nstatic void SV_QuickLoad_f( void )\n{\n\tCbuf_AddText( \"echo Quick Loading...; wait; load quick\" );\n}\n\n/*\n==============\nSV_Save_f\n\n==============\n*/\nstatic void SV_Save_f( void )\n{\n\tqboolean ret = false;\n\n\tswitch( Cmd_Argc( ))\n\t{\n\tcase 1:\n\t\tret = SV_SaveGame( \"new\" );\n\t\tbreak;\n\tcase 2:\n\t\tret = SV_SaveGame( Cmd_Argv( 1 ));\n\t\tbreak;\n\tdefault:\n\t\tCon_Printf( S_USAGE \"save <savename>\\n\" );\n\t\tbreak;\n\t}\n\n\tif( ret && CL_Active() && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\tCL_HudMessage( \"GAMESAVED\" ); // defined in titles.txt\n}\n\n/*\n==============\nSV_QuickSave_f\n\n==============\n*/\nstatic void SV_QuickSave_f( void )\n{\n\tCbuf_AddText( \"echo Quick Saving...; wait; save quick\" );\n}\n\n/*\n==============\nSV_DeleteSave_f\n\n==============\n*/\nstatic void SV_DeleteSave_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"killsave <name>\\n\" );\n\t\treturn;\n\t}\n\n\t// delete save and saveshot\n\tFS_Delete( va( DEFAULT_SAVE_DIRECTORY \"%s.sav\", Cmd_Argv( 1 )));\n\tFS_Delete( va( DEFAULT_SAVE_DIRECTORY \"%s.bmp\", Cmd_Argv( 1 )));\n}\n\n/*\n==============\nSV_AutoSave_f\n\n==============\n*/\nstatic void SV_AutoSave_f( void )\n{\n\tif( Cmd_Argc() != 1 )\n\t{\n\t\tCon_Printf( S_USAGE \"autosave\\n\" );\n\t\treturn;\n\t}\n\n\tif( sv_autosave.value )\n\t\tSV_SaveGame( \"autosave\" );\n}\n\n/*\n==================\nSV_Restart_f\n\nrestarts current level\n==================\n*/\nstatic void SV_Restart_f( void )\n{\n\t// because restart can be multiple issued\n\tif( sv.state != ss_active )\n\t\treturn;\n\tCOM_LoadLevel( sv.name, sv.background );\n}\n\n/*\n==================\nSV_Reload_f\n\ncontinue from latest savedgame\n==================\n*/\nstatic void SV_Reload_f( void )\n{\n\t// because reload can be multiple issued\n\tif( GameState->nextstate != STATE_RUNFRAME )\n\t\treturn;\n\n\tif( !SV_LoadGame( SV_GetLatestSave( )))\n\t\tCOM_LoadLevel( sv_hostmap.string, false );\n}\n\n/*\n==================\nSV_ChangeLevel_f\n\nclassic change level\n==================\n*/\nstatic void SV_ChangeLevel_f( void )\n{\n\tif( Cmd_Argc() < 2 ) // allow extra arguments, for compatibility\n\t{\n\t\tCon_Printf( S_USAGE \"changelevel <mapname>\\n\" );\n\t\treturn;\n\t}\n\n\tSV_QueueChangeLevel( Cmd_Argv( 1 ), NULL );\n}\n\n/*\n==================\nSV_ChangeLevel2_f\n\nsmooth change level\n==================\n*/\nstatic void SV_ChangeLevel2_f( void )\n{\n\tif( Cmd_Argc() < 2 ) // allow extra arguments, for compatibility\n\t{\n\t\tCon_Printf( S_USAGE \"changelevel2 <mapname> [landmark]\\n\" );\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() == 2 ) // with single argument, behaves like usual changelevel\n\t\tSV_QueueChangeLevel( Cmd_Argv( 1 ), NULL );\n\telse SV_QueueChangeLevel( Cmd_Argv( 1 ), Cmd_Argv( 2 ));\n}\n\n/*\n==================\nSV_Kick_f\n\nKick a user off of the server\n==================\n*/\nstatic void SV_Kick_f( void )\n{\n\tsv_client_t\t*cl;\n\tconst char *param;\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"kick <#id|name> [reason]\\n\" );\n\t\treturn;\n\t}\n\n\tparam = Cmd_Argv( 1 );\n\n\tif( *param == '#' && Q_isdigit( param + 1 ) )\n\t\tcl = SV_ClientById( Q_atoi( param + 1 ) );\n\telse cl = SV_ClientByName( param );\n\n\tif( !cl )\n\t{\n\t\tCon_Printf( \"Client is not on the server\\n\" );\n\t\treturn;\n\t}\n\n\tSV_KickPlayer( cl, \"%s\", Cmd_Argv( 2 ));\n}\n\n/*\n==================\nSV_EntPatch_f\n==================\n*/\nstatic void SV_EntPatch_f( void )\n{\n\tconst char\t*mapname;\n\n\tif( Cmd_Argc() < 2 )\n\t{\n\t\tif( sv.state != ss_dead )\n\t\t{\n\t\t\tmapname = sv.name;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_USAGE \"entpatch <mapname>\\n\" );\n\t\t\treturn;\n\t\t}\n\t}\n\telse mapname = Cmd_Argv( 1 );\n\n\tSV_WriteEntityPatch( mapname );\n}\n\n/*\n================\nSV_Status_f\n================\n*/\nstatic void SV_Status_f( void )\n{\n\tint\t\ti;\n\n#if !XASH_DEDICATED\n\tif( !svs.clients && CL_Active( ))\n\t{\n\t\tCmd_ForwardToServer();\n\t\treturn;\n\t}\n#endif // XASH_DEDICATED\n\n\tif( !svs.clients || sv.background )\n\t{\n\t\tCon_Printf( \"^3no server running.\\n\" );\n\t\treturn;\n\t}\n\n\tCon_Printf( \"map: %s\\n\", sv.name );\n\tCon_Printf( \"# score ping dev  lastmsg qport useragent\\t\\tname\\t\\taddress\\n\" );\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tconst sv_client_t *cl = &svs.clients[i];\n\t\tint j = 0;\n\t\tconst char *s;\n\t\tchar devices[8];\n\t\tstring version;\n\t\tstring os;\n\t\tstring arch;\n\t\tint buildnum;\n\t\tint input_devices;\n\n\t\tif( !cl->state )\n\t\t\tcontinue;\n\n\t\tif( cl->state == cs_connected )\n\t\t\ts = \"Connect \";\n\t\telse if( cl->state == cs_spawning )\n\t\t\ts = \"Spawning\";\n\t\telse if( cl->state == cs_zombie )\n\t\t\ts = \"Zombie  \";\n\t\telse if( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\ts = \"Bot     \";\n\t\telse\n\t\t\ts = va( \"%8i\", SV_CalcPing( cl ));\n\n\t\tinput_devices = Q_atoi( Info_ValueForKey( cl->useragent, \"d\" ));\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_MOUSE ))\n\t\t\tdevices[j++] = 'm';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_TOUCH ))\n\t\t\tdevices[j++] = 't';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_JOYSTICK ))\n\t\t\tdevices[j++] = 'j';\n\n\t\tif( FBitSet( input_devices, INPUT_DEVICE_VR ))\n\t\t\tdevices[j++] = 'v';\n\n\t\tif( j == 0 )\n\t\t\tQ_strncpy( devices, \"n/a\", sizeof( devices ));\n\t\telse\n\t\t\tdevices[j++] = 0;\n\n\t\tQ_strncpy( version, Info_ValueForKey( cl->useragent, \"v\" ), sizeof( version ));\n\t\tQ_strncpy( os, Info_ValueForKey( cl->useragent, \"o\" ), sizeof( os ));\n\t\tQ_strncpy( arch, Info_ValueForKey( cl->useragent, \"a\" ), sizeof( arch ));\n\t\tbuildnum = Q_atoi( Info_ValueForKey( cl->useragent, \"b\" ));\n\n\t\tif( !COM_CheckStringEmpty( version ))\n\t\t\tQ_strncpy( version, \"n/a\", sizeof( version ));\n\t\tif( !COM_CheckStringEmpty( os ))\n\t\t\tQ_strncpy( os, \"n/a\", sizeof( os ));\n\t\tif( !COM_CheckStringEmpty( arch ))\n\t\t\tQ_strncpy( arch, \"n/a\", sizeof( arch ));\n\n\t\tCon_Printf( \"%2i %5i %4s %4s %.5f %5i %s (%s-%s %i)\\t%8s\\t%8s\\n\",\n\t\t\ti, (int)cl->edict->v.frags, s, devices, host.realtime - cl->netchan.last_received, cl->netchan.qport,\n\t\t\tversion, os, arch, buildnum,\n\t\t\tcl->name, NET_BaseAdrToString( cl->netchan.remote_address ) );\n\n\t}\n\tCon_Printf( \"\\n\" );\n}\n\n/*\n==================\nSV_ConSay_f\n==================\n*/\nstatic void SV_ConSay_f( void )\n{\n\tconst char\t*p;\n\tchar\t\ttext[MAX_SYSPATH];\n\n\tif( Cmd_Argc() < 2 ) return;\n\n\tif( !svs.clients || sv.background )\n\t{\n\t\tCon_Printf( \"^3no server running.\\n\" );\n\t\treturn;\n\t}\n\n\tp = Cmd_Args();\n\tQ_strncpy( text, *p == '\"' ? p + 1 : p, sizeof( text ));\n\n\tif( *p == '\"' )\n\t{\n\t\ttext[Q_strlen(text) - 1] = 0;\n\t}\n\n\tLog_Printf( \"Server say: \\\"%s\\\"\\n\", text );\n\tQ_snprintf( text, sizeof( text ), \"%s: %s\", Cvar_VariableString( \"hostname\" ), p );\n\tSV_BroadcastPrintf( NULL, \"%s\\n\", text );\n}\n\n/*\n==================\nSV_Heartbeat_f\n==================\n*/\nstatic void SV_Heartbeat_f( void )\n{\n\tNET_MasterClear();\n}\n\n/*\n===========\nSV_ServerInfo_f\n\nExamine or change the serverinfo string\n===========\n*/\nstatic void SV_ServerInfo_f( void )\n{\n\tconvar_t\t*var;\n\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( \"Server info settings:\\n\" );\n\t\tInfo_Print( svs.serverinfo );\n\t\tCon_Printf( \"Total %zu symbols\\n\", Q_strlen( svs.serverinfo ));\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"serverinfo [ <key> <value> ]\\n\");\n\t\treturn;\n\t}\n\n\tif( Cmd_Argv(1)[0] == '*' )\n\t{\n\t\tCon_Printf( \"Star variables cannot be changed.\\n\" );\n\t\treturn;\n\t}\n\n\t// if this is a cvar, change it too\n\tvar = Cvar_FindVar( Cmd_Argv( 1 ));\n\tif( var )\n\t{\n\t\tfreestring( var->string ); // free the old value string\n\t\tvar->string = copystring( Cmd_Argv( 2 ));\n\t\tvar->value = Q_atof( var->string );\n\t}\n\n\tInfo_SetValueForStarKey( svs.serverinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_SERVERINFO_STRING );\n\tSV_BroadcastCommand( \"fullserverinfo \\\"%s\\\"\\n\", svs.serverinfo );\n}\n\n/*\n===========\nSV_LocalInfo_f\n\nExamine or change the localinfo string\n===========\n*/\nstatic void SV_LocalInfo_f( void )\n{\n\tif( Cmd_Argc() == 1 )\n\t{\n\t\tCon_Printf( \"Local info settings:\\n\" );\n\t\tInfo_Print( svs.localinfo );\n\t\tCon_Printf( \"Total %zu symbols\\n\", Q_strlen( svs.localinfo ));\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"localinfo [ <key> <value> ]\\n\");\n\t\treturn;\n\t}\n\n\tif( Cmd_Argv(1)[0] == '*' )\n\t{\n\t\tCon_Printf( \"Star variables cannot be changed.\\n\" );\n\t\treturn;\n\t}\n\n\tInfo_SetValueForStarKey( svs.localinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_LOCALINFO_STRING );\n}\n\n/*\n===========\nSV_ClientInfo_f\n\nExamine all a users info strings\n===========\n*/\nstatic void SV_ClientInfo_f( void )\n{\n\tsv_client_t\t*cl;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"clientinfo <userid>\\n\" );\n\t\treturn;\n\t}\n\n\tif(( cl = SV_SetPlayer( )) == NULL )\n\t\treturn;\n\n\tCon_Printf( \"userinfo\\n\" );\n\tCon_Printf( \"--------\\n\" );\n\tInfo_Print( cl->userinfo );\n\n}\n\n/*\n===========\nSV_ClientUserAgent_f\n\nExamine useragent strings\n===========\n*/\nstatic void SV_ClientUserAgent_f( void )\n{\n\tsv_client_t\t*cl;\n\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"clientuseragent <userid>\\n\" );\n\t\treturn;\n\t}\n\n\tif(( cl = SV_SetPlayer( )) == NULL )\n\t\treturn;\n\n\tCon_Printf( \"useragent\\n\" );\n\tCon_Printf( \"---------\\n\" );\n\tInfo_Print( cl->useragent );\n}\n\n/*\n===============\nSV_KillServer_f\n\nKick everyone off, possibly in preparation for a new game\n===============\n*/\nstatic void SV_KillServer_f( void )\n{\n\tSV_Shutdown( \"Server was killed due to shutdownserver command\\n\" );\n}\n\n/*\n===============\nSV_PlayersOnly_f\n\ndisable plhysics but players\n===============\n*/\nstatic void SV_PlayersOnly_f( void )\n{\n\tif( !Cvar_VariableInteger( \"sv_cheats\" )) return;\n\n\tsv.playersonly ^= 1;\n\n\tSV_BroadcastPrintf( NULL, \"%s game physic\\n\", sv.playersonly ? \"Freeze\" : \"Resume\" );\n}\n\n/*\n===============\nSV_EdictUsage_f\n\n===============\n*/\nstatic void SV_EdictUsage_f( void )\n{\n\tint\tactive;\n\n\tif( sv.state != ss_active )\n\t{\n\t\tCon_Printf( \"^3no server running.\\n\" );\n\t\treturn;\n\t}\n\n\tactive = pfnNumberOfEntities();\n\tCon_Printf( \"%5i edicts is used\\n\", active );\n\tCon_Printf( \"%5i edicts is free\\n\", GI->max_edicts - active );\n\tCon_Printf( \"%5i total\\n\", GI->max_edicts );\n}\n\n/*\n===============\nSV_EntityInfo_f\n\n===============\n*/\nstatic void SV_EntityInfo_f( void )\n{\n\tedict_t\t*ent;\n\tint\ti;\n\n\tif( sv.state != ss_active )\n\t{\n\t\tCon_Printf( \"^3no server running.\\n\" );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\t\tif( !SV_IsValidEdict( ent )) continue;\n\n\t\tCon_Printf( \"%5i origin: %.f %.f %.f\", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );\n\n\t\tif( ent->v.classname )\n\t\t\tCon_Printf( \", class: %s\", STRING( ent->v.classname ));\n\n\t\tif( ent->v.globalname )\n\t\t\tCon_Printf( \", global: %s\", STRING( ent->v.globalname ));\n\n\t\tif( ent->v.targetname )\n\t\t\tCon_Printf( \", name: %s\", STRING( ent->v.targetname ));\n\n\t\tif( ent->v.target )\n\t\t\tCon_Printf( \", target: %s\", STRING( ent->v.target ));\n\n\t\tif( ent->v.model )\n\t\t\tCon_Printf( \", model: %s\", STRING( ent->v.model ));\n\n\t\tCon_Printf( \"\\n\" );\n\t}\n}\n\n/*\n================\nRcon_Redirect_f\n\nForce redirect N lines of console output to client\n================\n*/\nstatic void Rcon_Redirect_f( void )\n{\n\tint lines = 2000;\n\n\tif( !host.rd.target )\n\t{\n\t\tMsg( \"redirect is only valid from rcon\\n\" );\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() == 2 )\n\t\tlines = Q_atoi( Cmd_Argv( 1 ) );\n\n\thost.rd.lines = lines;\n\tMsg( \"Redirection enabled for next %d lines\\n\", lines );\n}\n\nstatic void SV_ListMessages_f( void )\n{\n\tint i;\n\n\tCon_Printf( \"num size name\\n\" );\n\tfor( i = 1; i < MAX_USER_MESSAGES; i++ )\n\t{\n\t\tif( !COM_CheckStringEmpty( svgame.msg[i].name ))\n\t\t\tbreak;\n\n\t\tCon_Printf( \"%3d\\t%3d\\t%s\\n\", svgame.msg[i].number, svgame.msg[i].size, svgame.msg[i].name );\n\t}\n\n\tCon_Printf( \"Total %i messages\\n\", i - 1 );\n}\n\n/*\n==================\nSV_InitHostCommands\n\ncommands that create server\nis available always\n==================\n*/\nvoid SV_InitHostCommands( void )\n{\n\tCmd_AddRestrictedCommand( \"map\", SV_Map_f, \"start new level\" );\n\tCmd_AddCommand( \"maps\", SV_Maps_f, \"list maps\" );\n\n\tif( host.type == HOST_NORMAL )\n\t{\n\t\tCmd_AddRestrictedCommand( \"newgame\", SV_NewGame_f, \"begin new game\" );\n\t\tCmd_AddRestrictedCommand( \"hazardcourse\", SV_HazardCourse_f, \"starting a Hazard Course\" );\n\t\tCmd_AddRestrictedCommand( \"map_background\", SV_MapBackground_f, \"set background map\" );\n\t\tCmd_AddRestrictedCommand( \"load\", SV_Load_f, \"load a saved game file\" );\n\t\tCmd_AddRestrictedCommand( \"loadquick\", SV_QuickLoad_f, \"load a quick-saved game file\" );\n\t\tCmd_AddRestrictedCommand( \"reload\", SV_Reload_f, \"continue from latest save or restart level\" );\n\t\tCmd_AddRestrictedCommand( \"killsave\", SV_DeleteSave_f, \"delete a saved game file and saveshot\" );\n\t\tCmd_AddRestrictedCommand( \"nextmap\", SV_NextMap_f, \"load next level\" );\n\t}\n}\n\n/*\n==================\nSV_InitOperatorCommands\n==================\n*/\nvoid SV_InitOperatorCommands( void )\n{\n\tCmd_AddCommand( \"heartbeat\", SV_Heartbeat_f, \"send a heartbeat to the master server\" );\n\tCmd_AddCommand( \"kick\", SV_Kick_f, \"kick a player off the server by number or name\" );\n\tCmd_AddCommand( \"status\", SV_Status_f, \"print server status information\" );\n\tCmd_AddCommand( \"localinfo\", SV_LocalInfo_f, \"examine or change the localinfo string\" );\n\tCmd_AddCommand( \"serverinfo\", SV_ServerInfo_f, \"examine or change the serverinfo string\" );\n\tCmd_AddCommand( \"clientinfo\", SV_ClientInfo_f, \"print user infostring (player num required)\" );\n\tCmd_AddCommand( \"clientuseragent\", SV_ClientUserAgent_f, \"print user agent (player num required)\" );\n\tCmd_AddCommand( \"playersonly\", SV_PlayersOnly_f, \"freezes time, except for players\" );\n\tCmd_AddCommand( \"restart\", SV_Restart_f, \"restarting current level\" );\n\tCmd_AddCommand( \"entpatch\", SV_EntPatch_f, \"write entity patch to allow external editing\" );\n\tCmd_AddCommand( \"edict_usage\", SV_EdictUsage_f, \"show info about edicts usage\" );\n\tCmd_AddCommand( \"entity_info\", SV_EntityInfo_f, \"show more info about edicts\" );\n\tCmd_AddCommand( \"shutdownserver\", SV_KillServer_f, \"shutdown current server\" );\n\tCmd_AddCommand( \"changelevel\", SV_ChangeLevel_f, \"change level\" );\n\tCmd_AddCommand( \"changelevel2\", SV_ChangeLevel2_f, \"smooth change level\" );\n\tCmd_AddCommand( \"redirect\", Rcon_Redirect_f, \"force enable rcon redirection\" );\n\tCmd_AddCommand( \"logaddress\", SV_SetLogAddress_f, \"sets address and port for remote logging host\" );\n\tCmd_AddCommand( \"log\", SV_ServerLog_f, \"enables logging to file\" );\n\tCmd_AddCommand( \"str64stats\", SV_PrintStr64Stats_f, \"print engine pool string statistics\" );\n\tCmd_AddCommand( \"sv_list_messages\", SV_ListMessages_f, \"list registered user messages\" );\n\n\tif( host.type == HOST_NORMAL )\n\t{\n\t\tCmd_AddCommand( \"save\", SV_Save_f, \"save the game to a file\" );\n\t\tCmd_AddCommand( \"savequick\", SV_QuickSave_f, \"save the game to the quicksave\" );\n\t\tCmd_AddCommand( \"autosave\", SV_AutoSave_f, \"save the game to 'autosave' file\" );\n\t}\n\telse if( host.type == HOST_DEDICATED )\n\t{\n\t\tCmd_AddCommand( \"say\", SV_ConSay_f, \"send a chat message to everyone on the server\" );\n\t}\n}\n\n/*\n==================\nSV_KillOperatorCommands\n==================\n*/\nvoid SV_KillOperatorCommands( void )\n{\n\tCmd_RemoveCommand( \"heartbeat\" );\n\tCmd_RemoveCommand( \"kick\" );\n\tCmd_RemoveCommand( \"status\" );\n\tCmd_RemoveCommand( \"localinfo\" );\n\tCmd_RemoveCommand( \"serverinfo\" );\n\tCmd_RemoveCommand( \"clientinfo\" );\n\tCmd_RemoveCommand( \"clientuseragent\" );\n\tCmd_RemoveCommand( \"playersonly\" );\n\tCmd_RemoveCommand( \"restart\" );\n\tCmd_RemoveCommand( \"entpatch\" );\n\tCmd_RemoveCommand( \"edict_usage\" );\n\tCmd_RemoveCommand( \"entity_info\" );\n\tCmd_RemoveCommand( \"shutdownserver\" );\n\tCmd_RemoveCommand( \"changelevel\" );\n\tCmd_RemoveCommand( \"changelevel2\" );\n\tCmd_RemoveCommand( \"redirect\" );\n\tCmd_RemoveCommand( \"logaddress\" );\n\tCmd_RemoveCommand( \"log\" );\n\tCmd_RemoveCommand( \"str64stats\" );\n\n\tif( host.type == HOST_NORMAL )\n\t{\n\t\tCmd_RemoveCommand( \"save\" );\n\t\tCmd_RemoveCommand( \"savequick\" );\n\t\tCmd_RemoveCommand( \"autosave\" );\n\t}\n\telse if( host.type == HOST_DEDICATED )\n\t{\n\t\tCmd_RemoveCommand( \"say\" );\n\t}\n}\n"
  },
  {
    "path": "engine/server/sv_custom.c",
    "content": "/*\nsv_custom.c - downloading custom resources\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n\nstatic void SV_CreateCustomizationList( sv_client_t *cl )\n{\n\tresource_t\t*pResource;\n\tcustomization_t\t*pList, *pCust;\n\tqboolean\t\tbFound;\n\tint\t\tnLumps;\n\n\tcl->customdata.pNext = NULL;\n\n\tfor( pResource = cl->resourcesonhand.pNext; pResource != &cl->resourcesonhand; pResource = pResource->pNext )\n\t{\n\t\tbFound = false;\n\n\t\tfor( pList = cl->customdata.pNext; pList != NULL; pList = pList->pNext )\n\t\t{\n\t\t\tif( !memcmp( pList->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 ))\n\t\t\t{\n\t\t\t\tbFound = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !bFound )\n\t\t{\n\t\t\tnLumps = 0;\n\n\t\t\tif( COM_CreateCustomization( &cl->customdata, pResource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA, &pCust, &nLumps ))\n\t\t\t{\n\t\t\t\tpCust->nUserData2 = nLumps;\n\t\t\t\tsvgame.dllFuncs.pfnPlayerCustomization( cl->edict, pCust );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( sv_allow_upload.value )\n\t\t\t\t\tCon_Printf( \"Ignoring invalid custom decal from %s\\n\", cl->name );\n\t\t\t\telse Con_Printf( \"Ignoring custom decal from %s\\n\", cl->name );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_WARN \"%s: ignoring dup. resource for player %s\\n\", __func__, cl->name );\n\t\t}\n\t}\n}\n\nstatic qboolean SV_FileInConsistencyList( const char *filename, consistency_t **ppout )\n{\n\tint\ti;\n\n\tif( ppout != NULL )\n\t\t*ppout = NULL;\n\n\tfor( i = 0; i < MAX_MODELS; i++ )\n\t{\n\t\tconsistency_t\t*pc = &sv.consistency_list[i];\n\n\t\tif( !pc->filename )\n\t\t\tbreak;\n\n\t\tif( !Q_stricmp( pc->filename, filename ))\n\t\t{\n\t\t\tif( ppout != NULL )\n\t\t\t\t*ppout = pc;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg )\n{\n\tint\t\ti, c, idx, value;\n\tbyte\t\treadbuffer[32];\n\tbyte\t\tnullbuffer[32];\n\tbyte\t\tresbuffer[32];\n\tqboolean\t\tinvalid_type;\n\tvec3_t\t\tcmins, cmaxs;\n\tint\t\tbadresindex;\n\tvec3_t\t\tmins, maxs;\n\tFORCE_TYPE\tft;\n\tresource_t\t*r;\n\n\tmemset( nullbuffer, 0, sizeof( nullbuffer ));\n\tinvalid_type = false;\n\tbadresindex = 0;\n\tc = 0;\n\n\twhile( MSG_ReadOneBit( msg ))\n\t{\n\t\tidx = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );\n\t\tif( idx < 0 || idx >= sv.num_resources )\n\t\t\tbreak;\n\n\t\tr = &sv.resources[idx];\n\n\t\tif( !FBitSet( r->ucFlags, RES_CHECKFILE ))\n\t\t\tbreak;\n\n\t\tmemcpy( readbuffer, r->rguc_reserved, 32 );\n\n\t\tif( !memcmp( readbuffer, nullbuffer, 32 ))\n\t\t{\n\t\t\tvalue = MSG_ReadUBitLong( msg, 32 );\n\n\t\t\tLittleLongSW( value );\n\n\t\t\t// will be compare only first 4 bytes\n\t\t\tif( memcmp( &value, r->rgucMD5_hash, 4 ))\n\t\t\t\tbadresindex = idx + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_ReadBytes( msg, cmins, sizeof( cmins ));\n\t\t\tMSG_ReadBytes( msg, cmaxs, sizeof( cmaxs ));\n\n\t\t\tmemcpy( resbuffer, r->rguc_reserved, 32 );\n\t\t\tft = resbuffer[0];\n\n\t\t\tswitch( ft )\n\t\t\t{\n\t\t\tcase force_model_samebounds:\n\t\t\t\tmemcpy( mins, &resbuffer[0x01], sizeof( mins ));\n\t\t\t\tmemcpy( maxs, &resbuffer[0x0D], sizeof( maxs ));\n\n\t\t\t\tif( !VectorCompare( cmins, mins ) || !VectorCompare( cmaxs, maxs ))\n\t\t\t\t\tbadresindex = idx + 1;\n\t\t\t\tbreak;\n\t\t\tcase force_model_specifybounds:\n\t\t\t\tmemcpy( mins, &resbuffer[0x01], sizeof( mins ));\n\t\t\t\tmemcpy( maxs, &resbuffer[0x0D], sizeof( maxs ));\n\n\t\t\t\tfor( i = 0; i < 3; i++ )\n\t\t\t\t{\n\t\t\t\t\tif( cmins[i] < mins[i] || cmaxs[i] > maxs[i] )\n\t\t\t\t\t{\n\t\t\t\t\t\tbadresindex = idx + 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tinvalid_type = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( invalid_type )\n\t\t\tbreak;\n\t\tc++;\n\t}\n\n\tif( sv.num_consistency != c )\n\t{\n\t\tCon_Printf( S_WARN \"%s:%s sent bad file data\\n\", cl->name, NET_AdrToString( cl->netchan.remote_address ));\n\t\tSV_DropClient( cl, false );\n\t\treturn;\n\t}\n\n\tif( badresindex != 0 )\n\t{\n\t\tchar\tdropmessage[256];\n\n\t\tdropmessage[0] = 0;\n\t\tif( svgame.dllFuncs.pfnInconsistentFile( cl->edict, sv.resources[badresindex - 1].szFileName, dropmessage ))\n\t\t{\n\t\t\tif( COM_CheckString( dropmessage ))\n\t\t\t\tSV_ClientPrintf( cl, \"%s\", dropmessage );\n\t\t\tSV_DropClient( cl, false );\n\t\t}\n\t}\n\telse\n\t{\n\t\tClearBits( cl->flags, FCL_FORCE_UNMODIFIED );\n\t}\n}\n\nvoid SV_TransferConsistencyInfo( void )\n{\n\tvec3_t\t\tmins, maxs;\n\tint\t\ti, total = 0;\n\tresource_t\t*pResource;\n\tstring\t\tfilepath;\n\tconsistency_t\t*pc;\n\n\tfor( i = 0; i < sv.num_resources; i++ )\n\t{\n\t\tpResource = &sv.resources[i];\n\n\t\tif( FBitSet( pResource->ucFlags, RES_CHECKFILE ))\n\t\t\tcontinue;\t// already checked?\n\n\t\tif( !SV_FileInConsistencyList( pResource->szFileName, &pc ))\n\t\t\tcontinue;\n\n\t\tSetBits( pResource->ucFlags, RES_CHECKFILE );\n\n\t\tif( pResource->type == t_sound )\n\t\t\tQ_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH \"%s\", pResource->szFileName );\n\t\telse Q_strncpy( filepath, pResource->szFileName, sizeof( filepath ));\n\n\t\tMD5_HashFile( pResource->rgucMD5_hash, filepath, NULL );\n\n\t\tif( pResource->type == t_model )\n\t\t{\n\t\t\tswitch( pc->check_type )\n\t\t\t{\n\t\t\tcase force_exactfile:\n\t\t\t\t// only MD5 hash compare\n\t\t\t\tbreak;\n\t\t\tcase force_model_samebounds:\n\t\t\t\tif( !Mod_GetStudioBounds( filepath, mins, maxs ))\n\t\t\t\t\tHost_Error( \"%s: couldn't get bounds for %s\\n\", __func__, filepath );\n\t\t\t\tmemcpy( &pResource->rguc_reserved[0x01], mins, sizeof( mins ));\n\t\t\t\tmemcpy( &pResource->rguc_reserved[0x0D], maxs, sizeof( maxs ));\n\t\t\t\tpResource->rguc_reserved[0] = pc->check_type;\n\t\t\t\tbreak;\n\t\t\tcase force_model_specifybounds:\n\t\t\t\tmemcpy( &pResource->rguc_reserved[0x01], pc->mins, sizeof( pc->mins ));\n\t\t\t\tmemcpy( &pResource->rguc_reserved[0x0D], pc->maxs, sizeof( pc->maxs ));\n\t\t\t\tpResource->rguc_reserved[0] = pc->check_type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\ttotal++;\n\t}\n\n\tsv.num_consistency = total;\n}\n\nstatic void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg )\n{\n\tint\ti, lastcheck;\n\tint\tdelta;\n\n\tif( svs.maxclients == 1 || !sv_consistency.value || !sv.num_consistency || FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t{\n\t\tClearBits( cl->flags, FCL_FORCE_UNMODIFIED );\n\t\tMSG_WriteOneBit( msg, 0 );\n\t\treturn;\n\t}\n\n\tSetBits( cl->flags, FCL_FORCE_UNMODIFIED );\n\tMSG_WriteOneBit( msg, 1 );\n\tlastcheck = 0;\n\n\tfor( i = 0; i < sv.num_resources; i++ )\n\t{\n\t\tif( !FBitSet( sv.resources[i].ucFlags, RES_CHECKFILE ))\n\t\t\tcontinue;\n\n\t\tdelta = i - lastcheck;\n\t\tMSG_WriteOneBit( msg, 1 );\n\n\t\tif( delta > 31 )\n\t\t{\n\t\t\tMSG_WriteOneBit( msg, 0 );\n\t\t\tMSG_WriteUBitLong( msg, i, MAX_MODEL_BITS );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_WriteOneBit( msg, 1 );\n\t\t\tMSG_WriteUBitLong( msg, delta, 5 );\n\t\t}\n\n\t\tlastcheck = i;\n\t}\n\n\t// write end of the list\n\tMSG_WriteOneBit( msg, 0 );\n}\n\nstatic qboolean SV_CheckFile( sizebuf_t *msg, const char *filename )\n{\n\tresource_t\tp;\n\n\tmemset( &p, 0, sizeof( resource_t ));\n\n\tif( Q_strlen( filename ) == 36 && !Q_strnicmp( filename, \"!MD5\", 4 ))\n\t{\n\t\tCOM_HexConvert( filename + 4, 32, p.rgucMD5_hash );\n\n\t\tif( HPAK_GetDataPointer( hpk_custom_file.string, &p, NULL, NULL ))\n\t\t\treturn true;\n\t}\n\n\tif( !sv_allow_upload.value )\n\t\treturn true;\n\n\tMSG_BeginServerCmd( msg, svc_stufftext );\n\tMSG_WriteStringf( msg, \"upload \\\"!MD5%s\\\"\\n\", MD5_Print( p.rgucMD5_hash ));\n\n\treturn false;\n}\n\nvoid SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource )\n{\n\tif( !pResource )\n\t{\n\t\tCon_Reportf( \"Null resource passed to SV_MoveToOnHandList\\n\" );\n\t\treturn;\n\t}\n\n\tSV_RemoveFromResourceList( pResource );\n\tSV_AddToResourceList( pResource, &cl->resourcesonhand );\n}\n\nvoid SV_AddToResourceList( resource_t *pResource, resource_t *pList )\n{\n\tif( pResource->pPrev != NULL || pResource->pNext != NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"Resource already linked\\n\" );\n\t\treturn;\n\t}\n\n\tpResource->pPrev = pList->pPrev;\n\tpResource->pNext = pList;\n\tpList->pPrev->pNext = pResource;\n\tpList->pPrev = pResource;\n}\n\nstatic void SV_SendCustomization( sv_client_t *cl, int playernum, resource_t *pResource )\n{\n\tMSG_BeginServerCmd( &cl->netchan.message, svc_customization );\n\tMSG_WriteByte( &cl->netchan.message, playernum );\t// playernum\n\tMSG_WriteByte( &cl->netchan.message, pResource->type );\n\tMSG_WriteString( &cl->netchan.message, pResource->szFileName );\n\tMSG_WriteShort( &cl->netchan.message, pResource->nIndex );\n\tMSG_WriteLong( &cl->netchan.message, pResource->nDownloadSize );\n\tMSG_WriteByte( &cl->netchan.message, pResource->ucFlags );\n\n\tif( FBitSet( pResource->ucFlags, RES_CUSTOM ))\n\t\tMSG_WriteBytes( &cl->netchan.message, pResource->rgucMD5_hash, 16 );\n}\n\nvoid SV_RemoveFromResourceList( resource_t *pResource )\n{\n\tpResource->pPrev->pNext = pResource->pNext;\n\tpResource->pNext->pPrev = pResource->pPrev;\n\tpResource->pPrev = NULL;\n\tpResource->pNext = NULL;\n}\n\nvoid SV_ClearResourceList( resource_t *pList )\n{\n\tresource_t *p;\n\tresource_t *n;\n\n\tfor( p = pList->pNext; pList != p && p; p = n )\n\t{\n\t\tn = p->pNext;\n\n\t\tSV_RemoveFromResourceList( p );\n\t\tMem_Free( p );\n\t}\n\n\tpList->pPrev = pList;\n\tpList->pNext = pList;\n}\n\nvoid SV_ClearResourceLists( sv_client_t *cl )\n{\n\tSV_ClearResourceList( &cl->resourcesneeded );\n\tSV_ClearResourceList( &cl->resourcesonhand );\n}\n\nint SV_EstimateNeededResources( sv_client_t *cl )\n{\n\tint\t\tmissing = 0;\n\tint\t\tsize = 0;\n\tresource_t\t*p;\n\n\tfor( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = p->pNext )\n\t{\n\t\tif( p->type != t_decal )\n\t\t\tcontinue;\n\n\t\tif( !HPAK_ResourceForHash( hpk_custom_file.string, p->rgucMD5_hash, NULL ))\n\t\t{\n\t\t\tif( p->nDownloadSize != 0 )\n\t\t\t{\n\t\t\t\tSetBits( p->ucFlags, RES_WASMISSING );\n\t\t\t\tsize += p->nDownloadSize;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tmissing++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn size;\n}\n\nstatic void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSkipPlayer )\n{\n\tint\t\ti, nPlayerNumber = -1;\n\tsv_client_t\t*cl;\n\n\ti = pClient - svs.clients;\n\tif( i >= 0 && i < svs.maxclients )\n\t\tnPlayerNumber = i;\n\telse Host_Error( \"Couldn't find player index for customization.\\n\" );\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tif( cl == pClient && bSkipPlayer )\n\t\t\tcontinue;\n\n\t\tSV_SendCustomization( cl, nPlayerNumber, pResource );\n\t}\n}\n\nstatic void SV_PropagateCustomizations( sv_client_t *pHost )\n{\n\tcustomization_t\t*pCust;\n\tresource_t\t*pResource;\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tfor( pCust = cl->customdata.pNext; pCust != NULL; pCust = pCust->pNext )\n\t\t{\n\t\t\tif( !pCust->bInUse ) continue;\n\t\t\tpResource = &pCust->resource;\n\t\t\tSV_SendCustomization( pHost, i, pResource );\n\t\t}\n\t}\n}\n\nstatic void SV_RegisterResources( sv_client_t *pHost )\n{\n\tresource_t\t*pResource;\n\n\tfor( pResource = pHost->resourcesonhand.pNext; pResource != &pHost->resourcesonhand; pResource = pResource->pNext )\n\t{\n\t\tSV_CreateCustomizationList( pHost );\n\t\tSV_Customization( pHost, pResource, true );\n\t}\n}\n\nstatic qboolean SV_UploadComplete( sv_client_t *cl )\n{\n\tif( &cl->resourcesneeded != cl->resourcesneeded.pNext )\n\t\treturn false;\n\n\tSV_RegisterResources( cl );\n\tSV_PropagateCustomizations( cl );\n\n\tif( sv_allow_upload.value )\n\t\tCon_Printf( \"Custom resource propagation complete.\\n\" );\n\tcl->upstate = us_complete;\n\n\treturn true;\n}\n\nvoid SV_RequestMissingResources( void )\n{\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( cl->upstate == us_processing )\n\t\t\tSV_UploadComplete( cl );\n\t}\n}\n\nvoid SV_BatchUploadRequest( sv_client_t *cl )\n{\n\tstring\t\tfilename;\n\tresource_t\t*p, *n;\n\n\tfor( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = n )\n\t{\n\t\tn = p->pNext;\n\n\t\tif( !FBitSet( p->ucFlags, RES_WASMISSING ))\n\t\t{\n\t\t\tSV_MoveToOnHandList( cl, p );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( p->type == t_decal )\n\t\t{\n\t\t\tif( FBitSet( p->ucFlags, RES_CUSTOM ))\n\t\t\t{\n\t\t\t\tQ_snprintf( filename, sizeof( filename ), \"!MD5%s\", MD5_Print( p->rgucMD5_hash ));\n\n\t\t\t\tif( SV_CheckFile( &cl->netchan.message, filename ))\n\t\t\t\t\tSV_MoveToOnHandList( cl, p );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tCon_Reportf( S_ERROR \"Non customization in upload queue!\\n\" );\n\t\t\t\tSV_MoveToOnHandList( cl, p );\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid SV_SendResource( resource_t *pResource, sizebuf_t *msg )\n{\n\tstatic byte\tnullrguc[sizeof( pResource->rguc_reserved )];\n\n\tMSG_WriteUBitLong( msg, pResource->type, 4 );\n\tMSG_WriteString( msg, pResource->szFileName );\n\tMSG_WriteUBitLong( msg, pResource->nIndex, MAX_MODEL_BITS );\n\tMSG_WriteSBitLong( msg, pResource->nDownloadSize, 24 ); // prevent to download a very big files?\n\tMSG_WriteUBitLong( msg, pResource->ucFlags & ( RES_FATALIFMISSING|RES_WASMISSING ), 3 );\n\n\tif( FBitSet( pResource->ucFlags, RES_CUSTOM ))\n\t\tMSG_WriteBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));\n\n\tif( memcmp( nullrguc, pResource->rguc_reserved, sizeof( nullrguc )))\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n}\n\nvoid SV_SendResources( sv_client_t *cl, sizebuf_t *msg )\n{\n\tint\ti;\n\n\tMSG_BeginServerCmd( msg, svc_resourcerequest );\n\tMSG_WriteLong( msg, svs.spawncount );\n\tMSG_WriteLong( msg, 0 );\n\n\tif( COM_CheckString( sv_downloadurl.string ) && Q_strlen( sv_downloadurl.string ) < 256 )\n\t{\n\t\tMSG_BeginServerCmd( msg, svc_resourcelocation );\n\t\tMSG_WriteString( msg, sv_downloadurl.string );\n\t}\n\n\tMSG_BeginServerCmd( msg, svc_resourcelist );\n\tMSG_WriteUBitLong( msg, sv.num_resources, MAX_RESOURCE_BITS );\n\n\tfor( i = 0; i < sv.num_resources; i++ )\n\t{\n\t\tSV_SendResource( &sv.resources[i], msg );\n\t}\n\n\tSV_SendConsistencyList( cl, msg );\n}\n"
  },
  {
    "path": "engine/server/sv_filter.c",
    "content": "/*\nsv_filter.c - server ID/IP filter\nCopyright (C) 2017 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n\n\n/*\n=============================================================================\n\nPLAYER ID FILTER\n\n=============================================================================\n*/\ntypedef struct cidfilter_s\n{\n\tfloat endTime;\n\tstruct cidfilter_s *next;\n\tstring id;\n} cidfilter_t;\n\nstatic cidfilter_t *cidfilter = NULL;\n\nstatic void SV_RemoveID( const char *id )\n{\n\tcidfilter_t *filter, *prevfilter = NULL;\n\n\tfor( filter = cidfilter; filter; filter = filter->next )\n\t{\n\t\tif( Q_strcmp( filter->id, id ))\n\t\t{\n\t\t\tprevfilter = filter;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( filter == cidfilter )\n\t\t{\n\t\t\tcidfilter = cidfilter->next;\n\t\t\tMem_Free( filter );\n\t\t\treturn;\n\t\t}\n\n\t\tif( prevfilter )\n\t\tprevfilter->next = filter->next;\n\t\tMem_Free( filter );\n\t\treturn;\n\t}\n}\n\nqboolean SV_CheckID( const char *id )\n{\n\tqboolean ret = false;\n\tcidfilter_t *filter;\n\n\tfor( filter = cidfilter; filter; filter = filter->next )\n\t{\n\t\tint len1 = Q_strlen( id ), len2 = Q_strlen( filter->id );\n\t\tint len = Q_min( len1, len2 );\n\n\t\twhile( filter->endTime && host.realtime > filter->endTime )\n\t\t{\n\t\t\tchar *fid = filter->id;\n\t\t\tfilter = filter->next;\n\t\t\tSV_RemoveID( fid );\n\t\t\tif( !filter )\n\t\t\t\treturn false;\n\t\t}\n\n\t\tif( !Q_strncmp( id, filter->id, len ))\n\t\t{\n\t\t\tret = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nstatic void SV_BanID_f( void )\n{\n\tfloat time = Q_atof( Cmd_Argv( 1 ));\n\tconst char *id = Cmd_Argv( 2 );\n\tsv_client_t *cl = NULL;\n\tcidfilter_t *filter;\n\n\tif( time )\n\t\ttime = host.realtime + time * 60.0f;\n\n\tif( !id[0] )\n\t{\n\t\tCon_Reportf( S_USAGE \"banid <minutes> <#userid or unique id>\\n0 minutes for permanent ban\\n\" );\n\t\treturn;\n\t}\n\n\tif( !svs.clients )\n\t{\n\t\tCon_Reportf( S_ERROR \"banid: no players\\n\" );\n\t\treturn;\n\t}\n\n\tif( id[0] == '#' )\n\t{\n\t\tCon_Printf( S_ERROR \"banid: not supported\\n\" );\n\t\treturn;\n#if 0\n\t\tint i = Q_atoi( &id[1] );\n\n\t\tcl = SV_ClientById( i );\n\n\t\tif( !cl )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"banid: no such player with userid %d\\n\", i );\n\t\t\treturn;\n\t\t}\n#endif\n\t}\n\telse\n\t{\n\t\tsize_t len;\n\t\tint i;\n\n\t\tif( !Q_strnicmp( id, \"STEAM_\", 6 ) || !Q_strnicmp( id, \"VALVE_\", 6 ))\n\t\t\tid += 6;\n\t\tif( !Q_strnicmp( id, \"XASH_\", 5 ))\n\t\t\tid += 5;\n\n\t\tlen = Q_strlen( id );\n\n\t\tfor( i = 0; i < svs.maxclients; i++ )\n\t\t{\n\t\t\tif( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svs.clients[i].state != cs_spawned )\n\t\t\t\tcontinue;\n\n\t\t\tif( !Q_strncmp( id, Info_ValueForKey( svs.clients[i].useragent, \"uuid\" ), len ))\n\t\t\t{\n\t\t\t\tcl = &svs.clients[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !cl )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"banid: no such player with userid %s\\n\", id );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tid = Info_ValueForKey( cl->useragent, \"uuid\" );\n\n\tSV_RemoveID( id );\n\n\tfilter = Mem_Malloc( host.mempool, sizeof( cidfilter_t ));\n\tfilter->endTime = time;\n\tfilter->next = cidfilter;\n\tQ_strncpy( filter->id, id, sizeof( filter->id ));\n\tcidfilter = filter;\n\n\tif( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), \"kick\" ))\n\t\tCbuf_AddTextf( \"kick #%d \\\"Kicked and banned\\\"\\n\", cl->userid );\n}\n\nstatic void SV_ListID_f( void )\n{\n\tcidfilter_t *filter;\n\n\tCon_Reportf( \"id ban list\\n\" );\n\tCon_Reportf( \"-----------\\n\" );\n\n\tfor( filter = cidfilter; filter; filter = filter->next )\n\t{\n\t\tif( filter->endTime && host.realtime > filter->endTime )\n\t\t\tcontinue; // no negative time\n\n\t\tif( filter->endTime )\n\t\t\tCon_Reportf( \"%s expries in %f minutes\\n\", filter->id, ( filter->endTime - host.realtime ) / 60.0f );\n\t\telse\n\t\t\tCon_Reportf( \"%s permanent\\n\", filter->id );\n\t}\n}\n\nstatic void SV_RemoveID_f( void )\n{\n\tconst char *id = Cmd_Argv( 1 );\n\n\tif( id[0] == '#' && svs.clients )\n\t{\n\t\tint num = Q_atoi( id + 1 );\n\n\t\tif( num >= svs.maxclients || num < 0 )\n\t\t\treturn;\n\n\t\tid = Info_ValueForKey( svs.clients[num].useragent, \"uuid\" );\n\t}\n\n\tif( !id[0] )\n\t{\n\t\tCon_Reportf( S_USAGE \"removeid <#slotnumber or uniqueid>\\n\");\n\t\treturn;\n\t}\n\n\tSV_RemoveID( id );\n}\n\nstatic void SV_WriteID_f( void )\n{\n\tfile_t *f = FS_Open( Cvar_VariableString( \"bannedcfgfile\" ), \"w\", false );\n\tcidfilter_t *filter;\n\n\tif( !f )\n\t{\n\t\tCon_DPrintf( S_ERROR \"Could not write %s\\n\", Cvar_VariableString( \"bannedcfgfile\" ));\n\t\treturn;\n\t}\n\n\tFS_Printf( f, \"//=======================================================================\\n\" );\n\tFS_Printf( f, \"//\\t\\tCopyright Flying With Gauss Team %s ©\\n\", Q_timestamp( TIME_YEAR_ONLY ));\n\tFS_Printf( f, \"//\\t\\t    %s - archive of id blacklist\\n\", Cvar_VariableString( \"bannedcfgfile\" ));\n\tFS_Printf( f, \"//=======================================================================\\n\" );\n\n\tfor( filter = cidfilter; filter; filter = filter->next )\n\t\tif( !filter->endTime ) // only permanent\n\t\t\tFS_Printf( f, \"banid 0 %s\\n\", filter->id );\n\n\tFS_Close( f );\n}\n\nstatic void SV_InitIDFilter( void )\n{\n\tCmd_AddRestrictedCommand( \"banid\", SV_BanID_f, \"ban player by ID\" );\n\tCmd_AddRestrictedCommand( \"listid\", SV_ListID_f, \"list banned players\" );\n\tCmd_AddRestrictedCommand( \"removeid\", SV_RemoveID_f, \"remove player from banned list\" );\n\tCmd_AddRestrictedCommand( \"writeid\", SV_WriteID_f, \"write banned.cfg\" );\n}\n\nstatic void SV_ShutdownIDFilter( void )\n{\n\tcidfilter_t *cidList, *cidNext;\n\n\t// should be called manually because banned.cfg is not executed by engine\n\t//SV_WriteID_f();\n\n\tCmd_RemoveCommand( \"banid\" );\n\tCmd_RemoveCommand( \"listid\" );\n\tCmd_RemoveCommand( \"removeid\" );\n\tCmd_RemoveCommand( \"writeid\" );\n\n\tfor( cidList = cidfilter; cidList; cidList = cidNext )\n\t{\n\t\tcidNext = cidList->next;\n\t\tMem_Free( cidList );\n\t}\n\n\tcidfilter = NULL;\n}\n\n/*\n=============================================================================\n\nCLIENT IP FILTER\n\n=============================================================================\n*/\n\ntypedef struct ipfilter_s\n{\n\tfloat endTime;\n\tstruct ipfilter_s *next;\n\tnetadr_t adr;\n\tuint prefixlen;\n} ipfilter_t;\n\nstatic ipfilter_t *ipfilter = NULL;\n\nstatic int SV_FilterToString( char *dest, size_t size, qboolean config, ipfilter_t *f )\n{\n\tif( config )\n\t{\n\t\treturn Q_snprintf( dest, size, \"addip 0 %s/%d\\n\", NET_AdrToString( f->adr ), f->prefixlen );\n\t}\n\telse if( f->endTime )\n\t{\n\t\treturn Q_snprintf( dest, size, \"%s/%d (%f minutes)\", NET_AdrToString( f->adr ), f->prefixlen, f->endTime );\n\t}\n\n\treturn Q_snprintf( dest, size, \"%s/%d (permanent)\", NET_AdrToString( f->adr ), f->prefixlen );\n}\n\nstatic qboolean SV_IPFilterIncludesIPFilter( ipfilter_t *a, ipfilter_t *b )\n{\n\tif( NET_NetadrType( &a->adr ) != NET_NetadrType( &b->adr ))\n\t\treturn false;\n\n\t// can't include bigger subnet in small\n\tif( a->prefixlen < b->prefixlen )\n\t\treturn false;\n\n\tif( a->prefixlen == b->prefixlen )\n\t\treturn NET_CompareAdr( a->adr, b->adr );\n\n\treturn NET_CompareAdrByMask( a->adr, b->adr, b->prefixlen );\n}\n\nstatic void SV_RemoveIPFilter( ipfilter_t *toremove, qboolean removeAll, qboolean verbose )\n{\n\tipfilter_t *f, **back;\n\n\tback = &ipfilter;\n\twhile( 1 )\n\t{\n\t\tf = *back;\n\t\tif( !f ) return;\n\n\t\tif( SV_IPFilterIncludesIPFilter( toremove, f ))\n\t\t{\n\t\t\tif( verbose )\n\t\t\t{\n\t\t\t\tstring filterStr;\n\n\t\t\t\tSV_FilterToString( filterStr, sizeof( filterStr ), false, f );\n\n\t\t\t\tCon_Printf( \"%s removed.\\n\", filterStr );\n\t\t\t}\n\n\t\t\t*back = f->next;\n\t\t\tback = &f->next;\n\n\t\t\tMem_Free( f );\n\n\t\t\tif( !removeAll )\n\t\t\t\tbreak;\n\t\t}\n\t\telse back = &f->next;\n\t}\n}\n\n\nqboolean SV_CheckIP( netadr_t *adr )\n{\n\t// TODO: ip rate limit\n\tipfilter_t *entry = ipfilter;\n\n\tfor( ; entry; entry = entry->next )\n\t{\n\t\tif( entry->endTime && host.realtime > entry->endTime )\n\t\t\tcontinue; // expired\n\n\t\tswitch( NET_NetadrType( &entry->adr ))\n\t\t{\n\t\tcase NA_IP:\n\t\tcase NA_IP6:\n\t\t\tif( NET_CompareAdrByMask( *adr, entry->adr, entry->prefixlen ))\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nstatic void SV_AddIP_PrintUsage( void )\n{\n\tCon_Printf(S_USAGE \"addip <minutes> <ipaddress>\\n\"\n\t\tS_USAGE_INDENT \"addip <minutes> <ipaddress/CIDR>\\n\"\n\t\t\"Use 0 minutes for permanent\\n\"\n\t\t\"ipaddress A.B.C.D/24 is equivalent to A.B.C.0 and A.B.C\\n\"\n\t\t\"NOTE: IPv6 addresses only support prefix format!\\n\");\n}\n\nstatic void SV_RemoveIP_PrintUsage( void )\n{\n\tCon_Printf(S_USAGE \"removeip <ipaddress> [removeAll]\\n\"\n\t\tS_USAGE_INDENT \"removeip <ipaddress/CIDR> [removeAll]\\n\"\n\t\t\"Use removeAll to delete all ip filters which ipaddress or ipaddress/CIDR includes\\n\");\n}\n\nstatic void SV_ListIP_PrintUsage( void )\n{\n\tCon_Printf(S_USAGE \"listip [ipaddress]\\n\"\n\t\tS_USAGE_INDENT  \"listip [ipaddress/CIDR]\\n\");\n}\n\nstatic void SV_AddIP_f( void )\n{\n\tconst char *szMinutes = Cmd_Argv( 1 );\n\tconst char *adr = Cmd_Argv( 2 );\n\tipfilter_t filter, *newfilter;\n\tfloat minutes;\n\tint i;\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\t// a1ba: kudos to rehlds for an idea of using CIDR prefixes\n\t\t// in these commands :)\n\t\tSV_AddIP_PrintUsage();\n\t\treturn;\n\t}\n\n\tminutes = Q_atof( szMinutes );\n\tif( minutes < 0.1f )\n\t\tminutes = 0;\n\n\tif( minutes != 0.0f )\n\t\tfilter.endTime = host.realtime + minutes * 60;\n\telse filter.endTime = 0;\n\n\tif( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ))\n\t{\n\t\tCon_Printf( \"Invalid IP address!\\n\" );\n\t\tSV_AddIP_PrintUsage();\n\t\treturn;\n\t}\n\n\tnewfilter = Mem_Malloc( host.mempool, sizeof( *newfilter ));\n\tnewfilter->endTime = filter.endTime;\n\tnewfilter->adr = filter.adr;\n\tnewfilter->prefixlen = filter.prefixlen;\n\tnewfilter->next = ipfilter;\n\n\tipfilter = newfilter;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tnetadr_t clientadr = svs.clients[i].netchan.remote_address;\n\n\t\tif( !NET_CompareAdrByMask( clientadr, filter.adr, filter.prefixlen ))\n\t\t\tcontinue;\n\n\t\tSV_ClientPrintf( &svs.clients[i], \"The server operator has added you to banned list\\n\" );\n\t\tSV_DropClient( &svs.clients[i], false );\n\t}\n}\n\nstatic void SV_ListIP_f( void )\n{\n\tqboolean haveFilter = false;\n\tipfilter_t filter, *f;\n\n\tif( Cmd_Argc() > 2 )\n\t{\n\t\tSV_ListIP_PrintUsage();\n\t\treturn;\n\t}\n\n\tif( ipfilter == NULL )\n\t{\n\t\tCon_Printf( \"IP filter list is empty\\n\" );\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() == 2 )\n\t{\n\t\thaveFilter = NET_StringToFilterAdr( Cmd_Argv( 1 ), &filter.adr, &filter.prefixlen );\n\n\t\tif( !haveFilter )\n\t\t{\n\t\t\t Con_Printf( \"Invalid IP address!\\n\" );\n\t\t\t SV_ListIP_PrintUsage();\n\t\t\t return;\n\t\t}\n\t}\n\n\tCon_Printf( \"IP filter list:\\n\" );\n\n\tfor( f = ipfilter; f; f = f->next )\n\t{\n\t\tstring filterStr;\n\n\t\tif( haveFilter && !SV_IPFilterIncludesIPFilter( &filter, f ))\n\t\t\tcontinue;\n\n\t\tSV_FilterToString( filterStr, sizeof( filterStr ), false, f );\n\t\tCon_Printf( \"%s\\n\", filterStr );\n\t}\n}\n\nstatic void SV_RemoveIP_f( void )\n{\n\tconst char *adr = Cmd_Argv( 1 );\n\tqboolean removeAll;\n\tipfilter_t filter;\n\tint i;\n\n\tif( Cmd_Argc() != 2 && Cmd_Argc() != 3 )\n\t{\n\t\tSV_RemoveIP_PrintUsage();\n\t\treturn;\n\t}\n\n\tremoveAll = Cmd_Argc() == 3 && !Q_strcmp( Cmd_Argv( 2 ), \"removeAll\" );\n\n\tif( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen ))\n\t{\n\t\tCon_Printf( \"Invalid IP address!\\n\" );\n\t\tSV_RemoveIP_PrintUsage();\n\t\treturn;\n\t}\n\n\tSV_RemoveIPFilter( &filter, removeAll, true );\n}\n\nstatic void SV_WriteIP_f( void )\n{\n\tfile_t *fd = FS_Open( Cvar_VariableString( \"listipcfgfile\" ), \"w\", true );\n\tipfilter_t *f;\n\n\tif( !fd )\n\t{\n\t\tCon_Printf( \"Couldn't open listip.cfg\\n\" );\n\t\treturn;\n\t}\n\n\tfor( f = ipfilter; f; f = f->next )\n\t{\n\t\tstring filterStr;\n\t\tint size;\n\n\t\t// do not save temporary bans\n\t\tif( f->endTime )\n\t\t\tcontinue;\n\n\t\tsize = SV_FilterToString( filterStr, sizeof( filterStr ), true, f );\n\t\tFS_Write( fd, filterStr, size );\n\t}\n\n\tFS_Close( fd );\n}\n\nstatic void SV_InitIPFilter( void )\n{\n\tCmd_AddRestrictedCommand( \"addip\", SV_AddIP_f, \"add entry to IP filter\" );\n\tCmd_AddRestrictedCommand( \"listip\", SV_ListIP_f, \"list current IP filter\" );\n\tCmd_AddRestrictedCommand( \"removeip\", SV_RemoveIP_f, \"remove IP filter\" );\n\tCmd_AddRestrictedCommand( \"writeip\", SV_WriteIP_f, \"write listip.cfg\" );\n}\n\nstatic void SV_ShutdownIPFilter( void )\n{\n\tipfilter_t *ipList, *ipNext;\n\n\t// should be called manually because banned.cfg is not executed by engine\n\t//SV_WriteIP_f();\n\n\tfor( ipList = ipfilter; ipList; ipList = ipNext )\n\t{\n\t\tipNext = ipList->next;\n\t\tMem_Free( ipList );\n\t}\n\n\tipfilter = NULL;\n}\n\nvoid SV_InitFilter( void )\n{\n\tSV_InitIPFilter();\n\tSV_InitIDFilter();\n}\n\nvoid SV_ShutdownFilter( void )\n{\n\tSV_ShutdownIPFilter();\n\tSV_ShutdownIDFilter();\n}\n\n#if XASH_ENGINE_TESTS\n\n#include \"tests.h\"\n\nstatic void Test_StringToFilterAdr( void )\n{\n\tipfilter_t f1;\n\tint i;\n\tstruct\n\t{\n\t\tconst char *str;\n\t\tqboolean valid;\n\t\tint prefixlen;\n\t\tint a, b, c, d;\n\t} ipv4tests[] =\n\t{\n\t{ \"127.0.0.0/8\", true, 8, 127, 0, 0, 0 },\n\t{ \"192.168\",     true, 16, 192, 168, 0, 0 },\n\t{ \"192.168/23\",  true, 23, 192, 168, 0, 0 },\n\t{ \"192.168./23\", true, 23, 192, 168, 0, 0 },\n\t{ \"192.168../23\", true, 23, 192, 168, 0, 0 },\n\t{ \"..192...168/23\", false },\n\t{ \"\", false },\n\t{ \"abcd\", false }\n\t};\n\tstruct\n\t{\n\t\tconst char *str;\n\t\tqboolean valid;\n\t\tint prefixlen;\n\t\tuint8_t x[16];\n\t} ipv6tests[] =\n\t{\n\t{ \"::1\", true, 128, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },\n\t{ \"fd18:b9d4:65cf:83de::/64\", true, 64, { 0xfd, 0x18, 0xb9, 0xd4, 0x65, 0xcf, 0x83, 0xde } },\n\t{ \"kkljnljkhfjnkj\", false },\n\t{ \"fd8a:63d5:e014:0d62:ffff:ffff:ffff:ffff:ffff\", false },\n\t};\n\n\tfor( i = 0; i < ARRAYSIZE( ipv4tests ); i++ )\n\t{\n\t\tqboolean ret = NET_StringToFilterAdr( ipv4tests[i].str, &f1.adr, &f1.prefixlen );\n\n\t\tTASSERT_EQi( ret, ipv4tests[i].valid );\n\n\t\tif( ret )\n\t\t{\n\t\t\tTASSERT_EQi( f1.prefixlen, ipv4tests[i].prefixlen );\n\t\t\tTASSERT_EQi( f1.adr.ip[0], ipv4tests[i].a );\n\t\t\tTASSERT_EQi( f1.adr.ip[1], ipv4tests[i].b );\n\t\t\tTASSERT_EQi( f1.adr.ip[2], ipv4tests[i].c );\n\t\t\tTASSERT_EQi( f1.adr.ip[3], ipv4tests[i].d );\n\t\t}\n\t}\n\n\tfor( i = 0; i < ARRAYSIZE( ipv6tests ); i++ )\n\t{\n\t\tqboolean ret = NET_StringToFilterAdr( ipv6tests[i].str, &f1.adr, &f1.prefixlen );\n\t\tuint8_t x[16];\n\n\t\tTASSERT_EQi( ret, ipv6tests[i].valid );\n\n\t\tif( ret )\n\t\t{\n\t\t\tTASSERT_EQi( f1.prefixlen, ipv6tests[i].prefixlen );\n\n\t\t\tNET_NetadrToIP6Bytes( (uint8_t*)x, &f1.adr );\n\n\t\t\tTASSERT( memcmp( x, ipv6tests[i].x, sizeof( x )) == 0 );\n\t\t}\n\t}\n}\n\nstatic void Test_IPFilterIncludesIPFilter( void )\n{\n\tqboolean ret;\n\tconst char *adrs[] =\n\t{\n\t\t\"127.0.0.1/8\", // 0\n\t\t\"127.0.0.1\", // 1\n\t\t\"192.168/16\", // 2\n\t\t\"fe80::/64\", // 3\n\t\t\"fe80::96ab:9a49:2944:1808\", // 4\n\t\t\"2a00:1370:8190:f9eb::/62\", // 5\n\t\t\"2a00:1370:8190:f9eb:3866:6126:330c:b82b\" // 6\n\t};\n\tipfilter_t f[7];\n\tint i;\n\tint tests[][3] =\n\t{\n\t\t// ipv4\n\t\t{ 0, 0, true },\n\t\t{ 0, 1, false },\n\t\t{ 1, 0, true },\n\t\t{ 0, 2, false },\n\t\t{ 2, 0, false },\n\n\t\t// mixed\n\t\t{ 0, 3, false },\n\t\t{ 1, 4, false },\n\n\t\t// ipv6\n\t\t{ 3, 3, true },\n\t\t{ 3, 4, false },\n\t\t{ 4, 3, true },\n\t\t{ 5, 3, false },\n\t\t{ 3, 5, false },\n\t\t{ 6, 5, true },\n\t};\n\n\tfor( i = 0; i < 7; i++ )\n\t{\n\t\tNET_StringToFilterAdr( adrs[i], &f[i].adr, &f[i].prefixlen );\n\t}\n\n\tfor( i = 0; i < ARRAYSIZE( tests ); i++ )\n\t{\n\t\tret = SV_IPFilterIncludesIPFilter( &f[tests[i][0]], &f[tests[i][1]] );\n\n\t\tTASSERT_EQi( ret, tests[i][2] );\n\t}\n}\n\nvoid Test_RunIPFilter( void )\n{\n\tTest_StringToFilterAdr();\n\tTest_IPFilterIncludesIPFilter();\n}\n\n#endif // XASH_ENGINE_TESTS\n"
  },
  {
    "path": "engine/server/sv_frame.c",
    "content": "/*\nsv_frame.c - server world snapshot\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"const.h\"\n#include \"net_encode.h\"\n\ntypedef struct\n{\n\tint\t\tnum_entities;\n\tentity_state_t\tentities[MAX_VISIBLE_PACKET];\n\tbyte\t\tsended[MAX_EDICTS_BYTES];\n} sv_ents_t;\n\nstatic int\tc_fullsend;\t// just a debug counter\nstatic int\tc_notsend;\n\n/*\n=======================\nSV_EntityNumbers\n=======================\n*/\nstatic int SV_EntityNumbers( const void *a, const void *b )\n{\n\tint\tent1, ent2;\n\n\tent1 = ((entity_state_t *)a)->number;\n\tent2 = ((entity_state_t *)b)->number;\n\n\t// watcom libc compares ents with itself\n\tif( ent1 == ent2 )\n\t\treturn 0;\n\n\tif( ent1 < ent2 )\n\t\treturn -1;\n\treturn 1;\n}\n\n/*\n=============\nSV_AddEntitiesToPacket\n\n=============\n*/\nstatic void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_frame_t *frame, sv_ents_t *ents, qboolean from_client )\n{\n\tedict_t\t\t*ent;\n\tbyte\t\t*clientpvs;\n\tbyte\t\t*clientphs;\n\tqboolean\t\tfullvis = false;\n\tsv_client_t\t*cl = NULL;\n\tqboolean\t\tplayer;\n\tentity_state_t\t*state;\n\tint\t\te;\n\n\t// during an error shutdown message we may need to transmit\n\t// the shutdown message after the server has shutdown, so\n\t// specifically check for it\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\tcl = SV_ClientFromEdict( pClient, true );\n\n\tASSERT( cl != NULL );\n\n\t// portals can't change hostflags\n\tif( from_client )\n\t{\n\t\t// setup hostflags\n\t\tif( FBitSet( cl->flags, FCL_LOCAL_WEAPONS ))\n\t\t\tSetBits( sv.hostflags, SVF_SKIPLOCALHOST );\n\t\telse ClearBits( sv.hostflags, SVF_SKIPLOCALHOST );\n\n\t\t// reset viewents each frame\n\t\tcl->num_viewents = 0;\n\t}\n\n\tsvgame.dllFuncs.pfnSetupVisibility( pViewEnt, pClient, &clientpvs, &clientphs );\n\tif( !clientpvs ) fullvis = true;\n\n\t// g-cont: of course we can send world but not want to do it :-)\n\tfor( e = 1; e < svgame.numEntities; e++ )\n\t{\n\t\tbyte\t*pset;\n\n\t\tent = EDICT_NUM( e );\n\n\t\t// don't double add an entity through portals (in case this already added)\n\t\tif( CHECKVISBIT( ents->sended, e ))\n\t\t\tcontinue;\n\n\t\tif( e >= 1 && e <= svs.maxclients )\n\t\t\tplayer = 1;\n\t\telse player = 0;\n\n\t\tif( player )\n\t\t{\n\t\t\tsv_client_t *cl = &svs.clients[e - 1];\n\n\t\t\tif( cl->state != cs_spawned )\n\t\t\t\tcontinue;\n\n\t\t\tif( FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( FBitSet( ent->v.effects, EF_REQUEST_PHS ))\n\t\t\tpset = clientphs;\n\t\telse pset = clientpvs;\n\n\t\tstate = &ents->entities[ents->num_entities];\n\n\t\t// add entity to the net packet\n\t\tif( svgame.dllFuncs.pfnAddToFullPack( state, e, ent, pClient, sv.hostflags, player, pset ))\n\t\t{\n\t\t\t// to prevent adds it twice through portals\n\t\t\tSETVISBIT( ents->sended, e );\n\n\t\t\tif( SV_IsValidEdict( ent->v.aiment ) && FBitSet( ent->v.aiment->v.effects, EF_MERGE_VISIBILITY ))\n\t\t\t{\n\t\t\t\tif( cl->num_viewents < MAX_VIEWENTS )\n\t\t\t\t{\n\t\t\t\t\tcl->viewentity[cl->num_viewents] = ent->v.aiment;\n\t\t\t\t\tcl->num_viewents++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if we are full, silently discard entities\n\t\t\tif( ents->num_entities < ( MAX_VISIBLE_PACKET - 1 ))\n\t\t\t{\n\t\t\t\tents->num_entities++;\t// entity accepted\n\t\t\t\tc_fullsend++;\t\t// debug counter\n\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// visibility list is full\n\t\t\t\t// continue counting entities,\n\t\t\t\t// so we know how many it's ovreflowed\n\t\t\t\tc_notsend++;\n\t\t\t}\n\t\t}\n\n\t\tif( fullvis ) continue; // portal ents will be added anyway, ignore recursion\n\n\t\t// if it's a portal entity, add everything visible from its camera position\n\t\tif( from_client && FBitSet( ent->v.effects, EF_MERGE_VISIBILITY ))\n\t\t{\n\t\t\tSetBits( sv.hostflags, SVF_MERGE_VISIBILITY );\n\t\t\tSV_AddEntitiesToPacket( ent, pClient, frame, ents, false );\n\t\t\tClearBits( sv.hostflags, SVF_MERGE_VISIBILITY );\n\t\t}\n\t}\n}\n\n/*\n=============================================================================\n\nEncode a client frame onto the network channel\n\n=============================================================================\n*/\n/*\n=============\nSV_FindBestBaseline\n\ntrying to deltas with previous entities\nset frame to NULL to check for static entities\n=============\n*/\nint SV_FindBestBaseline( int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player )\n{\n\tint\tbestBitCount;\n\tint\ti, bitCount;\n\tint\tbestfound, j;\n\n\tbestBitCount = j = Delta_TestBaseline( *baseline, to, player, sv.time );\n\tbestfound = index;\n\n\t// lookup backward for previous 64 states and try to interpret current delta as baseline\n\tfor( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- )\n\t{\n\t\t// don't worry about underflow in circular buffer\n\t\tentity_state_t *test;\n\n\t\t// if set, then it's normal entity\n\t\tif( frame != NULL )\n\t\t\ttest = &svs.packet_entities[(frame->first_entity+i) % svs.num_client_entities];\n\t\telse\n\t\t\ttest = &svs.static_entities[i];\n\n\t\tif( to->entityType == test->entityType )\n\t\t{\n\t\t\tbitCount = Delta_TestBaseline( test, to, player, sv.time );\n\n\t\t\tif( bitCount < bestBitCount )\n\t\t\t{\n\t\t\t\tbestBitCount = bitCount;\n\t\t\t\tbestfound = i;\n\t\t\t}\n\t\t}\n\t}\n\n\t// using delta from previous entity as baseline for current\n\tif( index != bestfound )\n\t{\n\t\tif( frame != NULL )\n\t\t\t*baseline = &svs.packet_entities[(frame->first_entity+bestfound) % svs.num_client_entities];\n\t\telse\n\t\t\t*baseline = &svs.static_entities[bestfound];\n\t}\n\treturn index - bestfound;\n}\n\n/*\n=============\nSV_EmitPacketEntities\n\nWrites a delta update of an entity_state_t list to the message->\n=============\n*/\nstatic void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg )\n{\n\tentity_state_t\t*oldent, *newent;\n\tint\t\toldindex, newindex;\n\tint\t\ti, oldnum, newnum;\n\tqboolean\t\tplayer;\n\tint\t\toldmax;\n\tclient_frame_t\t*from;\n\n\t// this is the frame that we are going to delta update from\n\tif( cl->delta_sequence != -1 )\n\t{\n\t\tfrom = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK];\n\t\toldmax = from->num_entities;\n\n\t\t// the snapshot's entities may still have rolled off the buffer, though\n\t\tif( from->first_entity <= ( svs.next_client_entities - svs.num_client_entities ))\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"%s: delta request from out of date entities.\\n\", cl->name );\n\t\t\tMSG_BeginServerCmd( msg, svc_packetentities );\n\t\t\tMSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );\n\n\t\t\tfrom = NULL;\n\t\t\toldmax = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMSG_BeginServerCmd( msg, svc_deltapacketentities );\n\t\t\tMSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );\n\t\t\tMSG_WriteByte( msg, cl->delta_sequence );\n\t\t}\n\t}\n\telse\n\t{\n\t\tfrom = NULL;\n\t\toldmax = 0;\n\n\t\tMSG_BeginServerCmd( msg, svc_packetentities );\n\t\tMSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS );\n\t}\n\n\tnewent = NULL;\n\toldent = NULL;\n\tnewindex = 0;\n\toldindex = 0;\n\n\twhile( newindex < to->num_entities || oldindex < oldmax )\n\t{\n\t\tif( newindex >= to->num_entities )\n\t\t{\n\t\t\tnewnum = MAX_ENTNUMBER;\n\t\t\tplayer = false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnewent = &svs.packet_entities[(to->first_entity+newindex) % svs.num_client_entities];\n\t\t\tplayer = SV_IsPlayerIndex( newent->number );\n\t\t\tnewnum = newent->number;\n\t\t}\n\n\t\tif( oldindex >= oldmax )\n\t\t{\n\t\t\toldnum = MAX_ENTNUMBER;\n\t\t}\n\t\telse\n\t\t{\n\t\t\toldent = &svs.packet_entities[(from->first_entity+oldindex) % svs.num_client_entities];\n\t\t\toldnum = oldent->number;\n\t\t}\n\n\t\tif( newnum == oldnum )\n\t\t{\n\t\t\t// delta update from old position\n\t\t\t// because the force parm is false, this will not result\n\t\t\t// in any bytes being emited if the entity has not changed at all\n\t\t\tMSG_WriteDeltaEntity( oldent, newent, msg, false, player, sv.time, 0 );\n\t\t\toldindex++;\n\t\t\tnewindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( newnum < oldnum )\n\t\t{\n\t\t\tentity_state_t\t*baseline = &svs.baselines[newnum];\n\t\t\tconst char\t*classname = SV_ClassName( EDICT_NUM( newnum ));\n\t\t\tint\t\toffset = 0;\n\n\t\t\t// trying to reduce message by select optimal baseline\n\t\t\tif( !sv_instancedbaseline.value || !sv.num_instanced || sv.last_valid_baseline > newnum )\n\t\t\t{\n\t\t\t\toffset = SV_FindBestBaseline( newindex, &baseline, newent, to, player );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor( i = 0; i < sv.num_instanced; i++ )\n\t\t\t\t{\n\t\t\t\t\tif( !Q_strcmp( classname, sv.instanced[i].classname ))\n\t\t\t\t\t{\n\t\t\t\t\t\tbaseline = &sv.instanced[i].baseline;\n\t\t\t\t\t\toffset = -i - 1; // to avoid zero offset\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// this is a new entity, send it from the baseline\n\t\t\tMSG_WriteDeltaEntity( baseline, newent, msg, true, player, sv.time, offset );\n\t\t\tnewindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( newnum > oldnum )\n\t\t{\n\t\t\tedict_t\t*ed = EDICT_NUM( oldent->number );\n\t\t\tqboolean\tforce = false;\n\n\t\t\t// check if entity completely removed from server\n\t\t\tif( ed->free || FBitSet( ed->v.flags, FL_KILLME ))\n\t\t\t\tforce = true;\n\n\t\t\t// remove from message\n\t\t\tMSG_WriteDeltaEntity( oldent, NULL, msg, force, false, sv.time, 0 );\n\t\t\toldindex++;\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tMSG_WriteUBitLong( msg, LAST_EDICT, MAX_ENTITY_BITS ); // end of packetentities\n}\n\n/*\n=============\nSV_EmitEvents\n\n=============\n*/\nstatic void SV_EmitEvents( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg )\n{\n\tevent_state_t\t*es;\n\tevent_info_t\t*info;\n\tentity_state_t\t*state;\n\tevent_args_t\tnullargs;\n\tint\t\tev_count = 0;\n\tint\t\tcount, ent_index;\n\tint\t\ti, j, ev;\n\n\tmemset( &nullargs, 0, sizeof( nullargs ));\n\tes = &cl->events;\n\n\t// count events\n\tfor( ev = 0; ev < MAX_EVENT_QUEUE; ev++ )\n\t{\n\t\tif( es->ei[ev].index )\n\t\t\tev_count++;\n\t}\n\n\t// nothing to send\n\tif( !ev_count ) return; // nothing to send\n\n\tif ( ev_count >= MAX_EVENT_QUEUE / 2 )\n\t\tev_count = ( MAX_EVENT_QUEUE / 2 ) - 1;\n\n\tfor( i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tinfo = &es->ei[i];\n\t\tif( info->index == 0 )\n\t\t\tcontinue;\n\n\t\tent_index = info->entity_index;\n\n\t\tfor( j = 0; j < to->num_entities; j++ )\n\t\t{\n\t\t\tstate = &svs.packet_entities[(to->first_entity+j) % svs.num_client_entities];\n\t\t\tif( state->number == ent_index )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( j < to->num_entities )\n\t\t{\n\t\t\tinfo->packet_index = j;\n\t\t\tinfo->args.ducking = 0;\n\n\t\t\tif( !FBitSet( info->args.flags, FEVENT_ORIGIN ))\n\t\t\t\tVectorClear( info->args.origin );\n\n\t\t\tif( !FBitSet( info->args.flags, FEVENT_ANGLES ))\n\t\t\t\tVectorClear( info->args.angles );\n\n\t\t\tVectorClear( info->args.velocity );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// couldn't find\n\t\t\tinfo->packet_index = to->num_entities;\n\t\t\tinfo->args.entindex = ent_index;\n\t\t}\n\t}\n\n\tMSG_BeginServerCmd( msg, svc_event );\t// create message\n\tMSG_WriteUBitLong( msg, ev_count, 5 );\t// up to MAX_EVENT_QUEUE events\n\n\tfor( count = i = 0; i < MAX_EVENT_QUEUE; i++ )\n\t{\n\t\tinfo = &es->ei[i];\n\n\t\tif( info->index == 0 )\n\t\t{\n\t\t\tinfo->packet_index = -1;\n\t\t\tinfo->entity_index = -1;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// only send if there's room\n\t\tif( count < ev_count )\n\t\t{\n\t\t\tMSG_WriteUBitLong( msg, info->index, MAX_EVENT_BITS ); // 1024 events\n\n\t\t\tif( info->packet_index == -1 )\n\t\t\t{\n\t\t\t\tMSG_WriteOneBit( msg, 0 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMSG_WriteOneBit( msg, 1 );\n\t\t\t\tMSG_WriteUBitLong( msg, info->packet_index, MAX_ENTITY_BITS );\n\n\t\t\t\tif( !memcmp( &nullargs, &info->args, sizeof( event_args_t )))\n\t\t\t\t{\n\t\t\t\t\tMSG_WriteOneBit( msg, 0 );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMSG_WriteOneBit( msg, 1 );\n\t\t\t\t\tMSG_WriteDeltaEvent( msg, &nullargs, &info->args );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( info->fire_time )\n\t\t\t{\n\t\t\t\tMSG_WriteOneBit( msg, 1 );\n\t\t\t\tMSG_WriteWord( msg, ( info->fire_time * 100.0f ));\n\t\t\t}\n\t\t\telse MSG_WriteOneBit( msg, 0 );\n\t\t}\n\n\t\tinfo->index = 0;\n\t\tinfo->packet_index = -1;\n\t\tinfo->entity_index = -1;\n\t\tcount++;\n\t}\n}\n\n/*\n=============\nSV_EmitPings\n\n=============\n*/\nstatic void SV_EmitPings( sizebuf_t *msg )\n{\n\tsv_client_t *cl;\n\tint i;\n\n\tMSG_BeginServerCmd( msg, svc_pings );\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tint packet_loss, ping;\n\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tSV_GetPlayerStats( cl, &ping, &packet_loss );\n\n\t\t// there are 25 bits for each client\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS );\n\t\tMSG_WriteUBitLong( msg, ping, 12 );\n\t\tMSG_WriteUBitLong( msg, packet_loss, 7 );\n\t}\n\n\t// end marker\n\tMSG_WriteOneBit( msg, 0 );\n}\n\n/*\n==================\nSV_WriteClientdataToMessage\n\n==================\n*/\nstatic void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg )\n{\n\tclientdata_t\tnullcd;\n\tclientdata_t\t*from_cd, *to_cd;\n\tweapon_data_t\tnullwd;\n\tweapon_data_t\t*from_wd, *to_wd;\n\tclient_frame_t\t*frame;\n\tedict_t\t\t*clent;\n\tint\t\ti;\n\n\tmemset( &nullcd, 0, sizeof( nullcd ));\n\tframe = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK];\n\tframe->senttime = host.realtime;\n\tframe->ping_time = -1.0f;\n\tclent = cl->edict;\n\n\tif( cl->chokecount != 0 )\n\t{\n\t\tMSG_BeginServerCmd( msg, svc_choke );\n\t\tcl->chokecount = 0;\n\t}\n\n\t// update client fixangle\n\tswitch( clent->v.fixangle )\n\t{\n\tcase 1:\n\t\tMSG_BeginServerCmd( msg, svc_setangle );\n\t\tMSG_WriteVec3Angles( msg, clent->v.angles );\n\t\tbreak;\n\tcase 2:\n\t\tMSG_BeginServerCmd( msg, svc_addangle );\n\t\tMSG_WriteBitAngle( msg, clent->v.avelocity[YAW], 16 );\n\t\tclent->v.avelocity[YAW] = 0.0f;\n\t\tbreak;\n\t}\n\n\tclent->v.fixangle = 0; // reset fixangle\n\n\tmemset( &frame->clientdata, 0, sizeof( frame->clientdata ));\n\n\t// update clientdata_t\n\tsvgame.dllFuncs.pfnUpdateClientData( clent, FBitSet( cl->flags, FCL_LOCAL_WEAPONS ), &frame->clientdata );\n\n\tMSG_BeginServerCmd( msg, svc_clientdata );\n\tif( FBitSet( cl->flags, FCL_HLTV_PROXY )) return;\t// don't send more nothing\n\n\tif( cl->delta_sequence == -1 ) from_cd = &nullcd;\n\telse from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata;\n\tto_cd = &frame->clientdata;\n\n\tif( cl->delta_sequence == -1 )\n\t{\n\t\tMSG_WriteOneBit( msg, 0 );\t// no delta-compression\n\t}\n\telse\n\t{\n\t\tMSG_WriteOneBit( msg, 1 );\t// we are delta-ing from\n\t\tMSG_WriteByte( msg, cl->delta_sequence );\n\t}\n\n\t// write clientdata_t\n\tMSG_WriteClientData( msg, from_cd, to_cd, sv.time );\n\n\tif( FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata ))\n\t{\n\t\tmemset( &nullwd, 0, sizeof( nullwd ));\n\n\t\tfor( i = 0; i < MAX_LOCAL_WEAPONS; i++ )\n\t\t{\n\t\t\tif( cl->delta_sequence == -1 ) from_wd = &nullwd;\n\t\t\telse from_wd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].weapondata[i];\n\t\t\tto_wd = &frame->weapondata[i];\n\n\t\t\tMSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i );\n\t\t}\n\t}\n\n\t// end marker\n\tMSG_WriteOneBit( msg, 0 );\n}\n\n/*\n==================\nSV_WriteEntitiesToClient\n\n==================\n*/\nstatic void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg )\n{\n\tclient_frame_t\t*frame;\n\tentity_state_t\t*state;\n\tstatic sv_ents_t\tframe_ents;\n\tint\t\ti, send_pings;\n\n\tframe = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK];\n\tsend_pings = SV_ShouldUpdatePing( cl );\n\n\tmemset( frame_ents.sended, 0, sizeof( frame_ents.sended ));\n\tClearBits( sv.hostflags, SVF_MERGE_VISIBILITY );\n\n\t// clear everything in this snapshot\n\tframe_ents.num_entities = c_fullsend = c_notsend = 0;\n\n\t// add all the entities directly visible to the eye, which\n\t// may include portal entities that merge other viewpoints\n\tSV_AddEntitiesToPacket( cl->pViewEntity, cl->edict, frame, &frame_ents, true );\n\n\tif( c_notsend != cl->ignored_ents )\n\t{\n\t\tif( c_notsend > 0 )\n\t\t\tCon_Printf( S_ERROR \"Too many entities in visible packet list. Ignored %d entities\\n\", c_notsend );\n\t\tcl->ignored_ents = c_notsend;\n\t}\n\n\t// if there were portals visible, there may be out of order entities\n\t// in the list which will need to be resorted for the delta compression\n\t// to work correctly.  This also catches the error condition\n\t// of an entity being included twice.\n\tqsort( frame_ents.entities, frame_ents.num_entities, sizeof( frame_ents.entities[0] ), SV_EntityNumbers );\n\n\t// it will break all connected clients, but it takes more than one week to overflow it\n\tif(( (uint)svs.next_client_entities ) + frame_ents.num_entities >= 0x7FFFFFFE )\n\t{\n\t\tsvs.next_client_entities = 0;\n\n\t\t// delta is broken for now, cannot keep connected clients\n\t\tSV_FinalMessage( \"Server will restart due delta is outdated\\n\", true );\n\t}\n\n\t// copy the entity states out\n\tframe->first_entity = svs.next_client_entities;\n\tframe->num_entities = 0;\n\n\tfor( i = 0; i < frame_ents.num_entities; i++ )\n\t{\n\t\t// add it to the circular packet_entities array\n\t\tstate = &svs.packet_entities[svs.next_client_entities % svs.num_client_entities];\n\t\t*state = frame_ents.entities[i];\n\t\tsvs.next_client_entities++;\n\t\tframe->num_entities++;\n\t}\n\n\tSV_EmitPacketEntities( cl, frame, msg );\n\tSV_EmitEvents( cl, frame, msg );\n\tif( send_pings ) SV_EmitPings( msg );\n}\n\n/*\n===============================================================================\n\nFRAME UPDATES\n\n===============================================================================\n*/\n/*\n=======================\nSV_SendClientDatagram\n=======================\n*/\nstatic void SV_SendClientDatagram( sv_client_t *cl )\n{\n\tbyte\tmsg_buf[MAX_DATAGRAM];\n\tsizebuf_t\tmsg;\n\n\tmemset( msg_buf, 0, sizeof( msg_buf ));\n\tMSG_Init( &msg, \"Datagram\", msg_buf, sizeof( msg_buf ));\n\n\t// always send servertime at new frame\n\tMSG_BeginServerCmd( &msg, svc_time );\n\tMSG_WriteFloat( &msg, sv.time );\n\n\tSV_WriteClientdataToMessage( cl, &msg );\n\tSV_WriteEntitiesToClient( cl, &msg );\n\n\t// copy the accumulated multicast datagram\n\t// for this client out to the message\n\tif( MSG_CheckOverflow( &cl->datagram ))\n\t{\n\t\tCon_Printf( S_WARN \"%s overflowed for %s\\n\", MSG_GetName( &cl->datagram ), cl->name );\n\t}\n\telse\n\t{\n\t\tif( MSG_GetNumBytesWritten( &cl->datagram ) < MSG_GetNumBytesLeft( &msg ))\n\t\t\tMSG_WriteBits( &msg, MSG_GetData( &cl->datagram ), MSG_GetNumBitsWritten( &cl->datagram ));\n\t\telse if( host.realtime > cl->overflow_warn_time )\n\t\t{\n\t\t\tCon_DPrintf( S_WARN \"Ignoring unreliable datagram for %s, would overflow on msg\\n\", cl->name );\n\t\t\tcl->overflow_warn_time = host.realtime + 5.0f;\n\t\t}\n\t}\n\n\tMSG_Clear( &cl->datagram );\n\n\tif( MSG_CheckOverflow( &msg ))\n\t{\n\t\t// must have room left for the packet header\n\t\tCon_Printf( S_ERROR \"%s overflowed for %s\\n\", MSG_GetName( &msg ), cl->name );\n\t\tMSG_Clear( &msg );\n\t}\n\n\t// send the datagram\n\tNetchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg ));\n}\n\n/*\n=======================\nSV_UpdateUserInfo\n=======================\n*/\nstatic void SV_UpdateUserInfo( sv_client_t *cl )\n{\n\tSV_FullClientUpdate( cl, &sv.reliable_datagram );\n\tClearBits( cl->flags, FCL_RESEND_USERINFO );\n\tcl->next_sendinfotime = host.realtime + 1.0;\n}\n\n/*\n=======================\nSV_UpdateToReliableMessages\n=======================\n*/\nstatic void SV_UpdateToReliableMessages( void )\n{\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\t// check for changes to be sent over the reliable streams to all clients\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( !cl->edict ) continue;\t// not in game yet\n\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( cl->flags, FCL_RESEND_USERINFO ) && cl->next_sendinfotime <= host.realtime )\n\t\t{\n\t\t\tif( MSG_GetNumBytesLeft( &sv.reliable_datagram ) >= ( Q_strlen( cl->userinfo ) + 6 ))\n\t\t\t\tSV_UpdateUserInfo( cl );\n\t\t}\n\n\t\tif( FBitSet( cl->flags, FCL_RESEND_MOVEVARS ))\n\t\t{\n\t\t\tSV_FullUpdateMovevars( cl, &cl->netchan.message );\n\t\t\tClearBits( cl->flags, FCL_RESEND_MOVEVARS );\n\t\t}\n\t}\n\n\t// clear the server datagram if it overflowed.\n\tif( MSG_CheckOverflow( &sv.datagram ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"sv.datagram overflowed!\\n\" );\n\t\tMSG_Clear( &sv.datagram );\n\t}\n\n\t// clear the server datagram if it overflowed.\n\tif( MSG_CheckOverflow( &sv.spec_datagram ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"sv.spec_datagram overflowed!\\n\" );\n\t\tMSG_Clear( &sv.spec_datagram );\n\t}\n\n\t// now send the reliable and server datagrams to all clients.\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state < cs_connected || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\t// reliables go to all connected or spawned\n\n\t\tif( MSG_GetNumBytesWritten( &sv.reliable_datagram ) < MSG_GetNumBytesLeft( &cl->netchan.message ))\n\t\t\tMSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.reliable_datagram ), MSG_GetNumBitsWritten( &sv.reliable_datagram ));\n\t\telse Netchan_CreateFragments( &cl->netchan, &sv.reliable_datagram );\n\n\t\tif( MSG_GetNumBytesWritten( &sv.datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))\n\t\t\tMSG_WriteBits( &cl->datagram, MSG_GetData( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram ));\n\t\telse Con_DPrintf( S_WARN \"Ignoring unreliable datagram for %s, would overflow\\n\", cl->name );\n\n\t\tif( FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t\t{\n\t\t\tif( MSG_GetNumBytesWritten( &sv.spec_datagram ) < MSG_GetNumBytesLeft( &cl->datagram ))\n\t\t\t\tMSG_WriteBits( &cl->datagram, MSG_GetData( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram ));\n\t\t\telse Con_DPrintf( S_WARN \"Ignoring spectator datagram for %s, would overflow\\n\", cl->name );\n\t\t}\n\t}\n\n\t// now clear the reliable and datagram buffers.\n\tMSG_Clear( &sv.reliable_datagram );\n\tMSG_Clear( &sv.spec_datagram );\n\tMSG_Clear( &sv.datagram );\n}\n\n/*\n=======================\nSV_SendClientMessages\n=======================\n*/\nvoid SV_SendClientMessages( void )\n{\n\tsv_client_t *cl;\n\tint          i;\n\tdouble       updaterate_time;\n\tdouble       time_until_next_message;\n\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\tSV_UpdateToReliableMessages ();\n\n\t// send a message to each connected client\n\tfor( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ )\n\t{\n\t\tcl = sv.current_client;\n\n\t\tif( cl->state <= cs_zombie || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tif( FBitSet( cl->flags, FCL_SKIP_NET_MESSAGE ))\n\t\t{\n\t\t\tClearBits( cl->flags, FCL_SKIP_NET_MESSAGE );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( !host_limitlocal.value && NET_IsLocalAddress( cl->netchan.remote_address ))\n\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\n\t\tif( cl->state == cs_spawned )\n\t\t{\n\t\t\t// Try to send a message as soon as we can.\n\t\t\t// If the target time for sending is within the next frame interval ( based on last frame ),\n\t\t\t// trigger the send now. Note that in single player,\n\t\t\t// FCL_SEND_NET_MESSAGE flag is also set any time a packet arrives from the client.\n\t\t\ttime_until_next_message = cl->next_messagetime - ( host.realtime + sv.frametime );\n\t\t\tif( time_until_next_message <= 0.0 )\n\t\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\t\t\telse if( time_until_next_message > 2.0 ) // something got hosed\n\t\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\t\t}\n\n\t\t// if the reliable message overflowed, drop the client\n\t\tif( MSG_CheckOverflow( &cl->netchan.message ))\n\t\t{\n\t\t\tMSG_Clear( &cl->netchan.message );\n\t\t\tMSG_Clear( &cl->datagram );\n\t\t\tSV_BroadcastPrintf( NULL, \"%s overflowed\\n\", cl->name );\n\t\t\tCon_DPrintf( S_ERROR \"reliable overflow for %s\\n\", cl->name );\n\t\t\tSV_DropClient( cl, false );\n\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\t\t\tcl->netchan.cleartime = 0.0;\t// don't choke this message\n\t\t}\n\t\telse if( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE ))\n\t\t{\n\t\t\t// If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client\n\t\t\t// until we get another packet in from the client. This prevents crash/drop and reconnect where they are\n\t\t\t// being hosed with \"sequenced packet without connection\" packets.\n\t\t\tif( sv_failuretime.value < ( host.realtime - cl->netchan.last_received ))\n\t\t\t\tClearBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\t\t}\n\n\t\t// only send messages if the client has sent one\n\t\t// and the bandwidth is not choked\n\t\tif( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE ))\n\t\t{\n\t\t\t// bandwidth choke active?\n\t\t\tif( !Netchan_CanPacket( &cl->netchan, cl->state == cs_spawned ))\n\t\t\t{\n\t\t\t\tcl->chokecount++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// now that we were able to send, reset timer to point to next possible send time.\n\t\t\t// check here also because sv_max/minupdaterate could been changed in runtime\n\t\t\tupdaterate_time = bound( 1.0 / sv_maxupdaterate.value, cl->cl_updaterate, 1.0 / sv_minupdaterate.value );\n\t\t\tcl->next_messagetime = host.realtime + sv.frametime + updaterate_time;\n\t\t\tClearBits( cl->flags, FCL_SEND_NET_MESSAGE );\n\n\t\t\t// NOTE: we should send frame even if server is not simulated to prevent overflow\n\t\t\tif( cl->state == cs_spawned )\n\t\t\t\tSV_SendClientDatagram( cl );\n\t\t\telse Netchan_TransmitBits( &cl->netchan, 0, NULL ); // just update reliable\n\t\t}\n\t}\n\n\t// reset current client\n\tsv.current_client = NULL;\n}\n\n/*\n=======================\nSV_SkipUpdates\n\nused before changing level\n=======================\n*/\nvoid SV_SkipUpdates( void )\n{\n\tsv_client_t\t*cl;\n\tint\t\ti;\n\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tSetBits( cl->flags, FCL_SKIP_NET_MESSAGE );\n\t}\n}\n\n/*\n=======================\nSV_InactivateClients\n\nPurpose: Prepare for level transition, etc.\n=======================\n*/\nvoid SV_InactivateClients( void )\n{\n\tint\t\ti;\n\tsv_client_t\t*cl;\n\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\t// send a message to each connected client\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( !cl->state || !cl->edict )\n\t\t\tcontinue;\n\n\t\tif( !cl->edict  )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( cl->edict->v.flags, FL_FAKECLIENT ))\n\t\t{\n\t\t\tSV_DropClient( cl, false );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( cl->state > cs_connected )\n\t\t{\n\t\t\tcl->state = cs_connected;\n\n\t\t\t// bump connect timeout\n\t\t\tcl->connection_started = host.realtime;\n\t\t}\n\n\t\tCOM_ClearCustomizationList( &cl->customdata, false );\n\t\tmemset( cl->physinfo, 0, MAX_PHYSINFO_STRING );\n\n\t\t// NOTE: many mods sending messages that must be applied on a next level\n\t\t// e.g. CryOfFear sending HideHud and PlayMp3 that affected after map change\n\t\tif( svgame.globals->changelevel )\n\t\t\tcontinue;\n\n\t\tMSG_Clear( &cl->netchan.message );\n\t\tMSG_Clear( &cl->datagram );\n\t}\n}\n"
  },
  {
    "path": "engine/server/sv_game.c",
    "content": "/*\nsv_game.c - gamedll interaction\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"net_encode.h\"\n#include \"event_flags.h\"\n#include \"library.h\"\n#include \"pm_defs.h\"\n#include \"studio.h\"\n#include \"const.h\"\n#include \"render_api.h\"\t// modelstate_t\n#include \"ref_common.h\" // decals\n\n// GameAPI functions declarations\nstatic int GAME_EXPORT pfnModelIndex( const char *m );\n\n// fatpvs stuff\nstatic byte fatphs[(MAX_MAP_LEAFS+7)/8];\nstatic byte clientpvs[(MAX_MAP_LEAFS+7)/8];\t// for find client in PVS\n\n// exports\ntypedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev );\ntypedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals );\n\n#ifndef NDEBUG\nqboolean SV_CheckEdict( const edict_t *e, const char *file, const int line )\n{\n\tint\tn;\n\n\tif( !e ) return false; // may be NULL\n\n\tn = ((int)((edict_t *)(e) - svgame.edicts));\n\n\tif(( n >= 0 ) && ( n < GI->max_edicts ))\n\t\treturn !e->free;\n\tCon_Printf( \"bad entity %i (called at %s:%i)\\n\", n, file, line );\n\n\treturn false;\n}\n#endif\n\nstatic edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities )\n{\n\tif( iEntIndex >= 0 && iEntIndex < GI->max_edicts )\n\t{\n\t\tedict_t *pEdict = EDICT_NUM( iEntIndex );\n\t\tqboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients;\n\n\t\tif( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\t\treturn pEdict; // just get access to array\n\n\t\tif( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData )\n\t\t\treturn pEdict;\n\n\t\t// g-cont: world and clients can be accessed even without private data\n\t\tif( SV_IsValidEdict( pEdict ) && player )\n\t\t\treturn pEdict;\n\t}\n\n\treturn NULL;\n}\n\n\n/*\n=============\nEntvarsDescription\n\nentavrs table for FindEntityByString\n=============\n*/\nstatic const TYPEDESCRIPTION gEntvarsDescription[] =\n{\n\tDEFINE_ENTITY_FIELD( classname, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( globalname, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( model, FIELD_MODELNAME ),\n\tDEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ),\n\tDEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ),\n\tDEFINE_ENTITY_FIELD( target, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( targetname, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( netname, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( message, FIELD_STRING ),\n\tDEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ),\n\tDEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ),\n\tDEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ),\n\tDEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ),\n};\n\n/*\n=============\nSV_SysError\n\ntell the game.dll about system error\n=============\n*/\nvoid SV_SysError( const char *error_string )\n{\n\tLog_Printf( \"FATAL ERROR (shutting down): %s\\n\", error_string );\n\n\tif( svgame.hInstance != NULL )\n\t\tsvgame.dllFuncs.pfnSys_Error( error_string );\n}\n\n/*\n=============\nSV_Serverinfo\n\nget server infostring\n=============\n*/\nchar *SV_Serverinfo( void )\n{\n\treturn svs.serverinfo;\n}\n\n/*\n=============\nSV_AngleMod\n\ndo modulo on entity angles\n=============\n*/\nstatic float SV_AngleMod( float ideal, float current, float speed )\n{\n\tfloat\tmove;\n\n\tcurrent = anglemod( current );\n\n\tif( current == ideal ) // already there?\n\t\treturn current;\n\n\tmove = ideal - current;\n\n\tif( ideal > current )\n\t{\n\t\tif( move >= 180 )\n\t\t\tmove = move - 360;\n\t}\n\telse\n\t{\n\t\tif( move <= -180 )\n\t\t\tmove = move + 360;\n\t}\n\n\tif( move > 0 )\n\t{\n\t\tif( move > speed )\n\t\t\tmove = speed;\n\t}\n\telse\n\t{\n\t\tif( move < -speed )\n\t\t\tmove = -speed;\n\t}\n\n\treturn anglemod( current + move );\n}\n\n/*\n=============\nSV_SetMinMaxSize\n\nupdate entity bounds, relink into world\n=============\n*/\nvoid SV_SetMinMaxSize( edict_t *e, const float *mins, const float *maxs, qboolean relink )\n{\n\tint\ti;\n\n\tif( !SV_IsValidEdict( e ))\n\t\treturn;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( mins[i] > maxs[i] )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s[%i] has backwards mins/maxs\\n\", SV_ClassName( e ), NUM_FOR_EDICT( e ));\n\t\t\tif( relink ) SV_LinkEdict( e, false ); // just relink edict and exit\n\t\t\treturn;\n\t\t}\n\t}\n\n\tVectorCopy( mins, e->v.mins );\n\tVectorCopy( maxs, e->v.maxs );\n\tVectorSubtract( maxs, mins, e->v.size );\n\tif( relink ) SV_LinkEdict( e, false );\n}\n\n/*\n=============\nSV_CopyTraceToGlobal\n\neach trace will share their result into global state\n=============\n*/\nvoid SV_CopyTraceToGlobal( trace_t *trace )\n{\n\tsvgame.globals->trace_allsolid = trace->allsolid;\n\tsvgame.globals->trace_startsolid = trace->startsolid;\n\tsvgame.globals->trace_fraction = trace->fraction;\n\tsvgame.globals->trace_plane_dist = trace->plane.dist;\n\tsvgame.globals->trace_inopen = trace->inopen;\n\tsvgame.globals->trace_inwater = trace->inwater;\n\tVectorCopy( trace->endpos, svgame.globals->trace_endpos );\n\tVectorCopy( trace->plane.normal, svgame.globals->trace_plane_normal );\n\tsvgame.globals->trace_hitgroup = trace->hitgroup;\n\tsvgame.globals->trace_flags = 0; // g-cont: always reset config flags when trace is finished\n\n\tif( SV_IsValidEdict( trace->ent ))\n\t\tsvgame.globals->trace_ent = trace->ent;\n\telse svgame.globals->trace_ent = svgame.edicts;\n}\n\n/*\n==============\nSV_SetModel\n==============\n*/\nvoid GAME_EXPORT SV_SetModel( edict_t *ent, const char *modelname )\n{\n\tchar\tname[MAX_QPATH];\n\tmodel_t\t*mod;\n\tint\ti = 1;\n\n\tif( !SV_IsValidEdict( ent ))\n\t{\n\t\tCon_Printf( S_WARN \"%s: invalid entity %s\\n\", __func__, SV_ClassName( ent ));\n\t\treturn;\n\t}\n\n\tif( !modelname || ((byte)modelname[0] ) <= ' ' )\n\t{\n\t\tCon_Printf( S_WARN \"%s: null name\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tif( *modelname == '\\\\' || *modelname == '/' )\n\t\tmodelname++;\n\n\tQ_strncpy( name, modelname, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\ti = SV_ModelIndex( name );\n\tif( i == 0 )\n\t{\n\t\tif( sv.state == ss_active )\n\t\t\tCon_Printf( S_ERROR \"%s: failed to set model %s: world model cannot be changed\\n\", __func__, name );\n\t\treturn;\n\t}\n\n\tif( COM_CheckString( name ))\n\t{\n\t\tent->v.model = MAKE_STRING( sv.model_precache[i] );\n\t\tent->v.modelindex = i;\n\t\tmod = sv.models[i];\n\t}\n\telse\n\t{\n\t\t// model will be cleared\n\t\tent->v.model = ent->v.modelindex = 0;\n\t\tmod = NULL;\n\t}\n\n\t// set the model size\n\tif( mod && mod->type != mod_studio )\n\t\tSV_SetMinMaxSize( ent, mod->mins, mod->maxs, true );\n\telse SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true );\n}\n\n/*\n=============\nSV_ConvertTrace\n\nconvert trace_t to TraceResult\n=============\n*/\nstatic void SV_ConvertTrace( TraceResult *dst, trace_t *src )\n{\n\tif( !src || !dst ) return;\n\n\tdst->fAllSolid = src->allsolid;\n\tdst->fStartSolid = src->startsolid;\n\tdst->fInOpen = src->inopen;\n\tdst->fInWater = src->inwater;\n\tdst->flFraction = src->fraction;\n\tVectorCopy( src->endpos, dst->vecEndPos );\n\tdst->flPlaneDist = src->plane.dist;\n\tVectorCopy( src->plane.normal, dst->vecPlaneNormal );\n\tdst->pHit = src->ent;\n\tdst->iHitgroup = src->hitgroup;\n\n\t// g-cont: always reset config flags when trace is finished\n\tsvgame.globals->trace_flags = 0;\n}\n\n/*\n=============\nSV_CheckClientVisiblity\n\nCheck visibility through client camera, portal camera, etc\n=============\n*/\nstatic qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask )\n{\n\tint\ti, clientnum;\n\tvec3_t\tvieworg;\n\tmleaf_t\t*leaf;\n\n\tif( !mask ) return true; // GoldSrc rules\n\n\tclientnum = cl - svs.clients;\n\n\t// Invasion issues: wrong camera position received in ENGINE_SET_PVS\n\tif( cl->pViewEntity )\n\t\tVectorCopy( cl->pViewEntity->v.origin, vieworg );\n\telse\n\t\tVectorCopy( cl->edict->v.origin, vieworg );\n\n\tleaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel );\n\n\tif( CHECKVISBIT( mask, leaf->cluster ))\n\t\treturn true; // visible from player view or camera view\n\n\t// now check all the portal cameras\n\tfor( i = 0; i < cl->num_viewents; i++ )\n\t{\n\t\tedict_t\t*view = cl->viewentity[i];\n\n\t\tif( !SV_IsValidEdict( view ))\n\t\t\tcontinue;\n\n\t\tVectorAdd( view->v.origin, view->v.view_ofs, vieworg );\n\t\tleaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel );\n\n\t\tif( CHECKVISBIT( mask, leaf->cluster ))\n\t\t\treturn true; // visible from portal camera view\n\t}\n\n\t// not visible from any viewpoint\n\treturn false;\n}\n\n/*\n=================\nSV_Multicast\n\nSends the contents of sv.multicast to a subset of the clients,\nthen clears sv.multicast.\n\nMSG_INIT\twrite message into signon buffer\nMSG_ONE\tsend to one client (ent can't be NULL)\nMSG_ALL\tsame as broadcast (origin can be NULL)\nMSG_PVS\tsend to clients potentially visible from org\nMSG_PHS\tsend to clients potentially audible from org\n=================\n*/\nstatic int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboolean usermessage, qboolean filter )\n{\n\tbyte\t\t*mask = NULL;\n\tint\t\tj, numclients = svs.maxclients;\n\tsv_client_t\t*cl, *current = svs.clients;\n\tqboolean\t\treliable = false;\n\tqboolean\t\tspecproxy = false;\n\tint\t\tnumsends = 0;\n\n\t// some mods trying to send messages after SV_FinalMessage\n\tif( !svs.initialized || sv.state == ss_dead )\n\t{\n\t\tMSG_Clear( &sv.multicast );\n\t\treturn 0;\n\t}\n\n\tswitch( dest )\n\t{\n\tcase MSG_INIT:\n\t\tif( sv.state == ss_loading )\n\t\t{\n\t\t\t// copy to signon buffer\n\t\t\tMSG_WriteBits( &sv.signon, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast ));\n\t\t\tMSG_Clear( &sv.multicast );\n\t\t\treturn 1;\n\t\t}\n\t\t// intentional fallthrough (in-game MSG_INIT it's a MSG_ALL reliable)\n\tcase MSG_ALL:\n\t\treliable = true;\n\t\t// intentional fallthrough\n\tcase MSG_BROADCAST:\n\t\t// nothing to sort\n\t\tbreak;\n\tcase MSG_PAS_R:\n\t\treliable = true;\n\t\t// intentional fallthrough\n\tcase MSG_PAS:\n\t\tif( origin == NULL ) return false;\n\t\t// NOTE: GoldSource not using PHS for singleplayer\n\t\tMod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 ), true );\n\t\tmask = fatphs; // using the FatPVS like a PHS\n\t\tbreak;\n\tcase MSG_PVS_R:\n\t\treliable = true;\n\t\t// intentional fallthrough\n\tcase MSG_PVS:\n\t\tif( origin == NULL ) return 0;\n\t\tmask = Mod_GetPVSForPoint( origin );\n\t\tbreak;\n\tcase MSG_ONE:\n\t\treliable = true;\n\t\t// intentional fallthrough\n\tcase MSG_ONE_UNRELIABLE:\n\t\tif( !SV_IsValidEdict( ent )) return 0;\n\t\tj = NUM_FOR_EDICT( ent );\n\t\tif( j < 1 || j > numclients ) return 0;\n\t\tcurrent = svs.clients + (j - 1);\n\t\tnumclients = 1; // send to one\n\t\tbreak;\n\tcase MSG_SPEC:\n\t\tspecproxy = reliable = true;\n\t\tbreak;\n\tdefault:\n\t\tHost_Error( \"%s: bad dest: %i\\n\", __func__, dest );\n\t\treturn 0;\n\t}\n\n\t// send the data to all relevent clients (or once only)\n\tfor( j = 0, cl = current; j < numclients; j++, cl++ )\n\t{\n\t\tif( cl->state == cs_free || cl->state == cs_zombie )\n\t\t\tcontinue;\n\n\t\tif( cl->state != cs_spawned && ( !reliable || usermessage ))\n\t\t\tcontinue;\n\n\t\tif( specproxy && !FBitSet( cl->flags, FCL_HLTV_PROXY ))\n\t\t\tcontinue;\n\n\t\tif( !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\t// reject step sounds while predicting is enabled\n\t\t// FIXME: make sure what this code doesn't cutoff something important!!!\n\t\tif( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT ))\n\t\t\tcontinue;\n\n\t\tif( SV_IsValidEdict( ent ) && ent->v.groupinfo && cl->edict->v.groupinfo )\n\t\t{\n\t\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( !SV_CheckClientVisiblity( cl, mask ))\n\t\t\tcontinue;\n\n\t\tif( specproxy ) MSG_WriteBits( &sv.spec_datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast ));\n\t\telse if( reliable ) MSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast ));\n\t\telse MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast ));\n\t\tnumsends++;\n\t}\n\n\tMSG_Clear( &sv.multicast );\n\n\treturn numsends; // just for debug\n}\n\n/*\n=======================\nSV_GetReliableDatagram\n\nGet shared reliable buffer\n=======================\n*/\nstatic sizebuf_t *SV_GetReliableDatagram( void )\n{\n\treturn &sv.reliable_datagram;\n}\n\n/*\n=======================\nSV_RestoreCustomDecal\n\nLet the user spawn decal in game code\n=======================\n*/\nqboolean SV_RestoreCustomDecal( decallist_t *entry, edict_t *pEdict, qboolean adjacent )\n{\n\tif( svgame.physFuncs.pfnRestoreDecal != NULL )\n\t{\n\t\tif( !pEdict ) pEdict = EDICT_NUM( entry->entityIndex );\n\t\t// true if decal was sucessfully restored at the game-side\n\t\treturn svgame.physFuncs.pfnRestoreDecal( entry, pEdict, adjacent );\n\t}\n\treturn false;\n}\n\n/*\n=======================\nSV_CreateDecal\n\nNOTE: static decals only accepted when game is loading\n=======================\n*/\nvoid SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale )\n{\n\tif( msg == &sv.signon && sv.state != ss_loading )\n\t\treturn;\n\n\t// this can happens if serialized map contain 4096 static decals...\n\tif( MSG_GetNumBytesLeft( msg ) < 20 )\n\t{\n\t\tsv.ignored_world_decals++;\n\t\treturn;\n\t}\n\n\t// static decals are posters, it's always reliable\n\tMSG_BeginServerCmd( msg, svc_bspdecal );\n\tMSG_WriteVec3Coord( msg, origin );\n\tMSG_WriteWord( msg, decalIndex );\n\tMSG_WriteShort( msg, entityIndex );\n\tif( entityIndex > 0 )\n\t\tMSG_WriteWord( msg, modelIndex );\n\tMSG_WriteByte( msg, flags );\n\tMSG_WriteWord( msg, scale * 4096 );\n}\n\n/*\n=======================\nSV_CreateStaticEntity\n\nNOTE: static entities only accepted when game is loading\n=======================\n*/\nqboolean SV_CreateStaticEntity( sizebuf_t *msg, int index )\n{\n\tentity_state_t\tnullstate, *baseline;\n\tentity_state_t\t*state;\n\tint\t\toffset;\n\n\tif( index >= ( MAX_STATIC_ENTITIES - 1 ))\n\t{\n\t\tif( !sv.static_ents_overflow )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"MAX_STATIC_ENTITIES limit exceeded (%d)\\n\", MAX_STATIC_ENTITIES );\n\t\t\tsv.static_ents_overflow = true;\n\t\t}\n\n\t\tsv.ignored_static_ents++; // continue overflowed entities\n\t\treturn false;\n\t}\n\n\t// this can happens if serialized map contain too many static entities...\n\tif( MSG_GetNumBytesLeft( msg ) < 50 )\n\t{\n\t\tsv.ignored_static_ents++;\n\t\treturn false;\n\t}\n\n\tstate = &svs.static_entities[index]; // allocate a new one\n\tmemset( &nullstate, 0, sizeof( nullstate ));\n\tbaseline = &nullstate;\n\n\t// restore modelindex from modelname (already precached)\n\tstate->modelindex = pfnModelIndex( STRING( state->messagenum ));\n\tstate->entityType = ENTITY_NORMAL; // select delta-encode\n\tstate->number = 0;\n\n\t// trying to compress with previous delta's\n\toffset = SV_FindBestBaseline( index, &baseline, state, NULL, false );\n\n\tMSG_BeginServerCmd( msg, svc_spawnstatic );\n\tMSG_WriteDeltaEntity( baseline, state, msg, true, DELTA_STATIC, sv.time, offset );\n\n\treturn true;\n}\n\n/*\n=================\nSV_RestartStaticEnts\n\nWrite all the static ents into demo\n=================\n*/\nvoid SV_RestartStaticEnts( void )\n{\n\tint\ti;\n\n\t// remove all the static entities on the client\n\tCL_ClearStaticEntities();\n\n\t// resend them again\n\tfor( i = 0; i < sv.num_static_entities; i++ )\n\t\tSV_CreateStaticEntity( &sv.reliable_datagram, i );\n}\n\n/*\n=================\nSV_StartMusic\n\n=================\n*/\nstatic void SV_StartMusic( const char *curtrack, const char *looptrack, int position )\n{\n\tMSG_BeginServerCmd( &sv.multicast, svc_stufftext );\n\tMSG_WriteStringf( &sv.multicast, \"music \\\"%s\\\" \\\"%s\\\" %d\\n\", curtrack, looptrack, position );\n\tSV_Multicast( MSG_ALL, NULL, NULL, false, false );\n}\n\n/*\n=================\nSV_RestartAmbientSounds\n\nWrite ambient sounds into demo\n=================\n*/\nvoid SV_RestartAmbientSounds( void )\n{\n\t// TODO: we don't know sounds state on remote server\n\t// as it's used only for demos, maybe this could be implemented on client side?\n#if !XASH_DEDICATED\n\tsoundlist_t\tsoundInfo[256];\n\tstring\t\tcurtrack, looptrack;\n\tint\t\ti, nSounds;\n\tint\t\tposition;\n\n\tif( !SV_Active( ) || Host_IsDedicated( ))\n\t\treturn;\n\n\tnSounds = S_GetCurrentStaticSounds( soundInfo, 256 );\n\n\tfor( i = 0; i < nSounds; i++ )\n\t{\n\t\tsoundlist_t *si = &soundInfo[i];\n\n\t\tif( !si->looping || si->entnum == -1 )\n\t\t\tcontinue;\n\n\t\tS_StopSound( si->entnum, si->channel, si->name );\n\t\tSV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch );\n\t}\n\n\t// restart soundtrack\n\tif( S_StreamGetCurrentState( curtrack, sizeof( curtrack ), looptrack, sizeof( looptrack ), &position ))\n\t{\n\t\tSV_StartMusic( curtrack, looptrack, position );\n\t}\n#endif // !XASH_DEDICATED\n}\n\n/*\n=================\nSV_RestartDecals\n\nWrite all the decals into demo\n=================\n*/\nvoid SV_RestartDecals( void )\n{\n\t// TODO: similar to SV_RestartAmbientSounds, this is only used for demo recording\n\t// and better be reimplemented on client side\n#if !XASH_DEDICATED\n\tdecallist_t\t*list;\n\tint\t\tdecalIndex;\n\tint\t\tmodelIndex;\n\tsizebuf_t\t\t*msg;\n\tint\t\ti, numdecals;\n\n\tif( !SV_Active( ) || Host_IsDedicated( ))\n\t\treturn;\n\n\t// g-cont. add space for studiodecals if present\n\tlist = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 );\n\n\tnumdecals = ref.dllFuncs.R_CreateDecalList( list );\n\n\t// remove decals from map\n\tref.dllFuncs.R_ClearAllDecals();\n\n\t// write decals into reliable datagram\n\tmsg = SV_GetReliableDatagram();\n\n\t// restore decals and write them into network message\n\tfor( i = 0; i < numdecals; i++ )\n\t{\n\t\tdecallist_t *entry = &list[i];\n\t\tmodelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex;\n\n\t\t// game override\n\t\tif( SV_RestoreCustomDecal( entry, SV_PEntityOfEntIndex( entry->entityIndex, true ), false ))\n\t\t\tcontinue;\n\n\t\tdecalIndex = pfnDecalIndex( entry->name );\n\n\t\t// studiodecals will be restored at game-side\n\t\tif( !FBitSet( entry->flags, FDECAL_STUDIO ))\n\t\t\tSV_CreateDecal( msg, entry->position, decalIndex, entry->entityIndex, modelIndex, entry->flags, entry->scale );\n\t}\n\n\tZ_Free( list );\n#endif // !XASH_DEDICATED\n}\n\n/*\n==============\nSV_BoxInPVS\n\ncheck brush boxes in fat pvs\n==============\n*/\nqboolean GAME_EXPORT SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax )\n{\n\tif( !Mod_BoxVisible( absmin, absmax, Mod_GetPVSForPoint( org )))\n\t\treturn false;\n\treturn true;\n}\n\n/*\n=============\nSV_ChangeLevel\n\nIssue changing level\n=============\n*/\nvoid SV_QueueChangeLevel( const char *level, const char *landname )\n{\n\tuint\tflags, smooth = false;\n\tchar\tmapname[MAX_QPATH];\n\n\t// hold mapname to other place\n\tQ_strncpy( mapname, level, sizeof( mapname ));\n\tCOM_StripExtension( mapname );\n\n\tif( COM_CheckString( landname ))\n\t\tsmooth = true;\n\n\tflags = SV_MapIsValid( mapname, landname );\n\n\tif( FBitSet( flags, MAP_INVALID_VERSION ))\n\t{\n\t\tCon_Printf( S_ERROR \"changelevel: %s is invalid or not supported\\n\", mapname );\n\t\treturn;\n\t}\n\n\tif( !FBitSet( flags, MAP_IS_EXIST ))\n\t{\n\t\tCon_Printf( S_ERROR \"changelevel: map %s doesn't exist\\n\", mapname );\n\t\treturn;\n\t}\n\n\tif( smooth && !FBitSet( flags, MAP_HAS_LANDMARK ))\n\t{\n\t\tif( sv_validate_changelevel.value )\n\t\t{\n\t\t\t// NOTE: we find valid map but specified landmark it's doesn't exist\n\t\t\t// run simple changelevel like in q1, throw warning\n\t\t\tCon_Printf( S_WARN \"changelevel: %s doesn't contain landmark [%s]. smooth transition was disabled\\n\", mapname, landname );\n\t\t\tsmooth = false;\n\t\t}\n\t}\n\n\tif( svs.maxclients > 1 )\n\t\tsmooth = false; // multiplayer doesn't support smooth transition\n\n\tif( smooth && !Q_stricmp( sv.name, level ))\n\t{\n\t\tCon_Printf( S_ERROR \"can't changelevel with same map. Ignored.\\n\" );\n\t\treturn;\n\t}\n\n\t// bad changelevel position invoke enables in one-way transition\n\tif( sv.framecount < 15 )\n\t{\n\t\tif( sv_validate_changelevel.value )\n\t\t{\n\t\t\tCon_Printf( S_WARN \"an infinite changelevel was detected and will be disabled until a next save\\\\restore\\n\" );\n\t\t\treturn; // lock with svs.spawncount here\n\t\t}\n\t}\n\n\tSV_SkipUpdates ();\n\n\t// changelevel will be executed on a next frame\n\tif( smooth ) COM_ChangeLevel( mapname, landname, sv.background );\t// Smoothed Half-Life changelevel\n\telse COM_ChangeLevel( mapname, NULL, sv.background );\t\t// Classic Quake changlevel\n}\n\n/*\n==============\nSV_WriteEntityPatch\n\nCreate entity patch for selected map\n==============\n*/\nvoid SV_WriteEntityPatch( const char *filename )\n{\n\tint\t\tlumpofs = 0, lumplen = 0;\n\tbyte\t\tbuf[MAX_TOKEN]; // 1 kb\n\tstring\t\tbspfilename;\n\tdlump_t entities;\n\tfile_t\t\t*f;\n\n\tQ_snprintf( bspfilename, sizeof( bspfilename ), \"maps/%s.bsp\", filename );\n\n\tf = FS_Open( bspfilename, \"rb\", false );\n\tif( !f ) return;\n\n\tmemset( buf, 0, MAX_TOKEN );\n\tFS_Read( f, buf, MAX_TOKEN );\n\n\t// check all the lumps and some other errors\n\tif( !Mod_TestBmodelLumps( f, bspfilename, buf, true, &entities ))\n\t{\n\t\tFS_Close( f );\n\t\treturn;\n\t}\n\n\tlumpofs = entities.fileofs;\n\tlumplen = entities.filelen;\n\n\tif( lumplen >= 10 )\n\t{\n\t\tchar\t*entities = NULL;\n\n\t\tFS_Seek( f, lumpofs, SEEK_SET );\n\t\tentities = (char *)Z_Calloc( lumplen + 1 );\n\t\tFS_Read( f, entities, lumplen );\n\t\tFS_WriteFile( va( \"maps/%s.ent\", filename ), entities, lumplen );\n\t\tCon_Printf( \"Write 'maps/%s.ent'\\n\", filename );\n\t\tMem_Free( entities );\n\t}\n\n\tFS_Close( f );\n}\n\n/*\n==============\nSV_ReadEntityScript\n\npfnMapIsValid use this\n==============\n*/\nstatic char *SV_ReadEntityScript( const char *filename, int *flags )\n{\n\tstring\t\tbspfilename, entfilename;\n\tint\t\tlumpofs = 0, lumplen = 0;\n\tbyte\t\tbuf[MAX_TOKEN];\n\tchar\t\t*ents = NULL;\n\tdlump_t entities;\n\tsize_t\t\tft1, ft2;\n\tfile_t\t\t*f;\n\n\t*flags = 0;\n\n\tQ_snprintf( bspfilename, sizeof( bspfilename ), \"maps/%s.bsp\", filename );\n\n\tf = FS_Open( bspfilename, \"rb\", false );\n\tif( !f ) return NULL;\n\n\tSetBits( *flags, MAP_IS_EXIST );\n\tmemset( buf, 0, MAX_TOKEN );\n\tFS_Read( f, buf, MAX_TOKEN );\n\n\t// check all the lumps and some other errors\n\tif( !Mod_TestBmodelLumps( f, bspfilename, buf, (host_developer.value) ? false : true, &entities ))\n\t{\n\t\tSetBits( *flags, MAP_INVALID_VERSION );\n\t\tFS_Close( f );\n\t\treturn NULL;\n\t}\n\n\t// after call Mod_TestBmodelLumps we gurantee what map is valid\n\tlumpofs = entities.fileofs;\n\tlumplen = entities.filelen;\n\n\t// check for entfile too\n\tQ_snprintf( entfilename, sizeof( entfilename ), \"maps/%s.ent\", filename );\n\n\t// make sure what entity patch is newer than bsp\n\tft1 = FS_FileTime( bspfilename, false );\n\tft2 = FS_FileTime( entfilename, true );\n\n\tif( ft2 != -1 && ft1 < ft2 )\n\t{\n\t\t// grab .ent files only from gamedir\n\t\tents = (char *)FS_LoadFile( entfilename, NULL, true );\n\t}\n\n\t// at least entities should contain \"{ \"classname\" \"worldspawn\" }\\0\"\n\t// for correct spawn the level\n\tif( !ents && lumplen >= 32 )\n\t{\n\t\tFS_Seek( f, lumpofs, SEEK_SET );\n\t\tents = Z_Calloc( lumplen + 1 );\n\t\tFS_Read( f, ents, lumplen );\n\t}\n\tFS_Close( f ); // all done\n\n\treturn ents;\n}\n\n/*\n==============\nSV_MapIsValid\n\nValidate map\n==============\n*/\nuint SV_MapIsValid( const char *filename, const char *landmark_name )\n{\n\tuint\tflags = 0;\n\tchar\t*pfile;\n\tchar\t*ents;\n\n\tents = SV_ReadEntityScript( filename, &flags );\n\n\tif( ents )\n\t{\n\t\tqboolean\tneed_landmark;\n\t\tchar\ttoken[MAX_TOKEN];\n\t\tstring\tcheck_name;\n\n\t\tneed_landmark = COM_CheckString( landmark_name );\n\n\t\tif( !need_landmark )\n\t\t{\n\t\t\tMem_Free( ents );\n\n\t\t\treturn flags;\n\t\t}\n\n\t\tpfile = ents;\n\n\t\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t\t{\n\t\t\tif( !Q_strcmp( token, \"targetname\" ))\n\t\t\t{\n\t\t\t\t// check targetname for landmark entity\n\t\t\t\tpfile = COM_ParseFile( pfile, check_name, sizeof( check_name ));\n\n\t\t\t\tif( !Q_strcmp( landmark_name, check_name ))\n\t\t\t\t{\n\t\t\t\t\t// we found landmark, stop the parsing\n\t\t\t\t\tSetBits( flags, MAP_HAS_LANDMARK );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tMem_Free( ents );\n\t}\n\n\treturn flags;\n}\n\n/*\n==============\nSV_FreePrivateData\n\nrelease private edict memory\n==============\n*/\nstatic void GAME_EXPORT SV_FreePrivateData( edict_t *pEdict )\n{\n\tif( !pEdict || !pEdict->pvPrivateData )\n\t\treturn;\n\n\t// NOTE: new interface can be missing\n\tif( svgame.dllFuncs2.pfnOnFreeEntPrivateData != NULL )\n\t\tsvgame.dllFuncs2.pfnOnFreeEntPrivateData( pEdict );\n\n\tif( Mem_IsAllocatedExt( svgame.mempool, pEdict->pvPrivateData ))\n\t\tMem_Free( pEdict->pvPrivateData );\n\n\tpEdict->pvPrivateData = NULL;\n}\n\n/*\n==============\nSV_InitEdict\n\nclear edict for reuse\n==============\n*/\nvoid SV_InitEdict( edict_t *pEdict )\n{\n\tAssert( pEdict != NULL );\n\n\tSV_FreePrivateData( pEdict );\n\tmemset( &pEdict->v, 0, sizeof( entvars_t ));\n\tpEdict->v.pContainingEntity = pEdict;\n\tpEdict->v.controller[0] = 0x7F;\n\tpEdict->v.controller[1] = 0x7F;\n\tpEdict->v.controller[2] = 0x7F;\n\tpEdict->v.controller[3] = 0x7F;\n\tpEdict->free = false;\n}\n\n/*\n==============\nSV_FreeEdict\n\nunlink edict from world and free it\n==============\n*/\nvoid SV_FreeEdict( edict_t *pEdict )\n{\n\tAssert( pEdict != NULL );\n\tif( pEdict->free ) return;\n\n\t// unlink from world\n\tSV_UnlinkEdict( pEdict );\n\n\tSV_FreePrivateData( pEdict );\n\n\t// mark edict as freed\n\tpEdict->freetime = sv.time;\n\tpEdict->serialnumber++; // invalidate EHANDLE's\n\tpEdict->v.solid = SOLID_NOT;\n\tpEdict->v.flags = 0;\n\tpEdict->v.model = 0;\n\tpEdict->v.takedamage = 0;\n\tpEdict->v.modelindex = 0;\n\tpEdict->v.nextthink = -1;\n\tpEdict->v.colormap = 0;\n\tpEdict->v.frame = 0;\n\tpEdict->v.scale = 0;\n\tpEdict->v.gravity = 0;\n\tpEdict->v.skin = 0;\n\n\tVectorClear( pEdict->v.angles );\n\tVectorClear( pEdict->v.origin );\n\tpEdict->free = true;\n}\n\n/*\n==============\nSV_AllocEdict\n\nallocate new or reuse existing\n==============\n*/\nedict_t *GAME_EXPORT SV_AllocEdict( void )\n{\n\tedict_t\t*e;\n\tint\ti;\n\n\tfor( i = svs.maxclients + 1; i < svgame.numEntities; i++ )\n\t{\n\t\te = EDICT_NUM( i );\n\t\t// the first couple seconds of server time can involve a lot of\n\t\t// freeing and allocating, so relax the replacement policy\n\t\tif( e->free && ( e->freetime < 2.0f || ( sv.time - e->freetime ) > 0.5f ))\n\t\t{\n\t\t\tSV_InitEdict( e );\n\t\t\treturn e;\n\t\t}\n\t}\n\n\tif( i >= GI->max_edicts )\n\t\tHost_Error( \"%s: no free edicts (max is %d)\\n\", __func__, GI->max_edicts );\n\n\tsvgame.numEntities++;\n\te = EDICT_NUM( i );\n\tSV_InitEdict( e );\n\n\treturn e;\n}\n\n/*\n==============\nSV_GetEntityClass\n\nget pointer for entity class\n==============\n*/\nstatic LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName )\n{\n\t// allocate edict private memory (passed by dlls)\n\treturn (LINK_ENTITY_FUNC)COM_GetProcAddress( svgame.hInstance, pszClassName );\n}\n\n/*\n==============\nSV_AllocPrivateData\n\nallocate private data for a given edict\n\nif customentity is NULL, no \"custom\" entity EXPORT is being done\nif customentity is not NULL, will be set to true if \"custom\" export\nwas used to create this entity\n==============\n*/\nstatic edict_t* SV_AllocPrivateData( edict_t *ent, string_t className, qboolean *customentity )\n{\n\tconst char\t*pszClassName;\n\tLINK_ENTITY_FUNC\tSpawnEdict;\n\n\tpszClassName = STRING( className );\n\n\tif( customentity )\n\t\t*customentity = false;\n\n\tif( !ent )\n\t{\n\t\t// allocate a new one\n\t\tent = SV_AllocEdict();\n\t}\n\telse if( ent->free )\n\t{\n\t\tSV_InitEdict( ent ); // re-init edict\n\t}\n\n\tent->v.classname = className;\n\tent->v.pContainingEntity = ent; // re-link\n\n\t// allocate edict private memory (passed by dlls)\n\tSpawnEdict = SV_GetEntityClass( pszClassName );\n\n\tif( !SpawnEdict )\n\t{\n\t\t// attempt to create custom entity (Xash3D extension)\n\t\tif( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 )\n\t\t\treturn ent;\n\n\t\tif( customentity )\n\t\t{\n\t\t\tSpawnEdict = SV_GetEntityClass( \"custom\" );\n\t\t\t*customentity = SpawnEdict != NULL;\n\t\t}\n\n\t\tif( !SpawnEdict )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"No spawn function for \\\"%s\\\"\\n\", pszClassName );\n\n\t\t\t// free entity immediately\n\t\t\tSV_FreeEdict( ent );\n\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tSpawnEdict( &ent->v );\n\n\treturn ent;\n}\n\n/*\n==============\nSV_CreateNamedEntity\n\ncreate specified entity, alloc private data\n==============\n*/\nedict_t* SV_CreateNamedEntity( edict_t *ent, string_t className )\n{\n\treturn SV_AllocPrivateData( ent, className, NULL );\n}\n\n/*\n==============\nSV_FreeEdicts\n\nrelease all the edicts from server\n==============\n*/\nvoid SV_FreeEdicts( void )\n{\n\tint\ti = 0;\n\tedict_t\t*ent;\n\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\t\tif( ent->free ) continue;\n\t\tSV_FreeEdict( ent );\n\t}\n}\n\n/*\n==============\nSV_PlaybackReliableEvent\n\nreliable event is must be delivered always\n==============\n*/\nstatic void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args )\n{\n\tevent_args_t nullargs;\n\n\tmemset( &nullargs, 0, sizeof( nullargs ));\n\n\tMSG_BeginServerCmd( msg, svc_event_reliable );\n\n\t// send event index\n\tMSG_WriteUBitLong( msg, eventindex, MAX_EVENT_BITS );\n\n\tif( delay )\n\t{\n\t\t// send event delay\n\t\tMSG_WriteOneBit( msg, 1 );\n\t\tMSG_WriteWord( msg, ( delay * 100.0f ));\n\t}\n\telse MSG_WriteOneBit( msg, 0 );\n\n\t// reliable events not use delta-compression just null-compression\n\tMSG_WriteDeltaEvent( msg, &nullargs, args );\n}\n\n/*\n==============\nSV_ClassName\n\ntemplate to get edict classname\n==============\n*/\nconst char *SV_ClassName( const edict_t *e )\n{\n\tif( !e ) return \"(null)\";\n\tif( e->free ) return \"freed\";\n\treturn STRING( e->v.classname );\n}\n\n/*\n==============\nSV_IsValidCmd\n\ncommand validation\n==============\n*/\nstatic qboolean SV_IsValidCmd( const char *pCmd )\n{\n\tsize_t\tlen = Q_strlen( pCmd );\n\n\t// valid commands all have a ';' or newline '\\n' as their last character\n\tif( len && ( pCmd[len-1] == '\\n' || pCmd[len-1] == ';' ))\n\t\treturn true;\n\treturn false;\n}\n\n/*\n==============\nSV_ClientFromEdict\n\nget edict that attached to the client structure\n==============\n*/\nsv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only )\n{\n\tint\ti;\n\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn NULL;\n\n\ti = NUM_FOR_EDICT( pEdict ) - 1;\n\n\tif( i < 0 || i >= svs.maxclients )\n\t\treturn NULL;\n\n\tif( spawned_only )\n\t{\n\t\tif( svs.clients[i].state != cs_spawned )\n\t\t\treturn NULL;\n\t}\n\n\treturn (svs.clients + i);\n}\n\n/*\n===============================================================================\n\n\tGame Builtin Functions\n\n===============================================================================\n*/\n/*\n=========\npfnPrecacheModel\n\n=========\n*/\nstatic int GAME_EXPORT pfnPrecacheModel( const char *s )\n{\n\tqboolean\toptional = false;\n\tint\ti;\n\n\tif( *s == '!' )\n\t{\n\t\toptional = true;\n\t\ts++;\n\t}\n\n\tif(( i = SV_ModelIndex( s )) == 0 )\n\t\treturn 0;\n\n\tsv.models[i] = Mod_ForName( sv.model_precache[i], false, true );\n\n\tif( !optional )\n\t\tSetBits( sv.model_precache_flags[i], RES_FATALIFMISSING );\n\n\treturn i;\n}\n\n/*\n=================\npfnModelIndex\n\n=================\n*/\nstatic int GAME_EXPORT pfnModelIndex( const char *m )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( m ))\n\t\treturn 0;\n\n\tif( *m == '\\\\' || *m == '/' ) m++;\n\tQ_strncpy( name, m, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( sv.model_precache[i], name ))\n\t\t\treturn i;\n\t}\n\n\tCon_Printf( S_ERROR \"Cannot get index for model %s: not precached\\n\", name );\n\treturn 0;\n}\n\n/*\n=================\npfnModelFrames\n\n=================\n*/\nstatic int GAME_EXPORT pfnModelFrames( int modelIndex )\n{\n\tmodel_t\t*pmodel = SV_ModelHandle( modelIndex );\n\n\tif( pmodel != NULL )\n\t\treturn pmodel->numframes;\n\treturn 1;\n}\n\n/*\n=================\npfnSetSize\n\n=================\n*/\nstatic void GAME_EXPORT pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax )\n{\n\tif( !SV_IsValidEdict( e ))\n\t\treturn;\n\n\tSV_SetMinMaxSize( e, rgflMin, rgflMax, true );\n}\n\n/*\n=================\npfnChangeLevel\n\n=================\n*/\nstatic void GAME_EXPORT pfnChangeLevel( const char *level, const char *landmark )\n{\n\tstatic uint\tlast_spawncount = 0;\n\tchar\t\tlandname[MAX_QPATH];\n\tchar\t\t*text;\n\n\tif( !COM_CheckString( level ) || sv.state != ss_active )\n\t\treturn; // ???\n\n\t// make sure we don't issue two changelevels\n\tif( svs.spawncount == last_spawncount )\n\t\treturn;\n\tlast_spawncount = svs.spawncount;\n\tlandname[0] ='\\0';\n\n#ifdef HACKS_RELATED_HLMODS\n\t// g-cont. some level-designers wrote landmark name with space\n\t// and Cmd_TokenizeString separating all the after space as next argument\n\t// emulate this bug for compatibility\n\tif( COM_CheckString( landmark ))\n\t{\n\t\ttext = (char *)landname;\n\t\twhile( *landmark && ((byte)*landmark) != ' ' )\n\t\t\t*text++ = *landmark++;\n\t\t*text = '\\0';\n\t}\n#else\n\tQ_strncpy( landname, landmark, sizeof( landname ));\n#endif\n\tSV_QueueChangeLevel( level, landname );\n}\n\n/*\n=================\npfnGetSpawnParms\n\nOBSOLETE, UNUSED\n=================\n*/\nstatic void GAME_EXPORT pfnGetSpawnParms( edict_t *ent )\n{\n}\n\n/*\n=================\npfnSaveSpawnParms\n\nOBSOLETE, UNUSED\n=================\n*/\nstatic void GAME_EXPORT pfnSaveSpawnParms( edict_t *ent )\n{\n}\n\n/*\n=================\npfnVecToYaw\n\n=================\n*/\nstatic float GAME_EXPORT pfnVecToYaw( const float *rgflVector )\n{\n\treturn SV_VecToYaw( rgflVector );\n}\n\n/*\n=================\npfnMoveToOrigin\n\n=================\n*/\nstatic void GAME_EXPORT pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType )\n{\n\tif( !pflGoal || !SV_IsValidEdict( ent ))\n\t\treturn;\n\n\tSV_MoveToOrigin( ent, pflGoal, dist, iMoveType );\n}\n\n/*\n==============\npfnChangeYaw\n\n==============\n*/\nstatic void GAME_EXPORT pfnChangeYaw( edict_t* ent )\n{\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn;\n\n\tent->v.angles[YAW] = SV_AngleMod( ent->v.ideal_yaw, ent->v.angles[YAW], ent->v.yaw_speed );\n}\n\n/*\n==============\npfnChangePitch\n\n==============\n*/\nstatic void GAME_EXPORT pfnChangePitch( edict_t* ent )\n{\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn;\n\n\tent->v.angles[PITCH] = SV_AngleMod( ent->v.idealpitch, ent->v.angles[PITCH], ent->v.pitch_speed );\n}\n\n/*\n=========\nSV_FindEntityByString\n\n=========\n*/\nstatic edict_t *GAME_EXPORT SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue )\n{\n\tint\t\ti = 0, e = 0;\n\tconst TYPEDESCRIPTION\t*desc = NULL;\n\tedict_t\t\t*ed;\n\tconst char\t*t;\n\n\tif( !COM_CheckString( pszValue ))\n\t\treturn svgame.edicts;\n\n\tif( pStartEdict ) e = NUM_FOR_EDICT( pStartEdict );\n\n\tfor( i = 0; i < ARRAYSIZE( gEntvarsDescription ); i++ )\n\t{\n\t\tif( !Q_strcmp( pszField, gEntvarsDescription[i].fieldName ))\n\t\t{\n\t\t\tdesc = &gEntvarsDescription[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( desc == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"FindEntityByString: field %s not a string\\n\", pszField );\n\t\treturn svgame.edicts;\n\t}\n\n\tfor( e++; e < svgame.numEntities; e++ )\n\t{\n\t\ted = EDICT_NUM( e );\n\t\tif( !SV_IsValidEdict( ed )) continue;\n\n\t\tif( e <= svs.maxclients && !SV_ClientFromEdict( ed, ( svs.maxclients != 1 )))\n\t\t\tcontinue;\n\n\t\tswitch( desc->fieldType )\n\t\t{\n\t\tcase FIELD_STRING:\n\t\tcase FIELD_MODELNAME:\n\t\tcase FIELD_SOUNDNAME:\n\t\t\tt = STRING( *(string_t *)&((byte *)&ed->v)[desc->fieldOffset] );\n\t\t\tif( t != NULL && t != svgame.globals->pStringBase )\n\t\t\t{\n\t\t\t\tif( !Q_strcmp( t, pszValue ))\n\t\t\t\t\treturn ed;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tASSERT( 0 );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn svgame.edicts;\n}\n\n/*\n=========\nSV_FindGlobalEntity\n\nripped out from the hl.dll\n=========\n*/\nedict_t *SV_FindGlobalEntity( string_t classname, string_t globalname )\n{\n\tedict_t *pent = SV_FindEntityByString( NULL,  \"globalname\", STRING( globalname ));\n\n\tif( SV_IsValidEdict( pent ))\n\t{\n\t\t// don't spam about error - game code already tell us\n\t\tif( Q_strcmp( SV_ClassName( pent ), STRING( classname )))\n\t\t\tpent = NULL;\n\t}\n\n\treturn pent;\n}\n\n/*\n=================\npfnFindEntityInSphere\n\nfind the entity in sphere\n=================\n*/\nstatic edict_t *GAME_EXPORT pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius )\n{\n\tfloat\tdistSquared;\n\tint\tj, e = 0;\n\tfloat\teorg;\n\tedict_t\t*ent;\n\n\tflRadius *= flRadius;\n\n\tif( SV_IsValidEdict( pStartEdict ))\n\t\te = NUM_FOR_EDICT( pStartEdict );\n\n\tfor( e++; e < svgame.numEntities; e++ )\n\t{\n\t\tent = EDICT_NUM( e );\n\n\t\tif( !SV_IsValidEdict( ent ))\n\t\t\tcontinue;\n\n\t\t// ignore clients that not in a game\n\t\tif( e <= svs.maxclients && !SV_ClientFromEdict( ent, true ))\n\t\t\tcontinue;\n\n\t\tdistSquared = 0.0f;\n\n\t\tfor( j = 0; j < 3 && distSquared <= flRadius; j++ )\n\t\t{\n\t\t\tif( org[j] < ent->v.absmin[j] )\n\t\t\t\teorg = org[j] - ent->v.absmin[j];\n\t\t\telse if( org[j] > ent->v.absmax[j] )\n\t\t\t\teorg = org[j] - ent->v.absmax[j];\n\t\t\telse eorg = 0.0f;\n\n\t\t\tdistSquared += eorg * eorg;\n\t\t}\n\n\t\tif( distSquared < flRadius )\n\t\t\treturn ent;\n\t}\n\n\treturn svgame.edicts;\n}\n\n/*\n=================\nSV_CheckClientPVS\n\nbuild the new client PVS\n=================\n*/\nstatic int SV_CheckClientPVS( int check, qboolean bMergePVS )\n{\n\tbyte\t\t*pvs;\n\tvec3_t\t\tvieworg;\n\tsv_client_t\t*cl;\n\tint\t\ti, j, k;\n\tedict_t\t\t*ent = NULL;\n\n\t// cycle to the next one\n\tcheck = bound( 1, check, svs.maxclients );\n\n\tif( check == svs.maxclients )\n\t\ti = 1; // reset cycle\n\telse i = check + 1;\n\n\tfor( ;; i++ )\n\t{\n\t\tif( i == ( svs.maxclients + 1 ))\n\t\t\ti = 1;\n\n\t\tent = EDICT_NUM( i );\n\t\tif( i == check ) break; // didn't find anything else\n\n\t\tif( ent->free || !ent->pvPrivateData || FBitSet( ent->v.flags, FL_NOTARGET ))\n\t\t\tcontinue;\n\n\t\t// anything that is a client, or has a client as an enemy\n\t\tbreak;\n\t}\n\n\tcl = SV_ClientFromEdict( ent, true );\n\tmemset( clientpvs, 0xFF, world.visbytes );\n\n\t// get the PVS for the entity\n\tVectorAdd( ent->v.origin, ent->v.view_ofs, vieworg );\n\tpvs = Mod_GetPVSForPoint( vieworg );\n\tif( pvs ) memcpy( clientpvs, pvs, world.visbytes );\n\n\t// transition in progress\n\tif( !cl ) return i;\n\n\t// now merge PVS with all the portal cameras\n\tfor( k = 0; k < cl->num_viewents && bMergePVS; k++ )\n\t{\n\t\tedict_t\t*view = cl->viewentity[k];\n\n\t\tif( !SV_IsValidEdict( view ))\n\t\t\tcontinue;\n\n\t\tVectorAdd( view->v.origin, view->v.view_ofs, vieworg );\n\t\tpvs = Mod_GetPVSForPoint( vieworg );\n\n\t\tfor( j = 0; j < world.visbytes && pvs; j++ )\n\t\t\tSetBits( clientpvs[j], pvs[j] );\n\t}\n\n\treturn i;\n}\n\n/*\n=================\npfnFindClientInPVS\n\n=================\n*/\nstatic edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict )\n{\n\tedict_t\t*pClient;\n\tvec3_t\tview;\n\tfloat\tdelta;\n\tmodel_t\t*mod;\n\tqboolean\tbMergePVS;\n\tmleaf_t\t*leaf;\n\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn svgame.edicts;\n\n\tdelta = ( sv.time - sv.lastchecktime );\n\n\t// don't merge visibility for portal entity, only for monsters\n\tbMergePVS = FBitSet( pEdict->v.flags, FL_MONSTER ) ? true : false;\n\n\t// find a new check if on a new frame\n\tif( delta < 0.0f || delta >= 0.1f )\n\t{\n\t\tsv.lastcheck = SV_CheckClientPVS( sv.lastcheck, bMergePVS );\n\t\tsv.lastchecktime = sv.time;\n\t}\n\n\t// return check if it might be visible\n\tpClient = EDICT_NUM( sv.lastcheck );\n\n\tif( !SV_ClientFromEdict( pClient, true ))\n\t\treturn svgame.edicts;\n\n\tmod = SV_ModelHandle( pEdict->v.modelindex );\n\n\t// portals & monitors\n\t// NOTE: this specific break \"radiaton tick\" in normal half-life. use only as feature\n\tif( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ) && mod && mod->type == mod_brush && !FBitSet( mod->flags, MODEL_HAS_ORIGIN ))\n\t{\n\t\t// handle PVS origin for bmodels\n\t\tVectorAverage( pEdict->v.mins, pEdict->v.maxs, view );\n\t\tVectorAdd( view, pEdict->v.origin, view );\n\t}\n\telse\n\t{\n\t\tVectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view );\n\t}\n\n\tleaf = Mod_PointInLeaf( view, sv.worldmodel->nodes, sv.worldmodel );\n\n\tif( CHECKVISBIT( clientpvs, leaf->cluster ))\n\t\treturn pClient; // client which currently in PVS\n\n\treturn svgame.edicts;\n}\n\n/*\n=================\npfnEntitiesInPVS\n\n=================\n*/\nstatic edict_t *pfnEntitiesInPVS( edict_t *pview )\n{\n\tedict_t\t*pchain, *ptest;\n\tvec3_t\tviewpoint;\n\tedict_t\t*pent;\n\tint\ti;\n\n\tif( !SV_IsValidEdict( pview ))\n\t\treturn NULL;\n\n\tVectorAdd( pview->v.origin, pview->v.view_ofs, viewpoint );\n\tpchain = EDICT_NUM( 0 );\n\n\tfor( i = 1; i < svgame.numEntities; i++ )\n\t{\n\t\tpent = EDICT_NUM( i );\n\n\t\tif( !SV_IsValidEdict( pent ))\n\t\t\tcontinue;\n\n\t\tif( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( pent->v.aiment ))\n\t\t\tptest = pent->v.aiment;\n\t\telse ptest = pent;\n\n\t\tif( SV_BoxInPVS( viewpoint, ptest->v.absmin, ptest->v.absmax ))\n\t\t{\n\t\t\tpent->v.chain = pchain;\n\t\t\tpchain = pent;\n\t\t}\n\t}\n\n\treturn pchain;\n}\n\n/*\n==============\npfnMakeVectors\n\n==============\n*/\nstatic void GAME_EXPORT pfnMakeVectors( const float *rgflVector )\n{\n\tAngleVectors( rgflVector, svgame.globals->v_forward, svgame.globals->v_right, svgame.globals->v_up );\n}\n\n/*\n==============\npfnRemoveEntity\n\nfree edict private mem, unlink physics etc\n==============\n*/\nstatic void GAME_EXPORT pfnRemoveEntity( edict_t *e )\n{\n\tif( !SV_IsValidEdict( e ))\n\t\treturn;\n\n\t// never free client or world entity\n\tif( NUM_FOR_EDICT( e ) < ( svs.maxclients + 1 ))\n\t{\n\t\tCon_Printf( S_ERROR \"can't delete %s\\n\", ( e == EDICT_NUM( 0 )) ? \"world\" : \"client\" );\n\t\treturn;\n\t}\n\n\tSV_FreeEdict( e );\n}\n\n/*\n==============\npfnCreateNamedEntity\n\n==============\n*/\nstatic edict_t *GAME_EXPORT pfnCreateNamedEntity( string_t className )\n{\n\treturn SV_CreateNamedEntity( NULL, className );\n}\n\n/*\n=============\npfnMakeStatic\n\nmove entity to client\n=============\n*/\nstatic void GAME_EXPORT pfnMakeStatic( edict_t *ent )\n{\n\tentity_state_t\t*state;\n\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn;\n\n\t// fill the entity state\n\tstate = &svs.static_entities[sv.num_static_entities];\t// allocate a new one\n\tsvgame.dllFuncs.pfnCreateBaseline( false, NUM_FOR_EDICT( ent ), state, ent, 0, vec3_origin, vec3_origin );\n\tstate->messagenum = ent->v.model; // member modelname\n\n\tif( SV_CreateStaticEntity( &sv.signon, sv.num_static_entities ))\n\t\tsv.num_static_entities++;\n\n\t// remove at end of the frame\n\tSetBits( ent->v.flags, FL_KILLME );\n}\n\n/*\n=============\npfnEntIsOnFloor\n\nlegacy builtin\n=============\n*/\nstatic int GAME_EXPORT pfnEntIsOnFloor( edict_t *e )\n{\n\tif( !SV_IsValidEdict( e ))\n\t\treturn 0;\n\n\treturn SV_CheckBottom( e, MOVE_NORMAL );\n}\n\n/*\n===============\npfnDropToFloor\n\n===============\n*/\nint GAME_EXPORT pfnDropToFloor( edict_t *e )\n{\n\tqboolean\tmonsterClip;\n\ttrace_t\ttrace;\n\tvec3_t\tend;\n\n\tif( !SV_IsValidEdict( e ))\n\t\treturn 0;\n\n\tmonsterClip = FBitSet( e->v.flags, FL_MONSTERCLIP ) ? true : false;\n\tVectorCopy( e->v.origin, end );\n\tend[2] -= 256.0f;\n\n\ttrace = SV_Move( e->v.origin, e->v.mins, e->v.maxs, end, MOVE_NORMAL, e, monsterClip );\n\n\tif( trace.allsolid )\n\t\treturn -1;\n\n\tif( trace.fraction == 1.0f )\n\t\treturn 0;\n\n\tVectorCopy( trace.endpos, e->v.origin );\n\tSV_LinkEdict( e, false );\n\tSetBits( e->v.flags, FL_ONGROUND );\n\te->v.groundentity = trace.ent;\n\n\treturn 1;\n}\n\n/*\n===============\npfnWalkMove\n\n===============\n*/\nstatic int GAME_EXPORT pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode )\n{\n\tvec3_t\tmove;\n\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn 0;\n\n\tif( !FBitSet( ent->v.flags, FL_FLY|FL_SWIM|FL_ONGROUND ))\n\t\treturn 0;\n\n\tyaw = DEG2RAD( yaw );\n\tVectorSet( move, cos( yaw ) * dist, sin( yaw ) * dist, 0.0f );\n\n\tswitch( iMode )\n\t{\n\tcase WALKMOVE_NORMAL:\n\t\treturn SV_MoveStep( ent, move, true );\n\tcase WALKMOVE_WORLDONLY:\n\t\treturn SV_MoveTest( ent, move, true );\n\tcase WALKMOVE_CHECKONLY:\n\t\treturn SV_MoveStep( ent, move, false);\n\t}\n\treturn 0;\n}\n\n/*\n=================\npfnSetOrigin\n\n=================\n*/\nstatic void GAME_EXPORT pfnSetOrigin( edict_t *e, const float *rgflOrigin )\n{\n\tif( !SV_IsValidEdict( e ))\n\t\treturn;\n\n\tVectorCopy( rgflOrigin, e->v.origin );\n\tSV_LinkEdict( e, false );\n}\n\n/*\n=================\nSV_BuildSoundMsg\n\n=================\n*/\nint SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos )\n{\n\tint\tentityIndex;\n\tint\tsound_idx;\n\tqboolean\tspawn;\n\n\tif( vol < 0 || vol > 255 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: volume = %i\\n\", __func__, vol );\n\t\tvol = bound( 0, vol, 255 );\n\t}\n\n\tif( attn < 0.0f || attn > 4.0f )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: attenuation %g must be in range 0-4\\n\", __func__, attn );\n\t\tattn = bound( 0.0f, attn, 4.0f );\n\t}\n\n\tif( chan < 0 || chan > 7 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: channel must be in range 0-7\\n\", __func__ );\n\t\tchan = bound( 0, chan, 7 );\n\t}\n\n\tif( pitch < 0 || pitch > 255 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: pitch = %i\\n\", __func__, pitch );\n\t\tpitch = bound( 0, pitch, 255 );\n\t}\n\n\tif( !COM_CheckString( sample ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: passed NULL sample\\n\", __func__ );\n\t\treturn 0;\n\t}\n\n\tif( sample[0] == '!' && Q_isdigit( sample + 1 ))\n\t{\n\t\tsound_idx = Q_atoi( sample + 1 );\n\n\t\tif( sound_idx >= MAX_SOUNDS_NONSENTENCE )\n\t\t{\n\t\t\tSetBits( flags, SND_SENTENCE|SND_SEQUENCE );\n\t\t\tsound_idx -= MAX_SOUNDS_NONSENTENCE;\n\t\t}\n\t\telse SetBits( flags, SND_SENTENCE );\n\t}\n\telse if( sample[0] == '#' && Q_isdigit( sample + 1 ))\n\t{\n\t\tSetBits( flags, SND_SENTENCE|SND_SEQUENCE );\n\t\tsound_idx = Q_atoi( sample + 1 );\n\t}\n\telse\n\t{\n\t\t// '*' is special symbol to handle stream sounds\n\t\t// (CHAN_VOICE but cannot be overriden)\n\t\t// originally handled on client side\n\t\tif( *sample == '*' )\n\t\t\tchan = CHAN_STREAM;\n\n\t\t// precache_sound can be used twice: cache sounds when loading\n\t\t// and return sound index when server is active\n\t\tsound_idx = SV_SoundIndex( sample );\n\n\t\tif( !sound_idx )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: %s not precached (%d)\\n\", __func__, sample, sound_idx );\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tspawn = FBitSet( flags, SND_RESTORE_POSITION ) ? false : true;\n\n\tif( SV_IsValidEdict( ent ))\n\t\tentityIndex = NUM_FOR_EDICT( ent );\n\telse entityIndex = 0; // assume world\n\n\tif( vol != 255 ) SetBits( flags, SND_VOLUME );\n\tif( attn != ATTN_NONE ) SetBits( flags, SND_ATTENUATION );\n\tif( pitch != PITCH_NORM ) SetBits( flags, SND_PITCH );\n\n\t// not sending (because this is out of range)\n\tClearBits( flags, SND_RESTORE_POSITION );\n\tClearBits( flags, SND_FILTER_CLIENT );\n\tClearBits( flags, SND_SPAWNING );\n\n\tif( spawn ) MSG_BeginServerCmd( msg, svc_sound );\n\telse MSG_BeginServerCmd( msg, svc_restoresound );\n\tMSG_WriteUBitLong( msg, flags, MAX_SND_FLAGS_BITS );\n\tMSG_WriteUBitLong( msg, sound_idx, MAX_SOUND_BITS );\n\tMSG_WriteUBitLong( msg, chan, MAX_SND_CHAN_BITS );\n\n\tif( FBitSet( flags, SND_VOLUME )) MSG_WriteByte( msg, vol );\n\tif( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, Q_min( attn * 64, 255 ));\n\tif( FBitSet( flags, SND_PITCH )) MSG_WriteByte( msg, pitch );\n\n\tMSG_WriteUBitLong( msg, entityIndex, MAX_ENTITY_BITS );\n\tMSG_WriteVec3Coord( msg, pos );\n\n\treturn 1;\n}\n\n/*\n=================\nSV_StartSound\n\n=================\n*/\nvoid GAME_EXPORT SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch )\n{\n\tqboolean\tfilter = false;\n\tint\tmsg_dest;\n\tvec3_t\torigin;\n\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn;\n\n\tVectorAverage( ent->v.mins, ent->v.maxs, origin );\n\tVectorAdd( origin, ent->v.origin, origin );\n\n\tif( FBitSet( flags, SND_SPAWNING ))\n\t\tmsg_dest = MSG_INIT;\n\telse if( chan == CHAN_STATIC )\n\t\tmsg_dest = MSG_ALL;\n\telse if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\tmsg_dest = MSG_ALL;\n\telse msg_dest = (svs.maxclients <= 1 ) ? MSG_ALL : MSG_PAS_R;\n\n\t// always sending stop sound command\n\tif( FBitSet( flags, SND_STOP ))\n\t\tmsg_dest = MSG_ALL;\n\n\tif( FBitSet( flags, SND_FILTER_CLIENT ))\n\t\tfilter = true;\n\n\tif( SV_BuildSoundMsg( &sv.multicast, ent, chan, sample, vol * 255, attn, flags, pitch, origin ))\n\t\tSV_Multicast( msg_dest, origin, NULL, false, filter );\n}\n\n/*\n=================\npfnEmitAmbientSound\n\n=================\n*/\nstatic void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch )\n{\n\tint\tmsg_dest;\n\n\tif( sv.state == ss_loading )\n\t\tSetBits( flags, SND_SPAWNING );\n\n\tif( FBitSet( flags, SND_SPAWNING ))\n\t\tmsg_dest = MSG_INIT;\n\telse msg_dest = MSG_ALL;\n\n\t// always sending stop sound command\n\tif( FBitSet( flags, SND_STOP ))\n\t\tmsg_dest = MSG_ALL;\n\n\tif( SV_BuildSoundMsg( &sv.multicast, ent, CHAN_STATIC, sample, vol * 255, attn, flags, pitch, pos ))\n\t\tSV_Multicast( msg_dest, pos, NULL, false, false );\n}\n\n/*\n=================\npfnTraceLine\n\n=================\n*/\nstatic void GAME_EXPORT pfnTraceLine( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr )\n{\n\ttrace_t\ttrace;\n\n\ttrace = SV_Move( v1, vec3_origin, vec3_origin, v2, fNoMonsters, pentToSkip, false );\n\tif( !SV_IsValidEdict( trace.ent ))\n\t\ttrace.ent = svgame.edicts;\n\tSV_ConvertTrace( ptr, &trace );\n}\n\n/*\n=================\npfnTraceToss\n\n=================\n*/\nstatic void GAME_EXPORT pfnTraceToss( edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr )\n{\n\ttrace_t\ttrace;\n\n\tif( !SV_IsValidEdict( pent ))\n\t\treturn;\n\n\ttrace = SV_MoveToss( pent, pentToIgnore );\n\tSV_ConvertTrace( ptr, &trace );\n}\n\n/*\n=================\npfnTraceHull\n\n=================\n*/\nstatic void GAME_EXPORT pfnTraceHull( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr )\n{\n\ttrace_t\ttrace;\n\n\tif( hullNumber < 0 || hullNumber > 3 )\n\t\thullNumber = 0;\n\n\ttrace = SV_Move( v1, sv.worldmodel->hulls[hullNumber].clip_mins, sv.worldmodel->hulls[hullNumber].clip_maxs, v2, fNoMonsters, pentToSkip, false );\n\tSV_ConvertTrace( ptr, &trace );\n}\n\n/*\n=============\npfnTraceMonsterHull\n\n=============\n*/\nstatic int GAME_EXPORT pfnTraceMonsterHull( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr )\n{\n\tqboolean\tmonsterClip;\n\ttrace_t\ttrace;\n\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn 0;\n\n\tmonsterClip = FBitSet( pEdict->v.flags, FL_MONSTERCLIP ) ? true : false;\n\ttrace = SV_Move( v1, pEdict->v.mins, pEdict->v.maxs, v2, fNoMonsters, pentToSkip, monsterClip );\n\tSV_ConvertTrace( ptr, &trace );\n\n\tif( trace.allsolid || trace.fraction != 1.0f )\n\t\treturn true;\n\treturn false;\n}\n\n/*\n=============\npfnTraceModel\n\n=============\n*/\nstatic void GAME_EXPORT pfnTraceModel( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr )\n{\n\tfloat\t*mins, *maxs;\n\tmodel_t\t*model;\n\ttrace_t\ttrace;\n\n\tif( !SV_IsValidEdict( pent ))\n\t\treturn;\n\n\tif( hullNumber < 0 || hullNumber > 3 )\n\t\thullNumber = 0;\n\n\tmins = sv.worldmodel->hulls[hullNumber].clip_mins;\n\tmaxs = sv.worldmodel->hulls[hullNumber].clip_maxs;\n\tmodel = SV_ModelHandle( pent->v.modelindex );\n\n\tif( pent->v.solid == SOLID_CUSTOM )\n\t{\n\t\t// NOTE: always goes through custom clipping move\n\t\t// even if our callbacks is not initialized\n\t\tSV_CustomClipMoveToEntity( pent, v1, mins, maxs, v2, &trace );\n\t}\n\telse if( model && model->type == mod_brush )\n\t{\n\t\tint oldmovetype = pent->v.movetype;\n\t\tint oldsolid = pent->v.solid;\n\t\tpent->v.movetype = MOVETYPE_PUSH;\n\t\tpent->v.solid = SOLID_BSP;\n\n\t\tSV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace );\n\n\t\tpent->v.movetype = oldmovetype;\n\t\tpent->v.solid = oldsolid;\n\t}\n\telse\n\t{\n\t\tSV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace );\n\t}\n\n\tSV_ConvertTrace( ptr, &trace );\n}\n\n/*\n=============\npfnTraceTexture\n\nreturns texture basename\n=============\n*/\nstatic const char *pfnTraceTexture( edict_t *pTextureEntity, const float *v1, const float *v2 )\n{\n\tif( !SV_IsValidEdict( pTextureEntity ))\n\t\treturn NULL;\n\n\treturn SV_TraceTexture( pTextureEntity, v1, v2 );\n}\n\n/*\n=============\npfnTraceSphere\n\nOBSOLETE, UNUSED\n=============\n*/\nstatic void GAME_EXPORT pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr )\n{\n}\n\n/*\n=============\npfnGetAimVector\n\nNOTE: speed is unused\n=============\n*/\nstatic void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn )\n{\n\tedict_t\t\t*check;\n\tvec3_t\t\tstart, dir, end, bestdir;\n\tfloat\t\tdist, bestdist;\n\tint\t\ti, j;\n\ttrace_t\t\ttr;\n\n\tVectorCopy( svgame.globals->v_forward, rgflReturn );\t// assume failure if it returns early\n\n\tif( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_FAKECLIENT ))\n\t\treturn;\n\n\tVectorCopy( ent->v.origin, start );\n\tVectorAdd( start, ent->v.view_ofs, start );\n\n\t// try sending a trace straight\n\tVectorCopy( svgame.globals->v_forward, dir );\n\tVectorMA( start, 2048, dir, end );\n\ttr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false );\n\n\t// don't aim at teammate\n\tif( tr.ent && ( tr.ent->v.takedamage == DAMAGE_AIM || ent->v.team <= 0 || ent->v.team != tr.ent->v.team ))\n\t\treturn;\n\n\t// try all possible entities\n\tVectorCopy( svgame.globals->v_forward, bestdir );\n\tif( sv_allow_autoaim.value )\n\t\tbestdist = sv_aim.value;\n\telse bestdist = 0;\n\n\tcheck = EDICT_NUM( 1 ); // start at first client\n\tfor( i = 1; i < svgame.numEntities; i++, check++ )\n\t{\n\t\tif( check->v.takedamage != DAMAGE_AIM )\n\t\t\tcontinue;\n\n\t\tif( FBitSet( check->v.flags, FL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tif( ent->v.team > 0 && ent->v.team == check->v.team )\n\t\t\tcontinue;\n\n\t\tif( check == ent )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t\tend[j] = check->v.origin[j] + 0.5f * (check->v.mins[j] + check->v.maxs[j]);\n\n\t\tVectorSubtract( end, start, dir );\n\t\tVectorNormalize( dir );\n\t\tdist = DotProduct( dir, svgame.globals->v_forward );\n\n\t\tif( dist < bestdist )\n\t\t\tcontinue; // to far to turn\n\n\t\ttr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false );\n\n\t\tif( tr.ent == check )\n\t\t{\n\t\t\t// can shoot at this one\n\t\t\tVectorCopy( dir, bestdir );\n\t\t\tbestdist = dist;\n\t\t}\n\t}\n\n\tVectorCopy( bestdir, rgflReturn );\n}\n\n/*\n=========\npfnServerCommand\n\n=========\n*/\nstatic void GAME_EXPORT pfnServerCommand( const char* str )\n{\n\tif( !SV_IsValidCmd( str ))\n\t\tCon_Printf( S_ERROR \"bad server command %s\\n\", str );\n\telse Cbuf_AddText( str );\n}\n\n/*\n=========\npfnServerExecute\n\n=========\n*/\nstatic void GAME_EXPORT pfnServerExecute( void )\n{\n\tCbuf_Execute();\n}\n\n/*\n=========\npfnClientCommand\n\n=========\n*/\nvoid GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) FORMAT_CHECK( 2 );\nvoid GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... )\n{\n\tsv_client_t\t*cl;\n\tstring\t\tbuffer;\n\tva_list\t\targs;\n\n\tif( sv.state != ss_active )\n\t\treturn; // early out\n\n\tif(( cl = SV_ClientFromEdict( pEdict, false )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"stuffcmd: client is not spawned!\\n\" );\n\t\treturn;\n\t}\n\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tva_start( args, szFmt );\n\tQ_vsnprintf( buffer, MAX_STRING, szFmt, args );\n\tva_end( args );\n\n\tif( SV_IsValidCmd( buffer ))\n\t{\n\t\tMSG_BeginServerCmd( &cl->netchan.message, svc_stufftext );\n\t\tMSG_WriteString( &cl->netchan.message, buffer );\n\t}\n\telse Con_Printf( S_ERROR \"Tried to stuff bad command %s\\n\", buffer );\n}\n\n/*\n=================\npfnParticleEffect\n\nMake sure the event gets sent to all clients\n=================\n*/\nstatic void GAME_EXPORT pfnParticleEffect( const float *org, const float *dir, float color, float count )\n{\n\tint\tv;\n\n\tif( MSG_GetNumBytesLeft( &sv.datagram ) < 16 )\n\t\treturn;\n\n\tMSG_BeginServerCmd( &sv.datagram, svc_particle );\n\tMSG_WriteVec3Coord( &sv.datagram, org );\n\tv = bound( -128, dir[0] * 16.0f, 127 );\n\tMSG_WriteChar( &sv.datagram, v );\n\tv = bound( -128, dir[1] * 16.0f, 127 );\n\tMSG_WriteChar( &sv.datagram, v );\n\tv = bound( -128, dir[2] * 16.0f, 127 );\n\tMSG_WriteChar( &sv.datagram, v );\n\tMSG_WriteByte( &sv.datagram, count );\n\tMSG_WriteByte( &sv.datagram, color );\n\tMSG_WriteByte( &sv.datagram, 0 ); // z-vel\n}\n\n/*\n===============\npfnLightStyle\n\n===============\n*/\nstatic void GAME_EXPORT pfnLightStyle( int style, const char* val )\n{\n\tif( style < 0 )\n\t\tstyle = 0;\n\n\tif( style >= MAX_LIGHTSTYLES )\n\t{\n\t\tHost_Error( \"%s: style: %i >= %d\", __func__, style, MAX_LIGHTSTYLES );\n\t\treturn;\n\t}\n\n\tif( sv.loadgame )\n\t\treturn; // don't let the world overwrite our restored styles\n\n\tSV_SetLightStyle( style, val, 0.0f ); // set correct style\n}\n\n/*\n=================\npfnDecalIndex\n\nregister decal name on client\n=================\n*/\nint GAME_EXPORT pfnDecalIndex( const char *m )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( m ))\n\t\treturn -1;\n\n\tfor( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( host.draw_decals[i], m ))\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nstatic int SV_CanRewriteMessage( int msg_num )\n{\n\t// feature is disabled\n\tif( !FBitSet( host.bugcomp, BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG ))\n\t\treturn 0;\n\n\tswitch( msg_num )\n\t{\n\tcase svc_goldsrc_spawnstaticsound:\n\t\treturn svc_sound;\n\t}\n\n\treturn 0;\n}\n\nstatic qboolean SV_RewriteMessage( void )\n{\n\tvec3_t origin;\n\tconst char *sample = NULL;\n\tfloat vol, attn;\n\tint ent, pitch, flags, idx;\n\tint cmd;\n\n\tMSG_SeekToBit( &sv.multicast, svgame.msg_rewrite_pos, SEEK_SET );\n\n\tcmd = MSG_ReadCmd( &sv.multicast, NS_SERVER );\n\n\tswitch( cmd )\n\t{\n\tcase svc_goldsrc_spawnstaticsound:\n\t\tMSG_ReadVec3Coord( &sv.multicast, origin );\n\t\tidx   = MSG_ReadShort( &sv.multicast );\n\t\tvol   = MSG_ReadByte( &sv.multicast );\n\t\tattn  = MSG_ReadByte( &sv.multicast ) / 64.0f;\n\t\tent   = MSG_ReadShort( &sv.multicast );\n\t\tpitch = MSG_ReadByte( &sv.multicast );\n\t\tflags = MSG_ReadByte( &sv.multicast );\n\n\t\tif( FBitSet( flags, SND_SENTENCE ))\n\t\t\tsample = va( \"!%i\", idx );\n\t\telse if( idx >= 0 && idx < MAX_SOUNDS )\n\t\t\tsample = sv.sound_precache[idx];\n\n\t\tif( !COM_CheckString( sample ))\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: unrecognized sample in svc_spawnstaticsound, index %d, flags 0x%x\\n\", __func__, idx, flags );\n\t\t\treturn false;\n\t\t}\n\n\t\tMSG_SeekToBit( &sv.multicast, svgame.msg_rewrite_pos, SEEK_SET );\n\t\treturn SV_BuildSoundMsg( &sv.multicast, EDICT_NUM( ent ), CHAN_STATIC, sample, vol, attn, flags, pitch, origin );\n\t}\n\n\treturn false;\n}\n\n/*\n=============\npfnMessageBegin\n\n=============\n*/\nstatic void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed )\n{\n\tint\ti, iSize;\n\n\tif( svgame.msg_started )\n\t\tHost_Error( \"%s: New message started when msg '%s' has not been sent yet\\n\", __func__, svgame.msg_name );\n\tsvgame.msg_started = true;\n\n\t// check range\n\tmsg_num = bound( svc_bad, msg_num, 255 );\n\n\tsvgame.msg_rewrite_index = 0;\n\tsvgame.msg_rewrite_pos = 0;\n\n\tif( msg_num <= svc_lastmsg )\n\t{\n\t\t// check if we should rewrite this message into something else...\n\t\tif( SV_CanRewriteMessage( msg_num ))\n\t\t{\n\t\t\tsvgame.msg_index = -SV_CanRewriteMessage( msg_num );\n\t\t\tsvgame.msg_name = svc_goldsrc_strings[msg_num] ? svc_goldsrc_strings[msg_num] : svc_strings[msg_num];\n\t\t\tsvgame.msg_rewrite_index = msg_num;\n\t\t\tsvgame.msg_rewrite_pos = MSG_TellBit( &sv.multicast );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsvgame.msg_index = -msg_num; // this is a system message\n\t\t\tsvgame.msg_name = svc_strings[msg_num];\n\t\t}\n\n\t\tif( msg_num == svc_temp_entity )\n\t\t\tiSize = -1; // temp entity have variable size\n\t\telse iSize = 0;\n\t}\n\telse\n\t{\n\t\t// check for existing\n\t\tfor( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ )\n\t\t{\n\t\t\tif( svgame.msg[i].number == msg_num )\n\t\t\t\tbreak; // found\n\t\t}\n\n\t\tif( i == MAX_USER_MESSAGES )\n\t\t{\n\t\t\tHost_Error( \"%s: tried to send unregistered message %i\\n\", __func__, msg_num );\n\t\t\treturn;\n\t\t}\n\n\t\tsvgame.msg_name = svgame.msg[i].name;\n\t\tiSize = svgame.msg[i].size;\n\t\tsvgame.msg_index = i;\n\t}\n\n\tMSG_WriteCmdExt( &sv.multicast, msg_num, NS_SERVER, svgame.msg_name );\n\n\t// save message destination\n\tif( pOrigin ) VectorCopy( pOrigin, svgame.msg_org );\n\telse VectorClear( svgame.msg_org );\n\n\tif( iSize == -1 )\n\t{\n\t\t// variable sized messages sent size as first short\n\t\tsvgame.msg_size_index = MSG_GetNumBytesWritten( &sv.multicast );\n\t\tMSG_WriteWord( &sv.multicast, 0 ); // reserve space for now\n\t}\n\telse svgame.msg_size_index = -1; // message has constant size\n\n\tsvgame.msg_realsize = 0;\n\tsvgame.msg_dest = msg_dest;\n\tsvgame.msg_ent = ed;\n\n\t// enable message tracing\n\tsvgame.msg_trace = sv_trace_messages.value != 0 &&\n\t\tmsg_num > svc_lastmsg &&\n\t\tQ_strcmp( svgame.msg_name, \"ReqState\" );\n\n\tif( svgame.msg_trace ) Con_Printf( \"^3%s( %i, %s )\\n\", __func__, msg_dest, svgame.msg_name );\n}\n\n/*\n=============\npfnMessageEnd\n\n=============\n*/\nstatic void GAME_EXPORT pfnMessageEnd( void )\n{\n\tconst char\t*name = \"Unknown\";\n\tfloat\t\t*org = NULL;\n\tword realsize;\n\n\tif( svgame.msg_name ) name = svgame.msg_name;\n\tif( !svgame.msg_started ) Host_Error( \"%s: called with no active message\\n\", __func__ );\n\tsvgame.msg_started = false;\n\n\tif( MSG_CheckOverflow( &sv.multicast ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s has overflow multicast buffer\\n\", __func__, name );\n\t\tMSG_Clear( &sv.multicast );\n\t\treturn;\n\t}\n\n\tif( svgame.msg_rewrite_index != 0 )\n\t{\n\t\tif( SV_RewriteMessage( ))\n\t\t{\n\t\t\tif( MSG_CheckOverflow( &sv.multicast ))\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"%s: %s has overflow multicast buffer (post-rewrite)\\n\", __func__, name );\n\t\t\t\tMSG_Clear( &sv.multicast );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: failed to rewrite message %s\\n\", __func__, name );\n\t\t\tMSG_Clear( &sv.multicast );\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// check for system message\n\tif( svgame.msg_index < 0 )\n\t{\n\t\tif( svgame.msg_size_index != -1 )\n\t\t{\n\t\t\t// variable sized message\n\t\t\tif( svgame.msg_realsize > MAX_USERMSG_LENGTH )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"%s: %s too long (more than %d bytes)\\n\", __func__, name, MAX_USERMSG_LENGTH );\n\t\t\t\tMSG_Clear( &sv.multicast );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if( svgame.msg_realsize < 0 )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"%s: %s writes NULL message\\n\", __func__, name );\n\t\t\t\tMSG_Clear( &sv.multicast );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\trealsize = svgame.msg_realsize;\n\t\t\tmemcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize ));\n\t\t}\n\t}\n\telse if( svgame.msg[svgame.msg_index].size != -1 )\n\t{\n\t\tint\texpsize = svgame.msg[svgame.msg_index].size;\n\t\tint\trealsize = svgame.msg_realsize;\n\n\t\t// compare sizes\n\t\tif( expsize != realsize )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: %s expected %i bytes, it written %i. Ignored.\\n\", __func__, name, expsize, realsize );\n\t\t\tMSG_Clear( &sv.multicast );\n\t\t\treturn;\n\t\t}\n\t}\n\telse if( svgame.msg_size_index != -1 )\n\t{\n\t\t// variable sized message\n\t\tif( svgame.msg_realsize > MAX_USERMSG_LENGTH )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: %s too long (more than %d bytes)\\n\", __func__, name, MAX_USERMSG_LENGTH );\n\t\t\tMSG_Clear( &sv.multicast );\n\t\t\treturn;\n\t\t}\n\t\telse if( svgame.msg_realsize < 0 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: %s writes NULL message\\n\", __func__, name );\n\t\t\tMSG_Clear( &sv.multicast );\n\t\t\treturn;\n\t\t}\n\n\t\trealsize = svgame.msg_realsize;\n\t\tmemcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize ));\n\t}\n\telse\n\t{\n\t\t// this should never happen\n\t\tCon_Printf( S_ERROR \"%s: %s have encountered error\\n\", __func__, name );\n\t\tMSG_Clear( &sv.multicast );\n\t\treturn;\n\t}\n\n\t// update some messages in case their was format was changed and we want to keep backward compatibility\n\tif( svgame.msg_index < 0 )\n\t{\n\t\tif(( svgame.msg_index == -svc_finale || svgame.msg_index == -svc_cutscene ) && svgame.msg_realsize == 0 )\n\t\t\tMSG_WriteChar( &sv.multicast, 0 ); // write null string\n\t}\n\n\tif( !VectorIsNull( svgame.msg_org )) org = svgame.msg_org;\n\tsvgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC );\n\n\tSV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false );\n\n\tif( svgame.msg_trace ) Con_Printf( \"^3%s()\\n\", __func__ );\n}\n\n/*\n=============\npfnWriteByte\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteByte( int iValue )\n{\n\tif( iValue == -1 ) iValue = 0xFF; // convert char to byte\n\tMSG_WriteByte( &sv.multicast, (byte)iValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, iValue );\n\tsvgame.msg_realsize++;\n}\n\n/*\n=============\npfnWriteChar\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteChar( int iValue )\n{\n\tMSG_WriteChar( &sv.multicast, (signed char)iValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, iValue );\n\tsvgame.msg_realsize++;\n}\n\n/*\n=============\npfnWriteShort\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteShort( int iValue )\n{\n\tMSG_WriteShort( &sv.multicast, (short)iValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, iValue );\n\tsvgame.msg_realsize += 2;\n}\n\n/*\n=============\npfnWriteLong\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteLong( int iValue )\n{\n\tMSG_WriteLong( &sv.multicast, iValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, iValue );\n\tsvgame.msg_realsize += 4;\n}\n\n/*\n=============\npfnWriteAngle\n\nthis is low-res angle\n=============\n*/\nstatic void GAME_EXPORT pfnWriteAngle( float flValue )\n{\n\tint\tiAngle = ((int)(( flValue ) * 256 / 360) & 255);\n\n\tMSG_WriteChar( &sv.multicast, iAngle );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %f )\\n\", __func__, flValue );\n\tsvgame.msg_realsize += 1;\n}\n\n/*\n=============\npfnWriteCoord\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteCoord( float flValue )\n{\n\tMSG_WriteCoord( &sv.multicast, flValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %f )\\n\", __func__, flValue );\n\tsvgame.msg_realsize += 2;\n}\n\n/*\n=============\npfnWriteString\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteString( const char *src )\n{\n\tMSG_WriteString( &sv.multicast, src );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %s )\\n\", __func__, src );\n\n\t// NOTE: some messages with constant string length can be marked as known sized\n\tsvgame.msg_realsize += Q_strlen( src ) + 1;\n}\n\n/*\n=============\npfnWriteEntity\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteEntity( int iValue )\n{\n\tif( iValue < 0 || iValue >= svgame.numEntities )\n\t\tHost_Error( \"%s: invalid entnumber %i\\n\", __func__, iValue );\n\tMSG_WriteShort( &sv.multicast, (short)iValue );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, iValue );\n\tsvgame.msg_realsize += 2;\n}\n\n/*\n=============\npfnCvar_RegisterServerVariable\n\nstandard path to register game variable\n=============\n*/\nstatic void GAME_EXPORT pfnCvar_RegisterServerVariable( cvar_t *variable )\n{\n\tif( variable != NULL )\n\t{\n\t\tSetBits( variable->flags, FCVAR_EXTDLL );\n\t\tCvar_RegisterVariable( (convar_t *)variable );\n\t}\n}\n\n/*\n=============\npfnAlertMessage\n\n=============\n*/\nstatic void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) FORMAT_CHECK( 2 );\nstatic void GAME_EXPORT pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... )\n{\n\tchar\tbuffer[2048];\n\tva_list\targs;\n\n\tif( type == at_logged && svs.maxclients > 1 )\n\t{\n\t\tva_start( args, szFmt );\n\t\tQ_vsnprintf( buffer, sizeof( buffer ), szFmt, args );\n\t\tva_end( args );\n\t\tLog_Printf( \"%s\", buffer );\n\t\treturn;\n\t}\n\n\tif( host_developer.value <= DEV_NONE )\n\t\treturn;\n\n\t// g-cont: some mods have wrong aiconsole messages that crash the engine\n\tif( type == at_aiconsole && host_developer.value < DEV_EXTENDED )\n\t\treturn;\n\n\tva_start( args, szFmt );\n\tQ_vsnprintf( buffer, sizeof( buffer ), szFmt, args );\n\tva_end( args );\n\n\t// check message for pass\n\tswitch( type )\n\t{\n\tcase at_notice:\n\t\tCon_Printf( S_NOTE \"%s\", buffer );\n\t\tbreak;\n\tcase at_console:\n\t\tCon_Printf( \"%s\", buffer );\n\t\tbreak;\n\tcase at_aiconsole:\n\t\tCon_DPrintf( \"%s\", buffer );\n\t\tbreak;\n\tcase at_warning:\n\t\tCon_Printf( S_WARN \"%s\", buffer );\n\t\tbreak;\n\tcase at_error:\n\t\tCon_Printf( S_ERROR \"%s\", buffer );\n\t\tbreak;\n\t}\n}\n\n/*\n=============\npfnEngineFprintf\n\nOBSOLETE, UNUSED\n=============\n*/\nstatic void GAME_EXPORT pfnEngineFprintf( FILE *pfile, char *szFmt, ... )\n{\n}\n\n/*\n=============\npfnBuildSoundMsg\n\nCustomizable sound message\n=============\n*/\nstatic void GAME_EXPORT pfnBuildSoundMsg( edict_t *pSource, int chan, const char *samp, float fvol, float attn, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *pSend )\n{\n\tpfnMessageBegin( msg_dest, msg_type, pOrigin, pSend );\n\tSV_BuildSoundMsg( &sv.multicast, pSource, chan, samp, fvol * 255, attn, fFlags, pitch, pOrigin );\n\tpfnMessageEnd();\n}\n\n/*\n=============\npfnPvAllocEntPrivateData\n\n=============\n*/\nstatic void *GAME_EXPORT pfnPvAllocEntPrivateData( edict_t *pEdict, long cb )\n{\n\tAssert( pEdict != NULL );\n\n\tSV_FreePrivateData( pEdict );\n\n\tif( cb > 0 )\n\t{\n\t\t// a poke646 have memory corrupt in somewhere - this is trashed last sixteen bytes :(\n\t\tpEdict->pvPrivateData = Mem_Calloc( svgame.mempool, (cb + 15) & ~15 );\n\t}\n\n\treturn pEdict->pvPrivateData;\n}\n\n/*\n=============\npfnPvEntPrivateData\n\nwe already have copy of this function in 'enginecallback.h' :-)\n=============\n*/\nstatic void *GAME_EXPORT pfnPvEntPrivateData( edict_t *pEdict )\n{\n\tif( pEdict )\n\t\treturn pEdict->pvPrivateData;\n\treturn NULL;\n}\n\n\nstatic struct str64_s\n{\n\tsize_t maxstringarray;\n\tqboolean allowdup;\n\tqboolean dynamic;\n\tchar *staticstringarray;\n\tchar *pstringarray;\n\tchar *pstringarraystatic;\n\tchar *pstringbase;\n\tchar *poldstringbase;\n\tchar *plast;\n\tsize_t maxalloc;\n\tsize_t numdups;\n\tsize_t numoverflows;\n\tsize_t totalalloc;\n} str64;\n\n/*\n==================\nSV_EmptyStringPool\n\nFree strings on server stop. Reset string pointer on 64 bits\n==================\n*/\nvoid SV_EmptyStringPool( qboolean clear_stats )\n{\n#if XASH_64BIT\n\tif( str64.dynamic ) // switch only after array fill (more space for multiplayer games)\n\t{\n\t\tstr64.pstringbase = str64.pstringarray;\n\t}\n\telse\n\t{\n\t\tstr64.pstringbase = str64.poldstringbase = str64.pstringarraystatic;\n\t\tstr64.plast = str64.pstringbase + 1;\n\t}\n\n\tif( clear_stats )\n\t{\n\t\tstr64.maxalloc = 0;\n\t\tstr64.totalalloc = 0;\n\t\tstr64.numdups = 0;\n\t\tstr64.numoverflows = 0;\n\t}\n#endif // !XASH_64BIT\n}\n\n/*\n===============\nSV_SetStringArrayMode\n\nuse different arrays on 64 bit platforms\nset dynamic after complete server spawn\nthis helps not to lose strings that belongs to static game part\n===============\n*/\nvoid SV_SetStringArrayMode( qboolean dynamic )\n{\n#if XASH_64BIT\n\tCon_Reportf( \"%s(%d) %d\\n\", __func__, dynamic, str64.dynamic );\n\n\tif( dynamic == str64.dynamic )\n\t\treturn;\n\n\tstr64.dynamic = dynamic;\n\n\tSV_EmptyStringPool( false );\n#endif // !XASH_64BIT\n}\n\n#if XASH_AMD64 && XASH_LINUX && !XASH_ANDROID\n#define USE_MMAP 1\n#include <sys/mman.h>\n#endif\n\n/*\n==================\nSV_AllocStringPool\n\nalloc string pool on 32bit platforms\nalloc string array near the server library on 64bit platforms if possible\nalloc string array somewhere if not (MAKE_STRING will not work. Always call ALLOC_STRING instead, or crash)\nthis case need patched game dll with MAKE_STRING checking ptrdiff size\n==================\n*/\nstatic void SV_AllocStringPool( void )\n{\n#if XASH_64BIT\n\tvoid *ptr = NULL;\n\tstring lenstr;\n\n\tCon_Reportf( \"%s()\\n\", __func__ );\n\tif( Sys_GetParmFromCmdLine( \"-str64alloc\", lenstr ))\n\t{\n\t\tstr64.maxstringarray = Q_atoi( lenstr );\n\t\tif( str64.maxstringarray < 1024 || str64.maxstringarray >= INT_MAX )\n\t\t\tstr64.maxstringarray = 65536 * Q_ceil( GI->max_edicts / 1024.0f );\n\t}\n\telse str64.maxstringarray = 65536 * Q_ceil( GI->max_edicts / 1024.0f );\n\tif( Sys_CheckParm( \"-str64dup\" ) )\n\t\tstr64.allowdup = true;\n\n#if USE_MMAP\n\t{\n\t\tuint flags;\n\t\tsize_t pagesize = sysconf( _SC_PAGESIZE );\n\t\tint arrlen = (str64.maxstringarray * 2) & ~(pagesize - 1);\n\t\tvoid *base = svgame.dllFuncs.pfnGameInit;\n\t\tvoid *start = svgame.hInstance - arrlen;\n\n#if defined(MAP_ANON)\n\t\tflags = MAP_ANON | MAP_PRIVATE;\n#elif defined(MAP_ANONYMOUS)\n\t\tflags = MAP_ANONYMOUS | MAP_PRIVATE;\n#endif\n\n\t\twhile( start - base > INT_MIN )\n\t\t{\n\t\t\tvoid *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 );\n\t\t\tif( mapptr && mapptr != (void*)-1 && mapptr - base > INT_MIN && mapptr - base < INT_MAX )\n\t\t\t{\n\t\t\t\tptr = mapptr;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif( mapptr ) munmap( mapptr, arrlen );\n\t\t\tstart -= arrlen;\n\t\t}\n\n\t\tif( !ptr )\n\t\t{\n\t\t\tstart = base;\n\t\t\twhile( start - base < INT_MAX )\n\t\t\t{\n\t\t\t\tvoid *mapptr = mmap((void*)((unsigned long)start & ~(pagesize - 1)), arrlen, PROT_READ | PROT_WRITE, flags, 0, 0 );\n\t\t\t\tif( mapptr && mapptr != (void*)-1  && mapptr - base > INT_MIN && mapptr - base < INT_MAX )\n\t\t\t\t{\n\t\t\t\t\tptr = mapptr;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif( mapptr ) munmap( mapptr, arrlen );\n\t\t\t\tstart += arrlen;\n\t\t\t}\n\t\t}\n\n\n\t\tif( ptr )\n\t\t{\n\t\t\tCon_Reportf( \"%s: Allocated string array near the server library: %p %p\\n\", __func__, base, ptr );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Reportf( \"%s: Failed to allocate string array near the server library!\\n\", __func__ );\n\t\t\tptr = str64.staticstringarray = Mem_Calloc( host.mempool, str64.maxstringarray * 2 );\n\t\t}\n\t}\n#else // !USE_MMAP\n\tptr = str64.staticstringarray = Mem_Calloc( host.mempool, str64.maxstringarray * 2 );\n#endif // !USE_MMAP\n\n\tstr64.pstringarray = ptr;\n\tstr64.pstringarraystatic = (byte*)ptr + str64.maxstringarray;\n\tstr64.pstringbase = str64.poldstringbase = ptr;\n\tstr64.plast = (byte*)ptr + 1;\n\tsvgame.globals->pStringBase = ptr;\n#else // !XASH_64BIT\n\tsvgame.globals->pStringBase = \"\";\n#endif // !XASH_64BIT\n\n\tsvgame.stringspool = Mem_AllocPool( \"Server Strings\" );\n}\n\nstatic void SV_FreeStringPool( void )\n{\n#if XASH_64BIT\n\tCon_Reportf( \"%s()\\n\", __func__ );\n\n#if USE_MMAP\n\tif( str64.pstringarray != str64.staticstringarray )\n\t\tmunmap( str64.pstringarray, (str64.maxstringarray * 2) & ~(sysconf( _SC_PAGESIZE ) - 1) );\n\telse\n#endif // USE_MMAP\n\t{\n\t\tMem_Free( str64.staticstringarray );\n\t}\n#else // !XASH_64BIT\n\tMem_FreePool( &svgame.stringspool );\n#endif // !XASH_64BIT\n}\n\n/*\n============\nSV_ProcessString\n\nProcess newly allocated string\npass NULL pointer to dst to get required length incl. null terminator\n============\n*/\nstatic uint SV_ProcessString( char *dst, const char *src )\n{\n\tconst char *p;\n\tuint i = 0;\n\n\tp = src;\n\n\twhile( *p )\n\t{\n\t\tif( *p == '\\\\' )\n\t\t{\n\t\t\tchar replace = 0;\n\n\t\t\tswitch( p[1] )\n\t\t\t{\n\t\t\tcase 'n': replace = '\\n'; break;\n\t\t\t// GoldSrc doesn't replace these symbols\n\t\t\t// but old hack in pfnWriteString did\n\t\t\tcase 'r': replace = '\\r'; break;\n\t\t\tcase 't': replace = '\\t'; break;\n\t\t\t}\n\n\t\t\tif( replace )\n\t\t\t{\n\t\t\t\tif( dst )\n\t\t\t\t\tdst[i] = replace;\n\t\t\t\ti++;\n\t\t\t\tp += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif( dst )\n\t\t\tdst[i] = *p;\n\t\ti++;\n\t\tp++;\n\t}\n\n\t// null terminator\n\tif( dst )\n\t\tdst[i] = '\\0';\n\ti++;\n\n\treturn i;\n}\n\n/*\n=============\nSV_AllocString\n\nallocate new engine string\non 64bit platforms find in array string if deduplication enabled (default)\nif not found, add to array\nuse -str64dup to disable deduplication, -str64alloc to set array size\n=============\n*/\nstring_t GAME_EXPORT SV_AllocString( const char *szValue )\n{\n\tuint len = SV_ProcessString( NULL, szValue );\n\tchar *processed_string = Mem_Calloc( svgame.stringspool, len );\n\tchar *dupe_string = NULL;\n\tqboolean found_dupe = false;\n\n\t(void)dupe_string;\n\t(void)found_dupe;\n\n\tSV_ProcessString( processed_string, szValue );\n\n\tif( svgame.physFuncs.pfnAllocString != NULL )\n\t{\n\t\tstring_t i = svgame.physFuncs.pfnAllocString( processed_string );\n\t\tMem_Free( processed_string );\n\t\treturn i;\n\t}\n\n#if XASH_64BIT\n\tif( !str64.allowdup )\n\t{\n\t\tfor( dupe_string = str64.poldstringbase + 1; dupe_string < str64.plast; dupe_string += Q_strlen( dupe_string ) + 1 )\n\t\t{\n\t\t\tif( !Q_strcmp( dupe_string, processed_string ))\n\t\t\t{\n\t\t\t\tfound_dupe = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !found_dupe )\n\t{\n\t\tif( str64.plast - str64.poldstringbase + len + 1 > str64.maxstringarray )\n\t\t{\n\t\t\tstr64.plast = str64.pstringbase + 1;\n\t\t\tstr64.poldstringbase = str64.pstringbase;\n\t\t\tstr64.numoverflows++;\n\t\t}\n\n\t\t//MsgDev( D_NOTE, \"SV_AllocString: %ld %s\\n\", str64.plast - svgame.globals->pStringBase, processed_string );\n\t\tQ_strncpy( str64.plast, processed_string, len );\n\t\tstr64.totalalloc += len;\n\n\t\tdupe_string = str64.plast;\n\t\tstr64.plast += len;\n\t}\n\telse\n\t{\n\t\tstr64.numdups++;\n\t\t//MsgDev( D_NOTE, \"SV_AllocString: dup %ld %s\\n\", dupe_string - svgame.globals->pStringBase, processed_string );\n\t}\n\n\tif( dupe_string - str64.pstringarray > str64.maxalloc )\n\t\tstr64.maxalloc = dupe_string - str64.pstringarray;\n\n\tMem_Free( processed_string );\n\n\treturn dupe_string - svgame.globals->pStringBase;\n#else // !XASH_64BIT\n\treturn processed_string - svgame.globals->pStringBase;\n#endif // !XASH_64BIT\n}\n\nvoid SV_PrintStr64Stats_f( void )\n{\n#if XASH_64BIT\n\tCon_Printf( \"====================\\n\" );\n\tCon_Printf( \"64 bit string pool statistics\\n\" );\n\tCon_Printf( \"====================\\n\" );\n\tCon_Printf( \"string array size: %lu\\n\", str64.maxstringarray );\n\tCon_Printf( \"total alloc %lu\\n\", str64.totalalloc );\n\tCon_Printf( \"maximum array usage: %lu\\n\", str64.maxalloc );\n\tCon_Printf( \"overflow counter: %lu\\n\", str64.numoverflows );\n\tCon_Printf( \"dup string counter: %lu\\n\", str64.numdups );\n#else // !XASH_64BIT\n\tCon_Printf( \"Not implemented\\n\" );\n#endif // !XASH_64BIT\n}\n\n/*\n=============\nSV_MakeString\n\nmake constant string\n=============\n*/\nstring_t SV_MakeString( const char *szValue )\n{\n\tif( svgame.physFuncs.pfnMakeString != NULL )\n\t\treturn svgame.physFuncs.pfnMakeString( szValue );\n#if XASH_64BIT\n\t{\n\t\tlong long ptrdiff = szValue - svgame.globals->pStringBase;\n\t\tif( ptrdiff > INT_MAX || ptrdiff < INT_MIN )\n\t\t\treturn SV_AllocString(szValue);\n\t\telse\n\t\t\treturn (int)ptrdiff;\n\t}\n#else // !XASH_64BIT\n\treturn szValue - svgame.globals->pStringBase;\n#endif // !XASH_64BIT\n}\n\n/*\n=============\nSV_GetString\n\n=============\n*/\nconst char *GAME_EXPORT SV_GetString( string_t iString )\n{\n\tif( svgame.physFuncs.pfnGetString != NULL )\n\t\treturn svgame.physFuncs.pfnGetString( iString );\n\treturn (svgame.globals->pStringBase + iString);\n}\n\n/*\n=============\npfnGetVarsOfEnt\n\n=============\n*/\nstatic entvars_t *GAME_EXPORT pfnGetVarsOfEnt( edict_t *pEdict )\n{\n\tif( pEdict )\n\t\treturn &pEdict->v;\n\treturn NULL;\n}\n\n/*\n=============\npfnPEntityOfEntOffset\n\n=============\n*/\nstatic edict_t *GAME_EXPORT pfnPEntityOfEntOffset( int iEntOffset )\n{\n\treturn (edict_t *)((byte *)svgame.edicts + iEntOffset);\n}\n\n/*\n=============\npfnEntOffsetOfPEntity\n\n=============\n*/\nstatic int GAME_EXPORT pfnEntOffsetOfPEntity( const edict_t *pEdict )\n{\n\treturn (byte *)pEdict - (byte *)svgame.edicts;\n}\n\n/*\n=============\npfnIndexOfEdict\n\n=============\n*/\nint GAME_EXPORT pfnIndexOfEdict( const edict_t *pEdict )\n{\n\tint\tnumber;\n\n\tif( !pEdict ) return 0; // world ?\n\n\tnumber = NUM_FOR_EDICT( pEdict );\n\tif( number < 0 || number > GI->max_edicts )\n\t\tHost_Error( \"bad entity number %d\\n\", number );\n\treturn number;\n}\n\n/*\n=============\npfnPEntityOfEntIndex\n\n=============\n*/\nstatic edict_t *pfnPEntityOfEntIndexBroken( int iEntIndex )\n{\n\t// have to be bug-compatible with GoldSrc in this function\n\treturn SV_PEntityOfEntIndex( iEntIndex, false );\n}\n\n/*\n=============\npfnPEntityOfEntIndexAllEntities\n\n=============\n*/\nstatic edict_t *GAME_EXPORT pfnPEntityOfEntIndexAllEntities( int iEntIndex )\n{\n\treturn SV_PEntityOfEntIndex( iEntIndex, true );\n}\n\n/*\n=============\npfnFindEntityByVars\n\ndebug thing\n=============\n*/\nstatic edict_t *GAME_EXPORT pfnFindEntityByVars( entvars_t *pvars )\n{\n\tedict_t\t*pEdict;\n\tint\ti;\n\n\t// don't pass invalid arguments\n\tif( !pvars ) return NULL;\n\n\tfor( i = 0; i < GI->max_edicts; i++ )\n\t{\n\t\tpEdict = EDICT_NUM( i );\n\n\t\t// g-cont: we should compare pointers\n\t\tif( &pEdict->v == pvars )\n\t\t\treturn pEdict; // found it\n\t}\n\n\treturn NULL;\n}\n\n/*\n=============\npfnGetModelPtr\n\nreturns pointer to a studiomodel\n=============\n*/\nstatic void *pfnGetModelPtr( edict_t *pEdict )\n{\n\tmodel_t\t*mod;\n\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn NULL;\n\n\tmod = SV_ModelHandle( pEdict->v.modelindex );\n\treturn Mod_StudioExtradata( mod );\n}\n\n/*\n=============\nSV_SendUserReg\n\n=============\n*/\nvoid SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user )\n{\n\tMSG_BeginServerCmd( msg, svc_usermessage );\n\tMSG_WriteByte( msg, user->number );\n\tMSG_WriteWord( msg, (word)user->size );\n\tMSG_WriteString( msg, user->name );\n}\n\n/*\n=============\npfnRegUserMsg\n\n=============\n*/\nstatic int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize )\n{\n\tint\ti;\n\n\tif( !COM_CheckString( pszName ))\n\t\treturn svc_bad;\n\n\tif( Q_strlen( pszName ) >= sizeof( svgame.msg[0].name ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: too long name %s\\n\", __func__, pszName );\n\t\treturn svc_bad; // force error\n\t}\n\n\tif( iSize > MAX_USERMSG_LENGTH )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: %s has too big size %i\\n\", __func__, pszName, iSize );\n\t\treturn svc_bad; // force error\n\t}\n\n\t// make sure what size inrange\n\tiSize = bound( -1, iSize, MAX_USERMSG_LENGTH );\n\n\t// message 0 is reserved for svc_bad\n\tfor( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ )\n\t{\n\t\t// see if already registered\n\t\tif( !Q_strcmp( svgame.msg[i].name, pszName ))\n\t\t\treturn svgame.msg[i].number;\n\t}\n\n\tif( i == MAX_USER_MESSAGES )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: user messages limit exceeded\\n\", __func__ );\n\t\treturn svc_bad;\n\t}\n\n\t// register new message\n\tQ_strncpy( svgame.msg[i].name, pszName, sizeof( svgame.msg[i].name ));\n\tsvgame.msg[i].number = svc_lastmsg + i;\n\tsvgame.msg[i].size = iSize;\n\n\tif( sv.state == ss_active )\n\t{\n\t\t// tell the client about new user message\n\t\tSV_SendUserReg( &sv.multicast, &svgame.msg[i] );\n\t\tSV_Multicast( MSG_ALL, NULL, NULL, false, false );\n\t}\n\n\treturn svgame.msg[i].number;\n}\n\n/*\n=============\npfnAnimationAutomove\n\nOBSOLETE, UNUSED\n=============\n*/\nstatic void GAME_EXPORT pfnAnimationAutomove( const edict_t* pEdict, float flTime )\n{\n}\n\n/*\n=============\npfnGetBonePosition\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetBonePosition( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles )\n{\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn;\n\tMod_GetBonePosition( pEdict, iBone, rgflOrigin, rgflAngles );\n}\n\n/*\n=============\npfnFunctionFromName\n\n=============\n*/\nstatic void *GAME_EXPORT pfnFunctionFromName( const char *pName )\n{\n\treturn COM_FunctionFromName_SR( svgame.hInstance, pName );\n}\n\n/*\n=============\npfnNameForFunction\n\n=============\n*/\nstatic const char *GAME_EXPORT pfnNameForFunction( void *function )\n{\n\treturn COM_NameForFunction( svgame.hInstance, function );\n}\n\n/*\n=============\npfnClientPrintf\n\n=============\n*/\nstatic void GAME_EXPORT pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg )\n{\n\tsv_client_t\t*client;\n\n\tif(( client = SV_ClientFromEdict( pEdict, false )) == NULL )\n\t{\n\t\tCon_Printf( \"tried to sprint to a non-client\\n\" );\n\t\treturn;\n\t}\n\n\tif( FBitSet( client->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tswitch( ptype )\n\t{\n\tcase print_console:\n\tcase print_chat:\n\t\tSV_ClientPrintf( client, \"%s\", szMsg );\n\t\tbreak;\n\tcase print_center:\n\t\tMSG_BeginServerCmd( &client->netchan.message, svc_centerprint );\n\t\tMSG_WriteString( &client->netchan.message, szMsg );\n\t\tbreak;\n\t}\n}\n\n/*\n=============\npfnServerPrint\n\nprint to the server console\n=============\n*/\nstatic void GAME_EXPORT pfnServerPrint( const char *szMsg )\n{\n\tif( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\tSV_BroadcastPrintf( NULL, \"%s\", szMsg );\n\telse Con_Printf( \"%s\", szMsg );\n}\n\n/*\n=============\npfnGetAttachment\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles )\n{\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn;\n\tMod_StudioGetAttachment( pEdict, iAttachment, rgflOrigin, rgflAngles );\n}\n\n/*\n=============\npfnCrosshairAngle\n\n=============\n*/\nstatic void GAME_EXPORT pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw )\n{\n\tsv_client_t\t*client;\n\n\tif(( client = SV_ClientFromEdict( pClient, true )) == NULL )\n\t\treturn;\n\n\t// fakeclients ignores it silently\n\tif( FBitSet( client->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tif( pitch > 180.0f ) pitch -= 360;\n\tif( pitch < -180.0f ) pitch += 360;\n\tif( yaw > 180.0f ) yaw -= 360;\n\tif( yaw < -180.0f ) yaw += 360;\n\n\tMSG_BeginServerCmd( &client->netchan.message, svc_crosshairangle );\n\tMSG_WriteChar( &client->netchan.message, pitch * 5 );\n\tMSG_WriteChar( &client->netchan.message, yaw * 5 );\n}\n\n/*\n=============\npfnSetView\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent )\n{\n\tsv_client_t\t*client;\n\tint\t\tviewEnt;\n\n\tif( !SV_IsValidEdict( pClient ))\n\t\treturn;\n\n\tif(( client = SV_ClientFromEdict( pClient, false )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: not a client!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tif( !SV_IsValidEdict( pViewent ) || pClient == pViewent )\n\t\tclient->pViewEntity = NULL; // just reset viewentity\n\telse client->pViewEntity = (edict_t *)pViewent;\n\n\t// fakeclients ignore to send client message (but can see into the trigger_camera through the PVS)\n\tif( FBitSet( client->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tif( client->pViewEntity )\n\t\tviewEnt = NUM_FOR_EDICT( client->pViewEntity );\n\telse viewEnt = NUM_FOR_EDICT( client->edict );\n\n\tMSG_BeginServerCmd( &client->netchan.message, svc_setview );\n\tMSG_WriteWord( &client->netchan.message, viewEnt );\n}\n\n/*\n=============\npfnStaticDecal\n\n=============\n*/\nstatic void GAME_EXPORT pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex )\n{\n\tSV_CreateDecal( &sv.signon, origin, decalIndex, entityIndex, modelIndex, FDECAL_PERMANENT, 1.0f );\n}\n\n/*\n=============\npfnIsDedicatedServer\n\n=============\n*/\nstatic int GAME_EXPORT pfnIsDedicatedServer( void )\n{\n\treturn Host_IsDedicated();\n}\n\n/*\n=============\npfnGetPlayerWONId\n\nOBSOLETE, UNUSED\n=============\n*/\nstatic uint GAME_EXPORT pfnGetPlayerWONId( edict_t *e )\n{\n\treturn (uint)-1;\n}\n\n/*\n=============\npfnIsMapValid\n\nvaild map must contain one info_player_deatchmatch\n=============\n*/\nint GAME_EXPORT pfnIsMapValid( char *filename )\n{\n\tuint\tflags = SV_MapIsValid( filename, NULL );\n\n\tif( FBitSet( flags, MAP_IS_EXIST ))\n\t\treturn true;\n\treturn false;\n}\n\n/*\n=============\npfnCvar_RegisterEngineVariable\n\nuse with precaution: this cvar will NOT unlinked\nafter game.dll is unloaded\n=============\n*/\nstatic void GAME_EXPORT pfnCvar_RegisterEngineVariable( cvar_t *variable )\n{\n\tCvar_RegisterVariable( (convar_t *)variable );\n}\n\n/*\n=============\npfnFadeClientVolume\n\n=============\n*/\nstatic void GAME_EXPORT pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds )\n{\n\tsv_client_t\t*cl;\n\n\tif(( cl = SV_ClientFromEdict( pEdict, true )) == NULL )\n\t\treturn;\n\n\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn;\n\n\tMSG_BeginServerCmd( &cl->netchan.message, svc_soundfade );\n\tMSG_WriteByte( &cl->netchan.message, fadePercent );\n\tMSG_WriteByte( &cl->netchan.message, holdTime );\n\tMSG_WriteByte( &cl->netchan.message, fadeOutSeconds );\n\tMSG_WriteByte( &cl->netchan.message, fadeInSeconds );\n}\n\n/*\n=============\npfnSetClientMaxspeed\n\nfakeclients can be changed speed to\n=============\n*/\nstatic void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed )\n{\n\tsv_client_t\t*cl;\n\n\t// not spawned clients allowed\n\tif(( cl = SV_ClientFromEdict( pEdict, false )) == NULL )\n\t\treturn;\n\n\t// GoldSrc doesn't bound the value to the movevar here\n\tfNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed );\n\n\t// There isn't any reference to \"maxspd\" anywhere except some commented-out code in SDK\n\tInfo_SetValueForKeyf( cl->physinfo, \"maxspd\", MAX_INFO_STRING, \"%.f\", fNewMaxspeed );\n\n\tcl->edict->v.maxspeed = fNewMaxspeed;\n}\n\n/*\n=============\npfnRunPlayerMove\n\n=============\n*/\nstatic void GAME_EXPORT pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec )\n{\n\tsv_client_t\t*cl, *oldcl;\n\tusercmd_t\t\tcmd;\n\tuint\t\tseed;\n\n\tif(( cl = SV_ClientFromEdict( pClient, true )) == NULL )\n\t\treturn;\n\n\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\treturn; // only fakeclients allows\n\n\toldcl = sv.current_client;\n\n\tsv.current_client = SV_ClientFromEdict( pClient, true );\n\tsv.current_client->timebase = (sv.time + sv.frametime) - ((double)msec / 1000.0);\n\n\tmemset( &cmd, 0, sizeof( cmd ));\n\tVectorCopy( viewangles, cmd.viewangles );\n\tcmd.forwardmove = fmove;\n\tcmd.sidemove = smove;\n\tcmd.upmove = upmove;\n\tcmd.buttons = buttons;\n\tcmd.impulse = impulse;\n\tcmd.msec = msec;\n\n\tseed = COM_RandomLong( 0, 0x7fffffff ); // full range\n\n\tSV_RunCmd( cl, &cmd, seed );\n\n\tcl->lastcmd = cmd;\n\tsv.current_client = oldcl;\n}\n\n/*\n=============\npfnNumberOfEntities\n\nreturns actual entity count\n=============\n*/\nint GAME_EXPORT pfnNumberOfEntities( void )\n{\n\tint\ti, total = 0;\n\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tif( svgame.edicts[i].free )\n\t\t\tcontinue;\n\t\ttotal++;\n\t}\n\n\treturn total;\n}\n\n/*\n=============\npfnGetInfoKeyBuffer\n\n=============\n*/\nstatic char *GAME_EXPORT pfnGetInfoKeyBuffer( edict_t *e )\n{\n\tsv_client_t\t*cl;\n\n\t// NULL passes localinfo\n\tif( !SV_IsValidEdict( e ))\n\t\treturn svs.localinfo;\n\n\t// world passes serverinfo\n\tif( e == svgame.edicts )\n\t\treturn svs.serverinfo;\n\n\t// userinfo for specified edict\n\tif(( cl = SV_ClientFromEdict( e, false )) != NULL )\n\t\treturn cl->userinfo;\n\n\treturn (char*)\"\"; // assume error\n}\n\n/*\n=============\npfnSetValueForKey\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetValueForKey( char *infobuffer, char *key, char *value )\n{\n\tif( infobuffer == svs.localinfo )\n\t\tInfo_SetValueForStarKey( infobuffer, key, value, MAX_LOCALINFO_STRING );\n\telse if( infobuffer == svs.serverinfo )\n\t\tInfo_SetValueForStarKey( infobuffer, key, value, MAX_SERVERINFO_STRING );\n\telse Con_Printf( S_ERROR \"can't set client keys with SetValueForKey\\n\" );\n}\n\n/*\n=============\npfnSetClientKeyValue\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value )\n{\n\tsv_client_t\t*cl;\n\n\tif( infobuffer == svs.localinfo || infobuffer == svs.serverinfo )\n\t\treturn;\n\n\tclientIndex -= 1;\n\n\tif( !svs.clients || clientIndex < 0 || clientIndex >= svs.maxclients )\n\t\treturn;\n\n\t// value not changed?\n\tif( !Q_strcmp( Info_ValueForKey( infobuffer, key ), value ))\n\t\treturn;\n\n\tcl = &svs.clients[clientIndex];\n\n\tInfo_SetValueForStarKey( infobuffer, key, value, MAX_INFO_STRING );\n\tSetBits( cl->flags, FCL_RESEND_USERINFO );\n\tcl->next_sendinfotime = 0.0;\t// send immediately\n}\n\n/*\n=============\npfnGetPhysicsKeyValue\n\n=============\n*/\nstatic const char *GAME_EXPORT pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key )\n{\n\tsv_client_t\t*cl;\n\n\t// pfnUserInfoChanged passed\n\tif(( cl = SV_ClientFromEdict( pClient, false )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: tried to a non-client!\\n\", __func__ );\n\t\treturn \"\";\n\t}\n\n\treturn Info_ValueForKey( cl->physinfo, key );\n}\n\n/*\n=============\npfnSetPhysicsKeyValue\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value )\n{\n\tsv_client_t\t*cl;\n\n\t// pfnUserInfoChanged passed\n\tif(( cl = SV_ClientFromEdict( pClient, false )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: tried to a non-client!\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tInfo_SetValueForKey( cl->physinfo, key, value, MAX_INFO_STRING );\n}\n\n/*\n=============\npfnGetPhysicsInfoString\n\n=============\n*/\nstatic const char *GAME_EXPORT pfnGetPhysicsInfoString( const edict_t *pClient )\n{\n\tsv_client_t\t*cl;\n\n\t// pfnUserInfoChanged passed\n\tif(( cl = SV_ClientFromEdict( pClient, false )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: tried to a non-client!\\n\", __func__ );\n\t\treturn \"\";\n\t}\n\n\treturn cl->physinfo;\n}\n\n/*\n=============\npfnPrecacheEvent\n\nregister or returns already registered event id\na type of event is ignored at this moment\n=============\n*/\nstatic word GAME_EXPORT pfnPrecacheEvent( int type, const char *psz )\n{\n\treturn (word)SV_EventIndex( psz );\n}\n\n/*\n=============\npfnPlaybackEvent\n\n=============\n*/\nvoid GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )\n{\n\tsv_client_t\t*cl;\n\tevent_state_t\t*es;\n\tevent_args_t\targs;\n\tevent_info_t\t*ei = NULL;\n\tint\t\tj, slot, bestslot;\n\tint\t\tinvokerIndex;\n\tbyte\t\t*mask = NULL;\n\tvec3_t\t\tpvspoint;\n\n\tif( FBitSet( flags, FEV_CLIENT ))\n\t\treturn;\t// someone stupid joke\n\n\t// first check event for out of bounds\n\tif( eventindex < 1 || eventindex >= MAX_EVENTS )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: invalid eventindex %i\\n\", __func__, eventindex );\n\t\treturn;\n\t}\n\n\t// check event for precached\n\tif( !COM_CheckString( sv.event_precache[eventindex] ))\n\t{\n\t\tCon_Printf( S_ERROR \"%s: event %i was not precached\\n\", __func__, eventindex );\n\t\treturn;\n\t}\n\n\tmemset( &args, 0, sizeof( args ));\n\n\tif( origin && !VectorIsNull( origin ))\n\t{\n\t\tVectorCopy( origin, args.origin );\n\t\targs.flags |= FEVENT_ORIGIN;\n\t}\n\n\tif( angles && !VectorIsNull( angles ))\n\t{\n\t\tVectorCopy( angles, args.angles );\n\t\targs.flags |= FEVENT_ANGLES;\n\t}\n\n\t// copy other parms\n\targs.fparam1 = fparam1;\n\targs.fparam2 = fparam2;\n\targs.iparam1 = iparam1;\n\targs.iparam2 = iparam2;\n\targs.bparam1 = bparam1;\n\targs.bparam2 = bparam2;\n\n\tVectorClear( pvspoint );\n\n\tif( SV_IsValidEdict( pInvoker ))\n\t{\n\t\t// add the view_ofs to avoid problems with crossed contents line\n\t\tVectorAdd( pInvoker->v.origin, pInvoker->v.view_ofs, pvspoint );\n\t\targs.entindex = invokerIndex = NUM_FOR_EDICT( pInvoker );\n\n\t\t// g-cont. allow 'ducking' param for all entities\n\t\targs.ducking = FBitSet( pInvoker->v.flags, FL_DUCKING ) ? true : false;\n\n\t\t// this will be send only for reliable event\n\t\tif( !FBitSet( args.flags, FEVENT_ORIGIN ))\n\t\t\tVectorCopy( pInvoker->v.origin, args.origin );\n\n\t\t// this will be send only for reliable event\n\t\tif( !FBitSet( args.flags, FEVENT_ANGLES ))\n\t\t\tVectorCopy( pInvoker->v.angles, args.angles );\n\t}\n\telse\n\t{\n\t\tVectorCopy( args.origin, pvspoint );\n\t\targs.entindex = 0;\n\t\tinvokerIndex = -1;\n\t}\n\n\tif( !FBitSet( flags, FEV_GLOBAL ) && VectorIsNull( pvspoint ))\n\t{\n\t\tCon_DPrintf( S_ERROR \"%s: not a FEV_GLOBAL event missing origin. Ignored.\\n\", sv.event_precache[eventindex] );\n\t\treturn;\n\t}\n\n\t// check event for some user errors\n\tif( FBitSet( flags, FEV_NOTHOST|FEV_HOSTONLY ))\n\t{\n\t\tif( !SV_ClientFromEdict( pInvoker, true ))\n\t\t{\n\t\t\tconst char *ev_name = sv.event_precache[eventindex];\n\n\t\t\tif( FBitSet( flags, FEV_NOTHOST ))\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_WARN \"%s: specified FEV_NOTHOST when invoker not a client\\n\", ev_name );\n\t\t\t\tClearBits( flags, FEV_NOTHOST );\n\t\t\t}\n\n\t\t\tif( FBitSet( flags, FEV_HOSTONLY ))\n\t\t\t{\n\t\t\t\tCon_DPrintf( S_WARN \"%s: specified FEV_HOSTONLY when invoker not a client\\n\", ev_name );\n\t\t\t\tClearBits( flags, FEV_HOSTONLY );\n\t\t\t}\n\t\t}\n\t}\n\n\tSetBits( flags, FEV_SERVER );\t\t// it's a server event!\n\tif( delay < 0.0f ) delay = 0.0f;\t// fixup negative delays\n\n\t// setup pvs cluster for invoker\n\tif( !FBitSet( flags, FEV_GLOBAL ))\n\t{\n\t\tMod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 ), true );\n\t\tmask = fatphs; // using the FatPVS like a PHS\n\t}\n\n\t// process all the clients\n\tfor( slot = 0, cl = svs.clients; slot < svs.maxclients; slot++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tif( SV_IsValidEdict( pInvoker ) && pInvoker->v.groupinfo && cl->edict->v.groupinfo )\n\t\t{\n\t\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( SV_IsValidEdict( pInvoker ))\n\t\t{\n\t\t\tif( !SV_CheckClientVisiblity( cl, mask ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// a1ba: GoldSrc never cleans up host_client pointer (similar to sv.current_client)\n\t\t// so it's always points at some client and in singleplayer this check always succeedes\n\t\t// in Xash, however, sv.current_client might be reset and set to NULL\n\t\t// this is especially dangerous when weapons play events in Think functions\n\t\t//\n\t\t// IMHO, it doesn't make sense to me to compare it against current client when we have\n\t\t// invoker edict pointer but to preserve behaviour check for them both\n\t\t//\n\t\t// if it breaks some mods, probably sv.current_client semantics must be reworked to match GoldSrc\n\t\tif( FBitSet( flags, FEV_NOTHOST ) && ( cl == sv.current_client || cl->edict == pInvoker ) && FBitSet( cl->flags, FCL_LOCAL_WEAPONS ))\n\t\t\tcontinue;\t// will be played on client side\n\n\t\tif( FBitSet( flags, FEV_HOSTONLY ) && cl->edict != pInvoker )\n\t\t\tcontinue;\t// sending only to invoker\n\n\t\t// all checks passed, send the event\n\n\t\t// reliable event\n\t\tif( FBitSet( flags, FEV_RELIABLE ))\n\t\t{\n\t\t\t// skipping queue, write direct into reliable datagram\n\t\t\tSV_PlaybackReliableEvent( &cl->netchan.message, eventindex, delay, &args );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// unreliable event (stores in queue)\n\t\tes = &cl->events;\n\t\tbestslot = -1;\n\n\t\tif( FBitSet( flags, FEV_UPDATE ))\n\t\t{\n\t\t\tfor( j = 0; j < MAX_EVENT_QUEUE; j++ )\n\t\t\t{\n\t\t\t\tei = &es->ei[j];\n\n\t\t\t\tif( ei->index == eventindex && invokerIndex != -1 && invokerIndex == ei->entity_index )\n\t\t\t\t{\n\t\t\t\t\tbestslot = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif( bestslot == -1 )\n\t\t{\n\t\t\tfor( j = 0; j < MAX_EVENT_QUEUE; j++ )\n\t\t\t{\n\t\t\t\tei = &es->ei[j];\n\n\t\t\t\tif( ei->index == 0 )\n\t\t\t\t{\n\t\t\t\t\t// found an empty slot\n\t\t\t\t\tbestslot = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// no slot found for this player, oh well\n\t\tif( bestslot == -1 ) continue;\n\n\t\t// add event to queue\n\t\tei->index = eventindex;\n\t\tei->fire_time = delay;\n\t\tei->entity_index = invokerIndex;\n\t\tei->packet_index = -1;\n\t\tei->flags = flags;\n\t\tei->args = args;\n\t}\n}\n\n/*\n=============\npfnGetCurrentPlayer\n\n=============\n*/\nstatic int GAME_EXPORT pfnGetCurrentPlayer( void )\n{\n\tint\tidx = sv.current_client - svs.clients;\n\n\tif( idx < 0 || idx >= svs.maxclients )\n\t\treturn -1;\n\treturn idx;\n}\n\n/*\n=============\npfnSetFatPVS\n\nThe client will interpolate the view position,\nso we can't use a single PVS point\n=============\n*/\nstatic byte *GAME_EXPORT pfnSetFatPVS( const float *org )\n{\n\tstatic byte fatpvs[(MAX_MAP_LEAFS+7)/8];\n\tqboolean\tfullvis = false, merge = false;\n\n\tif( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( ))\n\t\tfullvis = true;\n\n\tif( FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY ))\n\t\tmerge = true;\n\n\tMod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, merge, fullvis, false );\n\n\treturn fatpvs;\n}\n\n/*\n=============\npfnSetFatPHS\n\nThe client will interpolate the hear position,\nso we can't use a single PHS point\n=============\n*/\nstatic byte *GAME_EXPORT pfnSetFatPAS( const float *org )\n{\n\tqboolean\tfullvis = false, merge = false;\n\n\tif( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( ))\n\t\tfullvis = true;\n\n\tif( FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY ))\n\t\tmerge = true;\n\n\tMod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, merge, fullvis, true );\n\n\treturn fatphs;\n}\n\n/*\n=============\nMod_HeadnodeVisible\n=============\n*/\nstatic qboolean Mod_HeadnodeVisible( model_t *mod, mnode_t *node, const byte *visbits, int *lastleaf )\n{\n\tif( !node || node->contents == CONTENTS_SOLID )\n\t\treturn false;\n\n\tif( node->contents < 0 )\n\t{\n\t\tif( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster ))\n\t\t\treturn false;\n\n\t\tif( lastleaf )\n\t\t\t*lastleaf = ((mleaf_t *)node)->cluster;\n\t\treturn true;\n\t}\n\n\tif( Mod_HeadnodeVisible( mod, node_child( node, 0, mod ), visbits, lastleaf ))\n\t\treturn true;\n\n\tif( Mod_HeadnodeVisible( mod, node_child( node, 1, mod ), visbits, lastleaf ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n=============\npfnCheckVisibility\n\n=============\n*/\nstatic int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset )\n{\n\tint\ti, leafnum;\n\tqboolean large_leafs = FBitSet( sv.worldmodel->flags, MODEL_QBSP2 );\n\n\tif( !SV_IsValidEdict( ent ))\n\t\treturn 0;\n\n\t// vis not set - fullvis enabled\n\tif( !pset ) return 1;\n\n\tif( FBitSet( ent->v.flags, FL_CUSTOMENTITY ) && ent->v.owner && FBitSet( ent->v.owner->v.flags, FL_CLIENT ))\n\t\tent = ent->v.owner;\t// upcast beams to my owner\n\n\tif( ent->headnode < 0 )\n\t{\n\t\t// check individual leafs\n\t\tfor( i = 0; i < ent->num_leafs; i++ )\n\t\t{\n\t\t\tif( large_leafs )\n\t\t\t{\n\t\t\t\tif( CHECKVISBIT( pset, ent->leafnums32[i] ))\n\t\t\t\t\treturn 1;\t// visible passed by leaf\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( CHECKVISBIT( pset, ent->leafnums16[i] ))\n\t\t\t\t\treturn 1;\t// visible passed by leaf\n\t\t\t}\n\t\t}\n\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < MAX_ENT_LEAFS( large_leafs ); i++ )\n\t\t{\n\t\t\tif( large_leafs )\n\t\t\t\tleafnum = ent->leafnums32[i];\n\t\t\telse\n\t\t\t\tleafnum = ent->leafnums16[i];\n\t\t\tif( leafnum == -1 ) break;\n\n\t\t\tif( CHECKVISBIT( pset, leafnum ))\n\t\t\t\treturn 1;\t// visible passed by leaf\n\t\t}\n\n\t\t// too many leafs for individual check, go by headnode\n\t\tif( !Mod_HeadnodeVisible( sv.worldmodel, &sv.worldmodel->nodes[ent->headnode], pset, &leafnum ))\n\t\t\treturn 0;\n\n\t\tif( large_leafs )\n\t\t\t((edict_t *)ent)->leafnums32[ent->num_leafs] = leafnum;\n\t\telse\n\t\t\t((edict_t *)ent)->leafnums16[ent->num_leafs] = leafnum;\n\n\t\t((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS( large_leafs );\n\n\t\treturn 2;\t// visible passed by headnode\n\t}\n}\n\n/*\n=============\npfnCanSkipPlayer\n\n=============\n*/\nstatic int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player )\n{\n\tsv_client_t\t*cl;\n\n\tif(( cl = SV_ClientFromEdict( player, false )) == NULL )\n\t\treturn false;\n\n\treturn FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) ? true : false;\n}\n\n/*\n=============\npfnSetGroupMask\n\n=============\n*/\nstatic void GAME_EXPORT pfnSetGroupMask( int mask, int op )\n{\n\tsvs.groupmask = mask;\n\tsvs.groupop = op;\n}\n\n/*\n=============\npfnCreateInstancedBaseline\n\n=============\n*/\nstatic int GAME_EXPORT pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline )\n{\n\tif( !baseline || sv.num_instanced >= MAX_CUSTOM_BASELINES )\n\t\treturn 0;\n\n\t// g-cont. must sure that classname is really allocated\n\tsv.instanced[sv.num_instanced].classname = SV_CopyString( STRING( classname ));\n\tsv.instanced[sv.num_instanced].baseline = *baseline;\n\tsv.num_instanced++;\n\n\treturn sv.num_instanced;\n}\n\n/*\n=============\npfnEndSection\n\n=============\n*/\nstatic void GAME_EXPORT pfnEndSection( const char *pszSection )\n{\n\tif( !Q_stricmp( \"oem_end_credits\", pszSection ))\n\t\tHost_Credits ();\n\telse Cbuf_AddText( \"\\ndisconnect\\n\" );\n}\n\n/*\n=============\npfnGetPlayerUserId\n\n=============\n*/\nstatic int GAME_EXPORT pfnGetPlayerUserId( edict_t *e )\n{\n\tsv_client_t\t*cl;\n\n\tif(( cl = SV_ClientFromEdict( e, false )) == NULL )\n\t\treturn -1;\n\treturn cl->userid;\n}\n\n/*\n=============\npfnGetPlayerStats\n\n=============\n*/\nstatic void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss )\n{\n\tsv_client_t\t*cl;\n\n\tif( packet_loss ) *packet_loss = 0;\n\tif( ping ) *ping = 0;\n\n\tif(( cl = SV_ClientFromEdict( pClient, false )) == NULL )\n\t\treturn;\n\n\tif( packet_loss ) *packet_loss = cl->packet_loss;\n\tif( ping ) *ping = cl->latency * 1000;\n}\n\nstatic void GAME_EXPORT Cmd_AddServerCommand( const char *cmd_name, xcommand_t function )\n{\n\tCmd_AddCommandEx( cmd_name, function, \"server command\", CMD_SERVERDLL, __func__ );\n}\n\n/*\n=============\npfnForceUnmodified\n\n=============\n*/\nstatic void GAME_EXPORT pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename )\n{\n\tconsistency_t\t*pc;\n\tint\t\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn;\n\n\tif( sv.state == ss_loading )\n\t{\n\t\tfor( i = 0; i < MAX_MODELS; i++ )\n\t\t{\n\t\t\tpc = &sv.consistency_list[i];\n\n\t\t\tif( !pc->filename )\n\t\t\t{\n\t\t\t\tif( mins ) VectorCopy( mins, pc->mins );\n\t\t\t\tif( maxs ) VectorCopy( maxs, pc->maxs );\n\t\t\t\tpc->filename = SV_CopyString( filename );\n\t\t\t\tpc->check_type = type;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if( !Q_strcmp( filename, pc->filename ))\n\t\t\t\treturn;\n\t\t}\n\t\tHost_Error( \"MAX_MODELS limit exceeded (%d)\\n\", MAX_MODELS );\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < MAX_MODELS; i++ )\n\t\t{\n\t\t\tpc = &sv.consistency_list[i];\n\t\t\tif( !pc->filename ) continue;\n\n\t\t\tif( !Q_strcmp( filename, pc->filename ))\n\t\t\t\treturn;\n\t\t}\n\t\tCon_Printf( S_ERROR \"Failed to enforce consistency for %s: was not precached\\n\", filename );\n\t}\n}\n\n/*\n=============\npfnVoice_GetClientListening\n\n=============\n*/\nstatic qboolean GAME_EXPORT pfnVoice_GetClientListening( int iReceiver, int iSender )\n{\n\tiReceiver -= 1;\n\tiSender -= 1;\n\n\tif( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients )\n\t\treturn false;\n\n\treturn (FBitSet( svs.clients[iSender].listeners, BIT( iReceiver )) != 0 );\n}\n\n/*\n=============\npfnVoice_SetClientListening\n\n=============\n*/\nstatic qboolean GAME_EXPORT pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen )\n{\n\tiReceiver -= 1;\n\tiSender -= 1;\n\n\tif( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients )\n\t\treturn false;\n\n\tif( bListen ) SetBits( svs.clients[iSender].listeners, BIT( iReceiver ));\n\telse ClearBits( svs.clients[iSender].listeners, BIT( iReceiver ));\n\n\treturn true;\n}\n\n/*\n=============\npfnGetPlayerAuthId\n\nThese function must returns cd-key hashed value\nbut Xash3D currently doesn't have any security checks\nreturn nullstring for now\n=============\n*/\nstatic const char *GAME_EXPORT pfnGetPlayerAuthId( edict_t *e )\n{\n\treturn SV_GetClientIDString( SV_ClientFromEdict( e, false ));\n}\n\n/*\n=============\npfnQueryClientCvarValue\n\nrequest client cvar value\n=============\n*/\nstatic void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cvarName )\n{\n\tsv_client_t *cl;\n\n\tif( !COM_CheckString( cvarName ))\n\t\treturn;\n\n\tif(( cl = SV_ClientFromEdict( player, false )) != NULL )\n\t{\n\t\tMSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue );\n\t\tMSG_WriteString( &cl->netchan.message, cvarName );\n\t}\n\telse\n\t{\n\t\tif( svgame.dllFuncs2.pfnCvarValue )\n\t\t\tsvgame.dllFuncs2.pfnCvarValue( player, \"Bad Player\" );\n\t\tCon_Printf( S_ERROR \"%s: tried to send to a non-client!\\n\", __func__ );\n\t}\n}\n\n/*\n=============\npfnQueryClientCvarValue2\n\nrequest client cvar value (bugfixed)\n=============\n*/\nstatic void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID )\n{\n\tsv_client_t *cl;\n\n\tif( !COM_CheckString( cvarName ))\n\t\treturn;\n\n\tif(( cl = SV_ClientFromEdict( player, false )) != NULL )\n\t{\n\t\tMSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue2 );\n\t\tMSG_WriteLong( &cl->netchan.message, requestID );\n\t\tMSG_WriteString( &cl->netchan.message, cvarName );\n\t}\n\telse\n\t{\n\t\tif( svgame.dllFuncs2.pfnCvarValue2 )\n\t\t\tsvgame.dllFuncs2.pfnCvarValue2( player, requestID, cvarName, \"Bad Player\" );\n\t\tCon_Printf( S_ERROR \"%s: tried to send to a non-client!\\n\", __func__ );\n\t}\n}\n\n/*\n=============\npfnEngineStub\n\nextended iface stubs\n=============\n*/\nstatic int GAME_EXPORT pfnGetLocalizedStringLength( const char *label )\n{\n\treturn 0;\n}\n\n\n/*\n=============\npfnRegisterTutorMessageShown\n\nonly exists in PlayStation version\n=============\n*/\nstatic void GAME_EXPORT pfnRegisterTutorMessageShown( int mid )\n{\n}\n\n/*\n=============\npfnGetTimesTutorMessageShown\n\nonly exists in PlayStation version\n=============\n*/\nstatic int GAME_EXPORT pfnGetTimesTutorMessageShown( int mid )\n{\n\treturn 0;\n}\n\nstatic void GAME_EXPORT pfnGetGameDir( char *out )\n{\n\tchar rootdir[MAX_SYSPATH];\n\n\tif( !out )\n\t\treturn;\n\n\tif( !FBitSet( host.bugcomp, BUGCOMP_GET_GAME_DIR_FULL_PATH ))\n\t{\n\t\tQ_strncpy( out, GI->gamefolder, 256 );\n\t}\n\telse\n\t{\n\t\t// in GoldSrc pre-1.1.1.1, it's a full path to game directory, limited by 256 characters\n\t\t// however the full path might easily overflow that limitation\n\t\t// here we check if it would overflow and just return game folder in that case\n\t\tif( !g_fsapi.GetRootDirectory( rootdir, sizeof( rootdir ))\n\t\t\t|| Q_snprintf( out, 256, \"%s/%s\", rootdir, GI->gamefolder ) < 0 )\n\t\t{\n\t\t\tQ_strncpy( out, GI->gamefolder, 256 );\n\t\t}\n\t}\n}\n\n// engine callbacks\nstatic enginefuncs_t gEngfuncs =\n{\n\tpfnPrecacheModel,\n\tSV_SoundIndex,\n\tSV_SetModel,\n\tpfnModelIndex,\n\tpfnModelFrames,\n\tpfnSetSize,\n\tpfnChangeLevel,\n\tpfnGetSpawnParms,\n\tpfnSaveSpawnParms,\n\tpfnVecToYaw,\n\tVectorAngles,\n\tpfnMoveToOrigin,\n\tpfnChangeYaw,\n\tpfnChangePitch,\n\tSV_FindEntityByString,\n\tSV_LightForEntity,\n\tpfnFindEntityInSphere,\n\tpfnFindClientInPVS,\n\tpfnEntitiesInPVS,\n\tpfnMakeVectors,\n\tAngleVectors,\n\tSV_AllocEdict,\n\tpfnRemoveEntity,\n\tpfnCreateNamedEntity,\n\tpfnMakeStatic,\n\tpfnEntIsOnFloor,\n\tpfnDropToFloor,\n\tpfnWalkMove,\n\tpfnSetOrigin,\n\tSV_StartSound,\n\tpfnEmitAmbientSound,\n\tpfnTraceLine,\n\tpfnTraceToss,\n\tpfnTraceMonsterHull,\n\tpfnTraceHull,\n\tpfnTraceModel,\n\tpfnTraceTexture,\n\tpfnTraceSphere,\n\tpfnGetAimVector,\n\tpfnServerCommand,\n\tpfnServerExecute,\n\tpfnClientCommand,\n\tpfnParticleEffect,\n\tpfnLightStyle,\n\tpfnDecalIndex,\n\tSV_PointContents,\n\tpfnMessageBegin,\n\tpfnMessageEnd,\n\tpfnWriteByte,\n\tpfnWriteChar,\n\tpfnWriteShort,\n\tpfnWriteLong,\n\tpfnWriteAngle,\n\tpfnWriteCoord,\n\tpfnWriteString,\n\tpfnWriteEntity,\n\tpfnCvar_RegisterServerVariable,\n\tCvar_VariableValue,\n\tCvar_VariableString,\n\tCvar_SetValue,\n\tCvar_Set,\n\tpfnAlertMessage,\n\tpfnEngineFprintf,\n\tpfnPvAllocEntPrivateData,\n\tpfnPvEntPrivateData,\n\tSV_FreePrivateData,\n\tSV_GetString,\n\tSV_AllocString,\n\tpfnGetVarsOfEnt,\n\tpfnPEntityOfEntOffset,\n\tpfnEntOffsetOfPEntity,\n\tpfnIndexOfEdict,\n\tpfnPEntityOfEntIndexAllEntities,\n\tpfnFindEntityByVars,\n\tpfnGetModelPtr,\n\tpfnRegUserMsg,\n\tpfnAnimationAutomove,\n\tpfnGetBonePosition,\n\t(void*)pfnFunctionFromName,\n\t(void*)pfnNameForFunction,\n\tpfnClientPrintf,\n\tpfnServerPrint,\n\tCmd_Args,\n\tCmd_Argv,\n\t(void*)Cmd_Argc,\n\tpfnGetAttachment,\n\tCRC32_Init,\n\tCRC32_ProcessBuffer,\n\tCRC32_ProcessByte,\n\tCRC32_Final,\n\tCOM_RandomLong,\n\tCOM_RandomFloat,\n\tpfnSetView,\n\tpfnTime,\n\tpfnCrosshairAngle,\n\tCOM_LoadFileForMe,\n\tCOM_FreeFile,\n\tpfnEndSection,\n\tpfnCompareFileTime,\n\tpfnGetGameDir,\n\tpfnCvar_RegisterEngineVariable,\n\tpfnFadeClientVolume,\n\tpfnSetClientMaxspeed,\n\tSV_FakeConnect,\n\tpfnRunPlayerMove,\n\tpfnNumberOfEntities,\n\tpfnGetInfoKeyBuffer,\n\tInfo_ValueForKey,\n\tpfnSetValueForKey,\n\tpfnSetClientKeyValue,\n\tpfnIsMapValid,\n\tpfnStaticDecal,\n\tSV_GenericIndex,\n\tpfnGetPlayerUserId,\n\tpfnBuildSoundMsg,\n\tpfnIsDedicatedServer,\n\tpfnCVarGetPointer,\n\tpfnGetPlayerWONId,\n\t(void*)Info_RemoveKey,\n\tpfnGetPhysicsKeyValue,\n\tpfnSetPhysicsKeyValue,\n\tpfnGetPhysicsInfoString,\n\tpfnPrecacheEvent,\n\tSV_PlaybackEventFull,\n\tpfnSetFatPVS,\n\tpfnSetFatPAS,\n\tpfnCheckVisibility,\n\tDelta_SetField,\n\tDelta_UnsetField,\n\tDelta_AddEncoder,\n\tpfnGetCurrentPlayer,\n\tpfnCanSkipPlayer,\n\tDelta_FindField,\n\tDelta_SetFieldByIndex,\n\tDelta_UnsetFieldByIndex,\n\tpfnSetGroupMask,\n\tpfnCreateInstancedBaseline,\n\t(void*)Cvar_DirectSet,\n\tpfnForceUnmodified,\n\tpfnGetPlayerStats,\n\tCmd_AddServerCommand,\n\tpfnVoice_GetClientListening,\n\tpfnVoice_SetClientListening,\n\tpfnGetPlayerAuthId,\n\tpfnSequenceGet,\n\tpfnSequencePickSentence,\n\tCOM_FileSize,\n\tSound_GetApproxWavePlayLen,\n\tpfnIsCareerMatch,\n\tpfnGetLocalizedStringLength,\n\tpfnRegisterTutorMessageShown,\n\tpfnGetTimesTutorMessageShown,\n\tpfnProcessTutorMessageDecayBuffer,\n\tpfnConstructTutorMessageDecayBuffer,\n\tpfnResetTutorMessageDecayData,\n\tpfnQueryClientCvarValue,\n\tpfnQueryClientCvarValue2,\n\tCOM_CheckParm,\n\tpfnPEntityOfEntIndexAllEntities,\n};\n\nstatic void SV_FreeKeyValueStrings( KeyValueData *kvd, int numpairs )\n{\n\tfor( int i = 0; i < numpairs; i++ )\n\t{\n\t\tMem_Free( kvd[i].szKeyName );\n\t\tMem_Free( kvd[i].szValue );\n\t}\n}\n\n/*\n====================\nSV_ParseEdict\n\nParses an edict out of the given string, returning the new position\ned should be a properly initialized empty edict.\n====================\n*/\nstatic qboolean SV_ParseEdict( char **pfile, edict_t *ent )\n{\n\tKeyValueData\tpkvd[256]; // per one entity\n\tqboolean\t\tadjust_origin = false, customentity;\n\tint\t\ti, numpairs = 0;\n\tconst char\t*classname = NULL;\n\n\t// go through all the dictionary pairs\n\twhile( 1 )\n\t{\n\t\tstring\tkeyname;\n\t\tchar\tvalue[2048];\n\t\tint len;\n\n\t\t// parse key\n\t\tif(( *pfile = COM_ParseFile( *pfile, keyname, sizeof( keyname ))) == NULL )\n\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\n\t\tif( keyname[0] == '}' )\n\t\t\tbreak; // end of desc\n\n\t\t// parse value\n\t\tif(( *pfile = COM_ParseFile( *pfile, value, sizeof( value ))) == NULL )\n\t\t\tHost_Error( \"%s: EOF without closing brace\\n\", __func__ );\n\n\t\tif( value[0] == '}' )\n\t\t\tHost_Error( \"%s: closing brace without data\\n\", __func__ );\n\n\t\t// ignore attempts to set empty key or value\n\t\t// \"wad\" field is already handled\n\t\tif( !keyname[0] || !value[0] || !Q_strcmp( keyname, \"wad\" ))\n\t\t\tcontinue;\n\n\t\t// keynames with a leading underscore are used for\n\t\t// utility comments and are immediately discarded by engine\n\t\tif( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' )\n\t\t\tcontinue;\n\n\t\t// classname must be first\n\t\tif( !Q_strcmp( keyname, \"classname\" ))\n\t\t{\n\t\t\tKeyValueData kvd = {\n\t\t\t\t.szClassName = NULL,\n\t\t\t\t.szKeyName = keyname,\n\t\t\t\t.szValue = value,\n\t\t\t\t.fHandled = false\n\t\t\t};\n\n\t\t\t// don't allow double classnames\n\t\t\tif( classname != NULL )\n\t\t\t\tcontinue;\n\n\t\t\tsvgame.dllFuncs.pfnKeyValue( ent, &kvd );\n\n\t\t\t// ideally, all game dlls should handle classname.\n\t\t\t// throw an error for now, improve the logic if it causes\n\t\t\t// compatibility issues with Xash-based games\n\t\t\tif( !kvd.fHandled )\n\t\t\t\tHost_Error( \"%s: game didn't handled \\\"%s\\\" classname\\n\", __func__,\tvalue );\n\n\t\t\t// this lets game dll override custom entity classname\n\t\t\t// to something bogus that's exported in game dll\n\t\t\tclassname = STRING( ent->v.classname );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// GoldSrc removes trailing spaces\n\t\t// but does this after sucking out classname\n\t\t// which doesn't have similar check\n\t\tfor( len = Q_strlen( keyname ); len > 0 && keyname[len - 1] == ' '; len-- )\n\t\t\tkeyname[len - 1] = '\\0';\n\n\t\t// create keyvalue strings\n\t\tpkvd[numpairs].szClassName = (char*)\"\"; // unknown at this moment\n\t\tpkvd[numpairs].szKeyName = copystring( keyname );\n\t\tpkvd[numpairs].szValue = copystring( value );\n\t\tpkvd[numpairs].fHandled = false;\n\t\tnumpairs++;\n\n\t\tif( numpairs > ARRAYSIZE( pkvd ))\n\t\t{\n\t\t\tif( classname )\n\t\t\t\tCon_Printf( S_ERROR \"%s: too many keyvalue pairs for %s!\\n\", __func__, classname );\n\t\t\telse Con_Printf( S_ERROR \"%s: too many keyvalue pairs!\\n\", __func__ );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( classname == NULL )\n\t{\n\t\t// release allocated strings\n\t\tSV_FreeKeyValueStrings( pkvd, numpairs );\n\t\treturn false;\n\t}\n\n\tent = SV_AllocPrivateData( ent, ent->v.classname, &customentity );\n\n\tif( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME ))\n\t{\n\t\t// release allocated strings\n\t\tSV_FreeKeyValueStrings( pkvd, numpairs );\n\t\treturn false;\n\t}\n\n\tif( customentity )\n\t{\n\t\tKeyValueData kvd = {\n\t\t\t.szClassName = (char *)\"custom\",\n\t\t\t.szKeyName = (char *)\"customclass\",\n\t\t\t.szValue = (char *)classname,\n\t\t\t.fHandled = false\n\t\t};\n\n\t\tsvgame.dllFuncs.pfnKeyValue( ent, &kvd );\n\t\t// no fHandled check, GoldSrc behavior\n\t}\n\n#ifdef HACKS_RELATED_HLMODS\n\t// chemical existence have broked changelevels\n\tif( !Q_stricmp( GI->gamefolder, \"ce\" ))\n\t{\n\t \tif( !Q_stricmp( sv.name, \"ce08_02\" ) && !Q_stricmp( classname, \"info_player_start_force\" ))\n\t\t\tadjust_origin = true;\n\t}\n#endif\n\n\tfor( i = 0; i < numpairs; i++ )\n\t{\n\t\tchar *keyname, *value;\n\t\tchar temp[MAX_VA_STRING];\n\n#if 0 // this is stupid bug in GoldSrc, disable\n\t\tif( !Q_strcmp( pkvd[i].szValue, classname ))\n\t\t\tcontinue;\n#endif\n\n\t\tif( !Q_strcmp( pkvd[i].szKeyName, \"angle\" ))\n\t\t{\n\t\t\tfloat\tflYawAngle = Q_atof( pkvd[i].szValue );\n\n\t\t\tMem_Free( pkvd[i].szKeyName ); // will be replace with 'angles'\n\t\t\tMem_Free( pkvd[i].szValue );\t// release old value, so we don't need these\n\t\t\tpkvd[i].szKeyName = copystring( \"angles\" );\n\n\t\t\tif( flYawAngle >= 0.0f )\n\t\t\t{\n\t\t\t\tQ_snprintf( temp, sizeof( temp ), \"%g %g %g\", ent->v.angles[0], flYawAngle, ent->v.angles[2] );\n\t\t\t\tpkvd[i].szValue = copystring( temp );\n\t\t\t}\n\t\t\telse if( flYawAngle == -1.0f )\n\t\t\t\tpkvd[i].szValue = copystring( \"-90 0 0\" );\n\t\t\telse if( flYawAngle == -2.0f )\n\t\t\t\tpkvd[i].szValue = copystring( \"90 0 0\" );\n\t\t\telse pkvd[i].szValue = copystring( \"0 0 0\" ); // technically an error\n\t\t}\n\n\t\tif( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, \"origin\" ))\n\t\t{\n\t\t\tchar   *pstart = pkvd[i].szValue;\n\t\t\tvec3_t origin;\n\n\t\t\tCOM_ParseVector( &pstart, origin, 3 );\n\t\t\tMem_Free( pkvd[i].szValue );\t// release old value, so we don't need these\n\n\t\t\tQ_snprintf( temp, sizeof( temp ), \"%g %g %g\", origin[0], origin[1], origin[2] - 16.0f );\n\t\t\tpkvd[i].szValue = copystring( temp );\n\t\t}\n\n\t\t// do not leak memory if game overwritten these pointers\n\t\tkeyname = pkvd[i].szKeyName;\n\t\tvalue = pkvd[i].szValue;\n\n\t\tpkvd[i].szClassName = (char *)classname;\n\t\tsvgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] );\n\n\t\tMem_Free( keyname );\n\t\tMem_Free( value );\n\t}\n\n\treturn true;\n}\n\n/*\n================\nSV_LoadFromFile\n\nThe entities are directly placed in the array, rather than allocated with\nED_Alloc, because otherwise an error loading the map would have entity\nnumber references out of order.\n\nCreates a server's entity / program execution context by\nparsing textual entity definitions out of an ent file.\n================\n*/\nstatic void SV_LoadFromFile( const char *mapname, char *entities )\n{\n\tchar\ttoken[2048];\n\tqboolean\tcreate_world = true;\n\tint\tinhibited;\n\tedict_t\t*ent;\n\n\tAssert( entities != NULL );\n\n\t// user dll can override spawn entities function (Xash3D extension)\n\tif( !svgame.physFuncs.SV_LoadEntities || !svgame.physFuncs.SV_LoadEntities( mapname, entities ))\n\t{\n\t\tinhibited = 0;\n\n\t\t// parse ents\n\t\twhile(( entities = COM_ParseFile( entities, token, sizeof( token ))) != NULL )\n\t\t{\n\t\t\tif( token[0] != '{' )\n\t\t\t\tHost_Error( \"%s: found %s when expecting {\\n\", __func__, token );\n\n\t\t\tif( create_world )\n\t\t\t{\n\t\t\t\tcreate_world = false;\n\t\t\t\tent = EDICT_NUM( 0 ); // already initialized\n\t\t\t}\n\t\t\telse ent = SV_AllocEdict();\n\n\t\t\tif( !SV_ParseEdict( &entities, ent ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svgame.dllFuncs.pfnSpawn( ent ) == -1 )\n\t\t\t{\n\t\t\t\t// game rejected the spawn\n\t\t\t\tif( !FBitSet( ent->v.flags, FL_KILLME ))\n\t\t\t\t{\n\t\t\t\t\tSV_FreeEdict( ent );\n\t\t\t\t\tinhibited++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tCon_DPrintf( \"\\n%i entities inhibited\\n\", inhibited );\n\t}\n\n\t// reset world origin and angles for some reason\n\tVectorClear( svgame.edicts->v.origin );\n\tVectorClear( svgame.edicts->v.angles );\n}\n\n/*\n==============\nSpawnEntities\n\nCreates a server's entity / program execution context by\nparsing textual entity definitions out of an ent file.\n==============\n*/\nvoid SV_SpawnEntities( const char *mapname )\n{\n\tedict_t\t*ent;\n\n\t// reset misc parms\n\tCvar_Reset( \"sv_zmax\" );\n\tCvar_Reset( \"sv_wateramp\" );\n\tCvar_Reset( \"sv_wateralpha\" );\n\n\t// reset sky parms\n\tCvar_Reset( \"sv_skycolor_r\" );\n\tCvar_Reset( \"sv_skycolor_g\" );\n\tCvar_Reset( \"sv_skycolor_b\" );\n\tCvar_Reset( \"sv_skyvec_x\" );\n\tCvar_Reset( \"sv_skyvec_y\" );\n\tCvar_Reset( \"sv_skyvec_z\" );\n\tCvar_Reset( \"sv_skyname\" );\n\n\tent = EDICT_NUM( 0 );\n\tif( ent->free ) SV_InitEdict( ent );\n\tent->v.model = MAKE_STRING( sv.model_precache[1] );\n\tent->v.modelindex = WORLD_INDEX; // world model\n\tent->v.solid = SOLID_BSP;\n\tent->v.movetype = MOVETYPE_PUSH;\n\tsvgame.movevars.fog_settings = 0;\n\n\tsvgame.globals->maxEntities = GI->max_edicts;\n\tsvgame.globals->mapname = MAKE_STRING( sv.name );\n\tsvgame.globals->startspot = MAKE_STRING( sv.startspot );\n\tsvgame.globals->time = sv.time;\n\n\t// spawn the rest of the entities on the map\n\tSV_LoadFromFile( mapname, sv.worldmodel->entities );\n}\n\nvoid SV_UnloadProgs( void )\n{\n\tpending_cvar_t *pending_cvars_list;\n\n\tif( !svgame.hInstance )\n\t\treturn;\n\n\tSV_DeactivateServer ();\n\tDelta_Shutdown ();\n\t/// TODO: reenable this when\n\t/// SV_UnloadProgs will be disabled\n\t//Mod_ClearUserData ();\n\n\tpending_cvars_list = Cvar_PrepareToUnlink( FCVAR_EXTDLL );\n\n\tif( svgame.dllFuncs2.pfnGameShutdown != NULL )\n\t\tsvgame.dllFuncs2.pfnGameShutdown ();\n\n\t// now we can unload cvars\n\tCvar_FullSet( \"host_gameloaded\", \"0\", FCVAR_READ_ONLY );\n\n\t// free entity baselines\n\tZ_Free( svs.static_entities );\n\tZ_Free( svs.baselines );\n\tsvs.baselines = NULL;\n\n\t// remove server cmds\n\tSV_KillOperatorCommands();\n\n\t// must unlink all game cvars,\n\t// before pointers on them will be lost...\n\tCvar_UnlinkPendingCvars( pending_cvars_list );\n\tCmd_Unlink( CMD_SERVERDLL );\n\n\tSV_FreeStringPool();\n\n\tMod_ResetStudioAPI ();\n\n\tCOM_FreeLibrary( svgame.hInstance );\n\tMem_FreePool( &svgame.mempool );\n\tmemset( &svgame, 0, sizeof( svgame ));\n}\n\nqboolean SV_LoadProgs( const char *name )\n{\n\tint\t\t\ti, version;\n\tstatic APIFUNCTION\t\tGetEntityAPI;\n\tstatic APIFUNCTION2\t\tGetEntityAPI2;\n\tstatic GIVEFNPTRSTODLL\tGiveFnptrsToDll;\n\tstatic NEW_DLL_FUNCTIONS_FN\tGiveNewDllFuncs;\n\tstatic enginefuncs_t\tgpEngfuncs;\n\tstatic globalvars_t\t\tgpGlobals;\n\tstatic playermove_t\t\tgpMove;\n\tedict_t\t\t\t*e;\n\n\tif( svgame.hInstance )\n\t\treturn true;\n\n\t// fill it in\n\tsvgame.pmove = &gpMove;\n\tsvgame.globals = &gpGlobals;\n\tsvgame.mempool = Mem_AllocPool( \"Server Edicts Zone\" );\n\n\tsvgame.hInstance = COM_LoadLibrary( name, true, false );\n\tif( !svgame.hInstance )\n\t{\n\t\tMem_FreePool(&svgame.mempool);\n\t\treturn false;\n\t}\n\n\t// make sure what new dll functions is cleared\n\tmemset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 ));\n\n\t// make sure what physic functions is cleared\n\tmemset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs ));\n\n\t// revert fix for pfnPEntityOfEntIndex to be compatible with GoldSrc\n\t// games that rely on this bug\n\tif( FBitSet( host.bugcomp, BUGCOMP_PENTITYOFENTINDEX_FLAG ))\n\t\tgEngfuncs.pfnPEntityOfEntIndex = pfnPEntityOfEntIndexBroken;\n\n\t// make local copy of engfuncs to prevent overwrite it with bots.dll\n\tgpEngfuncs = gEngfuncs;\n\n\tGetEntityAPI = (APIFUNCTION)COM_GetProcAddress( svgame.hInstance, \"GetEntityAPI\" );\n\tGetEntityAPI2 = (APIFUNCTION2)COM_GetProcAddress( svgame.hInstance, \"GetEntityAPI2\" );\n\tGiveNewDllFuncs = (NEW_DLL_FUNCTIONS_FN)COM_GetProcAddress( svgame.hInstance, \"GetNewDLLFunctions\" );\n\n\tif( !GetEntityAPI && !GetEntityAPI2 )\n\t{\n\t\tCOM_FreeLibrary( svgame.hInstance );\n\t\tCon_Printf( S_ERROR \"%s: failed to get address of GetEntityAPI proc\\n\", __func__ );\n\t\tsvgame.hInstance = NULL;\n\t\tMem_FreePool( &svgame.mempool );\n\t\treturn false;\n\t}\n\n\tGiveFnptrsToDll = (GIVEFNPTRSTODLL)COM_GetProcAddress( svgame.hInstance, \"GiveFnptrsToDll\" );\n\n\tif( !GiveFnptrsToDll )\n\t{\n\t\tCOM_FreeLibrary( svgame.hInstance );\n\t\tCon_Printf( S_ERROR \"%s: failed to get address of GiveFnptrsToDll proc\\n\", __func__ );\n\t\tsvgame.hInstance = NULL;\n\t\tMem_FreePool( &svgame.mempool );\n\t\treturn false;\n\t}\n\n\tGiveFnptrsToDll( &gpEngfuncs, svgame.globals );\n\n\t// get extended callbacks\n\tif( GiveNewDllFuncs )\n\t{\n\t\tversion = NEW_DLL_FUNCTIONS_VERSION;\n\n\t\tif( !GiveNewDllFuncs( &svgame.dllFuncs2, &version ))\n\t\t{\n\t\t\tif( version != NEW_DLL_FUNCTIONS_VERSION )\n\t\t\t\tCon_Printf( S_WARN \"%s: new interface version %i should be %i\\n\", __func__, NEW_DLL_FUNCTIONS_VERSION, version );\n\t\t\tmemset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 ));\n\t\t}\n\t}\n\n\tversion = INTERFACE_VERSION;\n\n\tif( GetEntityAPI2 )\n\t{\n\t\tif( !GetEntityAPI2( &svgame.dllFuncs, &version ))\n\t\t{\n\t\t\tif( INTERFACE_VERSION != version )\n\t\t\t\tCon_Printf( S_WARN \"%s: interface version %i should be %i\\n\", __func__, INTERFACE_VERSION, version );\n\n\t\t\t// fallback to old API\n\t\t\tif( GetEntityAPI && !GetEntityAPI( &svgame.dllFuncs, version ))\n\t\t\t{\n\t\t\t\tCOM_FreeLibrary( svgame.hInstance );\n\t\t\t\tCon_Printf( S_ERROR \"%s: couldn't get entity API\\n\", __func__ );\n\t\t\t\tsvgame.hInstance = NULL;\n\t\t\t\tMem_FreePool( &svgame.mempool );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse Con_Reportf( \"%s: ^2initailized legacy EntityAPI ^7ver. %i\\n\", __func__, version );\n\t\t}\n\t\telse Con_Reportf( \"%s: ^2initailized extended EntityAPI ^7ver. %i\\n\", __func__, version );\n\t}\n\telse if( GetEntityAPI && !GetEntityAPI( &svgame.dllFuncs, version ))\n\t{\n\t\tCOM_FreeLibrary( svgame.hInstance );\n\t\tCon_Printf( S_ERROR \"%s: couldn't get entity API\\n\", __func__ );\n\t\tsvgame.hInstance = NULL;\n\t\tMem_FreePool( &svgame.mempool );\n\t\treturn false;\n\t}\n\telse Con_Reportf( \"%s: ^2initailized legacy EntityAPI ^7ver. %i\\n\", __func__, version );\n\n\tSV_InitOperatorCommands();\n\tMod_InitStudioAPI();\n\n\tif( !SV_InitPhysicsAPI( ))\n\t{\n\t\tCon_Printf( S_WARN \"%s: couldn't get physics API\\n\", __func__ );\n\t}\n\n\t// grab function SV_SaveGameComment\n\tSV_InitSaveRestore ();\n\n\tsvgame.globals->pStringBase = \"\"; // setup string base\n\n\tsvgame.globals->maxEntities = GI->max_edicts;\n\tsvgame.globals->maxClients = svs.maxclients;\n\tsvgame.edicts = Mem_Calloc( svgame.mempool, sizeof( edict_t ) * GI->max_edicts );\n\tsvs.static_entities = Z_Calloc( sizeof( entity_state_t ) * MAX_STATIC_ENTITIES );\n\tsvs.baselines = Z_Calloc( sizeof( entity_state_t ) * GI->max_edicts );\n\tsvgame.numEntities = svs.maxclients + 1; // clients + world\n\n\tfor( i = 0, e = svgame.edicts; i < GI->max_edicts; i++, e++ )\n\t\te->free = true; // mark all edicts as freed\n\n\tCvar_FullSet( \"host_gameloaded\", \"1\", FCVAR_READ_ONLY );\n\tSV_AllocStringPool();\n\n\t// fire once\n\tCon_Printf( \"Dll loaded for game ^2\\\"%s\\\"\\n\", svgame.dllFuncs.pfnGetGameDescription( ));\n\n\t// all done, initialize game\n\tsvgame.dllFuncs.pfnGameInit();\n\n\t// initialize pm_shared\n\tSV_InitClientMove();\n\n\tDelta_Init ();\n\n\t// register custom encoders\n\tsvgame.dllFuncs.pfnRegisterEncoders();\n\n\treturn true;\n}\n"
  },
  {
    "path": "engine/server/sv_init.c",
    "content": "/*\nsv_init.c - server initialize operations\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"net_encode.h\"\n#include \"library.h\"\n#include \"voice.h\"\n#include \"pm_local.h\"\n\n#if XASH_LOW_MEMORY != 2\nint SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP;\n#endif\nserver_t\t\tsv;\t// local server\nserver_static_t\tsvs;\t// persistant server info\nsvgame_static_t\tsvgame;\t// persistant game info\n\n/*\n==================\nHost_SetServerState\n==================\n*/\nstatic void Host_SetServerState( int state )\n{\n\tCvar_FullSet( \"host_serverstate\", va( \"%i\", state ), FCVAR_READ_ONLY );\n\tsv.state = state;\n}\n\n/*\n================\nSV_AddResource\n\ngeneric method to put the resources into array\n================\n*/\nstatic void SV_AddResource( resourcetype_t type, const char *name, int size, byte flags, int index )\n{\n\tresource_t\t*pResource = &sv.resources[sv.num_resources];\n\n\tif( sv.num_resources >= MAX_RESOURCES )\n\t\tHost_Error( \"MAX_RESOURCES limit exceeded (%d)\\n\", MAX_RESOURCES );\n\tsv.num_resources++;\n\n\tQ_strncpy( pResource->szFileName, name, sizeof( pResource->szFileName ));\n\tpResource->nDownloadSize = size;\n\tpResource->ucFlags = flags;\n\tpResource->nIndex = index;\n\tpResource->type = type;\n}\n\n/*\n================\nSV_SendSingleResource\n\nhot precache on a flying\n================\n*/\nstatic void SV_SendSingleResource( const char *name, resourcetype_t type, int index, byte flags )\n{\n\tresource_t\t*pResource = &sv.resources[sv.num_resources];\n\tint\t\tnSize = 0;\n\n\tif( !COM_CheckString( name ))\n\t\treturn;\n\n\tswitch( type )\n\t{\n\tcase t_model:\n\t\tnSize = ( name[0] != '*' ) ? FS_FileSize( name, false ) : 0;\n\t\tbreak;\n\tcase t_sound:\n\t\tnSize = FS_FileSize( va( DEFAULT_SOUNDPATH \"%s\", name ), false );\n\t\tbreak;\n\tdefault:\n\t\tnSize = FS_FileSize( name, false );\n\t\tbreak;\n\t}\n\n\tSV_AddResource( type, name, nSize, flags, index );\n\tMSG_BeginServerCmd( &sv.reliable_datagram, svc_resource );\n\tSV_SendResource( pResource, &sv.reliable_datagram );\n}\n\n/*\n================\nSV_ModelIndex\n\nregister unique model for a server and client\n================\n*/\nint SV_ModelIndex( const char *filename )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn 0;\n\n\tif( *filename == '\\\\' || *filename == '/' )\n\t\tfilename++;\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( sv.model_precache[i], name ))\n\t\t\treturn i;\n\t}\n\n\tif( i == MAX_MODELS )\n\t{\n\t\tHost_Error( \"MAX_MODELS limit exceeded (%d)\\n\", MAX_MODELS );\n\t\treturn 0;\n\t}\n\n\t// register new model\n\tQ_strncpy( sv.model_precache[i], name, sizeof( sv.model_precache[i] ));\n\n\tif( sv.state != ss_loading )\n\t{\n\t\t// send the update to everyone\n\t\tSV_SendSingleResource( name, t_model, i, sv.model_precache_flags[i] );\n\t\tCon_Printf( S_WARN \"late precache of %s\\n\", name );\n\t}\n\n\treturn i;\n}\n\n/*\n================\nSV_SoundIndex\n\nregister unique sound for client\n================\n*/\nint GAME_EXPORT SV_SoundIndex( const char *filename )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn 0;\n\n\tif( filename[0] == '!' )\n\t{\n\t\tCon_Printf( S_WARN \"'%s' do not precache sentence names!\\n\", filename );\n\t\treturn 0;\n\t}\n\n\tif( *filename == '\\\\' || *filename == '/' )\n\t\tfilename++;\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 1; i < MAX_SOUNDS && sv.sound_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( sv.sound_precache[i], name ))\n\t\t\treturn i;\n\t}\n\n\tif( i == MAX_SOUNDS )\n\t{\n\t\tHost_Error( \"MAX_SOUNDS limit exceeded (%d)\\n\", MAX_SOUNDS );\n\t\treturn 0;\n\t}\n\n\t// register new sound\n\tQ_strncpy( sv.sound_precache[i], name, sizeof( sv.sound_precache[i] ));\n\n\tif( sv.state != ss_loading )\n\t{\n\t\t// send the update to everyone\n\t\tSV_SendSingleResource( name, t_sound, i, 0 );\n\t\tCon_Printf( S_WARN \"late precache of %s\\n\", name );\n\t}\n\n\treturn i;\n}\n\n/*\n================\nSV_EventIndex\n\nregister network event for a server and client\n================\n*/\nint SV_EventIndex( const char *filename )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn 0;\n\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 1; i < MAX_EVENTS && sv.event_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( sv.event_precache[i], name ))\n\t\t\treturn i;\n\t}\n\n\tif( i == MAX_EVENTS )\n\t{\n\t\tHost_Error( \"MAX_EVENTS limit exceeded (%d)\\n\", MAX_EVENTS );\n\t\treturn 0;\n\t}\n\n\t// register new event\n\tQ_strncpy( sv.event_precache[i], name, sizeof( sv.event_precache[i] ));\n\n\tif( sv.state != ss_loading )\n\t{\n\t\t// send the update to everyone\n\t\tSV_SendSingleResource( name, t_eventscript, i, RES_FATALIFMISSING );\n\t}\n\n\treturn i;\n}\n\n/*\n================\nSV_GenericIndex\n\nregister generic resourse for a server and client\n================\n*/\nint GAME_EXPORT SV_GenericIndex( const char *filename )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti;\n\n\tif( !COM_CheckString( filename ))\n\t\treturn 0;\n\n\tQ_strncpy( name, filename, sizeof( name ));\n\tCOM_FixSlashes( name );\n\n\tfor( i = 1; i < MAX_CUSTOM && sv.files_precache[i][0]; i++ )\n\t{\n\t\tif( !Q_stricmp( sv.files_precache[i], name ))\n\t\t\treturn i;\n\t}\n\n\tif( i == MAX_CUSTOM )\n\t{\n\t\tHost_Error( \"MAX_CUSTOM limit exceeded (%d)\\n\", MAX_CUSTOM );\n\t\treturn 0;\n\t}\n\n\t// register new generic resource\n\tQ_strncpy( sv.files_precache[i], name, sizeof( sv.files_precache[i] ));\n\n\tif( sv.state != ss_loading )\n\t{\n\t\t// send the update to everyone\n\t\tSV_SendSingleResource( name, t_generic, i, RES_FATALIFMISSING );\n\t}\n\n\treturn i;\n}\n\nstatic resourcetype_t SV_DetermineResourceType( const char *filename )\n{\n\tif( !Q_strncmp( filename, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ) && Sound_SupportedFileFormat( COM_FileExtension( filename )))\n\t\treturn t_sound;\n\telse\n\t\treturn t_generic;\n}\n\nstatic const char *SV_GetResourceTypeName( resourcetype_t restype )\n{\n\tswitch( restype )\n\t{\n\t\tcase t_decal: return \"decal\";\n\t\tcase t_eventscript: return \"eventscript\";\n\t\tcase t_generic: return \"generic\";\n\t\tcase t_model: return \"model\";\n\t\tcase t_skin: return \"skin\";\n\t\tcase t_sound: return \"sound\";\n\t\tcase t_world: return \"world\";\n\t\tdefault: return \"unknown\";\n\t}\n}\n\nstatic void SV_ReadResourceList( const char *filename )\n{\n\tstring token;\n\tbyte *afile;\n\tchar *pfile;\n\tresourcetype_t restype;\n\n\tafile = FS_LoadFile( filename, NULL, false );\n\tif( !afile ) return;\n\n\tpfile = (char *)afile;\n\n\tCon_DPrintf( \"Precaching from %s\\n\", filename );\n\tCon_DPrintf( \"----------------------------------\\n\" );\n\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\tif( !COM_IsSafeFileToDownload( token ))\n\t\t\tcontinue;\n\n\t\tCOM_FixSlashes( token );\n\t\trestype = SV_DetermineResourceType( token );\n\t\tCon_DPrintf( \"  %s (%s)\\n\", token, SV_GetResourceTypeName( restype ));\n\t\tswitch( restype )\n\t\t{\n\t\t\t// TODO do we need to handle other resource types specifically too?\n\t\t\tcase t_sound:\n\t\t\t{\n\t\t\t\tconst char *filepath = token;\n\t\t\t\tfilepath += sizeof( DEFAULT_SOUNDPATH ) - 1; // skip \"sound/\" part\n\t\t\t\tSV_SoundIndex( filepath );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tSV_GenericIndex( token );\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tCon_DPrintf( \"----------------------------------\\n\" );\n\tMem_Free( afile );\n}\n\n/*\n================\nSV_CreateGenericResources\n\nloads external resource list\n================\n*/\nstatic void SV_CreateGenericResources( void )\n{\n\tstring\tfilename;\n\tint i;\n\n\tQ_strncpy( filename, sv.model_precache[1], sizeof( filename ));\n\tCOM_ReplaceExtension( filename, \".res\", sizeof( filename ));\n\tCOM_FixSlashes( filename );\n\n\tSV_ReadResourceList( filename );\n\tSV_ReadResourceList( \"reslist.txt\" );\n\n\tfor( i = 0; i < world.wadlist.count; i++ )\n\t{\n\t\tif( world.wadlist.wadusage[i] > 0 )\n\t\t{\n\t\t\tSV_GenericIndex( world.wadlist.wadnames[i] );\n\t\t}\n\t}\n}\n\n/*\n================\nSV_CreateResourceList\n\nadd resources to common list\n================\n*/\nstatic void SV_CreateResourceList( void )\n{\n\tqboolean\tffirstsent = false;\n\tint\ti, nSize;\n\tchar\t*s;\n\n\tsv.num_resources = 0;\n\n\tfor( i = 1; i < MAX_CUSTOM; i++ )\n\t{\n\t\ts = sv.files_precache[i];\n\t\tif( !COM_CheckString( s )) break; // end of list\n\t\tnSize = FS_FileSize( s, false );\n\t\tSV_AddResource( t_generic, s, nSize, RES_FATALIFMISSING, i );\n\t}\n\n\tfor( i = 1; i < MAX_SOUNDS; i++ )\n\t{\n\t\ts = sv.sound_precache[i];\n\t\tif( !COM_CheckString( s ))\n\t\t\tbreak; // end of list\n\n\t\tif( s[0] == '!' )\n\t\t{\n\t\t\tif( !ffirstsent )\n\t\t\t{\n\t\t\t\tSV_AddResource( t_sound, \"!\", 0, RES_FATALIFMISSING, i );\n\t\t\t\tffirstsent = true;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnSize = FS_FileSize( va( DEFAULT_SOUNDPATH \"%s\", s ), false );\n\t\t\tSV_AddResource( t_sound, s, nSize, 0, i );\n\t\t}\n\t}\n\n\tfor( i = 1; i < MAX_MODELS; i++ )\n\t{\n\t\ts = sv.model_precache[i];\n\t\tif( !COM_CheckString( s )) break; // end of list\n\t\tnSize = ( s[0] != '*' ) ? FS_FileSize( s, false ) : 0;\n\t\tSV_AddResource( t_model, s, nSize, sv.model_precache_flags[i], i );\n\t}\n\n\t// just send names\n\tfor( i = 0; i < MAX_DECALS && host.draw_decals[i][0]; i++ )\n\t{\n\t\tSV_AddResource( t_decal, host.draw_decals[i], 0, 0, i );\n\t}\n\n\tfor( i = 1; i < MAX_EVENTS; i++ )\n\t{\n\t\ts = sv.event_precache[i];\n\t\tif( !COM_CheckString( s )) break; // end of list\n\t\tnSize = FS_FileSize( s, false );\n\t\tSV_AddResource( t_eventscript, s, nSize, RES_FATALIFMISSING, i );\n\t}\n}\n\n/*\n================\nSV_WriteVoiceCodec\n================\n*/\nstatic void SV_WriteVoiceCodec( sizebuf_t *msg )\n{\n\tMSG_BeginServerCmd( msg, svc_voiceinit );\n\tMSG_WriteString( msg, VOICE_DEFAULT_CODEC );\n\tMSG_WriteByte( msg, (int)sv_voicequality.value );\n}\n\n/*\n================\nSV_CreateBaseline\n\nEntity baselines are used to compress the update messages\nto the clients -- only the fields that differ from the\nbaseline will be transmitted\n\nINTERNAL RESOURCE\n================\n*/\nstatic void SV_CreateBaseline( void )\n{\n\tentity_state_t\tnullstate, *base;\n\tint\t\tplayermodel;\n\tint\t\tdelta_type;\n\tint\t\tentnum;\n\n\tif( svs.maxclients > 1 )\n\t\tSV_WriteVoiceCodec( &sv.signon );\n\n\tif( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\tplayermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE );\n\telse playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE );\n\n\tmemset( &nullstate, 0, sizeof( nullstate ));\n\n\tfor( entnum = 0; entnum < svgame.numEntities; entnum++ )\n\t{\n\t\tedict_t\t*pEdict = EDICT_NUM( entnum );\n\n\t\tif( !SV_IsValidEdict( pEdict ))\n\t\t\tcontinue;\n\n\t\tif( entnum != 0 && entnum <= svs.maxclients )\n\t\t{\n\t\t\tdelta_type = DELTA_PLAYER;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !pEdict->v.modelindex )\n\t\t\t\tcontinue; // invisible\n\t\t\tdelta_type = DELTA_ENTITY;\n\t\t}\n\n\t\t// take current state as baseline\n\t\tbase = &svs.baselines[entnum];\n\n\t\tbase->number = entnum;\n\n\t\t// set entity type\n\t\tif( FBitSet( pEdict->v.flags, FL_CUSTOMENTITY ))\n\t\t\tbase->entityType = ENTITY_BEAM;\n\t\telse base->entityType = ENTITY_NORMAL;\n\n\t\tsvgame.dllFuncs.pfnCreateBaseline( delta_type, entnum, base, pEdict, playermodel, host.player_mins[0], host.player_maxs[0] );\n\t\tsv.last_valid_baseline = entnum;\n\t}\n\n\t// create the instanced baselines\n\tsvgame.dllFuncs.pfnCreateInstancedBaselines();\n\n\t// now put the baseline into the signon message.\n\tMSG_BeginServerCmd( &sv.signon, svc_spawnbaseline );\n\n\tfor( entnum = 0; entnum < svgame.numEntities; entnum++ )\n\t{\n\t\tedict_t\t*pEdict = EDICT_NUM( entnum );\n\n\t\tif( !SV_IsValidEdict( pEdict ))\n\t\t\tcontinue;\n\n\t\tif( entnum != 0 && entnum <= svs.maxclients )\n\t\t{\n\t\t\tdelta_type = DELTA_PLAYER;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !pEdict->v.modelindex )\n\t\t\t\tcontinue; // invisible\n\t\t\tdelta_type = DELTA_ENTITY;\n\t\t}\n\n\t\t// take current state as baseline\n\t\tbase = &svs.baselines[entnum];\n\n\t\tMSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, delta_type, 1.0f, 0 );\n\t}\n\n\tMSG_WriteUBitLong( &sv.signon, LAST_EDICT, MAX_ENTITY_BITS ); // end of baselines\n\tMSG_WriteUBitLong( &sv.signon, sv.num_instanced, 6 );\n\n\tfor( entnum = 0; entnum < sv.num_instanced; entnum++ )\n\t{\n\t\tbase = &sv.instanced[entnum].baseline;\n\t\tMSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, DELTA_ENTITY, 1.0f, 0 );\n\t}\n}\n\n/*\n================\nSV_FreeOldEntities\n\nremove immediate entities\n================\n*/\nvoid SV_FreeOldEntities( void )\n{\n\tedict_t\t*ent;\n\tint\ti;\n\n\t// at end of frame kill all entities which supposed to it\n\tfor( i = svs.maxclients + 1; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\n\t\tif( !ent->free && FBitSet( ent->v.flags, FL_KILLME ))\n\t\t\tSV_FreeEdict( ent );\n\t}\n\n\t// decrement svgame.numEntities if the highest number entities died\n\tfor( ; ( ent = EDICT_NUM( svgame.numEntities - 1 )) && ent->free; svgame.numEntities-- );\n}\n\n/*\n================\nSV_ActivateServer\n\nactivate server on changed map, run physics\n================\n*/\nvoid SV_ActivateServer( int runPhysics )\n{\n\tint\t\ti, numFrames;\n\tbyte\t\tmsg_buf[MAX_INIT_MSG];\n\tsizebuf_t\t\tmsg;\n\tsv_client_t\t*cl;\n\n\tif( !svs.initialized )\n\t\treturn;\n\n\tMSG_Init( &msg, \"ActivateServer\", msg_buf, sizeof( msg_buf ));\n\n\t// always clearing newunit variable\n\tCvar_SetValue( \"sv_newunit\", 0 );\n\n\t// relese all intermediate entities\n\tSV_FreeOldEntities ();\n\n\t// Activate the DLL server code\n\tsvgame.globals->time = sv.time;\n\tsvgame.dllFuncs.pfnServerActivate( svgame.edicts, svgame.numEntities, svs.maxclients );\n\n\tSV_SetStringArrayMode( true );\n\n\t// parse user-specified resources\n\tSV_CreateGenericResources();\n\n\tif( runPhysics )\n\t{\n\t\tnumFrames = (svs.maxclients <= 1) ? 2 : 8;\n\t\tsv.frametime = SV_SPAWN_TIME;\n\t}\n\telse\n\t{\n\t\tsv.frametime = 0.001;\n\t\tnumFrames = 1;\n\t}\n\n\t// run some frames to allow everything to settle\n\tfor( i = 0; i < numFrames; i++ )\n\t\tSV_Physics();\n\n\t// create a baseline for more efficient communications\n\tSV_CreateBaseline();\n\n\t// collect all info from precached resources\n\tSV_CreateResourceList();\n\n\t// check and count all files that marked by user as unmodified (typically is a player models etc)\n\tSV_TransferConsistencyInfo();\n\n\t// send serverinfo to all connected clients\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state < cs_connected )\n\t\t\tcontinue;\n\n\t\tNetchan_Clear( &cl->netchan );\n\t\tcl->delta_sequence = -1;\n\n\t\t// bump connect timeout\n\t\tcl->connection_started = host.realtime;\n\t}\n\n\t// invoke to refresh all movevars\n\tmemset( &svgame.oldmovevars, 0, sizeof( movevars_t ));\n\tsvgame.globals->changelevel = false;\n\n\t// setup hostflags\n\tsv.hostflags = 0;\n\n\tHPAK_FlushHostQueue();\n\n\t// tell what kind of server has been started.\n\tif( svs.maxclients > 1 )\n\t\tCon_Printf( \"%i player server started\\n\", svs.maxclients );\n\telse Con_Printf( \"Game started\\n\" );\n\n\tLog_Printf( \"Started map \\\"%s\\\" (CRC \\\"%u\\\")\\n\", sv.name, sv.worldmapCRC );\n\n\t// dedicated server purge unused resources here\n\tif( Host_IsDedicated() )\n\t\tMod_FreeUnused ();\n\n\thost.movevars_changed = true;\n\tHost_SetServerState( ss_active );\n\n\tCon_DPrintf( \"level loaded at %.2f sec\\n\", Sys_DoubleTime() - svs.timestart );\n\n\tif( sv.ignored_static_ents )\n\t\tCon_Printf( S_WARN \"%i static entities was rejected due buffer overflow\\n\", sv.ignored_static_ents );\n\n\tif( sv.ignored_world_decals )\n\t\tCon_Printf( S_WARN \"%i static decals was rejected due buffer overflow\\n\", sv.ignored_world_decals );\n}\n\n/*\n================\nSV_DeactivateServer\n\ndeactivate server, free edicts, strings etc\n================\n*/\nvoid SV_DeactivateServer( void )\n{\n\tint\ti;\n\tconst char\t*cycle = Cvar_VariableString( \"disconcfgfile\" );\n\n\tif( COM_CheckString( cycle ))\n\t\tCbuf_AddTextf( \"exec %s\\n\", cycle );\n\n\tif( COM_CheckStringEmpty( sv.name ))\n\t\tCbuf_AddTextf( \"exec maps/%s_unload.cfg\\n\", sv.name );\n\n\tif( !svs.initialized || sv.state == ss_dead )\n\t\treturn;\n\n\tsvgame.globals->time = sv.time;\n\tsvgame.dllFuncs.pfnServerDeactivate();\n\tHost_SetServerState( ss_dead );\n\n\tSV_FreeEdicts ();\n\n\tPM_ClearPhysEnts( svgame.pmove );\n\n\tSV_EmptyStringPool( true );\n\tMem_EmptyPool( svgame.stringspool );\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\t// release client frames\n\t\tif( svs.clients[i].frames )\n\t\t\tMem_Free( svs.clients[i].frames );\n\t\tsvs.clients[i].frames = NULL;\n\t}\n\n\tsvgame.globals->maxEntities = GI->max_edicts;\n\tsvgame.globals->maxClients = svs.maxclients;\n\tsvgame.numEntities = svs.maxclients + 1; // clients + world\n\tsvgame.globals->startspot = 0;\n\tsvgame.globals->mapname = 0;\n}\n\n/*\n==============\nSV_InitGame\n\nA brand new game has been started\n==============\n*/\nqboolean SV_InitGame( void )\n{\n\tstring dllpath;\n\n\tif( svgame.hInstance )\n\t\treturn true;\n\n\t// first initialize?\n\tCOM_ResetLibraryError();\n\n\tCOM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath ));\n\n\tif( !SV_LoadProgs( dllpath ))\n\t{\n\t\tCon_Printf( S_ERROR \"can't initialize %s: %s\\n\", dllpath, COM_GetLibraryError() );\n\t\treturn false; // failed to loading server.dll\n\t}\n\n\t// client frames will be allocated in SV_ClientConnect\n\treturn true;\n}\n\n/*\n==============\nSV_ShutdownGame\n\nprepare to close server\n==============\n*/\nvoid SV_ShutdownGame( void )\n{\n\tif( !GameState->loadGame )\n\t\tSV_ClearGameState();\n\n\tSV_FinalMessage( \"\", true );\n\tS_StopBackgroundTrack();\n\tCL_StopPlayback(); // stop demo too\n\n\tif( GameState->newGame )\n\t{\n\t\tHost_EndGame( false, DEFAULT_ENDGAME_MESSAGE );\n\t}\n\telse\n\t{\n\t\tS_StopAllSounds( true );\n\t\tSV_DeactivateServer();\n\t}\n}\n\n/*\n================\nSV_SetupClients\n\ndetermine the game type and prepare clients\n================\n*/\nstatic void SV_SetupClients( void )\n{\n\tqboolean\tchanged_maxclients = false;\n\n\t// check if clients count was really changed\n\tif( svs.maxclients != (int)sv_maxclients.value )\n\t\tchanged_maxclients = true;\n\n\tif( !changed_maxclients ) return; // nothing to change\n\n\t// if clients count was changed we need to run full shutdown procedure\n\tif( svs.maxclients )\n\t\tSV_Shutdown( \"Server was killed due to maxclients change\\n\" );\n\n\t// copy the actual value from cvar\n\tsvs.maxclients = (int)sv_maxclients.value;\n\n\t// dedicated servers are can't be single player and are usually DM\n\tif( Host_IsDedicated() )\n\t\tsvs.maxclients = bound( 4, svs.maxclients, MAX_CLIENTS );\n\telse svs.maxclients = bound( 1, svs.maxclients, MAX_CLIENTS );\n\n\tif( svs.maxclients == 1 )\n\t\tCvar_SetValue( \"deathmatch\", 0.0f );\n\telse Cvar_SetValue( \"deathmatch\", 1.0f );\n\n\t// make cvars consistant\n\tif( coop.value ) Cvar_SetValue( \"deathmatch\", 0.0f );\n\n\t// feedback for cvar\n\tCvar_FullSet( \"maxplayers\", va( \"%d\", svs.maxclients ), FCVAR_LATCH );\n#if XASH_LOW_MEMORY != 2\n\tSV_UPDATE_BACKUP = ( svs.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;\n#endif\n\n\tsvs.clients = Z_Realloc( svs.clients, sizeof( sv_client_t ) * svs.maxclients );\n\tsvs.num_client_entities = svs.maxclients * SV_UPDATE_BACKUP * NUM_PACKET_ENTITIES;\n\tsvs.packet_entities = Z_Realloc( svs.packet_entities, sizeof( entity_state_t ) * svs.num_client_entities );\n\tCon_Reportf( \"%s alloced by server packet entities\\n\", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities ));\n\n\t// init network stuff\n\tNET_Config(( svs.maxclients > 1 ), true );\n\tsvgame.numEntities = svs.maxclients + 1; // clients + world\n\tClearBits( sv_maxclients.flags, FCVAR_CHANGED );\n}\n\nqboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer )\n{\n\tchar\theadbuf[1024], buffer[1024];\n\tint\ti, num_bytes, lumplen;\n\tint\tversion, hdr_size;\n\tdheader_t\t*header;\n\tfile_t\t*f;\n\n\tif( !crcvalue ) return false;\n\n\t// always calc same checksum for singleplayer\n\tif( multiplayer == false )\n\t{\n\t\t*crcvalue = (('H'<<24)+('S'<<16)+('A'<<8)+'X');\n\t\treturn true;\n\t}\n\n\tf = FS_Open( filename, \"rb\", false );\n\tif( !f ) return false;\n\n\t// read version number\n\tFS_Read( f, &version, sizeof( int ));\n\tFS_Seek( f, 0, SEEK_SET );\n\n\thdr_size = sizeof( int ) + sizeof( dlump_t ) * HEADER_LUMPS;\n\tnum_bytes = FS_Read( f, headbuf, hdr_size );\n\n\t// corrupted map ?\n\tif( num_bytes != hdr_size )\n\t{\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\theader = (dheader_t *)headbuf;\n\n\t// invalid version ?\n\tswitch( header->version )\n\t{\n\tcase Q1BSP_VERSION:\n\tcase HLBSP_VERSION:\n\tcase QBSP2_VERSION:\n\t\tbreak;\n\tdefault:\n\t\tFS_Close( f );\n\t\treturn false;\n\t}\n\n\tCRC32_Init( crcvalue );\n\n\tfor( i = LUMP_PLANES; i < HEADER_LUMPS; i++ )\n\t{\n\t\tlumplen = header->lumps[i].filelen;\n\t\tFS_Seek( f, header->lumps[i].fileofs, SEEK_SET );\n\n\t\twhile( lumplen > 0 )\n\t\t{\n\t\t\tif( lumplen >= sizeof( buffer ))\n\t\t\t\tnum_bytes = FS_Read( f, buffer, sizeof( buffer ));\n\t\t\telse num_bytes = FS_Read( f, buffer, lumplen );\n\n\t\t\tif( num_bytes > 0 )\n\t\t\t{\n\t\t\t\tlumplen -= num_bytes;\n\t\t\t\tCRC32_ProcessBuffer( crcvalue, buffer, num_bytes );\n\t\t\t}\n\n\t\t\t// file unexpected end ?\n\t\t\tif( FS_Eof( f )) break;\n\t\t}\n\t}\n\n\tFS_Close( f );\n\n\treturn 1;\n}\n\nvoid SV_FreeTestPacket( void )\n{\n\tif( svs.testpacket_buf )\n\t{\n\t\tMem_Free( svs.testpacket_buf );\n\t\tsvs.testpacket_buf = NULL;\n\t}\n\n\tif( svs.testpacket_crcs )\n\t{\n\t\tMem_Free( svs.testpacket_crcs );\n\t\tsvs.testpacket_crcs = NULL;\n\t}\n}\n\n/*\n================\nSV_GenerateTestPacket\n================\n*/\nstatic void SV_GenerateTestPacket( void )\n{\n\tconst int maxsize = FRAGMENT_MAX_SIZE;\n\tuint32_t crc;\n\tfile_t *file;\n\tbyte *filepos;\n\tint i;\n\n\tif( !sv_allow_testpacket.value )\n\t{\n\t\tSV_FreeTestPacket();\n\t\treturn;\n\t}\n\n\t// testpacket already generated once, exit\n\t// testpacket and lookup table takes ~300k of memory\n\t// disable for low memory mode\n\tif( svs.testpacket_buf || XASH_LOW_MEMORY > 0 )\n\t\treturn;\n\n\t// don't need in singleplayer with full client\n\tif( svs.maxclients <= 1 )\n\t\treturn;\n\n\tfile = FS_Open( \"gfx.wad\", \"rb\", false );\n\tif( FS_FileLength( file ) < maxsize )\n\t{\n\t\tFS_Close( file );\n\t\treturn;\n\t}\n\n\tsvs.testpacket_buf = Mem_Malloc( host.mempool, sizeof( *svs.testpacket_buf ) * maxsize );\n\n\t// write packet base data\n\tMSG_Init( &svs.testpacket, \"BandWidthTest\", svs.testpacket_buf, maxsize );\n\tMSG_WriteLong( &svs.testpacket, -1 );\n\tMSG_WriteString( &svs.testpacket, S2C_BANDWIDTHTEST );\n\tsvs.testpacket_crcpos = svs.testpacket.pData + MSG_GetNumBytesWritten( &svs.testpacket );\n\tMSG_WriteDword( &svs.testpacket, 0 ); // to be changed by crc\n\n\t// time to read our file\n\tsvs.testpacket_filepos = MSG_GetNumBytesWritten( &svs.testpacket );\n\tsvs.testpacket_filelen = maxsize - svs.testpacket_filepos;\n\n\tfilepos = svs.testpacket.pData + svs.testpacket_filepos;\n\tFS_Read( file, filepos, svs.testpacket_filelen );\n\tFS_Close( file );\n\n\t// now generate checksums lookup table\n\tsvs.testpacket_crcs = Mem_Malloc( host.mempool, sizeof( *svs.testpacket_crcs ) * svs.testpacket_filelen );\n\tcrc = 0; // intentional omit of CRC32_Init because of the client\n\n\t// TODO: shrink to minimum!\n\tfor( i = 0; i < svs.testpacket_filelen; i++ )\n\t{\n\t\tuint32_t crc2;\n\n\t\tCRC32_ProcessByte( &crc, filepos[i] );\n\t\tsvs.testpacket_crcs[i] = crc;\n#if 0\n\t\t// test\n\t\tcrc2 = 0;\n\t\tCRC32_ProcessBuffer( &crc2, filepos, i + 1 );\n\t\tif( svs.testpacket_crcs[i] != crc2 )\n\t\t{\n\t\t\tCon_Printf( \"%d: 0x%x != 0x%x\\n\", i, svs.testpacket_crcs[i], crc2 );\n\t\t\tsvs.testpacket_crcs[i] = crc = crc2;\n\t\t}\n#endif\n\t}\n}\n\n/*\n================\nSV_SpawnServer\n\nChange the server to a new map, taking all connected\nclients along with it.\n================\n*/\nqboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean background )\n{\n\tint\t\ti, current_skill;\n\tedict_t\t\t*ent;\n\tconst char\t*cycle;\n\n\tSV_SetupClients();\n\n\tif( !SV_InitGame( ))\n\t\treturn false;\n\n\tDelta_Init(); // re-initialize delta\n\n\t// unlock sv_cheats in local game\n\tClearBits( sv_cheats.flags, FCVAR_READ_ONLY );\n\n\tsvs.initialized = true;\n\tLog_Open();\n\tLog_Printf( \"Loading map \\\"%s\\\"\\n\", mapname );\n\tLog_PrintServerVars();\n\n\tsvs.timestart = Sys_DoubleTime();\n\tsvs.spawncount++; // any partially connected client will be restarted\n\n\tfor( i = 0; i < ARRAYSIZE( svs.challenge_salt ); i++ )\n\t\tsvs.challenge_salt[i] = COM_RandomLong( 0, 0x7FFFFFFE );\n\n\tcycle = Cvar_VariableString( \"mapchangecfgfile\" );\n\n\tif( COM_CheckString( cycle ))\n\t\tCbuf_AddTextf( \"exec %s\\n\", cycle );\n\n\tCbuf_AddTextf( \"exec maps/%s_load.cfg\\n\", mapname );\n\n\t// let's not have any servers with no name\n\tif( !COM_CheckString( hostname.string ))\n\t\tCvar_Set( \"hostname\", svgame.dllFuncs.pfnGetGameDescription ? svgame.dllFuncs.pfnGetGameDescription() : FS_Title( ));\n\n\tif( startspot )\n\t{\n\t\tCon_Printf( \"Spawn Server: %s [%s]\\n\", mapname, startspot );\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( \"Spawn Server: %s\\n\", mapname );\n\t}\n\n\tmemset( &sv, 0, sizeof( sv ));\t// wipe the entire per-level structure\n\tsv.time = svgame.globals->time = 1.0f;\t// server spawn time it's always 1.0 second\n\tsv.background = background;\n\n\t// initialize buffers\n\tMSG_Init( &sv.signon, \"Signon\", sv.signon_buf, sizeof( sv.signon_buf ));\n\tMSG_Init( &sv.multicast, \"Multicast\", sv.multicast_buf, sizeof( sv.multicast_buf ));\n\tMSG_Init( &sv.datagram, \"Datagram\", sv.datagram_buf, sizeof( sv.datagram_buf ));\n\tMSG_Init( &sv.reliable_datagram, \"Reliable Datagram\", sv.reliable_datagram_buf, sizeof( sv.reliable_datagram_buf ));\n\tMSG_Init( &sv.spec_datagram, \"Spectator Datagram\", sv.spectator_buf, sizeof( sv.spectator_buf ));\n\n\t// clearing all the baselines\n\tmemset( svs.static_entities, 0, sizeof( entity_state_t ) * MAX_STATIC_ENTITIES );\n\tmemset( svs.baselines, 0, sizeof( entity_state_t ) * GI->max_edicts );\n\n\t// make cvars consistant\n\tif( coop.value ) Cvar_SetValue( \"deathmatch\", 0 );\n\tcurrent_skill = Q_rint( skill.value );\n\tcurrent_skill = bound( 0, current_skill, 3 );\n\tCvar_SetValue( \"skill\", (float)current_skill );\n\n\t// enforce hpk_max_size\n\tHPAK_CheckSize( hpk_custom_file.string );\n\n\t// force normal player collisions for single player\n\tif( svs.maxclients == 1 )\n\t\tCvar_SetValue( \"sv_clienttrace\", 1 );\n\n\t// copy gamemode into svgame.globals\n\tsvgame.globals->deathmatch = deathmatch.value;\n\tsvgame.globals->coop = coop.value;\n\tsvgame.globals->maxClients = svs.maxclients;\n\n\tif( sv.background )\n\t{\n\t\t// tell the game parts about background state\n\t\tCvar_FullSet( \"sv_background\", \"1\", FCVAR_READ_ONLY );\n\t\tCvar_FullSet( \"cl_background\", \"1\", FCVAR_READ_ONLY );\n\t}\n\telse\n\t{\n\t\tCvar_FullSet( \"sv_background\", \"0\", FCVAR_READ_ONLY );\n\t\tCvar_FullSet( \"cl_background\", \"0\", FCVAR_READ_ONLY );\n\t}\n\n\t// force normal player collisions for single player\n\tif( svs.maxclients == 1 ) Cvar_SetValue( \"sv_clienttrace\", 1 );\n\n\t// allow loading maps from subdirectories, strip extension anyway\n\tQ_strncpy( sv.name, mapname, sizeof( sv.name ));\n\tCOM_StripExtension( sv.name );\n\n\t// precache and static commands can be issued during map initialization\n\tHost_SetServerState( ss_loading );\n\n\tif( startspot )\n\t\tQ_strncpy( sv.startspot, startspot, sizeof( sv.startspot ));\n\telse sv.startspot[0] = '\\0';\n\n\tQ_snprintf( sv.model_precache[WORLD_INDEX], sizeof( sv.model_precache[0] ), \"maps/%s.bsp\", sv.name );\n\tSetBits( sv.model_precache_flags[WORLD_INDEX], RES_FATALIFMISSING );\n\tsv.worldmodel = sv.models[WORLD_INDEX] = Mod_LoadWorld( sv.model_precache[WORLD_INDEX], true );\n\tCRC32_MapFile( &sv.worldmapCRC, sv.model_precache[WORLD_INDEX], svs.maxclients > 1 );\n\n\tif( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FS_FileExists( \"progs.dat\", false ))\n\t{\n\t\tfile_t *f = FS_Open( \"progs.dat\", \"rb\", false );\n\t\tFS_Seek( f, sizeof( int ), SEEK_SET );\n\t\tFS_Read( f, &sv.progsCRC, sizeof( int ));\n\t\tFS_Close( f );\n\t}\n\n\tfor( i = WORLD_INDEX; i < sv.worldmodel->numsubmodels; i++ )\n\t{\n\t\tQ_snprintf( sv.model_precache[i+1], sizeof( sv.model_precache[i+1] ), \"*%i\", i );\n\t\tsv.models[i+1] = Mod_ForName( sv.model_precache[i+1], false, false );\n\t\tSetBits( sv.model_precache_flags[i+1], RES_FATALIFMISSING );\n\t}\n\n\t// leave slots at start for clients only\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\t// needs to reconnect\n\t\tif( svs.clients[i].state > cs_connected )\n\t\t\tsvs.clients[i].state = cs_connected;\n\n\t\tent = EDICT_NUM( i + 1 );\n\t\tsvs.clients[i].pViewEntity = NULL;\n\t\tsvs.clients[i].edict = ent;\n\t\tSV_InitEdict( ent );\n\t}\n\n\t// heartbeats will always be sent to the id master\n\tNET_MasterClear();\n\n\t// get actual movevars\n\tSV_UpdateMovevars( true );\n\n\t// clear physics interaction links\n\tSV_ClearWorld();\n\n\t// pregenerate test packet\n\tSV_GenerateTestPacket();\n\n\treturn true;\n}\n\nqboolean SV_Active( void )\n{\n\treturn (sv.state != ss_dead);\n}\n\nqboolean SV_Initialized( void )\n{\n\treturn svs.initialized;\n}\n\nint SV_GetMaxClients( void )\n{\n\treturn svs.maxclients;\n}\n\n/*\n================\nSV_ExecLoadLevel\n\nState machine exec new map\n================\n*/\nvoid SV_ExecLoadLevel( void )\n{\n\tSV_SetStringArrayMode( false );\n\tif( SV_SpawnServer( GameState->levelName, NULL, GameState->backgroundMap ))\n\t{\n\t\tSV_SpawnEntities( GameState->levelName );\n\t\tSV_ActivateServer( true );\n\t}\n}\n\n/*\n================\nSV_ExecLoadGame\n\nState machine exec load saved game\n================\n*/\nvoid SV_ExecLoadGame( void )\n{\n\tif( SV_SpawnServer( GameState->levelName, NULL, false ))\n\t{\n\t\tif( !SV_LoadGameState( GameState->levelName ))\n\t\t\tSV_SpawnEntities( GameState->levelName );\n\t\tSV_ActivateServer( false );\n\t}\n}\n\n/*\n================\nSV_ExecChangeLevel\n\nState machine exec changelevel path\n================\n*/\nvoid SV_ExecChangeLevel( void )\n{\n\tSV_ChangeLevel( GameState->loadGame, GameState->levelName, GameState->landmarkName, GameState->backgroundMap );\n}\n"
  },
  {
    "path": "engine/server/sv_log.c",
    "content": "/*\nsv_log.c - server logging in multiplayer\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n\nvoid Log_Open( void )\n{\n\ttime_t\t\tltime;\n\tstruct tm\t\t*today;\n\tchar\t\tszFileBase[ MAX_OSPATH ];\n\tchar\t\tszTestFile[ MAX_OSPATH ];\n\tfile_t\t\t*fp = NULL;\n\tconst char\t\t*temp;\n\tint\t\ti;\n\n\tif( !svs.log.active )\n\t\treturn;\n\n\tif( sv_log_onefile.value && svs.log.file )\n\t\treturn;\n\n\tif( !mp_logfile.value )\n\t{\n\t\tCon_Printf( \"Server logging data to console.\\n\" );\n\t\treturn;\n\t}\n\n\tLog_Close();\n\n\t// Find a new log file slot\n\ttime( &ltime );\n\ttoday = localtime( &ltime );\n\ttemp = Cvar_VariableString( \"logsdir\" );\n\n\tif( COM_CheckString( temp ) && !Q_strchr( temp, ':' ) && !Q_strstr( temp, \"..\" ))\n\t\tQ_snprintf( szFileBase, sizeof( szFileBase ), \"%s/L%02i%02i\", temp, today->tm_mon + 1, today->tm_mday );\n\telse Q_snprintf( szFileBase, sizeof( szFileBase ), \"logs/L%02i%02i\", today->tm_mon + 1, today->tm_mday );\n\n\tfor ( i = 0; i < 1000; i++ )\n\t{\n\t\tQ_snprintf( szTestFile, sizeof( szTestFile ), \"%s%03i.log\", szFileBase, i );\n\n\t\tif( FS_FileExists( szTestFile, false ))\n\t\t\tcontinue;\n\n\t\tfp = FS_Open( szTestFile, \"w\", true );\n\t\tif( fp )\n\t\t{\n\t\t\tCon_Printf( \"Server logging data to file %s\\n\", szTestFile );\n\t\t}\n\t\telse\n\t\t{\n\t\t\ti = 1000;\n\t\t}\n\t\tbreak;\n\t}\n\n\tif( i == 1000 )\n\t{\n\t\tCon_Printf( \"Unable to open logfiles under %s\\nLogging disabled\\n\", szFileBase );\n\t\tsvs.log.active = false;\n\t\treturn;\n\t}\n\n\tif( fp ) svs.log.file = fp;\n\tLog_Printf( \"Log file started (file \\\"%s\\\") (game \\\"%s\\\") (version \\\"%i/\" XASH_VERSION \"/%d\\\")\\n\",\n\tszTestFile, Info_ValueForKey( svs.serverinfo, \"*gamedir\" ), PROTOCOL_VERSION, Q_buildnum() );\n}\n\nvoid Log_Close( void )\n{\n\tif( svs.log.file )\n\t{\n\t\tLog_Printf( \"Log file closed\\n\" );\n\t\tFS_Close( svs.log.file );\n\t}\n\tsvs.log.file = NULL;\n}\n\n/*\n==================\nLog_Printf\n\nPrints a frag log message to the server's frag log file, console, and possible a UDP port.\n==================\n*/\nvoid Log_Printf( const char *fmt, ... )\n{\n\tva_list\t\targptr;\n\tstatic char\tstring[1024];\n\tchar\t\t*p;\n\ttime_t\t\tltime;\n\tstruct tm\t*today;\n\tint\t\tlen;\n\n\tif( !svs.log.active )\n\t\treturn;\n\n\ttime( &ltime );\n\ttoday = localtime( &ltime );\n\n\tlen = Q_snprintf( string, sizeof( string ), \"%02i/%02i/%04i - %02i:%02i:%02i: \",\n\t\ttoday->tm_mon+1, today->tm_mday, 1900 + today->tm_year, today->tm_hour, today->tm_min, today->tm_sec );\n\n\tp = string + len;\n\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( p, sizeof( string ) - len, fmt, argptr );\n\tva_end( argptr );\n\n\tif( svs.log.net_log )\n\t\tNetchan_OutOfBandPrint( NS_SERVER, svs.log.net_address, \"log %s\", string );\n\n\tif( svs.log.active && ( svs.maxclients > 1 || sv_log_singleplayer.value != 0.0f ))\n\t{\n\t\t// echo to server console\n\t\tif( mp_logecho.value )\n\t\t\tCon_Printf( \"%s\", string );\n\n\t\t// echo to log file\n\t\tif( svs.log.file && mp_logfile.value )\n\t\t\tFS_Printf( svs.log.file, \"%s\", string );\n\t}\n}\n\nstatic void Log_PrintServerCvar( const char *var_name, const char *var_value, const void *unused2, void *unused3 )\n{\n\tLog_Printf( \"Server cvar \\\"%s\\\" = \\\"%s\\\"\\n\", var_name, var_value );\n}\n\n/*\n==================\nLog_PrintServerVars\n\n==================\n*/\nvoid Log_PrintServerVars( void )\n{\n\tif( !svs.log.active )\n\t\treturn;\n\n\tLog_Printf( \"Server cvars start\\n\" );\n\tCvar_LookupVars( FCVAR_SERVER, NULL, NULL, (setpair_t)Log_PrintServerCvar );\n\tLog_Printf( \"Server cvars end\\n\" );\n}\n\n/*\n====================\nSV_SetLogAddress_f\n\n====================\n*/\nvoid SV_SetLogAddress_f( void )\n{\n\tconst char *s;\n\tint port;\n\tstring addr;\n\n\tif( svs.log.net_log && Cmd_Argc() == 2 && !Q_strcmp( Cmd_Argv( 1 ), \"off\" )) \n\t{\n\t\tsvs.log.net_log = false;\n\t\tmemset( &svs.log.net_address, 0, sizeof( netadr_t ));\n\n\t\tCon_Printf( \"logaddress: disabled.\\n\" );\n\n\t\treturn;\n\t}\n\n\tif( Cmd_Argc() != 3 )\n\t{\n\t\tCon_Printf( S_USAGE \"logaddress < ip port | off >\\n\" );\n\n\t\tif( svs.log.active && svs.log.net_log )\n\t\t\tCon_Printf( \"current: %s\\n\", NET_AdrToString( svs.log.net_address ));\n\n\t\treturn;\n\t}\n\n\tport = Q_atoi( Cmd_Argv( 2 ));\n\tif( !port )\n\t{\n\t\tCon_Printf( \"logaddress: must specify a valid port\\n\" );\n\t\treturn;\n\t}\n\n\ts = Cmd_Argv( 1 );\n\tif( !COM_CheckString( s ))\n\t{\n\t\tCon_Printf( \"logaddress: unparseable address\\n\" );\n\t\treturn;\n\t}\n\n\tQ_snprintf( addr, sizeof( addr ), \"%s:%i\", s, port );\n\tif( !NET_StringToAdr( addr, &svs.log.net_address ))\n\t{\n\t\tCon_Printf( \"logaddress: unable to resolve %s\\n\", addr );\n\t\treturn;\n\t}\n\n\tsvs.log.net_log = true;\n\tCon_Printf( \"logaddress: %s\\n\", NET_AdrToString( svs.log.net_address ));\n}\n\n/*\n====================\nSV_ServerLog_f\n\n====================\n*/\nvoid SV_ServerLog_f( void )\n{\n\tif( Cmd_Argc() != 2 )\n\t{\n\t\tCon_Printf( S_USAGE \"log < on|off >\\n\" );\n\n\t\tif( svs.log.active )\n\t\t\tCon_Printf( \"currently logging\\n\" );\n\t\telse Con_Printf( \"not currently logging\\n\" );\n\t\treturn;\n\t}\n\n\tif( !Q_stricmp( Cmd_Argv( 1 ), \"off\" ))\n\t{\n\t\tif( svs.log.active )\n\t\t{\n\t\t\tLog_Close();\n\t\t\tsvs.log.active = false;\n\t\t}\n\t}\n\telse if( !Q_stricmp( Cmd_Argv( 1 ), \"on\" ))\n\t{\n\t\tsvs.log.active = true;\n\t\tLog_Open();\n\t}\n\telse\n\t{\n\t\tCon_Printf( \"log: unknown parameter %s\\n\", Cmd_Argv( 1 ));\n\t}\n\n\treturn;\n}\n"
  },
  {
    "path": "engine/server/sv_main.c",
    "content": "/*\nsv_main.c - server main loop\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"net_encode.h\"\n#include \"platform/platform.h\"\n\n// server cvars\nCVAR_DEFINE_AUTO( sv_lan, \"0\", 0, \"server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc.\" );\nCVAR_DEFINE_AUTO( sv_lan_rate, \"20000.0\", 0, \"rate for lan server\" );\nCVAR_DEFINE_AUTO( sv_nat, \"0\", 0, \"enable NAT bypass for this server\" );\nCVAR_DEFINE_AUTO( sv_aim, \"1\", FCVAR_ARCHIVE|FCVAR_SERVER, \"auto aiming option\" );\nCVAR_DEFINE_AUTO( sv_allow_autoaim, \"0\", FCVAR_ARCHIVE|FCVAR_SERVER, \"auto aiming option (for HL25 compatibility)\" );\nCVAR_DEFINE_AUTO( sv_unlag, \"1\", 0, \"allow lag compensation on server-side\" );\nCVAR_DEFINE_AUTO( sv_maxunlag, \"0.5\", 0, \"max latency value which can be interpolated (by default ping should not exceed 500 units)\" );\nCVAR_DEFINE_AUTO( sv_unlagpush, \"0.0\", 0, \"interpolation bias for unlag time\" );\nCVAR_DEFINE_AUTO( sv_unlagsamples, \"1\", 0, \"max samples to interpolate\" );\nCVAR_DEFINE_AUTO( rcon_password, \"\", FCVAR_PROTECTED | FCVAR_PRIVILEGED, \"remote connect password\" );\nCVAR_DEFINE_AUTO( rcon_enable, \"1\", FCVAR_PROTECTED, \"enable accepting remote commands on server\" );\n// TODO: CVAR_DEFINE_AUTO( sv_filterban, \"1\", 0, \"filter banned users\" );\nCVAR_DEFINE_AUTO( sv_cheats, \"0\", FCVAR_SERVER, \"allow cheats on server\" );\nCVAR_DEFINE_AUTO( sv_instancedbaseline, \"1\", 0, \"allow to use instanced baselines to saves network overhead\" );\nstatic CVAR_DEFINE_AUTO( sv_contact, \"\", FCVAR_ARCHIVE|FCVAR_SERVER, \"server techincal support contact address or web-page\" );\nCVAR_DEFINE_AUTO( sv_minupdaterate, \"25.0\", FCVAR_ARCHIVE, \"minimal value for 'cl_updaterate' window\" );\nCVAR_DEFINE_AUTO( sv_maxupdaterate, \"60.0\", FCVAR_ARCHIVE, \"maximal value for 'cl_updaterate' window\" );\nCVAR_DEFINE_AUTO( sv_minrate, \"5000\", FCVAR_SERVER, \"min bandwidth rate allowed on server, 0 == unlimited\" );\nCVAR_DEFINE_AUTO( sv_maxrate, \"50000\", FCVAR_SERVER, \"max bandwidth rate allowed on server, 0 == unlimited\" );\n// TODO: CVAR_DEFINE_AUTO( sv_logrelay, \"0\", FCVAR_ARCHIVE, \"allow log messages from remote machines to be logged on this server\" );\nCVAR_DEFINE_AUTO( sv_newunit, \"0\", 0, \"clear level-saves from previous SP game chapter to help keep .sav file size as minimum\" );\nCVAR_DEFINE_AUTO( sv_clienttrace, \"1\", FCVAR_SERVER, \"0 = big box(Quake), 0.5 = halfsize, 1 = normal (100%), otherwise it's a scaling factor\" );\nstatic CVAR_DEFINE_AUTO( sv_timeout, \"65\", 0, \"after this many seconds without a message from a client, the client is dropped\" );\nstatic CVAR_DEFINE_AUTO( sv_connect_timeout, \"15\", 0, \"after this many seconds without a message from a client, the client is dropped\" );\nCVAR_DEFINE_AUTO( sv_failuretime, \"0.5\", 0, \"after this long without a packet from client, don't send any more until client starts sending again\" );\nCVAR_DEFINE_AUTO( sv_password, \"\", FCVAR_SERVER|FCVAR_PROTECTED, \"server password for entry into multiplayer games\" );\n// TODO: CVAR_DEFINE_AUTO( sv_proxies, \"1\", FCVAR_SERVER, \"maximum count of allowed proxies for HLTV spectating\" );\nCVAR_DEFINE_AUTO( sv_send_logos, \"1\", 0, \"send custom decal logo to other players so they can view his too\" );\nCVAR_DEFINE_AUTO( sv_send_resources, \"1\", 0, \"allow to download missed resources for players\" );\n// TODO: CVAR_DEFINE_AUTO( sv_logbans, \"0\", 0, \"print into the server log info about player bans\" );\nCVAR_DEFINE( sv_allow_upload, \"sv_allowupload\", \"1\", FCVAR_SERVER, \"allow uploading custom resources on a server\" );\nCVAR_DEFINE( sv_allow_download, \"sv_allowdownload\", \"1\", FCVAR_SERVER, \"allow downloading custom resources to the client\" );\nstatic CVAR_DEFINE_AUTO( sv_allow_dlfile, \"1\", 0, \"compatibility cvar, does nothing\" );\nCVAR_DEFINE_AUTO( sv_uploadmax, \"0.5\", FCVAR_SERVER, \"max size to upload custom resources (500 kB as default)\" );\nCVAR_DEFINE_AUTO( sv_downloadurl, \"\", FCVAR_PROTECTED, \"location from which clients can download missing files\" );\nCVAR_DEFINE( sv_consistency, \"mp_consistency\", \"1\", FCVAR_SERVER, \"enbale consistency check in multiplayer\" );\nCVAR_DEFINE_AUTO( mp_logecho, \"1\", 0, \"log multiplayer frags to server logfile\" );\nCVAR_DEFINE_AUTO( mp_logfile, \"1\", 0, \"log multiplayer frags to console\" );\nCVAR_DEFINE_AUTO( sv_log_singleplayer, \"0\", FCVAR_ARCHIVE, \"allows logging in singleplayer games\" );\nCVAR_DEFINE_AUTO( sv_log_onefile, \"0\", FCVAR_ARCHIVE, \"logs server information to only one file\" );\nCVAR_DEFINE_AUTO( sv_trace_messages, \"0\", FCVAR_LATCH, \"enable server usermessages tracing (good for developers)\" );\nCVAR_DEFINE_AUTO( sv_master_response_timeout, \"4\", FCVAR_ARCHIVE, \"master server heartbeat response timeout in seconds\" );\nCVAR_DEFINE_AUTO( sv_autosave, \"1\", FCVAR_ARCHIVE|FCVAR_SERVER|FCVAR_PRIVILEGED, \"enable autosaving\" );\nCVAR_DEFINE_AUTO( sv_speedhack_kick, \"10\", FCVAR_ARCHIVE, \"number of speedhack warns before automatic kick (0 to disable)\" );\n\n// game-related cvars\nstatic CVAR_DEFINE_AUTO( mapcyclefile, \"mapcycle.txt\", 0, \"name of multiplayer map cycle configuration file\" );\nstatic CVAR_DEFINE_AUTO( motdfile, \"motd.txt\", 0, \"name of 'message of the day' file\" );\nstatic CVAR_DEFINE_AUTO( logsdir, \"logs\", 0, \"place to store multiplayer logs\" );\nstatic CVAR_DEFINE_AUTO( bannedcfgfile, \"banned.cfg\", 0, \"name of list of banned users\" );\nCVAR_DEFINE_AUTO( deathmatch, \"0\", FCVAR_SERVER, \"deathmatch mode in multiplayer game\" );\nCVAR_DEFINE_AUTO( coop, \"0\", FCVAR_SERVER, \"cooperative mode in multiplayer game\" );\nstatic CVAR_DEFINE_AUTO( teamplay, \"0\", 0, \"team mode in multiplayer game (Quake only)\" );\nCVAR_DEFINE_AUTO( skill, \"1\", 0, \"skill level in singleplayer game\" );\nstatic CVAR_DEFINE_AUTO( temp1, \"0\", 0, \"temporary cvar that used by some mods\" );\nstatic CVAR_DEFINE_AUTO( listipcfgfile, \"listip.cfg\", 0, \"name of listip.cfg file\" );\nstatic CVAR_DEFINE_AUTO( mapchangecfgfile, \"\", 0, \"name of map change configuration file\" );\nstatic CVAR_DEFINE_AUTO( disconcfgfile, \"\", 0, \"name of disconnect configuration file\" );\nstatic CVAR_DEFINE_AUTO( _sv_override_scientist_mdl, \"\", 0, \"override default scientist model name (specially for HL25 Uplink maps)\" );\n\n// physic-related variables\nCVAR_DEFINE_AUTO( sv_gravity, \"800\", FCVAR_SERVER|FCVAR_MOVEVARS, \"world gravity value\" );\nCVAR_DEFINE_AUTO( sv_stopspeed, \"100\", FCVAR_SERVER|FCVAR_MOVEVARS, \"how fast you come to a complete stop\" );\nstatic CVAR_DEFINE_AUTO( sv_maxspeed, \"320\", FCVAR_SERVER|FCVAR_MOVEVARS, \"maximum speed a player can accelerate to when on ground\" );\nstatic CVAR_DEFINE_AUTO( sv_spectatormaxspeed, \"500\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"maximum speed a spectator can accelerate in air\" );\nstatic CVAR_DEFINE_AUTO( sv_accelerate, \"10\", FCVAR_SERVER|FCVAR_MOVEVARS, \"rate at which a player accelerates to sv_maxspeed\" );\nstatic CVAR_DEFINE_AUTO( sv_airaccelerate, \"10\", FCVAR_SERVER|FCVAR_MOVEVARS, \"rate at which a player accelerates to sv_maxspeed while in the air\" );\nstatic CVAR_DEFINE_AUTO( sv_wateraccelerate, \"10\", FCVAR_SERVER|FCVAR_MOVEVARS, \"rate at which a player accelerates to sv_maxspeed while in the water\" );\nCVAR_DEFINE_AUTO( sv_friction, \"4\", FCVAR_SERVER|FCVAR_MOVEVARS, \"how fast you slow down\" );\nstatic CVAR_DEFINE( sv_edgefriction, \"edgefriction\", \"2\", FCVAR_SERVER|FCVAR_MOVEVARS, \"how much you slow down when nearing a ledge you might fall off\" );\nstatic CVAR_DEFINE_AUTO( sv_waterfriction, \"1\", FCVAR_SERVER|FCVAR_MOVEVARS, \"how fast you slow down in water\" );\nstatic CVAR_DEFINE_AUTO( sv_bounce, \"1\", FCVAR_SERVER|FCVAR_MOVEVARS, \"bounce factor for entities with MOVETYPE_BOUNCE\" );\nCVAR_DEFINE_AUTO( sv_stepsize, \"18\", FCVAR_SERVER|FCVAR_MOVEVARS, \"how high you and NPC's can step up\" );\nCVAR_DEFINE_AUTO( sv_maxvelocity, \"2000\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"max velocity for all things in the world\" );\nstatic CVAR_DEFINE_AUTO( sv_zmax, \"4096\", FCVAR_MOVEVARS|FCVAR_SPONLY, \"maximum viewable distance\" );\nCVAR_DEFINE_AUTO( sv_wateramp, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"world waveheight factor\" );\nstatic CVAR_DEFINE( sv_footsteps, \"mp_footsteps\", \"1\", FCVAR_SERVER|FCVAR_MOVEVARS, \"play foot steps for players\" );\nCVAR_DEFINE_AUTO( sv_skyname, \"desert\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skybox name (can be dynamically changed in-game)\" );\nstatic CVAR_DEFINE_AUTO( sv_rollangle, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED|FCVAR_ARCHIVE, \"how much to tilt the view when strafing\" );\nstatic CVAR_DEFINE_AUTO( sv_rollspeed, \"200\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"how much strafing is necessary to tilt the view\" );\nCVAR_DEFINE_AUTO( sv_skycolor_r, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight red component value\" );\nCVAR_DEFINE_AUTO( sv_skycolor_g, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight green component value\" );\nCVAR_DEFINE_AUTO( sv_skycolor_b, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight blue component value\" );\nCVAR_DEFINE_AUTO( sv_skyvec_x, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight direction by x-axis\" );\nCVAR_DEFINE_AUTO( sv_skyvec_y, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight direction by y-axis\" );\nCVAR_DEFINE_AUTO( sv_skyvec_z, \"0\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"skylight direction by z-axis\" );\nCVAR_DEFINE_AUTO( sv_wateralpha, \"1\", FCVAR_MOVEVARS|FCVAR_UNLOGGED, \"world surfaces water transparency factor. 1.0 - solid, 0.0 - fully transparent\" );\nCVAR_DEFINE_AUTO( sv_background_freeze, \"1\", FCVAR_ARCHIVE, \"freeze player movement on background maps (e.g. to prevent falling)\" );\nstatic CVAR_DEFINE_AUTO( showtriggers, \"0\", FCVAR_LATCH|FCVAR_TEMPORARY, \"debug cvar shows triggers\" );\nstatic CVAR_DEFINE_AUTO( sv_airmove, \"1\", FCVAR_SERVER, \"obsolete, compatibility issues\" );\nstatic CVAR_DEFINE_AUTO( sv_version, \"\", FCVAR_READ_ONLY, \"engine version string\" );\nCVAR_DEFINE_AUTO( hostname, \"\", FCVAR_PRINTABLEONLY, \"name of current host\" );\nstatic CVAR_DEFINE_AUTO( sv_fps, \"0.0\", 0, \"server framerate\" );\n\n// gore-related cvars\nstatic CVAR_DEFINE_AUTO( violence_hblood, \"1\", 0, \"draw human blood\" );\nstatic CVAR_DEFINE_AUTO( violence_ablood, \"1\", 0, \"draw alien blood\" );\nstatic CVAR_DEFINE_AUTO( violence_hgibs, \"1\", 0, \"show human gib entities\" );\nstatic CVAR_DEFINE_AUTO( violence_agibs, \"1\", 0, \"show alien gib entities\" );\n\n// voice chat\nCVAR_DEFINE_AUTO( sv_voiceenable, \"1\", FCVAR_ARCHIVE|FCVAR_SERVER, \"enable voice support\" );\nCVAR_DEFINE_AUTO( sv_voicequality, \"3\", FCVAR_ARCHIVE, \"voice chat quality level, from 0 to 5, higher is better\" );\n\n// enttools\nCVAR_DEFINE_AUTO( sv_enttools_enable, \"0\", FCVAR_ARCHIVE|FCVAR_PROTECTED, \"enable powerful and dangerous entity tools\" );\nCVAR_DEFINE_AUTO( sv_enttools_maxfire, \"5\", FCVAR_ARCHIVE|FCVAR_PROTECTED, \"limit ent_fire actions count to prevent flooding\" );\n\nCVAR_DEFINE( public_server, \"public\", \"0\", 0, \"change server type from private to public\" );\n\nCVAR_DEFINE_AUTO( sv_novis, \"0\", 0, \"force to ignore server visibility\" );\t\t\t// disable server culling entities by vis\nCVAR_DEFINE( sv_pausable, \"pausable\", \"1\", 0, \"allow players to pause or not\" );\nCVAR_DEFINE( sv_maxclients, \"maxplayers\", \"1\", FCVAR_LATCH, \"server max capacity\" );\nCVAR_DEFINE_AUTO( sv_check_errors, \"0\", FCVAR_ARCHIVE, \"check edicts for errors\" );\nCVAR_DEFINE_AUTO( sv_validate_changelevel, \"0\", 0, \"test change level for level-designer errors\" );\nCVAR_DEFINE( sv_hostmap, \"hostmap\", \"\", 0, \"keep name of last entered map\" );\n\nstatic CVAR_DEFINE_AUTO( sv_allow_joystick, \"1\", FCVAR_ARCHIVE, \"allow connect with joystick enabled\" );\nstatic CVAR_DEFINE_AUTO( sv_allow_mouse, \"1\", FCVAR_ARCHIVE, \"allow connect with mouse\" );\nstatic CVAR_DEFINE_AUTO( sv_allow_touch, \"1\", FCVAR_ARCHIVE, \"allow connect with touch controls\" );\nstatic CVAR_DEFINE_AUTO( sv_allow_vr, \"1\", FCVAR_ARCHIVE, \"allow connect from vr version\" );\nstatic CVAR_DEFINE_AUTO( sv_allow_noinputdevices, \"1\", FCVAR_ARCHIVE, \"allow connect from old versions without useragent\" );\n\nCVAR_DEFINE_AUTO( sv_userinfo_enable_penalty, \"1\", FCVAR_ARCHIVE, \"enable penalty time for too fast userinfo updates(name, model, etc)\" );\nCVAR_DEFINE_AUTO( sv_userinfo_penalty_time, \"0.3\", FCVAR_ARCHIVE, \"initial penalty time\" );\nCVAR_DEFINE_AUTO( sv_userinfo_penalty_multiplier, \"2\", FCVAR_ARCHIVE, \"penalty time multiplier\" );\nCVAR_DEFINE_AUTO( sv_userinfo_penalty_attempts, \"4\", FCVAR_ARCHIVE, \"if max attempts count was exceeded, penalty time will be increased\" );\nCVAR_DEFINE_AUTO( sv_fullupdate_penalty_time, \"1\", FCVAR_ARCHIVE, \"allow fullupdate command only once in this timewindow (set 0 to disable)\" );\nCVAR_DEFINE_AUTO( sv_log_outofband, \"0\", FCVAR_ARCHIVE, \"log out of band messages, can be useful for server admins and for engine debugging\" );\nCVAR_DEFINE_AUTO( sv_allow_testpacket, \"1\", FCVAR_ARCHIVE, \"allow generating and sending a big blob of data to test maximum packet size\" );\nCVAR_DEFINE_AUTO( sv_expose_player_list, \"1\", FCVAR_ARCHIVE, \"expose player list through packets that don't require connection\" );\n\n//============================================================================\n/*\n================\nSV_HasActivePlayers\n\nreturns true if server have spawned players\n================\n*/\nstatic qboolean SV_HasActivePlayers( void )\n{\n\tint\ti;\n\n\t// server inactive\n\tif( !svs.clients ) return false;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tif( svs.clients[i].state == cs_spawned )\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\n/*\n===================\nSV_UpdateMovevars\n\ncheck movevars for changes every frame\nsend updates to client if changed\n===================\n*/\nvoid SV_UpdateMovevars( qboolean initialize )\n{\n\tif( sv.state == ss_dead )\n\t\treturn;\n\n\tif( !initialize && !host.movevars_changed )\n\t\treturn;\n\n\t// NOTE: Natural Selection mod on ns_machina map that uses model as sky\n\t// it sets the value to 4000000 that even exceeds the coord limit, but\n\t// it's fine until the value fits in \"zmax\" delta field\n\t// However, some stupid mappers set an insane value like 999999999 which\n\t// overflows delta. In this case, just clamp it to something bigger\n\tif( sv_zmax.value < 256.0f )\n\t\tCvar_DirectSet( &sv_zmax, \"256\" );\n\telse if( sv_zmax.value > 16777216.0f ) // 2^24\n\t\tCvar_DirectSet( &sv_zmax, \"16777216\" );\n\n\tsvgame.movevars.gravity = sv_gravity.value;\n\tsvgame.movevars.stopspeed = sv_stopspeed.value;\n\tsvgame.movevars.maxspeed = sv_maxspeed.value;\n\tsvgame.movevars.spectatormaxspeed = sv_spectatormaxspeed.value;\n\tsvgame.movevars.accelerate = sv_accelerate.value;\n\tsvgame.movevars.airaccelerate = sv_airaccelerate.value;\n\tsvgame.movevars.wateraccelerate = sv_wateraccelerate.value;\n\tsvgame.movevars.friction = sv_friction.value;\n\tsvgame.movevars.edgefriction = sv_edgefriction.value;\n\tsvgame.movevars.waterfriction = sv_waterfriction.value;\n\tsvgame.movevars.bounce = sv_bounce.value;\n\tsvgame.movevars.stepsize = sv_stepsize.value;\n\tsvgame.movevars.maxvelocity = sv_maxvelocity.value;\n\tsvgame.movevars.zmax = sv_zmax.value;\n\tsvgame.movevars.waveHeight = sv_wateramp.value;\n\tQ_strncpy( svgame.movevars.skyName, sv_skyname.string, sizeof( svgame.movevars.skyName ));\n\tsvgame.movevars.footsteps = sv_footsteps.value;\n\tsvgame.movevars.rollangle = sv_rollangle.value;\n\tsvgame.movevars.rollspeed = sv_rollspeed.value;\n\tsvgame.movevars.skycolor_r = sv_skycolor_r.value;\n\tsvgame.movevars.skycolor_g = sv_skycolor_g.value;\n\tsvgame.movevars.skycolor_b = sv_skycolor_b.value;\n\tsvgame.movevars.skyvec_x = sv_skyvec_x.value;\n\tsvgame.movevars.skyvec_y = sv_skyvec_y.value;\n\tsvgame.movevars.skyvec_z = sv_skyvec_z.value;\n\tsvgame.movevars.wateralpha = sv_wateralpha.value;\n\tsvgame.movevars.features = host.features; // just in case. not really need\n\tsvgame.movevars.entgravity = 1.0f;\n\n\tif( initialize ) return; // too early\n\n\tif( MSG_WriteDeltaMovevars( &sv.reliable_datagram, &svgame.oldmovevars, &svgame.movevars ))\n\t\tsvgame.oldmovevars = svgame.movevars; // oldstate changed\n\n\thost.movevars_changed = false;\n}\n\n/*\n=================\nSV_CheckCmdTimes\n=================\n*/\nstatic void SV_CheckCmdTimes( void )\n{\n\tsv_client_t\t*cl;\n\tstatic double\tlastreset = 0;\n\tfloat\t\tdiff;\n\tint\t\ti;\n\n\tif( sv_fps.value != 0.0f )\n\t{\n\t\tif( sv_fps.value < MIN_FPS )\n\t\t\tCvar_SetValue( \"sv_fps\", MIN_FPS );\n\n\t\tif( sv_fps.value > MAX_FPS_HARD )\n\t\t\tCvar_SetValue( \"sv_fps\", MAX_FPS_HARD );\n\t}\n\n\tif( Host_IsLocalGame( ))\n\t\treturn;\n\n\tif(( host.realtime - lastreset ) < 1.0 )\n\t\treturn;\n\n\tlastreset = host.realtime;\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t{\n\t\tif( cl->state != cs_spawned )\n\t\t\tcontinue;\n\n\t\tif( cl->connecttime == 0.0 )\n\t\t{\n\t\t\tcl->connecttime = host.realtime;\n\t\t}\n\n\t\tdiff = cl->connecttime + cl->cmdtime - host.realtime;\n\n\t\tif( diff > net_clockwindow.value )\n\t\t{\n\t\t\tcl->ignorecmdtime = net_clockwindow.value + host.realtime;\n\t\t\tcl->cmdtime = host.realtime - cl->connecttime;\n\t\t}\n\t\telse if( diff < -net_clockwindow.value )\n\t\t{\n\t\t\tcl->cmdtime = host.realtime - cl->connecttime;\n\t\t}\n\t}\n}\n\n/*\n=================\nSV_ProcessFile\n\nprocess incoming file (customization)\n=================\n*/\nstatic void SV_ProcessFile( sv_client_t *cl, const char *filename )\n{\n\tcustomization_t\t*pList;\n\tresource_t\t*resource;\n\tresource_t\t*next;\n\tbyte\t\tmd5[16];\n\tqboolean\t\tbFound;\n\tqboolean\t\tbError;\n\n\tif( filename[0] != '!' )\n\t{\n\t\tCon_Printf( \"Ignoring non-customization file upload of %s\\n\", filename );\n\t\treturn;\n\t}\n\n\tCOM_HexConvert( filename + 4, 32, md5 );\n\n\tfor( resource = cl->resourcesneeded.pNext; resource != &cl->resourcesneeded; resource = next )\n\t{\n\t\tnext = resource->pNext;\n\n\t\tif( !memcmp( resource->rgucMD5_hash, md5, 16 ))\n\t\t\tbreak;\n\t}\n\n\tif( resource == &cl->resourcesneeded )\n\t{\n\t\tCon_Printf( \"%s: Unrequested decal\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tif( resource->nDownloadSize != cl->netchan.tempbuffersize )\n\t{\n\t\tCon_Printf( \"Downloaded %i bytes for purported %i byte file\\n\", cl->netchan.tempbuffersize, resource->nDownloadSize );\n\t\treturn;\n\t}\n\n\tHPAK_AddLump( true, hpk_custom_file.string, resource, cl->netchan.tempbuffer, NULL );\n\tClearBits( resource->ucFlags, RES_WASMISSING );\n\tSV_MoveToOnHandList( cl, resource );\n\n\tbError = false;\n\tbFound = false;\n\n\tfor( pList = cl->customdata.pNext; pList; pList = pList->pNext )\n\t{\n\t\tif( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 ))\n\t\t{\n\t\t\tbFound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !bFound )\n\t{\n\t\tif( !COM_CreateCustomization( &cl->customdata, resource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA|FCUST_IGNOREINIT, NULL, NULL ))\n\t\t\tbError = true;\n\t}\n\telse\n\t{\n\t\tCon_DPrintf( \"Duplicate resource received and ignored.\\n\" );\n\t}\n\n\tif( bError ) Con_Printf( S_ERROR \"parsing custom decal from %s\\n\", cl->name );\n}\n\n/*\n=================\nSV_ReadPackets\n=================\n*/\nstatic void SV_ReadPackets( void )\n{\n\tsv_client_t\t*cl;\n\tint\t\ti, qport;\n\tsize_t\t\tcurSize;\n\n\twhile( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize ))\n\t{\n\t\tMSG_Init( &net_message, \"ClientPacket\", net_message_buffer, curSize );\n\n\t\t// check for connectionless packet (0xffffffff) first\n\t\tif( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 )\n\t\t{\n\t\t\tSV_ConnectionlessPacket( net_from, &net_message );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// read the qport out of the message so we can fix up\n\t\t// stupid address translating routers\n\t\tMSG_Clear( &net_message );\n\t\tMSG_ReadLong( &net_message );\t// sequence number\n\t\tMSG_ReadLong( &net_message );\t// sequence number\n\t\tqport = (int)MSG_ReadShort( &net_message ) & 0xffff;\n\n\t\t// check for packets from connected clients\n\t\tfor( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ )\n\t\t{\n\t\t\tcl = sv.current_client;\n\n\t\t\tif( cl->state == cs_free || FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\t\tcontinue;\n\n\t\t\tif( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address ))\n\t\t\t\tcontinue;\n\n\t\t\tif( cl->netchan.qport != qport )\n\t\t\t\tcontinue;\n\n\t\t\tif( cl->netchan.remote_address.port != net_from.port )\n\t\t\t\tcl->netchan.remote_address.port = net_from.port;\n\n\t\t\tif( Netchan_Process( &cl->netchan, &net_message ))\n\t\t\t{\n\t\t\t\tif(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))\n\t\t\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame\n\n\t\t\t\t// this is a valid, sequenced packet, so process it\n\t\t\t\tif( cl->frames != NULL && cl->state != cs_zombie )\n\t\t\t\t{\n\t\t\t\t\tSV_ExecuteClientMessage( cl, &net_message );\n\t\t\t\t\tsvgame.globals->frametime = sv.frametime;\n\t\t\t\t\tsvgame.globals->time = sv.time;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// fragmentation/reassembly sending takes priority over all game messages, want this in the future?\n\t\t\tif( Netchan_IncomingReady( &cl->netchan ))\n\t\t\t{\n\t\t\t\tif( Netchan_CopyNormalFragments( &cl->netchan, &net_message, &curSize ))\n\t\t\t\t{\n\t\t\t\t\tMSG_Init( &net_message, \"ClientPacket\", net_message_buffer, curSize );\n\n\t\t\t\t\tif(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned ))\n\t\t\t\t\t\tSetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame\n\n\t\t\t\t\t// this is a valid, sequenced packet, so process it\n\t\t\t\t\tif( cl->frames != NULL && cl->state != cs_zombie )\n\t\t\t\t\t{\n\t\t\t\t\t\tSV_ExecuteClientMessage( cl, &net_message );\n\t\t\t\t\t\tsvgame.globals->frametime = sv.frametime;\n\t\t\t\t\t\tsvgame.globals->time = sv.time;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( Netchan_CopyFileFragments( &cl->netchan, &net_message ))\n\t\t\t\t{\n\t\t\t\t\tSV_ProcessFile( cl, cl->netchan.incomingfilename );\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tif( i != svs.maxclients )\n\t\t\tcontinue;\n\t}\n\n\tsv.current_client = NULL;\n}\n\nstatic void SV_DropTimedOutClient( sv_client_t *cl, qboolean ban )\n{\n\tSV_BroadcastPrintf( NULL, \"%s timed out\\n\", cl->name );\n\tSV_DropClient( cl, false );\n\tcl->state = cs_free; // don't bother with zombie state\n\n\tif( ban )\n\t{\n\t\tCbuf_AddTextf( \"addip 30 %s\\n\", NET_BaseAdrToString( cl->netchan.remote_address ));\n\t}\n}\n\n/*\n==================\nSV_CheckTimeouts\n\nIf a packet has not been received from a client for sv_timeout.value\nseconds, drop the conneciton.  Server frames are used instead of\nrealtime to avoid dropping the local client while debugging.\n\nWhen a client is normally dropped, the sv_client_t goes into a zombie state\nfor a few seconds to make sure any final reliable message gets resent\nif necessary\n==================\n*/\nstatic void SV_CheckTimeouts( void )\n{\n\tint i, numclients = 0;\n\tconst double spawned_droppoint = host.realtime - sv_timeout.value;\n\tconst double connected_droppoint = host.realtime - sv_connect_timeout.value;\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tsv_client_t *cl = &svs.clients[i];\n\n\t\tif( cl->state >= cs_connected )\n\t\t{\n\t\t\tif( cl->edict && !FBitSet( cl->edict->v.flags, FL_SPECTATOR|FL_FAKECLIENT ))\n\t\t\t\tnumclients++;\n\t\t}\n\n\t\t// fake clients do not timeout\n\t\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tcontinue;\n\n\t\tswitch( cl->state )\n\t\t{\n\t\tcase cs_zombie:\n\t\t\t// FIXME: get rid of the zombie state\n\t\t\tcl->state = cs_free; // can now be reused\n\t\t\tbreak;\n\t\tcase cs_connected:\n\t\tcase cs_spawning:\n\t\t\tif( !NET_IsLocalAddress( cl->netchan.remote_address ))\n\t\t\t{\n\t\t\t\tif( cl->connection_started < connected_droppoint )\n\t\t\t\t\tSV_DropTimedOutClient( cl, true );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase cs_spawned:\n\t\t\tif( !NET_IsLocalAddress( cl->netchan.remote_address ))\n\t\t\t{\n\t\t\t\tif( cl->netchan.last_received < spawned_droppoint )\n\t\t\t\t\tSV_DropTimedOutClient( cl, false );\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( svs.maxclients > 1 && sv.paused && !numclients )\n\t{\n\t\t// nobody left, unpause the server\n\t\tSV_TogglePause( \"Pause released since no players are left.\" );\n\t}\n}\n\n/*\n================\nSV_PrepWorldFrame\n\nThis has to be done before the world logic, because\nplayer processing happens outside RunWorldFrame\n================\n*/\nstatic void SV_PrepWorldFrame( void )\n{\n\tedict_t\t*ent;\n\tint\ti;\n\n\tfor( i = 1; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\t\tif( ent->free ) continue;\n\n\t\tClearBits( ent->v.effects, EF_MUZZLEFLASH|EF_NOINTERP );\n\t}\n\n\tif( svgame.physFuncs.pfnPrepWorldFrame != NULL )\n\t\tsvgame.physFuncs.pfnPrepWorldFrame();\n}\n\n/*\n=================\nSV_IsSimulating\n=================\n*/\nstatic qboolean SV_IsSimulating( void )\n{\n\tif( Host_IsDedicated( ))\n\t\treturn true; // always active for dedicated servers\n\n\tif( sv.background && SV_Active() && CL_Active())\n\t{\n\t\tif( CL_IsInConsole( ))\n\t\t\treturn false;\n\t\treturn true; // force simulating for background map\n\t}\n\n\tif( !SV_HasActivePlayers( ))\n\t\treturn false;\n\n\t// allow to freeze everything in singleplayer\n\tif( svs.maxclients <= 1 && sv.playersonly )\n\t\treturn false;\n\n\tif( !sv.paused && CL_IsInGame( ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n=================\nSV_RunGameFrame\n=================\n*/\nstatic qboolean SV_RunGameFrame( void )\n{\n\tsv.simulating = SV_IsSimulating();\n\n\tif( !sv.simulating )\n\t\treturn true;\n\n\tif( sv_fps.value != 0.0f )\n\t{\n\t\tdouble\t\tfps = (1.0 / (double)( sv_fps.value - 0.01f )); // FP issues\n\t\tint\t\tnumFrames = 0;\n\n\t\twhile( sv.time_residual >= fps )\n\t\t{\n\t\t\tsv.frametime = fps;\n\n\t\t\tSV_Physics();\n\n\t\t\tsv.time_residual -= fps;\n\t\t\tsv.time += fps;\n\t\t\tnumFrames++;\n\t\t}\n\n\t\treturn (numFrames != 0);\n\t}\n\telse\n\t{\n\t\tSV_Physics();\n\t\tsv.time += sv.frametime;\n\t\treturn true;\n\t}\n}\n\nstatic void SV_UpdateStatusLine( void )\n{\n#if XASH_PLATFORM_HAVE_STATUS\n\tstatic double lasttime;\n\tstring status;\n\n\tif( !Host_IsDedicated( ))\n\t\treturn;\n\n\t// update only every 1/2 seconds\n\tif(( host.realtime - lasttime ) < 0.5f )\n\t\treturn;\n\n\tif( sv.state == ss_active )\n\t{\n\t\tint clients, bots;\n\t\tSV_GetPlayerCount( &clients, &bots );\n\n\t\tQ_snprintf( status, sizeof( status ),\n\t\t\t\"%.1f fps %2i/%2i on %16s\",\n\t\t\t1.f / sv.frametime,\n\t\t\tclients, svs.maxclients, host.game.levelName );\n\t}\n\telse if( sv.state == ss_loading )\n\t\tQ_strncpy( status, \"Loading level\", sizeof( status ));\n\telse if( !svs.initialized )\n\t\tQ_strncpy( status, \"Server is not loaded\", sizeof( status ));\n\t// FIXME: unreachable branch\n\t// else if( host.status == HOST_SHUTDOWN )\n\t//\tQ_strncpy( status, \"Shutting down...\", sizeof( status ));\n\telse Q_strncpy( status, \"Unknown status\", sizeof( status ));\n\n\tPlatform_SetStatus( status );\n\tlasttime = host.realtime;\n#endif // XASH_PLATFORM_HAVE_STATUS\n}\n\n/*\n==================\nHost_ServerFrame\n\n==================\n*/\nvoid Host_ServerFrame( void )\n{\n\t// update dedicated server status line in console\n\tSV_UpdateStatusLine ();\n\n\t// if server is not active, do nothing\n\tif( !svs.initialized ) return;\n\n\tif( sv_fps.value != 0.0f && ( sv.simulating || sv.state != ss_active ))\n\t\tsv.time_residual += host.frametime;\n\n\tif( sv_fps.value == 0.0f )\n\t\tsv.frametime = host.frametime;\n\tsvgame.globals->frametime = sv.frametime;\n\n\t// check clients timewindow\n\tSV_CheckCmdTimes ();\n\n\t// read packets from clients\n\tSV_ReadPackets ();\n\n\t// refresh physic movevars on the client side\n\tSV_UpdateMovevars ( false );\n\n\t// request missing resources for clients\n\tSV_RequestMissingResources();\n\n\t// check timeouts\n\tSV_CheckTimeouts ();\n\n\t// let everything in the world think and move\n\tif( !SV_RunGameFrame ()) return;\n\n\t// send messages back to the clients that had packets read this frame\n\tSV_SendClientMessages ();\n\n\t// clear edict flags for next frame\n\tSV_PrepWorldFrame ();\n\n\t// send a heartbeat to the master if needed\n\tNET_MasterHeartbeat ();\n}\n\n//============================================================================\n/*\n=================\nSV_AddToMaster\n\nA server info answer to master server.\nMaster will validate challenge and this server to public list\n=================\n*/\nvoid SV_AddToMaster( netadr_t from, sizebuf_t *msg )\n{\n\tuint\tchallenge, challenge2, heartbeat_challenge;\n\tchar\ts[MAX_INFO_STRING] = S2M_INFO; // skip 2 bytes of header\n\tint\tclients, bots;\n\tdouble last_heartbeat;\n\tconst int len = sizeof( s );\n\n\tif( !NET_GetMaster( from, &heartbeat_challenge, &last_heartbeat ))\n\t{\n\t\tCon_Reportf( S_WARN \"unexpected master server info query packet from %s\\n\", NET_AdrToString( from ));\n\t\treturn;\n\t}\n\n\tchallenge = MSG_ReadDword( msg );\n\tchallenge2 = MSG_ReadDword( msg );\n\n\tif( challenge2 != heartbeat_challenge )\n\t{\n\t\tCon_Reportf( S_WARN \"unexpected master server info query packet (wrong challenge!)\\n\" );\n\t\treturn;\n\t}\n\n\tif( last_heartbeat + sv_master_response_timeout.value < host.realtime )\n\t{\n\t\tCon_Printf( S_WARN \"unexpected master server info query packet (too late? try increasing sv_master_response_timeout value)\\n\");\n\t\treturn;\n\t}\n\n\tSV_GetPlayerCount( &clients, &bots );\n\tInfo_SetValueForKeyf( s, \"protocol\", len, \"%d\", PROTOCOL_VERSION ); // protocol version\n\tInfo_SetValueForKeyf( s, \"challenge\", len, \"%u\", challenge ); // challenge number\n\tInfo_SetValueForKeyf( s, \"players\", len, \"%d\", clients ); // current player number, without bots\n\tInfo_SetValueForKeyf( s, \"max\", len, \"%d\", svs.maxclients ); // max_players\n\tInfo_SetValueForKeyf( s, \"bots\", len, \"%d\", bots ); // bot count\n\tInfo_SetValueForKey( s, \"gamedir\", GI->gamefolder, len ); // gamedir\n\tInfo_SetValueForKey( s, \"map\", sv.name, len ); // current map\n\tInfo_SetValueForKey( s, \"type\", (Host_IsDedicated()) ? \"d\" : \"l\", len ); // dedicated or local\n\tInfo_SetValueForKey( s, \"password\", \"0\", len ); // is password set\n\tInfo_SetValueForKey( s, \"os\", \"w\", len ); // Windows\n\tInfo_SetValueForKey( s, \"secure\", \"0\", len ); // server anti-cheat\n\tInfo_SetValueForKey( s, \"lan\", \"0\", len ); // LAN servers doesn't send info to master\n\tInfo_SetValueForKey( s, \"version\", XASH_VERSION, len ); // server region. 255 -- all regions\n\tInfo_SetValueForKey( s, \"region\", \"255\", len ); // server region. 255 -- all regions\n\tInfo_SetValueForKey( s, \"product\", GI->gamefolder, len ); // product? Where is the difference with gamedir?\n\tInfo_SetValueForKey( s, \"nat\", sv_nat.string, len ); // Server running under NAT, use reverse connection\n\n\tNET_SendPacket( NS_SERVER, Q_strlen( s ), s, from );\n}\n\n/*\n====================\nSV_ProcessUserAgent\n\nsend error message and return false on wrong input devices\n====================\n*/\nqboolean SV_ProcessUserAgent( netadr_t from, const char *useragent )\n{\n\tconst char *input_devices_str = Info_ValueForKey( useragent, \"d\" );\n\tconst char *id = Info_ValueForKey( useragent, \"uuid\" );\n\tsize_t len, i;\n\n\tlen = Q_strlen( id );\n\tif( len != 32 )\n\t{\n\t\tSV_RejectConnection( from, \"invalid authentication certificate\\n\" );\n\t\treturn false;\n\t}\n\n\tfor( i = 0; i < len; i++ )\n\t{\n\t\tchar c = id[i];\n\n\t\tif( !isdigit( id[i] ) && !( c >= 'a' && c <= 'f' ))\n\t\t{\n\t\t\tSV_RejectConnection( from, \"invalid authentication certificate\\n\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif( SV_CheckID( id ))\n\t{\n\t\tSV_RejectConnection( from, \"You are banned!\\n\" );\n\t\treturn false;\n\t}\n\n\tif( !sv_allow_noinputdevices.value && ( !input_devices_str || !input_devices_str[0] ) )\n\t{\n\t\tSV_RejectConnection( from, \"This server does not allow\\nconnect without input devices list.\\nPlease update your engine.\\n\" );\n\t\treturn false;\n\t}\n\n\tif( input_devices_str )\n\t{\n\t\tint input_devices = Q_atoi( input_devices_str );\n\n\t\tif( !sv_allow_touch.value && ( input_devices & INPUT_DEVICE_TOUCH ) )\n\t\t{\n\t\t\tSV_RejectConnection( from, \"This server does not allow touch\\nDisable it (touch_enable 0)\\nto play on this server\\n\" );\n\t\t\treturn false;\n\t\t}\n\t\tif( !sv_allow_mouse.value && ( input_devices & INPUT_DEVICE_MOUSE) )\n\t\t{\n\t\t\tSV_RejectConnection( from, \"This server does not allow mouse\\nDisable it(m_ignore 1)\\nto play on this server\\n\" );\n\t\t\treturn false;\n\t\t}\n\t\tif( !sv_allow_joystick.value && ( input_devices & INPUT_DEVICE_JOYSTICK) )\n\t\t{\n\t\t\tSV_RejectConnection( from, \"This server does not allow joystick\\nDisable it(joy_enable 0)\\nto play on this server\\n\" );\n\t\t\treturn false;\n\t\t}\n\t\tif( !sv_allow_vr.value && ( input_devices & INPUT_DEVICE_VR) )\n\t\t{\n\t\t\tSV_RejectConnection( from, \"This server does not allow VR\\n\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n//============================================================================\n\n/*\n===============\nSV_Init\n\nOnly called at startup, not for each game\n===============\n*/\nvoid SV_Init( void )\n{\n\tstring\tversionString;\n\n\tSV_InitHostCommands();\n\n\tCvar_Getf( \"protocol\", FCVAR_READ_ONLY, \"displays server protocol version\", \"%i\", PROTOCOL_VERSION );\n\tCvar_Get( \"suitvolume\", \"0.25\", FCVAR_ARCHIVE, \"HEV suit volume\" );\n\tCvar_Get( \"sv_background\", \"0\", FCVAR_READ_ONLY, \"indicate what background map is running\" );\n\tCvar_Get( \"gamedir\", GI->gamefolder, FCVAR_READ_ONLY, \"game folder\" );\n\tCvar_Get( \"sv_alltalk\", \"1\", 0, \"allow to talking for all players (legacy, unused)\" );\n\tCvar_Get( \"sv_allow_PhysX\", \"1\", FCVAR_ARCHIVE, \"allow XashXT to usage PhysX engine\" );\t\t\t// XashXT cvar\n\tCvar_Get( \"sv_precache_meshes\", \"1\", FCVAR_ARCHIVE, \"cache SOLID_CUSTOM meshes before level loading\" );\t// Paranoia 2 cvar\n\tCvar_Get( \"servercfgfile\", \"server.cfg\", 0, \"name of dedicated server configuration file\" );\n\tCvar_Get( \"lservercfgfile\", \"listenserver.cfg\", 0, \"name of listen server configuration file\" );\n\n\tCvar_RegisterVariable( &sv_zmax );\n\tCvar_RegisterVariable( &sv_wateramp );\n\tCvar_RegisterVariable( &sv_skycolor_r );\n\tCvar_RegisterVariable( &sv_skycolor_g );\n\tCvar_RegisterVariable( &sv_skycolor_b );\n\tCvar_RegisterVariable( &sv_skyvec_x );\n\tCvar_RegisterVariable( &sv_skyvec_y );\n\tCvar_RegisterVariable( &sv_skyvec_z );\n\tCvar_RegisterVariable( &sv_skyname );\n\tCvar_RegisterVariable( &sv_footsteps );\n\tCvar_RegisterVariable( &sv_wateralpha );\n\tCvar_RegisterVariable( &sv_minupdaterate );\n\tCvar_RegisterVariable( &sv_maxupdaterate );\n\tCvar_RegisterVariable( &sv_minrate );\n\tCvar_RegisterVariable( &sv_maxrate );\n\tCvar_RegisterVariable( &sv_cheats );\n\tCvar_RegisterVariable( &sv_airmove );\n\tCvar_RegisterVariable( &sv_fps );\n\tCvar_RegisterVariable( &showtriggers );\n\tCvar_RegisterVariable( &sv_aim );\n\tCvar_RegisterVariable( &sv_allow_autoaim );\n\tCvar_RegisterVariable( &deathmatch );\n\tCvar_RegisterVariable( &coop );\n\tCvar_RegisterVariable( &teamplay );\n\tCvar_RegisterVariable( &skill );\n\tCvar_RegisterVariable( &temp1 );\n\n\tCvar_RegisterVariable( &rcon_password );\n\tCvar_RegisterVariable( &rcon_enable );\n\tCvar_RegisterVariable( &sv_stepsize );\n\tCvar_RegisterVariable( &sv_newunit );\n\tCvar_RegisterVariable( &hostname );\n\tCvar_RegisterVariable( &sv_timeout );\n\tCvar_RegisterVariable( &sv_connect_timeout );\n\tCvar_RegisterVariable( &sv_pausable );\n\tCvar_RegisterVariable( &sv_validate_changelevel );\n\tCvar_RegisterVariable( &sv_clienttrace );\n\tCvar_RegisterVariable( &sv_bounce );\n\tCvar_RegisterVariable( &sv_spectatormaxspeed );\n\tCvar_RegisterVariable( &sv_waterfriction );\n\tCvar_RegisterVariable( &sv_wateraccelerate );\n\tCvar_RegisterVariable( &sv_rollangle );\n\tCvar_RegisterVariable( &sv_rollspeed );\n\tCvar_RegisterVariable( &sv_airaccelerate );\n\tCvar_RegisterVariable( &sv_maxvelocity );\n\tCvar_RegisterVariable( &sv_gravity );\n\tCvar_RegisterVariable( &sv_maxspeed );\n\tCvar_RegisterVariable( &sv_accelerate );\n\tCvar_RegisterVariable( &sv_friction );\n\tCvar_RegisterVariable( &sv_edgefriction );\n\tCvar_RegisterVariable( &sv_stopspeed );\n\tCvar_RegisterVariable( &sv_maxclients );\n\tCvar_RegisterVariable( &sv_check_errors );\n\tCvar_RegisterVariable( &public_server );\n\tCvar_RegisterVariable( &sv_failuretime );\n\tCvar_RegisterVariable( &sv_unlag );\n\tCvar_RegisterVariable( &sv_maxunlag );\n\tCvar_RegisterVariable( &sv_unlagpush );\n\tCvar_RegisterVariable( &sv_unlagsamples );\n\tCvar_RegisterVariable( &sv_allow_upload );\n\tCvar_RegisterVariable( &sv_allow_download );\n\tCvar_RegisterVariable( &sv_allow_dlfile );\n\tCvar_RegisterVariable( &sv_send_logos );\n\tCvar_RegisterVariable( &sv_send_resources );\n\tCvar_RegisterVariable( &sv_uploadmax );\n\tCvar_RegisterVariable( &sv_version );\n\tCvar_RegisterVariable( &sv_instancedbaseline );\n\tCvar_RegisterVariable( &sv_contact );\n\tCvar_RegisterVariable( &sv_consistency );\n\tCvar_RegisterVariable( &sv_downloadurl );\n\tCvar_RegisterVariable( &sv_novis );\n\tCvar_RegisterVariable( &sv_hostmap );\n\tCvar_DirectSet( &sv_hostmap, GI->startmap );\n\tCvar_RegisterVariable( &sv_password );\n\tCvar_RegisterVariable( &sv_lan );\n\tCvar_RegisterVariable( &sv_nat );\n\tCvar_RegisterVariable( &violence_ablood );\n\tCvar_RegisterVariable( &violence_hblood );\n\tCvar_RegisterVariable( &violence_agibs );\n\tCvar_RegisterVariable( &violence_hgibs );\n\tCvar_RegisterVariable( &mp_logecho );\n\tCvar_RegisterVariable( &mp_logfile );\n\tCvar_RegisterVariable( &sv_log_onefile );\n\tCvar_RegisterVariable( &sv_log_singleplayer );\n\tCvar_RegisterVariable( &sv_master_response_timeout );\n\n\tCvar_RegisterVariable( &sv_background_freeze );\n\tCvar_RegisterVariable( &sv_autosave );\n\n\tCvar_RegisterVariable( &mapcyclefile );\n\tCvar_RegisterVariable( &motdfile );\n\tCvar_RegisterVariable( &logsdir );\n\tCvar_RegisterVariable( &bannedcfgfile );\n\tCvar_RegisterVariable( &listipcfgfile );\n\tCvar_RegisterVariable( &mapchangecfgfile );\n\tCvar_RegisterVariable( &disconcfgfile );\n\tCvar_RegisterVariable( &_sv_override_scientist_mdl );\n\n\tCvar_RegisterVariable( &sv_voiceenable );\n\tCvar_RegisterVariable( &sv_voicequality );\n\tCvar_RegisterVariable( &sv_trace_messages );\n\tCvar_RegisterVariable( &sv_enttools_enable );\n\tCvar_RegisterVariable( &sv_enttools_maxfire );\n\n\tCvar_RegisterVariable( &sv_speedhack_kick );\n\n\tCvar_RegisterVariable( &sv_allow_joystick );\n\tCvar_RegisterVariable( &sv_allow_mouse );\n\tCvar_RegisterVariable( &sv_allow_touch );\n\tCvar_RegisterVariable( &sv_allow_vr );\n\tCvar_RegisterVariable( &sv_allow_noinputdevices );\n\n\tCvar_RegisterVariable( &sv_userinfo_enable_penalty );\n\tCvar_RegisterVariable( &sv_userinfo_penalty_time );\n\tCvar_RegisterVariable( &sv_userinfo_penalty_multiplier );\n\tCvar_RegisterVariable( &sv_userinfo_penalty_attempts );\n\tCvar_RegisterVariable( &sv_fullupdate_penalty_time );\n\tCvar_RegisterVariable( &sv_log_outofband );\n\tCvar_RegisterVariable( &sv_allow_testpacket );\n\tCvar_RegisterVariable( &sv_expose_player_list );\n\n\t// when we in developer-mode automatically turn cheats on\n\tif( host_developer.value ) Cvar_SetValue( \"sv_cheats\", 1.0f );\n\n\tMSG_Init( &net_message, \"NetMessage\", net_message_buffer, sizeof( net_message_buffer ));\n\n\tQ_snprintf( versionString, sizeof( versionString ), XASH_ENGINE_NAME \": \" XASH_VERSION \"-%s(%s-%s),%i,%i\",\n\t\tg_buildcommit, Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() );\n\n\tCvar_FullSet( \"sv_version\", versionString, FCVAR_READ_ONLY );\n\n\tSV_InitFilter();\n\tSV_ClearGameState ();\t// delete all temporary *.hl files\n\tSV_InitGame();\n}\n\n/*\n==================\nSV_FinalMessage\n\nUsed by SV_Shutdown to send a final message to all\nconnected clients before the server goes down.  The messages are sent immediately,\nnot just stuck on the outgoing message list, because the server is going\nto totally exit after returning from this function.\n==================\n*/\nvoid SV_FinalMessage( const char *message, qboolean reconnect )\n{\n\tbyte\t\tmsg_buf[1024];\n\tsv_client_t\t*cl;\n\tsizebuf_t\t\tmsg;\n\tint\t\ti;\n\n\tMSG_Init( &msg, \"FinalMessage\", msg_buf, sizeof( msg_buf ));\n\n\tif( COM_CheckString( message ))\n\t{\n\t\tMSG_BeginServerCmd( &msg, svc_print );\n\t\tMSG_WriteString( &msg, message );\n\t}\n\n\tif( reconnect )\n\t{\n\t\tif( svs.maxclients <= 1 )\n\t\t{\n\t\t\tMSG_BeginServerCmd( &msg, svc_changing );\n\t\t\tMSG_WriteOneBit( &msg, GameState->loadGame );\n\t\t}\n\t\telse SV_BuildReconnect( &msg );\n\t}\n\telse\n\t{\n\t\tMSG_BeginServerCmd( &msg, svc_disconnect );\n\t}\n\n\t// send it twice\n\t// stagger the packets to crutch operating system limited buffers\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t\tif( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tNetchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg ));\n\n\tfor( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )\n\t\tif( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tNetchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg ));\n}\n\n/*\n================\nSV_FreeClients\n\nrelease server clients\n================\n*/\nstatic void SV_FreeClients( void )\n{\n\tif( svs.maxclients != 0 )\n\t{\n\t\t// free server static data\n\t\tif( svs.clients )\n\t\t{\n\t\t\tZ_Free( svs.clients );\n\t\t\tsvs.clients = NULL;\n\t\t}\n\n\t\tif( svs.packet_entities )\n\t\t{\n\t\t\tZ_Free( svs.packet_entities );\n\t\t\tsvs.packet_entities = NULL;\n\t\t\tsvs.num_client_entities = 0;\n\t\t\tsvs.next_client_entities = 0;\n\t\t}\n\t}\n}\n\n/*\n================\nSV_Shutdown\n\nCalled when each game quits,\nbefore Sys_Quit or Sys_Error\n================\n*/\nvoid SV_Shutdown( const char *finalmsg )\n{\n\t// already freed\n\tif( !SV_Initialized( ))\n\t{\n\t\t// drop the client if want to load a new map\n\t\tif( CL_IsPlaybackDemo( ))\n\t\t\tCL_Drop();\n\n\t\treturn;\n\t}\n\n\t// don't forget to reset sv_background state\n\tCvar_FullSet( \"sv_background\", \"0\", FCVAR_READ_ONLY );\n\n\tif( COM_CheckString( finalmsg ))\n\t\tCon_Printf( \"%s\", finalmsg );\n\n\t// rcon will be disconnected\n\tSV_EndRedirect( &host.rd );\n\n\tif( svs.clients )\n\t\tSV_FinalMessage( finalmsg, false );\n\n\tif( public_server.value && svs.maxclients != 1 )\n\t\tNET_MasterShutdown();\n\n\tNET_Config( false, false );\n\tSV_DeactivateServer();\n\tCL_Drop();\n\n\t// free current level\n\tmemset( &sv, 0, sizeof( sv ));\n\n\tSV_FreeClients();\n\tsvs.maxclients = 0;\n\n\t// release test packet blob\n\tSV_FreeTestPacket();\n\n\t// release all models\n\tMod_FreeAll();\n\n\tHPAK_FlushHostQueue();\n\tLog_Printf( \"Server shutdown\\n\" );\n\tLog_Close();\n\n\tsvs.initialized = false;\n}\n"
  },
  {
    "path": "engine/server/sv_move.c",
    "content": "/*\nsv_move.c - monsters movement\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"xash3d_mathlib.h\"\n#include \"server.h\"\n#include \"const.h\"\n#include \"pm_defs.h\"\n\n#define MOVE_NORMAL\t\t0\t// normal move in the direction monster is facing\n#define MOVE_STRAFE\t\t1\t// moves in direction specified, no matter which way monster is facing\n\n/*\n=============\nSV_CheckBottom\n\nReturns false if any part of the bottom of the entity is off an edge that\nis not a staircase.\n\n=============\n*/\nqboolean SV_CheckBottom( edict_t *ent, int iMode )\n{\n\tvec3_t\tmins, maxs, start, stop;\n\tfloat\tmid, bottom;\n\tqboolean\tmonsterClip;\n\ttrace_t\ttrace;\n\tint\tx, y;\n\n\tmonsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\tVectorAdd( ent->v.origin, ent->v.mins, mins );\n\tVectorAdd( ent->v.origin, ent->v.maxs, maxs );\n\n\t// if all of the points under the corners are solid world, don't bother\n\t// with the tougher checks\n\t// the corners must be within 16 of the midpoint\n\tstart[2] = mins[2] - 1.0f;\n\n\tfor( x = 0; x <= 1; x++ )\n\t{\n\t\tfor( y = 0; y <= 1; y++ )\n\t\t{\n\t\t\tstart[0] = x ? maxs[0] : mins[0];\n\t\t\tstart[1] = y ? maxs[1] : mins[1];\n\t\t\tsvs.groupmask = ent->v.groupinfo;\n\n\t\t\tif( SV_PointContents( start ) != CONTENTS_SOLID )\n\t\t\t\tgoto realcheck;\n\t\t}\n\t}\n\treturn true; // we got out easy\nrealcheck:\n\t// check it for real...\n\tstart[2] = mins[2];\n\n\tif( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\tstart[2] += sv_stepsize.value;\n\n\t// the midpoint must be within 16 of the bottom\n\tstart[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f;\n\tstart[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f;\n\tstop[2] = start[2] - 2.0f * sv_stepsize.value;\n\n\tif( iMode == WALKMOVE_WORLDONLY )\n\t\ttrace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent );\n\telse trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, monsterClip );\n\n\tif( trace.fraction == 1.0f )\n\t\treturn false;\n\n\tmid = bottom = trace.endpos[2];\n\n\t// the corners must be within 16 of the midpoint\n\tfor( x = 0; x <= 1; x++ )\n\t{\n\t\tfor( y = 0; y <= 1; y++ )\n\t\t{\n\t\t\tstart[0] = stop[0] = x ? maxs[0] : mins[0];\n\t\t\tstart[1] = stop[1] = y ? maxs[1] : mins[1];\n\n\t\t\tif( iMode == WALKMOVE_WORLDONLY )\n\t\t\t\ttrace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent );\n\t\t\telse trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, monsterClip );\n\n\t\t\tif( trace.fraction != 1.0f && trace.endpos[2] > bottom )\n\t\t\t\tbottom = trace.endpos[2];\n\t\t\tif( trace.fraction == 1.0f || mid - trace.endpos[2] > sv_stepsize.value )\n\t\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nvoid SV_WaterMove( edict_t *ent )\n{\n\tfloat\tdrownlevel;\n\tint\twaterlevel;\n\tint\twatertype;\n\tint\tflags;\n\n\tif( ent->v.movetype == MOVETYPE_NOCLIP )\n\t{\n\t\tent->v.air_finished = sv.time + 12.0f;\n\t\treturn;\n\t}\n\n\t// no watermove for monsters but pushables\n\tif(( ent->v.flags & FL_MONSTER ) && ent->v.health <= 0.0f )\n\t\treturn;\n\n\tdrownlevel = (ent->v.deadflag == DEAD_NO) ? 3.0 : 1.0;\n\twaterlevel = ent->v.waterlevel;\n\twatertype = ent->v.watertype;\n\tflags = ent->v.flags;\n\n\tif( !( flags & ( FL_IMMUNE_WATER|FL_GODMODE )))\n\t{\n\t\tif((( flags & FL_SWIM ) && waterlevel > drownlevel ) || waterlevel <= drownlevel )\n\t\t{\n\t\t\tif( ent->v.air_finished > sv.time && ent->v.pain_finished > sv.time )\n\t\t\t{\n\t\t\t\tent->v.dmg += 2;\n\n\t\t\t\tif( ent->v.dmg < 15 )\n\t\t\t\t\tent->v.dmg = 10; // quake1 original code\n\t\t\t\tent->v.pain_finished = sv.time + 1.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->v.air_finished = sv.time + 12.0f;\n\t\t\tent->v.dmg = 2;\n\t\t}\n\t}\n\n\tif( !waterlevel )\n\t{\n\t\tif( flags & FL_INWATER )\n\t\t{\n\t\t\t// leave the water.\n\t\t\tconst char *snd = SoundList_GetRandom( EntityWaterExit );\n\t\t\tif( snd )\n\t\t\t\tSV_StartSound( ent, CHAN_BODY, snd, 1.0f, ATTN_NORM, 0, 100 );\n\n\t\t\tent->v.flags = flags & ~FL_INWATER;\n\t\t}\n\n\t\tent->v.air_finished = sv.time + 12.0f;\n\t\treturn;\n\t}\n\n\tif( watertype == CONTENTS_LAVA )\n\t{\n\t\tif((!( flags & ( FL_IMMUNE_LAVA|FL_GODMODE ))) && ent->v.dmgtime < sv.time )\n\t\t{\n\t\t\tif( ent->v.radsuit_finished < sv.time )\n\t\t\t\tent->v.dmgtime = sv.time + 0.2f;\n\t\t\telse ent->v.dmgtime = sv.time + 1.0f;\n\t\t}\n\t}\n\telse if( watertype == CONTENTS_SLIME )\n\t{\n\t\tif((!( flags & ( FL_IMMUNE_SLIME|FL_GODMODE ))) && ent->v.dmgtime < sv.time )\n\t\t{\n\t\t\tif( ent->v.radsuit_finished < sv.time )\n\t\t\t\tent->v.dmgtime = sv.time + 1.0;\n\t\t\t// otherwise radsuit is fully protect entity from slime\n\t\t}\n\t}\n\n\tif( !( flags & FL_INWATER ))\n\t{\n\t\tif( watertype == CONTENTS_WATER )\n\t\t{\n\t\t\t// entering the water\n\t\t\tconst char *snd = SoundList_GetRandom( EntityWaterEnter );\n\t\t\tif( snd )\n\t\t\t\tSV_StartSound( ent, CHAN_BODY, snd, 1.0f, ATTN_NORM, 0, 100 );\n\t\t}\n\n\t\tent->v.flags = flags | FL_INWATER;\n\t\tent->v.dmgtime = 0.0f;\n\t}\n\n\tif( !( flags & FL_WATERJUMP ))\n\t{\n\t\tVectorMA( ent->v.velocity, ( ent->v.waterlevel * -0.8f * sv.frametime ), ent->v.velocity, ent->v.velocity );\n\t}\n}\n\n/*\n=============\nSV_VecToYaw\n\nconverts dir to yaw\n=============\n*/\nfloat SV_VecToYaw( const vec3_t src )\n{\n\tfloat\tyaw;\n\n\tif( !src ) return 0.0f;\n\n\tif( src[1] == 0.0f && src[0] == 0.0f )\n\t{\n\t\tyaw = 0.0f;\n\t}\n\telse\n\t{\n\t\tyaw = (int)( atan2( src[1], src[0] ) * 180.0 / M_PI );\n\t\tif( yaw < 0 ) yaw += 360.0f;\n\t}\n\treturn yaw;\n}\n\n//============================================================================\n\nqboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink )\n{\n\tint\ti;\n\ttrace_t\ttrace;\n\tvec3_t\toldorg, neworg, end;\n\tqboolean\tmonsterClip;\n\tedict_t\t*enemy;\n\tfloat\tdz;\n\n\tVectorCopy( ent->v.origin, oldorg );\n\tVectorAdd( ent->v.origin, move, neworg );\n\tmonsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\n\t// well, try it.  Flying and swimming monsters are easiest.\n\tif( FBitSet( ent->v.flags, FL_SWIM|FL_FLY ))\n\t{\n\t\t// try one move with vertical motion, then one without\n\t\tfor( i = 0; i < 2; i++ )\n\t\t{\n\t\t\tVectorAdd( ent->v.origin, move, neworg );\n\n\t\t\tenemy = ent->v.enemy;\n\t\t\tif( i == 0 && enemy != NULL )\n\t\t\t{\n\t\t\t\tdz = ent->v.origin[2] - enemy->v.origin[2];\n\n\t\t\t\tif( dz > 40.0f ) neworg[2] -= 8.0f;\n\t\t\t\telse if( dz < 30.0f ) neworg[2] += 8.0f;\n\t\t\t}\n\n\t\t\ttrace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, neworg, MOVE_NORMAL, ent, monsterClip );\n\n\t\t\tif( trace.fraction == 1.0f )\n\t\t\t{\n\t\t\t\tsvs.groupmask = ent->v.groupinfo;\n\n\t\t\t\t// that move takes us out of the water.\n\t\t\t\t// apparently though, it's okay to travel into solids, lava, sky, etc :)\n\t\t\t\tif( FBitSet( ent->v.flags, FL_SWIM ) && SV_PointContents( trace.endpos ) == CONTENTS_EMPTY )\n\t\t\t\t\treturn 0;\n\n\t\t\t\tVectorCopy( trace.endpos, ent->v.origin );\n\t\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( !SV_IsValidEdict( enemy ))\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tdz = sv_stepsize.value;\n\t\tneworg[2] += dz;\n\t\tVectorCopy( neworg, end );\n\t\tend[2] -= dz * 2.0f;\n\n\t\ttrace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip );\n\t\tif( trace.allsolid )\n\t\t\treturn 0;\n\n\t\tif( trace.startsolid != 0 )\n\t\t{\n\t\t\tneworg[2] -= dz;\n\t\t\ttrace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip );\n\n\t\t\tif( trace.allsolid != 0 || trace.startsolid != 0 )\n\t\t\t\treturn 0;\n\t\t}\n\n\t\tif( trace.fraction == 1.0f )\n\t\t{\n\t\t\tif( FBitSet( ent->v.flags, FL_PARTIALGROUND ))\n\t\t\t{\n\t\t\t\tVectorAdd( ent->v.origin, move, ent->v.origin );\n\t\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\t\t\t\tClearBits( ent->v.flags, FL_ONGROUND );\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( trace.endpos, ent->v.origin );\n\n\t\t\tif( SV_CheckBottom( ent, WALKMOVE_NORMAL ) == 0 )\n\t\t\t{\n\t\t\t\tif( FBitSet( ent->v.flags, FL_PARTIALGROUND ))\n\t\t\t\t{\n\t\t\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\n\t\t\t\tVectorCopy( oldorg, ent->v.origin );\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tClearBits( ent->v.flags, FL_PARTIALGROUND );\n\t\t\t\tent->v.groundentity = trace.ent;\n\t\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\nqboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink )\n{\n\tfloat\ttemp;\n\tvec3_t\toldorg, neworg, end;\n\ttrace_t\ttrace;\n\n\tVectorCopy( ent->v.origin, oldorg );\n\tVectorAdd( ent->v.origin, move, neworg );\n\n\ttemp = sv_stepsize.value;\n\n\tneworg[2] += temp;\n\tVectorCopy( neworg, end );\n\tend[2] -= temp * 2.0f;\n\n\ttrace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent );\n\n\tif( trace.allsolid != 0 )\n\t\treturn 0;\n\n\tif( trace.startsolid != 0 )\n\t{\n\t\tneworg[2] -= temp;\n\t\ttrace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent );\n\n\t\tif( trace.allsolid != 0 || trace.startsolid != 0 )\n\t\t\treturn 0;\n\t}\n\n\tif( trace.fraction == 1.0f )\n\t{\n\t\tif( ent->v.flags & FL_PARTIALGROUND )\n\t\t{\n\t\t\tVectorAdd( ent->v.origin, move, ent->v.origin );\n\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\t\t\tent->v.flags &= ~FL_ONGROUND;\n\t\t\treturn 1;\n\t\t}\n\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tVectorCopy( trace.endpos, ent->v.origin );\n\n\t\tif( SV_CheckBottom( ent, WALKMOVE_WORLDONLY ) == 0 )\n\t\t{\n\t\t\tif( ent->v.flags & FL_PARTIALGROUND )\n\t\t\t{\n\t\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tVectorCopy( oldorg, ent->v.origin );\n\t\t\treturn 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->v.flags &= ~FL_PARTIALGROUND;\n\t\t\tent->v.groundentity = trace.ent;\n\t\t\tif( relink ) SV_LinkEdict( ent, true );\n\n\t\t\treturn 1;\n\t\t}\n\t}\n}\n\nstatic qboolean SV_StepDirection( edict_t *ent, float yaw, float dist )\n{\n\tint\tret;\n\tfloat\tcSin, cCos;\n\tvec3_t\tmove;\n\n\tyaw = yaw * M_PI2 / 360.0f;\n\tSinCos( yaw, &cSin, &cCos );\n\tVectorSet( move, cCos * dist, cSin * dist, 0.0f );\n\n\tret = SV_MoveStep( ent, move, false );\n\tSV_LinkEdict( ent, true );\n\n\treturn ret;\n}\n\nstatic qboolean SV_FlyDirection( edict_t *ent, vec3_t move )\n{\n\tint\tret;\n\n\tret = SV_MoveStep( ent, move, false );\n\tSV_LinkEdict( ent, true );\n\n\treturn ret;\n}\n\nstatic void SV_NewChaseDir( edict_t *actor, vec3_t destination, float dist )\n{\n\tfloat\tdeltax, deltay;\n\tfloat\ttempdir, olddir, turnaround;\n\tvec3_t\td;\n\n\tolddir = anglemod(((int)( actor->v.ideal_yaw / 45.0f )) * 45.0f );\n\tturnaround = anglemod( olddir - 180.0f );\n\n\tdeltax = destination[0] - actor->v.origin[0];\n\tdeltay = destination[1] - actor->v.origin[1];\n\n\tif( deltax > 10.0f )\n\t\td[1] = 0.0f;\n\telse if( deltax < -10.0f )\n\t\td[1] = 180.0f;\n\telse d[1] = -1;\n\n\tif( deltay < -10.0f )\n\t\td[2] = 270.0f;\n\telse if( deltay > 10.0f )\n\t\td[2] = 90.0f;\n\telse d[2] = -1.0f;\n\n\t// try direct route\n\tif( d[1] != -1.0f && d[2] != -1.0f )\n\t{\n\t\tif( d[1] == 0.0f )\n\t\t\ttempdir = ( d[2] == 90.0f ) ? 45.0f : 315.0f;\n\t\telse tempdir = ( d[2] == 90.0f ) ? 135.0f : 215.0f;\n\n\t\tif( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist ))\n\t\t\treturn;\n\t}\n\n\t// try other directions\n\tif( COM_RandomLong( 0, 1 ) != 0 || fabs( deltay ) > fabs( deltax ))\n\t{\n\t\ttempdir = d[1];\n\t\td[1] = d[2];\n\t\td[2] = tempdir;\n\t}\n\n\tif( d[1] != -1.0f && d[1] != turnaround && SV_StepDirection( actor, d[1], dist ))\n\t\treturn;\n\n\tif( d[2] != -1.0f && d[2] != turnaround && SV_StepDirection( actor, d[2], dist ))\n\t\treturn;\n\n\t// there is no direct path to the player, so pick another direction\n\tif( olddir != -1.0f && SV_StepDirection( actor, olddir, dist ))\n\t\treturn;\n\n\t// fine, just run somewhere.\n\tif( COM_RandomLong( 0, 1 ) != 1 )\n\t{\n\t\tfor( tempdir = 0; tempdir <= 315.0f; tempdir += 45.0f )\n\t\t{\n\t\t\tif( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist ))\n\t\t\t\treturn;\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( tempdir = 315.0f; tempdir >= 0.0f; tempdir -= 45.0f )\n\t\t{\n\t\t\tif( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist ))\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t// we tried. run backwards. that ought to work...\n\tif( turnaround != -1.0f && SV_StepDirection( actor, turnaround, dist ))\n\t\treturn;\n\n\t// well, we're stuck somehow.\n\tactor->v.ideal_yaw = olddir;\n\n\t// if a bridge was pulled out from underneath a monster, it may not have\n\t// a valid standing position at all.\n\tif( !SV_CheckBottom( actor, WALKMOVE_NORMAL ))\n\t{\n\t\tactor->v.flags |= FL_PARTIALGROUND;\n\t}\n}\n\nvoid SV_MoveToOrigin( edict_t *ent, const vec3_t pflGoal, float dist, int iMoveType )\n{\n\tvec3_t\tvecDist;\n\n\tVectorCopy( pflGoal, vecDist );\n\n\tif( ent->v.flags & ( FL_FLY|FL_SWIM|FL_ONGROUND ))\n\t{\n\t\tif( iMoveType == MOVE_NORMAL )\n\t\t{\n\t\t\tif( !SV_StepDirection( ent, ent->v.ideal_yaw, dist ))\n\t\t\t{\n\t\t\t\tSV_NewChaseDir( ent, vecDist, dist );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvecDist[0] -= ent->v.origin[0];\n\t\t\tvecDist[1] -= ent->v.origin[1];\n\n\t\t\tif( ent->v.flags & ( FL_FLY|FL_SWIM ))\n\t\t\t\tvecDist[2] -= ent->v.origin[2];\n\t\t\telse vecDist[2] = 0.0f;\n\n\t\t\tVectorNormalize( vecDist );\n\t\t\tVectorScale( vecDist, dist, vecDist );\n\t\t\tSV_FlyDirection( ent, vecDist );\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "engine/server/sv_phys.c",
    "content": "/*\nsv_phys.c - server physic\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"const.h\"\n#include \"library.h\"\n#include \"triangleapi.h\"\n#include \"ref_common.h\"\n\ntypedef int (*PHYSICAPI)( int, server_physics_api_t*, physics_interface_t* );\n#if !XASH_DEDICATED\nextern triangleapi_t gTriApi;\n#endif\n\n/*\npushmove objects do not obey gravity, and do not interact with each other or trigger fields,\nbut block normal movement and push normal objects when they move.\n\nonground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects\n\ndoors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH\nbonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS\ncorpses are SOLID_NOT and MOVETYPE_TOSS\ncrates are SOLID_BBOX and MOVETYPE_TOSS\nwalking monsters are SOLID_BBOX and MOVETYPE_STEP\nflying/floating monsters are SOLID_BBOX and MOVETYPE_FLY\n\nsolid_edge items only clip against bsp models.\n*/\n#define MOVE_EPSILON\t0.01f\n#define MAX_CLIP_PLANES\t5\n\nstatic const vec3_t current_table[] =\n{\n{ 1,  0, 0 },\n{ 0,  1, 0 },\n{-1,  0, 0 },\n{ 0, -1, 0 },\n{ 0,  0, 1 },\n{ 0,  0, -1}\n};\n\n/*\n===============================================================================\n\nUtility functions\n\n===============================================================================\n*/\n/*\n================\nSV_CheckAllEnts\n================\n*/\nstatic void SV_CheckAllEnts( void )\n{\n\tstatic double\tnextcheck;\n\tedict_t\t\t*e;\n\tint\t\ti;\n\n\tif( !sv_check_errors.value || sv.state != ss_active )\n\t\treturn;\n\n\tif(( nextcheck - Sys_DoubleTime()) > 0.0 )\n\t\treturn;\n\n\t// don't check entities every frame (but every 5 secs)\n\tnextcheck = Sys_DoubleTime() + 5.0;\n\n\t// check edicts errors\n\tfor( i = svs.maxclients + 1; i < svgame.numEntities; i++ )\n\t{\n\t\te = EDICT_NUM( i );\n\n\t\tif( e->free && e->pvPrivateData != NULL )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Freed entity %s (%i) has private data.\\n\", SV_ClassName( e ), i );\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( !SV_IsValidEdict( e ))\n\t\t\tcontinue;\n\n\t\tif( !e->v.pContainingEntity || e->v.pContainingEntity != e )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Entity %s (%i) has invalid container, fixed.\\n\", SV_ClassName( e ), i );\n\t\t\te->v.pContainingEntity = e;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( !e->pvPrivateData || !Mem_IsAllocatedExt( svgame.mempool, e->pvPrivateData ))\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"Entity %s (%i) trashed private data.\\n\", SV_ClassName( e ), i );\n\t\t\te->pvPrivateData = NULL;\n\t\t\tcontinue;\n\t\t}\n\n\t\tSV_CheckVelocity( e );\n\t}\n}\n\n/*\n================\nSV_CheckVelocity\n================\n*/\nvoid SV_CheckVelocity( edict_t *ent )\n{\n\tfloat\twishspd;\n\tfloat\tmaxspd;\n\tint\ti;\n\n\t// bound velocity\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( IS_NAN( ent->v.velocity[i] ))\n\t\t{\n\t\t\tif( sv_check_errors.value )\n\t\t\t\tCon_Printf( \"Got a NaN velocity on %s\\n\", STRING( ent->v.classname ));\n\t\t\tent->v.velocity[i] = 0.0f;\n\t\t}\n\n\t\tif( IS_NAN( ent->v.origin[i] ))\n\t\t{\n\t\t\tif( sv_check_errors.value )\n\t\t\t\tCon_Printf( \"Got a NaN origin on %s\\n\", STRING( ent->v.classname ));\n\t\t\tent->v.origin[i] = 0.0f;\n\t\t}\n\t}\n\n\twishspd = DotProduct( ent->v.velocity, ent->v.velocity );\n\tmaxspd = sv_maxvelocity.value * sv_maxvelocity.value * 1.73f; // half-diagonal\n\n\tif( wishspd > maxspd )\n\t{\n\t\twishspd = sqrt( wishspd );\n\t\tif( sv_check_errors.value )\n\t\t\tCon_Printf( \"Got a velocity too high on %s ( %.2f > %.2f )\\n\", STRING( ent->v.classname ), wishspd, sqrt( maxspd ));\n\t\twishspd = sv_maxvelocity.value / wishspd;\n\t\tVectorScale( ent->v.velocity, wishspd, ent->v.velocity );\n\t}\n}\n\n/*\n================\nSV_UpdateBaseVelocity\n================\n*/\nvoid SV_UpdateBaseVelocity( edict_t *ent )\n{\n\tif( ent->v.flags & FL_ONGROUND )\n\t{\n\t\tedict_t\t*groundentity = ent->v.groundentity;\n\n\t\tif( SV_IsValidEdict( groundentity ))\n\t\t{\n\t\t\t// On conveyor belt that's moving?\n\t\t\tif( groundentity->v.flags & FL_CONVEYOR )\n\t\t\t{\n\t\t\t\tvec3_t\tnew_basevel;\n\n\t\t\t\tVectorScale( groundentity->v.movedir, groundentity->v.speed, new_basevel );\n\t\t\t\tif( ent->v.flags & FL_BASEVELOCITY )\n\t\t\t\t\tVectorAdd( new_basevel, ent->v.basevelocity, new_basevel );\n\n\t\t\t\tent->v.flags |= FL_BASEVELOCITY;\n\t\t\t\tVectorCopy( new_basevel, ent->v.basevelocity );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n============\nSV_TestEntityPosition\n\nreturns true if the entity is in solid currently\n============\n*/\nstatic qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker )\n{\n\tqboolean\tmonsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\ttrace_t\ttrace;\n\n\tif( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))\n\t{\n\t\t// to avoid falling through tracktrain update client mins\\maxs here\n\t\tif( FBitSet( ent->v.flags, FL_DUCKING ))\n\t\t\tSV_SetMinMaxSize( ent, host.player_mins[1], host.player_maxs[1], true );\n\t\telse SV_SetMinMaxSize( ent, host.player_mins[0], host.player_maxs[0], true );\n\t}\n\n\ttrace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip );\n\n\tif( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent ))\n\t{\n\t\tif( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker )\n\t\t\treturn trace.startsolid;\n\t\treturn false;\n\t}\n\n\treturn trace.startsolid;\n}\n\n/*\n=============\nSV_RunThink\n\nRuns thinking code if time.  There is some play in the exact time the think\nfunction will be called, because it is called before any movement is done\nin a frame.  Not used for pushmove objects, because they must be exact.\nReturns false if the entity removed itself.\n=============\n*/\nstatic qboolean SV_RunThink( edict_t *ent )\n{\n\tfloat\tthinktime;\n\n\tif( !FBitSet( ent->v.flags, FL_KILLME ))\n\t{\n\t\tthinktime = ent->v.nextthink;\n\t\tif( thinktime <= 0.0f || thinktime > (sv.time + sv.frametime))\n\t\t\treturn true;\n\n\t\tif( thinktime < sv.time )\n\t\t\tthinktime = sv.time;\t// don't let things stay in the past.\n\t\t\t\t\t\t// it is possible to start that way\n\t\t\t\t\t\t// by a trigger with a local time.\n\t\tent->v.nextthink = 0.0f;\n\t\tsvgame.globals->time = thinktime;\n\t\tsvgame.dllFuncs.pfnThink( ent );\n\t}\n\n\tif( FBitSet( ent->v.flags, FL_KILLME ))\n\t\tSV_FreeEdict( ent );\n\n\treturn !ent->free;\n}\n\n/*\n=============\nSV_PlayerRunThink\n\nRuns thinking code if player time.  There is some play in the exact time the think\nfunction will be called, because it is called before any movement is done\nin a frame.  Not used for pushmove objects, because they must be exact.\nReturns false if the entity removed itself.\n=============\n*/\nqboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time )\n{\n\tfloat\tthinktime;\n\n\tif( svgame.physFuncs.SV_PlayerThink )\n\t\treturn svgame.physFuncs.SV_PlayerThink( ent, frametime, time );\n\n\tif( !FBitSet( ent->v.flags, FL_KILLME|FL_DORMANT ))\n\t{\n\t\tthinktime = ent->v.nextthink;\n\t\tif( thinktime <= 0.0f || thinktime > (time + frametime))\n\t\t\treturn true;\n\n\t\tif( thinktime < time )\n\t\t\tthinktime = time;\t// don't let things stay in the past.\n\t\t\t\t\t// it is possible to start that way\n\t\t\t\t\t// by a trigger with a local time.\n\n\t\tent->v.nextthink = 0.0f;\n\t\tsvgame.globals->time = thinktime;\n\t\tsvgame.dllFuncs.pfnThink( ent );\n\t}\n\n\tif( FBitSet( ent->v.flags, FL_KILLME ))\n\t\tClearBits( ent->v.flags, FL_KILLME );\n\n\treturn !ent->free;\n}\n\n/*\n==================\nSV_Impact\n\nTwo entities have touched, so run their touch functions\n==================\n*/\nvoid SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace )\n{\n\tsvgame.globals->time = sv.time;\n\n\tif(( e1->v.flags|e2->v.flags ) & FL_KILLME )\n\t\treturn;\n\n\tif( e1->v.groupinfo && e2->v.groupinfo )\n\t{\n\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( e1->v.groupinfo, e2->v.groupinfo ))\n\t\t\treturn;\n\n\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( e1->v.groupinfo, e2->v.groupinfo ))\n\t\t\treturn;\n\t}\n\n\tif( e1->v.solid != SOLID_NOT )\n\t{\n\t\tSV_CopyTraceToGlobal( trace );\n\t\tsvgame.dllFuncs.pfnTouch( e1, e2 );\n\t}\n\n\tif( e2->v.solid != SOLID_NOT )\n\t{\n\t\tSV_CopyTraceToGlobal( trace );\n\t\tsvgame.dllFuncs.pfnTouch( e2, e1 );\n\t}\n}\n\n/*\n=============\nSV_AngularMove\n\nmay use friction for smooth stopping\n=============\n*/\nstatic void SV_AngularMove( edict_t *ent, float frametime, float friction )\n{\n\tfloat\tadjustment;\n\tint\ti;\n\n\tVectorMA( ent->v.angles, frametime, ent->v.avelocity, ent->v.angles );\n\tif( friction == 0.0f ) return;\n\n\tadjustment = frametime * (sv_stopspeed.value / 10.0f) * sv_friction.value * fabs( friction );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( ent->v.avelocity[i] > 0.0f )\n\t\t{\n\t\t\tent->v.avelocity[i] -= adjustment;\n\t\t\tif( ent->v.avelocity[i] < 0.0f )\n\t\t\t\tent->v.avelocity[i] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->v.avelocity[i] += adjustment;\n\t\t\tif( ent->v.avelocity[i] > 0.0f )\n\t\t\t\tent->v.avelocity[i] = 0.0f;\n\t\t}\n\t}\n}\n\n/*\n=============\nSV_LinearMove\n\nuse friction for smooth stopping\n=============\n*/\nstatic void SV_LinearMove( edict_t *ent, float frametime, float friction )\n{\n\tint\ti;\n\tfloat\tadjustment;\n\n\tVectorMA( ent->v.origin, frametime, ent->v.velocity, ent->v.origin );\n\tif( friction == 0.0f ) return;\n\n\tadjustment = frametime * (sv_stopspeed.value / 10.0f) * sv_friction.value * fabs( friction );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( ent->v.velocity[i] > 0.0f )\n\t\t{\n\t\t\tent->v.velocity[i] -= adjustment;\n\t\t\tif( ent->v.velocity[i] < 0.0f )\n\t\t\t\tent->v.velocity[i] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->v.velocity[i] += adjustment;\n\t\t\tif( ent->v.velocity[i] > 0.0f )\n\t\t\t\tent->v.velocity[i] = 0.0f;\n\t\t}\n\t}\n}\n\n/*\n=============\nSV_RecursiveWaterLevel\n\nrecursively recalculating the middle\n=============\n*/\nstatic float SV_RecursiveWaterLevel( vec3_t origin, float out, float in, int count )\n{\n\tvec3_t\tpoint;\n\tfloat\toffset;\n\n\toffset = ((out - in) * 0.5f) + in;\n\tif( ++count > 5 ) return offset;\n\n\tVectorSet( point, origin[0], origin[1], origin[2] + offset );\n\n\tif( SV_PointContents( point ) == CONTENTS_WATER )\n\t\treturn SV_RecursiveWaterLevel( origin, out, offset, count );\n\treturn SV_RecursiveWaterLevel( origin, offset, in, count );\n}\n\n/*\n=============\nSV_Submerged\n\ndetermine how deep the entity is\n=============\n*/\nstatic float SV_Submerged( edict_t *ent )\n{\n\tfloat\tstart, bottom;\n\tvec3_t\tpoint;\n\tvec3_t\tcenter;\n\n\tVectorAverage( ent->v.absmin, ent->v.absmax, center );\n\tstart = ent->v.absmin[2] - center[2];\n\n\tswitch( ent->v.waterlevel )\n\t{\n\tcase 1:\n\t\tbottom = SV_RecursiveWaterLevel( center, 0.0f, start, 0 );\n\t\treturn bottom - start;\n\tcase 3:\n\t\tVectorSet( point, center[0], center[1], ent->v.absmax[2] );\n\t\tsvs.groupmask = ent->v.groupinfo;\n\t\tif( SV_PointContents( point ) == CONTENTS_WATER )\n\t\t\treturn (ent->v.maxs[2] - ent->v.mins[2]);\n\t\t// intentionally fallthrough\n\tcase 2:\n\t\tbottom = SV_RecursiveWaterLevel( center, ent->v.absmax[2] - center[2], 0.0f, 0 );\n\t\treturn bottom - start;\n\t}\n\n\treturn 0.0f;\n}\n\n/*\n=============\nSV_CheckWater\n=============\n*/\nstatic qboolean SV_CheckWater( edict_t *ent )\n{\n\tint\tcont, truecont;\n\tvec3_t\tpoint;\n\n\tpoint[0] = (ent->v.absmax[0] + ent->v.absmin[0]) * 0.5f;\n\tpoint[1] = (ent->v.absmax[1] + ent->v.absmin[1]) * 0.5f;\n\tpoint[2] = (ent->v.absmin[2] + 1.0f);\n\n\tent->v.watertype = CONTENTS_EMPTY;\n\tsvs.groupmask = ent->v.groupinfo;\n\tent->v.waterlevel = 0;\n\n\tcont = SV_PointContents( point );\n\n\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t{\n\t\tsvs.groupmask = ent->v.groupinfo;\n\t\ttruecont = SV_TruePointContents( point );\n\n\t\tent->v.watertype = cont;\n\t\tent->v.waterlevel = 1;\n\n\t\tif( ent->v.absmin[2] != ent->v.absmax[2] )\n\t\t{\n\t\t\tpoint[2] = (ent->v.absmin[2] + ent->v.absmax[2]) * 0.5f;\n\n\t\t\tsvs.groupmask = ent->v.groupinfo;\n\t\t\tcont = SV_PointContents( point );\n\n\t\t\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t\t\t{\n\t\t\t\tent->v.waterlevel = 2;\n\n\t\t\t\tVectorAdd( point, ent->v.view_ofs, point );\n\t\t\t\tsvs.groupmask = ent->v.groupinfo;\n\t\t\t\tcont = SV_PointContents( point );\n\n\t\t\t\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t\t\t\t\tent->v.waterlevel = 3;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// a point entity\n\t\t\tent->v.waterlevel = 3;\n\t\t}\n\n\t\t// Quake2 feature. Probably never was used in Half-Life...\n\t\tif( truecont <= CONTENTS_CURRENT_0 && truecont >= CONTENTS_CURRENT_DOWN )\n\t\t{\n\t\t\tfloat speed = 150.0f * ent->v.waterlevel / 3.0f;\n\t\t\tconst float *dir = current_table[CONTENTS_CURRENT_0 - truecont];\n\n\t\t\tVectorMA( ent->v.basevelocity, speed, dir, ent->v.basevelocity );\n\t\t}\n\t}\n\n\treturn (ent->v.waterlevel > 1);\n}\n\n/*\n=============\nSV_CheckMover\n\ntest thing (applies the friction to pushables while standing on moving platform)\n=============\n*/\nstatic qboolean SV_CheckMover( edict_t *ent )\n{\n\tedict_t\t*gnd = ent->v.groundentity;\n\n\tif( !SV_IsValidEdict( gnd ))\n\t\treturn false;\n\n\tif( gnd->v.movetype != MOVETYPE_PUSH )\n\t\treturn false;\n\n\tif( VectorIsNull( gnd->v.velocity ) && VectorIsNull( gnd->v.avelocity ))\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n==================\nSV_ClipVelocity\n\nSlide off of the impacting object\n==================\n*/\nstatic int SV_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce )\n{\n\tfloat\tbackoff;\n\tfloat\tchange;\n\tint\ti, blocked;\n\n\tblocked = 0;\n\tif( normal[2] > 0.0f ) blocked |= 1;\t// floor\n\tif( !normal[2] ) blocked |= 2;\t// step\n\n\tbackoff = DotProduct( in, normal ) * overbounce;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tchange = normal[i] * backoff;\n\t\tout[i] = in[i] - change;\n\n\t\tif( out[i] > -1.0f && out[i] < 1.0f )\n\t\t\tout[i] = 0.0f;\n\t}\n\n\treturn blocked;\n}\n\n/*\n===============================================================================\n\n\tFLYING MOVEMENT CODE\n\n===============================================================================\n*/\n/*\n============\nSV_FlyMove\n\nThe basic solid body movement clip that slides along multiple planes\n*steptrace - if not NULL, the trace results of any vertical wall hit will be stored\nReturns the clipflags if the velocity was modified (hit something solid)\n1 = floor\n2 = wall / step\n4 = dead stop\n============\n*/\nstatic int SV_FlyMove( edict_t *ent, float time, trace_t *steptrace )\n{\n\tint\ti, j, numplanes, bumpcount, blocked;\n\tvec3_t\tdir, end, planes[MAX_CLIP_PLANES];\n\tvec3_t\tprimal_velocity, original_velocity, new_velocity;\n\tfloat\td, time_left, allFraction;\n\tqboolean\tmonsterClip;\n\ttrace_t\ttrace;\n\n\tblocked = 0;\n\tmonsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\tVectorCopy( ent->v.velocity, original_velocity );\n\tVectorCopy( ent->v.velocity, primal_velocity );\n\tVectorClear( new_velocity );\n\tnumplanes = 0;\n\n\tallFraction = 0.0f;\n\ttime_left = time;\n\n\tfor( bumpcount = 0; bumpcount < MAX_CLIP_PLANES - 1; bumpcount++ )\n\t{\n\t\tif( VectorIsNull( ent->v.velocity ))\n\t\t\tbreak;\n\n\t\tVectorMA( ent->v.origin, time_left, ent->v.velocity, end );\n\t\ttrace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip );\n\n\t\tallFraction += trace.fraction;\n\n\t\tif( trace.allsolid )\n\t\t{\n\t\t\t// entity is trapped in another solid\n\t\t\tVectorClear( ent->v.velocity );\n\t\t\treturn 4;\n\t\t}\n\n\t\tif( trace.fraction > 0.0f )\n\t\t{\n\t\t\t// actually covered some distance\n\t\t\tVectorCopy( trace.endpos, ent->v.origin );\n\t\t\tVectorCopy( ent->v.velocity, original_velocity );\n\t\t\tnumplanes = 0;\n\t\t}\n\n\t\tif( trace.fraction == 1.0f )\n\t\t\t break; // moved the entire distance\n\n\t\tif( !SV_IsValidEdict( trace.ent ))\n\t\t\tbreak; // g-cont. this should never happens\n\n\t\tif( trace.plane.normal[2] > 0.7f )\n\t\t{\n\t\t\tblocked |= 1; // floor\n\n\t\t\tif( trace.ent->v.solid == SOLID_BSP || trace.ent->v.solid == SOLID_SLIDEBOX ||\n\t\t\t\ttrace.ent->v.movetype == MOVETYPE_PUSHSTEP || (trace.ent->v.flags & FL_CLIENT))\n\t\t\t{\n\t\t\t\tSetBits( ent->v.flags, FL_ONGROUND );\n\t\t\t\tent->v.groundentity = trace.ent;\n\t\t\t}\n\t\t}\n\n\t\tif( trace.plane.normal[2] == 0.0f )\n\t\t{\n\t\t\tblocked |= 2; // step\n\t\t\tif( steptrace ) *steptrace = trace; // save for player extrafriction\n\t\t}\n\n\t\t// run the impact function\n\t\tSV_Impact( ent, trace.ent, &trace );\n\n\t\t// break if removed by the impact function\n\t\tif( ent->free ) break;\n\n\t\ttime_left -= time_left * trace.fraction;\n\n\t\t// clipped to another plane\n\t\tif( numplanes >= MAX_CLIP_PLANES )\n\t\t{\n\t\t\t// this shouldn't really happen\n\t\t\tVectorClear( ent->v.velocity );\n\t\t\tbreak;\n\t\t}\n\n\t\tVectorCopy( trace.plane.normal, planes[numplanes] );\n\t\tnumplanes++;\n\n\t\t// modify original_velocity so it parallels all of the clip planes\n\t\tfor( i = 0; i < numplanes; i++ )\n\t\t{\n\t\t\tSV_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0f );\n\n\t\t\tfor( j = 0; j < numplanes; j++ )\n\t\t\t{\n\t\t\t\tif( j != i )\n\t\t\t\t{\n\t\t\t\t\tif( DotProduct( new_velocity, planes[j] ) < 0.0f )\n\t\t\t\t\t\tbreak; // not ok\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( j == numplanes )\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( i != numplanes )\n\t\t{\n\t\t\t// go along this plane\n\t\t\tVectorCopy( new_velocity, ent->v.velocity );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// go along the crease\n\t\t\tif( numplanes != 2 )\n\t\t\t{\n\t\t\t\tVectorClear( ent->v.velocity );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tCrossProduct( planes[0], planes[1], dir );\n\t\t\td = DotProduct( dir, ent->v.velocity );\n\t\t\tVectorScale( dir, d, ent->v.velocity );\n\t\t}\n\n\t\t// if current velocity is against the original velocity,\n\t\t// stop dead to avoid tiny occilations in sloping corners\n\t\tif( DotProduct( ent->v.velocity, primal_velocity ) <= 0.0f )\n\t\t{\n\t\t\tVectorClear( ent->v.velocity );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( allFraction == 0.0f )\n\t\tVectorClear( ent->v.velocity );\n\n\treturn blocked;\n}\n\n/*\n============\nSV_AddGravity\n\n============\n*/\nstatic void SV_AddGravity( edict_t *ent )\n{\n\tfloat\tent_gravity;\n\n\tif( ent->v.gravity )\n\t\tent_gravity = ent->v.gravity;\n\telse ent_gravity = 1.0f;\n\n\t// add gravity incorrectly\n\tent->v.velocity[2] -= ( ent_gravity * sv_gravity.value * sv.frametime );\n\tent->v.velocity[2] += ( ent->v.basevelocity[2] * sv.frametime );\n\tent->v.basevelocity[2] = 0.0f;\n\n\t// bound velocity\n\tSV_CheckVelocity( ent );\n}\n\n/*\n===============================================================================\n\nPUSHMOVE\n\n===============================================================================\n*/\n/*\n============\nSV_AllowPushRotate\n\nAllows to change entity yaw?\n============\n*/\nstatic qboolean SV_AllowPushRotate( edict_t *ent )\n{\n\tmodel_t\t*mod;\n\n\tmod = SV_ModelHandle( ent->v.modelindex );\n\n\tif( !mod || mod->type != mod_brush )\n\t\treturn true;\n\n\tif( !FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))\n\t\treturn false;\n\n\tif( FBitSet( mod->flags, MODEL_HAS_ORIGIN ))\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n============\nSV_PushEntity\n\nDoes not change the entities velocity at all\n============\n*/\nstatic trace_t SV_PushEntity( edict_t *ent, const vec3_t lpush, const vec3_t apush, int *blocked, float flDamage )\n{\n\ttrace_t\ttrace;\n\tqboolean\tmonsterBlock;\n\tqboolean\tmonsterClip;\n\tint\ttype;\n\tvec3_t\tend;\n\n\tmonsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\tVectorAdd( ent->v.origin, lpush, end );\n\n\tif( ent->v.movetype == MOVETYPE_FLYMISSILE )\n\t\ttype = MOVE_MISSILE;\n\telse if( ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT )\n\t\ttype = MOVE_NOMONSTERS; // only clip against bmodels\n\telse type = MOVE_NORMAL;\n\n\ttrace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, end, type, ent, monsterClip );\n\n\tif( trace.fraction != 0.0f )\n\t{\n\t\tVectorCopy( trace.endpos, ent->v.origin );\n\n\t\tif( sv.state == ss_active && apush[YAW] && ( ent->v.flags & FL_CLIENT ))\n\t\t{\n\t\t\tent->v.avelocity[1] += apush[1];\n\t\t\tent->v.fixangle = 2;\n\t\t}\n\n\t\t// don't rotate pushables!\n\t\tif( SV_AllowPushRotate( ent ))\n\t\t\tent->v.angles[YAW] += trace.fraction * apush[YAW];\n\t}\n\n\tSV_LinkEdict( ent, true );\n\n\tif( ent->v.movetype == MOVETYPE_WALK || ent->v.movetype == MOVETYPE_STEP || ent->v.movetype == MOVETYPE_PUSHSTEP )\n\t\tmonsterBlock = true;\n\telse monsterBlock = false;\n\n\tif( blocked )\n\t{\n\t\t// more accuracy blocking code\n\t\tif( monsterBlock )\n\t\t\t*blocked = !VectorCompareEpsilon( ent->v.origin, end, ON_EPSILON ); // can't move full distance\n\t\telse *blocked = true;\n\t}\n\n\t// so we can run impact function afterwards.\n\tif( SV_IsValidEdict( trace.ent ))\n\t\tSV_Impact( ent, trace.ent, &trace );\n\n\treturn trace;\n}\n\n/*\n============\nSV_CanPushed\n\nfilter entities for push\n============\n*/\nstatic qboolean SV_CanPushed( edict_t *ent )\n{\n\t// filter movetypes to collide with\n\tswitch( ent->v.movetype )\n\t{\n\tcase MOVETYPE_NONE:\n\tcase MOVETYPE_PUSH:\n\tcase MOVETYPE_FOLLOW:\n\tcase MOVETYPE_NOCLIP:\n\tcase MOVETYPE_COMPOUND:\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n/*\n============\nSV_CanBlock\n\nallow entity to block pusher?\n============\n*/\nstatic qboolean SV_CanBlock( edict_t *ent )\n{\n\tif( ent->v.mins[0] == ent->v.maxs[0] )\n\t\treturn false;\n\n\tif( ent->v.solid == SOLID_NOT || ent->v.solid == SOLID_TRIGGER )\n\t{\n\t\t// clear bounds for deadbody\n\t\tent->v.mins[0] = ent->v.mins[1] = 0;\n\t\tVectorCopy( ent->v.mins, ent->v.maxs );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nSV_PushMove\n\n============\n*/\nstatic edict_t *SV_PushMove( edict_t *pusher, float movetime )\n{\n\tint\t\ti, e, block;\n\tint\t\tnum_moved, oldsolid;\n\tvec3_t\t\tmins, maxs, lmove;\n\tsv_pushed_t\t*p, *pushed_p;\n\tedict_t\t\t*check;\n\n\tif( svgame.globals->changelevel || VectorIsNull( pusher->v.velocity ))\n\t{\n\t\tpusher->v.ltime += movetime;\n\t\treturn NULL;\n\t}\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tlmove[i] = pusher->v.velocity[i] * movetime;\n\t\tmins[i] = pusher->v.absmin[i] + lmove[i];\n\t\tmaxs[i] = pusher->v.absmax[i] + lmove[i];\n\t}\n\n\tpushed_p = svgame.pushed;\n\n\t// save the pusher's original position\n\tpushed_p->ent = pusher;\n\tVectorCopy( pusher->v.origin, pushed_p->origin );\n\tVectorCopy( pusher->v.angles, pushed_p->angles );\n\tpushed_p++;\n\n\t// move the pusher to it's final position\n\tSV_LinearMove( pusher, movetime, 0.0f );\n\tSV_LinkEdict( pusher, false );\n\tpusher->v.ltime += movetime;\n\toldsolid = pusher->v.solid;\n\n\t// non-solid pushers can't push anything\n\tif( pusher->v.solid == SOLID_NOT )\n\t\treturn NULL;\n\n\t// see if any solid entities are inside the final position\n\tnum_moved = 0;\n\n\tfor( e = 1; e < svgame.numEntities; e++ )\n\t{\n\t\tcheck = EDICT_NUM( e );\n\t\tif( !SV_IsValidEdict( check )) continue;\n\n\t\t// filter movetypes to collide with\n\t\tif( !SV_CanPushed( check ))\n\t\t\tcontinue;\n\n\t\tpusher->v.solid = SOLID_NOT;\n\t\tblock = SV_TestEntityPosition( check, pusher );\n\t\tpusher->v.solid = oldsolid;\n\t\tif( block ) continue;\n\n\t\t// if the entity is standing on the pusher, it will definately be moved\n\t\tif( !( FBitSet( check->v.flags, FL_ONGROUND ) && check->v.groundentity == pusher ))\n\t\t{\n\t\t\tif( check->v.absmin[0] >= maxs[0]\n\t\t\t || check->v.absmin[1] >= maxs[1]\n\t\t\t || check->v.absmin[2] >= maxs[2]\n\t\t\t || check->v.absmax[0] <= mins[0]\n\t\t\t || check->v.absmax[1] <= mins[1]\n\t\t\t || check->v.absmax[2] <= mins[2] )\n\t\t\t\tcontinue;\n\n\t\t\t// see if the ent's bbox is inside the pusher's final position\n\t\t\tif( !SV_TestEntityPosition( check, NULL ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// remove the onground flag for non-players\n\t\tif( check->v.movetype != MOVETYPE_WALK )\n\t\t\tcheck->v.flags &= ~FL_ONGROUND;\n\n\t\t// save original position of contacted entity\n\t\tpushed_p->ent = check;\n\t\tVectorCopy( check->v.origin, pushed_p->origin );\n\t\tVectorCopy( check->v.angles, pushed_p->angles );\n\t\tpushed_p++;\n\n\t\t// try moving the contacted entity\n\t\tpusher->v.solid = SOLID_NOT;\n\t\tSV_PushEntity( check, lmove, vec3_origin, &block, pusher->v.dmg );\n\t\tpusher->v.solid = oldsolid;\n\n\t\t// if it is still inside the pusher, block\n\t\tif( SV_TestEntityPosition( check, NULL ) && block )\n\t\t{\n\t\t\tif( !SV_CanBlock( check ))\n\t\t\t\tcontinue;\n\n\t\t\tpusher->v.ltime -= movetime;\n\n\t\t\t// move back any entities we already moved\n\t\t\t// go backwards, so if the same entity was pushed\n\t\t\t// twice, it goes back to the original position\n\t\t\tfor( p = pushed_p - 1; p >= svgame.pushed; p-- )\n\t\t\t{\n\t\t\t\tVectorCopy( p->origin, p->ent->v.origin );\n\t\t\t\tVectorCopy( p->angles, p->ent->v.angles );\n\t\t\t\tSV_LinkEdict( p->ent, (p->ent == check) ? true : false );\n\t\t\t}\n\t\t\treturn check;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/*\n============\nSV_PushRotate\n\n============\n*/\nstatic edict_t *SV_PushRotate( edict_t *pusher, float movetime )\n{\n\tint\t\ti, e, block, oldsolid;\n\tmatrix4x4\t\tstart_l, end_l;\n\tvec3_t\t\tlmove, amove;\n\tsv_pushed_t\t*p, *pushed_p;\n\tvec3_t\t\torg, org2, temp;\n\tedict_t\t\t*check;\n\n\tif( svgame.globals->changelevel || VectorIsNull( pusher->v.avelocity ))\n\t{\n\t\tpusher->v.ltime += movetime;\n\t\treturn NULL;\n\t}\n\n\tfor( i = 0; i < 3; i++ )\n\t\tamove[i] = pusher->v.avelocity[i] * movetime;\n\n\t// create pusher initial position\n\tMatrix4x4_CreateFromEntity( start_l, pusher->v.angles, pusher->v.origin, 1.0f );\n\n\tpushed_p = svgame.pushed;\n\n\t// save the pusher's original position\n\tpushed_p->ent = pusher;\n\tVectorCopy( pusher->v.origin, pushed_p->origin );\n\tVectorCopy( pusher->v.angles, pushed_p->angles );\n\tpushed_p++;\n\n\t// move the pusher to it's final position\n\tSV_AngularMove( pusher, movetime, pusher->v.friction );\n\tSV_LinkEdict( pusher, false );\n\tpusher->v.ltime += movetime;\n\toldsolid = pusher->v.solid;\n\n\t// non-solid pushers can't push anything\n\tif( pusher->v.solid == SOLID_NOT )\n\t\treturn NULL;\n\n\t// create pusher final position\n\tMatrix4x4_CreateFromEntity( end_l, pusher->v.angles, pusher->v.origin, 1.0f );\n\n\t// see if any solid entities are inside the final position\n\tfor( e = 1; e < svgame.numEntities; e++ )\n\t{\n\t\tcheck = EDICT_NUM( e );\n\t\tif( !SV_IsValidEdict( check ))\n\t\t\tcontinue;\n\n\t\t// filter movetypes to collide with\n\t\tif( !SV_CanPushed( check ))\n\t\t\tcontinue;\n\n\t\tpusher->v.solid = SOLID_NOT;\n\t\tblock = SV_TestEntityPosition( check, pusher );\n\t\tpusher->v.solid = oldsolid;\n\t\tif( block ) continue;\n\n\t\t// if the entity is standing on the pusher, it will definately be moved\n\t\tif( !(( check->v.flags & FL_ONGROUND ) && check->v.groundentity == pusher ))\n\t\t{\n\t\t\tif( check->v.absmin[0] >= pusher->v.absmax[0]\n\t\t\t|| check->v.absmin[1] >= pusher->v.absmax[1]\n\t\t\t|| check->v.absmin[2] >= pusher->v.absmax[2]\n\t\t\t|| check->v.absmax[0] <= pusher->v.absmin[0]\n\t\t\t|| check->v.absmax[1] <= pusher->v.absmin[1]\n\t\t\t|| check->v.absmax[2] <= pusher->v.absmin[2] )\n\t\t\t\tcontinue;\n\n\t\t\t// see if the ent's bbox is inside the pusher's final position\n\t\t\tif( !SV_TestEntityPosition( check, NULL ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\t// save original position of contacted entity\n\t\tpushed_p->ent = check;\n\t\tVectorCopy( check->v.origin, pushed_p->origin );\n\t\tVectorCopy( check->v.angles, pushed_p->angles );\n\t\tpushed_p->fixangle = check->v.fixangle;\n\t\tpushed_p++;\n\n\t\t// calculate destination position\n\t\tif( check->v.movetype == MOVETYPE_PUSHSTEP || check->v.movetype == MOVETYPE_STEP )\n\t\t\tVectorAverage( check->v.absmin, check->v.absmax, org );\n\t\telse VectorCopy( check->v.origin, org );\n\n\t\tMatrix4x4_VectorITransform( start_l, org, temp );\n\t\tMatrix4x4_VectorTransform( end_l, temp, org2 );\n\t\tVectorSubtract( org2, org, lmove );\n\n\t\t// i can't clear FL_ONGROUND in all cases because many bad things may be happen\n\t\tif( check->v.movetype != MOVETYPE_WALK )\n\t\t{\n\t\t\tif( lmove[2] != 0.0f ) check->v.flags &= ~FL_ONGROUND;\n\t\t\tif( lmove[2] < 0.0f && !pusher->v.dmg )\n\t\t\t\tlmove[2] = 0.0f; // let's the free falling\n\t\t}\n\n\t\t// try moving the contacted entity\n\t\tpusher->v.solid = SOLID_NOT;\n\t\tSV_PushEntity( check, lmove, amove, &block, pusher->v.dmg );\n\t\tpusher->v.solid = oldsolid;\n\n\t\t// pushed entity blocked by wall\n\t\tif( block && check->v.movetype != MOVETYPE_WALK )\n\t\t\tcheck->v.flags &= ~FL_ONGROUND;\n\n\t\t// if it is still inside the pusher, block\n\t\tif( SV_TestEntityPosition( check, NULL ) && block )\n\t\t{\n\t\t\tif( !SV_CanBlock( check ))\n\t\t\t\tcontinue;\n\n\t\t\tpusher->v.ltime -= movetime;\n\n\t\t\t// move back any entities we already moved\n\t\t\t// go backwards, so if the same entity was pushed\n\t\t\t// twice, it goes back to the original position\n\t\t\tfor( p = pushed_p - 1; p >= svgame.pushed; p-- )\n\t\t\t{\n\t\t\t\tVectorCopy( p->origin, p->ent->v.origin );\n\t\t\t\tVectorCopy( p->angles, p->ent->v.angles );\n\t\t\t\tSV_LinkEdict( p->ent, (p->ent == check) ? true : false );\n\t\t\t\tp->ent->v.fixangle = p->fixangle;\n\t\t\t}\n\t\t\treturn check;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/*\n================\nSV_Physics_Pusher\n\n================\n*/\nstatic void SV_Physics_Pusher( edict_t *ent )\n{\n\tfloat\toldtime, oldtime2;\n\tfloat\tthinktime, movetime;\n\tedict_t\t*pBlocker;\n\tint\ti;\n\n\tpBlocker = NULL;\n\toldtime = ent->v.ltime;\n\tthinktime = ent->v.nextthink;\n\n\tif( thinktime < oldtime + sv.frametime )\n\t{\n\t\tmovetime = thinktime - oldtime;\n\t\tif( movetime < 0.0f ) movetime = 0.0f;\n\t}\n\telse movetime = sv.frametime;\n\n\tif( movetime )\n\t{\n\t\tif( !VectorIsNull( ent->v.avelocity ))\n\t\t{\n\t\t\tif( !VectorIsNull( ent->v.velocity ))\n\t\t\t{\n\t\t\t\tpBlocker = SV_PushRotate( ent, movetime );\n\n\t\t\t\tif( !pBlocker )\n\t\t\t\t{\n\t\t\t\t\toldtime2 = ent->v.ltime;\n\n\t\t\t\t\t// reset the local time to what it was before we rotated\n\t\t\t\t\tent->v.ltime = oldtime;\n\t\t\t\t\tpBlocker = SV_PushMove( ent, movetime );\n\t\t\t\t\tif( ent->v.ltime < oldtime2 )\n\t\t\t\t\t\tent->v.ltime = oldtime2;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpBlocker = SV_PushRotate( ent, movetime );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpBlocker = SV_PushMove( ent, movetime );\n\t\t}\n\t}\n\n\t// if the pusher has a \"blocked\" function, call it\n\t// otherwise, just stay in place until the obstacle is gone\n\tif( pBlocker ) svgame.dllFuncs.pfnBlocked( ent, pBlocker );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( ent->v.angles[i] < -3600.0f || ent->v.angles[i] > 3600.0f )\n\t\t\tent->v.angles[i] = fmod( ent->v.angles[i], 3600.0f );\n\t}\n\n\tif( thinktime > oldtime && (( ent->v.flags & FL_ALWAYSTHINK ) || thinktime <= ent->v.ltime ))\n\t{\n\t\tent->v.nextthink = 0.0f;\n\t\tsvgame.globals->time = sv.time;\n\t\tsvgame.dllFuncs.pfnThink( ent );\n\t}\n}\n\n//============================================================================\n/*\n=============\nSV_Physics_Follow\n\njust copy angles and origin of parent\n=============\n*/\nstatic void SV_Physics_Follow( edict_t *ent )\n{\n\tedict_t\t*parent;\n\n\t// regular thinking\n\tif( !SV_RunThink( ent )) return;\n\n\tparent = ent->v.aiment;\n\n\tif( !SV_IsValidEdict( parent ))\n\t{\n\t\tent->v.movetype = MOVETYPE_NONE;\n\t\treturn;\n\t}\n\n\tVectorAdd( parent->v.origin, ent->v.v_angle, ent->v.origin );\n\tVectorCopy( parent->v.angles, ent->v.angles );\n\n\tSV_LinkEdict( ent, true );\n}\n\n/*\n=============\nSV_Physics_Compound\n\na glue two entities together\n=============\n*/\nstatic void SV_Physics_Compound( edict_t *ent )\n{\n\tedict_t\t*parent;\n\n\t// regular thinking\n\tif( !SV_RunThink( ent )) return;\n\n\tparent = ent->v.aiment;\n\n\tif( !SV_IsValidEdict( parent ))\n\t{\n\t\tent->v.movetype = MOVETYPE_NONE;\n\t\treturn;\n\t}\n\n\tif( ent->v.solid != SOLID_TRIGGER )\n\t\tent->v.solid = SOLID_NOT;\n\n\tswitch( parent->v.movetype )\n\t{\n\tcase MOVETYPE_PUSH:\n\tcase MOVETYPE_PUSHSTEP:\n\t\tbreak;\n\tdefault: return;\n\t}\n\n\t// not initialized ?\n\tif( ent->v.ltime == 0.0f )\n\t{\n\t\tVectorCopy( parent->v.origin, ent->v.oldorigin );\n\t\tVectorCopy( parent->v.angles, ent->v.avelocity );\n\t\tent->v.ltime = sv.frametime;\n\t\treturn;\n\t}\n\n\tif( !VectorCompare( parent->v.origin, ent->v.oldorigin ) || !VectorCompare( parent->v.angles, ent->v.avelocity ))\n\t{\n\t\tmatrix4x4\tstart_l, end_l, temp_l, child;\n\n\t\t// create parent old position\n\t\tMatrix4x4_CreateFromEntity( temp_l, ent->v.avelocity, ent->v.oldorigin, 1.0f );\n\t\tMatrix4x4_Invert_Simple( start_l, temp_l );\n\n\t\t// create parent actual position\n\t\tMatrix4x4_CreateFromEntity( end_l, parent->v.angles, parent->v.origin, 1.0f );\n\n\t\t// stupid quake bug!!!\n\t\tif( !( host.features & ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\t\tent->v.angles[PITCH] = -ent->v.angles[PITCH];\n\n\t\t// create child actual position\n\t\tMatrix4x4_CreateFromEntity( child, ent->v.angles, ent->v.origin, 1.0f );\n\n\t\t// transform child from start to end\n\t\tMatrix4x4_ConcatTransforms( temp_l, start_l, child );\n\t\tMatrix4x4_ConcatTransforms( child, end_l, temp_l );\n\n\t\t// create child final position\n\t\tMatrix4x4_ConvertToEntity( child, ent->v.angles, ent->v.origin );\n\n\t\t// stupid quake bug!!!\n\t\tif( !( host.features & ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\t\tent->v.angles[PITCH] = -ent->v.angles[PITCH];\n\t}\n\n\t// notsolid ents never touch triggers\n\tSV_LinkEdict( ent, (ent->v.solid == SOLID_NOT) ? false : true );\n\n\t// shuffle states\n\tVectorCopy( parent->v.origin, ent->v.oldorigin );\n\tVectorCopy( parent->v.angles, ent->v.avelocity );\n}\n\n/*\n=============\nSV_PhysicsNoclip\n\nA moving object that doesn't obey physics\n=============\n*/\nstatic void SV_Physics_Noclip( edict_t *ent )\n{\n\t// regular thinking\n\tif( !SV_RunThink( ent )) return;\n\n\tSV_CheckWater( ent );\n\n\tVectorMA( ent->v.origin, sv.frametime, ent->v.velocity,  ent->v.origin );\n\tVectorMA( ent->v.angles, sv.frametime, ent->v.avelocity, ent->v.angles );\n\n\t// noclip ents never touch triggers\n\tSV_LinkEdict( ent, false );\n}\n\n/*\n==============================================================================\n\nTOSS / BOUNCE\n\n==============================================================================\n*/\n/*\n=============\nSV_CheckWaterTransition\n\n=============\n*/\nstatic void SV_CheckWaterTransition( edict_t *ent )\n{\n\tvec3_t\tpoint;\n\tint\tcont;\n\n\tpoint[0] = (ent->v.absmax[0] + ent->v.absmin[0]) * 0.5f;\n\tpoint[1] = (ent->v.absmax[1] + ent->v.absmin[1]) * 0.5f;\n\tpoint[2] = (ent->v.absmin[2] + 1.0f);\n\n\tsvs.groupmask = ent->v.groupinfo;\n\tcont = SV_PointContents( point );\n\n\tif( !ent->v.watertype )\n\t{\n\t\t// just spawned here\n\t\tent->v.watertype = cont;\n\t\tent->v.waterlevel = 1;\n\t\treturn;\n\t}\n\n\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t{\n\t\tif( ent->v.watertype == CONTENTS_EMPTY )\n\t\t{\n\t\t\t// just crossed into water\n\t\t\tconst char *snd = SoundList_GetRandom( PlayerWaterEnter );\n\t\t\tif( snd )\n\t\t\t\tSV_StartSound( ent, CHAN_AUTO, snd, 1.0f, ATTN_NORM, 0, 100 );\n\t\t\tent->v.velocity[2] *= 0.5f;\n\t\t}\n\n\t\tent->v.watertype = cont;\n\t\tent->v.waterlevel = 1;\n\n\t\tif( ent->v.absmin[2] != ent->v.absmax[2] )\n\t\t{\n\t\t\tpoint[2] = (ent->v.absmin[2] + ent->v.absmax[2]) * 0.5f;\n\t\t\tsvs.groupmask = ent->v.groupinfo;\n\t\t\tcont = SV_PointContents( point );\n\n\t\t\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t\t\t{\n\t\t\t\tent->v.waterlevel = 2;\n\t\t\t\tVectorAdd( point, ent->v.view_ofs, point );\n\t\t\t\tsvs.groupmask = ent->v.groupinfo;\n\t\t\t\tcont = SV_PointContents( point );\n\t\t\t\tif( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )\n\t\t\t\t\tent->v.waterlevel = 3;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// point entity\n\t\t\tent->v.waterlevel = 3;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( ent->v.watertype != CONTENTS_EMPTY )\n\t\t{\n\t\t\t// just crossed into water\n\t\t\tconst char *snd = SoundList_GetRandom( PlayerWaterExit );\n\t\t\tif( snd )\n\t\t\t\tSV_StartSound( ent, CHAN_AUTO, snd, 1.0f, ATTN_NORM, 0, 100 );\n\t\t}\n\t\tent->v.watertype = CONTENTS_EMPTY;\n\t\tent->v.waterlevel = 0;\n\t}\n}\n\n/*\n=============\nSV_Physics_Toss\n\nToss, bounce, and fly movement.  When onground, do nothing.\n=============\n*/\nstatic void SV_Physics_Toss( edict_t *ent )\n{\n\ttrace_t\ttrace;\n\tvec3_t\tmove;\n\tfloat\tbackoff;\n\tedict_t\t*ground;\n\n\tSV_CheckWater( ent );\n\n\t// regular thinking\n\tif( !SV_RunThink( ent )) return;\n\n\tground = ent->v.groundentity;\n\n\tif( ent->v.velocity[2] > 0 )\n\t\tClearBits( ent->v.flags, FL_ONGROUND );\n\n\tif( !SV_IsValidEdict( ground ) || FBitSet( ground->v.flags, FL_MONSTER|FL_CLIENT ))\n\t\tClearBits( ent->v.flags, FL_ONGROUND );\n\n\t// if on ground and not moving, return.\n\tif( FBitSet( ent->v.flags, FL_ONGROUND ) && VectorIsNull( ent->v.velocity ))\n\t{\n\t\tVectorClear( ent->v.avelocity );\n\n\t\tif( VectorIsNull( ent->v.basevelocity ))\n\t\t\treturn;\t// at rest\n\t}\n\n\tSV_CheckVelocity( ent );\n\n\t// add gravity\n\tswitch( ent->v.movetype )\n\t{\n\tcase MOVETYPE_FLY:\n\tcase MOVETYPE_FLYMISSILE:\n\tcase MOVETYPE_BOUNCEMISSILE:\n\t\tbreak;\n\tdefault:\n\t\tSV_AddGravity( ent );\n\t\tbreak;\n\t}\n\n\t// move angles (with friction)\n\tswitch( ent->v.movetype )\n\t{\n\tcase MOVETYPE_TOSS:\n\tcase MOVETYPE_BOUNCE:\n\t\tSV_AngularMove( ent, sv.frametime, ent->v.friction );\n\t\tbreak;\n\tdefault:\n\t\tSV_AngularMove( ent, sv.frametime, 0.0f );\n\t\tbreak;\n\t}\n\n\t// move origin\n\t// Base velocity is not properly accounted for since this entity will move again\n\t// after the bounce without taking it into account\n\tVectorAdd( ent->v.velocity, ent->v.basevelocity, ent->v.velocity );\n\n\tSV_CheckVelocity( ent );\n\tVectorScale( ent->v.velocity, sv.frametime, move );\n\n\tVectorSubtract( ent->v.velocity, ent->v.basevelocity, ent->v.velocity );\n\n\ttrace = SV_PushEntity( ent, move, vec3_origin, NULL, 0.0f );\n\tif( ent->free ) return;\n\n\tSV_CheckVelocity( ent );\n\n\tif( trace.allsolid )\n\t{\n\t\t// entity is trapped in another solid\n\t\tVectorClear( ent->v.avelocity );\n\t\tVectorClear( ent->v.velocity );\n\t\treturn;\n\t}\n\n\tif( trace.fraction == 1.0f )\n\t{\n\t\tSV_CheckWaterTransition( ent );\n\t\treturn;\n\t}\n\n\tif( ent->v.movetype == MOVETYPE_BOUNCE )\n\t\tbackoff = 2.0f - ent->v.friction;\n\telse if( ent->v.movetype == MOVETYPE_BOUNCEMISSILE )\n\t\tbackoff = 2.0f;\n\telse backoff = 1.0f;\n\n\tSV_ClipVelocity( ent->v.velocity, trace.plane.normal, ent->v.velocity, backoff );\n\n\t// stop if on ground\n\tif( trace.plane.normal[2] > 0.7f )\n\t{\n\t\tfloat\tvel;\n\n\t\tVectorAdd( ent->v.velocity, ent->v.basevelocity, move );\n\t\tvel = DotProduct( move, move );\n\n\t\tif( ent->v.velocity[2] < sv_gravity.value * sv.frametime )\n\t\t{\n\t\t\t// we're rolling on the ground, add static friction.\n\t\t\tent->v.groundentity = trace.ent;\n\t\t\tent->v.flags |= FL_ONGROUND;\n\t\t\tent->v.velocity[2] = 0.0f;\n\t\t}\n\n\t\tif( vel < 900.0f || ( ent->v.movetype != MOVETYPE_BOUNCE && ent->v.movetype != MOVETYPE_BOUNCEMISSILE ))\n\t\t{\n\t\t\tent->v.flags |= FL_ONGROUND;\n\t\t\tent->v.groundentity = trace.ent;\n\t\t\tVectorClear( ent->v.avelocity );\n\t\t\tVectorClear( ent->v.velocity );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorScale( ent->v.velocity, (1.0f - trace.fraction) * sv.frametime * 0.9f, move );\n\t\t\tVectorMA( move, (1.0f - trace.fraction) * sv.frametime * 0.9f, ent->v.basevelocity, move );\n\t\t\ttrace = SV_PushEntity( ent, move, vec3_origin, NULL, 0.0f );\n\t\t\tif( ent->free ) return;\n\t\t}\n\t}\n\n\t// check for in water\n\tSV_CheckWaterTransition( ent );\n}\n\n/*\n===============================================================================\n\nSTEPPING MOVEMENT\n\n===============================================================================\n*/\n/*\n=============\nSV_Physics_Step\n\nMonsters freefall when they don't have a ground entity, otherwise\nall movement is done with discrete steps.\n\nThis is also used for objects that have become still on the ground, but\nwill fall if the floor is pulled out from under them.\n=============\n*/\nstatic void SV_Physics_Step( edict_t *ent )\n{\n\tqboolean\tinwater;\n\tqboolean\twasonground;\n\tqboolean\twasonmover;\n\tvec3_t\tmins, maxs;\n\tvec3_t\tpoint;\n\ttrace_t\ttrace;\n\tint\tx, y;\n\n\tSV_WaterMove( ent );\n\tSV_CheckVelocity( ent );\n\n\twasonground = (ent->v.flags & FL_ONGROUND);\n\twasonmover = SV_CheckMover( ent );\n\tinwater = SV_CheckWater( ent );\n\n\tif( FBitSet( ent->v.flags, FL_FLOAT ) && ent->v.waterlevel > 0 )\n\t{\n\t\tfloat buoyancy = SV_Submerged( ent ) * ent->v.skin * sv.frametime;\n\n\t\tSV_AddGravity( ent );\n\t\tent->v.velocity[2] += buoyancy;\n\t}\n\n\tif( !wasonground )\n\t{\n\t\tif( !FBitSet( ent->v.flags, FL_FLY ))\n\t\t{\n\t\t\tif( !FBitSet( ent->v.flags, FL_SWIM ) || ( ent->v.waterlevel <= 0 ))\n\t\t\t{\n\t\t\t\tif( !inwater )\n\t\t\t\t\tSV_AddGravity( ent );\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !VectorIsNull( ent->v.velocity ) || !VectorIsNull( ent->v.basevelocity ))\n\t{\n\t\tent->v.flags &= ~FL_ONGROUND;\n\n\t\tif(( wasonground || wasonmover ) && ( ent->v.health > 0 || SV_CheckBottom( ent, MOVE_NORMAL )))\n\t\t{\n\t\t\tfloat\t*vel = ent->v.velocity;\n\t\t\tfloat\tcontrol, speed, newspeed;\n\t\t\tfloat\tfriction;\n\n\t\t\tspeed = sqrt(( vel[0] * vel[0] ) + ( vel[1] * vel[1] ));\t// DotProduct2D\n\n\t\t\tif( speed )\n\t\t\t{\n\t\t\t\tfriction = sv_friction.value * ent->v.friction;\t// factor\n\t\t\t\tent->v.friction = 1.0f; // g-cont. ???\n\t\t\t\tif( wasonmover ) friction *= 0.5f; // add a little friction\n\n\t\t\t\tcontrol = (speed < sv_stopspeed.value) ? sv_stopspeed.value : speed;\n\t\t\t\tnewspeed = speed - (sv.frametime * control * friction);\n\t\t\t\tif( newspeed < 0 ) newspeed = 0;\n\t\t\t\tnewspeed /= speed;\n\n\t\t\t\tvel[0] = vel[0] * newspeed;\n\t\t\t\tvel[1] = vel[1] * newspeed;\n\t\t\t}\n\t\t}\n\n\t\tVectorAdd( ent->v.velocity, ent->v.basevelocity, ent->v.velocity );\n\t\tSV_CheckVelocity( ent );\n\n\t\tSV_FlyMove( ent, sv.frametime, NULL );\n\t\tif( ent->free ) return;\n\n\t\tSV_CheckVelocity( ent );\n\t\tVectorSubtract( ent->v.velocity, ent->v.basevelocity, ent->v.velocity );\n\t\tSV_CheckVelocity( ent );\n\n\t\tVectorAdd( ent->v.origin, ent->v.mins, mins );\n\t\tVectorAdd( ent->v.origin, ent->v.maxs, maxs );\n\n\t\tpoint[2] = mins[2] - 1.0f;\n\n\t\tfor( x = 0; x <= 1; x++ )\n\t\t{\n\t\t\tif( FBitSet( ent->v.flags, FL_ONGROUND ))\n\t\t\t\tbreak;\n\n\t\t\tfor( y = 0; y <= 1; y++ )\n\t\t\t{\n\t\t\t\tpoint[0] = x ? maxs[0] : mins[0];\n\t\t\t\tpoint[1] = y ? maxs[1] : mins[1];\n\n\t\t\t\ttrace = SV_Move( point, vec3_origin, vec3_origin, point, MOVE_NORMAL, ent, false );\n\n\t\t\t\tif( trace.startsolid )\n\t\t\t\t{\n\t\t\t\t\tSetBits( ent->v.flags, FL_ONGROUND );\n\t\t\t\t\tent->v.groundentity = trace.ent;\n\t\t\t\t\tent->v.friction = 1.0f;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tSV_LinkEdict( ent, true );\n\t}\n\telse\n\t{\n\t\tif( svgame.globals->force_retouch != 0 )\n\t\t{\n\t\t\tqboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;\n\t\t\ttrace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip );\n\n\t\t\t// hentacle impact code\n\t\t\tif(( trace.fraction < 1.0f || trace.startsolid ) && SV_IsValidEdict( trace.ent ))\n\t\t\t{\n\t\t\t\tSV_Impact( ent, trace.ent, &trace );\n\t\t\t\tif( ent->free ) return;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !SV_RunThink( ent )) return;\n\tSV_CheckWaterTransition( ent );\n\n}\n\n/*\n=============\nSV_PhysicsNone\n\nNon moving objects can only think\n=============\n*/\nstatic void SV_Physics_None( edict_t *ent )\n{\n\tSV_RunThink( ent );\n}\n\n//============================================================================\nstatic void SV_Physics_Entity( edict_t *ent )\n{\n\t// user dll can override movement type (Xash3D extension)\n\tif( svgame.physFuncs.SV_PhysicsEntity && svgame.physFuncs.SV_PhysicsEntity( ent ))\n\t\treturn; // overrided\n\n\tSV_UpdateBaseVelocity( ent );\n\n\tif( !FBitSet( ent->v.flags, FL_BASEVELOCITY ) && !VectorIsNull( ent->v.basevelocity ))\n\t{\n\t\t// Apply momentum (add in half of the previous frame of velocity first)\n\t\tVectorMA( ent->v.velocity, 1.0f + (sv.frametime * 0.5f), ent->v.basevelocity, ent->v.velocity );\n\t\tVectorClear( ent->v.basevelocity );\n\t}\n\n\tent->v.flags &= ~FL_BASEVELOCITY;\n\n\tif( svgame.globals->force_retouch != 0.0f )\n\t{\n\t\t// force retouch even for stationary\n\t\tSV_LinkEdict( ent, true );\n\t}\n\n\tswitch( ent->v.movetype )\n\t{\n\tcase MOVETYPE_NONE:\n\t\tSV_Physics_None( ent );\n\t\tbreak;\n\tcase MOVETYPE_NOCLIP:\n\t\tSV_Physics_Noclip( ent );\n\t\tbreak;\n\tcase MOVETYPE_FOLLOW:\n\t\tSV_Physics_Follow( ent );\n\t\tbreak;\n\tcase MOVETYPE_COMPOUND:\n\t\tSV_Physics_Compound( ent );\n\t\tbreak;\n\tcase MOVETYPE_STEP:\n\tcase MOVETYPE_PUSHSTEP:\n\t\tSV_Physics_Step( ent );\n\t\tbreak;\n\tcase MOVETYPE_FLY:\n\tcase MOVETYPE_TOSS:\n\tcase MOVETYPE_BOUNCE:\n\tcase MOVETYPE_FLYMISSILE:\n\tcase MOVETYPE_BOUNCEMISSILE:\n\t\tSV_Physics_Toss( ent );\n\t\tbreak;\n\tcase MOVETYPE_PUSH:\n\t\tSV_Physics_Pusher( ent );\n\t\tbreak;\n\tcase MOVETYPE_WALK:\n\t\tHost_Error( \"%s: bad movetype %i\\n\", __func__, ent->v.movetype );\n\t\tbreak;\n\t}\n\n\t// g-cont. don't alow free entities during loading because\n\t// this produce a corrupted baselines\n\tif( sv.state == ss_active && FBitSet( ent->v.flags, FL_KILLME ))\n\t\tSV_FreeEdict( ent );\n}\n\nstatic void SV_RunLightStyles( void )\n{\n\tint\ti;\n\n\t// run lightstyles animation\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tlightstyle_t *ls = &sv.lightstyles[i];\n\t\tint ofs;\n\n\t\tls->time += sv.frametime;\n\t\tofs = (ls->time * 10);\n\n\t\tif( ls->length == 0 )\n\t\t\tls->value = 1.0f; // disable this light\n\t\telse if( ls->length == 1 )\n\t\t\tls->value = ls->map[0] / 12.0f;\n\t\telse\n\t\t\tls->value = ls->map[ofs % ls->length] / 12.0f;\n\t}\n}\n\n/*\n================\nSV_Physics\n\n================\n*/\nvoid SV_Physics( void )\n{\n\tedict_t\t*ent;\n\tint    \ti;\n\n\tSV_CheckAllEnts ();\n\n\tsvgame.globals->time = sv.time;\n\n\t// let the progs know that a new frame has started\n\tsvgame.dllFuncs.pfnStartFrame();\n\n\t// treat each object in turn\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tent = EDICT_NUM( i );\n\n\t\tif( !SV_IsValidEdict( ent ))\n\t\t\tcontinue;\n\n\t\tif( i > 0 && i <= svs.maxclients )\n\t\t\tcontinue;\n\n\t\tSV_Physics_Entity( ent );\n\t}\n\n\tif( svgame.globals->force_retouch != 0.0f )\n\t\tsvgame.globals->force_retouch--;\n\n\tif( svgame.physFuncs.SV_EndFrame != NULL )\n\t\tsvgame.physFuncs.SV_EndFrame();\n\n\t// animate lightstyles (used for GetEntityIllum)\n\tSV_RunLightStyles ();\n\n\t// increase framecount\n\tsv.framecount++;\n\n#if 0 // figure out why this causes memory corruption\n\t// decrement svgame.numEntities if the highest number entities died\n\tfor( ; ( ent = EDICT_NUM( svgame.numEntities - 1 )) && ent->free; svgame.numEntities-- );\n#endif\n}\n\n/*\n================\nSV_GetServerTime\n\nInplementation for new physics interface\n================\n*/\nstatic double GAME_EXPORT SV_GetServerTime( void )\n{\n\treturn sv.time;\n}\n\n/*\n================\nSV_GetFrameTime\n\nInplementation for new physics interface\n================\n*/\nstatic double GAME_EXPORT SV_GetFrameTime( void )\n{\n\treturn sv.frametime;\n}\n\n/*\n================\nSV_GetHeadNode\n\nInplementation for new physics interface\n================\n*/\nstatic areanode_t *GAME_EXPORT SV_GetHeadNode( void )\n{\n\treturn sv_areanodes;\n}\n\n/*\n================\nSV_ServerState\n\nInplementation for new physics interface\n================\n*/\nstatic int GAME_EXPORT SV_ServerState( void )\n{\n\treturn sv.state;\n}\n\n/*\n================\nSV_DrawDebugTriangles\n\nCalled from renderer for debug purposes\n================\n*/\nvoid SV_DrawDebugTriangles( void )\n{\n\tif( host.type != HOST_NORMAL )\n\t\treturn;\n\n\tif( svgame.physFuncs.DrawNormalTriangles != NULL )\n\t{\n\t\t// draw solid overlay\n\t\tsvgame.physFuncs.DrawNormalTriangles ();\n\t}\n\n\tif( svgame.physFuncs.DrawDebugTriangles != NULL )\n\t{\n#if 0\n\t\t// debug draws only\n\t\tpglDisable( GL_BLEND );\n\t\tpglDepthMask( GL_FALSE );\n\t\tpglDisable( GL_TEXTURE_2D );\n#endif\n\t\t// draw wireframe overlay\n\t\tsvgame.physFuncs.DrawDebugTriangles ();\n#if 0\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglDepthMask( GL_TRUE );\n\t\tpglEnable( GL_BLEND );\n#endif\n\t}\n}\n\n/*\n================\nSV_DrawOrthoTriangles\n\nCalled from renderer for debug purposes\n================\n*/\nvoid SV_DrawOrthoTriangles( void )\n{\n\tif( host.type != HOST_NORMAL )\n\t\treturn;\n\n\tif( svgame.physFuncs.DrawOrthoTriangles != NULL )\n\t{\n\t\t// draw solid overlay\n\t\tsvgame.physFuncs.DrawOrthoTriangles ();\n\t}\n}\n\n/*\n==================\nSV_GetLightStyle\n\nneeds to get correct working SV_LightPoint\n==================\n*/\nstatic const char *GAME_EXPORT SV_GetLightStyle( int style )\n{\n\tif( style < 0 ) style = 0;\n\tif( style >= MAX_LIGHTSTYLES )\n\t\tHost_Error( \"%s: style: %i >= %d\", __func__, style, MAX_LIGHTSTYLES );\n\n\treturn sv.lightstyles[style].pattern;\n}\n\nstatic void GAME_EXPORT SV_UpdateFogSettings( unsigned int packed_fog )\n{\n\tsvgame.movevars.fog_settings = packed_fog;\n\thost.movevars_changed = true; // force to transmit\n}\n\n/*\n=========\npfnGetFilesList\n\n=========\n*/\nstatic char **GAME_EXPORT pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly )\n{\n\tstatic search_t\t*t = NULL;\n\n\tif( t ) Mem_Free( t ); // release prev search\n\n\tt = FS_Search( pattern, true, gamedironly );\n\n\tif( !t )\n\t{\n\t\tif( numFiles ) *numFiles = 0;\n\t\treturn NULL;\n\t}\n\n\tif( numFiles ) *numFiles = t->numfilenames;\n\treturn t->filenames;\n}\n\nstatic void *GAME_EXPORT pfnMem_Alloc( size_t cb, const char *filename, const int fileline )\n{\n\treturn _Mem_Alloc( svgame.mempool, cb, true, filename, fileline );\n}\n\nstatic void GAME_EXPORT pfnMem_Free( void *mem, const char *filename, const int fileline )\n{\n\tif( !mem ) return;\n\t_Mem_Free( mem, filename, fileline );\n}\n\n/*\n=============\npfnPointContents\n\n=============\n*/\nstatic int GAME_EXPORT pfnPointContents( const float *pos, int groupmask )\n{\n\tint\toldmask, cont;\n\n\tif( !pos ) return CONTENTS_NONE;\n\toldmask = svs.groupmask;\n\n\tsvs.groupmask = groupmask;\n\tcont = SV_PointContents( pos );\n\tsvs.groupmask = oldmask; // restore old mask\n\n\treturn cont;\n}\n\nstatic trace_t GAME_EXPORT SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e )\n{\n\treturn SV_Move( start, mins, maxs, end, type, e, false );\n}\n\n/*\n=============\npfnWriteBytes\n\n=============\n*/\nstatic void GAME_EXPORT pfnWriteBytes( const byte *bytes, int count )\n{\n\tMSG_WriteBytes( &sv.multicast, bytes, count );\n\tif( svgame.msg_trace ) Con_Printf( \"\\t^3%s( %i )\\n\", __func__, count );\n\tsvgame.msg_realsize += count;\n}\n\nstatic const byte *GAME_EXPORT pfnLoadImagePixels( const char *filename, int *width, int *height )\n{\n\trgbdata_t\t*pic = FS_LoadImage( filename, NULL, 0 );\n\tbyte\t*buffer;\n\n\tif( !pic ) return NULL;\n\n\tbuffer = Mem_Malloc( svgame.mempool, pic->size );\n\tif( buffer ) memcpy( buffer, pic->buffer, pic->size );\n\tif( width ) *width = pic->width;\n\tif( height ) *height = pic->height;\n\tFS_FreeImage( pic );\n\n\treturn buffer;\n}\n\nstatic const char *GAME_EXPORT pfnGetModelName( int modelindex )\n{\n\tif( modelindex < 0 || modelindex >= MAX_MODELS )\n\t\treturn NULL;\n\treturn sv.model_precache[modelindex];\n}\n\nstatic const byte *GAME_EXPORT GL_TextureData( unsigned int texnum )\n{\n#if !XASH_DEDICATED\n\treturn Host_IsDedicated() ? NULL : ref.dllFuncs.GL_TextureData( texnum );\n#else // XASH_DEDICATED\n\treturn NULL;\n#endif // XASH_DEDICATED\n}\n\nstatic server_physics_api_t gPhysicsAPI =\n{\n\tSV_LinkEdict,\n\tSV_GetServerTime,\n\tSV_GetFrameTime,\n\t(void*)SV_ModelHandle,\n\tSV_GetHeadNode,\n\tSV_ServerState,\n\tHost_Error,\n#if !XASH_DEDICATED\n\t&gTriApi,\t// ouch!\n\tpfnDrawConsoleString,\n\tpfnDrawSetTextColor,\n\tpfnDrawConsoleStringLen,\n#else\n\tNULL,\t\t// ouch! ouch!\n\tNULL,\t\t// ouch! ouch!\n\tNULL,\t\t// ouch! ouch!\n\tNULL,\t\t// ouch! ouch!\n#endif\n\tCon_NPrintf,\n\tCon_NXPrintf,\n\tSV_GetLightStyle,\n\tSV_UpdateFogSettings,\n\tpfnGetFilesList,\n\tSV_TraceSurface,\n\tGL_TextureData,\n\tpfnMem_Alloc,\n\tpfnMem_Free,\n\tpfnPointContents,\n\tSV_MoveNormal,\n\tSV_MoveNoEnts,\n\t(void*)SV_BoxInPVS,\n\tpfnWriteBytes,\n\tMod_CheckLump,\n\tMod_ReadLump,\n\tMod_SaveLump,\n\tCOM_SaveFile,\n\tpfnLoadImagePixels,\n\tpfnGetModelName,\n\tSys_GetNativeObject\n};\n\n/*\n===============\nSV_InitPhysicsAPI\n\nInitialize server external physics\n===============\n*/\nqboolean SV_InitPhysicsAPI( void )\n{\n\tstatic PHYSICAPI\tpPhysIface;\n\n\tpPhysIface = (PHYSICAPI)COM_GetProcAddress( svgame.hInstance, \"Server_GetPhysicsInterface\" );\n\tif( pPhysIface )\n\t{\n\t\tif( pPhysIface( SV_PHYSICS_INTERFACE_VERSION, &gPhysicsAPI, &svgame.physFuncs ))\n\t\t{\n\t\t\tCon_Reportf( \"%s: ^2initailized extended PhysicAPI ^7ver. %i\\n\", __func__, SV_PHYSICS_INTERFACE_VERSION );\n\n\t\t\tif( svgame.physFuncs.SV_CheckFeatures != NULL )\n\t\t\t{\n\t\t\t\t// grab common engine features (it will be shared across the network)\n\t\t\t\tHost_ValidateEngineFeatures( ENGINE_FEATURES_MASK, svgame.physFuncs.SV_CheckFeatures( ));\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\t// make sure what physic functions is cleared\n\t\tmemset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs ));\n\t\tHost_ValidateEngineFeatures( ENGINE_FEATURES_MASK, 0 );\n\t\treturn false; // just tell user about problems\n\t}\n\n\t// physic interface is missed\n\tHost_ValidateEngineFeatures( ENGINE_FEATURES_MASK, 0 );\n\treturn true;\n}\n"
  },
  {
    "path": "engine/server/sv_pmove.c",
    "content": "/*\nsv_pmove.c - server-side player physic\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"const.h\"\n#include \"pm_local.h\"\n#include \"event_flags.h\"\n#include \"studio.h\"\n\nstatic qboolean has_update = false;\nstatic void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin );\n\nvoid SV_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr )\n{\n\tAssert( tr != NULL );\n\n\tif( svgame.physFuncs.ClipPMoveToEntity != NULL )\n\t{\n\t\t// do custom sweep test\n\t\tsvgame.physFuncs.ClipPMoveToEntity( pe, start, mins, maxs, end, tr );\n\t}\n\telse\n\t{\n\t\t// function is missed, so we didn't hit anything\n\t\ttr->allsolid = false;\n\t}\n}\n\nstatic qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed )\n{\n\tmodel_t\t*mod = SV_ModelHandle( ed->v.modelindex );\n\n\tif( !mod ) return false;\n\tpe->player = false;\n\n\tpe->info = NUM_FOR_EDICT( ed );\n\tVectorCopy( ed->v.origin, pe->origin );\n\tVectorCopy( ed->v.angles, pe->angles );\n\n\tif( FBitSet( ed->v.flags, FL_CLIENT ))\n\t{\n\t\t// client\n\t\tSV_GetTrueOrigin( sv.current_client, pe->info, pe->origin );\n\t\tif( FBitSet( ed->v.flags, FL_FAKECLIENT )) // fakeclients have client flag too\n\t\t{\n\t\t\t// bot\n\t\t\tQ_strncpy( pe->name, \"bot\", sizeof( pe->name ));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_strncpy( pe->name, \"player\", sizeof( pe->name ));\n\t\t}\n\t\tpe->player = pe->info;\n\t}\n\telse\n\t{\n\t\t// otherwise copy the modelname\n\t\tQ_strncpy( pe->name, mod->name, sizeof( pe->name ));\n\t}\n\n\tpe->model = pe->studiomodel = NULL;\n\n\tswitch( ed->v.solid )\n\t{\n\tcase SOLID_NOT:\n\tcase SOLID_BSP:\n\t\tpe->model = mod;\n\t\tVectorClear( pe->mins );\n\t\tVectorClear( pe->maxs );\n\t\tbreak;\n\tcase SOLID_BBOX:\n\t\tif( mod && mod->type == mod_studio && mod->flags & STUDIO_TRACE_HITBOX )\n\t\t\tpe->studiomodel = mod;\n\t\tVectorCopy( ed->v.mins, pe->mins );\n\t\tVectorCopy( ed->v.maxs, pe->maxs );\n\t\tbreak;\n\tcase SOLID_CUSTOM:\n\t\tpe->model = (mod->type == mod_brush) ? mod : NULL;\n\t\tpe->studiomodel = (mod->type == mod_studio) ? mod : NULL;\n\t\tVectorCopy( ed->v.mins, pe->mins );\n\t\tVectorCopy( ed->v.maxs, pe->maxs );\n\t\tbreak;\n\tdefault:\n\t\tpe->studiomodel = (mod->type == mod_studio) ? mod : NULL;\n\t\tVectorCopy( ed->v.mins, pe->mins );\n\t\tVectorCopy( ed->v.maxs, pe->maxs );\n\t\tbreak;\n\t}\n\n\tpe->solid = ed->v.solid;\n\tpe->rendermode = ed->v.rendermode;\n\tpe->skin = ed->v.skin;\n\tpe->frame = ed->v.frame;\n\tpe->sequence = ed->v.sequence;\n\n\tmemcpy( &pe->controller[0], &ed->v.controller[0], 4 * sizeof( byte ));\n\tmemcpy( &pe->blending[0], &ed->v.blending[0], 2 * sizeof( byte ));\n\n\tpe->movetype = ed->v.movetype;\n\tpe->takedamage = ed->v.takedamage;\n\tpe->team = ed->v.team;\n\tpe->classnumber = ed->v.playerclass;\n\tpe->blooddecal = 0;\t// unused in GoldSrc\n\n\t// for mods\n\tpe->iuser1 = ed->v.iuser1;\n\tpe->iuser2 = ed->v.iuser2;\n\tpe->iuser3 = ed->v.iuser3;\n\tpe->iuser4 = ed->v.iuser4;\n\tpe->fuser1 = ed->v.fuser1;\n\tpe->fuser2 = ed->v.fuser2;\n\tpe->fuser3 = ed->v.fuser3;\n\tpe->fuser4 = ed->v.fuser4;\n\n\tVectorCopy( ed->v.vuser1, pe->vuser1 );\n\tVectorCopy( ed->v.vuser2, pe->vuser2 );\n\tVectorCopy( ed->v.vuser3, pe->vuser3 );\n\tVectorCopy( ed->v.vuser4, pe->vuser4 );\n\n\treturn true;\n}\n\nstatic qboolean SV_ShouldUnlagForPlayer( sv_client_t *cl )\n{\n\t// can't unlag in singleplayer\n\tif( svs.maxclients <= 1 )\n\t\treturn false;\n\n\t// unlag disabled globally\n\tif( !svgame.dllFuncs.pfnAllowLagCompensation() || !sv_unlag.value )\n\t\treturn false;\n\n\tif( !FBitSet( cl->flags, FCL_LAG_COMPENSATION ))\n\t\treturn false;\n\n\t// player not ready\n\tif( cl->state != cs_spawned )\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin )\n{\n\tif( !SV_ShouldUnlagForPlayer( cl ))\n\t\treturn;\n\n\tif( edictnum < 1 || edictnum > svs.maxclients )\n\t\treturn;\n\n\tif( svgame.interp[edictnum-1].active && svgame.interp[edictnum-1].moving )\n\t\tVectorCopy( svgame.interp[edictnum-1].oldpos, origin );\n}\n\nstatic void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs )\n{\n\tif( !SV_ShouldUnlagForPlayer( cl ))\n\t\treturn;\n\n\tif( edictnum < 1 || edictnum > svs.maxclients )\n\t\treturn;\n\n\tif( svgame.interp[edictnum-1].active && svgame.interp[edictnum-1].moving )\n\t{\n\t\tVectorCopy( svgame.interp[edictnum-1].mins, mins );\n\t\tVectorCopy( svgame.interp[edictnum-1].maxs, maxs );\n\t}\n}\n\n/*\n====================\nSV_AddLinksToPmove\n\ncollect solid entities\n====================\n*/\nstatic void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*check, *pl;\n\tvec3_t\tmins, maxs;\n\tphysent_t\t*pe;\n\n\tpl = EDICT_NUM( svgame.pmove->player_index + 1 );\n\tAssert( SV_IsValidEdict( pl ));\n\n\t// touch linked edicts\n\tfor( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\t\tcheck = EDICT_FROM_AREA( l );\n\n\t\tif( check->v.groupinfo != 0 )\n\t\t{\n\t\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( check->v.groupinfo, pl->v.groupinfo ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( check->v.groupinfo, pl->v.groupinfo ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( check->v.owner == pl || check->v.solid == SOLID_TRIGGER )\n\t\t\tcontinue; // player or player's own missile\n\n\t\tif( svgame.pmove->numvisent < MAX_PHYSENTS )\n\t\t{\n\t\t\tpe = &svgame.pmove->visents[svgame.pmove->numvisent];\n\t\t\tif( SV_CopyEdictToPhysEnt( pe, check ))\n\t\t\t\tsvgame.pmove->numvisent++;\n\t\t}\n\n\t\tif( check->v.solid == SOLID_NOT && ( check->v.skin == CONTENTS_NONE || check->v.modelindex == 0 ))\n\t\t\tcontinue;\n\n\t\t// ignore monsterclip brushes\n\t\tif( FBitSet( check->v.flags, FL_MONSTERCLIP ) && check->v.solid == SOLID_BSP )\n\t\t\tcontinue;\n\n\t\tif( check == pl ) continue;\t// himself\n\n\t\t// nehahra collision flags\n\t\tif( check->v.movetype != MOVETYPE_PUSH )\n\t\t{\n\t\t\tif(( FBitSet( check->v.flags, FL_CLIENT|FL_FAKECLIENT ) && check->v.health <= 0.0f ) || check->v.deadflag == DEAD_DEAD )\n\t\t\t\tcontinue;\t// dead body\n\t\t}\n\n\t\tif( VectorIsNull( check->v.size ))\n\t\t\tcontinue;\n\n\t\tVectorCopy( check->v.absmin, mins );\n\t\tVectorCopy( check->v.absmax, maxs );\n\n\t\tif( FBitSet( check->v.flags, FL_CLIENT ) && !FBitSet( check->v.flags, FL_FAKECLIENT ))\n\t\t{\n\t\t\tif( sv.current_client )\n\t\t\t{\n\t\t\t\t// trying to get interpolated values\n\t\t\t\tSV_GetTrueMinMax( sv.current_client, NUM_FOR_EDICT( check ), mins, maxs );\n\t\t\t}\n\t\t}\n\n\t\tif( !BoundsIntersect( pmove_mins, pmove_maxs, mins, maxs ))\n\t\t\tcontinue;\n\n\t\tif( svgame.pmove->numphysent < MAX_PHYSENTS )\n\t\t{\n\t\t\tpe = &svgame.pmove->physents[svgame.pmove->numphysent];\n\n\t\t\tif( SV_CopyEdictToPhysEnt( pe, check ))\n\t\t\t\tsvgame.pmove->numphysent++;\n\t\t}\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( pmove_maxs[node->axis] > node->dist )\n\t\tSV_AddLinksToPmove( node->children[0], pmove_mins, pmove_maxs );\n\tif( pmove_mins[node->axis] < node->dist )\n\t\tSV_AddLinksToPmove( node->children[1], pmove_mins, pmove_maxs );\n}\n\n/*\n====================\nSV_AddLaddersToPmove\n====================\n*/\nstatic void SV_AddLaddersToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*check;\n\tmodel_t\t*mod;\n\tphysent_t\t*pe;\n\n\t// get ladder edicts\n\tfor( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\t\tcheck = EDICT_FROM_AREA( l );\n\n\t\tif( check->v.solid != SOLID_NOT || check->v.skin != CONTENTS_LADDER )\n\t\t\tcontinue;\n\n\t\tmod = SV_ModelHandle( check->v.modelindex );\n\n\t\t// only brushes can have special contents\n\t\tif( !mod || mod->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tif( !BoundsIntersect( pmove_mins, pmove_maxs, check->v.absmin, check->v.absmax ))\n\t\t\tcontinue;\n\n\t\tif( svgame.pmove->nummoveent == MAX_MOVEENTS )\n\t\t\treturn;\n\n\t\tpe = &svgame.pmove->moveents[svgame.pmove->nummoveent];\n\t\tif( SV_CopyEdictToPhysEnt( pe, check ))\n\t\t\tsvgame.pmove->nummoveent++;\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( pmove_maxs[node->axis] > node->dist )\n\t\tSV_AddLaddersToPmove( node->children[0], pmove_mins, pmove_maxs );\n\tif( pmove_mins[node->axis] < node->dist )\n\t\tSV_AddLaddersToPmove( node->children[1], pmove_mins, pmove_maxs );\n}\n\nstatic void GAME_EXPORT pfnParticle( const float *origin, int color, float life, int zpos, int zvel )\n{\n\tint\tv;\n\n\tif( !origin )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: NULL origin. Ignored\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tMSG_WriteByte( &sv.reliable_datagram, svc_particle );\n\tMSG_WriteVec3Coord( &sv.reliable_datagram, origin );\n\tMSG_WriteChar( &sv.reliable_datagram, 0 ); // no x-vel\n\tMSG_WriteChar( &sv.reliable_datagram, 0 ); // no y-vel\n\tv = bound( -128, (zpos * zvel) * 16.0f, 127 );\n\tMSG_WriteChar( &sv.reliable_datagram, v ); // write z-vel\n\tMSG_WriteByte( &sv.reliable_datagram, 1 );\n\tMSG_WriteByte( &sv.reliable_datagram, color );\n\tMSG_WriteByte( &sv.reliable_datagram, bound( 0, life * 8, 255 ));\n}\n\nstatic int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace )\n{\n\treturn PM_TestPlayerPosition( svgame.pmove, pos, ptrace, NULL );\n}\n\nstatic void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr )\n{\n\tPM_StuckTouch( svgame.pmove, hitent, tr );\n}\n\nstatic int GAME_EXPORT pfnPointContents( float *p, int *truecontents )\n{\n\treturn PM_PointContentsPmove( svgame.pmove, p, truecontents );\n}\n\nstatic int GAME_EXPORT pfnTruePointContents( float *p )\n{\n\treturn PM_TruePointContents( svgame.pmove, p );\n}\n\nstatic pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe )\n{\n\treturn PM_PlayerTraceExt( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL );\n}\n\nstatic pmtrace_t *GAME_EXPORT pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )\n{\n\treturn PM_TraceLine( svgame.pmove, start, end, flags, usehull, ignore_pe );\n}\n\nstatic hull_t *GAME_EXPORT pfnHullForBsp( physent_t *pe, float *offset )\n{\n\treturn PM_HullForBsp( pe, svgame.pmove, offset );\n}\n\nstatic float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace )\n{\n\treturn PM_TraceModel( svgame.pmove, pe, start, end, trace );\n}\n\nstatic const char *GAME_EXPORT pfnTraceTexture( int ground, float *vstart, float *vend )\n{\n\treturn PM_TraceTexture( svgame.pmove, ground, vstart, vend );\n}\n\nstatic void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch )\n{\n\tedict_t\t*ent;\n\n\tent = EDICT_NUM( svgame.pmove->player_index + 1 );\n\tif( !SV_IsValidEdict( ent )) return;\n\n\tSV_StartSound( ent, channel, sample, volume, attenuation, fFlags|SND_FILTER_CLIENT, pitch );\n}\n\nstatic void GAME_EXPORT pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin,\n\tfloat *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )\n{\n\tedict_t\t*ent;\n\n\tent = EDICT_NUM( clientindex + 1 );\n\tif( !SV_IsValidEdict( ent )) return;\n\n\t// GoldSrc always sets FEV_NOTHOST in PMove version of this function\n\tSV_PlaybackEventFull( flags | FEV_NOTHOST, ent, eventindex,\n\t\tdelay, origin, angles,\n\t\tfparam1, fparam2,\n\t\tiparam1, iparam2,\n\t\tbparam1, bparam2 );\n}\n\nstatic pmtrace_t GAME_EXPORT pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter )\n{\n\treturn PM_PlayerTraceExt( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter );\n}\n\nstatic int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter )\n{\n\treturn PM_TestPlayerPosition( svgame.pmove, pos, ptrace, pmFilter );\n}\n\nstatic pmtrace_t *GAME_EXPORT pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter )\n{\n\treturn PM_TraceLineEx( svgame.pmove, start, end, flags, usehull, pmFilter );\n}\n\nstatic struct msurface_s *GAME_EXPORT pfnTraceSurface( int ground, float *vstart, float *vend )\n{\n\treturn PM_TraceSurfacePmove( svgame.pmove, ground, vstart, vend );\n}\n\n/*\n===============\nSV_InitClientMove\n\n===============\n*/\nvoid SV_InitClientMove( void )\n{\n\tint\ti;\n\n\tPmove_Init ();\n\n\tsvgame.pmove->server = true;\n\tsvgame.pmove->movevars = &svgame.movevars;\n\tsvgame.pmove->runfuncs = false;\n\n\t// enumerate client hulls\n\tfor( i = 0; i < MAX_MAP_HULLS; i++ )\n\t{\n\t\tif( svgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))\n\t\t\tCon_Reportf( \"SV: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\\n\", i,\n\t\t\thost.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2],\n\t\t\thost.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] );\n\t}\n\n\tmemcpy( svgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins ));\n\tmemcpy( svgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs ));\n\n\t// common utilities\n\tsvgame.pmove->PM_Info_ValueForKey = Info_ValueForKey;\n\tsvgame.pmove->PM_Particle = pfnParticle;\n\tsvgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition;\n\tsvgame.pmove->Con_NPrintf = Con_NPrintf;\n\tsvgame.pmove->Con_DPrintf = Con_DPrintf;\n\tsvgame.pmove->Con_Printf = Con_Printf;\n\tsvgame.pmove->Sys_FloatTime = Sys_DoubleTime;\n\tsvgame.pmove->PM_StuckTouch = pfnStuckTouch;\n\tsvgame.pmove->PM_PointContents = pfnPointContents;\n\tsvgame.pmove->PM_TruePointContents = pfnTruePointContents;\n\tsvgame.pmove->PM_HullPointContents = (void*)PM_HullPointContents;\n\tsvgame.pmove->PM_PlayerTrace = pfnPlayerTrace;\n\tsvgame.pmove->PM_TraceLine = pfnTraceLine;\n\tsvgame.pmove->RandomLong = COM_RandomLong;\n\tsvgame.pmove->RandomFloat = COM_RandomFloat;\n\tsvgame.pmove->PM_GetModelType = pfnGetModelType;\n\tsvgame.pmove->PM_GetModelBounds = pfnGetModelBounds;\n\tsvgame.pmove->PM_HullForBsp = (void*)pfnHullForBsp;\n\tsvgame.pmove->PM_TraceModel = pfnTraceModel;\n\tsvgame.pmove->COM_FileSize = COM_FileSize;\n\tsvgame.pmove->COM_LoadFile = COM_LoadFile;\n\tsvgame.pmove->COM_FreeFile = COM_FreeFile;\n\tsvgame.pmove->memfgets = COM_MemFgets;\n\tsvgame.pmove->PM_PlaySound = pfnPlaySound;\n\tsvgame.pmove->PM_TraceTexture = pfnTraceTexture;\n\tsvgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull;\n\tsvgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx;\n\tsvgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx;\n\tsvgame.pmove->PM_TraceLineEx = pfnTraceLineEx;\n\tsvgame.pmove->PM_TraceSurface = pfnTraceSurface;\n\n\t// initalize pmove\n\tsvgame.dllFuncs.pfnPM_Init( svgame.pmove );\n}\n\nstatic void PM_CheckMovingGround( edict_t *ent, float frametime )\n{\n\tif( svgame.physFuncs.SV_UpdatePlayerBaseVelocity != NULL )\n\t{\n\t\tsvgame.physFuncs.SV_UpdatePlayerBaseVelocity( ent );\n\t}\n\telse\n\t{\n\t\tSV_UpdateBaseVelocity( ent );\n\t}\n\n\tif( !FBitSet( ent->v.flags, FL_BASEVELOCITY ))\n\t{\n\t\t// apply momentum (add in half of the previous frame of velocity first)\n\t\tVectorMA( ent->v.velocity, 1.0f + (frametime * 0.5f), ent->v.basevelocity, ent->v.velocity );\n\t\tVectorClear( ent->v.basevelocity );\n\t}\n\n\tClearBits( ent->v.flags, FL_BASEVELOCITY );\n}\n\nstatic void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd, const char *physinfo )\n{\n\tvec3_t\tabsmin, absmax;\n\tedict_t\t*clent = cl->edict;\n\tint\ti;\n\n\tsvgame.globals->frametime = (ucmd->msec * 0.001f);\n\n\tpmove->player_index = NUM_FOR_EDICT( clent ) - 1;\n\tpmove->multiplayer = (svs.maxclients > 1) ? true : false;\n\tpmove->time = (float)(cl->timebase * 1000.0);\n\tVectorCopy( clent->v.origin, pmove->origin );\n\tVectorCopy( clent->v.v_angle, pmove->angles );\n\tVectorCopy( clent->v.v_angle, pmove->oldangles );\n\tVectorCopy( clent->v.velocity, pmove->velocity );\n\tVectorCopy( clent->v.basevelocity, pmove->basevelocity );\n\tVectorCopy( clent->v.view_ofs, pmove->view_ofs );\n\tVectorCopy( clent->v.movedir, pmove->movedir );\n\tpmove->flDuckTime = clent->v.flDuckTime;\n\tpmove->bInDuck = clent->v.bInDuck;\n\tpmove->usehull = (clent->v.flags & FL_DUCKING) ? 1 : 0; // reset hull\n\tpmove->flTimeStepSound = clent->v.flTimeStepSound;\n\tpmove->iStepLeft = clent->v.iStepLeft;\n\tpmove->flFallVelocity = clent->v.flFallVelocity;\n\tpmove->flSwimTime = clent->v.flSwimTime;\n\tVectorCopy( clent->v.punchangle, pmove->punchangle );\n\tpmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code\n\tpmove->effects = clent->v.effects;\n\tpmove->flags = clent->v.flags;\n\tpmove->gravity = clent->v.gravity;\n\tpmove->friction = clent->v.friction;\n\tpmove->oldbuttons = clent->v.oldbuttons;\n\tpmove->waterjumptime = clent->v.teleport_time;\n\tpmove->dead = (clent->v.health <= 0.0f ) ? true : false;\n\tpmove->deadflag = clent->v.deadflag;\n\tpmove->spectator = 0; // spectator physic all execute on client\n\tpmove->movetype = clent->v.movetype;\n\tif( pmove->multiplayer ) pmove->onground = -1;\n\tpmove->waterlevel = clent->v.waterlevel;\n\tpmove->watertype = clent->v.watertype;\n\tpmove->maxspeed = svgame.movevars.maxspeed; // GoldSrc uses sv_maxspeed here?\n\tpmove->clientmaxspeed = clent->v.maxspeed;\n\tpmove->iuser1 = clent->v.iuser1;\n\tpmove->iuser2 = clent->v.iuser2;\n\tpmove->iuser3 = clent->v.iuser3;\n\tpmove->iuser4 = clent->v.iuser4;\n\tpmove->fuser1 = clent->v.fuser1;\n\tpmove->fuser2 = clent->v.fuser2;\n\tpmove->fuser3 = clent->v.fuser3;\n\tpmove->fuser4 = clent->v.fuser4;\n\tVectorCopy( clent->v.vuser1, pmove->vuser1 );\n\tVectorCopy( clent->v.vuser2, pmove->vuser2 );\n\tVectorCopy( clent->v.vuser3, pmove->vuser3 );\n\tVectorCopy( clent->v.vuser4, pmove->vuser4 );\n\tpmove->cmd = *ucmd;\t// setup current cmds\n\tpmove->runfuncs = true;\n\n\tQ_strncpy( pmove->physinfo, physinfo, sizeof( pmove->physinfo ));\n\n\t// setup physents\n\tpmove->numvisent = 0;\n\tpmove->numphysent = 0;\n\tpmove->nummoveent = 0;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tabsmin[i] = clent->v.origin[i] - 256.0f;\n\t\tabsmax[i] = clent->v.origin[i] + 256.0f;\n\t}\n\n\tSV_CopyEdictToPhysEnt( &svgame.pmove->physents[0], &svgame.edicts[0] );\n\tsvgame.pmove->visents[0] = svgame.pmove->physents[0];\n\tsvgame.pmove->numphysent = 1;\t// always have world\n\tsvgame.pmove->numvisent = 1;\n\n\tSV_AddLinksToPmove( sv_areanodes, absmin, absmax );\n\tSV_AddLaddersToPmove( sv_areanodes, absmin, absmax );\n}\n\nstatic void SV_FinishPMove( playermove_t *pmove, sv_client_t *cl )\n{\n\tedict_t\t*clent = cl->edict;\n\n\tclent->v.teleport_time = pmove->waterjumptime;\n\tVectorCopy( pmove->origin, clent->v.origin );\n\tVectorCopy( pmove->view_ofs, clent->v.view_ofs );\n\tVectorCopy( pmove->velocity, clent->v.velocity );\n\tVectorCopy( pmove->basevelocity, clent->v.basevelocity );\n\tVectorCopy( pmove->punchangle, clent->v.punchangle );\n\tVectorCopy( pmove->movedir, clent->v.movedir );\n\tclent->v.flTimeStepSound = pmove->flTimeStepSound;\n\tclent->v.flFallVelocity = pmove->flFallVelocity;\n\tclent->v.oldbuttons = pmove->cmd.buttons;\n\tclent->v.waterlevel = pmove->waterlevel;\n\tclent->v.watertype = pmove->watertype;\n\tclent->v.maxspeed = pmove->clientmaxspeed;\n\tclent->v.flDuckTime = pmove->flDuckTime;\n\tclent->v.flSwimTime = pmove->flSwimTime;\n\tclent->v.iStepLeft = pmove->iStepLeft;\n\tclent->v.movetype = pmove->movetype;\n\tclent->v.friction = pmove->friction;\n\tclent->v.deadflag = pmove->deadflag;\n\tclent->v.effects = pmove->effects;\n\tclent->v.bInDuck = pmove->bInDuck;\n\tclent->v.flags = pmove->flags;\n\n\t// copy back user variables\n\tclent->v.iuser1 = pmove->iuser1;\n\tclent->v.iuser2 = pmove->iuser2;\n\tclent->v.iuser3 = pmove->iuser3;\n\tclent->v.iuser4 = pmove->iuser4;\n\tclent->v.fuser1 = pmove->fuser1;\n\tclent->v.fuser2 = pmove->fuser2;\n\tclent->v.fuser3 = pmove->fuser3;\n\tclent->v.fuser4 = pmove->fuser4;\n\tVectorCopy( pmove->vuser1, clent->v.vuser1 );\n\tVectorCopy( pmove->vuser2, clent->v.vuser2 );\n\tVectorCopy( pmove->vuser3, clent->v.vuser3 );\n\tVectorCopy( pmove->vuser4, clent->v.vuser4 );\n\n\tif( pmove->onground == -1 )\n\t{\n\t\tClearBits( clent->v.flags, FL_ONGROUND );\n\t}\n\telse if( pmove->onground >= 0 && pmove->onground < pmove->numphysent )\n\t{\n\t\tSetBits( clent->v.flags, FL_ONGROUND );\n\t\tclent->v.groundentity = EDICT_NUM( pmove->physents[pmove->onground].info );\n\t}\n\n\t// angles\n\t// show 1/3 the pitch angle and all the roll angle\n\tif( !clent->v.fixangle )\n\t{\n\t\tVectorCopy( pmove->angles, clent->v.v_angle );\n\t\tclent->v.angles[PITCH] = -( clent->v.v_angle[PITCH] / 3.0f );\n\t\tclent->v.angles[ROLL] = clent->v.v_angle[ROLL];\n\t\tclent->v.angles[YAW] = clent->v.v_angle[YAW];\n\t}\n\n\tSV_SetMinMaxSize( clent, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], false );\n\n\t// all next calls ignore footstep sounds\n\tpmove->runfuncs = false;\n}\n\nstatic entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame )\n{\n\tentity_state_t\t*state;\n\tint\t\ti;\n\n\tfor( i = 0; i < frame->num_entities; i++ )\n\t{\n\t\tstate = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities];\n\n\t\tif( state->number == index )\n\t\t\treturn state;\n\t}\n\treturn NULL;\n}\n\nstatic qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos )\n{\n\tint\ti;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( fabs( old_pos[i] - new_pos[i] ) > 64.0f )\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nstatic void SV_SetupMoveInterpolant( sv_client_t *cl )\n{\n\tint\t\ti, j, clientnum;\n\tfloat\t\tfinalpush, lerp_msec;\n\tfloat\t\tlatency, lerpFrac;\n\tclient_frame_t\t*frame, *frame2;\n\tentity_state_t\t*state, *lerpstate;\n\tvec3_t\t\tcurpos, newpos;\n\tsv_client_t\t*check;\n\tsv_interp_t\t*lerp;\n\n\tmemset( svgame.interp, 0, sizeof( svgame.interp ));\n\thas_update = false;\n\n\tif( !SV_ShouldUnlagForPlayer( cl ))\n\t\treturn;\n\n\thas_update = true;\n\n\tfor( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ )\n\t{\n\t\tif( check->state != cs_spawned || check == cl )\n\t\t\tcontinue;\n\n\t\tlerp = &svgame.interp[i];\n\n\t\tVectorCopy( check->edict->v.origin, lerp->oldpos );\n\t\tVectorCopy( check->edict->v.absmin, lerp->mins );\n\t\tVectorCopy( check->edict->v.absmax, lerp->maxs );\n\t\tlerp->active = true;\n\t}\n\n\tlatency = Q_min( cl->latency, 1.5f );\n\n\tif( sv_maxunlag.value != 0.0f )\n\t{\n\t\tif (sv_maxunlag.value < 0.0f )\n\t\t\tCvar_SetValue( \"sv_maxunlag\", 0.0f );\n\t\tlatency = Q_min( latency, sv_maxunlag.value );\n\t}\n\n\tlerp_msec = cl->lastcmd.lerp_msec * 0.001f;\n\tif( lerp_msec > 0.1f ) lerp_msec = 0.1f;\n\n\tif( lerp_msec < cl->cl_updaterate )\n\t\tlerp_msec = cl->cl_updaterate;\n\n\tfinalpush = ( host.realtime - latency - lerp_msec ) + sv_unlagpush.value;\n\tif( finalpush > host.realtime ) finalpush = host.realtime; // pushed too much ?\n\n\tframe = frame2 = NULL;\n\n\tfor( i = 0; i < SV_UPDATE_BACKUP; i++, frame2 = frame )\n\t{\n\t\tframe = &cl->frames[(cl->netchan.outgoing_sequence - (i + 1)) & SV_UPDATE_MASK];\n\n\t\tfor( j = 0; j < frame->num_entities; j++ )\n\t\t{\n\t\t\tstate = &svs.packet_entities[(frame->first_entity+j)%svs.num_client_entities];\n\n\t\t\tif( state->number < 1 || state->number > svs.maxclients )\n\t\t\t\tcontinue;\n\n\t\t\tlerp = &svgame.interp[state->number-1];\n\t\t\tif( lerp->nointerp ) continue;\n\n\t\t\tif( state->health <= 0 || FBitSet( state->effects, EF_NOINTERP ))\n\t\t\t\tlerp->nointerp = true;\n\n\t\t\tif( lerp->firstframe )\n\t\t\t{\n\t\t\t\tif( SV_UnlagCheckTeleport( state->origin, lerp->finalpos ))\n\t\t\t\t\tlerp->nointerp = true;\n\t\t\t}\n\t\t\telse lerp->firstframe = true;\n\n\t\t\tVectorCopy( state->origin, lerp->finalpos );\n\t\t}\n\n\t\tif( finalpush > frame->senttime )\n\t\t\tbreak;\n\t}\n\n\tif( i == SV_UPDATE_BACKUP || finalpush - frame->senttime > 1.0f )\n\t{\n\t\tmemset( svgame.interp, 0, sizeof( svgame.interp ));\n\t\thas_update = false;\n\t\treturn;\n\t}\n\n\tif( !frame2 )\n\t{\n\t\tframe2 = frame;\n\t\tlerpFrac = 0;\n\t}\n\telse\n\t{\n\t\tif( frame2->senttime - frame->senttime == 0.0 )\n\t\t{\n\t\t\tlerpFrac = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlerpFrac = (finalpush - frame->senttime) / (frame2->senttime - frame->senttime);\n\t\t\tlerpFrac = bound( 0.0f, lerpFrac, 1.0f );\n\t\t}\n\t}\n\n\tfor( i = 0; i < frame->num_entities; i++ )\n\t{\n\t\tstate = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities];\n\n\t\tif( state->number < 1 || state->number > svs.maxclients )\n\t\t\tcontinue;\n\n\t\tclientnum = state->number - 1;\n\t\tcheck = &svs.clients[clientnum];\n\n\t\tif( check->state != cs_spawned || check == cl )\n\t\t\tcontinue;\n\n\t\tlerp = &svgame.interp[clientnum];\n\n\t\tif( !lerp->active || lerp->nointerp )\n\t\t\tcontinue;\n\n\t\tlerpstate = SV_FindEntInPack( state->number, frame2 );\n\n\t\tif( !lerpstate )\n\t\t{\n\t\t\tVectorCopy( state->origin, curpos );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( lerpstate->origin, state->origin, newpos );\n\t\t\tVectorMA( state->origin, lerpFrac, newpos, curpos );\n\t\t}\n\n\t\tVectorCopy( curpos, lerp->curpos );\n\t\tVectorCopy( curpos, lerp->newpos );\n\n\t\tif( !VectorCompare( curpos, check->edict->v.origin ))\n\t\t{\n\t\t\tVectorCopy( curpos, check->edict->v.origin );\n\t\t\tSV_LinkEdict( check->edict, false );\n\t\t\tlerp->moving = true;\n\t\t}\n\t}\n}\n\nstatic void SV_RestoreMoveInterpolant( sv_client_t *cl )\n{\n\tsv_client_t\t*check;\n\tsv_interp_t\t*oldlerp;\n\tint\t\ti;\n\n\tif( !has_update )\n\t{\n\t\thas_update = true;\n\t\treturn;\n\t}\n\n\tif( !SV_ShouldUnlagForPlayer( cl ))\n\t\treturn;\n\n\tfor( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ )\n\t{\n\t\tif( check->state != cs_spawned || check == cl )\n\t\t\tcontinue;\n\n\t\toldlerp = &svgame.interp[i];\n\n\t\tif( VectorCompareEpsilon( oldlerp->oldpos, oldlerp->newpos, ON_EPSILON ))\n\t\t\tcontinue; // they didn't actually move.\n\n\t\tif( !oldlerp->moving || !oldlerp->active )\n\t\t\tcontinue;\n\n\t\tif( VectorCompare( oldlerp->curpos, check->edict->v.origin ))\n\t\t{\n\t\t\tVectorCopy( oldlerp->oldpos, check->edict->v.origin );\n\t\t\tSV_LinkEdict( check->edict, false );\n\t\t}\n\t}\n}\n\n/*\n===========\nSV_RunCmd\n===========\n*/\nvoid SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed )\n{\n\tedict_t\t*clent, *touch;\n\tdouble\tframetime;\n\tint\ti, oldmsec;\n\tpmtrace_t\t*pmtrace;\n\ttrace_t\ttrace;\n\tvec3_t\toldvel;\n\tusercmd_t cmd;\n\n\t// if the player got kicked, do not process commands\n\tif( cl->state <= cs_zombie )\n\t\treturn;\n\n\tclent = cl->edict;\n\tcmd = *ucmd;\n\n\tif( cl->ignorecmdtime > host.realtime )\n\t{\n\t\tif( !cl->ignorecmdtime_warned && !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t{\n\t\t\t// report to server op\n\t\t\tCon_Reportf( S_WARN \"%s time is faster than server time (speed hack?)\\n\", cl->name );\n\t\t\tcl->ignorecmdtime_warned = true;\n\t\t\tcl->ignorecmdtime_warns++;\n\n\t\t\t// automatically kick player\n\t\t\tif( sv_speedhack_kick.value && cl->ignorecmdtime_warns > sv_speedhack_kick.value )\n\t\t\t\tSV_KickPlayer( cl, \"Speed hacks aren't allowed on this server\" );\n\t\t}\n\t\tcl->cmdtime += ((double)ucmd->msec / 1000.0 );\n\t\treturn;\n\t}\n\n\tcl->ignorecmdtime = 0.0;\n\tcl->ignorecmdtime_warned = false;\n\n\t// chop up very long commands\n\tif( cmd.msec > 50 )\n\t{\n\t\toldmsec = ucmd->msec;\n\t\tcmd.msec = oldmsec / 2;\n\t\tSV_RunCmd( cl, &cmd, random_seed );\n\t\tcmd.msec = oldmsec / 2;\n\t\tcmd.impulse = 0;\n\t\tSV_RunCmd( cl, &cmd, random_seed );\n\t\treturn;\n\t}\n\n\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\tSV_SetupMoveInterpolant( cl );\n\n\tsvgame.dllFuncs.pfnCmdStart( cl->edict, ucmd, random_seed );\n\n\tframetime = ((double)ucmd->msec / 1000.0 );\n\tcl->timebase += frametime;\n\tcl->cmdtime += frametime;\n\n\tPM_CheckMovingGround( clent, frametime );\n\n\tVectorCopy( clent->v.v_angle, svgame.pmove->oldangles ); // save oldangles\n\tif( !clent->v.fixangle ) VectorCopy( ucmd->viewangles, clent->v.v_angle );\n\n\tVectorClear( clent->v.clbasevelocity );\n\n\t// copy player buttons\n\tclent->v.button = ucmd->buttons;\n\tclent->v.light_level = ucmd->lightlevel;\n\tif( ucmd->impulse ) clent->v.impulse = ucmd->impulse;\n\n\tsvgame.globals->time = cl->timebase;\n\tsvgame.dllFuncs.pfnPlayerPreThink( clent );\n\tSV_PlayerRunThink( clent, frametime, cl->timebase );\n\n\t// If conveyor, or think, set basevelocity, then send to client asap too.\n\tif( !VectorIsNull( clent->v.basevelocity ))\n\t\tVectorCopy( clent->v.basevelocity, clent->v.clbasevelocity );\n\n\t// setup playermove state\n\tSV_SetupPMove( svgame.pmove, cl, ucmd, cl->physinfo );\n\n\t// motor!\n\tsvgame.dllFuncs.pfnPM_Move( svgame.pmove, true );\n\n\t// copy results back to client\n\tSV_FinishPMove( svgame.pmove, cl );\n\n\tif( clent->v.solid != SOLID_NOT && !sv.playersonly )\n\t{\n\t\tif( svgame.physFuncs.PM_PlayerTouch != NULL )\n\t\t{\n\t\t\t// run custom impact function\n\t\t\tsvgame.physFuncs.PM_PlayerTouch( svgame.pmove, clent );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// link into place and touch triggers\n\t\t\tSV_LinkEdict( clent, true );\n\t\t\tVectorCopy( clent->v.velocity, oldvel ); // save velocity\n\n\t\t\t// touch other objects\n\t\t\tfor( i = 0; i < svgame.pmove->numtouch; i++ )\n\t\t\t{\n\t\t\t\tpmtrace = &svgame.pmove->touchindex[i];\n\t\t\t\ttouch = EDICT_NUM( svgame.pmove->physents[pmtrace->ent].info );\n\t\t\t\tVectorCopy( pmtrace->deltavelocity, clent->v.velocity );\n\t\t\t\tPM_ConvertTrace( &trace, pmtrace, touch );\n\t\t\t\tSV_Impact( touch, clent, &trace );\n\t\t\t}\n\n\t\t\t// restore velocity\n\t\t\tVectorCopy( oldvel, clent->v.velocity );\n\t\t}\n\t}\n\n\tsvgame.pmove->numtouch = 0;\n\tsvgame.globals->time = cl->timebase;\n\tsvgame.globals->frametime = frametime;\n\n\t// run post-think\n\tsvgame.dllFuncs.pfnPlayerPostThink( clent );\n\tsvgame.dllFuncs.pfnCmdEnd( clent );\n\n\tif( !FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t{\n\t\tSV_RestoreMoveInterpolant( cl );\n\t}\n}\n"
  },
  {
    "path": "engine/server/sv_query.c",
    "content": "/*\nsv_query.c - Source-engine like server querying\nCopyright (C) 2023 jeefo\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n\n/*\n==================\nSV_SourceQuery_Details\n==================\n*/\nstatic void SV_SourceQuery_Details( netadr_t from )\n{\n\tsizebuf_t buf;\n\tchar answer[2048];\n\tint bot_count, client_count;\n\n\tSV_GetPlayerCount( &client_count, &bot_count );\n\tclient_count += bot_count; // bots are counted as players in this reply\n\n\tMSG_Init( &buf, \"TSourceEngineQuery\", answer, sizeof( answer ));\n\n\tMSG_WriteDword( &buf, 0xFFFFFFFFU );\n\tMSG_WriteByte( &buf, S2A_GOLDSRC_INFO );\n\tMSG_WriteByte( &buf, PROTOCOL_VERSION );\n\n\tMSG_WriteString( &buf, hostname.string );\n\tMSG_WriteString( &buf, sv.name );\n\tMSG_WriteString( &buf, GI->gamefolder );\n\tMSG_WriteString( &buf, svgame.dllFuncs.pfnGetGameDescription( ));\n\n\tMSG_WriteShort( &buf, 0 );\n\tMSG_WriteByte( &buf, client_count );\n\tMSG_WriteByte( &buf, svs.maxclients );\n\tMSG_WriteByte( &buf, bot_count );\n\n\tMSG_WriteByte( &buf, Host_IsDedicated( ) ? 'd' : 'l' );\n#if XASH_WIN32\n\tMSG_WriteByte( &buf, 'w' );\n#elif XASH_APPLE\n\tMSG_WriteByte( &buf, 'm' );\n#else\n\tMSG_WriteByte( &buf, 'l' );\n#endif\n\n\tif( SV_HavePassword( ))\n\t\tMSG_WriteByte( &buf, 1 );\n\telse MSG_WriteByte( &buf, 0 );\n\tMSG_WriteByte( &buf, GI->secure );\n\tMSG_WriteString( &buf, XASH_VERSION );\n\n\tNET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &buf ), MSG_GetData( &buf ), from );\n}\n\n/*\n==================\nSV_SourceQuery_Rules\n==================\n*/\nstatic void SV_SourceQuery_Rules( netadr_t from )\n{\n\tconst cvar_t *cvar;\n\tsizebuf_t buf;\n\tchar answer[MAX_PRINT_MSG - 4];\n\tint pos;\n\tuint cvar_count = 0;\n\n\tMSG_Init( &buf, \"TSourceEngineQueryRules\", answer, sizeof( answer ));\n\n\tMSG_WriteDword( &buf, 0xFFFFFFFFU );\n\tMSG_WriteByte( &buf, S2A_GOLDSRC_RULES );\n\n\tpos = MSG_GetNumBitsWritten( &buf );\n\tMSG_WriteShort( &buf, 0 );\n\n\tfor( cvar = Cvar_GetList( ); cvar; cvar = cvar->next )\n\t{\n\t\tif( !FBitSet( cvar->flags, FCVAR_SERVER ))\n\t\t\tcontinue;\n\n\t\tMSG_WriteString( &buf, cvar->name );\n\n\t\tif( FBitSet( cvar->flags, FCVAR_PROTECTED ))\n\t\t{\n\t\t\tif( COM_CheckStringEmpty( cvar->string ) && Q_stricmp( cvar->string, \"none\" ))\n\t\t\t\tMSG_WriteString( &buf, \"1\" );\n\t\t\telse MSG_WriteString( &buf, \"0\" );\n\t\t}\n\t\telse MSG_WriteString( &buf, cvar->string );\n\n\t\tcvar_count++;\n\t}\n\n\tif( cvar_count != 0 )\n\t{\n\t\tint total = MSG_GetNumBytesWritten( &buf );\n\n\t\tMSG_SeekToBit( &buf, pos, SEEK_SET );\n\t\tMSG_WriteShort( &buf, cvar_count );\n\n\t\tNET_SendPacket( NS_SERVER, total, MSG_GetData( &buf ), from );\n\t}\n}\n\n/*\n==================\nSV_SourceQuery_Players\n==================\n*/\nstatic void SV_SourceQuery_Players( netadr_t from )\n{\n\tsizebuf_t buf;\n\tchar answer[MAX_PRINT_MSG - 4];\n\tint i, count = 0;\n\tint pos;\n\n\t// respect players privacy\n\tif( !sv_expose_player_list.value || SV_HavePassword( ))\n\t\treturn;\n\n\tMSG_Init( &buf, \"TSourceEngineQueryPlayers\", answer, sizeof( answer ));\n\n\tMSG_WriteDword( &buf, 0xFFFFFFFFU );\n\tMSG_WriteByte( &buf, S2A_GOLDSRC_PLAYERS );\n\n\tpos = MSG_GetNumBitsWritten( &buf );\n\tMSG_WriteByte( &buf, 0 );\n\n\tfor( i = 0; i < svs.maxclients; i++ )\n\t{\n\t\tconst sv_client_t *cl = &svs.clients[i];\n\n\t\tif( cl->state < cs_connected )\n\t\t\tcontinue;\n\n\t\tMSG_WriteByte( &buf, count );\n\t\tMSG_WriteString( &buf, cl->name );\n\t\tMSG_WriteLong( &buf, cl->edict->v.frags );\n\t\tif( FBitSet( cl->flags, FCL_FAKECLIENT ))\n\t\t\tMSG_WriteFloat( &buf, -1.0f );\n\t\telse MSG_WriteFloat( &buf, host.realtime - cl->connection_started );\n\n\t\tcount++;\n\t}\n\n\tif( count != 0 )\n\t{\n\t\tint total = MSG_GetNumBytesWritten( &buf );\n\n\t\tMSG_SeekToBit( &buf, pos, SEEK_SET );\n\t\tMSG_WriteByte( &buf, count );\n\n\t\tNET_SendPacket( NS_SERVER, total, MSG_GetData( &buf ), from );\n\t}\n}\n\n/*\n==================\nSV_SourceQuery_HandleConnnectionlessPacket\n==================\n*/\nvoid SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from )\n{\n\tif( !Q_strcmp( c, A2S_GOLDSRC_INFO ))\n\t{\n\t\tSV_SourceQuery_Details( from );\n\t}\n\telse switch( c[0] )\n\t{\n\tcase A2S_GOLDSRC_RULES:\n\t\tSV_SourceQuery_Rules( from );\n\t\tbreak;\n\tcase A2S_GOLDSRC_PLAYERS:\n\t\tSV_SourceQuery_Players( from );\n\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "engine/server/sv_save.c",
    "content": "/*\nsv_save.c - save\\restore implementation\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"library.h\"\n#include \"const.h\"\n#include \"render_api.h\"\t// decallist_t\n#include \"sound.h\"\t\t// S_GetDynamicSounds\n#include \"ref_common.h\" // decals\n\n/*\n==============================================================================\nSAVE FILE\n\nhalf-life implementation of saverestore system\n==============================================================================\n*/\n#define SAVEFILE_HEADER\t\t(('V'<<24)+('L'<<16)+('A'<<8)+'V')\t// little-endian \"VALV\"\n#define SAVEGAME_HEADER\t\t(('V'<<24)+('A'<<16)+('S'<<8)+'J')\t// little-endian \"JSAV\"\n#define SAVEGAME_VERSION\t\t0x0071\t\t\t\t// Version 0.71 GoldSrc compatible\n#define CLIENT_SAVEGAME_VERSION\t0x0067\t\t\t\t// Version 0.67\n\n#define SAVE_HEAPSIZE\t\t0x400000\t\t\t\t// reserve 4Mb for now\n#define SAVE_HASHSTRINGS\t\t0xFFF\t\t\t\t// 4095 unique strings\n\n// savedata headers\ntypedef struct\n{\n\tchar\tmapName[32];\n\tchar\tcomment[80];\n\tint\tmapCount;\n} GAME_HEADER;\n\ntypedef struct\n{\n\tint\tskillLevel;\n\tint\tentityCount;\n\tint\tconnectionCount;\n\tint\tlightStyleCount;\n\tfloat\ttime;\n\tchar\tmapName[32];\n\tchar\tskyName[32];\n\tint\tskyColor_r;\n\tint\tskyColor_g;\n\tint\tskyColor_b;\n\tfloat\tskyVec_x;\n\tfloat\tskyVec_y;\n\tfloat\tskyVec_z;\n} SAVE_HEADER;\n\ntypedef struct\n{\n\tint\tdecalCount;\t// render decals count\n\tint\tentityCount;\t// static entity count\n\tint\tsoundCount;\t// sounds count\n\tint\ttempEntsCount;\t// not used\n\tchar\tintroTrack[64];\n\tchar\tmainTrack[64];\n\tint\ttrackPosition;\n\tshort\tviewentity;\t// Xash3D added\n\tfloat\twateralpha;\n\tfloat\twateramp;\t\t// world waves\n} SAVE_CLIENT;\n\ntypedef struct\n{\n\tint\tindex;\n\tchar\tstyle[256];\n\tfloat\ttime;\n} SAVE_LIGHTSTYLE;\n\nstatic void (__cdecl *pfnSaveGameComment)( char *buffer, int max_length ) = NULL;\n\nstatic TYPEDESCRIPTION gGameHeader[] =\n{\n\tDEFINE_ARRAY( GAME_HEADER, mapName, FIELD_CHARACTER, 32 ),\n\tDEFINE_ARRAY( GAME_HEADER, comment, FIELD_CHARACTER, 80 ),\n\tDEFINE_FIELD( GAME_HEADER, mapCount, FIELD_INTEGER ),\n};\n\nstatic TYPEDESCRIPTION gSaveHeader[] =\n{\n\tDEFINE_FIELD( SAVE_HEADER, skillLevel, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, entityCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, connectionCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, lightStyleCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, time, FIELD_TIME ),\n\tDEFINE_ARRAY( SAVE_HEADER, mapName, FIELD_CHARACTER, 32 ),\n\tDEFINE_ARRAY( SAVE_HEADER, skyName, FIELD_CHARACTER, 32 ),\n\tDEFINE_FIELD( SAVE_HEADER, skyColor_r, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, skyColor_g, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, skyColor_b, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_HEADER, skyVec_x, FIELD_FLOAT ),\n\tDEFINE_FIELD( SAVE_HEADER, skyVec_y, FIELD_FLOAT ),\n\tDEFINE_FIELD( SAVE_HEADER, skyVec_z, FIELD_FLOAT ),\n};\n\nstatic TYPEDESCRIPTION gAdjacency[] =\n{\n\tDEFINE_ARRAY( LEVELLIST, mapName, FIELD_CHARACTER, 32 ),\n\tDEFINE_ARRAY( LEVELLIST, landmarkName, FIELD_CHARACTER, 32 ),\n\tDEFINE_FIELD( LEVELLIST, pentLandmark, FIELD_EDICT ),\n\tDEFINE_FIELD( LEVELLIST, vecLandmarkOrigin, FIELD_VECTOR ),\n};\n\nstatic TYPEDESCRIPTION gLightStyle[] =\n{\n\tDEFINE_FIELD( SAVE_LIGHTSTYLE, index, FIELD_INTEGER ),\n\tDEFINE_ARRAY( SAVE_LIGHTSTYLE, style, FIELD_CHARACTER, 256 ),\n\tDEFINE_FIELD( SAVE_LIGHTSTYLE, time, FIELD_FLOAT ),\n};\n\nstatic TYPEDESCRIPTION gEntityTable[] =\n{\n\tDEFINE_FIELD( ENTITYTABLE, id, FIELD_INTEGER ),\n\tDEFINE_FIELD( ENTITYTABLE, location, FIELD_INTEGER ),\n\tDEFINE_FIELD( ENTITYTABLE, size, FIELD_INTEGER ),\n\tDEFINE_FIELD( ENTITYTABLE, flags, FIELD_INTEGER ),\n\tDEFINE_FIELD( ENTITYTABLE, classname, FIELD_STRING ),\n};\n\nstatic TYPEDESCRIPTION gSaveClient[] =\n{\n\tDEFINE_FIELD( SAVE_CLIENT, decalCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_CLIENT, entityCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_CLIENT, soundCount, FIELD_INTEGER ),\n\tDEFINE_FIELD( SAVE_CLIENT, tempEntsCount, FIELD_INTEGER ),\n\tDEFINE_ARRAY( SAVE_CLIENT, introTrack, FIELD_CHARACTER, 64 ),\n\tDEFINE_ARRAY( SAVE_CLIENT, mainTrack, FIELD_CHARACTER, 64 ),\n\tDEFINE_FIELD( SAVE_CLIENT, trackPosition, FIELD_INTEGER ),\n\t// mods based on HLU SDK disallow usage of FIELD_SHORT\n\tDEFINE_ARRAY( SAVE_CLIENT, viewentity, FIELD_CHARACTER, sizeof( short )),\n\tDEFINE_FIELD( SAVE_CLIENT, wateralpha, FIELD_FLOAT ),\n\tDEFINE_FIELD( SAVE_CLIENT, wateramp, FIELD_FLOAT ),\n};\n\nstatic TYPEDESCRIPTION gDecalEntry[] =\n{\n\tDEFINE_FIELD( decallist_t, position, FIELD_VECTOR ),\n\tDEFINE_ARRAY( decallist_t, name, FIELD_CHARACTER, 64 ),\n\t// mods based on HLU SDK disallow usage of FIELD_SHORT\n\tDEFINE_ARRAY( decallist_t, entityIndex, FIELD_CHARACTER, sizeof( short )),\n\tDEFINE_FIELD( decallist_t, depth, FIELD_CHARACTER ),\n\tDEFINE_FIELD( decallist_t, flags, FIELD_CHARACTER ),\n\tDEFINE_FIELD( decallist_t, scale, FIELD_FLOAT ),\n\tDEFINE_FIELD( decallist_t, impactPlaneNormal, FIELD_VECTOR ),\n\tDEFINE_ARRAY( decallist_t, studio_state, FIELD_CHARACTER, sizeof( modelstate_t )),\n};\n\n// Can use any FIELD type here because only Xash3D games will spawn static entities\nstatic TYPEDESCRIPTION gStaticEntry[] =\n{\n\tDEFINE_FIELD( entity_state_t, messagenum, FIELD_MODELNAME ), // HACKHACK: store model into messagenum\n\tDEFINE_FIELD( entity_state_t, origin, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, angles, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, sequence, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, frame, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, colormap, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, skin, FIELD_SHORT ),\n\tDEFINE_FIELD( entity_state_t, body, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, scale, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, effects, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, framerate, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, mins, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, maxs, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, startpos, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, rendermode, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, renderamt, FIELD_FLOAT ),\n\tDEFINE_ARRAY( entity_state_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )),\n\tDEFINE_FIELD( entity_state_t, renderfx, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, controller, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, blending, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, solid, FIELD_SHORT ),\n\tDEFINE_FIELD( entity_state_t, animtime, FIELD_TIME ),\n\tDEFINE_FIELD( entity_state_t, movetype, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, vuser1, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, vuser2, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, vuser3, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, vuser4, FIELD_VECTOR ),\n\tDEFINE_FIELD( entity_state_t, iuser1, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, iuser2, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, iuser3, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, iuser4, FIELD_INTEGER ),\n\tDEFINE_FIELD( entity_state_t, fuser1, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, fuser2, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, fuser3, FIELD_FLOAT ),\n\tDEFINE_FIELD( entity_state_t, fuser4, FIELD_FLOAT ),\n};\n\nstatic TYPEDESCRIPTION gSoundEntry[] =\n{\n\tDEFINE_ARRAY( soundlist_t, name, FIELD_CHARACTER, 64 ),\n\t// mods based on HLU SDK disallow usage of FIELD_SHORT\n\tDEFINE_ARRAY( soundlist_t, entnum, FIELD_CHARACTER, sizeof( short )),\n\tDEFINE_FIELD( soundlist_t, origin, FIELD_VECTOR ),\n\tDEFINE_FIELD( soundlist_t, volume, FIELD_FLOAT ),\n\tDEFINE_FIELD( soundlist_t, attenuation, FIELD_FLOAT ),\n\tDEFINE_FIELD( soundlist_t, looping, FIELD_BOOLEAN ),\n\tDEFINE_FIELD( soundlist_t, channel, FIELD_CHARACTER ),\n\tDEFINE_FIELD( soundlist_t, pitch, FIELD_CHARACTER ),\n\tDEFINE_FIELD( soundlist_t, wordIndex, FIELD_CHARACTER ),\n\tDEFINE_ARRAY( soundlist_t, samplePos, FIELD_CHARACTER, sizeof( double )),\n\tDEFINE_ARRAY( soundlist_t, forcedEnd, FIELD_CHARACTER, sizeof( double )),\n};\n\nstatic TYPEDESCRIPTION gTempEntvars[] =\n{\n\tDEFINE_ENTITY_FIELD( classname, FIELD_STRING ),\n\tDEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ),\n};\n\nstatic const struct\n{\n\tconst char *mapname;\n\tconst char *titlename;\n} gTitleComments[] =\n{\n\t// default Half-Life map titles\n\t// ordering is important\n\t// strings hw.so| grep T0A0TITLE -B 50 -A 150\n\t{ \"T0A0\", \"#T0A0TITLE\" },\n\t{ \"C0A0\", \"#C0A0TITLE\" },\n\t{ \"C1A0\", \"#C0A1TITLE\" },\n\t{ \"C1A1\", \"#C1A1TITLE\" },\n\t{ \"C1A2\", \"#C1A2TITLE\" },\n\t{ \"C1A3\", \"#C1A3TITLE\" },\n\t{ \"C1A4\", \"#C1A4TITLE\" },\n\t{ \"C2A1\", \"#C2A1TITLE\" },\n\t{ \"C2A2\", \"#C2A2TITLE\" },\n\t{ \"C2A3\", \"#C2A3TITLE\" },\n\t{ \"C2A4D\", \"#C2A4TITLE2\" },\n\t{ \"C2A4E\", \"#C2A4TITLE2\" },\n\t{ \"C2A4F\", \"#C2A4TITLE2\" },\n\t{ \"C2A4G\", \"#C2A4TITLE2\" },\n\t{ \"C2A4\", \"#C2A4TITLE1\" },\n\t{ \"C2A5\", \"#C2A5TITLE\" },\n\t{ \"C3A1\", \"#C3A1TITLE\" },\n\t{ \"C3A2\", \"#C3A2TITLE\" },\n\t{ \"C4A1A\", \"#C4A1ATITLE\" },\n\t{ \"C4A1B\", \"#C4A1ATITLE\" },\n\t{ \"C4A1C\", \"#C4A1ATITLE\" },\n\t{ \"C4A1D\", \"#C4A1ATITLE\" },\n\t{ \"C4A1E\", \"#C4A1ATITLE\" },\n\t{ \"C4A1\", \"#C4A1TITLE\" },\n\t{ \"C4A2\", \"#C4A2TITLE\" },\n\t{ \"C4A3\", \"#C4A3TITLE\" },\n\t{ \"C5A1\", \"#C5TITLE\" },\n\t{ \"OFBOOT\", \"#OF_BOOT0TITLE\" },\n\t{ \"OF0A\", \"#OF1A1TITLE\" },\n\t{ \"OF1A1\", \"#OF1A3TITLE\" },\n\t{ \"OF1A2\", \"#OF1A3TITLE\" },\n\t{ \"OF1A3\", \"#OF1A3TITLE\" },\n\t{ \"OF1A4\", \"#OF1A3TITLE\" },\n\t{ \"OF1A\", \"#OF1A5TITLE\" },\n\t{ \"OF2A1\", \"#OF2A1TITLE\" },\n\t{ \"OF2A2\", \"#OF2A1TITLE\" },\n\t{ \"OF2A3\", \"#OF2A1TITLE\" },\n\t{ \"OF2A\", \"#OF2A4TITLE\" },\n\t{ \"OF3A1\", \"#OF3A1TITLE\" },\n\t{ \"OF3A2\", \"#OF3A1TITLE\" },\n\t{ \"OF3A\", \"#OF3A3TITLE\" },\n\t{ \"OF4A1\", \"#OF4A1TITLE\" },\n\t{ \"OF4A2\", \"#OF4A1TITLE\" },\n\t{ \"OF4A3\", \"#OF4A1TITLE\" },\n\t{ \"OF4A\", \"#OF4A4TITLE\" },\n\t{ \"OF5A\", \"#OF5A1TITLE\" },\n\t{ \"OF6A1\", \"#OF6A1TITLE\" },\n\t{ \"OF6A2\", \"#OF6A1TITLE\" },\n\t{ \"OF6A3\", \"#OF6A1TITLE\" },\n\t{ \"OF6A4b\", \"#OF6A4TITLE\" },\n\t{ \"OF6A4\", \"#OF6A4TITLE\" },\n\t{ \"OF6A5\", \"#OF6A4TITLE\" },\n\t{ \"OF6A\", \"#OF6A4TITLE\" },\n\t{ \"OF7A\", \"#OF7A0TITLE\" },\n\t{ \"ba_tram\", \"#BA_TRAMTITLE\" },\n\t{ \"ba_security\", \"#BA_SECURITYTITLE\" },\n\t{ \"ba_main\", \"#BA_SECURITYTITLE\" },\n\t{ \"ba_elevator\", \"#BA_SECURITYTITLE\" },\n\t{ \"ba_canal\", \"#BA_CANALSTITLE\" },\n\t{ \"ba_yard\", \"#BA_YARDTITLE\" },\n\t{ \"ba_xen\", \"#BA_XENTITLE\" },\n\t{ \"ba_hazard\", \"#BA_HAZARD\" },\n\t{ \"ba_power\", \"#BA_POWERTITLE\" },\n\t{ \"ba_teleport1\", \"#BA_POWERTITLE\" },\n\t{ \"ba_teleport\", \"#BA_TELEPORTTITLE\" },\n\t{ \"ba_outro\", \"#BA_OUTRO\" },\n};\n\n/*\n=============\nSaveBuildComment\n\nbuild commentary for each savegame\ntypically it writes world message and level time\n=============\n*/\nstatic void SaveBuildComment( char *text, int maxlength )\n{\n\tstring      comment;\n\tconst char *pName = NULL;\n\n\ttext[0] = '\\0'; // clear\n\n\tif( pfnSaveGameComment != NULL )\n\t{\n\t\t// get save comment from gamedll\n\t\tpfnSaveGameComment( comment, MAX_STRING );\n\t\tpName = comment;\n\t}\n\telse\n\t{\n\t\tsize_t i;\n\t\tconst char *mapname = STRING( svgame.globals->mapname );\n\n\t\tfor( i = 0; i < ARRAYSIZE( gTitleComments ); i++ )\n\t\t{\n\t\t\t// compare if strings are equal at beginning\n\t\t\tsize_t len = strlen( gTitleComments[i].mapname );\n\t\t\tif( !Q_strnicmp( mapname, gTitleComments[i].mapname, len ))\n\t\t\t{\n\t\t\t\tpName = gTitleComments[i].titlename;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !pName )\n\t\t{\n\t\t\tif( svgame.edicts->v.message != 0 )\n\t\t\t{\n\t\t\t\t// trying to extract message from the world\n\t\t\t\tpName = STRING( svgame.edicts->v.message );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// or use mapname\n\t\t\t\tpName = STRING( svgame.globals->mapname );\n\t\t\t}\n\t\t}\n\t}\n\n\tQ_snprintf( text, maxlength, \"%-64.64s %02d:%02d\", pName, (int)(sv.time / 60.0 ), (int)fmod( sv.time, 60.0 ));\n}\n\n/*\n=============\nDirectoryCount\n\ncounting all the files with HL1-HL3 extension\nin save folder\n=============\n*/\nstatic int DirectoryCount( const char *pPath )\n{\n\tint\tcount;\n\tsearch_t\t*t;\n\n\tt = FS_Search( pPath, true, true );\t// lookup only in gamedir\n\tif( !t ) return 0; // empty\n\n\tcount = t->numfilenames;\n\tMem_Free( t );\n\n\treturn count;\n}\n\n/*\n=============\nInitEntityTable\n\nreserve space for ETABLE's\n=============\n*/\nstatic void InitEntityTable( SAVERESTOREDATA *pSaveData, int entityCount )\n{\n\tENTITYTABLE\t*pTable;\n\tint\t\ti;\n\n\tpSaveData->pTable = Mem_Calloc( host.mempool, sizeof( ENTITYTABLE ) * entityCount );\n\tpSaveData->tableCount = entityCount;\n\n\t// setup entitytable\n\tfor( i = 0; i < entityCount; i++ )\n\t{\n\t\tpTable = &pSaveData->pTable[i];\n\t\tpTable->pent = EDICT_NUM( i );\n\t\tpTable->id = i;\n\t}\n}\n\n/*\n=============\nEntryInTable\n\ncheck level in transition list\n=============\n*/\nstatic int EntryInTable( SAVERESTOREDATA *pSaveData, const char *pMapName, int index )\n{\n\tint\ti;\n\n\tfor( i = index + 1; i < pSaveData->connectionCount; i++ )\n\t{\n\t\tif ( !Q_stricmp( pSaveData->levelList[i].mapName, pMapName ))\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\n/*\n=============\nEdictFromTable\n\nget edict from table\n=============\n*/\nstatic edict_t *EdictFromTable( SAVERESTOREDATA *pSaveData, int entityIndex )\n{\n\tif( pSaveData && pSaveData->pTable )\n\t{\n\t\tentityIndex = bound( 0, entityIndex, pSaveData->tableCount - 1 );\n\t\treturn pSaveData->pTable[entityIndex].pent;\n\t}\n\n\treturn NULL;\n}\n\n/*\n=============\nLandmarkOrigin\n\nfind global offset for a given landmark\n=============\n*/\nstatic void LandmarkOrigin( SAVERESTOREDATA *pSaveData, vec3_t output, const char *pLandmarkName )\n{\n\tint\ti;\n\n\tfor( i = 0; i < pSaveData->connectionCount; i++ )\n\t{\n\t\tif( !Q_strcmp( pSaveData->levelList[i].landmarkName, pLandmarkName ))\n\t\t{\n\t\t\tVectorCopy( pSaveData->levelList[i].vecLandmarkOrigin, output );\n\t\t\treturn;\n\t\t}\n\t}\n\n\tVectorClear( output );\n}\n\n/*\n=============\nEntityInSolid\n\nsome moved edicts on a next level cause stuck\noutside of world. Find them and remove\n=============\n*/\nstatic int EntityInSolid( edict_t *pent )\n{\n\tedict_t\t*aiment = pent->v.aiment;\n\tvec3_t\tpoint;\n\n\t// if you're attached to a client, always go through\n\tif( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( aiment ) && FBitSet( aiment->v.flags, FL_CLIENT ))\n\t\treturn 0;\n\n\tVectorAverage( pent->v.absmin, pent->v.absmax, point );\n\tsvs.groupmask = pent->v.groupinfo;\n\n\treturn (SV_PointContents( point ) == CONTENTS_SOLID);\n}\n\n/*\n=============\nClearSaveDir\n\nremove all the temp files HL1-HL3\n(it will be extracted again from another .sav file)\n=============\n*/\nstatic void ClearSaveDir( void )\n{\n\tsearch_t\t*t;\n\tint\ti;\n\n\t// just delete all HL? files\n\tt = FS_Search( DEFAULT_SAVE_DIRECTORY \"*.HL?\", true, true );\n\tif( !t ) return; // already empty\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t\tFS_Delete( t->filenames[i] );\n\n\tMem_Free( t );\n}\n\n/*\n=============\nIsValidSave\n\nsavegame is allowed?\n=============\n*/\nstatic int IsValidSave( void )\n{\n\tif( !svs.initialized || sv.state != ss_active )\n\t{\n\t\tCon_Printf( \"Not playing a local game.\\n\" );\n\t\treturn 0;\n\t}\n\n\t// ignore autosave during background\n\tif( sv.background || UI_CreditsActive( ))\n\t\treturn 0;\n\n\tif( svgame.physFuncs.SV_AllowSaveGame != NULL )\n\t{\n\t\tif( !svgame.physFuncs.SV_AllowSaveGame( ))\n\t\t{\n\t\t\tCon_Printf( \"Savegame is not allowed.\\n\" );\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif( !CL_Active( ))\n\t{\n\t\tCon_Printf( \"Can't save if not active.\\n\" );\n\t\treturn 0;\n\t}\n\n\tif( CL_IsIntermission( ))\n\t{\n\t\tCon_Printf( \"Can't save during intermission.\\n\" );\n\t\treturn 0;\n\t}\n\n\tif( svs.maxclients != 1 )\n\t{\n\t\tCon_Printf( \"Can't save multiplayer games.\\n\" );\n\t\treturn 0;\n\t}\n\n\tif( svs.clients && svs.clients[0].state == cs_spawned )\n\t{\n\t\tedict_t\t*pl = svs.clients[0].edict;\n\n\t\tif( !pl )\n\t\t{\n\t\t\tCon_Printf( \"Can't savegame without a player!\\n\" );\n\t\t\treturn 0;\n\t\t}\n\n\t\tif( pl->v.deadflag || pl->v.health <= 0.0f )\n\t\t{\n\t\t\tCon_Printf( \"Can't savegame with a dead player\\n\" );\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Passed all checks, it's ok to save\n\t\treturn 1;\n\t}\n\n\tCon_Printf( \"Can't savegame without a client!\\n\" );\n\n\treturn 0;\n}\n\n/*\n=============\nAgeSaveList\n\nscroll the name list down\n=============\n*/\nstatic void AgeSaveList( const char *pName, int count )\n{\n\tchar\tnewName[MAX_OSPATH], oldName[MAX_OSPATH];\n\tchar\tnewShot[MAX_OSPATH], oldShot[MAX_OSPATH];\n\n\t// delete last quick/autosave (e.g. quick05.sav)\n\tQ_snprintf( newName, sizeof( newName ), DEFAULT_SAVE_DIRECTORY \"%s%02d.sav\", pName, count );\n\tQ_snprintf( newShot, sizeof( newShot ), DEFAULT_SAVE_DIRECTORY \"%s%02d.bmp\", pName, count );\n\n\t// only delete from game directory, basedir is read-only\n\tFS_Delete( newName );\n\tFS_Delete( newShot );\n\n#if !XASH_DEDICATED\n\t// unloading the shot footprint\n\tGL_FreeImage( newShot );\n#endif // XASH_DEDICATED\n\n\twhile( count > 0 )\n\t{\n\t\tif( count == 1 )\n\t\t{\n\t\t\t// quick.sav\n\t\t\tQ_snprintf( oldName, sizeof( oldName ), DEFAULT_SAVE_DIRECTORY \"%s.sav\", pName );\n\t\t\tQ_snprintf( oldShot, sizeof( oldShot ), DEFAULT_SAVE_DIRECTORY \"%s.bmp\", pName );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// quick04.sav, etc.\n\t\t\tQ_snprintf( oldName, sizeof( oldName ), DEFAULT_SAVE_DIRECTORY \"%s%02d.sav\", pName, count - 1 );\n\t\t\tQ_snprintf( oldShot, sizeof( oldShot ), DEFAULT_SAVE_DIRECTORY \"%s%02d.bmp\", pName, count - 1 );\n\t\t}\n\n\t\tQ_snprintf( newName, sizeof( newName ), DEFAULT_SAVE_DIRECTORY \"%s%02d.sav\", pName, count );\n\t\tQ_snprintf( newShot, sizeof( newShot ), DEFAULT_SAVE_DIRECTORY \"%s%02d.bmp\", pName, count );\n\n#if !XASH_DEDICATED\n\t\t// unloading the oldshot footprint too\n\t\tGL_FreeImage( oldShot );\n#endif // XASH_DEDICATED\n\n\t\t// scroll the name list down (e.g. rename quick04.sav to quick05.sav)\n\t\tFS_Rename( oldName, newName );\n\t\tFS_Rename( oldShot, newShot );\n\t\tcount--;\n\t}\n}\n\n/*\n=============\nDirectoryCopy\n\nput the HL1-HL3 files into .sav file\n=============\n*/\nstatic void DirectoryCopy( const char *pPath, file_t *pFile )\n{\n\tchar\tszName[MAX_OSPATH];\n\tint\ti, fileSize;\n\tfile_t\t*pCopy;\n\tsearch_t\t*t;\n\n\tt = FS_Search( pPath, true, true );\n\tif( !t ) return; // nothing to copy ?\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t{\n\t\tpCopy = FS_Open( t->filenames[i], \"rb\", true );\n\t\tfileSize = FS_FileLength( pCopy );\n\n\t\tmemset( szName, 0, sizeof( szName )); // clearing the string to prevent garbage in output file\n\t\tQ_strncpy( szName, COM_FileWithoutPath( t->filenames[i] ), sizeof( szName ));\n\t\tFS_Write( pFile, szName, MAX_OSPATH );\n\t\tFS_Write( pFile, &fileSize, sizeof( int ));\n\t\tFS_FileCopy( pFile, pCopy, fileSize );\n\t\tFS_Close( pCopy );\n\t}\n\tMem_Free( t );\n}\n\n/*\n=============\nDirectoryExtract\n\nextract the HL1-HL3 files from the .sav file\n=============\n*/\nstatic void DirectoryExtract( file_t *pFile, int fileCount )\n{\n\tchar\tszName[MAX_OSPATH];\n\tchar\tfileName[MAX_OSPATH];\n\tint\ti, fileSize;\n\tfile_t\t*pCopy;\n\n\tfor( i = 0; i < fileCount; i++ )\n\t{\n\t\t// filename can only be as long as a map name + extension\n\t\tFS_Read( pFile, szName, MAX_OSPATH );\n\t\tFS_Read( pFile, &fileSize, sizeof( int ));\n\t\tQ_snprintf( fileName, sizeof( fileName ), DEFAULT_SAVE_DIRECTORY \"%s\", szName );\n\t\tCOM_FixSlashes( fileName );\n\n\t\tpCopy = FS_Open( fileName, \"wb\", true );\n\t\tFS_FileCopy( pCopy, pFile, fileSize );\n\t\tFS_Close( pCopy );\n\t}\n}\n\n/*\n=============\nSaveInit\n\ninitialize global save-restore buffer\n=============\n*/\nstatic SAVERESTOREDATA *SaveInit( int size, int tokenCount )\n{\n\tSAVERESTOREDATA\t*pSaveData;\n\n\tpSaveData = Mem_Calloc( host.mempool, sizeof( SAVERESTOREDATA ) + size );\n\tpSaveData->pTokens = (char **)Mem_Calloc( host.mempool, tokenCount * sizeof( char* ));\n\tpSaveData->tokenCount = tokenCount;\n\n\tpSaveData->pBaseData = (char *)(pSaveData + 1); // skip the save structure);\n\tpSaveData->pCurrentData = pSaveData->pBaseData; // reset the pointer\n\tpSaveData->bufferSize = size;\n\n\tpSaveData->time = svgame.globals->time;\t// Use DLL time\n\n\t// shared with dlls\n\tsvgame.globals->pSaveData = pSaveData;\n\n\treturn pSaveData;\n}\n\n/*\n=============\nSaveClear\n\nclearing buffer for reuse\n=============\n*/\nstatic void SaveClear( SAVERESTOREDATA *pSaveData )\n{\n\tmemset( pSaveData->pTokens, 0, pSaveData->tokenCount * sizeof( char* ));\n\n\tpSaveData->pBaseData = (char *)(pSaveData + 1); // skip the save structure);\n\tpSaveData->pCurrentData = pSaveData->pBaseData; // reset the pointer\n\tpSaveData->time = svgame.globals->time;\t// Use DLL time\n\tpSaveData->tokenSize = 0;\t// reset the hashtable\n\tpSaveData->size = 0;\t// reset the pointer\n\n\t// shared with dlls\n\tsvgame.globals->pSaveData = pSaveData;\n}\n\n/*\n=============\nSaveFinish\n\nrelease global save-restore buffer\n=============\n*/\nstatic void SaveFinish( SAVERESTOREDATA *pSaveData )\n{\n\tif( !pSaveData ) return;\n\n\tif( pSaveData->pTokens )\n\t{\n\t\tMem_Free( pSaveData->pTokens );\n\t\tpSaveData->pTokens = NULL;\n\t\tpSaveData->tokenCount = 0;\n\t}\n\n\tif( pSaveData->pTable )\n\t{\n\t\tMem_Free( pSaveData->pTable );\n\t\tpSaveData->pTable = NULL;\n\t\tpSaveData->tableCount = 0;\n\t}\n\n\tsvgame.globals->pSaveData = NULL;\n\tMem_Free( pSaveData );\n}\n\n/*\n=============\nStoreHashTable\n\nwrite the stringtable into file\n=============\n*/\nstatic char *StoreHashTable( SAVERESTOREDATA *pSaveData )\n{\n\tchar\t*pTokenData = pSaveData->pCurrentData;\n\tint\ti;\n\n\t// Write entity string token table\n\tif( pSaveData->pTokens )\n\t{\n\t\tfor( i = 0; i < pSaveData->tokenCount; i++ )\n\t\t{\n\t\t\tconst char *pszToken = pSaveData->pTokens[i] ? pSaveData->pTokens[i] : \"\";\n\n\t\t\t// just copy the token byte-by-byte\n\t\t\twhile( *pszToken )\n\t\t\t\t*pSaveData->pCurrentData++ = *pszToken++;\n\t\t\t*pSaveData->pCurrentData++ = 0; // Write the term\n\t\t}\n\t}\n\n\tpSaveData->tokenSize = pSaveData->pCurrentData - pTokenData;\n\n\treturn pTokenData;\n}\n\n/*\n=============\nBuildHashTable\n\nbuild the stringtable from buffer\n=============\n*/\nstatic void BuildHashTable( SAVERESTOREDATA *pSaveData, file_t *pFile )\n{\n\tchar\t*pszTokenList = pSaveData->pBaseData;\n\tint\ti;\n\n\t// Parse the symbol table\n\tif( pSaveData->tokenSize > 0 )\n\t{\n\t\tFS_Read( pFile, pszTokenList, pSaveData->tokenSize );\n\n\t\t// make sure the token strings pointed to by the pToken hashtable.\n\t\tfor( i = 0; i < pSaveData->tokenCount; i++ )\n\t\t{\n\t\t\tpSaveData->pTokens[i] = *pszTokenList ? pszTokenList : NULL;\n\t\t\twhile( *pszTokenList++ );\t// Find next token (after next null)\n\t\t}\n\t}\n\n\t// rebase the data pointer\n\tpSaveData->pBaseData = pszTokenList;\t// pszTokenList now points after token data\n\tpSaveData->pCurrentData = pSaveData->pBaseData;\n}\n\n/*\n=============\nGetClientDataSize\n\ng-cont: this routine is redundant\ni'm write it just for more readable code\n=============\n*/\nstatic int GetClientDataSize( const char *level )\n{\n\tint\ttokenCount, tokenSize;\n\tint\tsize, id, version;\n\tchar\tname[MAX_QPATH];\n\tfile_t\t*pFile;\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL2\", level );\n\n\tif(( pFile = FS_Open( name, \"rb\", true )) == NULL )\n\t\treturn 0;\n\n\tFS_Read( pFile, &id, sizeof( id ));\n\tif( id != SAVEGAME_HEADER )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn 0;\n\t}\n\n\tFS_Read( pFile, &version, sizeof( version ));\n\tif( version != CLIENT_SAVEGAME_VERSION )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn 0;\n\t}\n\n\tFS_Read( pFile, &size, sizeof( int ));\n\tFS_Read( pFile, &tokenCount, sizeof( int ));\n\tFS_Read( pFile, &tokenSize, sizeof( int ));\n\tFS_Close( pFile );\n\n\treturn ( size + tokenSize );\n}\n\n/*\n=============\nLoadSaveData\n\nfill the save resore buffer\nparse hash strings\n=============\n*/\nstatic SAVERESTOREDATA *LoadSaveData( const char *level )\n{\n\tint\t\ttokenSize, tableCount;\n\tint\t\tsize, tokenCount;\n\tchar\t\tname[MAX_OSPATH];\n\tint\t\tid, version;\n\tint\t\tclientSize;\n\tSAVERESTOREDATA\t*pSaveData;\n\tint\t\ttotalSize;\n\tfile_t\t\t*pFile;\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL1\", level );\n\tCon_Printf( \"Loading game from %s...\\n\", name );\n\n\tif(( pFile = FS_Open( name, \"rb\", true )) == NULL )\n\t{\n\t\tCon_Printf( S_ERROR \"Couldn't open save data file %s.\\n\", name );\n\t\treturn NULL;\n\t}\n\n\t// Read the header\n\tFS_Read( pFile, &id, sizeof( int ));\n\tFS_Read( pFile, &version, sizeof( int ));\n\n\t// is this a valid save?\n\tif( id != SAVEFILE_HEADER || version != SAVEGAME_VERSION )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn NULL;\n\t}\n\n\t// Read the sections info and the data\n\tFS_Read( pFile, &size, sizeof( int ));\t\t// total size of all data to initialize read buffer\n\tFS_Read( pFile, &tableCount, sizeof( int ));\t// entities count to right initialize entity table\n\tFS_Read( pFile, &tokenCount, sizeof( int ));\t// num hash tokens to prepare token table\n\tFS_Read( pFile, &tokenSize, sizeof( int ));\t// total size of hash tokens\n\n\t// determine highest size of seve-restore buffer\n\t// because it's used twice: for HL1 and HL2 restore\n\tclientSize = GetClientDataSize( level );\n\ttotalSize = Q_max( clientSize, ( size + tokenSize ));\n\n\t// init the read buffer\n\tpSaveData = SaveInit( totalSize, tokenCount );\n\n\tQ_strncpy( pSaveData->szCurrentMapName, level, sizeof( pSaveData->szCurrentMapName ));\n\tpSaveData->tableCount = tableCount;\t\t// count ETABLE entries\n\tpSaveData->tokenCount = tokenCount;\n\tpSaveData->tokenSize = tokenSize;\n\n\t// Parse the symbol table\n\tBuildHashTable( pSaveData, pFile );\n\n\t// Set up the restore basis\n\tpSaveData->fUseLandmark = true;\n\tpSaveData->time = 0.0f;\n\n\t// now reading all the rest of data\n\tFS_Read( pFile, pSaveData->pBaseData, size );\n\tFS_Close( pFile ); // data is sucessfully moved into SaveRestore buffer (ETABLE will be init later)\n\n\treturn pSaveData;\n}\n\n/*\n=============\nParseSaveTables\n\nreading global data, setup ETABLE's\n=============\n*/\nstatic void ParseSaveTables( SAVERESTOREDATA *pSaveData, SAVE_HEADER *pHeader, int updateGlobals )\n{\n\tSAVE_LIGHTSTYLE\tlight;\n\tint\t\ti;\n\n\t// Re-base the savedata since we re-ordered the entity/table / restore fields\n\tInitEntityTable( pSaveData, pSaveData->tableCount );\n\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t{\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"ETABLE\", &pSaveData->pTable[i], gEntityTable, ARRAYSIZE( gEntityTable ));\n\t\tpSaveData->pTable[i].pent = NULL;\n\t}\n\n\tpSaveData->pBaseData = pSaveData->pCurrentData;\n\tpSaveData->size = 0;\n\n\t// process SAVE_HEADER\n\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"Save Header\", pHeader, gSaveHeader, ARRAYSIZE( gSaveHeader ));\n\n\tpSaveData->connectionCount = pHeader->connectionCount;\n\tVectorClear( pSaveData->vecLandmarkOffset );\n\tpSaveData->time = pHeader->time;\n\tpSaveData->fUseLandmark = true;\n\n\t// read adjacency list\n\tfor( i = 0; i < pSaveData->connectionCount; i++ )\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"ADJACENCY\", &pSaveData->levelList[i], gAdjacency, ARRAYSIZE( gAdjacency ));\n\n\tif( updateGlobals )\n\t\tmemset( sv.lightstyles, 0, sizeof( sv.lightstyles ));\n\n\tfor( i = 0; i < pHeader->lightStyleCount; i++ )\n\t{\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"LIGHTSTYLE\", &light, gLightStyle, ARRAYSIZE( gLightStyle ));\n\t\tif( updateGlobals ) SV_SetLightStyle( light.index, light.style, light.time );\n\t}\n}\n\n/*\n=============\nEntityPatchWrite\n\nwrite out the list of entities that are no longer in the save file for this level\n(they've been moved to another level)\n=============\n*/\nstatic void EntityPatchWrite( SAVERESTOREDATA *pSaveData, const char *level )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti, size = 0;\n\tfile_t\t*pFile;\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL3\", level );\n\n\tif(( pFile = FS_Open( name, \"wb\", true )) == NULL )\n\t\treturn;\n\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t{\n\t\tif( FBitSet( pSaveData->pTable[i].flags, FENTTABLE_REMOVED ))\n\t\t\tsize++;\n\t}\n\n\t// patch count\n\tFS_Write( pFile, &size, sizeof( int ));\n\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t{\n\t\tif( FBitSet( pSaveData->pTable[i].flags, FENTTABLE_REMOVED ))\n\t\t\tFS_Write( pFile, &i, sizeof( int ));\n\t}\n\n\tFS_Close( pFile );\n}\n\n/*\n=============\nEntityPatchRead\n\nread the list of entities that are no longer in the save file for this level\n(they've been moved to another level)\n=============\n*/\nstatic void EntityPatchRead( SAVERESTOREDATA *pSaveData, const char *level )\n{\n\tchar\tname[MAX_QPATH];\n\tint\ti, size, entityId;\n\tfile_t\t*pFile;\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL3\", level );\n\n\tif(( pFile = FS_Open( name, \"rb\", true )) == NULL )\n\t\treturn;\n\n\t// patch count\n\tFS_Read( pFile, &size, sizeof( int ));\n\n\tfor( i = 0; i < size; i++ )\n\t{\n\t\tFS_Read( pFile, &entityId, sizeof( int ));\n\t\tpSaveData->pTable[entityId].flags = FENTTABLE_REMOVED;\n\t}\n\n\tFS_Close( pFile );\n}\n\n/*\n=============\nRestoreDecal\n\nrestore decal\\move across transition\n=============\n*/\nstatic void RestoreDecal( SAVERESTOREDATA *pSaveData, decallist_t *entry, qboolean adjacent )\n{\n\tint\tdecalIndex, entityIndex = 0;\n\tint\tflags = entry->flags;\n\tint\tmodelIndex = 0;\n\tedict_t\t*pEdict;\n\n\t// never move permanent decals\n\tif( adjacent && FBitSet( flags, FDECAL_PERMANENT ))\n\t\treturn;\n\n\t// restore entity and model index\n\tpEdict = EdictFromTable( pSaveData, entry->entityIndex );\n\n\tif( SV_RestoreCustomDecal( entry, pEdict, adjacent ))\n\t\treturn; // decal was sucessfully restored at the game-side\n\n\t// studio decals are handled at game-side\n\tif( FBitSet( flags, FDECAL_STUDIO ))\n\t\treturn;\n\n\tif( SV_IsValidEdict( pEdict ))\n\t\tmodelIndex = pEdict->v.modelindex;\n\n\tif( SV_IsValidEdict( pEdict ))\n\t\tentityIndex = NUM_FOR_EDICT( pEdict );\n\n\tdecalIndex = pfnDecalIndex( entry->name );\n\n\t// this can happens if brush entity from previous level was turned into world geometry\n\tif( adjacent && entry->entityIndex != 0 && !SV_IsValidEdict( pEdict ))\n\t{\n\t\tvec3_t\ttestspot, testend;\n\t\ttrace_t\ttr;\n\n\t\tCon_Printf( S_ERROR \"RestoreDecal: couldn't restore entity index %i\\n\", entry->entityIndex );\n\n\t\tVectorCopy( entry->position, testspot );\n\t\tVectorMA( testspot, 5.0f, entry->impactPlaneNormal, testspot );\n\n\t\tVectorCopy( entry->position, testend );\n\t\tVectorMA( testend, -5.0f, entry->impactPlaneNormal, testend );\n\n\t\ttr = SV_Move( testspot, vec3_origin, vec3_origin, testend, MOVE_NOMONSTERS, NULL, false );\n\n\t\t// NOTE: this code may does wrong result on moving brushes e.g. func_tracktrain\n\t\tif( tr.fraction != 1.0f && !tr.allsolid )\n\t\t{\n\t\t\t// check impact plane normal\n\t\t\tfloat\tdot = DotProduct( entry->impactPlaneNormal, tr.plane.normal );\n\n\t\t\tif( dot >= 0.95f )\n\t\t\t{\n\t\t\t\tentityIndex = pfnIndexOfEdict( tr.ent );\n\t\t\t\tif( entityIndex > 0 ) modelIndex = tr.ent->v.modelindex;\n\t\t\t\tSV_CreateDecal( &sv.signon, tr.endpos, decalIndex, entityIndex, modelIndex, flags, entry->scale );\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\t// global entity is exist on new level so we can apply decal in local space\n\t\tSV_CreateDecal( &sv.signon, entry->position, decalIndex, entityIndex, modelIndex, flags, entry->scale );\n\t}\n}\n\n/*\n=============\nRestoreSound\n\ncontinue playing sound from saved position\n=============\n*/\nstatic void RestoreSound( SAVERESTOREDATA *pSaveData, soundlist_t *snd )\n{\n\tedict_t\t*ent = EdictFromTable( pSaveData, snd->entnum );\n\tint\tflags = SND_RESTORE_POSITION;\n\n\t// this can happens if serialized map contain 4096 static decals...\n\tif( MSG_GetNumBytesLeft( &sv.signon ) < 36 )\n\t\treturn;\n\n\tif( !snd->looping )\n\t\tSetBits( flags, SND_STOP_LOOPING );\n\n\tif( SV_BuildSoundMsg( &sv.signon, ent, snd->channel, snd->name, snd->volume * 255, snd->attenuation, flags, snd->pitch, snd->origin ))\n\t{\n\t\t// write extradata for svc_restoresound\n\t\tMSG_WriteByte( &sv.signon, snd->wordIndex );\n\t\tMSG_WriteBytes( &sv.signon, &snd->samplePos, sizeof( snd->samplePos ));\n\t\tMSG_WriteBytes( &sv.signon, &snd->forcedEnd, sizeof( snd->forcedEnd ));\n\t}\n}\n\n/*\n=============\nSaveClientState\n\nwrite out the list of premanent decals for this level\n=============\n*/\nstatic void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int changelevel )\n{\n\tsoundlist_t\tsoundInfo[MAX_CHANNELS];\n\tsv_client_t\t*cl = svs.clients;\n\tchar\t\tname[MAX_QPATH];\n\tint\t\ti, id, version;\n\tchar\t\t*pTokenData;\n\tdecallist_t\t*decalList = NULL;\n\tSAVE_CLIENT\theader = { 0 };\n\tfile_t\t\t*pFile;\n\n\t// clearing the saving buffer to reuse\n\tSaveClear( pSaveData );\n\n\theader.entityCount = sv.num_static_entities;\n\n\t// initialize client header\n#if !XASH_DEDICATED\n\tif( !Host_IsDedicated( ))\n\t{\n\t\t// g-cont. add space for studiodecals if present\n\t\tdecalList = (decallist_t *)Mem_Calloc( host.mempool, sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 );\n\n\t\theader.decalCount = ref.dllFuncs.R_CreateDecalList( decalList );\n\n\t\tif( !changelevel ) // sounds won't going across transition\n\t\t{\n\t\t\theader.soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS );\n\n\t\t\t// music not reqiured to save position: it's just continue playing on a next level\n\t\t\tS_StreamGetCurrentState(\n\t\t\t\theader.introTrack, sizeof( header.introTrack ),\n\t\t\t\theader.mainTrack, sizeof( header.mainTrack ),\n\t\t\t\t&header.trackPosition );\n\t\t}\n\t}\n#endif // XASH_DEDICATED\n\n\t// save viewentity to allow camera works after save\\restore\n\tif( SV_IsValidEdict( cl->pViewEntity ) && cl->pViewEntity != cl->edict )\n\t\theader.viewentity = NUM_FOR_EDICT( cl->pViewEntity );\n\n\theader.wateralpha = sv_wateralpha.value;\n\theader.wateramp = sv_wateramp.value;\n\n\t// Store the client header\n\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"ClientHeader\", &header, gSaveClient, ARRAYSIZE( gSaveClient ));\n\n\t// store decals\n\tfor( i = 0; decalList != NULL && i < header.decalCount; i++ )\n\t{\n\t\t// NOTE: apply landmark offset only for brush entities without origin brushes\n\t\tif( pSaveData->fUseLandmark && FBitSet( decalList[i].flags, FDECAL_USE_LANDMARK ))\n\t\t\tVectorSubtract( decalList[i].position, pSaveData->vecLandmarkOffset, decalList[i].position );\n\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"DECALLIST\", &decalList[i], gDecalEntry, ARRAYSIZE( gDecalEntry ));\n\t}\n\n\tif( decalList )\n\t\tMem_Free( decalList );\n\n\t// write client entities\n\tfor( i = 0; i < header.entityCount; i++ )\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"STATICENTITY\", &svs.static_entities[i], gStaticEntry, ARRAYSIZE( gStaticEntry ));\n\n\t// write sounds\n\tfor( i = 0; i < header.soundCount; i++ )\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"SOUNDLIST\", &soundInfo[i], gSoundEntry, ARRAYSIZE( gSoundEntry ));\n\n\t// Write entity string token table\n\tpTokenData = StoreHashTable( pSaveData );\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL2\", level );\n\n\t// output to disk\n\tif(( pFile = FS_Open( name, \"wb\", true )) == NULL )\n\t\treturn; // something bad is happens\n\n\tversion = CLIENT_SAVEGAME_VERSION;\n\tid = SAVEGAME_HEADER;\n\n\tFS_Write( pFile, &id, sizeof( id ));\n\tFS_Write( pFile, &version, sizeof( version ));\n\tFS_Write( pFile, &pSaveData->size, sizeof( int )); // does not include token table\n\n\t// write out the tokens first so we can load them before we load the entities\n\tFS_Write( pFile, &pSaveData->tokenCount, sizeof( int ));\n\tFS_Write( pFile, &pSaveData->tokenSize, sizeof( int ));\n\tFS_Write( pFile, pTokenData, pSaveData->tokenSize );\n\tFS_Write( pFile, pSaveData->pBaseData, pSaveData->size ); // header and globals\n\tFS_Close( pFile );\n}\n\n/*\n=============\nLoadClientState\n\nread the list of decals and reapply them again\n=============\n*/\nstatic void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboolean changelevel, qboolean adjacent )\n{\n\tint\t\ttokenCount, tokenSize;\n\tint\t\ti, size, id, version;\n\tsv_client_t\t*cl = svs.clients;\n\tchar\t\tname[MAX_QPATH];\n\tsoundlist_t\tsoundEntry;\n\tdecallist_t\tdecalEntry;\n\tSAVE_CLIENT\theader;\n\tfile_t\t\t*pFile;\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL2\", level );\n\n\tif(( pFile = FS_Open( name, \"rb\", true )) == NULL )\n\t\treturn; // something bad is happens\n\n\tFS_Read( pFile, &id, sizeof( id ));\n\tif( id != SAVEGAME_HEADER )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn;\n\t}\n\n\tFS_Read( pFile, &version, sizeof( version ));\n\tif( version != CLIENT_SAVEGAME_VERSION )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn;\n\t}\n\n\tFS_Read( pFile, &size, sizeof( int ));\n\tFS_Read( pFile, &tokenCount, sizeof( int ));\n\tFS_Read( pFile, &tokenSize, sizeof( int ));\n\n\t// sanity check\n\tASSERT( pSaveData->bufferSize >= ( size + tokenSize ));\n\n\t// clearing the restore buffer to reuse\n\tSaveClear( pSaveData );\n\tpSaveData->tokenCount = tokenCount;\n\tpSaveData->tokenSize = tokenSize;\n\n\t// Parse the symbol table\n\tBuildHashTable( pSaveData, pFile );\n\n\tFS_Read( pFile, pSaveData->pBaseData, size );\n\tFS_Close( pFile );\n\n\t// Read the client header\n\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"ClientHeader\", &header, gSaveClient, ARRAYSIZE( gSaveClient ));\n\n\t// restore decals\n\tfor( i = 0; i < header.decalCount; i++ )\n\t{\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"DECALLIST\", &decalEntry, gDecalEntry, ARRAYSIZE( gDecalEntry ));\n\n\t\t// NOTE: apply landmark offset only for brush entities without origin brushes\n\t\tif( pSaveData->fUseLandmark && FBitSet( decalEntry.flags, FDECAL_USE_LANDMARK ))\n\t\t\tVectorAdd( decalEntry.position, pSaveData->vecLandmarkOffset, decalEntry.position );\n\t\tRestoreDecal( pSaveData, &decalEntry, adjacent );\n\t}\n\n\t// clear old entities\n\tif( !adjacent )\n\t{\n\t\tmemset( svs.static_entities, 0, sizeof( entity_state_t ) * MAX_STATIC_ENTITIES );\n\t\tsv.num_static_entities = 0;\n\t}\n\n\t// restore client entities\n\tfor( i = 0; i < header.entityCount; i++ )\n\t{\n\t\tid = sv.num_static_entities;\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"STATICENTITY\", &svs.static_entities[id], gStaticEntry, ARRAYSIZE( gStaticEntry ));\n\t\tif( adjacent ) continue; // static entities won't loading from adjacent levels\n\n\t\tif( SV_CreateStaticEntity( &sv.signon, id ))\n\t\t\tsv.num_static_entities++;\n\t}\n\n\t// restore sounds\n\tfor( i = 0; i < header.soundCount; i++ )\n\t{\n\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"SOUNDLIST\", &soundEntry, gSoundEntry, ARRAYSIZE( gSoundEntry ));\n\t\tif( adjacent ) continue; // sounds don't going across the levels\n\n\t\tRestoreSound( pSaveData, &soundEntry );\n\t}\n\n\tif( !adjacent )\n\t{\n\t\t// restore camera view here\n\t\tedict_t\t*pent = pSaveData->pTable[bound( 0, (word)header.viewentity, pSaveData->tableCount )].pent;\n\n\t\tif( COM_CheckStringEmpty( header.introTrack ) )\n\t\t{\n\t\t\t// NOTE: music is automatically goes across transition, never restore it on changelevel\n\t\t\tMSG_BeginServerCmd( &sv.signon, svc_stufftext );\n\t\t\tMSG_WriteStringf( &sv.signon, \"music \\\"%s\\\" \\\"%s\\\" %i\\n\", header.introTrack, header.mainTrack, header.trackPosition );\n\t\t}\n\n\t\t// don't go camera across the levels\n\t\tif( header.viewentity > svs.maxclients && !changelevel )\n\t\t\tcl->pViewEntity = pent;\n\n\t\t// restore some client cvars\n\t\tCvar_SetValue( \"sv_wateralpha\", header.wateralpha );\n\t\tCvar_SetValue( \"sv_wateramp\", header.wateramp );\n\t}\n}\n\n/*\n=============\nCreateEntitiesInRestoreList\n\nalloc private data for restored entities\n=============\n*/\nstatic void CreateEntitiesInRestoreList( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world )\n{\n\tint\t\ti, active;\n\tENTITYTABLE\t*pTable;\n\tedict_t\t\t*pent;\n\n\t// create entity list\n\tif( svgame.physFuncs.pfnCreateEntitiesInRestoreList != NULL )\n\t{\n\t\tsvgame.physFuncs.pfnCreateEntitiesInRestoreList( pSaveData, levelMask, create_world );\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t\t{\n\t\t\tpTable = &pSaveData->pTable[i];\n\t\t\tpent = NULL;\n\n\t\t\tif( pTable->classname && pTable->size && ( !FBitSet( pTable->flags, FENTTABLE_REMOVED ) || !create_world ))\n\t\t\t{\n\t\t\t\tif( !create_world )\n\t\t\t\t\tactive = FBitSet( pTable->flags, levelMask ) ? 1 : 0;\n\t\t\t\telse active = 1;\n\n\t\t\t\tif( pTable->id == 0 && create_world ) // worldspawn\n\t\t\t\t{\n\t\t\t\t\tpent = EDICT_NUM( 0 );\n\t\t\t\t\tSV_InitEdict( pent );\n\t\t\t\t\tpent = SV_CreateNamedEntity( pent, pTable->classname );\n\t\t\t\t}\n\t\t\t\telse if(( pTable->id > 0 ) && ( pTable->id < svs.maxclients + 1 ))\n\t\t\t\t{\n\t\t\t\t\tedict_t\t*ed = EDICT_NUM( pTable->id );\n\n\t\t\t\t\tif( !FBitSet( pTable->flags, FENTTABLE_PLAYER ))\n\t\t\t\t\t\tCon_Printf( S_ERROR \"ENTITY IS NOT A PLAYER: %d\\n\", i );\n\n\t\t\t\t\t// create the player\n\t\t\t\t\tif( active && SV_IsValidEdict( ed ))\n\t\t\t\t\t\tpent = SV_CreateNamedEntity( ed, pTable->classname );\n\t\t\t\t}\n\t\t\t\telse if( active )\n\t\t\t\t{\n\t\t\t\t\tpent = SV_CreateNamedEntity( NULL, pTable->classname );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpTable->pent = pent;\n\t\t}\n\t}\n}\n\n/*\n=============\nSaveGameState\n\nsave current game state\n=============\n*/\nstatic SAVERESTOREDATA *SaveGameState( int changelevel )\n{\n\tchar\t\tname[MAX_QPATH];\n\tint\t\ti, id, version;\n\tchar\t\t*pTableData;\n\tchar\t\t*pTokenData;\n\tSAVERESTOREDATA\t*pSaveData;\n\tint\t\ttableSize;\n\tint\t\tdataSize;\n\tENTITYTABLE\t*pTable;\n\tSAVE_HEADER\theader;\n\tSAVE_LIGHTSTYLE\tlight;\n\tfile_t\t\t*pFile;\n\n\tif( !svgame.dllFuncs.pfnParmsChangeLevel )\n\t\treturn NULL;\n\n\tpSaveData = SaveInit( SAVE_HEAPSIZE, SAVE_HASHSTRINGS );\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.HL1\", sv.name );\n\tCOM_FixSlashes( name );\n\n\t// initialize entity table to count moved entities\n\tInitEntityTable( pSaveData, svgame.numEntities );\n\n\t// Build the adjacent map list\n\tsvgame.dllFuncs.pfnParmsChangeLevel();\n\n\t// Write the global data\n\theader.skillLevel = (int)skill.value;\t// this is created from an int even though it's a float\n\theader.entityCount = pSaveData->tableCount;\n\theader.connectionCount = pSaveData->connectionCount;\n\theader.time = svgame.globals->time;\t// use DLL time\n\tQ_strncpy( header.mapName, sv.name, sizeof( header.mapName ));\n\tQ_strncpy( header.skyName, sv_skyname.string, sizeof( header.skyName ));\n\theader.skyColor_r = sv_skycolor_r.value;\n\theader.skyColor_g = sv_skycolor_g.value;\n\theader.skyColor_b = sv_skycolor_b.value;\n\theader.skyVec_x = sv_skyvec_x.value;\n\theader.skyVec_y = sv_skyvec_y.value;\n\theader.skyVec_z = sv_skyvec_z.value;\n\theader.lightStyleCount = 0;\n\n\t// counting the lightstyles\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tif( sv.lightstyles[i].pattern[0] )\n\t\t\theader.lightStyleCount++;\n\t}\n\n\t// Write the main header\n\tpSaveData->time = 0.0f; // prohibits rebase of header.time (keep compatibility with old saves)\n\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"Save Header\", &header, gSaveHeader, ARRAYSIZE( gSaveHeader ));\n\tpSaveData->time = header.time;\n\n\t// Write the adjacency list\n\tfor( i = 0; i < pSaveData->connectionCount; i++ )\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"ADJACENCY\", &pSaveData->levelList[i], gAdjacency, ARRAYSIZE( gAdjacency ));\n\n\t// Write the lightstyles\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tif( !sv.lightstyles[i].pattern[0] )\n\t\t\tcontinue;\n\n\t\tQ_strncpy( light.style, sv.lightstyles[i].pattern, sizeof( light.style ));\n\t\tlight.time = sv.lightstyles[i].time;\n\t\tlight.index = i;\n\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"LIGHTSTYLE\", &light, gLightStyle, ARRAYSIZE( gLightStyle ));\n\t}\n\n\t// build the table of entities\n\t// this is used to turn pointers into savable indices\n\t// build up ID numbers for each entity, for use in pointer conversions\n\t// if an entity requires a certain edict number upon restore, save that as well\n\tfor( i = 0; i < svgame.numEntities; i++ )\n\t{\n\t\tpTable = &pSaveData->pTable[i];\n\t\tpTable->location = pSaveData->size;\n\t\tpSaveData->currentIndex = i;\n\t\tpTable->size = 0;\n\n\t\tif( !SV_IsValidEdict( pTable->pent ))\n\t\t\tcontinue;\n\n\t\tsvgame.dllFuncs.pfnSave( pTable->pent, pSaveData );\n\n\t\tif( FBitSet( pTable->pent->v.flags, FL_CLIENT ))\n\t\t\tSetBits( pTable->flags, FENTTABLE_PLAYER );\n\t}\n\n\t// total data what includes:\n\t// 1. save header\n\t// 2. adjacency list\n\t// 3. lightstyles\n\t// 4. all the entity data\n\tdataSize = pSaveData->size;\n\n\t// Write entity table\n\tpTableData = pSaveData->pCurrentData;\n\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"ETABLE\", &pSaveData->pTable[i], gEntityTable, ARRAYSIZE( gEntityTable ));\n\n\ttableSize = pSaveData->size - dataSize;\n\n\t// Write entity string token table\n\tpTokenData = StoreHashTable( pSaveData );\n\n\t// output to disk\n\tif(( pFile = FS_Open( name, \"wb\", true )) == NULL )\n\t{\n\t\t// something bad is happens\n\t\tSaveFinish( pSaveData );\n\t\treturn NULL;\n\t}\n\n\t// Write the header -- THIS SHOULD NEVER CHANGE STRUCTURE, USE SAVE_HEADER FOR NEW HEADER INFORMATION\n\t// THIS IS ONLY HERE TO IDENTIFY THE FILE AND GET IT'S SIZE.\n\tversion = SAVEGAME_VERSION;\n\tid = SAVEFILE_HEADER;\n\n\t// write the header\n\tFS_Write( pFile, &id, sizeof( id ));\n\tFS_Write( pFile, &version, sizeof( version ));\n\n\t// Write out the tokens and table FIRST so they are loaded in the right order, then write out the rest of the data in the file.\n\tFS_Write( pFile, &pSaveData->size, sizeof( int ));\t// total size of all data to initialize read buffer\n\tFS_Write( pFile, &pSaveData->tableCount, sizeof( int ));\t// entities count to right initialize entity table\n\tFS_Write( pFile, &pSaveData->tokenCount, sizeof( int ));\t// num hash tokens to prepare token table\n\tFS_Write( pFile, &pSaveData->tokenSize, sizeof( int ));\t// total size of hash tokens\n\tFS_Write( pFile, pTokenData, pSaveData->tokenSize );\t// write tokens into the file\n\tFS_Write( pFile, pTableData, tableSize );\t\t// dump ETABLE structures\n\tFS_Write( pFile, pSaveData->pBaseData, dataSize );\t// and finally store all the other data\n\tFS_Close( pFile );\n\n\tEntityPatchWrite( pSaveData, sv.name );\n\n\tSaveClientState( pSaveData, sv.name, changelevel );\n\n\treturn pSaveData;\n}\n\n/*\n=============\nLoadGameState\n\nload current game state\n=============\n*/\nstatic int LoadGameState( char const *level, qboolean changelevel )\n{\n\tSAVERESTOREDATA\t*pSaveData;\n\tENTITYTABLE\t*pTable;\n\tSAVE_HEADER\theader;\n\tedict_t\t\t*pent;\n\tint\t\ti;\n\n\tpSaveData = LoadSaveData( level );\n\tif( !pSaveData ) return 0; // couldn't load the file\n\n\t// must set mapname before calling into DLL\n\tQ_strncpy( sv.name, level, sizeof( sv.name ));\n\tsvgame.globals->mapname = MAKE_STRING( sv.name );\n\n\tParseSaveTables( pSaveData, &header, true );\n\tEntityPatchRead( pSaveData, level );\n\n\t// pause until all clients connect\n\tsv.loadgame = sv.paused = true;\n\n\tCvar_SetValue( \"skill\", header.skillLevel );\n\tCvar_Set( \"sv_skyname\", header.skyName );\n\n\t// restore sky parms\n\tCvar_SetValue( \"sv_skycolor_r\", header.skyColor_r );\n\tCvar_SetValue( \"sv_skycolor_g\", header.skyColor_g );\n\tCvar_SetValue( \"sv_skycolor_b\", header.skyColor_b );\n\tCvar_SetValue( \"sv_skyvec_x\", header.skyVec_x );\n\tCvar_SetValue( \"sv_skyvec_y\", header.skyVec_y );\n\tCvar_SetValue( \"sv_skyvec_z\", header.skyVec_z );\n\n\t// create entity list\n\tCreateEntitiesInRestoreList( pSaveData, 0, true );\n\n\t// now spawn entities\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t{\n\t\tpTable = &pSaveData->pTable[i];\n\t\tpSaveData->pCurrentData = pSaveData->pBaseData + pTable->location;\n\t\tpSaveData->size = pTable->location;\n\t\tpSaveData->currentIndex = i;\n\t\tpent = pTable->pent;\n\n\t\tif( pent != NULL )\n\t\t{\n\t\t\tif( svgame.dllFuncs.pfnRestore( pent, pSaveData, 0 ) < 0 )\n\t\t\t{\n\t\t\t\tSetBits( pent->v.flags, FL_KILLME );\n\t\t\t\tpTable->pent = NULL;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// force the entity to be relinked\n//\t\t\t\tSV_LinkEdict( pent, false );\n\t\t\t}\n\t\t}\n\t}\n\n\tLoadClientState( pSaveData, level, changelevel, false );\n\n\tSaveFinish( pSaveData );\n\n\t// restore server time\n\tsv.time = header.time;\n\n\treturn 1;\n}\n\n/*\n=============\nSaveGameSlot\n\ndo a save game\n=============\n*/\nstatic qboolean SaveGameSlot( const char *pSaveName, const char *pSaveComment )\n{\n\tchar\t\thlPath[MAX_QPATH];\n\tchar\t\tname[MAX_QPATH];\n\tint\t\tid, version;\n\tchar\t\t*pTokenData;\n\tSAVERESTOREDATA\t*pSaveData;\n\tGAME_HEADER\tgameHeader;\n\tfile_t\t\t*pFile;\n\n\tpSaveData = SaveGameState( false );\n\tif( !pSaveData ) return false;\n\n\tSaveFinish( pSaveData );\n\tpSaveData = SaveInit( SAVE_HEAPSIZE, SAVE_HASHSTRINGS ); // re-init the buffer\n\n\tQ_strncpy( hlPath, DEFAULT_SAVE_DIRECTORY \"*.HL?\", sizeof( hlPath ) );\n\tQ_strncpy( gameHeader.mapName, sv.name, sizeof( gameHeader.mapName )); // get the name of level where a player\n\tQ_strncpy( gameHeader.comment, pSaveComment, sizeof( gameHeader.comment ));\n\tgameHeader.mapCount = DirectoryCount( hlPath ); // counting all the adjacency maps\n\n\t// Store the game header\n\tsvgame.dllFuncs.pfnSaveWriteFields( pSaveData, \"GameHeader\", &gameHeader, gGameHeader, ARRAYSIZE( gGameHeader ));\n\n\t// Write the game globals\n\tsvgame.dllFuncs.pfnSaveGlobalState( pSaveData );\n\n\t// Write entity string token table\n\tpTokenData = StoreHashTable( pSaveData );\n\n\tQ_snprintf( name, sizeof( name ), DEFAULT_SAVE_DIRECTORY \"%s.sav\", pSaveName );\n\tCOM_FixSlashes( name );\n\n\t// output to disk\n\tif( !Q_stricmp( pSaveName, \"quick\" ))\n\t\tAgeSaveList( pSaveName, GI->quicksave_aged_count );\n\telse if( !Q_stricmp( pSaveName, \"autosave\" ))\n\t\tAgeSaveList( pSaveName, GI->autosave_aged_count );\n\n\t// output to disk\n\tif(( pFile = FS_Open( name, \"wb\", true )) == NULL )\n\t{\n\t\t// something bad is happens\n\t\tSaveFinish( pSaveData );\n\t\treturn false;\n\t}\n\n\t// pending the preview image for savegame\n\tCbuf_AddTextf( \"saveshot \\\"%s\\\"\\n\", pSaveName );\n\tCon_Printf( \"Saving game to %s...\\n\", name );\n\n\tversion = SAVEGAME_VERSION;\n\tid = SAVEGAME_HEADER;\n\n\tFS_Write( pFile, &id, sizeof( id ));\n\tFS_Write( pFile, &version, sizeof( version ));\n\tFS_Write( pFile, &pSaveData->size, sizeof( int )); // does not include token table\n\n\t// write out the tokens first so we can load them before we load the entities\n\tFS_Write( pFile, &pSaveData->tokenCount, sizeof( int ));\n\tFS_Write( pFile, &pSaveData->tokenSize, sizeof( int ));\n\tFS_Write( pFile, pTokenData, pSaveData->tokenSize );\n\tFS_Write( pFile, pSaveData->pBaseData, pSaveData->size ); // header and globals\n\n\tDirectoryCopy( hlPath, pFile );\n\tSaveFinish( pSaveData );\n\tFS_Close( pFile );\n\n\treturn true;\n}\n\n/*\n=============\nSaveReadHeader\n\nread header of .sav file\n=============\n*/\nstatic int SaveReadHeader( file_t *pFile, GAME_HEADER *pHeader )\n{\n\tint\t\ttokenCount, tokenSize;\n\tint\t\tsize, id, version;\n\tSAVERESTOREDATA\t*pSaveData;\n\n\tFS_Read( pFile, &id, sizeof( id ));\n\tif( id != SAVEGAME_HEADER )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn 0;\n\t}\n\n\tFS_Read( pFile, &version, sizeof( version ));\n\tif( version != SAVEGAME_VERSION )\n\t{\n\t\tFS_Close( pFile );\n\t\treturn 0;\n\t}\n\n\tFS_Read( pFile, &size, sizeof( int ));\n\tFS_Read( pFile, &tokenCount, sizeof( int ));\n\tFS_Read( pFile, &tokenSize, sizeof( int ));\n\n\tpSaveData = SaveInit( size + tokenSize, tokenCount );\n\tpSaveData->tokenCount = tokenCount;\n\tpSaveData->tokenSize = tokenSize;\n\n\t// Parse the symbol table\n\tBuildHashTable( pSaveData, pFile );\n\n\t// Set up the restore basis\n\tpSaveData->fUseLandmark = false;\n\tpSaveData->time = 0.0f;\n\n\tFS_Read( pFile, pSaveData->pBaseData, size );\n\n\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"GameHeader\", pHeader, gGameHeader, ARRAYSIZE( gGameHeader ));\n\n\tsvgame.dllFuncs.pfnRestoreGlobalState( pSaveData );\n\n\tSaveFinish( pSaveData );\n\n\treturn 1;\n}\n\n/*\n=============\nCreateEntityTransitionList\n\nmoving edicts to another level\n=============\n*/\nstatic int CreateEntityTransitionList( SAVERESTOREDATA *pSaveData, int levelMask )\n{\n\tint\t\ti, movedCount;\n\tENTITYTABLE\t*pTable;\n\tedict_t\t\t*pent;\n\n\tmovedCount = 0;\n\n\t// create entity list\n\tCreateEntitiesInRestoreList( pSaveData, levelMask, false );\n\n\t// now spawn entities\n\tfor( i = 0; i < pSaveData->tableCount; i++ )\n\t{\n\t\tpTable = &pSaveData->pTable[i];\n\t\tpSaveData->pCurrentData = pSaveData->pBaseData + pTable->location;\n\t\tpSaveData->size = pTable->location;\n\t\tpSaveData->currentIndex = i;\n\t\tpent = pTable->pent;\n\n\t\tif( SV_IsValidEdict( pent ) && FBitSet( pTable->flags, levelMask )) // screen out the player if he's not to be spawned\n\t\t{\n\t\t\tif( FBitSet( pTable->flags, FENTTABLE_GLOBAL ))\n\t\t\t{\n\t\t\t\tentvars_t\ttmpVars;\n\t\t\t\tedict_t\t*pNewEnt;\n\n\t\t\t\t// NOTE: we need to update table pointer so decals on the global entities with brush models can be\n\t\t\t\t// correctly moved. found the classname and the globalname for our globalentity\n\t\t\t\tsvgame.dllFuncs.pfnSaveReadFields( pSaveData, \"ENTVARS\", &tmpVars, gTempEntvars, ARRAYSIZE( gTempEntvars ));\n\n\t\t\t\t// reset the save pointers, so dll can read this too\n\t\t\t\tpSaveData->pCurrentData = pSaveData->pBaseData + pTable->location;\n\t\t\t\tpSaveData->size = pTable->location;\n\n\t\t\t\t// IMPORTANT: we should find the already spawned or local restored global entity\n\t\t\t\tpNewEnt = SV_FindGlobalEntity( tmpVars.classname, tmpVars.globalname );\n\n\t\t\t\tCon_DPrintf( \"Merging changes for global: %s\\n\", STRING( pTable->classname ));\n\n\t\t\t\t// -------------------------------------------------------------------------\n\t\t\t\t// Pass the \"global\" flag to the DLL to indicate this entity should only override\n\t\t\t\t// a matching entity, not be spawned\n\t\t\t\tif( svgame.dllFuncs.pfnRestore( pent, pSaveData, 1 ) > 0 )\n\t\t\t\t{\n\t\t\t\t\tmovedCount++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( SV_IsValidEdict( pNewEnt )) // update the table so decals can find parent entity\n\t\t\t\t\t\tpTable->pent = pNewEnt;\n\t\t\t\t\tSetBits( pent->v.flags, FL_KILLME );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tCon_Reportf( \"Transferring %s (%d)\\n\", STRING( pTable->classname ), NUM_FOR_EDICT( pent ));\n\n\t\t\t\tif( svgame.dllFuncs.pfnRestore( pent, pSaveData, 0 ) < 0 )\n\t\t\t\t{\n\t\t\t\t\tSetBits( pent->v.flags, FL_KILLME );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif( !FBitSet( pTable->flags, FENTTABLE_PLAYER ) && EntityInSolid( pent ))\n\t\t\t\t\t{\n\t\t\t\t\t\t// this can happen during normal processing - PVS is just a guess,\n\t\t\t\t\t\t// some map areas won't exist in the new map\n\t\t\t\t\t\tCon_Reportf( \"Suppressing %s\\n\", STRING( pTable->classname ));\n\t\t\t\t\t\tSetBits( pent->v.flags, FL_KILLME );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tpTable->flags = FENTTABLE_REMOVED;\n\t\t\t\t\t\tmovedCount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// remove any entities that were removed using UTIL_Remove()\n\t\t\t// as a result of the above calls to UTIL_RemoveImmediate()\n\t\t\tSV_FreeOldEntities ();\n\t\t}\n\t}\n\n\treturn movedCount;\n}\n\n/*\n=============\nLoadAdjacentEnts\n\nloading edicts from adjacency levels\n=============\n*/\nstatic void LoadAdjacentEnts( const char *pOldLevel, const char *pLandmarkName )\n{\n\tSAVE_HEADER\theader;\n\tSAVERESTOREDATA\tcurrentLevelData, *pSaveData;\n\tint\t\ti, test, flags, index, movedCount = 0;\n\tqboolean\t\tfoundprevious = false;\n\tvec3_t\t\tlandmarkOrigin;\n\n\tmemset( &currentLevelData, 0, sizeof( SAVERESTOREDATA ));\n\tsvgame.globals->pSaveData = &currentLevelData;\n\tsv.loadgame = sv.paused = true;\n\n\t// build the adjacent map list\n\tsvgame.dllFuncs.pfnParmsChangeLevel();\n\n\tfor( i = 0; i < currentLevelData.connectionCount; i++ )\n\t{\n\t\t// make sure the previous level is in the connection list so we can\n\t\t// bring over the player.\n\t\tif( !Q_stricmp( currentLevelData.levelList[i].mapName, pOldLevel ))\n\t\t\tfoundprevious = true;\n\n\t\tfor( test = 0; test < i; test++ )\n\t\t{\n\t\t\t// only do maps once\n\t\t\tif( !Q_stricmp( currentLevelData.levelList[i].mapName, currentLevelData.levelList[test].mapName ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// map was already in the list\n\t\tif( test < i ) continue;\n\n\t\tpSaveData = LoadSaveData( currentLevelData.levelList[i].mapName );\n\n\t\tif( pSaveData )\n\t\t{\n\t\t\tParseSaveTables( pSaveData, &header, false );\n\t\t\tEntityPatchRead( pSaveData, currentLevelData.levelList[i].mapName );\n\n\t\t\tpSaveData->time = sv.time; // - header.time;\n\t\t\tpSaveData->fUseLandmark = true;\n\t\t\tflags = movedCount = 0;\n\t\t\tindex = -1;\n\n\t\t\t// calculate landmark offset\n\t\t\tLandmarkOrigin( &currentLevelData, landmarkOrigin, pLandmarkName );\n\t\t\tLandmarkOrigin( pSaveData, pSaveData->vecLandmarkOffset, pLandmarkName );\n\t\t\tVectorSubtract( landmarkOrigin, pSaveData->vecLandmarkOffset, pSaveData->vecLandmarkOffset );\n\n\t\t\tif( !Q_stricmp( currentLevelData.levelList[i].mapName, pOldLevel ))\n\t\t\t\tSetBits( flags, FENTTABLE_PLAYER );\n\n\t\t\twhile( 1 )\n\t\t\t{\n\t\t\t\tindex = EntryInTable( pSaveData, sv.name, index );\n\t\t\t\tif( index < 0 ) break;\n\t\t\t\tSetBits( flags, BIT( index ));\n\t\t\t}\n\n\t\t\tif( flags ) movedCount = CreateEntityTransitionList( pSaveData, flags );\n\n\t\t\t// if ents were moved, rewrite entity table to save file\n\t\t\tif( movedCount ) EntityPatchWrite( pSaveData, currentLevelData.levelList[i].mapName );\n\n\t\t\t// move the decals from another level\n\t\t\tLoadClientState( pSaveData, currentLevelData.levelList[i].mapName, true, true );\n\n\t\t\tSaveFinish( pSaveData );\n\t\t}\n\t}\n\n\tsvgame.globals->pSaveData = NULL;\n\n\tif( !foundprevious )\n\t\tHost_Error( \"Level transition ERROR\\nCan't find connection to %s from %s\\n\", pOldLevel, sv.name );\n}\n\n/*\n=============\nSV_LoadGameState\n\nloading entities from the savegame\n=============\n*/\nint SV_LoadGameState( char const *level )\n{\n\treturn LoadGameState( level, false );\n}\n\n/*\n=============\nSV_ClearGameState\n\nclear current game state\n=============\n*/\nvoid SV_ClearGameState( void )\n{\n\tClearSaveDir();\n\n\tif( svgame.dllFuncs.pfnResetGlobalState != NULL )\n\t\tsvgame.dllFuncs.pfnResetGlobalState();\n}\n\n/*\n=============\nSV_ChangeLevel\n=============\n*/\nvoid SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char *start, qboolean background )\n{\n\tchar\t\tlevel[MAX_QPATH];\n\tchar\t\toldlevel[MAX_QPATH];\n\tchar\t\t_startspot[MAX_QPATH];\n\tchar\t\t*startspot = NULL;\n\tSAVERESTOREDATA\t*pSaveData = NULL;\n\n\tif( sv.state != ss_active )\n\t{\n\t\tCon_Printf( S_ERROR \"server not running\\n\");\n\t\treturn;\n\t}\n\n\tif( start )\n\t{\n\t\tQ_strncpy( _startspot, start, sizeof( _startspot ));\n\t\tstartspot = _startspot;\n\t}\n\n\tQ_strncpy( level, mapname, sizeof( level ));\n\tQ_strncpy( oldlevel, sv.name, sizeof( oldlevel ));\n\n\tif( loadfromsavedgame )\n\t{\n\t\t// smooth transition in-progress\n\t\tsvgame.globals->changelevel = true;\n\n\t\t// save the current level's state\n\t\tpSaveData = SaveGameState( true );\n\t}\n\n\tSV_InactivateClients ();\n\tSV_FinalMessage( \"\", true );\n\tSV_DeactivateServer ();\n\n\tif( !SV_SpawnServer( level, startspot, background ))\n\t\treturn;\t// ???\n\n\tif( loadfromsavedgame )\n\t{\n\t\t// finish saving gamestate\n\t\tSaveFinish( pSaveData );\n\n\t\tif( !LoadGameState( level, true ))\n\t\t\tSV_SpawnEntities( level );\n\t\tLoadAdjacentEnts( oldlevel, startspot );\n\n\t\tif( sv_newunit.value )\n\t\t\tClearSaveDir();\n\t\tSV_ActivateServer( false );\n\t}\n\telse\n\t{\n\t\t// classic quake changelevel\n\t\tsvgame.dllFuncs.pfnResetGlobalState();\n\t\tSV_SpawnEntities( level );\n\t\tSV_ActivateServer( true );\n\t}\n}\n\n/*\n=============\nSV_LoadGame\n=============\n*/\nqboolean SV_LoadGame( const char *pPath )\n{\n\tqboolean\t\tvalidload = false;\n\tGAME_HEADER\tgameHeader;\n\tfile_t\t\t*pFile;\n\tuint\t\tflags;\n\n\tif( Host_IsDedicated() )\n\t\treturn false;\n\n\tif( UI_CreditsActive( ))\n\t\treturn false;\n\n\tif( !COM_CheckString( pPath ))\n\t\treturn false;\n\n\t// silently ignore if missed\n\tif( !FS_FileExists( pPath, true ))\n\t\treturn false;\n\n\t// initialize game if needs\n\tif( !SV_InitGame( ))\n\t\treturn false;\n\n\tsvs.initialized = true;\n\tpFile = FS_Open( pPath, \"rb\", true );\n\n\tif( pFile )\n\t{\n\t\tSV_ClearGameState();\n\n\t\tif( SaveReadHeader( pFile, &gameHeader ))\n\t\t{\n\t\t\tDirectoryExtract( pFile, gameHeader.mapCount );\n\t\t\tvalidload = true;\n\t\t}\n\t\tFS_Close( pFile );\n\n\t\tif( validload )\n\t\t{\n\t\t\t// now check for map problems\n\t\t\tflags = SV_MapIsValid( gameHeader.mapName, NULL );\n\n\t\t\tif( FBitSet( flags, MAP_INVALID_VERSION ))\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"map %s is invalid or not supported\\n\", gameHeader.mapName );\n\t\t\t\tvalidload = false;\n\t\t\t}\n\n\t\t\tif( !FBitSet( flags, MAP_IS_EXIST ))\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"map %s doesn't exist\\n\", gameHeader.mapName );\n\t\t\t\tvalidload = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( !validload )\n\t{\n\t\tCon_Printf( S_ERROR \"Couldn't load %s\\n\", pPath );\n\t\treturn false;\n\t}\n\n\tCon_Printf( \"Loading game from %s...\\n\", pPath );\n\tCvar_FullSet( \"maxplayers\", \"1\", FCVAR_LATCH );\n\tCvar_SetValue( \"deathmatch\", 0 );\n\tCvar_SetValue( \"coop\", 0 );\n\tCOM_LoadGame( gameHeader.mapName );\n\n\treturn true;\n}\n\n/*\n==================\nSV_SaveGame\n==================\n*/\nqboolean SV_SaveGame( const char *pName )\n{\n\tchar   comment[80];\n\tstring savename;\n\n\tif( !COM_CheckString( pName ))\n\t\treturn false;\n\n\t// can we save at this point?\n\tif( !IsValidSave( ))\n\t\treturn false;\n\n\tif( !Q_stricmp( pName, \"new\" ))\n\t{\n\t\tint n;\n\n\t\t// scan for a free filename\n\t\tfor( n = 0; n < 1000; n++ )\n\t\t{\n\t\t\tQ_snprintf( savename, sizeof( savename ), \"save%03d\", n );\n\n\t\t\tif( !FS_FileExists( va( DEFAULT_SAVE_DIRECTORY \"%s.sav\", savename ), true ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( n == 1000 )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"no free slots for savegame\\n\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\telse Q_strncpy( savename, pName, sizeof( savename ));\n\n#if !XASH_DEDICATED\n\t// unload previous image from memory (it's will be overwritten)\n\tGL_FreeImage( va( DEFAULT_SAVE_DIRECTORY \"%s.bmp\", savename ) );\n#endif // XASH_DEDICATED\n\n\tSaveBuildComment( comment, sizeof( comment ));\n\treturn SaveGameSlot( savename, comment );\n}\n\nstatic int SV_CompareFileTime( int ft1, int ft2 )\n{\n\tif( ft1 < ft2 )\n\t{\n\t\treturn -1;\n\t}\n\telse if( ft1 > ft2 )\n\t{\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n/*\n==================\nSV_GetLatestSave\n\nused for reload game after player death\n==================\n*/\nconst char *SV_GetLatestSave( void )\n{\n\tstatic char\tsavename[MAX_QPATH];\n\tint\t\tnewest = 0, ft;\n\tint\t\ti, found = 0;\n\tsearch_t\t\t*t;\n\n\tif(( t = FS_Search( DEFAULT_SAVE_DIRECTORY \"*.sav\" , true, true )) == NULL )\n\t\treturn NULL;\n\n\tfor( i = 0; i < t->numfilenames; i++ )\n\t{\n\t\tft = FS_FileTime( t->filenames[i], true );\n\n\t\t// found a match?\n\t\tif( ft > 0 )\n\t\t{\n\t\t\t// should we use the matched?\n\t\t\tif( !found || SV_CompareFileTime( newest, ft ) < 0 )\n\t\t\t{\n\t\t\t\tQ_strncpy( savename, t->filenames[i], sizeof( savename ));\n\t\t\t\tnewest = ft;\n\t\t\t\tfound = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tMem_Free( t ); // release search\n\n\tif( found )\n\t\treturn savename;\n\treturn NULL;\n}\n\n/*\n==================\nSV_GetSaveComment\n\ncheck savegame for valid\n==================\n*/\nint GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment )\n{\n\tint\ti, tag, size, nNumberOfFields, nFieldSize, tokenSize, tokenCount;\n\tchar\t*pData, *pSaveData, *pFieldName, **pTokenList;\n\tstring\tmapName, description;\n\tfile_t\t*f;\n\n\tif(( f = FS_Open( savename, \"rb\", true )) == NULL )\n\t{\n\t\t// just not exist - clear comment\n\t\tcomment[0] = '\\0';\n\t\treturn 0;\n\t}\n\n\tFS_Read( f, &tag, sizeof( int ));\n\tif( tag != SAVEGAME_HEADER )\n\t{\n\t\t// invalid header\n\t\tQ_strncpy( comment, \"<corrupted>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tFS_Read( f, &tag, sizeof( int ));\n\n\tif( tag == 0x0065 )\n\t{\n\t\tQ_strncpy( comment, \"<old version \"XASH_ENGINE_NAME\" unsupported>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tif( tag < SAVEGAME_VERSION )\n\t{\n\t\tQ_strncpy( comment, \"<old version>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tif( tag > SAVEGAME_VERSION )\n\t{\n\t\t// old xash version ?\n\t\tQ_strncpy( comment, \"<invalid version>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tmapName[0] = '\\0';\n\tcomment[0] = '\\0';\n\n\tFS_Read( f, &size, sizeof( int ));\n\tFS_Read( f, &tokenCount, sizeof( int ));\t// These two ints are the token list\n\tFS_Read( f, &tokenSize, sizeof( int ));\n\tsize += tokenSize;\n\n\t// sanity check.\n\tif( tokenCount < 0 || tokenCount > SAVE_HASHSTRINGS )\n\t{\n\t\tQ_strncpy( comment, \"<corrupted hashtable>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tif( tokenSize < 0 || tokenSize > SAVE_HEAPSIZE )\n\t{\n\t\tQ_strncpy( comment, \"<corrupted hashtable>\", MAX_STRING );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\tpSaveData = (char *)Mem_Malloc( host.mempool, size );\n\tFS_Read( f, pSaveData, size );\n\tpData = pSaveData;\n\n\t// allocate a table for the strings, and parse the table\n\tif( tokenSize > 0 )\n\t{\n\t\tpTokenList = Mem_Calloc( host.mempool, tokenCount * sizeof( char* ));\n\n\t\t// make sure the token strings pointed to by the pToken hashtable.\n\t\tfor( i = 0; i < tokenCount; i++ )\n\t\t{\n\t\t\tpTokenList[i] = *pData ? pData : NULL;\t// point to each string in the pToken table\n\t\t\twhile( *pData++ );\t\t\t// find next token (after next null)\n\t\t}\n\t}\n\telse pTokenList = NULL;\n\n\t// short, short (size, index of field name)\n\tnFieldSize = *(short *)pData;\n\tpData += sizeof( short );\n\tpFieldName = pTokenList[*(short *)pData];\n\n\tif( Q_stricmp( pFieldName, \"GameHeader\" ))\n\t{\n\t\tQ_strncpy( comment, \"<missing GameHeader>\", MAX_STRING );\n\t\tif( pTokenList ) Mem_Free( pTokenList );\n\t\tif( pSaveData ) Mem_Free( pSaveData );\n\t\tFS_Close( f );\n\t\treturn 0;\n\t}\n\n\t// int (fieldcount)\n\tpData += sizeof( short );\n\tnNumberOfFields = (int)*pData;\n\tpData += nFieldSize;\n\n\t// each field is a short (size), short (index of name), binary string of \"size\" bytes (data)\n\tfor( i = 0; i < nNumberOfFields; i++ )\n\t{\n\t\tsize_t size;\n\t\t// Data order is:\n\t\t// Size\n\t\t// szName\n\t\t// Actual Data\n\t\tnFieldSize = *(short *)pData;\n\t\tpData += sizeof( short );\n\n\t\tpFieldName = pTokenList[*(short *)pData];\n\t\tpData += sizeof( short );\n\n\t\tsize = Q_min( nFieldSize, MAX_STRING );\n\n\t\tif( !Q_stricmp( pFieldName, \"comment\" ))\n\t\t{\n\t\t\tQ_strncpy( description, pData, size );\n\t\t}\n\t\telse if( !Q_stricmp( pFieldName, \"mapName\" ))\n\t\t{\n\t\t\tQ_strncpy( mapName, pData, size );\n\t\t}\n\n\t\t// move to start of next field.\n\t\tpData += nFieldSize;\n\t}\n\n\t// delete the string table we allocated\n\tif( pTokenList ) Mem_Free( pTokenList );\n\tif( pSaveData ) Mem_Free( pSaveData );\n\tFS_Close( f );\n\n\t// at least mapname should be filled\n\tif( COM_CheckStringEmpty( mapName ) )\n\t{\n\t\ttime_t\t\tfileTime;\n\t\tconst struct tm\t*file_tm;\n\t\tstring\t\ttimestring;\n\t\tuint\t\tflags;\n\n\t\t// now check for map problems\n\t\tflags = SV_MapIsValid( mapName, NULL );\n\n\t\tif( FBitSet( flags, MAP_INVALID_VERSION ))\n\t\t{\n\t\t\tQ_snprintf( comment, MAX_STRING, \"<map %s has invalid format>\", mapName );\n\t\t\treturn 0;\n\t\t}\n\n\t\tif( !FBitSet( flags, MAP_IS_EXIST ))\n\t\t{\n\t\t\tQ_snprintf( comment, MAX_STRING, \"<map %s is missed>\", mapName );\n\t\t\treturn 0;\n\t\t}\n\n\t\tfileTime = FS_FileTime( savename, true );\n\t\tfile_tm = localtime( &fileTime );\n\n\t\t// split comment to sections\n\t\tif( Q_strstr( savename, \"quick\" ))\n\t\t\tQ_snprintf( comment, CS_SIZE, \"[quick]%s\", description );\n\t\telse if( Q_strstr( savename, \"autosave\" ))\n\t\t\tQ_snprintf( comment, CS_SIZE, \"[autosave]%s\", description );\n\t\telse Q_strncpy( comment, description, CS_SIZE );\n\t\tstrftime( timestring, sizeof ( timestring ), \"%b%d %Y\", file_tm );\n\t\tQ_strncpy( comment + CS_SIZE, timestring, CS_TIME );\n\t\tstrftime( timestring, sizeof( timestring ), \"%H:%M\", file_tm );\n\t\tQ_strncpy( comment + CS_SIZE + CS_TIME, timestring, CS_TIME );\n\t\tQ_strncpy( comment + CS_SIZE + (CS_TIME * 2), description + CS_SIZE, CS_SIZE );\n\n\t\treturn 1;\n\t}\n\n\tQ_strncpy( comment, \"<unknown version>\", MAX_STRING );\n\n\treturn 0;\n}\n\nvoid SV_InitSaveRestore( void )\n{\n\tpfnSaveGameComment = COM_GetProcAddress( svgame.hInstance, \"SV_SaveGameComment\" );\n}\n"
  },
  {
    "path": "engine/server/sv_world.c",
    "content": "/*\nsv_world.c - world query functions\nCopyright (C) 2008 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"const.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n\ntypedef struct moveclip_s\n{\n\tvec3_t\t\tboxmins, boxmaxs;\t// enclose the test object along entire move\n\tfloat\t\t*mins, *maxs;\t// size of the moving object\n\tvec3_t\t\tmins2, maxs2;\t// size when clipping against mosnters\n\tconst float\t*start, *end;\n\tedict_t\t\t*passedict;\n\ttrace_t\t\ttrace;\n\tint\t\ttype;\t\t// move type\n\tqboolean\t\tignoretrans;\n\tqboolean\t\tmonsterclip;\n} moveclip_t;\n\n/*\n===============================================================================\n\nHULL BOXES\n\n===============================================================================\n*/\n\nstatic hull_t        box_hull;\nstatic mplane_t      box_planes[6];\n\n/*\n===================\nSV_InitBoxHull\n\nSet up the planes and clipnodes so that the six floats of a bounding box\ncan just be stored out and get a proper hull_t structure.\n===================\n*/\nstatic void SV_InitBoxHull( void )\n{\n\tint\ti;\n\n\tbox_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\tbox_hull.planes = box_planes;\n\tbox_hull.firstclipnode = 0;\n\tbox_hull.lastclipnode = 5;\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tbox_planes[i].type = i>>1;\n\t\tbox_planes[i].normal[i>>1] = 1;\n\t\tbox_planes[i].signbits = 0;\n\t}\n\n}\n\n/*\n====================\nStudioPlayerBlend\n\n====================\n*/\nstatic void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )\n{\n\t// calc up/down pointing\n\t*pBlend = (*pPitch * 3);\n\n\tif( *pBlend < pseqdesc->blendstart[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendstart[0] / 3.0f;\n\t\t*pBlend = 0;\n\t}\n\telse if( *pBlend > pseqdesc->blendend[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendend[0] / 3.0f;\n\t\t*pBlend = 255;\n\t}\n\telse\n\t{\n\t\tif( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error\n\t\t\t*pBlend = 127;\n\t\telse *pBlend = 255.0f * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]);\n\t\t*pPitch = 0;\n\t}\n}\n\n/*\n====================\nSV_CheckSphereIntersection\n\ncheck clients only\n====================\n*/\nstatic qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end )\n{\n\tint\t\ti, sequence;\n\tfloat\t\tradiusSquared;\n\tvec3_t\t\ttraceOrg, traceDir;\n\tstudiohdr_t\t*pstudiohdr;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmodel_t\t\t*mod;\n\n\tif( !FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))\n\t\treturn true;\n\n\tif(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL )\n\t\treturn true;\n\n\tif(( pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod )) == NULL )\n\t\treturn true;\n\n\tsequence = ent->v.sequence;\n\tif( sequence < 0 || sequence >= pstudiohdr->numseq )\n\t\tsequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;\n\n\tVectorCopy( start, traceOrg );\n\tVectorSubtract( end, start, traceDir );\n\tradiusSquared = 0.0f;\n\n\tfor ( i = 0; i < 3; i++ )\n\t\tradiusSquared += Q_max( fabs( pseqdesc->bbmin[i] ), fabs( pseqdesc->bbmax[i] ));\n\n\treturn SphereIntersect( ent->v.origin, radiusSquared, traceOrg, traceDir );\n}\n\n\n/*\n===================\nSV_HullForBox\n\nTo keep everything totally uniform, bounding boxes are turned into small\nBSP trees instead of being compared directly.\n===================\n*/\nstatic hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs )\n{\n\tbox_planes[0].dist = maxs[0];\n\tbox_planes[1].dist = mins[0];\n\tbox_planes[2].dist = maxs[1];\n\tbox_planes[3].dist = mins[1];\n\tbox_planes[4].dist = maxs[2];\n\tbox_planes[5].dist = mins[2];\n\n\tif( world.version == QBSP2_VERSION )\n\t\tbox_hull.clipnodes32 = (mclipnode32_t *)box_clipnodes32;\n\telse\n\t\tbox_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;\n\n\treturn &box_hull;\n}\n\n/*\n==================\nSV_HullForBsp\n\nforcing to select BSP hull\n==================\n*/\nstatic hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset )\n{\n\thull_t\t\t*hull;\n\tmodel_t\t\t*model;\n\tvec3_t\t\tsize;\n\n\tif( svgame.physFuncs.SV_HullForBsp != NULL )\n\t{\n\t\thull = svgame.physFuncs.SV_HullForBsp( ent, mins, maxs, offset );\n\t\tif( hull ) return hull;\n\t}\n\n\t// decide which clipping hull to use, based on the size\n\tmodel = SV_ModelHandle( ent->v.modelindex );\n\n\tif( !model || model->type != mod_brush )\n\t\tHost_Error( \"Entity %i (%s) SOLID_BSP with a non bsp model %s\\n\", NUM_FOR_EDICT( ent ), SV_ClassName( ent ), STRING( ent->v.model ));\n\n\tVectorSubtract( maxs, mins, size );\n\n#ifdef RANDOM_HULL_NULLIZATION\n\t// author: The FiEctro\n\thull = &model->hulls[COM_RandomLong( 0, 0 )];\n#endif\n\t// g-cont: find a better method to detect quake-maps?\n\tif( FBitSet( world.flags, FWORLD_SKYSPHERE ))\n\t{\n\t\t// alternate hull select for quake maps\n\t\tif( size[0] < 3.0f || ent->v.solid == SOLID_PORTAL )\n\t\t\thull = &model->hulls[0];\n\t\telse if( size[0] <= 32.0f )\n\t\t\thull = &model->hulls[1];\n\t\telse hull = &model->hulls[2];\n\n\t\tVectorSubtract( hull->clip_mins, mins, offset );\n\t}\n\telse\n\t{\n\t\tif( size[0] <= 8.0f || ent->v.solid == SOLID_PORTAL )\n\t\t{\n\t\t\thull = &model->hulls[0];\n\t\t\tVectorCopy( hull->clip_mins, offset );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( size[0] <= 36.0f )\n\t\t\t{\n\t\t\t\tif( size[2] <= 36.0f )\n\t\t\t\t\thull = &model->hulls[3];\n\t\t\t\telse hull = &model->hulls[1];\n\t\t\t}\n\t\t\telse hull = &model->hulls[2];\n\n\t\t\tVectorSubtract( hull->clip_mins, mins, offset );\n\t\t}\n\t}\n\n\tVectorAdd( offset, ent->v.origin, offset );\n\n\treturn hull;\n}\n\n/*\n================\nSV_HullForEntity\n\nReturns a hull that can be used for testing or clipping an object of mins/maxs\nsize.\nOffset is filled in to contain the adjustment that must be added to the\ntesting object's origin to get a point to use with the returned hull.\n================\n*/\nstatic hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset )\n{\n\thull_t\t*hull;\n\tvec3_t\thullmins, hullmaxs;\n\n\tif( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL )\n\t{\n\t\tif( ent->v.solid != SOLID_PORTAL )\n\t\t{\n\t\t\tif( ent->v.movetype != MOVETYPE_PUSH && ent->v.movetype != MOVETYPE_PUSHSTEP )\n\t\t\t\tHost_Error( \"'%s' has SOLID_BSP without MOVETYPE_PUSH or MOVETYPE_PUSHSTEP\\n\", SV_ClassName( ent ));\n\t\t}\n\t\thull = SV_HullForBsp( ent, mins, maxs, offset );\n\t}\n\telse\n\t{\n\t\t// create a temp hull from bounding box sizes\n\t\tVectorSubtract( ent->v.mins, maxs, hullmins );\n\t\tVectorSubtract( ent->v.maxs, mins, hullmaxs );\n\t\thull = SV_HullForBox( hullmins, hullmaxs );\n\n\t\tVectorCopy( ent->v.origin, offset );\n\t}\n\n\treturn hull;\n}\n\n/*\n====================\nSV_HullForStudioModel\n\n====================\n*/\nstatic hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes )\n{\n\tqboolean\t\tuseComplexHull;\n\tfloat\t\tscale = 0.5f;\n\thull_t\t\t*hull = NULL;\n\tvec3_t\t\tsize;\n\tmodel_t\t\t*mod;\n\n\tif(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL )\n\t{\n\t\t*numhitboxes = 1;\n\t\treturn SV_HullForEntity( ent, mins, maxs, offset );\n\t}\n\n\tVectorSubtract( maxs, mins, size );\n\tuseComplexHull = false;\n\n\tif( VectorIsNull( size ) && !FBitSet( svgame.globals->trace_flags, FTRACE_SIMPLEBOX ))\n\t{\n\t\tuseComplexHull = true;\n\n\t\tif( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))\n\t\t{\n\t\t\tif( sv_clienttrace.value == 0.0f )\n\t\t\t{\n\t\t\t\t// so no way to trace studiomodels by hitboxes\n\t\t\t\t// use bbox instead\n\t\t\t\tuseComplexHull = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tscale = sv_clienttrace.value * 0.5f;\n\t\t\t\tVectorSet( size, 1.0f, 1.0f, 1.0f );\n\t\t\t}\n\t\t}\n\t}\n\n\tif( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ) || useComplexHull )\n\t{\n\t\tVectorScale( size, scale, size );\n\t\tVectorClear( offset );\n\n\t\tif( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))\n\t\t{\n\t\t\tstudiohdr_t\t*pstudio;\n\t\t\tmstudioseqdesc_t\t*pseqdesc;\n\t\t\tbyte\t\tcontroller[4];\n\t\t\tbyte\t\tblending[2];\n\t\t\tvec3_t\t\tangles;\n\t\t\tint\t\tiBlend;\n\n\t\t\tpstudio = Mod_StudioExtradata( mod );\n\t\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)pstudio + pstudio->seqindex) + ent->v.sequence;\n\t\t\tVectorCopy( ent->v.angles, angles );\n\n\t\t\tSV_StudioPlayerBlend( pseqdesc, &iBlend, &angles[PITCH] );\n\n\t\t\tcontroller[0] = controller[1] = 0x7F;\n\t\t\tcontroller[2] = controller[3] = 0x7F;\n\t\t\tblending[0] = (byte)iBlend;\n\t\t\tblending[1] = 0;\n\n\t\t\thull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, angles, ent->v.origin, size, controller, blending, numhitboxes, ent );\n\t\t}\n\t\telse\n\t\t{\n\t\t\thull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, ent->v.angles, ent->v.origin, size, ent->v.controller, ent->v.blending, numhitboxes, ent );\n\t\t}\n\t}\n\n\tif( hull ) return hull;\n\n\t*numhitboxes = 1;\n\treturn SV_HullForEntity( ent, mins, maxs, offset );\n}\n\n/*\n===============================================================================\n\n\tENTITY LINKING\n\n===============================================================================\n*/\n/*\n===============\nClearLink\n\nClearLink is used for new headnodes\n===============\n*/\nstatic void ClearLink( link_t *l )\n{\n\tl->prev = l->next = l;\n}\n\n/*\n===============\nRemoveLink\n\nremove link from chain\n===============\n*/\nstatic void RemoveLink( link_t *l )\n{\n\tl->next->prev = l->prev;\n\tl->prev->next = l->next;\n}\n\n/*\n===============\nInsertLinkBefore\n\nkept trigger and solid entities seperate\n===============\n*/\nstatic void InsertLinkBefore( link_t *l, link_t *before )\n{\n\tl->next = before;\n\tl->prev = before->prev;\n\tl->prev->next = l;\n\tl->next->prev = l;\n}\n\n/*\n===============================================================================\n\nENTITY AREA CHECKING\n\n===============================================================================\n*/\nstatic int\tiTouchLinkSemaphore = 0;\t// prevent recursion when SV_TouchLinks is active\nareanode_t\tsv_areanodes[AREA_NODES];\nstatic int\tsv_numareanodes;\n\n/*\n===============\nSV_CreateAreaNode\n\nbuilds a uniformly subdivided tree for the given world size\n===============\n*/\nstatic areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs )\n{\n\tareanode_t\t*anode;\n\tvec3_t\t\tsize;\n\tvec3_t\t\tmins1, maxs1;\n\tvec3_t\t\tmins2, maxs2;\n\n\tanode = &sv_areanodes[sv_numareanodes++];\n\n\tClearLink( &anode->trigger_edicts );\n\tClearLink( &anode->solid_edicts );\n\tClearLink( &anode->portal_edicts );\n\n\tif( depth == AREA_DEPTH )\n\t{\n\t\tanode->axis = -1;\n\t\tanode->children[0] = anode->children[1] = NULL;\n\t\treturn anode;\n\t}\n\n\tVectorSubtract( maxs, mins, size );\n\tif( size[0] > size[1] )\n\t\tanode->axis = 0;\n\telse anode->axis = 1;\n\n\tanode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] );\n\tVectorCopy( mins, mins1 );\n\tVectorCopy( mins, mins2 );\n\tVectorCopy( maxs, maxs1 );\n\tVectorCopy( maxs, maxs2 );\n\n\tmaxs1[anode->axis] = mins2[anode->axis] = anode->dist;\n\tanode->children[0] = SV_CreateAreaNode( depth+1, mins2, maxs2 );\n\tanode->children[1] = SV_CreateAreaNode( depth+1, mins1, maxs1 );\n\n\treturn anode;\n}\n\n/*\n===============\nSV_ClearWorld\n\n===============\n*/\nvoid SV_ClearWorld( void )\n{\n\tint\ti;\n\n\tSV_InitBoxHull(); // for box testing\n\n\t// clear lightstyles\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tsv.lightstyles[i].value = 256.0f;\n\t\tsv.lightstyles[i].time = 0.0f;\n\t}\n\n\tmemset( sv_areanodes, 0, sizeof( sv_areanodes ));\n\tiTouchLinkSemaphore = 0;\n\tsv_numareanodes = 0;\n\n\tSV_CreateAreaNode( 0, sv.worldmodel->mins, sv.worldmodel->maxs );\n}\n\n/*\n===============\nSV_UnlinkEdict\n===============\n*/\nvoid SV_UnlinkEdict( edict_t *ent )\n{\n\t// not linked in anywhere\n\tif( !ent->area.prev ) return;\n\n\tRemoveLink( &ent->area );\n\tent->area.prev = NULL;\n\tent->area.next = NULL;\n}\n\n/*\n====================\nSV_TouchLinks\n====================\n*/\nstatic void SV_TouchLinks( edict_t *ent, areanode_t *node )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*touch;\n\thull_t\t*hull;\n\tvec3_t\ttest, offset;\n\tmodel_t\t*mod;\n\n\t// touch linked edicts\n\tfor( l = node->trigger_edicts.next; l != &node->trigger_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\t\ttouch = EDICT_FROM_AREA( l );\n\n\t\tif( svgame.physFuncs.SV_TriggerTouch != NULL )\n\t\t{\n\t\t\t// user dll can override trigger checking (Xash3D extension)\n\t\t\tif( !svgame.physFuncs.SV_TriggerTouch( ent, touch ))\n\t\t\t\tcontinue;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ?\n\t\t\t\tcontinue;\n\n\t\t\tif( touch->v.groupinfo && ent->v.groupinfo )\n\t\t\t{\n\t\t\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, ent->v.groupinfo ))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, ent->v.groupinfo ))\n\t\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax ))\n\t\t\t\tcontinue;\n\n\t\t\tmod = SV_ModelHandle( touch->v.modelindex );\n\n\t\t\t// check brush triggers accuracy\n\t\t\tif( mod && mod->type == mod_brush )\n\t\t\t{\n\t\t\t\t// force to select bsp-hull\n\t\t\t\thull = SV_HullForBsp( touch, ent->v.mins, ent->v.maxs, offset );\n\n\t\t\t\t// support for rotational triggers\n\t\t\t\tif( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles ))\n\t\t\t\t{\n\t\t\t\t\tmatrix4x4\tmatrix;\n\t\t\t\t\tMatrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f );\n\t\t\t\t\tMatrix4x4_VectorITransform( matrix, ent->v.origin, test );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// offset the test point appropriately for this hull.\n\t\t\t\t\tVectorSubtract( ent->v.origin, offset, test );\n\t\t\t\t}\n\n\t\t\t\t// test hull for intersection with this model\n\t\t\t\tif( PM_HullPointContents( hull, hull->firstclipnode, test ) != CONTENTS_SOLID )\n\t\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// never touch the triggers when \"playersonly\" is active\n\t\tif( !sv.playersonly )\n\t\t{\n\t\t\tsvgame.globals->time = sv.time;\n\t\t\tsvgame.dllFuncs.pfnTouch( touch, ent );\n\t\t}\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( ent->v.absmax[node->axis] > node->dist )\n\t\tSV_TouchLinks( ent, node->children[0] );\n\tif( ent->v.absmin[node->axis] < node->dist )\n\t\tSV_TouchLinks( ent, node->children[1] );\n}\n\n/*\n===============\nSV_FindTouchedLeafs\n\n===============\n*/\nstatic void SV_FindTouchedLeafs( edict_t *ent, model_t *mod, mnode_t *node, int *headnode )\n{\n\tint\tsides;\n\tmleaf_t\t*leaf;\n\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn;\n\n\t// add an efrag if the node is a leaf\n\tif( node->contents < 0 )\n\t{\n\t\tif( ent->num_leafs >= MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 )))\n\t\t{\n\t\t\t// continue counting leafs,\n\t\t\t// so we know how many it's overrun\n\t\t\tent->num_leafs = (MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 )) + 1);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tleaf = (mleaf_t *)node;\n\t\t\tif( FBitSet( mod->flags, MODEL_QBSP2 ))\n\t\t\t\tent->leafnums32[ent->num_leafs] = leaf->cluster;\n\t\t\telse\n\t\t\t\tent->leafnums16[ent->num_leafs] = leaf->cluster;\n\t\t\tent->num_leafs++;\n\t\t}\n\t\treturn;\n\t}\n\n\t// NODE_MIXED\n\tsides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane );\n\n\tif(( sides == 3 ) && ( *headnode == -1 ))\n\t\t*headnode = node - mod->nodes;\n\n\t// recurse down the contacted sides\n\tif( sides & 1 )\n\t\tSV_FindTouchedLeafs( ent, mod, node_child( node, 0, mod ), headnode );\n\tif( sides & 2 )\n\t\tSV_FindTouchedLeafs( ent, mod, node_child( node, 1, mod ), headnode );\n}\n\n/*\n===============\nSV_LinkEdict\n===============\n*/\nvoid GAME_EXPORT SV_LinkEdict( edict_t *ent, qboolean touch_triggers )\n{\n\tareanode_t\t*node;\n\tint\t\theadnode;\n\n\tif( ent->area.prev ) SV_UnlinkEdict( ent );\t// unlink from old position\n\tif( ent == svgame.edicts ) return;\t\t// don't add the world\n\tif( !SV_IsValidEdict( ent )) return;\t\t// never add freed ents\n\n\t// set the abs box\n\tsvgame.dllFuncs.pfnSetAbsBox( ent );\n\n\tif( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment ))\n\t{\n\t\tmemcpy( ent->leafnums32, ent->v.aiment->leafnums32, sizeof( ent->leafnums32 ));\n\t\tent->num_leafs = ent->v.aiment->num_leafs;\n\t\tent->headnode = ent->v.aiment->headnode;\n\t}\n\telse\n\t{\n\t\t// link to PVS leafs\n\t\tent->num_leafs = 0;\n\t\tent->headnode = -1;\n\t\theadnode = -1;\n\n\t\tif( ent->v.modelindex )\n\t\t\tSV_FindTouchedLeafs( ent, sv.worldmodel, sv.worldmodel->nodes, &headnode );\n\n\t\tif( ent->num_leafs > MAX_ENT_LEAFS( FBitSet( sv.worldmodel->flags, MODEL_QBSP2 )))\n\t\t{\n\t\t\tmemset( ent->leafnums32, -1, sizeof( ent->leafnums32 ));\n\t\t\tent->num_leafs = 0;\t// so we use headnode instead\n\t\t\tent->headnode = headnode;\n\t\t}\n\t}\n\n\t// ignore non-solid bodies\n\tif( ent->v.solid == SOLID_NOT && ent->v.skin >= CONTENTS_EMPTY )\n\t\treturn;\n\n\t// find the first node that the ent's box crosses\n\tnode = sv_areanodes;\n\n\twhile( 1 )\n\t{\n\t\tif( node->axis == -1 ) break;\n\t\tif( ent->v.absmin[node->axis] > node->dist )\n\t\t\tnode = node->children[0];\n\t\telse if( ent->v.absmax[node->axis] < node->dist )\n\t\t\tnode = node->children[1];\n\t\telse break; // crosses the node\n\t}\n\n\t// link it in\n\tif( ent->v.solid == SOLID_TRIGGER )\n\t\tInsertLinkBefore( &ent->area, &node->trigger_edicts );\n\telse if( ent->v.solid == SOLID_PORTAL )\n\t\tInsertLinkBefore( &ent->area, &node->portal_edicts );\n\telse InsertLinkBefore( &ent->area, &node->solid_edicts );\n\n\tif( touch_triggers && !iTouchLinkSemaphore )\n\t{\n\t\tiTouchLinkSemaphore = true;\n\t\tSV_TouchLinks( ent, sv_areanodes );\n\t\tiTouchLinkSemaphore = false;\n\t}\n}\n\n/*\n===============================================================================\n\nPOINT TESTING IN HULLS\n\n===============================================================================\n*/\nstatic void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*touch;\n\thull_t\t*hull;\n\tvec3_t\ttest, offset;\n\tmodel_t\t*mod;\n\n\t// get water edicts\n\tfor( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\t\ttouch = EDICT_FROM_AREA( l );\n\n\t\tif( touch->v.solid != SOLID_NOT ) // disabled ?\n\t\t\tcontinue;\n\n\t\tif( touch->v.groupinfo )\n\t\t{\n\t\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, svs.groupmask ))\n\t\t\t\tcontinue;\n\n\t\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, svs.groupmask ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tmod = SV_ModelHandle( touch->v.modelindex );\n\n\t\t// only brushes can have special contents\n\t\tif( !mod || mod->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tif( !BoundsIntersect( origin, origin, touch->v.absmin, touch->v.absmax ))\n\t\t\tcontinue;\n\n\t\t// check water brushes accuracy\n\t\thull = SV_HullForBsp( touch, vec3_origin, vec3_origin, offset );\n\n\t\t// support for rotational water\n\t\tif( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles ))\n\t\t{\n\t\t\tmatrix4x4\tmatrix;\n\t\t\tMatrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, origin, test );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// offset the test point appropriately for this hull.\n\t\t\tVectorSubtract( origin, offset, test );\n\t\t}\n\n\t\t// test hull for intersection with this model\n\t\tif( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )\n\t\t\tcontinue;\n\n\t\t// compare contents ranking\n\t\tif( RankForContents( touch->v.skin ) > RankForContents( *pCont ))\n\t\t\t*pCont = touch->v.skin; // new content has more priority\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( origin[node->axis] > node->dist )\n\t\tSV_WaterLinks( origin, pCont, node->children[0] );\n\tif( origin[node->axis] < node->dist )\n\t\tSV_WaterLinks( origin, pCont, node->children[1] );\n}\n\n/*\n=============\nSV_TruePointContents\n\n=============\n*/\nint SV_TruePointContents( const vec3_t p )\n{\n\tint\tcont;\n\n\t// sanity check\n\tif( !p ) return CONTENTS_NONE;\n\n\t// get base contents from world\n\tcont = PM_HullPointContents( &sv.worldmodel->hulls[0], 0, p );\n\n\t// check all water entities\n\tSV_WaterLinks( p, &cont, sv_areanodes );\n\n\treturn cont;\n}\n\n/*\n=============\nSV_PointContents\n\n=============\n*/\nint GAME_EXPORT SV_PointContents( const vec3_t p )\n{\n\tint cont = SV_TruePointContents( p );\n\n\tif( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )\n\t\tcont = CONTENTS_WATER;\n\treturn cont;\n}\n\n/*\n===============================================================================\n\nLINE TESTING IN HULLS\n\n===============================================================================\n*/\n/*\n==================\nSV_ClipMoveToEntity\n\nHandles selection or creation of a clipping hull, and offseting (and\neventually rotation) of the end points\n==================\n*/\nvoid SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace )\n{\n\thull_t\t*hull;\n\tmodel_t\t*model;\n\tvec3_t\tstart_l, end_l;\n\tvec3_t\toffset, temp;\n\tint\tlast_hitgroup;\n\ttrace_t\ttrace_hitbox;\n\tint\ti, j, hullcount;\n\tqboolean\trotated, transform_bbox;\n\tmatrix4x4\tmatrix;\n\n\tPM_InitTrace( trace, end );\n\n\tmodel = SV_ModelHandle( ent->v.modelindex );\n\n\tif( model && model->type == mod_studio )\n\t{\n\t\thull = SV_HullForStudioModel( ent, mins, maxs, offset, &hullcount );\n\t}\n\telse\n\t{\n\t\thull = SV_HullForEntity( ent, mins, maxs, offset );\n\t\thullcount = 1;\n\t}\n\n\t// rotate start and end into the models frame of reference\n\tif(( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) && !VectorIsNull( ent->v.angles ))\n\t\trotated = true;\n\telse rotated = false;\n\n\tif( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))\n\t{\n\t\t// keep untransformed bbox less than 45 degress or train on subtransit.bsp will stop working\n\t\tif(( check_angles( ent->v.angles[0] ) || check_angles( ent->v.angles[2] )) && !VectorIsNull( mins ))\n\t\t\ttransform_bbox = true;\n\t\telse transform_bbox = false;\n\t}\n\telse transform_bbox = false;\n\n\tif( rotated )\n\t{\n\t\tvec3_t\tout_mins, out_maxs;\n\n\t\tif( transform_bbox )\n\t\t\tMatrix4x4_CreateFromEntity( matrix, ent->v.angles, ent->v.origin, 1.0f );\n\t\telse Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f );\n\n\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\n\t\tif( transform_bbox )\n\t\t{\n\t\t\tWorld_TransformAABB( matrix, mins, maxs, out_mins, out_maxs );\n\t\t\tVectorSubtract( hull->clip_mins, out_mins, offset ); // calc new local offset\n\n\t\t\tfor( j = 0; j < 3; j++ )\n\t\t\t{\n\t\t\t\tif( start_l[j] >= 0.0f )\n\t\t\t\t\tstart_l[j] -= offset[j];\n\t\t\t\telse start_l[j] += offset[j];\n\t\t\t\tif( end_l[j] >= 0.0f )\n\t\t\t\t\tend_l[j] -= offset[j];\n\t\t\t\telse end_l[j] += offset[j];\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tVectorSubtract( start, offset, start_l );\n\t\tVectorSubtract( end, offset, end_l );\n\t}\n\n\tif( hullcount == 1 )\n\t{\n\t\tPM_RecursiveHullCheck( hull, hull->firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)trace );\n\t}\n\telse\n\t{\n\t\tlast_hitgroup = 0;\n\n\t\tfor( i = 0; i < hullcount; i++ )\n\t\t{\n\t\t\tPM_InitTrace( &trace_hitbox, end );\n\n\t\t\tPM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox );\n\n\t\t\tif( i == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace->fraction )\n\t\t\t{\n\t\t\t\tif( trace->startsolid )\n\t\t\t\t{\n\t\t\t\t\t*trace = trace_hitbox;\n\t\t\t\t\ttrace->startsolid = true;\n\t\t\t\t}\n\t\t\t\telse *trace = trace_hitbox;\n\n\t\t\t\tlast_hitgroup = i;\n\t\t\t}\n\t\t}\n\n\t\ttrace->hitgroup = Mod_HitgroupForStudioHull( last_hitgroup );\n\t}\n\n\tif( trace->fraction != 1.0f )\n\t{\n\t\t// compute endpos (generic case)\n\t\tVectorLerp( start, trace->fraction, end, trace->endpos );\n\n\t\tif( rotated )\n\t\t{\n\t\t\t// transform plane\n\t\t\tVectorCopy( trace->plane.normal, temp );\n\t\t\tMatrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist );\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttrace->plane.dist = DotProduct( trace->endpos, trace->plane.normal );\n\t\t}\n\t}\n\n\tif( trace->fraction < 1.0f || trace->startsolid )\n\t\ttrace->ent = ent;\n}\n\n/*\n==================\nSV_PortalCSG\n\na portal is flush with a world surface behind it. this causes problems. namely that we can't pass through the portal plane\nif the bsp behind it prevents out origin from getting through. so if the trace was clipped and ended infront of the portal,\ncontinue the trace to the edges of the portal cutout instead.\n==================\n*/\nstatic void SV_PortalCSG( edict_t *portal, const vec3_t trace_mins, const vec3_t trace_maxs, const vec3_t start, const vec3_t end, trace_t *trace )\n{\n\tvec4_t\tplanes[6];\t//far, near, right, left, up, down\n\tint\tplane, k;\n\tvec3_t\tworldpos;\n\tfloat\tbestfrac;\n\tint\thitplane;\n\tmodel_t\t*model;\n\tfloat\tportalradius;\n\n\t// only run this code if we impacted on the portal's parent.\n\tif( trace->fraction == 1.0f && !trace->startsolid )\n\t\treturn;\n\n\t// decide which clipping hull to use, based on the size\n\tmodel = SV_ModelHandle( portal->v.modelindex );\n\n\tif( !model || model->type != mod_brush )\n\t\treturn;\n\n\t// make sure we use a sane valid position.\n\tif( trace->startsolid ) VectorCopy( start, worldpos );\n\telse VectorCopy( trace->endpos, worldpos );\n\n\t// determine the csg area. normals should be facing in\n\tAngleVectors( portal->v.angles, planes[1], planes[3], planes[5] );\n\tVectorNegate(planes[1], planes[0]);\n\tVectorNegate(planes[3], planes[2]);\n\tVectorNegate(planes[5], planes[4]);\n\n\tportalradius = model->radius * 0.5f;\n\tplanes[0][3] = DotProduct( portal->v.origin, planes[0] ) - (4.0f / 32.0f);\n\tplanes[1][3] = DotProduct( portal->v.origin, planes[1] ) - (4.0f / 32.0f);\t//an epsilon beyond the portal\n\tplanes[2][3] = DotProduct( portal->v.origin, planes[2] ) - portalradius;\n\tplanes[3][3] = DotProduct( portal->v.origin, planes[3] ) - portalradius;\n\tplanes[4][3] = DotProduct( portal->v.origin, planes[4] ) - portalradius;\n\tplanes[5][3] = DotProduct( portal->v.origin, planes[5] ) - portalradius;\n\n\t// if we're actually inside the csg region\n\tfor( plane = 0; plane < 6; plane++ )\n\t{\n\t\tfloat\td = DotProduct( worldpos, planes[plane] );\n\t\tvec3_t\tnearest;\n\n\t\tfor( k = 0; k < 3; k++ )\n\t\t\tnearest[k] = (planes[plane][k]>=0) ? trace_maxs[k] : trace_mins[k];\n\n\t\t// front plane gets further away with side\n\t\tif( !plane )\n\t\t{\n\t\t\tplanes[plane][3] -= DotProduct( nearest, planes[plane] );\n\t\t}\n\t\telse if( plane > 1 )\n\t\t{\n\t\t\t// side planes get nearer with size\n\t\t\tplanes[plane][3] += 24; // DotProduct( nearest, planes[plane] );\n\t\t}\n\n\t\tif( d - planes[plane][3] >= 0 )\n\t\t\tcontinue;\t// endpos is inside\n\t\telse return; // end is already outside\n\t}\n\n\t// yup, we're inside, the trace shouldn't end where it actually did\n\tbestfrac = 1;\n\thitplane = -1;\n\n\tfor( plane = 0; plane < 6; plane++ )\n\t{\n\t\tfloat\tds = DotProduct( start, planes[plane] ) - planes[plane][3];\n\t\tfloat\tde = DotProduct( end, planes[plane] ) - planes[plane][3];\n\t\tfloat\tfrac;\n\n\t\tif( ds >= 0 && de < 0 )\n\t\t{\n\t\t\tfrac = (ds) / (ds - de);\n\t\t\tif( frac < bestfrac )\n\t\t\t{\n\t\t\t\tif( frac < 0 )\n\t\t\t\t\tfrac = 0;\n\t\t\t\tbestfrac = frac;\n\t\t\t\thitplane = plane;\n\t\t\t}\n\t\t}\n\t}\n\n\ttrace->startsolid = trace->allsolid = false;\n\n\t// if we cross the front of the portal, don't shorten the trace,\n\t// that will artificially clip us\n\tif( hitplane == 0 && trace->fraction > bestfrac )\n\t\treturn;\n\n\t// okay, elongate to clip to the portal hole properly.\n\tVectorLerp( start, bestfrac, end, trace->endpos );\n\ttrace->fraction = bestfrac;\n\n\tif( hitplane >= 0 )\n\t{\n\t\tVectorCopy( planes[hitplane], trace->plane.normal );\n\t\ttrace->plane.dist = planes[hitplane][3];\n\t\tif( hitplane == 1 ) trace->ent = portal;\n\t}\n}\n\n/*\n==================\nSV_CustomClipMoveToEntity\n\nA part of physics engine implementation\nor custom physics implementation\n==================\n*/\nvoid SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace )\n{\n\t// initialize custom trace\n\tPM_InitTrace( trace, end );\n\n\tif( svgame.physFuncs.ClipMoveToEntity != NULL )\n\t{\n\t\t// do custom sweep test\n\t\tsvgame.physFuncs.ClipMoveToEntity( ent, start, mins, maxs, end, trace );\n\t}\n\telse\n\t{\n\t\t// function is missed, so we didn't hit anything\n\t\ttrace->allsolid = false;\n\t}\n}\n\n/*\n====================\nSV_ClipToEntity\n\ngeneric clip function\n====================\n*/\nstatic qboolean SV_ClipToEntity( edict_t *touch, moveclip_t *clip )\n{\n\ttrace_t\ttrace;\n\tmodel_t\t*mod;\n\n\tif( touch->v.groupinfo && SV_IsValidEdict( clip->passedict ) && clip->passedict->v.groupinfo != 0 )\n\t{\n\t\tif( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))\n\t\t\treturn true;\n\n\t\tif( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))\n\t\t\treturn true;\n\t}\n\n\tif( touch == clip->passedict || touch->v.solid == SOLID_NOT )\n\t\treturn true;\n\n\tif( touch->v.solid == SOLID_TRIGGER )\n\t\tHost_Error( \"trigger in clipping list\\n\" );\n\n\t// custom user filter\n\tif( svgame.dllFuncs2.pfnShouldCollide )\n\t{\n\t\tif( !svgame.dllFuncs2.pfnShouldCollide( touch, clip->passedict ))\n\t\t\treturn true;\n\t}\n\n\t// monsterclip filter (solid custom is a static or dynamic bodies)\n\tif( touch->v.solid == SOLID_BSP || touch->v.solid == SOLID_CUSTOM )\n\t{\n\t\t// func_monsterclip works only with monsters that have same flag!\n\t\tif( FBitSet( touch->v.flags, FL_MONSTERCLIP ) && !clip->monsterclip )\n\t\t\treturn true;\n\t}\n\telse\n\t{\n\t\t// ignore all monsters but pushables\n\t\tif( clip->type == MOVE_NOMONSTERS && touch->v.movetype != MOVETYPE_PUSHSTEP )\n\t\t\treturn true;\n\t}\n\n\tmod = SV_ModelHandle( touch->v.modelindex );\n\n\tif( mod && mod->type == mod_brush && clip->ignoretrans )\n\t{\n\t\t// we ignore brushes with rendermode != kRenderNormal and without FL_WORLDBRUSH set\n\t\tif( touch->v.rendermode != kRenderNormal && !FBitSet( touch->v.flags, FL_WORLDBRUSH ))\n\t\t\treturn true;\n\t}\n\n\tif( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax ))\n\t\treturn true;\n\n\t// aditional check to intersects clients with sphere\n\tif( touch->v.solid != SOLID_SLIDEBOX && !SV_CheckSphereIntersection( touch, clip->start, clip->end ))\n\t\treturn true;\n\n\t// Xash3D extension\n\tif( SV_IsValidEdict( clip->passedict ) && clip->passedict->v.solid == SOLID_TRIGGER )\n\t{\n\t\t// never collide items and player (because call \"give\" always stuck item in player\n\t\t// and total trace returns fail (old half-life bug)\n\t\t// items touch should be done in SV_TouchLinks not here\n\t\tif( FBitSet( touch->v.flags, FL_CLIENT|FL_FAKECLIENT ))\n\t\t\treturn true;\n\t}\n\n\t// g-cont. make sure what size is really zero - check all the components\n\tif( SV_IsValidEdict( clip->passedict ) && !VectorIsNull( clip->passedict->v.size ) && VectorIsNull( touch->v.size ))\n\t\treturn true; // points never interact\n\n\t// might intersect, so do an exact clip\n\tif( clip->trace.allsolid ) return false;\n\n\tif( SV_IsValidEdict( clip->passedict ))\n\t{\n\t \tif( touch->v.owner == clip->passedict )\n\t\t\treturn true; // don't clip against own missiles\n\t\tif( clip->passedict->v.owner == touch )\n\t\t\treturn true; // don't clip against owner\n\t}\n\n\t// make sure we don't hit the world if we're inside the portal\n\tif( touch->v.solid == SOLID_PORTAL )\n\t\tSV_PortalCSG( touch, clip->mins, clip->maxs, clip->start, clip->end, &clip->trace );\n\n\tif( touch->v.solid == SOLID_CUSTOM )\n\t\tSV_CustomClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );\n\telse if( FBitSet( touch->v.flags, FL_MONSTER ))\n\t\tSV_ClipMoveToEntity( touch, clip->start, clip->mins2, clip->maxs2, clip->end, &trace );\n\telse SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );\n\n\tclip->trace = World_CombineTraces( &clip->trace, &trace, touch );\n\n\treturn true;\n}\n\n/*\n====================\nSV_ClipToLinks\n\nMins and maxs enclose the entire area swept by the move\n====================\n*/\nstatic void SV_ClipToLinks( areanode_t *node, moveclip_t *clip )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*touch;\n\n\t// touch linked edicts\n\tfor( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\n\t\ttouch = EDICT_FROM_AREA( l );\n\n\t\tif( !SV_ClipToEntity( touch, clip ))\n\t\t\treturn; // trace.allsoild\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( clip->boxmaxs[node->axis] > node->dist )\n\t\tSV_ClipToLinks( node->children[0], clip );\n\tif( clip->boxmins[node->axis] < node->dist )\n\t\tSV_ClipToLinks( node->children[1], clip );\n}\n\n/*\n====================\nSV_ClipToPortals\n\nMins and maxs enclose the entire area swept by the move\n====================\n*/\nstatic void SV_ClipToPortals( areanode_t *node, moveclip_t *clip )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*touch;\n\n\t// touch linked edicts\n\tfor( l = node->portal_edicts.next; l != &node->portal_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\n\t\ttouch = EDICT_FROM_AREA( l );\n\n\t\tif( !SV_ClipToEntity( touch, clip ))\n\t\t\treturn; // trace.allsoild\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( clip->boxmaxs[node->axis] > node->dist )\n\t\tSV_ClipToPortals( node->children[0], clip );\n\tif( clip->boxmins[node->axis] < node->dist )\n\t\tSV_ClipToPortals( node->children[1], clip );\n}\n\n/*\n====================\nSV_ClipToWorldBrush\n\nMins and maxs enclose the entire area swept by the move\n====================\n*/\nstatic void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip )\n{\n\tlink_t\t*l, *next;\n\tedict_t\t*touch;\n\ttrace_t\ttrace;\n\n\tfor( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )\n\t{\n\t\tnext = l->next;\n\n\t\ttouch = EDICT_FROM_AREA( l );\n\n\t\tif( touch->v.solid != SOLID_BSP || touch == clip->passedict || !( touch->v.flags & FL_WORLDBRUSH ))\n\t\t\tcontinue;\n\n\t\tif( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax ))\n\t\t\tcontinue;\n\n\t\tif( clip->trace.allsolid ) return;\n\n\t\tSV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );\n\n\t\tclip->trace = World_CombineTraces( &clip->trace, &trace, touch );\n\t}\n\n\t// recurse down both sides\n\tif( node->axis == -1 ) return;\n\n\tif( clip->boxmaxs[node->axis] > node->dist )\n\t\tSV_ClipToWorldBrush( node->children[0], clip );\n\n\tif( clip->boxmins[node->axis] < node->dist )\n\t\tSV_ClipToWorldBrush( node->children[1], clip );\n}\n\n/*\n==================\nSV_Move\n==================\n*/\ntrace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip )\n{\n\tmoveclip_t clip = { 0 };\n\n\tSV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace );\n\n\tif( clip.trace.fraction != 0.0f )\n\t{\n\t\tconst float trace_fraction = clip.trace.fraction;\n\t\tvec3_t trace_endpos;\n\t\tVectorCopy( clip.trace.endpos, trace_endpos );\n\n\t\tclip.trace.fraction = 1.0f;\n\t\tclip.start = start;\n\t\tclip.end = trace_endpos;\n\t\tclip.type = (type & 0xFF);\n\t\tclip.ignoretrans = type >> 8;\n\t\tclip.monsterclip = false;\n\t\tclip.passedict = (e) ? e : EDICT_NUM( 0 );\n\t\tclip.mins = mins;\n\t\tclip.maxs = maxs;\n\n\t\tif( monsterclip && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))\n\t\t\tclip.monsterclip = true;\n\n\t\tif( clip.type == MOVE_MISSILE )\n\t\t{\n\t\t\tVectorSet( clip.mins2, -15.0f, -15.0f, -15.0f );\n\t\t\tVectorSet( clip.maxs2,  15.0f,  15.0f,  15.0f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( mins, clip.mins2 );\n\t\t\tVectorCopy( maxs, clip.maxs2 );\n\t\t}\n\n\t\tWorld_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs );\n\t\tSV_ClipToLinks( sv_areanodes, &clip );\n\t\tSV_ClipToPortals( sv_areanodes, &clip );\n\n\t\tclip.trace.fraction *= trace_fraction;\n\t\tsvgame.globals->trace_ent = clip.trace.ent;\n\t}\n\n\tSV_CopyTraceToGlobal( &clip.trace );\n\n\treturn clip.trace;\n}\n\n/*\n==================\nSV_MoveNoEnts\n==================\n*/\ntrace_t GAME_EXPORT SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e )\n{\n\tmoveclip_t\tclip;\n\tvec3_t\t\ttrace_endpos;\n\tfloat\t\ttrace_fraction;\n\n\tmemset( &clip, 0, sizeof( moveclip_t ));\n\tSV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace );\n\n\tif( clip.trace.fraction != 0.0f )\n\t{\n\t\tVectorCopy( clip.trace.endpos, trace_endpos );\n\t\ttrace_fraction = clip.trace.fraction;\n\t\tclip.trace.fraction = 1.0f;\n\t\tclip.start = start;\n\t\tclip.end = trace_endpos;\n\t\tclip.type = (type & 0xFF);\n\t\tclip.ignoretrans = type >> 8;\n\t\tclip.monsterclip = false;\n\t\tclip.passedict = (e) ? e : EDICT_NUM( 0 );\n\t\tclip.mins = mins;\n\t\tclip.maxs = maxs;\n\n\t\tVectorCopy( mins, clip.mins2 );\n\t\tVectorCopy( maxs, clip.maxs2 );\n\n\t\tWorld_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs );\n\t\tSV_ClipToWorldBrush( sv_areanodes, &clip );\n\t\tSV_ClipToPortals( sv_areanodes, &clip );\n\n\t\tclip.trace.fraction *= trace_fraction;\n\t\tsvgame.globals->trace_ent = clip.trace.ent;\n\t}\n\n\tSV_CopyTraceToGlobal( &clip.trace );\n\n\treturn clip.trace;\n}\n\n/*\n==================\nSV_TraceSurface\n\nfind the face where the traceline hit\nassume pTextureEntity is valid\n==================\n*/\nmsurface_t *GAME_EXPORT SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end )\n{\n\tmatrix4x4\t\tmatrix;\n\tmodel_t\t\t*bmodel;\n\thull_t\t\t*hull;\n\tvec3_t\t\tstart_l, end_l;\n\tvec3_t\t\toffset;\n\n\tbmodel = SV_ModelHandle( ent->v.modelindex );\n\tif( !bmodel || bmodel->type != mod_brush )\n\t\treturn NULL;\n\n\thull = SV_HullForBsp( ent, vec3_origin, vec3_origin, offset );\n\n\tVectorSubtract( start, offset, start_l );\n\tVectorSubtract( end, offset, end_l );\n\n\t// rotate start and end into the models frame of reference\n\tif( !VectorIsNull( ent->v.angles ))\n\t{\n\t\tMatrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f );\n\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t}\n\n\treturn PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l );\n}\n\n/*\n==================\nSV_TraceTexture\n\nfind the face where the traceline hit\nassume pTextureEntity is valid\n==================\n*/\nconst char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end )\n{\n\tmsurface_t\t*surf = SV_TraceSurface( ent, start, end );\n\n\tif( !surf || !surf->texinfo || !surf->texinfo->texture )\n\t\treturn NULL;\n\n\treturn surf->texinfo->texture->name;\n}\n\n/*\n==================\nSV_MoveToss\n==================\n*/\ntrace_t SV_MoveToss( edict_t *tossent, edict_t *ignore )\n{\n\tfloat \tgravity;\n\tvec3_t\tmove, end;\n\tvec3_t\toriginal_origin;\n\tvec3_t\toriginal_velocity;\n\tvec3_t\toriginal_angles;\n\tvec3_t\toriginal_avelocity;\n\ttrace_t\ttrace;\n\tint\ti;\n\n\tVectorCopy( tossent->v.origin, original_origin );\n\tVectorCopy( tossent->v.velocity, original_velocity );\n\tVectorCopy( tossent->v.angles, original_angles );\n\tVectorCopy( tossent->v.avelocity, original_avelocity );\n\tgravity = tossent->v.gravity * sv_gravity.value * 0.05f;\n\n\tfor( i = 0; i < 200; i++ )\n\t{\n\t\tSV_CheckVelocity( tossent );\n\t\ttossent->v.velocity[2] -= gravity;\n\t\tVectorMA( tossent->v.angles, 0.05f, tossent->v.avelocity, tossent->v.angles );\n\t\tVectorScale( tossent->v.velocity, 0.05f, move );\n\t\tVectorAdd( tossent->v.origin, move, end );\n\t\ttrace = SV_Move( tossent->v.origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent, false );\n\t\tVectorCopy( trace.endpos, tossent->v.origin );\n\t\tif( trace.fraction < 1.0f ) break;\n\t}\n\n\tVectorCopy( original_origin, tossent->v.origin );\n\tVectorCopy( original_velocity, tossent->v.velocity );\n\tVectorCopy( original_angles, tossent->v.angles );\n\tVectorCopy( original_avelocity, tossent->v.avelocity );\n\n\treturn trace;\n}\n\n/*\n===============================================================================\n\n\tLIGHTING INFO\n\n===============================================================================\n*/\n\n/*\n=================\nSV_RecursiveLightPoint\n=================\n*/\nstatic qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end, vec3_t point_color )\n{\n\tfloat front, back, frac;\n\tint i, side;\n\tvec3_t mid;\n\tmnode_t *children[2];\n\tint numsurfaces, firstsurface;\n\n\t// didn't hit anything\n\tif( !node || node->contents < 0 )\n\t\treturn false;\n\n\t// calculate mid point\n\tfront = PlaneDiff( start, node->plane );\n\tback = PlaneDiff( end, node->plane );\n\n\tnode_children( children, node, model );\n\n\tside = front < 0.0f;\n\tif(( back < 0.0f ) == side )\n\t\treturn SV_RecursiveLightPoint( model, children[side], start, end, point_color );\n\n\tfrac = front / ( front - back );\n\n\tVectorLerp( start, frac, end, mid );\n\n\t// co down front side\n\tif( SV_RecursiveLightPoint( model, children[side], start, mid, point_color ))\n\t\treturn true; // hit something\n\n\tif(( back < 0.0f ) == side )\n\t\treturn false; // didn't hit anything\n\n\t// check for impact on this node\n\tnumsurfaces = node_numsurfaces( node, model );\n\tfirstsurface = node_firstsurface( node, model );\n\tfor( i = 0; i < numsurfaces; i++ )\n\t{\n\t\tconst msurface_t *surf = &model->surfaces[firstsurface + i];\n\t\tconst mextrasurf_t *info = surf->info;\n\t\tint smax, tmax, map, size;\n\t\tint sample_size;\n\t\tfloat ds, dt, s, t;\n\t\tconst color24 *lm;\n\n\t\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\t\tcontinue;\t// no lightmaps\n\n\t\ts = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3];\n\t\tt = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3];\n\n\t\tif( s < info->lightmapmins[0] || t < info->lightmapmins[1] )\n\t\t\tcontinue;\n\n\t\tds = s - info->lightmapmins[0];\n\t\tdt = t - info->lightmapmins[1];\n\n\t\tif ( ds > info->lightextents[0] || dt > info->lightextents[1] )\n\t\t\tcontinue;\n\n\t\tif( !surf->samples )\n\t\t\treturn true;\n\n\t\tsample_size = Mod_SampleSizeForFace( surf );\n\t\tsmax = (info->lightextents[0] / sample_size) + 1;\n\t\ttmax = (info->lightextents[1] / sample_size) + 1;\n\t\tds /= sample_size;\n\t\tdt /= sample_size;\n\n\t\tVectorClear( point_color );\n\n\t\tlm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );\n\t\tsize = smax * tmax;\n\n\t\tfor( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )\n\t\t{\n\t\t\tfloat scale = sv.lightstyles[surf->styles[map]].value;\n\n\t\t\tpoint_color[0] += lm->r * scale;\n\t\t\tpoint_color[1] += lm->g * scale;\n\t\t\tpoint_color[2] += lm->b * scale;\n\n\t\t\tlm += size; // skip to next lightmap\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// go down back side\n\treturn SV_RecursiveLightPoint( model, children[!side], mid, end, point_color );\n}\n\n/*\n==================\nSV_SetLightStyle\n\nneeds to get correct working SV_LightPoint\n==================\n*/\nvoid SV_SetLightStyle( int style, const char* s, float f )\n{\n\tint\tj, k;\n\n\tj = Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern ));\n\tsv.lightstyles[style].time = f;\n\tsv.lightstyles[style].length = j;\n\n\tfor( k = 0; k < j; k++ )\n\t\tsv.lightstyles[style].map[k] = (float)(s[k] - 'a');\n\n\tif( sv.state != ss_active ) return;\n\n\t// tell the clients about changed lightstyle\n\tMSG_BeginServerCmd( &sv.reliable_datagram, svc_lightstyle );\n\tMSG_WriteByte( &sv.reliable_datagram, style );\n\tMSG_WriteString( &sv.reliable_datagram, sv.lightstyles[style].pattern );\n\tMSG_WriteFloat( &sv.reliable_datagram, sv.lightstyles[style].time );\n}\n\n/*\n==================\nSV_LightForEntity\n\ngrab the ambient lighting color for current point\n==================\n*/\nint SV_LightForEntity( edict_t *pEdict )\n{\n\tvec3_t point_color = { 1.0f, 1.0f, 1.0f };\n\tvec3_t start, end;\n\n\tif( !SV_IsValidEdict( pEdict ))\n\t\treturn -1;\n\n\tif( FBitSet( pEdict->v.effects, EF_FULLBRIGHT ) || !sv.worldmodel->lightdata )\n\t\treturn 255;\n\n\t// player has more precise light level that come from client-side\n\tif( FBitSet( pEdict->v.flags, FL_CLIENT ))\n\t\treturn pEdict->v.light_level;\n\n\tVectorCopy( pEdict->v.origin, start );\n\tVectorCopy( pEdict->v.origin, end );\n\n\tif( FBitSet( pEdict->v.effects, EF_INVLIGHT ))\n\t\tend[2] = start[2] + world.size[2];\n\telse end[2] = start[2] - world.size[2];\n\n\tSV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end, point_color );\n\n\treturn VectorAvg( point_color );\n}\n"
  },
  {
    "path": "engine/shake.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef SHAKE_H\n#define SHAKE_H\n\n// Screen / View effects\n\n// screen shake\nextern int gmsgShake;\n\n// This structure is sent over the net to describe a screen shake event\ntypedef struct\n{\n\tunsigned short\tamplitude;\t// FIXED 4.12 amount of shake\n\tunsigned short \tduration;\t\t// FIXED 4.12 seconds duration\n\tunsigned short\tfrequency;\t// FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble)\n} ScreenShake;\n\n// Fade in/out\nextern int gmsgFade;\n\n#define FFADE_IN\t\t0x0000\t\t// Just here so we don't pass 0 into the function\n#define FFADE_OUT\t\t0x0001\t\t// Fade out (not in)\n#define FFADE_MODULATE\t0x0002\t\t// Modulate (don't blend)\n#define FFADE_STAYOUT\t0x0004\t\t// ignores the duration, stays faded out until new ScreenFade message received\n#define FFADE_LONGFADE\t0x0008\t\t// used to indicate the fade can be longer than 16 seconds (added for czero)\n\n// This structure is sent over the net to describe a screen fade event\ntypedef struct\n{\n\tunsigned short \tduration;\t\t// FIXED 4.12 seconds duration\n\tunsigned short \tholdTime;\t\t// FIXED 4.12 seconds duration until reset (fade & hold)\n\tshort\t\tfadeFlags;\t// flags\n\tbyte\t\tr, g, b, a;\t// fade to color ( max alpha )\n} ScreenFade;\n\n#endif // SHAKE_H\n"
  },
  {
    "path": "engine/sprite.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef SPRITE_H\n#define SPRITE_H\n\n#include \"build.h\"\n#include STDINT_H\n#include \"synctype.h\"\n\n/*\n==============================================================================\n\nSPRITE MODELS\n\n.spr extended version (Half-Life compatible sprites with some Xash3D extensions)\n==============================================================================\n*/\n\n#define IDSPRITEHEADER\t(('P'<<24)+('S'<<16)+('D'<<8)+'I')\t// little-endian \"IDSP\"\n\n#define SPRITE_VERSION_Q1\t1\t\t\t\t// Quake sprites\n#define SPRITE_VERSION_HL\t2\t\t\t\t// Half-Life sprites\n#define SPRITE_VERSION_32\t32\t\t\t\t// Captain Obvious mode on\n\ntypedef enum\n{\n\tFRAME_SINGLE = 0,\n\tFRAME_GROUP,\n\tFRAME_ANGLED\t\t\t// Xash3D ext\n} frametype_t;\n\ntypedef enum\n{\n\tSPR_NORMAL = 0,\n\tSPR_ADDITIVE,\n\tSPR_INDEXALPHA,\n\tSPR_ALPHTEST,\n} drawtype_t;\n\ntypedef enum\n{\n\tSPR_FWD_PARALLEL_UPRIGHT = 0,\n\tSPR_FACING_UPRIGHT,\n\tSPR_FWD_PARALLEL,\n\tSPR_ORIENTED,\n\tSPR_FWD_PARALLEL_ORIENTED,\n} angletype_t;\n\ntypedef enum\n{\n\tSPR_CULL_FRONT = 0,\t\t\t// oriented sprite will be draw with one face\n\tSPR_CULL_NONE,\t\t\t// oriented sprite will be draw back face too\n} facetype_t;\n\n// generic helper\ntypedef struct\n{\n\tint32_t\t\tident;\t\t// LittleLong 'ISPR'\n\tint32_t\t\tversion;\t\t// current version 2\n} dsprite_t;\n\nSTATIC_CHECK_SIZEOF( dsprite_t, 8, 8 );\n\ntypedef struct\n{\n\tint32_t\t\tident;\t\t// LittleLong 'ISPR'\n\tint32_t\t\tversion;\t\t// current version 2\n\tint32_t\t\ttype;\t\t// camera align\n\tfloat\t\tboundingradius;\t// quick face culling\n\tint32_t\t\tbounds[2];\t// mins\\maxs\n\tint32_t\t\tnumframes;\t// including groups\n\tfloat\t\tbeamlength;\t// ???\n\tuint32_t\tsynctype;\t\t// animation synctype, was synctype_t\n} dsprite_q1_t;\n\nSTATIC_CHECK_SIZEOF( dsprite_q1_t, 36, 36 );\n\ntypedef struct\n{\n\tint32_t\t\tident;\t\t// LittleLong 'ISPR'\n\tint32_t\t\tversion;\t\t// current version 2\n\tuint32_t\ttype;\t\t// camera align, was angletype_t\n\tuint32_t\ttexFormat;\t// rendering mode, was drawtype_t\n\tint32_t\t\tboundingradius;\t// quick face culling\n\tint32_t\t\tbounds[2];\t// mins\\maxs\n\tint32_t\t\tnumframes;\t// including groups\n\tuint32_t\tfacetype;\t\t// cullface (Xash3D ext), was facetype_t\n\tuint32_t\tsynctype;\t\t// animation synctype, was synctype_t\n} dsprite_hl_t;\n\nSTATIC_CHECK_SIZEOF( dsprite_hl_t, 40, 40 );\n\ntypedef struct\n{\n\tint32_t\t\torigin[2];\n\tint32_t\t\twidth;\n\tint32_t\t\theight;\n} dspriteframe_t;\n\nSTATIC_CHECK_SIZEOF( dspriteframe_t, 16, 16 );\n\ntypedef struct\n{\n\tint32_t\t\tnumframes;\n} dspritegroup_t;\n\nSTATIC_CHECK_SIZEOF( dspritegroup_t, 4, 4 );\n\ntypedef struct\n{\n\tfloat\t\tinterval;\n} dspriteinterval_t;\n\nSTATIC_CHECK_SIZEOF( dspriteinterval_t, 4, 4 );\n\ntypedef struct\n{\n\tuint32_t\ttype; // was frametype_t\n} dframetype_t;\n\nSTATIC_CHECK_SIZEOF( dframetype_t, 4, 4 );\n\n#endif//SPRITE_H\n"
  },
  {
    "path": "engine/studio.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#pragma once\n#ifndef STUDIO_H\n#define STUDIO_H\n\n/*\n==============================================================================\n\nSTUDIO MODELS\n\nStudio models are position independent, so the cache manager can move them.\n==============================================================================\n*/\n\n// header\n#define STUDIO_VERSION\t10\n#define IDSTUDIOHEADER\t(('T'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian \"IDST\"\n#define IDSEQGRPHEADER\t(('Q'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian \"IDSQ\"\n\n// studio limits\n#define MAXSTUDIOVERTS\t\t16384\t// max vertices per submodel\n#define MAXSTUDIOSEQUENCES\t\t256\t// total animation sequences\n#define MAXSTUDIOSKINS\t\t256\t// total textures\n#define MAXSTUDIOSRCBONES\t\t512\t// bones allowed at source movement\n#define MAXSTUDIOBONES\t\t128\t// total bones actually used\n#define MAXSTUDIOMODELS\t\t32\t// sub-models per model\n#define MAXSTUDIOBODYPARTS\t\t32\t// body parts per submodel\n#define MAXSTUDIOGROUPS\t\t16\t// sequence groups (e.g. barney01.mdl, barney02.mdl, e.t.c)\n#define MAXSTUDIOMESHES\t\t256\t// max textures per model\n#define MAXSTUDIOCONTROLLERS\t\t32\t// max controllers per model\n#define MAXSTUDIOATTACHMENTS\t\t64\t// max attachments per model\n#define MAXSTUDIOBONEWEIGHTS\t\t4\t// absolute hardware limit!\n#define MAXSTUDIONAME\t\t32\t// a part of specs\n#define MAXSTUDIOPOSEPARAM\t\t24\n#define MAX_STUDIO_LIGHTMAP_SIZE\t256\t// must match with engine const!!!\n\n// client-side model flags\n#define STUDIO_ROCKET\t\t(1U<<0)\t// leave a trail\n#define STUDIO_GRENADE\t\t(1U<<1)\t// leave a trail\n#define STUDIO_GIB\t\t(1U<<2)\t// leave a trail\n#define STUDIO_ROTATE\t\t(1U<<3)\t// rotate (bonus items)\n#define STUDIO_TRACER\t\t(1U<<4)\t// green split trail\n#define STUDIO_ZOMGIB\t\t(1U<<5)\t// small blood trail\n#define STUDIO_TRACER2\t\t(1U<<6)\t// orange split trail + rotate\n#define STUDIO_TRACER3\t\t(1U<<7)\t// purple trail\n#define STUDIO_AMBIENT_LIGHT\t\t(1U<<8)\t// force to use ambient shading\n#define STUDIO_TRACE_HITBOX\t\t(1U<<9)\t// always use hitbox trace instead of bbox\n#define STUDIO_FORCE_SKYLIGHT\t\t(1U<<10)\t// always grab lightvalues from the sky settings (even if sky is invisible)\n\n#define STUDIO_HAS_BUMP\t\t\t(1U<<16)\t// loadtime set\n#define STUDIO_STATIC_PROP\t\t(1U<<29)\t// hint for engine\n#define STUDIO_HAS_BONEINFO\t\t(1U<<30)\t// extra info about bones (pose matrix, procedural index etc)\n#define STUDIO_HAS_BONEWEIGHTS\t(1U<<31)\t// yes we got support of bone weighting\n\n// lighting & rendermode options\n#define STUDIO_NF_FLATSHADE\t\t0x0001\n#define STUDIO_NF_CHROME\t\t0x0002\n#define STUDIO_NF_FULLBRIGHT\t\t0x0004\n#define STUDIO_NF_NOMIPS\t\t0x0008\t// ignore mip-maps\n#define STUDIO_NF_SMOOTH\t\t0x0010\t// smooth tangent space\n#define STUDIO_NF_ADDITIVE\t\t0x0020\t// rendering with additive mode\n#define STUDIO_NF_MASKED\t\t0x0040\t// use texture with alpha channel\n#define STUDIO_NF_NORMALMAP\t\t0x0080\t// indexed normalmap\n\n#define STUDIO_NF_GLOSSMAP\t\t0x0100\t// glossmap\n#define STUDIO_NF_GLOSSPOWER\t\t0x0200\n#define STUDIO_NF_LUMA\t\t\t0x0400\t// self-illuminate parts\n#define STUDIO_NF_ALPHASOLID\t\t0x0800\t// use with STUDIO_NF_MASKED to have solid alphatest surfaces for env_static\n#define STUDIO_NF_TWOSIDE\t\t0x1000\t// render mesh as twosided\n#define STUDIO_NF_HEIGHTMAP\t\t0x2000\n\n#define STUDIO_NF_NODRAW\t\t(1U<<16)\t// failed to create shader for this mesh\n#define STUDIO_NF_NODLIGHT\t\t(1U<<17)\t// failed to create dlight shader for this mesh\n#define STUDIO_NF_NOSUNLIGHT\t\t(1U<<18)\t// failed to create sun light shader for this mesh\n\n#define STUDIO_NF_HAS_ALPHA\t\t(1U<<20)\t// external texture has alpha-channel\n#define STUDIO_NF_HAS_DETAIL\t\t(1U<<21)\t// studiomodels has detail textures\n#define STUDIO_NF_COLORMAP\t\t(1U<<30)\t// internal system flag\n#define STUDIO_NF_UV_COORDS\t\t(1U<<31)\t// using half-float coords instead of ST\n\n// motion flags\n#define STUDIO_X\t\t\t0x0001\n#define STUDIO_Y\t\t\t0x0002\n#define STUDIO_Z\t\t\t0x0004\n#define STUDIO_XR\t\t\t0x0008\n#define STUDIO_YR\t\t\t0x0010\n#define STUDIO_ZR\t\t\t0x0020\n#define STUDIO_LX\t\t\t0x0040\n#define STUDIO_LY\t\t\t0x0080\n#define STUDIO_LZ\t\t\t0x0100\n#define STUDIO_LXR\t\t\t0x0200\n#define STUDIO_LYR\t\t\t0x0400\n#define STUDIO_LZR\t\t\t0x0800\n#define STUDIO_LINEAR\t\t\t0x1000\n#define STUDIO_QUADRATIC_MOTION\t\t0x2000\n#define STUDIO_RESERVED\t\t\t0x4000\t// g-cont. reserved one bit for me\n#define STUDIO_TYPES\t\t\t0x7FFF\n#define STUDIO_RLOOP\t\t\t0x8000\t// controller that wraps shortest distance\n\n// bonecontroller types\n#define STUDIO_MOUTH\t\t4\t// hardcoded\n\n// sequence flags\n#define STUDIO_LOOPING\t\t0x0001\t// ending frame should be the same as the starting frame\n#define STUDIO_SNAP\t\t\t0x0002\t// do not interpolate between previous animation and this one\n#define STUDIO_DELTA\t\t0x0004\t// this sequence \"adds\" to the base sequences, not slerp blends\n#define STUDIO_AUTOPLAY\t\t0x0008\t// temporary flag that forces the sequence to always play\n#define STUDIO_POST\t\t\t0x0010\t//\n#define STUDIO_ALLZEROS\t\t0x0020\t// this animation/sequence has no real animation data\n#define STUDIO_BLENDPOSE\t\t0x0040   \t// to differentiate GoldSrc style blending from Source style blending (with pose parameters)\n#define STUDIO_CYCLEPOSE\t\t0x0080\t// cycle index is taken from a pose parameter index\n#define STUDIO_REALTIME\t\t0x0100\t// cycle index is taken from a real-time clock, not the animations cycle index\n#define STUDIO_LOCAL\t\t0x0200\t// sequence has a local context sequence\n#define STUDIO_HIDDEN\t\t0x0400\t// don't show in default selection views\n#define STUDIO_IKRULES\t\t0x0800\t// sequence has IK-rules\n#define STUDIO_ACTIVITY\t\t0x1000\t// Has been updated at runtime to activity index\n#define STUDIO_EVENT\t\t0x2000\t// Has been updated at runtime to event index\n#define STUDIO_WORLD\t\t0x4000\t// sequence blends in worldspace\n#define STUDIO_LIGHT_FROM_ROOT\t0x8000\t// get lighting point from root bonepos not from entity origin\n\n// autolayer flags\n#define STUDIO_AL_POST\t\t0x0001\t//\n#define STUDIO_AL_SPLINE\t0x0002\t// convert layer ramp in/out curve is a spline instead of linear\n#define STUDIO_AL_XFADE\t\t0x0004\t// pre-bias the ramp curve to compense for a non-1 weight,\n\t\t\t\t\t// assuming a second layer is also going to accumulate\n#define STUDIO_AL_NOBLEND\t0x0008\t// animation always blends at 1.0 (ignores weight)\n#define STUDIO_AL_LOCAL\t\t0x0010\t// layer is a local context sequence\n#define STUDIO_AL_POSE\t\t0x0020\t// layer blends using a pose parameter instead of parent cycle\n\ntypedef struct studiohdr_s\n{\n\t// the model signature\n\tint32_t\t\tident;\n\n\t// studio model format version\n\tint32_t\t\tversion;\n\n\t// the model name\n\tchar\t\tname[64];\n\n\t// the total file size in bytes\n\tint32_t\t\tlength;\n\n\t// ideal eye position\n\tvec3_t\t\teyeposition;\n\n\t// ideal movement hull size\n\tvec3_t\t\tmin;\n\tvec3_t\t\tmax;\n\n\t// clipping bounding box\n\tvec3_t\t\tbbmin;\n\tvec3_t\t\tbbmax;\n\n\t// undocumented quake features flags\n\tint32_t\t\tflags;\n\n\t// the number of bones\n\tint32_t\t\tnumbones;\n\n\t// offset to the first bone chunk\n\tint32_t\t\tboneindex;\n\n\t// the number of bone controllers\n\tint32_t\t\tnumbonecontrollers;\n\n\t// offset to the first bone controller chunk\n\tint32_t\t\tbonecontrollerindex;\n\n\t// the number of hitboxes\n\tint32_t\t\tnumhitboxes;\n\n\t// offset to the first hitbox chunk\n\tint32_t\t\thitboxindex;\n\n\t// the number of sequences\n\tint32_t\t\tnumseq;\n\n\t// offset to the first sequence description chunk\n\tint32_t\t\tseqindex;\n\n\t// the number of sequence groups\n\tint32_t\t\tnumseqgroups;\n\n\t// offset to the first sequence group chunk\n\tint32_t\t\tseqgroupindex;\n\n\t// the number of textures\n\tint32_t\t\tnumtextures;\n\n\t// offset to the first texture chunk\n\tint32_t\t\ttextureindex;\n\n\t// offset to the first texture's image data\n\tint32_t\t\ttexturedataindex;\n\n\t// the number of replaceable textures\n\tint32_t\t\tnumskinref;\n\n\t// the number of skin families\n\tint32_t\t\tnumskinfamilies;\n\n\t// offset to the first replaceable texture\n\tint32_t\t\tskinindex;\n\n\t// the number of bodyparts\n\tint32_t\t\tnumbodyparts;\n\n\t// offset to the first bodypart\n\tint32_t\t\tbodypartindex;\n\n\t// the number of attachments\n\tint32_t\t\tnumattachments;\n\n\t// offset to the first attachment chunk\n\tint32_t\t\tattachmentindex;\n\n\t// offset to the second studio model header\n\tint32_t\t\tstudiohdr2index;\n\n\t// was \"soundindex\"\n\tint32_t\t\tunused;\n\n\t// was \"soundgroups\"\n\tint32_t\t\tunused2;\n\n\t// was \"soundgroupindex\"\n\tint32_t\t\tunused3;\n\n\t// the number of nodes in the sequence transition graph\n\tint32_t\t\tnumtransitions;\n\n\t// offset to the first sequence transition\n\tint32_t\t\ttransitionindex;\n} studiohdr_t;\n\n// extra header to hold more offsets\ntypedef struct\n{\n\t// number of pose parameters\n\tint32_t\t\tnumposeparameters;\n\n\t// offset to the first pose parameter\n\tint32_t\t\tposeparamindex;\n\n\t// number of IK-autoplaying locks\n\tint32_t\t\tnumikautoplaylocks;\n\n\t// offset to the first IK-autoplaying lock\n\tint32_t\t\tikautoplaylockindex;\n\n\t// number of IK-chains\n\tint32_t\t\tnumikchains;\n\n\t// offset to the first IK-chain\n\tint32_t\t\tikchainindex;\n\n\t// offset to the first key-value\n\tint32_t\t\tkeyvalueindex;\n\n\t// size of key-values\n\tint32_t\t\tkeyvaluesize;\n\n\t// number of hitbox sets\n\tint32_t\t\tnumhitboxsets;\n\n\t// offset to the first hitbox set\n\tint32_t\t\thitboxsetindex;\n\n\t// for future expansions\n\tint32_t\t\tunused[6];\n} studiohdr2_t;\n\n// header for demand loaded sequence group data\ntypedef struct\n{\n\t// the model signature\n\tint32_t\t\tid;\n\n\t// studio model format version\n\tint32_t\t\tversion;\n\n\t// the sequence group file name\n\tchar\t\tname[64];\n\n\t// the total file size in bytes\n\tint32_t\t\tlength;\n} studioseqhdr_t;\n\n// bone flags\n#define BONE_ALWAYS_PROCEDURAL\t\t0x0001\t// bone is always procedurally animated\n#define BONE_SCREEN_ALIGN_SPHERE\t0x0002\t// bone aligns to the screen, not constrained in motion.\n#define BONE_SCREEN_ALIGN_CYLINDER\t0x0004\t// bone aligns to the screen, constrained by it's own axis.\n#define BONE_JIGGLE_PROCEDURAL\t\t0x0008\n#define BONE_FIXED_ALIGNMENT\t\t0x0010\t// bone can't spin 360 degrees, all interpolation is normalized around a fixed orientation\n\n#define BONE_USED_MASK\t\t(BONE_USED_BY_HITBOX|BONE_USED_BY_ATTACHMENT|BONE_USED_BY_VERTEX|BONE_USED_BY_BONE_MERGE)\n#define BONE_USED_BY_ANYTHING\t\tBONE_USED_MASK\n#define BONE_USED_BY_HITBOX\t\t0x00000100\t// bone (or child) is used by a hit box\n#define BONE_USED_BY_ATTACHMENT\t\t0x00000200\t// bone (or child) is used by an attachment point\n#define BONE_USED_BY_VERTEX\t\t0x00000400\t// bone (or child) is used by the toplevel model via skinned vertex\n#define BONE_USED_BY_BONE_MERGE\t\t0x00000800\n\n// bones\ntypedef struct mstudiobone_s\n{\n\t// the bone name\n\tchar\t\tname[MAXSTUDIONAME];\n\n\t// the parent bone index. (-1) If it has no parent\n\tint32_t\t\tparent;\n\n\t// was \"flags\"\n\tint32_t\t\tunused;\n\n\t// 1vailable bone controller per motion type.\n\t// (-1) if no controller is available.\n\tint32_t\t\tbonecontroller[6];\n\n\t/* default position and rotation values where\n\t * scale[0] = position.X\n\t * scale[1] = position.Y\n\t * scale[2] = position.Z\n\t * scale[3] = rotation.X\n\t * scale[4] = rotation.Y\n\t * scale[5] = rotation.Z\n\t */\n\tvec_t\t\tvalue[6];\n\n\t/* compressed scale values where\n\t * scale[0] = position.X scale\n\t * scale[1] = position.Y scale\n\t * scale[2] = position.Z scale\n\t * scale[3] = rotation.X scale\n\t * scale[4] = rotation.Y scale\n\t * scale[5] = rotation.Z scale\n\t */\n\tvec_t\t\tscale[6];\n} mstudiobone_t;\n\n#define STUDIO_PROC_AXISINTERP\t1\n#define STUDIO_PROC_QUATINTERP\t2\n#define STUDIO_PROC_AIMATBONE\t3\n#define STUDIO_PROC_AIMATATTACH\t4\n#define STUDIO_PROC_JIGGLE\t5\n\ntypedef struct\n{\n\t// local transformation of this bone used to calc 3 point blend\n\tint32_t\t\tcontrol;\n\n\t// axis to check\n\tint32_t\t\taxis;\n\n\t// X+, X-, Y+, Y-, Z+, Z-\n\tvec3_t\t\tpos[6];\n\n\t// X+, X-, Y+, Y-, Z+, Z-\n\tvec4_t\t\tquat[6];\n} mstudioaxisinterpbone_t;\n\ntypedef struct\n{\n\t// 1.0f / radian angle of trigger influence\n\tvec_t\t\tinv_tolerance;\n\n\t// angle to match\n\tvec4_t\t\ttrigger;\n\n\t// new position\n\tvec3_t\t\tpos;\n\n\t// new angle\n\tvec4_t\t\tquat;\n} mstudioquatinterpinfo_t;\n\ntypedef struct\n{\n\t// local transformation to check\n\tint32_t\t\tcontrol;\n\tint32_t\t\tnumtriggers;\n\tint32_t\t\ttriggerindex;\n} mstudioquatinterpbone_t;\n\n// extra info for bones\ntypedef struct\n{\n\t// boneweighting reqiures\n\tvec_t\t\tposeToBone[3][4];\n\n\tvec4_t\t\tqAlignment;\n\n\tint32_t\t\tproctype;\n\n\t// procedural rule\n\tint32_t\t\tprocindex;\n\n\t// aligned bone rotation\n\tvec4_t\t\tquat;\n\n\t// for future expansions\n\tint32_t\t\treserved[10];\n} mstudioboneinfo_t;\n\n// JIGGLEBONES\n#define JIGGLE_IS_FLEXIBLE\t\t0x01\n#define JIGGLE_IS_RIGID\t\t\t0x02\n#define JIGGLE_HAS_YAW_CONSTRAINT\t0x04\n#define JIGGLE_HAS_PITCH_CONSTRAINT\t0x08\n#define JIGGLE_HAS_ANGLE_CONSTRAINT\t0x10\n#define JIGGLE_HAS_LENGTH_CONSTRAINT\t0x20\n#define JIGGLE_HAS_BASE_SPRING\t\t0x40\n#define JIGGLE_IS_BOING\t\t\t0x80\t// simple squash and stretch sinusoid \"boing\"\n\ntypedef struct\n{\n\tint32_t\t\tflags;\n\n\t// general params\n\tvec_t\t\tlength;\t\t// how from from bone base, along bone, is tip\n\tvec_t\t\ttipMass;\n\n\t// flexible params\n\tvec_t\t\tyawStiffness;\n\tvec_t\t\tyawDamping;\n\tvec_t\t\tpitchStiffness;\n\tvec_t\t\tpitchDamping;\n\tvec_t\t\talongStiffness;\n\tvec_t\t\talongDamping;\n\n\t// angle constraint\n\tvec_t\t\tangleLimit;\t// maximum deflection of tip in radians\n\n\t// yaw constraint\n\tvec_t\t\tminYaw;\t\t// in radians\n\tvec_t\t\tmaxYaw;\t\t// in radians\n\tvec_t\t\tyawFriction;\n\tvec_t\t\tyawBounce;\n\n\t// pitch constraint\n\tvec_t\t\tminPitch;\t\t// in radians\n\tvec_t\t\tmaxPitch;\t\t// in radians\n\tvec_t\t\tpitchFriction;\n\tvec_t\t\tpitchBounce;\n\n\t// base spring\n\tvec_t\t\tbaseMass;\n\tvec_t\t\tbaseStiffness;\n\tvec_t\t\tbaseDamping;\n\tvec_t\t\tbaseMinLeft;\n\tvec_t\t\tbaseMaxLeft;\n\tvec_t\t\tbaseLeftFriction;\n\tvec_t\t\tbaseMinUp;\n\tvec_t\t\tbaseMaxUp;\n\tvec_t\t\tbaseUpFriction;\n\tvec_t\t\tbaseMinForward;\n\tvec_t\t\tbaseMaxForward;\n\tvec_t\t\tbaseForwardFriction;\n\n\t// boing\n\tvec_t\t\tboingImpactSpeed;\n\tvec_t\t\tboingImpactAngle;\n\tvec_t\t\tboingDampingRate;\n\tvec_t\t\tboingFrequency;\n\tvec_t\t\tboingAmplitude;\n} mstudiojigglebone_t;\n\ntypedef struct\n{\n\tint32_t\t\tparent;\n\n\t// might be bone or attach\n\tint32_t\t\taim;\n\n\tvec3_t\t\taimvector;\n\tvec3_t\t\tupvector;\n\tvec3_t\t\tbasepos;\n} mstudioaimatbone_t;\n\n// bone controllers\ntypedef struct\n{\n\t// bone affected by this controller\n\tint32_t\t\tbone;\n\n\t// the motion type\n\tint32_t\t\ttype;\n\n\t// the minimum and maximum values\n\tvec_t\t\tstart;\n\tvec_t\t\tend;\n\n\t// was \"rest\"\n\tint32_t\t\tunused;\n\n\t// the bone controller channel\n\tint32_t\t\tindex;\n} mstudiobonecontroller_t;\n\n// intersection boxes\ntypedef struct\n{\n\t// the bone this hitbox follows\n\tint32_t\t\tbone;\n\n\t// the hit group\n\tint32_t\t\tgroup;\n\n\t// the hitbox minimum and maximum extents\n\tvec3_t\t\tbbmin;\n\tvec3_t\t\tbbmax;\n} mstudiobbox_t;\n\ntypedef struct\n{\n\tchar\t\tname[MAXSTUDIONAME];\n\tint32_t\t\tnumhitboxes;\n\tint32_t\t\thitboxindex;\n} mstudiohitboxset_t;\n\n// demand loaded sequence groups\ntypedef struct\n{\n\t// a textual name for this sequence group\n\tchar\t\tlabel[MAXSTUDIONAME];\n\n\t// the file name\n\tchar\t\tname[64];\n\n\t// was \"cache\"\n\tint32_t\t\tunused;\n\n\t// was \"data\"\n\tint32_t\t\tunused2;\n} mstudioseqgroup_t;\n\n// events\n#include \"studio_event.h\"\n\n#define STUDIO_ATTACHMENT_LOCAL\t(1<<0)\t// vectors are filled\n\n// attachment\ntypedef struct\n{\n\t// was \"name\"\n\tchar\t\tunused[MAXSTUDIONAME];\n\n\tint32_t\t\tflags;\n\n\t// the bone this attachment follows\n\tint32_t\t\tbone;\n\n\t// the attachment origin\n\tvec3_t\t\torg;\n\n\t// the attachment vectors\n\tvec3_t\t\tvectors[3];\n} mstudioattachment_t;\n\n#define IK_SELF\t\t1\n#define IK_WORLD\t\t2\n#define IK_GROUND\t\t3\n#define IK_RELEASE\t\t4\n#define IK_ATTACHMENT\t5\n#define IK_UNLATCH\t\t6\n\ntypedef struct\n{\n\tvec_t\t\tscale[6];\n\tuint16_t\toffset[6];\n} mstudioikerror_t;\n\ntypedef struct\n{\n\tint32_t\t\tindex;\n\n\tint32_t\t\ttype;\n\tint32_t\t\tchain;\n\n\tint32_t\t\tbone;\n\n\t// offset to the attachment\n\tint32_t\t\tattachment;\n\n\t// iktarget slot\n\t// usually same as chain\n\tint32_t\t\tslot;\n\n\tvec_t\t\theight;\n\tvec_t\t\tradius;\n\tvec_t\t\tfloor;\n\tvec3_t\t\tpos;\n\tvec4_t\t\tquat;\n\n\t// offset to the compressed IK error\n\tint32_t\t\tikerrorindex;\n\n\tint32_t\t\tiStart;\n\n\t// beginning of influence\n\tvec_t\t\tstart;\n\n\t// start of full influence\n\tvec_t\t\tpeak;\n\n\t// end of full influence\n\tvec_t\t\ttail;\n\n\t// end of all influence\n\tvec_t\t\tend;\n\n\t// frame footstep makes ground concact\n\tvec_t\t\tcontact;\n\n\t// how far down the foot should drop when reaching for IK\n\tvec_t\t\tdrop;\n\n\t// top of the foot box\n\tvec_t\t\ttop;\n\n\t// for future expansions\n\tint32_t\t\tunused[4];\n} mstudioikrule_t;\n\ntypedef struct\n{\n\tint32_t\t\tchain;\n\tvec_t\t\tflPosWeight;\n\tvec_t\t\tflLocalQWeight;\n\tint32_t\t\tflags;\n\n\t// for future expansions\n\tint32_t\t\tunused[4];\n} mstudioiklock_t;\n\ntypedef struct\n{\n\tint32_t\t\tendframe;\n\n\tint32_t\t\tmotionflags;\n\n\t// velocity at start of block\n\tvec_t\t\tv0;\n\n\t// velocity at end of block\n\tvec_t\t\tv1;\n\n\t// YAW rotation at end of this blocks movement\n\tvec_t\t\tangle;\n\n\t// movement vector relative to this blocks initial angle\n\tvec3_t\t\tvector;\n\n\t// relative to start of animation???\n\tvec3_t\t\tposition;\n} mstudiomovement_t;\n\n// additional info for each animation in sequence blend group or single sequence\ntypedef struct\n{\n\t// animation label (may be matched with sequence label)\n\tchar\t\tlabel[MAXSTUDIONAME];\n\n\t// frames per second (match with sequence fps or be different)\n\tvec_t\t\tfps;\n\n\t// looping/non-looping flags\n\tint32_t\t\tflags;\n\n\t// number of frames per animation\n\tint32_t\t\tnumframes;\n\n\t// number of piecewise movements\n\tint32_t\t\tnummovements;\n\n\t// offset to the first piecewise movement\n\tint32_t\t\tmovementindex;\n\n\t// number of IK-rules\n\tint32_t\t\tnumikrules;\n\n\t// offset to the first IK-rule\n\tint32_t\t\tikruleindex;\n\n\t// for future expansions\n\tint32_t\t\tunused[8];\n} mstudioanimdesc_t;\n\n// autoplaying sequences\ntypedef struct\n{\n\tint16_t\t\tiSequence;\n\tint16_t\t\tiPose;\n\tint32_t\t\tflags;\n\n\t// beginning of influence\n\tvec_t\t\tstart;\n\n\t// start of full influence\n\tvec_t\t\tpeak;\n\n\t// end of full influence\n\tvec_t\t\ttail;\n\n\t// end of all influence\n\tvec_t\t\tend;\n} mstudioautolayer_t;\n\n// sequence descriptions\ntypedef struct mstudioseqdesc_s\n{\n\t// the sequence name\n\tchar\t\tlabel[MAXSTUDIONAME];\n\n\t// frames per second\n\tvec_t\t\tfps;\n\n\t// looping/non-looping flags\n\tint32_t\t\tflags;\n\n\t// the sequence activity\n\tint32_t\t\tactivity;\n\n\t// the sequence activity weight\n\tint32_t\t\tactweight;\n\n\t// the number of animation events\n\tint32_t\t\tnumevents;\n\n\t// offset to the first animation event chunk\n\tint32_t\t\teventindex;\n\n\t// the number of frames in the sequence\n\tint32_t\t\tnumframes;\n\n\t// offset to the first weight list\n\tint32_t\t\tweightlistindex;\n\n\t// offset to the first IK lock\n\tint32_t\t\tiklockindex;\n\n\t// linear motion type\n\tint32_t\t\tmotiontype;\n\n\t// offset to the first pose parameter\n\tint32_t\t\tmotionbone;\n\n\t// linear motion\n\tvec3_t\t\tlinearmovement;\n\n\t// offset to the first autolayer description\n\tint32_t\t\tautolayerindex;\n\n\t// offset to the first local key-value\n\tint32_t\t\tkeyvalueindex;\n\n\t// the sequence minimum and maximum extents\n\tvec3_t\t\tbbmin;\n\tvec3_t\t\tbbmax;\n\n\t// The number of blend animations\n\tint32_t\t\tnumblends;\n\n\t// offset to thefirst mstudioanim_t chunk.\n\t// this offset is relative to the studioseqhdr_t of the file\n\t// that contains the animation data.\n\tint32_t\t\tanimindex;\n\n\t// the motion type of each blend controller\n\tint32_t\t\tblendtype[2];\n\n\t// the starting value of each blend controller\n\tvec_t\t\tblendstart[2];\n\n\t// the ending value of each blend controller\n\tvec_t\t\tblendend[2];\n\n\t// 255 x 255 blends should be enough\n\tuint8_t\t\tgroupsize[2];\n\n\t// number of autoplaying layers\n\tuint8_t\t\tnumautolayers;\n\n\t// number of IK-locks per sequence\n\tuint8_t\t\tnumiklocks;\n\n\t// the sequence group\n\tint32_t\t\tseqgroup;\n\n\t// the node at entry in the sequence transition graph\n\tint32_t\t\tentrynode;\n\n\t// the node at exit in the sequence transition graph\n\tint32_t\t\texitnode;\n\n\t// transition rules\n\tuint8_t\t\tnodeflags;\n\n\t// index of pose parameter to use as cycle index\n\tuint8_t\t\tcycleposeindex;\n\n\t// ideal cross fade in time (0.2 secs default) time = (fadeintime / 100)\n\tuint8_t\t\tfadeintime;\n\n\t// ideal cross fade out time (0.2 msecs default)  time = (fadeouttime / 100)\n\tuint8_t\t\tfadeouttime;\n\n\t// mstudioanimdesc_t [blend]\n\tint32_t\t\tanimdescindex;\n} mstudioseqdesc_t;\n\ntypedef struct\n{\n\t// pose parameter name\n\tchar\t\tname[MAXSTUDIONAME];\n\n\t// ????\n\tint32_t\t\tflags;\n\n\t// starting value\n\tvec_t\t\tstart;\n\n\t// ending value\n\tvec_t\t\tend;\n\n\t// looping range, 0 for no looping, 360 for rotations, etc\n\tvec_t\t\tloop;\n} mstudioposeparamdesc_t;\n\n// offsets to the animation frames\ntypedef struct mstudioanim_s\n{\n\tuint16_t\toffset[6];\n} mstudioanim_t;\n\n// animation frames\ntypedef union\n{\n\tstruct\n\t{\n\t\tuint8_t\tvalid;\n\t\tuint8_t\ttotal;\n\t} num;\n\tint16_t\t\tvalue;\n} mstudioanimvalue_t;\n\n// body part index\ntypedef struct\n{\n\t// the bodypart name\n\tchar\t\tname[64];\n\n\t// the number of available models for this bodypart\n\tint32_t\t\tnummodels;\n\n\t// used to convert from a global model index\n\t// to a local bodypart model index\n\tint32_t\t\tbase;\n\n\t// the offset to the first model chunk\n\tint32_t\t\tmodelindex;\t// index into models array\n} mstudiobodyparts_t;\n\n// skin info\ntypedef struct mstudiotex_s\n{\n\t// texture file name\n\tchar\t\tname[64];\n\n\t// texture flags\n\tuint32_t\tflags;\n\n\t// texture width in pixels\n\tint32_t\t\twidth;\n\n\t// texture height in pixels\n\tint32_t\t\theight;\n\n\t// offset to the image data\n\t// this offset is relative to the texture file header\n\tint32_t\t\tindex;\n} mstudiotexture_t;\n\n// ikinfo\ntypedef struct\n{\n\tint32_t\t\tbone;\n\n\t// ideal bending direction (per link, if applicable)\n\tvec3_t\t\tkneeDir;\n\n\t// unused\n\tvec3_t\t\tunused0;\n} mstudioiklink_t;\n\ntypedef struct\n{\n\tchar\t\tname[MAXSTUDIONAME];\n\tint32_t\t\tlinktype;\n\tint32_t\t\tnumlinks;\n\tint32_t\t\tlinkindex;\n} mstudioikchain_t;\n\ntypedef struct\n{\n\tuint8_t\t\tweight[4];\n\tint8_t\t\tbone[4];\n} mstudioboneweight_t;\n\n// skin families\n// short\tindex[skinfamilies][skinref]\n\n// studio models\ntypedef struct\n{\n\t// model name\n\tchar\t\tname[64];\n\n\t// was \"type\"\n\tint32_t\t\tunused;\n\n\t// was \"boundingradius\"\n\tvec_t\t\tunused2;\n\n\t// the number of meshes in the model\n\tint32_t\t\tnummesh;\n\n\t// offset to the first mesh chunk\n\tint32_t\t\tmeshindex;\n\n\t// the number of unique vertices\n\tint32_t\t\tnumverts;\n\n\t// offset to the vertex bone array\n\tint32_t\t\tvertinfoindex;\n\n\t// offset to the vertex array\n\tint32_t\t\tvertindex;\n\n\t// the number of unique normals\n\tint32_t\t\tnumnorms;\n\n\t// offset to the normal bone array\n\tint32_t\t\tnorminfoindex;\n\n\t// offset to the normal array\n\tint32_t\t\tnormindex;\n\n\t// offset to the boneweighted vertex info\n\tint32_t\t\tblendvertinfoindex;\n\n\t// offset to the boneweighted normal info\n\tint32_t\t\tblendnorminfoindex;\n} mstudiomodel_t;\n\n// vec3_t\tboundingbox[model][bone][2];\t\t// complex intersection info\n\n// meshes\ntypedef struct\n{\n\t// can be interpreted as the number of triangles in the mesh\n\tint32_t\t\tnumtris;\n\n\t// offset to the start of the tris sequence\n\tint32_t\t\ttriindex;\n\n\t// the skin index\n\tint32_t\t\tskinref;\n\n\t// the number of normals in the mesh\n\tint32_t\t\tnumnorms;\n\n\t// was \"normindex\"\n\tint32_t\t\tunused;\n} mstudiomesh_t;\n\n// triangles\ntypedef struct\n{\n\t// index into vertex array\n\tint16_t\t\tvertindex;\n\n\t// index into normal array\n\tint16_t\t\tnormindex;\n\n\t// texture coordinates in absolute space (unnormalized)\n\tint16_t\t\ts,t;\n} mstudiotrivert_t;\n\n#endif//STUDIO_H\n"
  },
  {
    "path": "engine/vgui_api.h",
    "content": "/*\nvgui_api.h - vgui_support library interface\nCopyright (C) 2015 Mittorn\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef VGUI_API_H\n#define VGUI_API_H\n\n#include \"xash3d_types.h\"\n#include \"key_modifiers.h\"\n#include \"cursor_type.h\"\n\n// VGUI generic vertex\n\ntypedef struct\n{\n\tvec2_t\tpoint;\n\tvec2_t\tcoord;\n} vpoint_t;\n\n// C-Style VGUI enums\n\nenum VGUI_MouseCode\n{\n\tMOUSE_LEFT=0,\n\tMOUSE_RIGHT,\n\tMOUSE_MIDDLE,\n\tMOUSE_LAST\n};\n\nenum VGUI_KeyCode\n{\n\tKEY_0=0,\n\tKEY_1,\n\tKEY_2,\n\tKEY_3,\n\tKEY_4,\n\tKEY_5,\n\tKEY_6,\n\tKEY_7,\n\tKEY_8,\n\tKEY_9,\n\tKEY_A,\n\tKEY_B,\n\tKEY_C,\n\tKEY_D,\n\tKEY_E,\n\tKEY_F,\n\tKEY_G,\n\tKEY_H,\n\tKEY_I,\n\tKEY_J,\n\tKEY_K,\n\tKEY_L,\n\tKEY_M,\n\tKEY_N,\n\tKEY_O,\n\tKEY_P,\n\tKEY_Q,\n\tKEY_R,\n\tKEY_S,\n\tKEY_T,\n\tKEY_U,\n\tKEY_V,\n\tKEY_W,\n\tKEY_X,\n\tKEY_Y,\n\tKEY_Z,\n\tKEY_PAD_0,\n\tKEY_PAD_1,\n\tKEY_PAD_2,\n\tKEY_PAD_3,\n\tKEY_PAD_4,\n\tKEY_PAD_5,\n\tKEY_PAD_6,\n\tKEY_PAD_7,\n\tKEY_PAD_8,\n\tKEY_PAD_9,\n\tKEY_PAD_DIVIDE,\n\tKEY_PAD_MULTIPLY,\n\tKEY_PAD_MINUS,\n\tKEY_PAD_PLUS,\n\tKEY_PAD_ENTER,\n\tKEY_PAD_DECIMAL,\n\tKEY_LBRACKET,\n\tKEY_RBRACKET,\n\tKEY_SEMICOLON,\n\tKEY_APOSTROPHE,\n\tKEY_BACKQUOTE,\n\tKEY_COMMA,\n\tKEY_PERIOD,\n\tKEY_SLASH,\n\tKEY_BACKSLASH,\n\tKEY_MINUS,\n\tKEY_EQUAL,\n\tKEY_ENTER,\n\tKEY_SPACE,\n\tKEY_BACKSPACE,\n\tKEY_TAB,\n\tKEY_CAPSLOCK,\n\tKEY_NUMLOCK,\n\tKEY_ESCAPE,\n\tKEY_SCROLLLOCK,\n\tKEY_INSERT,\n\tKEY_DELETE,\n\tKEY_HOME,\n\tKEY_END,\n\tKEY_PAGEUP,\n\tKEY_PAGEDOWN,\n\tKEY_BREAK,\n\tKEY_LSHIFT,\n\tKEY_RSHIFT,\n\tKEY_LALT,\n\tKEY_RALT,\n\tKEY_LCONTROL,\n\tKEY_RCONTROL,\n\tKEY_LWIN,\n\tKEY_RWIN,\n\tKEY_APP,\n\tKEY_UP,\n\tKEY_LEFT,\n\tKEY_DOWN,\n\tKEY_RIGHT,\n\tKEY_F1,\n\tKEY_F2,\n\tKEY_F3,\n\tKEY_F4,\n\tKEY_F5,\n\tKEY_F6,\n\tKEY_F7,\n\tKEY_F8,\n\tKEY_F9,\n\tKEY_F10,\n\tKEY_F11,\n\tKEY_F12,\n\tKEY_LAST\n};\n\nenum VGUI_KeyAction\n{\n\tKA_TYPED=0,\n\tKA_PRESSED,\n\tKA_RELEASED\n};\nenum VGUI_MouseAction\n{\n\tMA_PRESSED=0,\n\tMA_RELEASED,\n\tMA_DOUBLE,\n\tMA_WHEEL\n};\n\ntypedef struct  vguiapi_s\n{\n\tqboolean initialized;\n\t// called from vgui_support\n\tvoid\t(*DrawInit)( void );\n\tvoid\t(*DrawShutdown)( void );\n\tvoid\t(*SetupDrawingText)( int *pColor );\n\tvoid\t(*SetupDrawingRect)( int *pColor );\n\tvoid\t(*SetupDrawingImage)( int *pColor );\n\tvoid\t(*BindTexture)( int id );\n\tvoid\t(*EnableTexture)( qboolean enable );\n\tvoid\t(*Reserved0)( int id, int width, int height );\n\tvoid\t(*UploadTexture)( int id, const char *buffer, int width, int height );\n\tvoid\t(*Reserved1)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );\n\tvoid\t(*DrawQuad)( const vpoint_t *ul, const vpoint_t *lr );\n\tvoid\t(*GetTextureSizes)( int *width, int *height );\n\tint\t\t(*GenerateTexture)( void );\n\tvoid\t*(*EngineMalloc)( size_t size );\n\tvoid\t(*CursorSelect)( VGUI_DefaultCursor cursor );\n\tbyte\t\t(*GetColor)( int i, int j );\n\tqboolean\t(*IsInGame)( void );\n\tvoid\t(*EnableTextInput)( qboolean enable, qboolean force );\n\tvoid\t(*GetCursorPos)( int *x, int *y );\n\tint\t\t(*ProcessUtfChar)( int ch );\n\tint\t\t(*GetClipboardText)( char *buffer, size_t bufferSize );\n\tvoid\t(*SetClipboardText)( const char *text );\n\tkey_modifier_t (*GetKeyModifiers)( void );\n\t// called from engine side\n\tvoid\t(*Startup)( int width, int height );\n\tvoid\t(*Shutdown)( void );\n\tvoid\t*(*GetPanel)( void );\n\tvoid\t(*Paint)( void );\n\tvoid\t(*Mouse)( enum VGUI_MouseAction action, int code );\n\tvoid\t(*Key)( enum VGUI_KeyAction action, enum VGUI_KeyCode code );\n\tvoid\t(*MouseMove)( int x, int y );\n\tvoid\t(*TextInput)( const char *text );\n} vguiapi_t;\n#endif // VGUI_API_H\n"
  },
  {
    "path": "engine/warpsin.h",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n\n\n0.000000, 0.098165, 0.196270, 0.294259, 0.392069, 0.489643, 0.586920, 0.683850,\n0.780360, 0.876405, 0.971920, 1.066850, 1.161140, 1.254725, 1.347560, 1.439580,\n1.530735, 1.620965, 1.710220, 1.798445, 1.885585, 1.971595, 2.056410, 2.139990,\n2.222280, 2.303235, 2.382795, 2.460925, 2.537575, 2.612690, 2.686235, 2.758160,\n2.828425, 2.896990, 2.963805, 3.028835, 3.092040, 3.153385, 3.212830, 3.270340,\n3.325880, 3.379415, 3.430915, 3.480350, 3.527685, 3.572895, 3.615955, 3.656840,\n3.695520, 3.731970, 3.766175, 3.798115, 3.827760, 3.855105, 3.880125, 3.902810,\n3.923140, 3.941110, 3.956705, 3.969920, 3.980740, 3.989160, 3.995180, 3.998795,\n4.000000, 3.998795, 3.995180, 3.989160, 3.980740, 3.969920, 3.956705, 3.941110,\n3.923140, 3.902810, 3.880125, 3.855105, 3.827760, 3.798115, 3.766175, 3.731970,\n3.695520, 3.656840, 3.615955, 3.572895, 3.527685, 3.480350, 3.430915, 3.379415,\n3.325880, 3.270340, 3.212830, 3.153385, 3.092040, 3.028835, 2.963805, 2.896990,\n2.828425, 2.758160, 2.686235, 2.612690, 2.537575, 2.460925, 2.382795, 2.303235,\n2.222280, 2.139990, 2.056410, 1.971595, 1.885585, 1.798445, 1.710220, 1.620965,\n1.530735, 1.439580, 1.347560, 1.254725, 1.161140, 1.066850, 0.971920, 0.876405,\n0.780360, 0.683850, 0.586920, 0.489643, 0.392069, 0.294259, 0.196270, 0.098165,\n0.000000, -0.098165, -0.196270, -0.294259, -0.392069, -0.489643, -0.586920, -0.683850,\n-0.780360, -0.876405, -0.971920, -1.066850, -1.161140, -1.254725, -1.347560, -1.439580,\n-1.530735, -1.620965, -1.710220, -1.798445, -1.885585, -1.971595, -2.056410, -2.139990,\n-2.222280, -2.303235, -2.382795, -2.460925, -2.537575, -2.612690, -2.686235, -2.758160,\n-2.828425, -2.896990, -2.963805, -3.028835, -3.092040, -3.153385, -3.212830, -3.270340,\n-3.325880, -3.379415, -3.430915, -3.480350, -3.527685, -3.572895, -3.615955, -3.656840,\n-3.695520, -3.731970, -3.766175, -3.798115, -3.827760, -3.855105, -3.880125, -3.902810,\n-3.923140, -3.941110, -3.956705, -3.969920, -3.980740, -3.989160, -3.995180, -3.998795,\n-4.000000, -3.998795, -3.995180, -3.989160, -3.980740, -3.969920, -3.956705, -3.941110,\n-3.923140, -3.902810, -3.880125, -3.855105, -3.827760, -3.798115, -3.766175, -3.731970,\n-3.695520, -3.656840, -3.615955, -3.572895, -3.527685, -3.480350, -3.430915, -3.379415,\n-3.325880, -3.270340, -3.212830, -3.153385, -3.092040, -3.028835, -2.963805, -2.896990,\n-2.828425, -2.758160, -2.686235, -2.612690, -2.537575, -2.460925, -2.382795, -2.303235,\n-2.222280, -2.139990, -2.056410, -1.971595, -1.885585, -1.798445, -1.710220, -1.620965,\n-1.530735, -1.439580, -1.347560, -1.254725, -1.161140, -1.066850, -0.971920, -0.876405,\n-0.780360, -0.683850, -0.586920, -0.489643, -0.392069, -0.294259, -0.196270, -0.098165,\n"
  },
  {
    "path": "engine/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib import Logs\nfrom waflib.extras import pthread\nimport os\nfrom copy import copy\n\ntop = '.'\n\nEXECINFO_TEST = '''#include <execinfo.h>\nint main(int argc, char **argv)\n{\n\tbacktrace(0, 0);\n\tbacktrace_symbols(0, 0);\n}'''\n\nFFMPEG_CHECK_FRAGMENT='''\n#include <libswresample/swresample.h>\n#include <libavformat/avformat.h>\n#include <libavcodec/avcodec.h>\n#include <libswscale/swscale.h>\n#include <libavutil/avutil.h>\nint main(int argc, char **argv)\n{\n\tswresample_version();\n\tavformat_version();\n\tavcodec_version();\n\tswscale_version();\n\tavutil_version();\n\treturn 0;\n}\n'''\n\ndef options(opt):\n\tgrp = opt.add_option_group('Engine options')\n\n\tgrp.add_option('--enable-fbdev', action = 'store_true', dest = 'FBDEV_SW', default = False,\n\t\thelp = 'build fbdev-only software-only engine')\n\n\tgrp.add_option('--enable-custom-swap', action = 'store_true', dest = 'CUSTOM_SWAP', default = False,\n\t\thelp = 'enable custom swap allocator. For devices with no swap support')\n\n\tgrp.add_option('--use-sdl1', action = 'store_true', dest = 'SDL12', default = False,\n\t\thelp = 'enable using SDL1.2 instead of SDL2 (not supported or recommended) [default: %(default)s]')\n\n\tgrp.add_option('--enable-static-binary', action = 'store_true', dest = 'STATIC', default = False,\n\t\thelp = 'build static binary(not recommended, --single-binary required) [default: %(default)s]')\n\n\tgrp.add_option('--enable-engine-tests', action = 'store_true', dest = 'ENGINE_TESTS', default = False,\n\t\thelp = 'embed tests into the engine, jump into them by -runtests command line switch [default: %(default)s]')\n\n\tgrp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False,\n\t\thelp = 'add LLVM libFuzzer [default: %(default)s]' )\n\n\tgrp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False,\n\t\thelp = 'enable ffmpeg based movie playback [default: %(default)s')\n\n\tgrp.add_option('--enable-ffmpeg-dlopen', action = 'store_true', dest = 'FFMPEG_DLOPEN', default = False,\n\t\thelp = 'load ffmpeg libraries in runtime [default: %(default)s]')\n\n\topt.load('sdl2')\n\ndef find_sdl(conf):\n\terr_msg = '%s not available! If you want to build dedicated server, specify --dedicated'\n\tif conf.env.DEST_OS in ['nswitch', 'psvita']:\n\t\terr_msg = '%s not available!'\n\n\tconf.load('sdl2')\n\tif conf.options.SDL3:\n\t\tif not conf.env.HAVE_SDL3:\n\t\t\tconf.fatal(err_msg % 'SDL3')\n\t\tconf.env.XASH_SDL = 3\n\telse:\n\t\tif not conf.env.HAVE_SDL2:\n\t\t\tconf.fatal(err_msg % 'SDL2')\n\t\tconf.env.XASH_SDL = 2\n\ndef configure(conf):\n\thave_async_resolve = True\n\n\t# Common dependencies, will be linked to both dedicated server and client\n\tif conf.env.DEST_OS == 'linux':\n\t\tconf.check_cc(lib='rt')\n\t\tif conf.env.MAGX:\n\t\t\tconf.define('XASH_CRASHHANDLER', 0)\n\telif conf.env.DEST_OS == 'haiku':\n\t\tconf.env.LIB_HAIKU = ['network']\n\t\tconf.env.LIBPATH_HAIKU = ['/boot/system/lib']\n\telif conf.env.DEST_OS == 'wasi':\n\t\thave_async_resolve = False\n\t\tconf.env.CFLAGS += ['-mllvm', '-wasm-enable-sjlj']\n\telif conf.env.DEST_OS == 'emscripten':\n\t\thave_async_resolve = False\n\telif conf.env.DEST_OS == 'sunos':\n\t\tconf.check_cc(lib='socket')\n\telif conf.env.DEST_OS == 'dos':\n\t\tconf.options.STATIC = True\n\t\thave_async_resolve = False\n\n\tif conf.options.ENGINE_FUZZ:\n\t\tconf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link')\n\t\tconf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer')\n\n\t# Client-only dependencies\n\tif conf.env.CLIENT:\n\t\tif conf.env.DEST_OS in ['nswitch', 'psvita']:\n\t\t\textra_libs = ['-lm']\n\t\t\tif conf.env.DEST_OS == 'psvita':\n\t\t\t\textra_libs = ['-lstdc++', '-lm', '-lSceShaccCgExt', '-lSceShaccCg_stub', '-ltaihen_stub', '-lSceKernelDmacMgr_stub']\n\n\t\t\tconf.env.append_unique('CXXFLAGS', ['-Wl,--no-undefined'])\n\t\t\tconf.env.append_unique('CFLAGS', ['-Wl,--no-undefined'])\n\n\t\t\tconf.env.LDFLAGS += extra_libs\n\t\t\tfind_sdl(conf)\n\t\t\tfor lib in extra_libs: conf.env.LDFLAGS.remove(lib)\n\n\t\t\tif conf.env.DEST_OS == 'nswitch': # remove libstdc++ linking from SDL2, we link it later\n\t\t\t\tconf.env.LIB_SDL2.remove('stdc++')\n\t\telif conf.env.DEST_OS == 'linux' and conf.options.FBDEV_SW:\n\t\t\tconf.check_cc(lib='asound')\n\t\telif conf.options.SDL12: # TODO: move to sdl2.py\n\t\t\tconf.check_cfg(package='sdl', args='--cflags --libs', uselib_store='SDL1')\n\t\t\tconf.env.XASH_SDL = 1\n\t\t\thave_async_resolve = False\n\t\t\tconf.env.HAVE_SDL1 = True\n\t\telse:\n\t\t\tfind_sdl(conf)\n\n\n\tif conf.options.STATIC:\n\t\tconf.env.STATIC = True\n\t\tconf.define('XASH_NO_LIBDL', 1)\n\n\tif not conf.env.DEST_OS in ['win32', 'android'] and have_async_resolve:\n\t\tconf.check_pthreads(mode='c')\n\n\tif hasattr(conf.options, 'DLLEMU'):\n\t\tconf.define_cond('XASH_DLL_LOADER', conf.options.DLLEMU)\n\n\tconf.check_cc(fragment=EXECINFO_TEST, msg='Checking for glibc backtrace()', mandatory=False, define_name='HAVE_EXECINFO')\n\n\tconf.define('ENGINE_DLL', 1)\n\tconf.define_cond('XASH_ENGINE_TESTS', conf.options.ENGINE_TESTS)\n\n\tif conf.options.FFMPEG:\n\t\tpkgconf_args = '--cflags' if conf.options.FFMPEG_DLOPEN else '--cflags --libs'\n\t\tfeatures = 'c' if conf.options.FFMPEG_DLOPEN else 'c cprogram'\n\n\t\t# add ffmpeg libraries into single uselib package\n\t\tconf.check_cfg(package='libswresample', uselib_store='SWRESAMPLE', args=pkgconf_args)\n\t\tconf.check_cfg(package='libavformat', uselib_store='AVFORMAT', args=pkgconf_args)\n\t\tconf.check_cfg(package='libavcodec', uselib_store='AVCODEC', args=pkgconf_args)\n\t\tconf.check_cfg(package='libswscale', uselib_store='SWSCALE', args=pkgconf_args)\n\t\tconf.check_cfg(package='libavutil', uselib_store='AVUTIL', args=pkgconf_args)\n\n\t\t# validate ffmpeg libs installation\n\t\tconf.check(features=features, fragment=FFMPEG_CHECK_FRAGMENT, use='SWRESAMPLE AVCODEC AVFORMAT AVUTIL SWSCALE', msg='Checking for ffmpeg sanity')\n\n\t\tconf.env.FFMPEG = True\n\t\tconf.define('HAVE_FFMPEG', True)\n\t\tconf.define_cond('XASH_FFMPEG_DLOPEN', conf.options.FFMPEG_DLOPEN)\n\n\tconf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING)\n\tconf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP)\n\tconf.define_cond('XASH_NO_ASYNC_NS_RESOLVE', not have_async_resolve)\n\tconf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1\n\n\tfor refdll in conf.refdlls:\n\t\trefdll.register_define(conf)\n\ndef build(bld):\n\t# public includes for renderers and utils use\n\tbld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes')\n\n\tlibs = ['engine_includes', 'public', 'dllemu', 'werror', 'backtrace']\n\tincludes = ['server', 'client', 'client/vgui', 'common/soundlib', 'platform']\n\n\t# basic build: dedicated only\n\tsource = bld.path.ant_glob(['common/*.c', 'common/imagelib/*.c', 'common/soundlib/*.c', 'server/*.c'])\n\n\t# include platform-specific sources, this shall not fail if directory doesn't exist\n\tsource += bld.path.ant_glob('platform/%s/*.c' % bld.env.DEST_OS)\n\n\t# Android _is_ Linux based, after all\n\tif bld.env.DEST_OS == 'android':\n\t\tsource += bld.path.ant_glob('platform/linux/*.c')\n\n\t# include common POSIX conformant code\n\tif bld.env.DEST_OS not in ['win32', 'dos']:\n\t\tsource += bld.path.ant_glob('platform/posix/*.c')\n\n\t# include sources for optional features\n\tif bld.get_define('XASH_CUSTOM_SWAP'):\n\t\tsource += ['platform/misc/kmalloc.c', 'platform/misc/sbrk.c']\n\n\tif bld.get_define('XASH_STATIC_LIBS'):\n\t\tsource += ['platform/misc/lib_static.c']\n\n\tif bld.env.DEST_OS == 'win32':\n\t\tlibs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32']\n\telif bld.env.DEST_OS == 'nswitch':\n\t\tlibs += ['SOLDER']\n\t\t# HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting\n\t\t# we can't do this right away because std::filesystem will complain about not having pathconf(),\n\t\t# which we have defined in sys_nswitch.c\n\t\tbld.env.LDFLAGS += ['-v', '-Wl,--whole-archive', '-lstdc++', '-Wl,--no-whole-archive', '-lm']\n\telif bld.env.DEST_OS == 'psvita':\n\t\tlibs += ['VRTLD']\n\t\t# HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting\n\t\t# also link in all the funky dependencies that aren't in SDL2's LDFLAGS\n\t\tbld.env.LDFLAGS += ['-Wl,--whole-archive', '-lstdc++', '-lpthread', '-Wl,--no-whole-archive',\n\t\t\t'-lm', '-lSceShaccCgExt', '-lkubridge_stub', '-ltaihen_stub', '-lSceShaccCg_stub', '-lSceKernelModulemgr_stub', '-lSceSblSsMgr_stub', '-lSceVshBridge_stub', '-lSceKernelDmacMgr_stub', '-lSceLibKernel_stub',\n\t\t]\n\telse: # POSIX\n\t\tlibs += ['M', 'RT', 'PTHREAD', 'ASOUND', 'HAIKU', 'MAGX', 'LOG', 'SOCKET']\n\t\tif not bld.env.STATIC:\n\t\t\tlibs += ['DL']\n\n\tif bld.env.SERVER:\n\t\t# TODO: avoid possible name collision when client is built without launcher\n\t\t# but dedicated server is enabled. They're both called 'xash' in this case\n\t\tbld.program(\n\t\t\tsource = copy(source),\n\t\t\ttarget = 'xash',\n\t\t\tuse = copy(libs),\n\t\t\tincludes = includes,\n\t\t\tdefines = 'XASH_ENABLE_MAIN=1 XASH_DEDICATED=1',\n\t\t\trpath = bld.env.DEFAULT_RPATH,\n\t\t\tinstall_path = bld.env.BINDIR,\n\t\t)\n\n\tif bld.env.CLIENT:\n\t\tdefines = []\n\t\tif bld.env.XASH_SDL:\n\t\t\tlibs += ['SDL%d' % bld.env.XASH_SDL]\n\t\t\tsource += bld.path.ant_glob(['platform/sdl%d/*.c' % bld.env.XASH_SDL])\n\t\t\tdefines += ['XASH_SDL=%d' % bld.env.XASH_SDL]\n\n\t\tif bld.get_define('HAVE_FFMPEG'):\n\t\t\tlibs.append('SWRESAMPLE')\n\t\t\tlibs.append('AVCODEC')\n\t\t\tlibs.append('AVFORMAT')\n\t\t\tlibs.append('AVUTIL')\n\t\t\tlibs.append('SWSCALE')\n\n\t\t# add client files\n\t\tif not bld.env.DEDICATED:\n\t\t\tsource += bld.path.ant_glob('client/**/*.c')\n\t\t\tlibs += ['bzip2', 'MultiEmulator', 'opus', 'opusfile', 'vorbis', 'vorbisfile']\n\n\t\t# Android port is linked differently\n\t\tif bld.env.DEST_OS == 'android':\n\t\t\tf = bld.shlib\n\t\t\tinstall_path = bld.env.LIBDIR\n\t\t\tdefines += ['XASH_ENABLE_MAIN=1', 'XASH_SDLMAIN=1']\n\t\telif not bld.env.LAUNCHER:\n\t\t\tf = bld.program\n\t\t\tinstall_path = bld.env.BINDIR\n\t\t\tdefines += ['XASH_ENABLE_MAIN=1']\n\t\telse:\n\t\t\tf = bld.shlib\n\t\t\tinstall_path = bld.env.LIBDIR\n\n\t\tif bld.env.DEST_OS in ['nswitch', 'psvita', 'emscripten']:\n\t\t\tinstall_path = None\n\n\t\tlinkflags = []\n\n\t\tif bld.env.DEST_OS == 'emscripten':\n\t\t\tlinkflags = ['-sINITIAL_MEMORY=134217728', '-sALLOW_MEMORY_GROWTH=1', '-sSTACK_SIZE=16777216']\n\n\t\t\tlinkflags += ['-sASYNCIFY=1']\n\n\t\t\tnode = bld.srcnode.find_node('rodir')\n\t\t\tif not node:\n\t\t\t\tbld.fatal('Put game data into rodir folder in source tree root')\n\t\t\tlinkflags += ['--preload-file', '%s@/rodir' % node.path_from(bld.bldnode)]\n\n\t\tf(source = source,\n\t\t\ttarget = 'xash',\n\t\t\tincludes = includes,\n\t\t\tfeatures = 'cxx c',\n\t\t\tuse = libs,\n\t\t\tdefines = defines,\n\t\t\tinstall_path = install_path,\n\t\t\tspecial_install_path = bld.env.BINDIR,\n\t\t\tnacp = 'platform/nswitch/xash3d-fwgs.nacp',\n\t\t\ticon = 'platform/nswitch/icon.jpg',\n\t\t\tsce_sys = 'platform/psvita/sce_sys',\n\t\t\ttitle_id = 'XASH10000',\n\t\t\tapp_name = 'xash3d-fwgs',\n\t\t\trpath = bld.env.DEFAULT_RPATH,\n\t\t\tlinkflags = linkflags,\n\t\t)\n"
  },
  {
    "path": "filesystem/VFileSystem009.cpp",
    "content": "/*\nVFileSystem009.h - C++ interface for filesystem_stdio\nCopyright (C) 2022-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n*/\n#include <string.h>\n#include <stdio.h>\n#include <time.h>\n#include <stdarg.h>\n#include ALLOCA_H\n#include \"crtlib.h\"\n#include \"filesystem.h\"\n#include \"filesystem_internal.h\"\n#include \"VFileSystem009.h\"\n#include \"common/com_strings.h\"\n\n#if __cplusplus < 201103L\n#define override\n#define nullptr NULL\n#endif\n\n// GoldSrc Directories and ID\n// GAME          gamedir\n// GAMECONFIG    gamedir (rodir integration?)\n// GAMEDOWNLOAD  gamedir_downloads (gamedir/downloads for us)\n// GAME_FALLBACK liblist.gam's fallback_dir\n// ROOT and BASE rootdir\n// PLATFORM      platform\n// CONFIG        platform/config\n\n// This is a macro because pointers returned by alloca\n// shouldn't leave current scope\n#define FixupPath( var, str ) \\\n\tconst size_t var ## _size = Q_strlen(( str )) + 1; \\\n\tchar * const var = static_cast<char *>( alloca( var ## _size )); \\\n\tCopyAndFixSlashes( var, ( str ), var ## _size )\n\nstatic inline bool IsIdGamedir( const char *id )\n{\n\treturn !Q_strcmp( id, \"GAME\" ) ||\n\t\t!Q_strcmp( id, \"GAMECONFIG\" ) ||\n\t\t!Q_strcmp( id, \"GAMEDOWNLOAD\" );\n}\n\nstatic inline const char *IdToDir( char *dir, size_t size, const char *id )\n{\n\tif( !Q_strcmp( id, \"GAME\" ))\n\t\treturn GI->gamefolder;\n\n\tif( !Q_strcmp( id, \"GAMEDOWNLOAD\" ))\n\t{\n\t\tQ_snprintf( dir, size, \"%s/\" DEFAULT_DOWNLOADED_DIRECTORY , GI->gamefolder );\n\t\treturn dir;\n\t}\n\n\tif( !Q_strcmp( id, \"GAMECONFIG\" ))\n\t\treturn fs_writepath->filename; // full path here so it's totally our write allowed directory\n\n\tif( !Q_strcmp( id, \"PLATFORM\" ))\n\t\treturn \"platform\"; // stub\n\n\tif( !Q_strcmp( id, \"CONFIG\" ))\n\t\treturn \"platform/config\"; // stub\n\n\t// ROOT || BASE\n\treturn fs_rootdir; // give at least root directory\n}\n\nstatic inline void CopyAndFixSlashes( char *p, const char *in, size_t size )\n{\n\tQ_strncpy( p, in, size );\n\tCOM_FixSlashes( p );\n}\n\nclass CXashFS : public IFileSystem\n{\nprivate:\n\tclass CSearchState\n\t{\n\tpublic:\n\t\tCSearchState( CSearchState **head, search_t *search ) :\n\t\t\tnext( *head ),\n\t\t\tsearch( search ),\n\t\t\tindex( 0 ),\n\t\t\thandle( *head ? ( *head )->handle + 1 : 0 )\n\t\t{\n\t\t\t*head = this;\n\t\t}\n\t\t~CSearchState()\n\t\t{\n\t\t\tMem_Free( search );\n\t\t}\n\n\t\tCSearchState *next;\n\t\tsearch_t *search;\n\t\tint index;\n\t\tFileFindHandle_t handle;\n\t};\n\n\tCSearchState *searchHead;\n\n\tCSearchState *GetSearchStateByHandle( FileFindHandle_t handle )\n\t{\n\t\tfor( CSearchState *state = searchHead; state; state = state->next )\n\t\t{\n\t\t\tif( state->handle == handle )\n\t\t\t\treturn state;\n\t\t}\n\n\t\tCon_DPrintf( \"Can't find search state by handle %d\\n\", handle );\n\t\treturn nullptr;\n\t}\n\npublic:\n\tCXashFS() : searchHead( nullptr )\n\t{\n\t}\n\n\tvoid RemoveAllSearchPaths() override\n\t{\n\t\tFS_ClearSearchPath();\n\t}\n\n\tvoid AddSearchPath( const char *path, const char *id ) override\n\t{\n\t\tFixupPath( p, path );\n\t\tFS_AddGameDirectory( p, FS_CUSTOM_PATH );\n\t}\n\n\tvoid AddSearchPathNoWrite( const char *path, const char *id ) override\n\t{\n\t\tFixupPath( p, path );\n\t\tFS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH );\n\t}\n\n\tbool RemoveSearchPath( const char *id ) override\n\t{\n\t\t// TODO:\n\t\treturn true;\n\t}\n\n\tvoid RemoveFile( const char *path, const char *id ) override\n\t{\n\t\tchar dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];\n\n\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s/%s\", IdToDir( dir, sizeof( dir ), id ), path );\n\t\tFS_Delete( fullpath ); // FS_Delete is aware of slashes\n\t}\n\n\tvoid CreateDirHierarchy( const char *path, const char *id ) override\n\t{\n\t\tchar dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];\n\n\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s/%s\", IdToDir( dir, sizeof( dir ), id ), path );\n\t\tFS_CreatePath( fullpath ); // FS_CreatePath is aware of slashes\n\t}\n\n\tbool FileExists( const char *path ) override\n\t{\n\t\tFixupPath( p, path );\n\t\treturn FS_FileExists( p, false );\n\t}\n\n\tbool IsDirectory( const char *path ) override\n\t{\n\t\tFixupPath( p, path );\n\t\treturn FS_SysFolderExists( p );\n\t}\n\n\tFileHandle_t Open( const char *path, const char *mode, const char *id ) override\n\t{\n\t\tfile_t *fd;\n\n\t\tFixupPath( p, path );\n\t\tfd = FS_Open( p, mode, IsIdGamedir( id ));\n\n\t\treturn fd;\n\t}\n\n\tvoid Close( FileHandle_t handle ) override\n\t{\n\t\tFS_Close( static_cast<file_t *>( handle ));\n\t}\n\n\tvoid Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override\n\t{\n\t\tint whence_ = SEEK_SET;\n\n\t\tswitch( whence )\n\t\t{\n\t\tcase FILESYSTEM_SEEK_HEAD:\n\t\t\twhence_ = SEEK_SET;\n\t\t\tbreak;\n\t\tcase FILESYSTEM_SEEK_CURRENT:\n\t\t\twhence_ = SEEK_CUR;\n\t\t\tbreak;\n\t\tcase FILESYSTEM_SEEK_TAIL:\n\t\t\twhence_ = SEEK_END;\n\t\t\tbreak;\n\t\t}\n\n\t\tFS_Seek( static_cast<file_t *>( handle ), offset, whence_ );\n\t}\n\n\tunsigned int Tell( FileHandle_t handle ) override\n\t{\n\t\treturn FS_Tell( static_cast<file_t *>( handle ));\n\t}\n\n\tunsigned int Size( FileHandle_t handle ) override\n\t{\n\t\treturn static_cast<file_t *>( handle )->real_length;\n\t}\n\n\tunsigned int Size( const char *path ) override\n\t{\n\t\tFixupPath( p, path );\n\t\treturn FS_FileSize( p, false );\n\t}\n\n\tlong int GetFileTime( const char *path ) override\n\t{\n\t\tFixupPath( p, path );\n\t\treturn FS_FileTime( p, false );\n\t}\n\n\tlong int GetFileModificationTime( const char *path ) override\n\t{\n\t\t// TODO: properly reverse-engineer this\n\t\tFixupPath( p, path );\n\t\treturn FS_FileTime( p, false );\n\t}\n\n\tvoid FileTimeToString( char *p, int size, long int time ) override\n\t{\n\t\tconst time_t curtime = time;\n\t\tchar *buf = ctime( &curtime );\n\n\t\tQ_strncpy( p, buf, size );\n\t}\n\n\tbool IsOk( FileHandle_t handle ) override\n\t{\n\t\treturn !FS_Eof( static_cast<file_t *>( handle ));\n\t}\n\n\tvoid Flush( FileHandle_t handle ) override\n\t{\n\t\tFS_Flush( static_cast<file_t *>( handle ));\n\t}\n\n\tbool EndOfFile( FileHandle_t handle ) override\n\t{\n\t\treturn FS_Eof( static_cast<file_t *>( handle ));\n\t}\n\n\tint Read( void *buf, int size, FileHandle_t handle ) override\n\t{\n\t\treturn FS_Read( static_cast<file_t *>( handle ), buf, size );\n\t}\n\n\tint Write( const void *buf, int size, FileHandle_t handle ) override\n\t{\n\t\treturn FS_Write( static_cast<file_t *>( handle ), buf, size );\n\t}\n\n\tchar *ReadLine( char *buf, int size, FileHandle_t handle ) override\n\t{\n\t\tconst int c = FS_Gets( static_cast<file_t *>( handle ), buf, size );\n\n\t\treturn c >= 0 ? buf : nullptr;\n\t}\n\n\tint FPrintf( FileHandle_t handle, char *fmt, ... ) override\n\t{\n\t\tva_list ap;\n\t\tint ret;\n\n\t\tva_start( ap, fmt );\n\t\tret = FS_VPrintf( static_cast<file_t *>( handle ), fmt, ap );\n\t\tva_end( ap );\n\n\t\treturn ret;\n\t}\n\n\tvoid *GetReadBuffer( FileHandle_t, int *size, bool ) override\n\t{\n\t\t// deprecated by Valve\n\t\t*size = 0;\n\t\treturn nullptr;\n\t}\n\n\tvoid ReleaseReadBuffer( FileHandle_t, void * ) override\n\t{\n\t\t// deprecated by Valve\n\t\treturn;\n\t}\n\n\tconst char *FindFirst( const char *pattern, FileFindHandle_t *handle, const char *id ) override\n\t{\n\t\tCSearchState *state;\n\t\tsearch_t *search;\n\n\t\tif( !handle || !pattern )\n\t\t\treturn nullptr;\n\n\t\tFixupPath( p, pattern );\n\t\tsearch = FS_Search( p, true, IsIdGamedir( id ));\n\n\t\tif( !search )\n\t\t\treturn nullptr;\n\n\t\tstate = new CSearchState( &searchHead, search );\n\t\tif( !state )\n\t\t{\n\t\t\tMem_Free( search );\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t*handle = state->handle;\n\t\treturn state->search->filenames[0];\n\t}\n\n\tconst char *FindNext( FileFindHandle_t handle ) override\n\t{\n\t\tCSearchState *state = GetSearchStateByHandle( handle );\n\n\t\tif( !state )\n\t\t\treturn nullptr;\n\n\t\tif( state->index + 1 >= state->search->numfilenames )\n\t\t\treturn nullptr;\n\n\t\treturn state->search->filenames[++state->index];\n\t}\n\n\tbool FindIsDirectory( FileFindHandle_t handle ) override\n\t{\n\t\tCSearchState *state = GetSearchStateByHandle( handle );\n\n\t\tif( !state )\n\t\t\treturn false;\n\n\t\tif( state->index >= state->search->numfilenames )\n\t\t\treturn false;\n\n\t\treturn IsDirectory( state->search->filenames[state->index] );\n\t}\n\n\tvoid FindClose( FileFindHandle_t handle ) override\n\t{\n\t\tCSearchState *prev;\n\t\tCSearchState *i;\n\n\t\tfor( prev = nullptr, i = searchHead;\n\t\t\ti != nullptr && i->handle != handle;\n\t\t\tprev = i, i = i->next );\n\n\t\tif( i == nullptr )\n\t\t{\n\t\t\tCon_DPrintf( \"%s: Can't find search state by handle %d\\n\", __func__, handle );\n\t\t\treturn;\n\t\t}\n\n\t\tif( prev != nullptr )\n\t\t\tprev->next = i->next;\n\t\telse\n\t\t\tsearchHead = i->next;\n\n\t\tdelete i;\n\t}\n\n\tconst char *GetLocalPath( const char *name, char *buf, int size ) override\n\t{\n\t\tconst char *fullpath;\n\n\t\tif( !name )\n\t\t\treturn nullptr;\n\n\t\tFixupPath( p, name );\n\n#if !XASH_WIN32\n\t\tif( p[0] == '/' )\n#else\n\t\tif( Q_strchr( p, ':' ))\n#endif\n\t\t{\n\t\t\tQ_strncpy( buf, p, size );\n\n\t\t\treturn buf;\n\t\t}\n\n\t\tfullpath = FS_GetDiskPath( p, false );\n\t\tif( !fullpath )\n\t\t\treturn nullptr;\n\n\t\tQ_strncpy( buf, fullpath, size );\n\t\treturn buf;\n\t}\n\n\tchar *ParseFile( char *buf, char *token, bool *quoted ) override\n\t{\n\t\tqboolean qquoted;\n\t\tchar *p;\n\n\t\tp = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, nullptr, &qquoted );\n\n\t\tif( quoted )\n\t\t\t*quoted = qquoted;\n\n\t\treturn p;\n\t}\n\n\tbool FullPathToRelativePath( const char *path, char *out ) override\n\t{\n\t\tif( !COM_CheckString( path ))\n\t\t{\n\t\t\t*out = 0;\n\t\t\treturn false;\n\t\t}\n\n\t\tFixupPath( p, path );\n\n\t\treturn FS_FullPathToRelativePath( out, p, 512 );\n\t}\n\n\tbool GetCurrentDirectory( char *p, int size ) override\n\t{\n\t\treturn FS_GetRootDirectory( p, size );\n\t}\n\n\tvoid PrintOpenedFiles() override\n\t{\n\t\t// we don't track this yet\n\t\treturn;\n\t}\n\n\tvoid SetWarningFunc( void (*)( const char *, ... )) override\n\t{\n\t\t// TODO:\n\t\treturn;\n\t}\n\n\tvoid SetWarningLevel( FileWarningLevel_t ) override\n\t{\n\t\t// TODO:\n\t\treturn;\n\t}\n\n\tint SetVBuf( FileHandle_t handle, char *buf, int mode, long int size ) override\n\t{\n\t\t// TODO:\n\t\treturn 0;\n\t}\n\n\tvoid GetInterfaceVersion( char *p, int size ) override\n\t{\n\t\tQ_strncpy( p, \"Stdio\", size );\n\t}\n\n\tbool AddPackFile( const char *path, const char *id ) override\n\t{\n\t\tchar dir[MAX_VA_STRING], fullpath[MAX_VA_STRING];\n\n\t\tIdToDir( dir, sizeof( dir ), id );\n\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s/%s\", dir, path );\n\t\tCOM_FixSlashes( fullpath );\n\t\treturn FS_MountArchive_Fullpath( fullpath, FS_CUSTOM_PATH ) != NULL;\n\t}\n\n\tFileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override\n\t{\n\t\tFixupPath( p, path );\n\n\t\treturn FS_OpenReadFile( p, mode, IsIdGamedir( id ));\n\t}\n\n\t// stubs\n\tvoid Mount() override {}\n\tvoid Unmount() override {}\n\tvoid GetLocalCopy( const char * ) override {}\n\tvoid LogLevelLoadStarted( const char * ) override {}\n\tvoid LogLevelLoadFinished( const char * ) override {}\n\tvoid CancelWaitForResources( WaitForResourcesHandle_t ) override {}\n\tint HintResourceNeed( const char *, int ) override { return 0; }\n\tWaitForResourcesHandle_t WaitForResources( const char * ) override { return 0; }\n\tint PauseResourcePreloading() override { return 0; }\n\tint ResumeResourcePreloading() override { return 0; }\n\tbool IsAppReadyForOfflinePlay( int ) override { return true; }\n\tbool IsFileImmediatelyAvailable( const char * ) override { return true; }\n\tbool GetWaitForResourcesProgress( WaitForResourcesHandle_t, float *pProgress, bool *pOverride ) override\n\t{\n\t\tif( pProgress )\n\t\t\t*pProgress = 0;\n\n\t\tif( pOverride )\n\t\t\t*pOverride = true;\n\n\t\treturn false;\n\t}\n} g_VFileSystem009;\n\nextern \"C\" void EXPORT *CreateInterface( const char *interface, int *retval )\n{\n\tif( !Q_strcmp( interface, FILESYSTEM_INTERFACE_VERSION ))\n\t{\n\t\tif( retval )\n\t\t\t*retval = 0;\n\n\t\treturn &g_VFileSystem009;\n\t}\n\n\tif( !Q_strcmp( interface, FS_API_CREATEINTERFACE_TAG ))\n\t{\n\t\tstatic fs_api_t copy; // return a copy, to disallow overriding\n\n\t\tcopy = g_api;\n\n\t\tif( retval )\n\t\t\t*retval = 0;\n\n\t\treturn &copy;\n\t}\n\n\tif( retval )\n\t\t*retval = 1;\n\n\treturn nullptr;\n}\n"
  },
  {
    "path": "filesystem/VFileSystem009.h",
    "content": "/*\nVFileSystem009.h - C++ interface for filesystem_stdio\nCopyright (C) 2022-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n*/\n\n#ifndef VFILESYSTEM009_H\n#define VFILESYSTEM009_H\n\n// exported from dwarf\ntypedef enum {\n\tFILESYSTEM_SEEK_HEAD    = 0,\n\tFILESYSTEM_SEEK_CURRENT = 1,\n\tFILESYSTEM_SEEK_TAIL    = 2,\n} FileSystemSeek_t; /* size: 4 */\n\ntypedef enum {\n\tFILESYSTEM_WARNING_QUIET\t     = 0,\n\tFILESYSTEM_WARNING_REPORTUNCLOSED    = 1,\n\tFILESYSTEM_WARNING_REPORTUSAGE       = 2,\n\tFILESYSTEM_WARNING_REPORTALLACCESSES = 3,\n} FileWarningLevel_t; /* size: 4 */\n\ntypedef void * FileHandle_t; /* size: 4 */\ntypedef int FileFindHandle_t; /* size: 4 */\ntypedef int WaitForResourcesHandle_t; /* size: 4 */\n\nclass IBaseInterface\n{\npublic:\n\tvirtual ~IBaseInterface() {}\n};\n\nclass IFileSystem : public IBaseInterface {\npublic:\n\tvirtual void Mount() = 0; /* linkage=_ZN11IFileSystem5MountEv */\n\n\tvirtual void Unmount() = 0; /* linkage=_ZN11IFileSystem7UnmountEv */\n\n\tvirtual void RemoveAllSearchPaths() = 0; /* linkage=_ZN11IFileSystem20RemoveAllSearchPathsEv */\n\n\tvirtual void AddSearchPath(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem13AddSearchPathEPKcS1_ */\n\n\tvirtual bool RemoveSearchPath(const char *) = 0; /* linkage=_ZN11IFileSystem16RemoveSearchPathEPKc */\n\n\tvirtual void RemoveFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem10RemoveFileEPKcS1_ */\n\n\tvirtual void CreateDirHierarchy(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem18CreateDirHierarchyEPKcS1_ */\n\n\tvirtual bool FileExists(const char *) = 0; /* linkage=_ZN11IFileSystem10FileExistsEPKc */\n\n\tvirtual bool IsDirectory(const char *) = 0; /* linkage=_ZN11IFileSystem11IsDirectoryEPKc */\n\n\tvirtual FileHandle_t Open(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem4OpenEPKcS1_S1_ */\n\n\tvirtual void Close(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5CloseEPv */\n\n\tvirtual void Seek(FileHandle_t, int, FileSystemSeek_t) = 0; /* linkage=_ZN11IFileSystem4SeekEPvi16FileSystemSeek_t */\n\n\tvirtual unsigned int Tell(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4TellEPv */\n\n\tvirtual unsigned int Size(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4SizeEPv */\n\n\tvirtual unsigned int Size(const char *) = 0; /* linkage=_ZN11IFileSystem4SizeEPKc */\n\n\tvirtual long int GetFileTime(const char *) = 0; /* linkage=_ZN11IFileSystem11GetFileTimeEPKc */\n\n\tvirtual void FileTimeToString(char *, int, long int) = 0; /* linkage=_ZN11IFileSystem16FileTimeToStringEPcil */\n\n\tvirtual bool IsOk(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4IsOkEPv */\n\n\tvirtual void Flush(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5FlushEPv */\n\n\tvirtual bool EndOfFile(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem9EndOfFileEPv */\n\n\tvirtual int Read(void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4ReadEPviS0_ */\n\n\tvirtual int Write(const void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5WriteEPKviPv */\n\n\tvirtual char * ReadLine(char *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem8ReadLineEPciPv */\n\n\tvirtual int FPrintf(FileHandle_t, char *, ...) = 0; /* linkage=_ZN11IFileSystem7FPrintfEPvPcz */\n\n\tvirtual void * GetReadBuffer(FileHandle_t, int *, bool) = 0; /* linkage=_ZN11IFileSystem13GetReadBufferEPvPib */\n\n\tvirtual void ReleaseReadBuffer(FileHandle_t, void *) = 0; /* linkage=_ZN11IFileSystem17ReleaseReadBufferEPvS0_ */\n\n\tvirtual const char * FindFirst(const char *, FileFindHandle_t *, const char *) = 0; /* linkage=_ZN11IFileSystem9FindFirstEPKcPiS1_ */\n\n\tvirtual const char * FindNext(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem8FindNextEi */\n\n\tvirtual bool FindIsDirectory(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem15FindIsDirectoryEi */\n\n\tvirtual void FindClose(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem9FindCloseEi */\n\n\tvirtual void GetLocalCopy(const char *) = 0; /* linkage=_ZN11IFileSystem12GetLocalCopyEPKc */\n\n\tvirtual const char * GetLocalPath(const char *, char *, int) = 0; /* linkage=_ZN11IFileSystem12GetLocalPathEPKcPci */\n\n\tvirtual char * ParseFile(char *, char *, bool *) = 0; /* linkage=_ZN11IFileSystem9ParseFileEPcS0_Pb */\n\n\tvirtual bool FullPathToRelativePath(const char *, char *) = 0; /* linkage=_ZN11IFileSystem22FullPathToRelativePathEPKcPc */\n\n\tvirtual bool GetCurrentDirectory(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetCurrentDirectoryEPci */\n\n\tvirtual void PrintOpenedFiles() = 0; /* linkage=_ZN11IFileSystem16PrintOpenedFilesEv */\n\n\tvirtual void SetWarningFunc(void (*)(const char *, ...)) = 0; /* linkage=_ZN11IFileSystem14SetWarningFuncEPFvPKczE */\n\n\tvirtual void SetWarningLevel(FileWarningLevel_t) = 0; /* linkage=_ZN11IFileSystem15SetWarningLevelE18FileWarningLevel_t */\n\n\tvirtual void LogLevelLoadStarted(const char *) = 0; /* linkage=_ZN11IFileSystem19LogLevelLoadStartedEPKc */\n\n\tvirtual void LogLevelLoadFinished(const char *) = 0; /* linkage=_ZN11IFileSystem20LogLevelLoadFinishedEPKc */\n\n\tvirtual int HintResourceNeed(const char *, int) = 0; /* linkage=_ZN11IFileSystem16HintResourceNeedEPKci */\n\n\tvirtual int PauseResourcePreloading() = 0; /* linkage=_ZN11IFileSystem23PauseResourcePreloadingEv */\n\n\tvirtual int ResumeResourcePreloading() = 0; /* linkage=_ZN11IFileSystem24ResumeResourcePreloadingEv */\n\n\tvirtual int SetVBuf(FileHandle_t, char *, int, long int) = 0; /* linkage=_ZN11IFileSystem7SetVBufEPvPcil */\n\n\tvirtual void GetInterfaceVersion(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetInterfaceVersionEPci */\n\n\tvirtual bool IsFileImmediatelyAvailable(const char *) = 0; /* linkage=_ZN11IFileSystem26IsFileImmediatelyAvailableEPKc */\n\n\tvirtual WaitForResourcesHandle_t WaitForResources(const char *) = 0; /* linkage=_ZN11IFileSystem16WaitForResourcesEPKc */\n\n\tvirtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *, bool *) = 0; /* linkage=_ZN11IFileSystem27GetWaitForResourcesProgressEiPfPb */\n\n\tvirtual void CancelWaitForResources(WaitForResourcesHandle_t) = 0; /* linkage=_ZN11IFileSystem22CancelWaitForResourcesEi */\n\n\tvirtual bool IsAppReadyForOfflinePlay(int) = 0; /* linkage=_ZN11IFileSystem24IsAppReadyForOfflinePlayEi */\n\n\tvirtual bool AddPackFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem11AddPackFileEPKcS1_ */\n\n\tvirtual FileHandle_t OpenFromCacheForRead(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20OpenFromCacheForReadEPKcS1_S1_ */\n\n\tvirtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */\n\n\tvirtual long int GetFileModificationTime(const char *) = 0; /* linkage=_ZN11IFileSystem23GetFileModificationTimeEPKc */\n};\n\n#endif // VFILESYSTEM009_H\n"
  },
  {
    "path": "filesystem/android.c",
    "content": "/*\nandroid.c - android support for filesystem\nCopyright (C) 2022 Velaron\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"port.h\"\n\n#if XASH_ANDROID\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <stddef.h>\n#include STDINT_H\n#include \"filesystem_internal.h\"\n#include \"crtlib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"common/com_strings.h\"\n\n#include <jni.h>\n#include <android/asset_manager.h>\n#include <android/asset_manager_jni.h>\n#include <time.h>\n\nstruct android_assets_s\n{\n\tstring package_name;\n\tqboolean engine;\n\tAAssetManager *asset_manager;\n\tAAssetDir *dir;\n};\n\nstruct jni_methods_s\n{\n\tJNIEnv *env;\n\tjobject activity;\n\tjclass activity_class;\n\tjmethodID getPackageName;\n\tjmethodID getCallingPackage;\n\tjmethodID getAssetsList;\n\tjmethodID getAssets;\n} jni;\n\nstatic void Android_GetAssetManager( android_assets_t *assets )\n{\n\tjobject assetManager;\n\n\tassetManager = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAssets, assets->engine );\n\n\tif( assetManager )\n\t\tassets->asset_manager = AAssetManager_fromJava( jni.env, assetManager );\n\telse if( assets->engine )\n\t\tCon_Reportf( S_WARN \"Couldn't add engine assets!\" );\n}\n\nstatic const char *Android_GetPackageName( qboolean engine )\n{\n\tstatic string pkg;\n\tjstring resultJNIStr;\n\tconst char *resultCStr;\n\n\tresultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, engine ? jni.getPackageName : jni.getCallingPackage );\n\n\tif( !resultJNIStr )\n\t\treturn NULL;\n\n\tresultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL );\n\tQ_strncpy( pkg, resultCStr, sizeof( pkg ));\n\t(*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr );\n\n\treturn pkg;\n}\n\nstatic void Android_ListDirectory( stringlist_t *list, const char *path, qboolean engine )\n{\n\tjstring JStr = (*jni.env)->NewStringUTF( jni.env, path );\n\tjobjectArray JNIArray = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAssetsList, engine, JStr );\n\tint JNIArraySize = (*jni.env)->GetArrayLength( jni.env, JNIArray );\n\n\tfor( int i = 0; i < JNIArraySize; i++ )\n\t{\n\t\tjstring JNIStr = (*jni.env)->GetObjectArrayElement( jni.env, JNIArray, i );\n\t\tconst char *CStr = (*jni.env)->GetStringUTFChars( jni.env, JNIStr, NULL );\n\n\t\tstringlistappend( list, (char *)CStr );\n\t\t(*jni.env)->ReleaseStringUTFChars( jni.env, JNIStr, CStr );\n\t}\n}\n\nstatic void FS_CloseAndroidAssets( android_assets_t *assets )\n{\n\tif( assets->dir )\n\t\tAAssetDir_close( assets->dir );\n\n\tMem_Free( assets );\n}\n\nstatic android_assets_t *FS_LoadAndroidAssets( qboolean engine )\n{\n\tandroid_assets_t *assets = Mem_Calloc( fs_mempool, sizeof( *assets ));\n\n\tassets->engine = engine;\n\n\tAndroid_GetAssetManager( assets );\n\tif( !assets->asset_manager )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: Can't get asset manager\\n\", __func__ );\n\t\tFS_CloseAndroidAssets( assets );\n\t\treturn NULL;\n\t}\n\n\tassets->dir = AAssetManager_openDir( assets->asset_manager, \"\" );\n\tif( !assets->dir )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: Can't open root asset directory\\n\", __func__ );\n\t\tFS_CloseAndroidAssets( assets );\n\t\treturn NULL;\n\t}\n\n\treturn assets;\n}\n\nstatic int FS_FileTime_AndroidAssets( searchpath_t *search, const char *filename )\n{\n\tstatic time_t time;\n\n\tif( !time )\n\t{\n\t\tstruct tm file_tm;\n\n\t\tstrptime( g_buildcommit_date, \"%Y-%m-%d %H:%M:%S\", &file_tm );\n\t\ttime = mktime( &file_tm );\n\t}\n\n\treturn time;\n}\n\nstatic int FS_FindFile_AndroidAssets( struct searchpath_s *search, const char *path, char *fixedname, size_t len )\n{\n\tAAsset *assets = AAssetManager_open( search->assets->asset_manager, path, AASSET_MODE_UNKNOWN );\n\n\tif( assets )\n\t{\n\t\tAAsset_close( assets );\n\n\t\tQ_strncpy( fixedname, path, len );\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nstatic void FS_PrintInfo_AndroidAssets( searchpath_t *search, char *dst, size_t size )\n{\n\tQ_snprintf( dst, size, \"%s\", search->assets->package_name );\n}\n\nstatic void FS_Close_AndroidAssets( searchpath_t *search )\n{\n\tFS_CloseAndroidAssets( search->assets );\n}\n\nstatic void FS_Search_AndroidAssets( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )\n{\n\tstring temp;\n\tstringlist_t dirlist;\n\tconst char *slash, *backslash, *colon, *separator;\n\tint basepathlength, dirlistindex, resultlistindex;\n\tchar *basepath;\n\n\tslash = Q_strrchr( pattern, '/' );\n\tbackslash = Q_strrchr( pattern, '\\\\' );\n\tcolon = Q_strrchr( pattern, ':' );\n\n\tseparator = Q_max( slash, backslash );\n\tseparator = Q_max( separator, colon );\n\n\tbasepathlength = separator ? (separator + 1 - pattern) : 0;\n\tbasepath = Mem_Calloc( fs_mempool, basepathlength + 1 );\n\tif( basepathlength )\n\t\tmemcpy( basepath, pattern, basepathlength );\n\tbasepath[basepathlength] = '\\0';\n\n\tstringlistinit( &dirlist );\n\tAndroid_ListDirectory( &dirlist, basepath, search->assets->engine );\n\n\tQ_strncpy( temp, basepath, sizeof( temp ));\n\n\tfor( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ )\n\t{\n\t\tQ_strncpy( &temp[basepathlength], dirlist.strings[dirlistindex], sizeof( temp ) - basepathlength );\n\n\t\tif( matchpattern( temp, (char *)pattern, true ))\n\t\t{\n\t\t\tfor( resultlistindex = 0; resultlistindex < list->numstrings; resultlistindex++ )\n\t\t\t{\n\t\t\t\tif( !Q_strcmp( list->strings[resultlistindex], temp ))\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( resultlistindex == list->numstrings )\n\t\t\t\tstringlistappend( list, temp );\n\t\t}\n\t}\n\n\tstringlistfreecontents( &dirlist );\n\n\tMem_Free( basepath );\n}\n\nstatic file_t *FS_OpenFile_AndroidAssets( searchpath_t *search, const char *filename, const char *mode, int pack_ind )\n{\n\tfile_t *file = Mem_Calloc( fs_mempool, sizeof( *file ));\n\tAAsset *assets = AAssetManager_open( search->assets->asset_manager, filename, AASSET_MODE_RANDOM );\n\n\tfile->handle = AAsset_openFileDescriptor( assets, &file->offset, &file->real_length );\n\n\tfile->position = 0;\n\tfile->ungetc = EOF;\n\tfile->searchpath = search;\n\n\tAAsset_close( assets );\n\n\treturn file;\n}\n\nstatic byte *FS_LoadAndroidAssetsFile( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *filesize, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * ))\n{\n\tbyte *buf;\n\toff_t size;\n\tAAsset *asset;\n\n\tif( filesize ) *filesize = 0;\n\n\tasset = AAssetManager_open( search->assets->asset_manager, path, AASSET_MODE_BUFFER );\n\tif( !asset )\n\t\treturn NULL;\n\n\tsize = AAsset_getLength( asset );\n\n\tbuf = (byte *)pfnAlloc( size + 1 );\n\tif( unlikely( !buf ))\n\t{\n\t\tCon_Reportf( \"%s: can't alloc %d bytes, no free memory\\n\", __func__, size + 1 );\n\t\tAAsset_close( asset );\n\t\treturn NULL;\n\t}\n\n\tbuf[size] = '\\0';\n\n\tif( AAsset_read( asset, buf, size ) < 0 )\n\t{\n\t\tpfnFree( buf );\n\t\tAAsset_close( asset );\n\t\treturn NULL;\n\t}\n\n\tAAsset_close( asset );\n\tif( filesize ) *filesize = size;\n\n\treturn buf;\n}\n\nsearchpath_t *FS_AddAndroidAssets_Fullpath( const char *path, int flags )\n{\n\tsearchpath_t *search;\n\tandroid_assets_t *assets = NULL;\n\tqboolean engine = true;\n\n\tif( !jni.getPackageName || !jni.getCallingPackage || !jni.getAssetsList || !jni.getAssets )\n\t\treturn NULL;\n\n\tif( FBitSet( flags, FS_STATIC_PATH | FS_CUSTOM_PATH ))\n\t\treturn NULL;\n\n\tif( FBitSet( flags, FS_GAMEDIR_PATH ) && Q_stricmp( GI->basedir, GI->gamefolder ))\n\t\tengine = false;\n\n\tassets = FS_LoadAndroidAssets( engine );\n\n\tif( !assets )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: unable to load Android assets \\\"%s\\\"\\n\", __func__, Android_GetPackageName( engine ));\n\t\treturn NULL;\n\t}\n\n\tQ_strncpy( assets->package_name, Android_GetPackageName( engine ), sizeof( assets->package_name ));\n\n\tsearch = Mem_Calloc( fs_mempool, sizeof( *search ));\n\n\tQ_strncpy( search->filename, assets->package_name, sizeof( search->filename ));\n\tsearch->assets = assets;\n\tsearch->type = SEARCHPATH_ANDROID_ASSETS;\n\tSetBits( search->flags, FS_NOWRITE_PATH | FS_CUSTOM_PATH );\n\n\tsearch->pfnPrintInfo = FS_PrintInfo_AndroidAssets;\n\tsearch->pfnClose = FS_Close_AndroidAssets;\n\tsearch->pfnOpenFile = FS_OpenFile_AndroidAssets;\n\tsearch->pfnFileTime = FS_FileTime_AndroidAssets;\n\tsearch->pfnFindFile = FS_FindFile_AndroidAssets;\n\tsearch->pfnSearch = FS_Search_AndroidAssets;\n\tsearch->pfnLoadFile = FS_LoadAndroidAssetsFile;\n\n\tCon_Reportf( \"Adding Android assets: %s\\n\", assets->package_name );\n\n\treturn search;\n}\n\nvoid FS_InitAndroid( void )\n{\n\tjmethodID getContext;\n\n\tjni.env = (JNIEnv *)Sys_GetNativeObject( \"JNIEnv\" );\n\tjni.activity_class = Sys_GetNativeObject( \"ActivityClass\" );\n\n\tif( !jni.env || !jni.activity_class )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: unable to get JNI env to load Android assets\\n\", __func__ );\n\t\treturn;\n\t}\n\n\tgetContext = (*jni.env)->GetStaticMethodID( jni.env, jni.activity_class, \"getContext\", \"()Landroid/content/Context;\" );\n\tjni.activity = (*jni.env)->CallStaticObjectMethod( jni.env, jni.activity_class, getContext );\n\n\tjni.getPackageName = (*jni.env)->GetMethodID( jni.env, jni.activity_class, \"getPackageName\", \"()Ljava/lang/String;\" );\n\tjni.getCallingPackage = (*jni.env)->GetMethodID( jni.env, jni.activity_class, \"getCallingPackage\", \"()Ljava/lang/String;\" );\n\tjni.getAssetsList = (*jni.env)->GetMethodID( jni.env, jni.activity_class, \"getAssetsList\", \"(ZLjava/lang/String;)[Ljava/lang/String;\" );\n\tjni.getAssets = (*jni.env)->GetMethodID( jni.env, jni.activity_class, \"getAssets\", \"(Z)Landroid/content/res/AssetManager;\" );\n\n\tif( !jni.getPackageName || !jni.getCallingPackage || !jni.getAssetsList || !jni.getAssets )\n\t\tCon_Reportf( S_WARN \"%s: unable to find required JNI interfaces to load Android assets\\n\", __func__ );\n}\n\n#endif // XASH_ANDROID\n"
  },
  {
    "path": "filesystem/dir.c",
    "content": "/*\ndir.c - caseinsensitive directory operations\nCopyright (C) 2022 Alibek Omarov, Velaron\nCopyright (C) 2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"build.h\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <stddef.h>\n#if XASH_POSIX\n#include <unistd.h>\n#if !XASH_PSVITA\n#include <sys/ioctl.h>\n#endif\n#endif\n#if XASH_LINUX\n#include <linux/fs.h>\n#ifndef FS_CASEFOLD_FL // for compatibility with older distros\n#define FS_CASEFOLD_FL 0x40000000\n#endif // FS_CASEFOLD_FL\n#endif // XASH_LINUX\n\n#include \"port.h\"\n#include \"filesystem_internal.h\"\n#include \"crtlib.h\"\n#include \"xash3d_mathlib.h\"\n#include \"common/com_strings.h\"\n\nenum\n{\n\tDIRENTRY_EMPTY_DIRECTORY = 0, // don't care if it's not directory or it's empty\n\tDIRENTRY_NOT_SCANNED = -1,\n\tDIRENTRY_CASEINSENSITIVE = -2, // directory is already caseinsensitive, just copy whatever is left\n};\n\ntypedef struct dir_s\n{\n\tstring name;\n\tint numentries;\n\tstruct dir_s *entries; // sorted\n} dir_t;\n\nstatic qboolean Platform_GetDirectoryCaseSensitivity( const char *dir )\n{\n#if XASH_WIN32 || XASH_PSVITA || XASH_NSWITCH\n\treturn false;\n#elif XASH_ANDROID\n\t// on Android, doing code below causes crash in MediaProviderGoogle.apk!libfuse_jni.so\n\t// which in turn makes vold (Android's Volume Daemon) to umount /storage/emulated/0\n\t// and because you can't unmount a filesystem when there is file descriptors open\n\t// it has no other choice but to terminate and then kill our program\n\treturn true;\n#elif XASH_LINUX && defined( FS_IOC_GETFLAGS )\n\tint flags = 0;\n\tint fd;\n\n\tfd = open( dir, O_RDONLY | O_NONBLOCK );\n\tif( fd < 0 )\n\t\treturn true;\n\n\tif( ioctl( fd, FS_IOC_GETFLAGS, &flags ) < 0 )\n\t{\n\t\tclose( fd );\n\t\treturn true;\n\t}\n\n\tclose( fd );\n\n\treturn !FBitSet( flags, FS_CASEFOLD_FL );\n#else\n\treturn true;\n#endif\n}\n\nstatic int FS_SortDirEntries( const void *_a, const void *_b )\n{\n\tconst dir_t *a = _a;\n\tconst dir_t *b = _b;\n\treturn Q_stricmp( a->name, b->name );\n}\n\nstatic void FS_FreeDirEntries( dir_t *dir )\n{\n\tif( dir->entries )\n\t{\n\t\tint i;\n\t\tfor( i = 0; i < dir->numentries; i++ )\n\t\t\tFS_FreeDirEntries( &dir->entries[i] );\n\t\tdir->entries = NULL;\n\t}\n\n\tdir->numentries = DIRENTRY_NOT_SCANNED;\n}\n\nstatic void FS_InitDirEntries( dir_t *dir, const stringlist_t *list )\n{\n\tint i;\n\n\tdir->numentries = list->numstrings;\n\tdir->entries = Mem_Malloc( fs_mempool, sizeof( dir_t ) * dir->numentries );\n\n\tfor( i = 0; i < list->numstrings; i++ )\n\t{\n\t\tdir_t *entry = &dir->entries[i];\n\n\t\tQ_strncpy( entry->name, list->strings[i], sizeof( entry->name ));\n\t\tentry->numentries = DIRENTRY_NOT_SCANNED;\n\t\tentry->entries = NULL;\n\t}\n\n\tqsort( dir->entries, dir->numentries, sizeof( dir->entries[0] ), FS_SortDirEntries );\n}\n\nstatic void FS_PopulateDirEntries( dir_t *dir, const char *path )\n{\n\tstringlist_t list;\n\n\tif( !FS_SysFolderExists( path ))\n\t{\n\t\tdir->numentries = DIRENTRY_EMPTY_DIRECTORY;\n\t\tdir->entries = NULL;\n\t\treturn;\n\t}\n\n\tif( !Platform_GetDirectoryCaseSensitivity( path ))\n\t{\n\t\tdir->numentries = DIRENTRY_CASEINSENSITIVE;\n\t\tdir->entries = NULL;\n\t\treturn;\n\t}\n\n\tstringlistinit( &list );\n\tlistdirectory( &list, path, false );\n\tif( !list.numstrings )\n\t{\n\t\tdir->numentries = DIRENTRY_EMPTY_DIRECTORY;\n\t\tdir->entries = NULL;\n\t}\n\telse\n\t{\n\t\tFS_InitDirEntries( dir, &list );\n\t}\n\tstringlistfreecontents( &list );\n}\n\nstatic int FS_FindDirEntry( dir_t *dir, const char *name )\n{\n\tint left, right;\n\n\t// look for the file (binary search)\n\tleft = 0;\n\tright = dir->numentries - 1;\n\n\twhile( left <= right )\n\t{\n\t\tint   middle = (left + right) / 2;\n\t\tint\tdiff;\n\n\t\tdiff = Q_stricmp( dir->entries[middle].name, name );\n\n\t\t// found it\n\t\tif( !diff )\n\t\t\treturn middle;\n\n\t\t// if we're too far in the list\n\t\tif( diff > 0 )\n\t\t\tright = middle - 1;\n\t\telse left = middle + 1;\n\t}\n\treturn -1;\n}\n\nstatic void FS_MergeDirEntries( dir_t *dir, const stringlist_t *list )\n{\n\tint i;\n\tdir_t temp;\n\n\t// glorified realloc for sorted dir entries\n\t// make new array and copy old entries with same name and subentries\n\t// everything else get freed\n\n\tFS_InitDirEntries( &temp, list );\n\n\tfor( i = 0; i < dir->numentries; i++ )\n\t{\n\t\tdir_t *oldentry = &dir->entries[i];\n\t\tdir_t *newentry;\n\t\tint j;\n\n\t\t// don't care about directories without subentries\n\t\tif( oldentry->entries == NULL )\n\t\t\tcontinue;\n\n\t\t// try to find this directory in new tree\n\t\tj = FS_FindDirEntry( &temp, oldentry->name );\n\n\t\t// not found, free memory\n\t\tif( j < 0 )\n\t\t{\n\t\t\tFS_FreeDirEntries( oldentry );\n\t\t\tcontinue;\n\t\t}\n\n\t\t// found directory, move all entries\n\t\tnewentry = &temp.entries[j];\n\n\t\tnewentry->numentries = oldentry->numentries;\n\t\tnewentry->entries = oldentry->entries;\n\t}\n\n\t// now we can free old tree and replace it with temporary\n\t// do not add null check there! If we hit it, it's probably a logic error!\n\tMem_Free( dir->entries );\n\tdir->numentries = temp.numentries;\n\tdir->entries = temp.entries;\n}\n\nstatic int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *entryname )\n{\n\tstringlist_t list;\n\tint ret;\n\n\tstringlistinit( &list );\n\tlistdirectory( &list, path, false );\n\n\tif( list.numstrings == 0 ) // empty directory\n\t{\n\t\tFS_FreeDirEntries( dir );\n\t\tdir->numentries = DIRENTRY_EMPTY_DIRECTORY;\n\t\tret = -1;\n\t}\n\telse if( dir->numentries <= DIRENTRY_EMPTY_DIRECTORY ) // not initialized or was empty\n\t{\n\t\tFS_InitDirEntries( dir, &list );\n\t\tret = FS_FindDirEntry( dir, entryname );\n\t}\n\telse if( list.numstrings != dir->numentries ) // quick update\n\t{\n\t\tFS_MergeDirEntries( dir, &list );\n\t\tret = FS_FindDirEntry( dir, entryname );\n\t}\n\telse\n\t{\n\t\t// do heavy compare if directory now have an entry we need\n\t\tint i;\n\n\t\tfor( i = 0; i < list.numstrings; i++ )\n\t\t{\n\t\t\tif( !Q_stricmp( list.strings[i], entryname ))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif( i != list.numstrings )\n\t\t{\n\t\t\tFS_MergeDirEntries( dir, &list );\n\t\t\tret = FS_FindDirEntry( dir, entryname );\n\t\t}\n\t\telse ret = -1;\n\t}\n\n\tstringlistfreecontents( &list );\n\treturn ret;\n}\n\nstatic inline qboolean FS_AppendToPath( char *dst, size_t *pi, const size_t len, const char *src, const char *path, const char *err )\n{\n\tsize_t i = *pi;\n\n\ti += Q_strncpy( &dst[i], src, len - i );\n\t*pi = i;\n\n\tif( i >= len )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: overflow while appending %s (%s)\\n\", __func__, path, err );\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nqboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath )\n{\n\tconst char *prev;\n\tconst char *next;\n\tsize_t i = 0;\n\n\tif( !FS_AppendToPath( dst, &i, len, dir->name, path, \"init\" ))\n\t\treturn false;\n\n\t// nothing to fix\n\tif( !COM_CheckStringEmpty( path ))\n\t\treturn true;\n\n\tfor( prev = path, next = Q_strchrnul( prev, '/' );\n\t\t  ;\n\t\t  prev = next + 1, next = Q_strchrnul( prev, '/' ))\n\t{\n\t\tqboolean uptodate = false; // do not run second scan if we're just updated our directory list\n\t\tsize_t temp;\n\t\tchar entryname[MAX_SYSPATH];\n\t\tint ret;\n\n\t\tif( dir->numentries == DIRENTRY_NOT_SCANNED )\n\t\t{\n\t\t\t// read directory first time\n\t\t\tFS_PopulateDirEntries( dir, dst );\n\t\t\tuptodate = true;\n\t\t}\n\n\t\t// this subdirectory is case insensitive, just slam everything that's left\n\t\tif( dir->numentries == DIRENTRY_CASEINSENSITIVE )\n\t\t{\n\t\t\tif( !FS_AppendToPath( dst, &i, len, prev, path, \"caseinsensitive entry\" ))\n\t\t\t\treturn false;\n\n\t\t\t// check file existense\n\t\t\treturn createpath ? true : FS_SysFileOrFolderExists( dst );\n\t\t}\n\n\t\t// get our entry name\n\t\tQ_strncpy( entryname, prev, next - prev + 1 );\n\n\t\t// didn't found, but does it exists in FS?\n\t\tif(( ret = FS_FindDirEntry( dir, entryname )) < 0 )\n\t\t{\n\t\t\t// if we're creating files or folders, we don't care if path doesn't exist\n\t\t\t// so copy everything that's left and exit without an error\n\t\t\tif( uptodate || ( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 )\n\t\t\t\treturn createpath ? FS_AppendToPath( dst, &i, len, prev, path, \"create path\" ) : false;\n\n\t\t\tuptodate = true;\n\t\t}\n\n\t\tdir = &dir->entries[ret];\n\t\ttemp = i;\n\t\tif( !FS_AppendToPath( dst, &temp, len, dir->name, path, \"case fix\" ))\n\t\t\treturn false;\n\n\t\tif( !uptodate && !FS_SysFileOrFolderExists( dst )) // file not found, rescan...\n\t\t{\n\t\t\tdst[i] = 0; // strip failed part\n\n\t\t\t// if we're creating files or folders, we don't care if path doesn't exist\n\t\t\t// so copy everything that's left and exit without an error\n\t\t\tif(( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 )\n\t\t\t\treturn createpath ? FS_AppendToPath( dst, &i, len, prev, path, \"create path rescan\" ) : false;\n\n\t\t\tdir = &dir->entries[ret];\n\t\t\tif( !FS_AppendToPath( dst, &temp, len, dir->name, path, \"case fix rescan\" ))\n\t\t\t\treturn false;\n\t\t}\n\t\ti = temp;\n\n\t\t// end of string, found file, return\n\t\tif( next[0] == '\\0' || ( next[0] == '/' && next[1] == '\\0' ))\n\t\t\tbreak;\n\n\t\tif( !FS_AppendToPath( dst, &i, len, \"/\", path, \"path separator\" ))\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic void FS_Close_DIR( searchpath_t *search )\n{\n\tFS_FreeDirEntries( search->dir );\n\tMem_Free( search->dir );\n}\n\nstatic void FS_PrintInfo_DIR( searchpath_t *search, char *dst, size_t size )\n{\n\tQ_strncpy( dst, search->filename, size );\n}\n\nstatic int FS_FindFile_DIR( searchpath_t *search, const char *path, char *fixedname, size_t len )\n{\n\tchar netpath[MAX_SYSPATH];\n\n\tif( !FS_FixFileCase( search->dir, path, netpath, sizeof( netpath ), false ))\n\t\treturn -1;\n\n\tif( FS_SysFileExists( netpath ))\n\t{\n\t\t// return fixed case file name only local for that searchpath\n\t\tif( fixedname )\n\t\t\tQ_strncpy( fixedname, netpath + Q_strlen( search->filename ), len );\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\nstatic void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )\n{\n\tstring netpath, temp;\n\tstringlist_t dirlist;\n\tconst char *slash, *backslash, *colon, *separator;\n\tint basepathlength, dirlistindex, resultlistindex;\n\tchar *basepath;\n\n\tslash = Q_strrchr( pattern, '/' );\n\tbackslash = Q_strrchr( pattern, '\\\\' );\n\tcolon = Q_strrchr( pattern, ':' );\n\n\tseparator = Q_max( slash, backslash );\n\tseparator = Q_max( separator, colon );\n\n\tbasepathlength = separator ? (separator + 1 - pattern) : 0;\n\tbasepath = Mem_Calloc( fs_mempool, basepathlength + 1 );\n\tif( basepathlength ) memcpy( basepath, pattern, basepathlength );\n\tbasepath[basepathlength] = '\\0';\n\n\tif( !FS_FixFileCase( search->dir, basepath, netpath, sizeof( netpath ), false ))\n\t{\n\t\tMem_Free( basepath );\n\t\treturn;\n\t}\n\n\tstringlistinit( &dirlist );\n\tlistdirectory( &dirlist, netpath, false );\n\n\tQ_strncpy( temp, basepath, sizeof( temp ));\n\n\tfor( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ )\n\t{\n\t\tQ_strncpy( &temp[basepathlength], dirlist.strings[dirlistindex], sizeof( temp ) - basepathlength );\n\n\t\tif( matchpattern( temp, (char *)pattern, true ) )\n\t\t{\n\t\t\tfor( resultlistindex = 0; resultlistindex < list->numstrings; resultlistindex++ )\n\t\t\t{\n\t\t\t\tif( !Q_strcmp( list->strings[resultlistindex], temp ) )\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( resultlistindex == list->numstrings )\n\t\t\t\tstringlistappend( list, temp );\n\t\t}\n\t}\n\n\tstringlistfreecontents( &dirlist );\n\n\tMem_Free( basepath );\n}\n\nstatic int FS_FileTime_DIR( searchpath_t *search, const char *filename )\n{\n\tchar path[MAX_SYSPATH];\n\n\tQ_snprintf( path, sizeof( path ), \"%s%s\", search->filename, filename );\n\treturn FS_SysFileTime( path );\n}\n\nstatic file_t *FS_OpenFile_DIR( searchpath_t *search, const char *filename, const char *mode, int pack_ind )\n{\n\tfile_t *f;\n\tchar path[MAX_SYSPATH];\n\n\tQ_snprintf( path, sizeof( path ), \"%s%s\", search->filename, filename );\n\tf = FS_SysOpen( path, mode );\n\tif( !f )\n\t\treturn NULL;\n\n\tf->searchpath = search;\n\n\treturn f;\n}\n\nvoid FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int flags )\n{\n\tmemset( search, 0, sizeof( searchpath_t ));\n\n\tQ_strncpy( search->filename, path, sizeof( search->filename ) - 1 );\n\tCOM_PathSlashFix( search->filename );\n\n\tif( !Q_stricmp( COM_FileExtension( path ), \"pk3dir\" ))\n\t\tsearch->type = SEARCHPATH_PK3DIR;\n\telse search->type = SEARCHPATH_PLAIN;\n\tsearch->flags = flags;\n\tsearch->pfnPrintInfo = FS_PrintInfo_DIR;\n\tsearch->pfnClose = FS_Close_DIR;\n\tsearch->pfnOpenFile = FS_OpenFile_DIR;\n\tsearch->pfnFileTime = FS_FileTime_DIR;\n\tsearch->pfnFindFile = FS_FindFile_DIR;\n\tsearch->pfnSearch = FS_Search_DIR;\n\n\t// create cache root\n\tsearch->dir = Mem_Malloc( fs_mempool, sizeof( dir_t ));\n\tQ_strncpy( search->dir->name, search->filename, sizeof( search->dir->name ));\n\tFS_PopulateDirEntries( search->dir, path );\n}\n\nsearchpath_t *FS_AddDir_Fullpath( const char *path, int flags )\n{\n\tsearchpath_t *search;\n\n\tsearch = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ));\n\tFS_InitDirectorySearchpath( search, path, flags );\n\tCon_Printf( \"Adding directory: %s\\n\", path );\n\n\treturn search;\n}\n"
  },
  {
    "path": "filesystem/exports.txt",
    "content": "GetFSAPI\n"
  },
  {
    "path": "filesystem/filesystem.c",
    "content": "/*\nfilesystem.c - game filesystem based on DP fs\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#define _GNU_SOURCE 1\n\n#include \"build.h\"\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <time.h>\n#include <errno.h>\n#if XASH_WIN32\n#include <direct.h>\n#include <io.h>\n#elif XASH_DOS4GW\n#include <direct.h>\n#else\n#include <dirent.h>\n#endif\n#include <stdio.h>\n#include <stdarg.h>\n#if HAVE_MEMFD_CREATE\n#include <sys/mman.h>\n#endif\n#include \"port.h\"\n#include \"defaults.h\"\n#include \"const.h\"\n#include \"crtlib.h\"\n#include \"crclib.h\"\n#include \"filesystem.h\"\n#include \"filesystem_internal.h\"\n#include \"xash3d_mathlib.h\"\n#include \"common/com_strings.h\"\n#include \"common/protocol.h\"\n\n#define FILE_COPY_SIZE\t\t(1024 * 1024)\n#define SAVE_AGED_COUNT 2 // the default count of quick and auto saves\n\nfs_globals_t FI;\npoolhandle_t  fs_mempool;\nchar          fs_rootdir[MAX_SYSPATH];\nsearchpath_t *fs_writepath;\n\nstatic searchpath_t *fs_searchpaths = NULL;\t// chain\nstatic char fs_basedir[MAX_SYSPATH];\t// base game directory\nstatic char fs_gamedir[MAX_SYSPATH];\t// game current directory\nstatic char fs_rodir[MAX_SYSPATH];\nstatic string fs_language;\nstatic qboolean fs_ext_path = false;\t// attempt to read\\write from ./ or ../ pathes\n\ntypedef struct fs_archive_s\n{\n\tconst char *ext;\n\tint type;\n\tFS_ADDARCHIVE_FULLPATH pfnAddArchive_Fullpath;\n\tqboolean load_wads; // load wads from this archive\n\tqboolean real_archive;\n} fs_archive_t;\n\n// add archives in specific order PAK -> PK3 -> WAD\n// so raw WADs takes precedence over WADs included into PAKs and PK3s\nstatic const fs_archive_t g_archives[] =\n{\n\t{\n\t\t.ext = \"pak\",\n\t\t.type = SEARCHPATH_PAK,\n\t\t.pfnAddArchive_Fullpath = FS_AddPak_Fullpath,\n\t\t.load_wads = true,\n\t\t.real_archive = true,\n\t}, {\n\t\t.ext = \"pk3\",\n\t\t.type = SEARCHPATH_ZIP,\n\t\t.pfnAddArchive_Fullpath = FS_AddZip_Fullpath,\n\t\t.load_wads = true,\n\t\t.real_archive = true,\n\t}, {\n\t\t.ext = \"pk3dir\",\n\t\t.type = SEARCHPATH_PK3DIR,\n\t\t.pfnAddArchive_Fullpath = FS_AddDir_Fullpath,\n\t\t.load_wads = true,\n\t\t.real_archive = false,\n\t}, {\n\t\t.ext = \"wad\",\n\t\t.type = SEARCHPATH_WAD,\n\t\t.pfnAddArchive_Fullpath = FS_AddWad_Fullpath,\n\t\t.load_wads = false,\n\t\t.real_archive = true,\n\t},\n};\n\n// special fs_archive_t for plain directories\nstatic const fs_archive_t g_directory_archive =\n{\n\t.type = SEARCHPATH_PLAIN,\n\t.pfnAddArchive_Fullpath = FS_AddDir_Fullpath,\n};\n\n#if XASH_ANDROID\nstatic const fs_archive_t g_android_archive =\n{\n\t.type = SEARCHPATH_ANDROID_ASSETS,\n\t.pfnAddArchive_Fullpath = FS_AddAndroidAssets_Fullpath\n};\n#endif\n\n#ifdef XASH_REDUCE_FD\nstatic file_t *fs_last_readfile;\nstatic zip_t *fs_last_zip;\n\nstatic void FS_EnsureOpenFile( file_t *file )\n{\n\tif( fs_last_readfile == file )\n\t\treturn;\n\n\tif( file && !file->backup_path )\n\t\treturn;\n\n\tif( fs_last_readfile && (fs_last_readfile->handle != -1) )\n\t{\n\t\tfs_last_readfile->backup_position = lseek(  fs_last_readfile->handle, 0, SEEK_CUR );\n\t\tclose( fs_last_readfile->handle );\n\t\tfs_last_readfile->handle = -1;\n\t}\n\tfs_last_readfile = file;\n\tif( file && (file->handle == -1) )\n\t{\n\t\tfile->handle = open( file->backup_path, file->backup_options );\n\t\tlseek( file->handle, file->backup_position, SEEK_SET );\n\t}\n}\n\nstatic void FS_BackupFileName( file_t *file, const char *path, uint options )\n{\n\tif( path == NULL )\n\t{\n\t\tif( file->backup_path )\n\t\t\tMem_Free( (void*)file->backup_path );\n\t\tif( file == fs_last_readfile )\n\t\t\tFS_EnsureOpenFile( NULL );\n\t}\n\telse if( options == O_RDONLY || options == (O_RDONLY|O_BINARY) )\n\t{\n\t\tfile->backup_path = copystring( path );\n\t\tfile->backup_options = options;\n\t}\n}\n#else\nstatic void FS_EnsureOpenFile( file_t *file ) {}\nstatic void FS_BackupFileName( file_t *file, const char *path, uint options ) {}\n#endif\n\nstatic void FS_InitMemory( void );\nstatic void FS_Purge( file_t* file );\n\nvoid _Mem_Free( void *data, const char *filename, int fileline )\n{\n\tg_engfuncs._Mem_Free( data, filename, fileline );\n}\n\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\treturn g_engfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline );\n}\n\n/*\n=============================================================================\n\nFILEMATCH COMMON SYSTEM\n\n=============================================================================\n*/\nvoid stringlistinit( stringlist_t *list )\n{\n\tmemset( list, 0, sizeof( *list ));\n}\n\nvoid stringlistfreecontents( stringlist_t *list )\n{\n\tint\ti;\n\n\tfor( i = 0; i < list->numstrings; i++ )\n\t{\n\t\tif( list->strings[i] )\n\t\t\tMem_Free( list->strings[i] );\n\t\tlist->strings[i] = NULL;\n\t}\n\n\tif( list->strings )\n\t\tMem_Free( list->strings );\n\n\tlist->numstrings = 0;\n\tlist->maxstrings = 0;\n\tlist->strings = NULL;\n}\n\nvoid stringlistappend( stringlist_t *list, const char *text )\n{\n\tsize_t\ttextlen;\n\n\tif( !Q_strcmp( text, \".\" ) || !Q_strcmp( text, \"..\" ))\n\t\treturn; // ignore the virtual directories\n\n\tif( list->numstrings >= list->maxstrings )\n\t{\n\t\tlist->maxstrings += 4096;\n\t\tlist->strings = Mem_Realloc( fs_mempool, list->strings, list->maxstrings * sizeof( *list->strings ));\n\t}\n\n\ttextlen = Q_strlen( text ) + 1;\n\tlist->strings[list->numstrings] = Mem_Calloc( fs_mempool, textlen );\n\tmemcpy( list->strings[list->numstrings], text, textlen );\n\tlist->numstrings++;\n}\n\nvoid stringlistsort( stringlist_t *list )\n{\n\tchar\t*temp;\n\tint\ti, j;\n\n\t// this is a selection sort (finds the best entry for each slot)\n\tfor( i = 0; i < list->numstrings - 1; i++ )\n\t{\n\t\tfor( j = i + 1; j < list->numstrings; j++ )\n\t\t{\n\t\t\tif( Q_strcmp( list->strings[i], list->strings[j] ) > 0 )\n\t\t\t{\n\t\t\t\ttemp = list->strings[i];\n\t\t\t\tlist->strings[i] = list->strings[j];\n\t\t\t\tlist->strings[j] = temp;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#if XASH_DOS4GW\n// convert names to lowercase because dos doesn't care, but pattern matching code often does\nstatic void listlowercase( stringlist_t *list )\n{\n\tchar\t*c;\n\tint\ti;\n\n\tfor( i = 0; i < list->numstrings; i++ )\n\t{\n\t\tfor( c = list->strings[i]; *c; c++ )\n\t\t\t*c = Q_tolower( *c );\n\t}\n}\n#endif\n\nvoid listdirectory( stringlist_t *list, const char *path, qboolean dirs_only )\n{\n#if XASH_WIN32\n\tchar pattern[4096];\n\tstruct _finddata_t n_file;\n\tintptr_t hFile;\n\n\tQ_snprintf( pattern, sizeof( pattern ), \"%s/*\", path );\n\n\t// ask for the directory listing handle\n\thFile = _findfirst( pattern, &n_file );\n\tif( hFile == -1 ) return;\n\n\t// start a new chain with the the first name\n\tstringlistappend( list, n_file.name );\n\t// iterate through the directory\n\twhile( _findnext( hFile, &n_file ) == 0 )\n\t{\n\t\tif( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR ))\n\t\t\tcontinue;\n\n\t\tstringlistappend( list, n_file.name );\n\t}\n\t_findclose( hFile );\n#else\n\tDIR *dir;\n\tstruct dirent *entry;\n\n\tdir = opendir( path );\n\n\tif( !dir )\n\t\treturn;\n\n\t// iterate through the directory\n\twhile(( entry = readdir( dir )))\n\t{\n#if HAVE_DIRENT_D_TYPE\n\t\tif( dirs_only && entry->d_type != DT_DIR && entry->d_type != DT_LNK && entry->d_type != DT_UNKNOWN )\n\t\t\tcontinue;\n#endif\n\n\t\tstringlistappend( list, entry->d_name );\n\t}\n\n\tclosedir( dir );\n#endif\n\n#if XASH_DOS4GW\n\t// convert names to lowercase because 8.3 always in CAPS\n\tlistlowercase( list );\n#endif\n}\n\n/*\n=============================================================================\n\nOTHER PRIVATE FUNCTIONS\n\n=============================================================================\n*/\n\n#if XASH_WIN32\n/*\n====================\nFS_PathToWideChar\n\nConverts input UTF-8 string to wide char string.\n====================\n*/\nstatic const wchar_t *FS_PathToWideChar( const char *path )\n{\n\tstatic wchar_t pathBuffer[MAX_PATH];\n\tMultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH );\n\treturn pathBuffer;\n}\n#endif\n\n/*\n============\nFS_CreatePath\n\nOnly used for FS_Open.\n============\n*/\nvoid FS_CreatePath( char *path )\n{\n\tchar\t*ofs, save;\n\n\tfor( ofs = path + 1; *ofs; ofs++ )\n\t{\n\t\tif( *ofs == '/' || *ofs == '\\\\' )\n\t\t{\n\t\t\t// create the directory\n\t\t\tsave = *ofs;\n\t\t\t*ofs = 0;\n\t\t\t_mkdir( path );\n\t\t\t*ofs = save;\n\t\t}\n\t}\n}\n\nstatic searchpath_t *FS_AddArchive_Fullpath( const fs_archive_t *archive, const char *file, int flags )\n{\n\tsearchpath_t *search;\n\n\tif( !archive )\n\t{\n\t\tint i;\n\t\tconst char *ext = COM_FileExtension( file );\n\n\t\tfor( i = 0; i < sizeof( g_archives ) / sizeof( g_archives[0] ); i++ )\n\t\t{\n\t\t\tif( !Q_stricmp( g_archives[i].ext, ext ))\n\t\t\t{\n\t\t\t\tarchive = &g_archives[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( !archive )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: unknown archive format %s, not mounted\\n\", __func__, file );\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tfor( search = fs_searchpaths; search; search = search->next )\n\t{\n\t\tif( search->type == archive->type && !Q_stricmp( search->filename, file ))\n\t\t\treturn search; // already loaded\n\t}\n\n\tsearch = archive->pfnAddArchive_Fullpath( file, flags );\n\n\tif( !search )\n\t\treturn NULL;\n\n\tsearch->next = fs_searchpaths;\n\tfs_searchpaths = search;\n\n\t// time to add in search list all the wads from this archive\n\tif( archive->load_wads && !FBitSet( flags, FS_SKIP_ARCHIVED_WADS ))\n\t{\n\t\tstringlist_t list;\n\t\tint i;\n\n\t\tstringlistinit( &list );\n\t\tsearch->pfnSearch( search, &list, \"*.wad\", true );\n\t\tstringlistsort( &list ); // keep always sorted\n\n\t\tfor( i = 0; i < list.numstrings; i++ )\n\t\t{\n\t\t\tsearchpath_t *wad;\n\t\t\tchar fullpath[MAX_SYSPATH];\n\n\t\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s/%s\", file, list.strings[i] );\n\t\t\tif(( wad = FS_AddWad_Fullpath( fullpath, flags | FS_LOAD_PACKED_WAD )))\n\t\t\t{\n\t\t\t\twad->next = fs_searchpaths;\n\t\t\t\tfs_searchpaths = wad;\n\t\t\t}\n\t\t}\n\n\t\tstringlistfreecontents( &list );\n\t}\n\n\treturn search;\n}\n\n/*\n================\nFS_AddArchive_Fullpath\n================\n*/\nsearchpath_t *FS_MountArchive_Fullpath( const char *file, int flags )\n{\n\treturn FS_AddArchive_Fullpath( NULL, file, flags );\n}\n\n/*\n================\nFS_AddGameDirectory\n\nSets fs_writepath, adds the directory to the head of the path,\nthen loads and adds pak1.pak pak2.pak ...\n================\n*/\nvoid FS_AddGameDirectory( const char *dir, uint flags )\n{\n\tstringlist_t list;\n\tsearchpath_t *search;\n\tint i, j;\n\n\tstringlistinit( &list );\n\tlistdirectory( &list, dir, false );\n\tstringlistsort( &list );\n\n\tfor( j = 0; j < sizeof( g_archives ) / sizeof( g_archives[0] ); j++ )\n\t{\n\t\tchar fullpath[MAX_SYSPATH];\n\t\tint i;\n\n\t\tfor( i = 0; i < list.numstrings; i++ )\n\t\t{\n\t\t\tif( Q_stricmp( COM_FileExtension( list.strings[i] ), g_archives[j].ext ))\n\t\t\t\tcontinue;\n\n\t\t\tQ_snprintf( fullpath, sizeof( fullpath ), \"%s%s\", dir, list.strings[i] );\n\t\t\tFS_AddArchive_Fullpath( &g_archives[j], fullpath, flags );\n\t\t}\n\t}\n\n\tstringlistfreecontents( &list );\n\n#if XASH_ANDROID\n\tFS_AddArchive_Fullpath( &g_android_archive, dir, flags );\n#endif\n\n\t// add the directory to the search path\n\t// (unpacked files have the priority over packed files)\n\tsearch = FS_AddArchive_Fullpath( &g_directory_archive, dir, flags );\n\tif( !FBitSet( flags, FS_NOWRITE_PATH ))\n\t\tfs_writepath = search;\n}\n\n/*\n================\nFS_ClearSearchPath\n================\n*/\nvoid FS_ClearSearchPath( void )\n{\n\tsearchpath_t *cur, **prev;\n\tint i;\n\n\tprev = &fs_searchpaths;\n\n\twhile( true )\n\t{\n\t\tcur = *prev;\n\n\t\tif( !cur )\n\t\t\tbreak;\n\n\t\t// never delete static paths\n\t\tif( FBitSet( cur->flags, FS_STATIC_PATH ))\n\t\t{\n\t\t\tprev = &cur->next;\n\t\t\tcontinue;\n\t\t}\n\n\t\t*prev = cur->next;\n\t\tcur->pfnClose( cur );\n\t\tMem_Free( cur );\n\t}\n\n\tfor( i = 0; i < FI.numgames; i++ )\n\t{\n\t\tif( FI.games[i] )\n\t\t\tFI.games[i]->added = false;\n\t}\n}\n\n/*\n====================\nFS_CheckNastyPath\nReturn true if the path should be rejected due to one of the following:\n1: path elements that are non-portable\n2: path elements that would allow access to files outside the game directory,\n\tor are just not a good idea for a mod to be using.\n====================\n*/\nstatic int FS_CheckNastyPath( const char *path )\n{\n\t// all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless\n\tif( !COM_CheckString( path )) return 2;\n\n\tif( fs_ext_path ) return 0;     // allow any path\n\n\t// Mac: don't allow Mac-only filenames - : is a directory separator\n\t// instead of /, but we rely on / working already, so there's no reason to\n\t// support a Mac-only path\n\t// Amiga and Windows: : tries to go to root of drive\n\tif( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive\n\n\t// Amiga: // is parent directory\n\tif( Q_strstr( path, \"//\")) return 1; // non-portable attempt to go to parent directory\n\n\t// all: don't allow going to parent directory (../ or /../)\n\tif( Q_strstr( path, \"..\")) return 2; // attempt to go outside the game directory\n\n\t// Windows and UNIXes: don't allow absolute paths\n\tif( path[0] == '/') return 2; // attempt to go outside the game directory\n\n#if 0\n\t// all: forbid trailing slash on gamedir\n\tif( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2;\n#endif\n\n\t// all: forbid leading dot on any filename for any reason\n\tif( Q_strstr(path, \"/.\")) return 2; // attempt to go outside the game directory\n\n\t// after all these checks we're pretty sure it's a / separated filename\n\t// and won't do much if any harm\n\treturn false;\n}\n\n/*\n================\nFS_WriteGameInfo\n\nassume GameInfo is valid\n================\n*/\nstatic qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )\n{\n\tfile_t\t*f = FS_Open( filepath, \"w\", false ); // we in binary-mode\n\tint\ti, write_ambients = false;\n\n\tif( !f )\n\t\treturn false;\n\n\tFS_Printf( f, \"// generated by \" XASH_ENGINE_NAME \" \" XASH_VERSION \"-%s (%s-%s)\\n\\n\\n\", g_buildcommit, Q_buildos(), Q_buildarch() );\n\n\tif( COM_CheckStringEmpty( GameInfo->basedir ) )\n\t\tFS_Printf( f, \"basedir\\t\\t\\\"%s\\\"\\n\", GameInfo->basedir );\n\n\t// DEPRECATED: gamedir key isn't supported by FWGS fork\n\t// but write it anyway to keep compability with original Xash3D\n\tif( COM_CheckStringEmpty( GameInfo->gamefolder ) )\n\t\tFS_Printf( f, \"gamedir\\t\\t\\\"%s\\\"\\n\", GameInfo->gamefolder );\n\n\tif( COM_CheckStringEmpty( GameInfo->falldir ) )\n\t\tFS_Printf( f, \"fallback_dir\\t\\\"%s\\\"\\n\", GameInfo->falldir );\n\n\tif( COM_CheckStringEmpty( GameInfo->title ) )\n\t\tFS_Printf( f, \"title\\t\\t\\\"%s\\\"\\n\", GameInfo->title );\n\n\tif( COM_CheckStringEmpty( GameInfo->startmap ) )\n\t\tFS_Printf( f, \"startmap\\t\\t\\\"%s\\\"\\n\", GameInfo->startmap );\n\n\tif( COM_CheckStringEmpty( GameInfo->trainmap ) )\n\t\tFS_Printf( f, \"trainmap\\t\\t\\\"%s\\\"\\n\", GameInfo->trainmap );\n\n\tif( GameInfo->version != 0.0f )\n\t\tFS_Printf( f, \"version\\t\\t%g\\n\", GameInfo->version );\n\n\tif( GameInfo->size != 0 )\n\t\tFS_Printf( f, \"size\\t\\t%zu\\n\", GameInfo->size );\n\n\tif( COM_CheckStringEmpty( GameInfo->game_url ) )\n\t\tFS_Printf( f, \"url_info\\t\\t\\\"%s\\\"\\n\", GameInfo->game_url );\n\n\tif( COM_CheckStringEmpty( GameInfo->update_url ) )\n\t\tFS_Printf( f, \"url_update\\t\\t\\\"%s\\\"\\n\", GameInfo->update_url );\n\n\tif( COM_CheckStringEmpty( GameInfo->type ) )\n\t\tFS_Printf( f, \"type\\t\\t\\\"%s\\\"\\n\", GameInfo->type );\n\n\tif( COM_CheckStringEmpty( GameInfo->date ) )\n\t\tFS_Printf( f, \"date\\t\\t\\\"%s\\\"\\n\", GameInfo->date );\n\n\tif( COM_CheckStringEmpty( GameInfo->dll_path ) )\n\t\tFS_Printf( f, \"dllpath\\t\\t\\\"%s\\\"\\n\", GameInfo->dll_path );\n\n\tif( COM_CheckStringEmpty( GameInfo->game_dll ) )\n\t\tFS_Printf( f, \"gamedll\\t\\t\\\"%s\\\"\\n\", GameInfo->game_dll );\n\n\tif( COM_CheckStringEmpty( GameInfo->game_dll_linux ) )\n\t\tFS_Printf( f, \"gamedll_linux\\t\\t\\\"%s\\\"\\n\", GameInfo->game_dll_linux );\n\n\tif( COM_CheckStringEmpty( GameInfo->game_dll_osx ) )\n\t\tFS_Printf( f, \"gamedll_osx\\t\\t\\\"%s\\\"\\n\", GameInfo->game_dll_osx );\n\n\tif( COM_CheckStringEmpty( GameInfo->iconpath ))\n\t\tFS_Printf( f, \"icon\\t\\t\\\"%s\\\"\\n\", GameInfo->iconpath );\n\n\tswitch( GameInfo->gamemode )\n\t{\n\tcase 1: FS_Print( f, \"gamemode\\t\\t\\\"singleplayer_only\\\"\\n\" ); break;\n\tcase 2: FS_Print( f, \"gamemode\\t\\t\\\"multiplayer_only\\\"\\n\" ); break;\n\t}\n\n\tif( COM_CheckStringEmpty( GameInfo->sp_entity ))\n\t\tFS_Printf( f, \"sp_entity\\t\\t\\\"%s\\\"\\n\", GameInfo->sp_entity );\n\tif( COM_CheckStringEmpty( GameInfo->mp_entity ))\n\t\tFS_Printf( f, \"mp_entity\\t\\t\\\"%s\\\"\\n\", GameInfo->mp_entity );\n\tif( COM_CheckStringEmpty( GameInfo->mp_filter ))\n\t\tFS_Printf( f, \"mp_filter\\t\\t\\\"%s\\\"\\n\", GameInfo->mp_filter );\n\n\tif( GameInfo->secure )\n\t\tFS_Printf( f, \"secure\\t\\t\\\"%i\\\"\\n\", GameInfo->secure );\n\n\tif( GameInfo->nomodels )\n\t\tFS_Printf( f, \"nomodels\\t\\t\\\"%i\\\"\\n\", GameInfo->nomodels );\n\n\tif( GameInfo->max_edicts > 0 )\n\t\tFS_Printf( f, \"max_edicts\\t%i\\n\", GameInfo->max_edicts );\n\tif( GameInfo->max_tents > 0 )\n\t\tFS_Printf( f, \"max_tempents\\t%i\\n\", GameInfo->max_tents );\n\tif( GameInfo->max_beams > 0 )\n\t\tFS_Printf( f, \"max_beams\\t\\t%i\\n\", GameInfo->max_beams );\n\tif( GameInfo->max_particles > 0 )\n\t\tFS_Printf( f, \"max_particles\\t%i\\n\", GameInfo->max_particles );\n\n\tfor( i = 0; i < NUM_AMBIENTS; i++ )\n\t{\n\t\tif( *GameInfo->ambientsound[i] )\n\t\t{\n\t\t\tif( !write_ambients )\n\t\t\t{\n\t\t\t\tFS_Print( f, \"\\n\" );\n\t\t\t\twrite_ambients = true;\n\t\t\t}\n\t\t\tFS_Printf( f, \"ambient%i\\t\\t%s\\n\", i, GameInfo->ambientsound[i] );\n\t\t}\n\t}\n\n\tif( GameInfo->noskills )\n\t\tFS_Printf( f, \"noskills\\t\\t\\\"%i\\\"\\n\", GameInfo->noskills );\n\n\tif( GameInfo->quicksave_aged_count != SAVE_AGED_COUNT )\n\t\tFS_Printf( f, \"quicksave_aged_count\\t\\t%d\\n\", GameInfo->quicksave_aged_count );\n\n\tif( GameInfo->autosave_aged_count != SAVE_AGED_COUNT )\n\t\tFS_Printf( f, \"autosave_aged_count\\t\\t%d\\n\", GameInfo->autosave_aged_count );\n\n\t// HL25 compatibility\n\tif( GameInfo->animated_title )\n\t\tFS_Printf( f, \"animated_title\\t\\t%i\\n\", GameInfo->animated_title );\n\n\tif( GameInfo->hd_background )\n\t\tFS_Printf( f, \"hd_background\\t\\t%i\\n\", GameInfo->hd_background );\n\n\t// always expose our extensions :)\n\tFS_Printf( f, \"internal_vgui_support\\t\\t%s\\n\", GameInfo->internal_vgui_support ? \"1\" : \"0\" );\n\tFS_Printf( f, \"render_picbutton_text\\t\\t%s\\n\", GameInfo->render_picbutton_text ? \"1\" : \"0\" );\n\n\tif( COM_CheckStringEmpty( GameInfo->demomap ))\n\t\tFS_Printf( f, \"demomap\\t\\t\\\"%s\\\"\\n\", GameInfo->demomap );\n\n\tFS_Close( f );\t// all done\n\n\treturn true;\n}\n\nstatic void FS_MakeGameInfo( void )\n{\n\tif( FS_WriteGameInfo( \"gameinfo.txt\", FI.GameInfo ))\n\t\tCon_Printf( \"Successfully generated %s/gameinfo.txt\\n\", FI.GameInfo->gamefolder );\n\telse\n\t\tCon_Printf( S_ERROR \"Can't open %s/gameinfo.txt for write\\n\", FI.GameInfo->gamefolder );\n}\n\nstatic void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime )\n{\n\tmemset( GameInfo, 0, sizeof( *GameInfo ));\n\n\t// filesystem info\n\tGameInfo->mtime = mtime;\n\tQ_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder ));\n\tQ_strncpy( GameInfo->sp_entity, \"info_player_start\", sizeof( GameInfo->sp_entity ));\n\tQ_strncpy( GameInfo->mp_entity, \"info_player_deathmatch\", sizeof( GameInfo->mp_entity ));\n\tQ_strncpy( GameInfo->iconpath, \"game.ico\", sizeof( GameInfo->iconpath ));\n\n\tif( quake )\n\t{\n\t\tQ_strncpy( GameInfo->basedir, \"id1\", sizeof( GameInfo->basedir ));\n\t\tQ_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title ));\n\t\tQ_strncpy( GameInfo->startmap, \"start\", sizeof( GameInfo->startmap ));\n\t\tQ_strncpy( GameInfo->dll_path, \"bin\", sizeof( GameInfo->dll_path ));\n\t\tQ_strncpy( GameInfo->game_dll, \"bin/progs.dll\", sizeof( GameInfo->game_dll ));\n\t\tQ_strncpy( GameInfo->game_dll_linux, \"bin/progs.so\", sizeof( GameInfo->game_dll_linux ));\n\t\tQ_strncpy( GameInfo->game_dll_osx, \"bin/progs.dylib\", sizeof( GameInfo->game_dll_osx ));\n\t}\n\telse\n\t{\n\t\tQ_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));\n\t\tQ_strncpy( GameInfo->title, \"New Game\", sizeof( GameInfo->title ));\n\t\tQ_strncpy( GameInfo->startmap, \"newmap\", sizeof( GameInfo->startmap ));\n\t\tQ_strncpy( GameInfo->dll_path, \"cl_dlls\", sizeof( GameInfo->dll_path ));\n\t\tQ_strncpy( GameInfo->game_dll, \"dlls/hl.dll\", sizeof( GameInfo->game_dll ));\n\t\tQ_strncpy( GameInfo->game_dll_linux, \"dlls/hl.so\", sizeof( GameInfo->game_dll_linux ));\n\t\tQ_strncpy( GameInfo->game_dll_osx, \"dlls/hl.dylib\", sizeof( GameInfo->game_dll_osx ));\n\t}\n\n\tGameInfo->max_edicts     = DEFAULT_MAX_EDICTS; // default value if not specified\n\tGameInfo->max_tents      = 500;\n\tGameInfo->max_beams      = 128;\n\tGameInfo->max_particles  = 4096;\n\tGameInfo->version        = 1.0f;\n\n\tGameInfo->quicksave_aged_count = SAVE_AGED_COUNT;\n\tGameInfo->autosave_aged_count  = SAVE_AGED_COUNT;\n}\n\nstatic void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qboolean isGameInfo )\n{\n\tchar *pfile = (char*) buf;\n\tqboolean found_linux = false, found_osx = false;\n\tstring token;\n\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\t// different names in liblist/gameinfo\n\t\tif( !Q_stricmp( token, isGameInfo ? \"title\" : \"game\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->title, sizeof( GameInfo->title ));\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"fallback_dir\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->falldir, sizeof( GameInfo->falldir ));\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"startmap\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->startmap, sizeof( GameInfo->startmap ));\n\t\t\tCOM_StripExtension( GameInfo->startmap ); // HQ2:Amen has extension .bsp\n\t\t}\n\t\t// only trainmap is valid for gameinfo\n\t\telse if( !Q_stricmp( token, \"trainmap\" ) ||\n\t\t\t(!isGameInfo && !Q_stricmp( token, \"trainingmap\" )))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->trainmap, sizeof( GameInfo->trainmap ));\n\t\t\tCOM_StripExtension( GameInfo->trainmap ); // HQ2:Amen has extension .bsp\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"url_info\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->game_url, sizeof( GameInfo->game_url ));\n\t\t}\n\t\t// different names\n\t\telse if( !Q_stricmp( token, isGameInfo ? \"url_update\" : \"url_dl\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->update_url, sizeof( GameInfo->update_url ));\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"gamedll\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->game_dll, sizeof( GameInfo->game_dll ));\n\t\t\tCOM_FixSlashes( GameInfo->game_dll );\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"gamedll_linux\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->game_dll_linux, sizeof( GameInfo->game_dll_linux ));\n\t\t\tfound_linux = true;\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"gamedll_osx\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->game_dll_osx, sizeof( GameInfo->game_dll_osx ));\n\t\t\tfound_osx = true;\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"icon\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->iconpath, sizeof( GameInfo->iconpath ));\n\t\t\tCOM_FixSlashes( GameInfo->iconpath );\n\t\t\tCOM_DefaultExtension( GameInfo->iconpath, \".ico\", sizeof( GameInfo->iconpath ));\n\t\t}\n\t\telse if( !Q_stricmp( token, \"type\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\n\t\t\tif( isGameInfo )\n\t\t\t{\n\t\t\t\tQ_strncpy( GameInfo->type, token, sizeof( GameInfo->type ));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( !Q_stricmp( token, \"singleplayer_only\" ))\n\t\t\t\t{\n\t\t\t\t\t// TODO: Remove this ugly hack too.\n\t\t\t\t\t// This was made because Half-Life has multiplayer,\n\t\t\t\t\t// but for some reason it's marked as singleplayer_only.\n\t\t\t\t\t// Old WON version is fine.\n\t\t\t\t\tif( !Q_stricmp( GameInfo->gamefolder, \"valve\") )\n\t\t\t\t\t\tGameInfo->gamemode = GAME_NORMAL;\n\t\t\t\t\telse\n\t\t\t\t\t\tGameInfo->gamemode = GAME_SINGLEPLAYER_ONLY;\n\t\t\t\t\tQ_strncpy( GameInfo->type, \"Single\", sizeof( GameInfo->type ));\n\t\t\t\t}\n\t\t\t\telse if( !Q_stricmp( token, \"multiplayer_only\" ))\n\t\t\t\t{\n\t\t\t\t\tGameInfo->gamemode = GAME_MULTIPLAYER_ONLY;\n\t\t\t\t\tQ_strncpy( GameInfo->type, \"Multiplayer\", sizeof( GameInfo->type ));\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// pass type without changes\n\t\t\t\t\tif( !isGameInfo )\n\t\t\t\t\t\tGameInfo->gamemode = GAME_NORMAL;\n\t\t\t\t\tQ_strncpy( GameInfo->type, token, sizeof( GameInfo->type ));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"version\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->version = Q_atof( token );\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"size\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->size = Q_atoi( token );\n\t\t}\n\t\telse if( !Q_stricmp( token, isGameInfo ? \"mp_entity\" : \"mpentity\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->mp_entity, sizeof( GameInfo->mp_entity ));\n\t\t}\n\t\telse if( !Q_stricmp( token, isGameInfo ? \"mp_filter\" : \"mpfilter\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, GameInfo->mp_filter, sizeof( GameInfo->mp_filter ));\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"secure\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->secure = Q_atoi( token ) ? true : false;\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"nomodels\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->nomodels = Q_atoi( token ) ? true : false;\n\t\t}\n\t\telse if( !Q_stricmp( token, isGameInfo ? \"max_edicts\" : \"edicts\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->max_edicts = bound( MIN_EDICTS, Q_atoi( token ), MAX_EDICTS );\n\t\t}\n\t\t// valid for both\n\t\telse if( !Q_stricmp( token, \"hd_background\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->hd_background = Q_atoi( token ) ? true : false;\n\t\t}\n\t\telse if( !Q_stricmp( token, \"animated_title\" ))\n\t\t{\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tGameInfo->animated_title = Q_atoi( token ) ? true : false;\n\t\t}\n\t\t// only for gameinfo\n\t\telse if( isGameInfo )\n\t\t{\n\t\t\tif( !Q_stricmp( token, \"basedir\" ))\n\t\t\t{\n\t\t\t\tstring fs_path;\n\t\t\t\tpfile = COM_ParseFile( pfile, fs_path, sizeof( fs_path ));\n\t\t\t\tif( Q_stricmp( fs_path, GameInfo->basedir ) || Q_stricmp( fs_path, GameInfo->gamefolder ))\n\t\t\t\t\tQ_strncpy( GameInfo->basedir, fs_path, sizeof( GameInfo->basedir ));\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"sp_entity\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, GameInfo->sp_entity, sizeof( GameInfo->sp_entity ));\n\t\t\t}\n\t\t\telse if( isGameInfo && !Q_stricmp( token, \"dllpath\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, GameInfo->dll_path, sizeof( GameInfo->dll_path ));\n\t\t\t}\n\t\t\telse if( isGameInfo && !Q_stricmp( token, \"date\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, GameInfo->date, sizeof( GameInfo->date ));\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"max_tempents\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->max_tents = bound( 300, Q_atoi( token ), 2048 );\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"max_beams\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->max_beams = bound( 64, Q_atoi( token ), 512 );\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"max_particles\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->max_particles = bound( 1024, Q_atoi( token ), 131072 );\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"gamemode\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\t// TODO: Remove this ugly hack too.\n\t\t\t\t// This was made because Half-Life has multiplayer,\n\t\t\t\t// but for some reason it's marked as singleplayer_only.\n\t\t\t\t// Old WON version is fine.\n\t\t\t\tif( !Q_stricmp( token, \"singleplayer_only\" ) && Q_stricmp( GameInfo->gamefolder, \"valve\") )\n\t\t\t\t\tGameInfo->gamemode = GAME_SINGLEPLAYER_ONLY;\n\t\t\t\telse if( !Q_stricmp( token, \"multiplayer_only\" ))\n\t\t\t\t\tGameInfo->gamemode = GAME_MULTIPLAYER_ONLY;\n\t\t\t}\n\t\t\telse if( !Q_strnicmp( token, \"ambient\", 7 ))\n\t\t\t{\n\t\t\t\tint\tambientNum = Q_atoi( token + 7 );\n\n\t\t\t\tif( ambientNum < 0 || ambientNum >= NUM_AMBIENTS )\n\t\t\t\t\tambientNum = 0;\n\t\t\t\tpfile = COM_ParseFile( pfile, GameInfo->ambientsound[ambientNum],\n\t\t\t\t\tsizeof( GameInfo->ambientsound[ambientNum] ));\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"noskills\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->noskills = Q_atoi( token ) ? true : false;\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"render_picbutton_text\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->render_picbutton_text = Q_atoi( token ) ? true : false;\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"internal_vgui_support\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->internal_vgui_support = Q_atoi( token ) ? true : false;\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"quicksave_aged_count\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->quicksave_aged_count = bound( 2, Q_atoi( token ), 99 );\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"autosave_aged_count\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\t\tGameInfo->autosave_aged_count = bound( 2, Q_atoi( token ), 99 );\n\t\t\t}\n\t\t\telse if( !Q_stricmp( token, \"demomap\" ))\n\t\t\t{\n\t\t\t\tpfile = COM_ParseFile( pfile, GameInfo->demomap, sizeof( GameInfo->demomap ));\n\t\t\t}\n\t\t}\n\t}\n\n\t// demomap only valid for gameinfo.txt but HL1 after 25th anniversary update\n\t// comes with demo chapter. Set the demomap here.\n\tif( !COM_CheckStringEmpty( GameInfo->demomap ))\n\t{\n\t\tif( !Q_stricmp( GameInfo->title, \"Half-Life\" )) // original check from GameUI\n\t\t\tQ_strncpy( GameInfo->demomap, \"hldemo1\", sizeof( GameInfo->demomap ));\n\t}\n\n\tif( !found_linux || !found_osx )\n\t{\n\t\t// just replace extension from dll to so/dylib\n\t\tchar gamedll[64];\n\t\tQ_strncpy( gamedll, GameInfo->game_dll, sizeof( gamedll ));\n\t\tCOM_StripExtension( gamedll );\n\n\t\tif( !found_linux )\n\t\t\tQ_snprintf( GameInfo->game_dll_linux, sizeof( GameInfo->game_dll_linux ), \"%s.so\", gamedll );\n\n\t\tif( !found_osx )\n\t\t\tQ_snprintf( GameInfo->game_dll_osx, sizeof( GameInfo->game_dll_osx ), \"%s.dylib\", gamedll );\n\t}\n\n\t// make sure what gamedir is really exist\n\t// a1ba: why we are doing this???\n\tQ_snprintf( token, sizeof( token ), \"%s/%s\", fs_rootdir, GameInfo->falldir );\n\tif( !FS_SysFolderExists( token ))\n\t{\n\t\tif( COM_CheckStringEmpty( fs_rodir ))\n\t\t{\n\t\t\tQ_snprintf( token, sizeof( token ), \"%s/%s\", fs_rodir, GameInfo->falldir );\n\t\t\tif( !FS_SysFolderExists( token ))\n\t\t\t\tGameInfo->falldir[0] = 0;\n\t\t}\n\t\telse GameInfo->falldir[0] = 0;\n\t}\n}\n\n/*\n================\nFS_ParseLiblistGam\n================\n*/\nstatic qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )\n{\n\tchar *afile = FS_LoadDirectFile( filename, NULL );\n\n\tif( !afile )\n\t\treturn false;\n\n\tFS_InitGameInfo( GameInfo, gamedir, false, mtime );\n\tFS_ParseGenericGameInfo( GameInfo, afile, false );\n\n\tMem_Free( afile );\n\n\treturn true;\n}\n\n/*\n================\nFS_ConvertGameInfo\n================\n*/\nstatic qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path, gameinfo_t *gi, time_t liblist_mtime )\n{\n\tmemset( gi, 0, sizeof( *gi ));\n\n\t// liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir!\n\tif( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime ))\n\t{\n\t\tCon_DPrintf( \"Convert %s to %s\\n\", liblist_path, gameinfo_path );\n\t\treturn FS_WriteGameInfo( gameinfo_path, gi );\n\t}\n\n\treturn false;\n}\n\n/*\n================\nFS_ReadGameInfo\n================\n*/\nstatic qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )\n{\n\tchar *afile = FS_LoadDirectFile( filename, NULL );\n\n\tif( !afile )\n\t\treturn false;\n\n\tFS_InitGameInfo( GameInfo, gamedir, false, mtime );\n\tFS_ParseGenericGameInfo( GameInfo, afile, true );\n\n\tMem_Free( afile );\n\n\treturn true;\n}\n\n/*\n================\nFS_CheckForQuakeGameDir\n\nChecks if game directory resembles Quake Engine game directory\n(some of checks may as well work with Xash gamedirs, it's not a bug)\n================\n*/\nstatic qboolean FS_CheckForQuakeGameDir( const char *gamedir )\n{\n\t// if directory contain quake.rc or progs.dat it's 100% quake gamedir\n\t// quake mods probably always archived, so check pak0.pak too\n\tconst char *files[] = { \"progs.dat\", \"quake.rc\" };\n\tchar buf[MAX_SYSPATH];\n\tint i;\n\n\t// try to read pak0.pak first, most quake mods are archived\n\tif( Q_snprintf( buf, sizeof( buf ), \"%s/pak0.pak\", gamedir ) > 0 )\n\t{\n\t\tif( FS_SysFileExists( buf ))\n\t\t{\n\t\t\tif( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] )))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\t// search it in the filesystem\n\tfor( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )\n\t{\n\t\tif( Q_snprintf( buf, sizeof( buf ), \"%s/%s\", gamedir, files[i] ) > 0 )\n\t\t{\n\t\t\tif( FS_SysFileExists( buf ))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n===============\nFS_CheckForXashGameDir\n\nChecks if game directory resembles Xash3D game directory\n===============\n*/\nstatic qboolean FS_CheckForXashGameDir( const char *gamedir )\n{\n\t// if directory contain gameinfo.txt or liblist.gam it's 100% gamedir\n\tconst char *files[] = { \"gameinfo.txt\", \"liblist.gam\" };\n\tint i;\n\n\tfor( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )\n\t{\n\t\tchar buf[MAX_SYSPATH];\n\n\t\tif( Q_snprintf( buf, sizeof( buf ), \"%s/%s\", gamedir, files[i] ) > 0 )\n\t\t{\n\t\t\tif( FS_SysFileExists( buf ))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n================\nFS_ParseGameInfo\n================\n*/\nstatic qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir )\n{\n\tchar liblist_path[MAX_SYSPATH];\n\tchar gameinfo_path[MAX_SYSPATH];\n\tchar gamedir_path[MAX_SYSPATH];\n\ttime_t liblist_mtime = -1;\n\ttime_t gameinfo_mtime = -1;\n\n\tif( rodir )\n\t\tQ_snprintf( gamedir_path, sizeof( gamedir_path ), \"%s/%s\", fs_rodir, gamedir );\n\telse\n\t\tQ_snprintf( gamedir_path, sizeof( gamedir_path ), \"%s\", gamedir );\n\n\tif( !FS_CheckForXashGameDir( gamedir_path ))\n\t{\n\t\t// check if we need to generate gameinfo for Quake\n\t\tif( FS_CheckForQuakeGameDir( gamedir_path ))\n\t\t{\n\t\t\t// just generate stub gameinfo in memory\n\t\t\tFS_InitGameInfo( GameInfo, gamedir, true, -1 );\n\t\t\tGameInfo->rodir = rodir;\n\t\t\treturn true;\n\t\t}\n\n\t\t// don't add empty or addon directories\n\t\treturn false;\n\t}\n\n\tQ_snprintf( gameinfo_path, sizeof( gameinfo_path ), \"%s/gameinfo.txt\", gamedir_path );\n\tQ_snprintf( liblist_path, sizeof( liblist_path ), \"%s/liblist.gam\", gamedir_path );\n\n\tliblist_mtime = FS_SysFileTime( liblist_path );\n\tgameinfo_mtime = FS_SysFileTime( gameinfo_path );\n\n\t// in this function we never write new files for RoDir compatibility\n\t// since RoDir is only FWGS feature, do gameinfo.txt conversion only\n\t// for RwDir for those who worked with original Xash3D\n\tif( !rodir )\n\t{\n\t\t// !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!!\n\t\tif( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime )\n\t\t{\n\t\t\tif( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime ))\n\t\t\t\treturn true;\n\t\t}\n\t}\n\n\t// can we parse gameinfo.txt?\n\tif( gameinfo_mtime >= 0 )\n\t{\n\t\tif( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime ))\n\t\t{\n\t\t\tGameInfo->rodir = rodir;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// can we parse liblist.gam?\n\tif( liblist_mtime >= 0 )\n\t{\n\t\tif( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime ))\n\t\t{\n\t\t\tGameInfo->rodir = rodir;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/*\n================\nFS_AddGameHierarchy\n================\n*/\nvoid FS_AddGameHierarchy( const char *dir, uint flags )\n{\n\tint i;\n\tqboolean isGameDir = flags & FS_GAMEDIR_PATH;\n\tchar buf[MAX_VA_STRING];\n\n\tif( !COM_CheckString( dir ))\n\t\treturn;\n\n\tCon_Printf( \"%s( %s )\\n\", __func__, dir );\n\n\t// add the common game directory\n\n\t// recursive gamedirs\n\t// for example, czeror->czero->cstrike->valve\n\tfor( i = 0; i < FI.numgames; i++ )\n\t{\n\t\tif( !Q_stricmp( FI.games[i]->gamefolder, dir ))\n\t\t{\n\t\t\tdir = FI.games[i]->gamefolder; // fixup directory case\n\n\t\t\tif( FI.games[i]->added ) // already added, refusing\n\t\t\t\tbreak;\n\n\t\t\t// don't add our game directory as base dir to prevent cyclic dependency\n\t\t\tif( Q_stricmp( FI.games[i]->basedir, GI->gamefolder ))\n\t\t\t\tbreak;\n\n\t\t\t// don't add directory which basedir is equal to this gamefolder to prevent\n\t\t\t// endless loop\n\t\t\tif( Q_stricmp( FI.games[i]->basedir, FI.games[i]->gamefolder ))\n\t\t\t\tbreak;\n\n\t\t\tCon_Reportf( \"%s: adding recursive basedir %s\\n\", __func__, FI.games[i]->basedir );\n\t\t\tFI.games[i]->added = true;\n\t\t\tFS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( COM_CheckStringEmpty( fs_rodir ))\n\t{\n\t\t// append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH\n\t\tuint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH));\n\t\tif( isGameDir )\n\t\t\tSetBits( new_flags, FS_GAMERODIR_PATH );\n\n\t\tFS_AllowDirectPaths( true );\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s/%s/\", fs_rodir, dir );\n\t\tFS_AddGameDirectory( buf, new_flags );\n\t\tFS_AllowDirectPaths( false );\n\t}\n\n\tif( isGameDir )\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s/\" DEFAULT_DOWNLOADED_DIRECTORY, dir );\n\t\tFS_AddGameDirectory( buf, FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n\tQ_snprintf( buf, sizeof( buf ), \"%s/\", dir );\n\tFS_AddGameDirectory( buf, flags );\n\n\tif( FBitSet( flags, FS_MOUNT_HD ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s_hd/\", dir );\n\t\tFS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n\n\tif( FBitSet( flags, FS_MOUNT_ADDON ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s_addon/\", dir );\n\t\tFS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n\n\tif( FBitSet( flags, FS_MOUNT_LV ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s_lv/\", dir );\n\t\tFS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n\n\tif( FBitSet( flags, FS_MOUNT_L10N ) && COM_CheckStringEmpty( fs_language ) && Q_isalpha( fs_language ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s_%s/\", dir, fs_language );\n\t\tFS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n\n\tif( isGameDir )\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s/\" DEFAULT_CUSTOM_DIRECTORY, dir );\n\t\tFS_AddGameDirectory( buf, FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\t}\n}\n\n/*\n================\nFS_Rescan\n================\n*/\nvoid FS_Rescan( uint32_t flags, const char *language )\n{\n\tconst char *str;\n\tCon_Reportf( \"%s( %s )\\n\", __func__, GI->title );\n\n\tFS_ClearSearchPath();\n\n\tflags &= FS_MOUNT_HD|FS_MOUNT_LV|FS_MOUNT_ADDON|FS_MOUNT_L10N;\n\n\tif( FBitSet( flags, FS_MOUNT_L10N ))\n\t\tQ_strncpy( fs_language, language, sizeof( fs_language ));\n\telse\n\t\tfs_language[0] = 0;\n\n\tstr = getenv( \"XASH3D_EXTRAS_PAK1\" );\n\tif( COM_CheckString( str ))\n\t\tFS_MountArchive_Fullpath( str, FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\n\tstr = getenv( \"XASH3D_EXTRAS_PAK2\" );\n\tif( COM_CheckString( str ))\n\t\tFS_MountArchive_Fullpath( str, FS_NOWRITE_PATH|FS_CUSTOM_PATH );\n\n\tif( Q_stricmp( GI->basedir, GI->gamefolder ))\n\t\tFS_AddGameHierarchy( GI->basedir, flags );\n\tif( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir ))\n\t\tFS_AddGameHierarchy( GI->falldir, flags );\n\n\tGI->added = true;\n\tFS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH | flags );\n}\n\n/*\n===============\nFS_Gamedir\n\nAllows engine to know game directory before gameinfo is initialized\n===============\n*/\nstatic const char *FS_Gamedir( void )\n{\n\tif( GI )\n\t\treturn GI->gamefolder;\n\n\treturn fs_gamedir;\n}\n\n/*\n================\nFS_LoadGameInfo\n\ncan be passed null arg\n================\n*/\nstatic void FS_LoadGameInfo( uint32_t flags, const char *language )\n{\n\tint\ti;\n\n\t// lock uplevel of gamedir for read\\write\n\tFS_AllowDirectPaths( false );\n\n\tCon_Reportf( \"%s( %s, 0x%x, %s )\\n\", __func__, fs_gamedir, flags, language );\n\n\t// clear any old paths\n\tFS_ClearSearchPath();\n\n\t// validate gamedir\n\tfor( i = 0; i < FI.numgames; i++ )\n\t{\n\t\tif( !Q_stricmp( FI.games[i]->gamefolder, fs_gamedir ))\n\t\t\tbreak;\n\t}\n\n\tif( i == FI.numgames )\n\t\tSys_Error( \"Couldn't find game directory '%s'\\n\", fs_gamedir );\n\n\tFI.GameInfo = FI.games[i];\n\n\tif( FI.GameInfo->rodir )\n\t{\n\t\t// ensure we have directory in rwdir\n\t\tchar buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s/%s/\", fs_rootdir, FI.GameInfo->gamefolder );\n\t\tFS_CreatePath( buf );\n\t}\n\n\tFS_Rescan( flags, language ); // create new filesystem\n}\n\n/*\n==================\nFS_CheckForCrypt\n\nreturn true if library is crypted\n==================\n*/\nstatic qboolean FS_CheckForCrypt( const char *dllname )\n{\n\tfile_t\t*f;\n\tint\tkey;\n\n\t// this encryption is specific to DLLs\n\tif( Q_stricmp( COM_FileExtension( dllname ), \"dll\" ))\n\t\treturn false;\n\n\tf = FS_Open( dllname, \"rb\", false );\n\tif( !f ) return false;\n\n\tFS_Seek( f, 64, SEEK_SET );\t// skip first 64 bytes\n\tFS_Read( f, &key, sizeof( key ));\n\tFS_Close( f );\n\n\treturn ( key == 0x12345678 ) ? true : false;\n}\n\nstatic int FS_StripIdiotRelativePath( const char *dllname, const char *gamefolder )\n{\n\tstring idiot_relpath;\n\tint len;\n\n\tif(( len = Q_snprintf( idiot_relpath, sizeof( idiot_relpath ), \"../%s/\", gamefolder )) >= 4 )\n\t{\n\t\tif( !Q_strnicmp( dllname, idiot_relpath, len ))\n\t\t\treturn len;\n\n\t\t// try backslashes\n\t\tidiot_relpath[1] = '\\\\';\n\t\tidiot_relpath[len - 1] = '\\\\';\n\t\tif( !Q_strnicmp( dllname, idiot_relpath, len ))\n\t\t\treturn len;\n\t}\n\n\treturn 0;\n}\n\n/*\n==================\nFS_FindLibrary\n\nsearch for library, assume index is valid\n==================\n*/\nstatic qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo )\n{\n\tstring fixedname;\n\tsearchpath_t\t*search;\n\tint index, start = 0, len;\n\n\t// check for bad exports\n\tif( !COM_CheckString( dllname ))\n\t\treturn false;\n\n\tFS_AllowDirectPaths( directpath );\n\n\t// HACKHACK remove relative path to game folder\n\tif( !Q_strnicmp( dllname, \"..\", 2 ))\n\t{\n\t\t// some modders put relative path to themselves???\n\t\tlen = FS_StripIdiotRelativePath( dllname, GI->gamefolder );\n\n\t\tif( len == 0 ) // or put relative path to Half-Life game libs\n\t\t\tlen = FS_StripIdiotRelativePath( dllname, \"valve\" );\n\t\tstart += len;\n\t}\n\n\tQ_strnlwr( &dllname[start], dllInfo->shortPath, sizeof( dllInfo->shortPath )); // always in lower case (why?)\n\tCOM_FixSlashes( dllInfo->shortPath ); // replace all backward slashes\n\tCOM_DefaultExtension( dllInfo->shortPath, \".\"OS_LIB_EXT, sizeof( dllInfo->shortPath ));\t// apply ext if forget\n\n\tsearch = FS_FindFile( dllInfo->shortPath, &index, fixedname, sizeof( fixedname ), false );\n\n\tif( search )\n\t{\n\t\tQ_strncpy( dllInfo->shortPath, fixedname, sizeof( dllInfo->shortPath ));\n\t}\n\telse if( !directpath )\n\t{\n\t\tFS_AllowDirectPaths( false );\n\n\t\t// trying check also 'bin' folder for indirect paths\n\t\tsearch = FS_FindFile( dllname, &index, fixedname, sizeof( fixedname ), false );\n\t\tif( !search )\n\t\t\treturn false; // unable to find\n\n\t\tQ_strncpy( dllInfo->shortPath, fixedname, sizeof( dllInfo->shortPath ));\n\t}\n\n\tdllInfo->encrypted = dllInfo->custom_loader = false; // predict state\n\n\tif( search && index >= 0 ) // when library is available through VFS\n\t{\n\t\tdllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath );\n\n\t\tif( search->type == SEARCHPATH_PLAIN ) // is it on the disk? (intentionally omit pk3dir here)\n\t\t{\n\t\t\t// NOTE: gamedll might resolve it's own path using dladdr() and expects absolute path\n\t\t\t// NOTE: the only allowed case when searchpath is set by absolute path is the RoDir\n\t\t\t// rather than figuring out whether path is absolute, just check if it matches\n\t\t\tif( COM_CheckStringEmpty( fs_rodir ) && !Q_strnicmp( search->filename, fs_rodir, Q_strlen( fs_rodir )))\n\t\t\t{\n\t\t\t\tQ_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), \"%s%s\", search->filename, dllInfo->shortPath );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tQ_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), \"%s/%s%s\", fs_rootdir, search->filename, dllInfo->shortPath );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), \"%s\", dllInfo->shortPath );\n#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it)\n\t\t\tCon_Printf( S_WARN \"%s: loading libraries from archives is non portable and might fail on other platforms\\n\", __func__ );\n\t\t\tdllInfo->custom_loader = true;\n#else\n\t\t\tCon_Printf( S_WARN \"%s: loading libraries from archives is unsupported on this platform\\n\", __func__ );\n#endif\n\t\t}\n\t}\n\telse\n\t{\n\t\t// NOTE: if search is NULL let OS to find the library\n\t\tQ_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath ));\n\t}\n\n\tFS_AllowDirectPaths( false ); // always reset direct paths\n\n\treturn true;\n}\n\nstatic poolhandle_t Mem_AllocPoolStub( const char *name, const char *filename, int fileline )\n{\n\treturn (poolhandle_t)0xDEADC0DE;\n}\n\nstatic void Mem_FreePoolStub( poolhandle_t *poolptr, const char *filename, int fileline )\n{\n\t// stub\n}\n\nstatic void *Mem_AllocStub( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\tvoid *ptr = malloc( size );\n\tif( clear ) memset( ptr, 0, size );\n\treturn ptr;\n}\n\nstatic void *Mem_ReallocStub( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\treturn realloc( memptr, size );\n}\n\nstatic void Mem_FreeStub( void *data, const char *filename, int fileline )\n{\n\tfree( data );\n}\n\nstatic void Con_PrintfStub( const char *fmt, ... )\n{\n\tva_list ap;\n\n\tva_start( ap, fmt );\n\tvprintf( fmt, ap );\n\tva_end( ap );\n}\n\nstatic void Sys_ErrorStub( const char *fmt, ... )\n{\n\tva_list ap;\n\n\tva_start( ap, fmt );\n\tvfprintf( stderr, fmt, ap );\n\tva_end( ap );\n\n\texit( 1 );\n}\n\nstatic void *Sys_GetNativeObjectStub( const char *object )\n{\n\treturn NULL;\n}\n\nstatic void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir )\n{\n\tstringlist_t dirs;\n\tint i;\n\n\tstringlistinit( &dirs );\n\tlistdirectory( &dirs, path, true );\n\tstringlistsort( &dirs );\n\n\tfor( i = 0; i < dirs.numstrings; i++ )\n\t{\n\t\tif( !FS_SysFolderExists( dirs.strings[i] ))\n\t\t\tcontinue;\n\n\t\tif( !Q_stricmp( fs_basedir, dirs.strings[i] ))\n\t\t\t*has_base_dir = true;\n\n\t\tif( !Q_stricmp( fs_gamedir, dirs.strings[i] ))\n\t\t\t*has_game_dir = true;\n\n\t\tif( *has_base_dir && *has_game_dir )\n\t\t\tbreak;\n\t}\n\n\tstringlistfreecontents( &dirs );\n}\n\n/*\n================\nFS_Init\n================\n*/\nqboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir )\n{\n\tstringlist_t dirs;\n\tint i, rodir_num_games;\n\tchar buf[MAX_SYSPATH];\n\n\tFS_InitMemory();\n\n#if XASH_ANDROID\n\tFS_InitAndroid();\n#endif\n\n\tQ_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir ));\n\tQ_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir ));\n\tQ_strncpy( fs_basedir, basedir, sizeof( fs_basedir ));\n\tQ_strncpy( fs_rodir, rodir, sizeof( fs_rodir ));\n\tfs_language[0] = 0;\n\n\t// validate user input\n\tif( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir ))\n\t{\n\t\tSys_Error( \"RoDir and default rootdir can't point to same directory!\" );\n\t\treturn false;\n\t}\n\n\t// look for game directories in RwDir first\n\t// this whole check only required to fallback to base directory\n\t// (i.e. default game directory hardcoded in the executable file)\n\t{\n\t\tqboolean has_base_dir = false;\n\t\tqboolean has_game_dir = false;\n\n\t\tFS_ValidateDirectories( \"./\", &has_base_dir, &has_game_dir );\n\n\t\tif( !has_game_dir )\n\t\t{\n\t\t\t// look for game directories in RoDir now\n\t\t\tif( COM_CheckStringEmpty( fs_rodir ))\n\t\t\t\tFS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir );\n\n\t\t\tif( !has_game_dir )\n\t\t\t{\n\t\t\t\tCon_Printf( S_ERROR \"game directory \\\"%s\\\" not exist\\n\", fs_gamedir );\n\n\t\t\t\t// revert to base game directory\n\t\t\t\tif( has_base_dir )\n\t\t\t\t\tQ_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));\n\t\t\t}\n\t\t}\n\t}\n\n\t// now start building first level of directory hierarchy\n\tif( COM_CheckStringEmpty( fs_rodir ))\n\t{\n\t\tQ_snprintf( buf, sizeof( buf ), \"%s/\", fs_rodir );\n\t\tFS_AddGameDirectory( buf, FS_STATIC_PATH|FS_NOWRITE_PATH );\n\t}\n\tFS_AddGameDirectory( \"./\", FS_STATIC_PATH );\n\n\t// but scan rodir for games first\n\tif( COM_CheckStringEmpty( fs_rodir ))\n\t{\n\t\tstringlistinit( &dirs );\n\t\tlistdirectory( &dirs, fs_rodir, true );\n\t\tstringlistsort( &dirs );\n\n\t\tfor( i = 0; i < dirs.numstrings; i++ )\n\t\t{\n\t\t\tQ_snprintf( buf, sizeof( buf ), \"%s/%s\", fs_rodir, dirs.strings[i] );\n\t\t\tif( !FS_SysFolderExists( buf ))\n\t\t\t\tcontinue;\n\n\t\t\tif( FI.games[FI.numgames] == NULL )\n\t\t\t\tFI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));\n\n\t\t\tif( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true ))\n\t\t\t\tFI.numgames++;\n\t\t}\n\n\t\tstringlistfreecontents( &dirs );\n\t}\n\n\trodir_num_games = FI.numgames;\n\n\tstringlistinit( &dirs );\n\tlistdirectory( &dirs, \"./\", true );\n\tstringlistsort( &dirs );\n\n\tfor( i = 0; i < dirs.numstrings; i++ )\n\t{\n\t\tint j;\n\n\t\tif( !FS_SysFolderExists( dirs.strings[i] ))\n\t\t\tcontinue;\n\n\t\t// update gameinfo from rwdir, if it's newer\n\t\t// with rodir we should never create new gameinfos anymore,\n\t\t// so this only catches possible user changes\n\t\tfor( j = 0; j < rodir_num_games; j++ )\n\t\t{\n\t\t\tif( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder ))\n\t\t\t{\n\t\t\t\tgameinfo_t gi;\n\t\t\t\tif( FS_ParseGameInfo( dirs.strings[i], &gi, false ))\n\t\t\t\t{\n\t\t\t\t\tif( gi.mtime > FI.games[j]->mtime )\n\t\t\t\t\t\t*FI.games[j] = gi;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( FI.games[FI.numgames] == NULL )\n\t\t\tFI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));\n\n\t\tif( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false ))\n\t\t\tFI.numgames++; // added\n\t}\n\n\tstringlistfreecontents( &dirs );\n\n\tCon_Reportf( \"%s: done\\n\", __func__ );\n\n\treturn true;\n}\n\nvoid FS_AllowDirectPaths( qboolean enable )\n{\n\tfs_ext_path = enable;\n}\n\n/*\n================\nFS_Shutdown\n================\n*/\nvoid FS_ShutdownStdio( void )\n{\n\tint i;\n\n\t// release gamedirs\n\tfor( i = 0; i < FI.numgames; i++ )\n\t{\n\t\tif( FI.games[i] )\n\t\t{\n\t\t\tMem_Free( FI.games[i] );\n\t\t\tFI.games[i] = NULL;\n\t\t}\n\t}\n\tFI.numgames = 0;\n\n\tFS_ClearSearchPath(); // release all wad files too\n\tMem_FreePool( &fs_mempool );\n}\n\n/*\n============\nFS_Path_f\n\ndebug info\n============\n*/\nvoid FS_Path_f( void )\n{\n\tsearchpath_t\t*s;\n\n\tCon_Printf( \"Current search path:\\n\" );\n\n\tfor( s = fs_searchpaths; s; s = s->next )\n\t{\n\t\tstring info;\n\n\t\ts->pfnPrintInfo( s, info, sizeof(info) );\n\n\t\tCon_Printf( \"%s\", info );\n\n\t\tif( s->flags & FS_GAMERODIR_PATH ) Con_Printf( \" ^2rodir^7\" );\n\t\tif( s->flags & FS_GAMEDIR_PATH ) Con_Printf( \" ^2gamedir^7\" );\n\t\tif( s->flags & FS_CUSTOM_PATH ) Con_Printf( \" ^2custom^7\" );\n\t\tif( s->flags & FS_NOWRITE_PATH ) Con_Printf( \" ^2nowrite^7\" );\n\t\tif( s->flags & FS_STATIC_PATH ) Con_Printf( \" ^2static^7\" );\n\n\t\tCon_Printf( \"\\n\" );\n\t}\n}\n\n/*\n====================\nFS_SysFileTime\n\nInternal function used to determine filetime\n====================\n*/\nint FS_SysFileTime( const char *filename )\n{\n#if XASH_WIN32\n\tstruct _stat buf;\n\tif( _wstat( FS_PathToWideChar( filename ), &buf ) < 0 )\n#else\n\tstruct stat buf;\n\tif( stat( filename, &buf ) < 0 )\n#endif\n\t\treturn -1;\n\n\treturn buf.st_mtime;\n}\n\n/*\n====================\nFS_SysOpen\n\nInternal function used to create a file_t and open the relevant non-packed file on disk\n====================\n*/\nfile_t *FS_SysOpen( const char *filepath, const char *mode )\n{\n\tfile_t *file;\n\tint mod, opt, fd = -1;\n\tqboolean memfile = false;\n\tuint ind;\n\n\t// Parse the mode string\n\tswitch( mode[0] )\n\t{\n\tcase 'r':\t// read\n\t\tmod = O_RDONLY;\n\t\topt = 0;\n\t\tbreak;\n\tcase 'w': // write\n\t\tmod = O_WRONLY;\n\t\topt = O_CREAT | O_TRUNC;\n\t\tbreak;\n\tcase 'a': // append\n\t\tmod = O_WRONLY;\n\t\topt = O_CREAT | O_APPEND;\n\t\tbreak;\n\tcase 'e': // edit\n\t\tmod = O_WRONLY;\n\t\topt = O_CREAT;\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\n\tfor( ind = 1; mode[ind] != '\\0'; ind++ )\n\t{\n\t\tswitch( mode[ind] )\n\t\t{\n\t\tcase '+':\n\t\t\tmod = O_RDWR;\n\t\t\tbreak;\n\t\tcase 'b':\n\t\t\topt |= O_BINARY;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// the 'm' flag let's user to create temporary file in memory\n\t// through so-called \"anonymous files\"\n\tif( Q_strchr( mode, 'm' ))\n\t{\n#if HAVE_MEMFD_CREATE\n#ifndef MFD_NOEXEC_SEAL\n#define MFD_NOEXEC_SEAL 8U\n#endif\n\t\tfd = memfd_create( filepath, MFD_CLOEXEC | MFD_NOEXEC_SEAL );\n\n\t\t// through fcntl() and MFD_ALLOW_SEALING we could enforce\n\t\t// read-write flags but we don't really care about them yet\n\t\tif( fd < 0 )\n\t\t\tCon_Printf( S_ERROR \"%s: can't create anonymous file %s: %s\", __func__, filepath, strerror( errno ));\n\t\telse memfile = true;\n#endif\n\t\t// if it's unsupported, we can open it on disk\n\t}\n\n\tif( fd < 0 )\n\t{\n#if XASH_WIN32\n\t\tfd = _wopen( FS_PathToWideChar( filepath ), mod | opt, 0666 );\n#else\n\t\tfd = open( filepath, mod|opt, 0666 );\n#endif\n\t}\n\n\tif( fd < 0 )\n\t{\n\t\tif( errno != ENOENT )\n\t\t\tCon_Printf( S_ERROR \"%s: can't open file %s: %s\\n\", __func__, filepath, strerror( errno ));\n\n\t\treturn NULL;\n\t}\n\n\tfile = (file_t *)Mem_Calloc( fs_mempool, sizeof( *file ));\n\tfile->filetime = memfile ? 0 : FS_SysFileTime( filepath );\n\tfile->ungetc = EOF;\n\tfile->handle = fd;\n\n#if !XASH_WIN32\n\tif( !memfile )\n\t\tFS_BackupFileName( file, filepath, mod|opt );\n#endif\n\n\tfile->searchpath = NULL;\n\tfile->real_length = lseek( file->handle, 0, SEEK_END );\n\n\t// uncomment do disable write\n\t//if( opt & O_CREAT )\n\t//\treturn NULL;\n\n\t// For files opened in append mode, we start at the end of the file\n\tif( opt & O_APPEND )  file->position = file->real_length;\n\telse lseek( file->handle, 0, SEEK_SET );\n\n\treturn file;\n}\n/*\nstatic int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos )\n{\n#ifdef HAVE_DUP\n\treturn dup( handle );\n#else\n\tint newhandle = open( filename, O_RDONLY|O_BINARY );\n\tlseek( newhandle, pos, SEEK_SET );\n\treturn newhandle;\n#endif\n}\n*/\n\nfile_t *FS_OpenHandle( searchpath_t *searchpath, int handle, fs_offset_t offset, fs_offset_t len )\n{\n\tfile_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t ));\n#ifndef XASH_REDUCE_FD\n#ifdef HAVE_DUP\n\tfile->handle = dup( handle );\n#else\n\tfile->handle = open( searchpath->filename, O_RDONLY|O_BINARY );\n#endif\n\n\tif( file->handle < 0 )\n\t{\n\t\tCon_Printf( S_ERROR \"%s: couldn't create fd for %s:0x%lx: %s\\n\", __func__, searchpath->filename, (long)offset, strerror( errno ));\n\t\tMem_Free( file );\n\t\treturn NULL;\n\t}\n\n\tif( lseek( file->handle, offset, SEEK_SET ) == -1 )\n\t{\n\t\tMem_Free( file );\n\t\treturn NULL;\n\t}\n\n#else\n\tfile->backup_position = offset;\n\tfile->backup_path = copystring( syspath );\n\tfile->backup_options = O_RDONLY|O_BINARY;\n\tfile->handle = -1;\n#endif\n\n\tfile->real_length = len;\n\tfile->offset = offset;\n\tfile->position = 0;\n\tfile->ungetc = EOF;\n\tfile->searchpath = searchpath;\n\n\treturn file;\n}\n\n#if !defined( S_ISREG )\n#define S_ISREG( m ) ( FBitSet( m, S_IFMT ) == S_IFREG )\n#endif\n\n#if !defined( S_ISDIR )\n#define S_ISDIR( m ) ( FBitSet( m, S_IFMT ) == S_IFDIR )\n#endif\n\n/*\n==================\nFS_SysFileExists\n\nLook for a file in the filesystem only\n==================\n*/\nqboolean FS_SysFileExists( const char *path )\n{\n#if XASH_WIN32\n\tstruct _stat buf;\n\tif( _wstat( FS_PathToWideChar( path ), &buf ) < 0 )\n#else\n\tstruct stat buf;\n\tif( stat( path, &buf ) < 0 )\n#endif\n\t\treturn false;\n\n\treturn S_ISREG( buf.st_mode );\n}\n\n/*\n==================\nFS_SysFolderExists\n\nLook for a existing folder\n==================\n*/\nqboolean FS_SysFolderExists( const char *path )\n{\n#if XASH_WIN32\n\tstruct _stat buf;\n\tif( _wstat( FS_PathToWideChar( path ), &buf ) < 0 )\n#else\n\tstruct stat buf;\n\tif( stat( path, &buf ) < 0 )\n#endif\n\t\treturn false;\n\n\treturn S_ISDIR( buf.st_mode );\n}\n\n/*\n==============\nFS_SysFileOrFolderExists\n\nCheck if filesystem entry exists at all, don't mind the type\n==============\n*/\nqboolean FS_SysFileOrFolderExists( const char *path )\n{\n#if XASH_WIN32\n\tstruct _stat buf;\n\treturn _wstat( FS_PathToWideChar( path ), &buf ) >= 0;\n#else\n\tstruct stat buf;\n\treturn stat( path, &buf ) >= 0;\n#endif\n}\n\n/*\n==================\nFS_SetCurrentDirectory\n\nSets current directory, path should be in UTF-8 encoding\nTODO: make this non-fatal\n==================\n*/\nint FS_SetCurrentDirectory( const char *path )\n{\n#if XASH_WIN32\n\tif( !SetCurrentDirectoryW( FS_PathToWideChar( path )))\n\t{\n\t\tconst DWORD fm_flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK;\n\t\tDWORD errorcode;\n\t\twchar_t wide_buf[1024];\n\t\tchar buf[1024];\n\n\t\tFormatMessageW( fm_flags, NULL, GetLastError(), 0, wide_buf, sizeof( wide_buf ) / sizeof( wide_buf[0] ), NULL );\n\t\tQ_UTF16ToUTF8( buf, sizeof( buf ), wide_buf, sizeof( wide_buf ) / sizeof( wide_buf[0] ));\n\n\t\tSys_Error( \"Changing directory to %s failed: %s\\n\", path, buf );\n\t\treturn false;\n\t}\n#elif XASH_POSIX\n\tif( chdir( path ) < 0 )\n\t{\n\t\tSys_Error( \"Changing directory to %s failed: %s\\n\", path, strerror( errno ));\n\t\treturn false;\n\t}\n#else\n\t// it may be fine for some systems to skip chdir\n\tCon_Printf( \"%s: not implemented, ignoring...\\n\", __func__ );\n\treturn true;\n#endif\n\n\tCon_Printf( \"%s is working directory now\\n\", path );\n\treturn true;\n}\n\n/*\n==================\nFS_GetRootDirectory\n\nReturns writable root directory path\n==================\n*/\nqboolean FS_GetRootDirectory( char *path, size_t size )\n{\n\tsize_t dirlen = Q_strlen( fs_rootdir );\n\n\tif( dirlen >= size ) // check for possible overflow\n\t\treturn false;\n\n\tQ_strncpy( path, fs_rootdir, size );\n\treturn true;\n}\n\n/*\n====================\nFS_FindFile\n\nLook for a file in the packages and in the filesystem\n\nReturn the searchpath where the file was found (or NULL)\nand the file index in the package if relevant\n====================\n*/\nsearchpath_t *FS_FindFile( const char *name, int *index, char *fixedname, size_t len, qboolean gamedironly )\n{\n\tsearchpath_t\t*search;\n\n\t// search through the path, one element at a time\n\tfor( search = fs_searchpaths; search; search = search->next )\n\t{\n\t\tint pack_ind;\n\n\t\tif( gamedironly & !FBitSet( search->flags, FS_GAMEDIRONLY_SEARCH_FLAGS ))\n\t\t\tcontinue;\n\n\t\tpack_ind = search->pfnFindFile( search, name, fixedname, len );\n\t\tif( pack_ind >= 0 )\n\t\t{\n\t\t\tif( index )\n\t\t\t\t*index = pack_ind;\n\t\t\treturn search;\n\t\t}\n\t}\n\n\tif( fs_ext_path )\n\t{\n\t\tchar netpath[MAX_SYSPATH], dirpath[MAX_SYSPATH];\n\n\t\t// HACKHACK: when the code wants to access to root game directory\n\t\t// it often uses ../ in conjunction with FS_AllowDirectPath\n\t\t// it results in the access above the root game directory\n\t\t// FS_Open with \"write\" flag doesn't have this problem because\n\t\t// it looks up relative to fs_writepath\n\t\t// the correct solution MIGHT be using fs_writepath instead of fs_rootdir here?\n\t\t// but this need to be properly tested so as a temporary solution\n\t\t// just strip ../\n\t\tif( !Q_strncmp( name, \"../\", 3 ))\n\t\t\tname += 3;\n\n\t\tQ_snprintf( dirpath, sizeof( dirpath ), \"%s/\", fs_rootdir );\n\t\tQ_snprintf( netpath, sizeof( netpath ), \"%s%s\", dirpath, name );\n\n\t\tif( FS_SysFileExists( netpath ))\n\t\t{\n\t\t\tstatic searchpath_t fs_directpath;\n\n\t\t\t// clear old dir cache, if needed\n\t\t\tif( 0 != Q_strcmp( fs_directpath.filename, dirpath ))\n\t\t\t{\n\t\t\t\tif( fs_directpath.pfnClose )\n\t\t\t\t\tfs_directpath.pfnClose( &fs_directpath );\n\t\t\t\tFS_InitDirectorySearchpath( &fs_directpath, dirpath, 0 );\n\t\t\t}\n\n\t\t\t// just copy the name, we don't do case sensitivity fix there\n\t\t\tif( fixedname )\n\t\t\t\tQ_strncpy( fixedname, name, len );\n\n\t\t\tif( index != NULL )\n\t\t\t\t*index = 0;\n\n\t\t\treturn &fs_directpath;\n\t\t}\n\t}\n\n\tif( index != NULL )\n\t\t*index = -1;\n\n\treturn NULL;\n}\n\n/*\n===========================\nFS_FullPathToRelativePath\n\nConverts full path to the relative path considering current searchpaths\n(do not use this function, implemented only for VFileSystem009)\n===========================\n*/\nqboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size )\n{\n\tsearchpath_t *sp;\n\n\tfor( sp = fs_searchpaths; sp; sp = sp->next )\n\t{\n\t\tsize_t splen = Q_strlen( sp->filename );\n\n\t\tif( !Q_strnicmp( sp->filename, src, splen ))\n\t\t{\n\t\t\tQ_strncpy( dst, src + splen + 1, size );\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tQ_strncpy( dst, src, size );\n\treturn false;\n}\n\n\n/*\n===========\nFS_OpenReadFile\n\nLook for a file in the search paths and open it in read-only mode\n===========\n*/\nfile_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly )\n{\n\tsearchpath_t *search;\n\tchar netpath[MAX_SYSPATH];\n\tint pack_ind;\n\n\tsearch = FS_FindFile( filename, &pack_ind, netpath, sizeof( netpath ), gamedironly );\n\n\t// not found?\n\tif( search == NULL )\n\t\treturn NULL;\n\n\treturn search->pfnOpenFile( search, netpath, mode, pack_ind );\n}\n\n/*\n=============================================================================\n\nMAIN PUBLIC FUNCTIONS\n\n=============================================================================\n*/\n/*\n====================\nFS_Open\n\nOpen a file. The syntax is the same as fopen\n====================\n*/\nfile_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly )\n{\n\tif( !fs_searchpaths )\n\t\treturn NULL;\n\n\t// some mappers used leading '/' or '\\' in path to models or sounds\n\tif( filepath[0] == '/' || filepath[0] == '\\\\' )\n\t\tfilepath++;\n\n\tif( filepath[0] == '/' || filepath[0] == '\\\\' )\n\t\tfilepath++;\n\n\tif( FS_CheckNastyPath( filepath ))\n\t\treturn NULL;\n\n\t// if the file is opened in \"write\", \"append\", or \"read/write\" mode\n\tif( mode[0] == 'w' || mode[0] == 'a'|| mode[0] == 'e' || Q_strchr( mode, '+' ))\n\t{\n\t\tchar\treal_path[MAX_SYSPATH];\n\n\t\t// open the file on disk directly\n\t\tif( !FS_FixFileCase( fs_writepath->dir, filepath, real_path, sizeof( real_path ), true ))\n\t\t\treturn NULL;\n\n\t\tFS_CreatePath( real_path ); // Create directories up to the file\n\n\t\treturn FS_SysOpen( real_path, mode );\n\t}\n\n\t// else, we look at the various search paths and open the file in read-only mode\n\treturn FS_OpenReadFile( filepath, mode, gamedironly );\n}\n\n/*\n====================\nFS_Close\n\nClose a file\n====================\n*/\nint FS_Close( file_t *file )\n{\n\tif( !file ) return 0;\n\n\tFS_BackupFileName( file, NULL, 0 );\n\n\tif( file->handle >= 0 )\n\t{\n\t\tif( close( file->handle ))\n\t\t\treturn EOF;\n\t}\n\n\tif( file->ztk )\n\t{\n\t\tinflateEnd( &file->ztk->zstream );\n\t\tMem_Free( file->ztk );\n\t}\n\n\tMem_Free( file );\n\treturn 0;\n}\n\n/*\n====================\nFS_Flush\n\nflushes written data to disk\n====================\n*/\nint FS_Flush( file_t *file )\n{\n\tif( !file ) return 0;\n\n\t// purge cached data\n\tFS_Purge( file );\n\n\t// sync\n#if XASH_POSIX\n\tif( fsync( file->handle ) < 0 )\n\t\treturn EOF;\n#else\n\tif( _commit( file->handle ) < 0 )\n\t\treturn EOF;\n#endif\n\n\treturn 0;\n}\n\n/*\n====================\nFS_Write\n\nWrite \"datasize\" bytes into a file\n====================\n*/\nfs_offset_t FS_Write( file_t *file, const void *data, size_t datasize )\n{\n\tfs_offset_t\tresult;\n\n\tif( !file ) return 0;\n\n\t// if necessary, seek to the exact file position we're supposed to be\n\tif( file->buff_ind != file->buff_len )\n\t\tlseek( file->handle, file->buff_ind - file->buff_len, SEEK_CUR );\n\n\t// purge cached data\n\tFS_Purge( file );\n\n\t// write the buffer and update the position\n\tresult = write( file->handle, data, datasize );\n\tfile->position = lseek( file->handle, 0, SEEK_CUR );\n\n\tif( file->real_length < file->position )\n\t\tfile->real_length = file->position;\n\n\tif( result < 0 )\n\t\treturn 0;\n\treturn result;\n}\n\n/*\n====================\nFS_Read\n\nRead up to \"buffersize\" bytes from a file\n====================\n*/\nfs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize )\n{\n\tfs_offset_t\tdone;\n\tfs_offset_t\tnb;\n\tfs_offset_t\tcount;\n\n\t// nothing to copy\n\tif( buffersize == 0 ) return 1;\n\n\t// Get rid of the ungetc character\n\tif( file->ungetc != EOF )\n\t{\n\t\t((char*)buffer)[0] = file->ungetc;\n\t\tbuffersize--;\n\t\tfile->ungetc = EOF;\n\t\tdone = 1;\n\t}\n\telse done = 0;\n\n\t// first, we copy as many bytes as we can from \"buff\"\n\tif( file->buff_ind < file->buff_len )\n\t{\n\t\tcount = file->buff_len - file->buff_ind;\n\t\tcount = ( buffersize > count ) ? count : (fs_offset_t)buffersize;\n\n\t\tdone += count;\n\t\tmemcpy( buffer, &file->buff[file->buff_ind], count );\n\t\tfile->buff_ind += count;\n\n\t\tbuffersize -= count;\n\t\tif( buffersize == 0 )\n\t\t\treturn done;\n\t}\n\n\t// NOTE: at this point, the read buffer is always empty\n\n\tFS_EnsureOpenFile( file ); // FIXME: broken XASH_REDUCE_FD in case of compressed files!\n\n\tif( FBitSet( file->flags, FILE_DEFLATED ))\n\t{\n\t\t// If the file is compressed, it's more complicated...\n\t\t// We cycle through a few operations until we have read enough data\n\t\twhile( buffersize > 0 )\n\t\t{\n\t\t\tztoolkit_t *ztk = file->ztk;\n\t\t\tint error;\n\n\t\t\t// NOTE: at this point, the read buffer is always empty\n\n\t\t\t// If \"input\" is also empty, we need to refill it\n\t\t\tif( ztk->in_ind == ztk->in_len )\n\t\t\t{\n\t\t\t\t// If we are at the end of the file\n\t\t\t\tif( file->position == file->real_length )\n\t\t\t\t\treturn done;\n\n\t\t\t\tcount = (fs_offset_t)( ztk->comp_length - ztk->in_position );\n\t\t\t\tif( count > (fs_offset_t)sizeof( ztk->input ))\n\t\t\t\t\tcount = (fs_offset_t)sizeof( ztk->input );\n\t\t\t\tlseek( file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET );\n\t\t\t\tif( read( file->handle, ztk->input, count ) != count )\n\t\t\t\t{\n\t\t\t\t\tCon_Printf( \"%s: unexpected end of file\\n\", __func__ );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tztk->in_ind = 0;\n\t\t\t\tztk->in_len = count;\n\t\t\t\tztk->in_position += count;\n\t\t\t}\n\n\t\t\tztk->zstream.next_in = &ztk->input[ztk->in_ind];\n\t\t\tztk->zstream.avail_in = (unsigned int)( ztk->in_len - ztk->in_ind );\n\n\t\t\t// Now that we are sure we have compressed data available, we need to determine\n\t\t\t// if it's better to inflate it in \"file->buff\" or directly in \"buffer\"\n\n\t\t\t// Inflate the data in \"file->buff\"\n\t\t\tif( buffersize < sizeof( file->buff ) / 2 )\n\t\t\t{\n\t\t\t\tztk->zstream.next_out = file->buff;\n\t\t\t\tztk->zstream.avail_out = sizeof( file->buff );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tztk->zstream.next_out = &((unsigned char*)buffer)[done];\n\t\t\t\tztk->zstream.avail_out = (unsigned int)buffersize;\n\t\t\t}\n\n\t\t\terror = inflate( &ztk->zstream, Z_SYNC_FLUSH );\n\t\t\tif( error != Z_OK && error != Z_STREAM_END )\n\t\t\t{\n\t\t\t\tCon_Printf( \"%s: Can't inflate file (%d)\\n\", __func__, error );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tztk->in_ind = ztk->in_len - ztk->zstream.avail_in;\n\n\t\t\tif( buffersize < sizeof( file->buff ) / 2 )\n\t\t\t{\n\t\t\t\tfile->buff_len = (fs_offset_t)sizeof( file->buff ) - ztk->zstream.avail_out;\n\t\t\t\tfile->position += file->buff_len;\n\n\t\t\t\t// Copy the requested data in \"buffer\" (as much as we can)\n\t\t\t\tcount = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;\n\t\t\t\tmemcpy( &((unsigned char*)buffer)[done], file->buff, count );\n\t\t\t\tfile->buff_ind = count;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcount = (fs_offset_t)( buffersize - ztk->zstream.avail_out );\n\t\t\t\tfile->position += count;\n\n\t\t\t\t// Purge cached data\n\t\t\t\tFS_Purge( file );\n\t\t\t}\n\n\t\t\tdone += count;\n\t\t\tbuffersize -= count;\n\t\t}\n\n\t\treturn done;\n\t}\n\n\t// we must take care to not read after the end of the file\n\tcount = file->real_length - file->position;\n\n\t// if we have a lot of data to get, put them directly into \"buffer\"\n\tif( buffersize > sizeof( file->buff ) / 2 )\n\t{\n\t\tif( count > (fs_offset_t)buffersize )\n\t\t\tcount = (fs_offset_t)buffersize;\n\t\tlseek( file->handle, file->offset + file->position, SEEK_SET );\n\t\tnb = read( file->handle, &((byte *)buffer)[done], count );\n\n\t\tif( nb > 0 )\n\t\t{\n\t\t\tdone += nb;\n\t\t\tfile->position += nb;\n\t\t\t// purge cached data\n\t\t\tFS_Purge( file );\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( count > (fs_offset_t)sizeof( file->buff ))\n\t\t\tcount = (fs_offset_t)sizeof( file->buff );\n\t\tlseek( file->handle, file->offset + file->position, SEEK_SET );\n\t\tnb = read( file->handle, file->buff, count );\n\n\t\tif( nb > 0 )\n\t\t{\n\t\t\tfile->buff_len = nb;\n\t\t\tfile->position += nb;\n\n\t\t\t// copy the requested data in \"buffer\" (as much as we can)\n\t\t\tcount = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;\n\t\t\tmemcpy( &((byte *)buffer)[done], file->buff, count );\n\t\t\tfile->buff_ind = count;\n\t\t\tdone += count;\n\t\t}\n\t}\n\n\treturn done;\n}\n\n/*\n====================\nFS_Print\n\nPrint a string into a file\n====================\n*/\nint FS_Print( file_t *file, const char *msg )\n{\n\treturn FS_Write( file, msg, Q_strlen( msg ));\n}\n\n/*\n====================\nFS_Printf\n\nPrint a string into a file\n====================\n*/\nint FS_Printf( file_t *file, const char *format, ... )\n{\n\tint\tresult;\n\tva_list\targs;\n\n\tva_start( args, format );\n\tresult = FS_VPrintf( file, format, args );\n\tva_end( args );\n\n\treturn result;\n}\n\n/*\n====================\nFS_VPrintf\n\nPrint a string into a file\n====================\n*/\nint FS_VPrintf( file_t *file, const char *format, va_list ap )\n{\n\tint\tlen;\n\tfs_offset_t\tbuff_size = MAX_SYSPATH;\n\tchar\t*tempbuff;\n\n\tif( !file ) return 0;\n\n\twhile( 1 )\n\t{\n\t\ttempbuff = (char *)Mem_Malloc( fs_mempool, buff_size );\n\t\tlen = Q_vsnprintf( tempbuff, buff_size, format, ap );\n\n\t\tif( len >= 0 && len < buff_size )\n\t\t\tbreak;\n\n\t\tMem_Free( tempbuff );\n\t\tbuff_size *= 2;\n\t}\n\n\tlen = write( file->handle, tempbuff, len );\n\tMem_Free( tempbuff );\n\n\treturn len;\n}\n\n/*\n====================\nFS_Getc\n\nGet the next character of a file\n====================\n*/\nint FS_Getc( file_t *file )\n{\n\tchar\tc;\n\n\tif( FS_Read( file, &c, 1 ) != 1 )\n\t\treturn EOF;\n\n\treturn c;\n}\n\n/*\n====================\nFS_UnGetc\n\nPut a character back into the read buffer (only supports one character!)\n====================\n*/\nint FS_UnGetc( file_t *file, char c )\n{\n\t// If there's already a character waiting to be read\n\tif( file->ungetc != EOF )\n\t\treturn EOF;\n\n\tfile->ungetc = c;\n\treturn c;\n}\n\n/*\n====================\nFS_Gets\n\nSame as fgets\n====================\n*/\nint FS_Gets( file_t *file, char *string, size_t bufsize )\n{\n\tint\tc, end = 0;\n\n\twhile( 1 )\n\t{\n\t\tc = FS_Getc( file );\n\n\t\tif( c == '\\r' || c == '\\n' || c < 0 )\n\t\t\tbreak;\n\n\t\tif( end < bufsize - 1 )\n\t\t\tstring[end++] = c;\n\t}\n\tstring[end] = 0;\n\n\t// remove \\n following \\r\n\tif( c == '\\r' )\n\t{\n\t\tc = FS_Getc( file );\n\n\t\tif( c != '\\n' )\n\t\t\tFS_UnGetc( file, c );\n\t}\n\n\treturn c;\n}\n\n/*\n====================\nFS_Seek\n\nMove the position index in a file\nNOTE: when porting code, check return value!\nNOTE: it's not compatible with lseek!\n====================\n*/\nint FS_Seek( file_t *file, fs_offset_t offset, int whence )\n{\n\t// compute the file offset\n\tswitch( whence )\n\t{\n\tcase SEEK_CUR:\n\t\toffset += file->position - file->buff_len + file->buff_ind;\n\t\tbreak;\n\tcase SEEK_SET:\n\t\tbreak;\n\tcase SEEK_END:\n\t\toffset += file->real_length;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tif( offset < 0 || offset > file->real_length )\n\t\treturn -1;\n\n\t// if we have the data in our read buffer, we don't need to actually seek\n\tif( file->position - file->buff_len <= offset && offset <= file->position )\n\t{\n\t\tfile->buff_ind = offset + file->buff_len - file->position;\n\t\treturn 0;\n\t}\n\n\tFS_EnsureOpenFile( file );\n\t// Purge cached data\n\tFS_Purge( file );\n\n\tif( FBitSet( file->flags, FILE_DEFLATED ))\n\t{\n\t\t// Seeking in compressed files is more a hack than anything else,\n\t\t// but we need to support it, so here we go.\n\t\tztoolkit_t *ztk = file->ztk;\n\t\tunsigned char *buffer;\n\t\tfs_offset_t buffersize;\n\n\t\t// If we have to go back in the file, we need to restart from the beginning\n\t\tif( offset <= file->position )\n\t\t{\n\t\t\tztk->in_ind = 0;\n\t\t\tztk->in_len = 0;\n\t\t\tztk->in_position = 0;\n\t\t\tfile->position = 0;\n\t\t\tif( lseek( file->handle, file->offset, SEEK_SET ) == -1 )\n\t\t\t\tCon_Printf(\"IMPOSSIBLE: couldn't seek in already opened pk3 file.\\n\");\n\n\t\t\t// Reset the Zlib stream\n\t\t\tztk->zstream.next_in = ztk->input;\n\t\t\tztk->zstream.avail_in = 0;\n\t\t\tinflateReset( &ztk->zstream );\n\t\t}\n\n\t\t// We need a big buffer to force inflating into it directly\n\t\tbuffersize = 2 * sizeof( file->buff );\n\t\tbuffer = (unsigned char *)Mem_Malloc( fs_mempool, buffersize );\n\n\t\t// Skip all data until we reach the requested offset\n\t\twhile( offset > ( file->position - file->buff_len + file->buff_ind ))\n\t\t{\n\t\t\tfs_offset_t diff = offset - ( file->position - file->buff_len + file->buff_ind );\n\t\t\tfs_offset_t count;\n\n\t\t\tcount = ( diff > buffersize ) ? buffersize : diff;\n\t\t\tif( FS_Read( file, buffer, count ) != count )\n\t\t\t{\n\t\t\t\tMem_Free( buffer );\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\tMem_Free( buffer );\n\t\treturn 0;\n\t}\n\n\tif( lseek( file->handle, file->offset + offset, SEEK_SET ) == -1 )\n\t\treturn -1;\n\tfile->position = offset;\n\n\treturn 0;\n\n\n}\n\n/*\n====================\nFS_Tell\n\nGive the current position in a file\n====================\n*/\nfs_offset_t FS_Tell( file_t *file )\n{\n\tif( !file ) return 0;\n\treturn file->position - file->buff_len + file->buff_ind;\n}\n\n/*\n====================\nFS_Eof\n\nindicates at reached end of file\n====================\n*/\nqboolean FS_Eof( file_t *file )\n{\n\tif( !file ) return true;\n\treturn (( file->position - file->buff_len + file->buff_ind ) == file->real_length ) ? true : false;\n}\n\n/*\n====================\nFS_Purge\n\nErases any buffered input or output data\n====================\n*/\nstatic void FS_Purge( file_t *file )\n{\n\tfile->buff_len = 0;\n\tfile->buff_ind = 0;\n\tfile->ungetc = EOF;\n}\n\nstatic void *FS_CustomAlloc( size_t size )\n{\n\treturn Mem_Malloc( fs_mempool, size );\n}\n\nstatic void FS_CustomFree( void *data )\n{\n\tMem_Free( data );\n}\n\nstatic byte *FS_LoadFileFromArchive( searchpath_t *sp, const char *path, int pack_ind, fs_offset_t *filesizeptr, const qboolean sys_malloc )\n{\n\tfs_offset_t\tfilesize;\n\tfile_t *file;\n\tbyte *buf;\n\tvoid *( *pfnAlloc )( size_t ) = sys_malloc ? malloc : FS_CustomAlloc;\n\tvoid ( *pfnFree )( void * ) = sys_malloc ? free : FS_CustomFree;\n\n\t// custom load file function for compressed files\n\tif( sp->pfnLoadFile )\n\t\treturn sp->pfnLoadFile( sp, path, pack_ind, filesizeptr, pfnAlloc, pfnFree );\n\n\tfile = sp->pfnOpenFile( sp, path, \"rb\", pack_ind );\n\n\tif( !file ) // TODO: indicate errors\n\t\treturn NULL;\n\n\tfilesize = file->real_length;\n\tbuf = (byte *)pfnAlloc( filesize + 1 );\n\n\tif( unlikely( !buf )) // TODO: indicate errors\n\t{\n\t\tCon_Reportf( \"%s: can't alloc %li bytes, no free memory\\n\", __func__, (long)filesize + 1 );\n\t\tFS_Close( file );\n\t\treturn NULL;\n\t}\n\n\tbuf[filesize] = '\\0';\n\tFS_Read( file, buf, filesize );\n\tFS_Close( file );\n\tif( filesizeptr ) *filesizeptr = filesize;\n\n\treturn buf;\n}\n\n/*\n============\nFS_LoadFile\n\nFilename are relative to the xash directory.\nAlways appends a 0 byte.\n============\n*/\nstatic byte *FS_LoadFile_( const char *path, fs_offset_t *filesizeptr, const qboolean gamedironly, const qboolean custom_alloc )\n{\n\tsearchpath_t *search;\n\tchar netpath[MAX_SYSPATH];\n\tint pack_ind;\n\n\t// some mappers used leading '/' or '\\' in path to models or sounds\n\tif( path[0] == '/' || path[0] == '\\\\' )\n\t\tpath++;\n\n\tif( path[0] == '/' || path[0] == '\\\\' )\n\t\tpath++;\n\n\tif( !fs_searchpaths || FS_CheckNastyPath( path ))\n\t\treturn NULL;\n\n\tsearch = FS_FindFile( path, &pack_ind, netpath, sizeof( netpath ), gamedironly );\n\n\tif( !search )\n\t\treturn NULL;\n\n\treturn FS_LoadFileFromArchive( search, netpath, pack_ind, filesizeptr, !custom_alloc );\n}\n\nbyte *FS_LoadFileMalloc( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n{\n\treturn FS_LoadFile_( path, filesizeptr, gamedironly, false );\n}\n\nbyte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n{\n\treturn FS_LoadFile_( path, filesizeptr, gamedironly, true );\n}\n\nqboolean CRC32_File( dword *crcvalue, const char *filename )\n{\n\tchar\tbuffer[1024];\n\tint\tnum_bytes;\n\tfile_t\t*f;\n\n\tf = FS_Open( filename, \"rb\", false );\n\tif( !f ) return false;\n\n\tCRC32_Init( crcvalue );\n\n\twhile( 1 )\n\t{\n\t\tnum_bytes = FS_Read( f, buffer, sizeof( buffer ));\n\n\t\tif( num_bytes > 0 )\n\t\t\tCRC32_ProcessBuffer( crcvalue, buffer, num_bytes );\n\n\t\tif( FS_Eof( f )) break;\n\t}\n\n\tFS_Close( f );\n\treturn true;\n}\n\nqboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] )\n{\n\tfile_t\t\t*file;\n\tMD5Context_t\tMD5_Hash = { 0 };\n\n\tif(( file = FS_Open( pszFileName, \"rb\", false )) == NULL )\n\t\treturn false;\n\n\tMD5Init( &MD5_Hash );\n\n\tif( seed )\n\t\tMD5Update( &MD5_Hash, (const byte *)seed, 16 );\n\n\twhile( 1 )\n\t{\n\t\tbyte buffer[1024];\n\t\tint bytes = FS_Read( file, buffer, sizeof( buffer ));\n\n\t\tif( bytes > 0 )\n\t\t\tMD5Update( &MD5_Hash, buffer, bytes );\n\n\t\tif( FS_Eof( file ))\n\t\t\tbreak;\n\t}\n\n\tFS_Close( file );\n\tMD5Final( digest, &MD5_Hash );\n\n\treturn true;\n}\n\n/*\n============\nFS_LoadFile\n\nFilename are relative to the xash directory.\nAlways appends a 0 byte.\n============\n*/\nbyte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr )\n{\n\tfile_t\t\t*file;\n\tbyte\t\t*buf = NULL;\n\tfs_offset_t\tfilesize = 0;\n\n\tfile = FS_SysOpen( path, \"rb\" );\n\n\tif( !file )\n\t\treturn NULL;\n\n\t// Try to load\n\tfilesize = file->real_length;\n\tbuf = (byte *)Mem_Malloc( fs_mempool, filesize + 1 );\n\tbuf[filesize] = '\\0';\n\tFS_Read( file, buf, filesize );\n\tFS_Close( file );\n\n\tif( filesizeptr )\n\t\t*filesizeptr = filesize;\n\n\treturn buf;\n}\n\n\n/*\n============\nFS_WriteFile\n\nThe filename will be prefixed by the current game directory\n============\n*/\nqboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len )\n{\n\tfile_t *file;\n\n\tfile = FS_Open( filename, \"wb\", false );\n\n\tif( !file )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: failed on %s\\n\", __func__, filename );\n\t\treturn false;\n\t}\n\n\tFS_Write( file, data, len );\n\tFS_Close( file );\n\n\treturn true;\n}\n\n/*\n=============================================================================\n\nOTHERS PUBLIC FUNCTIONS\n\n=============================================================================\n*/\n/*\n==================\nFS_FileExists\n\nLook for a file in the packages and in the filesystem\n==================\n*/\nint GAME_EXPORT FS_FileExists( const char *filename, int gamedironly )\n{\n\treturn FS_FindFile( filename, NULL, NULL, 0, gamedironly ) != NULL;\n}\n\n/*\n==================\nFS_GetDiskPath\n\nBuild direct path for file in the filesystem\nreturn NULL for file in pack\n==================\n*/\nconst char *FS_GetDiskPath( const char *name, qboolean gamedironly )\n{\n\tstatic char diskpath[MAX_SYSPATH];\n\n\tif( FS_GetFullDiskPath( diskpath, sizeof( diskpath ), name, gamedironly ))\n\t\treturn diskpath;\n\n\treturn NULL;\n}\n\n/*\n==================\nFS_GetFullDiskPath\n\nBuild full path for file on disk\nreturn false for file in pack\n==================\n*/\nqboolean FS_GetFullDiskPath( char *buffer, size_t size, const char *name, qboolean gamedironly )\n{\n\tsearchpath_t *search;\n\tchar temp[MAX_SYSPATH];\n\n\tsearch = FS_FindFile( name, NULL, temp, sizeof( temp ), gamedironly );\n\n\tif( search && search->type == SEARCHPATH_PLAIN )\n\t{\n\t\tQ_snprintf( buffer, size, \"%s%s\", search->filename, temp );\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n==================\nFS_FileSize\n\nreturn size of file in bytes\n==================\n*/\nfs_offset_t FS_FileSize( const char *filename, qboolean gamedironly )\n{\n\tint\tlength = -1; // in case file was missed\n\tfile_t\t*fp;\n\n\tfp = FS_Open( filename, \"rb\", gamedironly );\n\n\tif( fp )\n\t{\n\t\t// it exists\n\t\tFS_Seek( fp, 0, SEEK_END );\n\t\tlength = FS_Tell( fp );\n\t\tFS_Close( fp );\n\t}\n\n\treturn length;\n}\n\n/*\n==================\nFS_FileLength\n\nreturn size of file in bytes\n==================\n*/\nfs_offset_t FS_FileLength( file_t *f )\n{\n\tif( !f ) return 0;\n\treturn f->real_length;\n}\n\n/*\n==================\nFS_FileTime\n\nreturn time of creation file in seconds\n==================\n*/\nint FS_FileTime( const char *filename, qboolean gamedironly )\n{\n\tsearchpath_t *search;\n\tchar netpath[MAX_SYSPATH];\n\tint pack_ind;\n\n\tsearch = FS_FindFile( filename, &pack_ind, netpath, sizeof( netpath ), gamedironly );\n\tif( !search ) return -1; // doesn't exist\n\n\treturn search->pfnFileTime( search, netpath );\n}\n\n/*\n==================\nFS_Rename\n\nrename specified file from gamefolder\n==================\n*/\nqboolean FS_Rename( const char *oldname, const char *newname )\n{\n\tchar oldname2[MAX_SYSPATH], newname2[MAX_SYSPATH], oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH];\n\tint ret;\n\n\t// a1ba: disallow path traversal\n\tif( FS_CheckNastyPath( oldname ) || FS_CheckNastyPath( newname ))\n\t\treturn false;\n\n\tif( !fs_writepath )\n\t\treturn false;\n\n\tif( !COM_CheckString( oldname ) || !COM_CheckString( newname ))\n\t\treturn false;\n\n\t// no work done\n\tif( !Q_stricmp( oldname, newname ))\n\t\treturn true;\n\n\t// fix up slashes\n\tQ_strncpy( oldname2, oldname, sizeof( oldname2 ));\n\tQ_strncpy( newname2, newname, sizeof( newname2 ));\n\n\tCOM_FixSlashes( oldname2 );\n\tCOM_FixSlashes( newname2 );\n\n\t// file does not exist\n\tif( !FS_FixFileCase( fs_writepath->dir, oldname2, oldpath, sizeof( oldpath ), false ))\n\t\treturn false;\n\n\t// exit if overflowed\n\tif( !FS_FixFileCase( fs_writepath->dir, newname2, newpath, sizeof( newpath ), true ))\n\t\treturn false;\n\n\tret = rename( oldpath, newpath );\n\tif( ret < 0 )\n\t{\n\t\tCon_Printf( \"%s: failed to rename file %s (%s) to %s (%s): %s\\n\",\n\t\t\t__func__, oldpath, oldname2, newpath, newname2, strerror( errno ));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nFS_Delete\n\ndelete specified file from gamefolder\n==================\n*/\nqboolean GAME_EXPORT FS_Delete( const char *path )\n{\n\tchar path2[MAX_SYSPATH], real_path[MAX_SYSPATH];\n\tint ret;\n\n\t// a1ba: disallow path traversal\n\tif( FS_CheckNastyPath( path ))\n\t\treturn false;\n\n\tif( !fs_writepath || !COM_CheckString( path ))\n\t\treturn false;\n\n\tQ_strncpy( path2, path, sizeof( path2 ));\n\tCOM_FixSlashes( path2 );\n\n\tif( !FS_FixFileCase( fs_writepath->dir, path2, real_path, sizeof( real_path ), true ))\n\t\treturn true;\n\n\tret = remove( real_path );\n\tif( ret < 0 && errno != ENOENT )\n\t{\n\t\tCon_Printf( \"%s: failed to delete file %s (%s): %s\\n\", __func__, real_path, path, strerror( errno ));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n==================\nFS_FileCopy\n\n==================\n*/\nqboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize )\n{\n\tchar\t*buf = Mem_Malloc( fs_mempool, FILE_COPY_SIZE );\n\tint\tsize, readSize;\n\tqboolean\tdone = true;\n\n\twhile( fileSize > 0 )\n\t{\n\t\tif( fileSize > FILE_COPY_SIZE )\n\t\t\tsize = FILE_COPY_SIZE;\n\t\telse size = fileSize;\n\n\t\tif(( readSize = FS_Read( pInput, buf, size )) < size )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: unexpected end of input file (%d < %d)\\n\", __func__, readSize, size );\n\t\t\tfileSize = 0;\n\t\t\tdone = false;\n\t\t\tbreak;\n\t\t}\n\n\t\tFS_Write( pOutput, buf, readSize );\n\t\tfileSize -= size;\n\t}\n\n\tMem_Free( buf );\n\treturn done;\n}\n\n/*\n===========\nFS_Search\n\nAllocate and fill a search structure with information on matching filenames.\n===========\n*/\nsearch_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly )\n{\n\tsearch_t *search = NULL;\n\tsearchpath_t *searchpath;\n\tint i, numfiles, numchars;\n\tstringlist_t resultlist;\n\n\tif( pattern[0] == '.' || pattern[0] == ':' || pattern[0] == '/' || pattern[0] == '\\\\' )\n\t\treturn NULL; // punctuation issues\n\n\tstringlistinit( &resultlist );\n\n\t// search through the path, one element at a time\n\tfor( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next )\n\t{\n\t\tif( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIRONLY_SEARCH_FLAGS ))\n\t\t\tcontinue;\n\n\t\tsearchpath->pfnSearch( searchpath, &resultlist, pattern, caseinsensitive );\n\t}\n\n\tif( resultlist.numstrings )\n\t{\n\t\tstringlistsort( &resultlist );\n\t\tnumfiles = resultlist.numstrings;\n\t\tnumchars = 0;\n\n\t\tfor( i = 0; i < resultlist.numstrings; i++ )\n\t\t\tnumchars += (int)Q_strlen( resultlist.strings[i]) + 1;\n\t\tsearch = Mem_Calloc( fs_mempool, sizeof(search_t) + numchars + numfiles * sizeof( char* ));\n\t\tsearch->filenames = (char **)((char *)search + sizeof( search_t ));\n\t\tsearch->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* ));\n\t\tsearch->numfilenames = (int)numfiles;\n\t\tnumfiles = numchars = 0;\n\n\t\tfor( i = 0; i < resultlist.numstrings; i++ )\n\t\t{\n\t\t\tsize_t\ttextlen;\n\n\t\t\tsearch->filenames[numfiles] = search->filenamesbuffer + numchars;\n\t\t\ttextlen = Q_strlen(resultlist.strings[i]) + 1;\n\t\t\tmemcpy( search->filenames[numfiles], resultlist.strings[i], textlen );\n\t\t\tnumfiles++;\n\t\t\tnumchars += (int)textlen;\n\t\t}\n\t}\n\n\tstringlistfreecontents( &resultlist );\n\n\treturn search;\n}\n\nstatic qboolean FS_IsArchiveExtensionSupported( const char *ext, uint flags )\n{\n\tint i;\n\n\tif( ext == NULL )\n\t\treturn false;\n\n\tfor( i = 0; i < sizeof( g_archives ) / sizeof( g_archives[0] ); i++ )\n\t{\n\t\tif( FBitSet( flags, IAES_ONLY_REAL_ARCHIVES ) && !g_archives[i].real_archive )\n\t\t\tcontinue;\n\n\t\tif( !Q_stricmp( ext, g_archives[i].ext ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic searchpath_t *FS_GetArchiveByName( const char *name, searchpath_t *prev )\n{\n\tsearchpath_t *sp = prev ? prev->next : fs_searchpaths;\n\n\tfor( ; sp; sp = sp->next )\n\t{\n\t\tif( !Q_stricmp( COM_FileWithoutPath( sp->filename ), name ))\n\t\t\treturn sp;\n\t}\n\n\treturn NULL;\n}\n\nstatic int FS_FindFileInArchive( searchpath_t *sp, const char *path, char *truepath, size_t len )\n{\n\treturn sp->pfnFindFile( sp, path, truepath, len );\n}\n\nstatic file_t *FS_OpenFileFromArchive( searchpath_t *sp, const char *path, const char *mode, int pack_ind )\n{\n\treturn sp->pfnOpenFile( sp, path, mode, pack_ind );\n}\n\nvoid FS_InitMemory( void )\n{\n\tfs_mempool = Mem_AllocPool( \"FileSystem Pool\" );\n\tfs_searchpaths = NULL;\n}\n\nfs_interface_t g_engfuncs =\n{\n\tCon_PrintfStub,\n\tCon_PrintfStub,\n\tCon_PrintfStub,\n\tSys_ErrorStub,\n\tMem_AllocPoolStub,\n\tMem_FreePoolStub,\n\tMem_AllocStub,\n\tMem_ReallocStub,\n\tMem_FreeStub,\n\tSys_GetNativeObjectStub,\n};\n\nstatic qboolean FS_InitInterface( int version, const fs_interface_t *engfuncs )\n{\n\t// to be extended in future interface revisions\n\tif( version != FS_API_VERSION )\n\t{\n\t\tCon_Printf( S_ERROR \"filesystem optional interface version mismatch: expected %d, got %d\\n\",\n\t\t\tFS_API_VERSION, version );\n\t\treturn false;\n\t}\n\n\tif( engfuncs->_Con_Printf )\n\t\tg_engfuncs._Con_Printf = engfuncs->_Con_Printf;\n\n\tif( engfuncs->_Con_DPrintf )\n\t\tg_engfuncs._Con_DPrintf = engfuncs->_Con_DPrintf;\n\n\tif( engfuncs->_Con_Reportf )\n\t\tg_engfuncs._Con_Reportf = engfuncs->_Con_Reportf;\n\n\tif( engfuncs->_Sys_Error )\n\t\tg_engfuncs._Sys_Error = engfuncs->_Sys_Error;\n\n\tif( engfuncs->_Mem_AllocPool && engfuncs->_Mem_FreePool )\n\t{\n\t\tg_engfuncs._Mem_AllocPool = engfuncs->_Mem_AllocPool;\n\t\tg_engfuncs._Mem_FreePool = engfuncs->_Mem_FreePool;\n\n\t\tCon_Reportf( \"filesystem_stdio: custom pool allocation functions found\\n\" );\n\t}\n\n\tif( engfuncs->_Mem_Alloc && engfuncs->_Mem_Realloc && engfuncs->_Mem_Free )\n\t{\n\t\tg_engfuncs._Mem_Alloc = engfuncs->_Mem_Alloc;\n\t\tg_engfuncs._Mem_Realloc = engfuncs->_Mem_Realloc;\n\t\tg_engfuncs._Mem_Free = engfuncs->_Mem_Free;\n\n\t\tCon_Reportf( \"filesystem_stdio: custom memory allocation functions found\\n\" );\n\t}\n\n\tif( engfuncs->_Sys_GetNativeObject )\n\t{\n\t\tg_engfuncs._Sys_GetNativeObject = engfuncs->_Sys_GetNativeObject;\n\t\tCon_Reportf( \"filesystem_stdio: custom platform-specific functions found\\n\" );\n\t}\n\n\treturn true;\n}\n\nconst fs_api_t g_api =\n{\n\tFS_InitStdio,\n\tFS_ShutdownStdio,\n\n\t// search path utils\n\tFS_Rescan,\n\tFS_ClearSearchPath,\n\tFS_AllowDirectPaths,\n\tFS_AddGameDirectory,\n\tFS_AddGameHierarchy,\n\tFS_Search,\n\tFS_SetCurrentDirectory,\n\tFS_FindLibrary,\n\tFS_Path_f,\n\n\t// gameinfo utils\n\tFS_Gamedir,\n\tFS_LoadGameInfo,\n\n\t// file ops\n\tFS_Open,\n\tFS_Write,\n\tFS_Read,\n\tFS_Seek,\n\tFS_Tell,\n\tFS_Eof,\n\tFS_Flush,\n\tFS_Close,\n\tFS_Gets,\n\tFS_UnGetc,\n\tFS_Getc,\n\tFS_VPrintf,\n\tFS_Printf,\n\tFS_Print,\n\tFS_FileLength,\n\tFS_FileCopy,\n\n\t// file buffer ops\n\tFS_LoadFile,\n\tFS_LoadDirectFile,\n\tFS_WriteFile,\n\n\t// file hashing\n\tCRC32_File,\n\tMD5_HashFile,\n\n\t// filesystem ops\n\tFS_FileExists,\n\tFS_FileTime,\n\tFS_FileSize,\n\tFS_Rename,\n\tFS_Delete,\n\tFS_SysFileExists,\n\tFS_GetDiskPath,\n\n\tNULL,\n\t(void *)FS_MountArchive_Fullpath,\n\n\tFS_GetFullDiskPath,\n\tFS_LoadFileMalloc,\n\n\tFS_IsArchiveExtensionSupported,\n\tFS_GetArchiveByName,\n\tFS_FindFileInArchive,\n\tFS_OpenFileFromArchive,\n\tFS_LoadFileFromArchive,\n\n\tFS_GetRootDirectory,\n\n\tFS_MakeGameInfo,\n};\n\nint EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs );\nint EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs )\n{\n\tif( engfuncs && !FS_InitInterface( version, engfuncs ))\n\t\treturn 0;\n\n\t*api = g_api;\n\t*globals = &FI;\n\n\treturn FS_API_VERSION;\n}\n"
  },
  {
    "path": "filesystem/filesystem.h",
    "content": "/*\nfilesystem.h - engine FS\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef FILESYSTEM_H\n#define FILESYSTEM_H\n\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdio.h>\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"gameinfo.h\"\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif // __cplusplus\n\n#define FS_API_VERSION 4 // not stable yet!\n#define FS_API_CREATEINTERFACE_TAG   \"XashFileSystem004\" // follow FS_API_VERSION!!!\n#define FILESYSTEM_INTERFACE_VERSION \"VFileSystem009\" // never change this!\n\n// search path flags\nenum\n{\n\tFS_STATIC_PATH    = BIT( 0 ), // FS_ClearSearchPath will be ignore this path\n\tFS_NOWRITE_PATH   = BIT( 1 ), // default behavior - last added gamedir set as writedir. This flag disables it\n\tFS_GAMEDIR_PATH   = BIT( 2 ), // just a marker for gamedir path\n\tFS_CUSTOM_PATH    = BIT( 3 ), // gamedir but with custom/mod data\n\tFS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only\n\n\tFS_SKIP_ARCHIVED_WADS = BIT( 5 ), // don't mount wads inside archives automatically\n\tFS_LOAD_PACKED_WAD = BIT( 6 ), // this wad is packed inside other archive\n\n\tFS_MOUNT_HD    = BIT( 7 ), // mount high definition content folder\n\tFS_MOUNT_LV    = BIT( 8 ), // mount low violence content folder\n\tFS_MOUNT_ADDON = BIT( 9 ), // mount addon folder\n\tFS_MOUNT_L10N  = BIT( 10 ), // mount localization folder\n\n\tFS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH\n};\n\ntypedef struct searchpath_s searchpath_t;\n\n// IsArchiveExtensionSupported flags\nenum\n{\n\t// excludes directories and pk3dir, i.e. archives that cannot be represented as a single file\n\tIAES_ONLY_REAL_ARCHIVES = BIT( 0 ),\n};\n\ntypedef struct\n{\n\tint\tnumfilenames;\n\tchar\t**filenames;\n\tchar\t*filenamesbuffer;\n} search_t;\n\ntypedef struct gameinfo_s\n{\n\t// filesystem info\n\tchar\t\tgamefolder[MAX_QPATH];\t// used for change game '-game x'\n\tchar\t\tbasedir[MAX_QPATH];\t// base game directory (like 'id1' for Quake or 'valve' for Half-Life)\n\tchar\t\tfalldir[MAX_QPATH];\t// used as second basedir\n\tchar\t\tstartmap[MAX_QPATH];// map to start singleplayer game\n\tchar\t\ttrainmap[MAX_QPATH];// map to start hazard course (if specified)\n\tchar\t\ttitle[64];\t// Game Main Title\n\tfloat\t\tversion;\t\t// game version (optional)\n\n\t// .dll pathes\n\tchar\t\tdll_path[MAX_QPATH];\t// e.g. \"bin\" or \"cl_dlls\"\n\tchar\t\tgame_dll[MAX_QPATH];\t// custom path for game.dll\n\n\t// .ico path\n\tchar\t\ticonpath[MAX_QPATH];\t// \"game.ico\" by default\n\n\t// about mod info\n\tstring\t\tgame_url;\t\t// link to a developer's site\n\tstring\t\tupdate_url;\t// link to updates page\n\tchar\t\ttype[MAX_QPATH];\t// single, toolkit, multiplayer etc\n\tchar\t\tdate[MAX_QPATH];\n\tsize_t\t\tsize;\n\n\tint\t\tgamemode;\n\tqboolean\tsecure;\t\t// prevent to console acess\n\tqboolean\tnomodels;\t\t// don't let player to choose model (use player.mdl always)\n\tqboolean\tnoskills;\t\t// disable skill menu selection\n\tqboolean\trender_picbutton_text; // use font renderer to render WON buttons\n\tqboolean\tinternal_vgui_support; // skip loading VGUI, pass ingame UI support API to client\n\n\tchar\t\tsp_entity[32];\t// e.g. info_player_start\n\tchar\t\tmp_entity[32];\t// e.g. info_player_deathmatch\n\tchar\t\tmp_filter[32];\t// filtering multiplayer-maps\n\n\tchar\t\tambientsound[NUM_AMBIENTS][MAX_QPATH];\t// quake ambient sounds\n\n\tint\t\tmax_edicts;\t// min edicts is 600, max edicts is 8196\n\tint\t\tmax_tents;\t// min temp ents is 300, max is 2048\n\tint\t\tmax_beams;\t// min beams is 64, max beams is 512\n\tint\t\tmax_particles;\t// min particles is 4096, max particles is 32768\n\n\tchar\t\tgame_dll_linux[64];\t// custom path for game.dll\n\tchar\t\tgame_dll_osx[64];\t// custom path for game.dll\n\n\tqboolean\tadded;\n\n\tint\t\tquicksave_aged_count; // min is 1, max is 99\n\tint\t\tautosave_aged_count; // min is 1, max is 99\n\n\t// HL25 compatibility keys\n\tqboolean hd_background;\n\tqboolean animated_title;\n\n\tchar demomap[MAX_QPATH];\n\n\tqboolean rodir; // if true, parsed from rodir\n\tint64_t mtime;\n} gameinfo_t;\n\ntypedef struct fs_dllinfo_t\n{\n\tchar fullPath[2048]; // absolute disk path\n\tstring shortPath; // vfs path\n\tqboolean encrypted; // do we need encrypted DLL loader?\n\tqboolean custom_loader; // do we need memory DLL loader?\n} fs_dllinfo_t;\n\ntypedef struct fs_globals_t\n{\n\tgameinfo_t\t*GameInfo;\t// current GameInfo\n\tgameinfo_t\t*games[MAX_MODS];\t// environment games (founded at each engine start)\n\tint\t\tnumgames;\n} fs_globals_t;\n\ntypedef struct file_s file_t;\n\ntypedef struct fs_api_t\n{\n\tqboolean (*InitStdio)( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir );\n\tvoid (*ShutdownStdio)( void );\n\n\t// search path utils\n\tvoid (*Rescan)( uint32_t flags, const char *language );\n\tvoid (*ClearSearchPath)( void );\n\tvoid (*AllowDirectPaths)( qboolean enable );\n\tvoid (*AddGameDirectory)( const char *dir, uint flags );\n\tvoid (*AddGameHierarchy)( const char *dir, uint flags );\n\tsearch_t *(*Search)( const char *pattern, int caseinsensitive, int gamedironly );\n\tint (*SetCurrentDirectory)( const char *path );\n\tqboolean (*FindLibrary)( const char *dllname, qboolean directpath, fs_dllinfo_t *dllinfo );\n\tvoid (*Path_f)( void );\n\n\t// gameinfo utils\n\tconst char *(*Gamedir)( void );\n\tvoid (*LoadGameInfo)( uint32_t flags, const char *language );\n\n\t// file ops\n\tfile_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly );\n\tfs_offset_t (*Write)( file_t *file, const void *data, size_t datasize );\n\tfs_offset_t (*Read)( file_t *file, void *buffer, size_t buffersize );\n\tint (*Seek)( file_t *file, fs_offset_t offset, int whence );\n\tfs_offset_t (*Tell)( file_t *file );\n\tqboolean (*Eof)( file_t *file );\n\tint (*Flush)( file_t *file );\n\tint (*Close)( file_t *file );\n\tint (*Gets)( file_t *file, char *string, size_t bufsize );\n\tint (*UnGetc)( file_t *file, char c );\n\tint (*Getc)( file_t *file );\n\tint (*VPrintf)( file_t *file, const char *format, va_list ap );\n\tint (*Printf)( file_t *file, const char *format, ... ) FORMAT_CHECK( 2 );\n\tint (*Print)( file_t *file, const char *msg );\n\tfs_offset_t (*FileLength)( file_t *f );\n\tqboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize );\n\n\t// file buffer ops\n\tbyte *(*LoadFile)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly );\n\tbyte *(*LoadDirectFile)( const char *path, fs_offset_t *filesizeptr );\n\tqboolean (*WriteFile)( const char *filename, const void *data, fs_offset_t len );\n\n\t// file hashing\n\tqboolean (*CRC32_File)( dword *crcvalue, const char *filename );\n\tqboolean (*MD5_HashFile)( byte digest[16], const char *pszFileName, uint seed[4] );\n\n\t// filesystem ops\n\tint (*FileExists)( const char *filename, int gamedironly );\n\tint (*FileTime)( const char *filename, qboolean gamedironly );\n\tfs_offset_t (*FileSize)( const char *filename, qboolean gamedironly );\n\tqboolean (*Rename)( const char *oldname, const char *newname );\n\tqboolean (*Delete)( const char *path );\n\tqboolean (*SysFileExists)( const char *path );\n\tconst char *(*GetDiskPath)( const char *name, qboolean gamedironly );\n\n\tconst char *(*ArchivePath)( file_t *f ); // returns path to archive from which file was opened or \"plain\"\n\tvoid *(*MountArchive_Fullpath)( const char *path, int flags ); // mounts the archive by path, if supported\n\n\tqboolean (*GetFullDiskPath)( char *buffer, size_t size, const char *name, qboolean gamedironly );\n\n\t// like LoadFile but returns pointer that can be free'd using standard library function\n\tbyte *(*LoadFileMalloc)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly );\n\n\t// **** archive interface ****\n\t// query supported formats\n\tqboolean (*IsArchiveExtensionSupported)( const char *ext, uint flags );\n\n\t// to speed up archive lookups, this function can be used to get the archive object by it's name\n\t// because archive can share the name, you can call this function repeatedly to get all archives\n\tsearchpath_t *(*GetArchiveByName)( const char *name, searchpath_t *prev );\n\n\t// return an index into the archive and a true path, if possible\n\tint (*FindFileInArchive)( searchpath_t *sp, const char *path, char *outpath, size_t len );\n\n\t// similarly to Open, opens file but from specified archive\n\t// NOTE: for speed reasons, path is case-sensitive here!\n\t// Use FindFileInArchive to retrieve real path from caseinsensitive FS emulation!\n\tfile_t *(*OpenFileFromArchive)( searchpath_t *, const char *path, const char *mode, int pack_ind );\n\n\t// similarly to LoadFile, loads whole file into memory from specified archive\n\t// NOTE: for speed reasons, path is case-sensitive here!\n\t// Use FindFileInArchive to retrieve real path from caseinsensitive FS emulation!\n\tbyte *(*LoadFileFromArchive)( searchpath_t *sp, const char *path, int pack_ind, fs_offset_t *filesizeptr, const qboolean sys_malloc );\n\n\t// gets current root directory, set by InitStdio\n\tqboolean (*GetRootDirectory)( char *path, size_t size );\n\n\tvoid (*MakeGameInfo)( void );\n} fs_api_t;\n\ntypedef struct fs_interface_t\n{\n\t// logging\n\tvoid    (*_Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // typical console allowed messages\n\tvoid    (*_Con_DPrintf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 1\n\tvoid    (*_Con_Reportf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 2\n\n\tvoid    (*_Sys_Error)( const char *fmt, ... ) FORMAT_CHECK( 1 );\n\n\t// memory\n\tpoolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline );\n\tvoid  (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline );\n\tvoid *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n\t\tALLOC_CHECK( 2 ) WARN_UNUSED_RESULT;\n\tvoid *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline )\n\t\tALLOC_CHECK( 3 ) WARN_UNUSED_RESULT;\n\tvoid  (*_Mem_Free)( void *data, const char *filename, int fileline );\n\n\t// platform\n\tvoid *(*_Sys_GetNativeObject)( const char *object );\n} fs_interface_t;\n\ntypedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, const fs_interface_t *interface );\n#define GET_FS_API \"GetFSAPI\"\n\n#ifdef __cplusplus\n}\n#endif // __cplusplus\n\n#endif//FILESYSTEM_H\n"
  },
  {
    "path": "filesystem/filesystem_internal.h",
    "content": "/*\nfilesystem.h - engine FS\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef FILESYSTEM_INTERNAL_H\n#define FILESYSTEM_INTERNAL_H\n\n#include <stdlib.h>\n#include \"xash3d_types.h\"\n#include \"filesystem.h\"\n#include \"miniz.h\"\n\n#if XASH_ANDROID\n#include <android/asset_manager.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\ntypedef struct searchpath_s searchpath_t;\ntypedef struct dir_s dir_t;\ntypedef struct zip_s zip_t;\ntypedef struct pack_s pack_t;\ntypedef struct wfile_s wfile_t;\ntypedef struct android_assets_s android_assets_t;\n\n#define FILE_BUFF_SIZE (2048)\n#define FILE_DEFLATED BIT( 0 )\n\ntypedef struct ztoolkit_s\n{\n\tz_stream zstream;\n\tsize_t   comp_length;\n\tsize_t   in_ind, in_len;\n\tsize_t   in_position;\n\tbyte     input[FILE_BUFF_SIZE];\n} ztoolkit_t;\n\nstruct file_s\n{\n\tint          handle;      // file descriptor\n\tint          ungetc;      // single stored character from ungetc, cleared to EOF when read\n\ttime_t       filetime;    // pak, wad or real filetime\n\tsearchpath_t *searchpath;\n\tfs_offset_t  real_length; // uncompressed file size (for files opened in \"read\" mode)\n\tfs_offset_t  position;    // current position in the file\n\tfs_offset_t  offset;      // offset into the package (0 if external file)\n\tuint32_t     flags;\n\tztoolkit_t   *ztk; // if not NULL, all read functions must go through decompression\n\n\t// contents buffer\n\tfs_offset_t buff_ind; // buffer current index\n\tfs_offset_t buff_len; // buffer current length\n\tbyte\t\tbuff[FILE_BUFF_SIZE]; // intermediate buffer\n\n#ifdef XASH_REDUCE_FD\n\tconst char *backup_path;\n\tfs_offset_t backup_position;\n\tuint backup_options;\n#endif\n};\n\ntypedef enum searchpathtype_e\n{\n\tSEARCHPATH_PLAIN = 0,\n\tSEARCHPATH_PAK,\n\tSEARCHPATH_WAD,\n\tSEARCHPATH_ZIP,\n\tSEARCHPATH_PK3DIR, // it's actually a plain directory but it must behave like a ZIP archive,\n\tSEARCHPATH_ANDROID_ASSETS\n} searchpathtype_t;\n\ntypedef struct stringlist_s\n{\n\t// maxstrings changes as needed, causing reallocation of strings[] array\n\tint\t\tmaxstrings;\n\tint\t\tnumstrings;\n\tchar\t\t**strings;\n} stringlist_t;\n\ntypedef struct searchpath_s\n{\n\tstring           filename;\n\tsearchpathtype_t type;\n\tint              flags;\n\n\tunion\n\t{\n\t\tdir_t   *dir;\n\t\tpack_t  *pack;\n\t\twfile_t *wad;\n\t\tzip_t   *zip;\n\t\tandroid_assets_t *assets;\n\t};\n\n\tstruct searchpath_s *next;\n\n\tvoid    (*pfnPrintInfo)( struct searchpath_s *search, char *dst, size_t size );\n\tvoid    (*pfnClose)( struct searchpath_s *search );\n\tfile_t *(*pfnOpenFile)( struct searchpath_s *search, const char *filename, const char *mode, int pack_ind );\n\tint     (*pfnFileTime)( struct searchpath_s *search, const char *filename );\n\tint     (*pfnFindFile)( struct searchpath_s *search, const char *path, char *fixedname, size_t len );\n\tvoid    (*pfnSearch)( struct searchpath_s *search, stringlist_t *list, const char *pattern, int caseinsensitive );\n\tbyte   *(*pfnLoadFile)( struct searchpath_s *search, const char *path, int pack_ind, fs_offset_t *filesize, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * ));\n} searchpath_t;\n\ntypedef searchpath_t *(*FS_ADDARCHIVE_FULLPATH)( const char *path, int flags );\n\nextern fs_globals_t  FI;\nextern searchpath_t *fs_writepath;\nextern poolhandle_t  fs_mempool;\nextern fs_interface_t g_engfuncs;\nextern char          fs_rootdir[MAX_SYSPATH];\nextern const fs_api_t     g_api;\n\n#define GI FI.GameInfo\n\n#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ )\n#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ )\n#define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ )\n#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ )\n#define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ )\n#define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ )\n\n#define Con_Printf  (*g_engfuncs._Con_Printf)\n#define Con_DPrintf (*g_engfuncs._Con_DPrintf)\n#define Con_Reportf (*g_engfuncs._Con_Reportf)\n#define Sys_Error   (*g_engfuncs._Sys_Error)\n#define Sys_GetNativeObject (*g_engfuncs._Sys_GetNativeObject)\n\n//\n// filesystem.c\n//\nqboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir );\nvoid FS_ShutdownStdio( void );\nsearchpath_t *FS_MountArchive_Fullpath( const char *file, int flags );\nvoid _Mem_Free( void *data, const char *filename, int fileline );\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n\tALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\n\n// search path utils\nvoid FS_Rescan( uint32_t flags, const char *language );\nvoid FS_ClearSearchPath( void );\nvoid FS_AllowDirectPaths( qboolean enable );\nvoid FS_AddGameDirectory( const char *dir, uint flags );\nvoid FS_AddGameHierarchy( const char *dir, uint flags );\nsearch_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nint FS_SetCurrentDirectory( const char *path );\nqboolean FS_GetRootDirectory( char *path, size_t size );\nvoid FS_Path_f( void );\n\n// file ops\nint FS_Close( file_t *file );\nfile_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly )\n\tMALLOC_LIKE( FS_Close, 1 ) WARN_UNUSED_RESULT;\nfs_offset_t FS_Write( file_t *file, const void *data, size_t datasize );\nfs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize );\nint FS_Seek( file_t *file, fs_offset_t offset, int whence );\nfs_offset_t FS_Tell( file_t *file );\nqboolean FS_Eof( file_t *file );\nint FS_Flush( file_t *file );\nint FS_Gets( file_t *file, char *string, size_t bufsize );\nint FS_UnGetc( file_t *file, char c );\nint FS_Getc( file_t *file );\nint FS_VPrintf( file_t *file, const char *format, va_list ap );\nint FS_Printf( file_t *file, const char *format, ... ) FORMAT_CHECK( 2 );\nint FS_Print( file_t *file, const char *msg );\nfs_offset_t FS_FileLength( file_t *f );\nqboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize );\n\n// file buffer ops\nbyte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nbyte *FS_LoadFileMalloc( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly )\n\tMALLOC_LIKE( free, 1 ) WARN_UNUSED_RESULT;\nbyte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr )\n\tMALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\nqboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len );\n\n// file hashing\nqboolean CRC32_File( dword *crcvalue, const char *filename );\nqboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] );\n\n// stringlist ops\nvoid stringlistinit( stringlist_t *list );\nvoid stringlistfreecontents( stringlist_t *list );\nvoid stringlistappend( stringlist_t *list, const char *text );\nvoid stringlistsort( stringlist_t *list );\nvoid listdirectory( stringlist_t *list, const char *path, qboolean dirs_only );\n\n// filesystem ops\nint FS_FileExists( const char *filename, int gamedironly );\nint FS_FileTime( const char *filename, qboolean gamedironly );\nfs_offset_t FS_FileSize( const char *filename, qboolean gamedironly );\nqboolean FS_Rename( const char *oldname, const char *newname );\nqboolean FS_Delete( const char *path );\nqboolean FS_SysFileExists( const char *path );\nconst char *FS_GetDiskPath( const char *name, qboolean gamedironly );\nqboolean FS_GetFullDiskPath( char *buffer, size_t size, const char *name, qboolean gamedironly );\nvoid     FS_CreatePath( char *path );\nqboolean FS_SysFolderExists( const char *path );\nqboolean FS_SysFileOrFolderExists( const char *path );\nfile_t  *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly );\n\nint           FS_SysFileTime( const char *filename );\nfile_t       *FS_OpenHandle( searchpath_t *search, int handle, fs_offset_t offset, fs_offset_t len );\nfile_t       *FS_SysOpen( const char *filepath, const char *mode );\nsearchpath_t *FS_FindFile( const char *name, int *index, char *fixedname, size_t len, qboolean gamedironly );\nqboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size );\n\n//\n// pak.c\n//\nqboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files );\nsearchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags );\n\n//\n// wad.c\n//\nsearchpath_t *FS_AddWad_Fullpath( const char *wadfile, int flags );\n\n//\n// zip.c\n//\nsearchpath_t *FS_AddZip_Fullpath( const char *zipfile, int flags );\n\n//\n// dir.c\n//\nsearchpath_t *FS_AddDir_Fullpath( const char *path, int flags );\nqboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath );\nvoid FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int flags );\n\n//\n// android.c\n//\nvoid FS_InitAndroid( void );\nsearchpath_t *FS_AddAndroidAssets_Fullpath( const char *path, int flags );\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // FILESYSTEM_INTERNAL_H\n"
  },
  {
    "path": "filesystem/fscallback.h",
    "content": "/*\nfscallback.h - common filesystem callbacks\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef FSCALLBACK_H\n#define FSCALLBACK_H\n\n#include \"filesystem.h\"\n\nextern fs_api_t g_fsapi;\nextern fs_globals_t *FI;\n\n#define GI           FI->GameInfo\n#define FS_Gamedir() GI->gamefolder\n#define FS_Title()   GI->title\n\n#define FS_InitStdio     (*g_fsapi.InitStdio)\n#define FS_ShutdownStdio (*g_fsapi.ShutdownStdio)\n\n// search path utils\n#define FS_Rescan (*g_fsapi.Rescan)\n#define FS_ClearSearchPath (*g_fsapi.ClearSearchPath)\n#define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths)\n#define FS_AddGameDirectory (*g_fsapi.AddGameDirectory)\n#define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy)\n#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE\n#define FS_Search (*g_fsapi.Search)\n#endif\n#define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory)\n#define FS_Path_f (*g_fsapi.Path_f)\n\n// file ops\n#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE\n#define FS_Open (*g_fsapi.Open)\n#define FS_Close (*g_fsapi.Close)\n#endif\n#define FS_Write (*g_fsapi.Write)\n#define FS_Read (*g_fsapi.Read)\n#define FS_Seek (*g_fsapi.Seek)\n#define FS_Tell (*g_fsapi.Tell)\n#define FS_Eof (*g_fsapi.Eof)\n#define FS_Flush (*g_fsapi.Flush)\n#define FS_Gets (*g_fsapi.Gets)\n#define FS_UnGetc (*g_fsapi.UnGetc)\n#define FS_Getc (*g_fsapi.Getc)\n#define FS_VPrintf (*g_fsapi.VPrintf)\n#define FS_Printf (*g_fsapi.Printf)\n#define FS_Print (*g_fsapi.Print)\n#define FS_FileLength (*g_fsapi.FileLength)\n#define FS_FileCopy (*g_fsapi.FileCopy)\n\n// file buffer ops\n#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE\n#define FS_LoadFile (*g_fsapi.LoadFile)\n#define FS_LoadDirectFile (*g_fsapi.LoadDirectFile)\n#endif\n#define FS_WriteFile (*g_fsapi.WriteFile)\n\n// file hashing\n#define CRC32_File (*g_fsapi.CRC32_File)\n#define MD5_HashFile (*g_fsapi.MD5_HashFile)\n\n// filesystem ops\n#define FS_FileExists (*g_fsapi.FileExists)\n#define FS_FileTime (*g_fsapi.FileTime)\n#define FS_FileSize (*g_fsapi.FileSize)\n#define FS_Rename (*g_fsapi.Rename)\n#define FS_Delete (*g_fsapi.Delete)\n#define FS_SysFileExists (*g_fsapi.SysFileExists)\n#define FS_GetDiskPath (*g_fsapi.GetDiskPath)\n\n\n#endif // FSCALLBACK_H\n"
  },
  {
    "path": "filesystem/pak.c",
    "content": "/*\npak.c - PAK support for filesystem\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"build.h\"\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#if XASH_POSIX\n#include <unistd.h>\n#endif\n#include <errno.h>\n#include <stddef.h>\n#include \"port.h\"\n#include \"filesystem_internal.h\"\n#include \"crtlib.h\"\n#include \"common/com_strings.h\"\n\n/*\n========================================================================\nPAK FILES\n\nThe .pak files are just a linear collapse of a directory tree\n========================================================================\n*/\n// header\n#define IDPACKV1HEADER\t(('K'<<24)+('C'<<16)+('A'<<8)+'P')\t// little-endian \"PACK\"\n\n#define MAX_FILES_IN_PACK\t65536 // pak\n\ntypedef struct\n{\n\tint\t\tident;\n\tint\t\tdirofs;\n\tint\t\tdirlen;\n} dpackheader_t;\n\ntypedef struct\n{\n\tchar\t\tname[56];\t\t// total 64 bytes\n\tint\t\tfilepos;\n\tint\t\tfilelen;\n} dpackfile_t;\n\n// PAK errors\n#define PAK_LOAD_OK\t\t\t0\n#define PAK_LOAD_COULDNT_OPEN\t\t1\n#define PAK_LOAD_BAD_HEADER\t\t2\n#define PAK_LOAD_BAD_FOLDERS\t\t3\n#define PAK_LOAD_TOO_MANY_FILES\t4\n#define PAK_LOAD_NO_FILES\t\t5\n#define PAK_LOAD_CORRUPTED\t\t6\n\nstruct pack_s\n{\n\tfile_t *handle;\n\tint\t\tnumfiles;\n\tdpackfile_t files[]; // flexible\n};\n\n/*\n====================\nFS_SortPak\n\n====================\n*/\nstatic int FS_SortPak( const void *_a, const void *_b )\n{\n\tconst dpackfile_t *a = _a, *b = _b;\n\n\treturn Q_stricmp( a->name, b->name );\n}\n\n/*\n=================\nFS_LoadPackPAK\n\nTakes an explicit (not game tree related) path to a pak file.\n\nLoads the header and directory, adding the files at the beginning\nof the list so they override previous pack files.\n=================\n*/\nstatic pack_t *FS_LoadPackPAK( const char *packfile, int *error )\n{\n\tdpackheader_t header;\n\tfile_t *packhandle;\n\tint         numpackfiles;\n\tpack_t      *pack;\n\tfs_size_t     c;\n\n\t// TODO: use FS_Open to allow PK3 to be included into other archives\n\t// Currently, it doesn't work with rodir due to FS_FindFile logic\n\t// when it will use FS_Open, check that FS_CheckForQuakePak correctly\n\t// detects Quake gamedirs in RoDir\n\tpackhandle = FS_SysOpen( packfile, \"rb\" );\n\n\tif( packhandle == NULL )\n\t{\n\t\tCon_Reportf( \"%s couldn't open: %s\\n\", packfile, strerror( errno ));\n\t\tif( error ) *error = PAK_LOAD_COULDNT_OPEN;\n\t\treturn NULL;\n\t}\n\n\tc = FS_Read( packhandle, (void *)&header, sizeof( header ));\n\n\tif( c != sizeof( header ) || header.ident != IDPACKV1HEADER )\n\t{\n\t\tCon_Reportf( \"%s is not a packfile. Ignored.\\n\", packfile );\n\t\tif( error ) *error = PAK_LOAD_BAD_HEADER;\n\t\tFS_Close( packhandle );\n\t\treturn NULL;\n\t}\n\n\tif( header.dirlen % sizeof( dpackfile_t ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s has an invalid directory size. Ignored.\\n\", packfile );\n\t\tif( error ) *error = PAK_LOAD_BAD_FOLDERS;\n\t\tFS_Close( packhandle );\n\t\treturn NULL;\n\t}\n\n\tnumpackfiles = header.dirlen / sizeof( dpackfile_t );\n\n\tif( numpackfiles > MAX_FILES_IN_PACK )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s has too many files ( %i ). Ignored.\\n\", packfile, numpackfiles );\n\t\tif( error ) *error = PAK_LOAD_TOO_MANY_FILES;\n\t\tFS_Close( packhandle );\n\t\treturn NULL;\n\t}\n\n\tif( numpackfiles <= 0 )\n\t{\n\t\tCon_Reportf( \"%s has no files. Ignored.\\n\", packfile );\n\t\tif( error ) *error = PAK_LOAD_NO_FILES;\n\t\tFS_Close( packhandle );\n\t\treturn NULL;\n\t}\n\n\tpack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t ) + sizeof( dpackfile_t ) * numpackfiles );\n\tFS_Seek( packhandle, header.dirofs, SEEK_SET );\n\n\tif( header.dirlen != FS_Read( packhandle, (void *)pack->files, header.dirlen ))\n\t{\n\t\tCon_Reportf( \"%s is an incomplete PAK, not loading\\n\", packfile );\n\t\tif( error )\n\t\t\t*error = PAK_LOAD_CORRUPTED;\n\t\tFS_Close( packhandle );\n\t\tMem_Free( pack );\n\t\treturn NULL;\n\t}\n\n\t// TODO: validate directory?\n\n\tpack->handle = packhandle;\n\tpack->numfiles = numpackfiles;\n\tqsort( pack->files, pack->numfiles, sizeof( pack->files[0] ), FS_SortPak );\n\n#ifdef XASH_REDUCE_FD\n\t// will reopen when needed\n\tclose( pack->handle );\n\tpack->handle = -1;\n#endif\n\n\tif( error )\n\t\t*error = PAK_LOAD_OK;\n\n\treturn pack;\n}\n\n/*\n===========\nFS_OpenPackedFile\n\nOpen a packed file using its package file descriptor\n===========\n*/\nstatic file_t *FS_OpenFile_PAK( searchpath_t *search, const char *filename, const char *mode, int pack_ind )\n{\n\tdpackfile_t\t*pfile;\n\n\tpfile = &search->pack->files[pack_ind];\n\n\treturn FS_OpenHandle( search, search->pack->handle->handle, pfile->filepos, pfile->filelen );\n}\n\n/*\n===========\nFS_FindFile_PAK\n\n===========\n*/\nstatic int FS_FindFile_PAK( searchpath_t *search, const char *path, char *fixedname, size_t len )\n{\n\tint\tleft, right, middle;\n\n\t// look for the file (binary search)\n\tleft = 0;\n\tright = search->pack->numfiles - 1;\n\twhile( left <= right )\n\t{\n\t\tint\tdiff;\n\n\t\tmiddle = (left + right) / 2;\n\t\tdiff = Q_stricmp( search->pack->files[middle].name, path );\n\n\t\t// Found it\n\t\tif( !diff )\n\t\t{\n\t\t\tif( fixedname )\n\t\t\t\tQ_strncpy( fixedname, search->pack->files[middle].name, len );\n\t\t\treturn middle;\n\t\t}\n\n\t\t// if we're too far in the list\n\t\tif( diff > 0 )\n\t\t\tright = middle - 1;\n\t\telse left = middle + 1;\n\t}\n\n\treturn -1;\n}\n\n/*\n===========\nFS_Search_PAK\n\n===========\n*/\nstatic void FS_Search_PAK( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )\n{\n\tstring temp;\n\tconst char *slash, *backslash, *colon, *separator;\n\tint j, i;\n\n\tfor( i = 0; i < search->pack->numfiles; i++ )\n\t{\n\t\tQ_strncpy( temp, search->pack->files[i].name, sizeof( temp ));\n\t\twhile( temp[0] )\n\t\t{\n\t\t\tif( matchpattern( temp, pattern, true ))\n\t\t\t{\n\t\t\t\tfor( j = 0; j < list->numstrings; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( !Q_strcmp( list->strings[j], temp ))\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif( j == list->numstrings )\n\t\t\t\t\tstringlistappend( list, temp );\n\t\t\t}\n\n\t\t\t// strip off one path element at a time until empty\n\t\t\t// this way directories are added to the listing if they match the pattern\n\t\t\tslash = Q_strrchr( temp, '/' );\n\t\t\tbackslash = Q_strrchr( temp, '\\\\' );\n\t\t\tcolon = Q_strrchr( temp, ':' );\n\t\t\tseparator = temp;\n\t\t\tif( separator < slash )\n\t\t\t\tseparator = slash;\n\t\t\tif( separator < backslash )\n\t\t\t\tseparator = backslash;\n\t\t\tif( separator < colon )\n\t\t\t\tseparator = colon;\n\t\t\t*((char *)separator) = 0;\n\t\t}\n\t}\n}\n\n/*\n===========\nFS_FileTime_PAK\n\n===========\n*/\nstatic int FS_FileTime_PAK( searchpath_t *search, const char *filename )\n{\n\treturn search->pack->handle->filetime;\n}\n\n/*\n===========\nFS_PrintInfo_PAK\n\n===========\n*/\nstatic void FS_PrintInfo_PAK( searchpath_t *search, char *dst, size_t size )\n{\n\tif( search->pack->handle->searchpath )\n\t\tQ_snprintf( dst, size, \"%s (%i files)\" S_CYAN \" from %s\" S_DEFAULT, search->filename, search->pack->numfiles, search->pack->handle->searchpath->filename );\n\telse Q_snprintf( dst, size, \"%s (%i files)\", search->filename, search->pack->numfiles );\n}\n\n/*\n===========\nFS_Close_PAK\n\n===========\n*/\nstatic void FS_Close_PAK( searchpath_t *search )\n{\n\tif( search->pack->handle != NULL )\n\t\tFS_Close( search->pack->handle );\n\tMem_Free( search->pack );\n}\n\n\n/*\n================\nFS_AddPak_Fullpath\n\nAdds the given pack to the search path.\nThe pack type is autodetected by the file extension.\n\nReturns true if the file was successfully added to the\nsearch path or if it was already included.\n\nIf keep_plain_dirs is set, the pack will be added AFTER the first sequence of\nplain directories.\n================\n*/\nsearchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags )\n{\n\tsearchpath_t *search;\n\tpack_t *pak;\n\tint errorcode = PAK_LOAD_COULDNT_OPEN;\n\n\tpak = FS_LoadPackPAK( pakfile, &errorcode );\n\n\tif( !pak )\n\t{\n\t\tif( errorcode != PAK_LOAD_NO_FILES )\n\t\t\tCon_Reportf( S_ERROR \"%s: unable to load pak \\\"%s\\\"\\n\", __func__, pakfile );\n\t\treturn NULL;\n\t}\n\n\tsearch = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ));\n\tQ_strncpy( search->filename, pakfile, sizeof( search->filename ));\n\tsearch->pack = pak;\n\tsearch->type = SEARCHPATH_PAK;\n\tsearch->flags = flags;\n\n\tsearch->pfnPrintInfo = FS_PrintInfo_PAK;\n\tsearch->pfnClose = FS_Close_PAK;\n\tsearch->pfnOpenFile = FS_OpenFile_PAK;\n\tsearch->pfnFileTime = FS_FileTime_PAK;\n\tsearch->pfnFindFile = FS_FindFile_PAK;\n\tsearch->pfnSearch = FS_Search_PAK;\n\n\tCon_Reportf( \"Adding PAK: %s (%i files)\\n\", pakfile, pak->numfiles );\n\n\treturn search;\n}\n\n/*\n================\nFS_CheckForQuakePak\n\nTo generate fake gameinfo for Quake directory, we need to parse pak0.pak\nand find progs.dat in it\n================\n*/\nqboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files )\n{\n\tqboolean is_quake = false;\n\tpack_t *pak;\n\tint i;\n\n\tpak = FS_LoadPackPAK( pakfile, NULL );\n\tif( !pak )\n\t\treturn false;\n\n\tfor( i = 0; i < num_files; i++ )\n\t{\n\t\tint j;\n\n\t\tfor( j = 0; j < pak->numfiles; j++ )\n\t\t{\n\t\t\tif( Q_strchr( pak->files[j].name, '/' ))\n\t\t\t\tcontinue; // exclude subdirectories\n\n\t\t\tif( !Q_stricmp( pak->files[j].name, files[i] ))\n\t\t\t{\n\t\t\t\tis_quake = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( is_quake )\n\t\t\tbreak;\n\t}\n\n\tMem_Free( pak );\n\treturn is_quake;\n}\n"
  },
  {
    "path": "filesystem/tests/caseinsensitive.c",
    "content": "#include \"port.h\"\n#include \"build.h\"\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include \"filesystem.h\"\n#if XASH_POSIX\n#include <dlfcn.h>\n#define LoadLibrary( x ) dlopen( x, RTLD_NOW )\n#define GetProcAddress( x, y ) dlsym( x, y )\n#define FreeLibrary( x ) dlclose( x )\n#elif XASH_WIN32\n#include <windows.h>\n#endif\n\nvoid *g_hModule;\nFSAPI g_pfnGetFSAPI;\nfs_api_t g_fs;\nfs_globals_t *g_nullglobals;\n\n\nstatic qboolean LoadFilesystem( void )\n{\n\tg_hModule = LoadLibrary( \"filesystem_stdio.\" OS_LIB_EXT );\n\tif( !g_hModule )\n\t\treturn false;\n\n\tg_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API );\n\tif( !g_pfnGetFSAPI )\n\t\treturn false;\n\n\tif( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic qboolean CheckFileContents( const char *path, const void *buf, fs_offset_t size )\n{\n\tfs_offset_t len;\n\tbyte *data;\n\n\tdata = g_fs.LoadFile( path, &len, true );\n\tif( !data )\n\t{\n\t\tprintf( \"LoadFile fail\\n\" );\n\t\treturn false;\n\t}\n\n\tif( len != size )\n\t{\n\t\tprintf( \"LoadFile sizeof fail\\n\" );\n\t\tfree( data );\n\t\treturn false;\n\t}\n\n\tif( memcmp( data, buf, size ))\n\t{\n\t\tprintf( \"LoadFile magic fail\\n\" );\n\t\tfree( data );\n\t\treturn false;\n\t}\n\n\tfree( data );\n\treturn true;\n}\n\nstatic qboolean TestCaseinsensitive( void )\n{\n\tfile_t *f1;\n\tFILE *f2;\n\tint magic = rand();\n\n\t// create game dir for us\n\tg_fs.AddGameDirectory( \"./\", FS_GAMEDIR_PATH );\n\n\t// create some files first and write data\n\tf1 = g_fs.Open( \"FOO/Bar.bin\", \"wb\", true );\n\tg_fs.Write( f1, &magic, sizeof( magic ));\n\tg_fs.Close( f1 );\n\n\t// try to search it with different file name\n\tif( !g_fs.FileExists( \"fOO/baR.bin\", true ))\n\t{\n\t\tprintf( \"FileExists fail\\n\" );\n\t\treturn false;\n\t}\n\n\t// create a file directly, to check if cache can re-read\n\tf2 = fopen( \"FOO/Baz.bin\", \"wb\" );\n\tfwrite( &magic, sizeof( magic ), 1, f2 );\n\tfclose( f2 );\n\n\t// try to open first file back but with different file name case\n\tif( !CheckFileContents( \"foo/bar.BIN\", &magic, sizeof( magic )))\n\t\treturn false;\n\n\t// try to open second file that we created directly\n\tif( !CheckFileContents( \"Foo/BaZ.Bin\", &magic, sizeof( magic )))\n\t\treturn false;\n\n\tg_fs.Delete( \"foo/Baz.biN\" );\n\tg_fs.Delete( \"foo/bar.bin\" );\n\tg_fs.Delete( \"Foo\" );\n\n\treturn true;\n}\n\nint main( void )\n{\n\tif( !LoadFilesystem() )\n\t\treturn EXIT_FAILURE;\n\n\tsrand( time( NULL ));\n\n\tif( !TestCaseinsensitive())\n\t\treturn EXIT_FAILURE;\n\n\tprintf( \"success\\n\" );\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "filesystem/tests/interface.cpp",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include \"port.h\"\n#include \"build.h\"\n#include \"VFileSystem009.h\"\n#include \"filesystem.h\"\n\n#if XASH_POSIX\n#include <dlfcn.h>\n#define LoadLibrary( x ) dlopen( x, RTLD_NOW )\n#define GetProcAddress( x, y ) dlsym( x, y )\n#define FreeLibrary( x ) dlclose( x )\ntypedef void *HMODULE;\n#elif XASH_WIN32\n#include <windows.h>\n#endif\n\nHMODULE g_hModule;\nFSAPI g_pfnGetFSAPI;\npfnCreateInterface_t g_pfnCreateInterface;\nfs_api_t g_fs;\nfs_globals_t *g_nullglobals;\n\nstatic bool LoadFilesystem()\n{\n\tint temp = -1;\n\n\tg_hModule = LoadLibrary( \"filesystem_stdio.\" OS_LIB_EXT );\n\tif( !g_hModule )\n\t\treturn false;\n\n\t// check our C-style interface existence\n\tg_pfnGetFSAPI = reinterpret_cast<FSAPI>( GetProcAddress( g_hModule, GET_FS_API ));\n\tif( !g_pfnGetFSAPI )\n\t\treturn false;\n\n\tg_nullglobals = NULL;\n\tif( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))\n\t\treturn false;\n\n\tif( !g_nullglobals )\n\t\treturn false;\n\n\t// check Valve-style interface existence\n\tg_pfnCreateInterface = reinterpret_cast<pfnCreateInterface_t>( GetProcAddress( g_hModule, \"CreateInterface\" ));\n\tif( !g_pfnCreateInterface )\n\t\treturn false;\n\n\tif( !g_pfnCreateInterface( FILESYSTEM_INTERFACE_VERSION, &temp ) || temp != 0 )\n\t\treturn false;\n\n\ttemp = -1;\n\n\tif( !g_pfnCreateInterface( FS_API_CREATEINTERFACE_TAG, &temp ) || temp != 0 )\n\t\treturn false;\n\n\treturn true;\n}\n\nint main()\n{\n\tif( !LoadFilesystem() )\n\t\treturn EXIT_FAILURE;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "filesystem/tests/no-init.c",
    "content": "#include \"port.h\"\n#include \"build.h\"\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include \"filesystem.h\"\n#if XASH_POSIX\n#include <dlfcn.h>\n#define LoadLibrary( x ) dlopen( x, RTLD_NOW )\n#define GetProcAddress( x, y ) dlsym( x, y )\n#define FreeLibrary( x ) dlclose( x )\n#elif XASH_WIN32\n#include <windows.h>\n#endif\n\nvoid *g_hModule;\nFSAPI g_pfnGetFSAPI;\nfs_api_t g_fs;\nfs_globals_t *g_nullglobals;\n\n\nstatic qboolean LoadFilesystem( void )\n{\n\tg_hModule = LoadLibrary( \"filesystem_stdio.\" OS_LIB_EXT );\n\tif( !g_hModule )\n\t\treturn false;\n\n\tg_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API );\n\tif( !g_pfnGetFSAPI )\n\t\treturn false;\n\n\tif( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))\n\t\treturn false;\n\n\treturn true;\n}\n\nstatic int TestNoInit( void )\n{\n\tchar nice[69];\n\tfs_dllinfo_t dllinfo;\n\tvoid *p;\n\n\t// here are the APIs that shouldn't fail without calling InitStdio\n\tg_fs.ShutdownStdio();\n\tg_fs.ClearSearchPath();\n\tg_fs.AllowDirectPaths( true );\n\tg_fs.AllowDirectPaths( false );\n\tif( g_fs.Search( \"asfjkajk\", 0, 0 ) != 0 )\n\t\treturn 0;\n\tg_fs.SetCurrentDirectory( \".\" ); // must succeed!\n\tg_fs.FindLibrary( \"kek\", true, &dllinfo );\n\tif( g_fs.Open( \"afbvasvwerf\", \"w+\", true ) != 0 )\n\t\treturn 0;\n\tif( g_fs.LoadFile( \"hehe\", NULL, true ) != 0 )\n\t\treturn 0;\n\tp = g_fs.LoadDirectFile( \"hehe\", NULL );\n\tif( p ) free( p );\n\n\tif( !g_fs.IsArchiveExtensionSupported( \"pk3\", 0 )) return 0;\n\tif( !g_fs.IsArchiveExtensionSupported( \"wad\", 0 )) return 0;\n\tif( !g_fs.IsArchiveExtensionSupported( \"pak\", 0 )) return 0;\n\tif( !g_fs.IsArchiveExtensionSupported( \"pk3dir\", 0 )) return 0;\n\n\tif( !g_fs.IsArchiveExtensionSupported( \"pk3\", IAES_ONLY_REAL_ARCHIVES )) return 0;\n\tif( g_fs.IsArchiveExtensionSupported( \"pk3dir\", IAES_ONLY_REAL_ARCHIVES )) return 0;\n\tif( g_fs.IsArchiveExtensionSupported( \"vpk\", 0)) return 0;\n\n\tg_fs.FileExists( \"asdcv\", 0 );\n\tg_fs.FileTime( \"zxcasdfd\", 0 );\n\tg_fs.FileSize( \"asdqwe\", 1 );\n\tg_fs.Rename( \"cafqefv\", \"zvewfw\" );\n\tg_fs.SysFileExists( \"zbarbgaer\" );\n\tg_fs.GetDiskPath( \"aggasdfbaergba\", 0 );\n\tg_fs.GetFullDiskPath( nice, sizeof( nice ), \"oh my!\", true );\n\tg_fs.Delete( \"blabla\" );\n\n\treturn 1;\n}\n\nint main( void )\n{\n\tif( !LoadFilesystem() )\n\t\treturn EXIT_FAILURE;\n\n\tif( !TestNoInit( ))\n\t\treturn EXIT_FAILURE;\n\n\tFreeLibrary( g_hModule );\n\n\tprintf( \"success\\n\" );\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "filesystem/wad.c",
    "content": "/*\nwad.c - WAD support for filesystem\nCopyright (C) 2003-2006 Mathieu Olivier\nCopyright (C) 2000-2007 DarkPlaces contributors\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2015-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#if XASH_POSIX\n#include <unistd.h>\n#endif\n#include <errno.h>\n#include <stddef.h>\n#include \"port.h\"\n#include \"filesystem_internal.h\"\n#include \"crtlib.h\"\n#include \"common/com_strings.h\"\n#include \"wadfile.h\"\n\n/*\n========================================================================\n.WAD archive format\t(WhereAllData - WAD)\n\nList of compressed files, that can be identify only by TYPE_*\n\n<format>\nheader:\tdwadinfo_t[dwadinfo_t]\nfile_1:\tbyte[dwadinfo_t[num]->disksize]\nfile_2:\tbyte[dwadinfo_t[num]->disksize]\nfile_3:\tbyte[dwadinfo_t[num]->disksize]\n...\nfile_n:\tbyte[dwadinfo_t[num]->disksize]\ninfotable\tdlumpinfo_t[dwadinfo_t->numlumps]\n========================================================================\n*/\n#define HINT_NAMELEN\t5\t// e.g. _mask, _norm\n#define MAX_FILES_IN_WAD\t65535\t// real limit as above <2Gb size not a lumpcount\n\n#include \"const.h\"\n\nstruct wfile_s\n{\n\tint\t\tinfotableofs;\n\tint\t\tnumlumps;\n\tpoolhandle_t mempool;\t\t\t// W_ReadLump temp buffers\n\tfile_t\t\t*handle;\n\tdlumpinfo_t\t*lumps;\n\ttime_t\t\tfiletime;\n};\n\n// WAD errors\n#define WAD_LOAD_OK\t\t\t0\n#define WAD_LOAD_COULDNT_OPEN\t\t1\n#define WAD_LOAD_BAD_HEADER\t\t2\n#define WAD_LOAD_BAD_FOLDERS\t\t3\n#define WAD_LOAD_TOO_MANY_FILES\t4\n#define WAD_LOAD_NO_FILES\t\t5\n#define WAD_LOAD_CORRUPTED\t\t6\n\ntypedef struct wadtype_s\n{\n\tchar        ext[4];\n\tsigned char type;\n} wadtype_t;\n\n// associate extension with wad type\nstatic const wadtype_t wad_types[] =\n{\n{ \"pal\", TYP_PALETTE }, // palette\n{ \"dds\", TYP_DDSTEX  }, // DDS image\n{ \"lmp\", TYP_GFXPIC  }, // quake1, hl pic\n{ \"fnt\", TYP_QFONT   }, // hl qfonts\n{ \"mip\", TYP_MIPTEX  }, // hl/q1 mip\n{ \"txt\", TYP_SCRIPT  }, // scripts\n};\n\n/*\n===========\nW_TypeFromExt\n\nExtracts file type from extension\n===========\n*/\nstatic signed char W_TypeFromExt( const char *lumpname )\n{\n\tconst char *ext = COM_FileExtension( lumpname );\n\tint i;\n\n\t// we not known about filetype, so match only by filename\n\tif( !Q_strcmp( ext, \"*\" ) || !COM_CheckStringEmpty( ext ))\n\t\treturn TYP_ANY;\n\n\tfor( i = 0; i < sizeof( wad_types ) / sizeof( wad_types[0] ); i++ )\n\t{\n\t\tif( !Q_stricmp( ext, wad_types[i].ext ))\n\t\t\treturn wad_types[i].type;\n\t}\n\n\treturn TYP_NONE;\n}\n\n/*\n===========\nW_ExtFromType\n\nConvert type to extension\n===========\n*/\nstatic const char *W_ExtFromType( signed char lumptype )\n{\n\tint i;\n\n\t// we not known aboyt filetype, so match only by filename\n\tif( lumptype == TYP_NONE || lumptype == TYP_ANY )\n\t\treturn \"\";\n\n\tfor( i = 0; i < sizeof( wad_types ) / sizeof( wad_types[0] ); i++ )\n\t{\n\t\tif( lumptype == wad_types[i].type )\n\t\t\treturn wad_types[i].ext;\n\t}\n\n\treturn \"\";\n}\n\n/*\n===========\nW_FindLump\n\nSerach for already existed lump\n===========\n*/\nstatic dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype )\n{\n\tint\tleft, right;\n\n\tif( !wad || !wad->lumps || matchtype == TYP_NONE )\n\t\treturn NULL;\n\n\t// look for the file (binary search)\n\tleft = 0;\n\tright = wad->numlumps - 1;\n\n\twhile( left <= right )\n\t{\n\t\tint\tmiddle = (left + right) / 2;\n\t\tint\tdiff = Q_stricmp( wad->lumps[middle].name, name );\n\n\t\tif( !diff )\n\t\t{\n\t\t\tif(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type ))\n\t\t\t\treturn &wad->lumps[middle]; // found\n\t\t\telse if( wad->lumps[middle].type < matchtype )\n\t\t\t\tdiff = 1;\n\t\t\telse if( wad->lumps[middle].type > matchtype )\n\t\t\t\tdiff = -1;\n\t\t\telse break; // not found\n\t\t}\n\n\t\t// if we're too far in the list\n\t\tif( diff > 0 ) right = middle - 1;\n\t\telse left = middle + 1;\n\t}\n\n\treturn NULL;\n}\n\n/*\n====================\nW_AddFileToWad\n\nAdd a file to the list of files contained into a package\nand sort LAT in alpha-bethical order\n====================\n*/\nstatic dlumpinfo_t *W_AddFileToWad( const char *wadfile, const char *name, wfile_t *wad, dlumpinfo_t *newlump )\n{\n\tint\t\tleft, right;\n\tdlumpinfo_t\t*plump;\n\n\t// look for the slot we should put that file into (binary search)\n\tleft = 0;\n\tright = wad->numlumps - 1;\n\n\twhile( left <= right )\n\t{\n\t\tint\tmiddle = ( left + right ) / 2;\n\t\tint\tdiff = Q_stricmp( wad->lumps[middle].name, name );\n\n\t\tif( !diff )\n\t\t{\n\t\t\tif( wad->lumps[middle].type < newlump->type )\n\t\t\t\tdiff = 1;\n\t\t\telse if( wad->lumps[middle].type > newlump->type )\n\t\t\t\tdiff = -1;\n\t\t\telse Con_Reportf( S_WARN \"Wad %s contains the file %s several times\\n\", wadfile, name );\n\t\t}\n\n\t\t// If we're too far in the list\n\t\tif( diff > 0 ) right = middle - 1;\n\t\telse left = middle + 1;\n\t}\n\n\t// we have to move the right of the list by one slot to free the one we need\n\tplump = &wad->lumps[left];\n\tmemmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump ));\n\twad->numlumps++;\n\n\t*plump = *newlump;\n\tmemcpy( plump->name, name, sizeof( plump->name ));\n\n\treturn plump;\n}\n\n/*\n===========\nFS_CloseWAD\n\nfinalize wad or just close\n===========\n*/\nstatic void FS_CloseWAD( wfile_t *wad )\n{\n\tMem_FreePool( &wad->mempool );\n\tif( wad->handle != NULL )\n\t\tFS_Close( wad->handle );\n\tMem_Free( wad ); // free himself\n}\n\n/*\n===========\nFS_Close_WAD\n===========\n*/\nstatic void FS_Close_WAD( searchpath_t *search )\n{\n\tFS_CloseWAD( search->wad );\n}\n\n/*\n===========\nFS_OpenFile_WAD\n===========\n*/\nstatic file_t *FS_OpenFile_WAD( searchpath_t *search, const char *filename, const char *mode, int pack_ind )\n{\n\treturn NULL;\n}\n\n/*\n===========\nW_Open\n\nopen the wad for reading & writing\n===========\n*/\nstatic wfile_t *W_Open( const char *filename, int *error, uint flags )\n{\n\twfile_t\t\t*wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t ));\n\tint\t\ti, lumpcount;\n\tdlumpinfo_t\t*srclumps;\n\tsize_t\t\tlat_size;\n\tdwadinfo_t\theader;\n\n\tif( FBitSet( flags, FS_LOAD_PACKED_WAD ))\n\t{\n\t\tconst char *basename = COM_FileWithoutPath( filename );\n\t\twad->handle = FS_Open( basename, \"rb\", false );\n\t}\n\telse\n\t{\n\t\twad->handle = FS_SysOpen( filename, \"rb\" );\n\t}\n\n\tif( wad->handle == NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: couldn't open %s: %s\\n\", __func__, filename, strerror( errno ));\n\t\tif( error ) *error = WAD_LOAD_COULDNT_OPEN;\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\n\t// copy wad name\n\twad->filetime = FS_SysFileTime( filename );\n\twad->mempool = Mem_AllocPool( filename );\n\n\tif( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s can't read header\\n\", __func__, filename );\n\t\tif( error ) *error = WAD_LOAD_BAD_HEADER;\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\n\tif( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s is not a WAD2 or WAD3 file\\n\", __func__, filename );\n\t\tif( error ) *error = WAD_LOAD_BAD_HEADER;\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\n\tlumpcount = header.numlumps;\n\n\tif( lumpcount >= MAX_FILES_IN_WAD )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: %s is full (%i lumps)\\n\", __func__, filename, lumpcount );\n\t\tif( error ) *error = WAD_LOAD_TOO_MANY_FILES;\n\t}\n\telse if( lumpcount <= 0 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s has no lumps\\n\", __func__, filename );\n\t\tif( error ) *error = WAD_LOAD_NO_FILES;\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\telse if( error ) *error = WAD_LOAD_OK;\n\n\twad->infotableofs = header.infotableofs; // save infotableofs position\n\n\tif( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s can't find lump allocation table\\n\", __func__, filename );\n\t\tif( error ) *error = WAD_LOAD_BAD_FOLDERS;\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\n\tlat_size = lumpcount * sizeof( dlumpinfo_t );\n\n\t// NOTE: lumps table can be reallocated for O_APPEND mode\n\tsrclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size );\n\n\tif( FS_Read( wad->handle, srclumps, lat_size ) != lat_size )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s has corrupted lump allocation table\\n\", __func__, filename );\n\t\tif( error ) *error = WAD_LOAD_CORRUPTED;\n\t\tMem_Free( srclumps );\n\t\tFS_CloseWAD( wad );\n\t\treturn NULL;\n\t}\n\n\t// starting to add lumps\n\twad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size );\n\twad->numlumps = 0;\n\n\t// sort lumps for binary search\n\tfor( i = 0; i < lumpcount; i++ )\n\t{\n\t\tchar\tname[16];\n\t\tint\tk;\n\n\t\t// cleanup lumpname\n\t\tQ_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name ));\n\n\t\t// check for '*' symbol issues (quake1)\n\t\tk = Q_strlen( Q_strrchr( name, '*' ));\n\t\tif( k ) name[Q_strlen( name ) - k] = '!';\n\n\t\t// check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic)\n\t\tif( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, \"conchars\" ))\n\t\t\tsrclumps[i].type = TYP_GFXPIC;\n\n\t\tW_AddFileToWad( filename, name, wad, &srclumps[i] );\n\t}\n\n\t// release source lumps\n\tMem_Free( srclumps );\n\n\t// and leave the file open\n\treturn wad;\n}\n\n/*\n===========\nFS_FileTime_WAD\n\n===========\n*/\nstatic int FS_FileTime_WAD( searchpath_t *search, const char *filename )\n{\n\treturn search->wad->filetime;\n}\n\n/*\n===========\nFS_PrintInfo_WAD\n\n===========\n*/\nstatic void FS_PrintInfo_WAD( searchpath_t *search, char *dst, size_t size )\n{\n\tif( search->wad->handle->searchpath )\n\t\tQ_snprintf( dst, size, \"%s (%i files)\" S_CYAN \" from %s\" S_DEFAULT, search->filename, search->wad->numlumps, search->wad->handle->searchpath->filename );\n\telse Q_snprintf( dst, size, \"%s (%i files)\", search->filename, search->wad->numlumps );\n}\n\n/*\n===========\nFS_FindFile_WAD\n\n===========\n*/\nstatic int FS_FindFile_WAD( searchpath_t *search, const char *path, char *fixedname, size_t len )\n{\n\tdlumpinfo_t\t*lump;\n\tsigned char\t\ttype = W_TypeFromExt( path );\n\tqboolean\t\tanywadname = true;\n\tstring\t\twadname;\n\tstring\t\tshortname;\n\n\t// quick reject by filetype\n\tif( type == TYP_NONE )\n\t\treturn -1;\n\n\tCOM_ExtractFilePath( path, wadname );\n\n\tif( COM_CheckStringEmpty( wadname ))\n\t{\n\t\tstring wadbasename;\n\n\t\tCOM_FileBase( wadname, wadbasename, sizeof( wadbasename ));\n\t\tQ_snprintf( wadname, sizeof( wadname ), \"%s.wad\", wadbasename );\n\t\tanywadname = false;\n\t}\n\n\t// make wadname from wad fullpath\n\tCOM_FileBase( search->filename, shortname, sizeof( shortname ));\n\tCOM_DefaultExtension( shortname, \".wad\", sizeof( shortname ));\n\n\t// quick reject by wadname\n\tif( !anywadname && Q_stricmp( wadname, shortname ))\n\t\treturn -1;\n\n\t// NOTE: we can't using long names for wad,\n\t// because we using original wad names[16];\n\tCOM_FileBase( path, shortname, sizeof( shortname ));\n\n\tlump = W_FindLump( search->wad, shortname, type );\n\n\tif( lump )\n\t{\n\t\tif( fixedname )\n\t\t\tQ_strncpy( fixedname, lump->name, len );\n\t\treturn lump - search->wad->lumps;\n\t}\n\n\treturn -1;\n}\n\n/*\n===========\nFS_Search_WAD\n\n===========\n*/\nstatic void FS_Search_WAD( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )\n{\n\tstring\twadpattern, wadname, temp2;\n\tsigned char\ttype = W_TypeFromExt( pattern );\n\tqboolean\tanywadname = true;\n\tstring\twadfolder, temp;\n\tint j, i;\n\tconst char *slash, *backslash, *colon, *separator;\n\tchar buf[MAX_VA_STRING];\n\n\t// quick reject by filetype\n\tif( type == TYP_NONE )\n\t\treturn;\n\n\tCOM_ExtractFilePath( pattern, wadname );\n\tCOM_FileBase( pattern, wadpattern, sizeof( wadpattern ));\n\twadfolder[0] = '\\0';\n\n\tif( COM_CheckStringEmpty( wadname ))\n\t{\n\t\tstring wadbasename;\n\n\t\tCOM_FileBase( wadname, wadbasename, sizeof( wadbasename ));\n\n\t\tQ_strncpy( wadfolder, wadbasename, sizeof( wadfolder ));\n\t\tQ_snprintf( wadname, sizeof( wadname ), \"%s.wad\", wadbasename );\n\t\tanywadname = false;\n\t}\n\n\t// make wadname from wad fullpath\n\tCOM_FileBase( search->filename, temp2, sizeof( temp2 ));\n\tCOM_DefaultExtension( temp2, \".wad\", sizeof( temp2 ));\n\n\t// quick reject by wadname\n\tif( !anywadname && Q_stricmp( wadname, temp2 ))\n\t\treturn;\n\n\tfor( i = 0; i < search->wad->numlumps; i++ )\n\t{\n\t\t// if type not matching, we already have no chance ...\n\t\tif( type != TYP_ANY && search->wad->lumps[i].type != type )\n\t\t\tcontinue;\n\n\t\t// build the lumpname with image suffix (if present)\n\t\tQ_strncpy( temp, search->wad->lumps[i].name, sizeof( temp ));\n\n\t\twhile( temp[0] )\n\t\t{\n\t\t\tif( matchpattern( temp, wadpattern, true ))\n\t\t\t{\n\t\t\t\tfor( j = 0; j < list->numstrings; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( !Q_strcmp( list->strings[j], temp ))\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif( j == list->numstrings )\n\t\t\t\t{\n\t\t\t\t\t// build path: wadname/lumpname.ext\n\t\t\t\t\tQ_snprintf( temp2, sizeof( temp2 ), \"%s/%s\", wadfolder, temp );\n\t\t\t\t\tQ_snprintf( buf, sizeof( buf ), \".%s\", W_ExtFromType( search->wad->lumps[i].type ));\n\t\t\t\t\tCOM_DefaultExtension( temp2, buf, sizeof( temp2 ));\n\t\t\t\t\tstringlistappend( list, temp2 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// strip off one path element at a time until empty\n\t\t\t// this way directories are added to the listing if they match the pattern\n\t\t\tslash = Q_strrchr( temp, '/' );\n\t\t\tbackslash = Q_strrchr( temp, '\\\\' );\n\t\t\tcolon = Q_strrchr( temp, ':' );\n\t\t\tseparator = temp;\n\t\t\tif( separator < slash )\n\t\t\t\tseparator = slash;\n\t\t\tif( separator < backslash )\n\t\t\t\tseparator = backslash;\n\t\t\tif( separator < colon )\n\t\t\t\tseparator = colon;\n\t\t\t*((char *)separator) = 0;\n\t\t}\n\t}\n}\n\n\n/*\n===========\nW_ReadLump\n\nreading lump into temp buffer\n===========\n*/\nstatic byte *W_ReadLump( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *lumpsizeptr, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * ))\n{\n\tconst wfile_t *wad = search->wad;\n\tconst dlumpinfo_t *lump = &wad->lumps[pack_ind];\n\tsize_t\toldpos, size = 0;\n\tbyte\t*buf;\n\n\t// assume error\n\tif( lumpsizeptr ) *lumpsizeptr = 0;\n\n\t// no wads loaded\n\tif( !wad || !lump ) return NULL;\n\n\toldpos = FS_Tell( wad->handle ); // don't forget restore original position\n\n\tif( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s is corrupted\\n\", __func__, lump->name );\n\t\tFS_Seek( wad->handle, oldpos, SEEK_SET );\n\t\treturn NULL;\n\t}\n\n\tbuf = (byte *)pfnAlloc( lump->disksize );\n\tif( unlikely( !buf ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: can't alloc %d bytes, no free memory\\n\", __func__, lump->disksize );\n\t\tFS_Seek( wad->handle, oldpos, SEEK_SET );\n\t\treturn NULL;\n\t}\n\n\tsize = FS_Read( wad->handle, buf, lump->disksize );\n\tFS_Seek( wad->handle, oldpos, SEEK_SET );\n\n\tif( size < lump->disksize )\n\t{\n\t\tCon_Reportf( S_WARN \"%s: %s is probably corrupted\\n\", __func__, lump->name );\n\t\tpfnFree( buf );\n\t\treturn NULL;\n\t}\n\n\tif( lumpsizeptr ) *lumpsizeptr = lump->disksize;\n\n\treturn buf;\n}\n\n/*\n====================\nFS_AddWad_Fullpath\n====================\n*/\nsearchpath_t *FS_AddWad_Fullpath( const char *wadfile, int flags )\n{\n\tsearchpath_t *search;\n\twfile_t *wad;\n\tint errorcode = WAD_LOAD_COULDNT_OPEN;\n\n\twad = W_Open( wadfile, &errorcode, flags );\n\n\tif( !wad )\n\t{\n\t\tif( errorcode != WAD_LOAD_NO_FILES )\n\t\t\tCon_Reportf( S_ERROR \"%s: unable to load wad \\\"%s\\\"\\n\", __func__, wadfile );\n\t\treturn NULL;\n\t}\n\n\tsearch = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ));\n\tQ_strncpy( search->filename, wadfile, sizeof( search->filename ));\n\tsearch->wad = wad;\n\tsearch->type = SEARCHPATH_WAD;\n\tsearch->flags = flags;\n\n\tsearch->pfnPrintInfo = FS_PrintInfo_WAD;\n\tsearch->pfnClose = FS_Close_WAD;\n\tsearch->pfnOpenFile = FS_OpenFile_WAD;\n\tsearch->pfnFileTime = FS_FileTime_WAD;\n\tsearch->pfnFindFile = FS_FindFile_WAD;\n\tsearch->pfnSearch = FS_Search_WAD;\n\tsearch->pfnLoadFile = W_ReadLump;\n\n\tCon_Reportf( \"Adding WAD: %s (%i files)\\n\", wadfile, wad->numlumps );\n\treturn search;\n}\n"
  },
  {
    "path": "filesystem/wscript",
    "content": "#!/usr/bin/env python\n\nMEMFD_CREATE_TEST = '''#define _GNU_SOURCE\n#include <sys/mman.h>\nint main(int argc, char **argv) { return memfd_create(argv[0], 0); }'''\n\nDIRENT_D_TYPE_TEST = '''#define _GNU_SOURCE\n#include <dirent.h>\nint main(int argc, char **argv) { struct dirent entry; entry.d_type = DT_DIR; return 0; }\n'''\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif conf.env.COMPILER_CXX != 'msvc':\n\t\tconf.env.append_unique('CXXFLAGS', ['-fno-exceptions'])\n\n\tif conf.env.DEST_OS == 'android':\n\t\tconf.check_cc(lib='android')\n\n\t# remove lib prefix, except on Android which has issues unpacking libraries without prefix when debuggable=false\n\tif conf.env.DEST_OS != 'android':\n\t\tif conf.env.cxxshlib_PATTERN.startswith('lib'):\n\t\t\tconf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:]\n\n\tif conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):\n\t\tconf.define('HAVE_MEMFD_CREATE', 1)\n\n\tif conf.check_cc(fragment=DIRENT_D_TYPE_TEST, msg='Checking for d_type field in struct dirent', mandatory=False):\n\t\tconf.define('HAVE_DIRENT_D_TYPE', 1)\n\ndef build(bld):\n\tbld(name = 'filesystem_includes', export_includes = '.')\n\n\tlibs = [ 'filesystem_includes', 'sdk_includes', 'werror' ]\n\n\t# on PSVita do not link any libraries that are already in the main executable, but add the includes target\n\tif bld.env.DEST_OS != 'psvita':\n\t\tlibs += [ 'public', 'ANDROID' ]\n\n\tbld.shlib(target = 'filesystem_stdio',\n\t\tfeatures = 'seq',\n\t\tsource = bld.path.ant_glob(['*.c', '*.cpp']),\n\t\tuse = libs,\n\t\tinstall_path = bld.env.LIBDIR)\n\n\tif bld.env.TESTS:\n\t\t# build in same module, so dynamic linking will work\n\t\t# for now (until we turn libpublic to shared module lol)\n\t\ttests = {\n\t\t\t'interface' : 'tests/interface.cpp',\n\t\t\t'caseinsensitive' : 'tests/caseinsensitive.c',\n\t\t\t'no-init': 'tests/no-init.c'\n\t\t}\n\n\t\tfor i in tests:\n\t\t\tbld.program(features = 'test seq',\n\t\t\t\tsource = tests[i],\n\t\t\t\ttarget = 'test_%s' % i,\n\t\t\t\tuse = libs + ['DL'],\n\t\t\t\trpath = bld.env.DEFAULT_RPATH,\n\t\t\t\tinstall_path = None)\n"
  },
  {
    "path": "filesystem/zip.c",
    "content": "/*\nzip.c - ZIP support for filesystem\nCopyright (C) 2019 Mr0maks\nCopyright (C) 2019-2023 Xash3D FWGS contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#if XASH_POSIX\n#include <unistd.h>\n#endif\n#include <errno.h>\n#include <stddef.h>\n#include STDINT_H\n#include \"port.h\"\n#include \"filesystem_internal.h\"\n#include \"crtlib.h\"\n#include \"common/com_strings.h\"\n\n#define ZIP_HEADER_LF      (('K'<<8)+('P')+(0x03<<16)+(0x04<<24))\n#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P')\n\n#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P')\n#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P')\n\n#define ZIP_COMPRESSION_NO_COMPRESSION\t    0\n#define ZIP_COMPRESSION_DEFLATED\t    8\n\n#define ZIP_ZIP64 0xffffffff\n\n#pragma pack( push, 1 )\ntypedef struct zip_header_s\n{\n\tuint32_t\tsignature; // little endian ZIP_HEADER\n\tuint16_t\tversion; // version of pkzip need to unpack\n\tuint16_t\tflags; // flags (16 bits == 16 flags)\n\tuint16_t\tcompression_flags; // compression flags (bits)\n\tuint32_t\tdos_date; // file modification time and file modification date\n\tuint32_t\tcrc32; //crc32\n\tuint32_t\tcompressed_size;\n\tuint32_t\tuncompressed_size;\n\tuint16_t\tfilename_len;\n\tuint16_t\textrafield_len;\n} zip_header_t;\n\n/*\n  in zip64 comp and uncompr size == 0xffffffff remeber this\n  compressed and uncompress filesize stored in extra field\n*/\n\ntypedef struct zip_header_extra_s\n{\n\tuint32_t\tsignature; // ZIP_HEADER_SPANNED\n\tuint32_t\tcrc32;\n\tuint32_t\tcompressed_size;\n\tuint32_t\tuncompressed_size;\n} zip_header_extra_t;\n\ntypedef struct zip_cdf_header_s\n{\n\tuint32_t\tsignature;\n\tuint16_t\tversion;\n\tuint16_t\tversion_need;\n\tuint16_t\tgeneralPurposeBitFlag;\n\tuint16_t\tflags;\n\tuint16_t\tmodification_time;\n\tuint16_t\tmodification_date;\n\tuint32_t\tcrc32;\n\tuint32_t\tcompressed_size;\n\tuint32_t\tuncompressed_size;\n\tuint16_t\tfilename_len;\n\tuint16_t\textrafield_len;\n\tuint16_t\tfile_commentary_len;\n\tuint16_t\tdisk_start;\n\tuint16_t\tinternal_attr;\n\tuint32_t\texternal_attr;\n\tuint32_t\tlocal_header_offset;\n} zip_cdf_header_t;\n\ntypedef struct zip_header_eocd_s\n{\n\tuint16_t\tdisk_number;\n\tuint16_t\tstart_disk_number;\n\tuint16_t\tnumber_central_directory_record;\n\tuint16_t\ttotal_central_directory_record;\n\tuint32_t\tsize_of_central_directory;\n\tuint32_t\tcentral_directory_offset;\n\tuint16_t\tcommentary_len;\n} zip_header_eocd_t;\n#pragma pack( pop )\n\n// ZIP errors\nenum\n{\n\tZIP_LOAD_OK = 0,\n\tZIP_LOAD_COULDNT_OPEN,\n\tZIP_LOAD_BAD_HEADER,\n\tZIP_LOAD_BAD_FOLDERS,\n\tZIP_LOAD_NO_FILES,\n\tZIP_LOAD_CORRUPTED\n};\n\ntypedef struct zipfile_s\n{\n\tchar\t\tname[MAX_SYSPATH];\n\tfs_offset_t\toffset; // offset of local file header\n\tfs_offset_t\tsize; //original file size\n\tfs_offset_t\tcompressed_size; // compressed file size\n\tuint16_t flags;\n} zipfile_t;\n\nstruct zip_s\n{\n\tfile_t *handle;\n\tint\t\tnumfiles;\n\tzipfile_t files[]; // flexible\n};\n\n// #define ENABLE_CRC_CHECK // known to be buggy because of possible libpublic crc32 bug, disabled\n\n/*\n============\nFS_CloseZIP\n============\n*/\nstatic void FS_CloseZIP( zip_t *zip )\n{\n\tif( zip->handle != NULL )\n\t\tFS_Close( zip->handle );\n\n\tMem_Free( zip );\n}\n\n/*\n============\nFS_Close_ZIP\n============\n*/\nstatic void FS_Close_ZIP( searchpath_t *search )\n{\n\tFS_CloseZIP( search->zip );\n}\n\n/*\n============\nFS_SortZip\n============\n*/\nstatic int FS_SortZip( const void *a, const void *b )\n{\n\treturn Q_stricmp(((zipfile_t *)a )->name, ((zipfile_t *)b )->name );\n}\n\n/*\n============\nFS_LoadZip\n============\n*/\nstatic zip_t *FS_LoadZip( const char *zipfile, int *error )\n{\n\tint\t\t  numpackfiles = 0, i;\n\tzip_cdf_header_t  header_cdf;\n\tzip_header_eocd_t header_eocd;\n\tuint32_t          signature;\n\tfs_offset_t\t  filepos = 0;\n\tzipfile_t\t  *info = NULL;\n\tchar\t\t  filename_buffer[MAX_SYSPATH];\n\tzip_t         *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip ));\n\tfs_size_t       c;\n\n\t// TODO: use FS_Open to allow PK3 to be included into other archives\n\t// Currently, it doesn't work with rodir due to FS_FindFile logic\n\tzip->handle = FS_SysOpen( zipfile, \"rb\" );\n\n\tif( zip->handle == NULL )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s couldn't open\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_COULDNT_OPEN;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\tif( zip->handle->real_length > UINT32_MAX )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s bigger than 4GB.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_COULDNT_OPEN;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\tFS_Seek( zip->handle, 0, SEEK_SET );\n\n\tc = FS_Read( zip->handle, &signature, sizeof( signature ));\n\n\tif( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD )\n\t{\n\t\tCon_Reportf( S_WARN \"%s has no files. Ignored.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_NO_FILES;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\tif( signature != ZIP_HEADER_LF )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s is not a zip file. Ignored.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_BAD_HEADER;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\t// Find oecd\n\tfilepos = zip->handle->real_length;\n\n\twhile( filepos > 0 )\n\t{\n\t\tFS_Seek( zip->handle, filepos, SEEK_SET );\n\t\tc = FS_Read( zip->handle, &signature, sizeof( signature ));\n\n\t\tif( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD )\n\t\t\tbreak;\n\n\t\tfilepos -= sizeof( char ); // step back one byte\n\t}\n\n\tif( ZIP_HEADER_EOCD != signature )\n\t{\n\t\tCon_Reportf( S_ERROR \"cannot find EOCD in %s. Zip file corrupted.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_BAD_HEADER;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\tc = FS_Read( zip->handle, &header_eocd, sizeof( header_eocd ));\n\n\tif( c != sizeof( header_eocd ))\n\t{\n\t\tCon_Reportf( S_ERROR \"invalid EOCD header in %s. Zip file corrupted.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_BAD_HEADER;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\tif( header_eocd.total_central_directory_record == 0 ) // refuse to load empty ZIP archives\n\t{\n\t\tCon_Reportf( S_WARN \"%s has no files (total records is zero). Ignored.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_NO_FILES;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\t// Move to CDF start\n\tFS_Seek( zip->handle, header_eocd.central_directory_offset, SEEK_SET );\n\n\t// Calc count of files in archive\n\tzip = (zip_t *)Mem_Realloc( fs_mempool, zip, sizeof( *zip ) + sizeof( *info ) * header_eocd.total_central_directory_record );\n\tinfo = zip->files;\n\n\tfor( i = 0; i < header_eocd.total_central_directory_record; i++ )\n\t{\n\t\tc = FS_Read( zip->handle, &header_cdf, sizeof( header_cdf ));\n\n\t\tif( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"CDF signature mismatch in %s. Zip file corrupted.\\n\", zipfile );\n\n\t\t\tif( error )\n\t\t\t\t*error = ZIP_LOAD_BAD_HEADER;\n\n\t\t\tFS_CloseZIP( zip );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif( header_cdf.uncompressed_size && header_cdf.filename_len && header_cdf.filename_len < sizeof( filename_buffer ))\n\t\t{\n\t\t\tmemset( &filename_buffer, '\\0', sizeof( filename_buffer ));\n\t\t\tc = FS_Read( zip->handle, &filename_buffer, header_cdf.filename_len );\n\n\t\t\tif( c != header_cdf.filename_len )\n\t\t\t{\n\t\t\t\tCon_Reportf( S_ERROR \"filename length mismatch in %s. Zip file corrupted.\\n\", zipfile );\n\n\t\t\t\tif( error )\n\t\t\t\t\t*error = ZIP_LOAD_CORRUPTED;\n\n\t\t\t\tFS_CloseZIP( zip );\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\tQ_strncpy( info[numpackfiles].name, filename_buffer, sizeof( info[numpackfiles].name ));\n\t\t\tinfo[numpackfiles].size = header_cdf.uncompressed_size;\n\t\t\tinfo[numpackfiles].compressed_size = header_cdf.compressed_size;\n\t\t\tinfo[numpackfiles].offset = header_cdf.local_header_offset;\n\t\t\tnumpackfiles++;\n\t\t}\n\t\telse\n\t\t\tFS_Seek( zip->handle, header_cdf.filename_len, SEEK_CUR );\n\n\t\tif( header_cdf.extrafield_len )\n\t\t\tFS_Seek( zip->handle, header_cdf.extrafield_len, SEEK_CUR );\n\n\t\tif( header_cdf.file_commentary_len )\n\t\t\tFS_Seek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR );\n\t}\n\n\t// refuse to load empty files again\n\tif( numpackfiles == 0 )\n\t{\n\t\tCon_Reportf( S_WARN \"%s has no files (recalculated). Ignored.\\n\", zipfile );\n\n\t\tif( error )\n\t\t\t*error = ZIP_LOAD_NO_FILES;\n\n\t\tFS_CloseZIP( zip );\n\t\treturn NULL;\n\t}\n\n\t// recalculate offsets\n\tfor( i = 0; i < numpackfiles; i++ )\n\t{\n\t\tzip_header_t header;\n\n\t\tFS_Seek( zip->handle, info[i].offset, SEEK_SET );\n\t\tc = FS_Read( zip->handle, &header, sizeof( header ) );\n\n\t\tif( c != sizeof( header ))\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"header length mismatch in %s. Zip file corrupted.\\n\", zipfile );\n\n\t\t\tif( error )\n\t\t\t\t*error = ZIP_LOAD_CORRUPTED;\n\n\t\t\tFS_CloseZIP( zip );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tinfo[i].flags = header.compression_flags;\n\t\tinfo[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header );\n\t}\n\n\tzip->numfiles = numpackfiles;\n\tqsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip );\n\n\tif( error )\n\t\t*error = ZIP_LOAD_OK;\n\n\treturn zip;\n}\n\n/*\n===========\nFS_OpenZipFile\n\nOpen a packed file using its package file descriptor\n===========\n*/\nstatic file_t *FS_OpenFile_ZIP( searchpath_t *search, const char *filename, const char *mode, int pack_ind )\n{\n\tzipfile_t *pfile = &search->zip->files[pack_ind];\n\tfile_t *f = FS_OpenHandle( search, search->zip->handle->handle, pfile->offset, pfile->size );\n\n\tif( !f )\n\t\treturn NULL;\n\n\tif( pfile->flags == ZIP_COMPRESSION_DEFLATED )\n\t{\n\t\tztoolkit_t *ztk;\n\n\t\tSetBits( f->flags, FILE_DEFLATED );\n\n\t\tztk = Mem_Calloc( fs_mempool, sizeof( *ztk ));\n\t\tztk->comp_length = pfile->compressed_size;\n\t\tztk->zstream.next_in = ztk->input;\n\t\tztk->zstream.avail_in = 0;\n\n\t\tif( inflateInit2( &ztk->zstream, -MAX_WBITS ) != Z_OK )\n\t\t{\n\t\t\tCon_Printf( \"%s: inflate init error (file: %s)\\n\", __func__, filename );\n\t\t\tFS_Close( f );\n\t\t\tMem_Free( ztk );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tztk->zstream.next_out = f->buff;\n\t\tztk->zstream.avail_out = sizeof( f->buff );\n\n\t\tf->ztk = ztk;\n\t}\n\telse if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s: file compressed with unknown algorithm.\\n\", __func__, filename );\n\t\tFS_Close( f );\n\t\treturn NULL;\n\t}\n\n\treturn f;\n}\n\n/*\n===========\nFS_LoadZIPFile\n\n===========\n*/\nstatic byte *FS_LoadZIPFile( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *sizeptr, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * ))\n{\n\tzipfile_t *file;\n\tbyte\t\t*compressed_buffer = NULL, *decompressed_buffer = NULL;\n\tint\t\tzlib_result = 0;\n\tz_stream\tdecompress_stream;\n\tsize_t      c;\n#ifdef ENABLE_CRC_CHECK\n\tdword\t\ttest_crc, final_crc;\n#endif // ENABLE_CRC_CHECK\n\n\tif( sizeptr ) *sizeptr = 0;\n\n\tfile = &search->zip->files[pack_ind];\n\n\tif( FS_Seek( search->zip->handle, file->offset, SEEK_SET ) == -1 )\n\t\treturn NULL;\n\n\t/*if( FS_Read( search->zip->handle, &header, sizeof( header )) < 0 )\n\t\treturn NULL;\n\n\tif( header.signature != ZIP_HEADER_LF )\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s signature error\\n\", __func__, file->name );\n\t\treturn NULL;\n\t}*/\n\n\tdecompressed_buffer = (byte *)pfnAlloc( file->size + 1 );\n\tif( unlikely( !decompressed_buffer ))\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: can't alloc %li bytes, no free memory\\n\", __func__, (long)file->size + 1 );\n\t\treturn NULL;\n\t}\n\tdecompressed_buffer[file->size] = '\\0';\n\n\tif( file->flags == ZIP_COMPRESSION_NO_COMPRESSION )\n\t{\n\t\tc = FS_Read( search->zip->handle, decompressed_buffer, file->size );\n\t\tif( c != file->size )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: %s size doesn't match\\n\", __func__, file->name );\n\t\t\treturn NULL;\n\t\t}\n\n#ifdef ENABLE_CRC_CHECK\n\t\tCRC32_Init( &test_crc );\n\t\tCRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size );\n\n\t\tfinal_crc = CRC32_Final( test_crc );\n\n\t\tif( final_crc != file->crc32 )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: %s file crc32 mismatch\\n\", __func__, file->name );\n\t\t\tpfnFree( decompressed_buffer );\n\t\t\treturn NULL;\n\t\t}\n#endif // ENABLE_CRC_CHECK\n\n\t\tif( sizeptr ) *sizeptr = file->size;\n\n\t\treturn decompressed_buffer;\n\t}\n\telse if( file->flags == ZIP_COMPRESSION_DEFLATED )\n\t{\n\t\tcompressed_buffer = (byte *)Mem_Malloc( fs_mempool, file->compressed_size + 1 );\n\n\t\tc = FS_Read( search->zip->handle, compressed_buffer, file->compressed_size );\n\t\tif( c != file->compressed_size )\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: %s compressed size doesn't match\\n\", __func__, file->name );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tmemset( &decompress_stream, 0, sizeof( decompress_stream ) );\n\n\t\tdecompress_stream.total_in = decompress_stream.avail_in = file->compressed_size;\n\t\tdecompress_stream.next_in = (Bytef *)compressed_buffer;\n\t\tdecompress_stream.total_out = decompress_stream.avail_out = file->size;\n\t\tdecompress_stream.next_out = (Bytef *)decompressed_buffer;\n\n\t\tdecompress_stream.zalloc = Z_NULL;\n\t\tdecompress_stream.zfree = Z_NULL;\n\t\tdecompress_stream.opaque = Z_NULL;\n\n\t\tif( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK )\n\t\t{\n\t\t\tCon_Printf( S_ERROR \"%s: inflateInit2 failed\\n\", __func__ );\n\t\t\tMem_Free( compressed_buffer );\n\t\t\tMem_Free( decompressed_buffer );\n\t\t\treturn NULL;\n\t\t}\n\n\t\tzlib_result = inflate( &decompress_stream, Z_NO_FLUSH );\n\t\tinflateEnd( &decompress_stream );\n\n\t\tif( zlib_result == Z_OK || zlib_result == Z_STREAM_END )\n\t\t{\n\t\t\tMem_Free( compressed_buffer ); // finaly free compressed buffer\n#if ENABLE_CRC_CHECK\n\t\t\tCRC32_Init( &test_crc );\n\t\t\tCRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size );\n\n\t\t\tfinal_crc = CRC32_Final( test_crc );\n\n\t\t\tif( final_crc != file->crc32 )\n\t\t\t{\n\t\t\t\tCon_Reportf( S_ERROR \"%s: %s file crc32 mismatch\\n\", __func__, file->name );\n\t\t\t\tpfnFree( decompressed_buffer );\n\t\t\t\treturn NULL;\n\t\t\t}\n#endif\n\t\t\tif( sizeptr ) *sizeptr = file->size;\n\n\t\t\treturn decompressed_buffer;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tCon_Reportf( S_ERROR \"%s: %s: error while file decompressing. Zlib return code %d.\\n\", __func__, file->name, zlib_result );\n\t\t\tMem_Free( compressed_buffer );\n\t\t\tpfnFree( decompressed_buffer );\n\t\t\treturn NULL;\n\t\t}\n\n\t}\n\telse\n\t{\n\t\tCon_Reportf( S_ERROR \"%s: %s: file compressed with unknown algorithm.\\n\", __func__, file->name );\n\t\tpfnFree( decompressed_buffer );\n\t\treturn NULL;\n\t}\n\n\treturn NULL;\n}\n\n/*\n===========\nFS_FileTime_ZIP\n\n===========\n*/\nstatic int FS_FileTime_ZIP( searchpath_t *search, const char *filename )\n{\n\treturn search->zip->handle->filetime;\n}\n\n/*\n===========\nFS_PrintInfo_ZIP\n\n===========\n*/\nstatic void FS_PrintInfo_ZIP( searchpath_t *search, char *dst, size_t size )\n{\n\tif( search->zip->handle->searchpath )\n\t\tQ_snprintf( dst, size, \"%s (%i files)\" S_CYAN \" from %s\" S_DEFAULT, search->filename, search->zip->numfiles, search->zip->handle->searchpath->filename );\n\telse Q_snprintf( dst, size, \"%s (%i files)\", search->filename, search->zip->numfiles );\n}\n\n/*\n===========\nFS_FindFile_ZIP\n\n===========\n*/\nstatic int FS_FindFile_ZIP( searchpath_t *search, const char *path, char *fixedname, size_t len )\n{\n\tint\tleft, right, middle;\n\n\t// look for the file (binary search)\n\tleft = 0;\n\tright = search->zip->numfiles - 1;\n\twhile( left <= right )\n\t{\n\t\tint\tdiff;\n\n\t\tmiddle = (left + right) / 2;\n\t\tdiff = Q_stricmp( search->zip->files[middle].name, path );\n\n\t\t// Found it\n\t\tif( !diff )\n\t\t{\n\t\t\tif( fixedname )\n\t\t\t\tQ_strncpy( fixedname, search->zip->files[middle].name, len );\n\t\t\treturn middle;\n\t\t}\n\n\t\t// if we're too far in the list\n\t\tif( diff > 0 )\n\t\t\tright = middle - 1;\n\t\telse left = middle + 1;\n\t}\n\n\treturn -1;\n}\n\n/*\n===========\nFS_Search_ZIP\n\n===========\n*/\nstatic void FS_Search_ZIP( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive )\n{\n\tstring temp;\n\tconst char *slash, *backslash, *colon, *separator;\n\tint j, i;\n\n\tfor( i = 0; i < search->zip->numfiles; i++ )\n\t{\n\t\tQ_strncpy( temp, search->zip->files[i].name, sizeof( temp ));\n\t\twhile( temp[0] )\n\t\t{\n\t\t\tif( matchpattern( temp, pattern, true ))\n\t\t\t{\n\t\t\t\tfor( j = 0; j < list->numstrings; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( !Q_strcmp( list->strings[j], temp ))\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif( j == list->numstrings )\n\t\t\t\t\tstringlistappend( list, temp );\n\t\t\t}\n\n\t\t\t// strip off one path element at a time until empty\n\t\t\t// this way directories are added to the listing if they match the pattern\n\t\t\tslash = Q_strrchr( temp, '/' );\n\t\t\tbackslash = Q_strrchr( temp, '\\\\' );\n\t\t\tcolon = Q_strrchr( temp, ':' );\n\t\t\tseparator = temp;\n\t\t\tif( separator < slash )\n\t\t\t\tseparator = slash;\n\t\t\tif( separator < backslash )\n\t\t\t\tseparator = backslash;\n\t\t\tif( separator < colon )\n\t\t\t\tseparator = colon;\n\t\t\t*((char *)separator) = 0;\n\t\t}\n\t}\n}\n\n/*\n===========\nFS_AddZip_Fullpath\n\n===========\n*/\nsearchpath_t *FS_AddZip_Fullpath( const char *zipfile, int flags )\n{\n\tsearchpath_t *search;\n\tzip_t *zip;\n\tint errorcode = ZIP_LOAD_COULDNT_OPEN;\n\n\tzip = FS_LoadZip( zipfile, &errorcode );\n\n\tif( !zip )\n\t{\n\t\tif( errorcode != ZIP_LOAD_NO_FILES )\n\t\t\tCon_Reportf( S_ERROR \"%s: unable to load zip \\\"%s\\\"\\n\", __func__, zipfile );\n\t\treturn NULL;\n\t}\n\n\tsearch = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) );\n\tQ_strncpy( search->filename, zipfile, sizeof( search->filename ));\n\tsearch->zip = zip;\n\tsearch->type = SEARCHPATH_ZIP;\n\tsearch->flags = flags;\n\n\tsearch->pfnPrintInfo = FS_PrintInfo_ZIP;\n\tsearch->pfnClose = FS_Close_ZIP;\n\tsearch->pfnOpenFile = FS_OpenFile_ZIP;\n\tsearch->pfnFileTime = FS_FileTime_ZIP;\n\tsearch->pfnFindFile = FS_FindFile_ZIP;\n\tsearch->pfnSearch = FS_Search_ZIP;\n\tsearch->pfnLoadFile = FS_LoadZIPFile;\n\n\tCon_Reportf( \"Adding ZIP: %s (%i files)\\n\", zipfile, zip->numfiles );\n\treturn search;\n}\n\n"
  },
  {
    "path": "game_launch/game.cpp",
    "content": "/*\ngame.cpp -- executable to run Xash Engine\nCopyright (C) 2011 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"port.h\"\n#include \"build.h\"\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdarg.h>\n\n#if XASH_POSIX\n#define XASHLIB \"libxash.\" OS_LIB_EXT\n#define FreeLibrary( x ) dlclose( x )\n#elif XASH_WIN32\n#include <shellapi.h> // CommandLineToArgvW\n#define XASHLIB L\"xash.dll\"\n#define SDL2LIB L\"SDL2.dll\"\n\nextern \"C\"\n{\n// Enable NVIDIA High Performance Graphics while using Integrated Graphics.\n__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;\n\n// Enable AMD High Performance Graphics while using Integrated Graphics.\n__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;\n}\n#else\n#error // port me!\n#endif\n\n#ifndef XASH_GAMEDIR\n#define XASH_GAMEDIR \"valve\" // !!! Replace with your default (base) game directory !!!\n#endif\n\ntypedef void (*pfnChangeGame)( const char *progname );\ntypedef int  (*pfnInit)( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func );\ntypedef void (*pfnShutdown)( void );\n\nstatic pfnInit     Host_Main;\nstatic pfnShutdown Host_Shutdown = NULL;\nstatic char        szGameDir[128]; // safe place to keep gamedir\nstatic int         szArgc;\nstatic char        **szArgv;\nstatic HINSTANCE   hEngine;\n\nstatic void Launch_Error( const char *szFmt, ... )\n{\n\tstatic char\tbuffer[16384];\t// must support > 1k messages\n\tva_list\t\targs;\n\n\tva_start( args, szFmt );\n\tvsnprintf( buffer, sizeof(buffer), szFmt, args );\n\tva_end( args );\n\n#if XASH_WIN32\n\tMessageBoxA( NULL, buffer, \"Xash Error\", MB_OK );\n#else\n\tfprintf( stderr, \"Xash Error: %s\\n\", buffer );\n#endif\n\n\texit( 1 );\n}\n\n#if XASH_WIN32\nstatic const char *GetStringLastError()\n{\n\tstatic char buf[1024];\n\n\tFormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\n\t\tNULL, GetLastError(), MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ),\n\t\tbuf, sizeof( buf ), NULL );\n\n\treturn buf;\n}\n#endif\n\nstatic void Sys_LoadEngine( void )\n{\n#if XASH_WIN32\n\tHMODULE hSDL = LoadLibraryExW( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE );\n\n\tif( !hSDL )\n\t{\n\t\tLaunch_Error(\"Unable to load %ls: %s\", SDL2LIB, GetStringLastError( ));\n\t\treturn;\n\t}\n\n\tFreeLibrary( hSDL );\n\n\thEngine = LoadLibraryW( XASHLIB );\n\tif( !hEngine )\n\t{\n\t\tLaunch_Error( \"Unable to load %ls: %s\", XASHLIB, GetStringLastError( ));\n\t\treturn;\n\t}\n\n\tHost_Main = (pfnInit)GetProcAddress( hEngine, \"Host_Main\" );\n\n\tif( !Host_Main )\n\t{\n\t\tLaunch_Error( \"%ls missed 'Host_Main' export: %s\", XASHLIB, GetStringLastError( ));\n\t\treturn;\n\t}\n\n\tHost_Shutdown = (pfnShutdown)GetProcAddress( hEngine, \"Host_Shutdown\" );\n#elif XASH_POSIX\n\thEngine = dlopen( XASHLIB, RTLD_NOW );\n\tif( !hEngine )\n\t{\n\t\tLaunch_Error( \"Unable to load %s: %s\", XASHLIB, dlerror( ));\n\t\treturn;\n\t}\n\n\tHost_Main = (pfnInit)dlsym( hEngine, \"Host_Main\" );\n\n\tif( !Host_Main )\n\t{\n\t\tLaunch_Error( \"%s missed 'Host_Main' export: %s\", XASHLIB, dlerror( ));\n\t\treturn;\n\t}\n\n\tHost_Shutdown = (pfnShutdown)dlsym( hEngine, \"Host_Shutdown\" );\n#else\n#error \"port me!\"\n#endif\n}\n\nstatic void Sys_UnloadEngine( void )\n{\n\tif( Host_Shutdown ) Host_Shutdown( );\n\tif( hEngine ) FreeLibrary( hEngine );\n\n\thEngine = NULL;\n\tHost_Main = NULL;\n\tHost_Shutdown = NULL;\n}\n\nstatic void Sys_ChangeGame( const char *progname )\n{\n\t// a1ba: may never be called within engine\n\t// if platform supports execv() function\n\tif( !progname || !progname[0] )\n\t{\n\t\tLaunch_Error( \"Sys_ChangeGame: NULL gamedir\" );\n\t\treturn;\n\t}\n\n\tif( Host_Shutdown == NULL )\n\t{\n\t\tLaunch_Error( \"Sys_ChangeGame: missed 'Host_Shutdown' export\\n\" );\n\t\treturn;\n\t}\n\n\tstrncpy( szGameDir, progname, sizeof( szGameDir ) - 1 );\n\n\tSys_UnloadEngine();\n\tSys_LoadEngine ();\n\tHost_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame );\n}\n\nstatic int Sys_Start( void )\n{\n\tint ret;\n\tpfnChangeGame changeGame = NULL;\n\n#if XASH_SAILFISH\n\tconst char *home = getenv( \"HOME\" );\n\tchar buf[1024];\n\n\tsnprintf( buf, sizeof( buf ), \"%s/xash\", home );\n\tsetenv( \"XASH3D_BASEDIR\", buf, true );\n#if XASH_AURORAOS\n\tsetenv( \"XASH3D_RODIR\", \"/usr/share/su.xash.Engine/rodir\", true );\n#else\n\tsetenv( \"XASH3D_RODIR\", \"/usr/share/harbour-xash3d-fwgs/rodir\", true );\n#endif // XASH_AURORAOS\n#endif // XASH_SAILFISH\n\n\tstrncpy( szGameDir, XASH_GAMEDIR, sizeof( szGameDir ) - 1 );\n\n\tSys_LoadEngine();\n\n\tif( Host_Shutdown )\n\t\tchangeGame = Sys_ChangeGame;\n\n\tret = Host_Main( szArgc, szArgv, szGameDir, 0, XASH_DISABLE_MENU_CHANGEGAME ? NULL : changeGame );\n\n\tSys_UnloadEngine();\n\n\treturn ret;\n}\n\n#if !XASH_WIN32\nint main( int argc, char **argv )\n{\n\tszArgc = argc;\n\tszArgv = argv;\n\n\treturn Sys_Start();\n}\n#else\nint __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow )\n{\n\tLPWSTR* lpArgv;\n\tint ret, i;\n\n\tlpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc );\n\tszArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* ));\n\n\tfor( i = 0; i < szArgc; ++i )\n\t{\n\t\tsize_t size = wcslen(lpArgv[i]) + 1;\n\n\t\t// just in case, allocate some more memory\n\t\tszArgv[i] = ( char * )malloc( size * sizeof( wchar_t ));\n\t\twcstombs( szArgv[i], lpArgv[i], size );\n\t}\n\tszArgv[szArgc] = 0;\n\n\tLocalFree( lpArgv );\n\n\tret = Sys_Start();\n\n\tfor( ; i < szArgc; ++i )\n\t\tfree( szArgv[i] );\n\tfree( szArgv );\n\n\treturn ret;\n}\n#endif\n"
  },
  {
    "path": "game_launch/game.rc",
    "content": "#include <winver.h>\n\n#define IDI_ICON1               101\n\n#define VER_FILEVERSION\t\t1,00\n#define VER_FILEVERSION_STR\t\"1.00\"\n#define VER_PRODUCTVERSION\t1,00\n#define VER_PRODUCTVERSION_STR\t\"1.00\"\n\n#define VER_FILEFLAGSMASK\tVS_FF_PRERELEASE | VS_FF_PATCHED\n#define VER_FILEFLAGS\t\tVS_FF_PRERELEASE\n#define VER_FILEOS\t\tVOS__WINDOWS32\n#define VER_FILETYPE\t\tVFT_DLL\n#define VER_FILESUBTYPE\t\tVFT2_UNKNOWN\n\n#define VER_COMPANYNAME_STR\t\"Flying With Gauss\"\n#define VER_LEGALCOPYRIGHT_STR\t\"Flying With Gauss\"\n#define VER_PRODUCTNAME_STR\t\"Xash3D Launcher\"\n\n#define VER_ANSICP\n\n#define VER_FILEDESCRIPTION_STR\t\t\"Xash3D FWGS Launcher\"\n#define VER_ORIGINALFILENAME_STR\t\"xash.exe\"\n#define VER_INTERNALNAME_STR\t\t\"xash\"\n\n#include <common.ver>\n\nIDI_ICON1\tICON    DISCARDABLE\t\t\"icon-xash-material.ico\"\n"
  },
  {
    "path": "game_launch/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# a1batross, mittorn, 2018\n\nfrom waflib import Logs\nimport os\nimport sys\n\ntop = '.'\n\ndef options(opt):\n\tgrp = opt.add_option_group('Game launcher options')\n\n\tgrp.add_option('--disable-menu-changegame', action = 'store_true', dest = 'DISABLE_MENU_CHANGEGAME', default = False,\n\t\thelp = 'disable changing the game from the menu [default: %(default)s]')\n\ndef configure(conf):\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.load('winres')\n\n\tconf.define('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME)\n\ndef build(bld):\n\tsource = ['game.cpp']\n\n\tif bld.env.DEST_OS == 'win32':\n\t\tsource += ['game.rc']\n\n\t# Half-Life 25th anniversary update doesn't have server library explicitly linked to libm\n\tif bld.env.DEST_OS == 'linux':\n\t\tbld.env.LDFLAGS += ['-Wl,--no-as-needed', '-lm']\n\n\tbld.program(source = source,\n\t\ttarget   = 'xash3d', # hl.exe\n\t\tuse      = 'sdk_includes DL USER32 SHELL32 werror',\n\t\trpath    = bld.env.DEFAULT_RPATH,\n\t\tinstall_path = bld.env.BINDIR,\n\t\tsubsystem = bld.env.MSVC_SUBSYSTEM\n\t)\n"
  },
  {
    "path": "pm_shared/pm_defs.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n\n#ifndef PM_DEFS_H\n#define PM_DEFS_H\n\n#define MAX_PHYSENTS\t600\t\t// Must have room for all entities in the world.\n#define MAX_MOVEENTS\t64\n#define MAX_CLIP_PLANES\t5\n\n#define PM_NORMAL\t\t0x00000000\n#define PM_STUDIO_IGNORE\t0x00000001\t// Skip studio models\n#define PM_STUDIO_BOX\t0x00000002\t// Use boxes for non-complex studio models (even in traceline)\n#define PM_GLASS_IGNORE\t0x00000004\t// Ignore entities with non-normal rendermode\n#define PM_WORLD_ONLY\t0x00000008\t// Only trace against the world\n#define PM_CUSTOM_IGNORE\t0x00000010\t// Ignore entities with SOLID_CUSTOM mode\n\n// Values for flags parameter of PM_TraceLine\n#define PM_TRACELINE_PHYSENTSONLY\t0\n#define PM_TRACELINE_ANYVISIBLE\t1\n\n#include \"pm_info.h\"\n\n// PM_PlayerTrace results.\n#include \"pmtrace.h\"\n\n\n#include \"usercmd.h\"\n\n// physent_t\ntypedef struct physent_s\n{\n\tchar\t\tname[32];\t\t// Name of model, or \"player\" or \"world\".\n\tint\t\tplayer;\n\tvec3_t\t\torigin;\t\t// Model's origin in world coordinates.\n\tstruct model_s\t*model;\t\t// only for bsp models\n\tstruct model_s\t*studiomodel;\t// SOLID_BBOX, but studio clip intersections.\n\tvec3_t\t\tmins, maxs;\t// only for non-bsp models\n\tint\t\tinfo;\t\t// For client or server to use to identify (index into edicts or cl_entities)\n\tvec3_t\t\tangles;\t\t// rotated entities need this info for hull testing to work.\n\n\tint\t\tsolid;\t\t// Triggers and func_door type WATER brushes are SOLID_NOT\n\tint\t\tskin;\t\t// BSP Contents for such things like fun_door water brushes.\n\tint\t\trendermode;\t// So we can ignore glass\n\n\t// Complex collision detection.\n\tfloat\t\tframe;\n\tint\t\tsequence;\n\tbyte\t\tcontroller[4];\n\tbyte\t\tblending[2];\n\n\tint\t\tmovetype;\n\tint\t\ttakedamage;\n\tint\t\tblooddecal;\n\tint\t\tteam;\n\tint\t\tclassnumber;\n\n\t// For mods\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\t\t// also contains pev->scale when \"sv_allow_studio_scaling\" is \"1\"\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n\tvec3_t\t\tvuser1;\n\tvec3_t\t\tvuser2;\n\tvec3_t\t\tvuser3;\n\tvec3_t\t\tvuser4;\n} physent_t;\n\ntypedef struct playermove_s\n{\n\tint\t\tplayer_index;\t// So we don't try to run the PM_CheckStuck nudging too quickly.\n\tqboolean\t\tserver;\t\t// For debugging, are we running physics code on server side?\n\n\tqboolean\t\tmultiplayer;\t// 1 == multiplayer server\n\tfloat\t\ttime;\t\t// realtime on host, for reckoning duck timing\n\tfloat\t\tframetime;\t// Duration of this frame\n\n\tvec3_t\t\tforward, right, up;\t// Vectors for angles\n\n\t// player state\n\tvec3_t\t\torigin;\t\t// Movement origin.\n\tvec3_t\t\tangles;\t\t// Movement view angles.\n\tvec3_t\t\toldangles;\t// Angles before movement view angles were looked at.\n\tvec3_t\t\tvelocity;\t\t// Current movement direction.\n\tvec3_t\t\tmovedir;\t\t// For waterjumping, a forced forward velocity so we can fly over lip of ledge.\n\tvec3_t\t\tbasevelocity;\t// Velocity of the conveyor we are standing, e.g.\n\n\t// For ducking/dead\n\tvec3_t\t\tview_ofs;\t\t// Our eye position.\n\tfloat\t\tflDuckTime;\t// Time we started duck\n\tqboolean\t\tbInDuck;\t\t// In process of ducking or ducked already?\n\n\t// For walking/falling\n\tint\t\tflTimeStepSound;\t// Next time we can play a step sound\n\tint\t\tiStepLeft;\n\n\tfloat\t\tflFallVelocity;\n\tvec3_t\t\tpunchangle;\n\n\tfloat\t\tflSwimTime;\n\tfloat\t\tflNextPrimaryAttack;\n\n\tint\t\teffects;\t\t// MUZZLE FLASH, e.g.\n\n\tint\t\tflags;\t\t// FL_ONGROUND, FL_DUCKING, etc.\n\tint\t\tusehull;\t\t// 0 = regular player hull, 1 = ducked player hull, 2 = point hull\n\tfloat\t\tgravity;\t\t// Our current gravity and friction.\n\tfloat\t\tfriction;\n\tint\t\toldbuttons;\t// Buttons last usercmd\n\tfloat\t\twaterjumptime;\t// Amount of time left in jumping out of water cycle.\n\tqboolean\t\tdead;\t\t// Are we a dead player?\n\tint\t\tdeadflag;\n\tint\t\tspectator;\t// Should we use spectator physics model?\n\tint\t\tmovetype;\t\t// Our movement type, NOCLIP, WALK, FLY\n\n\tint\t\tonground;\n\tint\t\twaterlevel;\n\tint\t\twatertype;\n\tint\t\toldwaterlevel;\n\n\tchar\t\tsztexturename[256];\n\tchar\t\tchtexturetype;\n\n\tfloat\t\tmaxspeed;\n\tfloat\t\tclientmaxspeed;\t// Player specific maxspeed\n\n\t// For mods\n\tint\t\tiuser1;\n\tint\t\tiuser2;\n\tint\t\tiuser3;\n\tint\t\tiuser4;\n\tfloat\t\tfuser1;\n\tfloat\t\tfuser2;\n\tfloat\t\tfuser3;\n\tfloat\t\tfuser4;\n\tvec3_t\t\tvuser1;\n\tvec3_t\t\tvuser2;\n\tvec3_t\t\tvuser3;\n\tvec3_t\t\tvuser4;\n\n\t// world state\n\n\t// Number of entities to clip against.\n\tint\t\tnumphysent;\n\tphysent_t\t\tphysents[MAX_PHYSENTS];\n\n\t// Number of momvement entities (ladders)\n\tint\t\tnummoveent;\n\t// just a list of ladders\n\tphysent_t\t\tmoveents[MAX_MOVEENTS];\n\n\t// All things being rendered, for tracing against things you don't actually collide with\n\tint\t\tnumvisent;\n\tphysent_t\t\tvisents[MAX_PHYSENTS];\n\n\t// input to run through physics.\n\tusercmd_t\t\tcmd;\n\n\t// Trace results for objects we collided with.\n\tint\t\tnumtouch;\n\tpmtrace_t\t\ttouchindex[MAX_PHYSENTS];\n\n\tchar\t\tphysinfo[MAX_PHYSINFO_STRING]; // Physics info string\n\n\tstruct movevars_s\t*movevars;\n\tvec3_t\t\tplayer_mins[4];\n\tvec3_t\t\tplayer_maxs[4];\n\n\t// Common functions\n\tconst char\t*(*PM_Info_ValueForKey) ( const char *s, const char *key );\n\tvoid\t\t(*PM_Particle)( const float *origin, int color, float life, int zpos, int zvel );\n\tint\t\t(*PM_TestPlayerPosition)( float *pos, pmtrace_t *ptrace );\n\tvoid\t\t(*Con_NPrintf)( int idx, const char *fmt, ... );\n\tvoid\t\t(*Con_DPrintf)( const char *fmt, ... );\n\tvoid\t\t(*Con_Printf)( const char *fmt, ... );\n\tdouble\t\t(*Sys_FloatTime)( void );\n\tvoid\t\t(*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult );\n\tint\t\t(*PM_PointContents)( float *p, int *truecontents /*filled in if this is non-null*/ );\n\tint\t\t(*PM_TruePointContents)( float *p );\n\tint\t\t(*PM_HullPointContents)( struct hull_s *hull, int num, float *p );\n\tpmtrace_t\t\t(*PM_PlayerTrace)( float *start, float *end, int traceFlags, int ignore_pe );\n\tstruct pmtrace_s\t*(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe );\n\tint\t\t\t(*RandomLong)( int lLow, int lHigh );\n\tfloat\t\t(*RandomFloat)( float flLow, float flHigh );\n\tint\t\t(*PM_GetModelType)( struct model_s *mod );\n\tvoid\t\t(*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs );\n\tvoid\t\t*(*PM_HullForBsp)( physent_t *pe, float *offset );\n\tfloat\t\t(*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace );\n\tint\t\t(*COM_FileSize)( const char *filename );\n\tbyte\t\t*(*COM_LoadFile)( const char *path, int usehunk, int *pLength );\n\tvoid\t\t(*COM_FreeFile)( void *buffer );\n\tchar\t\t*(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize );\n\n\t// Functions\n\t// Run functions for this frame?\n\tqboolean\t\trunfuncs;\n\tvoid\t\t(*PM_PlaySound)( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch );\n\tconst char\t*(*PM_TraceTexture)( int ground, float *vstart, float *vend );\n\tvoid\t\t(*PM_PlaybackEventFull)( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );\n\tpmtrace_t\t\t(*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ));\n\tint\t\t(*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ));\n\tstruct pmtrace_s\t*(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ));\n\n\tstruct msurface_s\t*(*PM_TraceSurface)( int ground, float *vstart, float *vend ); // Xash3D-specific\n} playermove_t;\n#endif//PM_DEFS_H\n"
  },
  {
    "path": "pm_shared/pm_info.h",
    "content": "/***\n*\n*\tCopyright (c) 1996-2002, Valve LLC. All rights reserved.\n*\n*\tThis product contains software technology licensed from Id\n*\tSoftware, Inc. (\"Id Technology\").  Id Technology (c) 1996 Id Software, Inc.\n*\tAll Rights Reserved.\n*\n*   Use, distribution, and modification of this source code and/or resulting\n*   object code is restricted to non-commercial enhancements to products from\n*   Valve LLC.  All other use, distribution, or modification is prohibited\n*   without written permission from Valve LLC.\n*\n****/\n#ifndef PM_INFO_H\n#define PM_INFO_H\n\n#define MAX_PHYSINFO_STRING\t\t256\n\n#endif//PM_INFO_H\n"
  },
  {
    "path": "pm_shared/pm_movevars.h",
    "content": "//========= Copyright (C) 1996-2002, Valve LLC, All rights reserved. ============\n//\n// Purpose:\n//\n// $NoKeywords: $\n//=============================================================================\n\n// pm_movevars.h\n#if !defined( PM_MOVEVARSH )\n#define PM_MOVEVARSH\n\n// movevars_t                  // Physics variables.\ntypedef struct movevars_s movevars_t;\n\nstruct movevars_s\n{\n\tfloat\tgravity;           // Gravity for map\n\tfloat\tstopspeed;         // Deceleration when not moving\n\tfloat\tmaxspeed;          // Max allowed speed\n\tfloat\tspectatormaxspeed;\n\tfloat\taccelerate;        // Acceleration factor\n\tfloat\tairaccelerate;     // Same for when in open air\n\tfloat\twateraccelerate;   // Same for when in water\n\tfloat\tfriction;\n\tfloat\tedgefriction;\t   // Extra friction near dropofs\n\tfloat\twaterfriction;     // Less in water\n\tfloat\tentgravity;        // 1.0\n\tfloat\tbounce;            // Wall bounce value. 1.0\n\tfloat\tstepsize;          // sv_stepsize;\n\tfloat\tmaxvelocity;       // maximum server velocity.\n\tfloat\tzmax;\t\t\t   // Max z-buffer range (for GL)\n\tfloat\twaveHeight;\t\t   // Water wave height (for GL)\n\tqboolean\tfootsteps;        // Play footstep sounds\n\tchar\tskyName[32];\t   // Name of the sky map\n\tfloat\trollangle;\n\tfloat\trollspeed;\n\tfloat\tskycolor_r;\t\t\t// Sky color\n\tfloat\tskycolor_g;\t\t\t//\n\tfloat\tskycolor_b;\t\t\t//\n\tfloat\tskyvec_x;\t\t\t// Sky vector\n\tfloat\tskyvec_y;\t\t\t//\n\tfloat\tskyvec_z;\t\t\t//\n\tint\tfeatures;\t\t// engine features that shared across network\n\tint\tfog_settings;\t// Global fog settings (packed color+density)\n\tfloat\twateralpha;\t// World water alpha 1.0 - solid 0.0 - transparent\n\tfloat\tskydir_x;\t\t// skybox rotate direction\n\tfloat\tskydir_y;\t\t//\n\tfloat\tskydir_z;\t\t//\n\tfloat\tskyangle;\t\t// skybox rotate angle\n};\n\nextern movevars_t movevars;\n\n#endif\n"
  },
  {
    "path": "public/build.c",
    "content": "/*\nbuild.c - returns a engine build number\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdio.h>\n#include \"crtlib.h\"\n#include \"buildenums.h\"\n\nstatic const char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };\n\nint Q_buildnum_iso( const char *date )\n{\n\tint y, m, d, b, i;\n\n\tif( sscanf( date, \"%d-%d-%d\", &y, &m, &d ) != 3 || y <= 1900 || m <= 0 || d <= 0 )\n\t\treturn -1;\n\n\t// fixup day and month\n\tm--;\n\td--;\n\n\tfor( i = 0; i < m; i++ )\n\t\td += mond[i];\n\n\ty -= 1900;\n\tb = d + (int)((y - 1) * 365.25f );\n\n\tif((( y % 4 ) == 0 ) && m > 1 )\n\t\tb += 1;\n\tb -= 41728; // Apr 1 2015\n\n\treturn b;\n}\n\n/*\n===============\nQ_buildnum\n\nreturns days since Apr 1 2015\n===============\n*/\nint Q_buildnum( void )\n{\n\tstatic int b = 0;\n\n\tif( b ) return b;\n\n\tb = Q_buildnum_iso( g_buildcommit_date );\n\n\treturn b;\n}\n\n/*\n=============\nQ_buildnum_compat\n\nReturns a Xash3D build number. This is left for compability with original Xash3D.\nIMPORTANT: this value must be changed ONLY after updating to newer Xash3D base\nIMPORTANT: this value must be acquired through \"build\" cvar.\n=============\n*/\nint Q_buildnum_compat( void )\n{\n\t// do not touch this! Only author of Xash3D can increase buildnumbers!\n\treturn 4529;\n}\n\n/*\n============\nQ_GetPlatformStringByID\n\nReturns name of operating system by ID. Without any spaces.\n\nTODO: add platform-dependent ABI variants, for example, different libc on Linux\n============\n*/\nconst char *Q_PlatformStringByID( const int platform )\n{\n\tswitch( platform )\n\t{\n\tcase PLATFORM_WIN32:\n\t\treturn \"win32\";\n\tcase PLATFORM_ANDROID:\n\t\treturn \"android\";\n\tcase PLATFORM_LINUX:\n\t\treturn \"linux\";\n\tcase PLATFORM_APPLE:\n\t\treturn \"apple\";\n\tcase PLATFORM_FREEBSD:\n\t\treturn \"freebsd\";\n\tcase PLATFORM_NETBSD:\n\t\treturn \"netbsd\";\n\tcase PLATFORM_OPENBSD:\n\t\treturn \"openbsd\";\n\tcase PLATFORM_EMSCRIPTEN:\n\t\treturn \"emscripten\";\n\tcase PLATFORM_DOS4GW:\n\t\treturn \"DOS4GW\";\n\tcase PLATFORM_HAIKU:\n\t\treturn \"haiku\";\n\tcase PLATFORM_SERENITY:\n\t\treturn \"serenity\";\n\tcase PLATFORM_IRIX:\n\t\treturn \"irix\";\n\tcase PLATFORM_NSWITCH:\n\t\treturn \"nswitch\";\n\tcase PLATFORM_PSVITA:\n\t\treturn \"psvita\";\n\tcase PLATFORM_WASI:\n\t\treturn \"wasi\";\n\tcase PLATFORM_SUNOS:\n\t\treturn \"sunos\";\n\tcase PLATFORM_HURD:\n\t\treturn \"hurd\";\n\t}\n\n\tassert( 0 );\n\treturn \"unknown\";\n}\n\n/*\n============\nQ_buildos\n\nShortcut for Q_buildos_\n============\n*/\nconst char *Q_buildos( void )\n{\n\treturn Q_PlatformStringByID( XASH_PLATFORM );\n}\n\n\n/*\n============\nQ_ArchitectureStringByID\n\nReturns name of the architecture by it's ID. Without any spaces.\n============\n*/\nconst char *Q_ArchitectureStringByID( const int arch, const uint abi, const int endianness, const qboolean is64 )\n{\n\t// I don't want to change this function prototype\n\t// and don't want to use static buffer either\n\t// so encode all possible variants... :)\n\tswitch( arch )\n\t{\n\tcase ARCHITECTURE_AMD64:\n\t\treturn \"amd64\";\n\tcase ARCHITECTURE_X86:\n\t\treturn \"i386\";\n\tcase ARCHITECTURE_E2K:\n\t\treturn \"e2k\";\n\tcase ARCHITECTURE_JS:\n\t\treturn \"javascript\";\n\tcase ARCHITECTURE_PPC:\n\t\treturn endianness == ENDIANNESS_LITTLE ?\n\t\t\t( is64 ? \"ppc64el\" : \"ppcel\" ):\n\t\t\t( is64 ? \"ppc64\" : \"ppc\" );\n\tcase ARCHITECTURE_MIPS:\n\t\treturn endianness == ENDIANNESS_LITTLE ?\n\t\t\t( is64 ? \"mips64el\" : \"mipsel\" ):\n\t\t\t( is64 ? \"mips64\" : \"mips\" );\n\tcase ARCHITECTURE_ARM:\n\t\t// no support for big endian ARM here\n\t\tif( endianness == ENDIANNESS_LITTLE )\n\t\t{\n\t\t\tconst uint ver = ( abi >> ARCH_ARM_VER_SHIFT ) & ARCH_ARM_VER_MASK;\n\t\t\tconst qboolean hardfp = FBitSet( abi, ARCH_ARM_HARDFP );\n\n\t\t\tif( is64 )\n\t\t\t\treturn \"arm64\"; // keep as arm64, it's not aarch64!\n\n\t\t\tswitch( ver )\n\t\t\t{\n\t\t\tcase 8:\n\t\t\t\treturn hardfp ? \"armv8_32hf\" : \"armv8_32l\";\n\t\t\tcase 7:\n\t\t\t\treturn hardfp ? \"armv7hf\" : \"armv7l\";\n\t\t\tcase 6:\n\t\t\t\treturn \"armv6l\";\n\t\t\tcase 5:\n\t\t\t\treturn \"armv5l\";\n\t\t\tcase 4:\n\t\t\t\treturn \"armv4l\";\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase ARCHITECTURE_RISCV:\n\t\tswitch( abi )\n\t\t{\n\t\tcase ARCH_RISCV_FP_SOFT:\n\t\t\treturn is64 ? \"riscv64\" : \"riscv32\";\n\t\tcase ARCH_RISCV_FP_SINGLE:\n\t\t\treturn is64 ? \"riscv64f\" : \"riscv32f\";\n\t\tcase ARCH_RISCV_FP_DOUBLE:\n\t\t\treturn is64 ? \"riscv64d\" : \"riscv32d\";\n\t\t}\n\t\tbreak;\n\tcase ARCHITECTURE_WASM:\n\t\treturn is64 ? \"wasm64\" : \"wasm32\";\n\t}\n\n\tassert( 0 );\n\treturn is64 ?\n\t\t( endianness == ENDIANNESS_LITTLE ? \"unknown64el\" : \"unknownel\" ) :\n\t\t( endianness == ENDIANNESS_LITTLE ? \"unknown64be\" : \"unknownbe\" );\n}\n\n/*\n============\nQ_buildarch\n\nReturns current name of the architecture. Without any spaces.\n============\n*/\nconst char *Q_buildarch( void )\n{\n\treturn Q_ArchitectureStringByID(\n\t\tXASH_ARCHITECTURE,\n\t\tXASH_ARCHITECTURE_ABI,\n\t\tXASH_ENDIANNESS,\n#if XASH_64BIT\n\t\ttrue\n#else\n\t\tfalse\n#endif\n\t);\n}\n\n"
  },
  {
    "path": "public/build.h",
    "content": "/*\nbuild.h - compile-time build information\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>\n*/\n#pragma once\n#ifndef BUILD_H\n#define BUILD_H\n\n/*\nAll XASH_* macros set by this header are guaranteed to have positive value\notherwise not defined.\n\nEvery macro is intended to be the unified interface for buildsystems that lack\nplatform & CPU detection, and a neat quick way for checks in platform code\nFor Q_build* macros, refer to buildenums.h\n\nAny new define must be undefined at first\nYou can generate #undef list below with this oneliner:\n  $ sed 's/\\t//g' build.h | grep '^#define XASH' | awk '{ print $2 }' | \\\n\t\tsort | uniq | awk '{ print \"#undef \" $1 }'\n\nThen you can use another oneliner to query all variables:\n  $ grep '^#undef XASH' build.h | awk '{ print $2 }'\n*/\n\n#undef XASH_64BIT\n#undef XASH_AMD64\n#undef XASH_ANDROID\n#undef XASH_APPLE\n#undef XASH_ARM\n#undef XASH_ARM_HARDFP\n#undef XASH_ARM_SOFTFP\n#undef XASH_ARMv4\n#undef XASH_ARMv5\n#undef XASH_ARMv6\n#undef XASH_ARMv7\n#undef XASH_ARMv8\n#undef XASH_BIG_ENDIAN\n#undef XASH_DOS4GW\n#undef XASH_E2K\n#undef XASH_EMSCRIPTEN\n#undef XASH_FREEBSD\n#undef XASH_HAIKU\n#undef XASH_HURD\n#undef XASH_IOS\n#undef XASH_IRIX\n#undef XASH_JS\n#undef XASH_LINUX\n#undef XASH_LITTLE_ENDIAN\n#undef XASH_MIPS\n#undef XASH_MOBILE_PLATFORM\n#undef XASH_NETBSD\n#undef XASH_OPENBSD\n#undef XASH_POSIX\n#undef XASH_PPC\n#undef XASH_RISCV\n#undef XASH_RISCV_DOUBLEFP\n#undef XASH_RISCV_SINGLEFP\n#undef XASH_RISCV_SOFTFP\n#undef XASH_SERENITY\n#undef XASH_SUNOS\n#undef XASH_TERMUX\n#undef XASH_WIN32\n#undef XASH_X86\n#undef XASH_NSWITCH\n#undef XASH_PSVITA\n#undef XASH_WASI\n#undef XASH_WASM\n\n//================================================================\n//\n//           PLATFORM DETECTION CODE\n//\n//================================================================\n#if defined _WIN32\n\t#define XASH_WIN32 1\n#elif defined __WATCOMC__ && defined __DOS__\n\t#define XASH_DOS4GW 1\n#else // POSIX compatible\n\t#define XASH_POSIX 1\n\t#if defined __linux__\n\t\t#if defined __ANDROID__\n\t\t\t#define XASH_ANDROID 1\n\t\t\t#if defined __TERMUX__\n\t\t\t\t#define XASH_TERMUX 1\n\t\t\t#endif\n\t\t#endif\n\t\t#define XASH_LINUX 1\n\t#elif defined __FreeBSD__\n\t\t#define XASH_FREEBSD 1\n\t#elif defined __NetBSD__\n\t\t#define XASH_NETBSD 1\n\t#elif defined __OpenBSD__\n\t\t#define XASH_OPENBSD 1\n\t#elif defined __HAIKU__\n\t\t#define XASH_HAIKU 1\n\t#elif defined __serenity__\n\t\t#define XASH_SERENITY 1\n\t#elif defined __sgi\n\t\t#define XASH_IRIX 1\n\t#elif defined __APPLE__\n\t\t#include <TargetConditionals.h>\n\t\t#define XASH_APPLE 1\n\t\t#if TARGET_OS_IOS\n\t\t\t#define XASH_IOS 1\n\t\t#endif // TARGET_OS_IOS\n\t#elif defined __SWITCH__\n\t\t#define XASH_NSWITCH 1\n\t#elif defined __vita__\n\t\t#define XASH_PSVITA 1\n\t#elif defined __wasi__\n\t\t#define XASH_WASI 1\n\t#elif defined __sun__\n\t\t#define XASH_SUNOS 1\n\t#elif defined __EMSCRIPTEN__\n\t\t#define XASH_EMSCRIPTEN 1\n\t#elif defined __gnu_hurd__\n\t\t#define XASH_HURD 1\n\t#else\n\t\t#error\n\t#endif\n#endif\n\n// XASH_SAILFISH is special: SailfishOS by itself is a normal GNU/Linux platform\n// It doesn't make sense to split it to separate platform\n// but we still need XASH_MOBILE_PLATFORM for the engine.\n// So this macro is defined entirely in build-system: see main wscript\n// HLSDK/PrimeXT/other SDKs users note: you may ignore this macro\n#if ( XASH_ANDROID && !XASH_TERMUX ) || XASH_IOS || XASH_NSWITCH || XASH_PSVITA || XASH_SAILFISH\n\t#define XASH_MOBILE_PLATFORM 1\n#endif\n\n//================================================================\n//\n//           ENDIANNESS DEFINES\n//\n//================================================================\n\n#if !defined XASH_ENDIANNESS\n\t#if defined XASH_WIN32 || __LITTLE_ENDIAN__\n\t\t//!!! Probably all WinNT installations runs in little endian\n\t\t#define XASH_LITTLE_ENDIAN 1\n\t#elif __BIG_ENDIAN__\n\t\t#define XASH_BIG_ENDIAN 1\n\t#elif defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ && defined __ORDER_LITTLE_ENDIAN__ // some compilers define this\n\t\t#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__\n\t\t\t#define XASH_BIG_ENDIAN 1\n\t\t#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n\t\t\t#define XASH_LITTLE_ENDIAN 1\n\t\t#endif\n\t#else\n\t\t#include <sys/param.h>\n\t\t#if __BYTE_ORDER == __BIG_ENDIAN\n\t\t\t#define XASH_BIG_ENDIAN 1\n\t\t#elif __BYTE_ORDER == __LITTLE_ENDIAN\n\t\t\t#define XASH_LITTLE_ENDIAN 1\n\t\t#endif\n\t#endif // !XASH_WIN32\n#endif\n\n//================================================================\n//\n//           CPU ARCHITECTURE DEFINES\n//\n//================================================================\n#if defined __x86_64__ || defined _M_X64\n\t#define XASH_64BIT 1\n\t#define XASH_AMD64 1\n#elif defined __i386__ || defined _X86_ || defined _M_IX86\n\t#define XASH_X86 1\n#elif defined __aarch64__ || defined _M_ARM64\n\t#define XASH_64BIT 1\n\t#define XASH_ARM   8\n#elif defined __mips__\n\t#define XASH_MIPS 1\n// commented out to avoid misdetection, modern Emscripten versions target WASM only\n//#elif defined __EMSCRIPTEN__\n//\t#define XASH_JS 1\n#elif defined __e2k__\n\t#define XASH_64BIT 1\n\t#define XASH_E2K 1\n#elif defined __PPC__ || defined __powerpc__\n\t#define XASH_PPC 1\n\t#if defined __PPC64__ || defined __powerpc64__\n\t\t#define XASH_64BIT 1\n\t#endif\n#elif defined _M_ARM // msvc\n\t#define XASH_ARM 7\n\t#define XASH_ARM_HARDFP 1\n#elif defined __arm__\n\t#if __ARM_ARCH == 8 || __ARM_ARCH_8__\n\t\t#define XASH_ARM 8\n\t#elif __ARM_ARCH == 7 || __ARM_ARCH_7__\n\t\t#define XASH_ARM 7\n\t#elif __ARM_ARCH == 6 || __ARM_ARCH_6__ || __ARM_ARCH_6J__\n\t\t#define XASH_ARM 6\n\t#elif __ARM_ARCH == 5 || __ARM_ARCH_5__\n\t\t#define XASH_ARM 5\n\t#elif __ARM_ARCH == 4 || __ARM_ARCH_4__\n\t\t#define XASH_ARM 4\n\t#else\n\t\t#error \"Unknown ARM\"\n\t#endif\n\n\t#if defined __SOFTFP__ || __ARM_PCS_VFP == 0\n\t\t#define XASH_ARM_SOFTFP 1\n\t#else // __SOFTFP__\n\t\t#define XASH_ARM_HARDFP 1\n\t#endif // __SOFTFP__\n#elif defined __riscv\n\t#define XASH_RISCV 1\n\n\t#if __riscv_xlen == 64\n\t\t#define XASH_64BIT 1\n\t#elif __riscv_xlen != 32\n\t\t#error \"Unknown RISC-V ABI\"\n\t#endif\n\n\t#if defined __riscv_float_abi_soft\n\t\t#define XASH_RISCV_SOFTFP 1\n\t#elif defined __riscv_float_abi_single\n\t\t#define XASH_RISCV_SINGLEFP 1\n\t#elif defined __riscv_float_abi_double\n\t\t#define XASH_RISCV_DOUBLEFP 1\n\t#else\n\t\t#error \"Unknown RISC-V float ABI\"\n\t#endif\n#elif defined __wasm__\n\t#if defined __wasm64__\n\t\t#define XASH_64BIT 1\n\t#endif\n\t#define XASH_WASM 1\n#else\n\t#error \"Place your architecture name here! If this is a mistake, try to fix conditions above and report a bug\"\n#endif\n\n#if !XASH_64BIT && ( defined( __LP64__ ) || defined( _LP64 ))\n#define XASH_64BIT 1\n#endif\n\n#if XASH_ARM == 8\n\t#define XASH_ARMv8 1\n#elif XASH_ARM == 7\n\t#define XASH_ARMv7 1\n#elif XASH_ARM == 6\n\t#define XASH_ARMv6 1\n#elif XASH_ARM == 5\n\t#define XASH_ARMv5 1\n#elif XASH_ARM == 4\n\t#define XASH_ARMv4 1\n#endif\n\n#endif // BUILD_H\n"
  },
  {
    "path": "public/build_vcs.c",
    "content": "/*\nbuild_vcs.c - info from VCS\nCopyright (C) 2025 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\nconst char *g_buildcommit = XASH_BUILD_COMMIT;\nconst char *g_buildbranch = XASH_BUILD_BRANCH;\nconst char *g_buildcommit_date = XASH_BUILD_COMMIT_DATE;\n"
  },
  {
    "path": "public/buildenums.h",
    "content": "/*\nbuild.h - compile-time build information\nCopyright (C) 2023 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef BUILDENUMS_H\n#define BUILDENUMS_H\n\n#include \"build.h\"\n\n// This header defines the enumeration values that can be passed to Q_build*\n// functions and get current value through XASH_PLATFORM, XASH_ARCHITECTURE and\n// XASH_ARCHITECTURE_ABI defines\n\n//================================================================\n//\n//           OPERATING SYSTEM DEFINES\n//\n//================================================================\n#define PLATFORM_WIN32      1\n#define PLATFORM_LINUX      2\n#define PLATFORM_FREEBSD    3\n#define PLATFORM_ANDROID    4\n#define PLATFORM_APPLE      5\n#define PLATFORM_NETBSD     6\n#define PLATFORM_OPENBSD    7\n#define PLATFORM_EMSCRIPTEN 8\n#define PLATFORM_DOS4GW     9\n#define PLATFORM_HAIKU      10\n#define PLATFORM_SERENITY   11\n#define PLATFORM_IRIX       12\n#define PLATFORM_NSWITCH    13\n#define PLATFORM_PSVITA     14\n#define PLATFORM_WASI       15\n#define PLATFORM_SUNOS      16\n#define PLATFORM_HURD       17\n\n#if XASH_WIN32\n\t#define XASH_PLATFORM PLATFORM_WIN32\n#elif XASH_ANDROID\n\t#define XASH_PLATFORM PLATFORM_ANDROID\n#elif XASH_LINUX\n\t#define XASH_PLATFORM PLATFORM_LINUX\n#elif XASH_APPLE\n\t#define XASH_PLATFORM PLATFORM_APPLE\n#elif XASH_FREEBSD\n\t#define XASH_PLATFORM PLATFORM_FREEBSD\n#elif XASH_NETBSD\n\t#define XASH_PLATFORM PLATFORM_NETBSD\n#elif XASH_OPENBSD\n\t#define XASH_PLATFORM PLATFORM_OPENBSD\n#elif XASH_EMSCRIPTEN\n\t#define XASH_PLATFORM PLATFORM_EMSCRIPTEN\n#elif XASH_DOS4GW\n\t#define XASH_PLATFORM PLATFORM_DOS4GW\n#elif XASH_HAIKU\n\t#define XASH_PLATFORM PLATFORM_HAIKU\n#elif XASH_SERENITY\n\t#define XASH_PLATFORM PLATFORM_SERENITY\n#elif XASH_IRIX\n\t#define XASH_PLATFORM PLATFORM_IRIX\n#elif XASH_NSWITCH\n\t#define XASH_PLATFORM PLATFORM_NSWITCH\n#elif XASH_PSVITA\n\t#define XASH_PLATFORM PLATFORM_PSVITA\n#elif XASH_WASI\n\t#define XASH_PLATFORM PLATFORM_WASI\n#elif XASH_SUNOS\n\t#define XASH_PLATFORM PLATFORM_SUNOS\n#elif XASH_HURD\n\t#define XASH_PLATFORM PLATFORM_HURD\n#else\n\t#error\n#endif\n\n//================================================================\n//\n//           CPU ARCHITECTURE DEFINES\n//\n//================================================================\n#define ARCHITECTURE_X86     1\n#define ARCHITECTURE_AMD64   2\n#define ARCHITECTURE_ARM     3\n#define ARCHITECTURE_MIPS    4\n#define ARCHITECTURE_JS      6\n#define ARCHITECTURE_E2K     7\n#define ARCHITECTURE_RISCV   8\n#define ARCHITECTURE_PPC     9\n#define ARCHITECTURE_WASM    10\n\n#if XASH_AMD64\n\t#define XASH_ARCHITECTURE ARCHITECTURE_AMD64\n#elif XASH_X86\n\t#define XASH_ARCHITECTURE ARCHITECTURE_X86\n#elif XASH_ARM\n\t#define XASH_ARCHITECTURE ARCHITECTURE_ARM\n#elif XASH_MIPS\n\t#define XASH_ARCHITECTURE ARCHITECTURE_MIPS\n#elif XASH_JS\n\t#define XASH_ARCHITECTURE ARCHITECTURE_JS\n#elif XASH_E2K\n\t#define XASH_ARCHITECTURE ARCHITECTURE_E2K\n#elif XASH_RISCV\n\t#define XASH_ARCHITECTURE ARCHITECTURE_RISCV\n#elif XASH_PPC\n\t#define XASH_ARCHITECTURE ARCHITECTURE_PPC\n#elif XASH_WASM\n\t#define XASH_ARCHITECTURE ARCHITECTURE_WASM\n#else\n\t#error\n#endif\n\n//================================================================\n//\n//           ENDIANNESS DEFINES\n//\n//================================================================\n#define ENDIANNESS_LITTLE  1\n#define ENDIANNESS_BIG     2\n\n#if XASH_LITTLE_ENDIAN\n\t#define XASH_ENDIANNESS ENDIANNESS_LITTLE\n#elif XASH_BIG_ENDIAN\n\t#define XASH_ENDIANNESS ENDIANNESS_BIG\n#else\n\t#error\n#endif\n\n//================================================================\n//\n//           APPLICATION BINARY INTERFACE\n//\n//================================================================\n#define BIT( n )\t\t( 1U << ( n ))\n\n#define ARCH_ARM_VER_MASK   ( BIT( 5 ) - 1 )\n#define ARCH_ARM_VER_SHIFT  0\n#define ARCH_ARM_HARDFP     BIT( 5 )\n\n#define ARCH_RISCV_FP_SOFT   0\n#define ARCH_RISCV_FP_SINGLE 1\n#define ARCH_RISCV_FP_DOUBLE 2\n\n#if XASH_ARCHITECTURE == ARCHITECTURE_ARM\n\t#if XASH_ARM_HARDFP\n\t\t#define XASH_ARCHITECTURE_ABI ( ARCH_ARM_HARDFP | XASH_ARM )\n\t#else\n\t\t#define XASH_ARCHITECTURE_ABI ( XASH_ARM )\n\t#endif\n#elif XASH_ARCHITECTURE == ARCHITECTURE_RISCV\n\t#if XASH_RISCV_SOFTFP\n\t\t#define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SOFT\n\t#elif XASH_RISCV_SINGLEFP\n\t\t#define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SINGLE\n\t#elif XASH_RISCV_DOUBLEFP\n\t\t#define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_DOUBLE\n\t#else\n\t\t#error\n\t#endif\n#else\n\t#define XASH_ARCHITECTURE_ABI 0 // unused\n#endif\n\n\n#endif // BUILDENUMS_H\n"
  },
  {
    "path": "public/crclib.c",
    "content": "/*\ncrclib.c - generate crc stuff\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"crclib.h\"\n#include \"crtlib.h\"\n#include <string.h>\n#include <stdlib.h>\n\n#define NUM_BYTES\t\t256\n\nstatic const uint32_t crc32table[NUM_BYTES] =\n{\n0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,\n0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,\n0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,\n0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,\n0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,\n0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,\n0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,\n0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,\n0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,\n0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\n0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,\n0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,\n0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,\n0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,\n0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,\n0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,\n0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,\n0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,\n0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,\n0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,\n0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,\n0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,\n0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,\n0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,\n0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,\n0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,\n0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,\n0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,\n0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,\n0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,\n0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,\n0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,\n0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,\n0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,\n0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,\n0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,\n0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,\n0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,\n0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,\n0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,\n0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,\n0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,\n0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,\n0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,\n0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,\n0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,\n0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,\n0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,\n0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,\n0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,\n0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,\n0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,\n0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,\n0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,\n0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,\n0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,\n0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,\n0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,\n0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,\n0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,\n0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,\n0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d\n};\n\nvoid GAME_EXPORT CRC32_ProcessByte( uint32_t *pulCRC, byte ch )\n{\n\tuint32_t\tulCrc = *pulCRC;\n\n\t*pulCRC = crc32table[((byte)ulCrc ^ ch)] ^ (ulCrc >> 8);\n}\n\nvoid GAME_EXPORT CRC32_ProcessBuffer( uint32_t *pulCRC, const void *pBuffer, int nBuffer )\n{\n\tuint32_t\tulCrc = *pulCRC, tmp;\n\tbyte\t*pb = (byte *)pBuffer;\n\n\twhile( nBuffer >= sizeof( uint64_t ))\n\t{\n\t\tmemcpy( &tmp, pb, sizeof( tmp ));\n\t\tulCrc ^= LittleLong( tmp );\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tmemcpy( &tmp, pb + sizeof( tmp ), sizeof( tmp ));\n\t\tulCrc ^= LittleLong( tmp );\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tnBuffer -= sizeof( uint64_t );\n\t\tpb += sizeof( uint64_t );\n\t}\n\n\tif( nBuffer & sizeof( uint32_t ))\n\t{\n\t\tmemcpy( &tmp, pb, sizeof( tmp ));\n\t\tulCrc ^= LittleLong( tmp );\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tulCrc  = crc32table[(byte)ulCrc] ^ (ulCrc >> 8);\n\t\tnBuffer -= sizeof( uint32_t );\n\t\tpb += sizeof( uint32_t );\n\t}\n\n\twhile( nBuffer-- )\n\t{\n\t\tulCrc  = crc32table[((byte)ulCrc ^ *pb++)] ^ (ulCrc >> 8);\n\t}\n\n\t*pulCRC = ulCrc;\n}\n\n/*\n====================\nCRC32_BlockSequence\n\nFor proxy protecting\n====================\n*/\nbyte CRC32_BlockSequence( byte *base, int length, int sequence )\n{\n\tuint32_t\tCRC;\n\tchar\t*ptr;\n\tchar\tbuffer[64];\n\n\tif( sequence < 0 ) sequence = abs( sequence );\n\tptr = (char *)crc32table + (sequence % 0x3FC);\n\n\tif( length > 60 ) length = 60;\n\tmemcpy( buffer, base, length );\n\n\tbuffer[length+0] = ptr[0];\n\tbuffer[length+1] = ptr[1];\n\tbuffer[length+2] = ptr[2];\n\tbuffer[length+3] = ptr[3];\n\n\tlength += 4;\n\n\tCRC32_Init( &CRC );\n\tCRC32_ProcessBuffer( &CRC, buffer, length );\n\tCRC = CRC32_Final( CRC );\n\n\treturn (byte)CRC;\n}\n\nvoid MD5Transform( uint buf[4], const uint in[16] );\n\n/*\n===================\nMD5Update\n\nUpdate context to reflect the concatenation of another buffer full of bytes.\n===================\n*/\nvoid MD5Update( MD5Context_t *ctx, const byte *buf, uint len )\n{\n\tuint\tt;\n\n\t// update bitcount\n\tt = ctx->bits[0];\n\n\tif(( ctx->bits[0] = t + ((uint) len << 3 )) < t )\n\t\tctx->bits[1]++;\t// carry from low to high\n\tctx->bits[1] += len >> 29;\n\n\tt = (t >> 3) & 0x3f;\t// bytes already in shsInfo->data\n\n\t// handle any leading odd-sized chunks\n\tif( t )\n\t{\n\t\tbyte\t*p = (byte *)ctx->in + t;\n\n\t\tt = 64 - t;\n\t\tif( len < t )\n\t\t{\n\t\t\tmemcpy( p, buf, len );\n\t\t\treturn;\n\t\t}\n\n\t\tmemcpy( p, buf, t );\n\t\tMD5Transform( ctx->buf, ctx->in );\n\t\tbuf += t;\n\t\tlen -= t;\n\t}\n\n\t// process data in 64-byte chunks\n\twhile( len >= 64 )\n\t{\n\t\tmemcpy( ctx->in, buf, 64 );\n\t\tMD5Transform( ctx->buf, ctx->in );\n\t\tbuf += 64;\n\t\tlen -= 64;\n\t}\n\n\t// handle any remaining bytes of data.\n\tmemcpy( ctx->in, buf, len );\n}\n\n/*\n===============\nMD5Final\n\nFinal wrapup - pad to 64-byte boundary with the bit pattern\n1 0* (64-bit count of bits processed, MSB-first)\n===============\n*/\nvoid MD5Final( byte digest[16], MD5Context_t *ctx )\n{\n\tuint\tcount;\n\tbyte\t*p;\n\n\t// compute number of bytes mod 64\n\tcount = ( ctx->bits[0] >> 3 ) & 0x3F;\n\n\t// set the first char of padding to 0x80.\n\t// this is safe since there is always at least one byte free\n\tp = (byte*)ctx->in + count;\n\t*p++ = 0x80;\n\n\t// bytes of padding needed to make 64 bytes\n\tcount = 64 - 1 - count;\n\n\t// pad out to 56 mod 64\n\tif( count < 8 )\n\t{\n\n\t\t// two lots of padding: pad the first block to 64 bytes\n\t\tmemset( p, 0, count );\n\t\tMD5Transform( ctx->buf, ctx->in );\n\n\t\t// now fill the next block with 56 bytes\n\t\tmemset( ctx->in, 0, 56 );\n\t}\n\telse\n\t{\n\t\t// pad block to 56 bytes\n\t\tmemset( p, 0, count - 8 );\n\t}\n\n\t// append length in bits and transform\n\tctx->in[14] = ctx->bits[0];\n\tctx->in[15] = ctx->bits[1];\n\n\tMD5Transform( ctx->buf, ctx->in );\n\tmemcpy( digest, ctx->buf, 16 );\n\tmemset( ctx, 0, sizeof( *ctx ));\t// in case it's sensitive\n}\n\n// The four core functions\n#define F1( x, y, z ) ( z ^ ( x & ( y ^ z )))\n#define F2( x, y, z ) F1( z, x, y )\n#define F3( x, y, z ) ( x ^ y ^ z )\n#define F4( x, y, z ) ( y ^ ( x | ~z ))\n\n// this is the central step in the MD5 algorithm.\n#define MD5STEP( f, w, x, y, z, data, s )\t( w += f( x, y, z ) + data,  w = w << s|w >> (32 - s), w += x )\n\n/*\n=================\nMD5Transform\n\nThe core of the MD5 algorithm, this alters an existing MD5 hash to\nreflect the addition of 16 longwords of new data.  MD5Update blocks\nthe data and converts bytes into longwords for this routine.\n=================\n*/\nvoid MD5Transform( uint buf[4], const uint in[16] )\n{\n\tregister uint\ta, b, c, d;\n\n\ta = buf[0];\n\tb = buf[1];\n\tc = buf[2];\n\td = buf[3];\n\n\tMD5STEP( F1, a, b, c, d, in[0] + 0xd76aa478, 7 );\n\tMD5STEP( F1, d, a, b, c, in[1] + 0xe8c7b756, 12 );\n\tMD5STEP( F1, c, d, a, b, in[2] + 0x242070db, 17 );\n\tMD5STEP( F1, b, c, d, a, in[3] + 0xc1bdceee, 22 );\n\tMD5STEP( F1, a, b, c, d, in[4] + 0xf57c0faf, 7 );\n\tMD5STEP( F1, d, a, b, c, in[5] + 0x4787c62a, 12 );\n\tMD5STEP( F1, c, d, a, b, in[6] + 0xa8304613, 17 );\n\tMD5STEP( F1, b, c, d, a, in[7] + 0xfd469501, 22 );\n\tMD5STEP( F1, a, b, c, d, in[8] + 0x698098d8, 7 );\n\tMD5STEP( F1, d, a, b, c, in[9] + 0x8b44f7af, 12 );\n\tMD5STEP( F1, c, d, a, b, in[10] + 0xffff5bb1, 17 );\n\tMD5STEP( F1, b, c, d, a, in[11] + 0x895cd7be, 22 );\n\tMD5STEP( F1, a, b, c, d, in[12] + 0x6b901122, 7 );\n\tMD5STEP( F1, d, a, b, c, in[13] + 0xfd987193, 12 );\n\tMD5STEP( F1, c, d, a, b, in[14] + 0xa679438e, 17 );\n\tMD5STEP( F1, b, c, d, a, in[15] + 0x49b40821, 22 );\n\n\tMD5STEP( F2, a, b, c, d, in[1] + 0xf61e2562, 5 );\n\tMD5STEP( F2, d, a, b, c, in[6] + 0xc040b340, 9 );\n\tMD5STEP( F2, c, d, a, b, in[11] + 0x265e5a51, 14 );\n\tMD5STEP( F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20 );\n\tMD5STEP( F2, a, b, c, d, in[5] + 0xd62f105d, 5 );\n\tMD5STEP( F2, d, a, b, c, in[10] + 0x02441453, 9 );\n\tMD5STEP( F2, c, d, a, b, in[15] + 0xd8a1e681, 14 );\n\tMD5STEP( F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20 );\n\tMD5STEP( F2, a, b, c, d, in[9] + 0x21e1cde6, 5 );\n\tMD5STEP( F2, d, a, b, c, in[14] + 0xc33707d6, 9 );\n\tMD5STEP( F2, c, d, a, b, in[3] + 0xf4d50d87, 14 );\n\tMD5STEP( F2, b, c, d, a, in[8] + 0x455a14ed, 20 );\n\tMD5STEP( F2, a, b, c, d, in[13] + 0xa9e3e905, 5 );\n\tMD5STEP( F2, d, a, b, c, in[2] + 0xfcefa3f8, 9 );\n\tMD5STEP( F2, c, d, a, b, in[7] + 0x676f02d9, 14 );\n\tMD5STEP( F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20 );\n\n\tMD5STEP( F3, a, b, c, d, in[5] + 0xfffa3942, 4 );\n\tMD5STEP( F3, d, a, b, c, in[8] + 0x8771f681, 11 );\n\tMD5STEP( F3, c, d, a, b, in[11] + 0x6d9d6122, 16 );\n\tMD5STEP( F3, b, c, d, a, in[14] + 0xfde5380c, 23 );\n\tMD5STEP( F3, a, b, c, d, in[1] + 0xa4beea44, 4 );\n\tMD5STEP( F3, d, a, b, c, in[4] + 0x4bdecfa9, 11 );\n\tMD5STEP( F3, c, d, a, b, in[7] + 0xf6bb4b60, 16 );\n\tMD5STEP( F3, b, c, d, a, in[10] + 0xbebfbc70, 23 );\n\tMD5STEP( F3, a, b, c, d, in[13] + 0x289b7ec6, 4 );\n\tMD5STEP( F3, d, a, b, c, in[0] + 0xeaa127fa, 11 );\n\tMD5STEP( F3, c, d, a, b, in[3] + 0xd4ef3085, 16 );\n\tMD5STEP( F3, b, c, d, a, in[6] + 0x04881d05, 23 );\n\tMD5STEP( F3, a, b, c, d, in[9] + 0xd9d4d039, 4 );\n\tMD5STEP( F3, d, a, b, c, in[12] + 0xe6db99e5, 11 );\n\tMD5STEP( F3, c, d, a, b, in[15] + 0x1fa27cf8, 16 );\n\tMD5STEP( F3, b, c, d, a, in[2] + 0xc4ac5665, 23 );\n\n\tMD5STEP( F4, a, b, c, d, in[0] + 0xf4292244, 6 );\n\tMD5STEP( F4, d, a, b, c, in[7] + 0x432aff97, 10 );\n\tMD5STEP( F4, c, d, a, b, in[14] + 0xab9423a7, 15 );\n\tMD5STEP( F4, b, c, d, a, in[5] + 0xfc93a039, 21 );\n\tMD5STEP( F4, a, b, c, d, in[12] + 0x655b59c3, 6 );\n\tMD5STEP( F4, d, a, b, c, in[3] + 0x8f0ccc92, 10 );\n\tMD5STEP( F4, c, d, a, b, in[10] + 0xffeff47d, 15 );\n\tMD5STEP( F4, b, c, d, a, in[1] + 0x85845dd1, 21 );\n\tMD5STEP( F4, a, b, c, d, in[8] + 0x6fa87e4f, 6 );\n\tMD5STEP( F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10 );\n\tMD5STEP( F4, c, d, a, b, in[6] + 0xa3014314, 15 );\n\tMD5STEP( F4, b, c, d, a, in[13] + 0x4e0811a1, 21 );\n\tMD5STEP( F4, a, b, c, d, in[4] + 0xf7537e82, 6 );\n\tMD5STEP( F4, d, a, b, c, in[11] + 0xbd3af235, 10 );\n\tMD5STEP( F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15 );\n\tMD5STEP( F4, b, c, d, a, in[9] + 0xeb86d391, 21 );\n\n\tbuf[0] += a;\n\tbuf[1] += b;\n\tbuf[2] += c;\n\tbuf[3] += d;\n}\n\n/*\n============\nCOM_Hex2Char\n============\n*/\nstatic char COM_Hex2Char( uint8_t hex )\n{\n\tif( hex >= 0x0 && hex <= 0x9 )\n\t\thex += '0';\n\telse if( hex >= 0xA && hex <= 0xF )\n\t\thex += '7';\n\n\treturn (char)hex;\n}\n\n/*\n============\nCOM_Hex2String\n============\n*/\nstatic void COM_Hex2String( uint8_t hex, char *str )\n{\n\t*str++ = COM_Hex2Char( hex >> 4 );\n\t*str++ = COM_Hex2Char( hex & 0x0F );\n\t*str = '\\0';\n}\n\n/*\n=================\nMD5_Print\n\ntransform hash to hexadecimal printable symbols\n=================\n*/\nchar *MD5_Print( byte hash[16] )\n{\n\tstatic char\tszReturn[64];\n\tint\t\ti;\n\n\tmemset( szReturn, 0, 64 );\n\n\tfor( i = 0; i < 16; i++ )\n\t\tCOM_Hex2String( hash[i], &szReturn[i * 2] );\n\n\treturn szReturn;\n}\n\n/*\n=================\nCOM_HashKey\n\nreturns hash key for string\n=================\n*/\nuint COM_HashKey( const char *string, uint hashSize )\n{\n\tuint hashKey = 5381;\n\tunsigned char i;\n\n\twhile(( i = *string++ ))\n\t{\n\t\ti = Q_tolower( i );\n\t\thashKey = ( hashKey << 5 ) + hashKey + ( i & 0xDF );\n\t}\n\n\treturn hashKey & ( hashSize - 1 );\n}\n"
  },
  {
    "path": "public/crclib.h",
    "content": "/*\ncrclib.c - generate crc stuff\nCopyright (C) 2007 Uncle Mike\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef CRCLIB_H\n#define CRCLIB_H\n\n#include \"xash3d_types.h\"\n\ntypedef struct\n{\n\tuint\tbuf[4];\n\tuint\tbits[2];\n\tuint\tin[16];\n} MD5Context_t;\n\n#define CRC32_INIT_VALUE 0xFFFFFFFFUL\n#define CRC32_XOR_VALUE  0xFFFFFFFFUL\n\nstatic inline void CRC32_Init( uint32_t *pulCRC )\n{\n\t*pulCRC = CRC32_INIT_VALUE;\n}\n\nstatic inline uint32_t CRC32_Final( uint32_t pulCRC )\n{\n\treturn pulCRC ^ CRC32_XOR_VALUE;\n}\n\nbyte CRC32_BlockSequence( byte *base, int length, int sequence );\nvoid CRC32_ProcessBuffer( uint32_t *pulCRC, const void *pBuffer, int nBuffer );\nvoid CRC32_ProcessByte( uint32_t *pulCRC, byte ch );\n\n/*\n==================\nMD5Init\n\nStart MD5 accumulation.  Set bit count to 0 and buffer to mysterious initialization constants.\n==================\n*/\nstatic inline void MD5Init( MD5Context_t *ctx )\n{\n\tctx->buf[0] = 0x67452301;\n\tctx->buf[1] = 0xefcdab89;\n\tctx->buf[2] = 0x98badcfe;\n\tctx->buf[3] = 0x10325476;\n\n\tctx->bits[0] = 0;\n\tctx->bits[1] = 0;\n}\n\nvoid MD5Update( MD5Context_t *ctx, const byte *buf, uint len );\nvoid MD5Final( byte digest[16], MD5Context_t *ctx );\nuint COM_HashKey( const char *string, uint hashSize );\nchar *MD5_Print( byte hash[16] );\n\n#endif // CRCLIB_H\n"
  },
  {
    "path": "public/crtlib.c",
    "content": "/*\ncrtlib.c - internal stdlib\nCopyright (C) 2011 Uncle Mike\nCopyright (c) QuakeSpasm contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include <math.h>\n#include <stdarg.h>\n#include <time.h>\n#include \"stdio.h\"\n#include \"crtlib.h\"\n#include \"xash3d_mathlib.h\"\n\nvoid Q_strnlwr( const char *in, char *out, size_t size_out )\n{\n\tsize_t len, i;\n\n\tlen = Q_strncpy( out, in, size_out );\n\n\tfor( i = 0; i < len; i++ )\n\t\tout[i] = Q_tolower( out[i] );\n}\n\nint Q_atoi_hex( int sign, const char *str )\n{\n\tint c, val = 0;\n\n\tif( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' ))\n\t\tstr += 2;\n\n\twhile( 1 )\n\t{\n\t\tc = *str++;\n\t\tif( c >= '0' && c <= '9' ) val = (val<<4) + c - '0';\n\t\telse if( c >= 'a' && c <= 'f' ) val = (val<<4) + c - 'a' + 10;\n\t\telse if( c >= 'A' && c <= 'F' ) val = (val<<4) + c - 'A' + 10;\n\t\telse return val * sign;\n\t}\n}\n\nstatic int Q_atoi_character( int sign, const char *str )\n{\n\treturn sign * str[1];\n}\n\nstatic const char *Q_atoi_strip_whitespace( const char *str )\n{\n\twhile( str && *str == ' ' )\n\t\tstr++;\n\n\treturn str;\n}\n\nint Q_atoi( const char *str )\n{\n\tint val = 0;\n\tint c, sign;\n\n\tif( !COM_CheckString( str ))\n\t\treturn 0;\n\n\tstr = Q_atoi_strip_whitespace( str );\n\n\tif( !COM_CheckString( str ))\n\t\treturn 0;\n\n\tif( *str == '-' )\n\t{\n\t\tsign = -1;\n\t\tstr++;\n\t}\n\telse sign = 1;\n\n\t// check for hex\n\tif( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' ))\n\t\treturn Q_atoi_hex( sign, str );\n\n\t// check for character\n\tif( str[0] == '\\'' )\n\t\treturn Q_atoi_character( sign, str );\n\n\t// assume decimal\n\twhile( 1 )\n\t{\n\t\tc = *str++;\n\t\tif( c < '0' || c > '9' )\n\t\t\treturn val * sign;\n\t\tval = val * 10 + c - '0';\n\t}\n\treturn 0;\n}\n\nfloat Q_atof( const char *str )\n{\n\tdouble\tval = 0;\n\tint\tc, sign, decimal, total;\n\n\tif( !COM_CheckString( str ))\n\t\treturn 0;\n\n\tstr = Q_atoi_strip_whitespace( str );\n\n\tif( !COM_CheckString( str ))\n\t\treturn 0;\n\n\tif( *str == '-' )\n\t{\n\t\tsign = -1;\n\t\tstr++;\n\t}\n\telse sign = 1;\n\n\t// check for hex\n\tif( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' ))\n\t\treturn Q_atoi_hex( sign, str );\n\n\t// check for character\n\tif( str[0] == '\\'' )\n\t\treturn Q_atoi_character( sign, str );\n\n\t// assume decimal\n\tdecimal = -1;\n\ttotal = 0;\n\n\twhile( 1 )\n\t{\n\t\tc = *str++;\n\t\tif( c == '.' )\n\t\t{\n\t\t\tdecimal = total;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( c < '0' || c > '9' )\n\t\t\tbreak;\n\t\tval = val * 10 + c - '0';\n\t\ttotal++;\n\t}\n\n\tif( decimal == -1 )\n\t\treturn val * sign;\n\n\twhile( total > decimal )\n\t{\n\t\tval /= 10;\n\t\ttotal--;\n\t}\n\n\treturn val * sign;\n}\n\nvoid Q_atov( float *vec, const char *str, size_t siz )\n{\n\tconst char *pstr, *pfront;\n\tint\tj;\n\n\tmemset( vec, 0, sizeof( *vec ) * siz );\n\tpstr = pfront = str;\n\n\tfor( j = 0; j < siz; j++ )\n\t{\n\t\tvec[j] = Q_atof( pfront );\n\n\t\t// valid separator is space\n\t\twhile( *pstr && *pstr != ' ' )\n\t\t\tpstr++;\n\n\t\tif( !*pstr ) break;\n\t\tpstr++;\n\t\tpfront = pstr;\n\t}\n}\n\nstatic qboolean Q_starcmp( const char *pattern, const char *text )\n{\n\tchar\t\tc, c1;\n\tconst char\t*p = pattern, *t = text;\n\n\twhile(( c = *p++ ) == '?' || c == '*' )\n\t{\n\t\tif( c == '?' && *t++ == '\\0' )\n\t\t\treturn false;\n\t}\n\n\tif( c == '\\0' ) return true;\n\n\tfor( c1 = (( c == '\\\\' ) ? *p : c ); ; )\n\t{\n\t\tif( Q_tolower( *t ) == c1 && Q_stricmpext( p - 1, t ))\n\t\t\treturn true;\n\t\tif( *t++ == '\\0' ) return false;\n\t}\n}\n\nqboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlength )\n{\n\tsize_t  i = 0;\n\tchar\tc;\n\n\twhile(( c = *pattern++ ) != '\\0' )\n\t{\n\t\ti++;\n\n\t\tswitch( c )\n\t\t{\n\t\tcase '?':\n\t\t\tif( *text++ == '\\0' )\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase '\\\\':\n\t\t\tif( Q_tolower( *pattern++ ) != Q_tolower( *text++ ))\n\t\t\t\treturn false;\n\t\t\tbreak;\n\t\tcase '*':\n\t\t\treturn Q_starcmp( pattern, text );\n\t\tdefault:\n\t\t\tif( Q_tolower( c ) != Q_tolower( *text++ ))\n\t\t\t\treturn false;\n\t\t}\n\t}\n\treturn ( *text == '\\0' ) || i == minimumlength;\n}\n\nqboolean Q_stricmpext( const char *pattern, const char *text )\n{\n\treturn Q_strnicmpext( pattern, text, ~((size_t)0) );\n}\n\nconst byte *Q_memmem( const byte *haystack, size_t haystacklen, const byte *needle, size_t needlelen )\n{\n\tconst byte *i;\n\n\t// quickly find first matching symbol\n\twhile( haystacklen && ( i = memchr( haystack, needle[0], haystacklen )))\n\t{\n\t\tif( !memcmp( i, needle, needlelen ))\n\t\t\treturn i;\n\n\t\t// skip one byte\n\t\ti++;\n\n\t\thaystacklen -= i - haystack;\n\t\thaystack = i;\n\t}\n\n\treturn NULL;\n}\n\nvoid Q_memor( byte *XASH_RESTRICT dst, const byte *XASH_RESTRICT src, size_t len )\n{\n\tsize_t i;\n\tfor( i = 0; i < len; i++ ) // msvc likes to optimize this loop form\n\t\tdst[i] |= src[i];\n}\n\nconst char* Q_timestamp( int format )\n{\n\tstatic string\ttimestamp;\n\ttime_t\t\tcrt_time;\n\tconst struct tm\t*crt_tm;\n\n\ttime( &crt_time );\n\tcrt_tm = localtime( &crt_time );\n\n\tswitch( format )\n\t{\n\tcase TIME_FULL:\n\t\t// Build the full timestamp (ex: \"Apr03 2007 [23:31.55]\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%b%d %Y [%H:%M.%S]\", crt_tm );\n\t\tbreak;\n\tcase TIME_DATE_ONLY:\n\t\t// Build the date stamp only (ex: \"Apr03 2007\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%b%d %Y\", crt_tm );\n\t\tbreak;\n\tcase TIME_TIME_ONLY:\n\t\t// Build the time stamp only (ex: \"23:31.55\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%H:%M.%S\", crt_tm );\n\t\tbreak;\n\tcase TIME_NO_SECONDS:\n\t\t// Build the time stamp exclude seconds (ex: \"13:46\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%H:%M\", crt_tm );\n\t\tbreak;\n\tcase TIME_YEAR_ONLY:\n\t\t// Build the date stamp year only (ex: \"2006\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%Y\", crt_tm );\n\t\tbreak;\n\tcase TIME_FILENAME:\n\t\t// Build a timestamp that can use for filename (ex: \"Nov2006-26 (19.14.28)\");\n\t\tstrftime( timestamp, sizeof( timestamp ), \"%b%Y-%d_%H.%M.%S\", crt_tm );\n\t\tbreak;\n\tdefault:\n\t\tQ_snprintf( timestamp, sizeof( timestamp ), \"%s: unknown format %d\", __func__, format );\n\t\tbreak;\n\t}\n\n\treturn timestamp;\n}\n\n#if !HAVE_STRCASESTR\nchar *Q_stristr( const char *string, const char *string2 )\n{\n\tint\tc;\n\tsize_t\tlen;\n\n\tif( !string || !string2 ) return NULL;\n\n\tc = Q_tolower( *string2 );\n\tlen = Q_strlen( string2 );\n\n\twhile( string )\n\t{\n\t\tfor( ; *string && Q_tolower( *string ) != c; string++ );\n\n\t\tif( *string )\n\t\t{\n\t\t\tif( !Q_strnicmp( string, string2, len ))\n\t\t\t\tbreak;\n\t\t\tstring++;\n\t\t}\n\t\telse return NULL;\n\t}\n\treturn (char *)string;\n}\n#endif // !defined( HAVE_STRCASESTR )\n\nint Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args )\n{\n\tint\tresult;\n\n\tif( unlikely( buffersize == 0 ))\n\t\treturn -1; // report as overflow\n\n#ifndef _MSC_VER\n\tresult = vsnprintf( buffer, buffersize, format, args );\n#else\n\t__try\n\t{\n\t\tresult = _vsnprintf( buffer, buffersize, format, args );\n\t}\n\n\t// to prevent crash while output\n\t__except( EXCEPTION_EXECUTE_HANDLER )\n\t{\n\t\tQ_strncpy( buffer, \"^1sprintf throw exception^7\\n\", buffersize );\n\t\tresult = buffersize;\n\t}\n#endif\n\n\tif( result >= buffersize )\n\t{\n\t\tbuffer[buffersize - 1] = '\\0';\n\t\treturn -1;\n\t}\n\n\treturn result;\n}\n\nint Q_snprintf( char *buffer, size_t buffersize, const char *format, ... )\n{\n\tva_list\targs;\n\tint\tresult;\n\n\tva_start( args, format );\n\tresult = Q_vsnprintf( buffer, buffersize, format, args );\n\tva_end( args );\n\n\treturn result;\n}\n\nvoid COM_StripColors( const char *in, char *out )\n{\n\twhile( *in )\n\t{\n\t\tif( IsColorString( in ))\n\t\t\tin += 2;\n\t\telse *out++ = *in++;\n\t}\n\t*out = '\\0';\n}\n\nchar *Q_pretifymem( float value, int digitsafterdecimal )\n{\n\tstatic char\toutput[8][32];\n\tstatic int\tcurrent;\n\tconst float onekb = 1024.0f;\n\tconst float onemb = onekb * onekb;\n\tconst char *suffix;\n\tchar\t\t*out = output[current];\n\tchar\t\tval[32], *i, *o, *dot;\n\tint\t\tpos;\n\n\tcurrent = ( current + 1 ) & ( 8 - 1 );\n\n\t// first figure out which bin to use\n\tif( value > onemb )\n\t{\n\t\tvalue /= onemb;\n\t\tsuffix = \"Mb\";\n\t}\n\telse if( value > onekb )\n\t{\n\t\tvalue /= onekb;\n\t\tsuffix = \"Kb\";\n\t}\n\telse\n\t{\n\t\tsuffix = \"bytes\";\n\t}\n\n\t// if it's basically integral, don't do any decimals\n\tif( fabs( value - (int)value ) < 0.00001f || digitsafterdecimal <= 0 )\n\t\tQ_snprintf( val, sizeof( val ), \"%i %s\", (int)Q_rint( value ), suffix );\n\telse if( digitsafterdecimal >= 1 )\n\t\tQ_snprintf( val, sizeof( val ), \"%.*f %s\", digitsafterdecimal, (double)value, suffix );\n\n\t// copy from in to out\n\ti = val;\n\to = out;\n\n\t// search for decimal or if it was integral, find the space after the raw number\n\tdot = Q_strchr( i, '.' );\n\tif( !dot ) dot = Q_strchr( i, ' ' );\n\n\tpos = dot - i;\t// compute position of dot\n\tpos -= 3;\t\t// don't put a comma if it's <= 3 long\n\n\twhile( *i )\n\t{\n\t\t// if pos is still valid then insert a comma every third digit, except if we would be\n\t\t// putting one in the first spot\n\t\tif( pos >= 0 && !( pos % 3 ))\n\t\t{\n\t\t\t// never in first spot\n\t\t\tif( o != out ) *o++ = ',';\n\t\t}\n\n\t\tpos--;\t\t// count down comma position\n\t\t*o++ = *i++;\t// copy rest of data as normal\n\t}\n\t*o = 0; // terminate\n\n\treturn out;\n}\n\n/*\n============\nCOM_FileBase\n\nExtracts the base name of a file (no path, no extension, assumes '/' as path separator)\na1ba: adapted and simplified version from QuakeSpasm\n============\n*/\nvoid COM_FileBase( const char *in, char *out, size_t size )\n{\n\tconst char *dot, *slash, *s;\n\tsize_t len;\n\n\tif( unlikely( !COM_CheckString( in ) || size <= 1 ))\n\t{\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tslash = in;\n\tdot = NULL;\n\tfor( s = in; *s; s++ )\n\t{\n\t\tif( *s == '/' || *s == '\\\\' )\n\t\t\tslash = s + 1;\n\n\t\tif( *s == '.' )\n\t\t\tdot = s;\n\t}\n\n\tif( dot == NULL || dot < slash )\n\t\tdot = s;\n\n\tlen = Q_min( size - 1, dot - slash );\n\n\tmemcpy( out, slash, len );\n\tout[len] = 0;\n}\n\n/*\n============\nCOM_FileExtension\n============\n*/\nconst char *COM_FileExtension( const char *in )\n{\n\tconst char *dot;\n\n\tdot = Q_strrchr( in, '.' );\n\n\t// quickly exit if there is no dot at all\n\tif( dot == NULL )\n\t\treturn \"\";\n\n\t// if there are any of these special symbols after the dot, the file has no extension\n\tif( Q_strpbrk( dot + 1, \"\\\\/:\" ))\n\t\treturn \"\";\n\n\treturn dot + 1;\n}\n\n/*\n============\nCOM_FileWithoutPath\n============\n*/\nconst char *COM_FileWithoutPath( const char *in )\n{\n\tconst char *separator, *backslash, *colon;\n\n\tseparator = Q_strrchr( in, '/' );\n\tbackslash = Q_strrchr( in, '\\\\' );\n\n\tif( !separator || separator < backslash )\n\t\tseparator = backslash;\n\n\tcolon = Q_strrchr( in, ':' );\n\n\tif( !separator || separator < colon )\n\t\tseparator = colon;\n\n\treturn separator ? separator + 1 : in;\n}\n\n/*\n============\nCOM_ExtractFilePath\n============\n*/\nvoid COM_ExtractFilePath( const char *path, char *dest )\n{\n\tconst char *src = path + Q_strlen( path ) - 1;\n\n\t// back up until a \\ or the start\n\twhile( src > path && !(*(src - 1) == '\\\\' || *(src - 1) == '/' ))\n\t\tsrc--;\n\n\tif( src > path )\n\t{\n\t\tmemcpy( dest, path, src - path );\n\t\tdest[src - path - 1] = 0; // cutoff backslash\n\t}\n\telse dest[0] = 0; // file without path\n}\n\n/*\n============\nCOM_StripExtension\n============\n*/\nvoid COM_StripExtension( char *path )\n{\n\tsize_t\tlength;\n\n\tlength = Q_strlen( path );\n\n\tif( length > 0 )\n\t\tlength--;\n\n\twhile( length > 0 && path[length] != '.' )\n\t{\n\t\tlength--;\n\t\tif( path[length] == '/' || path[length] == '\\\\' || path[length] == ':' )\n\t\t\treturn; // no extension\n\t}\n\n\tif( length ) path[length] = 0;\n}\n\n/*\n==================\nCOM_DefaultExtension\n==================\n*/\nvoid COM_DefaultExtension( char *path, const char *extension, size_t size )\n{\n\tconst char\t*src;\n\tsize_t\t\t len;\n\n\t// if path doesn't have a .EXT, append extension\n\t// (extension should include the .)\n\tlen = Q_strlen( path );\n\tsrc = path + len - 1;\n\n\twhile( *src != '/' && src != path )\n\t{\n\t\t// it has an extension\n\t\tif( *src == '.' ) return;\n\t\tsrc--;\n\t}\n\n\tQ_strncpy( &path[len], extension, size - len );\n}\n\n/*\n==================\nCOM_ReplaceExtension\n==================\n*/\nvoid COM_ReplaceExtension( char *path, const char *extension, size_t size )\n{\n\tCOM_StripExtension( path );\n\tCOM_DefaultExtension( path, extension, size );\n}\n\n/*\n============\nCOM_RemoveLineFeed\n============\n*/\nvoid COM_RemoveLineFeed( char *str, size_t bufsize )\n{\n\tsize_t i;\n\n\tfor( i = 0; i < bufsize && *str != '\\0'; i++, str++ )\n\t{\n\t\tif( *str == '\\r' || *str == '\\n' )\n\t\t\t*str = '\\0';\n\t}\n}\n\n/*\n============\nCOM_PathSlashFix\n\nensure directory path always ends on forward slash\n============\n*/\nvoid COM_PathSlashFix( char *path )\n{\n\tsize_t len = Q_strlen( path );\n\n\tif( path[len - 1] == '\\\\' )\n\t{\n\t\tpath[len - 1] = '/';\n\t}\n\telse if( path[len - 1] != '/' )\n\t{\n\t\tpath[len] = '/';\n\t\tpath[len + 1] = '\\0';\n\t}\n}\n\n/*\n==============\nCOM_IsSingleChar\n\ninterpert this character as single\n==============\n*/\nstatic int COM_IsSingleChar( unsigned int flags, char c )\n{\n\tif( c == '{' || c == '}' || c == '\\'' || c == ',' )\n\t\treturn true;\n\n\tif( !FBitSet( flags, PFILE_IGNOREBRACKET ) && ( c == ')' || c == '(' ))\n\t\treturn true;\n\n\tif( FBitSet( flags, PFILE_HANDLECOLON ) && c == ':' )\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n==============\nCOM_ParseFile\n\ntext parser\n==============\n*/\nchar *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen, qboolean *quoted )\n{\n\tint\tc, len = 0;\n\tqboolean overflow = false;\n\n\tif( quoted )\n\t\t*quoted = false;\n\n\tif( !token || !size )\n\t{\n\t\tif( plen ) *plen = 0;\n\t\treturn NULL;\n\t}\n\n\ttoken[0] = 0;\n\n\tif( !data )\n\t\treturn NULL;\n// skip whitespace\nskipwhite:\n\twhile(( c = ((byte)*data)) <= ' ' )\n\t{\n\t\tif( c == 0 )\n\t\t{\n\t\t\tif( plen ) *plen = overflow ? -1 : len;\n\t\t\treturn NULL;\t// end of file;\n\t\t}\n\t\tdata++;\n\t}\n\n\t// skip // or #, if requested, comments\n\tif(( c == '/' && data[1] == '/' ) || ( c == '#' && FBitSet( flags, PFILE_IGNOREHASHCMT )))\n\t{\n\t\twhile( *data && *data != '\\n' )\n\t\t\tdata++;\n\t\tgoto skipwhite;\n\t}\n\n\t// handle quoted strings specially\n\tif( c == '\\\"' )\n\t{\n\t\tif( quoted )\n\t\t\t*quoted = true;\n\n\t\tdata++;\n\t\twhile( 1 )\n\t\t{\n\t\t\tc = (byte)*data;\n\n\t\t\t// unexpected line end\n\t\t\tif( !c )\n\t\t\t{\n\t\t\t\ttoken[len] = 0;\n\t\t\t\tif( plen ) *plen = overflow ? -1 : len;\n\t\t\t\treturn data;\n\t\t\t}\n\t\t\tdata++;\n\n\t\t\tif( c == '\\\\' && *data == '\"' )\n\t\t\t{\n\t\t\t\tif( len + 1 < size )\n\t\t\t\t{\n\t\t\t\t\ttoken[len] = (byte)*data;\n\t\t\t\t\tlen++;\n\t\t\t\t}\n\t\t\t\telse overflow = true;\n\n\t\t\t\tdata++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif( c == '\\\"' )\n\t\t\t{\n\t\t\t\ttoken[len] = 0;\n\t\t\t\tif( plen ) *plen = overflow ? -1 : len;\n\t\t\t\treturn data;\n\t\t\t}\n\n\t\t\tif( len + 1 < size )\n\t\t\t{\n\t\t\t\ttoken[len] = c;\n\t\t\t\tlen++;\n\t\t\t}\n\t\t\telse overflow = true;\n\t\t}\n\t}\n\n\t// parse single characters\n\tif( COM_IsSingleChar( flags, c ))\n\t{\n\t\tif( size >= 2 ) // char and \\0\n\t\t{\n\t\t\ttoken[len] = c;\n\t\t\tlen++;\n\t\t\ttoken[len] = 0;\n\t\t\tif( plen ) *plen = overflow ? -1 : len;\n\t\t\treturn data + 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// couldn't pass anything\n\t\t\ttoken[0] = 0;\n\t\t\tif( plen ) *plen = overflow ? -1 : len;\n\t\t\treturn data;\n\t\t}\n\t}\n\n\t// parse a regular word\n\tdo\n\t{\n\t\tif( len + 1 < size )\n\t\t{\n\t\t\ttoken[len] = c;\n\t\t\tlen++;\n\t\t}\n\t\telse overflow = true;\n\n\t\tdata++;\n\t\tc = ((byte)*data);\n\n\t\tif( COM_IsSingleChar( flags, c ))\n\t\t\tbreak;\n\t} while( c > 32 );\n\n\ttoken[len] = 0;\n\n\tif( plen ) *plen = overflow ? -1 : len;\n\n\treturn data;\n}\n\nint matchpattern( const char *in, const char *pattern, qboolean caseinsensitive )\n{\n\tconst char *separators = \"/\\\\:\";\n\n\tif( !Q_strcmp( pattern, \"*\" ))\n\t\tseparators = \"\";\n\n\treturn matchpattern_with_separator( in, pattern, caseinsensitive, separators, false );\n}\n\n// wildcard_least_one: if true * matches 1 or more characters\n//                     if false * matches 0 or more characters\nint matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one )\n{\n\tint c1, c2;\n\n\twhile( *pattern )\n\t{\n\t\tswitch( *pattern )\n\t\t{\n\t\tcase 0:\n\t\t\treturn 1; // end of pattern\n\t\tcase '?': // match any single character\n\t\t\tif( *in == 0 || Q_strchr( separators, *in ))\n\t\t\t\treturn 0; // no match\n\t\t\tin++;\n\t\t\tpattern++;\n\t\t\tbreak;\n\t\tcase '*': // match anything until following string\n\t\t\tif( wildcard_least_one )\n\t\t\t{\n\t\t\t\tif( *in == 0 || Q_strchr( separators, *in ))\n\t\t\t\t\treturn 0; // no match\n\t\t\t\tin++;\n\t\t\t}\n\t\t\tpattern++;\n\t\t\twhile( *in )\n\t\t\t{\n\t\t\t\tif( Q_strchr(separators, *in ))\n\t\t\t\t\tbreak;\n\t\t\t\t// see if pattern matches at this offset\n\t\t\t\tif( matchpattern_with_separator(in, pattern, caseinsensitive, separators, wildcard_least_one ))\n\t\t\t\t\treturn 1;\n\t\t\t\t// nope, advance to next offset\n\t\t\t\tin++;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif( *in != *pattern )\n\t\t\t{\n\t\t\t\tif( !caseinsensitive )\n\t\t\t\t\treturn 0; // no match\n\t\t\t\tc1 = *in;\n\t\t\t\tif( c1 >= 'A' && c1 <= 'Z' )\n\t\t\t\t\tc1 += 'a' - 'A';\n\t\t\t\tc2 = *pattern;\n\t\t\t\tif( c2 >= 'A' && c2 <= 'Z' )\n\t\t\t\t\tc2 += 'a' - 'A';\n\t\t\t\tif( c1 != c2 )\n\t\t\t\t\treturn 0; // no match\n\t\t\t}\n\t\t\tin++;\n\t\t\tpattern++;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif( *in )\n\t\treturn 0; // reached end of pattern but not end of input\n\treturn 1; // success\n}\n\n"
  },
  {
    "path": "public/crtlib.h",
    "content": "/*\ncrtlib.h - internal stdlib\nCopyright (C) 2011 Uncle Mike\nCopyright (C) Free Software Foundation, Inc\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef STDLIB_H\n#define STDLIB_H\n\n#include <string.h>\n#include <stdarg.h>\n#include <ctype.h>\n#include \"build.h\"\n#include \"xash3d_types.h\"\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n// timestamp modes\nenum\n{\n\tTIME_FULL = 0,\n\tTIME_DATE_ONLY,\n\tTIME_TIME_ONLY,\n\tTIME_NO_SECONDS,\n\tTIME_YEAR_ONLY,\n\tTIME_FILENAME,\n};\n\n// a1ba: not using BIT macro, so flags can be copypasted into\n// exported APIs headers and will not get warning in case of changing values\n#define PFILE_IGNOREBRACKET (1<<0)\n#define PFILE_HANDLECOLON   (1<<1)\n#define PFILE_IGNOREHASHCMT (1<<2)\n\n#define PFILE_TOKEN_MAX_LENGTH 1024\n#define PFILE_FS_TOKEN_MAX_LENGTH 512\n\n#ifdef __cplusplus\n#define restrict\n#endif // __cplusplus\n\n//\n// build.c\n//\nint Q_buildnum( void );\nint Q_buildnum_iso( const char *date );\nint Q_buildnum_compat( void );\nconst char *Q_PlatformStringByID( const int platform );\nconst char *Q_buildos( void );\nconst char *Q_ArchitectureStringByID( const int arch, const uint abi, const int endianness, const qboolean is64 );\nconst char *Q_buildarch( void );\nextern const char *g_buildcommit;\nextern const char *g_buildbranch;\nextern const char *g_build_date;\nextern const char *g_buildcommit_date;\n\n//\n// crtlib.c\n//\nvoid Q_strnlwr( const char *in, char *out, size_t size_out );\n#define Q_strlen( str ) (( str ) ? strlen(( str )) : 0 )\nint Q_atoi_hex( int sign, const char *str );\nint Q_atoi( const char *str );\nfloat Q_atof( const char *str );\nvoid Q_atov( float *vec, const char *str, size_t siz );\n#define Q_strchr  strchr\n#define Q_strrchr strrchr\nqboolean Q_stricmpext( const char *pattern, const char *text );\nqboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlen );\nconst byte *Q_memmem( const byte *haystack, size_t haystacklen, const byte *needle, size_t needlelen );\nvoid Q_memor( byte *XASH_RESTRICT dst, const byte *XASH_RESTRICT src, size_t len );\nconst char *Q_timestamp( int format ) RETURNS_NONNULL;\nint Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args );\nint Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) FORMAT_CHECK( 3 );\n#define Q_strpbrk strpbrk\nvoid COM_StripColors( const char *in, char *out );\n#define Q_memprint( val ) Q_pretifymem( val, 2 )\nchar *Q_pretifymem( float value, int digitsafterdecimal );\nvoid COM_FileBase( const char *in, char *out, size_t size );\nconst char *COM_FileExtension( const char *in ) RETURNS_NONNULL;\nvoid COM_DefaultExtension( char *path, const char *extension, size_t size );\nvoid COM_ReplaceExtension( char *path, const char *extension, size_t size );\nvoid COM_ExtractFilePath( const char *path, char *dest );\nconst char *COM_FileWithoutPath( const char *in );\nvoid COM_StripExtension( char *path );\nvoid COM_RemoveLineFeed( char *str, size_t bufsize );\nvoid COM_PathSlashFix( char *path );\n// return 0 on empty or null string, 1 otherwise\n#define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 )\n#define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 )\nchar *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len, qboolean *quoted );\n#define COM_ParseFile( data, token, size ) COM_ParseFileSafe( data, token, size, 0, NULL, NULL )\nint matchpattern( const char *in, const char *pattern, qboolean caseinsensitive );\nint matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one );\n\n//\n// dllhelpers.c\n//\ntypedef struct dllfunc_s\n{\n\tconst char\t*name;\n\tvoid\t\t**func;\n} dllfunc_t;\n\nvoid ClearExports( const dllfunc_t *funcs, size_t num_funcs );\nqboolean ValidateExports( const dllfunc_t *funcs, size_t num_funcs );\n\nstatic inline char Q_toupper( const char in )\n{\n\tchar\tout;\n\n\tif( in >= 'a' && in <= 'z' )\n\t\tout = in + 'A' - 'a';\n\telse out = in;\n\n\treturn out;\n}\n\nstatic inline char Q_tolower( const char in )\n{\n\tchar\tout;\n\n\tif( in >= 'A' && in <= 'Z' )\n\t\tout = in + 'a' - 'A';\n\telse out = in;\n\n\treturn out;\n}\n\nstatic inline qboolean Q_istype( const char *str, int (*istype)( int c ))\n{\n\tif( likely( str && *str ))\n\t{\n\t\twhile( istype( *str )) str++;\n\t\tif( !*str ) return true;\n\t}\n\treturn false;\n}\n\nstatic inline qboolean Q_isdigit( const char *str )\n{\n\treturn Q_istype( str, isdigit );\n}\n\nstatic inline qboolean Q_isalpha( const char *str )\n{\n\treturn Q_istype( str, isalpha );\n}\n\nstatic inline qboolean Q_isspace( const char *str )\n{\n\treturn Q_istype( str, isspace );\n}\n\nstatic inline int Q_strcmp( const char *s1, const char *s2 )\n{\n\tif( likely( s1 && s2 ))\n\t\treturn strcmp( s1, s2 );\n\treturn ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 );\n}\n\nstatic inline int Q_strncmp( const char *s1, const char *s2, size_t n )\n{\n\tif( likely( s1 && s2 ))\n\t\treturn strncmp( s1, s2, n );\n\treturn ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 );\n}\n\nstatic inline char *Q_strstr( const char *s1, const char *s2 )\n{\n\tif( likely( s1 && s2 ))\n\t\treturn (char *)strstr( s1, s2 );\n\treturn NULL;\n}\n\n// libc extensions, be careful what to enable or what not\nstatic inline size_t Q_strnlen( const char *str, size_t size )\n{\n#if HAVE_STRNLEN\n\treturn strnlen( str, size );\n#else\n\tconst char *p = (const char *)memchr( str, 0, size );\n\treturn p ? p - str : size;\n#endif\n}\n\nstatic inline size_t Q_strncpy( char *dst, const char *src, size_t size )\n{\n\tif( unlikely( !dst || !src || !size ))\n\t\treturn 0;\n#if HAVE_STRLCPY\n\treturn strlcpy( dst, src, size );\n#else\n\t{\n\t\tsize_t len = strlen( src );\n\n\t\tif( len >= size ) // check if truncate\n\t\t{\n\t\t\tmemcpy( dst, src, size );\n\t\t\tdst[size - 1] = 0;\n\t\t}\n\t\telse memcpy( dst, src, len + 1 );\n\n\t\treturn len; // count does not include NULL\n\t}\n#endif\n}\n\nstatic inline size_t Q_strncat( char *dst, const char *src, size_t size )\n{\n\tif( unlikely( !dst || !src || !size ))\n\t\treturn 0;\n#if HAVE_STRLCAT\n\treturn strlcat( dst, src, size );\n#else\n\t{\n\t\tsize_t slen = strlen( src );\n\t\tsize_t dlen = Q_strnlen( dst, size );\n\n\t\tif( dlen != size )\n\t\t{\n\t\t\tsize_t copy = size - dlen - 1;\n\n\t\t\tif( copy > slen )\n\t\t\t\tcopy = slen;\n\n\t\t\tmemcpy( &dst[dlen], src, copy );\n\t\t\tdst[dlen + copy] = 0;\n\t\t}\n\n\t\treturn dlen + slen;\n\t}\n#endif\n}\n\n#if HAVE_STRICMP || HAVE_STRCASECMP\nstatic inline int Q_stricmp( const char *s1, const char *s2 )\n{\n\tif( likely( s1 && s2 ))\n\t{\n#if HAVE_STRICMP\n\t\treturn stricmp( s1, s2 );\n#elif HAVE_STRCASECMP\n\t\treturn strcasecmp( s1, s2 );\n#endif\n\t}\n\treturn ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 );\n}\n#else\nint Q_stricmp( const char *s1, const char *s2 );\n#endif\n\n#if HAVE_STRICMP || HAVE_STRCASECMP\nstatic inline int Q_strnicmp( const char *s1, const char *s2, size_t n )\n{\n\tif( likely( s1 && s2 ))\n\t{\n#if HAVE_STRICMP\n\t\treturn strnicmp( s1, s2, n );\n#elif HAVE_STRCASECMP\n\t\treturn strncasecmp( s1, s2, n );\n#endif\n\t}\n\treturn ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 );\n}\n#else\nint Q_strnicmp( const char *s1, const char *s2, size_t n );\n#endif\n\n\n#if HAVE_STRCASESTR\nstatic inline char *Q_stristr( const char *s1, const char *s2 )\n{\n\tif( likely( s1 && s2 ))\n\t\treturn (char *)strcasestr( s1, s2 );\n\treturn NULL;\n}\n#else // !HAVE_STRCASESTR\nchar *Q_stristr( const char *s1, const char *s2 );\n#endif // !HAVE_STRCASESTR\n\n#if HAVE_STRCHRNUL\n#define Q_strchrnul strchrnul\n#else // !HAVE_STRCHRNUL\nstatic inline char *Q_strchrnul( const char *s, int c )\n{\n\tchar *p = (char *)Q_strchr( s, c );\n\tif( p ) return p;\n\treturn (char *)s + Q_strlen( s );\n}\n#endif // !HAVE_STRCHRNUL\n\n/*\n===========\nQ_splitstr\n\nsplits strings by a character\nif handler returns nonzero value, exists with that value\n===========\n*/\nstatic inline int Q_splitstr( char *str, int delim, void *userdata,\n\tint (*handler)( char *prev, char *next, void *userdata ))\n{\n\tchar *prev = str;\n\tchar *next = Q_strchrnul( prev, delim );\n\tint ret = 0;\n\n\tfor( ; ; prev = next + 1, next = Q_strchrnul( prev, delim ))\n\t{\n\t\tint ch = *next; // save next value if it's modified by handler\n\n\t\tret = handler( prev, next, userdata );\n\n\t\tif( !ch || ret != 0 )\n\t\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\n/*\n============\nCOM_FixSlashes\n\nChanges all '\\' characters into '/' characters, in place.\n============\n*/\nstatic inline void COM_FixSlashes( char *pname )\n{\n\twhile(( pname = Q_strchr( pname, '\\\\' )))\n\t\t*pname = '/';\n}\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif//STDLIB_H\n"
  },
  {
    "path": "public/dllhelpers.c",
    "content": "/*\ndllhelpers.c - dll exports helpers\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"crtlib.h\"\n\nvoid ClearExports( const dllfunc_t *funcs, size_t num_funcs )\n{\n\tsize_t i;\n\n\tfor( i = 0; i < num_funcs; i++ )\n\t\t*(funcs[i].func) = NULL;\n}\n\nqboolean ValidateExports( const dllfunc_t *funcs, size_t num_funcs )\n{\n\tsize_t i;\n\n\tfor( i = 0; i < num_funcs; i++ )\n\t{\n\t\tif( *(funcs[i].func) == NULL )\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "public/getopt.c",
    "content": "/*\t$NetBSD: getopt.c,v 1.29 2014/06/05 22:00:22 christos Exp $\t*/\n\n/*-\n * SPDX-License-Identifier: BSD-3-Clause\n *\n * Copyright (c) 1987, 1993, 1994\n *\tThe Regents of the University of California.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 3. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n#include \"build.h\"\n#if XASH_WIN32\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n\nint\topterr = 1,\t\t/* if error message should be printed */\n\toptind = 1,\t\t/* index into parent argv vector */\n\toptopt,\t\t\t/* character checked for validity */\n\toptreset;\t\t/* reset getopt */\nchar\t*optarg;\t\t/* argument associated with option */\n\n#define\tBADCH\t(int)'?'\n#define\tBADARG\t(int)':'\nstatic char EMSG[] = \"\";\n/*\n * getopt --\n *\tParse argc/argv argument vector.\n */\nint\ngetopt(int nargc, char * const nargv[], const char *ostr)\n{\n\tstatic char *place = EMSG;\t\t/* option letter processing */\n\tchar *oli;\t\t\t\t/* option letter list index */\n\n\tif (optreset || *place == 0) {\t\t/* update scanning pointer */\n\t\toptreset = 0;\n\t\tplace = nargv[optind];\n\t\tif (optind >= nargc || *place++ != '-') {\n\t\t\t/* Argument is absent or is not an option */\n\t\t\tplace = EMSG;\n\t\t\treturn (-1);\n\t\t}\n\t\toptopt = *place++;\n\t\tif (optopt == '-' && *place == 0) {\n\t\t\t/* \"--\" => end of options */\n\t\t\t++optind;\n\t\t\tplace = EMSG;\n\t\t\treturn (-1);\n\t\t}\n\t\tif (optopt == 0) {\n\t\t\t/* Solitary '-', treat as a '-' option\n\t\t\t   if the program (eg su) is looking for it. */\n\t\t\tplace = EMSG;\n\t\t\tif (strchr(ostr, '-') == NULL)\n\t\t\t\treturn (-1);\n\t\t\toptopt = '-';\n\t\t}\n\t} else\n\t\toptopt = *place++;\n\n\t/* See if option letter is one the caller wanted... */\n\tif (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) {\n\t\tif (*place == 0)\n\t\t\t++optind;\n\t\tif (opterr && *ostr != ':')\n\t\t\t(void)fprintf(stderr,\n\t\t\t    \"%s: illegal option -- %c\\n\", nargv[0],\n\t\t\t    optopt);\n\t\treturn (BADCH);\n\t}\n\n\t/* Does this option need an argument? */\n\tif (oli[1] != ':') {\n\t\t/* don't need argument */\n\t\toptarg = NULL;\n\t\tif (*place == 0)\n\t\t\t++optind;\n\t} else {\n\t\t/* Option-argument is either the rest of this argument or the\n\t\t   entire next argument. */\n\t\tif (*place)\n\t\t\toptarg = place;\n\t\telse if (oli[2] == ':')\n\t\t\t/*\n\t\t\t * GNU Extension, for optional arguments if the rest of\n\t\t\t * the argument is empty, we return NULL\n\t\t\t */\n\t\t\toptarg = NULL;\n\t\telse if (nargc > ++optind)\n\t\t\toptarg = nargv[optind];\n\t\telse {\n\t\t\t/* option-argument absent */\n\t\t\tplace = EMSG;\n\t\t\tif (*ostr == ':')\n\t\t\t\treturn (BADARG);\n\t\t\tif (opterr)\n\t\t\t\t(void)fprintf(stderr,\n\t\t\t\t    \"%s: option requires an argument -- %c\\n\",\n\t\t\t\t    nargv[0], optopt);\n\t\t\treturn (BADCH);\n\t\t}\n\t\tplace = EMSG;\n\t\t++optind;\n\t}\n\treturn (optopt);\t\t\t/* return option letter */\n}\n#endif // XASH_WIN32\n"
  },
  {
    "path": "public/getopt.h",
    "content": "#pragma once\n#ifndef GETOPT_H\n#define GETOPT_H\n#if XASH_WIN32\nextern int\t opterr;\nextern int\t optind;\nextern int\t optopt;\nextern int\t optreset;\nextern char\t*optarg;\n\nint\t\t getopt( int nargc, char * const nargv[], const char *ostr );\n#endif // XASH_WIN32\n#endif // GETOPT_H\n"
  },
  {
    "path": "public/ktx2.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define KTX2_IDENTIFIER_SIZE 12\n#define KTX2_IDENTIFIER \"\\xABKTX 20\\xBB\\r\\n\\x1A\\n\"\n\n/*\nstatic const char k_ktx2_identifier[ktx2_IDENTIFIER_SIZE] =\n{\n  '\\xAB', 'K', 'T', 'X', ' ', '2', '0', '\\xBB', '\\r', '\\n', '\\x1A', '\\n'\n};\n*/\n\ntypedef struct\n{\n\tuint32_t vkFormat;\n\tuint32_t typeSize;\n\tuint32_t pixelWidth;\n\tuint32_t pixelHeight;\n\tuint32_t pixelDepth;\n\tuint32_t layerCount;\n\tuint32_t faceCount;\n\tuint32_t levelCount;\n\tuint32_t supercompressionScheme;\n} ktx2_header_t;\n\ntypedef struct\n{\n\tuint32_t dfdByteOffset;\n\tuint32_t dfdByteLength;\n\tuint32_t kvdByteOffset;\n\tuint32_t kvdByteLength;\n\tuint64_t sgdByteOffset;\n\tuint64_t sgdByteLength;\n} ktx2_index_t;\n\ntypedef struct\n{\n\tuint64_t byteOffset;\n\tuint64_t byteLength;\n\tuint64_t uncompressedByteLength;\n} ktx2_level_t;\n\n#define KTX2_LEVELS_OFFSET ( KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ) + sizeof( ktx2_index_t ))\n\n#define KTX2_MINIMAL_HEADER_SIZE ( KTX2_LEVELS_OFFSET + sizeof( ktx2_level_t ))\n\n// These have the same values as VkFormat in vulkan_core.h\ntypedef enum\n{\n    KTX2_FORMAT_BC4_UNORM_BLOCK = 139,\n    KTX2_FORMAT_BC4_SNORM_BLOCK = 140,\n    KTX2_FORMAT_BC5_UNORM_BLOCK = 141,\n    KTX2_FORMAT_BC5_SNORM_BLOCK = 142,\n    KTX2_FORMAT_BC6H_UFLOAT_BLOCK = 143,\n    KTX2_FORMAT_BC6H_SFLOAT_BLOCK = 144,\n    KTX2_FORMAT_BC7_UNORM_BLOCK = 145,\n    KTX2_FORMAT_BC7_SRGB_BLOCK = 146,\n} ktx2_format_t;\n"
  },
  {
    "path": "public/matrixlib.c",
    "content": "/*\nmatrixlib.c - internal matrixlib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"xash3d_mathlib.h\"\n\n/*\n========================================================================\n\n\t\tMatrix3x4 operations\n\n========================================================================\n*/\nvoid Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3];\n\tout[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3];\n\tout[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3];\n}\n\nvoid Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] )\n{\n\tvec3_t\tdir;\n\n\tdir[0] = v[0] - in[0][3];\n\tdir[1] = v[1] - in[1][3];\n\tdir[2] = v[2] - in[2][3];\n\n\tout[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0];\n\tout[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1];\n\tout[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2];\n}\n\nvoid Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2];\n\tout[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2];\n\tout[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2];\n}\n\nvoid Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0];\n\tout[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1];\n\tout[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2];\n}\n\nvoid Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3];\n}\n\nvoid Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out )\n{\n\tfloat xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] );\n\n\tif( xyDist > 0.001f )\n\t{\n\t\t// enough here to get angles?\n\t\tout[0] = RAD2DEG( atan2( -in[2][0], xyDist ));\n\t\tout[1] = RAD2DEG( atan2( in[1][0], in[0][0] ));\n\t\tout[2] = RAD2DEG( atan2( in[2][1], in[2][2] ));\n\t}\n\telse\n\t{\n\t\t// forward is mostly Z, gimbal lock\n\t\tout[0] = RAD2DEG( atan2( -in[2][0], xyDist ));\n\t\tout[1] = RAD2DEG( atan2( -in[0][1], in[1][1] ));\n\t\tout[2] = 0.0f;\n\t}\n}\n\nvoid Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin )\n{\n\tout[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2];\n\tout[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2];\n\tout[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1];\n\n\tout[0][1] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2];\n\tout[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2];\n\tout[2][1] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0];\n\n\tout[0][2] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1];\n\tout[1][2] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0];\n\tout[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1];\n\n\tout[0][3] = origin[0];\n\tout[1][3] = origin[1];\n\tout[2][3] = origin[2];\n}\n\nvoid Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale )\n{\n\tfloat\tangle, sr, sp, sy, cr, cp, cy;\n\n\tif( angles[ROLL] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\t\tangle = angles[PITCH] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sp, &cp );\n\t\tangle = angles[ROLL] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sr, &cr );\n\n\t\tout[0][0] = (cp*cy) * scale;\n\t\tout[0][1] = (sr*sp*cy+cr*-sy) * scale;\n\t\tout[0][2] = (cr*sp*cy+-sr*-sy) * scale;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (cp*sy) * scale;\n\t\tout[1][1] = (sr*sp*sy+cr*cy) * scale;\n\t\tout[1][2] = (cr*sp*sy+-sr*cy) * scale;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = (-sp) * scale;\n\t\tout[2][1] = (sr*cp) * scale;\n\t\tout[2][2] = (cr*cp) * scale;\n\t\tout[2][3] = origin[2];\n\t}\n\telse if( angles[PITCH] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\t\tangle = angles[PITCH] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sp, &cp );\n\n\t\tout[0][0] = (cp*cy) * scale;\n\t\tout[0][1] = (-sy) * scale;\n\t\tout[0][2] = (sp*cy) * scale;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (cp*sy) * scale;\n\t\tout[1][1] = (cy) * scale;\n\t\tout[1][2] = (sp*sy) * scale;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = (-sp) * scale;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = (cp) * scale;\n\t\tout[2][3] = origin[2];\n\t}\n\telse if( angles[YAW] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\n\t\tout[0][0] = (cy) * scale;\n\t\tout[0][1] = (-sy) * scale;\n\t\tout[0][2] = 0.0f;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (sy) * scale;\n\t\tout[1][1] = (cy) * scale;\n\t\tout[1][2] = 0.0f;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = 0.0f;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = scale;\n\t\tout[2][3] = origin[2];\n\t}\n\telse\n\t{\n\t\tout[0][0] = scale;\n\t\tout[0][1] = 0.0f;\n\t\tout[0][2] = 0.0f;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = 0.0f;\n\t\tout[1][1] = scale;\n\t\tout[1][2] = 0.0f;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = 0.0f;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = scale;\n\t\tout[2][3] = origin[2];\n\t}\n}\n\n/*\n==================\nMatrix3x4_TransformAABB\n==================\n*/\nvoid Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const vec3_t maxs, vec3_t absmin, vec3_t absmax )\n{\n\tvec3_t\tlocalCenter, localExtents;\n\tvec3_t\tworldCenter, worldExtents;\n\n\tVectorAverage( mins, maxs, localCenter );\n\tVectorSubtract( maxs, localCenter, localExtents );\n\n\tMatrix3x4_VectorTransform( world, localCenter, worldCenter );\n\tworldExtents[0] = DotProductFabs( localExtents, world[0] );\t// auto-transposed!\n\tworldExtents[1] = DotProductFabs( localExtents, world[1] );\n\tworldExtents[2] = DotProductFabs( localExtents, world[2] );\n\n\tVectorSubtract( worldCenter, worldExtents, absmin );\n\tVectorAdd( worldCenter, worldExtents, absmax );\n}\n\n/*\n========================================================================\n\n\t\tMatrix4x4 operations\n\n========================================================================\n*/\nvoid Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3];\n\tout[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3];\n\tout[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3];\n}\n\nvoid Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] )\n{\n\tvec3_t\tdir;\n\n\tdir[0] = v[0] - in[0][3];\n\tdir[1] = v[1] - in[1][3];\n\tdir[2] = v[2] - in[2][3];\n\n\tout[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0];\n\tout[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1];\n\tout[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2];\n}\n\nvoid Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2];\n\tout[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2];\n\tout[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2];\n}\n\nvoid Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] )\n{\n\tout[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0];\n\tout[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1];\n\tout[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2];\n}\n\nvoid Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3];\n}\n\nvoid Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][3] = x;\n\tout[1][3] = y;\n\tout[2][3] = z;\n}\n\nvoid Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale )\n{\n\tfloat\tangle, sr, sp, sy, cr, cp, cy;\n\n\tif( angles[ROLL] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\t\tangle = angles[PITCH] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sp, &cp );\n\t\tangle = angles[ROLL] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sr, &cr );\n\n\t\tout[0][0] = (cp*cy) * scale;\n\t\tout[0][1] = (sr*sp*cy+cr*-sy) * scale;\n\t\tout[0][2] = (cr*sp*cy+-sr*-sy) * scale;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (cp*sy) * scale;\n\t\tout[1][1] = (sr*sp*sy+cr*cy) * scale;\n\t\tout[1][2] = (cr*sp*sy+-sr*cy) * scale;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = (-sp) * scale;\n\t\tout[2][1] = (sr*cp) * scale;\n\t\tout[2][2] = (cr*cp) * scale;\n\t\tout[2][3] = origin[2];\n\t\tout[3][0] = 0.0f;\n\t\tout[3][1] = 0.0f;\n\t\tout[3][2] = 0.0f;\n\t\tout[3][3] = 1.0f;\n\t}\n\telse if( angles[PITCH] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\t\tangle = angles[PITCH] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sp, &cp );\n\n\t\tout[0][0] = (cp*cy) * scale;\n\t\tout[0][1] = (-sy) * scale;\n\t\tout[0][2] = (sp*cy) * scale;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (cp*sy) * scale;\n\t\tout[1][1] = (cy) * scale;\n\t\tout[1][2] = (sp*sy) * scale;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = (-sp) * scale;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = (cp) * scale;\n\t\tout[2][3] = origin[2];\n\t\tout[3][0] = 0.0f;\n\t\tout[3][1] = 0.0f;\n\t\tout[3][2] = 0.0f;\n\t\tout[3][3] = 1.0f;\n\t}\n\telse if( angles[YAW] )\n\t{\n\t\tangle = angles[YAW] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sy, &cy );\n\n\t\tout[0][0] = (cy) * scale;\n\t\tout[0][1] = (-sy) * scale;\n\t\tout[0][2] = 0.0f;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = (sy) * scale;\n\t\tout[1][1] = (cy) * scale;\n\t\tout[1][2] = 0.0f;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = 0.0f;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = scale;\n\t\tout[2][3] = origin[2];\n\t\tout[3][0] = 0.0f;\n\t\tout[3][1] = 0.0f;\n\t\tout[3][2] = 0.0f;\n\t\tout[3][3] = 1.0f;\n\t}\n\telse\n\t{\n\t\tout[0][0] = scale;\n\t\tout[0][1] = 0.0f;\n\t\tout[0][2] = 0.0f;\n\t\tout[0][3] = origin[0];\n\t\tout[1][0] = 0.0f;\n\t\tout[1][1] = scale;\n\t\tout[1][2] = 0.0f;\n\t\tout[1][3] = origin[1];\n\t\tout[2][0] = 0.0f;\n\t\tout[2][1] = 0.0f;\n\t\tout[2][2] = scale;\n\t\tout[2][3] = origin[2];\n\t\tout[3][0] = 0.0f;\n\t\tout[3][1] = 0.0f;\n\t\tout[3][2] = 0.0f;\n\t\tout[3][3] = 1.0f;\n\t}\n}\n\nvoid Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin )\n{\n\tfloat xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] );\n\n\t// enough here to get angles?\n\tif( xyDist > 0.001f )\n\t{\n\t\tangles[0] = RAD2DEG( atan2( -in[2][0], xyDist ));\n\t\tangles[1] = RAD2DEG( atan2( in[1][0], in[0][0] ));\n\t\tangles[2] = RAD2DEG( atan2( in[2][1], in[2][2] ));\n\t}\n\telse\t// forward is mostly Z, gimbal lock\n\t{\n\t\tangles[0] = RAD2DEG( atan2( -in[2][0], xyDist ));\n\t\tangles[1] = RAD2DEG( atan2( -in[0][1], in[1][1] ));\n\t\tangles[2] = 0.0f;\n\t}\n\n\torigin[0] = in[0][3];\n\torigin[1] = in[1][3];\n\torigin[2] = in[2][3];\n}\n\nvoid Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist )\n{\n\tfloat\tscale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] );\n\tfloat\tiscale = 1.0f / scale;\n\n\tout[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale;\n\tout[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale;\n\tout[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale;\n\t*dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] );\n}\n\nvoid Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 )\n{\n\t// we only support uniform scaling, so assume the first row is enough\n\t// (note the lack of sqrt here, because we're trying to undo the scaling,\n\t// this means multiplying by the inverse scale twice - squaring it, which\n\t// makes the sqrt a waste of time)\n\tfloat\tscale = 1.0f / (in1[0][0] * in1[0][0] + in1[0][1] * in1[0][1] + in1[0][2] * in1[0][2]);\n\n\t// invert the rotation by transposing and multiplying by the squared\n\t// recipricol of the input matrix scale as described above\n\tout[0][0] = in1[0][0] * scale;\n\tout[0][1] = in1[1][0] * scale;\n\tout[0][2] = in1[2][0] * scale;\n\tout[1][0] = in1[0][1] * scale;\n\tout[1][1] = in1[1][1] * scale;\n\tout[1][2] = in1[2][1] * scale;\n\tout[2][0] = in1[0][2] * scale;\n\tout[2][1] = in1[1][2] * scale;\n\tout[2][2] = in1[2][2] * scale;\n\n\t// invert the translate\n\tout[0][3] = -(in1[0][3] * out[0][0] + in1[1][3] * out[0][1] + in1[2][3] * out[0][2]);\n\tout[1][3] = -(in1[0][3] * out[1][0] + in1[1][3] * out[1][1] + in1[2][3] * out[1][2]);\n\tout[2][3] = -(in1[0][3] * out[2][0] + in1[1][3] * out[2][1] + in1[2][3] * out[2][2]);\n\n\t// don't know if there's anything worth doing here\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nqboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 )\n{\n\tfloat\t*temp;\n\tfloat\t*r[4];\n\tfloat\trtemp[4][8];\n\tfloat\tm[4];\n\tfloat\ts;\n\n\tr[0] = rtemp[0];\n\tr[1] = rtemp[1];\n\tr[2] = rtemp[2];\n\tr[3] = rtemp[3];\n\n\tr[0][0] = in1[0][0];\n\tr[0][1] = in1[0][1];\n\tr[0][2] = in1[0][2];\n\tr[0][3] = in1[0][3];\n\tr[0][4] = 1.0f;\n\tr[0][5] =\t0.0f;\n\tr[0][6] =\t0.0f;\n\tr[0][7] = 0.0f;\n\n\tr[1][0] = in1[1][0];\n\tr[1][1] = in1[1][1];\n\tr[1][2] = in1[1][2];\n\tr[1][3] = in1[1][3];\n\tr[1][5] = 1.0f;\n\tr[1][4] =\t0.0f;\n\tr[1][6] =\t0.0f;\n\tr[1][7] = 0.0f;\n\n\tr[2][0] = in1[2][0];\n\tr[2][1] = in1[2][1];\n\tr[2][2] = in1[2][2];\n\tr[2][3] = in1[2][3];\n\tr[2][6] = 1.0f;\n\tr[2][4] =\t0.0f;\n\tr[2][5] =\t0.0f;\n\tr[2][7] = 0.0f;\n\n\tr[3][0] = in1[3][0];\n\tr[3][1] = in1[3][1];\n\tr[3][2] = in1[3][2];\n\tr[3][3] = in1[3][3];\n\tr[3][4] =\t0.0f;\n\tr[3][5] = 0.0f;\n\tr[3][6] = 0.0f;\n\tr[3][7] = 1.0f;\n\n\tif( fabs( r[3][0] ) > fabs( r[2][0] ))\n\t{\n\t\ttemp = r[3];\n\t\tr[3] = r[2];\n\t\tr[2] = temp;\n\t}\n\n\tif( fabs( r[2][0] ) > fabs( r[1][0] ))\n\t{\n\t\ttemp = r[2];\n\t\tr[2] = r[1];\n\t\tr[1] = temp;\n\t}\n\n\tif( fabs( r[1][0] ) > fabs( r[0][0] ))\n\t{\n\t\ttemp = r[1];\n\t\tr[1] = r[0];\n\t\tr[0] = temp;\n\t}\n\n\tif( r[0][0] )\n\t{\n\t\tm[1] = r[1][0] / r[0][0];\n\t\tm[2] = r[2][0] / r[0][0];\n\t\tm[3] = r[3][0] / r[0][0];\n\n\t\ts = r[0][1];\n\t\tr[1][1] -= m[1] * s;\n\t\tr[2][1] -= m[2] * s;\n\t\tr[3][1] -= m[3] * s;\n\n\t\ts = r[0][2];\n\t\tr[1][2] -= m[1] * s;\n\t\tr[2][2] -= m[2] * s;\n\t\tr[3][2] -= m[3] * s;\n\n\t\ts = r[0][3];\n\t\tr[1][3] -= m[1] * s;\n\t\tr[2][3] -= m[2] * s;\n\t\tr[3][3] -= m[3] * s;\n\n\t\ts = r[0][4];\n\t\tif( s )\n\t\t{\n\t\t\tr[1][4] -= m[1] * s;\n\t\t\tr[2][4] -= m[2] * s;\n\t\t\tr[3][4] -= m[3] * s;\n\t\t}\n\n\t\ts = r[0][5];\n\t\tif( s )\n\t\t{\n\t\t\tr[1][5] -= m[1] * s;\n\t\t\tr[2][5] -= m[2] * s;\n\t\t\tr[3][5] -= m[3] * s;\n\t\t}\n\n\t\ts = r[0][6];\n\t\tif( s )\n\t\t{\n\t\t\tr[1][6] -= m[1] * s;\n\t\t\tr[2][6] -= m[2] * s;\n\t\t\tr[3][6] -= m[3] * s;\n\t\t}\n\n\t\ts = r[0][7];\n\t\tif( s )\n\t\t{\n\t\t\tr[1][7] -= m[1] * s;\n\t\t\tr[2][7] -= m[2] * s;\n\t\t\tr[3][7] -= m[3] * s;\n\t\t}\n\n\t\tif( fabs( r[3][1] ) > fabs( r[2][1] ))\n\t\t{\n\t\t\ttemp = r[3];\n\t\t\tr[3] = r[2];\n\t\t\tr[2] = temp;\n\t\t}\n\n\t\tif( fabs( r[2][1] ) > fabs( r[1][1] ))\n\t\t{\n\t\t\ttemp = r[2];\n\t\t\tr[2] = r[1];\n\t\t\tr[1] = temp;\n\t\t}\n\n\t\tif( r[1][1] )\n\t\t{\n\t\t\tm[2] = r[2][1] / r[1][1];\n\t\t\tm[3] = r[3][1] / r[1][1];\n\t\t\tr[2][2] -= m[2] * r[1][2];\n\t\t\tr[3][2] -= m[3] * r[1][2];\n\t\t\tr[2][3] -= m[2] * r[1][3];\n\t\t\tr[3][3] -= m[3] * r[1][3];\n\n\t\t\ts = r[1][4];\n\t\t\tif( s )\n\t\t\t{\n\t\t\t\tr[2][4] -= m[2] * s;\n\t\t\t\tr[3][4] -= m[3] * s;\n\t\t\t}\n\n\t\t\ts = r[1][5];\n\t\t\tif( s )\n\t\t\t{\n\t\t\t\tr[2][5] -= m[2] * s;\n\t\t\t\tr[3][5] -= m[3] * s;\n\t\t\t}\n\n\t\t\ts = r[1][6];\n\t\t\tif( s )\n\t\t\t{\n\t\t\t\tr[2][6] -= m[2] * s;\n\t\t\t\tr[3][6] -= m[3] * s;\n\t\t\t}\n\n\t\t\ts = r[1][7];\n\t\t\tif( s )\n\t\t\t{\n\t\t\t\tr[2][7] -= m[2] * s;\n\t\t\t\tr[3][7] -= m[3] * s;\n\t\t\t}\n\n\t\t\tif( fabs( r[3][2] ) > fabs( r[2][2] ))\n\t\t\t{\n\t\t\t\ttemp = r[3];\n\t\t\t\tr[3] = r[2];\n\t\t\t\tr[2] = temp;\n\t\t\t}\n\n\t\t\tif( r[2][2] )\n\t\t\t{\n\t\t\t\tm[3] = r[3][2] / r[2][2];\n\t\t\t\tr[3][3] -= m[3] * r[2][3];\n\t\t\t\tr[3][4] -= m[3] * r[2][4];\n\t\t\t\tr[3][5] -= m[3] * r[2][5];\n\t\t\t\tr[3][6] -= m[3] * r[2][6];\n\t\t\t\tr[3][7] -= m[3] * r[2][7];\n\n\t\t\t\tif( r[3][3] )\n\t\t\t\t{\n\t\t\t\t\ts = 1.0f / r[3][3];\n\t\t\t\t\tr[3][4] *= s;\n\t\t\t\t\tr[3][5] *= s;\n\t\t\t\t\tr[3][6] *= s;\n\t\t\t\t\tr[3][7] *= s;\n\n\t\t\t\t\tm[2] = r[2][3];\n\t\t\t\t\ts = 1.0f / r[2][2];\n\t\t\t\t\tr[2][4] = s * (r[2][4] - r[3][4] * m[2]);\n\t\t\t\t\tr[2][5] = s * (r[2][5] - r[3][5] * m[2]);\n\t\t\t\t\tr[2][6] = s * (r[2][6] - r[3][6] * m[2]);\n\t\t\t\t\tr[2][7] = s * (r[2][7] - r[3][7] * m[2]);\n\n\t\t\t\t\tm[1] = r[1][3];\n\t\t\t\t\tr[1][4] -= r[3][4] * m[1];\n\t\t\t\t\tr[1][5] -= r[3][5] * m[1];\n\t\t\t\t\tr[1][6] -= r[3][6] * m[1];\n\t\t\t\t\tr[1][7] -= r[3][7] * m[1];\n\n\t\t\t\t\tm[0] = r[0][3];\n\t\t\t\t\tr[0][4] -= r[3][4] * m[0];\n\t\t\t\t\tr[0][5] -= r[3][5] * m[0];\n\t\t\t\t\tr[0][6] -= r[3][6] * m[0];\n\t\t\t\t\tr[0][7] -= r[3][7] * m[0];\n\n\t\t\t\t\tm[1] = r[1][2];\n\t\t\t\t\ts = 1.0f / r[1][1];\n\t\t\t\t\tr[1][4] = s * (r[1][4] - r[2][4] * m[1]);\n\t\t\t\t\tr[1][5] = s * (r[1][5] - r[2][5] * m[1]);\n\t\t\t\t\tr[1][6] = s * (r[1][6] - r[2][6] * m[1]);\n\t\t\t\t\tr[1][7] = s * (r[1][7] - r[2][7] * m[1]);\n\n\t\t\t\t\tm[0] = r[0][2];\n\t\t\t\t\tr[0][4] -= r[2][4] * m[0];\n\t\t\t\t\tr[0][5] -= r[2][5] * m[0];\n\t\t\t\t\tr[0][6] -= r[2][6] * m[0];\n\t\t\t\t\tr[0][7] -= r[2][7] * m[0];\n\n\t\t\t\t\tm[0] = r[0][1];\n\t\t\t\t\ts = 1.0f / r[0][0];\n\t\t\t\t\tr[0][4] = s * (r[0][4] - r[1][4] * m[0]);\n\t\t\t\t\tr[0][5] = s * (r[0][5] - r[1][5] * m[0]);\n\t\t\t\t\tr[0][6] = s * (r[0][6] - r[1][6] * m[0]);\n\t\t\t\t\tr[0][7] = s * (r[0][7] - r[1][7] * m[0]);\n\n\t\t\t\t\tout[0][0]\t= r[0][4];\n\t\t\t\t\tout[0][1]\t= r[0][5];\n\t\t\t\t\tout[0][2]\t= r[0][6];\n\t\t\t\t\tout[0][3]\t= r[0][7];\n\t\t\t\t\tout[1][0]\t= r[1][4];\n\t\t\t\t\tout[1][1]\t= r[1][5];\n\t\t\t\t\tout[1][2]\t= r[1][6];\n\t\t\t\t\tout[1][3]\t= r[1][7];\n\t\t\t\t\tout[2][0]\t= r[2][4];\n\t\t\t\t\tout[2][1]\t= r[2][5];\n\t\t\t\t\tout[2][2]\t= r[2][6];\n\t\t\t\t\tout[2][3]\t= r[2][7];\n\t\t\t\t\tout[3][0]\t= r[3][4];\n\t\t\t\t\tout[3][1]\t= r[3][5];\n\t\t\t\t\tout[3][2]\t= r[3][6];\n\t\t\t\t\tout[3][3]\t= r[3][7];\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n"
  },
  {
    "path": "public/miniz.c",
    "content": "#include \"miniz.h\"\n/**************************************************************************\n *\n * Copyright 2013-2014 RAD Game Tools and Valve Software\n * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC\n * All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n **************************************************************************/\n\n\n\ntypedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1];\ntypedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];\ntypedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------- zlib-style API's */\n\nmz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)\n{\n    mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16);\n    size_t block_len = buf_len % 5552;\n    if (!ptr)\n        return MZ_ADLER32_INIT;\n    while (buf_len)\n    {\n        for (i = 0; i + 7 < block_len; i += 8, ptr += 8)\n        {\n            s1 += ptr[0], s2 += s1;\n            s1 += ptr[1], s2 += s1;\n            s1 += ptr[2], s2 += s1;\n            s1 += ptr[3], s2 += s1;\n            s1 += ptr[4], s2 += s1;\n            s1 += ptr[5], s2 += s1;\n            s1 += ptr[6], s2 += s1;\n            s1 += ptr[7], s2 += s1;\n        }\n        for (; i < block_len; ++i)\n            s1 += *ptr++, s2 += s1;\n        s1 %= 65521U, s2 %= 65521U;\n        buf_len -= block_len;\n        block_len = 5552;\n    }\n    return (s2 << 16) + s1;\n}\n\n/* Karl Malbrain's compact CRC-32. See \"A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed\": http://www.geocities.com/malbrain/ */\n#if 0\n    mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)\n    {\n        static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,\n                                               0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };\n        mz_uint32 crcu32 = (mz_uint32)crc;\n        if (!ptr)\n            return MZ_CRC32_INIT;\n        crcu32 = ~crcu32;\n        while (buf_len--)\n        {\n            mz_uint8 b = *ptr++;\n            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];\n            crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];\n        }\n        return ~crcu32;\n    }\n#elif defined(USE_EXTERNAL_MZCRC)\n/* If USE_EXTERNAL_CRC is defined, an external module will export the\n * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version.\n * Depending on the impl, it may be necessary to ~ the input/output crc values.\n */\nmz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len);\n#else\n/* Faster, but larger CPU cache footprint.\n */\nmz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)\n{\n    static const mz_uint32 s_crc_table[256] =\n        {\n          0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,\n          0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,\n          0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,\n          0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,\n          0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,\n          0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,\n          0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,\n          0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,\n          0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,\n          0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,\n          0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,\n          0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,\n          0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,\n          0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,\n          0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,\n          0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,\n          0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,\n          0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,\n          0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,\n          0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,\n          0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,\n          0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,\n          0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,\n          0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,\n          0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,\n          0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,\n          0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,\n          0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,\n          0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,\n          0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,\n          0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,\n          0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,\n          0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,\n          0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,\n          0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,\n          0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,\n          0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D\n        };\n\n    mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF;\n    const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr;\n\n    while (buf_len >= 4)\n    {\n        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];\n        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF];\n        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF];\n        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF];\n        pByte_buf += 4;\n        buf_len -= 4;\n    }\n\n    while (buf_len)\n    {\n        crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];\n        ++pByte_buf;\n        --buf_len;\n    }\n\n    return ~crc32;\n}\n#endif\n\nvoid mz_free(void *p)\n{\n    MZ_FREE(p);\n}\n\nMINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size)\n{\n    (void)opaque, (void)items, (void)size;\n    return MZ_MALLOC(items * size);\n}\nMINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address)\n{\n    (void)opaque, (void)address;\n    MZ_FREE(address);\n}\nMINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size)\n{\n    (void)opaque, (void)address, (void)items, (void)size;\n    return MZ_REALLOC(address, items * size);\n}\n\nconst char *mz_version(void)\n{\n    return MZ_VERSION;\n}\n\n#ifndef MINIZ_NO_ZLIB_APIS\n\n#ifndef MINIZ_NO_DEFLATE_APIS\n\nint mz_deflateInit(mz_streamp pStream, int level)\n{\n    return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);\n}\n\nint mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)\n{\n    tdefl_compressor *pComp;\n    mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);\n\n    if (!pStream)\n        return MZ_STREAM_ERROR;\n    if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))\n        return MZ_PARAM_ERROR;\n\n    pStream->data_type = 0;\n    pStream->adler = MZ_ADLER32_INIT;\n    pStream->msg = NULL;\n    pStream->reserved = 0;\n    pStream->total_in = 0;\n    pStream->total_out = 0;\n    if (!pStream->zalloc)\n        pStream->zalloc = miniz_def_alloc_func;\n    if (!pStream->zfree)\n        pStream->zfree = miniz_def_free_func;\n\n    pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));\n    if (!pComp)\n        return MZ_MEM_ERROR;\n\n    pStream->state = (struct mz_internal_state *)pComp;\n\n    if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)\n    {\n        mz_deflateEnd(pStream);\n        return MZ_PARAM_ERROR;\n    }\n\n    return MZ_OK;\n}\n\nint mz_deflateReset(mz_streamp pStream)\n{\n    if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree))\n        return MZ_STREAM_ERROR;\n    pStream->total_in = pStream->total_out = 0;\n    tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags);\n    return MZ_OK;\n}\n\nint mz_deflate(mz_streamp pStream, int flush)\n{\n    size_t in_bytes, out_bytes;\n    mz_ulong orig_total_in, orig_total_out;\n    int mz_status = MZ_OK;\n\n    if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))\n        return MZ_STREAM_ERROR;\n    if (!pStream->avail_out)\n        return MZ_BUF_ERROR;\n\n    if (flush == MZ_PARTIAL_FLUSH)\n        flush = MZ_SYNC_FLUSH;\n\n    if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)\n        return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;\n\n    orig_total_in = pStream->total_in;\n    orig_total_out = pStream->total_out;\n    for (;;)\n    {\n        tdefl_status defl_status;\n        in_bytes = pStream->avail_in;\n        out_bytes = pStream->avail_out;\n\n        defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);\n        pStream->next_in += (mz_uint)in_bytes;\n        pStream->avail_in -= (mz_uint)in_bytes;\n        pStream->total_in += (mz_uint)in_bytes;\n        pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);\n\n        pStream->next_out += (mz_uint)out_bytes;\n        pStream->avail_out -= (mz_uint)out_bytes;\n        pStream->total_out += (mz_uint)out_bytes;\n\n        if (defl_status < 0)\n        {\n            mz_status = MZ_STREAM_ERROR;\n            break;\n        }\n        else if (defl_status == TDEFL_STATUS_DONE)\n        {\n            mz_status = MZ_STREAM_END;\n            break;\n        }\n        else if (!pStream->avail_out)\n            break;\n        else if ((!pStream->avail_in) && (flush != MZ_FINISH))\n        {\n            if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))\n                break;\n            return MZ_BUF_ERROR; /* Can't make forward progress without some input.\n */\n        }\n    }\n    return mz_status;\n}\n\nint mz_deflateEnd(mz_streamp pStream)\n{\n    if (!pStream)\n        return MZ_STREAM_ERROR;\n    if (pStream->state)\n    {\n        pStream->zfree(pStream->opaque, pStream->state);\n        pStream->state = NULL;\n    }\n    return MZ_OK;\n}\n\nmz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)\n{\n    (void)pStream;\n    /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */\n    return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);\n}\n\nint mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)\n{\n    int status;\n    mz_stream stream;\n    memset(&stream, 0, sizeof(stream));\n\n    /* In case mz_ulong is 64-bits (argh I hate longs). */\n    if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU)\n        return MZ_PARAM_ERROR;\n\n    stream.next_in = pSource;\n    stream.avail_in = (mz_uint32)source_len;\n    stream.next_out = pDest;\n    stream.avail_out = (mz_uint32)*pDest_len;\n\n    status = mz_deflateInit(&stream, level);\n    if (status != MZ_OK)\n        return status;\n\n    status = mz_deflate(&stream, MZ_FINISH);\n    if (status != MZ_STREAM_END)\n    {\n        mz_deflateEnd(&stream);\n        return (status == MZ_OK) ? MZ_BUF_ERROR : status;\n    }\n\n    *pDest_len = stream.total_out;\n    return mz_deflateEnd(&stream);\n}\n\nint mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)\n{\n    return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);\n}\n\nmz_ulong mz_compressBound(mz_ulong source_len)\n{\n    return mz_deflateBound(NULL, source_len);\n}\n\n#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/\n\n#ifndef MINIZ_NO_INFLATE_APIS\n\ntypedef struct\n{\n    tinfl_decompressor m_decomp;\n    mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;\n    int m_window_bits;\n    mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];\n    tinfl_status m_last_status;\n} inflate_state;\n\nint mz_inflateInit2(mz_streamp pStream, int window_bits)\n{\n    inflate_state *pDecomp;\n    if (!pStream)\n        return MZ_STREAM_ERROR;\n    if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))\n        return MZ_PARAM_ERROR;\n\n    pStream->data_type = 0;\n    pStream->adler = 0;\n    pStream->msg = NULL;\n    pStream->total_in = 0;\n    pStream->total_out = 0;\n    pStream->reserved = 0;\n    if (!pStream->zalloc)\n        pStream->zalloc = miniz_def_alloc_func;\n    if (!pStream->zfree)\n        pStream->zfree = miniz_def_free_func;\n\n    pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));\n    if (!pDecomp)\n        return MZ_MEM_ERROR;\n\n    pStream->state = (struct mz_internal_state *)pDecomp;\n\n    tinfl_init(&pDecomp->m_decomp);\n    pDecomp->m_dict_ofs = 0;\n    pDecomp->m_dict_avail = 0;\n    pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;\n    pDecomp->m_first_call = 1;\n    pDecomp->m_has_flushed = 0;\n    pDecomp->m_window_bits = window_bits;\n\n    return MZ_OK;\n}\n\nint mz_inflateInit(mz_streamp pStream)\n{\n    return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);\n}\n\nint mz_inflateReset(mz_streamp pStream)\n{\n    inflate_state *pDecomp;\n    if (!pStream)\n        return MZ_STREAM_ERROR;\n\n    pStream->data_type = 0;\n    pStream->adler = 0;\n    pStream->msg = NULL;\n    pStream->total_in = 0;\n    pStream->total_out = 0;\n    pStream->reserved = 0;\n\n    pDecomp = (inflate_state *)pStream->state;\n\n    tinfl_init(&pDecomp->m_decomp);\n    pDecomp->m_dict_ofs = 0;\n    pDecomp->m_dict_avail = 0;\n    pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;\n    pDecomp->m_first_call = 1;\n    pDecomp->m_has_flushed = 0;\n    /* pDecomp->m_window_bits = window_bits */;\n\n    return MZ_OK;\n}\n\nint mz_inflate(mz_streamp pStream, int flush)\n{\n    inflate_state *pState;\n    mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;\n    size_t in_bytes, out_bytes, orig_avail_in;\n    tinfl_status status;\n\n    if ((!pStream) || (!pStream->state))\n        return MZ_STREAM_ERROR;\n    if (flush == MZ_PARTIAL_FLUSH)\n        flush = MZ_SYNC_FLUSH;\n    if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))\n        return MZ_STREAM_ERROR;\n\n    pState = (inflate_state *)pStream->state;\n    if (pState->m_window_bits > 0)\n        decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;\n    orig_avail_in = pStream->avail_in;\n\n    first_call = pState->m_first_call;\n    pState->m_first_call = 0;\n    if (pState->m_last_status < 0)\n        return MZ_DATA_ERROR;\n\n    if (pState->m_has_flushed && (flush != MZ_FINISH))\n        return MZ_STREAM_ERROR;\n    pState->m_has_flushed |= (flush == MZ_FINISH);\n\n    if ((flush == MZ_FINISH) && (first_call))\n    {\n        /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */\n        decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;\n        in_bytes = pStream->avail_in;\n        out_bytes = pStream->avail_out;\n        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);\n        pState->m_last_status = status;\n        pStream->next_in += (mz_uint)in_bytes;\n        pStream->avail_in -= (mz_uint)in_bytes;\n        pStream->total_in += (mz_uint)in_bytes;\n        pStream->adler = tinfl_get_adler32(&pState->m_decomp);\n        pStream->next_out += (mz_uint)out_bytes;\n        pStream->avail_out -= (mz_uint)out_bytes;\n        pStream->total_out += (mz_uint)out_bytes;\n\n        if (status < 0)\n            return MZ_DATA_ERROR;\n        else if (status != TINFL_STATUS_DONE)\n        {\n            pState->m_last_status = TINFL_STATUS_FAILED;\n            return MZ_BUF_ERROR;\n        }\n        return MZ_STREAM_END;\n    }\n    /* flush != MZ_FINISH then we must assume there's more input. */\n    if (flush != MZ_FINISH)\n        decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;\n\n    if (pState->m_dict_avail)\n    {\n        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);\n        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);\n        pStream->next_out += n;\n        pStream->avail_out -= n;\n        pStream->total_out += n;\n        pState->m_dict_avail -= n;\n        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);\n        return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;\n    }\n\n    for (;;)\n    {\n        in_bytes = pStream->avail_in;\n        out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;\n\n        status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);\n        pState->m_last_status = status;\n\n        pStream->next_in += (mz_uint)in_bytes;\n        pStream->avail_in -= (mz_uint)in_bytes;\n        pStream->total_in += (mz_uint)in_bytes;\n        pStream->adler = tinfl_get_adler32(&pState->m_decomp);\n\n        pState->m_dict_avail = (mz_uint)out_bytes;\n\n        n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);\n        memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);\n        pStream->next_out += n;\n        pStream->avail_out -= n;\n        pStream->total_out += n;\n        pState->m_dict_avail -= n;\n        pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);\n\n        if (status < 0)\n            return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */\n        else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))\n            return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */\n        else if (flush == MZ_FINISH)\n        {\n            /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */\n            if (status == TINFL_STATUS_DONE)\n                return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;\n            /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */\n            else if (!pStream->avail_out)\n                return MZ_BUF_ERROR;\n        }\n        else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))\n            break;\n    }\n\n    return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;\n}\n\nint mz_inflateEnd(mz_streamp pStream)\n{\n    if (!pStream)\n        return MZ_STREAM_ERROR;\n    if (pStream->state)\n    {\n        pStream->zfree(pStream->opaque, pStream->state);\n        pStream->state = NULL;\n    }\n    return MZ_OK;\n}\nint mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len)\n{\n    mz_stream stream;\n    int status;\n    memset(&stream, 0, sizeof(stream));\n\n    /* In case mz_ulong is 64-bits (argh I hate longs). */\n    if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU)\n        return MZ_PARAM_ERROR;\n\n    stream.next_in = pSource;\n    stream.avail_in = (mz_uint32)*pSource_len;\n    stream.next_out = pDest;\n    stream.avail_out = (mz_uint32)*pDest_len;\n\n    status = mz_inflateInit(&stream);\n    if (status != MZ_OK)\n        return status;\n\n    status = mz_inflate(&stream, MZ_FINISH);\n    *pSource_len = *pSource_len - stream.avail_in;\n    if (status != MZ_STREAM_END)\n    {\n        mz_inflateEnd(&stream);\n        return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;\n    }\n    *pDest_len = stream.total_out;\n\n    return mz_inflateEnd(&stream);\n}\n\nint mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)\n{\n    return mz_uncompress2(pDest, pDest_len, pSource, &source_len);\n}\n\n#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/\n\nconst char *mz_error(int err)\n{\n    static struct\n    {\n        int m_err;\n        const char *m_pDesc;\n    } s_error_descs[] =\n        {\n          { MZ_OK, \"\" }, { MZ_STREAM_END, \"stream end\" }, { MZ_NEED_DICT, \"need dictionary\" }, { MZ_ERRNO, \"file error\" }, { MZ_STREAM_ERROR, \"stream error\" }, { MZ_DATA_ERROR, \"data error\" }, { MZ_MEM_ERROR, \"out of memory\" }, { MZ_BUF_ERROR, \"buf error\" }, { MZ_VERSION_ERROR, \"version error\" }, { MZ_PARAM_ERROR, \"parameter error\" }\n        };\n    mz_uint i;\n    for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i)\n        if (s_error_descs[i].m_err == err)\n            return s_error_descs[i].m_pDesc;\n    return NULL;\n}\n\n#endif /*MINIZ_NO_ZLIB_APIS */\n\n#ifdef __cplusplus\n}\n#endif\n\n/*\n  This is free and unencumbered software released into the public domain.\n\n  Anyone is free to copy, modify, publish, use, compile, sell, or\n  distribute this software, either in source code form or as a compiled\n  binary, for any purpose, commercial or non-commercial, and by any\n  means.\n\n  In jurisdictions that recognize copyright laws, the author or authors\n  of this software dedicate any and all copyright interest in the\n  software to the public domain. We make this dedication for the benefit\n  of the public at large and to the detriment of our heirs and\n  successors. We intend this dedication to be an overt act of\n  relinquishment in perpetuity of all present and future rights to this\n  software under copyright law.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n  OTHER DEALINGS IN THE SOFTWARE.\n\n  For more information, please refer to <http://unlicense.org/>\n*/\n/**************************************************************************\n *\n * Copyright 2013-2014 RAD Game Tools and Valve Software\n * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC\n * All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n **************************************************************************/\n\n\n\n#ifndef MINIZ_NO_DEFLATE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------- Low-level Compression (independent from all decompression API's) */\n\n/* Purposely making these tables static for faster init and thread safety. */\nstatic const mz_uint16 s_tdefl_len_sym[256] =\n    {\n      257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272,\n      273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276,\n      277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,\n      279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280,\n      281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281,\n      282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282,\n      283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283,\n      284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285\n    };\n\nstatic const mz_uint8 s_tdefl_len_extra[256] =\n    {\n      0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,\n      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,\n      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0\n    };\n\nstatic const mz_uint8 s_tdefl_small_dist_sym[512] =\n    {\n      0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11,\n      11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13,\n      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,\n      14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,\n      14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,\n      15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,\n      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,\n      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,\n      16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,\n      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,\n      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,\n      17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17\n    };\n\nstatic const mz_uint8 s_tdefl_small_dist_extra[512] =\n    {\n      0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,\n      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n      6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n      7, 7, 7, 7, 7, 7, 7, 7\n    };\n\nstatic const mz_uint8 s_tdefl_large_dist_sym[128] =\n    {\n      0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,\n      26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,\n      28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29\n    };\n\nstatic const mz_uint8 s_tdefl_large_dist_extra[128] =\n    {\n      0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,\n      12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,\n      13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13\n    };\n\n/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */\ntypedef struct\n{\n    mz_uint16 m_key, m_sym_index;\n} tdefl_sym_freq;\nstatic tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1)\n{\n    mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2];\n    tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1;\n    MZ_CLEAR_ARR(hist);\n    for (i = 0; i < num_syms; i++)\n    {\n        mz_uint freq = pSyms0[i].m_key;\n        hist[freq & 0xFF]++;\n        hist[256 + ((freq >> 8) & 0xFF)]++;\n    }\n    while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256]))\n        total_passes--;\n    for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)\n    {\n        const mz_uint32 *pHist = &hist[pass << 8];\n        mz_uint offsets[256], cur_ofs = 0;\n        for (i = 0; i < 256; i++)\n        {\n            offsets[i] = cur_ofs;\n            cur_ofs += pHist[i];\n        }\n        for (i = 0; i < num_syms; i++)\n            pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];\n        {\n            tdefl_sym_freq *t = pCur_syms;\n            pCur_syms = pNew_syms;\n            pNew_syms = t;\n        }\n    }\n    return pCur_syms;\n}\n\n/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */\nstatic void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)\n{\n    int root, leaf, next, avbl, used, dpth;\n    if (n == 0)\n        return;\n    else if (n == 1)\n    {\n        A[0].m_key = 1;\n        return;\n    }\n    A[0].m_key += A[1].m_key;\n    root = 0;\n    leaf = 2;\n    for (next = 1; next < n - 1; next++)\n    {\n        if (leaf >= n || A[root].m_key < A[leaf].m_key)\n        {\n            A[next].m_key = A[root].m_key;\n            A[root++].m_key = (mz_uint16)next;\n        }\n        else\n            A[next].m_key = A[leaf++].m_key;\n        if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key))\n        {\n            A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key);\n            A[root++].m_key = (mz_uint16)next;\n        }\n        else\n            A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);\n    }\n    A[n - 2].m_key = 0;\n    for (next = n - 3; next >= 0; next--)\n        A[next].m_key = A[A[next].m_key].m_key + 1;\n    avbl = 1;\n    used = dpth = 0;\n    root = n - 2;\n    next = n - 1;\n    while (avbl > 0)\n    {\n        while (root >= 0 && (int)A[root].m_key == dpth)\n        {\n            used++;\n            root--;\n        }\n        while (avbl > used)\n        {\n            A[next--].m_key = (mz_uint16)(dpth);\n            avbl--;\n        }\n        avbl = 2 * used;\n        dpth++;\n        used = 0;\n    }\n}\n\n/* Limits canonical Huffman code table's max code size. */\nenum\n{\n    TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32\n};\nstatic void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)\n{\n    int i;\n    mz_uint32 total = 0;\n    if (code_list_len <= 1)\n        return;\n    for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++)\n        pNum_codes[max_code_size] += pNum_codes[i];\n    for (i = max_code_size; i > 0; i--)\n        total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));\n    while (total != (1UL << max_code_size))\n    {\n        pNum_codes[max_code_size]--;\n        for (i = max_code_size - 1; i > 0; i--)\n            if (pNum_codes[i])\n            {\n                pNum_codes[i]--;\n                pNum_codes[i + 1] += 2;\n                break;\n            }\n        total--;\n    }\n}\n\nstatic void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)\n{\n    int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE];\n    mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1];\n    MZ_CLEAR_ARR(num_codes);\n    if (static_table)\n    {\n        for (i = 0; i < table_len; i++)\n            num_codes[d->m_huff_code_sizes[table_num][i]]++;\n    }\n    else\n    {\n        tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;\n        int num_used_syms = 0;\n        const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];\n        for (i = 0; i < table_len; i++)\n            if (pSym_count[i])\n            {\n                syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i];\n                syms0[num_used_syms++].m_sym_index = (mz_uint16)i;\n            }\n\n        pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1);\n        tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);\n\n        for (i = 0; i < num_used_syms; i++)\n            num_codes[pSyms[i].m_key]++;\n\n        tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);\n\n        MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]);\n        MZ_CLEAR_ARR(d->m_huff_codes[table_num]);\n        for (i = 1, j = num_used_syms; i <= code_size_limit; i++)\n            for (l = num_codes[i]; l > 0; l--)\n                d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);\n    }\n\n    next_code[1] = 0;\n    for (j = 0, i = 2; i <= code_size_limit; i++)\n        next_code[i] = j = ((j + num_codes[i - 1]) << 1);\n\n    for (i = 0; i < table_len; i++)\n    {\n        mz_uint rev_code = 0, code, code_size;\n        if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0)\n            continue;\n        code = next_code[code_size]++;\n        for (l = code_size; l > 0; l--, code >>= 1)\n            rev_code = (rev_code << 1) | (code & 1);\n        d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;\n    }\n}\n\n#define TDEFL_PUT_BITS(b, l)                                       \\\n    do                                                             \\\n    {                                                              \\\n        mz_uint bits = b;                                          \\\n        mz_uint len = l;                                           \\\n        MZ_ASSERT(bits <= ((1U << len) - 1U));                     \\\n        d->m_bit_buffer |= (bits << d->m_bits_in);                 \\\n        d->m_bits_in += len;                                       \\\n        while (d->m_bits_in >= 8)                                  \\\n        {                                                          \\\n            if (d->m_pOutput_buf < d->m_pOutput_buf_end)           \\\n                *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \\\n            d->m_bit_buffer >>= 8;                                 \\\n            d->m_bits_in -= 8;                                     \\\n        }                                                          \\\n    }                                                              \\\n    MZ_MACRO_END\n\n#define TDEFL_RLE_PREV_CODE_SIZE()                                                                                       \\\n    {                                                                                                                    \\\n        if (rle_repeat_count)                                                                                            \\\n        {                                                                                                                \\\n            if (rle_repeat_count < 3)                                                                                    \\\n            {                                                                                                            \\\n                d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \\\n                while (rle_repeat_count--)                                                                               \\\n                    packed_code_sizes[num_packed_code_sizes++] = prev_code_size;                                         \\\n            }                                                                                                            \\\n            else                                                                                                         \\\n            {                                                                                                            \\\n                d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1);                                        \\\n                packed_code_sizes[num_packed_code_sizes++] = 16;                                                         \\\n                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3);                           \\\n            }                                                                                                            \\\n            rle_repeat_count = 0;                                                                                        \\\n        }                                                                                                                \\\n    }\n\n#define TDEFL_RLE_ZERO_CODE_SIZE()                                                         \\\n    {                                                                                      \\\n        if (rle_z_count)                                                                   \\\n        {                                                                                  \\\n            if (rle_z_count < 3)                                                           \\\n            {                                                                              \\\n                d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count);  \\\n                while (rle_z_count--)                                                      \\\n                    packed_code_sizes[num_packed_code_sizes++] = 0;                        \\\n            }                                                                              \\\n            else if (rle_z_count <= 10)                                                    \\\n            {                                                                              \\\n                d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1);          \\\n                packed_code_sizes[num_packed_code_sizes++] = 17;                           \\\n                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3);  \\\n            }                                                                              \\\n            else                                                                           \\\n            {                                                                              \\\n                d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1);          \\\n                packed_code_sizes[num_packed_code_sizes++] = 18;                           \\\n                packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \\\n            }                                                                              \\\n            rle_z_count = 0;                                                               \\\n        }                                                                                  \\\n    }\n\nstatic const mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };\n\nstatic void tdefl_start_dynamic_block(tdefl_compressor *d)\n{\n    int num_lit_codes, num_dist_codes, num_bit_lengths;\n    mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;\n    mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;\n\n    d->m_huff_count[0][256] = 1;\n\n    tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);\n    tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);\n\n    for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--)\n        if (d->m_huff_code_sizes[0][num_lit_codes - 1])\n            break;\n    for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--)\n        if (d->m_huff_code_sizes[1][num_dist_codes - 1])\n            break;\n\n    memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);\n    memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);\n    total_code_sizes_to_pack = num_lit_codes + num_dist_codes;\n    num_packed_code_sizes = 0;\n    rle_z_count = 0;\n    rle_repeat_count = 0;\n\n    memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);\n    for (i = 0; i < total_code_sizes_to_pack; i++)\n    {\n        mz_uint8 code_size = code_sizes_to_pack[i];\n        if (!code_size)\n        {\n            TDEFL_RLE_PREV_CODE_SIZE();\n            if (++rle_z_count == 138)\n            {\n                TDEFL_RLE_ZERO_CODE_SIZE();\n            }\n        }\n        else\n        {\n            TDEFL_RLE_ZERO_CODE_SIZE();\n            if (code_size != prev_code_size)\n            {\n                TDEFL_RLE_PREV_CODE_SIZE();\n                d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1);\n                packed_code_sizes[num_packed_code_sizes++] = code_size;\n            }\n            else if (++rle_repeat_count == 6)\n            {\n                TDEFL_RLE_PREV_CODE_SIZE();\n            }\n        }\n        prev_code_size = code_size;\n    }\n    if (rle_repeat_count)\n    {\n        TDEFL_RLE_PREV_CODE_SIZE();\n    }\n    else\n    {\n        TDEFL_RLE_ZERO_CODE_SIZE();\n    }\n\n    tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);\n\n    TDEFL_PUT_BITS(2, 2);\n\n    TDEFL_PUT_BITS(num_lit_codes - 257, 5);\n    TDEFL_PUT_BITS(num_dist_codes - 1, 5);\n\n    for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--)\n        if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]])\n            break;\n    num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1));\n    TDEFL_PUT_BITS(num_bit_lengths - 4, 4);\n    for (i = 0; (int)i < num_bit_lengths; i++)\n        TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);\n\n    for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;)\n    {\n        mz_uint code = packed_code_sizes[packed_code_sizes_index++];\n        MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);\n        TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);\n        if (code >= 16)\n            TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], \"\\02\\03\\07\"[code - 16]);\n    }\n}\n\nstatic void tdefl_start_static_block(tdefl_compressor *d)\n{\n    mz_uint i;\n    mz_uint8 *p = &d->m_huff_code_sizes[0][0];\n\n    for (i = 0; i <= 143; ++i)\n        *p++ = 8;\n    for (; i <= 255; ++i)\n        *p++ = 9;\n    for (; i <= 279; ++i)\n        *p++ = 7;\n    for (; i <= 287; ++i)\n        *p++ = 8;\n\n    memset(d->m_huff_code_sizes[1], 5, 32);\n\n    tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);\n    tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);\n\n    TDEFL_PUT_BITS(1, 2);\n}\n\nstatic const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };\n\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS\nstatic mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)\n{\n    mz_uint flags;\n    mz_uint8 *pLZ_codes;\n    mz_uint8 *pOutput_buf = d->m_pOutput_buf;\n    mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;\n    mz_uint64 bit_buffer = d->m_bit_buffer;\n    mz_uint bits_in = d->m_bits_in;\n\n#define TDEFL_PUT_BITS_FAST(b, l)                    \\\n    {                                                \\\n        bit_buffer |= (((mz_uint64)(b)) << bits_in); \\\n        bits_in += (l);                              \\\n    }\n\n    flags = 1;\n    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)\n    {\n        if (flags == 1)\n            flags = *pLZ_codes++ | 0x100;\n\n        if (flags & 1)\n        {\n            mz_uint s0, s1, n0, n1, sym, num_extra_bits;\n            mz_uint match_len = pLZ_codes[0];\n            mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));\n            pLZ_codes += 3;\n\n            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);\n            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);\n            TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);\n\n            /* This sequence coaxes MSVC into using cmov's vs. jmp's. */\n            s0 = s_tdefl_small_dist_sym[match_dist & 511];\n            n0 = s_tdefl_small_dist_extra[match_dist & 511];\n            s1 = s_tdefl_large_dist_sym[match_dist >> 8];\n            n1 = s_tdefl_large_dist_extra[match_dist >> 8];\n            sym = (match_dist < 512) ? s0 : s1;\n            num_extra_bits = (match_dist < 512) ? n0 : n1;\n\n            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);\n            TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);\n            TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);\n        }\n        else\n        {\n            mz_uint lit = *pLZ_codes++;\n            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);\n            TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);\n\n            if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))\n            {\n                flags >>= 1;\n                lit = *pLZ_codes++;\n                MZ_ASSERT(d->m_huff_code_sizes[0][lit]);\n                TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);\n\n                if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))\n                {\n                    flags >>= 1;\n                    lit = *pLZ_codes++;\n                    MZ_ASSERT(d->m_huff_code_sizes[0][lit]);\n                    TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);\n                }\n            }\n        }\n\n        if (pOutput_buf >= d->m_pOutput_buf_end)\n            return MZ_FALSE;\n\n        memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64));\n        pOutput_buf += (bits_in >> 3);\n        bit_buffer >>= (bits_in & ~7);\n        bits_in &= 7;\n    }\n\n#undef TDEFL_PUT_BITS_FAST\n\n    d->m_pOutput_buf = pOutput_buf;\n    d->m_bits_in = 0;\n    d->m_bit_buffer = 0;\n\n    while (bits_in)\n    {\n        mz_uint32 n = MZ_MIN(bits_in, 16);\n        TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);\n        bit_buffer >>= n;\n        bits_in -= n;\n    }\n\n    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);\n\n    return (d->m_pOutput_buf < d->m_pOutput_buf_end);\n}\n#else\nstatic mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)\n{\n    mz_uint flags;\n    mz_uint8 *pLZ_codes;\n\n    flags = 1;\n    for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)\n    {\n        if (flags == 1)\n            flags = *pLZ_codes++ | 0x100;\n        if (flags & 1)\n        {\n            mz_uint sym, num_extra_bits;\n            mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8));\n            pLZ_codes += 3;\n\n            MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);\n            TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);\n            TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);\n\n            if (match_dist < 512)\n            {\n                sym = s_tdefl_small_dist_sym[match_dist];\n                num_extra_bits = s_tdefl_small_dist_extra[match_dist];\n            }\n            else\n            {\n                sym = s_tdefl_large_dist_sym[match_dist >> 8];\n                num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];\n            }\n            MZ_ASSERT(d->m_huff_code_sizes[1][sym]);\n            TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);\n            TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);\n        }\n        else\n        {\n            mz_uint lit = *pLZ_codes++;\n            MZ_ASSERT(d->m_huff_code_sizes[0][lit]);\n            TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);\n        }\n    }\n\n    TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);\n\n    return (d->m_pOutput_buf < d->m_pOutput_buf_end);\n}\n#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */\n\nstatic mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)\n{\n    if (static_block)\n        tdefl_start_static_block(d);\n    else\n        tdefl_start_dynamic_block(d);\n    return tdefl_compress_lz_codes(d);\n}\n\nstatic const mz_uint s_tdefl_num_probes[11];\n\nstatic int tdefl_flush_block(tdefl_compressor *d, int flush)\n{\n    mz_uint saved_bit_buf, saved_bits_in;\n    mz_uint8 *pSaved_output_buf;\n    mz_bool comp_block_succeeded = MZ_FALSE;\n    int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;\n    mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;\n\n    d->m_pOutput_buf = pOutput_buf_start;\n    d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;\n\n    MZ_ASSERT(!d->m_output_flush_remaining);\n    d->m_output_flush_ofs = 0;\n    d->m_output_flush_remaining = 0;\n\n    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);\n    d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);\n\n    if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))\n    {\n        const mz_uint8 cmf = 0x78;\n        mz_uint8 flg, flevel = 3;\n        mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint);\n\n        /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */\n        for (i = 0; i < mz_un; i++)\n            if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break;\n\n        if (i < 2)\n            flevel = 0;\n        else if (i < 6)\n            flevel = 1;\n        else if (i == 6)\n            flevel = 2;\n\n        header = cmf << 8 | (flevel << 6);\n        header += 31 - (header % 31);\n        flg = header & 0xFF;\n\n        TDEFL_PUT_BITS(cmf, 8);\n        TDEFL_PUT_BITS(flg, 8);\n    }\n\n    TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);\n\n    pSaved_output_buf = d->m_pOutput_buf;\n    saved_bit_buf = d->m_bit_buffer;\n    saved_bits_in = d->m_bits_in;\n\n    if (!use_raw_block)\n        comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));\n\n    /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */\n    if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&\n        ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size))\n    {\n        mz_uint i;\n        d->m_pOutput_buf = pSaved_output_buf;\n        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;\n        TDEFL_PUT_BITS(0, 2);\n        if (d->m_bits_in)\n        {\n            TDEFL_PUT_BITS(0, 8 - d->m_bits_in);\n        }\n        for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)\n        {\n            TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);\n        }\n        for (i = 0; i < d->m_total_lz_bytes; ++i)\n        {\n            TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);\n        }\n    }\n    /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */\n    else if (!comp_block_succeeded)\n    {\n        d->m_pOutput_buf = pSaved_output_buf;\n        d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;\n        tdefl_compress_block(d, MZ_TRUE);\n    }\n\n    if (flush)\n    {\n        if (flush == TDEFL_FINISH)\n        {\n            if (d->m_bits_in)\n            {\n                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);\n            }\n            if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER)\n            {\n                mz_uint i, a = d->m_adler32;\n                for (i = 0; i < 4; i++)\n                {\n                    TDEFL_PUT_BITS((a >> 24) & 0xFF, 8);\n                    a <<= 8;\n                }\n            }\n        }\n        else\n        {\n            mz_uint i, z = 0;\n            TDEFL_PUT_BITS(0, 3);\n            if (d->m_bits_in)\n            {\n                TDEFL_PUT_BITS(0, 8 - d->m_bits_in);\n            }\n            for (i = 2; i; --i, z ^= 0xFFFF)\n            {\n                TDEFL_PUT_BITS(z & 0xFFFF, 16);\n            }\n        }\n    }\n\n    MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);\n\n    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);\n    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);\n\n    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;\n    d->m_pLZ_flags = d->m_lz_code_buf;\n    d->m_num_flags_left = 8;\n    d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes;\n    d->m_total_lz_bytes = 0;\n    d->m_block_index++;\n\n    if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)\n    {\n        if (d->m_pPut_buf_func)\n        {\n            *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;\n            if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))\n                return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);\n        }\n        else if (pOutput_buf_start == d->m_output_buf)\n        {\n            int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));\n            memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);\n            d->m_out_buf_ofs += bytes_to_copy;\n            if ((n -= bytes_to_copy) != 0)\n            {\n                d->m_output_flush_ofs = bytes_to_copy;\n                d->m_output_flush_remaining = n;\n            }\n        }\n        else\n        {\n            d->m_out_buf_ofs += n;\n        }\n    }\n\n    return d->m_output_flush_remaining;\n}\n\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES\n#ifdef MINIZ_UNALIGNED_USE_MEMCPY\nstatic mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p)\n{\n\tmz_uint16 ret;\n\tmemcpy(&ret, p, sizeof(mz_uint16));\n\treturn ret;\n}\nstatic mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p)\n{\n\tmz_uint16 ret;\n\tmemcpy(&ret, p, sizeof(mz_uint16));\n\treturn ret;\n}\n#else\n#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p)\n#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p)\n#endif\nstatic MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)\n{\n    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;\n    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];\n    const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q;\n    mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s);\n    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);\n    if (max_match_len <= match_len)\n        return;\n    for (;;)\n    {\n        for (;;)\n        {\n            if (--num_probes_left == 0)\n                return;\n#define TDEFL_PROBE                                                                             \\\n    next_probe_pos = d->m_next[probe_pos];                                                      \\\n    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \\\n        return;                                                                                 \\\n    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                       \\\n    if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01)                \\\n        break;\n            TDEFL_PROBE;\n            TDEFL_PROBE;\n            TDEFL_PROBE;\n        }\n        if (!dist)\n            break;\n        q = (const mz_uint16 *)(d->m_dict + probe_pos);\n        if (TDEFL_READ_UNALIGNED_WORD2(q) != s01)\n            continue;\n        p = s;\n        probe_len = 32;\n        do\n        {\n        } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&\n                 (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));\n        if (!probe_len)\n        {\n            *pMatch_dist = dist;\n            *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN);\n            break;\n        }\n        else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len)\n        {\n            *pMatch_dist = dist;\n            if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len)\n                break;\n            c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);\n        }\n    }\n}\n#else\nstatic MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)\n{\n    mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;\n    mz_uint num_probes_left = d->m_max_probes[match_len >= 32];\n    const mz_uint8 *s = d->m_dict + pos, *p, *q;\n    mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];\n    MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN);\n    if (max_match_len <= match_len)\n        return;\n    for (;;)\n    {\n        for (;;)\n        {\n            if (--num_probes_left == 0)\n                return;\n#define TDEFL_PROBE                                                                               \\\n    next_probe_pos = d->m_next[probe_pos];                                                        \\\n    if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist))   \\\n        return;                                                                                   \\\n    probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK;                                         \\\n    if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \\\n        break;\n            TDEFL_PROBE;\n            TDEFL_PROBE;\n            TDEFL_PROBE;\n        }\n        if (!dist)\n            break;\n        p = s;\n        q = d->m_dict + probe_pos;\n        for (probe_len = 0; probe_len < max_match_len; probe_len++)\n            if (*p++ != *q++)\n                break;\n        if (probe_len > match_len)\n        {\n            *pMatch_dist = dist;\n            if ((*pMatch_len = match_len = probe_len) == max_match_len)\n                return;\n            c0 = d->m_dict[pos + match_len];\n            c1 = d->m_dict[pos + match_len - 1];\n        }\n    }\n}\n#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */\n\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN\n#ifdef MINIZ_UNALIGNED_USE_MEMCPY\nstatic mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p)\n{\n\tmz_uint32 ret;\n\tmemcpy(&ret, p, sizeof(mz_uint32));\n\treturn ret;\n}\n#else\n#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p)\n#endif\nstatic mz_bool tdefl_compress_fast(tdefl_compressor *d)\n{\n    /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */\n    mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;\n    mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;\n    mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;\n\n    while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))\n    {\n        const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;\n        mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;\n        mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);\n        d->m_src_buf_left -= num_bytes_to_process;\n        lookahead_size += num_bytes_to_process;\n\n        while (num_bytes_to_process)\n        {\n            mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);\n            memcpy(d->m_dict + dst_pos, d->m_pSrc, n);\n            if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))\n                memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));\n            d->m_pSrc += n;\n            dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;\n            num_bytes_to_process -= n;\n        }\n\n        dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);\n        if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE))\n            break;\n\n        while (lookahead_size >= 4)\n        {\n            mz_uint cur_match_dist, cur_match_len = 1;\n            mz_uint8 *pCur_dict = d->m_dict + cur_pos;\n            mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF;\n            mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;\n            mz_uint probe_pos = d->m_hash[hash];\n            d->m_hash[hash] = (mz_uint16)lookahead_pos;\n\n            if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))\n            {\n                const mz_uint16 *p = (const mz_uint16 *)pCur_dict;\n                const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);\n                mz_uint32 probe_len = 32;\n                do\n                {\n                } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) &&\n                         (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0));\n                cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);\n                if (!probe_len)\n                    cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;\n\n                if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)))\n                {\n                    cur_match_len = 1;\n                    *pLZ_code_buf++ = (mz_uint8)first_trigram;\n                    *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);\n                    d->m_huff_count[0][(mz_uint8)first_trigram]++;\n                }\n                else\n                {\n                    mz_uint32 s0, s1;\n                    cur_match_len = MZ_MIN(cur_match_len, lookahead_size);\n\n                    MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));\n\n                    cur_match_dist--;\n\n                    pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);\n#ifdef MINIZ_UNALIGNED_USE_MEMCPY\n\t\t\t\t\tmemcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist));\n#else\n                    *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;\n#endif\n                    pLZ_code_buf += 3;\n                    *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);\n\n                    s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];\n                    s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];\n                    d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;\n\n                    d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;\n                }\n            }\n            else\n            {\n                *pLZ_code_buf++ = (mz_uint8)first_trigram;\n                *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);\n                d->m_huff_count[0][(mz_uint8)first_trigram]++;\n            }\n\n            if (--num_flags_left == 0)\n            {\n                num_flags_left = 8;\n                pLZ_flags = pLZ_code_buf++;\n            }\n\n            total_lz_bytes += cur_match_len;\n            lookahead_pos += cur_match_len;\n            dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE);\n            cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;\n            MZ_ASSERT(lookahead_size >= cur_match_len);\n            lookahead_size -= cur_match_len;\n\n            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])\n            {\n                int n;\n                d->m_lookahead_pos = lookahead_pos;\n                d->m_lookahead_size = lookahead_size;\n                d->m_dict_size = dict_size;\n                d->m_total_lz_bytes = total_lz_bytes;\n                d->m_pLZ_code_buf = pLZ_code_buf;\n                d->m_pLZ_flags = pLZ_flags;\n                d->m_num_flags_left = num_flags_left;\n                if ((n = tdefl_flush_block(d, 0)) != 0)\n                    return (n < 0) ? MZ_FALSE : MZ_TRUE;\n                total_lz_bytes = d->m_total_lz_bytes;\n                pLZ_code_buf = d->m_pLZ_code_buf;\n                pLZ_flags = d->m_pLZ_flags;\n                num_flags_left = d->m_num_flags_left;\n            }\n        }\n\n        while (lookahead_size)\n        {\n            mz_uint8 lit = d->m_dict[cur_pos];\n\n            total_lz_bytes++;\n            *pLZ_code_buf++ = lit;\n            *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);\n            if (--num_flags_left == 0)\n            {\n                num_flags_left = 8;\n                pLZ_flags = pLZ_code_buf++;\n            }\n\n            d->m_huff_count[0][lit]++;\n\n            lookahead_pos++;\n            dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE);\n            cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;\n            lookahead_size--;\n\n            if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])\n            {\n                int n;\n                d->m_lookahead_pos = lookahead_pos;\n                d->m_lookahead_size = lookahead_size;\n                d->m_dict_size = dict_size;\n                d->m_total_lz_bytes = total_lz_bytes;\n                d->m_pLZ_code_buf = pLZ_code_buf;\n                d->m_pLZ_flags = pLZ_flags;\n                d->m_num_flags_left = num_flags_left;\n                if ((n = tdefl_flush_block(d, 0)) != 0)\n                    return (n < 0) ? MZ_FALSE : MZ_TRUE;\n                total_lz_bytes = d->m_total_lz_bytes;\n                pLZ_code_buf = d->m_pLZ_code_buf;\n                pLZ_flags = d->m_pLZ_flags;\n                num_flags_left = d->m_num_flags_left;\n            }\n        }\n    }\n\n    d->m_lookahead_pos = lookahead_pos;\n    d->m_lookahead_size = lookahead_size;\n    d->m_dict_size = dict_size;\n    d->m_total_lz_bytes = total_lz_bytes;\n    d->m_pLZ_code_buf = pLZ_code_buf;\n    d->m_pLZ_flags = pLZ_flags;\n    d->m_num_flags_left = num_flags_left;\n    return MZ_TRUE;\n}\n#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */\n\nstatic MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)\n{\n    d->m_total_lz_bytes++;\n    *d->m_pLZ_code_buf++ = lit;\n    *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1);\n    if (--d->m_num_flags_left == 0)\n    {\n        d->m_num_flags_left = 8;\n        d->m_pLZ_flags = d->m_pLZ_code_buf++;\n    }\n    d->m_huff_count[0][lit]++;\n}\n\nstatic MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)\n{\n    mz_uint32 s0, s1;\n\n    MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));\n\n    d->m_total_lz_bytes += match_len;\n\n    d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);\n\n    match_dist -= 1;\n    d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);\n    d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8);\n    d->m_pLZ_code_buf += 3;\n\n    *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80);\n    if (--d->m_num_flags_left == 0)\n    {\n        d->m_num_flags_left = 8;\n        d->m_pLZ_flags = d->m_pLZ_code_buf++;\n    }\n\n    s0 = s_tdefl_small_dist_sym[match_dist & 511];\n    s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];\n    d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;\n    d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;\n}\n\nstatic mz_bool tdefl_compress_normal(tdefl_compressor *d)\n{\n    const mz_uint8 *pSrc = d->m_pSrc;\n    size_t src_buf_left = d->m_src_buf_left;\n    tdefl_flush flush = d->m_flush;\n\n    while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))\n    {\n        mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;\n        /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */\n        if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))\n        {\n            mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;\n            mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];\n            mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);\n            const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL;\n            src_buf_left -= num_bytes_to_process;\n            d->m_lookahead_size += num_bytes_to_process;\n            while (pSrc != pSrc_end)\n            {\n                mz_uint8 c = *pSrc++;\n                d->m_dict[dst_pos] = c;\n                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))\n                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;\n                hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);\n                d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];\n                d->m_hash[hash] = (mz_uint16)(ins_pos);\n                dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;\n                ins_pos++;\n            }\n        }\n        else\n        {\n            while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))\n            {\n                mz_uint8 c = *pSrc++;\n                mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;\n                src_buf_left--;\n                d->m_dict[dst_pos] = c;\n                if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))\n                    d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;\n                if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)\n                {\n                    mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;\n                    mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);\n                    d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash];\n                    d->m_hash[hash] = (mz_uint16)(ins_pos);\n                }\n            }\n        }\n        d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);\n        if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))\n            break;\n\n        /* Simple lazy/greedy parsing state machine. */\n        len_to_move = 1;\n        cur_match_dist = 0;\n        cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1);\n        cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;\n        if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))\n        {\n            if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))\n            {\n                mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];\n                cur_match_len = 0;\n                while (cur_match_len < d->m_lookahead_size)\n                {\n                    if (d->m_dict[cur_pos + cur_match_len] != c)\n                        break;\n                    cur_match_len++;\n                }\n                if (cur_match_len < TDEFL_MIN_MATCH_LEN)\n                    cur_match_len = 0;\n                else\n                    cur_match_dist = 1;\n            }\n        }\n        else\n        {\n            tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);\n        }\n        if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))\n        {\n            cur_match_dist = cur_match_len = 0;\n        }\n        if (d->m_saved_match_len)\n        {\n            if (cur_match_len > d->m_saved_match_len)\n            {\n                tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);\n                if (cur_match_len >= 128)\n                {\n                    tdefl_record_match(d, cur_match_len, cur_match_dist);\n                    d->m_saved_match_len = 0;\n                    len_to_move = cur_match_len;\n                }\n                else\n                {\n                    d->m_saved_lit = d->m_dict[cur_pos];\n                    d->m_saved_match_dist = cur_match_dist;\n                    d->m_saved_match_len = cur_match_len;\n                }\n            }\n            else\n            {\n                tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);\n                len_to_move = d->m_saved_match_len - 1;\n                d->m_saved_match_len = 0;\n            }\n        }\n        else if (!cur_match_dist)\n            tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);\n        else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))\n        {\n            tdefl_record_match(d, cur_match_len, cur_match_dist);\n            len_to_move = cur_match_len;\n        }\n        else\n        {\n            d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)];\n            d->m_saved_match_dist = cur_match_dist;\n            d->m_saved_match_len = cur_match_len;\n        }\n        /* Move the lookahead forward by len_to_move bytes. */\n        d->m_lookahead_pos += len_to_move;\n        MZ_ASSERT(d->m_lookahead_size >= len_to_move);\n        d->m_lookahead_size -= len_to_move;\n        d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE);\n        /* Check if it's time to flush the current LZ codes to the internal output buffer. */\n        if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||\n            ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))))\n        {\n            int n;\n            d->m_pSrc = pSrc;\n            d->m_src_buf_left = src_buf_left;\n            if ((n = tdefl_flush_block(d, 0)) != 0)\n                return (n < 0) ? MZ_FALSE : MZ_TRUE;\n        }\n    }\n\n    d->m_pSrc = pSrc;\n    d->m_src_buf_left = src_buf_left;\n    return MZ_TRUE;\n}\n\nstatic tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)\n{\n    if (d->m_pIn_buf_size)\n    {\n        *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;\n    }\n\n    if (d->m_pOut_buf_size)\n    {\n        size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);\n        memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);\n        d->m_output_flush_ofs += (mz_uint)n;\n        d->m_output_flush_remaining -= (mz_uint)n;\n        d->m_out_buf_ofs += n;\n\n        *d->m_pOut_buf_size = d->m_out_buf_ofs;\n    }\n\n    return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;\n}\n\ntdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)\n{\n    if (!d)\n    {\n        if (pIn_buf_size)\n            *pIn_buf_size = 0;\n        if (pOut_buf_size)\n            *pOut_buf_size = 0;\n        return TDEFL_STATUS_BAD_PARAM;\n    }\n\n    d->m_pIn_buf = pIn_buf;\n    d->m_pIn_buf_size = pIn_buf_size;\n    d->m_pOut_buf = pOut_buf;\n    d->m_pOut_buf_size = pOut_buf_size;\n    d->m_pSrc = (const mz_uint8 *)(pIn_buf);\n    d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;\n    d->m_out_buf_ofs = 0;\n    d->m_flush = flush;\n\n    if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||\n        (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf))\n    {\n        if (pIn_buf_size)\n            *pIn_buf_size = 0;\n        if (pOut_buf_size)\n            *pOut_buf_size = 0;\n        return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);\n    }\n    d->m_wants_to_finish |= (flush == TDEFL_FINISH);\n\n    if ((d->m_output_flush_remaining) || (d->m_finished))\n        return (d->m_prev_return_status = tdefl_flush_output_buffer(d));\n\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN\n    if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&\n        ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&\n        ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))\n    {\n        if (!tdefl_compress_fast(d))\n            return d->m_prev_return_status;\n    }\n    else\n#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */\n    {\n        if (!tdefl_compress_normal(d))\n            return d->m_prev_return_status;\n    }\n\n    if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))\n        d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);\n\n    if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))\n    {\n        if (tdefl_flush_block(d, flush) < 0)\n            return d->m_prev_return_status;\n        d->m_finished = (flush == TDEFL_FINISH);\n        if (flush == TDEFL_FULL_FLUSH)\n        {\n            MZ_CLEAR_ARR(d->m_hash);\n            MZ_CLEAR_ARR(d->m_next);\n            d->m_dict_size = 0;\n        }\n    }\n\n    return (d->m_prev_return_status = tdefl_flush_output_buffer(d));\n}\n\ntdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)\n{\n    MZ_ASSERT(d->m_pPut_buf_func);\n    return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);\n}\n\ntdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)\n{\n    d->m_pPut_buf_func = pPut_buf_func;\n    d->m_pPut_buf_user = pPut_buf_user;\n    d->m_flags = (mz_uint)(flags);\n    d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3;\n    d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;\n    d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;\n    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))\n        MZ_CLEAR_ARR(d->m_hash);\n    d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;\n    d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;\n    d->m_pLZ_code_buf = d->m_lz_code_buf + 1;\n    d->m_pLZ_flags = d->m_lz_code_buf;\n    *d->m_pLZ_flags = 0;\n    d->m_num_flags_left = 8;\n    d->m_pOutput_buf = d->m_output_buf;\n    d->m_pOutput_buf_end = d->m_output_buf;\n    d->m_prev_return_status = TDEFL_STATUS_OKAY;\n    d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0;\n    d->m_adler32 = 1;\n    d->m_pIn_buf = NULL;\n    d->m_pOut_buf = NULL;\n    d->m_pIn_buf_size = NULL;\n    d->m_pOut_buf_size = NULL;\n    d->m_flush = TDEFL_NO_FLUSH;\n    d->m_pSrc = NULL;\n    d->m_src_buf_left = 0;\n    d->m_out_buf_ofs = 0;\n    if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))\n        MZ_CLEAR_ARR(d->m_dict);\n    memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);\n    memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);\n    return TDEFL_STATUS_OKAY;\n}\n\ntdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)\n{\n    return d->m_prev_return_status;\n}\n\nmz_uint32 tdefl_get_adler32(tdefl_compressor *d)\n{\n    return d->m_adler32;\n}\n\nmz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)\n{\n    tdefl_compressor *pComp;\n    mz_bool succeeded;\n    if (((buf_len) && (!pBuf)) || (!pPut_buf_func))\n        return MZ_FALSE;\n    pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));\n    if (!pComp)\n        return MZ_FALSE;\n    succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);\n    succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);\n    MZ_FREE(pComp);\n    return succeeded;\n}\n\ntypedef struct\n{\n    size_t m_size, m_capacity;\n    mz_uint8 *m_pBuf;\n    mz_bool m_expandable;\n} tdefl_output_buffer;\n\nstatic mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)\n{\n    tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;\n    size_t new_size = p->m_size + len;\n    if (new_size > p->m_capacity)\n    {\n        size_t new_capacity = p->m_capacity;\n        mz_uint8 *pNew_buf;\n        if (!p->m_expandable)\n            return MZ_FALSE;\n        do\n        {\n            new_capacity = MZ_MAX(128U, new_capacity << 1U);\n        } while (new_size > new_capacity);\n        pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity);\n        if (!pNew_buf)\n            return MZ_FALSE;\n        p->m_pBuf = pNew_buf;\n        p->m_capacity = new_capacity;\n    }\n    memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len);\n    p->m_size = new_size;\n    return MZ_TRUE;\n}\n\nvoid *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)\n{\n    tdefl_output_buffer out_buf;\n    MZ_CLEAR_OBJ(out_buf);\n    if (!pOut_len)\n        return MZ_FALSE;\n    else\n        *pOut_len = 0;\n    out_buf.m_expandable = MZ_TRUE;\n    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))\n        return NULL;\n    *pOut_len = out_buf.m_size;\n    return out_buf.m_pBuf;\n}\n\nsize_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)\n{\n    tdefl_output_buffer out_buf;\n    MZ_CLEAR_OBJ(out_buf);\n    if (!pOut_buf)\n        return 0;\n    out_buf.m_pBuf = (mz_uint8 *)pOut_buf;\n    out_buf.m_capacity = out_buf_len;\n    if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags))\n        return 0;\n    return out_buf.m_size;\n}\n\nstatic const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };\n\n/* level may actually range from [0,10] (10 is a \"hidden\" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */\nmz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)\n{\n    mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);\n    if (window_bits > 0)\n        comp_flags |= TDEFL_WRITE_ZLIB_HEADER;\n\n    if (!level)\n        comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;\n    else if (strategy == MZ_FILTERED)\n        comp_flags |= TDEFL_FILTER_MATCHES;\n    else if (strategy == MZ_HUFFMAN_ONLY)\n        comp_flags &= ~TDEFL_MAX_PROBES_MASK;\n    else if (strategy == MZ_FIXED)\n        comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;\n    else if (strategy == MZ_RLE)\n        comp_flags |= TDEFL_RLE_MATCHES;\n\n    return comp_flags;\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */\n#endif\n\n/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at\n http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.\n This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */\nvoid *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)\n{\n    /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */\n    static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 };\n    tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));\n    tdefl_output_buffer out_buf;\n    int i, bpl = w * num_chans, y, z;\n    mz_uint32 c;\n    *pLen_out = 0;\n    if (!pComp)\n        return NULL;\n    MZ_CLEAR_OBJ(out_buf);\n    out_buf.m_expandable = MZ_TRUE;\n    out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h);\n    if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity)))\n    {\n        MZ_FREE(pComp);\n        return NULL;\n    }\n    /* write dummy header */\n    for (z = 41; z; --z)\n        tdefl_output_buffer_putter(&z, 1, &out_buf);\n    /* compress image data */\n    tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);\n    for (y = 0; y < h; ++y)\n    {\n        tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH);\n        tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH);\n    }\n    if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE)\n    {\n        MZ_FREE(pComp);\n        MZ_FREE(out_buf.m_pBuf);\n        return NULL;\n    }\n    /* write real header */\n    *pLen_out = out_buf.m_size - 41;\n    {\n        static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };\n        mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d,\n                                0x0a, 0x1a, 0x0a, 0x00, 0x00,\n                                0x00, 0x0d, 0x49, 0x48, 0x44,\n                                0x52, 0x00, 0x00, 0x00, 0x00,\n                                0x00, 0x00, 0x00, 0x00, 0x08,\n                                0x00, 0x00, 0x00, 0x00, 0x00,\n                                0x00, 0x00, 0x00, 0x00, 0x00,\n                                0x00, 0x00, 0x49, 0x44, 0x41,\n                                0x54 };\n        pnghdr[18] = (mz_uint8)(w >> 8);\n        pnghdr[19] = (mz_uint8)w;\n        pnghdr[22] = (mz_uint8)(h >> 8);\n        pnghdr[23] = (mz_uint8)h;\n        pnghdr[25] = chans[num_chans];\n        pnghdr[33] = (mz_uint8)(*pLen_out >> 24);\n        pnghdr[34] = (mz_uint8)(*pLen_out >> 16);\n        pnghdr[35] = (mz_uint8)(*pLen_out >> 8);\n        pnghdr[36] = (mz_uint8)*pLen_out;\n        c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17);\n        for (i = 0; i < 4; ++i, c <<= 8)\n            ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24);\n        memcpy(out_buf.m_pBuf, pnghdr, 41);\n    }\n    /* write footer (IDAT CRC-32, followed by IEND chunk) */\n    if (!tdefl_output_buffer_putter(\"\\0\\0\\0\\0\\0\\0\\0\\0\\x49\\x45\\x4e\\x44\\xae\\x42\\x60\\x82\", 16, &out_buf))\n    {\n        *pLen_out = 0;\n        MZ_FREE(pComp);\n        MZ_FREE(out_buf.m_pBuf);\n        return NULL;\n    }\n    c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4);\n    for (i = 0; i < 4; ++i, c <<= 8)\n        (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24);\n    /* compute final size of file, grab compressed data buffer and return */\n    *pLen_out += 57;\n    MZ_FREE(pComp);\n    return out_buf.m_pBuf;\n}\nvoid *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)\n{\n    /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */\n    return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);\n}\n\n#ifndef MINIZ_NO_MALLOC\n/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */\n/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */\n/* structure size and allocation mechanism. */\ntdefl_compressor *tdefl_compressor_alloc(void)\n{\n    return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));\n}\n\nvoid tdefl_compressor_free(tdefl_compressor *pComp)\n{\n    MZ_FREE(pComp);\n}\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/\n /**************************************************************************\n *\n * Copyright 2013-2014 RAD Game Tools and Valve Software\n * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC\n * All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n **************************************************************************/\n\n\n\n#ifndef MINIZ_NO_INFLATE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------- Low-level Decompression (completely independent from all compression API's) */\n\n#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)\n#define TINFL_MEMSET(p, c, l) memset(p, c, l)\n\n#define TINFL_CR_BEGIN  \\\n    switch (r->m_state) \\\n    {                   \\\n        case 0:\n#define TINFL_CR_RETURN(state_index, result) \\\n    do                                       \\\n    {                                        \\\n        status = result;                     \\\n        r->m_state = state_index;            \\\n        goto common_exit;                    \\\n        case state_index:;                   \\\n    }                                        \\\n    MZ_MACRO_END\n#define TINFL_CR_RETURN_FOREVER(state_index, result) \\\n    do                                               \\\n    {                                                \\\n        for (;;)                                     \\\n        {                                            \\\n            TINFL_CR_RETURN(state_index, result);    \\\n        }                                            \\\n    }                                                \\\n    MZ_MACRO_END\n#define TINFL_CR_FINISH }\n\n#define TINFL_GET_BYTE(state_index, c)                                                                                                                           \\\n    do                                                                                                                                                           \\\n    {                                                                                                                                                            \\\n        while (pIn_buf_cur >= pIn_buf_end)                                                                                                                       \\\n        {                                                                                                                                                        \\\n            TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \\\n        }                                                                                                                                                        \\\n        c = *pIn_buf_cur++;                                                                                                                                      \\\n    }                                                                                                                                                            \\\n    MZ_MACRO_END\n\n#define TINFL_NEED_BITS(state_index, n)                \\\n    do                                                 \\\n    {                                                  \\\n        mz_uint c;                                     \\\n        TINFL_GET_BYTE(state_index, c);                \\\n        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \\\n        num_bits += 8;                                 \\\n    } while (num_bits < (mz_uint)(n))\n#define TINFL_SKIP_BITS(state_index, n)      \\\n    do                                       \\\n    {                                        \\\n        if (num_bits < (mz_uint)(n))         \\\n        {                                    \\\n            TINFL_NEED_BITS(state_index, n); \\\n        }                                    \\\n        bit_buf >>= (n);                     \\\n        num_bits -= (n);                     \\\n    }                                        \\\n    MZ_MACRO_END\n#define TINFL_GET_BITS(state_index, b, n)    \\\n    do                                       \\\n    {                                        \\\n        if (num_bits < (mz_uint)(n))         \\\n        {                                    \\\n            TINFL_NEED_BITS(state_index, n); \\\n        }                                    \\\n        b = bit_buf & ((1 << (n)) - 1);      \\\n        bit_buf >>= (n);                     \\\n        num_bits -= (n);                     \\\n    }                                        \\\n    MZ_MACRO_END\n\n/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */\n/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */\n/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */\n/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */\n#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree)                    \\\n    do                                                                         \\\n    {                                                                          \\\n        temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)];                \\\n        if (temp >= 0)                                                         \\\n        {                                                                      \\\n            code_len = temp >> 9;                                              \\\n            if ((code_len) && (num_bits >= code_len))                          \\\n                break;                                                         \\\n        }                                                                      \\\n        else if (num_bits > TINFL_FAST_LOOKUP_BITS)                            \\\n        {                                                                      \\\n            code_len = TINFL_FAST_LOOKUP_BITS;                                 \\\n            do                                                                 \\\n            {                                                                  \\\n                temp = pTree[~temp + ((bit_buf >> code_len++) & 1)];           \\\n            } while ((temp < 0) && (num_bits >= (code_len + 1)));              \\\n            if (temp >= 0)                                                     \\\n                break;                                                         \\\n        }                                                                      \\\n        TINFL_GET_BYTE(state_index, c);                                        \\\n        bit_buf |= (((tinfl_bit_buf_t)c) << num_bits);                         \\\n        num_bits += 8;                                                         \\\n    } while (num_bits < 15);\n\n/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */\n/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */\n/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */\n/* The slow path is only executed at the very end of the input buffer. */\n/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */\n/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */\n#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree)                                                                         \\\n    do                                                                                                                              \\\n    {                                                                                                                               \\\n        int temp;                                                                                                                   \\\n        mz_uint code_len, c;                                                                                                        \\\n        if (num_bits < 15)                                                                                                          \\\n        {                                                                                                                           \\\n            if ((pIn_buf_end - pIn_buf_cur) < 2)                                                                                    \\\n            {                                                                                                                       \\\n                TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree);                                                                \\\n            }                                                                                                                       \\\n            else                                                                                                                    \\\n            {                                                                                                                       \\\n                bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \\\n                pIn_buf_cur += 2;                                                                                                   \\\n                num_bits += 16;                                                                                                     \\\n            }                                                                                                                       \\\n        }                                                                                                                           \\\n        if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)                                                          \\\n            code_len = temp >> 9, temp &= 511;                                                                                      \\\n        else                                                                                                                        \\\n        {                                                                                                                           \\\n            code_len = TINFL_FAST_LOOKUP_BITS;                                                                                      \\\n            do                                                                                                                      \\\n            {                                                                                                                       \\\n                temp = pTree[~temp + ((bit_buf >> code_len++) & 1)];                                                                \\\n            } while (temp < 0);                                                                                                     \\\n        }                                                                                                                           \\\n        sym = temp;                                                                                                                 \\\n        bit_buf >>= code_len;                                                                                                       \\\n        num_bits -= code_len;                                                                                                       \\\n    }                                                                                                                               \\\n    MZ_MACRO_END\n\nstatic void tinfl_clear_tree(tinfl_decompressor *r)\n{\n    if (r->m_type == 0)\n        MZ_CLEAR_ARR(r->m_tree_0);\n    else if (r->m_type == 1)\n        MZ_CLEAR_ARR(r->m_tree_1);\n    else\n        MZ_CLEAR_ARR(r->m_tree_2);\n}\n\ntinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)\n{\n    static const mz_uint16 s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 };\n    static const mz_uint8 s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 };\n    static const mz_uint16 s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 };\n    static const mz_uint8 s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };\n    static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };\n    static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 };\n\n    mz_int16 *pTrees[3];\n    mz_uint8 *pCode_sizes[3];\n\n    tinfl_status status = TINFL_STATUS_FAILED;\n    mz_uint32 num_bits, dist, counter, num_extra;\n    tinfl_bit_buf_t bit_buf;\n    const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;\n    mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL;\n    size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;\n\n    /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */\n    if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start))\n    {\n        *pIn_buf_size = *pOut_buf_size = 0;\n        return TINFL_STATUS_BAD_PARAM;\n    }\n\n    pTrees[0] = r->m_tree_0;\n    pTrees[1] = r->m_tree_1;\n    pTrees[2] = r->m_tree_2;\n    pCode_sizes[0] = r->m_code_size_0;\n    pCode_sizes[1] = r->m_code_size_1;\n    pCode_sizes[2] = r->m_code_size_2;\n\n    num_bits = r->m_num_bits;\n    bit_buf = r->m_bit_buf;\n    dist = r->m_dist;\n    counter = r->m_counter;\n    num_extra = r->m_num_extra;\n    dist_from_out_buf_start = r->m_dist_from_out_buf_start;\n    TINFL_CR_BEGIN\n\n    bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0;\n    r->m_z_adler32 = r->m_check_adler32 = 1;\n    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)\n    {\n        TINFL_GET_BYTE(1, r->m_zhdr0);\n        TINFL_GET_BYTE(2, r->m_zhdr1);\n        counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));\n        if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))\n            counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)((size_t)1 << (8U + (r->m_zhdr0 >> 4)))));\n        if (counter)\n        {\n            TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED);\n        }\n    }\n\n    do\n    {\n        TINFL_GET_BITS(3, r->m_final, 3);\n        r->m_type = r->m_final >> 1;\n        if (r->m_type == 0)\n        {\n            TINFL_SKIP_BITS(5, num_bits & 7);\n            for (counter = 0; counter < 4; ++counter)\n            {\n                if (num_bits)\n                    TINFL_GET_BITS(6, r->m_raw_header[counter], 8);\n                else\n                    TINFL_GET_BYTE(7, r->m_raw_header[counter]);\n            }\n            if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8))))\n            {\n                TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED);\n            }\n            while ((counter) && (num_bits))\n            {\n                TINFL_GET_BITS(51, dist, 8);\n                while (pOut_buf_cur >= pOut_buf_end)\n                {\n                    TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT);\n                }\n                *pOut_buf_cur++ = (mz_uint8)dist;\n                counter--;\n            }\n            while (counter)\n            {\n                size_t n;\n                while (pOut_buf_cur >= pOut_buf_end)\n                {\n                    TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT);\n                }\n                while (pIn_buf_cur >= pIn_buf_end)\n                {\n                    TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS);\n                }\n                n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);\n                TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n);\n                pIn_buf_cur += n;\n                pOut_buf_cur += n;\n                counter -= (mz_uint)n;\n            }\n        }\n        else if (r->m_type == 3)\n        {\n            TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);\n        }\n        else\n        {\n            if (r->m_type == 1)\n            {\n                mz_uint8 *p = r->m_code_size_0;\n                mz_uint i;\n                r->m_table_sizes[0] = 288;\n                r->m_table_sizes[1] = 32;\n                TINFL_MEMSET(r->m_code_size_1, 5, 32);\n                for (i = 0; i <= 143; ++i)\n                    *p++ = 8;\n                for (; i <= 255; ++i)\n                    *p++ = 9;\n                for (; i <= 279; ++i)\n                    *p++ = 7;\n                for (; i <= 287; ++i)\n                    *p++ = 8;\n            }\n            else\n            {\n                for (counter = 0; counter < 3; counter++)\n                {\n                    TINFL_GET_BITS(11, r->m_table_sizes[counter], \"\\05\\05\\04\"[counter]);\n                    r->m_table_sizes[counter] += s_min_table_sizes[counter];\n                }\n                MZ_CLEAR_ARR(r->m_code_size_2);\n                for (counter = 0; counter < r->m_table_sizes[2]; counter++)\n                {\n                    mz_uint s;\n                    TINFL_GET_BITS(14, s, 3);\n                    r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s;\n                }\n                r->m_table_sizes[2] = 19;\n            }\n            for (; (int)r->m_type >= 0; r->m_type--)\n            {\n                int tree_next, tree_cur;\n                mz_int16 *pLookUp;\n                mz_int16 *pTree;\n                mz_uint8 *pCode_size;\n                mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16];\n                pLookUp = r->m_look_up[r->m_type];\n                pTree = pTrees[r->m_type];\n                pCode_size = pCode_sizes[r->m_type];\n                MZ_CLEAR_ARR(total_syms);\n                TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0]));\n                tinfl_clear_tree(r);\n                for (i = 0; i < r->m_table_sizes[r->m_type]; ++i)\n                    total_syms[pCode_size[i]]++;\n                used_syms = 0, total = 0;\n                next_code[0] = next_code[1] = 0;\n                for (i = 1; i <= 15; ++i)\n                {\n                    used_syms += total_syms[i];\n                    next_code[i + 1] = (total = ((total + total_syms[i]) << 1));\n                }\n                if ((65536 != total) && (used_syms > 1))\n                {\n                    TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);\n                }\n                for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)\n                {\n                    mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index];\n                    if (!code_size)\n                        continue;\n                    cur_code = next_code[code_size]++;\n                    for (l = code_size; l > 0; l--, cur_code >>= 1)\n                        rev_code = (rev_code << 1) | (cur_code & 1);\n                    if (code_size <= TINFL_FAST_LOOKUP_BITS)\n                    {\n                        mz_int16 k = (mz_int16)((code_size << 9) | sym_index);\n                        while (rev_code < TINFL_FAST_LOOKUP_SIZE)\n                        {\n                            pLookUp[rev_code] = k;\n                            rev_code += (1 << code_size);\n                        }\n                        continue;\n                    }\n                    if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)]))\n                    {\n                        pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next;\n                        tree_cur = tree_next;\n                        tree_next -= 2;\n                    }\n                    rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);\n                    for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)\n                    {\n                        tree_cur -= ((rev_code >>= 1) & 1);\n                        if (!pTree[-tree_cur - 1])\n                        {\n                            pTree[-tree_cur - 1] = (mz_int16)tree_next;\n                            tree_cur = tree_next;\n                            tree_next -= 2;\n                        }\n                        else\n                            tree_cur = pTree[-tree_cur - 1];\n                    }\n                    tree_cur -= ((rev_code >>= 1) & 1);\n                    pTree[-tree_cur - 1] = (mz_int16)sym_index;\n                }\n                if (r->m_type == 2)\n                {\n                    for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);)\n                    {\n                        mz_uint s;\n                        TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2);\n                        if (dist < 16)\n                        {\n                            r->m_len_codes[counter++] = (mz_uint8)dist;\n                            continue;\n                        }\n                        if ((dist == 16) && (!counter))\n                        {\n                            TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);\n                        }\n                        num_extra = \"\\02\\03\\07\"[dist - 16];\n                        TINFL_GET_BITS(18, s, num_extra);\n                        s += \"\\03\\03\\013\"[dist - 16];\n                        TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s);\n                        counter += s;\n                    }\n                    if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)\n                    {\n                        TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);\n                    }\n                    TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]);\n                    TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);\n                }\n            }\n            for (;;)\n            {\n                mz_uint8 *pSrc;\n                for (;;)\n                {\n                    if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))\n                    {\n                        TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0);\n                        if (counter >= 256)\n                            break;\n                        while (pOut_buf_cur >= pOut_buf_end)\n                        {\n                            TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT);\n                        }\n                        *pOut_buf_cur++ = (mz_uint8)counter;\n                    }\n                    else\n                    {\n                        int sym2;\n                        mz_uint code_len;\n#if TINFL_USE_64BIT_BITBUF\n                        if (num_bits < 30)\n                        {\n                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits);\n                            pIn_buf_cur += 4;\n                            num_bits += 32;\n                        }\n#else\n                        if (num_bits < 15)\n                        {\n                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);\n                            pIn_buf_cur += 2;\n                            num_bits += 16;\n                        }\n#endif\n                        if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)\n                            code_len = sym2 >> 9;\n                        else\n                        {\n                            code_len = TINFL_FAST_LOOKUP_BITS;\n                            do\n                            {\n                                sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)];\n                            } while (sym2 < 0);\n                        }\n                        counter = sym2;\n                        bit_buf >>= code_len;\n                        num_bits -= code_len;\n                        if (counter & 256)\n                            break;\n\n#if !TINFL_USE_64BIT_BITBUF\n                        if (num_bits < 15)\n                        {\n                            bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits);\n                            pIn_buf_cur += 2;\n                            num_bits += 16;\n                        }\n#endif\n                        if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)\n                            code_len = sym2 >> 9;\n                        else\n                        {\n                            code_len = TINFL_FAST_LOOKUP_BITS;\n                            do\n                            {\n                                sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)];\n                            } while (sym2 < 0);\n                        }\n                        bit_buf >>= code_len;\n                        num_bits -= code_len;\n\n                        pOut_buf_cur[0] = (mz_uint8)counter;\n                        if (sym2 & 256)\n                        {\n                            pOut_buf_cur++;\n                            counter = sym2;\n                            break;\n                        }\n                        pOut_buf_cur[1] = (mz_uint8)sym2;\n                        pOut_buf_cur += 2;\n                    }\n                }\n                if ((counter &= 511) == 256)\n                    break;\n\n                num_extra = s_length_extra[counter - 257];\n                counter = s_length_base[counter - 257];\n                if (num_extra)\n                {\n                    mz_uint extra_bits;\n                    TINFL_GET_BITS(25, extra_bits, num_extra);\n                    counter += extra_bits;\n                }\n\n                TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1);\n                num_extra = s_dist_extra[dist];\n                dist = s_dist_base[dist];\n                if (num_extra)\n                {\n                    mz_uint extra_bits;\n                    TINFL_GET_BITS(27, extra_bits, num_extra);\n                    dist += extra_bits;\n                }\n\n                dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;\n                if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))\n                {\n                    TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);\n                }\n\n                pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);\n\n                if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)\n                {\n                    while (counter--)\n                    {\n                        while (pOut_buf_cur >= pOut_buf_end)\n                        {\n                            TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT);\n                        }\n                        *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];\n                    }\n                    continue;\n                }\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES\n                else if ((counter >= 9) && (counter <= dist))\n                {\n                    const mz_uint8 *pSrc_end = pSrc + (counter & ~7);\n                    do\n                    {\n#ifdef MINIZ_UNALIGNED_USE_MEMCPY\n\t\t\t\t\t\tmemcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2);\n#else\n                        ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];\n                        ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];\n#endif\n                        pOut_buf_cur += 8;\n                    } while ((pSrc += 8) < pSrc_end);\n                    if ((counter &= 7) < 3)\n                    {\n                        if (counter)\n                        {\n                            pOut_buf_cur[0] = pSrc[0];\n                            if (counter > 1)\n                                pOut_buf_cur[1] = pSrc[1];\n                            pOut_buf_cur += counter;\n                        }\n                        continue;\n                    }\n                }\n#endif\n                while(counter>2)\n                {\n                    pOut_buf_cur[0] = pSrc[0];\n                    pOut_buf_cur[1] = pSrc[1];\n                    pOut_buf_cur[2] = pSrc[2];\n                    pOut_buf_cur += 3;\n                    pSrc += 3;\n\t\t\t\t\tcounter -= 3;\n                }\n                if (counter > 0)\n                {\n                    pOut_buf_cur[0] = pSrc[0];\n                    if (counter > 1)\n                        pOut_buf_cur[1] = pSrc[1];\n                    pOut_buf_cur += counter;\n                }\n            }\n        }\n    } while (!(r->m_final & 1));\n\n    /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */\n    /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */\n    TINFL_SKIP_BITS(32, num_bits & 7);\n    while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))\n    {\n        --pIn_buf_cur;\n        num_bits -= 8;\n    }\n    bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits);\n    MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */\n\n    if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)\n    {\n        for (counter = 0; counter < 4; ++counter)\n        {\n            mz_uint s;\n            if (num_bits)\n                TINFL_GET_BITS(41, s, 8);\n            else\n                TINFL_GET_BYTE(42, s);\n            r->m_z_adler32 = (r->m_z_adler32 << 8) | s;\n        }\n    }\n    TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);\n\n    TINFL_CR_FINISH\n\ncommon_exit:\n    /* As long as we aren't telling the caller that we NEED more input to make forward progress: */\n    /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */\n    /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */\n    if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS))\n    {\n        while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8))\n        {\n            --pIn_buf_cur;\n            num_bits -= 8;\n        }\n    }\n    r->m_num_bits = num_bits;\n    r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits);\n    r->m_dist = dist;\n    r->m_counter = counter;\n    r->m_num_extra = num_extra;\n    r->m_dist_from_out_buf_start = dist_from_out_buf_start;\n    *pIn_buf_size = pIn_buf_cur - pIn_buf_next;\n    *pOut_buf_size = pOut_buf_cur - pOut_buf_next;\n    if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))\n    {\n        const mz_uint8 *ptr = pOut_buf_next;\n        size_t buf_len = *pOut_buf_size;\n        mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16;\n        size_t block_len = buf_len % 5552;\n        while (buf_len)\n        {\n            for (i = 0; i + 7 < block_len; i += 8, ptr += 8)\n            {\n                s1 += ptr[0], s2 += s1;\n                s1 += ptr[1], s2 += s1;\n                s1 += ptr[2], s2 += s1;\n                s1 += ptr[3], s2 += s1;\n                s1 += ptr[4], s2 += s1;\n                s1 += ptr[5], s2 += s1;\n                s1 += ptr[6], s2 += s1;\n                s1 += ptr[7], s2 += s1;\n            }\n            for (; i < block_len; ++i)\n                s1 += *ptr++, s2 += s1;\n            s1 %= 65521U, s2 %= 65521U;\n            buf_len -= block_len;\n            block_len = 5552;\n        }\n        r->m_check_adler32 = (s2 << 16) + s1;\n        if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32))\n            status = TINFL_STATUS_ADLER32_MISMATCH;\n    }\n    return status;\n}\n\n/* Higher level helper functions. */\nvoid *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)\n{\n    tinfl_decompressor decomp;\n    void *pBuf = NULL, *pNew_buf;\n    size_t src_buf_ofs = 0, out_buf_capacity = 0;\n    *pOut_len = 0;\n    tinfl_init(&decomp);\n    for (;;)\n    {\n        size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;\n        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size,\n                                               (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);\n        if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))\n        {\n            MZ_FREE(pBuf);\n            *pOut_len = 0;\n            return NULL;\n        }\n        src_buf_ofs += src_buf_size;\n        *pOut_len += dst_buf_size;\n        if (status == TINFL_STATUS_DONE)\n            break;\n        new_out_buf_capacity = out_buf_capacity * 2;\n        if (new_out_buf_capacity < 128)\n            new_out_buf_capacity = 128;\n        pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);\n        if (!pNew_buf)\n        {\n            MZ_FREE(pBuf);\n            *pOut_len = 0;\n            return NULL;\n        }\n        pBuf = pNew_buf;\n        out_buf_capacity = new_out_buf_capacity;\n    }\n    return pBuf;\n}\n\nsize_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)\n{\n    tinfl_decompressor decomp;\n    tinfl_status status;\n    tinfl_init(&decomp);\n    status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);\n    return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;\n}\n\nint tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)\n{\n    int result = 0;\n    tinfl_decompressor decomp;\n    mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE);\n    size_t in_buf_ofs = 0, dict_ofs = 0;\n    if (!pDict)\n        return TINFL_STATUS_FAILED;\n    memset(pDict,0,TINFL_LZ_DICT_SIZE);\n    tinfl_init(&decomp);\n    for (;;)\n    {\n        size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;\n        tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,\n                                               (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));\n        in_buf_ofs += in_buf_size;\n        if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))\n            break;\n        if (status != TINFL_STATUS_HAS_MORE_OUTPUT)\n        {\n            result = (status == TINFL_STATUS_DONE);\n            break;\n        }\n        dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);\n    }\n    MZ_FREE(pDict);\n    *pIn_buf_size = in_buf_ofs;\n    return result;\n}\n\n#ifndef MINIZ_NO_MALLOC\ntinfl_decompressor *tinfl_decompressor_alloc(void)\n{\n    tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor));\n    if (pDecomp)\n        tinfl_init(pDecomp);\n    return pDecomp;\n}\n\nvoid tinfl_decompressor_free(tinfl_decompressor *pDecomp)\n{\n    MZ_FREE(pDecomp);\n}\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/\n /**************************************************************************\n *\n * Copyright 2013-2014 RAD Game Tools and Valve Software\n * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC\n * Copyright 2016 Martin Raiber\n * All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n **************************************************************************/\n\n\n#ifndef MINIZ_NO_ARCHIVE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------- .ZIP archive reading */\n\n#ifdef MINIZ_NO_STDIO\n#define MZ_FILE void *\n#else\n#include <sys/stat.h>\n\n#if defined(_MSC_VER) || defined(__MINGW64__)\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\nstatic WCHAR* mz_utf8z_to_widechar(const char* str)\n{\n  int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);\n  WCHAR* wStr = (WCHAR*)malloc(reqChars * sizeof(WCHAR));\n  MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, reqChars);\n  return wStr;\n}\n\nstatic FILE *mz_fopen(const char *pFilename, const char *pMode)\n{\n  WCHAR* wFilename = mz_utf8z_to_widechar(pFilename);\n  WCHAR* wMode = mz_utf8z_to_widechar(pMode);\n  FILE* pFile = NULL;\n  errno_t err = _wfopen_s(&pFile, wFilename, wMode);\n  free(wFilename);\n  free(wMode);\n  return err ? NULL : pFile;\n}\n\nstatic FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)\n{\n  WCHAR* wPath = mz_utf8z_to_widechar(pPath);\n  WCHAR* wMode = mz_utf8z_to_widechar(pMode);\n  FILE* pFile = NULL;\n  errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream);\n  free(wPath);\n  free(wMode);\n  return err ? NULL : pFile;\n}\n\nstatic int mz_stat64(const char *path, struct __stat64 *buffer)\n{\n  WCHAR* wPath = mz_utf8z_to_widechar(path);\n  int res = _wstat64(wPath, buffer);\n  free(wPath);\n  return res;\n}\n\n#ifndef MINIZ_NO_TIME\n#include <sys/utime.h>\n#endif\n#define MZ_FOPEN mz_fopen\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#define MZ_FTELL64 _ftelli64\n#define MZ_FSEEK64 _fseeki64\n#define MZ_FILE_STAT_STRUCT _stat64\n#define MZ_FILE_STAT mz_stat64 \n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN mz_freopen\n#define MZ_DELETE_FILE remove\n\n#elif defined(__MINGW32__) || defined(__WATCOMC__)\n#ifndef MINIZ_NO_TIME\n#include <sys/utime.h>\n#endif\n#define MZ_FOPEN(f, m) fopen(f, m)\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#define MZ_FTELL64 _ftelli64\n#define MZ_FSEEK64 _fseeki64\n#define MZ_FILE_STAT_STRUCT stat\n#define MZ_FILE_STAT stat\n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN(f, m, s) freopen(f, m, s)\n#define MZ_DELETE_FILE remove\n\n#elif defined(__TINYC__)\n#ifndef MINIZ_NO_TIME\n#include <sys/utime.h>\n#endif\n#define MZ_FOPEN(f, m) fopen(f, m)\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#define MZ_FTELL64 ftell\n#define MZ_FSEEK64 fseek\n#define MZ_FILE_STAT_STRUCT stat\n#define MZ_FILE_STAT stat\n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN(f, m, s) freopen(f, m, s)\n#define MZ_DELETE_FILE remove\n\n#elif defined(__USE_LARGEFILE64) /* gcc, clang */\n#ifndef MINIZ_NO_TIME\n#include <utime.h>\n#endif\n#define MZ_FOPEN(f, m) fopen64(f, m)\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#define MZ_FTELL64 ftello64\n#define MZ_FSEEK64 fseeko64\n#define MZ_FILE_STAT_STRUCT stat64\n#define MZ_FILE_STAT stat64\n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)\n#define MZ_DELETE_FILE remove\n\n#elif defined(__APPLE__) || defined(__FreeBSD__)\n#ifndef MINIZ_NO_TIME\n#include <utime.h>\n#endif\n#define MZ_FOPEN(f, m) fopen(f, m)\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#define MZ_FTELL64 ftello\n#define MZ_FSEEK64 fseeko\n#define MZ_FILE_STAT_STRUCT stat\n#define MZ_FILE_STAT stat\n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN(p, m, s) freopen(p, m, s)\n#define MZ_DELETE_FILE remove\n\n#else\n#pragma message(\"Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.\")\n#ifndef MINIZ_NO_TIME\n#include <utime.h>\n#endif\n#define MZ_FOPEN(f, m) fopen(f, m)\n#define MZ_FCLOSE fclose\n#define MZ_FREAD fread\n#define MZ_FWRITE fwrite\n#ifdef __STRICT_ANSI__\n#define MZ_FTELL64 ftell\n#define MZ_FSEEK64 fseek\n#else\n#define MZ_FTELL64 ftello\n#define MZ_FSEEK64 fseeko\n#endif\n#define MZ_FILE_STAT_STRUCT stat\n#define MZ_FILE_STAT stat\n#define MZ_FFLUSH fflush\n#define MZ_FREOPEN(f, m, s) freopen(f, m, s)\n#define MZ_DELETE_FILE remove\n#endif /* #ifdef _MSC_VER */\n#endif /* #ifdef MINIZ_NO_STDIO */\n\n#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))\n\n/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */\nenum\n{\n    /* ZIP archive identifiers and record sizes */\n    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50,\n    MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50,\n    MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,\n    MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30,\n    MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46,\n    MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,\n\n    /* ZIP64 archive identifier and record sizes */\n    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50,\n    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50,\n    MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56,\n    MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20,\n    MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001,\n    MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50,\n    MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24,\n    MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16,\n\n    /* Central directory header record offsets */\n    MZ_ZIP_CDH_SIG_OFS = 0,\n    MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4,\n    MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6,\n    MZ_ZIP_CDH_BIT_FLAG_OFS = 8,\n    MZ_ZIP_CDH_METHOD_OFS = 10,\n    MZ_ZIP_CDH_FILE_TIME_OFS = 12,\n    MZ_ZIP_CDH_FILE_DATE_OFS = 14,\n    MZ_ZIP_CDH_CRC32_OFS = 16,\n    MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20,\n    MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24,\n    MZ_ZIP_CDH_FILENAME_LEN_OFS = 28,\n    MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,\n    MZ_ZIP_CDH_COMMENT_LEN_OFS = 32,\n    MZ_ZIP_CDH_DISK_START_OFS = 34,\n    MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36,\n    MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38,\n    MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,\n\n    /* Local directory header offsets */\n    MZ_ZIP_LDH_SIG_OFS = 0,\n    MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4,\n    MZ_ZIP_LDH_BIT_FLAG_OFS = 6,\n    MZ_ZIP_LDH_METHOD_OFS = 8,\n    MZ_ZIP_LDH_FILE_TIME_OFS = 10,\n    MZ_ZIP_LDH_FILE_DATE_OFS = 12,\n    MZ_ZIP_LDH_CRC32_OFS = 14,\n    MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18,\n    MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,\n    MZ_ZIP_LDH_FILENAME_LEN_OFS = 26,\n    MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,\n    MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3,\n\n    /* End of central directory offsets */\n    MZ_ZIP_ECDH_SIG_OFS = 0,\n    MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4,\n    MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6,\n    MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,\n    MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10,\n    MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12,\n    MZ_ZIP_ECDH_CDIR_OFS_OFS = 16,\n    MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,\n\n    /* ZIP64 End of central directory locator offsets */\n    MZ_ZIP64_ECDL_SIG_OFS = 0,                    /* 4 bytes */\n    MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4,          /* 4 bytes */\n    MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8,  /* 8 bytes */\n    MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */\n\n    /* ZIP64 End of central directory header offsets */\n    MZ_ZIP64_ECDH_SIG_OFS = 0,                       /* 4 bytes */\n    MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4,            /* 8 bytes */\n    MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12,          /* 2 bytes */\n    MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14,           /* 2 bytes */\n    MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16,            /* 4 bytes */\n    MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20,            /* 4 bytes */\n    MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */\n    MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32,       /* 8 bytes */\n    MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40,                /* 8 bytes */\n    MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48,                 /* 8 bytes */\n    MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0,\n    MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10,\n    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1,\n    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32,\n    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64,\n    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192,\n    MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11\n};\n\ntypedef struct\n{\n    void *m_p;\n    size_t m_size, m_capacity;\n    mz_uint m_element_size;\n} mz_zip_array;\n\nstruct mz_zip_internal_state_tag\n{\n    mz_zip_array m_central_dir;\n    mz_zip_array m_central_dir_offsets;\n    mz_zip_array m_sorted_central_dir_offsets;\n\n    /* The flags passed in when the archive is initially opened. */\n    mz_uint32 m_init_flags;\n\n    /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */\n    mz_bool m_zip64;\n\n    /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */\n    mz_bool m_zip64_has_extended_info_fields;\n\n    /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */\n    MZ_FILE *m_pFile;\n    mz_uint64 m_file_archive_start_ofs;\n\n    void *m_pMem;\n    size_t m_mem_size;\n    size_t m_mem_capacity;\n};\n\n#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size\n\n#if defined(DEBUG) || defined(_DEBUG)\nstatic MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index)\n{\n    MZ_ASSERT(index < pArray->m_size);\n    return index;\n}\n#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)]\n#else\n#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]\n#endif\n\nstatic MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size)\n{\n    memset(pArray, 0, sizeof(mz_zip_array));\n    pArray->m_element_size = element_size;\n}\n\nstatic MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)\n{\n    pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);\n    memset(pArray, 0, sizeof(mz_zip_array));\n}\n\nstatic mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)\n{\n    void *pNew_p;\n    size_t new_capacity = min_new_capacity;\n    MZ_ASSERT(pArray->m_element_size);\n    if (pArray->m_capacity >= min_new_capacity)\n        return MZ_TRUE;\n    if (growing)\n    {\n        new_capacity = MZ_MAX(1, pArray->m_capacity);\n        while (new_capacity < min_new_capacity)\n            new_capacity *= 2;\n    }\n    if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity)))\n        return MZ_FALSE;\n    pArray->m_p = pNew_p;\n    pArray->m_capacity = new_capacity;\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)\n{\n    if (new_capacity > pArray->m_capacity)\n    {\n        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing))\n            return MZ_FALSE;\n    }\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)\n{\n    if (new_size > pArray->m_capacity)\n    {\n        if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing))\n            return MZ_FALSE;\n    }\n    pArray->m_size = new_size;\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)\n{\n    return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)\n{\n    size_t orig_size = pArray->m_size;\n    if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE))\n        return MZ_FALSE;\n    if (n > 0)\n        memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);\n    return MZ_TRUE;\n}\n\n#ifndef MINIZ_NO_TIME\nstatic MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date)\n{\n    struct tm tm;\n    memset(&tm, 0, sizeof(tm));\n    tm.tm_isdst = -1;\n    tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900;\n    tm.tm_mon = ((dos_date >> 5) & 15) - 1;\n    tm.tm_mday = dos_date & 31;\n    tm.tm_hour = (dos_time >> 11) & 31;\n    tm.tm_min = (dos_time >> 5) & 63;\n    tm.tm_sec = (dos_time << 1) & 62;\n    return mktime(&tm);\n}\n\n#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS\nstatic void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)\n{\n#ifdef _MSC_VER\n    struct tm tm_struct;\n    struct tm *tm = &tm_struct;\n    errno_t err = localtime_s(tm, &time);\n    if (err)\n    {\n        *pDOS_date = 0;\n        *pDOS_time = 0;\n        return;\n    }\n#else\n    struct tm *tm = localtime(&time);\n#endif /* #ifdef _MSC_VER */\n\n    *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));\n    *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);\n}\n#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */\n\n#ifndef MINIZ_NO_STDIO\n#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS\nstatic mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime)\n{\n    struct MZ_FILE_STAT_STRUCT file_stat;\n\n    /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */\n    if (MZ_FILE_STAT(pFilename, &file_stat) != 0)\n        return MZ_FALSE;\n\n    *pTime = file_stat.st_mtime;\n\n    return MZ_TRUE;\n}\n#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/\n\nstatic mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time)\n{\n    struct utimbuf t;\n\n    memset(&t, 0, sizeof(t));\n    t.actime = access_time;\n    t.modtime = modified_time;\n\n    return !utime(pFilename, &t);\n}\n#endif /* #ifndef MINIZ_NO_STDIO */\n#endif /* #ifndef MINIZ_NO_TIME */\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num)\n{\n    if (pZip)\n        pZip->m_last_error = err_num;\n    return MZ_FALSE;\n}\n\nstatic mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags)\n{\n    (void)flags;\n    if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!pZip->m_pAlloc)\n        pZip->m_pAlloc = miniz_def_alloc_func;\n    if (!pZip->m_pFree)\n        pZip->m_pFree = miniz_def_free_func;\n    if (!pZip->m_pRealloc)\n        pZip->m_pRealloc = miniz_def_realloc_func;\n\n    pZip->m_archive_size = 0;\n    pZip->m_central_directory_file_ofs = 0;\n    pZip->m_total_files = 0;\n    pZip->m_last_error = MZ_ZIP_NO_ERROR;\n\n    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));\n    pZip->m_pState->m_init_flags = flags;\n    pZip->m_pState->m_zip64 = MZ_FALSE;\n    pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE;\n\n    pZip->m_zip_mode = MZ_ZIP_MODE_READING;\n\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)\n{\n    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;\n    const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));\n    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    mz_uint8 l = 0, r = 0;\n    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;\n    pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;\n    pE = pL + MZ_MIN(l_len, r_len);\n    while (pL < pE)\n    {\n        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))\n            break;\n        pL++;\n        pR++;\n    }\n    return (pL == pE) ? (l_len < r_len) : (l < r);\n}\n\n#define MZ_SWAP_UINT32(a, b) \\\n    do                       \\\n    {                        \\\n        mz_uint32 t = a;     \\\n        a = b;               \\\n        b = t;               \\\n    }                        \\\n    MZ_MACRO_END\n\n/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */\nstatic void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)\n{\n    mz_zip_internal_state *pState = pZip->m_pState;\n    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;\n    const mz_zip_array *pCentral_dir = &pState->m_central_dir;\n    mz_uint32 *pIndices;\n    mz_uint32 start, end;\n    const mz_uint32 size = pZip->m_total_files;\n\n    if (size <= 1U)\n        return;\n\n    pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);\n\n    start = (size - 2U) >> 1U;\n    for (;;)\n    {\n        mz_uint64 child, root = start;\n        for (;;)\n        {\n            if ((child = (root << 1U) + 1U) >= size)\n                break;\n            child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])));\n            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))\n                break;\n            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);\n            root = child;\n        }\n        if (!start)\n            break;\n        start--;\n    }\n\n    end = size - 1;\n    while (end > 0)\n    {\n        mz_uint64 child, root = 0;\n        MZ_SWAP_UINT32(pIndices[end], pIndices[0]);\n        for (;;)\n        {\n            if ((child = (root << 1U) + 1U) >= end)\n                break;\n            child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]));\n            if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))\n                break;\n            MZ_SWAP_UINT32(pIndices[root], pIndices[child]);\n            root = child;\n        }\n        end--;\n    }\n}\n\nstatic mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs)\n{\n    mz_int64 cur_file_ofs;\n    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];\n    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;\n\n    /* Basic sanity checks - reject files which are too small */\n    if (pZip->m_archive_size < record_size)\n        return MZ_FALSE;\n\n    /* Find the record by scanning the file from the end towards the beginning. */\n    cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);\n    for (;;)\n    {\n        int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);\n\n        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)\n            return MZ_FALSE;\n\n        for (i = n - 4; i >= 0; --i)\n        {\n            mz_uint s = MZ_READ_LE32(pBuf + i);\n            if (s == record_sig)\n            {\n                if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size)\n                    break;\n            }\n        }\n\n        if (i >= 0)\n        {\n            cur_file_ofs += i;\n            break;\n        }\n\n        /* Give up if we've searched the entire file, or we've gone back \"too far\" (~64kb) */\n        if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size)))\n            return MZ_FALSE;\n\n        cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);\n    }\n\n    *pOfs = cur_file_ofs;\n    return MZ_TRUE;\n}\n\nstatic mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags)\n{\n    mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0;\n    mz_uint64 cdir_ofs = 0;\n    mz_int64 cur_file_ofs = 0;\n    const mz_uint8 *p;\n\n    mz_uint32 buf_u32[4096 / sizeof(mz_uint32)];\n    mz_uint8 *pBuf = (mz_uint8 *)buf_u32;\n    mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);\n    mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32;\n\n    mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32;\n\n    mz_uint64 zip64_end_of_central_dir_ofs = 0;\n\n    /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */\n    if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n\n    if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))\n        return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);\n\n    /* Read and verify the end of central directory record. */\n    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n    if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n\n    if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))\n    {\n        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)\n        {\n            if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG)\n            {\n                zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);\n                if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))\n                    return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n\n                if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)\n                {\n                    if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)\n                    {\n                        pZip->m_pState->m_zip64 = MZ_TRUE;\n                    }\n                }\n            }\n        }\n    }\n\n    pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);\n    cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);\n    num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);\n    cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);\n    cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS);\n    cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);\n\n    if (pZip->m_pState->m_zip64)\n    {\n        mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS);\n        mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS);\n        mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);\n        mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS);\n        mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS);\n\n        if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12))\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n        if (zip64_total_num_of_disks != 1U)\n            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);\n\n        /* Check for miniz's practical limits */\n        if (zip64_cdir_total_entries > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n\n        pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries;\n\n        if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n\n        cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk;\n\n        /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */\n        if (zip64_size_of_central_directory > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n\n        cdir_size = (mz_uint32)zip64_size_of_central_directory;\n\n        num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS);\n\n        cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS);\n\n        cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS);\n    }\n\n    if (pZip->m_total_files != cdir_entries_on_this_disk)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);\n\n    if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);\n\n    if (cdir_size < (mz_uint64)pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    pZip->m_central_directory_file_ofs = cdir_ofs;\n\n    if (pZip->m_total_files)\n    {\n        mz_uint i, n;\n        /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */\n        if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||\n            (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n        if (sort_central_dir)\n        {\n            if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))\n                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n        /* Now create an index into the central directory file records, do some basic sanity checking on each record */\n        p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;\n        for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)\n        {\n            mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size;\n            mz_uint64 comp_size, decomp_size, local_header_ofs;\n\n            if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n            MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);\n\n            if (sort_central_dir)\n                MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;\n\n            comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);\n            decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);\n            local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);\n            filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n            ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);\n\n            if ((!pZip->m_pState->m_zip64_has_extended_info_fields) &&\n                (ext_data_size) &&\n                (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX))\n            {\n                /* Attempt to find zip64 extended information field in the entry's extra data */\n                mz_uint32 extra_size_remaining = ext_data_size;\n\n                if (extra_size_remaining)\n                {\n\t\t\t\t\tconst mz_uint8 *pExtra_data;\n\t\t\t\t\tvoid* buf = NULL;\n\n\t\t\t\t\tif (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n)\n\t\t\t\t\t{\n\t\t\t\t\t\tbuf = MZ_MALLOC(ext_data_size);\n\t\t\t\t\t\tif(buf==NULL)\n\t\t\t\t\t\t\treturn mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n\t\t\t\t\t\tif (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMZ_FREE(buf);\n\t\t\t\t\t\t\treturn mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpExtra_data = (mz_uint8*)buf;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tpExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size;\n\t\t\t\t\t}\n\n                    do\n                    {\n                        mz_uint32 field_id;\n                        mz_uint32 field_data_size;\n\n\t\t\t\t\t\tif (extra_size_remaining < (sizeof(mz_uint16) * 2))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMZ_FREE(buf);\n\t\t\t\t\t\t\treturn mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\t\t\t\t\t\t}\n\n                        field_id = MZ_READ_LE16(pExtra_data);\n                        field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));\n\n\t\t\t\t\t\tif ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMZ_FREE(buf);\n\t\t\t\t\t\t\treturn mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\t\t\t\t\t\t}\n\n                        if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)\n                        {\n                            /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */\n                            pZip->m_pState->m_zip64 = MZ_TRUE;\n                            pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE;\n                            break;\n                        }\n\n                        pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;\n                        extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;\n                    } while (extra_size_remaining);\n\n\t\t\t\t\tMZ_FREE(buf);\n                }\n            }\n\n            /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */\n            if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX))\n            {\n                if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size))\n                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n            }\n\n            disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);\n            if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1)))\n                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK);\n\n            if (comp_size != MZ_UINT32_MAX)\n            {\n                if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)\n                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n            }\n\n            bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);\n            if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED)\n                return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n\n            if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n            n -= total_header_size;\n            p += total_header_size;\n        }\n    }\n\n    if (sort_central_dir)\n        mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);\n\n    return MZ_TRUE;\n}\n\nvoid mz_zip_zero_struct(mz_zip_archive *pZip)\n{\n    if (pZip)\n        MZ_CLEAR_PTR(pZip);\n}\n\nstatic mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)\n{\n    mz_bool status = MZ_TRUE;\n\n    if (!pZip)\n        return MZ_FALSE;\n\n    if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))\n    {\n        if (set_last_error)\n            pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER;\n\n        return MZ_FALSE;\n    }\n\n    if (pZip->m_pState)\n    {\n        mz_zip_internal_state *pState = pZip->m_pState;\n        pZip->m_pState = NULL;\n\n        mz_zip_array_clear(pZip, &pState->m_central_dir);\n        mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);\n        mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);\n\n#ifndef MINIZ_NO_STDIO\n        if (pState->m_pFile)\n        {\n            if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)\n            {\n                if (MZ_FCLOSE(pState->m_pFile) == EOF)\n                {\n                    if (set_last_error)\n                        pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED;\n                    status = MZ_FALSE;\n                }\n            }\n            pState->m_pFile = NULL;\n        }\n#endif /* #ifndef MINIZ_NO_STDIO */\n\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n    }\n    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;\n\n    return status;\n}\n\nmz_bool mz_zip_reader_end(mz_zip_archive *pZip)\n{\n    return mz_zip_reader_end_internal(pZip, MZ_TRUE);\n}\nmz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags)\n{\n    if ((!pZip) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!mz_zip_reader_init_internal(pZip, flags))\n        return MZ_FALSE;\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_USER;\n    pZip->m_archive_size = size;\n\n    if (!mz_zip_reader_read_central_dir(pZip, flags))\n    {\n        mz_zip_reader_end_internal(pZip, MZ_FALSE);\n        return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\nstatic size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)\n{\n    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;\n    size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);\n    memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);\n    return s;\n}\n\nmz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags)\n{\n    if (!pMem)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n\n    if (!mz_zip_reader_init_internal(pZip, flags))\n        return MZ_FALSE;\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY;\n    pZip->m_archive_size = size;\n    pZip->m_pRead = mz_zip_mem_read_func;\n    pZip->m_pIO_opaque = pZip;\n    pZip->m_pNeeds_keepalive = NULL;\n\n#ifdef __cplusplus\n    pZip->m_pState->m_pMem = const_cast<void *>(pMem);\n#else\n    pZip->m_pState->m_pMem = (void *)pMem;\n#endif\n\n    pZip->m_pState->m_mem_size = size;\n\n    if (!mz_zip_reader_read_central_dir(pZip, flags))\n    {\n        mz_zip_reader_end_internal(pZip, MZ_FALSE);\n        return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\n#ifndef MINIZ_NO_STDIO\nstatic size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)\n{\n    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;\n    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);\n\n    file_ofs += pZip->m_pState->m_file_archive_start_ofs;\n\n    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))\n        return 0;\n\n    return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);\n}\n\nmz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)\n{\n    return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0);\n}\n\nmz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size)\n{\n    mz_uint64 file_size;\n    MZ_FILE *pFile;\n\n    if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pFile = MZ_FOPEN(pFilename, \"rb\");\n    if (!pFile)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n\n    file_size = archive_size;\n    if (!file_size)\n    {\n        if (MZ_FSEEK64(pFile, 0, SEEK_END))\n        {\n            MZ_FCLOSE(pFile);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);\n        }\n\n        file_size = MZ_FTELL64(pFile);\n    }\n\n    /* TODO: Better sanity check archive_size and the # of actual remaining bytes */\n\n    if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n    {\n\tMZ_FCLOSE(pFile);\n        return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n    }\n\n    if (!mz_zip_reader_init_internal(pZip, flags))\n    {\n        MZ_FCLOSE(pFile);\n        return MZ_FALSE;\n    }\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;\n    pZip->m_pRead = mz_zip_file_read_func;\n    pZip->m_pIO_opaque = pZip;\n    pZip->m_pState->m_pFile = pFile;\n    pZip->m_archive_size = file_size;\n    pZip->m_pState->m_file_archive_start_ofs = file_start_ofs;\n\n    if (!mz_zip_reader_read_central_dir(pZip, flags))\n    {\n        mz_zip_reader_end_internal(pZip, MZ_FALSE);\n        return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags)\n{\n    mz_uint64 cur_file_ofs;\n\n    if ((!pZip) || (!pFile))\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n\n    cur_file_ofs = MZ_FTELL64(pFile);\n\n    if (!archive_size)\n    {\n        if (MZ_FSEEK64(pFile, 0, SEEK_END))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);\n\n        archive_size = MZ_FTELL64(pFile) - cur_file_ofs;\n\n        if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n            return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);\n    }\n\n    if (!mz_zip_reader_init_internal(pZip, flags))\n        return MZ_FALSE;\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;\n    pZip->m_pRead = mz_zip_file_read_func;\n\n    pZip->m_pIO_opaque = pZip;\n    pZip->m_pState->m_pFile = pFile;\n    pZip->m_archive_size = archive_size;\n    pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs;\n\n    if (!mz_zip_reader_read_central_dir(pZip, flags))\n    {\n        mz_zip_reader_end_internal(pZip, MZ_FALSE);\n        return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\n#endif /* #ifndef MINIZ_NO_STDIO */\n\nstatic MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index)\n{\n    if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files))\n        return NULL;\n    return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));\n}\n\nmz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)\n{\n    mz_uint m_bit_flag;\n    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);\n    if (!p)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n        return MZ_FALSE;\n    }\n\n    m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);\n    return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0;\n}\n\nmz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index)\n{\n    mz_uint bit_flag;\n    mz_uint method;\n\n    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);\n    if (!p)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n        return MZ_FALSE;\n    }\n\n    method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);\n    bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);\n\n    if ((method != 0) && (method != MZ_DEFLATED))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);\n        return MZ_FALSE;\n    }\n\n    if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n        return MZ_FALSE;\n    }\n\n    if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);\n        return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)\n{\n    mz_uint filename_len, attribute_mapping_id, external_attr;\n    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);\n    if (!p)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n        return MZ_FALSE;\n    }\n\n    filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    if (filename_len)\n    {\n        if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')\n            return MZ_TRUE;\n    }\n\n    /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */\n    /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */\n    /* FIXME: Remove this check? Is it necessary - we already check the filename. */\n    attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8;\n    (void)attribute_mapping_id;\n\n    external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);\n    if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0)\n    {\n        return MZ_TRUE;\n    }\n\n    return MZ_FALSE;\n}\n\nstatic mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data)\n{\n    mz_uint n;\n    const mz_uint8 *p = pCentral_dir_header;\n\n    if (pFound_zip64_extra_data)\n        *pFound_zip64_extra_data = MZ_FALSE;\n\n    if ((!p) || (!pStat))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    /* Extract fields from the central directory record. */\n    pStat->m_file_index = file_index;\n    pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);\n    pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);\n    pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);\n    pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);\n    pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);\n#ifndef MINIZ_NO_TIME\n    pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));\n#endif\n    pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);\n    pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);\n    pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);\n    pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);\n    pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);\n    pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);\n\n    /* Copy as much of the filename and comment as possible. */\n    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);\n    memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);\n    pStat->m_filename[n] = '\\0';\n\n    n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS);\n    n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);\n    pStat->m_comment_size = n;\n    memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n);\n    pStat->m_comment[n] = '\\0';\n\n    /* Set some flags for convienance */\n    pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index);\n    pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index);\n    pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index);\n\n    /* See if we need to read any zip64 extended information fields. */\n    /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */\n    if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX)\n    {\n        /* Attempt to find zip64 extended information field in the entry's extra data */\n        mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);\n\n        if (extra_size_remaining)\n        {\n            const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n\n            do\n            {\n                mz_uint32 field_id;\n                mz_uint32 field_data_size;\n\n                if (extra_size_remaining < (sizeof(mz_uint16) * 2))\n                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n                field_id = MZ_READ_LE16(pExtra_data);\n                field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));\n\n                if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining)\n                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n                if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)\n                {\n                    const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2;\n                    mz_uint32 field_data_remaining = field_data_size;\n\n                    if (pFound_zip64_extra_data)\n                        *pFound_zip64_extra_data = MZ_TRUE;\n\n                    if (pStat->m_uncomp_size == MZ_UINT32_MAX)\n                    {\n                        if (field_data_remaining < sizeof(mz_uint64))\n                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n                        pStat->m_uncomp_size = MZ_READ_LE64(pField_data);\n                        pField_data += sizeof(mz_uint64);\n                        field_data_remaining -= sizeof(mz_uint64);\n                    }\n\n                    if (pStat->m_comp_size == MZ_UINT32_MAX)\n                    {\n                        if (field_data_remaining < sizeof(mz_uint64))\n                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n                        pStat->m_comp_size = MZ_READ_LE64(pField_data);\n                        pField_data += sizeof(mz_uint64);\n                        field_data_remaining -= sizeof(mz_uint64);\n                    }\n\n                    if (pStat->m_local_header_ofs == MZ_UINT32_MAX)\n                    {\n                        if (field_data_remaining < sizeof(mz_uint64))\n                            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n                        pStat->m_local_header_ofs = MZ_READ_LE64(pField_data);\n                        pField_data += sizeof(mz_uint64);\n                        field_data_remaining -= sizeof(mz_uint64);\n                    }\n\n                    break;\n                }\n\n                pExtra_data += sizeof(mz_uint16) * 2 + field_data_size;\n                extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size;\n            } while (extra_size_remaining);\n        }\n    }\n\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)\n{\n    mz_uint i;\n    if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)\n        return 0 == memcmp(pA, pB, len);\n    for (i = 0; i < len; ++i)\n        if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))\n            return MZ_FALSE;\n    return MZ_TRUE;\n}\n\nstatic MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)\n{\n    const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;\n    mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    mz_uint8 l = 0, r = 0;\n    pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;\n    pE = pL + MZ_MIN(l_len, r_len);\n    while (pL < pE)\n    {\n        if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))\n            break;\n        pL++;\n        pR++;\n    }\n    return (pL == pE) ? (int)(l_len - r_len) : (l - r);\n}\n\nstatic mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex)\n{\n    mz_zip_internal_state *pState = pZip->m_pState;\n    const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;\n    const mz_zip_array *pCentral_dir = &pState->m_central_dir;\n    mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);\n    const mz_uint32 size = pZip->m_total_files;\n    const mz_uint filename_len = (mz_uint)strlen(pFilename);\n\n    if (pIndex)\n        *pIndex = 0;\n\n    if (size)\n    {\n        /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */\n        /* honestly the major expense here on 32-bit CPU's will still be the filename compare */\n        mz_int64 l = 0, h = (mz_int64)size - 1;\n\n        while (l <= h)\n        {\n            mz_int64 m = l + ((h - l) >> 1);\n            mz_uint32 file_index = pIndices[(mz_uint32)m];\n\n            int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);\n            if (!comp)\n            {\n                if (pIndex)\n                    *pIndex = file_index;\n                return MZ_TRUE;\n            }\n            else if (comp < 0)\n                l = m + 1;\n            else\n                h = m - 1;\n        }\n    }\n\n    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);\n}\n\nint mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)\n{\n    mz_uint32 index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index))\n        return -1;\n    else\n        return (int)index;\n}\n\nmz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex)\n{\n    mz_uint file_index;\n    size_t name_len, comment_len;\n\n    if (pIndex)\n        *pIndex = 0;\n\n    if ((!pZip) || (!pZip->m_pState) || (!pName))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    /* See if we can use a binary search */\n    if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) &&\n        (pZip->m_zip_mode == MZ_ZIP_MODE_READING) &&\n        ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))\n    {\n        return mz_zip_locate_file_binary_search(pZip, pName, pIndex);\n    }\n\n    /* Locate the entry by scanning the entire central directory */\n    name_len = strlen(pName);\n    if (name_len > MZ_UINT16_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    comment_len = pComment ? strlen(pComment) : 0;\n    if (comment_len > MZ_UINT16_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    for (file_index = 0; file_index < pZip->m_total_files; file_index++)\n    {\n        const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));\n        mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n        const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;\n        if (filename_len < name_len)\n            continue;\n        if (comment_len)\n        {\n            mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);\n            const char *pFile_comment = pFilename + filename_len + file_extra_len;\n            if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags)))\n                continue;\n        }\n        if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))\n        {\n            int ofs = filename_len - 1;\n            do\n            {\n                if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\\\') || (pFilename[ofs] == ':'))\n                    break;\n            } while (--ofs >= 0);\n            ofs++;\n            pFilename += ofs;\n            filename_len -= ofs;\n        }\n        if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags)))\n        {\n            if (pIndex)\n                *pIndex = file_index;\n            return MZ_TRUE;\n        }\n    }\n\n    return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND);\n}\n\nstatic\nmz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st)\n{\n    int status = TINFL_STATUS_DONE;\n    mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;\n    mz_zip_archive_file_stat file_stat;\n    void *pRead_buf;\n    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;\n    tinfl_decompressor inflator;\n\n    if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (st) {\n        file_stat = *st;\n    } else\n    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))\n        return MZ_FALSE;\n\n    /* A directory or zero length file */\n    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))\n        return MZ_TRUE;\n\n    /* Encryption and patch files are not supported. */\n    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n\n    /* This function only supports decompressing stored and deflate. */\n    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);\n\n    /* Ensure supplied output buffer is large enough. */\n    needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;\n    if (buf_size < needed_size)\n        return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL);\n\n    /* Read and parse the local directory entry. */\n    cur_file_ofs = file_stat.m_local_header_ofs;\n    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);\n    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))\n    {\n        /* The file is stored or the caller has requested the compressed data. */\n        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n        if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0)\n        {\n            if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)\n                return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);\n        }\n#endif\n\n        return MZ_TRUE;\n    }\n\n    /* Decompress the file either directly from memory or from a file input buffer. */\n    tinfl_init(&inflator);\n\n    if (pZip->m_pState->m_pMem)\n    {\n        /* Read directly from the archive in memory. */\n        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;\n        read_buf_size = read_buf_avail = file_stat.m_comp_size;\n        comp_remaining = 0;\n    }\n    else if (pUser_read_buf)\n    {\n        /* Use a user provided read buffer. */\n        if (!user_read_buf_size)\n            return MZ_FALSE;\n        pRead_buf = (mz_uint8 *)pUser_read_buf;\n        read_buf_size = user_read_buf_size;\n        read_buf_avail = 0;\n        comp_remaining = file_stat.m_comp_size;\n    }\n    else\n    {\n        /* Temporarily allocate a read buffer. */\n        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);\n        if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n        read_buf_avail = 0;\n        comp_remaining = file_stat.m_comp_size;\n    }\n\n    do\n    {\n        /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */\n        size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);\n        if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))\n        {\n            read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);\n            if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)\n            {\n                status = TINFL_STATUS_FAILED;\n                mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);\n                break;\n            }\n            cur_file_ofs += read_buf_avail;\n            comp_remaining -= read_buf_avail;\n            read_buf_ofs = 0;\n        }\n        in_buf_size = (size_t)read_buf_avail;\n        status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));\n        read_buf_avail -= in_buf_size;\n        read_buf_ofs += in_buf_size;\n        out_buf_ofs += out_buf_size;\n    } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);\n\n    if (status == TINFL_STATUS_DONE)\n    {\n        /* Make sure the entire file was decompressed, and check its CRC. */\n        if (out_buf_ofs != file_stat.m_uncomp_size)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);\n            status = TINFL_STATUS_FAILED;\n        }\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n        else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED);\n            status = TINFL_STATUS_FAILED;\n        }\n#endif\n    }\n\n    if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n\n    return status == TINFL_STATUS_DONE;\n}\n\nmz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)\n{\n    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL);\n}\n\nmz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)\n{\n    mz_uint32 file_index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))\n        return MZ_FALSE;\n    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL);\n}\n\nmz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)\n{\n    return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL);\n}\n\nmz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)\n{\n    return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);\n}\n\nvoid *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)\n{\n    mz_zip_archive_file_stat file_stat;\n    mz_uint64 alloc_size;\n    void *pBuf;\n\n    if (pSize)\n        *pSize = 0;\n\n    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))\n        return NULL;\n\n    alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;\n    if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n        return NULL;\n    }\n\n    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        return NULL;\n    }\n\n    if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat))\n    {\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n        return NULL;\n    }\n\n    if (pSize)\n        *pSize = (size_t)alloc_size;\n    return pBuf;\n}\n\nvoid *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)\n{\n    mz_uint32 file_index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))\n    {\n        if (pSize)\n            *pSize = 0;\n        return MZ_FALSE;\n    }\n    return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);\n}\n\nmz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)\n{\n    int status = TINFL_STATUS_DONE;\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n    mz_uint file_crc32 = MZ_CRC32_INIT;\n#endif\n    mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;\n    mz_zip_archive_file_stat file_stat;\n    void *pRead_buf = NULL;\n    void *pWrite_buf = NULL;\n    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;\n\n    if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))\n        return MZ_FALSE;\n\n    /* A directory or zero length file */\n    if ((file_stat.m_is_directory) || (!file_stat.m_comp_size))\n        return MZ_TRUE;\n\n    /* Encryption and patch files are not supported. */\n    if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n\n    /* This function only supports decompressing stored and deflate. */\n    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);\n\n    /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */\n    cur_file_ofs = file_stat.m_local_header_ofs;\n    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);\n    if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    /* Decompress the file either directly from memory or from a file input buffer. */\n    if (pZip->m_pState->m_pMem)\n    {\n        pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;\n        read_buf_size = read_buf_avail = file_stat.m_comp_size;\n        comp_remaining = 0;\n    }\n    else\n    {\n        read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);\n        if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n        read_buf_avail = 0;\n        comp_remaining = file_stat.m_comp_size;\n    }\n\n    if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))\n    {\n        /* The file is stored or the caller has requested the compressed data. */\n        if (pZip->m_pState->m_pMem)\n        {\n            if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX))\n                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n            if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)\n            {\n                mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);\n                status = TINFL_STATUS_FAILED;\n            }\n            else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))\n            {\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n                file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);\n#endif\n            }\n\n            cur_file_ofs += file_stat.m_comp_size;\n            out_buf_ofs += file_stat.m_comp_size;\n            comp_remaining = 0;\n        }\n        else\n        {\n            while (comp_remaining)\n            {\n                read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);\n                if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)\n                {\n                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n                    status = TINFL_STATUS_FAILED;\n                    break;\n                }\n\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n                if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))\n                {\n                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);\n                }\n#endif\n\n                if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)\n                {\n                    mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);\n                    status = TINFL_STATUS_FAILED;\n                    break;\n                }\n\n                cur_file_ofs += read_buf_avail;\n                out_buf_ofs += read_buf_avail;\n                comp_remaining -= read_buf_avail;\n            }\n        }\n    }\n    else\n    {\n        tinfl_decompressor inflator;\n        tinfl_init(&inflator);\n\n        if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n            status = TINFL_STATUS_FAILED;\n        }\n        else\n        {\n            do\n            {\n                mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));\n                size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));\n                if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))\n                {\n                    read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);\n                    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)\n                    {\n                        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n                        status = TINFL_STATUS_FAILED;\n                        break;\n                    }\n                    cur_file_ofs += read_buf_avail;\n                    comp_remaining -= read_buf_avail;\n                    read_buf_ofs = 0;\n                }\n\n                in_buf_size = (size_t)read_buf_avail;\n                status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);\n                read_buf_avail -= in_buf_size;\n                read_buf_ofs += in_buf_size;\n\n                if (out_buf_size)\n                {\n                    if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)\n                    {\n                        mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED);\n                        status = TINFL_STATUS_FAILED;\n                        break;\n                    }\n\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n                    file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);\n#endif\n                    if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)\n                    {\n                        mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);\n                        status = TINFL_STATUS_FAILED;\n                        break;\n                    }\n                }\n            } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));\n        }\n    }\n\n    if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))\n    {\n        /* Make sure the entire file was decompressed, and check its CRC. */\n        if (out_buf_ofs != file_stat.m_uncomp_size)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);\n            status = TINFL_STATUS_FAILED;\n        }\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n        else if (file_crc32 != file_stat.m_crc32)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED);\n            status = TINFL_STATUS_FAILED;\n        }\n#endif\n    }\n\n    if (!pZip->m_pState->m_pMem)\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n\n    if (pWrite_buf)\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);\n\n    return status == TINFL_STATUS_DONE;\n}\n\nmz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)\n{\n    mz_uint32 file_index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))\n        return MZ_FALSE;\n\n    return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);\n}\n\nmz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)\n{\n    mz_zip_reader_extract_iter_state *pState;\n    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;\n\n    /* Argument sanity check */\n    if ((!pZip) || (!pZip->m_pState))\n        return NULL;\n\n    /* Allocate an iterator status structure */\n    pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state));\n    if (!pState)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        return NULL;\n    }\n\n    /* Fetch file details */\n    if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat))\n    {\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    /* Encryption and patch files are not supported. */\n    if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    /* This function only supports decompressing stored and deflate. */\n    if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    /* Init state - save args */\n    pState->pZip = pZip;\n    pState->flags = flags;\n\n    /* Init state - reset variables to defaults */\n    pState->status = TINFL_STATUS_DONE;\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n    pState->file_crc32 = MZ_CRC32_INIT;\n#endif\n    pState->read_buf_ofs = 0;\n    pState->out_buf_ofs = 0;\n    pState->pRead_buf = NULL;\n    pState->pWrite_buf = NULL;\n    pState->out_blk_remain = 0;\n\n    /* Read and parse the local directory entry. */\n    pState->cur_file_ofs = pState->file_stat.m_local_header_ofs;\n    if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);\n    if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size)\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n        return NULL;\n    }\n\n    /* Decompress the file either directly from memory or from a file input buffer. */\n    if (pZip->m_pState->m_pMem)\n    {\n        pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs;\n        pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size;\n        pState->comp_remaining = pState->file_stat.m_comp_size;\n    }\n    else\n    {\n        if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))\n        {\n            /* Decompression required, therefore intermediate read buffer required */\n            pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE);\n            if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size)))\n            {\n                mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n                return NULL;\n            }\n        }\n        else\n        {\n            /* Decompression not required - we will be reading directly into user buffer, no temp buf required */\n            pState->read_buf_size = 0;\n        }\n        pState->read_buf_avail = 0;\n        pState->comp_remaining = pState->file_stat.m_comp_size;\n    }\n\n    if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)))\n    {\n        /* Decompression required, init decompressor */\n        tinfl_init( &pState->inflator );\n\n        /* Allocate write buffer */\n        if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n            if (pState->pRead_buf)\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf);\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n            return NULL;\n        }\n    }\n\n    return pState;\n}\n\nmz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)\n{\n    mz_uint32 file_index;\n\n    /* Locate file index by name */\n    if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index))\n        return NULL;\n\n    /* Construct iterator */\n    return mz_zip_reader_extract_iter_new(pZip, file_index, flags);\n}\n\nsize_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size)\n{\n    size_t copied_to_caller = 0;\n\n    /* Argument sanity check */\n    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf))\n        return 0;\n\n    if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))\n    {\n        /* The file is stored or the caller has requested the compressed data, calc amount to return. */\n        copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining );\n\n        /* Zip is in memory....or requires reading from a file? */\n        if (pState->pZip->m_pState->m_pMem)\n        {\n            /* Copy data to caller's buffer */\n            memcpy( pvBuf, pState->pRead_buf, copied_to_caller );\n            pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller;\n        }\n        else\n        {\n            /* Read directly into caller's buffer */\n            if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller)\n            {\n                /* Failed to read all that was asked for, flag failure and alert user */\n                mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);\n                pState->status = TINFL_STATUS_FAILED;\n                copied_to_caller = 0;\n            }\n        }\n\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n        /* Compute CRC if not returning compressed data only */\n        if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))\n            pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller);\n#endif\n\n        /* Advance offsets, dec counters */\n        pState->cur_file_ofs += copied_to_caller;\n        pState->out_buf_ofs += copied_to_caller;\n        pState->comp_remaining -= copied_to_caller;\n    }\n    else\n    {\n        do\n        {\n            /* Calc ptr to write buffer - given current output pos and block size */\n            mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));\n\n            /* Calc max output size - given current output pos and block size */\n            size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));\n\n            if (!pState->out_blk_remain)\n            {\n                /* Read more data from file if none available (and reading from file) */\n                if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem))\n                {\n                    /* Calc read size */\n                    pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining);\n                    if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail)\n                    {\n                        mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED);\n                        pState->status = TINFL_STATUS_FAILED;\n                        break;\n                    }\n\n                    /* Advance offsets, dec counters */\n                    pState->cur_file_ofs += pState->read_buf_avail;\n                    pState->comp_remaining -= pState->read_buf_avail;\n                    pState->read_buf_ofs = 0;\n                }\n\n                /* Perform decompression */\n                in_buf_size = (size_t)pState->read_buf_avail;\n                pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);\n                pState->read_buf_avail -= in_buf_size;\n                pState->read_buf_ofs += in_buf_size;\n\n                /* Update current output block size remaining */\n                pState->out_blk_remain = out_buf_size;\n            }\n\n            if (pState->out_blk_remain)\n            {\n                /* Calc amount to return. */\n                size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain );\n\n                /* Copy data to caller's buffer */\n                memcpy( (mz_uint8*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy );\n\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n                /* Perform CRC */\n                pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy);\n#endif\n\n                /* Decrement data consumed from block */\n                pState->out_blk_remain -= to_copy;\n\n                /* Inc output offset, while performing sanity check */\n                if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size)\n                {\n                    mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);\n                    pState->status = TINFL_STATUS_FAILED;\n                    break;\n                }\n\n                /* Increment counter of data copied to caller */\n                copied_to_caller += to_copy;\n            }\n        } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) );\n    }\n\n    /* Return how many bytes were copied into user buffer */\n    return copied_to_caller;\n}\n\nmz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState)\n{\n    int status;\n\n    /* Argument sanity check */\n    if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState))\n        return MZ_FALSE;\n\n    /* Was decompression completed and requested? */\n    if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))\n    {\n        /* Make sure the entire file was decompressed, and check its CRC. */\n        if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size)\n        {\n            mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE);\n            pState->status = TINFL_STATUS_FAILED;\n        }\n#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n        else if (pState->file_crc32 != pState->file_stat.m_crc32)\n        {\n            mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED);\n            pState->status = TINFL_STATUS_FAILED;\n        }\n#endif\n    }\n\n    /* Free buffers */\n    if (!pState->pZip->m_pState->m_pMem)\n        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf);\n    if (pState->pWrite_buf)\n        pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf);\n\n    /* Save status */\n    status = pState->status;\n\n    /* Free context */\n    pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState);\n\n    return status == TINFL_STATUS_DONE;\n}\n\n#ifndef MINIZ_NO_STDIO\nstatic size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)\n{\n    (void)ofs;\n\n    return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque);\n}\n\nmz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)\n{\n    mz_bool status;\n    mz_zip_archive_file_stat file_stat;\n    MZ_FILE *pFile;\n\n    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))\n        return MZ_FALSE;\n\n    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);\n\n    pFile = MZ_FOPEN(pDst_filename, \"wb\");\n    if (!pFile)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n\n    status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);\n\n    if (MZ_FCLOSE(pFile) == EOF)\n    {\n        if (status)\n            mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);\n\n        status = MZ_FALSE;\n    }\n\n#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)\n    if (status)\n        mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);\n#endif\n\n    return status;\n}\n\nmz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)\n{\n    mz_uint32 file_index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))\n        return MZ_FALSE;\n\n    return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);\n}\n\nmz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags)\n{\n    mz_zip_archive_file_stat file_stat;\n\n    if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))\n        return MZ_FALSE;\n\n    if ((file_stat.m_is_directory) || (!file_stat.m_is_supported))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);\n\n    return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);\n}\n\nmz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags)\n{\n    mz_uint32 file_index;\n    if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index))\n        return MZ_FALSE;\n\n    return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags);\n}\n#endif /* #ifndef MINIZ_NO_STDIO */\n\nstatic size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)\n{\n    mz_uint32 *p = (mz_uint32 *)pOpaque;\n    (void)file_ofs;\n    *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n);\n    return n;\n}\n\nmz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags)\n{\n    mz_zip_archive_file_stat file_stat;\n    mz_zip_internal_state *pState;\n    const mz_uint8 *pCentral_dir_header;\n    mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE;\n    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;\n    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;\n    mz_uint64 local_header_ofs = 0;\n    mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32;\n    mz_uint64 local_header_comp_size, local_header_uncomp_size;\n    mz_uint32 uncomp_crc32 = MZ_CRC32_INIT;\n    mz_bool has_data_descriptor;\n    mz_uint32 local_header_bit_flags;\n\n    mz_zip_array file_data_array;\n    mz_zip_array_init(&file_data_array, 1);\n\n    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (file_index > pZip->m_total_files)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    pCentral_dir_header = mz_zip_get_cdh(pZip, file_index);\n\n    if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir))\n        return MZ_FALSE;\n\n    /* A directory or zero length file */\n    if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size))\n        return MZ_TRUE;\n\n    /* Encryption and patch files are not supported. */\n    if (file_stat.m_is_encrypted)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION);\n\n    /* This function only supports stored and deflate. */\n    if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD);\n\n    if (!file_stat.m_is_supported)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE);\n\n    /* Read and parse the local directory entry. */\n    local_header_ofs = file_stat.m_local_header_ofs;\n    if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);\n    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);\n    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);\n    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);\n    local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS);\n    local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);\n    has_data_descriptor = (local_header_bit_flags & 8) != 0;\n\n    if (local_header_filename_len != strlen(file_stat.m_filename))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        goto handle_failure;\n    }\n\n    if (local_header_filename_len)\n    {\n        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n            goto handle_failure;\n        }\n\n        /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */\n        if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);\n            goto handle_failure;\n        }\n    }\n\n    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))\n    {\n        mz_uint32 extra_size_remaining = local_header_extra_len;\n        const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p;\n\n        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n            goto handle_failure;\n        }\n\n        do\n        {\n            mz_uint32 field_id, field_data_size, field_total_size;\n\n            if (extra_size_remaining < (sizeof(mz_uint16) * 2))\n            {\n                mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n                goto handle_failure;\n            }\n\n            field_id = MZ_READ_LE16(pExtra_data);\n            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));\n            field_total_size = field_data_size + sizeof(mz_uint16) * 2;\n\n            if (field_total_size > extra_size_remaining)\n            {\n                mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n                goto handle_failure;\n            }\n\n            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)\n            {\n                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);\n\n                if (field_data_size < sizeof(mz_uint64) * 2)\n                {\n                    mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n                    goto handle_failure;\n                }\n\n                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);\n                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64));\n\n                found_zip64_ext_data_in_ldir = MZ_TRUE;\n                break;\n            }\n\n            pExtra_data += field_total_size;\n            extra_size_remaining -= field_total_size;\n        } while (extra_size_remaining);\n    }\n\n    /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */\n    /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */\n    if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32))\n    {\n        mz_uint8 descriptor_buf[32];\n        mz_bool has_id;\n        const mz_uint8 *pSrc;\n        mz_uint32 file_crc32;\n        mz_uint64 comp_size = 0, uncomp_size = 0;\n\n        mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4;\n\n        if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n            goto handle_failure;\n        }\n\n        has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID);\n        pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf;\n\n        file_crc32 = MZ_READ_LE32(pSrc);\n\n        if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir))\n        {\n            comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32));\n            uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64));\n        }\n        else\n        {\n            comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32));\n            uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32));\n        }\n\n        if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);\n            goto handle_failure;\n        }\n    }\n    else\n    {\n        if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);\n            goto handle_failure;\n        }\n    }\n\n    mz_zip_array_clear(pZip, &file_data_array);\n\n    if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0)\n    {\n        if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0))\n            return MZ_FALSE;\n\n        /* 1 more check to be sure, although the extract checks too. */\n        if (uncomp_crc32 != file_stat.m_crc32)\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);\n            return MZ_FALSE;\n        }\n    }\n\n    return MZ_TRUE;\n\nhandle_failure:\n    mz_zip_array_clear(pZip, &file_data_array);\n    return MZ_FALSE;\n}\n\nmz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags)\n{\n    mz_zip_internal_state *pState;\n    mz_uint32 i;\n\n    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    /* Basic sanity checks */\n    if (!pState->m_zip64)\n    {\n        if (pZip->m_total_files > MZ_UINT16_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n\n        if (pZip->m_archive_size > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n    }\n    else\n    {\n        if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n    }\n\n    for (i = 0; i < pZip->m_total_files; i++)\n    {\n        if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags)\n        {\n            mz_uint32 found_index;\n            mz_zip_archive_file_stat stat;\n\n            if (!mz_zip_reader_file_stat(pZip, i, &stat))\n                return MZ_FALSE;\n\n            if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index))\n                return MZ_FALSE;\n\n            /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */\n            if (found_index != i)\n                return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED);\n        }\n\n        if (!mz_zip_validate_file(pZip, i, flags))\n            return MZ_FALSE;\n    }\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr)\n{\n    mz_bool success = MZ_TRUE;\n    mz_zip_archive zip;\n    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;\n\n    if ((!pMem) || (!size))\n    {\n        if (pErr)\n            *pErr = MZ_ZIP_INVALID_PARAMETER;\n        return MZ_FALSE;\n    }\n\n    mz_zip_zero_struct(&zip);\n\n    if (!mz_zip_reader_init_mem(&zip, pMem, size, flags))\n    {\n        if (pErr)\n            *pErr = zip.m_last_error;\n        return MZ_FALSE;\n    }\n\n    if (!mz_zip_validate_archive(&zip, flags))\n    {\n        actual_err = zip.m_last_error;\n        success = MZ_FALSE;\n    }\n\n    if (!mz_zip_reader_end_internal(&zip, success))\n    {\n        if (!actual_err)\n            actual_err = zip.m_last_error;\n        success = MZ_FALSE;\n    }\n\n    if (pErr)\n        *pErr = actual_err;\n\n    return success;\n}\n\n#ifndef MINIZ_NO_STDIO\nmz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr)\n{\n    mz_bool success = MZ_TRUE;\n    mz_zip_archive zip;\n    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;\n\n    if (!pFilename)\n    {\n        if (pErr)\n            *pErr = MZ_ZIP_INVALID_PARAMETER;\n        return MZ_FALSE;\n    }\n\n    mz_zip_zero_struct(&zip);\n\n    if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0))\n    {\n        if (pErr)\n            *pErr = zip.m_last_error;\n        return MZ_FALSE;\n    }\n\n    if (!mz_zip_validate_archive(&zip, flags))\n    {\n        actual_err = zip.m_last_error;\n        success = MZ_FALSE;\n    }\n\n    if (!mz_zip_reader_end_internal(&zip, success))\n    {\n        if (!actual_err)\n            actual_err = zip.m_last_error;\n        success = MZ_FALSE;\n    }\n\n    if (pErr)\n        *pErr = actual_err;\n\n    return success;\n}\n#endif /* #ifndef MINIZ_NO_STDIO */\n\n/* ------------------- .ZIP archive writing */\n\n#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS\n\nstatic MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v)\n{\n    p[0] = (mz_uint8)v;\n    p[1] = (mz_uint8)(v >> 8);\n}\nstatic MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v)\n{\n    p[0] = (mz_uint8)v;\n    p[1] = (mz_uint8)(v >> 8);\n    p[2] = (mz_uint8)(v >> 16);\n    p[3] = (mz_uint8)(v >> 24);\n}\nstatic MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v)\n{\n    mz_write_le32(p, (mz_uint32)v);\n    mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32));\n}\n\n#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))\n#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))\n#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v))\n\nstatic size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)\n{\n    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;\n    mz_zip_internal_state *pState = pZip->m_pState;\n    mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);\n\n    if (!n)\n        return 0;\n\n    /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */\n    if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);\n        return 0;\n    }\n\n    if (new_size > pState->m_mem_capacity)\n    {\n        void *pNew_block;\n        size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity);\n\n        while (new_capacity < new_size)\n            new_capacity *= 2;\n\n        if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))\n        {\n            mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n            return 0;\n        }\n\n        pState->m_pMem = pNew_block;\n        pState->m_mem_capacity = new_capacity;\n    }\n    memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);\n    pState->m_mem_size = (size_t)new_size;\n    return n;\n}\n\nstatic mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error)\n{\n    mz_zip_internal_state *pState;\n    mz_bool status = MZ_TRUE;\n\n    if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))\n    {\n        if (set_last_error)\n            mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n        return MZ_FALSE;\n    }\n\n    pState = pZip->m_pState;\n    pZip->m_pState = NULL;\n    mz_zip_array_clear(pZip, &pState->m_central_dir);\n    mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);\n    mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);\n\n#ifndef MINIZ_NO_STDIO\n    if (pState->m_pFile)\n    {\n        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)\n        {\n            if (MZ_FCLOSE(pState->m_pFile) == EOF)\n            {\n                if (set_last_error)\n                    mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);\n                status = MZ_FALSE;\n            }\n        }\n\n        pState->m_pFile = NULL;\n    }\n#endif /* #ifndef MINIZ_NO_STDIO */\n\n    if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))\n    {\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);\n        pState->m_pMem = NULL;\n    }\n\n    pZip->m_pFree(pZip->m_pAlloc_opaque, pState);\n    pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;\n    return status;\n}\n\nmz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags)\n{\n    mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0;\n\n    if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)\n    {\n        if (!pZip->m_pRead)\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n    }\n\n    if (pZip->m_file_offset_alignment)\n    {\n        /* Ensure user specified file offset alignment is a power of 2. */\n        if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n    }\n\n    if (!pZip->m_pAlloc)\n        pZip->m_pAlloc = miniz_def_alloc_func;\n    if (!pZip->m_pFree)\n        pZip->m_pFree = miniz_def_free_func;\n    if (!pZip->m_pRealloc)\n        pZip->m_pRealloc = miniz_def_realloc_func;\n\n    pZip->m_archive_size = existing_size;\n    pZip->m_central_directory_file_ofs = 0;\n    pZip->m_total_files = 0;\n\n    if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n    memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));\n\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));\n    MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));\n\n    pZip->m_pState->m_zip64 = zip64;\n    pZip->m_pState->m_zip64_has_extended_info_fields = zip64;\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_USER;\n    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)\n{\n    return mz_zip_writer_init_v2(pZip, existing_size, 0);\n}\n\nmz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags)\n{\n    pZip->m_pWrite = mz_zip_heap_write_func;\n    pZip->m_pNeeds_keepalive = NULL;\n\n    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)\n        pZip->m_pRead = mz_zip_mem_read_func;\n\n    pZip->m_pIO_opaque = pZip;\n\n    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))\n        return MZ_FALSE;\n\n    pZip->m_zip_type = MZ_ZIP_TYPE_HEAP;\n\n    if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))\n    {\n        if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))\n        {\n            mz_zip_writer_end_internal(pZip, MZ_FALSE);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n        pZip->m_pState->m_mem_capacity = initial_allocation_size;\n    }\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)\n{\n    return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0);\n}\n\n#ifndef MINIZ_NO_STDIO\nstatic size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)\n{\n    mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;\n    mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);\n\n    file_ofs += pZip->m_pState->m_file_archive_start_ofs;\n\n    if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))\n    {\n        mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED);\n        return 0;\n    }\n\n    return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);\n}\n\nmz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)\n{\n    return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0);\n}\n\nmz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags)\n{\n    MZ_FILE *pFile;\n\n    pZip->m_pWrite = mz_zip_file_write_func;\n    pZip->m_pNeeds_keepalive = NULL;\n\n    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)\n        pZip->m_pRead = mz_zip_file_read_func;\n\n    pZip->m_pIO_opaque = pZip;\n\n    if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags))\n        return MZ_FALSE;\n\n    if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? \"w+b\" : \"wb\")))\n    {\n        mz_zip_writer_end(pZip);\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n    }\n\n    pZip->m_pState->m_pFile = pFile;\n    pZip->m_zip_type = MZ_ZIP_TYPE_FILE;\n\n    if (size_to_reserve_at_beginning)\n    {\n        mz_uint64 cur_ofs = 0;\n        char buf[4096];\n\n        MZ_CLEAR_ARR(buf);\n\n        do\n        {\n            size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);\n            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)\n            {\n                mz_zip_writer_end(pZip);\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n            }\n            cur_ofs += n;\n            size_to_reserve_at_beginning -= n;\n        } while (size_to_reserve_at_beginning);\n    }\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags)\n{\n    pZip->m_pWrite = mz_zip_file_write_func;\n    pZip->m_pNeeds_keepalive = NULL;\n\n    if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING)\n        pZip->m_pRead = mz_zip_file_read_func;\n\n    pZip->m_pIO_opaque = pZip;\n\n    if (!mz_zip_writer_init_v2(pZip, 0, flags))\n        return MZ_FALSE;\n\n    pZip->m_pState->m_pFile = pFile;\n    pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);\n    pZip->m_zip_type = MZ_ZIP_TYPE_CFILE;\n\n    return MZ_TRUE;\n}\n#endif /* #ifndef MINIZ_NO_STDIO */\n\nmz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags)\n{\n    mz_zip_internal_state *pState;\n\n    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (flags & MZ_ZIP_FLAG_WRITE_ZIP64)\n    {\n        /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */\n        if (!pZip->m_pState->m_zip64)\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n    }\n\n    /* No sense in trying to write to an archive that's already at the support max size */\n    if (pZip->m_pState->m_zip64)\n    {\n        if (pZip->m_total_files == MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n    else\n    {\n        if (pZip->m_total_files == MZ_UINT16_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n\n        if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);\n    }\n\n    pState = pZip->m_pState;\n\n    if (pState->m_pFile)\n    {\n#ifdef MINIZ_NO_STDIO\n        (void)pFilename;\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n#else\n        if (pZip->m_pIO_opaque != pZip)\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n        if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE)\n        {\n            if (!pFilename)\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n            /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */\n            if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, \"r+b\", pState->m_pFile)))\n            {\n                /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */\n                mz_zip_reader_end_internal(pZip, MZ_FALSE);\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n            }\n        }\n\n        pZip->m_pWrite = mz_zip_file_write_func;\n        pZip->m_pNeeds_keepalive = NULL;\n#endif /* #ifdef MINIZ_NO_STDIO */\n    }\n    else if (pState->m_pMem)\n    {\n        /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */\n        if (pZip->m_pIO_opaque != pZip)\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n        pState->m_mem_capacity = pState->m_mem_size;\n        pZip->m_pWrite = mz_zip_heap_write_func;\n        pZip->m_pNeeds_keepalive = NULL;\n    }\n    /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */\n    else if (!pZip->m_pWrite)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    /* Start writing new files at the archive's current central directory location. */\n    /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */\n    pZip->m_archive_size = pZip->m_central_directory_file_ofs;\n    pZip->m_central_directory_file_ofs = 0;\n\n    /* Clear the sorted central dir offsets, they aren't useful or maintained now. */\n    /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */\n    /* TODO: We could easily maintain the sorted central directory offsets. */\n    mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets);\n\n    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)\n{\n    return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0);\n}\n\n/* TODO: pArchive_name is a terrible name here! */\nmz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)\n{\n    return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);\n}\n\ntypedef struct\n{\n    mz_zip_archive *m_pZip;\n    mz_uint64 m_cur_archive_file_ofs;\n    mz_uint64 m_comp_size;\n} mz_zip_writer_add_state;\n\nstatic mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser)\n{\n    mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;\n    if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)\n        return MZ_FALSE;\n\n    pState->m_cur_archive_file_ofs += len;\n    pState->m_comp_size += len;\n    return MZ_TRUE;\n}\n\n#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2)\n#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3)\nstatic mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs)\n{\n    mz_uint8 *pDst = pBuf;\n    mz_uint32 field_size = 0;\n\n    MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);\n    MZ_WRITE_LE16(pDst + 2, 0);\n    pDst += sizeof(mz_uint16) * 2;\n\n    if (pUncomp_size)\n    {\n        MZ_WRITE_LE64(pDst, *pUncomp_size);\n        pDst += sizeof(mz_uint64);\n        field_size += sizeof(mz_uint64);\n    }\n\n    if (pComp_size)\n    {\n        MZ_WRITE_LE64(pDst, *pComp_size);\n        pDst += sizeof(mz_uint64);\n        field_size += sizeof(mz_uint64);\n    }\n\n    if (pLocal_header_ofs)\n    {\n        MZ_WRITE_LE64(pDst, *pLocal_header_ofs);\n        pDst += sizeof(mz_uint64);\n        field_size += sizeof(mz_uint64);\n    }\n\n    MZ_WRITE_LE16(pBuf + 2, field_size);\n\n    return (mz_uint32)(pDst - pBuf);\n}\n\nstatic mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)\n{\n    (void)pZip;\n    memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));\n    MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);\n    return MZ_TRUE;\n}\n\nstatic mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst,\n                                                       mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size,\n                                                       mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,\n                                                       mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,\n                                                       mz_uint64 local_header_ofs, mz_uint32 ext_attributes)\n{\n    (void)pZip;\n    memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX));\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX));\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);\n    MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);\n    MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX));\n    return MZ_TRUE;\n}\n\nstatic mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size,\n                                                const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size,\n                                                mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32,\n                                                mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date,\n                                                mz_uint64 local_header_ofs, mz_uint32 ext_attributes,\n                                                const char *user_extra_data, mz_uint user_extra_data_len)\n{\n    mz_zip_internal_state *pState = pZip->m_pState;\n    mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;\n    size_t orig_central_dir_size = pState->m_central_dir.m_size;\n    mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];\n\n    if (!pZip->m_pState->m_zip64)\n    {\n        if (local_header_ofs > 0xFFFFFFFF)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE);\n    }\n\n    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */\n    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n\n    if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))\n        return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n    if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||\n        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||\n        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||\n        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) ||\n        (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||\n        (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &central_dir_ofs, 1)))\n    {\n        /* Try to resize the central directory array back into its original state. */\n        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n    }\n\n    return MZ_TRUE;\n}\n\nstatic mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)\n{\n    /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */\n    if (*pArchive_name == '/')\n        return MZ_FALSE;\n\n    /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/\n\n    return MZ_TRUE;\n}\n\nstatic mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)\n{\n    mz_uint32 n;\n    if (!pZip->m_file_offset_alignment)\n        return 0;\n    n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));\n    return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1));\n}\n\nstatic mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)\n{\n    char buf[4096];\n    memset(buf, 0, MZ_MIN(sizeof(buf), n));\n    while (n)\n    {\n        mz_uint32 s = MZ_MIN(sizeof(buf), n);\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_file_ofs += s;\n        n -= s;\n    }\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,\n                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)\n{\n    return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0);\n}\n\nmz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size,\n                                    mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified,\n                                    const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)\n{\n    mz_uint16 method = 0, dos_time = 0, dos_date = 0;\n    mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;\n    mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;\n    size_t archive_name_size;\n    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];\n    tdefl_compressor *pComp = NULL;\n    mz_bool store_data_uncompressed;\n    mz_zip_internal_state *pState;\n    mz_uint8 *pExtra_data = NULL;\n    mz_uint32 extra_size = 0;\n    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];\n    mz_uint16 bit_flags = 0;\n\n    if ((int)level_and_flags < 0)\n        level_and_flags = MZ_DEFAULT_LEVEL;\n\n    if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))\n        bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;\n\n    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))\n        bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;\n\n    level = level_and_flags & 0xF;\n    store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));\n\n    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    if (pState->m_zip64)\n    {\n        if (pZip->m_total_files == MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n    else\n    {\n        if (pZip->m_total_files == MZ_UINT16_MAX)\n        {\n            pState->m_zip64 = MZ_TRUE;\n            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */\n        }\n        if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))\n        {\n            pState->m_zip64 = MZ_TRUE;\n            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */\n        }\n    }\n\n    if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!mz_zip_writer_validate_archive_name(pArchive_name))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);\n\n#ifndef MINIZ_NO_TIME\n    if (last_modified != NULL)\n    {\n        mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date);\n    }\n    else\n    {\n        MZ_TIME_T cur_time;\n        time(&cur_time);\n        mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date);\n    }\n#endif /* #ifndef MINIZ_NO_TIME */\n\n\tif (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))\n\t{\n\t\tuncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size);\n\t\tuncomp_size = buf_size;\n\t\tif (uncomp_size <= 3)\n\t\t{\n\t\t\tlevel = 0;\n\t\t\tstore_data_uncompressed = MZ_TRUE;\n\t\t}\n\t}\n\n    archive_name_size = strlen(pArchive_name);\n    if (archive_name_size > MZ_UINT16_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);\n\n    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);\n\n    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */\n    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n\n    if (!pState->m_zip64)\n    {\n        /* Bail early if the archive would obviously become too large */\n        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size\n\t\t\t+ MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len +\n\t\t\tpState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len\n\t\t\t+ MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF)\n        {\n            pState->m_zip64 = MZ_TRUE;\n            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */\n        }\n    }\n\n    if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))\n    {\n        /* Set DOS Subdirectory attribute bit. */\n        ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG;\n\n        /* Subdirectories cannot contain data. */\n        if ((buf_size) || (uncomp_size))\n            return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n    }\n\n    /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */\n    if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n    if ((!store_data_uncompressed) && (buf_size))\n    {\n        if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n    }\n\n    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))\n    {\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n        return MZ_FALSE;\n    }\n\n    local_dir_header_ofs += num_alignment_padding_bytes;\n    if (pZip->m_file_offset_alignment)\n    {\n        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);\n    }\n    cur_archive_file_ofs += num_alignment_padding_bytes;\n\n    MZ_CLEAR_ARR(local_dir_header);\n\n    if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))\n    {\n        method = MZ_DEFLATED;\n    }\n\n    if (pState->m_zip64)\n    {\n        if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)\n        {\n            pExtra_data = extra_data;\n            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,\n                                                               (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n        }\n\n        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += sizeof(local_dir_header);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n        cur_archive_file_ofs += archive_name_size;\n\n        if (pExtra_data != NULL)\n        {\n            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n            cur_archive_file_ofs += extra_size;\n        }\n    }\n    else\n    {\n        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += sizeof(local_dir_header);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n        cur_archive_file_ofs += archive_name_size;\n    }\n\n\tif (user_extra_data_len > 0)\n\t{\n\t\tif (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)\n\t\t\treturn mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n\t\tcur_archive_file_ofs += user_extra_data_len;\n\t}\n\n    if (store_data_uncompressed)\n    {\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n\n        cur_archive_file_ofs += buf_size;\n        comp_size = buf_size;\n    }\n    else if (buf_size)\n    {\n        mz_zip_writer_add_state state;\n\n        state.m_pZip = pZip;\n        state.m_cur_archive_file_ofs = cur_archive_file_ofs;\n        state.m_comp_size = 0;\n\n        if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||\n            (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n            return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);\n        }\n\n        comp_size = state.m_comp_size;\n        cur_archive_file_ofs = state.m_cur_archive_file_ofs;\n    }\n\n    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n    pComp = NULL;\n\n    if (uncomp_size)\n    {\n        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];\n        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;\n\n        MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR);\n\n        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);\n        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);\n        if (pExtra_data == NULL)\n        {\n            if (comp_size > MZ_UINT32_MAX)\n                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n\n            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);\n            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);\n        }\n        else\n        {\n            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);\n            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);\n            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;\n        }\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)\n            return MZ_FALSE;\n\n        cur_archive_file_ofs += local_dir_footer_size;\n    }\n\n    if (pExtra_data != NULL)\n    {\n        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,\n                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n    }\n\n    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment,\n                                          comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,\n                                          user_extra_data_central, user_extra_data_central_len))\n        return MZ_FALSE;\n\n    pZip->m_total_files++;\n    pZip->m_archive_size = cur_archive_file_ofs;\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,\n                                const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)\n{\n    mz_uint16 gen_flags;\n    mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;\n    mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;\n    mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0;\n    size_t archive_name_size;\n    mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];\n    mz_uint8 *pExtra_data = NULL;\n    mz_uint32 extra_size = 0;\n    mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];\n    mz_zip_internal_state *pState;\n    mz_uint64 file_ofs = 0, cur_archive_header_file_ofs;\n\n    if ((int)level_and_flags < 0)\n        level_and_flags = MZ_DEFAULT_LEVEL;\n    level = level_and_flags & 0xF;\n\n    gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;\n\n    if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))\n        gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;\n\n    /* Sanity checks */\n    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX))\n    {\n        /* Source file is too large for non-zip64 */\n        /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */\n        pState->m_zip64 = MZ_TRUE;\n    }\n\n    /* We could support this, but why? */\n    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!mz_zip_writer_validate_archive_name(pArchive_name))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);\n\n    if (pState->m_zip64)\n    {\n        if (pZip->m_total_files == MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n    else\n    {\n        if (pZip->m_total_files == MZ_UINT16_MAX)\n        {\n            pState->m_zip64 = MZ_TRUE;\n            /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */\n        }\n    }\n\n    archive_name_size = strlen(pArchive_name);\n    if (archive_name_size > MZ_UINT16_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME);\n\n    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);\n\n    /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */\n    if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n\n    if (!pState->m_zip64)\n    {\n        /* Bail early if the archive would obviously become too large */\n        if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE\n\t\t\t+ archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024\n\t\t\t+ MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF)\n        {\n            pState->m_zip64 = MZ_TRUE;\n            /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */\n        }\n    }\n\n#ifndef MINIZ_NO_TIME\n    if (pFile_time)\n    {\n        mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date);\n    }\n#endif\n\n    if (max_size <= 3)\n        level = 0;\n\n    if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes))\n    {\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n    }\n\n    cur_archive_file_ofs += num_alignment_padding_bytes;\n    local_dir_header_ofs = cur_archive_file_ofs;\n\n    if (pZip->m_file_offset_alignment)\n    {\n        MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0);\n    }\n\n    if (max_size && level)\n    {\n        method = MZ_DEFLATED;\n    }\n\n    MZ_CLEAR_ARR(local_dir_header);\n    if (pState->m_zip64)\n    {\n        if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX)\n        {\n            pExtra_data = extra_data;\n            if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)\n                extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,\n                                                               (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL,\n                                                                (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n            else\n                extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL,\n                                                                   NULL,\n                                                                   (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n        }\n\n        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += sizeof(local_dir_header);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)\n        {\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n\n        cur_archive_file_ofs += archive_name_size;\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += extra_size;\n    }\n    else\n    {\n        if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX))\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += sizeof(local_dir_header);\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)\n        {\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n\n        cur_archive_file_ofs += archive_name_size;\n    }\n\n    if (user_extra_data_len > 0)\n    {\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        cur_archive_file_ofs += user_extra_data_len;\n    }\n\n    if (max_size)\n    {\n        void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);\n        if (!pRead_buf)\n        {\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (!level)\n        {\n            while (1)\n            {\n                size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);\n                if (n == 0)\n                    break;\n\n                if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))\n                {\n                    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n                    return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n                }\n                if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)\n                {\n                    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n                    return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n                }\n                file_ofs += n;\n                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);\n                cur_archive_file_ofs += n;\n            }\n            uncomp_size = file_ofs;\n            comp_size = uncomp_size;\n        }\n        else\n        {\n            mz_bool result = MZ_FALSE;\n            mz_zip_writer_add_state state;\n            tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));\n            if (!pComp)\n            {\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n                return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n            }\n\n            state.m_pZip = pZip;\n            state.m_cur_archive_file_ofs = cur_archive_file_ofs;\n            state.m_comp_size = 0;\n\n            if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)\n            {\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n                return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n            }\n\n            for (;;)\n            {\n                tdefl_status status;\n                tdefl_flush flush = TDEFL_NO_FLUSH;\n\n                size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE);\n                if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size))\n                {\n                    mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n                    break;\n                }\n\n                file_ofs += n;\n                uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);\n\n                if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque))\n                    flush = TDEFL_FULL_FLUSH;\n\n                if (n == 0)\n                    flush = TDEFL_FINISH;\n\n                status = tdefl_compress_buffer(pComp, pRead_buf, n, flush);\n                if (status == TDEFL_STATUS_DONE)\n                {\n                    result = MZ_TRUE;\n                    break;\n                }\n                else if (status != TDEFL_STATUS_OKAY)\n                {\n                    mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED);\n                    break;\n                }\n            }\n\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);\n\n            if (!result)\n            {\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n                return MZ_FALSE;\n            }\n\n            uncomp_size = file_ofs;\n            comp_size = state.m_comp_size;\n            cur_archive_file_ofs = state.m_cur_archive_file_ofs;\n        }\n\n        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);\n    }\n\n    if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE))\n    {\n        mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64];\n        mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32;\n\n        MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID);\n        MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32);\n        if (pExtra_data == NULL)\n        {\n            if (comp_size > MZ_UINT32_MAX)\n                return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n\n            MZ_WRITE_LE32(local_dir_footer + 8, comp_size);\n            MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size);\n        }\n        else\n        {\n            MZ_WRITE_LE64(local_dir_footer + 8, comp_size);\n            MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size);\n            local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64;\n        }\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size)\n            return MZ_FALSE;\n\n        cur_archive_file_ofs += local_dir_footer_size;\n    }\n\n    if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)\n    {\n        if (pExtra_data != NULL)\n        {\n            extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,\n                                                               (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n        }\n\n        if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header,\n                                                   (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len),\n                                                   (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, \n                                                    (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size,\n                                                   uncomp_crc32, method, gen_flags, dos_time, dos_date))\n            return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR);\n\n        cur_archive_header_file_ofs = local_dir_header_ofs;\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        if (pExtra_data != NULL)\n        {\n            cur_archive_header_file_ofs += sizeof(local_dir_header);\n\n            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size)\n            {\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n            }\n\n            cur_archive_header_file_ofs += archive_name_size;\n\n            if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size)\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n            cur_archive_header_file_ofs += extra_size;\n        }\n    }\n\n    if (pExtra_data != NULL)\n    {\n        extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL,\n                                                           (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL);\n    }\n\n    if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size,\n                                          uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes,\n                                          user_extra_data_central, user_extra_data_central_len))\n        return MZ_FALSE;\n\n    pZip->m_total_files++;\n    pZip->m_archive_size = cur_archive_file_ofs;\n\n    return MZ_TRUE;\n}\n\n#ifndef MINIZ_NO_STDIO\n\nstatic size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)\n{\n\tMZ_FILE *pSrc_file = (MZ_FILE *)pOpaque;\n\tmz_int64 cur_ofs = MZ_FTELL64(pSrc_file);\n\n\tif (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET))))\n\t\treturn 0;\n\n\treturn MZ_FREAD(pBuf, 1, n, pSrc_file);\n}\n\nmz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,\n\tconst char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len)\n{\n\treturn mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags,\n\t\tuser_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len);\n}\n\nmz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)\n{\n    MZ_FILE *pSrc_file = NULL;\n    mz_uint64 uncomp_size = 0;\n    MZ_TIME_T file_modified_time;\n    MZ_TIME_T *pFile_time = NULL;\n    mz_bool status;\n\n    memset(&file_modified_time, 0, sizeof(file_modified_time));\n\n#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO)\n    pFile_time = &file_modified_time;\n    if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time))\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED);\n#endif\n\n    pSrc_file = MZ_FOPEN(pSrc_filename, \"rb\");\n    if (!pSrc_file)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED);\n\n    MZ_FSEEK64(pSrc_file, 0, SEEK_END);\n    uncomp_size = MZ_FTELL64(pSrc_file);\n    MZ_FSEEK64(pSrc_file, 0, SEEK_SET);\n\n    status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0);\n\n    MZ_FCLOSE(pSrc_file);\n\n    return status;\n}\n#endif /* #ifndef MINIZ_NO_STDIO */\n\nstatic mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, mz_uint32 ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start)\n{\n    /* + 64 should be enough for any new zip64 data */\n    if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE))\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n    mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE);\n\n    if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start))\n    {\n        mz_uint8 new_ext_block[64];\n        mz_uint8 *pDst = new_ext_block;\n        mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID);\n        mz_write_le16(pDst + sizeof(mz_uint16), 0);\n        pDst += sizeof(mz_uint16) * 2;\n\n        if (pUncomp_size)\n        {\n            mz_write_le64(pDst, *pUncomp_size);\n            pDst += sizeof(mz_uint64);\n        }\n\n        if (pComp_size)\n        {\n            mz_write_le64(pDst, *pComp_size);\n            pDst += sizeof(mz_uint64);\n        }\n\n        if (pLocal_header_ofs)\n        {\n            mz_write_le64(pDst, *pLocal_header_ofs);\n            pDst += sizeof(mz_uint64);\n        }\n\n        if (pDisk_start)\n        {\n            mz_write_le32(pDst, *pDisk_start);\n            pDst += sizeof(mz_uint32);\n        }\n\n        mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2));\n\n        if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n    }\n\n    if ((pExt) && (ext_len))\n    {\n        mz_uint32 extra_size_remaining = ext_len;\n        const mz_uint8 *pExtra_data = pExt;\n\n        do\n        {\n            mz_uint32 field_id, field_data_size, field_total_size;\n\n            if (extra_size_remaining < (sizeof(mz_uint16) * 2))\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n            field_id = MZ_READ_LE16(pExtra_data);\n            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));\n            field_total_size = field_data_size + sizeof(mz_uint16) * 2;\n\n            if (field_total_size > extra_size_remaining)\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n            if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)\n            {\n                if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size))\n                    return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n            }\n\n            pExtra_data += field_total_size;\n            extra_size_remaining -= field_total_size;\n        } while (extra_size_remaining);\n    }\n\n    return MZ_TRUE;\n}\n\n/* TODO: This func is now pretty freakin complex due to zip64, split it up? */\nmz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index)\n{\n    mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size;\n    mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs;\n    mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;\n    mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)];\n    mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;\n    mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];\n    size_t orig_central_dir_size;\n    mz_zip_internal_state *pState;\n    void *pBuf;\n    const mz_uint8 *pSrc_central_header;\n    mz_zip_archive_file_stat src_file_stat;\n    mz_uint32 src_filename_len, src_comment_len, src_ext_len;\n    mz_uint32 local_header_filename_size, local_header_extra_len;\n    mz_uint64 local_header_comp_size, local_header_uncomp_size;\n    mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE;\n\n    /* Sanity checks */\n    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */\n    if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    /* Get pointer to the source central dir header and crack it */\n    if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index)))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);\n    src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS);\n    src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len;\n\n    /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */\n    if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX)\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n\n    num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);\n\n    if (!pState->m_zip64)\n    {\n        if (pZip->m_total_files == MZ_UINT16_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n    else\n    {\n        /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */\n        if (pZip->m_total_files == MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n\n    if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL))\n        return MZ_FALSE;\n\n    cur_src_file_ofs = src_file_stat.m_local_header_ofs;\n    cur_dst_file_ofs = pZip->m_archive_size;\n\n    /* Read the source archive's local dir header */\n    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n\n    if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n\n    cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;\n\n    /* Compute the total size we need to copy (filename+extra data+compressed data) */\n    local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS);\n    local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);\n    local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS);\n    local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS);\n    src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size;\n\n    /* Try to find a zip64 extended information field */\n    if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX)))\n    {\n        mz_zip_array file_data_array;\n        const mz_uint8 *pExtra_data;\n        mz_uint32 extra_size_remaining = local_header_extra_len;\n\n        mz_zip_array_init(&file_data_array, 1);\n        if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE))\n        {\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len)\n        {\n            mz_zip_array_clear(pZip, &file_data_array);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n        }\n\n        pExtra_data = (const mz_uint8 *)file_data_array.m_p;\n\n        do\n        {\n            mz_uint32 field_id, field_data_size, field_total_size;\n\n            if (extra_size_remaining < (sizeof(mz_uint16) * 2))\n            {\n                mz_zip_array_clear(pZip, &file_data_array);\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n            }\n\n            field_id = MZ_READ_LE16(pExtra_data);\n            field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16));\n            field_total_size = field_data_size + sizeof(mz_uint16) * 2;\n\n            if (field_total_size > extra_size_remaining)\n            {\n                mz_zip_array_clear(pZip, &file_data_array);\n                return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n            }\n\n            if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID)\n            {\n                const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32);\n\n                if (field_data_size < sizeof(mz_uint64) * 2)\n                {\n                    mz_zip_array_clear(pZip, &file_data_array);\n                    return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);\n                }\n\n                local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data);\n                local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */\n\n                found_zip64_ext_data_in_ldir = MZ_TRUE;\n                break;\n            }\n\n            pExtra_data += field_total_size;\n            extra_size_remaining -= field_total_size;\n        } while (extra_size_remaining);\n\n        mz_zip_array_clear(pZip, &file_data_array);\n    }\n\n    if (!pState->m_zip64)\n    {\n        /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */\n        /* We also check when the archive is finalized so this doesn't need to be perfect. */\n        mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) +\n                                            pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64;\n\n        if (approx_new_archive_size >= MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n    }\n\n    /* Write dest archive padding */\n    if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))\n        return MZ_FALSE;\n\n    cur_dst_file_ofs += num_alignment_padding_bytes;\n\n    local_dir_header_ofs = cur_dst_file_ofs;\n    if (pZip->m_file_offset_alignment)\n    {\n        MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0);\n    }\n\n    /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */\n    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n    cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;\n\n    /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */\n    if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining)))))\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n    while (src_archive_bytes_remaining)\n    {\n        n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining);\n        if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n        }\n        cur_src_file_ofs += n;\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n        cur_dst_file_ofs += n;\n\n        src_archive_bytes_remaining -= n;\n    }\n\n    /* Now deal with the optional data descriptor */\n    bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);\n    if (bit_flags & 8)\n    {\n        /* Copy data descriptor */\n        if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir))\n        {\n            /* src is zip64, dest must be zip64 */\n\n            /* name\t\t\tuint32_t's */\n            /* id\t\t\t\t1 (optional in zip64?) */\n            /* crc\t\t\t1 */\n            /* comp_size\t2 */\n            /* uncomp_size 2 */\n            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6))\n            {\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n            }\n\n            n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5);\n        }\n        else\n        {\n            /* src is NOT zip64 */\n            mz_bool has_id;\n\n            if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)\n            {\n                pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n                return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);\n            }\n\n            has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID);\n\n            if (pZip->m_pState->m_zip64)\n            {\n                /* dest is zip64, so upgrade the data descriptor */\n                const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0);\n                const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor);\n                const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32));\n                const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2*sizeof(mz_uint32));\n\n                mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID);\n                mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32);\n                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size);\n                mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size);\n\n                n = sizeof(mz_uint32) * 6;\n            }\n            else\n            {\n                /* dest is NOT zip64, just copy it as-is */\n                n = sizeof(mz_uint32) * (has_id ? 4 : 3);\n            }\n        }\n\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)\n        {\n            pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n        }\n\n        cur_src_file_ofs += n;\n        cur_dst_file_ofs += n;\n    }\n    pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);\n\n    /* Finally, add the new central dir header */\n    orig_central_dir_size = pState->m_central_dir.m_size;\n\n    memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);\n\n    if (pState->m_zip64)\n    {\n        /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */\n        const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len;\n        mz_zip_array new_ext_block;\n\n        mz_zip_array_init(&new_ext_block, sizeof(mz_uint8));\n\n        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX);\n        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX);\n        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX);\n\n        if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL))\n        {\n            mz_zip_array_clear(pZip, &new_ext_block);\n            return MZ_FALSE;\n        }\n\n        MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size);\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))\n        {\n            mz_zip_array_clear(pZip, &new_ext_block);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len))\n        {\n            mz_zip_array_clear(pZip, &new_ext_block);\n            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size))\n        {\n            mz_zip_array_clear(pZip, &new_ext_block);\n            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len))\n        {\n            mz_zip_array_clear(pZip, &new_ext_block);\n            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n\n        mz_zip_array_clear(pZip, &new_ext_block);\n    }\n    else\n    {\n        /* sanity checks */\n        if (cur_dst_file_ofs > MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n\n        if (local_dir_header_ofs >= MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE);\n\n        MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n\n        if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size))\n        {\n            mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n            return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n        }\n    }\n\n    /* This shouldn't trigger unless we screwed up during the initial sanity checks */\n    if (pState->m_central_dir.m_size >= MZ_UINT32_MAX)\n    {\n        /* TODO: Support central dirs >= 32-bits in size */\n        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n        return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE);\n    }\n\n    n = (mz_uint32)orig_central_dir_size;\n    if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))\n    {\n        mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);\n        return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED);\n    }\n\n    pZip->m_total_files++;\n    pZip->m_archive_size = cur_dst_file_ofs;\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)\n{\n    mz_zip_internal_state *pState;\n    mz_uint64 central_dir_ofs, central_dir_size;\n    mz_uint8 hdr[256];\n\n    if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    pState = pZip->m_pState;\n\n    if (pState->m_zip64)\n    {\n        if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX)\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n    else\n    {\n        if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX))\n            return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES);\n    }\n\n    central_dir_ofs = 0;\n    central_dir_size = 0;\n    if (pZip->m_total_files)\n    {\n        /* Write central directory */\n        central_dir_ofs = pZip->m_archive_size;\n        central_dir_size = pState->m_central_dir.m_size;\n        pZip->m_central_directory_file_ofs = central_dir_ofs;\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        pZip->m_archive_size += central_dir_size;\n    }\n\n    if (pState->m_zip64)\n    {\n        /* Write zip64 end of central directory header */\n        mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size;\n\n        MZ_CLEAR_ARR(hdr);\n        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64));\n        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */\n        MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs);\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE;\n\n        /* Write zip64 end of central directory locator */\n        MZ_CLEAR_ARR(hdr);\n        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG);\n        MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr);\n        MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1);\n        if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)\n            return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n        pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE;\n    }\n\n    /* Write end of central directory record */\n    MZ_CLEAR_ARR(hdr);\n    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);\n    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));\n    MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files));\n    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size));\n    MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs));\n\n    if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED);\n\n#ifndef MINIZ_NO_STDIO\n    if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))\n        return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED);\n#endif /* #ifndef MINIZ_NO_STDIO */\n\n    pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE;\n\n    pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize)\n{\n    if ((!ppBuf) || (!pSize))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    *ppBuf = NULL;\n    *pSize = 0;\n\n    if ((!pZip) || (!pZip->m_pState))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (pZip->m_pWrite != mz_zip_heap_write_func)\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    if (!mz_zip_writer_finalize_archive(pZip))\n        return MZ_FALSE;\n\n    *ppBuf = pZip->m_pState->m_pMem;\n    *pSize = pZip->m_pState->m_mem_size;\n    pZip->m_pState->m_pMem = NULL;\n    pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;\n\n    return MZ_TRUE;\n}\n\nmz_bool mz_zip_writer_end(mz_zip_archive *pZip)\n{\n    return mz_zip_writer_end_internal(pZip, MZ_TRUE);\n}\n\n#ifndef MINIZ_NO_STDIO\nmz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)\n{\n    return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL);\n}\n\nmz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr)\n{\n    mz_bool status, created_new_archive = MZ_FALSE;\n    mz_zip_archive zip_archive;\n    struct MZ_FILE_STAT_STRUCT file_stat;\n    mz_zip_error actual_err = MZ_ZIP_NO_ERROR;\n\n    mz_zip_zero_struct(&zip_archive);\n    if ((int)level_and_flags < 0)\n        level_and_flags = MZ_DEFAULT_LEVEL;\n\n    if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))\n    {\n        if (pErr)\n            *pErr = MZ_ZIP_INVALID_PARAMETER;\n        return MZ_FALSE;\n    }\n\n    if (!mz_zip_writer_validate_archive_name(pArchive_name))\n    {\n        if (pErr)\n            *pErr = MZ_ZIP_INVALID_FILENAME;\n        return MZ_FALSE;\n    }\n\n    /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */\n    /* So be sure to compile with _LARGEFILE64_SOURCE 1 */\n    if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)\n    {\n        /* Create a new archive. */\n        if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags))\n        {\n            if (pErr)\n                *pErr = zip_archive.m_last_error;\n            return MZ_FALSE;\n        }\n\n        created_new_archive = MZ_TRUE;\n    }\n    else\n    {\n        /* Append to an existing archive. */\n        if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))\n        {\n            if (pErr)\n                *pErr = zip_archive.m_last_error;\n            return MZ_FALSE;\n        }\n\n        if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags))\n        {\n            if (pErr)\n                *pErr = zip_archive.m_last_error;\n\n            mz_zip_reader_end_internal(&zip_archive, MZ_FALSE);\n\n            return MZ_FALSE;\n        }\n    }\n\n    status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);\n    actual_err = zip_archive.m_last_error;\n\n    /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */\n    if (!mz_zip_writer_finalize_archive(&zip_archive))\n    {\n        if (!actual_err)\n            actual_err = zip_archive.m_last_error;\n\n        status = MZ_FALSE;\n    }\n\n    if (!mz_zip_writer_end_internal(&zip_archive, status))\n    {\n        if (!actual_err)\n            actual_err = zip_archive.m_last_error;\n\n        status = MZ_FALSE;\n    }\n\n    if ((!status) && (created_new_archive))\n    {\n        /* It's a new archive and something went wrong, so just delete it. */\n        int ignoredStatus = MZ_DELETE_FILE(pZip_filename);\n        (void)ignoredStatus;\n    }\n\n    if (pErr)\n        *pErr = actual_err;\n\n    return status;\n}\n\nvoid *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr)\n{\n    mz_uint32 file_index;\n    mz_zip_archive zip_archive;\n    void *p = NULL;\n\n    if (pSize)\n        *pSize = 0;\n\n    if ((!pZip_filename) || (!pArchive_name))\n    {\n        if (pErr)\n            *pErr = MZ_ZIP_INVALID_PARAMETER;\n\n        return NULL;\n    }\n\n    mz_zip_zero_struct(&zip_archive);\n    if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0))\n    {\n        if (pErr)\n            *pErr = zip_archive.m_last_error;\n\n        return NULL;\n    }\n\n    if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index))\n    {\n        p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);\n    }\n\n    mz_zip_reader_end_internal(&zip_archive, p != NULL);\n\n    if (pErr)\n        *pErr = zip_archive.m_last_error;\n\n    return p;\n}\n\nvoid *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)\n{\n    return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL);\n}\n\n#endif /* #ifndef MINIZ_NO_STDIO */\n\n#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */\n\n/* ------------------- Misc utils */\n\nmz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip)\n{\n    return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID;\n}\n\nmz_zip_type mz_zip_get_type(mz_zip_archive *pZip)\n{\n    return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID;\n}\n\nmz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num)\n{\n    mz_zip_error prev_err;\n\n    if (!pZip)\n        return MZ_ZIP_INVALID_PARAMETER;\n\n    prev_err = pZip->m_last_error;\n\n    pZip->m_last_error = err_num;\n    return prev_err;\n}\n\nmz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip)\n{\n    if (!pZip)\n        return MZ_ZIP_INVALID_PARAMETER;\n\n    return pZip->m_last_error;\n}\n\nmz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip)\n{\n    return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR);\n}\n\nmz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip)\n{\n    mz_zip_error prev_err;\n\n    if (!pZip)\n        return MZ_ZIP_INVALID_PARAMETER;\n\n    prev_err = pZip->m_last_error;\n\n    pZip->m_last_error = MZ_ZIP_NO_ERROR;\n    return prev_err;\n}\n\nconst char *mz_zip_get_error_string(mz_zip_error mz_err)\n{\n    switch (mz_err)\n    {\n        case MZ_ZIP_NO_ERROR:\n            return \"no error\";\n        case MZ_ZIP_UNDEFINED_ERROR:\n            return \"undefined error\";\n        case MZ_ZIP_TOO_MANY_FILES:\n            return \"too many files\";\n        case MZ_ZIP_FILE_TOO_LARGE:\n            return \"file too large\";\n        case MZ_ZIP_UNSUPPORTED_METHOD:\n            return \"unsupported method\";\n        case MZ_ZIP_UNSUPPORTED_ENCRYPTION:\n            return \"unsupported encryption\";\n        case MZ_ZIP_UNSUPPORTED_FEATURE:\n            return \"unsupported feature\";\n        case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR:\n            return \"failed finding central directory\";\n        case MZ_ZIP_NOT_AN_ARCHIVE:\n            return \"not a ZIP archive\";\n        case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:\n            return \"invalid header or archive is corrupted\";\n        case MZ_ZIP_UNSUPPORTED_MULTIDISK:\n            return \"unsupported multidisk archive\";\n        case MZ_ZIP_DECOMPRESSION_FAILED:\n            return \"decompression failed or archive is corrupted\";\n        case MZ_ZIP_COMPRESSION_FAILED:\n            return \"compression failed\";\n        case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE:\n            return \"unexpected decompressed size\";\n        case MZ_ZIP_CRC_CHECK_FAILED:\n            return \"CRC-32 check failed\";\n        case MZ_ZIP_UNSUPPORTED_CDIR_SIZE:\n            return \"unsupported central directory size\";\n        case MZ_ZIP_ALLOC_FAILED:\n            return \"allocation failed\";\n        case MZ_ZIP_FILE_OPEN_FAILED:\n            return \"file open failed\";\n        case MZ_ZIP_FILE_CREATE_FAILED:\n            return \"file create failed\";\n        case MZ_ZIP_FILE_WRITE_FAILED:\n            return \"file write failed\";\n        case MZ_ZIP_FILE_READ_FAILED:\n            return \"file read failed\";\n        case MZ_ZIP_FILE_CLOSE_FAILED:\n            return \"file close failed\";\n        case MZ_ZIP_FILE_SEEK_FAILED:\n            return \"file seek failed\";\n        case MZ_ZIP_FILE_STAT_FAILED:\n            return \"file stat failed\";\n        case MZ_ZIP_INVALID_PARAMETER:\n            return \"invalid parameter\";\n        case MZ_ZIP_INVALID_FILENAME:\n            return \"invalid filename\";\n        case MZ_ZIP_BUF_TOO_SMALL:\n            return \"buffer too small\";\n        case MZ_ZIP_INTERNAL_ERROR:\n            return \"internal error\";\n        case MZ_ZIP_FILE_NOT_FOUND:\n            return \"file not found\";\n        case MZ_ZIP_ARCHIVE_TOO_LARGE:\n            return \"archive is too large\";\n        case MZ_ZIP_VALIDATION_FAILED:\n            return \"validation failed\";\n        case MZ_ZIP_WRITE_CALLBACK_FAILED:\n            return \"write callback failed\";\n\tcase MZ_ZIP_TOTAL_ERRORS:\n            return \"total errors\";\n        default:\n            break;\n    }\n\n    return \"unknown error\";\n}\n\n/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */\nmz_bool mz_zip_is_zip64(mz_zip_archive *pZip)\n{\n    if ((!pZip) || (!pZip->m_pState))\n        return MZ_FALSE;\n\n    return pZip->m_pState->m_zip64;\n}\n\nsize_t mz_zip_get_central_dir_size(mz_zip_archive *pZip)\n{\n    if ((!pZip) || (!pZip->m_pState))\n        return 0;\n\n    return pZip->m_pState->m_central_dir.m_size;\n}\n\nmz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)\n{\n    return pZip ? pZip->m_total_files : 0;\n}\n\nmz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip)\n{\n    if (!pZip)\n        return 0;\n    return pZip->m_archive_size;\n}\n\nmz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip)\n{\n    if ((!pZip) || (!pZip->m_pState))\n        return 0;\n    return pZip->m_pState->m_file_archive_start_ofs;\n}\n\nMZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip)\n{\n    if ((!pZip) || (!pZip->m_pState))\n        return 0;\n    return pZip->m_pState->m_pFile;\n}\n\nsize_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n)\n{\n    if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead))\n        return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n\n    return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n);\n}\n\nmz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)\n{\n    mz_uint n;\n    const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index);\n    if (!p)\n    {\n        if (filename_buf_size)\n            pFilename[0] = '\\0';\n        mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);\n        return 0;\n    }\n    n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);\n    if (filename_buf_size)\n    {\n        n = MZ_MIN(n, filename_buf_size - 1);\n        memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);\n        pFilename[n] = '\\0';\n    }\n    return n + 1;\n}\n\nmz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)\n{\n    return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);\n}\n\nmz_bool mz_zip_end(mz_zip_archive *pZip)\n{\n    if (!pZip)\n        return MZ_FALSE;\n\n    if (pZip->m_zip_mode == MZ_ZIP_MODE_READING)\n        return mz_zip_reader_end(pZip);\n#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS\n    else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))\n        return mz_zip_writer_end(pZip);\n#endif\n\n    return MZ_FALSE;\n}\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/\n"
  },
  {
    "path": "public/miniz.h",
    "content": "#ifndef MINIZ_EXPORT\n#define MINIZ_EXPORT\n#endif\n/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing\n   See \"unlicense\" statement at the end of this file.\n   Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013\n   Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt\n\n   Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define\n   MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).\n\n   * Low-level Deflate/Inflate implementation notes:\n\n     Compression: Use the \"tdefl\" API's. The compressor supports raw, static, and dynamic blocks, lazy or\n     greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses\n     approximately as well as zlib.\n\n     Decompression: Use the \"tinfl\" API's. The entire decompressor is implemented as a single function\n     coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory\n     block large enough to hold the entire file.\n\n     The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.\n\n   * zlib-style API notes:\n\n     miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in\n     zlib replacement in many apps:\n        The z_stream struct, optional memory allocation callbacks\n        deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound\n        inflateInit/inflateInit2/inflate/inflateReset/inflateEnd\n        compress, compress2, compressBound, uncompress\n        CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.\n        Supports raw deflate streams or standard zlib streams with adler-32 checking.\n\n     Limitations:\n      The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.\n      I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but\n      there are no guarantees that miniz.c pulls this off perfectly.\n\n   * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by\n     Alex Evans. Supports 1-4 bytes/pixel images.\n\n   * ZIP archive API notes:\n\n     The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to\n     get the job done with minimal fuss. There are simple API's to retrieve file information, read files from\n     existing archives, create new archives, append new files to existing archives, or clone archive data from\n     one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),\n     or you can specify custom file read/write callbacks.\n\n     - Archive reading: Just call this function to read a single file from a disk archive:\n\n      void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,\n        size_t *pSize, mz_uint zip_flags);\n\n     For more complex cases, use the \"mz_zip_reader\" functions. Upon opening an archive, the entire central\n     directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.\n\n     - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:\n\n     int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);\n\n     The locate operation can optionally check file comments too, which (as one example) can be used to identify\n     multiple versions of the same file in an archive. This function uses a simple linear search through the central\n     directory, so it's not very fast.\n\n     Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and\n     retrieve detailed info on each file by calling mz_zip_reader_file_stat().\n\n     - Archive creation: Use the \"mz_zip_writer\" functions. The ZIP writer immediately writes compressed file data\n     to disk and builds an exact image of the central directory in memory. The central directory image is written\n     all at once at the end of the archive file when the archive is finalized.\n\n     The archive writer can optionally align each file's local header and file data to any power of 2 alignment,\n     which can be useful when the archive will be read from optical media. Also, the writer supports placing\n     arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still\n     readable by any ZIP tool.\n\n     - Archive appending: The simple way to add a single file to an archive is to call this function:\n\n      mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,\n        const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);\n\n     The archive will be created if it doesn't already exist, otherwise it'll be appended to.\n     Note the appending is done in-place and is not an atomic operation, so if something goes wrong\n     during the operation it's possible the archive could be left without a central directory (although the local\n     file headers and file data will be fine, so the archive will be recoverable).\n\n     For more complex archive modification scenarios:\n     1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to\n     preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the\n     compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and\n     you're done. This is safe but requires a bunch of temporary disk space or heap memory.\n\n     2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),\n     append new files as needed, then finalize the archive which will write an updated central directory to the\n     original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a\n     possibility that the archive's central directory could be lost with this method if anything goes wrong, though.\n\n     - ZIP archive support limitations:\n     No spanning support. Extraction functions can only handle unencrypted, stored or deflated files.\n     Requires streams capable of seeking.\n\n   * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the\n     below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.\n\n   * Important: For best perf. be sure to customize the below macros for your target platform:\n     #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1\n     #define MINIZ_LITTLE_ENDIAN 1\n     #define MINIZ_HAS_64BIT_REGISTERS 1\n\n   * On platforms using glibc, Be sure to \"#define _LARGEFILE64_SOURCE 1\" before including miniz.c to ensure miniz\n     uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files\n     (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).\n*/\n#pragma once\n\n\n\n/* Defines to completely disable specific portions of miniz.c: \n   If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */\n\n/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */\n#define MINIZ_NO_STDIO\n\n/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */\n/* get/set file times, and the C run-time funcs that get/set times won't be called. */\n/* The current downside is the times written to your archives will be from 1979. */\n#define MINIZ_NO_TIME\n\n/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */\n/*#define MINIZ_NO_DEFLATE_APIS */\n\n/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */\n/*#define MINIZ_NO_INFLATE_APIS */\n\n/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */\n#define MINIZ_NO_ARCHIVE_APIS\n\n/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */\n#define MINIZ_NO_ARCHIVE_WRITING_APIS\n\n/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */\n/*#define MINIZ_NO_ZLIB_APIS */\n\n/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */\n/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */\n\n/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. \n   Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc\n   callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user\n   functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */\n/*#define MINIZ_NO_MALLOC */\n\n#ifdef MINIZ_NO_INFLATE_APIS\n#define MINIZ_NO_ARCHIVE_APIS\n#endif\n\n#ifdef MINIZ_NO_DEFLATE_APIS\n#define MINIZ_NO_ARCHIVE_WRITING_APIS\n#endif\n\n#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))\n/* TODO: Work around \"error: include file 'sys\\utime.h' when compiling with tcc on Linux */\n#define MINIZ_NO_TIME\n#endif\n\n#include <stddef.h>\n\n#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)\n#include <time.h>\n#endif\n\n#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)\n/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */\n#define MINIZ_X86_OR_X64_CPU 1\n#else\n#define MINIZ_X86_OR_X64_CPU 0\n#endif\n\n/* Set MINIZ_LITTLE_ENDIAN only if not set */\n#if !defined(MINIZ_LITTLE_ENDIAN)\n#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__)\n\n#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)\n/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */\n#define MINIZ_LITTLE_ENDIAN 1\n#else\n#define MINIZ_LITTLE_ENDIAN 0\n#endif\n\n#else\n\n#if MINIZ_X86_OR_X64_CPU\n#define MINIZ_LITTLE_ENDIAN 1\n#else\n#define MINIZ_LITTLE_ENDIAN 0\n#endif\n\n#endif\n#endif\n\n/* Using unaligned loads and stores causes errors when using UBSan */\n#if defined(__has_feature)\n#if __has_feature(undefined_behavior_sanitizer)\n#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0\n#endif\n#endif\n\n/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */\n#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES)\n#if MINIZ_X86_OR_X64_CPU\n/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */\n#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1\n#define MINIZ_UNALIGNED_USE_MEMCPY\n#else\n#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0\n#endif\n#endif\n\n#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)\n/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */\n#define MINIZ_HAS_64BIT_REGISTERS 1\n#else\n#define MINIZ_HAS_64BIT_REGISTERS 0\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ------------------- zlib-style API Definitions. */\n\n/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */\ntypedef unsigned long mz_ulong;\n\n/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */\nMINIZ_EXPORT void mz_free(void *p);\n\n#define MZ_ADLER32_INIT (1)\n/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */\nMINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);\n\n#define MZ_CRC32_INIT (0)\n/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */\nMINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);\n\n/* Compression strategies. */\nenum\n{\n    MZ_DEFAULT_STRATEGY = 0,\n    MZ_FILTERED = 1,\n    MZ_HUFFMAN_ONLY = 2,\n    MZ_RLE = 3,\n    MZ_FIXED = 4\n};\n\n/* Method */\n#define MZ_DEFLATED 8\n\n/* Heap allocation callbacks.\nNote that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */\ntypedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);\ntypedef void (*mz_free_func)(void *opaque, void *address);\ntypedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);\n\n/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */\nenum\n{\n    MZ_NO_COMPRESSION = 0,\n    MZ_BEST_SPEED = 1,\n    MZ_BEST_COMPRESSION = 9,\n    MZ_UBER_COMPRESSION = 10,\n    MZ_DEFAULT_LEVEL = 6,\n    MZ_DEFAULT_COMPRESSION = -1\n};\n\n#define MZ_VERSION \"11.0.2\"\n#define MZ_VERNUM 0xB002\n#define MZ_VER_MAJOR 11\n#define MZ_VER_MINOR 2\n#define MZ_VER_REVISION 0\n#define MZ_VER_SUBREVISION 0\n\n#ifndef MINIZ_NO_ZLIB_APIS\n\n/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */\nenum\n{\n    MZ_NO_FLUSH = 0,\n    MZ_PARTIAL_FLUSH = 1,\n    MZ_SYNC_FLUSH = 2,\n    MZ_FULL_FLUSH = 3,\n    MZ_FINISH = 4,\n    MZ_BLOCK = 5\n};\n\n/* Return status codes. MZ_PARAM_ERROR is non-standard. */\nenum\n{\n    MZ_OK = 0,\n    MZ_STREAM_END = 1,\n    MZ_NEED_DICT = 2,\n    MZ_ERRNO = -1,\n    MZ_STREAM_ERROR = -2,\n    MZ_DATA_ERROR = -3,\n    MZ_MEM_ERROR = -4,\n    MZ_BUF_ERROR = -5,\n    MZ_VERSION_ERROR = -6,\n    MZ_PARAM_ERROR = -10000\n};\n\n/* Window bits */\n#define MZ_DEFAULT_WINDOW_BITS 15\n\nstruct mz_internal_state;\n\n/* Compression/decompression stream struct. */\ntypedef struct mz_stream_s\n{\n    const unsigned char *next_in; /* pointer to next byte to read */\n    unsigned int avail_in;        /* number of bytes available at next_in */\n    mz_ulong total_in;            /* total number of bytes consumed so far */\n\n    unsigned char *next_out; /* pointer to next byte to write */\n    unsigned int avail_out;  /* number of bytes that can be written to next_out */\n    mz_ulong total_out;      /* total number of bytes produced so far */\n\n    char *msg;                       /* error msg (unused) */\n    struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */\n\n    mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */\n    mz_free_func zfree;   /* optional heap free function (defaults to free) */\n    void *opaque;         /* heap alloc function user pointer */\n\n    int data_type;     /* data_type (unused) */\n    mz_ulong adler;    /* adler32 of the source or uncompressed data */\n    mz_ulong reserved; /* not used */\n} mz_stream;\n\ntypedef mz_stream *mz_streamp;\n\n/* Returns the version string of miniz.c. */\nMINIZ_EXPORT const char *mz_version(void);\n\n#ifndef MINIZ_NO_DEFLATE_APIS\n\n/* mz_deflateInit() initializes a compressor with default options: */\n/* Parameters: */\n/*  pStream must point to an initialized mz_stream struct. */\n/*  level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */\n/*  level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */\n/*  (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */\n/* Return values: */\n/*  MZ_OK on success. */\n/*  MZ_STREAM_ERROR if the stream is bogus. */\n/*  MZ_PARAM_ERROR if the input parameters are bogus. */\n/*  MZ_MEM_ERROR on out of memory. */\nMINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level);\n\n/* mz_deflateInit2() is like mz_deflate(), except with more control: */\n/* Additional parameters: */\n/*   method must be MZ_DEFLATED */\n/*   window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */\n/*   mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */\nMINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);\n\n/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */\nMINIZ_EXPORT int mz_deflateReset(mz_streamp pStream);\n\n/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */\n/* Parameters: */\n/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */\n/*   flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */\n/* Return values: */\n/*   MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */\n/*   MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */\n/*   MZ_STREAM_ERROR if the stream is bogus. */\n/*   MZ_PARAM_ERROR if one of the parameters is invalid. */\n/*   MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */\nMINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush);\n\n/* mz_deflateEnd() deinitializes a compressor: */\n/* Return values: */\n/*  MZ_OK on success. */\n/*  MZ_STREAM_ERROR if the stream is bogus. */\nMINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream);\n\n/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */\nMINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);\n\n/* Single-call compression functions mz_compress() and mz_compress2(): */\n/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */\nMINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);\nMINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);\n\n/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */\nMINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len);\n\n#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/\n\n#ifndef MINIZ_NO_INFLATE_APIS\n\n/* Initializes a decompressor. */\nMINIZ_EXPORT int mz_inflateInit(mz_streamp pStream);\n\n/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */\n/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */\nMINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits);\n\n/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */\nMINIZ_EXPORT int mz_inflateReset(mz_streamp pStream);\n\n/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */\n/* Parameters: */\n/*   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */\n/*   flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */\n/*   On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */\n/*   MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */\n/* Return values: */\n/*   MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */\n/*   MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */\n/*   MZ_STREAM_ERROR if the stream is bogus. */\n/*   MZ_DATA_ERROR if the deflate stream is invalid. */\n/*   MZ_PARAM_ERROR if one of the parameters is invalid. */\n/*   MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */\n/*   with more input data, or with more room in the output buffer (except when using single call decompression, described above). */\nMINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush);\n\n/* Deinitializes a decompressor. */\nMINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream);\n\n/* Single-call decompression. */\n/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */\nMINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);\nMINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len);\n#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/\n\n/* Returns a string description of the specified error code, or NULL if the error code is invalid. */\nMINIZ_EXPORT const char *mz_error(int err);\n\n/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */\n/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */\n#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES\ntypedef unsigned char Byte;\ntypedef unsigned int uInt;\ntypedef mz_ulong uLong;\ntypedef Byte Bytef;\ntypedef uInt uIntf;\ntypedef char charf;\ntypedef int intf;\ntypedef void *voidpf;\ntypedef uLong uLongf;\ntypedef void *voidp;\ntypedef void *const voidpc;\n#define Z_NULL 0\n#define Z_NO_FLUSH MZ_NO_FLUSH\n#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH\n#define Z_SYNC_FLUSH MZ_SYNC_FLUSH\n#define Z_FULL_FLUSH MZ_FULL_FLUSH\n#define Z_FINISH MZ_FINISH\n#define Z_BLOCK MZ_BLOCK\n#define Z_OK MZ_OK\n#define Z_STREAM_END MZ_STREAM_END\n#define Z_NEED_DICT MZ_NEED_DICT\n#define Z_ERRNO MZ_ERRNO\n#define Z_STREAM_ERROR MZ_STREAM_ERROR\n#define Z_DATA_ERROR MZ_DATA_ERROR\n#define Z_MEM_ERROR MZ_MEM_ERROR\n#define Z_BUF_ERROR MZ_BUF_ERROR\n#define Z_VERSION_ERROR MZ_VERSION_ERROR\n#define Z_PARAM_ERROR MZ_PARAM_ERROR\n#define Z_NO_COMPRESSION MZ_NO_COMPRESSION\n#define Z_BEST_SPEED MZ_BEST_SPEED\n#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION\n#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION\n#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY\n#define Z_FILTERED MZ_FILTERED\n#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY\n#define Z_RLE MZ_RLE\n#define Z_FIXED MZ_FIXED\n#define Z_DEFLATED MZ_DEFLATED\n#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS\n#define alloc_func mz_alloc_func\n#define free_func mz_free_func\n#define internal_state mz_internal_state\n#define z_stream mz_stream\n\n#ifndef MINIZ_NO_DEFLATE_APIS\n#define deflateInit mz_deflateInit\n#define deflateInit2 mz_deflateInit2\n#define deflateReset mz_deflateReset\n#define deflate mz_deflate\n#define deflateEnd mz_deflateEnd\n#define deflateBound mz_deflateBound\n#define compress mz_compress\n#define compress2 mz_compress2\n#define compressBound mz_compressBound\n#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/\n\n#ifndef MINIZ_NO_INFLATE_APIS\n#define inflateInit mz_inflateInit\n#define inflateInit2 mz_inflateInit2\n#define inflateReset mz_inflateReset\n#define inflate mz_inflate\n#define inflateEnd mz_inflateEnd\n#define uncompress mz_uncompress\n#define uncompress2 mz_uncompress2\n#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/\n\n#define crc32 mz_crc32\n#define adler32 mz_adler32\n#define MAX_WBITS 15\n#define MAX_MEM_LEVEL 9\n#define zError mz_error\n#define ZLIB_VERSION MZ_VERSION\n#define ZLIB_VERNUM MZ_VERNUM\n#define ZLIB_VER_MAJOR MZ_VER_MAJOR\n#define ZLIB_VER_MINOR MZ_VER_MINOR\n#define ZLIB_VER_REVISION MZ_VER_REVISION\n#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION\n#define zlibVersion mz_version\n#define zlib_version mz_version()\n#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */\n\n#endif /* MINIZ_NO_ZLIB_APIS */\n\n#ifdef __cplusplus\n}\n#endif\n\n\n\n\n\n#pragma once\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n\n\n/* ------------------- Types and macros */\ntypedef unsigned char mz_uint8;\ntypedef signed short mz_int16;\ntypedef unsigned short mz_uint16;\ntypedef unsigned int mz_uint32;\ntypedef unsigned int mz_uint;\ntypedef int64_t mz_int64;\ntypedef uint64_t mz_uint64;\ntypedef int mz_bool;\n\n#define MZ_FALSE (0)\n#define MZ_TRUE (1)\n\n/* Works around MSVC's spammy \"warning C4127: conditional expression is constant\" message. */\n#ifdef _MSC_VER\n#define MZ_MACRO_END while (0, 0)\n#else\n#define MZ_MACRO_END while (0)\n#endif\n\n#ifdef MINIZ_NO_STDIO\n#define MZ_FILE void *\n#else\n#include <stdio.h>\n#define MZ_FILE FILE\n#endif /* #ifdef MINIZ_NO_STDIO */\n\n#ifdef MINIZ_NO_TIME\ntypedef struct mz_dummy_time_t_tag\n{\n    mz_uint32 m_dummy1;\n    mz_uint32 m_dummy2;\n} mz_dummy_time_t;\n#define MZ_TIME_T mz_dummy_time_t\n#else\n#define MZ_TIME_T time_t\n#endif\n\n#define MZ_ASSERT(x) assert(x)\n\n#ifdef MINIZ_NO_MALLOC\n#define MZ_MALLOC(x) NULL\n#define MZ_FREE(x) (void)x, ((void)0)\n#define MZ_REALLOC(p, x) NULL\n#else\n#define MZ_MALLOC(x) malloc(x)\n#define MZ_FREE(x) free(x)\n#define MZ_REALLOC(p, x) realloc(p, x)\n#endif\n\n#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b))\n#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b))\n#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))\n#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj))\n#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj))\n\n#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN\n#define MZ_READ_LE16(p) *((const mz_uint16 *)(p))\n#define MZ_READ_LE32(p) *((const mz_uint32 *)(p))\n#else\n#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))\n#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))\n#endif\n\n#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U))\n\n#ifdef _MSC_VER\n#define MZ_FORCEINLINE __forceinline\n#elif defined(__GNUC__)\n#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__))\n#else\n#define MZ_FORCEINLINE inline\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nextern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size);\nextern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address);\nextern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size);\n\n#define MZ_UINT16_MAX (0xFFFFU)\n#define MZ_UINT32_MAX (0xFFFFFFFFU)\n\n#ifdef __cplusplus\n}\n#endif\n #pragma once\n\n\n#ifndef MINIZ_NO_DEFLATE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n/* ------------------- Low-level Compression API Definitions */\n\n/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */\n#define TDEFL_LESS_MEMORY 0\n\n/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */\n/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */\nenum\n{\n    TDEFL_HUFFMAN_ONLY = 0,\n    TDEFL_DEFAULT_MAX_PROBES = 128,\n    TDEFL_MAX_PROBES_MASK = 0xFFF\n};\n\n/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */\n/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */\n/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */\n/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */\n/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */\n/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */\n/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */\n/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */\n/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */\nenum\n{\n    TDEFL_WRITE_ZLIB_HEADER = 0x01000,\n    TDEFL_COMPUTE_ADLER32 = 0x02000,\n    TDEFL_GREEDY_PARSING_FLAG = 0x04000,\n    TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,\n    TDEFL_RLE_MATCHES = 0x10000,\n    TDEFL_FILTER_MATCHES = 0x20000,\n    TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000,\n    TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000\n};\n\n/* High level compression functions: */\n/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */\n/* On entry: */\n/*  pSrc_buf, src_buf_len: Pointer and size of source block to compress. */\n/*  flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */\n/* On return: */\n/*  Function returns a pointer to the compressed data, or NULL on failure. */\n/*  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */\n/*  The caller must free() the returned block when it's no longer needed. */\nMINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);\n\n/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */\n/* Returns 0 on failure. */\nMINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);\n\n/* Compresses an image to a compressed PNG file in memory. */\n/* On entry: */\n/*  pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */\n/*  The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */\n/*  level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */\n/*  If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */\n/* On return: */\n/*  Function returns a pointer to the compressed data, or NULL on failure. */\n/*  *pLen_out will be set to the size of the PNG image file. */\n/*  The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */\nMINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);\nMINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);\n\n/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */\ntypedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);\n\n/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */\nMINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);\n\nenum\n{\n    TDEFL_MAX_HUFF_TABLES = 3,\n    TDEFL_MAX_HUFF_SYMBOLS_0 = 288,\n    TDEFL_MAX_HUFF_SYMBOLS_1 = 32,\n    TDEFL_MAX_HUFF_SYMBOLS_2 = 19,\n    TDEFL_LZ_DICT_SIZE = 32768,\n    TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1,\n    TDEFL_MIN_MATCH_LEN = 3,\n    TDEFL_MAX_MATCH_LEN = 258\n};\n\n/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */\n#if TDEFL_LESS_MEMORY\nenum\n{\n    TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024,\n    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,\n    TDEFL_MAX_HUFF_SYMBOLS = 288,\n    TDEFL_LZ_HASH_BITS = 12,\n    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,\n    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,\n    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS\n};\n#else\nenum\n{\n    TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024,\n    TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10,\n    TDEFL_MAX_HUFF_SYMBOLS = 288,\n    TDEFL_LZ_HASH_BITS = 15,\n    TDEFL_LEVEL1_HASH_SIZE_MASK = 4095,\n    TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3,\n    TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS\n};\n#endif\n\n/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */\ntypedef enum {\n    TDEFL_STATUS_BAD_PARAM = -2,\n    TDEFL_STATUS_PUT_BUF_FAILED = -1,\n    TDEFL_STATUS_OKAY = 0,\n    TDEFL_STATUS_DONE = 1\n} tdefl_status;\n\n/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */\ntypedef enum {\n    TDEFL_NO_FLUSH = 0,\n    TDEFL_SYNC_FLUSH = 2,\n    TDEFL_FULL_FLUSH = 3,\n    TDEFL_FINISH = 4\n} tdefl_flush;\n\n/* tdefl's compression state structure. */\ntypedef struct\n{\n    tdefl_put_buf_func_ptr m_pPut_buf_func;\n    void *m_pPut_buf_user;\n    mz_uint m_flags, m_max_probes[2];\n    int m_greedy_parsing;\n    mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;\n    mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;\n    mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;\n    mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;\n    tdefl_status m_prev_return_status;\n    const void *m_pIn_buf;\n    void *m_pOut_buf;\n    size_t *m_pIn_buf_size, *m_pOut_buf_size;\n    tdefl_flush m_flush;\n    const mz_uint8 *m_pSrc;\n    size_t m_src_buf_left, m_out_buf_ofs;\n    mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];\n    mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];\n    mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];\n    mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];\n    mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];\n    mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];\n    mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];\n    mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];\n} tdefl_compressor;\n\n/* Initializes the compressor. */\n/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */\n/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */\n/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */\n/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */\nMINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);\n\n/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */\nMINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);\n\n/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */\n/* tdefl_compress_buffer() always consumes the entire input buffer. */\nMINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);\n\nMINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);\nMINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d);\n\n/* Create tdefl_compress() flags given zlib-style compression parameters. */\n/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */\n/* window_bits may be -15 (raw deflate) or 15 (zlib) */\n/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */\nMINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);\n\n#ifndef MINIZ_NO_MALLOC\n/* Allocate the tdefl_compressor structure in C so that */\n/* non-C language bindings to tdefl_ API don't need to worry about */\n/* structure size and allocation mechanism. */\nMINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void);\nMINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp);\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/\n #pragma once\n\n/* ------------------- Low-level Decompression API Definitions */\n\n#ifndef MINIZ_NO_INFLATE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n/* Decompression flags used by tinfl_decompress(). */\n/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */\n/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */\n/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */\n/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */\nenum\n{\n    TINFL_FLAG_PARSE_ZLIB_HEADER = 1,\n    TINFL_FLAG_HAS_MORE_INPUT = 2,\n    TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,\n    TINFL_FLAG_COMPUTE_ADLER32 = 8\n};\n\n/* High level decompression functions: */\n/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */\n/* On entry: */\n/*  pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */\n/* On return: */\n/*  Function returns a pointer to the decompressed data, or NULL on failure. */\n/*  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */\n/*  The caller must call mz_free() on the returned block when it's no longer needed. */\nMINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);\n\n/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */\n/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */\n#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))\nMINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);\n\n/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */\n/* Returns 1 on success or 0 on failure. */\ntypedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser);\nMINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);\n\nstruct tinfl_decompressor_tag;\ntypedef struct tinfl_decompressor_tag tinfl_decompressor;\n\n#ifndef MINIZ_NO_MALLOC\n/* Allocate the tinfl_decompressor structure in C so that */\n/* non-C language bindings to tinfl_ API don't need to worry about */\n/* structure size and allocation mechanism. */\nMINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void);\nMINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp);\n#endif\n\n/* Max size of LZ dictionary. */\n#define TINFL_LZ_DICT_SIZE 32768\n\n/* Return status. */\ntypedef enum {\n    /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */\n    /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */\n    /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */\n    TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4,\n\n    /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */\n    TINFL_STATUS_BAD_PARAM = -3,\n\n    /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */\n    TINFL_STATUS_ADLER32_MISMATCH = -2,\n\n    /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */\n    TINFL_STATUS_FAILED = -1,\n\n    /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */\n\n    /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */\n    /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */\n    TINFL_STATUS_DONE = 0,\n\n    /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */\n    /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */\n    /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */\n    TINFL_STATUS_NEEDS_MORE_INPUT = 1,\n\n    /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */\n    /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */\n    /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */\n    /* so I may need to add some code to address this. */\n    TINFL_STATUS_HAS_MORE_OUTPUT = 2\n} tinfl_status;\n\n/* Initializes the decompressor to its initial state. */\n#define tinfl_init(r)     \\\n    do                    \\\n    {                     \\\n        (r)->m_state = 0; \\\n    }                     \\\n    MZ_MACRO_END\n#define tinfl_get_adler32(r) (r)->m_check_adler32\n\n/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */\n/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */\nMINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);\n\n/* Internal/private bits follow. */\nenum\n{\n    TINFL_MAX_HUFF_TABLES = 3,\n    TINFL_MAX_HUFF_SYMBOLS_0 = 288,\n    TINFL_MAX_HUFF_SYMBOLS_1 = 32,\n    TINFL_MAX_HUFF_SYMBOLS_2 = 19,\n    TINFL_FAST_LOOKUP_BITS = 10,\n    TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS\n};\n\n#if MINIZ_HAS_64BIT_REGISTERS\n#define TINFL_USE_64BIT_BITBUF 1\n#else\n#define TINFL_USE_64BIT_BITBUF 0\n#endif\n\n#if TINFL_USE_64BIT_BITBUF\ntypedef mz_uint64 tinfl_bit_buf_t;\n#define TINFL_BITBUF_SIZE (64)\n#else\ntypedef mz_uint32 tinfl_bit_buf_t;\n#define TINFL_BITBUF_SIZE (32)\n#endif\n\nstruct tinfl_decompressor_tag\n{\n    mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];\n    tinfl_bit_buf_t m_bit_buf;\n    size_t m_dist_from_out_buf_start;\n    mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE];\n    mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2];\n    mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2];\n    mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2];\n    mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0];\n    mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1];\n    mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2];\n    mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];\n};\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/\n \n#pragma once\n\n\n/* ------------------- ZIP archive reading/writing */\n\n#ifndef MINIZ_NO_ARCHIVE_APIS\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum\n{\n    /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */\n    MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024,\n    MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512,\n    MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512\n};\n\ntypedef struct\n{\n    /* Central directory file index. */\n    mz_uint32 m_file_index;\n\n    /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */\n    mz_uint64 m_central_dir_ofs;\n\n    /* These fields are copied directly from the zip's central dir. */\n    mz_uint16 m_version_made_by;\n    mz_uint16 m_version_needed;\n    mz_uint16 m_bit_flag;\n    mz_uint16 m_method;\n\n    /* CRC-32 of uncompressed data. */\n    mz_uint32 m_crc32;\n\n    /* File's compressed size. */\n    mz_uint64 m_comp_size;\n\n    /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */\n    mz_uint64 m_uncomp_size;\n\n    /* Zip internal and external file attributes. */\n    mz_uint16 m_internal_attr;\n    mz_uint32 m_external_attr;\n\n    /* Entry's local header file offset in bytes. */\n    mz_uint64 m_local_header_ofs;\n\n    /* Size of comment in bytes. */\n    mz_uint32 m_comment_size;\n\n    /* MZ_TRUE if the entry appears to be a directory. */\n    mz_bool m_is_directory;\n\n    /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */\n    mz_bool m_is_encrypted;\n\n    /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */\n    mz_bool m_is_supported;\n\n    /* Filename. If string ends in '/' it's a subdirectory entry. */\n    /* Guaranteed to be zero terminated, may be truncated to fit. */\n    char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];\n\n    /* Comment field. */\n    /* Guaranteed to be zero terminated, may be truncated to fit. */\n    char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];\n\n#ifdef MINIZ_NO_TIME\n    MZ_TIME_T m_padding;\n#else\n    MZ_TIME_T m_time;\n#endif\n} mz_zip_archive_file_stat;\n\ntypedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);\ntypedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);\ntypedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque);\n\nstruct mz_zip_internal_state_tag;\ntypedef struct mz_zip_internal_state_tag mz_zip_internal_state;\n\ntypedef enum {\n    MZ_ZIP_MODE_INVALID = 0,\n    MZ_ZIP_MODE_READING = 1,\n    MZ_ZIP_MODE_WRITING = 2,\n    MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3\n} mz_zip_mode;\n\ntypedef enum {\n    MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100,\n    MZ_ZIP_FLAG_IGNORE_PATH = 0x0200,\n    MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400,\n    MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800,\n    MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */\n    MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000,     /* validate the local headers, but don't decompress the entire file and check the crc32 */\n    MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000,               /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */\n    MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000,\n    MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000,\n    /*After adding a compressed file, seek back\n    to local file header and set the correct sizes*/\n    MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000\n} mz_zip_flags;\n\ntypedef enum {\n    MZ_ZIP_TYPE_INVALID = 0,\n    MZ_ZIP_TYPE_USER,\n    MZ_ZIP_TYPE_MEMORY,\n    MZ_ZIP_TYPE_HEAP,\n    MZ_ZIP_TYPE_FILE,\n    MZ_ZIP_TYPE_CFILE,\n    MZ_ZIP_TOTAL_TYPES\n} mz_zip_type;\n\n/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */\ntypedef enum {\n    MZ_ZIP_NO_ERROR = 0,\n    MZ_ZIP_UNDEFINED_ERROR,\n    MZ_ZIP_TOO_MANY_FILES,\n    MZ_ZIP_FILE_TOO_LARGE,\n    MZ_ZIP_UNSUPPORTED_METHOD,\n    MZ_ZIP_UNSUPPORTED_ENCRYPTION,\n    MZ_ZIP_UNSUPPORTED_FEATURE,\n    MZ_ZIP_FAILED_FINDING_CENTRAL_DIR,\n    MZ_ZIP_NOT_AN_ARCHIVE,\n    MZ_ZIP_INVALID_HEADER_OR_CORRUPTED,\n    MZ_ZIP_UNSUPPORTED_MULTIDISK,\n    MZ_ZIP_DECOMPRESSION_FAILED,\n    MZ_ZIP_COMPRESSION_FAILED,\n    MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE,\n    MZ_ZIP_CRC_CHECK_FAILED,\n    MZ_ZIP_UNSUPPORTED_CDIR_SIZE,\n    MZ_ZIP_ALLOC_FAILED,\n    MZ_ZIP_FILE_OPEN_FAILED,\n    MZ_ZIP_FILE_CREATE_FAILED,\n    MZ_ZIP_FILE_WRITE_FAILED,\n    MZ_ZIP_FILE_READ_FAILED,\n    MZ_ZIP_FILE_CLOSE_FAILED,\n    MZ_ZIP_FILE_SEEK_FAILED,\n    MZ_ZIP_FILE_STAT_FAILED,\n    MZ_ZIP_INVALID_PARAMETER,\n    MZ_ZIP_INVALID_FILENAME,\n    MZ_ZIP_BUF_TOO_SMALL,\n    MZ_ZIP_INTERNAL_ERROR,\n    MZ_ZIP_FILE_NOT_FOUND,\n    MZ_ZIP_ARCHIVE_TOO_LARGE,\n    MZ_ZIP_VALIDATION_FAILED,\n    MZ_ZIP_WRITE_CALLBACK_FAILED,\n    MZ_ZIP_TOTAL_ERRORS\n} mz_zip_error;\n\ntypedef struct\n{\n    mz_uint64 m_archive_size;\n    mz_uint64 m_central_directory_file_ofs;\n\n    /* We only support up to UINT32_MAX files in zip64 mode. */\n    mz_uint32 m_total_files;\n    mz_zip_mode m_zip_mode;\n    mz_zip_type m_zip_type;\n    mz_zip_error m_last_error;\n\n    mz_uint64 m_file_offset_alignment;\n\n    mz_alloc_func m_pAlloc;\n    mz_free_func m_pFree;\n    mz_realloc_func m_pRealloc;\n    void *m_pAlloc_opaque;\n\n    mz_file_read_func m_pRead;\n    mz_file_write_func m_pWrite;\n    mz_file_needs_keepalive m_pNeeds_keepalive;\n    void *m_pIO_opaque;\n\n    mz_zip_internal_state *m_pState;\n\n} mz_zip_archive;\n\ntypedef struct\n{\n    mz_zip_archive *pZip;\n    mz_uint flags;\n\n    int status;\n\n    mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs;\n    mz_zip_archive_file_stat file_stat;\n    void *pRead_buf;\n    void *pWrite_buf;\n\n    size_t out_blk_remain;\n\n    tinfl_decompressor inflator;\n\n#ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS\n    mz_uint padding;\n#else\n    mz_uint file_crc32;\n#endif\n\n} mz_zip_reader_extract_iter_state;\n\n/* -------- ZIP reading */\n\n/* Inits a ZIP archive reader. */\n/* These functions read and validate the archive's central directory. */\nMINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags);\n\nMINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags);\n\n#ifndef MINIZ_NO_STDIO\n/* Read a archive from a disk file. */\n/* file_start_ofs is the file offset where the archive actually begins, or 0. */\n/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */\nMINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size);\n\n/* Read an archive from an already opened FILE, beginning at the current file position. */\n/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */\n/* The FILE will NOT be closed when mz_zip_reader_end() is called. */\nMINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags);\n#endif\n\n/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */\nMINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip);\n\n/* -------- ZIP reading or writing */\n\n/* Clears a mz_zip_archive struct to all zeros. */\n/* Important: This must be done before passing the struct to any mz_zip functions. */\nMINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip);\n\nMINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip);\nMINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip);\n\n/* Returns the total number of files in the archive. */\nMINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);\n\nMINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip);\nMINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip);\nMINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip);\n\n/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */\nMINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n);\n\n/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */\n/* Note that the m_last_error functionality is not thread safe. */\nMINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num);\nMINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip);\nMINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip);\nMINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip);\nMINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err);\n\n/* MZ_TRUE if the archive file entry is a directory entry. */\nMINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);\n\n/* MZ_TRUE if the file is encrypted/strong encrypted. */\nMINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);\n\n/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */\nMINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index);\n\n/* Retrieves the filename of an archive file entry. */\n/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */\nMINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);\n\n/* Attempts to locates a file in the archive's central directory. */\n/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */\n/* Returns -1 if the file cannot be found. */\nMINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);\n\n/* Returns detailed information about an archive file entry. */\nMINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);\n\n/* MZ_TRUE if the file is in zip64 format. */\n/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */\nMINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip);\n\n/* Returns the total central directory size in bytes. */\n/* The current max supported size is <= MZ_UINT32_MAX. */\nMINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip);\n\n/* Extracts a archive file to a memory buffer using no memory allocation. */\n/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);\n\n/* Extracts a archive file to a memory buffer. */\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);\n\n/* Extracts a archive file to a dynamically allocated heap buffer. */\n/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */\n/* Returns NULL and sets the last error on failure. */\nMINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);\nMINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);\n\n/* Extracts a archive file using a callback function to output the file's data. */\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);\n\n/* Extract a file iteratively */\nMINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);\nMINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);\nMINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState);\n\n#ifndef MINIZ_NO_STDIO\n/* Extracts a archive file to a disk file and sets its last accessed and modified times. */\n/* This function only extracts files, not archive directory records. */\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);\n\n/* Extracts a archive file starting at the current position in the destination FILE stream. */\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags);\n#endif\n\n#if 0\n/* TODO */\n\ttypedef void *mz_zip_streaming_extract_state_ptr;\n\tmz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);\n\tmz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);\n\tmz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);\n\tmz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs);\n\tsize_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size);\n\tmz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState);\n#endif\n\n/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */\n/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */\nMINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags);\n\n/* Validates an entire archive by calling mz_zip_validate_file() on each file. */\nMINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags);\n\n/* Misc utils/helpers, valid for ZIP reading or writing */\nMINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr);\n#ifndef MINIZ_NO_STDIO\nMINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr);\n#endif\n\n/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */\nMINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip);\n\n/* -------- ZIP writing */\n\n#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS\n\n/* Inits a ZIP archive writer. */\n/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/\n/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/\nMINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);\nMINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags);\n\nMINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);\nMINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags);\n\n#ifndef MINIZ_NO_STDIO\nMINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);\nMINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags);\nMINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags);\n#endif\n\n/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */\n/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */\n/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */\n/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */\n/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */\n/* the archive is finalized the file's central directory will be hosed. */\nMINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);\nMINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags);\n\n/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */\n/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */\n/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */\nMINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);\n\n/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */\n/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */\nMINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,\n                                              mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);\n\nMINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags,\n                                                 mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len,\n                                                 const char *user_extra_data_central, mz_uint user_extra_data_central_len);\n\n/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */\n/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/\nMINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size,\n\tconst MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,\n\tconst char *user_extra_data_central, mz_uint user_extra_data_central_len);\n\n\n#ifndef MINIZ_NO_STDIO\n/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */\n/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */\nMINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);\n\n/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */\nMINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size,\n                                const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len,\n                                const char *user_extra_data_central, mz_uint user_extra_data_central_len);\n#endif\n\n/* Adds a file to an archive by fully cloning the data from another archive. */\n/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */\nMINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index);\n\n/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */\n/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */\n/* An archive must be manually finalized by calling this function for it to be valid. */\nMINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);\n\n/* Finalizes a heap archive, returning a pointer to the heap block and its size. */\n/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */\nMINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize);\n\n/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */\n/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */\nMINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip);\n\n/* -------- Misc. high-level helper functions: */\n\n/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */\n/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */\n/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */\n/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */\nMINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);\nMINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr);\n\n#ifndef MINIZ_NO_STDIO\n/* Reads a single file from an archive into a heap block. */\n/* If pComment is not NULL, only the file with the specified comment will be extracted. */\n/* Returns NULL on failure. */\nMINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags);\nMINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr);\n#endif\n\n#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* MINIZ_NO_ARCHIVE_APIS */\n"
  },
  {
    "path": "public/pstdint.h",
    "content": "/*  A portable stdint.h\n ****************************************************************************\n *  BSD License:\n ****************************************************************************\n *\n *  Copyright (c) 2005-2016 Paul Hsieh\n *  All rights reserved.\n *\n *  Redistribution and use in source and binary forms, with or without\n *  modification, are permitted provided that the following conditions\n *  are met:\n *\n *  1. Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *  2. Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *  3. The name of the author may not be used to endorse or promote products\n *     derived from this software without specific prior written permission.\n *\n *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n ****************************************************************************\n *\n *  Version 0.1.16.0\n *\n *  The ANSI C standard committee, for the C99 standard, specified the\n *  inclusion of a new standard include file called stdint.h.  This is\n *  a very useful and long desired include file which contains several\n *  very precise definitions for integer scalar types that is critically\n *  important for making several classes of applications portable\n *  including cryptography, hashing, variable length integer libraries\n *  and so on.  But for most developers its likely useful just for\n *  programming sanity.\n *\n *  The problem is that some compiler vendors chose to ignore the C99\n *  standard and some older compilers have no opportunity to be updated.\n *  Because of this situation, simply including stdint.h in your code\n *  makes it unportable.\n *\n *  So that's what this file is all about.  It's an attempt to build a\n *  single universal include file that works on as many platforms as\n *  possible to deliver what stdint.h is supposed to.  Even compilers\n *  that already come with stdint.h can use this file instead without\n *  any loss of functionality.  A few things that should be noted about\n *  this file:\n *\n *    1) It is not guaranteed to be portable and/or present an identical\n *       interface on all platforms.  The extreme variability of the\n *       ANSI C standard makes this an impossibility right from the\n *       very get go. Its really only meant to be useful for the vast\n *       majority of platforms that possess the capability of\n *       implementing usefully and precisely defined, standard sized\n *       integer scalars.  Systems which are not intrinsically 2s\n *       complement may produce invalid constants.\n *\n *    2) There is an unavoidable use of non-reserved symbols.\n *\n *    3) Other standard include files are invoked.\n *\n *    4) This file may come in conflict with future platforms that do\n *       include stdint.h.  The hope is that one or the other can be\n *       used with no real difference.\n *\n *    5) In the current version, if your platform can't represent\n *       int32_t, int16_t and int8_t, it just dumps out with a compiler\n *       error.\n *\n *    6) 64 bit integers may or may not be defined.  Test for their\n *       presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX.\n *       Note that this is different from the C99 specification which\n *       requires the existence of 64 bit support in the compiler.  If\n *       this is not defined for your platform, yet it is capable of\n *       dealing with 64 bits then it is because this file has not yet\n *       been extended to cover all of your system's capabilities.\n *\n *    7) (u)intptr_t may or may not be defined.  Test for its presence\n *       with the test: #ifdef PTRDIFF_MAX.  If this is not defined\n *       for your platform, then it is because this file has not yet\n *       been extended to cover all of your system's capabilities, not\n *       because its optional.\n *\n *    8) The following might not been defined even if your platform is\n *       capable of defining it:\n *\n *       WCHAR_MIN\n *       WCHAR_MAX\n *       (u)int64_t\n *       PTRDIFF_MIN\n *       PTRDIFF_MAX\n *       (u)intptr_t\n *\n *    9) The following have not been defined:\n *\n *       WINT_MIN\n *       WINT_MAX\n *\n *   10) The criteria for defining (u)int_least(*)_t isn't clear,\n *       except for systems which don't have a type that precisely\n *       defined 8, 16, or 32 bit types (which this include file does\n *       not support anyways). Default definitions have been given.\n *\n *   11) The criteria for defining (u)int_fast(*)_t isn't something I\n *       would trust to any particular compiler vendor or the ANSI C\n *       committee.  It is well known that \"compatible systems\" are\n *       commonly created that have very different performance\n *       characteristics from the systems they are compatible with,\n *       especially those whose vendors make both the compiler and the\n *       system.  Default definitions have been given, but its strongly\n *       recommended that users never use these definitions for any\n *       reason (they do *NOT* deliver any serious guarantee of\n *       improved performance -- not in this file, nor any vendor's\n *       stdint.h).\n *\n *   12) The following macros:\n *\n *       PRINTF_INTMAX_MODIFIER\n *       PRINTF_INT64_MODIFIER\n *       PRINTF_INT32_MODIFIER\n *       PRINTF_INT16_MODIFIER\n *       PRINTF_LEAST64_MODIFIER\n *       PRINTF_LEAST32_MODIFIER\n *       PRINTF_LEAST16_MODIFIER\n *       PRINTF_INTPTR_MODIFIER\n *\n *       are strings which have been defined as the modifiers required\n *       for the \"d\", \"u\" and \"x\" printf formats to correctly output\n *       (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t,\n *       (u)least32_t, (u)least16_t and (u)intptr_t types respectively.\n *       PRINTF_INTPTR_MODIFIER is not defined for some systems which\n *       provide their own stdint.h.  PRINTF_INT64_MODIFIER is not\n *       defined if INT64_MAX is not defined.  These are an extension\n *       beyond what C99 specifies must be in stdint.h.\n *\n *       In addition, the following macros are defined:\n *\n *       PRINTF_INTMAX_HEX_WIDTH\n *       PRINTF_INT64_HEX_WIDTH\n *       PRINTF_INT32_HEX_WIDTH\n *       PRINTF_INT16_HEX_WIDTH\n *       PRINTF_INT8_HEX_WIDTH\n *       PRINTF_INTMAX_DEC_WIDTH\n *       PRINTF_INT64_DEC_WIDTH\n *       PRINTF_INT32_DEC_WIDTH\n *       PRINTF_INT16_DEC_WIDTH\n *       PRINTF_UINT8_DEC_WIDTH\n *       PRINTF_UINTMAX_DEC_WIDTH\n *       PRINTF_UINT64_DEC_WIDTH\n *       PRINTF_UINT32_DEC_WIDTH\n *       PRINTF_UINT16_DEC_WIDTH\n *       PRINTF_UINT8_DEC_WIDTH\n *\n *       Which specifies the maximum number of characters required to\n *       print the number of that type in either hexadecimal or decimal.\n *       These are an extension beyond what C99 specifies must be in\n *       stdint.h.\n *\n *  Compilers tested (all with 0 warnings at their highest respective\n *  settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32\n *  bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio\n *  .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3\n *\n *  This file should be considered a work in progress.  Suggestions for\n *  improvements, especially those which increase coverage are strongly\n *  encouraged.\n *\n *  Acknowledgements\n *\n *  The following people have made significant contributions to the\n *  development and testing of this file:\n *\n *  Chris Howie\n *  John Steele Scott\n *  Dave Thorup\n *  John Dill\n *  Florian Wobbe\n *  Christopher Sean Morrison\n *  Mikkel Fahnoe Jorgensen\n *\n */\n\n#include <stddef.h>\n#include <limits.h>\n#include <signal.h>\n\n/*\n *  For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and\n *  do nothing else.  On the Mac OS X version of gcc this is _STDINT_H_.\n */\n\n#if ((defined(__SUNPRO_C) && __SUNPRO_C >= 0x570) || (defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (__GNUC__ > 3 || defined(_STDINT_H) || defined(_STDINT_H_) || defined (__UINT_FAST64_TYPE__)) )) && !defined (_PSTDINT_H_INCLUDED)\n#include <stdint.h>\n#define _PSTDINT_H_INCLUDED\n# if defined(__GNUC__) && (defined(__x86_64__) || defined(__ppc64__)) && !(defined(__APPLE__) && defined(__MACH__))\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"l\"\n#  endif\n#  ifndef PRINTF_INT32_MODIFIER\n#   define PRINTF_INT32_MODIFIER \"\"\n#  endif\n# else\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"ll\"\n#  endif\n#  ifndef PRINTF_INT32_MODIFIER\n#   if (UINT_MAX == UINT32_MAX)\n#    define PRINTF_INT32_MODIFIER \"\"\n#   else\n#    define PRINTF_INT32_MODIFIER \"l\"\n#   endif\n#  endif\n# endif\n# ifndef PRINTF_INT16_MODIFIER\n#  define PRINTF_INT16_MODIFIER \"h\"\n# endif\n# ifndef PRINTF_INTMAX_MODIFIER\n#  define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER\n# endif\n# ifndef PRINTF_INT64_HEX_WIDTH\n#  define PRINTF_INT64_HEX_WIDTH \"16\"\n# endif\n# ifndef PRINTF_UINT64_HEX_WIDTH\n#  define PRINTF_UINT64_HEX_WIDTH \"16\"\n# endif\n# ifndef PRINTF_INT32_HEX_WIDTH\n#  define PRINTF_INT32_HEX_WIDTH \"8\"\n# endif\n# ifndef PRINTF_UINT32_HEX_WIDTH\n#  define PRINTF_UINT32_HEX_WIDTH \"8\"\n# endif\n# ifndef PRINTF_INT16_HEX_WIDTH\n#  define PRINTF_INT16_HEX_WIDTH \"4\"\n# endif\n# ifndef PRINTF_UINT16_HEX_WIDTH\n#  define PRINTF_UINT16_HEX_WIDTH \"4\"\n# endif\n# ifndef PRINTF_INT8_HEX_WIDTH\n#  define PRINTF_INT8_HEX_WIDTH \"2\"\n# endif\n# ifndef PRINTF_UINT8_HEX_WIDTH\n#  define PRINTF_UINT8_HEX_WIDTH \"2\"\n# endif\n# ifndef PRINTF_INT64_DEC_WIDTH\n#  define PRINTF_INT64_DEC_WIDTH \"19\"\n# endif\n# ifndef PRINTF_UINT64_DEC_WIDTH\n#  define PRINTF_UINT64_DEC_WIDTH \"20\"\n# endif\n# ifndef PRINTF_INT32_DEC_WIDTH\n#  define PRINTF_INT32_DEC_WIDTH \"10\"\n# endif\n# ifndef PRINTF_UINT32_DEC_WIDTH\n#  define PRINTF_UINT32_DEC_WIDTH \"10\"\n# endif\n# ifndef PRINTF_INT16_DEC_WIDTH\n#  define PRINTF_INT16_DEC_WIDTH \"5\"\n# endif\n# ifndef PRINTF_UINT16_DEC_WIDTH\n#  define PRINTF_UINT16_DEC_WIDTH \"5\"\n# endif\n# ifndef PRINTF_INT8_DEC_WIDTH\n#  define PRINTF_INT8_DEC_WIDTH \"3\"\n# endif\n# ifndef PRINTF_UINT8_DEC_WIDTH\n#  define PRINTF_UINT8_DEC_WIDTH \"3\"\n# endif\n# ifndef PRINTF_INTMAX_HEX_WIDTH\n#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH\n# endif\n# ifndef PRINTF_UINTMAX_HEX_WIDTH\n#  define PRINTF_UINTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH\n# endif\n# ifndef PRINTF_INTMAX_DEC_WIDTH\n#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH\n# endif\n# ifndef PRINTF_UINTMAX_DEC_WIDTH\n#  define PRINTF_UINTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH\n# endif\n\n/*\n *  Something really weird is going on with Open Watcom.  Just pull some of\n *  these duplicated definitions from Open Watcom's stdint.h file for now.\n */\n\n# if defined (__WATCOMC__) && __WATCOMC__ >= 1250\n#  if !defined (INT64_C)\n#   define INT64_C(x)   (x + (INT64_MAX - INT64_MAX))\n#  endif\n#  if !defined (UINT64_C)\n#   define UINT64_C(x)  (x + (UINT64_MAX - UINT64_MAX))\n#  endif\n#  if !defined (INT32_C)\n#   define INT32_C(x)   (x + (INT32_MAX - INT32_MAX))\n#  endif\n#  if !defined (UINT32_C)\n#   define UINT32_C(x)  (x + (UINT32_MAX - UINT32_MAX))\n#  endif\n#  if !defined (INT16_C)\n#   define INT16_C(x)   (x)\n#  endif\n#  if !defined (UINT16_C)\n#   define UINT16_C(x)  (x)\n#  endif\n#  if !defined (INT8_C)\n#   define INT8_C(x)   (x)\n#  endif\n#  if !defined (UINT8_C)\n#   define UINT8_C(x)  (x)\n#  endif\n#  if !defined (UINT64_MAX)\n#   define UINT64_MAX  18446744073709551615ULL\n#  endif\n#  if !defined (INT64_MAX)\n#   define INT64_MAX  9223372036854775807LL\n#  endif\n#  if !defined (UINT32_MAX)\n#   define UINT32_MAX  4294967295UL\n#  endif\n#  if !defined (INT32_MAX)\n#   define INT32_MAX  2147483647L\n#  endif\n#  if !defined (INTMAX_MAX)\n#   define INTMAX_MAX INT64_MAX\n#  endif\n#  if !defined (INTMAX_MIN)\n#   define INTMAX_MIN INT64_MIN\n#  endif\n# endif\n#endif\n\n/*\n *  I have no idea what is the truly correct thing to do on older Solaris.\n *  From some online discussions, this seems to be what is being\n *  recommended.  For people who actually are developing on older Solaris,\n *  what I would like to know is, does this define all of the relevant\n *  macros of a complete stdint.h?  Remember, in pstdint.h 64 bit is\n *  considered optional.\n */\n\n#if (defined(__SUNPRO_C) && __SUNPRO_C >= 0x420) && !defined(_PSTDINT_H_INCLUDED)\n#include <sys/inttypes.h>\n#define _PSTDINT_H_INCLUDED\n#endif\n\n#ifndef _PSTDINT_H_INCLUDED\n#define _PSTDINT_H_INCLUDED\n\n#ifndef SIZE_MAX\n# define SIZE_MAX ((size_t)-1)\n#endif\n\n/*\n *  Deduce the type assignments from limits.h under the assumption that\n *  integer sizes in bits are powers of 2, and follow the ANSI\n *  definitions.\n */\n\n#ifndef UINT8_MAX\n# define UINT8_MAX 0xff\n#endif\n#if !defined(uint8_t) && !defined(_UINT8_T) && !defined(vxWorks)\n# if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S)\n    typedef unsigned char uint8_t;\n#   define UINT8_C(v) ((uint8_t) v)\n# else\n#   error \"Platform not supported\"\n# endif\n#endif\n\n#ifndef INT8_MAX\n# define INT8_MAX 0x7f\n#endif\n#ifndef INT8_MIN\n# define INT8_MIN INT8_C(0x80)\n#endif\n#if !defined(int8_t) && !defined(_INT8_T) && !defined(vxWorks)\n# if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S)\n    typedef signed char int8_t;\n#   define INT8_C(v) ((int8_t) v)\n# else\n#   error \"Platform not supported\"\n# endif\n#endif\n\n#ifndef UINT16_MAX\n# define UINT16_MAX 0xffff\n#endif\n#if !defined(uint16_t) && !defined(_UINT16_T) && !defined(vxWorks)\n#if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S)\n  typedef unsigned int uint16_t;\n# ifndef PRINTF_INT16_MODIFIER\n#  define PRINTF_INT16_MODIFIER \"\"\n# endif\n# define UINT16_C(v) ((uint16_t) (v))\n#elif (USHRT_MAX == UINT16_MAX)\n  typedef unsigned short uint16_t;\n# define UINT16_C(v) ((uint16_t) (v))\n# ifndef PRINTF_INT16_MODIFIER\n#  define PRINTF_INT16_MODIFIER \"h\"\n# endif\n#else\n#error \"Platform not supported\"\n#endif\n#endif\n\n#ifndef INT16_MAX\n# define INT16_MAX 0x7fff\n#endif\n#ifndef INT16_MIN\n# define INT16_MIN INT16_C(0x8000)\n#endif\n#if !defined(int16_t) && !defined(_INT16_T) && !defined(vxWorks)\n#if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S)\n  typedef signed int int16_t;\n# define INT16_C(v) ((int16_t) (v))\n# ifndef PRINTF_INT16_MODIFIER\n#  define PRINTF_INT16_MODIFIER \"\"\n# endif\n#elif (SHRT_MAX == INT16_MAX)\n  typedef signed short int16_t;\n# define INT16_C(v) ((int16_t) (v))\n# ifndef PRINTF_INT16_MODIFIER\n#  define PRINTF_INT16_MODIFIER \"h\"\n# endif\n#else\n#error \"Platform not supported\"\n#endif\n#endif\n\n#ifndef UINT32_MAX\n# define UINT32_MAX (0xffffffffUL)\n#endif\n#if !defined(uint32_t) && !defined(_UINT32_T) && !defined(vxWorks)\n#if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S)\n  typedef unsigned long uint32_t;\n# define UINT32_C(v) v ## UL\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"l\"\n# endif\n#elif (UINT_MAX == UINT32_MAX)\n  typedef unsigned int uint32_t;\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"\"\n# endif\n# define UINT32_C(v) v ## U\n#elif (USHRT_MAX == UINT32_MAX)\n  typedef unsigned short uint32_t;\n# define UINT32_C(v) ((unsigned short) (v))\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"\"\n# endif\n#else\n#error \"Platform not supported\"\n#endif\n#endif\n\n#ifndef INT32_MAX\n# define INT32_MAX (0x7fffffffL)\n#endif\n#ifndef INT32_MIN\n# define INT32_MIN INT32_C(0x80000000)\n#endif\n#if !defined(int32_t) && !defined(_INT32_T) && !defined(vxWorks)\n#if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S)\n  typedef signed long int32_t;\n# define INT32_C(v) v ## L\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"l\"\n# endif\n#elif (INT_MAX == INT32_MAX)\n  typedef signed int int32_t;\n# define INT32_C(v) v\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"\"\n# endif\n#elif (SHRT_MAX == INT32_MAX)\n  typedef signed short int32_t;\n# define INT32_C(v) ((short) (v))\n# ifndef PRINTF_INT32_MODIFIER\n#  define PRINTF_INT32_MODIFIER \"\"\n# endif\n#else\n#error \"Platform not supported\"\n#endif\n#endif\n\n/*\n *  The macro stdint_int64_defined is temporarily used to record\n *  whether or not 64 integer support is available.  It must be\n *  defined for any 64 integer extensions for new platforms that are\n *  added.\n */\n\n#undef stdint_int64_defined\n#if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S)\n# if (__STDC__ && __STDC_VERSION__ >= 199901L) || defined (S_SPLINT_S)\n#  define stdint_int64_defined\n   typedef long long int64_t;\n   typedef unsigned long long uint64_t;\n#  define UINT64_C(v) v ## ULL\n#  define  INT64_C(v) v ## LL\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"ll\"\n#  endif\n# endif\n#endif\n\n#if !defined (stdint_int64_defined)\n# if defined(__GNUC__) && !defined(vxWorks)\n#  define stdint_int64_defined\n   __extension__ typedef long long int64_t;\n   __extension__ typedef unsigned long long uint64_t;\n#  define UINT64_C(v) v ## ULL\n#  define  INT64_C(v) v ## LL\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"ll\"\n#  endif\n# elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S)\n#  define stdint_int64_defined\n   typedef long long int64_t;\n   typedef unsigned long long uint64_t;\n#  define UINT64_C(v) v ## ULL\n#  define  INT64_C(v) v ## LL\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"ll\"\n#  endif\n# elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC)\n#  define stdint_int64_defined\n   typedef __int64 int64_t;\n   typedef unsigned __int64 uint64_t;\n#  define UINT64_C(v) v ## UI64\n#  define  INT64_C(v) v ## I64\n#  ifndef PRINTF_INT64_MODIFIER\n#   define PRINTF_INT64_MODIFIER \"I64\"\n#  endif\n# endif\n#endif\n\n#if !defined (LONG_LONG_MAX) && defined (INT64_C)\n# define LONG_LONG_MAX INT64_C (9223372036854775807)\n#endif\n#ifndef ULONG_LONG_MAX\n# define ULONG_LONG_MAX UINT64_C (18446744073709551615)\n#endif\n\n#if !defined (INT64_MAX) && defined (INT64_C)\n# define INT64_MAX INT64_C (9223372036854775807)\n#endif\n#if !defined (INT64_MIN) && defined (INT64_C)\n# define INT64_MIN INT64_C (-9223372036854775808)\n#endif\n#if !defined (UINT64_MAX) && defined (INT64_C)\n# define UINT64_MAX UINT64_C (18446744073709551615)\n#endif\n\n/*\n *  Width of hexadecimal for number field.\n */\n\n#ifndef PRINTF_INT64_HEX_WIDTH\n# define PRINTF_INT64_HEX_WIDTH \"16\"\n#endif\n#ifndef PRINTF_INT32_HEX_WIDTH\n# define PRINTF_INT32_HEX_WIDTH \"8\"\n#endif\n#ifndef PRINTF_INT16_HEX_WIDTH\n# define PRINTF_INT16_HEX_WIDTH \"4\"\n#endif\n#ifndef PRINTF_INT8_HEX_WIDTH\n# define PRINTF_INT8_HEX_WIDTH \"2\"\n#endif\n#ifndef PRINTF_INT64_DEC_WIDTH\n# define PRINTF_INT64_DEC_WIDTH \"19\"\n#endif\n#ifndef PRINTF_INT32_DEC_WIDTH\n# define PRINTF_INT32_DEC_WIDTH \"10\"\n#endif\n#ifndef PRINTF_INT16_DEC_WIDTH\n# define PRINTF_INT16_DEC_WIDTH \"5\"\n#endif\n#ifndef PRINTF_INT8_DEC_WIDTH\n# define PRINTF_INT8_DEC_WIDTH \"3\"\n#endif\n#ifndef PRINTF_UINT64_DEC_WIDTH\n# define PRINTF_UINT64_DEC_WIDTH \"20\"\n#endif\n#ifndef PRINTF_UINT32_DEC_WIDTH\n# define PRINTF_UINT32_DEC_WIDTH \"10\"\n#endif\n#ifndef PRINTF_UINT16_DEC_WIDTH\n# define PRINTF_UINT16_DEC_WIDTH \"5\"\n#endif\n#ifndef PRINTF_UINT8_DEC_WIDTH\n# define PRINTF_UINT8_DEC_WIDTH \"3\"\n#endif\n\n/*\n *  Ok, lets not worry about 128 bit integers for now.  Moore's law says\n *  we don't need to worry about that until about 2040 at which point\n *  we'll have bigger things to worry about.\n */\n\n#ifdef stdint_int64_defined\n  typedef int64_t intmax_t;\n  typedef uint64_t uintmax_t;\n# define  INTMAX_MAX   INT64_MAX\n# define  INTMAX_MIN   INT64_MIN\n# define UINTMAX_MAX  UINT64_MAX\n# define UINTMAX_C(v) UINT64_C(v)\n# define  INTMAX_C(v)  INT64_C(v)\n# ifndef PRINTF_INTMAX_MODIFIER\n#   define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER\n# endif\n# ifndef PRINTF_INTMAX_HEX_WIDTH\n#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH\n# endif\n# ifndef PRINTF_INTMAX_DEC_WIDTH\n#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH\n# endif\n#else\n  typedef int32_t intmax_t;\n  typedef uint32_t uintmax_t;\n# define  INTMAX_MAX   INT32_MAX\n# define UINTMAX_MAX  UINT32_MAX\n# define UINTMAX_C(v) UINT32_C(v)\n# define  INTMAX_C(v)  INT32_C(v)\n# ifndef PRINTF_INTMAX_MODIFIER\n#   define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER\n# endif\n# ifndef PRINTF_INTMAX_HEX_WIDTH\n#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH\n# endif\n# ifndef PRINTF_INTMAX_DEC_WIDTH\n#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH\n# endif\n#endif\n\n/*\n *  Because this file currently only supports platforms which have\n *  precise powers of 2 as bit sizes for the default integers, the\n *  least definitions are all trivial.  Its possible that a future\n *  version of this file could have different definitions.\n */\n\n#ifndef stdint_least_defined\n  typedef   int8_t   int_least8_t;\n  typedef  uint8_t  uint_least8_t;\n  typedef  int16_t  int_least16_t;\n  typedef uint16_t uint_least16_t;\n  typedef  int32_t  int_least32_t;\n  typedef uint32_t uint_least32_t;\n# define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER\n# define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER\n# define  UINT_LEAST8_MAX  UINT8_MAX\n# define   INT_LEAST8_MAX   INT8_MAX\n# define UINT_LEAST16_MAX UINT16_MAX\n# define  INT_LEAST16_MAX  INT16_MAX\n# define UINT_LEAST32_MAX UINT32_MAX\n# define  INT_LEAST32_MAX  INT32_MAX\n# define   INT_LEAST8_MIN   INT8_MIN\n# define  INT_LEAST16_MIN  INT16_MIN\n# define  INT_LEAST32_MIN  INT32_MIN\n# ifdef stdint_int64_defined\n    typedef  int64_t  int_least64_t;\n    typedef uint64_t uint_least64_t;\n#   define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER\n#   define UINT_LEAST64_MAX UINT64_MAX\n#   define  INT_LEAST64_MAX  INT64_MAX\n#   define  INT_LEAST64_MIN  INT64_MIN\n# endif\n#endif\n#undef stdint_least_defined\n\n/*\n *  The ANSI C committee has defined *int*_fast*_t types as well.  This,\n *  of course, defies rationality -- you can't know what will be fast\n *  just from the type itself.  Even for a given architecture, compatible\n *  implementations might have different performance characteristics.\n *  Developers are warned to stay away from these types when using this\n *  or any other stdint.h.\n */\n\ntypedef   int_least8_t   int_fast8_t;\ntypedef  uint_least8_t  uint_fast8_t;\ntypedef  int_least16_t  int_fast16_t;\ntypedef uint_least16_t uint_fast16_t;\ntypedef  int_least32_t  int_fast32_t;\ntypedef uint_least32_t uint_fast32_t;\n#define  UINT_FAST8_MAX  UINT_LEAST8_MAX\n#define   INT_FAST8_MAX   INT_LEAST8_MAX\n#define UINT_FAST16_MAX UINT_LEAST16_MAX\n#define  INT_FAST16_MAX  INT_LEAST16_MAX\n#define UINT_FAST32_MAX UINT_LEAST32_MAX\n#define  INT_FAST32_MAX  INT_LEAST32_MAX\n#define   INT_FAST8_MIN   INT_LEAST8_MIN\n#define  INT_FAST16_MIN  INT_LEAST16_MIN\n#define  INT_FAST32_MIN  INT_LEAST32_MIN\n#ifdef stdint_int64_defined\n  typedef  int_least64_t  int_fast64_t;\n  typedef uint_least64_t uint_fast64_t;\n# define UINT_FAST64_MAX UINT_LEAST64_MAX\n# define  INT_FAST64_MAX  INT_LEAST64_MAX\n# define  INT_FAST64_MIN  INT_LEAST64_MIN\n#endif\n\n#undef stdint_int64_defined\n\n/*\n *  Whatever piecemeal, per compiler thing we can do about the wchar_t\n *  type limits.\n */\n\n#if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) && !defined(vxWorks)\n# include <wchar.h>\n# ifndef WCHAR_MIN\n#  define WCHAR_MIN 0\n# endif\n# ifndef WCHAR_MAX\n#  define WCHAR_MAX ((wchar_t)-1)\n# endif\n#endif\n\n/*\n *  Whatever piecemeal, per compiler/platform thing we can do about the\n *  (u)intptr_t types and limits.\n */\n\n#if (defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED)) || defined (_UINTPTR_T)\n# define STDINT_H_UINTPTR_T_DEFINED\n#endif\n\n#ifndef STDINT_H_UINTPTR_T_DEFINED\n# if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) || defined (__ppc64__)\n#  define stdint_intptr_bits 64\n# elif defined (__WATCOMC__) || defined (__TURBOC__)\n#  if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)\n#    define stdint_intptr_bits 16\n#  else\n#    define stdint_intptr_bits 32\n#  endif\n# elif defined (__i386__) || defined (_WIN32) || defined (WIN32) || defined (__ppc64__)\n#  define stdint_intptr_bits 32\n# elif defined (__INTEL_COMPILER)\n/* TODO -- what did Intel do about x86-64? */\n# else\n/* #error \"This platform might not be supported yet\" */\n# endif\n\n# ifdef stdint_intptr_bits\n#  define stdint_intptr_glue3_i(a,b,c)  a##b##c\n#  define stdint_intptr_glue3(a,b,c)    stdint_intptr_glue3_i(a,b,c)\n#  ifndef PRINTF_INTPTR_MODIFIER\n#    define PRINTF_INTPTR_MODIFIER      stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER)\n#  endif\n#  ifndef PTRDIFF_MAX\n#    define PTRDIFF_MAX                 stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX)\n#  endif\n#  ifndef PTRDIFF_MIN\n#    define PTRDIFF_MIN                 stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN)\n#  endif\n#  ifndef UINTPTR_MAX\n#    define UINTPTR_MAX                 stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX)\n#  endif\n#  ifndef INTPTR_MAX\n#    define INTPTR_MAX                  stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX)\n#  endif\n#  ifndef INTPTR_MIN\n#    define INTPTR_MIN                  stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN)\n#  endif\n#  ifndef INTPTR_C\n#    define INTPTR_C(x)                 stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x)\n#  endif\n#  ifndef UINTPTR_C\n#    define UINTPTR_C(x)                stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x)\n#  endif\n  typedef stdint_intptr_glue3(uint,stdint_intptr_bits,_t) uintptr_t;\n  typedef stdint_intptr_glue3( int,stdint_intptr_bits,_t)  intptr_t;\n# else\n/* TODO -- This following is likely wrong for some platforms, and does\n   nothing for the definition of uintptr_t. */\n  typedef ptrdiff_t intptr_t;\n# endif\n# define STDINT_H_UINTPTR_T_DEFINED\n#endif\n\n/*\n *  Assumes sig_atomic_t is signed and we have a 2s complement machine.\n */\n\n#ifndef SIG_ATOMIC_MAX\n# define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1)\n#endif\n\n#endif\n\n#if defined (__TEST_PSTDINT_FOR_CORRECTNESS)\n\n/*\n *  Please compile with the maximum warning settings to make sure macros are\n *  not defined more than once.\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#define glue3_aux(x,y,z) x ## y ## z\n#define glue3(x,y,z) glue3_aux(x,y,z)\n\n#define DECLU(bits) glue3(uint,bits,_t) glue3(u,bits,) = glue3(UINT,bits,_C) (0);\n#define DECLI(bits) glue3(int,bits,_t) glue3(i,bits,) = glue3(INT,bits,_C) (0);\n\n#define DECL(us,bits) glue3(DECL,us,) (bits)\n\n#define TESTUMAX(bits) glue3(u,bits,) = ~glue3(u,bits,); if (glue3(UINT,bits,_MAX) != glue3(u,bits,)) printf (\"Something wrong with UINT%d_MAX\\n\", bits)\n\n#define REPORTERROR(msg) { err_n++; if (err_first <= 0) err_first = __LINE__; printf msg; }\n\n#define X_SIZE_MAX ((size_t)-1)\n\nint main () {\n\tint err_n = 0;\n\tint err_first = 0;\n\tDECL(I,8)\n\tDECL(U,8)\n\tDECL(I,16)\n\tDECL(U,16)\n\tDECL(I,32)\n\tDECL(U,32)\n#ifdef INT64_MAX\n\tDECL(I,64)\n\tDECL(U,64)\n#endif\n\tintmax_t imax = INTMAX_C(0);\n\tuintmax_t umax = UINTMAX_C(0);\n\tchar str0[256], str1[256];\n\n\tsprintf (str0, \"%\" PRINTF_INT32_MODIFIER \"d\", INT32_C(2147483647));\n\tif (0 != strcmp (str0, \"2147483647\")) REPORTERROR ((\"Something wrong with PRINTF_INT32_MODIFIER : %s\\n\", str0));\n\tif (atoi(PRINTF_INT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR ((\"Something wrong with PRINTF_INT32_DEC_WIDTH : %s\\n\", PRINTF_INT32_DEC_WIDTH));\n\tsprintf (str0, \"%\" PRINTF_INT32_MODIFIER \"u\", UINT32_C(4294967295));\n\tif (0 != strcmp (str0, \"4294967295\")) REPORTERROR ((\"Something wrong with PRINTF_INT32_MODIFIER : %s\\n\", str0));\n\tif (atoi(PRINTF_UINT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR ((\"Something wrong with PRINTF_UINT32_DEC_WIDTH : %s\\n\", PRINTF_UINT32_DEC_WIDTH));\n#ifdef INT64_MAX\n\tsprintf (str1, \"%\" PRINTF_INT64_MODIFIER \"d\", INT64_C(9223372036854775807));\n\tif (0 != strcmp (str1, \"9223372036854775807\")) REPORTERROR ((\"Something wrong with PRINTF_INT32_MODIFIER : %s\\n\", str1));\n\tif (atoi(PRINTF_INT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR ((\"Something wrong with PRINTF_INT64_DEC_WIDTH : %s, %d\\n\", PRINTF_INT64_DEC_WIDTH, (int) strlen(str1)));\n\tsprintf (str1, \"%\" PRINTF_INT64_MODIFIER \"u\", UINT64_C(18446744073709550591));\n\tif (0 != strcmp (str1, \"18446744073709550591\")) REPORTERROR ((\"Something wrong with PRINTF_INT32_MODIFIER : %s\\n\", str1));\n\tif (atoi(PRINTF_UINT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR ((\"Something wrong with PRINTF_UINT64_DEC_WIDTH : %s, %d\\n\", PRINTF_UINT64_DEC_WIDTH, (int) strlen(str1)));\n#endif\n\n\tsprintf (str0, \"%d %x\\n\", 0, ~0);\n\n\tsprintf (str1, \"%d %x\\n\",  i8, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with i8 : %s\\n\", str1));\n\tsprintf (str1, \"%u %x\\n\",  u8, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with u8 : %s\\n\", str1));\n\tsprintf (str1, \"%d %x\\n\",  i16, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with i16 : %s\\n\", str1));\n\tsprintf (str1, \"%u %x\\n\",  u16, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with u16 : %s\\n\", str1));\n\tsprintf (str1, \"%\" PRINTF_INT32_MODIFIER \"d %x\\n\",  i32, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with i32 : %s\\n\", str1));\n\tsprintf (str1, \"%\" PRINTF_INT32_MODIFIER \"u %x\\n\",  u32, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with u32 : %s\\n\", str1));\n#ifdef INT64_MAX\n\tsprintf (str1, \"%\" PRINTF_INT64_MODIFIER \"d %x\\n\",  i64, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with i64 : %s\\n\", str1));\n#endif\n\tsprintf (str1, \"%\" PRINTF_INTMAX_MODIFIER \"d %x\\n\",  imax, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with imax : %s\\n\", str1));\n\tsprintf (str1, \"%\" PRINTF_INTMAX_MODIFIER \"u %x\\n\",  umax, ~0);\n\tif (0 != strcmp (str0, str1)) REPORTERROR ((\"Something wrong with umax : %s\\n\", str1));\n\n\tTESTUMAX(8);\n\tTESTUMAX(16);\n\tTESTUMAX(32);\n#ifdef INT64_MAX\n\tTESTUMAX(64);\n#endif\n\n#define STR(v) #v\n#define Q(v) printf (\"sizeof \" STR(v) \" = %u\\n\", (unsigned) sizeof (v));\n\tif (err_n) {\n\t\tprintf (\"pstdint.h is not correct.  Please use sizes below to correct it:\\n\");\n\t}\n\n\tQ(int)\n\tQ(unsigned)\n\tQ(long int)\n\tQ(short int)\n\tQ(int8_t)\n\tQ(int16_t)\n\tQ(int32_t)\n#ifdef INT64_MAX\n\tQ(int64_t)\n#endif\n\n#if UINT_MAX < X_SIZE_MAX\n\tprintf (\"UINT_MAX < X_SIZE_MAX\\n\");\n#else\n\tprintf (\"UINT_MAX >= X_SIZE_MAX\\n\");\n#endif\n\tprintf (\"%\" PRINTF_INT64_MODIFIER \"u vs %\" PRINTF_INT64_MODIFIER \"u\\n\", UINT_MAX, X_SIZE_MAX);\n\n\treturn EXIT_SUCCESS;\n}\n\n#endif\n"
  },
  {
    "path": "public/tests/test_atoi.c",
    "content": "#include \"crtlib.h\"\n#include \"xash3d_mathlib.h\"\n\nstatic int Test_Atoi( void )\n{\n\tstruct {\n\t\tconst char *str;\n\t\tint result;\n\t} test_data[] = {\n\t\t{ \"\", 0 },\n\t\t{ \"   123\", 123 },\n\t\t{ \"-123\", -123 },\n\t\t{ \"0xa1ba\", 0xa1ba },\n\t\t{ \"-0xa1ba\", -0xa1ba },\n\t\t{ \"0XA1BA\", 0xa1ba },\n\t\t{ \"-0XA1BA\", -0xa1ba },\n\t\t{ \"'a'\", (byte)'a' },\n\t\t{ \"-'c'\", - ((byte)'c') },\n\t};\n\tint i;\n\n\tfor( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ )\n\t{\n\t\tif( Q_atoi( test_data[i].str ) != test_data[i].result )\n\t\t\treturn i + 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int Test_Atof( void )\n{\n\tstruct {\n\t\tconst char *str;\n\t\tfloat result;\n\t} test_data[] = {\n\t\t{ \"\", 0 },\n\t\t{ \"   123.123\", 123.123 },\n\t\t{ \"-123.13   \", -123.13 },\n\t\t{ \"0xa1ba\", 0xa1ba },\n\t\t{ \"-0xa1ba\", -0xa1ba },\n\t\t{ \"0XA1BA\", 0xa1ba },\n\t\t{ \"-0XA1BA\", -0xa1ba },\n\t\t{ \"'a'\", (byte)'a' },\n\t\t{ \"-'c'\", - ((byte)'c') }\n\t};\n\tint i;\n\n\tfor( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ )\n\t{\n\t\tfloat result = Q_atof( test_data[i].str );\n\n\t\tif( !Q_equal( result, test_data[i].result ))\n\t\t\treturn i + 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int Test_Atov( void )\n{\n\tstruct {\n\t\tconst char *str;\n\t\tint N;\n\t\tfloat result[3];\n\t} test_data[] = {\n\t\t{ \"1.0 1.2 3\", 3, { 1.0f, 1.2f, 3.0f }},\n\t\t{ \"1.234 1.32\", 2, { 1.234f, 1.32f }},\n\t};\n\tint i;\n\n\tfor( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ )\n\t{\n\t\tfloat result[3];\n\t\tint j;\n\n\t\tmemset( result, 0, sizeof( result ));\n\t\tQ_atov( result, test_data[i].str, test_data[i].N );\n\n\t\tfor( j = 0; j < 3; j++ ) // check that Q_atov didn't parsed more than requested\n\t\t{\n\t\t\tif( !Q_equal( result[j], test_data[i].result[j] ))\n\t\t\t\treturn i * 4 + j + 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint main( void )\n{\n\tint ret = Test_Atoi();\n\n\tif( ret > 0 )\n\t\treturn ret;\n\n\tret = Test_Atof();\n\n\tif( ret > 0 )\n\t\treturn ret + 32;\n\n\tret = Test_Atov();\n\n\tif( ret > 0 )\n\t\treturn ret + 64;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "public/tests/test_build.c",
    "content": "#include <stdlib.h>\n#include \"build.h\"\n#include \"buildenums.h\"\n#include \"crtlib.h\"\n\n// THESE DEFINITIONS MUST NEVER CHANGE\nstatic struct\n{\n\tint id;\n\tconst char *string;\n} expected_platforms[] =\n{\n{ PLATFORM_WIN32,         \"win32\" },\n{ PLATFORM_ANDROID,       \"android\" },\n{ PLATFORM_LINUX,         \"linux\" },\n{ PLATFORM_APPLE,         \"apple\" },\n{ PLATFORM_FREEBSD,       \"freebsd\" },\n{ PLATFORM_NETBSD,        \"netbsd\" },\n{ PLATFORM_OPENBSD,       \"openbsd\" },\n{ PLATFORM_EMSCRIPTEN,    \"emscripten\" },\n{ PLATFORM_DOS4GW,        \"DOS4GW\" },\n{ PLATFORM_HAIKU,         \"haiku\" },\n{ PLATFORM_SERENITY,      \"serenity\" },\n{ PLATFORM_IRIX,          \"irix\" },\n{ PLATFORM_NSWITCH,       \"nswitch\" },\n{ PLATFORM_PSVITA,        \"psvita\" },\n{ PLATFORM_WASI,          \"wasi\" },\n{ PLATFORM_HURD,          \"hurd\" },\n};\n\nstatic struct\n{\n\tint id;\n\tint abi;\n\tint endianness;\n\tint is64;\n\tconst char *string;\n} expected_architectures[] =\n{\n{ ARCHITECTURE_AMD64, 0, -1, -1, \"amd64\" },\n{ ARCHITECTURE_X86, 0, -1, -1, \"i386\" },\n{ ARCHITECTURE_E2K, 0, -1, -1, \"e2k\" },\n{ ARCHITECTURE_JS, 0, -1, -1, \"javascript\" },\n\n// all possible WebAssembly names\n{ ARCHITECTURE_WASM, 0, -1, true, \"wasm64\" },\n{ ARCHITECTURE_WASM, 0, -1, false, \"wasm32\" },\n\n// all possible MIPS names\n{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG,    true,  \"mips64\" },\n{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG,    false, \"mips\" },\n{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, true,  \"mips64el\" },\n{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, false, \"mipsel\" },\n\n// all possible PowerPC names\n{ ARCHITECTURE_PPC, 0, ENDIANNESS_BIG,    true,  \"ppc64\" },\n{ ARCHITECTURE_PPC, 0, ENDIANNESS_BIG,    false, \"ppc\" },\n{ ARCHITECTURE_PPC, 0, ENDIANNESS_LITTLE, true,  \"ppc64el\" },\n{ ARCHITECTURE_PPC, 0, ENDIANNESS_LITTLE, false, \"ppcel\" },\n\n// All ARM is little endian only (for now?)\n// Arm64 is always arm64, no matter the version (for now)\n// Arm64 don't care about hardfp bit\n{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, \"arm64\" },\n{ ARCHITECTURE_ARM, 8,                   ENDIANNESS_LITTLE, true, \"arm64\" },\n{ ARCHITECTURE_ARM, 0 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, \"arm64\" },\n{ ARCHITECTURE_ARM, 0,                   ENDIANNESS_LITTLE, true, \"arm64\" },\n\n// ARMv6 and below don't care about hardfp bit\n{ ARCHITECTURE_ARM, 4 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv4l\" },\n{ ARCHITECTURE_ARM, 4,                   ENDIANNESS_LITTLE, false, \"armv4l\" },\n{ ARCHITECTURE_ARM, 5 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv5l\" },\n{ ARCHITECTURE_ARM, 5,                   ENDIANNESS_LITTLE, false, \"armv5l\" },\n{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv6l\" },\n{ ARCHITECTURE_ARM, 6,                   ENDIANNESS_LITTLE, false, \"armv6l\" },\n{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv6l\" },\n{ ARCHITECTURE_ARM, 6,                   ENDIANNESS_LITTLE, false, \"armv6l\" },\n\n// ARMv7 and ARMv8 in 32-bit mode, hardfp bit is important\n{ ARCHITECTURE_ARM, 7 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv7hf\" },\n{ ARCHITECTURE_ARM, 7,                   ENDIANNESS_LITTLE, false, \"armv7l\" },\n{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, \"armv8_32hf\" },\n{ ARCHITECTURE_ARM, 8,                   ENDIANNESS_LITTLE, false, \"armv8_32l\" },\n\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT,   -1, true,  \"riscv64\" },\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT,   -1, false, \"riscv32\" },\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, true,  \"riscv64f\" },\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, false, \"riscv32f\" },\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, true,  \"riscv64d\" },\n{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, false, \"riscv32d\" },\n};\n\nstatic int TestPlatformString( void )\n{\n\tint i;\n\n\tfor( i = 0; i < sizeof( expected_platforms ) / sizeof( expected_platforms[0] ); i++ )\n\t{\n\t\tif( Q_strcmp( Q_PlatformStringByID( expected_platforms[i].id ), expected_platforms[i].string ))\n\t\t\treturn i + 1;\n\t}\n\n\treturn 0;\n}\n\nstatic qboolean TestArchitectureString_NoEndianness( int id, int abi, int is64, const char *string )\n{\n\treturn !Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_LITTLE, is64 ), string ) &&\n\t\t!Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_BIG, is64 ), string );\n}\n\nstatic qboolean TestArchitectureString_No64( int id, int abi, int endianness, const char *string )\n{\n\tif( endianness == -1 )\n\t{\n\t\treturn TestArchitectureString_NoEndianness( id, abi, false, string ) &&\n\t\t\tTestArchitectureString_NoEndianness( id, abi, true, string );\n\t}\n\n\treturn !Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, true ), string ) &&\n\t\t!Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, false ), string );\n}\n\nstatic int TestArchitectureString( void )\n{\n\tint i;\n\n\tfor( i = 0; i < sizeof( expected_architectures ) / sizeof( expected_architectures[0] ); i++ )\n\t{\n\t\tif( expected_architectures[i].is64 == -1 )\n\t\t{\n\t\t\tif( !TestArchitectureString_No64(\n\t\t\t\texpected_architectures[i].id,\n\t\t\t\texpected_architectures[i].abi,\n\t\t\t\texpected_architectures[i].endianness,\n\t\t\t\texpected_architectures[i].string ))\n\t\t\t\treturn i + 1;\n\t\t}\n\t\telse if( expected_architectures[i].endianness == -1 )\n\t\t{\n\t\t\tif( !TestArchitectureString_NoEndianness(\n\t\t\t\texpected_architectures[i].id,\n\t\t\t\texpected_architectures[i].abi,\n\t\t\t\texpected_architectures[i].is64,\n\t\t\t\texpected_architectures[i].string ))\n\t\t\t{\n\t\t\t\tabort();\n\t\t\t\treturn i + 1;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( Q_strcmp( Q_ArchitectureStringByID(\n\t\t\t\texpected_architectures[i].id,\n\t\t\t\texpected_architectures[i].abi,\n\t\t\t\texpected_architectures[i].endianness,\n\t\t\t\texpected_architectures[i].is64\n\t\t\t), expected_architectures[i].string ))\n\t\t\t\treturn i + 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint main( void )\n{\n\tint res;\n\n\tres = TestPlatformString();\n\tif( res != 0 )\n\t\treturn res;\n\n\tres = TestArchitectureString();\n\tif( res != 0 )\n\t\treturn res + 100;\n\n\tif( Q_buildnum_compat() != 4529 )\n\t\treturn 200;\n\n\tif( Q_buildnum_iso( \"2015-04-02 21:19:10 +0300\" ) != 1 )\n\t\treturn 202;\n\n\tif( Q_buildnum_iso( \"2023-04-17 21:19:10 +0300\" ) != 2938 )\n\t\treturn 204;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "public/tests/test_efp.c",
    "content": "#include <stdlib.h>\n#include \"crtlib.h\"\n#include <stdio.h>\n\nstatic int Test_ExtractFilePath( void )\n{\n\tchar dst[64];\n\tconst char *strings[] =\n\t{\n\t\t\"dir/file\", \"dir\",\n\t\t\"bark\\\\meow\", \"bark\",\n\t\t\"nopath\", \"\",\n\t\t\"knee/deep/in/paths\", \"knee/deep/in\",\n\t\t// yes, it removes the behavior/ even if it might be technically a directory\n\t\t\"keep/the/original/func/behavior/\", \"keep/the/original/func\",\n\t\t\"backslashes\\\\are\\\\annoying\\\\af\", \"backslashes\\\\are\\\\annoying\",\n\t\t\"\", \"\"\n\t};\n\tsize_t i;\n\n\tfor( i = 0; i < sizeof( strings ) / sizeof( strings[0] ); i += 2 )\n\t{\n\t\tCOM_ExtractFilePath( strings[i], dst );\n\t\tif( Q_strcmp( dst, strings[i+1] ))\n\t\t{\n\t\t\tprintf( \"%s %s %s\\n\", strings[i], strings[i+1], dst );\n\t\t\treturn (i >> 1) + 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint main( void )\n{\n\tif( Test_ExtractFilePath( ))\n\t\treturn EXIT_FAILURE;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "public/tests/test_filebase.c",
    "content": "#include <stdlib.h>\n#include <stdio.h>\n#include \"xash3d_mathlib.h\"\n#include \"crtlib.h\"\n\nstatic void COM_FileBase_old( const char *in, char *out, size_t size )\n{\n\tint\tlen, start, end;\n\n\tlen = Q_strlen( in );\n\tif( !len || !size ) return;\n\n\t// scan backward for '.'\n\tend = len - 1;\n\n\twhile( end && in[end] != '.' && in[end] != '/' && in[end] != '\\\\' )\n\t\tend--;\n\n\tif( in[end] != '.' )\n\t\tend = len-1; // no '.', copy to end\n\telse end--; // found ',', copy to left of '.'\n\n\t// scan backward for '/'\n\tstart = len - 1;\n\n\twhile( start >= 0 && in[start] != '/' && in[start] != '\\\\' )\n\t\tstart--;\n\n\tif( start < 0 || ( in[start] != '/' && in[start] != '\\\\' ))\n\t\tstart = 0;\n\telse start++;\n\n\t// length of new sting\n\tlen = end - start + 1;\n\n\tif( size > len ) // patched to somewhat support size limit\n\t{\n\t\t// Copy partial string\n\t\tQ_strncpy( out, &in[start], len + 1 );\n\t\tout[len] = 0;\n\t}\n\telse\n\t{\n\t\tQ_strncpy( out, &in[start], size );\n\t}\n}\n\n#define COM_FILEBASE_CHECK( in, out, size, expected ) \\\n\tCOM_FileBase( in, out, size );     \\\n\tif( Q_strcmp( out, expected ))     \\\n\t{                                  \\\n\t\tprintf( \"%d: fail with libpublic impl, got: %s, expected: %s\\n\", __LINE__, out, expected ); \\\n\t\treturn 1;                      \\\n\t}                                  \\\n\tCOM_FileBase_old( in, out, size ); \\\n\tif( Q_strcmp( out, expected ))     \\\n\t{                                  \\\n\t\tprintf( \"%d: fail with old impl, got: %s, expected: %s\\n\", __LINE__, out, expected ); \\\n\t\treturn 2;                      \\\n\t}\n\nstatic int Test_FileBase( void )\n{\n\tstring s;\n\n\t// test slashless case\n\tCOM_FILEBASE_CHECK( \"asdf\", s, sizeof( s ), \"asdf\" );\n\n\t// test slashless case with extension\n\tCOM_FILEBASE_CHECK( \"sdf.wad\", s, sizeof( s ), \"sdf\" );\n\n\t// test case with one slash\n\tCOM_FILEBASE_CHECK( \"zxcv/asdfqwer\", s, sizeof( s ), \"asdfqwer\" );\n\n\t// test case with multiple slashes\n\tCOM_FILEBASE_CHECK( \"zxc/asd/qwert\", s, sizeof( s ), \"qwert\" );\n\n\t// test case with full path\n\tCOM_FILEBASE_CHECK( \"zxc/asd/qvert.lkjefgkljh\", s, sizeof( s ), \"qvert\" );\n\n\t// test case where dot placed before last slash\n\tCOM_FILEBASE_CHECK( \"pak0.pk3/texture\", s, sizeof( s ), \"texture\" );\n\n\t// test case of directory path\n\tCOM_FILEBASE_CHECK( \"pak0.pk3/\", s, sizeof( s ), \"\" );\n\n\t// test case of file with no name\n\tCOM_FILEBASE_CHECK( \"blep/.nomedia\", s, sizeof( s ), \"\" );\n\n\t// test idiot cases\n\tCOM_FILEBASE_CHECK( NULL, s, sizeof( s ), \"\" );\n\tCOM_FILEBASE_CHECK( \"\", s, sizeof( s ), \"\" );\n\tCOM_FILEBASE_CHECK( \"jhnwrgkujihrgwfikouj\", s, 0, \"\" );\n\n\t// test length limit\n\tCOM_FILEBASE_CHECK( \"qwertyuiop\", s, 3, \"qw\" );\n\tCOM_FILEBASE_CHECK( \"qwertyuiop\", s, 1, \"\" );\n\n\treturn 0;\n}\n\nint main( void )\n{\n\tint ret;\n\n\tret = Test_FileBase();\n\n\tif( ret )\n\t\treturn EXIT_FAILURE;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "public/tests/test_fileext.c",
    "content": "#include \"crtlib.h\"\n\nint main( int argc, char **argv )\n{\n\tint i;\n\tconst char *strings[] =\n\t{\n\t\t\"test.txt\", \"txt\",\n\t\t\"path/to/file.wad\", \"wad\",\n\t\t\"noext\", \"\",\n\t\t\"dir/\", \"\",\n\t\t\"dir.pk3dir/\", \"\",\n\t\t\"https.proto://is_this_an_url?\", \"\",\n\t\t\"inside.wad/AAATRIGGER\", \"\",\n\t\t\"c:/games/gamedir/liblist.cum\", \"cum\",\n\t};\n\n\tfor( i = 0; i < sizeof( strings ) / sizeof( strings[0] ); i += 2 )\n\t{\n\t\tif( Q_strcmp( COM_FileExtension( strings[i] ), strings[i + 1] ))\n\t\t\treturn i;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "public/tests/test_parsefile.c",
    "content": "#include \"crtlib.h\"\n\nstatic const char *test_file =\n\"q asdf \\\"qwerty\\\" \\\"f \\\\\\\"f\\\" meowmeow\\n\"\n\"// comment \\\"stuff ignored\\\"\\n\"\n\"bark // ignore\\n\"\n\"bashlikecomment\t#notignored\ttest\\n\"\n\"#ignore comment\\n\"\n\"thisshall #be ignored\\n\"\n\"test_sentinel\\n\";\n\nint main( void )\n{\n\tint i;\n\tchar *file = (char *)test_file;\n\tstruct test\n\t{\n\t\tint bufsize;\n\t\tconst char *expected;\n\t\tint expected_len;\n\t\tint flags;\n\t} testdata[] =\n\t{\n\t{ 5, \"q\", 1 },\n\t{ 5, \"asdf\", 4 },\n\t{ 5, \"qwer\", -1 },\n\t{ 5, \"f \\\"f\", 4 },\n\t{ 5, \"meow\", -1 },\n\t{ 5, \"bark\", 4 },\n\t{ 32, \"bashlikecomment\", 15 },\n\t{ 32, \"#notignored\", 11 },\n\t{ 32, \"test\", 4, PFILE_IGNOREHASHCMT },\n\t{ 32, \"thisshall\", 9, PFILE_IGNOREHASHCMT },\n\t{ 32, \"test_sentinel\", 13, PFILE_IGNOREHASHCMT },\n\t};\n\n\tfor( i = 0; i < sizeof( testdata ) / sizeof( testdata[0] ); i++ )\n\t{\n\t\tstring buf;\n\t\tint len;\n\n\t\tfile = COM_ParseFileSafe( file, buf, testdata[i].bufsize, testdata[i].flags, &len, NULL );\n\n\t\tif( file == NULL )\n\t\t\treturn i;\n\n\t\tif( !( !Q_strcmp( buf, testdata[i].expected ) && len == testdata[i].expected_len ))\n\t\t\treturn i;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "public/tests/test_strings.c",
    "content": "#include \"crtlib.h\"\n\nstatic int Test_Strcpycatcmp( void )\n{\n\tchar buf[] = \"meowmeowmeow\", buf2[] = \"barkbark\";\n\tchar dst[64];\n\n\tif( Q_strncpy( dst, buf, sizeof( dst )) != sizeof( buf ) - 1 )\n\t\treturn 1;\n\n\tif( Q_strcmp( dst, buf ))\n\t\treturn 2;\n\n\tif( Q_strcmp( dst, buf ))\n\t\treturn 4;\n\n\tif( !Q_strcmp( dst, buf2 ))\n\t\treturn 5;\n\n\tif( Q_strncat( dst, buf2, sizeof( dst )) != sizeof( buf ) + sizeof( buf2 ) - 2 )\n\t\treturn 6;\n\n\tif( Q_strcmp( dst, \"meowmeowmeowbarkbark\" ))\n\t\treturn 7;\n\n\tif( Q_strncmp( dst, buf, sizeof( buf ) - 1 ))\n\t\treturn 8;\n\n\treturn 0;\n}\n\nstatic int Test_Strnlwr( void )\n{\n\tstring s;\n\n\tQ_strnlwr( \"ASDFGKJ\", s, sizeof( s ));\n\n\tif( Q_strcmp( s, \"asdfgkj\" ))\n\t\treturn 1;\n\n\tQ_strnlwr( \"qwertyuiop\", s, sizeof( s ));\n\n\tif( Q_strcmp( s, \"qwertyuiop\" ))\n\t\treturn 2;\n\n\treturn 0;\n}\n\nstatic int Test_FixSlashes( void )\n{\n\tstring s = \"path\\\\with\\\\back\\\\slashes\";\n\tstring s2 = \"path/with/fwd/slashes\";\n\tstring s3 = \"path\\\\with/mixed\\\\slashes\";\n\n\tCOM_FixSlashes( s );\n\n\tif( Q_strcmp( s, \"path/with/back/slashes\" ))\n\t\treturn 1;\n\n\tCOM_FixSlashes( s2 );\n\n\tif( Q_strcmp( s2, \"path/with/fwd/slashes\" ))\n\t\treturn 2;\n\n\tCOM_FixSlashes( s3 );\n\n\tif( Q_strcmp( s3, \"path/with/mixed/slashes\" ))\n\t\treturn 3;\n\n\treturn 0;\n}\n\nint main( void )\n{\n\tint ret = Test_Strcpycatcmp();\n\n\tif( ret > 0 )\n\t\treturn ret;\n\n\tret = Test_Strnlwr();\n\n\tif( ret > 0 )\n\t\treturn ret + 16;\n\n\tret = Test_FixSlashes();\n\n\tif( ret > 0 )\n\t\treturn ret + 48;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "public/tests/test_validate_target.c",
    "content": "#include <stdlib.h>\n#include \"build.h\"\n#include \"buildenums.h\"\n#include \"crtlib.h\"\n\n#ifndef VALIDATE_TARGET\n#error\n#endif\n\nint main( void )\n{\n\tstring buf;\n\n\tif( Q_snprintf( buf, sizeof( buf ), \"%s-%s\", Q_buildos( ), Q_buildarch( )) < 0 )\n\t\treturn 1;\n\n\tif( Q_strcmp( buf, VALIDATE_TARGET ) != 0 )\n\t\treturn 2;\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "public/utflib.c",
    "content": "/*\nutflib.c - small unicode conversion library\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"utflib.h\"\n#include \"xash3d_types.h\"\n\nuint32_t Q_DecodeUTF8( utfstate_t *s, uint32_t in )\n{\n\t// get character length\n\tif( s->len == 0 )\n\t{\n\t\t// init state\n\t\ts->uc = 0;\n\n\t\t// expect ASCII symbols by default\n\t\tif( likely( in <= 0x7fu ))\n\t\t\treturn in;\n\n\t\t// invalid sequence\n\t\tif( unlikely( in >= 0xf8u ))\n\t\t\treturn 0;\n\n\t\ts->k = 0;\n\n\t\tif( in >= 0xf0u )\n\t\t{\n\t\t\ts->uc = in & 0x07u;\n\t\t\ts->len = 3;\n\t\t}\n\t\telse if( in >= 0xe0u )\n\t\t{\n\t\t\ts->uc = in & 0x0fu;\n\t\t\ts->len = 2;\n\t\t}\n\t\telse if( in >= 0xc0u )\n\t\t{\n\t\t\ts->uc = in & 0x1fu;\n\t\t\ts->len = 1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\t// invalid sequence, reset\n\tif( unlikely( in > 0xbfu ))\n\t{\n\t\ts->len = 0;\n\t\treturn 0;\n\t}\n\n\ts->uc <<= 6;\n\ts->uc += in & 0x3fu;\n\ts->k++;\n\n\t// sequence complete, reset and return code point\n\tif( likely( s->k == s->len ))\n\t{\n\t\ts->len = 0;\n\t\treturn s->uc;\n\t}\n\n\t// feed more characters\n\treturn 0;\n}\n\nuint32_t Q_DecodeUTF16( utfstate_t *s, uint32_t in )\n{\n\t// get character length\n\tif( s->len == 0 )\n\t{\n\t\t// init state\n\t\ts->uc = 0;\n\n\t\t// expect simple case, after all decoding UTF-16 must be easy\n\t\tif( likely( in < 0xd800u || in > 0xdfffu ))\n\t\t\treturn in;\n\n\t\ts->uc = (( in - 0xd800u ) << 10 ) + 0x10000u;\n\t\ts->len = 1;\n\t\ts->k = 0;\n\n\t\treturn 0;\n\t}\n\n\t// invalid sequence, reset\n\tif( unlikely( in < 0xdc00u || in > 0xdfffu ))\n\t{\n\t\ts->len = 0;\n\t\treturn 0;\n\t}\n\n\ts->uc |= in - 0xdc00u;\n\ts->k++;\n\n\t// sequence complete, reset and return code point\n\tif( likely( s->k == s->len ))\n\t{\n\t\ts->len = 0;\n\t\treturn s->uc;\n\t}\n\n\t// feed more characters (should never happen with UTF-16)\n\treturn 0;\n}\n\nstatic size_t Q_CodepointLength( uint32_t ch )\n{\n\tif( ch <= 0x7fu )\n\t\treturn 1;\n\telse if( ch <= 0x7ffu )\n\t\treturn 2;\n\telse if( ch <= 0xffffu )\n\t\treturn 3;\n\n\treturn 4;\n}\n\nsize_t Q_EncodeUTF8( char dst[4], uint32_t ch )\n{\n\tswitch( Q_CodepointLength( ch ))\n\t{\n\tcase 1:\n\t\tdst[0] = ch;\n\t\treturn 1;\n\tcase 2:\n\t\tdst[0] = 0xc0u | (( ch >> 6 ) & 0x1fu );\n\t\tdst[1] = 0x80u | (( ch ) & 0x3fu );\n\t\treturn 2;\n\tcase 3:\n\t\tdst[0] = 0xe0u | (( ch >> 12 ) & 0x0fu );\n\t\tdst[1] = 0x80u | (( ch >> 6 ) & 0x3fu );\n\t\tdst[2] = 0x80u | (( ch ) & 0x3fu );\n\t\treturn 3;\n\t}\n\tdst[0] = 0xf0u | (( ch >> 18 ) & 0x07u );\n\tdst[1] = 0x80u | (( ch >> 12 ) & 0x3fu );\n\tdst[2] = 0x80u | (( ch >> 6 ) & 0x3fu );\n\tdst[3] = 0x80u | (( ch ) & 0x3fu );\n\treturn 4;\n}\n\nsize_t Q_UTF8Length( const char *s )\n{\n\tsize_t len = 0;\n\tutfstate_t state = { 0 };\n\n\tif( !s )\n\t\treturn 0;\n\n\tfor( ; *s; s++ )\n\t{\n\t\tuint32_t ch = Q_DecodeUTF8( &state, (uint32_t)*s );\n\n\t\tif( ch == 0 )\n\t\t\tcontinue;\n\n\t\tlen++;\n\t}\n\n\treturn len;\n}\n\nsize_t Q_UTF16ToUTF8( char *dst, size_t dstsize, const uint16_t *src, size_t srcsize )\n{\n\tutfstate_t state = { 0 };\n\tsize_t dsti = 0, srci;\n\n\tif( !dst || !src || !dstsize || !srcsize )\n\t\treturn 0;\n\n\tfor( srci = 0; srci < srcsize && src[srci]; srci++ )\n\t{\n\t\tuint32_t ch;\n\t\tsize_t len;\n\n\t\tch = Q_DecodeUTF16( &state, src[srci] );\n\n\t\tif( ch == 0 )\n\t\t\tcontinue;\n\n\t\tlen = Q_CodepointLength( ch );\n\n\t\tif( dsti + len + 1 > dstsize )\n\t\t\tbreak;\n\n\t\tdsti += Q_EncodeUTF8( &dst[dsti], ch );\n\t}\n\n\tdst[dsti] = 0;\n\n\treturn dsti;\n}\n\nstatic uint16_t table_cp1251[64] = {\n\t0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021,\n\t0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,\n\t0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,\n\t0x007F, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,\n\t0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,\n\t0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,\n\t0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7,\n\t0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457\n};\n\nuint32_t Q_UnicodeToCP1251( uint32_t uc )\n{\n\tsize_t i;\n\n\tif( uc < 0x80 )\n\t\treturn uc;\n\n\tif( uc >= 0x0410 && uc <= 0x042F )\n\t\treturn uc - 0x410 + 0xC0;\n\n\tif( uc >= 0x0430 && uc <= 0x044F )\n\t\treturn uc - 0x430 + 0xE0;\n\n\tfor( i = 0; i < sizeof( table_cp1251 ) / sizeof( table_cp1251[0] ); i++ )\n\t{\n\t\tif( uc == (uint32_t)table_cp1251[i] )\n\t\t\treturn i + 0x80;\n\t}\n\n\treturn '?';\n}\n\nuint32_t Q_UnicodeToCP1252( uint32_t uc )\n{\n\t// this is NOT valid way to convert Unicode codepoint back to CP1252!!!\n\treturn uc < 0xFF ? uc : '?';\n}\n"
  },
  {
    "path": "public/utflib.h",
    "content": "/*\nutflib.h - small unicode conversion library\nCopyright (C) 2024 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#ifndef UTFLIB_H\n#define UTFLIB_H\n\n#include STDINT_H\n#include <stddef.h>\n\ntypedef struct utfstate_s\n{\n\tuint32_t uc;\n\tuint8_t len;\n\tuint8_t k;\n} utfstate_t;\n\n// feed utf8 characters one by one\n// if it returns 0, feed more\n// utfstate_t must be zero initialized\nuint32_t Q_DecodeUTF8( utfstate_t *s, uint32_t ch );\nuint32_t Q_DecodeUTF16( utfstate_t *s, uint32_t ch );\nsize_t Q_EncodeUTF8( char dst[4], uint32_t ch );\n\nsize_t Q_UTF8Length( const char *s );\n\n// srcsize in byte pairs\nsize_t Q_UTF16ToUTF8( char *dst, size_t dstsize, const uint16_t *src, size_t srcsize );\n\n// function to convert Unicode codepoints into CP1251 or CP1252\nuint32_t Q_UnicodeToCP1251( uint32_t uc );\nuint32_t Q_UnicodeToCP1252( uint32_t uc );\n\n#endif // UTFLIB_H\n"
  },
  {
    "path": "public/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib import Logs, Configure\nfrom waflib.extras import gitversion\nimport os\n\ntop = '.'\n\nTGMATH_H_TEST = '''#include <tgmath.h>\nconst float val = 2, val2 = 3;\nint main(void){ return (int)(-asin(val) + cos(val2)); }'''\n\nSTRNCASECMP_TEST = '''#include <string.h>\nint main(int argc, char **argv) { return strncasecmp(argv[1], argv[2], 10); }'''\n\nSTRCASECMP_TEST = '''#include <string.h>\nint main(int argc, char **argv) { return strcasecmp(argv[1], argv[2]); }'''\n\nSTRCASESTR_TEST = '''#include <string.h>\nint main(int argc, char **argv) { argv[0] = strcasestr(argv[1], argv[2]); return 0; }'''\n\nSTRCHRNUL_TEST = '''#include <string.h>\nint main(int argc, char **argv) { argv[2] = strchrnul(argv[1], 'x'); return 0; }'''\n\nSTRLCPY_TEST = '''#include <string.h>\nint main(int argc, char **argv) { return strlcpy(argv[1], argv[2], 10); }'''\n\nSTRLCAT_TEST = '''#include <string.h>\nint main(int argc, char **argv) { return strlcat(argv[1], argv[2], 10); }'''\n\nSTRNLEN_TEST = '''#include <string.h>\nint main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }\n'''\n\nALLOCA_TEST = '''#include <%s>\nint main(void) { alloca(1); return 0; }'''\n\nHEADER_TEST = '''#include <%s>\nint main(void) { return 0; }\n'''\n\ndef options(opt):\n\t# stub\n\treturn\n\ndef header_frag(header_name):\n\treturn '#include <%s>' % header_name + EMPTY_PROGRAM\n\n@Configure.conf\ndef export_define(conf, define, value=1):\n\tif not value:\n\t\treturn\n\tif value is True:\n\t\tvalue = 1 # so python won't define it as string True\n\n\tconf.env.EXPORT_DEFINES_LIST += ['%s=%s' % (define, value)]\n\n@Configure.conf\ndef simple_check(conf, fragment, msg, mandatory=False, **kw):\n\treturn conf.check_cc(fragment=fragment, msg='Checking for %s' % msg, mandatory=mandatory, **kw)\n\n@Configure.conf\ndef get_git_commit_date(conf):\n\tnode = conf.srcnode.find_node('.git')\n\n\tif not node:\n\t\tLogs.debug('can\\'t find .git in conf.srcnode')\n\t\treturn None\n\n\treturn gitversion.run_git(conf, ['log', '-1', '--format=%ci'])\n\ndef options(opt):\n\topt.add_option('--validate-target', action='store', dest='VALIDATE_TARGET', default=None,\n\t\thelp='development option, needs --enable-tests flag')\n\ndef configure(conf):\n\t# private to libpublic\n\tconf.load('gitversion')\n\n\tconf.start_msg('Git commit date')\n\tconf.env.GIT_COMMIT_DATE = conf.get_git_commit_date()\n\tconf.end_msg(conf.env.GIT_COMMIT_DATE)\n\n\tconf.env.VALIDATE_TARGET = conf.options.VALIDATE_TARGET\n\n\t# need to expose it for everyone using libpublic headers\n\tconf.env.EXPORT_DEFINES_LIST = []\n\tconf.export_define('_CRT_SILENCE_NONCONFORMING_TGMATH_H', conf.env.COMPILER_CC == 'msvc')\n\tconf.export_define('_GNU_SOURCE', conf.env.DEST_OS != 'win32')\n\n\t# create temporary uselib that just enables extensions\n\tconf.env.DEFINES_export = list(conf.env.EXPORT_DEFINES_LIST)\n\n\t# check platform-specific header name for alloca(3)\n\tif conf.env.DEST_OS == 'win32':\n\t\t# don't waste time on Win32, it's documented to be always in malloc.h.\n\t\t# (plus test can fail because alloca is underscore-prefixed, e.g. _alloca)\n\t\tconf.export_define('ALLOCA_H', '<malloc.h>')\n\telif conf.simple_check(ALLOCA_TEST % 'alloca.h', msg='alloca in alloca.h'):\n\t\tconf.export_define('ALLOCA_H', '<alloca.h>')\n\telif conf.simple_check(ALLOCA_TEST % 'malloc.h', msg='alloca in malloc.h'):\n\t\tconf.export_define('ALLOCA_H', '<malloc.h>')\n\telif conf.simple_check(ALLOCA_TEST % 'stdlib.h', msg='alloca in stdlib.h'):\n\t\tconf.export_define('ALLOCA_H', '<stdlib.h>')\n\n\tconf.export_define('STDINT_H', '<stdint.h>' if conf.simple_check(HEADER_TEST % 'stdint.h', 'stdint.h') else '<pstdint.h>')\n\n\t# save some time on Windows, msvc is too slow\n\t# these calls must be available with both msvc and mingw\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.export_define('HAVE_TGMATH_H', conf.simple_check(TGMATH_H_TEST, 'tgmath.h', use='M werror export'))\n\t\tconf.export_define('HAVE_STRNICMP')\n\t\tconf.export_define('HAVE_STRICMP')\n\telse:\n\t\texports_list = []\n\n\t\tdef check_libc_extension(frag, msg, define):\n\t\t\texports_list.append(define)\n\t\t\treturn {'fragment': frag, 'msg': 'Checking for ' + msg, 'define_name': define, 'mandatory': False, 'use': 'M werror export', 'compiler': 'c'}\n\n\t\tconf.multicheck(\n\t\t\tcheck_libc_extension(TGMATH_H_TEST, 'tgmath.h', 'HAVE_TGMATH_H'),\n\t\t\tcheck_libc_extension(STRNCASECMP_TEST, 'strncasecmp', 'HAVE_STRNCASECMP'),\n\t\t\tcheck_libc_extension(STRCASECMP_TEST, 'strcasecmp', 'HAVE_STRCASECMP'),\n\t\t\tcheck_libc_extension(STRCASESTR_TEST, 'strcasestr', 'HAVE_STRCASESTR'),\n\t\t\tcheck_libc_extension(STRCHRNUL_TEST, 'strchrnul', 'HAVE_STRCHRNUL'),\n\t\t\tcheck_libc_extension(STRLCPY_TEST, 'strlcpy', 'HAVE_STRLCPY'),\n\t\t\tcheck_libc_extension(STRLCAT_TEST, 'strlcat', 'HAVE_STRLCAT'),\n\t\t\tcheck_libc_extension(STRNLEN_TEST, 'strnlen', 'HAVE_STRNLEN'),\n\t\t)\n\n\t\tfor export in exports_list:\n\t\t\tif conf.env[export]:\n\t\t\t\tconf.export_define(export)\n\n\t# kill temporary uselib\n\tdel conf.env.DEFINES_export\n\ndef build(bld):\n\tbld(name = 'sdk_includes',\n\t\texport_includes = '. ../common ../pm_shared ../engine',\n\t\texport_defines = bld.env.EXPORT_DEFINES_LIST)\n\n\t# build it separately to slightly improve rebuild times\n\tbld.stlib(source = 'build_vcs.c',\n\t\ttarget = 'build_vcs',\n\t\tdefines = ['XASH_BUILD_COMMIT=\\\"%s\\\"' % bld.env.GIT_VERSION, 'XASH_BUILD_BRANCH=\\\"%s\\\"' % bld.env.GIT_BRANCH, 'XASH_BUILD_COMMIT_DATE=\\\"%s\\\"' % bld.env.GIT_COMMIT_DATE])\n\n\tbld.stlib(source = bld.path.ant_glob('*.c', excl='build_vcs.c'),\n\t\ttarget = 'public',\n\t\tuse = 'sdk_includes werror build_vcs')\n\n\tif bld.env.TESTS:\n\t\tif bld.env.VALIDATE_TARGET:\n\t\t\tbld.program(features = 'test',\n\t\t\t\tsource = 'tests/test_validate_target.c',\n\t\t\t\ttarget = 'validate_target',\n\t\t\t\tuse = 'public',\n\t\t\t\tdefines = 'VALIDATE_TARGET=\"%s\"' % bld.env.VALIDATE_TARGET,\n\t\t\t\tinstall_path = None)\n\n\t\ttests = {\n\t\t\t'strings': 'tests/test_strings.c',\n\t\t\t'build': 'tests/test_build.c',\n\t\t\t'filebase': 'tests/test_filebase.c',\n\t\t\t'fileext': 'tests/test_fileext.c',\n\t\t\t'efp': 'tests/test_efp.c',\n\t\t\t'atoi': 'tests/test_atoi.c',\n\t\t\t'parsefile': 'tests/test_parsefile.c',\n\t\t}\n\n\t\tfor i in tests:\n\t\t\tbld.program(features = 'test',\n\t\t\t\tsource = tests[i],\n\t\t\t\ttarget = 'test_%s' % i,\n\t\t\t\tuse = 'public',\n\t\t\t\tinstall_path = None)\n"
  },
  {
    "path": "public/xash3d_mathlib.c",
    "content": "/*\nxash3d_mathlib.c - internal mathlib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"xash3d_mathlib.h\"\n#include \"eiface.h\"\n#include \"studio.h\"\n\n#define NUM_HULL_ROUNDS\tARRAYSIZE( hull_table )\n#define HULL_PRECISION\t4\n\nstatic const word hull_table[] = { 2, 4, 6, 8, 12, 16, 18, 24, 28, 32, 36, 40, 48, 54, 56, 60, 64, 72, 80, 112, 120, 128, 140, 176 };\n\nconst int boxpnt[6][4] =\n{\n{ 0, 4, 6, 2 }, // +X\n{ 0, 1, 5, 4 }, // +Y\n{ 0, 2, 3, 1 }, // +Z\n{ 7, 5, 1, 3 }, // -X\n{ 7, 3, 2, 6 }, // -Y\n{ 7, 6, 4, 5 }, // -Z\n};\n\n// pre-quantized table normals from Quake1\nconst float m_bytenormals[NUMVERTEXNORMALS][3] =\n{\n#include \"anorms.h\"\n};\n\nuint16_t FloatToHalf( float v )\n{\n\tunsigned int\ti = FloatAsUint( v );\n\tunsigned int\te = (i >> 23) & 0x00ff;\n\tunsigned int\tm = i & 0x007fffff;\n\tunsigned short\th;\n\n\tif( e <= 127 - 15 )\n\t\th = ((m | 0x00800000) >> (127 - 14 - e)) >> 13;\n\telse h = (i >> 13) & 0x3fff;\n\n\th |= (i >> 16) & 0xc000;\n\n\treturn h;\n}\n\nfloat HalfToFloat( uint16_t h )\n{\n\tunsigned int\tf = (h << 16) & 0x80000000;\n\tunsigned int\tem = h & 0x7fff;\n\n\tif( em > 0x03ff )\n\t{\n\t\tf |= (em << 13) + ((127 - 15) << 23);\n\t}\n\telse\n\t{\n\t\tunsigned int m = em & 0x03ff;\n\n\t\tif( m != 0 )\n\t\t{\n\t\t\tunsigned int e = (em >> 10) & 0x1f;\n\n\t\t\twhile(( m & 0x0400 ) == 0 )\n\t\t\t{\n\t\t\t\tm <<= 1;\n\t\t\t\te--;\n\t\t\t}\n\n\t\t\tm &= 0x3ff;\n\t\t\tf |= ((e + (127 - 14)) << 23) | (m << 13);\n\t\t}\n\t}\n\n\treturn UintAsFloat( f );\n}\n\n/*\n=================\nRoundUpHullSize\n\nround the hullsize to nearest 'right' value\n=================\n*/\nvoid RoundUpHullSize( vec3_t size )\n{\n\tint\ti, j;\n\n\tfor( i = 0; i < 3; i++)\n\t{\n\t\tqboolean\tnegative = false;\n\t\tfloat\tresult, value;\n\n\t\tvalue = size[i];\n\t\tif( value < 0.0f ) negative = true;\n\t\tvalue = Q_ceil( fabs( value ));\n\t\tresult = Q_ceil( size[i] );\n\n\t\t// lookup hull table to find nearest supposed value\n\t\tfor( j = 0; j < NUM_HULL_ROUNDS; j++ )\n\t\t{\n\t\t\tif( value > hull_table[j] )\n\t\t\t\tcontinue;\t// ceil only\n\n\t\t\tif( negative )\n\t\t\t{\n\t\t\t\tresult = ( value - hull_table[j] );\n\t\t\t\tif( result <= HULL_PRECISION )\n\t\t\t\t{\n\t\t\t\t\tresult = -hull_table[j];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tresult = ( value - hull_table[j] );\n\t\t\t\tif( result <= HULL_PRECISION )\n\t\t\t\t{\n\t\t\t\t\tresult = hull_table[j];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsize[i] = result;\n\t}\n}\n\n/*\n=================\nrsqrt\n=================\n*/\nfloat Q_rsqrt( float number )\n{\n\tint\ti;\n\tfloat\tx, y;\n\n\tif( number == 0.0f )\n\t\treturn 0.0f;\n\n\tx = number * 0.5f;\n\ti = FloatAsInt( number );\t// evil floating point bit level hacking\n\ti = 0x5f3759df - (i >> 1);\t// what the fuck?\n\ty = IntAsFloat( i );\n\ty = y * (1.5f - (x * y * y));\t// first iteration\n\n\treturn y;\n}\n\nvoid VectorVectors( const vec3_t forward, vec3_t right, vec3_t up )\n{\n\tfloat\td;\n\n\tright[0] = forward[2];\n\tright[1] = -forward[0];\n\tright[2] = forward[1];\n\n\td = DotProduct( forward, right );\n\tVectorMA( right, -d, forward, right );\n\tVectorNormalize( right );\n\tCrossProduct( right, forward, up );\n\tVectorNormalize( up );\n}\n\n/*\n=================\nVectorAngles\n\n=================\n*/\nvoid GAME_EXPORT VectorAngles( const float *forward, float *angles )\n{\n\tfloat\ttmp, yaw, pitch;\n\n\tif( !forward || !angles )\n\t{\n\t\tif( angles ) VectorClear( angles );\n\t\treturn;\n\t}\n\n\tif( forward[1] == 0 && forward[0] == 0 )\n\t{\n\t\t// fast case\n\t\tyaw = 0;\n\t\tif( forward[2] > 0 )\n\t\t\tpitch = 90.0f;\n\t\telse pitch = 270.0f;\n\t}\n\telse\n\t{\n\t\tyaw = ( atan2( forward[1], forward[0] ) * 180 / M_PI_F );\n\t\tif( yaw < 0 ) yaw += 360;\n\n\t\ttmp = sqrt( forward[0] * forward[0] + forward[1] * forward[1] );\n\t\tpitch = ( atan2( forward[2], tmp ) * 180 / M_PI_F );\n\t\tif( pitch < 0 ) pitch += 360;\n\t}\n\n\tVectorSet( angles, pitch, yaw, 0 );\n}\n\n/*\n=================\nVectorsAngles\n\n=================\n*/\nvoid VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles )\n{\n\tfloat\tpitch, cpitch, yaw, roll;\n\n\tpitch = -asin( forward[2] );\n\tcpitch = cos( pitch );\n\n\tif( fabs( cpitch ) > EQUAL_EPSILON )\t// gimball lock?\n\t{\n\t\tcpitch = 1.0f / cpitch;\n\t\tpitch = RAD2DEG( pitch );\n\t\tyaw = RAD2DEG( atan2( forward[1] * cpitch, forward[0] * cpitch ));\n\t\troll = RAD2DEG( atan2( -right[2] * cpitch, up[2] * cpitch ));\n\t}\n\telse\n\t{\n\t\tpitch = forward[2] > 0 ? -90.0f : 90.0f;\n\t\tyaw = RAD2DEG( atan2( right[0], -right[1] ));\n\t\troll = 180.0f;\n\t}\n\n\tangles[PITCH] = pitch;\n\tangles[YAW] = yaw;\n\tangles[ROLL] = roll;\n}\n\n//\n// bounds operations\n//\n/*\n=================\nSphereIntersect\n=================\n*/\nqboolean SphereIntersect( const vec3_t vSphereCenter, float fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir )\n{\n\tfloat\ta, b, c, insideSqr;\n\tvec3_t\tp;\n\n\t// translate sphere to origin.\n\tVectorSubtract( vLinePt, vSphereCenter, p );\n\n\ta = DotProduct( vLineDir, vLineDir );\n\tb = 2.0f * DotProduct( p, vLineDir );\n\tc = DotProduct( p, p ) - fSphereRadiusSquared;\n\n\tinsideSqr = b * b - 4.0f * a * c;\n\tif( insideSqr <= 0.000001f )\n\t\treturn false;\n\treturn true;\n}\n\n/*\n=================\nPlaneIntersect\n\nfind point where ray\nwas intersect with plane\n=================\n*/\nvoid PlaneIntersect( const mplane_t *plane, const vec3_t p0, const vec3_t p1, vec3_t out )\n{\n\tfloat distToPlane = PlaneDiff( p0, plane );\n\tfloat planeDotRay = DotProduct( plane->normal, p1 );\n\tfloat sect = -(distToPlane) / planeDotRay;\n\n\tVectorMA( p0, sect, p1, out );\n}\n\n//\n// studio utils\n//\n\n/*\n====================\nQuaternionAlign\n\nmake sure quaternions are within 180 degrees of one another,\nif not, reverse q\n====================\n*/\nstatic void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt )\n{\n\t// decide if one of the quaternions is backwards\n\tfloat\ta = 0.0f;\n\tfloat\tb = 0.0f;\n\tint\ti;\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\ta += (p[i] - q[i]) * (p[i] - q[i]);\n\t\tb += (p[i] + q[i]) * (p[i] + q[i]);\n\t}\n\n\tif( a > b )\n\t{\n\t\tfor( i = 0; i < 4; i++ )\n\t\t\tqt[i] = -q[i];\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < 4; i++ )\n\t\t\tqt[i] = q[i];\n\t}\n}\n\n/*\n====================\nQuaternionSlerpNoAlign\n====================\n*/\nstatic void QuaternionSlerpNoAlign( const vec4_t p, const vec4_t q, float t, vec4_t qt )\n{\n\tfloat\tomega, cosom, sinom, sclp, sclq;\n\tint\ti;\n\n\t// 0.0 returns p, 1.0 return q.\n\tcosom = p[0] * q[0] + p[1] * q[1] + p[2] * q[2] + p[3] * q[3];\n\n\tif(( 1.0f + cosom ) > 0.000001f )\n\t{\n\t\tif(( 1.0f - cosom ) > 0.000001f )\n\t\t{\n\t\t\tomega = acos( cosom );\n\t\t\tsinom = sin( omega );\n\t\t\tsclp = sin( (1.0f - t) * omega) / sinom;\n\t\t\tsclq = sin( t * omega ) / sinom;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsclp = 1.0f - t;\n\t\t\tsclq = t;\n\t\t}\n\n\t\tfor( i = 0; i < 4; i++ )\n\t\t{\n\t\t\tqt[i] = sclp * p[i] + sclq * q[i];\n\t\t}\n\t}\n\telse\n\t{\n\t\tqt[0] = -q[1];\n\t\tqt[1] = q[0];\n\t\tqt[2] = -q[3];\n\t\tqt[3] = q[2];\n\t\tsclp = sin(( 1.0f - t ) * ( 0.5f * M_PI_F ));\n\t\tsclq = sin( t * ( 0.5f * M_PI_F ));\n\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tqt[i] = sclp * p[i] + sclq * qt[i];\n\t\t}\n\t}\n}\n\n/*\n====================\nQuaternionSlerp\n\nQuaternion sphereical linear interpolation\n====================\n*/\nvoid QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt )\n{\n\tvec4_t\tq2;\n\n\t// 0.0 returns p, 1.0 return q.\n\t// decide if one of the quaternions is backwards\n\tQuaternionAlign( p, q, q2 );\n\n\tQuaternionSlerpNoAlign( p, q2, t, qt );\n}\n\n/*\n==================\nBoxOnPlaneSide\n\nReturns 1, 2, or 1 + 2\n==================\n*/\nint BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p )\n{\n\tfloat\tdist1, dist2;\n\tint\tsides = 0;\n\n\t// general case\n\tswitch( p->signbits )\n\t{\n\tcase 0:\n\t\tdist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];\n\t\tdist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];\n\t\tbreak;\n\tcase 1:\n\t\tdist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];\n\t\tdist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];\n\t\tbreak;\n\tcase 2:\n\t\tdist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];\n\t\tdist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];\n\t\tbreak;\n\tcase 3:\n\t\tdist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];\n\t\tdist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];\n\t\tbreak;\n\tcase 4:\n\t\tdist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];\n\t\tdist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];\n\t\tbreak;\n\tcase 5:\n\t\tdist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2];\n\t\tdist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2];\n\t\tbreak;\n\tcase 6:\n\t\tdist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];\n\t\tdist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];\n\t\tbreak;\n\tcase 7:\n\t\tdist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2];\n\t\tdist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2];\n\t\tbreak;\n\tdefault:\n\t\t// shut up compiler\n\t\tdist1 = dist2 = 0;\n\t\tbreak;\n\t}\n\n\tif( dist1 >= p->dist )\n\t\tsides = 1;\n\tif( dist2 < p->dist )\n\t\tsides |= 2;\n\n\treturn sides;\n}\n\nvoid R_StudioCalcBones( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const float *adj, vec3_t pos, vec4_t q )\n{\n\tfloat v1[6], v2[6];\n\tint i, max;\n\n\tmax = q != NULL ? 6 : 3;\n\n\tfor( i = 0; i < max; i++ )\n\t{\n\t\tmstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[i] );\n\t\tint j = frame;\n\t\tfloat fadj = 0.0f;\n\n\t\tif( pbone->bonecontroller[i] >= 0 && adj != NULL )\n\t\t\tfadj = adj[pbone->bonecontroller[i]];\n\n\t\tif( panim->offset[i] == 0 )\n\t\t{\n\t\t\tv1[i] = v2[i] = pbone->value[i] + fadj;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( panimvalue->num.total < panimvalue->num.valid )\n\t\t\tj = 0;\n\n\t\twhile( panimvalue->num.total <= j )\n\t\t{\n\t\t\tj -= panimvalue->num.total;\n\t\t\tpanimvalue += panimvalue->num.valid + 1;\n\n\t\t\tif( panimvalue->num.total < panimvalue->num.valid )\n\t\t\t\tj = 0;\n\t\t}\n\n\t\tif( panimvalue->num.valid > j )\n\t\t{\n\t\t\tv1[i] = panimvalue[j + 1].value;\n\n\t\t\tif( panimvalue->num.valid > j + 1 )\n\t\t\t\tv2[i] = panimvalue[j + 2].value;\n\t\t\telse if( panimvalue->num.total > j + 1 )\n\t\t\t\tv2[i] = v1[i];\n\t\t\telse\n\t\t\t\tv2[i] = panimvalue[panimvalue->num.valid + 2].value;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tv1[i] = panimvalue[panimvalue->num.valid].value;\n\n\t\t\tif( panimvalue->num.total > j + 1 )\n\t\t\t\tv2[i] = v1[i];\n\t\t\telse\n\t\t\t\tv2[i] = panimvalue[panimvalue->num.valid + 2].value;\n\t\t}\n\n\t\tv1[i] = pbone->value[i] + v1[i] * pbone->scale[i] + fadj;\n\t\tv2[i] = pbone->value[i] + v2[i] * pbone->scale[i] + fadj;\n\t}\n\n\tif( !VectorCompare( v1, v2 ))\n\t\tVectorLerp( v1, s, v2, pos );\n\telse\n\t\tVectorCopy( v1, pos );\n\n\tif( q != NULL )\n\t{\n\t\tif( !VectorCompare( &v1[3], &v2[3] ))\n\t\t{\n\t\t\tvec4_t q1, q2;\n\n\t\t\tAngleQuaternion( &v1[3], q1, true );\n\t\t\tAngleQuaternion( &v2[3], q2, true );\n\t\t\tQuaternionSlerp( q1, q2, s, q );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tAngleQuaternion( &v1[3], q, true );\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "public/xash3d_mathlib.h",
    "content": "/*\nxash3d_mathlib.h - base math functions\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef XASH3D_MATHLIB_H\n#define XASH3D_MATHLIB_H\n\n#include <math.h>\n#if HAVE_TGMATH_H\n#include <tgmath.h>\n#endif\n#include <string.h>\n\n#include \"build.h\"\n#include \"xash3d_types.h\"\n\n/*\n===========================\n\nCONSTANTS AND HELPER MACROS\n\n===========================\n*/\n\n// euler angle order\n#define PITCH 0\n#define YAW   1\n#define ROLL  2\n\n#ifndef M_PI\n#define M_PI    (double)3.14159265358979323846\n#endif\n\n#define M_PI2   ((double)(M_PI * 2))\n#define M_PI_F  ((float)(M_PI))\n#define M_PI2_F ((float)(M_PI2))\n\n#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI))\n#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0))\n\n#define NUMVERTEXNORMALS 162\n\n#define BOGUS_RANGE ((vec_t)114032.64)\t// world.size * 1.74\n\n#define SIDE_FRONT 0\n#define SIDE_BACK  1\n#define SIDE_ON    2\n#define SIDE_CROSS -2\n\n#define PLANE_X        0 // 0 - 2 are axial planes\n#define PLANE_Y        1 // 3 needs alternate calc\n#define PLANE_Z        2\n#define PLANE_NONAXIAL 3\n\n#define EQUAL_EPSILON 0.001f\n#define STOP_EPSILON  0.1f\n#define ON_EPSILON    0.1f\n\n#define RAD_TO_STUDIO (32768.0 / M_PI)\n#define STUDIO_TO_RAD (M_PI / 32768.0)\n\n#define INV127F          ( 1.0f / 127.0f )\n#define INV255F          ( 1.0f / 255.0f )\n#define MAKE_SIGNED( x ) ((( x ) * INV127F ) - 1.0f )\n\n#define Q_min( a, b ) (((a) < (b)) ? (a) : (b))\n#define Q_max( a, b ) (((a) > (b)) ? (a) : (b))\n#define Q_equal_e( a, b, e ) (((a) >= ((b) - (e))) && ((a) <= ((b) + (e))))\n#define Q_equal( a, b ) Q_equal_e( a, b, EQUAL_EPSILON )\n#define Q_floor( a )    ((float)(int)(a))\n#define Q_ceil( a )     ((float)(int)((a) + 1))\n#define Q_round( x, y ) (floor( x / y + 0.5f ) * y )\n#define Q_rint(x)       ((x) < 0.0f ? ((int)((x)-0.5f)) : ((int)((x)+0.5f)))\n#define ALIGN( x, a )   ((( x ) + (( size_t )( a ) - 1 )) & ~(( size_t )( a ) - 1 ))\n\n#define VectorIsNAN(v) (IS_NAN(v[0]) || IS_NAN(v[1]) || IS_NAN(v[2]))\n#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])\n#define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2]))\n#define DotProductPrecise(x,y) ((double)(x)[0]*(double)(y)[0]+(double)(x)[1]*(double)(y)[1]+(double)(x)[2]*(double)(y)[2])\n#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0])\n#define Vector2Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1])\n#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])\n#define Vector2Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1])\n#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])\n#define VectorAddScalar(a,b,c) ((c)[0]=(a)[0]+(b),(c)[1]=(a)[1]+(b),(c)[2]=(a)[2]+(b))\n#define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1])\n#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])\n#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3])\n#define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale))\n#define VectorCompare(v1,v2)\t((v1)[0]==(v2)[0] && (v1)[1]==(v2)[1] && (v1)[2]==(v2)[2])\n#define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out )\n#define VectorMax(a) ( Q_max((a)[0], Q_max((a)[1], (a)[2])) )\n#define VectorAvg(a) ( ((a)[0] + (a)[1] + (a)[2]) / 3 )\n#define VectorLength(a) ( sqrt( DotProduct( a, a )))\n#define VectorLength2(a) (DotProduct( a, a ))\n#define VectorDistance(a, b) (sqrt( VectorDistance2( a, b )))\n#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2]))\n#define Vector2Average(a,b,o)\t((o)[0]=((a)[0]+(b)[0])*0.5f,(o)[1]=((a)[1]+(b)[1])*0.5f)\n#define VectorAverage(a,b,o)\t((o)[0]=((a)[0]+(b)[0])*0.5f,(o)[1]=((a)[1]+(b)[1])*0.5f,(o)[2]=((a)[2]+(b)[2])*0.5f)\n#define Vector2Set(v, x, y) ((v)[0]=(x),(v)[1]=(y))\n#define Vector2Unpack(v, x, y) ((x)=(v)[0],(y)=(v)[1])\n#define VectorSet(v, x, y, z) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z))\n#define VectorUnpack(v, x, y, z) ((x)=(v)[0],(y)=(v)[1],(z)=(v)[2])\n#define Vector4Set(v, a, b, c, d) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c),(v)[3]=(d))\n#define Vector4Unpack(v, a, b, c, d) ((a)=(v)[0],(b)=(v)[1],(c)=(v)[2],(d)=(v)[3])\n#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0)\n#define Vector2Lerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]))\n#define VectorLerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2]))\n#define VectorNormalize( v ) { float ilength = (float)sqrt(DotProduct(v, v));if (ilength) ilength = 1.0f / ilength;v[0] *= ilength;v[1] *= ilength;v[2] *= ilength; }\n#define VectorNormalize2( v, dest ) {float ilength = (float)sqrt(DotProduct(v,v));if (ilength) ilength = 1.0f / ilength;dest[0] = v[0] * ilength;dest[1] = v[1] * ilength;dest[2] = v[2] * ilength; }\n#define VectorNormalizeFast( v ) {float ilength = (float)Q_rsqrt(DotProduct(v,v)); v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; }\n#define VectorNormalizeLength( v ) VectorNormalizeLength2((v), (v))\n#define VectorNegate(x, y) ((y)[0] = -(x)[0], (y)[1] = -(x)[1], (y)[2] = -(x)[2])\n#define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2])\n#define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2])\n#define VectorMAM(scale1, b1, scale2, b2, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2])\n#define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2])\n#define VectorIsNull( v ) ((v)[0] == 0.0f && (v)[1] == 0.0f && (v)[2] == 0.0f)\n#define MakeRGBA( out, x, y, z, w ) Vector4Set( out, x, y, z, w )\n#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal))\n#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist)\n#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min))\n\n/*\n===========================\n\nCONSTANTS GLOBALS\n\n===========================\n*/\n// a1ba: we never return pointers to these globals\n// so help compiler optimize constants away\n#define vec3_origin ((vec3_t){ 0.0f, 0.0f, 0.0f })\n\nextern const int       boxpnt[6][4];\nextern const float     m_bytenormals[NUMVERTEXNORMALS][3];\n\n\n/*\n===========================\n\nMATH FUNCTIONS\n\n===========================\n*/\ntypedef struct mplane_s mplane_t;\ntypedef struct mstudiobone_s mstudiobone_t;\ntypedef struct mstudioanim_s mstudioanim_t;\n\nfloat Q_rsqrt( float number );\nuint16_t FloatToHalf( float v );\nfloat HalfToFloat( uint16_t h );\nvoid RoundUpHullSize( vec3_t size );\nvoid VectorVectors( const vec3_t forward, vec3_t right, vec3_t up );\nvoid VectorAngles( const float *forward, float *angles );\nvoid VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles );\nvoid PlaneIntersect( const mplane_t *plane, const vec3_t p0, const vec3_t p1, vec3_t out );\nqboolean SphereIntersect( const vec3_t vSphereCenter, float fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir );\nvoid QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt );\n\nvoid R_StudioCalcBones( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const float *adj, vec3_t pos, vec4_t q );\nint BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p );\n#define BOX_ON_PLANE_SIDE( emins, emaxs, p )           \\\n\t((( p )->type < 3 ) ?                              \\\n\t(                                                  \\\n\t\t((p)->dist <= (emins)[(p)->type]) ? 1 :        \\\n\t\t(                                              \\\n\t\t\t((p)->dist >= (emaxs)[(p)->type] ) ? 2 : 3 \\\n\t\t)                                              \\\n\t) : BoxOnPlaneSide(( emins ), ( emaxs ), ( p )))\n\n//\n// matrixlib.c\n//\nstatic inline void Matrix3x4_LoadIdentity( matrix3x4 m )\n{\n\tmemset( m, 0, sizeof( matrix3x4 ));\n\tm[0][0] = m[1][1] = m[2][2] = 1.0f;\n}\n#define Matrix3x4_Copy( out, in )\t\tmemcpy( out, in, sizeof( matrix3x4 ))\nvoid Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] );\nvoid Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] );\nvoid Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] );\nvoid Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] );\nvoid Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 );\nvoid Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin );\nvoid Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale );\nvoid Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const vec3_t maxs, vec3_t absmin, vec3_t absmax );\nvoid Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out );\n\nstatic inline void Matrix4x4_LoadIdentity( matrix4x4 m )\n{\n\tmemset( m, 0, sizeof( matrix4x4 ));\n\tm[0][0] = m[1][1] = m[2][2] = m[3][3] = 1.0f;\n}\n#define Matrix4x4_Copy( out, in )\tmemcpy( out, in, sizeof( matrix4x4 ))\nvoid Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] );\nvoid Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] );\nvoid Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] );\nvoid Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] );\nvoid Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 );\nvoid Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale );\nvoid Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist );\nvoid Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin );\nvoid Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 );\nqboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 );\n\n// horrible cast but helps not breaking strict aliasing in mathlib\n// as union type punning should be fine in C but not in C++\n// so don't carry over this to C++ code\n#ifndef __cplusplus\ntypedef union\n{\n\tfloat fl;\n\tuint32_t u;\n\tint32_t i;\n} float_bits_t;\n\nstatic inline uint32_t FloatAsUint( float v )\n{\n\tfloat_bits_t bits = { v };\n\treturn bits.u;\n}\n\nstatic inline int32_t FloatAsInt( float v )\n{\n\tfloat_bits_t bits = { v };\n\treturn bits.i;\n}\n\nstatic inline float IntAsFloat( int32_t i )\n{\n\tfloat_bits_t bits;\n\tbits.i = i;\n\treturn bits.fl;\n}\n\nstatic inline float UintAsFloat( uint32_t u )\n{\n\tfloat_bits_t bits;\n\tbits.u = u;\n\treturn bits.fl;\n}\n#endif // __cplusplus\n\n// isnan implementation is broken on IRIX as reported in https://github.com/FWGS/xash3d-fwgs/pull/1211\n#if defined( XASH_IRIX ) || !defined( isnan )\nstatic inline int IS_NAN( float x )\n{\n\tint32_t i = FloatAsInt( x ); // only C\n\treturn i & ( 255 << 23 ) == ( 255 << 23 );\n}\n#else\n#define IS_NAN isnan\n#endif\n\nstatic inline float anglemod( float a )\n{\n\ta = (360.0f / 65536) * ((int)(a*(65536/360.0f)) & 65535);\n\treturn a;\n}\n\nstatic inline void SinCos( float radians, float *sine, float *cosine )\n{\n\t*sine = sin( radians );\n\t*cosine = cos( radians );\n}\n\nstatic inline int NearestPOW( int value, qboolean roundDown )\n{\n\tint\tn = 1;\n\n\tif( value <= 0 ) return 1;\n\twhile( n < value ) n <<= 1;\n\n\tif( roundDown )\n\t{\n\t\tif( n > value ) n >>= 1;\n\t}\n\treturn n;\n}\n\nstatic inline qboolean VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon )\n{\n\tvec_t ax = fabs( vec1[0] - vec2[0] );\n\tvec_t ay = fabs( vec1[1] - vec2[1] );\n\tvec_t az = fabs( vec1[2] - vec2[2] );\n\n\treturn ( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) ? true : false;\n}\n\nstatic inline float VectorNormalizeLength2( const vec3_t v, vec3_t out )\n{\n\tfloat length = VectorLength( v );\n\n\tif( length )\n\t{\n\t\tfloat ilength = 1.0f / length;\n\t\tout[0] = v[0] * ilength;\n\t\tout[1] = v[1] * ilength;\n\t\tout[2] = v[2] * ilength;\n\t}\n\n\treturn length;\n}\n\nstatic inline void GAME_EXPORT AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up )\n{\n\tfloat\tsr, sp, sy, cr, cp, cy;\n\n\tSinCos( DEG2RAD( angles[YAW] ), &sy, &cy );\n\tSinCos( DEG2RAD( angles[PITCH] ), &sp, &cp );\n\tSinCos( DEG2RAD( angles[ROLL] ), &sr, &cr );\n\n\tif( forward )\n\t{\n\t\tforward[0] = cp * cy;\n\t\tforward[1] = cp * sy;\n\t\tforward[2] = -sp;\n\t}\n\n\tif( right )\n\t{\n\t\tright[0] = (-1.0f * sr * sp * cy + -1.0f * cr * -sy );\n\t\tright[1] = (-1.0f * sr * sp * sy + -1.0f * cr * cy );\n\t\tright[2] = (-1.0f * sr * cp);\n\t}\n\n\tif( up )\n\t{\n\t\tup[0] = (cr * sp * cy + -sr * -sy );\n\t\tup[1] = (cr * sp * sy + -sr * cy );\n\t\tup[2] = (cr * cp);\n\t}\n}\n\nstatic inline void ClearBounds( vec3_t mins, vec3_t maxs )\n{\n\t// make bogus range\n\tmins[0] = mins[1] = mins[2] =  999999.0f;\n\tmaxs[0] = maxs[1] = maxs[2] = -999999.0f;\n}\n\nstatic inline qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 )\n{\n\tif( mins1[0] > maxs2[0] || mins1[1] > maxs2[1] || mins1[2] > maxs2[2] )\n\t\treturn false;\n\tif( maxs1[0] < mins2[0] || maxs1[1] < mins2[1] || maxs1[2] < mins2[2] )\n\t\treturn false;\n\treturn true;\n}\n\nstatic inline qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius )\n{\n\tif( mins[0] > origin[0] + radius || mins[1] > origin[1] + radius || mins[2] > origin[2] + radius )\n\t\treturn false;\n\tif( maxs[0] < origin[0] - radius || maxs[1] < origin[1] - radius || maxs[2] < origin[2] - radius )\n\t\treturn false;\n\treturn true;\n}\n\nstatic inline float RadiusFromBounds( const vec3_t mins, const vec3_t maxs )\n{\n\tvec3_t corner;\n\tint i;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tfloat a = fabs( mins[i] );\n\t\tfloat b = fabs( maxs[i] );\n\t\tcorner[i] = Q_max( a, b );\n\t}\n\n\treturn VectorLength( corner );\n}\n\nstatic inline void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs )\n{\n\tint i;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tfloat val = v[i];\n\t\tif( val < mins[i] ) mins[i] = val;\n\t\tif( val > maxs[i] ) maxs[i] = val;\n\t}\n}\n\nstatic inline void ExpandBounds( vec3_t mins, vec3_t maxs, float offset )\n{\n\tmins[0] -= offset;\n\tmins[1] -= offset;\n\tmins[2] -= offset;\n\tmaxs[0] += offset;\n\tmaxs[1] += offset;\n\tmaxs[2] += offset;\n}\n\nstatic inline int SignbitsForPlane( const vec3_t normal )\n{\n\tint\tbits, i;\n\n\tfor( bits = i = 0; i < 3; i++ )\n\t\tif( normal[i] < 0.0f ) bits |= 1<<i;\n\treturn bits;\n}\n\nstatic inline int PlaneTypeForNormal( const vec3_t normal )\n{\n\tif( normal[0] == 1.0f )\n\t\treturn PLANE_X;\n\tif( normal[1] == 1.0f )\n\t\treturn PLANE_Y;\n\tif( normal[2] == 1.0f )\n\t\treturn PLANE_Z;\n\treturn PLANE_NONAXIAL;\n}\n\nstatic inline void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio )\n{\n\tfloat\tsr, sp, sy, cr, cp, cy;\n\n\tif( studio )\n\t{\n\t\tSinCos( angles[ROLL] * 0.5f, &sy, &cy );\n\t\tSinCos( angles[YAW] * 0.5f, &sp, &cp );\n\t\tSinCos( angles[PITCH] * 0.5f, &sr, &cr );\n\t}\n\telse\n\t{\n\t\tSinCos( DEG2RAD( angles[YAW] ) * 0.5f, &sy, &cy );\n\t\tSinCos( DEG2RAD( angles[PITCH] ) * 0.5f, &sp, &cp );\n\t\tSinCos( DEG2RAD( angles[ROLL] ) * 0.5f, &sr, &cr );\n\t}\n\n\tq[0] = sr * cp * cy - cr * sp * sy; // X\n\tq[1] = cr * sp * cy + sr * cp * sy; // Y\n\tq[2] = cr * cp * sy - sr * sp * cy; // Z\n\tq[3] = cr * cp * cy + sr * sp * sy; // W\n}\n\nstatic inline void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z )\n{\n\tout[0][3] = x;\n\tout[1][3] = y;\n\tout[2][3] = z;\n}\n\nstatic inline void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out )\n{\n\tout[0] = in[0][3];\n\tout[1] = in[1][3];\n\tout[2] = in[2][3];\n}\n\nstatic inline void QuaternionAngle( const vec4_t q, vec3_t angles )\n{\n\tmatrix3x4\tmat;\n\tMatrix3x4_FromOriginQuat( mat, q, vec3_origin );\n\tMatrix3x4_AnglesFromMatrix( mat, angles );\n}\n\nstatic inline void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], const vec4_t q2[], const float pos2[][3], float s )\n{\n\tint\ti;\n\ts = bound( 0.0f, s, 1.0f );\n\n\tfor( i = 0; i < numbones; i++ )\n\t{\n\t\tQuaternionSlerp( q1[i], q2[i], s, q1[i] );\n\t\tVectorLerp( pos1[i], s, pos2[i], pos1[i] );\n\t}\n}\n\n#endif // XASH3D_MATHLIB_H\n"
  },
  {
    "path": "ref/gl/exports.txt",
    "content": "GetRefAPI\n"
  },
  {
    "path": "ref/gl/gl2_shim/fragment.glsl.inc",
    "content": "\"#if VER <= 300\\n\"\n\"#define layout(x)\\n\"\n\"#endif\\n\"\n\"#if VER < 300\\n\"\n\"#define out attribute\\n\"\n\"#define in varying\\n\"\n\"#define texture texture2D\\n\"\n\"#endif\\n\"\n\"#if VER >= 130 || VER == 100\\n\"\n\"precision mediump float;\\n\"\n\"#endif\\n\"\n\"#if VER == 100\\n\"\n\"#define PREC mediump\\n\"\n\"#else\\n\"\n\"#define PREC\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"uniform sampler2D uTex0;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"uniform sampler2D uTex1;\\n\"\n\"#endif\\n\"\n\"#if FEAT_ALPHA_TEST\\n\"\n\"uniform float uAlphaTest;\\n\"\n\"#endif\\n\"\n\"#if FEAT_FOG\\n\"\n\"uniform PREC vec4 uFog;\\n\"\n\"#endif\\n\"\n\"uniform PREC vec4 uColor;\\n\"\n\"#if ATTR_COLOR\\n\"\n\"in PREC vec4 vColor;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"in PREC vec2 vTexCoord0;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"in PREC vec2 vTexCoord1;\\n\"\n\"#endif\\n\"\n\"#if ATTR_NORMAL\\n\"\n\"in PREC vec2 vNormal;\\n\"\n\"#endif\\n\"\n\"#if VER >= 300\\n\"\n\"out vec4 oFragColor;\\n\"\n\"#else\\n\"\n\"#define oFragColor gl_FragColor\\n\"\n\"#endif\\n\"\n\"void main()\\n\"\n\"{\\n\"\n\"#if ATTR_COLOR\\n\"\n\"PREC vec4 c = vColor;\\n\"\n\"#else\\n\"\n\"PREC vec4 c = uColor;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"c = c * texture(uTex0, vTexCoord0);\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"c = c * texture(uTex1, vTexCoord1);\\n\"\n\"#endif\\n\"\n\"#if FEAT_ALPHA_TEST\\n\"\n\"if(c.a <= uAlphaTest)\\n\"\n\"discard;\\n\"\n\"#endif\\n\"\n\"#if FEAT_FOG\\n\"\n\"float fogDist = gl_FragCoord.z / gl_FragCoord.w;\\n\"\n\"float fogRate = clamp(exp(-uFog.w * fogDist), 0.0, 1.0);\\n\"\n\"c.rgb = mix(uFog.rgb, c.rgb, fogRate);\\n\"\n\"#endif\\n\"\n\"oFragColor = c;\\n\"\n\"}\\n\"\n"
  },
  {
    "path": "ref/gl/gl2_shim/gl2_shim.c",
    "content": "/*\ngl2_shim.c - GL CORE/ES2+ FFP emulation\nCopyright (C) 2023 mittorn, fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n/*\nbased on vglshim, as it has almost all needed for using glbegins on gles\nBufStorage mode should work similar to vglshim with vitagl's DRAW_SPEEDHACK\nas it uses gpu-mapped in similar way\n\ngl2shim will not give much performance gain. It does not batch small drawcalls, just do little copy optimization\nIt just allows to draw similar way as in native legacy context, but in es/core contexts\n\nLimitations:\n1. Matrix support is very limited, only combined MVP\n2. No TexEnv support, multitexture always works in MODULATE mode\n3. DrawElements with client pointers will not work on CORE/WEBGL contexts, DrawRangeElements will\n4. No quads in arrays drawing (simple DrawArrays(GL_QUADS) may be implemented, but DrawElements not)\n5. Textures are enabled with texcoord attribs, not glEnable (can be changed, but who cares?)\n6. Begin/End limited to 8192 vertices. It is possible to support more, but need change glVertex logic to split drawcals, which may make it slower\n7. Textures internalformat ignored except of removing alpha from RGB textures\n*/\n\n#include \"gl_local.h\"\n#if !XASH_GL_STATIC\n#include \"gl2_shim.h\"\n\n#define MAX_SHADERLEN 4096\n// increase this when adding more attributes\n#define MAX_PROGS 32\n// must be LESS GL2_MAX_VERTS\n#define MAX_BEGINEND_VERTS 8192\n\nenum gl2wrap_attrib_e\n{\n\tGL2_ATTR_POS       = 0, // 1\n\tGL2_ATTR_COLOR,         // 2\n\tGL2_ATTR_TEXCOORD0,     // 4\n\tGL2_ATTR_TEXCOORD1,     // 8\n\tGL2_ATTR_MAX\n};\n\n// continuation of previous enum\nenum gl2wrap_flag_e\n{\n\tGL2_FLAG_ALPHA_TEST = GL2_ATTR_MAX, // 16\n\tGL2_FLAG_FOG,                       // 32\n\tGL2_FLAG_NORMAL,                    // 64\n\tGL2_FLAG_MAX\n};\n\ntypedef struct\n{\n\tGLuint flags;\n\tGLint attridx[GL2_ATTR_MAX];\n\tGLuint glprog;\n\tGLint ucolor;\n\tGLint ualpha;\n\tGLint utex0;\n\tGLint utex1;\n\tGLint ufog;\n\tGLint uMVP;\n\tGLuint *vao_begin;\n} gl2wrap_prog_t;\n\nstatic const char *gl2wrap_vert_src =\n#include \"vertex.glsl.inc\"\n;\n\nstatic const char *gl2wrap_frag_src =\n#include \"fragment.glsl.inc\"\n;\n\nstatic int gl2wrap_init = 0;\n\nstatic struct\n{\n\tGLfloat *attrbuf[GL2_ATTR_MAX];\n\tGLuint *attrbufobj[GL2_ATTR_MAX];\n\tvoid **mappings[GL2_ATTR_MAX];\n\t//GLuint attrbufpers[GL2_ATTR_MAX];\n\tGLuint attrbufcycle;\n\tGLuint cur_flags;\n\tGLint begin;\n\tGLint end;\n\tGLenum prim;\n\tGLfloat color[4];\n\tGLfloat fog[4]; // color + density\n\tGLfloat alpharef;\n\tgl2wrap_prog_t progs[MAX_PROGS];\n\tgl2wrap_prog_t *cur_prog;\n\tGLboolean uchanged;\n\tGLuint triquads_ibo[4];\n} gl2wrap;\n\nstatic struct\n{\n\tqboolean buf_storage; // buffer storage is enabled, buffers mapped persistently (zero-copy glBegins)\n\tqboolean incremental; // incremental buffer streaming\n\tqboolean supports_mapbuffer; // set to false on systems with mapbuffer issues\n\tqboolean vao_mandatory; // even if incremental streaming unavailiable (it is very slow without mapbuffers) force VAO+VBO (WebGL-like or broken glcore)\n\tqboolean coherent; // enable MAP_COHERENT_BIT on persist mappings\n\tqboolean async; // enable MAP_UNSYNCHRONIZED_BIT on temporary mappings\n\tqboolean force_flush; // enable MAP_FLUSH_EXPLICIT_BIT and FlushMappedBufferRange calls\n\tuint32_t cycle_buffers; // cycle N buffers during draw to reduce locking in non-incremental mode\n\tuint32_t version; // glsl version to use\n} gl2wrap_config;\n\nstatic struct\n{\n\tfloat mvp[16], mv[16], pr[16], dummy[16];\n\tGLenum mode;\n\tfloat *current;\n\tuint64_t update;\n} gl2wrap_matrix;\n\nstatic struct\n{\n\tqboolean alpha_test;\n\tqboolean fog;\n\tGLuint vbo;\n\tGLuint tmu;\n} gl2wrap_state;\n\n//#define QUAD_BATCH\n\n#ifdef QUAD_BATCH\nstatic struct\n{\n\tunsigned int texture;\n\tunsigned int flags;\n\tGLboolean active;\n} gl2wrap_quad;\n#endif\n\nstatic const int gl2wrap_attr_size[GL2_ATTR_MAX] = { 3, 4, 2, 2 };\n\nstatic const char *gl2wrap_flag_name[GL2_FLAG_MAX] =\n{\n\t\"ATTR_POSITION\",\n\t\"ATTR_COLOR\",\n\t\"ATTR_TEXCOORD0\",\n\t\"ATTR_TEXCOORD1\",\n\t\"FEAT_ALPHA_TEST\",\n\t\"FEAT_FOG\",\n\t\"ATTR_NORMAL\",\n};\n\nstatic const char *gl2wrap_attr_name[GL2_ATTR_MAX] =\n{\n\t\"inPosition\",\n\t\"inColor\",\n\t\"inTexCoord0\",\n\t\"inTexCoord1\",\n};\n\n#define MB( x, y ) (( x ) ? GL_MAP_##y##_BIT : 0 )\n\nstatic void (APIENTRY *rpglEnable)( GLenum e );\nstatic void (APIENTRY *rpglDisable)( GLenum e );\nstatic void (APIENTRY *rpglDrawElements )( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices );\nstatic void (APIENTRY *rpglDrawArrays )( GLenum mode, GLint first, GLsizei count );\nstatic void (APIENTRY *rpglDrawRangeElements )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices );\nstatic void (APIENTRY *rpglBindBufferARB)( GLenum buf, GLuint obj );\n\nstatic void GL2_FreeArrays( void );\n\n#ifdef QUAD_BATCH\nstatic void GL2_FlushPrims( void );\nstatic void (APIENTRY *rpglBindTexture)( GLenum tex, GLuint obj );\nstatic void APIENTRY GL2_BindTexture( GLenum tex, GLuint obj )\n{\n\tif( gl2wrap_quad.texture != obj )\n\t{\n\t\tGL2_FlushPrims();\n\t\tgl2wrap_quad.texture = obj;\n\t}\n\trpglBindTexture( tex, obj );\n}\n#endif\n\nstatic char *GL_PrintInfoLog( GLhandleARB object, qboolean program )\n{\n\tstatic char\tmsg[8192];\n\tGLuint maxLength = 0;\n\n\tif( program && pglGetProgramiv )\n\t\tpglGetProgramiv( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength );\n\telse\n\t\tpglGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength );\n\n\tif( maxLength >= sizeof( msg ))\n\t{\n\t\t//ALERT( at_warning, \"GL_PrintInfoLog: message exceeds %i symbols\\n\", sizeof( msg ));\n\t\tmaxLength = sizeof( msg ) - 1;\n\t}\n\n\tif( program && pglGetProgramInfoLog )\n\t\tpglGetProgramInfoLog( object, maxLength, &maxLength, msg );\n\telse\n\t\tpglGetInfoLogARB( object, maxLength, &maxLength, msg );\n\n\treturn msg;\n}\n\n\nstatic GLuint GL2_GenerateShader( gl2wrap_prog_t *prog, GLenum type )\n{\n\tchar *shader, shader_buf[MAX_SHADERLEN + 1];\n\tchar tmp[256];\n\tint i;\n\tGLint status, len;\n\tGLuint id, loc;\n\tint version = gl2wrap_config.version;\n\n\tshader = shader_buf;\n\t//shader[0] = '\\n';\n\tshader[0] = 0;\n\n\tQ_snprintf( shader, MAX_SHADERLEN, \"#version %d%s\\n\", version, version >= 300 && version < 330 ? \" es\" : \"\" );\n\n\tQ_snprintf( tmp, sizeof( tmp ), \"#define VER %d\\n\", version );\n\tQ_strncat( shader, tmp, MAX_SHADERLEN );\n\n\tfor( i = 0; i < GL2_FLAG_MAX; ++i )\n\t{\n\t\tQ_snprintf( tmp, sizeof( tmp ), \"#define %s %d\\n\", gl2wrap_flag_name[i], FBitSet( prog->flags, BIT( i )));\n\t\tQ_strncat( shader, tmp, MAX_SHADERLEN );\n\t}\n\n\tif( version >= 310 )\n\t{\n\t\tloc = 0;\n\t\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t\t{\n\t\t\tif( FBitSet( prog->flags, BIT( i )))\n\t\t\t{\n\t\t\t\tQ_snprintf( tmp, sizeof( tmp ), \"#define LOC_%s %d\\n\", gl2wrap_flag_name[i], loc++ );\n\t\t\t\tQ_strncat( shader, tmp, MAX_SHADERLEN );\n\t\t\t\tprog->attridx[i] = loc;\n\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tprog->attridx[i] = -1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( type == GL_FRAGMENT_SHADER_ARB )\n\t\tQ_strncat( shader, gl2wrap_frag_src, MAX_SHADERLEN );\n\telse\n\t\tQ_strncat( shader, gl2wrap_vert_src, MAX_SHADERLEN );\n\n\tid = pglCreateShaderObjectARB( type );\n\tlen = Q_strlen( shader );\n\tpglShaderSourceARB( id, 1, (void *)&shader, &len );\n\tpglCompileShaderARB( id );\n\tpglGetObjectParameterivARB( id, GL_OBJECT_COMPILE_STATUS_ARB, &status );\n\n\tif( status == GL_FALSE )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"%s( 0x%04x, 0x%x ): compile failed: %s\\n\", __func__, prog->flags, type, GL_PrintInfoLog( id, false ));\n\n\t\tgEngfuncs.Con_DPrintf( \"Shader text:\\n%s\\n\\n\", shader );\n\t\tpglDeleteObjectARB( id );\n\t\treturn 0;\n\t}\n\n\treturn id;\n}\n\nstatic gl2wrap_prog_t *GL2_GetProg( const GLuint flags )\n{\n\tint i, loc;\n\tGLuint status = 0, vp, fp, glprog;\n\tgl2wrap_prog_t *prog;\n\n\t// try to find existing prog matching this feature set\n\n\tif( gl2wrap.cur_prog && gl2wrap.cur_prog->flags == flags )\n\t\treturn gl2wrap.cur_prog;\n\n\tfor( i = 0; i < MAX_PROGS; ++i )\n\t{\n\t\tif( gl2wrap.progs[i].flags == flags )\n\t\t\treturn &gl2wrap.progs[i];\n\t\telse if( gl2wrap.progs[i].flags == 0 )\n\t\t\tbreak;\n\t}\n\n\tif( i == MAX_PROGS )\n\t{\n\t\tgEngfuncs.Host_Error( \"%s: Ran out of program slots for 0x%04x\\n\", __func__, flags );\n\t\treturn NULL;\n\t}\n\n\t// new prog; generate shaders\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"%s: Generating progs for 0x%04x\\n\", __func__, flags );\n\tprog = &gl2wrap.progs[i];\n\tprog->flags = flags;\n\n\tvp = GL2_GenerateShader( prog, GL_VERTEX_SHADER_ARB );\n\tfp = GL2_GenerateShader( prog, GL_FRAGMENT_SHADER_ARB );\n\tif( !vp || !fp )\n\t{\n\t\tprog->flags = 0;\n\t\treturn NULL;\n\t}\n\n\tglprog = pglCreateProgramObjectARB();\n\tpglAttachObjectARB( glprog, vp );\n\tpglAttachObjectARB( glprog, fp );\n\n\tloc = 0;\n\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t{\n\t\tif( FBitSet( flags, BIT( i )))\n\t\t{\n\t\t\tprog->attridx[i] = loc;\n\t\t\tif( gl2wrap_config.version <= 300 )\n\t\t\t\tpglBindAttribLocationARB( glprog, loc++, gl2wrap_attr_name[i] );\n\t\t\telse\n\t\t\t\tloc++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprog->attridx[i] = -1;\n\t\t}\n\t}\n\n\tpglLinkProgramARB( glprog );\n\tpglDetachObjectARB( glprog, vp );\n\tpglDetachObjectARB( glprog, fp );\n\tpglDeleteObjectARB( vp );\n\tpglDeleteObjectARB( fp );\n\n/// TODO: detect arb/core shaders in engine\n\n\tif( pglGetProgramiv )\n\t\tpglGetProgramiv( glprog, GL_OBJECT_LINK_STATUS_ARB, &status );\n\telse\n\t\tpglGetObjectParameterivARB( glprog, GL_OBJECT_LINK_STATUS_ARB, &status );\n\n\tif( status == GL_FALSE )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"%s: Failed linking progs for 0x%04x!\\n%s\\n\", __func__, prog->flags, GL_PrintInfoLog( glprog, true ));\n\t\tprog->flags = 0;\n\t\tif( pglDeleteProgram )\n\t\t\tpglDeleteProgram( glprog );\n\t\telse\n\t\t\tpglDeleteObjectARB( glprog );\n\t\treturn NULL;\n\t}\n\n\tprog->ucolor = pglGetUniformLocationARB( glprog, \"uColor\" );\n\tprog->ualpha = pglGetUniformLocationARB( glprog, \"uAlphaTest\" );\n\tprog->utex0  = pglGetUniformLocationARB( glprog, \"uTex0\" );\n\tprog->utex1  = pglGetUniformLocationARB( glprog, \"uTex1\" );\n\tprog->ufog   = pglGetUniformLocationARB( glprog, \"uFog\" );\n\tprog->uMVP   = pglGetUniformLocationARB( glprog, \"uMVP\" );\n\n\tif( gl2wrap_config.vao_mandatory )\n\t{\n\t\tprog->vao_begin = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( GLuint ));\n\t\tpglGenVertexArrays( gl2wrap_config.cycle_buffers, prog->vao_begin );\n\t}\n\tpglUseProgramObjectARB( glprog );\n\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t{\n\t\tif( prog->attridx[i] >= 0 )\n\t\t{\n\t\t\tif( gl2wrap_config.vao_mandatory || gl2wrap_config.incremental )\n\t\t\t{\n\t\t\t\tint j;\n\n\t\t\t\tfor( j = 0; j < gl2wrap_config.cycle_buffers; j++ )\n\t\t\t\t{\n\t\t\t\t\tpglBindVertexArray( prog->vao_begin[j] );\n\t\t\t\t\tpglEnableVertexAttribArrayARB( prog->attridx[i] );\n\t\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] );\n\t\t\t\t\tpglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif( gl2wrap_config.vao_mandatory )\n\t\tpglBindVertexArray( 0 );\n\n\t// these never change\n\tif( FBitSet( prog->flags, BIT( GL2_ATTR_TEXCOORD0 )) && prog->utex0 >= 0 )\n\t\tpglUniform1iARB( prog->utex0, 0 );\n\tif( FBitSet( prog->flags, BIT( GL2_ATTR_TEXCOORD1 )) && prog->utex1 >= 0 )\n\t\tpglUniform1iARB( prog->utex1, 1 );\n\tif( gl2wrap.cur_prog )\n\t\tpglUseProgramObjectARB( gl2wrap.cur_prog->glprog );\n\tprog->glprog = glprog;\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"%s: Generated progs for 0x%04x\\n\", __func__, flags );\n\n\treturn prog;\n}\n\nstatic void GL2_UpdateMVP( gl2wrap_prog_t *prog );\nstatic gl2wrap_prog_t *GL2_SetProg( const GLuint flags )\n{\n\tgl2wrap_prog_t *prog = NULL;\n\n\tif( flags && ( prog = GL2_GetProg( flags )))\n\t{\n\t\tif( prog != gl2wrap.cur_prog )\n\t\t{\n\t\t\tpglUseProgramObjectARB( prog->glprog );\n\t\t\tgl2wrap.uchanged = GL_TRUE;\n\t\t}\n\t\tif( gl2wrap.uchanged )\n\t\t{\n\t\t\tif( prog->ualpha >= 0 )\n\t\t\t\tpglUniform1fARB( prog->ualpha, gl2wrap.alpharef );\n\t\t\tif( prog->ucolor >= 0 )\n\t\t\t\tpglUniform4fvARB( prog->ucolor, 1, gl2wrap.color );\n\t\t\tif( prog->ufog >= 0 )\n\t\t\t\tpglUniform4fvARB( prog->ufog, 1, gl2wrap.fog );\n\t\t\tgl2wrap.uchanged = GL_FALSE;\n\t\t}\n\t\tGL2_UpdateMVP( prog );\n\t}\n\telse\n\t{\n\t\tpglUseProgramObjectARB( 0 );\n\t}\n\n\tgl2wrap.cur_prog = prog;\n\treturn prog;\n}\n\n#define TRIQUADS_SIZE GL2_MAX_VERTS / 4 * 6\n\nstatic void GL2_InitTriQuads( void )\n{\n\tint i;\n\tfor( i = 0; i < ( !!pglDrawRangeElementsBaseVertex ? 1 : 4 ); i++ )\n\t{\n\t\tint j;\n\t\tGLushort triquads_array[TRIQUADS_SIZE];\n\n\t\tfor( j = 0; j < TRIQUADS_SIZE / 6; j++ )\n\t\t{\n\t\t\ttriquads_array[j * 6] = j * 4 + i;\n\t\t\ttriquads_array[j * 6 + 1] = j * 4 + 1 + i;\n\t\t\ttriquads_array[j * 6 + 2] = j * 4 + 2 + i;\n\t\t\ttriquads_array[j * 6 + 3] = j * 4 + i;\n\t\t\ttriquads_array[j * 6 + 4] = j * 4 + 2 + i;\n\t\t\ttriquads_array[j * 6 + 5] = j * 4 + 3 + i;\n\t\t}\n\t\tpglGenBuffersARB( 1, &gl2wrap.triquads_ibo[i] );\n\t\trpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[i] );\n\t\tpglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, sizeof( triquads_array ), triquads_array, GL_STATIC_DRAW_ARB );\n\t}\n\n\trpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );\n}\n\nstatic void GL2_InitIncrementalBuffer( int i, GLuint size )\n{\n\tint j;\n\n\tgl2wrap.attrbufobj[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( GLuint ));\n\tif( gl2wrap_config.buf_storage )\n\t\tgl2wrap.mappings[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( void * ));\n\tpglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] );\n\n\tfor( j = 0; j < gl2wrap_config.cycle_buffers; j++ )\n\t{\n\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] );\n\t\tif( gl2wrap_config.buf_storage )\n\t\t{\n\t\t\tGLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) |\n\t\t\t\tGL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT );\n\t\t\tpglBufferStorage( GL_ARRAY_BUFFER_ARB, size, NULL, GL_MAP_WRITE_BIT | MB( gl2wrap_config.coherent, COHERENT ) | GL_MAP_PERSISTENT_BIT );\n\t\t\tgl2wrap.mappings[i][j] = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, flags );\n\t\t}\n\t\telse\n\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, size, NULL, GL_STREAM_DRAW_ARB );\n\t}\n\tif( gl2wrap_config.buf_storage )\n\t\tgl2wrap.attrbuf[i] = gl2wrap.mappings[i][0];\n}\n\n\nstatic qboolean GL2_InitProgs( void )\n{\n\tstatic const GLuint precache_progs[] = {\n\t\tBIT( GL2_ATTR_POS ),                                                                                // out = ucolor\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ),                                                    // out = tex0 * ucolor\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_ATTR_COLOR ),                            // out = tex0 * vcolor\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_ALPHA_TEST ),                       // out = tex0 * ucolor + FEAT_ALPHA_TEST\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_FLAG_FOG ),                                                          // out = ucolor + FEAT_FOG\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_FOG ),                              // out = tex0 * ucolor + FEAT_FOG\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_ATTR_COLOR ) | BIT( GL2_FLAG_FOG ),      // out = tex0 * vcolor + FEAT_FOG\n\t\tBIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_ALPHA_TEST ) | BIT( GL2_FLAG_FOG ), // out = tex0 * ucolor + FEAT_ALPHA_TEST + FEAT_FOG\n\t};\n\tconst size_t precache_progs_count = sizeof( precache_progs ) / sizeof( precache_progs[0] );\n\tint i;\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"GL2_InitProgs: Pre-generating %u progs, version %d...\\n\", (uint)( precache_progs_count ), gl2wrap_config.version );\n\tfor( i = 0; i < (int)( precache_progs_count ); ++i )\n\t\tif( !GL2_GetProg( precache_progs[i] ))\n\t\t\t\treturn false;\n\treturn true;\n}\n\n\nint GL2_ShimInit( void )\n{\n\tint i;\n\tGLuint total;\n\n\tif( gl2wrap_init )\n\t\treturn 0;\n\n\tif( !pglBindBufferARB )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"GL2_ShimInit: missing VBO, disabling\\n\" );\n\t\treturn 1;\n\t}\n\n\tif( !pglCompileShaderARB )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"GL2_ShimInit: missing shaders, disabling\\n\" );\n\t\treturn 1;\n\t}\n\n\tgl2wrap_config.vao_mandatory = gEngfuncs.Sys_CheckParm( \"-vao\" ) || glConfig.context == CONTEXT_TYPE_GL_CORE;\n\tgl2wrap_config.incremental = true;\n\tgl2wrap_config.async = true;\n\tgl2wrap_config.force_flush = false;\n\tgl2wrap_config.buf_storage = true;\n\tgl2wrap_config.coherent = true;\n\tgl2wrap_config.supports_mapbuffer = true;\n\tgl2wrap_config.cycle_buffers = 4096;\n\n\tif( !pglBufferStorage )\n\t{\n\t\tgl2wrap_config.buf_storage = false;\n\t\tgEngfuncs.Con_Printf( S_NOTE \"GL2_ShimInit: missing BufferStorage\\n\" );\n\t}\n\n\tif( !pglMapBufferRange )\n\t{\n\t\tgl2wrap_config.incremental = false;\n\t\tgl2wrap_config.supports_mapbuffer = false;\n\t\tgEngfuncs.Con_Printf( S_NOTE \"GL2_ShimInit: missing MapBufferRange, disabling incremental rendering\\n\" );\n\t}\n\n\tif( gEngfuncs.Sys_CheckParm( \"-nocoherent\" ))\n\t\tgl2wrap_config.coherent = false;\n\tif( gEngfuncs.Sys_CheckParm( \"-nobufstor\" ))\n\t\tgl2wrap_config.buf_storage = false;\n\tif( gEngfuncs.Sys_CheckParm( \"-noasync\" ))\n\t\tgl2wrap_config.async = false;\n\tif( gEngfuncs.Sys_CheckParm( \"-forceflush\" ))\n\t\tgl2wrap_config.force_flush = true;\n\tif( gEngfuncs.Sys_CheckParm( \"-nomapbuffer\" ))\n\t\tgl2wrap_config.supports_mapbuffer = false;\n\tif( gEngfuncs.Sys_CheckParm( \"-noincremental\" ))\n\t\tgl2wrap_config.incremental = gl2wrap_config.buf_storage = false;\n\n\tgl2wrap_config.version = 310;\n\tif( gEngfuncs.Sys_CheckParm( \"-minshaders\" ))\n\t\tgl2wrap_config.version = 100;\n\tif( gl2wrap_config.buf_storage )\n\t\tgl2wrap_config.incremental = gl2wrap_config.vao_mandatory = true;\n\tif( !pglBindVertexArray || !gl2wrap_config.vao_mandatory )\n\t\tgl2wrap_config.incremental = gl2wrap_config.buf_storage = gl2wrap_config.vao_mandatory = false;\n\tif( gl2wrap_config.incremental && !gl2wrap_config.buf_storage )\n\t\tgl2wrap_config.async = true;\n\tif( gl2wrap_config.incremental )\n\t\tgl2wrap_config.cycle_buffers = 4;\n\tif( !gl2wrap_config.vao_mandatory )\n\t\tgl2wrap_config.cycle_buffers = 1;\n\tgEngfuncs.Con_Printf( S_NOTE \"GL2_ShimInit: config: %s%s%s%s%s%s%sCYCLE=%d VER=%d\\n\",\n\t\tgl2wrap_config.buf_storage ? \"BUF_STOR \" : \"\",\n\t\tgl2wrap_config.buf_storage&&gl2wrap_config.coherent ? \"COHERENT \" : \"\",\n\t\tgl2wrap_config.async ? \"ASYNC \" : \"\",\n\t\tgl2wrap_config.incremental ? \"INC \" : \"\",\n\t\tgl2wrap_config.force_flush ? \"FLUSH \" : \"\",\n\t\tgl2wrap_config.vao_mandatory ? \"VAO \" : \"\",\n\t\tgl2wrap_config.supports_mapbuffer ? \"MAP \" : \"\",\n\t\tgl2wrap_config.cycle_buffers, gl2wrap_config.version );\n\n\tmemset( &gl2wrap, 0, sizeof( gl2wrap ));\n\tGL2_ShimInstall();\n\tGL2_InitTriQuads();\n\n\tgl2wrap.color[0] = 1.f;\n\tgl2wrap.color[1] = 1.f;\n\tgl2wrap.color[2] = 1.f;\n\tgl2wrap.color[3] = 1.f;\n\tgl2wrap.uchanged = GL_TRUE;\n\n\ttotal = 0;\n\n\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t{\n\t\tGLuint size = GL2_MAX_VERTS * gl2wrap_attr_size[i] * sizeof( GLfloat );\n\t\tif( !gl2wrap_config.buf_storage )\n\t\t{\n\t\t\tgl2wrap.attrbuf[i] = Mem_Calloc( r_temppool, size );\n\t\t}\n\n\t\tif( gl2wrap_config.incremental )\n\t\t{\n\t\t\tGL2_InitIncrementalBuffer( i, size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( !gl2wrap_config.incremental && gl2wrap_config.vao_mandatory )\n\t\t\t{\n\t\t\t\tgl2wrap.attrbufobj[i] = malloc( gl2wrap_config.cycle_buffers * 4 );\n\t\t\t\tpglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] );\n\t\t\t\tif( gl2wrap_config.supports_mapbuffer )\n\t\t\t\t{\n\t\t\t\t\tint j;\n\n\t\t\t\t\tfor( j = 0; j < gl2wrap_config.cycle_buffers; j++ )\n\t\t\t\t\t{\n\t\t\t\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] );\n\t\t\t\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, MAX_BEGINEND_VERTS, NULL, GL_STREAM_DRAW_ARB );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotal += size;\n\t}\n\tif( gl2wrap_config.vao_mandatory )\n\t\tpglBindVertexArray( 0 );\n\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"%s: %u bytes allocated for vertex buffer\\n\", __func__, total );\n\n\tif( !GL2_InitProgs( ))\n\t{\n\t\tgl2wrap_config.version = 300;\n\t\tif( !GL2_InitProgs( ))\n\t\t{\n\t\t\tgl2wrap_config.version = 110;\n\t\t\tif( !GL2_InitProgs( ))\n\t\t\t{\n\t\t\t\tgl2wrap_config.version = 100;\n\t\t\t\tif( !GL2_InitProgs( ))\n\t\t\t\t\tgEngfuncs.Host_Error( \"%s: Failed to compile shaders!\\n\", __func__ );\n\t\t\t}\n\t\t}\n\t}\n\n\tgl2wrap_init = 1;\n\treturn 0;\n}\n\nvoid GL2_ShimShutdown( void )\n{\n\tint i;\n\n\tif( !gl2wrap_init )\n\t\treturn;\n\n\tpglFinish();\n\tpglUseProgramObjectARB( 0 );\n\tGL2_FreeArrays();\n\tpglDeleteBuffersARB(( !!pglDrawRangeElementsBaseVertex ? 1 : 4 ), gl2wrap.triquads_ibo );\n\n\tfor( i = 0; i < MAX_PROGS; ++i )\n\t{\n\t\tif( gl2wrap.progs[i].flags )\n\t\t{\n\t\t\tpglDeleteProgram( gl2wrap.progs[i].glprog );\n\t\t\tif( gl2wrap.progs[i].vao_begin )\n\t\t\t{\n\t\t\t\tpglDeleteVertexArrays( gl2wrap_config.cycle_buffers, gl2wrap.progs[i].vao_begin );\n\t\t\t\tMem_Free( gl2wrap.progs[i].vao_begin );\n\t\t\t}\n\t\t}\n\n\t}\n\n\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t{\n\t\tint j;\n\t\tif( gl2wrap_config.buf_storage )\n\t\t{\n\t\t\tfor( j = 0; j < gl2wrap_config.cycle_buffers; j++ )\n\t\t\t{\n\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] );\n\t\t\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\t\t}\n\t\t}\n\t\tif( gl2wrap.attrbufobj[i] )\n\t\t{\n\t\t\tpglDeleteBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] );\n\t\t\tMem_Free( gl2wrap.attrbufobj[i] );\n\t\t}\n\t\tif( gl2wrap.mappings[i] )\n\t\t\tMem_Free( gl2wrap.mappings[i] );\n\n\t\tif( !gl2wrap_config.buf_storage )\n\t\t\tMem_Free( gl2wrap.attrbuf[i] );\n\t}\n\n\tmemset( &gl2wrap, 0, sizeof( gl2wrap ));\n\n\tgl2wrap_init = 0;\n}\n\nstatic void GL2_ResetPersistentBuffer( void )\n{\n\tint i;\n\n#ifdef QUAD_BATCH\n\tGL2_FlushPrims();\n#endif\n\tgl2wrap.end = gl2wrap.begin = 0;\n\n\tif( gl2wrap_config.incremental )\n\t{\n\t\tgl2wrap.attrbufcycle = ( gl2wrap.attrbufcycle + 1 ) % gl2wrap_config.cycle_buffers;\n\t\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t\t{\n\t\t\tint size = GL2_MAX_VERTS * gl2wrap_attr_size[i] * sizeof( GLfloat );\n\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] );\n\t\t\tif( gl2wrap_config.buf_storage )\n\t\t\t{\n\t\t\t\tGLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) |\n\t\t\t\t\tGL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT );\n\t\t\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\t\t\tgl2wrap.mappings[i][gl2wrap.attrbufcycle] = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, flags );\n\t\t\t\tgl2wrap.attrbuf[i] = gl2wrap.mappings[i][gl2wrap.attrbufcycle];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvoid *mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT );\n\t\t\t\t(void)mem;\n\t\t\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\t\t}\n\n\t\t}\n\t}\n}\n\n\nvoid GL2_ShimEndFrame( void )\n{\n#ifdef QUAD_BATCH\n\tGL2_FlushPrims();\n#endif\n}\n\nstatic void APIENTRY GL2_Begin( GLenum prim )\n{\n\tint i;\n\tif( gl2wrap.begin + MAX_BEGINEND_VERTS > GL2_MAX_VERTS )\n\t\tGL2_ResetPersistentBuffer();\n\n#ifdef QUAD_BATCH\n\tif( gl2wrap.prim == GL_QUADS && gl2wrap_quad.active )\n\t{\n\t\tGLuint flags = gl2wrap.cur_flags;\n\t\tGLuint flags2 = gl2wrap.cur_flags;\n\n\t\tif( gl2wrap_quad.flags != flags || prim != GL_QUADS )\n\t\t\tGL2_FlushPrims();\n\t\telse if( gl2wrap_quad.flags == flags && prim == GL_QUADS )\n\t\t\treturn;\n\t}\n\tgl2wrap_quad.active = false;\n#endif\n\tgl2wrap.prim = prim;\n\tgl2wrap.begin = gl2wrap.end;\n\t// pos always enabled\n\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_POS ));\n}\n\n/*\n==============================\nUpdateIncrementalBuffer\n\nglBufferStorage allows to write directly without mapping buffer every time before draw\nThis allows keep VAO unchanged and fastly build needed pipeline in driver for every program variant\nWhen buffer storage not supported, we still may use cached VAO, but map/unmap it every time writing new data\n==============================\n*/\nstatic void GL2_UpdateIncrementalBuffer( gl2wrap_prog_t *prog, int count )\n{\n\tint i;\n\tif( !gl2wrap_config.buf_storage )\n\t{\n\t\tfor( i = 0; i < GL2_ATTR_MAX; i++ )\n\t\t{\n\t\t\tif( prog->attridx[i] >= 0 )\n\t\t\t{\n\t\t\t\tvoid *mem;\n\t\t\t\tGLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT |\n\t\t\t\t\tMB( gl2wrap_config.async, UNSYNCHRONIZED ) |\n\t\t\t\t\tMB( gl2wrap_config.force_flush, FLUSH_EXPLICIT );\n\t\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] );\n\t\t\t\tmem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count, flags );\n\t\t\t\tmemcpy( mem, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count );\n\t\t\t\tif( gl2wrap_config.force_flush )\n\t\t\t\t\tpglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count );\n\t\t\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\t\t}\n\t\t}\n\t}\n\telse if( !gl2wrap_config.coherent )\n\t{\n\t\t// non-coherent buffers anyway require unmapping or flushing after write\n\t\tfor( i = 0; i < GL2_ATTR_MAX; i++ )\n\t\t{\n\t\t\tif( prog->attridx[i] >= 0 )\n\t\t\t{\n\t\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] );\n\t\t\t\tpglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count );\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void GL2_FlushPrims( void )\n{\n\tint i;\n\tint startindex = 0;\n\tGLuint flags = gl2wrap.cur_flags;\n\tGLint count = gl2wrap.end - gl2wrap.begin;\n\tgl2wrap_prog_t *prog;\n\n\tif( !gl2wrap.prim || !count )\n\t\tgoto leave_label; // end without begin\n\n\t// enable alpha test and fog if needed\n\tif( gl2wrap_state.alpha_test )\n\t\tSetBits( flags, BIT( GL2_FLAG_ALPHA_TEST ));\n\tif( gl2wrap_state.fog )\n\t\tSetBits( flags, BIT( GL2_FLAG_FOG ));\n\n\t// disable all vertex attrib pointers\n\tif( !gl2wrap_config.vao_mandatory )\n\t{\n\t\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t\t\tpglDisableVertexAttribArrayARB( i );\n\t}\n\n\tprog = GL2_SetProg( flags );\n\tif( !prog )\n\t{\n\t\tgEngfuncs.Host_Error( \"%s: Could not find program for flags 0x%04x!\\n\", __func__, flags );\n\t\tgoto leave_label;\n\t}\n\n\tif( gl2wrap_config.incremental )\n\t{\n\t\tGL2_UpdateIncrementalBuffer( prog, count );\n\t\tpglBindVertexArray( prog->vao_begin[gl2wrap.attrbufcycle] );\n\t\tstartindex = gl2wrap.begin;\n\t}\n\telse\n\t{\n\t\tif( gl2wrap_config.vao_mandatory )\n\t\t\tpglBindVertexArray( prog->vao_begin[gl2wrap.attrbufcycle] );\n\t\tfor( i = 0; i < GL2_ATTR_MAX; ++i )\n\t\t{\n\t\t\tif( prog->attridx[i] >= 0 )\n\t\t\t{\n\t\t\t\tif(( gl2wrap_config.vao_mandatory && !gl2wrap_config.supports_mapbuffer ) || !gl2wrap_config.vao_mandatory )\n\t\t\t\t\tpglEnableVertexAttribArrayARB( prog->attridx[i] );\n\t\t\t\tif( gl2wrap_config.vao_mandatory )\n\t\t\t\t{\n\t\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] );\n\t\t\t\t\tif( gl2wrap_config.supports_mapbuffer )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( gl2wrap_attr_size[i] * 4 * count > MAX_BEGINEND_VERTS )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * count, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, GL_STREAM_DRAW_ARB );\n\t\t\t\t\t\t\tpglEnableVertexAttribArrayARB( prog->attridx[i] );\n\t\t\t\t\t\t\tpglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 );\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tGLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT |\n\t\t\t\t\t\t\t\tMB( gl2wrap_config.async, UNSYNCHRONIZED ) | MB( gl2wrap_config.force_flush, FLUSH_EXPLICIT );\n\t\t\t\t\t\t\tvoid *mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count, flags );\n\t\t\t\t\t\t\tmemcpy( mem, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count );\n\t\t\t\t\t\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * count, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, GL_STREAM_DRAW_ARB );\n\n\t\t\t\t\tif( gl2wrap_config.vao_mandatory && !gl2wrap_config.supports_mapbuffer )\n\t\t\t\t\t\tpglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 );\n\n\t\t\t\t}\n\t\t\t\telse // if vao is not mandatory, try use client pointers here\n\t\t\t\t\tpglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin );\n\t\t\t}\n\t\t}\n\t\tgl2wrap.attrbufcycle = ( gl2wrap.attrbufcycle + 1 ) % gl2wrap_config.cycle_buffers;\n\t}\n\n\tif( gl2wrap.prim == GL_QUADS )\n\t{\n\t\t// simple case, one quad may draw like polygon(4)\n\t\tif( count == 4 )\n\t\t\trpglDrawArrays( GL_TRIANGLE_FAN, startindex, count );\n\t\telse if( pglDrawRangeElementsBaseVertex )\n\t\t{\n\t\t\t/*\n\t\t\t * OpenGL deprecated QUADS, but made some workarounds availiable\n\t\t\t * idea: bound static index array that will repeat 0 1 2 0 2 3 4 5 6 4 6 7...\n\t\t\t * sequence and draw source arrays. But our array may have different offset\n\t\t\t * When DrawRangeElementsBaseVertex unavailiable, we need build 4 different index arrays (as sequence have period 4)\n\t\t\t * or just put 0-4 offset when it's availiable\n\t\t\t * */\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[0] );\n\t\t\tpglDrawRangeElementsBaseVertex( GL_TRIANGLES, startindex, startindex + count,\n\t\t\t\tQ_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT,\n\t\t\t\t(void *)(size_t)( startindex / 4 * 6 * 2 ), startindex % 4 );\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );\n\t\t}\n\t\telse if( rpglDrawRangeElements )\n\t\t{\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[startindex % 4] );\n\t\t\trpglDrawRangeElements( GL_TRIANGLES, startindex, startindex + count,\n\t\t\t\tQ_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT,\n\t\t\t\t(void *)(size_t)( startindex / 4 * 6 * 2 ));\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[startindex % 4] );\n\t\t\trpglDrawElements( GL_TRIANGLES,\n\t\t\t\tQ_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT,\n\t\t\t\t(void *)(size_t)( startindex / 4 * 6 * 2 ));\n\t\t\tpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );\n\t\t}\n\t}\n\telse if( gl2wrap.prim == GL_POLYGON ) // does it have any difference with TRIFAN?\n\t\trpglDrawArrays( GL_TRIANGLE_FAN, startindex, count );\n\telse // TRIANGLES, LINES, TRISTRIP, TRIFAN supported anyway\n\t\trpglDrawArrays( gl2wrap.prim, startindex, count );\n\nleave_label:\n\tif( gl2wrap_config.vao_mandatory )\n\t{\n\t\tpglBindVertexArray( 0 );\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\t}\n\n\tgl2wrap.prim = GL_NONE;\n\tgl2wrap.begin = gl2wrap.end;\n\tgl2wrap.cur_flags = 0;\n#ifdef QUAD_BATCH\n\tgl2wrap_quad.active = 0;\n#endif\n}\n\n\nstatic void APIENTRY GL2_End( void )\n{\n\tint i;\n#ifdef QUAD_BATCH\n\tif( gl2wrap.prim == GL_QUADS )\n\t{\n\t\tGLuint flags = gl2wrap.cur_flags;\n\t\t// enable alpha test and fog if needed\n\t\t/*if( alpha_test_state )\n\t\t\tSetBits( flags, BIT( GL2_FLAG_ALPHA_TEST ));\n\t\tif( fogging )\n\t\t\tSetBits( flags, BIT( GL2_FLAG_FOG ));*/\n\t\tgl2wrap_quad.flags = flags;\n\t\tgl2wrap_quad.active = 1;\n\t\treturn;\n\t}\n#endif\n\tGL2_FlushPrims();\n}\n\nstatic void (APIENTRY *rpglTexImage2D)( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels );\nstatic void APIENTRY GL2_TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )\n{\n\tvoid *data = (void *)pixels;\n\tif( pixels && format == GL_RGBA && (\n\t\tinternalformat == GL_RGB ||\n\t\tinternalformat == GL_RGB8 ||\n\t\tinternalformat == GL_RGB5 ||\n\t\tinternalformat == GL_LUMINANCE ||\n\t\tinternalformat == GL_LUMINANCE8 ||\n\t\tinternalformat == GL_LUMINANCE4 )) // strip alpha from texture\n\t{\n\t\tunsigned char *in = data, *out;\n\t\tint i = 0, size = width * height * 4;\n\n\t\tdata = out = (unsigned char *)malloc( size );\n\n\t\tfor( i = 0; i < size; i += 4, in += 4, out += 4 )\n\t\t{\n\t\t\tmemcpy( out, in, 3 );\n\t\t\tout[3] = 255;\n\t\t}\n\t\tinternalformat = format;\n\t}\n\tif( internalformat == GL_LUMINANCE8_ALPHA8 || internalformat == GL_RGB || internalformat == GL_RGB8 || internalformat == GL_RGB5 )\n\t\tinternalformat = GL_RGBA;\n\trpglTexImage2D( target, level, internalformat, width, height, border, format, type, data );\n\tif( data != pixels )\n\t\tfree( data );\n}\n\nstatic void (APIENTRY *rpglTexParameteri)( GLenum target, GLenum pname, GLint param );\nstatic void APIENTRY GL2_TexParameteri( GLenum target, GLenum pname, GLint param )\n{\n\tif( pname == GL_TEXTURE_BORDER_COLOR )\n\t{\n\t\treturn; // not supported by opengl es\n\t}\n\tif(( pname == GL_TEXTURE_WRAP_S ||\n\t\tpname == GL_TEXTURE_WRAP_T ) &&\n\t\tparam == GL_CLAMP )\n\t{\n\t\tparam = GL_CLAMP_TO_EDGE;\n\t}\n\n\trpglTexParameteri( target, pname, param );\n}\n\n\nGLboolean (APIENTRY *rpglIsEnabled)( GLenum e );\nstatic GLboolean APIENTRY GL2_IsEnabled( GLenum e )\n{\n\tif( e == GL_FOG )\n\t\treturn gl2wrap_state.fog;\n\treturn rpglIsEnabled( e );\n}\n\nstatic void APIENTRY GL2_Vertex3f( GLfloat x, GLfloat y, GLfloat z )\n{\n\tGLfloat *p = gl2wrap.attrbuf[GL2_ATTR_POS] + gl2wrap.end * 3;\n\t*p++ = x;\n\t*p++ = y;\n\t*p++ = z;\n\n\tif( FBitSet( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR )))\n\t{\n\t\tGLfloat *p = gl2wrap.attrbuf[GL2_ATTR_COLOR] + gl2wrap.end * 4;\n\t\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR ));\n\t\t*p++ = gl2wrap.color[0];\n\t\t*p++ = gl2wrap.color[1];\n\t\t*p++ = gl2wrap.color[2];\n\t\t*p++ = gl2wrap.color[3];\n\t}\n\t++gl2wrap.end;\n\n\tif( gl2wrap.prim == GL_QUADS )\n\t{\n\t\tif( !( ( gl2wrap.end - gl2wrap.begin ) % 4 ) && gl2wrap.end > ( GL2_MAX_VERTS - 4 ) )\n\t\t{\n\t\t\tGL2_FlushPrims();\n\t\t\tGL2_Begin( GL_QUADS );\n\t\t}\n\t}\n\telse if( gl2wrap.end - gl2wrap.begin >= MAX_BEGINEND_VERTS )\n\t{\n\t\tGLenum prim = gl2wrap.prim;\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"GL2_Vertex3f: Vertex buffer overflow!\\n\" );\n\t\tGL2_FlushPrims();\n\t\tGL2_Begin( prim );\n\t}\n}\n\nstatic void APIENTRY GL2_Vertex2f( GLfloat x, GLfloat y )\n{\n\tGL2_Vertex3f( x, y, 0.f );\n}\n\nstatic void APIENTRY GL2_Vertex3fv( const GLfloat *v )\n{\n\tGL2_Vertex3f( v[0], v[1], v[2] );\n}\n\nstatic void APIENTRY GL2_Color4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a )\n{\n#ifdef QUAD_BATCH\n\tif( gl2wrap_quad.active )\n\t{\n\t\tif( !( gl2wrap.color[0] == r && gl2wrap.color[1] == g && gl2wrap.color[2] == b && gl2wrap.color[3] == a ))\n\t\t\tGL2_FlushPrims();\n\t}\n#endif\n\tgl2wrap.color[0] = r;\n\tgl2wrap.color[1] = g;\n\tgl2wrap.color[2] = b;\n\tgl2wrap.color[3] = a;\n\tgl2wrap.uchanged = GL_TRUE;\n#ifdef QUAD_BATCH\n\tif( gl2wrap_quad.active )\n\t\treturn;\n#endif\n\tif( gl2wrap.prim )\n\t{\n\t\t// HACK: enable color attribute if we're using color inside a Begin-End pair\n\t\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR ));\n\t}\n}\n\nstatic void APIENTRY GL2_Color3f( GLfloat r, GLfloat g, GLfloat b )\n{\n\tGL2_Color4f( r, g, b, 1.f );\n}\n\nstatic void APIENTRY GL2_Color4ub( GLubyte r, GLubyte g, GLubyte b, GLubyte a )\n{\n\tGL2_Color4f((GLfloat)r / 255.f, (GLfloat)g / 255.f, (GLfloat)b / 255.f, (GLfloat)a / 255.f );\n}\n\nstatic void APIENTRY GL2_Color4ubv( const GLubyte *v )\n{\n\tGL2_Color4ub( v[0], v[1], v[2], v[3] );\n}\n\nstatic void APIENTRY GL2_TexCoord2f( GLfloat u, GLfloat v )\n{\n\t// by spec glTexCoord always updates texunit 0\n\tGLfloat *p = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD0] + gl2wrap.end * 2;\n\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD0 ));\n\t*p++ = u;\n\t*p++ = v;\n}\n\nstatic void APIENTRY GL2_MultiTexCoord2f( GLenum tex, GLfloat u, GLfloat v )\n{\n\tGLfloat *p;\n\n\t// assume there can only be two\n\tif( tex == GL_TEXTURE0_ARB )\n\t{\n\t\tp = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD0] + gl2wrap.end * 2;\n\t\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD0 ));\n\t}\n\telse\n\t{\n\t\tp = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD1] + gl2wrap.end * 2;\n\t\tSetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD1 ));\n\t}\n\t*p++ = u;\n\t*p++ = v;\n}\n\n\nstatic void APIENTRY GL2_AlphaFunc( GLenum mode, GLfloat ref )\n{\n\tgl2wrap.alpharef = ref;\n\tgl2wrap.uchanged = GL_TRUE;\n\t// mode is always GL_GREATER\n}\n\nstatic void APIENTRY GL2_Fogf( GLenum param, GLfloat val )\n{\n\tif( param == GL_FOG_DENSITY )\n\t{\n\t\tgl2wrap.fog[3] = val;\n\t\tgl2wrap.uchanged = GL_TRUE;\n\t}\n}\n\nstatic void APIENTRY GL2_Fogfv( GLenum param, const GLfloat *val )\n{\n\tif( param == GL_FOG_COLOR )\n\t{\n\t\tgl2wrap.fog[0] = val[0];\n\t\tgl2wrap.fog[1] = val[1];\n\t\tgl2wrap.fog[2] = val[2];\n\t\tgl2wrap.uchanged = GL_TRUE;\n\t}\n}\n\nstatic qboolean GL2_SkipEnable( GLenum e )\n{\n\treturn e == GL_TEXTURE_2D || e == GL_TEXTURE_1D;\n}\n\nstatic qboolean GL2_CatchEnable( GLenum e, qboolean enable )\n{\n\tif( e == GL_FOG )\n\t\tgl2wrap_state.fog = enable;\n\telse if( e == GL_ALPHA_TEST )\n\t\tgl2wrap_state.alpha_test = enable;\n\telse\n\t\treturn false;\n\treturn true;\n}\n\nstatic void APIENTRY GL2_Enable( GLenum e )\n{\n\tif( !GL2_SkipEnable( e ) && !GL2_CatchEnable( e, true ))\n\t\trpglEnable( e );\n}\n\n\nstatic void APIENTRY GL2_Disable( GLenum e )\n{\n\tif( !GL2_SkipEnable( e ) && !GL2_CatchEnable( e, false ))\n\t\trpglDisable( e );\n}\n\n/*\n===========================\n\nLimited matrix emulation\n\n===========================\n*/\n\nstatic void APIENTRY GL2_MatrixMode( GLenum m )\n{\n//\tif( gl2wrap_matrix.mode == m )\n//\t\treturn;\n#ifdef QUAD_BATCH\n\tGL2_FlushPrims();\n#endif\n\tgl2wrap_matrix.mode = m;\n\tswitch( m )\n\t{\n\tcase GL_MODELVIEW:\n\t\tgl2wrap_matrix.current = gl2wrap_matrix.mv;\n\t\tbreak;\n\tcase GL_PROJECTION:\n\t\tgl2wrap_matrix.current = gl2wrap_matrix.pr;\n\t\tbreak;\n\tdefault:\n\t\tgl2wrap_matrix.current = gl2wrap_matrix.dummy;\n\t\tbreak;\n\t}\n}\n\nstatic void APIENTRY GL2_LoadIdentity( void )\n{\n\tfloat *m = (float *)gl2wrap_matrix.current;\n\tm[1]  = m[2]  = m[3]  = m[4]  = 0.0f;\n\tm[6]  = m[7]  = m[8]  = m[9]  = 0.0f;\n\tm[11] = m[12] = m[13] = m[14] = 0.0f;\n\tm[0]  = m[5]  = m[10] = m[15] = 1.0f;\n\tgl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF;\n}\n\nstatic void APIENTRY GL2_Ortho( double l, double r, double b, double t, double n, double f )\n{\n\tGLfloat m0  = 2 / ( r - l );\n\tGLfloat m5  = 2 / ( t - b );\n\tGLfloat m10 = - 2 / ( f - n );\n\tGLfloat m12 = - ( r + l ) / ( r - l );\n\tGLfloat m13 = - ( t + b ) / ( t - b );\n\tGLfloat m14 = - ( f + n ) / ( f - n );\n\tfloat *m = gl2wrap_matrix.current;\n\n\tm[12] += m12 * m[0] + m13 * m[4] + m14 * m[8];\n\tm[13] += m12 * m[1] + m13 * m[5] + m14 * m[9];\n\tm[14] += m12 * m[2] + m13 * m[6] + m14 * m[10];\n\tm[15] += m12 * m[3] + m13 * m[7] + m14 * m[11];\n\tm[0]  *= m0;\n\tm[1]  *= m0;\n\tm[2]  *= m0;\n\tm[3]  *= m0;\n\tm[4]  *= m5;\n\tm[5]  *= m5;\n\tm[6]  *= m5;\n\tm[7]  *= m5;\n\tm[8]  *= m10;\n\tm[9]  *= m10;\n\tm[10] *= m10;\n\tm[11] *= m10;\n\tgl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF;\n}\n\nstatic void GL2_Mul4x4( const GLfloat *in0, const GLfloat *in1, GLfloat *out )\n{\n\tout[0]  = in0[0] * in1[0] + in0[1] * in1[4] + in0[2] * in1[8] + in0[3] * in1[12];\n\tout[1]  = in0[0] * in1[1] + in0[1] * in1[5] + in0[2] * in1[9] + in0[3] * in1[13];\n\tout[2]  = in0[0] * in1[2] + in0[1] * in1[6] + in0[2] * in1[10] + in0[3] * in1[14];\n\tout[3]  = in0[0] * in1[3] + in0[1] * in1[7] + in0[2] * in1[11] + in0[3] * in1[15];\n\tout[4]  = in0[4] * in1[0] + in0[5] * in1[4] + in0[6] * in1[8] + in0[7] * in1[12];\n\tout[5]  = in0[4] * in1[1] + in0[5] * in1[5] + in0[6] * in1[9] + in0[7] * in1[13];\n\tout[6]  = in0[4] * in1[2] + in0[5] * in1[6] + in0[6] * in1[10] + in0[7] * in1[14];\n\tout[7]  = in0[4] * in1[3] + in0[5] * in1[7] + in0[6] * in1[11] + in0[7] * in1[15];\n\tout[8]  = in0[8] * in1[0] + in0[9] * in1[4] + in0[10] * in1[8] + in0[11] * in1[12];\n\tout[9]  = in0[8] * in1[1] + in0[9] * in1[5] + in0[10] * in1[9] + in0[11] * in1[13];\n\tout[10] = in0[8] * in1[2] + in0[9] * in1[6] + in0[10] * in1[10] + in0[11] * in1[14];\n\tout[11] = in0[8] * in1[3] + in0[9] * in1[7] + in0[10] * in1[11] + in0[11] * in1[15];\n\tout[12] = in0[12] * in1[0] + in0[13] * in1[4] + in0[14] * in1[8] + in0[15] * in1[12];\n\tout[13] = in0[12] * in1[1] + in0[13] * in1[5] + in0[14] * in1[9] + in0[15] * in1[13];\n\tout[14] = in0[12] * in1[2] + in0[13] * in1[6] + in0[14] * in1[10] + in0[15] * in1[14];\n\tout[15] = in0[12] * in1[3] + in0[13] * in1[7] + in0[14] * in1[11] + in0[15] * in1[15];\n}\n\nstatic void GL2_UpdateMVP( gl2wrap_prog_t *prog )\n{\n\t// use bitset to determine if need update matrix for this prog\n\tif( FBitSet( gl2wrap_matrix.update, BIT64( prog->flags )))\n\t{\n\t\tClearBits( gl2wrap_matrix.update, BIT64( prog->flags ));\n\t\tGL2_Mul4x4( gl2wrap_matrix.mv, gl2wrap_matrix.pr, gl2wrap_matrix.mvp );\n\t\tpglUniformMatrix4fvARB( prog->uMVP, 1, false, (void *)gl2wrap_matrix.mvp );\n\t}\n}\n\nstatic void APIENTRY GL2_LoadMatrixf( const GLfloat *m )\n{\n\tmemcpy( gl2wrap_matrix.current, m, 16 * sizeof( float ));\n\tgl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF;\n}\n\n#if XASH_GLES\nstatic void ( APIENTRY *_pglDepthRangef)( GLfloat zFar, GLfloat zNear );\nstatic void APIENTRY GL2_DepthRange( GLdouble zFar, GLdouble zNear )\n{\n\t_pglDepthRangef( zFar, zNear );\n}\n#endif\n\n/*\n=====================\n\nArray drawing\n\n=====================\n*/\ntypedef struct gl2wrap_arraypointer_s\n{\n\tconst void *userptr;\n\tGLint size;\n\tGLenum type;\n\tGLsizei stride;\n\tGLuint vbo, *vbo_fb, vbo_cycle;\n} gl2wrap_arraypointer_t;\n\nstatic struct\n{\n\tgl2wrap_arraypointer_t ptr[GL2_ATTR_MAX];\n\tunsigned int flags;\n\t//unsigned int vbo_flags;\n\tGLuint stream_buffer;\n\tvoid *stream_pointer;\n\tsize_t stream_counter;\n\tGLuint vao_dynamic;\n} gl2wrap_arrays;\n\nstatic void GL2_SetPointer( int idx, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer )\n{\n\tgl2wrap_arrays.ptr[idx].size = size;\n\tgl2wrap_arrays.ptr[idx].type = type;\n\tgl2wrap_arrays.ptr[idx].stride = stride;\n\tgl2wrap_arrays.ptr[idx].userptr = pointer;\n\tgl2wrap_arrays.ptr[idx].vbo = gl2wrap_state.vbo;\n//\tif( vbo )\n//\t\tSetBits( gl2wrap_arrays.vbo_flags, BIT( idx );\n//\telse\n//\t\tClearBits( gl2wrap_arrays.vbo_flags, BIT( idx );\n}\n\nstatic void APIENTRY GL2_VertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer )\n{\n\tGL2_SetPointer( GL2_ATTR_POS, size, type, stride, pointer );\n}\n\nstatic void APIENTRY GL2_ColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer )\n{\n\tGL2_SetPointer( GL2_ATTR_COLOR, size, type, stride, pointer );\n}\n\nstatic void APIENTRY GL2_TexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer )\n{\n\tGL2_SetPointer( GL2_ATTR_TEXCOORD0 + gl2wrap_state.tmu, size, type, stride, pointer );\n}\n\nstatic unsigned int GL2_GetArrIdx( GLenum array )\n{\n\tswitch( array )\n\t{\n\tcase GL_VERTEX_ARRAY:\n\t\treturn GL2_ATTR_POS;\n\tcase GL_COLOR_ARRAY:\n\t\treturn GL2_ATTR_COLOR;\n\tcase GL_TEXTURE_COORD_ARRAY:\n\t\tASSERT( gl2wrap_state.tmu < 2 );\n\t\treturn GL2_ATTR_TEXCOORD0 + gl2wrap_state.tmu;\n\t}\n\treturn 0;\n}\n\nstatic void APIENTRY GL2_EnableClientState( GLenum array )\n{\n\tunsigned int idx = GL2_GetArrIdx( array );\n\tSetBits( gl2wrap_arrays.flags, BIT( idx ));\n}\n\nstatic void APIENTRY GL2_DisableClientState( GLenum array )\n{\n\tunsigned int idx = GL2_GetArrIdx( array );\n\tClearBits( gl2wrap_arrays.flags, BIT( idx ));\n}\n\n\n/*\n===========================\nUploadBufferData\n\nDumb buffer upload\nUsed when uploading very large buffers or when persistent/incremental buffers disabled (MapBuffer unavailiable?)\n===========================\n */\nstatic void GL2_UploadBufferData( gl2wrap_prog_t *prog, int size, GLuint start, GLuint end, int stride, int attr )\n{\n\tif( !gl2wrap_arrays.ptr[attr].vbo_fb )\n\t{\n\t\tgl2wrap_arrays.ptr[attr].vbo_fb = malloc( 4 * gl2wrap_config.cycle_buffers );\n\t\tpglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap_arrays.ptr[attr].vbo_fb );\n\t}\n\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.ptr[attr].vbo_fb[gl2wrap_arrays.ptr[attr].vbo_cycle] );\n\tgl2wrap_arrays.ptr[attr].vbo_cycle = ( gl2wrap_arrays.ptr[attr].vbo_cycle + 1 ) % gl2wrap_config.cycle_buffers;\n\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, end * stride, gl2wrap_arrays.ptr[attr].userptr, GL_STREAM_DRAW_ARB );\n\tpglVertexAttribPointerARB( prog->attridx[attr], gl2wrap_arrays.ptr[attr].size, gl2wrap_arrays.ptr[attr].type, attr == GL2_ATTR_COLOR, gl2wrap_arrays.ptr[attr].stride, 0 );\n}\n/*\n===========================\nUpdatePersistentArrayBuffer\n\nPersistent array always mapped to stream_pointer with BufferStorage\njust memcopy it into and flush when overflowed\n===========================\n */\nstatic void GL2_UpdatePersistentArrayBuffer( gl2wrap_prog_t *prog, int size, int offset, GLuint start, GLuint end, int stride, int attr )\n{\n\tif( gl2wrap_arrays.stream_counter + size > GL2_MAX_VERTS * 64 )\n\t{\n\t\tGLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) |\n\t\t\tGL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT );\n\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t\tgl2wrap_arrays.stream_counter = 0;\n\t\tgl2wrap_arrays.stream_pointer = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GL2_MAX_VERTS * 64, flags );\n\t\t//i = -1;\n\t\t//continue;\n\t\tsize = end * stride, offset = 0;\n\t}\n\n\tmemcpy(((char *)gl2wrap_arrays.stream_pointer ) + gl2wrap_arrays.stream_counter, ((char *)gl2wrap_arrays.ptr[attr].userptr ) + offset, size );\n\tif( !gl2wrap_config.coherent )\n\t\tpglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_counter, size );\n\tpglVertexAttribPointerARB( prog->attridx[attr], gl2wrap_arrays.ptr[attr].size, gl2wrap_arrays.ptr[attr].type, attr == GL2_ATTR_COLOR, gl2wrap_arrays.ptr[attr].stride, (void *)( gl2wrap_arrays.stream_counter - offset ));\n\tgl2wrap_arrays.stream_counter += size;\n}\n\n/*\n===========================\nUpdateIncrementalArrayBuffer\n\nLike persistent buffer, but map every time when copying data when BufferStorage unavailiable\n===========================\n */\nstatic void GL2_UpdateIncrementalArrayBuffer( gl2wrap_prog_t *prog, int size, int offset, GLuint start, GLuint end, int stride, int attr )\n{\n\tvoid *mem;\n\tqboolean inv = false;\n\tGLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | MB( inv,INVALIDATE_BUFFER ) |\n\t\tMB( gl2wrap_config.async, UNSYNCHRONIZED ) | MB( gl2wrap_config.force_flush, FLUSH_EXPLICIT );\n\n\tif( gl2wrap_arrays.stream_counter + size > GL2_MAX_VERTS * 64 )\n\t{\n\t\tsize = end * stride;\n\t\toffset = 0;\n\t\tgl2wrap_arrays.stream_counter = 0;\n\t\tinv = true;\n\t}\n\tmem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_counter, size, flags );\n\tmemcpy( mem, ((char *)gl2wrap_arrays.ptr[attr].userptr ) + offset, size );\n\tif( gl2wrap_config.force_flush )\n\t\tpglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, size );\n\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\tpglVertexAttribPointerARB( prog->attridx[attr], gl2wrap_arrays.ptr[attr].size, gl2wrap_arrays.ptr[attr].type, attr == GL2_ATTR_COLOR, gl2wrap_arrays.ptr[attr].stride, (void *)( gl2wrap_arrays.stream_counter - offset ));\n\tgl2wrap_arrays.stream_counter += size;\n}\n\n/*\n===========================\nAllocArrayPersistenStorage\n\nPrepare BufferStorage\n===========================\n */\nstatic void GL2_AllocArrayPersistenStorage( void )\n{\n\tGLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) |\n\t\tGL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT );\n\tpglGenBuffersARB( 1, &gl2wrap_arrays.stream_buffer );\n\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer );\n\tpglBufferStorage( GL_ARRAY_BUFFER_ARB, GL2_MAX_VERTS * 64, NULL, GL_MAP_WRITE_BIT | MB( gl2wrap_config.coherent, COHERENT ) | GL_MAP_PERSISTENT_BIT );\n\tgl2wrap_arrays.stream_pointer = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GL2_MAX_VERTS * 64, flags );\n}\n\nstatic void GL2_AllocArrays( void )\n{\n\tif( gl2wrap_config.buf_storage && !gl2wrap_arrays.stream_pointer )\n\t\tGL2_AllocArrayPersistenStorage();\n\telse if( !gl2wrap_config.buf_storage && gl2wrap_config.incremental && !gl2wrap_arrays.stream_buffer )\n\t{\n\t\t// prepare incremental buffer\n\t\tpglGenBuffersARB( 1, &gl2wrap_arrays.stream_buffer );\n\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer );\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, GL2_MAX_VERTS * 64, NULL, GL_STREAM_DRAW_ARB );\n\t}\n}\n\nstatic void GL2_FreeArrays( void )\n{\n\tif( gl2wrap_arrays.vao_dynamic )\n\t\tpglDeleteVertexArrays( 1, &gl2wrap_arrays.vao_dynamic );\n\tif( gl2wrap_arrays.stream_pointer )\n\t{\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer );\n\t\tpglUnmapBufferARB( GL_ARRAY_BUFFER_ARB );\n\t}\n\tpglDeleteBuffersARB( 1, &gl2wrap_arrays.stream_buffer );\n\tmemset( &gl2wrap_arrays, 0, sizeof( gl2wrap_arrays ));\n}\n\n\n/*\n======================\nSetupArrays\n\nIf vao usage mandatory, use persistent/incremental buffers when possible\nelse just set client pointers to default VAO\nUsage of client pointers is forbidden with non-default VAO and unavailiable in Core\n======================\n*/\nstatic void GL2_SetupArrays( GLuint start, GLuint end )\n{\n\tgl2wrap_prog_t *prog;\n\tunsigned int flags = gl2wrap_arrays.flags;\n\tint i;\n\n\tif( !flags )\n\t\treturn; // Legacy pointers not used\n\n#ifdef QUAD_BATCH\n\tGL2_FlushPrims();\n#endif\n\n\tif( gl2wrap_state.alpha_test )\n\t\tSetBits( flags, BIT( GL2_FLAG_ALPHA_TEST ));\n\tif( gl2wrap_state.fog )\n\t\tSetBits( flags, BIT( GL2_FLAG_FOG ));\n\tprog = GL2_SetProg( flags );// | GL2_ATTR_TEXCOORD0 );\n\tif( !prog )\n\t\treturn;\n\n\tif( gl2wrap_config.vao_mandatory )\n\t{\n\t\tif( !gl2wrap_arrays.vao_dynamic )\n\t\t\tpglGenVertexArrays( 1, &gl2wrap_arrays.vao_dynamic );\n\t\tpglBindVertexArray( gl2wrap_arrays.vao_dynamic );\n\t}\n\n\tfor( i = 0; i < GL2_ATTR_MAX; i++ )\n\t{\n\t\tif( prog->attridx[i] < 0 )\n\t\t\tcontinue;\n\t\tif( FBitSet( flags, BIT( i ))) // attribute is enabled\n\t\t{\n\t\t\tpglEnableVertexAttribArrayARB( prog->attridx[i] );\n\t\t\t// sometimes usage of client pointers may be faster, sometimes not\n\t\t\t// anyway gl core disallows that, so try use streaming\n\t\t\tif( gl2wrap_config.vao_mandatory && !gl2wrap_arrays.ptr[i].vbo )\n\t\t\t{\n\t\t\t\t// detect stride by type\n\t\t\t\tint stride = gl2wrap_arrays.ptr[i].stride, size, offset;\n\n\t\t\t\tif( stride == 0 )\n\t\t\t\t{\n\t\t\t\t\tif( gl2wrap_arrays.ptr[i].type == GL_UNSIGNED_BYTE )\n\t\t\t\t\t\tstride = gl2wrap_arrays.ptr[i].size;\n\t\t\t\t\telse\n\t\t\t\t\t\tstride = gl2wrap_arrays.ptr[i].size * 4;\n\t\t\t\t}\n\n\t\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer );\n\t\t\t\tif( !end )\n\t\t\t\t{\n\t\t\t\t\t// we cannot handle this case in VAO without known buffer length\n\t\t\t\t\t// only workaround is scanning index array to determine buffer limits, but it is slow,\n\t\t\t\t\t// so just do not use DrawElements when DrawRangeElements availiable\n\t\t\t\t\tpglDisableVertexAttribArrayARB( prog->attridx[i] );\n\t\t\t\t\tgEngfuncs.Con_Printf( S_ERROR \"NON-vbo array for DrawElements call, SKIPPING!\\n\" );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tsize = ( end - start ) * stride;\n\t\t\t\toffset = start * stride;\n\n\t\t\t\t// Logical buffer start can lie before real buffer start\n\t\t\t\t// but attrib pointer cannot have negative buffer offset\n\t\t\t\tif( gl2wrap_arrays.stream_counter < offset )\n\t\t\t\t\tsize = end * stride, offset = 0;\n\n\t\t\t\tif(( !gl2wrap_config.buf_storage && !gl2wrap_config.incremental ) || size > GL2_MAX_VERTS * 32 )\n\t\t\t\t{\n\t\t\t\t\tGL2_UploadBufferData( prog, size, start, end, stride, i );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif( !gl2wrap_config.buf_storage && gl2wrap_config.incremental )\n\t\t\t\t{\n\t\t\t\t\tGL2_UpdateIncrementalArrayBuffer( prog, size, offset, start, end, stride, i );\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tGL2_UpdatePersistentArrayBuffer( prog, size, offset, start, end, stride, i );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.ptr[i].vbo );\n\t\t\t\tpglVertexAttribPointerARB( prog->attridx[i], gl2wrap_arrays.ptr[i].size, gl2wrap_arrays.ptr[i].type, i == GL2_ATTR_COLOR, gl2wrap_arrays.ptr[i].stride, gl2wrap_arrays.ptr[i].userptr );\n\t\t\t}\n\t\t\t/*\n\t\t\tif( i == GL2_ATTR_TEXCOORD0 )\n\t\t\t\tpglUniform1iARB( prog->utex0, 0 );\n\t\t\tif( i == GL2_ATTR_TEXCOORD1 )\n\t\t\t\tpglUniform1iARB( prog->utex1, 1 );\n\t\t\t*/\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglDisableVertexAttribArrayARB( prog->attridx[i] );\n\t\t}\n\t}\n\t// restore state\n\trpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_state.vbo );\n}\n\nstatic void APIENTRY GL2_DrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices )\n{\n\tGL2_SetupArrays( 0, 0 );\n\trpglDrawElements( mode, count, type, indices );\n}\n\nstatic void APIENTRY GL2_DrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices )\n{\n\tGL2_SetupArrays( start, end );\n\tif( rpglDrawRangeElements )\n\t\trpglDrawRangeElements( mode, start, end, count, type, indices );\n\telse\n\t\trpglDrawElements( mode, count, type, indices );\n}\n\nstatic void APIENTRY GL2_DrawArrays( GLenum mode, GLint first, GLsizei count )\n{\n\tGL2_SetupArrays( 0, count );\n\trpglDrawArrays( mode, first, count );\n}\n\nstatic void APIENTRY GL2_BindBufferARB( GLenum buf, GLuint obj )\n{\n\tif( buf == GL_ARRAY_BUFFER_ARB )\n\t\tgl2wrap_state.vbo = obj;\n\trpglBindBufferARB( buf, obj );\n}\n\nstatic void APIENTRY GL2_ActiveTextureARB( GLenum tex )\n{\n\t//gl2wrap_arrays.texture = GL_TEXTURE0_ARB - tex;\n}\n\nstatic void APIENTRY GL2_ClientActiveTextureARB( GLenum tex )\n{\n\tgl2wrap_state.tmu = tex - GL_TEXTURE0_ARB;\n\n\t//pglActiveTextureARB( tex );\n}\n\n#define GL2_OVERRIDE_PTR( name ) \\\n{ \\\n\tpgl ## name = GL2_ ## name; \\\n}\n\n#define GL2_OVERRIDE_PTR_B( name ) \\\n{ \\\n\trpgl ## name = pgl ## name; \\\n\tpgl ## name = GL2_ ## name; \\\n}\n\nstatic void APIENTRY GL2_Normal3fv(const GLfloat *v)\n{\n}\n\nstatic void APIENTRY GL2_Hint(GLenum target, GLenum mode)\n{\n}\n\nstatic void APIENTRY GL2_Scalef(GLfloat x, GLfloat y, GLfloat z)\n{\n}\n\nstatic void APIENTRY GL2_Translatef(GLfloat x, GLfloat y, GLfloat z)\n{\n}\n\nstatic void APIENTRY GL2_TexEnvi(GLenum target, GLenum pname, GLint param)\n{\n}\n\nstatic void APIENTRY GL2_TexEnvf(GLenum target, GLenum pname, GLfloat param)\n{\n}\n\nstatic void APIENTRY GL2_Fogi(GLenum pname, GLint param)\n{\n}\n\nstatic void APIENTRY GL2_ShadeModel(GLenum mode)\n{\n}\n\nstatic void APIENTRY GL2_PolygonMode(GLenum face, GLenum mode)\n{\n}\n\nstatic void APIENTRY GL2_PointSize(GLfloat size)\n{\n}\n\nstatic void APIENTRY GL2_DrawBuffer(GLenum mode)\n{\n}\n\n#if XASH_EMSCRIPTEN\nstatic void GL2_PolygonOffset( GLfloat factor, GLfloat units )\n{\n}\n#endif // XASH_EMSCRIPTEN\n\nvoid GL2_ShimInstall( void )\n{\n\tGL2_OVERRIDE_PTR( Vertex2f )\n\tGL2_OVERRIDE_PTR( Vertex3f )\n\tGL2_OVERRIDE_PTR( Vertex3fv )\n\tGL2_OVERRIDE_PTR( Color3f )\n\tGL2_OVERRIDE_PTR( Color4f )\n\tGL2_OVERRIDE_PTR( Color4ub )\n\tGL2_OVERRIDE_PTR( Color4ubv )\n\tGL2_OVERRIDE_PTR( Normal3fv )\n\tGL2_OVERRIDE_PTR( TexCoord2f )\n\tGL2_OVERRIDE_PTR( MultiTexCoord2f )\n\tGL2_OVERRIDE_PTR( AlphaFunc )\n\tGL2_OVERRIDE_PTR( Fogf )\n\tGL2_OVERRIDE_PTR( Fogfv )\n\tGL2_OVERRIDE_PTR( Hint ) // fog\n\tGL2_OVERRIDE_PTR( Begin )\n\tGL2_OVERRIDE_PTR( End )\n\tGL2_OVERRIDE_PTR_B( Enable )\n\tGL2_OVERRIDE_PTR_B( Disable )\n\tGL2_OVERRIDE_PTR( MatrixMode )\n\tGL2_OVERRIDE_PTR( LoadIdentity )\n\tGL2_OVERRIDE_PTR( Ortho )\n\tGL2_OVERRIDE_PTR( LoadMatrixf )\n\tGL2_OVERRIDE_PTR( Scalef )\n\tGL2_OVERRIDE_PTR( Translatef )\n\tGL2_OVERRIDE_PTR( TexEnvi )\n\tGL2_OVERRIDE_PTR( TexEnvf )\n\tGL2_OVERRIDE_PTR( ClientActiveTextureARB )\n\t//GL2_OVERRIDE_PTR( ActiveTextureARB )\n\tGL2_OVERRIDE_PTR( Fogi )\n\tGL2_OVERRIDE_PTR( ShadeModel )\n#ifdef XASH_GLES\n\t_pglDepthRangef = gEngfuncs.GL_GetProcAddress( \"glDepthRangef\" );\n\tGL2_OVERRIDE_PTR( PolygonMode )\n\tGL2_OVERRIDE_PTR( PointSize )\n\tGL2_OVERRIDE_PTR( DepthRange )\n\tGL2_OVERRIDE_PTR( DrawBuffer )\n#endif\n\tif( glConfig.context != CONTEXT_TYPE_GL )\n\t{\n\t\tGL2_OVERRIDE_PTR_B( TexImage2D )\n\t\tGL2_OVERRIDE_PTR_B( TexParameteri )\n\t}\n#if XASH_EMSCRIPTEN\n\tGL2_OVERRIDE_PTR( PolygonOffset )\n#endif // XASH_EMSCRIPTEN\n\tGL2_OVERRIDE_PTR_B( IsEnabled )\n\tGL2_OVERRIDE_PTR_B( DrawRangeElements )\n\tGL2_OVERRIDE_PTR_B( DrawElements )\n\tGL2_OVERRIDE_PTR_B( DrawArrays )\n\tGL2_OVERRIDE_PTR_B( BindBufferARB )\n\tGL2_OVERRIDE_PTR( EnableClientState )\n\tGL2_OVERRIDE_PTR( DisableClientState )\n\tGL2_OVERRIDE_PTR( VertexPointer )\n\tGL2_OVERRIDE_PTR( ColorPointer )\n\tGL2_OVERRIDE_PTR( TexCoordPointer )\n\n#ifdef QUAD_BATCH\n\tGL2_OVERRIDE_PTR_B( BindTexture )\n#endif\n\tGL2_AllocArrays();\n}\n#endif\n"
  },
  {
    "path": "ref/gl/gl2_shim/gl2_shim.h",
    "content": "/*\nvgl_shim.h - vitaGL custom immediate mode shim\nCopyright (C) 2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n\n// max verts in a single frame\n#define GL2_MAX_VERTS 32768\n\nint GL2_ShimInit( void );\nvoid GL2_ShimInstall( void );\nvoid GL2_ShimShutdown( void );\nvoid GL2_ShimEndFrame( void );\n"
  },
  {
    "path": "ref/gl/gl2_shim/vertex.glsl.inc",
    "content": "\"#if VER <= 300\\n\"\n\"#define layout(x)\\n\"\n\"#endif\\n\"\n\"#if VER < 300\\n\"\n\"#define in attribute\\n\"\n\"#define out varying\\n\"\n\"#endif\\n\"\n\"\\n\"\n\"layout(location = LOC_ATTR_POSITION) in vec3 inPosition;\\n\"\n\"#if ATTR_COLOR\\n\"\n\"layout(location = LOC_ATTR_COLOR) in vec4 inColor;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"layout(location = LOC_ATTR_TEXCOORD0) in vec2 inTexCoord0;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"layout(location = LOC_ATTR_TEXCOORD1) in vec2 inTexCoord1;\\n\"\n\"#endif\\n\"\n\"\\n\"\n\"#if ATTR_NORMAL\\n\"\n\"in vec3 inNormal;\\n\"\n\"#endif\\n\"\n\"#if ATTR_COLOR\\n\"\n\"out vec4 vColor;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"out vec2 vTexCoord0;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"out vec2 vTexCoord1;\\n\"\n\"#endif\\n\"\n\"#if ATTR_NORMAL\\n\"\n\"out vec3 vNormal;\\n\"\n\"#endif\\n\"\n\"\\n\"\n\"uniform mat4 uMVP;\\n\"\n\"\\n\"\n\"void main()\\n\"\n\"{\\n\"\n\"gl_Position = uMVP * vec4(inPosition,1.0);\\n\"\n\"#if ATTR_COLOR\\n\"\n\"vColor = inColor;\\n\"\n\"#endif\\n\"\n\"#if ATTR_NORMAL\\n\"\n\"vNormal = inNormal;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD0\\n\"\n\"vTexCoord0 = inTexCoord0;\\n\"\n\"#endif\\n\"\n\"#if ATTR_TEXCOORD1\\n\"\n\"vTexCoord1 = inTexCoord1;\\n\"\n\"#endif\\n\"\n\"}\\n\"\n"
  },
  {
    "path": "ref/gl/gl_alias.c",
    "content": "/*\ngl_alias.c - alias model renderer\nCopyright (C) 2017 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"const.h\"\n#include \"r_studioint.h\"\n#include \"triangleapi.h\"\n#include \"alias.h\"\n#include \"pm_local.h\"\n#include \"pmtrace.h\"\n\ntypedef struct\n{\n\tdouble\t\ttime;\n\tdouble\t\tframetime;\n\tint\t\tframecount;\t// alias framecount\n\tqboolean\t\tinterpolate;\n\n\tfloat\t\tambientlight;\n\tfloat\t\tshadelight;\n\tvec3_t\t\tlightvec;\t\t// averaging light direction\n\tvec3_t\t\tlightvec_local;\t// light direction in local space\n\tvec3_t\t\tlightspot;\t// shadow spot\n\tvec3_t\t\tlightcolor;\t// averaging lightcolor\n\tint\t\toldpose;\t\t// shadow used\n\tint\t\tnewpose;\t\t// shadow used\n\tfloat\t\tlerpfrac;\t\t// lerp frames\n} alias_draw_state_t;\n\nstatic alias_draw_state_t\tg_alias;\t\t// global alias state\n\n/*\n=================================================================\n\nALIAS MODEL DISPLAY LIST GENERATION\n\n=================================================================\n*/\nstatic qboolean\tm_fDoRemap;\nstatic aliashdr_t\t*m_pAliasHeader;\nstatic dtriangle_t\tg_triangles[MAXALIASTRIS];\nstatic stvert_t\tg_stverts[MAXALIASVERTS];\nstatic int\tg_used[8192];\n\n// the command list holds counts and s/t values that are valid for\n// every frame\nstatic int\tg_commands[8192];\nstatic int\tg_numcommands;\n\n// all frames will have their vertexes rearranged and expanded\n// so they are in the order expected by the command list\nstatic int\tg_vertexorder[8192];\nstatic int\tg_numorder;\n\nstatic int\tg_stripverts[128];\nstatic int\tg_striptris[128];\nstatic int\tg_stripcount;\n\n/*\n====================\nR_StudioInit\n\n====================\n*/\nvoid R_AliasInit( void )\n{\n\tg_alias.interpolate = true;\n\tm_fDoRemap = false;\n}\n\n/*\n================\nStripLength\n================\n*/\nstatic int StripLength( int starttri, int startv )\n{\n\tint\t\tm1, m2, j, k;\n\tdtriangle_t\t*last, *check;\n\n\tg_used[starttri] = 2;\n\n\tlast = &g_triangles[starttri];\n\n\tg_stripverts[0] = last->vertindex[(startv+0) % 3];\n\tg_stripverts[1] = last->vertindex[(startv+1) % 3];\n\tg_stripverts[2] = last->vertindex[(startv+2) % 3];\n\n\tg_striptris[0] = starttri;\n\tg_stripcount = 1;\n\n\tm1 = last->vertindex[(startv+2)%3];\n\tm2 = last->vertindex[(startv+1)%3];\nnexttri:\n\t// look for a matching triangle\n\tfor( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ )\n\t{\n\t\tif( check->facesfront != last->facesfront )\n\t\t\tcontinue;\n\n\t\tfor( k = 0; k < 3; k++ )\n\t\t{\n\t\t\tif( check->vertindex[k] != m1 )\n\t\t\t\tcontinue;\n\t\t\tif( check->vertindex[(k+1) % 3] != m2 )\n\t\t\t\tcontinue;\n\n\t\t\t// this is the next part of the fan\n\n\t\t\t// if we can't use this triangle, this tristrip is done\n\t\t\tif( g_used[j] ) goto done;\n\n\t\t\t// the new edge\n\t\t\tif( g_stripcount & 1 )\n\t\t\t\tm2 = check->vertindex[(k+2) % 3];\n\t\t\telse m1 = check->vertindex[(k+2) % 3];\n\n\t\t\tg_stripverts[g_stripcount+2] = check->vertindex[(k+2) % 3];\n\t\t\tg_striptris[g_stripcount] = j;\n\t\t\tg_stripcount++;\n\n\t\t\tg_used[j] = 2;\n\t\t\tgoto nexttri;\n\t\t}\n\t}\ndone:\n\t// clear the temp used flags\n\tfor( j = starttri + 1; j < m_pAliasHeader->numtris; j++ )\n\t{\n\t\tif( g_used[j] == 2 )\n\t\t\tg_used[j] = 0;\n\t}\n\n\treturn g_stripcount;\n}\n\n/*\n===========\nFanLength\n===========\n*/\nstatic int FanLength( int starttri, int startv )\n{\n\tint\t\tm1, m2, j, k;\n\tdtriangle_t\t*last, *check;\n\n\tg_used[starttri] = 2;\n\n\tlast = &g_triangles[starttri];\n\n\tg_stripverts[0] = last->vertindex[(startv+0) % 3];\n\tg_stripverts[1] = last->vertindex[(startv+1) % 3];\n\tg_stripverts[2] = last->vertindex[(startv+2) % 3];\n\n\tg_striptris[0] = starttri;\n\tg_stripcount = 1;\n\n\tm1 = last->vertindex[(startv+0) % 3];\n\tm2 = last->vertindex[(startv+2) % 3];\n\nnexttri:\n\t// look for a matching triangle\n\tfor( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ )\n\t{\n\t\tif( check->facesfront != last->facesfront )\n\t\t\tcontinue;\n\n\t\tfor( k = 0; k < 3; k++ )\n\t\t{\n\t\t\tif( check->vertindex[k] != m1 )\n\t\t\t\tcontinue;\n\t\t\tif( check->vertindex[(k+1) % 3] != m2 )\n\t\t\t\tcontinue;\n\n\t\t\t// this is the next part of the fan\n\t\t\t// if we can't use this triangle, this tristrip is done\n\t\t\tif( g_used[j] ) goto done;\n\n\t\t\t// the new edge\n\t\t\tm2 = check->vertindex[(k+2) % 3];\n\n\t\t\tg_stripverts[g_stripcount + 2] = m2;\n\t\t\tg_striptris[g_stripcount] = j;\n\t\t\tg_stripcount++;\n\n\t\t\tg_used[j] = 2;\n\t\t\tgoto nexttri;\n\t\t}\n\t}\ndone:\n\t// clear the temp used flags\n\tfor( j = starttri + 1; j < m_pAliasHeader->numtris; j++ )\n\t{\n\t\tif( g_used[j] == 2 )\n\t\t\tg_used[j] = 0;\n\t}\n\n\treturn g_stripcount;\n}\n\n/*\n================\nBuildTris\n\nGenerate a list of trifans or strips\nfor the model, which holds for all frames\n================\n*/\nstatic void BuildTris( void )\n{\n\tint\tlen, bestlen, besttype = 0;\n\tint\tbestverts[1024];\n\tint\tbesttris[1024];\n\tint\ttype, startv;\n\tint\ti, j, k;\n\tfloat\ts, t;\n\n\t//\n\t// build tristrips\n\t//\n\tmemset( g_used, 0, sizeof( g_used ));\n\tg_numcommands = 0;\n\tg_numorder = 0;\n\n\tfor( i = 0; i < m_pAliasHeader->numtris; i++ )\n\t{\n\t\t// pick an unused triangle and start the trifan\n\t\tif( g_used[i] ) continue;\n\n\t\tbestlen = 0;\n\t\tfor( type = 0; type < 2; type++ )\n\t\t{\n\t\t\tfor( startv = 0; startv < 3; startv++ )\n\t\t\t{\n\t\t\t\tif( type == 1 ) len = StripLength( i, startv );\n\t\t\t\telse len = FanLength( i, startv );\n\n\t\t\t\tif( len > bestlen )\n\t\t\t\t{\n\t\t\t\t\tbesttype = type;\n\t\t\t\t\tbestlen = len;\n\n\t\t\t\t\tfor( j = 0; j < bestlen + 2; j++ )\n\t\t\t\t\t\tbestverts[j] = g_stripverts[j];\n\n\t\t\t\t\tfor( j = 0; j < bestlen; j++ )\n\t\t\t\t\t\tbesttris[j] = g_striptris[j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// mark the tris on the best strip as used\n\t\tfor( j = 0; j < bestlen; j++ )\n\t\t\tg_used[besttris[j]] = 1;\n\n\t\tif( besttype == 1 )\n\t\t\tg_commands[g_numcommands++] = (bestlen + 2);\n\t\telse g_commands[g_numcommands++] = -(bestlen + 2);\n\n\t\tfor( j = 0; j < bestlen + 2; j++ )\n\t\t{\n\t\t\t// emit a vertex into the reorder buffer\n\t\t\tk = bestverts[j];\n\t\t\tg_vertexorder[g_numorder++] = k;\n\n\t\t\t// emit s/t coords into the commands stream\n\t\t\ts = g_stverts[k].s;\n\t\t\tt = g_stverts[k].t;\n\n\t\t\tif( !g_triangles[besttris[0]].facesfront && g_stverts[k].onseam )\n\t\t\t\ts += m_pAliasHeader->skinwidth / 2;\t// on back side\n\t\t\ts = (s + 0.5f) / m_pAliasHeader->skinwidth;\n\t\t\tt = (t + 0.5f) / m_pAliasHeader->skinheight;\n\n\t\t\t// Carmack use floats and Valve use shorts here...\n\t\t\tg_commands[g_numcommands++] = FloatAsInt( s );\n\t\t\tg_commands[g_numcommands++] = FloatAsInt( t );\n\t\t}\n\t}\n\n\tg_commands[g_numcommands++] = 0; // end of list marker\n}\n\n/*\n================\nGL_MakeAliasModelDisplayLists\n================\n*/\nstatic void GL_MakeAliasModelDisplayLists( model_t *m )\n{\n\ttrivertex_t\t*verts;\n\tint\t\ti, j;\n\n\tBuildTris( );\n\n\t// save the data out\n\tm_pAliasHeader->poseverts = g_numorder;\n\n\tm_pAliasHeader->commands = Mem_Malloc( m->mempool, g_numcommands * 4 );\n\tmemcpy( m_pAliasHeader->commands, g_commands, g_numcommands * 4 );\n\n\tm_pAliasHeader->posedata = Mem_Malloc( m->mempool, m_pAliasHeader->numposes * m_pAliasHeader->poseverts * sizeof( trivertex_t ));\n\tverts = m_pAliasHeader->posedata;\n\n\tfor( i = 0; i < m_pAliasHeader->numposes; i++ )\n\t{\n\t\tfor( j = 0; j < g_numorder; j++ )\n\t\t\t*verts++ = m_pAliasHeader->pposeverts[i][g_vertexorder[j]];\n\t}\n}\n\n/*\n==============================================================================\n\nALIAS MODELS\n\n==============================================================================\n*/\n\n\n/*\n===============\nMod_CreateSkinData\n===============\n*/\nstatic rgbdata_t *Mod_CreateSkinData( model_t *mod, const byte *data, int width, int height )\n{\n\tstatic rgbdata_t\tskin;\n\tchar\t\tname[MAX_QPATH];\n\tint\t\ti;\n\n\tskin.width = width;\n\tskin.height = height;\n\tskin.depth = 1;\n\tskin.type = PF_INDEXED_24;\n\tskin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL;\n\tskin.encode = DXT_ENCODE_DEFAULT;\n\tskin.numMips = 1;\n\tskin.buffer = (byte *)data;\n\tskin.palette = (byte *)tr.palette;\n\tskin.size = width * height;\n\n\tif( !gEngfuncs.Image_CustomPalette() )\n\t{\n\t\tfor( i = 0; i < skin.width * skin.height; i++ )\n\t\t{\n\t\t\tif( data[i] > 224 && data[i] != 255 )\n\t\t\t{\n\t\t\t\tSetBits( skin.flags, IMAGE_HAS_LUMA );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tCOM_FileBase( mod->name, name, sizeof( name ));\n\n\t// for alias models only player can have remap textures\n\tif( mod != NULL && !Q_stricmp( name, \"player\" ))\n\t{\n\t\ttexture_t\t*tx = NULL;\n\t\tint\ti, size;\n\n\t\ti = mod->numtextures;\n\t\tmod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* ));\n\t\tsize = width * height + 768;\n\t\ttx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size );\n\t\tmod->textures[i] = tx;\n\n\t\tQ_strncpy( tx->name, \"DM_Skin\", sizeof( tx->name ));\n\t\ttx->anim_min = SHIRT_HUE_START; // topcolor start\n\t\ttx->anim_max = SHIRT_HUE_END; // topcolor end\n\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\ttx->anim_total = PANTS_HUE_END;// bottomcolor end\n\n\t\ttx->width = width;\n\t\ttx->height = height;\n\n\t\t// the pixels immediately follow the structures\n\t\tmemcpy( (tx+1), data, width * height );\n\t\tmemcpy( ((byte *)(tx+1)+(width * height)), skin.palette, 768 );\n\t\tmod->numtextures++;\t// done\n\t}\n\n\t// make an copy\n\treturn gEngfuncs.FS_CopyImage( &skin );\n}\n\nstatic const void *Mod_LoadSingleSkin( model_t *loadmodel, const daliasskintype_t *pskintype, int skinnum, int size )\n{\n\tconst byte *ptexture = (const byte *)&pskintype[1];\n\trgbdata_t *pic;\n\tstring name, lumaname, checkname;\n\n\tQ_snprintf( name, sizeof( name ), \"%s:frame%i\", loadmodel->name, skinnum );\n\tQ_snprintf( checkname, sizeof( checkname ), \"%s_%i.tga\", loadmodel->name, skinnum );\n\tif( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL )\n\t\tpic = Mod_CreateSkinData( loadmodel, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight );\n\n\tm_pAliasHeader->gl_texturenum[skinnum][0] =\n\t\tm_pAliasHeader->gl_texturenum[skinnum][1] =\n\t\tm_pAliasHeader->gl_texturenum[skinnum][2] =\n\t\tm_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0 );\n\tgEngfuncs.FS_FreeImage( pic );\n\n\tif( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags, TF_HAS_LUMA ))\n\t{\n\t\tQ_snprintf( lumaname, sizeof( lumaname ), \"%s:luma%i\", loadmodel->name, skinnum );\n\n\t\tpic = Mod_CreateSkinData( NULL,\tptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight );\n\t\tm_pAliasHeader->fb_texturenum[skinnum][0] =\n\t\t\tm_pAliasHeader->fb_texturenum[skinnum][1] =\n\t\t\tm_pAliasHeader->fb_texturenum[skinnum][2] =\n\t\t\tm_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA );\n\t\tgEngfuncs.FS_FreeImage( pic );\n\t}\n\n\treturn ptexture + size;\n}\n\nstatic const void *Mod_LoadGroupSkin( model_t *loadmodel, const daliasskintype_t *pskintype, int skinnum, int size )\n{\n\tconst daliasskininterval_t *pinskinintervals;\n\tconst daliasskingroup_t *pinskingroup;\n\tconst byte *ptexture;\n\trgbdata_t *pic;\n\tstring name, lumaname;\n\tint i, j;\n\n\t// animating skin group.  yuck.\n\tpinskingroup = (const daliasskingroup_t *)&pskintype[1];\n\tpinskinintervals = (const daliasskininterval_t *)(&pinskingroup[1]);\n\tptexture = (const byte *)&pinskinintervals[pinskingroup->numskins];\n\n\tfor( i = 0; i < pinskingroup->numskins; i++ )\n\t{\n\t\tQ_snprintf( name, sizeof( name ), \"%s_%i_%i\", loadmodel->name, skinnum, i );\n\t\tpic = Mod_CreateSkinData( loadmodel, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight );\n\t\tm_pAliasHeader->gl_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( name, pic, 0 );\n\t\tgEngfuncs.FS_FreeImage( pic );\n\n\t\tif( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags, TF_HAS_LUMA ))\n\t\t{\n\t\t\tQ_snprintf( lumaname, sizeof( lumaname ), \"%s_%i_%i_luma\", loadmodel->name, skinnum, i );\n\t\t\tpic = Mod_CreateSkinData( NULL, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight );\n\t\t\tm_pAliasHeader->fb_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA );\n\t\t\tgEngfuncs.FS_FreeImage( pic );\n\t\t}\n\n\t\tptexture += size;\n\t}\n\n\tfor( j = i; i < 4; i++ )\n\t{\n\t\tm_pAliasHeader->gl_texturenum[skinnum][i & 3] = m_pAliasHeader->gl_texturenum[skinnum][i - j];\n\t\tm_pAliasHeader->fb_texturenum[skinnum][i & 3] = m_pAliasHeader->fb_texturenum[skinnum][i - j];\n\t}\n\n\treturn ptexture;\n}\n\n/*\n===============\nMod_LoadAllSkins\n===============\n*/\nstatic const void *Mod_LoadAllSkins( model_t *mod, int numskins, const daliasskintype_t *pskintype )\n{\n\tint\ti, size;\n\n\tsize = m_pAliasHeader->skinwidth * m_pAliasHeader->skinheight;\n\n\t// TODO: texture replacement support here\n\tfor( i = 0; i < numskins; i++ )\n\t{\n\t\tif( pskintype->type == ALIAS_SKIN_SINGLE )\n\t\t\tpskintype = Mod_LoadSingleSkin( mod, pskintype, i, size );\n\t\telse\n\t\t\tpskintype = Mod_LoadGroupSkin( mod, pskintype, i, size );\n\t}\n\n\treturn pskintype;\n}\n\n//=========================================================================\n\n/*\n=================\nMod_LoadAliasModel\n=================\n*/\nvoid Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded )\n{\n\tconst daliasskintype_t *pskintype;\n\tconst dtriangle_t *pintriangles;\n\tconst daliashdr_t *pinmodel;\n\tconst stvert_t *pinstverts;\n\n\tif( loaded ) *loaded = false;\n\tpinmodel = (const daliashdr_t *)buffer;\n\tm_pAliasHeader = mod->cache.data;\n\tif( !m_pAliasHeader ) return;\n\n\t// load the skins\n\tpskintype = (const daliasskintype_t *)&pinmodel[1];\n\tpskintype = Mod_LoadAllSkins( mod, m_pAliasHeader->numskins, pskintype );\n\n\t// load base s and t vertices\n\tpinstverts = (const stvert_t *)pskintype;\n\tmemset( g_stverts, 0, sizeof( g_stverts ));\n\tmemcpy( g_stverts, pinstverts, sizeof( g_stverts[0] ) * m_pAliasHeader->numverts );\n\n\t// load triangle lists\n\tpintriangles = (const dtriangle_t *)&pinstverts[m_pAliasHeader->numverts];\n\tmemset( g_triangles, 0, sizeof( g_triangles ));\n\tmemcpy( g_triangles, pintriangles, sizeof( g_triangles[0] ) * m_pAliasHeader->numtris );\n\n\t// build the draw lists\n\tGL_MakeAliasModelDisplayLists( mod );\n\n\tm_pAliasHeader = NULL;\n\n\tif( loaded ) *loaded = true;\t// done\n}\n\nvoid Mod_AliasUnloadTextures( void *data )\n{\n\taliashdr_t\t*palias;\n\tint\t\ti, j;\n\n\tpalias = data;\n\tif( !palias ) return; // already freed\n\n\tfor( i = 0; i < MAX_SKINS; i++ )\n\t{\n\t\tif( !palias->gl_texturenum[i][0] )\n\t\t\tbreak;\n\n\t\tfor( j = 0; j < 4; j++ )\n\t\t{\n\t\t\tGL_FreeTexture( palias->gl_texturenum[i][j] );\n\t\t\tGL_FreeTexture( palias->fb_texturenum[i][j] );\n\t\t}\n\t}\n}\n\n/*\n=============================================================\n\n  ALIAS MODELS\n\n=============================================================\n*/\n\n/*\n===============\nR_AliasDynamicLight\n\nsimilar to R_StudioDynamicLight\n===============\n*/\nstatic void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight )\n{\n\tmovevars_t\t*mv = tr.movevars;\n\tvec3_t\t\tlightDir, vecSrc, vecEnd;\n\tvec3_t\t\torigin, dist, finalLight;\n\tfloat\t\tadd, radius, total;\n\tcolorVec\t\tlight;\n\tuint\t\tlnum;\n\n\tif( !plight || !ent )\n\t\treturn;\n\n\tif( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT ))\n\t{\n\t\tplight->shadelight = 0;\n\t\tplight->ambientlight = 192;\n\n\t\tVectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f );\n\t\tVectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\t\treturn;\n\t}\n\n\t// determine plane to get lightvalues from: ceil or floor\n\tif( FBitSet( ent->curstate.effects, EF_INVLIGHT ))\n\t\tVectorSet( lightDir, 0.0f, 0.0f, 1.0f );\n\telse VectorSet( lightDir, 0.0f, 0.0f, -1.0f );\n\n\tVectorCopy( ent->origin, origin );\n\n\tVectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f );\n\tlight.r = light.g = light.b = light.a = 0;\n\n\tif(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 )\n\t{\n\t\tmsurface_t\t*psurf = NULL;\n\t\tpmtrace_t\t\ttrace;\n\n\t\tif( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD ))\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f;\n\t\t}\n\n\t\ttrace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE );\n\t\tif( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd );\n\t\telse psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd );\n\n\t\tif( psurf && FBitSet( psurf->flags, SURF_DRAWSKY ))\n\t\t{\n\t\t\tVectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z );\n\n\t\t\tlight.r = mv->skycolor_r;\n\t\t\tlight.g = mv->skycolor_g;\n\t\t\tlight.b = mv->skycolor_b;\n\t\t}\n\t}\n\n\tif(( light.r + light.g + light.b ) == 0 )\n\t{\n\t\tcolorVec\tgcolor;\n\t\tfloat\tgrad[4];\n\n\t\tVectorScale( lightDir, 2048.0f, vecEnd );\n\t\tVectorAdd( vecEnd, vecSrc, vecEnd );\n\n\t\tlight = R_LightVec( vecSrc, vecEnd, g_alias.lightspot, g_alias.lightvec );\n\n\t\tif( VectorIsNull( g_alias.lightvec ))\n\t\t{\n\t\t\tvecSrc[0] -= 16.0f;\n\t\t\tvecSrc[1] -= 16.0f;\n\t\t\tvecEnd[0] -= 16.0f;\n\t\t\tvecEnd[1] -= 16.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] += 32.0f;\n\t\t\tvecEnd[0] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[1] += 32.0f;\n\t\t\tvecEnd[1] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] -= 32.0f;\n\t\t\tvecEnd[0] -= 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tlightDir[0] = grad[0] - grad[1] - grad[2] + grad[3];\n\t\t\tlightDir[1] = grad[1] + grad[0] - grad[2] - grad[3];\n\t\t\tVectorNormalize( lightDir );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( g_alias.lightvec, lightDir );\n\t\t}\n\t}\n\n\tVectorSet( finalLight, light.r, light.g, light.b );\n\tent->cvFloorColor = light;\n\n\ttotal = Q_max( Q_max( light.r, light.g ), light.b );\n\tif( total == 0.0f ) total = 1.0f;\n\n\t// scale lightdir by light intentsity\n\tVectorScale( lightDir, total, lightDir );\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tconst dlight_t *dl = &tr.dlights[lnum];\n\n\t\tif( dl->die < g_alias.time || !r_dynamic->value )\n\t\t\tcontinue;\n\n\t\tVectorSubtract( origin, dl->origin, dist );\n\n\t\tradius = VectorLength( dist );\n\t\tadd = dl->radius - radius;\n\n\t\tif( add > 0.0f )\n\t\t{\n\t\t\ttotal += add;\n\n\t\t\tif( radius > 1.0f )\n\t\t\t\tVectorScale( dist, ( add / radius ), dist );\n\t\t\telse VectorScale( dist, add, dist );\n\n\t\t\tVectorAdd( lightDir, dist, lightDir );\n\n\t\t\tfinalLight[0] += dl->color.r * ( add / 256.0f );\n\t\t\tfinalLight[1] += dl->color.g * ( add / 256.0f );\n\t\t\tfinalLight[2] += dl->color.b * ( add / 256.0f );\n\t\t}\n\t}\n\n\tVectorScale( lightDir, 0.9f, lightDir );\n\n\tplight->shadelight = VectorLength( lightDir );\n\tplight->ambientlight = total - plight->shadelight;\n\n\ttotal = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] );\n\n\tif( total > 0.0f )\n\t{\n\t\tplight->color[0] = finalLight[0] * ( 1.0f / total );\n\t\tplight->color[1] = finalLight[1] * ( 1.0f / total );\n\t\tplight->color[2] = finalLight[2] * ( 1.0f / total );\n\t}\n\telse VectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\n\tif( plight->ambientlight > 128 )\n\t\tplight->ambientlight = 128;\n\n\tif( plight->ambientlight + plight->shadelight > 255 )\n\t\tplight->shadelight = 255 - plight->ambientlight;\n\n\tVectorNormalize2( lightDir, plight->plightvec );\n}\n\n/*\n===============\nR_AliasSetupLighting\n\n===============\n*/\nstatic void R_AliasSetupLighting( alight_t *plight )\n{\n\tif( !m_pAliasHeader || !plight )\n\t\treturn;\n\n\tg_alias.ambientlight = plight->ambientlight;\n\tg_alias.shadelight = plight->shadelight;\n\tVectorCopy( plight->plightvec, g_alias.lightvec );\n\tVectorCopy( plight->color, g_alias.lightcolor );\n\n\t// transform back to local space\n\tMatrix4x4_VectorIRotate( RI.objectMatrix, g_alias.lightvec, g_alias.lightvec_local );\n\tVectorNormalize( g_alias.lightvec_local );\n}\n\n/*\n===============\nR_AliasLighting\n\n===============\n*/\nstatic void R_AliasLighting( float *lv, const vec3_t normal )\n{\n\tfloat \tillum = g_alias.ambientlight;\n\tfloat\tr, lightcos;\n\n\tlightcos = DotProduct( normal, g_alias.lightvec_local ); // -1 colinear, 1 opposite\n\tif( lightcos > 1.0f ) lightcos = 1.0f;\n\n\tillum += g_alias.shadelight;\n\n\tr = SHADE_LAMBERT;\n\n\t// do modified hemispherical lighting\n\tif( r <= 1.0f )\n\t{\n\t\tr += 1.0f;\n\t\tlightcos = (( r - 1.0f ) - lightcos) / r;\n\t\tif( lightcos > 0.0f )\n\t\t\tillum += g_alias.shadelight * lightcos;\n\t}\n\telse\n\t{\n\t\tlightcos = (lightcos + ( r - 1.0f )) / r;\n\t\tif( lightcos > 0.0f )\n\t\t\tillum -= g_alias.shadelight * lightcos;\n\t}\n\n\tillum = bound( 0.0f, illum, 255.0f );\n\n\t*lv = LightToTexGamma( illum * 4 ) / 1023.0f;\n}\n\n/*\n===============\nR_AliasSetRemapColors\n\n===============\n*/\nstatic void R_AliasSetRemapColors( int newTop, int newBottom )\n{\n\tif( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom ))\n\t\tm_fDoRemap = true;\n}\n\n/*\n=============\nGL_DrawAliasFrame\n=============\n*/\nstatic void GL_DrawAliasFrame( aliashdr_t *paliashdr )\n{\n\tfloat \t\tlv_tmp;\n\ttrivertex_t\t*verts0;\n\ttrivertex_t\t*verts1;\n\tvec3_t\t\tvert, norm;\n\tint\t\t*order;\n\tint\t\tcount;\n\n\tverts0 = verts1 = paliashdr->posedata;\n\tverts0 += g_alias.oldpose * paliashdr->poseverts;\n\tverts1 += g_alias.newpose * paliashdr->poseverts;\n\torder = paliashdr->commands;\n\n\twhile( 1 )\n\t{\n\t\t// get the vertex count and primitive type\n\t\tcount = *order++;\n\t\tif( !count ) break; // done\n\n\t\tif( count < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\tcount = -count;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_STRIP );\n\t\t}\n\n\t\tdo\n\t\t{\n\t\t\t// texture coordinates come from the draw list\n\t\t\tif( GL_Support( GL_ARB_MULTITEXTURE ) && glState.activeTMU > 0 )\n\t\t\t{\n\t\t\t\tGL_MultiTexCoord2f( XASH_TEXTURE0, ((float *)order)[0], ((float *)order)[1] );\n\t\t\t\tGL_MultiTexCoord2f( XASH_TEXTURE1, ((float *)order)[0], ((float *)order)[1] );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpglTexCoord2f( ((float *)order)[0], ((float *)order)[1] );\n\t\t\t}\n\t\t\torder += 2;\n\n\t\t\tVectorLerp( m_bytenormals[verts0->lightnormalindex], g_alias.lerpfrac, m_bytenormals[verts1->lightnormalindex], norm );\n\t\t\tVectorNormalize( norm );\n\t\t\tR_AliasLighting( &lv_tmp, norm );\n\t\t\tpglColor4f( g_alias.lightcolor[0] * lv_tmp, g_alias.lightcolor[1] * lv_tmp, g_alias.lightcolor[2] * lv_tmp, tr.blend );\n\t\t\tVectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, vert );\n\t\t\tpglVertex3fv( vert );\n\t\t\tverts0++, verts1++;\n\t\t} while( --count );\n\n\t\tpglEnd();\n\t}\n}\n\n/*\n=============\nGL_DrawAliasShadow\n=============\n*/\nstatic void GL_DrawAliasShadow( aliashdr_t *paliashdr )\n{\n\ttrivertex_t\t*verts0;\n\ttrivertex_t\t*verts1;\n\tfloat\t\tvec_x, vec_y;\n\tvec3_t\t\tav, point;\n\tint\t\t*order;\n\tfloat\t\theight;\n\tint\t\tcount;\n\n\tif( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW ))\n\t\treturn;\n\n\tif( glState.stencilEnabled )\n\t\tpglEnable( GL_STENCIL_TEST );\n\n\theight = g_alias.lightspot[2] + 1.0f;\n\tvec_x = -g_alias.lightvec[0] * 8.0f;\n\tvec_y = -g_alias.lightvec[1] * 8.0f;\n\n\tr_stats.c_alias_polys += paliashdr->numtris;\n\n\tverts0 = verts1 = paliashdr->posedata;\n\tverts0 += g_alias.oldpose * paliashdr->poseverts;\n\tverts1 += g_alias.newpose * paliashdr->poseverts;\n\torder = paliashdr->commands;\n\n\twhile( 1 )\n\t{\n\t\t// get the vertex count and primitive type\n\t\tcount = *order++;\n\t\tif( !count ) break; // done\n\n\t\tif( count < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\tcount = -count;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_STRIP );\n\t\t}\n\n\t\tdo\n\t\t{\n\t\t\t// texture coordinates come from the draw list\n\t\t\t// (skipped for shadows) pglTexCoord2fv ((float *)order);\n\t\t\torder += 2;\n\n\t\t\t// normals and vertexes come from the frame list\n\t\t\tVectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, av );\n\t\t\tpoint[0] = av[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];\n\t\t\tpoint[1] = av[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];\n\t\t\tpoint[2] = av[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];\n\t\t\tMatrix3x4_VectorTransform( RI.objectMatrix, point, av );\n\n\t\t\tpoint[0] = av[0] - (vec_x * ( av[2] - g_alias.lightspot[2] ));\n\t\t\tpoint[1] = av[1] - (vec_y * ( av[2] - g_alias.lightspot[2] ));\n\t\t\tpoint[2] = g_alias.lightspot[2] + 1.0f;\n\n\t\t\tpglVertex3fv( point );\n\t\t\tverts0++, verts1++;\n\n\t\t} while( --count );\n\n\t\tpglEnd();\n\t}\n\n\tif( glState.stencilEnabled )\n\t\tpglDisable( GL_STENCIL_TEST );\n}\n\n/*\n====================\nR_AliasLerpMovement\n\n====================\n*/\nstatic void R_AliasLerpMovement( cl_entity_t *e )\n{\n\tfloat\tf = 1.0f;\n\n\t// don't do it if the goalstarttime hasn't updated in a while.\n\t// NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit\n\t// was increased to 1.0 s., which is 2x the max lag we are accounting for.\n\tif( g_alias.interpolate && ( g_alias.time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime ))\n\t\tf = ( g_alias.time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime );\n\n\tif( ENGINE_GET_PARM( PARM_PLAYING_DEMO ) == DEMO_QUAKE1 )\n\t\tf = f + 1.0f;\n\n\tg_alias.lerpfrac = bound( 0.0f, f, 1.0f );\n\n\tif( e->player || e->curstate.movetype != MOVETYPE_STEP )\n\t\treturn; // monsters only\n\n\t// Con_Printf( \"%4.2f %.2f %.2f\\n\", f, e->curstate.animtime, g_alias.time );\n\tVectorLerp( e->latched.prevorigin, f, e->curstate.origin, e->origin );\n\n\tif( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON ))\n\t{\n\t\tvec4_t\tq, q1, q2;\n\n\t\tAngleQuaternion( e->curstate.angles, q1, false );\n\t\tAngleQuaternion( e->latched.prevangles, q2, false );\n\t\tQuaternionSlerp( q2, q1, f, q );\n\t\tQuaternionAngle( q, e->angles );\n\t}\n\telse VectorCopy( e->curstate.angles, e->angles );\n\n\t// NOTE: this completely over control about angles and don't broke interpolation\n\tif( FBitSet( e->model->flags, ALIAS_ROTATE ))\n\t\te->angles[1] = anglemod( 100.0f * g_alias.time );\n}\n\n/*\n=================\nR_SetupAliasFrame\n\n=================\n*/\nstatic void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr )\n{\n\tint\tnewpose, oldpose;\n\tint\tnewframe, oldframe;\n\tint\tnumposes, cycle;\n\tfloat\tinterval;\n\n\toldframe = e->latched.prevframe;\n\tnewframe = e->curstate.frame;\n\n\tif( newframe < 0 )\n\t{\n\t\tnewframe = 0;\n\t}\n\telse if( newframe >= paliashdr->numframes )\n\t{\n\t\tif( newframe > paliashdr->numframes )\n\t\t\tgEngfuncs.Con_Reportf( S_WARN \"%s: no such frame %d (%s)\\n\", __func__, newframe, e->model->name );\n\t\tnewframe = paliashdr->numframes - 1;\n\t}\n\n\tif(( oldframe >= paliashdr->numframes ) || ( oldframe < 0 ))\n\t\toldframe = newframe;\n\n\tnumposes = paliashdr->frames[newframe].numposes;\n\n\tif( numposes > 1 )\n\t{\n\t\toldpose = newpose = paliashdr->frames[newframe].firstpose;\n\t\tinterval = 1.0f / paliashdr->frames[newframe].interval;\n\t\tcycle = (int)(g_alias.time * interval);\n\t\toldpose += (cycle + 0) % numposes; // lerpframe from\n\t\tnewpose += (cycle + 1) % numposes; // lerpframe to\n\t\tg_alias.lerpfrac = ( g_alias.time * interval );\n\t\tg_alias.lerpfrac -= (int)g_alias.lerpfrac;\n\t}\n\telse\n\t{\n\t\toldpose = paliashdr->frames[oldframe].firstpose;\n\t\tnewpose = paliashdr->frames[newframe].firstpose;\n\t}\n\n\tg_alias.oldpose = oldpose;\n\tg_alias.newpose = newpose;\n\n\tGL_DrawAliasFrame( paliashdr );\n}\n\n/*\n===============\nR_StudioDrawAbsBBox\n\n===============\n*/\nstatic void R_AliasDrawAbsBBox( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax )\n{\n\tvec3_t\tp[8];\n\tint\ti;\n\n\t// looks ugly, skip\n\tif( r_drawentities->value != 5 || e == tr.viewent )\n\t\treturn;\n\n\t// compute a full bounding box\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\tp[i][0] = ( i & 1 ) ? absmin[0] : absmax[0];\n\t\tp[i][1] = ( i & 2 ) ? absmin[1] : absmax[1];\n\t\tp[i][2] = ( i & 4 ) ? absmin[2] : absmax[2];\n\t}\n\n\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\tTriColor4f( 0.5f, 0.5f, 1.0f, 0.5f );\n\tTriRenderMode( kRenderTransAdd );\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\tTriBegin( TRI_QUADS );\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tTriBrightness( g_alias.shadelight / 255.0f );\n\t\tTriVertex3fv( p[boxpnt[i][0]] );\n\t\tTriVertex3fv( p[boxpnt[i][1]] );\n\t\tTriVertex3fv( p[boxpnt[i][2]] );\n\t\tTriVertex3fv( p[boxpnt[i][3]] );\n\t}\n\tTriEnd();\n\n\tTriRenderMode( kRenderNormal );\n}\n\nstatic void R_AliasDrawLightTrace( cl_entity_t *e )\n{\n\tif( r_drawentities->value == 7 )\n\t{\n\t\tvec3_t\torigin;\n\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_DEPTH_TEST );\n\n\t\tpglBegin( GL_LINES );\n\t\tpglColor3f( 1, 0.5, 0 );\n\t\tpglVertex3fv( e->origin );\n\t\tpglVertex3fv( g_alias.lightspot );\n\t\tpglEnd();\n\n\t\tpglBegin( GL_LINES );\n\t\tpglColor3f( 0, 0.5, 1 );\n\t\tVectorMA( g_alias.lightspot, -64.0f, g_alias.lightvec, origin );\n\t\tpglVertex3fv( g_alias.lightspot );\n\t\tpglVertex3fv( origin );\n\t\tpglEnd();\n\n\t\tpglPointSize( 5.0f );\n\t\tpglColor3f( 1, 0, 0 );\n\t\tpglBegin( GL_POINTS );\n\t\tpglVertex3fv( g_alias.lightspot );\n\t\tpglEnd();\n\t\tpglPointSize( 1.0f );\n\n\t\tpglEnable( GL_DEPTH_TEST );\n\t\tpglEnable( GL_TEXTURE_2D );\n\t}\n}\n\n/*\n================\nR_AliasSetupTimings\n\ninit current time for a given model\n================\n*/\nstatic void R_AliasSetupTimings( void )\n{\n\tif( RI.drawWorld )\n\t{\n\t\t// synchronize with server time\n\t\tg_alias.time = gp_cl->time;\n\t}\n\telse\n\t{\n\t\t// menu stuff\n\t\tg_alias.time = gp_host->realtime;\n\t}\n\n\tm_fDoRemap = false;\n}\n\n/*\n=================\nR_DrawAliasModel\n\n=================\n*/\nvoid R_DrawAliasModel( cl_entity_t *e )\n{\n\tmodel_t\t\t*clmodel;\n\tvec3_t\t\tabsmin, absmax;\n\tremap_info_t\t*pinfo = NULL;\n\tint\t\tanim, skin;\n\talight_t\t\tlighting;\n\tplayer_info_t\t*playerinfo;\n\tvec3_t\t\tdir, angles;\n\n\tclmodel = RI.currententity->model;\n\n\tVectorAdd( e->origin, clmodel->mins, absmin );\n\tVectorAdd( e->origin, clmodel->maxs, absmax );\n\n\tif( R_CullModel( e, absmin, absmax ))\n\t\treturn;\n\n\t//\n\t// locate the proper data\n\t//\n\tm_pAliasHeader = (aliashdr_t *)gEngfuncs.Mod_Extradata( mod_alias, RI.currententity->model );\n\tif( !m_pAliasHeader ) return;\n\n\t// init time\n\tR_AliasSetupTimings();\n\n\t// angles will be modify below keep original\n\tVectorCopy( e->angles, angles );\n\n\tR_AliasLerpMovement( e );\n\n\tif( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\te->angles[PITCH] = -e->angles[PITCH]; // stupid quake bug\n\n\t// don't rotate clients, only aim\n\tif( e->player ) e->angles[PITCH] = 0.0f;\n\n\t//\n\t// get lighting information\n\t//\n\tlighting.plightvec = dir;\n\tR_AliasDynamicLight( e, &lighting );\n\n\tr_stats.c_alias_polys += m_pAliasHeader->numtris;\n\tr_stats.c_alias_models_drawn++;\n\n\t//\n\t// draw all the triangles\n\t//\n\n\tR_RotateForEntity( e );\n\n\t// model and frame independant\n\tR_AliasSetupLighting( &lighting );\n\tGL_SetRenderMode( e->curstate.rendermode );\n\n\t// setup remapping only for players\n\tif( e->player && ( playerinfo = pfnPlayerInfo( e->curstate.number - 1 )) != NULL )\n\t{\n\t\t// get remap colors\n\t\tint topcolor = bound( 0, playerinfo->topcolor, 13 );\n\t\tint bottomcolor = bound( 0, playerinfo->bottomcolor, 13 );\n\t\tR_AliasSetRemapColors( topcolor, bottomcolor );\n\t}\n\n\tif( tr.fFlipViewModel )\n\t{\n\t\tpglTranslatef( m_pAliasHeader->scale_origin[0], -m_pAliasHeader->scale_origin[1], m_pAliasHeader->scale_origin[2] );\n\t\tpglScalef( m_pAliasHeader->scale[0], -m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] );\n\t}\n\telse\n\t{\n\t\tpglTranslatef( m_pAliasHeader->scale_origin[0], m_pAliasHeader->scale_origin[1], m_pAliasHeader->scale_origin[2] );\n\t\tpglScalef( m_pAliasHeader->scale[0], m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] );\n\t}\n\n\tanim = (int)(g_alias.time * 10) & 3;\n\tskin = bound( 0, RI.currententity->curstate.skin, m_pAliasHeader->numskins - 1 );\n\tif( m_fDoRemap ) pinfo = gEngfuncs.CL_GetRemapInfoForEntity( e );\n\n\tif( r_lightmap->value && !r_fullbright->value )\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\telse if( pinfo != NULL && pinfo->textures[skin] != 0 )\n\t\tGL_Bind( XASH_TEXTURE0, pinfo->textures[skin] );\t// FIXME: allow remapping for skingroups someday\n\telse\n\t{\n\t\tGL_Bind( XASH_TEXTURE0, m_pAliasHeader->gl_texturenum[skin][anim] );\n\n\t\tif( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skin][anim] )->flags, TF_HAS_ALPHA ))\n\t\t{\n\t\t\tpglEnable( GL_ALPHA_TEST );\n\t\t\tpglAlphaFunc( GL_GREATER, 0.0f );\n\t\t\ttr.blend = 1.0f;\n\t\t}\n\t}\n\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\tif( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] )\n\t{\n\t\tGL_Bind( XASH_TEXTURE1, m_pAliasHeader->fb_texturenum[skin][anim] );\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );\n\t}\n\n\tpglShadeModel( GL_SMOOTH );\n\tR_SetupAliasFrame( e, m_pAliasHeader );\n\n\tif( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] )\n\t\tGL_CleanUpTextureUnits( 1 );\n\n\tpglShadeModel( GL_FLAT );\n\tR_LoadIdentity();\n\n\t// get lerped origin\n\tVectorAdd( e->origin, clmodel->mins, absmin );\n\tVectorAdd( e->origin, clmodel->maxs, absmax );\n\n\tR_AliasDrawAbsBBox( e, absmin, absmax );\n\tR_AliasDrawLightTrace( e );\n\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\tpglDisable( GL_ALPHA_TEST );\n\n\tif( r_shadows.value )\n\t{\n\t\t// need to compute transformation matrix\n\t\tMatrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, 1.0f );\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglEnable( GL_BLEND );\n\t\tpglColor4f( 0.0f, 0.0f, 0.0f, 0.5f );\n\t\tpglDepthFunc( GL_LESS );\n\n\t\tGL_DrawAliasShadow( m_pAliasHeader );\n\n\t\tpglDepthFunc( GL_LEQUAL );\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_BLEND );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\tR_LoadIdentity();\n\t}\n\n\t// restore original angles\n\tVectorCopy( angles, e->angles );\n}\n\n//==================================================================================\n"
  },
  {
    "path": "ref/gl/gl_backend.c",
    "content": "/*\ngl_backend.c - rendering backend\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n\nchar\t\tr_speeds_msg[MAX_SYSPATH];\nref_speeds_t\tr_stats;\t// r_speeds counters\n\n/*\n===============\nR_SpeedsMessage\n===============\n*/\nqboolean R_SpeedsMessage( char *out, size_t size )\n{\n\tif( gEngfuncs.drawFuncs->R_SpeedsMessage != NULL )\n\t{\n\t\tif( gEngfuncs.drawFuncs->R_SpeedsMessage( out, size ))\n\t\t\treturn true;\n\t\t// otherwise pass to default handler\n\t}\n\n\tif( r_speeds->value <= 0 ) return false;\n\tif( !out || !size ) return false;\n\n\tQ_strncpy( out, r_speeds_msg, size );\n\n\treturn true;\n}\n\n/*\n==============\nR_Speeds_Printf\n\nhelper to print into r_speeds message\n==============\n*/\nstatic void R_Speeds_Printf( const char *msg, ... )\n{\n\tva_list\targptr;\n\tchar\ttext[2048];\n\n\tva_start( argptr, msg );\n\tQ_vsnprintf( text, sizeof( text ), msg, argptr );\n\tva_end( argptr );\n\n\tQ_strncat( r_speeds_msg, text, sizeof( r_speeds_msg ));\n}\n\n/*\n==============\nGL_BackendStartFrame\n==============\n*/\nvoid GL_BackendStartFrame( void )\n{\n\tr_speeds_msg[0] = '\\0';\n}\n\n/*\n==============\nGL_BackendEndFrame\n==============\n*/\nvoid GL_BackendEndFrame( void )\n{\n\tmleaf_t\t*curleaf;\n\n\tif( r_speeds->value <= 0 || !RI.drawWorld )\n\t\treturn;\n\n\tif( !RI.viewleaf )\n\t\tcurleaf = WORLDMODEL->leafs;\n\telse curleaf = RI.viewleaf;\n\n\tR_Speeds_Printf( \"Renderer: ^1Engine^7\\n\\n\" );\n\n\tswitch( (int)r_speeds->value )\n\t{\n\tcase 1:\n\t\tQ_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), \"%3i wpoly, %3i apoly\\n%3i epoly, %3i spoly\",\n\t\t\tr_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys );\n\t\tbreak;\n\tcase 2:\n\t\tR_Speeds_Printf( \"visible leafs:\\n%3i leafs\\ncurrent leaf %3i\\n\", r_stats.c_world_leafs, curleaf - WORLDMODEL->leafs );\n\t\tR_Speeds_Printf( \"ReciusiveWorldNode: %3lf secs\\nDrawTextureChains %lf\\n\", r_stats.t_world_node, r_stats.t_world_draw );\n\t\tbreak;\n\tcase 3:\n\t\tQ_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), \"%3i alias models drawn\\n%3i studio models drawn\\n%3i sprites drawn\",\n\t\t\tr_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn );\n\t\tbreak;\n\tcase 4:\n\t\tQ_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), \"%3i static entities\\n%3i normal entities\\n%3i server entities\",\n\t\t\tr_numStatics, r_numEntities - r_numStatics, (int)ENGINE_GET_PARM( PARM_NUMENTITIES ));\n\t\tbreak;\n\tcase 5:\n\t\tQ_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), \"%3i tempents\\n%3i viewbeams\\n%3i particles\",\n\t\t\tr_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count );\n\t\tbreak;\n\t}\n\n\tmemset( &r_stats, 0, sizeof( r_stats ));\n}\n\n/*\n=================\nGL_LoadTexMatrixExt\n=================\n*/\nvoid GL_LoadTexMatrixExt( const float *glmatrix )\n{\n\tAssert( glmatrix != NULL );\n\tpglMatrixMode( GL_TEXTURE );\n\tpglLoadMatrixf( glmatrix );\n\tglState.texIdentityMatrix[glState.activeTMU] = false;\n}\n\n/*\n=================\nGL_LoadMatrix\n=================\n*/\nvoid GL_LoadMatrix( const matrix4x4 source )\n{\n\tGLfloat\tdest[16];\n\n\tMatrix4x4_ToArrayFloatGL( source, dest );\n\tpglLoadMatrixf( dest );\n}\n\n/*\n=================\nGL_LoadIdentityTexMatrix\n=================\n*/\nvoid GL_LoadIdentityTexMatrix( void )\n{\n\tif( glState.texIdentityMatrix[glState.activeTMU] )\n\t\treturn;\n\n\tpglMatrixMode( GL_TEXTURE );\n\tpglLoadIdentity();\n\tglState.texIdentityMatrix[glState.activeTMU] = true;\n}\n\n/*\n=================\nGL_SelectTexture\n=================\n*/\nvoid GL_SelectTexture( GLint tmu )\n{\n\tif( !GL_Support( GL_ARB_MULTITEXTURE ))\n\t\treturn;\n\n\t// don't allow negative texture units\n\tif( tmu < 0 ) return;\n\n\tif( tmu >= GL_MaxTextureUnits( ))\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"%s: bad tmu state %i\\n\", __func__, tmu );\n\t\treturn;\n\t}\n\n\tif( glState.activeTMU == tmu )\n\t\treturn;\n\n\tglState.activeTMU = tmu;\n\n\tif( pglActiveTextureARB )\n\t{\n\t\tpglActiveTextureARB( tmu + GL_TEXTURE0_ARB );\n\n\t\tif( tmu < glConfig.max_texture_coords )\n\t\t\tpglClientActiveTextureARB( tmu + GL_TEXTURE0_ARB );\n\t}\n}\n\n/*\n==============\nGL_DisableAllTexGens\n==============\n*/\nvoid GL_DisableAllTexGens( void )\n{\n\tGL_TexGen( GL_S, 0 );\n\tGL_TexGen( GL_T, 0 );\n\tGL_TexGen( GL_R, 0 );\n\tGL_TexGen( GL_Q, 0 );\n}\n\n/*\n==============\nGL_CleanUpTextureUnits\n==============\n*/\nvoid GL_CleanUpTextureUnits( int last )\n{\n\tint\ti;\n\n\tfor( i = glState.activeTMU; i > (last - 1); i-- )\n\t{\n\t\t// disable upper units\n\t\tif( glState.currentTextureTargets[i] != GL_NONE )\n\t\t{\n\t\t\tpglDisable( glState.currentTextureTargets[i] );\n\t\t\tglState.currentTextureTargets[i] = GL_NONE;\n\t\t\tglState.currentTextures[i] = -1; // unbind texture\n\t\t\tglState.currentTexturesIndex[i] = 0;\n\t\t}\n\n\t\tGL_SetTexCoordArrayMode( GL_NONE );\n\t\tGL_LoadIdentityTexMatrix();\n\t\tGL_DisableAllTexGens();\n\t\tGL_SelectTexture( i - 1 );\n\t}\n}\n\n/*\n==============\nGL_CleanupAllTextureUnits\n==============\n*/\nvoid GL_CleanupAllTextureUnits( void )\n{\n\tif( !glw_state.initialized ) return;\n\t// force to cleanup all the units\n\tGL_SelectTexture( GL_MaxTextureUnits() - 1 );\n\tGL_CleanUpTextureUnits( 0 );\n}\n\n/*\n=================\nGL_MultiTexCoord2f\n=================\n*/\nvoid GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t )\n{\n\tif( !GL_Support( GL_ARB_MULTITEXTURE ))\n\t\treturn;\n\n#ifndef XASH_GL_STATIC\n\tif( pglMultiTexCoord2f != NULL )\n#endif\n\t\tpglMultiTexCoord2f( texture + GL_TEXTURE0_ARB, s, t );\n}\n\n/*\n====================\nGL_EnableTextureUnit\n====================\n*/\nvoid GL_EnableTextureUnit( int tmu, qboolean enable )\n{\n\t// only enable fixed-function pipeline units\n\tif( tmu < glConfig.max_texture_units )\n\t{\n\t\tif( enable )\n\t\t{\n\t\t\tpglEnable( glState.currentTextureTargets[tmu] );\n\t\t}\n\t\telse if( glState.currentTextureTargets[tmu] != GL_NONE )\n\t\t{\n\t\t\tpglDisable( glState.currentTextureTargets[tmu] );\n\t\t}\n\t}\n}\n\n/*\n=================\nGL_TextureTarget\n=================\n*/\nvoid GL_TextureTarget( uint target )\n{\n\tif( glState.activeTMU < 0 || glState.activeTMU >= GL_MaxTextureUnits( ))\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"%s: bad tmu state %i\\n\", __func__, glState.activeTMU );\n\t\treturn;\n\t}\n\n\tif( glState.currentTextureTargets[glState.activeTMU] != target )\n\t{\n\t\tGL_EnableTextureUnit( glState.activeTMU, false );\n\t\tglState.currentTextureTargets[glState.activeTMU] = target;\n\t\tif( target != GL_NONE )\n\t\t\tGL_EnableTextureUnit( glState.activeTMU, true );\n\t}\n}\n\n/*\n=================\nGL_TexGen\n=================\n*/\nvoid GL_TexGen( GLenum coord, GLenum mode )\n{\n\tint\ttmu = Q_min( glConfig.max_texture_coords, glState.activeTMU );\n\tint\tbit, gen;\n\n\tswitch( coord )\n\t{\n\tcase GL_S:\n\t\tbit = 1;\n\t\tgen = GL_TEXTURE_GEN_S;\n\t\tbreak;\n\tcase GL_T:\n\t\tbit = 2;\n\t\tgen = GL_TEXTURE_GEN_T;\n\t\tbreak;\n\tcase GL_R:\n\t\tbit = 4;\n\t\tgen = GL_TEXTURE_GEN_R;\n\t\tbreak;\n\tcase GL_Q:\n\t\tbit = 8;\n\t\tgen = GL_TEXTURE_GEN_Q;\n\t\tbreak;\n\tdefault: return;\n\t}\n\n\tif( mode )\n\t{\n\t\tif( !( glState.genSTEnabled[tmu] & bit ))\n\t\t{\n\t\t\tpglEnable( gen );\n\t\t\tglState.genSTEnabled[tmu] |= bit;\n\t\t}\n\t\tpglTexGeni( coord, GL_TEXTURE_GEN_MODE, mode );\n\t}\n\telse\n\t{\n\t\tif( glState.genSTEnabled[tmu] & bit )\n\t\t{\n\t\t\tpglDisable( gen );\n\t\t\tglState.genSTEnabled[tmu] &= ~bit;\n\t\t}\n\t}\n}\n\n/*\n=================\nGL_SetTexCoordArrayMode\n=================\n*/\nvoid GL_SetTexCoordArrayMode( GLenum mode )\n{\n\tint\ttmu = Q_min( glConfig.max_texture_coords, glState.activeTMU );\n\tint\tbit, cmode = glState.texCoordArrayMode[tmu];\n\n\tif( mode == GL_TEXTURE_COORD_ARRAY )\n\t\tbit = 1;\n\telse if( mode == GL_TEXTURE_CUBE_MAP_ARB )\n\t\tbit = 2;\n\telse bit = 0;\n\n\tif( cmode != bit )\n\t{\n\t\tif( cmode == 1 ) pglDisableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\telse if( cmode == 2 ) pglDisable( GL_TEXTURE_CUBE_MAP_ARB );\n\n\t\tif( bit == 1 ) pglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\telse if( bit == 2 ) pglEnable( GL_TEXTURE_CUBE_MAP_ARB );\n\n\t\tglState.texCoordArrayMode[tmu] = bit;\n\t}\n}\n\n/*\n=================\nGL_Cull\n=================\n*/\nvoid GL_Cull( GLenum cull )\n{\n\tif( !cull )\n\t{\n\t\tpglDisable( GL_CULL_FACE );\n\t\tglState.faceCull = 0;\n\t\treturn;\n\t}\n\n\tpglEnable( GL_CULL_FACE );\n\tpglCullFace( cull );\n\tglState.faceCull = cull;\n}\n\nvoid GL_SetRenderMode( int mode )\n{\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\tswitch( mode )\n\t{\n\tcase kRenderNormal:\n\tdefault:\n\t\tpglDisable( GL_BLEND );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tbreak;\n\tcase kRenderTransColor:\n\tcase kRenderTransTexture:\n\t\tpglEnable( GL_BLEND );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tbreak;\n\tcase kRenderTransAlpha:\n\t\tpglDisable( GL_BLEND );\n\t\tpglEnable( GL_ALPHA_TEST );\n\t\tbreak;\n\tcase kRenderGlow:\n\tcase kRenderTransAdd:\n\t\tpglEnable( GL_BLEND );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\tbreak;\n\tcase kRenderScreenFadeModulate:\n\t\tpglEnable( GL_BLEND );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tpglBlendFunc( GL_ZERO, GL_SRC_COLOR );\n\t}\n}\n\n/*\n==============================================================================\n\nSCREEN SHOTS\n\n==============================================================================\n*/\n// used for 'env' and 'sky' shots\ntypedef struct envmap_s\n{\n\tvec3_t\tangles;\n\tint\tflags;\n} envmap_t;\n\nconst envmap_t r_skyBoxInfo[6] =\n{\n{{   0, 270, 180}, IMAGE_FLIP_X },\n{{   0,  90, 180}, IMAGE_FLIP_X },\n{{ -90,   0, 180}, IMAGE_FLIP_X },\n{{  90,   0, 180}, IMAGE_FLIP_X },\n{{   0,   0, 180}, IMAGE_FLIP_X },\n{{   0, 180, 180}, IMAGE_FLIP_X },\n};\n\nconst envmap_t r_envMapInfo[6] =\n{\n{{  0,   0,  90}, 0 },\n{{  0, 180, -90}, 0 },\n{{  0,  90,   0}, 0 },\n{{  0, 270, 180}, 0 },\n{{-90, 180, -90}, 0 },\n{{ 90,   0,  90}, 0 }\n};\n\nqboolean VID_ScreenShot( const char *filename, int shot_type )\n{\n\trgbdata_t *r_shot;\n\tuint\tflags = IMAGE_FLIP_Y;\n\tint\twidth = 0, height = 0;\n\tqboolean\tresult;\n\n\tr_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));\n\tr_shot->width = (gpGlobals->width + 3) & ~3;\n\tr_shot->height = (gpGlobals->height + 3) & ~3;\n\tr_shot->flags = IMAGE_HAS_COLOR;\n\tr_shot->type = PF_RGBA_32;\n\tr_shot->size = r_shot->width * r_shot->height * gEngfuncs.Image_GetPFDesc( r_shot->type )->bpp;\n\tr_shot->palette = NULL;\n\tr_shot->buffer = Mem_Malloc( r_temppool, r_shot->size );\n\n\t// get screen frame\n\tpglReadPixels( 0, 0, r_shot->width, r_shot->height, GL_RGBA, GL_UNSIGNED_BYTE, r_shot->buffer );\n\n\tswitch( shot_type )\n\t{\n\tcase VID_SCREENSHOT:\n\t\tbreak;\n\tcase VID_SNAPSHOT:\n\t\tgEngfuncs.fsapi->AllowDirectPaths( true );\n\t\tbreak;\n\tcase VID_LEVELSHOT:\n\tcase VID_MINISHOT:\n\t\tflags |= IMAGE_RESAMPLE;\n\t\theight = shot_type == VID_MINISHOT ? 200 : 480;\n\t\twidth = Q_rint( height * ((double)r_shot->width / r_shot->height ));\n\t\tbreak;\n\tcase VID_MAPSHOT:\n\t\tflags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE;\t// GoldSrc request overviews in 8-bit format\n\t\theight = 768;\n\t\twidth = 1024;\n\t\tbreak;\n\t}\n\n\tgEngfuncs.Image_Process( &r_shot, width, height, flags, 0.0f );\n\n\t// write image\n\tresult = gEngfuncs.FS_SaveImage( filename, r_shot );\n\tgEngfuncs.fsapi->AllowDirectPaths( false );\t\t\t// always reset after store screenshot\n\tgEngfuncs.FS_FreeImage( r_shot );\n\n\treturn result;\n}\n\n/*\n=================\nVID_CubemapShot\n=================\n*/\nqboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot )\n{\n\trgbdata_t\t\t*r_shot, *r_side;\n\tbyte\t\t*temp = NULL;\n\tbyte\t\t*buffer = NULL;\n\tstring\t\tbasename;\n\tint\t\ti = 1, flags, result;\n\n\tif( !RI.drawWorld || !WORLDMODEL )\n\t\treturn false;\n\n\t// make sure the specified size is valid\n\twhile( i < size ) i<<=1;\n\n\tif( i != size ) return false;\n\tif( size > gpGlobals->width || size > gpGlobals->height )\n\t\treturn false;\n\n\t// alloc space\n\ttemp = Mem_Malloc( r_temppool, size * size * 3 );\n\tbuffer = Mem_Malloc( r_temppool, size * size * 3 * 6 );\n\tr_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));\n\tr_side = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));\n\n\t// use client vieworg\n\tif( !vieworg ) vieworg = RI.vieworg;\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\t// go into 3d mode\n\t\tR_Set2DMode( false );\n\n\t\tif( skyshot )\n\t\t{\n\t\t\tR_DrawCubemapView( vieworg, r_skyBoxInfo[i].angles, size );\n\t\t\tflags = r_skyBoxInfo[i].flags;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tR_DrawCubemapView( vieworg, r_envMapInfo[i].angles, size );\n\t\t\tflags = r_envMapInfo[i].flags;\n\t\t}\n\n\t\tpglReadPixels( 0, 0, size, size, GL_RGB, GL_UNSIGNED_BYTE, temp );\n\t\tr_side->flags = IMAGE_HAS_COLOR;\n\t\tr_side->width = r_side->height = size;\n\t\tr_side->type = PF_RGB_24;\n\t\tr_side->size = r_side->width * r_side->height * 3;\n\t\tr_side->buffer = temp;\n\n\t\tif( flags ) gEngfuncs.Image_Process( &r_side, 0, 0, flags, 0.0f );\n\t\tmemcpy( buffer + (size * size * 3 * i), r_side->buffer, size * size * 3 );\n\t}\n\n\tr_shot->flags = IMAGE_HAS_COLOR;\n\tr_shot->flags |= (skyshot) ? IMAGE_SKYBOX : IMAGE_CUBEMAP;\n\tr_shot->width = size;\n\tr_shot->height = size;\n\tr_shot->type = PF_RGB_24;\n\tr_shot->size = r_shot->width * r_shot->height * 3 * 6;\n\tr_shot->palette = NULL;\n\tr_shot->buffer = buffer;\n\n\t// make sure what we have right extension\n\tQ_strncpy( basename, base, sizeof( basename ));\n\tCOM_ReplaceExtension( basename, \".tga\", sizeof( basename ));\n\n\t// write image as 6 sides\n\tresult = gEngfuncs.FS_SaveImage( basename, r_shot );\n\tgEngfuncs.FS_FreeImage( r_shot );\n\tgEngfuncs.FS_FreeImage( r_side );\n\n\treturn result;\n}\n\n//=======================================================\n\n/*\n===============\nR_ShowTextures\n\nDraw all the images to the screen, on top of whatever\nwas there.  This is used to test for texture thrashing.\n===============\n*/\nvoid R_ShowTextures( void )\n{\n\tfloat\t\tw, h;\n\tint\t\tstart, k;\n\tint base_w, base_h;\n\trgba_t\t\tcolor = { 255, 255, 255, 255 };\n\tint\t\tcharHeight;\n\tstatic qboolean\tshowHelp = true;\n\tfloat\ttime;\t//nc add\n\tfloat\ttime_cubemap;\t//nc add\n\tfloat\tcbm_cos, cbm_sin;\t//nc add\n\tint\t\tper_page; //nc add\n\tqboolean empty_page;\n\tint skipped_empty_pages;\n\n\tif( !r_showtextures->value )\n\t\treturn;\n\n\tif( showHelp )\n\t{\n\t\tgEngfuncs.CL_CenterPrint( \"use '<-' and '->' keys to change atlas page, ESC to quit\", 0.25f );\n\t\tshowHelp = false;\n\t}\n\n\tpglClear( GL_COLOR_BUFFER_BIT );\n\n\tw = 200;\n\th = 200;\n\n\ttime = gp_cl->time * 0.5f;\n\ttime -= floor( time );\n\ttime_cubemap = gp_cl->time * 0.25f;\n\ttime_cubemap -= floor( time_cubemap );\n\ttime_cubemap *= 6.2831853f;\n\tSinCos( time_cubemap, &cbm_sin, &cbm_cos );\n\n\tgEngfuncs.Con_DrawStringLen( NULL, NULL, &charHeight );\n\n\tbase_w = gpGlobals->width / w;\n\tbase_h = gpGlobals->height / ( h + charHeight * 2 );\n\tper_page = base_w * base_h;\n\tstart = per_page * ( r_showtextures->value - 1 ) + 1; // skip empty null texture\n\n\tGL_SetRenderMode( kRenderTransTexture );\t// nc changed from normal to trans, Con_DrawString does this anyway\n\n\tempty_page = true;\n\tskipped_empty_pages = 0;\n\twhile( empty_page )\n\t{\n\t\tfor( k = 0; k < per_page; k++ )\n\t\t{\n\t\t\tconst gl_texture_t *image;\n\t\t\tint i;\n\n\t\t\ti = k + start;\n\t\t\tif( i >= MAX_TEXTURES )\n\t\t\t{\n\t\t\t\tempty_page = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\timage = R_GetTexture( i );\n\t\t\tif( pglIsTexture( image->texnum ))\n\t\t\t{\n\t\t\t\tempty_page = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( empty_page )\n\t\t{\n\t\t\tstart += per_page;\n\t\t\tskipped_empty_pages++;\n\t\t}\n\t}\n\n\tif( skipped_empty_pages > 0 )\n\t{\n\t\tchar text[MAX_VA_STRING];\n\t\tQ_snprintf( text, sizeof( text ), \"%s: skipped %d empty texture pages\", __func__, skipped_empty_pages );\n\t\tgEngfuncs.CL_CenterPrint( text, 0.25f );\n\t}\n\n\tfor( k = 0; k < per_page; k++ )\n\t{\n\t\tconst gl_texture_t *image;\n\t\tint textlen, i;\n\t\tchar text[MAX_VA_STRING];\n\t\tstring shortname;\n\t\tfloat x, y;\n\n\t\ti = k + start;\n\t\tif ( i >= MAX_TEXTURES )\n\t\t\tbreak;\n\n\t\timage = R_GetTexture( i );\n\t\tif( !pglIsTexture( image->texnum ))\n\t\t\tcontinue;\n\n\t\tx = k % base_w * gpGlobals->width / base_w;\n\t\ty = k / base_w * gpGlobals->height / base_h;\n\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\tGL_Bind( XASH_TEXTURE0, image->texnum );\n\n\t\tif( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE ))\n\t\t\tpglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );\n\n\t\tpglBegin( GL_QUADS );\n\n#if XASH_NANOGL\n#undef pglTexCoord3f\n#define pglTexCoord3f( s, t, u ) pglTexCoord2f( s, t ) // not really correct but it requires nanogl rework\n#endif // XASH_GLES\n\n\t\tif( image->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t\t{\n\t\t\tpglTexCoord3f( 0.75 * cbm_cos - cbm_sin, 0.75 * cbm_sin + cbm_cos, 1.0 );\n\t\t\tpglVertex2f( x, y );\n\t\t\tpglTexCoord3f( 0.75 * cbm_cos + cbm_sin, 0.75 * cbm_sin - cbm_cos, 1.0 );\n\t\t\tpglVertex2f( x + w, y );\n\t\t\tpglTexCoord3f( 0.75 * cbm_cos + cbm_sin, 0.75 * cbm_sin - cbm_cos, -1.0 );\n\t\t\tpglVertex2f( x + w, y + h );\n\t\t\tpglTexCoord3f( 0.75 * cbm_cos - cbm_sin, 0.75 * cbm_sin + cbm_cos, -1.0 );\n\t\t\tpglVertex2f( x, y + h );\n\t\t}\n\t\telse if( image->target == GL_TEXTURE_RECTANGLE_EXT )\n\t\t{\n\t\t\tpglTexCoord2f( 0, 0 );\n\t\t\tpglVertex2f( x, y );\n\t\t\tpglTexCoord2f( image->width, 0 );\n\t\t\tpglVertex2f( x + w, y );\n\t\t\tpglTexCoord2f( image->width, image->height );\n\t\t\tpglVertex2f( x + w, y + h );\n\t\t\tpglTexCoord2f( 0, image->height );\n\t\t\tpglVertex2f( x, y + h );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglTexCoord3f( 0, 0, time );\n\t\t\tpglVertex2f( x, y );\n\t\t\tpglTexCoord3f( 1, 0, time );\n\t\t\tpglVertex2f( x + w, y );\n\t\t\tpglTexCoord3f( 1, 1, time );\n\t\t\tpglVertex2f( x + w, y + h );\n\t\t\tpglTexCoord3f( 0, 1, time);\n\t\t\tpglVertex2f( x, y + h );\n\t\t}\n\t\tpglEnd();\n\n\t\tif( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE ))\n\t\t\tpglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB );\n\n\t\tCOM_FileBase( image->name, shortname, sizeof( shortname ));\n\t\tgEngfuncs.Con_DrawStringLen( shortname, &textlen, NULL );\n\n\t\tif( textlen > w )\n\t\t{\n\t\t\t// cutoff too long names, it looks ugly\n\t\t\tshortname[16] = '.';\n\t\t\tshortname[17] = '.';\n\t\t\tshortname[18] = '\\0';\n\t\t}\n\n\t\tgEngfuncs.Con_DrawString( x + 1, y + h, shortname, color );\n\t\tif( image->target == GL_TEXTURE_3D || image->target == GL_TEXTURE_2D_ARRAY_EXT )\n\t\t\tQ_snprintf( text, sizeof( text ), \"%ix%ix%i %s\", image->width, image->height, image->depth, GL_TargetToString( image->target ));\n\t\telse\n\t\t\tQ_snprintf( text, sizeof( text ), \"%ix%i %s\", image->width, image->height, GL_TargetToString( image->target ));\n\t\tgEngfuncs.Con_DrawString( x + 1, y + h + charHeight, text, color );\n\n\t\tQ_strncpy( text, Q_memprint( image->size ), sizeof( text ));\n\t\tgEngfuncs.Con_DrawStringLen( text, &textlen, NULL );\n\t\tgEngfuncs.Con_DrawString(( x + w ) - textlen - 1, y + h + charHeight, text, color );\n\t}\n\n\tgEngfuncs.CL_DrawCenterPrint ();\n\tpglFinish();\n}\n\n/*\n================\nSCR_TimeRefresh_f\n\ntimerefresh [noflip]\n================\n*/\nvoid SCR_TimeRefresh_f( void )\n{\n\tint\ti;\n\tdouble\tstart, stop;\n\tdouble\ttime;\n\n\tif( ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_active )\n\t\treturn;\n\n\tstart = gEngfuncs.pfnTime();\n\n\t// run without page flipping like GoldSrc\n\tif( gEngfuncs.Cmd_Argc() == 1 )\n\t{\n\t\tpglDrawBuffer( GL_FRONT );\n\t\tfor( i = 0; i < 128; i++ )\n\t\t{\n\t\t\tgpGlobals->viewangles[1] = i / 128.0f * 360.0f;\n\t\t\tR_RenderScene();\n\t\t}\n\t\tpglFinish();\n\t\tR_EndFrame();\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < 128; i++ )\n\t\t{\n\t\t\tR_BeginFrame( true );\n\t\t\tgpGlobals->viewangles[1] = i / 128.0f * 360.0f;\n\t\t\tR_RenderScene();\n\t\t\tR_EndFrame();\n\t\t}\n\t}\n\n\tstop = gEngfuncs.pfnTime ();\n\ttime = (stop - start);\n\tgEngfuncs.Con_Printf( \"%f seconds (%f fps)\\n\", time, 128 / time );\n}\n"
  },
  {
    "path": "ref/gl/gl_beams.c",
    "content": "/*\ngl_beams.c - beams rendering\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"r_efx.h\"\n#include \"event_flags.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"customentity.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n\n#define NOISE_DIVISIONS\t64\t// don't touch - many tripmines cause the crash when it equal 128\n\ntypedef struct\n{\n\tvec3_t\tpos;\n\tfloat\ttexcoord;\t// Y texture coordinate\n\tfloat\twidth;\n} beamseg_t;\n\n/*\n==============================================================\n\nFRACTAL NOISE\n\n==============================================================\n*/\nstatic float\trgNoise[NOISE_DIVISIONS+1];\t// global noise array\n\n// freq2 += step * 0.1;\n// Fractal noise generator, power of 2 wavelength\nstatic void FracNoise( float *noise, int divs )\n{\n\tint\tdiv2;\n\n\tdiv2 = divs >> 1;\n\tif( divs < 2 ) return;\n\n\t// noise is normalized to +/- scale\n\tnoise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * gEngfuncs.COM_RandomFloat( -0.125f, 0.125f );\n\n\tif( div2 > 1 )\n\t{\n\t\tFracNoise( &noise[div2], div2 );\n\t\tFracNoise( noise, div2 );\n\t}\n}\n\nstatic void SineNoise( float *noise, int divs )\n{\n\tfloat\tfreq = 0;\n\tfloat\tstep = M_PI_F / (float)divs;\n\tint\ti;\n\n\tfor( i = 0; i < divs; i++ )\n\t{\n\t\tnoise[i] = sin( freq );\n\t\tfreq += step;\n\t}\n}\n\n\n/*\n==============================================================\n\nBEAM MATHLIB\n\n==============================================================\n*/\nstatic void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp )\n{\n\t// direction in worldspace of the center of the beam\n\tvec3_t\tvecBeamCenter;\n\n\tVectorNormalize2( vecBeamDelta, vecBeamCenter );\n\tCrossProduct( RI.vforward, vecBeamCenter, pPerp );\n\tVectorNormalize( pPerp );\n}\n\nstatic void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal )\n{\n\t// vTangentY = line vector for beam\n\tvec3_t\tvTangentY, vDirToBeam;\n\n\tVectorSubtract( vStartPos, vNextPos, vTangentY );\n\n\t// vDirToBeam = vector from viewer origin to beam\n\tVectorSubtract( vStartPos, RI.vieworg, vDirToBeam );\n\n\t// get a vector that is perpendicular to us and perpendicular to the beam.\n\t// this is used to fatten the beam.\n\tCrossProduct( vTangentY, vDirToBeam, pNormal );\n\tVectorNormalizeFast( pNormal );\n}\n\n\n/*\n==============\nR_BeamCull\n\nCull the beam by bbox\n==============\n*/\nqboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly )\n{\n\tvec3_t\tmins, maxs;\n\tint\ti;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t\tmaxs[i] += 1.0f;\n\t}\n\n\t// check bbox\n\tif( gEngfuncs.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )))\n\t{\n\t\tif( pvsOnly || !R_CullBox( mins, maxs ))\n\t\t{\n\t\t\t// beam is visible\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// beam is culled\n\treturn true;\n}\n\n/*\n================\nCL_AddCustomBeam\n\nAdd the beam that encoded as custom entity\n================\n*/\nvoid CL_AddCustomBeam( cl_entity_t *pEnvBeam )\n{\n\tif( tr.draw_list->num_beam_entities >= MAX_VISIBLE_PACKET )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Too many beams %d!\\n\", tr.draw_list->num_beam_entities );\n\t\treturn;\n\t}\n\n\tif( pEnvBeam )\n\t{\n\t\ttr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = pEnvBeam;\n\t\ttr.draw_list->num_beam_entities++;\n\t}\n}\n\n\n/*\n==============================================================\n\nBEAM DRAW METHODS\n\n==============================================================\n*/\n/*\n================\nR_DrawSegs\n\ngeneral code for drawing beams\n================\n*/\nstatic void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags )\n{\n\tint\tnoiseIndex, noiseStep;\n\tint\ti, total_segs, segs_drawn;\n\tfloat\tdiv, length, fraction, factor;\n\tfloat\tflMaxWidth, vLast, vStep, brightness;\n\tvec3_t\tperp1, vLastNormal;\n\tbeamseg_t\tcurSeg;\n\n\tif( segments < 2 ) return;\n\n\tlength = VectorLength( delta );\n\tflMaxWidth = width * 0.5f;\n\tdiv = 1.0f / ( segments - 1 );\n\n\tif( length * div < flMaxWidth * 1.414f )\n\t{\n\t\t// here, we have too many segments; we could get overlap... so lets have less segments\n\t\tsegments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f;\n\t\tif( segments < 2 ) segments = 2;\n\t}\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tdiv = 1.0f / (segments - 1);\n\tlength *= 0.01f;\n\tvStep = length * div;\t// Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\n\tif( flags & FBEAM_SINENOISE )\n\t{\n\t\tif( segments < 16 )\n\t\t{\n\t\t\tsegments = 16;\n\t\t\tdiv = 1.0f / ( segments - 1 );\n\t\t}\n\t\tscale *= 100.0f;\n\t\tlength = segments * 0.1f;\n\t}\n\telse\n\t{\n\t\tscale *= length * 2.0f;\n\t}\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tbrightness = 1.0f;\n\tnoiseIndex = 0;\n\n\tif( FBitSet( flags, FBEAM_SHADEIN ))\n\t\tbrightness = 0;\n\n\t// Choose two vectors that are perpendicular to the beam\n\tR_BeamComputePerpendicular( delta, perp1 );\n\n\ttotal_segs = segments;\n\tsegs_drawn = 0;\n\n\t// specify all the segments.\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tbeamseg_t\tnextSeg;\n\t\tvec3_t\tvPoint1, vPoint2;\n\n\t\tAssert( noiseIndex < ( NOISE_DIVISIONS << 16 ));\n\n\t\tfraction = i * div;\n\n\t\tVectorMA( source, fraction, delta, nextSeg.pos );\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tfactor = rgNoise[noiseIndex>>16] * scale;\n\n\t\t\tif( FBitSet( flags, FBEAM_SINENOISE ))\n\t\t\t{\n\t\t\t\tfloat\ts, c;\n\n\t\t\t\tSinCos( fraction * M_PI_F * length + freq, &s, &c );\n\t\t\t\tVectorMA( nextSeg.pos, (factor * s), RI.vup, nextSeg.pos );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tVectorMA( nextSeg.pos, (factor * c), RI.vright, nextSeg.pos );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorMA( nextSeg.pos, factor, perp1, nextSeg.pos );\n\t\t\t}\n\t\t}\n\n\t\t// specify the next segment.\n\t\tnextSeg.width = width * 2.0f;\n\t\tnextSeg.texcoord = vLast;\n\n \t\tif( segs_drawn > 0 )\n\t\t{\n\t\t\t// Get a vector that is perpendicular to us and perpendicular to the beam.\n\t\t\t// This is used to fatten the beam.\n\t\t\tvec3_t\tvNormal, vAveNormal;\n\n\t\t\tR_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal );\n\n\t\t\tif( segs_drawn > 1 )\n\t\t\t{\n\t\t\t\t// Average this with the previous normal\n\t\t\t\tVectorAdd( vNormal, vLastNormal, vAveNormal );\n\t\t\t\tVectorScale( vAveNormal, 0.5f, vAveNormal );\n\t\t\t\tVectorNormalizeFast( vAveNormal );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy( vNormal, vAveNormal );\n\t\t\t}\n\n\t\t\tVectorCopy( vNormal, vLastNormal );\n\n\t\t\t// draw regular segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 );\n\n\t\t\tpglTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tpglNormal3fv( vAveNormal );\n\t\t\tpglVertex3fv( vPoint1 );\n\n\t\t\tpglTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tpglNormal3fv( vAveNormal );\n\t\t\tpglVertex3fv( vPoint2 );\n\t\t}\n\n\t\tcurSeg = nextSeg;\n\t\tsegs_drawn++;\n\n\t\tif( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tif( fraction < 0.5f ) brightness = fraction;\n\t\t\telse brightness = ( 1.0f - fraction );\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEIN ))\n\t\t{\n\t\t\tbrightness = fraction;\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tbrightness = 1.0f - fraction;\n\t\t}\n\n \t\tif( segs_drawn == total_segs )\n\t\t{\n\t\t\t// draw the last segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 );\n\n\t\t\t// specify the points.\n\t\t\tpglTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tpglNormal3fv( vLastNormal );\n\t\t\tpglVertex3fv( vPoint1 );\n\n\t\t\tpglTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tpglNormal3fv( vLastNormal );\n\t\t\tpglVertex3fv( vPoint2 );\n\t\t}\n\n\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\t\tnoiseIndex += noiseStep;\n\t}\n}\n\n/*\n================\nR_DrawTorus\n\nDraw beamtours\n================\n*/\nstatic void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tint\ti, noiseIndex, noiseStep;\n\tfloat\tdiv, length, fraction, factor, vLast, vStep;\n\tvec3_t\tlast1, last2, point, screen, screenLast, tmp, normal;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\n\tvStep = length * div; // Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tnoiseIndex = 0;\n\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2];\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tif(( noiseIndex >> 16 ) < NOISE_DIVISIONS )\n\t\t\t{\n\t\t\t\tfactor = rgNoise[noiseIndex>>16] * scale;\n\t\t\t\tVectorMA( point, factor, RI.vup, point );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tfactor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI_F * 3 + freq );\n\t\t\t\tVectorMA( point, factor, RI.vright, point );\n\t\t\t}\n\t\t}\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\t\t\tVectorScale( RI.vup, -tmp[0], normal );\t// Build point along noraml line (normal is -y, x)\n\t\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep; // advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t}\n}\n\n/*\n================\nR_DrawDisk\n\nDraw beamdisk\n================\n*/\nstatic void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tfloat\tdiv, length, fraction;\n\tfloat\tw, vLast, vStep;\n\tvec3_t\tpoint;\n\tint\ti;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\t\t// UNDONE: Allow more segments?\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f;\t// don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\tvStep = length * div;\t\t// Texture length texels per space pixel\n\n\t// scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// clamp the beam width\n\tw = fmod( freq, width * 0.1f ) * delta[2];\n\n\t// NOTE: we must force the degenerate triangles to be on the edge\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tVectorCopy( source, point );\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 1.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\t\tpoint[0] = s * w + source[0];\n\t\tpoint[1] = c * w + source[1];\n\t\tpoint[2] = source[2];\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 0.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep;\t// advance texture scroll (v axis only)\n\t}\n}\n\n/*\n================\nR_DrawCylinder\n\nDraw beam cylinder\n================\n*/\nstatic void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tfloat\tdiv, length, fraction;\n\tfloat\tvLast, vStep;\n\tvec3_t\tpoint;\n\tint\ti;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f;\t// don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\tvStep = length * div;\t\t// texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\tfor ( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2] + width;\n\n\t\tTriBrightness( 0 );\n\t\tTriTexCoord2f( 1, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tpoint[0] = s * freq * ( delta[2] + width ) + source[0];\n\t\tpoint[1] = c * freq * ( delta[2] + width ) + source[1];\n\t\tpoint[2] = source[2] - width;\n\n\t\tTriBrightness( 1 );\n\t\tTriTexCoord2f( 0, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\t}\n}\n\n/*\n==============\nR_DrawBeamFollow\n\ndrawi followed beam\n==============\n*/\nstatic void R_DrawBeamFollow( BEAM *pbeam, float frametime )\n{\n\tparticle_t\t*pnew, *particles;\n\tfloat\t\tfraction, div, vLast, vStep;\n\tvec3_t\t\tlast1, last2, tmp, screen;\n\tvec3_t\t\tdelta, screenLast, normal;\n\n\tgEngfuncs.R_FreeDeadParticles( &pbeam->particles );\n\n\tparticles = pbeam->particles;\n\tpnew = NULL;\n\n\tdiv = 0;\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tif( particles )\n\t\t{\n\t\t\tVectorSubtract( particles->org, pbeam->source, delta );\n\t\t\tdiv = VectorLength( delta );\n\n\t\t\tif( div >= 32 )\n\t\t\t{\n\t\t\t\tpnew = gEngfuncs.CL_AllocParticleFast();\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpnew = gEngfuncs.CL_AllocParticleFast();\n\t\t}\n\t}\n\n\tif( pnew )\n\t{\n\t\tVectorCopy( pbeam->source, pnew->org );\n\t\tpnew->die = gp_cl->time + pbeam->amplitude;\n\t\tVectorClear( pnew->vel );\n\n\t\tpnew->next = particles;\n\t\tpbeam->particles = pnew;\n\t\tparticles = pnew;\n\t}\n\n\t// nothing to draw\n\tif( !particles ) return;\n\n\tif( !pnew && div != 0 )\n\t{\n\t\tVectorCopy( pbeam->source, delta );\n\t\tTriWorldToScreen( pbeam->source, screenLast );\n\t\tTriWorldToScreen( particles->org, screen );\n\t}\n\telse if( particles && particles->next )\n\t{\n\t\tVectorCopy( particles->org, delta );\n\t\tTriWorldToScreen( particles->org, screenLast );\n\t\tTriWorldToScreen( particles->next->org, screen );\n\t\tparticles = particles->next;\n\t}\n\telse\n\t{\n\t\treturn;\n\t}\n\n\t// UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the\n\t// first beam segment for this trail\n\n\t// build world-space normal to screen-space direction vector\n\tVectorSubtract( screen, screenLast, tmp );\n\t// we don't need Z, we're in screen space\n\ttmp[2] = 0;\n\tVectorNormalize( tmp );\n\n\t// Build point along noraml line (normal is -y, x)\n\tVectorScale( RI.vup, tmp[0], normal );\t// Build point along normal line (normal is -y, x)\n\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t// Make a wide line\n\tVectorMA( delta, pbeam->width, normal, last1 );\n\tVectorMA( delta, -pbeam->width, normal, last2 );\n\n\tdiv = 1.0f / pbeam->amplitude;\n\tfraction = ( pbeam->die - gp_cl->time ) * div;\n\n\tvLast = 0.0f;\n\tvStep = 1.0f;\n\n\twhile( particles )\n\t{\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 1 );\n\t\tTriVertex3fv( last2 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 1 );\n\t\tTriVertex3fv( last1 );\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( particles->org, screen );\n\t\t// Build world-space normal to screen-space direction vector\n\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t// we don't need Z, we're in screen space\n\t\ttmp[2] = 0;\n\t\tVectorNormalize( tmp );\n\t\tVectorScale( RI.vup, tmp[0], normal );\t// Build point along noraml line (normal is -y, x)\n\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t// Make a wide line\n\t\tVectorMA( particles->org, pbeam->width, normal, last1 );\n\t\tVectorMA( particles->org, -pbeam->width, normal, last2 );\n\n\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\n\t\tif( particles->next != NULL )\n\t\t{\n\t\t\tfraction = (particles->die - gp_cl->time) * div;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfraction = 0.0;\n\t\t}\n\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 0 );\n\t\tTriVertex3fv( last1 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 0 );\n\t\tTriVertex3fv( last2 );\n\n\t\tVectorCopy( screen, screenLast );\n\n\t\tparticles = particles->next;\n\t}\n\n\t// drift popcorn trail if there is a velocity\n\tparticles = pbeam->particles;\n\n\twhile( particles )\n\t{\n\t\tVectorMA( particles->org, frametime, particles->vel, particles->org );\n\t\tparticles = particles->next;\n\t}\n}\n\n/*\n================\nR_DrawRing\n\nDraw beamring\n================\n*/\nstatic void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments )\n{\n\tint\ti, j, noiseIndex, noiseStep;\n\tfloat\tdiv, length, fraction, factor, vLast, vStep;\n\tvec3_t\tlast1, last2, point, screen, screenLast;\n\tvec3_t\ttmp, normal, center, xaxis, yaxis;\n\tfloat\tradius, x, y, scale;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tVectorClear( screenLast );\n\tsegments = segments * M_PI_F;\n\n\tif( segments > NOISE_DIVISIONS * 8 )\n\t\tsegments = NOISE_DIVISIONS * 8;\n\n\tlength = VectorLength( delta ) * 0.01f * M_PI_F;\n\tif( length < 0.5f ) length = 0.5f;\t\t// Don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\n\tvStep = length * div / 8.0f;\t\t\t// texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1.0f );\n\tscale = amplitude * length / 8.0f;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8;\n\tnoiseIndex = 0;\n\n\tVectorScale( delta, 0.5f, delta );\n\tVectorAdd( source, delta, center );\n\n\tVectorCopy( delta, xaxis );\n\tradius = VectorLength( xaxis );\n\n\t// cull beamring\n\t// --------------------------------\n\t// Compute box center +/- radius\n\tVectorSet( last1, radius, radius, scale );\n\tVectorAdd( center, last1, tmp );\t\t// maxs\n\tVectorSubtract( center, last1, screen );\t// mins\n\n\tif( !WORLDMODEL )\n\t\treturn;\n\n\t// is that box in PVS && frustum?\n\tif( !gEngfuncs.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp ))\n\t{\n\t\treturn;\n\t}\n\n\tVectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f );\n\tVectorNormalize( yaxis );\n\tVectorScale( yaxis, radius, yaxis );\n\n\tj = segments / 8;\n\n\tfor( i = 0; i < segments + 1; i++ )\n\t{\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &x, &y );\n\n\t\tVectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point );\n\n\t\t// distort using noise\n\t\tfactor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;\n\t\tVectorMA( point, factor, RI.vup, point );\n\n\t\t// Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\tfactor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;\n\t\tfactor *= cos( fraction * M_PI_F * 24 + freq );\n\t\tVectorMA( point, factor, RI.vright, point );\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\t// Build point along normal line (normal is -y, x)\n\t\t\tVectorScale( RI.vup, tmp[0], normal );\n\t\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1.0f, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0.0f, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t\tj--;\n\n\t\tif( j == 0 && amplitude != 0 )\n\t\t{\n\t\t\tj = segments / 8;\n\t\t\tFracNoise( rgNoise, NOISE_DIVISIONS );\n\t\t}\n\t}\n}\n\n/*\n==============\nR_BeamComputePoint\n\ncompute attachment point for beam\n==============\n*/\nstatic qboolean R_BeamComputePoint( int beamEnt, vec3_t pt )\n{\n\tcl_entity_t\t*ent;\n\tint\t\tattach;\n\n\tent = gEngfuncs.R_BeamGetEntity( beamEnt );\n\n\tif( beamEnt < 0 )\n\t\tattach = BEAMENT_ATTACHMENT( -beamEnt );\n\telse attach = BEAMENT_ATTACHMENT( beamEnt );\n\n\tif( !ent )\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s: invalid entity %i\\n\", __func__, BEAMENT_ENTITY( beamEnt ));\n\t\tVectorClear( pt );\n\t\treturn false;\n\t}\n\n\t// get attachment\n\tif( attach > 0 )\n\t\tVectorCopy( ent->attachment[attach - 1], pt );\n\telse if( ent->index == ( gp_cl->playernum + 1 ))\n\t\tVectorCopy( gp_cl->simorg, pt );\n\telse VectorCopy( ent->origin, pt );\n\n\treturn true;\n}\n\n/*\n==============\nR_BeamRecomputeEndpoints\n\nRecomputes beam endpoints..\n==============\n*/\nstatic qboolean R_BeamRecomputeEndpoints( BEAM *pbeam )\n{\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tcl_entity_t *start = gEngfuncs.R_BeamGetEntity( pbeam->startEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->startEntity, pbeam->source ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = start->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_STARTVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_STARTENTITY );\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_ENDENTITY ))\n\t{\n\t\tcl_entity_t *end = gEngfuncs.R_BeamGetEntity( pbeam->endEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->endEntity, pbeam->target ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = end->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_ENDVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ENDENTITY );\n\t\t\tpbeam->die = gp_cl->time;\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE ))\n\t\treturn false;\n\treturn true;\n}\n\n\n/*\n==============\nR_BeamDraw\n\nUpdate beam vars and draw it\n==============\n*/\nstatic void R_BeamDraw( BEAM *pbeam, float frametime )\n{\n\tmodel_t\t*model;\n\tvec3_t\tdelta;\n\n\tmodel = CL_ModelHandle( pbeam->modelIndex );\n\tSetBits( pbeam->flags, FBEAM_ISACTIVE );\n\n\tif( !model || model->type != mod_sprite )\n\t{\n\t\tpbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore\n\t\tpbeam->die = gp_cl->time;\n\t\treturn;\n\t}\n\n\t// update frequency\n\tpbeam->freq += frametime;\n\n\t// generate fractal noise\n\tif( frametime != 0.0f )\n\t{\n\t\trgNoise[0] = 0;\n\t\trgNoise[NOISE_DIVISIONS] = 0;\n\t}\n\n\tif( pbeam->amplitude != 0 && frametime != 0.0f )\n\t{\n\t\tif( FBitSet( pbeam->flags, FBEAM_SINENOISE ))\n\t\t\tSineNoise( rgNoise, NOISE_DIVISIONS );\n\t\telse FracNoise( rgNoise, NOISE_DIVISIONS );\n\t}\n\n\t// update end points\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY ))\n\t{\n\t\t// makes sure attachment[0] + attachment[1] are valid\n\t\tif( !R_BeamRecomputeEndpoints( pbeam ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore\n\t\t\treturn;\n\t\t}\n\n\t\t// compute segments from the new endpoints\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorClear( pbeam->delta );\n\n\t\tif( VectorLength( delta ) > 0.0000001f )\n\t\t\tVectorCopy( delta, pbeam->delta );\n\n\t\tif( pbeam->amplitude >= 0.50f )\n\t\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels\n\t\telse pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels\n\t}\n\n\tif( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 ))\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\t// don't draw really short or inactive beams\n\tif( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f )\n\t{\n\t\treturn;\n\t}\n\n\tif( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT ))\n\t{\n\t\t// update life cycle\n\t\tpbeam->t = pbeam->freq + ( pbeam->die - gp_cl->time );\n\t\tif( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t;\n\t}\n\n\tif( pbeam->type == TE_BEAMHOSE )\n\t{\n\t\tfloat\tflDot;\n\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorNormalize( delta );\n\n\t\tflDot = DotProduct( delta, RI.vforward );\n\n\t\t// abort if the player's looking along it away from the source\n\t\tif( flDot > 0 )\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat\tflFade = pow( flDot, 10 );\n\t\t\tvec3_t\tlocalDir, vecProjection, tmp;\n\t\t\tfloat\tflDistance;\n\n\t\t\t// fade the beam if the player's not looking at the source\n\t\t\tVectorSubtract( RI.vieworg, pbeam->source, localDir );\n\t\t\tflDot = DotProduct( delta, localDir );\n\t\t\tVectorScale( delta, flDot, vecProjection );\n\t\t\tVectorSubtract( localDir, vecProjection, tmp );\n\t\t\tflDistance = VectorLength( tmp );\n\n\t\t\tif( flDistance > 30 )\n\t\t\t{\n\t\t\t\tflDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f );\n\t\t\t\tif( flDistance <= 0 ) flFade = 0;\n\t\t\t\telse flFade *= pow( flDistance, 3 );\n\t\t\t}\n\n\t\t\tif( flFade < ( 1.0f / 255.0f ))\n\t\t\t\treturn;\n\n\t\t\t// FIXME: needs to be testing\n\t\t\tpbeam->brightness *= flFade;\n\t\t}\n\t}\n\n\tTriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd );\n\n\tif( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gp_cl->time) % pbeam->frameCount ))\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\tif( pbeam->type == TE_BEAMFOLLOW )\n\t{\n\t\tcl_entity_t\t*pStart;\n\n\t\t// XASH SPECIFIC: get brightness from head entity\n\t\tpStart = gEngfuncs.R_BeamGetEntity( pbeam->startEntity );\n\t\tif( pStart && pStart->curstate.rendermode != kRenderNormal )\n\t\t\tpbeam->brightness = CL_FxBlend( pStart ) / 255.0f;\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_FADEIN ))\n\t\tTriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness );\n\telse if( FBitSet( pbeam->flags, FBEAM_FADEOUT ))\n\t\tTriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness );\n\telse TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness );\n\n\tswitch( pbeam->type )\n\t{\n\tcase TE_BEAMTORUS:\n\t\tGL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMDISK:\n\t\tGL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMCYLINDER:\n\t\tGL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMPOINTS:\n\tcase TE_BEAMHOSE:\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMFOLLOW:\n\t\tTriBegin( TRI_QUADS );\n\t\tR_DrawBeamFollow( pbeam, frametime );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMRING:\n\t\tGL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\t}\n\n\tGL_Cull( GL_FRONT );\n\tr_stats.c_view_beams_count++;\n}\n\n/*\n==============\nR_BeamSetAttributes\n\nset beam attributes\n==============\n*/\nstatic void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame )\n{\n\tpbeam->frame = (float)startFrame;\n\tpbeam->frameRate = framerate;\n\tpbeam->r = r;\n\tpbeam->g = g;\n\tpbeam->b = b;\n}\n\n/*\n==============\nR_BeamSetup\n\ngeneric function. all beams must be\npassed through this\n==============\n*/\nstatic void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )\n{\n\tmodel_t\t*sprite = CL_ModelHandle( modelIndex );\n\n\tif( !sprite ) return;\n\n\tpbeam->type = BEAM_POINTS;\n\tpbeam->modelIndex = modelIndex;\n\tpbeam->frame = 0;\n\tpbeam->frameRate = 0;\n\tpbeam->frameCount = sprite->numframes;\n\n\tVectorCopy( start, pbeam->source );\n\tVectorCopy( end, pbeam->target );\n\tVectorSubtract( end, start, pbeam->delta );\n\n\tpbeam->freq = speed * gp_cl->time;\n\tpbeam->die = life + gp_cl->time;\n\tpbeam->amplitude = amplitude;\n\tpbeam->brightness = brightness;\n\tpbeam->width = width;\n\tpbeam->speed = speed;\n\n\tif( amplitude >= 0.50f )\n\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f;\t// one per 4 pixels\n\telse pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f;\t\t// one per 16 pixels\n\n\tpbeam->pFollowModel = NULL;\n\tpbeam->flags = 0;\n}\n\n\n\n/*\n==============\nR_BeamDrawCustomEntity\n\ninitialize beam from server entity\n==============\n*/\nstatic void R_BeamDrawCustomEntity( cl_entity_t *ent )\n{\n\tBEAM\tbeam;\n\tfloat\tamp = ent->curstate.body / 100.0f;\n\tfloat\tblend = CL_FxBlend( ent ) / 255.0f;\n\tfloat\tr, g, b;\n\tint\tbeamFlags;\n\n\tr = ent->curstate.rendercolor.r / 255.0f;\n\tg = ent->curstate.rendercolor.g / 255.0f;\n\tb = ent->curstate.rendercolor.b / 255.0f;\n\n\tR_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime );\n\tR_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame );\n\tbeam.pFollowModel = NULL;\n\n\tswitch( ent->curstate.rendermode & 0x0F )\n\t{\n\tcase BEAM_ENTPOINT:\n\t\tbeam.type\t= TE_BEAMPOINTS;\n\t\tif( ent->curstate.sequence )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_STARTENTITY );\n\t\t\tbeam.startEntity = ent->curstate.sequence;\n\t\t}\n\t\tif( ent->curstate.skin )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_ENDENTITY );\n\t\t\tbeam.endEntity = ent->curstate.skin;\n\t\t}\n\t\tbreak;\n\tcase BEAM_ENTS:\n\t\tbeam.type\t= TE_BEAMPOINTS;\n\t\tSetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );\n\t\tbeam.startEntity = ent->curstate.sequence;\n\t\tbeam.endEntity = ent->curstate.skin;\n\t\tbreak;\n\tcase BEAM_HOSE:\n\t\tbeam.type\t= TE_BEAMHOSE;\n\t\tbreak;\n\tcase BEAM_POINTS:\n\t\t// already set up\n\t\tbreak;\n\t}\n\n\tbeamFlags = ( ent->curstate.rendermode & 0xF0 );\n\n\tif( FBitSet( beamFlags, BEAM_FSINE ))\n\t\tSetBits( beam.flags, FBEAM_SINENOISE );\n\n\tif( FBitSet( beamFlags, BEAM_FSOLID ))\n\t\tSetBits( beam.flags, FBEAM_SOLID );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEIN ))\n\t\tSetBits( beam.flags, FBEAM_SHADEIN );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEOUT ))\n\t\tSetBits( beam.flags, FBEAM_SHADEOUT );\n\n\t// draw it\n\tR_BeamDraw( &beam, tr.frametime );\n}\n\n\n/*\n==============\nCL_DrawBeams\n\ndraw beam loop\n==============\n*/\nvoid CL_DrawBeams( int fTrans, BEAM *active_beams )\n{\n\tBEAM\t*pBeam;\n\tint\ti, flags;\n\n\tpglShadeModel( GL_SMOOTH );\n\tpglDepthMask( fTrans ? GL_FALSE : GL_TRUE );\n\n\t// server beams don't allocate beam chains\n\t// all params are stored in cl_entity_t\n\tfor( i = 0; i < tr.draw_list->num_beam_entities; i++ )\n\t{\n\t\tRI.currentbeam = tr.draw_list->beam_entities[i];\n\t\tflags = RI.currentbeam->curstate.rendermode & 0xF0;\n\n\t\tif( fTrans && FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDrawCustomEntity( RI.currentbeam );\n\t\tr_stats.c_view_beams_count++;\n\t}\n\n\tRI.currentbeam = NULL;\n\n\t// draw temporary entity beams\n\tfor( pBeam = active_beams; pBeam; pBeam = pBeam->next )\n\t{\n\t\tif( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDraw( pBeam, gp_cl->time -   gp_cl->oldtime );\n\t}\n\n\tpglShadeModel( GL_FLAT );\n\tpglDepthMask( GL_TRUE );\n}\n"
  },
  {
    "path": "ref/gl/gl_context.c",
    "content": "/*\nvid_sdl.c - SDL vid component\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n// GL API function pointers, if any, reside in this translation unit\n#define APIENTRY_LINKAGE\n#include \"gl_local.h\"\n#include \"gl_export.h\"\n\n#if XASH_GL4ES\n#include \"gl4es/include/gl4esinit.h\"\n#endif\n\nref_api_t      gEngfuncs;\nref_globals_t *gpGlobals;\nref_client_t  *gp_cl;\nref_host_t    *gp_host;\n\nvoid _Mem_Free( void *data, const char *filename, int fileline )\n{\n\tgEngfuncs._Mem_Free( data, filename, fileline );\n}\n\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\treturn gEngfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline );\n}\n\nstatic void R_ClearScreen( void )\n{\n\tpglClearColor( 0.0f, 0.0f, 0.0f, 0.0f );\n\tpglClear( GL_COLOR_BUFFER_BIT );\n}\n\nstatic const byte *R_GetTextureOriginalBuffer( unsigned int idx )\n{\n\tgl_texture_t *glt = R_GetTexture( idx );\n\n\tif( !glt || !glt->original || !glt->original->buffer )\n\t\treturn NULL;\n\n\treturn glt->original->buffer;\n}\n\n/*\n=============\nCL_FillRGBA\n\n=============\n*/\nstatic void CL_FillRGBA( int rendermode, float _x, float _y, float _w, float _h, byte r, byte g, byte b, byte a )\n{\n\tpglDisable( GL_TEXTURE_2D );\n\tpglEnable( GL_BLEND );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tif( rendermode == kRenderTransAdd )\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\telse\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\tpglColor4ub( r, g, b, a );\n\n\tpglBegin( GL_QUADS );\n\t\tpglVertex2f( _x, _y );\n\t\tpglVertex2f( _x + _w, _y );\n\t\tpglVertex2f( _x + _w, _y + _h );\n\t\tpglVertex2f( _x, _y + _h );\n\tpglEnd ();\n\n\tpglEnable( GL_TEXTURE_2D );\n\tpglDisable( GL_BLEND );\n}\n\nstatic qboolean Mod_LooksLikeWaterTexture( const char *name )\n{\n\tif(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' )\n\t\treturn true;\n\n\tif( !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t{\n\t\tif( !Q_strncmp( name, \"water\", 5 ) || !Q_strnicmp( name, \"laser\", 5 ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void Mod_BrushUnloadTextures( model_t *mod )\n{\n\tint i;\n\n\tfor( i = 0; i < mod->numtextures; i++ )\n\t{\n\t\ttexture_t *tx = mod->textures[i];\n\t\tif( !tx )\n\t\t\tcontinue; // free slot\n\n\t\tif( tx->gl_texturenum != tr.defaultTexture )\n\t\t\tGL_FreeTexture( tx->gl_texturenum ); // main texture\n\n\t\tif( !Mod_LooksLikeWaterTexture( tx->name ))\n\t\t{\n\t\t\tGL_FreeTexture( tx->fb_texturenum ); // luma texture\n\t\t\tGL_FreeTexture( tx->dt_texturenum ); // detail texture\n\t\t}\n\t}\n}\n\nstatic void Mod_UnloadTextures( model_t *mod )\n{\n\tAssert( mod != NULL );\n\n\tswitch( mod->type )\n\t{\n\tcase mod_studio:\n\t\tMod_StudioUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_alias:\n\t\tMod_AliasUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_brush:\n\t\tMod_BrushUnloadTextures( mod );\n\t\tbreak;\n\tcase mod_sprite:\n\t\tMod_SpriteUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tdefault:\n\t\tASSERT( 0 );\n\t\tbreak;\n\t}\n}\n\nstatic qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf )\n{\n\tqboolean loaded = false;\n\n\tif( !create )\n\t{\n\t\tif( gEngfuncs.drawFuncs->Mod_ProcessUserData )\n\t\t\tgEngfuncs.drawFuncs->Mod_ProcessUserData( mod, false, buf );\n\t\tMod_UnloadTextures( mod );\n\t\treturn true;\n\t}\n\n\tswitch( mod->type )\n\t{\n\tcase mod_studio:\n\tcase mod_brush:\n\t\tloaded = true;\n\t\tbreak;\n\tcase mod_sprite:\n\t\tMod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo );\n\t\tbreak;\n\tcase mod_alias:\n\t\tMod_LoadAliasModel( mod, buf, &loaded );\n\t\tbreak;\n\tdefault:\n\t\tgEngfuncs.Host_Error( \"%s: unsupported type %d\\n\", __func__, mod->type );\n\t\treturn false;\n\t}\n\n\tif( gEngfuncs.drawFuncs->Mod_ProcessUserData )\n\t\tgEngfuncs.drawFuncs->Mod_ProcessUserData( mod, true, buf );\n\n\treturn loaded;\n}\n\nstatic int GL_RefGetParm( int parm, int arg )\n{\n\tgl_texture_t *glt;\n\n\tswitch( parm )\n\t{\n\tcase PARM_TEX_WIDTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->width;\n\tcase PARM_TEX_HEIGHT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->height;\n\tcase PARM_TEX_SRC_WIDTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->srcWidth;\n\tcase PARM_TEX_SRC_HEIGHT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->srcHeight;\n\tcase PARM_TEX_GLFORMAT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->format;\n\tcase PARM_TEX_ENCODE:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->encode;\n\tcase PARM_TEX_MIPCOUNT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->numMips;\n\tcase PARM_TEX_DEPTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->depth;\n\tcase PARM_TEX_SKYBOX:\n\t\tAssert( arg >= 0 && arg < 6 );\n\t\treturn tr.skyboxTextures[arg];\n\tcase PARM_TEX_SKYTEXNUM:\n\t\treturn tr.skytexturenum;\n\tcase PARM_TEX_LIGHTMAP:\n\t\targ = bound( 0, arg, MAX_LIGHTMAPS - 1 );\n\t\treturn tr.lightmapTextures[arg];\n\tcase PARM_TEX_TARGET:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->target;\n\tcase PARM_TEX_TEXNUM:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->texnum;\n\tcase PARM_TEX_FLAGS:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->flags;\n\tcase PARM_TEX_MEMORY:\n\t\treturn GL_TexMemory();\n\tcase PARM_ACTIVE_TMU:\n\t\treturn glState.activeTMU;\n\tcase PARM_LIGHTSTYLEVALUE:\n\t\targ = bound( 0, arg, MAX_LIGHTSTYLES - 1 );\n\t\treturn tr.lightstylevalue[arg];\n\tcase PARM_MAX_IMAGE_UNITS:\n\t\treturn GL_MaxTextureUnits();\n\tcase PARM_REBUILD_GAMMA:\n\t\treturn glConfig.softwareGammaUpdate;\n\tcase PARM_GL_CONTEXT_TYPE:\n\t\treturn glConfig.context;\n\tcase PARM_GLES_WRAPPER:\n\t\treturn glConfig.wrapper;\n\tcase PARM_STENCIL_ACTIVE:\n\t\treturn glState.stencilEnabled;\n\tcase PARM_TEX_FILTERING:\n\t\tif( arg < 0 )\n\t\t\treturn gl_texture_nearest.value == 0.0f;\n\n\t\treturn GL_TextureFilteringEnabled( R_GetTexture( arg ));\n\tdefault:\n\t\treturn ENGINE_GET_PARM_( parm, arg );\n\t}\n\treturn 0;\n}\n\nstatic void R_GetDetailScaleForTexture( int texture, float *xScale, float *yScale )\n{\n\tgl_texture_t *glt = R_GetTexture( texture );\n\n\tif( xScale ) *xScale = glt->xscale;\n\tif( yScale ) *yScale = glt->yscale;\n}\n\nstatic void R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density )\n{\n\tgl_texture_t *glt = R_GetTexture( texture );\n\n\tif( red ) *red = glt->fogParams[0];\n\tif( green ) *green = glt->fogParams[1];\n\tif( blue ) *blue = glt->fogParams[2];\n\tif( density ) *density = glt->fogParams[3];\n}\n\n\nstatic void R_SetCurrentEntity( cl_entity_t *ent )\n{\n\tRI.currententity = ent;\n\n\t// set model also\n\tif( RI.currententity != NULL )\n\t{\n\t\tRI.currentmodel = RI.currententity->model;\n\t}\n}\n\nstatic void R_SetCurrentModel( model_t *mod )\n{\n\tRI.currentmodel = mod;\n}\n\nstatic float R_GetFrameTime( void )\n{\n\treturn tr.frametime;\n}\n\nstatic const char *GL_TextureName( unsigned int texnum )\n{\n\treturn R_GetTexture( texnum )->name;\n}\n\nstatic const byte *GL_TextureData( unsigned int texnum )\n{\n\trgbdata_t *pic = R_GetTexture( texnum )->original;\n\n\tif( pic != NULL )\n\t\treturn pic->buffer;\n\treturn NULL;\n}\n\nstatic void R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities )\n{\n\tif( !allocate )\n\t{\n\t\ttr.draw_list->num_solid_entities = 0;\n\t\ttr.draw_list->num_trans_entities = 0;\n\t\ttr.draw_list->num_beam_entities = 0;\n\n\t\ttr.max_entities = 0;\n\t\ttr.entities = NULL;\n\t}\n\telse\n\t{\n\t\ttr.max_entities = max_entities;\n\t\ttr.entities = entities;\n\t}\n\n\tif( gEngfuncs.drawFuncs->R_ProcessEntData )\n\t\tgEngfuncs.drawFuncs->R_ProcessEntData( allocate );\n}\n\nstatic void GAME_EXPORT R_Flush( unsigned int flags )\n{\n\t// stub\n}\n\n/*\n=============\nR_SetSkyCloudsTextures\n\nQuake sky cloud texture was processed by the engine,\nremember them for easier access during rendering\n==============\n*/\nstatic void GAME_EXPORT R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture )\n{\n\ttr.solidskyTexture = solidskyTexture;\n\ttr.alphaskyTexture = alphaskyTexture;\n}\n\n/*\n===============\nR_SetupSky\n===============\n*/\nstatic void GAME_EXPORT R_SetupSky( int *skyboxTextures )\n{\n\tint i;\n\n\tR_UnloadSkybox();\n\n\tif( !skyboxTextures )\n\t\treturn;\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t\ttr.skyboxTextures[i] = skyboxTextures[i];\n}\n\nstatic qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y )\n{\n\tqboolean ret = true;\n\tif( rotate > 0 )\n\t{\n\t\tgEngfuncs.Con_Printf(\"rotation transform not supported\\n\");\n\t\tret = false;\n\t}\n\n\tif( offset_x || offset_y )\n\t{\n\t\tgEngfuncs.Con_Printf(\"offset transform not supported\\n\");\n\t\tret = false;\n\t}\n\n\tif( scale_x != 1.0f || scale_y != 1.0f )\n\t{\n\t\tgEngfuncs.Con_Printf(\"scale transform not supported\\n\");\n\t\tret = false;\n\t}\n\n\treturn ret;\n}\n\nstatic void GAME_EXPORT VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )\n{\n\tpglTexSubImage2D( GL_TEXTURE_2D, 0, drawX, drawY, blockWidth, blockHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba );\n}\n\nstatic void GAME_EXPORT VGUI_SetupDrawing( qboolean rect )\n{\n\tpglEnable( GL_BLEND );\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\n\tif( rect )\n\t{\n\t\tpglDisable( GL_ALPHA_TEST );\n\t}\n\telse\n\t{\n\t\tpglEnable( GL_ALPHA_TEST );\n\t\tpglAlphaFunc( GL_GREATER, 0.0f );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t}\n}\n\nstatic void GAME_EXPORT R_OverrideTextureSourceSize( unsigned int texnum, uint srcWidth, uint srcHeight )\n{\n\tgl_texture_t *tx = R_GetTexture( texnum );\n\n\ttx->srcWidth = srcWidth;\n\ttx->srcHeight = srcHeight;\n}\n\nstatic void* GAME_EXPORT R_GetProcAddress( const char *name )\n{\n#if XASH_GL4ES\n\treturn gl4es_GetProcAddress( name );\n#else // TODO: other wrappers\n\treturn gEngfuncs.GL_GetProcAddress( name );\n#endif\n}\n\nstatic const char *R_GetConfigName( void )\n{\n\treturn \"opengl\";\n}\n\nstatic const ref_interface_t gReffuncs =\n{\n\tR_Init,\n\tR_Shutdown,\n\tR_GetConfigName,\n\tR_SetDisplayTransform,\n\n\tGL_SetupAttributes,\n\tGL_InitExtensions,\n\tGL_ClearExtensions,\n\n\tR_GammaChanged,\n\tR_BeginFrame,\n\tR_RenderScene,\n\tR_EndFrame,\n\tR_PushScene,\n\tR_PopScene,\n\tGL_BackendStartFrame,\n\tGL_BackendEndFrame,\n\n\tR_ClearScreen,\n\tR_AllowFog,\n\tGL_SetRenderMode,\n\n\tR_AddEntity,\n\tCL_AddCustomBeam,\n\tR_ProcessEntData,\n\tR_Flush,\n\n\tR_ShowTextures,\n\n\tR_GetTextureOriginalBuffer,\n\tGL_LoadTextureFromBuffer,\n\tGL_ProcessTexture,\n\tR_SetupSky,\n\n\tR_Set2DMode,\n\tR_DrawStretchRaw,\n\tR_DrawStretchPic,\n\tCL_FillRGBA,\n\tR_WorldToScreen,\n\n\tVID_ScreenShot,\n\tVID_CubemapShot,\n\n\tR_LightPoint,\n\n\tR_DecalShoot,\n\tR_DecalRemoveAll,\n\tR_CreateDecalList,\n\tR_ClearAllDecals,\n\n\tR_StudioEstimateFrame,\n\tR_StudioLerpMovement,\n\tCL_InitStudioAPI,\n\n\tR_SetSkyCloudsTextures,\n\tGL_SubdivideSurface,\n\tCL_RunLightStyles,\n\n\tR_GetSpriteParms,\n\tR_GetSpriteTexture,\n\n\tMod_ProcessRenderData,\n\tMod_StudioLoadTextures,\n\n\tCL_DrawParticles,\n\tCL_DrawTracers,\n\tCL_DrawBeams,\n\tR_BeamCull,\n\n\tGL_RefGetParm,\n\tR_GetDetailScaleForTexture,\n\tR_GetExtraParmsForTexture,\n\tR_GetFrameTime,\n\n\tR_SetCurrentEntity,\n\tR_SetCurrentModel,\n\n\tGL_FindTexture,\n\tGL_TextureName,\n\tGL_TextureData,\n\tGL_LoadTexture,\n\tGL_CreateTexture,\n\tGL_LoadTextureArray,\n\tGL_CreateTextureArray,\n\tGL_FreeTexture,\n\tR_OverrideTextureSourceSize,\n\n\tDrawSingleDecal,\n\tR_DecalSetupVerts,\n\tR_EntityRemoveDecals,\n\n\tR_UploadStretchRaw,\n\n\tGL_Bind,\n\tGL_SelectTexture,\n\tGL_LoadTexMatrixExt,\n\tGL_LoadIdentityTexMatrix,\n\tGL_CleanUpTextureUnits,\n\tGL_TexGen,\n\tGL_TextureTarget,\n\tGL_SetTexCoordArrayMode,\n\tGL_UpdateTexSize,\n\tNULL,\n\tNULL,\n\n\tCL_DrawParticlesExternal,\n\tR_LightVec,\n\tR_StudioGetTexture,\n\n\tR_RenderFrame,\n\tMod_SetOrthoBounds,\n\tR_SpeedsMessage,\n\tMod_GetCurrentVis,\n\tR_NewMap,\n\tR_ClearScene,\n\tR_GetProcAddress,\n\n\tTriRenderMode,\n\tTriBegin,\n\tTriEnd,\n\t_TriColor4f,\n\t_TriColor4ub,\n\tTriTexCoord2f,\n\tTriVertex3fv,\n\tTriVertex3f,\n\tTriFog,\n\tR_ScreenToWorld,\n\tTriGetMatrix,\n\tTriFogParams,\n\tTriCullFace,\n\n\tVGUI_SetupDrawing,\n\tVGUI_UploadTextureBlock,\n};\n\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals );\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals )\n{\n\tif( version != REF_API_VERSION )\n\t\treturn 0;\n\n\t// fill in our callbacks\n\t*funcs = gReffuncs;\n\tgEngfuncs = *engfuncs;\n\tgpGlobals = globals;\n\n\tgp_cl = (ref_client_t *)ENGINE_GET_PARM( PARM_GET_CLIENT_PTR );\n\tgp_host = (ref_host_t *)ENGINE_GET_PARM( PARM_GET_HOST_PTR );\n\n\treturn REF_API_VERSION;\n}\n"
  },
  {
    "path": "ref/gl/gl_cull.c",
    "content": "/*\ngl_cull.c - render culling routines\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"entity_types.h\"\n\n/*\n=============================================================\n\nFRUSTUM AND PVS CULLING\n\n=============================================================\n*/\n/*\n=================\nR_CullBox\n\nReturns true if the box is completely outside the frustum\n=================\n*/\nqboolean R_CullBox( const vec3_t mins, const vec3_t maxs )\n{\n\treturn GL_FrustumCullBox( &RI.frustum, mins, maxs, 0 );\n}\n\n/*\n=============\nR_CullModel\n=============\n*/\nint R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax )\n{\n\tif( e == tr.viewent )\n\t{\n\t\tif( ENGINE_GET_PARM( PARM_DEV_OVERVIEW ))\n\t\t\treturn 1;\n\n\t\tif( RP_NORMALPASS() && !ENGINE_GET_PARM( PARM_THIRDPERSON ) && CL_IsViewEntityLocalPlayer())\n\t\t\treturn 0;\n\n\t\treturn 1;\n\t}\n\n\tif( R_CullBox( absmin, absmax ))\n\t\treturn 1;\n\n\treturn 0;\n}\n\n/*\n=================\nR_CullSurface\n\ncull invisible surfaces\n=================\n*/\nint R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags )\n{\n\tcl_entity_t\t*e = RI.currententity;\n\n\tif( !surf || !surf->texinfo || !surf->texinfo->texture )\n\t\treturn CULL_OTHER;\n\n\tif( r_nocull.value )\n\t\treturn CULL_VISIBLE;\n\n\t// world surfaces can be culled by vis frame too\n\tif( RI.currententity == CL_GetEntityByIndex( 0 ) && surf->visframe != tr.framecount )\n\t\treturn CULL_VISFRAME;\n\n\t// only static ents can be culled by frustum\n\tif( !R_StaticEntity( e )) frustum = NULL;\n\n\tif( !VectorIsNull( surf->plane->normal ))\n\t{\n\t\tfloat\tdist;\n\n\t\t// can use normal.z for world (optimisation)\n\t\tif( RI.drawOrtho )\n\t\t{\n\t\t\tvec3_t\torthonormal;\n\n\t\t\tif( e == CL_GetEntityByIndex( 0 )) orthonormal[2] = surf->plane->normal[2];\n\t\t\telse Matrix4x4_VectorRotate( RI.objectMatrix, surf->plane->normal, orthonormal );\n\t\t\tdist = orthonormal[2];\n\t\t}\n\t\telse dist = PlaneDiff( tr.modelorg, surf->plane );\n\n\t\tif( glState.faceCull == GL_FRONT )\n\t\t{\n\t\t\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\t\t{\n\t\t\t\tif( dist >= -BACKFACE_EPSILON )\n\t\t\t\t\treturn CULL_BACKSIDE; // wrong side\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( dist <= BACKFACE_EPSILON )\n\t\t\t\t\treturn CULL_BACKSIDE; // wrong side\n\t\t\t}\n\t\t}\n\t\telse if( glState.faceCull == GL_BACK )\n\t\t{\n\t\t\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\t\t{\n\t\t\t\tif( dist <= BACKFACE_EPSILON )\n\t\t\t\t\treturn CULL_BACKSIDE; // wrong side\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( dist >= -BACKFACE_EPSILON )\n\t\t\t\t\treturn CULL_BACKSIDE; // wrong side\n\t\t\t}\n\t\t}\n\t}\n\n\tif( frustum && GL_FrustumCullBox( frustum, surf->info->mins, surf->info->maxs, clipflags ))\n\t\treturn CULL_FRUSTUM;\n\n\treturn CULL_VISIBLE;\n}\n"
  },
  {
    "path": "ref/gl/gl_decals.c",
    "content": "/*\ngl_decals.c - decal paste and rendering\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n\n#define DECAL_OVERLAP_DISTANCE\t2\n#define DECAL_DISTANCE\t\t4\t// too big values produce more clipped polygons\n#define MAX_DECALCLIPVERT\t\t32\t// produced vertexes of fragmented decal\n#define DECAL_CACHEENTRY\t\t256\t// MUST BE POWER OF 2 or code below needs to change!\n#define DECAL_TRANSPARENT_THRESHOLD\t230\t// transparent decals draw with GL_MODULATE\n\n// empirically determined constants for minimizing overalpping decals\n#define MAX_OVERLAP_DECALS\t\t6\n#define DECAL_OVERLAP_DIST\t\t8\n#define MIN_DECAL_SCALE\t\t0.01f\n#define MAX_DECAL_SCALE\t\t16.0f\n\n// clip edges\n#define LEFT_EDGE\t\t\t0\n#define RIGHT_EDGE\t\t\t1\n#define TOP_EDGE\t\t\t2\n#define BOTTOM_EDGE\t\t\t3\n\n// This structure contains the information used to create new decals\ntypedef struct\n{\n\tvec3_t\t\tm_Position;\t// world coordinates of the decal center\n\tmodel_t\t\t*m_pModel;\t// the model the decal is going to be applied in\n\tint\t\tm_iTexture;\t// The decal material\n\tint\t\tm_Size;\t\t// Size of the decal (in world coords)\n\tint\t\tm_Flags;\n\tint\t\tm_Entity;\t\t// Entity the decal is applied to.\n\tfloat\t\tm_scale;\n\tint\t\tm_decalWidth;\n\tint\t\tm_decalHeight;\n\tvec3_t\t\tm_Basis[3];\n} decalinfo_t;\n\nstatic float\tg_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE];\nstatic float\tg_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE];\n\ndecal_t\tgDecalPool[MAX_RENDER_DECALS];\nstatic int\tgDecalCount;\n\nvoid R_ClearDecals( void )\n{\n\tmemset( gDecalPool, 0, sizeof( gDecalPool ));\n\tgDecalCount = 0;\n}\n\n// unlink pdecal from any surface it's attached to\nstatic void R_DecalUnlink( decal_t *pdecal )\n{\n\tdecal_t\t*tmp;\n\n\tif( pdecal->psurface )\n\t{\n\t\tif( pdecal->psurface->pdecals == pdecal )\n\t\t{\n\t\t\tpdecal->psurface->pdecals = pdecal->pnext;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttmp = pdecal->psurface->pdecals;\n\t\t\tif( !tmp ) gEngfuncs.Host_Error( \"%s: bad decal list\\n\", __func__ );\n\n\t\t\twhile( tmp->pnext )\n\t\t\t{\n\t\t\t\tif( tmp->pnext == pdecal )\n\t\t\t\t{\n\t\t\t\t\ttmp->pnext = pdecal->pnext;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttmp = tmp->pnext;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( pdecal->polys )\n\t\tMem_Free( pdecal->polys );\n\n\tpdecal->psurface = NULL;\n\tpdecal->polys = NULL;\n}\n\n// Just reuse next decal in list\n// A decal that spans multiple surfaces will use multiple decal_t pool entries,\n// as each surface needs it's own.\nstatic decal_t *R_DecalAlloc( decal_t *pdecal )\n{\n\tint\tlimit = MAX_RENDER_DECALS;\n\n\tif( r_decals->value < limit )\n\t\tlimit = r_decals->value;\n\n\tif( !limit ) return NULL;\n\n\tif( !pdecal )\n\t{\n\t\tint\tcount = 0;\n\n\t\t// check for the odd possiblity of infinte loop\n\t\tdo\n\t\t{\n\t\t\tif( gDecalCount >= limit )\n\t\t\t\tgDecalCount = 0;\n\n\t\t\tpdecal = &gDecalPool[gDecalCount]; // reuse next decal\n\t\t\tgDecalCount++;\n\t\t\tcount++;\n\t\t} while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit );\n\t}\n\n\t// if decal is already linked to a surface, unlink it.\n\tR_DecalUnlink( pdecal );\n\n\treturn pdecal;\n}\n\n//-----------------------------------------------------------------------------\n// find decal image and grab size from it\n//-----------------------------------------------------------------------------\nstatic void R_GetDecalDimensions( int texture, int *width, int *height )\n{\n\tif( width ) *width = 1;\t// to avoid divide by zero\n\tif( height ) *height = 1;\n\n\tR_GetTextureParms( width, height, texture );\n}\n\n//-----------------------------------------------------------------------------\n// compute the decal basis based on surface normal\n//-----------------------------------------------------------------------------\nstatic void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] )\n{\n\tvec3_t\tsurfaceNormal;\n\n\t// setup normal\n\tif( surf->flags & SURF_PLANEBACK )\n\t\tVectorNegate( surf->plane->normal, surfaceNormal );\n\telse VectorCopy( surf->plane->normal, surfaceNormal );\n\n\tVectorNormalize2( surfaceNormal, textureSpaceBasis[2] );\n#if 0\n\tif( FBitSet( flags, FDECAL_CUSTOM ))\n\t{\n\t\tvec3_t\tpSAxis = { 1, 0, 0 };\n\n\t\t// T = S cross N\n\t\tCrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] );\n\n\t\t// Name sure they aren't parallel or antiparallel\n\t\t// In that case, fall back to the normal algorithm.\n\t\tif( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 )\n\t\t{\n\t\t\t// S = N cross T\n\t\t\tCrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] );\n\n\t\t\tVectorNormalizeFast( textureSpaceBasis[0] );\n\t\t\tVectorNormalizeFast( textureSpaceBasis[1] );\n\t\t\treturn;\n\t\t}\n\t\t// Fall through to the standard algorithm for parallel or antiparallel\n\t}\n#endif\n\tVectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] );\n\tVectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] );\n}\n\nstatic void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tint\twidth, height;\n\n\t// Compute the non-scaled decal basis\n\tR_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis );\n\tR_GetDecalDimensions( texture, &width, &height );\n\n\t// world width of decal = ptexture->width / pDecal->scale\n\t// world height of decal = ptexture->height / pDecal->scale\n\t// scale is inverse, scales world space to decal u/v space [0,1]\n\t// OPTIMIZE: Get rid of these divides\n\tdecalWorldScale[0] = (float)pDecal->scale / width;\n\tdecalWorldScale[1] = (float)pDecal->scale / height;\n\n\tVectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] );\n\tVectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] );\n}\n\n// Build the initial list of vertices from the surface verts into the global array, 'verts'.\nstatic void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf,\tvec3_t textureSpaceBasis[3], float *verts )\n{\n\tfloat\t*v;\n\tint\ti;\n\n\tfor( i = 0, v = surf->polys->verts[0]; i < surf->polys->numverts; i++, v += VERTEXSIZE, verts += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, verts ); // copy model space coordinates\n\t\tverts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f;\n\t\tverts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f;\n\t\tverts[5] = verts[6] = 0.0f;\n\t}\n}\n\n// Figure out where the decal maps onto the surface.\nstatic void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// Generate texture coordinates for each vertex in decal s,t space\n\t// probably should pre-generate this, store it and use it for decal-decal collisions\n\t// as in R_DecalsIntersect()\n\tpDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] );\n\tpDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] );\n}\n\n// Quick and dirty sutherland Hodgman clipper\n// Clip polygon to decal in texture space\n// JAY: This code is lame, change it later.  It does way too much work per frame\n// It can be made to recursively call the clipping code and only copy the vertex list once\nstatic int R_ClipInside( float *vert, int edge )\n{\n\tswitch( edge )\n\t{\n\tcase LEFT_EDGE:\n\t\tif( vert[3] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase RIGHT_EDGE:\n\t\tif( vert[3] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase TOP_EDGE:\n\t\tif( vert[4] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase BOTTOM_EDGE:\n\t\tif( vert[4] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\t}\n\treturn 0;\n}\n\nstatic void R_ClipIntersect( float *one, float *two, float *out, int edge )\n{\n\tfloat\tt;\n\n\t// t is the parameter of the line between one and two clipped to the edge\n\t// or the fraction of the clipped point between one & two\n\t// vert[0], vert[1], vert[2] is X, Y, Z\n\t// vert[3] is u\n\t// vert[4] is v\n\t// vert[5] is lightmap u\n\t// vert[6] is lightmap v\n\n\tif( edge < TOP_EDGE )\n\t{\n\t\tif( edge == LEFT_EDGE )\n\t\t{\n\t\t\t// left\n\t\t\tt = ((one[3] - 0.0f) / (one[3] - two[3]));\n\t\t\tout[3] = out[5] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// right\n\t\t\tt = ((one[3] - 1.0f) / (one[3] - two[3]));\n\t\t\tout[3] = out[5] = 1.0f;\n\t\t}\n\n\t\tout[4] = one[4] + (two[4] - one[4]) * t;\n\t\tout[6] = one[6] + (two[6] - one[6]) * t;\n\t}\n\telse\n\t{\n\t\tif( edge == TOP_EDGE )\n\t\t{\n\t\t\t// top\n\t\t\tt = ((one[4] - 0.0f)  / (one[4] - two[4]));\n\t\t\tout[4] = out[6] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// bottom\n\t\t\tt = ((one[4] - 1.0f) / (one[4] - two[4]));\n\t\t\tout[4] = out[6] = 1.0f;\n\t\t}\n\n\t\tout[3] = one[3] + (two[3] - one[3]) * t;\n\t\tout[5] = one[5] + (two[4] - one[5]) * t;\n\t}\n\n\tVectorLerp( one, t, two, out );\n}\n\nstatic int SHClip( float *vert, int vertCount, float *out, int edge )\n{\n\tint\tj, outCount;\n\tfloat\t*s, *p;\n\n\toutCount = 0;\n\n\ts = &vert[(vertCount - 1) * VERTEXSIZE];\n\n\tfor( j = 0; j < vertCount; j++ )\n\t{\n\t\tp = &vert[j * VERTEXSIZE];\n\n\t\tif( R_ClipInside( p, edge ))\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\t// Add a vertex and advance out to next vertex\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tR_ClipIntersect( s, p, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\tR_ClipIntersect( p, s, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\n\t\ts = p;\n\t}\n\n\treturn outCount;\n}\n\nstatic float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount )\n{\n\tfloat\t*pOutVerts = g_DecalClipVerts[0];\n\tint\toutCount;\n\n\t// clip the polygon to the decal texture space\n\toutCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE );\n\n\tif( pVertCount )\n\t\t*pVertCount = outCount;\n\n\treturn pOutVerts;\n}\n\n//-----------------------------------------------------------------------------\n// Generate clipped vertex list for decal pdecal projected onto polygon psurf\n//-----------------------------------------------------------------------------\nstatic float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount )\n{\n\tfloat\tdecalWorldScale[2];\n\tvec3_t\ttextureSpaceBasis[3];\n\n\t// figure out where the decal maps onto the surface.\n\tR_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// build the initial list of vertices from the surface verts.\n\tR_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] );\n\n\treturn R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount );\n}\n\n// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf\nstatic void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount )\n{\n\tfloat\t\tsample_size;\n\tint\t\tj;\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\n\tfor( j = 0; j < vertCount; j++, v += VERTEXSIZE )\n\t{\n\t\t// lightmap texture coordinates\n\t\tR_LightmapCoord( v, surf, sample_size, &v[5] );\n\t}\n}\n\n// Check for intersecting decals on this surface\nstatic decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount )\n{\n\tint\t\ttexture;\n\tdecal_t\t\t*plast, *pDecal;\n\tvec3_t\t\tdecalExtents[2];\n\tfloat\t\tlastArea = 2;\n\tint\t\tmapSize[2];\n\n\tplast = NULL;\n\t*pcount = 0;\n\n\t// (Same as R_SetupDecalClip).\n\ttexture = decalinfo->m_iTexture;\n\n\t// precalculate the extents of decalinfo's decal in world space.\n\tR_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] );\n\tVectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] );\n\tVectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] );\n\n\tpDecal = surf->pdecals;\n\n\twhile( pDecal )\n\t{\n\t\ttexture = pDecal->texture;\n\n\t\t// Don't steal bigger decals and replace them with smaller decals\n\t\t// Don't steal permanent decals\n\t\tif( !FBitSet( pDecal->flags, FDECAL_PERMANENT ))\n\t\t{\n\t\t\tvec3_t\ttestBasis[3];\n\t\t\tvec3_t\ttestPosition[2];\n\t\t\tfloat\ttestWorldScale[2];\n\t\t\tvec2_t\tvDecalMin, vDecalMax;\n\t\t\tvec2_t\tvUnionMin, vUnionMax;\n\n\t\t\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale );\n\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\t// Here, we project the min and max extents of the decal that got passed in into\n\t\t\t// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were\n\t\t\t// clipping a triangle into pDecal's clip space.\n\t\t\tVector2Set( vDecalMin,\n\t\t\t\tDotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\tDotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\tVector2Set( vDecalMax,\n\t\t\t\tDotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\tDotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\t// Now figure out the part of the projection that intersects pDecal's\n\t\t\t// clip box [0,0,1,1].\n\t\t\tVector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 ));\n\t\t\tVector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 ));\n\n\t\t\tif( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 )\n\t\t\t{\n\t\t\t\t// Figure out how much of this intersects the (0,0) - (1,1) bbox.\n\t\t\t\tfloat\tflArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]);\n\n\t\t\t\tif( flArea > 0.6f )\n\t\t\t\t{\n\t\t\t\t\t*pcount += 1;\n\n\t\t\t\t\tif( !plast || flArea <= lastArea )\n\t\t\t\t\t{\n\t\t\t\t\t\tplast = pDecal;\n\t\t\t\t\t\tlastArea =  flArea;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpDecal = pDecal->pnext;\n\t}\n\treturn plast;\n}\n\n/*\n====================\nR_DecalCreatePoly\n\ncreates mesh for decal on first rendering\n====================\n*/\nstatic glpoly2_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf )\n{\n\tint\t\tlnumverts;\n\tglpoly2_t\t*poly;\n\tfloat\t\t*v;\n\tint\t\ti;\n\n\tif( pdecal->polys )\t// already created?\n\t\treturn pdecal->polys;\n\n\tv = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts );\n\tif( !lnumverts ) return NULL;\t// probably this never happens\n\n\t// allocate glpoly\n\t// REFTODO: com_studiocache pool!\n\tpoly = Mem_Calloc( r_temppool, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = pdecal->polys;\n\tpoly->flags = surf->flags;\n\tpdecal->polys = poly;\n\tpoly->numverts = lnumverts;\n\n\tfor( i = 0; i < lnumverts; i++, v += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, poly->verts[i] );\n\t\tpoly->verts[i][3] = v[3];\n\t\tpoly->verts[i][4] = v[4];\n\t\tpoly->verts[i][5] = v[5];\n\t\tpoly->verts[i][6] = v[6];\n\t}\n\n\treturn poly;\n}\n\n// Add the decal to the surface's list of decals.\nstatic void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo )\n{\n\tdecal_t\t*pold;\n\n\tpdecal->pnext = NULL;\n\tpold = surf->pdecals;\n\n\tif( pold )\n\t{\n\t\twhile( pold->pnext )\n\t\t\tpold = pold->pnext;\n\t\tpold->pnext = pdecal;\n\t}\n\telse\n\t{\n\t\tsurf->pdecals = pdecal;\n\t}\n\n\t// tag surface\n\tpdecal->psurface = surf;\n\n\t// at this point decal are linked with surface\n\t// and will be culled, drawing and sorting\n\t// together with surface\n\n\t// alloc clipped poly for decal\n\tR_DecalCreatePoly( decalinfo, pdecal, surf );\n\tR_AddDecalVBO( pdecal, surf );\n}\n\nstatic void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y )\n{\n\tdecal_t\t*pdecal, *pold;\n\tint\tcount, vertCount;\n\n\tif( !surf ) return;\t// ???\n\n\tpold = R_DecalIntersect( decalinfo, surf, &count );\n\tif( count < MAX_OVERLAP_DECALS ) pold = NULL;\n\n\tpdecal = R_DecalAlloc( pold );\n\tif( !pdecal ) return; // r_decals == 0 ???\n\n\tpdecal->flags = decalinfo->m_Flags;\n\n\tVectorCopy( decalinfo->m_Position, pdecal->position );\n\n\tpdecal->dx = x;\n\tpdecal->dy = y;\n\n\t// set scaling\n\tpdecal->scale = decalinfo->m_scale;\n\tpdecal->entityIndex = decalinfo->m_Entity;\n\tpdecal->texture = decalinfo->m_iTexture;\n\n\t// check to see if the decal actually intersects the surface\n\t// if not, then remove the decal\n\tR_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount );\n\n\tif( !vertCount )\n\t{\n\t\tR_DecalUnlink( pdecal );\n\t\treturn;\n\t}\n\n\t// add to the surface's list\n\tR_AddDecalToSurface( pdecal, surf, decalinfo );\n}\n\nstatic void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )\n{\n\t// get the texture associated with this surface\n\tmtexinfo_t\t*tex = surf->texinfo;\n\tdecal_t\t\t*decal = surf->pdecals;\n\tvec4_t\t\ttextureU, textureV;\n\tfloat\t\ts, t, w, h;\n\tconnstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE );\n\n\t// we in restore mode\n\tif( state == ca_connected || state == ca_validate )\n\t{\n\t\t// NOTE: we may have the decal on this surface that come from another level.\n\t\t// check duplicate with same position and texture\n\t\twhile( decal != NULL )\n\t\t{\n\t\t\tif( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture )\n\t\t\t\treturn; // decal already exists, don't place it again\n\t\t\tdecal = decal->pnext;\n\t\t}\n\t}\n\n\tVector4Copy( tex->vecs[0], textureU );\n\tVector4Copy( tex->vecs[1], textureV );\n\n\t// project decal center into the texture space of the surface\n\ts = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0];\n\tt = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1];\n\n\t// Determine the decal basis (measured in world space)\n\t// Note that the decal basis vectors 0 and 1 will always lie in the same\n\t// plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits.\n\tR_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis );\n\n\t// Compute an effective width and height (axis aligned) in the parent texture space\n\t// How does this work? decalBasis[0] represents the u-direction (width)\n\t// of the decal measured in world space, decalBasis[1] represents the\n\t// v-direction (height) measured in world space.\n\t// textureVecsTexelsPerWorldUnits[0] represents the u direction of\n\t// the surface's texture space measured in world space (with the appropriate\n\t// scale factor folded in), and textureVecsTexelsPerWorldUnits[1]\n\t// represents the texture space v direction. We want to find the dimensions (w,h)\n\t// of a square measured in texture space, axis aligned to that coordinate system.\n\t// All we need to do is to find the components of the decal edge vectors\n\t// (decalWidth * decalBasis[0], decalHeight * decalBasis[1])\n\t// in texture coordinates:\n\n\tw = fabs( decalinfo->m_decalWidth  * DotProduct( textureU, decalinfo->m_Basis[0] )) +\n\t    fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] ));\n\n\th = fabs( decalinfo->m_decalWidth  * DotProduct( textureV, decalinfo->m_Basis[0] )) +\n\t    fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] ));\n\n\t// move s,t to upper left corner\n\ts -= ( w * 0.5f );\n\tt -= ( h * 0.5f );\n\n\t// Is this rect within the surface? -- tex width & height are unsigned\n\tif( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h))\n\t{\n\t\treturn; // nope\n\t}\n\n\t// stamp it\n\tR_DecalCreate( decalinfo, surf, s, t );\n}\n\n//-----------------------------------------------------------------------------\n// iterate over all surfaces on a node, looking for surfaces to decal\n//-----------------------------------------------------------------------------\nstatic void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\t// iterate over all surfaces in the node\n\tmsurface_t\t*surf;\n\tint\t\ti;\n\tint firstsurface, numsurfaces;\n\n\tfirstsurface = node_firstsurface( node, model );\n\tnumsurfaces  = node_numsurfaces( node, model );\n\n\tsurf = model->surfaces + firstsurface;\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\t// never apply decals on the water or sky surfaces\n\t\tif( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))\n\t\t\tcontinue;\n\n\t\tif( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled )\n\t\t\tcontinue;\n\n\t\tR_DecalSurface( surf, decalinfo );\n\t}\n}\n\n//-----------------------------------------------------------------------------\n// Recursive routine to find surface to apply a decal to.  World coordinates of\n// the decal are passed in r_recalpos like the rest of the engine.  This should\n// be called through R_DecalShoot()\n//-----------------------------------------------------------------------------\nstatic void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\tmplane_t\t*splitplane;\n\tfloat\tdist;\n\tmnode_t *children[2];\n\n\tAssert( node != NULL );\n\n\tif( node->contents < 0 )\n\t{\n\t\t// hit a leaf\n\t\treturn;\n\t}\n\n\tsplitplane = node->plane;\n\tdist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;\n\tnode_children( children, node, model );\n\n\t// This is arbitrarily set to 10 right now. In an ideal world we'd have the\n\t// exact surface but we don't so, this tells me which planes are \"sort of\n\t// close\" to the gunshot -- the gunshot is actually 4 units in front of the\n\t// wall (see dlls\\weapons.cpp). We also need to check to see if the decal\n\t// actually intersects the texture space of the surface, as this method tags\n\t// parallel surfaces in the same node always.\n\t// JAY: This still tags faces that aren't correct at edges because we don't\n\t// have a surface normal\n\tif( dist > decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t}\n\telse if( dist < -decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n\telse\n\t{\n\t\tif( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )\n\t\t\tR_DecalNodeSurfaces( model, node, decalinfo );\n\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n}\n\n// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords\nvoid R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale )\n{\n\tdecalinfo_t\tdecalInfo;\n\tcl_entity_t\t*ent = NULL;\n\tmodel_t\t\t*model = NULL;\n\tint\t\twidth, height;\n\thull_t\t\t*hull;\n\n\tif( textureIndex <= 0 || textureIndex >= MAX_TEXTURES )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Decal has invalid texture!\\n\" );\n\t\treturn;\n\t}\n\n\tif( entityIndex > 0 )\n\t{\n\t\tent = CL_GetEntityByIndex( entityIndex );\n\n\t\tif( modelIndex > 0 ) model = CL_ModelHandle( modelIndex );\n\t\telse if( ent != NULL ) model = CL_ModelHandle( ent->curstate.modelindex );\n\t\telse return;\n\t}\n\telse if( modelIndex > 0 )\n\t\tmodel = CL_ModelHandle( modelIndex );\n\telse model = CL_ModelHandle( 1 );\n\n\tif( !model ) return;\n\n\tif( model->type != mod_brush )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Decals must hit mod_brush!\\n\" );\n\t\treturn;\n\t}\n\n\tdecalInfo.m_pModel = model;\n\thull = &model->hulls[0];\t// always use #0 hull\n\n\t// NOTE: all the decals at 'first shoot' placed into local space of parent entity\n\t// and won't transform again on a next restore, levelchange etc\n\tif( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE ))\n\t{\n\t\tvec3_t\tpos_l;\n\n\t\t// transform decal position in local bmodel space\n\t\tif( !VectorIsNull( ent->angles ))\n\t\t{\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tMatrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, pos, pos_l );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( pos, ent->origin, pos_l );\n\t\t}\n\n\t\tVectorCopy( pos_l, decalInfo.m_Position );\n\t\t// decal position moved into local space\n\t\tSetBits( flags, FDECAL_LOCAL_SPACE );\n\t}\n\telse\n\t{\n\t\t// already in local space\n\t\tVectorCopy( pos, decalInfo.m_Position );\n\t}\n\n\t// this decal must use landmark for correct transition\n\t// because their model exist only in world-space\n\tif( !FBitSet( model->flags, MODEL_HAS_ORIGIN ))\n\t\tSetBits( flags, FDECAL_USE_LANDMARK );\n\n\t// more state used by R_DecalNode()\n\tdecalInfo.m_iTexture = textureIndex;\n\tdecalInfo.m_Entity = entityIndex;\n\tdecalInfo.m_Flags = flags;\n\n\tR_GetDecalDimensions( textureIndex, &width, &height );\n\tdecalInfo.m_Size = width >> 1;\n\tif(( height >> 1 ) > decalInfo.m_Size )\n\t\tdecalInfo.m_Size = height >> 1;\n\n\tdecalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE );\n\n\t// compute the decal dimensions in world space\n\tdecalInfo.m_decalWidth = width / decalInfo.m_scale;\n\tdecalInfo.m_decalHeight = height / decalInfo.m_scale;\n\n\tR_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo );\n}\n\n// Build the vertex list for a decal on a surface and clip it to the surface.\n// This is a template so it can work on world surfaces and dynamic displacement\n// triangles the same way.\nfloat *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount )\n{\n\tglpoly2_t\t*p = pDecal->polys;\n\tint\ti, count;\n\tfloat\t*v, *v2;\n\n\tif( p )\n\t{\n\t\tv = g_DecalClipVerts[0];\n\t\tcount = p->numverts;\n\t\tv2 = p->verts[0];\n\n\t\t// if we have mesh so skip clipping and just copy vertexes out (perf)\n\t\tfor( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE )\n\t\t{\n\t\t\tVectorCopy( v2, v );\n\t\t\tv[3] = v2[3];\n\t\t\tv[4] = v2[4];\n\t\t\tv[5] = v2[5];\n\t\t\tv[6] = v2[6];\n\t\t}\n\n\t\t// restore pointer\n\t\tv = g_DecalClipVerts[0];\n\t}\n\telse\n\t{\n\t\tv = R_DecalVertsClip( pDecal, surf, texture, &count );\n\t\tR_DecalVertsLight( v, surf, count );\n\t}\n\n\tif( outCount )\n\t\t*outCount = count;\n\n\treturn v;\n}\n\nvoid DrawSingleDecal( decal_t *pDecal, msurface_t *fa )\n{\n\tfloat\t*v;\n\tint\ti, numVerts;\n\n\tv = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts );\n\tif( !numVerts ) return;\n\n\tGL_Bind( XASH_TEXTURE0, pDecal->texture );\n\n\tpglBegin( GL_POLYGON );\n\n\tfor( i = 0; i < numVerts; i++, v += VERTEXSIZE )\n\t{\n\t\tpglTexCoord2f( v[3], v[4] );\n\t\tpglVertex3fv( v );\n\t}\n\n\tpglEnd();\n}\n\nvoid DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse )\n{\n\tdecal_t\t\t*p;\n\tcl_entity_t\t*e;\n\n\tif( !fa->pdecals ) return;\n\n\te = RI.currententity;\n\tAssert( e != NULL );\n\n\tif( single )\n\t{\n\t\tif( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )\n\t\t{\n\t\t\tpglDepthMask( GL_FALSE );\n\t\t\tpglEnable( GL_BLEND );\n\n\t\t\tif( e->curstate.rendermode == kRenderTransAlpha )\n\t\t\t\tpglDisable( GL_ALPHA_TEST );\n\t\t}\n\n\t\tif( e->curstate.rendermode == kRenderTransColor )\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\n\t\tif( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )\n\t\t\tGL_Cull( GL_NONE );\n\n\t\tif( gl_polyoffset.value )\n\t\t{\n\t\t\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\t\t\tpglPolygonOffset( -1.0f, -gl_polyoffset.value );\n\t\t}\n\t}\n\n\tif( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled )\n\t{\n\t\tmtexinfo_t\t*tex = fa->texinfo;\n\n\t\tfor( p = fa->pdecals; p; p = p->pnext )\n\t\t{\n\t\t\tif( p->texture )\n\t\t\t{\n\t\t\t\tfloat *o, *v;\n\t\t\t\tint i, numVerts;\n\t\t\t\to = R_DecalSetupVerts( p, fa, p->texture, &numVerts );\n\n\t\t\t\tpglEnable( GL_STENCIL_TEST );\n\t\t\t\tpglStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFF );\n\t\t\t\tpglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );\n\n\t\t\t\tpglStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );\n\t\t\t\tpglBegin( GL_POLYGON );\n\n\t\t\t\tfor( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE )\n\t\t\t\t{\n\t\t\t\t\tv[5] = ( DotProduct( v, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width;\n\t\t\t\t\tv[6] = ( DotProduct( v, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height;\n\n\t\t\t\t\tpglTexCoord2f( v[5], v[6] );\n\t\t\t\t\tpglVertex3fv( v );\n\t\t\t\t}\n\n\t\t\t\tpglEnd();\n\t\t\t\tpglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );\n\n\t\t\t\tpglEnable( GL_ALPHA_TEST );\n\t\t\t\tpglBegin( GL_POLYGON );\n\n\t\t\t\tfor( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE )\n\t\t\t\t{\n\t\t\t\t\tpglTexCoord2f( v[5], v[6] );\n\t\t\t\t\tpglVertex3fv( v );\n\t\t\t\t}\n\n\t\t\t\tpglEnd();\n\t\t\t\tpglDisable( GL_ALPHA_TEST );\n\n\t\t\t\tpglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );\n\t\t\t\tpglStencilFunc( GL_EQUAL, 0, 0xFFFFFFFF );\n\t\t\t\tpglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );\n\t\t\t}\n\t\t}\n\t}\n\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\n\tif( reverse && e->curstate.rendermode == kRenderTransTexture )\n\t{\n\t\tdecal_t\t*list[1024];\n\t\tint\ti, count;\n\n\t\tfor( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext )\n\t\t\tif( p->texture ) list[count++] = p;\n\n\t\tfor( i = count - 1; i >= 0; i-- )\n\t\t\tDrawSingleDecal( list[i], fa );\n\t}\n\telse\n\t{\n\t\tfor( p = fa->pdecals; p; p = p->pnext )\n\t\t{\n\t\t\tif( !p->texture ) continue;\n\t\t\tDrawSingleDecal( p, fa );\n\t\t}\n\t}\n\n\tif( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled )\n\t\tpglDisable( GL_STENCIL_TEST );\n\n\tif( single )\n\t{\n\t\tif( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )\n\t\t{\n\t\t\tpglDepthMask( GL_TRUE );\n\t\t\tpglDisable( GL_BLEND );\n\n\t\t\tif( e->curstate.rendermode == kRenderTransAlpha )\n\t\t\t\tpglEnable( GL_ALPHA_TEST );\n\t\t}\n\n\t\tif( gl_polyoffset.value )\n\t\t\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\t\tif( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )\n\t\t\tGL_Cull( GL_FRONT );\n\n\t\tif( e->curstate.rendermode == kRenderTransColor )\n\t\t\tpglDisable( GL_TEXTURE_2D );\n\n\t\t// restore blendfunc here\n\t\tif( e->curstate.rendermode == kRenderTransAdd || e->curstate.rendermode == kRenderGlow )\n\t\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t}\n}\n\nvoid DrawDecalsBatch( void )\n{\n\tcl_entity_t\t*e;\n\tint\t\ti;\n\n\tif( !tr.num_draw_decals )\n\t\treturn;\n\n\te = RI.currententity;\n\tAssert( e != NULL );\n\n\tif( e->curstate.rendermode != kRenderTransTexture )\n\t{\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglDepthMask( GL_FALSE );\n\t}\n\n\tif( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )\n\t\tGL_Cull( GL_NONE );\n\n\tif( gl_polyoffset.value )\n\t{\n\t\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\t\tpglPolygonOffset( -1.0f, -gl_polyoffset.value );\n\t}\n\n\tfor( i = 0; i < tr.num_draw_decals; i++ )\n\t{\n\t\tDrawSurfaceDecals( tr.draw_decals[i], false, false );\n\t}\n\n\tif( e->curstate.rendermode != kRenderTransTexture )\n\t{\n\t\tpglDepthMask( GL_TRUE );\n\t\tpglDisable( GL_BLEND );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t}\n\n\tif( gl_polyoffset.value )\n\t\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\tif( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )\n\t\tGL_Cull( GL_FRONT );\n\n\ttr.num_draw_decals = 0;\n}\n\n/*\n=============================================================\n\n  DECALS SERIALIZATION\n\n=============================================================\n*/\nstatic qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry )\n{\n\tif( !pdecal || !( pdecal->psurface ))\n\t\treturn false;\n\n\tVectorCopy( pdecal->position, entry->position );\n\tentry->entityIndex = pdecal->entityIndex;\n\n\t// Grab surface plane equation\n\tif( pdecal->psurface->flags & SURF_PLANEBACK )\n\t\tVectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\telse VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\n\treturn true;\n}\n\n//-----------------------------------------------------------------------------\n// Purpose:\n// Input  : *pList -\n//\t\t\tcount -\n// Output : static int\n//-----------------------------------------------------------------------------\nstatic int DecalListAdd( decallist_t *pList, int count )\n{\n\tvec3_t\t\ttmp;\n\tdecallist_t\t*pdecal;\n\tint\t\ti;\n\n\tpdecal = pList + count;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tif( !Q_strcmp( pdecal->name, pList[i].name ) &&  pdecal->entityIndex == pList[i].entityIndex )\n\t\t{\n\t\t\tVectorSubtract( pdecal->position, pList[i].position, tmp );\t// Merge\n\n\t\t\tif( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE )\n\t\t\t\treturn count;\n\t\t}\n\t}\n\n\t// this is a new decal\n\treturn count + 1;\n}\n\nstatic int DecalDepthCompare( const void *a, const void *b )\n{\n\tconst decallist_t\t*elem1, *elem2;\n\n\telem1 = (const decallist_t *)a;\n\telem2 = (const decallist_t *)b;\n\n\tif( elem1->depth > elem2->depth )\n\t\treturn 1;\n\tif( elem1->depth < elem2->depth )\n\t\treturn -1;\n\n\treturn 0;\n}\n\n//-----------------------------------------------------------------------------\n// Purpose: Called by CSaveRestore::SaveClientState\n// Input  : *pList -\n// Output : int\n//-----------------------------------------------------------------------------\nint R_CreateDecalList( decallist_t *pList )\n{\n\tint\ttotal = 0;\n\tint\ti, depth;\n\n\tif( WORLDMODEL )\n\t{\n\t\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t\t{\n\t\t\tdecal_t\t*decal = &gDecalPool[i];\n\t\t\tdecal_t\t*pdecals;\n\n\t\t\t// decal is in use and is not a custom decal\n\t\t\tif( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE ))\n\t\t\t\t continue;\n\n\t\t\t// compute depth\n\t\t\tdepth = 0;\n\t\t\tpdecals = decal->psurface->pdecals;\n\n\t\t\twhile( pdecals && pdecals != decal )\n\t\t\t{\n\t\t\t\tdepth++;\n\t\t\t\tpdecals = pdecals->pnext;\n\t\t\t}\n\n\t\t\tpList[total].depth = depth;\n\t\t\tpList[total].flags = decal->flags;\n\t\t\tpList[total].scale = decal->scale;\n\n\t\t\tR_DecalUnProject( decal, &pList[total] );\n\t\t\tCOM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name ));\n\n\t\t\t// check to see if the decal should be added\n\t\t\ttotal = DecalListAdd( pList, total );\n\t\t}\n\n\t\tif( gEngfuncs.drawFuncs->R_CreateStudioDecalList )\n\t\t{\n\t\t\ttotal += gEngfuncs.drawFuncs->R_CreateStudioDecalList( pList, total );\n\t\t}\n\t}\n\n\t// sort the decals lowest depth first, so they can be re-applied in order\n\tqsort( pList, total, sizeof( decallist_t ), DecalDepthCompare );\n\n\treturn total;\n}\n\n/*\n===============\nR_DecalRemoveAll\n\nremove all decals with specified texture\n===============\n*/\nvoid R_DecalRemoveAll( int textureIndex )\n{\n\tdecal_t\t*pdecal;\n\tint\ti;\n\n\tif( textureIndex < 0 || textureIndex >= MAX_TEXTURES )\n\t\treturn; // out of bounds\n\n\tfor( i = 0; i < gDecalCount; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\n\t\t// don't remove permanent decals\n\t\tif( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT ))\n\t\t\tcontinue;\n\n\t\tif( !textureIndex || ( pdecal->texture == textureIndex ))\n\t\t\tR_DecalUnlink( pdecal );\n\t}\n}\n\n/*\n===============\nR_EntityRemoveDecals\n\nremove all decals from specified entity\n===============\n*/\nvoid R_EntityRemoveDecals( model_t *mod )\n{\n\tmsurface_t\t*psurf;\n\tdecal_t\t\t*p;\n\tint\t\ti;\n\n\tif( !mod || mod->type != mod_brush )\n\t\treturn;\n\n\tpsurf = &mod->surfaces[mod->firstmodelsurface];\n\tfor( i = 0; i < mod->nummodelsurfaces; i++, psurf++ )\n\t{\n\t\tfor( p = psurf->pdecals; p; p = p->pnext )\n\t\t\tR_DecalUnlink( p );\n\t}\n}\n\n/*\n===============\nR_ClearAllDecals\n\nremove all decals from anything\nused for full decals restart\n===============\n*/\nvoid R_ClearAllDecals( void )\n{\n\tdecal_t\t*pdecal;\n\tint\ti;\n\n\t// because gDecalCount may be zeroed after recach the decal limit\n\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\t\tR_DecalUnlink( pdecal );\n\t}\n\n\tif( gEngfuncs.drawFuncs->R_ClearStudioDecals )\n\t{\n\t\tgEngfuncs.drawFuncs->R_ClearStudioDecals();\n\t}\n}\n"
  },
  {
    "path": "ref/gl/gl_draw.c",
    "content": "/*\ngl_draw.c - orthogonal drawing stuff\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n\n/*\n=============\nR_GetImageParms\n=============\n*/\nvoid R_GetTextureParms( int *w, int *h, int texnum )\n{\n\tgl_texture_t\t*glt;\n\n\tglt = R_GetTexture( texnum );\n\tif( w ) *w = glt->srcWidth;\n\tif( h ) *h = glt->srcHeight;\n}\n\n/*\n=============\nR_GetSpriteParms\n\nsame as GetImageParms but used\nfor sprite models\n=============\n*/\nvoid R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )\n{\n\tmspriteframe_t\t*pFrame;\n\n\tif( !pSprite || pSprite->type != mod_sprite ) return; // bad model ?\n\tpFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );\n\n\tif( frameWidth ) *frameWidth = pFrame->width;\n\tif( frameHeight ) *frameHeight = pFrame->height;\n\tif( numFrames ) *numFrames = pSprite->numframes;\n}\n\nint R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )\n{\n\tif( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )\n\t\treturn 0;\n\n\treturn R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;\n}\n\n/*\n=============\nR_DrawStretchPic\n=============\n*/\nvoid R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum )\n{\n\tGL_Bind( XASH_TEXTURE0, texnum );\n\n\tpglBegin( GL_QUADS );\n\t\tpglTexCoord2f( s1, t1 );\n\t\tpglVertex2f( x, y );\n\n\t\tpglTexCoord2f( s2, t1 );\n\t\tpglVertex2f( x + w, y );\n\n\t\tpglTexCoord2f( s2, t2 );\n\t\tpglVertex2f( x + w, y + h );\n\n\t\tpglTexCoord2f( s1, t2 );\n\t\tpglVertex2f( x, y + h );\n\tpglEnd();\n}\n\n/*\n=============\nR_DrawStretchRaw\n=============\n*/\nvoid R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty )\n{\n\tbyte\t\t*raw = NULL;\n\tgl_texture_t\t*tex;\n\n\tif( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT ))\n\t{\n\t\tint\twidth = 1, height = 1;\n\n\t\t// check the dimensions\n\t\twidth = NearestPOW( cols, true );\n\t\theight = NearestPOW( rows, false );\n\n\t\tif( cols != width || rows != height )\n\t\t{\n\t\t\traw = GL_ResampleTexture( data, cols, rows, width, height, false );\n\t\t\tcols = width;\n\t\t\trows = height;\n\t\t}\n\t}\n\telse\n\t{\n\t\traw = (byte *)data;\n\t}\n\n\tif( cols > glConfig.max_2d_texture_size )\n\t\tgEngfuncs.Host_Error( \"%s: size %i exceeds hardware limits\\n\", __func__, cols );\n\tif( rows > glConfig.max_2d_texture_size )\n\t\tgEngfuncs.Host_Error( \"%s: size %i exceeds hardware limits\\n\", __func__, rows );\n\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\n\ttex = R_GetTexture( tr.cinTexture );\n\tGL_Bind( XASH_TEXTURE0, tr.cinTexture );\n\n\tif( cols == tex->width && rows == tex->height )\n\t{\n\t\tif( dirty )\n\t\t{\n\t\t\tpglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_BGRA, GL_UNSIGNED_BYTE, raw );\n\t\t}\n\t}\n\telse\n\t{\n\t\ttex->size = cols * rows * 4;\n\t\ttex->width = cols;\n\t\ttex->height = rows;\n\t\tif( dirty )\n\t\t{\n\t\t\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw );\n\t\t}\n\t}\n\n\tpglBegin( GL_QUADS );\n\tpglTexCoord2f( 0, 0 );\n\tpglVertex2f( x, y );\n\tpglTexCoord2f( 1, 0 );\n\tpglVertex2f( x + w, y );\n\tpglTexCoord2f( 1, 1 );\n\tpglVertex2f( x + w, y + h );\n\tpglTexCoord2f( 0, 1 );\n\tpglVertex2f( x, y + h );\n\tpglEnd();\n}\n\n/*\n=============\nR_UploadStretchRaw\n=============\n*/\nvoid R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data )\n{\n\tbyte\t\t*raw = NULL;\n\tgl_texture_t\t*tex;\n\n\tif( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT ))\n\t{\n\t\t// check the dimensions\n\t\twidth = NearestPOW( width, true );\n\t\theight = NearestPOW( height, false );\n\t}\n\telse\n\t{\n\t\twidth = bound( 128, width, glConfig.max_2d_texture_size );\n\t\theight = bound( 128, height, glConfig.max_2d_texture_size );\n\t}\n\n\tif( cols != width || rows != height )\n\t{\n\t\traw = GL_ResampleTexture( data, cols, rows, width, height, false );\n\t\tcols = width;\n\t\trows = height;\n\t}\n\telse\n\t{\n\t\traw = (byte *)data;\n\t}\n\n\tif( cols > glConfig.max_2d_texture_size )\n\t\tgEngfuncs.Host_Error( \"%s: size %i exceeds hardware limits\\n\", __func__, cols );\n\tif( rows > glConfig.max_2d_texture_size )\n\t\tgEngfuncs.Host_Error( \"%s: size %i exceeds hardware limits\\n\", __func__, rows );\n\n\ttex = R_GetTexture( texture );\n\tGL_Bind( GL_KEEP_UNIT, texture );\n\ttex->width = cols;\n\ttex->height = rows;\n\n\tpglTexImage2D( GL_TEXTURE_2D, 0, tex->format, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw );\n\tGL_ApplyTextureParams( tex );\n}\n\n/*\n===============\nR_Set2DMode\n===============\n*/\nvoid R_Set2DMode( qboolean enable )\n{\n\tif( enable )\n\t{\n\t\tif( glState.in2DMode )\n\t\t\treturn;\n\n\t\t// set 2D virtual screen size\n\t\tpglViewport( 0, 0, gpGlobals->width, gpGlobals->height );\n\t\tpglMatrixMode( GL_PROJECTION );\n\t\tpglLoadIdentity();\n\t\tpglOrtho( 0, gpGlobals->width, gpGlobals->height, 0, -99999, 99999 );\n\t\tpglMatrixMode( GL_MODELVIEW );\n\t\tpglLoadIdentity();\n\n\t\tGL_Cull( GL_NONE );\n\n\t\tpglDepthMask( GL_FALSE );\n\t\tpglDisable( GL_DEPTH_TEST );\n\t\tpglEnable( GL_ALPHA_TEST );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\n\t\tif( glConfig.max_multisamples > 1 && gl_msaa.value )\n\t\t\tpglDisable( GL_MULTISAMPLE_ARB );\n\n\t\tglState.in2DMode = true;\n\t\tRI.currententity = NULL;\n\t\tRI.currentmodel = NULL;\n\t}\n\telse\n\t{\n\t\tpglDepthMask( GL_TRUE );\n\t\tpglEnable( GL_DEPTH_TEST );\n\t\tglState.in2DMode = false;\n\n\t\tpglMatrixMode( GL_PROJECTION );\n\t\tGL_LoadMatrix( RI.projectionMatrix );\n\n\t\tpglMatrixMode( GL_MODELVIEW );\n\t\tGL_LoadMatrix( RI.worldviewMatrix );\n\n\t\tif( glConfig.max_multisamples > 1 )\n\t\t{\n\t\t\tif( gl_msaa.value )\n\t\t\t\tpglEnable( GL_MULTISAMPLE_ARB );\n\t\t\telse pglDisable( GL_MULTISAMPLE_ARB );\n\t\t}\n\n\t\tGL_Cull( GL_FRONT );\n\t}\n}\n"
  },
  {
    "path": "ref/gl/gl_export.h",
    "content": "/*\ngl_export.h - opengl definition\nCopyright (C) 2007 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GL_EXPORT_H\n#define GL_EXPORT_H\n#ifndef APIENTRY\n#define APIENTRY\n#endif\n\n#ifndef APIENTRY_LINKAGE\n\t#define APIENTRY_LINKAGE extern\n#endif\n\n#if XASH_NANOGL || XASH_WES || XASH_REGAL\n\t#define XASH_GLES 1\n\t#define XASH_GL_STATIC 1\n\t#define REF_GL_KEEP_MANGLED_FUNCTIONS 1\n#elif XASH_GLES3COMPAT\n\t#ifdef SOFTFP_LINK\n\t\t#undef APIENTRY\n\t\t#define APIENTRY __attribute__((pcs(\"aapcs\")))\n\t#endif // SOFTFP_LINK\n\t#define XASH_GLES 1\n#endif // XASH_GLES3COMPAT\n\ntypedef uint GLenum;\ntypedef byte GLboolean;\ntypedef uint GLbitfield;\ntypedef void GLvoid;\ntypedef signed char GLbyte;\ntypedef short GLshort;\ntypedef int GLint;\ntypedef byte GLubyte;\ntypedef word GLushort;\ntypedef uint GLuint;\ntypedef int GLsizei;\ntypedef float GLfloat;\ntypedef float GLclampf;\ntypedef double GLdouble;\ntypedef double GLclampd;\ntypedef int GLintptrARB;\ntypedef int GLsizeiptrARB;\ntypedef char GLcharARB;\ntypedef uint GLhandleARB;\ntypedef float GLmatrix[16];\n\n#define GL_MODELVIEW\t\t\t0x1700\n#define GL_PROJECTION\t\t\t0x1701\n#define GL_TEXTURE\t\t\t\t0x1702\n#define GL_MATRIX_MODE\t\t\t0x0BA0\n#define GL_MODELVIEW_MATRIX\t\t\t0x0BA6\n#define GL_PROJECTION_MATRIX\t\t\t0x0BA7\n#define GL_TEXTURE_MATRIX\t\t\t0x0BA8\n\n#define GL_DONT_CARE\t\t\t0x1100\n#define GL_FASTEST\t\t\t\t0x1101\n#define GL_NICEST\t\t\t\t0x1102\n\n#define GL_DEPTH_TEST\t\t\t0x0B71\n#define GL_DEPTH_WRITEMASK\t\t\t0x0B72\n#define GL_CULL_FACE\t\t\t0x0B44\n#define GL_CW\t\t\t\t0x0900\n#define GL_CCW\t\t\t\t0x0901\n#define GL_BLEND\t\t\t\t0x0BE2\n#define GL_ALPHA_TEST\t\t\t0x0BC0\n\n// shading model\n#define GL_FLAT\t\t\t\t0x1D00\n#define GL_SMOOTH\t\t\t\t0x1D01\n\n#define GL_ZERO\t\t\t\t0x0\n#define GL_ONE\t\t\t\t0x1\n#define GL_SRC_COLOR\t\t\t0x0300\n#define GL_ONE_MINUS_SRC_COLOR\t\t0x0301\n#define GL_DST_COLOR\t\t\t0x0306\n#define GL_ONE_MINUS_DST_COLOR\t\t0x0307\n#define GL_SRC_ALPHA\t\t\t0x0302\n#define GL_ONE_MINUS_SRC_ALPHA\t\t0x0303\n#define GL_DST_ALPHA\t\t\t0x0304\n#define GL_ONE_MINUS_DST_ALPHA\t\t0x0305\n#define GL_SRC_ALPHA_SATURATE\t\t\t0x0308\n#define GL_CONSTANT_COLOR\t\t\t0x8001\n#define GL_ONE_MINUS_CONSTANT_COLOR\t\t0x8002\n#define GL_CONSTANT_ALPHA\t\t\t0x8003\n#define GL_ONE_MINUS_CONSTANT_ALPHA\t\t0x8004\n\n#define GL_TEXTURE_ENV\t\t\t0x2300\n#define GL_TEXTURE_ENV_MODE\t\t\t0x2200\n#define GL_TEXTURE_ENV_COLOR\t\t\t0x2201\n#define GL_TEXTURE_1D\t\t\t0x0DE0\n#define GL_TEXTURE_2D\t\t\t0x0DE1\n#define GL_TEXTURE_WRAP_S\t\t\t0x2802\n#define GL_TEXTURE_WRAP_T\t\t\t0x2803\n#define GL_TEXTURE_WRAP_R\t\t\t0x8072\n#define GL_TEXTURE_BORDER_COLOR\t\t0x1004\n#define GL_TEXTURE_MAG_FILTER\t\t\t0x2800\n#define GL_TEXTURE_MIN_FILTER\t\t\t0x2801\n#define GL_PACK_ALIGNMENT\t\t\t0x0D05\n#define GL_UNPACK_ALIGNMENT\t\t\t0x0CF5\n#define GL_TEXTURE_BINDING_1D\t\t\t0x8068\n#define GL_TEXTURE_BINDING_2D\t\t\t0x8069\n#define GL_CLAMP_TO_EDGE                  \t0x812F\n#define GL_CLAMP_TO_BORDER                \t0x812D\n#define GL_NEAREST\t\t\t\t0x2600\n#define GL_LINEAR\t\t\t\t0x2601\n#define GL_NEAREST_MIPMAP_NEAREST\t\t0x2700\n#define GL_NEAREST_MIPMAP_LINEAR\t\t0x2702\n#define GL_LINEAR_MIPMAP_NEAREST\t\t0x2701\n#define GL_LINEAR_MIPMAP_LINEAR\t\t0x2703\n\n#define GL_LINE\t\t\t\t0x1B01\n#define GL_FILL\t\t\t\t0x1B02\n\n#define GL_TEXTURE_MAX_ANISOTROPY_EXT\t\t0x84FE\n#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT\t0x84FF\n\n#define GL_MAX_TEXTURE_LOD_BIAS_EXT\t\t0x84FD\n#define GL_TEXTURE_FILTER_CONTROL_EXT\t\t0x8500\n#define GL_TEXTURE_LOD_BIAS_EXT\t\t0x8501\n\n#define GL_CLAMP_TO_BORDER_ARB\t\t0x812D\n\n#define GL_ADD\t\t\t\t0x0104\n#define GL_DECAL\t\t\t\t0x2101\n#define GL_MODULATE\t\t\t\t0x2100\n\n#define GL_REPEAT\t\t\t\t0x2901\n#define GL_CLAMP\t\t\t\t0x2900\n\n#define GL_POINTS\t\t\t\t0x0000\n#define GL_LINES\t\t\t\t0x0001\n#define GL_LINE_LOOP\t\t\t0x0002\n#define GL_LINE_STRIP\t\t\t0x0003\n#define GL_TRIANGLES\t\t\t0x0004\n#define GL_TRIANGLE_STRIP\t\t\t0x0005\n#define GL_TRIANGLE_FAN\t\t\t0x0006\n#define GL_QUADS\t\t\t\t0x0007\n#define GL_QUAD_STRIP\t\t\t0x0008\n#define GL_POLYGON\t\t\t\t0x0009\n\n#define GL_FALSE\t\t\t\t0x0\n#define GL_TRUE\t\t\t\t0x1\n\n#define GL_BYTE\t\t\t\t0x1400\n#define GL_UNSIGNED_BYTE\t\t\t0x1401\n#define GL_SHORT\t\t\t\t0x1402\n#define GL_UNSIGNED_SHORT\t\t\t0x1403\n#define GL_INT\t\t\t\t0x1404\n#define GL_UNSIGNED_INT\t\t\t0x1405\n#define GL_FLOAT\t\t\t\t0x1406\n#define GL_DOUBLE\t\t\t\t0x140A\n#define GL_2_BYTES\t\t\t\t0x1407\n#define GL_3_BYTES\t\t\t\t0x1408\n#define GL_4_BYTES\t\t\t\t0x1409\n#define GL_HALF_FLOAT_ARB\t\t\t0x140B\n\n#define GL_VERTEX_ARRAY\t\t\t0x8074\n#define GL_NORMAL_ARRAY\t\t\t0x8075\n#define GL_COLOR_ARRAY\t\t\t0x8076\n#define GL_INDEX_ARRAY\t\t\t0x8077\n#define GL_TEXTURE_COORD_ARRAY\t\t0x8078\n#define GL_EDGE_FLAG_ARRAY\t\t\t0x8079\n\n#define GL_NONE\t\t\t\t0\n#define GL_FRONT_LEFT\t\t\t0x0400\n#define GL_FRONT_RIGHT\t\t\t0x0401\n#define GL_BACK_LEFT\t\t\t0x0402\n#define GL_BACK_RIGHT\t\t\t0x0403\n#define GL_FRONT\t\t\t\t0x0404\n#define GL_BACK\t\t\t\t0x0405\n#define GL_LEFT\t\t\t\t0x0406\n#define GL_RIGHT\t\t\t\t0x0407\n#define GL_FRONT_AND_BACK\t\t\t0x0408\n#define GL_AUX0\t\t\t\t0x0409\n#define GL_AUX1\t\t\t\t0x040A\n#define GL_AUX2\t\t\t\t0x040B\n#define GL_AUX3\t\t\t\t0x040C\n\n#define GL_VENDOR\t\t\t\t0x1F00\n#define GL_RENDERER\t\t\t\t0x1F01\n#define GL_VERSION\t\t\t\t0x1F02\n#define GL_EXTENSIONS\t\t\t0x1F03\n\n#define GL_NO_ERROR \t\t\t0x0\n#define GL_INVALID_VALUE\t\t\t0x0501\n#define GL_INVALID_ENUM\t\t\t0x0500\n#define GL_INVALID_OPERATION\t\t\t0x0502\n#define GL_STACK_OVERFLOW\t\t\t0x0503\n#define GL_STACK_UNDERFLOW\t\t\t0x0504\n#define GL_OUT_OF_MEMORY\t\t\t0x0505\n\n#define GL_DITHER\t\t\t\t0x0BD0\n#define GL_ALPHA\t\t\t\t0x1906\n#define GL_RGB\t\t\t\t0x1907\n#define GL_RGBA\t\t\t\t0x1908\n#define GL_BGR\t\t\t\t0x80E0\n#define GL_BGRA\t\t\t\t0x80E1\n#define GL_ALPHA4                         \t0x803B\n#define GL_ALPHA8                         \t0x803C\n#define GL_ALPHA12                        \t0x803D\n#define GL_ALPHA16                        \t0x803E\n#define GL_LUMINANCE4                     \t0x803F\n#define GL_LUMINANCE8                     \t0x8040\n#define GL_LUMINANCE12                    \t0x8041\n#define GL_LUMINANCE16                    \t0x8042\n#define GL_LUMINANCE4_ALPHA4              \t0x8043\n#define GL_LUMINANCE6_ALPHA2              \t0x8044\n#define GL_LUMINANCE8_ALPHA8              \t0x8045\n#define GL_LUMINANCE12_ALPHA4             \t0x8046\n#define GL_LUMINANCE12_ALPHA12            \t0x8047\n#define GL_LUMINANCE16_ALPHA16\t\t0x8048\n#define GL_LUMINANCE\t\t\t0x1909\n#define GL_LUMINANCE_ALPHA\t\t\t0x190A\n#define GL_DEPTH_COMPONENT\t\t\t0x1902\n#define GL_INTENSITY                      \t0x8049\n#define GL_INTENSITY4                     \t0x804A\n#define GL_INTENSITY8                     \t0x804B\n#define GL_INTENSITY12                    \t0x804C\n#define GL_INTENSITY16                    \t0x804D\n#define GL_R3_G3_B2                       \t0x2A10\n#define GL_RGB4                           \t0x804F\n#define GL_RGB5                           \t0x8050\n#define GL_RGB8                           \t0x8051\n#define GL_RGB10                          \t0x8052\n#define GL_RGB12                          \t0x8053\n#define GL_RGB16                          \t0x8054\n#define GL_RGBA2                          \t0x8055\n#define GL_RGBA4                          \t0x8056\n#define GL_RGB5_A1                        \t0x8057\n#define GL_RGBA8                          \t0x8058\n#define GL_RGB10_A2                       \t0x8059\n#define GL_RGBA12                         \t0x805A\n#define GL_RGBA16                         \t0x805B\n#define GL_TEXTURE_RED_SIZE               \t0x805C\n#define GL_TEXTURE_GREEN_SIZE             \t0x805D\n#define GL_TEXTURE_BLUE_SIZE              \t0x805E\n#define GL_TEXTURE_ALPHA_SIZE             \t0x805F\n#define GL_TEXTURE_LUMINANCE_SIZE         \t0x8060\n#define GL_TEXTURE_INTENSITY_SIZE         \t0x8061\n#define GL_PROXY_TEXTURE_1D               \t0x8063\n#define GL_PROXY_TEXTURE_2D               \t0x8064\n#define GL_MAX_TEXTURE_SIZE\t\t\t0x0D33\n\n#define GL_RG\t\t\t\t0x8227\n#define GL_RG_INTEGER\t\t\t0x8228\n#define GL_R8\t\t\t\t0x8229\n#define GL_R16\t\t\t\t0x822A\n#define GL_RG8\t\t\t\t0x822B\n#define GL_RG16\t\t\t\t0x822C\n#define GL_R16F\t\t\t\t0x822D\n#define GL_R32F\t\t\t\t0x822E\n#define GL_RG16F\t\t\t\t0x822F\n#define GL_RG32F\t\t\t\t0x8230\n#define GL_R8I\t\t\t\t0x8231\n#define GL_R8UI\t\t\t\t0x8232\n#define GL_R16I\t\t\t\t0x8233\n#define GL_R16UI\t\t\t\t0x8234\n#define GL_R32I\t\t\t\t0x8235\n#define GL_R32UI\t\t\t\t0x8236\n#define GL_RG8I\t\t\t\t0x8237\n#define GL_RG8UI\t\t\t\t0x8238\n#define GL_RG16I\t\t\t\t0x8239\n#define GL_RG16UI\t\t\t\t0x823A\n#define GL_RG32I\t\t\t\t0x823B\n#define GL_RG32UI\t\t\t\t0x823C\n\n// texture coord name\n#define GL_S\t\t\t\t0x2000\n#define GL_T\t\t\t\t0x2001\n#define GL_R\t\t\t\t0x2002\n#define GL_Q\t\t\t\t0x2003\n\n// texture gen mode\n#define GL_EYE_LINEAR\t\t\t0x2400\n#define GL_OBJECT_LINEAR\t\t\t0x2401\n#define GL_SPHERE_MAP\t\t\t0x2402\n\n// texture gen parameter\n#define GL_TEXTURE_GEN_MODE\t\t\t0x2500\n#define GL_OBJECT_PLANE\t\t\t0x2501\n#define GL_EYE_PLANE\t\t\t0x2502\n#define GL_FOG_HINT\t\t\t\t0x0C54\n#define GL_TEXTURE_GEN_S\t\t\t0x0C60\n#define GL_TEXTURE_GEN_T\t\t\t0x0C61\n#define GL_TEXTURE_GEN_R\t\t\t0x0C62\n#define GL_TEXTURE_GEN_Q\t\t\t0x0C63\n\n#define GL_SCISSOR_BOX\t\t\t0x0C10\n#define GL_SCISSOR_TEST\t\t\t0x0C11\n\n#define GL_NEVER\t\t\t\t0x0200\n#define GL_LESS\t\t\t\t0x0201\n#define GL_EQUAL\t\t\t\t0x0202\n#define GL_LEQUAL\t\t\t\t0x0203\n#define GL_GREATER\t\t\t\t0x0204\n#define GL_NOTEQUAL\t\t\t\t0x0205\n#define GL_GEQUAL\t\t\t\t0x0206\n#define GL_ALWAYS\t\t\t\t0x0207\n#define GL_DEPTH_TEST\t\t\t0x0B71\n\n#define GL_RED_SCALE\t\t\t0x0D14\n#define GL_GREEN_SCALE\t\t\t0x0D18\n#define GL_BLUE_SCALE\t\t\t0x0D1A\n#define GL_ALPHA_SCALE\t\t\t0x0D1C\n\n/* AttribMask */\n#define GL_CURRENT_BIT\t\t\t0x00000001\n#define GL_POINT_BIT\t\t\t0x00000002\n#define GL_LINE_BIT\t\t\t\t0x00000004\n#define GL_POLYGON_BIT\t\t\t0x00000008\n#define GL_POLYGON_STIPPLE_BIT\t\t0x00000010\n#define GL_PIXEL_MODE_BIT\t\t\t0x00000020\n#define GL_LIGHTING_BIT\t\t\t0x00000040\n#define GL_FOG_BIT\t\t\t\t0x00000080\n#define GL_DEPTH_BUFFER_BIT\t\t\t0x00000100\n#define GL_ACCUM_BUFFER_BIT\t\t\t0x00000200\n#define GL_STENCIL_BUFFER_BIT\t\t\t0x00000400\n#define GL_VIEWPORT_BIT\t\t\t0x00000800\n#define GL_TRANSFORM_BIT\t\t\t0x00001000\n#define GL_ENABLE_BIT\t\t\t0x00002000\n#define GL_COLOR_BUFFER_BIT\t\t\t0x00004000\n#define GL_HINT_BIT\t\t\t\t0x00008000\n#define GL_EVAL_BIT\t\t\t\t0x00010000\n#define GL_LIST_BIT\t\t\t\t0x00020000\n#define GL_TEXTURE_BIT\t\t\t0x00040000\n#define GL_SCISSOR_BIT\t\t\t0x00080000\n#define GL_ALL_ATTRIB_BITS\t\t\t0x000fffff\n\n#define GL_STENCIL_TEST\t\t\t0x0B90\n#define GL_KEEP\t\t\t\t0x1E00\n#define GL_REPLACE\t\t\t\t0x1E01\n#define GL_INCR\t\t\t\t0x1E02\n#define GL_DECR\t\t\t\t0x1E03\n\n// fog stuff\n#define GL_FOG\t\t\t\t0x0B60\n#define GL_FOG_INDEX\t\t\t0x0B61\n#define GL_FOG_DENSITY\t\t\t0x0B62\n#define GL_FOG_START\t\t\t0x0B63\n#define GL_FOG_END\t\t\t\t0x0B64\n#define GL_FOG_MODE\t\t\t\t0x0B65\n#define GL_FOG_COLOR\t\t\t0x0B66\n#define GL_EXP\t\t\t\t0x0800\n#define GL_EXP2\t\t\t\t0x0801\n\n#define GL_POLYGON_OFFSET_FACTOR\t\t0x8038\n#define GL_POLYGON_OFFSET_UNITS\t\t0x2A00\n#define GL_POLYGON_OFFSET_POINT\t\t0x2A01\n#define GL_POLYGON_OFFSET_LINE\t\t0x2A02\n#define GL_POLYGON_OFFSET_FILL\t\t0x8037\n\n#define GL_POINT_SMOOTH\t\t\t0x0B10\n#define GL_LINE_SMOOTH\t\t\t0x0B20\n#define GL_POLYGON_SMOOTH\t\t\t0x0B41\n#define GL_POLYGON_STIPPLE\t\t\t0x0B42\n#define GL_CLIP_PLANE0\t\t\t0x3000\n#define GL_CLIP_PLANE1\t\t\t0x3001\n#define GL_CLIP_PLANE2\t\t\t0x3002\n#define GL_CLIP_PLANE3\t\t\t0x3003\n#define GL_CLIP_PLANE4\t\t\t0x3004\n#define GL_CLIP_PLANE5\t\t\t0x3005\n#define GL_POINT_SIZE_MIN_EXT\t\t\t0x8126\n#define GL_POINT_SIZE_MAX_EXT\t\t\t0x8127\n#define GL_POINT_FADE_THRESHOLD_SIZE_EXT\t0x8128\n#define GL_DISTANCE_ATTENUATION_EXT\t\t0x8129\n#define GL_ACTIVE_TEXTURE_ARB\t\t\t0x84E0\n#define GL_CLIENT_ACTIVE_TEXTURE_ARB\t\t0x84E1\n#define GL_MAX_TEXTURE_UNITS_ARB\t\t0x84E2\n#define GL_TEXTURE0_ARB\t\t\t0x84C0\n#define GL_TEXTURE1_ARB\t\t\t0x84C1\n#define GL_TEXTURE2_ARB                   \t0x84C2\n#define GL_TEXTURE0_SGIS\t\t\t0x835E\n#define GL_TEXTURE1_SGIS\t\t\t0x835F\n#define GL_GENERATE_MIPMAP_SGIS           \t0x8191\n#define GL_GENERATE_MIPMAP_HINT_SGIS      \t0x8192\n#define GL_TEXTURE_RECTANGLE_NV\t\t0x84F5\n#define GL_TEXTURE_BINDING_RECTANGLE_NV\t\t0x84F6\n#define GL_PROXY_TEXTURE_RECTANGLE_NV\t\t0x84F7\n#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV\t0x84F8\n#define GL_TEXTURE_RECTANGLE_EXT\t\t0x84F5\n#define GL_TEXTURE_BINDING_RECTANGLE_EXT\t0x84F6\n#define GL_PROXY_TEXTURE_RECTANGLE_EXT\t\t0x84F7\n#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT\t0x84F8\n#define GL_MAX_TEXTURE_UNITS\t\t\t0x84E2\n#define GL_MAX_TEXTURE_UNITS_ARB\t\t0x84E2\n\n#define GL_DEPTH_COMPONENT16\t\t\t0x81A5\n#define GL_DEPTH_COMPONENT24\t\t\t0x81A6\n#define GL_DEPTH_COMPONENT32\t\t\t0x81A7\n#define GL_DEPTH_COMPONENT32F\t\t\t0x8CAC\n#define GL_DEPTH32F_STENCIL8\t\t\t0x8CAD\n#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV\t0x8DAD\n\n#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT\t\t0x83F0\n#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT\t0x83F1\n#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT\t0x83F2\n#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT\t0x83F3\n#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB           0x8E8C\n#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB     0x8E8D\n#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB     0x8E8E\n#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB   0x8E8F\n#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT\t0x8DBD\n#define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI\t0x8837\n#define GL_COMPRESSED_ALPHA_ARB\t\t0x84E9\n#define GL_COMPRESSED_LUMINANCE_ARB\t\t0x84EA\n#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB\t0x84EB\n#define GL_COMPRESSED_INTENSITY_ARB\t\t0x84EC\n#define GL_COMPRESSED_RGB_ARB\t\t\t0x84ED\n#define GL_COMPRESSED_RGBA_ARB\t\t0x84EE\n#define GL_TEXTURE_COMPRESSION_HINT_ARB\t\t0x84EF\n#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB\t0x86A0\n#define GL_TEXTURE_COMPRESSED_ARB\t\t0x86A1\n#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB\t0x86A2\n#define GL_COMPRESSED_TEXTURE_FORMATS_ARB\t0x86A3\n#define GL_UNSIGNED_BYTE_2_3_3_REV\t\t0x8362\n#define GL_UNSIGNED_SHORT_5_6_5\t\t0x8363\n#define GL_UNSIGNED_SHORT_5_6_5_REV\t\t0x8364\n#define GL_UNSIGNED_SHORT_4_4_4_4_REV\t\t0x8365\n#define GL_UNSIGNED_SHORT_1_5_5_5_REV\t\t0x8366\n#define GL_UNSIGNED_INT_8_8_8_8_REV\t\t0x8367\n#define GL_UNSIGNED_INT_2_10_10_10_REV\t\t0x8368\n#define GL_TEXTURE_MAX_LEVEL\t\t\t0x813D\n#define GL_GENERATE_MIPMAP\t\t\t0x8191\n#define GL_ADD_SIGNED\t\t\t0x8574\n\n#define GL_PROGRAM_OBJECT_ARB\t\t\t0x8B40\n#define GL_OBJECT_TYPE_ARB\t\t\t0x8B4E\n#define GL_OBJECT_SUBTYPE_ARB\t\t\t0x8B4F\n#define GL_OBJECT_DELETE_STATUS_ARB\t\t0x8B80\n#define GL_OBJECT_COMPILE_STATUS_ARB\t\t0x8B81\n#define GL_OBJECT_LINK_STATUS_ARB\t\t0x8B82\n#define GL_OBJECT_VALIDATE_STATUS_ARB\t\t0x8B83\n#define GL_OBJECT_INFO_LOG_LENGTH_ARB\t\t0x8B84\n#define GL_OBJECT_ATTACHED_OBJECTS_ARB\t\t0x8B85\n#define GL_OBJECT_ACTIVE_UNIFORMS_ARB\t\t0x8B86\n#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB\t0x8B87\n#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB\t0x8B88\n#define GL_SHADER_OBJECT_ARB\t\t\t0x8B48\n#define GL_FLOAT_VEC2_ARB\t\t\t0x8B50\n#define GL_FLOAT_VEC3_ARB\t\t\t0x8B51\n#define GL_FLOAT_VEC4_ARB\t\t\t0x8B52\n#define GL_INT_VEC2_ARB\t\t\t0x8B53\n#define GL_INT_VEC3_ARB\t\t\t0x8B54\n#define GL_INT_VEC4_ARB\t\t\t0x8B55\n#define GL_BOOL_ARB\t\t\t\t0x8B56\n#define GL_BOOL_VEC2_ARB\t\t\t0x8B57\n#define GL_BOOL_VEC3_ARB\t\t\t0x8B58\n#define GL_BOOL_VEC4_ARB\t\t\t0x8B59\n#define GL_FLOAT_MAT2_ARB\t\t\t0x8B5A\n#define GL_FLOAT_MAT3_ARB\t\t\t0x8B5B\n#define GL_FLOAT_MAT4_ARB\t\t\t0x8B5C\n#define GL_SAMPLER_1D_ARB\t\t\t0x8B5D\n#define GL_SAMPLER_2D_ARB\t\t\t0x8B5E\n#define GL_SAMPLER_3D_ARB\t\t\t0x8B5F\n#define GL_SAMPLER_CUBE_ARB\t\t\t0x8B60\n#define GL_SAMPLER_1D_SHADOW_ARB\t\t0x8B61\n#define GL_SAMPLER_2D_SHADOW_ARB\t\t0x8B62\n#define GL_SAMPLER_2D_RECT_ARB\t\t0x8B63\n#define GL_SAMPLER_2D_RECT_SHADOW_ARB\t\t0x8B64\n\n#define GL_PACK_SKIP_IMAGES\t\t\t0x806B\n#define GL_PACK_IMAGE_HEIGHT\t\t\t0x806C\n#define GL_UNPACK_SKIP_IMAGES\t\t\t0x806D\n#define GL_UNPACK_IMAGE_HEIGHT\t\t0x806E\n#define GL_TEXTURE_3D\t\t\t0x806F\n#define GL_PROXY_TEXTURE_3D\t\t\t0x8070\n#define GL_TEXTURE_DEPTH\t\t\t0x8071\n#define GL_TEXTURE_WRAP_R\t\t\t0x8072\n#define GL_MAX_3D_TEXTURE_SIZE\t\t0x8073\n#define GL_TEXTURE_BINDING_3D\t\t\t0x806A\n#define GL_TEXTURE_CUBE_MAP_SEAMLESS\t\t0x884F\n#define GL_STENCIL_TEST_TWO_SIDE_EXT\t\t0x8910\n#define GL_ACTIVE_STENCIL_FACE_EXT\t\t0x8911\n#define GL_STENCIL_BACK_FUNC              \t0x8800\n#define GL_STENCIL_BACK_FAIL              \t0x8801\n#define GL_STENCIL_BACK_PASS_DEPTH_FAIL   \t0x8802\n#define GL_STENCIL_BACK_PASS_DEPTH_PASS   \t0x8803\n\n#define GL_MAX_DRAW_BUFFERS_ARB\t\t0x8824\n#define GL_DRAW_BUFFER0_ARB\t\t\t0x8825\n#define GL_DRAW_BUFFER1_ARB\t\t\t0x8826\n#define GL_DRAW_BUFFER2_ARB\t\t\t0x8827\n#define GL_DRAW_BUFFER3_ARB\t\t\t0x8828\n#define GL_DRAW_BUFFER4_ARB\t\t\t0x8829\n#define GL_DRAW_BUFFER5_ARB\t\t\t0x882A\n#define GL_DRAW_BUFFER6_ARB\t\t\t0x882B\n#define GL_DRAW_BUFFER7_ARB\t\t\t0x882C\n#define GL_DRAW_BUFFER8_ARB\t\t\t0x882D\n#define GL_DRAW_BUFFER9_ARB\t\t\t0x882E\n#define GL_DRAW_BUFFER10_ARB\t\t\t0x882F\n#define GL_DRAW_BUFFER11_ARB\t\t\t0x8830\n#define GL_DRAW_BUFFER12_ARB\t\t\t0x8831\n#define GL_DRAW_BUFFER13_ARB\t\t\t0x8832\n#define GL_DRAW_BUFFER14_ARB\t\t\t0x8833\n#define GL_DRAW_BUFFER15_ARB\t\t\t0x8834\n\n#define GL_DEPTH_TEXTURE_MODE_ARB\t\t0x884B\n#define GL_TEXTURE_COMPARE_MODE_ARB\t\t0x884C\n#define GL_TEXTURE_COMPARE_FUNC_ARB\t\t0x884D\n#define GL_COMPARE_R_TO_TEXTURE_ARB\t\t0x884E\n#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB\t0x80BF\n\n#define GL_QUERY_COUNTER_BITS_ARB\t\t0x8864\n#define GL_CURRENT_QUERY_ARB\t\t\t0x8865\n#define GL_QUERY_RESULT_ARB\t\t\t0x8866\n#define GL_QUERY_RESULT_AVAILABLE_ARB\t\t0x8867\n#define GL_SAMPLES_PASSED_ARB\t\t\t0x8914\n\n#define GL_FUNC_ADD_EXT\t\t\t0x8006\n#define GL_FUNC_SUBTRACT_EXT\t\t\t0x800A\n#define GL_FUNC_REVERSE_SUBTRACT_EXT\t\t0x800B\n#define GL_MIN_EXT\t\t\t\t0x8007\n#define GL_MAX_EXT\t\t\t\t0x8008\n#define GL_BLEND_EQUATION_EXT\t\t\t0x8009\n\n#define GL_VERTEX_SHADER_ARB\t\t\t0x8B31\n#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB\t0x8B4A\n#define GL_MAX_VARYING_FLOATS_ARB\t\t0x8B4B\n#define GL_MAX_VERTEX_ATTRIBS_ARB\t\t0x8869\n#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB\t\t0x8872\n#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB\t0x8B4C\n#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB\t0x8B4D\n#define GL_MAX_TEXTURE_COORDS_ARB\t\t0x8871\n#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB\t0x8642\n#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB\t\t0x8643\n#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB\t\t0x8B89\n#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB\t0x8B8A\n#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB\t0x8622\n#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB\t\t0x8623\n#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB\t0x8624\n#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB\t\t0x8625\n#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB\t0x886A\n#define GL_CURRENT_VERTEX_ATTRIB_ARB\t\t0x8626\n#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB\t0x8645\n#define GL_FLOAT_VEC2_ARB\t\t\t0x8B50\n#define GL_FLOAT_VEC3_ARB\t\t\t0x8B51\n#define GL_FLOAT_VEC4_ARB\t\t\t0x8B52\n#define GL_FLOAT_MAT2_ARB\t\t\t0x8B5A\n#define GL_FLOAT_MAT3_ARB\t\t\t0x8B5B\n#define GL_FLOAT_MAT4_ARB\t\t\t0x8B5C\n\n#define GL_FLOAT_R_NV\t\t\t0x8880\n#define GL_FLOAT_RG_NV\t\t\t0x8881\n#define GL_FLOAT_RGB_NV\t\t\t0x8882\n#define GL_FLOAT_RGBA_NV\t\t\t0x8883\n#define GL_FLOAT_R16_NV\t\t\t0x8884\n#define GL_FLOAT_R32_NV\t\t\t0x8885\n#define GL_FLOAT_RG16_NV\t\t\t0x8886\n#define GL_FLOAT_RG32_NV\t\t\t0x8887\n#define GL_FLOAT_RGB16_NV\t\t\t0x8888\n#define GL_FLOAT_RGB32_NV\t\t\t0x8889\n#define GL_FLOAT_RGBA16_NV\t\t\t0x888A\n#define GL_FLOAT_RGBA32_NV\t\t\t0x888B\n#define GL_TEXTURE_FLOAT_COMPONENTS_NV\t\t0x888C\n#define GL_FLOAT_CLEAR_COLOR_VALUE_NV\t\t0x888D\n#define GL_FLOAT_RGBA_MODE_NV\t\t\t0x888E\n\n#define GL_FRAGMENT_SHADER_ARB\t\t0x8B30\n#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB\t0x8B49\n#define GL_MAX_TEXTURE_COORDS_ARB\t\t0x8871\n#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB\t\t0x8872\n#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB\t0x8B8B\n\n#define GL_TEXTURE_RED_TYPE_ARB                   0x8C10\n#define GL_TEXTURE_GREEN_TYPE_ARB                 0x8C11\n#define GL_TEXTURE_BLUE_TYPE_ARB                  0x8C12\n#define GL_TEXTURE_ALPHA_TYPE_ARB                 0x8C13\n#define GL_TEXTURE_LUMINANCE_TYPE_ARB             0x8C14\n#define GL_TEXTURE_INTENSITY_TYPE_ARB             0x8C15\n#define GL_TEXTURE_DEPTH_TYPE_ARB                 0x8C16\n#define GL_UNSIGNED_NORMALIZED_ARB                0x8C17\n#define GL_RGBA32F_ARB                            0x8814\n#define GL_RGB32F_ARB                             0x8815\n#define GL_ALPHA32F_ARB                           0x8816\n#define GL_INTENSITY32F_ARB                       0x8817\n#define GL_LUMINANCE32F_ARB                       0x8818\n#define GL_LUMINANCE_ALPHA32F_ARB                 0x8819\n#define GL_RGBA16F_ARB                            0x881A\n#define GL_RGB16F_ARB                             0x881B\n#define GL_ALPHA16F_ARB                           0x881C\n#define GL_INTENSITY16F_ARB                       0x881D\n#define GL_LUMINANCE16F_ARB                       0x881E\n#define GL_LUMINANCE_ALPHA16F_ARB                 0x881F\n\n#define GL_RGBA_FLOAT32_ATI                       0x8814\n#define GL_RGB_FLOAT32_ATI                        0x8815\n#define GL_ALPHA_FLOAT32_ATI                      0x8816\n#define GL_INTENSITY_FLOAT32_ATI                  0x8817\n#define GL_LUMINANCE_FLOAT32_ATI                  0x8818\n#define GL_LUMINANCE_ALPHA_FLOAT32_ATI            0x8819\n#define GL_RGBA_FLOAT16_ATI                       0x881A\n#define GL_RGB_FLOAT16_ATI                        0x881B\n#define GL_ALPHA_FLOAT16_ATI                      0x881C\n#define GL_INTENSITY_FLOAT16_ATI                  0x881D\n#define GL_LUMINANCE_FLOAT16_ATI                  0x881E\n#define GL_LUMINANCE_ALPHA_FLOAT16_ATI            0x881F\n\n//GL_ARB_vertex_buffer_object\n#define GL_ARRAY_BUFFER_ARB\t\t\t0x8892\n#define GL_ELEMENT_ARRAY_BUFFER_ARB\t\t0x8893\n#define GL_ARRAY_BUFFER_BINDING_ARB\t\t0x8894\n#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB\t0x8895\n#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB\t0x8896\n#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB\t0x8897\n#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB\t0x8898\n#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB\t0x8899\n#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB\t0x889A\n#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB\t0x889B\n#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB\t0x889E\n#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB\t0x889F\n#define GL_STREAM_DRAW_ARB\t\t\t0x88E0\n#define GL_STREAM_READ_ARB\t\t\t0x88E1\n#define GL_STREAM_COPY_ARB\t\t\t0x88E2\n#define GL_STATIC_DRAW_ARB\t\t\t0x88E4\n#define GL_STATIC_READ_ARB\t\t\t0x88E5\n#define GL_STATIC_COPY_ARB\t\t\t0x88E6\n#define GL_DYNAMIC_DRAW_ARB\t\t\t0x88E8\n#define GL_DYNAMIC_READ_ARB\t\t\t0x88E9\n#define GL_DYNAMIC_COPY_ARB\t\t\t0x88EA\n#define GL_READ_ONLY_ARB\t\t\t0x88B8\n#define GL_WRITE_ONLY_ARB\t\t\t0x88B9\n#define GL_READ_WRITE_ARB\t\t\t0x88BA\n#define GL_BUFFER_SIZE_ARB\t\t\t0x8764\n#define GL_BUFFER_USAGE_ARB\t\t\t0x8765\n#define GL_BUFFER_ACCESS_ARB\t\t\t0x88BB\n#define GL_BUFFER_MAPPED_ARB\t\t\t0x88BC\n#define GL_BUFFER_MAP_POINTER_ARB\t\t0x88BD\n#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB\t0x889C\n#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB\t0x889D\n\n#define GL_NORMAL_MAP_ARB\t\t\t0x8511\n#define GL_REFLECTION_MAP_ARB\t\t\t0x8512\n#define GL_TEXTURE_CUBE_MAP_ARB\t\t0x8513\n#define GL_TEXTURE_BINDING_CUBE_MAP_ARB\t\t0x8514\n#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB\t0x8515\n#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB\t0x8516\n#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB\t0x8517\n#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB\t0x8518\n#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB\t0x8519\n#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB\t0x851A\n#define GL_PROXY_TEXTURE_CUBE_MAP_ARB\t\t0x851B\n#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB\t0x851C\n\n#define GL_COMBINE_ARB\t\t\t0x8570\n#define GL_COMBINE_RGB_ARB\t\t\t0x8571\n#define GL_COMBINE_ALPHA_ARB\t\t\t0x8572\n#define GL_SOURCE0_RGB_ARB\t\t\t0x8580\n#define GL_SOURCE1_RGB_ARB\t\t\t0x8581\n#define GL_SOURCE2_RGB_ARB\t\t\t0x8582\n#define GL_SOURCE0_ALPHA_ARB\t\t\t0x8588\n#define GL_SOURCE1_ALPHA_ARB\t\t\t0x8589\n#define GL_SOURCE2_ALPHA_ARB\t\t\t0x858A\n#define GL_OPERAND0_RGB_ARB\t\t\t0x8590\n#define GL_OPERAND1_RGB_ARB\t\t\t0x8591\n#define GL_OPERAND2_RGB_ARB\t\t\t0x8592\n#define GL_OPERAND0_ALPHA_ARB\t\t\t0x8598\n#define GL_OPERAND1_ALPHA_ARB\t\t\t0x8599\n#define GL_OPERAND2_ALPHA_ARB\t\t\t0x859A\n#define GL_RGB_SCALE_ARB\t\t\t0x8573\n#define GL_ADD_SIGNED_ARB\t\t\t0x8574\n#define GL_INTERPOLATE_ARB\t\t\t0x8575\n#define GL_SUBTRACT_ARB\t\t\t0x84E7\n#define GL_CONSTANT_ARB\t\t\t0x8576\n#define GL_PRIMARY_COLOR_ARB\t\t\t0x8577\n#define GL_PREVIOUS_ARB\t\t\t0x8578\n\n#define GL_DOT3_RGB_ARB\t\t\t0x86AE\n#define GL_DOT3_RGBA_ARB\t\t\t0x86AF\n\n#define GL_TEXTURE_1D_ARRAY_EXT\t\t0x8C18\n#define GL_PROXY_TEXTURE_1D_ARRAY_EXT\t\t0x8C19\n#define GL_TEXTURE_2D_ARRAY_EXT\t\t0x8C1A\n#define GL_PROXY_TEXTURE_2D_ARRAY_EXT\t\t0x8C1B\n#define GL_TEXTURE_BINDING_1D_ARRAY_EXT\t\t0x8C1C\n#define GL_TEXTURE_BINDING_2D_ARRAY_EXT\t\t0x8C1D\n#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT\t\t0x88FF\n#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT\t0x884E\n\n#define GL_MULTISAMPLE_ARB\t\t\t0x809D\n#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB\t\t0x809E\n#define GL_SAMPLE_ALPHA_TO_ONE_ARB\t\t0x809F\n#define GL_SAMPLE_COVERAGE_ARB\t\t0x80A0\n#define GL_SAMPLE_BUFFERS_ARB\t\t\t0x80A8\n#define GL_SAMPLES_ARB\t\t\t0x80A9\n#define GL_SAMPLE_COVERAGE_VALUE_ARB\t\t0x80AA\n#define GL_SAMPLE_COVERAGE_INVERT_ARB\t\t0x80AB\n#define GL_MULTISAMPLE_BIT_ARB\t\t0x20000000\n#define GL_TEXTURE_2D_MULTISAMPLE\t\t\t0x9100\n\n#define GL_COLOR_SUM_ARB\t\t\t0x8458\n#define GL_VERTEX_PROGRAM_ARB\t\t\t0x8620\n#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB\t0x8622\n#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB\t\t0x8623\n#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB\t0x8624\n#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB\t\t0x8625\n#define GL_CURRENT_VERTEX_ATTRIB_ARB\t\t0x8626\n#define GL_PROGRAM_LENGTH_ARB\t\t\t0x8627\n#define GL_PROGRAM_STRING_ARB\t\t\t0x8628\n#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB\t0x862E\n#define GL_MAX_PROGRAM_MATRICES_ARB\t\t0x862F\n#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB\t0x8640\n#define GL_CURRENT_MATRIX_ARB\t\t\t0x8641\n#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB\t0x8642\n#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB\t\t0x8643\n#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB\t0x8645\n#define GL_PROGRAM_ERROR_POSITION_ARB\t\t0x864B\n#define GL_PROGRAM_BINDING_ARB\t\t0x8677\n#define GL_MAX_VERTEX_ATTRIBS_ARB\t\t0x8869\n#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB\t0x886A\n#define GL_PROGRAM_ERROR_STRING_ARB\t\t0x8874\n#define GL_PROGRAM_FORMAT_ASCII_ARB\t\t0x8875\n#define GL_PROGRAM_FORMAT_ARB\t\t\t0x8876\n#define GL_PROGRAM_INSTRUCTIONS_ARB\t\t0x88A0\n#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB\t\t0x88A1\n#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB\t0x88A2\n#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB\t0x88A3\n#define GL_PROGRAM_TEMPORARIES_ARB\t\t0x88A4\n#define GL_MAX_PROGRAM_TEMPORARIES_ARB\t\t0x88A5\n#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB\t0x88A6\n#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB\t0x88A7\n#define GL_PROGRAM_PARAMETERS_ARB\t\t0x88A8\n#define GL_MAX_PROGRAM_PARAMETERS_ARB\t\t0x88A9\n#define GL_PROGRAM_NATIVE_PARAMETERS_ARB\t0x88AA\n#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB\t0x88AB\n#define GL_PROGRAM_ATTRIBS_ARB\t\t0x88AC\n#define GL_MAX_PROGRAM_ATTRIBS_ARB\t\t0x88AD\n#define GL_PROGRAM_NATIVE_ATTRIBS_ARB\t\t0x88AE\n#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB\t0x88AF\n#define GL_PROGRAM_ADDRESS_REGISTERS_ARB\t0x88B0\n#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB\t0x88B1\n#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB\t0x88B2\n#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB\t0x88B3\n#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB\t0x88B4\n#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB\t0x88B5\n#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB\t0x88B6\n#define GL_TRANSPOSE_CURRENT_MATRIX_ARB\t\t0x88B7\n#define GL_MATRIX0_ARB\t\t\t0x88C0\n#define GL_MATRIX1_ARB\t\t\t0x88C1\n#define GL_MATRIX2_ARB\t\t\t0x88C2\n#define GL_MATRIX3_ARB\t\t\t0x88C3\n#define GL_MATRIX4_ARB\t\t\t0x88C4\n#define GL_MATRIX5_ARB\t\t\t0x88C5\n#define GL_MATRIX6_ARB\t\t\t0x88C6\n#define GL_MATRIX7_ARB\t\t\t0x88C7\n#define GL_MATRIX8_ARB\t\t\t0x88C8\n#define GL_MATRIX9_ARB\t\t\t0x88C9\n#define GL_MATRIX10_ARB\t\t\t0x88CA\n#define GL_MATRIX11_ARB\t\t\t0x88CB\n#define GL_MATRIX12_ARB\t\t\t0x88CC\n#define GL_MATRIX13_ARB\t\t\t0x88CD\n#define GL_MATRIX14_ARB\t\t\t0x88CE\n#define GL_MATRIX15_ARB\t\t\t0x88CF\n#define GL_MATRIX16_ARB\t\t\t0x88D0\n#define GL_MATRIX17_ARB\t\t\t0x88D1\n#define GL_MATRIX18_ARB\t\t\t0x88D2\n#define GL_MATRIX19_ARB\t\t\t0x88D3\n#define GL_MATRIX20_ARB\t\t\t0x88D4\n#define GL_MATRIX21_ARB\t\t\t0x88D5\n#define GL_MATRIX22_ARB\t\t\t0x88D6\n#define GL_MATRIX23_ARB\t\t\t0x88D7\n#define GL_MATRIX24_ARB\t\t\t0x88D8\n#define GL_MATRIX25_ARB\t\t\t0x88D9\n#define GL_MATRIX26_ARB\t\t\t0x88DA\n#define GL_MATRIX27_ARB\t\t\t0x88DB\n#define GL_MATRIX28_ARB\t\t\t0x88DC\n#define GL_MATRIX29_ARB\t\t\t0x88DD\n#define GL_MATRIX30_ARB\t\t\t0x88DE\n#define GL_MATRIX31_ARB\t\t\t0x88DF\n#define GL_FRAGMENT_PROGRAM_ARB\t\t0x8804\n#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB\t\t0x8805\n#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB\t\t0x8806\n#define GL_PROGRAM_TEX_INDIRECTIONS_ARB\t\t0x8807\n#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB\t0x8808\n#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB\t0x8809\n#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB\t0x880A\n#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB\t0x880B\n#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB\t0x880C\n#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB\t0x880D\n#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB\t0x880E\n#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB\t0x880F\n#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB\t0x8810\n#define GL_MAX_TEXTURE_COORDS_ARB\t\t0x8871\n#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB\t\t0x8872\n\n#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB\t\t0x8242\n#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB\t\t0x9143\n#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB\t0x9144\n#define GL_DEBUG_LOGGED_MESSAGES_ARB\t\t0x9145\n#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB\t0x8243\n#define GL_DEBUG_CALLBACK_FUNCTION_ARB\t\t0x8244\n#define GL_DEBUG_CALLBACK_USER_PARAM_ARB\t0x8245\n#define GL_DEBUG_SOURCE_API_ARB\t\t0x8246\n#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB\t0x8247\n#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB\t0x8248\n#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB\t\t0x8249\n#define GL_DEBUG_SOURCE_APPLICATION_ARB\t\t0x824A\n#define GL_DEBUG_SOURCE_OTHER_ARB\t\t0x824B\n#define GL_DEBUG_TYPE_ERROR_ARB\t\t0x824C\n#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB\t0x824D\n#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB\t0x824E\n#define GL_DEBUG_TYPE_PORTABILITY_ARB\t\t0x824F\n#define GL_DEBUG_TYPE_PERFORMANCE_ARB\t\t0x8250\n#define GL_DEBUG_TYPE_OTHER_ARB\t\t0x8251\n#define GL_DEBUG_SEVERITY_HIGH_ARB\t\t0x9146\n#define GL_DEBUG_SEVERITY_MEDIUM_ARB\t\t0x9147\n#define GL_DEBUG_SEVERITY_LOW_ARB\t\t0x9148\n\n// GL Core additions\n#define GL_NUM_EXTENSIONS                 0x821D\n#define GL_MAP_WRITE_BIT 0x0002\n#define GL_MAP_COHERENT_BIT 0x0080\n#define GL_MAP_PERSISTENT_BIT 0x0040\n#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020\n#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008\n#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004\n#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010\n\n#define GL_MAJOR_VERSION 0x821B\n#define GL_MINOR_VERSION 0x821C\n\n#define WGL_CONTEXT_MAJOR_VERSION_ARB\t\t0x2091\n#define WGL_CONTEXT_MINOR_VERSION_ARB\t\t0x2092\n#define WGL_CONTEXT_LAYER_PLANE_ARB\t\t0x2093\n#define WGL_CONTEXT_FLAGS_ARB\t\t\t0x2094\n#define WGL_CONTEXT_DEBUG_BIT_ARB\t\t0x0001\n#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB\t0x0002\n#define WGL_CONTEXT_PROFILE_MASK_ARB\t\t0x9126\n#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB\t0x00000001\n#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB\t0x00000002\n#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT\t\t0x00000004\t/*WGL_CONTEXT_ES2_PROFILE_BIT_EXT*/\n#define ERROR_INVALID_VERSION_ARB\t\t0x2095\n#define ERROR_INVALID_PROFILE_ARB\t\t0x2096\n\n#define WGL_NUMBER_PIXEL_FORMATS_ARB\t\t0x2000\n#define WGL_DRAW_TO_WINDOW_ARB\t\t0x2001\n#define WGL_DRAW_TO_BITMAP_ARB\t\t0x2002\n#define WGL_ACCELERATION_ARB\t\t\t0x2003\n#define WGL_NEED_PALETTE_ARB\t\t\t0x2004\n#define WGL_NEED_SYSTEM_PALETTE_ARB\t\t0x2005\n#define WGL_SWAP_LAYER_BUFFERS_ARB\t\t0x2006\n#define WGL_SWAP_METHOD_ARB\t\t\t0x2007\n#define WGL_NUMBER_OVERLAYS_ARB\t\t0x2008\n#define WGL_NUMBER_UNDERLAYS_ARB\t\t0x2009\n#define WGL_TRANSPARENT_ARB\t\t\t0x200A\n#define WGL_TRANSPARENT_RED_VALUE_ARB\t\t0x2037\n#define WGL_TRANSPARENT_GREEN_VALUE_ARB\t\t0x2038\n#define WGL_TRANSPARENT_BLUE_VALUE_ARB\t\t0x2039\n#define WGL_TRANSPARENT_ALPHA_VALUE_ARB\t\t0x203A\n#define WGL_TRANSPARENT_INDEX_VALUE_ARB\t\t0x203B\n#define WGL_SHARE_DEPTH_ARB\t\t\t0x200C\n#define WGL_SHARE_STENCIL_ARB\t\t\t0x200D\n#define WGL_SHARE_ACCUM_ARB\t\t\t0x200E\n#define WGL_SUPPORT_GDI_ARB\t\t\t0x200F\n#define WGL_SUPPORT_OPENGL_ARB\t\t0x2010\n#define WGL_DOUBLE_BUFFER_ARB\t\t\t0x2011\n#define WGL_STEREO_ARB\t\t\t0x2012\n#define WGL_PIXEL_TYPE_ARB\t\t\t0x2013\n#define WGL_COLOR_BITS_ARB\t\t\t0x2014\n#define WGL_RED_BITS_ARB\t\t\t0x2015\n#define WGL_RED_SHIFT_ARB\t\t\t0x2016\n#define WGL_GREEN_BITS_ARB\t\t\t0x2017\n#define WGL_GREEN_SHIFT_ARB\t\t\t0x2018\n#define WGL_BLUE_BITS_ARB\t\t\t0x2019\n#define WGL_BLUE_SHIFT_ARB\t\t\t0x201A\n#define WGL_ALPHA_BITS_ARB\t\t\t0x201B\n#define WGL_ALPHA_SHIFT_ARB\t\t\t0x201C\n#define WGL_ACCUM_BITS_ARB\t\t\t0x201D\n#define WGL_ACCUM_RED_BITS_ARB\t\t0x201E\n#define WGL_ACCUM_GREEN_BITS_ARB\t\t0x201F\n#define WGL_ACCUM_BLUE_BITS_ARB\t\t0x2020\n#define WGL_ACCUM_ALPHA_BITS_ARB\t\t0x2021\n#define WGL_DEPTH_BITS_ARB\t\t\t0x2022\n#define WGL_STENCIL_BITS_ARB\t\t\t0x2023\n#define WGL_AUX_BUFFERS_ARB\t\t\t0x2024\n#define WGL_NO_ACCELERATION_ARB\t\t0x2025\n#define WGL_GENERIC_ACCELERATION_ARB\t\t0x2026\n#define WGL_FULL_ACCELERATION_ARB\t\t0x2027\n#define WGL_SWAP_EXCHANGE_ARB\t\t\t0x2028\n#define WGL_SWAP_COPY_ARB\t\t\t0x2029\n#define WGL_SWAP_UNDEFINED_ARB\t\t0x202A\n#define WGL_TYPE_RGBA_ARB\t\t\t0x202B\n#define WGL_TYPE_COLORINDEX_ARB\t\t0x202C\n\n#define WGL_SAMPLE_BUFFERS_ARB\t\t0x2041\n#define WGL_SAMPLES_ARB\t\t\t0x2042\n\n#ifdef __GNUC__\n\t#pragma GCC diagnostic push\n\t#pragma GCC diagnostic ignored \"-Wunused-variable\"\n#endif\n\n#if XASH_GL_STATIC && !REF_GL_KEEP_MANGLED_FUNCTIONS\n\t#define GL_FUNCTION( name ) APIENTRY name\n#elif XASH_GL_STATIC && REF_GL_KEEP_MANGLED_FUNCTIONS\n\t#define GL_FUNCTION( name ) APIENTRY p##name\n#else\n\t#define GL_FUNCTION( name ) (APIENTRY *p##name)\n#endif\n\n// helper opengl functions\nAPIENTRY_LINKAGE GLenum GL_FUNCTION( glGetError )(void);\nAPIENTRY_LINKAGE const GLubyte * GL_FUNCTION( glGetString )(GLenum name);\nAPIENTRY_LINKAGE const GLubyte * GL_FUNCTION( glGetStringi )(GLenum name, GLint i);\n\n// base gl functions\nAPIENTRY_LINKAGE void GL_FUNCTION( glAccum )(GLenum op, GLfloat value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glAlphaFunc )(GLenum func, GLclampf ref);\nAPIENTRY_LINKAGE void GL_FUNCTION( glArrayElement )(GLint i);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBegin )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindTexture )(GLenum target, GLuint texture);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBlendFunc )(GLenum sfactor, GLenum dfactor);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCallList )(GLuint list);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCallLists )(GLsizei n, GLenum type, const GLvoid *lists);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClear )(GLbitfield mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClearDepth )(GLclampd depth);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClearIndex )(GLfloat c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClearStencil )(GLint s);\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsEnabled )( GLenum cap );\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsList )( GLuint list );\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsTexture )( GLuint texture );\nAPIENTRY_LINKAGE void GL_FUNCTION( glClipPlane )(GLenum plane, const GLdouble *equation);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3b )(GLbyte red, GLbyte green, GLbyte blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3bv )(const GLbyte *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3d )(GLdouble red, GLdouble green, GLdouble blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3f )(GLfloat red, GLfloat green, GLfloat blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3i )(GLint red, GLint green, GLint blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3s )(GLshort red, GLshort green, GLshort blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3ub )(GLubyte red, GLubyte green, GLubyte blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3ubv )(const GLubyte *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3ui )(GLuint red, GLuint green, GLuint blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3uiv )(const GLuint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3us )(GLushort red, GLushort green, GLushort blue);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor3usv )(const GLushort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4bv )(const GLbyte *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4i )(GLint red, GLint green, GLint blue, GLint alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4ubv )(const GLubyte *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4uiv )(const GLuint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColor4usv )(const GLushort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColorMaterial )(GLenum face, GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCullFace )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteLists )(GLuint list, GLsizei range);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteTextures )(GLsizei n, const GLuint *textures);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDepthFunc )(GLenum func);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDepthMask )(GLboolean flag);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDepthRange )(GLclampd zNear, GLclampd zFar);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDisable )(GLenum cap);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDisableClientState )(GLenum array);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawArrays )(GLenum mode, GLint first, GLsizei count);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawBuffer )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEdgeFlag )(GLboolean flag);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEdgeFlagv )(const GLboolean *flag);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEnable )(GLenum cap);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEnableClientState )(GLenum array);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEnd )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEndList )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord1d )(GLdouble u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord1dv )(const GLdouble *u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord1f )(GLfloat u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord1fv )(const GLfloat *u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord2d )(GLdouble u, GLdouble v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord2dv )(const GLdouble *u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord2f )(GLfloat u, GLfloat v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalCoord2fv )(const GLfloat *u);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalMesh1 )(GLenum mode, GLint i1, GLint i2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalPoint1 )(GLint i);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEvalPoint2 )(GLint i, GLint j);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFinish )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFlush )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFogf )(GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFogfv )(GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFogi )(GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFogiv )(GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFrontFace )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenTextures )(GLsizei n, GLuint *textures);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetBooleanv )(GLenum pname, GLboolean *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetClipPlane )(GLenum plane, GLdouble *equation);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetDoublev )(GLenum pname, GLdouble *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetFloatv )(GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetIntegerv )(GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetLightfv )(GLenum light, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetLightiv )(GLenum light, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetMapdv )(GLenum target, GLenum query, GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetMapfv )(GLenum target, GLenum query, GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetMapiv )(GLenum target, GLenum query, GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetMaterialiv )(GLenum face, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetPixelMapfv )(GLenum map, GLfloat *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetPixelMapuiv )(GLenum map, GLuint *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetPixelMapusv )(GLenum map, GLushort *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetPointerv )(GLenum pname, GLvoid* *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetPolygonStipple )(GLubyte *mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexEnviv )(GLenum target, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexGeniv )(GLenum coord, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetTexParameteriv )(GLenum target, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glHint )(GLenum target, GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexMask )(GLuint mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexd )(GLdouble c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexdv )(const GLdouble *c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexf )(GLfloat c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexfv )(const GLfloat *c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexi )(GLint c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexiv )(const GLint *c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexs )(GLshort c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexsv )(const GLshort *c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexub )(GLubyte c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glIndexubv )(const GLubyte *c);\nAPIENTRY_LINKAGE void GL_FUNCTION( glInitNames )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightModelf )(GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightModelfv )(GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightModeli )(GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightModeliv )(GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightf )(GLenum light, GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightfv )(GLenum light, GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLighti )(GLenum light, GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLightiv )(GLenum light, GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLineStipple )(GLint factor, GLushort pattern);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLineWidth )(GLfloat width);\nAPIENTRY_LINKAGE void GL_FUNCTION( glListBase )(GLuint base);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLoadIdentity )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLoadMatrixd )(const GLdouble *m);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLoadMatrixf )(const GLfloat *m);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLoadName )(GLuint name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLogicOp )(GLenum opcode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMapGrid1d )(GLint un, GLdouble u1, GLdouble u2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMapGrid1f )(GLint un, GLfloat u1, GLfloat u2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMaterialf )(GLenum face, GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMaterialfv )(GLenum face, GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMateriali )(GLenum face, GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMaterialiv )(GLenum face, GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMatrixMode )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultMatrixd )(const GLdouble *m);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultMatrixf )(const GLfloat *m);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNewList )(GLuint list, GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3bv )(const GLbyte *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3i )(GLint nx, GLint ny, GLint nz);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3s )(GLshort nx, GLshort ny, GLshort nz);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormal3sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPassThrough )(GLfloat token);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelStoref )(GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelStorei )(GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelTransferf )(GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelTransferi )(GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPixelZoom )(GLfloat xfactor, GLfloat yfactor);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPointSize )(GLfloat size);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPolygonMode )(GLenum face, GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPolygonOffset )(GLfloat factor, GLfloat units);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPolygonStipple )(const GLubyte *mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPopAttrib )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPopClientAttrib )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPopMatrix )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPopName )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPushAttrib )(GLbitfield mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPushClientAttrib )(GLbitfield mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPushMatrix )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPushName )(GLuint name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2d )(GLdouble x, GLdouble y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2f )(GLfloat x, GLfloat y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2i )(GLint x, GLint y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2s )(GLshort x, GLshort y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos2sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3d )(GLdouble x, GLdouble y, GLdouble z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3f )(GLfloat x, GLfloat y, GLfloat z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3i )(GLint x, GLint y, GLint z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3s )(GLshort x, GLshort y, GLshort z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos3sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4i )(GLint x, GLint y, GLint z, GLint w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRasterPos4sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glReadBuffer )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectdv )(const GLdouble *v1, const GLdouble *v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectfv )(const GLfloat *v1, const GLfloat *v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRecti )(GLint x1, GLint y1, GLint x2, GLint y2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectiv )(const GLint *v1, const GLint *v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRectsv )(const GLshort *v1, const GLshort *v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glScaled )(GLdouble x, GLdouble y, GLdouble z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glScalef )(GLfloat x, GLfloat y, GLfloat z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glScissor )(GLint x, GLint y, GLsizei width, GLsizei height);\nAPIENTRY_LINKAGE void GL_FUNCTION( glSelectBuffer )(GLsizei size, GLuint *buffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glShadeModel )(GLenum mode);\nAPIENTRY_LINKAGE void GL_FUNCTION( glStencilFunc )(GLenum func, GLint ref, GLuint mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glStencilMask )(GLuint mask);\nAPIENTRY_LINKAGE void GL_FUNCTION( glStencilOp )(GLenum fail, GLenum zfail, GLenum zpass);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1d )(GLdouble s);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1f )(GLfloat s);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1i )(GLint s);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1s )(GLshort s);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord1sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2d )(GLdouble s, GLdouble t);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2f )(GLfloat s, GLfloat t);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2i )(GLint s, GLint t);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2s )(GLshort s, GLshort t);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord2sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3d )(GLdouble s, GLdouble t, GLdouble r);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3f )(GLfloat s, GLfloat t, GLfloat r);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3i )(GLint s, GLint t, GLint r);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3s )(GLshort s, GLshort t, GLshort r);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord3sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4i )(GLint s, GLint t, GLint r, GLint q);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoord4sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexEnvf )(GLenum target, GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexEnvi )(GLenum target, GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexEnviv )(GLenum target, GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGend )(GLenum coord, GLenum pname, GLdouble param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGendv )(GLenum coord, GLenum pname, const GLdouble *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGenf )(GLenum coord, GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGeni )(GLenum coord, GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexGeniv )(GLenum coord, GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexParameterf )(GLenum target, GLenum pname, GLfloat param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexParameteri )(GLenum target, GLenum pname, GLint param);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexParameteriv )(GLenum target, GLenum pname, const GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTranslated )(GLdouble x, GLdouble y, GLdouble z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTranslatef )(GLfloat x, GLfloat y, GLfloat z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2d )(GLdouble x, GLdouble y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2f )(GLfloat x, GLfloat y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2i )(GLint x, GLint y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2s )(GLshort x, GLshort y);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex2sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3d )(GLdouble x, GLdouble y, GLdouble z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3f )(GLfloat x, GLfloat y, GLfloat z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3i )(GLint x, GLint y, GLint z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3s )(GLshort x, GLshort y, GLshort z);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex3sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4dv )(const GLdouble *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4fv )(const GLfloat *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4i )(GLint x, GLint y, GLint z, GLint w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4iv )(const GLint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertex4sv )(const GLshort *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glViewport )(GLint x, GLint y, GLsizei width, GLsizei height);\nAPIENTRY_LINKAGE void GL_FUNCTION( glPointParameterfEXT )( GLenum param, GLfloat value );\nAPIENTRY_LINKAGE void GL_FUNCTION( glPointParameterfvEXT )( GLenum param, const GLfloat *value );\nAPIENTRY_LINKAGE void GL_FUNCTION( glLockArraysEXT ) (int , int);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUnlockArraysEXT ) (void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glActiveTextureARB )( GLenum );\nAPIENTRY_LINKAGE void GL_FUNCTION( glClientActiveTextureARB )( GLenum );\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetCompressedTexImage )( GLenum target, GLint lod, const GLvoid* data );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElements )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElementsEXT )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices );\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord1f) (GLenum, GLfloat);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord2f) (GLenum, GLfloat, GLfloat);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat);\nAPIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat);\nAPIENTRY_LINKAGE void GL_FUNCTION( glActiveTexture) (GLenum);\nAPIENTRY_LINKAGE void GL_FUNCTION( glClientActiveTexture) (GLenum);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexImage3DARB )(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexImage2DARB )(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border,  GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexImage1DARB )(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexSubImage3DARB )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexSubImage2DARB )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompressedTexSubImage1DARB )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteObjectARB )(GLhandleARB obj);\nAPIENTRY_LINKAGE GLhandleARB GL_FUNCTION( glGetHandleARB )(GLenum pname);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDetachObjectARB )(GLhandleARB containerObj, GLhandleARB attachedObj);\nAPIENTRY_LINKAGE GLhandleARB GL_FUNCTION( glCreateShaderObjectARB )(GLenum shaderType);\nAPIENTRY_LINKAGE void GL_FUNCTION( glShaderSourceARB )(GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length);\nAPIENTRY_LINKAGE void GL_FUNCTION( glCompileShaderARB )(GLhandleARB shaderObj);\nAPIENTRY_LINKAGE GLhandleARB GL_FUNCTION( glCreateProgramObjectARB )(void);\nAPIENTRY_LINKAGE void GL_FUNCTION( glAttachObjectARB )(GLhandleARB containerObj, GLhandleARB obj);\nAPIENTRY_LINKAGE void GL_FUNCTION( glLinkProgramARB )(GLhandleARB programObj);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUseProgramObjectARB )(GLhandleARB programObj);\nAPIENTRY_LINKAGE void GL_FUNCTION( glValidateProgramARB )(GLhandleARB programObj);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindProgramARB )(GLenum target, GLuint program);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteProgramsARB )(GLsizei n, const GLuint *programs);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenProgramsARB )(GLsizei n, GLuint *programs);\nAPIENTRY_LINKAGE void GL_FUNCTION( glProgramStringARB )(GLenum target, GLenum format, GLsizei len, const GLvoid *string);\nAPIENTRY_LINKAGE void GL_FUNCTION( glProgramEnvParameter4fARB )(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glProgramLocalParameter4fARB )(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform1fARB )(GLint location, GLfloat v0);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform2fARB )(GLint location, GLfloat v0, GLfloat v1);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform3fARB )(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform4fARB )(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform1iARB )(GLint location, GLint v0);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform2iARB )(GLint location, GLint v0, GLint v1);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform3iARB )(GLint location, GLint v0, GLint v1, GLint v2);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform4iARB )(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform1fvARB )(GLint location, GLsizei count, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform2fvARB )(GLint location, GLsizei count, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform3fvARB )(GLint location, GLsizei count, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform4fvARB )(GLint location, GLsizei count, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform1ivARB )(GLint location, GLsizei count, const GLint *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform2ivARB )(GLint location, GLsizei count, const GLint *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform3ivARB )(GLint location, GLsizei count, const GLint *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniform4ivARB )(GLint location, GLsizei count, const GLint *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniformMatrix2fvARB )(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniformMatrix3fvARB )(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glUniformMatrix4fvARB )(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetObjectParameterfvARB )(GLhandleARB obj, GLenum pname, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetObjectParameterivARB )(GLhandleARB obj, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetInfoLogARB )(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetAttachedObjectsARB )(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj);\nAPIENTRY_LINKAGE GLint GL_FUNCTION( glGetUniformLocationARB )(GLhandleARB programObj, const GLcharARB *name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetActiveUniformARB )(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetUniformfvARB )(GLhandleARB programObj, GLint location, GLfloat *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetUniformivARB )(GLhandleARB programObj, GLint location, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetShaderSourceARB )(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source);\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexImage3D )( GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels );\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexSubImage3D )( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels );\nAPIENTRY_LINKAGE void GL_FUNCTION( glCopyTexSubImage3D )( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height );\nAPIENTRY_LINKAGE void GL_FUNCTION( glBlendEquationEXT )(GLenum);\nAPIENTRY_LINKAGE void GL_FUNCTION( glStencilOpSeparate )(GLenum, GLenum, GLenum, GLenum);\nAPIENTRY_LINKAGE void GL_FUNCTION( glStencilFuncSeparate )(GLenum, GLenum, GLint, GLuint);\nAPIENTRY_LINKAGE void GL_FUNCTION( glActiveStencilFaceEXT )(GLenum);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertexAttribPointerARB )(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEnableVertexAttribArrayARB )(GLuint index);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDisableVertexAttribArrayARB )(GLuint index);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindAttribLocationARB )(GLhandleARB programObj, GLuint index, const GLcharARB *name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetActiveAttribARB )(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name);\nAPIENTRY_LINKAGE GLint GL_FUNCTION( glGetAttribLocationARB )(GLhandleARB programObj, const GLcharARB *name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindFragDataLocation )(GLuint programObj, GLuint index, const GLcharARB *name);\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertexAttrib2fARB )( GLuint index, GLfloat x, GLfloat y );\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertexAttrib2fvARB )( GLuint index, const GLfloat *v );\nAPIENTRY_LINKAGE void GL_FUNCTION( glVertexAttrib3fvARB )( GLuint index, const GLfloat *v );\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindBufferARB )(GLenum target, GLuint buffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteBuffersARB )(GLsizei n, const GLuint *buffers);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenBuffersARB )(GLsizei n, GLuint *buffers);\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsBufferARB )(GLuint buffer);\nAPIENTRY_LINKAGE GLvoid* GL_FUNCTION( glMapBufferARB )(GLenum target, GLenum access);\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glUnmapBufferARB )(GLenum target);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBufferDataARB )(GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBufferSubDataARB )(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenQueriesARB )(GLsizei n, GLuint *ids);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteQueriesARB )(GLsizei n, const GLuint *ids);\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsQueryARB )(GLuint id);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBeginQueryARB )(GLenum target, GLuint id);\nAPIENTRY_LINKAGE void GL_FUNCTION( glEndQueryARB )(GLenum target);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetQueryivARB )(GLenum target, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetQueryObjectivARB )(GLuint id, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetQueryObjectuivARB )(GLuint id, GLenum pname, GLuint *params);\ntypedef void ( APIENTRY *GL_DEBUG_PROC_ARB )( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLcharARB* message, GLvoid* userParam );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDebugMessageControlARB )( GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDebugMessageInsertARB )( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* buf );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDebugMessageCallbackARB )( GL_DEBUG_PROC_ARB callback, void* userParam );\nAPIENTRY_LINKAGE GLuint GL_FUNCTION( glGetDebugMessageLogARB )( GLuint count, GLsizei bufsize, GLenum* sources, GLenum* types, GLuint* ids, GLuint* severities, GLsizei* lengths, char* messageLog );\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsRenderbuffer )(GLuint renderbuffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindRenderbuffer )(GLenum target, GLuint renderbuffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteRenderbuffers )(GLsizei n, const GLuint *renderbuffers);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenRenderbuffers )(GLsizei n, GLuint *renderbuffers);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRenderbufferStorage )(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);\nAPIENTRY_LINKAGE void GL_FUNCTION( glRenderbufferStorageMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetRenderbufferParameteriv )(GLenum target, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsFramebuffer )(GLuint framebuffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindFramebuffer )(GLenum target, GLuint framebuffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteFramebuffers )(GLsizei n, const GLuint *framebuffers);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenFramebuffers )(GLsizei n, GLuint *framebuffers);\nAPIENTRY_LINKAGE GLenum GL_FUNCTION( glCheckFramebufferStatus )(GLenum target);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFramebufferTexture1D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFramebufferTexture2D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFramebufferTexture3D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFramebufferTextureLayer )(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFramebufferRenderbuffer )(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetFramebufferAttachmentParameteriv )(GLenum target, GLenum attachment, GLenum pname, GLint *params);\nAPIENTRY_LINKAGE void GL_FUNCTION( glBlitFramebuffer )(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawBuffersARB )( GLsizei n, const GLenum *bufs );\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenerateMipmap )( GLenum target );\nAPIENTRY_LINKAGE void GL_FUNCTION( glBindVertexArray )( GLuint array );\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteVertexArrays )( GLsizei n, const GLuint *arrays );\nAPIENTRY_LINKAGE void GL_FUNCTION( glGenVertexArrays )( GLsizei n, const GLuint *arrays );\nAPIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsVertexArray )( GLuint array );\nAPIENTRY_LINKAGE void GL_FUNCTION( glSwapInterval ) ( int interval );\n\n// arb shaders change in core\nAPIENTRY_LINKAGE void GL_FUNCTION( glDeleteProgram )(GLuint program);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetProgramiv )(GLuint program, GLenum e, GLuint *v);\nAPIENTRY_LINKAGE void GL_FUNCTION( glGetProgramInfoLog )(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog);\n\n// gl2shim deps\nAPIENTRY_LINKAGE void GL_FUNCTION( glBufferStorage )( GLenum target,  GLsizei size, const GLvoid * data, GLbitfield flags);\nAPIENTRY_LINKAGE void GL_FUNCTION( glFlushMappedBufferRange )(GLenum target, GLsizei offset, GLsizei length);\nAPIENTRY_LINKAGE void *GL_FUNCTION( glMapBufferRange )(GLenum target, GLsizei offset, GLsizei length, GLbitfield access);\nAPIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElementsBaseVertex )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLuint vertex );\n\n#if !XASH_GL_STATIC || ( !XASH_GLES && !XASH_GL4ES )\nAPIENTRY_LINKAGE void GL_FUNCTION( glTexImage2DMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);\n#endif /* !XASH_GLES && !XASH_GL4ES */\n\n#if XASH_GL_STATIC && !REF_GL_KEEP_MANGLED_FUNCTIONS\n#define pglGetError glGetError\n#define pglGetString glGetString\n#define pglAccum glAccum\n#define pglAlphaFunc glAlphaFunc\n#define pglArrayElement glArrayElement\n#define pglBegin glBegin\n#define pglBindTexture glBindTexture\n#define pglBitmap glBitmap\n#define pglBlendFunc glBlendFunc\n#define pglCallList glCallList\n#define pglCallLists glCallLists\n#define pglClear glClear\n#define pglClearAccum glClearAccum\n#define pglClearColor glClearColor\n#define pglClearDepth glClearDepth\n#define pglClearIndex glClearIndex\n#define pglClearStencil glClearStencil\n#define pglIsEnabled glIsEnabled\n#define pglIsList glIsList\n#define pglIsTexture glIsTexture\n#define pglClipPlane glClipPlane\n#define pglColor3b glColor3b\n#define pglColor3bv glColor3bv\n#define pglColor3d glColor3d\n#define pglColor3dv glColor3dv\n#define pglColor3f glColor3f\n#define pglColor3fv glColor3fv\n#define pglColor3i glColor3i\n#define pglColor3iv glColor3iv\n#define pglColor3s glColor3s\n#define pglColor3sv glColor3sv\n#define pglColor3ub glColor3ub\n#define pglColor3ubv glColor3ubv\n#define pglColor3ui glColor3ui\n#define pglColor3uiv glColor3uiv\n#define pglColor3us glColor3us\n#define pglColor3usv glColor3usv\n#define pglColor4b glColor4b\n#define pglColor4bv glColor4bv\n#define pglColor4d glColor4d\n#define pglColor4dv glColor4dv\n#define pglColor4f glColor4f\n#define pglColor4fv glColor4fv\n#define pglColor4i glColor4i\n#define pglColor4iv glColor4iv\n#define pglColor4s glColor4s\n#define pglColor4sv glColor4sv\n#define pglColor4ub glColor4ub\n#define pglColor4ubv glColor4ubv\n#define pglColor4ui glColor4ui\n#define pglColor4uiv glColor4uiv\n#define pglColor4us glColor4us\n#define pglColor4usv glColor4usv\n#define pglColorMask glColorMask\n#define pglColorMaterial glColorMaterial\n#define pglColorPointer glColorPointer\n#define pglCopyPixels glCopyPixels\n#define pglCopyTexImage1D glCopyTexImage1D\n#define pglCopyTexImage2D glCopyTexImage2D\n#define pglCopyTexSubImage1D glCopyTexSubImage1D\n#define pglCopyTexSubImage2D glCopyTexSubImage2D\n#define pglCullFace glCullFace\n#define pglDeleteLists glDeleteLists\n#define pglDeleteTextures glDeleteTextures\n#define pglDepthFunc glDepthFunc\n#define pglDepthMask glDepthMask\n#define pglDepthRange glDepthRange\n#define pglDisable glDisable\n#define pglDisableClientState glDisableClientState\n#define pglDrawArrays glDrawArrays\n#define pglDrawBuffer glDrawBuffer\n#define pglDrawElements glDrawElements\n#define pglDrawPixels glDrawPixels\n#define pglEdgeFlag glEdgeFlag\n#define pglEdgeFlagPointer glEdgeFlagPointer\n#define pglEdgeFlagv glEdgeFlagv\n#define pglEnable glEnable\n#define pglEnableClientState glEnableClientState\n#define pglEnd glEnd\n#define pglEndList glEndList\n#define pglEvalCoord1d glEvalCoord1d\n#define pglEvalCoord1dv glEvalCoord1dv\n#define pglEvalCoord1f glEvalCoord1f\n#define pglEvalCoord1fv glEvalCoord1fv\n#define pglEvalCoord2d glEvalCoord2d\n#define pglEvalCoord2dv glEvalCoord2dv\n#define pglEvalCoord2f glEvalCoord2f\n#define pglEvalCoord2fv glEvalCoord2fv\n#define pglEvalMesh1 glEvalMesh1\n#define pglEvalMesh2 glEvalMesh2\n#define pglEvalPoint1 glEvalPoint1\n#define pglEvalPoint2 glEvalPoint2\n#define pglFeedbackBuffer glFeedbackBuffer\n#define pglFinish glFinish\n#define pglFlush glFlush\n#define pglFogf glFogf\n#define pglFogfv glFogfv\n#define pglFogi glFogi\n#define pglFogiv glFogiv\n#define pglFrontFace glFrontFace\n#define pglFrustum glFrustum\n#define pglGenTextures glGenTextures\n#define pglGetBooleanv glGetBooleanv\n#define pglGetClipPlane glGetClipPlane\n#define pglGetDoublev glGetDoublev\n#define pglGetFloatv glGetFloatv\n#define pglGetIntegerv glGetIntegerv\n#define pglGetLightfv glGetLightfv\n#define pglGetLightiv glGetLightiv\n#define pglGetMapdv glGetMapdv\n#define pglGetMapfv glGetMapfv\n#define pglGetMapiv glGetMapiv\n#define pglGetMaterialfv glGetMaterialfv\n#define pglGetMaterialiv glGetMaterialiv\n#define pglGetPixelMapfv glGetPixelMapfv\n#define pglGetPixelMapuiv glGetPixelMapuiv\n#define pglGetPixelMapusv glGetPixelMapusv\n#define pglGetPointerv glGetPointerv\n#define pglGetPolygonStipple glGetPolygonStipple\n#define pglGetTexEnvfv glGetTexEnvfv\n#define pglGetTexEnviv glGetTexEnviv\n#define pglGetTexGendv glGetTexGendv\n#define pglGetTexGenfv glGetTexGenfv\n#define pglGetTexGeniv glGetTexGeniv\n#define pglGetTexImage glGetTexImage\n#define pglGetTexLevelParameterfv glGetTexLevelParameterfv\n#define pglGetTexLevelParameteriv glGetTexLevelParameteriv\n#define pglGetTexParameterfv glGetTexParameterfv\n#define pglGetTexParameteriv glGetTexParameteriv\n#define pglHint glHint\n#define pglIndexMask glIndexMask\n#define pglIndexPointer glIndexPointer\n#define pglIndexd glIndexd\n#define pglIndexdv glIndexdv\n#define pglIndexf glIndexf\n#define pglIndexfv glIndexfv\n#define pglIndexi glIndexi\n#define pglIndexiv glIndexiv\n#define pglIndexs glIndexs\n#define pglIndexsv glIndexsv\n#define pglIndexub glIndexub\n#define pglIndexubv glIndexubv\n#define pglInitNames glInitNames\n#define pglInterleavedArrays glInterleavedArrays\n#define pglLightModelf glLightModelf\n#define pglLightModelfv glLightModelfv\n#define pglLightModeli glLightModeli\n#define pglLightModeliv glLightModeliv\n#define pglLightf glLightf\n#define pglLightfv glLightfv\n#define pglLighti glLighti\n#define pglLightiv glLightiv\n#define pglLineStipple glLineStipple\n#define pglLineWidth glLineWidth\n#define pglListBase glListBase\n#define pglLoadIdentity glLoadIdentity\n#define pglLoadMatrixd glLoadMatrixd\n#define pglLoadMatrixf glLoadMatrixf\n#define pglLoadName glLoadName\n#define pglLogicOp glLogicOp\n#define pglMap1d glMap1d\n#define pglMap1f glMap1f\n#define pglMap2d glMap2d\n#define pglMap2f glMap2f\n#define pglMapGrid1d glMapGrid1d\n#define pglMapGrid1f glMapGrid1f\n#define pglMapGrid2d glMapGrid2d\n#define pglMapGrid2f glMapGrid2f\n#define pglMaterialf glMaterialf\n#define pglMaterialfv glMaterialfv\n#define pglMateriali glMateriali\n#define pglMaterialiv glMaterialiv\n#define pglMatrixMode glMatrixMode\n#define pglMultMatrixd glMultMatrixd\n#define pglMultMatrixf glMultMatrixf\n#define pglNewList glNewList\n#define pglNormal3b glNormal3b\n#define pglNormal3bv glNormal3bv\n#define pglNormal3d glNormal3d\n#define pglNormal3dv glNormal3dv\n#define pglNormal3f glNormal3f\n#define pglNormal3fv glNormal3fv\n#define pglNormal3i glNormal3i\n#define pglNormal3iv glNormal3iv\n#define pglNormal3s glNormal3s\n#define pglNormal3sv glNormal3sv\n#define pglNormalPointer glNormalPointer\n#define pglOrtho glOrtho\n#define pglPassThrough glPassThrough\n#define pglPixelMapfv glPixelMapfv\n#define pglPixelMapuiv glPixelMapuiv\n#define pglPixelMapusv glPixelMapusv\n#define pglPixelStoref glPixelStoref\n#define pglPixelStorei glPixelStorei\n#define pglPixelTransferf glPixelTransferf\n#define pglPixelTransferi glPixelTransferi\n#define pglPixelZoom glPixelZoom\n#define pglPointSize glPointSize\n#define pglPolygonMode glPolygonMode\n#define pglPolygonOffset glPolygonOffset\n#define pglPolygonStipple glPolygonStipple\n#define pglPopAttrib glPopAttrib\n#define pglPopClientAttrib glPopClientAttrib\n#define pglPopMatrix glPopMatrix\n#define pglPopName glPopName\n#define pglPushAttrib glPushAttrib\n#define pglPushClientAttrib glPushClientAttrib\n#define pglPushMatrix glPushMatrix\n#define pglPushName glPushName\n#define pglRasterPos2d glRasterPos2d\n#define pglRasterPos2dv glRasterPos2dv\n#define pglRasterPos2f glRasterPos2f\n#define pglRasterPos2fv glRasterPos2fv\n#define pglRasterPos2i glRasterPos2i\n#define pglRasterPos2iv glRasterPos2iv\n#define pglRasterPos2s glRasterPos2s\n#define pglRasterPos2sv glRasterPos2sv\n#define pglRasterPos3d glRasterPos3d\n#define pglRasterPos3dv glRasterPos3dv\n#define pglRasterPos3f glRasterPos3f\n#define pglRasterPos3fv glRasterPos3fv\n#define pglRasterPos3i glRasterPos3i\n#define pglRasterPos3iv glRasterPos3iv\n#define pglRasterPos3s glRasterPos3s\n#define pglRasterPos3sv glRasterPos3sv\n#define pglRasterPos4d glRasterPos4d\n#define pglRasterPos4dv glRasterPos4dv\n#define pglRasterPos4f glRasterPos4f\n#define pglRasterPos4fv glRasterPos4fv\n#define pglRasterPos4i glRasterPos4i\n#define pglRasterPos4iv glRasterPos4iv\n#define pglRasterPos4s glRasterPos4s\n#define pglRasterPos4sv glRasterPos4sv\n#define pglReadBuffer glReadBuffer\n#define pglReadPixels glReadPixels\n#define pglRectd glRectd\n#define pglRectdv glRectdv\n#define pglRectf glRectf\n#define pglRectfv glRectfv\n#define pglRecti glRecti\n#define pglRectiv glRectiv\n#define pglRects glRects\n#define pglRectsv glRectsv\n#define pglRotated glRotated\n#define pglRotatef glRotatef\n#define pglScaled glScaled\n#define pglScalef glScalef\n#define pglScissor glScissor\n#define pglSelectBuffer glSelectBuffer\n#define pglShadeModel glShadeModel\n#define pglStencilFunc glStencilFunc\n#define pglStencilMask glStencilMask\n#define pglStencilOp glStencilOp\n#define pglTexCoord1d glTexCoord1d\n#define pglTexCoord1dv glTexCoord1dv\n#define pglTexCoord1f glTexCoord1f\n#define pglTexCoord1fv glTexCoord1fv\n#define pglTexCoord1i glTexCoord1i\n#define pglTexCoord1iv glTexCoord1iv\n#define pglTexCoord1s glTexCoord1s\n#define pglTexCoord1sv glTexCoord1sv\n#define pglTexCoord2d glTexCoord2d\n#define pglTexCoord2dv glTexCoord2dv\n#define pglTexCoord2f glTexCoord2f\n#define pglTexCoord2fv glTexCoord2fv\n#define pglTexCoord2i glTexCoord2i\n#define pglTexCoord2iv glTexCoord2iv\n#define pglTexCoord2s glTexCoord2s\n#define pglTexCoord2sv glTexCoord2sv\n#define pglTexCoord3d glTexCoord3d\n#define pglTexCoord3dv glTexCoord3dv\n#define pglTexCoord3f glTexCoord3f\n#define pglTexCoord3fv glTexCoord3fv\n#define pglTexCoord3i glTexCoord3i\n#define pglTexCoord3iv glTexCoord3iv\n#define pglTexCoord3s glTexCoord3s\n#define pglTexCoord3sv glTexCoord3sv\n#define pglTexCoord4d glTexCoord4d\n#define pglTexCoord4dv glTexCoord4dv\n#define pglTexCoord4f glTexCoord4f\n#define pglTexCoord4fv glTexCoord4fv\n#define pglTexCoord4i glTexCoord4i\n#define pglTexCoord4iv glTexCoord4iv\n#define pglTexCoord4s glTexCoord4s\n#define pglTexCoord4sv glTexCoord4sv\n#define pglTexCoordPointer glTexCoordPointer\n#define pglTexEnvf glTexEnvf\n#define pglTexEnvfv glTexEnvfv\n#define pglTexEnvi glTexEnvi\n#define pglTexEnviv glTexEnviv\n#define pglTexGend glTexGend\n#define pglTexGendv glTexGendv\n#define pglTexGenf glTexGenf\n#define pglTexGenfv glTexGenfv\n#define pglTexGeni glTexGeni\n#define pglTexGeniv glTexGeniv\n#define pglTexImage1D glTexImage1D\n#define pglTexImage2D glTexImage2D\n#define pglTexImage2DMultisample glTexImage2DMultisample\n#define pglTexParameterf glTexParameterf\n#define pglTexParameterfv glTexParameterfv\n#define pglTexParameteri glTexParameteri\n#define pglTexParameteriv glTexParameteriv\n#define pglTexSubImage1D glTexSubImage1D\n#define pglTexSubImage2D glTexSubImage2D\n#define pglTranslated glTranslated\n#define pglTranslatef glTranslatef\n#define pglVertex2d glVertex2d\n#define pglVertex2dv glVertex2dv\n#define pglVertex2f glVertex2f\n#define pglVertex2fv glVertex2fv\n#define pglVertex2i glVertex2i\n#define pglVertex2iv glVertex2iv\n#define pglVertex2s glVertex2s\n#define pglVertex2sv glVertex2sv\n#define pglVertex3d glVertex3d\n#define pglVertex3dv glVertex3dv\n#define pglVertex3f glVertex3f\n#define pglVertex3fv glVertex3fv\n#define pglVertex3i glVertex3i\n#define pglVertex3iv glVertex3iv\n#define pglVertex3s glVertex3s\n#define pglVertex3sv glVertex3sv\n#define pglVertex4d glVertex4d\n#define pglVertex4dv glVertex4dv\n#define pglVertex4f glVertex4f\n#define pglVertex4fv glVertex4fv\n#define pglVertex4i glVertex4i\n#define pglVertex4iv glVertex4iv\n#define pglVertex4s glVertex4s\n#define pglVertex4sv glVertex4sv\n#define pglVertexPointer glVertexPointer\n#define pglViewport glViewport\n#define pglPointParameterfEXT glPointParameterfEXT\n#define pglPointParameterfvEXT glPointParameterfvEXT\n#define pglLockArraysEXT glLockArraysEXT\n#define pglUnlockArraysEXT glUnlockArraysEXT\n#define pglActiveTextureARB glActiveTextureARB\n#define pglClientActiveTextureARB glClientActiveTextureARB\n#define pglGetCompressedTexImage glGetCompressedTexImage\n#define pglDrawRangeElements glDrawRangeElements\n#define pglDrawRangeElementsEXT glDrawRangeElementsEXT\n#define pglMultiTexCoord1f glMultiTexCoord1f\n#define pglMultiTexCoord2f glMultiTexCoord2f\n#define pglMultiTexCoord3f glMultiTexCoord3f\n#define pglMultiTexCoord4f glMultiTexCoord4f\n#define pglActiveTexture glActiveTexture\n#define pglClientActiveTexture glClientActiveTexture\n#define pglCompressedTexImage3DARB glCompressedTexImage3DARB\n#define pglCompressedTexImage2DARB glCompressedTexImage2DARB\n#define pglCompressedTexImage1DARB glCompressedTexImage1DARB\n#define pglCompressedTexSubImage3DARB glCompressedTexSubImage3DARB\n#define pglCompressedTexSubImage2DARB glCompressedTexSubImage2DARB\n#define pglCompressedTexSubImage1DARB glCompressedTexSubImage1DARB\n#define pglDeleteObjectARB glDeleteObjectARB\n#define pglGetHandleARB glGetHandleARB\n#define pglDetachObjectARB glDetachObjectARB\n#define pglCreateShaderObjectARB glCreateShaderObjectARB\n#define pglShaderSourceARB glShaderSourceARB\n#define pglCompileShaderARB glCompileShaderARB\n#define pglCreateProgramObjectARB glCreateProgramObjectARB\n#define pglAttachObjectARB glAttachObjectARB\n#define pglLinkProgramARB glLinkProgramARB\n#define pglUseProgramObjectARB glUseProgramObjectARB\n#define pglValidateProgramARB glValidateProgramARB\n#define pglBindProgramARB glBindProgramARB\n#define pglDeleteProgramsARB glDeleteProgramsARB\n#define pglGenProgramsARB glGenProgramsARB\n#define pglProgramStringARB glProgramStringARB\n#define pglProgramEnvParameter4fARB glProgramEnvParameter4fARB\n#define pglProgramLocalParameter4fARB glProgramLocalParameter4fARB\n#define pglUniform1fARB glUniform1fARB\n#define pglUniform2fARB glUniform2fARB\n#define pglUniform3fARB glUniform3fARB\n#define pglUniform4fARB glUniform4fARB\n#define pglUniform1iARB glUniform1iARB\n#define pglUniform2iARB glUniform2iARB\n#define pglUniform3iARB glUniform3iARB\n#define pglUniform4iARB glUniform4iARB\n#define pglUniform1fvARB glUniform1fvARB\n#define pglUniform2fvARB glUniform2fvARB\n#define pglUniform3fvARB glUniform3fvARB\n#define pglUniform4fvARB glUniform4fvARB\n#define pglUniform1ivARB glUniform1ivARB\n#define pglUniform2ivARB glUniform2ivARB\n#define pglUniform3ivARB glUniform3ivARB\n#define pglUniform4ivARB glUniform4ivARB\n#define pglUniformMatrix2fvARB glUniformMatrix2fvARB\n#define pglUniformMatrix3fvARB glUniformMatrix3fvARB\n#define pglUniformMatrix4fvARB glUniformMatrix4fvARB\n#define pglGetObjectParameterfvARB glGetObjectParameterfvARB\n#define pglGetObjectParameterivARB glGetObjectParameterivARB\n#define pglGetInfoLogARB glGetInfoLogARB\n#define pglGetAttachedObjectsARB glGetAttachedObjectsARB\n#define pglGetUniformLocationARB glGetUniformLocationARB\n#define pglGetActiveUniformARB glGetActiveUniformARB\n#define pglGetUniformfvARB glGetUniformfvARB\n#define pglGetUniformivARB glGetUniformivARB\n#define pglGetShaderSourceARB glGetShaderSourceARB\n#define pglTexImage3D glTexImage3D\n#define pglTexSubImage3D glTexSubImage3D\n#define pglCopyTexSubImage3D glCopyTexSubImage3D\n#define pglBlendEquationEXT glBlendEquationEXT\n#define pglStencilOpSeparate glStencilOpSeparate\n#define pglStencilFuncSeparate glStencilFuncSeparate\n#define pglActiveStencilFaceEXT glActiveStencilFaceEXT\n#define pglVertexAttribPointerARB glVertexAttribPointerARB\n#define pglEnableVertexAttribArrayARB glEnableVertexAttribArrayARB\n#define pglDisableVertexAttribArrayARB glDisableVertexAttribArrayARB\n#define pglBindAttribLocationARB glBindAttribLocationARB\n#define pglGetActiveAttribARB glGetActiveAttribARB\n#define pglGetAttribLocationARB glGetAttribLocationARB\n#define pglBindFragDataLocation glBindFragDataLocation\n#define pglVertexAttrib2fARB glVertexAttrib2fARB\n#define pglVertexAttrib2fvARB glVertexAttrib2fvARB\n#define pglVertexAttrib3fvARB glVertexAttrib3fvARB\n#define pglBindBufferARB glBindBufferARB\n#define pglDeleteBuffersARB glDeleteBuffersARB\n#define pglGenBuffersARB glGenBuffersARB\n#define pglIsBufferARB glIsBufferARB\n#define pglMapBufferARB glMapBufferARB\n#define pglUnmapBufferARB glUnmapBufferARB\n#define pglBufferDataARB glBufferDataARB\n#define pglBufferSubDataARB glBufferSubDataARB\n#define pglGenQueriesARB glGenQueriesARB\n#define pglDeleteQueriesARB glDeleteQueriesARB\n#define pglIsQueryARB glIsQueryARB\n#define pglBeginQueryARB glBeginQueryARB\n#define pglEndQueryARB glEndQueryARB\n#define pglGetQueryivARB glGetQueryivARB\n#define pglGetQueryObjectivARB glGetQueryObjectivARB\n#define pglGetQueryObjectuivARB glGetQueryObjectuivARB\n#define pglDebugMessageControlARB glDebugMessageControlARB\n#define pglDebugMessageInsertARB glDebugMessageInsertARB\n#define pglDebugMessageCallbackARB glDebugMessageCallbackARB\n#define pglGetDebugMessageLogARB glGetDebugMessageLogARB\n#define pglIsRenderbuffer glIsRenderbuffer\n#define pglBindRenderbuffer glBindRenderbuffer\n#define pglDeleteRenderbuffers glDeleteRenderbuffers\n#define pglGenRenderbuffers glGenRenderbuffers\n#define pglRenderbufferStorage glRenderbufferStorage\n#define pglRenderbufferStorageMultisample glRenderbufferStorageMultisample\n#define pglGetRenderbufferParameteriv glGetRenderbufferParameteriv\n#define pglIsFramebuffer glIsFramebuffer\n#define pglBindFramebuffer glBindFramebuffer\n#define pglDeleteFramebuffers glDeleteFramebuffers\n#define pglGenFramebuffers glGenFramebuffers\n#define pglCheckFramebufferStatus glCheckFramebufferStatus\n#define pglFramebufferTexture1D glFramebufferTexture1D\n#define pglFramebufferTexture2D glFramebufferTexture2D\n#define pglFramebufferTexture3D glFramebufferTexture3D\n#define pglFramebufferTextureLayer glFramebufferTextureLayer\n#define pglFramebufferRenderbuffer glFramebufferRenderbuffer\n#define pglGetFramebufferAttachmentParameteriv glGetFramebufferAttachmentParameteriv\n#define pglBlitFramebuffer glBlitFramebuffer\n#define pglDrawBuffersARB glDrawBuffersARB\n#define pglGenerateMipmap glGenerateMipmap\n#define pglBindVertexArray glBindVertexArray\n#define pglDeleteVertexArrays glDeleteVertexArrays\n#define pglGenVertexArrays glGenVertexArrays\n#define pglIsVertexArray glIsVertexArray\n#define pglSwapInterval glSwapInterval\n#endif\n\n#ifdef __GNUC__\n\t#pragma GCC diagnostic pop\n#endif\n\n#endif//GL_EXPORT_H\n"
  },
  {
    "path": "ref/gl/gl_frustum.c",
    "content": "/*\ngl_frustum.cpp - frustum test implementation\nCopyright (C) 2016 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n\nstatic void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist )\n{\n\tAssert( side >= 0 && side < FRUSTUM_PLANES );\n\n\tout->planes[side].type = PlaneTypeForNormal( vecNormal );\n\tout->planes[side].signbits = SignbitsForPlane( vecNormal );\n\tVectorCopy( vecNormal, out->planes[side].normal );\n\tout->planes[side].dist = flDist;\n\n\tSetBits( out->clipFlags, BIT( side ));\n}\n\nvoid GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY )\n{\n\tfloat\txs, xc;\n\tvec3_t\tfarpoint, nearpoint;\n\tvec3_t\tnormal, iforward;\n\n\t// horizontal fov used for left and right planes\n\tSinCos( DEG2RAD( flFovX ) * 0.5f, &xs, &xc );\n\n\t// setup left plane\n\tVectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vright, normal );\n\tGL_FrustumSetPlane( out, FRUSTUM_LEFT, normal, DotProduct( RI.cullorigin, normal ));\n\n\t// setup right plane\n\tVectorMAM( xs, RI.cull_vforward, xc, RI.cull_vright, normal );\n\tGL_FrustumSetPlane( out, FRUSTUM_RIGHT, normal, DotProduct( RI.cullorigin, normal ));\n\n\t// vertical fov used for top and bottom planes\n\tSinCos( DEG2RAD( flFovY ) * 0.5f, &xs, &xc );\n\tVectorNegate( RI.cull_vforward, iforward );\n\n\t// setup bottom plane\n\tVectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vup, normal );\n\tGL_FrustumSetPlane( out, FRUSTUM_BOTTOM, normal, DotProduct( RI.cullorigin, normal ));\n\n\t// setup top plane\n\tVectorMAM( xs, RI.cull_vforward, xc, RI.cull_vup, normal );\n\tGL_FrustumSetPlane( out, FRUSTUM_TOP, normal, DotProduct( RI.cullorigin, normal ));\n\n\t// setup far plane\n\tVectorMA( RI.cullorigin, flZFar, RI.cull_vforward, farpoint );\n\tGL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, DotProduct( iforward, farpoint ));\n\n\t// no need to setup backplane for general view.\n\tif( flZNear == 0.0f ) return;\n\n\t// setup near plane\n\tVectorMA( RI.cullorigin, flZNear, RI.cull_vforward, nearpoint );\n\tGL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, DotProduct( RI.cull_vforward, nearpoint ));\n}\n\nvoid GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar )\n{\n\tvec3_t\tiforward, iright, iup;\n\n\t// setup the near and far planes\n\tfloat orgOffset = DotProduct( RI.cullorigin, RI.cull_vforward );\n\tVectorNegate( RI.cull_vforward, iforward );\n\n\t// because quake ortho is inverted and far and near should be swaped\n\tGL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, -flZNear - orgOffset );\n\tGL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, flZFar + orgOffset );\n\n\t// setup left and right planes\n\torgOffset = DotProduct( RI.cullorigin, RI.cull_vright );\n\tVectorNegate( RI.cull_vright, iright );\n\n\tGL_FrustumSetPlane( out, FRUSTUM_LEFT, RI.cull_vright, xLeft + orgOffset );\n\tGL_FrustumSetPlane( out, FRUSTUM_RIGHT, iright, -xRight - orgOffset );\n\n\t// setup top and buttom planes\n\torgOffset = DotProduct( RI.cullorigin, RI.cull_vup );\n\tVectorNegate( RI.cull_vup, iup );\n\n\tGL_FrustumSetPlane( out, FRUSTUM_TOP, RI.cull_vup, yTop + orgOffset );\n\tGL_FrustumSetPlane( out, FRUSTUM_BOTTOM, iup, -yBottom - orgOffset );\n}\n\n// cull methods\nqboolean GL_FrustumCullBox( const gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags )\n{\n\tint\tiClipFlags;\n\tint\ti, bit;\n\n\tif( r_nocull.value )\n\t\treturn false;\n\n\tif( userClipFlags != 0 )\n\t\tiClipFlags = userClipFlags;\n\telse iClipFlags = out->clipFlags;\n\n\tfor( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 )\n\t{\n\t\tconst mplane_t\t*p = &out->planes[FRUSTUM_PLANES - i];\n\n\t\tif( !FBitSet( iClipFlags, bit ))\n\t\t\tcontinue;\n\n\t\tswitch( p->signbits )\n\t\t{\n\t\tcase 0:\n\t\t\tif( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\tif( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tif( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tif( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tif( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tif( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tif( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tif( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist )\n\t\t\t\treturn true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nqboolean GL_FrustumCullSphere( const gl_frustum_t *out, const vec3_t center, float radius, int userClipFlags )\n{\n\tint\tiClipFlags;\n\tint\ti, bit;\n\n\tif( r_nocull.value )\n\t\treturn false;\n\n\tif( userClipFlags != 0 )\n\t\tiClipFlags = userClipFlags;\n\telse iClipFlags = out->clipFlags;\n\n\tfor( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 )\n\t{\n\t\tconst mplane_t *p = &out->planes[FRUSTUM_PLANES - i];\n\n\t\tif( !FBitSet( iClipFlags, bit ))\n\t\t\tcontinue;\n\n\t\tif( DotProduct( center, p->normal ) - p->dist <= -radius )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n"
  },
  {
    "path": "ref/gl/gl_frustum.h",
    "content": "/*\ngl_frustum.cpp - frustum test implementation\nCopyright (C) 2016 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GL_FRUSTUM_H\n#define GL_FRUSTUM_H\n\n// don't change this order\n#define FRUSTUM_LEFT\t0\n#define FRUSTUM_RIGHT\t1\n#define FRUSTUM_BOTTOM\t2\n#define FRUSTUM_TOP\t\t3\n#define FRUSTUM_FAR\t\t4\n#define FRUSTUM_NEAR\t5\n#define FRUSTUM_PLANES\t6\n\ntypedef struct gl_frustum_s\n{\n\tmplane_t\t\tplanes[FRUSTUM_PLANES];\n\tunsigned int \tclipFlags;\n} gl_frustum_t;\n\nvoid GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY );\nvoid GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar );\n\n// cull methods\nqboolean GL_FrustumCullBox( const gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags );\nqboolean GL_FrustumCullSphere( const gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags );\n\n#endif//GL_FRUSTUM_H\n"
  },
  {
    "path": "ref/gl/gl_image.c",
    "content": "/*\ngl_image.c - texture uploading and processing\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdarg.h>\n#include \"gl_local.h\"\n#include \"crclib.h\"\n\n#define TEXTURES_HASH_SIZE\t(MAX_TEXTURES >> 2)\n\nstatic gl_texture_t\t\tgl_textures[MAX_TEXTURES];\nstatic gl_texture_t*\tgl_texturesHashTable[TEXTURES_HASH_SIZE];\nstatic uint\t\tgl_numTextures;\n\nstatic byte    dottexture[8][8] =\n{\n\t  {0,1,1,0,0,0,0,0},\n\t  {1,1,1,1,0,0,0,0},\n\t  {1,1,1,1,0,0,0,0},\n\t  {0,1,1,0,0,0,0,0},\n\t  {0,0,0,0,0,0,0,0},\n\t  {0,0,0,0,0,0,0,0},\n\t  {0,0,0,0,0,0,0,0},\n\t  {0,0,0,0,0,0,0,0},\n};\n\n\n#define IsLightMap( tex )\t( FBitSet(( tex )->flags, TF_ATLAS_PAGE ))\n/*\n=================\nR_GetTexture\n\nacess to array elem\n=================\n*/\ngl_texture_t *R_GetTexture( GLenum texnum )\n{\n\tASSERT( texnum >= 0 && texnum < MAX_TEXTURES );\n\treturn &gl_textures[texnum];\n}\n\n/*\n=================\nGL_TargetToString\n=================\n*/\nconst char *GL_TargetToString( GLenum target )\n{\n\tswitch( target )\n\t{\n\tcase GL_TEXTURE_1D:\n\t\treturn \"1D\";\n\tcase GL_TEXTURE_2D:\n\t\treturn \"2D\";\n\tcase GL_TEXTURE_2D_MULTISAMPLE:\n\t\treturn \"2D Multisample\";\n\tcase GL_TEXTURE_3D:\n\t\treturn \"3D\";\n\tcase GL_TEXTURE_CUBE_MAP_ARB:\n\t\treturn \"Cube\";\n\tcase GL_TEXTURE_2D_ARRAY_EXT:\n\t\treturn \"Array\";\n\tcase GL_TEXTURE_RECTANGLE_EXT:\n\t\treturn \"Rect\";\n\t}\n\treturn \"??\";\n}\n\n/*\n=================\nGL_Bind\n=================\n*/\nvoid GL_Bind( GLint tmu, GLenum texnum )\n{\n\tgl_texture_t\t*texture;\n\tGLuint\t\tglTarget;\n\n\t// missed or invalid texture?\n\tif( texnum <= 0 || texnum >= MAX_TEXTURES )\n\t{\n\t\tif( texnum != 0 )\n\t\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s: invalid texturenum %d\\n\", __func__, texnum );\n\t\ttexnum = tr.defaultTexture;\n\t}\n\tif( tmu != GL_KEEP_UNIT )\n\t\tGL_SelectTexture( tmu );\n\telse tmu = glState.activeTMU;\n\n\ttexture = &gl_textures[texnum];\n\tglTarget = texture->target;\n\n\tif( glTarget == GL_TEXTURE_2D_ARRAY_EXT )\n\t\tglTarget = GL_TEXTURE_2D;\n\n\tif( glState.currentTextureTargets[tmu] != glTarget )\n\t{\n\t\tGL_EnableTextureUnit( tmu, false );\n\t\tglState.currentTextureTargets[tmu] = glTarget;\n\t\tGL_EnableTextureUnit( tmu, true );\n\t}\n\n\tif( glState.currentTextures[tmu] == texture->texnum )\n\t\treturn;\n\n\tpglBindTexture( texture->target, texture->texnum );\n\tglState.currentTextures[tmu] = texture->texnum;\n\tglState.currentTexturesIndex[tmu] = texnum;\n}\n\nqboolean GL_TextureFilteringEnabled( const gl_texture_t *tex )\n{\n\tif( FBitSet( tex->flags, TF_NEAREST ))\n\t\treturn false;\n\n\tif( FBitSet( tex->flags, TF_DEPTHMAP ))\n\t\treturn true;\n\n\tif( FBitSet( tex->flags, TF_NOMIPMAP ) || tex->numMips <= 1 )\n\t{\n\t\tif( FBitSet( tex->flags, TF_ATLAS_PAGE ))\n\t\t\treturn gl_lightmap_nearest.value == 0.0f;\n\n\t\tif( FBitSet( tex->flags, TF_ALLOW_NEAREST ))\n\t\t\treturn gl_texture_nearest.value == 0.0f;\n\n\t\treturn true;\n\t}\n\n\treturn gl_texture_nearest.value == 0.0f;\n}\n\n/*\n=================\nGL_ApplyTextureParams\n=================\n*/\nvoid GL_ApplyTextureParams( gl_texture_t *tex )\n{\n\tvec4_t\tborder = { 0.0f, 0.0f, 0.0f, 1.0f };\n\tqboolean nomipmap;\n\n\tif( !glw_state.initialized )\n\t\treturn;\n\n\tAssert( tex != NULL );\n\n\t// multisample textures does not support any sampling state changing\n\tif( FBitSet( tex->flags, TF_MULTISAMPLE ))\n\t\treturn;\n\n\t// set texture filter\n\tnomipmap = tex->numMips <= 1 || FBitSet( tex->flags, TF_NOMIPMAP|TF_DEPTHMAP );\n\tif( !GL_TextureFilteringEnabled( tex ))\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_NEAREST : GL_NEAREST_MIPMAP_NEAREST );\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST );\n\t}\n\telse\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR );\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );\n\t}\n\n\tif( FBitSet( tex->flags, TF_DEPTHMAP ))\n\t{\n\t\tif( !FBitSet( tex->flags, TF_NOCOMPARE ))\n\t\t{\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB );\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL );\n\t\t}\n\n\t\tif( FBitSet( tex->flags, TF_LUMINANCE ))\n\t\t\tpglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE );\n\t\telse pglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY );\n\n\t\t// allow max anisotropy as 1.0f on depth textures\n\t\tif( GL_Support( GL_ANISOTROPY_EXT ))\n\t\t\tpglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f );\n\t}\n\telse if( !FBitSet( tex->flags, TF_NOMIPMAP ) && tex->numMips > 1 )\n\t{\n\t\t// set texture anisotropy if available\n\t\tif( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_ALPHACONTRAST ))\n\t\t\tpglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value );\n\n\t\t// set texture LOD bias if available\n\t\tif( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 ))\n\t\t\tpglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias.value );\n\t}\n\n\t// check if border is not supported\n\tif( FBitSet( tex->flags, TF_BORDER ) && !GL_Support( GL_CLAMP_TEXBORDER_EXT ))\n\t{\n\t\tClearBits( tex->flags, TF_BORDER );\n\t\tSetBits( tex->flags, TF_CLAMP );\n\t}\n\n\t// only seamless cubemaps allows wrap 'clamp_to_border\"\n\tif( tex->target == GL_TEXTURE_CUBE_MAP_ARB && !GL_Support( GL_ARB_SEAMLESS_CUBEMAP ) && FBitSet( tex->flags, TF_BORDER ))\n\t\tClearBits( tex->flags, TF_BORDER );\n\n\t// set texture wrap\n\tif( FBitSet( tex->flags, TF_BORDER ))\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );\n\n\t\tif( tex->target != GL_TEXTURE_1D )\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );\n\n\t\tif( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER );\n\n\t\tpglTexParameterfv( tex->target, GL_TEXTURE_BORDER_COLOR, border );\n\t}\n\telse if( FBitSet( tex->flags, TF_CLAMP ))\n\t{\n\t\tif( GL_Support( GL_CLAMPTOEDGE_EXT ))\n\t\t{\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );\n\n\t\t\tif( tex->target != GL_TEXTURE_1D )\n\t\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );\n\n\t\t\tif( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP );\n\n\t\t\tif( tex->target != GL_TEXTURE_1D )\n\t\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP );\n\n\t\t\tif( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP );\n\t\t}\n\t}\n\telse\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_REPEAT );\n\n\t\tif( tex->target != GL_TEXTURE_1D )\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_REPEAT );\n\n\t\tif( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t\t\tpglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_REPEAT );\n\t}\n}\n\n/*\n=================\nGL_UpdateTextureParams\n=================\n*/\nstatic void GL_UpdateTextureParams( int iTexture )\n{\n\tgl_texture_t\t*tex = &gl_textures[iTexture];\n\tqboolean nomipmap;\n\n\tAssert( tex != NULL );\n\n\tif( !tex->texnum ) return; // free slot\n\n\tGL_Bind( XASH_TEXTURE0, iTexture );\n\n\t// set texture anisotropy if available\n\tif( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP|TF_ALPHACONTRAST ))\n\t\tpglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value );\n\n\t// set texture LOD bias if available\n\tif( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP ))\n\t\tpglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias.value );\n\n\tnomipmap = tex->numMips <= 1 || FBitSet( tex->flags, TF_NOMIPMAP|TF_DEPTHMAP );\n\n\tif( !GL_TextureFilteringEnabled( tex ))\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_NEAREST : GL_NEAREST_MIPMAP_NEAREST );\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST );\n\t}\n\telse\n\t{\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR );\n\t\tpglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );\n\t}\n}\n\n/*\n=================\nR_SetTextureParameters\n=================\n*/\nvoid R_SetTextureParameters( void )\n{\n\tint\ti;\n\n\tif( GL_Support( GL_ANISOTROPY_EXT ))\n\t{\n\t\tif( gl_texture_anisotropy.value > glConfig.max_texture_anisotropy )\n\t\t\tgEngfuncs.Cvar_SetValue( \"gl_anisotropy\", glConfig.max_texture_anisotropy );\n\t\telse if( gl_texture_anisotropy.value < 1.0f )\n\t\t\tgEngfuncs.Cvar_SetValue( \"gl_anisotropy\", 1.0f );\n\t}\n\n\tif( GL_Support( GL_TEXTURE_LOD_BIAS ))\n\t{\n\t\tif( gl_texture_lodbias.value < -glConfig.max_texture_lod_bias )\n\t\t\tgEngfuncs.Cvar_SetValue( \"gl_texture_lodbias\", -glConfig.max_texture_lod_bias );\n\t\telse if( gl_texture_lodbias.value > glConfig.max_texture_lod_bias )\n\t\t\tgEngfuncs.Cvar_SetValue( \"gl_texture_lodbias\", glConfig.max_texture_lod_bias );\n\t}\n\n\tClearBits( gl_texture_anisotropy.flags, FCVAR_CHANGED );\n\tClearBits( gl_texture_lodbias.flags, FCVAR_CHANGED );\n\tClearBits( gl_texture_nearest.flags, FCVAR_CHANGED );\n\tClearBits( gl_lightmap_nearest.flags, FCVAR_CHANGED );\n\n\t// change all the existing mipmapped texture objects\n\tfor( i = 0; i < gl_numTextures; i++ )\n\t\tGL_UpdateTextureParams( i );\n}\n\n/*\n================\nGL_CalcTextureSamples\n================\n*/\nstatic int GL_CalcTextureSamples( int flags )\n{\n\tif( FBitSet( flags, IMAGE_HAS_COLOR ))\n\t\treturn FBitSet( flags, IMAGE_HAS_ALPHA ) ? 4 : 3;\n\treturn FBitSet( flags, IMAGE_HAS_ALPHA ) ? 2 : 1;\n}\n\n/*\n==================\nGL_CalcImageSize\n==================\n*/\nstatic size_t GL_CalcImageSize( pixformat_t format, int width, int height, int depth )\n{\n\tsize_t\tsize = 0;\n\n\t// check the depth error\n\tdepth = Q_max( 1, depth );\n\n\tswitch( format )\n\t{\n\tcase PF_LUMINANCE:\n\t\tsize = width * height * depth;\n\t\tbreak;\n\tcase PF_RGB_24:\n\tcase PF_BGR_24:\n\t\tsize = width * height * depth * 3;\n\t\tbreak;\n\tcase PF_BGRA_32:\n\tcase PF_RGBA_32:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase PF_DXT1:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth;\n\t\tbreak;\n\tcase PF_DXT3:\n\tcase PF_DXT5:\n\tcase PF_BC6H_SIGNED:\n\tcase PF_BC6H_UNSIGNED:\n\tcase PF_BC7_UNORM:\n\tcase PF_BC7_SRGB:\n\tcase PF_ATI2:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth;\n\t\tbreak;\n\t}\n\n\treturn size;\n}\n\n/*\n==================\nGL_CalcTextureSize\n==================\n*/\nstatic size_t GL_CalcTextureSize( GLenum format, int width, int height, int depth )\n{\n\tsize_t\tsize = 0;\n\n\t// check the depth error\n\tdepth = Q_max( 1, depth );\n\n\tswitch( format )\n\t{\n\tcase GL_COMPRESSED_RGB_S3TC_DXT1_EXT:\n\tcase GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth;\n\t\tbreak;\n\tcase GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:\n\tcase GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:\n\tcase GL_COMPRESSED_RED_GREEN_RGTC2_EXT:\n\tcase GL_COMPRESSED_LUMINANCE_ALPHA_ARB:\n\tcase GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI:\n\tcase GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:\n\tcase GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:\n\tcase GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:\n\tcase GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth;\n\t\tbreak;\n\tcase GL_RGBA8:\n\tcase GL_RGBA:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase GL_RGB8:\n\tcase GL_RGB:\n\t\tsize = width * height * depth * 3;\n\t\tbreak;\n\tcase GL_RGB5:\n\t\tsize = (width * height * depth * 3) / 2;\n\t\tbreak;\n\tcase GL_RGBA4:\n\t\tsize = (width * height * depth * 4) / 2;\n\t\tbreak;\n\tcase GL_INTENSITY:\n\tcase GL_LUMINANCE:\n\tcase GL_INTENSITY8:\n\tcase GL_LUMINANCE8:\n\t\tsize = (width * height * depth);\n\t\tbreak;\n\tcase GL_LUMINANCE_ALPHA:\n\tcase GL_LUMINANCE8_ALPHA8:\n\t\tsize = width * height * depth * 2;\n\t\tbreak;\n\tcase GL_R8:\n\t\tsize = width * height * depth;\n\t\tbreak;\n\tcase GL_RG8:\n\t\tsize = width * height * depth * 2;\n\t\tbreak;\n\tcase GL_R16:\n\t\tsize = width * height * depth * 2;\n\t\tbreak;\n\tcase GL_RG16:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase GL_R16F:\n\tcase GL_LUMINANCE16F_ARB:\n\t\tsize = width * height * depth * 2;\t// half-floats\n\t\tbreak;\n\tcase GL_R32F:\n\tcase GL_LUMINANCE32F_ARB:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase GL_RG16F:\n\tcase GL_LUMINANCE_ALPHA16F_ARB:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase GL_RG32F:\n\tcase GL_LUMINANCE_ALPHA32F_ARB:\n\t\tsize = width * height * depth * 8;\n\t\tbreak;\n\tcase GL_RGB16F_ARB:\n\t\tsize = width * height * depth * 6;\n\t\tbreak;\n\tcase GL_RGBA16F_ARB:\n\t\tsize = width * height * depth * 8;\n\t\tbreak;\n\tcase GL_RGB32F_ARB:\n\t\tsize = width * height * depth * 12;\n\t\tbreak;\n\tcase GL_RGBA32F_ARB:\n\t\tsize = width * height * depth * 16;\n\t\tbreak;\n\tcase GL_DEPTH_COMPONENT16:\n\t\tsize = width * height * depth * 2;\n\t\tbreak;\n\tcase GL_DEPTH_COMPONENT24:\n\t\tsize = width * height * depth * 3;\n\t\tbreak;\n\tcase GL_DEPTH_COMPONENT32F:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tdefault:\n\t\tgEngfuncs.Host_Error( \"%s: bad texture internal format (%u)\\n\", __func__, format );\n\t\tbreak;\n\t}\n\n\treturn size;\n}\n\nstatic int GL_CalcMipmapCount( gl_texture_t *tex, qboolean haveBuffer )\n{\n\tint\twidth, height;\n\tint\tmipcount;\n\n\tAssert( tex != NULL );\n\n\tif( !haveBuffer || tex->target == GL_TEXTURE_3D )\n\t\treturn 1;\n\n\t// generate mip-levels by user request\n\tif( FBitSet( tex->flags, TF_NOMIPMAP ))\n\t\treturn 1;\n\n\t// mip-maps can't exceeds 16\n\tfor( mipcount = 0; mipcount < 16; mipcount++ )\n\t{\n\t\twidth = Q_max( 1, ( tex->width >> mipcount ));\n\t\theight = Q_max( 1, ( tex->height >> mipcount ));\n\t\tif( width == 1 && height == 1 )\n\t\t\tbreak;\n\t}\n\n\treturn mipcount + 1;\n}\n\n/*\n================\nGL_SetTextureDimensions\n================\n*/\nstatic void GL_SetTextureDimensions( gl_texture_t *tex, int width, int height, int depth )\n{\n\tint\tmaxTextureSize = 0;\n\tint\tmaxDepthSize = 1;\n\n\tAssert( tex != NULL );\n\n\tswitch( tex->target )\n\t{\n\tcase GL_TEXTURE_1D:\n\tcase GL_TEXTURE_2D:\n\tcase GL_TEXTURE_2D_MULTISAMPLE:\n\t\tmaxTextureSize = glConfig.max_2d_texture_size;\n\t\tbreak;\n\tcase GL_TEXTURE_2D_ARRAY_EXT:\n\t\tmaxDepthSize = glConfig.max_2d_texture_layers;\n\t\tmaxTextureSize = glConfig.max_2d_texture_size;\n\t\tbreak;\n\tcase GL_TEXTURE_RECTANGLE_EXT:\n\t\tmaxTextureSize = glConfig.max_2d_rectangle_size;\n\t\tbreak;\n\tcase GL_TEXTURE_CUBE_MAP_ARB:\n\t\tmaxTextureSize = glConfig.max_cubemap_size;\n\t\tbreak;\n\tcase GL_TEXTURE_3D:\n\t\tmaxDepthSize = glConfig.max_3d_texture_size;\n\t\tmaxTextureSize = glConfig.max_3d_texture_size;\n\t\tbreak;\n\tdefault:\n\t\tAssert( false );\n\t}\n\n\t// store original sizes\n\ttex->srcWidth = width;\n\ttex->srcHeight = height;\n\n\tif( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT ))\n\t{\n\t\tint\tstep = (int)gl_round_down.value;\n\t\tint\tscaled_width, scaled_height;\n\n\t\tfor( scaled_width = 1; scaled_width < width; scaled_width <<= 1 );\n\n\t\tif( step > 0 && width < scaled_width && ( step == 1 || ( scaled_width - width ) > ( scaled_width >> step )))\n\t\t\tscaled_width >>= 1;\n\n\t\tfor( scaled_height = 1; scaled_height < height; scaled_height <<= 1 );\n\n\t\tif( step > 0 && height < scaled_height && ( step == 1 || ( scaled_height - height ) > ( scaled_height >> step )))\n\t\t\tscaled_height >>= 1;\n\n\t\twidth = scaled_width;\n\t\theight = scaled_height;\n\t}\n\n\tif( width > maxTextureSize || height > maxTextureSize || depth > maxDepthSize )\n\t{\n\t\tif( tex->target == GL_TEXTURE_1D )\n\t\t{\n\t\t\twhile( width > maxTextureSize )\n\t\t\t\twidth >>= 1;\n\t\t}\n\t\telse if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT )\n\t\t{\n\t\t\twhile( width > maxTextureSize || height > maxTextureSize || depth > maxDepthSize )\n\t\t\t{\n\t\t\t\twidth >>= 1;\n\t\t\t\theight >>= 1;\n\t\t\t\tdepth >>= 1;\n\t\t\t}\n\t\t}\n\t\telse // all remaining cases\n\t\t{\n\t\t\twhile( width > maxTextureSize || height > maxTextureSize )\n\t\t\t{\n\t\t\t\twidth >>= 1;\n\t\t\t\theight >>= 1;\n\t\t\t}\n\t\t}\n\t}\n\n\t// set the texture dimensions\n\ttex->width = Q_max( 1, width );\n\ttex->height = Q_max( 1, height );\n\ttex->depth = Q_max( 1, depth );\n}\n\n/*\n===============\nGL_SetTextureTarget\n===============\n*/\nstatic void GL_SetTextureTarget( gl_texture_t *tex, rgbdata_t *pic )\n{\n\tAssert( pic != NULL );\n\tAssert( tex != NULL );\n\n\t// correct depth size\n\tpic->depth = Q_max( 1, pic->depth );\n\ttex->numMips = 0; // begin counting\n\n\t// correct mip count\n\tpic->numMips = Q_max( 1, pic->numMips );\n\n\t// trying to determine texture type\n#if !XASH_GLES\n\tif( pic->width > 1 && pic->height <= 1 )\n\t\ttex->target = GL_TEXTURE_1D;\n\telse \n#endif // just skip first condition\n\tif( FBitSet( pic->flags, IMAGE_CUBEMAP ))\n\t\ttex->target = GL_TEXTURE_CUBE_MAP_ARB;\n\telse if( FBitSet( pic->flags, IMAGE_MULTILAYER ) && pic->depth >= 1 )\n\t\ttex->target = GL_TEXTURE_2D_ARRAY_EXT;\n\telse if( pic->width > 1 && pic->height > 1 && pic->depth > 1 )\n\t\ttex->target = GL_TEXTURE_3D;\n\telse if( FBitSet( tex->flags, TF_RECTANGLE ))\n\t\ttex->target = GL_TEXTURE_RECTANGLE_EXT;\n\telse if( FBitSet(tex->flags, TF_MULTISAMPLE ))\n\t\ttex->target = GL_TEXTURE_2D_MULTISAMPLE;\n\telse tex->target = GL_TEXTURE_2D; // default case\n\n\t// check for hardware support\n\tif(( tex->target == GL_TEXTURE_CUBE_MAP_ARB ) && !GL_Support( GL_TEXTURE_CUBEMAP_EXT ))\n\t\ttex->target = GL_NONE;\n\n\tif(( tex->target == GL_TEXTURE_RECTANGLE_EXT ) && !GL_Support( GL_TEXTURE_2D_RECT_EXT ))\n\t\ttex->target = GL_TEXTURE_2D;\t// fallback\n\n\tif(( tex->target == GL_TEXTURE_2D_ARRAY_EXT ) && !GL_Support( GL_TEXTURE_ARRAY_EXT ))\n\t\ttex->target = GL_NONE;\n\n\tif(( tex->target == GL_TEXTURE_3D ) && !GL_Support( GL_TEXTURE_3D_EXT ))\n\t\ttex->target = GL_NONE;\n\n\t// check if depth textures are not supported\n\tif( FBitSet( tex->flags, TF_DEPTHMAP ) && !GL_Support( GL_DEPTH_TEXTURE ))\n\t\ttex->target = GL_NONE;\n\n\t// depth cubemaps only allowed when GL_EXT_gpu_shader4 is supported\n\tif( tex->target == GL_TEXTURE_CUBE_MAP_ARB && !GL_Support( GL_EXT_GPU_SHADER4 ) && FBitSet( tex->flags, TF_DEPTHMAP ))\n\t\ttex->target = GL_NONE;\n\n\tif(( tex->target == GL_TEXTURE_2D_MULTISAMPLE ) && !GL_Support( GL_TEXTURE_MULTISAMPLE ))\n\t\ttex->target = GL_NONE;\n}\n\n/*\n===============\nGL_SetTextureFormat\n===============\n*/\nstatic void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int channelMask )\n{\n\tqboolean\thaveColor = ( channelMask & IMAGE_HAS_COLOR );\n\tqboolean\thaveAlpha = ( channelMask & IMAGE_HAS_ALPHA );\n\n\tAssert( tex != NULL );\n\n\tif( ImageCompressed( format ))\n\t{\n\t\tswitch( format )\n\t\t{\n\t\tcase PF_DXT1: tex->format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break;\t// never use DXT1 with 1-bit alpha\n\t\tcase PF_DXT3: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break;\n\t\tcase PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break;\n\t\tcase PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break;\n\t\tcase PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break;\n\t\tcase PF_BC7_SRGB:\n\t\tcase PF_BC7_UNORM: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break;\n\t\tcase PF_ATI2:\n\t\t\tif( glConfig.hardware_type == GLHW_RADEON )\n\t\t\t\ttex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI;\n\t\t\telse tex->format = GL_COMPRESSED_RED_GREEN_RGTC2_EXT;\n\t\t\tbreak;\n\t\t}\n\t\treturn;\n\t}\n\telse if( FBitSet( tex->flags, TF_DEPTHMAP ))\n\t{\n\t\tif( FBitSet( tex->flags, TF_ARB_16BIT ))\n\t\t\ttex->format = GL_DEPTH_COMPONENT16;\n\t\telse if( FBitSet( tex->flags, TF_ARB_FLOAT ) && GL_Support( GL_ARB_DEPTH_FLOAT_EXT ))\n\t\t\ttex->format = GL_DEPTH_COMPONENT32F;\n\t\telse tex->format = GL_DEPTH_COMPONENT24;\n\t}\n\telse if( FBitSet( tex->flags, TF_ARB_FLOAT|TF_ARB_16BIT ) && GL_Support( GL_ARB_TEXTURE_FLOAT_EXT ))\n\t{\n\t\tif( haveColor && haveAlpha )\n\t\t{\n\t\t\tif( FBitSet( tex->flags, TF_ARB_16BIT ) || gpGlobals->desktopBitsPixel == 16 )\n\t\t\t\ttex->format = GL_RGBA16F_ARB;\n\t\t\telse tex->format = GL_RGBA32F_ARB;\n\t\t}\n\t\telse if( haveColor )\n\t\t{\n\t\t\tif( FBitSet( tex->flags, TF_ARB_16BIT ) || gpGlobals->desktopBitsPixel == 16 )\n\t\t\t\ttex->format = GL_RGB16F_ARB;\n\t\t\telse tex->format = GL_RGB32F_ARB;\n\t\t}\n\t\telse if( haveAlpha )\n\t\t{\n\t\t\tif( FBitSet( tex->flags, TF_ARB_16BIT ) || gpGlobals->desktopBitsPixel == 16 )\n\t\t\t\ttex->format = GL_RG16F;\n\t\t\telse tex->format = GL_RG32F;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( FBitSet( tex->flags, TF_ARB_16BIT ) || gpGlobals->desktopBitsPixel == 16 )\n\t\t\t\ttex->format = GL_LUMINANCE16F_ARB;\n\t\t\telse tex->format = GL_LUMINANCE32F_ARB;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// NOTE: not all the types will be compressed\n\t\tint\tbits = gpGlobals->desktopBitsPixel;\n\n\t\tswitch( GL_CalcTextureSamples( channelMask ))\n\t\t{\n\t\tcase 1:\n\t\t\tif( FBitSet( tex->flags, TF_ALPHACONTRAST ))\n\t\t\t\ttex->format = GL_INTENSITY8;\n\t\t\telse tex->format = GL_LUMINANCE8;\n\t\t\tbreak;\n\t\tcase 2: tex->format = GL_LUMINANCE8_ALPHA8; break;\n\t\tcase 3:\n\t\t\tswitch( bits )\n\t\t\t{\n\t\t\tcase 16: tex->format = GL_RGB5; break;\n\t\t\tcase 32: tex->format = GL_RGB8; break;\n\t\t\tdefault: tex->format = GL_RGB; break;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 4:\n\t\tdefault:\n\t\t\tswitch( bits )\n\t\t\t{\n\t\t\tcase 16: tex->format = GL_RGBA4; break;\n\t\t\tcase 32: tex->format = GL_RGBA8; break;\n\t\t\tdefault: tex->format = GL_RGBA; break;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n=================\nGL_ResampleTexture\n\nAssume input buffer is RGBA\n=================\n*/\nbyte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int outWidth, int outHeight, qboolean isNormalMap )\n{\n\tuint\t\tfrac, fracStep;\n\tuint\t\t*in = (uint *)source;\n\tuint\t\tp1[0x1000], p2[0x1000];\n\tbyte\t\t*pix1, *pix2, *pix3, *pix4;\n\tuint\t\t*out, *inRow1, *inRow2;\n\tstatic byte\t*scaledImage = NULL;\t// pointer to a scaled image\n\tvec3_t\t\tnormal;\n\tint\t\ti, x, y;\n\n\tif( !source ) return NULL;\n\n\tscaledImage = Mem_Realloc( r_temppool, scaledImage, outWidth * outHeight * 4 );\n\tfracStep = inWidth * 0x10000 / outWidth;\n\tout = (uint *)scaledImage;\n\n\tfrac = fracStep >> 2;\n\tfor( i = 0; i < outWidth; i++ )\n\t{\n\t\tp1[i] = 4 * (frac >> 16);\n\t\tfrac += fracStep;\n\t}\n\n\tfrac = (fracStep >> 2) * 3;\n\tfor( i = 0; i < outWidth; i++ )\n\t{\n\t\tp2[i] = 4 * (frac >> 16);\n\t\tfrac += fracStep;\n\t}\n\n\tif( isNormalMap )\n\t{\n\t\tfor( y = 0; y < outHeight; y++, out += outWidth )\n\t\t{\n\t\t\tinRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight);\n\t\t\tinRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight);\n\n\t\t\tfor( x = 0; x < outWidth; x++ )\n\t\t\t{\n\t\t\t\tpix1 = (byte *)inRow1 + p1[x];\n\t\t\t\tpix2 = (byte *)inRow1 + p2[x];\n\t\t\t\tpix3 = (byte *)inRow2 + p1[x];\n\t\t\t\tpix4 = (byte *)inRow2 + p2[x];\n\n\t\t\t\tnormal[0] = MAKE_SIGNED( pix1[0] ) + MAKE_SIGNED( pix2[0] ) + MAKE_SIGNED( pix3[0] ) + MAKE_SIGNED( pix4[0] );\n\t\t\t\tnormal[1] = MAKE_SIGNED( pix1[1] ) + MAKE_SIGNED( pix2[1] ) + MAKE_SIGNED( pix3[1] ) + MAKE_SIGNED( pix4[1] );\n\t\t\t\tnormal[2] = MAKE_SIGNED( pix1[2] ) + MAKE_SIGNED( pix2[2] ) + MAKE_SIGNED( pix3[2] ) + MAKE_SIGNED( pix4[2] );\n\n\t\t\t\tif( !VectorNormalizeLength( normal ))\n\t\t\t\t\tVectorSet( normal, 0.5f, 0.5f, 1.0f );\n\n\t\t\t\t((byte *)(out+x))[0] = 128 + (byte)(127.0f * normal[0]);\n\t\t\t\t((byte *)(out+x))[1] = 128 + (byte)(127.0f * normal[1]);\n\t\t\t\t((byte *)(out+x))[2] = 128 + (byte)(127.0f * normal[2]);\n\t\t\t\t((byte *)(out+x))[3] = 255;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( y = 0; y < outHeight; y++, out += outWidth )\n\t\t{\n\t\t\tinRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight);\n\t\t\tinRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight);\n\n\t\t\tfor( x = 0; x < outWidth; x++ )\n\t\t\t{\n\t\t\t\tpix1 = (byte *)inRow1 + p1[x];\n\t\t\t\tpix2 = (byte *)inRow1 + p2[x];\n\t\t\t\tpix3 = (byte *)inRow2 + p1[x];\n\t\t\t\tpix4 = (byte *)inRow2 + p2[x];\n\n\t\t\t\t((byte *)(out+x))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;\n\t\t\t\t((byte *)(out+x))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;\n\t\t\t\t((byte *)(out+x))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;\n\t\t\t\t((byte *)(out+x))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn scaledImage;\n}\n\n/*\n=================\nGL_BoxFilter3x3\n\nbox filter 3x3\n=================\n*/\nstatic void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y )\n{\n\tint\t\tr = 0, g = 0, b = 0, a = 0;\n\tint\t\tcount = 0, acount = 0;\n\tint\t\ti, j, u, v;\n\tconst byte\t*pixel;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tu = ( i - 1 ) + x;\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tv = ( j - 1 ) + y;\n\n\t\t\tif( u >= 0 && u < w && v >= 0 && v < h )\n\t\t\t{\n\t\t\t\tpixel = &in[( u + v * w ) * 4];\n\n\t\t\t\tif( pixel[3] != 0 )\n\t\t\t\t{\n\t\t\t\t\tr += pixel[0];\n\t\t\t\t\tg += pixel[1];\n\t\t\t\t\tb += pixel[2];\n\t\t\t\t\ta += pixel[3];\n\t\t\t\t\tacount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif(  acount == 0 )\n\t\tacount = 1;\n\n\tout[0] = r / acount;\n\tout[1] = g / acount;\n\tout[2] = b / acount;\n//\tout[3] = (int)( SimpleSpline( ( a / 12.0f ) / 255.0f ) * 255 );\n}\n\n/*\n=================\nGL_ApplyFilter\n\nApply box-filter to 1-bit alpha\n=================\n*/\nstatic byte *GL_ApplyFilter( const byte *source, int width, int height )\n{\n\tbyte\t*in = (byte *)source;\n\tbyte\t*out = (byte *)source;\n\tint\ti;\n\n\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) || glConfig.max_multisamples > 1 )\n\t\treturn in;\n\n\tfor( i = 0; source && i < width * height; i++, in += 4 )\n\t{\n\t\tif( in[0] == 0 && in[1] == 0 && in[2] == 0 && in[3] == 0 )\n\t\t\tGL_BoxFilter3x3( in, source, width, height, i % width, i / width );\n\t}\n\n\treturn out;\n}\n\n/*\n=================\nGL_BuildMipMap\n\nOperates in place, quartering the size of the texture\n=================\n*/\nstatic void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags )\n{\n\tbyte\t*out = in;\n\tint\tinstride = ALIGN( srcWidth * 4, 1 );\n\tint\tmipWidth, mipHeight, outpadding;\n\tint\trow, x, y, z;\n\tvec3_t\tnormal;\n\n\tif( !in ) return;\n\n\tmipWidth = Q_max( 1, ( srcWidth >> 1 ));\n\tmipHeight = Q_max( 1, ( srcHeight >> 1 ));\n\toutpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4;\n\trow = srcWidth << 2;\n\n\tif( FBitSet( flags, TF_ALPHACONTRAST ))\n\t{\n\t\tmemset( in, mipWidth, mipWidth * mipHeight * 4 );\n\t\treturn;\n\t}\n\n\t// move through all layers\n\tfor( z = 0; z < srcDepth; z++ )\n\t{\n\t\tif( FBitSet( flags, TF_NORMALMAP ))\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( !VectorNormalizeLength( normal ))\n\t\t\t\t\t\tVectorSet( normal, 0.5f, 0.5f, 1.0f );\n\n\t\t\t\t\tout[0] = 128 + (byte)(127.0f * normal[0]);\n\t\t\t\t\tout[1] = 128 + (byte)(127.0f * normal[1]);\n\t\t\t\t\tout[2] = 128 + (byte)(127.0f * normal[2]);\n\t\t\t\t\tout[3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2;\n\t\t\t\t\t\tout[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2;\n\t\t\t\t\t\tout[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2;\n\t\t\t\t\t\tout[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (in[row+0] + next[row+0]) >> 1;\n\t\t\t\t\t\tout[1] = (in[row+1] + next[row+1]) >> 1;\n\t\t\t\t\t\tout[2] = (in[row+2] + next[row+2]) >> 1;\n\t\t\t\t\t\tout[3] = (in[row+3] + next[row+3]) >> 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, GLint type, const void *data )\n{\n\tGLuint\tcubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;\n\tqboolean\tsubImage = FBitSet( tex->flags, TF_IMG_UPLOADED ) && data != NULL;\n\tGLenum\tinFormat = gEngfuncs.Image_GetPFDesc( type )->glFormat;\n\tGLint\tdataType = GL_UNSIGNED_BYTE;\n\tGLsizei\tsamplesCount = 0;\n\n\tAssert( tex != NULL );\n\n\tif( FBitSet( tex->flags, TF_DEPTHMAP ))\n\t\tinFormat = GL_DEPTH_COMPONENT;\n\n\tif( FBitSet( tex->flags, TF_ARB_16BIT ))\n\t\tdataType = GL_HALF_FLOAT_ARB;\n\telse if( FBitSet( tex->flags, TF_ARB_FLOAT ))\n\t\tdataType = GL_FLOAT;\n\n\tif( tex->target == GL_TEXTURE_1D )\n\t{\n\t\tif( subImage ) pglTexSubImage1D( tex->target, level, 0, width, inFormat, dataType, data );\n\t\telse pglTexImage1D( tex->target, level, tex->format, width, 0, inFormat, dataType, data );\n\t}\n\telse if( tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t{\n\t\tif( subImage ) pglTexSubImage2D( cubeTarget + side, level, 0, 0, width, height, inFormat, dataType, data );\n\t\telse pglTexImage2D( cubeTarget + side, level, tex->format, width, height, 0, inFormat, dataType, data );\n\t}\n\telse if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT )\n\t{\n\t\tif( subImage ) pglTexSubImage3D( tex->target, level, 0, 0, 0, width, height, depth, inFormat, dataType, data );\n\t\telse pglTexImage3D( tex->target, level, tex->format, width, height, depth, 0, inFormat, dataType, data );\n\t}\n\telse if( tex->target == GL_TEXTURE_2D_MULTISAMPLE )\n\t{\n#if !defined( XASH_GL_STATIC ) || (!defined( XASH_GLES ) && !defined( XASH_GL4ES ))\n\t\tsamplesCount = (GLsizei)gEngfuncs.pfnGetCvarFloat( \"gl_msaa_samples\" );\n\t\tswitch (samplesCount)\n\t\t{\n\t\t\tcase 2:\n\t\t\tcase 4:\n\t\t\tcase 8:\n\t\t\tcase 16:\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tsamplesCount = 1;\n\t\t}\n\t\tpglTexImage2DMultisample( tex->target, samplesCount, tex->format, width, height, GL_TRUE );\n#else /* !XASH_GL_STATIC !XASH_GLES && !XASH_GL4ES */\n\t\tgEngfuncs.Con_Printf( S_ERROR \"GLES renderer don't support GL_TEXTURE_2D_MULTISAMPLE!\\n\" );\n#endif /* !XASH_GL_STATIC !XASH_GLES && !XASH_GL4ES */\n\t}\n\telse // 2D or RECT\n\t{\n\t\tif( subImage ) pglTexSubImage2D( tex->target, level, 0, 0, width, height, inFormat, dataType, data );\n\t\telse pglTexImage2D( tex->target, level, tex->format, width, height, 0, inFormat, dataType, data );\n\t}\n}\n\nstatic void GL_TextureImageCompressed( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data )\n{\n\tGLuint\tcubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;\n\tqboolean\tsubImage = FBitSet( tex->flags, TF_IMG_UPLOADED );\n\n\tAssert( tex != NULL );\n\n#if !XASH_GLES\n\tif( tex->target == GL_TEXTURE_1D )\n\t{\n\t\tif( subImage ) pglCompressedTexSubImage1DARB( tex->target, level, 0, width, tex->format, size, data );\n\t\telse pglCompressedTexImage1DARB( tex->target, level, tex->format, width, 0, size, data );\n\t}\n\telse if( tex->target == GL_TEXTURE_CUBE_MAP_ARB )\n\t{\n\t\tif( subImage ) pglCompressedTexSubImage2DARB( cubeTarget + side, level, 0, 0, width, height, tex->format, size, data );\n\t\telse pglCompressedTexImage2DARB( cubeTarget + side, level, tex->format, width, height, 0, size, data );\n\t}\n\telse if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT )\n\t{\n\t\tif( subImage ) pglCompressedTexSubImage3DARB( tex->target, level, 0, 0, 0, width, height, depth, tex->format, size, data );\n\t\telse pglCompressedTexImage3DARB( tex->target, level, tex->format, width, height, depth, 0, size, data );\n\t}\n\telse // 2D or RECT\n\t{\n\t\tif( subImage ) pglCompressedTexSubImage2DARB( tex->target, level, 0, 0, width, height, tex->format, size, data );\n\t\telse pglCompressedTexImage2DARB( tex->target, level, tex->format, width, height, 0, size, data );\n\t}\n#endif\n}\n\n/*\n===============\nGL_CheckTexImageError\n\nshow GL-errors on load images\n===============\n*/\nstatic void GL_CheckTexImageError( gl_texture_t *tex )\n{\n\tint\terr;\n\n\tAssert( tex != NULL );\n\n\t// catch possible errors\n\tif( gl_check_errors.value && ( err = pglGetError()) != GL_NO_ERROR )\n\t\tgEngfuncs.Con_Printf( S_OPENGL_ERROR \"%s while uploading %s [%s]\\n\", GL_ErrorString( err ), tex->name, GL_TargetToString( tex->target ));\n}\n\n/*\n===============\nGL_UploadTexture\n\nupload texture into video memory\n===============\n*/\nstatic qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic )\n{\n\tbyte\t\t*buf, *data;\n\tsize_t\t\ttexsize, size;\n\tuint\t\twidth, height;\n\tuint\t\ti, j, numSides;\n\tuint\t\toffset = 0;\n\tqboolean\t\tnormalMap;\n\tconst byte\t*bufend;\n\n\t// dedicated server\n\tif( !glw_state.initialized )\n\t\treturn true;\n\n\tAssert( pic != NULL );\n\tAssert( tex != NULL );\n\n\tGL_SetTextureTarget( tex, pic ); // must be first\n\n\t// make sure what target is correct\n\tif( tex->target == GL_NONE )\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s: %s is not supported by your hardware\\n\", __func__, tex->name );\n\t\treturn false;\n\t}\n\n\tif( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7_UNORM || pic->type == PF_BC7_SRGB )\n\t{\n\t\tif( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC ))\n\t\t{\n\t\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s: BC6H/BC7 compression formats is not supported by your hardware\\n\", __func__ );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tGL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth );\n\tGL_SetTextureFormat( tex, pic->type, pic->flags );\n\n\ttex->fogParams[0] = pic->fogParams[0];\n\ttex->fogParams[1] = pic->fogParams[1];\n\ttex->fogParams[2] = pic->fogParams[2];\n\ttex->fogParams[3] = pic->fogParams[3];\n\n\tif(( pic->width * pic->height ) & 3 )\n\t{\n\t\t// will be resampled, just tell me for debug targets\n\t\tgEngfuncs.Con_Reportf( \"%s: %s s&3 [%d x %d]\\n\", __func__, tex->name, pic->width, pic->height );\n\t}\n\n\tbuf = pic->buffer;\n\tbufend = pic->buffer + pic->size; // total image size include all the layers, cube sides, mipmaps\n\toffset = GL_CalcImageSize( pic->type, pic->width, pic->height, pic->depth );\n\ttexsize = GL_CalcTextureSize( tex->format, tex->width, tex->height, tex->depth );\n\tnormalMap = FBitSet( tex->flags, TF_NORMALMAP ) ? true : false;\n\tnumSides = FBitSet( pic->flags, IMAGE_CUBEMAP ) ? 6 : 1;\n\n\t// uploading texture into video memory, change the binding\n\tglState.currentTextures[glState.activeTMU] = tex->texnum;\n\tglState.currentTexturesIndex[glState.activeTMU] = tex - gl_textures;\n\tpglBindTexture( tex->target, tex->texnum );\n\n\tfor( i = 0; i < numSides; i++ )\n\t{\n\t\t// track the buffer bounds\n\t\tif( buf != NULL && buf >= bufend )\n\t\t\tgEngfuncs.Host_Error( \"%s: %s image buffer overflow\\n\", __func__, tex->name );\n\n\t\tif( ImageCompressed( pic->type ))\n\t\t{\n\t\t\tfor( j = 0; j < Q_max( 1, pic->numMips ); j++ )\n\t\t\t{\n\t\t\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\t\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\t\t\ttexsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );\n\t\t\t\tsize = GL_CalcImageSize( pic->type, width, height, tex->depth );\n\t\t\t\tGL_TextureImageCompressed( tex, i, j, width, height, tex->depth, size, buf );\n\t\t\t\ttex->size += texsize;\n\t\t\t\tbuf += size; // move pointer\n\t\t\t\ttex->numMips++;\n\n\t\t\t\tGL_CheckTexImageError( tex );\n\t\t\t}\n\t\t}\n\t\telse if( Q_max( 1, pic->numMips ) > 1 )\t// not-compressed DDS\n\t\t{\n\t\t\tfor( j = 0; j < Q_max( 1, pic->numMips ); j++ )\n\t\t\t{\n\t\t\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\t\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\t\t\ttexsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );\n\t\t\t\tsize = GL_CalcImageSize( pic->type, width, height, tex->depth );\n\t\t\t\tGL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, buf );\n\t\t\t\ttex->size += texsize;\n\t\t\t\tbuf += size; // move pointer\n\t\t\t\ttex->numMips++;\n\n\t\t\t\tGL_CheckTexImageError( tex );\n\n\t\t\t}\n\t\t}\n\t\telse // RGBA32\n\t\t{\n\t\t\tint mipCount = GL_CalcMipmapCount( tex, ( buf != NULL ));\n\n\t\t\t// NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides\n\t\t\tif(( tex->depth == 1 ) && (( pic->width != tex->width ) || ( pic->height != tex->height )))\n\t\t\t\tdata = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap );\n\t\t\telse data = buf;\n\n\t\t\tif( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))\n\t\t\t\tdata = GL_ApplyFilter( data, tex->width, tex->height );\n\n\t\t\t// mips will be auto-generated if desired\n\t\t\tfor( j = 0; j < mipCount; j++ )\n\t\t\t{\n\t\t\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\t\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\t\t\ttexsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );\n\t\t\t\tsize = GL_CalcImageSize( pic->type, width, height, tex->depth );\n\t\t\t\tGL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data );\n\t\t\t\tif( mipCount > 1 )\n\t\t\t\t\tGL_BuildMipMap( data, width, height, tex->depth, tex->flags );\n\t\t\t\ttex->size += texsize;\n\t\t\t\ttex->numMips++;\n\n\t\t\t\tGL_CheckTexImageError( tex );\n\t\t\t}\n\n\t\t\t// move to next side\n\t\t\tif( numSides > 1 && ( buf != NULL ))\n\t\t\t\tbuf += GL_CalcImageSize( pic->type, pic->width, pic->height, 1 );\n\t\t}\n\t}\n\n\tSetBits( tex->flags, TF_IMG_UPLOADED ); // done\n\ttex->numMips /= numSides;\n\n\treturn true;\n}\n\n/*\n===============\nGL_ProcessImage\n\ndo specified actions on pixels\n===============\n*/\nstatic void GL_ProcessImage( gl_texture_t *tex, rgbdata_t *pic )\n{\n\tuint\timg_flags = 0;\n\n\t// force upload texture as RGB or RGBA (detail textures requires this)\n\tif( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR;\n\tif( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA;\n\n\ttex->encode = pic->encode; // share encode method\n\n\tif( ImageCompressed( pic->type ))\n\t{\n\t\tif( !pic->numMips )\n\t\t\ttex->flags |= TF_NOMIPMAP; // disable mipmapping by user request\n\n\t\t// clear all the unsupported flags\n\t\ttex->flags &= ~TF_KEEP_SOURCE;\n\t}\n\telse\n\t{\n\t\t// copy flag about luma pixels\n\t\tif( pic->flags & IMAGE_HAS_LUMA )\n\t\t\ttex->flags |= TF_HAS_LUMA;\n\n\t\tif( pic->flags & IMAGE_QUAKEPAL )\n\t\t\ttex->flags |= TF_QUAKEPAL;\n\n\t\t// create luma texture from quake texture\n\t\tif( tex->flags & TF_MAKELUMA )\n\t\t{\n\t\t\timg_flags |= IMAGE_MAKE_LUMA;\n\t\t\ttex->flags &= ~TF_MAKELUMA;\n\t\t}\n\n\t\tif( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE ))\n\t\t\ttex->original = gEngfuncs.FS_CopyImage( pic ); // because current pic will be expanded to rgba\n\n\t\t// we need to expand image into RGBA buffer\n\t\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\t\timg_flags |= IMAGE_FORCE_RGBA;\n\n\t\t// processing image before uploading (force to rgba, make luma etc)\n\t\tif( pic->buffer ) gEngfuncs.Image_Process( &pic, 0, 0, img_flags, 0 );\n\n\t\tif( FBitSet( tex->flags, TF_LUMINANCE ))\n\t\t\tClearBits( pic->flags, IMAGE_HAS_COLOR );\n\t}\n}\n\n/*\n================\nGL_CheckTexName\n================\n*/\nstatic qboolean GL_CheckTexName( const char *name )\n{\n\tint len;\n\n\tif( !COM_CheckString( name ))\n\t\treturn false;\n\n\tlen = Q_strlen( name );\n\n\t// because multi-layered textures can exceed name string\n\tif( len >= sizeof( gl_textures->name ))\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"LoadTexture: too long name %s (%d)\\n\", name, len );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n================\nGL_TextureForName\n================\n*/\nstatic gl_texture_t *GL_TextureForName( const char *name )\n{\n\tgl_texture_t\t*tex;\n\tuint\t\thash;\n\n\t// find the texture in array\n\thash = COM_HashKey( name, TEXTURES_HASH_SIZE );\n\n\tfor( tex = gl_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash )\n\t{\n\t\tif( !Q_stricmp( tex->name, name ))\n\t\t\treturn tex;\n\t}\n\n\treturn NULL;\n}\n\n/*\n================\nGL_AllocTexture\n================\n*/\nstatic gl_texture_t *GL_AllocTexture( const char *name, texFlags_t flags )\n{\n\tconst qboolean skyboxhack = FBitSet( flags, TF_SKYSIDE ) && glConfig.context == CONTEXT_TYPE_GL;\n\tgl_texture_t *tex = NULL;\n\tGLuint texnum = 1;\n\n\tif( !skyboxhack )\n\t{\n\t\t// keep generating new texture names to avoid collision with predefined skybox objects\n\t\tdo\n\t\t{\n\t\t\tpglGenTextures( 1, &texnum );\n\t\t}\n\t\twhile( texnum >= SKYBOX_BASE_NUM && texnum <= SKYBOX_BASE_NUM + SKYBOX_MAX_SIDES );\n\t}\n\telse texnum = tr.skyboxbasenum;\n\n\t// try to match texture slot and texture handle because of buggy games\n\tif( texnum >= MAX_TEXTURES || gl_textures[texnum].texnum != 0 )\n\t{\n\t\t// find a free texture_t slot\n\t\tuint i;\n\n\t\tfor( i = 0; i < MAX_TEXTURES; i++ )\n\t\t{\n\t\t\tif( gl_textures[i].texnum )\n\t\t\t\tcontinue;\n\n\t\t\ttex = &gl_textures[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\telse tex = &gl_textures[texnum];\n\n\tif( tex == NULL )\n\t{\n\t\tgEngfuncs.Host_Error( \"%s: MAX_TEXTURES limit exceeds\\n\", __func__ );\n\t\treturn NULL;\n\t}\n\n\t// copy initial params\n\tQ_strncpy( tex->name, name, sizeof( tex->name ));\n\ttex->texnum = texnum;\n\ttex->flags = flags;\n\n\t// increase counter\n\tgl_numTextures = Q_max(( tex - gl_textures ) + 1, gl_numTextures );\n\tif( skyboxhack )\n\t\ttr.skyboxbasenum++;\n\n\t// add to hash table\n\ttex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE );\n\ttex->nextHash = gl_texturesHashTable[tex->hashValue];\n\tgl_texturesHashTable[tex->hashValue] = tex;\n\n\treturn tex;\n}\n\n/*\n================\nGL_DeleteTexture\n================\n*/\nstatic void GL_DeleteTexture( gl_texture_t *tex )\n{\n\tgl_texture_t\t**prev;\n\tgl_texture_t\t*cur;\n\n\tASSERT( tex != NULL );\n\n\t// already freed?\n\tif( !tex->texnum ) return;\n\n\t// debug\n\tif( !tex->name[0] )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: trying to free unnamed texture with texnum %i\\n\", __func__, tex->texnum );\n\t\treturn;\n\t}\n\n\t// remove from hash table\n\tprev = &gl_texturesHashTable[tex->hashValue];\n\n\twhile( 1 )\n\t{\n\t\tcur = *prev;\n\t\tif( !cur ) break;\n\n\t\tif( cur == tex )\n\t\t{\n\t\t\t*prev = cur->nextHash;\n\t\t\tbreak;\n\t\t}\n\t\tprev = &cur->nextHash;\n\t}\n\n\t// invalidate texture units state cache\n\tfor( int i = 0; i < MAX_TEXTURE_UNITS; i++ )\n\t{\n\t\tif( glState.currentTextures[i] == tex->texnum )\n\t\t{\n\t\t\tif( glState.currentTextureTargets[i] != GL_NONE )\n\t\t\t{\n\t\t\t\tGL_SelectTexture( i );\n\t\t\t\tpglDisable( glState.currentTextureTargets[i] );\n\t\t\t}\n\t\t\tglState.currentTextureTargets[i] = GL_NONE;\n\t\t\tglState.currentTextures[i] = -1;\n\t\t\tglState.currentTexturesIndex[i] = 0;\n\t\t}\n\t}\n\n\t// release source\n\tif( tex->original )\n\t\tgEngfuncs.FS_FreeImage( tex->original );\n\n\tif( glw_state.initialized )\n\t\tpglDeleteTextures( 1, &tex->texnum );\n\tmemset( tex, 0, sizeof( *tex ));\n}\n\n/*\n================\nGL_UpdateTexSize\n\nrecalc image room\n================\n*/\nvoid GL_UpdateTexSize( int texnum, int width, int height, int depth )\n{\n\tint\t\ti, j, texsize;\n\tint\t\tnumSides;\n\tgl_texture_t\t*tex;\n\n\tif( texnum <= 0 || texnum >= MAX_TEXTURES )\n\t\treturn;\n\n\ttex = &gl_textures[texnum];\n\tnumSides = FBitSet( tex->flags, TF_CUBEMAP ) ? 6 : 1;\n\tGL_SetTextureDimensions( tex, width, height, depth );\n\ttex->size = 0; // recompute now\n\n\tfor( i = 0; i < numSides; i++ )\n\t{\n\t\tfor( j = 0; j < Q_max( 1, tex->numMips ); j++ )\n\t\t{\n\t\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\t\ttexsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );\n\t\t\ttex->size += texsize;\n\t\t}\n\t}\n}\n\n/*\n================\nGL_LoadTexture\n================\n*/\nint GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags )\n{\n\tgl_texture_t\t*tex;\n\trgbdata_t\t\t*pic;\n\tuint\t\tpicFlags = 0;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )))\n\t\treturn (tex - gl_textures);\n\n\tif( FBitSet( flags, TF_NOFLIP_TGA ))\n\t\tSetBits( picFlags, IL_DONTFLIP_TGA );\n\n\tif( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE ))\n\t\tSetBits( picFlags, IL_KEEP_8BIT );\n\n\t// set some image flags\n\tgEngfuncs.Image_SetForceFlags( picFlags );\n\n\tpic = gEngfuncs.FS_LoadImage( name, buf, size );\n\tif( !pic ) return 0; // couldn't loading image\n\n\t// allocate the new one\n\ttex = GL_AllocTexture( name, flags );\n\tGL_ProcessImage( tex, pic );\n\n\tif( !GL_UploadTexture( tex, pic ))\n\t{\n\t\tmemset( tex, 0, sizeof( gl_texture_t ));\n\t\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\t\treturn 0;\n\t}\n\n\tGL_ApplyTextureParams( tex ); // update texture filter, wrap etc\n\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\n\t// NOTE: always return texnum as index in array or engine will stop work !!!\n\treturn tex - gl_textures;\n}\n\n/*\n================\nGL_LoadTextureArray\n================\n*/\nint GL_LoadTextureArray( const char **names, int flags )\n{\n\trgbdata_t\t\t*pic, *src;\n\tchar\t\tbasename[256];\n\tuint\t\tnumLayers = 0;\n\tuint\t\tpicFlags = 0;\n\tchar\t\tname[256];\n\tgl_texture_t\t*tex;\n\tsize_t\t\tlen = 0;\n\tint\t\tret = 0;\n\tuint\t\ti, j;\n\n\tif( !names || !names[0] || !glw_state.initialized )\n\t\treturn 0;\n\n\t// count layers (g-cont. this is pontentially unsafe loop)\n\tfor( i = 0; i < glConfig.max_2d_texture_layers && ( *names[i] != '\\0' ); i++ )\n\t\tnumLayers++;\n\tname[0] = '\\0';\n\n\tif( numLayers <= 0 ) return 0;\n\n\t// create complexname from layer names\n\tfor( i = 0; i < numLayers - 1; i++ )\n\t{\n\t\tCOM_FileBase( names[i], basename, sizeof( basename ));\n\t\tret = Q_snprintf( &name[len], sizeof( name ) - len, \"%s|\", basename );\n\n\t\tif( ret == -1 )\n\t\t\treturn 0;\n\n\t\tlen += ret;\n\t}\n\n\tCOM_FileBase( names[i], basename, sizeof( basename ));\n\tret = Q_snprintf( &name[len], sizeof( name ) - len, \"%s[%i]\", basename, numLayers );\n\n\tif( ret == -1 )\n\t\treturn 0;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )))\n\t\treturn (tex - gl_textures);\n\n\t// load all the images and pack it into single image\n\tfor( i = 0, pic = NULL; i < numLayers; i++ )\n\t{\n\t\tsize_t\tsrcsize, dstsize, mipsize;\n\n\t\tsrc = gEngfuncs.FS_LoadImage( names[i], NULL, 0 );\n\t\tif( !src ) break; // coldn't find layer\n\n\t\tif( pic )\n\t\t{\n\t\t\t// mixed mode: DXT + RGB\n\t\t\tif( pic->type != src->type )\n\t\t\t{\n\t\t\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: mismatch image format for %s and %s\\n\", __func__, names[0], names[i] );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// different mipcount\n\t\t\tif( pic->numMips != src->numMips )\n\t\t\t{\n\t\t\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: mismatch mip count for %s and %s\\n\", __func__, names[0], names[i] );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif( pic->encode != src->encode )\n\t\t\t{\n\t\t\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: mismatch custom encoding for %s and %s\\n\", __func__, names[0], names[i] );\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// but allow to rescale raw images\n\t\t\tif( ImageRAW( pic->type ) && ImageRAW( src->type ) && ( pic->width != src->width || pic->height != src->height ))\n\t\t\t\tgEngfuncs.Image_Process( &src, pic->width, pic->height, IMAGE_RESAMPLE, 0.0f );\n\n\t\t\tif( pic->size != src->size )\n\t\t\t{\n\t\t\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: mismatch image size for %s and %s\\n\", __func__, names[0], names[i] );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// create new image\n\t\t\tpic = Mem_Malloc( r_temppool, sizeof( rgbdata_t ));\n\t\t\tmemcpy( pic, src, sizeof( rgbdata_t ));\n\n\t\t\t// expand pic buffer for all layers\n\t\t\tpic->buffer = Mem_Malloc( r_temppool, pic->size * numLayers );\n\t\t\tpic->depth = 0;\n\t\t}\n\n\t\tmipsize = srcsize = dstsize = 0;\n\n\t\tfor( j = 0; j < Q_max( 1, pic->numMips ); j++ )\n\t\t{\n\t\t\tint width = Q_max( 1, ( pic->width >> j ));\n\t\t\tint height = Q_max( 1, ( pic->height >> j ));\n\t\t\tmipsize = GL_CalcImageSize( pic->type, width, height, 1 );\n\t\t\tmemcpy( pic->buffer + dstsize + mipsize * i, src->buffer + srcsize, mipsize );\n\t\t\tdstsize += mipsize * numLayers;\n\t\t\tsrcsize += mipsize;\n\t\t}\n\n\t\tgEngfuncs.FS_FreeImage( src );\n\n\t\t// increase layers\n\t\tpic->depth++;\n\t}\n\n\t// there were errors\n\tif( !pic || ( pic->depth != numLayers ))\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: not all layers were loaded. Texture array is not created\\n\", __func__ );\n\t\tif( pic ) gEngfuncs.FS_FreeImage( pic );\n\t\treturn 0;\n\t}\n\n\t// it's multilayer image!\n\tSetBits( pic->flags, IMAGE_MULTILAYER );\n\tpic->size *= numLayers;\n\n\t// allocate the new one\n\ttex = GL_AllocTexture( name, flags );\n\tGL_ProcessImage( tex, pic );\n\n\tif( !GL_UploadTexture( tex, pic ))\n\t{\n\t\tmemset( tex, 0, sizeof( gl_texture_t ));\n\t\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\t\treturn 0;\n\t}\n\n\tGL_ApplyTextureParams( tex ); // update texture filter, wrap etc\n\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\n\t// NOTE: always return texnum as index in array or engine will stop work !!!\n\treturn tex - gl_textures;\n}\n\n/*\n================\nGL_LoadTextureFromBuffer\n================\n*/\nint GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update )\n{\n\tgl_texture_t\t*tex;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )) && !update )\n\t\treturn (tex - gl_textures);\n\n\t// couldn't loading image\n\tif( !pic ) return 0;\n\n\tif( update )\n\t{\n\t\tif( tex == NULL )\n\t\t\tgEngfuncs.Host_Error( \"%s: couldn't find texture %s for update\\n\", __func__, name );\n\t\tSetBits( tex->flags, flags );\n\t}\n\telse\n\t{\n\t\t// allocate the new one\n\t\ttex = GL_AllocTexture( name, flags );\n\t}\n\n\tGL_ProcessImage( tex, pic );\n\n\tif( !GL_UploadTexture( tex, pic ))\n\t{\n\t\tmemset( tex, 0, sizeof( gl_texture_t ));\n\t\treturn 0;\n\t}\n\n\tGL_ApplyTextureParams( tex ); // update texture filter, wrap etc\n\treturn (tex - gl_textures);\n}\n\n/*\n================\nGL_CreateTexture\n\ncreates texture from buffer\n================\n*/\nint GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags )\n{\n\tqboolean\tupdate = FBitSet( flags, TF_UPDATE ) ? true : false;\n\tint\tdatasize = 1;\n\trgbdata_t\tr_empty;\n\n\tif( FBitSet( flags, TF_ARB_16BIT ))\n\t\tdatasize = 2;\n\telse if( FBitSet( flags, TF_ARB_FLOAT ))\n\t\tdatasize = 4;\n\n\tClearBits( flags, TF_UPDATE );\n\tmemset( &r_empty, 0, sizeof( r_empty ));\n\tr_empty.width = width;\n\tr_empty.height = height;\n\tr_empty.type = PF_RGBA_32;\n\tr_empty.size = r_empty.width * r_empty.height * datasize * 4;\n\tr_empty.buffer = (byte *)buffer;\n\n\t// clear invalid combinations\n\tClearBits( flags, TF_TEXTURE_3D );\n\n\t// if image not luminance and not alphacontrast it will have color\n\tif( !FBitSet( flags, TF_LUMINANCE ) && !FBitSet( flags, TF_ALPHACONTRAST ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_COLOR );\n\n\tif( FBitSet( flags, TF_HAS_ALPHA ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_ALPHA );\n\n\tif( FBitSet( flags, TF_CUBEMAP ))\n\t{\n\t\tif( !GL_Support( GL_TEXTURE_CUBEMAP_EXT ))\n\t\t\treturn 0;\n\t\tSetBits( r_empty.flags, IMAGE_CUBEMAP );\n\t\tr_empty.size *= 6;\n\t}\n\n\treturn GL_LoadTextureFromBuffer( name, &r_empty, flags, update );\n}\n\n/*\n================\nGL_CreateTextureArray\n\ncreates texture array from buffer\n================\n*/\nint GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags )\n{\n\trgbdata_t\tr_empty;\n\n\tmemset( &r_empty, 0, sizeof( r_empty ));\n\tr_empty.width = Q_max( width, 1 );\n\tr_empty.height = Q_max( height, 1 );\n\tr_empty.depth = Q_max( depth, 1 );\n\tr_empty.type = PF_RGBA_32;\n\tr_empty.size = r_empty.width * r_empty.height * r_empty.depth * 4;\n\tr_empty.buffer = (byte *)buffer;\n\n\t// clear invalid combinations\n\tClearBits( flags, TF_CUBEMAP|TF_SKYSIDE|TF_HAS_LUMA|TF_MAKELUMA|TF_ALPHACONTRAST );\n\n\t// if image not luminance it will have color\n\tif( !FBitSet( flags, TF_LUMINANCE ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_COLOR );\n\n\tif( FBitSet( flags, TF_HAS_ALPHA ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_ALPHA );\n\n\tif( FBitSet( flags, TF_TEXTURE_3D ))\n\t{\n\t\tif( !GL_Support( GL_TEXTURE_3D_EXT ))\n\t\t\treturn 0;\n\t}\n\telse\n\t{\n\t\tif( !GL_Support( GL_TEXTURE_ARRAY_EXT ))\n\t\t\treturn 0;\n\t\tSetBits( r_empty.flags, IMAGE_MULTILAYER );\n\t}\n\n\treturn GL_LoadTextureInternal( name, &r_empty, flags );\n}\n\n/*\n================\nGL_FindTexture\n================\n*/\nint GL_FindTexture( const char *name )\n{\n\tgl_texture_t\t*tex;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )))\n\t\treturn (tex - gl_textures);\n\n\treturn 0;\n}\n\n/*\n================\nGL_FreeTexture\n================\n*/\nvoid GL_FreeTexture( GLenum texnum )\n{\n\t// number 0 it's already freed\n\tif( texnum <= 0 ) return;\n\n\tGL_DeleteTexture( &gl_textures[texnum] );\n}\n\n/*\n================\nGL_ProcessTexture\n================\n*/\nvoid GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )\n{\n\tgl_texture_t\t*image;\n\trgbdata_t\t\t*pic;\n\tint\t\tflags = 0;\n\n\tif( texnum <= 0 || texnum >= MAX_TEXTURES )\n\t\treturn; // missed image\n\timage = &gl_textures[texnum];\n\n\t// select mode\n\tif( gamma != -1.0f )\n\t{\n\t\tflags = IMAGE_LIGHTGAMMA;\n\t}\n\telse if( topColor != -1 && bottomColor != -1 )\n\t{\n\t\tflags = IMAGE_REMAP;\n\t}\n\telse\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: bad operation for %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\tif( !image->original )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: no input data for %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\tif( ImageCompressed( image->original->type ))\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: can't process compressed texture %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\t// all the operations makes over the image copy not an original\n\tpic = gEngfuncs.FS_CopyImage( image->original );\n\n\t// we need to expand image into RGBA buffer\n\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\tflags |= IMAGE_FORCE_RGBA;\n\n\tgEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f );\n\n\tGL_UploadTexture( image, pic );\n\tGL_ApplyTextureParams( image ); // update texture filter, wrap etc\n\n\tgEngfuncs.FS_FreeImage( pic );\n}\n\n/*\n================\nGL_TexMemory\n\nreturn size of all uploaded textures\n================\n*/\nint GL_TexMemory( void )\n{\n\tint\ti, total = 0;\n\n\tfor( i = 0; i < gl_numTextures; i++ )\n\t\ttotal += gl_textures[i].size;\n\n\treturn total;\n}\n\n/*\n==============================================================================\n\nINTERNAL TEXTURES\n\n==============================================================================\n*/\n/*\n==================\nGL_FakeImage\n==================\n*/\nstatic rgbdata_t *GL_FakeImage( int width, int height, int depth, int flags )\n{\n\tstatic byte\tdata2D[1024]; // 16x16x4\n\tstatic rgbdata_t\tr_image;\n\n\t// also use this for bad textures, but without alpha\n\tr_image.width = Q_max( 1, width );\n\tr_image.height = Q_max( 1, height );\n\tr_image.depth = Q_max( 1, depth );\n\tr_image.flags = flags;\n\tr_image.type = PF_RGBA_32;\n\tr_image.size = r_image.width * r_image.height * r_image.depth * 4;\n\tr_image.buffer = (r_image.size > sizeof( data2D )) ? NULL : data2D;\n\tr_image.palette = NULL;\n\tr_image.numMips = 1;\n\tr_image.encode = 0;\n\n\tif( FBitSet( r_image.flags, IMAGE_CUBEMAP ))\n\t\tr_image.size *= 6;\n\tmemset( data2D, 0xFF, sizeof( data2D ));\n\n\treturn &r_image;\n}\n\n/*\n==================\nR_InitDlightTexture\n==================\n*/\nvoid R_InitDlightTexture( void )\n{\n\trgbdata_t r_image =\n\t{\n\t\t.width = BLOCK_SIZE,\n\t\t.height = BLOCK_SIZE,\n\t\t.type = PF_RGBA_32,\n\t\t.flags = IMAGE_HAS_COLOR,\n\t\t.size = r_image.width * r_image.height * 4\n\t};\n\tqboolean update = false;\n\n\tif( tr.dlightTexture != 0 )\n\t\tupdate = true;\n\n\ttr.dlightTexture = GL_LoadTextureFromBuffer( \"*dlight\", &r_image, TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE, update );\n}\n\n/*\n==================\nGL_CreateInternalTextures\n==================\n*/\nstatic void GL_CreateInternalTextures( void )\n{\n\tint\tdx2, dy, d;\n\tint\tx, y;\n\trgbdata_t\t*pic;\n\n\t// emo-texture from quake1\n\tpic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR );\n\n\tfor( y = 0; y < 16; y++ )\n\t{\n\t\tfor( x = 0; x < 16; x++ )\n\t\t{\n\t\t\tif(( y < 8 ) ^ ( x < 8 ))\n\t\t\t\t((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF;\n\t\t\telse ((uint *)pic->buffer)[y*16+x] = 0xFF000000;\n\t\t}\n\t}\n\n\ttr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP );\n\n\t// particle texture from quake1\n\tpic = GL_FakeImage( 8, 8, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA );\n\n\tfor( x = 0; x < 8; x++ )\n\t{\n\t\tfor( y = 0; y < 8; y++ )\n\t\t{\n\t\t\tif( dottexture[x][y] )\n\t\t\t\tpic->buffer[( y * 8 + x ) * 4 + 3] = 255;\n\t\t\telse pic->buffer[( y * 8 + x ) * 4 + 3] = 0;\n\t\t}\n\t}\n\n\ttr.particleTexture = GL_LoadTextureInternal( REF_PARTICLE_TEXTURE, pic, TF_CLAMP );\n\n\t// white texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer)[x] = 0xFFFFFFFF;\n\ttr.whiteTexture = GL_LoadTextureInternal( REF_WHITE_TEXTURE, pic, TF_COLORMAP );\n\n\t// gray texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer)[x] = 0xFF7F7F7F;\n\ttr.grayTexture = GL_LoadTextureInternal( REF_GRAY_TEXTURE, pic, TF_COLORMAP );\n\n\t// black texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer)[x] = 0xFF000000;\n\ttr.blackTexture = GL_LoadTextureInternal( REF_BLACK_TEXTURE, pic, TF_COLORMAP );\n\n\t// cinematic dummy\n\tpic = GL_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR );\n\ttr.cinTexture = GL_LoadTextureInternal( \"*cintexture\", pic, TF_NOMIPMAP|TF_CLAMP );\n}\n\n/*\n===============\nR_TextureList_f\n===============\n*/\nvoid R_TextureList_f( void )\n{\n\tgl_texture_t\t*image;\n\tint\t\ti, texCount, bytes = 0;\n\n\tgEngfuncs.Con_Printf( \"\\n\" );\n\tgEngfuncs.Con_Printf( \" -id-   -w-  -h-     -size- -fmt- -type- -data-  -encode- -wrap- -depth- -name--------\\n\" );\n\n\tfor( i = texCount = 0, image = gl_textures; i < gl_numTextures; i++, image++ )\n\t{\n\t\tif( !image->texnum ) continue;\n\n\t\tbytes += image->size;\n\t\ttexCount++;\n\n\t\tgEngfuncs.Con_Printf( \"%4i: \", i );\n\t\tgEngfuncs.Con_Printf( \"%4i %4i \", image->width, image->height );\n\t\tgEngfuncs.Con_Printf( \"%12s \", Q_memprint( image->size ));\n\n\t\tswitch( image->format )\n\t\t{\n\t\tcase GL_COMPRESSED_RGBA_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CRGBA \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RGB_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CRGB  \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_LUMINANCE_ALPHA_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CLA   \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_LUMINANCE_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CL    \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_ALPHA_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CA    \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_INTENSITY_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CI    \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RGB_S3TC_DXT1_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"DXT1c \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"DXT1a \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"DXT3  \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"DXT5  \" );\n\t\t\tbreak;\n\t\tcase GL_COMPRESSED_RED_GREEN_RGTC2_EXT:\n\t\tcase GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI:\n\t\t\tgEngfuncs.Con_Printf( \"ATI2  \" );\n\t\t\tbreak;\n\t\tcase GL_RGBA:\n\t\t\tgEngfuncs.Con_Printf( \"RGBA  \" );\n\t\t\tbreak;\n\t\tcase GL_RGBA8:\n\t\t\tgEngfuncs.Con_Printf( \"RGBA8 \" );\n\t\t\tbreak;\n\t\tcase GL_RGBA4:\n\t\t\tgEngfuncs.Con_Printf( \"RGBA4 \" );\n\t\t\tbreak;\n\t\tcase GL_RGB:\n\t\t\tgEngfuncs.Con_Printf( \"RGB   \" );\n\t\t\tbreak;\n\t\tcase GL_RGB8:\n\t\t\tgEngfuncs.Con_Printf( \"RGB8  \" );\n\t\t\tbreak;\n\t\tcase GL_RGB5:\n\t\t\tgEngfuncs.Con_Printf( \"RGB5  \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE4_ALPHA4:\n\t\t\tgEngfuncs.Con_Printf( \"L4A4  \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE_ALPHA:\n\t\tcase GL_LUMINANCE8_ALPHA8:\n\t\t\tgEngfuncs.Con_Printf( \"L8A8  \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE4:\n\t\t\tgEngfuncs.Con_Printf( \"L4    \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE:\n\t\tcase GL_LUMINANCE8:\n\t\t\tgEngfuncs.Con_Printf( \"L8    \" );\n\t\t\tbreak;\n\t\tcase GL_ALPHA8:\n\t\t\tgEngfuncs.Con_Printf( \"A8    \" );\n\t\t\tbreak;\n\t\tcase GL_INTENSITY8:\n\t\t\tgEngfuncs.Con_Printf( \"I8    \" );\n\t\t\tbreak;\n\t\tcase GL_DEPTH_COMPONENT:\n\t\tcase GL_DEPTH_COMPONENT24:\n\t\t\tgEngfuncs.Con_Printf( \"DPTH24\" );\n\t\t\tbreak;\n\t\tcase GL_DEPTH_COMPONENT32F:\n\t\t\tgEngfuncs.Con_Printf( \"DPTH32\" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE16F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"L16F  \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE32F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"L32F  \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE_ALPHA16F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"LA16F \" );\n\t\t\tbreak;\n\t\tcase GL_LUMINANCE_ALPHA32F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"LA32F \" );\n\t\t\tbreak;\n\t\tcase GL_RG16F:\n\t\t\tgEngfuncs.Con_Printf( \"RG16F \" );\n\t\t\tbreak;\n\t\tcase GL_RG32F:\n\t\t\tgEngfuncs.Con_Printf( \"RG32F \" );\n\t\t\tbreak;\n\t\tcase GL_RGB16F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"RGB16F\" );\n\t\t\tbreak;\n\t\tcase GL_RGB32F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"RGB32F\" );\n\t\t\tbreak;\n\t\tcase GL_RGBA16F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"RGBA16F\" );\n\t\t\tbreak;\n\t\tcase GL_RGBA32F_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"RGBA32F\" );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tgEngfuncs.Con_Printf( \" ^1ERROR^7 \" );\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch( image->target )\n\t\t{\n\t\tcase GL_TEXTURE_1D:\n\t\t\tgEngfuncs.Con_Printf( \" 1D   \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_2D:\n\t\t\tgEngfuncs.Con_Printf( \" 2D   \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_3D:\n\t\t\tgEngfuncs.Con_Printf( \" 3D   \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_CUBE_MAP_ARB:\n\t\t\tgEngfuncs.Con_Printf( \"CUBE  \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_RECTANGLE_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"RECT  \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_2D_ARRAY_EXT:\n\t\t\tgEngfuncs.Con_Printf( \"ARRAY \" );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_2D_MULTISAMPLE:\n\t\t\tgEngfuncs.Con_Printf( \"MSAA  \");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tgEngfuncs.Con_Printf( \"????  \" );\n\t\t\tbreak;\n\t\t}\n\n\t\tif( image->flags & TF_NORMALMAP )\n\t\t\tgEngfuncs.Con_Printf( \"normal  \" );\n\t\telse gEngfuncs.Con_Printf( \"diffuse \" );\n\n\t\tswitch( image->encode )\n\t\t{\n\t\tcase DXT_ENCODE_COLOR_YCoCg:\n\t\t\tgEngfuncs.Con_Printf( \"YCoCg     \" );\n\t\t\tbreak;\n\t\tcase DXT_ENCODE_NORMAL_AG_ORTHO:\n\t\t\tgEngfuncs.Con_Printf( \"ortho     \" );\n\t\t\tbreak;\n\t\tcase DXT_ENCODE_NORMAL_AG_STEREO:\n\t\t\tgEngfuncs.Con_Printf( \"stereo    \" );\n\t\t\tbreak;\n\t\tcase DXT_ENCODE_NORMAL_AG_PARABOLOID:\n\t\t\tgEngfuncs.Con_Printf( \"parabolic \" );\n\t\t\tbreak;\n\t\tcase DXT_ENCODE_NORMAL_AG_QUARTIC:\n\t\t\tgEngfuncs.Con_Printf( \"quartic   \" );\n\t\t\tbreak;\n\t\tcase DXT_ENCODE_NORMAL_AG_AZIMUTHAL:\n\t\t\tgEngfuncs.Con_Printf( \"azimuthal \" );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tgEngfuncs.Con_Printf( \"default   \" );\n\t\t\tbreak;\n\t\t}\n\n\t\tif( image->flags & TF_CLAMP )\n\t\t\tgEngfuncs.Con_Printf( \"clamp  \" );\n\t\telse if( image->flags & TF_BORDER )\n\t\t\tgEngfuncs.Con_Printf( \"border \" );\n\t\telse gEngfuncs.Con_Printf( \"repeat \" );\n\t\tgEngfuncs.Con_Printf( \"   %d  \", image->depth );\n\t\tgEngfuncs.Con_Printf( \"  %s\\n\", image->name );\n\t}\n\n\tgEngfuncs.Con_Printf( \"---------------------------------------------------------\\n\" );\n\tgEngfuncs.Con_Printf( \"%i total textures\\n\", texCount );\n\tgEngfuncs.Con_Printf( \"%s total memory used\\n\", Q_memprint( bytes ));\n\tgEngfuncs.Con_Printf( \"\\n\" );\n}\n\n/*\n===============\nR_InitImages\n===============\n*/\nvoid R_InitImages( void )\n{\n\tmemset( gl_textures, 0, sizeof( gl_textures ));\n\tmemset( gl_texturesHashTable, 0, sizeof( gl_texturesHashTable ));\n\tgl_numTextures = 0;\n\n\t// create unused 0-entry\n\tQ_strncpy( gl_textures->name, \"*unused*\", sizeof( gl_textures->name ));\n\tgl_textures->hashValue = COM_HashKey( gl_textures->name, TEXTURES_HASH_SIZE );\n\tgl_textures->nextHash = gl_texturesHashTable[gl_textures->hashValue];\n\tgl_texturesHashTable[gl_textures->hashValue] = gl_textures;\n\tgl_numTextures = 1;\n\n\t// validate cvars\n\tR_SetTextureParameters();\n\tGL_CreateInternalTextures();\n\n\tgEngfuncs.Cmd_AddCommand( \"texturelist\", R_TextureList_f, \"display loaded textures list\" );\n}\n\n/*\n===============\nR_ShutdownImages\n===============\n*/\nvoid R_ShutdownImages( void )\n{\n\tgl_texture_t\t*tex;\n\tint\t\ti;\n\n\tgEngfuncs.Cmd_RemoveCommand( \"texturelist\" );\n\tGL_CleanupAllTextureUnits();\n\n\tfor( i = 0, tex = gl_textures; i < gl_numTextures; i++, tex++ )\n\t\tGL_DeleteTexture( tex );\n\n\tmemset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));\n\tmemset( gl_texturesHashTable, 0, sizeof( gl_texturesHashTable ));\n\tmemset( gl_textures, 0, sizeof( gl_textures ));\n\tgl_numTextures = 0;\n}\n\nvoid R_TextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath )\n{\n\tif( host_allow_materials->value != 2.0f )\n\t\treturn;\n\n\tif( gl_texturenum > 0 )\n\t\tgEngfuncs.Con_Printf( \"Looking for %s tex replacement...\" S_GREEN \"OK (%s)\\n\", modelname, foundpath );\n\telse if( gl_texturenum < 0 )\n\t\tgEngfuncs.Con_Printf( \"Looking for %s tex replacement...\" S_YELLOW \"MISS (%s)\\n\", modelname, foundpath );\n\telse\n\t\tgEngfuncs.Con_Printf( \"Looking for %s tex replacement...\" S_RED \"FAIL (%s)\\n\", modelname, foundpath );\n}\n\nqboolean R_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... )\n{\n\tva_list ap;\n\tint ret;\n\n\tva_start( ap, fmt );\n\tret = Q_vsnprintf( out,\tsize, fmt, ap );\n\tva_end( ap );\n\n\tif( ret < 0 )\n\t{\n\t\tR_TextureReplacementReport( modelname, -1, \"overflow\" );\n\t\treturn false;\n\t}\n\n\tif( gEngfuncs.fsapi->FileExists( out, false ))\n\t\treturn true;\n\n\tR_TextureReplacementReport( modelname, -1, out );\n\treturn false;\n}\n"
  },
  {
    "path": "ref/gl/gl_local.h",
    "content": "/*\ngl_local.h - renderer local declarations\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GL_LOCAL_H\n#define GL_LOCAL_H\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"cvardef.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"cl_entity.h\"\n#include \"render_api.h\"\n#include \"protocol.h\"\n#include \"dlight.h\"\n#include \"gl_frustum.h\"\n#include \"ref_api.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_params.h\"\n#include \"enginefeatures.h\"\n#include \"com_strings.h\"\n#include \"pm_movevars.h\"\n#include \"cvardef.h\"\n#include \"gl_export.h\"\n#include \"wadfile.h\"\n#include \"common/mod_local.h\"\n\n#if XASH_PSVITA\nint VGL_ShimInit( void );\nvoid VGL_ShimShutdown( void );\nvoid VGL_ShimEndFrame( void );\n#endif\n#if !defined(XASH_GL_STATIC)\n#include \"gl2_shim/gl2_shim.h\"\n#endif\n\n#ifndef offsetof\n#ifdef __GNUC__\n#define offsetof(s,m) __builtin_offsetof(s,m)\n#else\n#define offsetof(s,m) (size_t)&(((s *)0)->m)\n#endif\n#endif\n\n#define ASSERT(x) if(!( x )) gEngfuncs.Host_Error( \"assert failed at %s:%i\\n\", __FILE__, __LINE__ )\n#define Assert(x) if(!( x )) gEngfuncs.Host_Error( \"assert failed at %s:%i\\n\", __FILE__, __LINE__ )\n\n#include <stdio.h>\n\n// make mod_ref.h?\n#define LM_SAMPLE_SIZE             16\n\n\nextern poolhandle_t r_temppool;\n\n#define BLOCK_SIZE\t\ttr.block_size\t// lightmap blocksize\n#define BLOCK_SIZE_DEFAULT\t128\t\t// for keep backward compatibility\n#define BLOCK_SIZE_MAX\t1024\n\n#define MAX_TEXTURES            8192\t// a1ba: increased by users request\n#define MAX_DETAIL_TEXTURES\t256\n#define MAX_LIGHTMAPS\t256\n#define SUBDIVIDE_SIZE\t64\n#define MAX_DECAL_SURFS\t4096\n#define MAX_DRAW_STACK\t2\t\t// normal view and menu view\n\n#define SHADEDOT_QUANT \t16\t\t// precalculated dot products for quantized angles\n#define SHADE_LAMBERT\t1.4953241\n#define DEFAULT_ALPHATEST\t0.0f\n\n// refparams\n#define RP_NONE\t\t0\n#define RP_ENVVIEW\t\tBIT( 0 )\t// used for cubemapshot\n#define RP_OLDVIEWLEAF\tBIT( 1 )\n#define RP_CLIPPLANE\tBIT( 2 )\n\n#define RP_NONVIEWERREF\t(RP_ENVVIEW)\n#define R_ModelOpaque( rm )\t( rm == kRenderNormal )\n#define R_StaticEntity( ent )\t( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles ))\n#define RP_LOCALCLIENT( e )\t((e) != NULL && (e)->index == ( gp_cl->playernum + 1 ) && e->player )\n#define RP_NORMALPASS()\t( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 )\n\n#define CL_IsViewEntityLocalPlayer() ( gp_cl->viewentity == ( gp_cl->playernum + 1 ))\n\n#define CULL_VISIBLE\t0\t\t// not culled\n#define CULL_BACKSIDE\t1\t\t// backside of transparent wall\n#define CULL_FRUSTUM\t2\t\t// culled by frustum\n#define CULL_VISFRAME\t3\t\t// culled by PVS\n#define CULL_OTHER\t\t4\t\t// culled by other reason\n\n#define HACKS_RELATED_HLMODS\t\t// some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time\n\n#define SKYBOX_BASE_NUM 5800 // set skybox base (to let some mods load hi-res skyboxes)\n\ntypedef struct gltexture_s\n{\n\tchar\t\tname[256];\t// game path, including extension (can be store image programs)\n\tword\t\tsrcWidth;\t\t// keep unscaled sizes\n\tword\t\tsrcHeight;\n\tword\t\twidth;\t\t// upload width\\height\n\tword\t\theight;\n\tword\t\tdepth;\t\t// texture depth or count of layers for 2D_ARRAY\n\tbyte\t\tnumMips;\t\t// mipmap count\n\n\tGLuint\t\ttarget;\t\t// glTarget\n\tGLuint\t\ttexnum;\t\t// gl texture binding\n\tGLint\t\tformat;\t\t// uploaded format\n\tGLint\t\tencode;\t\t// using GLSL decoder\n\ttexFlags_t\tflags;\n\n\trgba_t\t\tfogParams;\t// some water textures\n\t\t\t\t\t// contain info about underwater fog\n\trgbdata_t\t\t*original;\t// keep original image\n\n\t// debug info\n\tsize_t\t\tsize;\t\t// upload size for debug targets\n\n\t// detail textures stuff\n\tfloat\t\txscale;\n\tfloat\t\tyscale;\n\n\tuint\t\thashValue;\n\tstruct gltexture_s\t*nextHash;\n} gl_texture_t;\n\ntypedef struct\n{\n\tint\t\tparams;\t\t// rendering parameters\n\n\tqboolean\t\tdrawWorld;\t// ignore world for drawing PlayerModel\n\tqboolean\t\tisSkyVisible;\t// sky is visible\n\tqboolean\t\tonlyClientDraw;\t// disabled by client request\n\tqboolean\t\tdrawOrtho;\t// draw world as orthogonal projection\n\n\tfloat\t\tfov_x, fov_y;\t// current view fov\n\n\tcl_entity_t\t*currententity;\n\tmodel_t\t\t*currentmodel;\n\tcl_entity_t\t*currentbeam;\t// same as above but for beams\n\n\tint\t\tviewport[4];\n\tgl_frustum_t\tfrustum;\n\n\tmleaf_t\t\t*viewleaf;\n\tmleaf_t\t\t*oldviewleaf;\n\tvec3_t\t\tpvsorigin;\n\tvec3_t\t\tvieworg;\t\t// locked vieworigin\n\tvec3_t\t\tviewangles;\n\tvec3_t\t\tvforward;\n\tvec3_t\t\tvright;\n\tvec3_t\t\tvup;\n\n\tvec3_t\t\tcullorigin;\n\tvec3_t\t\tcull_vforward;\n\tvec3_t\t\tcull_vright;\n\tvec3_t\t\tcull_vup;\n\n\tfloat\t\tfarClip;\n\n\tqboolean\t\tfogCustom;\n\tqboolean\t\tfogEnabled;\n\tqboolean\t\tfogSkybox;\n\tvec4_t\t\tfogColor;\n\tfloat\t\tfogDensity;\n\tfloat\t\tfogStart;\n\tfloat\t\tfogEnd;\n\tint\t\tcached_contents;\t// in water\n\tint\t\tcached_waterlevel;\t// was in water\n\n\tfloat\t\tskyMins[2][SKYBOX_MAX_SIDES];\n\tfloat\t\tskyMaxs[2][SKYBOX_MAX_SIDES];\n\n\tmatrix4x4\t\tobjectMatrix;\t\t// currententity matrix\n\tmatrix4x4\t\tworldviewMatrix;\t\t// modelview for world\n\tmatrix4x4\t\tmodelviewMatrix;\t\t// worldviewMatrix * objectMatrix\n\n\tmatrix4x4\t\tprojectionMatrix;\n\tmatrix4x4\t\tworldviewProjectionMatrix;\t// worldviewMatrix * projectionMatrix\n\tbyte\t\tvisbytes[(MAX_MAP_LEAFS+7)/8];// actual PVS for current frame\n\n\tfloat\t\tviewplanedist;\n\tmplane_t\t\tclipPlane;\n} ref_instance_t;\n\ntypedef struct\n{\n\tcl_entity_t\t*solid_entities[MAX_VISIBLE_PACKET];\t// opaque moving or alpha brushes\n\tcl_entity_t\t*trans_entities[MAX_VISIBLE_PACKET];\t// translucent brushes\n\tcl_entity_t\t*beam_entities[MAX_VISIBLE_PACKET];\n\tuint\t\tnum_solid_entities;\n\tuint\t\tnum_trans_entities;\n\tuint\t\tnum_beam_entities;\n} draw_list_t;\n\ntypedef struct\n{\n\tint\t\tdefaultTexture;   \t// use for bad textures\n\tint\t\tparticleTexture;\n\tint\t\twhiteTexture;\n\tint\t\tgrayTexture;\n\tint\t\tblackTexture;\n\tint\t\tsolidskyTexture;\t// quake1 solid-sky layer\n\tint\t\talphaskyTexture;\t// quake1 alpha-sky layer\n\tint\t\tlightmapTextures[MAX_LIGHTMAPS];\n\tint\t\tdlightTexture;\t// custom dlight texture\n\tint\t\tskyboxTextures[SKYBOX_MAX_SIDES];\t// skybox sides\n\tint\t\tcinTexture;      \t// cinematic texture\n\n\tint\t\tskytexturenum;\t// this not a gl_texturenum!\n\tint\t\tskyboxbasenum;\t// start with 5800\n\n\t// entity lists\n\tdraw_list_t\tdraw_stack[MAX_DRAW_STACK];\n\tint\t\tdraw_stack_pos;\n\tdraw_list_t\t*draw_list;\n\n\tmsurface_t\t*draw_decals[MAX_DECAL_SURFS];\n\tint\t\tnum_draw_decals;\n\n\t// OpenGL matrix states\n\tqboolean\t\tmodelviewIdentity;\n\n\tint\t\tvisframecount;\t// PVS frame\n\tint\t\tdlightframecount;\t// dynamic light frame\n\tint\t\trealframecount;\t// not including viewpasses\n\tint\t\tframecount;\n\n\tqboolean\t\tfCustomRendering;\n\tqboolean\t\tfResetVis;\n\tqboolean\t\tfFlipViewModel;\n\n\tbyte\t\tvisbytes[(MAX_MAP_LEAFS+7)/8];\t// member custom PVS\n\tint\t\tlightstylevalue[MAX_LIGHTSTYLES];\t// value 0 - 65536\n\tint\t\tblock_size;\t\t\t// lightmap blocksize\n\n\tdouble\t\tframetime;\t// special frametime for multipass rendering (will set to 0 on a nextview)\n\tfloat\t\tblend;\t\t// global blend value\n\n\t// cull info\n\tvec3_t\t\tmodelorg;\t\t// relative to viewpoint\n\n\t// get from engine\n\tworld_static_t *world;\n\tcl_entity_t *entities;\n\tmovevars_t *movevars;\n\tcolor24 *palette;\n\tcl_entity_t *viewent;\n\tdlight_t *dlights;\n\tdlight_t *elights;\n\tbyte *texgammatable;\n\tuint *lightgammatable;\n\tuint *lineargammatable;\n\tuint *screengammatable;\n\n\tuint max_entities;\n} gl_globals_t;\n\ntypedef struct\n{\n\tuint\t\tc_world_polys;\n\tuint\t\tc_studio_polys;\n\tuint\t\tc_sprite_polys;\n\tuint\t\tc_alias_polys;\n\tuint\t\tc_world_leafs;\n\n\tuint\t\tc_view_beams_count;\n\tuint\t\tc_active_tents_count;\n\tuint\t\tc_alias_models_drawn;\n\tuint\t\tc_studio_models_drawn;\n\tuint\t\tc_sprite_models_drawn;\n\tuint\t\tc_particle_count;\n\n\tuint\t\tc_client_ents;\t// entities that moved to client\n\tdouble\t\tt_world_node;\n\tdouble\t\tt_world_draw;\n} ref_speeds_t;\n\nextern ref_speeds_t\t\tr_stats;\nextern ref_instance_t\tRI;\nextern gl_globals_t\ttr;\n\nextern float\t\tgldepthmin, gldepthmax;\n#define r_numEntities\t(tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities)\n#define r_numStatics\t(r_stats.c_client_ents)\n#define Mod_AllowMaterials() (host_allow_materials->value && !FBitSet( gp_host->features, ENGINE_DISABLE_HDTEXTURES ))\n\n//\n// gl_backend.c\n//\nvoid GL_BackendStartFrame( void );\nvoid GL_BackendEndFrame( void );\nvoid GL_CleanUpTextureUnits( int last );\nvoid GL_Bind( GLint tmu, GLenum texnum );\nvoid GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t );\nvoid GL_SetTexCoordArrayMode( GLenum mode );\nvoid GL_LoadTexMatrixExt( const float *glmatrix );\nvoid GL_LoadMatrix( const matrix4x4 source );\nvoid GL_TexGen( GLenum coord, GLenum mode );\nvoid GL_SelectTexture( GLint texture );\nvoid GL_CleanupAllTextureUnits( void );\nvoid GL_LoadIdentityTexMatrix( void );\nvoid GL_DisableAllTexGens( void );\nvoid GL_SetRenderMode( int mode );\nvoid GL_EnableTextureUnit( int tmu, qboolean enable );\nvoid GL_TextureTarget( uint target );\nvoid GL_Cull( GLenum cull );\nvoid R_ShowTextures( void );\nvoid SCR_TimeRefresh_f( void );\n\n//\n// gl_beams.c\n//\nvoid CL_DrawBeams( int fTrans, BEAM *active_beams );\nqboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly );\n\n//\n// gl_cull.c\n//\nint R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax );\nqboolean R_CullBox( const vec3_t mins, const vec3_t maxs );\nint R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags );\n\n//\n// gl_decals.c\n//\nvoid DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse );\nfloat *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount );\nvoid DrawSingleDecal( decal_t *pDecal, msurface_t *fa );\nvoid R_EntityRemoveDecals( model_t *mod );\nvoid DrawDecalsBatch( void );\nvoid R_ClearDecals( void );\n\n//\n// gl_draw.c\n//\nvoid R_Set2DMode( qboolean enable );\nvoid R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data );\n\n//\n// gl_drawhulls.c\n//\nvoid R_DrawWorldHull( void );\nvoid R_DrawModelHull( void );\n\n//\n// gl_image.c\n//\nvoid R_SetTextureParameters( void );\ngl_texture_t *R_GetTexture( GLenum texnum );\nconst char *GL_TargetToString( GLenum target );\n#define GL_LoadTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, false )\n#define GL_UpdateTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, true )\nint GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags );\nint GL_LoadTextureArray( const char **names, int flags );\nint GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update );\nbyte *GL_ResampleTexture( const byte *source, int in_w, int in_h, int out_w, int out_h, qboolean isNormalMap );\nint GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags );\nint GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags );\nvoid GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor );\nvoid GL_UpdateTexSize( int texnum, int width, int height, int depth );\nqboolean GL_TextureFilteringEnabled( const gl_texture_t *tex );\nvoid GL_ApplyTextureParams( gl_texture_t *tex );\nint GL_FindTexture( const char *name );\nvoid GL_FreeTexture( GLenum texnum );\nconst char *GL_Target( GLenum target );\nvoid R_InitDlightTexture( void );\nvoid R_TextureList_f( void );\nvoid R_InitImages( void );\nvoid R_ShutdownImages( void );\nint GL_TexMemory( void );\nqboolean R_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... ) FORMAT_CHECK( 4 );\nvoid R_TextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath );\n\n//\n// gl_rlight.c\n//\nvoid CL_RunLightStyles( lightstyle_t *ls );\nvoid R_PushDlights( void );\nvoid R_GetLightSpot( vec3_t lightspot );\nvoid R_MarkLights( const dlight_t *light, int bit, const mnode_t *node );\ncolorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec );\ncolorVec R_LightPoint( const vec3_t p0 );\n\n//\n// gl_rmain.c\n//\nvoid R_ClearScene( void );\nvoid R_LoadIdentity( void );\nvoid R_RenderScene( void );\nvoid R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size );\nvoid R_SetupRefParams( const struct ref_viewpass_s *rvp );\nvoid R_TranslateForEntity( cl_entity_t *e );\nvoid R_RotateForEntity( cl_entity_t *e );\nvoid R_SetupGL( qboolean set_gl_state );\nvoid R_AllowFog( qboolean allowed );\nqboolean R_OpaqueEntity( cl_entity_t *ent );\nvoid R_SetupFrustum( void );\nvoid R_FindViewLeaf( void );\nvoid R_PushScene( void );\nvoid R_PopScene( void );\nvoid R_DrawFog( void );\nint CL_FxBlend( cl_entity_t *e );\n\n//\n// gl_rmath.c\n//\nvoid Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] );\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 );\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z );\nvoid Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar);\nvoid Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar);\nvoid Matrix4x4_CreateModelview( matrix4x4 out );\n\n//\n// gl_rmisc.c\n//\nvoid R_ClearStaticEntities( void );\n\n//\n// gl_rsurf.c\n//\nvoid R_MarkLeaves( void );\nvoid R_DrawWorld( void );\nvoid R_DrawWaterSurfaces( void );\nvoid R_DrawBrushModel( cl_entity_t *e );\nvoid GL_SubdivideSurface( model_t *mod, msurface_t *fa );\nvoid GL_SetupFogColorForSurfaces( void );\nvoid R_DrawAlphaTextureChains( void );\nvoid GL_RebuildLightmaps( void );\nvoid GL_InitRandomTable( void );\nvoid GL_BuildLightmaps( void );\nvoid GL_ResetFogColor( void );\nvoid R_GenerateVBO( void );\nvoid R_ClearVBO( void );\nvoid R_AddDecalVBO( decal_t *pdecal, msurface_t *surf );\nvoid R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords );\nqboolean R_HasGeneratedVBO( void );\nvoid R_EnableVBO( qboolean enable );\nqboolean R_HasEnabledVBO( void );\n\n//\n// gl_rpart.c\n//\nvoid CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime );\nvoid CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize );\nvoid CL_DrawTracers( double frametime, particle_t *cl_active_tracers );\n\n\n//\n// gl_sprite.c\n//\nvoid R_SpriteInit( void );\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags );\nmspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw );\nvoid R_DrawSpriteModel( cl_entity_t *e );\n\n//\n// gl_studio.c\n//\nvoid R_StudioInit( void );\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\nstruct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e );\nint R_GetEntityRenderMode( cl_entity_t *ent );\nvoid R_DrawStudioModel( cl_entity_t *e );\nplayer_info_t *pfnPlayerInfo( int index );\nvoid R_GatherPlayerLight( void );\nfloat R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time );\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\nvoid R_StudioResetPlayerModels( void );\nvoid CL_InitStudioAPI( void );\nvoid Mod_StudioLoadTextures( model_t *mod, void *data );\nvoid Mod_StudioUnloadTextures( void *data );\n\n//\n// gl_alias.c\n//\nvoid Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded );\nvoid R_DrawAliasModel( cl_entity_t *e );\nvoid R_AliasInit( void );\n\n//\n// gl_warp.c\n//\nvoid R_AddSkyBoxSurface( msurface_t *fa );\nvoid R_ClearSkyBox( void );\nvoid R_DrawSkyBox( void );\nvoid R_DrawClouds( void );\nvoid R_UnloadSkybox( void );\nvoid EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples );\nvoid R_ResetRipples( void );\nvoid R_AnimateRipples( void );\nqboolean R_UploadRipples( texture_t *image );\n\n//#include \"vid_common.h\"\n\n//\n// renderer exports\n//\nqboolean R_Init( void );\nvoid R_Shutdown( void );\nvoid GL_SetupAttributes( int safegl );\nvoid GL_OnContextCreated( void );\nvoid GL_InitExtensions( void );\nvoid GL_ClearExtensions( void );\nint GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags );\nvoid GL_FreeImage( const char *name );\nqboolean VID_ScreenShot( const char *filename, int shot_type );\nqboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot );\nvoid R_GammaChanged( qboolean do_reset_gamma );\nvoid R_BeginFrame( qboolean clearScene );\nvoid R_RenderFrame( const struct ref_viewpass_s *vp );\nvoid R_EndFrame( void );\nvoid R_ClearScene( void );\nvoid R_GetTextureParms( int *w, int *h, int texnum );\nvoid R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int curFrame, const struct model_s *pSprite );\nvoid R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty );\nvoid R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum );\nqboolean R_SpeedsMessage( char *out, size_t size );\nqboolean R_CullBox( const vec3_t mins, const vec3_t maxs );\nint R_WorldToScreen( const vec3_t point, vec3_t screen );\nvoid R_ScreenToWorld( const vec3_t screen, vec3_t point );\nqboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType );\nvoid Mod_SpriteUnloadTextures( void *data );\nvoid Mod_UnloadAliasModel( struct model_s *mod );\nvoid Mod_AliasUnloadTextures( void *data );\nvoid GL_SetRenderMode( int mode );\nvoid R_RunViewmodelEvents( void );\nvoid R_DrawViewModel( void );\nint R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame );\nvoid R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale );\nvoid R_DecalRemoveAll( int texture );\nint R_CreateDecalList( decallist_t *pList );\nvoid R_ClearAllDecals( void );\nbyte *Mod_GetCurrentVis( void );\nvoid Mod_SetOrthoBounds( const float *mins, const float *maxs );\nvoid R_NewMap( void );\nvoid CL_AddCustomBeam( cl_entity_t *pEnvBeam );\n\n//\n// gl_opengl.c\n//\n#define GL_CheckForErrors() GL_CheckForErrors_( __FILE__, __LINE__ )\nvoid GL_CheckForErrors_( const char *filename, const int fileline );\nconst char *GL_ErrorString( int err );\n\n//\n// gl_triapi.c\n//\nvoid TriRenderMode( int mode );\nvoid TriBegin( int mode );\nvoid TriEnd( void );\nvoid TriTexCoord2f( float u, float v );\nvoid TriVertex3fv( const float *v );\nvoid TriVertex3f( float x, float y, float z );\nvoid _TriColor4f( float r, float g, float b, float a );\nvoid _TriColor4ub( byte r, byte g, byte b, byte a );\nvoid TriColor4f( float r, float g, float b, float a );\nvoid TriColor4ub( byte r, byte g, byte b, byte a );\nvoid TriBrightness( float brightness );\nint TriWorldToScreen( const float *world, float *screen );\nint TriSpriteTexture( model_t *pSpriteModel, int frame );\nvoid TriFog( float flFogColor[3], float flStart, float flEnd, int bOn );\nvoid TriGetMatrix( const int pname, float *matrix );\nvoid TriFogParams( float flDensity, int iFogSkybox );\nvoid TriCullFace( TRICULLSTYLE mode );\n\n/*\n=======================================================================\n\n GL STATE MACHINE\n\n=======================================================================\n*/\nenum\n{\n\tGL_OPENGL_110 = 0,\t\t// base\n\tGL_ARB_MULTITEXTURE,\n\tGL_TEXTURE_CUBEMAP_EXT,\n\tGL_ANISOTROPY_EXT,\n\tGL_TEXTURE_LOD_BIAS,\n\tGL_TEXTURE_COMPRESSION_EXT,\n\tGL_SHADER_GLSL100_EXT,\n\tGL_TEXTURE_2D_RECT_EXT,\n\tGL_TEXTURE_ARRAY_EXT,\n\tGL_TEXTURE_3D_EXT,\n\tGL_CLAMPTOEDGE_EXT,\n\tGL_ARB_TEXTURE_NPOT_EXT,\n\tGL_CLAMP_TEXBORDER_EXT,\n\tGL_ARB_TEXTURE_FLOAT_EXT,\n\tGL_ARB_DEPTH_FLOAT_EXT,\n\tGL_ARB_SEAMLESS_CUBEMAP,\n\tGL_EXT_GPU_SHADER4,\t\t// shaders only\n\tGL_DEPTH_TEXTURE,\n\tGL_DEBUG_OUTPUT,\n\tGL_ARB_VERTEX_BUFFER_OBJECT_EXT,\n\tGL_DRAW_RANGEELEMENTS_EXT,\n\tGL_TEXTURE_MULTISAMPLE,\n\tGL_ARB_TEXTURE_COMPRESSION_BPTC,\n\tGL_SHADER_OBJECTS_EXT,\n\tGL_ARB_VERTEX_ARRAY_OBJECT_EXT,\n\tGL_BUFFER_STORAGE_EXT,\n\tGL_MAP_BUFFER_RANGE_EXT,\n\tGL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT,\n\tGL_EXTCOUNT,\t\t// must be last\n};\n\ntypedef enum\n{\n\tGLHW_GENERIC,\t\t// where everthing works the way it should\n\tGLHW_RADEON,\t\t// where you don't have proper GLSL support\n\tGLHW_NVIDIA,\t\t// Geforce 8/9 class DX10 hardware\n\tGLHW_INTEL\t\t// Intel Mobile Graphics\n} glHWType_t;\n\ntypedef struct\n{\n\tconst char\t*renderer_string;\t\t// ptrs to OpenGL32.dll, use with caution\n\tconst char\t*vendor_string;\n\tconst char\t*version_string;\n\n\tglHWType_t\thardware_type;\n\n\t// list of supported extensions\n\tconst char\t*extensions_string;\n\tbyte\t\textension[GL_EXTCOUNT];\n\n\tint\t\tmax_texture_units;\n\tint\t\tmax_texture_coords;\n\tint\t\tmax_teximage_units;\n\tGLint\t\tmax_2d_texture_size;\n\tGLint\t\tmax_2d_rectangle_size;\n\tGLint\t\tmax_2d_texture_layers;\n\tGLint\t\tmax_3d_texture_size;\n\tGLint\t\tmax_cubemap_size;\n\n\tGLfloat\t\tmax_texture_anisotropy;\n\tGLfloat\t\tmax_texture_lod_bias;\n\n\tGLint\t\tmax_vertex_uniforms;\n\tGLint\t\tmax_vertex_attribs;\n\n\tGLint\t\tmax_multisamples;\n\n\tint\t\tcolor_bits;\n\tint\t\talpha_bits;\n\tint\t\tdepth_bits;\n\tint\t\tstencil_bits;\n\tint\t\tmsaasamples;\n\tint\t\tversion_major;\n\tint\t\tversion_minor;\n\n\tgl_context_type_t\tcontext;\n\tgles_wrapper_t\twrapper;\n\n\tqboolean\t\tsoftwareGammaUpdate;\n\tqboolean\t\tfCustomRenderer;\n\tint\t\tprev_width;\n\tint\t\tprev_height;\n} glconfig_t;\n\ntypedef struct\n{\n\tint\t\tactiveTMU;\n\tGLint\t\tcurrentTextures[MAX_TEXTURE_UNITS];\n\tGLint\t\tcurrentTexturesIndex[MAX_TEXTURE_UNITS];\n\tGLuint\t\tcurrentTextureTargets[MAX_TEXTURE_UNITS];\n\tGLboolean\t\ttexIdentityMatrix[MAX_TEXTURE_UNITS];\n\tGLint\t\tgenSTEnabled[MAX_TEXTURE_UNITS];\t// 0 - disabled, OR 1 - S, OR 2 - T, OR 4 - R\n\tGLint\t\ttexCoordArrayMode[MAX_TEXTURE_UNITS];\t// 0 - disabled, 1 - enabled, 2 - cubemap\n\tGLint\t\tisFogEnabled;\n\n\tint\t\tfaceCull;\n\n\tqboolean\t\tstencilEnabled;\n\tqboolean\t\tin2DMode;\n} glstate_t;\n\ntypedef struct\n{\n\tqboolean\t\tinitialized;\t// OpenGL subsystem started\n\tqboolean\t\textended;\t\t// extended context allows to GL_Debug\n} glwstate_t;\n\nextern glconfig_t\t\tglConfig;\nextern glstate_t\t\tglState;\n// move to engine\nextern glwstate_t\t\tglw_state;\nextern ref_api_t      gEngfuncs;\nextern ref_globals_t *gpGlobals;\nextern ref_client_t  *gp_cl;\nextern ref_host_t    *gp_host;\n\n#define ENGINE_GET_PARM_ (*gEngfuncs.EngineGetParm)\n#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 )\n\n//\n// helper funcs\n//\nstatic inline cl_entity_t *CL_GetEntityByIndex( int index )\n{\n\tif( unlikely( index < 0 || index >= tr.max_entities || !tr.entities ))\n\t\treturn NULL;\n\n\treturn &tr.entities[index];\n}\n\nstatic inline model_t *CL_ModelHandle( int index )\n{\n\tif( unlikely( index < 0 || index >= gp_cl->nummodels ))\n\t\treturn NULL;\n\n\treturn gp_cl->models[index];\n}\n\nstatic inline byte TextureToGamma( byte b )\n{\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.texgammatable[b] : b;\n}\n\nstatic inline uint LightToTexGamma( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lightgammatable[b] : b;\n}\n\nstatic inline uint ScreenGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.screengammatable[b] : b;\n}\n\nstatic inline uint LinearGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lineargammatable[b] : b;\n}\n\nstatic inline qboolean GL_Support( int r_ext )\n{\n\tif( r_ext >= 0 && r_ext < GL_EXTCOUNT )\n\t\treturn glConfig.extension[r_ext] ? true : false;\n\tgEngfuncs.Con_Printf( S_ERROR \"%s: invalid extension %d\\n\", __func__, r_ext );\n\n\treturn false;\n}\n\nstatic inline int GL_MaxTextureUnits( void )\n{\n\tif( GL_Support( GL_SHADER_GLSL100_EXT ))\n\t\treturn Q_min( Q_max( glConfig.max_texture_coords, glConfig.max_teximage_units ), MAX_TEXTURE_UNITS );\n\treturn glConfig.max_texture_units;\n}\n\n#define WORLDMODEL (gp_cl->models[1])\n\n//\n// renderer cvars\n//\nextern convar_t\tgl_texture_anisotropy;\nextern convar_t\tgl_extensions;\nextern convar_t\tgl_check_errors;\nextern convar_t\tgl_texture_lodbias;\nextern convar_t\tgl_texture_nearest;\nextern convar_t\tgl_lightmap_nearest;\nextern convar_t\tgl_keeptjunctions;\nextern convar_t\tgl_round_down;\nextern convar_t\tgl_wireframe;\nextern convar_t\tgl_polyoffset;\nextern convar_t\tgl_finish;\nextern convar_t\tgl_nosort;\nextern convar_t\tgl_test;\t\t// cvar to testify new effects\nextern convar_t\tgl_msaa;\nextern convar_t\tgl_stencilbits;\nextern convar_t\tgl_overbright;\nextern convar_t gl_fog;\n\nextern convar_t\tr_lighting_extended;\nextern convar_t\tr_lighting_ambient;\nextern convar_t\tr_studio_lambert;\nextern convar_t\tr_detailtextures;\nextern convar_t\tr_novis;\nextern convar_t\tr_nocull;\nextern convar_t\tr_lockpvs;\nextern convar_t\tr_lockfrustum;\nextern convar_t\tr_traceglow;\nextern convar_t\tr_vbo;\nextern convar_t\tr_vbo_dlightmode;\nextern convar_t\tr_vbo_detail;\nextern convar_t\tr_vbo_overbrightmode;\nextern convar_t r_studio_sort_textures;\nextern convar_t r_studio_drawelements;\nextern convar_t r_shadows;\nextern convar_t r_ripple;\nextern convar_t r_ripple_updatetime;\nextern convar_t r_ripple_spawntime;\nextern convar_t r_large_lightmaps;\nextern convar_t r_dlight_virtual_radius;\n\n//\n// engine shared convars\n//\nDECLARE_ENGINE_SHARED_CVAR_LIST()\n\n//\n// engine callbacks\n//\n#include \"crtlib.h\"\n\nvoid _Mem_Free( void *data, const char *filename, int fileline );\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n\tALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\n\n#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ )\n#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ )\n#define Mem_Realloc( pool, ptr, size ) gEngfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ )\n#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ )\n#define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ )\n#define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ )\n#define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ )\n\n#endif // GL_LOCAL_H\n"
  },
  {
    "path": "ref/gl/gl_opengl.c",
    "content": "\n#include \"gl_local.h\"\n#if XASH_GL4ES\n#include \"gl4es/include/gl4esinit.h\"\n#include \"gl4es/include/gl4eshint.h\"\n#endif // XASH_GL4ES\n\nCVAR_DEFINE( gl_extensions, \"gl_allow_extensions\", \"1\", FCVAR_GLCONFIG|FCVAR_READ_ONLY, \"allow gl_extensions\" );\nCVAR_DEFINE( gl_texture_anisotropy, \"gl_anisotropy\", \"8\", FCVAR_GLCONFIG, \"textures anisotropic filter\" );\nCVAR_DEFINE_AUTO( gl_texture_lodbias, \"0.0\", FCVAR_GLCONFIG, \"LOD bias for mipmapped textures (perfomance|quality)\" );\nCVAR_DEFINE_AUTO( gl_texture_nearest, \"0\", FCVAR_GLCONFIG, \"disable texture filter\" );\nCVAR_DEFINE_AUTO( gl_lightmap_nearest, \"0\", FCVAR_GLCONFIG, \"disable lightmap filter\" );\nCVAR_DEFINE_AUTO( gl_keeptjunctions, \"1\", FCVAR_GLCONFIG, \"removing tjuncs causes blinking pixels\" );\nCVAR_DEFINE_AUTO( gl_check_errors, \"1\", FCVAR_GLCONFIG, \"ignore video engine errors\" );\nCVAR_DEFINE_AUTO( gl_polyoffset, \"2.0\", FCVAR_GLCONFIG, \"polygon offset for decals\" );\nCVAR_DEFINE_AUTO( gl_wireframe, \"0\", FCVAR_GLCONFIG|FCVAR_SPONLY, \"show wireframe overlay\" );\nCVAR_DEFINE_AUTO( gl_finish, \"0\", FCVAR_GLCONFIG, \"use glFinish instead of glFlush\" );\nCVAR_DEFINE_AUTO( gl_nosort, \"0\", FCVAR_GLCONFIG, \"disable sorting of translucent surfaces\" );\nCVAR_DEFINE_AUTO( gl_test, \"0\", 0, \"engine developer cvar for quick testing new features\" );\nCVAR_DEFINE_AUTO( gl_msaa, \"1\", FCVAR_GLCONFIG, \"enable or disable multisample anti-aliasing\" );\nCVAR_DEFINE_AUTO( gl_stencilbits, \"8\", FCVAR_GLCONFIG|FCVAR_READ_ONLY, \"pixelformat stencil bits (0 - auto)\" );\nCVAR_DEFINE_AUTO( gl_overbright, \"1\", FCVAR_GLCONFIG, \"overbrights\" );\nCVAR_DEFINE_AUTO( gl_fog, \"1\", FCVAR_GLCONFIG, \"allow for rendering fog using built-in OpenGL fog implementation\" );\nCVAR_DEFINE_AUTO( r_lighting_extended, \"1\", FCVAR_GLCONFIG, \"allow to get lighting from world and bmodels\" );\nCVAR_DEFINE_AUTO( r_lighting_ambient, \"0.3\", FCVAR_GLCONFIG, \"map ambient lighting scale\" );\nCVAR_DEFINE_AUTO( r_detailtextures, \"1\", FCVAR_GLCONFIG, \"enable detail textures support\" );\nCVAR_DEFINE_AUTO( r_novis, \"0\", 0, \"ignore vis information (perfomance test)\" );\nCVAR_DEFINE_AUTO( r_nocull, \"0\", 0, \"ignore frustrum culling (perfomance test)\" );\nCVAR_DEFINE_AUTO( r_lockpvs, \"0\", FCVAR_CHEAT, \"lockpvs area at current point (pvs test)\" );\nCVAR_DEFINE_AUTO( r_lockfrustum, \"0\", FCVAR_CHEAT, \"lock frustrum area at current point (cull test)\" );\nCVAR_DEFINE_AUTO( r_traceglow, \"0\", FCVAR_GLCONFIG, \"cull flares behind models\" );\nCVAR_DEFINE_AUTO( gl_round_down, \"2\", FCVAR_GLCONFIG|FCVAR_READ_ONLY, \"round texture sizes to nearest POT value\" );\nCVAR_DEFINE( r_vbo, \"gl_vbo\", \"0\", FCVAR_GLCONFIG, \"draw world using VBO (known to be glitchy)\" );\nCVAR_DEFINE( r_vbo_detail, \"gl_vbo_detail\", \"0\", FCVAR_GLCONFIG, \"detail vbo mode (0: disable, 1: multipass, 2: singlepass, broken decal dlights)\" );\nCVAR_DEFINE( r_vbo_dlightmode, \"gl_vbo_dlightmode\", \"1\", FCVAR_GLCONFIG, \"vbo dlight rendering mode (0-1)\" );\nCVAR_DEFINE( r_vbo_overbrightmode, \"gl_vbo_overbrightmode\", \"0\", FCVAR_GLCONFIG, \"vbo overbright rendering mode (0-1)\" );\nCVAR_DEFINE_AUTO( r_ripple, \"0\", FCVAR_GLCONFIG, \"enable software-like water texture ripple simulation\" );\nCVAR_DEFINE_AUTO( r_ripple_updatetime, \"0.05\", FCVAR_GLCONFIG, \"how fast ripple simulation is\" );\nCVAR_DEFINE_AUTO( r_ripple_spawntime, \"0.1\", FCVAR_GLCONFIG, \"how fast new ripples spawn\" );\nCVAR_DEFINE_AUTO( r_large_lightmaps, \"0\", FCVAR_GLCONFIG|FCVAR_LATCH, \"enable larger lightmap atlas textures (might break custom renderer mods)\" );\nCVAR_DEFINE_AUTO( r_dlight_virtual_radius, \"3\", FCVAR_GLCONFIG, \"increase dlight radius virtually by this amount, should help against ugly cut off dlights on highly scaled textures\" );\n\nDEFINE_ENGINE_SHARED_CVAR_LIST()\n\npoolhandle_t r_temppool;\n\ngl_globals_t\ttr;\nglconfig_t\tglConfig;\nglstate_t\tglState;\nglwstate_t\tglw_state;\n\n#if XASH_GL_STATIC\n\t#define GL_CALL( x ) #x, NULL\n#else\n\t#define GL_CALL( x ) #x, (void**)&p##x\n#endif\n\nstatic const dllfunc_t opengl_110funcs[] =\n{\n{ GL_CALL( glClearColor ) },\n{ GL_CALL( glClear ) },\n{ GL_CALL( glAlphaFunc ) },\n{ GL_CALL( glBlendFunc ) },\n{ GL_CALL( glCullFace ) },\n{ GL_CALL( glDrawBuffer ) },\n{ GL_CALL( glReadBuffer ) },\n{ GL_CALL( glAccum ) },\n{ GL_CALL( glEnable ) },\n{ GL_CALL( glDisable ) },\n{ GL_CALL( glEnableClientState ) },\n{ GL_CALL( glDisableClientState ) },\n{ GL_CALL( glGetBooleanv ) },\n{ GL_CALL( glGetDoublev ) },\n{ GL_CALL( glGetFloatv ) },\n{ GL_CALL( glGetIntegerv ) },\n{ GL_CALL( glGetError ) },\n{ GL_CALL( glGetString ) },\n{ GL_CALL( glFinish ) },\n{ GL_CALL( glFlush ) },\n{ GL_CALL( glClearDepth ) },\n{ GL_CALL( glDepthFunc ) },\n{ GL_CALL( glDepthMask ) },\n{ GL_CALL( glDepthRange ) },\n{ GL_CALL( glFrontFace ) },\n{ GL_CALL( glDrawElements ) },\n{ GL_CALL( glDrawArrays ) },\n{ GL_CALL( glColorMask ) },\n{ GL_CALL( glIndexPointer ) },\n{ GL_CALL( glVertexPointer ) },\n{ GL_CALL( glNormalPointer ) },\n{ GL_CALL( glColorPointer ) },\n{ GL_CALL( glTexCoordPointer ) },\n{ GL_CALL( glArrayElement ) },\n{ GL_CALL( glColor3f ) },\n{ GL_CALL( glColor3fv ) },\n{ GL_CALL( glColor4f ) },\n{ GL_CALL( glColor4fv ) },\n{ GL_CALL( glColor3ub ) },\n{ GL_CALL( glColor4ub ) },\n{ GL_CALL( glColor4ubv ) },\n{ GL_CALL( glTexCoord1f ) },\n{ GL_CALL( glTexCoord2f ) },\n{ GL_CALL( glTexCoord3f ) },\n{ GL_CALL( glTexCoord4f ) },\n{ GL_CALL( glTexCoord1fv ) },\n{ GL_CALL( glTexCoord2fv ) },\n{ GL_CALL( glTexCoord3fv ) },\n{ GL_CALL( glTexCoord4fv ) },\n{ GL_CALL( glTexGenf ) },\n{ GL_CALL( glTexGenfv ) },\n{ GL_CALL( glTexGeni ) },\n{ GL_CALL( glVertex2f ) },\n{ GL_CALL( glVertex3f ) },\n{ GL_CALL( glVertex3fv ) },\n{ GL_CALL( glNormal3f ) },\n{ GL_CALL( glNormal3fv ) },\n{ GL_CALL( glBegin ) },\n{ GL_CALL( glEnd ) },\n{ GL_CALL( glLineWidth ) },\n{ GL_CALL( glPointSize ) },\n{ GL_CALL( glMatrixMode ) },\n{ GL_CALL( glOrtho ) },\n{ GL_CALL( glRasterPos2f ) },\n{ GL_CALL( glFrustum ) },\n{ GL_CALL( glViewport ) },\n{ GL_CALL( glPushMatrix ) },\n{ GL_CALL( glPopMatrix ) },\n{ GL_CALL( glPushAttrib ) },\n{ GL_CALL( glPopAttrib ) },\n{ GL_CALL( glLoadIdentity ) },\n{ GL_CALL( glLoadMatrixd ) },\n{ GL_CALL( glLoadMatrixf ) },\n{ GL_CALL( glMultMatrixd ) },\n{ GL_CALL( glMultMatrixf ) },\n{ GL_CALL( glRotated ) },\n{ GL_CALL( glRotatef ) },\n{ GL_CALL( glScaled ) },\n{ GL_CALL( glScalef ) },\n{ GL_CALL( glTranslated ) },\n{ GL_CALL( glTranslatef ) },\n{ GL_CALL( glReadPixels ) },\n{ GL_CALL( glDrawPixels ) },\n{ GL_CALL( glStencilFunc ) },\n{ GL_CALL( glStencilMask ) },\n{ GL_CALL( glStencilOp ) },\n{ GL_CALL( glClearStencil ) },\n{ GL_CALL( glIsEnabled ) },\n{ GL_CALL( glIsList ) },\n{ GL_CALL( glIsTexture ) },\n{ GL_CALL( glTexEnvf ) },\n{ GL_CALL( glTexEnvfv ) },\n{ GL_CALL( glTexEnvi ) },\n{ GL_CALL( glTexParameterf ) },\n{ GL_CALL( glTexParameterfv ) },\n{ GL_CALL( glTexParameteri ) },\n{ GL_CALL( glHint ) },\n{ GL_CALL( glPixelStoref ) },\n{ GL_CALL( glPixelStorei ) },\n{ GL_CALL( glGenTextures ) },\n{ GL_CALL( glDeleteTextures ) },\n{ GL_CALL( glBindTexture ) },\n{ GL_CALL( glTexImage1D ) },\n{ GL_CALL( glTexImage2D ) },\n{ GL_CALL( glTexSubImage1D ) },\n{ GL_CALL( glTexSubImage2D ) },\n{ GL_CALL( glCopyTexImage1D ) },\n{ GL_CALL( glCopyTexImage2D ) },\n{ GL_CALL( glCopyTexSubImage1D ) },\n{ GL_CALL( glCopyTexSubImage2D ) },\n{ GL_CALL( glScissor ) },\n{ GL_CALL( glGetTexImage ) },\n{ GL_CALL( glGetTexEnviv ) },\n{ GL_CALL( glPolygonOffset ) },\n{ GL_CALL( glPolygonMode ) },\n{ GL_CALL( glPolygonStipple ) },\n{ GL_CALL( glClipPlane ) },\n{ GL_CALL( glGetClipPlane ) },\n{ GL_CALL( glShadeModel ) },\n{ GL_CALL( glGetTexLevelParameteriv ) },\n{ GL_CALL( glGetTexLevelParameterfv ) },\n{ GL_CALL( glFogfv ) },\n{ GL_CALL( glFogf ) },\n{ GL_CALL( glFogi ) },\n};\n\nstatic const dllfunc_t debugoutputfuncs[] =\n{\n{ GL_CALL( glDebugMessageControlARB ) },\n{ GL_CALL( glDebugMessageInsertARB ) },\n{ GL_CALL( glDebugMessageCallbackARB ) },\n{ GL_CALL( glGetDebugMessageLogARB ) },\n};\n\nstatic const dllfunc_t multitexturefuncs[] =\n{\n{ GL_CALL( glMultiTexCoord1f ) },\n{ GL_CALL( glMultiTexCoord2f ) },\n{ GL_CALL( glMultiTexCoord3f ) },\n{ GL_CALL( glMultiTexCoord4f ) },\n{ GL_CALL( glActiveTexture ) },\n{ GL_CALL( glActiveTextureARB ) },\n{ GL_CALL( glClientActiveTexture ) },\n{ GL_CALL( glClientActiveTextureARB ) },\n};\n\nstatic const dllfunc_t texture3dextfuncs[] =\n{\n{ GL_CALL( glTexImage3D ) },\n{ GL_CALL( glTexSubImage3D ) },\n{ GL_CALL( glCopyTexSubImage3D ) },\n};\n\nstatic const dllfunc_t texturecompressionfuncs[] =\n{\n{ GL_CALL( glCompressedTexImage3DARB ) },\n{ GL_CALL( glCompressedTexImage2DARB ) },\n{ GL_CALL( glCompressedTexImage1DARB ) },\n{ GL_CALL( glCompressedTexSubImage3DARB ) },\n{ GL_CALL( glCompressedTexSubImage2DARB ) },\n{ GL_CALL( glCompressedTexSubImage1DARB ) },\n{ GL_CALL( glGetCompressedTexImage ) },\n};\n\nstatic const dllfunc_t vbofuncs[] =\n{\n{ GL_CALL( glBindBufferARB ) },\n{ GL_CALL( glDeleteBuffersARB ) },\n{ GL_CALL( glGenBuffersARB ) },\n{ GL_CALL( glIsBufferARB ) },\n#if !XASH_GLES\n{ GL_CALL( glMapBufferARB ) },\n{ GL_CALL( glUnmapBufferARB ) },\n#endif\n{ GL_CALL( glBufferDataARB ) },\n{ GL_CALL( glBufferSubDataARB ) },\n};\n\nstatic const dllfunc_t multisampletexfuncs[] =\n{\n{ GL_CALL(glTexImage2DMultisample) },\n};\n\nstatic const dllfunc_t drawrangeelementsfuncs[] =\n{\n{ GL_CALL( glDrawRangeElements ) },\n};\n\nstatic const dllfunc_t drawrangeelementsextfuncs[] =\n{\n{ GL_CALL( glDrawRangeElementsEXT ) },\n};\n\n\n// mangling in gl2shim???\n// still need resolve some ext dynamicly, and mangling beginend wrappers will help only with LTO\n// anyway this will not work with gl-wes/nanogl, we do not link to libGLESv2, so skip this now\n#if !XASH_GL_STATIC\nstatic const dllfunc_t mapbufferrangefuncs[] =\n{\n{ GL_CALL( glMapBufferRange ) },\n{ GL_CALL( glFlushMappedBufferRange ) },\n#if XASH_GLES\n{ GL_CALL( glUnmapBufferARB ) },\n#endif\n};\n\nstatic const dllfunc_t drawrangeelementsbasevertexfuncs[] =\n{\n{ GL_CALL( glDrawRangeElementsBaseVertex ) },\n};\n\nstatic const dllfunc_t bufferstoragefuncs[] =\n{\n{ GL_CALL( glBufferStorage ) },\n};\n\nstatic const dllfunc_t shaderobjectsfuncs[] =\n{\n{ GL_CALL( glDeleteObjectARB ) },\n{ GL_CALL( glGetHandleARB ) },\n{ GL_CALL( glDetachObjectARB ) },\n{ GL_CALL( glCreateShaderObjectARB ) },\n{ GL_CALL( glShaderSourceARB ) },\n{ GL_CALL( glCompileShaderARB ) },\n{ GL_CALL( glCreateProgramObjectARB ) },\n{ GL_CALL( glAttachObjectARB ) },\n{ GL_CALL( glLinkProgramARB ) },\n{ GL_CALL( glUseProgramObjectARB ) },\n{ GL_CALL( glValidateProgramARB ) },\n{ GL_CALL( glUniform1fARB ) },\n{ GL_CALL( glUniform2fARB ) },\n{ GL_CALL( glUniform3fARB ) },\n{ GL_CALL( glUniform4fARB ) },\n{ GL_CALL( glUniform1iARB ) },\n{ GL_CALL( glUniform2iARB ) },\n{ GL_CALL( glUniform3iARB ) },\n{ GL_CALL( glUniform4iARB ) },\n{ GL_CALL( glUniform1fvARB ) },\n{ GL_CALL( glUniform2fvARB ) },\n{ GL_CALL( glUniform3fvARB ) },\n{ GL_CALL( glUniform4fvARB ) },\n{ GL_CALL( glUniform1ivARB ) },\n{ GL_CALL( glUniform2ivARB ) },\n{ GL_CALL( glUniform3ivARB ) },\n{ GL_CALL( glUniform4ivARB ) },\n{ GL_CALL( glUniformMatrix2fvARB ) },\n{ GL_CALL( glUniformMatrix3fvARB ) },\n{ GL_CALL( glUniformMatrix4fvARB ) },\n{ GL_CALL( glGetObjectParameterfvARB ) },\n{ GL_CALL( glGetObjectParameterivARB ) },\n{ GL_CALL( glGetInfoLogARB ) },\n{ GL_CALL( glGetAttachedObjectsARB ) },\n{ GL_CALL( glGetUniformLocationARB ) },\n{ GL_CALL( glGetActiveUniformARB ) },\n{ GL_CALL( glGetUniformfvARB ) },\n{ GL_CALL( glGetUniformivARB ) },\n{ GL_CALL( glGetShaderSourceARB ) },\n{ GL_CALL( glVertexAttribPointerARB ) },\n{ GL_CALL( glEnableVertexAttribArrayARB ) },\n{ GL_CALL( glDisableVertexAttribArrayARB ) },\n{ GL_CALL( glBindAttribLocationARB ) },\n{ GL_CALL( glGetActiveAttribARB ) },\n{ GL_CALL( glGetAttribLocationARB ) },\n{ GL_CALL( glVertexAttrib2fARB ) },\n{ GL_CALL( glVertexAttrib2fvARB ) },\n//{ GL_CALL( glVertexAttrib3fv ) },\n//{ GL_CALL( glVertexAttrib4f ) },\n//{ GL_CALL( glVertexAttrib4fv ) },\n//{ GL_CALL( glVertexAttrib4ubv ) },\n};\n\n/*\n==================\nEven if *ARB functions may work in GL driver in Core context,\nrenderdoc completely ignores this calls, so we cannot workaround this\nby removing ARB suffix after failed function resolve\nI desided not to remove ARB suffix from function declarations because\nit historicaly related to ARB_shader_object extension, not GL2+ functions\nand all shader code from XashXT/ancient xash3d uses it too\nCommented out lines left there intentionally to prevent usage on core/gles\n==================\n*/\n\nstatic const dllfunc_t shaderobjectsfuncs_gles[] =\n{\n{ \"glDeleteShader\"             , (void **)&pglDeleteObjectARB },\n//{ \"glGetHandleARB\"           , (void **)&pglGetHandleARB },\n{ \"glDetachShader\"             , (void **)&pglDetachObjectARB },\n{ \"glCreateShader\"             , (void **)&pglCreateShaderObjectARB },\n{ \"glShaderSource\"             , (void **)&pglShaderSourceARB },\n{ \"glCompileShader\"            , (void **)&pglCompileShaderARB },\n{ \"glCreateProgram\"            , (void **)&pglCreateProgramObjectARB },\n{ \"glAttachShader\"             , (void **)&pglAttachObjectARB },\n{ \"glLinkProgram\"              , (void **)&pglLinkProgramARB },\n{ \"glUseProgram\"               , (void **)&pglUseProgramObjectARB },\n{ \"glValidateProgram\"          , (void **)&pglValidateProgramARB },\n{ \"glUniform1f\"                , (void **)&pglUniform1fARB },\n{ \"glUniform2f\"                , (void **)&pglUniform2fARB },\n{ \"glUniform3f\"                , (void **)&pglUniform3fARB },\n{ \"glUniform4f\"                , (void **)&pglUniform4fARB },\n{ \"glUniform1i\"                , (void **)&pglUniform1iARB },\n{ \"glUniform2i\"                , (void **)&pglUniform2iARB },\n{ \"glUniform3i\"                , (void **)&pglUniform3iARB },\n{ \"glUniform4i\"                , (void **)&pglUniform4iARB },\n{ \"glUniform1f\"                , (void **)&pglUniform1fvARB },\n{ \"glUniform2fv\"               , (void **)&pglUniform2fvARB },\n{ \"glUniform3fv\"               , (void **)&pglUniform3fvARB },\n{ \"glUniform4fv\"               , (void **)&pglUniform4fvARB },\n{ \"glUniform1iv\"               , (void **)&pglUniform1ivARB },\n{ \"glUniform2iv\"               , (void **)&pglUniform2ivARB },\n{ \"glUniform3iv\"               , (void **)&pglUniform3ivARB },\n{ \"glUniform4iv\"               , (void **)&pglUniform4ivARB },\n{ \"glUniformMatrix2fv\"         , (void **)&pglUniformMatrix2fvARB },\n{ \"glUniformMatrix3fv\"         , (void **)&pglUniformMatrix3fvARB },\n{ \"glUniformMatrix4fv\"         , (void **)&pglUniformMatrix4fvARB },\n//{ \"glGetShaderfv\"            , (void **)&pglGetObjectParameterfvARB }, // missing in ES2?\n{ \"glGetShaderiv\"              , (void **)&pglGetObjectParameterivARB },\n{ \"glGetShaderInfoLog\"         , (void **)&pglGetInfoLogARB },\n//{ \"glGetAttachedObjects\"     , (void **)&pglGetAttachedObjectsARB }, // missing in ES2?\n{ \"glGetUniformLocation\"       , (void **)&pglGetUniformLocationARB },\n{ \"glGetActiveUniform\"         , (void **)&pglGetActiveUniformARB },\n{ \"glGetUniformfv\"             , (void **)&pglGetUniformfvARB },\n{ \"glGetUniformiv\"             , (void **)&pglGetUniformivARB },\n{ \"glGetShaderSource\"          , (void **)&pglGetShaderSourceARB },\n{ \"glVertexAttribPointer\"      , (void **)&pglVertexAttribPointerARB },\n{ \"glEnableVertexAttribArray\"  , (void **)&pglEnableVertexAttribArrayARB },\n{ \"glDisableVertexAttribArray\" , (void **)&pglDisableVertexAttribArrayARB },\n{ \"glBindAttribLocation\"       , (void **)&pglBindAttribLocationARB },\n{ \"glGetActiveAttrib\"          , (void **)&pglGetActiveAttribARB },\n{ \"glGetAttribLocation\"        , (void **)&pglGetAttribLocationARB },\n{ \"glVertexAttrib2f\"           , (void **)&pglVertexAttrib2fARB },\n{ \"glVertexAttrib2fv\"          , (void **)&pglVertexAttrib2fvARB },\n{ \"glVertexAttrib3fv\"          , (void **)&pglVertexAttrib3fvARB },\n\n// Core/GLES only\n{ GL_CALL( glGetProgramiv ) },\n{ GL_CALL( glDeleteProgram ) },\n{ GL_CALL( glGetProgramInfoLog ) },\n//{ \"glVertexAttrib4f\"              , (void **)&pglVertexAttrib4fARB },\n//{ \"glVertexAttrib4fv\"             , (void **)&pglVertexAttrib4fvARB },\n//{ \"glVertexAttrib4ubv\"            , (void **)&pglVertexAttrib4ubvARB },\n};\n\nstatic const dllfunc_t vaofuncs[] =\n{\n{ GL_CALL( glBindVertexArray ) },\n{ GL_CALL( glDeleteVertexArrays ) },\n{ GL_CALL( glGenVertexArrays ) },\n{ GL_CALL( glIsVertexArray ) },\n};\n\nstatic const dllfunc_t multitexturefuncs_es[] =\n{\n{ GL_CALL( glActiveTexture ) },\n{ GL_CALL( glActiveTextureARB ) },\n{ GL_CALL( glClientActiveTexture ) },\n{ GL_CALL( glClientActiveTextureARB ) },\n};\n\nstatic const dllfunc_t multitexturefuncs_es2[] =\n{\n{ GL_CALL( glActiveTexture ) },\n{ GL_CALL( glActiveTextureARB ) },\n};\n\n#endif // !XASH_GL_STATIC\n\n/*\n========================\nDebugCallback\n\nFor ARB_debug_output\n========================\n*/\nstatic void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLuint severity, GLint length, const GLcharARB *message, GLvoid *userParam )\n{\n\tswitch( type )\n\t{\n\tcase GL_DEBUG_TYPE_ERROR_ARB:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_ERROR \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB:\n\tcase GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_WARN \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_PORTABILITY_ARB:\n\t\tgEngfuncs.Con_Reportf( S_OPENGL_WARN \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_PERFORMANCE_ARB:\n\tcase GL_DEBUG_TYPE_OTHER_ARB:\n\tdefault:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_NOTE \"%s\\n\", message );\n\t\tbreak;\n\t}\n}\n\n/*\n=================\nGL_SetExtension\n=================\n*/\nstatic void GL_SetExtension( int r_ext, int enable )\n{\n\tif( r_ext >= 0 && r_ext < GL_EXTCOUNT )\n\t\tglConfig.extension[r_ext] = enable ? GL_TRUE : GL_FALSE;\n\telse gEngfuncs.Con_Printf( S_ERROR \"%s: invalid extension %d\\n\", __func__, r_ext );\n}\n\n/*\n=================\nGL_CheckExtension\n=================\n*/\nstatic qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, size_t num_funcs, const char *cvarname, int r_ext, float minver )\n{\n\tsize_t i;\n\tcvar_t *parm = NULL;\n\tconst char *extensions_string;\n\tchar desc[MAX_VA_STRING];\n\tfloat glver = (float)glConfig.version_major + glConfig.version_minor / 10.0f;\n\n\tgEngfuncs.Con_Reportf( \"%s: %s \", __func__, name );\n\tGL_SetExtension( r_ext, true );\n\n\tif( cvarname )\n\t{\n\t\t// system config disable extensions\n\t\tQ_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, name );\n\t\tparm = gEngfuncs.Cvar_Get( cvarname, \"1\", FCVAR_GLCONFIG|FCVAR_READ_ONLY, desc );\n\t}\n\n\tif(( parm && !parm->value ) || ( !gl_extensions.value && r_ext != GL_OPENGL_110 ))\n\t{\n\t\tgEngfuncs.Con_Reportf( \"- disabled\\n\" );\n\t\tGL_SetExtension( r_ext, false );\n\t\treturn false; // nothing to process at\n\t}\n\n\textensions_string = glConfig.extensions_string;\n\n\tif(( name[2] == '_' || name[3] == '_' ) && !Q_strstr( extensions_string, name ) && ( glver < minver  || !minver || !glver ) )\n\t{\n\t\tGL_SetExtension( r_ext, false );\t// update render info\n\t\tgEngfuncs.Con_Reportf( \"- ^1failed\\n\" );\n\t\treturn false;\n\t}\n\n#if !XASH_GL_STATIC\n\t// clear exports\n\tClearExports( funcs, num_funcs );\n\n\tfor( i = 0; i < num_funcs; i++ )\n\t{\n\t\t// functions are cleared before all the extensions are evaluated\n\t\tif(( *(funcs[i].func) = (void *)gEngfuncs.GL_GetProcAddress( funcs[i].name )) == NULL )\n\t\t{\n\t\t\tstring name;\n\t\t\tchar *end;\n\t\t\tsize_t j = 0;\n#if XASH_GLES\n\t\t\tconst char *suffixes[] = { \"\", \"EXT\", \"OES\" };\n#else\n\t\t\tconst char *suffixes[] = { \"\", \"EXT\" };\n#endif\n\n\t\t\t// HACK: fix ARB names\n\t\t\tQ_strncpy( name, funcs[i].name, sizeof( name ));\n\t\t\tif(( end = Q_strstr( name, \"ARB\" )))\n\t\t\t{\n\t\t\t\t*end = '\\0';\n\t\t\t}\n\t\t\telse // I need Q_strstrnul\n\t\t\t{\n\t\t\t\tend = name + Q_strlen( name );\n\t\t\t\tj++; // skip empty suffix\n\t\t\t}\n\n\t\t\tfor( ; j < sizeof( suffixes ) / sizeof( suffixes[0] ); j++ )\n\t\t\t{\n\t\t\t\tvoid *f;\n\n\t\t\t\tQ_strncat( name, suffixes[j], sizeof( name ));\n\n\t\t\t\tif(( f = gEngfuncs.GL_GetProcAddress( name )))\n\t\t\t\t{\n\t\t\t\t\t// GL_GetProcAddress prints errors about missing functions, so tell user that we found it with different name\n\t\t\t\t\tgEngfuncs.Con_Printf( S_NOTE \"found %s\\n\", name );\n\n\t\t\t\t\t*(funcs[i].func) = f;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t*end = '\\0'; // cut suffix, try next\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// not found...\n\t\t\tif( j == sizeof( suffixes ) / sizeof( suffixes[0] ))\n\t\t\t{\n\t\t\t\tGL_SetExtension( r_ext, false );\n\t\t\t}\n\t\t}\n\t}\n#endif\n\n\tif( GL_Support( r_ext ))\n\t{\n\t\tgEngfuncs.Con_Reportf( \"- ^2enabled\\n\" );\n\t\treturn true;\n\t}\n\n\tgEngfuncs.Con_Reportf( \"- ^1failed\\n\" );\n\treturn false;\n}\n\n/*\n==============\nGL_GetProcAddress\n\ndefined just for nanogl/glwes, so it don't link to SDL2 directly, nor use dlsym\n==============\n*/\nvoid GAME_EXPORT *GL_GetProcAddress( const char *name ); // keep defined for nanogl/wes\nvoid GAME_EXPORT *GL_GetProcAddress( const char *name )\n{\n\treturn gEngfuncs.GL_GetProcAddress( name );\n}\n\n/*\n===============\nGL_SetDefaultTexState\n===============\n*/\nstatic void GL_SetDefaultTexState( void )\n{\n\n\tint\ti;\n\n\tmemset( glState.currentTextures, -1, MAX_TEXTURE_UNITS * sizeof( *glState.currentTextures ));\n\tmemset( glState.texCoordArrayMode, 0, MAX_TEXTURE_UNITS * sizeof( *glState.texCoordArrayMode ));\n\tmemset( glState.genSTEnabled, 0, MAX_TEXTURE_UNITS * sizeof( *glState.genSTEnabled ));\n\n\tfor( i = 0; i < MAX_TEXTURE_UNITS; i++ )\n\t{\n\t\tglState.currentTextureTargets[i] = GL_NONE;\n\t\tglState.texIdentityMatrix[i] = true;\n\t}\n}\n\n/*\n===============\nGL_SetDefaultState\n===============\n*/\nstatic void GL_SetDefaultState( void )\n{\n\tmemset( &glState, 0, sizeof( glState ));\n\tGL_SetDefaultTexState ();\n\n\t// init draw stack\n\ttr.draw_list = &tr.draw_stack[0];\n\ttr.draw_stack_pos = 0;\n}\n\n/*\n===============\nGL_SetDefaults\n===============\n*/\nstatic void GL_SetDefaults( void )\n{\n\tpglFinish();\n\n\tpglClearColor( 0.5f, 0.5f, 0.5f, 1.0f );\n\n\tpglDisable( GL_DEPTH_TEST );\n\tpglDisable( GL_CULL_FACE );\n\tpglDisable( GL_SCISSOR_TEST );\n\tpglDepthFunc( GL_LEQUAL );\n\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\n\tif( glState.stencilEnabled )\n\t{\n\t\tpglDisable( GL_STENCIL_TEST );\n\t\tpglStencilMask( ( GLuint ) ~0 );\n\t\tpglStencilFunc( GL_EQUAL, 0, ~0 );\n\t\tpglStencilOp( GL_KEEP, GL_INCR, GL_INCR );\n\t}\n\n\tpglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );\n\tpglPolygonOffset( -1.0f, -2.0f );\n\n\tGL_CleanupAllTextureUnits();\n\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\tpglEnable( GL_TEXTURE_2D );\n\tpglShadeModel( GL_SMOOTH );\n\tpglFrontFace( GL_CCW );\n\n\tpglPointSize( 1.2f );\n\tpglLineWidth( 1.2f );\n\n\tGL_Cull( GL_NONE );\n}\n\n\n/*\n=================\nR_RenderInfo_f\n=================\n*/\nstatic void R_RenderInfo_f( void )\n{\n\tgEngfuncs.Con_Printf( \"\\n\" );\n\tgEngfuncs.Con_Printf( \"GL_VENDOR: %s\\n\", glConfig.vendor_string );\n\tgEngfuncs.Con_Printf( \"GL_RENDERER: %s\\n\", glConfig.renderer_string );\n\tgEngfuncs.Con_Printf( \"GL_VERSION: %s\\n\", glConfig.version_string );\n\n\t// don't spam about extensions\n\tgEngfuncs.Con_Reportf( \"GL_EXTENSIONS: %s\\n\", glConfig.extensions_string );\n\n\tif( glConfig.wrapper == GLES_WRAPPER_GL4ES )\n\t{\n\t\tconst char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 );\n\t\tconst char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 );\n\t\tconst char *version = (const char *)pglGetString( GL_VERSION | 0x10000 );\n\t\tconst char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 );\n\n\t\tif( vendor )\n\t\t\tgEngfuncs.Con_Printf( \"GL4ES_VENDOR: %s\\n\", vendor );\n\t\tif( renderer )\n\t\t\tgEngfuncs.Con_Printf( \"GL4ES_RENDERER: %s\\n\", renderer );\n\t\tif( version )\n\t\t\tgEngfuncs.Con_Printf( \"GL4ES_VERSION: %s\\n\", version );\n\t\tif( extensions )\n\t\t\tgEngfuncs.Con_Reportf( \"GL4ES_EXTENSIONS: %s\\n\", extensions );\n\t}\n\n\tgEngfuncs.Con_Printf( \"GL_MAX_TEXTURE_SIZE: %i\\n\", glConfig.max_2d_texture_size );\n\n\tif( GL_Support( GL_ARB_MULTITEXTURE ))\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_TEXTURE_UNITS_ARB: %i\\n\", glConfig.max_texture_units );\n\tif( GL_Support( GL_TEXTURE_CUBEMAP_EXT ))\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB: %i\\n\", glConfig.max_cubemap_size );\n\tif( GL_Support( GL_ANISOTROPY_EXT ))\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: %.1f\\n\", glConfig.max_texture_anisotropy );\n\tif( GL_Support( GL_TEXTURE_2D_RECT_EXT ))\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_RECTANGLE_TEXTURE_SIZE: %i\\n\", glConfig.max_2d_rectangle_size );\n\tif( GL_Support( GL_TEXTURE_ARRAY_EXT ))\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_ARRAY_TEXTURE_LAYERS_EXT: %i\\n\", glConfig.max_2d_texture_layers );\n\tif( GL_Support( GL_SHADER_GLSL100_EXT ))\n\t{\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_TEXTURE_COORDS_ARB: %i\\n\", glConfig.max_texture_coords );\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_TEXTURE_IMAGE_UNITS_ARB: %i\\n\", glConfig.max_teximage_units );\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB: %i\\n\", glConfig.max_vertex_uniforms );\n\t\tgEngfuncs.Con_Printf( \"GL_MAX_VERTEX_ATTRIBS_ARB: %i\\n\", glConfig.max_vertex_attribs );\n\t}\n\n\tgEngfuncs.Con_Printf( \"\\n\" );\n\tgEngfuncs.Con_Printf( \"MODE: %ix%i\\n\", gpGlobals->width, gpGlobals->height );\n\tgEngfuncs.Con_Printf( \"\\n\" );\n\tgEngfuncs.Con_Printf( \"VERTICAL SYNC: %s\\n\", gl_vsync->value ? \"enabled\" : \"disabled\" );\n\tgEngfuncs.Con_Printf( \"Color %d bits, Alpha %d bits, Depth %d bits, Stencil %d bits\\n\", glConfig.color_bits,\n\t\tglConfig.alpha_bits, glConfig.depth_bits, glConfig.stencil_bits );\n}\n\n#if XASH_GLES\nstatic void GL_InitExtensionsGLES( void )\n{\n\tint extid;\n\n\t// intialize wrapper type\n#if XASH_NANOGL\n\tglConfig.context = CONTEXT_TYPE_GLES_1_X;\n\tglConfig.wrapper = GLES_WRAPPER_NANOGL;\n#elif XASH_WES\n\tglConfig.context = CONTEXT_TYPE_GLES_2_X;\n\tglConfig.wrapper = GLES_WRAPPER_WES;\n#elif XASH_GLES3COMPAT\n\tglConfig.context = CONTEXT_TYPE_GLES_2_X;\n\tglConfig.wrapper = GLES_WRAPPER_NONE;\n#else\n\t#error \"unknown gles wrapper\"\n#endif\n\n\tglConfig.hardware_type = GLHW_GENERIC;\n\n\tfor( extid = GL_OPENGL_110 + 1; extid < GL_EXTCOUNT; extid++ )\n\t{\n\t\tswitch( extid )\n\t\t{\n\t\tcase GL_ARB_VERTEX_BUFFER_OBJECT_EXT:\n\t\t\tGL_CheckExtension( \"vertex_buffer_object\", vbofuncs, ARRAYSIZE( vbofuncs ), \"gl_vertex_buffer_object\", extid, 1.0 );\n\t\t\tbreak;\n\t\tcase GL_ARB_MULTITEXTURE:\n\t\t\tif( !GL_CheckExtension( \"multitexture\", multitexturefuncs, ARRAYSIZE( multitexturefuncs ), \"gl_arb_multitexture\", GL_ARB_MULTITEXTURE, 1.0 ) && glConfig.wrapper == GLES_WRAPPER_NONE )\n\t\t\t{\n#if !XASH_GL_STATIC\n\t\t\t\tif( !GL_CheckExtension( \"multitexture_es1\", multitexturefuncs_es, ARRAYSIZE( multitexturefuncs_es ), \"gl_arb_multitexture\", GL_ARB_MULTITEXTURE, 1.0 )\n\t\t\t\t\t\t&& !GL_CheckExtension( \"multitexture_es2\", multitexturefuncs_es2, ARRAYSIZE( multitexturefuncs_es2 ), \"gl_arb_multitexture\", GL_ARB_MULTITEXTURE, 2.0 ))\n\t\t\t\t\tbreak;\n#endif\n\t\t\t}\n\t\t\tGL_SetExtension( extid, true ); // required to be supported by wrapper\n\n\t\t\tpglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units );\n\t\t\tif( glConfig.max_texture_units <= 1 )\n\t\t\t\tpglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_texture_units );\n\t\t\tif( glConfig.max_texture_units <= 1 )\n\t\t\t{\n\t\t\t\tGL_SetExtension( extid, false );\n\t\t\t\tglConfig.max_texture_units = 1;\n\t\t\t}\n\n\t\t\tglConfig.max_texture_coords = glConfig.max_teximage_units = glConfig.max_texture_units;\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_CUBEMAP_EXT:\n\t\t\tif( GL_CheckExtension( \"GL_OES_texture_cube_map\", NULL, 0, \"gl_texture_cubemap\", extid, 0 ))\n\t\t\t\tpglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size );\n\t\t\tbreak;\n\t\tcase GL_ANISOTROPY_EXT:\n\t\t\tglConfig.max_texture_anisotropy = 0.0f;\n\t\t\tif( GL_CheckExtension( \"GL_EXT_texture_filter_anisotropic\", NULL, 0, \"gl_ext_anisotropic_filter\", extid, 0 ))\n\t\t\t\tpglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.max_texture_anisotropy );\n\t\t\tbreak;\n\t\tcase GL_TEXTURE_LOD_BIAS:\n\t\t\tif( GL_CheckExtension( \"GL_EXT_texture_lod_bias\", NULL, 0, \"gl_texture_mipmap_biasing\", extid, 0 ))\n\t\t\t\tpglGetFloatv( GL_MAX_TEXTURE_LOD_BIAS_EXT, &glConfig.max_texture_lod_bias );\n\t\t\tbreak;\n\t\tcase GL_ARB_TEXTURE_NPOT_EXT:\n\t\t\tGL_CheckExtension( \"GL_OES_texture_npot\", NULL, 0, \"gl_texture_npot\", extid, 0 );\n\t\t\tbreak;\n#if !XASH_GL_STATIC\n\t\tcase GL_SHADER_OBJECTS_EXT:\n\t\t\tGL_CheckExtension( \"ES2 Shaders\", shaderobjectsfuncs_gles, ARRAYSIZE( shaderobjectsfuncs_gles ), \"gl_shaderobjects\", extid, 2.0 );\n\t\t\tbreak;\n\t\tcase GL_ARB_VERTEX_ARRAY_OBJECT_EXT:\n\t\t\tif( !GL_CheckExtension( \"GL_OES_vertex_array_object\", vaofuncs, ARRAYSIZE( vaofuncs ), \"gl_vertex_array_object\", extid, 3.0 ))\n\t\t\t\tGL_CheckExtension( \"GL_EXT_vertex_array_object\", vaofuncs, ARRAYSIZE( vaofuncs ), \"gl_vertex_array_object\", extid, 3.0 );\n\t\t\tbreak;\n\t\tcase GL_DRAW_RANGEELEMENTS_EXT:\n\t\t\tif( !GL_CheckExtension( \"GL_EXT_draw_range_elements\", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), \"gl_drawrangeelements\", extid, 3.0 ))\n\t\t\t\tGL_CheckExtension( \"GL_OES_draw_range_elements\", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), \"gl_drawrangeelements\", extid, 3.0 );\n\t\t\tbreak;\n\t\tcase GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT:\n\t\t\tif( !GL_CheckExtension( \"GL_OES_draw_elements_base_vertex\", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), \"gl_drawrangeelementsbasevertex\", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 0 ))\n\t\t\t\tGL_CheckExtension( \"GL_EXT_draw_elements_base_vertex\", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), \"gl_drawrangeelementsbasevertex\", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 3.2 );\n\t\t\tbreak;\n\t\tcase GL_MAP_BUFFER_RANGE_EXT:\n\t\t\tGL_CheckExtension( \"GL_EXT_map_buffer_range\", mapbufferrangefuncs, ARRAYSIZE( mapbufferrangefuncs ), \"gl_map_buffer_range\", GL_MAP_BUFFER_RANGE_EXT , 3.0);\n\t\t\tbreak;\n\t\tcase GL_BUFFER_STORAGE_EXT:\n\t\t\tGL_CheckExtension( \"GL_EXT_buffer_storage\", bufferstoragefuncs, ARRAYSIZE( bufferstoragefuncs ), \"gl_buffer_storage\", GL_BUFFER_STORAGE_EXT, 0);\n\t\t\tbreak;\n\n#endif\n\t\tcase GL_DEBUG_OUTPUT:\n\t\t\tif( glw_state.extended )\n\t\t\t\tGL_CheckExtension( \"GL_KHR_debug\", debugoutputfuncs, ARRAYSIZE( debugoutputfuncs ), \"gl_debug_output\", extid, 0 );\n\t\t\telse\n\t\t\t\tGL_SetExtension( extid, false );\n\t\t\tbreak;\n\t\t// case GL_TEXTURE_COMPRESSION_EXT: NOPE\n\t\t// case GL_SHADER_GLSL100_EXT: NOPE\n\t\t// case GL_TEXTURE_2D_RECT_EXT: NOPE\n\t\t// case GL_TEXTURE_ARRAY_EXT: NOPE\n\t\t// case GL_TEXTURE_3D_EXT: NOPE\n\t\t// case GL_CLAMPTOEDGE_EXT: NOPE\n\t\t// case GL_CLAMP_TEXBORDER_EXT: NOPE\n\t\t// case GL_ARB_TEXTURE_FLOAT_EXT: NOPE\n\t\t// case GL_ARB_DEPTH_FLOAT_EXT: NOPE\n\t\t// case GL_ARB_SEAMLESS_CUBEMAP: NOPE\n\t\t// case GL_EXT_GPU_SHADER4: NOPE\n\t\t// case GL_DEPTH_TEXTURE: NOPE\n\t\t// case GL_DRAWRANGEELEMENTS_EXT: NOPE\n\t\tdefault:\n\t\t\tGL_SetExtension( extid, false );\n\t\t}\n\t}\n#if !XASH_GL_STATIC\n\tGL2_ShimInit();\n#endif\n}\n#else\nstatic void GL_InitExtensionsBigGL( void )\n{\n\t// intialize wrapper type\n\tglConfig.context = gEngfuncs.Sys_CheckParm( \"-glcore\" )? CONTEXT_TYPE_GL_CORE : CONTEXT_TYPE_GL;\n\tglConfig.wrapper = GLES_WRAPPER_NONE;\n\n\tif( Q_stristr( glConfig.renderer_string, \"geforce\" ))\n\t\tglConfig.hardware_type = GLHW_NVIDIA;\n\telse if( Q_stristr( glConfig.renderer_string, \"quadro fx\" ))\n\t\tglConfig.hardware_type = GLHW_NVIDIA;\n\telse if( Q_stristr(glConfig.renderer_string, \"rv770\" ))\n\t\tglConfig.hardware_type = GLHW_RADEON;\n\telse if( Q_stristr(glConfig.renderer_string, \"radeon hd\" ))\n\t\tglConfig.hardware_type = GLHW_RADEON;\n\telse if( Q_stristr( glConfig.renderer_string, \"eah4850\" ) || Q_stristr( glConfig.renderer_string, \"eah4870\" ))\n\t\tglConfig.hardware_type = GLHW_RADEON;\n\telse if( Q_stristr( glConfig.renderer_string, \"radeon\" ))\n\t\tglConfig.hardware_type = GLHW_RADEON;\n\telse if( Q_stristr( glConfig.renderer_string, \"intel\" ))\n\t\tglConfig.hardware_type = GLHW_INTEL;\n\telse glConfig.hardware_type = GLHW_GENERIC;\n\n\t// gl4es may be used system-wide\n\tif( Q_stristr( glConfig.renderer_string, \"gl4es\" ))\n\t\tglConfig.wrapper = GLES_WRAPPER_GL4ES;\n\n\t// multitexture\n\tglConfig.max_texture_units = glConfig.max_texture_coords = glConfig.max_teximage_units = 1;\n\tif( GL_CheckExtension( \"GL_ARB_multitexture\", multitexturefuncs, ARRAYSIZE( multitexturefuncs ), \"gl_arb_multitexture\", GL_ARB_MULTITEXTURE, 1.3f ))\n\t{\n\t\tpglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units );\n\t}\n\n\tif( glConfig.max_texture_units == 1 )\n\t\tGL_SetExtension( GL_ARB_MULTITEXTURE, false );\n\n\t// 3d texture support\n\tif( GL_CheckExtension( \"GL_EXT_texture3D\", texture3dextfuncs, ARRAYSIZE( texture3dextfuncs ), \"gl_texture_3d\", GL_TEXTURE_3D_EXT, 2.0f ))\n\t{\n\t\tpglGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size );\n\n\t\tif( glConfig.max_3d_texture_size < 32 )\n\t\t{\n\t\t\tGL_SetExtension( GL_TEXTURE_3D_EXT, false );\n\t\t\tgEngfuncs.Con_Printf( S_ERROR \"GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\\n\" );\n\t\t}\n\t}\n\n\t// 2d texture array support\n\tif( GL_CheckExtension( \"GL_EXT_texture_array\", texture3dextfuncs, ARRAYSIZE( texture3dextfuncs ), \"gl_texture_2d_array\", GL_TEXTURE_ARRAY_EXT, 0 ))\n\t\tpglGetIntegerv( GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &glConfig.max_2d_texture_layers );\n\n\t// cubemaps support\n\tif( GL_CheckExtension( \"GL_ARB_texture_cube_map\", NULL, 0, \"gl_texture_cubemap\", GL_TEXTURE_CUBEMAP_EXT, 0 ))\n\t{\n\t\tpglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size );\n\n\t\t// check for seamless cubemaps too\n\t\tGL_CheckExtension( \"GL_ARB_seamless_cube_map\", NULL, 0, \"gl_texture_cubemap_seamless\", GL_ARB_SEAMLESS_CUBEMAP, 0 );\n\t}\n\n\tGL_CheckExtension( \"GL_ARB_texture_non_power_of_two\", NULL, 0, \"gl_texture_npot\", GL_ARB_TEXTURE_NPOT_EXT, 0 );\n\tGL_CheckExtension( \"GL_ARB_texture_compression\", texturecompressionfuncs, ARRAYSIZE( texturecompressionfuncs ), \"gl_texture_dxt_compression\", GL_TEXTURE_COMPRESSION_EXT, 0 );\n\tif( !GL_CheckExtension( \"GL_EXT_texture_edge_clamp\", NULL, 0, \"gl_clamp_to_edge\", GL_CLAMPTOEDGE_EXT, 2.0 )) // present in ES2\n\t\tGL_CheckExtension( \"GL_SGIS_texture_edge_clamp\", NULL, 0, \"gl_clamp_to_edge\", GL_CLAMPTOEDGE_EXT, 0 );\n\n\tglConfig.max_texture_anisotropy = 0.0f;\n\tif( GL_CheckExtension( \"GL_EXT_texture_filter_anisotropic\", NULL, 0, \"gl_texture_anisotropic_filter\", GL_ANISOTROPY_EXT, 0 ))\n\t\tpglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.max_texture_anisotropy );\n\n#if XASH_WIN32 // Win32 only drivers?\n\t// g-cont. because lodbias it too glitchy on Intel's cards\n\tif( glConfig.hardware_type != GLHW_INTEL )\n#endif\n\t{\n\t\tif( GL_CheckExtension( \"GL_EXT_texture_lod_bias\", NULL, 0, \"gl_texture_mipmap_biasing\", GL_TEXTURE_LOD_BIAS, 1.4 ))\n\t\t\tpglGetFloatv( GL_MAX_TEXTURE_LOD_BIAS_EXT, &glConfig.max_texture_lod_bias );\n\t}\n\n\tGL_CheckExtension( \"GL_ARB_texture_border_clamp\", NULL, 0, NULL, GL_CLAMP_TEXBORDER_EXT, 2.0 ); // present in ES2\n\n\tGL_CheckExtension( \"GL_ARB_depth_texture\", NULL, 0, NULL, GL_DEPTH_TEXTURE, 1.4 ); // missing in gles, check GL_OES_depth_texture\n\tGL_CheckExtension( \"GL_ARB_texture_float\", NULL, 0, \"gl_texture_float\", GL_ARB_TEXTURE_FLOAT_EXT, 0 );\n\tGL_CheckExtension( \"GL_ARB_depth_buffer_float\", NULL, 0, \"gl_texture_depth_float\", GL_ARB_DEPTH_FLOAT_EXT, 0 );\n\tGL_CheckExtension( \"GL_EXT_gpu_shader4\", NULL, 0, NULL, GL_EXT_GPU_SHADER4, 0 ); // don't confuse users\n\tGL_CheckExtension( \"GL_ARB_vertex_buffer_object\", vbofuncs, ARRAYSIZE( vbofuncs ), \"gl_vertex_buffer_object\", GL_ARB_VERTEX_BUFFER_OBJECT_EXT, 2.0 );\n\tGL_CheckExtension( \"GL_ARB_texture_multisample\", multisampletexfuncs, ARRAYSIZE( multisampletexfuncs ), \"gl_texture_multisample\", GL_TEXTURE_MULTISAMPLE, 0 );\n\tGL_CheckExtension( \"GL_ARB_texture_compression_bptc\", NULL, 0, \"gl_texture_bptc_compression\", GL_ARB_TEXTURE_COMPRESSION_BPTC, 0 );\n#if !XASH_GL_STATIC\n\tif( glConfig.context == CONTEXT_TYPE_GL_CORE )\n\t\tGL_CheckExtension( \"shader_objects\", shaderobjectsfuncs_gles, ARRAYSIZE( shaderobjectsfuncs_gles ), \"gl_shaderobjects\", GL_SHADER_OBJECTS_EXT, 2.0 );\n\telse\n\t\tGL_CheckExtension( \"GL_ARB_shader_objects\", shaderobjectsfuncs, ARRAYSIZE( shaderobjectsfuncs ), \"gl_shaderobjects\", GL_SHADER_OBJECTS_EXT, 2.0 );\n\tGL_CheckExtension( \"GL_ARB_vertex_array_object\", vaofuncs, ARRAYSIZE( vaofuncs ), \"gl_vertex_array_object\", GL_ARB_VERTEX_ARRAY_OBJECT_EXT, 3.0 );\n\tGL_CheckExtension( \"GL_ARB_buffer_storage\", bufferstoragefuncs, ARRAYSIZE( bufferstoragefuncs ), \"gl_buffer_storage\", GL_BUFFER_STORAGE_EXT, 4.4);\n\tGL_CheckExtension( \"GL_ARB_map_buffer_range\", mapbufferrangefuncs, ARRAYSIZE( mapbufferrangefuncs ), \"gl_map_buffer_range\", GL_MAP_BUFFER_RANGE_EXT , 3.0);\n\tGL_CheckExtension( \"GL_ARB_draw_elements_base_vertex\", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), \"gl_drawrangeelementsbasevertex\", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 3.2 );\n#endif\n\tif( GL_CheckExtension( \"GL_ARB_shading_language_100\", NULL, 0, NULL, GL_SHADER_GLSL100_EXT, 2.0 ))\n\t{\n\t\tpglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords );\n\t\tpglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_teximage_units );\n\n\t\t// check for hardware skinning\n\t\tpglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.max_vertex_uniforms );\n\t\tpglGetIntegerv( GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.max_vertex_attribs );\n\n#if XASH_WIN32 // Win32 only drivers?\n\t\tif( glConfig.hardware_type == GLHW_RADEON && glConfig.max_vertex_uniforms > 512 )\n\t\t\tglConfig.max_vertex_uniforms /= 4; // radeon returns not correct info\n#endif\n\t}\n\telse\n\t{\n\t\t// just get from multitexturing\n\t\tglConfig.max_texture_coords = glConfig.max_teximage_units = glConfig.max_texture_units;\n\t}\n\n\t// rectangle textures support\n\tGL_CheckExtension( \"GL_ARB_texture_rectangle\", NULL, 0, \"gl_texture_rectangle\", GL_TEXTURE_2D_RECT_EXT, 0 );\n\n\tif( !GL_CheckExtension( \"glDrawRangeElements\", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), \"gl_drawrangeelements\", GL_DRAW_RANGEELEMENTS_EXT, 0 ) )\n\t{\n\t\tif( GL_CheckExtension( \"glDrawRangeElementsEXT\", drawrangeelementsextfuncs, ARRAYSIZE( drawrangeelementsextfuncs ),\n\t\t\t\"gl_drawrangelements\", GL_DRAW_RANGEELEMENTS_EXT, 0 ))\n\t\t{\n#if !XASH_GL_STATIC\n\t\t\tpglDrawRangeElements = pglDrawRangeElementsEXT;\n#endif\n\t\t}\n\t}\n\n\t// this won't work without extended context\n\tif( glw_state.extended )\n\t\tGL_CheckExtension( \"GL_ARB_debug_output\", debugoutputfuncs, ARRAYSIZE( debugoutputfuncs ), \"gl_debug_output\", GL_DEBUG_OUTPUT, 0 );\n\n#if XASH_PSVITA\n\t// not all GL1.1 functions are implemented in vitaGL, but there's enough\n\tGL_SetExtension( GL_OPENGL_110, true );\n\t// init our immediate mode override\n\tVGL_ShimInit();\n#endif\n#if !XASH_GLES && !XASH_GL_STATIC\n\tif( gEngfuncs.Sys_CheckParm( \"-gl2shim\" ))\n\t\tGL2_ShimInit();\n#endif\n}\n#endif\n\nvoid GL_InitExtensions( void )\n{\n\tchar value[MAX_VA_STRING];\n\tGLint major = 0, minor = 0;\n\n\tGL_OnContextCreated();\n\n\t// initialize gl extensions\n\tGL_CheckExtension( \"OpenGL 1.1.0\", opengl_110funcs, ARRAYSIZE( opengl_110funcs ), NULL, GL_OPENGL_110, 1.0 );\n\n\t// get our various GL strings\n\tglConfig.vendor_string = (const char *)pglGetString( GL_VENDOR );\n\tglConfig.renderer_string = (const char *)pglGetString( GL_RENDERER );\n\tglConfig.version_string = (const char *)pglGetString( GL_VERSION );\n\tglConfig.extensions_string = (const char *)pglGetString( GL_EXTENSIONS );\n\n\tpglGetIntegerv( GL_MAJOR_VERSION, &major );\n\tpglGetIntegerv( GL_MINOR_VERSION, &minor );\n\tif( !major && glConfig.version_string )\n\t{\n\t\tconst char *str = glConfig.version_string;\n\t\tfloat ver;\n\n\t\twhile( *str && ( *str < '0' || *str > '9' )) str++;\n\t\tver = Q_atof(str);\n\t\tif( ver )\n\t\t{\n\t\t\tglConfig.version_major = ver;\n\t\t\tglConfig.version_minor = (int)(ver * 10) % 10;\n\t\t}\n\t}\n\telse\n\t{\n\t\tglConfig.version_major = major;\n\t\tglConfig.version_minor = minor;\n\t}\n#if !XASH_GL_STATIC\n\tif( !glConfig.extensions_string )\n\t{\n\t\tint n = 0;\n\t\tpglGetStringi = gEngfuncs.GL_GetProcAddress( \"glGetStringi\" );\n\n\t\tpglGetIntegerv( GL_NUM_EXTENSIONS, &n );\n\t\tif( n && pglGetStringi )\n\t\t{\n\t\t\tint i, len = 1;\n\t\t\tchar *str;\n\n\t\t\tfor( i = 0; i < n; i++ )\n\t\t\t\tlen += Q_strlen((const char *)pglGetStringi( GL_EXTENSIONS, i )) + 1;\n\n\t\t\tstr = (char*)Mem_Calloc( r_temppool, len );\n\t\t\tglConfig.extensions_string = str;\n\n\t\t\tfor( i = 0; i < n; i++ )\n\t\t\t{\n\t\t\t\tint l = Q_strncpy( str, pglGetStringi( GL_EXTENSIONS, i ), len );\n\t\t\t\tstr += l;\n\t\t\t\t*str++ = ' ';\n\t\t\t\tlen -= l + 1;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\tgEngfuncs.Con_Reportf( \"^3Video^7: %s\\n\", glConfig.renderer_string );\n\n#if XASH_GLES\n\tGL_InitExtensionsGLES();\n#else\n\tGL_InitExtensionsBigGL();\n#endif\n\n\tpglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.max_2d_texture_size );\n\tif( glConfig.max_2d_texture_size <= 0 ) glConfig.max_2d_texture_size = 256;\n#if !XASH_GL4ES\n\t// enable gldebug if allowed\n\tif( GL_Support( GL_DEBUG_OUTPUT ))\n\t{\n\t\tif( gpGlobals->developer )\n\t\t{\n\t\t\tgEngfuncs.Con_Reportf( \"Installing GL_DebugOutput...\\n\");\n\t\t\tpglDebugMessageCallbackARB( GL_DebugOutput, NULL );\n\n\t\t\t// force everything to happen in the main thread instead of in a separate driver thread\n\t\t\tpglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB );\n\n\t\t}\n\n\t\t// enable all the low priority messages\n\t\tpglDebugMessageControlARB( GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, true );\n\t}\n#endif\n\tif( GL_Support( GL_TEXTURE_2D_RECT_EXT ))\n\t\tpglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &glConfig.max_2d_rectangle_size );\n\n\tQ_snprintf( value, sizeof( value ), \"%i\", glConfig.max_2d_texture_size );\n\tgEngfuncs.Cvar_Get( \"gl_max_size\", value, 0, \"opengl texture max dims\" );\n\tgEngfuncs.Cvar_SetValue( \"gl_anisotropy\", bound( 0, gl_texture_anisotropy.value, glConfig.max_texture_anisotropy ));\n\n\tif( GL_Support( GL_TEXTURE_COMPRESSION_EXT ))\n\t\tgEngfuncs.Image_AddCmdFlags( IL_DDS_HARDWARE );\n\n\t// MCD has buffering issues\n#if XASH_WIN32\n\tif( Q_strstr( glConfig.renderer_string, \"gdi\" ))\n\t\tgEngfuncs.Cvar_SetValue( \"gl_finish\", 1 );\n#endif\n\n\tR_RenderInfo_f();\n\n\ttr.framecount = tr.visframecount = 1;\n\tglw_state.initialized = true;\n}\n\nvoid GL_ClearExtensions( void )\n{\n\t// now all extensions are disabled\n\tmemset( glConfig.extension, 0, sizeof( glConfig.extension ));\n\tglw_state.initialized = false;\n#if XASH_PSVITA\n\t// deinit our immediate mode override\n\tVGL_ShimShutdown();\n#endif\n}\n\n//=======================================================================\n\n/*\n=================\nGL_InitCommands\n=================\n*/\nstatic void GL_InitCommands( void )\n{\n\tRETRIEVE_ENGINE_SHARED_CVAR_LIST();\n\n\tgEngfuncs.Cvar_RegisterVariable( &r_lighting_extended );\n\tgEngfuncs.Cvar_RegisterVariable( &r_lighting_ambient );\n\tgEngfuncs.Cvar_RegisterVariable( &r_novis );\n\tgEngfuncs.Cvar_RegisterVariable( &r_nocull );\n\tgEngfuncs.Cvar_RegisterVariable( &r_detailtextures );\n\tgEngfuncs.Cvar_RegisterVariable( &r_lockpvs );\n\tgEngfuncs.Cvar_RegisterVariable( &r_lockfrustum );\n\tgEngfuncs.Cvar_RegisterVariable( &r_traceglow );\n\tgEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures );\n\tgEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements );\n\tgEngfuncs.Cvar_RegisterVariable( &r_ripple );\n\tgEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime );\n\tgEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime );\n\tgEngfuncs.Cvar_RegisterVariable( &r_shadows );\n\tgEngfuncs.Cvar_RegisterVariable( &r_vbo );\n\tgEngfuncs.Cvar_RegisterVariable( &r_vbo_dlightmode );\n\tgEngfuncs.Cvar_RegisterVariable( &r_vbo_overbrightmode );\n\tgEngfuncs.Cvar_RegisterVariable( &r_vbo_detail );\n\tgEngfuncs.Cvar_RegisterVariable( &r_large_lightmaps );\n\tgEngfuncs.Cvar_RegisterVariable( &r_dlight_virtual_radius );\n\n\tgEngfuncs.Cvar_RegisterVariable( &gl_extensions );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_texture_nearest );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_lightmap_nearest );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_check_errors );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_texture_anisotropy );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_texture_lodbias );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_keeptjunctions );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_finish );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_nosort );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_test );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_wireframe );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_msaa );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_stencilbits );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_round_down );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_overbright );\n\tgEngfuncs.Cvar_RegisterVariable( &gl_fog );\n\n\t// these cvar not used by engine but some mods requires this\n\tgEngfuncs.Cvar_RegisterVariable( &gl_polyoffset );\n\n\t// make sure gl_vsync is checked after vid_restart\n\tSetBits( gl_vsync->flags, FCVAR_CHANGED );\n\n\tgEngfuncs.Cmd_AddCommand( \"r_info\", R_RenderInfo_f, \"display renderer info\" );\n\tgEngfuncs.Cmd_AddCommand( \"timerefresh\", SCR_TimeRefresh_f, \"turn quickly and print rendering statistcs\" );\n}\n\n/*\n===============\nR_CheckVBO\n\nregister VBO cvars and get default value\n===============\n*/\nstatic void R_CheckVBO( void )\n{\n\tqboolean disable = false;\n\tint flags = 0;\n\n\t// some bad GLES1 implementations breaks dlights completely\n\tif( glConfig.max_texture_units < 3 )\n\t\tdisable = true;\n\n#if XASH_MOBILE_PLATFORM\n\t// VideoCore4 drivers have a problem with mixing VBO and client arrays\n\t// Disable it, as there is no suitable workaround here\n\tif( Q_stristr( glConfig.renderer_string, \"VideoCore IV\" ) || Q_stristr( glConfig.renderer_string, \"vc4\" ) )\n\t\tdisable = true;\n#endif\n\n\t// we do not want to write vbo code that does not use multitexture\n\tif( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 )\n\t{\n\t\tflags = FCVAR_READ_ONLY;\n\t\tdisable = true;\n\t}\n\n\tif( disable )\n\t{\n\t\tgEngfuncs.Cvar_FullSet( r_vbo.name, \"0\", flags );\n\t\tgEngfuncs.Cvar_FullSet( r_vbo_dlightmode.name, \"0\", flags );\n\t}\n}\n\n/*\n=================\nGL_RemoveCommands\n=================\n*/\nstatic void GL_RemoveCommands( void )\n{\n\tgEngfuncs.Cmd_RemoveCommand( \"r_info\" );\n\tgEngfuncs.Cmd_RemoveCommand( \"timerefresh\" );\n}\n\n/*\n===============\nR_Init\n===============\n*/\nqboolean R_Init( void )\n{\n\tif( glw_state.initialized )\n\t\treturn true;\n\n\tGL_InitCommands();\n\tGL_InitRandomTable();\n\n\tGL_SetDefaultState();\n\n\tr_temppool = Mem_AllocPool( \"Render Zone\" );\n\n\t// create the window and set up the context\n\tif( !gEngfuncs.R_Init_Video( REF_GL )) // request GL context\n\t{\n\t\tGL_RemoveCommands();\n\t\tgEngfuncs.R_Free_Video();\n// Why? Host_Error again???\n//\t\tgEngfuncs.Host_Error( \"Can't initialize video subsystem\\nProbably driver was not installed\" );\n\t\tMem_FreePool( &r_temppool );\n\t\treturn false;\n\t}\n\n\t// see R_ProcessEntData for tr.entities initialization\n\ttr.world = (struct world_static_s *)ENGINE_GET_PARM( PARM_GET_WORLD_PTR );\n\ttr.movevars = (movevars_t *)ENGINE_GET_PARM( PARM_GET_MOVEVARS_PTR );\n\ttr.palette = (color24 *)ENGINE_GET_PARM( PARM_GET_PALETTE_PTR );\n\ttr.viewent = (cl_entity_t *)ENGINE_GET_PARM( PARM_GET_VIEWENT_PTR );\n\ttr.texgammatable = (byte *)ENGINE_GET_PARM( PARM_GET_TEXGAMMATABLE_PTR );\n\ttr.lightgammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LIGHTGAMMATABLE_PTR );\n\ttr.screengammatable = (uint *)ENGINE_GET_PARM( PARM_GET_SCREENGAMMATABLE_PTR );\n\ttr.lineargammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LINEARGAMMATABLE_PTR );\n\ttr.dlights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_DLIGHTS_PTR );\n\ttr.elights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_ELIGHTS_PTR );\n\n\tGL_SetDefaults();\n\tR_CheckVBO();\n\tR_InitImages();\n\tR_SpriteInit();\n\tR_StudioInit();\n\tR_AliasInit();\n\tR_ClearDecals();\n\tR_ClearScene();\n\n\treturn true;\n}\n\n/*\n===============\nR_Shutdown\n===============\n*/\nvoid R_Shutdown( void )\n{\n\tif( !glw_state.initialized )\n\t\treturn;\n\n\tGL_RemoveCommands();\n\tR_ShutdownImages();\n#if !XASH_GLES && !XASH_GL_STATIC\n\tGL2_ShimShutdown();\n#endif\n\n\tMem_FreePool( &r_temppool );\n\n#if XASH_GL4ES\n\tclose_gl4es();\n#endif // XASH_GL4ES\n\n\t// shut down OS specific OpenGL stuff like contexts, etc.\n\tgEngfuncs.R_Free_Video();\n}\n\n/*\n=================\nGL_ErrorString\nconvert errorcode to string\n=================\n*/\nconst char *GL_ErrorString( int err )\n{\n\tswitch( err )\n\t{\n\tcase GL_STACK_OVERFLOW:\n\t\treturn \"GL_STACK_OVERFLOW\";\n\tcase GL_STACK_UNDERFLOW:\n\t\treturn \"GL_STACK_UNDERFLOW\";\n\tcase GL_INVALID_ENUM:\n\t\treturn \"GL_INVALID_ENUM\";\n\tcase GL_INVALID_VALUE:\n\t\treturn \"GL_INVALID_VALUE\";\n\tcase GL_INVALID_OPERATION:\n\t\treturn \"GL_INVALID_OPERATION\";\n\tcase GL_OUT_OF_MEMORY:\n\t\treturn \"GL_OUT_OF_MEMORY\";\n\tdefault:\n\t\treturn \"UNKNOWN ERROR\";\n\t}\n}\n\n/*\n=================\nGL_CheckForErrors\nobsolete\n=================\n*/\nvoid GL_CheckForErrors_( const char *filename, const int fileline )\n{\n\tint\terr;\n\n\tif( !gl_check_errors.value )\n\t\treturn;\n\n\tif(( err = pglGetError( )) == GL_NO_ERROR )\n\t\treturn;\n\n\tgEngfuncs.Con_Printf( S_OPENGL_ERROR \"%s (at %s:%i)\\n\", GL_ErrorString( err ), filename, fileline );\n}\n\nvoid GL_SetupAttributes( int safegl )\n{\n\tint context_flags = 0; // REFTODO!!!!!\n\tint samples = 0;\n\n#if XASH_GLES\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_ES );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_EGL, 1 );\n#if XASH_NANOGL\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 );\n#else // !XASH_NANOGL\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 2 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 0 );\n#endif\n\n#elif XASH_GL4ES\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_ES );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_EGL, 1 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 2 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 0 );\n#else // GL1.x\n\tif( gEngfuncs.Sys_CheckParm( \"-glcore\" ))\n\t{\n\t\tSetBits( context_flags, FCONTEXT_CORE_PROFILE );\n\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_CORE );\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 3 );\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 3 );\n\t}\n\telse\n\t{\n\t\tif( !safegl )\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_COMPATIBILITY );\n\t\telse\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 );\n\t\t}\n\t}\n#endif // XASH_GLES\n\n\tif( gEngfuncs.Sys_CheckParm( \"-gldebug\" ))\n\t{\n\t\tgEngfuncs.Con_Reportf( \"Creating an extended GL context for debug...\\n\" );\n\t\tSetBits( context_flags, FCONTEXT_DEBUG_ARB );\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_FLAGS, REF_GL_CONTEXT_DEBUG_FLAG );\n\t\tglw_state.extended = true;\n\t}\n\n\tif( safegl > SAFE_DONTCARE )\n\t{\n\t\tsafegl = -1; // can't retry anymore, can only shutdown engine\n\t\treturn;\n\t}\n\n\tgEngfuncs.Con_Printf( \"Trying safe opengl mode %d\\n\", safegl );\n\n\tif( safegl == SAFE_DONTCARE )\n\t\treturn;\n\n\tgEngfuncs.GL_SetAttribute( REF_GL_DOUBLEBUFFER, 1 );\n\n\tif( safegl < SAFE_NOACC )\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_ACCELERATED_VISUAL, 1 );\n\n\tgEngfuncs.Con_Printf( \"bpp %d\\n\", gpGlobals->desktopBitsPixel );\n\n\tif( safegl < SAFE_NOSTENCIL )\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_STENCIL_SIZE, gl_stencilbits.value );\n\n\tif( safegl < SAFE_NOALPHA )\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_ALPHA_SIZE, 8 );\n\n\tif( safegl < SAFE_NODEPTH )\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_DEPTH_SIZE, 24 );\n\telse\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_DEPTH_SIZE, 8 );\n\n\tif( safegl < SAFE_NOCOLOR )\n\t{\n\t\tif( gpGlobals->desktopBitsPixel >= 24 )\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_RED_SIZE, 8 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_GREEN_SIZE, 8 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_BLUE_SIZE, 8 );\n\t\t}\n\t\telse if( gpGlobals->desktopBitsPixel >= 16 )\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_RED_SIZE, 5 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_GREEN_SIZE, 6 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_BLUE_SIZE, 5 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_RED_SIZE, 3 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_GREEN_SIZE, 3 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_BLUE_SIZE, 2 );\n\t\t}\n\t}\n\n\tif( safegl < SAFE_NOMSAA )\n\t{\n\t\tswitch( (int)gEngfuncs.pfnGetCvarFloat( \"gl_msaa_samples\" ))\n\t\t{\n\t\tcase 2:\n\t\tcase 4:\n\t\tcase 8:\n\t\tcase 16:\n\t\t\tsamples = gEngfuncs.pfnGetCvarFloat( \"gl_msaa_samples\" );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tsamples = 0; // don't use, because invalid parameter is passed\n\t\t}\n\n\t\tif( samples )\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_MULTISAMPLEBUFFERS, 1 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_MULTISAMPLESAMPLES, samples );\n\n\t\t\tglConfig.max_multisamples = samples;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_MULTISAMPLEBUFFERS, 0 );\n\t\t\tgEngfuncs.GL_SetAttribute( REF_GL_MULTISAMPLESAMPLES, 0 );\n\n\t\t\tglConfig.max_multisamples = 0;\n\t\t}\n\t}\n\telse\n\t{\n\t\tgEngfuncs.Cvar_Set( \"gl_msaa_samples\", \"0\" );\n\t}\n}\n\nvoid wes_init( const char *gles2 );\nint nanoGL_Init( void );\n#if XASH_GL4ES\nstatic void APIENTRY GL4ES_GetMainFBSize( int *width, int *height )\n{\n\t*width = gpGlobals->width;\n\t*height = gpGlobals->height;\n}\n\nstatic void * APIENTRY GL4ES_GetProcAddress( const char *name )\n{\n\tif( !Q_strcmp(name, \"glShadeModel\") )\n\t\t// combined gles/gles2/gl implementation exports this, but it is invalid\n\t\treturn NULL;\n\treturn gEngfuncs.GL_GetProcAddress( name );\n}\n#endif // XASH_GL4ES\n\nvoid GL_OnContextCreated( void )\n{\n\tint colorBits[3];\n#if XASH_NANOGL\n\tnanoGL_Init();\n#endif\n\n\tgEngfuncs.GL_GetAttribute( REF_GL_RED_SIZE, &colorBits[0] );\n\tgEngfuncs.GL_GetAttribute( REF_GL_GREEN_SIZE, &colorBits[1] );\n\tgEngfuncs.GL_GetAttribute( REF_GL_BLUE_SIZE, &colorBits[2] );\n\tglConfig.color_bits = colorBits[0] + colorBits[1] + colorBits[2];\n\n\tgEngfuncs.GL_GetAttribute( REF_GL_ALPHA_SIZE, &glConfig.alpha_bits );\n\tgEngfuncs.GL_GetAttribute( REF_GL_DEPTH_SIZE, &glConfig.depth_bits );\n\tgEngfuncs.GL_GetAttribute( REF_GL_STENCIL_SIZE, &glConfig.stencil_bits );\n\tglState.stencilEnabled = glConfig.stencil_bits ? true : false;\n\n\tgEngfuncs.GL_GetAttribute( REF_GL_MULTISAMPLESAMPLES, &glConfig.msaasamples );\n\tgEngfuncs.GL_GetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, &glConfig.version_major );\n\tgEngfuncs.GL_GetAttribute( REF_GL_CONTEXT_MINOR_VERSION, &glConfig.version_minor );\n\n#if XASH_WES\n\twes_init( \"\" );\n#endif // XASH_WES\n\n#if XASH_GL4ES\n\tset_getprocaddress( GL4ES_GetProcAddress );\n\tset_getmainfbsize( GL4ES_GetMainFBSize );\n\tinitialize_gl4es();\n\n\t// merge glBegin/glEnd in beams and console\n\tpglHint( GL_BEGINEND_HINT_GL4ES, 1 );\n\t// dxt unpacked to 16-bit looks ugly\n\tpglHint( GL_AVOID16BITS_HINT_GL4ES, 1 );\n#endif // XASH_GL4ES\n}\n"
  },
  {
    "path": "ref/gl/gl_rlight.c",
    "content": "/*\ngl_rlight.c - dynamic and static lights\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_params.h\"\n\n/*\n=============================================================================\n\nDYNAMIC LIGHTS\n\n=============================================================================\n*/\n/*\n==================\nCL_RunLightStyles\n\n==================\n*/\nvoid CL_RunLightStyles( lightstyle_t *ls )\n{\n\tint i;\n\tfloat frametime = (gp_cl->time - gp_cl->oldtime);\n\n\tif( !WORLDMODEL )\n\t\treturn;\n\n\tif( !WORLDMODEL->lightdata )\n\t{\n\t\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t\t\ttr.lightstylevalue[i] = 256 * 256;\n\t\treturn;\n\t}\n\n\t// light animations\n\t// 'm' is normal light, 'a' is no light, 'z' is double bright\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tint k, flight, clight;\n\t\tfloat l, lerpfrac, backlerp;\n\n\t\tif( !gp_cl->paused && frametime <= 0.1f )\n\t\t\tls[i].time += frametime; // evaluate local time\n\n\t\tif( !ls[i].length )\n\t\t{\n\t\t\ttr.lightstylevalue[i] = 256;\n\t\t\tcontinue;\n\t\t}\n\t\telse if( ls[i].length == 1 )\n\t\t{\n\t\t\t// single length style so don't bother interpolating\n\t\t\ttr.lightstylevalue[i] = ( ls[i].map[0] ) * 22;\n\t\t\tcontinue;\n\t\t}\n\n\t\tflight = (int)Q_floor( ls[i].time * 10 );\n\n\t\tif( !ls[i].interp || !cl_lightstyle_lerping->value )\n\t\t{\n\t\t\ttr.lightstylevalue[i] = ls[i].map[flight%ls[i].length] * 22;\n\t\t\tcontinue;\n\t\t}\n\n\t\tclight = (int)Q_ceil( ls[i].time * 10 );\n\t\tlerpfrac = ( ls[i].time * 10 ) - flight;\n\t\tbacklerp = 1.0f - lerpfrac;\n\n\t\t// interpolate animating light\n\t\t// frame just gone\n\t\tk = ls[i].map[flight % ls[i].length];\n\t\tl = (float)( k * 22.0f ) * backlerp;\n\n\t\t// upcoming frame\n\t\tk = ls[i].map[clight % ls[i].length];\n\t\tl += (float)( k * 22.0f ) * lerpfrac;\n\n\t\ttr.lightstylevalue[i] = (int)l;\n\t}\n}\n\n/*\n=============\nR_MarkLights\n=============\n*/\nvoid R_MarkLights( const dlight_t *light, int bit, const mnode_t *node )\n{\n\tconst float virtual_radius = light->radius * Q_max( 1.0f, r_dlight_virtual_radius.value );\n\tconst float maxdist = light->radius * light->radius;\n\tfloat dist;\n\tint i;\n\tmnode_t *children[2];\n\tint firstsurface, numsurfaces;\n\nstart:\n\n\tif( !node || node->contents < 0 )\n\t\treturn;\n\n\tdist = PlaneDiff( light->origin, node->plane );\n\n\tnode_children( children, node, RI.currentmodel );\n\n\tif( dist > virtual_radius )\n\t{\n\t\tnode = children[0];\n\t\tgoto start;\n\t}\n\n\tif( dist < -virtual_radius )\n\t{\n\t\tnode = children[1];\n\t\tgoto start;\n\t}\n\n\t// mark the polygons\n\tfirstsurface = node_firstsurface( node, RI.currentmodel );\n\tnumsurfaces = node_numsurfaces( node, RI.currentmodel );\n\n\tfor( i = 0; i < numsurfaces; i++ )\n\t{\n\t\tvec3_t impact;\n\t\tfloat s, t, l;\n\t\tmsurface_t *surf = &RI.currentmodel->surfaces[firstsurface + i];\n\t\tconst mextrasurf_t *info = surf->info;\n\n\t\tif( surf->plane->type < 3 )\n\t\t{\n\t\t\tVectorCopy( light->origin, impact );\n\t\t\timpact[surf->plane->type] -= dist;\n\t\t}\n\t\telse VectorMA( light->origin, -dist, surf->plane->normal, impact );\n\n\t\t// a1ba: the fix was taken from JoeQuake, which traces back to FitzQuake,\n\t\t// which attributes it to LadyHavoc (Darkplaces author)\n\t\t// clamp center of light to corner and check brightness\n\t\tl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\t\ts = l + 0.5;\n\t\ts = bound( 0, s, info->lightextents[0] );\n\t\ts = l - s;\n\n\t\tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\t\tt = l + 0.5;\n\t\tt = bound( 0, t, info->lightextents[1] );\n\t\tt = l - t;\n\n\t\tif( s * s + t * t + dist * dist >= maxdist )\n\t\t\tcontinue;\n\n\t\tif( surf->dlightframe != tr.dlightframecount )\n\t\t{\n\t\t\tsurf->dlightbits = bit;\n\t\t\tsurf->dlightframe = tr.dlightframecount;\n\t\t}\n\t\telse surf->dlightbits |= bit;\n\t}\n\n\tR_MarkLights( light, bit, children[0] );\n\tR_MarkLights( light, bit, children[1] );\n}\n\n/*\n=============\nR_PushDlights\n=============\n*/\nvoid R_PushDlights( void )\n{\n\tint\ti;\n\n\ttr.dlightframecount = tr.framecount;\n\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\n\t// no world -- no dlights\n\tif( !RI.currententity )\n\t\treturn;\n\n\tRI.currentmodel = RI.currententity->model;\n\n\tfor( i = 0; i < MAX_DLIGHTS; i++ )\n\t{\n\t\tdlight_t *l = &tr.dlights[i];\n\n\t\tif( l->die < gp_cl->time || !l->radius )\n\t\t\tcontinue;\n\n\t\tif( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 ))\n\t\t\tcontinue;\n\n\t\tR_MarkLights( l, 1<<i, RI.currentmodel->nodes );\n\t}\n}\n\n/*\n=======================================================================\n\n\tAMBIENT LIGHTING\n\n=======================================================================\n*/\nstatic vec3_t\tg_trace_lightspot;\nstatic vec3_t\tg_trace_lightvec;\nstatic float\tg_trace_fraction;\n\n/*\n=================\nR_RecursiveLightPoint\n=================\n*/\nstatic qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end )\n{\n\tfloat\t\tfront, back, frac, midf;\n\tint\t\ti, map, side, size;\n\tfloat\t\tds, dt, s, t;\n\tint\t\tsample_size;\n\tcolor24\t\t*lm, *dm;\n\tmextrasurf_t\t*info;\n\tmsurface_t\t*surf;\n\tmtexinfo_t\t*tex;\n\tmatrix3x4\t\ttbn;\n\tvec3_t\t\tmid;\n\tmnode_t *children[2];\n\tint firstsurface, numsurfaces;\n\n\t// didn't hit anything\n\tif( !node || node->contents < 0 )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false;\n\t}\n\n\tnode_children( children, node, model );\n\tfirstsurface = node_firstsurface( node, model );\n\tnumsurfaces = node_numsurfaces( node, model );\n\n\t// calculate mid point\n\tfront = PlaneDiff( start, node->plane );\n\tback = PlaneDiff( end, node->plane );\n\n\tside = front < 0;\n\tif(( back < 0 ) == side )\n\t\treturn R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end );\n\n\tfrac = front / ( front - back );\n\n\tVectorLerp( start, frac, end, mid );\n\tmidf = p1f + ( p2f - p1f ) * frac;\n\n\t// co down front side\n\tif( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid ))\n\t\treturn true; // hit something\n\n\tif(( back < 0 ) == side )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false; // didn't hit anything\n\t}\n\n\t// check for impact on this node\n\tsurf = model->surfaces + firstsurface;\n\tVectorCopy( mid, g_trace_lightspot );\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\tint\tsmax, tmax;\n\n\t\ttex = surf->texinfo;\n\t\tinfo = surf->info;\n\n\t\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\t\tcontinue;\t// no lightmaps\n\n\t\ts = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3];\n\t\tt = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3];\n\n\t\tif( s < info->lightmapmins[0] || t < info->lightmapmins[1] )\n\t\t\tcontinue;\n\n\t\tds = s - info->lightmapmins[0];\n\t\tdt = t - info->lightmapmins[1];\n\n\t\tif ( ds > info->lightextents[0] || dt > info->lightextents[1] )\n\t\t\tcontinue;\n\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\n\t\tif( !surf->samples )\n\t\t\treturn true;\n\n\t\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\t\tsmax = (info->lightextents[0] / sample_size) + 1;\n\t\ttmax = (info->lightextents[1] / sample_size) + 1;\n\t\tds /= sample_size;\n\t\tdt /= sample_size;\n\n\t\tlm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );\n\t\tg_trace_fraction = midf;\n\t\tsize = smax * tmax;\n\t\tdm = NULL;\n\n\t\tif( surf->info->deluxemap )\n\t\t{\n\t\t\tvec3_t\tfaceNormal;\n\n\t\t\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\t\t\tVectorNegate( surf->plane->normal, faceNormal );\n\t\t\telse VectorCopy( surf->plane->normal, faceNormal );\n\n\t\t\t// compute face TBN\n#if 1\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], surf->info->lmvecs[0][1], surf->info->lmvecs[0][2], 0.0f );\n\t\t\tVector4Set( tbn[1], -surf->info->lmvecs[1][0], -surf->info->lmvecs[1][1], -surf->info->lmvecs[1][2], 0.0f );\n\t\t\tVector4Set( tbn[2], faceNormal[0], faceNormal[1], faceNormal[2], 0.0f );\n#else\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], -surf->info->lmvecs[1][0], faceNormal[0], 0.0f );\n\t\t\tVector4Set( tbn[1], surf->info->lmvecs[0][1], -surf->info->lmvecs[1][1], faceNormal[1], 0.0f );\n\t\t\tVector4Set( tbn[2], surf->info->lmvecs[0][2], -surf->info->lmvecs[1][2], faceNormal[2], 0.0f );\n#endif\n\t\t\tVectorNormalize( tbn[0] );\n\t\t\tVectorNormalize( tbn[1] );\n\t\t\tVectorNormalize( tbn[2] );\n\t\t\tdm = surf->info->deluxemap + Q_rint( dt ) * smax + Q_rint( ds );\n\t\t}\n\n\t\tfor( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )\n\t\t{\n\t\t\tuint\tscale = tr.lightstylevalue[surf->styles[map]];\n\n\t\t\tcv->r += lm->r * scale;\n\t\t\tcv->g += lm->g * scale;\n\t\t\tcv->b += lm->b * scale;\n\n\t\t\tlm += size; // skip to next lightmap\n\n\t\t\tif( dm != NULL )\n\t\t\t{\n\t\t\t\tvec3_t\tsrcNormal, lightNormal;\n\t\t\t\tfloat\tf = (1.0f / 128.0f);\n\n\t\t\t\tVectorSet( srcNormal, ((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f );\n\t\t\t\tMatrix3x4_VectorIRotate( tbn, srcNormal, lightNormal );\t\t// turn to world space\n\t\t\t\tVectorScale( lightNormal, (float)scale * -1.0f, lightNormal );\t// turn direction from light\n\t\t\t\tVectorAdd( g_trace_lightvec, lightNormal, g_trace_lightvec );\n\t\t\t\tdm += size; // skip to next deluxmap\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// go down back side\n\treturn R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end );\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\nstatic colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tfloat\tlast_fraction;\n\tint\ti, maxEnts = 1;\n\tcolorVec\tlight, cv;\n\n\tif( lspot ) VectorClear( lspot );\n\tif( lvec ) VectorClear( lvec );\n\n\tif( WORLDMODEL && WORLDMODEL->lightdata )\n\t{\n\t\tlight.r = light.g = light.b = light.a = 0;\n\t\tlast_fraction = 1.0f;\n\n\t\t// get light from bmodels too\n\t\tif( r_lighting_extended.value )\n\t\t\tmaxEnts = MAX_PHYSENTS;\n\n\t\t// check all the bsp-models\n\t\tfor( i = 0; i < maxEnts; i++ )\n\t\t{\n\t\t\tphysent_t\t*pe = gEngfuncs.EV_GetPhysent( i );\n\t\t\tvec3_t\toffset, start_l, end_l;\n\t\t\tmnode_t\t*pnodes;\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tif( !pe )\n\t\t\t\tbreak;\n\n\t\t\tif( !pe->model || pe->model->type != mod_brush )\n\t\t\t\tcontinue; // skip non-bsp models\n\n\t\t\tpnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode];\n\t\t\tVectorSubtract( pe->model->hulls[0].clip_mins, vec3_origin, offset );\n\t\t\tVectorAdd( offset, pe->origin, offset );\n\t\t\tVectorSubtract( start, offset, start_l );\n\t\t\tVectorSubtract( end, offset, end_l );\n\n\t\t\t// rotate start and end into the models frame of reference\n\t\t\tif( !VectorIsNull( pe->angles ))\n\t\t\t{\n\t\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t\t\t}\n\n\t\t\tVectorClear( g_trace_lightspot );\n\t\t\tVectorClear( g_trace_lightvec );\n\t\t\tg_trace_fraction = 1.0f;\n\n\t\t\tif( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l ))\n\t\t\t\tcontinue;\t// didn't hit anything\n\n\t\t\tif( g_trace_fraction < last_fraction )\n\t\t\t{\n\t\t\t\tif( lspot ) VectorCopy( g_trace_lightspot, lspot );\n\t\t\t\tif( lvec ) VectorNormalize2( g_trace_lightvec, lvec );\n\n\t\t\t\tlight.r = Q_min(( cv.r >> 8 ), 255 );\n\t\t\t\tlight.g = Q_min(( cv.g >> 8 ), 255 );\n\t\t\t\tlight.b = Q_min(( cv.b >> 8 ), 255 );\n\t\t\t\tlast_fraction = g_trace_fraction;\n\n\t\t\t\tif(( light.r + light.g + light.b ) != 0 )\n\t\t\t\t\tbreak; // we get light now\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tlight.r = light.g = light.b = 255;\n\t\tlight.a = 0;\n\t}\n\n\treturn light;\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\ncolorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tcolorVec\tlight = R_LightVecInternal( start, end, lspot, lvec );\n\n\tif( r_lighting_extended.value && lspot != NULL && lvec != NULL )\n\t{\n\t\t// trying to get light from ceiling (but ignore gradient analyze)\n\t\tif(( light.r + light.g + light.b ) == 0 )\n\t\t\treturn R_LightVecInternal( end, start, lspot, lvec );\n\t}\n\n\treturn light;\n}\n\n/*\n=================\nR_LightPoint\n\nlight from floor\n=================\n*/\ncolorVec R_LightPoint( const vec3_t p0 )\n{\n\tvec3_t\tp1;\n\n\tVectorSet( p1, p0[0], p0[1], p0[2] - 2048.0f );\n\n\treturn R_LightVec( p0, p1, NULL, NULL );\n}\n"
  },
  {
    "path": "ref/gl/gl_rmain.c",
    "content": "/*\ngl_rmain.c - renderer main loop\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"library.h\"\n#include \"beamdef.h\"\n#include \"particledef.h\"\n#include \"entity_types.h\"\n\n\n#define IsLiquidContents( cnt )\t( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA )\n\nfloat\t\tgldepthmin, gldepthmax;\nref_instance_t\tRI;\n\nstatic int R_RankForRenderMode( int rendermode )\n{\n\tswitch( rendermode )\n\t{\n\tcase kRenderTransTexture:\n\t\treturn 1;\t// draw second\n\tcase kRenderTransAdd:\n\t\treturn 2;\t// draw third\n\tcase kRenderGlow:\n\t\treturn 3;\t// must be last!\n\t}\n\treturn 0;\n}\n\nvoid R_AllowFog( qboolean allowed )\n{\n\tif( allowed )\n\t{\n\t\tif( glState.isFogEnabled && gl_fog.value )\n\t\t\tpglEnable( GL_FOG );\n\t}\n\telse\n\t{\n\t\tif( glState.isFogEnabled )\n\t\t\tpglDisable( GL_FOG );\n\t}\n}\n\n/*\n===============\nR_OpaqueEntity\n\nOpaque entity can be brush or studio model but sprite\n===============\n*/\nqboolean R_OpaqueEntity( cl_entity_t *ent )\n{\n\tif( R_GetEntityRenderMode( ent ) == kRenderNormal )\n\t{\n\t\tswitch( ent->curstate.renderfx )\n\t\t{\n\t\tcase kRenderFxNone:\n\t\tcase kRenderFxDeadPlayer:\n\t\tcase kRenderFxLightMultiplier:\n\t\tcase kRenderFxExplode:\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/*\n===============\nR_TransEntityCompare\n\nSorting translucent entities by rendermode then by distance\n===============\n*/\nstatic int R_TransEntityCompare( const void *a, const void *b )\n{\n\tcl_entity_t\t*ent1, *ent2;\n\tvec3_t\t\tvecLen, org;\n\tfloat\t\tdist1, dist2;\n\tint\t\trendermode1;\n\tint\t\trendermode2;\n\n\tent1 = *(cl_entity_t **)a;\n\tent2 = *(cl_entity_t **)b;\n\trendermode1 = R_GetEntityRenderMode( ent1 );\n\trendermode2 = R_GetEntityRenderMode( ent2 );\n\n\t// sort by distance\n\tif( ent1->model->type != mod_brush || rendermode1 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent1->model->mins, ent1->model->maxs, org );\n\t\tVectorAdd( ent1->origin, org, org );\n\t\tVectorSubtract( RI.vieworg, org, vecLen );\n\t\tdist1 = DotProduct( vecLen, vecLen );\n\t}\n\telse dist1 = 1000000000;\n\n\tif( ent2->model->type != mod_brush || rendermode2 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent2->model->mins, ent2->model->maxs, org );\n\t\tVectorAdd( ent2->origin, org, org );\n\t\tVectorSubtract( RI.vieworg, org, vecLen );\n\t\tdist2 = DotProduct( vecLen, vecLen );\n\t}\n\telse dist2 = 1000000000;\n\n\tif( dist1 > dist2 )\n\t\treturn -1;\n\tif( dist1 < dist2 )\n\t\treturn 1;\n\n\t// then sort by rendermode\n\tif( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 ))\n\t\treturn 1;\n\tif( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 ))\n\t\treturn -1;\n\n\treturn 0;\n}\n\n/*\n===============\nR_WorldToScreen\n\nConvert a given point from world into screen space\nReturns true if we behind to screen\n===============\n*/\nint R_WorldToScreen( const vec3_t point, vec3_t screen )\n{\n\tmatrix4x4\tworldToScreen;\n\tqboolean\tbehind;\n\tfloat\tw;\n\n\tif( !point || !screen )\n\t\treturn true;\n\n\tMatrix4x4_Copy( worldToScreen, RI.worldviewProjectionMatrix );\n\tscreen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3];\n\tscreen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3];\n\tw = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3];\n\tscreen[2] = 0.0f; // just so we have something valid here\n\n\tif( w < 0.001f )\n\t{\n\t\tbehind = true;\n\t}\n\telse\n\t{\n\t\tfloat invw = 1.0f / w;\n\t\tscreen[0] *= invw;\n\t\tscreen[1] *= invw;\n\t\tbehind = false;\n\t}\n\n\treturn behind;\n}\n\n/*\n===============\nR_ScreenToWorld\n\nConvert a given point from screen into world space\n===============\n*/\nvoid R_ScreenToWorld( const vec3_t screen, vec3_t point )\n{\n\tmatrix4x4\tscreenToWorld;\n\tfloat\tw;\n\n\tif( !point || !screen )\n\t\treturn;\n\n\tMatrix4x4_Invert_Full( screenToWorld, RI.worldviewProjectionMatrix );\n\n\tpoint[0] = screen[0] * screenToWorld[0][0] + screen[1] * screenToWorld[0][1] + screen[2] * screenToWorld[0][2] + screenToWorld[0][3];\n\tpoint[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3];\n\tpoint[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3];\n\tw = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3];\n\tif( w != 0.0f ) VectorScale( point, ( 1.0f / w ), point );\n}\n\n/*\n===============\nR_PushScene\n===============\n*/\nvoid R_PushScene( void )\n{\n\tif( ++tr.draw_stack_pos >= MAX_DRAW_STACK )\n\t\tgEngfuncs.Host_Error( \"draw stack overflow\\n\" );\n\n\ttr.draw_list = &tr.draw_stack[tr.draw_stack_pos];\n}\n\n/*\n===============\nR_PopScene\n===============\n*/\nvoid R_PopScene( void )\n{\n\tif( --tr.draw_stack_pos < 0 )\n\t\tgEngfuncs.Host_Error( \"draw stack underflow\\n\" );\n\ttr.draw_list = &tr.draw_stack[tr.draw_stack_pos];\n}\n\n/*\n===============\nR_ClearScene\n===============\n*/\nvoid R_ClearScene( void )\n{\n\ttr.draw_list->num_solid_entities = 0;\n\ttr.draw_list->num_trans_entities = 0;\n\ttr.draw_list->num_beam_entities = 0;\n\n\t// clear the scene befor start new frame\n\tif( gEngfuncs.drawFuncs->R_ClearScene != NULL )\n\t\tgEngfuncs.drawFuncs->R_ClearScene();\n\n}\n\n/*\n===============\nR_AddEntity\n===============\n*/\nqboolean R_AddEntity( struct cl_entity_s *clent, int type )\n{\n\tif( !r_drawentities->value )\n\t\treturn false; // not allow to drawing\n\n\tif( !clent || !clent->model )\n\t\treturn false; // if set to invisible, skip\n\n\tif( FBitSet( clent->curstate.effects, EF_NODRAW ))\n\t\treturn false; // done\n\n\tif( !R_ModelOpaque( clent->curstate.rendermode ) && CL_FxBlend( clent ) <= 0 )\n\t\treturn true; // invisible\n\n\tswitch( type )\n\t{\n\tcase ET_FRAGMENTED:\n\t\tr_stats.c_client_ents++;\n\t\tbreak;\n\tcase ET_TEMPENTITY:\n\t\tr_stats.c_active_tents_count++;\n\t\tbreak;\n\tdefault: break;\n\t}\n\n\tif( R_OpaqueEntity( clent ))\n\t{\n\t\t// opaque\n\t\tif( tr.draw_list->num_solid_entities >= MAX_VISIBLE_PACKET )\n\t\t\treturn false;\n\n\t\ttr.draw_list->solid_entities[tr.draw_list->num_solid_entities] = clent;\n\t\ttr.draw_list->num_solid_entities++;\n\t}\n\telse\n\t{\n\t\t// translucent\n\t\tif( tr.draw_list->num_trans_entities >= MAX_VISIBLE_PACKET )\n\t\t\treturn false;\n\n\t\ttr.draw_list->trans_entities[tr.draw_list->num_trans_entities] = clent;\n\t\ttr.draw_list->num_trans_entities++;\n\t}\n\n\treturn true;\n}\n\n/*\n=============\nR_Clear\n=============\n*/\nstatic void R_Clear( int bitMask )\n{\n\tint\tbits;\n\n\tif( ENGINE_GET_PARM( PARM_DEV_OVERVIEW ))\n\t\tpglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules)\n\telse pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f );\n\n\tbits = GL_DEPTH_BUFFER_BIT;\n\n\tif( glState.stencilEnabled )\n\t\tbits |= GL_STENCIL_BUFFER_BIT;\n\n\tbits &= bitMask;\n\n\tpglClear( bits );\n\n\t// change ordering for overview\n\tif( RI.drawOrtho )\n\t{\n\t\tgldepthmin = 1.0f;\n\t\tgldepthmax = 0.0f;\n\t}\n\telse\n\t{\n\t\tgldepthmin = 0.0f;\n\t\tgldepthmax = 1.0f;\n\t}\n\n\tpglDepthFunc( GL_LEQUAL );\n\tpglDepthRange( gldepthmin, gldepthmax );\n}\n\n//=============================================================================\n/*\n===============\nR_GetFarClip\n===============\n*/\nstatic float R_GetFarClip( void )\n{\n\tif( WORLDMODEL && RI.drawWorld )\n\t\treturn tr.movevars->zmax * 1.73f;\n\treturn 2048.0f;\n}\n\n/*\n===============\nR_SetupFrustum\n===============\n*/\nvoid R_SetupFrustum( void )\n{\n\tconst ref_overview_t\t*ov = gEngfuncs.GetOverviewParms();\n\n\tif( RP_NORMALPASS() && ( ENGINE_GET_PARM( PARM_WATER_LEVEL ) >= 3 ) && ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t{\n\t\tRI.fov_x = atan( tan( DEG2RAD( RI.fov_x ) / 2 ) * ( 0.97f + sin( gp_cl->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);\n\t\tRI.fov_y = atan( tan( DEG2RAD( RI.fov_y ) / 2 ) * ( 1.03f - sin( gp_cl->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);\n\t}\n\n\t// build the transformation matrix for the given view angles\n\tAngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup );\n\n\tif( !r_lockfrustum.value )\n\t{\n\t\tVectorCopy( RI.vieworg, RI.cullorigin );\n\t\tVectorCopy( RI.vforward, RI.cull_vforward );\n\t\tVectorCopy( RI.vright, RI.cull_vright );\n\t\tVectorCopy( RI.vup, RI.cull_vup );\n\t}\n\n\tif( RI.drawOrtho )\n\t\tGL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );\n\telse GL_FrustumInitProj( &RI.frustum, 0.0f, R_GetFarClip(), RI.fov_x, RI.fov_y ); // NOTE: we ignore nearplane here (mirrors only)\n}\n\n/*\n=============\nR_SetupProjectionMatrix\n=============\n*/\nstatic void R_SetupProjectionMatrix( matrix4x4 m )\n{\n\tGLfloat\txMin, xMax, yMin, yMax, zNear, zFar;\n\n\tif( RI.drawOrtho )\n\t{\n\t\tconst ref_overview_t *ov = gEngfuncs.GetOverviewParms();\n\t\tMatrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );\n\t\treturn;\n\t}\n\n\tRI.farClip = R_GetFarClip();\n\n\tzNear = 4.0f;\n\tzFar = Q_max( 256.0f, RI.farClip );\n\n\tyMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f );\n\tyMin = -yMax;\n\n\txMax = zNear * tan( RI.fov_x * M_PI_F / 360.0f );\n\txMin = -xMax;\n\n\tMatrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar );\n}\n\n/*\n=============\nR_SetupModelviewMatrix\n=============\n*/\nstatic void R_SetupModelviewMatrix( matrix4x4 m )\n{\n\tMatrix4x4_CreateModelview( m );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[2], 1, 0, 0 );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[0], 0, 1, 0 );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[1], 0, 0, 1 );\n\tMatrix4x4_ConcatTranslate( m, -RI.vieworg[0], -RI.vieworg[1], -RI.vieworg[2] );\n}\n\n/*\n=============\nR_LoadIdentity\n=============\n*/\nvoid R_LoadIdentity( void )\n{\n\tif( tr.modelviewIdentity ) return;\n\n\tMatrix4x4_LoadIdentity( RI.objectMatrix );\n\tMatrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix );\n\n\tpglMatrixMode( GL_MODELVIEW );\n\tGL_LoadMatrix( RI.modelviewMatrix );\n\ttr.modelviewIdentity = true;\n}\n\n/*\n=============\nR_RotateForEntity\n=============\n*/\nvoid R_RotateForEntity( cl_entity_t *e )\n{\n\tfloat\tscale = 1.0f;\n\n\tif( e == CL_GetEntityByIndex( 0 ))\n\t{\n\t\tR_LoadIdentity();\n\t\treturn;\n\t}\n\n\tif( e->model->type != mod_brush && e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\tMatrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, scale );\n\tMatrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix );\n\n\tpglMatrixMode( GL_MODELVIEW );\n\tGL_LoadMatrix( RI.modelviewMatrix );\n\ttr.modelviewIdentity = false;\n}\n\n/*\n=============\nR_TranslateForEntity\n=============\n*/\nvoid R_TranslateForEntity( cl_entity_t *e )\n{\n\tfloat\tscale = 1.0f;\n\n\tif( e == CL_GetEntityByIndex( 0 ))\n\t{\n\t\tR_LoadIdentity();\n\t\treturn;\n\t}\n\n\tif( e->model->type != mod_brush && e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\tMatrix4x4_CreateFromEntity( RI.objectMatrix, vec3_origin, e->origin, scale );\n\tMatrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix );\n\n\tpglMatrixMode( GL_MODELVIEW );\n\tGL_LoadMatrix( RI.modelviewMatrix );\n\ttr.modelviewIdentity = false;\n}\n\n/*\n===============\nR_FindViewLeaf\n===============\n*/\nvoid R_FindViewLeaf( void )\n{\n\tRI.oldviewleaf = RI.viewleaf;\n\tRI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.pvsorigin, WORLDMODEL->nodes );\n}\n\n/*\n===============\nR_SetupFrame\n===============\n*/\nstatic void R_SetupFrame( void )\n{\n\t// setup viewplane dist\n\tRI.viewplanedist = DotProduct( RI.vieworg, RI.vforward );\n\n\t// NOTE: this request is the fps-killer on some NVidia drivers\n\tglState.isFogEnabled = pglIsEnabled( GL_FOG );\n\n\tif( !gl_nosort.value )\n\t{\n\t\t// sort translucents entities by rendermode and distance\n\t\tqsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare );\n\t}\n\n\t// current viewleaf\n\tif( RI.drawWorld )\n\t{\n\t\tRI.isSkyVisible = false; // unknown at this moment\n\t\tR_FindViewLeaf();\n\t}\n}\n\n/*\n=============\nR_SetupGL\n=============\n*/\nvoid R_SetupGL( qboolean set_gl_state )\n{\n\tR_SetupModelviewMatrix( RI.worldviewMatrix );\n\tR_SetupProjectionMatrix( RI.projectionMatrix );\n\n\tMatrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix );\n\n\tif( !set_gl_state ) return;\n\n\tif( RP_NORMALPASS( ))\n\t{\n\t\tint\tx, x2, y, y2;\n\n\t\t// set up viewport (main, playersetup)\n\t\tx = floor( RI.viewport[0] * gpGlobals->width / gpGlobals->width );\n\t\tx2 = ceil(( RI.viewport[0] + RI.viewport[2] ) * gpGlobals->width / gpGlobals->width );\n\t\ty = floor( gpGlobals->height - RI.viewport[1] * gpGlobals->height / gpGlobals->height );\n\t\ty2 = ceil( gpGlobals->height - ( RI.viewport[1] + RI.viewport[3] ) * gpGlobals->height / gpGlobals->height );\n\n\t\tpglViewport( x, y2, x2 - x, y - y2 );\n\t}\n\telse\n\t{\n\t\t// envpass, mirrorpass\n\t\tpglViewport( RI.viewport[0], RI.viewport[1], RI.viewport[2], RI.viewport[3] );\n\t}\n\n\tpglMatrixMode( GL_PROJECTION );\n\tGL_LoadMatrix( RI.projectionMatrix );\n\n\tpglMatrixMode( GL_MODELVIEW );\n\tGL_LoadMatrix( RI.worldviewMatrix );\n\n\tif( FBitSet( RI.params, RP_CLIPPLANE ))\n\t{\n\t\tGLdouble\tclip[4];\n\t\tmplane_t\t*p = &RI.clipPlane;\n\n\t\tclip[0] = p->normal[0];\n\t\tclip[1] = p->normal[1];\n\t\tclip[2] = p->normal[2];\n\t\tclip[3] = -p->dist;\n\n\t\tpglClipPlane( GL_CLIP_PLANE0, clip );\n\t\tpglEnable( GL_CLIP_PLANE0 );\n\t}\n\n\tGL_Cull( GL_FRONT );\n\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n}\n\n/*\n=============\nR_EndGL\n=============\n*/\nstatic void R_EndGL( void )\n{\n\tif( RI.params & RP_CLIPPLANE )\n\t\tpglDisable( GL_CLIP_PLANE0 );\n}\n\n/*\n=============\nR_RecursiveFindWaterTexture\n\nusing to find source waterleaf with\nwatertexture to grab fog values from it\n=============\n*/\nstatic gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down )\n{\n\tgl_texture_t *tex = NULL;\n\tmnode_t *children[2];\n\n\t// assure the initial node is not null\n\t// we could check it here, but we would rather check it\n\t// outside the call to get rid of one additional recursion level\n\tAssert( node != NULL );\n\n\t// ignore solid nodes\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn NULL;\n\n\tif( node->contents < 0 )\n\t{\n\t\tmleaf_t\t\t*pleaf;\n\t\tmsurface_t\t**mark;\n\t\tint\t\ti, c;\n\n\t\t// ignore non-liquid leaves\n\t\tif( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME )\n\t\t\t return NULL;\n\n\t\t// find texture\n\t\tpleaf = (mleaf_t *)node;\n\t\tmark = pleaf->firstmarksurface;\n\t\tc = pleaf->nummarksurfaces;\n\n\t\tfor( i = 0; i < c; i++, mark++ )\n\t\t{\n\t\t\tif( (*mark)->flags & SURF_DRAWTURB && (*mark)->texinfo && (*mark)->texinfo->texture )\n\t\t\t\treturn R_GetTexture( (*mark)->texinfo->texture->gl_texturenum );\n\t\t}\n\n\t\t// texture not found\n\t\treturn NULL;\n\t}\n\n\t// this is a regular node\n\t// traverse children\n\tnode_children( children, node, WORLDMODEL );\n\n\tif( children[0] && ( children[0] != ignore ))\n\t{\n\t\ttex = R_RecursiveFindWaterTexture( children[0], node, true );\n\t\tif( tex ) return tex;\n\t}\n\n\tif( children[1] && ( children[1] != ignore ))\n\t{\n\t\ttex = R_RecursiveFindWaterTexture( children[1], node, true );\n\t\tif( tex )\treturn tex;\n\t}\n\n\t// for down recursion, return immediately\n\tif( down ) return NULL;\n\n\t// texture not found, step up if any\n\tif( node->parent )\n\t\treturn R_RecursiveFindWaterTexture( node->parent, node, false );\n\n\t// top-level node, bail out\n\treturn NULL;\n}\n\n/*\n=============\nR_CheckFog\n\ncheck for underwater fog\nUsing backward recursion to find waterline leaf\nfrom underwater leaf (idea: XaeroX)\n=============\n*/\nstatic void R_CheckFog( void )\n{\n\tcl_entity_t\t*ent;\n\tgl_texture_t\t*tex;\n\tint\t\ti, cnt, count;\n\n\t// quake global fog\n\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t{\n\t\tif( !tr.movevars->fog_settings )\n\t\t{\n\t\t\tif( pglIsEnabled( GL_FOG ))\n\t\t\t\tpglDisable( GL_FOG );\n\t\t\tRI.fogEnabled = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// quake-style global fog\n\t\tRI.fogColor[0] = ((tr.movevars->fog_settings & 0xFF000000) >> 24) / 255.0f;\n\t\tRI.fogColor[1] = ((tr.movevars->fog_settings & 0xFF0000) >> 16) / 255.0f;\n\t\tRI.fogColor[2] = ((tr.movevars->fog_settings & 0xFF00) >> 8) / 255.0f;\n\t\tRI.fogDensity = ((tr.movevars->fog_settings & 0xFF) / 255.0f) * 0.01f;\n\t\tRI.fogStart = RI.fogEnd = 0.0f;\n\t\tRI.fogColor[3] = 1.0f;\n\t\tRI.fogCustom = false;\n\t\tRI.fogEnabled = true;\n\t\tRI.fogSkybox = true;\n\t\treturn;\n\t}\n\n\tRI.fogEnabled = false;\n\n\tif( RI.onlyClientDraw || ENGINE_GET_PARM( PARM_WATER_LEVEL ) < 3 || !RI.drawWorld || !RI.viewleaf )\n\t{\n\t\tif( RI.cached_waterlevel == 3 )\n\t\t{\n\t\t\t// in some cases waterlevel jumps from 3 to 1. Catch it\n\t\t\tRI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL );\n\t\t\tRI.cached_contents = CONTENTS_EMPTY;\n\t\t\tif( !RI.fogCustom )\n\t\t\t{\n\t\t\t\tglState.isFogEnabled = false;\n\t\t\t\tpglDisable( GL_FOG );\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tent = gEngfuncs.CL_GetWaterEntity( RI.vieworg );\n\tif( ent && ent->model && ent->model->type == mod_brush && ent->curstate.skin < 0 )\n\t\tcnt = ent->curstate.skin;\n\telse cnt = RI.viewleaf->contents;\n\n\tRI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL );\n\n\tif( !IsLiquidContents( RI.cached_contents ) && IsLiquidContents( cnt ))\n\t{\n\t\ttex = NULL;\n\n\t\t// check for water texture\n\t\tif( ent && ent->model && ent->model->type == mod_brush )\n\t\t{\n\t\t\tmsurface_t\t*surf;\n\n\t\t\tcount = ent->model->nummodelsurfaces;\n\n\t\t\tfor( i = 0, surf = &ent->model->surfaces[ent->model->firstmodelsurface]; i < count; i++, surf++ )\n\t\t\t{\n\t\t\t\tif( surf->flags & SURF_DRAWTURB && surf->texinfo && surf->texinfo->texture )\n\t\t\t\t{\n\t\t\t\t\ttex = R_GetTexture( surf->texinfo->texture->gl_texturenum );\n\t\t\t\t\tRI.cached_contents = ent->curstate.skin;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttex = R_RecursiveFindWaterTexture( RI.viewleaf->parent, NULL, false );\n\t\t\tif( tex ) RI.cached_contents = RI.viewleaf->contents;\n\t\t}\n\n\t\tif( !tex ) return;\t// no valid fogs\n\n\t\t// copy fog params\n\t\tRI.fogColor[0] = tex->fogParams[0] / 255.0f;\n\t\tRI.fogColor[1] = tex->fogParams[1] / 255.0f;\n\t\tRI.fogColor[2] = tex->fogParams[2] / 255.0f;\n\t\tRI.fogDensity = tex->fogParams[3] * 0.000025f;\n\t\tRI.fogStart = RI.fogEnd = 0.0f;\n\t\tRI.fogColor[3] = 1.0f;\n\t\tRI.fogCustom = false;\n\t\tRI.fogEnabled = true;\n\t\tRI.fogSkybox = true;\n\t}\n\telse\n\t{\n\t\tRI.fogCustom = false;\n\t\tRI.fogEnabled = true;\n\t\tRI.fogSkybox = true;\n\t}\n}\n\n/*\n=============\nR_CheckGLFog\n\nspecial condition for Spirit 1.9\nthat used direct calls of glFog-functions\n=============\n*/\nstatic void R_CheckGLFog( void )\n{\n#ifdef HACKS_RELATED_HLMODS\n\tif(( !RI.fogEnabled && !RI.fogCustom ) && pglIsEnabled( GL_FOG ) && VectorIsNull( RI.fogColor ))\n\t{\n\t\t// fill the fog color from GL-state machine\n\t\tpglGetFloatv( GL_FOG_COLOR, RI.fogColor );\n\t\tRI.fogSkybox = true;\n\t}\n#endif\n}\n\n/*\n=============\nR_DrawFog\n\n=============\n*/\nvoid R_DrawFog( void )\n{\n\tif( !RI.fogEnabled || !gl_fog.value )\n\t\treturn;\n\n\tpglEnable( GL_FOG );\n\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\tpglFogi( GL_FOG_MODE, GL_EXP2 );\n\telse pglFogi( GL_FOG_MODE, GL_EXP );\n\tpglFogf( GL_FOG_DENSITY, RI.fogDensity );\n\tpglFogfv( GL_FOG_COLOR, RI.fogColor );\n\tpglHint( GL_FOG_HINT, GL_NICEST );\n}\n\n/*\n=============\nR_DrawEntitiesOnList\n=============\n*/\nstatic void R_DrawEntitiesOnList( void )\n{\n\tint\ti;\n\n\ttr.blend = 1.0f;\n\tGL_CheckForErrors();\n\n\t// first draw solid entities\n\tfor( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->solid_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_brush:\n\t\t\tR_DrawBrushModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_alias:\n\t\t\tR_DrawAliasModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_studio:\n\t\t\tR_DrawStudioModel( RI.currententity );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tGL_CheckForErrors();\n\n\t// quake-specific feature\n\tR_DrawAlphaTextureChains();\n\n\tGL_CheckForErrors();\n\n\t// draw sprites seperately, because of alpha blending\n\tfor( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->solid_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_sprite:\n\t\t\tR_DrawSpriteModel( RI.currententity );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tGL_CheckForErrors();\n\n\tif( !RI.onlyClientDraw )\n\t{\n\t\tgEngfuncs.CL_DrawEFX( tr.frametime, false );\n\t}\n\n\tGL_CheckForErrors();\n\n\tif( RI.drawWorld )\n\t\tgEngfuncs.pfnDrawNormalTriangles();\n\n\tGL_CheckForErrors();\n\n\t// then draw translucent entities\n\tfor( i = 0; i < tr.draw_list->num_trans_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->trans_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\n\t\t// handle studiomodels with custom rendermodes on texture\n\t\tif( RI.currententity->curstate.rendermode != kRenderNormal )\n\t\t\ttr.blend = CL_FxBlend( RI.currententity ) / 255.0f;\n\t\telse tr.blend = 1.0f; // draw as solid but sorted by distance\n\n\t\tif( tr.blend <= 0.0f ) continue;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_brush:\n\t\t\tR_DrawBrushModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_alias:\n\t\t\tR_DrawAliasModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_studio:\n\t\t\tR_DrawStudioModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_sprite:\n\t\t\tR_DrawSpriteModel( RI.currententity );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tGL_CheckForErrors();\n\n\tif( RI.drawWorld )\n\t{\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tgEngfuncs.pfnDrawTransparentTriangles ();\n\t}\n\n\tGL_CheckForErrors();\n\n\tif( !RI.onlyClientDraw )\n\t{\n\t\tR_AllowFog( false );\n\t\tgEngfuncs.CL_DrawEFX( tr.frametime, true );\n\t\tR_AllowFog( true );\n\t}\n\n\tGL_CheckForErrors();\n\n\tpglDisable( GL_BLEND );\t// Trinity Render issues\n\n\tif( !RI.onlyClientDraw )\n\t\tR_DrawViewModel();\n\tgEngfuncs.CL_ExtraUpdate();\n\n\tGL_CheckForErrors();\n}\n\n/*\n================\nR_RenderScene\n\nR_SetupRefParams must be called right before\n================\n*/\nvoid R_RenderScene( void )\n{\n\tif( !WORLDMODEL && RI.drawWorld )\n\t\tgEngfuncs.Host_Error( \"%s: NULL worldmodel\\n\", __func__ );\n\n\t// frametime is valid only for normal pass\n\tif( RP_NORMALPASS( ))\n\t\ttr.frametime = gp_cl->time -   gp_cl->oldtime;\n\telse tr.frametime = 0.0;\n\n\t// begin a new frame\n\ttr.framecount++;\n\n\tR_PushDlights();\n\n\tR_SetupFrustum();\n\tR_SetupFrame();\n\tR_SetupGL( true );\n\tR_Clear( ~0 );\n\n\tR_MarkLeaves();\n\tR_DrawFog ();\n\tif( RI.drawWorld )\n\t\tR_AnimateRipples();\n\n\tR_CheckGLFog();\n\tR_DrawWorld();\n\tR_CheckFog();\n\n\tgEngfuncs.CL_ExtraUpdate ();\t// don't let sound get messed up if going slow\n\n\tR_DrawEntitiesOnList();\n\n\tR_DrawWaterSurfaces();\n\n\tR_EndGL();\n}\n\nvoid R_GammaChanged( qboolean do_reset_gamma )\n{\n\tif( do_reset_gamma )\n\t{\n\t\t// paranoia cubemap rendering\n\t\tif( gEngfuncs.drawFuncs->GL_BuildLightmaps )\n\t\t\tgEngfuncs.drawFuncs->GL_BuildLightmaps( );\n\t}\n\telse\n\t{\n\t\tglConfig.softwareGammaUpdate = true;\n\t\tGL_RebuildLightmaps();\n\t\tglConfig.softwareGammaUpdate = false;\n\t}\n}\n\nstatic void R_CheckCvars( void )\n{\n\tqboolean rebuild = false;\n\n\tif( FBitSet( gl_overbright.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( gl_overbright.flags, FCVAR_CHANGED );\n\t\trebuild = true;\n\t}\n\n\tif( FBitSet( r_vbo.flags, FCVAR_CHANGED ))\n\t{\n\t\tClearBits( r_vbo.flags, FCVAR_CHANGED );\n\n\t\tR_EnableVBO( r_vbo.value ? true : false );\n\t\tif( R_HasEnabledVBO( ))\n\t\t\tR_GenerateVBO();\n\n\t\tif( gl_overbright.value )\n\t\t\trebuild = true;\n\t}\n\n\tif( FBitSet( r_vbo_overbrightmode.flags, FCVAR_CHANGED ) && gl_overbright.value )\n\t{\n\t\tClearBits( r_vbo_overbrightmode.flags, FCVAR_CHANGED );\n\t\trebuild = true;\n\t}\n\n\tif( rebuild )\n\t\tR_GammaChanged( false );\n}\n\n/*\n===============\nR_BeginFrame\n===============\n*/\nvoid R_BeginFrame( qboolean clearScene )\n{\n\tglConfig.softwareGammaUpdate = false;\t// in case of possible fails\n\n\tif(( gl_clear->value || ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) &&\n\t\tclearScene && ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_cinematic )\n\t{\n\t\tpglClear( GL_COLOR_BUFFER_BIT );\n\t}\n\n\tR_CheckCvars();\n\n\tR_Set2DMode( true );\n\n\t// draw buffer stuff\n\tpglDrawBuffer( GL_BACK );\n\n\t// update texture parameters\n\tif( FBitSet( gl_texture_nearest.flags|gl_lightmap_nearest.flags|gl_texture_anisotropy.flags|gl_texture_lodbias.flags, FCVAR_CHANGED ))\n\t\tR_SetTextureParameters();\n\n\tgEngfuncs.CL_ExtraUpdate ();\n}\n\n/*\n===============\nR_SetupRefParams\n\nset initial params for renderer\n===============\n*/\nvoid R_SetupRefParams( const ref_viewpass_t *rvp )\n{\n\tRI.params = RP_NONE;\n\tRI.drawWorld = FBitSet( rvp->flags, RF_DRAW_WORLD );\n\tRI.onlyClientDraw = FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW );\n\tRI.farClip = 0;\n\n\tif( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP ))\n\t\tRI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW );\n\telse RI.drawOrtho = false;\n\n\t// setup viewport\n\tRI.viewport[0] = rvp->viewport[0];\n\tRI.viewport[1] = rvp->viewport[1];\n\tRI.viewport[2] = rvp->viewport[2];\n\tRI.viewport[3] = rvp->viewport[3];\n\n\t// calc FOV\n\tRI.fov_x = rvp->fov_x;\n\tRI.fov_y = rvp->fov_y;\n\n\tVectorCopy( rvp->vieworigin, RI.vieworg );\n\tVectorCopy( rvp->viewangles, RI.viewangles );\n\tVectorCopy( rvp->vieworigin, RI.pvsorigin );\n}\n\n/*\n===============\nR_RenderFrame\n===============\n*/\nvoid R_RenderFrame( const ref_viewpass_t *rvp )\n{\n\tif( r_norefresh->value )\n\t\treturn;\n\n\t// setup the initial render params\n\tR_SetupRefParams( rvp );\n\n\tif( gl_finish.value && RI.drawWorld )\n\t\tpglFinish();\n\n\t// completely override rendering\n\tif( gEngfuncs.drawFuncs->GL_RenderFrame != NULL )\n\t{\n\t\ttr.fCustomRendering = true;\n\n\t\tif( gEngfuncs.drawFuncs->GL_RenderFrame( rvp ))\n\t\t{\n\t\t\tR_GatherPlayerLight();\n\t\t\ttr.realframecount++;\n\t\t\ttr.fResetVis = true;\n\t\t\treturn;\n\t\t}\n\t}\n\n\ttr.fCustomRendering = false;\n\tif( !RI.onlyClientDraw )\n\t\tR_RunViewmodelEvents();\n\n\ttr.realframecount++; // right called after viewmodel events\n\tR_RenderScene();\n\n\treturn;\n}\n\n/*\n===============\nR_EndFrame\n===============\n*/\nvoid R_EndFrame( void )\n{\n#if XASH_PSVITA\n\tVGL_ShimEndFrame();\n#endif\n#if !defined( XASH_GL_STATIC )\n\tGL2_ShimEndFrame();\n#endif\n\t// flush any remaining 2D bits\n\tR_Set2DMode( false );\n\tgEngfuncs.GL_SwapBuffers();\n}\n\n/*\n===============\nR_DrawCubemapView\n===============\n*/\nvoid R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size )\n{\n\tref_viewpass_t rvp;\n\n\t// basic params\n\trvp.flags = rvp.viewentity = 0;\n\tSetBits( rvp.flags, RF_DRAW_WORLD );\n\tSetBits( rvp.flags, RF_DRAW_CUBEMAP );\n\n\trvp.viewport[0] = rvp.viewport[1] = 0;\n\trvp.viewport[2] = rvp.viewport[3] = size;\n\trvp.fov_x = rvp.fov_y = 90.0f; // this is a final fov value\n\n\t// setup origin & angles\n\tVectorCopy( origin, rvp.vieworigin );\n\tVectorCopy( angles, rvp.viewangles );\n\n\tR_RenderFrame( &rvp );\n\n\tRI.viewleaf = NULL;\t\t// force markleafs next frame\n}\n\n/*\n===============\nCL_FxBlend\n===============\n*/\nint CL_FxBlend( cl_entity_t *e )\n{\n\tint\tblend = 0;\n\tfloat\toffset, dist;\n\tvec3_t\ttmp;\n\n\toffset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx\n\n\tswitch( e->curstate.renderfx )\n\t{\n\tcase kRenderFxPulseSlowWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFastWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseSlow:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFast:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxFadeSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 0 )\n\t\t\t\te->curstate.renderamt -= 1;\n\t\t\telse e->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFadeFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 3 )\n\t\t\t\te->curstate.renderamt -= 4;\n\t\t\telse e->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 255 )\n\t\t\t\te->curstate.renderamt += 1;\n\t\t\telse e->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 252 )\n\t\t\t\te->curstate.renderamt += 4;\n\t\t\telse e->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeSlow:\n\t\tblend = 20 * sin( gp_cl->time * 4 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFast:\n\t\tblend = 20 * sin( gp_cl->time * 16 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFaster:\n\t\tblend = 20 * sin( gp_cl->time * 36 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerSlow:\n\t\tblend = 20 * (sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset ));\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerFast:\n\t\tblend = 20 * (sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset ));\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxHologram:\n\tcase kRenderFxDistort:\n\t\tVectorCopy( e->origin, tmp );\n\t\tVectorSubtract( tmp, RI.vieworg, tmp );\n\t\tdist = DotProduct( tmp, RI.vforward );\n\n\t\t// turn off distance fade\n\t\tif( e->curstate.renderfx == kRenderFxDistort )\n\t\t\tdist = 1;\n\n\t\tif( dist <= 0 )\n\t\t{\n\t\t\tblend = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\te->curstate.renderamt = 180;\n\t\t\tif( dist <= 100 ) blend = e->curstate.renderamt;\n\t\t\telse blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt );\n\t\t\tblend += gEngfuncs.COM_RandomLong( -32, 31 );\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\t}\n\n\tblend = bound( 0, blend, 255 );\n\n\treturn blend;\n}\n"
  },
  {
    "path": "ref/gl/gl_rmath.c",
    "content": "/*\ngl_rmath.c - renderer mathlib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n\n/*\n========================================================================\n\n\t       Matrix4x4 operations (private to renderer)\n\n========================================================================\n*/\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3];\n\tout[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0];\n\tout[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1];\n\tout[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2];\n\tout[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3];\n}\n\n/*\n================\nMatrix4x4_CreateProjection\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar )\n{\n\tout[0][0] = ( 2.0f * zNear ) / ( xMax - xMin );\n\tout[1][1] = ( 2.0f * zNear ) / ( yMax - yMin );\n\tout[2][2] = -( zFar + zNear ) / ( zFar - zNear );\n\tout[3][3] = out[0][1] = out[1][0] = out[3][0] = out[0][3] = out[3][1] = out[1][3] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][2] = ( xMax + xMin ) / ( xMax - xMin );\n\tout[1][2] = ( yMax + yMin ) / ( yMax - yMin );\n\tout[3][2] = -1.0f;\n\tout[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear );\n}\n\nvoid Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar )\n{\n\tout[0][0] = 2.0f / (xRight - xLeft);\n\tout[1][1] = 2.0f / (yTop - yBottom);\n\tout[2][2] = -2.0f / (zFar - zNear);\n\tout[3][3] = 1.0f;\n\tout[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][3] = -(xRight + xLeft) / (xRight - xLeft);\n\tout[1][3] = -(yTop + yBottom) / (yTop - yBottom);\n\tout[2][3] = -(zFar + zNear) / (zFar - zNear);\n}\n\n/*\n================\nMatrix4x4_CreateModelview\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateModelview( matrix4x4 out )\n{\n\tout[0][0] = out[1][1] = out[2][2] = 0.0f;\n\tout[3][0] = out[0][3] = 0.0f;\n\tout[3][1] = out[1][3] = 0.0f;\n\tout[3][2] = out[2][3] = 0.0f;\n\tout[3][3] = 1.0f;\n\tout[1][0] = out[0][2] = out[2][1] = 0.0f;\n\tout[2][0] = out[0][1] = -1.0f;\n\tout[1][2] = 1.0f;\n}\n\nvoid Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] )\n{\n\tout[ 0] = in[0][0];\n\tout[ 1] = in[1][0];\n\tout[ 2] = in[2][0];\n\tout[ 3] = in[3][0];\n\tout[ 4] = in[0][1];\n\tout[ 5] = in[1][1];\n\tout[ 6] = in[2][1];\n\tout[ 7] = in[3][1];\n\tout[ 8] = in[0][2];\n\tout[ 9] = in[1][2];\n\tout[10] = in[2][2];\n\tout[11] = in[3][2];\n\tout[12] = in[0][3];\n\tout[13] = in[1][3];\n\tout[14] = in[2][3];\n\tout[15] = in[3][3];\n}\n\nstatic void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][0] = 1.0f;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = x;\n\tout[1][0] = 0.0f;\n\tout[1][1] = 1.0f;\n\tout[1][2] = 0.0f;\n\tout[1][3] = y;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = 1.0f;\n\tout[2][3] = z;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nstatic void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tfloat\tlen, c, s;\n\n\tlen = x * x + y * y + z * z;\n\tif( len != 0.0f ) len = 1.0f / sqrt( len );\n\tx *= len;\n\ty *= len;\n\tz *= len;\n\n\tangle *= (-M_PI_F / 180.0f);\n\tSinCos( angle, &s, &c );\n\n\tout[0][0]=x * x + c * (1 - x * x);\n\tout[0][1]=x * y * (1 - c) + z * s;\n\tout[0][2]=z * x * (1 - c) - y * s;\n\tout[0][3]=0.0f;\n\tout[1][0]=x * y * (1 - c) - z * s;\n\tout[1][1]=y * y + c * (1 - y * y);\n\tout[1][2]=y * z * (1 - c) + x * s;\n\tout[1][3]=0.0f;\n\tout[2][0]=z * x * (1 - c) + y * s;\n\tout[2][1]=y * z * (1 - c) - x * s;\n\tout[2][2]=z * z + c * (1 - z * z);\n\tout[2][3]=0.0f;\n\tout[3][0]=0.0f;\n\tout[3][1]=0.0f;\n\tout[3][2]=0.0f;\n\tout[3][3]=1.0f;\n}\n\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateTranslate( temp, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateRotate( temp, angle, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n"
  },
  {
    "path": "ref/gl/gl_rmisc.c",
    "content": "/*\ngl_rmisc.c - renderer misceallaneous\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"shake.h\"\n#include \"screenfade.h\"\n#include \"cdll_int.h\"\n\nstatic void R_ParseDetailTextures( const char *filename )\n{\n\tbyte *afile;\n\tchar *pfile;\n\tstring\ttoken, texname;\n\tstring\tdetail_texname;\n\tstring\tdetail_path;\n\tfloat\txScale, yScale;\n\ttexture_t\t*tex;\n\tint\ti;\n\n\tafile = gEngfuncs.fsapi->LoadFile( filename, NULL, false );\n\tif( !afile ) return;\n\n\tpfile = (char *)afile;\n\n\t// format: 'texturename' 'detailtexture' 'xScale' 'yScale'\n\twhile(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )\n\t{\n\t\ttexname[0] = '\\0';\n\t\tdetail_texname[0] = '\\0';\n\n\t\t// read texname\n\t\tif( token[0] == '{' )\n\t\t{\n\t\t\t// NOTE: COM_ParseFile handled some symbols seperately\n\t\t\t// this code will be fix it\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\t\tQ_snprintf( texname, sizeof( texname ), \"{%s\", token );\n\t\t}\n\t\telse Q_strncpy( texname, token, sizeof( texname ));\n\n\t\t// read detailtexture name\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tQ_strncpy( detail_texname, token, sizeof( detail_texname ));\n\n\t\t// trying the scales or '{'\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\n\t\t// read second part of detailtexture name\n\t\tif( token[0] == '{' )\n\t\t{\n\t\t\tQ_strncat( detail_texname, token, sizeof( detail_texname ));\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token )); // read scales\n\t\t\tQ_strncat( detail_texname, token, sizeof( detail_texname ));\n\t\t\tpfile = COM_ParseFile( pfile, token, sizeof( token )); // parse scales\n\t\t}\n\n\t\tQ_snprintf( detail_path, sizeof( detail_path ), \"gfx/%s\", detail_texname );\n\n\t\t// read scales\n\t\txScale = Q_atof( token );\n\n\t\tpfile = COM_ParseFile( pfile, token, sizeof( token ));\n\t\tyScale = Q_atof( token );\n\n\t\tif( xScale <= 0.0f || yScale <= 0.0f )\n\t\t\tcontinue;\n\n\t\t// search for existing texture and uploading detail texture\n\t\tfor( i = 0; i < WORLDMODEL->numtextures; i++ )\n\t\t{\n\t\t\ttex = WORLDMODEL->textures[i];\n\n\t\t\tif( Q_stricmp( tex->name, texname ))\n\t\t\t\tcontinue;\n\n\t\t\ttex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR|TF_NOFLIP_TGA );\n\n\t\t\t// texture is loaded\n\t\t\tif( tex->dt_texturenum )\n\t\t\t{\n\t\t\t\tgl_texture_t\t*glt;\n\n\t\t\t\tglt = R_GetTexture( tex->gl_texturenum );\n\t\t\t\tglt->xscale = xScale;\n\t\t\t\tglt->yscale = yScale;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tMem_Free( afile );\n}\n\nvoid R_NewMap( void )\n{\n\ttexture_t\t*tx;\n\tint\ti;\n\n\tR_ClearDecals(); // clear all level decals\n\n\tR_StudioResetPlayerModels();\n\n\t// upload detailtextures\n\tif( r_detailtextures.value )\n\t{\n\t\tstring\tmapname, filepath;\n\n\t\tQ_strncpy( mapname, WORLDMODEL->name, sizeof( mapname ));\n\t\tCOM_StripExtension( mapname );\n\t\tQ_snprintf( filepath, sizeof( filepath ), \"%s_detail.txt\", mapname );\n\n\t\tR_ParseDetailTextures( filepath );\n\t}\n\n\t// clear out efrags in case the level hasn't been reloaded\n\tfor( i = 0; i < WORLDMODEL->numleafs; i++ )\n\t\tWORLDMODEL->leafs[i+1].efrags = NULL;\n\n\tglState.isFogEnabled = false;\n\ttr.skytexturenum = -1;\n\tpglDisable( GL_FOG );\n\n\t// clearing texture chains\n\tfor( i = 0; i < WORLDMODEL->numtextures; i++ )\n\t{\n\t\tif( !WORLDMODEL->textures[i] )\n\t\t\tcontinue;\n\n\t\ttx = WORLDMODEL->textures[i];\n\n\t\tif( !Q_strncmp( tx->name, \"sky\", 3 ) && tx->width == ( tx->height * 2 ))\n\t\t\ttr.skytexturenum = i;\n\n \t\ttx->texturechain = NULL;\n\t}\n\n\tGL_BuildLightmaps ();\n\n\tR_ClearVBO();\n\tif( R_HasEnabledVBO( ))\n\t\tR_GenerateVBO();\n\tR_ResetRipples();\n\n\tif( gEngfuncs.drawFuncs->R_NewMap != NULL )\n\t\tgEngfuncs.drawFuncs->R_NewMap();\n\n}\n"
  },
  {
    "path": "ref/gl/gl_rpart.c",
    "content": "/*\ncl_part.c - particles and tracers\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"r_efx.h\"\n#include \"event_flags.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n\nstatic float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };\nstatic color24 gTracerColors[] =\n{\n{ 255, 255, 255 },\t\t// White\n{ 255, 0, 0 },\t\t// Red\n{ 0, 255, 0 },\t\t// Green\n{ 0, 0, 255 },\t\t// Blue\n{ 0, 0, 0 },\t\t// Tracer default, filled in from cvars, etc.\n{ 255, 167, 17 },\t\t// Yellow-orange sparks\n{ 255, 130, 90 },\t\t// Yellowish streaks (garg)\n{ 55, 60, 144 },\t\t// Blue egon streak\n{ 255, 130, 90 },\t\t// More Yellowish streaks (garg)\n{ 255, 140, 90 },\t\t// More Yellowish streaks (garg)\n{ 200, 130, 90 },\t\t// More red streaks (garg)\n{ 255, 120, 70 },\t\t// Darker red streaks (garg)\n};\n\n/*\n================\nCL_DrawParticles\n\nupdate particle color, position, free expired and draw it\n================\n*/\nvoid CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize )\n{\n\tparticle_t\t*p;\n\tvec3_t\t\tright, up;\n\tcolor24\t\tcolor;\n\tint\t\talpha;\n\tfloat\t\tsize;\n\n\tif( !cl_active_particles )\n\t\treturn;\t// nothing to draw?\n\n\tpglEnable( GL_BLEND );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\n\tGL_Bind( XASH_TEXTURE0, tr.particleTexture );\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglDepthMask( GL_FALSE );\n\n\tpglBegin( GL_QUADS );\n\n\tfor( p = cl_active_particles; p; p = p->next )\n\t{\n\t\tif(( p->type != pt_blob ) || ( p->packedColor == 255 ))\n\t\t{\n\t\t\tsize = partsize; // get initial size of particle\n\n\t\t\t// scale up to keep particles from disappearing\n\t\t\tsize += (p->org[0] - RI.vieworg[0]) * RI.cull_vforward[0];\n\t\t\tsize += (p->org[1] - RI.vieworg[1]) * RI.cull_vforward[1];\n\t\t\tsize += (p->org[2] - RI.vieworg[2]) * RI.cull_vforward[2];\n\n\t\t\tif( size < 20.0f ) size = partsize;\n\t\t\telse size = partsize + size * 0.002f;\n\n\t\t\t// scale the axes by radius\n\t\t\tVectorScale( RI.cull_vright, size, right );\n\t\t\tVectorScale( RI.cull_vup, size, up );\n\n\t\t\tp->color = bound( 0, p->color, 255 );\n\t\t\tcolor = tr.palette[p->color];\n\n\t\t\talpha = 255 * (p->die - gp_cl->time) * 16.0f;\n\t\t\tif( alpha > 255 || p->type == pt_static )\n\t\t\t\talpha = 255;\n\n\t\t\tpglColor4ub( color.r, color.g, color.b, alpha );\n\n\t\t\tpglTexCoord2f( 0.0f, 1.0f );\n\t\t\tpglVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] );\n\t\t\tpglTexCoord2f( 0.0f, 0.0f );\n\t\t\tpglVertex3f( p->org[0] + right[0] + up[0], p->org[1] + right[1] + up[1], p->org[2] + right[2] + up[2] );\n\t\t\tpglTexCoord2f( 1.0f, 0.0f );\n\t\t\tpglVertex3f( p->org[0] + right[0] - up[0], p->org[1] + right[1] - up[1], p->org[2] + right[2] - up[2] );\n\t\t\tpglTexCoord2f( 1.0f, 1.0f );\n\t\t\tpglVertex3f( p->org[0] - right[0] - up[0], p->org[1] - right[1] - up[1], p->org[2] - right[2] - up[2] );\n\t\t\tr_stats.c_particle_count++;\n\t\t}\n\n\t\tgEngfuncs.CL_ThinkParticle( frametime, p );\n\t}\n\n\tpglEnd();\n\tpglDepthMask( GL_TRUE );\n}\n\n/*\n================\nCL_CullTracer\n\ncheck tracer bbox\n================\n*/\nstatic qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end )\n{\n\tvec3_t\tmins, maxs;\n\tint\ti;\n\n\t// compute the bounding box\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t{\n\t\t\tmaxs[i] += gTracerSize[p->type] * 2.0f;\n\t\t}\n\t}\n\n\t// check bbox\n\treturn R_CullBox( mins, maxs );\n}\n\n/*\n================\nCL_DrawTracers\n\nupdate tracer color, position, free expired and draw it\n================\n*/\nvoid CL_DrawTracers( double frametime, particle_t *cl_active_tracers )\n{\n\tfloat\t\tscale, atten, gravity;\n\tvec3_t\t\tscreenLast, screen;\n\tvec3_t\t\tstart, end, delta;\n\tparticle_t\t*p;\n\n\t// update tracer color if this is changed\n\tif( FBitSet( tracerred->flags|tracergreen->flags|tracerblue->flags|traceralpha->flags, FCVAR_CHANGED ))\n\t{\n\t\tcolor24 *customColors = &gTracerColors[4];\n\t\tcustomColors->r = (byte)(tracerred->value * traceralpha->value * 255);\n\t\tcustomColors->g = (byte)(tracergreen->value * traceralpha->value * 255);\n\t\tcustomColors->b = (byte)(tracerblue->value * traceralpha->value * 255);\n\t\tClearBits( tracerred->flags, FCVAR_CHANGED );\n\t\tClearBits( tracergreen->flags, FCVAR_CHANGED );\n\t\tClearBits( tracerblue->flags, FCVAR_CHANGED );\n\t\tClearBits( traceralpha->flags, FCVAR_CHANGED );\n\t}\n\n\tif( !cl_active_tracers )\n\t\treturn;\t// nothing to draw?\n\n\tif( !TriSpriteTexture( gEngfuncs.GetDefaultSprite( REF_DOT_SPRITE ), 0 ))\n\t\treturn;\n\n\tpglEnable( GL_BLEND );\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglDepthMask( GL_FALSE );\n\n\tgravity = frametime * tr.movevars->gravity;\n\tscale = 1.0 - (frametime * 0.9);\n\tif( scale < 0.0f ) scale = 0.0f;\n\n\tpglBegin( GL_QUADS );\n\n\tfor( p = cl_active_tracers; p; p = p->next )\n\t{\n\t\tatten = (p->die - gp_cl->time);\n\t\tif( atten > 0.1f ) atten = 0.1f;\n\n\t\tVectorScale( p->vel, ( p->ramp * atten ), delta );\n\t\tVectorAdd( p->org, delta, end );\n\t\tVectorCopy( p->org, start );\n\n\t\tif( !CL_CullTracer( p, start, end ))\n\t\t{\n\t\t\tvec3_t\tverts[4], tmp2;\n\t\t\tvec3_t\ttmp, normal;\n\t\t\tcolor24\tcolor;\n\n\t\t\t// Transform point into screen space\n\t\t\tTriWorldToScreen( start, screen );\n\t\t\tTriWorldToScreen( end, screenLast );\n\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\t// build point along noraml line (normal is -y, x)\n\t\t\tVectorScale( RI.cull_vup, tmp[0] * gTracerSize[p->type], normal );\n\t\t\tVectorScale( RI.cull_vright, -tmp[1] * gTracerSize[p->type], tmp2 );\n\t\t\tVectorSubtract( normal, tmp2, normal );\n\n\t\t\t// compute four vertexes\n\t\t\tVectorSubtract( start, normal, verts[0] );\n\t\t\tVectorAdd( start, normal, verts[1] );\n\t\t\tVectorAdd( verts[0], delta, verts[2] );\n\t\t\tVectorAdd( verts[1], delta, verts[3] );\n\n\t\t\tif( p->color < 0 || p->color >= sizeof( gTracerColors ) / sizeof( gTracerColors[0] ))\n\t\t\t{\n\t\t\t\tp->color = TRACER_COLORINDEX_DEFAULT;\n\t\t\t}\n\n\t\t\tcolor = gTracerColors[p->color];\n\t\t\tpglColor4ub( color.r, color.g, color.b, p->packedColor );\n\n\t\t\t\tpglTexCoord2f( 0.0f, 0.8f );\n\t\t\t\tpglVertex3fv( verts[2] );\n\t\t\t\tpglTexCoord2f( 1.0f, 0.8f );\n\t\t\t\tpglVertex3fv( verts[3] );\n\t\t\t\tpglTexCoord2f( 1.0f, 0.0f );\n\t\t\t\tpglVertex3fv( verts[1] );\n\t\t\t\tpglTexCoord2f( 0.0f, 0.0f );\n\t\t\t\tpglVertex3fv( verts[0] );\n\t\t}\n\n\t\t// evaluate position\n\t\tVectorMA( p->org, frametime, p->vel, p->org );\n\n\t\tif( p->type == pt_grav )\n\t\t{\n\t\t\tp->vel[0] *= scale;\n\t\t\tp->vel[1] *= scale;\n\t\t\tp->vel[2] -= gravity;\n\n\t\t\tp->packedColor = 255 * (p->die - gp_cl->time) * 2;\n\t\t\tif( p->packedColor > 255 ) p->packedColor = 255;\n\t\t}\n\t\telse if( p->type == pt_slowgrav )\n\t\t{\n\t\t\tp->vel[2] = gravity * 0.05f;\n\t\t}\n\t}\n\tpglEnd();\n\n\tpglDepthMask( GL_TRUE );\n}\n\n/*\n===============\nCL_DrawParticlesExternal\n\nallow to draw effects from custom renderer\n===============\n*/\nvoid CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime )\n{\n\tref_instance_t\toldRI = RI;\n\n\tR_SetupRefParams( rvp );\n\tR_SetupFrustum();\n\tR_SetupGL( false );\t// don't touch GL-states\n\ttr.frametime = frametime;\n\n\tgEngfuncs.CL_DrawEFX( frametime, trans_pass );\n\n\t// restore internal state\n\tRI = oldRI;\n}\n"
  },
  {
    "path": "ref/gl/gl_rsurf.c",
    "content": "/*\ngl_rsurf.c - surface-related refresh code\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"mod_local.h\"\n\ntypedef struct\n{\n\tint\t\tallocated[BLOCK_SIZE_MAX];\n\tint\t\tcurrent_lightmap_texture;\n\tmsurface_t\t*dynamic_surfaces;\n\tmsurface_t\t*lightmap_surfaces[MAX_LIGHTMAPS];\n\tbyte\t\tlightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4];\n} gllightmapstate_t;\n\nstatic vec2_t\t\tworld_orthocenter;\nstatic vec2_t\t\tworld_orthohalf;\nstatic uint\t\tr_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3];\nstatic mextrasurf_t\t\t*fullbright_surfaces[MAX_TEXTURES];\nstatic mextrasurf_t\t\t*detail_surfaces[MAX_TEXTURES];\nstatic int\t\trtable[MOD_FRAMES][MOD_FRAMES];\n\ntypedef struct\n{\n\tint first, last;\n} separate_pass_t;\n\nstatic separate_pass_t draw_wateralpha = { 0, -1 };\nstatic separate_pass_t draw_alpha_surfaces = { 0, -1 };\nstatic separate_pass_t draw_fullbrights = { 0, -1 };\nstatic separate_pass_t draw_details = { 0, -1 };\nstatic msurface_t\t\t*skychain = NULL;\nstatic gllightmapstate_t\tgl_lms;\n\nstatic void LM_UploadBlock( qboolean dynamic );\nstatic qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmaps );\nstatic void R_DrawVBO( qboolean drawlightmaps, qboolean drawtextures );\n\nstatic inline void R_AddToSeparatePass( separate_pass_t *sp, int num )\n{\n\tif( sp->first > num )\n\t\tsp->first = num;\n\n\tif( sp->last < num )\n\t\tsp->last = num;\n}\n\nstatic inline void R_ResetSeparatePass( separate_pass_t *sp )\n{\n\tsp->last = -1;\n}\n\nstatic inline qboolean R_SeparatePassActive( const separate_pass_t *sp )\n{\n\treturn sp->last >= 0 ? true : false;\n}\n\nbyte *Mod_GetCurrentVis( void )\n{\n\tif( gEngfuncs.drawFuncs->Mod_GetCurrentVis && tr.fCustomRendering )\n\t\treturn gEngfuncs.drawFuncs->Mod_GetCurrentVis();\n\treturn RI.visbytes;\n}\n\nvoid Mod_SetOrthoBounds( const float *mins, const float *maxs )\n{\n\tif( gEngfuncs.drawFuncs->GL_OrthoBounds )\n\t{\n\t\tgEngfuncs.drawFuncs->GL_OrthoBounds( mins, maxs );\n\t}\n\n\tVector2Average( maxs, mins, world_orthocenter );\n\tVector2Subtract( maxs, world_orthocenter, world_orthohalf );\n}\n\nvoid R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords )\n{\n\tconst mextrasurf_t *info = surf->info;\n\tfloat s, t;\n\n\ts = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\ts += surf->light_s * sample_size;\n\ts += sample_size * 0.5f;\n\ts /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\tt = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\tt += surf->light_t * sample_size;\n\tt += sample_size * 0.5f;\n\tt /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\tVector2Set( coords, s, t );\n}\n\nstatic void R_TextureCoord( const vec3_t v, const msurface_t *surf, vec2_t coords )\n{\n\tconst mtexinfo_t *info = surf->texinfo;\n\tfloat s, t;\n\n\ts = DotProduct( v, info->vecs[0] );\n\tt = DotProduct( v, info->vecs[1] );\n\n\tif( !FBitSet( surf->flags, SURF_DRAWTURB ))\n\t{\n\t\ts = ( s + info->vecs[0][3] ) / info->texture->width;\n\t\tt = ( t + info->vecs[1][3] ) / info->texture->height;\n\t}\n\n\tVector2Set( coords, s, t );\n}\n\nstatic void R_GetEdgePosition( const model_t *mod, const msurface_t *fa, int i, vec3_t vec )\n{\n\tconst int lindex = mod->surfedges[fa->firstedge + i];\n\n\tif( FBitSet( mod->flags, MODEL_QBSP2 ))\n\t{\n\t\tconst medge32_t *pedges = mod->edges32;\n\n\t\tif( lindex > 0 )\n\t\t\tVectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );\n\t\telse\n\t\t\tVectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );\n\t}\n\telse\n\t{\n\t\tconst medge16_t *pedges = mod->edges16;\n\n\t\tif( lindex > 0 )\n\t\t\tVectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );\n\t\telse\n\t\t\tVectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );\n\t}\n}\n\nstatic void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs )\n{\n\tint\ti, j;\n\tfloat\t*v;\n\n\tClearBounds( mins, maxs );\n\n\tfor( i = 0, v = verts; i < numverts; i++ )\n\t{\n\t\tfor( j = 0; j < 3; j++, v++ )\n\t\t{\n\t\t\tif( *v < mins[j] ) mins[j] = *v;\n\t\t\tif( *v > maxs[j] ) maxs[j] = *v;\n\t\t}\n\t}\n}\n\nstatic void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts )\n{\n\tvec3_t\t\tfront[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE];\n\tfloat\t\tdist[SUBDIVIDE_SIZE];\n\tfloat\t\tm, frac, *v;\n\tint\t\ti, j, k, f, b;\n\tfloat\t\tsample_size;\n\tvec3_t\t\tmins, maxs;\n\tglpoly2_t\t\t*poly;\n\n\tif( numverts > ( SUBDIVIDE_SIZE - 4 ))\n\t\tgEngfuncs.Host_Error( \"%s: too many vertexes on face ( %i )\\n\", __func__, numverts );\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( warpface );\n\tBoundPoly( numverts, verts, mins, maxs );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tm = ( mins[i] + maxs[i] ) * 0.5f;\n\t\tm = SUBDIVIDE_SIZE * floor( m / SUBDIVIDE_SIZE + 0.5f );\n\t\tif( maxs[i] - m < 8 ) continue;\n\t\tif( m - mins[i] < 8 ) continue;\n\n\t\t// cut it\n\t\tv = verts + i;\n\t\tfor( j = 0; j < numverts; j++, v += 3 )\n\t\t\tdist[j] = *v - m;\n\n\t\t// wrap cases\n\t\tdist[j] = dist[0];\n\t\tv -= i;\n\t\tVectorCopy( verts, v );\n\n\t\tf = b = 0;\n\t\tv = verts;\n\t\tfor( j = 0; j < numverts; j++, v += 3 )\n\t\t{\n\t\t\tif( dist[j] >= 0 )\n\t\t\t{\n\t\t\t\tVectorCopy( v, front[f] );\n\t\t\t\tf++;\n\t\t\t}\n\n\t\t\tif( dist[j] <= 0 )\n\t\t\t{\n\t\t\t\tVectorCopy (v, back[b]);\n\t\t\t\tb++;\n\t\t\t}\n\n\t\t\tif( dist[j] == 0 || dist[j+1] == 0 )\n\t\t\t\tcontinue;\n\n\t\t\tif(( dist[j] > 0 ) != ( dist[j+1] > 0 ))\n\t\t\t{\n\t\t\t\t// clip point\n\t\t\t\tfrac = dist[j] / ( dist[j] - dist[j+1] );\n\t\t\t\tfor( k = 0; k < 3; k++ )\n\t\t\t\t\tfront[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]);\n\t\t\t\tf++;\n\t\t\t\tb++;\n\t\t\t}\n\t\t}\n\n\t\tSubdividePolygon_r( loadmodel, warpface, f, front[0] );\n\t\tSubdividePolygon_r( loadmodel, warpface, b, back[0] );\n\t\treturn;\n\t}\n\n\tif( numverts != 4 )\n\t\tClearBits( warpface->flags, SURF_DRAWTURB_QUADS );\n\n\t// add a point in the center to help keep warp valid\n\tpoly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly2_t ) + numverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = warpface->polys;\n\tpoly->flags = warpface->flags;\n\twarpface->polys = poly;\n\tpoly->numverts = numverts;\n\n\tfor( i = 0; i < numverts; i++, verts += 3 )\n\t{\n\t\tVectorCopy( verts, poly->verts[i] );\n\t\tR_TextureCoord( verts, warpface, &poly->verts[i][3] );\n\n\t\t// for speed reasons\n\t\tif( !FBitSet( warpface->flags, SURF_DRAWTURB ))\n\t\t{\n\t\t\t// lightmap texture coordinates\n\t\t\tR_LightmapCoord( verts, warpface, sample_size, &poly->verts[i][5] );\n\t\t}\n\t}\n}\n\n/*\n===============================\nGL_SetupFogColorForSurfaces\n\nevery render pass applies new fog layer, resulting in wrong fog color\nrecalculate fog color for current pass count\n===============================\n*/\nstatic void GL_SetupFogColorForSurfacesEx( int passes, float density, qboolean blend_lightmaps )\n{\n\tvec4_t\tfogColor;\n\tfloat\tfactor, div;\n\n\tif( !glState.isFogEnabled )\n\t\treturn;\n\n\tif(( passes < 2 ) || (RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ))\n\t{\n\t\tpglFogfv( GL_FOG_COLOR, RI.fogColor );\n\t\treturn;\n\t}\n\n\tdiv = passes - 1;\n\tfactor = passes;\n\tfogColor[0] = pow( RI.fogColor[0] / div, ( 1.0f / factor ));\n\tfogColor[1] = pow( RI.fogColor[1] / div, ( 1.0f / factor ));\n\tfogColor[2] = pow( RI.fogColor[2] / div, ( 1.0f / factor ));\n\tfogColor[3] = 1.0f; // ignored but GL_FOG_COLOR requires vec4_t\n\n\t// because of enabled blending in R_BlendLightmaps, need to scale down fog color\n\t// but only during lightmap blending & without VBO (it takes another route)\n\tif( blend_lightmaps && gl_overbright.value )\n\t\tVectorScale( fogColor, 0.5f, fogColor );\n\n\tpglFogfv( GL_FOG_COLOR, fogColor );\n\tpglFogf( GL_FOG_DENSITY, RI.fogDensity * density );\n}\n\n\nvoid GL_SetupFogColorForSurfaces( void )\n{\n\tGL_SetupFogColorForSurfacesEx( r_detailtextures.value ? 3 : 2, 1.0f, false );\n}\n\nvoid GL_ResetFogColor( void )\n{\n\t// restore fog here\n\tif( glState.isFogEnabled )\n\t\tpglFogfv( GL_FOG_COLOR, RI.fogColor );\n}\n\n/*\n================\nGL_SubdivideSurface\n\nBreaks a polygon up along axial 64 unit\nboundaries so that turbulent and sky warps\ncan be done reasonably.\n================\n*/\nvoid GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa )\n{\n\tvec3_t\tverts[SUBDIVIDE_SIZE];\n\tint\ti;\n\n\t// convert edges back to a normal polygon\n\tfor( i = 0; i < fa->numedges; i++ )\n\t\tR_GetEdgePosition( loadmodel, fa, i, verts[i] );\n\n\tSetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state\n\n\t// do subdivide\n\tSubdividePolygon_r( loadmodel, fa, fa->numedges, verts[0] );\n}\n\n/*\n================\nGL_BuildPolygonFromSurface\n================\n*/\nstatic int GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa )\n{\n\tint\t\ti, lnumverts, nColinElim = 0;\n\tfloat\t\tsample_size;\n\ttexture_t\t\t*tex;\n\tgl_texture_t\t*glt;\n\tglpoly2_t\t\t*poly;\n\n\tif( !mod || !fa->texinfo || !fa->texinfo->texture )\n\t\treturn nColinElim; // bad polygon ?\n\n\tif( FBitSet( fa->flags, SURF_CONVEYOR ) && fa->texinfo->texture->gl_texturenum != 0 )\n\t{\n\t\tglt = R_GetTexture( fa->texinfo->texture->gl_texturenum );\n\t\ttex = fa->texinfo->texture;\n\t\tAssert( glt != NULL && tex != NULL );\n\n\t\t// update conveyor widths for keep properly speed of scrolling\n\t\tglt->srcWidth = tex->width;\n\t\tglt->srcHeight = tex->height;\n\t}\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( fa );\n\n\t// reconstruct the polygon\n\tlnumverts = fa->numedges;\n\n\t// detach if already created, reconstruct again\n\tpoly = fa->polys;\n\tfa->polys = NULL;\n\n\t// quake simple models (healthkits etc) need to be reconstructed their polys because LM coords has changed after the map change\n\tpoly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = fa->polys;\n\tpoly->flags = fa->flags;\n\tfa->polys = poly;\n\tpoly->numverts = lnumverts;\n\n\tfor( i = 0; i < lnumverts; i++ )\n\t{\n\t\tR_GetEdgePosition( mod, fa, i, poly->verts[i] );\n\t\tR_TextureCoord( poly->verts[i], fa, &poly->verts[i][3] );\n\t\tR_LightmapCoord( poly->verts[i], fa, sample_size, &poly->verts[i][5] );\n\t}\n\n\t// remove co-linear points - Ed\n\tif( !gl_keeptjunctions.value && !FBitSet( fa->flags, SURF_UNDERWATER ))\n\t{\n\t\tfor( i = 0; i < lnumverts; i++ )\n\t\t{\n\t\t\tvec3_t\tv1, v2;\n\t\t\tfloat\t*prev, *this, *next;\n\n\t\t\tprev = poly->verts[(i + lnumverts - 1) % lnumverts];\n\t\t\tnext = poly->verts[(i + 1) % lnumverts];\n\t\t\tthis = poly->verts[i];\n\n\t\t\tVectorSubtract( this, prev, v1 );\n\t\t\tVectorNormalize( v1 );\n\t\t\tVectorSubtract( next, prev, v2 );\n\t\t\tVectorNormalize( v2 );\n\n\t\t\t// skip co-linear points\n\t\t\tif(( fabs( v1[0] - v2[0] ) <= 0.001f) && (fabs( v1[1] - v2[1] ) <= 0.001f) && (fabs( v1[2] - v2[2] ) <= 0.001f))\n\t\t\t{\n\t\t\t\tint\tj, k;\n\n\t\t\t\tfor( j = i + 1; j < lnumverts; j++ )\n\t\t\t\t{\n\t\t\t\t\tfor( k = 0; k < VERTEXSIZE; k++ )\n\t\t\t\t\t\tpoly->verts[j-1][k] = poly->verts[j][k];\n\t\t\t\t}\n\n\t\t\t\t// retry next vertex next time, which is now current vertex\n\t\t\t\tlnumverts--;\n\t\t\t\tnColinElim++;\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t}\n\n\tpoly->numverts = lnumverts;\n\treturn nColinElim;\n}\n\n\n/*\n===============\nR_TextureAnim\n\nReturns the proper texture for a given time and base texture, do not process random tiling\n===============\n*/\nstatic texture_t *R_TextureAnim( texture_t *b )\n{\n\ttexture_t *base = b;\n\tint\tcount, reletive;\n\n\tif( RI.currententity->curstate.frame )\n\t{\n\t\tif( base->alternate_anims )\n\t\t\tbase = base->alternate_anims;\n\t}\n\n\tif( !base->anim_total )\n\t\treturn base;\n\n\tif( base->name[0] == '-' )\n\t{\n\t\treturn b; // already tiled\n\t}\n\telse\n\t{\n\t\tint\tspeed;\n\n\t\t// Quake1 textures uses 10 frames per second\n\t\tif( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))\n\t\t\tspeed = 10;\n\t\telse speed = 20;\n\n\t\treletive = (int)(gp_cl->time * speed) % base->anim_total;\n\t}\n\n\n\tcount = 0;\n\n\twhile( base->anim_min > reletive || base->anim_max <= reletive )\n\t{\n\t\tbase = base->anim_next;\n\n\t\tif( !base || ++count > MOD_FRAMES )\n\t\t\treturn b;\n\t}\n\n\treturn base;\n}\n\n/*\n===============\nR_TextureAnimation\n\nReturns the proper texture for a given time and surface\n===============\n*/\nstatic texture_t *R_TextureAnimation( msurface_t *s )\n{\n\ttexture_t\t*base = s->texinfo->texture;\n\tint\tcount, reletive;\n\n\tif( RI.currententity && RI.currententity->curstate.frame )\n\t{\n\t\tif( base->alternate_anims )\n\t\t\tbase = base->alternate_anims;\n\t}\n\n\tif( !base->anim_total )\n\t\treturn base;\n\n\tif( base->name[0] == '-' )\n\t{\n\t\tint\ttx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES;\n\t\tint\tty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES;\n\n\t\treletive = rtable[tx][ty] % base->anim_total;\n\t}\n\telse\n\t{\n\t\tint\tspeed;\n\n\t\t// Quake1 textures uses 10 frames per second\n\t\tif( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))\n\t\t\tspeed = 10;\n\t\telse speed = 20;\n\n\t\treletive = (int)(gp_cl->time * speed) % base->anim_total;\n\t}\n\n\tcount = 0;\n\n\twhile( base->anim_min > reletive || base->anim_max <= reletive )\n\t{\n\t\tbase = base->anim_next;\n\n\t\tif( !base || ++count > MOD_FRAMES )\n\t\t\treturn s->texinfo->texture;\n\t}\n\n\treturn base;\n}\n\n/*\n===============\nR_AddDynamicLights\n===============\n*/\nstatic void R_AddDynamicLights( const msurface_t *surf )\n{\n\tconst mextrasurf_t *info = surf->info;\n\tint lnum, smax, tmax;\n\tint sample_frac = 1.0;\n\tfloat sample_size;\n\tmtexinfo_t *tex;\n\n\t// no dlighted surfaces here\n\tif( !surf->dlightbits )\n\t\treturn;\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\tsmax = (info->lightextents[0] / sample_size) + 1;\n\ttmax = (info->lightextents[1] / sample_size) + 1;\n\ttex = surf->texinfo;\n\n\tif( FBitSet( tex->flags, TEX_WORLD_LUXELS ))\n\t{\n\t\tif( surf->texinfo->faceinfo )\n\t\t\tsample_frac = surf->texinfo->faceinfo->texture_step;\n\t\telse if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))\n\t\t\tsample_frac = LM_SAMPLE_EXTRASIZE;\n\t\telse sample_frac = LM_SAMPLE_SIZE;\n\t}\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tdlight_t *dl;\n\t\tvec3_t impact, origin_l;\n\t\tfloat dist, rad, minlight;\n\t\tfloat sl, tl;\n\t\tint t;\n\n\t\tif( !FBitSet( surf->dlightbits, BIT( lnum )))\n\t\t\tcontinue;\t// not lit by this light\n\n\t\tdl = &tr.dlights[lnum];\n\n\t\t// transform light origin to local bmodel space\n\t\tif( !tr.modelviewIdentity )\n\t\t\tMatrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l );\n\t\telse VectorCopy( dl->origin, origin_l );\n\n\t\trad = dl->radius;\n\t\tdist = PlaneDiff( origin_l, surf->plane );\n\t\trad -= fabs( dist );\n\n\t\t// rad is now the highest intensity on the plane\n\t\tminlight = dl->minlight;\n\t\tif( rad < minlight )\n\t\t\tcontinue;\n\n\t\tminlight = rad - minlight;\n\n\t\tif( surf->plane->type < 3 )\n\t\t{\n\t\t\tVectorCopy( origin_l, impact );\n\t\t\timpact[surf->plane->type] -= dist;\n\t\t}\n\t\telse VectorMA( origin_l, -dist, surf->plane->normal, impact );\n\n\t\tsl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\t\ttl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\n\t\tfor( t = 0; t < tmax; t++ )\n\t\t{\n\t\t\tint td = (tl - sample_size * t) * sample_frac;\n\t\t\tint s;\n\n\t\t\tif( td < 0 )\n\t\t\t\ttd = -td;\n\n\t\t\tfor( s = 0; s < smax; s++ )\n\t\t\t{\n\t\t\t\tint sd = (sl - sample_size * s) * sample_frac;\n\t\t\t\tfloat dist;\n\n\t\t\t\tif( sd < 0 )\n\t\t\t\t\tsd = -sd;\n\n\t\t\t\tif( sd > td )\n\t\t\t\t\tdist = sd + (td >> 1);\n\t\t\t\telse\n\t\t\t\t\tdist = td + (sd >> 1);\n\n\t\t\t\tif( dist < minlight )\n\t\t\t\t{\n\t\t\t\t\tuint *bl = &r_blocklights[(s + (t * smax)) * 3];\n\n\t\t\t\t\tbl[0] += ((int)((rad - dist) * 256) * dl->color.r ) / 256;\n\t\t\t\t\tbl[1] += ((int)((rad - dist) * 256) * dl->color.g ) / 256;\n\t\t\t\t\tbl[2] += ((int)((rad - dist) * 256) * dl->color.b ) / 256;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nR_SetCacheState\n================\n*/\nstatic void R_SetCacheState( msurface_t *surf )\n{\n\tint\tmaps;\n\n\tfor( maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++ )\n\t{\n\t\tsurf->cached_light[maps] = tr.lightstylevalue[surf->styles[maps]];\n\t}\n}\n\n/*\n=============================================================================\n\n  LIGHTMAP ALLOCATION\n\n=============================================================================\n*/\nstatic void LM_InitBlock( void )\n{\n\tmemset( gl_lms.allocated, 0, sizeof( gl_lms.allocated ));\n}\n\nstatic int LM_AllocBlock( int w, int h, int *x, int *y )\n{\n\tint\ti, j;\n\tint\tbest, best2;\n\n\tbest = BLOCK_SIZE;\n\n\tfor( i = 0; i < BLOCK_SIZE - w; i++ )\n\t{\n\t\tbest2 = 0;\n\n\t\tfor( j = 0; j < w; j++ )\n\t\t{\n\t\t\tif( gl_lms.allocated[i+j] >= best )\n\t\t\t\tbreak;\n\t\t\tif( gl_lms.allocated[i+j] > best2 )\n\t\t\t\tbest2 = gl_lms.allocated[i+j];\n\t\t}\n\n\t\tif( j == w )\n\t\t{\n\t\t\t// this is a valid spot\n\t\t\t*x = i;\n\t\t\t*y = best = best2;\n\t\t}\n\t}\n\n\tif( best + h > BLOCK_SIZE )\n\t\treturn false;\n\n\tfor( i = 0; i < w; i++ )\n\t\tgl_lms.allocated[*x + i] = best + h;\n\n\treturn true;\n}\n\nstatic void LM_UploadDynamicBlock( void )\n{\n\tint\theight = 0, i;\n\n\tfor( i = 0; i < BLOCK_SIZE; i++ )\n\t{\n\t\tif( gl_lms.allocated[i] > height )\n\t\t\theight = gl_lms.allocated[i];\n\t}\n\n\tpglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer );\n}\n\nstatic void LM_UploadBlock( qboolean dynamic )\n{\n\tif( dynamic )\n\t{\n\t\tGL_Bind( XASH_TEXTURE0, tr.dlightTexture );\n\t\tLM_UploadDynamicBlock();\n\t}\n\telse\n\t{\n\t\trgbdata_t\tr_lightmap;\n\t\tchar\tlmName[16];\n\t\tint i = gl_lms.current_lightmap_texture;\n\n\t\t// upload static lightmaps only during loading\n\t\tmemset( &r_lightmap, 0, sizeof( r_lightmap ));\n\t\tQ_snprintf( lmName, sizeof( lmName ), \"*lightmap%i\", i );\n\n\t\tr_lightmap.width = BLOCK_SIZE;\n\t\tr_lightmap.height = BLOCK_SIZE;\n\t\tr_lightmap.type = PF_RGBA_32;\n\t\tr_lightmap.size = r_lightmap.width * r_lightmap.height * 4;\n\t\tr_lightmap.flags = IMAGE_HAS_COLOR;\n\t\tr_lightmap.buffer = gl_lms.lightmap_buffer;\n\t\ttr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_NOMIPMAP|TF_ATLAS_PAGE );\n\n\t\tif( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS )\n\t\t\tgEngfuncs.Host_Error( \"%s: full\\n\", __func__ );\n\t}\n}\n\n/*\n=================\nR_BuildLightmap\n\nCombine and scale multiple lightmaps into the floating\nformat in r_blocklights\n=================\n*/\nstatic void R_BuildLightMap( const msurface_t *surf, byte *dest, int stride, qboolean dynamic )\n{\n\tint map, t;\n\tconst mextrasurf_t *info = surf->info;\n\tint lightscale;\n\n\tconst int sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\tconst int smax = ( info->lightextents[0] / sample_size ) + 1;\n\tconst int tmax = ( info->lightextents[1] / sample_size ) + 1;\n\tconst int size = smax * tmax;\n\n\tif( gl_overbright.value )\n\t\tlightscale = ( R_HasEnabledVBO() && !r_vbo_overbrightmode.value) ? 171 : 256;\n\telse lightscale = ( pow( 2.0f, 1.0f / v_lightgamma->value ) * 256 ) + 0.5;\n\n\tmemset( r_blocklights, 0, sizeof( uint ) * size * 3 );\n\n\t// add all the lightmaps\n\tfor( map = 0; map < MAXLIGHTMAPS && surf->samples; map++ )\n\t{\n\t\tconst color24 *lm = &surf->samples[map * size];\n\t\tuint scale;\n\t\tint i;\n\n\t\tif( surf->styles[map] >= 255 )\n\t\t\tbreak;\n\n\t\tscale = tr.lightstylevalue[surf->styles[map]];\n\n\t\tfor( i = 0; i < size; i++ )\n\t\t{\n\t\t\tr_blocklights[i * 3 + 0] += lm[i].r * scale;\n\t\t\tr_blocklights[i * 3 + 1] += lm[i].g * scale;\n\t\t\tr_blocklights[i * 3 + 2] += lm[i].b * scale;\n\t\t}\n\t}\n\n\t// add all the dynamic lights\n\tif( surf->dlightframe == tr.framecount && dynamic )\n\t\tR_AddDynamicLights( surf );\n\n\tfor( t = 0; t < tmax; t++ )\n\t{\n\t\tint s;\n\n\t\tfor( s = 0; s < smax; s++ )\n\t\t{\n\t\t\tconst uint *bl = &r_blocklights[(s + (t * smax)) * 3];\n\t\t\tbyte *dst = &dest[(t * stride) + (s * 4)];\n\t\t\tint i;\n\n\t\t\tfor( i = 0; i < 3; i++ )\n\t\t\t{\n\t\t\t\tint t = bl[i] * lightscale >> 14;\n\n\t\t\t\tif( t > 1023 )\n\t\t\t\t\tt = 1023;\n\n\t\t\t\tdst[i] = LightToTexGamma( t ) >> 2;\n\t\t\t}\n\t\t\tdst[3] = 255;\n\t\t}\n\t}\n}\n\n/*\n================\nDrawGLPoly\n================\n*/\nstatic void DrawGLPoly( glpoly2_t *p, float xScale, float yScale )\n{\n\tfloat\t\t*v;\n\tfloat\t\tsOffset, sy;\n\tfloat\t\ttOffset, cy;\n\tcl_entity_t\t*e = RI.currententity;\n\tint\t\ti, hasScale = false;\n\n\tif( !p ) return;\n\n\tif( FBitSet( p->flags, SURF_DRAWTILED ))\n\t\tGL_ResetFogColor();\n\n\tif( p->flags & SURF_CONVEYOR )\n\t{\n\t\tfloat\t\tflConveyorSpeed = 0.0f;\n\t\tfloat\t\tflRate, flAngle;\n\t\tgl_texture_t\t*texture;\n\n\t\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == CL_GetEntityByIndex( 0 ))\n\t\t{\n\t\t\t// same as doom speed\n\t\t\tflConveyorSpeed = -35.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tflConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f;\n\t\t\tif( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed;\n\t\t}\n\t\ttexture = R_GetTexture( glState.currentTexturesIndex[glState.activeTMU] );\n\n\t\tflRate = fabs( flConveyorSpeed ) / (float)texture->srcWidth;\n\t\tflAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0;\n\n\t\tSinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy );\n\t\tsOffset = gp_cl->time * cy * flRate;\n\t\ttOffset = gp_cl->time * sy * flRate;\n\n\t\t// make sure that we are positive\n\t\tif( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset;\n\t\tif( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset;\n\n\t\t// make sure that we are in a [0,1] range\n\t\tsOffset = sOffset - (int)sOffset;\n\t\ttOffset = tOffset - (int)tOffset;\n\t}\n\telse\n\t{\n\t\tsOffset = tOffset = 0.0f;\n\t}\n\n\tif( xScale != 0.0f && yScale != 0.0f )\n\t\thasScale = true;\n\n\tpglBegin( GL_POLYGON );\n\n\tfor( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE )\n\t{\n\t\tif( hasScale )\n\t\t\tpglTexCoord2f(( v[3] + sOffset ) * xScale, ( v[4] + tOffset ) * yScale );\n\t\telse pglTexCoord2f( v[3] + sOffset, v[4] + tOffset );\n\n\t\tpglVertex3fv( v );\n\t}\n\n\tpglEnd();\n\n\tif( FBitSet( p->flags, SURF_DRAWTILED ))\n\t\tGL_SetupFogColorForSurfaces();\n}\n\n/*\n================\nDrawGLPolyChain\n\nRender lightmaps\n================\n*/\nstatic void DrawGLPolyChain( glpoly2_t *p, float soffset, float toffset )\n{\n\tqboolean\tdynamic = true;\n\n\tif( soffset == 0.0f && toffset == 0.0f )\n\t\tdynamic = false;\n\n\tfor( ; p != NULL; p = p->chain )\n\t{\n\t\tfloat\t*v;\n\t\tint\ti;\n\n\t\tpglBegin( GL_POLYGON );\n\n\t\tv = p->verts[0];\n\t\tfor( i = 0; i < p->numverts; i++, v += VERTEXSIZE )\n\t\t{\n\t\t\tif( !dynamic ) pglTexCoord2f( v[5], v[6] );\n\t\t\telse pglTexCoord2f( v[5] - soffset, v[6] - toffset );\n\t\t\tpglVertex3fv( v );\n\t\t}\n\t\tpglEnd ();\n\t}\n}\n\nstatic qboolean R_HasLightmap( void )\n{\n\tif( r_fullbright->value || !WORLDMODEL->lightdata )\n\t\treturn false;\n\n\tif( RI.currententity )\n\t{\n\t\tif( RI.currententity->curstate.effects & EF_FULLBRIGHT )\n\t\t\treturn false;\t// disabled by user\n\n\t\t// check for rendermode\n\t\tswitch( RI.currententity->curstate.rendermode )\n\t\t{\n\t\tcase kRenderTransTexture:\n\t\tcase kRenderTransColor:\n\t\tcase kRenderTransAdd:\n\t\tcase kRenderGlow:\n\t\t\treturn false; // no lightmaps\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/*\n================\nR_BlendLightmaps\n================\n*/\nstatic void R_BlendLightmaps( void )\n{\n\tmsurface_t\t*surf, *newsurf = NULL;\n\tint\t\ti;\n\n\tif( !R_HasLightmap() )\n\t\treturn;\n\n\tGL_SetupFogColorForSurfacesEx( r_detailtextures.value ? 3 : 2, 1.0f, true );\n\n\tif( !r_lightmap->value )\n\t\tpglEnable( GL_BLEND );\n\telse pglDisable( GL_BLEND );\n\n\t// lightmapped solid surfaces\n\tpglDepthMask( GL_FALSE );\n\tpglDepthFunc( GL_EQUAL );\n\n\tpglDisable( GL_ALPHA_TEST );\n\tif( gl_overbright.value )\n\t{\n\t\tpglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );\n\t\tif(!( R_HasEnabledVBO() && !r_vbo_overbrightmode.value ))\n\t\t\tpglColor4f( 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f );\n\t}\n\telse\n\t{\n\t\tpglBlendFunc( GL_ZERO, GL_SRC_COLOR );\n\t}\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\t// render static lightmaps first\n\tfor( i = 0; i < MAX_LIGHTMAPS; i++ )\n\t{\n\t\tif( gl_lms.lightmap_surfaces[i] )\n\t\t{\n\t\t\tGL_Bind( XASH_TEXTURE0, tr.lightmapTextures[i] );\n\n\t\t\tfor( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain )\n\t\t\t{\n\t\t\t\tif( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f );\n\t\t\t}\n\t\t}\n\t}\n\n\t// render dynamic lightmaps\n\tif( r_dynamic->value )\n\t{\n\t\tLM_InitBlock();\n\n\t\tGL_Bind( XASH_TEXTURE0, tr.dlightTexture );\n\t\tnewsurf = gl_lms.dynamic_surfaces;\n\n\t\tfor( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain )\n\t\t{\n\t\t\tint\t\tsmax, tmax;\n\t\t\tint\t\tsample_size;\n\t\t\tmextrasurf_t\t*info = surf->info;\n\t\t\tbyte\t\t*base;\n\n\t\t\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\t\t\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\t\t\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\n\t\t\tif( LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t ))\n\t\t\t{\n\t\t\t\tbase = gl_lms.lightmap_buffer;\n\t\t\t\tbase += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4;\n\n\t\t\t\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tmsurface_t\t*drawsurf;\n\n\t\t\t\t// upload what we have so far\n\t\t\t\tLM_UploadBlock( true );\n\n\t\t\t\t// draw all surfaces that use this lightmap\n\t\t\t\tfor( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->info->lightmapchain )\n\t\t\t\t{\n\t\t\t\t\tif( drawsurf->polys )\n\t\t\t\t\t{\n\t\t\t\t\t\tDrawGLPolyChain( drawsurf->polys,\n\t\t\t\t\t\t( drawsurf->light_s - drawsurf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ),\n\t\t\t\t\t\t( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tnewsurf = drawsurf;\n\n\t\t\t\t// clear the block\n\t\t\t\tLM_InitBlock();\n\n\t\t\t\t// try uploading the block now\n\t\t\t\tif( !LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t ))\n\t\t\t\t\tgEngfuncs.Host_Error( \"AllocBlock: full\\n\" );\n\n\t\t\t\tbase = gl_lms.lightmap_buffer;\n\t\t\t\tbase += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4;\n\n\t\t\t\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );\n\t\t\t}\n\t\t}\n\n\t\t// draw remainder of dynamic lightmaps that haven't been uploaded yet\n\t\tif( newsurf ) LM_UploadBlock( true );\n\n\t\tfor( surf = newsurf; surf != NULL; surf = surf->info->lightmapchain )\n\t\t{\n\t\t\tif( surf->polys )\n\t\t\t{\n\t\t\t\tDrawGLPolyChain( surf->polys,\n\t\t\t\t( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ),\n\t\t\t\t( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ));\n\t\t\t}\n\t\t}\n\t}\n\n\tpglDisable( GL_BLEND );\n\tpglDepthMask( GL_TRUE );\n\tpglDepthFunc( GL_LEQUAL );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\n\t// restore fog here\n\tGL_ResetFogColor();\n}\n\n/*\n================\nR_RenderFullbrights\n================\n*/\nstatic void R_RenderFullbrights( void )\n{\n\tmextrasurf_t\t*es, *p;\n\tint\t\ti;\n\n\tif( !R_SeparatePassActive( &draw_fullbrights ))\n\t\treturn;\n\n\tR_AllowFog( false );\n\tpglEnable( GL_BLEND );\n\tpglDepthMask( GL_FALSE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglBlendFunc( GL_ONE, GL_ONE );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\t// if fullbright textures are drawn in separate pass from VBO they\n\t// cause z-fighting, this is noticeable on `slide_waifufu.bsp` map\n\tif( R_HasEnabledVBO() && gl_polyoffset.value )\n\t{\n\t\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\t\tpglPolygonOffset( -1.0f, -gl_polyoffset.value );\n\t}\n\n\tfor( i = draw_fullbrights.first; i <= draw_fullbrights.last; i++ )\n\t{\n\t\tes = fullbright_surfaces[i];\n\t\tif( !es )\n\t\t\tcontinue;\n\n\t\tGL_Bind( XASH_TEXTURE0, i );\n\n\t\tfor( p = es; p; p = p->lumachain )\n\t\t\tDrawGLPoly( p->surf->polys, 0.0f, 0.0f );\n\n\t\tfullbright_surfaces[i] = NULL;\n\t\tes->lumachain = NULL;\n\t}\n\n\tif( R_HasEnabledVBO() && gl_polyoffset.value )\n\t\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\tpglDisable( GL_BLEND );\n\tpglDepthMask( GL_TRUE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\n\tR_ResetSeparatePass( &draw_fullbrights );\n\tR_AllowFog( true );\n}\n\n/*\n================\nR_RenderDetails\n================\n*/\nstatic void R_RenderDetails( int passes )\n{\n\tgl_texture_t\t*glt;\n\tmextrasurf_t\t*es, *p;\n\tmsurface_t\t*fa;\n\tint\t\ti;\n\n\tif( !R_SeparatePassActive( &draw_details ))\n\t\treturn;\n\n\tGL_SetupFogColorForSurfacesEx( passes, passes == 2 ? 0.5f : 1.0f, false );\n\n\tpglEnable( GL_BLEND );\n\tpglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );\n\tif(passes == 3)\n\t\tpglDepthFunc( GL_EQUAL );\n\telse\n\t{\n\t\tpglDepthFunc( GL_LEQUAL );\n\t\t//pglDepthMask( GL_FALSE );\n\t\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\t}\n\n\tfor( i = draw_details.first; i <= draw_details.last; i++ )\n\t{\n\t\tes = detail_surfaces[i];\n\t\tif( !es )\n\t\t\tcontinue;\n\n\t\tGL_Bind( XASH_TEXTURE0, i );\n\n\t\tfor( p = es; p; p = p->detailchain )\n\t\t{\n\t\t\tfa = p->surf;\n\t\t\tglt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); // get texture scale\n\t\t\tDrawGLPoly( fa->polys, glt->xscale, glt->yscale );\n\t\t}\n\n\t\tdetail_surfaces[i] = NULL;\n\t\tes->detailchain = NULL;\n\t}\n\n\tpglDisable( GL_BLEND );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglDepthFunc( GL_LEQUAL );\n\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\tR_ResetSeparatePass( &draw_details );\n\n\t// restore fog here\n\tGL_ResetFogColor();\n}\n\nstatic void R_RenderFullbrightForSurface( msurface_t *fa, texture_t *t )\n{\n\tif( !t->fb_texturenum )\n\t\treturn;\n\n\tfa->info->lumachain = fullbright_surfaces[t->fb_texturenum];\n\tfullbright_surfaces[t->fb_texturenum] = fa->info;\n\tR_AddToSeparatePass( &draw_fullbrights, t->fb_texturenum );\n}\n\nstatic void R_RenderDetailsForSurface( msurface_t *fa, texture_t *t )\n{\n\tif( !r_detailtextures.value )\n\t\treturn;\n\n\tif( glState.isFogEnabled )\n\t{\n\t\t// don't apply detail textures for windows in the fog\n\t\tif( RI.currententity->curstate.rendermode != kRenderTransTexture )\n\t\t{\n\t\t\t// draw stub detail texture for underwater surfaces\n\t\t\tint texturenum = t->dt_texturenum ? t->dt_texturenum : tr.grayTexture;\n\n\t\t\tfa->info->detailchain = detail_surfaces[texturenum];\n\t\t\tdetail_surfaces[texturenum] = fa->info;\n\t\t\tR_AddToSeparatePass( &draw_details, texturenum );\n\t\t}\n\t}\n\telse if( t->dt_texturenum )\n\t{\n\t\tfa->info->detailchain = detail_surfaces[t->dt_texturenum];\n\t\tdetail_surfaces[t->dt_texturenum] = fa->info;\n\t\tR_AddToSeparatePass( &draw_details, t->dt_texturenum );\n\t}\n}\n\nstatic void R_RenderDecalsForSurface( msurface_t *fa, int cull_type )\n{\n\tif( RI.currententity->curstate.rendermode == kRenderNormal )\n\t{\n\t\t// batch decals to draw later\n\t\tif( tr.num_draw_decals < MAX_DECAL_SURFS && fa->pdecals )\n\t\t\ttr.draw_decals[tr.num_draw_decals++] = fa;\n\t}\n\telse\n\t{\n\t\t// if rendermode != kRenderNormal draw decals sequentially\n\t\tDrawSurfaceDecals( fa, true, (cull_type == CULL_BACKSIDE));\n\t}\n}\n\nstatic qboolean R_CheckLightMap( msurface_t *fa )\n{\n\tqboolean is_dynamic = false;\n\tint maps;\n\n\t// check for lightmap modification\n\tfor( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ )\n\t{\n\t\tif( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] )\n\t\t\tgoto dynamic;\n\t}\n\n\t// dynamic this frame or dynamic previously\n\tif( fa->dlightframe == tr.framecount )\n\t{\ndynamic:\n\t\t// NOTE: at this point we have only valid textures\n\t\tif( r_dynamic->value )\n\t\t\tis_dynamic = true;\n\t}\n\n\tif( is_dynamic )\n\t{\n\t\tconst int style = fa->styles[maps];\n\n\t\tif( maps < MAXLIGHTMAPS && ( style >= 32 || style == 0 || style == 20 ) && fa->dlightframe != tr.framecount )\n\t\t{\n\t\t\tbyte\t\ttemp[132*132*4];\n\t\t\tmextrasurf_t\t*info = fa->info;\n\t\t\tint\t\tsample_size;\n\t\t\tint\t\tsmax, tmax;\n\n\t\t\tsample_size = gEngfuncs.Mod_SampleSizeForFace( fa );\n\t\t\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\t\t\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\n\t\t\tif( smax < 132 && tmax < 132 )\n\t\t\t\tR_BuildLightMap( fa, temp, smax * 4, true );\n\t\t\telse\n\t\t\t{\n\t\t\t\tsmax = Q_min( smax, 132 );\n\t\t\t\ttmax = Q_min( tmax, 132 );\n\t\t\t\tmemset( temp, 255, sizeof( temp ));\n\t\t\t\t//Host_MapDesignError( \"%s: bad surface extents: %d %d\", __func__, fa->extents[0], fa->extents[1] );\n\t\t\t}\n\n\t\t\tR_SetCacheState( fa );\n\n#if XASH_WES\n\t\t\tGL_Bind( XASH_TEXTURE1, tr.lightmapTextures[fa->lightmaptexturenum] );\n\t\t\tpglTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );\n#else\n\t\t\tGL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] );\n#endif\n\n\t\t\tpglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, temp );\n\n#if XASH_WES\n\t\t\tGL_SelectTexture( XASH_TEXTURE0 );\n#endif\n\t\t}\n\t\telse\n\t\t\treturn true; // add to dynamic chain\n\t}\n\n\treturn false; // updated\n}\n\nstatic void R_RenderLightmapForSurface( msurface_t *fa )\n{\n\tif( !fa->polys || FBitSet( fa->flags, SURF_DRAWTILED ))\n\t\treturn;\n\n\tif( R_CheckLightMap( fa ))\n\t{\n\t\tfa->info->lightmapchain = gl_lms.dynamic_surfaces;\n\t\tgl_lms.dynamic_surfaces = fa;\n\t}\n\telse\n\t{\n\t\tfa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum];\n\t\tgl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa;\n\t}\n}\n\n/*\n================\nR_RenderBrushPoly\n================\n*/\nstatic void R_RenderBrushPoly( msurface_t *fa, int cull_type )\n{\n\ttexture_t\t*t;\n\n\tr_stats.c_world_polys++;\n\n\tif( fa->flags & SURF_DRAWSKY )\n\t\treturn; // already handled\n\n\tt = R_TextureAnimation( fa );\n\n\tif( FBitSet( fa->flags, SURF_DRAWTURB ))\n\t{\n\t\t// warp texture, no lightmaps\n\t\tEmitWaterPolys( fa, cull_type == CULL_BACKSIDE, R_UploadRipples( t ));\n\t\treturn;\n\t}\n\telse GL_Bind( XASH_TEXTURE0, t->gl_texturenum );\n\n\tR_RenderFullbrightForSurface( fa, t );\n\tR_RenderDetailsForSurface( fa, t );\n\tDrawGLPoly( fa->polys, 0.0f, 0.0f );\n\tR_RenderDecalsForSurface( fa, cull_type );\n\tR_RenderLightmapForSurface( fa );\n}\n\n/*\n================\nR_DrawTextureChains\n================\n*/\nstatic void R_DrawTextureChains( void )\n{\n\tint\t\ti;\n\tmsurface_t\t*s;\n\ttexture_t\t\t*t;\n\n\t// make sure what color is reset\n\tpglColor4ub( 255, 255, 255, 255 );\n\tR_LoadIdentity();\t// set identity matrix\n\n\tGL_SetupFogColorForSurfaces();\n\n\t// restore worldmodel\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\tRI.currentmodel = RI.currententity->model;\n\n\tif( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX ))\n\t{\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglColor3f( 1.0f, 1.0f, 1.0f );\n\t}\n\n\t// clip skybox surfaces\n\tfor( s = skychain; s != NULL; s = s->texturechain )\n\t\tR_AddSkyBoxSurface( s );\n\n\tif( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX ))\n\t{\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tif( skychain )\n\t\t\tR_DrawClouds();\n\t\tskychain = NULL;\n\t}\n\n\tR_DrawVBO( !r_fullbright->value && !!WORLDMODEL->lightdata, true );\n\n\tfor( i = 0; i < WORLDMODEL->numtextures; i++ )\n\t{\n\t\tt = WORLDMODEL->textures[i];\n\t\tif( !t ) continue;\n\n\t\ts = t->texturechain;\n\n\t\tif( !s || ( i == tr.skytexturenum ))\n\t\t\tcontinue;\n\n\t\tif(( s->flags & SURF_DRAWTURB ) && tr.movevars->wateralpha < 1.0f )\n\t\t{\n\t\t\tR_AddToSeparatePass( &draw_wateralpha, i );\n\t\t\tcontinue;\t// draw translucent water later\n\t\t}\n\n\t\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT ))\n\t\t{\n\t\t\tR_AddToSeparatePass( &draw_alpha_surfaces, i );\n\t\t\tcontinue;\t// draw transparent surfaces later\n\t\t}\n\n\t\tfor( ; s != NULL; s = s->texturechain )\n\t\t\tR_RenderBrushPoly( s, CULL_VISIBLE );\n\t\tt->texturechain = NULL;\n\t}\n}\n\n/*\n================\nR_DrawAlphaTextureChains\n================\n*/\nvoid R_DrawAlphaTextureChains( void )\n{\n\tint\t\ti;\n\tmsurface_t\t*s;\n\ttexture_t\t\t*t;\n\n\tif( !R_SeparatePassActive( &draw_alpha_surfaces ))\n\t\treturn;\n\n\tmemset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));\n\tgl_lms.dynamic_surfaces = NULL;\n\n\t// make sure what color is reset\n\tpglColor4ub( 255, 255, 255, 255 );\n\tR_LoadIdentity(); // set identity matrix\n\n\tpglDisable( GL_BLEND );\n\tpglEnable( GL_ALPHA_TEST );\n\tpglAlphaFunc( GL_GREATER, 0.25f );\n\n\tGL_SetupFogColorForSurfaces();\n\n\t// restore worldmodel\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\tRI.currentmodel = RI.currententity->model;\n\tRI.currententity->curstate.rendermode = kRenderTransAlpha;\n\n\tfor( i = draw_alpha_surfaces.first; i <= draw_alpha_surfaces.last; i++ )\n\t{\n\t\tt = WORLDMODEL->textures[i];\n\t\tif( !t )\n\t\t\tcontinue;\n\n\t\ts = t->texturechain;\n\n\t\tif( !s || !FBitSet( s->flags, SURF_TRANSPARENT ))\n\t\t\tcontinue;\n\n\t\tfor( ; s != NULL; s = s->texturechain )\n\t\t\tR_RenderBrushPoly( s, CULL_VISIBLE );\n\t\tt->texturechain = NULL;\n\t}\n\n\tR_ResetSeparatePass( &draw_alpha_surfaces );\n\n\tGL_ResetFogColor();\n\tR_BlendLightmaps();\n\tRI.currententity->curstate.rendermode = kRenderNormal; // restore world rendermode\n\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n}\n\n/*\n================\nR_DrawWaterSurfaces\n================\n*/\nvoid R_DrawWaterSurfaces( void )\n{\n\tint\t\ti;\n\tmsurface_t\t*s;\n\ttexture_t\t\t*t;\n\n\tif( !RI.drawWorld || RI.onlyClientDraw )\n\t\treturn;\n\n\t// non-transparent water is already drawed\n\tif( !R_SeparatePassActive( &draw_wateralpha ))\n\t\treturn;\n\n\t// restore worldmodel\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\tRI.currentmodel = RI.currententity->model;\n\n\t// go back to the world matrix\n\tR_LoadIdentity();\n\n\tpglEnable( GL_BLEND );\n\tpglDepthMask( GL_FALSE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglColor4f( 1.0f, 1.0f, 1.0f, tr.movevars->wateralpha );\n\n\tfor( i = draw_wateralpha.first; i <= draw_wateralpha.last; i++ )\n\t{\n\t\tt = WORLDMODEL->textures[i];\n\t\tif( !t ) continue;\n\n\t\ts = t->texturechain;\n\t\tif( !s ) continue;\n\n\t\tif( !FBitSet( s->flags, SURF_DRAWTURB ))\n\t\t\tcontinue;\n\n\t\tfor( ; s; s = s->texturechain )\n\t\t\tEmitWaterPolys( s, false, R_UploadRipples( t ));\n\n\t\tt->texturechain = NULL;\n\t}\n\n\tR_ResetSeparatePass( &draw_wateralpha );\n\n\tpglDisable( GL_BLEND );\n\tpglDepthMask( GL_TRUE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglColor4ub( 255, 255, 255, 255 );\n}\n\n/*\n=================\nR_SurfaceCompare\n\ncompare translucent surfaces\n=================\n*/\nstatic int R_SurfaceCompare( const void *a, const void *b )\n{\n\tmsurface_t\t*surf1, *surf2;\n\tvec3_t\t\torg1, org2;\n\tfloat\t\tlen1, len2;\n\n\tsurf1 = (msurface_t *)((sortedface_t *)a)->surf;\n\tsurf2 = (msurface_t *)((sortedface_t *)b)->surf;\n\n\tVectorAdd( RI.currententity->origin, surf1->info->origin, org1 );\n\tVectorAdd( RI.currententity->origin, surf2->info->origin, org2 );\n\n\t// compare by plane dists\n\tlen1 = DotProduct( org1, RI.vforward ) - RI.viewplanedist;\n\tlen2 = DotProduct( org2, RI.vforward ) - RI.viewplanedist;\n\n\tif( len1 > len2 )\n\t\treturn -1;\n\tif( len1 < len2 )\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void R_SetRenderMode( cl_entity_t *e )\n{\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderNormal:\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\tbreak;\n\tcase kRenderTransColor:\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, e->curstate.renderamt );\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\tcase kRenderTransAdd:\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglColor4f( tr.blend, tr.blend, tr.blend, 1.0f );\n\t\tpglBlendFunc( GL_ONE, GL_ONE );\n\t\tpglDepthMask( GL_FALSE );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\tcase kRenderTransAlpha:\n\t\tpglEnable( GL_ALPHA_TEST );\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\t{\n\t\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\t\tpglColor4f( 1.0f, 1.0f, 1.0f, tr.blend );\n\t\t\tpglEnable( GL_BLEND );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\t\tpglDisable( GL_BLEND );\n\t\t}\n\t\tpglAlphaFunc( GL_GREATER, 0.25f );\n\t\tbreak;\n\tdefault:\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, tr.blend );\n\t\tpglDepthMask( GL_FALSE );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\t}\n}\n\n/*\n=================\nR_DrawBrushModel\n=================\n*/\nvoid R_DrawBrushModel( cl_entity_t *e )\n{\n\tint\t\ti, k, num_sorted;\n\tvec3_t\t\torigin_l, oldorigin;\n\tint\t\told_rendermode;\n\tvec3_t\t\tmins, maxs;\n\tint\t\tcull_type;\n\tmsurface_t\t*psurf;\n\tmodel_t\t\t*clmodel;\n\tqboolean\t\trotated;\n\tdlight_t\t\t*l;\n\tqboolean allow_vbo = R_HasEnabledVBO();\n\n\tif( !RI.drawWorld ) return;\n\n\tclmodel = e->model;\n\n\t// external models not loaded to VBO\n\tif( clmodel->surfaces != WORLDMODEL->surfaces )\n\t\tallow_vbo = false;\n\n\tif( !VectorIsNull( e->angles ))\n\t{\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tmins[i] = e->origin[i] - clmodel->radius;\n\t\t\tmaxs[i] = e->origin[i] + clmodel->radius;\n\t\t}\n\t\trotated = true;\n\t}\n\telse\n\t{\n\t\tVectorAdd( e->origin, clmodel->mins, mins );\n\t\tVectorAdd( e->origin, clmodel->maxs, maxs );\n\t\trotated = false;\n\t}\n\n\tif( R_CullBox( mins, maxs ))\n\t\treturn;\n\n\tmemset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));\n\told_rendermode = e->curstate.rendermode;\n\tgl_lms.dynamic_surfaces = NULL;\n\n\tif( rotated ) R_RotateForEntity( e );\n\telse R_TranslateForEntity( e );\n\n\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( clmodel->flags, MODEL_TRANSPARENT ))\n\t\te->curstate.rendermode = kRenderTransAlpha;\n\n\te->visframe = tr.realframecount; // visible\n\n\tif( rotated ) Matrix4x4_VectorITransform( RI.objectMatrix, RI.cullorigin, tr.modelorg );\n\telse VectorSubtract( RI.cullorigin, e->origin, tr.modelorg );\n\n\t// calculate dynamic lighting for bmodel\n\tfor( k = 0; k < MAX_DLIGHTS; k++ )\n\t{\n\t\tl = &tr.dlights[k];\n\n\t\tif( l->die < gp_cl->time || !l->radius )\n\t\t\tcontinue;\n\n\t\tVectorCopy( l->origin, oldorigin ); // save lightorigin\n\t\tMatrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l );\n\t\tVectorCopy( origin_l, l->origin ); // move light in bmodel space\n\t\tR_MarkLights( l, 1<<k, clmodel->nodes + clmodel->hulls[0].firstclipnode );\n\t\tVectorCopy( oldorigin, l->origin ); // restore lightorigin\n\t}\n\n\t// setup the rendermode\n\tR_SetRenderMode( e );\n\tif( e->curstate.rendermode == kRenderTransAdd )\n\t{\n\t\tR_AllowFog( false );\n\t\tallow_vbo = false;\n\t}\n\n\tif( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture )\n\t\tallow_vbo = false;\n\n\tif( !allow_vbo )\n\t\tGL_SetupFogColorForSurfaces ();\n\n\tpsurf = &clmodel->surfaces[clmodel->firstmodelsurface];\n\tnum_sorted = 0;\n\n\tfor( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ )\n\t{\n\t\tif( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\t{\n\t\t\tif( psurf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES ))\n\t\t\t\tcontinue;\n\t\t\tif( mins[2] + 1.0f >= psurf->plane->dist )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tcull_type = R_CullSurface( psurf, &RI.frustum, RI.frustum.clipFlags );\n\n\t\tif( cull_type >= CULL_FRUSTUM )\n\t\t\tcontinue;\n\n\t\tif( cull_type == CULL_BACKSIDE )\n\t\t{\n\t\t\tif( !FBitSet( psurf->flags, SURF_DRAWTURB ) && !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture ))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif( num_sorted < gpGlobals->max_surfaces )\n\t\t{\n\t\t\tgpGlobals->draw_surfaces[num_sorted].surf = psurf;\n\t\t\tgpGlobals->draw_surfaces[num_sorted].cull = cull_type;\n\t\t\tnum_sorted++;\n\t\t}\n\t}\n\n\t// sort faces if needs\n\tif( !FBitSet( clmodel->flags, MODEL_LIQUID ) && e->curstate.rendermode == kRenderTransTexture && !gl_nosort.value )\n\t\tqsort( gpGlobals->draw_surfaces, num_sorted, sizeof( sortedface_t ), R_SurfaceCompare );\n\n\t// draw sorted translucent surfaces\n\tfor( i = 0; i < num_sorted; i++ )\n\t\tif( !allow_vbo || !R_AddSurfToVBO( gpGlobals->draw_surfaces[i].surf, true ) )\n\t\t\tR_RenderBrushPoly( gpGlobals->draw_surfaces[i].surf, gpGlobals->draw_surfaces[i].cull );\n\tR_DrawVBO( R_HasLightmap(), true );\n\n\tif( e->curstate.rendermode == kRenderTransColor )\n\t\tpglEnable( GL_TEXTURE_2D );\n\n\tDrawDecalsBatch();\n\tGL_ResetFogColor();\n\tR_BlendLightmaps();\n\tR_RenderFullbrights();\n\tR_RenderDetails( allow_vbo? 2: 3 );\n\n\t// restore fog here\n\tif( e->curstate.rendermode == kRenderTransAdd )\n\t\tR_AllowFog( true );\n\n\te->curstate.rendermode = old_rendermode;\n\tpglDisable( GL_ALPHA_TEST );\n\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\tpglDisable( GL_BLEND );\n\tpglDepthMask( GL_TRUE );\n\n\tif( r_showhull->value > 0.0f )\n\t{\n\t\tGLfloat factor, units;\n\n\t\tpglGetFloatv( GL_POLYGON_OFFSET_FACTOR, &factor );\n\t\tpglGetFloatv( GL_POLYGON_OFFSET_UNITS, &units );\n\n\t\tpglPolygonOffset( 1.0f, 2.0f );\n\t\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\t\tgEngfuncs.R_DrawModelHull( clmodel );\t// draw before restore\n\t\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\t\tpglPolygonOffset( factor, units );\n\t}\n\n\tR_LoadIdentity();\t// restore worldmatrix\n}\n\n\n/*\n==============================\n\nVBO\n\n==============================\n*/\n/*\nBulld arrays (vboarray_t) for all map geometry on map load.\nStore index base for every surface (vbosurfdata_t) to build index arrays\nFor each texture build index arrays (vbotexture_t) every frame.\n*/\n// vertex attribs\n//#define NO_TEXTURE_MATRIX // need debug\ntypedef struct vbovertex_s\n{\n\tvec3_t pos;\n\tvec2_t gl_tc;\n\tvec2_t lm_tc;\n#ifdef NO_TEXTURE_MATRIX\n\tvec2_t dt_tc;\n#endif\n} vbovertex_t;\n\n#ifndef UINT_INDEX\ntypedef unsigned short vboindex_t;\n#define VBOINDEX_MAX USHRT_MAX\n#define GL_VBOINDEX_TYPE GL_UNSIGNED_SHORT\n#else\ntypedef unsigned int vboindex_t;\n#define VBOINDEX_MAX UINT_MAX\n#define GL_VBOINDEX_TYPE GL_UNSIGNED_INT\n#endif\n\n// store indexes for each texture\ntypedef struct vbotexture_s\n{\n\tvboindex_t *indexarray; // index array (generated instead of texture chains)\n\tuint curindex; // counter for index array\n\tuint len; // maximum index array length\n\tstruct vbotexture_s *next; // if cannot fit into one array, allocate new one, as every array has own index space\n\tmsurface_t *dlightchain; // list of dlight surfaces\n\tstruct vboarray_s *vboarray; // debug\n\tuint lightmaptexturenum;\n} vbotexture_t;\n\n// array list\ntypedef struct vboarray_s\n{\n\tuint glindex; // glGenBuffers\n\tint array_len; // allocation length\n\tvbovertex_t *array; // vertex attrib array\n\tstruct vboarray_s *next; // split by 65536 vertices\n} vboarray_t;\n\n// every surface is linked to vbo texture\ntypedef struct vbosurfdata_s\n{\n\tvbotexture_t *vbotexture;\n\tuint texturenum;\n\tuint startindex;\n} vbosurfdata_t;\n\ntypedef struct vbodecal_s\n{\n\tint numVerts;\n} vbodecal_t;\n\n#define DECAL_VERTS_MAX 32\n#define DECAL_VERTS_CUT 8\n\ntypedef struct vbodecaldata_s\n{\n\tvbodecal_t decals[MAX_RENDER_DECALS];\n\tvbovertex_t decalarray[MAX_RENDER_DECALS * DECAL_VERTS_CUT];\n\tuint decalvbo;\n\tmsurface_t **lm;\n} vbodecaldata_t;\n\n// gl_decals.c\nextern decal_t\tgDecalPool[MAX_RENDER_DECALS];\n\nstatic struct vbo_static_s\n{\n\t// quickly free all allocations on map change\n\tpoolhandle_t mempool;\n\n\t// arays\n\tvbodecaldata_t *decaldata; // array\n\tvbotexture_t *textures; // array\n\tvbosurfdata_t *surfdata; // array\n\tvboarray_t *arraylist; // linked list\n\n\t// separate areay for dlights (build during draw)\n\tvboindex_t *dlight_index; // array\n\tvec2_t *dlight_tc; // array\n\tunsigned int dlight_vbo;\n\tvbovertex_t decal_dlight[MAX_RENDER_DECALS * DECAL_VERTS_MAX];\n\tunsigned int decal_dlight_vbo;\n\tint decal_numverts[MAX_RENDER_DECALS * DECAL_VERTS_MAX];\n\n\t// prevent draining cpu on empty cycles\n\tint minlightmap;\n\tint maxlightmap;\n\tint mintexture;\n\tint maxtexture;\n\n\t// never skip array splits\n\tint minarraysplit_tex;\n\tint maxarraysplit_tex;\n\tint minarraysplit_lm;\n\tint maxarraysplit_lm;\n\n\t// cvar state potentially might be changed during frame\n\t// so only enable VBO at the beginning of frame\n\tqboolean enabled;\n} vbos;\n\nstatic struct multitexturestate_s\n{\n\tint tmu_gl; // texture tmu\n\tint tmu_dt; // detail tmu\n\tint tmu_lm; // lightmap tmu\n\tqboolean details_enabled; // current texture has details\n\tint lm; // current lightmap texture\n\tqboolean skiptexture;\n\tgl_texture_t *glt; // details scale\n} mtst;\n\nenum array_state_e\n{\n\tVBO_ARRAY_NONE,\n\tVBO_ARRAY_STATIC,\n\tVBO_ARRAY_DECAL,\n\tVBO_ARRAY_DLIGHT,\n\tVBO_ARRAY_DECAL_DLIGHT\n};\n\nenum texture_state_e\n{\n\tVBO_TEXTURE_NONE,\n\tVBO_TEXTURE_MAIN,\n\tVBO_TEXTURE_DECAL\n};\n\nenum lightmap_state_e\n{\n\tVBO_LIGHTMAP_NONE,\n\tVBO_LIGHTMAP_STATIC,\n\tVBO_LIGHTMAP_DYNAMIC\n};\n\nstatic struct arraystate_s\n{\n\tenum array_state_e astate;\n\tenum texture_state_e tstate;\n\tenum lightmap_state_e lstate;\n\tint itexture;\n\tqboolean decal_mode;\n} vboarray;\n\nqboolean R_HasGeneratedVBO( void )\n{\n\treturn vbos.mempool != 0;\n}\n\nvoid R_EnableVBO( qboolean enable )\n{\n\tvbos.enabled = enable;\n}\n\nqboolean R_HasEnabledVBO( void )\n{\n\treturn vbos.enabled;\n}\n\n/*\n===================\nR_GenerateVBO\n\nAllocate memory for arrays, fill it with vertex attribs and upload to GPU\n===================\n*/\nvoid R_GenerateVBO( void )\n{\n\tmodel_t *world = WORLDMODEL;\n\tmsurface_t *surfaces;\n\tint numsurfaces;\n\tint numtextures;\n\tconst int numlightmaps = gl_lms.current_lightmap_texture;\n\tint k, len = 0;\n\tvboarray_t *vbo;\n\tuint maxindex = 0;\n\tdouble t1, t2, t3;\n\n\tif( R_HasGeneratedVBO() || !world || !world->surfaces )\n\t\treturn;\n\n\tt1 = gEngfuncs.pfnTime();\n\n\tsurfaces = world->surfaces;\n\tnumsurfaces = world->numsurfaces;\n\tnumtextures = world->numtextures;\n\n\tvbos.mempool = Mem_AllocPool(\"Render VBO Zone\");\n\n\tvbos.minarraysplit_tex = INT_MAX;\n\tvbos.maxarraysplit_tex = 0;\n\tvbos.minarraysplit_lm = MAXLIGHTMAPS;\n\tvbos.maxarraysplit_lm = 0;\n\tvbos.minlightmap = MAX_LIGHTMAPS;\n\tvbos.maxlightmap = 0;\n\tvbos.mintexture = INT_MAX;\n\tvbos.maxtexture = 0;\n\n\tvbos.textures = Mem_Calloc( vbos.mempool, numtextures * numlightmaps * sizeof( vbotexture_t ) );\n\tvbos.surfdata = Mem_Calloc( vbos.mempool, WORLDMODEL->numsurfaces * sizeof( vbosurfdata_t ) );\n\tvbos.arraylist = vbo = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) );\n\tvbos.decaldata = Mem_Calloc( vbos.mempool, sizeof( vbodecaldata_t ) );\n\tvbos.decaldata->lm = Mem_Calloc( vbos.mempool, sizeof( msurface_t* ) * numlightmaps );\n\n\t// count array lengths\n\tfor( k = 0; k < numlightmaps; k++ )\n\t{\n\t\tint j;\n\n\t\tfor( j = 0; j < numtextures; j++ )\n\t\t{\n\t\t\tint i;\n\t\t\tvbotexture_t *vbotex = &vbos.textures[k * numtextures + j];\n\n\t\t\tfor( i = 0; i < numsurfaces; i++ )\n\t\t\t{\n\t\t\t\tmsurface_t *surf = &surfaces[i];\n\n\t\t\t\tif( surf->lightmaptexturenum != k )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( R_TextureAnimation( surf ) != world->textures[j] )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( vbo->array_len + surf->polys->numverts > VBOINDEX_MAX )\n\t\t\t\t{\n\t\t\t\t\tvbotex->vboarray = vbo;\n\t\t\t\t\t// generate new array and new vbotexture node\n\t\t\t\t\tvbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len );\n\t\t\t\t\tgEngfuncs.Con_Printf( S_NOTE \"%s: allocated array of %d verts, texture %d, lm %d\\n\", __func__, vbo->array_len, j, k );\n\t\t\t\t\tvbo->next = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) );\n\t\t\t\t\tvbo = vbo->next;\n\t\t\t\t\tvbotex->next = Mem_Calloc( vbos.mempool, sizeof( vbotexture_t ) );\n\t\t\t\t\tvbotex = vbotex->next;\n\n\t\t\t\t\t// never skip this textures and lightmaps\n\t\t\t\t\tif( vbos.minarraysplit_tex > j )\n\t\t\t\t\t\tvbos.minarraysplit_tex = j;\n\t\t\t\t\tif( vbos.minarraysplit_lm > k )\n\t\t\t\t\t\tvbos.minarraysplit_lm = k;\n\t\t\t\t\tif( vbos.maxarraysplit_tex < j + 1 )\n\t\t\t\t\t\tvbos.maxarraysplit_tex = j + 1;\n\t\t\t\t\tif( vbos.maxarraysplit_lm < k + 1 )\n\t\t\t\t\t\tvbos.maxarraysplit_lm = k + 1;\n\t\t\t\t}\n\t\t\t\tvbos.surfdata[i].vbotexture = vbotex;\n\t\t\t\tvbos.surfdata[i].startindex = vbo->array_len;\n\t\t\t\tvbos.surfdata[i].texturenum = j;\n\t\t\t\tvbo->array_len += surf->polys->numverts;\n\t\t\t\tvbotex->len += surf->polys->numverts;\n\t\t\t\tvbotex->vboarray = vbo;\n\t\t\t}\n\t\t}\n\t}\n\n\t// allocate last array\n\tvbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len );\n\n\tt2 = gEngfuncs.pfnTime();\n\tgEngfuncs.Con_Printf( S_NOTE \"%s: allocated array of %d verts in %.3g seconds\\n\", __func__, vbo->array_len, t2 - t1 );\n\n\t// switch to list begin\n\tvbo = vbos.arraylist;\n\n\t// fill and upload\n\tfor( k = 0; k < numlightmaps; k++ )\n\t{\n\t\tint j;\n\n\t\tfor( j = 0; j < numtextures; j++ )\n\t\t{\n\t\t\tint i;\n\t\t\tvbotexture_t *vbotex = &vbos.textures[k * numtextures + j];\n\n\t\t\t// preallocate index arrays\n\t\t\tvbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( *vbotex->indexarray ) * 6 * vbotex->len );\n\t\t\tvbotex->lightmaptexturenum = k;\n\n\t\t\tif( maxindex < vbotex->len )\n\t\t\t\tmaxindex = vbotex->len;\n\n\t\t\tfor( i = 0; i < numsurfaces; i++ )\n\t\t\t{\n\t\t\t\tmsurface_t *surf = &surfaces[i];\n\t\t\t\tint l;\n\n\t\t\t\tif( surf->lightmaptexturenum != k )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif( R_TextureAnimation( surf ) != world->textures[j] )\n\t\t\t\t\tcontinue;\n\n\t\t\t\t// switch to next array\n\t\t\t\tif( len + surf->polys->numverts > VBOINDEX_MAX )\n\t\t\t\t{\n\t\t\t\t\t// upload last generated array\n\t\t\t\t\tpglGenBuffersARB( 1, &vbo->glindex );\n\t\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\t\t\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB );\n\n\t\t\t\t\tASSERT( len == vbo->array_len );\n\n\t\t\t\t\tvbo = vbo->next;\n\t\t\t\t\tvbotex = vbotex->next;\n\t\t\t\t\tvbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( *vbotex->indexarray ) * 6 *  vbotex->len );\n\t\t\t\t\tvbotex->lightmaptexturenum = k;\n\n\t\t\t\t\t// calculate limits for dlights\n\t\t\t\t\tif( maxindex < vbotex->len )\n\t\t\t\t\t\tmaxindex = vbotex->len;\n\n\t\t\t\t\tlen = 0;\n\t\t\t\t}\n\n\t\t\t\t// fill vbovertex_t\n\t\t\t\tfor( l = 0; l < surf->polys->numverts; l++ )\n\t\t\t\t{\n\t\t\t\t\tfloat *v = surf->polys->verts[l];\n\n\t\t\t\t\tVectorCopy( v, vbo->array[len + l].pos );\n\t\t\t\t\tvbo->array[len + l].gl_tc[0] = v[3];\n\t\t\t\t\tvbo->array[len + l].gl_tc[1] = v[4];\n\t\t\t\t\tvbo->array[len + l].lm_tc[0] = v[5];\n\t\t\t\t\tvbo->array[len + l].lm_tc[1] = v[6];\n#ifdef NO_TEXTURE_MATRIX\n\t\t\t\t\tif( world->textures[j]->dt_texturenum )\n\t\t\t\t\t{\n\t\t\t\t\t\tgl_texture_t *glt = R_GetTexture( world->textures[j]->gl_texturenum );\n\t\t\t\t\t\tvbo->array[len + l].dt_tc[0] = v[3] * glt->xscale;\n\t\t\t\t\t\tvbo->array[len + l].dt_tc[1] = v[4] * glt->yscale;\n\t\t\t\t\t}\n#endif\n\t\t\t\t}\n\n\t\t\t\tlen += surf->polys->numverts;\n\n\t\t\t}\n\t\t}\n\t}\n\tASSERT( len == vbo->array_len );\n\n\t// upload last array\n\tpglGenBuffersARB( 1, &vbo->glindex );\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB );\n\n\t// prepare decal array\n\tpglGenBuffersARB( 1, &vbos.decaldata->decalvbo );\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );\n\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_CUT * MAX_RENDER_DECALS, vbos.decaldata->decalarray, GL_DYNAMIC_DRAW_ARB );\n\n\t// preallocate dlight arrays\n\tvbos.dlight_index = Mem_Calloc( vbos.mempool, maxindex * sizeof( *vbos.dlight_index ) * 6 );\n\n\t// select maximum possible length for dlight\n\tvbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)( vbos.arraylist->next ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 ));\n\n\tif( r_vbo_dlightmode.value )\n\t{\n\t\tpglGenBuffersARB( 1, &vbos.dlight_vbo );\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (int)( vbos.arraylist->next ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 ), NULL, GL_STREAM_DRAW_ARB );\n\t\tpglGenBuffersARB( 1, &vbos.decal_dlight_vbo );\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB );\n\t}\n\n\n\t// reset state\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\tmtst.tmu_gl = XASH_TEXTURE0;\n\n\tt3 = gEngfuncs.pfnTime();\n\n\tgEngfuncs.Con_Reportf( S_NOTE \"%s: uploaded VBOs in %.3g seconds, %.3g seconds total\\n\", __func__, t3 - t2, t3 - t1 );\n}\n\n/*\n==============\nR_AddDecalVBO\n\ngenerate decal mesh and put it to array\n==============\n*/\nvoid R_AddDecalVBO( decal_t *pdecal, msurface_t *surf )\n{\n\tint numVerts, i;\n\tfloat *v;\n\tint decalindex = pdecal - &gDecalPool[0];\n\n\tif( !vbos.decaldata )\n\t\treturn;\n\n\tv = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts );\n\n\tif( numVerts > DECAL_VERTS_CUT )\n\t{\n\t\t// use client arrays\n\t\tvbos.decaldata->decals[decalindex].numVerts = -1;\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < numVerts; i++ )\n\t\tmemcpy( &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i], v + i * VERTEXSIZE, VERTEXSIZE * 4 );\n\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );\n\tpglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, decalindex * sizeof( vbovertex_t ) * DECAL_VERTS_CUT, sizeof( vbovertex_t ) * numVerts, &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT] );\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\n\tvbos.decaldata->decals[decalindex].numVerts = numVerts;\n}\n\n/*\n=============\nR_ClearVBO\n\nfree all vbo data\n=============\n*/\nvoid R_ClearVBO( void )\n{\n\tvboarray_t *vbo;\n\n\tfor( vbo = vbos.arraylist; vbo; vbo = vbo->next )\n\t\tpglDeleteBuffersARB( 1, &vbo->glindex );\n\n\tvbos.arraylist = NULL;\n\n\tif( vbos.decaldata )\n\t\tpglDeleteBuffersARB( 1, &vbos.decaldata->decalvbo );\n\n\tif( vbos.dlight_vbo )\n\t\tpglDeleteBuffersARB( 1, &vbos.dlight_vbo );\n\n\tif( vbos.decal_dlight_vbo )\n\t\tpglDeleteBuffersARB( 1, &vbos.decal_dlight_vbo );\n\tvbos.decal_dlight_vbo = vbos.dlight_vbo = 0;\n\n\tvbos.decaldata = NULL;\n\tMem_FreePool( &vbos.mempool );\n}\n\n\n/*\n===================\nR_DisableDetail\n\ndisable detail tmu\n===================\n*/\nstatic void R_DisableDetail( void )\n{\n\tif( mtst.details_enabled && mtst.tmu_dt != -1 )\n\t{\n\t\tGL_SelectTexture( mtst.tmu_dt );\n\t\tpglDisableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglMatrixMode( GL_TEXTURE );\n\t\tpglLoadIdentity();\n\t}\n}\n\n/*\n===================\nR_EnableDetail\n\nenable detail tmu if availiable\n===================\n*/\nstatic void R_EnableDetail( void )\n{\n\tif( mtst.details_enabled && mtst.tmu_dt != -1 )\n\t{\n\t\tGL_SelectTexture( mtst.tmu_dt );\n\t\tpglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );\n\n\t\t// use transform matrix for details (undone)\n#ifndef NO_TEXTURE_MATRIX\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );\n\t\tpglMatrixMode( GL_TEXTURE );\n\t\tpglLoadIdentity();\n\t\tpglScalef( mtst.glt->xscale, mtst.glt->yscale, 1 );\n#else\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, dt_tc ) );\n#endif\n\t}\n}\n\n/*\n==============\nR_SetLightmap\n\nenable lightmap on current tmu\n==============\n*/\nstatic void R_SetLightmap( void )\n{\n\tif( mtst.skiptexture )\n\t\treturn;\n\n\tif( gl_overbright.value )\n\t{\n\t\tif( r_vbo_overbrightmode.value == 1 )\n\t\t{\n\t\t\tGLfloat color[4] = { 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f };\n\t\t\tint tmu = glState.activeTMU;\n\t\t\tGL_SelectTexture( tmu - 1 );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_COLOR );\n\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );\n\t\t\t// doesn't work here for some reason\n\t\t\tpglTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );\n\t\t\tGL_SelectTexture( tmu );\n\t\t}\n\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );\n\t}\n\telse\n\t{\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t}\n\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );\n}\n\n/*\n==============\nR_SetDecalMode\n\nWhen drawing decal, disable or restore bump and details\n==============\n*/\nstatic void R_SetDecalMode( qboolean enable )\n{\n\tif( vboarray.decal_mode == enable )\n\t\treturn;\n\tvboarray.decal_mode = enable;\n\n\t// order is important to correctly rearrange TMUs\n\tif( enable )\n\t{\n\t\t// disable detail texture if enabled\n\t\tR_DisableDetail();\n\t}\n\telse\n\t{\n\t\tR_EnableDetail();\n\t}\n\n}\n\n/*\n==============\nR_SetupVBOTexture\n\nsetup multitexture mode before drawing VBOs\nif tex is NULL, load texture by number\n==============\n*/\nstatic texture_t *R_SetupVBOTexture( texture_t *tex, int number )\n{\n\tif( mtst.skiptexture )\n\t\treturn tex;\n\n\tif( !tex )\n\t\ttex = R_TextureAnim( WORLDMODEL->textures[number] );\n\n\tif( r_detailtextures.value && tex->dt_texturenum && mtst.tmu_dt != -1 )\n\t{\n\t\tmtst.details_enabled = true;\n\t\tGL_Bind( mtst.tmu_dt, tex->dt_texturenum );\n\t\tmtst.glt = R_GetTexture( tex->gl_texturenum );\n\t\tR_EnableDetail();\n\t}\n\telse R_DisableDetail();\n\n\tGL_Bind( mtst.tmu_gl, r_lightmap->value ?tr.whiteTexture:tex->gl_texturenum );\n\tif(number)\n\t\tvboarray.itexture = number;\n\n\treturn tex;\n}\n\n\nstatic void R_SetupVBOArrayStatic( vboarray_t *vbo, qboolean drawlightmap, qboolean drawtextures )\n{\n\tif( vboarray.astate != VBO_ARRAY_STATIC )\n\t{\n\t\t// bind array\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\t\t// dlights use same vertex array\n\t\tif( vboarray.astate != VBO_ARRAY_DLIGHT )\n\t\t{\n\n\t\t\tpglEnableClientState( GL_VERTEX_ARRAY );\n\t\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );\n\t\t}\n\n\n\t\t// setup multitexture\n\t\tif( drawtextures && vboarray.tstate != VBO_TEXTURE_MAIN )\n\t\t{\n\t\t\tGL_SelectTexture( mtst.tmu_gl = XASH_TEXTURE0 );\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\t\t\tpglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );\n\t\t\tvboarray.tstate = VBO_TEXTURE_MAIN;\n\t\t}\n\n\t\tif( drawlightmap && (vboarray.lstate != VBO_LIGHTMAP_STATIC || vboarray.astate == VBO_ARRAY_DECAL ) )\n\t\t{\n\t\t\t// set lightmap texenv\n\t\t\tif( mtst.lm  )\n\t\t\t\tGL_Bind( mtst.tmu_lm = XASH_TEXTURE1, mtst.lm );\n\t\t\telse\n\t\t\t\tGL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 );\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\t\t\tpglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\t\tR_SetLightmap();\n\t\t\tvboarray.lstate =  VBO_LIGHTMAP_STATIC;\n\t\t}\n\t\tvboarray.astate = VBO_ARRAY_STATIC;\n\t\tR_SetDecalMode( false );\n\t}\n}\n\n\n\nstatic void R_SetupVBOArrayDlight( vboarray_t *vbo, texture_t *texture )\n{\n\n\tif( vboarray.astate != VBO_ARRAY_DLIGHT )\n\t{\n\t\tif( vboarray.astate == VBO_ARRAY_DECAL_DLIGHT )\n\t\t{\n\t\t\t// bind array\n\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\t\t\tpglEnableClientState( GL_VERTEX_ARRAY );\n\t\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );\n\t\t}\n\t\tif( vboarray.tstate != VBO_TEXTURE_MAIN || vboarray.astate == VBO_ARRAY_DECAL_DLIGHT )\n\t\t{\n\t\t\tR_SetupVBOTexture( texture, vboarray.itexture );\n\t\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );\n\t\t\tvboarray.tstate = VBO_TEXTURE_MAIN;\n\t\t}\n\t\tGL_Bind( mtst.tmu_lm, tr.dlightTexture );\n\t\tvboarray.lstate = VBO_LIGHTMAP_DYNAMIC;\n\n\t\t// replace lightmap texcoord array by dlight array\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\tif( vbos.dlight_vbo  )\n\t\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 );\n\t\telse\n\t\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc );\n\n\t\tvboarray.astate = VBO_ARRAY_DLIGHT;\n\t}\n}\n\n#define SPARSE_DECALS_UPLOAD 0\n\nstatic void R_SetupVBOArrayDecalDlight( int decalcount )\n{\n\tif( vbos.decal_dlight_vbo )\n\t{\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );\n#if !SPARSE_DECALS_UPLOAD\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_MAX * decalcount, vbos.decal_dlight, GL_STREAM_DRAW_ARB );\n#endif\n\t}\n\n\tR_SetDecalMode( true );\n\t// hack: fix decal dlights on gl_vbo_details == 2 (wrong state??)\n\t/*if( mtst.details_enabled && mtst.tmu_dt != -1 )\n\t{\n\t\tGL_Bind( mtst.tmu_dt, tr.whiteTexture );\n\t}*/\n\tGL_SelectTexture( mtst.tmu_lm );\n\tif( vbos.decal_dlight_vbo )\n\t{\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );\n\t\tGL_SelectTexture( mtst.tmu_gl );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) );\n\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) );\n\t}\n\telse\n\t{\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc );\n\t\tGL_SelectTexture( mtst.tmu_gl );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc );\n\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos);\n\t}\n\tvboarray.astate = VBO_ARRAY_DECAL_DLIGHT;\n\tvboarray.tstate = VBO_TEXTURE_DECAL;\n\tvboarray.lstate = VBO_LIGHTMAP_DYNAMIC;\n}\n\n\n/*\n===================\nR_AdditionalPasses\n\ndraw details when not enough tmus\n===================\n*/\nstatic void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo, size_t offset )\n{\n\tif( !indexlen )\n\t\treturn;\n\n\t// draw details in additional pass\n\tif( r_detailtextures.value && r_vbo_detail.value == 1 && mtst.tmu_dt == -1 && tex->dt_texturenum )\n\t{\n\t\tgl_texture_t *glt = R_GetTexture( tex->gl_texturenum );\n\n\t\tGL_SelectTexture( XASH_TEXTURE1 );\n\t\tpglDisable( GL_TEXTURE_2D );\n\n\t\t// setup detail\n\t\tGL_Bind( XASH_TEXTURE0, tex->dt_texturenum );\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );\n\n\t\t// when drawing dlights, we need to bind array and unbind it again\n\t\tif( resetvbo )\n\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(offset + offsetof( vbovertex_t, gl_tc )));\n\n\t\t// apply scale\n\t\tpglMatrixMode( GL_TEXTURE );\n\t\tpglLoadIdentity();\n\t\tpglScalef( glt->xscale, glt->yscale, 1 );\n\n\t\t// draw\n#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes\n\t\tif( pglDrawRangeElements )\n\t\t\tpglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, indexlen, GL_VBOINDEX_TYPE, indexarray );\n\t\telse\n#endif\n\t\tpglDrawElements( GL_TRIANGLES, indexlen, GL_VBOINDEX_TYPE, indexarray );\n\n\n\t\t// restore state\n\t\tpglLoadIdentity();\n\t\t/*\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\tpglDisable( GL_BLEND );\n\t\t//GL_Bind( XASH_TEXTURE1, mtst.lm );\n\t\t//pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );;*/\n\n\t\tGL_SelectTexture( XASH_TEXTURE1 );\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_BLEND );\n\t\tvboarray.astate = VBO_ARRAY_NONE;\n\t\tvboarray.tstate = VBO_TEXTURE_NONE;\n\t\tvboarray.lstate = VBO_LIGHTMAP_NONE;\n\t\tif( resetvbo )\n\t\t\tR_SetupVBOArrayDlight( vbo, tex );\n\t\telse\n\t\t\tR_SetupVBOArrayStatic( vbo, true, true );\n\t}\n}\n\n\n#define MINIMIZE_UPLOAD\n#define DISCARD_DLIGHTS\n\n\nstatic void R_DrawDlightedDecals( vboarray_t *vbo, msurface_t *newsurf, msurface_t *surf, int decalcount, texture_t *texture )\n{\n\tmsurface_t *decalsurf;\n\tdecal_t *pdecal;\n\tint decali = 0;\n\n\tpglDepthMask( GL_FALSE );\n\tpglEnable( GL_BLEND );\n\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\tif( RI.currententity->curstate.rendermode == kRenderTransAlpha )\n\t\tpglDisable( GL_ALPHA_TEST );\n\n\tR_SetupVBOArrayDecalDlight( decalcount );\n\n\tfor( decalsurf = newsurf; ( decali < decalcount ) && (!surf ||( decalsurf != surf )); decalsurf = decalsurf->info->lightmapchain )\n\t{\n\t\tfor( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext )\n\t\t{\n\t\t\tgl_texture_t *glt;\n\n\t\t\tif( !pdecal->texture )\n\t\t\t\tcontinue;\n\n\t\t\tglt = R_GetTexture( pdecal->texture );\n\n\t\t\tGL_Bind( mtst.tmu_gl, pdecal->texture );\n\n\t\t\t// normal HL decal with alpha-channel\n\t\t\tif( glt->flags & TF_HAS_ALPHA )\n\t\t\t{\n\t\t\t\t// draw transparent decals with GL_MODULATE\n\t\t\t\tif( glt->fogParams[3] > 230 )\n\t\t\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\t\t\telse pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\t\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// color decal like detail texture. Base color is 127 127 127\n\t\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\t\t\tpglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );\n\t\t\t}\n\n\t\t\tpglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] );\n\t\t\tdecali++;\n\t\t}\n\t\tnewsurf = surf;\n\n\t}\n\n#if SPARSE_DECALS_UPLOAD\n\tif( vbos.decal_dlight_vbo )\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB );\n#endif\n\n\t// restore states pointers for next dynamic lightmap\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglDepthMask( GL_TRUE );\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\tif( RI.currententity->curstate.rendermode == kRenderTransAlpha )\n\t\tpglEnable( GL_ALPHA_TEST );\n\tR_SetDecalMode( false );\n}\n\nstatic void R_FlushDlights( vboarray_t *vbo, int min_index, int max_index, int dlightindex, vboindex_t *dlightarray )\n{\n\tif( max_index == 0 )\n\t\treturn;\n\tif( vbos.dlight_vbo )\n\t{\n#ifndef MINIMIZE_UPLOAD\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), vbos.dlight_tc + min_index, GL_STREAM_DRAW_ARB );\n#endif\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );\n\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ),  (void*)(min_index* sizeof( vbovertex_t ) + offsetof(vbovertex_t,pos)) );\n\t\tGL_SelectTexture( mtst.tmu_gl );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ),  (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) );\n\t\tif( mtst.details_enabled && mtst.tmu_dt != -1 )\n\t\t{\n\t\t\tGL_SelectTexture( mtst.tmu_dt );\n\t\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ),  (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) );\n\t\t}\n\n\t}\n\t//GL_SelectTexture( mtst.tmu_lm );\n\tGL_Bind( mtst.tmu_lm, tr.dlightTexture );\n#ifdef DISCARD_DLIGHTS\n\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, BLOCK_SIZE, BLOCK_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );\n#endif\n\tLM_UploadDynamicBlock();\n#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes\n\tif( pglDrawRangeElements )\n\t\tpglDrawRangeElements( GL_TRIANGLES, min_index, max_index, dlightindex, GL_VBOINDEX_TYPE, dlightarray );\n\telse\n#endif\n\tpglDrawElements( GL_TRIANGLES, dlightindex, GL_VBOINDEX_TYPE, dlightarray );\n}\n\nstatic void R_AddSurfaceDecalsDlight( msurface_t *surf, int *pdecalcount )\n{\n\tdecal_t *pdecal;\n\tint decalcount = *pdecalcount;\n#if SPARSE_DECALS_UPLOAD\n\tif( decalcount == 0 )\n\t{\n\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );\n\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB );\n\t}\n#endif\n\tfor( pdecal = surf->pdecals; pdecal; pdecal = pdecal->pnext )\n\t{\n\t\tint decalindex = pdecal - &gDecalPool[0];\n\t\tint numVerts = vbos.decaldata->decals[decalindex].numVerts;\n\t\tint i;\n\n\t\tif( numVerts == -1 )\n\t\t{\n\t\t\t// build decal array\n\t\t\tfloat *v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts );\n\n\t\t\tfor( i = 0; i < numVerts; i++, v += VERTEXSIZE )\n\t\t\t{\n\t\t\t\tVectorCopy( v, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos );\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = v[3];\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = v[4];\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = v[5] - ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// copy from vbo\n\t\t\tfor( i = 0; i < numVerts; i++ )\n\t\t\t{\n\t\t\t\tVectorCopy( vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].pos, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos );\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[0];\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[1];\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[0] - ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t\tvbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[1] - ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t}\n\t\t}\n#if SPARSE_DECALS_UPLOAD\n\t\tif( vbos.dlight_vbo )\n\t\t{\n\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );\n\t\t\tpglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * decalcount * DECAL_VERTS_MAX, sizeof( vbovertex_t )* numVerts, vbos.decal_dlight + decalcount * DECAL_VERTS_MAX );\n\t\t}\n#endif\n\n\t\tvbos.decal_numverts[decalcount] = numVerts;\n\t\tdecalcount++;\n\t}\n\t*pdecalcount = decalcount;\n}\n\n\nstatic void R_DrawVBODlights( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap )\n{\n\t// draw dlights and dlighted decals\n\tif( vbotex->dlightchain )\n\t{\n\t\tvboindex_t *dlightarray = vbos.dlight_index; // preallocated array\n\t\tunsigned int dlightindex = 0;\n\t\tmsurface_t *surf, *newsurf;\n\t\tint decalcount = 0;\n\t\tint min_index = 65536;\n\t\tint max_index = 0;\n\n\t\tR_SetupVBOArrayDlight( vbo, texture );\n\n\t\t// clear the block\n\t\tLM_InitBlock();\n\n\n\t\tif( vbos.dlight_vbo )\n\t\t{\n\t\t// calculate minimum indexbase\n\t\t\tfor( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain )\n\t\t\t{\n\t\t\t\tuint indexbase = vbos.surfdata[((char*)surf - (char*)WORLDMODEL->surfaces) / sizeof( *surf )].startindex;\n\t\t\t\tif(min_index > indexbase)\n\t\t\t\t\tmin_index = indexbase;\n#ifdef MINIMIZE_UPLOAD\n\t\t\t\tif( max_index < indexbase + surf->polys->numverts )\n\t\t\t\t\tmax_index = indexbase + surf->polys->numverts;\n#endif\n\t\t\t}\n#ifdef MINIMIZE_UPLOAD\n\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB );\n#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmin_index = 0;\n\t\t\tmax_index = vbo->array_len;\n\t\t}\n\n\t\t// accumulate indexes for every dlighted surface until dlight block full\n\t\tfor( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain )\n\t\t{\n\t\t\tint\tsmax, tmax;\n\t\t\tbyte\t*base;\n\t\t\tuint indexbase = vbos.surfdata[((char*)surf - (char*)WORLDMODEL->surfaces) / sizeof( *surf )].startindex;\n\t\t\tuint index;\n\t\t\tmextrasurf_t *info; // this stores current dlight offset\n\t\t\tdecal_t *pdecal;\n\t\t\tint sample_size;\n\n\t\t\tinfo = surf->info;\n\t\t\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\t\t\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\t\t\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\n\n\t\t\t// find space for this surface and get offsets\n\t\t\tif( LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t ))\n\t\t\t{\n\t\t\t\tbase = gl_lms.lightmap_buffer;\n\t\t\t\tbase += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4;\n\n\t\t\t\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\n\n\t\t\t\t// out of free block space. Draw all generated index array and clear it\n\t\t\t\t// upload already generated block\n\t\t\t\tR_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray );\n\n\t\t\t\tR_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) );\n#ifdef MINIMIZE_UPLOAD\n\t\t\t\t// invalidate buffer to prevent blocking on SubData\n\t\t\t\tif( vbos.dlight_vbo )\n\t\t\t\t{\n\t\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\t\t\t\tpglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB );\n\t\t\t\t}\n#else\n\t\t\t\tif( vbos.dlight_vbo )\n\t\t\t\t\tmax_index = 0;\n#endif\n\n\t\t\t\t// draw decals that lighted with this lightmap\n\t\t\t\tif( decalcount )\n\t\t\t\t\tR_DrawDlightedDecals( vbo, newsurf, surf, decalcount, texture );\n\t\t\t\tdecalcount = 0;\n\t\t\t\tR_SetupVBOArrayDlight( vbo, texture );\n\n\t\t\t\t// clear the block\n\t\t\t\tLM_InitBlock();\n\t\t\t\tdlightindex = 0;\n\n\t\t\t\t// try upload the block now\n\t\t\t\tif( !LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t ))\n\t\t\t\t\tgEngfuncs.Host_Error( \"%s: full\\n\", __func__ );\n\n\t\t\t\tbase = gl_lms.lightmap_buffer;\n\t\t\t\tbase += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4;\n\n\t\t\t\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );\n\t\t\t}\n\n\t\t\t// build index and texcoords arrays\n\t\t\tvbos.dlight_tc[indexbase][0] = surf->polys->verts[0][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\tvbos.dlight_tc[indexbase][1] = surf->polys->verts[0][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\tvbos.dlight_tc[indexbase + 1][0] = surf->polys->verts[1][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\tvbos.dlight_tc[indexbase + 1][1] = surf->polys->verts[1][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );\n\n\t\t\tfor( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ )\n\t\t\t{\n\t\t\t\tdlightarray[dlightindex++] = indexbase - min_index;\n\t\t\t\tdlightarray[dlightindex++] = index - 1 - min_index;\n\t\t\t\tdlightarray[dlightindex++] = index - min_index;\n\t\t\t\tvbos.dlight_tc[index][0] = surf->polys->verts[index - indexbase][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t\tvbos.dlight_tc[index][1] = surf->polys->verts[index - indexbase][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );\n\t\t\t}\n#ifndef MINIMIZE_UPLOAD\n\t\t\tif( max_index < indexbase + surf->polys->numverts )\n\t\t\t\tmax_index = indexbase + surf->polys->numverts;\n#else\n\t\t\tif( vbos.dlight_vbo )\n\t\t\t{\n\t\t\t\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );\n\t\t\t\tpglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (indexbase - min_index), sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase );\n\t\t\t}\n#endif\n\n\t\t\t// if surface has decals, build decal array\n\t\t\tR_AddSurfaceDecalsDlight( surf, &decalcount );\n\t\t\t//info->dlight_s = info->dlight_t = 0;\n\t\t}\n\n\t\tif( dlightindex )\n\t\t{\n\t\t\tR_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray );\n\t\t\tR_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) );\n\n\t\t\t// draw remaining decals\n\t\t\tif( decalcount )\n\t\t\t{\n\t\t\t\tR_DrawDlightedDecals( vbo, newsurf, NULL, decalcount, texture );\n\t\t\t}\n\t\t}\n\n\t\tif( vbos.dlight_vbo )\n\t\t{\n\t\t\t// invalidate state to reset vertex array offset\n\t\t\tvboarray.astate = VBO_ARRAY_NONE;\n\t\t\tvboarray.tstate = VBO_TEXTURE_NONE;\n\t\t\tvboarray.lstate = VBO_LIGHTMAP_NONE;\n\t\t}\n\t\tR_SetupVBOArrayStatic( vbo, true, true );\n\t\tR_SetupVBOTexture( texture, vboarray.itexture );\n\n\t\t// prepare to next frame\n\t\tvbotex->dlightchain = NULL;\n\t}\n}\n\n\n\n/*\n=====================\nR_DrawLightmappedVBO\n\nDraw array for given vbotexture_t. build and draw dynamic lightmaps if present\n=====================\n*/\nstatic void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap, qboolean skiplighting )\n{\n\tif( vbotex->curindex )\n\t{\n#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes\n\t\tif( pglDrawRangeElements )\n\t\t\tpglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray );\n\t\telse\n#endif\n\t\tpglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray );\n\n\t\t// draw debug lines\n\t\tif( gl_wireframe.value && !skiplighting )\n\t\t{\n\t\t\tR_SetDecalMode( true );\n\t\t\tpglDisable( GL_TEXTURE_2D );\n\t\t\tGL_SelectTexture( XASH_TEXTURE0 );\n\t\t\tpglDisable( GL_TEXTURE_2D );\n\t\t\tpglDisable( GL_DEPTH_TEST );\n#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes\n\t\t\tif( pglDrawRangeElements )\n\t\t\t\tpglDrawRangeElements( GL_LINES, 0, vbo->array_len, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray );\n\t\t\telse\n#endif\n\t\t\t\tpglDrawElements( GL_LINES, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray );\n\t\t\tpglEnable( GL_DEPTH_TEST );\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\t\t\tGL_SelectTexture( XASH_TEXTURE1 );\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\t\t\tR_SetDecalMode( false );\n\t\t}\n\t}\n\n\t//Msg( \"%d %d %d\\n\", vbo->array_len, vbotex->len, lightmap );\n\tif( skiplighting )\n\t{\n\t\tvbotex->curindex = 0;\n\t\tvbotex->dlightchain = NULL;\n\t\treturn;\n\t}\n\n\tR_DrawVBODlights( vbo, vbotex, texture, lightmap );\n\n\tR_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false, 0 );\n\t// prepare to next frame\n\tvbotex->curindex = 0;\n}\n\nstatic void R_SetupVBOArrayDecal( qboolean drawlightmap )\n{\n\t// prepare for decal draw\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );\n\t// Set pointers to vbodecaldata->decalvbo\n\tif( drawlightmap )\n\t{\n\t\tGL_SelectTexture( mtst.tmu_lm );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );\n\t\tGL_SelectTexture( mtst.tmu_gl );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );\n\t\tpglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) );\n\t\t/*if( mtst.details_enabled && mtst.tmu_dt != -1 )\n\t\t{\n\t\t\tGL_Bind( mtst.tmu_dt, tr.whiteTexture );\n\t\t}*/\n\t}\n\tR_SetDecalMode( true );\n\tvboarray.astate = VBO_ARRAY_DECAL;\n\tvboarray.tstate = VBO_TEXTURE_DECAL;\n\n}\n\nstatic void R_SetupVBOArrayDecalDyn( qboolean drawlightmap, float *v )\n{\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\tpglVertexPointer( 3, GL_FLOAT, VERTEXSIZE * 4, v );\n\tpglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 3 );\n\tif( drawlightmap )\n\t{\n\t\tGL_SelectTexture( mtst.tmu_lm );\n\t\tpglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 5 );\n\t}\n\tR_SetDecalMode( true );\n\tvboarray.astate = VBO_ARRAY_DECAL;\n\tvboarray.tstate = VBO_TEXTURE_DECAL;\n\n}\n\nstatic void R_DrawStaticDecals( vboarray_t *vbo, qboolean drawlightmap, int ilightmap )\n{\n\tint k = ilightmap;\n\tmsurface_t *lightmapchain;\n\n\tpglDepthMask( GL_FALSE );\n\tpglEnable( GL_BLEND );\n\tpglEnable( GL_POLYGON_OFFSET_FILL );\n\n\tif( RI.currententity->curstate.rendermode == kRenderTransAlpha )\n\t\tpglDisable( GL_ALPHA_TEST );\n\n\tR_SetupVBOArrayDecal( drawlightmap );\n\n\t// all surfaces having decals and this lightmap\n\tfor( lightmapchain = vbos.decaldata->lm[k]; lightmapchain; lightmapchain = lightmapchain->info->lightmapchain )\n\t{\n\t\tdecal_t *pdecal;\n\n\t\t// all decals of surface\n\t\tfor( pdecal = lightmapchain->pdecals; pdecal; pdecal = pdecal->pnext )\n\t\t{\n\t\t\tgl_texture_t *glt;\n\t\t\tint decalindex = pdecal - &gDecalPool[0];\n\n\t\t\tif( !pdecal->texture )\n\t\t\t\tcontinue;\n\n\t\t\tglt = R_GetTexture( pdecal->texture );\n\n\t\t\tGL_Bind( mtst.tmu_gl, pdecal->texture );\n\n\t\t\t// normal HL decal with alpha-channel\n\t\t\tif( glt->flags & TF_HAS_ALPHA )\n\t\t\t{\n\t\t\t\t// draw transparent decals with GL_MODULATE\n\t\t\t\tif( glt->fogParams[3] > 230 )\n\t\t\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\t\t\telse pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\t\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// color decal like detail texture. Base color is 127 127 127\n\t\t\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\t\t\tpglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );\n\t\t\t}\n\n\t\t\tif( vbos.decaldata->decals[decalindex].numVerts == -1 )\n\t\t\t{\n\t\t\t\tint numVerts;\n\t\t\t\tfloat *v;\n\n\t\t\t\tv = R_DecalSetupVerts( pdecal, lightmapchain, pdecal->texture, &numVerts );\n\n\t\t\t\t// to many verts to keep in sparse array, so build it now\n\t\t\t\tR_SetupVBOArrayDecalDyn( drawlightmap, v );\n\n\t\t\t\tpglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts );\n\n\t\t\t\tR_SetupVBOArrayDecal( drawlightmap );\n\t\t\t}\n\t\t\telse // just draw VBO\n\t\t\t\tpglDrawArrays( GL_TRIANGLE_FAN, decalindex * DECAL_VERTS_CUT, vbos.decaldata->decals[decalindex].numVerts );\n\t\t}\n\t}\n\n\t// prepare for next frame\n\tvbos.decaldata->lm[k] = NULL;\n\n\t// prepare for next texture\n\tpglDepthMask( GL_TRUE );\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_POLYGON_OFFSET_FILL );\n\n\tR_SetupVBOArrayStatic( vbo, drawlightmap, true );\n\n\tif( RI.currententity->curstate.rendermode == kRenderTransAlpha )\n\t\tpglEnable( GL_ALPHA_TEST );\n}\n\nstatic void R_ClearVBOState( qboolean drawlightmap, qboolean drawtextures )\n{\n\t// restore states\n\tR_DisableDetail();\n\n\tif( drawlightmap )\n\t{\n\t\t// reset states\n\t\tGL_Bind( XASH_TEXTURE1, tr.defaultTexture ); // force reset tmu in case we have only one lightmap\n\t\tpglDisableClientState( GL_TEXTURE_COORD_ARRAY );\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tif( drawtextures )\n\t\t{\n\t\t\tGL_SelectTexture( XASH_TEXTURE0 );\n\t\t\tpglEnable( GL_TEXTURE_2D );\n\t\t}\n\t}\n\n\tif( drawtextures )\n\t\tpglDisableClientState( GL_TEXTURE_COORD_ARRAY );\n\n\n\tpglDisableClientState( GL_VERTEX_ARRAY );\n\tpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );\n\n\tvboarray.astate = VBO_ARRAY_NONE;\n\tvboarray.tstate = VBO_TEXTURE_NONE;\n\tvboarray.lstate = VBO_LIGHTMAP_NONE;\n\tvboarray.itexture = 0;\n\tmtst.lm = 0;\n}\n\n\n/*\n=====================\nR_DrawVBO\n\nDraw generated index arrays\n=====================\n*/\nvoid R_DrawVBO( qboolean drawlightmap, qboolean drawtextures )\n{\n\tint numtextures = WORLDMODEL->numtextures;\n\tint numlightmaps =  gl_lms.current_lightmap_texture;\n\tint k;\n\tvboarray_t *vbo = vbos.arraylist;\n\n\tif( !R_HasGeneratedVBO() || !R_HasEnabledVBO() )\n\t\treturn;\n\n\tGL_SetupFogColorForSurfacesEx( 1, 0.5f, false );\n\n\tR_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures );\n\tmtst.skiptexture = !drawtextures;\n\tmtst.tmu_dt = glConfig.max_texture_units > 2 && r_vbo_detail.value == 2 ? XASH_TEXTURE2 : -1;\n\n\t// setup limits\n\tif( vbos.minlightmap > vbos.minarraysplit_lm )\n\t\tvbos.minlightmap = vbos.minarraysplit_lm;\n\tif( vbos.maxlightmap < vbos.maxarraysplit_lm )\n\t\tvbos.maxlightmap = vbos.maxarraysplit_lm;\n\tif( vbos.maxlightmap > numlightmaps )\n\t\tvbos.maxlightmap = numlightmaps;\n\tif( vbos.mintexture > vbos.minarraysplit_tex )\n\t\tvbos.mintexture = vbos.minarraysplit_tex;\n\tif( vbos.maxtexture < vbos.maxarraysplit_tex )\n\t\tvbos.maxtexture = vbos.maxarraysplit_tex;\n\tif( vbos.maxtexture > numtextures )\n\t\tvbos.maxtexture = numtextures;\n\n\tfor( k = vbos.minlightmap; k < vbos.maxlightmap; k++ )\n\t{\n\t\tint j;\n\n\t\tif( drawlightmap )\n\t\t{\n\t\t\tGL_Bind( mtst.tmu_lm, mtst.lm = tr.lightmapTextures[k] );\n\t\t}\n\n\t\tfor( j = vbos.mintexture; j < vbos.maxtexture; j++ )\n\t\t{\n\t\t\tvbotexture_t *vbotex = &vbos.textures[k * numtextures + j];\n\t\t\ttexture_t *tex = NULL;\n\t\t\tif( !vbotex->vboarray )\n\t\t\t\tcontinue;\n\n\t\t\t// ASSERT( vbotex->vboarray == vbo );\n\t\t\tif( vbotex->vboarray != vbo )\n\t\t\t\tcontinue;\n\n\t\t\tif( vbotex->curindex || vbotex->dlightchain )\n\t\t\t{\n\t\t\t\t// draw textures static lightmap first\n\t\t\t\tif( drawtextures )\n\t\t\t\t\ttex = R_SetupVBOTexture( NULL, j );\n\n\t\t\t\tR_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap );\n\t\t\t}\n\n\t\t\t// if we need to switch to next array (only if map has >65536 vertices)\n\t\t\twhile( vbotex->next )\n\t\t\t{\n\n\t\t\t\tvbotex = vbotex->next;\n\t\t\t\tvbo = vbo->next;\n\t\t\t\tvboarray.astate = VBO_ARRAY_NONE; // invalidate\n\t\t\t\tvboarray.tstate = VBO_TEXTURE_NONE;\n\t\t\t\tvboarray.lstate = VBO_LIGHTMAP_NONE;\n\n\t\t\t\tif( drawtextures )\n\t\t\t\t{\n\t\t\t\t\ttex = R_SetupVBOTexture( tex, j );\n\t\t\t\t}\n\n\t\t\t\t// update texcoord pointers\n\t\t\t\tR_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures );\n\n\t\t\t\tif( drawlightmap )\n\t\t\t\t{\n\t\t\t\t\tGL_Bind( mtst.tmu_lm, tr.lightmapTextures[k] );\n\t\t\t\t}\n\n\t\t\t\t// draw new array\n\t\t\t\tif( (vbotex->curindex || vbotex->dlightchain) )\n\t\t\t\t\tR_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap );\n\t\t\t\tvboarray.astate = VBO_ARRAY_NONE; // invalidate\n\t\t\t\tvboarray.tstate = VBO_TEXTURE_NONE;\n\t\t\t}\n\t\t}\n\n\t\tif( drawtextures && drawlightmap && vbos.decaldata->lm[k] )\n\t\t{\n\t\t\tR_DrawStaticDecals( vbo, drawlightmap, k );\n\t\t}\n\t\tif( !drawtextures || !drawlightmap )\n\t\t\tvbos.decaldata->lm[k] = NULL;\n\t}\n\t// ASSERT( !vbo->next );\n\tR_ClearVBOState( drawlightmap, drawtextures );\n\n\tmtst.details_enabled = false;\n\n\tvbos.minlightmap = MAX_LIGHTMAPS;\n\tvbos.maxlightmap = 0;\n\tvbos.mintexture = INT_MAX;\n\tvbos.maxtexture = 0;\n}\n\nqboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmap )\n{\n\tconst int idx = surf - WORLDMODEL->surfaces;\n\tvbotexture_t *vbotex;\n\tint texturenum;\n\n\tif( !R_HasGeneratedVBO() || !R_HasEnabledVBO( ))\n\t\treturn false;\n\n\t// find vbotexture_t assotiated with this surface\n\tvbotex = vbos.surfdata[idx].vbotexture;\n\ttexturenum = vbos.surfdata[idx].texturenum;\n\n\tif( !vbotex )\n\t\treturn false;\n\n\tif( !surf->polys )\n\t\treturn true;\n\n\tif( vbos.maxlightmap < surf->lightmaptexturenum + 1 )\n\t\tvbos.maxlightmap = surf->lightmaptexturenum + 1;\n\tif( vbos.minlightmap > surf->lightmaptexturenum )\n\t\tvbos.minlightmap = surf->lightmaptexturenum;\n\tif( vbos.maxtexture < texturenum + 1 )\n\t\tvbos.maxtexture = texturenum + 1;\n\tif( vbos.mintexture > texturenum )\n\t\tvbos.mintexture = texturenum;\n\n\tbuildlightmap &= !r_fullbright->value && !!WORLDMODEL->lightdata;\n\n\tif( surf->texinfo != NULL )\n\t{\n\t\t// fullbright textures are rare, no sense to build VBO for them\n\t\tR_RenderFullbrightForSurface( surf, surf->texinfo->texture );\n\n\t\t// draw details in regular way\n\t\tif( r_vbo_detail.value )\n\t\t\tR_RenderDetailsForSurface( surf, surf->texinfo->texture );\n\t}\n\n\tif( buildlightmap && R_CheckLightMap( surf ))\n\t{\n\t\t// every vbotex has own lightmap chain (as we sorted if by textures to use multitexture)\n\t\tsurf->info->lightmapchain = vbotex->dlightchain;\n\t\tvbotex->dlightchain = surf;\n\t}\n\telse\n\t{\n\t\tuint indexbase = vbos.surfdata[idx].startindex;\n\t\tuint index;\n\n\t\t// GL_TRIANGLE_FAN: 0 1 2 0 2 3 0 3 4 ...\n\t\tfor( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ )\n\t\t{\n\t\t\tvbotex->indexarray[vbotex->curindex++] = indexbase;\n\t\t\tvbotex->indexarray[vbotex->curindex++] = index - 1;\n\t\t\tvbotex->indexarray[vbotex->curindex++] = index;\n\t\t}\n\n\t\t// if surface has decals, add it to decal lightmapchain\n\t\tif( surf->pdecals )\n\t\t{\n\t\t\tsurf->info->lightmapchain = vbos.decaldata->lm[vbotex->lightmaptexturenum];\n\t\t\tvbos.decaldata->lm[vbotex->lightmaptexturenum] = surf;\n\t\t}\n\t}\n\n\t// now this path does not draw wapred surfaces, so count it as one poly\n\tr_stats.c_world_polys++;\n\n\treturn true;\n}\n\n/*\n=============================================================\n\n\tWORLD MODEL\n\n=============================================================\n*/\n/*\n================\nR_RecursiveWorldNode\n================\n*/\nstatic void R_RecursiveWorldNode( mnode_t *node, uint clipflags )\n{\n\tint\t\ti, clipped;\n\tmsurface_t\t*surf, **mark;\n\tmleaf_t\t\t*pleaf;\n\tint\t\tc, side;\n\tfloat\t\tdot;\n\tmnode_t *children[2];\n\tint numsurfaces, firstsurface;\n\nloc0:\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn; // hit a solid leaf\n\n\tif( node->visframe != tr.visframecount )\n\t\treturn;\n\n\tif( clipflags && !r_nocull.value )\n\t{\n\t\tfor( i = 0; i < 6; i++ )\n\t\t{\n\t\t\tconst mplane_t\t*p = &RI.frustum.planes[i];\n\n\t\t\tif( !FBitSet( clipflags, BIT( i )))\n\t\t\t\tcontinue;\n\n\t\t\tclipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p );\n\t\t\tif( clipped == 2 ) return;\n\t\t\tif( clipped == 1 ) ClearBits( clipflags, BIT( i ));\n\t\t}\n\t}\n\n\t// if a leaf node, draw stuff\n\tif( node->contents < 0 )\n\t{\n\t\tpleaf = (mleaf_t *)node;\n\n\t\tmark = pleaf->firstmarksurface;\n\t\tc = pleaf->nummarksurfaces;\n\n\t\tif( c )\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\t(*mark)->visframe = tr.framecount;\n\t\t\t\tmark++;\n\t\t\t} while( --c );\n\t\t}\n\n\t\t// deal with model fragments in this leaf\n\t\tif( pleaf->efrags )\n\t\t\tgEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );\n\n\t\tr_stats.c_world_leafs++;\n\t\treturn;\n\t}\n\n\t// node is just a decision point, so go down the apropriate sides\n\n\t// find which side of the node we are on\n\tdot = PlaneDiff( tr.modelorg, node->plane );\n\tside = (dot >= 0.0f) ? 0 : 1;\n\n\t// recurse down the children, front side first\n\tnode_children( children, node, WORLDMODEL );\n\tR_RecursiveWorldNode( children[side], clipflags );\n\n\tfirstsurface = node_firstsurface( node, WORLDMODEL );\n\tnumsurfaces = node_numsurfaces( node, WORLDMODEL );\n\n\t// draw stuff\n\tfor( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ )\n\t{\n\t\tif( R_CullSurface( surf, &RI.frustum, clipflags ))\n\t\t\tcontinue;\n\n\t\tif( surf->flags & SURF_DRAWSKY )\n\t\t{\n\t\t\t// make sky chain to right clip the skybox\n\t\t\tsurf->texturechain = skychain;\n\t\t\tskychain = surf;\n\t\t}\n\t\telse if( !R_AddSurfToVBO( surf, true ) )\n\t\t{\n\t\t\tsurf->texturechain = surf->texinfo->texture->texturechain;\n\t\t\tsurf->texinfo->texture->texturechain = surf;\n\t\t}\n\t}\n\n\t// recurse down the back side\n\tnode = children[!side];\n\tgoto loc0;\n}\n\n/*\n================\nR_CullNodeTopView\n\ncull node by user rectangle (simple scissor)\n================\n*/\nstatic qboolean R_CullNodeTopView( mnode_t *node )\n{\n\tvec2_t\tdelta, size;\n\tvec3_t\tcenter, half;\n\n\t// build the node center and half-diagonal\n\tVectorAverage( node->minmaxs, node->minmaxs + 3, center );\n\tVectorSubtract( node->minmaxs + 3, center, half );\n\n\t// cull against the screen frustum or the appropriate area's frustum.\n\tVector2Subtract( center, world_orthocenter, delta );\n\tVector2Add( half, world_orthohalf, size );\n\n\treturn ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] );\n}\n\n/*\n================\nR_DrawTopViewLeaf\n================\n*/\nstatic void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags )\n{\n\tmsurface_t\t**mark, *surf;\n\tint\t\ti;\n\n\tfor( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ )\n\t{\n\t\tsurf = *mark;\n\n\t\t// don't process the same surface twice\n\t\tif( surf->visframe == tr.framecount )\n\t\t\tcontinue;\n\n\t\tsurf->visframe = tr.framecount;\n\n\t\tif( R_CullSurface( surf, &RI.frustum, clipflags ))\n\t\t\tcontinue;\n\n\t\tif(!( surf->flags & SURF_DRAWSKY ))\n\t\t{\n\t\t\tsurf->texturechain = surf->texinfo->texture->texturechain;\n\t\t\tsurf->texinfo->texture->texturechain = surf;\n\t\t}\n\t}\n\n\t// deal with model fragments in this leaf\n\tif( pleaf->efrags )\n\t\tgEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );\n\n\tr_stats.c_world_leafs++;\n}\n\n/*\n================\nR_DrawWorldTopView\n================\n*/\nstatic void R_DrawWorldTopView( mnode_t *node, uint clipflags )\n{\n\tint\t\ti, c, clipped;\n\tmsurface_t\t*surf;\n\n\tdo\n\t{\n\t\tmnode_t *children[2];\n\t\tint numsurfaces, firstsurface;\n\n\t\tif( node->contents == CONTENTS_SOLID )\n\t\t\treturn;\t// hit a solid leaf\n\n\t\tif( node->visframe != tr.visframecount )\n\t\t\treturn;\n\n\t\tif( clipflags && !r_nocull.value )\n\t\t{\n\t\t\tfor( i = 0; i < 6; i++ )\n\t\t\t{\n\t\t\t\tconst mplane_t\t*p = &RI.frustum.planes[i];\n\n\t\t\t\tif( !FBitSet( clipflags, BIT( i )))\n\t\t\t\t\tcontinue;\n\n\t\t\t\tclipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p );\n\t\t\t\tif( clipped == 2 ) return;\n\t\t\t\tif( clipped == 1 ) ClearBits( clipflags, BIT( i ));\n\t\t\t}\n\t\t}\n\n\t\t// cull against the screen frustum or the appropriate area's frustum.\n\t\tif( R_CullNodeTopView( node ))\n\t\t\treturn;\n\n\t\t// if a leaf node, draw stuff\n\t\tif( node->contents < 0 )\n\t\t{\n\t\t\tR_DrawTopViewLeaf( (mleaf_t *)node, clipflags );\n\t\t\treturn;\n\t\t}\n\n\t\t// draw stuff\n\t\tnumsurfaces = node_numsurfaces( node, WORLDMODEL );\n\t\tfirstsurface = node_firstsurface( node, WORLDMODEL );\n\n\t\tfor( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ )\n\t\t{\n\t\t\t// don't process the same surface twice\n\t\t\tif( surf->visframe == tr.framecount )\n\t\t\t\tcontinue;\n\n\t\t\tsurf->visframe = tr.framecount;\n\n\t\t\tif( R_CullSurface( surf, &RI.frustum, clipflags ))\n\t\t\t\tcontinue;\n\n\t\t\tif(!( surf->flags & SURF_DRAWSKY ))\n\t\t\t{\n\t\t\t\tsurf->texturechain = surf->texinfo->texture->texturechain;\n\t\t\t\tsurf->texinfo->texture->texturechain = surf;\n\t\t\t}\n\t\t}\n\n\t\t// recurse down both children, we don't care the order...\n\t\tnode_children( children, node, WORLDMODEL );\n\t\tR_DrawWorldTopView( children[0], clipflags );\n\t\tnode = children[1];\n\t} while( node );\n}\n\n/*\n=============\nR_DrawTriangleOutlines\n=============\n*/\nstatic void R_DrawTriangleOutlines( void )\n{\n\tint\t\ti, j;\n\tmsurface_t\t*surf;\n\tglpoly2_t\t\t*p;\n\tfloat\t\t*v;\n\n\tif( !gl_wireframe.value )\n\t\treturn;\n\n\tpglDisable( GL_TEXTURE_2D );\n\tpglDisable( GL_DEPTH_TEST );\n\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\tpglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );\n\n\t// render static surfaces first\n\tfor( i = 0; i < MAX_LIGHTMAPS; i++ )\n\t{\n\t\tfor( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain )\n\t\t{\n\t\t\tp = surf->polys;\n\t\t\tfor( ; p != NULL; p = p->chain )\n\t\t\t{\n\t\t\t\tpglBegin( GL_POLYGON );\n\t\t\t\tv = p->verts[0];\n\t\t\t\tfor( j = 0; j < p->numverts; j++, v += VERTEXSIZE )\n\t\t\t\t\tpglVertex3fv( v );\n\t\t\t\tpglEnd ();\n\t\t\t}\n\t\t}\n\t}\n\n\t// render surfaces with dynamic lightmaps\n\tfor( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain )\n\t{\n\t\tp = surf->polys;\n\n\t\tfor( ; p != NULL; p = p->chain )\n\t\t{\n\t\t\tpglBegin( GL_POLYGON );\n\t\t\tv = p->verts[0];\n\t\t\tfor( j = 0; j < p->numverts; j++, v += VERTEXSIZE )\n\t\t\t\tpglVertex3fv( v );\n\t\t\tpglEnd ();\n\t\t}\n\t}\n\n\tpglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );\n\tpglEnable( GL_DEPTH_TEST );\n\tpglEnable( GL_TEXTURE_2D );\n}\n\n/*\n=============\nR_DrawWorld\n=============\n*/\nvoid R_DrawWorld( void )\n{\n\tdouble\tstart, end;\n\n\t// paranoia issues: when gl_renderer is \"0\" we need have something valid for currententity\n\t// to prevent crashing until HeadShield drawing.\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\tif( !RI.currententity )\n\t\treturn;\n\n\tRI.currentmodel = RI.currententity->model;\n\tif( !RI.drawWorld || RI.onlyClientDraw )\n\t\treturn;\n\n\tVectorCopy( RI.cullorigin, tr.modelorg );\n\tmemset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));\n\tmemset( fullbright_surfaces, 0, sizeof( fullbright_surfaces ));\n\tmemset( detail_surfaces, 0, sizeof( detail_surfaces ));\n\n\tgl_lms.dynamic_surfaces = NULL;\n\tpglDisable( GL_ALPHA_TEST );\n\tpglDisable( GL_BLEND );\n\ttr.blend = 1.0f;\n\n\tR_ClearSkyBox ();\n\n\tstart = gEngfuncs.pfnTime();\n\tif( RI.drawOrtho )\n\t\tR_DrawWorldTopView( WORLDMODEL->nodes, RI.frustum.clipFlags );\n\telse R_RecursiveWorldNode( WORLDMODEL->nodes, RI.frustum.clipFlags );\n\tend = gEngfuncs.pfnTime();\n\n\tr_stats.t_world_node = end - start;\n\n\tstart = gEngfuncs.pfnTime();\n\n\tR_DrawTextureChains();\n\n\tif( !ENGINE_GET_PARM( PARM_DEV_OVERVIEW ))\n\t{\n\t\tDrawDecalsBatch();\n\t\tGL_ResetFogColor();\n\t\tR_BlendLightmaps();\n\t\tR_RenderFullbrights();\n\t\tR_RenderDetails( R_HasEnabledVBO() ? 2 : 3 );\n\n\t\tif( skychain )\n\t\t\tR_DrawSkyBox();\n\t}\n\n\tend = gEngfuncs.pfnTime();\n\n\tr_stats.t_world_draw = end - start;\n\ttr.num_draw_decals = 0;\n\tskychain = NULL;\n\n\tR_DrawTriangleOutlines ();\n\n\tgEngfuncs.R_DrawWorldHull();\n}\n\n/*\n===============\nR_MarkLeaves\n\nMark the leaves and nodes that are in the PVS for the current leaf\n===============\n*/\nvoid R_MarkLeaves( void )\n{\n\tqboolean\tnovis = false;\n\tqboolean\tforce = false;\n\tmleaf_t\t*leaf = NULL;\n\tmnode_t\t*node;\n\tvec3_t\ttest;\n\tint\ti;\n\n\tif( !RI.drawWorld ) return;\n\n\tif( FBitSet( r_novis.flags, FCVAR_CHANGED ) || tr.fResetVis )\n\t{\n\t\t// force recalc viewleaf\n\t\tClearBits( r_novis.flags, FCVAR_CHANGED );\n\t\ttr.fResetVis = false;\n\t\tRI.viewleaf = NULL;\n\t}\n\n\tVectorCopy( RI.pvsorigin, test );\n\n\tif( RI.viewleaf != NULL )\n\t{\n\t\t// merge two leafs that can be a crossed-line contents\n\t\tif( RI.viewleaf->contents == CONTENTS_EMPTY )\n\t\t{\n\t\t\tVectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] - 16.0f );\n\t\t\tleaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] + 16.0f );\n\t\t\tleaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes );\n\t\t}\n\n\t\tif(( leaf->contents != CONTENTS_SOLID ) && ( RI.viewleaf != leaf ))\n\t\t\tforce = true;\n\t}\n\n\tif( RI.viewleaf == RI.oldviewleaf && RI.viewleaf != NULL && !force )\n\t\treturn;\n\n\t// development aid to let you run around\n\t// and see exactly where the pvs ends\n\tif( r_lockpvs.value ) return;\n\n\tRI.oldviewleaf = RI.viewleaf;\n\ttr.visframecount++;\n\n\tif( r_novis.value || RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata )\n\t\tnovis = true;\n\n\tgEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis );\n\tif( force && !novis ) gEngfuncs.R_FatPVS( test, REFPVS_RADIUS, RI.visbytes, true, novis );\n\n\tfor( i = 0; i < WORLDMODEL->numleafs; i++ )\n\t{\n\t\tif( CHECKVISBIT( RI.visbytes, i ))\n\t\t{\n\t\t\tnode = (mnode_t *)&WORLDMODEL->leafs[i+1];\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif( node->visframe == tr.visframecount )\n\t\t\t\t\tbreak;\n\t\t\t\tnode->visframe = tr.visframecount;\n\t\t\t\tnode = node->parent;\n\t\t\t} while( node );\n\t\t}\n\t}\n}\n\n/*\n========================\nGL_CreateSurfaceLightmap\n========================\n*/\nstatic void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel )\n{\n\tint\t\tsmax, tmax;\n\tint\t\tsample_size;\n\tmextrasurf_t\t*info = surf->info;\n\tbyte\t\t*base;\n\n\tif( !loadmodel->lightdata )\n\t\treturn;\n\n\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\treturn;\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\n\tif( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))\n\t{\n\t\tLM_UploadBlock( false );\n\t\tLM_InitBlock();\n\n\t\tif( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))\n\t\t\tgEngfuncs.Host_Error( \"%s: full\\n\", __func__ );\n\t}\n\n\tsurf->lightmaptexturenum = gl_lms.current_lightmap_texture;\n\n\tbase = gl_lms.lightmap_buffer;\n\tbase += ( surf->light_t * BLOCK_SIZE + surf->light_s ) * 4;\n\n\tR_SetCacheState( surf );\n\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, false );\n}\n\n/*\n==================\nGL_RebuildLightmaps\n\nRebuilds the lightmap texture\nwhen gamma is changed\n==================\n*/\nvoid GL_RebuildLightmaps( void )\n{\n\tint\ti, j;\n\tmodel_t\t*m;\n\n\tif( !ENGINE_GET_PARM( PARM_CLIENT_ACTIVE ) )\n\t\treturn; // wait for worldmodel\n\n\t// release old lightmaps\n\tfor( i = 0; i < MAX_LIGHTMAPS; i++ )\n\t{\n\t\tif( !tr.lightmapTextures[i] ) break;\n\t\tGL_FreeTexture( tr.lightmapTextures[i] );\n\t}\n\n\tmemset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));\n\tgl_lms.current_lightmap_texture = 0;\n\n\t// setup all the lightstyles\n\tCL_RunLightStyles((lightstyle_t *)ENGINE_GET_PARM( PARM_GET_LIGHTSTYLES_PTR ));\n\n\tLM_InitBlock();\n\n\tfor( i = 0; i < gp_cl->nummodels; i++ )\n\t{\n\t\tif(( m = CL_ModelHandle( i + 1 )) == NULL )\n\t\t\tcontinue;\n\n\t\tif( m->name[0] == '*' || m->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < m->numsurfaces; j++ )\n\t\t\tGL_CreateSurfaceLightmap( m->surfaces + j, m );\n\t}\n\tLM_UploadBlock( false );\n\n\tif( gEngfuncs.drawFuncs->GL_BuildLightmaps )\n\t{\n\t\t// build lightmaps on the client-side\n\t\tgEngfuncs.drawFuncs->GL_BuildLightmaps( );\n\t}\n}\n\n/*\n==================\nGL_BuildLightmaps\n\nBuilds the lightmap texture\nwith all the surfaces from all brush models\n==================\n*/\nvoid GL_BuildLightmaps( void )\n{\n\tint\ti, j, nColinElim = 0;\n\tmodel_t\t*m;\n\n\t// release old lightmaps\n\tfor( i = 0; i < MAX_LIGHTMAPS; i++ )\n\t{\n\t\tif( !tr.lightmapTextures[i] ) break;\n\t\tGL_FreeTexture( tr.lightmapTextures[i] );\n\t}\n\n\tmemset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));\n\tmemset( &RI, 0, sizeof( RI ));\n\n\t// update the lightmap blocksize\n\tif( FBitSet( gp_host->features, ENGINE_LARGE_LIGHTMAPS ) || tr.world->version == QBSP2_VERSION || r_large_lightmaps.value )\n\t\ttr.block_size = BLOCK_SIZE_MAX;\n\telse tr.block_size = BLOCK_SIZE_DEFAULT;\n\n\tskychain = NULL;\n\n\ttr.framecount = tr.visframecount = 1;\t// no dlight cache\n\tgl_lms.current_lightmap_texture = 0;\n\ttr.modelviewIdentity = false;\n\ttr.realframecount = 1;\n\n\t// setup the texture for dlights\n\tR_InitDlightTexture();\n\n\t// setup all the lightstyles\n\tCL_RunLightStyles((lightstyle_t *)ENGINE_GET_PARM( PARM_GET_LIGHTSTYLES_PTR ));\n\n\tLM_InitBlock();\n\n\tfor( i = 0; i < gp_cl->nummodels; i++ )\n\t{\n\t\tif(( m = CL_ModelHandle( i + 1 )) == NULL )\n\t\t\tcontinue;\n\n\t\tif( m->name[0] == '*' || m->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < m->numsurfaces; j++ )\n\t\t{\n\t\t\t// clearing all decal chains\n\t\t\tm->surfaces[j].pdecals = NULL;\n\t\t\tm->surfaces[j].visframe = 0;\n\n\t\t\tGL_CreateSurfaceLightmap( m->surfaces + j, m );\n\n\t\t\tif( m->surfaces[j].flags & SURF_DRAWTURB )\n\t\t\t\tcontinue;\n\n\t\t\tnColinElim += GL_BuildPolygonFromSurface( m, m->surfaces + j );\n\t\t}\n\n\t\t// clearing visframe\n\t\tfor( j = 0; j < m->numleafs; j++ )\n\t\t\tm->leafs[j+1].visframe = 0;\n\t\tfor( j = 0; j < m->numnodes; j++ )\n\t\t\tm->nodes[j].visframe = 0;\n\t}\n\n\tLM_UploadBlock( false );\n\n\tif( gEngfuncs.drawFuncs->GL_BuildLightmaps )\n\t{\n\t\t// build lightmaps on the client-side\n\t\tgEngfuncs.drawFuncs->GL_BuildLightmaps( );\n\t}\n}\n\nvoid GL_InitRandomTable( void )\n{\n\tint\ttu, tv;\n\n\tfor( tu = 0; tu < MOD_FRAMES; tu++ )\n\t{\n\t\tfor( tv = 0; tv < MOD_FRAMES; tv++ )\n\t\t{\n\t\t\trtable[tu][tv] = gEngfuncs.COM_RandomLong( 0, 0x7FFF );\n\t\t}\n\t}\n\n\tgEngfuncs.COM_SetRandomSeed( 0 );\n}\n"
  },
  {
    "path": "ref/gl/gl_sprite.c",
    "content": "/*\ngl_sprite.c - sprite rendering\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"pm_local.h\"\n#include \"sprite.h\"\n#include \"studio.h\"\n#include \"entity_types.h\"\n\n#define GLARE_FALLOFF\t19000.0f\n\nchar\t\tsprite_name[MAX_QPATH];\nchar\t\tgroup_suffix[8];\nstatic uint\tr_texFlags = 0;\nstatic int\tsprite_version;\nfloat\t\tsprite_radius;\n\n/*\n====================\nR_SpriteInit\n\n====================\n*/\nvoid R_SpriteInit( void )\n{\n}\n\n/*\n====================\nR_SpriteLoadFrame\n\nupload a single frame\n====================\n*/\nstatic const byte *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num )\n{\n\tdspriteframe_t\tpinframe;\n\tmspriteframe_t\t*pspriteframe;\n\tint\t\tgl_texturenum = 0;\n\tchar\t\ttexname[128];\n\tint\t\tbytes = 1;\n\n\tmemcpy( &pinframe, pin, sizeof( dspriteframe_t ));\n\n\tif( sprite_version == SPRITE_VERSION_32 )\n\t\tbytes = 4;\n\n\t// build uinque frame name\n\tif( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"#HUD/%s(%s:%i%i).spr\", sprite_name, group_suffix, num / 10, num % 10 );\n\t\tgl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );\n\t}\n\telse\n\t{\n\t\t// partial HD-textures support\n\t\tif( Mod_AllowMaterials( ))\n\t\t{\n\t\t\tif( R_SearchForTextureReplacement( texname, sizeof( texname ), sprite_name, \"materials/%s/%s%i%i.tga\", sprite_name, group_suffix, num / 10, num % 10 ))\n\t\t\t{\n\t\t\t\tgl_texturenum = GL_LoadTexture( texname, NULL, 0, r_texFlags );\n\t\t\t\tR_TextureReplacementReport( sprite_name, gl_texturenum, texname );\n\t\t\t}\n\t\t}\n\n\t\tif( gl_texturenum == 0 )\n\t\t{\n\t\t\tQ_snprintf( texname, sizeof( texname ), \"#%s(%s:%i%i).spr\", sprite_name, group_suffix, num / 10, num % 10 );\n\t\t\tgl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );\n\t\t}\n\t}\n\n\t// setup frame description\n\tpspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));\n\tpspriteframe->width = pinframe.width;\n\tpspriteframe->height = pinframe.height;\n\tpspriteframe->up = pinframe.origin[1];\n\tpspriteframe->left = pinframe.origin[0];\n\tpspriteframe->down = pinframe.origin[1] - pinframe.height;\n\tpspriteframe->right = pinframe.width + pinframe.origin[0];\n\tpspriteframe->gl_texturenum = gl_texturenum;\n\t*ppframe = pspriteframe;\n\n\treturn (( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes );\n}\n\n/*\n====================\nR_SpriteLoadGroup\n\nupload a group frames\n====================\n*/\nstatic const byte *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )\n{\n\tconst dspritegroup_t\t*pingroup;\n\tmspritegroup_t\t*pspritegroup;\n\tconst dspriteinterval_t\t*pin_intervals;\n\tfloat\t\t*poutintervals;\n\tint\t\ti, groupsize, numframes;\n\tconst void\t\t*ptemp;\n\n\tpingroup = (const dspritegroup_t *)pin;\n\tnumframes = pingroup->numframes;\n\n\tgroupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] );\n\tpspritegroup = Mem_Calloc( mod->mempool, groupsize );\n\tpspritegroup->numframes = numframes;\n\n\t*ppframe = (mspriteframe_t *)pspritegroup;\n\tpin_intervals = (const dspriteinterval_t *)(pingroup + 1);\n\tpoutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));\n\tpspritegroup->intervals = poutintervals;\n\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\t*poutintervals = pin_intervals->interval;\n\t\tif( *poutintervals <= 0.0f )\n\t\t\t*poutintervals = 1.0f; // set error value\n\t\tpoutintervals++;\n\t\tpin_intervals++;\n\t}\n\n\tptemp = (const void *)pin_intervals;\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\tptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );\n\t}\n\n\treturn ptemp;\n}\n\n\n/*\n====================\nMod_LoadSpriteModel\n\nload sprite model\n====================\n*/\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags )\n{\n\tconst dsprite_t *pin;\n\tconst short     *numi = NULL;\n\tconst byte      *pframetype;\n\tmsprite_t       *psprite;\n\tint i;\n\n\tpin = buffer;\n\tpsprite = mod->cache.data;\n\n\tif( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )\n\t\tnumi = NULL;\n\telse if( pin->version == SPRITE_VERSION_HL )\n\t\tnumi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t ));\n\n\tr_texFlags = texFlags;\n\tsprite_version = pin->version;\n\tQ_strncpy( sprite_name, mod->name, sizeof( sprite_name ));\n\tCOM_StripExtension( sprite_name );\n\n\tif( numi == NULL )\n\t{\n\t\trgbdata_t\t*pal;\n\n\t\tpal = gEngfuncs.FS_LoadImage( \"#id.pal\", (byte *)&i, 768 );\n\t\tpframetype = ((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1\n\t\tgEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data\n\t}\n\telse if( *numi <= 256 )\n\t{\n\t\tconst byte\t*src = (const byte *)(numi+1);\n\t\trgbdata_t\t*pal;\n\t\tsize_t pal_bytes = *numi * 3;\n\n\t\t// install palette\n\t\tswitch( psprite->texFormat )\n\t\t{\n\t\tcase SPR_INDEXALPHA:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#gradient.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\tcase SPR_ALPHTEST:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#masked.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#texgamma.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\t}\n\n\t\tpframetype = (const byte *)(src + pal_bytes);\n\t\tgEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data\n\t}\n\telse\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s has wrong number of palette colors %i (should be less or equal than 256)\\n\", mod->name, *numi );\n\t\treturn;\n\t}\n\n\tif( mod->numframes < 1 )\n\t\treturn;\n\n\tfor( i = 0; i < mod->numframes; i++ )\n\t{\n\t\tframetype_t frametype;\n\t\tdframetype_t dframetype;\n\n\t\tmemcpy( &dframetype, pframetype, sizeof( dframetype ));\n\t\tframetype = dframetype.type;\n\t\tpsprite->frames[i].type = (spriteframetype_t)frametype;\n\n\t\tswitch( frametype )\n\t\t{\n\t\tcase FRAME_SINGLE:\n\t\t\tQ_strncpy( group_suffix, \"frame\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\tcase FRAME_GROUP:\n\t\t\tQ_strncpy( group_suffix, \"group\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\tcase FRAME_ANGLED:\n\t\t\tQ_strncpy( group_suffix, \"angle\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\t}\n\t\tif( pframetype == NULL ) break; // technically an error\n\t}\n\n\tif( loaded ) *loaded = true;\t// done\n}\n\n/*\n====================\nMod_UnloadSpriteModel\n\nrelease sprite model and frames\n====================\n*/\nvoid Mod_SpriteUnloadTextures( void *data )\n{\n\tmsprite_t *psprite = data;\n\tint i;\n\n\tif( !data )\n\t\treturn;\n\n\t// release all textures\n\tfor( i = 0; i < psprite->numframes; i++ )\n\t{\n\t\tif( !psprite->frames[i].frameptr )\n\t\t\tcontinue;\n\n\t\tif( psprite->frames[i].type == SPR_SINGLE )\n\t\t{\n\t\t\tGL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;\n\t\t\tint j;\n\n\t\t\tfor( j = 0; j < pspritegroup->numframes; j++ )\n\t\t\t{\n\t\t\t\tif( pspritegroup->frames[j] )\n\t\t\t\t\tGL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nR_GetSpriteFrame\n\nassume pModel is valid\n================\n*/\nmspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )\n{\n\tmsprite_t\t\t*psprite;\n\tmspritegroup_t\t*pspritegroup;\n\tmspriteframe_t\t*pspriteframe = NULL;\n\tfloat\t\t*pintervals, fullinterval;\n\tint\t\ti, numframes;\n\tfloat\t\ttargettime;\n\n\tAssert( pModel != NULL );\n\tpsprite = pModel->cache.data;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tif( frame > psprite->numframes )\n\t\t\tgEngfuncs.Con_Printf( S_WARN \"%s: no such frame %d (%s)\\n\", __func__, frame, pModel->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == SPR_SINGLE )\n\t{\n\t\tpspriteframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == SPR_GROUP )\n\t{\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes-1];\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = gp_cl->time - ((int)( gp_cl->time / fullinterval )) * fullinterval;\n\n\t\tfor( i = 0; i < (numframes - 1); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t}\n\t\tpspriteframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == FRAME_ANGLED )\n\t{\n\t\tint\tangleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7;\n\n\t\t// e.g. doom-style sprite monsters\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpspriteframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn pspriteframe;\n}\n\n/*\n================\nR_GetSpriteFrameInterpolant\n\nNOTE: we using prevblending[0] and [1] for holds interval\nbetween frames where are we lerping\n================\n*/\nstatic float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe )\n{\n\tmsprite_t\t\t*psprite;\n\tmspritegroup_t\t*pspritegroup;\n\tint\t\ti, j, numframes, frame;\n\tfloat\t\tlerpFrac, time, jtime, jinterval;\n\tfloat\t\t*pintervals, fullinterval, targettime;\n\tint\t\tm_fDoInterp;\n\n\tpsprite = ent->model->cache.data;\n\tframe = (int)ent->curstate.frame;\n\tlerpFrac = 1.0f;\n\n\t// misc info\n\tm_fDoInterp = (ent->curstate.effects & EF_NOINTERP) ? false : true;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_WARN \"%s: no such frame %d (%s)\\n\", __func__, frame, ent->model->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == FRAME_SINGLE )\n\t{\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_SINGLE )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse lerpFrac = (gp_cl->time - ent->latched.sequencetime) * 11.0f;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tif( ent->latched.prevblending[0] >= psprite->numframes )\n\t\t{\n\t\t\t// reset interpolation on change model\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\tlerpFrac = 0.0f;\n\t\t}\n\n\t\t// get the interpolated frames\n\t\tif( oldframe ) *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr;\n\t\tif( curframe ) *curframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == FRAME_GROUP )\n\t{\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes-1];\n\t\tjinterval = pintervals[1] - pintervals[0];\n\t\ttime = gp_cl->time;\n\t\tjtime = 0.0f;\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = time - ((int)(time / fullinterval)) * fullinterval;\n\n\t\t// LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0,\n\t\t// i instead measure the time of the first frame, hoping it is consistent\n\t\tfor( i = 0, j = numframes - 1; i < (numframes - 1); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t\tj = i;\n\t\t\tjinterval = pintervals[i] - jtime;\n\t\t\tjtime = pintervals[i];\n\t\t}\n\n\t\tif( m_fDoInterp )\n\t\t\tlerpFrac = (targettime - jtime) / jinterval;\n\t\telse j = i; // no lerping\n\n\t\t// get the interpolated frames\n\t\tif( oldframe ) *oldframe = pspritegroup->frames[j];\n\t\tif( curframe ) *curframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == FRAME_ANGLED )\n\t{\n\t\t// e.g. doom-style sprite monsters\n\t\tfloat\tyaw = ent->angles[YAW];\n\t\tint\tangleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7;\n\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_ANGLED )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse lerpFrac = (gp_cl->time - ent->latched.sequencetime) * ent->curstate.framerate;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[ent->latched.prevblending[0]].frameptr;\n\t\tif( oldframe ) *oldframe = pspritegroup->frames[angleframe];\n\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tif( curframe ) *curframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn lerpFrac;\n}\n\n/*\n================\nR_CullSpriteModel\n\nCull sprite model by bbox\n================\n*/\nstatic qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin )\n{\n\tvec3_t\tsprite_mins, sprite_maxs;\n\tfloat\tscale = 1.0f;\n\n\tif( !e->model->cache.data )\n\t\treturn true;\n\n\tif( e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\t// scale original bbox (no rotation for sprites)\n\tVectorScale( e->model->mins, scale, sprite_mins );\n\tVectorScale( e->model->maxs, scale, sprite_maxs );\n\n\tsprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs );\n\n\tVectorAdd( sprite_mins, origin, sprite_mins );\n\tVectorAdd( sprite_maxs, origin, sprite_maxs );\n\n\treturn R_CullModel( e, sprite_mins, sprite_maxs );\n}\n\n/*\n================\nR_GlowSightDistance\n\nSet sprite brightness factor\n================\n*/\nstatic float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale )\n{\n\tfloat\tdist, brightness;\n\tvec3_t\tglowDist;\n\tpmtrace_t\t*tr;\n\n\tVectorSubtract( origin, RI.vieworg, glowDist );\n\tdist = VectorLength( glowDist );\n\n\tif( RP_NORMALPASS( ))\n\t{\n\t\ttr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow.value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE));\n\n\t\tif(( 1.0f - tr->fraction ) * dist > 8.0f )\n\t\t\treturn 0.0f;\n\t}\n\n\tif( renderfx == kRenderFxNoDissipation )\n\t\treturn 1.0f;\n\n\tbrightness = GLARE_FALLOFF / ( dist * dist );\n\tbrightness = bound( 0.05f, brightness, 1.0f );\n\t*pscale *= dist * ( 1.0f / 200.0f );\n\n\treturn brightness;\n}\n\n/*\n================\nR_SpriteOccluded\n\nDo occlusion test for glow-sprites\n================\n*/\nstatic qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale )\n{\n\tif( e->curstate.rendermode == kRenderGlow )\n\t{\n\t\tfloat\tblend;\n\t\tvec3_t\tv;\n\n\t\tTriWorldToScreen( origin, v );\n\n\t\tif( v[0] < RI.viewport[0] || v[0] > RI.viewport[0] + RI.viewport[2] )\n\t\t\treturn true; // do scissor\n\t\tif( v[1] < RI.viewport[1] || v[1] > RI.viewport[1] + RI.viewport[3] )\n\t\t\treturn true; // do scissor\n\n\t\tblend = R_SpriteGlowBlend( origin, e->curstate.rendermode, e->curstate.renderfx, pscale );\n\t\ttr.blend *= blend;\n\n\t\tif( blend <= 0.01f )\n\t\t\treturn true; // faded\n\t}\n\telse\n\t{\n\t\tif( R_CullSpriteModel( e, origin ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n=================\nR_DrawSpriteQuad\n=================\n*/\nstatic void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale )\n{\n\tvec3_t\tpoint;\n\n\tr_stats.c_sprite_polys++;\n\n\tpglBegin( GL_QUADS );\n\t\tpglTexCoord2f( 0.0f, 1.0f );\n\t\tVectorMA( org, frame->down * scale, v_up, point );\n\t\tVectorMA( point, frame->left * scale, v_right, point );\n\t\tpglVertex3fv( point );\n\t\tpglTexCoord2f( 0.0f, 0.0f );\n\t\tVectorMA( org, frame->up * scale, v_up, point );\n\t\tVectorMA( point, frame->left * scale, v_right, point );\n\t\tpglVertex3fv( point );\n\t\tpglTexCoord2f( 1.0f, 0.0f );\n\t\tVectorMA( org, frame->up * scale, v_up, point );\n\t\tVectorMA( point, frame->right * scale, v_right, point );\n\t\tpglVertex3fv( point );\n\t\tpglTexCoord2f( 1.0f, 1.0f );\n\t\tVectorMA( org, frame->down * scale, v_up, point );\n\t\tVectorMA( point, frame->right * scale, v_right, point );\n\t\tpglVertex3fv( point );\n\tpglEnd();\n}\n\nstatic qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat )\n{\n\tif( !r_sprite_lighting->value )\n\t\treturn false;\n\n\tif( texFormat != SPR_ALPHTEST )\n\t\treturn false;\n\n\tif( FBitSet( e->curstate.effects, EF_FULLBRIGHT ))\n\t\treturn false;\n\n\tif( e->curstate.renderamt <= 127 )\n\t\treturn false;\n\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderNormal:\n\tcase kRenderTransAlpha:\n\tcase kRenderTransTexture:\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nR_SpriteAllowLerping\n=================\n*/\nstatic qboolean R_SpriteAllowLerping( cl_entity_t *e, msprite_t *psprite )\n{\n\tif( !r_sprite_lerping->value )\n\t\treturn false;\n\n\tif( psprite->numframes <= 1 )\n\t\treturn false;\n\n\tif( psprite->texFormat != SPR_ADDITIVE )\n\t\treturn false;\n\n\tif( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n=================\nR_DrawSpriteModel\n=================\n*/\nvoid R_DrawSpriteModel( cl_entity_t *e )\n{\n\tmspriteframe_t\t*frame = NULL, *oldframe = NULL;\n\tmsprite_t\t\t*psprite;\n\tmodel_t\t\t*model;\n\tint\t\ti, type;\n\tfloat\t\tangle, dot, sr, cr;\n\tfloat\t\tlerp = 1.0f, ilerp, scale;\n\tvec3_t\t\tv_forward, v_right, v_up;\n\tvec3_t\t\torigin, color, color2 = { 0.0f };\n\n\tif( RI.params & RP_ENVVIEW )\n\t\treturn;\n\n\tmodel = e->model;\n\tpsprite = (msprite_t * )model->cache.data;\n\tVectorCopy( e->origin, origin );\t// set render origin\n\n\t// do movewith\n\tif( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW )\n\t{\n\t\tcl_entity_t\t*parent;\n\n\t\tparent = CL_GetEntityByIndex( e->curstate.aiment );\n\n\t\tif( parent && parent->model )\n\t\t{\n\t\t\tif( parent->model->type == mod_studio && e->curstate.body > 0 )\n\t\t\t{\n\t\t\t\tint num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS );\n\t\t\t\tVectorCopy( parent->attachment[num-1], origin );\n\t\t\t}\n\t\t\telse VectorCopy( parent->origin, origin );\n\t\t}\n\t}\n\n\tscale = e->curstate.scale;\n\tif( !scale ) scale = 1.0f;\n\n\tif( R_SpriteOccluded( e, origin, &scale ))\n\t\treturn; // sprite culled\n\n\tr_stats.c_sprite_models_drawn++;\n\n\tif( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd )\n\t\tR_AllowFog( false );\n\n\t// select properly rendermode\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderTransAlpha:\n\t\tpglDepthMask( GL_FALSE );\n\t\t// fallthrough\n\tcase kRenderTransColor:\n\tcase kRenderTransTexture:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tbreak;\n\tcase kRenderGlow:\n\t\tpglDisable( GL_DEPTH_TEST );\n\t\t// fallthrough\n\tcase kRenderTransAdd:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\tpglDepthMask( GL_FALSE );\n\t\tbreak;\n\tcase kRenderNormal:\n\tdefault:\n\t\tpglDisable( GL_BLEND );\n\t\tbreak;\n\t}\n\n\t// all sprites can have color\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglEnable( GL_ALPHA_TEST );\n\n\t// NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t{\n\t\tcolor[0] = (float)e->curstate.rendercolor.r * ( 1.0f / 255.0f );\n\t\tcolor[1] = (float)e->curstate.rendercolor.g * ( 1.0f / 255.0f );\n\t\tcolor[2] = (float)e->curstate.rendercolor.b * ( 1.0f / 255.0f );\n\t}\n\telse\n\t{\n\t\tcolor[0] = 1.0f;\n\t\tcolor[1] = 1.0f;\n\t\tcolor[2] = 1.0f;\n\t}\n\n\tif( R_SpriteHasLightmap( e, psprite->texFormat ))\n\t{\n\t\tcolorVec lightColor = R_LightPoint( origin );\n\t\t// FIXME: collect light from dlights?\n\t\tcolor2[0] = (float)lightColor.r * ( 1.0f / 255.0f );\n\t\tcolor2[1] = (float)lightColor.g * ( 1.0f / 255.0f );\n\t\tcolor2[2] = (float)lightColor.b * ( 1.0f / 255.0f );\n\t\t// NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0\n\t\t// NOTE: make them easier to see with 0.3333, was 0.5 in original\n\t\tpglAlphaFunc( GL_GREATER, 1.0f / 3.0f );\n\t}\n\n\tif( R_SpriteAllowLerping( e, psprite ))\n\t\tlerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame );\n\telse frame = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] );\n\n\ttype = psprite->type;\n\n\t// automatically roll parallel sprites if requested\n\tif( e->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL )\n\t\ttype = SPR_FWD_PARALLEL_ORIENTED;\n\n\tswitch( type )\n\t{\n\tcase SPR_ORIENTED:\n\t\tAngleVectors( e->angles, v_forward, v_right, v_up );\n\t\tVectorScale( v_forward, 0.01f, v_forward );\t// to avoid z-fighting\n\t\tVectorSubtract( origin, v_forward, origin );\n\t\tbreak;\n\tcase SPR_FACING_UPRIGHT:\n\t\tVectorSet( v_right, origin[1] - RI.vieworg[1], -(origin[0] - RI.vieworg[0]), 0.0f );\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_UPRIGHT:\n\t\tdot = RI.vforward[2];\n\t\tif(( dot > 0.999848f ) || ( dot < -0.999848f ))\t// cos(1 degree) = 0.999848\n\t\t\treturn; // invisible\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorSet( v_right, RI.vforward[1], -RI.vforward[0], 0.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_ORIENTED:\n\t\tangle = e->angles[ROLL] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sr, &cr );\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tv_right[i] = (RI.vright[i] * cr + RI.vup[i] * sr);\n\t\t\tv_up[i] = RI.vright[i] * -sr + RI.vup[i] * cr;\n\t\t}\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL: // normal sprite\n\tdefault:\n\t\tVectorCopy( RI.vright, v_right );\n\t\tVectorCopy( RI.vup, v_up );\n\t\tbreak;\n\t}\n\n\tif( psprite->facecull == SPR_CULL_NONE )\n\t\tGL_Cull( GL_NONE );\n\n\tif( oldframe == frame )\n\t{\n\t\t// draw the single non-lerped frame\n\t\tpglColor4f( color[0], color[1], color[2], tr.blend );\n\t\tGL_Bind( XASH_TEXTURE0, frame->gl_texturenum );\n\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale );\n\t}\n\telse\n\t{\n\t\t// draw two combined lerped frames\n\t\tlerp = bound( 0.0f, lerp, 1.0f );\n\t\tilerp = 1.0f - lerp;\n\n\t\tif( ilerp != 0.0f )\n\t\t{\n\t\t\tpglColor4f( color[0], color[1], color[2], tr.blend * ilerp );\n\t\t\tGL_Bind( XASH_TEXTURE0, oldframe->gl_texturenum );\n\t\t\tR_DrawSpriteQuad( oldframe, origin, v_right, v_up, scale );\n\t\t}\n\n\t\tif( lerp != 0.0f )\n\t\t{\n\t\t\tpglColor4f( color[0], color[1], color[2], tr.blend * lerp );\n\t\t\tGL_Bind( XASH_TEXTURE0, frame->gl_texturenum );\n\t\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale );\n\t\t}\n\t}\n\n\t// draw the sprite 'lightmap' :-)\n\tif( R_SpriteHasLightmap( e, psprite->texFormat ))\n\t{\n\t\tif( !r_lightmap->value )\n\t\t\tpglEnable( GL_BLEND );\n\t\telse pglDisable( GL_BLEND );\n\t\tpglDepthFunc( GL_EQUAL );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tpglBlendFunc( GL_ZERO, GL_SRC_COLOR );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\t\tpglColor4f( color2[0], color2[1], color2[2], tr.blend );\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale );\n\t\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\t\tpglDepthFunc( GL_LEQUAL );\n\t\tpglDisable( GL_BLEND );\n\t}\n\n\tif( psprite->facecull == SPR_CULL_NONE )\n\t\tGL_Cull( GL_FRONT );\n\n\tpglDisable( GL_ALPHA_TEST );\n\tpglDepthMask( GL_TRUE );\n\n\tif( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd )\n\t\tR_AllowFog( true );\n\n\tif( e->curstate.rendermode != kRenderNormal )\n\t{\n\t\tpglDisable( GL_BLEND );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\t\tpglEnable( GL_DEPTH_TEST );\n\t}\n}\n"
  },
  {
    "path": "ref/gl/gl_studio.c",
    "content": "/*\ngl_studio.c - studio model renderer\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"const.h\"\n#include \"r_studioint.h\"\n#include \"triangleapi.h\"\n#include \"studio.h\"\n#include \"pm_local.h\"\n#include \"pmtrace.h\"\n\n#define EVENT_CLIENT\t5000\t// less than this value it's a server-side studio events\n#define MAX_LOCALLIGHTS\t4\n\ntypedef struct\n{\n\tchar\t\tname[MAX_OSPATH];\n\tchar\t\tmodelname[MAX_OSPATH];\n\tmodel_t\t\t*model;\n} player_model_t;\n\n// never gonna change, just shut up const warning\nCVAR_DEFINE_AUTO( r_shadows, \"0\", 0, \"draw ugly shadows\" );\n\nstatic const vec3_t hullcolor[8] =\n{\n{ 1.0f, 1.0f, 1.0f },\n{ 1.0f, 0.5f, 0.5f },\n{ 0.5f, 1.0f, 0.5f },\n{ 1.0f, 1.0f, 0.5f },\n{ 0.5f, 0.5f, 1.0f },\n{ 1.0f, 0.5f, 1.0f },\n{ 0.5f, 1.0f, 1.0f },\n{ 1.0f, 1.0f, 1.0f },\n};\n\ntypedef struct sortedmesh_s\n{\n\tmstudiomesh_t\t*mesh;\n\tint\t\tflags;\t\t\t// face flags\n} sortedmesh_t;\n\ntypedef struct\n{\n\tdouble\t\ttime;\n\tdouble\t\tframetime;\n\tint\t\tframecount;\t\t// studio framecount\n\tqboolean\t\tinterpolate;\n\tint\t\trendermode;\n\tfloat\t\tblend;\t\t\t// blend value\n\n\t// bones\n\tmatrix3x4\t\trotationmatrix;\n\tmatrix3x4\t\tbonestransform[MAXSTUDIOBONES];\n\tmatrix3x4\t\tlighttransform[MAXSTUDIOBONES];\n\n\t// boneweighting stuff\n\tmatrix3x4\t\tworldtransform[MAXSTUDIOBONES];\n\n\t// cached bones\n\tmatrix3x4\t\tcached_bonestransform[MAXSTUDIOBONES];\n\tmatrix3x4\t\tcached_lighttransform[MAXSTUDIOBONES];\n\tchar\t\tcached_bonenames[MAXSTUDIOBONES][32];\n\tint\t\tcached_numbones;\t\t// number of bones in cache\n\n\tsortedmesh_t\tmeshes[MAXSTUDIOMESHES];\t// sorted meshes\n\tvec3_t\t\tverts[MAXSTUDIOVERTS];\n\tvec3_t\t\tnorms[MAXSTUDIOVERTS];\n\n\t// lighting state\n\tfloat\t\tambientlight;\n\tfloat\t\tshadelight;\n\tvec3_t\t\tlightvec;\t\t\t// averaging light direction\n\tvec3_t\t\tlightspot;\t\t// shadow spot\n\tvec3_t\t\tlightcolor;\t\t// averaging lightcolor\n\tvec3_t\t\tblightvec[MAXSTUDIOBONES];\t// bone light vecs\n\tvec3_t\t\tlightvalues[MAXSTUDIOVERTS];\t// precomputed lightvalues per each shared vertex of submodel\n\n\t// chrome stuff\n\tvec3_t\t\tchrome_origin;\n\tvec2_t\t\tchrome[MAXSTUDIOVERTS];\t// texture coords for surface normals\n\tvec3_t\t\tchromeright[MAXSTUDIOBONES];\t// chrome vector \"right\" in bone reference frames\n\tvec3_t\t\tchromeup[MAXSTUDIOBONES];\t// chrome vector \"up\" in bone reference frames\n\tint\t\tchromeage[MAXSTUDIOBONES];\t// last time chrome vectors were updated\n\n\t// glowshell stuff\n\tint\t\tnormaltable[MAXSTUDIOVERTS];\t// glowshell uses this\n\n\t// elights cache\n\tint\t\tnumlocallights;\n\tint\t\tlightage[MAXSTUDIOBONES];\n\tdlight_t\t\t*locallight[MAX_LOCALLIGHTS];\n\tint\t\tlocallightcolor[MAX_LOCALLIGHTS][3];\n\tvec4_t\t\tlightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS];\n\tvec3_t\t\tlightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS];\n\tfloat\t\tlocallightR2[MAX_LOCALLIGHTS];\n\n\t// playermodels\n\tplayer_model_t  player_models[MAX_CLIENTS];\n\n\t// drawelements renderer\n\tvec3_t\t\t\tarrayverts[MAXSTUDIOVERTS];\n\tvec2_t\t\t\tarraycoord[MAXSTUDIOVERTS];\n\tunsigned short\tarrayelems[MAXSTUDIOVERTS*6];\n\tGLubyte\t\t\tarraycolor[MAXSTUDIOVERTS][4];\n\tuint\t\t\tnumverts;\n\tuint\t\t\tnumelems;\n} studio_draw_state_t;\n\n// studio-related cvars\nCVAR_DEFINE_AUTO( r_studio_sort_textures, \"0\", FCVAR_GLCONFIG, \"change draw order for additive meshes\" );\nCVAR_DEFINE_AUTO( r_studio_drawelements, \"1\", FCVAR_GLCONFIG, \"use glDrawElements for studiomodels\" );\nstatic cvar_t\t\t\t*cl_righthand = NULL;\n\nstatic r_studio_interface_t\t*pStudioDraw;\nstatic studio_draw_state_t\tg_studio;\t\t// global studio state\n\n// global variables\nstatic qboolean\t\tm_fDoRemap;\nmstudiomodel_t\t\t*m_pSubModel;\nmstudiobodyparts_t\t\t*m_pBodyPart;\nplayer_info_t\t\t*m_pPlayerInfo;\nstudiohdr_t\t\t*m_pStudioHeader;\nfloat\t\t\tm_flGaitMovement;\nint\t\t\tg_nTopColor, g_nBottomColor;\t// remap colors\nint\t\t\tg_nFaceFlags, g_nForceFaceFlags;\n\n/*\n====================\nR_StudioInit\n\n====================\n*/\nvoid R_StudioInit( void )\n{\n\n#if XASH_PSVITA\n\t// don't do the same array-building work twice since that's what our FFP shim does anyway\n\tgEngfuncs.Cvar_FullSet( \"r_studio_drawelements\", \"0\", FCVAR_READ_ONLY );\n#endif\n\n\tMatrix3x4_LoadIdentity( g_studio.rotationmatrix );\n\n\tg_studio.interpolate = true;\n\tg_studio.framecount = 0;\n\tm_fDoRemap = false;\n}\n\n/*\n================\nR_StudioSetupTimings\n\ninit current time for a given model\n================\n*/\nstatic void R_StudioSetupTimings( void )\n{\n\tif( RI.drawWorld )\n\t{\n\t\t// synchronize with server time\n\t\tg_studio.time = gp_cl->time;\n\t\tg_studio.frametime = gp_cl->time - gp_cl->oldtime;\n\t}\n\telse\n\t{\n\t\t// menu stuff\n\t\tg_studio.time = gp_host->realtime;\n\t\tg_studio.frametime = gp_host->frametime;\n\t}\n}\n\n/*\n================\nR_AllowFlipViewModel\n\nshould a flip the viewmodel if cl_righthand is set to 1\n================\n*/\nstatic qboolean R_AllowFlipViewModel( cl_entity_t *e )\n{\n\tif( cl_righthand && cl_righthand->value > 0 )\n\t{\n\t\tif( e == tr.viewent )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n================\nR_StudioComputeBBox\n\nCompute a full bounding box for current sequence\n================\n*/\nstatic qboolean R_StudioComputeBBox( vec3_t bbox[8] )\n{\n\tvec3_t\t\tstudio_mins, studio_maxs;\n\tvec3_t\t\tmins, maxs, p1, p2;\n\tcl_entity_t\t*e = RI.currententity;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tint\t\ti;\n\n\tif( !m_pStudioHeader )\n\t\treturn false;\n\n\t// check if we have valid mins\\maxs\n\tif( !VectorCompare( vec3_origin, RI.currentmodel->mins ))\n\t{\n\t\t// clipping bounding box\n\t\tVectorCopy( RI.currentmodel->mins, mins );\n\t\tVectorCopy( RI.currentmodel->maxs, maxs );\n\t}\n\telse\n\t{\n\t\tClearBounds( mins, maxs );\n\t}\n\n\t// check sequence range\n\tif( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\t// add sequence box to the model box\n\tAddPointToBounds( pseqdesc->bbmin, mins, maxs );\n\tAddPointToBounds( pseqdesc->bbmax, mins, maxs );\n\tClearBounds( studio_mins, studio_maxs );\n\n\t// compute a full bounding box\n\tfor( i = 0; i < 8; i++ )\n\t{\n  \t\tp1[0] = ( i & 1 ) ? mins[0] : maxs[0];\n  \t\tp1[1] = ( i & 2 ) ? mins[1] : maxs[1];\n  \t\tp1[2] = ( i & 4 ) ? mins[2] : maxs[2];\n\n\t\tMatrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 );\n\t\tAddPointToBounds( p2, studio_mins, studio_maxs );\n\t\tif( bbox ) VectorCopy( p2, bbox[i] );\n\t}\n\n\tif( !bbox && R_CullModel( e, studio_mins, studio_maxs ))\n\t\treturn false; // model culled\n\treturn true; // visible\n}\n\nstatic void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result )\n{\n\tfloat\tflWeight0, flWeight1, flWeight2, flWeight3;\n\tint\ti, numbones = 0;\n\tfloat\tflTotal;\n\n\tfor( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )\n\t{\n\t\tif( boneweights->bone[i] != -1 )\n\t\t\tnumbones++;\n\t}\n\n\tif( numbones == 4 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]];\n\t\tvec4_t *boneMat3 = (vec4_t *)g_studio.worldtransform[boneweights->bone[3]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflWeight3 = boneweights->weight[3] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3;\n\t}\n\telse if( numbones == 3 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2;\n\t}\n\telse if( numbones == 2 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1;\n\t}\n\telse\n\t{\n\t\tMatrix3x4_Copy( result, g_studio.worldtransform[boneweights->bone[0]] );\n\t}\n}\n\n/*\n===============\npfnGetCurrentEntity\n\n===============\n*/\nstatic cl_entity_t *pfnGetCurrentEntity( void )\n{\n\treturn RI.currententity;\n}\n\n/*\n===============\npfnPlayerInfo\n\n===============\n*/\nplayer_info_t *pfnPlayerInfo( int index )\n{\n\tif( !RI.drawWorld )\n\t\tindex = -1;\n\n\treturn gEngfuncs.pfnPlayerInfo( index );\n}\n\n/*\n===============\npfnMod_ForName\n\n===============\n*/\nstatic model_t *pfnMod_ForName( const char *model, int crash )\n{\n\treturn gEngfuncs.Mod_ForName( model, crash, false );\n}\n\n/*\n===============\npfnGetPlayerState\n\n===============\n*/\nstatic entity_state_t *R_StudioGetPlayerState( int index )\n{\n\tif( !RI.drawWorld )\n\t\treturn &RI.currententity->curstate;\n\n\treturn gEngfuncs.pfnGetPlayerState( index );\n}\n\n/*\n===============\npfnGetViewEntity\n\n===============\n*/\nstatic cl_entity_t *pfnGetViewEntity( void )\n{\n\treturn tr.viewent;\n}\n\n/*\n===============\npfnGetEngineTimes\n\n===============\n*/\nstatic void pfnGetEngineTimes( int *framecount, double *current, double *old )\n{\n\tif( framecount ) *framecount = tr.realframecount;\n\tif( current ) *current = gp_cl->time;\n\tif( old ) *old =   gp_cl->oldtime;\n}\n\n/*\n===============\npfnGetViewInfo\n\n===============\n*/\nstatic void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv )\n{\n\tif( origin ) VectorCopy( RI.vieworg, origin );\n\tif( forwardv ) VectorCopy( RI.vforward, forwardv );\n\tif( rightv ) VectorCopy( RI.vright, rightv );\n\tif( upv ) VectorCopy( RI.vup, upv );\n}\n\n/*\n===============\nR_GetChromeSprite\n\n===============\n*/\nstatic model_t *R_GetChromeSprite( void )\n{\n\treturn gEngfuncs.GetDefaultSprite( REF_CHROME_SPRITE );\n}\n\n/*\n===============\npfnGetModelCounters\n\n===============\n*/\nstatic void pfnGetModelCounters( int **s, int **a )\n{\n\t*s = &g_studio.framecount;\n\t*a = &r_stats.c_studio_models_drawn;\n}\n\n/*\n===============\npfnGetAliasScale\n\n===============\n*/\nstatic void pfnGetAliasScale( float *x, float *y )\n{\n\tif( x ) *x = 1.0f;\n\tif( y ) *y = 1.0f;\n}\n\n/*\n===============\npfnStudioGetBoneTransform\n\n===============\n*/\nstatic float ****pfnStudioGetBoneTransform( void )\n{\n\treturn (float ****)g_studio.bonestransform;\n}\n\n/*\n===============\npfnStudioGetLightTransform\n\n===============\n*/\nstatic float ****pfnStudioGetLightTransform( void )\n{\n\treturn (float ****)g_studio.lighttransform;\n}\n\n/*\n===============\npfnStudioGetAliasTransform\n\n===============\n*/\nstatic float ***pfnStudioGetAliasTransform( void )\n{\n\treturn NULL;\n}\n\n/*\n===============\npfnStudioGetRotationMatrix\n\n===============\n*/\nstatic float ***pfnStudioGetRotationMatrix( void )\n{\n\treturn (float ***)g_studio.rotationmatrix;\n}\n\n/*\n====================\nStudioPlayerBlend\n\n====================\n*/\nstatic void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )\n{\n\t// calc up/down pointing\n\t*pBlend = (*pPitch * 3.0f);\n\n\tif( *pBlend < pseqdesc->blendstart[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendstart[0] / 3.0f;\n\t\t*pBlend = 0;\n\t}\n\telse if( *pBlend > pseqdesc->blendend[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendend[0] / 3.0f;\n\t\t*pBlend = 255;\n\t}\n\telse\n\t{\n\t\tif( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error\n\t\t\t*pBlend = 127;\n\t\telse *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]);\n\t\t*pPitch = 0.0f;\n\t}\n}\n\n/*\n====================\nR_StudioLerpMovement\n\n====================\n*/\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles )\n{\n\tfloat\tf = 1.0f;\n\n\t// don't do it if the goalstarttime hasn't updated in a while.\n\t// NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit\n\t// was increased to 1.0 s., which is 2x the max lag we are accounting for.\n\tif( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime ))\n\t\tf = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime );\n\n\t// Con_Printf( \"%4.2f %.2f %.2f\\n\", f, e->curstate.animtime, g_studio.time );\n\tVectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin );\n\n\tif( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON ))\n\t{\n\t\tvec4_t\tq, q1, q2;\n\n\t\tAngleQuaternion( e->curstate.angles, q1, false );\n\t\tAngleQuaternion( e->latched.prevangles, q2, false );\n\t\tQuaternionSlerp( q2, q1, f, q );\n\t\tQuaternionAngle( q, angles );\n\t}\n\telse VectorCopy( e->curstate.angles, angles );\n}\n\n/*\n====================\nStudioSetUpTransform\n\n====================\n*/\nstatic void R_StudioSetUpTransform( cl_entity_t *e )\n{\n\tvec3_t\torigin, angles;\n\n\tVectorCopy( e->origin, origin );\n\tVectorCopy( e->angles, angles );\n\n\t// interpolate monsters position (moved into UpdateEntityFields by user request)\n\tif( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gp_host->features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t{\n\t\tR_StudioLerpMovement( e, g_studio.time, origin, angles );\n\t}\n\n\tif( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\tangles[PITCH] = -angles[PITCH]; // stupid quake bug\n\n\t// don't rotate clients, only aim\n\tif( e->player ) angles[PITCH] = 0.0f;\n\n\tMatrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f );\n\n\tif( tr.fFlipViewModel )\n\t{\n\t\tg_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1];\n\t\tg_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1];\n\t\tg_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1];\n\t}\n}\n\n/*\n====================\nStudioEstimateFrame\n\n====================\n*/\nfloat R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time )\n{\n\tdouble\tdfdt, f;\n\n\tif( g_studio.interpolate )\n\t{\n\t\tif( time < e->curstate.animtime ) dfdt = 0.0;\n\t\telse dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps;\n\t}\n\telse dfdt = 0;\n\n\tif( pseqdesc->numframes <= 1 ) f = 0.0;\n\telse f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0f;\n\n\tf += dfdt;\n\n\tif( pseqdesc->flags & STUDIO_LOOPING )\n\t{\n\t\tif( pseqdesc->numframes > 1 )\n\t\t\tf -= (int)(f / (pseqdesc->numframes - 1)) *  (pseqdesc->numframes - 1);\n\t\tif( f < 0 ) f += (pseqdesc->numframes - 1);\n\t}\n\telse\n\t{\n\t\tif( f >= pseqdesc->numframes - 1.001 )\n\t\t\tf = pseqdesc->numframes - 1.001;\n\t\tif( f < 0.0 )  f = 0.0;\n\t}\n\treturn f;\n}\n\n/*\n====================\nStudioEstimateInterpolant\n\n====================\n*/\nstatic float R_StudioEstimateInterpolant( cl_entity_t *e )\n{\n\tfloat\tdadt = 1.0f;\n\n\tif( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f ))\n\t{\n\t\tdadt = ( g_studio.time - e->curstate.animtime ) / 0.1f;\n\t\tif( dadt > 2.0f ) dadt = 2.0f;\n\t}\n\n\treturn dadt;\n}\n\n/*\n====================\nStudioFxTransform\n\n====================\n*/\nstatic void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform )\n{\n\tswitch( ent->curstate.renderfx )\n\t{\n\tcase kRenderFxDistort:\n\tcase kRenderFxHologram:\n\t\tif( !gEngfuncs.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tint\taxis = gEngfuncs.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 ) axis = 2; // choose between x & z\n\t\t\tVectorScale( transform[axis], gEngfuncs.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] );\n\t\t}\n\t\telse if( !gEngfuncs.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tfloat\toffset;\n\t\t\tint\taxis = gEngfuncs.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 ) axis = 2; // choose between x & z\n\t\t\toffset = gEngfuncs.COM_RandomFloat( -10.0f, 10.0f );\n\t\t\ttransform[gEngfuncs.COM_RandomLong( 0, 2 )][3] += offset;\n\t\t}\n\t\tbreak;\n\tcase kRenderFxExplode:\n\t\t{\n\t\t\tfloat\tscale;\n\n\t\t\tscale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f;\n\t\t\tif( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200%\n\n\t\t\ttransform[0][1] *= scale;\n\t\t\ttransform[1][1] *= scale;\n\t\t\ttransform[2][1] *= scale;\n\t\t}\n\t\tbreak;\n\t}\n}\n\n/*\n====================\nStudioCalcBoneAdj\n\n====================\n*/\nstatic void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen )\n{\n\tmstudiobonecontroller_t\t*pbonecontroller;\n\tfloat\t\t\tvalue = 0.0f;\n\tint\t\t\ti, j;\n\n\tpbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex);\n\n\tfor( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )\n\t{\n\t\ti = pbonecontroller[j].index;\n\n\t\tif( i == STUDIO_MOUTH )\n\t\t{\n\t\t\t// mouth hardcoded at controller 4\n\t\t\tvalue = (float)mouthopen / 64.0f;\n\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\tvalue = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t}\n\t\telse if( i < 4 )\n\t\t{\n\t\t\t// check for 360% wrapping\n\t\t\tif( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP ))\n\t\t\t{\n\t\t\t\tif( abs( pcontroller1[i] - pcontroller2[i] ) > 128 )\n\t\t\t\t{\n\t\t\t\t\tint a = (pcontroller1[i] + 128) % 256;\n\t\t\t\t\tint b = (pcontroller2[i] + 128) % 256;\n\t\t\t\t\tvalue = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tvalue = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvalue = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f;\n\t\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\t\tvalue = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t\t}\n\t\t}\n\n\t\tswitch( pbonecontroller[j].type & STUDIO_TYPES )\n\t\t{\n\t\tcase STUDIO_XR:\n\t\tcase STUDIO_YR:\n\t\tcase STUDIO_ZR:\n\t\t\tadj[j] = DEG2RAD( value );\n\t\t\tbreak;\n\t\tcase STUDIO_X:\n\t\tcase STUDIO_Y:\n\t\tcase STUDIO_Z:\n\t\t\tadj[j] = value;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioCalcRotations\n\n====================\n*/\nstatic void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f )\n{\n\tint\t\ti, frame;\n\tfloat\t\tadj[MAXSTUDIOCONTROLLERS];\n\tfloat\t\ts, dadt;\n\tmstudiobone_t\t*pbone;\n\n\t// bah, fix this bug with changing sequences too fast\n\tif( f > pseqdesc->numframes - 1 )\n\t{\n\t\tf = 0.0f;\n\t}\n\telse if( f < -0.01f )\n\t{\n\t\t// BUG ( somewhere else ) but this code should validate this data.\n\t\t// This could cause a crash if the frame # is negative, so we'll go ahead\n\t\t// and clamp it here\n\t\tf = -0.01f;\n\t}\n\n\tframe = (int)f;\n\n\tdadt = R_StudioEstimateInterpolant( e );\n\ts = (f - frame);\n\n\t// add in programtic controllers\n\tpbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\tR_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ )\n\t\tR_StudioCalcBones( frame, s, pbone, panim, adj, pos[i], q[i] );\n\n\tif( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f;\n}\n\n/*\n====================\nStudioMergeBones\n\n====================\n*/\nstatic void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel )\n{\n\tint\t\ti, j;\n\tmstudiobone_t\t*pbones;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioanim_t\t*panim;\n\tmatrix3x4\t\tbonematrix;\n\tstatic vec4_t\tq[MAXSTUDIOBONES];\n\tstatic float\tpos[MAXSTUDIOBONES][3];\n\tfloat\t\tf;\n\n\tif( e->curstate.sequence >=  m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tfor( j = 0; j < g_studio.cached_numbones; j++ )\n\t\t{\n\t\t\tif( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] ))\n\t\t\t{\n\t\t\t\tMatrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( j >= g_studio.cached_numbones )\n\t\t{\n\t\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\t\t\tif( pbones[i].parent == -1 )\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t\t// apply client-side effects to the transformation matrix\n\t\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioSetupBones\n\n====================\n*/\nstatic void R_StudioSetupBones( cl_entity_t *e )\n{\n\tfloat\t\tf;\n\tmstudiobone_t\t*pbones;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioanim_t\t*panim;\n\tmatrix3x4\t\tbonematrix;\n\tstatic vec3_t\tpos[MAXSTUDIOBONES];\n\tstatic vec4_t\tq[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos2[MAXSTUDIOBONES];\n\tstatic vec4_t\tq2[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos3[MAXSTUDIOBONES];\n\tstatic vec4_t\tq3[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos4[MAXSTUDIOBONES];\n\tstatic vec4_t\tq4[MAXSTUDIOBONES];\n\tint\t\ti;\n\n\tif( e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\n\tif( pseqdesc->numblends > 1 )\n\t{\n\t\tfloat\ts;\n\t\tfloat\tdadt;\n\n\t\tpanim += m_pStudioHeader->numbones;\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f );\n\n\t\tdadt = R_StudioEstimateInterpolant( e );\n\t\ts = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;\n\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s );\n\n\t\tif( pseqdesc->numblends == 4 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f );\n\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f );\n\n\t\t\ts = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\ts = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s );\n\t\t}\n\t}\n\n\tif( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq ))\n\t{\n\t\t// blend from last sequence\n\t\tstatic vec3_t\tpos1b[MAXSTUDIOBONES];\n\t\tstatic vec4_t\tq1b[MAXSTUDIOBONES];\n\t\tfloat\t\ts;\n\n\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence;\n\t\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\n\t\t// clip prevframe\n\t\tR_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe );\n\n\t\tif( pseqdesc->numblends > 1 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\ts = (e->latched.prevseqblending[0]) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s );\n\n\t\t\tif( pseqdesc->numblends == 4 )\n\t\t\t{\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\ts = (e->latched.prevseqblending[0]) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\t\ts = (e->latched.prevseqblending[1]) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s );\n\t\t\t}\n\t\t}\n\n\t\ts = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f;\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s );\n\t}\n\telse\n\t{\n\t\t// store prevframe otherwise\n\t\te->latched.prevframe = f;\n\t}\n\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\t// calc gait animation\n\tif( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 )\n\t{\n\t\tqboolean\tcopy_bones = true;\n\n\t\tif( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq )\n\t\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence;\n\n\t\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe );\n\n\t\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\t{\n\t\t\tif( !Q_strcmp( pbones[i].name, \"Bip01 Spine\" ))\n\t\t\t\tcopy_bones = false;\n\t\t\telse if( !Q_strcmp( pbones[pbones[i].parent].name, \"Bip01 Pelvis\" ))\n\t\t\t\tcopy_bones = true;\n\n\t\t\tif( !copy_bones ) continue;\n\n\t\t\tVectorCopy( pos2[i], pos[i] );\n\t\t\tVector4Copy( q2[i], q[i] );\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\n\t\tif( pbones[i].parent == -1 )\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t// apply client-side effects to the transformation matrix\n\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioSaveBones\n\n====================\n*/\nstatic void R_StudioSaveBones( void )\n{\n\tmstudiobone_t\t*pbones;\n\tint\t\ti;\n\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\tg_studio.cached_numbones = m_pStudioHeader->numbones;\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] );\n\t\tMatrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] );\n\t\tQ_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 );\n\t}\n}\n\n/*\n====================\nStudioBuildNormalTable\n\nNOTE: m_pSubModel must be set\n====================\n*/\nstatic void R_StudioBuildNormalTable( void )\n{\n\tcl_entity_t\t*e = RI.currententity;\n\tmstudiomesh_t\t*pmesh;\n\tint\t\ti, j;\n\n\tAssert( m_pSubModel != NULL );\n\n\t// reset chrome cache\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\tg_studio.chromeage[i] = 0;\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tg_studio.normaltable[i] = -1;\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort\t*ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 ) i = -i;\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tif( g_studio.normaltable[ptricmds[0]] < 0 )\n\t\t\t\t\tg_studio.normaltable[ptricmds[0]] = ptricmds[1];\n\t\t\t}\n\t\t}\n\t}\n\n\tg_studio.chrome_origin[0] = cos( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[1] = sin( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[2] = cos( r_glowshellfreq->value * g_studio.time * 0.33f ) * 4000.0f;\n\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t\tTriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 );\n\telse TriColor4ub( 255, 255, 255, 255 );\n}\n\n/*\n====================\nStudioGenerateNormals\n\nNOTE: m_pSubModel must be set\ng_studio.verts must be computed\n====================\n*/\nstatic void R_StudioGenerateNormals( void )\n{\n\tint\t\tv0, v1, v2;\n\tvec3_t\t\te0, e1, norm;\n\tmstudiomesh_t\t*pmesh;\n\tint\t\ti, j;\n\n\tAssert( m_pSubModel != NULL );\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tVectorClear( g_studio.norms[i] );\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort\t*ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\ti = -i;\n\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tv1 = v2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\tqboolean\todd = false;\n\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tif( odd ) v1 = v2;\n\t\t\t\t\t\telse v0 = v2;\n\n\t\t\t\t\t\todd = !odd;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tVectorNormalize( g_studio.norms[i] );\n}\n\n/*\n====================\nStudioSetupChrome\n\n====================\n*/\nstatic void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal )\n{\n\tfloat\tn;\n\n\tif( g_studio.chromeage[bone] != g_studio.framecount )\n\t{\n\t\t// calculate vectors from the viewer to the bone. This roughly adjusts for position\n\t\tvec3_t\tchromeupvec;\t// g_studio.chrome t vector in world reference frame\n\t\tvec3_t\tchromerightvec;\t// g_studio.chrome s vector in world reference frame\n\t\tvec3_t\ttmp;\t\t// vector pointing at bone in world reference frame\n\n\t\tVectorNegate( g_studio.chrome_origin, tmp );\n\t\ttmp[0] += g_studio.lighttransform[bone][0][3];\n\t\ttmp[1] += g_studio.lighttransform[bone][1][3];\n\t\ttmp[2] += g_studio.lighttransform[bone][2][3];\n\n\t\tVectorNormalize( tmp );\n\t\tCrossProduct( tmp, RI.vright, chromeupvec );\n\t\tVectorNormalize( chromeupvec );\n\t\tCrossProduct( chromeupvec, tmp, chromerightvec );\n\t\tVectorNormalize( chromerightvec );\n\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromeupvec, g_studio.chromeup[bone] );\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromerightvec, g_studio.chromeright[bone] );\n\n\t\tg_studio.chromeage[bone] = g_studio.framecount;\n\t}\n\n\t// calc s coord\n\tn = DotProduct( normal, g_studio.chromeright[bone] );\n\tpchrome[0] = (n + 1.0f) * 32.0f;\n\n\t// calc t coord\n\tn = DotProduct( normal, g_studio.chromeup[bone] );\n\tpchrome[1] = (n + 1.0f) * 32.0f;\n}\n\n/*\n====================\nStudioCalcAttachments\n\n====================\n*/\nstatic void R_StudioCalcAttachments( void )\n{\n\tmstudioattachment_t\t*pAtt;\n\tint\t\ti;\n\n\t// calculate attachment points\n\tpAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex);\n\n\tfor( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ )\n\t{\n\t\tMatrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] );\n\t}\n}\n\n/*\n===============\npfnStudioSetupModel\n\n===============\n*/\nstatic void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel )\n{\n\tint\tindex;\n\n\tif( bodypart > m_pStudioHeader->numbodyparts )\n\t\tbodypart = 0;\n\n\tm_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart;\n\n\tindex = RI.currententity->curstate.body / m_pBodyPart->base;\n\tindex = index % m_pBodyPart->nummodels;\n\n\tm_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index;\n\n\tif( ppbodypart ) *ppbodypart = m_pBodyPart;\n\tif( ppsubmodel ) *ppsubmodel = m_pSubModel;\n}\n\n/*\n===============\nR_StudioCheckBBox\n\n===============\n*/\nstatic int R_StudioCheckBBox( void )\n{\n\tif( !RI.currententity || !RI.currentmodel )\n\t\treturn false;\n\n\treturn R_StudioComputeBBox( NULL );\n}\n\n/*\n===============\nR_StudioDynamicLight\n\n===============\n*/\nstatic void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight )\n{\n\tmovevars_t\t*mv = tr.movevars;\n\tvec3_t\t\tlightDir, vecSrc, vecEnd;\n\tvec3_t\t\torigin, dist, finalLight;\n\tfloat\t\tadd, radius, total;\n\tcolorVec\t\tlight;\n\tuint\t\tlnum;\n\tdlight_t\t\t*dl;\n\n\tif( !plight || !ent || !ent->model )\n\t\treturn;\n\n\tif( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT ))\n\t{\n\t\tplight->shadelight = 0;\n\t\tplight->ambientlight = 192;\n\n\t\tVectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f );\n\t\tVectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\t\treturn;\n\t}\n\n\t// determine plane to get lightvalues from: ceil or floor\n\tif( FBitSet( ent->curstate.effects, EF_INVLIGHT ))\n\t\tVectorSet( lightDir, 0.0f, 0.0f, 1.0f );\n\telse VectorSet( lightDir, 0.0f, 0.0f, -1.0f );\n\n\tVectorCopy( ent->origin, origin );\n\n\tVectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f );\n\tlight.r = light.g = light.b = light.a = 0;\n\n\tif(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 )\n\t{\n\t\tmsurface_t\t*psurf = NULL;\n\t\tpmtrace_t\t\ttrace;\n\n\t\tif( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD ))\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f;\n\t\t}\n\n\t\ttrace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY );\n\t\tif( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd );\n\t\telse psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd );\n\n\t\tif( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )))\n\t\t{\n\t\t\tVectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z );\n\n\t\t\tlight.r = mv->skycolor_r;\n\t\t\tlight.g = mv->skycolor_g;\n\t\t\tlight.b = mv->skycolor_b;\n\t\t}\n\t}\n\n\tif(( light.r + light.g + light.b ) == 0 )\n\t{\n\t\tcolorVec\tgcolor;\n\t\tfloat\tgrad[4];\n\n\t\tVectorScale( lightDir, 2048.0f, vecEnd );\n\t\tVectorAdd( vecEnd, vecSrc, vecEnd );\n\n\t\tlight = R_LightVec( vecSrc, vecEnd, g_studio.lightspot, g_studio.lightvec );\n\n\t\tif( VectorIsNull( g_studio.lightvec ))\n\t\t{\n\t\t\tvecSrc[0] -= 16.0f;\n\t\t\tvecSrc[1] -= 16.0f;\n\t\t\tvecEnd[0] -= 16.0f;\n\t\t\tvecEnd[1] -= 16.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] += 32.0f;\n\t\t\tvecEnd[0] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[1] += 32.0f;\n\t\t\tvecEnd[1] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] -= 32.0f;\n\t\t\tvecEnd[0] -= 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tlightDir[0] = grad[0] - grad[1] - grad[2] + grad[3];\n\t\t\tlightDir[1] = grad[1] + grad[0] - grad[2] - grad[3];\n\t\t\tVectorNormalize( lightDir );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( g_studio.lightvec, lightDir );\n\t\t}\n\t}\n\n\tif( ent->curstate.renderfx == kRenderFxLightMultiplier && ent->curstate.iuser4 != 10 )\n\t{\n\t\tlight.r *= ent->curstate.iuser4 / 10.0f;\n\t\tlight.g *= ent->curstate.iuser4 / 10.0f;\n\t\tlight.b *= ent->curstate.iuser4 / 10.0f;\n\t}\n\n\tVectorSet( finalLight, light.r, light.g, light.b );\n\tent->cvFloorColor = light;\n\n\ttotal = Q_max( Q_max( light.r, light.g ), light.b );\n\tif( total == 0.0f ) total = 1.0f;\n\n\t// scale lightdir by light intentsity\n\tVectorScale( lightDir, total, lightDir );\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tdl = &tr.dlights[lnum];\n\n\t\tif( dl->die < g_studio.time || !r_dynamic->value )\n\t\t\tcontinue;\n\n\t\tVectorSubtract( ent->origin, dl->origin, dist );\n\n\t\tradius = VectorLength( dist );\n\t\tadd = (dl->radius - radius);\n\n\t\tif( add > 0.0f )\n\t\t{\n\t\t\ttotal += add;\n\n\t\t\tif( radius > 1.0f )\n\t\t\t\tVectorScale( dist, ( add / radius ), dist );\n\t\t\telse VectorScale( dist, add, dist );\n\n\t\t\tVectorAdd( lightDir, dist, lightDir );\n\n\t\t\tfinalLight[0] += dl->color.r * ( add / 256.0f );\n\t\t\tfinalLight[1] += dl->color.g * ( add / 256.0f );\n\t\t\tfinalLight[2] += dl->color.b * ( add / 256.0f );\n\t\t}\n\t}\n\n\tif( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT ))\n\t\tadd = 0.6f;\n\telse add = bound( 0.75f, v_direct->value, 1.0f );\n\n\tVectorScale( lightDir, add, lightDir );\n\n\tplight->shadelight = VectorLength( lightDir );\n\tplight->ambientlight = total - plight->shadelight;\n\n\ttotal = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] );\n\n\tif( total > 0.0f )\n\t{\n\t\tplight->color[0] = finalLight[0] * ( 1.0f / total );\n\t\tplight->color[1] = finalLight[1] * ( 1.0f / total );\n\t\tplight->color[2] = finalLight[2] * ( 1.0f / total );\n\t}\n\telse VectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\n\tif( plight->ambientlight > 128 )\n\t\tplight->ambientlight = 128;\n\n\tif( plight->ambientlight + plight->shadelight > 255 )\n\t\tplight->shadelight = 255 - plight->ambientlight;\n\n\tVectorNormalize2( lightDir, plight->plightvec );\n}\n\n/*\n===============\npfnStudioEntityLight\n\n===============\n*/\nstatic void R_StudioEntityLight( alight_t *lightinfo )\n{\n\tint\t\tlnum, i, j, k;\n\tfloat\t\tminstrength, dist2, f, r2;\n\tfloat\t\tlstrength[MAX_LOCALLIGHTS];\n\tcl_entity_t\t*ent = RI.currententity;\n\tvec3_t\t\tmid, origin, pos;\n\n\tg_studio.numlocallights = 0;\n\n\tif( !ent || !r_dynamic->value )\n\t\treturn;\n\n\tfor( i = 0; i < MAX_LOCALLIGHTS; i++ )\n\t\tlstrength[i] = 0;\n\n\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin );\n\tdist2 = 1000000.0f;\n\tk = 0;\n\n\tfor( lnum = 0; lnum < MAX_ELIGHTS; lnum++ )\n\t{\n\t\tdlight_t *el = &tr.elights[lnum];\n\n\t\tif( el->die < g_studio.time || el->radius <= 0.0f )\n\t\t\tcontinue;\n\n\t\tif(( el->key & 0xFFF ) == ent->index )\n\t\t{\n\t\t\tint\tatt = (el->key >> 12) & 0xF;\n\n\t\t\tif( att ) VectorCopy( ent->attachment[att], el->origin );\n\t\t\telse VectorCopy( ent->origin, el->origin );\n\t\t}\n\n\t\tVectorCopy( el->origin, pos );\n\t\tVectorSubtract( origin, el->origin, mid );\n\n\t\tf = DotProduct( mid, mid );\n\t\tr2 = el->radius * el->radius;\n\n\t\tif( f > r2 ) minstrength = r2 / f;\n\t\telse minstrength = 1.0f;\n\n\t\tif( minstrength > 0.05f )\n\t\t{\n\t\t\tif( g_studio.numlocallights >= MAX_LOCALLIGHTS )\n\t\t\t{\n\t\t\t\tfor( j = 0, k = -1; j < g_studio.numlocallights; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( lstrength[j] < dist2 && lstrength[j] < minstrength )\n\t\t\t\t\t{\n\t\t\t\t\t\tdist2 = lstrength[j];\n\t\t\t\t\t\tk = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse k = g_studio.numlocallights;\n\n\t\t\tif( k != -1 )\n\t\t\t{\n\t\t\t\tg_studio.locallightcolor[k][0] = LinearGammaTable( el->color.r << 2 );\n\t\t\t\tg_studio.locallightcolor[k][1] = LinearGammaTable( el->color.g << 2 );\n\t\t\t\tg_studio.locallightcolor[k][2] = LinearGammaTable( el->color.b << 2 );\n\t\t\t\tg_studio.locallightR2[k] = r2;\n\t\t\t\tg_studio.locallight[k] = el;\n\t\t\t\tlstrength[k] = minstrength;\n\n\t\t\t\tif( k >= g_studio.numlocallights )\n\t\t\t\t\tg_studio.numlocallights = k + 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nR_StudioSetupLighting\n\n===============\n*/\nstatic void R_StudioSetupLighting( alight_t *plight )\n{\n\tfloat\tscale = 1.0f;\n\tint\ti;\n\n\tif( !m_pStudioHeader || !plight )\n\t\treturn;\n\n\tif( RI.currententity != NULL )\n\t\tscale = RI.currententity->curstate.scale;\n\n\tg_studio.ambientlight = plight->ambientlight;\n\tg_studio.shadelight = plight->shadelight;\n\tVectorCopy( plight->plightvec, g_studio.lightvec );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] );\n\t\tif( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled\n\t}\n\n\tVectorCopy( plight->color, g_studio.lightcolor );\n}\n\n/*\n===============\nR_StudioLighting\n\n===============\n*/\nstatic void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal )\n{\n\tfloat \tillum;\n\n\tif( FBitSet( flags, STUDIO_NF_FULLBRIGHT ))\n\t{\n\t\t*lv = 1.0f;\n\t\treturn;\n\t}\n\n\tillum = g_studio.ambientlight;\n\n\tif( FBitSet( flags, STUDIO_NF_FLATSHADE ))\n\t{\n\t\tillum += g_studio.shadelight * 0.8f;\n\t}\n\telse\n\t{\n\t\tfloat\tr, lightcos;\n\n\t\tif( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] );\n\t\telse lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite\n\t\tif( lightcos > 1.0f ) lightcos = 1.0f;\n\n\t\tillum += g_studio.shadelight;\n\n\t\tr = SHADE_LAMBERT;\n\n \t\t// do modified hemispherical lighting\n\t\tif( r <= 1.0f )\n\t\t{\n\t\t\tr += 1.0f;\n\t\t\tlightcos = (( r - 1.0f ) - lightcos) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum += g_studio.shadelight * lightcos;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlightcos = (lightcos + ( r - 1.0f )) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum -= g_studio.shadelight * lightcos;\n\t\t}\n\n\t\tillum = Q_max( illum, 0.0f );\n\t}\n\n\tillum = Q_min( illum, 255.0f );\n\n\t*lv = LightToTexGamma( illum * 4 ) / 1023.0f;\n}\n\n/*\n====================\nR_LightLambert\n\n====================\n*/\nstatic void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out )\n{\n\tvec3_t\tfinalLight;\n\tint\ti;\n\n\tif( !g_studio.numlocallights )\n\t{\n\t\tVectorScale( color, 255.0f, out );\n\t\treturn;\n\t}\n\n\tVectorSet( finalLight, 0, 0, 0 );\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tfloat\tr;\n\n\t\tr = DotProduct( normal, light[i] );\n\t\tif( likely( !tr.fFlipViewModel ))\n\t\t\tr = -r;\n\n\t\tif( r > 0.0f )\n\t\t{\n\t\t\tvec3_t localLight;\n\t\t\tfloat temp;\n\n\t\t\tif( light[i][3] == 0.0f )\n\t\t\t{\n\t\t\t\tfloat r2 = DotProduct( light[i], light[i] );\n\n\t\t\t\tif( r2 > 0.0f )\n\t\t\t\t\tlight[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 ));\n\t\t\t\telse light[i][3] = 0.0001f;\n\t\t\t}\n\n\t\t\ttemp = r * light[i][3];\n\n\t\t\tVectorAddScalar( g_studio.locallightcolor[i], temp, localLight );\n\t\t\tVectorAdd( finalLight, localLight, finalLight );\n\t\t}\n\t}\n\n\tif( !VectorIsNull( finalLight ))\n\t{\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tfloat c = finalLight[i] + LinearGammaTable( color[i] * 1023.0f );\n\n\t\t\tif( c > 1023.0f )\n\t\t\t\tout[i] = 255;\n\t\t\telse\n\t\t\t\tout[i] = ScreenGammaTable( c ) >> 2;\n\t\t}\n\t}\n\telse\n\t{\n\t\tVectorScale( color, 255.0f, out );\n\t}\n}\n\nstatic void R_StudioSetColorArray( short *ptricmds, vec3_t *pstudionorms, byte *color )\n{\n\tfloat\t*lv = (float *)g_studio.lightvalues[ptricmds[1]];\n\n\tcolor[3] = tr.blend * 255;\n\tR_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color );\n}\n\nstatic void R_StudioSetColorBegin( short *ptricmds, vec3_t *pstudionorms )\n{\n\trgba_t color;\n\n\tR_StudioSetColorArray( ptricmds, pstudionorms, color );\n\tpglColor4ubv( color );\n}\n\n/*\n====================\nR_LightStrength\n\n====================\n*/\nstatic void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] )\n{\n\tint\ti;\n\n\tif( g_studio.lightage[bone] != g_studio.framecount )\n\t{\n\t\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t\t{\n\t\t\tdlight_t *el = g_studio.locallight[i];\n\t\t\tMatrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] );\n\t\t}\n\n\t\tg_studio.lightage[bone] = g_studio.framecount;\n\t}\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tVectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] );\n\t\tlight[i][3] = 0.0f;\n\t}\n}\n\n/*\n===============\nR_StudioSetupSkin\n\n===============\n*/\nstatic void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index )\n{\n\tmstudiotexture_t\t*ptexture = NULL;\n\n\tif( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME ))\n\t\treturn;\n\n\tif( ptexturehdr == NULL )\n\t\treturn;\n\n\t// NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable\n\tif( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )->ptexture;\n\tif( !ptexture ) ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex); // fallback\n\n\tif( r_lightmap->value && !r_fullbright->value )\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\telse GL_Bind( XASH_TEXTURE0, ptexture[index].index );\n}\n\n/*\n===============\nR_StudioGetTexture\n\nDoesn't changes studio global state at all\n===============\n*/\nmstudiotexture_t *R_StudioGetTexture( cl_entity_t *e )\n{\n\tmstudiotexture_t\t*ptexture;\n\tstudiohdr_t\t*phdr, *thdr;\n\n\tif(( phdr = gEngfuncs.Mod_Extradata( mod_studio, e->model )) == NULL )\n\t\treturn NULL;\n\n\tthdr = m_pStudioHeader;\n\tif( !thdr ) return NULL;\n\n\tif( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( e )->ptexture;\n\telse ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex);\n\n\treturn ptexture;\n}\n\nstatic void R_StudioSetRenderamt( int iRenderamt )\n{\n\tif( !RI.currententity ) return;\n\n\tRI.currententity->curstate.renderamt = iRenderamt;\n\ttr.blend = CL_FxBlend( RI.currententity ) / 255.0f;\n}\n\n/*\n===============\nR_StudioSetCullState\n\nsets true for enable backculling (for left-hand viewmodel)\n===============\n*/\nstatic void R_StudioSetCullState( int iCull )\n{\n\t// This function intentionally does nothing\n}\n\n/*\n===============\nR_StudioRenderShadow\n\njust a prefab for render shadow\n===============\n*/\nstatic void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 )\n{\n\tif( !p1 || !p2 || !p3 || !p4 )\n\t\treturn;\n\n\tif( TriSpriteTexture( CL_ModelHandle( iSprite ), 0 ))\n\t{\n\t\tTriRenderMode( kRenderTransAlpha );\n\t\tTriColor4f( 0.0f, 0.0f, 0.0f, 1.0f );\n\n\t\tpglBegin( GL_QUADS );\n\t\t\tpglTexCoord2f( 0.0f, 0.0f );\n\t\t\tpglVertex3fv( p1 );\n\t\t\tpglTexCoord2f( 0.0f, 1.0f );\n\t\t\tpglVertex3fv( p2 );\n\t\t\tpglTexCoord2f( 1.0f, 1.0f );\n\t\t\tpglVertex3fv( p3 );\n\t\t\tpglTexCoord2f( 1.0f, 0.0f );\n\t\t\tpglVertex3fv( p4 );\n\t\tpglEnd();\n\n\t\tTriRenderMode( kRenderNormal );\n\t}\n}\n\n/*\n===============\nR_StudioMeshCompare\n\nSorting opaque entities by model type\n===============\n*/\nstatic int R_StudioMeshCompare( const void *a, const void *b )\n{\n\tif( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_ADDITIVE ))\n\t\treturn 1;\n\n\tif( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_MASKED ))\n\t\treturn -1;\n\n\treturn 0;\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t )\n{\n\tint\ti;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse pglBegin( GL_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\n\t\t\tpglTexCoord2f( ptricmds[2] * s, ptricmds[3] * t );\n\t\t\tpglVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t}\n\n\t\tpglEnd();\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms )\n{\n\tint\ti;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse pglBegin( GL_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\tpglTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] ));\n\t\t\tpglVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t}\n\n\t\tpglEnd();\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale )\n{\n\tfloat\t*lv, *av;\n\tint\ti, idx;\n\tqboolean\tglowShell = (scale > 0.0f) ? true : false;\n\tvec3_t\tvert;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse pglBegin( GL_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tif( glowShell )\n\t\t\t{\n\t\t\t\tcolor24 *clr = &RI.currententity->curstate.rendercolor;\n\n\t\t\t\tidx = g_studio.normaltable[ptricmds[0]];\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tlv = g_studio.norms[ptricmds[0]];\n\t\t\t\tVectorMA( av, scale, lv, vert );\n\t\t\t\tpglColor4ub( clr->r, clr->g, clr->b, 255 );\n\t\t\t\tpglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tpglVertex3fv( vert );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tidx = ptricmds[1];\n\t\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\t\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\t\tpglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tpglVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t\t}\n\t\t}\n\n\t\tpglEnd();\n\t}\n}\n\n\nstatic int R_StudioBuildIndices( qboolean tri_strip, int vertexState )\n{\n\t// build in indices\n\tif( vertexState++ < 3 )\n\t{\n\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts;\n\t}\n\telse if( tri_strip )\n\t{\n\t\t// flip triangles between clockwise and counter clockwise\n\t\tif( vertexState & 1 )\n\t\t{\n\t\t\t// draw triangle [n-2 n-1 n]\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2;\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1;\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// draw triangle [n-1 n-2 n]\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1;\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 2;\n\t\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts;\n\t\t}\n\t}\n\telse\n\t{\n\t\t// draw triangle fan [0 n-1 n]\n\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - ( vertexState - 1 );\n\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts - 1;\n\t\tg_studio.arrayelems[g_studio.numelems++] = g_studio.numverts;\n\t}\n\n\treturn vertexState;\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t )\n{\n\tfloat\t*lv;\n\tint\ti;\n\tfloat alpha = tr.blend;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tint vertexState = 0;\n\t\tqboolean tri_strip = true;\n\n\t\tif( i < 0 )\n\t\t{\n\t\t\ttri_strip = false;\n\t\t\ti = -i;\n\t\t}\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tGLubyte *cl;\n\t\t\tcl = g_studio.arraycolor[g_studio.numverts];\n\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\n\t\t\tvertexState = R_StudioBuildIndices( tri_strip, vertexState );\n\n\t\t\tR_StudioSetColorArray( ptricmds, pstudionorms, cl );\n\n\t\t\tg_studio.arraycoord[g_studio.numverts][0] = ptricmds[2] * s;\n\t\t\tg_studio.arraycoord[g_studio.numverts][1] = ptricmds[3] * t;\n\n\t\t\tVectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] );\n\t\t\tg_studio.numverts++;\n\t\t}\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms )\n{\n\tfloat\t*lv;\n\tint\ti;\n\tfloat alpha = tr.blend;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tint vertexState = 0;\n\t\tqboolean tri_strip = true;\n\n\t\tif( i < 0 )\n\t\t{\n\t\t\ttri_strip = false;\n\t\t\ti = -i;\n\t\t}\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tGLubyte *cl;\n\t\t\tcl = g_studio.arraycolor[g_studio.numverts];\n\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\n\t\t\tvertexState = R_StudioBuildIndices( tri_strip, vertexState );\n\n\t\t\tR_StudioSetColorArray( ptricmds, pstudionorms, cl );\n\n\t\t\tg_studio.arraycoord[g_studio.numverts][0] = HalfToFloat( ptricmds[2] );\n\t\t\tg_studio.arraycoord[g_studio.numverts][1] = HalfToFloat( ptricmds[3] );\n\n\t\t\tVectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] );\n\t\t\tg_studio.numverts++;\n\t\t}\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale )\n{\n\tfloat\t*lv, *av;\n\tint\ti, idx;\n\tqboolean\tglowShell = (scale > 0.0f) ? true : false;\n\tvec3_t\tvert;\n\tfloat alpha = tr.blend;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tint vertexState = 0;\n\t\tqboolean tri_strip = true;\n\n\t\tif( i < 0 )\n\t\t{\n\t\t\ttri_strip = false;\n\t\t\ti = -i;\n\t\t}\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tGLubyte *cl;\n\t\t\tcl = g_studio.arraycolor[g_studio.numverts];\n\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\n\t\t\tvertexState = R_StudioBuildIndices( tri_strip, vertexState );\n\n\t\t\tif( glowShell )\n\t\t\t{\n\t\t\t\tidx = g_studio.normaltable[ptricmds[0]];\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tlv = g_studio.norms[ptricmds[0]];\n\n\t\t\t\tcl[0] = RI.currententity->curstate.rendercolor.r;\n\t\t\t\tcl[1] = RI.currententity->curstate.rendercolor.g;\n\t\t\t\tcl[2] = RI.currententity->curstate.rendercolor.b;\n\t\t\t\tcl[3] = 255;\n\n\t\t\t\tVectorMA( av, scale, lv, vert );\n\t\t\t\tVectorCopy( vert, g_studio.arrayverts[g_studio.numverts] );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tidx = ptricmds[1];\n\t\t\t\tR_StudioSetColorArray( ptricmds, pstudionorms, cl );\n\n\t\t\t\tVectorCopy( g_studio.verts[ptricmds[0]], g_studio.arrayverts[g_studio.numverts] );\n\t\t\t}\n\n\t\t\tg_studio.arraycoord[g_studio.numverts][0] = g_studio.chrome[idx][0] * s;\n\t\t\tg_studio.arraycoord[g_studio.numverts][1] = g_studio.chrome[idx][1] * t;\n\n\t\t\tg_studio.numverts++;\n\t\t}\n\t}\n}\n\nstatic void R_StudioDrawArrays( uint startverts, uint startelems )\n{\n\tpglEnableClientState( GL_VERTEX_ARRAY );\n\tpglVertexPointer( 3, GL_FLOAT, 12, g_studio.arrayverts );\n\n\tpglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\tpglTexCoordPointer( 2, GL_FLOAT, 0, g_studio.arraycoord );\n\n\tif( !( g_nForceFaceFlags & STUDIO_NF_CHROME ) )\n\t{\n\t\tpglEnableClientState( GL_COLOR_ARRAY );\n\t\tpglColorPointer( 4, GL_UNSIGNED_BYTE, 0, g_studio.arraycolor );\n\t}\n\n#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes\n\tif( pglDrawRangeElements )\n\t\tpglDrawRangeElements( GL_TRIANGLES, startverts, g_studio.numverts,\n\t\t\tg_studio.numelems - startelems, GL_UNSIGNED_SHORT, &g_studio.arrayelems[startelems] );\n\telse\n#endif\n\t\tpglDrawElements( GL_TRIANGLES, g_studio.numelems - startelems, GL_UNSIGNED_SHORT, &g_studio.arrayelems[startelems] );\n\tpglDisableClientState( GL_VERTEX_ARRAY );\n\tpglDisableClientState( GL_TEXTURE_COORD_ARRAY );\n\tif( !( g_nForceFaceFlags & STUDIO_NF_CHROME ) )\n\t\tpglDisableClientState( GL_COLOR_ARRAY );\n}\n\n/*\n===============\nR_StudioDrawPoints\n\n===============\n*/\nstatic void R_StudioDrawPoints( void )\n{\n\tint\t\ti, j, k, m_skinnum;\n\tfloat\t\tshellscale = 0.0f;\n\tqboolean\t\tneed_sort = false;\n\tbyte\t\t*pvertbone;\n\tbyte\t\t*pnormbone;\n\tvec3_t\t\t*pstudioverts;\n\tvec3_t\t\t*pstudionorms;\n\tmstudiotexture_t\t*ptexture;\n\tmstudiomesh_t\t*pmesh;\n\tshort\t\t*pskinref;\n\tfloat\t\tlv_tmp;\n\n\tif( !m_pStudioHeader ) return;\n\n\n\tg_studio.numverts = g_studio.numelems = 0;\n\n\tm_skinnum = RI.currententity->curstate.skin;\n\tptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex);\n\tpvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex);\n\tpnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex);\n\n\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex);\n\tpstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex);\n\tpstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex);\n\n\tpskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);\n\tif( m_skinnum > 0 && m_skinnum < m_pStudioHeader->numskinfamilies )\n\t\tpskinref += (m_skinnum * m_pStudioHeader->numskinref);\n\n\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 )\n\t{\n\t\tmstudioboneweight_t\t*pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex);\n\t\tmstudioboneweight_t\t*pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex);\n\t\tmatrix3x4\t\tskinMat;\n\n\t\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pvertweight[i], skinMat );\n\t\t\tMatrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.verts[i] );\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\n\t\tfor( i = 0; i < m_pSubModel->numnorms; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pnormweight[i], skinMat );\n\t\t\tMatrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] );\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] );\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\t}\n\n\t// generate shared normals for properly scaling glowing shell\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\tfloat factor = (1.0f / 128.0f);\n\t\tshellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor );\n\t\tR_StudioBuildNormalTable();\n\t\tR_StudioGenerateNormals();\n\t}\n\n\tfor( j = k = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tg_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags;\n\n\t\t// fill in sortedmesh info\n\t\tg_studio.meshes[j].flags = g_nFaceFlags;\n\t\tg_studio.meshes[j].mesh = &pmesh[j];\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE ))\n\t\t\tneed_sort = true;\n\n\t\tif( RI.currententity->curstate.rendermode == kRenderTransAdd )\n\t\t{\n\t\t\tfor( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )\n\t\t\t{\n\t\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorSet( g_studio.lightvalues[k], tr.blend, tr.blend, tr.blend );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )\n\t\t\t{\n\t\t\t\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ))\n\t\t\t\t\tR_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] );\n\t\t\t\telse R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms );\n\n\t\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] );\n\t\t\t}\n\t\t}\n\t}\n\n\tif( r_studio_sort_textures.value && need_sort )\n\t{\n\t\t// resort opaque and translucent meshes draw order\n\t\tqsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare );\n\t}\n\n\t// NOTE: rewind normals at start\n\tpstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex);\n\n\t// backface culling for left-handed weapons\n\tif( R_AllowFlipViewModel( RI.currententity ))\n\t{\n\t\ttr.fFlipViewModel = true;\n\t\tGL_Cull( GL_NONE );\n\t}\n\telse\n\t{\n\t\ttr.fFlipViewModel = false;\n\t\tGL_Cull( GL_FRONT );\n\t}\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tfloat\toldblend = tr.blend;\n\t\tuint startArrayVerts = g_studio.numverts;\n\t\tuint startArrayElems = g_studio.numelems;\n\t\tshort\t*ptricmds;\n\t\tfloat\ts, t;\n\n\t\tpmesh = g_studio.meshes[j].mesh;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\tg_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags;\n\n\t\ts = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;\n\t\tt = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\tpglEnable( GL_ALPHA_TEST );\n\t\t\tpglAlphaFunc( GL_GREATER, 0.5f );\n\t\t\tpglDepthMask( GL_TRUE );\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t\ttr.blend = 1.0f;\n\t\t}\n\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ))\n\t\t{\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t{\n\t\t\t\tpglBlendFunc( GL_ONE, GL_ONE );\n\t\t\t\tpglDepthMask( GL_FALSE );\n\t\t\t\tpglEnable( GL_BLEND );\n\t\t\t\tR_AllowFog( false );\n\t\t\t}\n\t\t\telse pglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\t}\n\n\t\tR_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] );\n\n\t\tif( r_studio_drawelements.value )\n\t\t{\n\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\tR_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale );\n\t\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))\n\t\t\t\tR_StudioBuildArrayFloatMesh( ptricmds, pstudionorms );\n\t\t\telse R_StudioBuildArrayNormalMesh( ptricmds, pstudionorms, s, t );\n\n\t\t\tR_StudioDrawArrays( startArrayVerts, startArrayElems );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\tR_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );\n\t\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))\n\t\t\t\tR_StudioDrawFloatMesh( ptricmds, pstudionorms );\n\t\t\telse R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t );\n\t\t}\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\t\t\tpglDisable( GL_ALPHA_TEST );\n\t\t}\n\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t{\n\t\t\tpglDepthMask( GL_TRUE );\n\t\t\tpglDisable( GL_BLEND );\n\t\t\tR_AllowFog( true );\n\t\t}\n\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\t\ttr.blend = oldblend;\n\t}\n}\n\n/*\n===============\nR_StudioDrawHulls\n\n===============\n*/\nstatic void R_StudioDrawHulls( void )\n{\n\tfloat\talpha, lv;\n\tint\ti, j;\n\n\tif( r_drawentities->value == 4 )\n\t\talpha = 0.5f;\n\telse alpha = 1.0f;\n\n\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\tfor( i = 0; i < m_pStudioHeader->numhitboxes; i++ )\n\t{\n\t\tmstudiobbox_t\t*pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex);\n\t\tvec3_t\t\ttmp, p[8];\n\n\t\tfor( j = 0; j < 8; j++ )\n\t\t{\n\t\t\ttmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0];\n\t\t\ttmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1];\n\t\t\ttmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2];\n\n\t\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pbbox[i].bone], tmp, p[j] );\n\t\t}\n\n\t\tj = (pbbox[i].group % 8);\n\n\t\tTriBegin( TRI_QUADS );\n\t\tTriColor4f( hullcolor[j][0], hullcolor[j][1], hullcolor[j][2], alpha );\n\n\t\tfor( j = 0; j < 6; j++ )\n\t\t{\n\t\t\tVectorClear( tmp );\n\t\t\ttmp[j % 3] = (j < 3) ? 1.0f : -1.0f;\n\t\t\tR_StudioLighting( &lv, pbbox[i].bone, 0, tmp );\n\n\t\t\tTriBrightness( lv );\n\t\t\tTriVertex3fv( p[boxpnt[j][0]] );\n\t\t\tTriVertex3fv( p[boxpnt[j][1]] );\n\t\t\tTriVertex3fv( p[boxpnt[j][2]] );\n\t\t\tTriVertex3fv( p[boxpnt[j][3]] );\n\t\t}\n\t\tTriEnd();\n\t}\n}\n\n/*\n===============\nR_StudioDrawAbsBBox\n\n===============\n*/\nstatic void R_StudioDrawAbsBBox( void )\n{\n\tvec3_t\tp[8], tmp;\n\tfloat\tlv;\n\tint\ti;\n\n\t// looks ugly, skip\n\tif( RI.currententity == tr.viewent )\n\t\treturn;\n\n\tif( !R_StudioComputeBBox( p ))\n\t\treturn;\n\n\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\tTriColor4f( 0.5f, 0.5f, 1.0f, 0.5f );\n\tTriRenderMode( kRenderTransAdd );\n\n\tTriBegin( TRI_QUADS );\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tVectorClear( tmp );\n\t\ttmp[i % 3] = (i < 3) ? 1.0f : -1.0f;\n\t\tR_StudioLighting( &lv, -1, 0, tmp );\n\n\t\tTriBrightness( lv );\n\t\tTriVertex3fv( p[boxpnt[i][0]] );\n\t\tTriVertex3fv( p[boxpnt[i][1]] );\n\t\tTriVertex3fv( p[boxpnt[i][2]] );\n\t\tTriVertex3fv( p[boxpnt[i][3]] );\n\t}\n\tTriEnd();\n\tTriRenderMode( kRenderNormal );\n}\n\n/*\n===============\nR_StudioDrawBones\n\n===============\n*/\nstatic void R_StudioDrawBones( void )\n{\n\tmstudiobone_t\t*pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\tvec3_t\t\tpoint;\n\tint\t\ti;\n\n\tpglDisable( GL_TEXTURE_2D );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tif( pbones[i].parent >= 0 )\n\t\t{\n\t\t\tpglPointSize( 3.0f );\n\t\t\tpglColor3f( 1, 0.7f, 0 );\n\t\t\tpglBegin( GL_LINES );\n\n\t\t\tMatrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point );\n\t\t\tpglVertex3fv( point );\n\t\t\tMatrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point );\n\t\t\tpglVertex3fv( point );\n\n\t\t\tpglEnd();\n\n\t\t\tpglColor3f( 0, 0, 0.8f );\n\t\t\tpglBegin( GL_POINTS );\n\t\t\tif( pbones[pbones[i].parent].parent != -1 )\n\t\t\t{\n\t\t\t\tMatrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point );\n\t\t\t\tpglVertex3fv( point );\n\t\t\t}\n\t\t\tMatrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point );\n\t\t\tpglVertex3fv( point );\n\t\t\tpglEnd();\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// draw parent bone node\n\t\t\tpglPointSize( 5.0f );\n\t\t\tpglColor3f( 0.8f, 0, 0 );\n\t\t\tpglBegin( GL_POINTS );\n\t\t\tMatrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point );\n\t\t\tpglVertex3fv( point );\n\t\t\tpglEnd();\n\t\t}\n\t}\n\n\tpglPointSize( 1.0f );\n\tpglEnable( GL_TEXTURE_2D );\n}\n\nstatic void R_StudioDrawAttachments( void )\n{\n\tint\ti;\n\n\tpglDisable( GL_TEXTURE_2D );\n\tpglDisable( GL_DEPTH_TEST );\n\n\tfor( i = 0; i < m_pStudioHeader->numattachments; i++ )\n\t{\n\t\tmstudioattachment_t\t*pattachments;\n\t\tvec3_t\t\tv[4];\n\n\t\tpattachments = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex);\n\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].org, v[0] );\n\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[0], v[1] );\n\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[1], v[2] );\n\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[2], v[3] );\n\n\t\tpglBegin( GL_LINES );\n\t\tpglColor3f( 1, 0, 0 );\n\t\tpglVertex3fv( v[0] );\n\t\tpglColor3f( 1, 1, 1 );\n\t\tpglVertex3fv (v[1] );\n\t\tpglColor3f( 1, 0, 0 );\n\t\tpglVertex3fv (v[0] );\n\t\tpglColor3f( 1, 1, 1 );\n\t\tpglVertex3fv (v[2] );\n\t\tpglColor3f( 1, 0, 0 );\n\t\tpglVertex3fv (v[0] );\n\t\tpglColor3f( 1, 1, 1 );\n\t\tpglVertex3fv( v[3] );\n\t\tpglEnd();\n\n\t\tpglPointSize( 5.0f );\n\t\tpglColor3f( 0, 1, 0 );\n\t\tpglBegin( GL_POINTS );\n\t\tpglVertex3fv( v[0] );\n\t\tpglEnd();\n\t\tpglPointSize( 1.0f );\n\t}\n\n\tpglEnable( GL_TEXTURE_2D );\n\tpglEnable( GL_DEPTH_TEST );\n}\n\n/*\n===============\nR_StudioSetRemapColors\n\n===============\n*/\nstatic void R_StudioSetRemapColors( int newTop, int newBottom )\n{\n\tif( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom ))\n\t\tm_fDoRemap = true;\n}\n\nvoid R_StudioResetPlayerModels( void )\n{\n\tmemset( g_studio.player_models, 0, sizeof( g_studio.player_models ));\n}\n\n/*\n===============\nR_StudioSetupPlayerModel\n\n===============\n*/\nstatic model_t *R_StudioSetupPlayerModel( int index )\n{\n\tplayer_info_t  *info = gEngfuncs.pfnPlayerInfo( index );\n\tplayer_model_t *state;\n\n\tif( index < 0 || index >= gp_cl->maxclients )\n\t\treturn NULL;\n\n\tstate = &g_studio.player_models[index];\n\n\t// g-cont: force for \"dev-mode\", non-local games and menu preview\n\tif(( gpGlobals->developer || !ENGINE_GET_PARM( PARM_LOCAL_GAME ) || !RI.drawWorld ) && info->model[0] )\n\t{\n\t\tif( Q_strcmp( state->name, info->model ))\n\t\t{\n\t\t\tQ_strncpy( state->name, info->model, sizeof( state->name ));\n\t\t\tstate->name[sizeof( state->name ) - 1] = 0;\n\n\t\t\tQ_snprintf( state->modelname, sizeof( state->modelname ), \"models/player/%s/%s.mdl\", info->model, info->model );\n\n\t\t\tif( gEngfuncs.fsapi->FileExists( state->modelname, false ))\n\t\t\t\tstate->model = gEngfuncs.Mod_ForName( state->modelname, false, true );\n\t\t\telse\n\t\t\t\tstate->model = NULL;\n\n\t\t\tif( !state->model )\n\t\t\t\tstate->model = RI.currententity->model;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( state->model != RI.currententity->model )\n\t\t\tstate->model = RI.currententity->model;\n\t\tstate->name[0] = 0;\n\t}\n\n\treturn state->model;\n}\n\n/*\n================\nR_GetEntityRenderMode\n\ncheck for texture flags\n================\n*/\nint R_GetEntityRenderMode( cl_entity_t *ent )\n{\n\tint              i, opaque, trans;\n\tmstudiotexture_t *ptexture;\n\tcl_entity_t      *oldent;\n\tmodel_t          *model = NULL;\n\tstudiohdr_t      *phdr;\n\n\toldent = RI.currententity;\n\tRI.currententity = ent;\n\n\tif( ent->player ) // check it for real playermodel\n\t\tmodel = R_StudioSetupPlayerModel( ent->curstate.number - 1 );\n\n\tif( !model )\n\t\tmodel = ent->model;\n\n\tRI.currententity = oldent;\n\n\tif(( phdr = gEngfuncs.Mod_Extradata( mod_studio, model )) == NULL )\n\t{\n\t\tif( R_ModelOpaque( ent->curstate.rendermode ))\n\t\t{\n\t\t\t// forcing to choose right sorting type\n\t\t\tif(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT ))\n\t\t\t\treturn kRenderTransAlpha;\n\t\t}\n\t\treturn ent->curstate.rendermode;\n\t}\n\tptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex);\n\n\tfor( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ )\n\t{\n\t\t// ignore chrome & additive it's just a specular-like effect\n\t\tif( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME ))\n\t\t\ttrans++;\n\t\telse opaque++;\n\t}\n\n\t// if model is more additive than opaque\n\tif( trans > opaque )\n\t\treturn kRenderTransAdd;\n\treturn ent->curstate.rendermode;\n}\n\n/*\n===============\nR_StudioClientEvents\n\n===============\n*/\nstatic void R_StudioClientEvents( void )\n{\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioevent_t\t*pevent;\n\tcl_entity_t\t*e = RI.currententity;\n\tint\t\ti, sequence;\n\tfloat\t\tend, start;\n\n\tif( g_studio.frametime == 0.0 )\n\t\treturn; // gamepaused\n\n\t// fill attachments with interpolated origin\n\tif( m_pStudioHeader->numattachments <= 0 )\n\t{\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] );\n\t}\n\n\tif( FBitSet( e->curstate.effects, EF_MUZZLEFLASH ))\n\t{\n\t\tdlight_t\t*el = gEngfuncs.CL_AllocElight( 0 );\n\n\t\tClearBits( e->curstate.effects, EF_MUZZLEFLASH );\n\t\tVectorCopy( e->attachment[0], el->origin );\n\t\tel->die = gp_cl->time + 0.05f;\n\t\tel->color.r = 255;\n\t\tel->color.g = 192;\n\t\tel->color.b = 64;\n\t\tel->decay = 320;\n\t\tel->radius = 24;\n\t}\n\n\tsequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 );\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;\n\n\t// no events for this animation\n\tif( pseqdesc->numevents == 0 )\n\t\treturn;\n\n\tend = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\tstart = end - e->curstate.framerate * gp_host->frametime * pseqdesc->fps;\n\tpevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex);\n\n\tif( e->latched.sequencetime == e->curstate.animtime )\n\t{\n\t\tif( !FBitSet( pseqdesc->flags, STUDIO_LOOPING ))\n\t\t\tstart = -0.01f;\n\t}\n\n\tfor( i = 0; i < pseqdesc->numevents; i++ )\n\t{\n\t\t// ignore all non-client-side events\n\t\tif( pevent[i].event < EVENT_CLIENT )\n\t\t\tcontinue;\n\n\t\tif( (float)pevent[i].frame > start && pevent[i].frame <= end )\n\t\t\tgEngfuncs.pfnStudioEvent( &pevent[i], e );\n\t}\n}\n\n/*\n===============\nR_StudioGetForceFaceFlags\n\n===============\n*/\nstatic int R_StudioGetForceFaceFlags( void )\n{\n\treturn g_nForceFaceFlags;\n}\n\n/*\n===============\nR_StudioSetForceFaceFlags\n\n===============\n*/\nstatic void R_StudioSetForceFaceFlags( int flags )\n{\n\tg_nForceFaceFlags = flags;\n}\n\n/*\n===============\npfnStudioSetHeader\n\n===============\n*/\nstatic void R_StudioSetHeader( studiohdr_t *pheader )\n{\n\tm_pStudioHeader = pheader;\n\tm_fDoRemap = false;\n}\n\n/*\n===============\nR_StudioSetRenderModel\n\n===============\n*/\nstatic void R_StudioSetRenderModel( model_t *model )\n{\n\tRI.currentmodel = model;\n}\n\n/*\n===============\nR_StudioSetupRenderer\n\n===============\n*/\nstatic void R_StudioSetupRenderer( int rendermode )\n{\n\tstudiohdr_t\t*phdr = m_pStudioHeader;\n\tint\t\ti;\n\n\tif( rendermode > kRenderTransAdd ) rendermode = 0;\n\tg_studio.rendermode = bound( 0, rendermode, kRenderTransAdd );\n\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglShadeModel( GL_SMOOTH );\n\n\t// a point to setup local to world transform for boneweighted models\n\tif( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO ))\n\t{\n\t\t// NOTE: extended boneinfo goes immediately after bones\n\t\tmstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t ));\n\n\t\tfor( i = 0; i < phdr->numbones; i++ )\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone );\n\t}\n}\n\n/*\n===============\nR_StudioRestoreRenderer\n\n===============\n*/\nstatic void R_StudioRestoreRenderer( void )\n{\n\tif( g_studio.rendermode != kRenderNormal )\n\t\tpglDisable( GL_BLEND );\n\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglShadeModel( GL_FLAT );\n\tm_fDoRemap = false;\n}\n\n/*\n===============\nR_StudioSetChromeOrigin\n\n===============\n*/\nstatic void R_StudioSetChromeOrigin( void )\n{\n\tVectorCopy( RI.vieworg, g_studio.chrome_origin );\n}\n\n/*\n===============\npfnIsHardware\n\nXash3D is always works in hardware mode\n===============\n*/\nstatic int pfnIsHardware( void )\n{\n\treturn 1;\t// 0 is Software, 1 is OpenGL, 2 is Direct3D\n}\n\n/*\n===============\nR_StudioDrawPointsShadow\n\n===============\n*/\nstatic void R_StudioDrawPointsShadow( void )\n{\n\tfloat\t\t*av, height;\n\tfloat\t\tvec_x, vec_y;\n\tmstudiomesh_t\t*pmesh;\n\tvec3_t\t\tpoint;\n\tint\t\ti, k;\n\n\tif( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW ))\n\t\treturn;\n\n\tif( glState.stencilEnabled )\n\t\tpglEnable( GL_STENCIL_TEST );\n\n\theight = g_studio.lightspot[2] + 1.0f;\n\tvec_x = -g_studio.lightvec[0] * 8.0f;\n\tvec_y = -g_studio.lightvec[1] * 8.0f;\n\n\tfor( k = 0; k < m_pSubModel->nummesh; k++ )\n\t{\n\t\tshort\t*ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + k;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\t\ti = -i;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpglBegin( GL_TRIANGLE_STRIP );\n\t\t\t}\n\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tpoint[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[2] = g_studio.lightspot[2] + 1.0f;\n\n\t\t\t\tpglVertex3fv( point );\n\t\t\t}\n\n\t\t\tpglEnd();\n\t\t}\n\t}\n\n\tif( glState.stencilEnabled )\n\t\tpglDisable( GL_STENCIL_TEST );\n}\n\n/*\n===============\nGL_StudioSetRenderMode\n\nset rendermode for studiomodel\n===============\n*/\nstatic void GL_StudioSetRenderMode( int rendermode )\n{\n\tswitch( rendermode )\n\t{\n\tcase kRenderNormal:\n\t\tbreak;\n\tcase kRenderTransColor:\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\tcase kRenderTransAdd:\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglColor4f( tr.blend, tr.blend, tr.blend, 1.0f );\n\t\tpglBlendFunc( GL_ONE, GL_ONE );\n\t\tpglDepthMask( GL_FALSE );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\tdefault:\n\t\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, tr.blend );\n\t\tpglDepthMask( GL_TRUE );\n\t\tpglEnable( GL_BLEND );\n\t\tbreak;\n\t}\n}\n\n/*\n===============\nGL_StudioDrawShadow\n\ng-cont: don't modify this code it's 100% matched with\noriginal GoldSrc code and used in some mods to enable\nstudio shadows with some asm tricks\n===============\n*/\nstatic void GL_StudioDrawShadow( void )\n{\n\tpglDepthMask( GL_TRUE );\n\n\tif( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT ))\n\t{\n\t\tfloat\tcolor = 1.0f - (tr.blend * 0.5f);\n\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglEnable( GL_BLEND );\n\t\tpglColor4f( 0.0f, 0.0f, 0.0f, 1.0f - color );\n\n\t\tpglDepthFunc( GL_LESS );\n\t\tR_StudioDrawPointsShadow();\n\t\tpglDepthFunc( GL_LEQUAL );\n\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_BLEND );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\tpglShadeModel( GL_SMOOTH );\n\t}\n}\n\n/*\n====================\nStudioRenderFinal\n\n====================\n*/\nstatic void R_StudioRenderFinal( void )\n{\n\tint\ti, rendermode;\n\n\trendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode;\n\tR_StudioSetupRenderer( rendermode );\n\n\tif( r_drawentities->value == 2 )\n\t{\n\t\tR_StudioDrawBones();\n\t}\n\telse if( r_drawentities->value == 3 )\n\t{\n\t\tR_StudioDrawHulls();\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < m_pStudioHeader->numbodyparts; i++ )\n\t\t{\n\t\t\tR_StudioSetupModel( i, (void**)&m_pBodyPart, (void**)&m_pSubModel );\n\n\t\t\tGL_StudioSetRenderMode( rendermode );\n\t\t\tR_StudioDrawPoints();\n\t\t\tGL_StudioDrawShadow();\n\t\t}\n\t}\n\n\tif( r_drawentities->value == 4 )\n\t{\n\t\tTriRenderMode( kRenderTransAdd );\n\t\tR_StudioDrawHulls( );\n\t\tTriRenderMode( kRenderNormal );\n\t}\n\n\tif( r_drawentities->value == 5 )\n\t{\n\t\tR_StudioDrawAbsBBox( );\n\t}\n\n\tif( r_drawentities->value == 6 )\n\t{\n\t\tR_StudioDrawAttachments();\n\t}\n\n\tif( r_drawentities->value == 7 )\n\t{\n\t\tvec3_t\torigin;\n\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_DEPTH_TEST );\n\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin );\n\n\t\tpglBegin( GL_LINES );\n\t\tpglColor3f( 1, 0.5, 0 );\n\t\tpglVertex3fv( origin );\n\t\tpglVertex3fv( g_studio.lightspot );\n\t\tpglEnd();\n\n\t\tpglBegin( GL_LINES );\n\t\tpglColor3f( 0, 0.5, 1 );\n\t\tVectorMA( g_studio.lightspot, -64.0f, g_studio.lightvec, origin );\n\t\tpglVertex3fv( g_studio.lightspot );\n\t\tpglVertex3fv( origin );\n\t\tpglEnd();\n\n\t\tpglPointSize( 5.0f );\n\t\tpglColor3f( 1, 0, 0 );\n\t\tpglBegin( GL_POINTS );\n\t\tpglVertex3fv( g_studio.lightspot );\n\t\tpglEnd();\n\t\tpglPointSize( 1.0f );\n\n\t\tpglEnable( GL_DEPTH_TEST );\n\t\tpglEnable( GL_TEXTURE_2D );\n\t}\n\n\tR_StudioRestoreRenderer();\n}\n\n/*\n====================\nStudioRenderModel\n\n====================\n*/\nstatic void R_StudioRenderModel( void )\n{\n\tR_StudioSetChromeOrigin();\n\tR_StudioSetForceFaceFlags( 0 );\n\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\tRI.currententity->curstate.renderfx = kRenderFxNone;\n\n\t\tR_StudioRenderFinal( );\n\n\t\tR_StudioSetForceFaceFlags( STUDIO_NF_CHROME );\n\t\tTriSpriteTexture( R_GetChromeSprite(), 0 );\n\t\tRI.currententity->curstate.renderfx = kRenderFxGlowShell;\n\n\t\tR_StudioRenderFinal( );\n\t}\n\telse\n\t{\n\t\tR_StudioRenderFinal( );\n\t}\n}\n\n/*\n====================\nStudioEstimateGait\n\n====================\n*/\nstatic void R_StudioEstimateGait( entity_state_t *pplayer )\n{\n\tvec3_t\test_velocity;\n\tfloat\tdt;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tif( dt == 0.0f || m_pPlayerInfo->renderframe == tr.realframecount )\n\t{\n\t\tm_flGaitMovement = 0;\n\t\treturn;\n\t}\n\n\tVectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity );\n\tVectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin );\n\tm_flGaitMovement = VectorLength( est_velocity );\n\n\tif( dt <= 0.0f || m_flGaitMovement / dt < 5.0f )\n\t{\n\t\tm_flGaitMovement = 0.0f;\n\t\test_velocity[0] = 0.0f;\n\t\test_velocity[1] = 0.0f;\n\t}\n\n\tif( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f )\n\t{\n\t\tfloat\tflYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\n\t\tflYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360;\n\t\tif( flYawDiff > 180.0f ) flYawDiff -= 360.0f;\n\t\tif( flYawDiff < -180.0f ) flYawDiff += 360.0f;\n\n\t\tif( dt < 0.25f )\n\t\t\tflYawDiff *= dt * 4.0f;\n\t\telse flYawDiff *= dt;\n\n\t\tm_pPlayerInfo->gaityaw += flYawDiff;\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360;\n\n\t\tm_flGaitMovement = 0.0f;\n\t}\n\telse\n\t{\n\t\tm_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F );\n\t\tif( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f;\n\t\tif( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f;\n\t}\n\n}\n\n/*\n====================\nStudioProcessGait\n\n====================\n*/\nstatic void R_StudioProcessGait( entity_state_t *pplayer )\n{\n\tmstudioseqdesc_t\t*pseqdesc;\n\tint\t\tiBlend;\n\tfloat\t\tdt, flYaw; // view direction relative to movement\n\n\tif( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq )\n\t\tRI.currententity->curstate.sequence = 0;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence;\n\n\tR_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] );\n\n\tRI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH];\n\tRI.currententity->curstate.blending[0] = iBlend;\n\tRI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0];\n\tRI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0];\n\tR_StudioEstimateGait( pplayer );\n\n\t// calc side to side turning\n\tflYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\tflYaw = flYaw - (int)(flYaw / 360) * 360;\n\tif( flYaw < -180.0f ) flYaw = flYaw + 360.0f;\n\tif( flYaw > 180.0f ) flYaw = flYaw - 360.0f;\n\n\tif( flYaw > 120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw - 180.0f;\n\t}\n\telse if( flYaw < -120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw + 180.0f;\n\t}\n\n\t// adjust torso\n\tRI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\tRI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw;\n\tif( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f;\n\tRI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW];\n\n\tif( pplayer->gaitsequence >= m_pStudioHeader->numseq )\n\t\tpplayer->gaitsequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence;\n\n\t// calc gait frame\n\tif( pseqdesc->linearmovement[0] > 0 )\n\t\tm_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes;\n\telse m_pPlayerInfo->gaitframe += pseqdesc->fps * dt;\n\n\t// do modulo\n\tm_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes;\n\tif( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes;\n}\n\n/*\n===============\nR_StudioDrawPlayer\n\n===============\n*/\nstatic int R_StudioDrawPlayer( int flags, entity_state_t *pplayer )\n{\n\tint\tm_nPlayerIndex;\n\talight_t\tlighting;\n\tvec3_t\tdir;\n\n\tm_nPlayerIndex = pplayer->number - 1;\n\n\tif( m_nPlayerIndex < 0 || m_nPlayerIndex >= gp_cl->maxclients )\n\t\treturn 0;\n\n\tRI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex );\n\tif( RI.currentmodel == NULL )\n\t\treturn 0;\n\n\tR_StudioSetHeader((studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tif( pplayer->gaitsequence )\n\t{\n\t\tvec3_t orig_angles;\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tVectorCopy( RI.currententity->angles, orig_angles );\n\n\t\tR_StudioProcessGait( pplayer );\n\n\t\tm_pPlayerInfo->gaitsequence = pplayer->gaitsequence;\n\t\tm_pPlayerInfo = NULL;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t\tVectorCopy( orig_angles, RI.currententity->angles );\n\t}\n\telse\n\t{\n\t\tRI.currententity->curstate.controller[0] = 127;\n\t\tRI.currententity->curstate.controller[1] = 127;\n\t\tRI.currententity->curstate.controller[2] = 127;\n\t\tRI.currententity->curstate.controller[3] = 127;\n\t\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\t\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\t\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\t\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\tr_stats.c_studio_models_drawn++;\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\tR_StudioSetupBones( RI.currententity );\n\tR_StudioSaveBones( );\n\n\tm_pPlayerInfo->renderframe = tr.realframecount;\n\tm_pPlayerInfo = NULL;\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index );\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// change body if it's a menu entity\n\t\tif( cl_himodels->value && ( RI.currentmodel != RI.currententity->model || !RI.drawWorld ))\n\t\t{\n\t\t\t// show highest resolution multiplayer model\n\t\t\tRI.currententity->curstate.body = 255;\n\t\t}\n\n\t\tif( !( !gpGlobals->developer && gp_cl->maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model ))\n\t\t\tRI.currententity->curstate.body = 1; // force helmet\n\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\n\t\t// get remap colors\n\t\tg_nTopColor = m_pPlayerInfo->topcolor;\n\t\tg_nBottomColor = m_pPlayerInfo->bottomcolor;\n\n\t\tif( g_nTopColor < 0 ) g_nTopColor = 0;\n\t\tif( g_nTopColor > 360 ) g_nTopColor = 360;\n\t\tif( g_nBottomColor < 0 ) g_nBottomColor = 0;\n\t\tif( g_nBottomColor > 360 ) g_nBottomColor = 360;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel( );\n\t\tm_pPlayerInfo = NULL;\n\n\t\tif( pplayer->weaponmodel )\n\t\t{\n\t\t\tcl_entity_t\tsaveent = *RI.currententity;\n\t\t\tmodel_t\t\t*pweaponmodel = CL_ModelHandle( pplayer->weaponmodel );\n\n\t\t\tm_pStudioHeader = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, pweaponmodel );\n\n\t\t\tR_StudioMergeBones( RI.currententity, pweaponmodel );\n\t\t\tR_StudioSetupLighting( &lighting );\n\t\t\tR_StudioRenderModel( );\n\t\t\tR_StudioCalcAttachments( );\n\n\t\t\t*RI.currententity = saveent;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\n/*\n===============\nR_StudioDrawModel\n\n===============\n*/\nstatic int R_StudioDrawModel( int flags )\n{\n\talight_t\tlighting;\n\tvec3_t\tdir;\n\n\tif( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer )\n\t{\n\t\tentity_state_t\tdeadplayer;\n\t\tint\t\tresult;\n\n\t\tif( RI.currententity->curstate.renderamt <= 0 ||\n\t\t\tRI.currententity->curstate.renderamt > gp_cl->maxclients )\n\t\t\treturn 0;\n\n\t\t// get copy of player\n\t\tdeadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 );\n\n\t\t// clear weapon, movement state\n\t\tdeadplayer.number = RI.currententity->curstate.renderamt;\n\t\tdeadplayer.weaponmodel = 0;\n\t\tdeadplayer.gaitsequence = 0;\n\n\t\tdeadplayer.movetype = MOVETYPE_NONE;\n\t\tVectorCopy( RI.currententity->curstate.angles, deadplayer.angles );\n\t\tVectorCopy( RI.currententity->curstate.origin, deadplayer.origin );\n\n\t\tg_studio.interpolate = false;\n\t\tresult = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player\n\t\tg_studio.interpolate = true;\n\n\t\treturn result;\n\t}\n\n\tR_StudioSetHeader((studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tR_StudioSetUpTransform( RI.currententity );\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\tr_stats.c_studio_models_drawn++;\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tif( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW )\n\t\tR_StudioMergeBones( RI.currententity, RI.currentmodel );\n\telse R_StudioSetupBones( RI.currententity );\n\n\tR_StudioSaveBones();\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index );\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\t// get remap colors\n\t\tg_nTopColor = RI.currententity->curstate.colormap & 0xFF;\n\t\tg_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel();\n\t}\n\n\treturn 1;\n}\n\n/*\n=================\nR_StudioDrawModelInternal\n=================\n*/\nstatic void R_StudioDrawModelInternal( cl_entity_t *e, int flags )\n{\n\tif( !RI.drawWorld )\n\t{\n\t\tif( e->player )\n\t\t\tR_StudioDrawPlayer( flags, &e->curstate );\n\t\telse R_StudioDrawModel( flags );\n\t}\n\telse\n\t{\n\t\t// select the properly method\n\t\tif( e->player )\n\t\t\tpStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));\n\t\telse pStudioDraw->StudioDrawModel( flags );\n\t}\n}\n\nstatic cl_entity_t *R_FindParentEntity( cl_entity_t *e, cl_entity_t **entities, uint num_entities )\n{\n\tuint i;\n\n\tfor( i = 0; i < num_entities; i++ )\n\t{\n\t\tif( entities[i]->index == e->curstate.aiment )\n\t\t\treturn entities[i];\n\t}\n\n\treturn NULL;\n}\n\n/*\n=================\nR_DrawStudioModel\n=================\n*/\nvoid R_DrawStudioModel( cl_entity_t *e )\n{\n\tif( FBitSet( RI.params, RP_ENVVIEW ))\n\t\treturn;\n\n\tR_StudioSetupTimings();\n\n\tif( e->player )\n\t{\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );\n\t}\n\telse if( e->curstate.movetype == MOVETYPE_FOLLOW )\n\t{\n\t\tcl_entity_t *parent = CL_GetEntityByIndex( e->curstate.aiment );\n\n\t\tif( !parent || !parent->model || parent->model->type != mod_studio )\n\t\t\treturn;\n\n\t\tparent = R_FindParentEntity( e, tr.draw_list->solid_entities, tr.draw_list->num_solid_entities );\n\n\t\tif( !parent )\n\t\t\tparent = R_FindParentEntity( e, tr.draw_list->trans_entities, tr.draw_list->num_trans_entities );\n\n\t\tif( parent )\n\t\t{\n\t\t\tRI.currententity = parent;\n\t\t\tR_StudioDrawModelInternal( RI.currententity, 0 );\n\t\t\tVectorCopy( RI.currententity->curstate.origin, e->curstate.origin );\n\t\t\tVectorCopy( RI.currententity->origin, e->origin );\n\t\t\tRI.currententity = e;\n\n\t\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );\n\t\t}\n\t}\n\telse\n\t{\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );\n\t}\n}\n\n/*\n=================\nR_RunViewmodelEvents\n=================\n*/\nvoid R_RunViewmodelEvents( void )\n{\n\tint\ti;\n\tvec3_t simorg;\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\tRI.currententity = tr.viewent;\n\n\tif( !RI.currententity->model || RI.currententity->model->type != mod_studio )\n\t\treturn;\n\n\tR_StudioSetupTimings();\n\n\tVectorCopy( gp_cl->simorg, simorg );\n\tfor( i = 0; i < 4; i++ )\n\t\tVectorCopy( simorg, RI.currententity->attachment[i] );\n\tRI.currentmodel = RI.currententity->model;\n\n\tR_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS );\n}\n\n/*\n=================\nR_GatherPlayerLight\n=================\n*/\nvoid R_GatherPlayerLight( void )\n{\n\tcl_entity_t\t*view = tr.viewent;\n\tcolorVec\t\tc;\n\n\tc = R_LightPoint( view->origin );\n\tgEngfuncs.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 );\n}\n\n/*\n=================\nR_DrawViewModel\n=================\n*/\nvoid R_DrawViewModel( void )\n{\n\tcl_entity_t\t*view = tr.viewent;\n\n\tR_GatherPlayerLight();\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\ttr.blend = CL_FxBlend( view ) / 255.0f;\n\tif( !R_ModelOpaque( view->curstate.rendermode ) && tr.blend <= 0.0f )\n\t\treturn; // invisible ?\n\n\tRI.currententity = view;\n\n\tif( !RI.currententity->model )\n\t\treturn;\n\n\t// adjust the depth range to prevent view model from poking into walls\n\tpglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin ));\n\tRI.currentmodel = RI.currententity->model;\n\n\tswitch( RI.currententity->model->type )\n\t{\n\tcase mod_alias:\n\t\tR_DrawAliasModel( RI.currententity );\n\t\tbreak;\n\tcase mod_studio:\n\t\tR_StudioSetupTimings();\n\t\tR_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER );\n\t\tbreak;\n\t}\n\n\t// restore depth range\n\tpglDepthRange( gldepthmin, gldepthmax );\n}\n\n/*\n====================\nR_StudioLoadTexture\n\nload model texture with unique name\n====================\n*/\nstatic void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture )\n{\n\tsize_t\t\tsize;\n\tint\t\tflags = 0;\n\tchar\t\ttexname[128], name[128], mdlname[128];\n\ttexture_t\t\t*tx = NULL;\n\tqboolean load_external = false;\n\n\tif( ptexture->flags & STUDIO_NF_NORMALMAP )\n\t\tflags |= (TF_NORMALMAP);\n\n\t// store some textures for remapping\n\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ) || !Q_strnicmp( ptexture->name, \"remap\", 5 ))\n\t{\n\t\tint\ti, size;\n\t\tchar\tval[6];\n\t\tbyte\t*pixels;\n\n\t\ti = mod->numtextures;\n\t\tmod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* ));\n\t\tsize = ptexture->width * ptexture->height + 768;\n\t\ttx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size );\n\t\tmod->textures[i] = tx;\n\n\t\t// store ranges into anim_min, anim_max etc\n\t\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ))\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_Base\", sizeof( tx->name ));\n\t\t\ttx->anim_min = PLATE_HUE_START; // topcolor start\n\t\t\ttx->anim_max = PLATE_HUE_END; // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\ttx->anim_total = SUIT_HUE_END;// bottomcolor end\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_User\", sizeof( tx->name )); // custom remapped\n\t\t\tQ_strncpy( val, ptexture->name + 7, 4 );\n\t\t\ttx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start\n\t\t\tQ_strncpy( val, ptexture->name + 11, 4 );\n\t\t\ttx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\tQ_strncpy( val, ptexture->name + 15, 4 );\n\t\t\ttx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end\n\t\t}\n\n\t\ttx->width = ptexture->width;\n\t\ttx->height = ptexture->height;\n\n\t\t// the pixels immediately follow the structures\n\t\tpixels = (byte *)phdr + ptexture->index;\n\t\tmemcpy( tx+1, pixels, size );\n\n\t\tptexture->flags |= STUDIO_NF_COLORMAP;\t// yes, this is colormap image\n\t\tflags |= TF_FORCE_COLOR;\n\n\t\tmod->numtextures++;\t// done\n\t}\n\n\tQ_strncpy( mdlname, mod->name, sizeof( mdlname ));\n\tCOM_FileBase( ptexture->name, name, sizeof( name ));\n\tCOM_StripExtension( mdlname );\n\n\tif( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS ))\n\t\tSetBits( flags, TF_NOMIPMAP );\n\n\tif( FBitSet( gp_host->features, ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED ))\n\t\tflags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing\n\n\t// NOTE: colormaps must have the palette for properly work. Ignore them\n\tif( Mod_AllowMaterials( ) && !FBitSet( ptexture->flags, STUDIO_NF_COLORMAP ))\n\t{\n\t\tif( R_SearchForTextureReplacement( texname, sizeof( texname ), mdlname, \"materials/%s/%s.tga\", mdlname, name ))\n\t\t{\n\t\t\tint gl_texturenum = GL_LoadTexture( texname, NULL, 0, flags );\n\t\t\tR_TextureReplacementReport( mdlname, gl_texturenum, texname );\n\t\t\tif(( load_external = gl_texturenum != 0 ))\n\t\t\t\tptexture->index = gl_texturenum;\n\t\t}\n\t}\n\n\tif( !load_external )\n\t{\n\t\t// NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it\n\t\tgEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index);\n\t\tsize = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768;\n\n\t\t// build the texname\n\t\tQ_snprintf( texname, sizeof( texname ), \"#%s/%s.mdl\", mdlname, name );\n\t\tptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags );\n\t}\n\n\tif( !ptexture->index )\n\t{\n\t\tptexture->index = tr.defaultTexture;\n\t}\n\telse if( tx )\n\t{\n\t\t// duplicate texnum for easy acess\n\t\ttx->gl_texturenum = ptexture->index;\n\t}\n}\n\n/*\n=================\nMod_StudioLoadTextures\n=================\n*/\nvoid Mod_StudioLoadTextures( model_t *mod, void *data )\n{\n\tstudiohdr_t\t*phdr = (studiohdr_t *)data;\n\tmstudiotexture_t\t*ptexture;\n\tint\t\ti;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);\n\tif( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS )\n\t{\n\t\tfor( i = 0; i < phdr->numtextures; i++ )\n\t\t\tR_StudioLoadTexture( mod, phdr, &ptexture[i] );\n\t}\n}\n\n/*\n=================\nMod_StudioUnloadTextures\n=================\n*/\nvoid Mod_StudioUnloadTextures( void *data )\n{\n\tstudiohdr_t\t*phdr = (studiohdr_t *)data;\n\tmstudiotexture_t\t*ptexture;\n\tint\t\ti;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);\n\n\t// release all textures\n\tfor( i = 0; i < phdr->numtextures; i++ )\n\t{\n\t\tif( ptexture[i].index == tr.defaultTexture )\n\t\t\tcontinue;\n\t\tGL_FreeTexture( ptexture[i].index );\n\t}\n}\n\nstatic model_t *pfnModelHandle( int modelindex )\n{\n\tif( modelindex < 0 || modelindex >= MAX_MODELS )\n\t\treturn NULL;\n\treturn CL_ModelHandle( modelindex );\n}\n\nstatic void *pfnMod_CacheCheck( struct cache_user_s *c )\n{\n\treturn gEngfuncs.Mod_CacheCheck( c );\n}\n\nstatic void *pfnMod_StudioExtradata( model_t *mod )\n{\n\treturn gEngfuncs.Mod_Extradata( mod_studio, mod );\n}\n\nstatic void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu )\n{\n\tgEngfuncs.Mod_LoadCacheFile( path, cu );\n}\n\nstatic cvar_t *pfnGetCvarPointer( const char *name )\n{\n\treturn (cvar_t*)gEngfuncs.pfnGetCvarPointer( name, 0 );\n}\n\nstatic void *pfnMod_Calloc( int number, size_t size )\n{\n\treturn gEngfuncs.Mod_Calloc( number, size );\n}\n\nstatic engine_studio_api_t gStudioAPI =\n{\n\tpfnMod_Calloc,\n\tpfnMod_CacheCheck,\n\tpfnMod_LoadCacheFile,\n\tpfnMod_ForName,\n\tpfnMod_StudioExtradata,\n\tpfnModelHandle,\n\tpfnGetCurrentEntity,\n\tpfnPlayerInfo,\n\tR_StudioGetPlayerState,\n\tpfnGetViewEntity,\n\tpfnGetEngineTimes,\n\tpfnGetCvarPointer,\n\tpfnGetViewInfo,\n\tR_GetChromeSprite,\n\tpfnGetModelCounters,\n\tpfnGetAliasScale,\n\tpfnStudioGetBoneTransform,\n\tpfnStudioGetLightTransform,\n\tpfnStudioGetAliasTransform,\n\tpfnStudioGetRotationMatrix,\n\tR_StudioSetupModel,\n\tR_StudioCheckBBox,\n\tR_StudioDynamicLight,\n\tR_StudioEntityLight,\n\tR_StudioSetupLighting,\n\tR_StudioDrawPoints,\n\tR_StudioDrawHulls,\n\tR_StudioDrawAbsBBox,\n\tR_StudioDrawBones,\n\t(void*)R_StudioSetupSkin,\n\tR_StudioSetRemapColors,\n\tR_StudioSetupPlayerModel,\n\tR_StudioClientEvents,\n\tR_StudioGetForceFaceFlags,\n\tR_StudioSetForceFaceFlags,\n\t(void*)R_StudioSetHeader,\n\tR_StudioSetRenderModel,\n\tR_StudioSetupRenderer,\n\tR_StudioRestoreRenderer,\n\tR_StudioSetChromeOrigin,\n\tpfnIsHardware,\n\tGL_StudioDrawShadow,\n\tGL_StudioSetRenderMode,\n\tR_StudioSetRenderamt,\n\tR_StudioSetCullState,\n\tR_StudioRenderShadow,\n};\n\nstatic r_studio_interface_t gStudioDraw =\n{\n\tSTUDIO_INTERFACE_VERSION,\n\tR_StudioDrawModel,\n\tR_StudioDrawPlayer,\n};\n\n/*\n===============\nCL_InitStudioAPI\n\nInitialize client studio\n===============\n*/\nvoid CL_InitStudioAPI( void )\n{\n\tpStudioDraw = &gStudioDraw;\n\n\t// trying to grab them from client.dll\n\tcl_righthand = gEngfuncs.pfnGetCvarPointer( \"cl_righthand\", 0 );\n\n\t// Xash will be used internal StudioModelRenderer\n\tif( gEngfuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI ))\n\t\treturn;\n\n\t// NOTE: we always return true even if game interface was not correct\n\t// because we need Draw our StudioModels\n\t// just restore pointer to builtin function\n\tpStudioDraw = &gStudioDraw;\n}\n"
  },
  {
    "path": "ref/gl/gl_triapi.c",
    "content": "/*\ngl_triapi.c - TriAPI draw methods\nCopyright (C) 2011 Uncle Mike\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"gl_local.h\"\n#include \"const.h\"\n\nstatic struct\n{\n\tint\t\trenderMode;\t\t// override kRenderMode from TriAPI\n\tvec4_t\t\ttriRGBA;\n} ds;\n\n/*\n===============================================================\n\n  TRIAPI IMPLEMENTATION\n\n===============================================================\n*/\n/*\n=============\nTriRenderMode\n\nset rendermode\n=============\n*/\nvoid TriRenderMode( int mode )\n{\n\tds.renderMode = mode;\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\tswitch( mode )\n\t{\n\tcase kRenderNormal:\n\t\tpglDisable( GL_BLEND );\n\t\tpglDepthMask( GL_TRUE );\n\t\tbreak;\n\tcase kRenderTransAlpha:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglDepthMask( GL_FALSE );\n\t\tbreak;\n\tcase kRenderTransColor:\n\tcase kRenderTransTexture:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tbreak;\n\tcase kRenderGlow:\n\tcase kRenderTransAdd:\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\tpglEnable( GL_BLEND );\n\t\tpglDepthMask( GL_FALSE );\n\t\tbreak;\n\t}\n}\n\n/*\n=============\nTriBegin\n\nbegin triangle sequence\n=============\n*/\nvoid TriBegin( int mode )\n{\n\tswitch( mode )\n\t{\n\tcase TRI_POINTS:\n\t\tmode = GL_POINTS;\n\t\tbreak;\n\tcase TRI_TRIANGLES:\n\t\tmode = GL_TRIANGLES;\n\t\tbreak;\n\tcase TRI_TRIANGLE_FAN:\n\t\tmode = GL_TRIANGLE_FAN;\n\t\tbreak;\n\tcase TRI_QUADS:\n\t\tmode = GL_QUADS;\n\t\tbreak;\n\tcase TRI_LINES:\n\t\tmode = GL_LINES;\n\t\tbreak;\n\tcase TRI_TRIANGLE_STRIP:\n\t\tmode = GL_TRIANGLE_STRIP;\n\t\tbreak;\n\tcase TRI_QUAD_STRIP:\n\t\tmode = GL_QUAD_STRIP;\n\t\tbreak;\n\tcase TRI_POLYGON:\n\tdefault:\n\t\tmode = GL_POLYGON;\n\t\tbreak;\n\t}\n\n\tpglBegin( mode );\n}\n\n/*\n=============\nTriEnd\n\ndraw triangle sequence\n=============\n*/\nvoid TriEnd( void )\n{\n\tpglEnd( );\n}\n\n/*\n=============\n_TriColor4f\n\n=============\n*/\nvoid _TriColor4f( float r, float g, float b, float a )\n{\n\tpglColor4f( r, g, b, a );\n}\n\n/*\n=============\n_TriColor4f\n\n=============\n*/\nvoid _TriColor4ub( byte r, byte g, byte b, byte a )\n{\n\tpglColor4ub( r, g, b, a );\n}\n\n\n/*\n=============\nTriColor4ub\n\n=============\n*/\nvoid TriColor4ub( byte r, byte g, byte b, byte a )\n{\n\tds.triRGBA[0] = r * (1.0f / 255.0f);\n\tds.triRGBA[1] = g * (1.0f / 255.0f);\n\tds.triRGBA[2] = b * (1.0f / 255.0f);\n\tds.triRGBA[3] = a * (1.0f / 255.0f);\n\n\t_TriColor4f( ds.triRGBA[0], ds.triRGBA[1], ds.triRGBA[2], 1.0f );\n}\n\n/*\n=================\nTriColor4f\n=================\n*/\nvoid TriColor4f( float r, float g, float b, float a )\n{\n\tif( ds.renderMode == kRenderTransAlpha )\n\t\tTriColor4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f );\n\telse _TriColor4f( r * a, g * a, b * a, 1.0 );\n\n\tds.triRGBA[0] = r;\n\tds.triRGBA[1] = g;\n\tds.triRGBA[2] = b;\n\tds.triRGBA[3] = a;\n}\n\n/*\n=============\nTriTexCoord2f\n\n=============\n*/\nvoid TriTexCoord2f( float u, float v )\n{\n\tpglTexCoord2f( u, v );\n}\n\n/*\n=============\nTriVertex3fv\n\n=============\n*/\nvoid TriVertex3fv( const float *v )\n{\n\tpglVertex3fv( v );\n}\n\n/*\n=============\nTriVertex3f\n\n=============\n*/\nvoid TriVertex3f( float x, float y, float z )\n{\n\tpglVertex3f( x, y, z );\n}\n\n/*\n=============\nTriWorldToScreen\n\nconvert world coordinates (x,y,z) into screen (x, y)\n=============\n*/\nint TriWorldToScreen( const float *world, float *screen )\n{\n\tint\tretval;\n\n\tretval = R_WorldToScreen( world, screen );\n\n\tscreen[0] =  0.5f * screen[0] * (float)RI.viewport[2];\n\tscreen[1] = -0.5f * screen[1] * (float)RI.viewport[3];\n\tscreen[0] += 0.5f * (float)RI.viewport[2];\n\tscreen[1] += 0.5f * (float)RI.viewport[3];\n\n\treturn retval;\n}\n\n/*\n=============\nTriSpriteTexture\n\nbind current texture\n=============\n*/\nint TriSpriteTexture( model_t *pSpriteModel, int frame )\n{\n\tint\tgl_texturenum;\n\n\tif(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 )\n\t\treturn 0;\n\n\tif( gl_texturenum <= 0 || gl_texturenum >= MAX_TEXTURES )\n\t\tgl_texturenum = tr.defaultTexture;\n\n\tGL_Bind( XASH_TEXTURE0, gl_texturenum );\n\n\treturn 1;\n}\n\n/*\n=============\nTriFog\n\nenables global fog on the level\n=============\n*/\nvoid TriFog( float flFogColor[3], float flStart, float flEnd, int bOn )\n{\n\t// overrided by internal fog\n\tif( RI.fogEnabled || !gl_fog.value ) return;\n\tRI.fogCustom = bOn;\n\n\t// check for invalid parms\n\tif( flEnd <= flStart )\n\t{\n\t\tglState.isFogEnabled = RI.fogCustom = false;\n\t\tpglDisable( GL_FOG );\n\t\treturn;\n\t}\n\n\tif( RI.fogCustom )\n\t\tpglEnable( GL_FOG );\n\telse pglDisable( GL_FOG );\n\n\t// copy fog params\n\tRI.fogColor[0] = flFogColor[0] / 255.0f;\n\tRI.fogColor[1] = flFogColor[1] / 255.0f;\n\tRI.fogColor[2] = flFogColor[2] / 255.0f;\n\tRI.fogColor[3] = 1.0f;\n\n\tRI.fogStart = flStart;\n\tRI.fogEnd = flEnd;\n\n\tif( RI.fogDensity > 0.0f )\n\t{\n\t\tpglFogi( GL_FOG_MODE, GL_EXP2 );\n\t\tpglFogf( GL_FOG_DENSITY, RI.fogDensity );\n\t}\n\telse\n\t{\n\t\tpglFogi( GL_FOG_MODE, GL_LINEAR );\n\t\tRI.fogSkybox = true;\n\t}\n\n\tpglFogfv( GL_FOG_COLOR, RI.fogColor );\n\tpglFogf( GL_FOG_START, RI.fogStart );\n\tpglFogf( GL_FOG_END, RI.fogEnd );\n\tpglHint( GL_FOG_HINT, GL_NICEST );\n}\n\n/*\n=============\nTriGetMatrix\n\nvery strange export\n=============\n*/\nvoid TriGetMatrix( const int pname, float *matrix )\n{\n\tpglGetFloatv( pname, matrix );\n}\n\n/*\n=============\nTriForParams\n\n=============\n*/\nvoid TriFogParams( float flDensity, int iFogSkybox )\n{\n\tRI.fogDensity = flDensity;\n\tRI.fogSkybox = iFogSkybox;\n}\n\n/*\n=============\nTriCullFace\n\n=============\n*/\nvoid TriCullFace( TRICULLSTYLE mode )\n{\n\tint glMode;\n\n\tswitch( mode )\n\t{\n\tcase TRI_FRONT:\n\t\tglMode = GL_FRONT;\n\t\tbreak;\n\tdefault:\n\t\tglMode = GL_NONE;\n\t\tbreak;\n\t}\n\n\tGL_Cull( glMode );\n}\n\n/*\n=============\nTriBrightness\n=============\n*/\nvoid TriBrightness( float brightness )\n{\n\tfloat\tr, g, b;\n\n\tr = ds.triRGBA[0] * ds.triRGBA[3] * brightness;\n\tg = ds.triRGBA[1] * ds.triRGBA[3] * brightness;\n\tb = ds.triRGBA[2] * ds.triRGBA[3] * brightness;\n\n\t_TriColor4f( r, g, b, 1.0f );\n}\n\n"
  },
  {
    "path": "ref/gl/gl_warp.c",
    "content": "/*\ngl_warp.c - sky and water polygons\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n\n#include \"gl_local.h\"\n#include \"wadfile.h\"\n\n#define SKYCLOUDS_QUALITY\t12\n#define MAX_CLIP_VERTS\t128 // skybox clip vertices\n#define TURBSCALE\t\t( 256.0f / ( M_PI2 ))\n\nstatic const int r_skyTexOrder[SKYBOX_MAX_SIDES] = { 0, 2, 1, 3, 4, 5 };\n\nstatic const vec3_t skyclip[SKYBOX_MAX_SIDES] =\n{\n{  1,  1,  0 },\n{  1, -1,  0 },\n{  0, -1,  1 },\n{  0,  1,  1 },\n{  1,  0,  1 },\n{ -1,  0,  1 }\n};\n\n// 1 = s, 2 = t, 3 = 2048\nstatic const int st_to_vec[SKYBOX_MAX_SIDES][3] =\n{\n{  3, -1,  2 },\n{ -3,  1,  2 },\n{  1,  3,  2 },\n{ -1, -3,  2 },\n{ -2, -1,  3 },  // 0 degrees yaw, look straight up\n{  2, -1, -3 }   // look straight down\n};\n\n// s = [0]/[2], t = [1]/[2]\nstatic const int vec_to_st[SKYBOX_MAX_SIDES][3] =\n{\n{ -2,  3,  1 },\n{  2,  3, -1 },\n{  1,  3,  2 },\n{ -1,  3, -2 },\n{ -2, -1,  3 },\n{ -2,  1, -3 }\n};\n\n// speed up sin calculations\nstatic float r_turbsin[] =\n{\n#include \"warpsin.h\"\n};\n\n#define RIPPLES_CACHEWIDTH_BITS 7\n#define RIPPLES_CACHEWIDTH ( 1 << RIPPLES_CACHEWIDTH_BITS )\n#define RIPPLES_CACHEWIDTH_MASK (( RIPPLES_CACHEWIDTH ) - 1 )\n#define RIPPLES_TEXSIZE ( RIPPLES_CACHEWIDTH * RIPPLES_CACHEWIDTH )\n#define RIPPLES_TEXSIZE_MASK ( RIPPLES_TEXSIZE - 1 )\n\nSTATIC_ASSERT( RIPPLES_TEXSIZE == 0x4000, \"fix the algorithm to work with custom resolution\" );\n\nstatic struct\n{\n\tdouble time;\n\tdouble oldtime;\n\n\tshort *curbuf, *oldbuf;\n\tshort buf[2][RIPPLES_TEXSIZE];\n\tqboolean update;\n\n\tuint32_t texture[RIPPLES_TEXSIZE];\n} g_ripple;\n\n\nstatic void DrawSkyPolygon( int nump, vec3_t vecs )\n{\n\tint\ti, j, axis;\n\tfloat\ts, t, dv, *vp;\n\tvec3_t\tv, av;\n\n\t// decide which face it maps to\n\tVectorClear( v );\n\n\tfor( i = 0, vp = vecs; i < nump; i++, vp += 3 )\n\t\tVectorAdd( vp, v, v );\n\n\tav[0] = fabs( v[0] );\n\tav[1] = fabs( v[1] );\n\tav[2] = fabs( v[2] );\n\n\tif( av[0] > av[1] && av[0] > av[2] )\n\t\taxis = (v[0] < 0) ? 1 : 0;\n\telse if( av[1] > av[2] && av[1] > av[0] )\n\t\taxis = (v[1] < 0) ? 3 : 2;\n\telse axis = (v[2] < 0) ? 5 : 4;\n\n\t// project new texture coords\n\tfor( i = 0; i < nump; i++, vecs += 3 )\n\t{\n\t\tj = vec_to_st[axis][2];\n\t\tdv = (j > 0) ? vecs[j-1] : -vecs[-j-1];\n\n\t\tif( dv == 0.0f ) continue;\n\n\t\tj = vec_to_st[axis][0];\n\t\ts = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv;\n\n\t\tj = vec_to_st[axis][1];\n\t\tt = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv;\n\n\t\tif( s < RI.skyMins[0][axis] ) RI.skyMins[0][axis] = s;\n\t\tif( t < RI.skyMins[1][axis] ) RI.skyMins[1][axis] = t;\n\t\tif( s > RI.skyMaxs[0][axis] ) RI.skyMaxs[0][axis] = s;\n\t\tif( t > RI.skyMaxs[1][axis] ) RI.skyMaxs[1][axis] = t;\n\t}\n}\n\n/*\n==============\nClipSkyPolygon\n==============\n*/\nstatic void ClipSkyPolygon( int nump, vec3_t vecs, int stage )\n{\n\tconst float\t*norm;\n\tfloat\t\t*v, d, e;\n\tqboolean\t\tfront, back;\n\tfloat\t\tdists[MAX_CLIP_VERTS + 1];\n\tint\t\tsides[MAX_CLIP_VERTS + 1];\n\tvec3_t\t\tnewv[2][MAX_CLIP_VERTS + 1];\n\tint\t\tnewc[2];\n\tint\t\ti, j;\n\n\tif( nump > MAX_CLIP_VERTS )\n\t\tgEngfuncs.Host_Error( \"%s: MAX_CLIP_VERTS\\n\", __func__ );\nloc1:\n\tif( stage == 6 )\n\t{\n\t\t// fully clipped, so draw it\n\t\tDrawSkyPolygon( nump, vecs );\n\t\treturn;\n\t}\n\n\tfront = back = false;\n\tnorm = skyclip[stage];\n\tfor( i = 0, v = vecs; i < nump; i++, v += 3 )\n\t{\n\t\td = DotProduct( v, norm );\n\t\tif( d > ON_EPSILON )\n\t\t{\n\t\t\tfront = true;\n\t\t\tsides[i] = SIDE_FRONT;\n\t\t}\n\t\telse if( d < -ON_EPSILON )\n\t\t{\n\t\t\tback = true;\n\t\t\tsides[i] = SIDE_BACK;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsides[i] = SIDE_ON;\n\t\t}\n\t\tdists[i] = d;\n\t}\n\n\tif( !front || !back )\n\t{\n\t\t// not clipped\n\t\tstage++;\n\t\tgoto loc1;\n\t}\n\n\t// clip it\n\tsides[i] = sides[0];\n\tdists[i] = dists[0];\n\tVectorCopy( vecs, ( vecs + ( i * 3 )));\n\tnewc[0] = newc[1] = 0;\n\n\tfor( i = 0, v = vecs; i < nump; i++, v += 3 )\n\t{\n\t\tswitch( sides[i] )\n\t\t{\n\t\tcase SIDE_FRONT:\n\t\t\tVectorCopy( v, newv[0][newc[0]] );\n\t\t\tnewc[0]++;\n\t\t\tbreak;\n\t\tcase SIDE_BACK:\n\t\t\tVectorCopy( v, newv[1][newc[1]] );\n\t\t\tnewc[1]++;\n\t\t\tbreak;\n\t\tcase SIDE_ON:\n\t\t\tVectorCopy( v, newv[0][newc[0]] );\n\t\t\tnewc[0]++;\n\t\t\tVectorCopy( v, newv[1][newc[1]] );\n\t\t\tnewc[1]++;\n\t\t\tbreak;\n\t\t}\n\n\t\tif( sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i] )\n\t\t\tcontinue;\n\n\t\td = dists[i] / ( dists[i] - dists[i+1] );\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\te = v[j] + d * ( v[j+3] - v[j] );\n\t\t\tnewv[0][newc[0]][j] = e;\n\t\t\tnewv[1][newc[1]][j] = e;\n\t\t}\n\t\tnewc[0]++;\n\t\tnewc[1]++;\n\t}\n\n\t// continue\n\tClipSkyPolygon( newc[0], newv[0][0], stage + 1 );\n\tClipSkyPolygon( newc[1], newv[1][0], stage + 1 );\n}\n\nstatic void MakeSkyVec( float s, float t, int axis )\n{\n\tint\tj, k, farclip;\n\tvec3_t\tv, b;\n\n\tfarclip = RI.farClip;\n\n\tb[0] = s * (farclip >> 1);\n\tb[1] = t * (farclip >> 1);\n\tb[2] = (farclip >> 1);\n\n\tfor( j = 0; j < 3; j++ )\n\t{\n\t\tk = st_to_vec[axis][j];\n\t\tv[j] = (k < 0) ? -b[-k-1] : b[k-1];\n\t\tv[j] += RI.cullorigin[j];\n\t}\n\n\t// avoid bilerp seam\n\ts = (s + 1.0f) * 0.5f;\n\tt = (t + 1.0f) * 0.5f;\n\n\tif( GL_Support( GL_CLAMPTOEDGE_EXT ))\n\t{\n\t\ts = bound( 0.0f, s, 1.0f );\n\t\tt = bound( 0.0f, t, 1.0f );\n\t}\n\telse\n\t{\n\t\ts = bound( 1.0f / 512.0f, s, 511.0f / 512.0f );\n\t\tt = bound( 1.0f / 512.0f, t, 511.0f / 512.0f );\n\t}\n\n\tt = 1.0f - t;\n\n\tpglTexCoord2f( s, t );\n\tpglVertex3fv( v );\n}\n\n/*\n==============\nR_ClearSkyBox\n==============\n*/\nvoid R_ClearSkyBox( void )\n{\n\tint\ti;\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tRI.skyMins[0][i] = RI.skyMins[1][i] = 9999999.0f;\n\t\tRI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999.0f;\n\t}\n}\n\n/*\n=================\nR_AddSkyBoxSurface\n=================\n*/\nvoid R_AddSkyBoxSurface( msurface_t *fa )\n{\n\tvec3_t\tverts[MAX_CLIP_VERTS];\n\tglpoly2_t\t*p;\n\tfloat\t*v;\n\tint\ti;\n\n\tif( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && fa->polys && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX ))\n\t{\n\t\tglpoly2_t\t*p = fa->polys;\n\n\t\t// draw the sky poly\n\t\tpglBegin( GL_POLYGON );\n\t\tfor( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE )\n\t\t{\n\t\t\tpglTexCoord2f( v[3], v[4] );\n\t\t\tpglVertex3fv( v );\n\t\t}\n\t\tpglEnd ();\n\t}\n\n\t// calculate vertex values for sky box\n\tfor( p = fa->polys; p; p = p->next )\n\t{\n\t\tfor( i = 0; i < p->numverts; i++ )\n\t\t\tVectorSubtract( p->verts[i], RI.cullorigin, verts[i] );\n\t\tClipSkyPolygon( p->numverts, verts[0], 0 );\n\t}\n}\n\n/*\n==============\nR_UnloadSkybox\n\nUnload previous skybox\n==============\n*/\nvoid R_UnloadSkybox( void )\n{\n\tint\ti;\n\n\t// release old skybox\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tif( !tr.skyboxTextures[i] ) continue;\n\t\tGL_FreeTexture( tr.skyboxTextures[i] );\n\t}\n\n\ttr.skyboxbasenum = SKYBOX_BASE_NUM;\t// set skybox base (to let some mods load hi-res skyboxes)\n\n\tmemset( tr.skyboxTextures, 0, sizeof( tr.skyboxTextures ));\n\tClearBits( tr.world->flags, FWORLD_CUSTOM_SKYBOX );\n}\n\n/*\n==============\nR_DrawSkybox\n==============\n*/\nvoid R_DrawSkyBox( void )\n{\n\tint\ti;\n\n\tRI.isSkyVisible = true;\n\n\t// don't fogging skybox (this fix old Half-Life bug)\n\tif( !RI.fogSkybox ) R_AllowFog( false );\n\n\tif( RI.fogEnabled )\n\t\tpglFogf( GL_FOG_DENSITY, RI.fogDensity * 0.5f );\n\n\tpglDisable( GL_BLEND );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tif( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] )\n\t\t\tcontinue;\n\n\t\tif( tr.skyboxTextures[r_skyTexOrder[i]] )\n\t\t\tGL_Bind( XASH_TEXTURE0, tr.skyboxTextures[r_skyTexOrder[i]] );\n\t\telse GL_Bind( XASH_TEXTURE0, tr.grayTexture ); // stub\n\n\t\tpglBegin( GL_QUADS );\n\t\tMakeSkyVec( RI.skyMins[0][i], RI.skyMins[1][i], i );\n\t\tMakeSkyVec( RI.skyMins[0][i], RI.skyMaxs[1][i], i );\n\t\tMakeSkyVec( RI.skyMaxs[0][i], RI.skyMaxs[1][i], i );\n\t\tMakeSkyVec( RI.skyMaxs[0][i], RI.skyMins[1][i], i );\n\t\tpglEnd();\n\t}\n\n\tif( !RI.fogSkybox )\n\t\tR_AllowFog( true );\n\n\tif( RI.fogEnabled )\n\t\tpglFogf( GL_FOG_DENSITY, RI.fogDensity );\n\n\tR_LoadIdentity();\n}\n\n//==============================================================================\n//\n//  RENDER CLOUDS\n//\n//==============================================================================\n/*\n==============\nR_CloudVertex\n==============\n*/\nstatic void R_CloudVertex( float s, float t, int axis, vec3_t v )\n{\n\tint\tj, k, farclip;\n\tvec3_t\tb;\n\n\tfarclip = RI.farClip;\n\n\tb[0] = s * (farclip >> 1);\n\tb[1] = t * (farclip >> 1);\n\tb[2] = (farclip >> 1);\n\n\tfor( j = 0; j < 3; j++ )\n\t{\n\t\tk = st_to_vec[axis][j];\n\t\tv[j] = (k < 0) ? -b[-k-1] : b[k-1];\n\t\tv[j] += RI.cullorigin[j];\n\t}\n}\n\n/*\n=============\nR_CloudTexCoord\n=============\n*/\nstatic void R_CloudTexCoord( const vec3_t v, float speed, float *s, float *t )\n{\n\tfloat\tlength, speedscale;\n\tvec3_t\tdir;\n\n\tspeedscale = gp_cl->time * speed;\n\tspeedscale -= (int)speedscale & ~127;\n\n\tVectorSubtract( v, RI.vieworg, dir );\n\tdir[2] *= 3.0f; // flatten the sphere\n\n\tlength = VectorLength( dir );\n\tlength = 6.0f * 63.0f / length;\n\n\t*s = ( speedscale + dir[0] * length ) * (1.0f / 128.0f);\n\t*t = ( speedscale + dir[1] * length ) * (1.0f / 128.0f);\n}\n\n/*\n===============\nR_CloudDrawPoly\n===============\n*/\nstatic void R_CloudDrawPoly( const float *verts )\n{\n\tconst float\t*v;\n\tfloat\ts, t;\n\tint\t\ti;\n\n\tGL_SetRenderMode( kRenderNormal );\n\tGL_Bind( XASH_TEXTURE0, tr.solidskyTexture );\n\n\tpglBegin( GL_QUADS );\n\tfor( i = 0, v = verts; i < 4; i++, v += VERTEXSIZE )\n\t{\n\t\tR_CloudTexCoord( v, 8.0f, &s, &t );\n\t\tpglTexCoord2f( s, t );\n\t\tpglVertex3fv( v );\n\t}\n\tpglEnd();\n\n\tGL_SetRenderMode( kRenderTransTexture );\n\tGL_Bind( XASH_TEXTURE0, tr.alphaskyTexture );\n\n\tpglBegin( GL_QUADS );\n\tfor( i = 0, v = verts; i < 4; i++, v += VERTEXSIZE )\n\t{\n\t\tR_CloudTexCoord( v, 16.0f, &s, &t );\n\t\tpglTexCoord2f( s, t );\n\t\tpglVertex3fv( v );\n\t}\n\tpglEnd();\n\n\tpglDisable( GL_BLEND );\n}\n\n/*\n==============\nR_CloudRenderSide\n==============\n*/\nstatic void R_CloudRenderSide( int axis )\n{\n\tvec3_t\tverts[4];\n\tfloat\tfinal_verts[4][VERTEXSIZE];\n\tfloat\tdi, qi, dj, qj;\n\tvec3_t\tvup, vright;\n\tvec3_t\ttemp, temp2;\n\tint\ti, j;\n\n\tR_CloudVertex( -1.0f, -1.0f, axis, verts[0] );\n\tR_CloudVertex( -1.0f,  1.0f, axis, verts[1] );\n\tR_CloudVertex(  1.0f,  1.0f, axis, verts[2] );\n\tR_CloudVertex(  1.0f, -1.0f, axis, verts[3] );\n\n\tVectorSubtract( verts[2], verts[3], vup );\n\tVectorSubtract( verts[2], verts[1], vright );\n\n\tdi = SKYCLOUDS_QUALITY;\n\tqi = 1.0f / di;\n\tdj = (axis < 4) ? di * 2 : di; //subdivide vertically more than horizontally on skybox sides\n\tqj = 1.0f / dj;\n\n\tfor( i = 0; i < di; i++ )\n\t{\n\t\tfor( j = 0; j < dj; j++ )\n\t\t{\n\t\t\tif( i * qi < RI.skyMins[0][axis] / 2 + 0.5f - qi\n\t\t\t || i * qi > RI.skyMaxs[0][axis] / 2 + 0.5f\n\t\t\t || j * qj < RI.skyMins[1][axis] / 2 + 0.5f - qj\n\t\t\t || j * qj > RI.skyMaxs[1][axis] / 2 + 0.5f )\n\t\t\t\tcontinue;\n\n\t\t\tVectorScale( vright, qi * i, temp );\n\t\t\tVectorScale( vup, qj * j, temp2 );\n\t\t\tVectorAdd( temp, temp2, temp );\n\t\t\tVectorAdd( verts[0], temp, final_verts[0] );\n\n\t\t\tVectorScale( vup, qj, temp );\n\t\t\tVectorAdd( final_verts[0], temp, final_verts[1] );\n\n\t\t\tVectorScale( vright, qi, temp );\n\t\t\tVectorAdd( final_verts[1], temp, final_verts[2] );\n\n\t\t\tVectorAdd( final_verts[0], temp, final_verts[3] );\n\n\t\t\tR_CloudDrawPoly( final_verts[0] );\n\t\t}\n\t}\n}\n\n/*\n==============\nR_DrawClouds\n\nQuake-style clouds\n==============\n*/\nvoid R_DrawClouds( void )\n{\n\tint\ti;\n\n\tRI.isSkyVisible = true;\n\n\tif( RI.fogEnabled )\n\t\tpglFogf( GL_FOG_DENSITY, RI.fogDensity * 0.25f );\n\tpglDepthFunc( GL_GEQUAL );\n\tpglDepthMask( GL_FALSE );\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t{\n\t\tif( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] )\n\t\t\tcontinue;\n\t\tR_CloudRenderSide( i );\n\t}\n\n\tpglDepthFunc( GL_LEQUAL );\n\tpglDepthMask( GL_TRUE );\n\n\tif( RI.fogEnabled )\n\t\tpglFogf( GL_FOG_DENSITY, RI.fogDensity );\n}\n\n/*\n=============\nEmitWaterPolys\n\nDoes a water warp on the pre-fragmented glpoly_t chain\n=============\n*/\nvoid EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples )\n{\n\tfloat\t*v, nv, waveHeight;\n\tfloat\ts, t, os, ot;\n\tglpoly2_t\t*p;\n\tint\ti;\n\n\tconst qboolean useQuads = FBitSet( warp->flags, SURF_DRAWTURB_QUADS ) && glConfig.context == CONTEXT_TYPE_GL;\n\n\tif( !warp->polys ) return;\n\n\t// set the current waveheight\n\tif( warp->polys->verts[0][2] >= RI.vieworg[2] )\n\t\twaveHeight = -RI.currententity->curstate.scale;\n\telse waveHeight = RI.currententity->curstate.scale;\n\n\t// reset fog color for nonlightmapped water\n\tGL_ResetFogColor();\n\n\tif( useQuads )\n\t\tpglBegin( GL_QUADS );\n\n\tfor( p = warp->polys; p; p = p->next )\n\t{\n\t\tif( reverse )\n\t\t\tv = p->verts[0] + ( p->numverts - 1 ) * VERTEXSIZE;\n\t\telse v = p->verts[0];\n\n\t\tif( !useQuads )\n\t\t\tpglBegin( GL_POLYGON );\n\n\t\tfor( i = 0; i < p->numverts; i++ )\n\t\t{\n\t\t\tif( waveHeight )\n\t\t\t{\n\t\t\t\tnv = r_turbsin[(int)(gp_cl->time * 160.0f + v[1] + v[0]) & 255] + 8.0f;\n\t\t\t\tnv = (r_turbsin[(int)(v[0] * 5.0f + gp_cl->time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv;\n\t\t\t\tnv = nv * waveHeight + v[2];\n\t\t\t}\n\t\t\telse nv = v[2];\n\n\t\t\tos = v[3];\n\t\t\tot = v[4];\n\n\t\t\tif( !ripples )\n\t\t\t{\n\t\t\t\ts = os + r_turbsin[(int)((ot * 0.125f + gp_cl->time) * TURBSCALE) & 255];\n\t\t\t\tt = ot + r_turbsin[(int)((os * 0.125f + gp_cl->time) * TURBSCALE) & 255];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ts = os;\n\t\t\t\tt = ot;\n\t\t\t}\n\n\t\t\ts *= ( 1.0f / SUBDIVIDE_SIZE );\n\t\t\tt *= ( 1.0f / SUBDIVIDE_SIZE );\n\n\t\t\tpglTexCoord2f( s, t );\n\t\t\tpglVertex3f( v[0], v[1], nv );\n\n\t\t\tif( reverse )\n\t\t\t\tv -= VERTEXSIZE;\n\t\t\telse v += VERTEXSIZE;\n\t\t}\n\n\t\tif( !useQuads )\n\t\t\tpglEnd();\n\t}\n\n\tif( useQuads )\n\t\tpglEnd();\n\n\tGL_SetupFogColorForSurfaces();\n}\n\n/*\n============================================================\n\n\tHALF-LIFE SOFTWARE WATER\n\n============================================================\n*/\nvoid R_ResetRipples( void )\n{\n\tg_ripple.curbuf = g_ripple.buf[0];\n\tg_ripple.oldbuf = g_ripple.buf[1];\n\tg_ripple.time = g_ripple.oldtime = gp_cl->time - 0.1;\n\tmemset( g_ripple.buf, 0, sizeof( g_ripple.buf ));\n}\n\nstatic void R_SwapBufs( void )\n{\n\tshort *tempbufp = g_ripple.curbuf;\n\tg_ripple.curbuf = g_ripple.oldbuf;\n\tg_ripple.oldbuf = tempbufp;\n}\n\nstatic void R_SpawnNewRipple( int x, int y, short val )\n{\n#define PIXEL( x, y ) ((( x ) & RIPPLES_CACHEWIDTH_MASK ) + ((( y ) & RIPPLES_CACHEWIDTH_MASK) << 7 ))\n\tg_ripple.oldbuf[PIXEL( x, y )] += val;\n\n\tval >>= 2;\n\tg_ripple.oldbuf[PIXEL( x + 1, y )] += val;\n\tg_ripple.oldbuf[PIXEL( x - 1, y )] += val;\n\tg_ripple.oldbuf[PIXEL( x, y + 1 )] += val;\n\tg_ripple.oldbuf[PIXEL( x, y - 1 )] += val;\n#undef PIXEL\n}\n\nstatic void R_RunRipplesAnimation( const short *oldbuf, short *pbuf )\n{\n\tsize_t i = 0;\n\tconst int w = RIPPLES_CACHEWIDTH;\n\tconst int m = RIPPLES_TEXSIZE_MASK;\n\n\tfor( i = w; i < m + w; i++, pbuf++ )\n\t{\n\t\t*pbuf = (\n\t\t\t( (int)oldbuf[( i - ( w * 2 )) & m]\n\t\t\t+ (int)oldbuf[( i - ( w + 1 )) & m]\n\t\t\t+ (int)oldbuf[( i - ( w - 1 )) & m]\n\t\t\t+ (int)oldbuf[( i ) & m]) >> 1 ) - (int)*pbuf;\n\n\t\t*pbuf -= ( *pbuf >> 6 );\n\t}\n}\n\nvoid R_AnimateRipples( void )\n{\n\tdouble frametime = gp_cl->time - g_ripple.time;\n\n\tg_ripple.update = r_ripple.value && frametime >= r_ripple_updatetime.value;\n\n\tif( !g_ripple.update )\n\t\treturn;\n\n\tg_ripple.time = gp_cl->time;\n\n\tR_SwapBufs();\n\n\tif( g_ripple.time - g_ripple.oldtime > r_ripple_spawntime.value )\n\t{\n\t\tint x, y, val;\n\n\t\tg_ripple.oldtime = g_ripple.time;\n\n\t\tx = rand() & 0x7fff;\n\t\ty = rand() & 0x7fff;\n\t\tval = rand() & 0x3ff;\n\n\t\tR_SpawnNewRipple( x, y, val );\n\t}\n\n\tR_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf );\n}\n\nstatic void R_GetRippleTextureSize( const texture_t *image, int *width, int *height )\n{\n\t// try to preserve aspect ratio\n\tif( image->width > image->height )\n\t{\n\t\t*width = RIPPLES_CACHEWIDTH;\n\t\t*height = (float)image->height / image->width * RIPPLES_CACHEWIDTH;\n\t}\n\telse if( image->width < image->height )\n\t{\n\t\t*width = (float)image->width / image->height * RIPPLES_CACHEWIDTH;\n\t\t*height = RIPPLES_CACHEWIDTH;\n\t}\n\telse\n\t{\n\t\t*width = *height = RIPPLES_CACHEWIDTH;\n\t}\n}\n\nqboolean R_UploadRipples( texture_t *image )\n{\n\tconst gl_texture_t *glt;\n\tconst uint32_t *pixels;\n\tint y;\n\tint width, height, size;\n\tqboolean update = g_ripple.update;\n\n\tif( !r_ripple.value )\n\t{\n\t\tGL_Bind( XASH_TEXTURE0, image->gl_texturenum );\n\t\treturn false;\n\t}\n\n\t// discard unuseful textures\n\tglt = R_GetTexture( image->gl_texturenum );\n\tif( !glt || !glt->original || !glt->original->buffer )\n\t{\n\t\tGL_Bind( XASH_TEXTURE0, image->gl_texturenum );\n\t\treturn false;\n\t}\n\n\tif( !image->fb_texturenum )\n\t{\n\t\trgbdata_t pic = { 0 };\n\t\tstring name;\n\n\t\tQ_snprintf( name, sizeof( name ), \"*rippletex_%s\", image->name );\n\t\tR_GetRippleTextureSize( image, &width, &height );\n\n\t\tpic.width = width;\n\t\tpic.height = height;\n\t\tpic.depth = 1;\n\t\tpic.flags = IMAGE_HAS_COLOR;\n\t\tpic.buffer = (byte *)g_ripple.texture;\n\t\tpic.type = PF_RGBA_32;\n\t\tpic.size = width * height * 4;\n\t\tpic.numMips = 1;\n\t\tmemset( pic.buffer, 0, pic.size );\n\n\t\timage->fb_texturenum = GL_LoadTextureInternal( name, &pic, TF_NOMIPMAP | TF_ALLOW_NEAREST );\n\n\t\tupdate = true;\n\t\timage->dt_texturenum = ( tr.framecount - 1 ) & 0xFFFF;\n\t}\n\n\tGL_Bind( XASH_TEXTURE0, image->fb_texturenum );\n\n\t// no updates this frame\n\tif( !update || image->dt_texturenum == ( tr.framecount & 0xFFFF ))\n\t\treturn true;\n\n\t// prevent rendering texture multiple times in frame\n\timage->dt_texturenum = tr.framecount & 0xFFFF;\n\n\tR_GetRippleTextureSize( image, &width, &height );\n\n\tsize = r_ripple.value == 1.0f ? 64 : RIPPLES_CACHEWIDTH;\n\tpixels = (const uint32_t *)glt->original->buffer;\n\n\tfor( y = 0; y < height; y++ )\n\t{\n\t\tint ry = (float)y / height * size;\n\t\tint x;\n\n\t\tfor( x = 0; x < width; x++ )\n\t\t{\n\t\t\tint rx = (float)x / width * size;\n\t\t\tint val = g_ripple.curbuf[ry * RIPPLES_CACHEWIDTH + rx] / 16;\n\n\t\t\t// transform it to texture space and get nice tiling effect\n\t\t\tint rpy = ( y - val ) % height;\n\t\t\tint rpx = ( x + val ) % width;\n\n\t\t\tint py = (float)rpy / height * image->height;\n\t\t\tint px = (float)rpx / width * image->width;\n\n\t\t\tif( py < 0 ) py = image->height + py;\n\t\t\tif( px < 0 ) px = image->width + px;\n\n\t\t\tg_ripple.texture[y * width + x] = pixels[py * image->width + px];\n\t\t}\n\t}\n\n\tpglTexImage2D( GL_TEXTURE_2D, 0, glt->format, width, height, 0,\n\t\tGL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture );\n\n\treturn true;\n}\n"
  },
  {
    "path": "ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc",
    "content": "R\"\"(\n\n#if ATTR_TEXCOORD0\nuniform sampler2D uTex0 : TEXUNIT0;\n#endif\n#if ATTR_TEXCOORD1\nuniform sampler2D uTex1 : TEXUNIT1;\n#endif\n#if FEAT_ALPHA_TEST\nuniform float uAlphaTest;\n#endif\n#if FEAT_FOG\n// color + density, mode always GL_EXP\nuniform float4 uFog;\n#endif\n\nfloat4 main(\n    uniform float4 uColor\n#if ATTR_COLOR\n  , float4 vColor : COLOR\n#endif\n#if ATTR_TEXCOORD0\n  , float2 vTexCoord0 : TEXCOORD0\n#endif\n#if ATTR_TEXCOORD1\n  , float2 vTexCoord1 : TEXCOORD1\n#endif\n#if ATTR_NORMAL\n  , float3 vNormal : TEXCOORD2\n#endif\n#if FEAT_FOG\n  , float4 vPosition : WPOS\n#endif\n) {\n#if ATTR_COLOR\n  float4 c = vColor;\n#else\n  float4 c = uColor;\n#endif\n#if ATTR_TEXCOORD0\n  c *= tex2D(uTex0, vTexCoord0);\n#endif\n#if ATTR_TEXCOORD1\n  c *= tex2D(uTex1, vTexCoord1);\n#endif\n#if FEAT_ALPHA_TEST\n  if (c.a <= uAlphaTest)\n    discard;\n#endif\n#if FEAT_FOG\n  float fogDist = vPosition.z / vPosition.w;\n  float fogRate = clamp(exp(-uFog.w * fogDist), 0.f, 1.f);\n  c.rgb = lerp(uFog.rgb, c.rgb, fogRate);\n#endif\n  return c;\n}\n\n)\"\"\n"
  },
  {
    "path": "ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc",
    "content": "R\"\"(\n\n// has to be called this for VGL to fill it in automatically\nuniform float4x4 gl_ModelViewProjectionMatrix;\n\nvoid main(\n    float3 inPosition\n#if ATTR_COLOR\n  , float4 inColor\n#endif\n#if ATTR_NORMAL\n  , float3 inNormal\n#endif\n#if ATTR_TEXCOORD0\n  , float2 inTexCoord0\n#endif\n#if ATTR_TEXCOORD1\n  , float2 inTexCoord1\n#endif\n  , float4 out vPosition : POSITION\n#if ATTR_COLOR\n  , float4 out vColor : COLOR\n#endif\n#if ATTR_TEXCOORD0\n  , float2 out vTexCoord0 : TEXCOORD0\n#endif\n#if ATTR_TEXCOORD1\n  , float2 out vTexCoord1 : TEXCOORD1\n#endif\n#if ATTR_NORMAL\n  , float3 out vNormal : TEXCOORD2\n#endif\n) {\n  vPosition = mul(gl_ModelViewProjectionMatrix, float4(inPosition, 1.f));\n#if ATTR_COLOR\n  vColor = inColor;\n#endif\n#if ATTR_NORMAL\n  vNormal = inNormal;\n#endif\n#if ATTR_TEXCOORD0\n  vTexCoord0 = inTexCoord0;\n#endif\n#if ATTR_TEXCOORD1\n  vTexCoord1 = inTexCoord1;\n#endif\n}\n\n)\"\"\n"
  },
  {
    "path": "ref/gl/vgl_shim/vgl_shim.c",
    "content": "/*\nvgl_shim.c - vitaGL custom immediate mode shim\nCopyright (C) 2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n/*\n\tthis is a \"replacement\" for vitaGL's immediate mode tailored specifically for xash\n\tthis will only provide performance gains if vitaGL is built with DRAW_SPEEDHACK=1\n\tsince that makes it assume that all vertex data pointers are GPU-mapped\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <malloc.h>\n#include <vitaGL.h>\n\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"cvardef.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"cl_entity.h\"\n#include \"render_api.h\"\n#include \"protocol.h\"\n#include \"dlight.h\"\n#include \"ref_api.h\"\n#include \"com_strings.h\"\n#include \"crtlib.h\"\n#include \"vgl_shim.h\"\n\n#define MAX_SHADERLEN 4096\n// increase this when adding more attributes\n#define MAX_PROGS 32\n\nextern ref_api_t gEngfuncs;\n\nenum vgl_attrib_e\n{\n\tVGL_ATTR_POS       = 0, // 1\n\tVGL_ATTR_COLOR     = 1, // 2\n\tVGL_ATTR_TEXCOORD0 = 2, // 4\n\tVGL_ATTR_TEXCOORD1 = 3, // 8\n\tVGL_ATTR_MAX\n};\n\n// continuation of previous enum\nenum vgl_flag_e\n{\n\tVGL_FLAG_ALPHA_TEST = VGL_ATTR_MAX, // 16\n\tVGL_FLAG_FOG,                       // 32\n\tVGL_FLAG_MAX\n};\n\ntypedef struct\n{\n\tGLuint flags;\n\tGLint attridx[VGL_ATTR_MAX];\n\tGLuint glprog;\n\tGLint ucolor;\n\tGLint ualpha;\n\tGLint utex0;\n\tGLint utex1;\n\tGLint ufog;\n} vgl_prog_t;\n\nstatic const char *vgl_vert_src =\n#include \"vgl_shaders/vertex.cg.inc\"\n;\n\nstatic const char *vgl_frag_src =\n#include \"vgl_shaders/fragment.cg.inc\"\n;\n\nstatic int vgl_init = 0;\n\nstatic struct\n{\n\tGLfloat *attrbuf[VGL_ATTR_MAX];\n\tGLuint cur_flags;\n\tGLint begin;\n\tGLint end;\n\tGLenum prim;\n\tGLfloat color[4];\n\tGLfloat fog[4]; // color + density\n\tGLfloat alpharef;\n\tvgl_prog_t progs[MAX_PROGS];\n\tvgl_prog_t *cur_prog;\n\tGLboolean uchanged;\n} vgl;\n\nstatic const int vgl_attr_size[VGL_ATTR_MAX] = { 3, 4, 2, 2 };\n\nstatic const char *vgl_flag_name[VGL_FLAG_MAX] =\n{\n\t\"ATTR_POSITION\",\n\t\"ATTR_COLOR\",\n\t\"ATTR_TEXCOORD0\",\n\t\"ATTR_TEXCOORD1\",\n\t\"FEAT_ALPHA_TEST\",\n\t\"FEAT_FOG\",\n};\n\nstatic const char *vgl_attr_name[VGL_ATTR_MAX] =\n{\n\t\"inPosition\",\n\t\"inColor\",\n\t\"inTexCoord0\",\n\t\"inTexCoord1\",\n};\n\n// HACK: borrow alpha test and fog flags from internal vitaGL state\nextern GLboolean alpha_test_state;\nextern GLboolean fogging;\n\nstatic GLuint VGL_GenerateShader( const vgl_prog_t *prog, GLenum type )\n{\n\tchar *shader, shader_buf[MAX_SHADERLEN + 1];\n\tchar tmp[256];\n\tint i;\n\tGLint status, len;\n\tGLuint id;\n\n\tshader = shader_buf;\n\tshader[0] = '\\n';\n\tshader[1] = 0;\n\n\tfor ( i = 0; i < VGL_FLAG_MAX; ++i )\n\t{\n\t\tQ_snprintf( tmp, sizeof( tmp ), \"#define %s %d\\n\", vgl_flag_name[i], prog->flags & ( 1 << i ) );\n\t\tQ_strncat( shader, tmp, MAX_SHADERLEN );\n\t}\n\n\tif ( type == GL_FRAGMENT_SHADER )\n\t\tQ_strncat( shader, vgl_frag_src, MAX_SHADERLEN );\n\telse\n\t\tQ_strncat( shader, vgl_vert_src, MAX_SHADERLEN );\n\n\tid = glCreateShader( type );\n\tlen = Q_strlen( shader );\n\tglShaderSource( id, 1, (const void *)&shader, &len );\n\tglCompileShader( id );\n\tglGetShaderiv( id, GL_COMPILE_STATUS, &status );\n\tif ( status == GL_FALSE )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"VGL_GenerateShader( 0x%04x, 0x%x ): compile failed:\\n\", prog->flags, type );\n\t\tgEngfuncs.Con_DPrintf( \"Shader text:\\n%s\\n\\n\", shader );\n\t\tglDeleteShader( id );\n\t\treturn 0;\n\t}\n\n\treturn id;\n}\n\nstatic vgl_prog_t *VGL_GetProg( const GLuint flags )\n{\n\tint i, loc, status;\n\tGLuint vp, fp, glprog;\n\tvgl_prog_t *prog;\n\n\t// try to find existing prog matching this feature set\n\n\tif ( vgl.cur_prog && vgl.cur_prog->flags == flags )\n\t\treturn vgl.cur_prog;\n\n\tfor ( i = 0; i < MAX_PROGS; ++i )\n\t{\n\t\tif ( vgl.progs[i].flags == flags )\n\t\t\treturn &vgl.progs[i];\n\t\telse if ( vgl.progs[i].flags == 0 )\n\t\t\tbreak;\n\t}\n\n\tif ( i == MAX_PROGS )\n\t{\n\t\tgEngfuncs.Host_Error( \"VGL_GetProg(): Ran out of program slots for 0x%04x\\n\", flags );\n\t\treturn NULL;\n\t}\n\n\t// new prog; generate shaders\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"VGL_GetProg(): Generating progs for 0x%04x\\n\", flags );\n\tprog = &vgl.progs[i];\n\tprog->flags = flags;\n\n\tvp = VGL_GenerateShader( prog, GL_VERTEX_SHADER );\n\tfp = VGL_GenerateShader( prog, GL_FRAGMENT_SHADER );\n\tif ( !vp || !fp )\n\t{\n\t\tprog->flags = 0;\n\t\treturn NULL;\n\t}\n\n\tglprog = glCreateProgram();\n\tglAttachShader( glprog, vp );\n\tglAttachShader( glprog, fp );\n\n\tloc = 0;\n\tfor ( i = 0; i < VGL_ATTR_MAX; ++i )\n\t{\n\t\tif ( flags & ( 1 << i ) )\n\t\t{\n\t\t\tprog->attridx[i] = loc;\n\t\t\tglBindAttribLocation( glprog, loc++, vgl_attr_name[i] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprog->attridx[i] = -1;\n\t\t}\n\t}\n\n\tglLinkProgram( glprog );\n\tglDeleteShader( vp );\n\tglDeleteShader( fp );\n\n\tglGetProgramiv( glprog, GL_LINK_STATUS, &status );\n\tif ( status == GL_FALSE )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_ERROR \"VGL_GetProg(): Failed linking progs for 0x%04x!\\n\", prog->flags );\n\t\tprog->flags = 0;\n\t\tglDeleteProgram( glprog );\n\t\treturn NULL;\n\t}\n\n\tprog->ucolor = glGetUniformLocation( glprog, \"uColor\" );\n\tprog->ualpha = glGetUniformLocation( glprog, \"uAlphaTest\" );\n\tprog->utex0  = glGetUniformLocation( glprog, \"uTex0\" );\n\tprog->utex1  = glGetUniformLocation( glprog, \"uTex1\" );\n\tprog->ufog   = glGetUniformLocation( glprog, \"uFog\" );\n\n\t// these never change\n\tif ( prog->utex0 >= 0 )\n\t\tglUniform1i( prog->utex0, 0 );\n\tif ( prog->utex1 >= 0 )\n\t\tglUniform1i( prog->utex1, 1 );\n\n\tprog->glprog = glprog;\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"VGL_GetProg(): Generated progs for 0x%04x\\n\", flags );\n\n\treturn prog;\n}\n\nstatic vgl_prog_t *VGL_SetProg( const GLuint flags )\n{\n\tvgl_prog_t *prog = NULL;\n\n\tif ( flags && ( prog = VGL_GetProg( flags ) ) )\n\t{\n\t\tif ( prog != vgl.cur_prog )\n\t\t{\n\t\t\tglUseProgram( prog->glprog );\n\t\t\tvgl.uchanged = GL_TRUE;\n\t\t}\n\t\tif ( vgl.uchanged )\n\t\t{\n\t\t\tif ( prog->ualpha >= 0 )\n\t\t\t\tglUniform1f( prog->ualpha, vgl.alpharef );\n\t\t\tif ( prog->ucolor >= 0 )\n\t\t\t\tglUniform4fv( prog->ucolor, 1, vgl.color );\n\t\t\tif ( prog->ufog >= 0 )\n\t\t\t\tglUniform4fv( prog->ufog, 1, vgl.fog );\n\t\t\tvgl.uchanged = GL_FALSE;\n\t\t}\n\t}\n\telse\n\t{\n\t\tglUseProgram( 0 );\n\t}\n\n\tvgl.cur_prog = prog;\n\treturn prog;\n}\n\nint VGL_ShimInit( void )\n{\n\tint i;\n\tGLuint total, size;\n\tstatic const GLuint precache_progs[] = {\n\t\t0x0001, // out = ucolor\n\t\t0x0005, // out = tex0 * ucolor\n\t\t0x0007, // out = tex0 * vcolor\n\t\t0x0015, // out = tex0 * ucolor + FEAT_ALPHA_TEST\n\t\t0x0021, // out = ucolor + FEAT_FOG\n\t\t0x0025, // out = tex0 * ucolor + FEAT_FOG\n\t\t0x0027, // out = tex0 * vcolor + FEAT_FOG\n\t\t0x0035, // out = tex0 * ucolor + FEAT_ALPHA_TEST + FEAT_FOG\n\t};\n\n\tif ( vgl_init )\n\t\treturn 0;\n\n\tmemset( &vgl, 0, sizeof( vgl ) );\n\n\tvgl.color[0] = 1.f;\n\tvgl.color[1] = 1.f;\n\tvgl.color[2] = 1.f;\n\tvgl.color[3] = 1.f;\n\tvgl.uchanged = GL_TRUE;\n\n\ttotal = 0;\n\tfor ( i = 0; i < VGL_ATTR_MAX; ++i )\n\t{\n\t\tsize = VGL_MAX_VERTS * vgl_attr_size[i] * sizeof( GLfloat );\n\t\tvgl.attrbuf[i] = memalign( 0x100, size );\n\t\ttotal += size;\n\t}\n\n\tVGL_ShimInstall();\n\n\tgEngfuncs.Con_DPrintf( S_NOTE \"VGL_ShimInit(): %u bytes allocated for vertex buffer\\n\", total );\n\tgEngfuncs.Con_DPrintf( S_NOTE \"VGL_ShimInit(): Pre-generating %u progs...\\n\", sizeof( precache_progs ) / sizeof( *precache_progs ) );\n\tfor ( i = 0; i < (int)( sizeof( precache_progs ) / sizeof( *precache_progs ) ); ++i )\n\t\tVGL_GetProg( precache_progs[i] );\n\n\tvgl_init = 1;\n\treturn 0;\n}\n\nvoid VGL_ShimShutdown( void )\n{\n\tint i;\n\n\tif ( !vgl_init )\n\t\treturn;\n\n\tglFinish();\n\tglUseProgram( 0 );\n\n\t/*\n\t// FIXME: this sometimes causes the game to block on glDeleteProgram for up to a minute\n\t//        but since this is only called on shutdown or game change, it should be fine to skip\n\tfor ( i = 0; i < MAX_PROGS; ++i )\n\t{\n\t\tif ( vgl.progs[i].flags )\n\t\t\tglDeleteProgram( vgl.progs[i].glprog );\n\t}\n\t*/\n\n\tfor ( i = 0; i < VGL_ATTR_MAX; ++i )\n\t\tfree( vgl.attrbuf[i] );\n\n\tmemset( &vgl, 0, sizeof( vgl ) );\n\n\tvgl_init = 0;\n}\n\nvoid VGL_ShimEndFrame( void )\n{\n\tvgl.end = vgl.begin = 0;\n}\n\nstatic void VGL_Begin( GLenum prim )\n{\n\tint i;\n\tvgl.prim = prim;\n\tvgl.begin = vgl.end;\n\t// pos always enabled\n\tvgl.cur_flags = 1 << VGL_ATTR_POS;\n\t// disable all vertex attrib pointers\n\tfor ( i = 0; i < VGL_ATTR_MAX; ++i )\n\t\tglDisableVertexAttribArray( i );\n}\n\nstatic void VGL_End( void )\n{\n\tint i;\n\tvgl_prog_t *prog;\n\tGLuint flags = vgl.cur_flags;\n\tGLint count = vgl.end - vgl.begin;\n\n\tif ( !vgl.prim || !count )\n\t\tgoto _leave; // end without begin\n\n\t// enable alpha test and fog if needed\n\tif ( alpha_test_state )\n\t\tflags |= 1 << VGL_FLAG_ALPHA_TEST;\n\tif ( fogging )\n\t\tflags |= 1 << VGL_FLAG_FOG;\n\n\tprog = VGL_SetProg( flags );\n\tif ( !prog )\n\t{\n\t\tgEngfuncs.Host_Error( \"VGL_End(): Could not find program for flags 0x%04x!\\n\", flags );\n\t\tgoto _leave;\n\t}\n\n\tfor ( i = 0; i < VGL_ATTR_MAX; ++i )\n\t{\n\t\tif ( prog->attridx[i] >= 0 )\n\t\t{\n\t\t\tglEnableVertexAttribArray( prog->attridx[i] );\n\t\t\tglVertexAttribPointer( prog->attridx[i], vgl_attr_size[i], GL_FLOAT, GL_FALSE, 0, vgl.attrbuf[i] + vgl_attr_size[i] * vgl.begin );\n\t\t}\n\t}\n\n\tglDrawArrays( vgl.prim, 0, count );\n\n_leave:\n\tvgl.prim = GL_NONE;\n\tvgl.begin = vgl.end;\n\tvgl.cur_flags = 0;\n}\n\nstatic void VGL_Vertex3f( GLfloat x, GLfloat y, GLfloat z )\n{\n\tGLfloat *p = vgl.attrbuf[VGL_ATTR_POS] + vgl.end * 3;\n\t*p++ = x;\n\t*p++ = y;\n\t*p++ = z;\n\t++vgl.end;\n\tif ( vgl.end >= VGL_MAX_VERTS )\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"VGL_Vertex3f(): Vertex buffer overflow!\\n\" );\n\t\tvgl.end = vgl.begin = 0;\n\t}\n}\n\nstatic void VGL_Vertex2f( GLfloat x, GLfloat y )\n{\n\tVGL_Vertex3f( x, y, 0.f );\n}\n\nstatic void VGL_Vertex3fv( const GLfloat *v )\n{\n\tVGL_Vertex3f( v[0], v[1], v[2] );\n}\n\nstatic void VGL_Color4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a )\n{\n\tvgl.color[0] = r;\n\tvgl.color[1] = g;\n\tvgl.color[2] = b;\n\tvgl.color[3] = a;\n\tvgl.uchanged = GL_TRUE;\n\tif ( vgl.prim )\n\t{\n\t\t// HACK: enable color attribute if we're using color inside a Begin-End pair\n\t\tGLfloat *p = vgl.attrbuf[VGL_ATTR_COLOR] + vgl.end * 4;\n\t\tvgl.cur_flags |= 1 << VGL_ATTR_COLOR;\n\t\t*p++ = r;\n\t\t*p++ = g;\n\t\t*p++ = b;\n\t\t*p++ = a;\n\t}\n}\n\nstatic void VGL_Color3f( GLfloat r, GLfloat g, GLfloat b )\n{\n\tVGL_Color4f( r, g, b, 1.f );\n}\n\nstatic void VGL_Color4ub( GLubyte r, GLubyte g, GLubyte b, GLubyte a )\n{\n\tVGL_Color4f( (GLfloat)r / 255.f, (GLfloat)g / 255.f, (GLfloat)b / 255.f, (GLfloat)a / 255.f );\n}\n\nstatic void VGL_Color4ubv( const GLubyte *v )\n{\n\tVGL_Color4ub( v[0], v[1], v[2], v[3] );\n}\n\nstatic void VGL_TexCoord2f( GLfloat u, GLfloat v )\n{\n\t// by spec glTexCoord always updates texunit 0\n\tGLfloat *p = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2;\n\tvgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0;\n\t*p++ = u;\n\t*p++ = v;\n}\n\nstatic void VGL_MultiTexCoord2f( GLenum tex, GLfloat u, GLfloat v )\n{\n\tGLfloat *p;\n\t// assume there can only be two\n\tif ( tex == GL_TEXTURE0 )\n\t{\n\t\tp = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2;\n\t\tvgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0;\n\t}\n\telse\n\t{\n\t\tp = vgl.attrbuf[VGL_ATTR_TEXCOORD1] + vgl.end * 2;\n\t\tvgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD1;\n\t}\n\t*p++ = u;\n\t*p++ = v;\n}\n\nstatic void VGL_Normal3fv( const GLfloat *v )\n{\n\t/* this does not seem to be necessary */\n}\n\nstatic void VGL_ShadeModel( GLenum unused )\n{\n\t/* this doesn't do anything in vitaGL except spit errors in debug mode, so stub it out */\n}\n\nstatic void VGL_AlphaFunc( GLenum mode, GLfloat ref )\n{\n\tvgl.alpharef = ref;\n\tvgl.uchanged = GL_TRUE;\n\t// mode is always GL_GREATER\n}\n\nstatic void VGL_Fogf( GLenum param, GLfloat val )\n{\n\tif ( param == GL_FOG_DENSITY )\n\t{\n\t\tvgl.fog[3] = val;\n\t\tvgl.uchanged = GL_TRUE;\n\t}\n}\n\nstatic void VGL_Fogfv( GLenum param, const GLfloat *val )\n{\n\tif ( param == GL_FOG_COLOR )\n\t{\n\t\tvgl.fog[0] = val[0];\n\t\tvgl.fog[1] = val[1];\n\t\tvgl.fog[2] = val[2];\n\t\tvgl.uchanged = GL_TRUE;\n\t}\n}\n\nstatic void VGL_DrawBuffer( GLenum mode )\n{\n\t/* unsupported */\n}\n\nstatic void VGL_Hint( GLenum hint, GLenum val )\n{\n\t/* none of the used hints are supported; stub to prevent errors */\n}\n\n#define VGL_OVERRIDE_PTR( name ) \\\n{ \\\n\textern void *pgl ## name; \\\n\tpgl ## name = VGL_ ## name; \\\n}\n\nvoid VGL_ShimInstall( void )\n{\n\tVGL_OVERRIDE_PTR( Vertex2f )\n\tVGL_OVERRIDE_PTR( Vertex3f )\n\tVGL_OVERRIDE_PTR( Vertex3fv )\n\tVGL_OVERRIDE_PTR( Color3f )\n\tVGL_OVERRIDE_PTR( Color4f )\n\tVGL_OVERRIDE_PTR( Color4ub )\n\tVGL_OVERRIDE_PTR( Color4ubv )\n\tVGL_OVERRIDE_PTR( Normal3fv )\n\tVGL_OVERRIDE_PTR( TexCoord2f )\n\tVGL_OVERRIDE_PTR( MultiTexCoord2f )\n\tVGL_OVERRIDE_PTR( ShadeModel )\n\tVGL_OVERRIDE_PTR( DrawBuffer )\n\tVGL_OVERRIDE_PTR( AlphaFunc )\n\tVGL_OVERRIDE_PTR( Fogf )\n\tVGL_OVERRIDE_PTR( Fogfv )\n\tVGL_OVERRIDE_PTR( Hint )\n\tVGL_OVERRIDE_PTR( Begin )\n\tVGL_OVERRIDE_PTR( End )\n}\n"
  },
  {
    "path": "ref/gl/vgl_shim/vgl_shim.h",
    "content": "/*\nvgl_shim.h - vitaGL custom immediate mode shim\nCopyright (C) 2023 fgsfds\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#pragma once\n\n// max verts in a single frame\n#define VGL_MAX_VERTS 32768\n\nint VGL_ShimInit( void );\nvoid VGL_ShimInstall( void );\nvoid VGL_ShimShutdown( void );\nvoid VGL_ShimEndFrame( void );\n"
  },
  {
    "path": "ref/gl/vgl_shim/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nimport os\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tconf.define('REF_DLL', 1)\n\ndef build(bld):\n\tbld.env.CFLAGS_cstlib.remove('-fno-PIC')\n\tbld.env.CFLAGS += ['-fPIC']\n\tbld.stlib(source = bld.path.ant_glob('*.c'),\n\t\ttarget = 'vgl_shim',\n\t\tuse = 'engine_includes sdk_includes werror',\n\t\tincludes = '.')\n"
  },
  {
    "path": "ref/gl/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib import Logs\nimport os\n\ntop = '.'\n\ndef options(opt):\n\tgrp = opt.add_option_group('ref_gl options')\n\n\tgrp.add_option('--enable-static-gl', action='store_true', dest='GL_STATIC', default=False,\n\t\thelp = 'enable direct linking to opengl [default: %(default)s]')\n\n\t# stub\n\treturn\n\ndef configure(conf):\n\tconf.env.GL_STATIC = conf.options.GL_STATIC\n\tif conf.env.GL_STATIC:\n\t\tconf.check(lib='GL')\n\n\tif conf.env.DEST_OS2 == 'android':\n\t\tconf.check_cc(lib='log')\n\ndef build(bld):\n\tlibs = [ 'engine_includes', 'werror' ]\n\t# on PSVita do not link any libraries that are already in the main executable, but add the includes target\n\tif bld.env.DEST_OS == 'psvita':\n\t\tlibs += [ 'sdk_includes', 'vgl_shim' ]\n\telse:\n\t\tlibs += [ 'public', 'M' ]\n\n\tsource = bld.path.ant_glob(['*.c', 'gl2_shim/*.c'])\n\tincludes = '.'\n\n\ttargets = {\n\t\t'ref_gl': {\n\t\t\t'enable':  bld.env.GL,\n\t\t\t'libs':    ['GL'] if bld.env.GL_STATIC else [],\n\t\t\t'defines': ['XASH_GL_STATIC=1'] if bld.env.GL_STATIC else [],\n\t\t},\n\t\t'ref_gles1': {\n\t\t\t'enable':  bld.env.NANOGL,\n\t\t\t'libs':    ['DL', 'nanogl', 'LOG'],\n\t\t\t'defines': ['XASH_NANOGL=1'],\n\t\t},\n\t\t'ref_gles2': {\n\t\t\t'enable':  bld.env.GLWES,\n\t\t\t'libs':    ['DL', 'gl-wes-v2', 'LOG'],\n\t\t\t'defines': ['XASH_WES=1'],\n\t\t},\n\t\t'ref_gl4es': {\n\t\t\t'enable':  bld.env.GL4ES,\n\t\t\t'libs':    ['DL', 'gl4es', 'LOG'],\n\t\t\t'defines': ['XASH_GL_STATIC=1', 'XASH_GL4ES=1'],\n\t\t},\n\t\t'ref_gles3compat': {\n\t\t\t'enable':  bld.env.GLES3COMPAT,\n\t\t\t'libs':    [],\n\t\t\t'defines': ['XASH_GLES3COMPAT=1'],\n\t\t},\n\t}\n\n\tfor k,v in targets.items():\n\t\tif not v['enable']:\n\t\t\tcontinue\n\n\t\tbld.shlib(source = source,\n\t\t\ttarget = k,\n\t\t\tincludes = includes,\n\t\t\tuse = libs + v['libs'],\n\t\t\tdefines = v['defines'] + ['REF_DLL=1'],\n\t\t\tinstall_path = bld.env.LIBDIR)\n"
  },
  {
    "path": "ref/null/r_context.c",
    "content": "/*\nr_context.c -- null renderer context\nCopyright (C) 2023-2024 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <string.h>\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"cvardef.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_api.h\"\n\n/*\n * this initially was made to be able to run full client\n * in hazardous^Wheadless environments\n * but might be a starting point for new renderers as well\n */\n\nstatic ref_api_t      gEngfuncs;\nstatic ref_globals_t *gpGlobals;\n\nstatic void R_SimpleStub( void )\n{\n\t;\n}\n\nstatic void R_SimpleStubInt( int unused )\n{\n\t;\n}\n\nstatic void R_SimpleStubUInt( unsigned int unused )\n{\n\t;\n}\n\nstatic void R_SimpleStubBool( qboolean unused )\n{\n\t;\n}\n\nstatic qboolean R_Init( void )\n{\n\tgEngfuncs.R_Init_Video( REF_SOFTWARE );\n\treturn true;\n}\n\nstatic const char *R_GetConfigName( void )\n{\n\treturn NULL;\n}\n\nstatic qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int x, int y, float scale_x, float scale_y )\n{\n\treturn true;\n}\n\nstatic void GL_SetupAttributes( int safegl )\n{\n\t;\n}\n\nstatic qboolean R_AddEntity( struct cl_entity_s *clent, int type )\n{\n\treturn true;\n}\n\nstatic void CL_AddCustomBeam( cl_entity_t *pEnvBeam )\n{\n\t;\n}\n\nstatic void R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities )\n{\n\t;\n}\n\nstatic const byte *R_GetTextureOriginalBuffer( unsigned int idx )\n{\n\treturn NULL;\n}\n\nstatic int GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update )\n{\n\treturn 0;\n}\n\nstatic void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )\n{\n\t;\n}\n\nstatic void R_SetupSky( int *skytextures )\n{\n\t;\n}\n\nstatic void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty )\n{\n\t;\n}\n\nstatic void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum )\n{\n\t;\n}\n\nstatic void FillRGBA( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a )\n{\n\t;\n}\n\nstatic int  WorldToScreen( const vec3_t world, vec3_t screen )\n{\n\tVectorClear( screen );\n\treturn 0;\n}\n\nstatic qboolean VID_ScreenShot( const char *filename, int shot_type )\n{\n\treturn false;\n}\n\nstatic qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot )\n{\n\treturn false;\n}\n\nstatic colorVec R_LightPoint( const float *p )\n{\n\tcolorVec c = { 0 };\n\treturn c;\n}\n\nstatic void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale )\n{\n\t;\n}\n\nstatic int R_CreateDecalList( struct decallist_s *pList )\n{\n\treturn 0;\n}\n\nstatic float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time )\n{\n\treturn 0.0f;\n}\n\nstatic void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles )\n{\n\t;\n}\n\nstatic void R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture )\n{\n\t;\n}\n\nstatic void GL_SubdivideSurface( model_t *mod, msurface_t *fa )\n{\n\t;\n}\n\nstatic void CL_RunLightStyles( lightstyle_t *ls )\n{\n\n}\n\nstatic void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )\n{\n\tif( frameWidth )\n\t\t*frameWidth\t= 0;\n\n\tif( frameHeight )\n\t\t*frameHeight = 0;\n\n\tif( numFrames )\n\t\t*numFrames = 0;\n}\n\nstatic int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )\n{\n\treturn 0;\n}\n\nstatic qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buffer )\n{\n\treturn true;\n}\n\nstatic void Mod_StudioLoadTextures( model_t *mod, void *data )\n{\n\t;\n}\n\nstatic void CL_DrawParticles( double frametime, particle_t *particles, float partsize )\n{\n\t;\n}\n\nstatic void CL_DrawTracers( double frametime, particle_t *tracers )\n{\n\t;\n}\n\nstatic void CL_DrawBeams( int fTrans, BEAM *beams )\n{\n\t;\n}\n\nstatic qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly )\n{\n\treturn false;\n}\n\nstatic int RefGetParm( int parm, int arg )\n{\n\treturn 0;\n}\n\nstatic void GetDetailScaleForTexture( int texture, float *xScale, float *yScale )\n{\n\t*xScale = *yScale = 1.0f;\n}\n\nstatic void GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *alpha )\n{\n\t*red = *green = *blue = *alpha = 0;\n}\n\nstatic float GetFrameTime( void )\n{\n\treturn 0.0f;\n}\n\nstatic void R_SetCurrentEntity( struct cl_entity_s *ent )\n{\n\t;\n}\n\nstatic void R_SetCurrentModel( struct model_s *mod )\n{\n\t;\n}\n\nstatic int GL_FindTexture( const char *name )\n{\n\treturn 0;\n}\n\nstatic const char *GL_TextureName( unsigned int texnum )\n{\n\treturn NULL;\n}\n\nstatic const byte *GL_TextureData( unsigned int texnum )\n{\n\treturn NULL;\n}\n\nstatic int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags )\n{\n\treturn 0;\n}\n\nstatic int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags )\n{\n\treturn 0;\n}\n\nstatic int GL_LoadTextureArray( const char **names, int flags )\n{\n\treturn 0;\n}\n\nstatic int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags )\n{\n\treturn 0;\n}\n\nstatic void R_OverrideTextureSourceSize( unsigned int textnum, unsigned int srcWidth, unsigned int srcHeight )\n{\n\n}\n\nstatic void DrawSingleDecal( struct decal_s *pDecal, struct msurface_s *fa )\n{\n\t;\n}\n\nstatic float *R_DecalSetupVerts( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount )\n{\n\treturn NULL;\n}\n\nstatic void R_EntityRemoveDecals( struct model_s *mod )\n{\n\t;\n}\n\nstatic void AVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )\n{\n\t;\n}\n\nstatic void GL_Bind( int tmu, unsigned int texnum )\n{\n\t;\n}\n\nstatic void GL_LoadTextureMatrix( const float *glmatrix )\n{\n\t;\n}\n\nstatic void GL_TexGen( unsigned int coord, unsigned int mode )\n{\n\t;\n}\n\nstatic void GL_UpdateTexSize( int texnum, int width, int height, int depth )\n{\n\t;\n}\n\nstatic void GL_DrawParticles( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime )\n{\n\t;\n}\n\nstatic colorVec LightVec( const float *start, const float *end, float *lightspot, float *lightvec )\n{\n\tcolorVec c = { 0 };\n\treturn c;\n}\n\nstatic struct mstudiotex_s *StudioGetTexture( struct cl_entity_s *e )\n{\n\treturn NULL;\n}\n\nstatic void GL_RenderFrame( const struct ref_viewpass_s *rvp )\n{\n\t;\n}\n\nstatic void GL_OrthoBounds( const float *mins, const float *maxs )\n{\n\t;\n}\n\nstatic qboolean R_SpeedsMessage( char *out, size_t size )\n{\n\treturn false;\n}\n\nstatic byte *Mod_GetCurrentVis( void )\n{\n\treturn NULL;\n}\n\nstatic void *R_GetProcAddress( const char *name )\n{\n\treturn NULL;\n}\n\nstatic void Color4f( float r, float g, float b, float a )\n{\n\t;\n}\n\nstatic void Color4ub( unsigned char r, unsigned char g, unsigned char b, unsigned char a )\n{\n\t;\n}\n\nstatic void TexCoord2f( float u, float v )\n{\n\t;\n}\n\nstatic void Vertex3fv( const float *worldPnt )\n{\n\t;\n}\n\nstatic void Vertex3f( float x, float y, float z )\n{\n\t;\n}\n\nstatic void Fog( float flFogColor[3], float flStart, float flEnd, int bOn )\n{\n\t;\n}\n\nstatic void ScreenToWorld( const float *screen, float *world  )\n{\n\t;\n}\n\nstatic void GetMatrix( const int pname, float *matrix )\n{\n\t;\n}\n\nstatic void FogParams( float flDensity, int iFogSkybox )\n{\n\t;\n}\n\nstatic void CullFace( TRICULLSTYLE mode )\n{\n\t;\n}\n\nstatic void VGUI_SetupDrawing( qboolean rect )\n{\n\t;\n}\n\nstatic void VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )\n{\n\t;\n}\n\nstatic const ref_interface_t gReffuncs =\n{\n\t.R_Init                = R_Init,\n\t.R_Shutdown            = R_SimpleStub,\n\t.R_GetConfigName       = R_GetConfigName,\n\t.R_SetDisplayTransform = R_SetDisplayTransform,\n\n\t.GL_SetupAttributes = GL_SetupAttributes,\n\t.GL_InitExtensions  = R_SimpleStub,\n\t.GL_ClearExtensions = R_SimpleStub,\n\n\t.R_GammaChanged       = R_SimpleStubBool,\n\t.R_BeginFrame         = R_SimpleStubBool,\n\t.R_RenderScene        = R_SimpleStub,\n\t.R_EndFrame           = R_SimpleStub,\n\t.R_PushScene          = R_SimpleStub,\n\t.R_PopScene           = R_SimpleStub,\n\t.GL_BackendStartFrame = R_SimpleStub,\n\t.GL_BackendEndFrame   = R_SimpleStub,\n\n\t.R_ClearScreen    = R_SimpleStub,\n\t.R_AllowFog       = R_SimpleStubBool,\n\t.GL_SetRenderMode = R_SimpleStubInt,\n\n\t.R_AddEntity      = R_AddEntity,\n\t.CL_AddCustomBeam = CL_AddCustomBeam,\n\t.R_ProcessEntData = R_ProcessEntData,\n\t.R_Flush          = R_SimpleStubUInt,\n\n\t.R_ShowTextures = R_SimpleStub,\n\n\t.R_GetTextureOriginalBuffer = R_GetTextureOriginalBuffer,\n\t.GL_LoadTextureFromBuffer   = GL_LoadTextureFromBuffer,\n\t.GL_ProcessTexture          = GL_ProcessTexture,\n\t.R_SetupSky                 = R_SetupSky,\n\n\t.R_Set2DMode      = R_SimpleStubBool,\n\t.R_DrawStretchRaw = R_DrawStretchRaw,\n\t.R_DrawStretchPic = R_DrawStretchPic,\n\t.FillRGBA         = FillRGBA,\n\t.WorldToScreen    = WorldToScreen,\n\n\t.VID_ScreenShot  = VID_ScreenShot,\n\t.VID_CubemapShot = VID_CubemapShot,\n\n\t.R_LightPoint = R_LightPoint,\n\n\t.R_DecalShoot      = R_DecalShoot,\n\t.R_DecalRemoveAll  = R_SimpleStubInt,\n\t.R_CreateDecalList = R_CreateDecalList,\n\t.R_ClearAllDecals  = R_SimpleStub,\n\n\t.R_StudioEstimateFrame = R_StudioEstimateFrame,\n\t.R_StudioLerpMovement  = R_StudioLerpMovement,\n\t.CL_InitStudioAPI      = R_SimpleStub,\n\n\t.R_SetSkyCloudsTextures     = R_SetSkyCloudsTextures,\n\t.GL_SubdivideSurface = GL_SubdivideSurface,\n\t.CL_RunLightStyles   = CL_RunLightStyles,\n\n\t.R_GetSpriteParms    = R_GetSpriteParms,\n\t.R_GetSpriteTexture  = R_GetSpriteTexture,\n\n\t.Mod_ProcessRenderData  = Mod_ProcessRenderData,\n\t.Mod_StudioLoadTextures = Mod_StudioLoadTextures,\n\n\t.CL_DrawParticles = CL_DrawParticles,\n\t.CL_DrawTracers   = CL_DrawTracers,\n\t.CL_DrawBeams     = CL_DrawBeams,\n\t.R_BeamCull       = R_BeamCull,\n\n\t.RefGetParm               = RefGetParm,\n\t.GetDetailScaleForTexture = GetDetailScaleForTexture,\n\t.GetExtraParmsForTexture  = GetExtraParmsForTexture,\n\t.GetFrameTime             = GetFrameTime,\n\n\t.R_SetCurrentEntity = R_SetCurrentEntity,\n\t.R_SetCurrentModel  = R_SetCurrentModel,\n\n\t.GL_FindTexture        = GL_FindTexture,\n\t.GL_TextureName        = GL_TextureName,\n\t.GL_TextureData        = GL_TextureData,\n\t.GL_LoadTexture        = GL_LoadTexture,\n\t.GL_CreateTexture      = GL_CreateTexture,\n\t.GL_LoadTextureArray   = GL_LoadTextureArray,\n\t.GL_CreateTextureArray = GL_CreateTextureArray,\n\t.GL_FreeTexture        = R_SimpleStubUInt,\n\t.R_OverrideTextureSourceSize = R_OverrideTextureSourceSize,\n\n\t.DrawSingleDecal      = DrawSingleDecal,\n\t.R_DecalSetupVerts    = R_DecalSetupVerts,\n\t.R_EntityRemoveDecals = R_EntityRemoveDecals,\n\n\t.AVI_UploadRawFrame = AVI_UploadRawFrame,\n\n\t.GL_Bind                = GL_Bind,\n\t.GL_SelectTexture       = R_SimpleStubInt,\n\t.GL_LoadTextureMatrix   = GL_LoadTextureMatrix,\n\t.GL_TexMatrixIdentity   = R_SimpleStub,\n\t.GL_CleanUpTextureUnits = R_SimpleStubInt,\n\t.GL_TexGen              = GL_TexGen,\n\t.GL_TextureTarget       = R_SimpleStubUInt,\n\t.GL_TexCoordArrayMode   = R_SimpleStubUInt,\n\t.GL_UpdateTexSize       = GL_UpdateTexSize,\n\t.GL_Reserved0           = NULL,\n\t.GL_Reserved1           = NULL,\n\n\t.GL_DrawParticles = GL_DrawParticles,\n\t.LightVec         = LightVec,\n\t.StudioGetTexture = StudioGetTexture,\n\n\t.GL_RenderFrame    = GL_RenderFrame,\n\t.GL_OrthoBounds    = GL_OrthoBounds,\n\t.R_SpeedsMessage   = R_SpeedsMessage,\n\t.Mod_GetCurrentVis = Mod_GetCurrentVis,\n\t.R_NewMap          = R_SimpleStub,\n\t.R_ClearScene      = R_SimpleStub,\n\t.R_GetProcAddress  = R_GetProcAddress,\n\n\t.TriRenderMode = R_SimpleStubInt,\n\t.Begin         = R_SimpleStubInt,\n\t.End           = R_SimpleStub,\n\t.Color4f       = Color4f,\n\t.Color4ub      = Color4ub,\n\t.TexCoord2f    = TexCoord2f,\n\t.Vertex3fv     = Vertex3fv,\n\t.Vertex3f      = Vertex3f,\n\t.Fog           = Fog,\n\t.ScreenToWorld = ScreenToWorld,\n\t.GetMatrix     = GetMatrix,\n\t.FogParams     = FogParams,\n\t.CullFace      = CullFace,\n\n\t.VGUI_SetupDrawing   = VGUI_SetupDrawing,\n\t.VGUI_UploadTextureBlock = VGUI_UploadTextureBlock,\n};\n\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals );\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals )\n{\n\tif( version != REF_API_VERSION )\n\t\treturn 0;\n\n\t// fill in our callbacks\n\t*funcs = gReffuncs;\n\tgEngfuncs = *engfuncs;\n\tgpGlobals = globals;\n\n\treturn REF_API_VERSION;\n}\n"
  },
  {
    "path": "ref/null/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tpass\n\ndef build(bld):\n\tbld.shlib(\n\t\tsource   = 'r_context.c',\n\t\ttarget   = 'ref_null',\n\t\tdefines  = 'REF_DLL',\n\t\tuse      = 'engine_includes sdk_includes werror',\n\t\tinstall_path = bld.env.LIBDIR\n\t)\n"
  },
  {
    "path": "ref/soft/adivtab.h",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// table of quotients and remainders for [-15...16] / [-15...16]\n\n// numerator = -15\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{1, -5},\n{1, -6},\n{1, -7},\n{2, -1},\n{2, -3},\n{3, 0},\n{3, -3},\n{5, 0},\n{7, -1},\n{15, 0},\n{0, 0},\n{-15, 0},\n{-8, 1},\n{-5, 0},\n{-4, 1},\n{-3, 0},\n{-3, 3},\n{-3, 6},\n{-2, 1},\n{-2, 3},\n{-2, 5},\n{-2, 7},\n{-2, 9},\n{-2, 11},\n{-2, 13},\n{-1, 0},\n{-1, 1},\n// numerator = -14\n{0, -14},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{1, -5},\n{1, -6},\n{2, 0},\n{2, -2},\n{2, -4},\n{3, -2},\n{4, -2},\n{7, 0},\n{14, 0},\n{0, 0},\n{-14, 0},\n{-7, 0},\n{-5, 1},\n{-4, 2},\n{-3, 1},\n{-3, 4},\n{-2, 0},\n{-2, 2},\n{-2, 4},\n{-2, 6},\n{-2, 8},\n{-2, 10},\n{-2, 12},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n// numerator = -13\n{0, -13},\n{0, -13},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{1, -5},\n{1, -6},\n{2, -1},\n{2, -3},\n{3, -1},\n{4, -1},\n{6, -1},\n{13, 0},\n{0, 0},\n{-13, 0},\n{-7, 1},\n{-5, 2},\n{-4, 3},\n{-3, 2},\n{-3, 5},\n{-2, 1},\n{-2, 3},\n{-2, 5},\n{-2, 7},\n{-2, 9},\n{-2, 11},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n// numerator = -12\n{0, -12},\n{0, -12},\n{0, -12},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{1, -5},\n{2, 0},\n{2, -2},\n{3, 0},\n{4, 0},\n{6, 0},\n{12, 0},\n{0, 0},\n{-12, 0},\n{-6, 0},\n{-4, 0},\n{-3, 0},\n{-3, 3},\n{-2, 0},\n{-2, 2},\n{-2, 4},\n{-2, 6},\n{-2, 8},\n{-2, 10},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n// numerator = -11\n{0, -11},\n{0, -11},\n{0, -11},\n{0, -11},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{1, -5},\n{2, -1},\n{2, -3},\n{3, -2},\n{5, -1},\n{11, 0},\n{0, 0},\n{-11, 0},\n{-6, 1},\n{-4, 1},\n{-3, 1},\n{-3, 4},\n{-2, 1},\n{-2, 3},\n{-2, 5},\n{-2, 7},\n{-2, 9},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n// numerator = -10\n{0, -10},\n{0, -10},\n{0, -10},\n{0, -10},\n{0, -10},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{2, 0},\n{2, -2},\n{3, -1},\n{5, 0},\n{10, 0},\n{0, 0},\n{-10, 0},\n{-5, 0},\n{-4, 2},\n{-3, 2},\n{-2, 0},\n{-2, 2},\n{-2, 4},\n{-2, 6},\n{-2, 8},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n// numerator = -9\n{0, -9},\n{0, -9},\n{0, -9},\n{0, -9},\n{0, -9},\n{0, -9},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{1, -4},\n{2, -1},\n{3, 0},\n{4, -1},\n{9, 0},\n{0, 0},\n{-9, 0},\n{-5, 1},\n{-3, 0},\n{-3, 3},\n{-2, 1},\n{-2, 3},\n{-2, 5},\n{-2, 7},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n// numerator = -8\n{0, -8},\n{0, -8},\n{0, -8},\n{0, -8},\n{0, -8},\n{0, -8},\n{0, -8},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{2, 0},\n{2, -2},\n{4, 0},\n{8, 0},\n{0, 0},\n{-8, 0},\n{-4, 0},\n{-3, 1},\n{-2, 0},\n{-2, 2},\n{-2, 4},\n{-2, 6},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n// numerator = -7\n{0, -7},\n{0, -7},\n{0, -7},\n{0, -7},\n{0, -7},\n{0, -7},\n{0, -7},\n{0, -7},\n{1, 0},\n{1, -1},\n{1, -2},\n{1, -3},\n{2, -1},\n{3, -1},\n{7, 0},\n{0, 0},\n{-7, 0},\n{-4, 1},\n{-3, 2},\n{-2, 1},\n{-2, 3},\n{-2, 5},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n// numerator = -6\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{0, -6},\n{1, 0},\n{1, -1},\n{1, -2},\n{2, 0},\n{3, 0},\n{6, 0},\n{0, 0},\n{-6, 0},\n{-3, 0},\n{-2, 0},\n{-2, 2},\n{-2, 4},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n// numerator = -5\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{0, -5},\n{1, 0},\n{1, -1},\n{1, -2},\n{2, -1},\n{5, 0},\n{0, 0},\n{-5, 0},\n{-3, 1},\n{-2, 1},\n{-2, 3},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n{-1, 11},\n// numerator = -4\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{0, -4},\n{1, 0},\n{1, -1},\n{2, 0},\n{4, 0},\n{0, 0},\n{-4, 0},\n{-2, 0},\n{-2, 2},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n{-1, 11},\n{-1, 12},\n// numerator = -3\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{0, -3},\n{1, 0},\n{1, -1},\n{3, 0},\n{0, 0},\n{-3, 0},\n{-2, 1},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n{-1, 11},\n{-1, 12},\n{-1, 13},\n// numerator = -2\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{0, -2},\n{1, 0},\n{2, 0},\n{0, 0},\n{-2, 0},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n{-1, 11},\n{-1, 12},\n{-1, 13},\n{-1, 14},\n// numerator = -1\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{0, -1},\n{1, 0},\n{0, 0},\n{-1, 0},\n{-1, 1},\n{-1, 2},\n{-1, 3},\n{-1, 4},\n{-1, 5},\n{-1, 6},\n{-1, 7},\n{-1, 8},\n{-1, 9},\n{-1, 10},\n{-1, 11},\n{-1, 12},\n{-1, 13},\n{-1, 14},\n{-1, 15},\n// numerator = 0\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n{0, 0},\n// numerator = 1\n{-1, -14},\n{-1, -13},\n{-1, -12},\n{-1, -11},\n{-1, -10},\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{0, 0},\n{1, 0},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n{0, 1},\n// numerator = 2\n{-1, -13},\n{-1, -12},\n{-1, -11},\n{-1, -10},\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, 0},\n{0, 0},\n{2, 0},\n{1, 0},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n{0, 2},\n// numerator = 3\n{-1, -12},\n{-1, -11},\n{-1, -10},\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -1},\n{-3, 0},\n{0, 0},\n{3, 0},\n{1, 1},\n{1, 0},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n{0, 3},\n// numerator = 4\n{-1, -11},\n{-1, -10},\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -2},\n{-2, 0},\n{-4, 0},\n{0, 0},\n{4, 0},\n{2, 0},\n{1, 1},\n{1, 0},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n{0, 4},\n// numerator = 5\n{-1, -10},\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -3},\n{-2, -1},\n{-3, -1},\n{-5, 0},\n{0, 0},\n{5, 0},\n{2, 1},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n{0, 5},\n// numerator = 6\n{-1, -9},\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, 0},\n{-6, 0},\n{0, 0},\n{6, 0},\n{3, 0},\n{2, 0},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n{0, 6},\n// numerator = 7\n{-1, -8},\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -5},\n{-2, -3},\n{-2, -1},\n{-3, -2},\n{-4, -1},\n{-7, 0},\n{0, 0},\n{7, 0},\n{3, 1},\n{2, 1},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n{0, 7},\n// numerator = 8\n{-1, -7},\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -6},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, -1},\n{-4, 0},\n{-8, 0},\n{0, 0},\n{8, 0},\n{4, 0},\n{2, 2},\n{2, 0},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 8},\n{0, 8},\n{0, 8},\n{0, 8},\n{0, 8},\n{0, 8},\n{0, 8},\n{0, 8},\n// numerator = 9\n{-1, -6},\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -7},\n{-2, -5},\n{-2, -3},\n{-2, -1},\n{-3, -3},\n{-3, 0},\n{-5, -1},\n{-9, 0},\n{0, 0},\n{9, 0},\n{4, 1},\n{3, 0},\n{2, 1},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 9},\n{0, 9},\n{0, 9},\n{0, 9},\n{0, 9},\n{0, 9},\n{0, 9},\n// numerator = 10\n{-1, -5},\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -8},\n{-2, -6},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, -2},\n{-4, -2},\n{-5, 0},\n{-10, 0},\n{0, 0},\n{10, 0},\n{5, 0},\n{3, 1},\n{2, 2},\n{2, 0},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 10},\n{0, 10},\n{0, 10},\n{0, 10},\n{0, 10},\n{0, 10},\n// numerator = 11\n{-1, -4},\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -9},\n{-2, -7},\n{-2, -5},\n{-2, -3},\n{-2, -1},\n{-3, -4},\n{-3, -1},\n{-4, -1},\n{-6, -1},\n{-11, 0},\n{0, 0},\n{11, 0},\n{5, 1},\n{3, 2},\n{2, 3},\n{2, 1},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 11},\n{0, 11},\n{0, 11},\n{0, 11},\n{0, 11},\n// numerator = 12\n{-1, -3},\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -10},\n{-2, -8},\n{-2, -6},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, -3},\n{-3, 0},\n{-4, 0},\n{-6, 0},\n{-12, 0},\n{0, 0},\n{12, 0},\n{6, 0},\n{4, 0},\n{3, 0},\n{2, 2},\n{2, 0},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 12},\n{0, 12},\n{0, 12},\n{0, 12},\n// numerator = 13\n{-1, -2},\n{-1, -1},\n{-1, 0},\n{-2, -11},\n{-2, -9},\n{-2, -7},\n{-2, -5},\n{-2, -3},\n{-2, -1},\n{-3, -5},\n{-3, -2},\n{-4, -3},\n{-5, -2},\n{-7, -1},\n{-13, 0},\n{0, 0},\n{13, 0},\n{6, 1},\n{4, 1},\n{3, 1},\n{2, 3},\n{2, 1},\n{1, 6},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 13},\n{0, 13},\n{0, 13},\n// numerator = 14\n{-1, -1},\n{-1, 0},\n{-2, -12},\n{-2, -10},\n{-2, -8},\n{-2, -6},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, -4},\n{-3, -1},\n{-4, -2},\n{-5, -1},\n{-7, 0},\n{-14, 0},\n{0, 0},\n{14, 0},\n{7, 0},\n{4, 2},\n{3, 2},\n{2, 4},\n{2, 2},\n{2, 0},\n{1, 6},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 14},\n{0, 14},\n// numerator = 15\n{-1, 0},\n{-2, -13},\n{-2, -11},\n{-2, -9},\n{-2, -7},\n{-2, -5},\n{-2, -3},\n{-2, -1},\n{-3, -6},\n{-3, -3},\n{-3, 0},\n{-4, -1},\n{-5, 0},\n{-8, -1},\n{-15, 0},\n{0, 0},\n{15, 0},\n{7, 1},\n{5, 0},\n{3, 3},\n{3, 0},\n{2, 3},\n{2, 1},\n{1, 7},\n{1, 6},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n{0, 15},\n// numerator = 16\n{-2, -14},\n{-2, -12},\n{-2, -10},\n{-2, -8},\n{-2, -6},\n{-2, -4},\n{-2, -2},\n{-2, 0},\n{-3, -5},\n{-3, -2},\n{-4, -4},\n{-4, 0},\n{-6, -2},\n{-8, 0},\n{-16, 0},\n{0, 0},\n{16, 0},\n{8, 0},\n{5, 1},\n{4, 0},\n{3, 1},\n{2, 4},\n{2, 2},\n{2, 0},\n{1, 7},\n{1, 6},\n{1, 5},\n{1, 4},\n{1, 3},\n{1, 2},\n{1, 1},\n{1, 0},\n"
  },
  {
    "path": "ref/soft/r_aclip.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_aclip.c: clip routines for drawing Alias models directly to the screen\n\n#include \"r_local.h\"\n\nstatic finalvert_t fv[2][8];\n\nvoid R_AliasProjectAndClipTestFinalVert( finalvert_t *fv );\nvoid R_Alias_clip_top( finalvert_t *pfv0, finalvert_t *pfv1,\n\t\t       finalvert_t *out );\nvoid R_Alias_clip_bottom( finalvert_t *pfv0, finalvert_t *pfv1,\n\t\t\t  finalvert_t *out );\nvoid R_Alias_clip_left( finalvert_t *pfv0, finalvert_t *pfv1,\n\t\t\tfinalvert_t *out );\nvoid R_Alias_clip_right( finalvert_t *pfv0, finalvert_t *pfv1,\n\t\t\t finalvert_t *out );\n\n\n/*\n================\nR_Alias_clip_z\n\npfv0 is the unclipped vertex, pfv1 is the z-clipped vertex\n================\n*/\nstatic void R_Alias_clip_z( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out )\n{\n\tfloat scale;\n\n\tscale = ( ALIAS_Z_CLIP_PLANE - pfv0->xyz[2] )\n\t\t/ ( pfv1->xyz[2] - pfv0->xyz[2] );\n\n\tout->xyz[0] = pfv0->xyz[0] + ( pfv1->xyz[0] - pfv0->xyz[0] ) * scale;\n\tout->xyz[1] = pfv0->xyz[1] + ( pfv1->xyz[1] - pfv0->xyz[1] ) * scale;\n\tout->xyz[2] = ALIAS_Z_CLIP_PLANE;\n\n\tout->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale;\n\tout->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale;\n\tout->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale;\n\n\tR_AliasProjectAndClipTestFinalVert( out );\n}\n\nvoid R_Alias_clip_left( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out )\n{\n\tfloat scale;\n\n\tif( pfv0->v >= pfv1->v )\n\t{\n\t\tscale = (float)( RI.aliasvrect.x - pfv0->u )\n\t\t\t/ ( pfv1->u - pfv0->u );\n\t\tout->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f;\n\t\tout->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f;\n\t\tout->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f;\n\t\tout->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f;\n\t\tout->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f;\n\t\tout->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f;\n\t}\n\telse\n\t{\n\t\tscale = (float)( RI.aliasvrect.x - pfv1->u )\n\t\t\t/ ( pfv0->u - pfv1->u );\n\t\tout->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f;\n\t\tout->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f;\n\t\tout->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f;\n\t\tout->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f;\n\t\tout->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f;\n\t\tout->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f;\n\t}\n}\n\nvoid R_Alias_clip_right( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out )\n{\n\tfloat scale;\n\n\tif( pfv0->v >= pfv1->v )\n\t{\n\t\tscale = (float)( RI.aliasvrectright - pfv0->u )\n\t\t\t/ ( pfv1->u - pfv0->u );\n\t\tout->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f;\n\t\tout->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f;\n\t\tout->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f;\n\t\tout->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f;\n\t\tout->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f;\n\t\tout->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f;\n\t}\n\telse\n\t{\n\t\tscale = (float)( RI.aliasvrectright - pfv1->u )\n\t\t\t/ ( pfv0->u - pfv1->u );\n\t\tout->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f;\n\t\tout->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f;\n\t\tout->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f;\n\t\tout->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f;\n\t\tout->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f;\n\t\tout->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f;\n\t}\n}\n\nvoid R_Alias_clip_top( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out )\n{\n\tfloat scale;\n\n\tif( pfv0->v >= pfv1->v )\n\t{\n\t\tscale = (float)( RI.aliasvrect.y - pfv0->v )\n\t\t\t/ ( pfv1->v - pfv0->v );\n\t\tout->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f;\n\t\tout->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f;\n\t\tout->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f;\n\t\tout->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f;\n\t\tout->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f;\n\t\tout->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f;\n\t}\n\telse\n\t{\n\t\tscale = (float)( RI.aliasvrect.y - pfv1->v )\n\t\t\t/ ( pfv0->v - pfv1->v );\n\t\tout->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f;\n\t\tout->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f;\n\t\tout->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f;\n\t\tout->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f;\n\t\tout->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f;\n\t\tout->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f;\n\t}\n}\n\nvoid R_Alias_clip_bottom( finalvert_t *pfv0, finalvert_t *pfv1,\n\t\t\t  finalvert_t *out )\n{\n\tfloat scale;\n\n\tif( pfv0->v >= pfv1->v )\n\t{\n\t\tscale = (float)( RI.aliasvrectbottom - pfv0->v )\n\t\t\t/ ( pfv1->v - pfv0->v );\n\n\t\tout->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f;\n\t\tout->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f;\n\t\tout->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f;\n\t\tout->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f;\n\t\tout->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f;\n\t\tout->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f;\n\t}\n\telse\n\t{\n\t\tscale = (float)( RI.aliasvrectbottom - pfv1->v )\n\t\t\t/ ( pfv0->v - pfv1->v );\n\n\t\tout->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f;\n\t\tout->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f;\n\t\tout->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f;\n\t\tout->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f;\n\t\tout->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f;\n\t\tout->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f;\n\t}\n}\n\nstatic int R_AliasClip( finalvert_t *in, finalvert_t *out, int flag, int count,\n\t\t\tvoid ( *clip )( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out ))\n{\n\tint i, j, k;\n\tint flags, oldflags;\n\n\tj = count - 1;\n\tk = 0;\n\tfor( i = 0; i < count; j = i, i++ )\n\t{\n\t\toldflags = in[j].flags & flag;\n\t\tflags = in[i].flags & flag;\n\n\t\tif( flags && oldflags )\n\t\t\tcontinue;\n\t\tif( oldflags ^ flags )\n\t\t{\n\t\t\tclip( &in[j], &in[i], &out[k] );\n\t\t\tout[k].flags = 0;\n\t\t\tif( out[k].u < RI.aliasvrect.x )\n\t\t\t\tout[k].flags |= ALIAS_LEFT_CLIP;\n\t\t\tif( out[k].v < RI.aliasvrect.y )\n\t\t\t\tout[k].flags |= ALIAS_TOP_CLIP;\n\t\t\tif( out[k].u > RI.aliasvrectright )\n\t\t\t\tout[k].flags |= ALIAS_RIGHT_CLIP;\n\t\t\tif( out[k].v > RI.aliasvrectbottom )\n\t\t\t\tout[k].flags |= ALIAS_BOTTOM_CLIP;\n\t\t\tk++;\n\t\t}\n\t\tif( !flags )\n\t\t{\n\t\t\tout[k] = in[i];\n\t\t\tk++;\n\t\t}\n\t}\n\n\treturn k;\n}\n\n/*\n================\nR_AliasClipTriangle\n================\n*/\nvoid R_AliasClipTriangle( finalvert_t *index0, finalvert_t *index1, finalvert_t *index2 )\n{\n\tint      i, k, pingpong;\n\tunsigned clipflags;\n\n// copy vertexes and fix seam texture coordinates\n\tfv[0][0] = *index0;\n\tfv[0][1] = *index1;\n\tfv[0][2] = *index2;\n\n// clip\n\tclipflags = fv[0][0].flags | fv[0][1].flags | fv[0][2].flags;\n\n\tif( clipflags & ALIAS_Z_CLIP )\n\t{\n\t\tk = R_AliasClip( fv[0], fv[1], ALIAS_Z_CLIP, 3, R_Alias_clip_z );\n\t\tif( k == 0 )\n\t\t\treturn;\n\n\t\tpingpong = 1;\n\t\tclipflags = fv[1][0].flags | fv[1][1].flags | fv[1][2].flags;\n\t}\n\telse\n\t{\n\t\tpingpong = 0;\n\t\tk = 3;\n\t}\n\n\tif( clipflags & ALIAS_LEFT_CLIP )\n\t{\n\t\tk = R_AliasClip( fv[pingpong], fv[pingpong ^ 1],\n\t\t\t\t ALIAS_LEFT_CLIP, k, R_Alias_clip_left );\n\t\tif( k == 0 )\n\t\t\treturn;\n\n\t\tpingpong ^= 1;\n\t}\n\n\tif( clipflags & ALIAS_RIGHT_CLIP )\n\t{\n\t\tk = R_AliasClip( fv[pingpong], fv[pingpong ^ 1],\n\t\t\t\t ALIAS_RIGHT_CLIP, k, R_Alias_clip_right );\n\t\tif( k == 0 )\n\t\t\treturn;\n\n\t\tpingpong ^= 1;\n\t}\n\n\tif( clipflags & ALIAS_BOTTOM_CLIP )\n\t{\n\t\tk = R_AliasClip( fv[pingpong], fv[pingpong ^ 1],\n\t\t\t\t ALIAS_BOTTOM_CLIP, k, R_Alias_clip_bottom );\n\t\tif( k == 0 )\n\t\t\treturn;\n\n\t\tpingpong ^= 1;\n\t}\n\n\tif( clipflags & ALIAS_TOP_CLIP )\n\t{\n\t\tk = R_AliasClip( fv[pingpong], fv[pingpong ^ 1],\n\t\t\t\t ALIAS_TOP_CLIP, k, R_Alias_clip_top );\n\t\tif( k == 0 )\n\t\t\treturn;\n\n\t\tpingpong ^= 1;\n\t}\n\n\tfor( i = 0; i < k; i++ )\n\t{\n\t\tif( fv[pingpong][i].u < RI.aliasvrect.x )\n\t\t\tfv[pingpong][i].u = RI.aliasvrect.x;\n\t\telse if( fv[pingpong][i].u > RI.aliasvrectright )\n\t\t\tfv[pingpong][i].u = RI.aliasvrectright;\n\n\t\tif( fv[pingpong][i].v < RI.aliasvrect.y )\n\t\t\tfv[pingpong][i].v = RI.aliasvrect.y;\n\t\telse if( fv[pingpong][i].v > RI.aliasvrectbottom )\n\t\t\tfv[pingpong][i].v = RI.aliasvrectbottom;\n\n\t\tfv[pingpong][i].flags = 0;\n\t}\n\n// draw triangles\n\tfor( i = 1; i < k - 1; i++ )\n\t{\n\t\taliastriangleparms.a = &fv[pingpong][0];\n\t\taliastriangleparms.b = &fv[pingpong][i];\n\t\taliastriangleparms.c = &fv[pingpong][i + 1];\n\t\tR_DrawTriangle();\n\t}\n}\n\n"
  },
  {
    "path": "ref/soft/r_beams.c",
    "content": "/*\ngl_beams.c - beams rendering\nCopyright (C) 2009 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"r_efx.h\"\n#include \"event_flags.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"customentity.h\"\n#include \"pm_local.h\"\n#include \"triangleapi.h\"\n#include \"studio.h\"\n\n#define NOISE_DIVISIONS 64 // don't touch - many tripmines cause the crash when it equal 128\n\ntypedef struct\n{\n\tvec3_t pos;\n\tfloat  texcoord;        // Y texture coordinate\n\tfloat  width;\n} beamseg_t;\n\n/*\n==============================================================\n\nFRACTAL NOISE\n\n==============================================================\n*/\nstatic float rgNoise[NOISE_DIVISIONS + 1]; // global noise array\n\n// freq2 += step * 0.1;\n// Fractal noise generator, power of 2 wavelength\nstatic void FracNoise( float *noise, int divs )\n{\n\tint div2;\n\n\tdiv2 = divs >> 1;\n\tif( divs < 2 )\n\t\treturn;\n\n\t// noise is normalized to +/- scale\n\tnoise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * gEngfuncs.COM_RandomFloat( -0.125f, 0.125f );\n\n\tif( div2 > 1 )\n\t{\n\t\tFracNoise( &noise[div2], div2 );\n\t\tFracNoise( noise, div2 );\n\t}\n}\n\nstatic void SineNoise( float *noise, int divs )\n{\n\tfloat freq = 0;\n\tfloat step = M_PI_F / (float)divs;\n\tint   i;\n\n\tfor( i = 0; i < divs; i++ )\n\t{\n\t\tnoise[i] = sin( freq );\n\t\tfreq += step;\n\t}\n}\n\n\n/*\n==============================================================\n\nBEAM MATHLIB\n\n==============================================================\n*/\nstatic void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp )\n{\n\t// direction in worldspace of the center of the beam\n\tvec3_t vecBeamCenter;\n\n\tVectorNormalize2( vecBeamDelta, vecBeamCenter );\n\tCrossProduct( RI.vforward, vecBeamCenter, pPerp );\n\tVectorNormalize( pPerp );\n}\n\nstatic void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal )\n{\n\t// vTangentY = line vector for beam\n\tvec3_t vTangentY, vDirToBeam;\n\n\tVectorSubtract( vStartPos, vNextPos, vTangentY );\n\n\t// vDirToBeam = vector from viewer origin to beam\n\tVectorSubtract( vStartPos, RI.vieworg, vDirToBeam );\n\n\t// get a vector that is perpendicular to us and perpendicular to the beam.\n\t// this is used to fatten the beam.\n\tCrossProduct( vTangentY, vDirToBeam, pNormal );\n\tVectorNormalizeFast( pNormal );\n}\n\n\n/*\n==============\nR_BeamCull\n\nCull the beam by bbox\n==============\n*/\nqboolean GAME_EXPORT R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly )\n{\n\tvec3_t mins, maxs;\n\tint    i;\n\treturn false;\n/*\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t\tmaxs[i] += 1.0f;\n\t}\n\n\t// check bbox\n\tif( gEngfuncs.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )))\n\t{\n\t\tif( pvsOnly || !R_CullBox( mins, maxs ))\n\t\t{\n\t\t\t// beam is visible\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// beam is culled\n\treturn true;\n\t*/\n}\n\n/*\n================\nCL_AddCustomBeam\n\nAdd the beam that encoded as custom entity\n================\n*/\nvoid GAME_EXPORT CL_AddCustomBeam( cl_entity_t *pEnvBeam )\n{\n\tif( tr.draw_list->num_beam_entities >= MAX_VISIBLE_PACKET )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Too many beams %d!\\n\", tr.draw_list->num_beam_entities );\n\t\treturn;\n\t}\n\n\tif( pEnvBeam )\n\t{\n\t\ttr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = pEnvBeam;\n\t\ttr.draw_list->num_beam_entities++;\n\t}\n}\n\n\n/*\n==============================================================\n\nBEAM DRAW METHODS\n\n==============================================================\n*/\n/*\n================\nR_DrawSegs\n\ngeneral code for drawing beams\n================\n*/\nstatic void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags )\n{\n\tint       noiseIndex, noiseStep;\n\tint       i, total_segs, segs_drawn;\n\tfloat     div, length, fraction, factor;\n\tfloat     flMaxWidth, vLast, vStep, brightness;\n\tvec3_t    perp1, vLastNormal = { 0.0f };\n\tbeamseg_t curSeg;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tlength = VectorLength( delta );\n\tflMaxWidth = width * 0.5f;\n\tdiv = 1.0f / ( segments - 1 );\n\n\tif( length * div < flMaxWidth * 1.414f )\n\t{\n\t\t// here, we have too many segments; we could get overlap... so lets have less segments\n\t\tsegments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f;\n\t\tif( segments < 2 )\n\t\t\tsegments = 2;\n\t}\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tdiv = 1.0f / ( segments - 1 );\n\tlength *= 0.01f;\n\tvStep = length * div; // Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\n\tif( flags & FBEAM_SINENOISE )\n\t{\n\t\tif( segments < 16 )\n\t\t{\n\t\t\tsegments = 16;\n\t\t\tdiv = 1.0f / ( segments - 1 );\n\t\t}\n\t\tscale *= 100.0f;\n\t\tlength = segments * 0.1f;\n\t}\n\telse\n\t{\n\t\tscale *= length * 2.0f;\n\t}\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tbrightness = 1.0f;\n\tnoiseIndex = 0;\n\n\tif( FBitSet( flags, FBEAM_SHADEIN ))\n\t\tbrightness = 0;\n\n\t// Choose two vectors that are perpendicular to the beam\n\tR_BeamComputePerpendicular( delta, perp1 );\n\n\ttotal_segs = segments;\n\tsegs_drawn = 0;\n\n\t// specify all the segments.\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tbeamseg_t nextSeg;\n\t\tvec3_t    vPoint1, vPoint2;\n\n\t\tAssert( noiseIndex < ( NOISE_DIVISIONS << 16 ));\n\n\t\tfraction = i * div;\n\n\t\tVectorMA( source, fraction, delta, nextSeg.pos );\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tfactor = rgNoise[noiseIndex >> 16] * scale;\n\n\t\t\tif( FBitSet( flags, FBEAM_SINENOISE ))\n\t\t\t{\n\t\t\t\tfloat s, c;\n\n\t\t\t\tSinCos( fraction * M_PI_F * length + freq, &s, &c );\n\t\t\t\tVectorMA( nextSeg.pos, ( factor * s ), RI.vup, nextSeg.pos );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tVectorMA( nextSeg.pos, ( factor * c ), RI.vright, nextSeg.pos );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorMA( nextSeg.pos, factor, perp1, nextSeg.pos );\n\t\t\t}\n\t\t}\n\n\t\t// specify the next segment.\n\t\tnextSeg.width = width * 2.0f;\n\t\tnextSeg.texcoord = vLast;\n\n\t\tif( segs_drawn > 0 )\n\t\t{\n\t\t\t// Get a vector that is perpendicular to us and perpendicular to the beam.\n\t\t\t// This is used to fatten the beam.\n\t\t\tvec3_t vNormal, vAveNormal;\n\n\t\t\tR_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal );\n\n\t\t\tif( segs_drawn > 1 )\n\t\t\t{\n\t\t\t\t// Average this with the previous normal\n\t\t\t\tVectorAdd( vNormal, vLastNormal, vAveNormal );\n\t\t\t\tVectorScale( vAveNormal, 0.5f, vAveNormal );\n\t\t\t\tVectorNormalizeFast( vAveNormal );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy( vNormal, vAveNormal );\n\t\t\t}\n\n\t\t\tVectorCopy( vNormal, vLastNormal );\n\n\t\t\t// draw regular segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, ( -curSeg.width * 0.5f ), vAveNormal, vPoint2 );\n\n\t\t\tTriTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\t// pglNormal3fv( vAveNormal );\n\t\t\tTriVertex3fv( vPoint1 );\n\n\t\t\tTriTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\t// pflNormal3fv( vAveNormal );\n\t\t\tTriVertex3fv( vPoint2 );\n\t\t}\n\n\t\tcurSeg = nextSeg;\n\t\tsegs_drawn++;\n\n\t\tif( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tif( fraction < 0.5f )\n\t\t\t\tbrightness = fraction;\n\t\t\telse\n\t\t\t\tbrightness = ( 1.0f - fraction );\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEIN ))\n\t\t{\n\t\t\tbrightness = fraction;\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tbrightness = 1.0f - fraction;\n\t\t}\n\n\t\tif( segs_drawn == total_segs )\n\t\t{\n\t\t\t// draw the last segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, ( -curSeg.width * 0.5f ), vLastNormal, vPoint2 );\n\n\t\t\t// specify the points.\n\t\t\tTriTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\t// pglNormal3fv( vLastNormal );\n\t\t\tTriVertex3fv( vPoint1 );\n\n\t\t\tTriTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\t// pglNormal3fv( vLastNormal );\n\t\t\tTriVertex3fv( vPoint2 );\n\t\t}\n\n\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\t\tnoiseIndex += noiseStep;\n\t}\n}\n\n/*\n================\nR_DrawTorus\n\nDraw beamtours\n================\n*/\nstatic void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tint    i, noiseIndex, noiseStep;\n\tfloat  div, length, fraction, factor, vLast, vStep;\n\tvec3_t last1, last2, point, screen, screenLast, tmp, normal;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f )\n\t\tlength = 0.5f;             // don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\n\tvStep = length * div; // Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tnoiseIndex = 0;\n\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat s, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2];\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tif(( noiseIndex >> 16 ) < NOISE_DIVISIONS )\n\t\t\t{\n\t\t\t\tfactor = rgNoise[noiseIndex >> 16] * scale;\n\t\t\t\tVectorMA( point, factor, RI.vup, point );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tfactor = rgNoise[noiseIndex >> 16] * scale * cos( fraction *M_PI_F * 3 + freq );\n\t\t\t\tVectorMA( point, factor, RI.vright, point );\n\t\t\t}\n\t\t}\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\t\t\tVectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x)\n\t\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep; // advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t}\n}\n\n/*\n================\nR_DrawDisk\n\nDraw beamdisk\n================\n*/\nstatic void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tfloat  div, length, fraction;\n\tfloat  w, vLast, vStep;\n\tvec3_t point;\n\tint    i;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )                // UNDONE: Allow more segments?\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f )\n\t\tlength = 0.5f;                  // don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\tvStep = length * div; // Texture length texels per space pixel\n\n\t// scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// clamp the beam width\n\tw = fmod( freq, width ) * delta[2];\n\n\t// NOTE: we must force the degenerate triangles to be on the edge\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat s, c;\n\n\t\tfraction = i * div;\n\t\tVectorCopy( source, point );\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 1.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tSinCos( fraction * M_PI2, &s, &c );\n\t\tpoint[0] = s * w + source[0];\n\t\tpoint[1] = c * w + source[1];\n\t\tpoint[2] = source[2];\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 0.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep; // advance texture scroll (v axis only)\n\t}\n}\n\n/*\n================\nR_DrawCylinder\n\nDraw beam cylinder\n================\n*/\nstatic void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )\n{\n\tfloat  div, length, fraction;\n\tfloat  vLast, vStep;\n\tvec3_t point;\n\tint    i;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f )\n\t\tlength = 0.5f;                  // don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\tvStep = length * div; // texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat s, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2] + width;\n\n\t\tTriBrightness( 0 );\n\t\tTriTexCoord2f( 1, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tpoint[0] = s * freq * ( delta[2] + width ) + source[0];\n\t\tpoint[1] = c * freq * ( delta[2] + width ) + source[1];\n\t\tpoint[2] = source[2] - width;\n\n\t\tTriBrightness( 1 );\n\t\tTriTexCoord2f( 0, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\t}\n}\n\n/*\n==============\nR_DrawBeamFollow\n\ndrawi followed beam\n==============\n*/\nstatic void R_DrawBeamFollow( BEAM *pbeam, float frametime )\n{\n\tparticle_t *pnew, *particles;\n\tfloat      fraction, div, vLast, vStep, saved_fraction;\n\tvec3_t     last1, last2, tmp, screen, saved_last2;\n\tvec3_t     delta, screenLast, normal;\n\n\tgEngfuncs.R_FreeDeadParticles( &pbeam->particles );\n\n\tparticles = pbeam->particles;\n\tpnew = NULL;\n\n\tdiv = 0;\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tif( particles )\n\t\t{\n\t\t\tVectorSubtract( particles->org, pbeam->source, delta );\n\t\t\tdiv = VectorLength( delta );\n\n\t\t\tif( div >= 32 )\n\t\t\t{\n\t\t\t\tpnew = gEngfuncs.CL_AllocParticleFast();\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpnew = gEngfuncs.CL_AllocParticleFast();\n\t\t}\n\t}\n\n\tif( pnew )\n\t{\n\t\tVectorCopy( pbeam->source, pnew->org );\n\t\tpnew->die = gp_cl->time + pbeam->amplitude;\n\t\tVectorClear( pnew->vel );\n\n\t\tpnew->next = particles;\n\t\tpbeam->particles = pnew;\n\t\tparticles = pnew;\n\t}\n\n\t// nothing to draw\n\tif( !particles )\n\t\treturn;\n\n\tif( !pnew && div != 0 )\n\t{\n\t\tVectorCopy( pbeam->source, delta );\n\t\tTriWorldToScreen( pbeam->source, screenLast );\n\t\tTriWorldToScreen( particles->org, screen );\n\t}\n\telse if( particles && particles->next )\n\t{\n\t\tVectorCopy( particles->org, delta );\n\t\tTriWorldToScreen( particles->org, screenLast );\n\t\tTriWorldToScreen( particles->next->org, screen );\n\t\tparticles = particles->next;\n\t}\n\telse\n\t{\n\t\treturn;\n\t}\n\n\t// UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the\n\t// first beam segment for this trail\n\n\t// build world-space normal to screen-space direction vector\n\tVectorSubtract( screen, screenLast, tmp );\n\t// we don't need Z, we're in screen space\n\ttmp[2] = 0;\n\tVectorNormalize( tmp );\n\n\t// Build point along noraml line (normal is -y, x)\n\tVectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x)\n\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t// Make a wide line\n\tVectorMA( delta, pbeam->width, normal, last1 );\n\tVectorMA( delta, -pbeam->width, normal, last2 );\n\n\tdiv = 1.0f / pbeam->amplitude;\n\tfraction = ( pbeam->die - gp_cl->time ) * div;\n\n\tvLast = 0.0f;\n\tvStep = 1.0f;\n\n\twhile( particles )\n\t{\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 1 );\n\t\tTriVertex3fv( last2 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 1 );\n\t\tTriVertex3fv( last1 );\n\n\t\tVectorCopy( last2, saved_last2 );\n\t\tsaved_fraction = fraction;\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( particles->org, screen );\n\t\t// Build world-space normal to screen-space direction vector\n\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t// we don't need Z, we're in screen space\n\t\ttmp[2] = 0;\n\t\tVectorNormalize( tmp );\n\t\tVectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x)\n\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t// Make a wide line\n\t\tVectorMA( particles->org, pbeam->width, normal, last1 );\n\t\tVectorMA( particles->org, -pbeam->width, normal, last2 );\n\n\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\n\t\tif( particles->next != NULL )\n\t\t{\n\t\t\tfraction = ( particles->die - gp_cl->time ) * div;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfraction = 0.0;\n\t\t}\n\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 0 );\n\t\tTriVertex3fv( last1 );\n\t\tTriBrightness( saved_fraction );\n\t\tTriTexCoord2f( 1.0f, 1.0f );\n\t\tTriVertex3fv( saved_last2 );\n\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\tTriVertex3fv( last1 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 0 );\n\t\tTriVertex3fv( last2 );\n\n\t\tVectorCopy( screen, screenLast );\n\n\t\tparticles = particles->next;\n\t}\n\n\t// drift popcorn trail if there is a velocity\n\tparticles = pbeam->particles;\n\n\twhile( particles )\n\t{\n\t\tVectorMA( particles->org, frametime, particles->vel, particles->org );\n\t\tparticles = particles->next;\n\t}\n}\n\n/*\n================\nR_DrawRing\n\nDraw beamring\n================\n*/\nstatic void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments )\n{\n\tint    i, j, noiseIndex, noiseStep;\n\tfloat  div, length, fraction, factor, vLast, vStep;\n\tvec3_t last1, last2, point, screen, screenLast;\n\tvec3_t tmp, normal, center, xaxis, yaxis;\n\tfloat  radius, x, y, scale;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tVectorClear( screenLast );\n\tsegments = segments * M_PI;\n\n\tif( segments > NOISE_DIVISIONS * 8 )\n\t\tsegments = NOISE_DIVISIONS * 8;\n\n\tlength = VectorLength( delta ) * 0.01f * M_PI_F;\n\tif( length < 0.5f )\n\t\tlength = 0.5f;                          // Don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\n\tvStep = length * div / 8.0f; // texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1.0f );\n\tscale = amplitude * length / 8.0f;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8;\n\tnoiseIndex = 0;\n\n\tVectorScale( delta, 0.5f, delta );\n\tVectorAdd( source, delta, center );\n\n\tVectorCopy( delta, xaxis );\n\tradius = VectorLength( xaxis );\n\n\t// cull beamring\n\t// --------------------------------\n\t// Compute box center +/- radius\n\tVectorSet( last1, radius, radius, scale );\n\tVectorAdd( center, last1, tmp );         // maxs\n\tVectorSubtract( center, last1, screen ); // mins\n\n\tif( !WORLDMODEL )\n\t\treturn;\n\n\t// is that box in PVS && frustum?\n\tif( !gEngfuncs.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( ))) // || R_CullBox( screen, tmp ))\n\t{\n\t\treturn;\n\t}\n\n\tVectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f );\n\tVectorNormalize( yaxis );\n\tVectorScale( yaxis, radius, yaxis );\n\n\tj = segments / 8;\n\n\tfor( i = 0; i < segments + 1; i++ )\n\t{\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2, &x, &y );\n\n\t\tVectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point );\n\n\t\t// distort using noise\n\t\tfactor = rgNoise[( noiseIndex >> 16 ) & ( NOISE_DIVISIONS - 1 )] * scale;\n\t\tVectorMA( point, factor, RI.vup, point );\n\n\t\t// Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\tfactor = rgNoise[( noiseIndex >> 16 ) & ( NOISE_DIVISIONS - 1 )] * scale;\n\t\tfactor *= cos( fraction * M_PI_F * 24 + freq );\n\t\tVectorMA( point, factor, RI.vright, point );\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\t// Build point along normal line (normal is -y, x)\n\t\t\tVectorScale( RI.vup, tmp[0], normal );\n\t\t\tVectorMA( normal, tmp[1], RI.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1.0f, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0.0f, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t\tj--;\n\n\t\tif( j == 0 && amplitude != 0 )\n\t\t{\n\t\t\tj = segments / 8;\n\t\t\tFracNoise( rgNoise, NOISE_DIVISIONS );\n\t\t}\n\t}\n}\n\n/*\n==============\nR_BeamComputePoint\n\ncompute attachment point for beam\n==============\n*/\nstatic qboolean R_BeamComputePoint( int beamEnt, vec3_t pt )\n{\n\tcl_entity_t *ent;\n\tint         attach;\n\n\tent = gEngfuncs.R_BeamGetEntity( beamEnt );\n\n\tif( beamEnt < 0 )\n\t\tattach = BEAMENT_ATTACHMENT( -beamEnt );\n\telse\n\t\tattach = BEAMENT_ATTACHMENT( beamEnt );\n\n\tif( !ent )\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s: invalid entity %i\\n\", __func__, BEAMENT_ENTITY( beamEnt ));\n\t\tVectorClear( pt );\n\t\treturn false;\n\t}\n\n\t// get attachment\n\tif( attach > 0 )\n\t\tVectorCopy( ent->attachment[attach - 1], pt );\n\telse if( ent->index == ( gp_cl->playernum + 1 ))\n\t\tVectorCopy( gp_cl->simorg, pt );\n\telse\n\t\tVectorCopy( ent->origin, pt );\n\n\treturn true;\n}\n\n/*\n==============\nR_BeamRecomputeEndpoints\n\nRecomputes beam endpoints..\n==============\n*/\nstatic qboolean R_BeamRecomputeEndpoints( BEAM *pbeam )\n{\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tcl_entity_t *start = gEngfuncs.R_BeamGetEntity( pbeam->startEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->startEntity, pbeam->source ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = start->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_STARTVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_STARTENTITY );\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_ENDENTITY ))\n\t{\n\t\tcl_entity_t *end = gEngfuncs.R_BeamGetEntity( pbeam->endEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->endEntity, pbeam->target ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = end->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_ENDVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ENDENTITY );\n\t\t\tpbeam->die = gp_cl->time;\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE ))\n\t\treturn false;\n\treturn true;\n}\n\n\n/*\n==============\nR_BeamDraw\n\nUpdate beam vars and draw it\n==============\n*/\nstatic void R_BeamDraw( BEAM *pbeam, float frametime )\n{\n\tmodel_t *model;\n\tvec3_t  delta;\n\n\tmodel = CL_ModelHandle( pbeam->modelIndex );\n\tSetBits( pbeam->flags, FBEAM_ISACTIVE );\n\n\tif( !model || model->type != mod_sprite )\n\t{\n\t\tpbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore\n\t\tpbeam->die = gp_cl->time;\n\t\treturn;\n\t}\n\n\t// update frequency\n\tpbeam->freq += frametime;\n\n\t// generate fractal noise\n\tif( frametime != 0.0f )\n\t{\n\t\trgNoise[0] = 0;\n\t\trgNoise[NOISE_DIVISIONS] = 0;\n\t}\n\n\tif( pbeam->amplitude != 0 && frametime != 0.0f )\n\t{\n\t\tif( FBitSet( pbeam->flags, FBEAM_SINENOISE ))\n\t\t\tSineNoise( rgNoise, NOISE_DIVISIONS );\n\t\telse\n\t\t\tFracNoise( rgNoise, NOISE_DIVISIONS );\n\t}\n\n\t// update end points\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ))\n\t{\n\t\t// makes sure attachment[0] + attachment[1] are valid\n\t\tif( !R_BeamRecomputeEndpoints( pbeam ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore\n\t\t\treturn;\n\t\t}\n\n\t\t// compute segments from the new endpoints\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorClear( pbeam->delta );\n\n\t\tif( VectorLength( delta ) > 0.0000001f )\n\t\t\tVectorCopy( delta, pbeam->delta );\n\n\t\tif( pbeam->amplitude >= 0.50f )\n\t\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels\n\t\telse\n\t\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels\n\t}\n\n\tif( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 ))\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\t// don't draw really short or inactive beams\n\tif( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f )\n\t{\n\t\treturn;\n\t}\n\n\tif( pbeam->flags & ( FBEAM_FADEIN | FBEAM_FADEOUT ))\n\t{\n\t\t// update life cycle\n\t\tpbeam->t = pbeam->freq + ( pbeam->die - gp_cl->time );\n\t\tif( pbeam->t != 0.0f )\n\t\t\tpbeam->t = 1.0f - pbeam->freq / pbeam->t;\n\t}\n\n\tif( pbeam->type == TE_BEAMHOSE )\n\t{\n\t\tfloat flDot;\n\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorNormalize( delta );\n\n\t\tflDot = DotProduct( delta, RI.vforward );\n\n\t\t// abort if the player's looking along it away from the source\n\t\tif( flDot > 0 )\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat  flFade = pow( flDot, 10 );\n\t\t\tvec3_t localDir, vecProjection, tmp;\n\t\t\tfloat  flDistance;\n\n\t\t\t// fade the beam if the player's not looking at the source\n\t\t\tVectorSubtract( RI.vieworg, pbeam->source, localDir );\n\t\t\tflDot = DotProduct( delta, localDir );\n\t\t\tVectorScale( delta, flDot, vecProjection );\n\t\t\tVectorSubtract( localDir, vecProjection, tmp );\n\t\t\tflDistance = VectorLength( tmp );\n\n\t\t\tif( flDistance > 30 )\n\t\t\t{\n\t\t\t\tflDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f );\n\t\t\t\tif( flDistance <= 0 )\n\t\t\t\t\tflFade = 0;\n\t\t\t\telse\n\t\t\t\t\tflFade *= pow( flDistance, 3 );\n\t\t\t}\n\n\t\t\tif( flFade < ( 1.0f / 255.0f ))\n\t\t\t\treturn;\n\n\t\t\t// FIXME: needs to be testing\n\t\t\tpbeam->brightness *= flFade;\n\t\t}\n\t}\n\n\tTriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd );\n\n\tif( !TriSpriteTexture( model, (int)( pbeam->frame + pbeam->frameRate * gp_cl->time ) % pbeam->frameCount ))\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\tif( pbeam->type == TE_BEAMFOLLOW )\n\t{\n\t\tcl_entity_t *pStart;\n\n\t\t// XASH SPECIFIC: get brightness from head entity\n\t\tpStart = gEngfuncs.R_BeamGetEntity( pbeam->startEntity );\n\t\tif( pStart && pStart->curstate.rendermode != kRenderNormal )\n\t\t\tpbeam->brightness = CL_FxBlend( pStart ) / 255.0f;\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_FADEIN ))\n\t\tTriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness );\n\telse if( FBitSet( pbeam->flags, FBEAM_FADEOUT ))\n\t\tTriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness );\n\telse\n\t\tTriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness );\n\n\tswitch( pbeam->type )\n\t{\n\tcase TE_BEAMTORUS:\n\t\t// GL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMDISK:\n\t\t// GL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMCYLINDER:\n\t\t// GL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMPOINTS:\n\tcase TE_BEAMHOSE:\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMFOLLOW:\n\t\tTriBegin( TRI_TRIANGLES );\n\t\tR_DrawBeamFollow( pbeam, frametime );\n\t\tTriEnd();\n\t\tbreak;\n\tcase TE_BEAMRING:\n\t\t// GL_Cull( GL_NONE );\n\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\tR_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );\n\t\tTriEnd();\n\t\tbreak;\n\t}\n\n\t// GL_Cull( GL_FRONT );\n\tr_stats.c_view_beams_count++;\n}\n\n/*\n==============\nR_BeamSetAttributes\n\nset beam attributes\n==============\n*/\nstatic void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame )\n{\n\tpbeam->frame = (float)startFrame;\n\tpbeam->frameRate = framerate;\n\tpbeam->r = r;\n\tpbeam->g = g;\n\tpbeam->b = b;\n}\n\n/*\n==============\nR_BeamSetup\n\ngeneric function. all beams must be\npassed through this\n==============\n*/\nstatic void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )\n{\n\tmodel_t *sprite = CL_ModelHandle( modelIndex );\n\n\tif( !sprite )\n\t\treturn;\n\n\tpbeam->type = BEAM_POINTS;\n\tpbeam->modelIndex = modelIndex;\n\tpbeam->frame = 0;\n\tpbeam->frameRate = 0;\n\tpbeam->frameCount = sprite->numframes;\n\n\tVectorCopy( start, pbeam->source );\n\tVectorCopy( end, pbeam->target );\n\tVectorSubtract( end, start, pbeam->delta );\n\n\tpbeam->freq = speed * gp_cl->time;\n\tpbeam->die = life + gp_cl->time;\n\tpbeam->amplitude = amplitude;\n\tpbeam->brightness = brightness;\n\tpbeam->width = width;\n\tpbeam->speed = speed;\n\n\tif( amplitude >= 0.50f )\n\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels\n\telse\n\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f;         // one per 16 pixels\n\n\tpbeam->pFollowModel = NULL;\n\tpbeam->flags = 0;\n}\n\n\n\n/*\n==============\nR_BeamDrawCustomEntity\n\ninitialize beam from server entity\n==============\n*/\nstatic void R_BeamDrawCustomEntity( cl_entity_t *ent )\n{\n\tBEAM  beam;\n\tfloat amp = ent->curstate.body / 100.0f;\n\tfloat blend = CL_FxBlend( ent ) / 255.0f;\n\tfloat r, g, b;\n\tint   beamFlags;\n\n\tr = ent->curstate.rendercolor.r / 255.0f;\n\tg = ent->curstate.rendercolor.g / 255.0f;\n\tb = ent->curstate.rendercolor.b / 255.0f;\n\n\tR_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime );\n\tR_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame );\n\tbeam.pFollowModel = NULL;\n\n\tswitch( ent->curstate.rendermode & 0x0F )\n\t{\n\tcase BEAM_ENTPOINT:\n\t\tbeam.type = TE_BEAMPOINTS;\n\t\tif( ent->curstate.sequence )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_STARTENTITY );\n\t\t\tbeam.startEntity = ent->curstate.sequence;\n\t\t}\n\t\tif( ent->curstate.skin )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_ENDENTITY );\n\t\t\tbeam.endEntity = ent->curstate.skin;\n\t\t}\n\t\tbreak;\n\tcase BEAM_ENTS:\n\t\tbeam.type = TE_BEAMPOINTS;\n\t\tSetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );\n\t\tbeam.startEntity = ent->curstate.sequence;\n\t\tbeam.endEntity = ent->curstate.skin;\n\t\tbreak;\n\tcase BEAM_HOSE:\n\t\tbeam.type = TE_BEAMHOSE;\n\t\tbreak;\n\tcase BEAM_POINTS:\n\t\t// already set up\n\t\tbreak;\n\t}\n\n\tbeamFlags = ( ent->curstate.rendermode & 0xF0 );\n\n\tif( FBitSet( beamFlags, BEAM_FSINE ))\n\t\tSetBits( beam.flags, FBEAM_SINENOISE );\n\n\tif( FBitSet( beamFlags, BEAM_FSOLID ))\n\t\tSetBits( beam.flags, FBEAM_SOLID );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEIN ))\n\t\tSetBits( beam.flags, FBEAM_SHADEIN );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEOUT ))\n\t\tSetBits( beam.flags, FBEAM_SHADEOUT );\n\n\t// draw it\n\tR_BeamDraw( &beam, tr.frametime );\n}\n\n\n/*\n==============\nCL_DrawBeams\n\ndraw beam loop\n==============\n*/\nvoid GAME_EXPORT CL_DrawBeams( int fTrans, BEAM *active_beams )\n{\n\tBEAM *pBeam;\n\tint  i, flags;\n\n\t// pglShadeModel( GL_SMOOTH );\n\t// pglDepthMask( fTrans ? GL_FALSE : GL_TRUE );\n\n\t// server beams don't allocate beam chains\n\t// all params are stored in cl_entity_t\n\tfor( i = 0; i < tr.draw_list->num_beam_entities; i++ )\n\t{\n\t\tRI.currentbeam = tr.draw_list->beam_entities[i];\n\t\tflags = RI.currentbeam->curstate.rendermode & 0xF0;\n\n\t\tif( fTrans && FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDrawCustomEntity( RI.currentbeam );\n\t\tr_stats.c_view_beams_count++;\n\t}\n\n\tRI.currentbeam = NULL;\n\n\t// draw temporary entity beams\n\tfor( pBeam = active_beams; pBeam; pBeam = pBeam->next )\n\t{\n\t\tif( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDraw( pBeam, gp_cl->time - gp_cl->oldtime );\n\t}\n\n\t// pglShadeModel( GL_FLAT );\n\t// pglDepthMask( GL_TRUE );\n}\n"
  },
  {
    "path": "ref/soft/r_bsp.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_bsp.c\n\n#include \"r_local.h\"\n\n//\n// current entity info\n//\nvec3_t r_entorigin; // the currently rendering entity in world\n// coordinates\n\nfloat  entity_rotation[3][3];\n\nint    r_currentbkey;\n\ntypedef enum {touchessolid, drawnode, nodrawnode} solidstate_t;\n\n#define MAX_BMODEL_VERTS 1000                   // 12K\n#define MAX_BMODEL_EDGES 2000                   // 24K\n\nstatic mvertex_t *pbverts;\nstatic bedge_t   *pbedges;\nstatic int       numbverts, numbedges;\n\nstatic mvertex_t *pfrontenter, *pfrontexit;\n\nstatic qboolean  makeclippededge;\n\n\n\n/*\n================\nR_ConcatRotations\n================\n*/\nstatic void R_ConcatRotations( float in1[3][3], float in2[3][3], float out[3][3] )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0]\n\t\t    + in1[0][2] * in2[2][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1]\n\t\t    + in1[0][2] * in2[2][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2]\n\t\t    + in1[0][2] * in2[2][2];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0]\n\t\t    + in1[1][2] * in2[2][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1]\n\t\t    + in1[1][2] * in2[2][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2]\n\t\t    + in1[1][2] * in2[2][2];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0]\n\t\t    + in1[2][2] * in2[2][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1]\n\t\t    + in1[2][2] * in2[2][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2]\n\t\t    + in1[2][2] * in2[2][2];\n}\n\n\n// ===========================================================================\n\n/*\n================\nR_EntityRotate\n================\n*/\nstatic void R_EntityRotate( vec3_t vec )\n{\n\tvec3_t tvec;\n\n\tVectorCopy( vec, tvec );\n\tvec[0] = DotProduct( entity_rotation[0], tvec );\n\tvec[1] = DotProduct( entity_rotation[1], tvec );\n\tvec[2] = DotProduct( entity_rotation[2], tvec );\n}\n\n\n/*\n================\nR_RotateBmodel\n================\n*/\nvoid R_RotateBmodel( void )\n{\n\tfloat angle, s, c, temp1[3][3], temp2[3][3], temp3[3][3];\n\n// TODO: should use a look-up table\n// TODO: should really be stored with the entity instead of being reconstructed\n// TODO: could cache lazily, stored in the entity\n// TODO: share work with R_SetUpAliasTransform\n\n// yaw\n\tangle = RI.currententity->angles[YAW];\n\tangle = angle * M_PI_F * 2 / 360.0f;\n\ts = sin( angle );\n\tc = cos( angle );\n\n\ttemp1[0][0] = c;\n\ttemp1[0][1] = s;\n\ttemp1[0][2] = 0;\n\ttemp1[1][0] = -s;\n\ttemp1[1][1] = c;\n\ttemp1[1][2] = 0;\n\ttemp1[2][0] = 0;\n\ttemp1[2][1] = 0;\n\ttemp1[2][2] = 1;\n\n\n// pitch\n\tangle = RI.currententity->angles[PITCH];\n\tangle = angle * M_PI_F * 2 / 360.0f;\n\ts = sin( angle );\n\tc = cos( angle );\n\n\ttemp2[0][0] = c;\n\ttemp2[0][1] = 0;\n\ttemp2[0][2] = -s;\n\ttemp2[1][0] = 0;\n\ttemp2[1][1] = 1;\n\ttemp2[1][2] = 0;\n\ttemp2[2][0] = s;\n\ttemp2[2][1] = 0;\n\ttemp2[2][2] = c;\n\n\tR_ConcatRotations( temp2, temp1, temp3 );\n\n// roll\n\tangle = RI.currententity->angles[ROLL];\n\tangle = angle * M_PI_F * 2 / 360.0f;\n\ts = sin( angle );\n\tc = cos( angle );\n\n\ttemp1[0][0] = 1;\n\ttemp1[0][1] = 0;\n\ttemp1[0][2] = 0;\n\ttemp1[1][0] = 0;\n\ttemp1[1][1] = c;\n\ttemp1[1][2] = s;\n\ttemp1[2][0] = 0;\n\ttemp1[2][1] = -s;\n\ttemp1[2][2] = c;\n\n\tR_ConcatRotations( temp1, temp3, entity_rotation );\n\n//\n// rotate modelorg and the transformation matrix\n//\n\tR_EntityRotate( tr.modelorg );\n\tR_EntityRotate( RI.vforward );\n\tR_EntityRotate( RI.vright );\n\tR_EntityRotate( RI.vup );\n\n\tR_TransformFrustum();\n}\n\n/*\n================\nR_RecursiveClipBPoly\n================\n*/\nstatic void R_RecursiveClipBPoly( model_t *mod, bedge_t *pedges, mnode_t *pnode, msurface_t *psurf )\n{\n\tbedge_t   *psideedges[2], *pnextedge, *ptedge;\n\tint       i, side, lastside;\n\tfloat     dist, frac, lastdist;\n\tmplane_t  *splitplane, tplane;\n\tmvertex_t *pvert, *plastvert, *ptvert;\n\tmnode_t   *pn;\n\n\tpsideedges[0] = psideedges[1] = NULL;\n\n\tmakeclippededge = false;\n\n// transform the BSP plane into model space\n// FIXME: cache these?\n\tsplitplane = pnode->plane;\n\ttplane.dist = splitplane->dist\n\t\t      - DotProduct( r_entorigin, splitplane->normal );\n\ttplane.normal[0] = DotProduct( entity_rotation[0], splitplane->normal );\n\ttplane.normal[1] = DotProduct( entity_rotation[1], splitplane->normal );\n\ttplane.normal[2] = DotProduct( entity_rotation[2], splitplane->normal );\n\n// clip edges to BSP plane\n\tfor( ; pedges; pedges = pnextedge )\n\t{\n\t\tpnextedge = pedges->pnext;\n\n\t\t// set the status for the last point as the previous point\n\t\t// FIXME: cache this stuff somehow?\n\t\tplastvert = pedges->v[0];\n\t\tlastdist = DotProduct( plastvert->position, tplane.normal )\n\t\t\t   - tplane.dist;\n\n\t\tif( lastdist > 0 )\n\t\t\tlastside = 0;\n\t\telse\n\t\t\tlastside = 1;\n\n\t\tpvert = pedges->v[1];\n\n\t\tdist = DotProduct( pvert->position, tplane.normal ) - tplane.dist;\n\n\t\tif( dist > 0 )\n\t\t\tside = 0;\n\t\telse\n\t\t\tside = 1;\n\n\t\tif( side != lastside )\n\t\t{\n\t\t\t// clipped\n\t\t\tif( numbverts >= MAX_BMODEL_VERTS )\n\t\t\t\treturn;\n\n\t\t\t// generate the clipped vertex\n\t\t\tfrac = lastdist / ( lastdist - dist );\n\t\t\tptvert = &pbverts[numbverts++];\n\t\t\tptvert->position[0] = plastvert->position[0]\n\t\t\t\t\t      + frac * ( pvert->position[0]\n\t\t\t\t\t\t\t - plastvert->position[0] );\n\t\t\tptvert->position[1] = plastvert->position[1]\n\t\t\t\t\t      + frac * ( pvert->position[1]\n\t\t\t\t\t\t\t - plastvert->position[1] );\n\t\t\tptvert->position[2] = plastvert->position[2]\n\t\t\t\t\t      + frac * ( pvert->position[2]\n\t\t\t\t\t\t\t - plastvert->position[2] );\n\n\t\t\t// split into two edges, one on each side, and remember entering\n\t\t\t// and exiting points\n\t\t\t// FIXME: share the clip edge by having a winding direction flag?\n\t\t\tif( numbedges >= ( MAX_BMODEL_EDGES - 1 ))\n\t\t\t{\n\t\t\t\t// gEngfuncs.Con_Printf (\"Out of edges for bmodel\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tptedge = &pbedges[numbedges];\n\t\t\tptedge->pnext = psideedges[lastside];\n\t\t\tpsideedges[lastside] = ptedge;\n\t\t\tptedge->v[0] = plastvert;\n\t\t\tptedge->v[1] = ptvert;\n\n\t\t\tptedge = &pbedges[numbedges + 1];\n\t\t\tptedge->pnext = psideedges[side];\n\t\t\tpsideedges[side] = ptedge;\n\t\t\tptedge->v[0] = ptvert;\n\t\t\tptedge->v[1] = pvert;\n\n\t\t\tnumbedges += 2;\n\n\t\t\tif( side == 0 )\n\t\t\t{\n\t\t\t\t// entering for front, exiting for back\n\t\t\t\tpfrontenter = ptvert;\n\t\t\t\tmakeclippededge = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpfrontexit = ptvert;\n\t\t\t\tmakeclippededge = true;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// add the edge to the appropriate side\n\t\t\tpedges->pnext = psideedges[side];\n\t\t\tpsideedges[side] = pedges;\n\t\t}\n\t}\n\n// if anything was clipped, reconstitute and add the edges along the clip\n// plane to both sides (but in opposite directions)\n\tif( makeclippededge )\n\t{\n\t\tif( numbedges >= ( MAX_BMODEL_EDGES - 2 ))\n\t\t{\n\t\t\t// gEngfuncs.Con_Printf (\"Out of edges for bmodel\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tptedge = &pbedges[numbedges];\n\t\tptedge->pnext = psideedges[0];\n\t\tpsideedges[0] = ptedge;\n\t\tptedge->v[0] = pfrontexit;\n\t\tptedge->v[1] = pfrontenter;\n\n\t\tptedge = &pbedges[numbedges + 1];\n\t\tptedge->pnext = psideedges[1];\n\t\tpsideedges[1] = ptedge;\n\t\tptedge->v[0] = pfrontenter;\n\t\tptedge->v[1] = pfrontexit;\n\n\t\tnumbedges += 2;\n\t}\n\n// draw or recurse further\n\tfor( i = 0; i < 2; i++ )\n\t{\n\t\tif( psideedges[i] )\n\t\t{\n\t\t\t// draw if we've reached a non-solid leaf, done if all that's left is a\n\t\t\t// solid leaf, and continue down the tree if it's not a leaf\n\t\t\tpn = node_child( pnode, i, mod );\n\n\t\t\t// we're done with this branch if the node or leaf isn't in the PVS\n\t\t\tif( pn->visframe == tr.visframecount )\n\t\t\t{\n\t\t\t\tif( pn->contents < 0 )\n\t\t\t\t{\n\t\t\t\t\tif( pn->contents != CONTENTS_SOLID )\n\t\t\t\t\t{\n\t\t\t\t\t\t// r_currentbkey = ((mleaf_t *)pn)->cluster;\n\t\t\t\t\t\tr_currentbkey = LEAF_KEY(((mleaf_t *)pn ));\n\t\t\t\t\t\tR_RenderBmodelFace( psideedges[i], psurf );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tR_RecursiveClipBPoly( mod, psideedges[i], pn, psurf );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nR_DrawSolidClippedSubmodelPolygons\n\nBmodel crosses multiple leafs\n================\n*/\nvoid R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode )\n{\n\tint        i, j, lindex;\n\tvec_t      dot;\n\tmsurface_t *psurf;\n\tint        numsurfaces;\n\tmplane_t   *pplane;\n\tmvertex_t  bverts[MAX_BMODEL_VERTS];\n\tbedge_t    bedges[MAX_BMODEL_EDGES], *pbedge;\n\tmedge16_t  *pedge, *pedges;\n\n// FIXME: use bounding-box-based frustum clipping info?\n\n\tpsurf = &pmodel->surfaces[pmodel->firstmodelsurface];\n\tnumsurfaces = pmodel->nummodelsurfaces;\n\tpedges = pmodel->edges16;\n\n\tfor( i = 0; i < numsurfaces; i++, psurf++ )\n\t{\n\t\tif( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\t{\n\t\t\tif( psurf->plane->type != PLANE_Z && !FBitSet( RI.currententity->curstate.effects, EF_WATERSIDES ))\n\t\t\t\tcontinue;\n\t\t\tif( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist )\n\t\t\t\tcontinue;\n\t\t}\n\t\t// find which side of the node we are on\n\t\tpplane = psurf->plane;\n\n\t\tdot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist;\n\n\t\t// draw the polygon\n\t\tif(( !( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON ))\n\t\t   || (( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON )))\n\t\t\tcontinue;\n\n\t\t// FIXME: use bounding-box-based frustum clipping info?\n\n\t\t// copy the edges to bedges, flipping if necessary so always\n\t\t// clockwise winding\n\t\t// FIXME: if edges and vertices get caches, these assignments must move\n\t\t// outside the loop, and overflow checking must be done here\n\t\tpbverts = bverts;\n\t\tpbedges = bedges;\n\t\tnumbverts = numbedges = 0;\n\t\tpbedge = &bedges[numbedges];\n\t\tnumbedges += psurf->numedges;\n\n\t\tfor( j = 0; j < psurf->numedges; j++ )\n\t\t{\n\t\t\tlindex = pmodel->surfedges[psurf->firstedge + j];\n\n\t\t\tif( lindex > 0 )\n\t\t\t{\n\t\t\t\tpedge = &pedges[lindex];\n\t\t\t\tpbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]];\n\t\t\t\tpbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[1]];\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlindex = -lindex;\n\t\t\t\tpedge = &pedges[lindex];\n\t\t\t\tpbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[1]];\n\t\t\t\tpbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]];\n\t\t\t}\n\n\t\t\tpbedge[j].pnext = &pbedge[j + 1];\n\t\t}\n\n\t\tpbedge[j - 1].pnext = NULL; // mark end of edges\n\n\t\t// if ( !( psurf->texinfo->flags & ( SURF_TRANS66 | SURF_TRANS33 ) ) )\n\t\tR_RecursiveClipBPoly( pmodel, pbedge, topnode, psurf );\n\t\t// else\n\t\t//\tR_RenderBmodelFace( pbedge, psurf );\n\t}\n}\n\n\n/*\n================\nR_DrawSubmodelPolygons\n\nAll in one leaf\n================\n*/\nvoid R_DrawSubmodelPolygons( model_t *pmodel, int clipflags, mnode_t *topnode )\n{\n\tint        i;\n\tvec_t      dot;\n\tmsurface_t *psurf;\n\tint        numsurfaces;\n\tmplane_t   *pplane;\n\n// FIXME: use bounding-box-based frustum clipping info?\n\n\tpsurf = &pmodel->surfaces[pmodel->firstmodelsurface];\n\tnumsurfaces = pmodel->nummodelsurfaces;\n\n\tfor( i = 0; i < numsurfaces; i++, psurf++ )\n\t{\n\t\tif( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\t{\n\t\t\tif( psurf->plane->type != PLANE_Z && !FBitSet( RI.currententity->curstate.effects, EF_WATERSIDES ))\n\t\t\t\tcontinue;\n\t\t\tif( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist )\n\t\t\t\tcontinue;\n\t\t}\n\t\t// find which side of the node we are on\n\t\tpplane = psurf->plane;\n\n\t\tdot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist;\n\n\t\t// draw the polygon\n\t\tif((( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON ))\n\t\t   || ( !( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON )))\n\t\t{\n\t\t\tr_currentkey = LEAF_KEY(((mleaf_t *)topnode ));\n\n\t\t\t// FIXME: use bounding-box-based frustum clipping info?\n\t\t\tR_RenderFace( psurf, clipflags );\n\t\t}\n\t}\n}\n\n#if XASH_LOW_MEMORY\nunsigned short r_leafkeys[MAX_MAP_LEAFS];\n#else\nint r_leafkeys[MAX_MAP_LEAFS];\n#endif\n/*\n================\nR_RecursiveWorldNode\n================\n*/\nstatic void R_RecursiveWorldNode( mnode_t *node, int clipflags )\n{\n\tint        i, c, side, *pindex;\n\tvec3_t     acceptpt, rejectpt;\n\tmplane_t   *plane;\n\tmsurface_t *surf, **mark;\n\tmleaf_t    *pleaf;\n\tdouble     d, dot;\n\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn; // solid\n\n\tif( node->visframe != tr.visframecount )\n\t\treturn;\n\n// cull the clipping planes if not trivial accept\n// FIXME: the compiler is doing a lousy job of optimizing here; it could be\n//  twice as fast in ASM\n\tif( clipflags )\n\t{\n\t\tfor( i = 0; i < 4; i++ )\n\t\t{\n\t\t\tif( !( clipflags & ( 1 << i )))\n\t\t\t\tcontinue; // don't need to clip against it\n\n\t\t\t// generate accept and reject points\n\t\t\t// FIXME: do with fast look-ups or integer tests based on the sign bit\n\t\t\t// of the floating point values\n\n\t\t\tpindex = qfrustum.pfrustum_indexes[i];\n\n\t\t\trejectpt[0] = (float)node->minmaxs[pindex[0]];\n\t\t\trejectpt[1] = (float)node->minmaxs[pindex[1]];\n\t\t\trejectpt[2] = (float)node->minmaxs[pindex[2]];\n\n\t\t\td = DotProduct( rejectpt, qfrustum.view_clipplanes[i].normal );\n\t\t\td -= qfrustum.view_clipplanes[i].dist;\n\n\t\t\tif( d <= 0 )\n\t\t\t\treturn;\n\n\t\t\tacceptpt[0] = (float)node->minmaxs[pindex[3 + 0]];\n\t\t\tacceptpt[1] = (float)node->minmaxs[pindex[3 + 1]];\n\t\t\tacceptpt[2] = (float)node->minmaxs[pindex[3 + 2]];\n\n\t\t\td = DotProduct( acceptpt, qfrustum.view_clipplanes[i].normal );\n\t\t\td -= qfrustum.view_clipplanes[i].dist;\n\n\t\t\tif( d >= 0 )\n\t\t\t\tclipflags &= ~( 1 << i ); // node is entirely on screen\n\t\t}\n\t}\n\n// if a leaf node, draw stuff\n\tif( node->contents < 0 )\n\t{\n\t\tpleaf = (mleaf_t *)node;\n\n\t\tmark = pleaf->firstmarksurface;\n\t\tc = pleaf->nummarksurfaces;\n\n\t\tif( c )\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\t( *mark )->visframe = tr.framecount;\n\t\t\t\tmark++;\n\t\t\t}\n\t\t\twhile( --c );\n\t\t}\n\n\t\t// deal with model fragments in this leaf\n\t\tif( pleaf->efrags )\n\t\t{\n\t\t\tgEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );\n\t\t}\n\n\n\t\t//\tpleaf->cluster\n\t\tLEAF_KEY( pleaf ) = r_currentkey;\n\t\tr_currentkey++; // all bmodels in a leaf share the same key\n\t}\n\telse\n\t{\n\t\tmnode_t    *children[2];\n\t\tint firstsurface;\n\n\t\t// node is just a decision point, so go down the apropriate sides\n\n\t\t// find which side of the node we are on\n\t\tplane = node->plane;\n\n\t\tswitch( plane->type )\n\t\t{\n\t\tcase PLANE_X:\n\t\t\tdot = tr.modelorg[0] - plane->dist;\n\t\t\tbreak;\n\t\tcase PLANE_Y:\n\t\t\tdot = tr.modelorg[1] - plane->dist;\n\t\t\tbreak;\n\t\tcase PLANE_Z:\n\t\t\tdot = tr.modelorg[2] - plane->dist;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tdot = DotProduct( tr.modelorg, plane->normal ) - plane->dist;\n\t\t\tbreak;\n\t\t}\n\n\t\tif( dot >= 0 )\n\t\t\tside = 0;\n\t\telse\n\t\t\tside = 1;\n\n\t\t// recurse down the children, front side first\n\t\tnode_children( children, node, WORLDMODEL );\n\t\tR_RecursiveWorldNode( children[side], clipflags );\n\n\t\t// draw stuff\n\t\tc = node_numsurfaces( node, WORLDMODEL );\n\t\tfirstsurface = node_firstsurface( node, WORLDMODEL );\n\n\t\tif( c )\n\t\t{\n\t\t\tsurf = WORLDMODEL->surfaces + firstsurface;\n\n\t\t\tif( dot < -BACKFACE_EPSILON )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tif(( surf->flags & SURF_PLANEBACK )\n\t\t\t\t\t   && ( surf->visframe == tr.framecount ))\n\t\t\t\t\t{\n\t\t\t\t\t\tR_RenderFace( surf, clipflags );\n\t\t\t\t\t}\n\n\t\t\t\t\tsurf++;\n\t\t\t\t}\n\t\t\t\twhile( --c );\n\t\t\t}\n\t\t\telse if( dot > BACKFACE_EPSILON )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tif( !( surf->flags & SURF_PLANEBACK )\n\t\t\t\t\t    && ( surf->visframe == tr.framecount ))\n\t\t\t\t\t{\n\t\t\t\t\t\tR_RenderFace( surf, clipflags );\n\t\t\t\t\t}\n\n\t\t\t\t\tsurf++;\n\t\t\t\t}\n\t\t\t\twhile( --c );\n\t\t\t}\n\n\t\t\t// all surfaces on the same node share the same sequence number\n\t\t\tr_currentkey++;\n\t\t}\n\n\t\t// recurse down the back side\n\t\tR_RecursiveWorldNode( children[!side], clipflags );\n\t}\n}\n\n/*\n================\nR_RenderWorld\n================\n*/\nvoid R_RenderWorld( void )\n{\n\tif( !RI.drawWorld )\n\t\treturn;\n\n\t// auto cycle the world frame for texture animation\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\t// RI.currententity->frame = (int)(gp_cl->time*2);\n\n\tVectorCopy( RI.vieworg, tr.modelorg );\n\tRI.currentmodel = WORLDMODEL;\n\tr_pcurrentvertbase = RI.currentmodel->vertexes;\n\n\tR_RecursiveWorldNode( RI.currentmodel->nodes, 15 );\n}\n"
  },
  {
    "path": "ref/soft/r_context.c",
    "content": "/*\nvid_sdl.c - SDL vid component\nCopyright (C) 2018 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n\nref_api_t     gEngfuncs;\nref_globals_t *gpGlobals;\nref_client_t  *gp_cl;\nref_host_t    *gp_host;\ngl_globals_t  tr;\nref_speeds_t  r_stats;\npoolhandle_t  r_temppool;\nviddef_t      vid;\n\nvoid _Mem_Free( void *data, const char *filename, int fileline )\n{\n\tgEngfuncs._Mem_Free( data, filename, fileline );\n}\n\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\n{\n\treturn gEngfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline );\n}\n\nstatic void GAME_EXPORT R_ClearScreen( void )\n{\n\n}\n\nstatic const byte * GAME_EXPORT R_GetTextureOriginalBuffer( unsigned int idx )\n{\n\timage_t *glt = R_GetTexture( idx );\n\n\tif( !glt || !glt->original || !glt->original->buffer )\n\t\treturn NULL;\n\n\treturn glt->original->buffer;\n}\n\n/*\n=============\nCL_FillRGBA\n\n=============\n*/\nstatic void GAME_EXPORT CL_FillRGBA( int rendermode, float _x, float _y, float _w, float _h, byte r, byte g, byte b, byte a )\n{\n\tvid.rendermode = rendermode;\n\t_TriColor4ub( r, g, b, a );\n\tDraw_Fill( _x, _y, _w, _h );\n}\n\nvoid Mod_UnloadTextures( model_t *mod );\n\nstatic qboolean GAME_EXPORT Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf )\n{\n\tqboolean loaded = false;\n\n\tif( !create )\n\t{\n\t\tif( gEngfuncs.drawFuncs->Mod_ProcessUserData )\n\t\t\tgEngfuncs.drawFuncs->Mod_ProcessUserData( mod, false, buf );\n\t\tMod_UnloadTextures( mod );\n\t\treturn true;\n\t}\n\n\tswitch( mod->type )\n\t{\n\tcase mod_studio:\n\tcase mod_brush:\n\tcase mod_alias:\n\t\tloaded = true;\n\t\tbreak;\n\tcase mod_sprite:\n\t\tMod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo );\n\t\tbreak;\n\tdefault:\n\t\tgEngfuncs.Host_Error( \"%s: unsupported type %d\\n\", __func__, mod->type );\n\t\treturn false;\n\t}\n\n\tif( gEngfuncs.drawFuncs->Mod_ProcessUserData )\n\t\tgEngfuncs.drawFuncs->Mod_ProcessUserData( mod, true, buf );\n\n\treturn loaded;\n}\n\nstatic int GL_RefGetParm( int parm, int arg )\n{\n\timage_t *glt;\n\n\tswitch( parm )\n\t{\n\tcase PARM_TEX_WIDTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->width;\n\tcase PARM_TEX_HEIGHT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->height;\n\tcase PARM_TEX_SRC_WIDTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->srcWidth;\n\tcase PARM_TEX_SRC_HEIGHT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->srcHeight;\n\tcase PARM_TEX_GLFORMAT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn 0; // glt->format;\n\tcase PARM_TEX_ENCODE:\n\t\tglt = R_GetTexture( arg );\n\t\treturn 0; // glt->encode;\n\tcase PARM_TEX_MIPCOUNT:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->numMips;\n\tcase PARM_TEX_DEPTH:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->depth;\n\tcase PARM_TEX_SKYBOX:\n\t\tAssert( arg >= 0 && arg < 6 );\n\t\treturn tr.skyboxTextures[arg];\n\tcase PARM_TEX_SKYTEXNUM:\n\t\treturn 0; // tr.skytexturenum;\n\tcase PARM_TEX_LIGHTMAP:\n\t\targ = bound( 0, arg, MAX_LIGHTMAPS - 1 );\n\t\treturn tr.lightmapTextures[arg];\n\tcase PARM_TEX_TARGET:\n\t\tglt = R_GetTexture( arg );\n\t\treturn 0; // glt->target;\n\tcase PARM_TEX_TEXNUM:\n\t\tglt = R_GetTexture( arg );\n\t\treturn 0; // glt->texnum;\n\tcase PARM_TEX_FLAGS:\n\t\tglt = R_GetTexture( arg );\n\t\treturn glt->flags;\n\tcase PARM_TEX_MEMORY:\n\t\treturn R_TexMemory();\n\tcase PARM_ACTIVE_TMU:\n\t\treturn 0; // glState.activeTMU;\n\tcase PARM_LIGHTSTYLEVALUE:\n\t\targ = bound( 0, arg, MAX_LIGHTSTYLES - 1 );\n\t\treturn tr.lightstylevalue[arg];\n\tcase PARM_MAX_IMAGE_UNITS:\n\t\treturn 0; // GL_MaxTextureUnits();\n\tcase PARM_REBUILD_GAMMA:\n\t\treturn 0;\n\tcase PARM_GL_CONTEXT_TYPE:\n\t\treturn 0; // glConfig.context;\n\tcase PARM_GLES_WRAPPER:\n\t\treturn 0; // glConfig.wrapper;\n\tcase PARM_STENCIL_ACTIVE:\n\t\treturn 0; // glState.stencilEnabled;\n\tcase PARM_SKY_SPHERE:\n\t\treturn 0; // ref_soft doesn't support sky sphere\n\tcase PARM_TEX_FILTERING:\n\t\treturn 0; // ref_soft doesn't do filtering in general\n\tdefault:\n\t\treturn ENGINE_GET_PARM_( parm, arg );\n\t}\n\treturn 0;\n}\n\nstatic void GAME_EXPORT R_GetDetailScaleForTexture( int texture, float *xScale, float *yScale )\n{\n\timage_t *glt = R_GetTexture( texture );\n\n\tif( xScale )\n\t\t*xScale = glt->xscale;\n\tif( yScale )\n\t\t*yScale = glt->yscale;\n}\n\nstatic void GAME_EXPORT R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density )\n{\n\timage_t *glt = R_GetTexture( texture );\n\n\tif( red )\n\t\t*red = glt->fogParams[0];\n\tif( green )\n\t\t*green = glt->fogParams[1];\n\tif( blue )\n\t\t*blue = glt->fogParams[2];\n\tif( density )\n\t\t*density = glt->fogParams[3];\n}\n\n\nstatic void GAME_EXPORT R_SetCurrentEntity( cl_entity_t *ent )\n{\n\tRI.currententity = ent;\n\n\t// set model also\n\tif( RI.currententity != NULL )\n\t{\n\t\tRI.currentmodel = RI.currententity->model;\n\t}\n}\n\nstatic void GAME_EXPORT R_SetCurrentModel( model_t *mod )\n{\n\tRI.currentmodel = mod;\n}\n\nstatic float GAME_EXPORT R_GetFrameTime( void )\n{\n\treturn tr.frametime;\n}\n\nstatic const char * GAME_EXPORT GL_TextureName( unsigned int texnum )\n{\n\treturn R_GetTexture( texnum )->name;\n}\n\nstatic const byte * GAME_EXPORT GL_TextureData( unsigned int texnum )\n{\n\trgbdata_t *pic = R_GetTexture( texnum )->original;\n\n\tif( pic != NULL )\n\t\treturn pic->buffer;\n\treturn NULL;\n}\n\nstatic void Mod_BrushUnloadTextures( model_t *mod )\n{\n\tint i;\n\n\n\tgEngfuncs.Con_Printf( \"Unloading world\\n\" );\n\ttr.map_unload = true;\n\n\tfor( i = 0; i < mod->numtextures; i++ )\n\t{\n\t\ttexture_t *tx = mod->textures[i];\n\t\tif( !tx || tx->gl_texturenum == tr.defaultTexture )\n\t\t\tcontinue; // free slot\n\n\t\tGL_FreeTexture( tx->gl_texturenum ); // main texture\n\t\tGL_FreeTexture( tx->fb_texturenum ); // luma texture\n\t}\n}\n\nvoid Mod_UnloadTextures( model_t *mod )\n{\n\tint i, j;\n\n\tAssert( mod != NULL );\n\n\tswitch( mod->type )\n\t{\n\tcase mod_studio:\n\t\t// Mod_StudioUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_alias:\n\t\t// Mod_AliasUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_brush:\n\t\tMod_BrushUnloadTextures( mod );\n\t\tbreak;\n\tcase mod_sprite:\n\t\tMod_SpriteUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tdefault: gEngfuncs.Host_Error( \"%s: unsupported type %d\\n\", __func__, mod->type );\n\t}\n}\n\nstatic void GAME_EXPORT R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities )\n{\n\ttr.entities = entities;\n\ttr.max_entities = max_entities;\n}\n\nstatic void GAME_EXPORT R_Flush( unsigned int flags )\n{\n\t// stub\n}\n\n// stubs\n\nstatic void GAME_EXPORT GL_SetTexCoordArrayMode( uint mode )\n{\n\n}\n\nstatic void GAME_EXPORT GL_BackendStartFrame( void )\n{\n\n}\n\nstatic void GAME_EXPORT GL_BackendEndFrame( void )\n{\n\n}\n\n\nvoid GAME_EXPORT GL_SetRenderMode( int mode )\n{\n\tvid.rendermode = mode;\n\t/// TODO: table shading/blending???\n\t/// maybe, setup block drawing function pointers here\n}\n\nstatic void GAME_EXPORT R_ShowTextures( void )\n{\n\t// textures undone too\n}\n\nstatic void GAME_EXPORT R_SetupSky( int *skyboxTextures )\n{\n\tint i;\n\n\t// TODO: R_UnloadSkybox();\n\tif( !skyboxTextures )\n\t\treturn;\n\n\tfor( i = 0; i < SKYBOX_MAX_SIDES; i++ )\n\t\ttr.skyboxTextures[i] = skyboxTextures[i];\n}\n\nqboolean GAME_EXPORT VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot )\n{\n\t// cubemaps? in my softrender???\n\treturn false;\n}\n\nstatic void GAME_EXPORT R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture )\n{\n\ttr.solidskyTexture = solidskyTexture;\n\ttr.alphaskyTexture = alphaskyTexture;\n}\n\nstatic void GAME_EXPORT GL_SubdivideSurface( model_t *mod, msurface_t *fa )\n{\n\n}\n\nstatic void GAME_EXPORT DrawSingleDecal( decal_t *pDecal, msurface_t *fa )\n{\n\n}\n\nstatic void GAME_EXPORT GL_SelectTexture( int texture )\n{\n\n}\n\nstatic void GAME_EXPORT GL_LoadTexMatrixExt( const float *glmatrix )\n{\n\n}\n\nstatic void GAME_EXPORT GL_LoadIdentityTexMatrix( void )\n{\n\n}\n\nstatic void GAME_EXPORT GL_CleanUpTextureUnits( int last )\n{\n\n}\n\nstatic void GAME_EXPORT GL_TexGen( unsigned int coord, unsigned int mode )\n{\n\n}\n\nstatic void GAME_EXPORT GL_TextureTarget( uint target )\n{\n\n}\n\nvoid GAME_EXPORT Mod_SetOrthoBounds( const float *mins, const float *maxs )\n{\n\n}\n\nqboolean GAME_EXPORT R_SpeedsMessage( char *out, size_t size )\n{\n\treturn false;\n}\n\nbyte *GAME_EXPORT Mod_GetCurrentVis( void )\n{\n\treturn NULL;\n}\n\nstatic void GAME_EXPORT VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )\n{\n}\n\nstatic void GAME_EXPORT VGUI_SetupDrawing( qboolean rect )\n{\n}\n\nstatic void GAME_EXPORT R_OverrideTextureSourceSize( unsigned int texnum, uint srcWidth, uint srcHeight )\n{\n\timage_t *tx = R_GetTexture( texnum );\n\n\ttx->srcWidth = srcWidth;\n\ttx->srcHeight = srcHeight;\n}\n\nstatic const char *R_GetConfigName( void )\n{\n\treturn \"ref_soft\"; // software specific cvars will go to ref_soft.cfg\n}\n\nstatic void * GAME_EXPORT R_GetProcAddress( const char *name )\n{\n\treturn gEngfuncs.GL_GetProcAddress( name );\n}\n\nstatic const ref_interface_t gReffuncs =\n{\n\tR_Init,\n\tR_Shutdown,\n\tR_GetConfigName,\n\tR_SetDisplayTransform,\n\n\tGL_SetupAttributes,\n\tGL_InitExtensions,\n\tGL_ClearExtensions,\n\n\tR_GammaChanged,\n\tR_BeginFrame,\n\tR_RenderScene,\n\tR_EndFrame,\n\tR_PushScene,\n\tR_PopScene,\n\tGL_BackendStartFrame,\n\tGL_BackendEndFrame,\n\n\tR_ClearScreen,\n\tR_AllowFog,\n\tGL_SetRenderMode,\n\n\tR_AddEntity,\n\tCL_AddCustomBeam,\n\tR_ProcessEntData,\n\tR_Flush,\n\n\tR_ShowTextures,\n\n\tR_GetTextureOriginalBuffer,\n\tGL_LoadTextureFromBuffer,\n\tGL_ProcessTexture,\n\tR_SetupSky,\n\n\tR_Set2DMode,\n\tR_DrawStretchRaw,\n\tR_DrawStretchPic,\n\tCL_FillRGBA,\n\tR_WorldToScreen,\n\n\tVID_ScreenShot,\n\tVID_CubemapShot,\n\n\tR_LightPoint,\n\n\tR_DecalShoot,\n\tR_DecalRemoveAll,\n\tR_CreateDecalList,\n\tR_ClearAllDecals,\n\n\tR_StudioEstimateFrame,\n\tR_StudioLerpMovement,\n\tCL_InitStudioAPI,\n\n\tR_SetSkyCloudsTextures,\n\tGL_SubdivideSurface,\n\tCL_RunLightStyles,\n\n\tR_GetSpriteParms,\n\tR_GetSpriteTexture,\n\n\tMod_ProcessRenderData,\n\tMod_StudioLoadTextures,\n\n\tCL_DrawParticles,\n\tCL_DrawTracers,\n\tCL_DrawBeams,\n\tR_BeamCull,\n\n\tGL_RefGetParm,\n\tR_GetDetailScaleForTexture,\n\tR_GetExtraParmsForTexture,\n\tR_GetFrameTime,\n\n\tR_SetCurrentEntity,\n\tR_SetCurrentModel,\n\n\tGL_FindTexture,\n\tGL_TextureName,\n\tGL_TextureData,\n\tGL_LoadTexture,\n\tGL_CreateTexture,\n\tGL_LoadTextureArray,\n\tGL_CreateTextureArray,\n\tGL_FreeTexture,\n\tR_OverrideTextureSourceSize,\n\n\tDrawSingleDecal,\n\tR_DecalSetupVerts,\n\tR_EntityRemoveDecals,\n\n\tR_UploadStretchRaw,\n\n\tGL_Bind,\n\tGL_SelectTexture,\n\tGL_LoadTexMatrixExt,\n\tGL_LoadIdentityTexMatrix,\n\tGL_CleanUpTextureUnits,\n\tGL_TexGen,\n\tGL_TextureTarget,\n\tGL_SetTexCoordArrayMode,\n\tGL_UpdateTexSize,\n\tNULL,\n\tNULL,\n\n\tCL_DrawParticlesExternal,\n\tR_LightVec,\n\tR_StudioGetTexture,\n\n\tR_RenderFrame,\n\tMod_SetOrthoBounds,\n\tR_SpeedsMessage,\n\tMod_GetCurrentVis,\n\tR_NewMap,\n\tR_ClearScene,\n\tR_GetProcAddress,\n\n\tTriRenderMode,\n\tTriBegin,\n\tTriEnd,\n\t_TriColor4f,\n\t_TriColor4ub,\n\tTriTexCoord2f,\n\tTriVertex3fv,\n\tTriVertex3f,\n\tTriFog,\n\tR_ScreenToWorld,\n\tTriGetMatrix,\n\tTriFogParams,\n\tTriCullFace,\n\n\tVGUI_SetupDrawing,\n\tVGUI_UploadTextureBlock,\n};\n\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals );\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals )\n{\n\tif( version != REF_API_VERSION )\n\t\treturn 0;\n\n\t// fill in our callbacks\n\t*funcs = gReffuncs;\n\tgEngfuncs = *engfuncs;\n\tgpGlobals = globals;\n\n\tgp_cl = (ref_client_t *)ENGINE_GET_PARM( PARM_GET_CLIENT_PTR );\n\tgp_host = (ref_host_t *)ENGINE_GET_PARM( PARM_GET_HOST_PTR );\n\n\treturn REF_API_VERSION;\n}\n"
  },
  {
    "path": "ref/soft/r_decals.c",
    "content": "/*\ngl_decals.c - decal paste and rendering\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n\n#define DECAL_OVERLAP_DISTANCE      2\n#define DECAL_DISTANCE              4   // too big values produce more clipped polygons\n#define MAX_DECALCLIPVERT           32  // produced vertexes of fragmented decal\n#define DECAL_CACHEENTRY            256 // MUST BE POWER OF 2 or code below needs to change!\n#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE\n\n// empirically determined constants for minimizing overalpping decals\n#define MAX_OVERLAP_DECALS 6\n#define DECAL_OVERLAP_DIST 8\n#define MIN_DECAL_SCALE    0.01f\n#define MAX_DECAL_SCALE    16.0f\n\n// clip edges\n#define LEFT_EDGE   0\n#define RIGHT_EDGE  1\n#define TOP_EDGE    2\n#define BOTTOM_EDGE 3\n\n// This structure contains the information used to create new decals\ntypedef struct\n{\n\tvec3_t  m_Position;             // world coordinates of the decal center\n\tmodel_t *m_pModel;              // the model the decal is going to be applied in\n\tint     m_iTexture;             // The decal material\n\tint     m_Size;                 // Size of the decal (in world coords)\n\tint     m_Flags;\n\tint     m_Entity;                       // Entity the decal is applied to.\n\tfloat   m_scale;\n\tint     m_decalWidth;\n\tint     m_decalHeight;\n\tvec3_t  m_Basis[3];\n} decalinfo_t;\n\nstatic float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE];\nstatic float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE];\n\ndecal_t      gDecalPool[MAX_RENDER_DECALS];\nstatic int   gDecalCount;\n\nvoid R_ClearDecals( void )\n{\n\tmemset( gDecalPool, 0, sizeof( gDecalPool ));\n\tgDecalCount = 0;\n}\n\n// unlink pdecal from any surface it's attached to\nstatic void R_DecalUnlink( decal_t *pdecal )\n{\n\tdecal_t *tmp;\n\n\tif( pdecal->psurface )\n\t{\n\t\tif( pdecal->psurface->pdecals == pdecal )\n\t\t{\n\t\t\tpdecal->psurface->pdecals = pdecal->pnext;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttmp = pdecal->psurface->pdecals;\n\t\t\tif( !tmp )\n\t\t\t\tgEngfuncs.Host_Error( \"%s: bad decal list\\n\", __func__ );\n\n\t\t\twhile( tmp->pnext )\n\t\t\t{\n\t\t\t\tif( tmp->pnext == pdecal )\n\t\t\t\t{\n\t\t\t\t\ttmp->pnext = pdecal->pnext;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttmp = tmp->pnext;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( pdecal->polys )\n\t\tMem_Free( pdecal->polys );\n\n\tpdecal->psurface = NULL;\n\tpdecal->polys = NULL;\n}\n\n// Just reuse next decal in list\n// A decal that spans multiple surfaces will use multiple decal_t pool entries,\n// as each surface needs it's own.\nstatic decal_t *R_DecalAlloc( decal_t *pdecal )\n{\n\tint limit = MAX_RENDER_DECALS;\n\n\tif( r_decals->value < limit )\n\t\tlimit = r_decals->value;\n\n\tif( !limit )\n\t\treturn NULL;\n\n\tif( !pdecal )\n\t{\n\t\tint count = 0;\n\n\t\t// check for the odd possiblity of infinte loop\n\t\tdo\n\t\t{\n\t\t\tif( gDecalCount >= limit )\n\t\t\t\tgDecalCount = 0;\n\n\t\t\tpdecal = &gDecalPool[gDecalCount]; // reuse next decal\n\t\t\tgDecalCount++;\n\t\t\tcount++;\n\t\t}\n\t\twhile( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit );\n\t}\n\n\t// if decal is already linked to a surface, unlink it.\n\tR_DecalUnlink( pdecal );\n\n\treturn pdecal;\n}\n\n// -----------------------------------------------------------------------------\n// find decal image and grab size from it\n// -----------------------------------------------------------------------------\nstatic void R_GetDecalDimensions( int texture, int *width, int *height )\n{\n\tif( width )\n\t\t*width = 1;     // to avoid divide by zero\n\tif( height )\n\t\t*height = 1;\n\n\tR_GetTextureParms( width, height, texture );\n}\n\n// -----------------------------------------------------------------------------\n// compute the decal basis based on surface normal\n// -----------------------------------------------------------------------------\nvoid R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] )\n{\n\tvec3_t surfaceNormal;\n\n\t// setup normal\n\tif( surf->flags & SURF_PLANEBACK )\n\t\tVectorNegate( surf->plane->normal, surfaceNormal );\n\telse\n\t\tVectorCopy( surf->plane->normal, surfaceNormal );\n\n\tVectorNormalize2( surfaceNormal, textureSpaceBasis[2] );\n\tVectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] );\n\tVectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] );\n}\n\nstatic void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tint width, height;\n\n\t// Compute the non-scaled decal basis\n\tR_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis );\n\tR_GetDecalDimensions( texture, &width, &height );\n\n\t// world width of decal = ptexture->width / pDecal->scale\n\t// world height of decal = ptexture->height / pDecal->scale\n\t// scale is inverse, scales world space to decal u/v space [0,1]\n\t// OPTIMIZE: Get rid of these divides\n\tdecalWorldScale[0] = (float)pDecal->scale / width;\n\tdecalWorldScale[1] = (float)pDecal->scale / height;\n\n\tVectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] );\n\tVectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] );\n}\n\n// Build the initial list of vertices from the surface verts into the global array, 'verts'.\nstatic void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts )\n{\n\tfloat *v;\n\tint   i;\n\n\tif( !surf->polys )\n\t\treturn;\n\tfor( i = 0, v = surf->polys->verts[0]; i < surf->polys->numverts; i++, v += VERTEXSIZE, verts += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, verts ); // copy model space coordinates\n\t\tverts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f;\n\t\tverts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f;\n\t\tverts[5] = verts[6] = 0.0f;\n\t}\n}\n\n// Figure out where the decal maps onto the surface.\nstatic void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// Generate texture coordinates for each vertex in decal s,t space\n\t// probably should pre-generate this, store it and use it for decal-decal collisions\n\t// as in R_DecalsIntersect()\n\tpDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] );\n\tpDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] );\n}\n\n// Quick and dirty sutherland Hodgman clipper\n// Clip polygon to decal in texture space\n// JAY: This code is lame, change it later.  It does way too much work per frame\n// It can be made to recursively call the clipping code and only copy the vertex list once\nstatic int R_ClipInside( float *vert, int edge )\n{\n\tswitch( edge )\n\t{\n\tcase LEFT_EDGE:\n\t\tif( vert[3] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase RIGHT_EDGE:\n\t\tif( vert[3] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase TOP_EDGE:\n\t\tif( vert[4] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase BOTTOM_EDGE:\n\t\tif( vert[4] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\t}\n\treturn 0;\n}\n\nstatic void R_ClipIntersect( float *one, float *two, float *out, int edge )\n{\n\tfloat t;\n\n\t// t is the parameter of the line between one and two clipped to the edge\n\t// or the fraction of the clipped point between one & two\n\t// vert[0], vert[1], vert[2] is X, Y, Z\n\t// vert[3] is u\n\t// vert[4] is v\n\t// vert[5] is lightmap u\n\t// vert[6] is lightmap v\n\n\tif( edge < TOP_EDGE )\n\t{\n\t\tif( edge == LEFT_EDGE )\n\t\t{\n\t\t\t// left\n\t\t\tt = (( one[3] - 0.0f ) / ( one[3] - two[3] ));\n\t\t\tout[3] = out[5] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// right\n\t\t\tt = (( one[3] - 1.0f ) / ( one[3] - two[3] ));\n\t\t\tout[3] = out[5] = 1.0f;\n\t\t}\n\n\t\tout[4] = one[4] + ( two[4] - one[4] ) * t;\n\t\tout[6] = one[6] + ( two[6] - one[6] ) * t;\n\t}\n\telse\n\t{\n\t\tif( edge == TOP_EDGE )\n\t\t{\n\t\t\t// top\n\t\t\tt = (( one[4] - 0.0f ) / ( one[4] - two[4] ));\n\t\t\tout[4] = out[6] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// bottom\n\t\t\tt = (( one[4] - 1.0f ) / ( one[4] - two[4] ));\n\t\t\tout[4] = out[6] = 1.0f;\n\t\t}\n\n\t\tout[3] = one[3] + ( two[3] - one[3] ) * t;\n\t\tout[5] = one[5] + ( two[4] - one[5] ) * t;\n\t}\n\n\tVectorLerp( one, t, two, out );\n}\n\nstatic int SHClip( float *vert, int vertCount, float *out, int edge )\n{\n\tint   j, outCount;\n\tfloat *s, *p;\n\n\toutCount = 0;\n\n\ts = &vert[( vertCount - 1 ) * VERTEXSIZE];\n\n\tfor( j = 0; j < vertCount; j++ )\n\t{\n\t\tp = &vert[j * VERTEXSIZE];\n\n\t\tif( R_ClipInside( p, edge ))\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\t// Add a vertex and advance out to next vertex\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tR_ClipIntersect( s, p, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\tR_ClipIntersect( p, s, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\n\t\ts = p;\n\t}\n\n\treturn outCount;\n}\n\nstatic float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount )\n{\n\tfloat *pOutVerts = g_DecalClipVerts[0];\n\tint   outCount;\n\n\t// clip the polygon to the decal texture space\n\toutCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE );\n\n\tif( pVertCount )\n\t\t*pVertCount = outCount;\n\n\treturn pOutVerts;\n}\n\n// -----------------------------------------------------------------------------\n// Generate clipped vertex list for decal pdecal projected onto polygon psurf\n// -----------------------------------------------------------------------------\nstatic float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount )\n{\n\tfloat  decalWorldScale[2];\n\tvec3_t textureSpaceBasis[3];\n\n\t// figure out where the decal maps onto the surface.\n\tR_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// build the initial list of vertices from the surface verts.\n\tR_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] );\n\n\tif( !surf->polys )\n\t\treturn 0;\n\treturn R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount );\n}\n\n// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf\nstatic void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount )\n{\n\tfloat        s, t;\n\tmtexinfo_t   *tex;\n\tmextrasurf_t *info = surf->info;\n\tfloat        sample_size;\n\tint          j;\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\ttex = surf->texinfo;\n\n\tfor( j = 0; j < vertCount; j++, v += VERTEXSIZE )\n\t{\n\t\t// lightmap texture coordinates\n\t\ts = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\t\ts += surf->light_s * sample_size;\n\t\ts += sample_size * 0.5f;\n\t\ts /= BLOCK_SIZE * sample_size; // fa->texinfo->texture->width;\n\n\t\tt = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\t\tt += surf->light_t * sample_size;\n\t\tt += sample_size * 0.5f;\n\t\tt /= BLOCK_SIZE * sample_size; // fa->texinfo->texture->height;\n\n\t\tv[5] = s;\n\t\tv[6] = t;\n\t}\n}\n\n// Check for intersecting decals on this surface\nstatic decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount )\n{\n\tint     texture;\n\tdecal_t *plast, *pDecal;\n\tvec3_t  decalExtents[2];\n\tfloat   lastArea = 2;\n\tint     mapSize[2];\n\n\tplast = NULL;\n\t*pcount = 0;\n\n\t// (Same as R_SetupDecalClip).\n\ttexture = decalinfo->m_iTexture;\n\n\t// precalculate the extents of decalinfo's decal in world space.\n\tR_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] );\n\tVectorScale( decalinfo->m_Basis[0], (( mapSize[0] / decalinfo->m_scale ) * 0.5f ), decalExtents[0] );\n\tVectorScale( decalinfo->m_Basis[1], (( mapSize[1] / decalinfo->m_scale ) * 0.5f ), decalExtents[1] );\n\n\tpDecal = surf->pdecals;\n\n\twhile( pDecal )\n\t{\n\t\ttexture = pDecal->texture;\n\n\t\t// Don't steal bigger decals and replace them with smaller decals\n\t\t// Don't steal permanent decals\n\t\tif( !FBitSet( pDecal->flags, FDECAL_PERMANENT ))\n\t\t{\n\t\t\tvec3_t testBasis[3];\n\t\t\tvec3_t testPosition[2];\n\t\t\tfloat  testWorldScale[2];\n\t\t\tvec2_t vDecalMin, vDecalMax;\n\t\t\tvec2_t vUnionMin, vUnionMax;\n\n\t\t\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale );\n\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\t// Here, we project the min and max extents of the decal that got passed in into\n\t\t\t// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were\n\t\t\t// clipping a triangle into pDecal's clip space.\n\t\t\tVector2Set( vDecalMin,\n\t\t\t\t    DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\t    DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\tVector2Set( vDecalMax,\n\t\t\t\t    DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\t    DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\t// Now figure out the part of the projection that intersects pDecal's\n\t\t\t// clip box [0,0,1,1].\n\t\t\tVector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 ));\n\t\t\tVector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 ));\n\n\t\t\tif( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 )\n\t\t\t{\n\t\t\t\t// Figure out how much of this intersects the (0,0) - (1,1) bbox.\n\t\t\t\tfloat flArea = ( vUnionMax[0] - vUnionMin[1] ) * ( vUnionMax[1] - vUnionMin[1] );\n\n\t\t\t\tif( flArea > 0.6f )\n\t\t\t\t{\n\t\t\t\t\t*pcount += 1;\n\n\t\t\t\t\tif( !plast || flArea <= lastArea )\n\t\t\t\t\t{\n\t\t\t\t\t\tplast = pDecal;\n\t\t\t\t\t\tlastArea = flArea;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpDecal = pDecal->pnext;\n\t}\n\treturn plast;\n}\n\n/*\n====================\nR_DecalCreatePoly\n\ncreates mesh for decal on first rendering\n====================\n*/\nstatic glpoly2_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf )\n{\n\tint       lnumverts;\n\tglpoly2_t *poly;\n\tfloat     *v;\n\tint       i;\n\n\treturn NULL;\n\tif( pdecal->polys )     // already created?\n\t\treturn pdecal->polys;\n\n\tv = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts );\n\tif( !lnumverts )\n\t\treturn NULL;            // probably this never happens\n\n\t// allocate glpoly\n\t// REFTODO: com_studiocache pool!\n\tpoly = Mem_Calloc( r_temppool, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = pdecal->polys;\n\tpoly->flags = surf->flags;\n\tpdecal->polys = poly;\n\tpoly->numverts = lnumverts;\n\n\tfor( i = 0; i < lnumverts; i++, v += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, poly->verts[i] );\n\t\tpoly->verts[i][3] = v[3];\n\t\tpoly->verts[i][4] = v[4];\n\t\tpoly->verts[i][5] = v[5];\n\t\tpoly->verts[i][6] = v[6];\n\t}\n\n\treturn poly;\n}\n\n// Add the decal to the surface's list of decals.\nstatic void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo )\n{\n\tdecal_t *pold;\n\n\tpdecal->pnext = NULL;\n\tpold = surf->pdecals;\n\n\tif( pold )\n\t{\n\t\twhile( pold->pnext )\n\t\t\tpold = pold->pnext;\n\t\tpold->pnext = pdecal;\n\t}\n\telse\n\t{\n\t\tsurf->pdecals = pdecal;\n\t}\n\n\t// force surface cache rebuild\n\tsurf->dlightframe = tr.framecount + 1;\n\n\t// tag surface\n\tpdecal->psurface = surf;\n\n\t// at this point decal are linked with surface\n\t// and will be culled, drawing and sorting\n\t// together with surface\n\n\t// alloc clipped poly for decal\n\tR_DecalCreatePoly( decalinfo, pdecal, surf );\n\t// R_AddDecalVBO( pdecal, surf );\n}\n\nstatic void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y )\n{\n\tdecal_t *pdecal, *pold;\n\tint     count, vertCount;\n\n\tif( !surf )\n\t\treturn;         // ???\n\n\tpold = R_DecalIntersect( decalinfo, surf, &count );\n\tif( count < MAX_OVERLAP_DECALS )\n\t\tpold = NULL;\n\n\tpdecal = R_DecalAlloc( pold );\n\tif( !pdecal )\n\t\treturn;       // r_decals == 0 ???\n\n\tpdecal->flags = decalinfo->m_Flags;\n\n\tVectorCopy( decalinfo->m_Position, pdecal->position );\n\n\tpdecal->dx = x;\n\tpdecal->dy = y;\n\n\t// set scaling\n\tpdecal->scale = decalinfo->m_scale;\n\tpdecal->entityIndex = decalinfo->m_Entity;\n\tpdecal->texture = decalinfo->m_iTexture;\n\n\t// check to see if the decal actually intersects the surface\n\t// if not, then remove the decal\n\tR_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount );\n\n\tif( !vertCount )\n\t{\n\t\tR_DecalUnlink( pdecal );\n\t\treturn;\n\t}\n\n\t// add to the surface's list\n\tR_AddDecalToSurface( pdecal, surf, decalinfo );\n}\n\nstatic void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )\n{\n\t// get the texture associated with this surface\n\tmtexinfo_t  *tex = surf->texinfo;\n\tdecal_t     *decal = surf->pdecals;\n\tvec4_t      textureU, textureV;\n\tfloat       s, t, w, h;\n\tconnstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE );\n\n\t// we in restore mode\n\tif( state == ca_connected || state == ca_validate )\n\t{\n\t\t// NOTE: we may have the decal on this surface that come from another level.\n\t\t// check duplicate with same position and texture\n\t\twhile( decal != NULL && decal != decal->pnext )\n\t\t{\n\t\t\tif( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture )\n\t\t\t\treturn; // decal already exists, don't place it again\n\t\t\tdecal = decal->pnext;\n\t\t}\n\t}\n\n\tVector4Copy( tex->vecs[0], textureU );\n\tVector4Copy( tex->vecs[1], textureV );\n\n\t// project decal center into the texture space of the surface\n\ts = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0];\n\tt = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1];\n\n\t// Determine the decal basis (measured in world space)\n\t// Note that the decal basis vectors 0 and 1 will always lie in the same\n\t// plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits.\n\tR_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis );\n\n\t// Compute an effective width and height (axis aligned) in the parent texture space\n\t// How does this work? decalBasis[0] represents the u-direction (width)\n\t// of the decal measured in world space, decalBasis[1] represents the\n\t// v-direction (height) measured in world space.\n\t// textureVecsTexelsPerWorldUnits[0] represents the u direction of\n\t// the surface's texture space measured in world space (with the appropriate\n\t// scale factor folded in), and textureVecsTexelsPerWorldUnits[1]\n\t// represents the texture space v direction. We want to find the dimensions (w,h)\n\t// of a square measured in texture space, axis aligned to that coordinate system.\n\t// All we need to do is to find the components of the decal edge vectors\n\t// (decalWidth * decalBasis[0], decalHeight * decalBasis[1])\n\t// in texture coordinates:\n\n\tw = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] ))\n\t    + fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] ));\n\n\th = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] ))\n\t    + fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] ));\n\n\t// move s,t to upper left corner\n\ts -= ( w * 0.5f );\n\tt -= ( h * 0.5f );\n\n\t// Is this rect within the surface? -- tex width & height are unsigned\n\tif( s <= -w || t <= -h || s > ( surf->extents[0] + w ) || t > ( surf->extents[1] + h ))\n\t{\n\t\treturn; // nope\n\t}\n\n\t// stamp it\n\tR_DecalCreate( decalinfo, surf, s, t );\n}\n\n// -----------------------------------------------------------------------------\n// iterate over all surfaces on a node, looking for surfaces to decal\n// -----------------------------------------------------------------------------\nstatic void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\t// iterate over all surfaces in the node\n\tmsurface_t\t*surf;\n\tint\t\ti;\n\tint firstsurface, numsurfaces;\n\n\tfirstsurface = node_firstsurface( node, model );\n\tnumsurfaces  = node_numsurfaces( node, model );\n\n\tsurf = model->surfaces + firstsurface;\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\t// never apply decals on the water or sky surfaces\n\t\tif( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))\n\t\t\tcontinue;\n\n\t\tR_DecalSurface( surf, decalinfo );\n\t}\n\n}\n\n// -----------------------------------------------------------------------------\n// Recursive routine to find surface to apply a decal to.  World coordinates of\n// the decal are passed in r_recalpos like the rest of the engine.  This should\n// be called through R_DecalShoot()\n// -----------------------------------------------------------------------------\nstatic void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\tmplane_t *splitplane;\n\tfloat    dist;\n\tmnode_t  *children[2];\n\n\tAssert( node != NULL );\n\n\tif( node->contents < 0 )\n\t{\n\t\t// hit a leaf\n\t\treturn;\n\t}\n\n\tsplitplane = node->plane;\n\tdist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;\n\tnode_children( children, node, model );\n\n\t// This is arbitrarily set to 10 right now. In an ideal world we'd have the\n\t// exact surface but we don't so, this tells me which planes are \"sort of\n\t// close\" to the gunshot -- the gunshot is actually 4 units in front of the\n\t// wall (see dlls\\weapons.cpp). We also need to check to see if the decal\n\t// actually intersects the texture space of the surface, as this method tags\n\t// parallel surfaces in the same node always.\n\t// JAY: This still tags faces that aren't correct at edges because we don't\n\t// have a surface normal\n\tif( dist > decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t}\n\telse if( dist < -decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n\telse\n\t{\n\t\tif( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )\n\t\t\tR_DecalNodeSurfaces( model, node, decalinfo );\n\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n}\n\n// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords\nvoid GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale )\n{\n\tdecalinfo_t decalInfo;\n\tcl_entity_t *ent = NULL;\n\tmodel_t     *model = NULL;\n\tint         width, height;\n\thull_t      *hull;\n\n\tif( textureIndex <= 0 || textureIndex >= MAX_TEXTURES )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Decal has invalid texture!\\n\" );\n\t\treturn;\n\t}\n\n\tif( entityIndex > 0 )\n\t{\n\t\tent = CL_GetEntityByIndex( entityIndex );\n\n\t\tif( modelIndex > 0 )\n\t\t\tmodel = CL_ModelHandle( modelIndex );\n\t\telse if( ent != NULL )\n\t\t\tmodel = CL_ModelHandle( ent->curstate.modelindex );\n\t\telse\n\t\t\treturn;\n\t}\n\telse if( modelIndex > 0 )\n\t\tmodel = CL_ModelHandle( modelIndex );\n\telse\n\t\tmodel = WORLDMODEL;\n\n\tif( !model )\n\t\treturn;\n\n\tif( model->type != mod_brush )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"Decals must hit mod_brush!\\n\" );\n\t\treturn;\n\t}\n\n\tdecalInfo.m_pModel = model;\n\thull = &model->hulls[0]; // always use #0 hull\n\n\t// NOTE: all the decals at 'first shoot' placed into local space of parent entity\n\t// and won't transform again on a next restore, levelchange etc\n\tif( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE ))\n\t{\n\t\tvec3_t pos_l;\n\n\t\t// transform decal position in local bmodel space\n\t\tif( !VectorIsNull( ent->angles ))\n\t\t{\n\t\t\tmatrix4x4 matrix;\n\n\t\t\tMatrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, pos, pos_l );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( pos, ent->origin, pos_l );\n\t\t}\n\n\t\tVectorCopy( pos_l, decalInfo.m_Position );\n\t\t// decal position moved into local space\n\t\tSetBits( flags, FDECAL_LOCAL_SPACE );\n\t}\n\telse\n\t{\n\t\t// already in local space\n\t\tVectorCopy( pos, decalInfo.m_Position );\n\t}\n\n\t// this decal must use landmark for correct transition\n\t// because their model exist only in world-space\n\tif( !FBitSet( model->flags, MODEL_HAS_ORIGIN ))\n\t\tSetBits( flags, FDECAL_USE_LANDMARK );\n\n\t// more state used by R_DecalNode()\n\tdecalInfo.m_iTexture = textureIndex;\n\tdecalInfo.m_Entity = entityIndex;\n\tdecalInfo.m_Flags = flags;\n\n\tR_GetDecalDimensions( textureIndex, &width, &height );\n\tdecalInfo.m_Size = width >> 1;\n\tif(( height >> 1 ) > decalInfo.m_Size )\n\t\tdecalInfo.m_Size = height >> 1;\n\n\tdecalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE );\n\n\t// compute the decal dimensions in world space\n\tdecalInfo.m_decalWidth = width / decalInfo.m_scale;\n\tdecalInfo.m_decalHeight = height / decalInfo.m_scale;\n\n\tR_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo );\n}\n\n// Build the vertex list for a decal on a surface and clip it to the surface.\n// This is a template so it can work on world surfaces and dynamic displacement\n// triangles the same way.\nfloat * GAME_EXPORT R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount )\n{\n\tglpoly2_t *p = pDecal->polys;\n\tint       i, count;\n\tfloat     *v, *v2;\n\n\tif( p )\n\t{\n\t\tv = g_DecalClipVerts[0];\n\t\tcount = p->numverts;\n\t\tv2 = p->verts[0];\n\n\t\t// if we have mesh so skip clipping and just copy vertexes out (perf)\n\t\tfor( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE )\n\t\t{\n\t\t\tVectorCopy( v2, v );\n\t\t\tv[3] = v2[3];\n\t\t\tv[4] = v2[4];\n\t\t\tv[5] = v2[5];\n\t\t\tv[6] = v2[6];\n\t\t}\n\n\t\t// restore pointer\n\t\tv = g_DecalClipVerts[0];\n\t}\n\telse\n\t{\n\t\tv = R_DecalVertsClip( pDecal, surf, texture, &count );\n\t\tR_DecalVertsLight( v, surf, count );\n\t}\n\n\tif( outCount )\n\t\t*outCount = count;\n\n\treturn v;\n}\n\n/*\n=============================================================\n\n  DECALS SERIALIZATION\n\n=============================================================\n*/\nstatic qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry )\n{\n\tif( !pdecal || !( pdecal->psurface ))\n\t\treturn false;\n\n\tVectorCopy( pdecal->position, entry->position );\n\tentry->entityIndex = pdecal->entityIndex;\n\n\t// Grab surface plane equation\n\tif( pdecal->psurface->flags & SURF_PLANEBACK )\n\t\tVectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\telse\n\t\tVectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\n\treturn true;\n}\n\n// -----------------------------------------------------------------------------\n// Purpose:\n// Input  : *pList -\n//\t\t\tcount -\n// Output : static int\n// -----------------------------------------------------------------------------\nstatic int DecalListAdd( decallist_t *pList, int count )\n{\n\tvec3_t      tmp;\n\tdecallist_t *pdecal;\n\tint         i;\n\n\tpdecal = pList + count;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tif( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex )\n\t\t{\n\t\t\tVectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge\n\n\t\t\tif( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE )\n\t\t\t\treturn count;\n\t\t}\n\t}\n\n\t// this is a new decal\n\treturn count + 1;\n}\n\nstatic int DecalDepthCompare( const void *a, const void *b )\n{\n\tconst decallist_t *elem1, *elem2;\n\n\telem1 = (const decallist_t *)a;\n\telem2 = (const decallist_t *)b;\n\n\tif( elem1->depth > elem2->depth )\n\t\treturn 1;\n\tif( elem1->depth < elem2->depth )\n\t\treturn -1;\n\n\treturn 0;\n}\n\n// -----------------------------------------------------------------------------\n// Purpose: Called by CSaveRestore::SaveClientState\n// Input  : *pList -\n// Output : int\n// -----------------------------------------------------------------------------\nint GAME_EXPORT R_CreateDecalList( decallist_t *pList )\n{\n\tint total = 0;\n\tint i, depth;\n\n//\treturn 0; // crash on changelevel. API bug?\n\n\tif( WORLDMODEL )\n\t{\n\t\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t\t{\n\t\t\tdecal_t *decal = &gDecalPool[i];\n\t\t\tdecal_t *pdecals;\n\n\t\t\t// decal is in use and is not a custom decal\n\t\t\tif( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE ))\n\t\t\t\tcontinue;\n\n\t\t\t// compute depth\n\t\t\tdepth = 0;\n\t\t\tpdecals = decal->psurface->pdecals;\n\n\t\t\twhile( pdecals && pdecals != decal )\n\t\t\t{\n\t\t\t\tdepth++;\n\t\t\t\tpdecals = pdecals->pnext;\n\t\t\t}\n\n\t\t\tpList[total].depth = depth;\n\t\t\tpList[total].flags = decal->flags;\n\t\t\tpList[total].scale = decal->scale;\n\n\t\t\tR_DecalUnProject( decal, &pList[total] );\n\t\t\tCOM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name ));\n\n\t\t\t// check to see if the decal should be added\n\t\t\ttotal = DecalListAdd( pList, total );\n\t\t}\n\n\t\tif( gEngfuncs.drawFuncs->R_CreateStudioDecalList )\n\t\t{\n\t\t\ttotal += gEngfuncs.drawFuncs->R_CreateStudioDecalList( pList, total );\n\t\t}\n\t}\n\n\t// sort the decals lowest depth first, so they can be re-applied in order\n\tqsort( pList, total, sizeof( decallist_t ), DecalDepthCompare );\n\n\treturn total;\n}\n\n/*\n===============\nR_DecalRemoveAll\n\nremove all decals with specified texture\n===============\n*/\nvoid GAME_EXPORT R_DecalRemoveAll( int textureIndex )\n{\n\tdecal_t *pdecal;\n\tint     i;\n\n\tif( textureIndex < 0 || textureIndex >= MAX_TEXTURES )\n\t\treturn; // out of bounds\n\n\tfor( i = 0; i < gDecalCount; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\n\t\t// don't remove permanent decals\n\t\tif( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT ))\n\t\t\tcontinue;\n\n\t\tif( !textureIndex || ( pdecal->texture == textureIndex ))\n\t\t\tR_DecalUnlink( pdecal );\n\t}\n}\n\n/*\n===============\nR_EntityRemoveDecals\n\nremove all decals from specified entity\n===============\n*/\nvoid GAME_EXPORT R_EntityRemoveDecals( model_t *mod )\n{\n\tmsurface_t *psurf;\n\tdecal_t    *p;\n\tint        i;\n\n\tif( !mod || mod->type != mod_brush )\n\t\treturn;\n\n\tpsurf = &mod->surfaces[mod->firstmodelsurface];\n\tfor( i = 0; i < mod->nummodelsurfaces; i++, psurf++ )\n\t{\n\t\tfor( p = psurf->pdecals; p; p = p->pnext )\n\t\t\tR_DecalUnlink( p );\n\t}\n}\n\n/*\n===============\nR_ClearAllDecals\n\nremove all decals from anything\nused for full decals restart\n===============\n*/\nvoid GAME_EXPORT R_ClearAllDecals( void )\n{\n\tdecal_t *pdecal;\n\tint     i;\n\n\t// because gDecalCount may be zeroed after recach the decal limit\n\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\t\tR_DecalUnlink( pdecal );\n\t}\n\n\tif( gEngfuncs.drawFuncs->R_ClearStudioDecals )\n\t{\n\t\tgEngfuncs.drawFuncs->R_ClearStudioDecals();\n\t}\n}\n"
  },
  {
    "path": "ref/soft/r_draw.c",
    "content": "/*\ngl_draw.c - orthogonal drawing stuff\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n\n/*\n=============\nR_GetImageParms\n=============\n*/\nvoid R_GetTextureParms( int *w, int *h, int texnum )\n{\n\timage_t *glt;\n\n\tglt = R_GetTexture( texnum );\n\tif( w )\n\t\t*w = glt->srcWidth;\n\tif( h )\n\t\t*h = glt->srcHeight;\n}\n\n/*\n=============\nR_GetSpriteParms\n\nsame as GetImageParms but used\nfor sprite models\n=============\n*/\nvoid GAME_EXPORT R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )\n{\n\tmspriteframe_t *pFrame;\n\n\tif( !pSprite || pSprite->type != mod_sprite )\n\t\treturn;                                       // bad model ?\n\tpFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );\n\n\tif( frameWidth )\n\t\t*frameWidth = pFrame->width;\n\tif( frameHeight )\n\t\t*frameHeight = pFrame->height;\n\tif( numFrames )\n\t\t*numFrames = pSprite->numframes;\n}\n\nint GAME_EXPORT R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )\n{\n\tif( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )\n\t\treturn 0;\n\n\treturn R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;\n}\n\n\n/*\n=============\nDraw_StretchPicImplementation\n=============\n*/\nstatic void R_DrawStretchPicImplementation( int x, int y, int w, int h, int s1, int t1, int s2, int t2, image_t *pic )\n{\n\tunsigned int height;\n\tint          skip, v;\n\tqboolean     transparent = false;\n\tpixel_t      *buffer;\n\n\tif( x < 0 )\n\t{\n\t\ts1 += ( -x ) * ( s2 - s1 ) / w;\n\t\tx = 0;\n\t}\n\tif( x + w > vid.width )\n\t{\n\t\ts2 -= ( x + w - vid.width ) * ( s2 - s1 ) / w;\n\t\tw = vid.width - x;\n\t}\n\tif( y + h > vid.height )\n\t{\n\t\tt2 -= ( y + h - vid.height ) * ( t2 - t1 ) / h;\n\t\th = vid.height - y;\n\t}\n\n\tif( !pic->pixels[0] || s1 >= s2 || t1 >= t2 )\n\t\treturn;\n\n\t// gEngfuncs.Con_Printf (\"pixels is %p\\n\", pic->pixels[0] );\n\n\theight = h;\n\n\tif( y < -h ) // out of display, out of bounds\n\t\treturn;\n\n\tif( y < 0 )\n\t{\n\t\tskip = -y;\n\t\theight += y;\n\t\ty = 0;\n\t}\n\telse\n\t\tskip = 0;\n\n\tif( pic->alpha_pixels )\n\t{\n\t\tbuffer = pic->alpha_pixels;\n\t\ttransparent = true;\n\t}\n\telse\n\t\tbuffer = pic->pixels[0];\n\n\n#pragma omp parallel for schedule(static)\n\tfor( v = 0; v < height; v++ )\n\t{\n\t\tint     alpha1 = vid.alpha;\n\t\tpixel_t *dest = vid.buffer + ( y + v ) * vid.rowbytes + x;\n\t\tuint    sv = ( skip + v ) * ( t2 - t1 ) / h + t1;\n\t\tuint    u, f, fstep;\n\t\tpixel_t *source = buffer + sv * pic->width + s1;\n\n\t\tf = 0;\n\t\tfstep = (( s2 - s1 ) << 16 ) / w;\n\n\t\tfor( u = 0; u < w; u++ )\n\t\t{\n\t\t\tpixel_t src = source[f >> 16];\n\t\t\tint     alpha = alpha1;\n\t\t\tf += fstep;\n\n\t\t\tif( transparent )\n\t\t\t{\n\t\t\t\talpha &= src >> ( 16 - 3 );\n\t\t\t\tsrc = src << 3;\n\t\t\t}\n\n\t\t\tif( alpha == 0 )\n\t\t\t\tcontinue;\n\n\t\t\tif( vid.color != COLOR_WHITE )\n\t\t\t\tsrc = vid.modmap[( src & 0xff00 ) | ( vid.color >> 8 )] << 8 | ( src & vid.color & 0xff ) | (( src & 0xff ) >> 3 );\n\n\t\t\tif( vid.rendermode == kRenderTransAdd )\n\t\t\t{\n\t\t\t\tpixel_t screen = dest[u];\n\t\t\t\tdest[u] = vid.addmap[( src & 0xff00 ) | ( screen >> 8 )] << 8 | ( screen & 0xff ) | (( src & 0xff ) >> 0 );\n\t\t\t}\n\t\t\telse if( vid.rendermode == kRenderScreenFadeModulate )\n\t\t\t{\n\t\t\t\tpixel_t screen = dest[u];\n\t\t\t\tdest[u] = BLEND_COLOR( screen, vid.color );\n\t\t\t}\n\t\t\telse if( alpha < 7 ) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) )\n\t\t\t{\n\t\t\t\tpixel_t screen = dest[u];                    //  | 0xff & screen & src ;\n\t\t\t\tdest[u] = BLEND_ALPHA( alpha, src, screen ); // vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3);\n\t\t\t}\n\t\t\telse\n\t\t\t\tdest[u] = src;\n\n\t\t}\n\t}\n}\n\n\n/*\n=============\nR_DrawStretchPic\n=============\n*/\nvoid GAME_EXPORT R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum )\n{\n\timage_t *pic = R_GetTexture( texnum );\n\tint     width = pic->width, height = pic->height;\n//\tGL_Bind( XASH_TEXTURE0, texnum );\n\tif( s2 > 1.0f || t2 > 1.0f )\n\t\treturn;\n\tif( s1 < 0.0f || t1 < 0.0f )\n\t\treturn;\n\tif( w < 1.0f || h < 1.0f )\n\t\treturn;\n\tR_DrawStretchPicImplementation( x, y, w, h, width * s1, height * t1, width * s2, height * t2, pic );\n}\n\nvoid Draw_Fill( int x, int y, int w, int h )\n{\n\tunsigned int height;\n\tint          v;\n\tpixel_t      src = vid.color;\n\tint          alpha = vid.alpha;\n\n\tif( x < 0 )\n\t\tx = 0;\n\n\tif( x + w > vid.width )\n\t\tw = vid.width - x;\n\n\tif( w <= 0 )\n\t\treturn;\n\n\tif( y + h > vid.height )\n\t\th = vid.height - y;\n\n\tif( h <= 0 )\n\t\treturn;\n\n\theight = h;\n\tif( y < 0 )\n\t{\n\t\tif( h <= -y )\n\t\t\treturn;\n\t\theight += y;\n\t\ty = 0;\n\t}\n\n#pragma omp parallel for schedule(static)\n\tfor( v = 0; v < height; v++ )\n\t{\n\t\tpixel_t *dest = vid.buffer + ( y + v ) * vid.rowbytes + x;\n\t\tuint    u;\n\n\t\tfor( u = 0; u < w; u++ )\n\t\t{\n\t\t\tif( alpha == 0 )\n\t\t\t\tcontinue;\n\n\t\t\tif( vid.rendermode == kRenderTransAdd )\n\t\t\t{\n\t\t\t\tpixel_t screen = dest[u];\n\t\t\t\tdest[u] = vid.addmap[( src & 0xff00 ) | ( screen >> 8 )] << 8 | ( screen & 0xff ) | (( src & 0xff ) >> 0 );\n\t\t\t}\n\t\t\telse if( alpha < 7 ) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) )\n\t\t\t{\n\t\t\t\tpixel_t screen = dest[u];                    //  | 0xff & screen & src ;\n\t\t\t\tdest[u] = BLEND_ALPHA( alpha, src, screen ); // vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3);\n\t\t\t}\n\t\t\telse\n\t\t\t\tdest[u] = src;\n\t\t}\n\t}\n}\n\n/*\n=============\nR_DrawStretchRaw\n=============\n*/\nvoid GAME_EXPORT R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty )\n{\n\tbyte    *raw = NULL;\n\timage_t *tex;\n\n\traw = (byte *)data;\n\n\t// pglDisable( GL_BLEND );\n\t// pglDisable( GL_ALPHA_TEST );\n\t// pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\n\ttex = R_GetTexture( tr.cinTexture );\n\tGL_Bind( XASH_TEXTURE0, tr.cinTexture );\n}\n\n/*\n=============\nR_UploadStretchRaw\n=============\n*/\nvoid GAME_EXPORT R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data )\n{\n\tbyte    *raw = NULL;\n\timage_t *tex;\n\traw = (byte *)data;\n\n\ttex = R_GetTexture( texture );\n\tGL_Bind( GL_KEEP_UNIT, texture );\n\ttex->width = cols;\n\ttex->height = rows;\n}\n\n/*\n===============\nR_Set2DMode\n===============\n*/\nvoid GAME_EXPORT R_Set2DMode( qboolean enable )\n{\n\tvid.color = COLOR_WHITE;\n\tvid.is2d = enable;\n\tvid.alpha = 7;\n\n\tif( enable )\n\t{\n\t\tRI.currententity = NULL;\n\t\tRI.currentmodel = NULL;\n\t}\n}\n"
  },
  {
    "path": "ref/soft/r_edge.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_edge.c\n\n#include \"r_local.h\"\n\nedge_t *auxedges;\nedge_t *r_edges, *edge_p, *edge_max;\n\nsurf_t *surfaces, *surface_p, *surf_max;\n\n// surfaces are generated in back to front order by the bsp, so if a surf\n// pointer is greater than another one, it should be drawn in front\n// surfaces[1] is the background, and is used as the active surface stack\n\nedge_t       *newedges[MAXHEIGHT];\nedge_t       *removeedges[MAXHEIGHT];\n\nespan_t      *span_p, *max_span_p;\n\nint          r_currentkey;\n\nint          current_iv;\n\nint          edge_head_u_shift20, edge_tail_u_shift20;\n\nstatic void  (*pdrawfunc)( void );\n\nedge_t       edge_head;\nedge_t       edge_tail;\n\nedge_t       edge_aftertail;\nedge_t       edge_sentinel;\n\nstatic float fv;\n\nstatic int   miplevel;\n\nfloat        scale_for_mip;\n\n// FIXME: should go away\nextern void R_RotateBmodel( void );\nextern void R_TransformFrustum( void );\n\n\n\nvoid R_GenerateSpans( void );\nvoid R_GenerateSpansBackward( void );\n\nvoid R_LeadingEdge( edge_t *edge );\nvoid R_LeadingEdgeBackwards( edge_t *edge );\nvoid R_TrailingEdge( surf_t *surf, edge_t *edge );\n\n\n/*\n===============================================================================\n\nEDGE SCANNING\n\n===============================================================================\n*/\n\n/*\n==============\nR_BeginEdgeFrame\n==============\n*/\nvoid R_BeginEdgeFrame( void )\n{\n\tint v;\n\n\tedge_p = r_edges;\n\tedge_max = &r_edges[r_numallocatededges];\n\n\tsurface_p = &surfaces[2]; // background is surface 1,\n\t//  surface 0 is a dummy\n\tsurfaces[1].spans = NULL; // no background spans yet\n\tsurfaces[1].flags = 0;    // SURF_DRAWBACKGROUND;\n\n// put the background behind everything in the world\n\tif( sw_draworder.value )\n\t{\n\t\tpdrawfunc = R_GenerateSpansBackward;\n\t\tsurfaces[1].key = 0;\n\t\tr_currentkey = 1;\n\t}\n\telse\n\t{\n\t\tpdrawfunc = R_GenerateSpans;\n\t\tsurfaces[1].key = 0x7FFfFFFF;\n\t\tr_currentkey = 0;\n\t}\n\n// FIXME: set with memset\n\tfor( v = RI.vrect.y; v < RI.vrectbottom; v++ )\n\t{\n\t\tnewedges[v] = removeedges[v] = NULL;\n\t}\n}\n\n/*\n==============\nR_InsertNewEdges\n\nAdds the edges in the linked list edgestoadd, adding them to the edges in the\nlinked list edgelist.  edgestoadd is assumed to be sorted on u, and non-empty (this is actually newedges[v]).  edgelist is assumed to be sorted on u, with a\nsentinel at the end (actually, this is the active edge table starting at\nedge_head.next).\n==============\n*/\nstatic void R_InsertNewEdges( edge_t *edgestoadd, edge_t *edgelist )\n{\n\tedge_t *next_edge;\n\n\tdo\n\t{\n\t\tnext_edge = edgestoadd->next;\nedgesearch:\n\t\tif( !edgelist )\n\t\t{\n\t\t\t//\tgEngfuncs.Con_Printf(\"NULL edgelist!\\n\");\n\t\t\t// return;\n\t\t}\n\t\tif( edgelist->u >= edgestoadd->u )\n\t\t\tgoto addedge;\n\t\tedgelist = edgelist->next;\n\t\tif( edgelist->u >= edgestoadd->u )\n\t\t\tgoto addedge;\n\t\tedgelist = edgelist->next;\n\t\tif( edgelist->u >= edgestoadd->u )\n\t\t\tgoto addedge;\n\t\tedgelist = edgelist->next;\n\t\tif( edgelist->u >= edgestoadd->u )\n\t\t\tgoto addedge;\n\t\tedgelist = edgelist->next;\n\t\tgoto edgesearch;\n\n\t\t// insert edgestoadd before edgelist\naddedge:\n\t\tedgestoadd->next = edgelist;\n\t\tedgestoadd->prev = edgelist->prev;\n\t\tedgelist->prev->next = edgestoadd;\n\t\tedgelist->prev = edgestoadd;\n\t}\n\twhile(( edgestoadd = next_edge ) != NULL );\n}\n\n/*\n==============\nR_RemoveEdges\n==============\n*/\nstatic void R_RemoveEdges( edge_t *pedge )\n{\n\n\tdo\n\t{\n\t\tpedge->next->prev = pedge->prev;\n\t\tpedge->prev->next = pedge->next;\n\t}\n\twhile(( pedge = pedge->nextremove ) != NULL );\n}\n\n/*\n==============\nR_StepActiveU\n==============\n*/\nstatic void R_StepActiveU( edge_t *pedge )\n{\n\tedge_t *pnext_edge, *pwedge;\n\n\twhile( 1 )\n\t{\nnextedge:\n\t\tpedge->u += pedge->u_step;\n\t\tif( pedge->u < pedge->prev->u )\n\t\t\tgoto pushback;\n\t\tpedge = pedge->next;\n\n\t\tpedge->u += pedge->u_step;\n\t\tif( pedge->u < pedge->prev->u )\n\t\t\tgoto pushback;\n\t\tpedge = pedge->next;\n\n\t\tpedge->u += pedge->u_step;\n\t\tif( pedge->u < pedge->prev->u )\n\t\t\tgoto pushback;\n\t\tpedge = pedge->next;\n\n\t\tpedge->u += pedge->u_step;\n\t\tif( pedge->u < pedge->prev->u )\n\t\t\tgoto pushback;\n\t\tpedge = pedge->next;\n\n\t\tgoto nextedge;\n\npushback:\n\t\tif( pedge == &edge_aftertail )\n\t\t\treturn;\n\n\t\t// push it back to keep it sorted\n\t\tpnext_edge = pedge->next;\n\n\t\t// pull the edge out of the edge list\n\t\tpedge->next->prev = pedge->prev;\n\t\tpedge->prev->next = pedge->next;\n\n\t\t// find out where the edge goes in the edge list\n\t\tpwedge = pedge->prev->prev;\n\t\t//\tif( !pwedge )\n\t\t//\treturn;\n\n\t\twhile( pwedge->u > pedge->u )\n\t\t{\n\t\t\tpwedge = pwedge->prev;\n\t\t\t// if( !pwedge )\n\t\t\t// return;\n\t\t}\n\n\t\t// put the edge back into the edge list\n\t\tpedge->next = pwedge->next;\n\t\tpedge->prev = pwedge;\n\t\tpedge->next->prev = pedge;\n\t\tpwedge->next = pedge;\n\n\t\tpedge = pnext_edge;\n\t\tif( pedge == &edge_tail )\n\t\t\treturn;\n\t}\n}\n\n/*\n==============\nR_CleanupSpan\n==============\n*/\nstatic void R_CleanupSpan( void )\n{\n\tsurf_t  *surf;\n\tint     iu;\n\tespan_t *span;\n\n// now that we've reached the right edge of the screen, we're done with any\n// unfinished surfaces, so emit a span for whatever's on top\n\tsurf = surfaces[1].next;\n\tiu = edge_tail_u_shift20;\n\tif( iu > surf->last_u )\n\t{\n\t\tspan = span_p++;\n\t\tspan->u = surf->last_u;\n\t\tspan->count = iu - span->u;\n\t\tspan->v = current_iv;\n\t\tspan->pnext = surf->spans;\n\t\tsurf->spans = span;\n\t}\n\n// reset spanstate for all surfaces in the surface stack\n\tdo\n\t{\n\t\tsurf->spanstate = 0;\n\t\tsurf = surf->next;\n\t}\n\twhile( surf != &surfaces[1] );\n}\n\n\n/*\n==============\nR_LeadingEdgeBackwards\n==============\n*/\nvoid R_LeadingEdgeBackwards( edge_t *edge )\n{\n\tespan_t *span;\n\tsurf_t  *surf, *surf2;\n\tint     iu;\n\n// it's adding a new surface in, so find the correct place\n\tsurf = &surfaces[edge->surfs[1]];\n\n// don't start a span if this is an inverted span, with the end\n// edge preceding the start edge (that is, we've already seen the\n// end edge)\n\tif( ++surf->spanstate == 1 )\n\t{\n\t\tsurf2 = surfaces[1].next;\n\n\t\tif( surf->key > surf2->key )\n\t\t\tgoto newtop;\n\n\t\t// if it's two surfaces on the same plane, the one that's already\n\t\t// active is in front, so keep going unless it's a bmodel\n\t\tif( surf->insubmodel && ( surf->key == surf2->key ))\n\t\t{\n\t\t\t// must be two bmodels in the same leaf; don't care, because they'll\n\t\t\t// never be farthest anyway\n\t\t\tgoto newtop;\n\t\t}\n\ncontinue_search:\n\n\t\tdo\n\t\t{\n\t\t\tsurf2 = surf2->next;\n\t\t}\n\t\twhile( surf->key < surf2->key );\n\n\t\tif( surf->key == surf2->key )\n\t\t{\n\t\t\t// if it's two surfaces on the same plane, the one that's already\n\t\t\t// active is in front, so keep going unless it's a bmodel\n\t\t\tif( !surf->insubmodel )\n\t\t\t\tgoto continue_search;\n\n\t\t\t// must be two bmodels in the same leaf; don't care which is really\n\t\t\t// in front, because they'll never be farthest anyway\n\t\t}\n\n\t\tgoto gotposition;\n\nnewtop:\n\t\t// emit a span (obscures current top)\n\t\tiu = edge->u >> 20;\n\n\t\tif( iu > surf2->last_u )\n\t\t{\n\t\t\tspan = span_p++;\n\t\t\tspan->u = surf2->last_u;\n\t\t\tspan->count = iu - span->u;\n\t\t\tspan->v = current_iv;\n\t\t\tspan->pnext = surf2->spans;\n\t\t\tsurf2->spans = span;\n\t\t}\n\n\t\t// set last_u on the new span\n\t\tsurf->last_u = iu;\n\ngotposition:\n\t\t// insert before surf2\n\t\tsurf->next = surf2;\n\t\tsurf->prev = surf2->prev;\n\t\tsurf2->prev->next = surf;\n\t\tsurf2->prev = surf;\n\t}\n}\n\n\n/*\n==============\nR_TrailingEdge\n==============\n*/\nvoid R_TrailingEdge( surf_t *surf, edge_t *edge )\n{\n\tespan_t *span;\n\tint     iu;\n\n// don't generate a span if this is an inverted span, with the end\n// edge preceding the start edge (that is, we haven't seen the\n// start edge yet)\n\tif( --surf->spanstate == 0 )\n\t{\n\t\tif( surf == surfaces[1].next )\n\t\t{\n\t\t\t// emit a span (current top going away)\n\t\t\tiu = edge->u >> 20;\n\t\t\tif( iu > surf->last_u )\n\t\t\t{\n\t\t\t\tspan = span_p++;\n\t\t\t\tspan->u = surf->last_u;\n\t\t\t\tspan->count = iu - span->u;\n\t\t\t\tspan->v = current_iv;\n\t\t\t\tspan->pnext = surf->spans;\n\t\t\t\tsurf->spans = span;\n\t\t\t}\n\n\t\t\t// set last_u on the surface below\n\t\t\tsurf->next->last_u = iu;\n\t\t}\n\n\t\tsurf->prev->next = surf->next;\n\t\tsurf->next->prev = surf->prev;\n\t}\n}\n\n/*\n==============\nR_LeadingEdge\n==============\n*/\nvoid R_LeadingEdge( edge_t *edge )\n{\n\tespan_t *span;\n\tsurf_t  *surf, *surf2;\n\tint     iu;\n\tfloat   fu, newzi, testzi, newzitop, newzibottom;\n\n\tif( edge->surfs[1] )\n\t{\n\t\t// it's adding a new surface in, so find the correct place\n\t\tsurf = &surfaces[edge->surfs[1]];\n\n\t\t// don't start a span if this is an inverted span, with the end\n\t\t// edge preceding the start edge (that is, we've already seen the\n\t\t// end edge)\n\t\tif( ++surf->spanstate == 1 )\n\t\t{\n\t\t\tsurf2 = surfaces[1].next;\n\n\t\t\tif( surf->key < surf2->key )\n\t\t\t\tgoto newtop;\n\n\t\t\t// if it's two surfaces on the same plane, the one that's already\n\t\t\t// active is in front, so keep going unless it's a bmodel\n\t\t\tif( surf->insubmodel && ( surf->key == surf2->key ))\n\t\t\t{\n\t\t\t\t// must be two bmodels in the same leaf; sort on 1/z\n\t\t\t\tfu = (float)( edge->u - 0xFFFFF ) * ( 1.0f / 0x100000 );\n\t\t\t\tnewzi = surf->d_ziorigin + fv * surf->d_zistepv\n\t\t\t\t\t+ fu * surf->d_zistepu;\n\t\t\t\tnewzibottom = newzi * 0.99f;\n\n\t\t\t\ttestzi = surf2->d_ziorigin + fv * surf2->d_zistepv\n\t\t\t\t\t + fu * surf2->d_zistepu;\n\n\t\t\t\tif( newzibottom >= testzi )\n\t\t\t\t{\n\t\t\t\t\tgoto newtop;\n\t\t\t\t}\n\n\t\t\t\tnewzitop = newzi * 1.01f;\n\t\t\t\tif( newzitop >= testzi )\n\t\t\t\t{\n\t\t\t\t\tif( surf->d_zistepu >= surf2->d_zistepu )\n\t\t\t\t\t{\n\t\t\t\t\t\tgoto newtop;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\ncontinue_search:\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tsurf2 = surf2->next;\n\t\t\t}\n\t\t\twhile( surf->key > surf2->key );\n\n\t\t\tif( surf->key == surf2->key )\n\t\t\t{\n\t\t\t\t// if it's two surfaces on the same plane, the one that's already\n\t\t\t\t// active is in front, so keep going unless it's a bmodel\n\t\t\t\tif( !surf->insubmodel )\n\t\t\t\t\tgoto continue_search;\n\n\t\t\t\t// must be two bmodels in the same leaf; sort on 1/z\n\t\t\t\tfu = (float)( edge->u - 0xFFFFF ) * ( 1.0f / 0x100000 );\n\t\t\t\tnewzi = surf->d_ziorigin + fv * surf->d_zistepv\n\t\t\t\t\t+ fu * surf->d_zistepu;\n\t\t\t\tnewzibottom = newzi * 0.99f;\n\n\t\t\t\ttestzi = surf2->d_ziorigin + fv * surf2->d_zistepv\n\t\t\t\t\t + fu * surf2->d_zistepu;\n\n\t\t\t\tif( newzibottom >= testzi )\n\t\t\t\t{\n\t\t\t\t\tgoto gotposition;\n\t\t\t\t}\n\n\t\t\t\tnewzitop = newzi * 1.01f;\n\t\t\t\tif( newzitop >= testzi )\n\t\t\t\t{\n\t\t\t\t\tif( surf->d_zistepu >= surf2->d_zistepu )\n\t\t\t\t\t{\n\t\t\t\t\t\tgoto gotposition;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tgoto continue_search;\n\t\t\t}\n\n\t\t\tgoto gotposition;\n\nnewtop:\n\t\t\t// emit a span (obscures current top)\n\t\t\tiu = edge->u >> 20;\n\n\t\t\tif( iu > surf2->last_u )\n\t\t\t{\n\t\t\t\tspan = span_p++;\n\t\t\t\tspan->u = surf2->last_u;\n\t\t\t\tspan->count = iu - span->u;\n\t\t\t\tspan->v = current_iv;\n\t\t\t\tspan->pnext = surf2->spans;\n\t\t\t\tsurf2->spans = span;\n\t\t\t}\n\n\t\t\t// set last_u on the new span\n\t\t\tsurf->last_u = iu;\n\ngotposition:\n\t\t\t// insert before surf2\n\t\t\tsurf->next = surf2;\n\t\t\tsurf->prev = surf2->prev;\n\t\t\tsurf2->prev->next = surf;\n\t\t\tsurf2->prev = surf;\n\t\t}\n\t}\n}\n\n\n/*\n==============\nR_GenerateSpans\n==============\n*/\nvoid R_GenerateSpans( void )\n{\n\tedge_t *edge;\n\tsurf_t *surf;\n\n// clear active surfaces to just the background surface\n\tsurfaces[1].next = surfaces[1].prev = &surfaces[1];\n\tsurfaces[1].last_u = edge_head_u_shift20;\n\n// generate spans\n\tfor( edge = edge_head.next; edge != &edge_tail; edge = edge->next )\n\t{\n\t\tif( edge->surfs[0] )\n\t\t{\n\t\t\t// it has a left surface, so a surface is going away for this span\n\t\t\tsurf = &surfaces[edge->surfs[0]];\n\n\t\t\tR_TrailingEdge( surf, edge );\n\n\t\t\tif( !edge->surfs[1] )\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tR_LeadingEdge( edge );\n\t}\n\n\tR_CleanupSpan();\n}\n\n/*\n==============\nR_GenerateSpansBackward\n==============\n*/\nvoid R_GenerateSpansBackward( void )\n{\n\tedge_t *edge;\n\n// clear active surfaces to just the background surface\n\tsurfaces[1].next = surfaces[1].prev = &surfaces[1];\n\tsurfaces[1].last_u = edge_head_u_shift20;\n\n// generate spans\n\tfor( edge = edge_head.next; edge != &edge_tail; edge = edge->next )\n\t{\n\t\tif( edge->surfs[0] )\n\t\t\tR_TrailingEdge( &surfaces[edge->surfs[0]], edge );\n\n\t\tif( edge->surfs[1] )\n\t\t\tR_LeadingEdgeBackwards( edge );\n\t}\n\n\tR_CleanupSpan();\n}\n\n\n/*\n==============\nR_ScanEdges\n\nInput:\nnewedges[] array\n\tthis has links to edges, which have links to surfaces\n\nOutput:\nEach surface has a linked list of its visible spans\n==============\n*/\nvoid R_ScanEdges( void )\n{\n\tint     iv, bottom;\n\tbyte    basespans[MAXSPANS * sizeof( espan_t ) + CACHE_SIZE];\n\tespan_t *basespan_p;\n\tsurf_t  *s;\n\n\tbasespan_p = (espan_t *)\n\t\t     ((uintptr_t)( basespans + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 ));\n\tmax_span_p = &basespan_p[MAXSPANS - RI.vrect.width];\n\n\tspan_p = basespan_p;\n\n// clear active edges to just the background edges around the whole screen\n// FIXME: most of this only needs to be set up once\n\tedge_head.u = RI.vrect.x << 20;\n\tedge_head_u_shift20 = edge_head.u >> 20;\n\tedge_head.u_step = 0;\n\tedge_head.prev = NULL;\n\tedge_head.next = &edge_tail;\n\tedge_head.surfs[0] = 0;\n\tedge_head.surfs[1] = 1;\n\n\tedge_tail.u = ( RI.vrectright << 20 ) + 0xFFFFF; // (r_refdef.vrectright << 20) + 0xFFFFF;\n\tedge_tail_u_shift20 = edge_tail.u >> 20;\n\tedge_tail.u_step = 0;\n\tedge_tail.prev = &edge_head;\n\tedge_tail.next = &edge_aftertail;\n\tedge_tail.surfs[0] = 1;\n\tedge_tail.surfs[1] = 0;\n\n\tedge_aftertail.u = -1; // force a move\n\tedge_aftertail.u_step = 0;\n\tedge_aftertail.next = &edge_sentinel;\n\tedge_aftertail.prev = &edge_tail;\n\n// FIXME: do we need this now that we clamp x in r_draw.c?\n\tedge_sentinel.u = 2000 << 20; // make sure nothing sorts past this\n\tedge_sentinel.prev = &edge_aftertail;\n\n//\n// process all scan lines\n//\n\tbottom = RI.vrectbottom - 1;\n\n\tfor( iv = 0; iv < bottom; iv++ )\n\t{\n\t\tcurrent_iv = iv;\n\t\tfv = (float)iv;\n\n\t\t// mark that the head (background start) span is pre-included\n\t\tsurfaces[1].spanstate = 1;\n\n\t\tif( newedges[iv] )\n\t\t{\n\t\t\tR_InsertNewEdges( newedges[iv], edge_head.next );\n\t\t}\n\t\t( *pdrawfunc )();\n\n\t\t// flush the span list if we can't be sure we have enough spans left for\n\t\t// the next scan\n\t\tif( span_p > max_span_p )\n\t\t{\n\t\t\tD_DrawSurfaces();\n\n\t\t\t// clear the surface span pointers\n\t\t\tfor( s = &surfaces[1]; s < surface_p; s++ )\n\t\t\t\ts->spans = NULL;\n\n\t\t\tspan_p = basespan_p;\n\t\t}\n\t\tif( removeedges[iv] )\n\t\t\tR_RemoveEdges( removeedges[iv] );\n\n\t\tif( edge_head.next != &edge_tail )\n\t\t\tR_StepActiveU( edge_head.next );\n\t}\n\n// do the last scan (no need to step or sort or remove on the last scan)\n\n\tcurrent_iv = iv;\n\tfv = (float)iv;\n\n// mark that the head (background start) span is pre-included\n\tsurfaces[1].spanstate = 1;\n\n\tif( newedges[iv] )\n\t\tR_InsertNewEdges( newedges[iv], edge_head.next );\n\n\t( *pdrawfunc )();\n\n// draw whatever's left in the span list\n\tD_DrawSurfaces();\n}\n\n\n/*\n=========================================================================\n\nSURFACE FILLING\n\n=========================================================================\n*/\n\nmsurface_t  *pface;\nsurfcache_t *pcurrentcache;\nvec3_t      transformed_modelorg;\nvec3_t      world_transformed_modelorg;\nvec3_t      local_modelorg;\n\n/*\n=============\nD_MipLevelForScale\n=============\n*/\nstatic int D_MipLevelForScale( float scale )\n{\n\tint lmiplevel;\n\n\tif( scale >= d_scalemip[0] )\n\t\tlmiplevel = 0;\n\telse if( scale >= d_scalemip[1] )\n\t\tlmiplevel = 1;\n\telse if( scale >= d_scalemip[2] )\n\t\tlmiplevel = 2;\n\telse\n\t\tlmiplevel = 3;\n\n\tif( lmiplevel < d_minmip )\n\t\tlmiplevel = d_minmip;\n\n\treturn lmiplevel;\n}\n\n\n/*\n==============\nD_FlatFillSurface\n\nSimple single color fill with no texture mapping\n==============\n*/\nstatic void D_FlatFillSurface( surf_t *surf, int color )\n{\n\tespan_t *span;\n\tpixel_t *pdest;\n\tint     u, u2;\n\n\tfor( span = surf->spans; span; span = span->pnext )\n\t{\n\t\tpdest = d_viewbuffer + r_screenwidth * span->v;\n\t\tu = span->u;\n\t\tu2 = span->u + span->count - 1;\n\t\tfor( ; u <= u2; u++ )\n\t\t\tpdest[u] = color;\n\t}\n}\n\n\n/*\n==============\nD_CalcGradients\n==============\n*/\nstatic void D_CalcGradients( msurface_t *pface )\n{\n\tmplane_t *pplane;\n\tfloat    mipscale;\n\tvec3_t   p_temp1;\n\tvec3_t   p_saxis, p_taxis;\n\tfloat    t;\n\n\tpplane = pface->plane;\n\n\tmipscale = 1.0f / (float)( 1 << miplevel );\n\n\n\tif( pface->texinfo->flags & TEX_WORLD_LUXELS )\n\t{\n\t\tTransformVector( pface->texinfo->vecs[0], p_saxis );\n\t\tTransformVector( pface->texinfo->vecs[1], p_taxis );\n\t}\n\telse\n\t{\n\t\tTransformVector( pface->info->lmvecs[0], p_saxis );\n\t\tTransformVector( pface->info->lmvecs[1], p_taxis );\n\t}\n\n\tt = xscaleinv * mipscale;\n\td_sdivzstepu = p_saxis[0] * t;\n\td_tdivzstepu = p_taxis[0] * t;\n\n\tt = yscaleinv * mipscale;\n\td_sdivzstepv = -p_saxis[1] * t;\n\td_tdivzstepv = -p_taxis[1] * t;\n\n\td_sdivzorigin = p_saxis[2] * mipscale - xcenter * d_sdivzstepu\n\t\t\t- ycenter * d_sdivzstepv;\n\td_tdivzorigin = p_taxis[2] * mipscale - xcenter * d_tdivzstepu\n\t\t\t- ycenter * d_tdivzstepv;\n\n\tVectorScale( transformed_modelorg, mipscale, p_temp1 );\n\n\tt = 0x10000 * mipscale;\n\tif( pface->texinfo->flags & TEX_WORLD_LUXELS )\n\t{\n\t\tsadjust = ((fixed16_t)( DotProduct( p_temp1, p_saxis ) * 0x10000 + 0.5f ))\n\t\t\t  - (( pface->texturemins[0] << 16 ) >> miplevel )\n\t\t\t  + pface->texinfo->vecs[0][3] * t;\n\t\ttadjust = ((fixed16_t)( DotProduct( p_temp1, p_taxis ) * 0x10000 + 0.5f ))\n\t\t\t  - (( pface->texturemins[1] << 16 ) >> miplevel )\n\t\t\t  + pface->texinfo->vecs[1][3] * t;\n\t}\n\telse\n\t{\n\t\tsadjust = ((fixed16_t)( DotProduct( p_temp1, p_saxis ) * 0x10000 + 0.5f ))\n\t\t\t  - (( pface->info->lightmapmins[0] << 16 ) >> miplevel )\n\t\t\t  + pface->info->lmvecs[0][3] * t;\n\t\ttadjust = ((fixed16_t)( DotProduct( p_temp1, p_taxis ) * 0x10000 + 0.5f ))\n\t\t\t  - (( pface->info->lightmapmins[1] << 16 ) >> miplevel )\n\t\t\t  + pface->info->lmvecs[1][3] * t;\n\t}\n\t// PGM - changing flow speed for non-warping textures.\n\tif( pface->flags & SURF_CONVEYOR )\n\t{\n\n\t\tif( pface->flags & SURF_DRAWTURB )\n\t\t\tsadjust += 0x10000 * ( -128 * (( gp_cl->time * 0.25f ) - (int)( gp_cl->time * 0.25f )));\n\t\telse\n\t\t\tsadjust += 0x10000 * ( -128 * (( gp_cl->time * 0.77f ) - (int)( gp_cl->time * 0.77f )));\n\t\tbbextents = (( pface->extents[0] << 16 ) >> miplevel ) - 1;\n\t}\n\telse\n\t\tbbextents = (( pface->info->lightextents[0] << 16 ) >> miplevel ) - 1;\n\tbbextentt = (( pface->info->lightextents[1] << 16 ) >> miplevel ) - 1;\n\n\tif( pface->texinfo->flags & TEX_WORLD_LUXELS )\n\t{\n\t\tbbextents = (( pface->extents[0] << 16 ) >> miplevel ) - 1;\n\t\tbbextentt = (( pface->extents[1] << 16 ) >> miplevel ) - 1;\n\t}\n}\n\n\n/*\n==============\nD_BackgroundSurf\n\nThe grey background filler seen when there is a hole in the map\n==============\n*/\nstatic void D_BackgroundSurf( surf_t *s )\n{\n// set up a gradient for the background surface that places it\n// effectively at infinity distance from the viewpoint\n\td_zistepu = 0;\n\td_zistepv = 0;\n\td_ziorigin = -0.9;\n\n\tD_FlatFillSurface( s, (int)sw_clearcolor.value & 0xFFFF );\n\tD_DrawZSpans( s->spans );\n}\n\n/*\n=================\nD_TurbulentSurf\n=================\n*/\nstatic void D_TurbulentSurf( surf_t *s )\n{\n\td_zistepu = s->d_zistepu;\n\td_zistepv = s->d_zistepv;\n\td_ziorigin = s->d_ziorigin;\n\n\tpface = s->msurf;\n\tmiplevel = 0;\n\tcacheblock = R_GetTexture( pface->texinfo->texture->gl_texturenum )->pixels[0];\n\tcachewidth = 64;\n\n\tif( s->insubmodel )\n\t{\n\t\t// FIXME: we don't want to do all this for every polygon!\n\t\t// TODO: store once at start of frame\n\t\tRI.currententity = s->entity; // FIXME: make this passed in to\n\t\t// R_RotateBmodel ()\n\t\tVectorSubtract( RI.vieworg, RI.currententity->origin,\n\t\t\t\tlocal_modelorg );\n\t\tTransformVector( local_modelorg, transformed_modelorg );\n\n\t\tR_RotateBmodel(); // FIXME: don't mess with the frustum,\n\t\t// make entity passed in\n\t}\n\n\tD_CalcGradients( pface );\n\n// ============\n// PGM\n\t// textures that aren't warping are just flowing. Use NonTurbulent8 instead\n\tif( !( pface->flags & SURF_DRAWTURB ))\n\t\tNonTurbulent8( s->spans );\n\telse\n\t\tTurbulent8( s->spans );\n// PGM\n// ============\n\n\tD_DrawZSpans( s->spans );\n\n\tif( s->insubmodel )\n\t{\n\t\t//\n\t\t// restore the old drawing state\n\t\t// FIXME: we don't want to do this every time!\n\t\t// TODO: speed up\n\t\t//\n\t\tRI.currententity = NULL; // &r_worldentity;\n\t\tVectorCopy( world_transformed_modelorg,\n\t\t\t    transformed_modelorg );\n\t\tVectorCopy( RI.base_vpn, RI.vforward );\n\t\tVectorCopy( RI.base_vup, RI.vup );\n\t\tVectorCopy( RI.base_vright, RI.vright );\n\t\tR_TransformFrustum();\n\t}\n}\n\nqboolean alphaspans;\n/*\n==============\nD_SolidSurf\n\nNormal surface cached, texture mapped surface\n==============\n*/\nstatic void D_AlphaSurf( surf_t *s )\n{\n\tint alpha;\n\n\td_zistepu = s->d_zistepu;\n\td_zistepv = s->d_zistepv;\n\td_ziorigin = s->d_ziorigin;\n\tif( s->flags & SURF_DRAWSKY )\n\t\treturn;\n\tif( !s->insubmodel )// wtf? how it is possible?\n\t\treturn;\n\n// FIXME: we don't want to do all this for every polygon!\n// TODO: store once at start of frame\n\tRI.currententity = s->entity; // FIXME: make this passed in to\n\t// R_RotateBmodel ()\n\tVectorSubtract( RI.vieworg, RI.currententity->origin, local_modelorg );\n\tTransformVector( local_modelorg, transformed_modelorg );\n\n\tR_RotateBmodel(); // FIXME: don't mess with the frustum,\n\t// make entity passed in\n\n\n\tpface = s->msurf;\n\n\n\tif( !pface )\n\t\treturn;\n\n\tif( pface->flags & SURF_CONVEYOR )\n\t\tmiplevel = 1;\n\telse\n\t\tmiplevel = 0;\n\n\talpha = RI.currententity->curstate.renderamt * 7 / 255;\n\tif( alpha <= 0 && RI.currententity->curstate.renderamt > 0 )\n\t\talpha = 1;\n\n\tif( s->flags & SURF_DRAWTURB )\n\t{\n\t\tcacheblock = R_GetTexture( pface->texinfo->texture->gl_texturenum )->pixels[0];\n\t\tcachewidth = 64;\n\t\tD_CalcGradients( pface );\n\t\tTurbulentZ8( s->spans, alpha );\n\t}\n\telse\n\t{\n\t\t// FIXME: make this passed in to D_CacheSurface\n\t\tpcurrentcache = D_CacheSurface( pface, miplevel );\n\n\t\tcacheblock = (pixel_t *)pcurrentcache->data;\n\t\tcachewidth = pcurrentcache->width;\n\n\t\tD_CalcGradients( pface );\n\n\n\n\t\tif( RI.currententity->curstate.rendermode == kRenderTransAlpha )\n\t\t\tD_AlphaSpans16( s->spans );\n\t\telse if( RI.currententity->curstate.rendermode == kRenderTransAdd )\n\t\t\tD_AddSpans16( s->spans );\n\t\telse\n\t\t\tD_BlendSpans16( s->spans, alpha );\n\t}\n\n\tVectorCopy( world_transformed_modelorg,\n\t\t    transformed_modelorg );\n\tVectorCopy( RI.base_vpn, RI.vforward );\n\tVectorCopy( RI.base_vup, RI.vup );\n\tVectorCopy( RI.base_vright, RI.vright );\n\tR_TransformFrustum();\n}\n\n\n/*\n==============\nD_SolidSurf\n\nNormal surface cached, texture mapped surface\n==============\n*/\nstatic void D_SolidSurf( surf_t *s )\n{\n\td_zistepu = s->d_zistepu;\n\td_zistepv = s->d_zistepv;\n\td_ziorigin = s->d_ziorigin;\n\tif( s->flags & SURF_DRAWSKY )\n\t\treturn;\n\tif( s->flags & SURF_DRAWTURB )\n\t\treturn;\n\n\tif( s->insubmodel )\n\t{\n\t\t// FIXME: we don't want to do all this for every polygon!\n\t\t// TODO: store once at start of frame\n\t\tRI.currententity = s->entity; // FIXME: make this passed in to\n\t\t// R_RotateBmodel ()\n\t\tVectorSubtract( RI.vieworg, RI.currententity->origin, local_modelorg );\n\t\tTransformVector( local_modelorg, transformed_modelorg );\n\n\t\tR_RotateBmodel(); // FIXME: don't mess with the frustum,\n\t\t// make entity passed in\n\t\t// setup dlight transform\n\t\tif( s->msurf && s->msurf->dlightframe == tr.framecount )\n\t\t{\n\t\t\tMatrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 );\n\t\t\ttr.modelviewIdentity = false;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( alphaspans )\n\t\t\treturn;\n\t\tRI.currententity = CL_GetEntityByIndex( 0 ); // r_worldentity;\n\t\ttr.modelviewIdentity = true;\n\t}\n\n\tpface = s->msurf;\n\n\n\tif( !pface )\n\t\treturn;\n\n\tif( pface->flags & SURF_CONVEYOR )\n\t\tmiplevel = 1;\n\telse\n\t\tmiplevel = D_MipLevelForScale( s->nearzi * scale_for_mip );\n\twhile( 1 << miplevel > gEngfuncs.Mod_SampleSizeForFace( pface ))\n\t\tmiplevel--;\n\n\t// FIXME: make this passed in to D_CacheSurface\n\tpcurrentcache = D_CacheSurface( pface, miplevel );\n\n\tcacheblock = (pixel_t *)pcurrentcache->data;\n\tcachewidth = pcurrentcache->width;\n\n\tD_CalcGradients( pface );\n\n\tD_DrawSpans16( s->spans );\n\n\tD_DrawZSpans( s->spans );\n\n\tif( s->insubmodel )\n\t{\n\t\t//\n\t\t// restore the old drawing state\n\t\t// FIXME: we don't want to do this every time!\n\t\t// TODO: speed up\n\t\t//\n\t\tVectorCopy( world_transformed_modelorg,\n\t\t\t    transformed_modelorg );\n\t\tVectorCopy( RI.base_vpn, RI.vforward );\n\t\tVectorCopy( RI.base_vup, RI.vup );\n\t\tVectorCopy( RI.base_vright, RI.vright );\n\t\tR_TransformFrustum();\n\t\tRI.currententity = NULL; // &r_worldentity;\n\t}\n}\n\n/*\n=============\nD_DrawflatSurfaces\n\nTo allow developers to see the polygon carving of the world\n=============\n*/\nstatic void D_DrawflatSurfaces( void )\n{\n\tsurf_t *s;\n\n\tfor( s = &surfaces[1]; s < surface_p; s++ )\n\t{\n\t\tif( !s->spans )\n\t\t\tcontinue;\n\n\t\td_zistepu = s->d_zistepu;\n\t\td_zistepv = s->d_zistepv;\n\t\td_ziorigin = s->d_ziorigin;\n\n\t\t// make a stable color for each surface by taking the low\n\t\t// bits of the msurface pointer\n\t\tD_FlatFillSurface( s, (uintptr_t)s->msurf & 0xFFFF );\n\t\tD_DrawZSpans( s->spans );\n\t}\n}\n\n/*\n==============\nD_DrawSurfaces\n\nRasterize all the span lists.  Guaranteed zero overdraw.\nMay be called more than once a frame if the surf list overflows (higher res)\n==============\n*/\nvoid D_DrawSurfaces( void )\n{\n\tsurf_t *s;\n\n//\tcurrententity = NULL;\t//&r_worldentity;\n\tVectorSubtract( RI.vieworg, vec3_origin, tr.modelorg );\n\tTransformVector( tr.modelorg, transformed_modelorg );\n\tVectorCopy( transformed_modelorg, world_transformed_modelorg );\n\n\tif( !sw_drawflat.value )\n\t{\n\t\tfor( s = &surfaces[1]; s < surface_p; s++ )\n\t\t{\n\t\t\tif( !s->spans )\n\t\t\t\tcontinue;\n\n\t\t\tif( alphaspans )\n\t\t\t\tD_AlphaSurf( s );\n\t\t\telse if( s->flags & SURF_DRAWSKY )\n\t\t\t\tD_BackgroundSurf( s );\n\t\t\telse if( s->flags & SURF_DRAWTURB )\n\t\t\t\tD_TurbulentSurf( s );\n\t\t\telse\n\t\t\t\tD_SolidSurf( s );\n\t\t}\n\t}\n\telse\n\t\tD_DrawflatSurfaces();\n\n\t// RI.currententity = NULL;\t//&r_worldentity;\n\tVectorSubtract( RI.vieworg, vec3_origin, tr.modelorg );\n\tR_TransformFrustum();\n}\n\n"
  },
  {
    "path": "ref/soft/r_glblit.c",
    "content": "#include \"r_local.h\"\n#define APIENTRY_LINKAGE static\n#include \"../gl/gl_export.h\"\n\nstruct swblit_s\n{\n\tuint     stride;\n\tuint     bpp;\n\tuint     rmask, gmask, bmask;\n\tvoid     *(*pLockBuffer)( void );\n\tvoid     (*pUnlockBuffer)( void );\n\tqboolean (*pCreateBuffer)( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b );\n\tuint     rotate;\n\tqboolean gl1;\n} swblit;\n\n\nqboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y )\n{\n\tqboolean ret = true;\n\tif( rotate > 1 )\n\t{\n\t\tgEngfuncs.Con_Printf( \"only 0-1 rotation supported\\n\" );\n\t\tret = false;\n\t}\n\telse\n\t\tswblit.rotate = rotate;\n\n\tif( offset_x || offset_y )\n\t{\n\t\t// it is possible implement for offset > 0\n\t\tgEngfuncs.Con_Printf( \"offset transform not supported\\n\" );\n\t\tret = false;\n\t}\n\n\tif( scale_x != 1.0f || scale_y != 1.0f )\n\t{\n\t\t// maybe implement 2x2?\n\t\tgEngfuncs.Con_Printf( \"scale transform not supported\\n\" );\n\t\tret = false;\n\t}\n\n\treturn ret;\n}\n\n/*\n========================\nDebugCallback\n\nFor ARB_debug_output\n========================\n*/\nstatic void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLuint severity, GLint length, const GLcharARB *message, GLvoid *userParam )\n{\n\tswitch( type )\n\t{\n\tcase GL_DEBUG_TYPE_ERROR_ARB:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_ERROR \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB:\n\tcase GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_WARN \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_PORTABILITY_ARB:\n\t\tgEngfuncs.Con_Reportf( S_OPENGL_WARN \"%s\\n\", message );\n\t\tbreak;\n\tcase GL_DEBUG_TYPE_PERFORMANCE_ARB:\n\tcase GL_DEBUG_TYPE_OTHER_ARB:\n\tdefault:\n\t\tgEngfuncs.Con_Printf( S_OPENGL_NOTE \"%s\\n\", message );\n\t\tbreak;\n\t}\n}\n\n\nstatic unsigned short *glbuf;\nstatic int tex;\n\n#define LOAD( x ) p ## x = gEngfuncs.GL_GetProcAddress(#x ); \\\n\tgEngfuncs.Con_Printf(#x \" : %p\\n\", p ## x )\n\n\nvoid GAME_EXPORT GL_SetupAttributes( int safegl )\n{\n#if GLDEBUG\n\tgEngfuncs.Con_Reportf( \"Creating an extended GL context for debug...\\n\" );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_FLAGS, REF_GL_CONTEXT_DEBUG_FLAG );\n#endif\n\t// untill we have any blitter in ref api, setup GL\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_ES );\n\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_EGL, 1 );\n//\tsafegl=1;\n\tif( safegl )\n\t{\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 );\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 );\n\t\tswblit.gl1 = true;\n\t}\n\telse\n\t{\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 3 );\n\t\tgEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 0 );\n\t}\n\tgEngfuncs.GL_SetAttribute( REF_GL_DOUBLEBUFFER, 1 );\n\n\tgEngfuncs.GL_SetAttribute( REF_GL_RED_SIZE, 5 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_GREEN_SIZE, 6 );\n\tgEngfuncs.GL_SetAttribute( REF_GL_BLUE_SIZE, 5 );\n}\n\nvoid (*pglOrthof)( GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar );\nvoid GL_FUNCTION( glBindBuffer )( GLenum target, GLuint buffer );\n\nvoid GL_FUNCTION( glBufferData )( GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage );\nvoid GL_FUNCTION( glGenBuffers )( GLsizei n, GLuint *buffers );\nvoid GL_FUNCTION( glDeleteBuffers )( GLsizei n, const GLuint *buffers );\nGLvoid * GL_FUNCTION( glMapBufferOES )( GLenum target, GLenum access );\nGLboolean GL_FUNCTION( glUnmapBufferOES )( GLenum target );\n#define GL_PIXEL_UNPACK_BUFFER 0x88EC\n#define GL_FRAMEBUFFER         0x8D40\n#define GL_COLOR_ATTACHMENT0   0x8CE0\n#define GL_READ_FRAMEBUFFER    0x8CA8\n#define GL_DRAW_FRAMEBUFFER    0x8CA9\nvoid GAME_EXPORT GL_InitExtensions( void )\n{\n\tLOAD( glBegin );\n\tLOAD( glEnd );\n\tLOAD( glTexCoord2f );\n\tLOAD( glVertex2f );\n\tLOAD( glEnable );\n\tLOAD( glDisable );\n\tLOAD( glTexImage2D );\n\tLOAD( glOrtho );\n\tLOAD( glOrthof );\n\tLOAD( glMatrixMode );\n\tLOAD( glLoadIdentity );\n\tLOAD( glViewport );\n\tLOAD( glBindTexture );\n\tLOAD( glDebugMessageCallbackARB );\n\tLOAD( glDebugMessageControlARB );\n\tLOAD( glGetError );\n\tLOAD( glGenTextures );\n\tLOAD( glTexParameteri );\n\tLOAD( glEnableClientState );\n\tLOAD( glDisableClientState );\n\tLOAD( glVertexPointer );\n\tLOAD( glTexCoordPointer );\n\tLOAD( glDrawElements );\n\tLOAD( glClear );\n\tLOAD( glClearColor );\n\tLOAD( glGetString );\n\tLOAD( glColor4f );\n\tLOAD( glDrawArrays );\n\tLOAD( glBindBuffer );\n\tLOAD( glBufferData );\n\tLOAD( glGenBuffers );\n\tLOAD( glDeleteBuffers );\n\tLOAD( glMapBufferOES );\n\tif( !pglMapBufferOES )\n\t\tpglMapBufferOES = gEngfuncs.GL_GetProcAddress( \"glMapBuffer\" );\n\tLOAD( glUnmapBufferOES );\n\tif( !pglUnmapBufferOES )\n\t\tpglUnmapBufferOES = gEngfuncs.GL_GetProcAddress( \"glUnmapBuffer\" );\n\tLOAD( glGenFramebuffers );\n\tLOAD( glBindFramebuffer );\n\tLOAD( glFramebufferTexture2D );\n\tLOAD( glBlitFramebuffer );\n\tLOAD( glGenTextures );\n\tgEngfuncs.Con_Printf( \"version:%s\\n\", pglGetString( GL_VERSION ));\n#if GLDEBUG\n\tif( gpGlobals->developer )\n\t{\n\t\tgEngfuncs.Con_Reportf( \"Installing GL_DebugOutput...\\n\" );\n\t\tpglDebugMessageCallbackARB( GL_DebugOutput, NULL );\n\n\t\t// force everything to happen in the main thread instead of in a separate driver thread\n\t\tpglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB );\n\t}\n\n\t// enable all the low priority messages\n\tpglDebugMessageControlARB( GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, true );\n#endif\n\n}\nvoid GAME_EXPORT GL_ClearExtensions( void )\n{\n\n}\n\nstatic void *R_Lock_GL1( void )\n{\n\treturn glbuf;\n}\n\nstatic void R_Unlock_GL1( void )\n{\n\n\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf );\n\t// gEngfuncs.Con_Printf(\"%d\\n\",pglGetError());\n\tpglBegin( GL_QUADS );\n\tpglTexCoord2f( 0, 0 );\n\tpglVertex2f( 0, 0 );\n\n\tpglTexCoord2f( 1, 0 );\n\tpglVertex2f( 1, 0 );\n\n\tpglTexCoord2f( 1, 1 );\n\tpglVertex2f( 1, 1 );\n\n\tpglTexCoord2f( 0, 1 );\n\tpglVertex2f( 0, 1 );\n\tpglEnd();\n\tgEngfuncs.GL_SwapBuffers();\n}\n\n\nstatic void R_Unlock_GLES1( void )\n{\n\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf );\n\tpglDrawArrays( GL_TRIANGLE_FAN, 0, 4 );\n\n\tgEngfuncs.GL_SwapBuffers();\n}\n\nstatic qboolean R_CreateBuffer_GL1( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tpglViewport( 0, 0, width, height );\n\tpglMatrixMode( GL_PROJECTION );\n\tpglLoadIdentity();\n\tpglOrtho( 0, 1, 1, 0, -99999, 99999 );\n\tpglMatrixMode( GL_MODELVIEW );\n\tpglLoadIdentity();\n\n\tpglEnable( GL_TEXTURE_2D );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );\n\n\tif( glbuf )\n\t\tMem_Free( glbuf );\n\n\tglbuf = Mem_Malloc( r_temppool, width * height * 2 );\n\n\t*stride = width;\n\t*bpp = 2;\n\t*r = MASK( 5 ) << ( 6 + 5 );\n\t*g = MASK( 6 ) << 5;\n\t*b = MASK( 5 );\n\n\treturn true;\n}\n\nstatic qboolean R_CreateBuffer_GLES1( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tfloat data[] = {\n\t\t// quad verts match texcoords\n\t\t0, 0,\n\t\t1, 0,\n\t\t1, 1,\n\t\t0, 1,\n\t};\n\tint   vbo;\n\n\tpglViewport( 0, 0, width, height );\n\tpglMatrixMode( GL_PROJECTION );\n\tpglLoadIdentity();\n\t// project 0..1 to screen size\n\tpglOrthof( 0, 1, 1, 0, -99999, 99999 );\n\tpglMatrixMode( GL_MODELVIEW );\n\tpglLoadIdentity();\n\n\tpglEnable( GL_TEXTURE_2D );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );\n\n\t// if( vbo )\n\t//\tpglDeleteBuffers( 1,&vbo );\n\n\tpglGenBuffers( 1, &vbo );\n\tpglBindBuffer( GL_ARRAY_BUFFER_ARB, vbo );\n\tpglBufferData( GL_ARRAY_BUFFER_ARB, sizeof( data ), data, GL_STATIC_DRAW_ARB );\n\n\tpglEnableClientState( GL_VERTEX_ARRAY );\n\tpglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\n\tpglVertexPointer( 2, GL_FLOAT, 8, 0 );\n\tpglTexCoordPointer( 2, GL_FLOAT, 8, 0 );\n\tpglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 );\n\tpglColor4f( 1, 1, 1, 1 );\n\n\n\tif( glbuf )\n\t\tMem_Free( glbuf );\n\n\tglbuf = Mem_Malloc( r_temppool, width * height * 2 );\n\n\t*stride = width;\n\t*bpp = 2;\n\t*r = MASK( 5 ) << ( 6 + 5 );\n\t*g = MASK( 6 ) << 5;\n\t*b = MASK( 5 );\n\n\treturn true;\n}\n\nstatic void *R_Lock_GLES3( void )\n{\n\tvoid *buf = NULL;\n\n\tif( !vid.width || !vid.height )\n\t\treturn NULL;\n\n\tif( glbuf )\n\t\treturn glbuf;\n\n\tpglBufferData( GL_PIXEL_UNPACK_BUFFER, vid.width * vid.height * 2, 0, GL_STREAM_DRAW_ARB );\n\tif( pglMapBufferOES )\n\t\tbuf = pglMapBufferOES( GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY_ARB );\n\tif( !buf )\n\t{\n\t\tif( pglUnmapBufferOES )\n\t\t\tpglUnmapBufferOES( GL_PIXEL_UNPACK_BUFFER );\n\t\tpglBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );\n\t\tglbuf = Mem_Malloc( r_temppool, vid.width * vid.height * 2 );\n\t\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf );\n\t\treturn glbuf;\n\t}\n\telse\n\t\treturn buf;\n}\n\n\nstatic void R_Unlock_GLES3( void )\n{\n\tgEngfuncs.GL_SwapBuffers();\n\tif( glbuf )\n\t\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf );\n\telse\n\t{\n\t\tif( pglUnmapBufferOES )\n\t\t\tpglUnmapBufferOES( GL_PIXEL_UNPACK_BUFFER );\n\t\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0 );\n\t}\n\t// pglDrawArrays( GL_TRIANGLE_FAN, 0,4 );\n\tpglBlitFramebuffer( 0, vid.height, vid.width, 0, 0, 0, vid.width, vid.height, GL_COLOR_BUFFER_BIT, GL_NEAREST );\n}\n\nstatic qboolean R_CreateBuffer_GLES3( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b )\n{\n\tfloat  data[] = {\n\t\t// quad verts match texcoords\n\t\t0, 0,\n\t\t1, 0,\n\t\t1, 1,\n\t\t0, 1,\n\t};\n\tGLuint vbo, pbo, fbo, to;\n\n\t// shitty fbo does not work without texture objects :(\n\tpglGenTextures( 1, &to );\n\tpglBindTexture( GL_TEXTURE_2D, to );\n\tpglViewport( 0, 0, width, height );\n\t/*\n\tpglMatrixMode( GL_PROJECTION );\n\tpglLoadIdentity();\n\t// project 0..1 to screen size\n\tpglOrtho( 0, 1, 1, 0, -99999, 99999 );\n\tpglMatrixMode( GL_MODELVIEW );\n\tpglLoadIdentity();\n\n\tpglEnable( GL_TEXTURE_2D );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );\n\tpglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );\n\n\tif( vbo )\n\t\tpglDeleteBuffers( 1,&vbo );\n\n\tif( pbo )\n\t\tpglDeleteBuffers( 1,&pbo );\n\t*/\n\n\t// pglGenBuffers( 1,&vbo );\n\tpglGenBuffers( 1, &pbo );\n\t// pglBindBuffer( GL_ARRAY_BUFFER_ARB, vbo );\n\t// pglBufferData( GL_ARRAY_BUFFER_ARB, sizeof(data), data, GL_STATIC_DRAW_ARB );\n\n\t// pglEnableClientState( GL_VERTEX_ARRAY );\n\t// pglEnableClientState( GL_TEXTURE_COORD_ARRAY );\n\n\t// pglVertexPointer( 2, GL_FLOAT, 8, 0 );\n\t// pglTexCoordPointer( 2, GL_FLOAT, 8, 0 );\n\t// pglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 );\n\n\tpglBindBuffer( GL_PIXEL_UNPACK_BUFFER, pbo );\n\tpglBufferData( GL_PIXEL_UNPACK_BUFFER, width * height * 2, 0, GL_STREAM_DRAW_ARB );\n\tpglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0 );\n\n\tpglGenFramebuffers( 1, &fbo );\n\tpglBindFramebuffer( GL_READ_FRAMEBUFFER, fbo );\n\tpglFramebufferTexture2D( GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, to, 0 );\n\tpglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );\n\n\t// pglColor4f( 1, 1, 1, 1 );\n\n\n\t*stride = width;\n\t*bpp = 2;\n\t*r = MASK( 5 ) << ( 6 + 5 );\n\t*g = MASK( 6 ) << 5;\n\t*b = MASK( 5 );\n\n\treturn true;\n}\n\n\n\nstatic int FIRST_BIT( uint mask )\n{\n\tuint i;\n\n\tfor( i = 0; !( BIT( i ) & mask ); i++ )\n\t\t;\n\n\treturn i;\n}\n\nstatic int COUNT_BITS( uint mask )\n{\n\tuint i;\n\n\tfor( i = 0; mask; mask = mask >> 1 )\n\t\ti += mask & 1;\n\n\treturn i;\n}\n\nstatic void R_BuildScreenMap( void )\n{\n\tint  i;\n\tuint rshift = FIRST_BIT( swblit.rmask ), gshift = FIRST_BIT( swblit.gmask ), bshift = FIRST_BIT( swblit.bmask );\n\tuint rbits = COUNT_BITS( swblit.rmask ), gbits = COUNT_BITS( swblit.gmask ), bbits = COUNT_BITS( swblit.bmask );\n\tuint rmult = BIT( rbits ), gmult = BIT( gbits ), bmult = BIT( bbits );\n\tuint rdiv = MASK( 5 ), gdiv = MASK( 6 ), bdiv = MASK( 5 );\n\n\tgEngfuncs.Con_Printf( \"Blit table: %d %d %d %d %d %d\\n\", rmult, gmult, bmult, rdiv, gdiv, bdiv );\n\n#ifdef SEPARATE_BLIT\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tunsigned int r, g, b;\n\n\t\t// 332 to 565\n\t\tr = (( i >> ( 8 - 3 )) << 2 ) & MASK( 5 );\n\t\tg = (( i >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 );\n\t\tb = (( i >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 );\n\t\tvid.screen_major[i] = r << ( 6 + 5 ) | ( g << 5 ) | b;\n\n\n\t\t// restore minor GBRGBRGB\n\t\tr = MOVE_BIT( i, 5, 1 ) | MOVE_BIT( i, 2, 0 );\n\t\tg = MOVE_BIT( i, 7, 2 ) | MOVE_BIT( i, 4, 1 ) | MOVE_BIT( i, 1, 0 );\n\t\tb = MOVE_BIT( i, 6, 2 ) | MOVE_BIT( i, 3, 1 ) | MOVE_BIT( i, 0, 0 );\n\t\tvid.screen_minor[i] = r << ( 6 + 5 ) | ( g << 5 ) | b;\n\n\t}\n#else\n\tfor( i = 0; i < 256; i++ )\n\t{\n\t\tunsigned int r, g, b, major, j;\n\n\t\t// 332 to 565\n\t\tr = (( i >> ( 8 - 3 )) << 2 ) & MASK( 5 );\n\t\tg = (( i >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 );\n\t\tb = (( i >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 );\n\t\t// major = r << (6 + 5) | (g << 5) | b;\n\t\tmajor = ( r * rmult / rdiv ) << rshift | ( g * gmult / gdiv ) << gshift | ( b * bmult / bdiv ) << bshift;\n\n\n\t\tfor( j = 0; j < 256; j++ )\n\t\t{\n\t\t\tuint minor;\n\t\t\t// restore minor GBRGBRGB\n\t\t\tr = MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 );\n\t\t\tg = MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 );\n\t\t\tb = MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 );\n\t\t\t// vid.screen[(i<<8)|j] = r << (6 + 5) | (g << 5) | b | major;\n\t\t\tminor = ( r * rmult / rdiv ) << rshift | ( g * gmult / gdiv ) << gshift | ( b * bmult / bdiv ) << bshift;\n\n\t\t\tif( swblit.bpp == 2 )\n\t\t\t\tvid.screen[( i << 8 ) | j] = major | minor;\n\t\t\telse\n\t\t\t\tvid.screen32[( i << 8 ) | j] = major | minor;\n\n\t\t}\n\n\t}\n#endif\n}\n\n#define FOR_EACH_COLOR( x ) for( r ## x = 0; r ## x < BIT( 3 ); r ## x++ ) for( g ## x = 0; g ## x < BIT( 3 ); g ## x++ ) for( b ## x = 0; b ## x < BIT( 2 ); b ## x++ )\n\nstatic void R_BuildBlendMaps( void )\n{\n\tunsigned int r1, g1, b1;\n\tunsigned int r2, g2, b2;\n\tunsigned int i, j;\n\n\tFOR_EACH_COLOR( 1 ) FOR_EACH_COLOR( 2 )\n\t{\n\t\tunsigned int   r, g, b;\n\t\tunsigned short index1 = r1 << ( 2 + 3 ) | g1 << 2 | b1;\n\t\tunsigned short index2 = ( r2 << ( 2 + 3 ) | g2 << 2 | b2 ) << 8;\n\t\tunsigned int   a;\n\n\t\tr = r1 + r2;\n\t\tg = g1 + g2;\n\t\tb = b1 + b2;\n\t\tif( r > MASK( 3 ))\n\t\t\tr = MASK( 3 );\n\t\tif( g > MASK( 3 ))\n\t\t\tg = MASK( 3 );\n\t\tif( b > MASK( 2 ))\n\t\t\tb = MASK( 2 );\n\t\tASSERT( !vid.addmap[index2 | index1] );\n\n\t\tvid.addmap[index2 | index1] = r << ( 2 + 3 ) | g << 2 | b;\n\t\tr = r1 * r2 / MASK( 3 );\n\t\tg = g1 * g2 / MASK( 3 );\n\t\tb = b1 * b2 / MASK( 2 );\n\n\t\tvid.modmap[index2 | index1] = r << ( 2 + 3 ) | g << 2 | b;\n\t}\n\tfor( i = 0; i < 8192; i++ )\n\t{\n\t\tunsigned int   r, g, b;\n\t\tuint           color = i << 3;\n\t\tuint           m = color >> 8;\n\t\tuint           j = color & 0xff;\n\t\tunsigned short index1 = i;\n\n\t\tr1 = (( m >> ( 8 - 3 )) << 2 ) & MASK( 5 );\n\t\tg1 = (( m >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 );\n\t\tb1 = (( m >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 );\n\t\tr1 |= MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 );\n\t\tg1 |= MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 );\n\t\tb1 |= MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 );\n\n\n\t\tfor( j = 0; j < 32; j++ )\n\t\t{\n\t\t\tunsigned int index2 = j << 13;\n\t\t\tunsigned int major, minor;\n\t\t\tr = r1 * j / 32;\n\t\t\tg = g1 * j / 32;\n\t\t\tb = b1 * j / 32;\n\t\t\tmajor = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 )));\n\n\t\t\t// save minor GBRGBRGB\n\t\t\tminor = MOVE_BIT( r, 1, 5 ) | MOVE_BIT( r, 0, 2 ) | MOVE_BIT( g, 2, 7 ) | MOVE_BIT( g, 1, 4 ) | MOVE_BIT( g, 0, 1 ) | MOVE_BIT( b, 2, 6 ) | MOVE_BIT( b, 1, 3 ) | MOVE_BIT( b, 0, 0 );\n\n\t\t\tvid.colormap[index2 | index1] = major << 8 | ( minor & 0xFF );\n\t\t}\n\t}\n\n\tfor( i = 0; i < 1024; i++ )\n\t{\n\t\tunsigned int   r, g, b;\n\t\tuint           color = i << 6 | BIT( 5 ) | BIT( 4 ) | BIT( 3 );\n\t\tuint           m = color >> 8;\n\t\tuint           j = color & 0xff;\n\t\tunsigned short index1 = i;\n\n\t\tr1 = (( m >> ( 8 - 3 )) << 2 ) & MASK( 5 );\n\t\tg1 = (( m >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 );\n\t\tb1 = (( m >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 );\n\t\tr1 |= MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 );\n\t\tg1 |= MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 );\n\t\tb1 |= MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 );\n\n\n\t\tFOR_EACH_COLOR( 2 )\n\t\t{\n\t\t\tunsigned int index2 = ( r2 << ( 2 + 3 ) | g2 << 2 | b2 ) << 10;\n\t\t\tunsigned int k;\n\t\t\tfor( k = 0; k < 3; k++ )\n\t\t\t{\n\t\t\t\tunsigned int major, minor;\n\t\t\t\tunsigned int a = k + 2;\n\n\n\t\t\t\tr = r1 * ( 7 - a ) / 7 + ( r2 << 2 | BIT( 2 )) * a / 7;\n\t\t\t\tg = g1 * ( 7 - a ) / 7 + ( g2 << 3 | MASK( 2 )) * a / 7;\n\t\t\t\tb = b1 * ( 7 - a ) / 7 + ( b2 << 3 | MASK( 2 )) * a / 7;\n\t\t\t\tif( r > MASK( 5 ))\n\t\t\t\t\tr = MASK( 5 );\n\t\t\t\tif( g > MASK( 6 ))\n\t\t\t\t\tg = MASK( 6 );\n\t\t\t\tif( b > MASK( 5 ))\n\t\t\t\t\tb = MASK( 5 );\n\n\n\t\t\t\tASSERT( b < 32 );\n\t\t\t\tmajor = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 )));\n\n\t\t\t\t// save minor GBRGBRGB\n\t\t\t\tminor = MOVE_BIT( r, 1, 5 ) | MOVE_BIT( r, 0, 2 ) | MOVE_BIT( g, 2, 7 ) | MOVE_BIT( g, 1, 4 ) | MOVE_BIT( g, 0, 1 ) | MOVE_BIT( b, 2, 6 ) | MOVE_BIT( b, 1, 3 ) | MOVE_BIT( b, 0, 0 );\n\t\t\t\tminor = minor & ~0x3f;\n\n\t\t\t\tvid.alphamap[k << 18 | index2 | index1] = major << 8 | ( minor & 0xFF );\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic qboolean R_AllocScreen( void );\n\nqboolean R_InitBlit( qboolean glblit )\n{\n\tR_BuildBlendMaps();\n\n\tif( glblit && swblit.gl1 )\n\t{\n\t\tswblit.pLockBuffer = R_Lock_GL1;\n\t\tswblit.pUnlockBuffer = R_Unlock_GLES1;\n\t\tswblit.pCreateBuffer = R_CreateBuffer_GLES1;\n\t}\n\telse if( glblit )\n\t{\n\t\tswblit.pLockBuffer = R_Lock_GLES3;\n\t\tswblit.pUnlockBuffer = R_Unlock_GLES3;\n\t\tswblit.pCreateBuffer = R_CreateBuffer_GLES3;\n\t}\n\telse\n\t{\n\t\tswblit.pLockBuffer = gEngfuncs.SW_LockBuffer;\n\t\tswblit.pUnlockBuffer = gEngfuncs.SW_UnlockBuffer;\n\t\tswblit.pCreateBuffer = gEngfuncs.SW_CreateBuffer;\n\t}\n\n\treturn R_AllocScreen();\n}\n\nstatic qboolean R_AllocScreen( void )\n{\n\tint w, h;\n\n\tif( gpGlobals->width < 128 )\n\t\tgpGlobals->width = 128;\n\tif( gpGlobals->height < 128 )\n\t\tgpGlobals->height = 128;\n\n\tR_InitCaches();\n\n\tif( swblit.rotate )\n\t{\n\t\tw = gpGlobals->height;\n\t\th = gpGlobals->width;\n\t}\n\telse\n\t{\n\t\tw = gpGlobals->width;\n\t\th = gpGlobals->height;\n\t}\n\n\tif( !swblit.pCreateBuffer( w, h, &swblit.stride, &swblit.bpp,\n\t\t\t\t   &swblit.rmask, &swblit.gmask, &swblit.bmask ))\n\t\treturn false;\n\n\tR_BuildScreenMap();\n\tvid.width = gpGlobals->width;\n\tvid.height = gpGlobals->height;\n\tvid.rowbytes = gpGlobals->width; // rowpixels\n\tif( d_pzbuffer )\n\t\tfree( d_pzbuffer );\n\td_pzbuffer = malloc( vid.width * vid.height * 2 + 64 );\n\n\tif( vid.buffer )\n\t\tfree( vid.buffer );\n\tvid.buffer = malloc( vid.width * vid.height * sizeof( pixel_t ));\n\n\treturn true;\n}\n\nvoid R_BlitScreen( void )\n{\n\tint  u, v;\n\tvoid *buffer = swblit.pLockBuffer();\n//\tgEngfuncs.Con_Printf(\"blit begin\\n\");\n\t// memset( vid.buffer, 10, vid.width * vid.height );\n\n\tif( !buffer || gpGlobals->width != vid.width || gpGlobals->height != vid.height )\n\t{\n\t\tgEngfuncs.Con_Printf( \"pre allocscrn\\n\" );\n\t\tR_AllocScreen();\n\t\tgEngfuncs.Con_Printf( \"post allocscrn\\n\" );\n\t\treturn;\n\t}\n\t// return;\n\t// byte *buf = vid.buffer;\n\n\t// #pragma omp parallel for schedule(static)\n\t// gEngfuncs.Con_Printf(\"swblit %d %d\", swblit.bpp, vid.height );\n\tif( swblit.rotate )\n\t{\n\t\tif( swblit.bpp == 2 )\n\t\t{\n\t\t\tunsigned short *pbuf = buffer;\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint d = swblit.stride - v - 1;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[d] = s;\n\t\t\t\t\td += swblit.stride;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if( swblit.bpp == 4 )\n\t\t{\n\t\t\tunsigned int *pbuf = buffer;\n\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint d = swblit.stride - v - 1;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen32[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[d] = s;\n\t\t\t\t\td += swblit.stride;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if( swblit.bpp == 3 )\n\t\t{\n\t\t\tbyte *pbuf = buffer;\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint d = swblit.stride - v - 1;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen32[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[( d ) * 3] = s;\n\t\t\t\t\ts = s >> 8;\n\t\t\t\t\tpbuf[( d ) * 3 + 1] = s;\n\t\t\t\t\ts = s >> 8;\n\t\t\t\t\tpbuf[( d ) * 3 + 2] = s;\n\t\t\t\t\td += swblit.stride;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( swblit.bpp == 2 )\n\t\t{\n\t\t\tunsigned short *pbuf = buffer;\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint dstart = swblit.stride * v;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[dstart + u] = s;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if( swblit.bpp == 4 )\n\t\t{\n\t\t\tunsigned int *pbuf = buffer;\n\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint dstart = swblit.stride * v;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen32[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[dstart + u] = s;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if( swblit.bpp == 3 )\n\t\t{\n\t\t\tbyte *pbuf = buffer;\n\t\t\tfor( v = 0; v < vid.height; v++ )\n\t\t\t{\n\t\t\t\tuint start = vid.rowbytes * v;\n\t\t\t\tuint dstart = swblit.stride * v;\n\n\t\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t\t{\n\t\t\t\t\tunsigned int s = vid.screen32[vid.buffer[start + u]];\n\t\t\t\t\tpbuf[( dstart + u ) * 3] = s;\n\t\t\t\t\ts = s >> 8;\n\t\t\t\t\tpbuf[( dstart + u ) * 3 + 1] = s;\n\t\t\t\t\ts = s >> 8;\n\t\t\t\t\tpbuf[( dstart + u ) * 3 + 2] = s;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tswblit.pUnlockBuffer();\n//\tgEngfuncs.Con_Printf(\"blit end\\n\");\n}\n\nstatic uint32_t Get8888PixelAt( int u, int start )\n{\n\tuint32_t s;\n\tswitch( swblit.bpp )\n\t{\n\tcase 2:\n\t{\n\t\tpixel_t color = vid.screen[vid.buffer[start + u]];\n\t\tuint8_t c[3];\n\t\tc[0] = (((( color >> 11 ) & 0x1F ) * 527 ) + 23 ) >> 6;\n\t\tc[1] = (((( color >> 5 ) & 0x3F ) * 259 ) + 33 ) >> 6;\n\t\tc[2] = (((( color ) & 0x1F ) * 527 ) + 23 ) >> 6;\n\n\t\ts = c[0] << 16 | c[1] << 8 | c[2];\n\t\tbreak;\n\t}\n\tcase 3:\n\tcase 4:\n\tdefault:\n\t\ts = vid.screen32[vid.buffer[start + u]];\n\t\tbreak;\n\t}\n\treturn s | 0xFF000000;\n}\n\nqboolean GAME_EXPORT VID_ScreenShot( const char *filename, int shot_type )\n{\n\trgbdata_t *r_shot;\n\tuint      flags = IMAGE_FLIP_Y;\n\tint       width = 0, height = 0, u, v;\n\tqboolean  result;\n\n\tr_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));\n\tr_shot->width = ( vid.width + 3 ) & ~3;\n\tr_shot->height = ( vid.height + 3 ) & ~3;\n\tr_shot->flags = IMAGE_HAS_COLOR;\n\tr_shot->type = PF_BGRA_32; // was RGBA\n\tr_shot->size = r_shot->width * r_shot->height * gEngfuncs.Image_GetPFDesc( r_shot->type )->bpp;\n\tr_shot->palette = NULL;\n\tr_shot->buffer = Mem_Malloc( r_temppool, r_shot->size );\n\n\t// get screen frame\n\tif( swblit.rotate )\n\t{\n\t\tuint32_t *pbuf = (uint32_t *)r_shot->buffer;\n\n\t\tfor( v = 0; v < vid.height; v++ )\n\t\t{\n\t\t\tuint start = vid.rowbytes * ( vid.height - v );\n\t\t\tuint d = swblit.stride - v - 1;\n\n\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t{\n\t\t\t\tpbuf[d] = Get8888PixelAt( u, start );\n\t\t\t\td += swblit.stride;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tuint32_t *pbuf = (uint32_t *)r_shot->buffer;\n\n\t\tfor( v = 0; v < vid.height; v++ )\n\t\t{\n\t\t\tuint start = vid.rowbytes * ( vid.height - v );\n\t\t\tuint dstart = swblit.stride * v;\n\n\t\t\tfor( u = 0; u < vid.width; u++ )\n\t\t\t{\n\t\t\t\tpbuf[dstart + u] = Get8888PixelAt( u, start );\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch( shot_type )\n\t{\n\tcase VID_SCREENSHOT:\n\t\tbreak;\n\tcase VID_SNAPSHOT:\n\t\tgEngfuncs.fsapi->AllowDirectPaths( true );\n\t\tbreak;\n\tcase VID_LEVELSHOT:\n\tcase VID_MINISHOT:\n\t\tflags |= IMAGE_RESAMPLE;\n\t\theight = shot_type == VID_MINISHOT ? 200 : 480;\n\t\twidth = Q_rint( height * ((double)r_shot->width / r_shot->height ));\n\t\tbreak;\n\tcase VID_MAPSHOT:\n\t\tflags |= IMAGE_RESAMPLE | IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format\n\t\theight = 768;\n\t\twidth = 1024;\n\t\tbreak;\n\t}\n\n\tgEngfuncs.Image_Process( &r_shot, width, height, flags, 0.0f );\n\n\t// write image\n\tresult = gEngfuncs.FS_SaveImage( filename, r_shot );\n\tgEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot\n\tgEngfuncs.FS_FreeImage( r_shot );\n\n\treturn result;\n}\n\n"
  },
  {
    "path": "ref/soft/r_image.c",
    "content": "/*\ngl_image.c - texture uploading and processing\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n\n#define TEXTURES_HASH_SIZE ( MAX_TEXTURES >> 2 )\n\nstatic image_t r_images[MAX_TEXTURES];\nstatic image_t *r_imagesHashTable[TEXTURES_HASH_SIZE];\nstatic uint    r_numImages;\n\n#define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE ))\n/*\n=================\nR_GetTexture\n\nacess to array elem\n=================\n*/\nimage_t *R_GetTexture( unsigned int texnum )\n{\n\tASSERT( texnum >= 0 && texnum < MAX_TEXTURES );\n\treturn &r_images[texnum];\n}\n\n/*\n=================\nGL_Bind\n=================\n*/\nvoid GAME_EXPORT GL_Bind( int tmu, unsigned int texnum )\n{\n\timage_t *image;\n\n\timage = &r_images[texnum];\n\t// vid.rendermode = kRenderNormal;\n\n\tif( vid.rendermode == kRenderNormal )\n\t{\n\t\tr_affinetridesc.pskin = image->pixels[0];\n\t\td_pdrawspans = R_PolysetFillSpans8;\n\t}\n\telse if( vid.rendermode == kRenderTransAdd )\n\t{\n\t\tr_affinetridesc.pskin = image->pixels[0];\n\t\td_pdrawspans = R_PolysetDrawSpansAdditive;\n\t}\n\telse if( vid.rendermode == kRenderGlow )\n\t{\n\t\tr_affinetridesc.pskin = image->pixels[0];\n\t\td_pdrawspans = R_PolysetDrawSpansGlow;\n\t}\n\telse if( image->alpha_pixels )\n\t{\n\t\tr_affinetridesc.pskin = image->alpha_pixels;\n\t\td_pdrawspans = R_PolysetDrawSpansTextureBlended;\n\t}\n\telse\n\t{\n\t\tr_affinetridesc.pskin = image->pixels[0];\n\t\td_pdrawspans = R_PolysetDrawSpansBlended;\n\t}\n\n\tr_affinetridesc.skinwidth = image->width;\n\tr_affinetridesc.skinheight = image->height;\n}\n\n/*\n=================\nGL_ApplyTextureParams\n=================\n*/\nvoid GL_ApplyTextureParams( image_t *tex )\n{\n\n\tAssert( tex != NULL );\n}\n\n/*\n=================\nGL_UpdateTextureParams\n=================\n*/\nstatic void GL_UpdateTextureParams( int iTexture )\n{\n\timage_t *tex = &r_images[iTexture];\n\n\tAssert( tex != NULL );\n\n\tif( !tex->pixels )\n\t\treturn;           // free slot\n\n\tGL_Bind( XASH_TEXTURE0, iTexture );\n}\n\n/*\n=================\nR_SetTextureParameters\n=================\n*/\nvoid R_SetTextureParameters( void )\n{\n\tint i;\n\n\t// change all the existing mipmapped texture objects\n\tfor( i = 0; i < r_numImages; i++ )\n\t\tGL_UpdateTextureParams( i );\n}\n\n\n/*\n==================\nGL_CalcImageSize\n==================\n*/\nstatic size_t GL_CalcImageSize( pixformat_t format, int width, int height, int depth )\n{\n\tsize_t size = 0;\n\n\t// check the depth error\n\tdepth = Q_max( 1, depth );\n\n\tswitch( format )\n\t{\n\tcase PF_RGB_24:\n\tcase PF_BGR_24:\n\t\tsize = width * height * depth * 3;\n\t\tbreak;\n\tcase PF_BGRA_32:\n\tcase PF_RGBA_32:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase PF_DXT1:\n\t\tsize = ((( width + 3 ) >> 2 ) * (( height + 3 ) >> 2 ) * 8 ) * depth;\n\t\tbreak;\n\tcase PF_DXT3:\n\tcase PF_DXT5:\n\tcase PF_ATI2:\n\t\tsize = ((( width + 3 ) >> 2 ) * (( height + 3 ) >> 2 ) * 16 ) * depth;\n\t\tbreak;\n\t}\n\n\treturn size;\n}\n\n/*\n==================\nGL_CalcTextureSize\n==================\n*/\nstatic size_t GL_CalcTextureSize( int width, int height, int depth )\n{\n\treturn width * height * 2;\n}\n\nstatic int GL_CalcMipmapCount( image_t *tex, qboolean haveBuffer )\n{\n\tint width, height;\n\tint mipcount;\n\n\tAssert( tex != NULL );\n\n\tif( !haveBuffer )\n\t\treturn 1;\n\n\t// generate mip-levels by user request\n\tif( FBitSet( tex->flags, TF_NOMIPMAP ))\n\t\treturn 1;\n\n\t// mip-maps can't exceeds 4\n\tfor( mipcount = 0; mipcount < 4; mipcount++ )\n\t{\n\t\twidth = Q_max( 1, ( tex->width >> mipcount ));\n\t\theight = Q_max( 1, ( tex->height >> mipcount ));\n\t\tif( width == 1 && height == 1 )\n\t\t\tbreak;\n\t}\n\n\treturn mipcount + 1;\n}\n\n/*\n================\nGL_SetTextureDimensions\n================\n*/\nstatic void GL_SetTextureDimensions( image_t *tex, int width, int height, int depth )\n{\n\tint maxTextureSize = 1024;\n\tint maxDepthSize = 1;\n\n\tAssert( tex != NULL );\n\n\t// store original sizes\n\ttex->srcWidth = width;\n\ttex->srcHeight = height;\n\n\tif( width > maxTextureSize || height > maxTextureSize || depth > maxDepthSize )\n\t{\n\t\twhile( width > maxTextureSize || height > maxTextureSize )\n\t\t{\n\t\t\twidth >>= 1;\n\t\t\theight >>= 1;\n\t\t}\n\t}\n\n\t// set the texture dimensions\n\ttex->width = Q_max( 1, width );\n\ttex->height = Q_max( 1, height );\n\ttex->depth = Q_max( 1, depth );\n}\n\n/*\n===============\nGL_SetTextureTarget\n===============\n*/\nstatic void GL_SetTextureTarget( image_t *tex, rgbdata_t *pic )\n{\n\tAssert( pic != NULL );\n\tAssert( tex != NULL );\n\n\t// correct depth size\n\tpic->depth = Q_max( 1, pic->depth );\n\ttex->numMips = 0; // begin counting\n\n\t// correct mip count\n\tpic->numMips = Q_max( 1, pic->numMips );\n}\n\n/*\n===============\nGL_SetTextureFormat\n===============\n*/\nstatic void GL_SetTextureFormat( image_t *tex, pixformat_t format, int channelMask )\n{\n\tqboolean haveColor = ( channelMask & IMAGE_HAS_COLOR );\n\tqboolean haveAlpha = ( channelMask & IMAGE_HAS_ALPHA );\n\n\tAssert( tex != NULL );\n\t// tex->transparent = !!( channelMask & IMAGE_HAS_ALPHA );\n}\n\n/*\n=================\nGL_ResampleTexture\n\nAssume input buffer is RGBA\n=================\n*/\nbyte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int outWidth, int outHeight, qboolean isNormalMap )\n{\n\tuint        frac, fracStep;\n\tuint        *in = (uint *)source;\n\tuint        p1[0x1000], p2[0x1000];\n\tbyte        *pix1, *pix2, *pix3, *pix4;\n\tuint        *out, *inRow1, *inRow2;\n\tstatic byte *scaledImage = NULL;        // pointer to a scaled image\n\tvec3_t      normal;\n\tint         i, x, y;\n\n\tif( !source )\n\t\treturn NULL;\n\n\tscaledImage = Mem_Realloc( r_temppool, scaledImage, outWidth * outHeight * 4 );\n\tfracStep = inWidth * 0x10000 / outWidth;\n\tout = (uint *)scaledImage;\n\n\tfrac = fracStep >> 2;\n\tfor( i = 0; i < outWidth; i++ )\n\t{\n\t\tp1[i] = 4 * ( frac >> 16 );\n\t\tfrac += fracStep;\n\t}\n\n\tfrac = ( fracStep >> 2 ) * 3;\n\tfor( i = 0; i < outWidth; i++ )\n\t{\n\t\tp2[i] = 4 * ( frac >> 16 );\n\t\tfrac += fracStep;\n\t}\n\n\tif( isNormalMap )\n\t{\n\t\tfor( y = 0; y < outHeight; y++, out += outWidth )\n\t\t{\n\t\t\tinRow1 = in + inWidth * (int)(((float)y + 0.25f ) * inHeight / outHeight );\n\t\t\tinRow2 = in + inWidth * (int)(((float)y + 0.75f ) * inHeight / outHeight );\n\n\t\t\tfor( x = 0; x < outWidth; x++ )\n\t\t\t{\n\t\t\t\tpix1 = (byte *)inRow1 + p1[x];\n\t\t\t\tpix2 = (byte *)inRow1 + p2[x];\n\t\t\t\tpix3 = (byte *)inRow2 + p1[x];\n\t\t\t\tpix4 = (byte *)inRow2 + p2[x];\n\n\t\t\t\tnormal[0] = MAKE_SIGNED( pix1[0] ) + MAKE_SIGNED( pix2[0] ) + MAKE_SIGNED( pix3[0] ) + MAKE_SIGNED( pix4[0] );\n\t\t\t\tnormal[1] = MAKE_SIGNED( pix1[1] ) + MAKE_SIGNED( pix2[1] ) + MAKE_SIGNED( pix3[1] ) + MAKE_SIGNED( pix4[1] );\n\t\t\t\tnormal[2] = MAKE_SIGNED( pix1[2] ) + MAKE_SIGNED( pix2[2] ) + MAKE_SIGNED( pix3[2] ) + MAKE_SIGNED( pix4[2] );\n\n\t\t\t\tif( !VectorNormalizeLength( normal ))\n\t\t\t\t\tVectorSet( normal, 0.5f, 0.5f, 1.0f );\n\n\t\t\t\t((byte *)( out + x ))[0] = 128 + (byte)( 127.0f * normal[0] );\n\t\t\t\t((byte *)( out + x ))[1] = 128 + (byte)( 127.0f * normal[1] );\n\t\t\t\t((byte *)( out + x ))[2] = 128 + (byte)( 127.0f * normal[2] );\n\t\t\t\t((byte *)( out + x ))[3] = 255;\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( y = 0; y < outHeight; y++, out += outWidth )\n\t\t{\n\t\t\tinRow1 = in + inWidth * (int)(((float)y + 0.25f ) * inHeight / outHeight );\n\t\t\tinRow2 = in + inWidth * (int)(((float)y + 0.75f ) * inHeight / outHeight );\n\n\t\t\tfor( x = 0; x < outWidth; x++ )\n\t\t\t{\n\t\t\t\tpix1 = (byte *)inRow1 + p1[x];\n\t\t\t\tpix2 = (byte *)inRow1 + p2[x];\n\t\t\t\tpix3 = (byte *)inRow2 + p1[x];\n\t\t\t\tpix4 = (byte *)inRow2 + p2[x];\n\n\t\t\t\t((byte *)( out + x ))[0] = ( pix1[0] + pix2[0] + pix3[0] + pix4[0] ) >> 2;\n\t\t\t\t((byte *)( out + x ))[1] = ( pix1[1] + pix2[1] + pix3[1] + pix4[1] ) >> 2;\n\t\t\t\t((byte *)( out + x ))[2] = ( pix1[2] + pix2[2] + pix3[2] + pix4[2] ) >> 2;\n\t\t\t\t((byte *)( out + x ))[3] = ( pix1[3] + pix2[3] + pix3[3] + pix4[3] ) >> 2;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn scaledImage;\n}\n\n/*\n=================\nGL_BoxFilter3x3\n\nbox filter 3x3\n=================\n*/\nstatic void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y )\n{\n\tint        r = 0, g = 0, b = 0, a = 0;\n\tint        count = 0, acount = 0;\n\tint        i, j, u, v;\n\tconst byte *pixel;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tu = ( i - 1 ) + x;\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tv = ( j - 1 ) + y;\n\n\t\t\tif( u >= 0 && u < w && v >= 0 && v < h )\n\t\t\t{\n\t\t\t\tpixel = &in[( u + v * w ) * 4];\n\n\t\t\t\tif( pixel[3] != 0 )\n\t\t\t\t{\n\t\t\t\t\tr += pixel[0];\n\t\t\t\t\tg += pixel[1];\n\t\t\t\t\tb += pixel[2];\n\t\t\t\t\ta += pixel[3];\n\t\t\t\t\tacount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif( acount == 0 )\n\t\tacount = 1;\n\n\tout[0] = r / acount;\n\tout[1] = g / acount;\n\tout[2] = b / acount;\n//\tout[3] = (int)( SimpleSpline( ( a / 12.0f ) / 255.0f ) * 255 );\n}\n\n/*\n=================\nGL_ApplyFilter\n\nApply box-filter to 1-bit alpha\n=================\n*/\nstatic byte *GL_ApplyFilter( const byte *source, int width, int height )\n{\n\tbyte *in = (byte *)source;\n\tbyte *out = (byte *)source;\n\tint  i;\n\n\tif( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))\n\t\treturn in;\n\n\tfor( i = 0; source && i < width * height; i++, in += 4 )\n\t{\n\t\tif( in[0] == 0 && in[1] == 0 && in[2] == 0 && in[3] == 0 )\n\t\t\tGL_BoxFilter3x3( in, source, width, height, i % width, i / width );\n\t}\n\n\treturn out;\n}\n\n/*\n=================\nGL_BuildMipMap\n\nOperates in place, quartering the size of the texture\n=================\n*/\nstatic void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags )\n{\n\tbyte   *out = in;\n\tint    instride = ALIGN( srcWidth * 4, 1 );\n\tint    mipWidth, mipHeight, outpadding;\n\tint    row, x, y, z;\n\tvec3_t normal;\n\n\tif( !in )\n\t\treturn;\n\n\tmipWidth = Q_max( 1, ( srcWidth >> 1 ));\n\tmipHeight = Q_max( 1, ( srcHeight >> 1 ));\n\toutpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4;\n\trow = srcWidth << 2;\n\n\tif( FBitSet( flags, TF_ALPHACONTRAST ))\n\t{\n\t\tmemset( in, mipWidth, mipWidth * mipHeight * 4 );\n\t\treturn;\n\t}\n\n\t// move through all layers\n\tfor( z = 0; z < srcDepth; z++ )\n\t{\n\t\tif( FBitSet( flags, TF_NORMALMAP ))\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row + 0] ) + MAKE_SIGNED( in[row + 4] )\n\t\t\t\t\t\t\t    + MAKE_SIGNED( next[row + 0] ) + MAKE_SIGNED( next[row + 4] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row + 1] ) + MAKE_SIGNED( in[row + 5] )\n\t\t\t\t\t\t\t    + MAKE_SIGNED( next[row + 1] ) + MAKE_SIGNED( next[row + 5] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row + 2] ) + MAKE_SIGNED( in[row + 6] )\n\t\t\t\t\t\t\t    + MAKE_SIGNED( next[row + 2] ) + MAKE_SIGNED( next[row + 6] );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row + 0] ) + MAKE_SIGNED( next[row + 0] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row + 1] ) + MAKE_SIGNED( next[row + 1] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row + 2] ) + MAKE_SIGNED( next[row + 2] );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( !VectorNormalizeLength( normal ))\n\t\t\t\t\t\tVectorSet( normal, 0.5f, 0.5f, 1.0f );\n\n\t\t\t\t\tout[0] = 128 + (byte)( 127.0f * normal[0] );\n\t\t\t\t\tout[1] = 128 + (byte)( 127.0f * normal[1] );\n\t\t\t\t\tout[2] = 128 + (byte)( 127.0f * normal[2] );\n\t\t\t\t\tout[3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = ( in[row + 0] + in[row + 4] + next[row + 0] + next[row + 4] ) >> 2;\n\t\t\t\t\t\tout[1] = ( in[row + 1] + in[row + 5] + next[row + 1] + next[row + 5] ) >> 2;\n\t\t\t\t\t\tout[2] = ( in[row + 2] + in[row + 6] + next[row + 2] + next[row + 6] ) >> 2;\n\t\t\t\t\t\tout[3] = ( in[row + 3] + in[row + 7] + next[row + 3] + next[row + 7] ) >> 2;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = ( in[row + 0] + next[row + 0] ) >> 1;\n\t\t\t\t\t\tout[1] = ( in[row + 1] + next[row + 1] ) >> 1;\n\t\t\t\t\t\tout[2] = ( in[row + 2] + next[row + 2] ) >> 1;\n\t\t\t\t\t\tout[3] = ( in[row + 3] + next[row + 3] ) >> 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nGL_UploadTexture\n\nupload texture into video memory\n===============\n*/\nstatic qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic )\n{\n\tbyte       *buf, *data;\n\tsize_t     texsize, size;\n\tuint       width, height;\n\tuint       i, j, numSides;\n\tuint       offset = 0;\n\tqboolean   normalMap = false;\n\tconst byte *bufend;\n\tint        mipCount;\n\n\ttex->fogParams[0] = pic->fogParams[0];\n\ttex->fogParams[1] = pic->fogParams[1];\n\ttex->fogParams[2] = pic->fogParams[2];\n\ttex->fogParams[3] = pic->fogParams[3];\n\tGL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth );\n\tGL_SetTextureFormat( tex, pic->type, pic->flags );\n\n\t// gEngfuncs.Con_Printf(\"%s %d %d\\n\", tex->name, tex->width, tex->height );\n\n\tAssert( pic != NULL );\n\tAssert( tex != NULL );\n\n\tif( !pic->buffer )\n\t\treturn true;\n\n\tbuf = pic->buffer;\n\n\tmipCount = 4; // GL_CalcMipmapCount( tex, ( buf != NULL ));\n\n\t// NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides\n\tif((( pic->width != tex->width ) || ( pic->height != tex->height )))\n\t\tdata = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap );\n\telse\n\t\tdata = buf;\n\n\t// if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))\n\t//\tdata = GL_ApplyFilter( data, tex->width, tex->height );\n\n\t// mips will be auto-generated if desired\n\tfor( j = 0; j < mipCount; j++ )\n\t{\n\t\tint x, y;\n\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\ttexsize = GL_CalcTextureSize( width, height, tex->depth );\n\t\tsize = GL_CalcImageSize( pic->type, width, height, tex->depth );\n\t\t// GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data );\n\t\t// increase size to workaround triangle renderer bugs\n\t\t// it seems to assume memory readable. maybe it was pointed to WAD?\n\t\t// tex->pixels[j] = (byte*)Mem_Calloc( r_temppool, width * height * sizeof(pixel_t) + 1024 ) + 512;\n\t\ttex->pixels[j] = (pixel_t *)Mem_Calloc( r_temppool, width * height * sizeof( pixel_t ));\n\n\n\t\t// memset( (byte*)tex->pixels[j] - 512, 0xFF, 512 );\n\t\t// memset( (byte*)tex->pixels[j] + width * height * sizeof(pixel_t), 0xFF, 512 );\n\n\t\tif( j == 0 && tex->flags & TF_HAS_ALPHA )\n\t\t\ttex->alpha_pixels = (pixel_t *)Mem_Calloc( r_temppool, width * height * sizeof( pixel_t ));\n\n\t\tfor( i = 0; i < height * width; i++ )\n\t\t{\n\t\t\tunsigned int r, g, b, major, minor;\n\t\t\t// seems to look better\n\t\t\tr = data[i * 4 + 0] * BIT( 5 ) / 256;\n\t\t\tg = data[i * 4 + 1] * BIT( 6 ) / 256;\n\t\t\tb = data[i * 4 + 2] * BIT( 5 ) / 256;\n\n\t\t\t// 565 to 332\n\t\t\tmajor = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 )));\n\n\t\t\t// save minor GBRGBRGB\n\t\t\tminor = MOVE_BIT( r, 1, 5 ) | MOVE_BIT( r, 0, 2 ) | MOVE_BIT( g, 2, 7 ) | MOVE_BIT( g, 1, 4 ) | MOVE_BIT( g, 0, 1 ) | MOVE_BIT( b, 2, 6 ) | MOVE_BIT( b, 1, 3 ) | MOVE_BIT( b, 0, 0 );\n\n\t\t\ttex->pixels[j][i] = major << 8 | ( minor & 0xFF );\n\t\t\tif( j == 0 && tex->alpha_pixels )\n\t\t\t{\n\t\t\t\tunsigned int alpha = ( data[i * 4 + 3] * 8 / 256 ) << ( 16 - 3 );\n\t\t\t\ttex->alpha_pixels[i] = ( tex->pixels[j][i] >> 3 ) | alpha;\n\t\t\t\tif( !sw_noalphabrushes.value && data[i * 4 + 3] < 128 && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))\n\t\t\t\t\ttex->pixels[j][i] = TRANSPARENT_COLOR;         // 0000 0011 0100 1001;\n\t\t\t}\n\n\t\t}\n\n\t\tif( mipCount > 1 )\n\t\t\tGL_BuildMipMap( data, width, height, tex->depth, tex->flags );\n\n\t\ttex->size += texsize;\n\t\ttex->numMips++;\n\n\t\t// GL_CheckTexImageError( tex );\n\t}\n\n\treturn true;\n}\n\n/*\n===============\nGL_ProcessImage\n\ndo specified actions on pixels\n===============\n*/\nstatic void GL_ProcessImage( image_t *tex, rgbdata_t *pic )\n{\n\tuint img_flags = 0;\n\n\t// force upload texture as RGB or RGBA (detail textures requires this)\n\tif( tex->flags & TF_FORCE_COLOR )\n\t\tpic->flags |= IMAGE_HAS_COLOR;\n\tif( pic->flags & IMAGE_HAS_ALPHA )\n\t\ttex->flags |= TF_HAS_ALPHA;\n\n\tif( ImageCompressed( pic->type ))\n\t{\n\t\tif( !pic->numMips )\n\t\t\ttex->flags |= TF_NOMIPMAP; // disable mipmapping by user request\n\n\t\t// clear all the unsupported flags\n\t\ttex->flags &= ~TF_KEEP_SOURCE;\n\t}\n\telse\n\t{\n\t\t// copy flag about luma pixels\n\t\tif( pic->flags & IMAGE_HAS_LUMA )\n\t\t\ttex->flags |= TF_HAS_LUMA;\n\n\t\tif( pic->flags & IMAGE_QUAKEPAL )\n\t\t\ttex->flags |= TF_QUAKEPAL;\n\n\t\t// create luma texture from quake texture\n\t\tif( tex->flags & TF_MAKELUMA )\n\t\t{\n\t\t\timg_flags |= IMAGE_MAKE_LUMA;\n\t\t\ttex->flags &= ~TF_MAKELUMA;\n\t\t}\n\n\t\tif( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE ))\n\t\t\ttex->original = gEngfuncs.FS_CopyImage( pic ); // because current pic will be expanded to rgba\n\n\t\t// we need to expand image into RGBA buffer\n\t\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\t\timg_flags |= IMAGE_FORCE_RGBA;\n\n\t\tif( FBitSet( tex->flags, TF_LUMINANCE ))\n\t\t\tClearBits( pic->flags, IMAGE_HAS_COLOR );\n\t}\n}\n\n/*\n================\nGL_CheckTexName\n================\n*/\nstatic qboolean GL_CheckTexName( const char *name )\n{\n\tint len;\n\n\tif( !COM_CheckString( name ))\n\t\treturn false;\n\n\tlen = Q_strlen( name );\n\n\t// because multi-layered textures can exceed name string\n\tif( len >= sizeof( r_images->name ))\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: too long name %s (%d)\\n\", __func__, name, len );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n================\nGL_TextureForName\n================\n*/\nstatic image_t *GL_TextureForName( const char *name )\n{\n\timage_t *tex;\n\tuint    hash;\n\n\t// find the texture in array\n\thash = COM_HashKey( name, TEXTURES_HASH_SIZE );\n\n\tfor( tex = r_imagesHashTable[hash]; tex != NULL; tex = tex->nextHash )\n\t{\n\t\tif( !Q_stricmp( tex->name, name ))\n\t\t\treturn tex;\n\t}\n\n\treturn NULL;\n}\n\n/*\n================\nGL_AllocTexture\n================\n*/\nstatic image_t *GL_AllocTexture( const char *name, texFlags_t flags )\n{\n\timage_t *tex;\n\tuint    i;\n\n\t// find a free texture_t slot\n\tfor( i = 0, tex = r_images; i < r_numImages; i++, tex++ )\n\t\tif( !tex->name[0] )\n\t\t\tbreak;\n\n\tif( i == r_numImages )\n\t{\n\t\tif( r_numImages == MAX_TEXTURES )\n\t\t\tgEngfuncs.Host_Error( \"%s: MAX_TEXTURES limit exceeds\\n\", __func__ );\n\t\tr_numImages++;\n\t}\n\n\ttex = &r_images[i];\n\n\t// copy initial params\n\tQ_strncpy( tex->name, name, sizeof( tex->name ));\n\n\t// tex->texnum = i; // texnum is used for fast acess into gl_textures array too\n\ttex->flags = flags;\n\n\t// add to hash table\n\ttex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE );\n\ttex->nextHash = r_imagesHashTable[tex->hashValue];\n\tr_imagesHashTable[tex->hashValue] = tex;\n\n\treturn tex;\n}\n\n/*\n================\nGL_DeleteTexture\n================\n*/\nstatic void GL_DeleteTexture( image_t *tex )\n{\n\timage_t **prev;\n\timage_t *cur;\n\tint     i;\n\n\tASSERT( tex != NULL );\n\n\t// already freed?\n\tif( !tex->pixels[0] )\n\t\treturn;\n\n\t// debug\n\tif( !tex->name[0] )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: trying to free unnamed texture\\n\", __func__ );\n\t\treturn;\n\t}\n\n\t// remove from hash table\n\tprev = &r_imagesHashTable[tex->hashValue];\n\n\twhile( 1 )\n\t{\n\t\tcur = *prev;\n\t\tif( !cur )\n\t\t\tbreak;\n\n\t\tif( cur == tex )\n\t\t{\n\t\t\t*prev = cur->nextHash;\n\t\t\tbreak;\n\t\t}\n\t\tprev = &cur->nextHash;\n\t}\n\n\t// release source\n\tif( tex->original )\n\t\tgEngfuncs.FS_FreeImage( tex->original );\n\n\tfor( i = 0; i < 4; i++ )\n\t\tif( tex->pixels[i] )\n\t\t\tMem_Free( tex->pixels[i] );\n\tif( tex->alpha_pixels )\n\t\tMem_Free( tex->alpha_pixels );\n\n\tmemset( tex, 0, sizeof( *tex ));\n}\n\n/*\n================\nGL_UpdateTexSize\n\nrecalc image room\n================\n*/\nvoid GAME_EXPORT GL_UpdateTexSize( int texnum, int width, int height, int depth )\n{\n\tint     i, j, texsize;\n\tint     numSides;\n\timage_t *tex;\n\n\tif( texnum <= 0 || texnum >= MAX_TEXTURES )\n\t\treturn;\n\n\ttex = &r_images[texnum];\n\tnumSides = FBitSet( tex->flags, TF_CUBEMAP ) ? 6 : 1;\n\tGL_SetTextureDimensions( tex, width, height, depth );\n\ttex->size = 0; // recompute now\n\n\tfor( i = 0; i < numSides; i++ )\n\t{\n\t\tfor( j = 0; j < Q_max( 1, tex->numMips ); j++ )\n\t\t{\n\t\t\twidth = Q_max( 1, ( tex->width >> j ));\n\t\t\theight = Q_max( 1, ( tex->height >> j ));\n\t\t\ttexsize = GL_CalcTextureSize( width, height, tex->depth );\n\t\t\ttex->size += texsize;\n\t\t}\n\t}\n}\n\n/*\n================\nGL_LoadTexture\n================\n*/\nint GAME_EXPORT GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags )\n{\n\timage_t   *tex;\n\trgbdata_t *pic;\n\tuint      picFlags = 0;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )))\n\t\treturn( tex - r_images );\n\n\tif( FBitSet( flags, TF_NOFLIP_TGA ))\n\t\tSetBits( picFlags, IL_DONTFLIP_TGA );\n\n\tif( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE ))\n\t\tSetBits( picFlags, IL_KEEP_8BIT );\n\n\t// set some image flags\n\tgEngfuncs.Image_SetForceFlags( picFlags );\n\n\tpic = gEngfuncs.FS_LoadImage( name, buf, size );\n\tif( !pic )\n\t\treturn 0;    // couldn't loading image\n\n\t// allocate the new one\n\ttex = GL_AllocTexture( name, flags );\n\tGL_ProcessImage( tex, pic );\n\n\tif( !GL_UploadTexture( tex, pic ))\n\t{\n\t\tmemset( tex, 0, sizeof( image_t ));\n\t\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\t\treturn 0;\n\t}\n\n\tGL_ApplyTextureParams( tex );  // update texture filter, wrap etc\n\tgEngfuncs.FS_FreeImage( pic ); // release source texture\n\n\t// NOTE: always return texnum as index in array or engine will stop work !!!\n\treturn tex - r_images;\n}\n\n/*\n================\nGL_LoadTextureArray\n================\n*/\nint GAME_EXPORT GL_LoadTextureArray( const char **names, int flags )\n{\n\treturn 0;\n}\n\n/*\n================\nGL_LoadTextureFromBuffer\n================\n*/\nint GAME_EXPORT GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update )\n{\n\timage_t *tex;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )) && !update )\n\t\treturn( tex - r_images );\n\n\t// couldn't loading image\n\tif( !pic )\n\t\treturn 0;\n\n\tif( update )\n\t{\n\t\tif( tex == NULL )\n\t\t\tgEngfuncs.Host_Error( \"%s: couldn't find texture %s for update\\n\", __func__, name );\n\t\tSetBits( tex->flags, flags );\n\t}\n\telse\n\t{\n\t\t// allocate the new one\n\t\ttex = GL_AllocTexture( name, flags );\n\t}\n\n\tGL_ProcessImage( tex, pic );\n\tif( !GL_UploadTexture( tex, pic ))\n\t{\n\t\tmemset( tex, 0, sizeof( image_t ));\n\t\treturn 0;\n\t}\n\n\tGL_ApplyTextureParams( tex ); // update texture filter, wrap etc\n\treturn( tex - r_images );\n}\n\n/*\n================\nGL_CreateTexture\n\ncreates texture from buffer\n================\n*/\nint GAME_EXPORT GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags )\n{\n\tint       datasize = 1;\n\trgbdata_t r_empty;\n\n\tif( FBitSet( flags, TF_ARB_16BIT ))\n\t\tdatasize = 2;\n\telse if( FBitSet( flags, TF_ARB_FLOAT ))\n\t\tdatasize = 4;\n\n\tmemset( &r_empty, 0, sizeof( r_empty ));\n\tr_empty.width = width;\n\tr_empty.height = height;\n\tr_empty.type = PF_RGBA_32;\n\tr_empty.size = r_empty.width * r_empty.height * datasize * 4;\n\tr_empty.buffer = (byte *)buffer;\n\n\t// clear invalid combinations\n\tClearBits( flags, TF_TEXTURE_3D );\n\n\t// if image not luminance and not alphacontrast it will have color\n\tif( !FBitSet( flags, TF_LUMINANCE ) && !FBitSet( flags, TF_ALPHACONTRAST ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_COLOR );\n\n\tif( FBitSet( flags, TF_HAS_ALPHA ))\n\t\tSetBits( r_empty.flags, IMAGE_HAS_ALPHA );\n\n\tif( FBitSet( flags, TF_CUBEMAP ))\n\t{\n\t\treturn 0;\n\t}\n\n\treturn GL_LoadTextureInternal( name, &r_empty, flags );\n}\n\n/*\n================\nGL_CreateTextureArray\n\ncreates texture array from buffer\n================\n*/\nint GAME_EXPORT GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags )\n{\n\treturn 0;\n}\n\n/*\n================\nGL_FindTexture\n================\n*/\nint GAME_EXPORT GL_FindTexture( const char *name )\n{\n\timage_t *tex;\n\n\tif( !GL_CheckTexName( name ))\n\t\treturn 0;\n\n\t// see if already loaded\n\tif(( tex = GL_TextureForName( name )))\n\t\treturn( tex - r_images );\n\n\treturn 0;\n}\n\n/*\n================\nGL_FreeTexture\n================\n*/\nvoid GAME_EXPORT GL_FreeTexture( unsigned int texnum )\n{\n\t// number 0 it's already freed\n\tif( texnum <= 0 )\n\t\treturn;\n\n\tGL_DeleteTexture( &r_images[texnum] );\n}\n\n/*\n================\nGL_ProcessTexture\n================\n*/\nvoid GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )\n{\n\timage_t   *image;\n\trgbdata_t *pic;\n\tint       flags = 0;\n\n\tif( texnum <= 0 || texnum >= MAX_TEXTURES )\n\t\treturn; // missed image\n\timage = &r_images[texnum];\n\n\t// select mode\n\tif( gamma != -1.0f )\n\t{\n\t\tflags = IMAGE_LIGHTGAMMA;\n\t}\n\telse if( topColor != -1 && bottomColor != -1 )\n\t{\n\t\tflags = IMAGE_REMAP;\n\t}\n\telse\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: bad operation for %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\tif( !image->original )\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: no input data for %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\tif( ImageCompressed( image->original->type ))\n\t{\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: can't process compressed texture %s\\n\", __func__, image->name );\n\t\treturn;\n\t}\n\n\t// all the operations makes over the image copy not an original\n\tpic = gEngfuncs.FS_CopyImage( image->original );\n\n\t// we need to expand image into RGBA buffer\n\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\tflags |= IMAGE_FORCE_RGBA;\n\n\tgEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f );\n\n\tGL_UploadTexture( image, pic );\n\tGL_ApplyTextureParams( image ); // update texture filter, wrap etc\n\n\tgEngfuncs.FS_FreeImage( pic );\n}\n\n/*\n================\nR_TexMemory\n\nreturn size of all uploaded textures\n================\n*/\nint R_TexMemory( void )\n{\n\tint i, total = 0;\n\n\tfor( i = 0; i < r_numImages; i++ )\n\t\ttotal += r_images[i].size;\n\n\treturn total;\n}\n\n/*\n==============================================================================\n\nINTERNAL TEXTURES\n\n==============================================================================\n*/\n/*\n==================\nGL_FakeImage\n==================\n*/\nstatic rgbdata_t *GL_FakeImage( int width, int height, int depth, int flags )\n{\n\tstatic byte      data2D[1024]; // 16x16x4\n\tstatic rgbdata_t r_image;\n\n\t// also use this for bad textures, but without alpha\n\tr_image.width = Q_max( 1, width );\n\tr_image.height = Q_max( 1, height );\n\tr_image.depth = Q_max( 1, depth );\n\tr_image.flags = flags;\n\tr_image.type = PF_RGBA_32;\n\tr_image.size = r_image.width * r_image.height * r_image.depth * 4;\n\tr_image.buffer = ( r_image.size > sizeof( data2D )) ? NULL : data2D;\n\tr_image.palette = NULL;\n\tr_image.numMips = 1;\n\tr_image.encode = 0;\n\n\tif( FBitSet( r_image.flags, IMAGE_CUBEMAP ))\n\t\tr_image.size *= 6;\n\tmemset( data2D, 0xFF, sizeof( data2D ));\n\n\treturn &r_image;\n}\n\n/*\n==================\nR_InitDlightTexture\n==================\n*/\nvoid R_InitDlightTexture( void )\n{\n\trgbdata_t r_image;\n\n\tif( tr.dlightTexture != 0 )\n\t\treturn; // already initialized\n\n\tmemset( &r_image, 0, sizeof( r_image ));\n\tr_image.width = BLOCK_SIZE;\n\tr_image.height = BLOCK_SIZE;\n\tr_image.flags = IMAGE_HAS_COLOR;\n\tr_image.type = PF_RGBA_32;\n\tr_image.size = r_image.width * r_image.height * 4;\n\n\ttr.dlightTexture = GL_LoadTextureInternal( \"*dlight\", &r_image, TF_NOMIPMAP | TF_CLAMP | TF_ATLAS_PAGE );\n}\n\n/*\n==================\nGL_CreateInternalTextures\n==================\n*/\nstatic void GL_CreateInternalTextures( void )\n{\n\tint       dx2, dy, d;\n\tint       x, y;\n\trgbdata_t *pic;\n\n\t// emo-texture from quake1\n\tpic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR );\n\n\tfor( y = 0; y < 16; y++ )\n\t{\n\t\tfor( x = 0; x < 16; x++ )\n\t\t{\n\t\t\tif(( y < 8 ) ^ ( x < 8 ))\n\t\t\t\t((uint *)pic->buffer )[y * 16 + x] = 0xFFFF00FF;\n\t\t\telse\n\t\t\t\t((uint *)pic->buffer )[y * 16 + x] = 0xFF000000;\n\t\t}\n\t}\n\n\ttr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP );\n\n\t// particle texture from quake1\n\tpic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA );\n\n\tfor( x = 0; x < 16; x++ )\n\t{\n\t\tdx2 = x - 8;\n\t\tdx2 = dx2 * dx2;\n\n\t\tfor( y = 0; y < 16; y++ )\n\t\t{\n\t\t\tdy = y - 8;\n\t\t\td = 255 - 35 * sqrt( dx2 + dy * dy );\n\t\t\tpic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 );\n\t\t}\n\t}\n\n\ttr.particleTexture = GL_LoadTextureInternal( \"*particle\", pic, TF_CLAMP );\n\n\t// white texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer )[x] = 0xFFFFFFFF;\n\ttr.whiteTexture = GL_LoadTextureInternal( REF_WHITE_TEXTURE, pic, TF_COLORMAP );\n\n\t// gray texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer )[x] = 0xFF7F7F7F;\n\ttr.grayTexture = GL_LoadTextureInternal( REF_GRAY_TEXTURE, pic, TF_COLORMAP );\n\n\t// black texture\n\tpic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\t((uint *)pic->buffer )[x] = 0xFF000000;\n\ttr.blackTexture = GL_LoadTextureInternal( REF_BLACK_TEXTURE, pic, TF_COLORMAP );\n\n\t// cinematic dummy\n\tpic = GL_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR );\n\ttr.cinTexture = GL_LoadTextureInternal( \"*cintexture\", pic, TF_NOMIPMAP | TF_CLAMP );\n}\n\n/*\n===============\nR_TextureList_f\n===============\n*/\nvoid R_TextureList_f( void )\n{\n\timage_t *image;\n\tint     i, texCount, bytes = 0;\n\n\tgEngfuncs.Con_Printf( \"\\n\" );\n\tgEngfuncs.Con_Printf( \" -id-   -w-  -h-     -size- -fmt- -type- -data-  -encode- -wrap- -depth- -name--------\\n\" );\n\n\tfor( i = texCount = 0, image = r_images; i < r_numImages; i++, image++ )\n\t{\n\t\tif( !image->pixels )\n\t\t\tcontinue;\n\n\t\tbytes += image->size;\n\t\ttexCount++;\n\n\t\tgEngfuncs.Con_Printf( \"%4i: \", i );\n\t\tgEngfuncs.Con_Printf( \"%4i %4i \", image->width, image->height );\n\t\tgEngfuncs.Con_Printf( \"%12s \", Q_memprint( image->size ));\n\n\t\tif( image->flags & TF_NORMALMAP )\n\t\t\tgEngfuncs.Con_Printf( \"normal  \" );\n\t\telse\n\t\t\tgEngfuncs.Con_Printf( \"diffuse \" );\n\n\t\tif( image->flags & TF_CLAMP )\n\t\t\tgEngfuncs.Con_Printf( \"clamp  \" );\n\t\telse if( image->flags & TF_BORDER )\n\t\t\tgEngfuncs.Con_Printf( \"border \" );\n\t\telse\n\t\t\tgEngfuncs.Con_Printf( \"repeat \" );\n\t\tgEngfuncs.Con_Printf( \"   %d  \", image->depth );\n\t\tgEngfuncs.Con_Printf( \"  %s\\n\", image->name );\n\t}\n\n\tgEngfuncs.Con_Printf( \"---------------------------------------------------------\\n\" );\n\tgEngfuncs.Con_Printf( \"%i total textures\\n\", texCount );\n\tgEngfuncs.Con_Printf( \"%s total memory used\\n\", Q_memprint( bytes ));\n\tgEngfuncs.Con_Printf( \"\\n\" );\n}\n\n/*\n===============\nR_InitImages\n===============\n*/\nvoid R_InitImages( void )\n{\n\tmemset( r_images, 0, sizeof( r_images ));\n\tmemset( r_imagesHashTable, 0, sizeof( r_imagesHashTable ));\n\tr_numImages = 0;\n\n\t// create unused 0-entry\n\tQ_strncpy( r_images->name, \"*unused*\", sizeof( r_images->name ));\n\tr_images->hashValue = COM_HashKey( r_images->name, TEXTURES_HASH_SIZE );\n\tr_images->nextHash = r_imagesHashTable[r_images->hashValue];\n\tr_imagesHashTable[r_images->hashValue] = r_images;\n\tr_numImages = 1;\n\n\t// validate cvars\n\tR_SetTextureParameters();\n\tGL_CreateInternalTextures();\n\n\tgEngfuncs.Cmd_AddCommand( \"texturelist\", R_TextureList_f, \"display loaded textures list\" );\n}\n\n/*\n===============\nR_ShutdownImages\n===============\n*/\nvoid R_ShutdownImages( void )\n{\n\timage_t *tex;\n\tint     i;\n\n\tgEngfuncs.Cmd_RemoveCommand( \"texturelist\" );\n\n\tfor( i = 0, tex = r_images; i < r_numImages; i++, tex++ )\n\t\tGL_DeleteTexture( tex );\n\n\tmemset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));\n\tmemset( r_imagesHashTable, 0, sizeof( r_imagesHashTable ));\n\tmemset( r_images, 0, sizeof( r_images ));\n\tr_numImages = 0;\n}\n"
  },
  {
    "path": "ref/soft/r_light.c",
    "content": "/*\ngl_rlight.c - dynamic and static lights\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_params.h\"\n\n// unused, need refactor\nunsigned blocklights[10240];\n\n/*\n=============================================================================\n\nDYNAMIC LIGHTS\n\n=============================================================================\n*/\n/*\n==================\nCL_RunLightStyles\n\n==================\n*/\nvoid CL_RunLightStyles( lightstyle_t *ls )\n{\n\tint   i;\n\tfloat frametime = ( gp_cl->time - gp_cl->oldtime );\n\n\tif( !WORLDMODEL )\n\t\treturn;\n\n\tif( !WORLDMODEL->lightdata )\n\t{\n\t\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t\t\ttr.lightstylevalue[i] = 256 * 256;\n\t\treturn;\n\t}\n\n\t// light animations\n\t// 'm' is normal light, 'a' is no light, 'z' is double bright\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tint   k, flight, clight;\n\t\tfloat l, lerpfrac, backlerp;\n\n\t\tif( !gp_cl->paused && frametime <= 0.1f )\n\t\t\tls[i].time += frametime; // evaluate local time\n\n\t\tif( !ls[i].length )\n\t\t{\n\t\t\ttr.lightstylevalue[i] = 256;\n\t\t\tcontinue;\n\t\t}\n\t\telse if( ls[i].length == 1 )\n\t\t{\n\t\t\t// single length style so don't bother interpolating\n\t\t\ttr.lightstylevalue[i] = ( ls[i].map[0] ) * 22;\n\t\t\tcontinue;\n\t\t}\n\n\t\tflight = (int)Q_floor( ls[i].time * 10 );\n\n\t\tif( !ls[i].interp || !cl_lightstyle_lerping->value )\n\t\t{\n\t\t\ttr.lightstylevalue[i] = ls[i].map[flight % ls[i].length] * 22;\n\t\t\tcontinue;\n\t\t}\n\n\t\tclight = (int)Q_ceil( ls[i].time * 10 );\n\t\tlerpfrac = ( ls[i].time * 10 ) - flight;\n\t\tbacklerp = 1.0f - lerpfrac;\n\n\t\t// interpolate animating light\n\t\t// frame just gone\n\t\tk = ls[i].map[flight % ls[i].length];\n\t\tl = (float)( k * 22.0f ) * backlerp;\n\n\t\t// upcoming frame\n\t\tk = ls[i].map[clight % ls[i].length];\n\t\tl += (float)( k * 22.0f ) * lerpfrac;\n\n\t\ttr.lightstylevalue[i] = (int)l;\n\t}\n}\n\n/*\n=============\nR_MarkLights\n=============\n*/\nvoid R_MarkLights( dlight_t *light, int bit, mnode_t *node )\n{\n\tfloat\t\tdist;\n\tmsurface_t\t*surf;\n\tint\t\ti;\n\tmnode_t *children[2];\n\tint firstsurface, numsurfaces;\n\n\tif( !node || node->contents < 0 )\n\t\treturn;\n\n\tdist = PlaneDiff( light->origin, node->plane );\n\n\tnode_children( children, node, RI.currentmodel );\n\tfirstsurface = node_firstsurface( node, RI.currentmodel );\n\tnumsurfaces = node_numsurfaces( node, RI.currentmodel );\n\n\tif( dist > light->radius )\n\t{\n\t\tR_MarkLights( light, bit, children[0] );\n\t\treturn;\n\t}\n\tif( dist < -light->radius )\n\t{\n\t\tR_MarkLights( light, bit, children[1] );\n\t\treturn;\n\t}\n\n\t// mark the polygons\n\tsurf = RI.currentmodel->surfaces + firstsurface;\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\tif( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius ))\n\t\t\tcontinue;\t// no intersection\n\n\t\tif( surf->dlightframe != tr.dlightframecount )\n\t\t{\n\t\t\tsurf->dlightbits = 0;\n\t\t\tsurf->dlightframe = tr.dlightframecount;\n\t\t}\n\t\tsurf->dlightbits |= bit;\n\t}\n\n\tR_MarkLights( light, bit, children[0] );\n\tR_MarkLights( light, bit, children[1] );\n}\n\n/*\n=============\nR_PushDlights\n=============\n*/\nvoid R_PushDlights( void )\n{\n\tint i;\n\n\ttr.dlightframecount = tr.framecount;\n\n\tRI.currententity = CL_GetEntityByIndex( 0 );\n\n\t// no world -- no dlights\n\tif( !RI.currententity )\n\t\treturn;\n\n\tRI.currentmodel = RI.currententity->model;\n\n\tfor( i = 0; i < MAX_DLIGHTS; i++ )\n\t{\n\t\tdlight_t *l = &tr.dlights[i];\n\n\t\tif( l->die < gp_cl->time || !l->radius )\n\t\t\tcontinue;\n\n\t\t// if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 ))\n\t\t// continue;\n\n\t\tR_MarkLights( l, 1 << i, RI.currentmodel->nodes );\n\t}\n}\n\n/*\n=======================================================================\n\n\tAMBIENT LIGHTING\n\n=======================================================================\n*/\nstatic vec3_t g_trace_lightspot;\nstatic vec3_t g_trace_lightvec;\nstatic float  g_trace_fraction;\n\n/*\n=================\nR_RecursiveLightPoint\n=================\n*/\nstatic qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end )\n{\n\tfloat        front, back, frac, midf;\n\tint          i, map, side, size;\n\tfloat        ds, dt, s, t;\n\tint          sample_size;\n\tcolor24      *lm, *dm;\n\tmextrasurf_t *info;\n\tmsurface_t   *surf;\n\tmtexinfo_t   *tex;\n\tmatrix3x4    tbn;\n\tvec3_t       mid;\n\tmnode_t *children[2];\n\tint firstsurface, numsurfaces;\n\n\t// didn't hit anything\n\tif( !node || node->contents < 0 )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false;\n\t}\n\n\tnode_children( children, node, model );\n\tfirstsurface = node_firstsurface( node, model );\n\tnumsurfaces = node_numsurfaces( node, model );\n\n\t// calculate mid point\n\tfront = PlaneDiff( start, node->plane );\n\tback = PlaneDiff( end, node->plane );\n\n\tside = front < 0;\n\tif(( back < 0 ) == side )\n\t\treturn R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end );\n\n\tfrac = front / ( front - back );\n\n\tVectorLerp( start, frac, end, mid );\n\tmidf = p1f + ( p2f - p1f ) * frac;\n\n\t// co down front side\n\tif( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid ))\n\t\treturn true; // hit something\n\n\tif(( back < 0 ) == side )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false; // didn't hit anything\n\t}\n\n\t// check for impact on this node\n\tsurf = model->surfaces + firstsurface;\n\tVectorCopy( mid, g_trace_lightspot );\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\tint smax, tmax;\n\n\t\ttex = surf->texinfo;\n\t\tinfo = surf->info;\n\n\t\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\t\tcontinue; // no lightmaps\n\n\t\ts = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3];\n\t\tt = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3];\n\n\t\tif( s < info->lightmapmins[0] || t < info->lightmapmins[1] )\n\t\t\tcontinue;\n\n\t\tds = s - info->lightmapmins[0];\n\t\tdt = t - info->lightmapmins[1];\n\n\t\tif( ds > info->lightextents[0] || dt > info->lightextents[1] )\n\t\t\tcontinue;\n\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\n\t\tif( !surf->samples )\n\t\t\treturn true;\n\n\t\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\t\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\t\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\t\tds /= sample_size;\n\t\tdt /= sample_size;\n\n\t\tlm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );\n\t\tg_trace_fraction = midf;\n\t\tsize = smax * tmax;\n\t\tdm = NULL;\n\n\t\tif( surf->info->deluxemap )\n\t\t{\n\t\t\tvec3_t faceNormal;\n\n\t\t\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\t\t\tVectorNegate( surf->plane->normal, faceNormal );\n\t\t\telse\n\t\t\t\tVectorCopy( surf->plane->normal, faceNormal );\n\n\t\t\t// compute face TBN\n#if 1\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], surf->info->lmvecs[0][1], surf->info->lmvecs[0][2], 0.0f );\n\t\t\tVector4Set( tbn[1], -surf->info->lmvecs[1][0], -surf->info->lmvecs[1][1], -surf->info->lmvecs[1][2], 0.0f );\n\t\t\tVector4Set( tbn[2], faceNormal[0], faceNormal[1], faceNormal[2], 0.0f );\n#else\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], -surf->info->lmvecs[1][0], faceNormal[0], 0.0f );\n\t\t\tVector4Set( tbn[1], surf->info->lmvecs[0][1], -surf->info->lmvecs[1][1], faceNormal[1], 0.0f );\n\t\t\tVector4Set( tbn[2], surf->info->lmvecs[0][2], -surf->info->lmvecs[1][2], faceNormal[2], 0.0f );\n#endif\n\t\t\tVectorNormalize( tbn[0] );\n\t\t\tVectorNormalize( tbn[1] );\n\t\t\tVectorNormalize( tbn[2] );\n\t\t\tdm = surf->info->deluxemap + Q_rint( dt ) * smax + Q_rint( ds );\n\t\t}\n\n\t\tfor( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )\n\t\t{\n\t\t\tuint scale = tr.lightstylevalue[surf->styles[map]];\n\n\t\t\tcv->r += lm->r * scale;\n\t\t\tcv->g += lm->g * scale;\n\t\t\tcv->b += lm->b * scale;\n\n\t\t\tlm += size; // skip to next lightmap\n\n\t\t\tif( dm != NULL )\n\t\t\t{\n\t\t\t\tvec3_t srcNormal, lightNormal;\n\t\t\t\tfloat  f = ( 1.0f / 128.0f );\n\n\t\t\t\tVectorSet( srcNormal, ((float)dm->r - 128.0f ) * f, ((float)dm->g - 128.0f ) * f, ((float)dm->b - 128.0f ) * f );\n\t\t\t\tMatrix3x4_VectorIRotate( tbn, srcNormal, lightNormal );        // turn to world space\n\t\t\t\tVectorScale( lightNormal, (float)scale * -1.0f, lightNormal ); // turn direction from light\n\t\t\t\tVectorAdd( g_trace_lightvec, lightNormal, g_trace_lightvec );\n\t\t\t\tdm += size; // skip to next deluxmap\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// go down back side\n\treturn R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end );\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\nstatic colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tfloat    last_fraction;\n\tint      i, maxEnts = 1;\n\tcolorVec light, cv;\n\n\tif( lspot )\n\t\tVectorClear( lspot );\n\tif( lvec )\n\t\tVectorClear( lvec );\n\n\tif( WORLDMODEL && WORLDMODEL->lightdata )\n\t{\n\t\tlight.r = light.g = light.b = light.a = 0;\n\t\tlast_fraction = 1.0f;\n\n\t\t// get light from bmodels too\n\t\t// if( CVAR_TO_BOOL( r_lighting_extended ))\n\t\tmaxEnts = MAX_PHYSENTS;\n\n\t\t// check all the bsp-models\n\t\tfor( i = 0; i < maxEnts; i++ )\n\t\t{\n\t\t\tphysent_t *pe = gEngfuncs.EV_GetPhysent( i );\n\t\t\tvec3_t    offset, start_l, end_l;\n\t\t\tmnode_t   *pnodes;\n\t\t\tmatrix4x4 matrix;\n\n\t\t\tif( !pe )\n\t\t\t\tbreak;\n\n\t\t\tif( !pe->model || pe->model->type != mod_brush )\n\t\t\t\tcontinue; // skip non-bsp models\n\n\t\t\tpnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode];\n\t\t\tVectorSubtract( pe->model->hulls[0].clip_mins, vec3_origin, offset );\n\t\t\tVectorAdd( offset, pe->origin, offset );\n\t\t\tVectorSubtract( start, offset, start_l );\n\t\t\tVectorSubtract( end, offset, end_l );\n\n\t\t\t// rotate start and end into the models frame of reference\n\t\t\tif( !VectorIsNull( pe->angles ))\n\t\t\t{\n\t\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t\t\t}\n\n\t\t\tVectorClear( g_trace_lightspot );\n\t\t\tVectorClear( g_trace_lightvec );\n\t\t\tg_trace_fraction = 1.0f;\n\n\t\t\tif( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l ))\n\t\t\t\tcontinue; // didn't hit anything\n\n\t\t\tif( g_trace_fraction < last_fraction )\n\t\t\t{\n\t\t\t\tif( lspot )\n\t\t\t\t\tVectorCopy( g_trace_lightspot, lspot );\n\t\t\t\tif( lvec )\n\t\t\t\t\tVectorNormalize2( g_trace_lightvec, lvec );\n\t\t\t\tlight.r = Q_min(( cv.r >> 8 ), 255 );\n\t\t\t\tlight.g = Q_min(( cv.g >> 8 ), 255 );\n\t\t\t\tlight.b = Q_min(( cv.b >> 8 ), 255 );\n\t\t\t\tlast_fraction = g_trace_fraction;\n\n\t\t\t\tif(( light.r + light.g + light.b ) != 0 )\n\t\t\t\t\tbreak; // we get light now\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tlight.r = light.g = light.b = 255;\n\t\tlight.a = 0;\n\t}\n\n\treturn light;\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\ncolorVec GAME_EXPORT R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tcolorVec light = R_LightVecInternal( start, end, lspot, lvec );\n\n\t// light.r = light.g = light.b = 255;\n\n\tif( lspot != NULL && lvec != NULL ) // CVAR_TO_BOOL( r_lighting_extended ) &&\n\t{\n\t\t// trying to get light from ceiling (but ignore gradient analyze)\n\t\tif(( light.r + light.g + light.b ) == 0 )\n\t\t\treturn R_LightVecInternal( end, start, lspot, lvec );\n\t}\n\n\treturn light;\n}\n\n/*\n=================\nR_LightPoint\n\nlight from floor\n=================\n*/\ncolorVec GAME_EXPORT R_LightPoint( const vec3_t p0 )\n{\n\tvec3_t p1;\n\n\tVectorSet( p1, p0[0], p0[1], p0[2] - 2048.0f );\n\n\treturn R_LightVec( p0, p1, NULL, NULL );\n}\n"
  },
  {
    "path": "ref/soft/r_local.h",
    "content": "/*\ngl_local.h - renderer local declarations\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#ifndef GL_LOCAL_H\n#define GL_LOCAL_H\n#include \"port.h\"\n#include \"xash3d_types.h\"\n#include \"cvardef.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"cl_entity.h\"\n#include \"render_api.h\"\n#include \"protocol.h\"\n#include \"dlight.h\"\n#include \"ref_api.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_params.h\"\n#include \"enginefeatures.h\"\n#include \"com_strings.h\"\n#include \"pm_movevars.h\"\n#include \"cvardef.h\"\ntypedef struct mip_s mip_t;\n\ntypedef int fixed8_t;\ntypedef int fixed16_t;\n\n#define ASSERT( x ) if( !( x )) gEngfuncs.Host_Error( \"assert failed at %s:%i\\n\", __FILE__, __LINE__ )\n#define Assert( x ) if( !( x )) gEngfuncs.Host_Error( \"assert failed at %s:%i\\n\", __FILE__, __LINE__ )\n\n#include <stdio.h>\n\n// make mod_ref.h?\n#define LM_SAMPLE_SIZE 16\n\nextern poolhandle_t r_temppool;\n\n#define BLOCK_SIZE         tr.block_size        // lightmap blocksize\n#define BLOCK_SIZE_DEFAULT 128                  // for keep backward compatibility\n#define BLOCK_SIZE_MAX     1024\n\n#define MAX_TEXTURES    8192 // a1ba: increased by users request\n#define MAX_DECAL_SURFS 4096\n\n#if XASH_LOW_MEMORY\n\t#undef MAX_TEXTURES\n\t#undef MAX_DECAL_SURFS\n\t#define MAX_TEXTURES    1024\n\t#define MAX_DECAL_SURFS 256\n#endif\n\n#define MAX_DETAIL_TEXTURES 256\n#define MAX_LIGHTMAPS       256\n#define SUBDIVIDE_SIZE      64\n#define MAX_DRAW_STACK      2           // normal view and menu view\n\n#define SHADEDOT_QUANT    16            // precalculated dot products for quantized angles\n#define SHADE_LAMBERT     1.4953241\n#define DEFAULT_ALPHATEST 0.0f\n\n// refparams\n#define RP_NONE        0\n#define RP_ENVVIEW     BIT( 0 )                 // used for cubemapshot\n#define RP_OLDVIEWLEAF BIT( 1 )\n#define RP_CLIPPLANE   BIT( 2 )\n\n#define RP_NONVIEWERREF ( RP_ENVVIEW )\n#define R_ModelOpaque( rm )   ( rm == kRenderNormal )\n#define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles ))\n#define RP_LOCALCLIENT( e )   (( e ) != NULL && ( e )->index == ( gp_cl->playernum + 1 ) && e->player )\n#define RP_NORMALPASS()       ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 )\n\n#define CL_IsViewEntityLocalPlayer() ( gp_cl->viewentity == ( gp_cl->playernum + 1 ))\n\n#define CULL_VISIBLE  0                 // not culled\n#define CULL_BACKSIDE 1                 // backside of transparent wall\n#define CULL_FRUSTUM  2                 // culled by frustum\n#define CULL_VISFRAME 3                 // culled by PVS\n#define CULL_OTHER    4                 // culled by other reason\n\n// bit operation helpers\n#define MASK( x )           ( BIT( x ) - 1 )\n#define GET_BIT( s, b )     (( s & ( 1 << b )) >> b )\n#define MOVE_BIT( s, f, t ) ( GET_BIT( s, f ) << t )\n\n\n/*\n  skins will be outline flood filled and mip mapped\n  pics and sprites with alpha will be outline flood filled\n  pic won't be mip mapped\n  model skin\n  sprite frame\n  wall texture\n  pic\n*/\n\ntypedef enum\n{\n\tit_skin,\n\tit_sprite,\n\tit_wall,\n\tit_pic,\n\tit_sky\n} imagetype_t;\n\n\n// ===================================================================\n\ntypedef unsigned short pixel_t;\n\ntypedef struct vrect_s\n{\n\tint            x, y, width, height;\n\tstruct vrect_s *pnext;\n} vrect_t;\n\n#define COLOR_WHITE 0xFFFF\n// #define SEPARATE_BLIT\ntypedef struct\n{\n\tpixel_t      *buffer;                           // invisible buffer\n\tpixel_t      colormap[32 * 8192];               // 8192 * light levels\n\t// pixel_t                 *alphamap;              // 256 * 256 translucency map\n#ifdef SEPARATE_BLIT\n\tpixel_t      screen_minor[256];\n\tpixel_t      screen_major[256];\n#else\n\tpixel_t      screen[256 * 256];\n\tunsigned int screen32[256 * 256];\n#endif\n\tbyte         addmap[256 * 256];\n\tbyte         modmap[256 * 256];\n\tpixel_t      alphamap[3 * 1024 * 256];\n\tpixel_t      color;\n\tqboolean     is2d;\n\tbyte         alpha;\n\n\t// maybe compute colormask for minor byte?\n\tint          rendermode;\n\tint          rowbytes;                                  // may be > width if displayed in a window\n\t// can be negative for stupid dibs\n\tint          width;\n\tint          height;\n} viddef_t;\n\nextern viddef_t vid;\n\ntypedef struct\n{\n\tint         params;             // rendering parameters\n\n\tqboolean    drawWorld;                  // ignore world for drawing PlayerModel\n\tqboolean    isSkyVisible;               // sky is visible\n\tqboolean    onlyClientDraw;             // disabled by client request\n\tqboolean    drawOrtho;                  // draw world as orthogonal projection\n\n\tfloat       fov_x, fov_y;       // current view fov\n\n\tcl_entity_t *currententity;\n\tmodel_t     *currentmodel;\n\tcl_entity_t *currentbeam;       // same as above but for beams\n\n\tint         viewport[4];\n\t// gl_frustum_t\tfrustum;\n\n\tmleaf_t     *viewleaf;\n\tmleaf_t     *oldviewleaf;\n\tvec3_t      pvsorigin;\n\tvec3_t      vieworg;                    // locked vieworigin\n\tvec3_t      viewangles;\n\tvec3_t      vforward;\n\tvec3_t      vright;\n\tvec3_t      vup;\n\tvec3_t      base_vup;\n\tvec3_t      base_vpn;\n\tvec3_t      base_vright;\n\n\tvec3_t      cullorigin;\n\tvec3_t      cull_vforward;\n\tvec3_t      cull_vright;\n\tvec3_t      cull_vup;\n\n\tint         cached_contents;            // in water\n\tint         cached_waterlevel;          // was in water\n\tfloat       farClip;\n\n\tfloat       skyMins[2][6];\n\tfloat       skyMaxs[2][6];\n\n\tmatrix4x4   objectMatrix;                       // currententity matrix\n\tmatrix4x4   worldviewMatrix;                    // modelview for world\n\tmatrix4x4   modelviewMatrix;                    // worldviewMatrix * objectMatrix\n\n\tmatrix4x4   projectionMatrix;\n\tmatrix4x4   worldviewProjectionMatrix;           // worldviewMatrix * projectionMatrix\n\tbyte        visbytes[( MAX_MAP_LEAFS + 7 ) / 8]; // actual PVS for current frame\n\n\tfloat       viewplanedist;\n\n\t// q2 oldrefdef\n\tvrect_t     vrect;                              // subwindow in video for refresh\n\t// FIXME: not need vrect next field here?\n\tvrect_t     aliasvrect;                         // scaled Alias version\n\tint         vrectright, vrectbottom;            // right & bottom screen coords\n\tint         aliasvrectright, aliasvrectbottom;  // scaled Alias versions\n\tfloat       vrectrightedge;                     // rightmost right edge we care about,\n\t//  for use in edge list\n\tfloat       fvrectx, fvrecty;             // for floating-point compares\n\tfloat       fvrectx_adj, fvrecty_adj;     // left and top edges, for clamping\n\tint         vrect_x_adj_shift20;          // (vrect.x + 0.5 - epsilon) << 20\n\tint         vrectright_adj_shift20;       // (vrectright + 0.5 - epsilon) << 20\n\tfloat       fvrectright_adj, fvrectbottom_adj;\n\t// right and bottom edges, for clamping\n\tfloat       fvrectright;                        // rightmost edge, for Alias clamping\n\tfloat       fvrectbottom;                       // bottommost edge, for Alias clampin\n\n\n} ref_instance_t;\n\ntypedef struct\n{\n\tcl_entity_t *edge_entities[MAX_VISIBLE_PACKET];         // brush edge drawing\n\tcl_entity_t *solid_entities[MAX_VISIBLE_PACKET];        // opaque moving or alpha brushes\n\tcl_entity_t *trans_entities[MAX_VISIBLE_PACKET];        // translucent brushes\n\tcl_entity_t *beam_entities[MAX_VISIBLE_PACKET];\n\tuint        num_edge_entities;\n\tuint        num_solid_entities;\n\tuint        num_trans_entities;\n\tuint        num_beam_entities;\n} draw_list_t;\n\ntypedef struct\n{\n\tint          defaultTexture;            // use for bad textures\n\tint          particleTexture;\n\tint          whiteTexture;\n\tint          grayTexture;\n\tint          blackTexture;\n\tint          solidskyTexture;           // quake1 solid-sky layer\n\tint          alphaskyTexture;           // quake1 alpha-sky layer\n\tint          lightmapTextures[MAX_LIGHTMAPS];\n\tint          dlightTexture;     // custom dlight texture\n\tint          skyboxTextures[6]; // skybox sides\n\tint          cinTexture;        // cinematic texture\n\n\t// entity lists\n\tdraw_list_t  draw_stack[MAX_DRAW_STACK];\n\tint          draw_stack_pos;\n\tdraw_list_t  *draw_list;\n\n\tmsurface_t   *draw_decals[MAX_DECAL_SURFS];\n\tint          num_draw_decals;\n\n\t// OpenGL matrix states\n\tqboolean     modelviewIdentity;\n\n\tint          visframecount;     // PVS frame\n\tint          dlightframecount;  // dynamic light frame\n\tint          realframecount;    // not including viewpasses\n\tint          framecount;\n\n\tqboolean     fCustomRendering;\n\tqboolean     fResetVis;\n\tqboolean     fFlipViewModel;\n\n\t// tree visualization stuff\n\tint          recursion_level;\n\tint          max_recursion;\n\n\tbyte         visbytes[( MAX_MAP_LEAFS + 7 ) / 8]; // member custom PVS\n\tint          lightstylevalue[MAX_LIGHTSTYLES];    // value 0 - 65536\n\tint          block_size;                          // lightmap blocksize\n\n\tdouble       frametime;         // special frametime for multipass rendering (will set to 0 on a nextview)\n\tfloat        blend;             // global blend value\n\n\t// cull info\n\tvec3_t       modelorg;                  // relative to viewpoint\n\n\tint          sample_size;\n\tuint         sample_bits;\n\tqboolean     map_unload;\n\n\t// get from engine\n\tcl_entity_t  *entities;\n\tmovevars_t   *movevars;\n\tcolor24      *palette;\n\tcl_entity_t  *viewent;\n\tlightstyle_t *lightstyles;\n\tdlight_t     *dlights;\n\tdlight_t     *elights;\n\tbyte         *texgammatable;\n\tuint         *lightgammatable;\n\tuint         *lineargammatable;\n\tuint         *screengammatable;\n\n\tuint         max_entities;\n} gl_globals_t;\n\ntypedef struct\n{\n\tuint   c_world_polys;\n\tuint   c_studio_polys;\n\tuint   c_sprite_polys;\n\tuint   c_alias_polys;\n\tuint   c_world_leafs;\n\n\tuint   c_view_beams_count;\n\tuint   c_active_tents_count;\n\tuint   c_alias_models_drawn;\n\tuint   c_studio_models_drawn;\n\tuint   c_sprite_models_drawn;\n\tuint   c_particle_count;\n\n\tuint   c_client_ents;           // entities that moved to client\n\tdouble t_world_node;\n\tdouble t_world_draw;\n} ref_speeds_t;\n\nextern ref_speeds_t   r_stats;\nextern ref_instance_t RI;\nextern gl_globals_t   tr;\n\n#define r_numEntities ( tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities )\n#define r_numStatics  ( r_stats.c_client_ents )\n\ntypedef struct image_s\n{\n\tchar           name[256];       // game path, including extension (can be store image programs)\n\tword           srcWidth;        // keep unscaled sizes\n\tword           srcHeight;\n\tword           width;           // upload width\\height\n\tword           height;\n\tword           depth;           // texture depth or count of layers for 2D_ARRAY\n\tbyte           numMips;         // mipmap count\n\n\n\ttexFlags_t     flags;\n\n\trgba_t         fogParams;       // some water textures\n\t                                // contain info about underwater fog\n\trgbdata_t      *original;       // keep original image\n\n\t// debug info\n\tsize_t         size;            // upload size for debug targets\n\n\t// detail textures stuff\n\tfloat          xscale;\n\tfloat          yscale;\n\n\timagetype_t    type;\n\tpixel_t        *pixels[4];                              // mip levels\n\tpixel_t        *alpha_pixels;                           // mip levels\n\n\tuint           hashValue;\n\tstruct image_s *nextHash;\n} image_t;\n\n//\n// gl_beams.c\n//\nvoid CL_DrawBeams( int fTrans, BEAM *active_beams );\nqboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly );\n\n//\n// gl_decals.c\n//\nvoid DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse );\nfloat *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount );\n// void DrawSingleDecal( decal_t *pDecal, msurface_t *fa );\nvoid R_EntityRemoveDecals( model_t *mod );\n// void DrawDecalsBatch( void );\nvoid R_ClearDecals( void );\nvoid R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] );\n\nvoid GL_Bind( int tmu, unsigned int texnum );\n\n//\n// gl_draw.c\n//\nvoid R_Set2DMode( qboolean enable );\nvoid R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ); //\n\n// gl_image.c\n//\nvoid R_SetTextureParameters( void );\nimage_t *R_GetTexture( unsigned int texnum );\n#define GL_LoadTextureInternal( name, pic, flags )   GL_LoadTextureFromBuffer( name, pic, flags, false )\n#define GL_UpdateTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, true )\nint GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags );\nint GL_LoadTextureArray( const char **names, int flags );\nint GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update );\nbyte *GL_ResampleTexture( const byte *source, int in_w, int in_h, int out_w, int out_h, qboolean isNormalMap );\nint GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags );\nint GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags );\nvoid GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor );\nvoid GL_UpdateTexSize( int texnum, int width, int height, int depth );\nvoid GL_ApplyTextureParams( image_t *tex );\nint GL_FindTexture( const char *name );\nvoid GL_FreeTexture( unsigned int texnum );\nconst char *GL_Target( unsigned int target );\nvoid R_InitDlightTexture( void );\nvoid R_TextureList_f( void );\nvoid R_InitImages( void );\nvoid R_ShutdownImages( void );\nint R_TexMemory( void );\n\n#if 1\n//\n// gl_rlight.c\n//\nvoid CL_RunLightStyles( lightstyle_t *ls );\nvoid R_PushDlights( void );\nvoid R_GetLightSpot( vec3_t lightspot );\nvoid R_MarkLights( dlight_t *light, int bit, mnode_t *node );\ncolorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec );\ncolorVec R_LightPoint( const vec3_t p0 );\n#endif\n//\n// gl_rmain.c\n//\nvoid R_ClearScene( void );\nvoid R_LoadIdentity( void );\nvoid R_RenderScene( void );\nvoid R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size );\nvoid R_SetupRefParams( const struct ref_viewpass_s *rvp );\nvoid R_TranslateForEntity( cl_entity_t *e );\nvoid R_RotateForEntity( cl_entity_t *e );\nvoid R_SetupGL( qboolean set_gl_state );\nqboolean R_OpaqueEntity( cl_entity_t *ent );\nvoid R_AllowFog( qboolean allowed );\nvoid R_SetupFrustum( void );\nvoid R_FindViewLeaf( void );\nvoid R_PushScene( void );\nvoid R_PopScene( void );\nvoid R_DrawFog( void );\n\n//\n// gl_rmath.c\n//\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 );\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z );\nvoid Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z );\nvoid Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar );\nvoid Matrix4x4_CreateOrtho( matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar );\nvoid Matrix4x4_CreateModelview( matrix4x4 out );\n\n//\n// gl_rpart.c\n//\nvoid CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime );\nvoid CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize );\nvoid CL_DrawTracers( double frametime, particle_t *cl_active_tracers );\n\n\n//\n// gl_sprite.c\n//\nvoid R_SpriteInit( void );\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags );\nmspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw );\nvoid R_DrawSpriteModel( cl_entity_t *e );\n\n//\n// gl_studio.c\n//\nvoid R_StudioInit( void );\nvoid Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded );\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\nstruct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e );\nint R_GetEntityRenderMode( cl_entity_t *ent );\nvoid R_DrawStudioModel( cl_entity_t *e );\nplayer_info_t *pfnPlayerInfo( int index );\nvoid R_GatherPlayerLight( void );\nfloat R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time );\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\nvoid R_StudioResetPlayerModels( void );\nvoid CL_InitStudioAPI( void );\nvoid Mod_StudioLoadTextures( model_t *mod, void *data );\nvoid Mod_StudioUnloadTextures( void *data );\n\n//\n// r_polyse.c\n//\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct\n{\n\tvoid    *pdest;\n\tshort   *pz;\n\tint     count;\n\tpixel_t *ptex;\n\tint     sfrac, tfrac, light, zi;\n} spanpackage_t;\n\nextern void (*d_pdrawspans)( spanpackage_t * );\nvoid R_PolysetFillSpans8( spanpackage_t * );\nvoid R_PolysetDrawSpans8_33( spanpackage_t * );\nvoid R_PolysetDrawSpansConstant8_33( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage );\n\n// #include \"vid_common.h\"\n\n//\n// renderer exports\n//\nqboolean R_Init( void );\nvoid R_Shutdown( void );\nvoid GL_SetupAttributes( int safegl );\nvoid GL_InitExtensions( void );\nvoid GL_ClearExtensions( void );\nvoid VID_CheckChanges( void );\nint GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags );\nvoid GL_FreeImage( const char *name );\nqboolean VID_ScreenShot( const char *filename, int shot_type );\nqboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot );\nvoid R_GammaChanged( qboolean do_reset_gamma );\nvoid R_BeginFrame( qboolean clearScene );\nvoid R_RenderFrame( const struct ref_viewpass_s *vp );\nvoid R_EndFrame( void );\nvoid R_ClearScene( void );\nvoid R_GetTextureParms( int *w, int *h, int texnum );\nvoid R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int curFrame, const struct model_s *pSprite );\nvoid R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty );\nvoid R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum );\nqboolean R_SpeedsMessage( char *out, size_t size );\nqboolean R_CullBox( const vec3_t mins, const vec3_t maxs );\nint R_WorldToScreen( const vec3_t point, vec3_t screen );\nvoid R_ScreenToWorld( const vec3_t screen, vec3_t point );\nqboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType );\nvoid Mod_SpriteUnloadTextures( void *data );\nvoid Mod_UnloadAliasModel( struct model_s *mod );\nvoid Mod_AliasUnloadTextures( void *data );\nvoid GL_SetRenderMode( int mode );\nvoid R_RunViewmodelEvents( void );\nvoid R_DrawViewModel( void );\nint R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame );\nvoid R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale );\nvoid R_DecalRemoveAll( int texture );\nint R_CreateDecalList( decallist_t *pList );\nvoid R_ClearAllDecals( void );\nbyte *Mod_GetCurrentVis( void );\nvoid Mod_SetOrthoBounds( const float *mins, const float *maxs );\nvoid R_NewMap( void );\nvoid CL_AddCustomBeam( cl_entity_t *pEnvBeam );\n\n//\n// gl_triapi.c\n//\nvoid TriRenderMode( int mode );\nvoid TriBegin( int mode );\nvoid TriEnd( void );\nvoid TriTexCoord2f( float u, float v );\nvoid TriVertex3fv( const float *v );\nvoid TriVertex3f( float x, float y, float z );\nvoid TriColor4f( float r, float g, float b, float a );\nvoid _TriColor4f( float r, float g, float b, float a );\nvoid TriColor4ub( byte r, byte g, byte b, byte a );\nvoid _TriColor4ub( byte r, byte g, byte b, byte a );\nint TriWorldToScreen( const float *world, float *screen );\nint TriSpriteTexture( model_t *pSpriteModel, int frame );\nvoid TriFog( float flFogColor[3], float flStart, float flEnd, int bOn );\nvoid TriGetMatrix( const int pname, float *matrix );\nvoid TriFogParams( float flDensity, int iFogSkybox );\nvoid TriCullFace( TRICULLSTYLE mode );\nvoid TriBrightness( float brightness );\n\n#define ENGINE_GET_PARM_ ( *gEngfuncs.EngineGetParm )\n#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_(( parm ), 0 )\n\nextern ref_api_t     gEngfuncs;\nextern ref_globals_t *gpGlobals;\nextern ref_client_t  *gp_cl;\nextern ref_host_t    *gp_host;\n\nDECLARE_ENGINE_SHARED_CVAR_LIST()\n\n//\n// helper funcs\n//\nstatic inline cl_entity_t *CL_GetEntityByIndex( int index )\n{\n\tif( unlikely( index < 0 || index >= tr.max_entities || !tr.entities ))\n\t\treturn NULL;\n\n\treturn &tr.entities[index];\n}\n\nstatic inline model_t *CL_ModelHandle( int index )\n{\n\tif( unlikely( index < 0 || index >= gp_cl->nummodels ))\n\t\treturn NULL;\n\n\treturn gp_cl->models[index];\n}\n\nstatic inline byte TextureToGamma( byte b )\n{\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.texgammatable[b] : b;\n}\n\nstatic inline uint LightToTexGamma( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lightgammatable[b] : b;\n}\n\nstatic inline uint ScreenGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.screengammatable[b] : b;\n}\n\nstatic inline uint LinearGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lineargammatable[b] : b;\n}\n\n#define WORLDMODEL ( gp_cl->models[1] )\n\n// todo: gl_cull.c\n#define R_CullModel( ... ) 0\n\n// softrender defs\n\n#define CACHE_SIZE 32\n\n/*\n====================================================\n  CONSTANTS\n====================================================\n*/\n\n#define VID_CBITS  6\n#define VID_GRADES ( 1 << VID_CBITS )\n\n\n// r_shared.h: general refresh-related stuff shared between the refresh and the\n// driver\n\n\n#define MAXVERTS        64               // max points in a surface polygon\n#define MAXWORKINGVERTS ( MAXVERTS + 4 ) // max points in an intermediate\n//  polygon (while processing)\n// !!! if this is changed, it must be changed in d_ifacea.h too !!!\n#define MAXHEIGHT 1200\n#define MAXWIDTH  1920\n\n#define INFINITE_DISTANCE 0x10000               // distance that's always guaranteed to\n//  be farther away than anything in\n//  the scene\n\n\n// d_iface.h: interface header file for rasterization driver modules\n\n#define WARP_WIDTH  320\n#define WARP_HEIGHT 240\n\n#define MAX_LBM_HEIGHT 480\n\n\n#define PARTICLE_Z_CLIP 8.0\n\n// !!! must be kept the same as in quakeasm.h !!!\n#define TRANSPARENT_COLOR 0x0349       // 0xFF\n\n\n// !!! if this is changed, it must be changed in d_ifacea.h too !!!\n#define TURB_TEX_SIZE 64                // base turbulent texture size\n\n// !!! if this is changed, it must be changed in d_ifacea.h too !!!\n#define CYCLE 128                               // turbulent cycle size\n\n#define SCANBUFFERPAD 0x1000\n\n#define DS_SPAN_LIST_END -128\n\n#define NUMSTACKEDGES    4000\n#define MINEDGES         NUMSTACKEDGES\n#define NUMSTACKSURFACES 2000\n#define MINSURFACES      NUMSTACKSURFACES\n#define MAXSPANS         6000\n\n// flags in finalvert_t.flags\n#define ALIAS_LEFT_CLIP    0x0001\n#define ALIAS_TOP_CLIP     0x0002\n#define ALIAS_RIGHT_CLIP   0x0004\n#define ALIAS_BOTTOM_CLIP  0x0008\n#define ALIAS_Z_CLIP       0x0010\n#define ALIAS_XY_CLIP_MASK 0x000F\n\n#define SURFCACHE_SIZE_AT_320X240 1024 * 768\n\n#define BMODEL_FULLY_CLIPPED 0x10    // value returned by R_BmodelCheckBBox ()\n//  if bbox is trivially rejected\n\n#define XCENTERING ( 1.0f / 2.0f )\n#define YCENTERING ( 1.0f / 2.0f )\n\n#define CLIP_EPSILON 0.001f\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\n#define NEAR_CLIP 0.01f\n\n\n// #define MAXALIASVERTS           2000    // TODO: tune this\n#define ALIAS_Z_CLIP_PLANE 4\n\n// turbulence stuff\n\n#define AMP   8 * 0x10000\n#define AMP2  3\n#define SPEED 20\n\n\n/*\n====================================================\nTYPES\n====================================================\n*/\n\ntypedef struct\n{\n\tfloat u, v;\n\tfloat s, t;\n\tfloat zi;\n} emitpoint_t;\n\n/*\n** if you change this structure be sure to change the #defines\n** listed after it!\n*/\n#define SMALL_FINALVERT 0\n\n#if SMALL_FINALVERT\n\ntypedef struct finalvert_s\n{\n\tshort u, v, s, t;\n\tint   l;\n\tint   zi;\n\tint   flags;\n\tfloat xyz[3]; // eye space\n} finalvert_t;\n\n#define FINALVERT_V0    0\n#define FINALVERT_V1    2\n#define FINALVERT_V2    4\n#define FINALVERT_V3    6\n#define FINALVERT_V4    8\n#define FINALVERT_V5    12\n#define FINALVERT_FLAGS 16\n#define FINALVERT_X     20\n#define FINALVERT_Y     24\n#define FINALVERT_Z     28\n#define FINALVERT_SIZE  32\n\n#else\n\ntypedef struct finalvert_s\n{\n\tint   u, v, s, t;\n\tint   l;\n\tint   zi;\n\tint   flags;\n\tfloat xyz[3]; // eye space\n} finalvert_t;\n\n#define FINALVERT_V0    0\n#define FINALVERT_V1    4\n#define FINALVERT_V2    8\n#define FINALVERT_V3    12\n#define FINALVERT_V4    16\n#define FINALVERT_V5    20\n#define FINALVERT_FLAGS 24\n#define FINALVERT_X     28\n#define FINALVERT_Y     32\n#define FINALVERT_Z     36\n#define FINALVERT_SIZE  40\n\n#endif\n\n\ntypedef struct\n{\n\tshort s;\n\tshort t;\n} dstvert_t;\n\ntypedef struct\n{\n\tshort index_xyz[3];\n\tshort index_st[3];\n} dtriangle_t;\n\ntypedef struct\n{\n\tbyte v[3]; // scaled byte to fit in frame mins/maxs\n\tbyte lightnormalindex;\n} dtrivertx_t;\n\n#define DTRIVERTX_V0   0\n#define DTRIVERTX_V1   1\n#define DTRIVERTX_V2   2\n#define DTRIVERTX_LNI  3\n#define DTRIVERTX_SIZE 4\n\ntypedef struct\n{\n\tvoid        *pskin;\n\tint         pskindesc;\n\tint         skinwidth;\n\tint         skinheight;\n\tdtriangle_t *ptriangles;\n\tfinalvert_t *pfinalverts;\n\tint         numtriangles;\n\tint         drawtype;\n\tint         seamfixupX16;\n\tqboolean    do_vis_thresh;\n\tint         vis_thresh;\n} affinetridesc_t;\n\n\n\ntypedef struct\n{\n\tpixel_t    *surfdat;            // destination for generated surface\n\tint        rowbytes;            // destination logical width in bytes\n\tmsurface_t *surf;               // description for surface to generate\n\tfixed8_t   lightadj[MAXLIGHTMAPS];\n\t// adjust for lightmap levels for dynamic lighting\n\timage_t    *image;\n\tint        surfmip;                     // mipmapped ratio of surface texels / world pixels\n\tint        surfwidth;                   // in mipmapped texels\n\tint        surfheight;                  // in mipmapped texels\n} drawsurf_t;\n\n// clipped bmodel edges\ntypedef struct bedge_s\n{\n\tmvertex_t      *v[2];\n\tstruct bedge_s *pnext;\n} bedge_t;\n\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct clipplane_s\n{\n\tvec3_t                      normal;\n\tfloat                       dist;\n\tstruct          clipplane_s *next;\n\tbyte                        leftedge;\n\tbyte                        rightedge;\n\tbyte                        reserved[2];\n} clipplane_t;\n\n\ntypedef struct surfcache_s\n{\n\tstruct surfcache_s *next;\n\tstruct surfcache_s **owner;                     // NULL is an empty chunk of memory\n\tint                lightadj[MAXLIGHTMAPS];      // checked for strobe flush\n\tint                dlight;\n\tint                size;                                // including header\n\tunsigned           width;\n\tunsigned           height;                      // DEBUG only needed for debug\n\tfloat              mipscale;\n\timage_t            *image;\n\tbyte               data[4];                     // width*height elements\n} surfcache_t;\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct espan_s\n{\n\tint            u, v, count;\n\tstruct espan_s *pnext;\n} espan_t;\n\n\n// FIXME: compress, make a union if that will help\n// insubmodel is only 1, flags is fewer than 32, spanstate could be a byte\ntypedef struct surf_s\n{\n\tstruct surf_s  *next;                   // active surface stack in r_edge.c\n\tstruct surf_s  *prev;                   // used in r_edge.c for active surf stack\n\tstruct espan_s *spans;                  // pointer to linked list of spans to draw\n\tint            key;                     // sorting key (BSP order)\n\tint            last_u;                  // set during tracing\n\tint            spanstate;               // 0 = not in span\n\t// 1 = in span\n\t// -1 = in inverted span (end before\n\t//  start)\n\tint         flags;                                      // currentface flags\n\tmsurface_t  *msurf;\n\tcl_entity_t *entity;\n\tfloat       nearzi;                             // nearest 1/z on surface, for mipmapping\n\tqboolean    insubmodel;\n\tfloat       d_ziorigin, d_zistepu, d_zistepv;\n\n\tint         pad[2];                                     // to 64 bytes\n} surf_t;\n\n// !!! if this is changed, it must be changed in asm_draw.h too !!!\ntypedef struct edge_s\n{\n\tfixed16_t      u;\n\tfixed16_t      u_step;\n\tstruct edge_s  *prev, *next;\n\tunsigned short surfs[2];\n\tstruct edge_s  *nextremove;\n\tfloat          nearzi;\n\tmedge16_t      *owner;\n} edge_t;\n\n\n/*\n====================================================\nVARS\n====================================================\n*/\n\n//  started\nextern float           r_aliasuvscale;  // scale-up factor for screen u and v\n//  on Alias vertices passed to driver\n\nextern affinetridesc_t r_affinetridesc;\n\nvoid D_DrawSurfaces( void );\nvoid R_DrawParticle( void );\nvoid D_ViewChanged( void );\n\n// =======================================================================//\n\n// callbacks to Quake\n\nextern drawsurf_t r_drawsurf;\n\nvoid R_DrawSurface( void );\n\n// extern int              c_surf;\n\nextern byte r_warpbuffer[WARP_WIDTH * WARP_HEIGHT];\n\n\n\n\nextern float       scale_for_mip;\n\nextern qboolean    d_roverwrapped;\nextern surfcache_t *sc_rover;\nextern surfcache_t *d_initial_rover;\n\nextern float       d_sdivzstepu, d_tdivzstepu, d_zistepu;\nextern float       d_sdivzstepv, d_tdivzstepv, d_zistepv;\nextern float       d_sdivzorigin, d_tdivzorigin, d_ziorigin;\n\nextern fixed16_t   sadjust, tadjust;\nextern fixed16_t   bbextents, bbextentt;\n\n\nvoid D_DrawSpans16( espan_t *pspans );\nvoid D_DrawZSpans( espan_t *pspans );\nvoid Turbulent8( espan_t *pspan );\nvoid NonTurbulent8( espan_t *pspan ); // PGM\nvoid D_BlendSpans16( espan_t *pspan, int alpha );\nvoid D_AlphaSpans16( espan_t *pspan );\nvoid D_AddSpans16( espan_t *pspan );\nvoid TurbulentZ8( espan_t *pspan, int alpha );\n\nsurfcache_t     *D_CacheSurface( msurface_t *surface, int miplevel );\n\n\nextern pixel_t      *d_viewbuffer;\nextern short        *d_pzbuffer;\nextern unsigned int d_zrowbytes, d_zwidth;\nextern short        *zspantable[MAXHEIGHT];\nextern int          d_scantable[MAXHEIGHT];\n\nextern int          d_minmip;\nextern float        d_scalemip[3];\n\n// ===================================================================\n\nextern int     cachewidth;\nextern pixel_t *cacheblock;\nextern int     r_screenwidth;\n\n\nextern int     sintable[1280];\nextern int     intsintable[1280];\nextern int     blanktable[1280];                        // PGM\n\nextern surf_t  *surfaces, *surface_p, *surf_max;\n\n// surfaces are generated in back to front order by the bsp, so if a surf\n// pointer is greater than another one, it should be drawn in front\n// surfaces[1] is the background, and is used as the active surface stack.\n// surfaces[0] is a dummy, because index 0 is used to indicate no surface\n//  attached to an edge_t\n\n// ===================================================================\n\n// extern vec3_t   sxformaxis[4];  // s axis transformed into viewspace\n// extern vec3_t   txformaxis[4];  // t axis transformed into viewspac\n\nextern float xcenter, ycenter;\nextern float xscale, yscale;\nextern float xscaleinv, yscaleinv;\n// extern float xscaleshrink, yscaleshrink;\n\n\nextern edge_t *auxedges;\nextern int    r_numallocatededges;\nextern edge_t *r_edges, *edge_p, *edge_max;\n\nextern edge_t *newedges[MAXHEIGHT];\nextern edge_t *removeedges[MAXHEIGHT];\n\nextern int    r_viewcluster, r_oldviewcluster;\n\nextern int    r_clipflags;\n// extern qboolean r_fov_greater_than_90;\n\n\nextern convar_t sw_clearcolor;\nextern convar_t sw_drawflat;\nextern convar_t sw_draworder;\nextern convar_t sw_maxedges;\nextern convar_t sw_mipcap;\nextern convar_t sw_mipscale;\nextern convar_t sw_surfcacheoverride;\nextern convar_t sw_texfilt;\nextern convar_t r_traceglow;\nextern convar_t sw_noalphabrushes;\nextern convar_t r_studio_sort_textures;\n\nextern struct qfrustum_s\n{\n\tmplane_t    screenedge[4];\n\tclipplane_t view_clipplanes[4];\n\tint         frustum_indexes[4 * 6];\n\tint         *pfrustum_indexes[4];\n} qfrustum;\n\n#define CACHESPOT( surf ) ((surfcache_t **)surf->info->reserved )\nextern int            r_currentkey;\nextern int            r_currentbkey;\nextern qboolean       insubmodel;\n\nextern vec3_t         r_entorigin;\n#if XASH_LOW_MEMORY\nextern unsigned short r_leafkeys[MAX_MAP_LEAFS];\n#else\nextern int            r_leafkeys[MAX_MAP_LEAFS];\n#endif\n#define LEAF_KEY( pleaf ) r_leafkeys[( pleaf - WORLDMODEL->leafs )]\n\n\n\nextern mvertex_t *r_pcurrentvertbase;\n// extern int                      r_maxvalidedgeoffset;\n\ntypedef struct\n{\n\tfinalvert_t *a, *b, *c;\n} aliastriangleparms_t;\n\nextern aliastriangleparms_t aliastriangleparms;\n\n\nextern int   r_aliasblendcolor;\n\nextern float aliasxscale, aliasyscale, aliasxcenter, aliasycenter;\nextern float s_ziscale;\n\nvoid R_DrawTriangle( void );\n// void R_DrawTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t *index2);\nvoid R_AliasClipTriangle( finalvert_t *index0, finalvert_t *index1, finalvert_t *index2 );\n\n//\n// r_bsp.c\n//\nvoid R_RotateBmodel( void );\nvoid R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode );\nvoid R_DrawSubmodelPolygons( model_t *pmodel, int clipflags, mnode_t *topnode );\nvoid R_DrawBrushModel( cl_entity_t *pent );\n\n//\n// r_blitscreen.c\n//\nvoid R_InitCaches( void );\nvoid R_BlitScreen( void );\nqboolean R_InitBlit( qboolean gl );\nqboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y );\n\n//\n// r_edge.c\n//\nstatic inline void R_SurfacePatch( void ) {\n}\nvoid R_BeginEdgeFrame( void );\nvoid R_RenderWorld( void );\nvoid R_ScanEdges( void );\n\n\n//\n// r_surf.c\n//\nvoid GL_InitRandomTable( void );\nvoid D_FlushCaches( void );\n\n//\n// r_draw.c\n//\nvoid Draw_Fill( int x, int y, int w, int h );\n\n//\n// r_misc.c\n//\nvoid R_SetupFrameQ( void );\nvoid R_TransformFrustum( void );\nvoid TransformVector( vec3_t in, vec3_t out );\n\n//\n// r_rast.c\n//\nvoid R_RenderBmodelFace( bedge_t *pedges, msurface_t *psurf );\nvoid R_RenderFace( msurface_t *fa, int clipflags );\n\n//\n// r_main.c\n//\n\nvoid R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 );\nvoid R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, int s, int t );\nvoid RotatedBBox( vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs );\nint R_BmodelCheckBBox( float *minmaxs );\nint CL_FxBlend( cl_entity_t *e );\n\n\nvoid R_SetUpWorldTransform( void );\n\n#define BLEND_ALPHA_LOW( alpha, src, screen ) ( vid.alphamap[(( alpha ) << 18 ) | ((( src ) & 0xff00 ) << 2 ) | (( screen ) >> 6 )] | (( screen ) & 0x3f ))\n#define BLEND_ALPHA( alpha, src, dst )        ( alpha ) > 3 ? BLEND_ALPHA_LOW( 7 - 1 - ( alpha ), ( dst ), ( src )) : BLEND_ALPHA_LOW(( alpha ) - 1, ( src ), ( dst ))\n#define BLEND_ADD( src, screen )              vid.addmap[(( src ) & 0xff00 ) | (( screen ) >> 8 )] << 8 | (( screen ) & 0xff ) | ((( src ) & 0xff ) >> 0 );\n#define BLEND_COLOR( src, color )             vid.modmap[(( src ) & 0xff00 ) | (( color ) >> 8 )] << 8 | (( src ) & ( color ) & 0xff ) | ((( src ) & 0xff ) >> 3 );\n\n#define LM_SAMPLE_SIZE_AUTO( surf ) ( tr.sample_size == -1 ? gEngfuncs.Mod_SampleSizeForFace( surf ) : tr.sample_size )\n\n\n\n//\n// engine callbacks\n//\n#include \"crtlib.h\"\n#include \"crclib.h\"\nvoid _Mem_Free( void *data, const char *filename, int fileline );\nvoid *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline )\nALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT;\n\n#define Mem_Malloc( pool, size )       _Mem_Alloc( pool, size, false, __FILE__, __LINE__ )\n#define Mem_Calloc( pool, size )       _Mem_Alloc( pool, size, true, __FILE__, __LINE__ )\n#define Mem_Realloc( pool, ptr, size ) gEngfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ )\n#define Mem_Free( mem )                _Mem_Free( mem, __FILE__, __LINE__ )\n#define Mem_AllocPool( name )          gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ )\n#define Mem_FreePool( pool )           gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ )\n#define Mem_EmptyPool( pool )          gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ )\n\n#endif // GL_LOCAL_H\n"
  },
  {
    "path": "ref/soft/r_main.c",
    "content": "/*\ngl_rmain.c - renderer main loop\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"library.h\"\n// #include \"beamdef.h\"\n// #include \"particledef.h\"\n#include \"entity_types.h\"\n#include \"mod_local.h\"\nint r_cnumsurfs;\n#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA )\n\nref_instance_t RI;\n\n\n// quake defines. will be refactored\n\n// view origin\n//\n\n//\n// screen size info\n//\nfloat xcenter, ycenter;\nfloat xscale, yscale;\nfloat xscaleinv, yscaleinv;\n// float\t\txscaleshrink, yscaleshrink;\nfloat aliasxscale, aliasyscale, aliasxcenter, aliasycenter;\n\nint   r_screenwidth;\n\n\n\n\n//\n// refresh flags\n//\n\n// int             d_spanpixcount;\n// int             r_polycount;\n// int             r_drawnpolycount;\n// int             r_wholepolycount;\n\nint r_viewcluster, r_oldviewcluster;\n\nCVAR_DEFINE_AUTO( sw_clearcolor, \"48999\", 0, \"screen clear color\" );\nCVAR_DEFINE_AUTO( sw_drawflat, \"0\", FCVAR_CHEAT, \"\" );\nCVAR_DEFINE_AUTO( sw_draworder, \"0\", FCVAR_CHEAT, \"\" );\nCVAR_DEFINE_AUTO( sw_maxedges, \"32\", 0, \"\" );\nstatic CVAR_DEFINE_AUTO( sw_maxsurfs, \"0\", 0, \"\" );\nCVAR_DEFINE_AUTO( sw_mipscale, \"1\", FCVAR_GLCONFIG, \"nothing\" );\nCVAR_DEFINE_AUTO( sw_mipcap, \"0\", FCVAR_GLCONFIG, \"nothing\" );\nCVAR_DEFINE_AUTO( sw_surfcacheoverride, \"0\", 0, \"\" );\nstatic CVAR_DEFINE_AUTO( sw_waterwarp, \"1\", FCVAR_GLCONFIG, \"nothing\" );\nstatic CVAR_DEFINE_AUTO( sw_notransbrushes, \"0\", FCVAR_GLCONFIG, \"do not apply transparency to water/glasses (faster)\" );\nCVAR_DEFINE_AUTO( sw_noalphabrushes, \"0\", FCVAR_GLCONFIG, \"do not draw brush holes (faster)\" );\nCVAR_DEFINE_AUTO( r_traceglow, \"0\", FCVAR_GLCONFIG, \"cull flares behind models\" );\nCVAR_DEFINE_AUTO( sw_texfilt, \"0\", FCVAR_GLCONFIG, \"texture dither\" );\nstatic CVAR_DEFINE_AUTO( r_novis, \"0\", 0, \"\" );\n\n\nDEFINE_ENGINE_SHARED_CVAR_LIST()\n\nint r_viewcluster, r_oldviewcluster;\n\nfloat        d_sdivzstepu, d_tdivzstepu, d_zistepu;\nfloat        d_sdivzstepv, d_tdivzstepv, d_zistepv;\nfloat        d_sdivzorigin, d_tdivzorigin, d_ziorigin;\n\nfixed16_t    sadjust, tadjust, bbextents, bbextentt;\n\npixel_t      *cacheblock;\nint          cachewidth;\npixel_t      *d_viewbuffer;\nshort        *d_pzbuffer;\nunsigned int d_zrowbytes;\nunsigned int d_zwidth;\n\nmvertex_t    *r_pcurrentvertbase;\n\n// int                     c_surf;\nqboolean     r_surfsonstack;\nint          r_clipflags;\nbyte         r_warpbuffer[WARP_WIDTH * WARP_HEIGHT];\nint          r_numallocatededges;\n\nfloat        r_aliasuvscale = 1.0;\n\nstatic int R_RankForRenderMode( int rendermode )\n{\n\tswitch( rendermode )\n\t{\n\tcase kRenderTransTexture:\n\t\treturn 1; // draw second\n\tcase kRenderTransAdd:\n\t\treturn 2; // draw third\n\tcase kRenderGlow:\n\t\treturn 3; // must be last!\n\t}\n\treturn 0;\n}\n\nvoid GAME_EXPORT R_AllowFog( qboolean allowed )\n{\n}\n\n/*\n===============\nR_OpaqueEntity\n\nOpaque entity can be brush or studio model but sprite\n===============\n*/\nqboolean R_OpaqueEntity( cl_entity_t *ent )\n{\n\tint rendermode = R_GetEntityRenderMode( ent );\n\n\tif( rendermode == kRenderNormal )\n\t{\n\t\tswitch( ent->curstate.renderfx )\n\t\t{\n\t\tcase kRenderFxNone:\n\t\tcase kRenderFxDeadPlayer:\n\t\tcase kRenderFxLightMultiplier:\n\t\tcase kRenderFxExplode:\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif( sw_notransbrushes.value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransTexture )\n\t\treturn true;\n\n\tif( sw_noalphabrushes.value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransAlpha )\n\t\treturn true;\n\n\treturn false;\n}\n\n/*\n===============\nR_TransEntityCompare\n\nSorting translucent entities by rendermode then by distance\n===============\n*/\nstatic int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b )\n{\n\tcl_entity_t *ent1, *ent2;\n\tvec3_t      vecLen, org;\n\tfloat       dist1, dist2;\n\tint         rendermode1;\n\tint         rendermode2;\n\n\tent1 = (cl_entity_t *)*a;\n\tent2 = (cl_entity_t *)*b;\n\trendermode1 = R_GetEntityRenderMode( ent1 );\n\trendermode2 = R_GetEntityRenderMode( ent2 );\n\n\t// sort by distance\n\tif(( ent1->model && ent1->model->type != mod_brush ) || rendermode1 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent1->model->mins, ent1->model->maxs, org );\n\t\tVectorAdd( ent1->origin, org, org );\n\t\tVectorSubtract( RI.vieworg, org, vecLen );\n\t\tdist1 = DotProduct( vecLen, vecLen );\n\t}\n\telse\n\t\tdist1 = 1000000000;\n\n\tif(( ent1->model && ent2->model->type != mod_brush ) || rendermode2 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent2->model->mins, ent2->model->maxs, org );\n\t\tVectorAdd( ent2->origin, org, org );\n\t\tVectorSubtract( RI.vieworg, org, vecLen );\n\t\tdist2 = DotProduct( vecLen, vecLen );\n\t}\n\telse\n\t\tdist2 = 1000000000;\n\n\tif( dist1 > dist2 )\n\t\treturn -1;\n\tif( dist1 < dist2 )\n\t\treturn 1;\n\n\t// then sort by rendermode\n\tif( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 ))\n\t\treturn 1;\n\tif( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 ))\n\t\treturn -1;\n\n\treturn 0;\n}\n\n/*\n===============\nR_WorldToScreen\n\nConvert a given point from world into screen space\nReturns true if we behind to screen\n===============\n*/\nint R_WorldToScreen( const vec3_t point, vec3_t screen )\n{\n\tmatrix4x4 worldToScreen;\n\tqboolean  behind;\n\tfloat     w;\n\n\tif( !point || !screen )\n\t\treturn true;\n\n\tMatrix4x4_Copy( worldToScreen, RI.worldviewProjectionMatrix );\n\tscreen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3];\n\tscreen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3];\n\tw = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3];\n\tscreen[2] = 0.0f; // just so we have something valid here\n\n\tif( w < 0.001f )\n\t{\n\t\tbehind = true;\n\t}\n\telse\n\t{\n\t\tfloat invw = 1.0f / w;\n\t\tscreen[0] *= invw;\n\t\tscreen[1] *= invw;\n\t\tbehind = false;\n\t}\n\n\treturn behind;\n}\n\n/*\n===============\nR_ScreenToWorld\n\nConvert a given point from screen into world space\n===============\n*/\nvoid GAME_EXPORT R_ScreenToWorld( const vec3_t screen, vec3_t point )\n{\n\tmatrix4x4 screenToWorld;\n\tfloat     w;\n\n\tif( !point || !screen )\n\t\treturn;\n\n\tMatrix4x4_Invert_Full( screenToWorld, RI.worldviewProjectionMatrix );\n\n\tpoint[0] = screen[0] * screenToWorld[0][0] + screen[1] * screenToWorld[0][1] + screen[2] * screenToWorld[0][2] + screenToWorld[0][3];\n\tpoint[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3];\n\tpoint[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3];\n\tw = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3];\n\tif( w != 0.0f )\n\t\tVectorScale( point, ( 1.0f / w ), point );\n}\n\n/*\n===============\nR_PushScene\n===============\n*/\nvoid GAME_EXPORT R_PushScene( void )\n{\n\tif( ++tr.draw_stack_pos >= MAX_DRAW_STACK )\n\t\tgEngfuncs.Host_Error( \"draw stack overflow\\n\" );\n\n\ttr.draw_list = &tr.draw_stack[tr.draw_stack_pos];\n}\n\n/*\n===============\nR_PopScene\n===============\n*/\nvoid GAME_EXPORT R_PopScene( void )\n{\n\tif( --tr.draw_stack_pos < 0 )\n\t\tgEngfuncs.Host_Error( \"draw stack underflow\\n\" );\n\ttr.draw_list = &tr.draw_stack[tr.draw_stack_pos];\n}\n\n/*\n===============\nR_ClearScene\n===============\n*/\nvoid GAME_EXPORT R_ClearScene( void )\n{\n\ttr.draw_list->num_solid_entities = 0;\n\ttr.draw_list->num_trans_entities = 0;\n\ttr.draw_list->num_beam_entities = 0;\n\ttr.draw_list->num_edge_entities = 0;\n\n\t// clear the scene befor start new frame\n\tif( gEngfuncs.drawFuncs->R_ClearScene != NULL )\n\t\tgEngfuncs.drawFuncs->R_ClearScene();\n\n}\n\n/*\n===============\nR_AddEntity\n===============\n*/\nqboolean GAME_EXPORT R_AddEntity( struct cl_entity_s *clent, int type )\n{\n\tif( !r_drawentities->value )\n\t\treturn false; // not allow to drawing\n\n\tif( !clent || !clent->model )\n\t\treturn false; // if set to invisible, skip\n\n\tif( FBitSet( clent->curstate.effects, EF_NODRAW ))\n\t\treturn false; // done\n\n\tif( !R_ModelOpaque( clent->curstate.rendermode ) && CL_FxBlend( clent ) <= 0 )\n\t\treturn true; // invisible\n\n\tif( type == ET_FRAGMENTED )\n\t\tr_stats.c_client_ents++;\n\n\tif( R_OpaqueEntity( clent ))\n\t{\n\t\tif( clent->model->type == mod_brush )\n\t\t{\n\t\t\tif( tr.draw_list->num_edge_entities >= MAX_VISIBLE_PACKET )\n\t\t\t\treturn false;\n\n\t\t\ttr.draw_list->edge_entities[tr.draw_list->num_edge_entities] = clent;\n\t\t\ttr.draw_list->num_edge_entities++;\n\t\t\treturn true;\n\t\t}\n\t\t// opaque\n\t\tif( tr.draw_list->num_solid_entities >= MAX_VISIBLE_PACKET )\n\t\t\treturn false;\n\n\t\ttr.draw_list->solid_entities[tr.draw_list->num_solid_entities] = clent;\n\t\ttr.draw_list->num_solid_entities++;\n\t}\n\telse\n\t{\n\t\t// translucent\n\t\tif( tr.draw_list->num_trans_entities >= MAX_VISIBLE_PACKET )\n\t\t\treturn false;\n\n\t\ttr.draw_list->trans_entities[tr.draw_list->num_trans_entities] = clent;\n\t\ttr.draw_list->num_trans_entities++;\n\t}\n\n\treturn true;\n}\n\n/*\n=============\nR_Clear\n=============\n*/\nstatic void R_Clear( int bitMask )\n{\n\tmemset( vid.buffer, 0, vid.width * vid.height * 2 );\n}\n\n// =============================================================================\n/*\n===============\nR_GetFarClip\n===============\n*/\nstatic float R_GetFarClip( void )\n{\n\tif( WORLDMODEL && RI.drawWorld )\n\t\treturn tr.movevars->zmax * 1.73f;\n\treturn 2048.0f;\n}\n\n/*\n===============\nR_SetupFrustum\n===============\n*/\nvoid R_SetupFrustum( void )\n{\n\t// build the transformation matrix for the given view angles\n\tAngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup );\n\n\t{\n\t\tVectorCopy( RI.vieworg, RI.cullorigin );\n\t\tVectorCopy( RI.vforward, RI.cull_vforward );\n\t\tVectorCopy( RI.vright, RI.cull_vright );\n\t\tVectorCopy( RI.vup, RI.cull_vup );\n\t}\n}\n\n/*\n=============\nR_SetupProjectionMatrix\n=============\n*/\nstatic void R_SetupProjectionMatrix( matrix4x4 m )\n{\n\tfloat xMin, xMax, yMin, yMax, zNear, zFar;\n\n\tif( RI.drawOrtho )\n\t{\n\t\tconst ref_overview_t *ov = gEngfuncs.GetOverviewParms();\n\t\tMatrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );\n\t\treturn;\n\t}\n\n\tRI.farClip = R_GetFarClip();\n\n\tzNear = 4.0f;\n\tzFar = Q_max( 256.0f, RI.farClip );\n\n\tyMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f );\n\tyMin = -yMax;\n\n\txMax = zNear * tan( RI.fov_x * M_PI_F / 360.0f );\n\txMin = -xMax;\n\n\tMatrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar );\n}\n\n/*\n=============\nR_SetupModelviewMatrix\n=============\n*/\nstatic void R_SetupModelviewMatrix( matrix4x4 m )\n{\n\tMatrix4x4_CreateModelview( m );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[2], 1, 0, 0 );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[0], 0, 1, 0 );\n\tMatrix4x4_ConcatRotate( m, -RI.viewangles[1], 0, 0, 1 );\n\tMatrix4x4_ConcatTranslate( m, -RI.vieworg[0], -RI.vieworg[1], -RI.vieworg[2] );\n}\n\n/*\n=============\nR_LoadIdentity\n=============\n*/\nvoid R_LoadIdentity( void )\n{\n}\n\n/*\n=============\nR_RotateForEntity\n=============\n*/\nvoid R_RotateForEntity( cl_entity_t *e )\n{\n}\n\n/*\n=============\nR_TranslateForEntity\n=============\n*/\nvoid R_TranslateForEntity( cl_entity_t *e )\n{\n}\n\n/*\n===============\nR_FindViewLeaf\n===============\n*/\nvoid R_FindViewLeaf( void )\n{\n\tRI.oldviewleaf = RI.viewleaf;\n\tRI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.pvsorigin, WORLDMODEL->nodes );\n}\n\n/*\n===============\nR_SetupFrame\n===============\n*/\nstatic void R_SetupFrame( void )\n{\n\t// setup viewplane dist\n\tRI.viewplanedist = DotProduct( RI.vieworg, RI.vforward );\n\n//\tif( !gl_nosort->value )\n\t{\n\t\t// sort translucents entities by rendermode and distance\n\t\tqsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t * ), (void *)R_TransEntityCompare );\n\t}\n\n\t// current viewleaf\n\tif( RI.drawWorld )\n\t{\n\t\tRI.isSkyVisible = false; // unknown at this moment\n\t\tR_FindViewLeaf();\n\t}\n\n\t// setup twice until globals fully refactored\n\tR_SetupFrameQ();\n}\n\n/*\n=============\nR_RecursiveFindWaterTexture\n\nusing to find source waterleaf with\nwatertexture to grab fog values from it\n=============\n*/\nstatic image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down )\n{\n\timage_t *tex = NULL;\n\tmnode_t *children[2];\n\n\t// assure the initial node is not null\n\t// we could check it here, but we would rather check it\n\t// outside the call to get rid of one additional recursion level\n\tAssert( node != NULL );\n\n\t// ignore solid nodes\n\tif( node->contents == CONTENTS_SOLID )\n\t\treturn NULL;\n\n\tif( node->contents < 0 )\n\t{\n\t\tmleaf_t    *pleaf;\n\t\tmsurface_t **mark;\n\t\tint        i, c;\n\n\t\t// ignore non-liquid leaves\n\t\tif( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME )\n\t\t\treturn NULL;\n\n\t\t// find texture\n\t\tpleaf = (mleaf_t *)node;\n\t\tmark = pleaf->firstmarksurface;\n\t\tc = pleaf->nummarksurfaces;\n\n\t\tfor( i = 0; i < c; i++, mark++ )\n\t\t{\n\t\t\tif(( *mark )->flags & SURF_DRAWTURB && ( *mark )->texinfo && ( *mark )->texinfo->texture )\n\t\t\t\treturn R_GetTexture(( *mark )->texinfo->texture->gl_texturenum );\n\t\t}\n\n\t\t// texture not found\n\t\treturn NULL;\n\t}\n\n\t// this is a regular node\n\t// traverse children\n\tnode_children( children, node, WORLDMODEL );\n\n\tif( children[0] && ( children[0] != ignore ))\n\t{\n\t\ttex = R_RecursiveFindWaterTexture( children[0], node, true );\n\t\tif( tex ) return tex;\n\t}\n\n\tif( children[1] && ( children[1] != ignore ))\n\t{\n\t\ttex = R_RecursiveFindWaterTexture( children[1], node, true );\n\t\tif( tex )\treturn tex;\n\t}\n\n\t// for down recursion, return immediately\n\tif( down )\n\t\treturn NULL;\n\n\t// texture not found, step up if any\n\tif( node->parent )\n\t\treturn R_RecursiveFindWaterTexture( node->parent, node, false );\n\n\t// top-level node, bail out\n\treturn NULL;\n}\n\n/*\n=============\nR_DrawEntitiesOnList\n=============\n*/\nstatic void R_DrawEntitiesOnList( void )\n{\n\tint i;\n\t// extern int d_aflatcolor;\n\t// d_aflatcolor = 0;\n\ttr.blend = 1.0f;\n//\tGL_CheckForErrors();\n\t// RI.currententity = CL_GetEntityByIndex(0);\n\td_pdrawspans = R_PolysetFillSpans8;\n\tGL_SetRenderMode( kRenderNormal );\n\t// first draw solid entities\n\tfor( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->solid_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\t\t// d_aflatcolor += 500;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_brush:\n\t\t\tR_DrawBrushModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_alias:\n\t\t\t// R_DrawAliasModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_studio:\n\t\t\tR_SetUpWorldTransform();\n\t\t\tR_DrawStudioModel( RI.currententity );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tR_SetUpWorldTransform();\n\t// draw sprites seperately, because of alpha blending\n\tfor( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->solid_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_sprite:\n\t\t\tR_DrawSpriteModel( RI.currententity );\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( !RI.onlyClientDraw )\n\t{\n\t\tgEngfuncs.CL_DrawEFX( tr.frametime, false );\n\t}\n\n\tif( RI.drawWorld )\n\t\tgEngfuncs.pfnDrawNormalTriangles();\n\n\td_pdrawspans = R_PolysetDrawSpans8_33;\n\t// then draw translucent entities\n\tfor( i = 0; i < tr.draw_list->num_trans_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tRI.currententity = tr.draw_list->trans_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\n\t\t// handle studiomodels with custom rendermodes on texture\n\t\tif( RI.currententity->curstate.rendermode != kRenderNormal )\n\t\t\ttr.blend = CL_FxBlend( RI.currententity ) / 255.0f;\n\t\telse\n\t\t\ttr.blend = 1.0f; // draw as solid but sorted by distance\n\n\t\tif( tr.blend <= 0.0f )\n\t\t\tcontinue;\n\n\t\tAssert( RI.currententity != NULL );\n\t\tAssert( RI.currentmodel != NULL );\n\n\t\tswitch( RI.currentmodel->type )\n\t\t{\n\t\tcase mod_brush:\n\t\t\tR_DrawBrushModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_alias:\n\t\t\t// R_DrawAliasModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_studio:\n\t\t\tR_SetUpWorldTransform();\n\t\t\tR_DrawStudioModel( RI.currententity );\n\t\t\tbreak;\n\t\tcase mod_sprite:\n\t\t\tR_SetUpWorldTransform();\n\t\t\tR_DrawSpriteModel( RI.currententity );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif( RI.drawWorld )\n\t{\n\t\tgEngfuncs.pfnDrawTransparentTriangles();\n\t}\n\n\tif( !RI.onlyClientDraw )\n\t{\n\t\tR_AllowFog( false );\n\t\tgEngfuncs.CL_DrawEFX( tr.frametime, true );\n\t\tR_AllowFog( true );\n\t}\n\n\tGL_SetRenderMode( kRenderNormal );\n\tR_SetUpWorldTransform();\n\tif( !RI.onlyClientDraw )\n\t\tR_DrawViewModel();\n\tgEngfuncs.CL_ExtraUpdate();\n\n}\n\nqboolean insubmodel;\n\n/*\n=============\nR_BmodelCheckBBox\n=============\n*/\nint R_BmodelCheckBBox( float *minmaxs )\n{\n\tint    i, *pindex, clipflags;\n\tvec3_t acceptpt, rejectpt;\n\tfloat  d;\n\n\tclipflags = 0;\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\t// generate accept and reject points\n\t\t// FIXME: do with fast look-ups or integer tests based on the sign bit\n\t\t// of the floating point values\n\n\t\tpindex = qfrustum.pfrustum_indexes[i];\n\n\t\trejectpt[0] = minmaxs[pindex[0]];\n\t\trejectpt[1] = minmaxs[pindex[1]];\n\t\trejectpt[2] = minmaxs[pindex[2]];\n\n\t\td = DotProduct( rejectpt, qfrustum.view_clipplanes[i].normal );\n\t\td -= qfrustum.view_clipplanes[i].dist;\n\n\t\tif( d <= 0 )\n\t\t\treturn BMODEL_FULLY_CLIPPED;\n\n\t\tacceptpt[0] = minmaxs[pindex[3 + 0]];\n\t\tacceptpt[1] = minmaxs[pindex[3 + 1]];\n\t\tacceptpt[2] = minmaxs[pindex[3 + 2]];\n\n\t\td = DotProduct( acceptpt, qfrustum.view_clipplanes[i].normal );\n\t\td -= qfrustum.view_clipplanes[i].dist;\n\n\t\tif( d <= 0 )\n\t\t\tclipflags |= ( 1 << i );\n\t}\n\n\treturn clipflags;\n}\n\n/*\n===================\nR_FindTopNode\n===================\n*/\nstatic mnode_t *R_FindTopnode( vec3_t mins, vec3_t maxs )\n{\n\tmplane_t *splitplane;\n\tint      sides;\n\tmnode_t  *node;\n\n\tnode = WORLDMODEL->nodes;\n\n\twhile( 1 )\n\t{\n\t\tif( node->visframe != tr.visframecount )\n\t\t\treturn NULL;                                    // not visible at all\n\n\t\tif( node->contents < 0 )\n\t\t{\n\t\t\tif( node->contents != CONTENTS_SOLID )\n\t\t\t\treturn node;                                 // we've reached a non-solid leaf, so it's\n\t\t\t//  visible and not BSP clipped\n\t\t\treturn NULL;                            // in solid, so not visible\n\t\t}\n\n\t\tsplitplane = node->plane;\n\t\tsides = BOX_ON_PLANE_SIDE( mins, maxs, splitplane );\n\n\t\tif( sides == 3 )\n\t\t\treturn node;                            // this is the splitter\n\n\t\t// not split yet; recurse down the contacted side\n\t\tif( sides & 1 )\n\t\t\tnode = node_child( node, 0, WORLDMODEL );\n\t\telse\n\t\t\tnode = node_child( node, 1, WORLDMODEL );\n\t}\n}\n\n\n/*\n=============\nRotatedBBox\n\nReturns an axially aligned box that contains the input box at the given rotation\n=============\n*/\nvoid RotatedBBox( vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs )\n{\n\tvec3_t tmp, v;\n\tint    i, j;\n\tvec3_t forward, right, up;\n\n\tif( !angles[0] && !angles[1] && !angles[2] )\n\t{\n\t\tVectorCopy( mins, tmins );\n\t\tVectorCopy( maxs, tmaxs );\n\t\treturn;\n\t}\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\ttmins[i] = 99999;\n\t\ttmaxs[i] = -99999;\n\t}\n\n\tAngleVectors( angles, forward, right, up );\n\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\tif( i & 1 )\n\t\t\ttmp[0] = mins[0];\n\t\telse\n\t\t\ttmp[0] = maxs[0];\n\n\t\tif( i & 2 )\n\t\t\ttmp[1] = mins[1];\n\t\telse\n\t\t\ttmp[1] = maxs[1];\n\n\t\tif( i & 4 )\n\t\t\ttmp[2] = mins[2];\n\t\telse\n\t\t\ttmp[2] = maxs[2];\n\n\n\t\tVectorScale( forward, tmp[0], v );\n\t\tVectorMA( v, -tmp[1], right, v );\n\t\tVectorMA( v, tmp[2], up, v );\n\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tif( v[j] < tmins[j] )\n\t\t\t\ttmins[j] = v[j];\n\t\t\tif( v[j] > tmaxs[j] )\n\t\t\t\ttmaxs[j] = v[j];\n\t\t}\n\t}\n}\n\n\n/*\n=============\nR_DrawBEntitiesOnList\n=============\n*/\nstatic void R_DrawBEntitiesOnList( void )\n{\n\tint     i, clipflags;\n\tvec3_t  oldorigin;\n\tvec3_t  mins, maxs;\n\tfloat   minmaxs[6];\n\tmnode_t *topnode;\n\n\tVectorCopy( tr.modelorg, oldorigin );\n\tinsubmodel = true;\n\n\tfor( i = 0; i < tr.draw_list->num_edge_entities && !RI.onlyClientDraw; i++ )\n\t{\n\t\tint k;\n\t\tRI.currententity = tr.draw_list->edge_entities[i];\n\t\tRI.currentmodel = RI.currententity->model;\n\t\tif( !RI.currentmodel )\n\t\t\tcontinue;\n\t\tif( RI.currentmodel->nummodelsurfaces == 0 )\n\t\t\tcontinue; // clip brush only\n\t\tif( RI.currentmodel->type != mod_brush )\n\t\t\tcontinue;\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\t// trivial accept status\n\t\tRotatedBBox( RI.currentmodel->mins, RI.currentmodel->maxs,\n\t\t\t     RI.currententity->angles, mins, maxs );\n\t\tVectorAdd( mins, RI.currententity->origin, minmaxs );\n\t\tVectorAdd( maxs, RI.currententity->origin, ( minmaxs + 3 ));\n\n\t\tclipflags = R_BmodelCheckBBox( minmaxs );\n\t\tif( clipflags == BMODEL_FULLY_CLIPPED )\n\t\t\tcontinue; // off the edge of the screen\n\t\t// clipflags = 0;\n\n\t\ttopnode = R_FindTopnode( minmaxs, minmaxs + 3 );\n\t\tif( !topnode )\n\t\t\tcontinue; // no part in a visible leaf\n\n\t\tVectorCopy( RI.currententity->origin, r_entorigin );\n\t\tVectorSubtract( RI.vieworg, r_entorigin, tr.modelorg );\n\t\t// VectorSubtract (r_origin, RI.currententity->origin, modelorg);\n\t\tr_pcurrentvertbase = RI.currentmodel->vertexes;\n\n\t\t// FIXME: stop transforming twice\n\t\tR_RotateBmodel();\n\n\t\t// calculate dynamic lighting for bmodel\n\t\tfor( k = 0; k < MAX_DLIGHTS; k++ )\n\t\t{\n\t\t\tdlight_t *l = &tr.dlights[k];\n\t\t\tvec3_t   origin_l, oldorigin;\n\n\t\t\tif( l->die < gp_cl->time || !l->radius )\n\t\t\t\tcontinue;\n\n\t\t\tVectorCopy( l->origin, oldorigin ); // save lightorigin\n\t\t\tMatrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 );\n\t\t\tMatrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l );\n\t\t\tVectorCopy( origin_l, l->origin ); // move light in bmodel space\n\t\t\tR_MarkLights( l, 1 << k, RI.currentmodel->nodes + RI.currentmodel->hulls[0].firstclipnode );\n\t\t\tVectorCopy( oldorigin, l->origin ); // restore lightorigin\n\t\t}\n\n\t\tRI.currententity->topnode = topnode;\n\t\tif( topnode->contents >= 0 )\n\t\t{\n\t\t\t// not a leaf; has to be clipped to the world BSP\n\t\t\tr_clipflags = clipflags;\n\t\t\tR_DrawSolidClippedSubmodelPolygons( RI.currentmodel, topnode );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// falls entirely in one leaf, so we just put all the\n\t\t\t// edges in the edge list and let 1/z sorting handle\n\t\t\t// drawing order\n\t\t\tR_DrawSubmodelPolygons( RI.currentmodel, clipflags, topnode );\n\t\t}\n\t\tRI.currententity->topnode = NULL;\n\n\t\t// put back world rotation and frustum clipping\n\t\t// FIXME: R_RotateBmodel should just work off base_vxx\n\t\tVectorCopy( RI.base_vpn, RI.vforward );\n\t\tVectorCopy( RI.base_vup, RI.vup );\n\t\tVectorCopy( RI.base_vright, RI.vright );\n\t\tVectorCopy( oldorigin, tr.modelorg );\n\t\tR_TransformFrustum();\n\t}\n\n\tinsubmodel = false;\n}\n\nextern qboolean alphaspans;\n/*\n=============\nR_DrawBEntitiesOnList\n=============\n*/\nvoid R_DrawBrushModel( cl_entity_t *pent )\n{\n\tint     i, clipflags;\n\tvec3_t  oldorigin;\n\tvec3_t  mins, maxs;\n\tfloat   minmaxs[6];\n\tmnode_t *topnode;\n\tint     k;\n\tedge_t  ledges[NUMSTACKEDGES\n\t\t       + (( CACHE_SIZE - 1 ) / sizeof( edge_t )) + 1];\n\tsurf_t  lsurfs[NUMSTACKSURFACES\n\t\t       + (( CACHE_SIZE - 1 ) / sizeof( surf_t )) + 1];\n\n\tif( !RI.drawWorld )\n\t\treturn;\n\n\tif( auxedges )\n\t{\n\t\tr_edges = auxedges;\n\t}\n\telse\n\t{\n\t\tr_edges = (edge_t *)\n\t\t\t  (((uintptr_t)&ledges[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 ));\n\t}\n\n\tif( r_surfsonstack )\n\t{\n\t\tsurfaces = (surf_t *)(((uintptr_t)&lsurfs[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 ));\n\t\tsurf_max = &surfaces[r_cnumsurfs];\n\t\t// surface 0 doesn't really exist; it's just a dummy because index 0\n\t\t// is used to indicate no edge attached to surface\n\t\tmemset( &surfaces[0], 0, sizeof( surf_t ));\n\t\tsurfaces--;\n\t\tR_SurfacePatch();\n\t}\n\n\n\tR_BeginEdgeFrame();\n\n\tVectorCopy( tr.modelorg, oldorigin );\n\tinsubmodel = true;\n\n\tif( !RI.currentmodel )\n\t\treturn;\n\tif( RI.currentmodel->nummodelsurfaces == 0 )\n\t\treturn;         // clip brush only\n\tif( RI.currentmodel->type != mod_brush )\n\t\treturn;\n\t// see if the bounding box lets us trivially reject, also sets\n\t// trivial accept status\n\tRotatedBBox( RI.currentmodel->mins, RI.currentmodel->maxs,\n\t\t     RI.currententity->angles, mins, maxs );\n\tVectorAdd( mins, RI.currententity->origin, minmaxs );\n\tVectorAdd( maxs, RI.currententity->origin, ( minmaxs + 3 ));\n\n\tclipflags = R_BmodelCheckBBox( minmaxs );\n\tif( clipflags == BMODEL_FULLY_CLIPPED )\n\t\treturn;         // off the edge of the screen\n\t// clipflags = 0;\n\n\ttopnode = R_FindTopnode( minmaxs, minmaxs + 3 );\n\tif( !topnode )\n\t\treturn;         // no part in a visible leaf\n\n\talphaspans = true;\n\tVectorCopy( RI.currententity->origin, r_entorigin );\n\tVectorSubtract( RI.vieworg, r_entorigin, tr.modelorg );\n\t// VectorSubtract (r_origin, RI.currententity->origin, modelorg);\n\tr_pcurrentvertbase = RI.currentmodel->vertexes;\n\n\t// FIXME: stop transforming twice\n\tR_RotateBmodel();\n\n\t// calculate dynamic lighting for bmodel\n\tfor( k = 0; k < MAX_DLIGHTS; k++ )\n\t{\n\t\tdlight_t *l = &tr.dlights[k];\n\t\tvec3_t   origin_l, oldorigin;\n\n\t\tif( l->die < gp_cl->time || !l->radius )\n\t\t\tcontinue;\n\n\t\tVectorCopy( l->origin, oldorigin );         // save lightorigin\n\t\tMatrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 );\n\t\tMatrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l );\n\t\ttr.modelviewIdentity = false;\n\t\tVectorCopy( origin_l, l->origin );         // move light in bmodel space\n\t\tR_MarkLights( l, 1 << k, RI.currentmodel->nodes + RI.currentmodel->hulls[0].firstclipnode );\n\t\tVectorCopy( oldorigin, l->origin );         // restore lightorigin*/\n\t}\n\n\tRI.currententity->topnode = topnode;\n\tif( topnode->contents >= 0 )\n\t{\n\t\t// not a leaf; has to be clipped to the world BSP\n\t\tr_clipflags = clipflags;\n\t\tR_DrawSolidClippedSubmodelPolygons( RI.currentmodel, topnode );\n\t}\n\telse\n\t{\n\t\t// falls entirely in one leaf, so we just put all the\n\t\t// edges in the edge list and let 1/z sorting handle\n\t\t// drawing order\n\t\tR_DrawSubmodelPolygons( RI.currentmodel, clipflags, topnode );\n\t}\n\tRI.currententity->topnode = NULL;\n\n\t// put back world rotation and frustum clipping\n\t// FIXME: R_RotateBmodel should just work off base_vxx\n\tVectorCopy( RI.base_vpn, RI.vforward );\n\tVectorCopy( RI.base_vup, RI.vup );\n\tVectorCopy( RI.base_vright, RI.vright );\n\tVectorCopy( oldorigin, tr.modelorg );\n\tR_TransformFrustum();\n\n\n\tinsubmodel = false;\n\tR_ScanEdges();\n\talphaspans = false;\n}\n\n/*\n================\nR_EdgeDrawing\n================\n*/\nstatic void R_EdgeDrawing( void )\n{\n\tedge_t ledges[NUMSTACKEDGES\n\t\t      + (( CACHE_SIZE - 1 ) / sizeof( edge_t )) + 1];\n\tsurf_t lsurfs[NUMSTACKSURFACES\n\t\t      + (( CACHE_SIZE - 1 ) / sizeof( surf_t )) + 1];\n\n\tif( !RI.drawWorld )\n\t\treturn;\n\n\tif( auxedges )\n\t{\n\t\tr_edges = auxedges;\n\t}\n\telse\n\t{\n\t\tr_edges = (edge_t *)\n\t\t\t  (((uintptr_t)&ledges[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 ));\n\t}\n\n\tif( r_surfsonstack )\n\t{\n\t\tsurfaces = (surf_t *)(((uintptr_t)&lsurfs + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 ));\n\t\tsurf_max = &surfaces[r_cnumsurfs];\n\n\t\t// surface 0 doesn't really exist; it's just a dummy because index 0\n\t\t// is used to indicate no edge attached to surface\n\n\t\tmemset( surfaces, 0, sizeof( surf_t ));\n\t\tsurfaces--;\n\t\tR_SurfacePatch();\n\t}\n\n\tR_BeginEdgeFrame();\n\n\t// this will prepare edges\n\tR_RenderWorld();\n\n\t// move brushes to separate list to merge with edges?\n\tR_DrawBEntitiesOnList();\n\n\t// display all edges\n\tR_ScanEdges();\n}\n\n/*\n===============\nR_MarkLeaves\n===============\n*/\nstatic void R_MarkLeaves( void )\n{\n\tbyte    *vis;\n\tmnode_t *node;\n\tint     i;\n\n\tif( r_oldviewcluster == r_viewcluster && !r_novis.value && r_viewcluster != -1 )\n\t\treturn;\n\n\ttr.visframecount++;\n\tr_oldviewcluster = r_viewcluster;\n\n\tgEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), false );\n\tvis = RI.visbytes;\n\n\tfor( i = 0; i < WORLDMODEL->numleafs; i++ )\n\t{\n\t\tif( vis[i >> 3] & ( 1 << ( i & 7 )))\n\t\t{\n\t\t\tnode = (mnode_t *) &WORLDMODEL->leafs[i + 1];\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif( node->visframe == tr.visframecount )\n\t\t\t\t\tbreak;\n\t\t\t\tnode->visframe = tr.visframecount;\n\t\t\t\tnode = node->parent;\n\t\t\t}\n\t\t\twhile( node );\n\t\t}\n\t}\n}\n\n/*\n================\nR_RenderScene\n\nR_SetupRefParams must be called right before\n================\n*/\nvoid GAME_EXPORT R_RenderScene( void )\n{\n\tif( !WORLDMODEL && RI.drawWorld )\n\t\tgEngfuncs.Host_Error( \"%s: NULL worldmodel\\n\", __func__ );\n\n\t// frametime is valid only for normal pass\n\tif( RP_NORMALPASS( ))\n\t\ttr.frametime = gp_cl->time - gp_cl->oldtime;\n\telse\n\t\ttr.frametime = 0.0;\n\n\t// begin a new frame\n\ttr.framecount++;\n\n\tif( tr.map_unload )\n\t{\n\t\tD_FlushCaches();\n\t\ttr.map_unload = false;\n\t}\n\n\n\tR_SetupFrustum();\n\tR_SetupFrame();\n\n\tR_PushDlights();\n\tR_SetupModelviewMatrix( RI.worldviewMatrix );\n\tR_SetupProjectionMatrix( RI.projectionMatrix );\n\n\tMatrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix );\n\ttr.modelviewIdentity = true;\n\n//\tR_SetupGL( true );\n\t// R_Clear( ~0 );\n\n\tR_MarkLeaves();\n\t// R_PushDlights (r_worldmodel); ??\n\t// R_DrawWorld();\n\tR_EdgeDrawing();\n\n\tgEngfuncs.CL_ExtraUpdate(); // don't let sound get messed up if going slow\n\n\tR_DrawEntitiesOnList();\n\n//\tR_DrawWaterSurfaces();\n\n//\tR_EndGL();\n}\n\nvoid R_GammaChanged( qboolean do_reset_gamma )\n{\n\tif( do_reset_gamma ) // unused\n\t\treturn;\n\n\tD_FlushCaches( );\n}\n\n/*\n===============\nR_BeginFrame\n===============\n*/\nvoid GAME_EXPORT R_BeginFrame( qboolean clearScene )\n{\n\tR_Set2DMode( true );\n\n\t// draw buffer stuff\n\t// pglDrawBuffer( GL_BACK );\n\n\t// update texture parameters\n\t// if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED ))\n\t// R_SetTextureParameters();\n\n\tgEngfuncs.CL_ExtraUpdate();\n}\n\n/*\n===============\nR_SetupRefParams\n\nset initial params for renderer\n===============\n*/\nvoid R_SetupRefParams( const ref_viewpass_t *rvp )\n{\n\tRI.params = RP_NONE;\n\tRI.drawWorld = FBitSet( rvp->flags, RF_DRAW_WORLD );\n\tRI.onlyClientDraw = FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW );\n\n\tif( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP ))\n\t\tRI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW );\n\telse\n\t\tRI.drawOrtho = false;\n\n\t// setup viewport\n\tRI.viewport[0] = rvp->viewport[0];\n\tRI.viewport[1] = rvp->viewport[1];\n\tRI.viewport[2] = rvp->viewport[2];\n\tRI.viewport[3] = rvp->viewport[3];\n\n\t// calc FOV\n\tRI.fov_x = rvp->fov_x;\n\tRI.fov_y = rvp->fov_y;\n\n\tVectorCopy( rvp->vieworigin, RI.vieworg );\n\tVectorCopy( rvp->viewangles, RI.viewangles );\n\tVectorCopy( rvp->vieworigin, RI.pvsorigin );\n}\n\n/*\n===============\nR_RenderFrame\n===============\n*/\nvoid GAME_EXPORT R_RenderFrame( const ref_viewpass_t *rvp )\n{\n\tif( r_norefresh->value )\n\t\treturn;\n\n\t// prevent cache overrun\n\tif( gpGlobals->height > vid.height || gpGlobals->width > vid.width )\n\t\treturn;\n\n\t// setup the initial render params\n\tR_SetupRefParams( rvp );\n\n\t// completely override rendering\n\tif( gEngfuncs.drawFuncs->GL_RenderFrame != NULL )\n\t{\n\t\ttr.fCustomRendering = true;\n\n\t\tif( gEngfuncs.drawFuncs->GL_RenderFrame( rvp ))\n\t\t{\n\t\t\t// R_GatherPlayerLight();\n\t\t\ttr.realframecount++;\n\t\t\ttr.fResetVis = true;\n\t\t\treturn;\n\t\t}\n\t}\n\n\ttr.fCustomRendering = false;\n\tif( !RI.onlyClientDraw )\n\t\tR_RunViewmodelEvents();\n\n\ttr.realframecount++; // right called after viewmodel events\n\tR_RenderScene();\n\n\treturn;\n}\n\n/*\n===============\nR_EndFrame\n===============\n*/\nvoid GAME_EXPORT R_EndFrame( void )\n{\n\t// flush any remaining 2D bits\n\tR_Set2DMode( false );\n\n\t// blit pixels\n\tR_BlitScreen();\n}\n\n/*\n===============\nR_DrawCubemapView\n===============\n*/\nvoid R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size )\n{\n\tref_viewpass_t rvp;\n\n\t// basic params\n\trvp.flags = rvp.viewentity = 0;\n\tSetBits( rvp.flags, RF_DRAW_WORLD );\n\tSetBits( rvp.flags, RF_DRAW_CUBEMAP );\n\n\trvp.viewport[0] = rvp.viewport[1] = 0;\n\trvp.viewport[2] = rvp.viewport[3] = size;\n\trvp.fov_x = rvp.fov_y = 90.0f; // this is a final fov value\n\n\t// setup origin & angles\n\tVectorCopy( origin, rvp.vieworigin );\n\tVectorCopy( angles, rvp.viewangles );\n\n\tR_RenderFrame( &rvp );\n\n\tRI.viewleaf = NULL; // force markleafs next frame\n}\n\n/*\n===============\nR_NewMap\n===============\n*/\nvoid GAME_EXPORT R_NewMap( void )\n{\n\tint     i;\n\tmodel_t *world = WORLDMODEL;\n\n\tr_viewcluster = -1;\n\n\ttr.draw_list->num_solid_entities = 0;\n\ttr.draw_list->num_trans_entities = 0;\n\ttr.draw_list->num_beam_entities = 0;\n\ttr.draw_list->num_edge_entities = 0;\n\n\tR_ClearDecals(); // clear all level decals\n\tR_StudioResetPlayerModels();\n\n\tif( FBitSet( world->flags, MODEL_QBSP2 ))\n\t{\n\t\tgEngfuncs.Host_Error( \"Sorry, ref_soft can't load maps in BSP2 format.\\n\" );\n\t\treturn;\n\t}\n\n\tr_cnumsurfs = sw_maxsurfs.value;\n\n\tif( r_cnumsurfs <= MINSURFACES )\n\t\tr_cnumsurfs = MINSURFACES;\n\n\tif( r_cnumsurfs > NUMSTACKSURFACES )\n\t{\n\t\tsurfaces = Mem_Calloc( r_temppool, r_cnumsurfs * sizeof( surf_t ));\n\t\tsurface_p = surfaces;\n\t\tsurf_max = &surfaces[r_cnumsurfs];\n\t\tr_surfsonstack = false;\n\t\t// surface 0 doesn't really exist; it's just a dummy because index 0\n\t\t// is used to indicate no edge attached to surface\n\t\tsurfaces--;\n\t\tR_SurfacePatch();\n\t}\n\telse\n\t{\n\t\tr_surfsonstack = true;\n\t}\n\n\tr_numallocatededges = sw_maxedges.value;\n\n\tif( r_numallocatededges < MINEDGES )\n\t\tr_numallocatededges = MINEDGES;\n\n\tif( r_numallocatededges <= NUMSTACKEDGES )\n\t{\n\t\tauxedges = NULL;\n\t}\n\telse\n\t{\n\t\tauxedges = Mem_Malloc( r_temppool, r_numallocatededges * sizeof( edge_t ));\n\t}\n\n\t// clear out efrags in case the level hasn't been reloaded\n\tfor( i = 0; i < world->numleafs; i++ )\n\t\tworld->leafs[i + 1].efrags = NULL;\n\n\ttr.sample_size = gEngfuncs.Mod_SampleSizeForFace( &world->surfaces[0] );\n\n\tfor( i = 1; i < world->numsurfaces; i++ )\n\t{\n\t\tint sample_size = gEngfuncs.Mod_SampleSizeForFace( &world->surfaces[i] );\n\t\tif( sample_size != tr.sample_size )\n\t\t{\n\t\t\ttr.sample_size = -1;\n\t\t\tbreak;\n\t\t}\n\t}\n\ttr.sample_bits = -1;\n\n\tif( tr.sample_size != -1 )\n\t{\n\t\tuint sample_pot;\n\n\t\ttr.sample_bits = 0;\n\n\t\tfor( sample_pot = 1; sample_pot < tr.sample_size; sample_pot <<= 1, tr.sample_bits++ )\n\t\t\t;\n\t}\n\n\tgEngfuncs.Con_Printf( \"Map sample size is %d\\n\", tr.sample_size );\n\n}\n\n/*\n================\nR_InitTurb\n================\n*/\nstatic void R_InitTurb( void )\n{\n\tint i;\n\n\tfor( i = 0; i < 1280; i++ )\n\t{\n\t\tsintable[i] = AMP + sin( i * 3.14159 * 2 / CYCLE ) * AMP;\n\t\tintsintable[i] = AMP2 + sin( i * 3.14159 * 2 / CYCLE ) * AMP2; // AMP2, not 20\n\t\tblanktable[i] = 0;                                             // PGM\n\t}\n}\n\n\n\nqboolean GAME_EXPORT R_Init( void )\n{\n\tqboolean glblit = false;\n\n\tRETRIEVE_ENGINE_SHARED_CVAR_LIST();\n\n\n\tgEngfuncs.Cvar_RegisterVariable( &sw_clearcolor );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_drawflat );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_draworder );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_maxedges );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_maxsurfs );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_mipscale );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_mipcap );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_surfcacheoverride );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_waterwarp );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_notransbrushes );\n\tgEngfuncs.Cvar_RegisterVariable( &sw_noalphabrushes );\n\tgEngfuncs.Cvar_RegisterVariable( &r_traceglow );\n#ifndef DISABLE_TEXFILTER\n\tgEngfuncs.Cvar_RegisterVariable( &sw_texfilt );\n#endif\n\tgEngfuncs.Cvar_RegisterVariable( &r_novis );\n\tgEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures );\n\n\tr_temppool = Mem_AllocPool( \"ref_soft zone\" );\n\n\tglblit = !!gEngfuncs.Sys_CheckParm( \"-glblit\" );\n\n\t// create the window and set up the context\n\tif( !glblit && !gEngfuncs.R_Init_Video( REF_SOFTWARE )) // request software blitter\n\t{\n\t\tgEngfuncs.R_Free_Video();\n\t\tgEngfuncs.Con_Printf( \"failed to initialize software blitter, fallback to glblit\\n\" );\n\t\tglblit = true;\n\t}\n\n\tif( glblit && !gEngfuncs.R_Init_Video( REF_GL )) // request GL context\n\t{\n\t\tgEngfuncs.R_Free_Video();\n\t\treturn false;\n\t}\n\n\t// see R_ProcessEntData for tr.entities initialization\n\ttr.movevars = (movevars_t *)ENGINE_GET_PARM( PARM_GET_MOVEVARS_PTR );\n\ttr.palette = (color24 *)ENGINE_GET_PARM( PARM_GET_PALETTE_PTR );\n\ttr.viewent = (cl_entity_t *)ENGINE_GET_PARM( PARM_GET_VIEWENT_PTR );\n\ttr.texgammatable = (byte *)ENGINE_GET_PARM( PARM_GET_TEXGAMMATABLE_PTR );\n\ttr.lightgammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LIGHTGAMMATABLE_PTR );\n\ttr.screengammatable = (uint *)ENGINE_GET_PARM( PARM_GET_SCREENGAMMATABLE_PTR );\n\ttr.lineargammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LINEARGAMMATABLE_PTR );\n\ttr.dlights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_DLIGHTS_PTR );\n\ttr.elights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_ELIGHTS_PTR );\n\n\tif( !R_InitBlit( glblit ))\n\t{\n\t\tgEngfuncs.R_Free_Video();\n\t\treturn false;\n\t}\n\n\tR_InitImages();\n\t// init draw stack\n\ttr.draw_list = &tr.draw_stack[0];\n\ttr.draw_stack_pos = 0;\n\tqfrustum.view_clipplanes[0].leftedge = true;\n\tqfrustum.view_clipplanes[1].rightedge = true;\n\tqfrustum.view_clipplanes[1].leftedge = qfrustum.view_clipplanes[2].leftedge = qfrustum.view_clipplanes[3].leftedge = false;\n\tqfrustum.view_clipplanes[0].rightedge = qfrustum.view_clipplanes[2].rightedge = qfrustum.view_clipplanes[3].rightedge = false;\n\tR_StudioInit();\n\tR_SpriteInit();\n\tR_InitTurb();\n\tGL_InitRandomTable();\n\n\treturn true;\n}\n\nvoid GAME_EXPORT R_Shutdown( void )\n{\n\tR_ShutdownImages();\n\tgEngfuncs.R_Free_Video();\n}\n\n\n/*\n===============\nCL_FxBlend\n===============\n*/\nint CL_FxBlend( cl_entity_t *e )\n{\n\tint    blend = 0;\n\tfloat  offset, dist;\n\tvec3_t tmp;\n\n\toffset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx\n\n\tswitch( e->curstate.renderfx )\n\t{\n\tcase kRenderFxPulseSlowWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFastWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseSlow:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFast:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxFadeSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 0 )\n\t\t\t\te->curstate.renderamt -= 1;\n\t\t\telse\n\t\t\t\te->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFadeFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 3 )\n\t\t\t\te->curstate.renderamt -= 4;\n\t\t\telse\n\t\t\t\te->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 255 )\n\t\t\t\te->curstate.renderamt += 1;\n\t\t\telse\n\t\t\t\te->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 252 )\n\t\t\t\te->curstate.renderamt += 4;\n\t\t\telse\n\t\t\t\te->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeSlow:\n\t\tblend = 20 * sin( gp_cl->time * 4 + offset );\n\t\tif( blend < 0 )\n\t\t\tblend = 0;\n\t\telse\n\t\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFast:\n\t\tblend = 20 * sin( gp_cl->time * 16 + offset );\n\t\tif( blend < 0 )\n\t\t\tblend = 0;\n\t\telse\n\t\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFaster:\n\t\tblend = 20 * sin( gp_cl->time * 36 + offset );\n\t\tif( blend < 0 )\n\t\t\tblend = 0;\n\t\telse\n\t\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerSlow:\n\t\tblend = 20 * ( sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset ));\n\t\tif( blend < 0 )\n\t\t\tblend = 0;\n\t\telse\n\t\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerFast:\n\t\tblend = 20 * ( sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset ));\n\t\tif( blend < 0 )\n\t\t\tblend = 0;\n\t\telse\n\t\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxHologram:\n\tcase kRenderFxDistort:\n\t\tVectorCopy( e->origin, tmp );\n\t\tVectorSubtract( tmp, RI.vieworg, tmp );\n\t\tdist = DotProduct( tmp, RI.vforward );\n\n\t\t// turn off distance fade\n\t\tif( e->curstate.renderfx == kRenderFxDistort )\n\t\t\tdist = 1;\n\n\t\tif( dist <= 0 )\n\t\t{\n\t\t\tblend = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\te->curstate.renderamt = 180;\n\t\t\tif( dist <= 100 )\n\t\t\t\tblend = e->curstate.renderamt;\n\t\t\telse\n\t\t\t\tblend = (int) (( 1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt );\n\t\t\tblend += gEngfuncs.COM_RandomLong( -32, 31 );\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\t}\n\n\tblend = bound( 0, blend, 255 );\n\n\treturn blend;\n}\n\n"
  },
  {
    "path": "ref/soft/r_math.c",
    "content": "/*\ngl_rmath.c - renderer mathlib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"xash3d_mathlib.h\"\n\n/*\n========================================================================\n\n\t       Matrix4x4 operations (private to renderer)\n\n========================================================================\n*/\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3];\n\tout[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0];\n\tout[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1];\n\tout[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2];\n\tout[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3];\n}\n\n/*\n================\nMatrix4x4_CreateProjection\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar )\n{\n\tout[0][0] = ( 2.0f * zNear ) / ( xMax - xMin );\n\tout[1][1] = ( 2.0f * zNear ) / ( yMax - yMin );\n\tout[2][2] = -( zFar + zNear ) / ( zFar - zNear );\n\tout[3][3] = out[0][1] = out[1][0] = out[3][0] = out[0][3] = out[3][1] = out[1][3] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][2] = ( xMax + xMin ) / ( xMax - xMin );\n\tout[1][2] = ( yMax + yMin ) / ( yMax - yMin );\n\tout[3][2] = -1.0f;\n\tout[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear );\n}\n\nvoid Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar )\n{\n\tout[0][0] = 2.0f / ( xRight - xLeft );\n\tout[1][1] = 2.0f / ( yTop - yBottom );\n\tout[2][2] = -2.0f / ( zFar - zNear );\n\tout[3][3] = 1.0f;\n\tout[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][3] = -( xRight + xLeft ) / ( xRight - xLeft );\n\tout[1][3] = -( yTop + yBottom ) / ( yTop - yBottom );\n\tout[2][3] = -( zFar + zNear ) / ( zFar - zNear );\n}\n\n/*\n================\nMatrix4x4_CreateModelview\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateModelview( matrix4x4 out )\n{\n\tout[0][0] = out[1][1] = out[2][2] = 0.0f;\n\tout[3][0] = out[0][3] = 0.0f;\n\tout[3][1] = out[1][3] = 0.0f;\n\tout[3][2] = out[2][3] = 0.0f;\n\tout[3][3] = 1.0f;\n\tout[1][0] = out[0][2] = out[2][1] = 0.0f;\n\tout[2][0] = out[0][1] = -1.0f;\n\tout[1][2] = 1.0f;\n}\n\nvoid Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][0] = 1.0f;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = x;\n\tout[1][0] = 0.0f;\n\tout[1][1] = 1.0f;\n\tout[1][2] = 0.0f;\n\tout[1][3] = y;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = 1.0f;\n\tout[2][3] = z;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nvoid Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tfloat len, c, s;\n\n\tlen = x * x + y * y + z * z;\n\tif( len != 0.0f )\n\t\tlen = 1.0f / sqrt( len );\n\tx *= len;\n\ty *= len;\n\tz *= len;\n\n\tangle *= ( -M_PI_F / 180.0f );\n\tSinCos( angle, &s, &c );\n\n\tout[0][0] = x * x + c * ( 1 - x * x );\n\tout[0][1] = x * y * ( 1 - c ) + z * s;\n\tout[0][2] = z * x * ( 1 - c ) - y * s;\n\tout[0][3] = 0.0f;\n\tout[1][0] = x * y * ( 1 - c ) - z * s;\n\tout[1][1] = y * y + c * ( 1 - y * y );\n\tout[1][2] = y * z * ( 1 - c ) + x * s;\n\tout[1][3] = 0.0f;\n\tout[2][0] = z * x * ( 1 - c ) + y * s;\n\tout[2][1] = y * z * ( 1 - c ) - x * s;\n\tout[2][2] = z * z + c * ( 1 - z * z );\n\tout[2][3] = 0.0f;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nstatic void Matrix4x4_CreateScale( matrix4x4 out, float x )\n{\n\tout[0][0] = x;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = 0.0f;\n\tout[1][0] = 0.0f;\n\tout[1][1] = x;\n\tout[1][2] = 0.0f;\n\tout[1][3] = 0.0f;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = x;\n\tout[2][3] = 0.0f;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nstatic void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][0] = x;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = 0.0f;\n\tout[1][0] = 0.0f;\n\tout[1][1] = y;\n\tout[1][2] = 0.0f;\n\tout[1][3] = 0.0f;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = z;\n\tout[2][3] = 0.0f;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateTranslate( temp, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateRotate( temp, angle, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n"
  },
  {
    "path": "ref/soft/r_misc.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_misc.c\n\n#include \"r_local.h\"\n\n#define NUM_MIPS 4\n\nsurfcache_t  *d_initial_rover;\nqboolean     d_roverwrapped;\nint          d_minmip;\nfloat        d_scalemip[NUM_MIPS - 1];\n\nstatic float basemip[NUM_MIPS - 1] = {1.0, 0.5 * 0.8, 0.25 * 0.8};\n\n\n// int\td_vrectx, d_vrecty, d_vrectright_particle, d_vrectbottom_particle;\n\n// int\td_pix_min, d_pix_max, d_pix_shift;\n\nint   d_scantable[MAXHEIGHT];\nshort *zspantable[MAXHEIGHT];\nstruct qfrustum_s qfrustum;\n/*\n================\nD_Patch\n================\n*/\nstatic void D_Patch( void )\n{\n}\n/*\n================\nD_ViewChanged\n================\n*/\n\nvoid D_ViewChanged( void )\n{\n\tint i;\n\n\tscale_for_mip = xscale;\n\tif( yscale > xscale )\n\t\tscale_for_mip = yscale;\n\n\td_zrowbytes = vid.width * 2;\n\td_zwidth = vid.width;\n\n\t/*d_pix_min = gpGlobals->width / 320;\n\tif (d_pix_min < 1)\n\t\td_pix_min = 1;\n\n\td_pix_max = (int)((float)gpGlobals->height / (320.0 / 4.0) + 0.5);\n\td_pix_shift = 8 - (int)((float)gpGlobals->height / 320.0 + 0.5);\n\tif (d_pix_max < 1)\n\t\td_pix_max = 1;*/\n\n\t// d_vrectx = RI.vrect.x;\n\t// d_vrecty = RI.vrect.y;\n\t// d_vrectright_particle = gpGlobals->width - d_pix_max;\n\t// d_vrectbottom_particle =\n\t//\tgpGlobals->height - d_pix_max;\n\n\tfor( i = 0; i < vid.height; i++ )\n\t{\n\t\td_scantable[i] = i * r_screenwidth;\n\t\tzspantable[i] = d_pzbuffer + i * d_zwidth;\n\t}\n\n\t/*\n\t** clear Z-buffer and color-buffers if we're doing the gallery\n\t*/\n\tif( !RI.drawWorld )\n\t{\n\t\tmemset( d_pzbuffer, 0xff, vid.width * vid.height * sizeof( d_pzbuffer[0] ));\n\t}\n\n\tD_Patch();\n}\n\n\n\n/*\n===================\nR_TransformFrustum\n===================\n*/\nvoid R_TransformFrustum( void )\n{\n\tint    i;\n\tvec3_t v, v2;\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\tv[0] = qfrustum.screenedge[i].normal[2];\n\t\tv[1] = -qfrustum.screenedge[i].normal[0];\n\t\tv[2] = qfrustum.screenedge[i].normal[1];\n\n\t\tv2[0] = v[1] * RI.vright[0] + v[2] * RI.vup[0] + v[0] * RI.vforward[0];\n\t\tv2[1] = v[1] * RI.vright[1] + v[2] * RI.vup[1] + v[0] * RI.vforward[1];\n\t\tv2[2] = v[1] * RI.vright[2] + v[2] * RI.vup[2] + v[0] * RI.vforward[2];\n\n\t\tVectorCopy( v2, qfrustum.view_clipplanes[i].normal );\n\n\t\tqfrustum.view_clipplanes[i].dist = DotProduct( tr.modelorg, v2 );\n\t}\n}\n\n\n/*\n================\nTransformVector\n================\n*/\nvoid TransformVector( vec3_t in, vec3_t out )\n{\n\tout[0] = DotProduct( in, RI.vright );\n\tout[1] = DotProduct( in, RI.vup );\n\tout[2] = DotProduct( in, RI.vforward );\n}\n\n/*\n===============\nR_SetUpFrustumIndexes\n===============\n*/\nstatic void R_SetUpFrustumIndexes( void )\n{\n\tint i, j, *pindex;\n\n\tpindex = qfrustum.frustum_indexes;\n\n\tfor( i = 0; i < 4; i++ )\n\t{\n\t\tfor( j = 0; j < 3; j++ )\n\t\t{\n\t\t\tif( qfrustum.view_clipplanes[i].normal[j] < 0 )\n\t\t\t{\n\t\t\t\tpindex[j] = j;\n\t\t\t\tpindex[j + 3] = j + 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpindex[j] = j + 3;\n\t\t\t\tpindex[j + 3] = j;\n\t\t\t}\n\t\t}\n\n\t\t// FIXME: do just once at start\n\t\tqfrustum.pfrustum_indexes[i] = pindex;\n\t\tpindex += 6;\n\t}\n}\n\n/*\n===============\nR_ViewChanged\n\nCalled every time the vid structure or r_refdef changes.\nGuaranteed to be called before the first refresh\n===============\n*/\nstatic void R_ViewChanged( vrect_t *vr )\n{\n\tint   i;\n\tfloat verticalFieldOfView, horizontalFieldOfView, xOrigin, yOrigin;\n\n\tRI.vrect = *vr;\n\n\thorizontalFieldOfView = 2 * tan((float)RI.fov_x / 360.0f * M_PI_F );\n\tverticalFieldOfView = 2 * tan((float)RI.fov_y / 360.0f * M_PI_F );\n\n\tRI.fvrectx = (float)RI.vrect.x;\n\tRI.fvrectx_adj = (float)RI.vrect.x - 0.5f;\n\tRI.vrect_x_adj_shift20 = ( RI.vrect.x << 20 ) + ( 1 << 19 ) - 1;\n\tRI.fvrecty = (float)RI.vrect.y;\n\tRI.fvrecty_adj = (float)RI.vrect.y - 0.5f;\n\tRI.vrectright = RI.vrect.x + RI.vrect.width;\n\tRI.vrectright_adj_shift20 = ( RI.vrectright << 20 ) + ( 1 << 19 ) - 1;\n\tRI.fvrectright = (float)RI.vrectright;\n\tRI.fvrectright_adj = (float)RI.vrectright - 0.5f;\n\tRI.vrectrightedge = (float)RI.vrectright - 0.99f;\n\tRI.vrectbottom = RI.vrect.y + RI.vrect.height;\n\tRI.fvrectbottom = (float)RI.vrectbottom;\n\tRI.fvrectbottom_adj = (float)RI.vrectbottom - 0.5f;\n\n\tRI.aliasvrect.x = (int)( RI.vrect.x * r_aliasuvscale );\n\tRI.aliasvrect.y = (int)( RI.vrect.y * r_aliasuvscale );\n\tRI.aliasvrect.width = (int)( RI.vrect.width * r_aliasuvscale );\n\tRI.aliasvrect.height = (int)( RI.vrect.height * r_aliasuvscale );\n\tRI.aliasvrectright = RI.aliasvrect.x\n\t\t\t     + RI.aliasvrect.width;\n\tRI.aliasvrectbottom = RI.aliasvrect.y\n\t\t\t      + RI.aliasvrect.height;\n\n\txOrigin = XCENTERING;\n\tyOrigin = YCENTERING;\n#define PLANE_ANYZ 5\n// values for perspective projection\n// if math were exact, the values would range from 0.5 to to range+0.5\n// hopefully they wll be in the 0.000001 to range+.999999 and truncate\n// the polygon rasterization will never render in the first row or column\n// but will definately render in the [range] row and column, so adjust the\n// buffer origin to get an exact edge to edge fill\n\txcenter = ((float)RI.vrect.width * XCENTERING )\n\t\t  + RI.vrect.x - 0.5f;\n\taliasxcenter = xcenter * r_aliasuvscale;\n\tycenter = ((float)RI.vrect.height * YCENTERING )\n\t\t  + RI.vrect.y - 0.5f;\n\taliasycenter = ycenter * r_aliasuvscale;\n\n\txscale = RI.vrect.width / horizontalFieldOfView;\n\taliasxscale = xscale * r_aliasuvscale;\n\txscaleinv = 1.0f / xscale;\n\n\tyscale = xscale;\n\taliasyscale = yscale * r_aliasuvscale;\n\tyscaleinv = 1.0f / yscale;\n\t// xscaleshrink = (RI.vrect.width-6)/RI.horizontalFieldOfView;\n\t// yscaleshrink = xscaleshrink;\n\n// left side clip\n\tqfrustum.screenedge[0].normal[0] = -1.0f / ( xOrigin * horizontalFieldOfView );\n\tqfrustum.screenedge[0].normal[1] = 0;\n\tqfrustum.screenedge[0].normal[2] = 1;\n\tqfrustum.screenedge[0].type = PLANE_ANYZ;\n\n// right side clip\n\tqfrustum.screenedge[1].normal[0]\n\t\t= 1.0f / (( 1.0f - xOrigin ) * horizontalFieldOfView );\n\tqfrustum.screenedge[1].normal[1] = 0;\n\tqfrustum.screenedge[1].normal[2] = 1;\n\tqfrustum.screenedge[1].type = PLANE_ANYZ;\n\n// top side clip\n\tqfrustum.screenedge[2].normal[0] = 0;\n\tqfrustum.screenedge[2].normal[1] = -1.0f / ( yOrigin * verticalFieldOfView );\n\tqfrustum.screenedge[2].normal[2] = 1;\n\tqfrustum.screenedge[2].type = PLANE_ANYZ;\n\n// bottom side clip\n\tqfrustum.screenedge[3].normal[0] = 0;\n\tqfrustum.screenedge[3].normal[1] = 1.0f / (( 1.0f - yOrigin ) * verticalFieldOfView );\n\tqfrustum.screenedge[3].normal[2] = 1;\n\tqfrustum.screenedge[3].type = PLANE_ANYZ;\n\n\tfor( i = 0; i < 4; i++ )\n\t\tVectorNormalize( qfrustum.screenedge[i].normal );\n\n\tD_ViewChanged();\n}\n\n\n/*\n===============\nR_SetupFrame\n===============\n*/\nvoid R_SetupFrameQ( void )\n{\n\tint     i;\n\tvrect_t vrect;\n\n\tif( r_fullbright->flags & FCVAR_CHANGED )\n\t{\n\t\tr_fullbright->flags &= ~FCVAR_CHANGED;\n\t\tD_FlushCaches( ); // so all lighting changes\n\t}\n\n\t// tr.framecount++;\n\n\n// build the transformation matrix for the given view angles\n\tVectorCopy( RI.vieworg, tr.modelorg );\n\n\t// AngleVectors (RI.viewangles, RI.vforward, RI.vright, RI.vup);\n\n// current viewleaf\n\tif( RI.drawWorld )\n\t{\n\t\tRI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.vieworg, WORLDMODEL->nodes );\n\t\tr_viewcluster = RI.viewleaf->cluster;\n\t}\n\n//\tif (sw_waterwarp->value && (r_newrefdef.rdflags & RDF_UNDERWATER) )\n//\t\tr_dowarp = true;\n//\telse\n\n\t/*vrect.x = 0;//r_newrefdef.x;\n\tvrect.y = 0;//r_newrefdef.y;\n\tvrect.width = gpGlobals->width;\n\tvrect.height = gpGlobals->height;*/\n\tvrect.x = RI.viewport[0];\n\tvrect.y = RI.viewport[1];\n\tvrect.width = RI.viewport[2];\n\tvrect.height = RI.viewport[3];\n\n\td_viewbuffer = (void *)vid.buffer;\n\tr_screenwidth = vid.rowbytes;\n\n\tR_ViewChanged( &vrect );\n\n// start off with just the four screen edge clip planes\n\tR_TransformFrustum();\n\tR_SetUpFrustumIndexes();\n\n// save base values\n\tVectorCopy( RI.vforward, RI.base_vpn );\n\tVectorCopy( RI.vright, RI.base_vright );\n\tVectorCopy( RI.vup, RI.base_vup );\n\n// clear frame counts\n/*\tc_faceclip = 0;\n\td_spanpixcount = 0;\n\tr_polycount = 0;\n\tr_drawnpolycount = 0;\n\tr_wholepolycount = 0;\n\tr_amodels_drawn = 0;\n\tr_outofsurfaces = 0;\n\tr_outofedges = 0;*/\n\n// d_setup\n\td_roverwrapped = false;\n\td_initial_rover = sc_rover;\n\n\td_minmip = sw_mipcap.value;\n\tif( d_minmip > 3 )\n\t\td_minmip = 3;\n\telse if( d_minmip < 0 )\n\t\td_minmip = 0;\n\n\tfor( i = 0; i < ( NUM_MIPS - 1 ); i++ )\n\t\td_scalemip[i] = basemip[i] * sw_mipscale.value;\n\n\t// d_aflatcolor = 0;\n}\n"
  },
  {
    "path": "ref/soft/r_part.c",
    "content": "/*\ncl_part.c - particles and tracers\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"r_efx.h\"\n#include \"event_flags.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n\nstatic float   gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };\nstatic color24 gTracerColors[] =\n{\n\t{ 255, 255, 255 }, // White\n\t{ 255, 0, 0 },     // Red\n\t{ 0, 255, 0 },     // Green\n\t{ 0, 0, 255 },     // Blue\n\t{ 0, 0, 0 },       // Tracer default, filled in from cvars, etc.\n\t{ 255, 167, 17 },  // Yellow-orange sparks\n\t{ 255, 130, 90 },  // Yellowish streaks (garg)\n\t{ 55, 60, 144 },   // Blue egon streak\n\t{ 255, 130, 90 },  // More Yellowish streaks (garg)\n\t{ 255, 140, 90 },  // More Yellowish streaks (garg)\n\t{ 200, 130, 90 },  // More red streaks (garg)\n\t{ 255, 120, 70 },  // Darker red streaks (garg)\n};\n\n/*\n================\nCL_DrawParticles\n\nupdate particle color, position, free expired and draw it\n================\n*/\nvoid GAME_EXPORT CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize )\n{\n\tparticle_t *p;\n\tvec3_t     right, up;\n\tcolor24    color;\n\tint        alpha;\n\tfloat      size;\n\n\tif( !cl_active_particles )\n\t\treturn; // nothing to draw?\n\n\t// pglEnable( GL_BLEND );\n\t// pglDisable( GL_ALPHA_TEST );\n\t// pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\tGL_SetRenderMode( kRenderTransAdd );\n\n\tGL_Bind( XASH_TEXTURE0, tr.particleTexture );\n\t// pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\t// pglDepthMask( GL_FALSE );\n\n\tfor( p = cl_active_particles; p; p = p->next )\n\t{\n\t\tif(( p->type != pt_blob ) || ( p->packedColor == 255 ))\n\t\t{\n\t\t\tsize = partsize; // get initial size of particle\n\n\t\t\t// scale up to keep particles from disappearing\n\t\t\tsize += ( p->org[0] - RI.vieworg[0] ) * RI.cull_vforward[0];\n\t\t\tsize += ( p->org[1] - RI.vieworg[1] ) * RI.cull_vforward[1];\n\t\t\tsize += ( p->org[2] - RI.vieworg[2] ) * RI.cull_vforward[2];\n\n\t\t\tif( size < 20.0f )\n\t\t\t\tsize = partsize;\n\t\t\telse\n\t\t\t\tsize = partsize + size * 0.002f;\n\n\t\t\t// scale the axes by radius\n\t\t\tVectorScale( RI.cull_vright, size, right );\n\t\t\tVectorScale( RI.cull_vup, size, up );\n\n\t\t\tp->color = bound( 0, p->color, 255 );\n\t\t\tcolor = tr.palette[p->color];\n\n\t\t\talpha = 255 * ( p->die - gp_cl->time ) * 16.0f;\n\t\t\tif( alpha > 255 || p->type == pt_static )\n\t\t\t\talpha = 255;\n\n\t\t\t// TriColor4ub( LightToTexGamma( color.r ),\n\t\t\t//\tLightToTexGamma( color.g ),\n\t\t\t//\t\tLightToTexGamma( color.b ), alpha );\n\t\t\t// TriBrightness( alpha / 255.0f );\n\t\t\t_TriColor4f( 1.0f * alpha / 255 / 255 * color.r, 1.0f * alpha / 255 / 255 * color.g, 1.0f * alpha / 255 / 255 * color.b, 1.0f );\n\n\t\t\tTriBegin( TRI_QUADS );\n\t\t\tTriTexCoord2f( 0.0f, 1.0f );\n\t\t\tTriVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] );\n\t\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\t\tTriVertex3f( p->org[0] + right[0] + up[0], p->org[1] + right[1] + up[1], p->org[2] + right[2] + up[2] );\n\t\t\tTriTexCoord2f( 1.0f, 0.0f );\n\t\t\tTriVertex3f( p->org[0] + right[0] - up[0], p->org[1] + right[1] - up[1], p->org[2] + right[2] - up[2] );\n\t\t\tTriTexCoord2f( 1.0f, 1.0f );\n\t\t\tTriVertex3f( p->org[0] - right[0] - up[0], p->org[1] - right[1] - up[1], p->org[2] - right[2] - up[2] );\n\t\t\tTriEnd();\n\t\t\tr_stats.c_particle_count++;\n\t\t}\n\n\t\tgEngfuncs.CL_ThinkParticle( frametime, p );\n\t}\n\n\tTriEnd();\n\t// pglDepthMask( GL_TRUE );\n}\n\n/*\n================\nCL_CullTracer\n\ncheck tracer bbox\n================\n*/\nstatic qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end )\n{\n\tvec3_t mins, maxs;\n\tint    i;\n\treturn false;\n/*\n\t// compute the bounding box\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t{\n\t\t\tmaxs[i] += gTracerSize[p->type] * 2.0f;\n\t\t}\n\t}\n\n\t// check bbox\n\treturn R_CullBox( mins, maxs );*/\n}\n\n/*\n================\nCL_DrawTracers\n\nupdate tracer color, position, free expired and draw it\n================\n*/\nvoid GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers )\n{\n\tfloat      scale, atten, gravity;\n\tvec3_t     screenLast, screen;\n\tvec3_t     start, end, delta;\n\tparticle_t *p;\n\n\t// update tracer color if this is changed\n\tif( FBitSet( tracerred->flags | tracergreen->flags | tracerblue->flags | traceralpha->flags, FCVAR_CHANGED ))\n\t{\n\t\tcolor24 *customColors = &gTracerColors[4];\n\t\tcustomColors->r = (byte)( tracerred->value * traceralpha->value * 255 );\n\t\tcustomColors->g = (byte)( tracergreen->value * traceralpha->value * 255 );\n\t\tcustomColors->b = (byte)( tracerblue->value * traceralpha->value * 255 );\n\t\tClearBits( tracerred->flags, FCVAR_CHANGED );\n\t\tClearBits( tracergreen->flags, FCVAR_CHANGED );\n\t\tClearBits( tracerblue->flags, FCVAR_CHANGED );\n\t\tClearBits( traceralpha->flags, FCVAR_CHANGED );\n\t}\n\n\tif( !cl_active_tracers )\n\t\treturn; // nothing to draw?\n\n\tGL_SetRenderMode( kRenderTransAdd );\n\n\tif( !TriSpriteTexture( gEngfuncs.GetDefaultSprite( REF_DOT_SPRITE ), 0 ))\n\t\treturn;\n\n\t// pglEnable( GL_BLEND );\n\t// pglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t// pglDisable( GL_ALPHA_TEST );\n\t// pglDepthMask( GL_FALSE );\n\n\tgravity = frametime * tr.movevars->gravity;\n\tscale = 1.0 - ( frametime * 0.9 );\n\tif( scale < 0.0f )\n\t\tscale = 0.0f;\n\n\tfor( p = cl_active_tracers; p; p = p->next )\n\t{\n\t\tatten = ( p->die - gp_cl->time );\n\t\tif( atten > 0.1f )\n\t\t\tatten = 0.1f;\n\n\t\tVectorScale( p->vel, ( p->ramp * atten ), delta );\n\t\tVectorAdd( p->org, delta, end );\n\t\tVectorCopy( p->org, start );\n\n\t\tif( !CL_CullTracer( p, start, end ))\n\t\t{\n\t\t\tvec3_t  verts[4], tmp2;\n\t\t\tvec3_t  tmp, normal;\n\t\t\tcolor24 color;\n\t\t\tshort   alpha = p->packedColor;\n\n\t\t\t// Transform point into screen space\n\t\t\tTriWorldToScreen( start, screen );\n\t\t\tTriWorldToScreen( end, screenLast );\n\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\t// build point along noraml line (normal is -y, x)\n\t\t\tVectorScale( RI.cull_vup, tmp[0] * gTracerSize[p->type], normal );\n\t\t\tVectorScale( RI.cull_vright, -tmp[1] * gTracerSize[p->type], tmp2 );\n\t\t\tVectorSubtract( normal, tmp2, normal );\n\n\t\t\t// compute four vertexes\n\t\t\tVectorSubtract( start, normal, verts[0] );\n\t\t\tVectorAdd( start, normal, verts[1] );\n\t\t\tVectorAdd( verts[0], delta, verts[2] );\n\t\t\tVectorAdd( verts[1], delta, verts[3] );\n\n\t\t\tif( p->color < 0 || p->color >= sizeof( gTracerColors ) / sizeof( gTracerColors[0] ))\n\t\t\t{\n\t\t\t\tp->color = TRACER_COLORINDEX_DEFAULT;\n\t\t\t}\n\n\t\t\tcolor = gTracerColors[p->color];\n\t\t\t// TriColor4ub( color.r, color.g, color.b, p->packedColor );\n\t\t\t_TriColor4f( 1.0f * alpha / 255 / 255 * color.r, 1.0f * alpha / 255 / 255 * color.g, 1.0f * alpha / 255 / 255 * color.b, 1.0f );\n\n\n\t\t\tTriBegin( TRI_QUADS );\n\t\t\tTriTexCoord2f( 0.0f, 0.8f );\n\t\t\tTriVertex3fv( verts[2] );\n\t\t\tTriTexCoord2f( 1.0f, 0.8f );\n\t\t\tTriVertex3fv( verts[3] );\n\t\t\tTriTexCoord2f( 1.0f, 0.0f );\n\t\t\tTriVertex3fv( verts[1] );\n\t\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\t\tTriVertex3fv( verts[0] );\n\t\t\tTriEnd();\n\t\t}\n\n\t\t// evaluate position\n\t\tVectorMA( p->org, frametime, p->vel, p->org );\n\n\t\tif( p->type == pt_grav )\n\t\t{\n\t\t\tp->vel[0] *= scale;\n\t\t\tp->vel[1] *= scale;\n\t\t\tp->vel[2] -= gravity;\n\n\t\t\tp->packedColor = 255 * ( p->die - gp_cl->time ) * 2;\n\t\t\tif( p->packedColor > 255 )\n\t\t\t\tp->packedColor = 255;\n\t\t}\n\t\telse if( p->type == pt_slowgrav )\n\t\t{\n\t\t\tp->vel[2] = gravity * 0.05;\n\t\t}\n\t}\n\n\t// pglDepthMask( GL_TRUE );\n}\n\n/*\n===============\nCL_DrawParticlesExternal\n\nallow to draw effects from custom renderer\n===============\n*/\nvoid GAME_EXPORT CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime )\n{\n\tref_instance_t oldRI = RI;\n\n\tR_SetupRefParams( rvp );\n\tR_SetupFrustum();\n//\tR_SetupGL( false );\t// don't touch GL-states\n\n\t// setup PVS for frame\n\tmemcpy( RI.visbytes, tr.visbytes, gpGlobals->visbytes );\n\ttr.frametime = frametime;\n\n\tgEngfuncs.CL_DrawEFX( frametime, trans_pass );\n\n\t// restore internal state\n\tRI = oldRI;\n}\n"
  },
  {
    "path": "ref/soft/r_polyse.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// d_polyset.c: routines for drawing sets of polygons sharing the same\n// texture (used for Alias models)\n\n#include \"r_local.h\"\n\n// TODO: put in span spilling to shrink list size\n// !!! if this is changed, it must be changed in d_polysa.s too !!!\n#define DPS_MAXSPANS MAXHEIGHT + 1\n// 1 extra for spanpackage that marks end\n\ntypedef struct\n{\n\tint isflattop;\n\tint numleftedges;\n\tint *pleftedgevert0;\n\tint *pleftedgevert1;\n\tint *pleftedgevert2;\n\tint numrightedges;\n\tint *prightedgevert0;\n\tint *prightedgevert1;\n\tint *prightedgevert2;\n} edgetable;\n\naliastriangleparms_t aliastriangleparms;\n\nint           r_p0[6], r_p1[6], r_p2[6];\n\nint           d_xdenom;\n\nedgetable     *pedgetable;\n\nedgetable     edgetables[12] = {\n\t{0, 1, r_p0, r_p2, NULL, 2, r_p0, r_p1, r_p2 },\n\t{0, 2, r_p1, r_p0, r_p2, 1, r_p1, r_p2, NULL},\n\t{1, 1, r_p0, r_p2, NULL, 1, r_p1, r_p2, NULL},\n\t{0, 1, r_p1, r_p0, NULL, 2, r_p1, r_p2, r_p0 },\n\t{0, 2, r_p0, r_p2, r_p1, 1, r_p0, r_p1, NULL},\n\t{0, 1, r_p2, r_p1, NULL, 1, r_p2, r_p0, NULL},\n\t{0, 1, r_p2, r_p1, NULL, 2, r_p2, r_p0, r_p1 },\n\t{0, 2, r_p2, r_p1, r_p0, 1, r_p2, r_p0, NULL},\n\t{0, 1, r_p1, r_p0, NULL, 1, r_p1, r_p2, NULL},\n\t{1, 1, r_p2, r_p1, NULL, 1, r_p0, r_p1, NULL},\n\t{1, 1, r_p1, r_p0, NULL, 1, r_p2, r_p0, NULL},\n\t{0, 1, r_p0, r_p2, NULL, 1, r_p0, r_p1, NULL},\n};\n\n// FIXME: some of these can become statics\nint           a_sstepxfrac, a_tstepxfrac, r_lstepx, a_ststepxwhole;\nint           r_sstepx, r_tstepx, r_lstepy, r_sstepy, r_tstepy;\nint           r_zistepx, r_zistepy;\nint           d_aspancount, d_countextrastep;\n\nspanpackage_t *a_spans;\nspanpackage_t *d_pedgespanpackage;\nstatic int    ystart;\npixel_t       *d_pdest, *d_ptex;\nshort         *d_pz;\nint           d_sfrac, d_tfrac, d_light, d_zi;\nint           d_ptexextrastep, d_sfracextrastep;\nint           d_tfracextrastep, d_lightextrastep, d_pdestextrastep;\nint           d_lightbasestep, d_pdestbasestep, d_ptexbasestep;\nint           d_sfracbasestep, d_tfracbasestep;\nint           d_ziextrastep, d_zibasestep;\nint           d_pzextrastep, d_pzbasestep;\n\nstatic int    ubasestep, errorterm, erroradjustup, erroradjustdown;\n\ntypedef struct\n{\n\tint quotient;\n\tint remainder;\n} adivtab_t;\n\nstatic adivtab_t adivtab[32 * 32] = {\n#include \"adivtab.h\"\n};\n\nbyte *skintable[MAX_LBM_HEIGHT];\nint  skinwidth;\nbyte *skinstart;\n\nvoid (*d_pdrawspans)( spanpackage_t *pspanpackage );\n\nstatic void R_PolysetStub( spanpackage_t *pspanpackage )\n{\n\n}\n\nvoid R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpans8_66( spanpackage_t *pspanpackage );\nvoid R_PolysetDrawSpans8_Opaque( spanpackage_t *pspanpackage );\n\nqboolean R_PolysetCalcGradients( int skinwidth );\nvoid R_DrawNonSubdiv( void );\nvoid R_PolysetSetEdgeTable( void );\nvoid R_RasterizeAliasPolySmooth( void );\nvoid R_PolysetScanLeftEdge( int height );\nqboolean R_PolysetScanLeftEdge_C( int height );\n\n/*\n================\nR_DrawTriangle\n================\n*/\nvoid R_DrawTriangle( void )\n{\n\tspanpackage_t spans[DPS_MAXSPANS];\n\n\tint           dv1_ab, dv0_ac;\n\tint           dv0_ab, dv1_ac;\n\n\t/*\n\td_xdenom = ( aliastriangleparms.a->v[1] - aliastriangleparms.b->v[1] ) * ( aliastriangleparms.a->v[0] - aliastriangleparms.c->v[0] ) -\n\t\t\t   ( aliastriangleparms.a->v[0] - aliastriangleparms.b->v[0] ) * ( aliastriangleparms.a->v[1] - aliastriangleparms.c->v[1] );\n\t*/\n\n\tdv0_ab = aliastriangleparms.a->u - aliastriangleparms.b->u;\n\tdv1_ab = aliastriangleparms.a->v - aliastriangleparms.b->v;\n\n\tif( !( dv0_ab | dv1_ab ))\n\t\treturn;\n\n\tdv0_ac = aliastriangleparms.a->u - aliastriangleparms.c->u;\n\tdv1_ac = aliastriangleparms.a->v - aliastriangleparms.c->v;\n\n\tif( !( dv0_ac | dv1_ac ))\n\t\treturn;\n\n\td_xdenom = ( dv0_ac * dv1_ab ) - ( dv0_ab * dv1_ac );\n\n\tif( d_xdenom < 0 )\n\t{\n\t\ta_spans = spans;\n\n\t\tr_p0[0] = aliastriangleparms.a->u;  // u\n\t\tr_p0[1] = aliastriangleparms.a->v;  // v\n\t\tr_p0[2] = aliastriangleparms.a->s;  // s\n\t\tr_p0[3] = aliastriangleparms.a->t;  // t\n\t\tr_p0[4] = aliastriangleparms.a->l;  // light\n\t\tr_p0[5] = aliastriangleparms.a->zi; // iz\n\n\t\tr_p1[0] = aliastriangleparms.b->u;\n\t\tr_p1[1] = aliastriangleparms.b->v;\n\t\tr_p1[2] = aliastriangleparms.b->s;\n\t\tr_p1[3] = aliastriangleparms.b->t;\n\t\tr_p1[4] = aliastriangleparms.b->l;\n\t\tr_p1[5] = aliastriangleparms.b->zi;\n\n\t\tr_p2[0] = aliastriangleparms.c->u;\n\t\tr_p2[1] = aliastriangleparms.c->v;\n\t\tr_p2[2] = aliastriangleparms.c->s;\n\t\tr_p2[3] = aliastriangleparms.c->t;\n\t\tr_p2[4] = aliastriangleparms.c->l;\n\t\tr_p2[5] = aliastriangleparms.c->zi;\n\n\t\tR_PolysetSetEdgeTable();\n\t\tR_RasterizeAliasPolySmooth();\n\t}\n}\n\n\nstatic pixel_t *skinend;\n\nstatic inline qboolean R_DrawCheckBounds( pixel_t *lptex )\n{\n\tpixel_t *skin = r_affinetridesc.pskin;\n\tif( lptex - skin < 0 || lptex - skinend >= 0 )\n\t\treturn false;\n\treturn true;\n}\n\nstatic inline qboolean R_PolysetCheckBounds( pixel_t *lptex, int lsfrac, int ltfrac, int lcount )\n{\n\tpixel_t *start, *end;\n\tstart = r_affinetridesc.pskin;\n\tend = skinend;\n\n\t// span is linear, so only need to check first and last\n\tif( lptex - start < 0 || lptex - end >= 0 )\n\t\treturn false;\n\n\tif( !( --lcount ))\n\t\treturn true;\n\n\tlptex = lptex + a_ststepxwhole * lcount + (( lsfrac + ( a_sstepxfrac * lcount )) >> 16 ) + (( ltfrac + ( a_tstepxfrac * lcount )) >> 16 ) * r_affinetridesc.skinwidth;\n\n\tif( lptex - start < 0 || lptex - end >= 0 )\n\t\treturn false;\n\n\n\treturn true;\n}\n\n\n/*\n===================\nR_PolysetScanLeftEdge_C\n====================\n*/\nqboolean R_PolysetScanLeftEdge_C( int height )\n{\n\tdo\n\t{\n\t\td_pedgespanpackage->pdest = d_pdest;\n\t\td_pedgespanpackage->pz = d_pz;\n\t\td_pedgespanpackage->count = d_aspancount;\n\t\td_pedgespanpackage->ptex = d_ptex;\n\n\t\td_pedgespanpackage->sfrac = d_sfrac;\n\t\td_pedgespanpackage->tfrac = d_tfrac;\n\n\t\t// FIXME: need to clamp l, s, t, at both ends?\n\t\td_pedgespanpackage->light = d_light;\n\t\td_pedgespanpackage->zi = d_zi;\n\n\t\td_pedgespanpackage++;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_pdest += d_pdestextrastep;\n\t\t\td_pz += d_pzextrastep;\n\t\t\td_aspancount += d_countextrastep;\n\t\t\td_ptex += d_ptexextrastep;\n\t\t\td_sfrac += d_sfracextrastep;\n\t\t\td_ptex += d_sfrac >> 16;\n\n\n\t\t\td_sfrac &= 0xFFFF;\n\t\t\td_tfrac += d_tfracextrastep;\n\t\t\tif( d_tfrac & 0x10000 )\n\t\t\t{\n\t\t\t\td_ptex += r_affinetridesc.skinwidth;\n\t\t\t\td_tfrac &= 0xFFFF;\n\t\t\t}\n\t\t\td_light += d_lightextrastep;\n\t\t\td_zi += d_ziextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_pdest += d_pdestbasestep;\n\t\t\td_pz += d_pzbasestep;\n\t\t\td_aspancount += ubasestep;\n\t\t\td_ptex += d_ptexbasestep;\n\t\t\td_sfrac += d_sfracbasestep;\n\t\t\td_ptex += d_sfrac >> 16;\n\t\t\td_sfrac &= 0xFFFF;\n\t\t\td_tfrac += d_tfracbasestep;\n\t\t\tif( d_tfrac & 0x10000 )\n\t\t\t{\n\t\t\t\td_ptex += r_affinetridesc.skinwidth;\n\t\t\t\td_tfrac &= 0xFFFF;\n\t\t\t}\n\t\t\td_light += d_lightbasestep;\n\t\t\td_zi += d_zibasestep;\n\t\t}\n\t}\n\twhile( --height );\n\treturn true;\n}\n\n/*\n===================\nFloorDivMod\n\nReturns mathematically correct (floor-based) quotient and remainder for\nnumer and denom, both of which should contain no fractional part. The\nquotient must fit in 32 bits.\nFIXME: GET RID OF THIS! (FloorDivMod)\n====================\n*/\nstatic void FloorDivMod( float numer, float denom, int *quotient,\n\t\t\t int *rem )\n{\n\tint   q, r;\n\tfloat x;\n\n\tif( numer >= 0.0f )\n\t{\n\n\t\tx = floor( numer / denom );\n\t\tq = (int)x;\n\t\tr = (int)floor( numer - ( x * denom ));\n\t}\n\telse\n\t{\n\t\t//\n\t\t// perform operations with positive values, and fix mod to make floor-based\n\t\t//\n\t\tx = floor( -numer / denom );\n\t\tq = -(int)x;\n\t\tr = (int)floor( -numer - ( x * denom ));\n\t\tif( r != 0 )\n\t\t{\n\t\t\tq--;\n\t\t\tr = (int)denom - r;\n\t\t}\n\t}\n\tif( q > INT_MAX / 2 || q < INT_MIN / 2 )\n\t{\n\t\tint i;\n\t\td_pdrawspans = R_PolysetStub;\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: q overflow!\\n\", __func__ );\n\t\tq = 1;\n\t}\n\n\tif( r > INT_MAX / 2 || r < INT_MIN / 2 )\n\t{\n\t\tint i;\n\t\td_pdrawspans = R_PolysetStub;\n\t\tgEngfuncs.Con_Printf( S_ERROR \"%s: r overflow!\\n\", __func__ );\n\t\tr = 1;\n\t}\n\n\t*quotient = q;\n\t*rem = r;\n}\n\n\n/*\n===================\nR_PolysetSetUpForLineScan\n====================\n*/\nstatic void R_PolysetSetUpForLineScan( fixed8_t startvertu, fixed8_t startvertv,\n\t\t\t\t       fixed8_t endvertu, fixed8_t endvertv )\n{\n\tfloat     dm, dn;\n\tint       tm, tn;\n\tadivtab_t *ptemp;\n\n// TODO: implement x86 version\n\n\terrorterm = -1;\n\n\ttm = endvertu - startvertu;\n\ttn = endvertv - startvertv;\n\n\tif((( tm <= 16 ) && ( tm >= -15 ))\n\t   && (( tn <= 16 ) && ( tn >= -15 )))\n\t{\n\t\tptemp = &adivtab[(( tm + 15 ) << 5 ) + ( tn + 15 )];\n\t\tubasestep = ptemp->quotient;\n\t\terroradjustup = ptemp->remainder;\n\t\terroradjustdown = tn;\n\t}\n\telse\n\t{\n\t\tdm = tm;\n\t\tdn = tn;\n\n\t\tFloorDivMod( dm, dn, &ubasestep, &erroradjustup );\n\n\t\terroradjustdown = dn;\n\t}\n}\n\n\n\n/*\n================\nR_PolysetCalcGradients\n================\n*/\nqboolean R_PolysetCalcGradients( int skinwidth )\n{\n\tfloat xstepdenominv, ystepdenominv, t0, t1;\n\tfloat p01_minus_p21, p11_minus_p21, p00_minus_p20, p10_minus_p20;\n\n\tp00_minus_p20 = r_p0[0] - r_p2[0];\n\tp01_minus_p21 = r_p0[1] - r_p2[1];\n\tp10_minus_p20 = r_p1[0] - r_p2[0];\n\tp11_minus_p21 = r_p1[1] - r_p2[1];\n\n\t/*printf(\"gradients for triangle\\n\");\n\tprintf(\"%d %d %d %d %d %d\\n\" ,  r_p0[0], r_p0[1], r_p0[2] >> 16, r_p0[3] >> 16, r_p0[4], r_p0[5]);\n\tprintf(\"%d %d %d %d %d %d\\n\" ,  r_p1[0], r_p1[1], r_p1[2] >> 16, r_p1[3] >> 16, r_p1[4], r_p1[5]);\n\tprintf(\"%d %d %d %d %d %d\\n\\n\", r_p2[0], r_p2[1], r_p2[2] >> 16, r_p2[3] >> 16, r_p2[4], r_p2[5]);\n\t*/\n\txstepdenominv = 1.0f / (float)d_xdenom;\n\n\tystepdenominv = -xstepdenominv;\n\n// ceil () for light so positive steps are exaggerated, negative steps\n// diminished,  pushing us away from underflow toward overflow. Underflow is\n// very visible, overflow is very unlikely, because of ambient lighting\n\tt0 = r_p0[4] - r_p2[4];\n\tt1 = r_p1[4] - r_p2[4];\n\tr_lstepx = (int)\n\t\t   ceil(( t1 * p01_minus_p21 - t0 * p11_minus_p21 ) * xstepdenominv );\n\tr_lstepy = (int)\n\t\t   ceil(( t1 * p00_minus_p20 - t0 * p10_minus_p20 ) * ystepdenominv );\n\n\tt0 = r_p0[2] - r_p2[2];\n\tt1 = r_p1[2] - r_p2[2];\n\tr_sstepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 )\n\t\t\t * xstepdenominv );\n\tr_sstepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 )\n\t\t\t * ystepdenominv );\n\n\tt0 = r_p0[3] - r_p2[3];\n\tt1 = r_p1[3] - r_p2[3];\n\tr_tstepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 )\n\t\t\t * xstepdenominv );\n\tr_tstepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 )\n\t\t\t * ystepdenominv );\n\n\tt0 = r_p0[5] - r_p2[5];\n\tt1 = r_p1[5] - r_p2[5];\n\tr_zistepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 )\n\t\t\t  * xstepdenominv );\n\tr_zistepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 )\n\t\t\t  * ystepdenominv );\n\n\t{\n\t\ta_sstepxfrac = r_sstepx & 0xFFFF;\n\t\ta_tstepxfrac = r_tstepx & 0xFFFF;\n\t}\n\n\ta_ststepxwhole = skinwidth * ( r_tstepx >> 16 ) + ( r_sstepx >> 16 );\n\n\tskinend = (pixel_t *)r_affinetridesc.pskin + r_affinetridesc.skinwidth * r_affinetridesc.skinheight;\n\treturn true;\n}\n\n\n/*\n================\nR_PolysetDrawSpans8\n================\n*/\nvoid R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\t\t\tpspanpackage++;\n#if BOUNDCHECK_MODE == 0\n\t\t\tif( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ))\n\t\t\t\tcontinue;\n#endif\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n#if BOUNDCHECK_MODE == 1\n\t\t\t\t\tif( !R_DrawCheckBounds( lptex ))\n\t\t\t\t\t\treturn;\n#endif\n\n\t\t\t\t\tpixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )];\n\t\t\t\t\tint     alpha = vid.alpha;\n\t\t\t\t\ttemp = BLEND_COLOR( temp, vid.color );\n\n\t\t\t\t\tif( alpha == 7 )\n\t\t\t\t\t\t*lpdest = temp;\n\t\t\t\t\telse if( alpha )\n\t\t\t\t\t\t*lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256];\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\t\telse\n\t\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\n\n/*\n================\nR_PolysetDrawSpans8\n================\n*/\nvoid R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\t\t\tpspanpackage++;\n#if BOUNDCHECK_MODE == 0\n\t\t\tif( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ))\n\t\t\t\tcontinue;\n#endif\n\t\t\tdo\n\t\t\t{\n\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n#if BOUNDCHECK_MODE == 1\n\t\t\t\t\tif( !R_DrawCheckBounds( lptex ))\n\t\t\t\t\t\treturn;\n#endif\n\n\t\t\t\t\tpixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )];\n\t\t\t\t\ttemp = BLEND_COLOR( temp, vid.color );\n\n\t\t\t\t\t*lpdest = BLEND_ADD( temp, *lpdest );\n\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\t\telse\n\t\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\n\n/*\n================\nR_PolysetDrawSpans8\n================\n*/\nvoid R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\t\t\tpspanpackage++;\n#if BOUNDCHECK_MODE == 0\n\t\t\tif( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ))\n\t\t\t\tcontinue;\n#endif\n\t\t\tdo\n\t\t\t{\n\t\t\t\t// if ((lzi >> 16) >= *lpz)\n\t\t\t\t{\n#if BOUNDCHECK_MODE == 1\n\t\t\t\t\tif( !R_DrawCheckBounds( lptex ))\n\t\t\t\t\t\treturn;\n#endif\n\t\t\t\t\tpixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )];\n\t\t\t\t\ttemp = BLEND_COLOR( temp, vid.color );\n\n\t\t\t\t\t*lpdest = BLEND_ADD( temp, *lpdest );\n\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\t\telse\n\t\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\n\n/*\n================\nR_PolysetDrawSpans8\n================\n*/\nvoid R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\t\t\tpspanpackage++;\n#if BOUNDCHECK_MODE == 0\n\t\t\tif( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ))\n\t\t\t\tcontinue;\n#endif\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n#if BOUNDCHECK_MODE == 1\n\t\t\t\t\tif( !R_DrawCheckBounds( lptex ))\n\t\t\t\t\t\treturn;\n#endif\n\t\t\t\t\tpixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )];\n\n\t\t\t\t\tint     alpha = temp >> 13;\n\t\t\t\t\ttemp = temp << 3;\n\t\t\t\t\ttemp = BLEND_COLOR( temp, vid.color );\n\t\t\t\t\tif( alpha == 7 )\n\t\t\t\t\t\t*lpdest = temp;\n\t\t\t\t\telse if( alpha )\n\t\t\t\t\t\t*lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256];\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\t\telse\n\t\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\n\n\n/*\n================\nR_PolysetDrawSpans8\n================\n*/\nvoid R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n\t\t\t\t\tpixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )];\n\n\t\t\t\t\tint     alpha = tr.blend * 7;\n\t\t\t\t\tif( alpha == 7 )\n\t\t\t\t\t\t*lpdest = temp;\n\t\t\t\t\telse if( alpha )\n\t\t\t\t\t\t*lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256];\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\n\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\nvoid R_PolysetDrawSpansConstant8_33( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlzi = pspanpackage->zi;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n\t\t\t\t\t*lpdest = BLEND_ALPHA( 2, r_aliasblendcolor, *lpdest ); // vid.alphamap[r_aliasblendcolor + *lpdest*256];\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\n\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\nvoid R_PolysetDrawSpans8_66( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tpixel_t *lptex;\n\tint     lsfrac, ltfrac;\n\tint     llight;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n\t\t\t\t\tint temp = vid.colormap[*lptex + ( llight & 0xFF00 )];\n\n\t\t\t\t\t*lpdest = BLEND_ALPHA( 5, temp, *lpdest ); // vid.alphamap[temp*256 + *lpdest];\n\t\t\t\t\t*lpz = lzi >> 16;\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\n\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\nstatic void R_PolysetDrawSpansConstant8_66( spanpackage_t *pspanpackage )\n{\n\tint     lcount;\n\tpixel_t *lpdest;\n\tint     lzi;\n\tshort   *lpz;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlzi = pspanpackage->zi;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n\t\t\t\t\t*lpdest = BLEND_ALPHA( 5, r_aliasblendcolor, *lpdest ); // vid.alphamap[r_aliasblendcolor*256 + *lpdest];\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\n\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\nvoid R_PolysetDrawSpans8_Opaque( spanpackage_t *pspanpackage )\n{\n\tint lcount;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tint     lsfrac, ltfrac;\n\t\t\tpixel_t *lpdest;\n\t\t\tpixel_t *lptex;\n\t\t\tint     llight;\n\t\t\tint     lzi;\n\t\t\tshort   *lpz;\n\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n// PGM\n\t\t\t\t\t/*if(r_newrefdef.rdflags & RDF_IRGOGGLES && RI.currententity->flags & RF_IR_VISIBLE)\n\t\t\t\t\t\t*lpdest = ((byte *)vid.colormap)[irtable[*lptex]];\n\t\t\t\t\telse*/\n\t\t\t\t\t*lpdest = ((byte *)vid.colormap )[*lptex + ( llight & 0xFF00 )];\n// PGM\n\t\t\t\t\t*lpz = lzi >> 16;\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\n\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\nvoid R_PolysetFillSpans8( spanpackage_t *pspanpackage )\n{\n\t// int\t\t\t\tcolor;\n\tint lcount;\n// FIXME: do z buffering\n\n\t// color = d_aflatcolor++ * 10;\n\n\tdo\n\t{\n\t\tlcount = d_aspancount - pspanpackage->count;\n\t\t// d_ptex + a_ststepxwhole * lcount  + ((a_sstepxfrac * lcount) >> 16) + ((a_tstepxfrac * lcount) >> 16)*r_affinetridesc.skinwidth;\n\n\t\terrorterm += erroradjustup;\n\t\tif( errorterm >= 0 )\n\t\t{\n\t\t\td_aspancount += d_countextrastep;\n\t\t\terrorterm -= erroradjustdown;\n\t\t}\n\t\telse\n\t\t{\n\t\t\td_aspancount += ubasestep;\n\t\t}\n\n\t\tif( lcount )\n\t\t{\n\t\t\tint     lsfrac, ltfrac;\n\t\t\tpixel_t *lpdest;\n\t\t\tpixel_t *lptex;\n\t\t\tint     llight;\n\t\t\tint     lzi;\n\t\t\tshort   *lpz;\n\n\n\t\t\tlpdest = pspanpackage->pdest;\n\t\t\tlptex = pspanpackage->ptex;\n\t\t\tlpz = pspanpackage->pz;\n\t\t\tlsfrac = pspanpackage->sfrac;\n\t\t\tltfrac = pspanpackage->tfrac;\n\t\t\tllight = pspanpackage->light;\n\t\t\tlzi = pspanpackage->zi;\n\t\t\tpspanpackage++;\n#if BOUNDCHECK_MODE == 0\n\t\t\tif( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ))\n\t\t\t\tcontinue;\n#endif\n\n\t\t\tdo\n\t\t\t{\n\t\t\t\tif(( lzi >> 16 ) >= *lpz )\n\t\t\t\t{\n#if BOUNDCHECK_MODE == 1\n\t\t\t\t\tif( !R_DrawCheckBounds( lptex ))\n\t\t\t\t\t\treturn;\n#endif\n\t\t\t\t\tpixel_t src = *lptex;\n\t\t\t\t\t*lpdest = vid.colormap[( src >> 3 ) | (( llight & 0x1F00 ) << 5 )] | ( src & 7 );\n\t\t\t\t\t*lpz = lzi >> 16;\n\t\t\t\t}\n\t\t\t\tlpdest++;\n\t\t\t\tlzi += r_zistepx;\n\t\t\t\tlpz++;\n\t\t\t\tllight += r_lstepx;\n\t\t\t\tlptex += a_ststepxwhole;\n\t\t\t\tlsfrac += a_sstepxfrac;\n\t\t\t\tlptex += lsfrac >> 16;\n\t\t\t\tlsfrac &= 0xFFFF;\n\t\t\t\tltfrac += a_tstepxfrac;\n\t\t\t\tif( ltfrac & 0x10000 )\n\t\t\t\t{\n\t\t\t\t\tlptex += r_affinetridesc.skinwidth;\n\t\t\t\t\tltfrac &= 0xFFFF;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile( --lcount );\n\t\t}\n\t\telse\n\t\t\tpspanpackage++;\n\t}\n\twhile( pspanpackage->count != -999999 );\n}\n\n/*\n================\nR_RasterizeAliasPolySmooth\n================\n*/\nvoid R_RasterizeAliasPolySmooth( void )\n{\n\tint initialleftheight, initialrightheight;\n\tint *plefttop, *prighttop, *pleftbottom, *prightbottom;\n\tint working_lstepx, originalcount;\n\n\tplefttop = pedgetable->pleftedgevert0;\n\tprighttop = pedgetable->prightedgevert0;\n\n\tpleftbottom = pedgetable->pleftedgevert1;\n\tprightbottom = pedgetable->prightedgevert1;\n\n\tinitialleftheight = pleftbottom[1] - plefttop[1];\n\tinitialrightheight = prightbottom[1] - prighttop[1];\n\n//\n// set the s, t, and light gradients, which are consistent across the triangle\n// because being a triangle, things are affine\n//\n\tif( !R_PolysetCalcGradients( r_affinetridesc.skinwidth ))\n\t\treturn;\n//\n// rasterize the polygon\n//\n\n//\n// scan out the top (and possibly only) part of the left edge\n//\n\td_pedgespanpackage = a_spans;\n\n\tystart = plefttop[1];\n\td_aspancount = plefttop[0] - prighttop[0];\n\n\td_ptex = (pixel_t *)r_affinetridesc.pskin + ( plefttop[2] >> 16 )\n\t\t + ( plefttop[3] >> 16 ) * r_affinetridesc.skinwidth;\n\n\t{\n\t\td_sfrac = plefttop[2] & 0xFFFF;\n\t\td_tfrac = plefttop[3] & 0xFFFF;\n\t}\n\td_light = plefttop[4];\n\td_zi = plefttop[5];\n\n\td_pdest = (pixel_t *)d_viewbuffer\n\t\t  + ystart * r_screenwidth + plefttop[0];\n\td_pz = d_pzbuffer + ystart * d_zwidth + plefttop[0];\n\n\tif( initialleftheight == 1 )\n\t{\n\n\t\td_pedgespanpackage->pdest = d_pdest;\n\t\td_pedgespanpackage->pz = d_pz;\n\t\td_pedgespanpackage->count = d_aspancount;\n\t\td_pedgespanpackage->ptex = d_ptex;\n\n\t\td_pedgespanpackage->sfrac = d_sfrac;\n\t\td_pedgespanpackage->tfrac = d_tfrac;\n\n\t\t// FIXME: need to clamp l, s, t, at both ends?\n\t\td_pedgespanpackage->light = d_light;\n\t\td_pedgespanpackage->zi = d_zi;\n\t\td_pedgespanpackage++;\n\t}\n\telse\n\t{\n\t\tR_PolysetSetUpForLineScan( plefttop[0], plefttop[1],\n\t\t\t\t\t   pleftbottom[0], pleftbottom[1] );\n\n\t\t{\n\t\t\td_pzbasestep = d_zwidth + ubasestep;\n\t\t\td_pzextrastep = d_pzbasestep + 1;\n\t\t}\n\n\t\td_pdestbasestep = r_screenwidth + ubasestep;\n\t\td_pdestextrastep = d_pdestbasestep + 1;\n\n\t\t// TODO: can reuse partial expressions here\n\n\t\t// for negative steps in x along left edge, bias toward overflow rather than\n\t\t// underflow (sort of turning the floor () we did in the gradient calcs into\n\t\t// ceil (), but plus a little bit)\n\t\tif( ubasestep < 0 )\n\t\t\tworking_lstepx = r_lstepx - 1;\n\t\telse\n\t\t\tworking_lstepx = r_lstepx;\n\n\t\td_countextrastep = ubasestep + 1;\n\t\td_ptexbasestep = (( r_sstepy + r_sstepx * ubasestep ) >> 16 )\n\t\t\t\t + (( r_tstepy + r_tstepx * ubasestep ) >> 16 )\n\t\t\t\t * r_affinetridesc.skinwidth;\n\t\t{\n\t\t\td_sfracbasestep = ( r_sstepy + r_sstepx * ubasestep ) & 0xFFFF;\n\t\t\td_tfracbasestep = ( r_tstepy + r_tstepx * ubasestep ) & 0xFFFF;\n\t\t}\n\t\td_lightbasestep = r_lstepy + working_lstepx * ubasestep;\n\t\td_zibasestep = r_zistepy + r_zistepx * ubasestep;\n\n\t\td_ptexextrastep = (( r_sstepy + r_sstepx * d_countextrastep ) >> 16 )\n\t\t\t\t  + (( r_tstepy + r_tstepx * d_countextrastep ) >> 16 )\n\t\t\t\t  * r_affinetridesc.skinwidth;\n\t\t{\n\t\t\td_sfracextrastep = ( r_sstepy + r_sstepx * d_countextrastep ) & 0xFFFF;\n\t\t\td_tfracextrastep = ( r_tstepy + r_tstepx * d_countextrastep ) & 0xFFFF;\n\t\t}\n\t\td_lightextrastep = d_lightbasestep + working_lstepx;\n\t\td_ziextrastep = d_zibasestep + r_zistepx;\n\n\t\t{\n\t\t\tif( !R_PolysetScanLeftEdge_C( initialleftheight ))\n\t\t\t\treturn;\n\t\t}\n\t}\n\n//\n// scan out the bottom part of the left edge, if it exists\n//\n\tif( pedgetable->numleftedges == 2 )\n\t{\n\t\tint height;\n\n\t\tplefttop = pleftbottom;\n\t\tpleftbottom = pedgetable->pleftedgevert2;\n\n\t\theight = pleftbottom[1] - plefttop[1];\n\n// TODO: make this a function; modularize this function in general\n\n\t\tystart = plefttop[1];\n\t\td_aspancount = plefttop[0] - prighttop[0];\n\t\td_ptex = (pixel_t *)r_affinetridesc.pskin + ( plefttop[2] >> 16 )\n\t\t\t + ( plefttop[3] >> 16 ) * r_affinetridesc.skinwidth;\n\n\t\td_sfrac = 0;\n\t\td_tfrac = 0;\n\t\td_light = plefttop[4];\n\t\td_zi = plefttop[5];\n\n\t\td_pdest = (pixel_t *)d_viewbuffer + ystart * r_screenwidth + plefttop[0];\n\t\td_pz = d_pzbuffer + ystart * d_zwidth + plefttop[0];\n\n\n\n\t\tif( height == 1 )\n\t\t{\n\t\t\td_pedgespanpackage->pdest = d_pdest;\n\t\t\td_pedgespanpackage->pz = d_pz;\n\t\t\td_pedgespanpackage->count = d_aspancount;\n\t\t\td_pedgespanpackage->ptex = d_ptex;\n\n\t\t\td_pedgespanpackage->sfrac = d_sfrac;\n\t\t\td_pedgespanpackage->tfrac = d_tfrac;\n\n\t\t\t// FIXME: need to clamp l, s, t, at both ends?\n\t\t\td_pedgespanpackage->light = d_light;\n\t\t\td_pedgespanpackage->zi = d_zi;\n\t\t\td_pedgespanpackage++;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tR_PolysetSetUpForLineScan( plefttop[0], plefttop[1],\n\t\t\t\t\t\t   pleftbottom[0], pleftbottom[1] );\n\n\t\t\td_pdestbasestep = r_screenwidth + ubasestep;\n\t\t\td_pdestextrastep = d_pdestbasestep + 1;\n\n\t\t\t{\n\t\t\t\td_pzbasestep = d_zwidth + ubasestep;\n\t\t\t\td_pzextrastep = d_pzbasestep + 1;\n\t\t\t}\n\n\t\t\tif( ubasestep < 0 )\n\t\t\t\tworking_lstepx = r_lstepx - 1;\n\t\t\telse\n\t\t\t\tworking_lstepx = r_lstepx;\n\n\t\t\td_countextrastep = ubasestep + 1;\n\t\t\td_ptexbasestep = (( r_sstepy + r_sstepx * ubasestep ) >> 16 )\n\t\t\t\t\t + (( r_tstepy + r_tstepx * ubasestep ) >> 16 )\n\t\t\t\t\t * r_affinetridesc.skinwidth;\n\t\t\t{\n\t\t\t\td_sfracbasestep = ( r_sstepy + r_sstepx * ubasestep ) & 0xFFFF;\n\t\t\t\td_tfracbasestep = ( r_tstepy + r_tstepx * ubasestep ) & 0xFFFF;\n\t\t\t}\n\t\t\td_lightbasestep = r_lstepy + working_lstepx * ubasestep;\n\t\t\td_zibasestep = r_zistepy + r_zistepx * ubasestep;\n\n\t\t\td_ptexextrastep = (( r_sstepy + r_sstepx * d_countextrastep ) >> 16 )\n\t\t\t\t\t  + (( r_tstepy + r_tstepx * d_countextrastep ) >> 16 )\n\t\t\t\t\t  * r_affinetridesc.skinwidth;\n\t\t\t{\n\t\t\t\td_sfracextrastep = ( r_sstepy + r_sstepx * d_countextrastep ) & 0xFFFF;\n\t\t\t\td_tfracextrastep = ( r_tstepy + r_tstepx * d_countextrastep ) & 0xFFFF;\n\t\t\t}\n\t\t\td_lightextrastep = d_lightbasestep + working_lstepx;\n\t\t\td_ziextrastep = d_zibasestep + r_zistepx;\n\n\t\t\t{\n\t\t\t\tif( !R_PolysetScanLeftEdge_C( height ))\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n// scan out the top (and possibly only) part of the right edge, updating the\n// count field\n\td_pedgespanpackage = a_spans;\n\n\tR_PolysetSetUpForLineScan( prighttop[0], prighttop[1],\n\t\t\t\t   prightbottom[0], prightbottom[1] );\n\td_aspancount = 0;\n\td_countextrastep = ubasestep + 1;\n\toriginalcount = a_spans[initialrightheight].count;\n\ta_spans[initialrightheight].count = -999999; // mark end of the spanpackages\n\n\t( *d_pdrawspans )( a_spans );\n\n// scan out the bottom part of the right edge, if it exists\n\tif( pedgetable->numrightedges == 2 )\n\t{\n\t\tint height;\n\t\tspanpackage_t *pstart;\n\n\t\tpstart = a_spans + initialrightheight;\n\t\tpstart->count = originalcount;\n\n\t\td_aspancount = prightbottom[0] - prighttop[0];\n\n\t\tprighttop = prightbottom;\n\t\tprightbottom = pedgetable->prightedgevert2;\n\n\t\theight = prightbottom[1] - prighttop[1];\n\n\t\tR_PolysetSetUpForLineScan( prighttop[0], prighttop[1],\n\t\t\t\t\t   prightbottom[0], prightbottom[1] );\n\n\t\td_countextrastep = ubasestep + 1;\n\t\ta_spans[initialrightheight + height].count = -999999;\n\t\t// mark end of the spanpackages\n\t\t( *d_pdrawspans )( pstart );\n\t}\n}\n\n\n/*\n================\nR_PolysetSetEdgeTable\n================\n*/\nvoid R_PolysetSetEdgeTable( void )\n{\n\tint edgetableindex;\n\n\tedgetableindex = 0; // assume the vertices are already in\n\t//  top to bottom order\n\n//\n// determine which edges are right & left, and the order in which\n// to rasterize them\n//\n\tif( r_p0[1] >= r_p1[1] )\n\t{\n\t\tif( r_p0[1] == r_p1[1] )\n\t\t{\n\t\t\tif( r_p0[1] < r_p2[1] )\n\t\t\t\tpedgetable = &edgetables[2];\n\t\t\telse\n\t\t\t\tpedgetable = &edgetables[5];\n\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tedgetableindex = 1;\n\t\t}\n\t}\n\n\tif( r_p0[1] == r_p2[1] )\n\t{\n\t\tif( edgetableindex )\n\t\t\tpedgetable = &edgetables[8];\n\t\telse\n\t\t\tpedgetable = &edgetables[9];\n\n\t\treturn;\n\t}\n\telse if( r_p1[1] == r_p2[1] )\n\t{\n\t\tif( edgetableindex )\n\t\t\tpedgetable = &edgetables[10];\n\t\telse\n\t\t\tpedgetable = &edgetables[11];\n\n\t\treturn;\n\t}\n\n\tif( r_p0[1] > r_p2[1] )\n\t\tedgetableindex += 2;\n\n\tif( r_p1[1] > r_p2[1] )\n\t\tedgetableindex += 4;\n\n\tpedgetable = &edgetables[edgetableindex];\n}\n\n\n"
  },
  {
    "path": "ref/soft/r_rast.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_rast.c\n\n#include <assert.h>\n\n#include \"r_local.h\"\n\n#define MAXLEFTCLIPEDGES 100\n\n// !!! if these are changed, they must be changed in asm_draw.h too !!!\n#define FULLY_CLIPPED_CACHED 0x80000000\n#define FRAMECOUNT_MASK      0x7FFFFFFF\n\nunsigned int    cacheoffset;\n\nint             c_faceclip;                                             // number of faces clipped\n\n\nclipplane_t     *entity_clipplanes;\nclipplane_t     world_clipplanes[16];\n\nmedge16_t       *r_pedge;\n\nqboolean        r_leftclipped, r_rightclipped;\nstatic qboolean makeleftedge, makerightedge;\nqboolean        r_nearzionly;\n\nint             sintable[1280];\nint             intsintable[1280];\nint             blanktable[1280];               // PGM\n\nmvertex_t       r_leftenter, r_leftexit;\nmvertex_t       r_rightenter, r_rightexit;\n\ntypedef struct\n{\n\tfloat u, v;\n\tint   ceilv;\n} evert_t;\n\nint        r_emitted;\nfloat      r_nearzi;\nfloat      r_u1, r_v1, r_lzi1;\nint        r_ceilv1;\n\nqboolean   r_lastvertvalid;\nint        r_skyframe;\n\nmsurface_t *r_skyfaces;\nmplane_t   r_skyplanes[6];\nmtexinfo_t r_skytexinfo[6];\nmvertex_t  *r_skyverts;\nmedge16_t  *r_skyedges;\nint        *r_skysurfedges;\n\n// I just copied this data from a box map...\nint        skybox_planes[12] = {2, -128, 0, -128, 2, 128, 1, 128, 0, 128, 1, -128};\n\nint        box_surfedges[24] = { 1, 2, 3, 4, -1, 5, 6, 7, 8, 9, -6, 10, -2, -7, -9, 11,\n\t\t\t\t 12, -3, -11, -8, -12, -10, -5, -4};\nint        box_edges[24] = { 1, 2, 2, 3, 3, 4, 4, 1, 1, 5, 5, 6, 6, 2, 7, 8, 8, 6, 5, 7, 8, 3, 7, 4};\n\nint        box_faces[6] = {0, 0, 2, 2, 2, 0};\n\nvec3_t     box_vecs[6][2] = {\n\t{       {0, -1, 0}, {-1, 0, 0} },\n\t{ {0, 1, 0}, {0, 0, -1} },\n\t{       {0, -1, 0}, {1, 0, 0} },\n\t{ {1, 0, 0}, {0, 0, -1} },\n\t{ {0, -1, 0}, {0, 0, -1} },\n\t{ {-1, 0, 0}, {0, 0, -1} }\n};\n\nfloat      box_verts[8][3] = {\n\t{-1, -1, -1},\n\t{-1, 1, -1},\n\t{1, 1, -1},\n\t{1, -1, -1},\n\t{-1, -1, 1},\n\t{-1, 1, 1},\n\t{1, -1, 1},\n\t{1, 1, 1}\n};\n\n// down, west, up, north, east, south\n// {\"rt\", \"bk\", \"lf\", \"ft\", \"up\", \"dn\"};\n\n/*\n================\nR_EmitEdge\n================\n*/\nstatic void R_EmitEdge( mvertex_t *pv0, mvertex_t *pv1 )\n{\n\tedge_t *edge, *pcheck;\n\tint    u_check;\n\tfloat  u, u_step;\n\tvec3_t local, transformed;\n\tfloat  *world;\n\tint    v, v2, ceilv0;\n\tfloat  scale, lzi0, u0, v0;\n\tint    side;\n\n\tif( r_lastvertvalid )\n\t{\n\t\tu0 = r_u1;\n\t\tv0 = r_v1;\n\t\tlzi0 = r_lzi1;\n\t\tceilv0 = r_ceilv1;\n\t}\n\telse\n\t{\n\t\tworld = &pv0->position[0];\n\n\t\t// transform and project\n\t\tVectorSubtract( world, tr.modelorg, local );\n\t\tTransformVector( local, transformed );\n\n\t\tif( transformed[2] < NEAR_CLIP )\n\t\t\ttransformed[2] = NEAR_CLIP;\n\n\t\tlzi0 = 1.0f / transformed[2];\n\n\t\t// FIXME: build x/yscale into transform?\n\t\tscale = xscale * lzi0;\n\t\tu0 = ( xcenter + scale * transformed[0] );\n\t\tif( u0 < RI.fvrectx_adj )\n\t\t\tu0 = RI.fvrectx_adj;\n\t\tif( u0 > RI.fvrectright_adj )\n\t\t\tu0 = RI.fvrectright_adj;\n\n\t\tscale = yscale * lzi0;\n\t\tv0 = ( ycenter - scale * transformed[1] );\n\t\tif( v0 < RI.fvrecty_adj )\n\t\t\tv0 = RI.fvrecty_adj;\n\t\tif( v0 > RI.fvrectbottom_adj )\n\t\t\tv0 = RI.fvrectbottom_adj;\n\n\t\tceilv0 = (int) ceil( v0 );\n\t}\n\n\tworld = &pv1->position[0];\n\n// transform and project\n\tVectorSubtract( world, tr.modelorg, local );\n\tTransformVector( local, transformed );\n\n\tif( transformed[2] < NEAR_CLIP )\n\t\ttransformed[2] = NEAR_CLIP;\n\n\tr_lzi1 = 1.0f / transformed[2];\n\n\tscale = xscale * r_lzi1;\n\tr_u1 = ( xcenter + scale * transformed[0] );\n\tif( r_u1 < RI.fvrectx_adj )\n\t\tr_u1 = RI.fvrectx_adj;\n\tif( r_u1 > RI.fvrectright_adj )\n\t\tr_u1 = RI.fvrectright_adj;\n\n\tscale = yscale * r_lzi1;\n\tr_v1 = ( ycenter - scale * transformed[1] );\n\tif( r_v1 < RI.fvrecty_adj )\n\t\tr_v1 = RI.fvrecty_adj;\n\tif( r_v1 > RI.fvrectbottom_adj )\n\t\tr_v1 = RI.fvrectbottom_adj;\n\n\tif( r_lzi1 > lzi0 )\n\t\tlzi0 = r_lzi1;\n\n\tif( lzi0 > r_nearzi )   // for mipmap finding\n\t\tr_nearzi = lzi0;\n\n// for right edges, all we want is the effect on 1/z\n\tif( r_nearzionly )\n\t\treturn;\n\n\tr_emitted = 1;\n\n\tr_ceilv1 = (int) ceil( r_v1 );\n\n\n// create the edge\n\tif( ceilv0 == r_ceilv1 || ceilv0 < 0 )\n\t{\n\t\t// we cache unclipped horizontal edges as fully clipped\n\t\tif( cacheoffset != 0x7FFFFFFF )\n\t\t{\n\t\t\tcacheoffset = FULLY_CLIPPED_CACHED\n\t\t\t\t      | ( tr.framecount & FRAMECOUNT_MASK );\n\t\t}\n\n\t\treturn; // horizontal edge\n\t}\n\n\tside = ceilv0 > r_ceilv1;\n\n\tedge = edge_p++;\n\n\tedge->owner = r_pedge;\n\n\tedge->nearzi = lzi0;\n\n\tif( side == 0 )\n\t{\n\t\t// trailing edge (go from p1 to p2)\n\t\tv = ceilv0;\n\t\tv2 = r_ceilv1 - 1;\n\n\t\tif( v < 0 || v >= MAXHEIGHT )\n\t\t{\n\t\t\tgEngfuncs.Con_Printf( S_ERROR \"trailing edge overflow : %d\\n\", v );\n\t\t\treturn;\n\t\t}\n\n\t\tedge->surfs[0] = surface_p - surfaces;\n\t\tedge->surfs[1] = 0;\n\n\t\tu_step = (( r_u1 - u0 ) / ( r_v1 - v0 ));\n\t\tu = u0 + ((float)v - v0 ) * u_step;\n\t}\n\telse\n\t{\n\t\t// leading edge (go from p2 to p1)\n\t\tv2 = ceilv0 - 1;\n\t\tv = r_ceilv1;\n\n\t\tif( v < 0 || v >= MAXHEIGHT )\n\t\t{\n\t\t\tgEngfuncs.Con_Printf( S_ERROR \"leading edge overflow : %d\\n\", v );\n\t\t\treturn;\n\t\t}\n\n\t\tedge->surfs[0] = 0;\n\t\tedge->surfs[1] = surface_p - surfaces;\n\n\t\tu_step = (( u0 - r_u1 ) / ( v0 - r_v1 ));\n\t\tu = r_u1 + ((float)v - r_v1 ) * u_step;\n\t}\n\n\tedge->u_step = u_step * 0x100000;\n\tedge->u = u * 0x100000 + 0xFFFFF;\n\n// we need to do this to avoid stepping off the edges if a very nearly\n// horizontal edge is less than epsilon above a scan, and numeric error causes\n// it to incorrectly extend to the scan, and the extension of the line goes off\n// the edge of the screen\n// FIXME: is this actually needed?\n\t/*int r = (gpGlobals->width<<20) + (1<<19) - 1;\n\tint x = (1<<20) + (1<<19) - 1;\n\tif (edge->u < x)\n\t\tedge->u = x;\n\tif (edge->u > r)\n\t\tedge->u = r;*/\n\tif( edge->u < RI.vrect_x_adj_shift20 )\n\t\tedge->u = RI.vrect_x_adj_shift20;\n\tif( edge->u > RI.vrectright_adj_shift20 )\n\t\tedge->u = RI.vrectright_adj_shift20;\n\n\n//\n// sort the edge in normally\n//\n\tu_check = edge->u;\n\tif( edge->surfs[0] )\n\t\tu_check++; // sort trailers after leaders\n\n\tif( !newedges[v] || newedges[v]->u >= u_check )\n\t{\n\t\tedge->next = newedges[v];\n\t\tnewedges[v] = edge;\n\t}\n\telse\n\t{\n\t\tpcheck = newedges[v];\n\t\twhile( pcheck->next && pcheck->next->u < u_check )\n\t\t\tpcheck = pcheck->next;\n\t\tedge->next = pcheck->next;\n\t\tpcheck->next = edge;\n\t}\n\n\tedge->nextremove = removeedges[v2];\n\tremoveedges[v2] = edge;\n}\n\n\n/*\n================\nR_ClipEdge\n================\n*/\nstatic void R_ClipEdge( mvertex_t *pv0, mvertex_t *pv1, clipplane_t *clip )\n{\n\tfloat     d0, d1, f;\n\tmvertex_t clipvert;\n\n\tif( clip )\n\t{\n\t\tdo\n\t\t{\n\t\t\td0 = DotProduct( pv0->position, clip->normal ) - clip->dist;\n\t\t\td1 = DotProduct( pv1->position, clip->normal ) - clip->dist;\n\n\t\t\tif( d0 >= 0 )\n\t\t\t{\n\t\t\t\t// point 0 is unclipped\n\t\t\t\tif( d1 >= 0 )\n\t\t\t\t{\n\t\t\t\t\t// both points are unclipped\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// only point 1 is clipped\n\n\t\t\t\t// we don't cache clipped edges\n\t\t\t\tcacheoffset = 0x7FFFFFFF;\n\n\t\t\t\tf = d0 / ( d0 - d1 );\n\t\t\t\tclipvert.position[0] = pv0->position[0]\n\t\t\t\t\t\t       + f * ( pv1->position[0] - pv0->position[0] );\n\t\t\t\tclipvert.position[1] = pv0->position[1]\n\t\t\t\t\t\t       + f * ( pv1->position[1] - pv0->position[1] );\n\t\t\t\tclipvert.position[2] = pv0->position[2]\n\t\t\t\t\t\t       + f * ( pv1->position[2] - pv0->position[2] );\n\n\t\t\t\tif( clip->leftedge )\n\t\t\t\t{\n\t\t\t\t\tr_leftclipped = true;\n\t\t\t\t\tr_leftexit = clipvert;\n\t\t\t\t}\n\t\t\t\telse if( clip->rightedge )\n\t\t\t\t{\n\t\t\t\t\tr_rightclipped = true;\n\t\t\t\t\tr_rightexit = clipvert;\n\t\t\t\t}\n\n\t\t\t\tR_ClipEdge( pv0, &clipvert, clip->next );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// point 0 is clipped\n\t\t\t\tif( d1 < 0 )\n\t\t\t\t{\n\t\t\t\t\t// both points are clipped\n\t\t\t\t\t// we do cache fully clipped edges\n\t\t\t\t\tif( !r_leftclipped )\n\t\t\t\t\t\tcacheoffset = FULLY_CLIPPED_CACHED\n\t\t\t\t\t\t\t      | ( tr.framecount & FRAMECOUNT_MASK );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// only point 0 is clipped\n\t\t\t\tr_lastvertvalid = false;\n\n\t\t\t\t// we don't cache partially clipped edges\n\t\t\t\tcacheoffset = 0x7FFFFFFF;\n\n\t\t\t\tf = d0 / ( d0 - d1 );\n\t\t\t\tclipvert.position[0] = pv0->position[0]\n\t\t\t\t\t\t       + f * ( pv1->position[0] - pv0->position[0] );\n\t\t\t\tclipvert.position[1] = pv0->position[1]\n\t\t\t\t\t\t       + f * ( pv1->position[1] - pv0->position[1] );\n\t\t\t\tclipvert.position[2] = pv0->position[2]\n\t\t\t\t\t\t       + f * ( pv1->position[2] - pv0->position[2] );\n\n\t\t\t\tif( clip->leftedge )\n\t\t\t\t{\n\t\t\t\t\tr_leftclipped = true;\n\t\t\t\t\tr_leftenter = clipvert;\n\t\t\t\t}\n\t\t\t\telse if( clip->rightedge )\n\t\t\t\t{\n\t\t\t\t\tr_rightclipped = true;\n\t\t\t\t\tr_rightenter = clipvert;\n\t\t\t\t}\n\n\t\t\t\tR_ClipEdge( &clipvert, pv1, clip->next );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\twhile(( clip = clip->next ) != NULL );\n\t}\n\n// add the edge\n\tR_EmitEdge( pv0, pv1 );\n}\n\n/*\n================\nR_EmitCachedEdge\n================\n*/\nstatic void R_EmitCachedEdge( void )\n{\n\tedge_t *pedge_t;\n\n\tpedge_t = (edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset );\n\n\tif( !pedge_t->surfs[0] )\n\t\tpedge_t->surfs[0] = surface_p - surfaces;\n\telse\n\t\tpedge_t->surfs[1] = surface_p - surfaces;\n\n\tif( pedge_t->nearzi > r_nearzi )// for mipmap finding\n\t\tr_nearzi = pedge_t->nearzi;\n\n\tr_emitted = 1;\n}\n\n\n/*\n================\nR_RenderFace\n================\n*/\nvoid R_RenderFace( msurface_t *fa, int clipflags )\n{\n\tint         i, lindex;\n\tunsigned    mask;\n\tmplane_t    *pplane;\n\tfloat       distinv;\n\tvec3_t      p_normal;\n\tmedge16_t   *pedges, tedge;\n\tclipplane_t *pclip;\n\n\t// translucent surfaces are not drawn by the edge renderer\n\tif( fa->flags & ( SURF_DRAWTURB | SURF_TRANSPARENT ))\n\t{\n\t\t// fa->nextalphasurface = r_alpha_surfaces;\n\t\t// r_alpha_surfaces = fa;\n\t\t// return;\n\t}\n\n\t// sky surfaces encountered in the world will cause the\n\t// environment box surfaces to be emited\n\tif( fa->flags & SURF_DRAWSKY )\n\t{\n\t\t// R_EmitSkyBox ();\n\t\t//\treturn;\n\t}\n\n// skip out if no more surfs\n\tif(( surface_p ) >= surf_max )\n\t{\n\t\t//\tr_outofsurfaces++;\n\t\treturn;\n\t}\n\n// ditto if not enough edges left, or switch to auxedges if possible\n\tif(( edge_p + fa->numedges + 4 ) >= edge_max )\n\t{\n\t\t// r_outofedges += fa->numedges;\n\t\treturn;\n\t}\n\n\tc_faceclip++;\n\n// set up clip planes\n\tpclip = NULL;\n\n\tfor( i = 3, mask = 0x08; i >= 0; i--, mask >>= 1 )\n\t{\n\t\tif( clipflags & mask )\n\t\t{\n\t\t\tqfrustum.view_clipplanes[i].next = pclip;\n\t\t\tpclip = &qfrustum.view_clipplanes[i];\n\t\t}\n\t}\n\n// push the edges through\n\tr_emitted = 0;\n\tr_nearzi = 0;\n\tr_nearzionly = false;\n\tmakeleftedge = makerightedge = false;\n\tpedges = RI.currentmodel->edges16;\n\tr_lastvertvalid = false;\n\n\tfor( i = 0; i < fa->numedges; i++ )\n\t{\n\t\tlindex = RI.currentmodel->surfedges[fa->firstedge + i];\n\n\t\tif( lindex > 0 )\n\t\t{\n\t\t\tr_pedge = &pedges[lindex];\n\n\t\t\t// if the edge is cached, we can just reuse the edge\n\t\t\tif( !insubmodel )\n\t\t\t{\n\t\t\t\tif( r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED )\n\t\t\t\t{\n\t\t\t\t\tif(( r_pedge->cachededgeoffset & FRAMECOUNT_MASK )\n\t\t\t\t\t   == tr.framecount )\n\t\t\t\t\t{\n\t\t\t\t\t\tr_lastvertvalid = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tif((((uintptr_t)edge_p - (uintptr_t)r_edges )\n\t\t\t\t\t    > r_pedge->cachededgeoffset )\n\t\t\t\t\t   && (((edge_t *)((uintptr_t)r_edges\n\t\t\t\t\t\t\t   + r_pedge->cachededgeoffset ))->owner == r_pedge ))\n\t\t\t\t\t{\n\t\t\t\t\t\tR_EmitCachedEdge();\n\t\t\t\t\t\tr_lastvertvalid = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// assume it's cacheable\n\t\t\tcacheoffset = (byte *)edge_p - (byte *)r_edges;\n\t\t\tr_leftclipped = r_rightclipped = false;\n\t\t\tR_ClipEdge( &r_pcurrentvertbase[r_pedge->v[0]],\n\t\t\t\t    &r_pcurrentvertbase[r_pedge->v[1]],\n\t\t\t\t    pclip );\n\t\t\tr_pedge->cachededgeoffset = cacheoffset;\n\n\t\t\tif( r_leftclipped )\n\t\t\t\tmakeleftedge = true;\n\t\t\tif( r_rightclipped )\n\t\t\t\tmakerightedge = true;\n\t\t\tr_lastvertvalid = true;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlindex = -lindex;\n\t\t\tr_pedge = &pedges[lindex];\n\t\t\t// if the edge is cached, we can just reuse the edge\n\t\t\tif( !insubmodel )\n\t\t\t{\n\t\t\t\tif( r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED )\n\t\t\t\t{\n\t\t\t\t\tif(( r_pedge->cachededgeoffset & FRAMECOUNT_MASK )\n\t\t\t\t\t   == tr.framecount )\n\t\t\t\t\t{\n\t\t\t\t\t\tr_lastvertvalid = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// it's cached if the cached edge is valid and is owned\n\t\t\t\t\t// by this medge16_t\n\t\t\t\t\tif((((uintptr_t)edge_p - (uintptr_t)r_edges )\n\t\t\t\t\t    > r_pedge->cachededgeoffset )\n\t\t\t\t\t   && (((edge_t *)((uintptr_t)r_edges\n\t\t\t\t\t\t\t   + r_pedge->cachededgeoffset ))->owner == r_pedge ))\n\t\t\t\t\t{\n\t\t\t\t\t\tR_EmitCachedEdge();\n\t\t\t\t\t\tr_lastvertvalid = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// assume it's cacheable\n\t\t\tcacheoffset = (byte *)edge_p - (byte *)r_edges;\n\t\t\tr_leftclipped = r_rightclipped = false;\n\t\t\tR_ClipEdge( &r_pcurrentvertbase[r_pedge->v[1]],\n\t\t\t\t    &r_pcurrentvertbase[r_pedge->v[0]],\n\t\t\t\t    pclip );\n\t\t\tr_pedge->cachededgeoffset = cacheoffset;\n\n\t\t\tif( r_leftclipped )\n\t\t\t\tmakeleftedge = true;\n\t\t\tif( r_rightclipped )\n\t\t\t\tmakerightedge = true;\n\t\t\tr_lastvertvalid = true;\n\t\t}\n\t}\n\n// if there was a clip off the left edge, add that edge too\n// FIXME: faster to do in screen space?\n// FIXME: share clipped edges?\n\tif( makeleftedge )\n\t{\n\t\tr_pedge = &tedge;\n\t\tr_lastvertvalid = false;\n\t\tR_ClipEdge( &r_leftexit, &r_leftenter, pclip->next );\n\t}\n\n// if there was a clip off the right edge, get the right r_nearzi\n\tif( makerightedge )\n\t{\n\t\tr_pedge = &tedge;\n\t\tr_lastvertvalid = false;\n\t\tr_nearzionly = true;\n\t\tR_ClipEdge( &r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next );\n\t}\n\n// if no edges made it out, return without posting the surface\n\tif( !r_emitted )\n\t\treturn;\n\n//\tr_polycount++;\n\n\tsurface_p->msurf = fa;\n\tsurface_p->nearzi = r_nearzi;\n\tsurface_p->flags = fa->flags;\n\tsurface_p->insubmodel = insubmodel;\n\tsurface_p->spanstate = 0;\n\tsurface_p->entity = RI.currententity;\n\tsurface_p->key = r_currentkey++;\n\tsurface_p->spans = NULL;\n\n\tpplane = fa->plane;\n// FIXME: cache this?\n\tTransformVector( pplane->normal, p_normal );\n// FIXME: cache this?\n\tdistinv = 1.0f / ( pplane->dist - DotProduct( tr.modelorg, pplane->normal ));\n\n\tsurface_p->d_zistepu = p_normal[0] * xscaleinv * distinv;\n\tsurface_p->d_zistepv = -p_normal[1] * yscaleinv * distinv;\n\tsurface_p->d_ziorigin = p_normal[2] * distinv\n\t\t\t\t- xcenter * surface_p->d_zistepu\n\t\t\t\t- ycenter * surface_p->d_zistepv;\n\n\tsurface_p++;\n}\n\n\n/*\n================\nR_RenderBmodelFace\n================\n*/\nvoid R_RenderBmodelFace( bedge_t *pedges, msurface_t *psurf )\n{\n\tint         i;\n\tunsigned    mask;\n\tmplane_t    *pplane;\n\tfloat       distinv;\n\tvec3_t      p_normal;\n\tmedge16_t   tedge;\n\tclipplane_t *pclip;\n\n\t/*if (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66))\n\t{\n\t\tpsurf->nextalphasurface = r_alpha_surfaces;\n\t\tr_alpha_surfaces = psurf;\n\t\treturn;\n\t}*/\n\n// skip out if no more surfs\n\tif( surface_p >= surf_max )\n\t{\n\t\t// r_outofsurfaces++;\n\t\treturn;\n\t}\n\n// ditto if not enough edges left, or switch to auxedges if possible\n\tif(( edge_p + psurf->numedges + 4 ) >= edge_max )\n\t{\n\t\t// r_outofedges += psurf->numedges;\n\t\treturn;\n\t}\n\n\tc_faceclip++;\n\n// this is a dummy to give the caching mechanism someplace to write to\n\tr_pedge = &tedge;\n\n// set up clip planes\n\tpclip = NULL;\n\n\tfor( i = 3, mask = 0x08; i >= 0; i--, mask >>= 1 )\n\t{\n\t\tif( r_clipflags & mask )\n\t\t{\n\t\t\tqfrustum.view_clipplanes[i].next = pclip;\n\t\t\tpclip = &qfrustum.view_clipplanes[i];\n\t\t}\n\t}\n\n// push the edges through\n\tr_emitted = 0;\n\tr_nearzi = 0;\n\tr_nearzionly = false;\n\tmakeleftedge = makerightedge = false;\n// FIXME: keep clipped bmodel edges in clockwise order so last vertex caching\n// can be used?\n\tr_lastvertvalid = false;\n\n\tfor( ; pedges; pedges = pedges->pnext )\n\t{\n\t\tr_leftclipped = r_rightclipped = false;\n\t\tR_ClipEdge( pedges->v[0], pedges->v[1], pclip );\n\n\t\tif( r_leftclipped )\n\t\t\tmakeleftedge = true;\n\t\tif( r_rightclipped )\n\t\t\tmakerightedge = true;\n\t}\n\n// if there was a clip off the left edge, add that edge too\n// FIXME: faster to do in screen space?\n// FIXME: share clipped edges?\n\tif( makeleftedge )\n\t{\n\t\tr_pedge = &tedge;\n\t\tR_ClipEdge( &r_leftexit, &r_leftenter, pclip->next );\n\t}\n\n// if there was a clip off the right edge, get the right r_nearzi\n\tif( makerightedge )\n\t{\n\t\tr_pedge = &tedge;\n\t\tr_nearzionly = true;\n\t\tR_ClipEdge( &r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next );\n\t}\n\n// if no edges made it out, return without posting the surface\n\tif( !r_emitted )\n\t\treturn;\n\n\t// r_polycount++;\n\n\tsurface_p->msurf = psurf;\n\tsurface_p->nearzi = r_nearzi;\n\tsurface_p->flags = psurf->flags;\n\tsurface_p->insubmodel = true;\n\tsurface_p->spanstate = 0;\n\tsurface_p->entity = RI.currententity;\n\tsurface_p->key = r_currentbkey;\n\tsurface_p->spans = NULL;\n\n\tpplane = psurf->plane;\n// FIXME: cache this?\n\tTransformVector( pplane->normal, p_normal );\n// FIXME: cache this?\n\tdistinv = 1.0f / ( pplane->dist - DotProduct( tr.modelorg, pplane->normal ));\n\n\tsurface_p->d_zistepu = p_normal[0] * xscaleinv * distinv;\n\tsurface_p->d_zistepv = -p_normal[1] * yscaleinv * distinv;\n\tsurface_p->d_ziorigin = p_normal[2] * distinv\n\t\t\t\t- xcenter * surface_p->d_zistepu\n\t\t\t\t- ycenter * surface_p->d_zistepv;\n\n\tsurface_p++;\n}\n\n"
  },
  {
    "path": "ref/soft/r_scan.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// d_scan.c\n//\n// Portable C scan-level rasterization code, all pixel depths.\n\n#include \"r_local.h\"\n\npixel_t    *r_turb_pbase, *r_turb_pdest;\nshort      *r_turb_pz;\nfixed16_t  r_turb_s, r_turb_t, r_turb_sstep, r_turb_tstep;\nint        r_turb_izistep, r_turb_izi;\nint        *r_turb_turb;\nstatic int r_turb_spancount;\nint        alpha;\n\n/*\n=============\nD_DrawTurbulent8Span\n=============\n*/\nvoid D_DrawTurbulent8Span( void )\n{\n\tint sturb, tturb;\n\n\tdo\n\t{\n\t\tsturb = (( r_turb_s + r_turb_turb[( r_turb_t >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63;\n\t\ttturb = (( r_turb_t + r_turb_turb[( r_turb_s >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63;\n\t\t*r_turb_pdest++ = *( r_turb_pbase + ( tturb << 6 ) + sturb );\n\t\tr_turb_s += r_turb_sstep;\n\t\tr_turb_t += r_turb_tstep;\n\t}\n\twhile( --r_turb_spancount > 0 );\n}\n\n/*\n=============\nD_DrawTurbulent8Span\n=============\n*/\nstatic void D_DrawTurbulent8ZSpan( void )\n{\n\tint sturb, tturb;\n\n\tdo\n\t{\n\t\tsturb = (( r_turb_s + r_turb_turb[( r_turb_t >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63;\n\t\ttturb = (( r_turb_t + r_turb_turb[( r_turb_s >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63;\n\t\tif( *r_turb_pz <= ( r_turb_izi >> 16 ))\n\t\t{\n\t\t\tpixel_t btemp = *( r_turb_pbase + ( tturb << 6 ) + sturb );\n\t\t\tif( alpha == 7 )\n\t\t\t\t*r_turb_pdest = btemp;\n\t\t\telse\n\t\t\t\t*r_turb_pdest = BLEND_ALPHA( alpha, btemp, *r_turb_pdest );\n\t\t}\n\t\tr_turb_pdest++;\n\t\tr_turb_pz++;\n\t\tr_turb_izi += r_turb_izistep;\n\t\tr_turb_s += r_turb_sstep;\n\t\tr_turb_t += r_turb_tstep;\n\t}\n\twhile( --r_turb_spancount > 0 );\n}\n\n/*\n=============\nTurbulent8\n=============\n*/\nvoid Turbulent8( espan_t *pspan )\n{\n\tint       count;\n\tfixed16_t snext, tnext;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz16stepu, tdivz16stepu, zi16stepu;\n\n\tr_turb_turb = sintable + ((int)( gp_cl->time * SPEED ) & ( CYCLE - 1 ));\n\n\tr_turb_sstep = 0; // keep compiler happy\n\tr_turb_tstep = 0; // ditto\n\n\tr_turb_pbase = cacheblock;\n\n\tsdivz16stepu = d_sdivzstepu * 16;\n\ttdivz16stepu = d_tdivzstepu * 16;\n\tzi16stepu = d_zistepu * 16;\n\n\tdo\n\t{\n\t\tr_turb_pdest = ( d_viewbuffer\n\t\t\t\t + ( r_screenwidth * pspan->v ) + pspan->u );\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\tr_turb_s = (int)( sdivz * z ) + sadjust;\n\t\tif( r_turb_s > bbextents )\n\t\t\tr_turb_s = bbextents;\n\t\telse if( r_turb_s < 0 )\n\t\t\tr_turb_s = 0;\n\n\t\tr_turb_t = (int)( tdivz * z ) + tadjust;\n\t\tif( r_turb_t > bbextentt )\n\t\t\tr_turb_t = bbextentt;\n\t\telse if( r_turb_t < 0 )\n\t\t\tr_turb_t = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 16 )\n\t\t\t\tr_turb_spancount = 16;\n\t\t\telse\n\t\t\t\tr_turb_spancount = count;\n\n\t\t\tcount -= r_turb_spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz16stepu;\n\t\t\t\ttdivz += tdivz16stepu;\n\t\t\t\tzi += zi16stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) >> 4;\n\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) >> 4;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( r_turb_spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tif( r_turb_spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 );\n\t\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tr_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 );\n\t\t\tr_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 );\n\n\t\t\tD_DrawTurbulent8Span();\n\n\t\t\tr_turb_s = snext;\n\t\t\tr_turb_t = tnext;\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n/*\n=============\nTurbulent8\n=============\n*/\nvoid TurbulentZ8( espan_t *pspan, int alpha1 )\n{\n\tint       count;\n\tfixed16_t snext, tnext;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz16stepu, tdivz16stepu, zi16stepu;\n\talpha = alpha1;\n\n\tif( alpha > 7 )\n\t\talpha = 7;\n\tif( alpha == 0 )\n\t\treturn;\n\n\tr_turb_turb = sintable + ((int)( gp_cl->time * SPEED ) & ( CYCLE - 1 ));\n\n\tr_turb_sstep = 0; // keep compiler happy\n\tr_turb_tstep = 0; // ditto\n\n\tr_turb_pbase = cacheblock;\n\n\tsdivz16stepu = d_sdivzstepu * 16;\n\ttdivz16stepu = d_tdivzstepu * 16;\n\tzi16stepu = d_zistepu * 16;\n\tr_turb_izistep = (int)( d_zistepu * 0x8000 * 0x10000 );\n\n\tdo\n\t{\n\t\tr_turb_pdest = ( d_viewbuffer\n\t\t\t\t + ( r_screenwidth * pspan->v ) + pspan->u );\n\t\tr_turb_pz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u;\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\tr_turb_izi = (int)( zi * 0x8000 * 0x10000 );\n\n\t\tr_turb_s = (int)( sdivz * z ) + sadjust;\n\t\tif( r_turb_s > bbextents )\n\t\t\tr_turb_s = bbextents;\n\t\telse if( r_turb_s < 0 )\n\t\t\tr_turb_s = 0;\n\n\t\tr_turb_t = (int)( tdivz * z ) + tadjust;\n\t\tif( r_turb_t > bbextentt )\n\t\t\tr_turb_t = bbextentt;\n\t\telse if( r_turb_t < 0 )\n\t\t\tr_turb_t = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 16 )\n\t\t\t\tr_turb_spancount = 16;\n\t\t\telse\n\t\t\t\tr_turb_spancount = count;\n\n\t\t\tcount -= r_turb_spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz16stepu;\n\t\t\t\ttdivz += tdivz16stepu;\n\t\t\t\tzi += zi16stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) >> 4;\n\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) >> 4;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( r_turb_spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tif( r_turb_spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 );\n\t\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tr_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 );\n\t\t\tr_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 );\n\n\t\t\tD_DrawTurbulent8ZSpan();\n\n\t\t\tr_turb_s = snext;\n\t\t\tr_turb_t = tnext;\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n\n\n// ====================\n// PGM\n/*\n=============\nNonTurbulent8 - this is for drawing scrolling textures. they're warping water textures\n\tbut the turbulence is automatically 0.\n=============\n*/\nvoid NonTurbulent8( espan_t *pspan )\n{\n\tint       count;\n\tfixed16_t snext, tnext;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz16stepu, tdivz16stepu, zi16stepu;\n\n//\tr_turb_turb = sintable + ((int)(r_newrefdef.time*SPEED)&(CYCLE-1));\n\tr_turb_turb = blanktable;\n\n\tr_turb_sstep = 0; // keep compiler happy\n\tr_turb_tstep = 0; // ditto\n\n\tr_turb_pbase = cacheblock;\n\n\tsdivz16stepu = d_sdivzstepu * 16;\n\ttdivz16stepu = d_tdivzstepu * 16;\n\tzi16stepu = d_zistepu * 16;\n\n\tdo\n\t{\n\t\tr_turb_pdest = ( d_viewbuffer\n\t\t\t\t + ( r_screenwidth * pspan->v ) + pspan->u );\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\tr_turb_s = (int)( sdivz * z ) + sadjust;\n\t\tif( r_turb_s > bbextents )\n\t\t\tr_turb_s = bbextents;\n\t\telse if( r_turb_s < 0 )\n\t\t\tr_turb_s = 0;\n\n\t\tr_turb_t = (int)( tdivz * z ) + tadjust;\n\t\tif( r_turb_t > bbextentt )\n\t\t\tr_turb_t = bbextentt;\n\t\telse if( r_turb_t < 0 )\n\t\t\tr_turb_t = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 16 )\n\t\t\t\tr_turb_spancount = 16;\n\t\t\telse\n\t\t\t\tr_turb_spancount = count;\n\n\t\t\tcount -= r_turb_spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz16stepu;\n\t\t\t\ttdivz += tdivz16stepu;\n\t\t\t\tzi += zi16stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) >> 4;\n\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) >> 4;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( r_turb_spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 16 )\n\t\t\t\t\tsnext = 16; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 16 )\n\t\t\t\t\ttnext = 16; // guard against round-off error on <0 steps\n\n\t\t\t\tif( r_turb_spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tr_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 );\n\t\t\t\t\tr_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tr_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 );\n\t\t\tr_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 );\n\n\t\t\tD_DrawTurbulent8Span();\n\n\t\t\tr_turb_s = snext;\n\t\t\tr_turb_t = tnext;\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n// PGM\n// ====================\n\nint kernel[2][2][2] =\n{\n\t{\n\t\t{16384, 0},\n\t\t{49152, 32768}\n\t}\n\t,\n\t{\n\t\t{32768, 49152},\n\t\t{0, 16384}\n\t}\n};\n#ifndef DISABLE_TEXFILTER\n#define SW_TEXFILT ( sw_texfilt.value == 1.0f )\n#else\n#define SW_TEXFILT 0\n#endif\n/*\n=============\nD_DrawSpans16\n\n  FIXME: actually make this subdivide by 16 instead of 8!!!\n=============\n*/\nvoid D_DrawSpans16( espan_t *pspan )\n{\n\tint       count, spancount;\n\tpixel_t   *pbase, *pdest;\n\tfixed16_t s, t, snext, tnext, sstep, tstep;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz8stepu, tdivz8stepu, zi8stepu;\n\n\tsstep = 0; // keep compiler happy\n\ttstep = 0; // ditto\n\n\tpbase = cacheblock;\n\n\tsdivz8stepu = d_sdivzstepu * 8;\n\ttdivz8stepu = d_tdivzstepu * 8;\n\tzi8stepu = d_zistepu * 8;\n\n\tdo\n\t{\n\t\tpdest = ( d_viewbuffer\n\t\t\t  + ( r_screenwidth * pspan->v ) + pspan->u );\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\ts = (int)( sdivz * z ) + sadjust;\n\t\tif( s > bbextents )\n\t\t\ts = bbextents;\n\t\telse if( s < 0 )\n\t\t\ts = 0;\n\n\t\tt = (int)( tdivz * z ) + tadjust;\n\t\tif( t > bbextentt )\n\t\t\tt = bbextentt;\n\t\telse if( t < 0 )\n\t\t\tt = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 8 )\n\t\t\t\tspancount = 8;\n\t\t\telse\n\t\t\t\tspancount = count;\n\n\t\t\tcount -= spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz8stepu;\n\t\t\t\ttdivz += tdivz8stepu;\n\t\t\t\tzi += zi8stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tsstep = ( snext - s ) >> 3;\n\t\t\t\ttstep = ( tnext - t ) >> 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tif( spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tsstep = ( snext - s ) / ( spancount - 1 );\n\t\t\t\t\ttstep = ( tnext - t ) / ( spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t// Drawing phrase\n\t\t\tif( !SW_TEXFILT )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\t*pdest++ = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth );\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tint idiths = s;\n\t\t\t\t\tint iditht = t;\n\n\t\t\t\t\tint X = ( pspan->u + spancount ) & 1;\n\t\t\t\t\tint Y = ( pspan->v ) & 1;\n\n\t\t\t\t\t// Using the kernel\n\t\t\t\t\tidiths += kernel[X][Y][0];\n\t\t\t\t\tiditht += kernel[X][Y][1];\n\n\t\t\t\t\tidiths = idiths >> 16;\n\t\t\t\t\tidiths = idiths ? idiths - 1 : idiths;\n\n\n\t\t\t\t\tiditht = iditht >> 16;\n\t\t\t\t\tiditht = iditht ? iditht - 1 : iditht;\n\n\n\t\t\t\t\t*pdest++ = *( pbase + idiths + iditht * cachewidth );\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n\n/*\n=============\nD_DrawSpans16\n\n  FIXME: actually make this subdivide by 16 instead of 8!!!\n=============\n*/\nvoid D_AlphaSpans16( espan_t *pspan )\n{\n\tint       count, spancount;\n\tpixel_t   *pbase, *pdest;\n\tfixed16_t s, t, snext, tnext, sstep, tstep;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz8stepu, tdivz8stepu, zi8stepu;\n\tint       izi, izistep;\n\tshort     *pz;\n\n\tsstep = 0; // keep compiler happy\n\ttstep = 0; // ditto\n\n\tpbase = cacheblock;\n\n\tsdivz8stepu = d_sdivzstepu * 8;\n\ttdivz8stepu = d_tdivzstepu * 8;\n\tzi8stepu = d_zistepu * 8;\n\tizistep = (int)( d_zistepu * 0x8000 * 0x10000 );\n\n\tdo\n\t{\n\t\tpdest = ( d_viewbuffer\n\t\t\t  + ( r_screenwidth * pspan->v ) + pspan->u );\n\t\tpz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u;\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tizi = (int)( zi * 0x8000 * 0x10000 );\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\ts = (int)( sdivz * z ) + sadjust;\n\t\tif( s > bbextents )\n\t\t\ts = bbextents;\n\t\telse if( s < 0 )\n\t\t\ts = 0;\n\n\t\tt = (int)( tdivz * z ) + tadjust;\n\t\tif( t > bbextentt )\n\t\t\tt = bbextentt;\n\t\telse if( t < 0 )\n\t\t\tt = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 8 )\n\t\t\t\tspancount = 8;\n\t\t\telse\n\t\t\t\tspancount = count;\n\n\t\t\tcount -= spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz8stepu;\n\t\t\t\ttdivz += tdivz8stepu;\n\t\t\t\tzi += zi8stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tsstep = ( snext - s ) >> 3;\n\t\t\t\ttstep = ( tnext - t ) >> 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tif( spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tsstep = ( snext - s ) / ( spancount - 1 );\n\t\t\t\t\ttstep = ( tnext - t ) / ( spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t// Drawing phrase\n\t\t\tif( !SW_TEXFILT )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\tbtemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth );\n\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t{\n\t\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t\t*pz = izi >> 16;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\tizi += izistep;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tint     idiths = s;\n\t\t\t\t\t\tint     iditht = t;\n\n\t\t\t\t\t\tint     X = ( pspan->u + spancount ) & 1;\n\t\t\t\t\t\tint     Y = ( pspan->v ) & 1;\n\t\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\t\t// Using the kernel\n\t\t\t\t\t\tidiths += kernel[X][Y][0];\n\t\t\t\t\t\tiditht += kernel[X][Y][1];\n\n\t\t\t\t\t\tidiths = idiths >> 16;\n\t\t\t\t\t\tidiths = idiths ? idiths - 1 : idiths;\n\n\n\t\t\t\t\t\tiditht = iditht >> 16;\n\t\t\t\t\t\tiditht = iditht ? iditht - 1 : iditht;\n\n\n\t\t\t\t\t\tbtemp = *( pbase + idiths + iditht * cachewidth );\n\t\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t\t*pz = izi >> 16;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n\n\n/*\n=============\nD_DrawSpans16\n\n  FIXME: actually make this subdivide by 16 instead of 8!!!\n=============\n*/\nvoid D_BlendSpans16( espan_t *pspan, int alpha )\n{\n\tint       count, spancount;\n\tpixel_t   *pbase, *pdest;\n\tfixed16_t s, t, snext, tnext, sstep, tstep;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz8stepu, tdivz8stepu, zi8stepu;\n\tint       izi, izistep;\n\tshort     *pz;\n\n\tif( alpha > 7 )\n\t\talpha = 7;\n\tif( alpha == 0 )\n\t\treturn;\n\n\tsstep = 0; // keep compiler happy\n\ttstep = 0; // ditto\n\n\tpbase = cacheblock;\n\n\tsdivz8stepu = d_sdivzstepu * 8;\n\ttdivz8stepu = d_tdivzstepu * 8;\n\tzi8stepu = d_zistepu * 8;\n\tizistep = (int)( d_zistepu * 0x8000 * 0x10000 );\n\n\tdo\n\t{\n\t\tpdest = ( d_viewbuffer\n\t\t\t  + ( r_screenwidth * pspan->v ) + pspan->u );\n\t\tpz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u;\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tizi = (int)( zi * 0x8000 * 0x10000 );\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\ts = (int)( sdivz * z ) + sadjust;\n\t\tif( s > bbextents )\n\t\t\ts = bbextents;\n\t\telse if( s < 0 )\n\t\t\ts = 0;\n\n\t\tt = (int)( tdivz * z ) + tadjust;\n\t\tif( t > bbextentt )\n\t\t\tt = bbextentt;\n\t\telse if( t < 0 )\n\t\t\tt = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 8 )\n\t\t\t\tspancount = 8;\n\t\t\telse\n\t\t\t\tspancount = count;\n\n\t\t\tcount -= spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz8stepu;\n\t\t\t\ttdivz += tdivz8stepu;\n\t\t\t\tzi += zi8stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tsstep = ( snext - s ) >> 3;\n\t\t\t\ttstep = ( tnext - t ) >> 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tif( spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tsstep = ( snext - s ) / ( spancount - 1 );\n\t\t\t\t\ttstep = ( tnext - t ) / ( spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t// Drawing phrase\n\t\t\tif( !SW_TEXFILT )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\t\tbtemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth );\n\n\t\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif( alpha != 7 )\n\t\t\t\t\t\t\t\tbtemp = BLEND_ALPHA( alpha, btemp, *pdest );\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// *pz    = izi >> 16;\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\tizi += izistep;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tint idiths = s;\n\t\t\t\t\tint iditht = t;\n\n\t\t\t\t\tint X = ( pspan->u + spancount ) & 1;\n\t\t\t\t\tint Y = ( pspan->v ) & 1;\n\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\t\t// Using the kernel\n\t\t\t\t\t\tidiths += kernel[X][Y][0];\n\t\t\t\t\t\tiditht += kernel[X][Y][1];\n\n\t\t\t\t\t\tidiths = idiths >> 16;\n\t\t\t\t\t\tidiths = idiths ? idiths - 1 : idiths;\n\n\n\t\t\t\t\t\tiditht = iditht >> 16;\n\t\t\t\t\t\tiditht = iditht ? iditht - 1 : iditht;\n\n\t\t\t\t\t\tbtemp = *( pbase + idiths + iditht * cachewidth );\n\n\t\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif( alpha != 7 )\n\t\t\t\t\t\t\t\tbtemp = BLEND_ALPHA( alpha, btemp, *pdest );\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// *pz    = izi >> 16;\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\tizi += izistep;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n\n\n/*\n=============\nD_DrawSpans16\n\n  FIXME: actually make this subdivide by 16 instead of 8!!!\n=============\n*/\nvoid D_AddSpans16( espan_t *pspan )\n{\n\tint       count, spancount;\n\tpixel_t   *pbase, *pdest;\n\tfixed16_t s, t, snext, tnext, sstep, tstep;\n\tfloat     sdivz, tdivz, zi, z, du, dv, spancountminus1;\n\tfloat     sdivz8stepu, tdivz8stepu, zi8stepu;\n\tint       izi, izistep;\n\tshort     *pz;\n\n\tsstep = 0; // keep compiler happy\n\ttstep = 0; // ditto\n\n\tpbase = cacheblock;\n\n\tsdivz8stepu = d_sdivzstepu * 8;\n\ttdivz8stepu = d_tdivzstepu * 8;\n\tzi8stepu = d_zistepu * 8;\n\tizistep = (int)( d_zistepu * 0x8000 * 0x10000 );\n\n\tdo\n\t{\n\t\tpdest = ( d_viewbuffer\n\t\t\t  + ( r_screenwidth * pspan->v ) + pspan->u );\n\t\tpz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u;\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial s/z, t/z, 1/z, s, and t and clamp\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tsdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu;\n\t\ttdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu;\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\tizi = (int)( zi * 0x8000 * 0x10000 );\n\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\ts = (int)( sdivz * z ) + sadjust;\n\t\tif( s > bbextents )\n\t\t\ts = bbextents;\n\t\telse if( s < 0 )\n\t\t\ts = 0;\n\n\t\tt = (int)( tdivz * z ) + tadjust;\n\t\tif( t > bbextentt )\n\t\t\tt = bbextentt;\n\t\telse if( t < 0 )\n\t\t\tt = 0;\n\n\t\tdo\n\t\t{\n\t\t\t// calculate s and t at the far end of the span\n\t\t\tif( count >= 8 )\n\t\t\t\tspancount = 8;\n\t\t\telse\n\t\t\t\tspancount = count;\n\n\t\t\tcount -= spancount;\n\n\t\t\tif( count )\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at far end of span,\n\t\t\t\t// calculate s and t steps across span by shifting\n\t\t\t\tsdivz += sdivz8stepu;\n\t\t\t\ttdivz += tdivz8stepu;\n\t\t\t\tzi += zi8stepu;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tsstep = ( snext - s ) >> 3;\n\t\t\t\ttstep = ( tnext - t ) >> 3;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// calculate s/z, t/z, zi->fixed s and t at last pixel in span (so\n\t\t\t\t// can't step off polygon), clamp, calculate s and t steps across\n\t\t\t\t// span by division, biasing steps low so we don't run off the\n\t\t\t\t// texture\n\t\t\t\tspancountminus1 = (float)( spancount - 1 );\n\t\t\t\tsdivz += d_sdivzstepu * spancountminus1;\n\t\t\t\ttdivz += d_tdivzstepu * spancountminus1;\n\t\t\t\tzi += d_zistepu * spancountminus1;\n\t\t\t\tz = (float)0x10000 / zi; // prescale to 16.16 fixed-point\n\t\t\t\tsnext = (int)( sdivz * z ) + sadjust;\n\t\t\t\tif( snext > bbextents )\n\t\t\t\t\tsnext = bbextents;\n\t\t\t\telse if( snext < 8 )\n\t\t\t\t\tsnext = 8; // prevent round-off error on <0 steps from\n\t\t\t\t//  from causing overstepping & running off the\n\t\t\t\t//  edge of the texture\n\n\t\t\t\ttnext = (int)( tdivz * z ) + tadjust;\n\t\t\t\tif( tnext > bbextentt )\n\t\t\t\t\ttnext = bbextentt;\n\t\t\t\telse if( tnext < 8 )\n\t\t\t\t\ttnext = 8; // guard against round-off error on <0 steps\n\n\t\t\t\tif( spancount > 1 )\n\t\t\t\t{\n\t\t\t\t\tsstep = ( snext - s ) / ( spancount - 1 );\n\t\t\t\t\ttstep = ( tnext - t ) / ( spancount - 1 );\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t// Drawing phrase\n\t\t\tif( !SW_TEXFILT )\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\t\tbtemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth );\n\n\t\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbtemp = BLEND_ADD( btemp, *pdest );\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// *pz    = izi >> 16;\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\tizi += izistep;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tint idiths = s;\n\t\t\t\t\tint iditht = t;\n\n\t\t\t\t\tint X = ( pspan->u + spancount ) & 1;\n\t\t\t\t\tint Y = ( pspan->v ) & 1;\n\t\t\t\t\tif( *pz <= ( izi >> 16 ))\n\t\t\t\t\t{\n\t\t\t\t\t\tpixel_t btemp;\n\n\t\t\t\t\t\t// Using the kernel\n\t\t\t\t\t\tidiths += kernel[X][Y][0];\n\t\t\t\t\t\tiditht += kernel[X][Y][1];\n\n\t\t\t\t\t\tidiths = idiths >> 16;\n\t\t\t\t\t\tidiths = idiths ? idiths - 1 : idiths;\n\n\n\t\t\t\t\t\tiditht = iditht >> 16;\n\t\t\t\t\t\tiditht = iditht ? iditht - 1 : iditht;\n\n\t\t\t\t\t\tbtemp = *( pbase + idiths + iditht * cachewidth );\n\n\t\t\t\t\t\tif( btemp != TRANSPARENT_COLOR )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbtemp = BLEND_ADD( btemp, *pdest );\n\t\t\t\t\t\t\t*pdest = btemp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// *pz    = izi >> 16;\n\t\t\t\t\t}\n\t\t\t\t\tpdest++;\n\t\t\t\t\tpz++;\n\t\t\t\t\tizi += izistep;\n\t\t\t\t\ts += sstep;\n\t\t\t\t\tt += tstep;\n\t\t\t\t}\n\t\t\t\twhile( --spancount > 0 );\n\t\t\t}\n\n\n\t\t}\n\t\twhile( count > 0 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n/*\n=============\nD_DrawZSpans\n=============\n*/\nvoid D_DrawZSpans( espan_t *pspan )\n{\n\tint      count, doublecount, izistep;\n\tint      izi;\n\tshort    *pdest;\n\tunsigned ltemp;\n\tfloat    zi;\n\tfloat    du, dv;\n\n// FIXME: check for clamping/range problems\n// we count on FP exceptions being turned off to avoid range problems\n\tizistep = (int)( d_zistepu * 0x8000 * 0x10000 );\n\n\tdo\n\t{\n\t\tpdest = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u;\n\n\t\tcount = pspan->count;\n\n\t\t// calculate the initial 1/z\n\t\tdu = (float)pspan->u;\n\t\tdv = (float)pspan->v;\n\n\t\tzi = d_ziorigin + dv * d_zistepv + du * d_zistepu;\n\t\t// we count on FP exceptions being turned off to avoid range problems\n\t\tizi = (int)( zi * 0x8000 * 0x10000 );\n\n\t\tif((uintptr_t)pdest & 0x02 )\n\t\t{\n\t\t\t*pdest++ = (short)( izi >> 16 );\n\t\t\tizi += izistep;\n\t\t\tcount--;\n\t\t}\n\n\t\tif(( doublecount = count >> 1 ) > 0 )\n\t\t{\n\t\t\tdo\n\t\t\t{\n\t\t\t\tltemp = izi >> 16;\n\t\t\t\tizi += izistep;\n\t\t\t\tltemp |= izi & 0xFFFF0000;\n\t\t\t\tizi += izistep;\n\t\t\t\t*(int *)pdest = ltemp;\n\t\t\t\tpdest += 2;\n\t\t\t}\n\t\t\twhile( --doublecount > 0 );\n\t\t}\n\n\t\tif( count & 1 )\n\t\t\t*pdest = (short)( izi >> 16 );\n\n\t}\n\twhile(( pspan = pspan->pnext ) != NULL );\n}\n\n"
  },
  {
    "path": "ref/soft/r_sprite.c",
    "content": "/*\ngl_sprite.c - sprite rendering\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"pm_local.h\"\n#include \"sprite.h\"\n#include \"studio.h\"\n#include \"entity_types.h\"\n\n#define GLARE_FALLOFF 19000.0f\n\nchar        sprite_name[MAX_QPATH];\nchar        group_suffix[8];\nstatic uint r_texFlags = 0;\nstatic int  sprite_version;\nfloat       sprite_radius;\n\n/*\n====================\nR_SpriteInit\n\n====================\n*/\nvoid R_SpriteInit( void )\n{\n}\n\n/*\n====================\nR_SpriteLoadFrame\n\nupload a single frame\n====================\n*/\nstatic const byte *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num )\n{\n\tdspriteframe_t pinframe;\n\tmspriteframe_t *pspriteframe;\n\tint gl_texturenum = 0;\n\tchar           texname[128];\n\tint bytes = 1;\n\n\tmemcpy( &pinframe, pin, sizeof( dspriteframe_t ));\n\n\tif( sprite_version == SPRITE_VERSION_32 )\n\t\tbytes = 4;\n\n\t// build uinque frame name\n\tif( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"#HUD/%s(%s:%i%i).spr\", sprite_name, group_suffix, num / 10, num % 10 );\n\t\tgl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );\n\t}\n\telse\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"#%s(%s:%i%i).spr\", sprite_name, group_suffix, num / 10, num % 10 );\n\t\tgl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );\n\t}\n\n\t// setup frame description\n\tpspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));\n\tpspriteframe->width = pinframe.width;\n\tpspriteframe->height = pinframe.height;\n\tpspriteframe->up = pinframe.origin[1];\n\tpspriteframe->left = pinframe.origin[0];\n\tpspriteframe->down = pinframe.origin[1] - pinframe.height;\n\tpspriteframe->right = pinframe.width + pinframe.origin[0];\n\tpspriteframe->gl_texturenum = gl_texturenum;\n\t*ppframe = pspriteframe;\n\n\treturn (const byte *)((const byte *)pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes );\n}\n\n/*\n====================\nR_SpriteLoadGroup\n\nupload a group frames\n====================\n*/\nstatic const void *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )\n{\n\tconst dspritegroup_t    *pingroup;\n\tmspritegroup_t          *pspritegroup;\n\tconst dspriteinterval_t *pin_intervals;\n\tfloat      *poutintervals;\n\tint        i, groupsize, numframes;\n\tconst void *ptemp;\n\n\tpingroup = (const dspritegroup_t *)pin;\n\tnumframes = pingroup->numframes;\n\n\tgroupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );\n\tpspritegroup = Mem_Calloc( mod->mempool, groupsize );\n\tpspritegroup->numframes = numframes;\n\n\t*ppframe = (mspriteframe_t *)pspritegroup;\n\tpin_intervals = (const dspriteinterval_t *)( pingroup + 1 );\n\tpoutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));\n\tpspritegroup->intervals = poutintervals;\n\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\t*poutintervals = pin_intervals->interval;\n\t\tif( *poutintervals <= 0.0f )\n\t\t\t*poutintervals = 1.0f; // set error value\n\t\tpoutintervals++;\n\t\tpin_intervals++;\n\t}\n\n\tptemp = (const void *)pin_intervals;\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\tptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );\n\t}\n\n\treturn ptemp;\n}\n\n/*\n====================\nMod_LoadSpriteModel\n\nload sprite model\n====================\n*/\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags )\n{\n\tconst dsprite_t *pin;\n\tconst short     *numi = NULL;\n\tconst byte      *pframetype;\n\tmsprite_t       *psprite;\n\tint i;\n\n\tpin = buffer;\n\tpsprite = mod->cache.data;\n\n\tif( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )\n\t\tnumi = NULL;\n\telse if( pin->version == SPRITE_VERSION_HL )\n\t\tnumi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t ));\n\n\tr_texFlags = texFlags;\n\tsprite_version = pin->version;\n\tQ_strncpy( sprite_name, mod->name, sizeof( sprite_name ));\n\tCOM_StripExtension( sprite_name );\n\n\tif( numi == NULL )\n\t{\n\t\trgbdata_t *pal;\n\n\t\tpal = gEngfuncs.FS_LoadImage( \"#id.pal\", (byte *)&i, 768 );\n\t\tpframetype = ((const byte *)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1\n\t\tgEngfuncs.FS_FreeImage( pal );                                // palette installed, no reason to keep this data\n\t}\n\telse if( *numi <= 256 )\n\t{\n\t\tconst byte\t*src = (const byte *)(numi+1);\n\t\trgbdata_t\t*pal;\n\t\tsize_t pal_bytes = *numi * 3;\n\n\t\t// install palette\n\t\tswitch( psprite->texFormat )\n\t\t{\n\t\tcase SPR_INDEXALPHA:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#gradient.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\tcase SPR_ALPHTEST:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#masked.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpal = gEngfuncs.FS_LoadImage( \"#texgamma.pal\", src, pal_bytes );\n\t\t\tbreak;\n\t\t}\n\n\t\tpframetype = (const byte *)(src + pal_bytes);\n\t\tgEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data\n\t}\n\telse\n\t{\n\t\tgEngfuncs.Con_DPrintf( S_ERROR \"%s has wrong number of palette colors %i (should be less or equal than 256)\\n\", mod->name, *numi );\n\t\treturn;\n\t}\n\n\tif( mod->numframes < 1 )\n\t\treturn;\n\n\tfor( i = 0; i < mod->numframes; i++ )\n\t{\n\t\tframetype_t  frametype;\n\t\tdframetype_t dframetype;\n\n\t\tmemcpy( &dframetype, pframetype, sizeof( dframetype ));\n\t\tframetype = dframetype.type;\n\t\tpsprite->frames[i].type = (spriteframetype_t)frametype;\n\n\t\tswitch( frametype )\n\t\t{\n\t\tcase FRAME_SINGLE:\n\t\t\tQ_strncpy( group_suffix, \"frame\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\tcase FRAME_GROUP:\n\t\t\tQ_strncpy( group_suffix, \"group\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\tcase FRAME_ANGLED:\n\t\t\tQ_strncpy( group_suffix, \"angle\", sizeof( group_suffix ));\n\t\t\tpframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );\n\t\t\tbreak;\n\t\t}\n\t\tif( pframetype == NULL )\n\t\t\tbreak;                  // technically an error\n\t}\n\n\tif( loaded )\n\t\t*loaded = true;         // done\n}\n\n/*\n====================\nMod_UnloadSpriteModel\n\nrelease sprite model and frames\n====================\n*/\nvoid Mod_SpriteUnloadTextures( void *data )\n{\n\tmsprite_t *psprite = data;\n\tint i;\n\n\tif( !data )\n\t\treturn;\n\n\t// release all textures\n\tfor( i = 0; i < psprite->numframes; i++ )\n\t{\n\t\tif( !psprite->frames[i].frameptr )\n\t\t\tcontinue;\n\n\t\tif( psprite->frames[i].type == SPR_SINGLE )\n\t\t{\n\t\t\tGL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;\n\t\t\tint j;\n\n\t\t\tfor( j = 0; j < pspritegroup->numframes; j++ )\n\t\t\t{\n\t\t\t\tif( pspritegroup->frames[j] )\n\t\t\t\t\tGL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n================\nR_GetSpriteFrame\n\nassume pModel is valid\n================\n*/\nmspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )\n{\n\tmsprite_t      *psprite;\n\tmspritegroup_t *pspritegroup;\n\tmspriteframe_t *pspriteframe = NULL;\n\tfloat          *pintervals, fullinterval;\n\tint i, numframes;\n\tfloat          targettime;\n\n\tAssert( pModel != NULL );\n\tpsprite = pModel->cache.data;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tif( frame > psprite->numframes )\n\t\t\tgEngfuncs.Con_Printf( S_WARN \"%s: no such frame %d (%s)\\n\", __func__, frame, pModel->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == SPR_SINGLE )\n\t{\n\t\tpspriteframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == SPR_GROUP )\n\t{\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes - 1];\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = gp_cl->time - ((int)( gp_cl->time / fullinterval )) * fullinterval;\n\n\t\tfor( i = 0; i < ( numframes - 1 ); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t}\n\t\tpspriteframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == FRAME_ANGLED )\n\t{\n\t\tint angleframe = (int)( Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;\n\n\t\t// e.g. doom-style sprite monsters\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpspriteframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn pspriteframe;\n}\n\n/*\n================\nR_GetSpriteFrameInterpolant\n\nNOTE: we using prevblending[0] and [1] for holds interval\nbetween frames where are we lerping\n================\n*/\nstatic float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe )\n{\n\tmsprite_t      *psprite;\n\tmspritegroup_t *pspritegroup;\n\tint i, j, numframes, frame;\n\tfloat          lerpFrac, time, jtime, jinterval;\n\tfloat          *pintervals, fullinterval, targettime;\n\tint m_fDoInterp;\n\n\tpsprite = ent->model->cache.data;\n\tframe = (int)ent->curstate.frame;\n\tlerpFrac = 1.0f;\n\n\t// misc info\n\tm_fDoInterp = ( ent->curstate.effects & EF_NOINTERP ) ? false : true;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tgEngfuncs.Con_Reportf( S_WARN \"%s: no such frame %d (%s)\\n\", __func__, frame, ent->model->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == FRAME_SINGLE )\n\t{\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_SINGLE )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tlerpFrac = ( gp_cl->time - ent->latched.sequencetime ) * 11.0f;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tif( ent->latched.prevblending[0] >= psprite->numframes )\n\t\t{\n\t\t\t// reset interpolation on change model\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\tlerpFrac = 0.0f;\n\t\t}\n\n\t\t// get the interpolated frames\n\t\tif( oldframe )\n\t\t\t*oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr;\n\t\tif( curframe )\n\t\t\t*curframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == FRAME_GROUP )\n\t{\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes - 1];\n\t\tjinterval = pintervals[1] - pintervals[0];\n\t\ttime = gp_cl->time;\n\t\tjtime = 0.0f;\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = time - ((int)( time / fullinterval )) * fullinterval;\n\n\t\t// LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0,\n\t\t// i instead measure the time of the first frame, hoping it is consistent\n\t\tfor( i = 0, j = numframes - 1; i < ( numframes - 1 ); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t\tj = i;\n\t\t\tjinterval = pintervals[i] - jtime;\n\t\t\tjtime = pintervals[i];\n\t\t}\n\n\t\tif( m_fDoInterp )\n\t\t\tlerpFrac = ( targettime - jtime ) / jinterval;\n\t\telse\n\t\t\tj = i; // no lerping\n\n\t\t// get the interpolated frames\n\t\tif( oldframe )\n\t\t\t*oldframe = pspritegroup->frames[j];\n\t\tif( curframe )\n\t\t\t*curframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == FRAME_ANGLED )\n\t{\n\t\t// e.g. doom-style sprite monsters\n\t\tfloat yaw = ent->angles[YAW];\n\t\tint   angleframe = (int)( Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;\n\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_ANGLED )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tlerpFrac = ( gp_cl->time - ent->latched.sequencetime ) * ent->curstate.framerate;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[ent->latched.prevblending[0]].frameptr;\n\t\tif( oldframe )\n\t\t\t*oldframe = pspritegroup->frames[angleframe];\n\n\t\tpspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;\n\t\tif( curframe )\n\t\t\t*curframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn lerpFrac;\n}\n\n/*\n================\nR_CullSpriteModel\n\nCull sprite model by bbox\n================\n*/\nstatic qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin )\n{\n\tvec3_t sprite_mins, sprite_maxs;\n\tfloat  scale = 1.0f;\n\n\tif( !e->model->cache.data )\n\t\treturn true;\n\n\tif( e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\t// scale original bbox (no rotation for sprites)\n\tVectorScale( e->model->mins, scale, sprite_mins );\n\tVectorScale( e->model->maxs, scale, sprite_maxs );\n\n\tsprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs );\n\n\tVectorAdd( sprite_mins, origin, sprite_mins );\n\tVectorAdd( sprite_maxs, origin, sprite_maxs );\n\n\treturn R_CullModel( e, sprite_mins, sprite_maxs );\n}\n\n/*\n================\nR_GlowSightDistance\n\nSet sprite brightness factor\n================\n*/\nstatic float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale )\n{\n\tfloat     dist, brightness;\n\tvec3_t    glowDist;\n\tpmtrace_t *tr;\n\n\tVectorSubtract( origin, RI.vieworg, glowDist );\n\tdist = VectorLength( glowDist );\n\n\tif( RP_NORMALPASS( ))\n\t{\n\t\ttr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow.value ? PM_GLASS_IGNORE : ( PM_GLASS_IGNORE | PM_STUDIO_IGNORE ));\n\n\t\tif(( 1.0f - tr->fraction ) * dist > 8.0f )\n\t\t\treturn 0.0f;\n\t}\n\n\tif( renderfx == kRenderFxNoDissipation )\n\t\treturn 1.0f;\n\n\tbrightness = GLARE_FALLOFF / ( dist * dist );\n\tbrightness = bound( 0.05f, brightness, 1.0f );\n\t*pscale *= dist * ( 1.0f / 200.0f );\n\n\treturn brightness;\n}\n\n/*\n================\nR_SpriteOccluded\n\nDo occlusion test for glow-sprites\n================\n*/\nstatic qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale )\n{\n\tif( e->curstate.rendermode == kRenderGlow )\n\t{\n\t\tfloat  blend;\n\t\tvec3_t v;\n\n\t\tTriWorldToScreen( origin, v );\n\n\t\tif( v[0] < RI.viewport[0] || v[0] > RI.viewport[0] + RI.viewport[2] )\n\t\t\treturn true; // do scissor\n\t\tif( v[1] < RI.viewport[1] || v[1] > RI.viewport[1] + RI.viewport[3] )\n\t\t\treturn true; // do scissor\n\n\t\tblend = R_SpriteGlowBlend( origin, e->curstate.rendermode, e->curstate.renderfx, pscale );\n\t\ttr.blend *= blend;\n\n\t\tif( blend <= 0.01f )\n\t\t\treturn true; // faded\n\t}\n\telse\n\t{\n\t\tif( R_CullSpriteModel( e, origin ))\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n=================\nR_DrawSpriteQuad\n=================\n*/\nstatic void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale )\n{\n\tvec3_t  point;\n\timage_t *image;\n\n\tr_stats.c_sprite_polys++;\n\t/*image = R_GetTexture(frame->gl_texturenum);\n\tr_affinetridesc.pskin = image->pixels[0];\n\tr_affinetridesc.skinwidth = image->width;\n\tr_affinetridesc.skinheight = image->height;*/\n\n\tTriBegin( TRI_QUADS );\n\tTriTexCoord2f( 0.0f, 1.0f );\n\tVectorMA( org, frame->down * scale, v_up, point );\n\tVectorMA( point, frame->left * scale, v_right, point );\n\tTriVertex3fv( point );\n\tTriTexCoord2f( 0.0f, 0.0f );\n\tVectorMA( org, frame->up * scale, v_up, point );\n\tVectorMA( point, frame->left * scale, v_right, point );\n\tTriVertex3fv( point );\n\tTriTexCoord2f( 1.0f, 0.0f );\n\tVectorMA( org, frame->up * scale, v_up, point );\n\tVectorMA( point, frame->right * scale, v_right, point );\n\tTriVertex3fv( point );\n\tTriTexCoord2f( 1.0f, 1.0f );\n\tVectorMA( org, frame->down * scale, v_up, point );\n\tVectorMA( point, frame->right * scale, v_right, point );\n\tTriVertex3fv( point );\n\tTriEnd();\n}\n\nstatic qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat )\n{\n\tif( !r_sprite_lighting->value )\n\t\treturn false;\n\n\tif( texFormat != SPR_ALPHTEST )\n\t\treturn false;\n\n\tif( e->curstate.effects & EF_FULLBRIGHT )\n\t\treturn false;\n\n\tif( e->curstate.renderamt <= 127 )\n\t\treturn false;\n\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderNormal:\n\tcase kRenderTransAlpha:\n\tcase kRenderTransTexture:\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n=================\nR_SpriteAllowLerping\n=================\n*/\nstatic qboolean R_SpriteAllowLerping( cl_entity_t *e, msprite_t *psprite )\n{\n\tif( !r_sprite_lerping->value )\n\t\treturn false;\n\n\tif( psprite->numframes <= 1 )\n\t\treturn false;\n\n\tif( psprite->texFormat != SPR_ADDITIVE )\n\t\treturn false;\n\n\tif( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )\n\t\treturn false;\n\n\treturn true;\n}\n\n/*\n=================\nR_DrawSpriteModel\n=================\n*/\nvoid R_DrawSpriteModel( cl_entity_t *e )\n{\n\tmspriteframe_t *frame = NULL, *oldframe = NULL;\n\tmsprite_t      *psprite;\n\tmodel_t        *model;\n\tint i, type;\n\tfloat          angle, dot, sr, cr;\n\tfloat          lerp = 1.0f, ilerp, scale;\n\tvec3_t         v_forward, v_right, v_up;\n\tvec3_t         origin, color, color2;\n\n\tif( RI.params & RP_ENVVIEW )\n\t\treturn;\n\n\tmodel = e->model;\n\tpsprite = (msprite_t *)model->cache.data;\n\tVectorCopy( e->origin, origin ); // set render origin\n\n\t// do movewith\n\tif( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW )\n\t{\n\t\tcl_entity_t *parent;\n\n\t\tparent = CL_GetEntityByIndex( e->curstate.aiment );\n\n\t\tif( parent && parent->model )\n\t\t{\n\t\t\tif( parent->model->type == mod_studio && e->curstate.body > 0 )\n\t\t\t{\n\t\t\t\tint num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS );\n\t\t\t\tVectorCopy( parent->attachment[num - 1], origin );\n\t\t\t}\n\t\t\telse\n\t\t\t\tVectorCopy( parent->origin, origin );\n\t\t}\n\t}\n\n\tscale = e->curstate.scale;\n\tif( !scale )\n\t\tscale = 1.0f;\n\n\tif( R_SpriteOccluded( e, origin, &scale ))\n\t\treturn; // sprite culled\n\n\tr_stats.c_sprite_models_drawn++;\n\n\tif( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd )\n\t\tR_AllowFog( false );\n\n\tGL_SetRenderMode( e->curstate.rendermode );\n\n\t// NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t{\n\t\tcolor[0] = (float)e->curstate.rendercolor.r * ( 1.0f / 255.0f );\n\t\tcolor[1] = (float)e->curstate.rendercolor.g * ( 1.0f / 255.0f );\n\t\tcolor[2] = (float)e->curstate.rendercolor.b * ( 1.0f / 255.0f );\n\t}\n\telse\n\t{\n\t\tcolor[0] = 1.0f;\n\t\tcolor[1] = 1.0f;\n\t\tcolor[2] = 1.0f;\n\t}\n\n\tif( R_SpriteHasLightmap( e, psprite->texFormat ))\n\t{\n\t\tcolorVec lightColor = R_LightPoint( origin );\n\t\t// FIXME: collect light from dlights?\n\t\tcolor2[0] = (float)lightColor.r * ( 1.0f / 255.0f );\n\t\tcolor2[1] = (float)lightColor.g * ( 1.0f / 255.0f );\n\t\tcolor2[2] = (float)lightColor.b * ( 1.0f / 255.0f );\n\t\t// NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0\n\t\t//\tpglAlphaFunc( GL_GREATER, 0.5f );\n\t}\n\n\tif( R_SpriteAllowLerping( e, psprite ))\n\t\tlerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame );\n\telse\n\t\tframe = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] );\n\n\ttype = psprite->type;\n\n\t// automatically roll parallel sprites if requested\n\tif( e->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL )\n\t\ttype = SPR_FWD_PARALLEL_ORIENTED;\n\n\tswitch( type )\n\t{\n\tcase SPR_ORIENTED:\n\t\tAngleVectors( e->angles, v_forward, v_right, v_up );\n\t\tVectorScale( v_forward, 0.01f, v_forward ); // to avoid z-fighting\n\t\tVectorSubtract( origin, v_forward, origin );\n\t\tbreak;\n\tcase SPR_FACING_UPRIGHT:\n\t\tVectorSet( v_right, origin[1] - RI.vieworg[1], -( origin[0] - RI.vieworg[0] ), 0.0f );\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_UPRIGHT:\n\t\tdot = RI.vforward[2];\n\t\tif(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848\n\t\t\treturn; // invisible\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorSet( v_right, RI.vforward[1], -RI.vforward[0], 0.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_ORIENTED:\n\t\tangle = e->angles[ROLL] * ( M_PI2 / 360.0f );\n\t\tSinCos( angle, &sr, &cr );\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tv_right[i] = ( RI.vright[i] * cr + RI.vup[i] * sr );\n\t\t\tv_up[i] = RI.vright[i] * -sr + RI.vup[i] * cr;\n\t\t}\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL: // normal sprite\n\tdefault:\n\t\tVectorCopy( RI.vright, v_right );\n\t\tVectorCopy( RI.vup, v_up );\n\t\tbreak;\n\t}\n\n\t// if( psprite->facecull == SPR_CULL_NONE )\n\t// GL_Cull( GL_NONE );\n\n\tif( oldframe == frame )\n\t{\n\t\t// draw the single non-lerped frame\n\t\t_TriColor4f( color[0], color[1], color[2], tr.blend );\n\t\tGL_Bind( XASH_TEXTURE0, frame->gl_texturenum );\n\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale );\n\t}\n\telse\n\t{\n\t\t// draw two combined lerped frames\n\t\tlerp = bound( 0.0f, lerp, 1.0f );\n\t\tilerp = 1.0f - lerp;\n\n\t\tif( ilerp != 0.0f )\n\t\t{\n\t\t\t_TriColor4f( color[0], color[1], color[2], tr.blend * ilerp );\n\t\t\tGL_Bind( XASH_TEXTURE0, oldframe->gl_texturenum );\n\t\t\tR_DrawSpriteQuad( oldframe, origin, v_right, v_up, scale );\n\t\t}\n\n\t\tif( lerp != 0.0f )\n\t\t{\n\t\t\t_TriColor4f( color[0], color[1], color[2], tr.blend * lerp );\n\t\t\tGL_Bind( XASH_TEXTURE0, frame->gl_texturenum );\n\t\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale );\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ref/soft/r_studio.c",
    "content": "/*\ngl_studio.c - studio model renderer\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"xash3d_mathlib.h\"\n#include \"const.h\"\n#include \"r_studioint.h\"\n#include \"triangleapi.h\"\n#include \"studio.h\"\n#include \"pm_local.h\"\n#include \"pmtrace.h\"\n\n#define EVENT_CLIENT    5000    // less than this value it's a server-side studio events\n#define MAX_LOCALLIGHTS 4\n\ntypedef struct\n{\n\tchar    name[MAX_OSPATH];\n\tchar    modelname[MAX_OSPATH];\n\tmodel_t *model;\n} player_model_t;\n\nCVAR_DEFINE_AUTO( r_shadows, \"0\", 0, \"draw ugly shadows\" );\n\nstatic const vec3_t hullcolor[8] =\n{\n\t{ 1.0f, 1.0f, 1.0f },\n\t{ 1.0f, 0.5f, 0.5f },\n\t{ 0.5f, 1.0f, 0.5f },\n\t{ 1.0f, 1.0f, 0.5f },\n\t{ 0.5f, 0.5f, 1.0f },\n\t{ 1.0f, 0.5f, 1.0f },\n\t{ 0.5f, 1.0f, 1.0f },\n\t{ 1.0f, 1.0f, 1.0f },\n};\n\ntypedef struct sortedmesh_s\n{\n\tmstudiomesh_t *mesh;\n\tint           flags;                    // face flags\n} sortedmesh_t;\n\n#if XASH_LOW_MEMORY\n\t#undef MAXSTUDIOVERTS\n\t#define MAXSTUDIOVERTS 1024\n#endif\n\ntypedef struct\n{\n\tdouble         time;\n\tdouble         frametime;\n\tint            framecount;              // studio framecount\n\tqboolean       interpolate;\n\tint            rendermode;\n\tfloat          blend;                   // blend value\n\n\t// bones\n\tmatrix3x4      rotationmatrix;\n\tmatrix3x4      bonestransform[MAXSTUDIOBONES];\n\tmatrix3x4      lighttransform[MAXSTUDIOBONES];\n\n\t// boneweighting stuff\n\tmatrix3x4      worldtransform[MAXSTUDIOBONES];\n\n\t// cached bones\n\tmatrix3x4      cached_bonestransform[MAXSTUDIOBONES];\n\tmatrix3x4      cached_lighttransform[MAXSTUDIOBONES];\n\tchar           cached_bonenames[MAXSTUDIOBONES][32];\n\tint            cached_numbones;                 // number of bones in cache\n\n\tsortedmesh_t   meshes[MAXSTUDIOMESHES];         // sorted meshes\n\tvec3_t         verts[MAXSTUDIOVERTS];\n\tvec3_t         norms[MAXSTUDIOVERTS];\n\n\t// lighting state\n\tfloat          ambientlight;\n\tfloat          shadelight;\n\tvec3_t         lightvec;                    // averaging light direction\n\tvec3_t         lightspot;                   // shadow spot\n\tvec3_t         lightcolor;                  // averaging lightcolor\n\tvec3_t         blightvec[MAXSTUDIOBONES];   // bone light vecs\n\tvec3_t         lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel\n\n\t// chrome stuff\n\tvec3_t         chrome_origin;\n\tvec2_t         chrome[MAXSTUDIOVERTS];      // texture coords for surface normals\n\tvec3_t         chromeright[MAXSTUDIOBONES]; // chrome vector \"right\" in bone reference frames\n\tvec3_t         chromeup[MAXSTUDIOBONES];    // chrome vector \"up\" in bone reference frames\n\tint            chromeage[MAXSTUDIOBONES];   // last time chrome vectors were updated\n\n\t// glowshell stuff\n\tint            normaltable[MAXSTUDIOVERTS];     // glowshell uses this\n\n\t// elights cache\n\tint            numlocallights;\n\tint            lightage[MAXSTUDIOBONES];\n\tdlight_t       *locallight[MAX_LOCALLIGHTS];\n\tuint           locallightcolor[MAX_LOCALLIGHTS][3];\n\tvec4_t         lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS];\n\tvec3_t         lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS];\n\tfloat          locallightR2[MAX_LOCALLIGHTS];\n\n\t// playermodels\n\tplayer_model_t player_models[MAX_CLIENTS];\n} studio_draw_state_t;\n\n// studio-related cvars\nCVAR_DEFINE_AUTO( r_studio_sort_textures, \"0\", FCVAR_GLCONFIG, \"change draw order for additive meshes\" );\nstatic cvar_t *cl_righthand = NULL;\n\nstatic r_studio_interface_t *pStudioDraw;\nstatic studio_draw_state_t  g_studio;                   // global studio state\n\n// global variables\nstatic qboolean    m_fDoRemap;\nmstudiomodel_t     *m_pSubModel;\nmstudiobodyparts_t *m_pBodyPart;\nplayer_info_t      *m_pPlayerInfo;\nstudiohdr_t        *m_pStudioHeader;\nfloat m_flGaitMovement;\nint   g_nTopColor, g_nBottomColor;                      // remap colors\nint   g_nFaceFlags, g_nForceFaceFlags;\n\n/*\n====================\nR_StudioInit\n\n====================\n*/\nvoid R_StudioInit( void )\n{\n\tMatrix3x4_LoadIdentity( g_studio.rotationmatrix );\n\n\t// g-cont. cvar disabled by Valve\n//\tgEngfuncs.Cvar_RegisterVariable( &r_shadows );\n\n\tg_studio.interpolate = true;\n\tg_studio.framecount = 0;\n\tm_fDoRemap = false;\n}\n\n/*\n================\nR_StudioSetupTimings\n\ninit current time for a given model\n================\n*/\nstatic void R_StudioSetupTimings( void )\n{\n\tif( RI.drawWorld )\n\t{\n\t\t// synchronize with server time\n\t\tg_studio.time = gp_cl->time;\n\t\tg_studio.frametime = gp_cl->time - gp_cl->oldtime;\n\t}\n\telse\n\t{\n\t\t// menu stuff\n\t\tg_studio.time = gp_host->realtime;\n\t\tg_studio.frametime = gp_host->frametime;\n\t}\n}\n\n/*\n================\nR_AllowFlipViewModel\n\nshould a flip the viewmodel if cl_righthand is set to 1\n================\n*/\nstatic qboolean R_AllowFlipViewModel( cl_entity_t *e )\n{\n\tif( cl_righthand && cl_righthand->value > 0 )\n\t{\n\t\tif( e == tr.viewent )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/*\n================\nR_StudioComputeBBox\n\nCompute a full bounding box for current sequence\n================\n*/\nstatic qboolean R_StudioComputeBBox( vec3_t bbox[8] )\n{\n\tvec3_t           studio_mins, studio_maxs;\n\tvec3_t           mins, maxs, p1, p2;\n\tcl_entity_t      *e = RI.currententity;\n\tmstudioseqdesc_t *pseqdesc;\n\tint i;\n\n\tif( !m_pStudioHeader )\n\t\treturn false;\n\n\t// check if we have valid mins\\maxs\n\tif( !VectorCompare( vec3_origin, RI.currentmodel->mins ))\n\t{\n\t\t// clipping bounding box\n\t\tVectorCopy( RI.currentmodel->mins, mins );\n\t\tVectorCopy( RI.currentmodel->maxs, maxs );\n\t}\n\telse\n\t{\n\t\tClearBounds( mins, maxs );\n\t}\n\n\t// check sequence range\n\tif( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence;\n\n\t// add sequence box to the model box\n\tAddPointToBounds( pseqdesc->bbmin, mins, maxs );\n\tAddPointToBounds( pseqdesc->bbmax, mins, maxs );\n\tClearBounds( studio_mins, studio_maxs );\n\n\t// compute a full bounding box\n\tfor( i = 0; i < 8; i++ )\n\t{\n\t\tp1[0] = ( i & 1 ) ? mins[0] : maxs[0];\n\t\tp1[1] = ( i & 2 ) ? mins[1] : maxs[1];\n\t\tp1[2] = ( i & 4 ) ? mins[2] : maxs[2];\n\n\t\tMatrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 );\n\t\tAddPointToBounds( p2, studio_mins, studio_maxs );\n\t\tif( bbox )\n\t\t\tVectorCopy( p2, bbox[i] );\n\t}\n\n\tif( !bbox && R_CullModel( e, studio_mins, studio_maxs ))\n\t\treturn false;  // model culled\n\treturn true;           // visible\n}\n\nstatic void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result )\n{\n\tfloat flWeight0, flWeight1, flWeight2, flWeight3;\n\tint   i, numbones = 0;\n\tfloat flTotal;\n\n\tfor( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )\n\t{\n\t\tif( boneweights->bone[i] != -1 )\n\t\t\tnumbones++;\n\t}\n\n\tif( numbones == 4 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]];\n\t\tvec4_t *boneMat3 = (vec4_t *)g_studio.worldtransform[boneweights->bone[3]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflWeight3 = boneweights->weight[3] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3;\n\n\t\tif( flTotal < 1.0f )\n\t\t\tflWeight0 += 1.0f - flTotal;                    // compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3;\n\t}\n\telse if( numbones == 3 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2;\n\n\t\tif( flTotal < 1.0f )\n\t\t\tflWeight0 += 1.0f - flTotal;                    // compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2;\n\t}\n\telse if( numbones == 2 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1;\n\n\t\tif( flTotal < 1.0f )\n\t\t\tflWeight0 += 1.0f - flTotal;                    // compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1;\n\t}\n\telse\n\t{\n\t\tMatrix3x4_Copy( result, g_studio.worldtransform[boneweights->bone[0]] );\n\t}\n}\n\n/*\n===============\npfnGetCurrentEntity\n\n===============\n*/\nstatic cl_entity_t *pfnGetCurrentEntity( void )\n{\n\treturn RI.currententity;\n}\n\n/*\n===============\npfnPlayerInfo\n\n===============\n*/\nplayer_info_t *pfnPlayerInfo( int index )\n{\n\tif( !RI.drawWorld )\n\t\tindex = -1;\n\n\treturn gEngfuncs.pfnPlayerInfo( index );\n}\n\n/*\n===============\npfnMod_ForName\n\n===============\n*/\nstatic model_t *pfnMod_ForName( const char *model, int crash )\n{\n\treturn gEngfuncs.Mod_ForName( model, crash, false );\n}\n\n/*\n===============\npfnGetPlayerState\n\n===============\n*/\nstatic entity_state_t *R_StudioGetPlayerState( int index )\n{\n\tif( !RI.drawWorld )\n\t\treturn &RI.currententity->curstate;\n\n\treturn gEngfuncs.pfnGetPlayerState( index );\n}\n\n/*\n===============\npfnGetViewEntity\n\n===============\n*/\nstatic cl_entity_t *pfnGetViewEntity( void )\n{\n\treturn tr.viewent;\n}\n\n/*\n===============\npfnGetEngineTimes\n\n===============\n*/\nstatic void pfnGetEngineTimes( int *framecount, double *current, double *old )\n{\n\tif( framecount )\n\t\t*framecount = tr.realframecount;\n\tif( current )\n\t\t*current = gp_cl->time;\n\tif( old )\n\t\t*old = gp_cl->oldtime;\n}\n\n/*\n===============\npfnGetViewInfo\n\n===============\n*/\nstatic void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv )\n{\n\tif( origin )\n\t\tVectorCopy( RI.vieworg, origin );\n\tif( forwardv )\n\t\tVectorCopy( RI.vforward, forwardv );\n\tif( rightv )\n\t\tVectorCopy( RI.vright, rightv );\n\tif( upv )\n\t\tVectorCopy( RI.vup, upv );\n}\n\n/*\n===============\nR_GetChromeSprite\n\n===============\n*/\nstatic model_t *R_GetChromeSprite( void )\n{\n\treturn gEngfuncs.GetDefaultSprite( REF_CHROME_SPRITE );\n}\n\n/*\n===============\npfnGetModelCounters\n\n===============\n*/\nstatic void pfnGetModelCounters( int **s, int **a )\n{\n\t*s = &g_studio.framecount;\n\t*a = &r_stats.c_studio_models_drawn;\n}\n\n/*\n===============\npfnGetAliasScale\n\n===============\n*/\nstatic void pfnGetAliasScale( float *x, float *y )\n{\n\tif( x )\n\t\t*x = 1.0f;\n\tif( y )\n\t\t*y = 1.0f;\n}\n\n/*\n===============\npfnStudioGetBoneTransform\n\n===============\n*/\nstatic float ****pfnStudioGetBoneTransform( void )\n{\n\treturn (float ****)g_studio.bonestransform;\n}\n\n/*\n===============\npfnStudioGetLightTransform\n\n===============\n*/\nstatic float ****pfnStudioGetLightTransform( void )\n{\n\treturn (float ****)g_studio.lighttransform;\n}\n\n/*\n===============\npfnStudioGetAliasTransform\n\n===============\n*/\nstatic float ***pfnStudioGetAliasTransform( void )\n{\n\treturn NULL;\n}\n\n/*\n===============\npfnStudioGetRotationMatrix\n\n===============\n*/\nstatic float ***pfnStudioGetRotationMatrix( void )\n{\n\treturn (float ***)g_studio.rotationmatrix;\n}\n\n/*\n====================\nStudioPlayerBlend\n\n====================\n*/\nstatic void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )\n{\n\t// calc up/down pointing\n\t*pBlend = ( *pPitch * 3.0f );\n\n\tif( *pBlend < pseqdesc->blendstart[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendstart[0] / 3.0f;\n\t\t*pBlend = 0;\n\t}\n\telse if( *pBlend > pseqdesc->blendend[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendend[0] / 3.0f;\n\t\t*pBlend = 255;\n\t}\n\telse\n\t{\n\t\tif( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error\n\t\t\t*pBlend = 127;\n\t\telse\n\t\t\t*pBlend = 255 * ( *pBlend - pseqdesc->blendstart[0] ) / ( pseqdesc->blendend[0] - pseqdesc->blendstart[0] );\n\t\t*pPitch = 0.0f;\n\t}\n}\n\n/*\n====================\nR_StudioLerpMovement\n\n====================\n*/\nvoid GAME_EXPORT R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles )\n{\n\tfloat f = 1.0f;\n\n\t// don't do it if the goalstarttime hasn't updated in a while.\n\t// NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit\n\t// was increased to 1.0 s., which is 2x the max lag we are accounting for.\n\tif( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime ))\n\t\tf = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime );\n\n\t// Con_Printf( \"%4.2f %.2f %.2f\\n\", f, e->curstate.animtime, g_studio.time );\n\tVectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin );\n\n\tif( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON ))\n\t{\n\t\tvec4_t q, q1, q2;\n\n\t\tAngleQuaternion( e->curstate.angles, q1, false );\n\t\tAngleQuaternion( e->latched.prevangles, q2, false );\n\t\tQuaternionSlerp( q2, q1, f, q );\n\t\tQuaternionAngle( q, angles );\n\t}\n\telse\n\t\tVectorCopy( e->curstate.angles, angles );\n}\n\n/*\n====================\nStudioSetUpTransform\n\n====================\n*/\nstatic void R_StudioSetUpTransform( cl_entity_t *e )\n{\n\tvec3_t origin, angles;\n\n\tVectorCopy( e->origin, origin );\n\tVectorCopy( e->angles, angles );\n\n\t// interpolate monsters position (moved into UpdateEntityFields by user request)\n\tif( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gp_host->features, ENGINE_COMPUTE_STUDIO_LERP ))\n\t{\n\t\tR_StudioLerpMovement( e, g_studio.time, origin, angles );\n\t}\n\n\tif( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\tangles[PITCH] = -angles[PITCH]; // stupid quake bug\n\n\t// don't rotate clients, only aim\n\tif( e->player )\n\t\tangles[PITCH] = 0.0f;\n\n\tMatrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f );\n\n\tif( tr.fFlipViewModel )\n\t{\n\t\tg_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1];\n\t\tg_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1];\n\t\tg_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1];\n\t}\n}\n\n/*\n====================\nStudioEstimateFrame\n\n====================\n*/\nfloat GAME_EXPORT R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time )\n{\n\tdouble dfdt, f;\n\n\tif( g_studio.interpolate )\n\t{\n\t\tif( time < e->curstate.animtime )\n\t\t\tdfdt = 0.0;\n\t\telse\n\t\t\tdfdt = ( time - e->curstate.animtime ) * e->curstate.framerate * pseqdesc->fps;\n\t}\n\telse\n\t\tdfdt = 0;\n\n\tif( pseqdesc->numframes <= 1 )\n\t\tf = 0.0;\n\telse\n\t\tf = ( e->curstate.frame * ( pseqdesc->numframes - 1 )) / 256.0;\n\n\tf += dfdt;\n\n\tif( pseqdesc->flags & STUDIO_LOOPING )\n\t{\n\t\tif( pseqdesc->numframes > 1 )\n\t\t\tf -= (int)( f / ( pseqdesc->numframes - 1 )) * ( pseqdesc->numframes - 1 );\n\t\tif( f < 0 )\n\t\t\tf += ( pseqdesc->numframes - 1 );\n\t}\n\telse\n\t{\n\t\tif( f >= pseqdesc->numframes - 1.001 )\n\t\t\tf = pseqdesc->numframes - 1.001;\n\t\tif( f < 0.0 )\n\t\t\tf = 0.0;\n\t}\n\treturn f;\n}\n\n/*\n====================\nStudioEstimateInterpolant\n\n====================\n*/\nstatic float R_StudioEstimateInterpolant( cl_entity_t *e )\n{\n\tfloat dadt = 1.0f;\n\n\tif( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f ))\n\t{\n\t\tdadt = ( g_studio.time - e->curstate.animtime ) / 0.1f;\n\t\tif( dadt > 2.0f )\n\t\t\tdadt = 2.0f;\n\t}\n\n\treturn dadt;\n}\n\n/*\n====================\nStudioFxTransform\n\n====================\n*/\nstatic void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform )\n{\n\tswitch( ent->curstate.renderfx )\n\t{\n\tcase kRenderFxDistort:\n\tcase kRenderFxHologram:\n\t\tif( !gEngfuncs.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tint axis = gEngfuncs.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 )\n\t\t\t\taxis = 2;         // choose between x & z\n\t\t\tVectorScale( transform[axis], gEngfuncs.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] );\n\t\t}\n\t\telse if( !gEngfuncs.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tfloat offset;\n\t\t\tint   axis = gEngfuncs.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 )\n\t\t\t\taxis = 2;         // choose between x & z\n\t\t\toffset = gEngfuncs.COM_RandomFloat( -10.0f, 10.0f );\n\t\t\ttransform[gEngfuncs.COM_RandomLong( 0, 2 )][3] += offset;\n\t\t}\n\t\tbreak;\n\tcase kRenderFxExplode:\n\t{\n\t\tfloat scale;\n\n\t\tscale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f;\n\t\tif( scale > 2.0f )\n\t\t\tscale = 2.0f;                    // don't blow up more than 200%\n\n\t\ttransform[0][1] *= scale;\n\t\ttransform[1][1] *= scale;\n\t\ttransform[2][1] *= scale;\n\t}\n\tbreak;\n\t}\n}\n\n/*\n====================\nStudioCalcBoneAdj\n\n====================\n*/\nstatic void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen )\n{\n\tmstudiobonecontroller_t *pbonecontroller;\n\tfloat value = 0.0f;\n\tint   i, j;\n\n\tpbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex );\n\n\tfor( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )\n\t{\n\t\ti = pbonecontroller[j].index;\n\n\t\tif( i == STUDIO_MOUTH )\n\t\t{\n\t\t\t// mouth hardcoded at controller 4\n\t\t\tvalue = (float)mouthopen / 64.0f;\n\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\tvalue = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t}\n\t\telse if( i < 4 )\n\t\t{\n\t\t\t// check for 360% wrapping\n\t\t\tif( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP ))\n\t\t\t{\n\t\t\t\tif( abs( pcontroller1[i] - pcontroller2[i] ) > 128 )\n\t\t\t\t{\n\t\t\t\t\tint a = ( pcontroller1[i] + 128 ) % 256;\n\t\t\t\t\tint b = ( pcontroller2[i] + 128 ) % 256;\n\t\t\t\t\tvalue = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128 ) * ( 360.0f / 256.0f ) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tvalue = (( pcontroller1[i] * dadt + ( pcontroller2[i] ) * ( 1.0f - dadt ))) * ( 360.0f / 256.0f ) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvalue = ( pcontroller1[i] * dadt + pcontroller2[i] * ( 1.0f - dadt )) / 255.0f;\n\t\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\t\tvalue = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t\t}\n\t\t}\n\n\t\tswitch( pbonecontroller[j].type & STUDIO_TYPES )\n\t\t{\n\t\tcase STUDIO_XR:\n\t\tcase STUDIO_YR:\n\t\tcase STUDIO_ZR:\n\t\t\tadj[j] = DEG2RAD( value );\n\t\t\tbreak;\n\t\tcase STUDIO_X:\n\t\tcase STUDIO_Y:\n\t\tcase STUDIO_Z:\n\t\t\tadj[j] = value;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioCalcRotations\n\n====================\n*/\nstatic void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f )\n{\n\tint           i, frame;\n\tfloat         adj[MAXSTUDIOCONTROLLERS];\n\tfloat         s, dadt;\n\tmstudiobone_t *pbone;\n\n\t// bah, fix this bug with changing sequences too fast\n\tif( f > pseqdesc->numframes - 1 )\n\t{\n\t\tf = 0.0f;\n\t}\n\telse if( f < -0.01f )\n\t{\n\t\t// BUG ( somewhere else ) but this code should validate this data.\n\t\t// This could cause a crash if the frame # is negative, so we'll go ahead\n\t\t// and clamp it here\n\t\tf = -0.01f;\n\t}\n\n\tframe = (int)f;\n\n\tdadt = R_StudioEstimateInterpolant( e );\n\ts = ( f - frame );\n\n\t// add in programtic controllers\n\tpbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex );\n\n\tR_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ )\n\t\tR_StudioCalcBones( frame, s, pbone, panim, adj, pos[i], q[i] );\n\n\tif( pseqdesc->motiontype & STUDIO_X )\n\t\tpos[pseqdesc->motionbone][0] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Y )\n\t\tpos[pseqdesc->motionbone][1] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Z )\n\t\tpos[pseqdesc->motionbone][2] = 0.0f;\n}\n\n/*\n====================\nStudioMergeBones\n\n====================\n*/\nstatic void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel )\n{\n\tint i, j;\n\tmstudiobone_t    *pbones;\n\tmstudioseqdesc_t *pseqdesc;\n\tmstudioanim_t    *panim;\n\tmatrix3x4        bonematrix;\n\tstatic vec4_t    q[MAXSTUDIOBONES];\n\tstatic float     pos[MAXSTUDIOBONES][3];\n\tfloat f;\n\n\tif( e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tfor( j = 0; j < g_studio.cached_numbones; j++ )\n\t\t{\n\t\t\tif( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] ))\n\t\t\t{\n\t\t\t\tMatrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( j >= g_studio.cached_numbones )\n\t\t{\n\t\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\t\t\tif( pbones[i].parent == -1 )\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t\t// apply client-side effects to the transformation matrix\n\t\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioSetupBones\n\n====================\n*/\nstatic void R_StudioSetupBones( cl_entity_t *e )\n{\n\tfloat            f;\n\tmstudiobone_t    *pbones;\n\tmstudioseqdesc_t *pseqdesc;\n\tmstudioanim_t    *panim;\n\tmatrix3x4        bonematrix;\n\tstatic vec3_t    pos[MAXSTUDIOBONES];\n\tstatic vec4_t    q[MAXSTUDIOBONES];\n\tstatic vec3_t    pos2[MAXSTUDIOBONES];\n\tstatic vec4_t    q2[MAXSTUDIOBONES];\n\tstatic vec3_t    pos3[MAXSTUDIOBONES];\n\tstatic vec4_t    q3[MAXSTUDIOBONES];\n\tstatic vec3_t    pos4[MAXSTUDIOBONES];\n\tstatic vec4_t    q4[MAXSTUDIOBONES];\n\tint i;\n\n\tif( e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\n\tif( pseqdesc->numblends > 1 )\n\t{\n\t\tfloat s;\n\t\tfloat dadt;\n\n\t\tpanim += m_pStudioHeader->numbones;\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f );\n\n\t\tdadt = R_StudioEstimateInterpolant( e );\n\t\ts = ( e->curstate.blending[0] * dadt + e->latched.prevblending[0] * ( 1.0f - dadt )) / 255.0f;\n\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s );\n\n\t\tif( pseqdesc->numblends == 4 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f );\n\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f );\n\n\t\t\ts = ( e->curstate.blending[0] * dadt + e->latched.prevblending[0] * ( 1.0f - dadt )) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\ts = ( e->curstate.blending[1] * dadt + e->latched.prevblending[1] * ( 1.0f - dadt )) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s );\n\t\t}\n\t}\n\n\tif( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq ))\n\t{\n\t\t// blend from last sequence\n\t\tstatic vec3_t pos1b[MAXSTUDIOBONES];\n\t\tstatic vec4_t q1b[MAXSTUDIOBONES];\n\t\tfloat         s;\n\n\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->latched.prevsequence;\n\t\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\n\t\t// clip prevframe\n\t\tR_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe );\n\n\t\tif( pseqdesc->numblends > 1 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\ts = ( e->latched.prevseqblending[0] ) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s );\n\n\t\t\tif( pseqdesc->numblends == 4 )\n\t\t\t{\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\ts = ( e->latched.prevseqblending[0] ) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\t\ts = ( e->latched.prevseqblending[1] ) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s );\n\t\t\t}\n\t\t}\n\n\t\ts = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f;\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s );\n\t}\n\telse\n\t{\n\t\t// store prevframe otherwise\n\t\te->latched.prevframe = f;\n\t}\n\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex );\n\n\t// calc gait animation\n\tif( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 )\n\t{\n\t\tqboolean copy_bones = true;\n\n\t\tif( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq )\n\t\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pPlayerInfo->gaitsequence;\n\n\t\tpanim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe );\n\n\t\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\t{\n\t\t\tif( !Q_strcmp( pbones[i].name, \"Bip01 Spine\" ))\n\t\t\t\tcopy_bones = false;\n\t\t\telse if( !Q_strcmp( pbones[pbones[i].parent].name, \"Bip01 Pelvis\" ))\n\t\t\t\tcopy_bones = true;\n\n\t\t\tif( !copy_bones )\n\t\t\t\tcontinue;\n\n\t\t\tVectorCopy( pos2[i], pos[i] );\n\t\t\tVector4Copy( q2[i], q[i] );\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\n\t\tif( pbones[i].parent == -1 )\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t// apply client-side effects to the transformation matrix\n\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t}\n\t}\n}\n\n/*\n====================\nStudioSaveBones\n\n====================\n*/\nstatic void R_StudioSaveBones( void )\n{\n\tmstudiobone_t *pbones;\n\tint           i;\n\n\tpbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex );\n\tg_studio.cached_numbones = m_pStudioHeader->numbones;\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] );\n\t\tMatrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] );\n\t\tQ_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 );\n\t}\n}\n\n/*\n====================\nStudioBuildNormalTable\n\nNOTE: m_pSubModel must be set\n====================\n*/\nstatic void R_StudioBuildNormalTable( void )\n{\n\tcl_entity_t   *e = RI.currententity;\n\tmstudiomesh_t *pmesh;\n\tint           i, j;\n\n\tAssert( m_pSubModel != NULL );\n\n\t// reset chrome cache\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\tg_studio.chromeage[i] = 0;\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tg_studio.normaltable[i] = -1;\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort *ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + j;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex );\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t\ti = -i;\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tif( g_studio.normaltable[ptricmds[0]] < 0 )\n\t\t\t\t\tg_studio.normaltable[ptricmds[0]] = ptricmds[1];\n\t\t\t}\n\t\t}\n\t}\n\n\tg_studio.chrome_origin[0] = cos( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[1] = sin( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[2] = cos( r_glowshellfreq->value * g_studio.time * 0.33f ) * 4000.0f;\n\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t\tTriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 );\n\telse\n\t\tTriColor4ub( 255, 255, 255, 255 );\n}\n\n/*\n====================\nStudioGenerateNormals\n\nNOTE: m_pSubModel must be set\ng_studio.verts must be computed\n====================\n*/\nstatic void R_StudioGenerateNormals( void )\n{\n\tint           v0, v1, v2;\n\tvec3_t        e0, e1, norm;\n\tmstudiomesh_t *pmesh;\n\tint           i, j;\n\n\tAssert( m_pSubModel != NULL );\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tVectorClear( g_studio.norms[i] );\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort *ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + j;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex );\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\ti = -i;\n\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tv1 = v2;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\tqboolean odd = false;\n\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tif( odd )\n\t\t\t\t\t\t\tv1 = v2;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tv0 = v2;\n\n\t\t\t\t\t\todd = !odd;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tVectorNormalize( g_studio.norms[i] );\n}\n\n/*\n====================\nStudioSetupChrome\n\n====================\n*/\nstatic void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal )\n{\n\tfloat n;\n\n\tif( g_studio.chromeage[bone] != g_studio.framecount )\n\t{\n\t\t// calculate vectors from the viewer to the bone. This roughly adjusts for position\n\t\tvec3_t chromeupvec;    // g_studio.chrome t vector in world reference frame\n\t\tvec3_t chromerightvec; // g_studio.chrome s vector in world reference frame\n\t\tvec3_t tmp;            // vector pointing at bone in world reference frame\n\n\t\tVectorNegate( g_studio.chrome_origin, tmp );\n\t\ttmp[0] += g_studio.lighttransform[bone][0][3];\n\t\ttmp[1] += g_studio.lighttransform[bone][1][3];\n\t\ttmp[2] += g_studio.lighttransform[bone][2][3];\n\n\t\tVectorNormalize( tmp );\n\t\tCrossProduct( tmp, RI.vright, chromeupvec );\n\t\tVectorNormalize( chromeupvec );\n\t\tCrossProduct( chromeupvec, tmp, chromerightvec );\n\t\tVectorNormalize( chromerightvec );\n\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromeupvec, g_studio.chromeup[bone] );\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromerightvec, g_studio.chromeright[bone] );\n\n\t\tg_studio.chromeage[bone] = g_studio.framecount;\n\t}\n\n\t// calc s coord\n\tn = DotProduct( normal, g_studio.chromeright[bone] );\n\tpchrome[0] = ( n + 1.0f ) * 32.0f;\n\n\t// calc t coord\n\tn = DotProduct( normal, g_studio.chromeup[bone] );\n\tpchrome[1] = ( n + 1.0f ) * 32.0f;\n}\n\n/*\n====================\nStudioCalcAttachments\n\n====================\n*/\nstatic void R_StudioCalcAttachments( void )\n{\n\tmstudioattachment_t *pAtt;\n\tvec3_t forward, bonepos;\n\tvec3_t localOrg, localAng;\n\tint    i;\n\n\t// calculate attachment points\n\tpAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex );\n\n\tfor( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ )\n\t{\n\t\tMatrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] );\n\t\tVectorSubtract( RI.currententity->attachment[i], RI.currententity->origin, localOrg );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.lighttransform[pAtt[i].bone], bonepos );\n\t\tVectorSubtract( localOrg, bonepos, forward ); // make forward\n\t\tVectorNormalizeFast( forward );\n\t\tVectorAngles( forward, localAng );\n\t}\n}\n\n/*\n===============\npfnStudioSetupModel\n\n===============\n*/\nstatic void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel )\n{\n\tint index;\n\n\tif( bodypart > m_pStudioHeader->numbodyparts )\n\t\tbodypart = 0;\n\n\tm_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex ) + bodypart;\n\n\tindex = RI.currententity->curstate.body / m_pBodyPart->base;\n\tindex = index % m_pBodyPart->nummodels;\n\n\tm_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex ) + index;\n\n\tif( ppbodypart )\n\t\t*ppbodypart = m_pBodyPart;\n\tif( ppsubmodel )\n\t\t*ppsubmodel = m_pSubModel;\n}\n\n/*\n===============\nR_StudioCheckBBox\n\n===============\n*/\nstatic int R_StudioCheckBBox( void )\n{\n\tif( !RI.currententity || !RI.currentmodel )\n\t\treturn false;\n\n\treturn R_StudioComputeBBox( NULL );\n}\n\n/*\n===============\nR_StudioDynamicLight\n\n===============\n*/\nstatic void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight )\n{\n\tmovevars_t *mv = tr.movevars;\n\tvec3_t     lightDir, vecSrc, vecEnd;\n\tvec3_t     origin, dist, finalLight;\n\tfloat      add, radius, total;\n\tcolorVec   light;\n\tuint       lnum;\n\tdlight_t   *dl;\n\n\tif( !plight || !ent || !ent->model )\n\t\treturn;\n\n\tif( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT ))\n\t{\n\t\tplight->shadelight = 0;\n\t\tplight->ambientlight = 192;\n\n\t\tVectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f );\n\t\tVectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\t\treturn;\n\t}\n\n\t// determine plane to get lightvalues from: ceil or floor\n\tif( FBitSet( ent->curstate.effects, EF_INVLIGHT ))\n\t\tVectorSet( lightDir, 0.0f, 0.0f, 1.0f );\n\telse\n\t\tVectorSet( lightDir, 0.0f, 0.0f, -1.0f );\n\n\tVectorCopy( ent->origin, origin );\n\n\tVectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f );\n\tlight.r = light.g = light.b = light.a = 0;\n\n\tif(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 )\n\t{\n\t\tmsurface_t *psurf = NULL;\n\t\tpmtrace_t  trace;\n\n\t\tif( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD ))\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f;\n\t\t}\n\n\t\ttrace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY );\n\t\tif( trace.ent > 0 )\n\t\t\tpsurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd );\n\t\telse\n\t\t\tpsurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd );\n\n\t\tif( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )))\n\t\t{\n\t\t\tVectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z );\n\n\t\t\tlight.r = mv->skycolor_r;\n\t\t\tlight.g = mv->skycolor_g;\n\t\t\tlight.b = mv->skycolor_b;\n\t\t}\n\t}\n\n\tif(( light.r + light.g + light.b ) == 0 )\n\t{\n\t\tcolorVec gcolor;\n\t\tfloat    grad[4];\n\n\t\tVectorScale( lightDir, 2048.0f, vecEnd );\n\t\tVectorAdd( vecEnd, vecSrc, vecEnd );\n\n\t\tlight = R_LightVec( vecSrc, vecEnd, g_studio.lightspot, g_studio.lightvec );\n\n\t\tif( VectorIsNull( g_studio.lightvec ))\n\t\t{\n\t\t\tvecSrc[0] -= 16.0f;\n\t\t\tvecSrc[1] -= 16.0f;\n\t\t\tvecEnd[0] -= 16.0f;\n\t\t\tvecEnd[1] -= 16.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] += 32.0f;\n\t\t\tvecEnd[0] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[1] += 32.0f;\n\t\t\tvecEnd[1] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] -= 32.0f;\n\t\t\tvecEnd[0] -= 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tlightDir[0] = grad[0] - grad[1] - grad[2] + grad[3];\n\t\t\tlightDir[1] = grad[1] + grad[0] - grad[2] - grad[3];\n\t\t\tVectorNormalize( lightDir );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( g_studio.lightvec, lightDir );\n\t\t}\n\t}\n\n\tif( ent->curstate.renderfx == kRenderFxLightMultiplier && ent->curstate.iuser4 != 10 )\n\t{\n\t\tlight.r *= ent->curstate.iuser4 / 10.0f;\n\t\tlight.g *= ent->curstate.iuser4 / 10.0f;\n\t\tlight.b *= ent->curstate.iuser4 / 10.0f;\n\t}\n\n\tVectorSet( finalLight, light.r, light.g, light.b );\n\tent->cvFloorColor = light;\n\n\ttotal = Q_max( Q_max( light.r, light.g ), light.b );\n\tif( total == 0.0f )\n\t\ttotal = 1.0f;\n\n\t// scale lightdir by light intentsity\n\tVectorScale( lightDir, total, lightDir );\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tdl = &tr.dlights[lnum];\n\n\t\tif( dl->die < g_studio.time || !r_dynamic->value )\n\t\t\tcontinue;\n\n\t\tVectorSubtract( ent->origin, dl->origin, dist );\n\n\t\tradius = VectorLength( dist );\n\t\tadd = ( dl->radius - radius );\n\n\t\tif( add > 0.0f )\n\t\t{\n\t\t\ttotal += add;\n\n\t\t\tif( radius > 1.0f )\n\t\t\t\tVectorScale( dist, ( add / radius ), dist );\n\t\t\telse\n\t\t\t\tVectorScale( dist, add, dist );\n\n\t\t\tVectorAdd( lightDir, dist, lightDir );\n\n\t\t\tfinalLight[0] += dl->color.r * ( add / 256.0f );\n\t\t\tfinalLight[1] += dl->color.g * ( add / 256.0f );\n\t\t\tfinalLight[2] += dl->color.b * ( add / 256.0f );\n\t\t}\n\t}\n\n\tif( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT ))\n\t\tadd = 0.6f;\n\telse\n\t\tadd = bound( 0.75f, v_direct->value, 1.0f );\n\n\tVectorScale( lightDir, add, lightDir );\n\n\tplight->shadelight = VectorLength( lightDir );\n\tplight->ambientlight = total - plight->shadelight;\n\n\ttotal = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] );\n\n\tif( total > 0.0f )\n\t{\n\t\tplight->color[0] = finalLight[0] * ( 1.0f / total );\n\t\tplight->color[1] = finalLight[1] * ( 1.0f / total );\n\t\tplight->color[2] = finalLight[2] * ( 1.0f / total );\n\t}\n\telse\n\t\tVectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\n\tif( plight->ambientlight > 128 )\n\t\tplight->ambientlight = 128;\n\n\tif( plight->ambientlight + plight->shadelight > 255 )\n\t\tplight->shadelight = 255 - plight->ambientlight;\n\n\tVectorNormalize2( lightDir, plight->plightvec );\n}\n\n/*\n===============\npfnStudioEntityLight\n\n===============\n*/\nstatic void R_StudioEntityLight( alight_t *lightinfo )\n{\n\tint         lnum, i, j, k;\n\tfloat       minstrength, dist2, f, r2;\n\tfloat       lstrength[MAX_LOCALLIGHTS];\n\tcl_entity_t *ent = RI.currententity;\n\tvec3_t      mid, origin, pos;\n\tdlight_t    *el;\n\n\tg_studio.numlocallights = 0;\n\n\tif( !ent || !r_dynamic->value )\n\t\treturn;\n\n\tfor( i = 0; i < MAX_LOCALLIGHTS; i++ )\n\t\tlstrength[i] = 0;\n\n\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin );\n\tdist2 = 1000000.0f;\n\tk = 0;\n\n\tfor( lnum = 0; lnum < MAX_ELIGHTS; lnum++ )\n\t{\n\t\tel = &tr.elights[lnum];\n\n\t\tif( el->die < g_studio.time || el->radius <= 0.0f )\n\t\t\tcontinue;\n\n\t\tif(( el->key & 0xFFF ) == ent->index )\n\t\t{\n\t\t\tint att = ( el->key >> 12 ) & 0xF;\n\n\t\t\tif( att )\n\t\t\t\tVectorCopy( ent->attachment[att], el->origin );\n\t\t\telse\n\t\t\t\tVectorCopy( ent->origin, el->origin );\n\t\t}\n\n\t\tVectorCopy( el->origin, pos );\n\t\tVectorSubtract( origin, el->origin, mid );\n\n\t\tf = DotProduct( mid, mid );\n\t\tr2 = el->radius * el->radius;\n\n\t\tif( f > r2 )\n\t\t\tminstrength = r2 / f;\n\t\telse\n\t\t\tminstrength = 1.0f;\n\n\t\tif( minstrength > 0.05f )\n\t\t{\n\t\t\tif( g_studio.numlocallights >= MAX_LOCALLIGHTS )\n\t\t\t{\n\t\t\t\tfor( j = 0, k = -1; j < g_studio.numlocallights; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( lstrength[j] < dist2 && lstrength[j] < minstrength )\n\t\t\t\t\t{\n\t\t\t\t\t\tdist2 = lstrength[j];\n\t\t\t\t\t\tk = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tk = g_studio.numlocallights;\n\n\t\t\tif( k != -1 )\n\t\t\t{\n\t\t\t\tg_studio.locallightcolor[k][0] = LinearGammaTable( el->color.r << 2 );\n\t\t\t\tg_studio.locallightcolor[k][1] = LinearGammaTable( el->color.g << 2 );\n\t\t\t\tg_studio.locallightcolor[k][2] = LinearGammaTable( el->color.b << 2 );\n\t\t\t\tg_studio.locallightR2[k] = r2;\n\t\t\t\tg_studio.locallight[k] = el;\n\t\t\t\tlstrength[k] = minstrength;\n\n\t\t\t\tif( k >= g_studio.numlocallights )\n\t\t\t\t\tg_studio.numlocallights = k + 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nR_StudioSetupLighting\n\n===============\n*/\nstatic void R_StudioSetupLighting( alight_t *plight )\n{\n\tfloat scale = 1.0f;\n\tint   i;\n\n\tif( !m_pStudioHeader || !plight )\n\t\treturn;\n\n\tif( RI.currententity != NULL )\n\t\tscale = RI.currententity->curstate.scale;\n\n\tg_studio.ambientlight = plight->ambientlight;\n\tg_studio.shadelight = plight->shadelight;\n\tVectorCopy( plight->plightvec, g_studio.lightvec );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] );\n\t\tif( scale > 1.0f )\n\t\t\tVectorNormalize( g_studio.blightvec[i] );            // in case model may be scaled\n\t}\n\n\tVectorCopy( plight->color, g_studio.lightcolor );\n}\n\n/*\n===============\nR_StudioLighting\n\n===============\n*/\nstatic void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal )\n{\n\tfloat illum;\n\n\tif( FBitSet( flags, STUDIO_NF_FULLBRIGHT ))\n\t{\n\t\t*lv = 1.0f;\n\t\treturn;\n\t}\n\n\tillum = g_studio.ambientlight;\n\n\tif( FBitSet( flags, STUDIO_NF_FLATSHADE ))\n\t{\n\t\tillum += g_studio.shadelight * 0.8f;\n\t}\n\telse\n\t{\n\t\tfloat r, lightcos;\n\n\t\tif( bone != -1 )\n\t\t\tlightcos = DotProduct( normal, g_studio.blightvec[bone] );\n\t\telse\n\t\t\tlightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite\n\t\tif( lightcos > 1.0f )\n\t\t\tlightcos = 1.0f;\n\n\t\tillum += g_studio.shadelight;\n\n\t\tr = SHADE_LAMBERT;\n\n\t\t// do modified hemispherical lighting\n\t\tif( r <= 1.0f )\n\t\t{\n\t\t\tr += 1.0f;\n\t\t\tlightcos = (( r - 1.0f ) - lightcos ) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum += g_studio.shadelight * lightcos;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlightcos = ( lightcos + ( r - 1.0f )) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum -= g_studio.shadelight * lightcos;\n\t\t}\n\n\t\tillum = Q_max( illum, 0.0f );\n\t}\n\n\tillum = Q_min( illum, 255.0f );\n\n\t*lv = LightToTexGamma( illum * 4 ) / 1023.0f;\n}\n\n/*\n====================\nR_LightLambert\n\n====================\n*/\nstatic void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out )\n{\n\tvec3_t finalLight;\n\tint    i;\n\n\tif( !g_studio.numlocallights )\n\t{\n\t\tVectorScale( color, 255.0f, out );\n\t\treturn;\n\t}\n\n\tVectorSet( finalLight, 0, 0, 0 );\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tfloat r;\n\n\t\tr = DotProduct( normal, light[i] );\n\t\tif( likely( !tr.fFlipViewModel ))\n\t\t\tr = -r;\n\n\t\tif( r > 0.0f )\n\t\t{\n\t\t\tvec3_t localLight;\n\t\t\tfloat  temp;\n\n\t\t\tif( light[i][3] == 0.0f )\n\t\t\t{\n\t\t\t\tfloat r2 = DotProduct( light[i], light[i] );\n\n\t\t\t\tif( r2 > 0.0f )\n\t\t\t\t\tlight[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 ));\n\t\t\t\telse\n\t\t\t\t\tlight[i][3] = 0.0001f;\n\t\t\t}\n\n\t\t\ttemp = r * light[i][3];\n\n\t\t\tVectorAddScalar( g_studio.locallightcolor[i], temp, localLight );\n\t\t\tVectorAdd( finalLight, localLight, finalLight );\n\t\t}\n\t}\n\n\tif( !VectorIsNull( finalLight ))\n\t{\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tfloat c = finalLight[i] + LinearGammaTable( color[i] * 1023.0f );\n\n\t\t\tif( c > 1023.0f )\n\t\t\t\tout[i] = 255;\n\t\t\telse\n\t\t\t\tout[i] = ScreenGammaTable( c ) >> 2;\n\t\t}\n\t}\n\telse\n\t{\n\t\tVectorScale( color, 255.0f, out );\n\t}\n}\n\nstatic void R_StudioSetColorBegin( short *ptricmds, vec3_t *pstudionorms )\n{\n\tfloat  *lv = (float *)g_studio.lightvalues[ptricmds[1]];\n\trgba_t color;\n\n\tcolor[3] = tr.blend * 255;\n\n\tR_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color );\n\tTriColor4ub( color[0], color[1], color[2], color[3] );\n}\n\n\n/*\n====================\nR_LightStrength\n\n====================\n*/\nstatic void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] )\n{\n\tint i;\n\n\tif( g_studio.lightage[bone] != g_studio.framecount )\n\t{\n\t\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t\t{\n\t\t\tdlight_t *el = g_studio.locallight[i];\n\t\t\tMatrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] );\n\t\t}\n\n\t\tg_studio.lightage[bone] = g_studio.framecount;\n\t}\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tVectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] );\n\t\tlight[i][3] = 0.0f;\n\t}\n}\n\n/*\n===============\nR_StudioSetupSkin\n\n===============\n*/\nstatic void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index )\n{\n\tmstudiotexture_t *ptexture = NULL;\n\n\tif( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME ))\n\t{\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\t\treturn;\n\t}\n\n\tif( ptexturehdr == NULL )\n\t\treturn;\n\n\t// NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable\n\tif( m_fDoRemap )\n\t\tptexture = gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )->ptexture;\n\tif( !ptexture )\n\t\tptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex );        // fallback\n\n\tif( r_lightmap->value && !r_fullbright->value )\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\telse\n\t\tGL_Bind( XASH_TEXTURE0, ptexture[index].index );\n}\n\n/*\n===============\nR_StudioGetTexture\n\nDoesn't changes studio global state at all\n===============\n*/\nmstudiotexture_t * GAME_EXPORT R_StudioGetTexture( cl_entity_t *e )\n{\n\tmstudiotexture_t *ptexture;\n\tstudiohdr_t      *phdr, *thdr;\n\n\tif(( phdr = gEngfuncs.Mod_Extradata( mod_studio, e->model )) == NULL )\n\t\treturn NULL;\n\n\tthdr = m_pStudioHeader;\n\tif( !thdr )\n\t\treturn NULL;\n\n\tif( m_fDoRemap )\n\t\tptexture = gEngfuncs.CL_GetRemapInfoForEntity( e )->ptexture;\n\telse\n\t\tptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex );\n\n\treturn ptexture;\n}\n\nstatic void R_StudioSetRenderamt( int iRenderamt )\n{\n\tif( !RI.currententity )\n\t\treturn;\n\n\tRI.currententity->curstate.renderamt = iRenderamt;\n\ttr.blend = CL_FxBlend( RI.currententity ) / 255.0f;\n}\n\n/*\n===============\nR_StudioSetCullState\n\nsets true for enable backculling (for left-hand viewmodel)\n===============\n*/\nstatic void R_StudioSetCullState( int iCull )\n{\n\t// This function intentionally does nothing\n}\n\n/*\n===============\nR_StudioRenderShadow\n\njust a prefab for render shadow\n===============\n*/\nstatic void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 )\n{\n\tif( !p1 || !p2 || !p3 || !p4 )\n\t\treturn;\n\n\tif( TriSpriteTexture( CL_ModelHandle( iSprite ), 0 ))\n\t{\n\t\tTriRenderMode( kRenderTransAlpha );\n\t\t_TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f );\n\n\t\tTriBegin( TRI_QUADS );\n\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\tTriVertex3fv( p1 );\n\t\tTriTexCoord2f( 0.0f, 1.0f );\n\t\tTriVertex3fv( p2 );\n\t\tTriTexCoord2f( 1.0f, 1.0f );\n\t\tTriVertex3fv( p3 );\n\t\tTriTexCoord2f( 1.0f, 0.0f );\n\t\tTriVertex3fv( p4 );\n\t\tTriEnd();\n\n\t\tTriRenderMode( kRenderNormal );\n\t}\n}\n\n/*\n===============\nR_StudioMeshCompare\n\nSorting opaque entities by model type\n===============\n*/\nstatic int R_StudioMeshCompare( const sortedmesh_t *a, const sortedmesh_t *b )\n{\n\tif( FBitSet( a->flags, STUDIO_NF_ADDITIVE ))\n\t\treturn 1;\n\n\tif( FBitSet( a->flags, STUDIO_NF_MASKED ))\n\t\treturn -1;\n\n\treturn 0;\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t )\n{\n\tfloat *lv;\n\tint   i;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tTriBegin( TRI_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse\n\t\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\n\t\t\tTriTexCoord2f( ptricmds[2] * s, ptricmds[3] * t );\n\t\t\tTriVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t}\n\n\t\tTriEnd();\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms )\n{\n\tfloat *lv;\n\tint   i;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tTriBegin( TRI_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse\n\t\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\tTriTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] ));\n\t\t\tTriVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t}\n\n\t\tTriEnd();\n\t}\n}\n\n/*\n===============\nR_StudioDrawNormalMesh\n\ngeneric path\n===============\n*/\nstatic void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale )\n{\n\tfloat    *lv, *av;\n\tint      i, idx;\n\tqboolean glowShell = ( scale > 0.0f ) ? true : false;\n\tvec3_t   vert;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tTriBegin( TRI_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse\n\t\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tif( glowShell )\n\t\t\t{\n\t\t\t\tcolor24 *clr = &RI.currententity->curstate.rendercolor;\n\n\t\t\t\tidx = g_studio.normaltable[ptricmds[0]];\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tlv = g_studio.norms[ptricmds[0]];\n\t\t\t\tVectorMA( av, scale, lv, vert );\n\t\t\t\tTriColor4ub( clr->r, clr->g, clr->b, 255 );\n\t\t\t\tTriTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tTriVertex3fv( vert );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tidx = ptricmds[1];\n\t\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\t\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\t\tTriTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tTriVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t\t}\n\t\t}\n\n\t\tTriEnd();\n\t}\n}\n\n\n/*\n===============\nR_StudioDrawPoints\n\n===============\n*/\nstatic void R_StudioDrawPoints( void )\n{\n\tint              i, j, k, m_skinnum;\n\tfloat            shellscale = 0.0f;\n\tqboolean         need_sort = false;\n\tbyte             *pvertbone;\n\tbyte             *pnormbone;\n\tvec3_t           *pstudioverts;\n\tvec3_t           *pstudionorms;\n\tmstudiotexture_t *ptexture;\n\tmstudiomesh_t    *pmesh;\n\tshort            *pskinref;\n\tfloat            lv_tmp;\n\n\tif( !m_pStudioHeader )\n\t\treturn;\n\n\tm_skinnum = RI.currententity->curstate.skin;\n\tptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex );\n\tpvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex );\n\tpnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex );\n\n\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex );\n\tpstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex );\n\tpstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex );\n\n\tpskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex );\n\tif( m_skinnum > 0 && m_skinnum < m_pStudioHeader->numskinfamilies )\n\t\tpskinref += ( m_skinnum * m_pStudioHeader->numskinref );\n\n\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 )\n\t{\n\t\tmstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex );\n\t\tmstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex );\n\t\tmatrix3x4           skinMat;\n\n\t\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pvertweight[i], skinMat );\n\t\t\tMatrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.verts[i] );\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\n\t\tfor( i = 0; i < m_pSubModel->numnorms; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pnormweight[i], skinMat );\n\t\t\tMatrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] );\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] );\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\t}\n\n\t// generate shared normals for properly scaling glowing shell\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\tfloat factor = ( 1.0f / 128.0f );\n\t\tshellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor );\n\t\tR_StudioBuildNormalTable();\n\t\tR_StudioGenerateNormals();\n\t}\n\n\tfor( j = k = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tg_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags;\n\n\t\t// fill in sortedmesh info\n\t\tg_studio.meshes[j].flags = g_nFaceFlags;\n\t\tg_studio.meshes[j].mesh = &pmesh[j];\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED | STUDIO_NF_ADDITIVE ))\n\t\t\tneed_sort = true;\n\n\t\tif( RI.currententity->curstate.rendermode == kRenderTransAdd )\n\t\t{\n\t\t\tfor( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )\n\t\t\t{\n\t\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorSet( g_studio.lightvalues[k], tr.blend, tr.blend, tr.blend );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )\n\t\t\t{\n\t\t\t\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ))\n\t\t\t\t\tR_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] );\n\t\t\t\telse\n\t\t\t\t\tR_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms );\n\n\t\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] );\n\t\t\t}\n\t\t}\n\t}\n\n\tif( r_studio_sort_textures.value && need_sort )\n\t{\n\t\t// resort opaque and translucent meshes draw order\n\t\tqsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), (void *)R_StudioMeshCompare );\n\t}\n\n\t// NOTE: rewind normals at start\n\tpstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex );\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tfloat oldblend = tr.blend;\n\t\tshort *ptricmds;\n\t\tfloat s, t;\n\n\t\tpmesh = g_studio.meshes[j].mesh;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex );\n\n\t\tg_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags;\n\n\t\ts = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;\n\t\tt = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\t// pglEnable( GL_ALPHA_TEST );\n\t\t\t// pglAlphaFunc( GL_GREATER, 0.5f );\n\t\t\t// pglDepthMask( GL_TRUE );\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t\ttr.blend = 1.0f;\n\t\t}\n\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ))\n\t\t{\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t{\n\t\t\t\t// pglBlendFunc( GL_ONE, GL_ONE );\n\t\t\t\t// pglDepthMask( GL_FALSE );\n\t\t\t\t// pglEnable( GL_BLEND );\n\t\t\t\tR_AllowFog( false );\n\t\t\t}\n\t\t\t// else pglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\t}\n\n\t\tR_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] );\n\n\t\t{\n\t\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))\n\t\t\t\tR_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );\n\t\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))\n\t\t\t\tR_StudioDrawFloatMesh( ptricmds, pstudionorms );\n\t\t\telse\n\t\t\t\tR_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t );\n\t\t}\n\n\t\tif( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\t// pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\t\t\t// pglDisable( GL_ALPHA_TEST );\n\t\t}\n\t\telse if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t{\n\t\t\t// pglDepthMask( GL_TRUE );\n\t\t\t// pglDisable( GL_BLEND );\n\t\t\tR_AllowFog( true );\n\t\t}\n\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\t\ttr.blend = oldblend;\n\t}\n}\n\n/*\n===============\nR_StudioDrawHulls\n\n===============\n*/\nstatic void R_StudioDrawHulls( void )\n{\n}\n\n/*\n===============\nR_StudioDrawAbsBBox\n\n===============\n*/\nstatic void R_StudioDrawAbsBBox( void )\n{\n\tvec3_t p[8], tmp;\n\tfloat  lv;\n\tint    i;\n\n\t// looks ugly, skip\n\tif( RI.currententity == tr.viewent )\n\t\treturn;\n\n\tif( !R_StudioComputeBBox( p ))\n\t\treturn;\n\n\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\t_TriColor4f( 0.5f, 0.5f, 1.0f, 0.5f );\n\tTriRenderMode( kRenderTransAdd );\n\n\tTriBegin( TRI_QUADS );\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tVectorClear( tmp );\n\t\ttmp[i % 3] = ( i < 3 ) ? 1.0f : -1.0f;\n\t\tR_StudioLighting( &lv, -1, 0, tmp );\n\n\t\tTriBrightness( lv );\n\t\tTriVertex3fv( p[boxpnt[i][0]] );\n\t\tTriVertex3fv( p[boxpnt[i][1]] );\n\t\tTriVertex3fv( p[boxpnt[i][2]] );\n\t\tTriVertex3fv( p[boxpnt[i][3]] );\n\t}\n\tTriEnd();\n\tTriRenderMode( kRenderNormal );\n}\n\n/*\n===============\nR_StudioDrawBones\n\n===============\n*/\nstatic void R_StudioDrawBones( void )\n{\n}\n\nstatic void R_StudioDrawAttachments( void )\n{\n}\n\n/*\n===============\nR_StudioSetRemapColors\n\n===============\n*/\nstatic void R_StudioSetRemapColors( int newTop, int newBottom )\n{\n\tif( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom ))\n\t\tm_fDoRemap = true;\n}\n\nvoid R_StudioResetPlayerModels( void )\n{\n\tmemset( g_studio.player_models, 0, sizeof( g_studio.player_models ));\n}\n\n/*\n===============\nR_StudioSetupPlayerModel\n\n===============\n*/\nstatic model_t *R_StudioSetupPlayerModel( int index )\n{\n\tplayer_info_t  *info = gEngfuncs.pfnPlayerInfo( index );\n\tplayer_model_t *state;\n\n\tif( index < 0 || index >= gp_cl->maxclients )\n\t\treturn NULL;\n\n\tstate = &g_studio.player_models[index];\n\n\t// g-cont: force for \"dev-mode\", non-local games and menu preview\n\tif(( gpGlobals->developer || !ENGINE_GET_PARM( PARM_LOCAL_GAME ) || !RI.drawWorld ) && info->model[0] )\n\t{\n\t\tif( Q_strcmp( state->name, info->model ))\n\t\t{\n\t\t\tQ_strncpy( state->name, info->model, sizeof( state->name ));\n\t\t\tstate->name[sizeof( state->name ) - 1] = 0;\n\n\t\t\tQ_snprintf( state->modelname, sizeof( state->modelname ), \"models/player/%s/%s.mdl\", info->model, info->model );\n\n\t\t\tif( gEngfuncs.fsapi->FileExists( state->modelname, false ))\n\t\t\t\tstate->model = gEngfuncs.Mod_ForName( state->modelname, false, true );\n\t\t\telse\n\t\t\t\tstate->model = NULL;\n\n\t\t\tif( !state->model )\n\t\t\t\tstate->model = RI.currententity->model;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( state->model != RI.currententity->model )\n\t\t\tstate->model = RI.currententity->model;\n\t\tstate->name[0] = 0;\n\t}\n\n\treturn state->model;\n}\n\n/*\n================\nR_GetEntityRenderMode\n\ncheck for texture flags\n================\n*/\nint R_GetEntityRenderMode( cl_entity_t *ent )\n{\n\tint              i, opaque, trans;\n\tmstudiotexture_t *ptexture;\n\tcl_entity_t      *oldent;\n\tmodel_t          *model = NULL;\n\tstudiohdr_t      *phdr;\n\n\toldent = RI.currententity;\n\tRI.currententity = ent;\n\n\tif( ent->player ) // check it for real playermodel\n\t\tmodel = R_StudioSetupPlayerModel( ent->curstate.number - 1 );\n\n\tif( !model )\n\t\tmodel = ent->model;\n\n\tRI.currententity = oldent;\n\n\tif(( phdr = gEngfuncs.Mod_Extradata( mod_studio, model )) == NULL )\n\t{\n\t\tif( R_ModelOpaque( ent->curstate.rendermode ))\n\t\t{\n\t\t\t// forcing to choose right sorting type\n\t\t\tif(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT ))\n\t\t\t\treturn kRenderTransAlpha;\n\t\t}\n\t\treturn ent->curstate.rendermode;\n\t}\n\tptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex );\n\n\tfor( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ )\n\t{\n\t\t// ignore chrome & additive it's just a specular-like effect\n\t\tif( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME ))\n\t\t\ttrans++;\n\t\telse\n\t\t\topaque++;\n\t}\n\n\t// if model is more additive than opaque\n\tif( trans > opaque )\n\t\treturn kRenderTransAdd;\n\treturn ent->curstate.rendermode;\n}\n\n/*\n===============\nR_StudioClientEvents\n\n===============\n*/\nstatic void R_StudioClientEvents( void )\n{\n\tmstudioseqdesc_t *pseqdesc;\n\tmstudioevent_t   *pevent;\n\tcl_entity_t      *e = RI.currententity;\n\tint   i, sequence;\n\tfloat end, start;\n\n\tif( g_studio.frametime == 0.0 )\n\t\treturn; // gamepaused\n\n\t// fill attachments with interpolated origin\n\tif( m_pStudioHeader->numattachments <= 0 )\n\t{\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] );\n\t}\n\n\tif( FBitSet( e->curstate.effects, EF_MUZZLEFLASH ))\n\t{\n\t\tdlight_t *el = gEngfuncs.CL_AllocElight( 0 );\n\n\t\tClearBits( e->curstate.effects, EF_MUZZLEFLASH );\n\t\tVectorCopy( e->attachment[0], el->origin );\n\t\tel->die = gp_cl->time + 0.05f;\n\t\tel->color.r = 255;\n\t\tel->color.g = 192;\n\t\tel->color.b = 64;\n\t\tel->decay = 320;\n\t\tel->radius = 24;\n\t}\n\n\tsequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 );\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + sequence;\n\n\t// no events for this animation\n\tif( pseqdesc->numevents == 0 )\n\t\treturn;\n\n\tend = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\tstart = end - e->curstate.framerate * gp_host->frametime * pseqdesc->fps;\n\tpevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex );\n\n\tif( e->latched.sequencetime == e->curstate.animtime )\n\t{\n\t\tif( !FBitSet( pseqdesc->flags, STUDIO_LOOPING ))\n\t\t\tstart = -0.01f;\n\t}\n\n\tfor( i = 0; i < pseqdesc->numevents; i++ )\n\t{\n\t\t// ignore all non-client-side events\n\t\tif( pevent[i].event < EVENT_CLIENT )\n\t\t\tcontinue;\n\n\t\tif((float)pevent[i].frame > start && pevent[i].frame <= end )\n\t\t\tgEngfuncs.pfnStudioEvent( &pevent[i], e );\n\t}\n}\n\n/*\n===============\nR_StudioGetForceFaceFlags\n\n===============\n*/\nstatic int R_StudioGetForceFaceFlags( void )\n{\n\treturn g_nForceFaceFlags;\n}\n\n/*\n===============\nR_StudioSetForceFaceFlags\n\n===============\n*/\nstatic void R_StudioSetForceFaceFlags( int flags )\n{\n\tg_nForceFaceFlags = flags;\n}\n\n/*\n===============\npfnStudioSetHeader\n\n===============\n*/\nstatic void R_StudioSetHeader( studiohdr_t *pheader )\n{\n\tm_pStudioHeader = pheader;\n\tm_fDoRemap = false;\n}\n\n/*\n===============\nR_StudioSetRenderModel\n\n===============\n*/\nstatic void R_StudioSetRenderModel( model_t *model )\n{\n\tRI.currentmodel = model;\n}\n\n/*\n===============\nR_StudioSetupRenderer\n\n===============\n*/\nstatic void R_StudioSetupRenderer( int rendermode )\n{\n\tstudiohdr_t *phdr = m_pStudioHeader;\n\tint         i;\n\n\tif( rendermode > kRenderTransAdd )\n\t\trendermode = 0;\n\tg_studio.rendermode = bound( 0, rendermode, kRenderTransAdd );\n\n\t// a point to setup local to world transform for boneweighted models\n\tif( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO ))\n\t{\n\t\t// NOTE: extended boneinfo goes immediately after bones\n\t\tmstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t ));\n\n\t\tfor( i = 0; i < phdr->numbones; i++ )\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone );\n\t}\n}\n\n/*\n===============\nR_StudioRestoreRenderer\n\n===============\n*/\nstatic void R_StudioRestoreRenderer( void )\n{\n\tm_fDoRemap = false;\n}\n\n/*\n===============\nR_StudioSetChromeOrigin\n\n===============\n*/\nstatic void R_StudioSetChromeOrigin( void )\n{\n\tVectorCopy( RI.vieworg, g_studio.chrome_origin );\n}\n\n/*\n===============\npfnIsHardware\n\nXash3D is always works in hardware mode\n===============\n*/\nstatic int pfnIsHardware( void )\n{\n\treturn 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D\n}\n\n/*\n===============\nR_StudioDrawPointsShadow\n\n===============\n*/\nstatic void R_StudioDrawPointsShadow( void )\n{\n\tfloat         *av, height;\n\tfloat         vec_x, vec_y;\n\tmstudiomesh_t *pmesh;\n\tvec3_t        point;\n\tint           i, k;\n\n\tif( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW ))\n\t\treturn;\n\n\t// if( glState.stencilEnabled )\n\t// pglEnable( GL_STENCIL_TEST );\n\n\theight = g_studio.lightspot[2] + 1.0f;\n\tvec_x = -g_studio.lightvec[0] * 8.0f;\n\tvec_y = -g_studio.lightvec[1] * 8.0f;\n\n\tfor( k = 0; k < m_pSubModel->nummesh; k++ )\n\t{\n\t\tshort *ptricmds;\n\n\t\tpmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + k;\n\t\tptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex );\n\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\tTriBegin( TRI_TRIANGLE_FAN );\n\t\t\t\ti = -i;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tTriBegin( TRI_TRIANGLE_STRIP );\n\t\t\t}\n\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tpoint[0] = av[0] - ( vec_x * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[1] = av[1] - ( vec_y * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[2] = g_studio.lightspot[2] + 1.0f;\n\n\t\t\t\tTriVertex3fv( point );\n\t\t\t}\n\n\t\t\tTriEnd();\n\t\t}\n\t}\n\n\t// if( glState.stencilEnabled )\n\t// pglDisable( GL_STENCIL_TEST );\n}\n\n/*\n===============\nGL_StudioSetRenderMode\n\nset rendermode for studiomodel\n===============\n*/\nstatic void GL_StudioSetRenderMode( int rendermode )\n{\n\tGL_SetRenderMode( rendermode );\n}\n\n/*\n===============\nGL_StudioDrawShadow\n\ng-cont: don't modify this code it's 100% matched with\noriginal GoldSrc code and used in some mods to enable\nstudio shadows with some asm tricks\n===============\n*/\nstatic void GL_StudioDrawShadow( void )\n{\n}\n\n/*\n====================\nStudioRenderFinal\n\n====================\n*/\nstatic void R_StudioRenderFinal( void )\n{\n\tint i, rendermode;\n\n\trendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode;\n\tR_StudioSetupRenderer( rendermode );\n\n\tif( r_drawentities->value == 2 )\n\t{\n\t\tR_StudioDrawBones();\n\t}\n\telse if( r_drawentities->value == 3 )\n\t{\n\t\tR_StudioDrawHulls();\n\t}\n\telse\n\t{\n\t\tfor( i = 0; i < m_pStudioHeader->numbodyparts; i++ )\n\t\t{\n\t\t\tR_StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel );\n\n\t\t\tGL_StudioSetRenderMode( rendermode );\n\t\t\tR_StudioDrawPoints();\n\t\t\tGL_StudioDrawShadow();\n\t\t}\n\t}\n\n\tif( r_drawentities->value == 4 )\n\t{\n\t\tTriRenderMode( kRenderTransAdd );\n\t\tR_StudioDrawHulls( );\n\t\tTriRenderMode( kRenderNormal );\n\t}\n\n\tif( r_drawentities->value == 5 )\n\t{\n\t\tR_StudioDrawAbsBBox( );\n\t}\n\n\tif( r_drawentities->value == 6 )\n\t{\n\t\tR_StudioDrawAttachments();\n\t}\n\n\tR_StudioRestoreRenderer();\n}\n\n/*\n====================\nStudioRenderModel\n\n====================\n*/\nstatic void R_StudioRenderModel( void )\n{\n\tR_StudioSetChromeOrigin();\n\tR_StudioSetForceFaceFlags( 0 );\n\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\tRI.currententity->curstate.renderfx = kRenderFxNone;\n\n\t\tR_StudioRenderFinal( );\n\n\t\tR_StudioSetForceFaceFlags( STUDIO_NF_CHROME );\n\t\tTriSpriteTexture( R_GetChromeSprite(), 0 );\n\t\tRI.currententity->curstate.renderfx = kRenderFxGlowShell;\n\n\t\tR_StudioRenderFinal( );\n\t}\n\telse\n\t{\n\t\tR_StudioRenderFinal( );\n\t}\n}\n\n/*\n====================\nStudioEstimateGait\n\n====================\n*/\nstatic void R_StudioEstimateGait( entity_state_t *pplayer )\n{\n\tvec3_t est_velocity;\n\tfloat  dt;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tif( dt == 0.0f || m_pPlayerInfo->renderframe == tr.realframecount )\n\t{\n\t\tm_flGaitMovement = 0;\n\t\treturn;\n\t}\n\n\tVectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity );\n\tVectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin );\n\tm_flGaitMovement = VectorLength( est_velocity );\n\n\tif( dt <= 0.0f || m_flGaitMovement / dt < 5.0f )\n\t{\n\t\tm_flGaitMovement = 0.0f;\n\t\test_velocity[0] = 0.0f;\n\t\test_velocity[1] = 0.0f;\n\t}\n\n\tif( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f )\n\t{\n\t\tfloat flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\n\t\tflYawDiff = flYawDiff - (int)( flYawDiff / 360 ) * 360;\n\t\tif( flYawDiff > 180.0f )\n\t\t\tflYawDiff -= 360.0f;\n\t\tif( flYawDiff < -180.0f )\n\t\t\tflYawDiff += 360.0f;\n\n\t\tif( dt < 0.25f )\n\t\t\tflYawDiff *= dt * 4.0f;\n\t\telse\n\t\t\tflYawDiff *= dt;\n\n\t\tm_pPlayerInfo->gaityaw += flYawDiff;\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)( m_pPlayerInfo->gaityaw / 360 ) * 360;\n\n\t\tm_flGaitMovement = 0.0f;\n\t}\n\telse\n\t{\n\t\tm_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F );\n\t\tif( m_pPlayerInfo->gaityaw > 180.0f )\n\t\t\tm_pPlayerInfo->gaityaw = 180.0f;\n\t\tif( m_pPlayerInfo->gaityaw < -180.0f )\n\t\t\tm_pPlayerInfo->gaityaw = -180.0f;\n\t}\n\n}\n\n/*\n====================\nStudioProcessGait\n\n====================\n*/\nstatic void R_StudioProcessGait( entity_state_t *pplayer )\n{\n\tmstudioseqdesc_t *pseqdesc;\n\tint   iBlend;\n\tfloat dt, flYaw; // view direction relative to movement\n\n\tif( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq )\n\t\tRI.currententity->curstate.sequence = 0;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + RI.currententity->curstate.sequence;\n\n\tR_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] );\n\n\tRI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH];\n\tRI.currententity->curstate.blending[0] = iBlend;\n\tRI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0];\n\tRI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0];\n\tR_StudioEstimateGait( pplayer );\n\n\t// calc side to side turning\n\tflYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\tflYaw = flYaw - (int)( flYaw / 360 ) * 360;\n\tif( flYaw < -180.0f )\n\t\tflYaw = flYaw + 360.0f;\n\tif( flYaw > 180.0f )\n\t\tflYaw = flYaw - 360.0f;\n\n\tif( flYaw > 120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw - 180.0f;\n\t}\n\telse if( flYaw < -120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw + 180.0f;\n\t}\n\n\t// adjust torso\n\tRI.currententity->curstate.controller[0] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f );\n\tRI.currententity->curstate.controller[1] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f );\n\tRI.currententity->curstate.controller[2] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f );\n\tRI.currententity->curstate.controller[3] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f );\n\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\tRI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw;\n\tif( RI.currententity->angles[YAW] < -0 )\n\t\tRI.currententity->angles[YAW] += 360.0f;\n\tRI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW];\n\n\tif( pplayer->gaitsequence >= m_pStudioHeader->numseq )\n\t\tpplayer->gaitsequence = 0;\n\n\tpseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + pplayer->gaitsequence;\n\n\t// calc gait frame\n\tif( pseqdesc->linearmovement[0] > 0 )\n\t\tm_pPlayerInfo->gaitframe += ( m_flGaitMovement / pseqdesc->linearmovement[0] ) * pseqdesc->numframes;\n\telse\n\t\tm_pPlayerInfo->gaitframe += pseqdesc->fps * dt;\n\n\t// do modulo\n\tm_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)( m_pPlayerInfo->gaitframe / pseqdesc->numframes ) * pseqdesc->numframes;\n\tif( m_pPlayerInfo->gaitframe < 0 )\n\t\tm_pPlayerInfo->gaitframe += pseqdesc->numframes;\n}\n\n/*\n===============\nR_StudioDrawPlayer\n\n===============\n*/\nstatic int R_StudioDrawPlayer( int flags, entity_state_t *pplayer )\n{\n\tint      m_nPlayerIndex;\n\talight_t lighting;\n\tvec3_t   dir;\n\n\tm_nPlayerIndex = pplayer->number - 1;\n\n\tif( m_nPlayerIndex < 0 || m_nPlayerIndex >= gp_cl->maxclients )\n\t\treturn 0;\n\n\tRI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex );\n\tif( RI.currentmodel == NULL )\n\t\treturn 0;\n\n\tR_StudioSetHeader((studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tif( pplayer->gaitsequence )\n\t{\n\t\tvec3_t orig_angles;\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tVectorCopy( RI.currententity->angles, orig_angles );\n\n\t\tR_StudioProcessGait( pplayer );\n\n\t\tm_pPlayerInfo->gaitsequence = pplayer->gaitsequence;\n\t\tm_pPlayerInfo = NULL;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t\tVectorCopy( orig_angles, RI.currententity->angles );\n\t}\n\telse\n\t{\n\t\tRI.currententity->curstate.controller[0] = 127;\n\t\tRI.currententity->curstate.controller[1] = 127;\n\t\tRI.currententity->curstate.controller[2] = 127;\n\t\tRI.currententity->curstate.controller[3] = 127;\n\t\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\t\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\t\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\t\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\tr_stats.c_studio_models_drawn++;\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\tR_StudioSetupBones( RI.currententity );\n\tR_StudioSaveBones( );\n\n\tm_pPlayerInfo->renderframe = tr.realframecount;\n\tm_pPlayerInfo = NULL;\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index );\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// change body if it's a menu entity\n\t\tif( cl_himodels->value && ( RI.currentmodel != RI.currententity->model || !RI.drawWorld ))\n\t\t{\n\t\t\t// show highest resolution multiplayer model\n\t\t\tRI.currententity->curstate.body = 255;\n\t\t}\n\n\t\tif( !( !gpGlobals->developer && gp_cl->maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model ))\n\t\t\tRI.currententity->curstate.body = 1; // force helmet\n\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\n\t\t// get remap colors\n\t\tg_nTopColor = m_pPlayerInfo->topcolor;\n\t\tg_nBottomColor = m_pPlayerInfo->bottomcolor;\n\n\t\tif( g_nTopColor < 0 )\n\t\t\tg_nTopColor = 0;\n\t\tif( g_nTopColor > 360 )\n\t\t\tg_nTopColor = 360;\n\t\tif( g_nBottomColor < 0 )\n\t\t\tg_nBottomColor = 0;\n\t\tif( g_nBottomColor > 360 )\n\t\t\tg_nBottomColor = 360;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel( );\n\t\tm_pPlayerInfo = NULL;\n\n\t\tif( pplayer->weaponmodel )\n\t\t{\n\t\t\tcl_entity_t saveent = *RI.currententity;\n\t\t\tmodel_t     *pweaponmodel = CL_ModelHandle( pplayer->weaponmodel );\n\n\t\t\tm_pStudioHeader = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, pweaponmodel );\n\n\t\t\tR_StudioMergeBones( RI.currententity, pweaponmodel );\n\t\t\tR_StudioSetupLighting( &lighting );\n\t\t\tR_StudioRenderModel( );\n\t\t\tR_StudioCalcAttachments( );\n\n\t\t\t*RI.currententity = saveent;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\n/*\n===============\nR_StudioDrawModel\n\n===============\n*/\nstatic int R_StudioDrawModel( int flags )\n{\n\talight_t lighting;\n\tvec3_t   dir;\n\n\tif( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer )\n\t{\n\t\tentity_state_t deadplayer;\n\t\tint result;\n\n\t\tif( RI.currententity->curstate.renderamt <= 0\n\t\t    || RI.currententity->curstate.renderamt > gp_cl->maxclients )\n\t\t\treturn 0;\n\n\t\t// get copy of player\n\t\tdeadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 );\n\n\t\t// clear weapon, movement state\n\t\tdeadplayer.number = RI.currententity->curstate.renderamt;\n\t\tdeadplayer.weaponmodel = 0;\n\t\tdeadplayer.gaitsequence = 0;\n\n\t\tdeadplayer.movetype = MOVETYPE_NONE;\n\t\tVectorCopy( RI.currententity->curstate.angles, deadplayer.angles );\n\t\tVectorCopy( RI.currententity->curstate.origin, deadplayer.origin );\n\n\t\tg_studio.interpolate = false;\n\t\tresult = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player\n\t\tg_studio.interpolate = true;\n\n\t\treturn result;\n\t}\n\n\tR_StudioSetHeader((studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tR_StudioSetUpTransform( RI.currententity );\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\tr_stats.c_studio_models_drawn++;\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tif( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW )\n\t\tR_StudioMergeBones( RI.currententity, RI.currentmodel );\n\telse\n\t\tR_StudioSetupBones( RI.currententity );\n\n\tR_StudioSaveBones();\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index );\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\t// get remap colors\n\t\tg_nTopColor = RI.currententity->curstate.colormap & 0xFF;\n\t\tg_nBottomColor = ( RI.currententity->curstate.colormap & 0xFF00 ) >> 8;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel();\n\t}\n\n\treturn 1;\n}\n\n/*\n=================\nR_StudioDrawModelInternal\n=================\n*/\nstatic void R_StudioDrawModelInternal( cl_entity_t *e, int flags )\n{\n\tif( !RI.drawWorld )\n\t{\n\t\tif( e->player )\n\t\t\tR_StudioDrawPlayer( flags, &e->curstate );\n\t\telse\n\t\t\tR_StudioDrawModel( flags );\n\t}\n\telse\n\t{\n\t\t// select the properly method\n\t\tif( e->player )\n\t\t\tpStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));\n\t\telse\n\t\t\tpStudioDraw->StudioDrawModel( flags );\n\t}\n}\n\nstatic cl_entity_t *R_FindParentEntity( cl_entity_t *e, cl_entity_t **entities, uint num_entities )\n{\n\tuint i;\n\n\tfor( i = 0; i < num_entities; i++ )\n\t{\n\t\tif( entities[i]->index == e->curstate.aiment )\n\t\t\treturn entities[i];\n\t}\n\n\treturn NULL;\n}\n\n/*\n=================\nR_DrawStudioModel\n=================\n*/\nvoid R_DrawStudioModel( cl_entity_t *e )\n{\n\tif( FBitSet( RI.params, RP_ENVVIEW ))\n\t\treturn;\n\n\tR_StudioSetupTimings();\n\n\tif( e->player )\n\t{\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS );\n\t}\n\telse if( e->curstate.movetype == MOVETYPE_FOLLOW )\n\t{\n\t\tcl_entity_t *parent = CL_GetEntityByIndex( e->curstate.aiment );\n\n\t\tif( !parent || !parent->model || parent->model->type != mod_studio )\n\t\t\treturn;\n\n\t\tparent = R_FindParentEntity( e, tr.draw_list->solid_entities, tr.draw_list->num_solid_entities );\n\n\t\tif( !parent )\n\t\t\tparent = R_FindParentEntity( e, tr.draw_list->trans_entities, tr.draw_list->num_trans_entities );\n\n\t\tif( parent )\n\t\t{\n\t\t\tRI.currententity = parent;\n\t\t\tR_StudioDrawModelInternal( RI.currententity, 0 );\n\t\t\tVectorCopy( RI.currententity->curstate.origin, e->curstate.origin );\n\t\t\tVectorCopy( RI.currententity->origin, e->origin );\n\t\t\tRI.currententity = e;\n\n\t\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS );\n\t\t}\n\t}\n\telse\n\t{\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS );\n\t}\n}\n\n\n/*\n=================\nR_RunViewmodelEvents\n=================\n*/\nvoid R_RunViewmodelEvents( void )\n{\n\tint    i;\n\tvec3_t simorg;\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\tRI.currententity = tr.viewent;\n\n\tif( !RI.currententity->model || RI.currententity->model->type != mod_studio )\n\t\treturn;\n\n\tR_StudioSetupTimings();\n\n\tVectorCopy( gp_cl->simorg, simorg );\n\tfor( i = 0; i < 4; i++ )\n\t\tVectorCopy( simorg, RI.currententity->attachment[i] );\n\tRI.currentmodel = RI.currententity->model;\n\n\tR_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS );\n}\n\n/*\n=================\nR_GatherPlayerLight\n=================\n*/\nvoid R_GatherPlayerLight( void )\n{\n\tcl_entity_t *view = tr.viewent;\n\tcolorVec    c;\n\n\tc = R_LightPoint( view->origin );\n\tgEngfuncs.SetLocalLightLevel(( c.r + c.g + c.b ) / 3 );\n}\n\n/*\n=================\nR_DrawViewModel\n=================\n*/\nvoid R_DrawViewModel( void )\n{\n\tcl_entity_t *view = tr.viewent;\n\n\tR_GatherPlayerLight();\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\ttr.blend = CL_FxBlend( view ) / 255.0f;\n\tif( !R_ModelOpaque( view->curstate.rendermode ) && tr.blend <= 0.0f )\n\t\treturn; // invisible ?\n\n\tRI.currententity = view;\n\n\tif( !RI.currententity->model )\n\t\treturn;\n\n\t// adjust the depth range to prevent view model from poking into walls\n\t// pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin ));\n\ts_ziscale = (float)0x8000 * (float)0x10000 * 3.0f;\n\tRI.currentmodel = RI.currententity->model;\n\n\t// backface culling for left-handed weapons\n\tif( R_AllowFlipViewModel( RI.currententity ))\n\t{\n\t\ttr.fFlipViewModel = true;\n\t\t// pglFrontFace( GL_CW );\n\t}\n\n\tswitch( RI.currententity->model->type )\n\t{\n\tcase mod_alias:\n\t\t//\tR_DrawAliasModel( RI.currententity );\n\t\tbreak;\n\tcase mod_studio:\n\t\tR_StudioSetupTimings();\n\t\tR_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER );\n\t\tbreak;\n\t}\n\n\t// restore depth range\n\t// pglDepthRange( gldepthmin, gldepthmax );\n\ts_ziscale = (float)0x8000 * (float)0x10000;\n\n\t// backface culling for left-handed weapons\n\tif( R_AllowFlipViewModel( RI.currententity ))\n\t{\n\t\ttr.fFlipViewModel = false;\n\t\t// pglFrontFace( GL_CCW );\n\t}\n}\n\n/*\n====================\nR_StudioLoadTexture\n\nload model texture with unique name\n====================\n*/\nstatic void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture )\n{\n\tsize_t    size;\n\tint       flags = 0;\n\tchar      texname[128], name[128], mdlname[128];\n\ttexture_t *tx = NULL;\n\n\tif( ptexture->flags & STUDIO_NF_NORMALMAP )\n\t\tflags |= ( TF_NORMALMAP );\n\n\t// store some textures for remapping\n\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ) || !Q_strnicmp( ptexture->name, \"remap\", 5 ))\n\t{\n\t\tint  i, size;\n\t\tchar val[6];\n\t\tbyte *pixels;\n\n\t\ti = mod->numtextures;\n\t\tmod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t * ));\n\t\tsize = ptexture->width * ptexture->height + 768;\n\t\ttx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size );\n\t\tmod->textures[i] = tx;\n\n\t\t// store ranges into anim_min, anim_max etc\n\t\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ))\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_Base\", sizeof( tx->name ));\n\t\t\ttx->anim_min = PLATE_HUE_START; // topcolor start\n\t\t\ttx->anim_max = PLATE_HUE_END;   // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\ttx->anim_total = SUIT_HUE_END; // bottomcolor end\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_User\", sizeof( tx->name )); // custom remapped\n\t\t\tQ_strncpy( val, ptexture->name + 7, 4 );\n\t\t\ttx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start\n\t\t\tQ_strncpy( val, ptexture->name + 11, 4 );\n\t\t\ttx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\tQ_strncpy( val, ptexture->name + 15, 4 );\n\t\t\ttx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end\n\t\t}\n\n\t\ttx->width = ptexture->width;\n\t\ttx->height = ptexture->height;\n\n\t\t// the pixels immediately follow the structures\n\t\tpixels = (byte *)phdr + ptexture->index;\n\t\tmemcpy( tx + 1, pixels, size );\n\n\t\tptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image\n\t\tflags |= TF_FORCE_COLOR;\n\n\t\tmod->numtextures++; // done\n\t}\n\n\tQ_strncpy( mdlname, mod->name, sizeof( mdlname ));\n\tCOM_FileBase( ptexture->name, name, sizeof( name ));\n\tCOM_StripExtension( mdlname );\n\n\tif( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS ))\n\t\tSetBits( flags, TF_NOMIPMAP );\n\n\t// NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it\n\t// ptexture->index = (int)((byte *)phdr) + ptexture->index;\n\tgEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index );\n\tsize = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768;\n\n\tif( FBitSet( gp_host->features, ENGINE_LOAD_DELUXEDATA ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED ))\n\t\tflags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing\n\n\t// build the texname\n\tQ_snprintf( texname, sizeof( texname ), \"#%s/%s.mdl\", mdlname, name );\n\tptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags );\n\n\tif( !ptexture->index )\n\t{\n\t\tptexture->index = tr.defaultTexture;\n\t}\n\telse if( tx )\n\t{\n\t\t// duplicate texnum for easy acess\n\t\ttx->gl_texturenum = ptexture->index;\n\t}\n}\n\n/*\n=================\nMod_StudioLoadTextures\n=================\n*/\nvoid GAME_EXPORT Mod_StudioLoadTextures( model_t *mod, void *data )\n{\n\tstudiohdr_t      *phdr = (studiohdr_t *)data;\n\tmstudiotexture_t *ptexture;\n\tint i;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = (mstudiotexture_t *)(((byte *)phdr ) + phdr->textureindex );\n\tif( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS )\n\t{\n\t\tfor( i = 0; i < phdr->numtextures; i++ )\n\t\t\tR_StudioLoadTexture( mod, phdr, &ptexture[i] );\n\t}\n}\n\n/*\n=================\nMod_StudioUnloadTextures\n=================\n*/\nvoid Mod_StudioUnloadTextures( void *data )\n{\n\tstudiohdr_t      *phdr = (studiohdr_t *)data;\n\tmstudiotexture_t *ptexture;\n\tint i;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = (mstudiotexture_t *)(((byte *)phdr ) + phdr->textureindex );\n\n\t// release all textures\n\tfor( i = 0; i < phdr->numtextures; i++ )\n\t{\n\t\tif( ptexture[i].index == tr.defaultTexture )\n\t\t\tcontinue;\n\t\tGL_FreeTexture( ptexture[i].index );\n\t}\n}\n\nstatic model_t *pfnModelHandle( int modelindex )\n{\n\treturn CL_ModelHandle( modelindex );\n}\n\nstatic void *pfnMod_CacheCheck( struct cache_user_s *c )\n{\n\treturn gEngfuncs.Mod_CacheCheck( c );\n}\n\nstatic void *pfnMod_StudioExtradata( model_t *mod )\n{\n\treturn gEngfuncs.Mod_Extradata( mod_studio, mod );\n}\n\nstatic void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu )\n{\n\tgEngfuncs.Mod_LoadCacheFile( path, cu );\n}\n\nstatic cvar_t *pfnGetCvarPointer( const char *name )\n{\n\treturn (cvar_t *)gEngfuncs.pfnGetCvarPointer( name, 0 );\n}\n\nstatic void *pfnMod_Calloc( int number, size_t size )\n{\n\treturn gEngfuncs.Mod_Calloc( number, size );\n}\n\nstatic engine_studio_api_t  gStudioAPI =\n{\n\tpfnMod_Calloc,\n\tpfnMod_CacheCheck,\n\tpfnMod_LoadCacheFile,\n\tpfnMod_ForName,\n\tpfnMod_StudioExtradata,\n\tpfnModelHandle,\n\tpfnGetCurrentEntity,\n\tpfnPlayerInfo,\n\tR_StudioGetPlayerState,\n\tpfnGetViewEntity,\n\tpfnGetEngineTimes,\n\tpfnGetCvarPointer,\n\tpfnGetViewInfo,\n\tR_GetChromeSprite,\n\tpfnGetModelCounters,\n\tpfnGetAliasScale,\n\tpfnStudioGetBoneTransform,\n\tpfnStudioGetLightTransform,\n\tpfnStudioGetAliasTransform,\n\tpfnStudioGetRotationMatrix,\n\tR_StudioSetupModel,\n\tR_StudioCheckBBox,\n\tR_StudioDynamicLight,\n\tR_StudioEntityLight,\n\tR_StudioSetupLighting,\n\tR_StudioDrawPoints,\n\tR_StudioDrawHulls,\n\tR_StudioDrawAbsBBox,\n\tR_StudioDrawBones,\n\t(void *)R_StudioSetupSkin,\n\tR_StudioSetRemapColors,\n\tR_StudioSetupPlayerModel,\n\tR_StudioClientEvents,\n\tR_StudioGetForceFaceFlags,\n\tR_StudioSetForceFaceFlags,\n\t(void *)R_StudioSetHeader,\n\tR_StudioSetRenderModel,\n\tR_StudioSetupRenderer,\n\tR_StudioRestoreRenderer,\n\tR_StudioSetChromeOrigin,\n\tpfnIsHardware,\n\tGL_StudioDrawShadow,\n\tGL_StudioSetRenderMode,\n\tR_StudioSetRenderamt,\n\tR_StudioSetCullState,\n\tR_StudioRenderShadow,\n};\n\nstatic r_studio_interface_t gStudioDraw =\n{\n\tSTUDIO_INTERFACE_VERSION,\n\tR_StudioDrawModel,\n\tR_StudioDrawPlayer,\n};\n\n/*\n===============\nCL_InitStudioAPI\n\nInitialize client studio\n===============\n*/\nvoid GAME_EXPORT CL_InitStudioAPI( void )\n{\n\tpStudioDraw = &gStudioDraw;\n\n\t// trying to grab them from client.dll\n\tcl_righthand = gEngfuncs.pfnGetCvarPointer( \"cl_righthand\", 0 );\n\n\t// Xash will be used internal StudioModelRenderer\n\tif( gEngfuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI ))\n\t\treturn;\n\n\t// NOTE: we always return true even if game interface was not correct\n\t// because we need Draw our StudioModels\n\t// just restore pointer to builtin function\n\tpStudioDraw = &gStudioDraw;\n}\n"
  },
  {
    "path": "ref/soft/r_surf.c",
    "content": "/*\nCopyright (C) 1997-2001 Id Software, Inc.\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n\n*/\n// r_surf.c: surface-related refresh code\n\n#include \"r_local.h\"\n#include \"mod_local.h\"\n\ndrawsurf_t r_drawsurf;\n\nuint       lightleft, sourcesstep, blocksize, sourcetstep;\nuint       lightdelta, lightdeltastep;\nuint       lightright, lightleftstep, lightrightstep, blockdivshift;\nunsigned   blockdivmask;\nvoid       *prowdestbase;\npixel_t    *pbasesource;\nint        surfrowbytes;                        // used by ASM files\nunsigned   *r_lightptr;\nint        r_stepback;\nint        r_lightwidth;\nint        r_numhblocks, r_numvblocks;\npixel_t    *r_source, *r_sourcemax;\n\nvoid R_DrawSurfaceBlock8_mip0( void );\nvoid R_DrawSurfaceBlock8_mip1( void );\nvoid R_DrawSurfaceBlock8_mip2( void );\nvoid R_DrawSurfaceBlock8_mip3( void );\nvoid R_DrawSurfaceBlock8_Generic( void );\nvoid R_DrawSurfaceBlock8_World( void );\n\nstatic float    worldlux_s, worldlux_t;\n\nstatic void     (*surfmiptable[4])( void ) = {\n\tR_DrawSurfaceBlock8_mip0,\n\tR_DrawSurfaceBlock8_mip1,\n\tR_DrawSurfaceBlock8_mip2,\n\tR_DrawSurfaceBlock8_mip3\n};\n\n// void R_BuildLightMap (void);\nextern unsigned blocklights[10240]; // allow some very large lightmaps\n\nfloat           surfscale;\nqboolean        r_cache_thrash;         // set if surface cache is thrashing\n\nint sc_size;\nsurfcache_t     *sc_rover, *sc_base;\n\nstatic int      rtable[MOD_FRAMES][MOD_FRAMES];\n\nstatic void R_BuildLightMap( void );\n/*\n===============\nR_AddDynamicLights\n===============\n*/\nstatic void R_AddDynamicLights( const msurface_t *surf )\n{\n\tconst mextrasurf_t *info = surf->info;\n\tint        lnum, smax, tmax;\n\tint        sample_frac = 1.0;\n\tfloat      sample_size;\n\tmtexinfo_t *tex;\n\n\t// no dlighted surfaces here\n\tif( !surf->dlightbits )\n\t\treturn;\n\n\tsample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\ttex = surf->texinfo;\n\n\tif( FBitSet( tex->flags, TEX_WORLD_LUXELS ))\n\t{\n\t\tif( surf->texinfo->faceinfo )\n\t\t\tsample_frac = surf->texinfo->faceinfo->texture_step;\n\t\telse if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))\n\t\t\tsample_frac = LM_SAMPLE_EXTRASIZE;\n\t\telse\n\t\t\tsample_frac = LM_SAMPLE_SIZE;\n\t}\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tdlight_t *dl;\n\t\tvec3_t   impact, origin_l;\n\t\tfloat    dist, rad, minlight;\n\t\tfloat    sl, tl;\n\t\tint      t, monolight;\n\n\t\tif( !FBitSet( surf->dlightbits, BIT( lnum )))\n\t\t\tcontinue; // not lit by this light\n\n\t\tdl = &tr.dlights[lnum];\n\n\t\t// transform light origin to local bmodel space\n\t\tif( !tr.modelviewIdentity )\n\t\t\tMatrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l );\n\t\telse\n\t\t\tVectorCopy( dl->origin, origin_l );\n\n\t\trad = dl->radius;\n\t\tdist = PlaneDiff( origin_l, surf->plane );\n\t\trad -= fabs( dist );\n\n\t\t// rad is now the highest intensity on the plane\n\t\tminlight = dl->minlight;\n\t\tif( rad < minlight )\n\t\t\tcontinue;\n\n\t\tminlight = rad - minlight;\n\n\t\tif( surf->plane->type < 3 )\n\t\t{\n\t\t\tVectorCopy( origin_l, impact );\n\t\t\timpact[surf->plane->type] -= dist;\n\t\t}\n\t\telse\n\t\t\tVectorMA( origin_l, -dist, surf->plane->normal, impact );\n\n\t\tsl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\t\ttl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\n\t\tmonolight = LightToTexGamma(( dl->color.r + dl->color.g + dl->color.b ) / 3 * 4 ) * 3;\n\n\t\tfor( t = 0; t < tmax; t++ )\n\t\t{\n\t\t\tint td = ( tl - sample_size * t ) * sample_frac;\n\t\t\tint s;\n\n\t\t\tif( td < 0 )\n\t\t\t\ttd = -td;\n\n\t\t\tfor( s = 0; s < smax; s++ )\n\t\t\t{\n\t\t\t\tint   sd = ( sl - sample_size * s ) * sample_frac;\n\t\t\t\tfloat dist;\n\n\t\t\t\tif( sd < 0 )\n\t\t\t\t\tsd = -sd;\n\n\t\t\t\tif( sd > td )\n\t\t\t\t\tdist = sd + ( td >> 1 );\n\t\t\t\telse\n\t\t\t\t\tdist = td + ( sd >> 1 );\n\n\t\t\t\tif( dist < minlight )\n\t\t\t\t{\n\t\t\t\t\tblocklights[( s + ( t * smax ))] += ((int)(( rad - dist ) * 256 ) * monolight ) / 256;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n/*\n=================\nR_BuildLightmap\n\nCombine and scale multiple lightmaps into the floating\nformat in r_blocklights\n=================\n*/\nstatic void R_BuildLightMap( void )\n{\n\tint                map, t, i;\n\tconst msurface_t   *surf = r_drawsurf.surf;\n\tconst mextrasurf_t *info = surf->info;\n\tconst int          sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );\n\tint                smax = ( info->lightextents[0] / sample_size ) + 1;\n\tint                tmax = ( info->lightextents[1] / sample_size ) + 1;\n\tint                size = smax * tmax;\n\n\tif( FBitSet( surf->flags, SURF_CONVEYOR ))\n\t{\n\t\tsmax = ( info->lightextents[0] * 3 / sample_size ) + 1;\n\t\tsize = smax * tmax;\n\t\tmemset( blocklights, 0xff, sizeof( uint ) * size );\n\t\treturn;\n\t}\n\n\tmemset( blocklights, 0, sizeof( uint ) * size );\n\n\t// add all the lightmaps\n\tfor( map = 0; map < MAXLIGHTMAPS && surf->samples; map++ )\n\t{\n\t\tconst color24 *lm = &surf->samples[map * size];\n\t\tuint          scale;\n\t\tint           i;\n\n\t\tif( surf->styles[map] >= 255 )\n\t\t\tbreak;\n\n\t\tscale = tr.lightstylevalue[surf->styles[map]];\n\n\t\tfor( i = 0; i < size; i++ )\n\t\t\tblocklights[i] += ( lm[i].r + lm[i].g + lm[i].b ) * scale;\n\t}\n\n\t// add all the dynamic lights\n\tif( surf->dlightframe == tr.framecount )\n\t\tR_AddDynamicLights( surf );\n\n\t// bound, invert, and shift\n\tfor( i = 0; i < size; i++ )\n\t{\n\t\tif( blocklights[i] < 65280 )\n\t\t\tt = LightToTexGamma( blocklights[i] >> 6 ) << 6;\n\t\telse\n\t\t\tt = (int)blocklights[i];\n\n\t\tt = bound( 0, t, 65535 * 3 );\n\t\tt = t / 2048 / 3; // (255*256 - t) >> (8 - VID_CBITS);\n\n\t\t// if (t < (1 << 6))\n\t\t// t = (1 << 6);\n\t\tt = t << 8;\n\n\t\tblocklights[i] = t;\n\t}\n}\n\nvoid GL_InitRandomTable( void )\n{\n\tint tu, tv;\n\n\tfor( tu = 0; tu < MOD_FRAMES; tu++ )\n\t{\n\t\tfor( tv = 0; tv < MOD_FRAMES; tv++ )\n\t\t{\n\t\t\trtable[tu][tv] = gEngfuncs.COM_RandomLong( 0, 0x7FFF );\n\t\t}\n\t}\n\n\tgEngfuncs.COM_SetRandomSeed( 0 );\n}\n\n/*\n===============\nR_TextureAnim\n\nReturns the proper texture for a given time and base texture, do not process random tiling\n===============\n*/\nstatic texture_t *R_TextureAnim( texture_t *b )\n{\n\ttexture_t *base = b;\n\tint       count, reletive;\n\n\tif( RI.currententity->curstate.frame )\n\t{\n\t\tif( base->alternate_anims )\n\t\t\tbase = base->alternate_anims;\n\t}\n\n\tif( !base->anim_total )\n\t\treturn base;\n\tif( base->name[0] == '-' )\n\t{\n\t\treturn b; // already tiled\n\t}\n\telse\n\t{\n\t\tint speed;\n\n\t\t// Quake1 textures uses 10 frames per second\n\t\tif( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))\n\t\t\tspeed = 10;\n\t\telse\n\t\t\tspeed = 20;\n\n\t\treletive = (int)( gp_cl->time * speed ) % base->anim_total;\n\t}\n\n\n\tcount = 0;\n\n\twhile( base->anim_min > reletive || base->anim_max <= reletive )\n\t{\n\t\tbase = base->anim_next;\n\n\t\tif( !base || ++count > MOD_FRAMES )\n\t\t\treturn b;\n\t}\n\n\treturn base;\n}\n\n/*\n===============\nR_TextureAnimation\n\nReturns the proper texture for a given time and surface\n===============\n*/\nstatic texture_t *R_TextureAnimation( msurface_t *s )\n{\n\ttexture_t *base = s->texinfo->texture;\n\tint       count, reletive;\n\n\tif( RI.currententity && RI.currententity->curstate.frame )\n\t{\n\t\tif( base->alternate_anims )\n\t\t\tbase = base->alternate_anims;\n\t}\n\n\tif( !base->anim_total )\n\t\treturn base;\n\n\tif( base->name[0] == '-' )\n\t{\n\t\tint tx = (int)(( s->texturemins[0] + ( base->width << 16 )) / base->width ) % MOD_FRAMES;\n\t\tint ty = (int)(( s->texturemins[1] + ( base->height << 16 )) / base->height ) % MOD_FRAMES;\n\n\t\treletive = rtable[tx][ty] % base->anim_total;\n\t}\n\telse\n\t{\n\t\tint speed;\n\n\t\t// Quake1 textures uses 10 frames per second\n\t\tif( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))\n\t\t\tspeed = 10;\n\t\telse\n\t\t\tspeed = 20;\n\n\t\treletive = (int)( gp_cl->time * speed ) % base->anim_total;\n\t}\n\n\tcount = 0;\n\n\twhile( base->anim_min > reletive || base->anim_max <= reletive )\n\t{\n\t\tbase = base->anim_next;\n\n\t\tif( !base || ++count > MOD_FRAMES )\n\t\t\treturn s->texinfo->texture;\n\t}\n\n\treturn base;\n}\n\n/*\n===============\nR_DrawSurface\n===============\n*/\nvoid R_DrawSurface( void )\n{\n\tpixel_t *basetptr;\n\tint     smax, tmax, twidth;\n\tint     u;\n\tint     soffset, basetoffset, texwidth;\n\tint     horzblockstep;\n\tpixel_t *pcolumndest;\n\tvoid    (*pblockdrawer)( void );\n\timage_t *mt;\n\tuint    sample_size, sample_bits, sample_pot;\n\n\tsurfrowbytes = r_drawsurf.rowbytes;\n\n\tsample_size = LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf );\n\tif( sample_size == 16 )\n\t\tsample_bits = 4, sample_pot = sample_size;\n\telse\n\t{\n\t\tsample_bits = tr.sample_bits;\n\n\t\tif( sample_bits == -1 )\n\t\t{\n\t\t\tsample_bits = 0;\n\t\t\tfor( sample_pot = 1; sample_pot < sample_size; sample_pot <<= 1, sample_bits++ )\n\t\t\t\t;\n\t\t}\n\t\telse\n\t\t\tsample_pot = 1 << sample_bits;\n\t}\n\tmt = r_drawsurf.image;\n\n\tr_source = mt->pixels[r_drawsurf.surfmip];\n\n// the fractional light values should range from 0 to (VID_GRADES - 1) << 16\n// from a source range of 0 - 255\n\n\ttexwidth = mt->width >> r_drawsurf.surfmip;\n\n\tblocksize = sample_pot >> r_drawsurf.surfmip;\n\tblockdivshift = sample_bits - r_drawsurf.surfmip;\n\tblockdivmask = ( 1 << blockdivshift ) - 1;\n\n\tif( sample_size == 16 )\n\t\tr_lightwidth = ( r_drawsurf.surf->info->lightextents[0] >> 4 ) + 1;\n\telse\n\t\tr_lightwidth = ( r_drawsurf.surf->info->lightextents[0] / sample_size ) + 1;\n\n\tr_numhblocks = r_drawsurf.surfwidth >> blockdivshift;\n\tr_numvblocks = r_drawsurf.surfheight >> blockdivshift;\n\n\n// ==============================\n\n\tif( sample_size == 16 )\n\t\tpblockdrawer = surfmiptable[r_drawsurf.surfmip];\n\telse\n\t\tpblockdrawer = R_DrawSurfaceBlock8_Generic;\n\n// TODO: only needs to be set when there is a display settings change\n\thorzblockstep = blocksize;\n\n\tsmax = mt->width >> r_drawsurf.surfmip;\n\ttwidth = texwidth;\n\ttmax = mt->height >> r_drawsurf.surfmip;\n\tsourcetstep = texwidth;\n\tr_stepback = tmax * twidth;\n\n\tr_sourcemax = r_source + ( tmax * smax );\n\n\t// glitchy and slow way to draw some lightmap\n\tif( r_drawsurf.surf->texinfo->flags & TEX_WORLD_LUXELS )\n\t{\n\t\tworldlux_s = r_drawsurf.surf->extents[0] / r_drawsurf.surf->info->lightextents[0];\n\t\tworldlux_t = r_drawsurf.surf->extents[1] / r_drawsurf.surf->info->lightextents[1];\n\t\tif( worldlux_s == 0 )\n\t\t\tworldlux_s = 1;\n\t\tif( worldlux_t == 0 )\n\t\t\tworldlux_t = 1;\n\n\t\tsoffset = r_drawsurf.surf->texturemins[0];\n\t\tbasetoffset = r_drawsurf.surf->texturemins[1];\n\t\t// soffset =  r_drawsurf.surf->info->lightmapmins[0] * worldlux_s;\n\t\t// basetoffset = r_drawsurf.surf->info->lightmapmins[1] * worldlux_t;\n\t\t// << 16 components are to guarantee positive values for %\n\t\tsoffset = (( soffset >> r_drawsurf.surfmip ) + ( smax << 16 )) % smax;\n\t\tbasetptr = &r_source[(((( basetoffset >> r_drawsurf.surfmip )\n\t\t\t\t\t+ ( tmax << 16 )) % tmax ) * twidth )];\n\n\t\tpcolumndest = r_drawsurf.surfdat;\n\n\t\tfor( u = 0; u < r_numhblocks; u++ )\n\t\t{\n\t\t\tr_lightptr = blocklights + (int)( u / ( worldlux_s + 0.5f ));\n\n\t\t\tprowdestbase = pcolumndest;\n\n\t\t\tpbasesource = basetptr + soffset;\n\n\t\t\tR_DrawSurfaceBlock8_World();\n\n\t\t\tsoffset = soffset + blocksize;\n\t\t\tif( soffset >= smax )\n\t\t\t\tsoffset = 0;\n\n\t\t\tpcolumndest += horzblockstep;\n\t\t}\n\t\treturn;\n\t}\n\n\tsoffset = r_drawsurf.surf->info->lightmapmins[0];\n\tbasetoffset = r_drawsurf.surf->info->lightmapmins[1];\n\n// << 16 components are to guarantee positive values for %\n\tsoffset = (( soffset >> r_drawsurf.surfmip ) + ( smax << 16 )) % smax;\n\tbasetptr = &r_source[(((( basetoffset >> r_drawsurf.surfmip )\n\t\t\t\t+ ( tmax << 16 )) % tmax ) * twidth )];\n\n\tpcolumndest = r_drawsurf.surfdat;\n\n\tfor( u = 0; u < r_numhblocks; u++ )\n\t{\n\t\tr_lightptr = blocklights + u;\n\n\t\tprowdestbase = pcolumndest;\n\n\t\tpbasesource = basetptr + soffset;\n\n\t\t( *pblockdrawer )();\n\n\t\tsoffset = soffset + blocksize;\n\t\tif( soffset >= smax )\n\t\t\tsoffset = 0;\n\n\t\tpcolumndest += horzblockstep;\n\t}\n\t// test what if we have very slow cache building\n\t// usleep(10000);\n}\n\n\n// =============================================================================\n\n#define BLEND_LM( pix, light ) vid.colormap[( pix >> 3 ) | (( light & 0x1f00 ) << 5 )] | ( pix & 7 );\n\n/*\n================\nR_DrawSurfaceBlock8_World\n\nDoes not draw lightmap correclty, but scale it correctly. Better than nothing\n================\n*/\nvoid R_DrawSurfaceBlock8_World( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\tint     lightpos = 0;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth];\n\t\tlightright = r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth + 1];\n\t\tlightpos += r_lightwidth / worldlux_s;\n\t\tlightleftstep = ( r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth] - lightleft ) >> ( 4 - r_drawsurf.surfmip );\n\t\tlightrightstep = ( r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth + 1] - lightright ) >> ( 4 - r_drawsurf.surfmip );\n\n\t\tfor( i = 0; i < blocksize; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> ( 4 - r_drawsurf.surfmip );\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = blocksize - 1; b >= 0; b-- )\n\t\t\t{\n\t\t\t\t// pix = psource[(uint)(b * worldlux_s)];\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );\n\t\t\t\tif( pix == TRANSPARENT_COLOR )\n\t\t\t\t\tprowdest[b] = TRANSPARENT_COLOR;\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n\n/*\n================\nR_DrawSurfaceBlock8_Generic\n================\n*/\nvoid R_DrawSurfaceBlock8_Generic( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[0];\n\t\tlightright = r_lightptr[1];\n\t\tr_lightptr += r_lightwidth;\n\t\tlightleftstep = ( r_lightptr[0] - lightleft ) >> ( 4 - r_drawsurf.surfmip );\n\t\tlightrightstep = ( r_lightptr[1] - lightright ) >> ( 4 - r_drawsurf.surfmip );\n\n\t\tfor( i = 0; i < blocksize; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> ( 4 - r_drawsurf.surfmip );\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = blocksize - 1; b >= 0; b-- )\n\t\t\t{\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );\n\t\t\t\tif( pix == TRANSPARENT_COLOR )\n\t\t\t\t\tprowdest[b] = TRANSPARENT_COLOR;\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n\n/*\n================\nR_DrawSurfaceBlock8_mip0\n================\n*/\nvoid R_DrawSurfaceBlock8_mip0( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[0];\n\t\tlightright = r_lightptr[1];\n\t\tr_lightptr += r_lightwidth;\n\t\tlightleftstep = ( r_lightptr[0] - lightleft ) >> 4;\n\t\tlightrightstep = ( r_lightptr[1] - lightright ) >> 4;\n\n\t\tfor( i = 0; i < 16; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> 4;\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = 15; b >= 0; b-- )\n\t\t\t{\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );\n\t\t\t\tif( pix == TRANSPARENT_COLOR )\n\t\t\t\t\tprowdest[b] = TRANSPARENT_COLOR;\n\n\t\t\t\t// pix;\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n\n/*\n================\nR_DrawSurfaceBlock8_mip1\n================\n*/\nvoid R_DrawSurfaceBlock8_mip1( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[0];\n\t\tlightright = r_lightptr[1];\n\t\tr_lightptr += r_lightwidth;\n\t\tlightleftstep = ( r_lightptr[0] - lightleft ) >> 3;\n\t\tlightrightstep = ( r_lightptr[1] - lightright ) >> 3;\n\n\t\tfor( i = 0; i < 8; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> 3;\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = 7; b >= 0; b-- )\n\t\t\t{\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n\n/*\n================\nR_DrawSurfaceBlock8_mip2\n================\n*/\nvoid R_DrawSurfaceBlock8_mip2( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[0];\n\t\tlightright = r_lightptr[1];\n\t\tr_lightptr += r_lightwidth;\n\t\tlightleftstep = ( r_lightptr[0] - lightleft ) >> 2;\n\t\tlightrightstep = ( r_lightptr[1] - lightright ) >> 2;\n\n\t\tfor( i = 0; i < 4; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> 2;\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = 3; b >= 0; b-- )\n\t\t\t{\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );;\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n\n/*\n================\nR_DrawSurfaceBlock8_mip3\n================\n*/\nvoid R_DrawSurfaceBlock8_mip3( void )\n{\n\tint     v, i, b;\n\tuint    lightstep, lighttemp, light;\n\tpixel_t pix, *psource, *prowdest;\n\n\tpsource = pbasesource;\n\tprowdest = prowdestbase;\n\n\tfor( v = 0; v < r_numvblocks; v++ )\n\t{\n\t\t// FIXME: make these locals?\n\t\t// FIXME: use delta rather than both right and left, like ASM?\n\t\tlightleft = r_lightptr[0];\n\t\tlightright = r_lightptr[1];\n\t\tr_lightptr += r_lightwidth;\n\t\tlightleftstep = ( r_lightptr[0] - lightleft ) >> 1;\n\t\tlightrightstep = ( r_lightptr[1] - lightright ) >> 1;\n\n\t\tfor( i = 0; i < 2; i++ )\n\t\t{\n\t\t\tlighttemp = lightleft - lightright;\n\t\t\tlightstep = lighttemp >> 1;\n\n\t\t\tlight = lightright;\n\n\t\t\tfor( b = 1; b >= 0; b-- )\n\t\t\t{\n\t\t\t\tpix = psource[b];\n\t\t\t\tprowdest[b] = BLEND_LM( pix, light );;\n\t\t\t\t// ((unsigned char *)vid.colormap)\n\t\t\t\t// [(light & 0xFF00) + pix];\n\t\t\t\tlight += lightstep;\n\t\t\t}\n\n\t\t\tpsource += sourcetstep;\n\t\t\tlightright += lightrightstep;\n\t\t\tlightleft += lightleftstep;\n\t\t\tprowdest += surfrowbytes;\n\t\t}\n\n\t\tif( psource >= r_sourcemax )\n\t\t\tpsource -= r_stepback;\n\t}\n}\n\n// ============================================================================\n/*\n================\nR_InitCaches\n\n================\n*/\nvoid R_InitCaches( void )\n{\n\tint size;\n\tint pix;\n\n\t// calculate size to allocate\n\tif( sw_surfcacheoverride.value )\n\t{\n\t\tsize = sw_surfcacheoverride.value;\n\t}\n\telse\n\t{\n\t\tsize = SURFCACHE_SIZE_AT_320X240 * 2;\n\n\t\tpix = vid.width * vid.height * 2;\n\t\tif( pix > 64000 )\n\t\t\tsize += ( pix - 64000 ) * 3;\n\t}\n\n\t// round up to page size\n\tsize = ( size + 8191 ) & ~8191;\n\n\tgEngfuncs.Con_Printf( \"%s surface cache\\n\", Q_memprint( size ));\n\n\tsc_size = size;\n\tif( sc_base )\n\t{\n\t\tD_FlushCaches(  );\n\t\tMem_Free( sc_base );\n\t}\n\tsc_base = (surfcache_t *)Mem_Calloc( r_temppool, size );\n\tsc_rover = sc_base;\n\n\tsc_base->next = NULL;\n\tsc_base->owner = NULL;\n\tsc_base->size = sc_size;\n}\n\n\n/*\n==================\nD_FlushCaches\n==================\n*/\nvoid D_FlushCaches( void )\n{\n\tsurfcache_t *c;\n\n\t// if newmap, surfaces already freed\n\tif( !tr.map_unload )\n\t{\n\t\tfor( c = sc_base; c; c = c->next )\n\t\t{\n\t\t\tif( c->owner )\n\t\t\t\t*c->owner = NULL;\n\t\t}\n\t}\n\n\tsc_rover = sc_base;\n\tsc_base->next = NULL;\n\tsc_base->owner = NULL;\n\tsc_base->size = sc_size;\n}\n\n/*\n=================\nD_SCAlloc\n=================\n*/\nstatic surfcache_t     *D_SCAlloc( int width, int size )\n{\n\tsurfcache_t *new;\n\tqboolean    wrapped_this_time;\n\n\tif(( width < 0 ))// || (width > 256))\n\t\tgEngfuncs.Host_Error( \"%s: bad cache width %d\\n\", __func__, width );\n\n\tif(( size <= 0 ) || ( size > 0x10000000 ))\n\t\tgEngfuncs.Host_Error( \"%s: bad cache size %d\\n\", __func__, size );\n\n\tsize = (int)&((surfcache_t *)0 )->data[size];\n\tsize = ( size + 3 ) & ~3;\n\tif( size > sc_size )\n\t\tgEngfuncs.Host_Error( \"%s: %i > cache size of %i\", __func__, size, sc_size );\n\n// if there is not size bytes after the rover, reset to the start\n\twrapped_this_time = false;\n\n\tif( !sc_rover || (byte *)sc_rover - (byte *)sc_base > sc_size - size )\n\t{\n\t\tif( sc_rover )\n\t\t{\n\t\t\twrapped_this_time = true;\n\t\t}\n\t\tsc_rover = sc_base;\n\t}\n\n// colect and free surfcache_t blocks until the rover block is large enough\n\tnew = sc_rover;\n\tif( sc_rover->owner )\n\t\t*sc_rover->owner = NULL;\n\n\twhile( new->size < size )\n\t{\n\t\t// free another\n\t\tsc_rover = sc_rover->next;\n\t\tif( !sc_rover )\n\t\t\tgEngfuncs.Host_Error( \"%s: hit the end of memory\", __func__ );\n\t\tif( sc_rover->owner )\n\t\t\t*sc_rover->owner = NULL;\n\n\t\tnew->size += sc_rover->size;\n\t\tnew->next = sc_rover->next;\n\t}\n\n// create a fragment out of any leftovers\n\tif( new->size - size > 256 )\n\t{\n\t\tsc_rover = (surfcache_t *)((byte *)new + size );\n\t\tsc_rover->size = new->size - size;\n\t\tsc_rover->next = new->next;\n\t\tsc_rover->width = 0;\n\t\tsc_rover->owner = NULL;\n\t\tnew->next = sc_rover;\n\t\tnew->size = size;\n\t}\n\telse\n\t\tsc_rover = new->next;\n\n\tnew->width = width;\n// DEBUG\n\tif( width > 0 )\n\t\tnew->height = ( size - sizeof( *new ) + sizeof( new->data )) / width;\n\n\tnew->owner = NULL; // should be set properly after return\n\n\tif( d_roverwrapped )\n\t{\n\t\tif( wrapped_this_time || ( sc_rover >= d_initial_rover ))\n\t\t\tr_cache_thrash = true;\n\t}\n\telse if( wrapped_this_time )\n\t{\n\t\td_roverwrapped = true;\n\t}\n\n\treturn new;\n}\n\n// =============================================================================\nstatic void R_DrawSurfaceDecals( void )\n{\n\tmsurface_t *fa = r_drawsurf.surf;\n\tdecal_t    *p;\n\n\tfor( p = fa->pdecals; p; p = p->pnext )\n\t{\n\t\tpixel_t      *dest, *source;\n\t\tvec4_t       textureU, textureV;\n\t\timage_t      *tex = R_GetTexture( p->texture );\n\t\tint          s1 = 0, t1 = 0, s2 = tex->width, t2 = tex->height;\n\t\tunsigned int height;\n\t\tunsigned int f, fstep;\n\t\tint          skip;\n\t\tpixel_t      *buffer;\n\t\tqboolean     transparent;\n\t\tint          x, y, u, v, sv, w, h;\n\t\tvec3_t       basis[3];\n\n\t\tVector4Copy( fa->texinfo->vecs[0], textureU );\n\t\tVector4Copy( fa->texinfo->vecs[1], textureV );\n\n\t\tR_DecalComputeBasis( fa, 0, basis );\n\n\t\tw = fabs( tex->width * DotProduct( textureU, basis[0] ))\n\t\t    + fabs( tex->height * DotProduct( textureU, basis[1] ));\n\t\th = fabs( tex->width * DotProduct( textureV, basis[0] ))\n\t\t    + fabs( tex->height * DotProduct( textureV, basis[1] ));\n\n\t\t// project decal center into the texture space of the surface\n\t\tx = DotProduct( p->position, textureU ) + textureU[3] - fa->texturemins[0] - w / 2;\n\t\ty = DotProduct( p->position, textureV ) + textureV[3] - fa->texturemins[1] - h / 2;\n\n\t\tx = x >> r_drawsurf.surfmip;\n\t\ty = y >> r_drawsurf.surfmip;\n\t\tw = w >> r_drawsurf.surfmip;\n\t\th = h >> r_drawsurf.surfmip;\n\n\t\tif( w < 1 || h < 1 )\n\t\t\tcontinue;\n\n\t\tif( x < 0 )\n\t\t{\n\t\t\ts1 += ( -x ) * ( s2 - s1 ) / w;\n\t\t\tx = 0;\n\t\t}\n\t\tif( x + w > r_drawsurf.surfwidth )\n\t\t{\n\t\t\ts2 -= ( x + w - r_drawsurf.surfwidth ) * ( s2 - s1 ) / w;\n\t\t\tw = r_drawsurf.surfwidth - x;\n\t\t}\n\t\tif( y + h > r_drawsurf.surfheight )\n\t\t{\n\t\t\tt2 -= ( y + h - r_drawsurf.surfheight ) * ( t2 - t1 ) / h;\n\t\t\th = r_drawsurf.surfheight - y;\n\t\t}\n\n\t\tif( s1 < 0 )\n\t\t\ts1 = 0;\n\t\tif( t1 < 0 )\n\t\t\tt1 = 0;\n\n\t\tif( s2 > tex->width )\n\t\t\ts2 = tex->width;\n\t\tif( t2 > tex->height )\n\t\t\tt2 = tex->height;\n\n\t\tif( !tex->pixels[0] || s1 >= s2 || t1 >= t2 || !w )\n\t\t\tcontinue;\n\n\t\tif( tex->alpha_pixels )\n\t\t{\n\t\t\tbuffer = tex->alpha_pixels;\n\t\t\ttransparent = true;\n\t\t}\n\t\telse\n\t\t\tbuffer = tex->pixels[0];\n\n\t\theight = h;\n\t\tif( y < 0 )\n\t\t{\n\t\t\tskip = -y;\n\t\t\theight += y;\n\t\t\ty = 0;\n\t\t}\n\t\telse\n\t\t\tskip = 0;\n\n\t\tdest = ((pixel_t *)r_drawsurf.surfdat ) + y * r_drawsurf.rowbytes + x;\n\n\t\tfor( v = 0; v < height; v++ )\n\t\t{\n\t\t\t// int alpha1 = vid.alpha;\n\t\t\tsv = ( skip + v ) * ( t2 - t1 ) / h + t1;\n\t\t\tsource = buffer + sv * tex->width + s1;\n\n\t\t\t{\n\t\t\t\tf = 0;\n\t\t\t\tfstep = ( s2 - s1 ) * 0x10000 / w;\n\t\t\t\tif( w == s2 - s1 )\n\t\t\t\t\tfstep = 0x10000;\n\n\t\t\t\tfor( u = 0; u < w; u++ )\n\t\t\t\t{\n\t\t\t\t\tpixel_t src = source[f >> 16];\n\t\t\t\t\tint     alpha = 7;\n\t\t\t\t\tf += fstep;\n\n\t\t\t\t\tif( transparent )\n\t\t\t\t\t{\n\t\t\t\t\t\talpha &= src >> ( 16 - 3 );\n\t\t\t\t\t\tsrc = src << 3;\n\t\t\t\t\t}\n\n\t\t\t\t\tif( alpha <= 0 )\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\tif( alpha < 7 )        // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tpixel_t screen = dest[u];         //  | 0xff & screen & src ;\n\t\t\t\t\t\tif( screen == TRANSPARENT_COLOR )\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\tdest[u] = BLEND_ALPHA( alpha, src, screen );\n\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t\tdest[u] = src;\n\n\t\t\t\t}\n\t\t\t}\n\t\t\tdest += r_drawsurf.rowbytes;\n\t\t}\n\t}\n\n}\n\n/*\n================\nD_CacheSurface\n================\n*/\nsurfcache_t *D_CacheSurface( msurface_t *surface, int miplevel )\n{\n\tsurfcache_t *cache;\n\tint         maps;\n//\n// if the surface is animating or flashing, flush the cache\n//\n\tr_drawsurf.image = R_GetTexture( R_TextureAnimation( surface )->gl_texturenum );\n\n\t// does not support conveyors with world luxels now\n\tif( surface->texinfo->flags & TEX_WORLD_LUXELS )\n\t\tsurface->flags &= ~SURF_CONVEYOR;\n\n\tif( surface->flags & SURF_CONVEYOR )\n\t{\n\t\tif( miplevel >= 1 )\n\t\t{\n\t\t\tsurface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) * 2;\n\t\t\tsurface->info->lightmapmins[0] = -surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsurface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf );\n\t\t\tsurface->info->lightmapmins[0] = -surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) / 2;\n\t\t}\n\t}\n\t/// todo: port this\n\t// r_drawsurf.lightadj[0] = r_newrefdef.lightstyles[surface->styles[0]].white*128;\n\t// r_drawsurf.lightadj[1] = r_newrefdef.lightstyles[surface->styles[1]].white*128;\n\t// r_drawsurf.lightadj[2] = r_newrefdef.lightstyles[surface->styles[2]].white*128;\n\t// r_drawsurf.lightadj[3] = r_newrefdef.lightstyles[surface->styles[3]].white*128;\n\n//\n// see if the cache holds apropriate data\n//\n\tcache = CACHESPOT( surface )[miplevel];\n\n\t// check for lightmap modification\n\tfor( maps = 0; maps < MAXLIGHTMAPS && surface->styles[maps] != 255; maps++ )\n\t{\n\t\tif( tr.lightstylevalue[surface->styles[maps]] != surface->cached_light[maps] )\n\t\t{\n\t\t\tsurface->dlightframe = tr.framecount;\n\t\t}\n\t}\n\n\n\tif( cache && !cache->dlight && surface->dlightframe != tr.framecount\n\t    && cache->image == r_drawsurf.image\n\t    && cache->lightadj[0] == r_drawsurf.lightadj[0]\n\t    && cache->lightadj[1] == r_drawsurf.lightadj[1]\n\t    && cache->lightadj[2] == r_drawsurf.lightadj[2]\n\t    && cache->lightadj[3] == r_drawsurf.lightadj[3] )\n\t\treturn cache;\n\n\tif( surface->dlightframe == tr.framecount )\n\t{\n\t\tint i;\n\t\t// invalidate dlight cache\n\t\tfor( i = 0; i < 4; i++ )\n\t\t{\n\t\t\tif( CACHESPOT( surface )[i] )\n\t\t\t\tCACHESPOT( surface )[i]->image = NULL;\n\t\t}\n\t}\n//\n// determine shape of surface\n//\n\tsurfscale = 1.0 / ( 1 << miplevel );\n\tr_drawsurf.surfmip = miplevel;\n\tif( surface->flags & SURF_CONVEYOR )\n\t\tr_drawsurf.surfwidth = surface->extents[0] >> miplevel;\n\telse\n\t\tr_drawsurf.surfwidth = surface->info->lightextents[0] >> miplevel;\n\tr_drawsurf.rowbytes = r_drawsurf.surfwidth;\n\tr_drawsurf.surfheight = surface->info->lightextents[1] >> miplevel;\n\n\t// use texture space if world luxels used\n\tif( surface->texinfo->flags & TEX_WORLD_LUXELS )\n\t{\n\t\tr_drawsurf.surfwidth = surface->extents[0] >> miplevel;\n\t\tr_drawsurf.rowbytes = r_drawsurf.surfwidth;\n\t\tr_drawsurf.surfheight = surface->extents[1] >> miplevel;\n\t}\n\n\n//\n// allocate memory if needed\n//\n\tif( !cache ) // if a texture just animated, don't reallocate it\n\t{\n\t\tcache = D_SCAlloc( r_drawsurf.surfwidth,\n\t\t\t\t   r_drawsurf.surfwidth * r_drawsurf.surfheight * 2 );\n\t\tCACHESPOT( surface )[miplevel] = cache;\n\t\tcache->owner = &CACHESPOT( surface )[miplevel];\n\t\tcache->mipscale = surfscale;\n\t}\n\n\tif( surface->dlightframe == tr.framecount )\n\t\tcache->dlight = 1;\n\telse\n\t\tcache->dlight = 0;\n\n\tr_drawsurf.surfdat = (pixel_t *)cache->data;\n\n\tcache->image = r_drawsurf.image;\n\tcache->lightadj[0] = r_drawsurf.lightadj[0];\n\tcache->lightadj[1] = r_drawsurf.lightadj[1];\n\tcache->lightadj[2] = r_drawsurf.lightadj[2];\n\tcache->lightadj[3] = r_drawsurf.lightadj[3];\n\tfor( maps = 0; maps < MAXLIGHTMAPS && surface->styles[maps] != 255; maps++ )\n\t{\n\t\tsurface->cached_light[maps] = tr.lightstylevalue[surface->styles[maps]];\n\t}\n//\n// draw and light the surface texture\n//\n\tr_drawsurf.surf = surface;\n\n\t// c_surf++;\n\n\t// calculate the lightings\n\tR_BuildLightMap( );\n\n\t// rasterize the surface into the cache\n\tR_DrawSurface();\n\tR_DrawSurfaceDecals();\n\n\treturn cache;\n}\n\n\n"
  },
  {
    "path": "ref/soft/r_trialias.c",
    "content": "#include \"r_local.h\"\n\n// not really draw alias models here, but use this to draw triangles\n\n\naffinetridesc_t r_affinetridesc;\n\n\nint           r_aliasblendcolor;\n\n\nfloat         aliastransform[3][4];\nfloat         aliasworldtransform[3][4];\nfloat         aliasoldworldtransform[3][4];\n\nfloat         s_ziscale;\nstatic vec3_t s_alias_forward, s_alias_right, s_alias_up;\n\n\n#define NUMVERTEXNORMALS 162\n\nfloat r_avertexnormals[NUMVERTEXNORMALS][3] = {\n#include \"anorms.h\"\n};\n\n\nvoid R_AliasSetUpTransform( void );\nvoid R_AliasProjectAndClipTestFinalVert( finalvert_t *fv );\n\nvoid R_AliasTransformFinalVerts( int numpoints, finalvert_t *fv, dtrivertx_t *oldv, dtrivertx_t *newv );\n\n\n/*\n================\nR_AliasCheckBBox\n================\n*/\n\n#define BBOX_TRIVIAL_ACCEPT 0\n#define BBOX_MUST_CLIP_XY   1\n#define BBOX_MUST_CLIP_Z    2\n#define BBOX_TRIVIAL_REJECT 8\n\nstatic void VectorInverse( vec3_t v )\n{\n\tv[0] = -v[0];\n\tv[1] = -v[1];\n\tv[2] = -v[2];\n}\n\n/*\n================\nR_SetUpWorldTransform\n================\n*/\nvoid R_SetUpWorldTransform( void )\n{\n\tint          i;\n\tstatic float viewmatrix[3][4];\n\tvec3_t       angles;\n\n// TODO: should really be stored with the entity instead of being reconstructed\n// TODO: should use a look-up table\n// TODO: could cache lazily, stored in the entity\n//\n\n\ts_ziscale = (float)0x8000 * (float)0x10000;\n\tangles[ROLL] = 0;\n\tangles[PITCH] = 0;\n\tangles[YAW] = 0;\n\tAngleVectors( angles, s_alias_forward, s_alias_right, s_alias_up );\n\n// TODO: can do this with simple matrix rearrangement\n\n\tmemset( aliasworldtransform, 0, sizeof( aliasworldtransform ));\n\tmemset( aliasoldworldtransform, 0, sizeof( aliasworldtransform ));\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i];\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][1] = -s_alias_right[i];\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i];\n\t}\n\n\taliasworldtransform[0][3] = -RI.vieworg[0];\n\taliasworldtransform[1][3] = -RI.vieworg[1];\n\taliasworldtransform[2][3] = -RI.vieworg[2];\n\n\t// aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0];\n\t// aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1];\n\t// aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2];\n\n// FIXME: can do more efficiently than full concatenation\n//\tmemcpy( rotationmatrix, t2matrix, sizeof( rotationmatrix ) );\n\n//\tR_ConcatTransforms (t2matrix, tmatrix, rotationmatrix);\n\n// TODO: should be global, set when vright, etc., set\n\tVectorCopy( RI.vright, viewmatrix[0] );\n\tVectorCopy( RI.vup, viewmatrix[1] );\n\tVectorInverse( viewmatrix[1] );\n\t// VectorScale(viewmatrix[1], -1, viewmatrix[1]);\n\tVectorCopy( RI.vforward, viewmatrix[2] );\n\n\tviewmatrix[0][3] = 0;\n\tviewmatrix[1][3] = 0;\n\tviewmatrix[2][3] = 0;\n\n//\tmemcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) );\n\n\t// R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform);\n\tMatrix3x4_ConcatTransforms( aliastransform, viewmatrix, aliasworldtransform );\n\n\taliasworldtransform[0][3] = 0;\n\taliasworldtransform[1][3] = 0;\n\taliasworldtransform[2][3] = 0;\n\n\t// aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0];\n\t// aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1];\n\t// aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2];\n}\n\n\n/*\n================\nR_AliasSetUpTransform\n================\n*/\nvoid R_AliasSetUpTransform( void )\n{\n\tint          i;\n\tstatic float viewmatrix[3][4];\n\tvec3_t       angles;\n\n// TODO: should really be stored with the entity instead of being reconstructed\n// TODO: should use a look-up table\n// TODO: could cache lazily, stored in the entity\n//\n\n\ts_ziscale = (float)0x8000 * (float)0x10000;\n\tangles[ROLL] = RI.currententity->angles[ROLL];\n\tangles[PITCH] = RI.currententity->angles[PITCH];\n\tangles[YAW] = RI.currententity->angles[YAW];\n\tAngleVectors( angles, s_alias_forward, s_alias_right, s_alias_up );\n\n// TODO: can do this with simple matrix rearrangement\n\n\tmemset( aliasworldtransform, 0, sizeof( aliasworldtransform ));\n\tmemset( aliasoldworldtransform, 0, sizeof( aliasworldtransform ));\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i];\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][1] = -s_alias_right[i];\n\t\taliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i];\n\t}\n\n\taliasworldtransform[0][3] = RI.currententity->origin[0] - RI.vieworg[0];\n\taliasworldtransform[1][3] = RI.currententity->origin[1] - RI.vieworg[1];\n\taliasworldtransform[2][3] = RI.currententity->origin[2] - RI.vieworg[2];\n\n\t// aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0];\n\t// aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1];\n\t// aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2];\n\n// FIXME: can do more efficiently than full concatenation\n//\tmemcpy( rotationmatrix, t2matrix, sizeof( rotationmatrix ) );\n\n//\tR_ConcatTransforms (t2matrix, tmatrix, rotationmatrix);\n\n// TODO: should be global, set when vright, etc., set\n\tVectorCopy( RI.vright, viewmatrix[0] );\n\tVectorCopy( RI.vup, viewmatrix[1] );\n\tVectorInverse( viewmatrix[1] );\n\t// VectorScale(viewmatrix[1], -1, viewmatrix[1]);\n\tVectorCopy( RI.vforward, viewmatrix[2] );\n\n\tviewmatrix[0][3] = 0;\n\tviewmatrix[1][3] = 0;\n\tviewmatrix[2][3] = 0;\n\n//\tmemcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) );\n\n\t// R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform);\n\tMatrix3x4_ConcatTransforms( aliastransform, viewmatrix, aliasworldtransform );\n\n\taliasworldtransform[0][3] = RI.currententity->origin[0];\n\taliasworldtransform[1][3] = RI.currententity->origin[1];\n\taliasworldtransform[2][3] = RI.currententity->origin[2];\n\n\t// aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0];\n\t// aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1];\n\t// aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2];\n}\n\n/*\n================\nR_AliasProjectAndClipTestFinalVert\n================\n*/\nvoid R_AliasProjectAndClipTestFinalVert( finalvert_t *fv )\n{\n\tfloat zi;\n\tfloat x, y, z;\n\n\t// project points\n\tx = fv->xyz[0];\n\ty = fv->xyz[1];\n\tz = fv->xyz[2];\n\tzi = 1.0f / z;\n\n\tfv->zi = zi * s_ziscale;\n\n\tfv->u = ( x * aliasxscale * zi ) + aliasxcenter;\n\tfv->v = ( y * aliasyscale * zi ) + aliasycenter;\n\n\tif( fv->u < RI.aliasvrect.x )\n\t\tfv->flags |= ALIAS_LEFT_CLIP;\n\tif( fv->v < RI.aliasvrect.y )\n\t\tfv->flags |= ALIAS_TOP_CLIP;\n\tif( fv->u > RI.aliasvrectright )\n\t\tfv->flags |= ALIAS_RIGHT_CLIP;\n\tif( fv->v > RI.aliasvrectbottom )\n\t\tfv->flags |= ALIAS_BOTTOM_CLIP;\n}\n\nvoid R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, int s, int t )\n{\n\tvec3_t v = {x, y, z};\n\n\tfv->xyz[0] = DotProduct( v, aliastransform[0] ) + aliastransform[0][3];\n\tfv->xyz[1] = DotProduct( v, aliastransform[1] ) + aliastransform[1][3];\n\tfv->xyz[2] = DotProduct( v, aliastransform[2] ) + aliastransform[2][3];\n\n\tfv->flags = 0;\n\n\tfv->l = light;\n\n\tif( fv->xyz[2] < ALIAS_Z_CLIP_PLANE )\n\t{\n\t\tfv->flags |= ALIAS_Z_CLIP;\n\t}\n\telse\n\t{\n\t\tR_AliasProjectAndClipTestFinalVert( fv );\n\t}\n\n\tfv->s = s << 16;\n\tfv->t = t << 16;\n}\n\nvoid R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 )\n{\n\n\tif( fv1->flags & fv2->flags & fv3->flags )\n\t\treturn; // completely clipped\n\n\tif( !( fv1->flags | fv2->flags | fv3->flags ))\n\t{ // totally unclipped\n\t\taliastriangleparms.a = fv1;\n\t\taliastriangleparms.b = fv2;\n\t\taliastriangleparms.c = fv3;\n\n\t\tR_DrawTriangle();\n\t}\n\telse\n\t{ // partially clipped\n\t\tR_AliasClipTriangle( fv1, fv2, fv3 );\n\t}\n}\n\n\n\n"
  },
  {
    "path": "ref/soft/r_triapi.c",
    "content": "/*\ngl_triapi.c - TriAPI draw methods\nCopyright (C) 2011 Uncle Mike\nCopyright (C) 2019 a1batross\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"r_local.h\"\n#include \"const.h\"\n\nstatic struct\n{\n\tint    renderMode;                      // override kRenderMode from TriAPI\n\tvec4_t triRGBA;\n} ds;\n\nstatic finalvert_t triv[3];\nstatic int         vertcount, n;\nstatic int         mode;\nstatic short       s, t;\nstatic uint        light;\n\n/*\n===============================================================\n\n  TRIAPI IMPLEMENTATION\n\n===============================================================\n*/\n/*\n=============\nTriRenderMode\n\nset rendermode\n=============\n*/\nvoid GAME_EXPORT TriRenderMode( int mode )\n{\n\tds.renderMode = vid.rendermode = mode;\n}\n\n/*\n=============\nTriBegin\n\nbegin triangle sequence\n=============\n*/\nvoid GAME_EXPORT TriBegin( int mode1 )\n{\n\tif( mode1 == TRI_QUADS )\n\t\tmode1 = TRI_TRIANGLE_FAN;\n\tmode = mode1;\n\tn = vertcount = 0;\n}\n\n/*\n=============\nTriEnd\n\ndraw triangle sequence\n=============\n*/\nvoid GAME_EXPORT TriEnd( void )\n{\n}\n\n/*\n=============\n_TriColor4f\n\n=============\n*/\nvoid GAME_EXPORT _TriColor4f( float rr, float gg, float bb, float aa )\n{\n\t// pglColor4f( r, g, b, a );\n\tunsigned short r, g, b;\n\tunsigned int   major, minor;\n\n\tif( vid.rendermode == kRenderTransAdd || vid.rendermode == kRenderGlow )\n\t\trr *= aa, gg *= aa, bb *= aa;\n\n\t// gEngfuncs.Con_Printf(\"%d\\n\", vid.alpha);\n\n\tlight = ( rr + gg + bb ) * 31 / 3;\n\tif( light > 31 )\n\t\tlight = 31;\n\n\tif( !vid.is2d && vid.rendermode == kRenderNormal )\n\t\treturn;\n\n\tvid.alpha = aa * 7;\n\tif( vid.alpha > 7 )\n\t\tvid.alpha = 7;\n\n\tif( rr == 1 && gg == 1 && bb == 1 )\n\t{\n\t\tvid.color = COLOR_WHITE;\n\t\treturn;\n\t}\n\tr = rr * 31, g = gg * 63, b = bb * 31;\n\tif( r > 31 )\n\t\tr = 31;\n\tif( g > 63 )\n\t\tg = 63;\n\tif( b > 31 )\n\t\tb = 31;\n\n\n\tmajor = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 )));\n\n\t// save minor GBRGBRGB\n\tminor = MOVE_BIT( r, 1, 5 ) | MOVE_BIT( r, 0, 2 ) | MOVE_BIT( g, 2, 7 ) | MOVE_BIT( g, 1, 4 ) | MOVE_BIT( g, 0, 1 ) | MOVE_BIT( b, 2, 6 ) | MOVE_BIT( b, 1, 3 ) | MOVE_BIT( b, 0, 0 );\n\n\tvid.color = major << 8 | ( minor & 0xFF );\n}\n\n/*\n=============\nTriColor4ub\n\n=============\n*/\nvoid TriColor4ub( byte r, byte g, byte b, byte a )\n{\n\tds.triRGBA[0] = r * ( 1.0f / 255.0f );\n\tds.triRGBA[1] = g * ( 1.0f / 255.0f );\n\tds.triRGBA[2] = b * ( 1.0f / 255.0f );\n\tds.triRGBA[3] = a * ( 1.0f / 255.0f );\n\n\t_TriColor4f( ds.triRGBA[0], ds.triRGBA[1], ds.triRGBA[2], 1.0f );\n}\n\n/*\n=============\nTriColor4ub\n\n=============\n*/\nvoid GAME_EXPORT _TriColor4ub( byte r, byte g, byte b, byte a )\n{\n\t_TriColor4f( r * ( 1.0f / 255.0f ),\n\t\t     g * ( 1.0f / 255.0f ),\n\t\t     b * ( 1.0f / 255.0f ),\n\t\t     a * ( 1.0f / 255.0f ));\n}\n\n/*\n=================\nTriColor4f\n=================\n*/\nvoid TriColor4f( float r, float g, float b, float a )\n{\n\t// if( a < 0.5 )\n\t//\ta = 1;\n\tif( ds.renderMode == kRenderTransAlpha )\n\t\tTriColor4ub( r * 255.0f, g * 255.0f, b * 255.0f, a * 255.0f );\n\telse\n\t\t_TriColor4f( r * a, g * a, b * a, 1.0 );\n\n\tds.triRGBA[0] = r;\n\tds.triRGBA[1] = g;\n\tds.triRGBA[2] = b;\n\tds.triRGBA[3] = a;\n}\n\n/*\n=============\nTriTexCoord2f\n\n=============\n*/\nvoid GAME_EXPORT TriTexCoord2f( float u, float v )\n{\n\tdouble u1 = 0, v1 = 0;\n\tu = fmodf( u, 10 );\n\tv = fmodf( v, 10 );\n\tif( u < 1000 && u > -1000 )\n\t\tu1 = u;\n\tif( v < 1000 && v > -1000 )\n\t\tv1 = v;\n\twhile( u1 < 0 )\n\t\tu1 = u1 + 1;\n\twhile( v1 < 0 )\n\t\tv1 = v1 + 1;\n\n\twhile( u1 > 1 )\n\t\tu1 = u1 - 1;\n\twhile( v1 > 1 )\n\t\tv1 = v1 - 1;\n\n\n\ts = r_affinetridesc.skinwidth * bound( 0.01, u1, 0.99 );\n\tt = r_affinetridesc.skinheight * bound( 0.01, v1, 0.99 );\n}\n\n/*\n=============\nTriVertex3fv\n\n=============\n*/\nvoid GAME_EXPORT TriVertex3fv( const float *v )\n{\n\tTriVertex3f( v[0], v[1], v[2] );\n}\n\n/*\n=============\nTriVertex3f\n\n=============\n*/\nvoid GAME_EXPORT TriVertex3f( float x, float y, float z )\n{\n\tif( mode == TRI_TRIANGLES )\n\t{\n\t\tR_SetupFinalVert( &triv[vertcount], x, y, z, light << 8, s, t );\n\t\tvertcount++;\n\t\tif( vertcount == 3 )\n\t\t{\n\t\t\tR_RenderTriangle( &triv[0], &triv[1], &triv[2] );\n\t\t\t// R_RenderTriangle( &triv[2], &triv[1], &triv[0] );\n\t\t\tvertcount = 0;\n\t\t}\n\t}\n\tif( mode == TRI_TRIANGLE_FAN )\n\t{\n\t\tR_SetupFinalVert( &triv[vertcount], x, y, z, light << 8, s, t );\n\t\tvertcount++;\n\t\tif( vertcount >= 3 )\n\t\t{\n\t\t\tR_RenderTriangle( &triv[0], &triv[1], &triv[2] );\n\t\t\t// R_RenderTriangle( &triv[2], &triv[1], &triv[0] );\n\t\t\ttriv[1] = triv[2];\n\t\t\tvertcount = 2;\n\t\t}\n\t}\n\tif( mode == TRI_TRIANGLE_STRIP )\n\t{\n\t\tR_SetupFinalVert( &triv[n], x, y, z, light << 8, s, t );\n\t\tn++;\n\t\tvertcount++;\n\t\tif( n == 3 )\n\t\t\tn = 0;\n\t\tif( vertcount >= 3 )\n\t\t{\n\t\t\tif( vertcount & 1 )\n\t\t\t\tR_RenderTriangle( &triv[0], &triv[1], &triv[2] );\n\t\t\telse\n\t\t\t\tR_RenderTriangle( &triv[2], &triv[1], &triv[0] );\n\t\t}\n\t}\n}\n\n/*\n=============\nTriWorldToScreen\n\nconvert world coordinates (x,y,z) into screen (x, y)\n=============\n*/\nint GAME_EXPORT TriWorldToScreen( const float *world, float *screen )\n{\n\treturn R_WorldToScreen( world, screen );\n}\n\n/*\n=============\nTriSpriteTexture\n\nbind current texture\n=============\n*/\nint TriSpriteTexture( model_t *pSpriteModel, int frame )\n{\n\tint gl_texturenum;\n\n\tif(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 )\n\t\treturn 0;\n\n\tif( gl_texturenum <= 0 || gl_texturenum >= MAX_TEXTURES )\n\t\tgl_texturenum = tr.defaultTexture;\n\n\tGL_Bind( XASH_TEXTURE0, gl_texturenum );\n\n\treturn 1;\n}\n\n/*\n=============\nTriFog\n\nenables global fog on the level\n=============\n*/\nvoid GAME_EXPORT TriFog( float flFogColor[3], float flStart, float flEnd, int bOn )\n{\n}\n\n/*\n=============\nTriGetMatrix\n\nvery strange export\n=============\n*/\nvoid GAME_EXPORT TriGetMatrix( const int pname, float *matrix )\n{\n}\n\n/*\n=============\nTriForParams\n\n=============\n*/\nvoid GAME_EXPORT TriFogParams( float flDensity, int iFogSkybox )\n{\n}\n\n/*\n=============\nTriCullFace\n\n=============\n*/\nvoid GAME_EXPORT TriCullFace( TRICULLSTYLE mode )\n{\n}\n\n/*\n=============\nTriBrightness\n=============\n*/\nvoid TriBrightness( float brightness )\n{\n\tfloat r, g, b;\n\n\tr = ds.triRGBA[0] * ds.triRGBA[3] * brightness;\n\tg = ds.triRGBA[1] * ds.triRGBA[3] * brightness;\n\tb = ds.triRGBA[2] * ds.triRGBA[3] * brightness;\n\n\t_TriColor4f( r, g, b, 1.0f );\n}\n"
  },
  {
    "path": "ref/soft/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib import Logs\nimport os\n\ntop = '.'\n\ndef options(opt):\n\treturn\n\ndef configure(conf):\n\treturn\n\ndef build(bld):\n\tlibs = [ 'engine_includes', 'werror' ]\n\t# on PSVita do not link any libraries that are already in the main executable, but add the includes target\n\tif bld.env.DEST_OS == 'psvita':\n\t\tlibs += [ 'sdk_includes' ]\n\telse:\n\t\tlibs += [ 'public', 'M' ]\n\n\tbld.shlib(source   = bld.path.ant_glob('*.c'),\n\t\ttarget   = 'ref_soft',\n\t\tincludes = '.',\n\t\tdefines  = 'REF_DLL=1',\n\t\tuse      = libs,\n\t\tinstall_path = bld.env.LIBDIR\n\t)\n"
  },
  {
    "path": "ref/vk/NOTES.md",
    "content": "# cvars\n## `rt_force_disable`\nOn GPUs that support ray tracing forcefully disables it as if it wasn't supported at all. I.e. no RT extensions and modules are initialized. Useful for testing sometimes.\nNote: this cvar is read early in `R_VkInit()`, which gets executed before `autoexec.cfg`, `config.cfg`, etc are read. So putting it there will not work.\n`video.cfg` and `vk.cfg` are read before Vk initialization, so this cvar should go there.\n\n# Frame structure wrt calls from the engine\n- (eng) SCR_UpdateScreen()\n\t- (eng) V_PreRender()\n\t\t- **(ref) R_BeginFrame()**\n\t- (eng) V_RenderView()\n\t\t- **(ref) GL_BackendStartFrame()** -- ref_gl only sets speeds string to empty here\n\t\t\t- (eng) loop over ref_params_t views\n\t\t\t\t- **(ref) GL_RenderFrame()**\n\t\t\t- (eng) ??? SV_DrawDebugTriangles()\n\t\t- **(ref) GL_BackendEndFrame()** -- ref_gl only produces speeds string here\n\t- (eng) V_PostRender()\n\t\t- **(ref) R_AllowFog(), R_Set2DMode(true)**\n\t\t- **(ref) R_DrawTileClear()** x N\n\t\t- (vgui) Paint() -> ???\n\t\t- (eng) SCR_RSpeeds()\n\t\t\t- **(ref) R_SpeedsMessage()**\n\t\t\t- (eng) CL_DrawString() ...\n\t\t\t  - **(ref) GL_SetRenderMode()**\n\t\t\t\t- **(ref) RefGetParm()** for texture resolution\n\t\t\t\t- **(ref) Color4ub()**\n\t\t\t\t- **(ref) R_DrawStretchPic()**\n\t\t- (eng) SRC_DrawNetGraph()\n\t\t\t- **(ref) many TriApi calls** -- 2D usage of triapi. we were not ready for this (maybe track R_Set2DMode()?)\n\t\t- **(ref) R_ShowTextures()** kekw\n\t\t- **(ref) VID_ScreenShot()**\n\t\t- **(ref) R_AllowFog(true)**\n\t\t- **(ref) R_EndFrame()**\n\n# Staging and multiple command buffers\nWe want to get rid of extra command buffers used for staging (and building blases). That would mean tying any command-buffer related things in there to framectl.\nHowever, there are several staging cmdbuf usages which are technically out-of-band wrt framectl:\n0. Staging data can get full, which requires sync flush: filling cmdbuf outside of frame (or while still building a frame), submitting it and waiting on it.\n1. Texture uploading. There's an explicit usage of staging cmdbuf in vk_texture to do layout transfer. This layout transfer can be moved to staging itself.\n2. BLAS building. Creating a ray model uploads its geometry via staging and then immediately builds its BLAS on the same staging cmdbuf. Ideally(?), we'd like to split BLAS building to some later stage to do it in bulk.\n\n# OpenGL-like immediate mode rendering, ~TriApi\n## Functions:\n\tR_Set2DMode(bool) -- switches between 3D scene and 2D overlay modes; used in engine\n\tR_DrawStretchRaw,\n\tR_DrawStretchPic,\n\tR_DrawTileClear,\n\tCL_FillRGBA,\n\tCL_FillRGBABlend,\n\n\tR_AllowFog,\n\tGL_SetRenderMode,\n\n\tvoid\t\t(*GL_Bind)( int tmu, unsigned int texnum );\n\tvoid\t\t(*GL_SelectTexture)( int tmu );\n\n\tvoid\t\t(*GL_LoadTextureMatrix)( const float *glmatrix ); -- exported to the game, not used in engine\n\tvoid\t\t(*GL_TexMatrixIdentity)( void ); -- exported to the game, not used in engine\n\n\tvoid\t\t(*GL_CleanUpTextureUnits)( int last );\t// pass 0 for clear all the texture units\n\tvoid\t\t(*GL_TexGen)( unsigned int coord, unsigned int mode );\n\tvoid\t\t(*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture\n\tvoid\t\t(*GL_TexCoordArrayMode)( unsigned int texmode );\n\tvoid\t\t(*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics\n\n\tTriRenderMode,\n\tTriBegin,\n\tTriEnd,\n\tTriColor4f,\n\tTriColor4ub,\n\tTriTexCoord2f,\n\tTriVertex3fv,\n\tTriVertex3f,\n\tTriFog,\n\tTriGetMatrix,\n\tTriFogParams,\n\tTriCullFace,\n\n\n# Better BLAS management API\n\n~~\nBLAS:\n- geom_count => kusok.geom/material.size() == geom_count\n\nModel types:\n1. Fully static (brush model w/o animated textures; studio model w/o animations): singleton, fixed geoms and materials, uploaded only once\n2. Semi-static (brush model w/ animated textures): singleton, fixed geoms, may update materials, inplace (e.g. animated textures)\n3. Dynamic (beams, triapi, etc): singleton, may update both geoms and materials, inplace\n4. Template (sprites): used by multiple instances, fixed geom, multiple materials (colors, textures etc) instances/copies\n5. Update-from template (studo models): used by multiple dynamic models, deriving from it wvia BLAS UPDATE, dynamic geom+locations, fixed-ish materials.\n\nAPI ~\n1. RT_ModelCreate(geometries_count dynamic?static?) -> rt_model + preallocated mem\n2. RT_ModelBuild/Update(geometries[]) -> (blas + kusok.geom[])\n3. RT_ModelUpdateMaterials(model, geometries/textures/materials[]); -> (kusok.material[])\n4. RT_FrameAddModel(model + kusok.geom[] + kusok.material[] + render_type + xform + color)\n~~\n\n\nrt_instance_t/rt_blas_t:\n- VkAS blas\n\t- VkASGeometry geom[] -> (vertex+index buffer address)\n\t- VkASBuildRangeInfo ranges[] -> (vtxidx buffer offsets)\n\t- ~~TODO: updateable: blas[2]? Ping-pong update, cannot do inplace?~~ Nope, can do inplace.\n- kusochki\n\t- kusok[]\n\t\t- geometry -> (vtxidx buffer offsets)\n\t\t\t- TODO roughly the same data as VkASBuildRangeInfo, can reuse?\n\t\t- material (currently embedded in kusok)\n\t\t\t- static: tex[], scalar[]\n\t\t\t- semi-dynamic:\n\t\t\t\t- (a few) animated tex_base_color\n\t\t\t\t- emissive\n\t\t\t\t\t- animated with tex_base_color\n\t\t\t\t\t- individual per-surface patches\n\t\t\t- TODO: extract as a different modality not congruent with kusok data\n\nUsage cases for the above:\n1. (Fully+semi) static.\n  - Accept geom[] from above with vtx+idx refernces. Consider them static.\n\t- Allocate static/fixed blas + kusok data once at map load.\n\t- Allocate geom+ranges[] temporarily. Fill them with vtx+idx refs.\n\t- Build BLAS (?: how does this work with lazy/deferred BLAS building wrt geom+ranges allocation)\n\t\t- Similar to staging: collect everything + temp data, then commit.\n\t\t- Needs BLAS manager, similar to vk_staging\n\t- Generate Kusok data with current geoms and materials\n\t- Free geom+ranges\n\t- Each frame:\n\t\t- (semi-static only) Update kusochki materials for animated textures\n\t\t- Add blas+kusochki_offset (+dynamic color/xform/mmode) to TLAS\n2. Preallocated dynamic (triapi)\n  - Preallocate for fixed N geoms:\n\t\t- geom+ranges[N].\n\t\t- BLAS for N geometries\n\t\t- kusochki[N]\n\t- Each frame:\n\t\t- Fill geom+ranges with geom data fed from outside\n\t\t- Fill kusochki --//--\n\t\t- Fast-Build BLAS as new\n\t\t- Add to TLAS\n3. Dynamic with update (animated studio models, beams)\n\t- When a new studio model entity is encountered:\n\t\t- Allocate:\n\t\t\t- AT FIXED OFFSET: vtx+idx block\n\t\t\t- geom+ranges[N], BLAS for N, kusochki[N]\n\t- Each frame:\n\t\t- Fill geom+ranges with geom data\n\t\t- Fill kusochki --//--\n\t\t- First frame: BLAS as new\n\t\t- Next frames: UPDATE BLAS in-place (depends on fixed offsets for vtx+idx)\n\t\t- Add to TLAS\n4. Instanced (sprites, studio models w/o animations).\n\t- Same as static, BUT potentially dynamic and different materials. I.e. have to have per-instance kusochki copies with slightly different material contents.\n\t- I.e. each frame\n\t\t- If modifying materials (e.g. different texture for sprites):\n\t\t\t- allocate temporary (for this frame only) kusochki block\n\t\t\t- fill geom+material kusochki data\n\t\t- Add to TLAS w/ correct kusochki offset.\n\nExposed ops:\n- Create BLAS for N geoms\n- Allocate kusochki[N]\n\t- static (fixed pos)\n\t- temporary (any location, single frame lifetime)\n- Fill kusochki\n\t- All geoms[]\n\t- Subset of geoms[] (animated textures for static)\n- Build BLAS\n\t- Allocate geom+ranges[N]\n\t\t- Single frame staging-like?\n\t\t- Needed only for BLAS BUILD/UPDATE\n\t- from geoms+ranges[N]\n\t- build vs update\n- Add to TLAS w/ color/xform/mmode/...\n\n- geometry_buffer -- vtx+idx static + multi-frame dynamic + single-frame dynamic\n- kusochki_buffer -- kusok[] static + dynamic + clone_dynamic\n- accel_buffer -- static + multiframe dynamic + single-frame dynamic\n- scratch_buffer - single-frame dynamic\n- model_buffer - single-frame dynamic\n\n# E268: explicit kusochki management\nKusochki buffer has a similar lifetime rules to geometry buffer\nFuncs:\n- Allocate kusochki[N] w/ static/long lifetime\n- Allocate dynamic (single-frame) kusochki[N]\n- Upload geom[N] -> kusochki[N]\n- Upload subset geom[ind[M] -> kusochki[M]\n\n# E269\n\nRT model alloc:\n- blas -- fixed\n\t- accel buffer region -- fixed\n\t- (scratch: once for build)\n\t- (geoms: once for build)\n- -> geometry buffer -- fixed\n- kusochki[G]: geometry data -- fixed\n- materials[G]: -- fixed\n\nRT model update:\n- lives in the same statically allocated blas + accel_buffer\n-\n\nRT model draw:\n- mmode\n- materials[G] -- can be fully static, partially dynamic, fully dynamic\n\t- update inplace for most of dynamic things\n\t- clone for instanced\n- color\n- transforms\n\n## Blocks\n### Layer 0: abstract, not backing-dependent\n\thandle = R_BlockAlloc(int size, lifetime);\n\t- block possible users: {accel buffer, geometry, kusochki, materials};\n\t- lifetime\n\t\t- long: map, N frames: basically everything)\n\t\t- once = this frame only: sprite materials, triapi geometry/kusochki/materials\n\t- handle: offset, size\n\t- R_BlockAcquire/Release(handle);\n\t- R_BlocksClearOnce(); -- frees \"once\" regions, checking that they are not referenced\n\t- R_blocksClearFull(); -- clears everything, checking that there are not external references\n\n### Layer 1: backed by buffer\n- lock = R_SmthLock(handle, size, offset)\n\t- marks region/block as dirty (cannot be used by anything yet, prevents release, clear, etc.),\n\t- opens staging regiong for filling and uploading\n- R_SmthUnlock(lock)\n\t- remembers dirty region (for barriers)\n\t- submits into staging queue\n- ?? R_SmthBarrier -- somehow ask for the barrier struct given pipelines, etc\n\n# E271\n\n## Map loading sequence\n1. For a bunch of sprites:\n\t1. Load their textures\n\t2. Mod_ProcessRenderData(spr, create=1)\n2. \"loading maps/c1a0.bsp\" message\n\t1. Load a bunch of `#maps/c1a0.bsp:*.mip` textures\n\t2. Mod_ProcessRenderData(maps/c1a0.bsp, create=1)\n3. For studio models:\n\t1. Load their textures\n\t2. Mod_ProcessRenderData(mdl, create=1)\n4. \"level loaded at 0.31 sec\" message\n5. 1-2 frames drawn (judging by vk swapchain logs)\n6. Do another bunch of sprites (as #1)\n7. Lightstyles logs\n8. \"Setting up renderer...\" message\n9. R_NewMap() is called\n\t1. (vk) load skybox\n\t2. (vk) extract WADs, parse entities\n\t3. (vk) parse materials\n\t4. (vk) parse patches\n\t5. (vk) load models\n\t\t1. load brush models\n\t\t2. skip studio and sprite models\n\t6. (vk) load lights: parse rad files, etc\n10. \"loading model/scientist02.mdl\"\n11. Load 640_pain.spr ???, Mod_ProcessRenderData() first, then textures ??\n\n## Map unloading sequence\n1. Mod_ProcessRenderData(maps/c1a0.bps, create=0)\n\t- NO similar calls for `*` brush submodels.\n2. For the rest of studio and sprite models:\n\t- Mod_ProcessRenderData(create=0)\n\n# E274\n\nrt_model:\n\t- kusok/geom\n\t\t- index_,vertex_offset (static, same as geom/blas lifetime)\n\t\t- ref to material (static or dynamic)\n\t\t- emissive (mostly static, independent to material)\n\t- instanceCustomIndex (24 bits) = offset to kusochki buffer\n\t- kusochki[G]\n\t\t- geom data (index, vertex offsets)\n\t\t- emissive\n\t\t- material\n\t- materials[M]\n  - kusochki[N] <- iCI\n\n\n# E275 studio models\n\n- `R_StudioDrawPoints()`\n\t- `VK_RenderModelDynamicBegin()`\n\t- compute `g_studio.verts`\n\t\t- in:\n\t\t\t- `m_pSubModel`\n\t\t\t- `m_pStudioHeader`\n\t\t\t- `g_studio.worldtransform`\n\t- `R_StudioBuildNormalTable()` ...\n\t- `R_StudioGenerateNormals()`\n\t\t- in:\n\t\t\t- `m_pStudioHeader`\n\t\t\t- `m_pSubModel`\n\t\t\t- `g_studio.verts`\n\t\t- out:\n\t\t\t- `g_studio.norms`\n\t\t\t- `g_studio.tangents`\n\t\t- for all submodel meshes\n\t\t\t- compute normals+tangents\n\t- for all submodel meshes\n\t\t- `R_StudioDrawNormalMesh()`\n\t\t\t- `R_GeometryBufferAllocOnceAndLock()`\n\t\t\t- fills it with vertex/index data, reading `g_studio.verts/norms/tangents/...`\n\t\t\t\t- `R_StudioSetColorBegin()` ???\n\t\t\t- `R_GeometryBufferUnlock()`\n\t\t\t- `VK_RenderModelDynamicAddGeometry()`\n\t- `VK_RenderModelDynamicCommit()`\n\n- `R_StudioDrawPoints()` callers:\n\t- external ???\n\t- `R_StudioRenderFinal()`\n\n- `R_StudioRenderFinal()`\n\t- ... TBD\n\t- `VK_RenderDebugLabelBegin()`\n\t- for all `m_pStudioHeader->numbodyparts`\n\t\t- `R_StudioSetupModel()` -- also can be called externally\n\t\t\t- set `m_pBodyPart`\n\t\t\t- set `m_pSubModel`\n\t\t- `R_StudioDrawPoints()`\n\t\t- `GL_StudioDrawShadow()`\n\t- `VK_RenderDebugLabelEnd()`\n\n- `R_StudioDrawModelInternal()`\n\t- called from:\n\t\t- `R_DrawStudioModel()` 3x\n\t\t- `R_DrawViewModel()`\n\t\t- `R_RunViewmodelEvents()`\n\t- `VK_RenderDebugLabelBegin()`\n\t- `R_StudioDrawModel()`\n\t\t- in:\n\t\t\t- `RI.currententity`\n\t\t\t- `RI.currentmodel`\n\t\t- `R_StudioSetHeader()`\n\t\t\t- sets `m_pStudioHeader`\n\t\t- `R_StudioSetUpTransform(entity = RI.currententity)`\n\t\t\t- `R_StudioLerpMovement(entity)`\n\t\t\t\t- updates entity internal state\n\t\t\t- `g_studio.rotationmatrix = Matrix3x4_CreateFromEntity()`\n\t- `VK_RenderDebugLabelEnd()`\n\n- `VK_StudioDrawModel()` -- called from vk_scene.c\n\t- sets `RI.currententity`, `RI.currentmodel`\n\t- `R_DrawStudioModel()`\n\t\t- `R_StudioSetupTimings()` -- sets `g_studio.time/frametime`\n\t\t- `R_StudioDrawModelInternal()`\n\n# E279\n## Studio model animation\n- studiohdr_t\n\t- int numseq -- number of \"sequences\"?\n\t- int seqindex -- offset to sequences:\n\t\t\t`pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;`\n- mstudioseqdesc_t\n\t- int numframes\n\t- int fps\n- mstudioanim_t\n\t- = gEngine.R_StudioGetAnim(studiohdr, model, seqdesc)\n\n- cl_entity_t\n\t- sequence -- references studio model sequence\n\t- animtime/frame -- references animation state within sequence\n\n# E282\n## Studio model tracking\n`m_pStudioHeader` is set from:\n- `R_StudioSetHeader()` from:\n\t- EXTERNAL\n\t- `R_StudioDrawModel()`\n\t- `R_StudioDrawPlayer()`\n- `R_StudioDrawPlayer()`\n\n## Detecting static/unchanged studio submodels\n### Parse `studiohdr_t` eagerly\nGo deeply into sequences, animations, etc and figure out whether vertices will actually change.\nMight not catch models which are not being animated right now, i.e. current frame is the same as previous one, altough it is not guaranteed to be so.\nThis potentially conflicts with game logic updating bonetransforms manually even though there are no recorded animations in studio file.\n\n### Detect changes dynamically\nLet it process vertices as usual, but then compute hash of vertices values.\nDepends on floating point vertices coordinates being bit-perfect same every time, even for moving entities. This is not strictly speaking true because studio model rendering is organized in such a way that bone matrices are pre-multiplied by entity transform matrix. This is done outside of vk_studio.c, and in game dll,which we have no control over. We then undo this multiplication. Given floating point nature of all of this garbage, there will be precision errors and resulting coordinates are not guaranteed to be the same even for completely static models.\n\n### Lazily detect static models, and draw the rest as fully dynamic with fast build\n- Detect simple static cases (one sequence, one frame), and pre-build those.\n- For everything else, just build it from scratch every frame w/o caching or anything.\nIf that is not fast enough, then we can proceed with more involved per-entity caching, BLAS updates, cache eviction, etc.\n\nTODO: can we not have a BLAS/model for each submodel? Can it be per-model instead? This would need prior knowledge of submodel count, mesh count, vertices and indices counts. (Potentially conflicts with game dll doing weird things, e.g. skipping certain submodels based on whatever game specific logic)\n\n### Action plan\n- [ ] Try to pre-build static studio models. If fails (e.g. still need dynamic knowledge for the first build), then build it lazily, i.e. when the model is rendered for the first time.\n\t- [ ] Needs tracking of model cache entry whenever `m_pStudioHeader` is set.\n- [ ] Add a cache for entities, store all prev_* stuff there.\n\t- [ ] Needs tracking of entity cache entry whenever `RI.currententity` is set.\n\n- [ ] Alternative model/entity tracking: just check current ptrs in `R_StudioDrawPoints()` and update them if changed.\n\n# 2023-07-30\n- ~~R_DrawStudioModel is the main func for drawing studio model. Called from scene code for each studio entity, with everything current (RI and stuff) set up~~\n- `R_StudioDrawModelInternal()` is the main one. It is where it splits into renderer-vs-game rendering functions.\n\n# 2023-09-11 E293\n- light shaders include structure\n- ray_light_direct_{poly,point}.comp\n\t- ray_light_direct.glsl\n\t\t- utils.glsl\n\t\t- noise.glsl\n\t\t- ray_interop.h\n\t\t- ray_kusochki.glsl\n\t\t- light.glsl\n\t\t\t- brdf.h\n\t\t\t- light_common.glsl\n\t\t\t- LIGHT_POLYGON: light_polygon.glsl\n\n# 2023-09-19 E298\n## SURF_DRAWSKY\n- (context: want to remove kXVkMaterialSky, #474)\n- qrad:\n    - uses textue name \"sky\" or \"SKY\" to check `IsSky()`. `IsSky()` surfaces do not get patches and do not participate in radiosity.\n    - uses CONTENTS_SKY node flag to detect whether a ray has hit skybox and can contribute sky light.\n- xash/gl:\n    - CONTENTS_SKY is not used in any meaningful way\n    - sets SURF_DRAWSKY for surfaces with \"sky\" texture.\n    - uses SURF_DRAWSKY:\n        - to build skychain, and then draw it in Quake mode (the other branch does a bunch of math, which seemingly isn't used for anything at all).\n        - for dynamic lighting: if sky ray has hit sky surface then sky is contributing light\n\n# 2023-09-25 #301\n## Materials format\nDefine new material, independently of any existing textures, etc\nThis can be .vmat compatible, primext compatbile, etc.\nThe important parts:\n- It has a unique name that we can reference it with\n- It has all the fields that we use for our PBR shading model\n- (? Material mode can be specified)\n```\n{\n\t\"material\" \"MAT_NAME\"\n\t\"map_base_color\" \"base.png\"\n\t\"map_normal\" \"irregular.ktx2\"\n\t\"base_color\" \"1 .5 0\"\n\t// ...\n}\n\n{\n\t\"material\" \"mirror\"\n    \"map_base_color\" \"white\"\n    \"base_color\" \"1 1 1\"\n    \"roughness\" \"0\"\n    \"metalness\" \"1\"\n    // ...\n}\n```\n\nThen, we can map existing textures to new materials:\n```\n{\n\t\"for_texture\" \"+EXIT\"\n    \"use\" \"MAT_NAME\"\n}\n```\n\nOr, with more context:\n```\n{\n    \"for_model_type\" \"brush\"\n    \"for_rendermode\" \"kRenderTransAlpha\"\n    \"for_texture\" \"wood\"\n    \"use\" \"mat_glass\"\n    \"mode\" \"translucent\"\n    \"map_base_color\" \"glass2.ktx\"\n}\n\n// ??? meh, see the better _xvk_ example below\n{\n    \"for_model_type\" \"brush\"\n    \"for_surface_id\" \"584\"\n    \"use\" \"mirror\"\n}\n\n// This example: use previously specified material (e.g. via _xvk stuff below)\n// (Depends on applying multiple matching rules, see questions below)\n{\n    \"for_model_type\" \"brush\"\n    \"for_rendermode\" \"kRenderTransAlpha\"\n    \"mode\" \"translucent\"\n    \"map_normal\" \"glass2.ktx\"\n}\n\n// We also want this (for maps, not globally ofc), see https://github.com/w23/xash3d-fwgs/issues/526\n{\n    \"for_entity_id\" \"39\"\n    \"for_texture\" \"generic028\"\n    \"use\" \"generic_metal1\"\n}\n\n{\n    \"for_entity_id\" \"39\"\n    \"for_texture\" \"generic029\"\n    \"use\" \"generic_metal2\"\n}\n```\n\nWhat it does is:\n1. If all `\"for_\"` fields match, apply values from `\"use\"` material (in this case `\"wood\"`)\n2. Additionally, override any extra fields/values with ones specified in this block\n\nAs we already have surface-patching ability, can just use that for patching materials directly for brush surfaces:\n```\n// mirror in toilet\n{\n    \"_xvk_surface_id\" \"2057\"\n    \"_xvk_material\" \"mirror\"\n}\n```\n\nQuestions:\n- Should it apply the first found rule that matches a given geometry and stop?\n  Or should it apply updates to the material using all the rules that matched in their specified order? Doing the first rule and stopping is more readable and perofrmant, but also might be verbose in some cases.\n- Should we do \"automatic\" materials? I.e. if there's no manually specified material for a texture named `\"<TEX>\"`, then we try to load `\"<TEX>_basecolor.ktx\"`, `\"<TEX>_normalmap.ktx\"`, etc automatically.\n\n# 2023-09-26 E302\nMap loading sequence\n```\n[2023:09:26|11:30:31] Couldn't open file overviews/c1a0d.txt. Using default values for overiew mode.\n[2023:09:26|11:30:31] CL_SignonReply: 2\n[2023:09:26|11:30:31] Signon network traffic:  10.380 Kb from server, 349 bytes to server\n[2023:09:26|11:30:31] client connected at 0.07 sec\n[2023:09:26|11:30:31] Error: SDL_GL_SetSwapInterval: No OpenGL context has been made current\n[2023:09:26|11:30:31] vk: Mod_ProcessRenderData(sprites/640_pain.spr, create=1)\n\n[2023:09:26|11:30:43] Loading game from save/autosave01.sav...\n[2023:09:26|11:30:43] Spawn Server: c2a5\n[2023:09:26|11:30:43] vk: Mod_ProcessRenderData(maps/c1a0d.bsp, create=0)\n\n[2023:09:26|11:30:43] Warning: VK FIXME Trying to unload brush model maps/c1a0d.bsp\n[2023:09:26|11:30:43] Error: VK NOT_IMPLEMENTED(x0): RT_KusochkiFree\n[2023:09:26|11:30:43] loading maps/c2a5.bsp\n[2023:09:26|11:30:43] Warning: FS_LoadImage: couldn't load \"alpha_sky\"\n[2023:09:26|11:30:43] Warning: FS_LoadImage: couldn't load \"solid_sky\"\n[2023:09:26|11:30:43] lighting: colored\n[2023:09:26|11:30:43] Wad files required to run the map: \"halflife.wad; liquids.wad; xeno.wad\"\n[2023:09:26|11:30:43] vk: Mod_ProcessRenderData(maps/c2a5.bsp, create=1)\n\n[2023:09:26|11:30:43] Loading game from save/c2a5.HL1...\n[2023:09:26|11:30:43]\nGAME SKILL LEVEL:1\n[2023:09:26|11:30:43] Loading CGraph in GRAPH_VERSION 16 compatibility mode\n[2023:09:26|11:30:43] Loading CLink array in GRAPH_VERSION 16 compatibility mode\n[2023:09:26|11:30:43]\n*Graph Loaded!\n[2023:09:26|11:30:43] **Graph Pointers Set!\n[2023:09:26|11:30:43] loading sprites/flare1.spr\n[2023:09:26|11:30:43] vk: Mod_ProcessRenderData(sprites/flare1.spr, create=1)\n.. more Mod_ProcessRenderData\n.. and only then R_NewMap\n```\n\n# 2023-09-28 E303\n## #526\nReplace textures for specific brush entities.\nFor a single texture it might be as easy as:\n```\n{\n\t\"_xvk_ent_id\" \"39\"\n\t\"_xvk_texture\" \"generic028\"\n\t\"_xvk_material\" \"generic_metal1\"\n}\n```\n\nFor multiple replacements:\n0. Multiple entries\n```\n{\n\t\"_xvk_ent_id\" \"39\"\n\t\"_xvk_texture\" \"generic028\"\n\t\"_xvk_material\" \"generic_metal1\"\n}\n\n{\n\t\"_xvk_ent_id\" \"39\"\n\t\"_xvk_texture\" \"generic029\"\n\t\"_xvk_material\" \"generic_metal2\"\n}\n```\n\n1. Pairwise\n```\n{\n\t\"_xvk_ent_id\" \"39\"\n\t\"_xvk_texture\" \"generic028 generic029 ...\"\n\t\"_xvk_material\" \"generic_metal1 generic_metal2 ...\"\n}\n```\n\n2. Pair list <-- preferred\n```\n{\n\t\"_xvk_ent_id\" \"39\"\n\t\"_xvk_texture_material\" \"generic028 generic_metal1 generic029 generic_metal2 ... ...\"\n}\n```\n\n# 2023-10-02 E305\n## Materials table\n\n### Operations\n- Clean\n- load materials from file\n\t- current format (mixes materials and selection rules a bit)\n\t- other formats (can only support named materials w/o any selection rules)\n\t- inherit/use from previously defined materials\n\t\t- needs index/value by name below\n- Get materials by:\n\t- value by tex_id\n\t- value by tex_id + rendermode\n\t- value by tex_id + chrome\n\t\t- ~~(do we need to specialize \"for_chrome\"? were there any cases where it would be useful?)~~ It seems not.\n\t- index by name (currently works by having a dummy texture with this name; reuses vk_textures hash search)\n\t- Lazy: Getting by value performs loading, getting by index does not.\n\n### Data structures overview\n- materials[mat_id] -- indexed by indexes independent of tex_id, referenced externally, requires stable index.\n\t- (typical material fields)\n\t\t- possibly lazily loaded\n\t\t\t- arg `-vknolazymaterials` for development. To immediately recognize missing textues, not until they are requested for the first time.\n\t\t\t- fallback onto default/error texture on lazy loading errors\n- tex_to_material[tex_id] table (hash table, array, whatever)\n\t- state -- {NotChecked, NoReplacement, ReplacementExists}\n\t\t- NotChecked -- means there was no explicit replacement, but we still can check for auto replacement (TEXNAME_roughness.png, etc)\n\t\t- NoReplacement -- there's nothing to replace with, use original texture with default material parameters.\n\t- mat_id -- index into materials[] table if there's a replacement\n    - rendermodes-specific overrides (fixed array? too expensive; linked list?)\n        - rendermode\n        - mat_id\n- name_to_material[] -- string \"name\" to mat_id\n    - hash table of some sorts\n\n# 2023-10-16 E313\n## Pre-next:\n- validation crash\n## Next:\n- KTX2 PR against upstream\n- texture leaks\n\t- better texture storage\n\t\t- hash map\n\t- texture lifetimes/refcounts\n\t- texture deletion\n\t\t- mass (for single device wait idle)\n\n# 2023-10-17 E314\n- [x] imagelib/ktx2 PR to upstream\n\t1. [x] Make a vulkan branch with the latest upstream merged in\n\t2. [x] Make another branch `upstream-ktx2` from upstream/master with imagelib changes hand-picked\n\t3. [x] Make a PR against upstream with ktx2\n\t4. [x] Make a PR against vulkan with recentmost upstream\n\n- [x] Contemplate texture storage\n\n# 2023-10-19 E315\nTried refcounts. They only break things, many textures get released prematurely.\nHunch: need to split external/refapi refcount-unaware functionality and hash map into two things:\n- old name->gl_texturenum table that is refcount-unaware\n- new name->vk.image refcount-aware table and api\n\n# 2023-10-20 E316\n## Texture mgmt refcount impedance mismatch: losing textures on changelevel\n1. ref_interface_t api is refcount-oblivious: it creates and destroys textures directly. Can't really update refcounts in these calls because they are not balanced at all. Can potentially call Load on the same texture twice, and then delete it only once. (Can it delete the same texture multiple times? Probably not -- need index, which *SHOULD* be inaccessible after the first delte by the API logic -- this is broken now with refcounts)\n2. Sequence of events:\n    1. Changlevel initiated\n    2. Textures for the old map are deleted using ref_interface api\n        - mostly us in ref_vk (so we can adjust), but there are a few possible calls from the engine and game.dll\n        - brings refcount down to \"1\" (many textures are still referenced from materials)\n    3. Texture for the new map are created using ref_interface api\n        - There are common textures that weren't deleted due to being referenced by materials, so they are considered being uploaded already.\n        - ref_interface_t is refcount-oblivious, so refcounts are not updated, i.e. remaining =1.\n    4. ref_vk finally notices the changelevel, and starts reloading materials.\n        - goes through the list of all old materials and releases all the textures\n        - old textures (but which should've been loaded for the new map too) with refcount=1 are deleted\n        - ;_;\n\n# 2023-11-07 E326\nWater :|\n\n## Overview\n- water is `msurface_t` with `SURF_DRAWTURB` (?)\n- Engine calls `GL_SubdivideSurface()` to produce `glpoly_t` chain of subdivided polygons for a given `msurface_t`\n\t- When? How? Is it correct?\n\t- What does it do exactly?\n- rendering uses `glpoly_t` to generate and submit heightmap-animated triangles\n- animated height depends on current camera position. Height is inverted if camera is underwater.\n- there are \"water sides\" with `PLANE_Z` flag. These are drawn only when `cl_entity_t.curstate.effects` has `EF_WATERSIDES` bit\n\t- water sides can be found in test_brush2\n\n# 2023-11-09 E327\nGL water vs test_brush2\n- `EmitWaterPolys()`\n    - is called by `R_RenderBrushPoly()`\n        - if commented out, the inner sphere disappears, as all other water\n        - is called from `R_DrawTextureChains()`\n            - is called from `R_DrawWorld()`\n    - is called by `R_DrawWaterSurfaces()`\n        - commenting out doesn affect anything ?! -- doesn't seem to be called at all\n\n- `PLANE_Z` check is in `R_DrawBrushModel()`\n\nGL:\n- default:          sphere=1 side=0\n- no PLANE_Z check: sphere=1 side=0.5 (?? two sides missing)\n\nVK:\n- default:          sphere=0 side=0\n- no PLANE_Z check: sphere=1 side=1\n\nEXPLANATION: `!=PLANE_Z` is only culled for non-worldmodel entities. Worldmodel doesn't cull by != PLANE_Z.\n\n# 2023-11-10 #E328\nMORE WATER\n- There are 2 msurface_t for water surfaces, one for each \"orientation\": front and back\n- The \"back\" one usually has SURF_PLANEBACK flag, and can be culled as such\n- For most of water bodies completely removing the SURF_PLANEBACK surface solves the coplanar glitches\n    - However, that breaks the trad rederer: can no longer see the water surface from underwater\n    - Also breaks the water spehere in test_brush2: its surfaces are not oriented properly and uniformly \"outwards\" vs \"inwards\"\n    - No amount of flag SURF_UNDERWATER/SURF_PLANEBACK culling produces consistent results\n\nWhat can be done:\n1. Leave it as-is, with double sided surfaces and all that. To fix ray tracing:\n    - Make it cull back-sided polygons\n    - Ensure that any reflections and refractions are delta-far-away enough to not be caught between imprecise coplanar planes.\n2. Do the culling later: at glpoly stage. Do not emit glpolys that are oriented in the opposite direction from the surface producing them.\n\n# 2023-11-13 E329\n## To cull or not to cull?\n- Original renderer culls backfacing triangles in general.\n- Culling leads to some visual glitches for RT:\n    - First person weapon models are designed to be visible only from the first person perspective.\n        - Culling leads to holes in shadows and reflections.\n        - [x] Can culling be specified per BLAS/geometry?\n            - `VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR` can disable culling for BLAS, regardless of ray flag\n    - Some alpha-tested geometries are not thin and have two faces: front and back.\n        - When culling: there are misaligned shadows that \"start from nowhere\", a few cm off the visible geometry.\n        - When not culling: there are geometry doubles, and shadow doubles, ladder backsides, etc.\n            - [ ] Can we do culling for alpha-tested geometries only?\n    - Leaking shadows in cs: https://github.com/w23/xash3d-fwgs/issues/507\n        - [-] Maybe shadow rays need front-face-culling instead? Or no culling?\n            - probably not: e.g. ladder back sides are broken.\n- Is there a performance penalty to cull for RT? (likely negligible)\n\nExtra considerations:\n- Medium boundaries.\n  Glass in HL also often comes as a non-thin brush box with all 6 sides.\n  Traditionally only the front-facing sided is not culled and rendered.\n  However, for proper \"physical\" ray tracing both boundaries between mediums are important.\n  - [ ] Not sure we want to get _that_ physical.\n  - [ ] Glass is rather thin anyway usually.\n  - [ ] What to do with water? The game suggests having two surfaces: front and back facing.\n\n## Water\n(see E326-E328 above)\nWater surfaces are generally visible to us as surfaces with `SURF_DRAWTURB*` flag.\nThey come in pairs: front and back facing.\n\nAll these surfaces have tesselation, and are updating their uvs (to draw \"turbulence\").\n\nProperties:\n- (potentially dynamic; known only at rendering time; only for non-worldmodel): waveHeight -- makes tesselated verts go up/down.\n- Transparency\n    - Opaque with currently disabled culling are prone to co-planarity issues.\n- Emissive -- hopefully fixed, no animated textures.\n     - [ ] Known cases of nonzero waveHeight for emissive surface? Hopefully not too many.\n\nBasic kinds of water:\n- with waveHeight=0 (dynamically)\n    - remains completely flat, but texture coordinates should be updated \"as turbulence\"\n        - No need to tesselate? Or turbulence still implies tesselation?\n    - [ ] How do we notice that it will not have waves?\n- with nonzero waveHeight\n    - Should have both waves and turbulence uvs\n    - Needs to be tesselated\n\n- Only worldmodel seems to be generating two-sided water surfaces\n- [ ] then how do other models make water visible from under?\n\n# 2023-11-14 E330\n## EVEN MOAR WATER\n- Culling all water surfaces by `SURF_PLANEBACK` (see E328)\n    - fixes worldmodel coplanarity\n    - breaks test_brush2 sphere: half of the surfaces look inward (red), another set look outward (green)\n        - EXPECTED: everything is green\n        - [-]: try detecting by glpoly normal alignment vs surface alignment\n            - Doesn't really work. Opposite alignment just means that this is a PLANEBACK surface\n    - [x] What works is: leaving only `SURF_UNDERWATER` surfaces. These seem to be directed towards \"air\" universally,\n        which is what we need exactly.\n- [-] Transparent (and non-worldmodel brush) water surfaces don't seem to have a back side msurface_t. How does GL renderer draw them?\n    - Hypothesis: they are reversed when `EmitWaterPolys()` is called with `reverse = cull_type == CULL_BACKSIDE`\n        - `CULL_BACKSIDE` is based on `camera.origin`, `surf->plane->normal` and `SURF_PLANEBACK`\n\n## How to do trans lucent surfaces\n### Opt. I:\nHave two of them: front and back + backface culling.\nEach side has an explicit flag which one is it: water/glass -> air, or air -> water/glass.\nShader then can figure out the refraction angle, etc.\n\n### Opt. II:\nHave only a single surface oriented towards air and no backface culling.\nShader then figures the medium transition direction based on whether it is aligned with the normal.\nSeems to be the preferred option: less geometry overall. Do not need to generate missing \"back\" surfaces, as only\nworlmodel has them.\nHowever: glass brushes still do have back surfaces. How to deal with those? Doesn't seem to break anything for now.\n\n# 2023-11-17 E332\n## Automated testing\nQ:\n- How to run? On which hardware?\n    - Steam Deck as a target HW.\n- How to run from GH actions CI?\n- Do we need a headless build? It does require engine changes.\n- How to enable/integrate testing into the build system?\n\n### Unit tests\nSome things can be covered by unit tests. Things that are independent of the engine.\nCurrently somewhat covered:\n- alolcator\n- urmom\n\nPossibly coverable:\n- Water tesselation\n- Math stuff (tangents, etc)\n- Parts of light clusters\n- Studio model:\n    - cache\n    - geometry generation\n- sebastian + meatpipe\n- brush loading\n\nThings that potentially coverable, but depend on the engine:\n- Loading patches, mapents, rad files, etc. -- Depend on engine file loading and parsing\n- Texture loading\n- studio model loading\n\n### Regression testing\nCheck that:\n- internal structures have expected values. I.e. that all expected entity/material/... patches are applied for a given map:\n    - Internal lists of brush models has expected items.\n    - Each brush model has expected number of surfaces, geometries, water models, etc.\n    - Each brush surface has expected number of vertices (with expected values like position, normal, uvs, ...) expected textures/materials, etc.\n    - There's an expected number of light sources with expected properties (vertices, etc)\n- the desired image is rendered.\n- internal state remains valid/expected during game/demo play\n- performance remains within expected bounds\n\n#### Internal structures verification\nWe can make a thing that dumps internal structures we care about into a file. This is done once for a \"golden\" state.\nThis state is now what we need to compare against.\nThen for a test run we do the same thing: we just dump internal state into another file. And then we just `diff -u` this file\nwith a golden file. If there are any differences, then it's a failure, and the diff file highlight which things have changed.\nThis way there's no immediate need to write a deserialization -- just comparing text files is enough.\nSerialization step is expected to be reasonably simple.\nPossible concerns:\n- Text file should be somewhat structured so that context for found differences is easily reconstructible.\n- Some things are not expected to match exactly. There can be floating point differences, etc.\n- Some things can be order independent. Serializator should have a way to make a stable order for them.\n\nPossible serialization implementation:\n- Similar to R_SPEEDS, provide a way to register structures to be dumped. Pass a function that dumps these structures by `const void*`\n- This function can further pass sub-structures for serialization.\n- Pass array of types/structs for serialization. Possibly with a sort function for stable ordering.\n- Pass basic types for serialization, possibly with precision hints.\n- Pass strings for serialization.\n\nWhat should be the format? Simple text format is bad when we have arbitrary strings.\nSomething json-like (not necessarily valid json) might be good enough.\n\nUpdates to code/patches/materials might need changes to the golden state.\nQ: materials are tracked in a different repo, need to have a way to synchronize with golden state.\nWe can track golden state also in a different repo, have it link to PBR repo as a submodule (or, better, just a link to a commit).\nAnd it can itself be a submodule for xash3d-fwgs-rt.\n\n#### Comparing rendering results\nNeed to make a screenshot at desired location. Can be the first frame of a playdemo, or just a save file.\nThen need a way to compare images with given error tolerance. OR we can make everything (that we can) completely reproducible.\nE.g. fix all random seeds with known values for testing.\nShould it be a special mode, e.g. run with a save/demo, make a first screenshot and exit?\nCan we do multiple screenshots during a timedemo? Concern here is that it might be not stable enough (e.g. random particles, etc).\n\n#### Validating internal state\nBasically, lots of expensive TESTING-ONLY asserts everywhere.\nWould probably benefit from extensive context collector. `proval.h`\n\n#### Performance tracking\nRelease build (i.e. w/o expensive asserts, state validation, dumping or anything) with an ability to dump profiling data.\nRun a short 1-2min timedemo, collect ALL performance stats for the entire lifetime and ALL frames:\n- all memory allocations\n- all custom metrics\n- all cpu and gpu scopes, the entire timeline\nThis is then dumped into a file.\nThen there's a piece of software that analyzes these dumps. It can check for a few basic metrics (e.g. frame percentiles,\namount and count of memory allocations, etc.) and compare them against known bounds. Going way too fast or too slow is a failure.\nThe same software could do more analysis, e.g. producing graphs and statistics for all other metrics.\n\n# 2023-11-20 E333\n## Comparing rendering results\n1. Make a script that loads save files, and takes screenshots. The result is a bunch of screenshots.\n2. Make a tiny program comparing these screenshots with golden ones.\n3. Make initial set of golden screenshots.\n4. Make a representative set of places to take screenshots of.\n5. Make a script taking screenshots, comparing results and presenting that as a build artifact.\n5. (draw the rest of the fucking owl) Integrate this into CI.\n\nObservations:\n- Need to fix window/screen resolution (doesn't work with tiling managers that well)\n- There's still some console garbage on screen\n- Making random_seed constant is not enough. There are still some per-pixel differences. Not sure why. Timing dependent, getting a different frame?\n\n# 2023-11-21 E334\n## Reproducible rendering\n### More observations\n- PNG is super slow to write. 300-700ms. Copying screenshots from vulkan is ~30ms.\n- There are still lots of small pixel differences even for basecolor/normal and other similar light-independent things.\n    It would seem that there's inherent timing instabilites -- we can't guarantee the same game state even for the\n    first frame (verify that?).\n- Image comparison is slow. ~~4.7s for all images.~~\n    After optimization: 1.3s for everything. Built using `-O3 -march=native`. Saves into tga. Was > 16s.\n\n# 2023-12-01 E340\n## Dynamic max frame size\n- Why do we even need it: current max of UHD is\n\t- (a) too big for most use cases (e.g. steam deck is 800p and can never be larger), wastes too much memory\n\t- (b) may be too small for the bright hidpi future\n- Issues with making it dynamic:\n\t- Need to resize resources, i.e. g-buffer images.\n\t- We can't yet free empty devmem allocations. Freeing devmem entries leads to a bit of refactoring. Need to manage\n\t  them using a freelist, handle holes, etc. Or, alternatively, do the dumb iteration over everything twice:\n\t  first, looking for an existing compatible allocation,\n\t  second, looking for a hole, if no compat allocation found.\n\t  Whether that's too slow will be visible when we embark on changelevel optimization journey. And if it is, we\n\t  could replace it with proper freelist thing from alolcator.\n\n# 2023-12-05 E342\n## Shader profiling\n### Data sources\n- VK_KHR_performance_query\n    - Available only on AMD ≤ RX 6900 on Linux and Intel cards\n    - Example list of metrics available on my AMD card:\n        - Got 17 counters:\n            - 0: command GRBM/GPU active cycles, C (cycles the GPU is active processing a command buffer.)\n            - 1: command Shaders/Waves, generic (Number of waves executed)\n            - 2: command Shaders/Instructions, generic (Number of Instructions executed)\n            - 3: command Shaders/VALU Instructions, generic (Number of VALU Instructions executed)\n            - 4: command Shaders/SALU Instructions, generic (Number of SALU Instructions executed)\n            - 5: command Shaders/VMEM Load Instructions, generic (Number of VMEM load instructions executed)\n            - 6: command Shaders/SMEM Load Instructions, generic (Number of SMEM load instructions executed)\n            - 7: command Shaders/VMEM Store Instructions, generic (Number of VMEM store instructions executed)\n            - 8: command Shaders/LDS Instructions, generic (Number of LDS Instructions executed)\n            - 9: command Shaders/GDS Instructions, generic (Number of GDS Instructions executed)\n            - 10: command Shader Utilization/VALU Busy, % (Percentage of time the VALU units are busy)\n            - 11: command Shader Utilization/SALU Busy, % (Percentage of time the SALU units are busy)\n            - 12: command Memory/VRAM read size, b (Number of bytes read from VRAM)\n            - 13: command Memory/VRAM write size, b (Number of bytes written to VRAM)\n            - 14: command Memory/L0 cache hit ratio, b (Hit ratio of L0 cache)\n            - 15: command Memory/L1 cache hit ratio, b (Hit ratio of L1 cache)\n            - 16: command Memory/L2 cache hit ratio, b (Hit ratio of L2 cache)\n- VK_KHR_shader_clock\n    - Available almost everywhere\n    - Enables:\n        - https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shader_clock.txt\n            - `uint64_t clockARB()` || `uvec2 clock2x32ARB()`\n            - gives subgroup-local monotonic time in unspecified units\n        - https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_realtime_clock.txt\n            - `clockRealtimeEXT` || `clockRealtime2x32EXT`\n            - gpu-global time in unspecified units\n\n### Data collection\n#### Shader clocks\nNeed to get per-pixel values out of shader.\n##### Simplest method: thermal map\nThe simplest method is to have yet another texture that we can write into, and have a rt_debug_display_only output.\n- Texture format? E.g.:\n    - rgba16f -- 4 channels for 4 delta values scaled by something from UBO\n    - rgba16f -- 4 channels for 4 absolute values scaled by UBO const\n\nThis doesn't need anything special. Can use basically the same machinery we have now.\n\n##### On-GPU profile analysis\n1. Shader specifies arbitrary buffer+struct\n2. Sebastian reads struct size (w/o parsing fields) and notes that in meat file\n3. Native code just creates the buffer for GPU only, w/o being aware of what's inside.\n4. Have a dedicated compute pass reading these buffers and summarizing them into a texture or a buffer defined at compile time.\n5. Read this texture/buffer after frame.\n\nThis would need:\n- sebastian parsing buffer array item size\n- meat buffer item size metadata\n- native buffer creation\n- native buffer r/w state/barrier tracking\n\nIf extracting data from buffer:\n- passing vk buffer data back to cpu land w/ synchronization\n\n##### Universal method would be\n1. Allow specifying arbitrary structures/buffers in shaders.\n2. Teach sebastian.py to parse these structures and encode their layout in meat files\n3. Native could would create such structures with specified size/resolution (how to know expected size?)\n4. Native would then copy them over to CPU land and parse based on meat metadata.\n\nThis would need the same as above, plus:\n- sebastian parsing struct fields\n- meatpipe reading fields\n\n- Q: how to analyze this volume of data on CPU?\n- A: probably should still do it on GPU lol\n\nThis would also allow passing arbitrary per-pixel data from shaders, which would make shader debugging much much easier.\n\n# 2023-12-12 E346\n## Skyboxes\n### Current state\n- `R_TextureSetupSky()`\n    - called from:\n        ← `vk_scene.c`/`R_NewMap()`\n        ← engine ?? -- seems optional, r_soft doesn't implement it. Set on:\n            - `skybox` console command\n            - certain movevars change, whatever that is\n    - `unloadSkybox()`\n    - for [pbr/, old/] do\n        - `CheckSkybox()`\n            - make sidenames and check whether files exist\n        - `loadSkybox()`\n            - `unloadSkybox()`\n            - make sidenames\n            - `FS_LoadImage()` and `ImageProcess()`\n            - `R_VkTextureSkyboxUpload(sides)`\n    - if failed and not default already: `R_TextureSetupSky(default)` (recurse)\n\n# 2023-12-14 E347\n## TIL engine imagelib `FS_LoadImage()`:\n1. Pick format based on extension\n2. If no extension is specified, try all supported extensions in sequence.\n3. If loading single file failed, try to load it as skybox cubemap:\n   Go through all sides suffixes and try to load them in the similar fashion:\n   if no extension, then try all supported extensions\n\n- `Image_Process()` can only rotate uncompressed formats. (Technically it might be possible to also\n  rotate some compressed format, which will amount to just reordering blocks, and then reordering block\n  contents. Mendokusai). Therefore, we can't just replace png sides with compressed ktx2 sides directly.\n  KTX2 sides should be pre-rotated.\n\n# 2023-12-15 E348\n## Textures layout\nimagelib image buffer layout:\n\t- sides[1|6]\n\t\t- mips[biggest -> smallest]\n\t\t\t- (pixel data)\n\nKTX2 file layout:\n- mips[smallest -> biggest]\n\t- sides[1|6]\n\t\t- (pixel data)\n\n# 2023-12-18 E349\n## Xash vs KTX2/vk cubemap face order\nVulkan order:\n+X, -X, +Y, -Y, +Z, -Z\nrt, lf, bk, ft, up, dn\n 0,  1,  2,  3,  4,  5\n\nXash order:\nft, bk, up, dn, rt, lf\n\nRemap (KTX2 -> xash || xash[face] = KTX2[map[face]]):\n 3,  2,  4,  5,  0,  1\n\nRemap (xash -> vk, vk[map[face]] = xash[face]):\n 4,  5,  1,  0,  2,  3\n??? this shoudln't work\n\ndefault cubemap order:\nxash   vk (remapped)\n+Y = -X\n+X = +Z\n+Z = +Y\n\n# 2023-12-22 E352\n## sRGB vs γ blending\nOriginal:\n    `color = a + b`\nOur:\n    `color = sqrt(a*a + b*b)`\nThere's nothing we can to do `a` only that would make it fake the \"original\" mixing result.\n\n# 2023-12-28 E353\n## Passing colors from all over the place into `trace_simple_blending.glsl`\n- color = mm_color * texture_color * geom.vertex_color * alpha\n\t- alpha = mm_color.a * texture_color.a * geom.vertex_color.a\n\t- mm_color = model.color * kusok.material.base_color\n\t\t- model.color -- already linearized\n\t\t- kusok.material.base_color = mat->base_color * override_color\n\t\t\t- mat->base_color -- specified in .mat files by hand\n\t\t\t\t- [x] Which colorspace should it be specified in?\n\t\t\t\t      Currently it is passed in as is, which means that it's accidentally linear.\n\t\t\t- override_color -- passed from the engine through `R_RenderDrawOnce()`, called from triapi\n\t\t\t\t- [x] sRGB-γ, should linearize\n\t- texture_color -- just texture sampled color. sRGB-γ vs linear is specified at VkImageView level at loading time\n\t- geom.vertex_color -- barycentric-lerped from vk_vertex[].color\n\t\t- vk_vertex[].color -- rgba8\n\t\t\t- [x] which colorspace? Should be sRGB-γ originally\n\t\t- [x] Do we need to linearize it? YES\n\t\t\t- [x] Before lerping or after? BEFORE -- already done\n\n- Should α be converted from gamma to linear?\n\t- Doing so:\n\t\t- seems kinda logical -- everything is gamma-space in engine, so it probably should be.\n\t\t- fixes some 'background' sprites transparency\n\t\t- makes too-brighs c0a0c beams darker (but kinda too dark imo)\n\t\t- breaks sprite animation lerping -- now we need 2 native gamma-to-linear functions, wich alpha conv and w/o\n\nAs usual -- original sRGB-specialized game art is painfully incompatible with modern linear PBR.\nThe best way to address it (hopefully w/o breaking too much linear rendering math) remains to be discovered.\n\n# 2023-12-29 E354\n## Sprite animation lerping woes\nProblem: converting alpha from sRGB to linear fixes various blending glitches, but makes animation blink.\n\nPossible approaches:\n1. Original math: pass and compute colors and alphas for simple blending in the original (sRGB-γ) colorspace. PBR-incorrect, but should give the original look.\nPro:\n- original look\n- should solve a whole class of issues.\n- Relatively separate from physically-correct math, doesn't interfere that much.\n\tExcept for background + emissive part.\n- Individual PRB-ized parts of blending could be extracted out from legacy mode gradually.\nCons:\n- special legacy blending code.\n- Passing these things around is obnoxious: needs lots of special code for model passing.\n- Large amount of work.\n\nPossible implementation plan:\n- `vk_ray_model.c`: sRGB-to-linear colorspace conversion should be made based on `material_mode`:\n  do not convert for legacy blending modes\n\t- what to do with `mat->base_color`, which is assumed linear? Leaving it as-is for now.\n- sRGB-γ-ize linear texture color (still a bit different from legacy. alt: specifically for sprites and beams textures mark them as UNORM)\n- keep/lerp vertex colors in sRGB space\n\n2. Special code for sprite lerping: add second texture channel, add lerp parameter, etc.\nPro: should be relatively easy to do.\nCons: Fragile special code for special case.\n\n3. Track alpha channel with animation lerping in mind: only linearize it for no-animation case.\nPro: no additional parameters to pass to shaders.\nCons: math might not converge on a good solution.\n\n4. Generate intermediate textures.\nPro: no special code for shaders/model passing.\nCons: ridiculous texture explosion\n\n5. Hand-patch things that look weird. E.g. for known sprite/beam textures specify how their alphas should be mapped.\n\n\n# 2024-05-07 E376\n## Resource tables\n### Types\n- `rt_resource_t` [vk_rtx.c]:\n\t- name, `vk_resource_t`, image, refcount, source_index_plus_1\n- `vk_resource_t`/`vk_resource_s`/`*vk_resource_p` [ray_resources.h]:\n\t- desc `type`, state `read/write`, desc `value`\n\n### Variables\n- `g_rtx.res[]` -- `rt_resource_t`[`MAX_RESOURCES`=32]\n\t- `findResource(name)` / `findResourceOrEmptySlot(name)`\n\t- access by builtin index names like `g_rtx.res[ExternalResource_...]` for both read and write\n\t\t- `performTracing()` write resource desc values passed from outside on each call\n\t- new resources are added in `reloadMainpipe()`\n    - resource with zero refcount are destroyed in `cleanupResources()`\n\n\n# 2024-11-26\n`./waf clangdb` produces `compile_commands.json` file inside of the build directory. All the paths in the file are relative to that directory.\nIf the build directory is something 2nd level, like `build/amd64-debug`, and the file is then symlinked to (as nvim/lsp/clangd only looks for the file in the root and in the `./build` dir), then it confuses nvim/lsp/clangd.\nSolution: make build dir literally just `./build`.\n\n\n# 2024-11-27 E381\n## Removing staging flush\n\n### vk_scene.c/reloadPatches()\n- Can ignore for now\n\n### Staging full\n- (I) Just allocate another buffer for staging\n- (II) Figure out why the hell do we need so much staging memory\n\t- PBR/remastered textures\n\t\t- possible solution: lazy/ondemand loading\n\n### vk_brush.c / collect emissive surfaces\n- (I) try to merge emissive collection with surface loading\n- (II) convert from pushing material data to pulling. Not really clear how to do easily.\n\n# 2024-12-19 E386 resources / pre render graph\nWhat do we want? Resources and producers! When do we want it? Maybe next stream.\n- Resource itself: `r_vk_resource_i` -- interface\n\t- name\n\t- type\n\t- producer\n\t- (opaque impl: e.g. in the same alloc, right after this header struct)\n- Resource manager:\n\t- `r_vk_resource_i* resources[]` -- collection of pointers to resources\n\t\t- dynamic array, hash table, etc..\n\t- `findResourceByName(const char *)`\n\t\t- also by type? not sure. Requester can check for the expected type too.\n\t- `registerResource(r_vk_resource_i *res)`\n\t- `deregisterResource(..)`\n- Resource producer\n\t- When resource is used, the user should call `resource->produce()` or something like that.\n\t- ????? It's not really clear how to do this properly. E.g. how to invoke producing only once per frame?\n\n# 2025-01-23 E387\n## CI/performance on BC-250\n- BC-250 doesn't expose performance_query.\n  - why: `radv_perf_query_supported()` checks for `>= GFX10_3` saying some register interference\n  - what to do:\n    - make a simple perf query test app (and put it on github or something)\n    - just try force-enabling perf query on `GFX10` (bc250 is gfx1013)\n\t- if fails: more research is needed, probably unfeasible at my skill level\n\n# 2025-03-05 E397\n## Abstract graph solver\n- `struct resource`\n\t- type()\n\t- descriptor()\n\t- producer() ->\n- `struct producer`\n\t- dependencies[] -> resource\n\t- results[] -> resource\n\t- dispatch(combuf, barriers)\n\n# 2025-03-14 E400\n- Resource\n    - ...\n    - `Producer *producer;`\n        - optional -- can be `NULL`\n        - action to perform to make resource available for reading\n        - assumption: resource can be produced only once per frame\n        - and then read multiple times\n- Producer\n    - `const char *name;`\n    - dtor?\n        - currently all potential dynamic producers are handled by meatpipe\n        - so for now there's no need for dtor\n    - `void produce(combuf, ...frame context?.. )`\n    - `u32 frame_sequence_tag;`\n        - visitor must ensure that producer produces products only once\n    - Speculative:\n        - `rt_resource_t *products[];`\n            - produced resources\n            - no need for it now -- all dynamic producers are handled in meatpipe explicitly\n            - but might be beneficial for extracting graph from resource + producers soup\n"
  },
  {
    "path": "ref/vk/TODO.md",
    "content": "# Current\n\n## 2025-08-XX EN/A offline\n- [x] issue: something something validation swapchain semaphore\n- [x] issue: query pool is not destroyed\n- [x] validation about profiling lock being held incorrectly\n\t- https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html#VUID-vkQueueSubmit-pCommandBuffers-03220 says:\n\t- needs to hold profiling lock for all command buffers in submission, if ANY of them are using perf query.\n\t- unnatural to maintain, so:\n\t- [x] add `-vkperfquery` cli arg to explicitly enable performance query and acquire profiling lock immediately\n- [x] keep track of current perf query object, gracefully handle when it changes\n- [x] display percent/permyriad properly\n- [x] use logger in r_speeds\n\n# Next\n- [ ] `r_speeds_graphs` w/ numerical args\n- [ ] alt/TODO: universal metrics\n\t- [ ] refactor metrics code, allow registering and de-registering metrics\n- [ ] track Y offset for scopes blocks properly\n- [ ] r_speeds\n    - [ ] s/metric/unit/\n- [ ] make a function that \"retires\" the commmand buffer and reads all the queries, making this data passively available.\n  this will also move us closer to explicit after-combuf cleanup handling\n\t- [ ] combuf state machine: what states is it in?\n\n# Upcoming\n- [ ] Figure out naming, code style, make clang-format\n- [ ] performance profiling and comparison\n- [ ] rendertests TODO -- blocked by external infra stuff\n  - [ ] script:\n    - [ ] prepares installdir in tmpfs (transient docker/podman volume?)\n    - [ ] takes vanilla valve steam HL data into installdir\n    - [ ] applies PBR texture patches given known commit id\n      - alt: vanilla HL data could already include PBR textures\n    - [ ] installs xash3d into this installdir\n    - [ ] runs headless x11/wayland session\n      - alt: expect it to be running on the platform already\n    - [ ] gets rendertests with given commit id\n    - [ ] runs rendertests\n    - [ ] generates:\n      - [ ] relevant metadata: all repos commit ids, current date, current HW (GPU, CPU, mesa+kernel vers, mem, etc)\n      - [ ] summary json with overall diff scores, and binary pass/fail result for each scene/channel\n      - [ ] huge dated archive with report and images\n  - [ ] weird stripes on some channel images\n    - [ ] data from previous maps? why?\n- [ ] framectl frame tracking, e.g.:\n\t- [ ] wait for frame fence only really before actually starting to build combuf in R_BeginFrame()\n\t\t- why: there should be nothing to synchronize with\n\t\t- why: more straightforward dependency tracking\n\t\t- why not: waiting on frame fence allows freeing up staging and other temp memory\n- [ ] Remove second semaphore from submit, replace it with explicit barriers for e.g. geom buffer\n\t- [x] why: best practice validation complains about too wide ALL_COMMANDS semaphore\n\t- why: explicit barriers are more clear, better perf possible too\n\t- [ ] Do not lose barrier-tracking state between frames\n- [ ] Render graph\n\n# Log\n\n## 2025-07-11 E402 Payment Required\n- [x] add commmand to list performance query counters\n- [x] add command/variable to select enabled counters\n\t- [x] add command to enable/disable profiling counters -- enabled when list of counters is not empty\n- [x] visualize counters in r_speeds display\n- [x] issue: misaligned perf query lock\n\n## 2025-06-XX EN/A offline\n- [x] per-scope perf query counters\n  - [x] .... `VUID-vkCmdBeginQuery-queryPool-01922` seemingly prohibits overlapping queries.\n    - [x] Cannot do hierarchical scopes, and likely must do a full barrier at each scope end in this mode.\n          No idea how to expose this.\n      - ~~~[ ] Easiest way is to allow this only for a single scope per frame.~~~\n      - [x] Cover single-command scopes explicitly (by flag)\n  - [x] return counters tied to scopes\n    - [x] refactor how this data is returned\n\t- [x] fixup r_speeds to display the new format for GPU\n\t- [x] register new GPU scopes properly\n\t- [x] restore concurrent scopes overlap stacking\n- [x] measure counters for the entire command buffer\n- [x] convert and pass named results to r_speeds.c\n        - [x] unify gpu and cpu scopes with timestamps\n        - [x] add universal counters, attachable to both cpu and gpu scopes\n- [x] address r_speeds metrics/graphs by index number instead of full text name\n\n## 2025-05-05 E401 VK_KHR_performance_query\n- [x] detect perf query availability and enable it\n- [x] enumerate counters\n- [x] scaffold the query pool code\n\n## 2025-03-21 EN/A offline\n- [ ] Make producers\n    - [x] Builtin producers\n        - [x] geometry: vertices, indices\n        - [x] TLAS\n        - [x] UBO\n        - [x] model_headers\n\t\t- [x] light\n\n## 2025-03-14 E400 graphores p.1\n- [x] Producer interface\n    - [x] made notes\n- [ ] Make producers\n    - [ ] Builtin producers\n        - [x] lights\n        - [x] kusochki\n            - [x] extract to its own module\n    - [ ] Meatpipe\n\n## 2025-03-11 E399 resograph p.4\n### Barriers\n- [x] swapchain barrier warning + error\n- [x] skybox_placeholder unexpected barrier values\n    - [x] skybox texture usage issues noop barrier for some reason\n- [x] no meatpipe g-buffer barriers?!\n- [x] remove combuf tags?\n- [x] move combuf begin to frame end, where it is used\n- [x] API refactor:\n    - [x] barrier object + function adding buf/img directly to vk barriers type\n\n## 2025-03-07 E398 resograph p.3\n- [x] resource types\n\t- [x] buffer pointer type (non-owning)\n\t- [x] TLAS type\n\t- [x] storage image pointer type (non-owning)\n\t- [-] ping-pong images\n### Off-stream\n- [x] add resource dtors\n- [x] Move all resources/state/barrier management to passes\n\n## 2025-03-06 EN/A offline\n- [ ] hide resource internals; rt_resource_t is just a header\n\t- [ ] resource types\n\t\t- [x] dummy \"return descriptors\" type, no barriers\n\t\t- [x] sampled image pointer type (non-owning)\n\n## 2025-03-05 E397\n- [ ] hide resource internals; rt_resource_t is just a header\n    - [ ] `rt_resource_t` interface spec/API\n\t\t- [x] acquire_descriptor func\n\n## 2025-03-04 E396\n- [x] fix discontinuity glitches in rendertests\n- [ ] hide resource internals; rt_resource_t is just a header\n    - [x] Disown `rt_resource_t`'s from vk_resources.c -- only store pointers\n        - why: rt_resource_t will be an interface with variable size depending on type\n        - [x] vk_resources module only stores pointers\n        - [x] replace alloc with register\n\n## 2025-02-26 EN/A: offline\n- [x] Metapass sketch\n\t- [x] move meatpipe perform there\n- [x] Metapass -- construct one from meatpipe + resources\n\t- [x] make meatpipe just a parser, load passes in metapass\n    - [x] moved everything into meatpipe, meatpipe === metapass\n\n## 2025-02-24 E395\n- [x] register resources\n    - [x] skybox\n\n## 2025-02-17 E394\n- [ ] register resources\n    - [x] kusochki\n    - [x] model_headers\n    - [x] indices\n    - [x] vertices\n    - [x] lights\n    - [x] light_grid\n    - [x] textures\n    - [x] tlas\n    - [x] ubo\n\n## 2025-02-13 E393\n- [ ] register all resources in their modules\n    - [ ] resource management refactoring:\n        - [ ] resource automatic resolution: prducing, barriers, etc\n        - [ ] resource destruction\n    - [ ] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc)\n### after stream\n- [ ] register existing resources (tlas, buffers, temp images, ...) in their producers\n\t- [x] blue_noise_texture as a quick test\n\t- [ ] all textures\n\n## 2025-02-10 E392\n- [x] fix vk_studio ASAN fail\n\n## 2025-02-06 E391\n- [ ] rendertests\n  - [x] add cvars to renderscript\n  - [x] update gold\n  - [ ] how to tie repo state to tests state\n      - [ ] propose: add tests-referencing `RENDERTESTS_COMMIT=` envvar to gh/workflow\n- [x] vk_studio fails ASAN\n- [x] fix zero-size asan errors in r_speeds\n\n## 2025-02-04 E390\n- [x] run render tests\n- [x] check diff wrt non-ref-vk changes\n- [ ] rendertests\n  - [x] update gold?\n    - [ ] what happened to fresnel\n        - [x] gold before or after the new brdf? -- no gold is with new BRDF\n        - [x] is it dontnoiser? -- YES\n        - [ ] what exactly changed?\n        - [ ] fix it back\n            - [x] created issue for lifekilled: https://github.com/w23/xash3d-fwgs/issues/759\n\n## 2025-01-30 E389\n- [x] `PARM_TEX_FILTERING`\n- [x] Fix some missing textures\n\n## 2025-01-23 E387\n- [x] (local) waf prefix shenanigans\n- [x] merge resources branch into vulkan\n  - [x] reverse-merge vulkan\n  - [x] ghci upload s/v3/v4/\n- [x] merge from upstream\n- [ ] discuss further agenda\n\n## 2024-12-17 E385\n- [x] fix rendering on amdgpu+radv\n### After stream\n- [x] cleanup TLAS creation and building code\n\n## 2024-12-12 E384\n- [x] track image sync state with the image object itself (and not with vk_resource)\n\n### After stream\n- [x] Proper staging-vs-frame tracking, replace tag with something sensitive\n\t- currently assert fails because there's 1 frame latency, not one.\n\t- [x] comment for future: full staging might want to wait for previous frame to finish\n- [x] zero vkCmdPipelineBarriers calls\n\t- [x] grep for anything else\n\n## 2024-12-10 E383\n- [x] Add transfer stage to submit semaphore separating command buffer: fixes sync for rt\n- [x] Issue staging commit for a bunch of RT buffers (likely not all of them)\n- [x] move destination buffer tracking to outside of staging:\n\t- [x] vk_geometry\n\t- [x] vk_light: grid, metadata\n\t- [x] vk_ray_accel: TLAS geometries\n\t- [x] vk_ray_model: kusochki\n- [x] staging should not be aware of cmdbuf either\n\t- [x] `R_VkStagingCommit()`: -- removed\n\t- [x] `R_VkStagingGetCommandBuffer()` -- removed\n- [x] Go through all staged buffers and make sure that they are committed\n- [x] Commit staging in right places for right buffers\n- [x] Add mode staging debug tracking/logs\n\n### After stream\n- [x] Fix glitch geometry\n\t- [x] Which specific models produce it? Use nsight btw\n\n## 2024-05-24 E379\n- [ ] refactor staging:\n\t- [ ] move destination image tracking to outside of staging\n\t\t- [x] vk_image ← vk_texture (E380)\n\t\t- [x] implement generic staging regions (E380)\n\t\t- [ ] implement stricter staging regions tracking\n\n## 2024-05-07 E376\n- [ ] resource manager\n    - [x] extract all resource mgmt from vk_rtx into a designated file\n    - [ ] register all resources in their modules\n    - [ ] massage resource state tracking (read-write vs write-current; `value` field consistency, etc)\n\n## 2024-04-12 E374\n- [x] ~~`-vknort` arg to force-disable RT at init time~~ -- reverted on 2024-04-29\n\n## 2024-03-21 E372: agonizig over agenda\n### Player-visible essentials and blockers. Big projects.\n- [ ] Light clusters, sampling, and performance -- 90fps HDR on a Steam Deck\n- [ ] Transparency, refractions: glass, water, etc\n- [ ] Moar and moar correct bounces\n- [ ] Denoiser\n- [ ] Decals\n- [ ] Volumetrics and fog\n- [ ] HDR and tonemapping\n\n### Invisible blockers -- foundation/systems stuff\n- [ ] Render graph\n- [x] and resource tracking -- track textures, buffers+regions ownership and usage, automatic barriers, etc.\n- [ ] Modules and dependencies tracking\n- [ ] Integrate rendertests into CI\n\n### Small things\n- [ ] Material patching refactoring: do not load any patched textures before they are referenced by the engine itself.\n\tOnly load patched textures for the textures that are in fact used by something.\n\n### Nice-to-have\n- [ ] Split Vulkan+RT from xash specifics, start preparing it for being a standalone thing.\n\t- [ ] clang-format for it\n\n# Previously\n## 2024-02-05 E373\n- [x] Skybox for traditional renderer\n\t- [x] Sky pipeline\n\t- [x] Submit SURF_DRAWSKY draw commands\n\t- [x] Use original skybox for trad renderer\n\n## 2024-02-01 E371\n- [x] tune A-Trous step widths for different channels\n\t- [x] multiple passes -- core of the paper lol\n- [x] fix no-hit bounce absent legacy blending\n- [x] update render tests\n- [x] add new channels to render tests\n- [x] add white furnace render test\n- [ ] :x: temporal glitches with dontBlurSamples() and ATrous → no longer reproduces\n- [x] add `-vkdbg_shaderprintf` arg to explicitly enable shader debug printfs\n\n## 2024-01-29 E370\n- [x] bounce > 1 brighness\n- [ ] tune A-Trous step widths for different channels\n\t- [x] tune parameters\n\t- [x] \"cone width\"\n\t- [x] different parameters/radii for different channels\n\t- [ ] multiple passes -- core of the paper lol\n\n## 2024-01-26 E369\n- [x] white furnace test\n\t- [x] do it using display_only mode\n\t- [x] do it via separate flag\n\t- [x] too dark indirect: blurSamples() returns values too small (incorrect sigma|scale?)\n\t\t- [x] do a box blur test\n\t\t- [x] do A-trous wavelet denoiser\n\t- [x] diffuse and specular debug display modes\n- [x] why did direct lighting became brighter on c2a5?\n\n## 2024-01-23 E368\n- [ ] specular bounce\n    - [x] specular-vs-diffuse choice based on metalness+frensel\n\t\t- [x] better spec-vs-diff bounce type estimation\n\t\t\t- [x] better: also include fresnel\n\t\t\t- [ ] best: see literature\n\t\t\t\t- [ ] brdf.h\n\t\t\t\t- [x] rt gems 1/2\n\t\t\t\t- [ ] papers please?\n\t- [ ] BRDF material params attenuation\n\t\t- [x] try improving, multiply specular by fresnel\n\n## 2024-01-22 E367\n- [ ] specular bounce\n    - [ ] specular-vs-diffuse choice based on metalness+frensel\n\t\t- [x] simple: metalness only\n    - [ ] VNDF? sampling\n\t\t- [x] mindlessly copypasted from some paper\n\t\t- [ ] figure out what all of that means\n\t- [ ] BRDF material params attenuation\n\t- [ ] decide on the diffuse-vs-specular out channel based on the first bounce\n\n## 2024-01-19 E366\n- [x] investigate more shading nans\n\t- found zero normals in studio models, see #731\n- [x] guns and transparency → added legacy transparency overshoot threshold\n- [x] cvar to force culling\n- [x] flashlight is too bright\n- [x] bounce diffuse is still way darker than before\n    → it shouldn't have been multiplied by diffuse value\n\n## 2024-01-18 E365\n- [-] flashlight far circular glitches\n\t- This is due to f32 precision not being enough when working with small (light radius ~=1) and large (light\n\t  distance ~=1e4) numbers.\n- [x] patchable sun angle\n\t- [ ] :x: does qrad already have something for that? → no it doesn't\n- [x] cleanup this TODO\n\n## 2024-01-16 E364\n- [x] P NaNs\n\t- [x] need to remove degenerate triangles\n- [x] light_environment is too dark\n- [ ] add direct_{diff,spec} to rendertests → only can do for this handmade-brdfs branch\n\t- [ ] :x: and rerun tests for vulkan to get new gold images → imuposshiburu, see above\n\n## 2024-01-15 E363\n- [x] filter out invalid (r=0, etc) lights in native\n\t- [-] :o: already do; it seems that clusters are not getting updates → see #730\n- [x] pass point lights r² directly?\n- [x] move empirical scaling to native code\n- [x] modify point light radius in entity patches → already done\n\t- [x] adjust brightness based on radius? → already done\n- [ ] :x: ~~common intersection-local-normal-oriented basis~~ → point light construct light-oriented frames, not reusable\n\n## 2024-01-12 E362\n- [x] point→spherical light sampling\n\t- [x] 1/pdf → pdf *= 2π\n\t- [x] disk sampling\n\n## 2024-01-11 E361\n- [x] fix zero-area polygon lights nanites, fixes #461\n\t- [x] c1a1a NaNs are still there\n- [x] fix point light computation instabilites\n\t- [x] need proper sampling asap, as different instabilities approaches are visually different, and it's impossible to reason which one is preferable\n- [x] add material debug display mode\n- [ ] vulkan validation layers crashes on too many `debugPrintfEXT` messages\n\n## 2024-01-09 E360\n- [x] validate all intermediate and final outputs against invalid values, complain into log\n- [ ] brdf math surprising edge cases\n    - [ ] alpha^2 == 0 ???\n    - [ ] various N,L,V collinearities, zero denoms and infinities\n        - [ ] h_dot_l | h_dot_v < .0 because of numerical precision\n        - [ ] ggxV|ggxG denoms->0\n\n## 2024-01-08 E359\n- [-] find and fix MORE NaNs\n    - [x] add debugPrintfEXT to shaders\n    - [x] fix black dots on glass surfaces\n    - [ ] fix polygon light nans in logs\n    - [x] magenta gliches -- dot(N,L) < 0.\n    - [x] disableable NaN debugging with macro\n    - [ ] enable NaN debugging with -vkvalidate\n\n## 2024-01-04 E357\n- [x] Black metals: https://github.com/w23/xash3d-fwgs/issues/666\n    - [x] fix missing dot(N,L) term\n- [x] try bespoke diffuse term -- yes, mine seems to be more correct\n    - [ ] PR against glTF\n- [ ] Bounces\n    - [x] idiotic sampling\n    - [ ] sampling functions\n        - [x] diffuse\n        - [ ] specular\n    - [ ] how to mix properly with brdf itself\n    - [x] find and fix NaNs\n- [ ] Better PBR math, e.g.:\n\t- [ ] Fresnel issues (esp. with skybox)\n\t- [ ] Just make sure that all the BRDF math is correct\n\n## 2023-12-29 E354\n- [x] Figure out why additive transparency differs visibly from raster\n- [x] Implement special legacy-blending in sRGB-γ colorspace\n\n## 2023-12-28 E353\n- [x] track color spaces when passing colors into shaders\n- [-] validation failure at startup, #723 -- seems like memory corruption\n\nLonger-term agenda for current season:\n- [ ] Transparency/translucency:\n\t- [ ] Proper material mode for translucency, with reflections, refraction (index), fresnel, etc.\n\t- [ ] Extract and specialize effects, e.g.\n\t\t- [ ] Rays -> volumetrics\n\t\t- [ ] Glow -> bloom\n\t\t- [ ] Smoke -> volumetrics\n\t\t- [ ] Sprites/portals -> emissive volumetrics\n\t\t- [x] Holo models -> emissive additive\n\t\t- [ ] Some additive -> translucent\n\t\t- [ ] what else\n- [ ] Render-graph-ish approach to resources.\n- [ ] Performance tools -- needed for perf and lighting work below:\n\t- [ ] Needs: render-graph-ish things for fast iterations when exporting custom situational metrics for the shader.\n\t- [ ] Purpose: shader profiling. Measure impact of changes. Regressions.\n\t- [ ] WIP shader clocks: https://github.com/w23/xash3d-fwgs/pull/692\n\t- [ ] WIP perf query: https://github.com/w23/xash3d-fwgs/pull/500\n- [ ] Lighting\n\t- [x] Point spheres sampling\n\t- [ ] Increase limits\n\t- [ ] s/poly/triangle/ -- simpler sampling, universal\n\t- [ ] Better and dynamically sized clusters\n\t- [ ] Cache rays -- do not cast shadow rays for everything, do a separate ray-only pass for visibility caching\n- [ ] Bounces\n\t- [ ] Moar bounces\n\t- [ ] MIS\n\t- [ ] Cache directions for strong indirect light\n\n\n## 2023-12-19 E350\n- [x] fixup skybox reflections\n- [x] improve logs \"vk/tex: Loaded skybox pbr/env/%.*s\"\n- [x] add skybox test\n\n## 2023-12-18 E349\n- [x] KTX2 cubemaps\n- [x] variable cubemap exposure (in .mat file)\n\n## 2023-12-15 E348\n- [x] fix ktx2 sides corruption\n\n## 2023-12-14 E346-E347\n- [x] Optimize skybox loading, #706\n    - [x] Do not load skybox when there are no SURF_DRAWSKY, #579\n    - [x] Do not reload the same skybox\n    - [-] Load skyboxes from KTX2 sides\n        → doesn't work as easily, as there's no way to rotate compressed images.\n          KTX2 sides should be pre-rotated\n    - [x] do not generate mips for skybox\n    - [x] support imagelib cubemaps\n    - [x] use imagelib skybox loader\n- [x] Hide all SURF_DRAWSKY while retaining skybox, #579\n- [x] possible issues with TF_NOMIPMAP\n    - [x] used incorrectly when loading blue noise textures\n    - [x] what about regular usage?\n\n## 2023-12-11 E345\n- [x] fix black dielectrics, #666\n    - [x] fix incorrect basecolor brdf multiplication, #666\n    - [x] fixup skybox glitches caused by #666 fix\n- [ ] Patch overlay textures (#696) → turned out to be much more difficult than expected.\n- [x] Do not patch sprite textures for traditional raster, #695\n\n## 2023-12-05 E342\n- [x] tone down the specular indirect blur\n- [-] try func_wall static light opt, #687\n\t→ decided to postpone, a lot more logic changes are needed\n- [x] increase rendertest wait by 1 -- increased scroll speed instead\n- [x] update rendertest images\n- [x] Discuss shader profiling\n- [-] Discuss Env-based verbose log control\n\n## 2023-12-04 E341\n- [-] investigate envlight missing #680\n\t- couldn't reproduce more than once\n- [x] add more logs for the above\n- [x] double switchable lights, #679\n\n-- season cut --\n\n## 2023-12-01 E340\n- [x] Better resolution changes:\n    - [x] Dynamic max resolution (start with current one, then grow by some growth factor)\n\n## 2023-11-30 E339\n- [x] rendermode patch\n\t- [x] track patch by boolean, not another field\n- [x] missing polylight on c2a1b\n\t- [x] \"proper\" slow fix: make func_water emissive surfaces dynamic\n\t\t- [x] extract dynamic polylights from render/rt models\n- [x] Reuse GPU scope names\n- [x] Support changing screen resolution up to UHD\n    - [x] Increase devmem count.\n\n## 2023-11-28 E338\n- [x] rendertest\n    - [x] read imagecompare results\n    - [x] html report\n\n## 2023-11-27 E337\n- [x] make rendetest.py the central script\n    - [x] parallelize/make gifs\n    - [x] diff/convert in parallel\n- [-] backside transparency\n     - [x] added to rendertest\n     - [ ] consider passing a special flag for single-sided blended surfaces (i.e. brush surfaces)\n- [x] fix per-entity material mapping, #669\n    - [x] add to rendertest\n\n## 2023-11-24 E336\n- reproducible rendering:\n    - [x] make sure it's reproducible -- given carefully spaced `wait N`s and `playersonly` it gets pretty reproducible\n    - [x] difference heatmap\n    - [x] contemplate infrastructure: scripts, repo, etc.\n\n## 2023-11-23 E335\n- [x] spec for profiler dumper\n- reproducible rendering:\n    - [ ] write fixed resolution internal images -- only need this because i'm stupid and using tiling window manager\n        - [ ] how to synchronize with frames\n        - [ ] how to extract vk images\n        - [ ] how to blit/copy various image pixel formats\n        - [ ] what file format to choose for non-rgba8 formats? do we even need them?\n    - [x] script for running and comparing results\n    - [-] extras:\n        - [x] difference gif\n        - [ ] difference summary table\n        - [ ] summary html\n- [x] consolidate all binding in shaders\n\n## 2023-11-21 E334\n- [ ] reproducible rendering\n    - [ ] dump all components\n        - [x] script\n        - [-] ~~try also dumping in native code~~ -- no need, it's fast enough\n    - [x] command for random seed fixation\n\n## 2023-11-20 E333\n- [ ] contemplate testing rendered images\n    - [x] try making a rendertest script: load multiple save, make multiple screenshots\n    - [x] compare screenshots\n    - [ ] Other infrastructure:\n        - tracking golden states\n        - testing script\n\n## 2023-11-17 E332\n- [-] backside emissive water polygons:\n    - adding them makes things worse in other parts of the level\n- [x] water normalmap support -- added missing tangents\n- [x] discuss integration test strategies\n\n## 2023-11-16 E331\n- [x] Emissive waters\n    - [x] add emissive water surface to polygon lights\n    - [x] update emissive color for water surfaces\n- [x] trihash option\n- [x] dynamic UVs\n    - [x] update UVs for conveyors\n    - [ ] pls don't aggravate validation on changelevel -- cannot reproduce\n\n## 2023-11-14 E330\n- [x] culling worldmodel waters\n     - [-] try simple flag culling (probably won't work)\n     - [-] try detecting glpoly normals -> consistent with SURF_PLANEBACK, doesn't help\n     - [x] SURF_UNDERWATER seems to get us a SINLE surface looking outwards\n- [x] investigate gl backface culling for transparent surfaces:\n    - [ ] glass -- seems to have 2nd face (brush backside)\n    - [x] water -- doesn't seem to have 2nd face\n        - [x] glpoly_t winding order is reversed when camera origin is opposite to (SURF_PLANEBACK-aware) surface normal\n- [x] discuss culling transparent surfaces strategies\n\n## 2023-11-13 E329\n- [-] culling -> need to cull everything except opaque and blend. Alpha-mask is culled.\n- [-] waters:\n     - [-] No water surface visible from underneath -- hidden by enabling culling\n     - [-] No coplanar issues visible? -- hidden by culling. Disabling culling makes glitches reappear\n\n## 2023-11-10 E328\n- [ ] woditschka\n     - [-] potentially collinear planes vs ray tracing #264\n         - not super clear how exactly it works, and what it does. And how to cull things\n         - leaning towards making our own tesselator, as it might be universally usable for other things, e.g. detail mapping\n         - [ ] (A) try producing simple surfaces w/o tesselation, similar to regular brush surfaces\n         - [x] (C) print out all surfaces and polys to see where are they looking\n         - [-] (B) try filtering surfaces looking down\n\n## 2023-11-09 E327\n- [x] update animated textures is now super slow: some static map surfaces have alternate anims (e.g. light post on c2a5)\n- [-] woditschka\n     - [x] height not switching to negative underwater -- decided that we don't need it for now\n     - [x] do not draw water sides when not requested.\n\n## 2023-11-07 E326\n- [x] list supported arguments for `rt_debug_display_only` cvar\n- [x] make vk_debug_log a command\n- [x] remove stvecs from patches -- not used, inconvenient\n- [x] patch texture coordinates by matrices\n- [x] add `_xvk_tex_rotate`\n- [x] ASSERT in c2a5 -- skybox sentinel\n\n## 2023-11-06 E325\n- [x] fix material asserts and inherit\n- [x] fixup -vkverboselogs\n- [x] changing textures on buttons, etc\n- [x] fix unpatched chrome surfaces brightness glitches\n\n## 2023-11-03 E324\n- [x] add cvar for displaying only specified channel\n- [x] r_lightmap\n- [x] highlight all surfaces with random colors\n- [ ] highlight selected surfaces -- decided to postpone\n- [ ] massage shaders: consolidate all bindings explicitly\n- [ ] skip sorting-by-texture when loading brush models ~~(=> geometry count explosion; i.e. kusochki count will explode too)~~\n- [ ] kusochki-vs-materials structures\n- [x] -vkverbose arg for turning all debug logs before detailed cvars are read\n\n## 2023-11-02 E323\n- [x] lol meta: read and sort issues\n- [x] merge from upstream\n- [x] hevsuit glitches\n- [x] inverted normal map orientation\n\n## 2023-10-31 E322\n- [x] load png blue noise files\n- [-] translucent animated thing -> needs shader rework\n- [x] massage texture code\n    - [x] single return/goto cleanup\n    - [-] pass args via structs? -> not necessary\n    - [-] collapse texture uploading into a single function -> not necessary, they are different enough\n- [x] merge materials PR\n- [x] studio gibs translucency\n- [x] smoothing exclusion\n\n## 2023-10-30 E321\n- [x] missing skybox\n- [x] explicitly free default textures; and complain about any leftovers\n- [x] use the new hash table in materials too, remove dummy textures\n- [x] why are there references to \\*unused\n- [ ] restore blue noise\n    - [x] vk_texture_t blue_noise; 3d texture\n\t- [x] separate binding similar to skybox in vk_rtx.c and shaders\n\t- [x] patch shader function\n\t- [ ] load 64xpngs into a single big pic\n\n## 2023-10-27 E320\n- [x] fix windows build\n- [x] track texture visibility for ref_api via flag and refcounts\n- [ ] devmem assert, not all textures are destroyed in wagonchik\n    - [ ] new material names+fixme => move to material hash table\n    - [x] preallocated default textures\n- [x] check urmom stats after a few different changelevels\n    - [x] COUNT(IS_DELETED)\n    - [x] clusters size histogram\n- [x] silence logs\n    - [x] \"accessing empty texture\"\n    - [x] \"found existing texture\"\n- [x] check mips\n\n## 2023-10-26 E319\n- [x] fix pbr materials disappearing\n- [x] fix surface lights\n- [ ] pbr/material refcount leaks\n    - [ ] track texture visibility for ref_api\n- [x] handle existing image on texture upload\n    - [x] sanely recreate\n    - [x] reuse if possible\n- [x] case insensitive hash table\n\n## 2023-10-24 E318\n- [ ] use new hashmap for textures\n    - [x] use vk_texure array directly as open addressing hash table\n        - [x] Completely hide `struct vk_texture`\n        - [x] just try\n        - [x] texture indexes are no longer consecutive\n    - [ ] blue noise texture breaks => make it a separate (3d) thing\n    - [ ] index=0 is now valid\n        - [x] I. mark 0 as occupied to avoid allocating it\n        - [ ] II. Increase all returned indexes by 1. Then dec it back wherever it is passed back\n    - (SAD): cannot make builtin textures have stable indexes anymore\n\n# E313\n## Pre-next:\n- validation crash\n## Next:\n- KTX2 PR against upstream\n- texture leaks\n\t- better texture storage\n\t\t- hash map\n\t- texture lifetimes/refcounts\n\t- texture deletion\n\t\t- mass (for single device wait idle)\n\n# Programmable render\n- [ ] implicit dependency tracking. pass defines:\n\t- [x] imports: list of things it needs\n\t- [ ] exports: list of things it produces. those get created and registered with this pass as a producer\n- [x] resource management refactoring:\n\t- [x] register existing resources (tlas, buffers, temp images, ...) in their producers\n\t- [x] resource automatic resolution: prducing, barriers, etc\n\t- [x] resource destruction\n- [x] ? resource object: name, metadata(type, etc.), producer, status (ready, barriers, etc)\n\n# Postponed\n## Resograf agenda\n- [ ] Explicit dependency graph\n\t- BLOCKED BY explicit image reuse in denoiser, see https://github.com/w23/xash3d-fwgs/issues/774\n\t- [ ] Build it from meatpipe and resources\n\t- [ ] Linearize it into metapass program\n- [ ] eventually: meatpipe resolves its graph and linearizes it into linear set of ops and barriers to perform\n## Multipass + Sampling\n- [ ] better simple sampling\n\t- [x] all triangles\n\t- [x] area based on triangles\n\t- [ ] clipping?\n\t- [ ] can we pack polygon lights better? e.g.:\n\t\t- each light is strictly a triangle\n\t\t- index is offset into triangles\n\t\t- layout:\n\t\t\t- vec4(plane) // is it really needed? is early culling important? can we shove area into there too? e.g plane_n.xy,plane_d, area\n\t\t\t- vec4(v0xyz, e_r)\n\t\t\t- vec4(v1xyz, e_g)\n\t\t\t- vec4(v2xyz, e_b)\n## Old Next\n- [ ] remove surface visibility cache\n- [ ] rtx: rename point lights to lampochki\n- [ ] rtx: rename emissive surface to surface lights\n- [ ] rtx: dynamically sized light clusters\n\tSplit into 2 buffers:\n\t\tstruct LightCluster { uint16 offset, length; }\n\t\tuint8_t data[];\n\n# Planned\n- [ ] improve nonuniformEXT usage: https://github.com/KhronosGroup/Vulkan-Samples/pull/243/files#diff-262568ff21d7a618c0069d6a4ddf78e715fe5326c71dd2f5cdf8fc8da929bc4eR31\n- [ ] rtx: experiment with refraction index and \"refraction roughness\"\n- [ ] emissive beams\n- [ ] emissive particles/sprites\n- [ ] issue: transparent brushes are too transparent (train ride)\n\t- [ ] (test_shaders_basic.bsp) shows that for brushes at least there are the following discrepancies with gl renderer:\n\t\t- [ ] traditional:\n\t\t\t- [ ] anything textured transparent is slightly darker in ref_vk\n\t\t\t- [ ] \"Color\" render mode should not sample texture at all and use just color\n\t\t\t- [ ] \"Texture\" looks mostly correct, but ~2x darker than it should be\n\t\t\t- [ ] \"Glow\" looks totally incorrect, it should be the same as \"Texture\" (as in ref_gl)\n\t\t\t- [ ] \"Additive\" is way too dark in ref_vk\n\t- [ ] rtx:\n\t\t\t- [ ] \"Color\" should use solid color instead of texture\n\t\t\t- [ ] \"Color\", \"Texture\", (\"Glow\"?) should be able to reflect and refract, likely not universally though, as they might be used for different intended effects in game. figure this out on case-by-case basis. maybe we could control it based on texture names and such.\n\t\t\t- [ ] \"Additive\" should just be emissive and not reflective/refractive\n- [ ] rtx: filter things to render, e.g.: some sprites are there to fake bloom, we don't need to draw them in rtx mode\n- [ ] possibly split vk_render into (a) rendering/pipeline, (b) buffer management/allocation, (c) render state\n- [ ] studio models: fix lighting: should have white texture instead of lightmap OR we could write nearest surface lightmap coords to fake light\n\t- [ ] make it look correct lol\n- [ ] studio model types:\n\t- [x] normal\n\t- [ ] float\n\t- [x] chrome\n- [ ] rtx: sky light/emissive skybox:\n\t- [ ] consider baking it into a single (or a few localized) kusok that has one entry in light cluster\n\t- [x] just ignore sky surfaces and treat not hitting anything as hitting sky. importance-sample by sun direction\n\t- [ ] pre-compute importance sampling direction by searching for ray-miss directions\n- [ ] rtx: importance-sample sky light; there are sky surfaces that we can consider light sources\n- [ ] cull water surfaces (see c3a2a)\n- [ ] consider doing per-geometry rendermode: brushes can be built only once; late transparency depth sorting for vk render;\n- [ ] rtx: too many emissive lights in c3a1b\n- [ ] rtx: denoise\n\t- [ ] non local means ?\n\t- [x] reprojection\n\t- [ ] SVG+\n\t- [ ] ...\n- [ ] rtx: bake light visibility in compute shader\n- [ ] make 2nd commad buffer for resource upload\n- [ ] :x: bad condition for temp vs map-permanent buffer error message\n- [ ] fix brush blending\n- [ ] sprite depth offset\n- [ ] fix incorrect viewport sprite culling\n- [ ] improve g_camera handling; trace SetViewPass vs RenderScene ...\n- [ ] studio model lighting\n- [ ] :x: move all consts to vk_const\n- [ ] decals\n- [ ] lightmap dynamic styles\n- [ ] fog\n- [ ] studio models survive NewMap; need to compactify buffers after removing all brushes\n- [ ] sometimes it gets very slow (1fps) when ran under lldb (only on stream?)\n- [ ] rtx: non-realtime unbiased mode: make \"ground truth\" screenshots that take 1e5 samples per pixels and seconds to produce. what for: semi-interactive material tuning, comparison w/ denoise, etc.\n- [ ] Barrier: check for incompatible duplicates\n\n# Someday\n- [ ] more than one lightmap texture. E.g. sponza ends up having 3 lightmaps\n- [ ] better 2d renderer: fill DRAWQUAD(texture, color, ...) command into storage buffer instead of 4 vertices\n- [ ] brush geometry is not watertight\n- [ ] collect render_draw_t w/o submitting them to cmdbuf, then sort by render_mode, trans depth, and other parameters, trying to batch as much stuff as possible; only then submit\n\n## 2021-02-06\n- [x] alpha test\n- [x] compare w/ gl R_SetRendeMode\n\t- [x] raster state\n\t- [x] color constants\n- [x] culling\n- [x] shaders s/map/brush/\n- [x] pipeline cache\n- [x] swapchain getting stale\n- [x] HUD sprites\n- [x] issue: lightmap sometimes gets corrupted on map load\n\n## 2021-02-08\n- [x] move entity rendering-enumeration into vk_scene\n\n## 2021-02-10\n- [x] refactor brush into brushes and separate rendering/buffer management\n- [x] animated textures (accept PR)\n\n## 2021-02-13\n- [x] move pipelines from brush to render\n- [x] render temp buffer api\n- [x] draw studio models somehow\n- [x] studio models vk debug markers\n- [x] studio models white texture as lightmap\n- [x] studio models fixes\n\n## 2021-02-15\n- [x] weapon models -- viewmodel\n- [x] coalesce studio model draw calls\n- [x] initual sprite support\n\n## 2021-02-17\n- [x] draw some beams\n\n## 2021-02-20\n- [x] refactor vk_render interface:\n\t- [x] move uniform_data_t to global render state ~inside render_draw_t, remove any mentions of uniform/slots from api; alt: global render state?~\n\t- [x] rename RenderDraw to SubmitDraw\n\t- [x] ~add debug label to render_draw_t?;~ alt: VK_RenderDebugNameBegin/End\n\t- [x] perform 3d rendering on corresponding refapi calls, not endframe\n- [x] fix sprite blending\n\n## 2021-02-22\n- [x] RTX: load extensions with -rtx arg\n- [x] vk_render: buffer-alloc-centric upload and draw api\n\n## 2021-03-06\n- [x] (RTX; common) Staging vs on-GPU buffers\n- [x] rtx: BLAS construction on buffer unlock\n- [x] rtx: ray trace compute shader\n- [x] dlight test\n\n## 2021-03-08\n- [x] studio models normals\n- [x] rtx: geometry indexing\n\n## 2021-03-10\n- [x] rtx: dlights\n- [x] rtx: dlight shadows\n- [x] rtx: dlight soft shadows\n\n## 2021-03-13\n- [x] rtx: blend normals according to barycentrics\n- [x] rtx: (debug/dev) shader reload\n- [x] rtx: make projection matrix independent render global/current/static state\n- [x] rtx: model matrices\n- [x] rtx: light entities -- still not enough to enlight maps :(\n- [x] rtx: path tracing\n\n## 2021-03-15\n- [x] rtx: control bounces with cvars\n- [x] rtx: device-local buffers -- doesn't affect perf noticeably :(\n- [x] rtx: emissive materials\n\t- [x] rtx: emissive textures\n\t- [x] rtx: emissive beams\n\n## 2021-03-17..20\n- [x] rtx: lower resolution framebuffer + upscale\n- [x] rtx: importance sample emissive surface\n- [x] rtx: remove entnity-parsed lights\n- [x] rtx: naive temporal denoise: mix with previous frame\n\n## 2021-03-22\n- [x] rtx: traverse bsp for science!\n\n## 2021-03-28\n- [x] bake s/d-lights visibility data into bsp leaves\n\n## 2021-04-06..08\n- [x] persistent models\n\t- [x] load brushes into render model\n\t- [x] destroy brushes when time comes (when?)\n\t- [x] rasterize models in renderer\n\n## 2021-04-09\n- [x] rtx: build AS for model\n- [x] rtx: include pre-built models in TLAS\n\n## 2021-04-10\n- [x] rtx: fix tlas rebuild\n- [x] rtx: upload kusochki metadata ~~w/ leaves~~\n- [x] rtx: add fps\n\t- [x] rtx: don't group brush draws by texture\n\t- [x] better AS structure (fewer blases, etc)\n\n## 2021-04-11\n- [x] vscode build and debug\n\n## 2021-04-12\n- [x] rtx: fix surface-kusok index mismatch\n- [x] rtx: try to use light visibility data\n\t- too few slots for light sources\n\t- some areas have too many naively visible lights\n- [x] rtx: fix light shadow artefacts\n\n## 2021-04-13\n- [x] rtx: \"toilet error\": attempting to get AS device address crashes the driver\n- [x] rtx: fix blas destruction on exit\n- [x] rtx: sometimes we get uninitialized models\n\n## 2021-04-14..16\n- [x] rtx: grid-based light clusters\n\n## 2021-04-17\n- [x] rtx: read rad file data\n\n## 2021-04-19\n- [x] rtx: light intensity-based light clusters visibility\n- [x] rtx: check multiple variants of texture name (wad and non-wad)\n- [x] rtx: rad liquids/xeno/... textures\n\n## 2021-04-22\n- [x] rtx: fix backlight glitch\n- [x] rtx: textures\n\n## 2021-04-24, E86\n- [x] rtx: restore studio models\n\n## 2021-05-01, E89\n- [x] make a wrapper for descriptor sets/layouts\n\n## 2021-05-03, E90\n- [x] make map/frame lifetime aware allocator and use it everywhere: render, rtx buffers, etc\n\n## 2021-05-08, E92\n- [x] rtx: weird purple bbox-like glitches on dynamic geometry (tlas vs blas memory corruption/aliasing)\n- [x] rtx: some studio models have glitchy geometry\n\n## 2021-05-10, E93\n- [x] rtx: don't recreate tlas each frame\n- [x] rtx: dynamic models AS caching\n\n## 2021-05-..-17, E93, E94\n- [x] rtx: improve AS lifetime/management; i.e. pre-cache them, etc\n- [x] add debug names to all of the buffers\n\n## 2021-05-22, E97\n- [x] add nvidia aftermath sdk\n\n## 2021-05-24, E98\n- [x] rtx: simplify AS tracking\n\n## 2021-05-26, E99\n- [x] rtx: fix device lost after map load\n\n## 2021-05-28, E100\n- [x] rtx: build acceleration structures in a single queue/cmdbuf\n\n## 2021-06-05, E103\n- [x] rtx: dynamic surface lights / dynamic light clusters\n- [x] rtx: animated textures\n- [x] rtx: attenuate surface lights by normal\n\n## 2021-06-07, E104..\n- [x] fix CI for vulkan branch\n\n## 2021-06-09..12, E105..106\n- [x] c3a2a: no water surfaces in vk (transparent in gl: *45,*24,*19-21)\n- [x] water surfaces\n\n## 2021-06-14, E107\n- [x] rtx: optimize water normals. now they're very slow because we R/W gpu mem? yes\n- [x] cull bottom water surfaces (they're PLANE_Z looking down)\n- [x] fix water normals\n\n## 2021-06-23, E109\n- [x] rtx: ray tracing shaders specialization, e.g. for light clusters constants\n- [x] rtx: restore dynamic stuff like particles, beams, etc\n- [x] rtx: c3a1b: assert model->size >= build_size.accelerationStructureSize failed at vk_rtx.c:347\n\n## 2021-07-17, E110..120\n- [x] rtx: ray tracing pipeline\n- [x] rtx: fix rendering on AMD\n- [x] rtx: split models into a separate module\n- [x] rtx: alpha test\n\n## 2021-07-31, E121\n- [x] rtx: alpha blending -- did a PoC\n\n## 2021-08-02..04, E122-123\n- [x] mipmaps\n- [x] rtx: better random\n\n## 2021-08-07, E124\n- [x] anisotropic texture sampling\n- [x] studio model lighting prep\n\t- [x] copy over R_LightVec from GL renderer\n\t- [x] add per-vertex color attribute\n\t- [x] support per-vertex colors\n\t- [x] disable lightmaps, or use white texture for it instead\n\n## 2021-08-11, E125\n- [x] simplify buffer api: do alloc+lock as a single op\n\n## 2021-08-15, E126\n- [x] restore render debug labels\n- [x] restore draw call concatenation; brush geoms are generated in a way that makes concatenating them impossible\n\n## 2021-08-16, E127\n- [x] better device enumeration\n\n## 2021-08-18, E128\n- [x] rtx: fix maxVertex for brushes\n\n## 2021-08-22, E129\n- [x] fix depth test for glow render mode\n- [x] screenshots\n\n## 2021-08-26, E131\n- [x] rtx: material flags for kusochki\n\n## 2021-09-01, E132\n- [x] rtx: ingest brdfs from ray tracing gems 2\n- [x] rtx: directly select a triangle for light sampling\n\n## 2021-09-04, E133\n- [x] rtx: different sbts for opaque and alpha mask\n- [x] include common headers with struct definitions from both shaders and c code\n\n## 2021-09-06, E134\n- [x] rtx: pass alpha for transparency\n- [x] rtx: remove additive/refractive flags in favor or probability of ray continuing further instead of bouncing off\n- [x] make a list of all possible materials, categorize them and figure out what to do\n\n# E149\n- [x] rtx: remove sun\n- [x] rtx: point lights:\n\t- [x] static lights\n\t\t- [x] intensity \"fix\"\n\t- [x] dlights\n\t- [ ] elights\n\t- [x] intensity fix for d/elights?\n\t- [x] point light clusters\n\t- [x] bsp:\n\t\t- [x] leaf culling\n\t\t- [x] pvs\n\n- [x] rtx: better light culling: normal, bsp visibility, (~light volumes and intensity, sort by intensity, etc~)\n- [x] rtx: cluster dlights\n\n## 2021-10-24 E155\n- [x] rtx: static lights\n\t- [x] point lights\n\t- [x] surface lights\n\n## 2021-10-26 E156\n- [x] enable entity-parsed lights by lightstyles\n\n## 2021-12-21 DONE SOMEWHEN\n- [x] rtx: dynamic rtx/non-rtx switching breaks dynamic models (haven't seen this in a while)\n- [x] run under asan\n- [x] rtx: map name to rad files mapping\n- [x] rtx: live rad file reloading (or other solution for tuning lights)\n- [x] rtx: move entity parsing to its own module\n- [x] rtx: configuration that includes texture name -> pbr params mapping, etc. Global, per-map, ...\n- [x] rtx: simple convolution denoise (bilateral?)\n- [x] rtx: cull light sources (dlights and light textures) using bsp\n- [-] crash in PM_RecursiveHullCheck. havent seen this in a while\n- [x] rtx: remove lbsp\n\n## 2022-09-17 E207 Parallel frames\n- [x] allocate for N frames:\n\t- [x] geometries\n\t- [x] rt models\n\t\t- [x] kusochki\n\t\t  - [x] same ring buffer alloc as for geometries\n\t\t    - [x] extract as a unit\n\t\t- [x] tlas geom --//--\n\t- [-] lights\n\t\t- [x] make metadata buffer in lights\n\t\t- [-] join lights grid+meta into a single buffer => pipeline loading issues\n\t\t- [x] put lights data into a cpu-side vk buffer\n\t\t- [-] sync+barrier upload => TOO BIG AND TOO SLOW, need to e.g. track dirty regions, compactify stuff (many clusters are the same), etc\n- [x] scratch buffer:\n  - should be fine (assuming intra-cmdbuf sync), contents lifetime is single frame only\n- [x] accels_buffer:\n  - ~~[ ] lifetime: multiple frames; dynamic: some b/tlases get rebuilt every frame~~\n  - ~~[ ] opt 1: double buffering~~\n  - [x] opt 2: intra-cmdbuf sync (can't write unless previous frame is done)\n- [x] uniform_buffer:\n  - lifetime: single frame\n- [x] tlas_geom_buffer:\n  - similar to scratch_buffer\n  - BUT: filled on CPU, so it's not properly synchronsized\n  - fix: upload using staging?\n\t- [x] double/ring buffering\n\n- [x] E213:\n\t- [x] parse binding types\n\t- [x] remove types from resources FIXME\n- [x] E214: ~tentative~\n\t- [x] integrate sebastian into waf\n- [x] E215:\n\t- [x] serialize binding image format\n\n## 2022-11-26 E216 rake yuri\n\t- [x] validate meatpipe image formats\n\t- [x] begin Rake Yuri migration\n\t\t- [x] direct lights\n\n## 2023-01-21 E217-E221\n- [x] meatpipe resource tracking\n\t- [x] name -> index mapping\n\t- [x] create images on meatpipe load\n\t- [x] automatic resource creation\n\t- [x] serialize all resources with in/out and formats for images\n\t- [x] create resources on demand\n- [x] parse spirv -> get bindings with names\n  - [x] spirv needs to be compiled with -g, otherwise there are no OpName entries. Need a custom strip util that strips the rest?\n\t- [x] unnamed uniform blocks are uncomfortable to parse.\n- [x] passes \"export\" their bindings as detailed resource descriptions:\n  - [x] images: name, r/w, format, resolution (? not found in spv, needs to be externally supplied)\n\t- [-] buffers: name, r/w, size, type name (?) -- can't really do, too hard for now\n- [x] name -> index resolver (hashmap kekw)\n- [x] automatic creation of resources\n\t- [x] images\n\t- [-] buffers -- no immediate need for that\n\n## 2023-01-22 E222\n- [x] refcount meatpipe created images\n- [x] rake yuri primary ray\n\n## 2023-01-28 E223\n- [x] previous frame resources reference\n\t\t- specification:\n\t\t\t- [x] I: prev_ -> resource flag + pair index\n\t\t\t- [ ] II: new section in json\n\t\t- internals:\n\t\t\t- [x] I: create a new image for prev_, track its source; swap them each frame\n\t\t\t\t\t\tResult is meh: too much indirection, hard to follow, many things need manual fragile updates.\n\t\t\t- [ ] II: create tightly coupled image pair[2], read from [frame%2] write to [frame%2+1]\n\t\t\t- [ ] III: like (I) but with more general resource management: i.e. resource object for prev_ points to its source\n\n## 2023-01-28-02-08 E224-229\n- [x] light_grid_buffer (+ small lights_buffer):\n  - lifetime: single frame\n  - BUT: populated by CPU, needs sync; can't just ring-buffer it\n  - fixes: double-buffering?\n    - staging + sync upload? staging needs to be huge or done in chunks. also, cpu needs to wait on staging upload\n\t- 2x size + wait: won't fit into device-local-host-visible mem\n\t- decrease size first?\n- [x] additive transparency\n- [x] bounces\n- [x] skybox shadows\n- [-] rtx: shrink payload between shaders\n- [x] rtx: split ray tracing into modules: pipeline mgmt, buffer mgmt\n- [x] nvnsight into buffer memory and stuff\n- [x] multiple frames in flight (#nd cmdbuf, ...)\n- [x] embed shaders into binary\n- [x] verify resources lifetime: make sure we don't leak and delete all textures, brushes, models, etc between maps\n- [x] custom allocator for vulkan\n- [x] rtx: better mip lods: there's a weird math that operates on fov degrees (not radians) that we copypasted from ray tracing gems 2 chapter 7. When the book is available, get through the math and figure this out.\n- [x] render skybox\n- [x] better flashlight: spotlight instead of dlight point\n- [x] rtx: add fps: rasterize into G-buffer, and only then compute lighting with rtx\n\n# Done somewhen\n- [x] create water surfaces once in vk_brush\n- [x] loading to the same map breaks geometry\n- [x] (helps with RTX?) unified rendering (brush/studio models/...), each model is instance, instance data is read from storage buffers, gives info about vertex format, texture bindings, etc; which are read from another set of storage buffers, ..\n- [x] waf shader build step -- get from upstream\n\n## Collected on 2024-01-18\n- [x] what if new meatpipe has different image format for a creatable image?\n- [x] rtx: light styles: need static lights data, not clear how and what to do\n- [x] more beams types\n- [x] more particle types\n- [x] sane texture memory management: do not allocate VKDeviceMemory for every texture\n- [x] rtx: transparency layering issue, possible approaches:\n\t- [x]  trace a special transparent-only ray separately from opaque. This can at least be used to remove black texture areas\n- [x] rtx: better memory handling\n\t- [x] robust tracking of memory hierarchies: global/static, map, frame\n\t- or just do a generic allocator with compaction?\n- [x] rtx: coalesce all these buffers\n- [x] rtx: entity lights\n- [x] rtx: do not rebuild static studio models (most of them). BLAS building takes most of the frame time (~12ms where ray tracing itself is just 3ms)\n- [x] studio models: pre-compute buffer sizes and allocate them at once\n- [x] dlight for flashlight seems to be broken\n- [x] fix sprite blending; there are commented out functions that we really need (see tunnel before the helicopter in the very beginning)\n- [x] fix projection matrix differences w/ gl render\n- [x] what is GL_Backend*/GL_RenderFrame ???\n- [x] particles\n- [x] optimize perf: cmdbuf managements and semaphores, upload to gpu, ...\n- [x] rtx: studio models should not pre-transform vertices with modelView matrix\n- [x] start building command buffers in beginframe\n- [x] cleanup unused stuff in vk_studio.c\n- [x] stats\n- [-] auto-atlas lots of smol textures: most of model texture are tiny (64x64 or less), can we not rebind them all the time? alt: bindless texture array\n- [x] can we also try to coalesce sprite draw calls?\n"
  },
  {
    "path": "ref/vk/camera.c",
    "content": "#include \"camera.h\"\n#include \"vk_common.h\"\n#include \"vk_math.h\"\n\n#include \"ref_params.h\"\n#include \"pm_movevars.h\"\n\nvk_global_camera_t g_camera;\n\nstatic float R_GetFarClip( void )\n{\n\tif( WORLDMODEL /* FIXME VK && RI.drawWorld */ )\n\t\treturn MOVEVARS->zmax * 1.73f;\n\treturn 2048.0f;\n}\n\nstatic void R_SetupModelviewMatrix( matrix4x4 m )\n{\n\tMatrix4x4_CreateModelview( m );\n\tMatrix4x4_ConcatRotate( m, -g_camera.viewangles[2], 1, 0, 0 );\n\tMatrix4x4_ConcatRotate( m, -g_camera.viewangles[0], 0, 1, 0 );\n\tMatrix4x4_ConcatRotate( m, -g_camera.viewangles[1], 0, 0, 1 );\n\tMatrix4x4_ConcatTranslate( m, -g_camera.vieworg[0], -g_camera.vieworg[1], -g_camera.vieworg[2] );\n}\n\nstatic void R_SetupProjectionMatrix( matrix4x4 m )\n{\n\tfloat xMin, xMax, yMin, yMax, zNear, zFar;\n\n\t/*\n\tif( RI.drawOrtho )\n\t{\n\t\tconst ref_overview_t *ov = gEngfuncs.GetOverviewParms();\n\t\tMatrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );\n\t\treturn;\n\t}\n\t*/\n\n\tconst float farClip = R_GetFarClip();\n\n\tzNear = 4.0f;\n\tzFar = Q_max( 256.0f, farClip );\n\n\tyMax = zNear * tan( g_camera.fov_y * M_PI_F / 360.0f );\n\tyMin = -yMax;\n\n\txMax = zNear * tan( g_camera.fov_x * M_PI_F / 360.0f );\n\txMin = -xMax;\n\n\tMatrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar );\n}\n\n// Analagous to R_SetupRefParams, R_SetupFrustum in GL/Soft renderers\nvoid R_SetupCamera( const ref_viewpass_t *rvp )\n{\n\t/* FIXME VK unused?\n\tRI.params = RP_NONE;\n\tRI.drawWorld = FBitSet( rvp->flags, RF_DRAW_WORLD );\n\tRI.onlyClientDraw = FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW );\n\tRI.farClip = 0;\n\n\tif( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP ))\n\t\tRI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW );\n\telse RI.drawOrtho = false;\n\t*/\n\n\t// setup viewport\n\tg_camera.viewport[0] = rvp->viewport[0];\n\tg_camera.viewport[1] = rvp->viewport[1];\n\tg_camera.viewport[2] = rvp->viewport[2];\n\tg_camera.viewport[3] = rvp->viewport[3];\n\n\t// calc FOV\n\tg_camera.fov_x = rvp->fov_x;\n\tg_camera.fov_y = rvp->fov_y;\n\n\tVectorCopy( rvp->vieworigin, g_camera.vieworg );\n\tVectorCopy( rvp->viewangles, g_camera.viewangles );\n\t// FIXME VK unused? VectorCopy( rvp->vieworigin, g_camera.pvsorigin );\n\n#define RP_NORMALPASS() true // FIXME ???\n\tif( RP_NORMALPASS() && ( gEngine.EngineGetParm( PARM_WATER_LEVEL, 0 ) >= 3 ))\n\t{\n\t\tg_camera.fov_x = atan( tan( DEG2RAD( g_camera.fov_x ) / 2 ) * ( 0.97f + sin( gp_cl->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);\n\t\tg_camera.fov_y = atan( tan( DEG2RAD( g_camera.fov_y ) / 2 ) * ( 1.03f - sin( gp_cl->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);\n\t}\n\n\t// build the transformation matrix for the given view angles\n\tAngleVectors( g_camera.viewangles, g_camera.vforward, g_camera.vright, g_camera.vup );\n\n\t/* FIXME VK unused?\n\tif( !r_lockfrustum->value )\n\t{\n\t\tVectorCopy( RI.vieworg, RI.cullorigin );\n\t\tVectorCopy( RI.vforward, RI.cull_vforward );\n\t\tVectorCopy( RI.vright, RI.cull_vright );\n\t\tVectorCopy( RI.vup, RI.cull_vup );\n\t}\n\t*/\n\n\t/* FIXME VK unused?\n\tif( RI.drawOrtho )\n\t\tGL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );\n\telse GL_FrustumInitProj( &g_camera.frustum, 0.0f, R_GetFarClip(), g_camera.fov_x, g_camera.fov_y ); // NOTE: we ignore nearplane here (mirrors only)\n\t*/\n\n\tR_SetupProjectionMatrix( g_camera.projectionMatrix );\n\tR_SetupModelviewMatrix( g_camera.viewMatrix );\n\n\tMatrix4x4_Concat( g_camera.worldviewProjectionMatrix, g_camera.projectionMatrix, g_camera.viewMatrix );\n}\n\nint R_WorldToScreen( const vec3_t point, vec3_t screen )\n{\n\tmatrix4x4\tworldToScreen;\n\tqboolean\tbehind;\n\tfloat\tw;\n\n\tif( !point || !screen )\n\t\treturn true;\n\n\tMatrix4x4_Copy( worldToScreen, g_camera.worldviewProjectionMatrix );\n\tscreen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3];\n\tscreen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3];\n\tw = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3];\n\tscreen[2] = 0.0f; // just so we have something valid here\n\n\tif( w < 0.001f )\n\t{\n\t\tscreen[0] *= 100000;\n\t\tscreen[1] *= 100000;\n\t\tbehind = true;\n\t}\n\telse\n\t{\n\t\tfloat invw = 1.0f / w;\n\t\tscreen[0] *= invw;\n\t\tscreen[1] *= invw;\n\t\tbehind = false;\n\t}\n\n\treturn behind;\n}\n\nint TriWorldToScreen( const float *world, float *screen )\n{\n\tint\tretval;\n\n\tretval = R_WorldToScreen( world, screen );\n\n\tscreen[0] =  0.5f * screen[0] * (float)g_camera.viewport[2];\n\tscreen[1] = -0.5f * screen[1] * (float)g_camera.viewport[3];\n\tscreen[0] += 0.5f * (float)g_camera.viewport[2];\n\tscreen[1] += 0.5f * (float)g_camera.viewport[3];\n\n\treturn retval;\n}\n\n"
  },
  {
    "path": "ref/vk/camera.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\ntypedef struct vk_global_camera_s {\n\tvec3_t vieworg; // locked vieworigin\n\tvec3_t viewangles;\n\tvec3_t vforward;\n\tvec3_t vright;\n\tvec3_t vup;\n\n\tfloat fov_x, fov_y; // current view fov\n\n\tint viewport[4];\n\t//gl_frustum_t frustum;\n\n\tmatrix4x4 viewMatrix;\n\tmatrix4x4 projectionMatrix;\n\tmatrix4x4 worldviewProjectionMatrix; // worldviewMatrix * projectionMatrix\n} vk_global_camera_t;\n\nextern vk_global_camera_t g_camera;\n\nstruct ref_viewpass_s;\n\nvoid R_SetupCamera( const struct ref_viewpass_s *rvp );\n\nint R_WorldToScreen( const vec3_t point, vec3_t screen );\nint TriWorldToScreen( const float *world, float *screen );\n\n// TODO move to infotool.h\nvoid XVK_CameraDebugPrintCenterEntity( void );\n"
  },
  {
    "path": "ref/vk/common_geometry.c",
    "content": "#include \"vk_common.h\"\n#include \"vk_lightmap.h\"\n\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"xash3d_mathlib.h\"\n#include \"mod_local.h\"\n\n#define SUBDIVIDE_SIZE\t64\n\n\nstatic void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs )\n{\n\tint\ti, j;\n\tfloat\t*v;\n\n\tClearBounds( mins, maxs );\n\n\tfor( i = 0, v = verts; i < numverts; i++ )\n\t{\n\t\tfor( j = 0; j < 3; j++, v++ )\n\t\t{\n\t\t\tif( *v < mins[j] ) mins[j] = *v;\n\t\t\tif( *v > maxs[j] ) maxs[j] = *v;\n\t\t}\n\t}\n}\n\nstatic void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts )\n{\n\tvec3_t\t\tfront[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE];\n\tmextrasurf_t\t*warpinfo = warpface->info;\n\tfloat\t\tdist[SUBDIVIDE_SIZE];\n\tfloat\t\tm, frac, s, t, *v;\n\tint\t\ti, j, k, f, b;\n\tfloat\t\tsample_size;\n\tvec3_t\t\tmins, maxs;\n\tglpoly2_t\t\t*poly;\n\n\tif( numverts > ( SUBDIVIDE_SIZE - 4 ))\n\t\tgEngine.Host_Error( \"Mod_SubdividePolygon: too many vertexes on face ( %i )\\n\", numverts );\n\n\tsample_size = gEngine.Mod_SampleSizeForFace( warpface );\n\tBoundPoly( numverts, verts, mins, maxs );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tm = ( mins[i] + maxs[i] ) * 0.5f;\n\t\tm = SUBDIVIDE_SIZE * floor( m / SUBDIVIDE_SIZE + 0.5f );\n\t\tif( maxs[i] - m < 8 ) continue;\n\t\tif( m - mins[i] < 8 ) continue;\n\n\t\t// cut it\n\t\tv = verts + i;\n\t\tfor( j = 0; j < numverts; j++, v += 3 )\n\t\t\tdist[j] = *v - m;\n\n\t\t// wrap cases\n\t\tdist[j] = dist[0];\n\t\tv -= i;\n\t\tVectorCopy( verts, v );\n\n\t\tf = b = 0;\n\t\tv = verts;\n\t\tfor( j = 0; j < numverts; j++, v += 3 )\n\t\t{\n\t\t\tif( dist[j] >= 0 )\n\t\t\t{\n\t\t\t\tVectorCopy( v, front[f] );\n\t\t\t\tf++;\n\t\t\t}\n\n\t\t\tif( dist[j] <= 0 )\n\t\t\t{\n\t\t\t\tVectorCopy (v, back[b]);\n\t\t\t\tb++;\n\t\t\t}\n\n\t\t\tif( dist[j] == 0 || dist[j+1] == 0 )\n\t\t\t\tcontinue;\n\n\t\t\tif(( dist[j] > 0 ) != ( dist[j+1] > 0 ))\n\t\t\t{\n\t\t\t\t// clip point\n\t\t\t\tfrac = dist[j] / ( dist[j] - dist[j+1] );\n\t\t\t\tfor( k = 0; k < 3; k++ )\n\t\t\t\t\tfront[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]);\n\t\t\t\tf++;\n\t\t\t\tb++;\n\t\t\t}\n\t\t}\n\n\t\tSubdividePolygon_r( loadmodel, warpface, f, front[0] );\n\t\tSubdividePolygon_r( loadmodel, warpface, b, back[0] );\n\t\treturn;\n\t}\n\n\tif( numverts != 4 )\n\t\tClearBits( warpface->flags, SURF_DRAWTURB_QUADS );\n\n\t// add a point in the center to help keep warp valid\n\tpoly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly2_t ) + numverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = warpface->polys;\n\tpoly->flags = warpface->flags;\n\twarpface->polys = poly;\n\tpoly->numverts = numverts;\n\n\tfor( i = 0; i < numverts; i++, verts += 3 )\n\t{\n\t\tVectorCopy( verts, poly->verts[i] );\n\n\t\tif( FBitSet( warpface->flags, SURF_DRAWTURB ))\n\t\t{\n\t\t\ts = DotProduct( verts, warpface->texinfo->vecs[0] );\n\t\t\tt = DotProduct( verts, warpface->texinfo->vecs[1] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\ts = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3];\n\t\t\tt = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3];\n\t\t\ts /= warpface->texinfo->texture->width;\n\t\t\tt /= warpface->texinfo->texture->height;\n\t\t}\n\n\t\tpoly->verts[i][3] = s;\n\t\tpoly->verts[i][4] = t;\n\n\t\t// for speed reasons\n\t\tif( !FBitSet( warpface->flags, SURF_DRAWTURB ))\n\t\t{\n\t\t\t// lightmap texture coordinates\n\t\t\ts = DotProduct( verts, warpinfo->lmvecs[0] ) + warpinfo->lmvecs[0][3];\n\t\t\ts -= warpinfo->lightmapmins[0];\n\t\t\ts += warpface->light_s * sample_size;\n\t\t\ts += sample_size * 0.5f;\n\t\t\ts /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\t\t\tt = DotProduct( verts, warpinfo->lmvecs[1] ) + warpinfo->lmvecs[1][3];\n\t\t\tt -= warpinfo->lightmapmins[1];\n\t\t\tt += warpface->light_t * sample_size;\n\t\t\tt += sample_size * 0.5f;\n\t\t\tt /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height;\n\n\t\t\tpoly->verts[i][5] = s;\n\t\t\tpoly->verts[i][6] = t;\n\t\t}\n\t}\n}\n\nstatic void R_GetEdgePosition( const model_t *mod, const msurface_t *fa, int i, vec3_t vec )\n{\n\tconst int lindex = mod->surfedges[fa->firstedge + i];\n\n\tif( FBitSet( mod->flags, MODEL_QBSP2 ))\n\t{\n\t\tconst medge32_t *pedges = mod->edges32;\n\n\t\tif( lindex > 0 )\n\t\t\tVectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );\n\t\telse\n\t\t\tVectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );\n\t}\n\telse\n\t{\n\t\tconst medge16_t *pedges = mod->edges16;\n\n\t\tif( lindex > 0 )\n\t\t\tVectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );\n\t\telse\n\t\t\tVectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );\n\t}\n}\n\n/*\n================\nGL_SubdivideSurface\n\nBreaks a polygon up along axial 64 unit\nboundaries so that turbulent and sky warps\ncan be done reasonably.\n================\n*/\nvoid GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa )\n{\n\tvec3_t\tverts[SUBDIVIDE_SIZE];\n\tint\ti;\n\n\t// convert edges back to a normal polygon\n\tfor( i = 0; i < fa->numedges; i++ )\n\t\tR_GetEdgePosition( loadmodel, fa, i, verts[i] );\n\n\tSetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state\n\n\t// do subdivide\n\tSubdividePolygon_r( loadmodel, fa, fa->numedges, verts[0] );\n}\n\n/*\n================\nR_LightmapCoord\n\nTotal copypaste of R_LightmapCoord from gl_rsurf.c\n================\n*/\nvoid R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords )\n{\n\tconst mextrasurf_t *info = surf->info;\n\tfloat s, t;\n\n\ts = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];\n\ts += surf->light_s * sample_size;\n\ts += sample_size * 0.5f;\n\ts /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\tt = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];\n\tt += surf->light_t * sample_size;\n\tt += sample_size * 0.5f;\n\tt /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\tVector2Set( coords, s, t );\n}\n\n/*\n================\nR_TextureCoord\n\nTotal copypaste of R_TextureCoord from gl_rsurf.c\n================\n*/\nstatic void R_TextureCoord( const vec3_t v, const msurface_t *surf, vec2_t coords )\n{\n\tconst mtexinfo_t *info = surf->texinfo;\n\tfloat s, t;\n\n\ts = DotProduct( v, info->vecs[0] );\n\tt = DotProduct( v, info->vecs[1] );\n\n\tif( !FBitSet( surf->flags, SURF_DRAWTURB ))\n\t{\n\t\ts = ( s + info->vecs[0][3] ) / info->texture->width;\n\t\tt = ( t + info->vecs[1][3] ) / info->texture->height;\n\t}\n\n\tVector2Set( coords, s, t );\n}\n\n/*\n================\nR_BuildPolygonFromSurface\n\nInit surf->polys for decals\nAdapted copypaste of GL_BuildPolygonFromSurface\n================\n*/\nint R_BuildPolygonFromSurface( model_t *mod, msurface_t *fa )\n{\n\tint\t\ti, lnumverts, nColinElim = 0;\n\tfloat\t\tsample_size;\n\t//texture_t\t\t*tex;\n\t//gl_texture_t\t*glt;\n\tglpoly2_t\t\t*poly;\n\n\tif( !mod || !fa->texinfo || !fa->texinfo->texture )\n\t\treturn nColinElim; // bad polygon ?\n\n\t// if( FBitSet( fa->flags, SURF_CONVEYOR ) && fa->texinfo->texture->gl_texturenum != 0 )\n\t// {\n\t// \tglt = R_GetTexture( fa->texinfo->texture->gl_texturenum );\n\t// \ttex = fa->texinfo->texture;\n\t// \tAssert( glt != NULL && tex != NULL );\n\n\t// \t// update conveyor widths for keep properly speed of scrolling\n\t// \tglt->srcWidth = tex->width;\n\t// \tglt->srcHeight = tex->height;\n\t// }\n\n\tsample_size = gEngine.Mod_SampleSizeForFace( fa );\n\n\t// reconstruct the polygon\n\tlnumverts = fa->numedges;\n\n\t// detach if already created, reconstruct again\n\tpoly = fa->polys;\n\tfa->polys = NULL;\n\n\t// quake simple models (healthkits etc) need to be reconstructed their polys because LM coords has changed after the map change\n\tpoly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = fa->polys;\n\tpoly->flags = fa->flags;\n\tfa->polys = poly;\n\tpoly->numverts = lnumverts;\n\n\tfor( i = 0; i < lnumverts; i++ )\n\t{\n\t\tR_GetEdgePosition( mod, fa, i, poly->verts[i] );\n\t\tR_TextureCoord( poly->verts[i], fa, &poly->verts[i][3] );\n\t\tR_LightmapCoord( poly->verts[i], fa, sample_size, &poly->verts[i][5] );\n\t}\n\n\t// remove co-linear points - Ed\n\tif( /*!gl_keeptjunctions.value &&*/ !FBitSet( fa->flags, SURF_UNDERWATER )) // TODO: fugure out gl_keeptjunctions\n\t{\n\t\tfor( i = 0; i < lnumverts; i++ )\n\t\t{\n\t\t\tvec3_t\tv1, v2;\n\t\t\tfloat\t*prev, *this, *next;\n\n\t\t\tprev = poly->verts[(i + lnumverts - 1) % lnumverts];\n\t\t\tnext = poly->verts[(i + 1) % lnumverts];\n\t\t\tthis = poly->verts[i];\n\n\t\t\tVectorSubtract( this, prev, v1 );\n\t\t\tVectorNormalize( v1 );\n\t\t\tVectorSubtract( next, prev, v2 );\n\t\t\tVectorNormalize( v2 );\n\n\t\t\t// skip co-linear points\n\t\t\tif(( fabs( v1[0] - v2[0] ) <= 0.001f) && (fabs( v1[1] - v2[1] ) <= 0.001f) && (fabs( v1[2] - v2[2] ) <= 0.001f))\n\t\t\t{\n\t\t\t\tint\tj, k;\n\n\t\t\t\tfor( j = i + 1; j < lnumverts; j++ )\n\t\t\t\t{\n\t\t\t\t\tfor( k = 0; k < VERTEXSIZE; k++ )\n\t\t\t\t\t\tpoly->verts[j-1][k] = poly->verts[j][k];\n\t\t\t\t}\n\n\t\t\t\t// retry next vertex next time, which is now current vertex\n\t\t\t\tlnumverts--;\n\t\t\t\tnColinElim++;\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t}\n\n\tpoly->numverts = lnumverts;\n\treturn nColinElim;\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_canal1.patch",
    "content": "{\n\"_xvk_ent_id\" \"349\" // correct spotlight\n\"_xvk_radius\" \"10\"\n//\"_light\" \"100 0 0 500\"\n\"_light\" \"100 0 0 500\"\n}\n\n{\n\"_xvk_smoothing_excluded \"275 276 277\"\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_canal2.patch",
    "content": "{\n\"_xvk_ent_id\" \"142\"\n\"_light\" \"250 240 200 3000\" // 250 240 200 300\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_elevator.patch",
    "content": "{\n\"_xvk_surface_id\" \"2921 2732 3009 2814 4720 4738 4970 4952 4844 4831\"\n\"_light\" \"128 0 0 10000\" // +0~GENERIC86R    128 0   0    10000\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_security1.patch",
    "content": "{\n\"_xvk_surface_id\" \"1495\"\n\"_light\" \"255 0 0 1000\" // EXIT1            255 0   0    1000\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_security2.patch",
    "content": "{\n\"_xvk_surface_id\" \"731\"\n\"_xvk_material\" \"mirror\"\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_tram2.patch",
    "content": "{\n\"_xvk_remove_all_sky_surfaces\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/luchiki/maps/ba_yard5a.patch",
    "content": "{\n\"_xvk_ent_id\" \"219\" // remove hack spotlight\n}\n{\n\"classname\" \"light\"\n\"origin\" \"3152 640 170\"\n\"_xvk_radius\" \"2\"\n\"_light\" \"247 249 157 64\" // add light hack\n}\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_canal1.rad",
    "content": "+0~LIGHT2A       255 250 100  0\n+0~DRKMTLS1      205 0   0    6000\n~TRN_LT1         255 255 160  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_canal1b.rad",
    "content": "~TRN_LT1         255 235 160  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_elevator.rad",
    "content": "+0~GENERIC86R    128 0   0    0\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard1.rad",
    "content": "+0~FIFTS_LGHT5   130 190 220  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  6000\n+0~GENERIC86B    60  220 170  0\n+0~TNNL_LGT4     255 245 100  2000\n+0~GENERIC86     255 245 100  2000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard2.rad",
    "content": "+0~FIFTS_LGHT5   130 190 220  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  6000\n+0~GENERIC86B    60  220 170  0\n+0~TNNL_LGT4     255 245 100  2000\n+0~GENERIC86     255 245 100  2000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard3.rad",
    "content": "+0~FIFTS_LGHT5   130 190 220  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  6000\n+0~GENERIC86B    60  220 170  0\n+0~TNNL_LGT4     255 245 100  2000\n+0~GENERIC86     255 245 100  2000\n+A~TNNL_LGT3     150 150 210  0\n~TRN_LT1         255 255 170  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard4.rad",
    "content": "+0~FIFTS_LGHT5   130 190 220  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  6000\n+0~GENERIC86B    60  220 170  0\n+0~TNNL_LGT4     255 245 100  2000\n+0~GENERIC86     255 245 100  2000\n+A~TNNL_LGT3     150 150 210  0\n~TRN_LT1         255 255 170  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard5.rad",
    "content": "+0~FIFTS_LGHT5   130 190 220  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  6000\n+0~GENERIC86B    60  220 170  0\n+0~TNNL_LGT4     255 245 100  2000\n+0~GENERIC86     255 245 100  2000\n+A~TNNL_LGT3     150 150 210  0\n~TRN_LT1         255 255 170  6000\n!TOXICGRN2       50  255 50   2000\n+0~FIFTS_LGHT01  230 230 230  1500\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_hazard6.rad",
    "content": "+0~FIFTS_LGHT01  230 230 230  1500\nGENERIC106       100 255 100  0\nGENERIC107       255 100 100  0\n+0~FIFTS_LGHT5   130 190 220  8000\n~LIGHT3C         220 210 150  6000\n~TRACKLITE       255 255 225  0\n+0~LIGHT6A       255 5   5    0\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_power1.rad",
    "content": "YELLOW           255 250 100  20000\n~TRN_LT1         255 235 160  2000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_power2.rad",
    "content": "~TRN_LT1         175 255 255  2000\nSUBWAY_LIGHTS    175 255 255  5000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_security2.rad",
    "content": "~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_teleport1.rad",
    "content": "ELEV2_CIEL       255 200 100  0\n+0~LIGHT3A       180 180 230  2500\n+0LAB1_W6        150 160 210  1000\n~SPOTRED         255 5   5    5000\n~TRN_LT1         255 240 170  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_teleport2.rad",
    "content": "ELEV2_CIEL       255 200 100  0\n+0~LIGHT3A       180 180 230  2500\n+0LAB1_W6        150 160 210  1000\n~SPOTRED         255 5   5    5000\n~TRN_LT1         255 240 170  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard1.rad",
    "content": "YELLOW           255 220 100  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard2.rad",
    "content": "~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard3.rad",
    "content": "~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard3a.rad",
    "content": "~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard3b.rad",
    "content": "~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard4.rad",
    "content": "~LIGHT3C         220 210 150  3000\nYELLOW           255 220 100  6000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard4a.rad",
    "content": "~LIGHT3C         220 210 150  3000\n0GYMLIGHT        255 250 200   500\nYELLOW           255 200 100  3000\n0LIGHT6A         255 5   5    5000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard5.rad",
    "content": "YELLOW2          255 230 100  5000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/ba_yard5a.rad",
    "content": "YELLOW2          255 230 100  5000\n"
  },
  {
    "path": "ref/vk/data/bshift/maps/lights.rad",
    "content": "// hlrad settings:\r\n// -bounce 1 -smooth 50 -extra\r\n// (from hlbs_example.rad)\r\n\r\n// halflife.wad\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n~LIGHT4A         255 255 255  5000\r\n~LIGHT3C         220 210 150  14000\r\n+0~TNNL_LGT4     180 90  40   10000\r\n+0~TNNL_LGT1     240 230 100  10000 // FIXME\r\nRED              255 0   0    1000\r\nGENERIC87A       100 255 100  1000\r\nGENERIC88A       255 100 100  1000\r\n+0~LIGHT2A       255 250 100  6000\r\n+0~FIFTIES_LGT2  160 170 220  5000 // from hlbs_example.rad\r\n+0~FIFTS_LGHT01  160 170 230  4000\r\nGENERIC106       100 255 100  1000\r\nGENERIC107       255 100 100  1000\r\nGEN_VEND1        50  180 50   1000\r\n0GYMLIGHT        255 190 60   6000\r\n+0~GENERIC65     255 255 255  1500\r\n~TRN_LT1         235 235 220  10000\r\nELEV2_CIEL       255 200 100  1000\r\n~SPOTYELLOW      189 231 253  20000\r\n+0~LIGHT3A       180 180 230  10000\r\n+0~TNNL_LGT2     190 255 255  12000\r\n+0~DRKMTLS1      205 0   0    6000\r\n+0~GENERIC86R    128 0   0    10000\r\nPANELLITE1       235 225 210  6000\r\n+0~GENERIC86B    60  220 170  20000\r\n+0~FIFTS_LGHT5   195 200 255  2000\r\n~LIGHT5A         180 170 255  2000\r\n~SPOTRED         255 5   5    20000\r\n+0~LIGHT5A       80  150 200  20000\r\nSKKYLITE         165 230 255  5000\r\n+0~LIGHT6A       255 5   5    25000\r\n//YELLOW           255 200 100  10000\r\n+0~GENERIC86     255 230 125  8000\r\n~TRACKLITE       255 255 225  1500\r\nSTAIRSRT         255 128 128  500\r\nEXIT1            255 128 128  500\r\nSTAIRSLFT        255 128 128  500\r\n~LIGHT5F         255 254 190  250\r\n+0~LIGHT4A       200 190 130  11000\r\n+0LAB1_W6        150 160 210  6000\r\n+0~LIGHT1        40  60  150  3000\r\n+0~FIFTS_LGHT3   160 170 220  3000\r\n~LIGHT3B         155 155 235  6000\r\n+0LAB1_W7        245 240 210  4000\r\n~LIGHT3E         90  190 190  6000\r\nFLATBED_HLITE2   215 180 95   20000\r\n+0~GYMLIGHT      255 190 60   20000\r\n\r\n\r\n// barney.wad\r\nba_light01       255 255 220  3500\r\nYELLOW2          255 200 100  2000\r\n0LIGHT6A         255 5   5    25000\r\n0GENERIC86       255 230 125  8000\r\n0DRKMTLS1        205 0   0    20000\r\n0TNNL_LGT4       180 90  40   3000\r\nLIGHT1_DEAD      40  60  150  1500\r\n\r\n// xeno.wad\r\nCRYS_3TOP        255 170 20   2000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/cstrike/luchiki/maps/de_dust2.patch",
    "content": "// remove bad sky surfaces\n{\n\"_xvk_surface_id\" \"2543 2544 2545 2546 2556 2559 2560 2548 2557 2561 2558 2547\"\n}\n{\n\"_xvk_surface_id\" \"2549 2550 2551 2552\"\n}\n{\n\"_xvk_surface_id\" \"2091 2106\" // fix shadow\n}\n{\n\"_xvk_surface_id\" \"302 303 268 269 525 654 653 900 899 902 926 923 898 897 924 918 925 911 923 917 901 906\"\n}\n{\n\"_xvk_surface_id\" \"260 414 413 261 415 416\"\n}\n{\n\"_xvk_surface_id\" \"2781 2789 2941 2943 2940 2942\"\n}\n{\n\"_xvk_surface_id\" \"3069 3039 3068 3038\"\n}\n{\n\"_xvk_surface_id\" \"4453 4455 4460 4456 4459\"\n}\n{\n\"_xvk_surface_id\" \"213 214 207 208 211 203 195\"\n}\n{\n\"_xvk_surface_id\" \"2094 2099 2098\"\n}\n"
  },
  {
    "path": "ref/vk/data/cstrike/maps/cs_747.rad",
    "content": "+0pl_light1 128 150 192 4000"
  },
  {
    "path": "ref/vk/data/cstrike/maps/cs_assault.rad",
    "content": "//+0~LIGHT4A       200 190 130  0\n+0~TNNL_LGT3     150 150 210  7000\nSKKYLITE         165 230 255  5000\n~LIGHT5A         75 125 255  25000\n\n"
  },
  {
    "path": "ref/vk/data/cstrike/maps/cs_delta_assault.rad",
    "content": "+0~LIGHT4A       200 190 130  0\r\n+0~TNNL_LGT3     150 150 210  7000\r\n"
  },
  {
    "path": "ref/vk/data/cstrike/maps/cs_italy.rad",
    "content": "sideLight 255 255 128 5000\r\nyellow 255 192 128 7000\r\n~blueRug 128 128 192 1000"
  },
  {
    "path": "ref/vk/data/cstrike/maps/fy_pool_day.rad",
    "content": "~LIGHT5A         210 245 255  15000\r\nbluewater        80  240 255  2000"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0.patch",
    "content": "// for dev purposes\r\n//{\r\n//\"_xvk_ent_id\" \"217\" // delete fade in entity // not work :(\r\n//\"rendercolor\" \"255 0 0\"\r\n//\"renderamt\" \"0\"\r\n//}\r\n\r\n// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"198 192 193 199 203 200 190 191 201 202 194 195 196 197\" // remove hack light entity\r\n//\"_light\" \"223 222 248 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"188 189\" // remove hack light entity \"EXIT\"\r\n//\"_light\" \"223 222 248 0\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"3456 3457 3540 3539\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3473 3471 3452 3450\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"3628\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"3638\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n//}\r\n{\r\n\"_xvk_surface_id\" \"3641 3642\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"3406 3405\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"88 87 86 89 90\" // remove hack \"ambient\" lights entity\r\n\"_light\" \"100 100 255 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"38 36 40 35 32 30 28 43 27\" // remove hack \"ambient\" lights entity\r\n\"_light\" \"100 100 255 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"92 215 214\" // remove hack lights entity\r\n\"_light\" \"146 136 232 0\" // 244 252 158\r\n}\r\n// norm lampochki\r\n{\r\n\"_xvk_ent_id\" \"39 37 41 34 33 31 29 42 26\"\r\n\"_xvk_radius\" \"5\" // fix radius\r\n\"pitch\" \"-90\" // correct pitch\r\n}\r\n{\r\n\"_xvk_surface_id\" \"340 468 909 910\" //+0~TNNL_LGT1     255 245 110  8000\r\n\"_light\" \"255 245 110  15000\" // correct light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"398 399\" //GENERIC87A GENERIC88A\r\n\"_light\" \"0 0 0 0\" // correct light\r\n}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"52 58 64 70 59 53 65 71 60 54 66 72 63 57 62 69 75 56 74 68 121 61 55 73 67\" // remove hack \"ambient\" lights entity\r\n\"_light\" \"100 100 255 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"93 94 95 96\" // remove hack spot light entity\r\n\"_light\" \"131 131 243 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"117 123 124\" // remove hack light entity\r\n\"_light\" \"240 230 100 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"5 4\" // remove hack light entity\r\n\"_light\" \"170 170 60 0\"\r\n}\r\n// lines light\r\n{\r\n\"_xvk_ent_id\" \"132 133 130 131 128 129 126 127 134 135\" // remove hack light entity\r\n\"_light\" \"170 170 60 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"166 167 164 165 156 157 154 155 152 153 150 151 162 163 160 161 159 158\" // remove hack light entity\r\n\"_light\" \"170 170 60 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2257\" // SKKYLITE         255 110 40   700\r\n\"_light\" \"240 230 100 20000\" // from hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"118 147 148 149 213\" // remove hack light entity\r\n\"_light\" \"240 230 100 0\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1573 1576 2207 2216\" //+0~FIFTS_LGHT01  230 234 255  3000\r\n\"_light\" \"230 234 255  7000\" // correct light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1566 1562\" //GENERIC87A GENERIC88A\r\n\"_light\" \"0 0 0 0\" // correct light\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1722 2397 2395\" //+0~FIFTIES_LGT2  185 195 255  3000\r\n//\"_light\" \"185 195 255  12000\" // correct light\r\n//}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0a.patch",
    "content": "// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"127 117 121 116 126 119 123 128 125 115 122 118 124 120\" // remove hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"114 113\" // remove hack light entity \"EXIT\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"3289 3290 3368 3367\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3298 3300 3281 3279\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1511\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1520\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n//}\r\n{\r\n\"_xvk_surface_id\" \"3469 3470\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"3236 3235\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3173 3172 3174 3178 3176 3175 3177 3180 3181 3179 3182 3183 3185 3184\" // subway_inend -> subway_outend\r\n\"_xvk_material\" \"subway_outend_2\"\r\n}\r\n\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"20\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"661 854\" // SKKYLITE         255 110 40   700\r\n\"_light\" \"255 110 40 4000\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"2179 1927\" // SKKYLITE         255 110 40   700\r\n//\"_light\" \"255 110 40 4000\"\r\n//}\r\n{\r\n\"_xvk_ent_id\" \"22\" // remove hack light\r\n}\r\n{\r\n\"_xvk_ent_id\" \"23\" // tune hack light\r\n\"origin\" \"-344 46 85\"\r\n\"_xvk_radius\" \"10\"\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_surface_id\" \"1675 783\" // +0~LIGHT2A       255 255 90   2500\r\n\"_light\" \"255 255 90 8000\" // more correct light\r\n}\r\n{\r\n\"_xvk_ent_id\" \"142\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"178 167\" // SKKYLITE         255 110 40   700\r\n\"_light\" \"255 110 40 2400\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1041 1092\" // SKKYLITE         255 110 40   700\r\n\"_light\" \"255 110 40 2400\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"101 102 104 103 105 106 107 108 109\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1507 1508 1633 1634 1635\" // +0~FIFTIES_LGT2  185 195 255  3000\r\n\"_light\" \"185 195 255 8000\"\r\n}\r\n\r\n\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_group\" \"2117 2116 2115 2099\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2207 2202 2209 2208\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"2707 2803 2804 2805 2806 2809 2704 2705 2727 2728 2714 2826 2824 2827 2828 2714 2715 2713 2703 2789 2790 2749 2771 2785 2764 2770 2751 2779 2736 2738 2708\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2819 2818 2816 2740 2741 2775 2753 2773 2754 2766 2786 2787 2794 2795 2725 2726 2729 2730 2799 2800 2801 2802\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0b.patch",
    "content": "// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"81 73 80 70 81 71 77 82 74 78 72 76 69 79\" // remove hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"68 67\" // remove hack light entity \"EXIT\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"4213 4214 4292 4291\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4225 4223 4202 4204\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1511\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1520\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n//}\r\n{\r\n\"_xvk_surface_id\" \"4393 4394\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"4158 4157\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4101 4102 4100 4103 4104 4106 4105 4094 4093 4095 4099 4097 4096 4098\" // subway_inend -> subway_outend\r\n\"_xvk_material\" \"subway_outend_2\"\r\n}\r\n\r\n\r\n// section 0\r\n{\r\n\"_xvk_ent_id\" \"28 29 31 30 32 33 34 35 36\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2192 2244 2243 2242 2230\" // +0~FIFTIES_LGT2  185 195 255  3000\r\n\"_light\" \"185 195 255 8000\"\r\n}\r\n\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"8\" // remove hack light\r\n}\r\n\r\n// add missing light // ~LIGHT3C         220 210 150  2500\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-2474 2456 -1530\"\r\n\"_light\" \"220 210 150 250\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-1551 2456 -1530\"\r\n\"_light\" \"220 210 150 250\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-559 2456 -1530\"\r\n\"_light\" \"220 210 150 250\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"433 2456 -1530\"\r\n\"_light\" \"220 210 150 250\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"1426 2456 -1530\"\r\n\"_light\" \"220 210 150 250\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"55\" // remove hack light\r\n}\r\n\r\n\r\n// remove hack light and spotlights for fake godrays\r\n{\r\n\"_xvk_ent_id\" \"49\" // remove hack light\r\n}\r\n{\r\n\"_xvk_ent_id\" \"57 56\" // remove hack light\r\n}\r\n\r\n{ // compromise (wrong light angle)\r\n\"classname\" \"light_environment\"\r\n\"origin\" \"-290 2299 -1513\"\r\n\"_light\" \"250 210 110 2500\"\r\n//\"_cone\" \"1\"\r\n//\"_cone2\" \"10\"\r\n//\"pitch\" \"-30\"\r\n//\"angle\" \"104\"\r\n//\"pitch\" \"-30\"\r\n//\"angle\" \"120\"\r\n\"pitch\" \"-15\"\r\n\"angle\" \"90\"\r\n}\r\n\r\n{\r\n//\"_xvk_surface_id\" \"4085 4082 4083 4084 4086 4081\" // remove fake bad god rays\r\n}\r\n{\r\n//\"_xvk_surface_id\" \"4077 4076 4080 4078 4079 4075 4073 4074\" // remove fake bad god rays\r\n}\r\n\r\n\r\n\r\n\r\n\r\n// section 2\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"45 323\" // rail\r\n\"_xvk_tex_rotate\" \"1.5\"\r\n\"_xvk_tex_offset\" \"0.2 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"322\"\r\n\"_xvk_tex_rotate\" \"2.7\"\r\n\"_xvk_tex_offset\" \"-26.3 -1\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"1\" // sun\r\n\"_light\" \"250 210 110 2500\" // original: 250 210 110 150 // need tune for hdr-autoexposure\r\n\"pitch\" \"-30\" // original pitch\r\n\"angle\" \"141\" // original angle\r\n\"pitch\" \"-30\"\r\n\"angle\" \"120\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"148 146 149\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"437 1112\" // ~SPOTBLUE        80  190 240  2000\r\n\"_light\" \"80 190 240 100000\"\r\n}\r\n\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"124 109 117 112 119 110 118 111 120 121 150\" // remove hack light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4477 4475 4470 4468 4484 4482 4491 4487\" // ~LIGHT3C         220 210 150  2500\r\n\"_light\" \"220 210 150 10000\"\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0c.patch",
    "content": "// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"354 346 353 343 348 344 350 355 347 351 345 349 342 352\" // remove hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"340 341\" // remove hack light entity \"EXIT\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"4646 4647 4724 4725\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4658 4656 4635 4637\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1511\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1520\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n//}\r\n{\r\n\"_xvk_surface_id\" \"4826 4827\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"4591 4590\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4534 4533 4535 4539 4537 4536 4538 4527 4528 4526 4529 4530 4532 4531\" // subway_inend -> subway_outend\r\n\"_xvk_material\" \"subway_outend_2\"\r\n}\r\n\r\n\r\n// section 0\r\n{\r\n\"_xvk_ent_id\" \"257 252 245 254 248 253 246 247 255 256\" // remove hack light\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"296 297 298 299 300 379 1 326\" // remove hack light\r\n}\r\n{\r\n\"_xvk_ent_id\" \"261 262 265 264 267 268 270 269 272 273 274 275 278 277 279 280 283 282 284 285 288 287 289 290 293 292 382 381 378 380 377 374 376 375\" // correct\r\n\"_light\" \"170 90 40 250\" // original: 170 90 40 150\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"71\" // remove hack light\r\n}\r\n{\r\n\"_xvk_ent_id\" \"72\" // remove hack (forgotten) light\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1866\" // +0~LIGHT4A       200 190 130  11000\r\n\"_light\" \"200 190 130 31000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3428\" // +0~TNNL_LGT4     170 90  40   10000\r\n\"_light\" \"170 90 40 50000\"\r\n}\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"104 121\" // remove hack light\r\n}\r\n// correct position and color\r\n{\r\n\"_xvk_ent_id\" \"127\"\r\n\"origin\" \"2152 1048 1255\"\r\n\"_light\" \"255 0 0 100\" // green->red\r\n}\r\n{\r\n\"_xvk_ent_id\" \"125\"\r\n\"origin\" \"2152 1048 1231\"\r\n\"_light\" \"0 255 0 100\" // red->green\r\n}\r\n{\r\n\"_xvk_ent_id\" \"126\"\r\n\"origin\" \"2112 1048 1255\"\r\n\"_light\" \"255 0 0 100\" // green->red\r\n}\r\n{\r\n\"_xvk_ent_id\" \"124\"\r\n\"origin\" \"2112 1048 1231\"\r\n\"_light\" \"0 255 0 100\" // red->green\r\n}\r\n{\r\n\"_xvk_ent_id\" \"137\"\r\n\"origin\" \"1743 760 1255\"\r\n\"_light\" \"255 0 0 100\" // green->red\r\n}\r\n{\r\n\"_xvk_ent_id\" \"135\"\r\n\"origin\" \"1743 760 1231\"\r\n\"_light\" \"0 255 0 100\" // red->green\r\n}\r\n{\r\n\"_xvk_ent_id\" \"136\"\r\n\"origin\" \"1703 760 1255\"\r\n\"_light\" \"255 0 0 100\" // green->red\r\n}\r\n{\r\n\"_xvk_ent_id\" \"134\"\r\n\"origin\" \"1703 760 1231\"\r\n\"_light\" \"0 255 0 100\" // red->green\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3728 3740 3771 3783\" // ~SPOTRED\r\n\"_xvk_material\" \"+A~GENERIC86B\" // red->green\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3722 3734 3765 3777\" // +A~GENERIC86B\r\n\"_xvk_material\" \"~SPOTRED\" // green->red\r\n}\r\n\r\n\r\n\r\n\r\n\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"147\" // remove hack light\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0d.patch",
    "content": "// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"71 63 70 60 65 61 67 72 64 68 62 66 59 69\" // remove hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"57 58\" // remove hack light entity \"EXIT\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"3124 3125 3208 3207\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3141 3139 3118 3120\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1511\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1520\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n//}\r\n{\r\n\"_xvk_surface_id\" \"3309 3310\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"3074 3073\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3010\t3009 3011 3015 3013 3012 3014 3017 3018 3016 3019 3020 3022 3021\" // subway_inend -> subway_outend\r\n\"_xvk_material\" \"subway_outend_2\"\r\n}\r\n\r\n\r\n// section 0\r\n{\r\n\"_xvk_ent_id\" \"13\" // remove hack light\r\n}\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"28 31 32 30 29 26 27\" // remove hack light\r\n}\r\n// correct light\r\n{\r\n\"_xvk_surface_id\" \"629 1552\" // SKKYLITE         255 110 40   700\r\n\"_light\" \"255 110 40 100000\"\r\n}\r\n\r\n//{ // testing\r\n//\"_xvk_surface_id\" \"980 975 872 881 999 885 886 1883 1852 1763 1709 1723 1724 1750 1728\" // !TOXICGRN2       20 255 0    350\r\n//\"_xvk_material\" \"\"\r\n//}\r\n\r\n\r\n// correct light for gman\r\n{\r\n\"_xvk_surface_id\" \"1295 1294 1436 1437\" // SUBWAY_LIGHTS    190 195 255  1500\r\n\"_light\" \"190 195 255 5000\"\r\n}\r\n\r\n// smoothing\r\n// broken massive \"barrel\"\r\n{ \r\n\"_xvk_smoothing_group\" \"1061 1017 1057 1062 1018 1058 951 913 949\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1837 1832 1838 1866 1860 1868 1865 1859 1867\"\r\n}\r\n// pipe 1\r\n{\r\n\"_xvk_smoothing_group\" \"920 933 924 931 923 917\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"911 919 918 930 934 929 926 925 928 922 921\" // FIXME: bad geometry\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"854 858 860 856 864 862\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"853 857 859 855 863 861\"\r\n}\r\n// pipe 2\r\n{\r\n\"_xvk_smoothing_group\" \"916 937 939 0 942 935\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"941 936 912 938 940\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1826 1830 1831 1827 1829 1828\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c0a0e.patch",
    "content": "// wagonchik\r\n{\r\n\"_xvk_ent_id\" \"29 21 25 19 23 16 26 28 20 27 17 22 18 24\" // remove hack light entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"14 15\" // remove hack light entity \"EXIT\"\r\n}\r\n// more bright lamp\r\n{\r\n\"_xvk_surface_id\" \"567\" // +0LAB1_W6        150 160 210  8800\r\n\"_light\" \"150 160 210  35000\"\r\n}\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_threshold\" \"55\"\r\n}\r\n// fix texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"1362 1363 1442 1443\" // subway_lights\r\n\"_xvk_tex_offset\" \"0 1.4\"\r\n\"_xvk_tex_scale\" \"1 0.9\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1374 1372 1353 1351\" // subway_ceiling (fan box)\r\n\"_xvk_tex_offset\" \"40 0\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1511\" // subway_seat\r\n//\"_xvk_tex_offset\" \"0 0.3\"\r\n//}\r\n{\r\n//\"_xvk_surface_id\" \"1520\" // subway_floor1\r\n//\"_xvk_tex_offset\" \"0 2.8\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1528 1529\" // subway_floor2\r\n\"_xvk_tex_offset\" \"0 1\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1300 1274 1278\" // subway_outside\r\n\"_xvk_tex_offset\" \"1 1\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1280\" // subway_outside\r\n\"_xvk_tex_offset\" \"0 -1\"\r\n}\r\n// fix textures\r\n{\r\n\"_xvk_surface_id\" \"1531 1530\" // subway_inpanel (more correct)\r\n\"_xvk_material\" \"subway_inpanel_2\"\r\n//\"_xvk_tex_offset\" \"15 0\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0.patch",
    "content": "\r\n// FOR TESTING (disable light)\r\n//{\r\n//\"_xvk_ent_id\" \"181\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"761 762 249 248 742 740 744 428 28 192 749 750 748 430 576 575 577 573 574 571 572 570 568 567 569 564 565 566 645 623 618 350 351 359 26 190 1670 1676\"\r\n//\"_light\" \"0 0 0 0\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"318\" // uglovoy komp\r\n//\"_light\" \"255 255 255 20\"\r\n//}\r\n\r\n\r\n\r\n// main room and corridor\r\n{\r\n\"_xvk_ent_id\" \"42 43 44 14 45 46 47\" // remove hack lights\r\n}\r\n\r\n// science room\r\n{\r\n\"_xvk_ent_id\" \"220 221\" // remove hack lights\r\n}\r\n// correct light\r\n{\r\n\"_xvk_ent_id\" \"209\"\r\n\"origin\" \"-1059 -765 -51\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"208\"\r\n\"origin\" \"-901 -765 -51\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"targetname\" \"office1\"\r\n\"style\" \"33\"\r\n//\"_light\" \"90 90 110\" // 130 130 150 (-40)\r\n\"_light\" \"130 130 150\"\r\n\"origin\" \"-1059 -725 -51\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"targetname\" \"office1\"\r\n\"style\" \"33\"\r\n//\"_light\" \"90 90 110\" // 130 130 150 (-40)\r\n\"_light\" \"130 130 150\"\r\n\"origin\" \"-901 -725 -51\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"210\"\r\n\"origin\" \"-1057 -565 -51\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"207\"\r\n\"origin\" \"-901 -565 -51\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"targetname\" \"office1\"\r\n\"style\" \"33\"\r\n\"_light\" \"130 130 150\"\r\n\"origin\" \"-1057 -525 -51\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"targetname\" \"office1\"\r\n\"style\" \"33\"\r\n\"_light\" \"130 130 150\"\r\n\"origin\" \"-901 -525 -51\"\r\n}\r\n\r\n// servernaya\r\n{\r\n\"_xvk_ent_id\" \"10 17 9 18 51 15 12 11 19 13 16\" // remove hack lights\r\n}\r\n// monitors\r\n{\r\n\"_xvk_surface_id\" \"930\"\r\n\"_xvk_tex_offset\" \"-2.4 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"934\"\r\n\"_xvk_tex_offset\" \"3 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2082\"\r\n\"_xvk_tex_offset\" \"-2.7 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2086\"\r\n\"_xvk_tex_offset\" \"-2.4 0\"\r\n}\r\n\r\n\r\n\r\n// end\r\n{\r\n\"_xvk_ent_id\" \"20\" // tune hack light entity\r\n\"_cone\" \"0\"\r\n\"_cone2\" \"50\"\r\n}\r\n\r\n\r\n// smoothing\r\n// gate\r\n{\r\n\"_xvk_smoothing_group\" \"3135 3133 3131 3129 3130 3132 3134 3136\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3143 3141 3139 3140 3142 3144 3146 3145\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3123 3121 3119 3120 3122 3124 3126 3125\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3109 3107 3105 3103 3104 3106 3108 3110\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"937\" // wall near sector B sign // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1588 2707 2736 2711 1619 1552 1515 1511 1555 2713\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1022 1038\" // walls after transition // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2167 2171 2168 2204\" // maybe slightly incorrect\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0a.patch",
    "content": "{\r\n\"_xvk_ent_id\" \"6 4 7 5\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3010\" // +0~LIGHT5A       80  150 200  10000\r\n\"_light\" \"80 150 200 120000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"198\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60  220 170 100000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"205 204\" // fix z-fighting\r\n\"origin\" \"-0.1 0 0\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"10 1 3 12 13\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"103 28 29 30 31 32 107\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"2010 2009 2008 2007 2006\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60  220 170 40000\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"195\" // remove hack lights entity\r\n}\r\n\r\n\r\n// restore light after remove hack light id 5\r\n{\r\n\"_xvk_surface_id\" \"931 1702\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"20 220 170 80000\"\r\n}\r\n\r\n\r\n// fix broken beams\r\n{\r\n\"_xvk_ent_id\" \"166 163\"\r\n\"renderamt\" \"128\"\r\n}\r\n\r\n\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_threshold\" \"50\" // TODO: more manual smoothing\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2159 2162 2156 2161 2169 2168 2160 2155 2158 2166 2165 2164\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2232 2231 2228 2229 2230 2227\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2286 2290 2291 2292 2284 2281 2285 2288 2294 2295 2287 2283\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2394 2392 2390 2388 2389 2391 2393 2395\" //  2383 2384 2387\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"1322 1324 1308\" // FIXME\r\n}\r\n//{\r\n//\"_xvk_smoothing_excluded_pairs\" \"1321 1322\" // TODO: only this\r\n//}\r\n//{\r\n//\"_xvk_smoothing_excluded_pairs\" \"1200 1220 1221 1199 1207 1217\" // TODO: need investigate\r\n//}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"145 174\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"170 174 166\"  // TODO: these faces somehow didn't autosmooth\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1200\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1324\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1323 1324 1320 1322\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1202\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1197 1202 1201\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0b.patch",
    "content": "{\r\n\"_xvk_ent_id\" \"239\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"37 38 39 40\" // remove hack lights entity\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"135 74 73 72 77 76\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"146 147\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1774 1773\" // +0~LIGHT4A       200 190 130  11000\r\n\"_light\" \"200 190 130 50000\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1837 2331 2329 2330 2393\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60  220 170 20000\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"888\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60  220 170 50000\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"18\" // remove hack lights entity\r\n\"origin\" \"2160 656 -210\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"22 19 20 21\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"134 133\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"199 198 201 200\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"191 190 192 189\" // remove hack unused lights entity\r\n}\r\n\r\n\r\n{\r\n\"_xvk_surface_id\" \"2817 2832 2827 2822\" // +ALAB1_W6        255 255 255  100\r\n\"_light\" \"255 255 255  3000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3558 3540 3546 3552\" // +0~DRKMTLS2      80  60  30   1000\r\n\"_light\" \"80 60 30 80000\"\r\n}\r\n\r\n\r\n// smoothing\r\n{\r\n//\"_xvk_smoothing_threshold\" \"0\"\r\n}\r\n\r\n//{ // transit wall\r\n//\"_xvk_smoothing_excluded_pairs\" \"883 884 886 887 903 1156\" // TODO: bad autosmooth \r\n//}\r\n{\r\n\"_xvk_smoothing_excluded\" \"886 883\" // TODO: bad autosmooth \r\n}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"1492 1496 1491 1498 1499 1497 1494 1495\" // pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1590 1587 1597 1599 1600 1598 1576 1584\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1607 1608 1604 1609 1603 1576 1584\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1592 1594 1595 1605 1610 1602 1586 1589\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1503 1505 1506 1504 1502 1500 1501 1493\" // pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1583 1613 1555 1559 1557 1528 1579 1578 1612\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1583 1613 1556 1560 1558 1529 1579 1578 1612\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1581\" // TODO: bad autosmooth \r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1577 1581 1623 1571\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1623 1633 1630 1625 1620 1563 1568 1571\" // continue pipe\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"1577 1581 1623 1571\" // continue pipe\r\n}\r\n\r\n//\r\n{\r\n\"_xvk_smoothing_group\" \"3006 3007 3003 3005 3004 3002\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2966 2967 2963 2965 2964 2962\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3124 3125 3120 3123 3122 3121\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2407\" // TODO: bad autosmooth \r\n}\r\n{\r\n//\"_xvk_smoothing_excluded\" \"2413 2417\" // TODO: bad autosmooth \r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2071 749\" // TODO: bad autosmooth \r\n}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"716 749 2071 2068\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1687 1686 1479 1747 1463\" // TODO: bad autosmooth \r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1314 1291\" // pipe // TODO: bad autosmooth \r\n}\t\r\n{\r\n\"_xvk_smoothing_group\" \"1310 1314\" // fix pipe\r\n}\r\n// TODO\r\n{\r\n//\"_xvk_smoothing_group\" \"1291 1314\" // continue pipe\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"1287 1314\" // continue pipe\r\n}\r\n\r\n// wall corner near the sign C-33/a and off lamp\r\n\r\n//{\r\n//\"_xvk_smoothing_group\" \"754 758 764 766\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"668 765 762\" // 763 (bad) // TODO: fix bad geometry\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2029 2028 2052 768 760 761 663\"\r\n//}\r\n{\r\n\"_xvk_smoothing_excluded\" \"764 758 754 766\" // TODO: bad autosmooth \r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2044 2041 763 765\" // TODO: bad autosmooth \r\n}\r\n{\r\n//\"_xvk_smoothing_excluded\" \"2029 2028 2052 760 761\" // TODO: bad autosmooth \r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"668 667\" // TODO: bad autosmooth \r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"4 754\" \r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"16 754 758 764 766 666\" \r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"2044 2044 2041 753 2045 757 2053 763 765\" //756 // TODO: fix bad geometry\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1877\" // TODO: bad autosmooth \r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0c.patch",
    "content": "// section 1\r\n{\r\n\"_xvk_ent_id\" \"17 152 20\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"24 21 18 23\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"53 303 348 364\" // +0~GENERIC86R    128 0   0    60000\r\n\"_light\" \"255 0 0 50000\"\r\n}\r\n// workaround\r\n{\r\n\"_xvk_surface_id\" \"93\" // +0~DRKMTLS2      80  60  30   1000\r\n\"_light\" \"255 0 0 200000\"\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"16 15 22 19\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"113\"\r\n\"_light\" \"100 120 160 100\"\r\n\"_xvk_radius\" \"10\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3287\" // +ALAB1_W6B       215 180 95   0\r\n\"_light\" \"215 180 95 6000\"\r\n}\r\n\r\n// \"i tak soydet\" pipe\r\n{\r\n\"_xvk_surface_id\" \"1851 1852\"\r\n\"_xvk_material\" \"generic031b\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1851 1852\"\r\n//\"_xvk_tex_offset\" \"0 200\"\r\n//\"_xvk_tex_scale\" \"1 0.05\" // ugly \"hack\" // FIXME: 90 rotation\r\n\"_xvk_tex_rotate\" \"270\"\r\n\"_xvk_tex_scale\" \"1 2.5\" // TODO: better scale and ofsset\r\n\"_xvk_tex_offset\" \"31 40\"\r\n}\r\n{ // TODO: fix bad geometry\r\n\"_xvk_smoothing_group\" \"1854 1852 1866 1853 1851 1865\"\r\n}\r\n\r\n// lift/elevator\r\n{\r\n\"_xvk_ent_id\" \"354 355\" // remove hack lights entity // TODO: fix \"buttons\"\r\n}\r\n\r\n\r\n// section 3\r\n\r\n{\r\n\"_xvk_ent_id\" \"167 166 165 164 163 186\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2390 2326 2327 2328 1704\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60  220 170 20000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1628\" // +0~LIGHT4A       200 190 130  11000\r\n\"_light\" \"200 190 130 21000\"\r\n}\r\n\r\n// section 4 (control panel)\r\n{\r\n\"_xvk_ent_id\" \"162\" // remove hack lights entity\r\n}\r\n\r\n\r\n\r\n// smoothing\r\n//{\r\n//\"_xvk_smoothing_threshold\" \"44\" // FIXME, 44 workaround // bad autosmooth (for metallic room wall)\r\n//}\r\n//{\r\n//\"_xvk_smoothing_excluded_pairs\" \"189 188 183 182 184\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \r\n//\"_xvk_smoothing_excluded_pairs\" \"182 183 184 97 74 187 188 2714 76 185\" // FIXME\r\n//}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"1067 1042\" // FIXME: bad autosmooth\r\n}\r\n//{\r\n//\"_xvk_smoothing_excluded_pairs\" \"747 748 750 751\" // FIXME: bad autosmooth\r\n//}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"747 748\" // FIXME: bad autosmooth\r\n}\r\n\r\n//{ // FIXME: bad autosmooth\r\n//\"_xvk_smoothing_group\" \"50 132 135 130 134 128 125 131 43 47 39 49 41 37 45 50 43 48 40 49 42 38 45 51 44\"\r\n//}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"3185 3188 3178 3174 3182 3194 3191\" \r\n}\r\n\r\n//\r\n{\r\n\"_xvk_smoothing_group\" \"3476 3477 3473 3475 3474 3472\" \r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3436 3437 3433 3435 3434 3432\" \r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3560 3561 3557 3559 3558 3556\" \r\n}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"1669 1673 1675 1677 1676 1672 1671 1674\" // pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2041 2054 2056 2057 2055 2051 2052 2053\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2071 2072 2070 2068 2066 2059 2062 2042 2069\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2042 2078 2080 2092 2095 2089 2075 2079\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2096 2094 2091\" // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2073 2076 2082 2086 2091 2090 2060 2063 2087 2083\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2082 2086 2093\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2093 2094\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2094 2096\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2094 2091\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2096 2091\" // continue pipe\r\n}\r\n\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"1681 1683 1684 1682 1680 1678 1679 1670\" // continue pipe\r\n}\r\n{ // TODO: too bad geometry\r\n\"_xvk_smoothing_group\" \"2112 2116 2018 2016 2012 1992 2109 2043\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2113 2117 2019 2017 2013 1993 2110 2044\" // continue pipe\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2022 2036 2101 2105 2108 2104 2030 2027\" // continue pipe\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1608 1525 1524 1527 1526\" //wall // TODO: bad autosmooth\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"750 760\" //wall // TODO: bad autosmooth\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0d.patch",
    "content": "// remove hack lights in kuhnya\r\n{\r\n\"_xvk_ent_id\" \"253\" // remove hack lights entity\r\n}\r\n\r\n\r\n// TESTED\r\n//{\r\n//\"_xvk_surface_id\" \"1234 4011 4017 4023 4029 4035 4010 4031 4033 4027 4021 4015 4009 4030 4024 4018 4012 4006 1225 4028 1232 1230 1231 4034 4019 4016 4025 4007 4022 1229 4013 1233 1227\" // generic105\r\n\"_light\" \"0 0 0 0\" // disable light from junk surfaces\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"4040 4041 4047 4053 4059 4065 4039 4045 4051 4057 4063 4036 4042 4048 4054 4060 4061 1206 1207 1208 1205 4058 4049 4064 4046 4055 4037 4052 4043 1204\" // generic106\r\n\"_light\" \"0 0 0 0\" // disable light from junk surfaces\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1224 1175 1173 1174 1219 1218 1220 1215 1216 4008 4014 4020 4026 4032 1217 1221 1179 1180\" // generic105\r\n//\"_light\" \"0 0 0 0\" // disable light from soda vending machine \r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"1191 1176 1194 1193 1186 1185 1187 1182 1183 4038 4044 4050 4056 4062 1184 1188\" // generic106\r\n\"_light\" \"0 0 0 0\" // disable light from soda vending machine\r\n//}\r\n\r\n// mirror in toilet\r\n{\r\n\"_xvk_surface_id\" \"2057\"\r\n\"_xvk_material\" \"mirror\"\r\n}\r\n\r\n\r\n\r\n\r\n// correct position spotlight in suit room\r\n{\r\n\"_xvk_ent_id\" \"220\"\r\n\"origin\" \"-3648 832 -188\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"221\"\r\n\"origin\" \"-3648 960 -188\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"223\"\r\n\"origin\" \"-3648 960 -188\"\r\n\"_xvk_radius\" \"5\" // focus\r\n}\r\n{\r\n\"_xvk_ent_id\" \"224\"\r\n\"origin\" \"-3648 1088 -188\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n\r\n// remove hack light in razdevalka\r\n{\r\n\"_xvk_ent_id\" \"231 229\"\r\n}\r\n\r\n//{\r\n//\"_xvk_surface_id\" \"1507 1508\" // +A~FIFTIES_LGT2  160 170 220  4000\r\n//\"_light\" \"160 170 220 8000\"\r\n//}\r\n\r\n\r\n\r\n// remove hack light in servernya (for perfomance? this room out of reach player)\r\n{\r\n\"_xvk_ent_id\" \"122 128 121 129 135 126 124 123 130 125 127\"\r\n}\r\n\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"918 924 922\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"2550 2551 2549\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"2634 2635 2562\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"2647 2648 2646\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2899\" // wall // TODO: bad autosmooth\r\n}\r\n//{\r\n//\"_xvk_smoothing_excluded\" \"2210 2214\" // TODO: bad autosmooth (impossible to fix manually)\r\n//}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1988 1983 1985\" // wall // TODO: bad autosmooth\r\n}\r\n// maybe incorrect\r\n{\r\n//\"_xvk_smoothing_group\" \"1988 1985\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"2130 1988\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"2131 1983\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"1988 1983\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"2130 2131\"\r\n}\r\n//\r\n{\r\n\"_xvk_smoothing_excluded\" \"1446 1494 1971 1970 1965 1967 1968 1966 1906 1907 1908 1439 1877 1956 1955\" // near the ceiling // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1952 1955\"\r\n}\r\n//\r\n{ // slightly incorrect\r\n//\"_xvk_smoothing_excluded\" \"2535 2536\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"2536 2813\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2895\" // wall // TODO: bad autosmooth\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a0e.patch",
    "content": "{\r\n\"_xvk_ent_id\" \"59 58 60 57\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"95\" // remove hack lights entity\r\n}\r\n\r\n// don't uncomment, need for green state\r\n//{\r\n//\"_xvk_ent_id\" \"199 200 201 198\" // remove hack lights entity\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"2070 2055 2060 2065\" // +ALAB1_W6        255 255 255  100\r\n//\"_light\" \"255 255 255  3000\"\r\n//}\r\n\r\n{\r\n\"_xvk_surface_id\" \"2467 2473 2455 2461\" // +0~DRKMTLS2      80  60  30   1000\r\n\"_light\" \"80 60 30 80000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"490 172 488 489 173 487 175 486 174\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"97\"\r\n\"origin\" \"1848 226 168\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"56\"\r\n\"origin\" \"2164 656 -213\"\r\n}\r\n\r\n\r\n\r\n\r\n// red flashing lights (does not work now)\r\n//{\r\n//\"classname\" \"light\"\r\n//\"targetname\" \"red\"\r\n//\"spawnflags\" \"1\"\r\n//\"style\" \"33\"\r\n//\"_light\" \"255 0 0 100\"\r\n//\"origin\" \"1323 562 -277\"\r\n//\"_xvk_radius\" \"2\"\r\n//}\r\n//{\r\n//\"classname\" \"light\"\r\n//\"targetname\" \"red\"\r\n//\"spawnflags\" \"1\"\r\n//\"style\" \"33\"\r\n//\"_light\" \"255 0 0 100\"\r\n//\"origin\" \"1323 678 -277\"\r\n//\"_xvk_radius\" \"2\"\r\n//}\r\n//{\r\n//\"classname\" \"light\"\r\n//\"targetname\" \"red\"\r\n//\"spawnflags\" \"1\"\r\n//\"style\" \"33\"\r\n//\"_light\" \"255 0 0 100\"\r\n//\"origin\" \"1146 582 -281\"\r\n//\"_xvk_radius\" \"2\"\r\n//}\r\n//{\r\n//\"classname\" \"light\"\r\n//\"targetname\" \"red\"\r\n//\"spawnflags\" \"1\"\r\n//\"style\" \"33\"\r\n//\"_light\" \"255 0 0 100\"\r\n//\"origin\" \"1146 658 -281\"\r\n//\"_xvk_radius\" \"2\"\r\n//}\r\n\r\n{ // remove bad texture faces\r\n\"_xvk_surface_id\" \"2105 2106 2104\" // {ladder2\r\n}\r\n\r\n\r\n// missing xen lights\r\n{\r\n\"classname\" \"light\"\r\n\"_light\" \"0 50 255 300\"\r\n\"origin\" \"-1419 -2640 3071\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"_light\" \"0 50 255 300\"\r\n\"origin\" \"-1150 -2445 3083\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"_light\" \"0 50 255 300\"\r\n\"origin\" \"-2918 -2295 3174\"\r\n\"_xvk_radius\" \"2\"\r\n}\r\n\r\n\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_excluded\" \"560 528\" // wall // TODO: bad autosmooth \r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"630 210 961 628 646\" // wall // TODO: bad autosmooth \r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"214 210 218 630\"\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1.patch",
    "content": "// section 1\r\n{\r\n\"_xvk_ent_id\" \"117 116 115 113 142\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2681 2700 2703\" // +0~GENERIC86B    60  220 170  10000\r\n\"_light\" \"60 220 170 20000\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"2997 2994 2999 2993 2998\" // +0~LIGHT6A       150 5   5    25000\r\n\"_light\" \"150 5 5 125000\"\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"131 43 44 45 9 10\" // remove hack lights entity\r\n}\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"85 77\" // remove hack lights entity\r\n}\r\n\r\n// section 4 // lift/elevator\r\n{\r\n\"_xvk_ent_id\" \"5 4 6\" // remove hack lights entity\r\n}\r\n{ // TODO: animate texture\r\n\"_xvk_ent_id\" \"354 355\" // remove hack lights entity\r\n}\r\n{ // TODO: animate texture\r\n\"_xvk_surface_id\" \"1183\" // +0~DRKMTLS1      205 0   0    6000\r\n\"_light\" \"205 0 0 10000\"\r\n//\"_xvk_material\" \"RED1_ANIMATE\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"55 54 53 52 51\" // remove hack lights entity\r\n}\r\n{ // disable light for broken elevator\r\n\"_xvk_surface_id\" \"3484\" // ++0~LIGHT5A       80  150 200  10000\r\n\"_light\" \"80 150 200 0\"\r\n}\r\n\r\n// section 5\r\n{\r\n\"_xvk_ent_id\" \"2 3 7\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"109\" // remove hack lights entity\r\n}\r\n\r\n// smoothing\r\n{\r\n\"_xvk_smoothing_excluded\" \"2162 2215\" //wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2078 2077 2231 2233 2232\" //wall // TODO: bad autosmooth\r\n}\r\n\r\n// broken mini pipe\r\n// 2794 2792 2790 2791 2799 2802 2755 2787 2788 2800 2801\r\n{\r\n\"_xvk_smoothing_group\" \"2802 2803 2786 2768 2764 2796\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2803 2801 2800\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2786 2801\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2786 2789 2787 2792\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2768 2793 2792\"\r\n}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2788 2800\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2787 2788\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2794 2792\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2794 2790\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2790 2791\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2791 2792\"\r\n//}\r\n//{\r\n//\"_xvk_smoothing_group\" \"2792 2796 2799\"\r\n//}\r\n// mini pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2824 2827 2828 2823 2826 2825\"\r\n}\r\n// mini pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2878 2884 2890 2888 2887 2882\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2880 2881 2892 2891 2885 2883\"\r\n}\r\n\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2975 2974 2972 2970 2965 2967 2971 2973\"\r\n}\r\n\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"3944 3946 3947 3945 3943 3941 3940 3942\"\r\n}\r\n\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2613 2609 2606 2586 2587 2581 2602 2601\"\r\n}\r\n// continue pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2619 2617 2593 2589 2591 2596 2604 2616 2618 2608\" // 2600 2612 2611 2610\r\n}\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2628 2632 2634 2636 2635 2599 2624\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2636 2642 2643 2664 2640 2639\"\r\n}\r\n// continue pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2627 2631 2633 2641 2645 2638 2621 2623\"\r\n}\r\n\r\n//wall ceil\r\n{\r\n\"_xvk_smoothing_excluded\" \"2926\"\r\n}\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1a.patch",
    "content": "// section 1\r\n{\r\n\"_xvk_ent_id\" \"191 181\" // remove hack lights entity\r\n}\r\n\r\n\r\n\r\n// razdevalka\r\n{\r\n\"_xvk_ent_id\" \"310 255\" // remove hack lights entity\r\n}\r\n\r\n// mirror in toilet\r\n{\r\n\"_xvk_surface_id\" \"1936 1934 1933 1935\"\r\n\"_xvk_material\" \"mirror_broken\"\r\n}\r\n\r\n//smooth\r\n\r\n//wall\r\n{\r\n//\"_xvk_smoothing_excluded\" \"2471\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1b.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"140\" // remove hack lights entity\r\n}\r\n{ // TODO: animate texture\r\n\"_xvk_surface_id\" \"3391 3407\" // +0~FIFTS_LGHT4   160 170 220  4000\r\n\"_light\" \"160 170 220  5000\"\r\n//\"_xvk_material\" \"WHITE1_ANIMATE\"\r\n}\r\n\r\n// section 1\r\n// TODO: correct brightness\r\n{\r\n\"_xvk_ent_id\" \"19\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2298\" // +A~FIFTIES_LGT2  160 170 220  5000\r\n\"_light\" \"160 170 220 0\"\r\n}\r\n\r\n\r\n// section ...\r\n{\r\n\"_xvk_ent_id\" \"17 18\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"25\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"20\" // remove hack lights entity\r\n\"_light\" \"80 80 160 500\"\r\n\"origin\" \"1392 -10 35\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"27\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"26\" // remove hack lights entity\r\n\"_light\" \"80 80 160 500\"\r\n\"origin\" \"1761 -1034 57\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"110 109 108 23\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"22 21 24\" // remove hack lights entity\r\n}\r\n\r\n\r\n// canalization\r\n{\r\n\"_xvk_ent_id\" \"366 365 364 363 362 367 368 369 370 371\" // remove hack lights entity\r\n}\r\n{ // focus\r\n\"_xvk_ent_id\" \"361\"\r\n\"origin\" \"2348 -1186 -288\"\r\n\"_light\" \"247 249 157 150\"\r\n}\r\n\r\n// section ...\r\n{\r\n\"_xvk_ent_id\" \"267 266\" // remove hack lights entity\r\n}\r\n{ // FIXME: color should take from translucent texture\r\n\"_xvk_surface_id\" \"9\" // +0LAB1_W6B       150 160 210  4000\r\n\"_light\" \"70 200 110 4000\" // color from hack light\r\n}\r\n\r\n\r\n// smooth\r\n{\r\n\"_xvk_ent_id\" \"329 321 322 323 324\"\r\n\"_xvk_smooth_entire_model\" \"1\"\r\n}\r\n//{ // FOR TESTING\r\n//\"_xvk_smoothing_group\" \"5577 5575 5579 5581 5576 5586 5583 5585 5584 5582 5580 5578 \" // 5569 5572 5571 5570 5573 + //5567 5565 5568 5566\r\n//}\r\n\r\n\r\n{\r\n//\"_xvk_smoothing_excluded_pairs\" \"2790 2791 2792 2793 3187 2850 2874 2871 2872 2853 2900 2901 3182 3181\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n//\"_xvk_smoothing_excluded_pairs\" \"2792 2790 2793 2791 2871 2850 2872 3187\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n//\"_xvk_smoothing_excluded_pairs\" \"2788 2786 2787 3191 2786 2787 2870 2849 2868\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n//\"_xvk_smoothing_threshold\" \"44\" //workaround\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2120 2117 2118 1269 1267 2791 2792 2793\"\r\n}\r\n\r\n// wall\r\n{\r\n\"_xvk_smoothing_excluded\" \"2302 2480 2540\"\r\n}\r\n\r\n// pipes\r\n{ // pipe1\r\n\"_xvk_smoothing_group\" \"2262 2263 2261 2259 2186 2257 2258 2260\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2278 2280 2279 2277 2275 2273 2274 2276\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2272 2270 2268 2266 2187 2264 2269 2271\"\r\n}\r\n{ // water pipe1\r\n\"_xvk_smoothing_group\" \"3932 3930 3928 3934 3944 3940 3938 3939 3933\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3929 3931 3927 3935 3936 3943 3941 3942 3937 3926\"\r\n}\r\n{ // pipe2\r\n\"_xvk_smoothing_group\" \"2212 2214 2216 2215 2213 2211 2208 2210\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2228 2230 2229 2227 2225 2223 2224 2226\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2242 2244 2248 2250 2249 2247 2238 2240\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2717 2719 2721 2723 2724 2722 2715 2705\"\r\n}\r\n{ // pipe3\r\n\"_xvk_smoothing_group\" \"2220 2218 2207 2196 2194 2198 2219 2209\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2221 2232 2191 2193 2192 2190 2200 2235 2234\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2253 2252 2243 2241 2239 2237 2251 2245\"\r\n}\r\n{ // pipe kusok\r\n\"_xvk_smoothing_group\" \"4405 4407 4409 4408 4406 4404 4402 4403\"\r\n}\r\n\r\n// mini pipes\r\n{\r\n\"_xvk_smoothing_group\" \"3854 3855 3848\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"3850 3872 3000 2988 2999 2987 2998 2986\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2996 2979\"\r\n}\r\n\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2367 2369 2370 2368 2366 2364 2363 2365\"\r\n}\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2448 2446 2447 2449 2391 2398 2399 2397\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2402 2403 2446 2447 2450 2454 2391\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2405 2401 2407 2452 2451 2387 2382 2383 2381\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1010 1002 1009 1011 1013 1015 1014 1012\"\r\n}\r\n\r\n// pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2430 2434 2420 2428 2415 2410 2412 2411\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2432 2431 2433 2425 2424\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"2427 2425 2424 2426 2418 2406 2409 2400\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1002 1007 999 998 1004 1006 1008 1003\"\r\n}\r\n\r\n// wall\r\n{\r\n\"_xvk_smoothing_excluded\" \"2633 3178 2634\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2788 2786 2787\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2898\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2798 2800 2799\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1605 1606\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1258 831 833\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"840 837\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"1807 1805 1481 1479\"\r\n}\r\n\r\n// mine pipe\r\n{\r\n\"_xvk_smoothing_group\" \"2584 2597 2582 2581 2962 2965 2961 2958\"\r\n}\r\n\r\n//wall\r\n{\r\n\"_xvk_smoothing_excluded\" \"2930 2929\"\r\n}\r\n\r\n// wall\r\n{\r\n\"_xvk_smoothing_excluded\" \"1076\"\r\n}\r\n\r\n// grating\r\n{\r\n\"_xvk_smoothing_group\" \"1172 1173 1174 1175 803 800 801 802\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1841 1845 1838 1846 1349 1353 1354 1355\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1842 1844 1839 1847 1350 1357 1358 1352\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1843 1849 1840 1848 1351 1360 1359 1356\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1177 1176 1179 1778 1837 1850 1852 1851\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"808 807 799 809 1366 1365 1368 1367\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"806 805 798 804 1362 1363 1364 1361\"\r\n}\r\n\r\n\r\n\r\n// canalization\r\n{ // bad geometry\r\n//\"_xvk_smoothing_excluded\" \"629 581 577 583\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"629 581 577 583\"\r\n}\r\n\r\n\r\n\r\n// wall\r\n{\r\n\"_xvk_smoothing_excluded\" \"1781 1782 1783\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1c.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"211 210\" // remove hack lights entity\r\n}\r\n{ // FIXME: color should take from translucent texture \r\n\"_xvk_surface_id\" \"3506\" // +0LAB1_W6B       150 160 210  4000\r\n\"_light\" \"70 200 110 4000\" // color from hack light\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"112 111 115 114 121 116 109 110 113 95\" // remove hack lights entity\r\n}\r\n{ // light-wall fix\r\n\"_xvk_ent_id\" \"36 38 41 37 39\"\r\n\"origin\" \"0 0 -7\"\r\n}\r\n{ // sign \"caution\"\r\n\"_xvk_ent_id\" \"40\"\r\n\"origin\" \"0 -1 0\"\r\n}\r\n// remove hack light 93 (for sign \"caution\")\r\n{\r\n\"_xvk_ent_id\" \"93\" // remove hack lights entity\r\n}\r\n{ // sign \"caution\"\r\n\"_xvk_surface_id\" \"4169\"\r\n\"_light\" \"255 255 255 100\"\r\n}\r\n\r\n// section 2 (elevator)\r\n{\r\n\"_xvk_ent_id\" \"102 92 101 99\" // remove hack lights entity\r\n}\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"100 166 108 94\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"96 149 150 152 151 147 144\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"531\" // +0~LIGHT4A       200 190 130  11000\r\n\"_light\" \"200 190 130 35000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"421 736\" // ~TRN_LT1         160 170 220  10000\r\n\"_light\" \"160 170 220 30000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"104\"\r\n\"origin\" \"216 -1128 -2409\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"119\"\r\n\"origin\" \"-8 -1128 -2409\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"120\"\r\n\"origin\" \"-8 -904 -2409\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"118\"\r\n\"origin\" \"216 -904 -2409\"\r\n}\r\n\r\n// section 4\r\n{ // TODO: tune origin\r\n\"_xvk_ent_id\" \"390 389 445 444\" // remove hack lights entity\r\n}\r\n\r\n// section 5\r\n// smooth pipes\r\n// pipe 1\r\n{\r\n\"_xvk_smoothing_group\" \"1523 1519 1506 1503 1512 1509 1516 1520\"\r\n}\r\n{\r\n//\"_xvk_smoothing_group\" \"1558 1554 1506 999 997 1003 1001 1509 1553 1516 1520\"\r\n\"_xvk_smoothing_group\" \"1001 1003 997 999\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1557 1555 1556 1507 998 996 1000 1002 1510 1552 1513\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1554 1558 1557\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"5768 5766 5764 5765 5767 5769 5771 5770\"\r\n}\r\n// pipe 2\r\n{\r\n\"_xvk_smoothing_group\" \"1514 1525 1535 1545 1550 1540 1528 1533\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1546 1542 1548 1543 1547\" // bad\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"1543 1531 1530 1527 1524 1537 1539 1538 1536 1531\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"5780 5778 5776 5774 5775 5777 5779 5781\"\r\n}\r\n// pipe 3\r\n{\r\n\"_xvk_smoothing_group\" \"1499 1497 1377 1369 1379 1378 1376 1367 1498 1500\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"975 990 978 994 993 988 967 971\"\r\n}\r\n// pipe 4\r\n{\r\n\"_xvk_smoothing_group\" \"1375 1374 1372 1368 1366 1370 1371 1373\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"985 976 972 968 964 980 982 986\"\r\n}\r\n// pipe 5\r\n{\r\n\"_xvk_smoothing_group\" \"919 923 920 916\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"887 843 854 892\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"927 930 934 935 948\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"887 948 916\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"925 928 895 907 906 911 900 949 939 941 933 931 940 932 929 944\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"887 948 916\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"901 947\"\r\n}\r\n// pipe 6\r\n{\r\n\"_xvk_smoothing_group\" \"884 881 878 859 897 899 898 896\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"909 910 908 902 912 904 905 903\" // bad\r\n} \r\n{\r\n\"_xvk_smoothing_group\" \"946 938 943 926\"\r\n} \r\n{\r\n\"_xvk_smoothing_group\" \"943 937\"\r\n}\r\n// pipe 7\r\n{\r\n\"_xvk_smoothing_group\" \"846 963 960 956 875 952 954 959\"\r\n}\r\n// pipe 8\r\n{\r\n\"_xvk_smoothing_group\" \"864 868 872 873 869 865 852 848\"\r\n}\r\n\r\n\r\n// section 6\r\n{\r\n\"_xvk_ent_id\" \"405 404 397 396\" // remove hack lights entity\r\n}\r\n// section 7\r\n{\r\n\"_xvk_ent_id\" \"288 289\" // remove hack lights entity\r\n}\r\n// section 8\r\n{\r\n\"_xvk_ent_id\" \"172 169\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"105\"\r\n\"origin\" \"-408 446 -2911\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"117\"\r\n\"origin\" \"-1845 449 -2911\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"103\"\r\n\"origin\" \"-2188 444 -2911\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"98 97\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"107\"\r\n\"origin\" \"-2797 376 -2959\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"106\"\r\n\"origin\" \"-2797 688 -2959\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"293 292 297\" // remove hack lights entity\r\n}\r\n\r\n// section 9\r\n{\r\n\"_xvk_ent_id\" \"174\" // remove hack lights entity\r\n}\r\n\r\n// section 10\r\n{\r\n\"_xvk_ent_id\" \"451 450 457 456\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"449\"\r\n\"origin\" \"-1231 1040 -2597\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"448\"\r\n\"origin\" \"-1231 1304 -2597\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"455\"\r\n\"origin\" \"-1322 1558 -2595\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"454\"\r\n\"origin\" \"-1586 1558 -2595\"\r\n}\r\n\r\n// section 11\r\n{\r\n\"_xvk_ent_id\" \"330 331 328 329\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"339 324 326 325\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1973 1972 1971 1974\" // ELEV2_CIEL       255 200 100  2000\r\n\"_light\" \"255 200 100 7000\"\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1d.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"48\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"34\" // remove hack lights entity\r\n}\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"30 29 26 27 31 32 28 25\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"896 907 775 761 753 754 147 100\" // +A~FIFTIES_LGT2  160 170 220  5000\r\n\"_light\" \"160 170 220 25000\"\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"49\" // remove hack lights entity\r\n}\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"39\" // ?\r\n\"origin\" \"1144 914 -123\"\r\n}\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"43 44\" // remove hack lights entity\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a1f.patch",
    "content": "// section 0\r\n{\r\n//\"_xvk_ent_id\" \"294\" // remove hack lights entity\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"274 295 272 276 279\" // remove hack lights entity\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"280 281\" // remove hack lights entity\r\n}\r\n{ // another red light\r\n\"_xvk_ent_id\" \"284\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"290\"\r\n\"origin\" \"202 524 -32\"\r\n//\"style\" \"1\"\r\n//\"style\" \"32\"\r\n//\"pattern\" \"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba\"\r\n//\"pattern\" \"amammamaamamama\"\r\n\"_light\" \"255 0 0 150\" // TODO: lightstyle\r\n}\r\n{\r\n\"_xvk_ent_id\" \"287\"\r\n//\"origin\" \"63 50 -75\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"285\"\r\n//\"origin\" \"-161 50 -76\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"289\"\r\n//\"origin\" \"-53 356 -142\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"288\"\r\n\"origin\" \"-53 285 -142\"\r\n}\r\n\r\n// section 3 (servernaya)\r\n{\r\n\"_xvk_ent_id\" \"283 296 275 278 291 273 292 271\" // remove hack lights entity\r\n}\r\n\r\n// monitors // TODO: BETTER STRETCH\r\n{\r\n\"_xvk_surface_id\" \"4146\" // 4140\r\n\"_xvk_tex_offset\" \"1 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4158\" // 4152\r\n\"_xvk_tex_offset\" \"1.5 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4170\" // 4164\r\n\"_xvk_tex_offset\" \"0.3 0\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"4176\" // 4134\r\n\"_xvk_tex_offset\" \"1.5 0\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"4592 4593 4594 4591 4589\" // remove additive \"god rays\" fade2\r\n\"_xvk_material\" \"\"\r\n}\r\n\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"400\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"299 303 304 305\" // remove hack lights entity\r\n}\r\n// TODO: animate texture\r\n{\r\n\"classname\" \"light\"// hack hack light entity\r\n\"_xvk_radius\" \"2\"\r\n\"style\" \"10\"\r\n\"origin\" \"-671 -521 -65\"\r\n\"_light\" \"40 60 70 180\"\r\n}\r\n{\r\n\"classname\" \"light\"// hack hack light entity\r\n\"_xvk_radius\" \"2\"\r\n\"style\" \"10\"\r\n\"origin\" \"-671 -486 -65\"\r\n\"_light\" \"40 60 70 180\"\r\n}\r\n{\r\n\"classname\" \"light\"// hack hack light entity\r\n\"_xvk_radius\" \"2\"\r\n\"style\" \"10\"\r\n\"origin\" \"-671 -450 -65\"\r\n\"_light\" \"40 60 70 180\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"306\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"302 300\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3035 3052\" // ~SPOTBLUE        7   163 245  18000\r\n\"_light\" \"7 163 245 25000\"\r\n}\r\n\r\n// section 5\r\n{\r\n\"_xvk_ent_id\" \"377\" // remove hack lights entity\r\n// TODO: animate texture\r\n}\r\n{\r\n\"_xvk_ent_id\" \"297 301\" // remove hack lights entity\r\n}\r\n\r\n// fix wrong textures with texture coordinates\r\n{\r\n\"_xvk_surface_id\" \"2363 2352 2351 2386 2389 2562 1002 1003 1013 1014\"\r\n\"_xvk_material\" \"crete4_flr03\"\r\n\"_xvk_tex_offset\" \"20 -15.5\"\r\n\"_xvk_tex_scale\" \"0.5 1\"\r\n}\r\n\r\n{ // i tak soydet\r\n\"_xvk_surface_id\" \"2362\"\r\n\"_xvk_material\" \"crete4_flr03\"\r\n\"_xvk_tex_offset\" \"10 16\"\r\n\"_xvk_tex_scale\" \"0.5 1\"\r\n}\r\n{ // i tak soydet\r\n\"_xvk_surface_id\" \"2563\"\r\n\"_xvk_material\" \"crete4_flr03\"\r\n\"_xvk_tex_offset\" \"10 -15.5\"\r\n\"_xvk_tex_scale\" \"0.5 1\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"999\" // LITEPANEL1\r\n\"_xvk_material\" \"+ALAB1_W6\"\r\n\"_light\" \"0 0 0 0\"\r\n\"_xvk_tex_rotate\" \"90\"\r\n\"_xvk_tex_scale\" \"1.6 0.5\"\r\n\"_xvk_tex_offset\" \"35 2.5\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1010\" // LITEPANEL1\r\n\"_light\" \"255 255 255 4000\"\r\n\"_xvk_material\" \"+0LAB1_W6\"\r\n\"_xvk_tex_rotate\" \"90\"\r\n\"_xvk_tex_scale\" \"1.62 0.5\"\r\n\"_xvk_tex_offset\" \"25 2.5\" // 24.7\r\n}\r\n{\r\n\"_xvk_surface_id\" \"975 2354\" // +0~FIFTS_LGHT01\r\n\"_xvk_material\" \"+0~FIFTIES_LGT2\"\r\n\"_light\" \"255 255 255 3000\"\r\n\"_xvk_tex_offset\" \"42 20\"\r\n\"_xvk_tex_scale\" \"2 1.2\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"974 2357\" // +0~FIFTS_LGHT01\r\n\"_xvk_material\" \"+0~FIFTIES_LGT2\"\r\n\"_light\" \"255 255 255 3000\"\r\n\"_xvk_tex_offset\" \"42 -19\"\r\n\"_xvk_tex_scale\" \"2 1.2\"\r\n}\r\n{ \r\n\"_xvk_ent_id\" \"282\" // remove hack light entity\r\n\r\n// fix gate\r\n{\r\n\"_xvk_smoothing_group\" \"796 795 807 800 809 808 805 801\" // maybe 780\r\n}\r\n{\r\n\"_xvk_surface_id\" \"796 795 807 800 809 808 805 801 796 780\"\r\n\"_xvk_material\" \"generic_metal1\"\r\n}\r\n{\r\n\"_xvk_smoothing_group\" \"766 765 772 773 777 776 768 769\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"766 765 772 773 777 776 768 769\"\r\n\"_xvk_material\" \"generic_metal1\"\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_group\" \"3176 3175 3195\" // wall // TODO: fix bad geometry\r\n}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"3060 3062\" // wall // TODO: bad autosmooth\r\n}\r\n{\r\n\"_xvk_smoothing_excluded_pairs\" \"3061 3063 3065\" // wall // TODO: bad autosmooth\r\n}\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"2941 3107 2981 2980 2976 2944\" // wall // TODO: bad autosmooth\r\n}\r\n\r\n\r\n{\r\n\"_xvk_smoothing_excluded\" \"989 988\" // wall // TODO: bad autosmooth\r\n}\r\n\r\n\r\n// bottom tunnel gate light\r\n{\r\n\"_xvk_ent_id\" \"485\" // remove hack lights entity\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a2.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"330 331 328 329\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"323 324 326 325\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"723 722 903 902\" // ELEV2_CIEL       255 200 100  2000\r\n\"_light\" \"255 200 100 7000\"\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"501\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"502\"\r\n\"_light\" \"160 170 220 100\"\r\n}\r\n{ // TODO: better design light\r\n\"_xvk_ent_id\" \"378\"\r\n\"_cone\" \"1\"\r\n\"_cone2\" \"300\"\r\n//\"targetname\" \"flickerlight\"\r\n//\"style\" \"10\"\r\n}\r\n{ // FIXME: remove and recreate by points light with lightstyle (when it will work)\r\n\"_xvk_ent_id\" \"276\"\r\n\"_cone\" \"1\"\r\n\"_cone2\" \"200\"\r\n//\"style\" \"10\"\r\n\"origin\" \"1833 -674 -521\"\r\n\"pitch\" \"120\"\r\n\"_light\" \"255 255 255 170\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"379\"\r\n\"_light\" \"160 170 220 250\"\r\n\"_cone\" \"1\"\r\n\"_cone2\" \"300\"\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"1719\" // +A~FIFTS_LGHT4\r\n//\"_light\" \"160 170 220 100\"\r\n//}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"15 493 178\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"214 222\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"220 225\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"420\"\r\n\"origin\" \"2942 -996 -539\"\r\n\"_light\" \"255 255 255 150\" // correct light\r\n\"pitch\" \"10\"\r\n\"angle\" \"315\"\r\n}\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"169 170 70 71 68 69 66 65 64\" // remove hack lights entity\r\n}\r\n\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"181\"\r\n\"origin\" \"358 -524 -540\"\r\n\"_light\" \"40 80 40 700\" // correct light\r\n}\r\n\r\n// section 5\r\n{\r\n\"_xvk_ent_id\" \"602 504\"\r\n}\r\n\r\n// section 6\r\n{\r\n\"_xvk_ent_id\" \"248\"\r\n\"origin\" \"38 -1046 -461\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"246\"\r\n\"origin\" \"-203 -1174 -461\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"245\"\r\n\"origin\" \"-515 -1103 -461\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"247\"\r\n\"origin\" \"-196 -1024 -461\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"498\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"184\"\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a2a.patch",
    "content": "// more correct hack\r\n{\r\n\"_xvk_ent_id\" \"52\"\r\n\"origin\" \"-56 -624 -140\"\r\n\"_light\" \"120 130 180\" // 160 170 220 (-40)\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-84 -624 -140\"\r\n\"targetname\" \"scilights\"\r\n\"style\" \"33\"\r\n\"_light\" \"120 130 180\" // 160 170 220 (-40)\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"120 119\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"126 127\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"2412\" // generic88a\r\n//\"_xvk_material\" \"black\" // fix texture\r\n\"_light\" \"0 0 0 0\"\r\n}\r\n\r\n\r\n// more correct hack\r\n{\r\n\"_xvk_ent_id\" \"50\"\r\n\"origin\" \"1062 -892 -140\"\r\n\"_light\" \"120 120 145\" // 160 160 185 (-40)\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"1094 -892 -140\"\r\n\"targetname\" \"barnlights\"\r\n\"style\" \"32\"\r\n\"_light\" \"120 120 145\" // 160 160 185 (-40)\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"83\" // remove hack lights entity\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a2b.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"99\" // remove hack lights entity\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"458 459\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"284 285\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1974 298\" // +0~TNNL_LGT2     190 255 255  12000\r\n\"_light\" \"190 255 255 100000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"146 145\" // remove hack lights entity\r\n}\r\n\r\n// correct lights\r\n{\r\n\"_xvk_ent_id\" \"192\"\r\n\"origin\" \"1040 -156 31\"\r\n\"_light\" \"244 252 158 125\" // 244 252 158 175\r\n\"_cone\" \"64\"\r\n\"_cone2\" \"128\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"195\"\r\n\"origin\" \"1040 -420 31\"\r\n\"_light\" \"244 252 158 125\" // 244 252 158 175\r\n\"_cone\" \"64\"\r\n\"_cone2\" \"128\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"147 151 148 149 150\" // remove hack lights entity\r\n}\r\n\r\n// section 1.5\r\n\r\n// correct lights\r\n{\r\n\"_xvk_ent_id\" \"385\"\r\n\"origin\" \"1099 -57 176\"\r\n\"_light\" \"244 252 158 105\" // \"244 252 158 175\"\r\n\"_cone\" \"64\"\r\n\"_cone2\" \"128\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"388\"\r\n\"origin\" \"1099 -320 176\"\r\n\"_light\" \"244 252 158 105\" // 244 252 158 175\r\n\"_cone\" \"64\"\r\n\"_cone2\" \"128\"\r\n}\r\n\r\n\r\n//{ // TODO: fix 2 side texture coordinates\r\n//\"_xvk_surface_id\" \"7011 7010\" // {gratestep2\r\n//\"_xvk_material\" \"\" // remove broken texture\r\n//}\r\n\r\n{ // fix 2 side texture coordinates\r\n\"_xvk_surface_id\" \"7011 7010\" // {gratestep2\r\n\"_xvk_tex_offset\" \"-16 0\"\r\n}\r\n\r\n// section 2\r\n\r\n{\r\n\"_xvk_ent_id\" \"437 436 434 435\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"432 431 433 430\"\r\n//\"_light\" \"160 170 220 250\" // 160 170 220 250\r\n}\r\n\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"106 105\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"99\" // remove hack lights entity\r\n}\r\n\r\n\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"380 381\" // remove hack lights entity\r\n}\r\n\r\n// section 5\r\n{\r\n\"_xvk_ent_id\" \"229\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"246\" // remove hack lights entity\r\n}\r\n\r\n\r\n// section 6\r\n{\r\n\"_xvk_ent_id\" \"471\" // gman exit light\r\n\"_light\" \"255 0 0 120\" // 255 0 0 35\r\n\"origin\" \"833 -1279 481\"\r\n}\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a2c.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"106 108 105 107\" // remove hack lights entity\r\n}\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"289\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"104\" // remove hack lights entity\r\n}\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"124 123 133\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1035 1022\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 15000\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"131 122 130 120\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1036 1140\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 15000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"125 126\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"125 126\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"132 129\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"1719\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 15000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"128\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"127 121\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1576\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 15000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"103\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2073\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 15000\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2055\" // +0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\"_light\" \"160 170 220 10000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"138\" // remove hack lights entity\r\n}\r\n\r\n// section 3\r\n\r\n{\r\n\"_xvk_ent_id\" \"181\" // remove hack lights entity\r\n}\r\n\r\n\r\n{\r\n\"_xvk_surface_id\" \"656\" // +0~GENERIC86B    60  220 170  20000\r\n\"_light\" \"60 220 170 2500\"\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a2d.patch",
    "content": "// section 0\r\n{\r\n\"_xvk_ent_id\" \"70\" // remove hack lights entity\r\n}\r\n\r\n// section 1\r\n{\r\n\"_xvk_ent_id\" \"51 52\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"45\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"297\" // +0~FIFTIES_LGT2  160 170 220  5000\r\n\"_light\" \"160 170 220 10000\" // focus\r\n}\r\n{\r\n\"_xvk_ent_id\" \"32 31 11 12\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"949\" // +0~FIFTS_LGHT01  160 170 220  4000\r\n\"_light\" \"160 170 220 8000\" // focus\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3.patch",
    "content": "// section 1\r\n{\r\n\"_xvk_ent_id\" \"409 491\" // remove hack lights entity\r\n}\r\n//{\r\n//\"_xvk_surface_id\" \"3875 3874\" // BLACK\r\n//\"_xvk_material\" \"c1a3yellow\"\r\n//}\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"11\"\r\n\"origin\" \"186 832 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"8\"\r\n\"origin\" \"186 640 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"257\"\r\n\"origin\" \"-304 200 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"258\"\r\n\"origin\" \"-304 296 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"259\"\r\n\"origin\" \"-304 488 240\"\r\n}\r\n\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"254\"\r\n\"origin\" \"-536 200 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"261\"\r\n\"origin\" \"-536 392 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"256\"\r\n\"origin\" \"-776 200 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"255\"\r\n\"origin\" \"-776 392 240\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"262\"\r\n\"origin\" \"-1016 296 240\"\r\n}\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"98\"\r\n\"origin\" \"-1216 557 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"99\"\r\n\"origin\" \"-1408 557 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n// soda machine\r\n// FIXME\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-1400 -480 63\"\r\n\"_light\" \"255 100 100 100\" // GENERIC105       255 100 100  1000\r\n\"targetname\" \"busted_soda_spawner_1\"\r\n//\"style\" \"33\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"-1400 -400 63\"\r\n\"_light\" \"50 180 50 100\" // GEN_VEND1        50  180 50   1000\r\n\"targetname\" \"busted_soda_spawner_2\"\r\n//\"style\" \"33\"\r\n}\r\n\r\n\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"408\"\r\n\"origin\" \"-840 -376 112\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"260\"\r\n\"origin\" \"-560 -152 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"247\"\r\n\"origin\" \"-368 -152 112\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"253\"\r\n\"origin\" \"-560 -376 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"248\"\r\n\"origin\" \"-368 -376 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"249\"\r\n\"origin\" \"-560 -624 224\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"250\"\r\n\"origin\" \"-368 -624 112\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"251\"\r\n\"origin\" \"-560 -856 224\"\r\n}\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"167\"\r\n\"origin\" \"-88 -924 -56\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"171\"\r\n\"origin\" \"104 -924 -56\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"39\"\r\n\"_xvk_map_material\" \"generic028 generic_metal1\" // FIXME\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3a.patch",
    "content": "// section 1\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"388\"\r\n\"origin\" \"904 -1288 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"387\"\r\n\"origin\" \"760 -1288 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"385\"\r\n\"origin\" \"904 -1576 112\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"386\"\r\n\"origin\" \"760 -1576 112\"\r\n}\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"368\"\r\n\"origin\" \"568 -1803 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"365\"\r\n\"origin\" \"376 -1804 72\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"469 468\" // remove hack lights entity\r\n}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_surface_id\" \"657 656\" // +0~LIGHT6A       150 5   5    25000\r\n\"_light\" \"150 5 5 50000\" // for barnacle\r\n}\r\n{\r\n\"_xvk_surface_id\" \"794 1092\" // +0~LIGHT3A       180 180 230  10000\r\n\"_light\" \"180 180 230 2000\" // for barnacle\r\n}\r\n\r\n\r\n// section 3\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"323\"\r\n\"origin\" \"-1669 -3096 -56\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"320\"\r\n\"origin\" \"-1669 -3288 -56\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n// section 4\r\n{\r\n\"_xvk_ent_id\" \"482\" // remove hack lights entity\r\n}\r\n\r\n// section 5\r\n{\r\n\"_xvk_ent_id\" \"477 476\" // remove hack lights entity\r\n}\r\n\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"398\"\r\n\"origin\" \"-2464 -1680 112\"\r\n}\r\n\r\n// section 6\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"449\"\r\n\"origin\" \"-536 -768 916\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"452\"\r\n\"origin\" \"-536 -568 916\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"453\"\r\n\"origin\" \"-376 -568 916\"\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3b-dayone.patch",
    "content": "// section 1\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"109\"\r\n\"origin\" \"136 -848 916\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"111\"\r\n\"origin\" \"136 -648 916\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"112\"\r\n\"origin\" \"296 -648 916\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"5\" // sun\r\n\"_light\" \"179 158 211 80\" // original // need tune for hdr-autoexposure\r\n\"_light\" \"210 200 120 1750\" // demo lights // 210 200 120 175 // need tune for hdr-autoexposure\r\n\"pitch\" \"-45\"\r\n\"angle\" \"15\" // from demo c1a3c (better sun angle)\r\n}\r\n\r\n\r\n\r\n// section 1\r\n// for demo map\r\n{\r\n\"_xvk_surface_id\" \"1684\" // {ladder3a\r\n}\r\n\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3b.patch",
    "content": "// section 1\r\n// remove hack spot lights\r\n{\r\n\"_xvk_ent_id\" \"146\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"622\" // +0~LIGHT3A       180 180 230  25000\r\n\"_light\" \"180 180 230 105000\"\r\n}\r\n\r\n// correct sunlight (shadows angle from the skybox)\r\n{\r\n\"_xvk_ent_id\" \"5\"\r\n\"_light\" \"120 64 105 80\"\r\n\"angle\" \"-70\"\r\n\"pitch\" \"-45\" // copy because the angle resets the pitch\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3c-dayone.patch",
    "content": "// section 1\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"123 122\" // remove hack lights entity\r\n}\r\n\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"20\" // sun\r\n\"_light\" \"210 200 120 1750\" // original: 210 200 120 175 // need tune for hdr-autoexposure\r\n\"pitch\" \"-45\"\r\n\"angle\" \"15\" // restore missing angle from demo (better sun angle)\r\n}\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a3d.patch",
    "content": "// section 1\r\n// spot lights\r\n{\r\n\"_xvk_ent_id\" \"143\"\r\n\"origin\" \"800 -280 881\"\r\n}\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"43\"\r\n\"origin\" \"960 -212 713\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"44\"\r\n\"origin\" \"1152 -212 713\"\r\n\"_light\" \"233 186 0 80\"\r\n}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"198\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"1744 1647\" // {ladder1\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a4d.patch",
    "content": "\r\n// remove hack spot lights\r\n{\r\n\"_xvk_ent_id\" \"43 44 45\"\r\n}\r\n// remove hack light\r\n{\r\n\"_xvk_ent_id\" \"148\"\r\n}\r\n\r\n// ???\r\n//{\r\n//\"_xvk_surface_id\" \"378 320 381\" // ~LIGHT3C         220 210 150  14000\r\n//\"_light\" \"220 210 150  15000\"\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"414 411 416 415 413\" // +0~drkmtls1 ???\r\n//\"_light\" \"255 0 0  20000\" // RED              255 0   0    1000\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"330 331 332 328 329\" // +0~drkmtls1 ???\r\n//\"_light\" \"255 0 0  20000\" // RED              255 0   0    1000\r\n//}\r\n//{\r\n//\"_xvk_surface_id\" \"398 392 399 394 397\" // +0~drkmtls1 ???\r\n//\"_light\" \"255 0 0  20000\" // RED              255 0   0    1000\r\n//}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a4e.patch",
    "content": "// remove hack spot lights\n{\n\"_xvk_ent_id\" \"16 14 15 12 13\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n}\n{\n\"_xvk_ent_id\" \"22 20 21\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n\n}\n{\n\"_xvk_ent_id\" \"19\"\n\"origin\" \"1864 336 -916\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n}\n\n// correct spotlight position\n{\n\"_xvk_ent_id\" \"17\"\n\"origin\" \"1588 -14 -1756\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n}\n{\n\"_xvk_ent_id\" \"18\"\n\"origin\" \"1420 -10 -1756\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a4f.patch",
    "content": "{\n\"_xvk_ent_id\" \"198\" // remove hack lights entity\n}\n\n// FIXME: remove temp hacks (wait support light from invisible brush)\n{\n\"classname\" \"light\"\n\"origin\" \"-30 500 -897\"\n\"_light\" \"0 255 0 100\"\n\"_xvk_radius\" \"3\"\n}\n{\n\"classname\" \"light\"\n\"origin\" \"-30 500 -905\"\n\"_light\" \"0 255 0 100\"\n\"_xvk_radius\" \"5\"\n}\n{\n\"classname\" \"light\"\n\"origin\" \"-30 505 -925\"\n\"_light\" \"0 255 0 100\"\n}\n{\n\"classname\" \"light\"\n\"origin\" \"50 500 -890\"\n\"_light\" \"0 255 0 100\"\n\"_xvk_radius\" \"10\"\n}\n{\n\"classname\" \"light\"\n\"origin\" \"120 500 -890\"\n\"_light\" \"0 255 0 50\"\n\"_xvk_radius\" \"10\"\n}\n\n\n{\n\"_xvk_ent_id\" \"169 173 166 167 172 168\" // remove hack lights entity\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a4g.patch",
    "content": "{ // FIXME: temp hack (wait support light from invisible brush)\n\"_xvk_ent_id\" \"180\"\n\"_xvk_radius\" \"10\"\n\"_cone\" \"16\"\n\"_cone2\" \"80\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c1a4k.patch",
    "content": "\r\n// remove hack spot lights\r\n{\r\n\"_xvk_ent_id\" \"40 43 38 39 42 37\"\r\n}\r\n// remove hack lights\r\n{\r\n\"_xvk_ent_id\" \"27 31 16 17 32\"\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a1.patch",
    "content": "{ // Fix origin for entity-lamp (func_wall)\n\"_xvk_ent_id\" \"127\"\n\"origin\" \"0 0 -5\"\n}\n\n{ // barrels: fix normal smooth\n\"_xvk_ent_id\" \"426 425 428 427\"\n\"_xvk_smooth_entire_model\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a1b.patch",
    "content": "{ // barrels: fix normal smooth\n\"_xvk_ent_id\" \"179 174 171 181 175 170 173 172 180 178 177 176\"\n\"_xvk_smooth_entire_model\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a2h.patch",
    "content": "//{ // FIXME: remove this hack after better solution\n//\"_xvk_surface_id\" \"3884 3885 3886\" // rocket bottom\n//\"_light\" \"255 255 255 100500\"\n//}\n//{ // remove hack lights\n//\"_xvk_ent_id\" \"164 165\" // rocket bottom\n//}\n//{ // test\n//\"_xvk_surface_id\" \"2825 2826\"\n//\"_light\" \"255 255 255 100500\"\n//}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a3.patch",
    "content": "{ // barrels: fix normal smooth\n\"_xvk_ent_id\" \"185 184 183 74 22 25 21\"\n\"_xvk_smooth_entire_model\" \"1\"\n}\n\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1259 -388 730\"\n//\"origin\" \"-1227 -424 900\"\n\"_light\" \"120 220 150 20\"\n\"_xvk_radius\" \"50\"\n}\n\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1133 159 920\"\n//\"origin\" \"-1227 -424 900\"\n\"_light\" \"120 220 150 10\"\n\"_xvk_radius\" \"50\"\n}\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1623 -470 1004\"\n\"_light\" \"255 175 55 20\"\n\"_xvk_radius\" \"20\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1622 -470 899\"\n\"_light\" \"120 220 150 125\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-2076 -338 682\"\n\"_light\" \"120 220 150 125\"\n}\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-871 -388 968\"\n\"_light\" \"120 220 150 225\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-502 -461 768\"\n\"_light\" \"120 220 150 150\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-357 -101 608\"\n\"_light\" \"120 220 150 20\"\n\"_xvk_radius\" \"10\"\n}\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-587 -924 1375\"\n\"_light\" \"120 220 150 20\"\n\"_xvk_radius\" \"10\"\n}\n\n\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-349 205 959\"\n\"_light\" \"120 220 150 20\"\n\"_xvk_radius\" \"10\"\n}\n\n\n\n\n\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a3a.patch",
    "content": "{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"1535 808 687\"\n\"_light\" \"120 220 150 35\"\n\"_xvk_radius\" \"40\"\n}\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"1249 1234 578\"\n\"_light\" \"120 220 150 30\"\n\"_xvk_radius\" \"20\"\n}\n\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"1098 1346 568\"\n\"_light\" \"120 220 150 5\"\n\"_xvk_radius\" \"40\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"1178 1945 796\"\n\"_light\" \"255 175 55 7\"\n\"_xvk_radius\" \"40\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"1267 1625 928\"\n\"_light\" \"255 175 55 7\"\n\"_xvk_radius\" \"40\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a3b.patch",
    "content": "\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"3018 1836 669\"\n\"_light\" \"120 220 150 20\"\n\"_xvk_radius\" \"40\"\n}\n\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"2561 2059 -3399\"\n\"_light\" \"120 220 150 15\"\n\"_xvk_radius\" \"70\"\n}\n\n\n{\n\"_xvk_ent_id\" \"66\"\n//\"origin\" \"3024 1694 1011\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"255 230 125 200\n\"_light\" \"255 230 125 70\"\n}\n{\n\"_xvk_ent_id\" \"99\"\n//\"origin\" \"-386 2332 1175\"\n//\"_light\" \"255 230 125 200\n\"_light\" \"255 230 125 70\"\n\"_xvk_radius\" \"10\"\n//\"_cone\" \"16\"\n//\"_cone2\" \"24\"\n}\n{\n\"_xvk_ent_id\" \"107\"\n//\"_light\" \"255 230 125 200\"\n\"_light\" \"255 230 125 70\"\n\"_xvk_radius\" \"10\"\n}\n{\n\"_xvk_ent_id\" \"417\"\n//\"_light\" \"255 230 125 200\"\n\"_light\" \"255 230 125 70\"\n\"_xvk_radius\" \"10\"\n}\n{\n\"_xvk_ent_id\" \"208\"\n//\"origin\" \"-496 2514 1174\"\n//\"_light\" \"255 230 125 200\"\n\"_light\" \"255 230 125 70\"\n\"_xvk_radius\" \"10\"\n}\n{\n\"_xvk_ent_id\" \"209\"\n//\"_light\" \"255 230 125 200\"\n\"_light\" \"255 230 125 70\"\n\"_xvk_radius\" \"10\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a3d.patch",
    "content": "{ // remove hack light\n\"_xvk_ent_id\" \"441\"\n}\n{ // correct light\n\"_xvk_surface_id\" \"4327\" // +0~LIGHT2A       150 150 230  10000\n\"_light\" \"255 255 255 20000\" // FIXME: correct after fix bug\n}\n\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a3e.patch",
    "content": "{ // cutscene\r\n\"_xvk_ent_id\" \"44\"\r\n\"_xvk_smooth_entire_model\" \"1\"\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2090 2091 1857 1700\" // lamp\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"2064 2070 2069 2027 2065 2066 2014 2016 2018 2020 2022 1986 1988 1664 1666 1668 1691 1692 1690 1688 1693 1675 1674 1689 1654 1652 1650 1848 1850 1847 2031 2040 2030 2034 2033 2032 2026 2209 2024 2029 2028 2027 2036\" // walls\r\n}\r\n{\r\n\"_xvk_smoothing_excluded\" \"1628 1630 1631 1632 1831 1832 2113 1824 2100 2110 2111 2112 2096 2097 2108 2095 2099 2502 2098\" // ceil\r\n}\r\n\r\n\r\n{ // FIXME: temp hack\r\n\"_xvk_ent_id\" \"2\"\r\n\"_light\" \"200 180 100 1500\"\r\n}\r\n\r\n{ // correct hack light\r\n\"_xvk_ent_id\" \"64\"\r\n\"origin\" \"-2834 120 444\"\r\n}\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a4a.patch",
    "content": "{ // correct hack light\n\"_xvk_ent_id\" \"62\"\n\"origin\" \"-120 -696 254\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"170 90 40 700\"\n\"_light\" \"170 90 40 250\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a4e.patch",
    "content": "{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1725 2045 495\"\n\"_light\" \"0 60 255 10\"\n\"_xvk_radius\" \"70\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1344 2045 495\"\n\"_light\" \"0 60 255 10\"\n\"_xvk_radius\" \"70\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-1525 2087 495\"\n\"_light\" \"220 240 200 15\"\n\"_xvk_radius\" \"70\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a4f.patch",
    "content": "{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-193 469 496\"\n\"_light\" \"0 60 255 10\"\n\"_xvk_radius\" \"70\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"193 469 496\"\n\"_light\" \"0 60 255 10\"\n\"_xvk_radius\" \"70\"\n}\n{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"18 469 496\"\n\"_light\" \"220 240 200 15\"\n\"_xvk_radius\" \"70\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a4g.patch",
    "content": "// TODO: better ligthing\n{ \n\"_xvk_ent_id\" \"40 41 42 46\" // remove hack spotlights\n//\"_xvk_radius\" \"5\"\n//\"_light\" \"180 180 210\n//\"_light\" \"180 180 210 100\"\n//\"_cone\" \"16\"\n//\"_cone2\" \"100\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a5.patch",
    "content": "// TODO: better ligthing\n{ \n\"_xvk_ent_id\" \"6 7 8 12\" // remove hack spotlights\n//\"_xvk_radius\" \"5\"\n//\"_light\" \"180 180 210\n//\"_light\" \"180 180 210 100\"\n//\"_cone\" \"16\"\n//\"_cone2\" \"100\"\n}\n\n\n\n{\n\"_xvk_ent_id\" \"246\" // DANGER sign\n\"origin\" \"1 0 0\"\n}\n{\n\"_xvk_smoothing_threshold\" \"54\" // FIXME\n\"_xvk_remove_all_sky_surfaces\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a5c.patch",
    "content": "{\n\"_xvk_ent_id\" \"320\" // ORDINANCE STORAGE FACILITY sign\n\"origin\" \"1 0 0\"\n}\n{\n\"_xvk_ent_id\" \"321\" // HIGH SECURITY STORAGE FACILITY sign\n\"origin\" \"1 0 0\"\n}\n{\n\"_xvk_ent_id\" \"322\" // ORDINANCE STORAGE FACILITY sign\n\"origin\" \"-1 0 0\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a5e.patch",
    "content": "{\n\"_xvk_ent_id\" \"14\" // correct spotlight car\n\"origin\" \"-1924 -325 -2176\"\n}\n{\n\"_xvk_ent_id\" \"15\" // correct spotlight car\n\"origin\" \"-1924 -380 -2175\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c2a5f.patch",
    "content": "{ // barrels: fix normal smooth\n\"_xvk_ent_id\" \"628 194\"\n\"_xvk_smooth_entire_model\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c3a1.patch",
    "content": "{\n\"_xvk_ent_id\" \"18\" // correct spotlight\n\"origin\" \"614 -63 744\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"220 220 220\"\n\"_light\" \"220 220 220 100\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c3a1a.patch",
    "content": "{ // remove hack spotlight\n\"_xvk_ent_id\" \"65\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c3a2.patch",
    "content": "// hack for better UX (elevator shaft)\n{\n\"classname\" \"light\" // TODO: better solution\n\"origin\" \"1949 289 199\"\n\"_light\" \"200 190 130 10\"\n\"_xvk_radius\" \"40\"\n}\n\n{ // barrels: fix normal smooth\n\"_xvk_ent_id\" \"122 123 114 115 116 112 111\"\n\"_xvk_smooth_entire_model\" \"1\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c3a2d.patch",
    "content": "// TODO: better horror lighing\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c3a2e.patch",
    "content": "{\n\"_xvk_ent_id\" \"352\" // correct spotlight\n\"origin\" \"1944 2072 2343\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"255 255 255 260\"\n\"_light\" \"255 255 255 92\"\n}\n{\n\"_xvk_ent_id\" \"353\" // correct spotlight\n\"origin\" \"1768 2072 2343\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"255 255 255 260\"\n\"_light\" \"255 255 255 92\"\n}\n\n{ // correct light\n\"_xvk_surface_id\" \"57 59\" // +0~LIGHT3A       255 255 255  25000\n\"_light\" \"255 255 255 200\" // for better visual a spotlight\n}\n\n{ // better platform light\n\"_xvk_surface_id\" \"2971 2973\" // +0~LIGHT6A       150 5   5    25000\n\"_light\" \"150 5 5 225000\" // FIXME: correct after fix entity light bug\n}\n\n// hack for better UX (elevator shaft)\n{\n\"classname\" \"light\" // TODO: better solution\n\"origin\" \"1960 288 449\"\n\"_light\" \"200 190 130 10\"\n\"_xvk_radius\" \"40\"\n}\n//{\n//\"classname\" \"light\" // TODO: better solution\n//\"origin\" \"1950 288 172\"\n//\"_light\" \"200 190 130 10\"\n//\"_xvk_radius\" \"40\"\n//}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c4a1a.patch",
    "content": "{\n\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n\"origin\" \"-306 187 -198\"\n\"_light\" \"20  160 120 30\"\n\"_xvk_radius\" \"20\"\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/c5a1.patch",
    "content": "{\n\"_xvk_ent_id\" \"28\" // correct gman light\n//\"origin\" \"-3376 3412 -3444\"\n\"_xvk_radius\" \"10\"\n//\"_light\" \"0 255 0 200\"\n\"_light\" \"0 255 0 40\"\n}\n//{\n//\"_xvk_ent_id\" \"29\" // correct gman light\n//\"origin\" \"-3545 3477 -3464\"\n//\"_xvk_radius\" \"2\"\n////\"_light\" \"0 255 0 200\"\n//\"_light\" \"50 255 255 400\"\n//}\n//{\n//\"classname\" \"light\" // TODO: remove after add light from invisible brush support\n//\"origin\" \"-3649 3486 -3464\"\n//\"_light\" \"50 255 255 1000\"\n//\"_xvk_radius\" \"2\"\n//}\n{\n\"_xvk_ent_id\" \"29\" // correct gman light\n\"origin\" \"-3760 3542 -3464\"\n\"_xvk_radius\" \"40\"\n//\"_light\" \"0 255 0 200\"\n\"_light\" \"50 255 255 200\"\n}\n\n\n\n\n//{\n//\"_xvk_ent_id\" \"44\" // correct gman light\n//\"origin\" \"-1743 -2592 -2733\"\n//\"_xvk_radius\" \"10\"\n////\"_light\" \"128 255 0 50\"\n////\"_light\" \"128 255 0 20\"\n//}\n\n{\n\"_xvk_ent_id\" \"44\" // correct gman light\n//\"origin\" \"-1743 -2592 -2690\"\n\"origin\" \"-3115 -1862 -2661\"\n\"_xvk_radius\" \"80\"\n\"_light\" \"25 255 255 150\"\n}\n\n{\n\"_xvk_ent_id\" \"262 260\" // remove hack light entity\n}\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/hldemo1.patch",
    "content": "// section 1\r\n\r\n// alarm lights\r\n{\r\n\"_xvk_ent_id\" \"100\"\r\n\"origin\" \"-2117 688 86\"\r\n\"_light\" \"255 255 0 100\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"94\"\r\n\"origin\" \"-2116 144 86\"\r\n\"_light\" \"255 255 0 100\"\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"103\"\r\n\"_light\" \"215 231 238 100\"\r\n}\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"42\"\r\n\"origin\" \"-1840 -96 281\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"43\"\r\n\"origin\" \"-1840 -224 281\"\r\n}\r\n\r\n{\r\n//\"_xvk_ent_id\" \"36 35 34 33 37 38\" // remove hack lights entity\r\n}\r\n{\r\n//\"_xvk_ent_id\" \"26 25 28 27 32 31 30 24 23 29\" // remove hack lights entity\r\n}\r\n{\r\n//\"_xvk_ent_id\" \"403 404 405 406 21 22 20 19\" // remove hack lights entity\r\n}\r\n\r\n{\r\n//\"_xvk_surface_id\" \"2104 2101 2099\" // SCRN3\r\n//\"_light\" \"24 30 252 2000\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"403\"\r\n\"origin\" \"-2168 -866 302\"\r\n\"_light\" \"24 30 252 100\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"404\"\r\n\"origin\" \"-2216 -866 302\"\r\n\"_light\" \"24 30 252 100\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"405\"\r\n\"origin\" \"-2262 -866 302\"\r\n\"_light\" \"255 255 255 40\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"406\"\r\n\"origin\" \"-2309 -866 302\"\r\n\"_light\" \"255 255 255 40\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"405\"\r\n\"origin\" \"-2262 -866 302\"\r\n\"_light\" \"255 255 255 40\"\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"21\"\r\n\"origin\" \"-2384 -712 301\"\r\n\"_light\" \"24 30 252 100\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"22\"\r\n\"origin\" \"-2384 -664 301\"\r\n\"_light\" \"255 255 255 60\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"20\"\r\n\"origin\" \"-2384 -616 301\"\r\n\"_light\" \"24 30 252 100\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"19\"\r\n\"origin\" \"-2384 -568 301\"\r\n\"_light\" \"24 30 252 100\"\r\n}\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/hldemo2.patch",
    "content": "// section 1\r\n{\r\n\"_xvk_ent_id\" \"150 151 3 4 1 2\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_ent_id\" \"629 628\" // remove hack lights entity\r\n}\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"5 6\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"7 8\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_surface_id\" \"3518 3531\" // +0~LIGHT6A       150 5   5    25000\r\n\"_light\" \"150 5 5 85000\"\r\n}\r\n\r\n\r\n// section 2\r\n{\r\n\"_xvk_ent_id\" \"77 78 76 71 72 73 74 75\" // remove hack lights entity\r\n}\r\n{\r\n\"_xvk_ent_id\" \"597 598\" // remove hack lights entity\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"2595 2596 2597 2089 2085 2080 2062\" // +0~FIFTS_LGHT01  160 170 220  3000\r\n\"_light\" \"160 170 220 5000\"\r\n}\r\n\r\n// section 3\r\n{\r\n\"_xvk_ent_id\" \"21\" // sun\r\n\"origin\" \"1680 1241 301\"\r\n\"_light\" \"78 73 167 20\"\r\n//\"pitch\" \"-71\"\r\n//\"angle\" \"270\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"79\" // sun\r\n\"origin\" \"2353 278 109\"\r\n\"_light\" \"78 73 167 10\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"39\" // sun\r\n\"origin\" \"3123 -288 102\"\r\n\"_light\" \"78 73 167 10\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"30\" // sun\r\n\"origin\" \"3056 2141 -76\"\r\n\"_light\" \"78 73 167 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"30\" // sun\r\n\"origin\" \"3056 2141 -76\"\r\n\"_light\" \"78 73 167 0\"\r\n}\r\n{\r\n\"_xvk_ent_id\" \"43\" // sun\r\n\"origin\" \"2032 2640 37\"\r\n\"_light\" \"78 73 167 20\"\r\n}\r\n\r\n\r\n\r\n{\r\n//\"classname\" \"light\"\r\n//\"origin\" \"2011 2279 278\"\r\n//\"_light\" \"78 73 167 800\"\r\n}\r\n{\r\n\"classname\" \"light\"\r\n\"origin\" \"2981 1709 252\"\r\n\"_light\" \"78 73 167 300\"\r\n}\r\n{\r\n//\"classname\" \"light\"\r\n//\"origin\" \"3079 826 -1\"\r\n//\"_light\" \"78 73 167 300\"\r\n}\r\n\r\n\r\n\r\n\r\n{\r\n\"_xvk_ent_id\" \"70\"\r\n//\"origin\" \"2202 840 88\"\r\n}\r\n{\r\n\"_xvk_surface_id\" \"2258\" // +0~LIGHT3A       180 180 230  10000\r\n\"_light\" \"180 180 230 100000\"\r\n}\r\n\r\n{\r\n\"_xvk_surface_id\" \"6142 6136 6141 6137 6134 6138 6135 6143 6139 6140\"\r\n\"_light\" \"0 0 0 0\"\r\n}"
  },
  {
    "path": "ref/vk/data/valve/luchiki/maps/t0a0.patch",
    "content": "{\n\"_xvk_ent_id\" \"14 15\" // correct spotlights\n\"_xvk_radius\" \"6\"\n\"_light\" \"245 248 146 75\"\n//\"_light\" \"245 248 146 30\"\n}\n//{\n//\"_xvk_ent_id\" \"259\" // correct spotlights\n//\"origin\" \"-1380 -1560 7\"\n//\"_xvk_radius\" \"4\"\n//\"_light\" \"0 255 255 50\"\n////\"_light\" \"0 255 255 20\"\n//}\n"
  },
  {
    "path": "ref/vk/data/valve/maps/README.txt",
    "content": "Note (\"codestyle\"):\r\n* Use a monospaced font for editing.\r\n* Don't use tabs for align, only spaces (tabs only for indents, indents not needed in rad files).\r\n* Don't use leading zero to avoid misunderstandings.\r\n* Use UPPERCASE for texture name.\r\n* Use // for commenting.\r\n* Do not add +A~ textures to lights.rad (you have to add them to mapname.rad) unless you are sure it is not an off light bulb."
  },
  {
    "path": "ref/vk/data/valve/maps/boot_camp.rad",
    "content": "~LIGHT5F         255 220 100  5000\n+0~FIFTIES_LGT2  215 230 255  6000\n~LIGHT5A         5   5   255  10000\n+0~GENERIC85     130 170 200  30000\n~LIGHT3C         220 210 150  3000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0.rad",
    "content": "+0~LIGHT2A       255 255 90   2500\r\n+0~TNNL_LGT1     255 245 110  8000\r\nGENERIC87A       100 255 100  1000\r\nGENERIC88A       255 100 100  1000\r\nRED              255 0   0    2000\r\n//YELLOW           255 244 202  1000\r\n+0~FIFTIES_LGT2  185 195 255  3000\r\nSKKYLITE         255 110 40   700\r\n+0~FIFTS_LGHT01  230 234 255  3000\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\nEXIT1            255 0   0    1000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0a.rad",
    "content": "+0~LIGHT2A       255 255 90   2500\r\nRED              255 0   0    2000\r\n+0~FIFTIES_LGT2  185 195 255  3000\r\n//SKKYLITE         255 155 70   1000\r\nSKKYLITE         255 110 40   700\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\nEXIT1            255 0   0    1000"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0b.rad",
    "content": "RED              255 0   0    2000 // FIXME: perfomance, need remore light from some surfaces\r\n+0~FIFTIES_LGT2  185 195 255  3000\r\n~LIGHT3C         220 210 150  0 // 2500 // for perfomance\r\n+0~LIGHT6A       150 5   5    25000\r\n~SPOTBLUE        80  190 240  2000\r\n+0~TNNL_LGT4     170 90  40   10000\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\nEXIT1            255 0   0    1000"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0c.rad",
    "content": "//+0~TNNL_LGT4     250 150 100   2000\r\n//+0~TNNL_LGT4     170 90  40   4000\r\n+0~TNNL_LGT4     170 90  40   10000\r\n+0LAB1_W7        245 240 210  4000\r\n+0~TNNL_LGT3     150 150 210  17000\r\n+0~LIGHT6A       150 5   5    25000\r\n+0~LIGHT4A       200 190 130  11000\r\n+0LAB1_W6        150 160 210  8800\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\n~SPOTRED         255 25  30   0\r\n//+A~GENERIC86B\r\n//+0~GENERIC86B    60  220 170  0\r\n\r\nEXIT1            255 0   0    1000"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0d.rad",
    "content": "//!TOXICGRN        20 255 0    350\r\n//!TOXICGRN2       20 255 0    350\r\n!TOXICGRN        20 255 0    2500 // FIXME\r\n!TOXICGRN2       20 255 0    550\r\n//+0~TNNL_LGT4     170 90  40   10000\r\n//+0~TNNL_LGT4     250 150 100   2000\r\n+0~TNNL_LGT4     170 90  40   4000\r\n+0~GENERIC86     255 230 125  10000\r\nRED              255 0   0    2000\r\n+0~LIGHT2A       255 255 90   2500\r\nSKKYLITE         255 110 40   700\r\n+0~FIFTIES_LGT2  185 195 255  3000\r\n+0LAB1_W6        150 160 210  8800\r\n+0~GENERIC86B    60  220 170  20000\r\n~LIGHT3C         220 210 150  2500\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\n+0~LAB_CRT2      255 255 255  20\r\n\r\nEXIT1            255 0   0    1000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c0a0e.rad",
    "content": "+0~FIFTIES_LGT2  185 195 255  3000\r\n+0~LIGHT2A       255 255 90   2500\r\n+0LAB1_W6        150 160 210  8800\r\n+0~GENERIC86B    60  220 170  20000\r\n~LIGHT3C         220 210 150  2500\r\n~SPOTYELLOW      189 231 253  20000\r\nSUBWAY_LIGHTS    190 195 255  1500\r\n\r\n+0~LAB_CRT2      255 255 255  20\r\n+1~LAB_CRT2      255 255 255  20\r\n+2~LAB_CRT2      255 255 255  20\r\n+3~LAB_CRT2      255 255 255  20\r\n\r\nEXIT1            255 0   0    1000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0.rad",
    "content": "+0~GENERIC65     255 255 255  14000\r\n+0~GENERIC85     110 160 220  25500 // 11000 16000 22000\r\n+0~GENERIC86R    128 0   0    60000\r\n+0~LIGHT3A       255 255 255  25000\r\n+0~DRKMTLS1      255 10  10   14000\r\n+0~LIGHT4A       231 223 82   20000\r\n+0~LIGHT5A       80  150 200  10000\r\n+0~FIFTS_LGHT01  160 170 220  4000\r\n+0~FIFTS_LGHT06  255 255 255  8000\r\n+0~FIFTIES_LGT2  255 255 255  20000\r\n~LIGHT3B         84  118 198  14000\r\n~LIGHT3A         190 20  20   14000\r\n~LIGHT3C         198 215 74   14000\r\n~SPOTYELLOW      189 231 253  20000\r\n~SPOTBLUE        7   163 245  18000\r\n//LAB1_COMP3D      255 255 255  20 // TODO: emissive texture\r\n//~LAB1_COMP7      255 255 255  20 // TODO: emissive texture\r\n\r\nCRYS_2TOP        171 254 168  14000\r\n\r\nDRKMTL_SCRN3     1   111 220  200\r\n\r\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n\r\n+0~LAB_CRT8      50  50  255  200\r\n+1~LAB_CRT8      50  50  255  200 // hack, because no inheritance yet\r\n\r\n+0DRKMTL_SCRN    60  80  255  200\r\n+1DRKMTL_SCRN    60  80  255  200\r\n+2DRKMTL_SCRN    60  80  255  200\r\n\r\n//FIFTIES_MON1B    100 100 180  30\r\n\r\n~LAB_CRT9A       225 150 150  100\r\n~LAB_CRT9B       100 100 255  100\r\n~LAB_CRT9C       100 200 150  100\r\n\r\nLITEPANEL1       190 170 120  2500\r\n\r\n\r\n+0~FIFTS_LGHT4   160 170 220  4000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0a.rad",
    "content": "+A~FIFTIES_LGT2  160 170 220  5000\n//+0~FIFTIES_LGT2  160 170 220  5000\n+0~FIFTIES_LGT2  255 255 255  20000\n+0~GENERIC85     120 170 235  20000\n~TRN_LT1         160 170 220  1500\n+0~GENERIC86B    60  220 170  10000\n\n+0~LIGHT5A       80  150 200  10000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0b.rad",
    "content": "+0~GENERIC86B    60  220 170  10000\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\n~LIGHT3C         220 210 150  2500\n+A~FIFTIES_LGT2  160 170 220  4000\n+0~FIFTIES_LGT2  160 170 220  10000\nFLATBED_HLITE2   150 200 220  10000\n+0~DRKMTLS2      80  60  30   1000\n//C1A3YELLOW       255 200 100  100\n+ALAB1_W6        255 255 255  100\n+0~GENERIC86R    128 16  16   60000\n\n+0~LIGHT4A       200 190 130  11000\n\nRED 255 0 0 0 // for optimization"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0c.rad",
    "content": "+A~FIFTIES_LGT2  160 170 220  5000\r\n+ALAB1_W6B       215 180 95   0\r\n+0~DRKMTLS2      80  60  30   1000\r\n+ALAB1_W6        255 255 255  4000\r\n+0~FIFTIES_LGT2  160 170 220  10000\r\nFLATBED_HLITE2   150 200 220  10000\r\n+0~LIGHT1        70  100 150  5000\r\n+0~GENERIC86B    60  220 170  10000\r\n\r\n+0~GENERIC86R    128 0   0    60000\r\n\r\n+0~LIGHT4A       200 190 130  11000\r\n+0~LIGHT5A       80  150 200  10000\r\n+0~LIGHT6A       150 5   5    25000\r\n\r\n~LIGHT3F         220 210 175  0\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0d.rad",
    "content": "+0~LIGHT3A       255 255 255  25000\r\n~LIGHT3B         84  118 198  14000\r\n~LIGHT3A         190 20  20   14000\r\n~LIGHT3C         198 215 74   14000\r\n+0~LIGHT4A       231 223 82   20000\r\n+0~FIFTS_LGHT06  255 255 255  8000\r\n+0~FIFTIES_LGT2  255 255 255  20000\r\n~SPOTYELLOW      189 231 253  20000\r\n+0~DRKMTLS1      255 10  10   14000\r\nLITEPANEL1       190 170 120  2500\r\n+0BUTTONLITE     255 255 255  25\r\n+ABUTTONLITE     255 255 255  25\r\n+0~GENERIC65     255 255 255  750\r\n+0~FIFTS_LGHT3   160 170 220  4000\r\n~LIGHT5F         200 190 140  2500\r\n+A~FIFTIES_LGT2  160 170 220  4000\r\n~LIGHT3F         200 190 140  2500\r\n+0~GENERIC85     110 160 220  25500 // 11000 16000 22000\r\n~SPOTBLUE        7   163 245  18000\r\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//LAB1_COMP3D      255 255 255  20 // TODO: emissive texture\r\n//~LAB1_COMP7      255 255 255  20 // TODO: emissive texture\r\n+0~FIFTS_LGHT5   255 255 255  10000\r\n\r\nGENERIC105       255 100 100  0\r\nGENERIC105A      255 100 100  30\r\nGENERIC106       120 120 100  0\r\nGENERIC106A      120 120 100  30\r\n\r\n// hack\r\n//+0GENERIC_113    255 255 255  1000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a0e.rad",
    "content": "//RED              255 0   0    10000\r\nRED              255 0   0    0 // no need\r\n+0~GENERIC86B    60  220 170  10000\r\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n~LIGHT3C         220 210 150  2500\r\n+A~FIFTIES_LGT2  160 170 220  4000\r\n+0~FIFTIES_LGT2  160 170 220  10000\r\nFLATBED_HLITE2   150 200 220  10000\r\n+0~DRKMTLS2      80  60  30   1000\r\nC1A3YELLOW       255 200 100  100\r\n+ALAB1_W6        255 255 255  100\r\n+0~GENERIC86R    128 16  16   60000\r\nXENO_18          0   50  255  10000\r\n\r\nCRYS_3TOP        255 152 79   10000\r\n+0~LIGHT1        40  60  150  10000\r\n+0LAB1_W6        150 160 210  8800\r\n//LAB1_COMP3D      255 255 255  20 // TODO: emissive texture\r\n+0~DRKMTLS2      150 120 20   30000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1.rad",
    "content": "+0~FIFTS_LGHT3   160 170 220  0\r\n+A~FIFTIES_LGT2  160 170 220  5000\r\n+0~GENERIC86B    60  220 170  10000\r\n\r\n+0~LIGHT5A       80  150 200  10000\r\n+0~LIGHT6A       150 5   5    25000\r\n\r\n+ALAB1_W6        150 160 210  0\r\n\r\n+0~DRKMTLS1      205 0   0    6000\r\n+1~DRKMTLS1      205 0   0    0 // TODO: animate texture"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1a.rad",
    "content": "//+A~FIFTIES_LGT2  160 170 220  5000\r\n+A~FIFTIES_LGT2  160 170 220  4000\r\n+0~GENERIC86B    60  220 170  10000\r\n\r\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n\r\nLAB1_COMP3D      255 255 255  0\r\n\r\n// TODO: optimization\r\nGENERIC105       255 100 100  1000\r\nGENERIC105A      255 100 100  30\r\nGENERIC106       120 120 100  1000\r\nGENERIC106A      120 120 100  30\r\n\r\nLITEPANEL1       190 170 120  2500\r\n\r\n~LIGHT3F         200 190 140  2500\r\n\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1b.rad",
    "content": "+A~FIFTIES_LGT2  160 170 220  5000\r\n//+A~FIFTIES_LGT2  160 170 220  0\r\n+ALAB1_W6        150 160 210  0\r\n+0LAB1_W6B       150 160 210  4000\r\n\r\n// +0~FIFTS_LGHT01 in c1a1f\r\n+0~FIFTIES_LGT2  255 255 255  3000\r\n\r\n// LITEPANEL1 in c1a1f\r\n+0LAB1_W6          255 255 255  4000\r\n\r\n\r\n//+0~LIGHT4A       200 90 90  15000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1c.rad",
    "content": "+A~FIFTIES_LGT2  160 170 220  5000\r\n+ALAB1_W6        150 160 210  0\r\n+0LAB1_W6B       150 160 210  4000\r\n\r\n// FIXME: need emissive mask or emissive texture\r\n+0MEDKIT         255 192 128  100\r\nMEDKITEDGE1      255 192 128  100\r\n+0RECHARGE       255 192 0    30\r\nRECHARGEEDGE1    255 192 0    30\r\n\r\n+0~LIGHT3A       180 180 230  50000\r\n\r\n//~LIGHT3C         220 210 150   14000\r\n~LIGHT3C         220 210 150  4000\r\n\r\n// for optimization\r\n+0~DRKMTLS2C     255 200 100  0\r\n\r\n+0~LIGHT4A       200 190 130  11000\r\n\r\n~TRN_LT1         160 170 220  10000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1d.rad",
    "content": "//~LIGHT3C         220 210 150   14000\r\n~LIGHT3C         220 210 150  4000\r\n\r\nLITEPANEL1       190 170 120  5000\r\n//LITEPANEL1       190 170 120  2500\r\n\r\n+A~FIFTIES_LGT2  160 170 220  5000\r\n\r\nELEV2_CIEL       255 200 100  2000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a1f.rad",
    "content": "//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n\r\n//LAB1_COMP3D      255 255 255  20 // TODO: emissive texture\r\n\r\n+0DRKMTL_SCRN    60  80  255  200\r\n+1DRKMTL_SCRN    60  80  255  200\r\n+2DRKMTL_SCRN    60  80  255  200\r\n\r\nDRKMTL_SCRN3     1   111 220  200\r\n\r\n+0~GENERIC86     255 230 125  50000\r\n\r\n+1FLICKERMON     255 255 255  10000\r\n+3FLICKERMON     255 255 255  1000\r\n+4FLICKERMON     255 255 255  10000\r\n+6FLICKERMON     255 255 255  1000\r\n+8FLICKERMON     255 255 255  10000\r\n+9FLICKERMON     255 255 255  1000\r\n\r\n// +0~FIFTIES_LGT2 in c1a1b\r\n+0~FIFTS_LGHT01  255 255 255  3000\r\n\r\n// +0LAB1_W6 in c1a1b\r\nLITEPANEL1       255 255 255  4000\r\n\r\n~LIGHT3F         220 210 175  0\r\n\r\n~LIGHT3C         180 190 60  15000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a2.rad",
    "content": "+0~FIFTS_LGHT4   160 170 220  100 //hack\r\n+A~FIFTS_LGHT4   160 170 220  0   //hack"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a2a.rad",
    "content": "+0~LIGHT2A       255 250 130  2000 // 3000? TODO: compare by QRAD\r\n+0DRKMTL_SCRN    100 200 170  200\r\n+1DRKMTL_SCRN    100 200 170  200\r\n+2DRKMTL_SCRN    100 200 170  200\r\n\r\n// from hack light (0   255 0    35)\r\n// TODO: need emissive texture\r\n//+0EXIT           0   255 0    0\r\n//+AEXIT           0   255 0    200\r\n+AEXIT           255 255 255  30"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a2b.rad",
    "content": "// from hack light (0   255 0    35)\r\n// TODO: need emissive texture\r\n//+0EXIT           0   255 0    0\r\n//+AEXIT           0   255 0    200\r\n+AEXIT           255 255 255  30\r\n\r\n+0~TNNL_LGT2     190 255 255  12000\r\n\r\n+0~FIFTIES_LGT2  160 170 220  5000\r\n\r\n\r\n+0~FIFTS_LGHT06  255 255 255  1000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a2c.rad",
    "content": "//+0~FIFTIES_LGT2  255 255 255  20000\r\n+0~FIFTIES_LGT2  160 170 220  4000 // 5000?\r\n\r\n+0~FIFTS_LGHT01  160 170 220  4000\r\n\r\n\r\n+0~GENERIC86B    60  220 170  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a2d.rad",
    "content": "+0~FIFTIES_LGT2  160 170 220  5000\r\n//+0~FIFTIES_LGT2  255 255 255  20000\r\n\r\n+0~FIFTS_LGHT01  160 170 220  4000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a3.rad",
    "content": "~SPOTBLUE        7   163 245  0\r\n//+0~LIGHT3A       180 180 230  20000\r\n//+0~LIGHT3A       180 180 230  45000\r\n+0~LIGHT3A       180 180 180  35000 // 10000\r\n//~LIGHT3B         84  118 198  14000\r\n~LIGHT3B         84  118 198  20000\r\n\r\n+0~DRKMTLS2C     255 200 100  0\r\nC1A3YELLOW       255 255 128  2000\r\n\r\n// for optimization\r\nGENERIC105       255 100 100  0\r\nGENERIC105A      255 100 100  0\r\nGEN_VEND1        50  180 50   0\r\n\r\n//+0~FIFTIES_LGT2  255 255 255  20000\r\n+0~FIFTIES_LGT2  180 180 180  30000\r\n\r\n+0~GENERIC65     255 255 255  7500\r\n\r\nRED              255 0   0    1000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a3a.rad",
    "content": "+0~DRKMTLS2C     255 200 100  0\r\nC1A3YELLOW       255 255 128  2000\r\n\r\n//+0~FIFTIES_LGT2  255 255 255  20000\r\n//+0~FIFTIES_LGT2  180 180 160  30000\r\n+0~FIFTIES_LGT2  180 180 180  25000\r\n\r\n+0~LIGHT1        25  45  170  2000 // 10000\r\n//+0~LIGHT3A       180 180 230  18000 // 10000\r\n+0~LIGHT3A       180 180 180  28000 // 10000\r\n+0~LIGHT2A       255 250 130  0\r\n//+0~LIGHT4A       200 190 130  15000\r\n+0~LIGHT4A       200 190 60   20000\r\n\r\n//~LIGHT3C         180 210 180  10000\r\n//~LIGHT3C         200 190 60  20000\r\n~LIGHT3C         180 190 40  15000\r\n~LIGHT5B         210 245 255  0\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a3b.rad",
    "content": "+0~LIGHT3A       180 180 230  45000 // 10000\r\n+0~LIGHT4A       200 190 60   20000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a3c.rad",
    "content": "+0~LIGHT3A       180 180 180  35000 // 10000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a3d.rad",
    "content": "+0~DRKMTLS2C     255 200 100  0\r\nC1A3YELLOW       255 255 128  2000\r\n\r\n+0~LIGHT4A       200 190 60   20000"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4.rad",
    "content": "//+0~FIFTIES_LGT2  255 255 255    2000\r\n+0~FIFTIES_LGT2  160 160 220    4000\r\n~LIGHT3C         220 210 150    3000\r\n!RADIO           50  255 50      500\r\n+0~TNNL_LGT1     220 230 100   10000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4b.rad",
    "content": "!TOXICGRN2       0  255  0    5000\r\ncrete3_wall02    0  255  0    500\r\n//+0~FIFTIES_LGT2  255 255 255  2000\r\n+0~FIFTIES_LGT2  190 210 255  5000\r\n~LIGHT3E         90  190 140  5000\r\nSKKYLITE         255 110 40   1000\r\n+0~GENERIC85     110 140 235  6000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4d.rad",
    "content": "+0~FIFTIES_LGT2  255 255 255  5000\r\n~LIGHT3E         90  190 140  10000\r\n~LIGHT3C         220 210 150  20000\r\n+0~DRKMTLS1      205 0   0    12000\r\nSKKYLITE         255 110 40   700\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4e.rad",
    "content": "SKKYLITE         255 110 40   4000\n+0~FIFTIES_LGT2  255 255 255  5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4f.rad",
    "content": "+0~FIFTIES_LGT2  255 255 255  5000\r\n!TOXICGRN2       0  255 0    5000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4g.rad",
    "content": "!TOXICGRN2       20 255 0    350 // FIXME\r\n+0~FIFTIES_LGT2  255 255 255  4000\r\n+0~GENERIC86     255 230 125  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4i.rad",
    "content": "+0~FIFTIES_LGT2  190 210 255  5000\r\n!TOXICGRN        0   255 0    5000\r\nSKKYLITE         255 110 40   700\r\n+0~TNNL_LGT4     170 90  40   6000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4j.rad",
    "content": "//+0~FIFTS_LGHT3   255 190 40   5000\n+0~FIFTS_LGHT3   255 175 55   5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c1a4k.rad",
    "content": "!TOXICGRN2       0  255 0    1000\r\ncrete3_wall02    0  255 0    500"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a1.rad",
    "content": "//+0~FIFTIES_LGT2  255 255 255  2000\r\n+0~FIFTIES_LGT2  185 195 255  5000\r\n+A~FIFTIES_LGT2  255 255 125  3000\r\n//+0~FIFTS_LGHT3   255 155 25   2500\r\n//+0~FIFTS_LGHT3   255 175 55   5000\r\n+0~FIFTS_LGHT3   255 175 55   5000\r\n\r\n+0~FIFTS_LGHT5   255 255 255  0\r\n//+0~TNNL_LGT1     255 200 200  5000\r\n+0~TNNL_LGT1     235 200 200  10000\r\n\r\n+0~GENERIC86R    128 0   0    100000\r\n~TRN_LT1B        255 225 100   10000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a1a.rad",
    "content": "//+A~FIFTS_LGHT3   255 255 255   5000\r\n+A~FIFTS_LGHT3   255 255 255   7000 // FIXME: temporary\r\n\r\n+0~LIGHT1        40  60  150   3000\r\n+ALAB1_W6        150 160 210   1000\r\n\r\n//+0~FIFTIES_LGT2  175 175 255   2500\r\n+0~FIFTIES_LGT2  175 190 255   5000\r\nRED              255 0   0     0\r\n\r\n+0~LIGHT3A       180 180 250  15000 // FIXME: temporary\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a1b.rad",
    "content": "//!TOXICGRN        10  255 45   300\r\n!TOXICGRN        10  255 45   3000 // FIXME: temporary\r\n//+0~FIFTS_LGHT3   255 155 25   2500\r\n+0~FIFTS_LGHT3   255 175 55   5000\r\n+A~FIFTIES_LGT2  255 255 125  3000\r\n+0~FIFTS_LGHT5   255 255 255  0\r\n+0~GENERIC86R    128 0   0    100000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2.rad",
    "content": "+0~FIFTIES_LGT2  175 175 255  2500\r\n+A~FIFTIES_LGT2  205 225 255  8000\r\n//~LIGHT3C         220 210 150  4000\r\n//~LIGHT3C         200 190 60   7000\r\n~LIGHT3C         220 210 150  7000\r\n\r\n+0~TNNL_LGT2     255 150 25   18000\r\n\r\n+0~FIFTS_LGHT5   160 220 250  13000\r\n\r\n//~LIGHT3B         84  118 198  14000\r\n~LIGHT3B         170 170 255  14000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2a.rad",
    "content": "//~LIGHT3C         220 210 150  4000\r\n//~LIGHT3C           200 190 60   7000\r\n~LIGHT3C           220 210 150  7000\r\n~LIGHT3F           255 190 60   7000\r\n+0~FIFTS_LGHT5     150 230 255  10000\r\n//~LIGHT3B           243 243 255  4000\r\n//~LIGHT3B           170 170 255  14000\r\n~LIGHT3B           180 170 255  14000\r\n+0~TNNL_LGT2       255 150 25   20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2b1.rad",
    "content": "//~LIGHT3E         90  190 140  10000\r\n~LIGHT3E           40  160 150  8000\r\n//~LIGHT3C           200 190 60   7000\r\n~LIGHT3C           220 210 150  7000\r\n+0~FIFTS_LGHT5     150 230 255  10000\r\n//~LIGHT3B           243 243 255  4000\r\n~LIGHT3B           180 170 255  14000\r\n~LIGHT3F           255 190 60   7000\r\n+0~TNNL_LGT2       255 150 25   22000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2b2.rad",
    "content": "~LIGHT3F         255 190 60   7000\r\n~LIGHT5B         210 245 255  0\r\n~LIGHT3B           243 243 255  4000"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2c.rad",
    "content": "//+0~FIFTS_LGHT5     150 230 255  10000\r\n//+0~FIFTS_LGHT5     150 215 255  10000\r\n+0~FIFTS_LGHT5     160 220 255  10000\r\n~LIGHT3F           255 190 60   7000\r\n+0~TNNL_LGT2       255 150 25   18000\r\n//~LIGHT3B           243 243 255  4000\r\n~LIGHT3B           180 170 255  10000\r\n~LIGHT3C           220 210 150  7000\r\n\r\n+0~TNNL_LGT2       255 150 25   22000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2d.rad",
    "content": "+0~TNNL_LGT2       255 150 25   18000\r\n//+0~FIFTS_LGHT5     150 230 255  10000\r\n+0~FIFTS_LGHT5     160 220 255  10000\r\n~LIGHT3F           255 190 60   7000\r\n~LIGHT3B           243 243 255  4000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2e.rad",
    "content": "+0~FIFTS_LGHT5     150 230 255  10000\r\n~LIGHT3C           220 210 150  7000\r\n+0~TNNL_LGT2       255 150 25   18000\r\n~LIGHT3F           255 190 60   7000\r\n//~LIGHT3B           243 243 255  4000\r\n~LIGHT3B           180 170 255  10000\r\n\r\n~LIGHT3E           40  160 150  12000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2f.rad",
    "content": "~LIGHT3F           255 190 60   7000\r\n//~LIGHT3B           243 243 255  4000\r\n~LIGHT3B           180 170 255  10000\r\n+0~TNNL_LGT2       255 150 25   18000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2g.rad",
    "content": "//+0~FIFTS_LGHT5     150 230 255  10000\r\n+0~FIFTS_LGHT5     160 220 255  10000\r\n\r\n+0~TNNL_LGT2       255 150 25   20000\r\n~LIGHT3C           220 210 150  7000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a2h.rad",
    "content": "+0~FIFTS_LGHT5     150 230 255  10000\r\n//~LIGHT3B           243 243 255  4000\r\n~LIGHT3B           180 170 255  10000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3.rad",
    "content": "+0~FIFTS_LGHT5     150 230 255  0\r\n+0~FIFTIES_LGT2    170 185 255  5500\r\n+0~TNNL_LGT3       150 150 210  17000\r\n//EMERGLIGHT       255 200 100  50000\r\nEMERGLIGHT         255 200 100  75000\r\n\r\n// vagonetka room\r\n+0~TNNL_LGT1       220 230 100  20000\r\n\r\nFILL1              120 220 150  10000 // TODO: correct after add light from invisible brush support\r\nFILL2              255 175 55   2000 // TODO: correct after add light from invisible brush support\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3a.rad",
    "content": "~SPOTYELLOW        225 145 55   25000\r\n//+0~TNNL_LGT4       170 90  40   20000\r\n+0~TNNL_LGT4       170 90  40   30000 // TODO: test and fix\r\n+0~FIFTIES_LGT2    170 185 255  5500\r\n\r\n\r\nFILL1              120 220 150  10000 // TODO: correct after add light from invisible brush support\r\nFILL2              255 175 55   2000 // TODO: correct after add light from invisible brush support\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3b.rad",
    "content": "//~SPOTYELLOW      255 155 25   25000\r\n~SPOTYELLOW      225 145 55   30000\r\n~SPOTRED         255 0   0    20000\r\n//+0~TNNL_LGT4     170 90  40   20000\r\n+0~TNNL_LGT4     170 90  40   25000\r\n+0~GYMLIGHT      255 195 65   5000\r\n\r\n~LIGHT3E         70  160 120  8000\r\n\r\n\r\nFILL1            120 220 150  10000 // TODO: correct after add light from invisible brush support\r\nFILL2            255 175 55   2000 // TODO: correct after add light from invisible brush support\r\n//+0~GENERIC86       180 240 255  100\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3c.rad",
    "content": "+0~GYMLIGHT      255 195 65   5000\r\n~LIGHT3C         220 210 150  3000\r\n~GENERIC87       180 240 255  50000\r\n+0~LIGHT2A       150 150 230  3000\r\n~SPOTBLUE        40  70  255  30000\r\n~SPOTYELLOW      225 145 55   30000\r\n~SPOTGREEN       60  255 125  30000\r\n\r\n\r\nFILL2            255 175 55   2000 // TODO: correct after add light from invisible brush support\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3d.rad",
    "content": "~SPOTYELLOW      255 155 25   25000\r\n+0~FIFTIES_LGT2  175 175 255  2500\r\n+0~LIGHT2A       150 150 230  8000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a3e.rad",
    "content": "+0~GYMLIGHT      255 195 55   20000\r\n~SPOTGREEN       60  255 125  100000 // FIXME\r\n~SPOTBLUE        80  120 255  20000 // FIXME\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4a.rad",
    "content": "~SPOTYELLOW      255 155 25   10000\r\n+A~FIFTIES_LGT2  175 175 255  3000\r\n~LIGHT3A         190 10  10   14000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4b.rad",
    "content": "//+0~FIFTIES_LGT2    175 175 255  15000\r\n+0~FIFTIES_LGT2    100 140 255  15000\r\n+0~FIFTS_LGHT5     255 185 65   10000\r\n//+A~FIFTS_LGHT3     255 255 255  5000\r\n+A~FIFTS_LGHT3     255 255 255  7000\r\n//+0~FIFTS_LGHT3     255 75  25   5000\r\n+0~FIFTS_LGHT3     255 95  5    5000\r\n+0~TNNL_LGT4       170 90  40   15000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4c.rad",
    "content": "+0~FIFTIES_LGT2    100 140 255  15000\r\n+0~FIFTS_LGHT5     255 185 25   10000\r\n+A~FIFTS_LGHT3     255 255 255  5000\r\n+0~FIFTS_LGHT3     255 95  5    5000\r\n\r\n+0~FIFTS_LGHT01    160 170 220  8000\r\n\r\n!TOXICGRN          20  255 0    500\r\n+0~GENERIC86       255 125 20   5000\r\n+0~GENERIC86R      128 2   2    20000\r\n\r\n+0~LIGHT4A         200 190 90  11000\r\n\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4d.rad",
    "content": "~LIGHT3C         220 210 150  3000\r\n~SPOTYELLOW      255 255 255  2000\r\n+0~GENERIC86     255 155 25   2000\r\n+0~FIFTIES_LGT2  0   60  255  1000 // FIXME\r\n//~LIGHT3B         200 200 255  2000 // FIXME\r\n~LIGHT3B         190 180 255  3000\r\n+0~GENERIC86R    128 2   2    20000\r\n+0~GENERIC86B    70  255 70   20000 // TODO: better color\r\n+0~GENERIC86     255 125 20   3000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4e.rad",
    "content": "~LIGHT3C         220 210 150  3000\r\n~SPOTYELLOW      255 255 255  2000\r\n//+0~GENERIC86     255 105 25   2000\r\n+0~GENERIC86     255 125 5    3000\r\n+0~FIFTIES_LGT2  0   50  255  1000 // FIXME\r\n//~LIGHT3B         200 200 255  2000 // FIXME\r\n~LIGHT3B         190 180 255  3000\r\n+0~FIFTS_LGHT5   255 255 255  3000 // FIXME\r\n+0~GENERIC86B    70  255 70   7000 // TODO: better color\r\nDRKMTLT_WALL08B  0   100 255  50   // FIXME\r\n\r\nLAB1_BLUXFLR1    79  255 255  150\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4f.rad",
    "content": "//~LIGHT3C         220 210 150  3000\r\n~SPOTYELLOW      255 255 255  2000\r\n+0~GENERIC86     255 125 25   3000\r\n//+0~FIFTIES_LGT2  0   50  255  1000  // FIXME\r\n+0~FIFTIES_LGT2  0   60  255  10000 // FIXME\r\n//~LIGHT3B         200 200 255  2000 // FIXME\r\n//+0~FIFTS_LGHT5   255 255 255  3000 // FIXME\r\n//+0~GENERIC86B    60  220 170  7000\r\n//DRKMTLT_WALL08B  0   100 255  50   // FIXME\r\n~LIGHT3B         190 180 255  3000\r\n+0~GENERIC86B    70  255 70   7000 // TODO: better color\r\n~LIGHT3C         220 240 200  20000\r\n~TRN_LT1         220 240 200  8000\r\n+0~FIFTS_LGHT5   255 255 255  3000 // FIXME\r\n\r\n+A~GENERIC86     255 225 125    5000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a4g.rad",
    "content": "+A~GENERIC86     255 225 125  5000\n+0~FIFTIES_LGT2  185 195 255  10000 // FIXME\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5.rad",
    "content": "+0~FIFTIES_LGT2  185 195 255  10000 // FIXME\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5a.rad",
    "content": "//~LIGHT3C         220 210 150  3000\r\n~LIGHT3C         220 210 150  2000\r\n+0~LIGHT4A       200 190 130  11000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5d.rad",
    "content": "//+0~FIFTIES_LGT2    175 175 255  5000\r\n+0~FIFTIES_LGT2    160 170 220  5000\r\n+0~DRKMTLS2C       255 200 100  2000\r\n~TRN_LT1           220 220 220  1000 // TODO: better color\r\n+0~LIGHT4A       200 190 130  11000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5e.rad",
    "content": "//+0~FIFTIES_LGT2    175 175 255  5000\r\n+0~FIFTIES_LGT2    160 170 220  5000\r\nEMERGLIGHT         255 200 100  75000\r\n+0~LIGHT4A         200 190 130  11000\r\n+0~FIFTS_LGHT5     255 165 25   5000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5f.rad",
    "content": "+0~TNNL_LGT4       170 90  40   25000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c2a5x.rad",
    "content": "+0~LIGHT4A       200 190 130  11000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a1.rad",
    "content": "+0~FIFTS_LGHT5   150 230 255  10000\n~LIGHT3E         70  160 120  8000\n~LIGHT3B         190 180 255  5000\n~LIGHT3C         220 210 150  4000\n~LIGHT5B         210 245 255  0\n~TRN_LT1         240 250 220  10000 // TODO: better color\n~SPOTRED         255 25  30   0\n+A~FIFTIES_LGT2  215 225 255  5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a1a.rad",
    "content": "+A~FIFTIES_LGT2  215 225 255  5000\n+0~WHITE         255 255 255  15000\n+0~TNNL_LGT2     255 150 25   15000\n+0~FIFTS_LGHT5   160 220 255  10000\n~LIGHT3C         220 210 150  4000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a1b.rad",
    "content": "GLASSGREEN       0   255 0    300\r\n//~LIGHT3E         220 210 150  2500\r\n//~LIGHT3E         90  190 140  14000\r\n~LIGHT3E         70  160 120  8000\r\n~TRN_LT1         255 250 160  6000 // TODO: better color\r\n+A~TNNL_LGT3     150 150 210  0\r\n~LIGHT3C         220 210 150  4000\r\n~LIGHT3B         190 180 255  5000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2.rad",
    "content": "+0~FIFTS_LGHT01  160 170 240  5000\n//+0~LIGHT4A       200 190 130  40000\n+0~LIGHT4A       200 190 130  11000\n+0~LIGHT3A       255 255 255  25000\n+A~FIFTIES_LGT2  215 225 255  0\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2a.rad",
    "content": "+0~LIGHT4A       200 190 130  11000\n+0~GENERIC65     255 255 255  17500\n+0~LIGHT3A       180 180 180  35000\n~SPOTBLUE        7   163 255  18000\n~SPOTYELLOW      189 231 253  5\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2b.rad",
    "content": "C3A2_LIGHT2      255 155 0    5000\n+0~GENERIC65     255 255 255  20000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2c.rad",
    "content": "+0~GENERIC65     255 255 255  20000\n+0~LIGHT3A       255 255 255  25000\nC3A2_LIGHT2      255 155 0    5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2d.rad",
    "content": "+0~GENERIC65     255 255 255  17500\n+0~LIGHT4A       230 210 120  11000\n+ALAB1_W6        150 160 210  0\nC3A2_LIGHT       255 105 0    5000 // TODO: better color/brightness\n+0~FIFTIES_LGT2  160 170 220  40000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2e.rad",
    "content": "+0~GENERIC65     255 255 255  17500\n+0~LIGHT3A       255 255 255  25000\n+0~LIGHT6A       150 5   5    25000\n+0~FIFTS_LGHT01  160 170 240  5000\n+0~LIGHT4A       200 190 130  40000 // FIXME\n//+0~LIGHT4A       200 190 130  11000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c3a2f.rad",
    "content": "C3A2_LIGHT2      255 155 0    5000\n+0~GENERIC65     255 255 255  20000\n+0~LIGHT3A       255 255 255  25000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a1b.rad",
    "content": "CRYS_3TOP        255 160 30   4000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a1c.rad",
    "content": "C4A1A_SPIKE1B    110 100 255  80000\nTECH_LITE1       255 170 60   80000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a1d.rad",
    "content": "C4A1A_SPIKE1B    110 100 255  80000\nTECH_LITE1       255 170 60   80000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a1e.rad",
    "content": "XENO_BN4         255 0   0    100000\r\nC4A1A_SPIKE1B    110 100 255  80000\r\nTECH_LITE1       255 170 60   80000\r\n\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a1f.rad",
    "content": "XENO_20D  255 0   0   1000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a2.rad",
    "content": "CRYS_2TOP        20  160 120  10000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a2a.rad",
    "content": "CRYS_2TOP        20  160 120  10000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c4a2b.rad",
    "content": ""
  },
  {
    "path": "ref/vk/data/valve/maps/c4a3.rad",
    "content": "//CRYS_3TOP        255 160 30   4000\nCRYS_3B          255 152 79   1000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/c5a1.rad",
    "content": "+0~WHITE         255 255 255  25000\n~LIGHT5D         50  160 50   3000\n~LIGHT3A         190 20  20   14000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/cornell.rad",
    "content": "RED              255 0   0    0\n+0~WHITE         255 255 255  200\n"
  },
  {
    "path": "ref/vk/data/valve/maps/crossfire.rad",
    "content": "+A~FIFTS_LGHT3   255 175 55   2000\n~LIGHT3C         220 210 150  4000\n~LIGHT3B         160 160 255  5000\n+0~FIFTS_LGHT5   255 175 55   5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/datacore.rad",
    "content": "+0~FIFTS_LGHT5   150 230 255  10000\n~LIGHT3C         220 210 150  6000\n~SPOTRED         255 5   5    10000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/doublecross.rad",
    "content": "+A~FIFTS_LGHT3   255 175 55   2000\n~LIGHT3B         160 160 255  5000\n+0~FIFTIES_LGT2  50  50  255  6000\n+0~FIFTS_LGHT5   255 175 55   5000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/frenzy.rad",
    "content": "+A~FIFTIES_LGT2  225 255 195  6000\n+0~FIFTIES_LGT2  185 195 255  6000\nC2A4X_GLU        0   0   255  1000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/gasworks.rad",
    "content": "+0~LIGHT4A       220 205 130  20000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/hldemo1.rad",
    "content": "+0~DRKMTLS2C     255 255 0    0\r\nC1A3YELLOW       255 255 128  2000\r\n\r\n\r\n//+0SCRN           255 255 255  60\r\n//+1SCRN           255 255 255  60\r\n//+2SCRN           255 255 255  60\r\n//SCRN3            24  30  252  70\r\n\r\n+0~LIGHT3A       180 180 230  4000\r\n//+0~LIGHT3A       180 180 230  0 // for perfomance\r\n\r\n\r\n+0~FIFTS_LGHT01  160 170 220  3000\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/hldemo2.rad",
    "content": "+0~FIFTS_LGHT01  160 170 220  3000\r\n+0~LIGHT6A       150 5   5    25000\r\n\r\n\r\n// FIXME: need emissive mask or emissive texture\r\n+0MEDKIT         255 192 128  100 // reused\r\nMEDKITEDGE1      255 192 128  100\r\n\r\n+0RECHARGE       255 192 0    30\r\nRECHARGEEDGE1    255 192 0    30"
  },
  {
    "path": "ref/vk/data/valve/maps/lambda_bunker.rad",
    "content": "+0~TNNL_LGT4     180 90  40   13000\n~SPOTYELLOW      255 255 255  20000\n~LIGHT3B         160 160 255  5000\n~LIGHT3C         220 210 150  6000\n+0~FIFTS_LGHT5   175 255 255  10000\n~LIGHT5B         210 245 255  0\n~LIGHT3E         40  160 150  8000\n+0~TNNL_LGT2     255 150 25   20000\n+A~FIFTIES_LGT2  215 225 255  8000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/lights.rad",
    "content": "// halflife.wad\r\n//!TOXICGRN        20  255 0    300\r\n//!TOXICGRN2       20  255 0    300\r\n+0BUTTONLITE     255 0   0    220\r\n//+0DRKMTL_SCRN    60  80  255  10000\r\n//+1DRKMTL_SCRN    60  80  255  10000 // hack, because no inheritance yet\r\n//+2DRKMTL_SCRN    60  80  255  10000 // hack, because no inheritance yet\r\n+0DRKMTL_SCRN    60  80  255  200\r\n+1DRKMTL_SCRN    60  80  255  200 // hack, because no inheritance yet\r\n+2DRKMTL_SCRN    60  80  255  200 // hack, because no inheritance yet\r\n+1FLICKERMON     255 255 255  1000\r\n+3FLICKERMON     255 255 255  300\r\n+4FLICKERMON     255 255 255  1000\r\n+6FLICKERMON     255 255 255  300\r\n+8FLICKERMON     255 255 255  1000\r\n+9FLICKERMON     255 255 255  300\r\n+0LAB1_W6        150 160 210  8800\r\n+0LAB1_W6B       150 160 210  4000\r\n+0LAB1_W7        245 240 210  4000\r\n+0~C2A4_LGT1     235 215 52   2000\r\n+0~DRKMTLLGT1    235 195 100  2000\r\n+0~DRKMTLS1      205 0   0    6000\r\n+0~DRKMTLS2      150 120 20   30000\r\n+0~DRKMTLS2C     255 200 100  50000\r\n+0~ELEV2_PAN     255 255 255  300\r\n+1~ELEV2_PAN     255 255 255  300 // hack, because no inheritance yet\r\n+2~ELEV2_PAN     255 255 255  300 // hack, because no inheritance yet\r\n+A~ELEV2_PAN     255 255 255  300 // hack, because no inheritance yet\r\n//+0~FIFTIES_LGT2  160 170 220  5000\r\n+0~FIFTIES_LGT2  255 255 255  20000\r\n+0~FIFTS_LGHT01  160 170 220  4000\r\n+0~FIFTS_LGHT06  255 255 255  8000\r\n+0~FIFTS_LGHT3   160 170 220  6000\r\n+0~FIFTS_LGHT4   160 170 220  4000\r\n//+0~FIFTS_LGHT5   160 170 220  1100\r\n+0~FIFTS_LGHT5   255 255 255  10000\r\n+0~GENERIC65     255 255 255  750\r\n+0~GENERIC85     110 140 235  20000\r\n+0~GENERIC86     255 230 125  10000\r\n+0~GENERIC86B    60  220 170  20000\r\n+0~GENERIC86R    128 0   0    60000\r\n+0~GYMLIGHT      255 230 150  2500\r\n+0~LAB_CRT8      50  50  255  100\r\n+1~LAB_CRT8      50  50  255  100 // hack, because no inheritance yet\r\n//+0~LAB1_CMP2     255 255 255  20 // TODO: emissive texture\r\n//+1~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+2~LAB1_CMP2     255 255 255  20 // hack, because no inheritance yet\r\n//+0~LIGHT1        40  60  150  3000\r\n+0~LIGHT1        40  60  150  10000\r\n+0~LIGHT2A       255 250 130  2000\r\n+0~LIGHT3A       180 180 230  10000\r\n+0~LIGHT4A       200 190 130  11000\r\n+0~LIGHT5A       80  150 200  10000\r\n+0~LIGHT6A       150 5   5    25000\r\n+0~TNNL_LGT1     240 230 100  10000\r\n+0~TNNL_LGT2     190 255 255  12000\r\n+0~TNNL_LGT3     150 150 210  17000\r\n+0~TNNL_LGT4     170 90  40   10000\r\n+0~WHITE         255 255 255  100\r\n+ABUTTONLITE     115 255 105  220\r\n+ALAB1_W6        150 160 210  6500\r\nC1A3C_MAP        100 100 255  100\r\nC3A2_LIGHT       234 195 0    500\r\nC3A2_LIGHT2      234 195 0    500\r\n//DRKMTL_SCRN3     0   110 220  500\r\nDRKMTL_SCRN3     1   111 220  200\r\nELEV2_CIEL       255 200 100  2000\r\nEMERGLIGHT       255 200 100  50000\r\nEMERGLIGHTC      101 255 170  8000\r\n//FIFTIES_MON1B    100 100 180  30\r\nFILL1            140 190 175  30\r\nFILL2            140 190 175  30\r\n//FLATBED_HLITE2   215 180 95   2000\r\nFLATBED_HLITE2   215 180 95   20000\r\nFLATBED_LITE1    215 180 95   2000\r\n//FLATBED_LITE2    215 180 95   2000\r\nFLATBED_TLITE2   215 0   0    2000\r\nGENERIC105       255 100 100  1000\r\nGENERIC105A      255 100 100  1000\r\nGENERIC106       120 120 100  1000\r\nGENERIC106A      120 120 100  1000\r\nGENERIC107       180 50  180  1000\r\nGENERIC107A      180 50  180  30\r\nGENERIC87A       100 255 100  1000\r\nGENERIC88A       255 100 100  1000\r\nGENERIC89A       40  40  130  30\r\nGENERIC90A       200 255 200  1000\r\nGENERIC99B       0   215 200  30\r\nGENERIC99C       105 0   0    30\r\nGEN_VEND1        50  180 50   1000\r\n//GLASSGREEN       50  255 50   200\r\n//LITEPANEL1       190 170 120  1100\r\n//LITEPANEL1       190 170 120  5000\r\nLITEPANEL1       190 170 120  2500\r\nPANELLITE1       190 210 210  2000\r\nRED              255 0   0    1000\r\nSKKYLITE         165 230 255  1000\r\nSKKYLITE2        190 210 210  2000\r\nSKKYLITEDAWN     210 175 195  2000\r\nSUBWAY_LIGHTS    255 250 202  2000\r\n//WHITE            255 255 255  100\r\n//YELLOW           255 200 100  2000\r\n//LAB1_COMP3D      255 255 255  20 // TODO: emissive texture\r\n~GENERIC87       205 230 255  550\r\n~GENERIC88       205 230 255  550\r\n~LAB_CRT9A       225 150 150  100\r\n~LAB_CRT9B       100 100 255  100\r\n~LAB_CRT9C       100 200 150  100\r\n//~LAB1_COMP7      255 255 255  20 // TODO: emissive texture\r\n~LIGHT3A         190 20  20   3000\r\n//~LIGHT3B         155 155 235  2000\r\n~LIGHT3B         84  118 198  14000\r\n//~LIGHT3C         220 210 150  2500\r\n~LIGHT3C         220 210 150  14000\r\n~LIGHT3D         45  245 10   2000\r\n//~LIGHT3E         90  190 140  6000\r\n~LIGHT3E         90  190 140  14000\r\n~LIGHT3F         220 210 175  7000\r\n~LIGHT5A         210 245 255  2000\r\n~LIGHT5B         210 245 255  2000\r\n~LIGHT5C         150 255 200  2000\r\n~LIGHT5D         185 235 230  1000\r\n~LIGHT5E         255 20  20   3000\r\n~LIGHT5F         255 254 190  2000\r\n//~SPOTBLUE        80  190 240  2000\r\n~SPOTBLUE        7   163 245  18000\r\n//~SPOTGREEN       60  255 125  2000\r\n~SPOTGREEN       60  255 125  20000\r\n//~SPOTRED         255 25  30   2000\r\n~SPOTRED         255 25  30   20000\r\n//~SPOTYELLOW      255 250 140  2000\r\n~SPOTYELLOW      189 231 253  20000\r\n~TRN_LT1         160 170 220  10000\r\n\r\n// liquids.wad\r\n!RADIO           100 255 40   1000\r\n//SCROLLTOXIC      100 255 40   1000\r\n\r\n\r\n// xeno.wad\r\nC4A1A_SPIKE1B    150 150 255  50000\r\nCRYS_1A          248 193 255  10000\r\nCRYS_1B          248 193 255  10000\r\nCRYS_1C          248 193 255  10000\r\nCRYS_1TOP        248 193 255  10000\r\nCRYS_2A          169 255 182  10000\r\nCRYS_2B          169 255 182  10000\r\n//CRYS_2TOP        169 255 182  10000\r\nCRYS_3A          255 152 79   10000\r\nCRYS_3B          255 152 79   10000\r\nCRYS_3TOP        255 152 79   1000\r\nCRYS_4A          255 209 153  10000\r\nCRYS_4B          255 209 153  10000\r\n//CRYS_4B          255 160 128  10000\r\nTECH_LITE1       255 235 160  60000\r\nTECH_LITE2       230 125 20   60000\r\nTECH_LITE3       60  210 25   60000\r\n\r\n\r\n// tfc.wad\r\n//+0~TNNL_LGT5\r\n//+AELEV1\r\n//+AELEV2\r\n//+AELEV3\r\n//+A_CLOSED\r\n//+A_OPEN\r\n\r\n// cstrike.wad\r\n//+0CSTRIKE_IE1LT\r\n//+0CSTRIKE_SE4SW\r\n//+ACSTRIKE_SE4SW\r\n\r\n// cs_747.wad\r\n//+0PL_LIGHT1\r\n\r\n// itsitaly.wad\r\n//SIDELIGHT\r\n//~BLUERUG\r\n//~REDRUG\r\n\r\n// cs_cbble.wad\r\n//+0SMLIGHT1\r\n"
  },
  {
    "path": "ref/vk/data/valve/maps/rapidcore.rad",
    "content": "+0~FIFTIES_LGT2  185 195 255  6000\n~LIGHT3C         220 210 150  4000\nFLATBED_HLITE2   150 150 220  10000 // FIXME\n"
  },
  {
    "path": "ref/vk/data/valve/maps/rustmill.rad",
    "content": "+0~FIFTS_LGHT5   160 220 255  10000\n~LIGHT3B         160 160 255  8000\n~LIGHT3C         220 210 150  6000\n+0~FIFTIES_LGT2  185 195 255  6000\n+0~LIGHT4A       220 205 130  20000\nSKKYLITE2        255 110 40   2000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/snark_pit.rad",
    "content": "+0~FIFTS_LGHT5   255 145 65   20000\n+A~FIFTIES_LGT2  185 195 255  20000\n!TOXICGRN        90  255 5    1000 // 400 // FIXME\n~LIGHT5A         5   5   255  10000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/stalkyard.rad",
    "content": "+0~FIFTS_LGHT4   150 160 220  5000\n+0~LIGHT2A       235 195 120  5000\nFLATBED_HLITE2   180 180 255  10000\n+0~TNNL_LGT3     140 140 255  10000\nFLATBED_LITE1    255 245 220  10000\n\n"
  },
  {
    "path": "ref/vk/data/valve/maps/subtransit.rad",
    "content": "+0~FIFTS_LGHT3   255 110  25  7000\n+0~GENERIC85     80  160 220  15000\n~LIGHT5A         160 180 255  500 // FIXME\n~LIGHT3B         160 160 255  18000\n~SPOTYELLOW      255 255 255  10000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0.rad",
    "content": "~LIGHT3B         160 160 255  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3C         220 210 150  3000\n+0~FIFTS_LGHT5   160 220 250  8000\n~LIGHT3E         60  190 140  8000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0a.rad",
    "content": "~LIGHT3E         60  190 140  8000\n~LIGHT3C         220 210 150  6000\n~TRN_LT1         255 250 160  6000 // TODO: better color\n~LIGHT3B         160 160 255  8000\n+0~FIFTS_LGHT5   160 220 250  8000\n+A~FIFTS_LGHT5   255 175 55   5\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0b.rad",
    "content": "~LIGHT3C         220 210 150  3000\n~LIGHT3B         160 160 255  8000\n+A~TNNL_LGT3     150 150 210  0\n~TRN_LT1         255 250 160  6000 // TODO: better color\n+A~FIFTS_LGHT5   255 175 55   5\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0b1.rad",
    "content": "+A~TNNL_LGT3     150 150 210  0\n~LIGHT3C         220 210 150  4000\n~TRN_LT1         255 250 160  6000 // TODO: better color\n~LIGHT3B         160 160 255  8000\n+A~FIFTS_LGHT5   255 175 55   5\n~LIGHT3E         60  190 140  6000\n+0~FIFTS_LGHT5   160 220 250  8000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0b2.rad",
    "content": "+A~TNNL_LGT3     150 150 210  0\n~LIGHT3C         220 210 150  4000\n~LIGHT3B         160 160 255  8000\n~TRN_LT1         255 250 160  6000 // TODO: better color\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0c.rad",
    "content": "//!TOXICGRN2       20  255 0    300\n!TOXICGRN2       20  255 0    2000 // FIXME\n~LIGHT3C         220 210 150  4000\n~LIGHT3B         160 160 255  8000\n~LIGHT3E         60  190 140  6000\n+A~FIFTS_LGHT5   255 175 55   5\n+0~FIFTS_LGHT5   160 220 250  8000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/t0a0d.rad",
    "content": "+0~FIFTS_LGHT5   160 220 250  8000\n~LIGHT3C         220 210 150  4000\n~LIGHT3B         160 160 255  8000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/team9.rad",
    "content": "~LIGHT3C         220 210 150  2000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/thehill.rad",
    "content": "+0~FIFTIES_LGT2  185 195 255  10000\nC2A4X_GLU        0   0   255  1000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/undertow.rad",
    "content": "+0~FIFTS_LGHT3     190 190 190  6000\n~SPOTYELLOW        225 205 95   25000\n+0~FIFTIES_LGT2    185 190 255  10000\n+A~FIFTIES_LGT2    255 205 75   6000\n"
  },
  {
    "path": "ref/vk/data/valve/maps/xen_dm.rad",
    "content": "TECH_LITE1       255 165 55   80000\n"
  },
  {
    "path": "ref/vk/dumbspter.c",
    "content": "#if 0\n#include \"vk_common.h\"\n#include \"xash3d_types.h\"\n#include \"protocol.h\"\n#include \"const.h\"\n#include \"bspfile.h\"\n#include \"mod_local.h\"\n\n#include \"vk_light.h\"\n\n#define PR(...) gEngine.Con_Reportf(__VA_ARGS__)\n\n// FIXME copied from mod_bmodel.c\n// TODO would it be possible to not decompress each time, but instead get a list of all leaves?\nstatic byte\t\tg_visdata[(MAX_MAP_LEAFS+7)/8];\t// intermediate buffer\nbyte *Mod_DecompressPVS( const byte *in, int visbytes )\n{\n\tbyte\t*out;\n\tint\tc;\n\n\tout = g_visdata;\n\n\tif( !in )\n\t{\n\t\t// no vis info, so make all visible\n\t\twhile( visbytes )\n\t\t{\n\t\t\t*out++ = 0xff;\n\t\t\tvisbytes--;\n\t\t}\n\t\treturn g_visdata;\n\t}\n\n\tdo\n\t{\n\t\tif( *in )\n\t\t{\n\t\t\t*out++ = *in++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tc = in[1];\n\t\tin += 2;\n\n\t\twhile( c )\n\t\t{\n\t\t\t*out++ = 0;\n\t\t\tc--;\n\t\t}\n\t} while( out - g_visdata < visbytes );\n\n\treturn g_visdata;\n}\n\n\nstatic void DumpLeaves( void ) {\n\tmodel_t\t*map = gEngine.pfnGetModelByIndex( 1 );\n\tconst world_static_t *world = gEngine.GetWorld();\n\tASSERT(map);\n\n\tPR(\"visbytes=%d leafs: %d:\\n\", world->visbytes, map->numleafs);\n\tfor (int i = 0; i < map->numleafs; ++i) {\n\t\tconst mleaf_t* leaf = map->leafs + i;\n\t\tPR(\"  %d: contents=%d numsurfaces=%d cluster=%d\\n\",\n\t\t\ti, leaf->contents, leaf->nummarksurfaces, leaf->cluster);\n\n\t\t// TODO: mark which surfaces belong to which leaves\n\t\t// TODO: figure out whether this relationship is stable (surface belongs to only one leaf)\n\n\t\t// print out PVS\n\t\t{\n\t\t\tint pvs_count = 0;\n\t\t\tconst byte *visdata = Mod_DecompressPVS(leaf->compressed_vis, world->visbytes);\n\t\t\tif (!visdata) continue;\n\t\t\tPR(\"    PVS:\");\n\t\t\tfor (int j = 0; j < map->numleafs; ++j) {\n\t\t\t\tif (CHECKVISBIT(visdata, map->leafs[j].cluster /* FIXME cluster (j+1) or j??!?!*/)) {\n\t\t\t\t\tpvs_count++;\n\t\t\t\t\tPR(\" %d\", j);\n\t\t\t\t}\n\t\t\t}\n\t\t\tPR(\" TOTAL: %d\\n\", pvs_count);\n\t\t}\n\t}\n}\n\ntypedef struct {\n\tmodel_t\t*map;\n\tconst world_static_t *world;\n\tFILE *f;\n} traversal_context_t;\n\nstatic void visitLeaf(const mleaf_t *leaf, const mnode_t *parent, const traversal_context_t *ctx) {\n\tconst int parent_index = parent - ctx->map->nodes;\n\tint pvs_count = 0;\n\tconst byte *visdata = Mod_DecompressPVS(leaf->compressed_vis, ctx->world->visbytes);\n\tint num_emissive = 0;\n\n\t// ??? empty leaf?\n\tif (leaf->cluster < 0) // || leaf->nummarksurfaces == 0)\n\t\treturn;\n\n\tfprintf(ctx->f, \"\\\"N%d\\\" -> \\\"L%d\\\"\\n\", parent_index, leaf->cluster);\n\tfor (int i = 0; i < leaf->nummarksurfaces; ++i) {\n\t\tconst msurface_t *surf = leaf->firstmarksurface[i];\n\t\tconst int surf_index = surf - ctx->map->surfaces;\n\t\tconst int texture_num = surf->texinfo->texture->gl_texturenum;\n\t\tconst qboolean emissive = texture_num >= 0 && g_lights.map.emissive_textures[texture_num].set;\n\n\t\tif (emissive) num_emissive++;\n\n\t\tfprintf(ctx->f, \"L%d -> S%d [color=\\\"#%s\\\"; dir=\\\"none\\\"];\\n\",\n\t\t\tleaf->cluster, surf_index, emissive ? \"ff0000ff\" : \"00000040\");\n\t}\n\n\tif (!visdata)\n\t\treturn;\n\n\tfor (int j = 0; j < ctx->map->numleafs; ++j) {\n\t\tif (CHECKVISBIT(visdata, ctx->map->leafs[j].cluster)) {\n\t\t\tpvs_count++;\n\t\t}\n\t}\n\n\tfprintf(ctx->f, \"\\\"L%d\\\" [label=\\\"Leaf cluster %d\\\\npvs_count: %d\\\\nummarksurfaces: %d\\\\n num_emissive: %d\\\"; style=filled; fillcolor=\\\"%s\\\"; ];\\n\",\n\t\tleaf->cluster, leaf->cluster, pvs_count, leaf->nummarksurfaces, num_emissive,\n\t\tnum_emissive > 0 ? \"red\" : \"transparent\"\n\t\t);\n}\n\nstatic void visitNode(const mnode_t *node, const mnode_t *parent, const traversal_context_t *ctx) {\n\tif (node->contents < 0) {\n\t\tvisitLeaf((const mleaf_t*)node, parent, ctx);\n\t} else {\n\t\tconst int parent_index = parent ? parent - ctx->map->nodes : -1;\n\t\tconst int node_index = node - ctx->map->nodes;\n\t\tfprintf(ctx->f, \"\\\"N%d\\\" -> \\\"N%d\\\"\\n\", parent_index, node_index);\n\t\tfprintf(ctx->f, \"\\\"N%d\\\" [label=\\\"numsurfaces: %d\\\\nfirstsurface: %d\\\"];\\n\",\n\t\t\tnode_index, node->numsurfaces, node->firstsurface);\n\t\tvisitNode(node->children[0], node, ctx);\n\t\tvisitNode(node->children[1], node, ctx);\n\t}\n}\n\nvoid traverseBSP( void ) {\n\tconst traversal_context_t ctx = {\n\t\t.map = gEngine.pfnGetModelByIndex( 1 ),\n\t\t.world = gEngine.GetWorld(),\n\t\t.f = fopen(\"bsp.dot\", \"w\"),\n\t};\n\n\tfprintf(ctx.f, \"digraph bsp { node [shape=box];\\n\");\n\tvisitNode(ctx.map->nodes, NULL, &ctx);\n\tfprintf(ctx.f,\n\t\t\"subgraph surfaces {rank = max; style= filled; color = lightgray;\\n\");\n\tfor (int i = 0; i < ctx.map->numsurfaces; i++) {\n\t\tconst msurface_t *surf = ctx.map->surfaces + i;\n\t\tconst int texture_num = surf->texinfo->texture->gl_texturenum;\n\t\tfprintf(ctx.f, \"S%d [rank=\\\"max\\\"; label=\\\"S%d\\\\ntexture: %s\\\\nnumedges: %d\\\\ntexture_num=%d\\\"; style=filled; fillcolor=\\\"%s\\\";];\\n\",\n\t\t\ti, i,\n\t\t\tsurf->texinfo && surf->texinfo->texture ? surf->texinfo->texture->name : \"NULL\",\n\t\t\tsurf->numedges, texture_num,\n\t\t\t(texture_num >= 0 && g_lights.map.emissive_textures[texture_num].set) ? \"red\" : \"transparent\" );\n\t}\n\tfprintf(ctx.f, \"}\\n}\\n\");\n\tfclose(ctx.f);\n\t//exit(0);\n}\n#endif\n"
  },
  {
    "path": "ref/vk/infotool.c",
    "content": "#include \"camera.h\"\n#include \"vk_math.h\"\n#include \"vk_common.h\"\n#include \"r_textures.h\"\n#include \"vk_brush.h\"\n#include \"vk_light.h\"\n\n#include \"pm_defs.h\"\n#include \"pmtrace.h\"\n\nstatic const char *renderModeName( int rendermode ) {\n\tswitch (rendermode) {\n\t\tcase kRenderNormal: return \"kRenderNormal\";\n\t\tcase kRenderTransColor: return \"kRenderTransColor\";\n\t\tcase kRenderTransTexture: return \"kRenderTransTexture\";\n\t\tcase kRenderGlow: return \"kRenderGlow\";\n\t\tcase kRenderTransAlpha: return \"kRenderTransAlpha\";\n\t\tcase kRenderTransAdd: return \"kRenderTransAdd\";\n\t\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nvoid XVK_CameraDebugPrintCenterEntity( void ) {\n\tvec3_t vec_end;\n\tpmtrace_t trace;\n\tconst msurface_t *surf;\n\tchar buf[1024], *p = buf, *const end = buf + sizeof(buf);\n\tconst physent_t *physent = NULL;\n\tconst cl_entity_t *ent = NULL;\n\n\tVectorMA(g_camera.vieworg, 1e6, g_camera.vforward, vec_end);\n\n\ttrace = gEngine.CL_TraceLine( g_camera.vieworg, vec_end, PM_NORMAL );\n\tsurf = gEngine.EV_TraceSurface( Q_max(trace.ent, 0), g_camera.vieworg, vec_end );\n\n\tif (trace.ent > 0) {\n\t\tphysent = gEngine.EV_GetPhysent( trace.ent );\n\t}\n\n\tent = globals.entities + ((physent && physent->info > 0) ? physent->info : 0);\n\n\tp += Q_snprintf(p, end - p,\n\t\t\"^\\n\"\n\t\t\"cam.origin: %.03f %.03f %.03f\"\n\t\t\" hit: %.03f %.03f %.03f\"\n\t\t// TODO cam dir\n\t\t\"\\n\",\n\t\tg_camera.vieworg[0], g_camera.vieworg[1], g_camera.vieworg[2],\n\t\ttrace.endpos[0], trace.endpos[1], trace.endpos[2]\n\t);\n\n\tp += Q_snprintf(p, end - p,\n\t\t\"entity (dynamic index: %d, info: %d), name: %s\\n\",\n\t\tent ? ent->index : -1,\n\t\t(physent && physent->info > 0) ? physent->info : -1,\n\t\t(ent && ent->model) ? ent->model->name : \"N/A\");\n\n\tif (ent) {\n\t\tp += Q_snprintf(p, end - p,\n\t\t\t\"ent type: %d, rendermode: %d(%s)\\n\",\n\t\t\tent->curstate.entityType,\n\t\t\tent->curstate.rendermode,\n\t\t\trenderModeName(ent->curstate.rendermode));\n\t}\n\n\tif (surf && ent && ent->model && ent->model->surfaces) {\n\t\tconst int surface_index = surf - ent->model->surfaces;\n\t\tconst texture_t *current_tex = R_TextureAnimation(ent, surf);\n\t\tconst int tex_id = current_tex->gl_texturenum;\n\t\tconst char *const tex_name = R_TextureGetNameByIndex( tex_id );\n\t\tconst texture_t *tex = surf->texinfo->texture;\n\n\t\tconst texture_t* const alt = tex->alternate_anims;\n\n\t\tp += Q_snprintf(p, end - p,\n\t\t\t\"surface index: [[ %d ]];\\ntexture: %s(%d)\\n\"\n\t\t\t\"alternate_texture: %s(%d)\\n\",\n\t\t\tsurface_index, tex_name ? tex_name : \"NONE\", tex_id,\n\t\t\talt ? alt->name : \"N/A\", alt ? alt->gl_texturenum : -1\n\t\t);\n\n\t\tif (tex->anim_total > 0 && tex->anim_next) {\n\t\t\ttex = tex->anim_next;\n\t\t\tp += Q_snprintf(p, end - p,\n\t\t\t\t\"anim textures chain (%d):\\n\", tex->anim_total);\n\t\t\tfor (int i = 0; i < tex->anim_total && tex; ++i) {\n\t\t\t\tconst char* const texname = R_TextureGetNameByIndex(tex->gl_texturenum);\n\t\t\t\tp += Q_snprintf(p, end - p,\n\t\t\t\t\t\"%d: %s(%d)%s\\n\", i, texname ? texname : \"NONE\", tex->gl_texturenum, tex == current_tex ? \" <-\" : \"   \");\n\t\t\t\ttex = tex->anim_next;\n\t\t\t}\n\t\t}\n\t}\n\n\tp = RT_LightPrintCellInfo(p, end, trace.endpos);\n\n\tgEngine.CL_CenterPrint(buf, 0.5f);\n}\n"
  },
  {
    "path": "ref/vk/r_block.c",
    "content": "#include \"r_block.h\"\n\n#include \"vk_common.h\" // ASSERT\n#include \"vk_core.h\" // vk_core.pool\n\ntypedef struct r_blocks_block_s {\n\tint long_index;\n\tuint32_t refcount;\n} r_blocks_block_t;\n\n// logical blocks\n//  <---- lifetime long -><-- once -->\n// [.....................|............]\n//  <--- pool         --><-- ring --->\n//    offset ?       --->\n\nstatic int allocMetablock(r_blocks_t *blocks) {\n\treturn aloIntPoolAlloc(&blocks->blocks.freelist);\n\t// TODO grow if needed\n}\n\nr_block_t R_BlockAllocLong(r_blocks_t *blocks, uint32_t size, uint32_t alignment) {\n\tr_block_t ret = {\n\t\t.offset = ALO_ALLOC_FAILED,\n\t\t.size = 0,\n\t\t.impl_ = {-1}\n\t};\n\n\tconst alo_block_t ablock = aloPoolAllocate(blocks->long_pool, size, alignment);\n\tif (ablock.offset == ALO_ALLOC_FAILED) {\n\t\tgEngine.Con_Reportf(S_ERROR \"aloPoolAllocate failed\\n\");\n\t\treturn ret;\n\t}\n\n\tconst int metablock_index = allocMetablock(blocks);\n\tif (metablock_index < 0) {\n\t\tgEngine.Con_Reportf(S_ERROR \"allocMetablock failed\\n\");\n\t\taloPoolFree(blocks->long_pool, ablock.index);\n\t\treturn ret;\n\t}\n\n\tret.offset = ablock.offset;\n\tret.size = ablock.size;\n\tret.impl_.index = metablock_index;\n\tret.impl_.blocks = blocks;\n\n\tr_blocks_block_t *metablock = blocks->blocks.storage + metablock_index;\n\tmetablock->long_index = ablock.index;\n\tmetablock->refcount = 1;\n\n\t/* gEngine.Con_Reportf(\"block alloc %dKiB => index=%d offset=%u\\n\", (int)size/1024, metablock_index, (int)ret.offset); */\n\n\tblocks->allocated_long += size;\n\treturn ret;\n}\n\nuint32_t R_BlockAllocOnce(r_blocks_t *blocks, uint32_t size, uint32_t alignment) {\n\tconst uint32_t offset = R_FlippingBuffer_Alloc(&blocks->once.flipping, size, alignment);\n\tif (offset == ALO_ALLOC_FAILED)\n\t\treturn ALO_ALLOC_FAILED;\n\n\treturn offset + blocks->once.ring_offset;\n}\n\nvoid R_BlocksCreate(r_blocks_t *blocks, uint32_t size, uint32_t once_size, int expected_allocs) {\n\tmemset(blocks, 0, sizeof(*blocks));\n\n\tblocks->size = size;\n\tblocks->allocated_long = 0;\n\n\tblocks->long_pool = aloPoolCreate(size - once_size, expected_allocs, 4);\n\taloIntPoolGrow(&blocks->blocks.freelist, expected_allocs);\n\tblocks->blocks.storage = Mem_Malloc(vk_core.pool, expected_allocs * sizeof(blocks->blocks.storage[0]));\n\n\tblocks->once.ring_offset = size - once_size;\n\tR_FlippingBuffer_Init(&blocks->once.flipping, once_size);\n}\n\nvoid R_BlockRelease(const r_block_t *block) {\n\tr_blocks_t *const blocks = block->impl_.blocks;\n\tif (!blocks || !block->size)\n\t\treturn;\n\n\tASSERT(block->impl_.index >= 0);\n\tASSERT(block->impl_.index < blocks->blocks.freelist.capacity);\n\n\tr_blocks_block_t *const metablock = blocks->blocks.storage + block->impl_.index;\n\n\t/* gEngine.Con_Reportf(\"block release index=%d offset=%u refcount=%d\\n\", block->impl_.index, (int)block->offset, (int)metablock->refcount); */\n\n\tASSERT (metablock->refcount > 0);\n\tif (--metablock->refcount)\n\t\treturn;\n\n\t/* gEngine.Con_Reportf(\"block free index=%d offset=%u\\n\", block->impl_.index, (int)block->offset); */\n\n\taloPoolFree(blocks->long_pool, metablock->long_index);\n\taloIntPoolFree(&blocks->blocks.freelist, block->impl_.index);\n\tblocks->allocated_long -= block->size;\n}\n\nvoid R_BlocksDestroy(r_blocks_t *blocks) {\n\tfor (int i = blocks->blocks.freelist.free; i < blocks->blocks.freelist.capacity; ++i) {\n\t\tr_blocks_block_t *b = blocks->blocks.storage + blocks->blocks.freelist.free_list[i];\n\t\tASSERT(b->refcount == 0);\n\t}\n\n\taloPoolDestroy(blocks->long_pool);\n\taloIntPoolDestroy(&blocks->blocks.freelist);\n\tMem_Free(blocks->blocks.storage);\n}\n\n// Clear all LifetimeOnce blocks, checking that they are not referenced by anything\nvoid R_BlocksClearOnce(r_blocks_t *blocks) {\n\tR_FlippingBuffer_Flip(&blocks->once.flipping);\n}\n"
  },
  {
    "path": "ref/vk/r_block.h",
    "content": "#pragma once\n\n#include \"std/flipping.h\"\n#include \"std/alolcator.h\"\n#include <stdint.h>\n\nstruct r_blocks_s;\ntypedef struct r_block_s {\n\tuint32_t offset;\n\tuint32_t size;\n\n\tstruct {\n\t\tint index;\n\t\tstruct r_blocks_s *blocks;\n\t} impl_;\n} r_block_t;\n\nstruct r_blocks_block_s;\ntypedef struct r_blocks_s {\n\tuint32_t size;\n\n\tstruct alo_pool_s *long_pool;\n\n\tstruct {\n\t\tuint32_t ring_offset;\n\t\tr_flipping_buffer_t flipping;\n\t} once;\n\n\tstruct {\n\t\talo_int_pool_t freelist;\n\t\tstruct r_blocks_block_s *storage;\n\t} blocks;\n\n\t// This is an estimate, it doesn't count alignment holes\n\tint allocated_long;\n} r_blocks_t;\n\nr_block_t R_BlockAllocLong(r_blocks_t *blocks, uint32_t size, uint32_t alignment);\nuint32_t R_BlockAllocOnce(r_blocks_t *blocks, uint32_t size, uint32_t alignment);\n\n//void R_BlockAcquire(r_block_t *block);\nvoid R_BlockRelease(const r_block_t *block);\n\nvoid R_BlocksCreate(r_blocks_t *blocks, uint32_t max_size, uint32_t once_max, int expected_allocs);\nvoid R_BlocksDestroy(r_blocks_t *blocks);\n\nvoid R_BlocksClearOnce(r_blocks_t *blocks);\n"
  },
  {
    "path": "ref/vk/r_decals.c",
    "content": "/*\ngl_decals.c - decal paste and rendering\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"vk_core.h\"\n#include \"vk_logs.h\"\n#include \"vk_common.h\"\n#include \"vk_const.h\"\n#include \"vk_studio.h\"\n#include \"vk_scene.h\"\n#include \"vk_triapi.h\"\n#include \"vk_render.h\"\n#include \"r_speeds.h\"\n#include \"r_decals.h\"\n#include \"r_textures.h\"\n#include \"xash3d_mathlib.h\"\n\n#include <stdlib.h>\n\n#define MODULE_NAME \"decals\"\n\n// increase on z-fighting\n#define DECAL_DEPTH_OFFSET -0.2f\n\n// same with OpenGL renderer\n#define DECAL_OVERLAP_DISTANCE\t2\n#define DECAL_DISTANCE\t\t4\t// too big values produce more clipped polygons\n#define MAX_DECALCLIPVERT\t\t32\t// produced vertexes of fragmented decal\n#define DECAL_CACHEENTRY\t\t256\t// MUST BE POWER OF 2 or code below needs to change!\n#define DECAL_TRANSPARENT_THRESHOLD\t230\t// transparent decals draw with GL_MODULATE\n#define DECAL_BATCH_VERTEX_BUDGET 900\n\n// empirically determined constants for minimizing overalpping decals\n#define MAX_OVERLAP_DECALS\t\t6\n#define DECAL_OVERLAP_DIST\t\t8\n#define MIN_DECAL_SCALE\t\t0.01f\n#define MAX_DECAL_SCALE\t\t16.0f\n\n// clip edges\n#define LEFT_EDGE\t\t\t0\n#define RIGHT_EDGE\t\t\t1\n#define TOP_EDGE\t\t\t2\n#define BOTTOM_EDGE\t\t\t3\n\n// This structure contains the information used to create new decals\ntypedef struct\n{\n\tvec3_t\t\tm_Position;\t// world coordinates of the decal center\n\tmodel_t\t\t*m_pModel;\t// the model the decal is going to be applied in\n\tint\t\tm_iTexture;\t// The decal material\n\tint\t\tm_Size;\t\t// Size of the decal (in world coords)\n\tint\t\tm_Flags;\n\tint\t\tm_Entity;\t\t// Entity the decal is applied to.\n\tfloat\t\tm_scale;\n\tint\t\tm_decalWidth;\n\tint\t\tm_decalHeight;\n\tvec3_t\t\tm_Basis[3];\n} decalinfo_t;\n\nstatic float\tg_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE];\nstatic float\tg_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE];\n\ndecal_t\tgDecalPool[MAX_RENDER_DECALS];\nstatic int\tgDecalCount;\n\nmatrix4x4 gDecalTransform;\n\nstatic struct {\n\tint decals_total;\n\tint batches_total;\n\tqboolean initialized;\n} g_decal_stats;\n\ntypedef struct {\n\tdecal_t *decal;\n\tmsurface_t *surf;\n\tint lightmap;\n\tint texture;\n\tmatrix4x4 transform;\n} vk_decal_draw_item_t;\n\nstatic struct {\n\tvk_decal_draw_item_t items[MAX_RENDER_DECALS];\n\tint count;\n} g_decal_queue;\n\nvoid R_DecalsFrameBegin( void )\n{\n\tif( !g_decal_stats.initialized )\n\t{\n\t\tg_decal_stats.initialized = true;\n\t\tg_decal_stats.decals_total = 0;\n\t\tg_decal_stats.batches_total = 0;\n\t\tR_SPEEDS_COUNTER( g_decal_stats.decals_total, \"decals unique\", kSpeedsMetricCount );\n\t\tR_SPEEDS_COUNTER( g_decal_stats.batches_total, \"decals batches\", kSpeedsMetricCount );\n\t\treturn;\n\t}\n\n\tg_decal_stats.decals_total = 0;\n\tg_decal_stats.batches_total = 0;\n\tg_decal_queue.count = 0;\n}\n\nvoid R_ClearDecals( void )\n{\n\tmemset( gDecalPool, 0, sizeof( gDecalPool ));\n\tgDecalCount = 0;\n\n\tMatrix4x4_LoadIdentity(gDecalTransform);\n\tg_decal_queue.count = 0;\n}\n\n// unlink pdecal from any surface it's attached to\nstatic void R_DecalUnlink( decal_t *pdecal )\n{\n\tdecal_t\t*tmp;\n\n\tif( pdecal->psurface )\n\t{\n\t\tif( pdecal->psurface->pdecals == pdecal )\n\t\t{\n\t\t\tpdecal->psurface->pdecals = pdecal->pnext;\n\t\t}\n\t\telse\n\t\t{\n\t\t\ttmp = pdecal->psurface->pdecals;\n\t\t\tif( !tmp ) gEngine.Con_Printf(S_ERROR \"%s: bad decal list\\n\", __func__ );\n\n\t\t\twhile( tmp->pnext )\n\t\t\t{\n\t\t\t\tif( tmp->pnext == pdecal )\n\t\t\t\t{\n\t\t\t\t\ttmp->pnext = pdecal->pnext;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttmp = tmp->pnext;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( pdecal->polys )\n\t\tMem_Free( pdecal->polys );\n\n\tpdecal->psurface = NULL;\n\tpdecal->polys = NULL;\n}\n\n// Just reuse next decal in list\n// A decal that spans multiple surfaces will use multiple decal_t pool entries,\n// as each surface needs it's own.\nstatic decal_t *R_DecalAlloc( decal_t *pdecal )\n{\n\tint\tlimit = MAX_RENDER_DECALS;\n\n\t// if( r_decals->value < limit ) // TODO\n\t// \tlimit = r_decals->value;\n\n\tif( !limit ) return NULL;\n\n\tif( !pdecal )\n\t{\n\t\tint\tcount = 0;\n\n\t\t// check for the odd possiblity of infinte loop\n\t\tdo\n\t\t{\n\t\t\tif( gDecalCount >= limit )\n\t\t\t\tgDecalCount = 0;\n\n\t\t\tpdecal = &gDecalPool[gDecalCount]; // reuse next decal\n\t\t\tgDecalCount++;\n\t\t\tcount++;\n\t\t} while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit );\n\t}\n\n\t// if decal is already linked to a surface, unlink it.\n\tR_DecalUnlink( pdecal );\n\n\treturn pdecal;\n}\n\n//-----------------------------------------------------------------------------\n// find decal image and grab size from it\n//-----------------------------------------------------------------------------\nstatic void R_GetDecalDimensions( int texture, int *width, int *height )\n{\n\tif (width)\n\t{\n\t\t*width = R_TexturesGetParm( PARM_TEX_SRC_WIDTH, texture );\n\t\tif (*width == 0) *width = 1; // to avoid divide by zero\n\t}\n\n\tif (height)\n\t{\n\t\t*height = R_TexturesGetParm( PARM_TEX_SRC_HEIGHT, texture );\n\t\tif (*height == 0) *height = 1; // to avoid divide by zero\n\t}\n}\n\n//-----------------------------------------------------------------------------\n// compute the decal basis based on surface normal\n//-----------------------------------------------------------------------------\nstatic void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] )\n{\n\tvec3_t\tsurfaceNormal = {};\n\n\t// setup normal\n\tif (surf->plane->normal) {\n\t\tif( surf->flags & SURF_PLANEBACK )\n\t\t\tVectorNegate( surf->plane->normal, surfaceNormal );\n\t\telse VectorCopy( surf->plane->normal, surfaceNormal );\n\t}\n\n\tVectorNormalize2( surfaceNormal, textureSpaceBasis[2] );\n#if 0\n\tif( FBitSet( flags, FDECAL_CUSTOM ))\n\t{\n\t\tvec3_t\tpSAxis = { 1, 0, 0 };\n\n\t\t// T = S cross N\n\t\tCrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] );\n\n\t\t// Name sure they aren't parallel or antiparallel\n\t\t// In that case, fall back to the normal algorithm.\n\t\tif( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 )\n\t\t{\n\t\t\t// S = N cross T\n\t\t\tCrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] );\n\n\t\t\tVectorNormalizeFast( textureSpaceBasis[0] );\n\t\t\tVectorNormalizeFast( textureSpaceBasis[1] );\n\t\t\treturn;\n\t\t}\n\t\t// Fall through to the standard algorithm for parallel or antiparallel\n\t}\n#endif\n\tVectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] );\n\tVectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] );\n}\n\nstatic void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tint\twidth, height;\n\n\t// Compute the non-scaled decal basis\n\tR_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis );\n\tR_GetDecalDimensions( texture, &width, &height );\n\n\t// world width of decal = ptexture->width / pDecal->scale\n\t// world height of decal = ptexture->height / pDecal->scale\n\t// scale is inverse, scales world space to decal u/v space [0,1]\n\t// OPTIMIZE: Get rid of these divides\n\tdecalWorldScale[0] = (float)pDecal->scale / width;\n\tdecalWorldScale[1] = (float)pDecal->scale / height;\n\n\tVectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] );\n\tVectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] );\n}\n\n// Build the initial list of vertices from the surface verts into the global array, 'verts'.\nstatic void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf,\tvec3_t textureSpaceBasis[3], float *verts )\n{\n\tfloat\t*v;\n\tint\ti;\n\n\tfor( i = 0, v = surf->polys->verts[0]; i < surf->polys->numverts; i++, v += VERTEXSIZE, verts += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, verts ); // copy model space coordinates\n\t\tverts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f;\n\t\tverts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f;\n\t\tverts[5] = verts[6] = 0.0f;\n\t}\n}\n\n// Figure out where the decal maps onto the surface.\nstatic void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] )\n{\n\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// Generate texture coordinates for each vertex in decal s,t space\n\t// probably should pre-generate this, store it and use it for decal-decal collisions\n\t// as in R_DecalsIntersect()\n\tpDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] );\n\tpDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] );\n}\n\n// Quick and dirty sutherland Hodgman clipper\n// Clip polygon to decal in texture space\n// JAY: This code is lame, change it later.  It does way too much work per frame\n// It can be made to recursively call the clipping code and only copy the vertex list once\nstatic int R_ClipInside( float *vert, int edge )\n{\n\tswitch( edge )\n\t{\n\tcase LEFT_EDGE:\n\t\tif( vert[3] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase RIGHT_EDGE:\n\t\tif( vert[3] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase TOP_EDGE:\n\t\tif( vert[4] > 0.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\tcase BOTTOM_EDGE:\n\t\tif( vert[4] < 1.0f )\n\t\t\treturn 1;\n\t\treturn 0;\n\t}\n\treturn 0;\n}\n\nstatic void R_ClipIntersect( float *one, float *two, float *out, int edge )\n{\n\tfloat\tt;\n\n\t// t is the parameter of the line between one and two clipped to the edge\n\t// or the fraction of the clipped point between one & two\n\t// vert[0], vert[1], vert[2] is X, Y, Z\n\t// vert[3] is u\n\t// vert[4] is v\n\t// vert[5] is lightmap u\n\t// vert[6] is lightmap v\n\n\tif( edge < TOP_EDGE )\n\t{\n\t\tif( edge == LEFT_EDGE )\n\t\t{\n\t\t\t// left\n\t\t\tt = ((one[3] - 0.0f) / (one[3] - two[3]));\n\t\t\tout[3] = out[5] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// right\n\t\t\tt = ((one[3] - 1.0f) / (one[3] - two[3]));\n\t\t\tout[3] = out[5] = 1.0f;\n\t\t}\n\n\t\tout[4] = one[4] + (two[4] - one[4]) * t;\n\t\tout[6] = one[6] + (two[6] - one[6]) * t;\n\t}\n\telse\n\t{\n\t\tif( edge == TOP_EDGE )\n\t\t{\n\t\t\t// top\n\t\t\tt = ((one[4] - 0.0f)  / (one[4] - two[4]));\n\t\t\tout[4] = out[6] = 0.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// bottom\n\t\t\tt = ((one[4] - 1.0f) / (one[4] - two[4]));\n\t\t\tout[4] = out[6] = 1.0f;\n\t\t}\n\n\t\tout[3] = one[3] + (two[3] - one[3]) * t;\n\t\tout[5] = one[5] + (two[4] - one[5]) * t;\n\t}\n\n\tVectorLerp( one, t, two, out );\n}\n\nstatic int SHClip( float *vert, int vertCount, float *out, int edge )\n{\n\tint\tj, outCount;\n\tfloat\t*s, *p;\n\n\toutCount = 0;\n\n\ts = &vert[(vertCount - 1) * VERTEXSIZE];\n\n\tfor( j = 0; j < vertCount; j++ )\n\t{\n\t\tp = &vert[j * VERTEXSIZE];\n\n\t\tif( R_ClipInside( p, edge ))\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\t// Add a vertex and advance out to next vertex\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tR_ClipIntersect( s, p, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\n\t\t\t\tmemcpy( out, p, sizeof( float ) * VERTEXSIZE );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( R_ClipInside( s, edge ))\n\t\t\t{\n\t\t\t\tR_ClipIntersect( p, s, out, edge );\n\t\t\t\tout += VERTEXSIZE;\n\t\t\t\toutCount++;\n\t\t\t}\n\t\t}\n\n\t\ts = p;\n\t}\n\n\treturn outCount;\n}\n\nstatic float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount )\n{\n\tfloat\t*pOutVerts = g_DecalClipVerts[0];\n\tint\toutCount;\n\n\t// clip the polygon to the decal texture space\n\toutCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE );\n\toutCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE );\n\toutCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE );\n\n\tif( pVertCount )\n\t\t*pVertCount = outCount;\n\n\treturn pOutVerts;\n}\n\n//-----------------------------------------------------------------------------\n// Generate clipped vertex list for decal pdecal projected onto polygon psurf\n//-----------------------------------------------------------------------------\nstatic float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount )\n{\n\tfloat\tdecalWorldScale[2];\n\tvec3_t\ttextureSpaceBasis[3];\n\n\t// figure out where the decal maps onto the surface.\n\tR_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale );\n\n\t// build the initial list of vertices from the surface verts.\n\tR_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] );\n\n\treturn R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount );\n}\n\n// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf\nstatic void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount )\n{\n\tfloat\t\tsample_size;\n\tint\t\tj;\n\n\tsample_size = gEngine.Mod_SampleSizeForFace( surf ); \n\n\tfor( j = 0; j < vertCount; j++, v += VERTEXSIZE )\n\t{\n\t\t// lightmap texture coordinates\n\t\tR_LightmapCoord( v, surf, sample_size, &v[5] );\n\t}\n}\n\n// Check for intersecting decals on this surface\nstatic decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount )\n{\n\tint\t\ttexture;\n\tdecal_t\t\t*plast, *pDecal;\n\tvec3_t\t\tdecalExtents[2];\n\tfloat\t\tlastArea = 2;\n\tint\t\tmapSize[2];\n\n\tplast = NULL;\n\t*pcount = 0;\n\n\t// (Same as R_SetupDecalClip).\n\ttexture = decalinfo->m_iTexture;\n\n\t// precalculate the extents of decalinfo's decal in world space.\n\tR_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] );\n\tVectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] );\n\tVectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] );\n\n\tpDecal = surf->pdecals;\n\n\twhile( pDecal )\n\t{\n\t\ttexture = pDecal->texture;\n\n\t\t// Don't steal bigger decals and replace them with smaller decals\n\t\t// Don't steal permanent decals\n\t\tif( !FBitSet( pDecal->flags, FDECAL_PERMANENT ))\n\t\t{\n\t\t\tvec3_t\ttestBasis[3];\n\t\t\tvec3_t\ttestPosition[2];\n\t\t\tfloat\ttestWorldScale[2];\n\t\t\tvec2_t\tvDecalMin, vDecalMax;\n\t\t\tvec2_t\tvUnionMin, vUnionMax;\n\n\t\t\tR_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale );\n\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\t// Here, we project the min and max extents of the decal that got passed in into\n\t\t\t// this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were\n\t\t\t// clipping a triangle into pDecal's clip space.\n\t\t\tVector2Set( vDecalMin,\n\t\t\t\tDotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\tDotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] );\n\t\t\tVectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] );\n\n\t\t\tVector2Set( vDecalMax,\n\t\t\t\tDotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f,\n\t\t\t\tDotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f );\n\n\t\t\t// Now figure out the part of the projection that intersects pDecal's\n\t\t\t// clip box [0,0,1,1].\n\t\t\tVector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 ));\n\t\t\tVector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 ));\n\n\t\t\tif( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 )\n\t\t\t{\n\t\t\t\t// Figure out how much of this intersects the (0,0) - (1,1) bbox.\n\t\t\t\tfloat\tflArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]);\n\n\t\t\t\tif( flArea > 0.6f )\n\t\t\t\t{\n\t\t\t\t\t*pcount += 1;\n\n\t\t\t\t\tif( !plast || flArea <= lastArea )\n\t\t\t\t\t{\n\t\t\t\t\t\tplast = pDecal;\n\t\t\t\t\t\tlastArea =  flArea;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpDecal = pDecal->pnext;\n\t}\n\treturn plast;\n}\n\n/*\n====================\nR_DecalCreatePoly\n\ncreates mesh for decal on first rendering\n====================\n*/\nstatic glpoly2_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf )\n{\n\tint\t\tlnumverts;\n\tglpoly2_t\t*poly;\n\tfloat\t\t*v;\n\tint\t\ti;\n\n\tif( pdecal->polys )\t// already created?\n\t\treturn pdecal->polys;\n\n\tv = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts );\n\tif( !lnumverts ) return NULL;\t// probably this never happens\n\n\t// allocate glpoly\n\tpoly = Mem_Calloc( vk_core.pool, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float ));\n\tpoly->next = pdecal->polys;\n\tpoly->flags = surf->flags;\n\tpdecal->polys = poly;\n\tpoly->numverts = lnumverts;\n\n\tfor( i = 0; i < lnumverts; i++, v += VERTEXSIZE )\n\t{\n\t\tVectorCopy( v, poly->verts[i] );\n\t\tpoly->verts[i][3] = v[3];\n\t\tpoly->verts[i][4] = v[4];\n\t\tpoly->verts[i][5] = v[5];\n\t\tpoly->verts[i][6] = v[6];\n\t}\n\n\treturn poly;\n}\n\n// Add the decal to the surface's list of decals.\nstatic void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo )\n{\n\tdecal_t\t*pold;\n\n\tpdecal->pnext = NULL;\n\tpold = surf->pdecals;\n\n\tif( pold )\n\t{\n\t\twhile( pold->pnext )\n\t\t\tpold = pold->pnext;\n\t\tpold->pnext = pdecal;\n\t}\n\telse\n\t{\n\t\tsurf->pdecals = pdecal;\n\t}\n\n\t// tag surface\n\tpdecal->psurface = surf;\n\n\t// at this point decal are linked with surface\n\t// and will be culled, drawing and sorting\n\t// together with surface\n\n\t// alloc clipped poly for decal\n\tR_DecalCreatePoly( decalinfo, pdecal, surf );\n}\n\nstatic void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y )\n{\n\tdecal_t\t*pdecal, *pold;\n\tint\tcount, vertCount;\n\n\tif( !surf ) return;\t// ???\n\n\tpold = R_DecalIntersect( decalinfo, surf, &count );\n\tif( count < MAX_OVERLAP_DECALS ) pold = NULL;\n\n\tpdecal = R_DecalAlloc( pold );\n\tif( !pdecal ) return; // r_decals == 0 ???\n\n\tpdecal->flags = decalinfo->m_Flags;\n\n\tVectorCopy( decalinfo->m_Position, pdecal->position );\n\n\tpdecal->dx = x;\n\tpdecal->dy = y;\n\n\t// set scaling\n\tpdecal->scale = decalinfo->m_scale;\n\tpdecal->entityIndex = decalinfo->m_Entity;\n\tpdecal->texture = decalinfo->m_iTexture;\n\n\t// check to see if the decal actually intersects the surface\n\t// if not, then remove the decal\n\tR_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount );\n\n\tif( !vertCount )\n\t{\n\t\tR_DecalUnlink( pdecal );\n\t\treturn;\n\t}\n\n\t// add to the surface's list\n\tR_AddDecalToSurface( pdecal, surf, decalinfo );\n}\n\nstatic void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )\n{\n\t// get the texture associated with this surface\n\tmtexinfo_t\t*tex = surf->texinfo;\n\tdecal_t\t\t*decal = surf->pdecals;\n\tvec4_t\t\ttextureU, textureV;\n\tfloat\t\ts, t, w, h;\n\tconnstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE );\n\n\t// we in restore mode\n\tif( state == ca_connected || state == ca_validate )\n\t{\n\t\t// NOTE: we may have the decal on this surface that come from another level.\n\t\t// check duplicate with same position and texture\n\t\twhile( decal != NULL )\n\t\t{\n\t\t\tif( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture )\n\t\t\t\treturn; // decal already exists, don't place it again\n\t\t\tdecal = decal->pnext;\n\t\t}\n\t}\n\n\tVector4Copy( tex->vecs[0], textureU );\n\tVector4Copy( tex->vecs[1], textureV );\n\n\t// project decal center into the texture space of the surface\n\ts = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0];\n\tt = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1];\n\n\t// Determine the decal basis (measured in world space)\n\t// Note that the decal basis vectors 0 and 1 will always lie in the same\n\t// plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits.\n\tR_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis );\n\n\t// Compute an effective width and height (axis aligned) in the parent texture space\n\t// How does this work? decalBasis[0] represents the u-direction (width)\n\t// of the decal measured in world space, decalBasis[1] represents the\n\t// v-direction (height) measured in world space.\n\t// textureVecsTexelsPerWorldUnits[0] represents the u direction of\n\t// the surface's texture space measured in world space (with the appropriate\n\t// scale factor folded in), and textureVecsTexelsPerWorldUnits[1]\n\t// represents the texture space v direction. We want to find the dimensions (w,h)\n\t// of a square measured in texture space, axis aligned to that coordinate system.\n\t// All we need to do is to find the components of the decal edge vectors\n\t// (decalWidth * decalBasis[0], decalHeight * decalBasis[1])\n\t// in texture coordinates:\n\n\tw = fabs( decalinfo->m_decalWidth  * DotProduct( textureU, decalinfo->m_Basis[0] )) +\n\t    fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] ));\n\n\th = fabs( decalinfo->m_decalWidth  * DotProduct( textureV, decalinfo->m_Basis[0] )) +\n\t    fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] ));\n\n\t// move s,t to upper left corner\n\ts -= ( w * 0.5f );\n\tt -= ( h * 0.5f );\n\n\t// Is this rect within the surface? -- tex width & height are unsigned\n\tif( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h))\n\t{\n\t\treturn; // nope\n\t}\n\n\t// stamp it\n\tR_DecalCreate( decalinfo, surf, s, t );\n}\n\n//-----------------------------------------------------------------------------\n// iterate over all surfaces on a node, looking for surfaces to decal\n//-----------------------------------------------------------------------------\nstatic void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\t// iterate over all surfaces in the node\n\tmsurface_t\t*surf;\n\tint\t\ti;\n\tint firstsurface, numsurfaces;\n\n\tfirstsurface = node_firstsurface( node, model );\n\tnumsurfaces  = node_numsurfaces( node, model );\n\n\tsurf = model->surfaces + firstsurface;\n\n\tfor( i = 0; i < numsurfaces; i++, surf++ )\n\t{\n\t\t// never apply decals on the water or sky surfaces\n\t\tif( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))\n\t\t\tcontinue;\n\n\t\t// if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled ) // TODO is this needed?\n\t\t// \tcontinue;\n\n\t\tR_DecalSurface( surf, decalinfo );\n\t}\n}\n\n//-----------------------------------------------------------------------------\n// Recursive routine to find surface to apply a decal to.  World coordinates of\n// the decal are passed in r_recalpos like the rest of the engine.  This should\n// be called through R_DecalShoot()\n//-----------------------------------------------------------------------------\nstatic void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )\n{\n\tmplane_t\t*splitplane;\n\tfloat\tdist;\n\tmnode_t *children[2];\n\n\tassert( node != NULL );\n\n\tif( node->contents < 0 )\n\t{\n\t\t// hit a leaf\n\t\treturn;\n\t}\n\n\tsplitplane = node->plane;\n\tdist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;\n\tnode_children( children, node, model );\n\n\t// This is arbitrarily set to 10 right now. In an ideal world we'd have the\n\t// exact surface but we don't so, this tells me which planes are \"sort of\n\t// close\" to the gunshot -- the gunshot is actually 4 units in front of the\n\t// wall (see dlls\\weapons.cpp). We also need to check to see if the decal\n\t// actually intersects the texture space of the surface, as this method tags\n\t// parallel surfaces in the same node always.\n\t// JAY: This still tags faces that aren't correct at edges because we don't\n\t// have a surface normal\n\tif( dist > decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t}\n\telse if( dist < -decalinfo->m_Size )\n\t{\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n\telse\n\t{\n\t\tif( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )\n\t\t\tR_DecalNodeSurfaces( model, node, decalinfo );\n\n\t\tR_DecalNode( model, children[0], decalinfo );\n\t\tR_DecalNode( model, children[1], decalinfo );\n\t}\n}\n\n// Shoots a decal onto the surface of the BSP.  position is the center of the decal in world coords\nvoid R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale )\n{\n\tdecalinfo_t\tdecalInfo;\n\tcl_entity_t\t*ent = NULL;\n\tmodel_t\t\t*model = NULL;\n\tint\t\twidth, height;\n\thull_t\t\t*hull;\n\n\tif( textureIndex <= 0 || textureIndex >= MAX_TEXTURES )\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Decal has invalid texture!\\n\" );\n\t\treturn;\n\t}\n\n\tif( entityIndex > 0 )\n\t{\n\t\tent = R_GetEntityByIndex(entityIndex);\n\n\t\tif( modelIndex > 0 ) model = R_ModelHandle( modelIndex );\n\t\telse if( ent != NULL ) model = R_ModelHandle( ent->curstate.modelindex );\n\t\telse return;\n\t}\n\telse if( modelIndex > 0 )\n\t\tmodel = R_ModelHandle( modelIndex );\n\telse model = R_ModelHandle( 1 );\n\n\tif( !model ) return;\n\n\tif( model->type != mod_brush )\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Decals must hit mod_brush!\\n\" );\n\t\treturn;\n\t}\n\n\tdecalInfo.m_pModel = model;\n\thull = &model->hulls[0];\t// always use #0 hull\n\n\t// NOTE: all the decals at 'first shoot' placed into local space of parent entity\n\t// and won't transform again on a next restore, levelchange etc\n\tif( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE ))\n\t{\n\t\tvec3_t\tpos_l;\n\n\t\t// transform decal position in local bmodel space\n\t\tif( !VectorIsNull( ent->angles ))\n\t\t{\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tMatrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f );\n\t\t\tMatrix4x4_VectorITransform( matrix, pos, pos_l );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorSubtract( pos, ent->origin, pos_l );\n\t\t}\n\n\t\tVectorCopy( pos_l, decalInfo.m_Position );\n\t\t// decal position moved into local space\n\t\tSetBits( flags, FDECAL_LOCAL_SPACE );\n\t}\n\telse\n\t{\n\t\t// already in local space\n\t\tVectorCopy( pos, decalInfo.m_Position );\n\t}\n\n\t// this decal must use landmark for correct transition\n\t// because their model exist only in world-space\n\tif( !FBitSet( model->flags, MODEL_HAS_ORIGIN ))\n\t\tSetBits( flags, FDECAL_USE_LANDMARK );\n\n\t// more state used by R_DecalNode()\n\tdecalInfo.m_iTexture = textureIndex;\n\tdecalInfo.m_Entity = entityIndex;\n\tdecalInfo.m_Flags = flags;\n\n\tR_GetDecalDimensions( textureIndex, &width, &height );\n\tdecalInfo.m_Size = width >> 1;\n\tif(( height >> 1 ) > decalInfo.m_Size )\n\t\tdecalInfo.m_Size = height >> 1;\n\n\tdecalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE );\n\n\t// compute the decal dimensions in world space\n\tdecalInfo.m_decalWidth = width / decalInfo.m_scale;\n\tdecalInfo.m_decalHeight = height / decalInfo.m_scale;\n\n\tR_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo );\n}\n\n// Build the vertex list for a decal on a surface and clip it to the surface.\n// This is a template so it can work on world surfaces and dynamic displacement\n// triangles the same way.\nfloat *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount )\n{\n\tglpoly2_t\t*p = pDecal->polys;\n\tint\ti, count;\n\tfloat\t*v, *v2;\n\n\tif( p )\n\t{\n\t\tv = g_DecalClipVerts[0];\n\t\tcount = p->numverts;\n\t\tv2 = p->verts[0];\n\n\t\t// if we have mesh so skip clipping and just copy vertexes out (perf)\n\t\tfor( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE )\n\t\t{\n\t\t\tVectorCopy( v2, v );\n\t\t\tv[3] = v2[3];\n\t\t\tv[4] = v2[4];\n\t\t\tv[5] = v2[5];\n\t\t\tv[6] = v2[6];\n\t\t}\n\n\t\t// restore pointer\n\t\tv = g_DecalClipVerts[0];\n\t}\n\telse\n\t{\n\t\tv = R_DecalVertsClip( pDecal, surf, texture, &count );\n\t\tR_DecalVertsLight( v, surf, count );\n\t}\n\n\tif( outCount )\n\t\t*outCount = count;\n\n\treturn v;\n}\n\nvoid R_DrawSingleDecal( decal_t *pDecal, msurface_t *fa )\n{\n\tfloat\t*v;\n\tint\ti, numVerts;\n\n\tv = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts );\n\n\tif( !numVerts )\n\t\treturn;\n\n\tTriSetTexture( pDecal->texture );\n\tTriColor4f( 1, 1, 1, 1 );\n\n\tTriBegin( TRI_POLYGON );\n\n\t// TODO: do decals offset by vulkan depth offset and use cvar\n\tvec3_t normal, offset, bumpedPos, worldPos;\n\tfloat sign = (pDecal->psurface->flags & SURF_PLANEBACK) ? 1.0f : -1.0f;\n\tVectorScale(pDecal->psurface->plane->normal, sign, normal);\n\tVectorScale(normal, DECAL_DEPTH_OFFSET, offset);\n\n\tfor( i = 0; i < numVerts; i++, v += VERTEXSIZE )\n\t{\n\t\tVectorAdd(v, offset, bumpedPos);\n\t\tMatrix3x4_VectorTransform(gDecalTransform, bumpedPos, worldPos);\n\n\t\tTriTexCoord2f( v[3], v[4] );\n\t\tTriLightmapCoord2f( v[5], v[6] );\n\t\tTriVertex3fv( worldPos );\n\t\tTriNormal3fv( normal );\n\t}\n\n\tconst vec4_t color = { 1, 1, 1, 1 }; // TODO: get decal color\n\tTriEndEx( color, \"single decal\" );\n}\n\nstatic int R_DecalEmitTrianglesToBatch( decal_t *pDecal, msurface_t *fa )\n{\n\tfloat *v;\n\tint i, numVerts;\n\tvec3_t normal, offset, bumpedPos, worldPos;\n\tfloat sign;\n\n\tv = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts );\n\tif( numVerts < 3 )\n\t\treturn 0;\n\n\tsign = (pDecal->psurface->flags & SURF_PLANEBACK) ? 1.0f : -1.0f;\n\tVectorScale( pDecal->psurface->plane->normal, sign, normal );\n\tVectorScale( normal, DECAL_DEPTH_OFFSET, offset );\n\n\tfor( i = 1; i < numVerts - 1; ++i )\n\t{\n\t\tfloat *const v0 = v;\n\t\tfloat *const v1 = v + i * VERTEXSIZE;\n\t\tfloat *const v2 = v + ( i + 1 ) * VERTEXSIZE;\n\t\tfloat *const tri[3] = { v0, v1, v2 };\n\n\t\tfor( int k = 0; k < 3; ++k )\n\t\t{\n\t\t\tfloat *const tv = tri[k];\n\t\t\tVectorAdd( tv, offset, bumpedPos );\n\t\t\tMatrix3x4_VectorTransform( gDecalTransform, bumpedPos, worldPos );\n\n\t\t\tTriTexCoord2f( tv[3], tv[4] );\n\t\t\tTriLightmapCoord2f( tv[5], tv[6] );\n\t\t\tTriVertex3fv( worldPos );\n\t\t\tTriNormal3fv( normal );\n\t\t}\n\t}\n\n\treturn ( numVerts - 2 ) * 3;\n}\n\nstatic int R_DecalDrawItemCompare( const void *a, const void *b )\n{\n\tconst vk_decal_draw_item_t *da = (const vk_decal_draw_item_t *)a;\n\tconst vk_decal_draw_item_t *db = (const vk_decal_draw_item_t *)b;\n\n\tif( da->lightmap < db->lightmap ) return -1;\n\tif( da->lightmap > db->lightmap ) return 1;\n\tif( da->texture < db->texture ) return -1;\n\tif( da->texture > db->texture ) return 1;\n\treturn 0;\n}\n\nstatic void R_DecalQueuePush( decal_t *decal, msurface_t *fa )\n{\n\tif( !decal || !decal->texture )\n\t\treturn;\n\n\tif( g_decal_queue.count >= MAX_RENDER_DECALS )\n\t{\n\t\tWARN_THROTTLED( 10, \"decal queue overflow, dropping decals\\n\" );\n\t\treturn;\n\t}\n\n\tvk_decal_draw_item_t *const item = &g_decal_queue.items[g_decal_queue.count++];\n\titem->decal = decal;\n\titem->surf = fa;\n\titem->lightmap = fa->lightmaptexturenum + 1;\n\titem->texture = decal->texture;\n\tMatrix4x4_Copy( item->transform, gDecalTransform );\n}\n\nvoid R_DecalsFlush( void )\n{\n\tint current_lightmap = -1;\n\tint current_texture = -1;\n\tint batch_vertices = 0;\n\tqboolean batch_open = false;\n\tconst vec4_t batch_color = { 1, 1, 1, 1 };\n\n\tif( g_decal_queue.count <= 0 )\n\t\treturn;\n\n\tqsort( g_decal_queue.items, g_decal_queue.count, sizeof( g_decal_queue.items[0] ), R_DecalDrawItemCompare );\n\n\tTriRenderType( kVkRenderType_Decal );\n\n\tfor( int i = 0; i < g_decal_queue.count; ++i )\n\t{\n\t\tconst vk_decal_draw_item_t *const item = &g_decal_queue.items[i];\n\t\tconst qboolean split =\n\t\t\tcurrent_lightmap != item->lightmap ||\n\t\t\tcurrent_texture != item->texture ||\n\t\t\tbatch_vertices >= DECAL_BATCH_VERTEX_BUDGET;\n\n\t\tif( split )\n\t\t{\n\t\t\tif( batch_open )\n\t\t\t\tTriEndEx( batch_color, \"batched decals\" );\n\n\t\t\tif( current_lightmap != item->lightmap )\n\t\t\t{\n\t\t\t\tTriSetLightmap( item->lightmap );\n\t\t\t\tcurrent_lightmap = item->lightmap;\n\t\t\t}\n\n\t\t\tTriSetTexture( item->texture );\n\t\t\tTriColor4f( 1, 1, 1, 1 );\n\t\t\tTriBegin( TRI_TRIANGLES );\n\t\t\tcurrent_texture = item->texture;\n\t\t\tbatch_vertices = 0;\n\t\t\tbatch_open = true;\n\t\t\t++g_decal_stats.batches_total;\n\t\t}\n\n\t\tMatrix4x4_Copy( gDecalTransform, item->transform );\n\t\t{\n\t\t\tconst int emitted = R_DecalEmitTrianglesToBatch( item->decal, item->surf );\n\t\t\tif( emitted > 0 )\n\t\t\t{\n\t\t\t\tbatch_vertices += emitted;\n\t\t\t\t++g_decal_stats.decals_total;\n\t\t\t}\n\t\t}\n\t}\n\n\tif( batch_open )\n\t\tTriEndEx( batch_color, \"batched decals\" );\n\n\tTriSetLightmap( 0 );\n\tTriRenderMode( kRenderNormal );\n\tg_decal_queue.count = 0;\n}\n\nvoid R_DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse )\n{\n\tdecal_t *p;\n\t(void)single;\n\n\tif( !fa || !fa->pdecals )\n\t\treturn;\n\n\tif( reverse )\n\t{\n\t\tdecal_t *list[1024];\n\t\tint i, count;\n\n\t\tfor( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext )\n\t\t\tif( p->texture ) list[count++] = p;\n\n\t\tfor( i = count - 1; i >= 0; --i )\n\t\t\tR_DecalQueuePush( list[i], fa );\n\t}\n\telse\n\t{\n\t\tfor( p = fa->pdecals; p; p = p->pnext )\n\t\t\tR_DecalQueuePush( p, fa );\n\t}\n}\n\n/*\n=============================================================\n\n  DECALS SERIALIZATION\n\n=============================================================\n*/\nstatic qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry )\n{\n\tif( !pdecal || !( pdecal->psurface ))\n\t\treturn false;\n\n\tVectorCopy( pdecal->position, entry->position );\n\tentry->entityIndex = pdecal->entityIndex;\n\n\t// Grab surface plane equation\n\t// TODO: calculate pdecal->psurface->plane->normal\n\tif (pdecal->psurface->plane->normal) {\n\t\tif( pdecal->psurface->flags & SURF_PLANEBACK )\n\t\t\tVectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\t\telse VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal );\n\t}\n\n\treturn true;\n}\n\n//-----------------------------------------------------------------------------\n// Purpose:\n// Input  : *pList -\n//\t\t\tcount -\n// Output : static int\n//-----------------------------------------------------------------------------\nstatic int DecalListAdd( decallist_t *pList, int count )\n{\n\tvec3_t\t\ttmp;\n\tdecallist_t\t*pdecal;\n\tint\t\ti;\n\n\tpdecal = pList + count;\n\n\tfor( i = 0; i < count; i++ )\n\t{\n\t\tif( !Q_strcmp( pdecal->name, pList[i].name ) &&  pdecal->entityIndex == pList[i].entityIndex )\n\t\t{\n\t\t\tVectorSubtract( pdecal->position, pList[i].position, tmp );\t// Merge\n\n\t\t\tif( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE )\n\t\t\t\treturn count;\n\t\t}\n\t}\n\n\t// this is a new decal\n\treturn count + 1;\n}\n\nstatic int DecalDepthCompare( const void *a, const void *b )\n{\n\tconst decallist_t\t*elem1, *elem2;\n\n\telem1 = (const decallist_t *)a;\n\telem2 = (const decallist_t *)b;\n\n\tif( elem1->depth > elem2->depth )\n\t\treturn 1;\n\tif( elem1->depth < elem2->depth )\n\t\treturn -1;\n\n\treturn 0;\n}\n\n//-----------------------------------------------------------------------------\n// Purpose: Called by CSaveRestore::SaveClientState\n// Input  : *pList -\n// Output : int\n//-----------------------------------------------------------------------------\nint R_CreateDecalList( decallist_t *pList )\n{\n\tint\ttotal = 0;\n\tint\ti, depth;\n\n\tif( WORLDMODEL )\n\t{\n\t\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t\t{\n\t\t\tdecal_t\t*decal = &gDecalPool[i];\n\t\t\tdecal_t\t*pdecals;\n\n\t\t\t// decal is in use and is not a custom decal\n\t\t\tif( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE ))\n\t\t\t\t continue;\n\n\t\t\t// compute depth\n\t\t\tdepth = 0;\n\t\t\tpdecals = decal->psurface->pdecals;\n\n\t\t\twhile( pdecals && pdecals != decal )\n\t\t\t{\n\t\t\t\tdepth++;\n\t\t\t\tpdecals = pdecals->pnext;\n\t\t\t}\n\n\t\t\tpList[total].depth = depth;\n\t\t\tpList[total].flags = decal->flags;\n\t\t\tpList[total].scale = decal->scale;\n\n\t\t\tR_DecalUnProject( decal, &pList[total] );\n\n\t\t\tCOM_FileBase( R_TextureGetNameByIndex( decal->texture ), pList[total].name, sizeof( pList[total].name ));\n\n\t\t\t// check to see if the decal should be added\n\t\t\ttotal = DecalListAdd( pList, total );\n\t\t}\n\n\t\tif( gEngine.drawFuncs->R_CreateStudioDecalList )\n\t\t{\n\t\t\ttotal += gEngine.drawFuncs->R_CreateStudioDecalList( pList, total );\n\t\t}\n\t}\n\n\t// sort the decals lowest depth first, so they can be re-applied in order\n\tqsort( pList, total, sizeof( decallist_t ), DecalDepthCompare );\n\n\treturn total;\n}\n\n/*\n===============\nR_DecalRemoveAll\n\nremove all decals with specified texture\n===============\n*/\nvoid R_DecalRemoveAll( int textureIndex )\n{\n\tdecal_t\t*pdecal;\n\tint\ti;\n\n\tif( textureIndex < 0 || textureIndex >= MAX_TEXTURES )\n\t\treturn; // out of bounds\n\n\tfor( i = 0; i < gDecalCount; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\n\t\t// don't remove permanent decals\n\t\tif( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT ))\n\t\t\tcontinue;\n\n\t\tif( !textureIndex || ( pdecal->texture == textureIndex ))\n\t\t\tR_DecalUnlink( pdecal );\n\t}\n}\n\n/*\n===============\nR_EntityRemoveDecals\n\nremove all decals from specified entity\n===============\n*/\nvoid R_EntityRemoveDecals( model_t *mod )\n{\n\tmsurface_t\t*psurf;\n\tdecal_t\t\t*p;\n\tint\t\ti;\n\n\tif( !mod || mod->type != mod_brush )\n\t\treturn;\n\n\tpsurf = &mod->surfaces[mod->firstmodelsurface];\n\tfor( i = 0; i < mod->nummodelsurfaces; i++, psurf++ )\n\t{\n\t\tfor( p = psurf->pdecals; p; p = p->pnext )\n\t\t\tR_DecalUnlink( p );\n\t}\n}\n\n/*\n===============\nR_ClearAllDecals\n\nremove all decals from anything\nused for full decals restart\n===============\n*/\nvoid R_ClearAllDecals( void )\n{\n\tdecal_t\t*pdecal;\n\tint\ti;\n\n\t// because gDecalCount may be zeroed after recach the decal limit\n\tfor( i = 0; i < MAX_RENDER_DECALS; i++ )\n\t{\n\t\tpdecal = &gDecalPool[i];\n\t\tR_DecalUnlink( pdecal );\n\t}\n\n\tif( gEngine.drawFuncs->R_ClearStudioDecals )\n\t{\n\t\tgEngine.drawFuncs->R_ClearStudioDecals();\n\t}\n}\n\nvoid R_SetDecalsTransform( const matrix4x4* transform )\n{\n\tif (!transform) {\n\t\tMatrix4x4_LoadIdentity(gDecalTransform);\n\t} else {\n\t\tMatrix4x4_Copy(gDecalTransform, transform);\n\t}\n}\n"
  },
  {
    "path": "ref/vk/r_decals.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\nvoid R_ClearDecals( void );\nvoid R_DecalsFrameBegin( void );\nvoid R_DecalsFlush( void );\nvoid R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale );\nfloat *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount );\nvoid R_DrawSingleDecal( decal_t *pDecal, msurface_t *fa );\nvoid R_DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse );\nvoid R_DecalRemoveAll( int texture );\nint R_CreateDecalList( struct decallist_s *pList );\nvoid R_ClearAllDecals( void );\nvoid R_EntityRemoveDecals( struct model_s *mod );\nvoid R_SetDecalsTransform( const matrix4x4* transform );\n"
  },
  {
    "path": "ref/vk/r_speeds.c",
    "content": "#include \"r_speeds.h\"\n#include \"vk_overlay.h\"\n#include \"vk_framectl.h\"\n#include \"vk_cvar.h\"\n#include \"vk_logs.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"std/stringview.h\"\n\n#include \"std/profiler.h\"\n\n#include \"crclib.h\" // CRC32 for stable random colors\n#include \"xash3d_mathlib.h\" // Q_min\n#include <limits.h>\n\n#define MAX_GPU_SCOPES 64\n\n#define MAX_SPEEDS_MESSAGE (1024)\n#define MAX_SPEEDS_METRICS (512)\n#define TARGET_FRAME_TIME (1000.f / 60.f)\n#define MAX_GRAPHS 8\n#define MAX_COUNTERS_PER_SCOPE (32)\n\n#define MODULE_NAME \"speeds\"\n#define LOG_MODULE speeds\n\n// Valid bits for `r_speeds` argument:\nenum {\n\tSPEEDS_BIT_OFF = 0,       // `r_speeds 0` turns off all performance stats display\n\tSPEEDS_BIT_SIMPLE = 1,    // `r_speeds 1` displays only basic info about frame time\n\tSPEEDS_BIT_STATS = 2,     // `r_speeds 2` displays additional metrics, i.e. lights counts, dynamic geometry upload sizes, etc\n\tSPEEDS_BIT_GRAPHS = 4,     // `r_speeds 4` display instrumental metrics graphs, controlled by r_speeds_graphs var\n\tSPEEDS_BIT_FRAME = 8,     // `r_speeds 8` diplays details instrumental profiler flame graph\n\t// TODO SPEEDS_BIT_GPU_USAGE = 16, // `r_speeds 16` displays overall GPU usage stats\n\n\t// These bits can be combined, e.g. `r_speeds 9`, 8+1, will display 1: basic timing info and 8: frame graphs\n};\n\ntypedef struct {\n\tint *p_value;\n\tqboolean reset;\n\tchar name[64];\n\tconst char *var_name;\n\tconst char *src_file;\n\tint src_line;\n\tr_speeds_metric_type_t type;\n\tint low_watermark, high_watermark, max_value;\n\tint graph_index;\n} r_speeds_metric_t;\n\ntypedef struct {\n\tchar name[64];\n\tfloat *data;\n\tint data_count;\n\tint data_write;\n\tint source_metric; // can be -1 for missing metrics\n\n\tint height;\n\tint max_value; // Computed automatically every frame\n\trgba_t color;\n} r_speeds_graph_t;\n\ntypedef enum {\n\tkSpeedsMprintNone,\n\tkSpeedsMprintList,\n\tkSpeedsMprintTable\n} r_speeds_mprint_mode_t;\n\ntypedef struct {\n\tint initialized;\n\tint value;\n} Metacounter;\n\ntypedef struct {\n\tint initialized;\n\tint time_us; // automatically zeroed by metrics each frame\n\tMetacounter counters[MAX_COUNTERS_PER_SCOPE];\n} Metascope;\n\nstatic struct {\n\tcvar_t *r_speeds_graphs;\n\tcvar_t *r_speeds_graphs_width;\n\n\taprof_event_t *paused_events;\n\tint paused_events_count;\n\tint pause_requested;\n\n\tstruct {\n\t\tint glyph_width, glyph_height;\n\t\tfloat scale;\n\t} font_metrics;\n\n\tr_speeds_metric_t metrics[MAX_SPEEDS_METRICS];\n\tint metrics_count;\n\n\tr_speeds_graph_t graphs[MAX_GRAPHS];\n\tint graphs_count;\n\n\tstruct {\n\t\tint frame_time_us, cpu_time_us, cpu_wait_time_us, gpu_time_us;\n\t\tMetascope scopes[APROF_MAX_SCOPES];\n\t\tMetascope gpu_scopes[MAX_GPU_SCOPES];\n\t\tchar message[MAX_SPEEDS_MESSAGE];\n\n\t\tr_speeds_mprint_mode_t metrics_print_mode;\n\t\tstring metrics_print_filter;\n\t} frame;\n\n\t// Mask g_speeds_graphs cvar writes\n\tchar graphs_list[1024];\n} g_speeds;\n\nstatic void speedsStrcat( const char *msg ) {\n\tQ_strncat( g_speeds.frame.message, msg, sizeof( g_speeds.frame.message ));\n}\n\nstatic void speedsPrintf( const char *msg, ... ) FORMAT_CHECK(1);\nstatic void speedsPrintf( const char *msg, ... ) {\n\tva_list argptr;\n\tchar text[MAX_SPEEDS_MESSAGE];\n\n\tva_start( argptr, msg );\n\tQ_vsnprintf( text, sizeof( text ), msg, argptr );\n\tva_end( argptr );\n\n\tspeedsStrcat(text);\n}\n\nstatic void metricTypeSnprintf(char *buf, int buf_size, int value, r_speeds_metric_type_t type) {\n\tswitch (type) {\n\t\tcase kSpeedsMetricCount:\n\t\t\tQ_snprintf( buf, buf_size, \"%d\", value );\n\t\t\tbreak;\n\t\tcase kSpeedsMetricBytes:\n\t\t\tQ_strncpy( buf, Q_memprint( (float) value ), buf_size );\n\t\t\tbreak;\n\t\tcase kSpeedsMetricMicroseconds: {\n\t\t\tconst float msecs = value * 1e-3f; // us -> ms\n\t\t\tQ_snprintf( buf, buf_size, \"%.03fms\", msecs );\n\t\t\tbreak;\n\t\t}\n\t\tcase kSpeedsMetricPermyriad: {\n\t\t\tconst float percent = value * 1e-2f;\n\t\t\tQ_snprintf( buf, buf_size, \"%.02f%%\", percent );\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic float linearstep(float min, float max, float v) {\n\tif (v <= min) return 0;\n\tif (v >= max) return 1;\n\treturn (v - min) / (max - min);\n}\n\n// TODO better \"random\" colors for scope bars\nstatic uint32_t getHash(const char *s) {\n\tdword crc;\n\tCRC32_Init(&crc);\n\tCRC32_ProcessBuffer(&crc, s, Q_strlen(s));\n\treturn CRC32_Final(crc);\n}\n\nstatic void getColorForString( const char *s, rgba_t out_color ) {\n\tconst uint32_t hash = getHash(s);\n\tout_color[0] = (hash >> 16) & 0xff;\n\tout_color[1] = (hash >> 8) & 0xff;\n\tout_color[2] = hash & 0xff;\n}\n\nstatic void drawTimeBar(uint64_t begin_time_ns, float time_scale_ms, int64_t begin_ns, int64_t end_ns, int y, int height, const char *label, const rgba_t color) {\n\tconst float delta_ms = (end_ns - begin_ns) * 1e-6;\n\tconst int width = delta_ms  * time_scale_ms;\n\tconst int x = (begin_ns - begin_time_ns) * 1e-6 * time_scale_ms;\n\n\trgba_t text_color = {191 + color[0]/4, 191 + color[1]/4, 191 + color[2]/4, 255};\n\tCL_FillRGBA(kRenderTransAdd, x, y, width, height, color[0], color[1], color[2], color[3]);\n\n\t// Tweak this if scope names escape the block boundaries\n\tchar tmp[64];\n\ttmp[0] = '\\0';\n\tconst int glyph_width = g_speeds.font_metrics.glyph_width;\n\tconst int box_capped_length = Q_min(sizeof(tmp), width / glyph_width);\n\tif (box_capped_length > 0) {\n\t\tQ_snprintf(tmp, box_capped_length, \"%s %.3fms\", label, delta_ms);\n\t\tgEngine.Con_DrawString(x, y, tmp, text_color);\n\t}\n}\n\nstatic void updateMetascope(Metascope *metascope, const char* prefix, const aprof_scope_t* scope, uint64_t delta_ns) {\n\tif (!metascope->initialized) {\n\t\tconst qboolean reset = true;\n\t\tR_SpeedsRegisterMetric(&metascope->time_us, prefix, scope->name, kSpeedsMetricMicroseconds, reset,\n\t\t\tscope->name, scope->source_file, scope->source_line);\n\t\tmetascope->initialized = 1;\n\t}\n\n\tmetascope->time_us += delta_ns / 1000;\n}\n\ntypedef struct {\n\tint draw;\n\tconst aprof_event_t *events;\n\tuint64_t begin_time;\n\tfloat time_scale_ms;\n\tuint32_t begin;\n\tuint32_t end;\n\tint y;\n\tMetascope *scopes;\n\tconst aprof_scope_t *aprof_scopes;\n\tVIEW_DECLARE_CONST(aprof_counter_desc_t, aprof_counters);\n\tconst char *scope_name_prefix;\n\tint *out_active_time_us;\n\tint *out_wait_time_us;\n} ProcessAndDrawAprofEvents;\n\nstatic void processAndDrawAprofEvents(ProcessAndDrawAprofEvents args) {\n#define MAX_STACK_DEPTH 8\n\tstruct StackFrame {\n\t\tint scope_id;\n\t\tuint64_t begin_ns;\n\t\tuint64_t latest_child_end_ns;\n\t\tint overlaps;\n\t} stack[MAX_STACK_DEPTH];\n\tint depth = 0;\n\tint max_depth = 0;\n\n\tconst int bar_height = g_speeds.font_metrics.glyph_height;\n\n\tint under_waiting = 0;\n\tuint64_t active_time_ns = 0;\n\tuint64_t wait_time_ns = 0;\n\n\tfor (uint32_t begin = args.begin; begin != args.end; begin = (begin + 1) % APROF_EVENT_BUFFER_SIZE) {\n\t\tconst aprof_event_t event = args.events[begin];\n\t\tconst int event_type = APROF_EVENT_TYPE(event);\n\t\tconst uint64_t timestamp_ns = APROF_EVENT_TIMESTAMP(event);\n\t\tconst int scope_id = APROF_EVENT_SCOPE_ID(event);\n\t\tswitch (event_type) {\n\t\t\tcase APROF_EVENT_FRAME_BOUNDARY:\n\t\t\t\tactive_time_ns = 0;\n\t\t\t\twait_time_ns = 0;\n\t\t\t\tunder_waiting = 0;\n\t\t\t\tbreak;\n\n\t\t\tcase APROF_EVENT_SCOPE_BEGIN: {\n\t\t\t\t\tif (depth < MAX_STACK_DEPTH) {\n\t\t\t\t\t\tstack[depth] = (struct StackFrame) {\n\t\t\t\t\t\t\t.scope_id = scope_id,\n\t\t\t\t\t\t\t.begin_ns = timestamp_ns,\n\t\t\t\t\t\t\t.latest_child_end_ns = timestamp_ns,\n\t\t\t\t\t\t\t.overlaps = 0,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t++depth;\n\t\t\t\t\tif (max_depth < depth)\n\t\t\t\t\t\tmax_depth = depth;\n\n\t\t\t\t\tconst aprof_scope_t *const scope = args.aprof_scopes + scope_id;\n\t\t\t\t\tif (scope->flags & APROF_SCOPE_FLAG_WAIT)\n\t\t\t\t\t\tunder_waiting++;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\tcase APROF_EVENT_SCOPE_END: {\n\t\t\t\t\tASSERT(depth > 0);\n\t\t\t\t\t--depth;\n\n\t\t\t\t\tASSERT(scope_id >= 0);\n\t\t\t\t\tASSERT(scope_id < APROF_MAX_SCOPES);\n\n\t\t\t\t\tstruct StackFrame *const stack_frame = stack + depth;\n\t\t\t\t\tstruct StackFrame *const parent_frame = depth > 0 ? stack + depth - 1 : NULL;\n\n\t\t\t\t\tif (stack_frame->scope_id != scope_id) {\n\t\t\t\t\t\tERR(\"scope_id mismatch at stack depth=%d: found %d(%s), expected %d(%s)\",\n\t\t\t\t\t\t\tdepth,\n\t\t\t\t\t\t\tscope_id, args.aprof_scopes[scope_id].name,\n\t\t\t\t\t\t\tstack_frame->scope_id, args.aprof_scopes[stack_frame->scope_id].name);\n\n\t\t\t\t\t\tERR(\"Full stack:\");\n\t\t\t\t\t\tfor (int i = depth; i >= 0; --i) {\n\t\t\t\t\t\t\tERR(\"  %d: scope_id=%d(%s)\", i,\n\t\t\t\t\t\t\t\tstack[i].scope_id, args.aprof_scopes[stack[i].scope_id].name);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst aprof_scope_t *const scope = args.aprof_scopes + scope_id;\n\t\t\t\t\tconst uint64_t delta_ns = timestamp_ns - stack_frame->begin_ns;\n\n\t\t\t\t\tupdateMetascope(args.scopes + scope_id, args.scope_name_prefix, scope, delta_ns);\n\n\t\t\t\t\t// This is a top level scope that should be counted towards active usage\n\t\t\t\t\tconst int is_top_level = ((scope->flags & APROF_SCOPE_FLAG_DECOR) == 0)\n\t\t\t\t\t\t&& (depth == 0 || (args.aprof_scopes[stack[depth-1].scope_id].flags & APROF_SCOPE_FLAG_DECOR));\n\n\t\t\t\t\tconst int64_t latest_end_delta_ns = parent_frame\n\t\t\t\t\t\t? (int64_t)parent_frame->latest_child_end_ns - (int64_t)stack_frame->begin_ns\n\t\t\t\t\t\t: 0ll;\n\t\t\t\t\tconst int overlap_pixels = (float)(latest_end_delta_ns / 1000000) * args.time_scale_ms;\n\t\t\t\t\tconst int OVERLAP_PIXELS_THRESHOLD = 2;\n\t\t\t\t\tconst int overlaps_with_siblings = overlap_pixels > OVERLAP_PIXELS_THRESHOLD;\n\n\t\t\t\t\tint y_overlap_offset = 0;\n\t\t\t\t\tif (overlaps_with_siblings) {\n\t\t\t\t\t\tparent_frame->overlaps += 1;\n\t\t\t\t\t\ty_overlap_offset = parent_frame->overlaps * bar_height / 2;\n\t\t\t\t\t} else if (parent_frame) {\n\t\t\t\t\t\tparent_frame->overlaps = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Updated parent's latest child end timestamp\n\t\t\t\t\tif (parent_frame) {\n\t\t\t\t\t\tparent_frame->latest_child_end_ns = Q_max(parent_frame->latest_child_end_ns, timestamp_ns);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only count top level scopes towards active time, and only if it's not waiting\n\t\t\t\t\tif (is_top_level && under_waiting == 0)\n\t\t\t\t\t\tactive_time_ns += delta_ns;\n\n\t\t\t\t\t// If this is a top level waiting scope (under any depth)\n\t\t\t\t\tif (under_waiting == 1) {\n\t\t\t\t\t\t// Count it towards waiting time\n\t\t\t\t\t\twait_time_ns += delta_ns;\n\n\t\t\t\t\t\t// If this is not a top level scope, then we might count its top level parent\n\t\t\t\t\t\t// towards cpu usage time, which is not correct. Subtract this waiting time from it.\n\t\t\t\t\t\tif (!is_top_level)\n\t\t\t\t\t\t\tactive_time_ns -= delta_ns;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (scope->flags & APROF_SCOPE_FLAG_WAIT)\n\t\t\t\t\t\tunder_waiting--;\n\n\t\t\t\t\tif (args.draw) {\n\t\t\t\t\t\trgba_t color = {0, 0, 0, 127};\n\t\t\t\t\t\tgetColorForString(scope->name, color);\n\t\t\t\t\t\tdrawTimeBar(args.begin_time, args.time_scale_ms, stack_frame->begin_ns, timestamp_ns,\n\t\t\t\t\t\t\targs.y + y_overlap_offset + depth * bar_height, bar_height, scope->name, color);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\tcase APROF_EVENT_COUNTER: {\n\t\t\t\t\tstruct StackFrame *const frame = depth > 0 ? stack + depth - 1 : NULL;\n\t\t\t\t\tif (!frame || !args.aprof_counters.items)\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tconst uint32_t counter_index = APROF_EVENT_COUNTER_INDEX(event);\n\t\t\t\t\tconst uint64_t counter_value = APROF_EVENT_COUNTER_VALUE(event);\n\n\t\t\t\t\tASSERT(counter_index < args.aprof_counters.count);\n\n\t\t\t\t\tif (counter_index >= MAX_COUNTERS_PER_SCOPE) {\n\t\t\t\t\t\t// TODO throttled error\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst aprof_counter_desc_t *const counter_desc = &args.aprof_counters.items[counter_index];\n\t\t\t\t\tMetascope *const metascope = &args.scopes[frame->scope_id];\n\t\t\t\t\tMetacounter *const metacounter = &metascope->counters[counter_index];\n\n\t\t\t\t\tmetacounter->value = counter_value;\n\n\t\t\t\t\tr_speeds_metric_type_t metric = kSpeedsMetricCount;\n\t\t\t\t\tswitch (counter_desc->unit) {\n\t\t\t\t\t\tcase AprofCounterUnit_Bytes:\n\t\t\t\t\t\t\tmetric = kSpeedsMetricBytes;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase AprofCounterUnit_Nanoseconds:\n\t\t\t\t\t\t\tmetacounter->value = counter_value / 1000;\n\t\t\t\t\t\t\tmetric = kSpeedsMetricMicroseconds;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase AprofCounterUnit_Permyriad:\n\t\t\t\t\t\t\tmetric = kSpeedsMetricPermyriad;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase AprofCounterUnit_Generic:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!metacounter->initialized) {\n\t\t\t\t\t\tconst qboolean reset = true;\n\t\t\t\t\t\tR_SpeedsRegisterMetric(&metacounter->value,\n\t\t\t\t\t\t\targs.aprof_scopes[frame->scope_id].name, counter_desc->name,\n\t\t\t\t\t\t\tmetric, reset, counter_desc->name, __FILE__, __LINE__);\n\t\t\t\t\t\tmetacounter->initialized = 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t// gEngine.Con_Reportf(\"%s.%s (%d) = %llu\\n\",\n\t\t\t\t\t// \targs.aprof_scopes[frame->scope_id].name,\n\t\t\t\t\t// \targs.aprof_counters.items[counter_index].name,\n\t\t\t\t\t// \targs.aprof_counters.items[counter_index].unit,\n\t\t\t\t\t// \t(unsigned long long)counter_value);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (args.out_wait_time_us)\n\t\t*args.out_wait_time_us += wait_time_ns / 1000;\n\n\tif (args.out_active_time_us)\n\t\t*args.out_active_time_us += active_time_ns / 1000;\n\n\tif (max_depth > MAX_STACK_DEPTH)\n\t\tgEngine.Con_NPrintf(4, S_ERROR \"Profiler stack overflow: reached %d, max available %d\\n\", max_depth, MAX_STACK_DEPTH);\n}\n\nstatic void handlePause( uint32_t prev_frame_index ) {\n\tif (!g_speeds.pause_requested || g_speeds.paused_events)\n\t\treturn;\n\n\tconst uint32_t frame_begin = prev_frame_index;\n\tconst uint32_t frame_end = g_aprof.events_last_frame + 1;\n\n\tg_speeds.paused_events_count = frame_end >= frame_begin ? frame_end - frame_begin : (frame_end + APROF_EVENT_BUFFER_SIZE - frame_begin);\n\tg_speeds.paused_events = Mem_Malloc(vk_core.pool, g_speeds.paused_events_count * sizeof(g_speeds.paused_events[0]));\n\n\tif (frame_end >= frame_begin) {\n\t\tmemcpy(g_speeds.paused_events, g_aprof.events + frame_begin, g_speeds.paused_events_count * sizeof(g_speeds.paused_events[0]));\n\t} else {\n\t\tconst int first_chunk = (APROF_EVENT_BUFFER_SIZE - frame_begin) * sizeof(g_speeds.paused_events[0]);\n\t\tmemcpy(g_speeds.paused_events, g_aprof.events + frame_begin, first_chunk);\n\t\tmemcpy(g_speeds.paused_events + first_chunk, g_aprof.events, frame_end * sizeof(g_speeds.paused_events[0]));\n\t}\n}\n\nstatic int findMetricIndexByName( const_string_view_t name) {\n\tfor (int i = 0; i < g_speeds.metrics_count; ++i) {\n\t\tif (svCmp(name, g_speeds.metrics[i].name) == 0)\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nstatic int findMetricIndexByIndexOrName( const_string_view_t name) {\n\t// try to read it as metric index first\n\tconst SVParseLongResult parsed = svParseLong(name);\n\tif (parsed.chars_converted == name.len) {\n\t\tif (parsed.value < 0 || parsed.value > g_speeds.metrics_count) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn parsed.value;\n\t}\n\n\treturn findMetricIndexByName(name);\n}\n\nstatic int findGraphIndexByName( const_string_view_t name) {\n\t// TODO also delete by index. But need to have the active graph list first\n\tfor (int i = 0; i < g_speeds.graphs_count; ++i) {\n\t\tif (svCmp(name, g_speeds.graphs[i].name) == 0)\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nstatic int drawGraph( r_speeds_graph_t *const graph, int frame_bar_y ) {\n\tconst int min_width = 100 * g_speeds.font_metrics.scale;\n\tconst int graph_width = clampi32(\n\t\tg_speeds.r_speeds_graphs_width->value < 1\n\t\t? vk_frame.width / 2 // half frame width if invalid\n\t\t: (int)(g_speeds.r_speeds_graphs_width->value * g_speeds.font_metrics.scale), // scaled value if valid\n\t\tmin_width, vk_frame.width); // clamp to min_width..frame_width\n\tconst int graph_height = graph->height * g_speeds.font_metrics.scale;\n\n\tif (graph->source_metric < 0) {\n\t\t// Check whether this metric has been registered\n\t\tconst int metric_index = findMetricIndexByName((const_string_view_t){graph->name, Q_strlen(graph->name)});\n\n\t\tif (metric_index >= 0) {\n\t\t\tgraph->source_metric = metric_index;\n\t\t\tg_speeds.metrics[metric_index].graph_index = graph - g_speeds.graphs;\n\t\t} else {\n\t\t\tconst char *name = graph->name;\n\t\t\trgba_t text_color = {0xff, 0x00, 0x00, 0xff};\n\t\t\tgEngine.Con_DrawString(0, frame_bar_y, name, text_color);\n\t\t\tframe_bar_y += g_speeds.font_metrics.glyph_height;\n\t\t\treturn frame_bar_y;\n\t\t}\n\t}\n\n\tconst r_speeds_metric_t *const metric = g_speeds.metrics + graph->source_metric;\n\tconst int graph_max_value = metric->max_value ? Q_max(metric->max_value, graph->max_value) : graph->max_value;\n\n\tconst float width_factor = (float)graph_width / graph->data_count;\n\tconst float height_scale = (float)graph_height / graph_max_value;\n\n\trgba_t text_color = {0xed, 0x9f, 0x01, 0xff};\n\n\t// Draw graph name\n\tconst char *name = metric->name;\n\tgEngine.Con_DrawString(0, frame_bar_y, name, text_color);\n\tframe_bar_y += g_speeds.font_metrics.glyph_height;\n\n\t// Draw background that podcherkivaet graph area\n\tCL_FillRGBA(kRenderTransColor, 0, frame_bar_y, graph_width, graph_height, graph->color[0], graph->color[1], graph->color[2], 32);\n\n\t// Draw max value\n\t{\n\t\tchar buf[16];\n\t\tmetricTypeSnprintf(buf, sizeof(buf), graph_max_value, metric->type);\n\t\tgEngine.Con_DrawString(0, frame_bar_y, buf, text_color);\n\t}\n\n\t// Draw zero\n\tgEngine.Con_DrawString(0, frame_bar_y + graph->height - g_speeds.font_metrics.glyph_height, \"0\", text_color);\n\tframe_bar_y += graph_height;\n\n\tif (metric->low_watermark && metric->low_watermark < graph_max_value) {\n\t\tconst int y = frame_bar_y - metric->low_watermark * height_scale;\n\t\tCL_FillRGBA(kRenderTransAdd, 0, y, graph_width, 1, 0, 255, 0, 50);\n\t}\n\n\tif (metric->high_watermark && metric->high_watermark < graph_max_value) {\n\t\tconst int y = frame_bar_y - metric->high_watermark * height_scale;\n\t\tCL_FillRGBA(kRenderTransAdd, 0, y, graph_width, 1, 255, 0, 0, 50);\n\t}\n\n\t// Invert graph origin for better math below\n\tint max_value = INT_MIN;\n\tconst qboolean watermarks = metric->low_watermark && metric->high_watermark;\n\tfor (int i = 0; i < graph->data_count; ++i) {\n\t\tconst int raw_value = Q_max(0, graph->data[(graph->data_write + i) % graph->data_count]);\n\t\tmax_value = Q_max(max_value, raw_value);\n\t\tconst int value = Q_min(graph_max_value, raw_value);\n\n\t\tint red = 0xed, green = 0x9f, blue = 0x01;\n\t\tif (watermarks) {\n\t\t\t// E.g: > 60 fps => 0, 30..60 fps -> 1..0, <30fps => 1\n\t\t\tconst float k = linearstep(metric->low_watermark, metric->high_watermark, value);\n\t\t\tred = value < metric->low_watermark ? 0 : 255;\n\t\t\tgreen = 255 * (1 - k);\n\t\t}\n\n\t\tconst int x0 = (float)i * width_factor;\n\t\tconst int x1 = (float)(i+1) * width_factor;\n\t\tconst int y_pos = value * height_scale;\n\t\tconst int height = watermarks ? y_pos : 2 * g_speeds.font_metrics.scale;\n\t\tconst int y = frame_bar_y - y_pos;\n\n\t\t// TODO lines\n\t\tCL_FillRGBA(kRenderTransAdd, x0, y, x1-x0, height, red, green, blue, 127);\n\n\t\tif (i == graph->data_count - 1) {\n\t\t\tchar buf[16];\n\t\t\tmetricTypeSnprintf(buf, sizeof(buf), raw_value, metric->type);\n\t\t\tgEngine.Con_DrawString(x1, y - g_speeds.font_metrics.glyph_height / 2, buf, text_color);\n\t\t}\n\t}\n\n\tgraph->max_value = max_value ? max_value : 1;\n\n\treturn frame_bar_y;\n}\n\nstatic int analyzeScopesAndDrawFrames( int draw, uint32_t prev_frame_index, int y, const VCombufProfilingResult *gpurofls, int gpurofls_count) {\n\t// Draw latest 2 frames; find their boundaries\n\tuint32_t rewind_frame = prev_frame_index;\n\tconst int max_frames_to_draw = 2;\n\tfor (int frame = 1; frame < max_frames_to_draw;) {\n\t\trewind_frame = (rewind_frame - 1) % APROF_EVENT_BUFFER_SIZE; // NOTE: only correct for power-of-2 buffer sizes\n\t\tconst aprof_event_t event = g_aprof.events[rewind_frame];\n\n\t\t// Exhausted all events\n\t\tif (event == 0 || rewind_frame == g_aprof.events_write)\n\t\t\tbreak;\n\n\t\t// Note the frame\n\t\tif (APROF_EVENT_TYPE(event) == APROF_EVENT_FRAME_BOUNDARY) {\n\t\t\t++frame;\n\t\t\tprev_frame_index = rewind_frame;\n\t\t}\n\t}\n\n\tconst aprof_event_t *const events = g_speeds.paused_events ? g_speeds.paused_events : g_aprof.events;\n\tconst int event_begin = g_speeds.paused_events ? 0 : prev_frame_index;\n\tconst int event_end = g_speeds.paused_events ? g_speeds.paused_events_count - 1 : g_aprof.events_last_frame;\n\tconst uint64_t frame_begin_time = APROF_EVENT_TIMESTAMP(events[event_begin]);\n\tconst uint64_t frame_end_time = APROF_EVENT_TIMESTAMP(events[event_end]);\n\tconst uint64_t delta_ns = frame_end_time - frame_begin_time;\n\tconst float time_scale_ms = (double)vk_frame.width / (delta_ns / 1e6);\n\n\t// TODO? manage y based on depths\n\tprocessAndDrawAprofEvents((ProcessAndDrawAprofEvents){\n\t\t.draw = draw,\n\t\t.events = events,\n\t\t.begin_time = frame_begin_time,\n\t\t.time_scale_ms = time_scale_ms,\n\t\t.begin = event_begin,\n\t\t.end = event_end,\n\t\t.y = y,\n\t\t.scopes = g_speeds.frame.scopes,\n\t\t.aprof_scopes = g_aprof.scopes,\n\t\t.scope_name_prefix = \"scope\",\n\t\t.out_active_time_us = &g_speeds.frame.cpu_time_us,\n\t\t.out_wait_time_us = &g_speeds.frame.cpu_wait_time_us,\n\t});\n\n\tfor (int i = 0; i < gpurofls_count; ++i) {\n\t\tconst VCombufProfilingResult *const gpurofl = &gpurofls[i];\n\n\t\ty += g_speeds.font_metrics.glyph_height * (MAX_STACK_DEPTH + 1);\n\n\t\tprocessAndDrawAprofEvents((ProcessAndDrawAprofEvents){\n\t\t\t.draw = draw,\n\t\t\t.events = gpurofl->events.items,\n\t\t\t.begin_time = frame_begin_time,\n\t\t\t.time_scale_ms = time_scale_ms,\n\t\t\t.begin = 0,\n\t\t\t.end = gpurofl->events.count,\n\t\t\t.y = y,\n\t\t\t.scopes = g_speeds.frame.gpu_scopes,\n\t\t\t.aprof_scopes = gpurofl->scopes.items,\n\t\t\t.aprof_counters = {\n\t\t\t\t.items = gpurofl->counters.items,\n\t\t\t\t.count = gpurofl->counters.count,\n\t\t\t},\n\t\t\t.scope_name_prefix = \"gpuscope\",\n\t\t\t.out_active_time_us = NULL, // GPU time is handled elsewhere for now\n\t\t\t.out_wait_time_us = NULL,\n\t\t});\n\t}\n\n\treturn y + g_speeds.font_metrics.glyph_height * 6;\n}\n\nstatic void printMetrics( void ) {\n\tfor (int i = 0; i < g_speeds.metrics_count; ++i) {\n\t\tconst r_speeds_metric_t *const metric = g_speeds.metrics + i;\n\t\tif (Q_strncmp(metric->name, \"speeds\", 6) != 0)\n\t\t\tcontinue;\n\n\t\tchar buf[32];\n\t\tmetricTypeSnprintf(buf, sizeof(buf), (*metric->p_value), metric->type);\n\t\tspeedsPrintf(\"%s: %s\\n\", metric->name, buf);\n\t}\n}\n\nstatic void resetMetrics( void ) {\n\tfor (int i = 0; i < g_speeds.metrics_count; ++i) {\n\t\tconst r_speeds_metric_t *const metric = g_speeds.metrics + i;\n\t\tif (metric->reset)\n\t\t\t*metric->p_value = 0;\n\t}\n}\n\nstatic void getCurrentFontMetrics(void) {\n\t// hidpi scaling\n\tfloat scale = gEngine.pfnGetCvarFloat(\"con_fontscale\");\n\tif (scale <= 0.f)\n\t\tscale = 1.f;\n\n\t// TODO these numbers are mostly fine for the \"default\" font. Unfortunately\n\t// we don't have any access to real font metrics from here, ref_api_t doesn't give us anything about fonts. ;_;\n\tg_speeds.font_metrics.glyph_width = 7 * scale;\n\tg_speeds.font_metrics.glyph_height = 20 * scale;\n\tg_speeds.font_metrics.scale = scale;\n}\n\nstatic int drawGraphs( int y ) {\n\tfor (int i = 0; i < g_speeds.graphs_count; ++i) {\n\t\tr_speeds_graph_t *const graph = g_speeds.graphs + i;\n\n\t\tif (graph->source_metric >= 0)\n\t\t\tgraph->data[graph->data_write] = *g_speeds.metrics[graph->source_metric].p_value;\n\n\t\tgraph->data_write = (graph->data_write + 1) % graph->data_count;\n\t\ty = drawGraph(graph, y) + 10;\n\t}\n\n\treturn y;\n}\n\nstatic void togglePause( void ) {\n\tif (g_speeds.paused_events) {\n\t\tMem_Free(g_speeds.paused_events);\n\t\tg_speeds.paused_events = NULL;\n\t\tg_speeds.paused_events_count = 0;\n\t\tg_speeds.pause_requested = 0;\n\t} else {\n\t\tg_speeds.pause_requested = 1;\n\t}\n}\n\nstatic void speedsGraphAdd(const_string_view_t name, int metric_index) {\n\tINFO(\"Adding profiler graph for metric %.*s(%d) at graph index %d\", name.len, name.s, metric_index, g_speeds.graphs_count);\n\n\tif (g_speeds.graphs_count == MAX_GRAPHS) {\n\t\tERR(\"Cannot add graph \\\"%.*s\\\", no free graphs slots (max=%d)\", name.len, name.s, MAX_GRAPHS);\n\t\treturn;\n\t}\n\n\tif (metric_index >= 0) {\n\t\tr_speeds_metric_t *const metric = g_speeds.metrics + metric_index;\n\t\tmetric->graph_index = g_speeds.graphs_count;\n\t}\n\n\tr_speeds_graph_t *const graph = g_speeds.graphs + g_speeds.graphs_count++;\n\n\t// TODO make these customizable\n\tgraph->data_count = 256;\n\tgraph->height = 100;\n\tgraph->max_value = 1; // Will be computed automatically on first frame\n\tgraph->color[3] = 255;\n\n\tconst int len = Q_min(name.len, sizeof(graph->name) - 1);\n\tmemcpy(graph->name, name.s, len);\n\tgraph->name[len] = '\\0';\n\tgetColorForString(graph->name, graph->color);\n\n\tASSERT(!graph->data);\n\tgraph->data = Mem_Calloc(vk_core.pool, graph->data_count * sizeof(float));\n\tgraph->data_write = 0;\n\tgraph->source_metric = metric_index;\n}\n\nstatic void speedsGraphAddByMetricName( const_string_view_t name ) {\n\tconst int metric_index = findMetricIndexByIndexOrName(name);\n\tif (metric_index < 0) {\n\t\tERR(\"Metric \\\"%.*s\\\" not found\", name.len, name.s);\n\t\treturn;\n\t}\n\n\tr_speeds_metric_t *const metric = g_speeds.metrics + metric_index;\n\tif (metric->graph_index >= 0) {\n\t\tWARN(\"Metric \\\"%.*s\\\" already has graph @%d\", name.len, name.s, metric->graph_index);\n\t\treturn;\n\t}\n\n\tspeedsGraphAdd( svFromNullTerminated(metric->name), metric_index );\n}\n\nstatic void speedsGraphDelete( r_speeds_graph_t *graph ) {\n\tASSERT(graph->data);\n\tMem_Free(graph->data);\n\tgraph->data = NULL;\n\tgraph->name[0] = '\\0';\n\n\tif (graph->source_metric >= 0) {\n\t\tASSERT(graph->source_metric < g_speeds.metrics_count);\n\t\tr_speeds_metric_t *const metric = g_speeds.metrics + graph->source_metric;\n\t\tmetric->graph_index = -1;\n\t}\n\n\tgraph->source_metric = -1;\n}\n\nstatic void speedsGraphRemoveByName( const_string_view_t name ) {\n\tconst int graph_index = findGraphIndexByName(name);\n\tif (graph_index < 0) {\n\t\tERR(\"Graph \\\"%.*s\\\" not found\", name.len, name.s);\n\t\treturn;\n\t}\n\n\tr_speeds_graph_t *const graph = g_speeds.graphs + graph_index;\n\tspeedsGraphDelete( graph );\n\n\tINFO(\"Removing profiler graph %.*s(%d) at graph index %d\", name.len, name.s, graph->source_metric, graph_index);\n\n\t// Move all further graphs one slot back, also updating their indices\n\tfor (int i = graph_index + 1; i < g_speeds.graphs_count; ++i) {\n\t\tr_speeds_graph_t *const dst = g_speeds.graphs + i - 1;\n\t\tconst r_speeds_graph_t *const src = g_speeds.graphs + i;\n\n\t\tif (src->source_metric >= 0) {\n\t\t\tASSERT(src->source_metric < g_speeds.metrics_count);\n\t\t\tg_speeds.metrics[src->source_metric].graph_index--;\n\t\t}\n\n\t\tmemcpy(dst, src, sizeof(r_speeds_graph_t));\n\t}\n\n\tg_speeds.graphs_count--;\n}\n\nstatic void speedsGraphsRemoveAll( void ) {\n\tINFO(\"Removing all %d profiler graphs\", g_speeds.graphs_count);\n\tfor (int i = 0; i < g_speeds.graphs_count; ++i) {\n\t\tr_speeds_graph_t *const graph = g_speeds.graphs + i;\n\t\tspeedsGraphDelete(graph);\n\t}\n\n\tg_speeds.graphs_count = 0;\n}\n\nstatic void processGraphCvar( void ) {\n\tif (!(g_speeds.r_speeds_graphs->flags & FCVAR_CHANGED))\n\t\treturn;\n\n\tif (0 == Q_strcmp(g_speeds.r_speeds_graphs->string, g_speeds.graphs_list))\n\t\treturn;\n\n\t// TODO only remove graphs that are not present in the new list\n\tspeedsGraphsRemoveAll();\n\n\tconst char *p = g_speeds.r_speeds_graphs->string;\n\twhile (*p) {\n\t\tconst char *next = Q_strchrnul(p, ',');\n\t\tconst const_string_view_t name = {p, next - p};\n\n\t\tconst int metric_index = findMetricIndexByName(name);\n\t\tif (metric_index < 0) {\n\t\t\tWARN(\"Metric \\\"%.*s\\\" not found (yet? can be registered later)\", name.len, name.s);\n\t\t}\n\n\t\tspeedsGraphAdd( name, metric_index );\n\t\tif (!*next)\n\t\t\tbreak;\n\t\tp = next + 1;\n\t}\n\n\tg_speeds.r_speeds_graphs->flags &= ~FCVAR_CHANGED;\n}\n\n/*\nstatic const char *getMetricTypeName(r_speeds_metric_type_t type) {\n\tswitch (type) {\n\t\tcase kSpeedsMetricCount: return \"count\";\n\t\tcase kSpeedsMetricMicroseconds: return \"ms\";\n\t\tcase kSpeedsMetricBytes: return \"bytes\";\n\t}\n\n\treturn \"UNKNOWN\";\n}\n*/\n\n// Actually does the job of `r_speeds_mlist` and `r_speeds_mtable` commands.\n// We can't just directly call this function from little command handler ones, because\n// all the metrics calculations happen inside `R_SpeedsDisplayMore` function.\nstatic void doPrintMetrics( void ) {\n\tif ( g_speeds.frame.metrics_print_mode == kSpeedsMprintNone )\n\t\treturn;\n\n\tconst char *header_format = NULL;\n\tconst char *line_format = NULL;\n\tconst char *row_format = NULL;\n\tchar line[64];\n\tif ( g_speeds.frame.metrics_print_mode == kSpeedsMprintTable ) {\n\t\t// Note:\n\t\t// This table alignment method relies on monospace font\n\t\t// and will have its alignment completly broken without one.\n\t\theader_format = \"  | %-3s | %-38s | %-10s | %-46s | %21s\\n\";\n\t\tline_format   = \"  | %.3s | %.38s | %.10s | %.46s | %.21s\\n\";\n\t\trow_format    = \"  | ^2%-3d^7 | ^2%-38s^7 | ^3%-10s^7 | ^5%-46s^7 | ^6%s:%d^7\\n\";\n\n\t\tsize_t line_size = sizeof ( line );\n\t\tmemset( line, '-', line_size - 1 );\n\t\tline[line_size - 1] = '\\0';\n\t} else {\n\t\theader_format = \" [%s] %s = %s  -->  (%s, %s)\\n\";\n\t\tline_format   = NULL;\n\t\trow_format    = \" [^2%d^7] ^2%s^7 = ^3%s^7  -->  (^5%s^7, ^6%s:%d^7)\\n\";\n\n\t\tline[0] = '\\0';\n\t}\n\n\t// Reset mode to print only this frame.\n\tg_speeds.frame.metrics_print_mode = kSpeedsMprintNone;\n\n\tgEngine.Con_Printf( header_format, \"index\", \"module.metric_name\", \"value\", \"variable\", \"registration_location\" );\n\tif ( line_format )  gEngine.Con_Printf( line_format, line, line, line, line, line );\n\tfor ( int i = 0; i < g_speeds.metrics_count; ++i ) {\n\t\tconst r_speeds_metric_t *metric = g_speeds.metrics + i;\n\n\t\tif ( g_speeds.frame.metrics_print_filter[0] && !Q_strstr( metric->name, g_speeds.frame.metrics_print_filter ) )\n\t\t\tcontinue;\n\n\t\tchar value_with_unit[16];\n\t\tmetricTypeSnprintf( value_with_unit, sizeof( value_with_unit ), *metric->p_value, metric->type );\n\t\tgEngine.Con_Printf( row_format, i, metric->name, value_with_unit, metric->var_name, COM_FileWithoutPath( metric->src_file ), metric->src_line );\n\t}\n\tif ( line_format )  gEngine.Con_Printf( line_format, line, line, line, line, line );\n\tgEngine.Con_Printf( header_format, \"index\", \"module.metric_name\", \"value\", \"variable\", \"registration_location\" );\n}\n\n// Handles optional filter argument for `r_speeds_mlist` and `r_speeds_mtable` commands.\nstatic void handlePrintFilterArg( void ) {\n\tif ( gEngine.Cmd_Argc() > 1 ) {\n\t\tQ_strncpy( g_speeds.frame.metrics_print_filter, gEngine.Cmd_Argv( 1 ), sizeof( g_speeds.frame.metrics_print_filter ) );\n\t} else {\n\t\tg_speeds.frame.metrics_print_filter[0] = '\\0';\n\t}\n}\n\n// Ideally, we'd just autocomplete the r_speeds_graphs cvar/cmd.\n// However, autocompletion is not exposed to the renderer. It is completely internal to the engine, see con_utils.c, var cmd_list.\n// -------\n// Handles `r_speeds_mlist` command.\nstatic void printMetricsList( void ) {\n\thandlePrintFilterArg();\n\tg_speeds.frame.metrics_print_mode = kSpeedsMprintList;\n}\n\n// Handles `r_speeds_mtable` command.\nstatic void printMetricsTable( void ) {\n\thandlePrintFilterArg();\n\tg_speeds.frame.metrics_print_mode = kSpeedsMprintTable;\n}\n\nstatic void graphCmd( void ) {\n\tenum { Unknown, Add, Remove, Clear } action = Unknown;\n\n\tconst int argc = gEngine.Cmd_Argc();\n\n\tif (argc > 1) {\n\t\tconst char *const cmd = gEngine.Cmd_Argv(1);\n\t\tif (0 == Q_strcmp(\"add\", cmd) && argc > 2)\n\t\t\taction = Add;\n\t\telse if (0 == Q_strcmp(\"del\", cmd) && argc > 2)\n\t\t\taction = Remove;\n\t\telse if (0 == Q_strcmp(\"clear\", cmd))\n\t\t\taction = Clear;\n\t}\n\n\n\tswitch (action) {\n\t\tcase Add:\n\t\t\tfor (int i = 2; i < argc; ++i) {\n\t\t\t\tconst char *const arg = gEngine.Cmd_Argv(i);\n\t\t\t\tconst const_string_view_t name = {arg, Q_strlen(arg) };\n\t\t\t\tspeedsGraphAddByMetricName( name );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase Remove:\n\t\t\tfor (int i = 2; i < argc; ++i) {\n\t\t\t\tconst char *const arg = gEngine.Cmd_Argv(i);\n\t\t\t\tconst const_string_view_t name = {arg, Q_strlen(arg) };\n\t\t\t\tspeedsGraphRemoveByName( name );\n\t\t\t}\n\t\t\tbreak;\n\t\tcase Clear:\n\t\t\tspeedsGraphsRemoveAll();\n\t\t\tbreak;\n\t\tcase Unknown:\n\t\t\tINFO(\"Usage:\\n%s <add/del> metric0 metric1 ...\", gEngine.Cmd_Argv(0));\n\t\t\tINFO(\"\\t%s <add/del> metric0 metric1 ...\", gEngine.Cmd_Argv(0));\n\t\t\tINFO(\"\\t%s clear\", gEngine.Cmd_Argv(0));\n\t\t\treturn;\n\t}\n\n\t// update cvar\n\t{\n\t\tconst int len = sizeof(g_speeds.graphs_list) - 1;\n\t\tchar *const buf = g_speeds.graphs_list;\n\n\t\tbuf[0] = '\\0';\n\t\tint off = 0;\n\t\tfor (int i = 0; i < g_speeds.graphs_count; ++i) {\n\t\t\tconst r_speeds_graph_t *const graph = g_speeds.graphs + i;\n\n\t\t\tif (off)\n\t\t\t\tbuf[off++] = ',';\n\n\t\t\t//gEngine.Con_Reportf(\"buf='%s' off=%d %s(%d)\\n\", buf, off, graph->name, (int)Q_strlen(graph->name));\n\n\t\t\tconst char *s = graph->name;\n\t\t\twhile (off < len && *s)\n\t\t\t\tbuf[off++] = *s++;\n\n\t\t\tbuf[off] = '\\0';\n\n\t\t\tif (off >= len - 1)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tgEngine.Cvar_Set(\"r_speeds_graphs\", buf);\n\t}\n}\n\nvoid R_SpeedsInit( void ) {\n\tg_speeds.r_speeds_graphs = gEngine.Cvar_Get(\"r_speeds_graphs\", \"\", FCVAR_GLCONFIG, \"List of metrics to plot as graphs, separated by commas\");\n\tg_speeds.r_speeds_graphs_width = gEngine.Cvar_Get(\"r_speeds_graphs_width\", \"\", FCVAR_GLCONFIG, \"Graphs width in pixels\");\n\n\tgEngine.Cmd_AddCommand(\"r_speeds_toggle_pause\", togglePause, \"Toggle frame profiler pause\");\n\tgEngine.Cmd_AddCommand(\"r_speeds_mlist\", printMetricsList, \"Print all registered metrics as a list\");\n\tgEngine.Cmd_AddCommand(\"r_speeds_mtable\", printMetricsTable, \"Print all registered metrics as a table\");\n\tgEngine.Cmd_AddCommand(\"r_speeds_graph\", graphCmd, \"Manipulate add/remove metrics graphs\");\n\n\tR_SPEEDS_COUNTER(g_speeds.frame.frame_time_us, \"frame\", kSpeedsMetricMicroseconds);\n\tR_SPEEDS_COUNTER(g_speeds.frame.cpu_time_us, \"cpu\", kSpeedsMetricMicroseconds);\n\tR_SPEEDS_COUNTER(g_speeds.frame.cpu_wait_time_us, \"cpu_wait\", kSpeedsMetricMicroseconds);\n\tR_SPEEDS_COUNTER(g_speeds.frame.gpu_time_us, \"gpu\", kSpeedsMetricMicroseconds);\n}\n\n// grab r_speeds message\nqboolean R_SpeedsMessage( char *out, size_t size )\n{\n\tif( gEngine.drawFuncs->R_SpeedsMessage != NULL )\n\t{\n\t\tif( gEngine.drawFuncs->R_SpeedsMessage( out, size ))\n\t\t\treturn true;\n\t\t// otherwise pass to default handler\n\t}\n\n\tif( r_speeds->value <= 0 ) return false;\n\tif( !out || !size ) return false;\n\n\tQ_strncpy( out, g_speeds.frame.message, size );\n\n\treturn true;\n}\n\nvoid R_SpeedsRegisterMetric(int* p_value, const char *module, const char *name, r_speeds_metric_type_t type, qboolean reset, const char *var_name, const char *file, int line) {\n\tASSERT(g_speeds.metrics_count < MAX_SPEEDS_METRICS);\n\n\tr_speeds_metric_t *metric = g_speeds.metrics + (g_speeds.metrics_count++);\n\tmetric->p_value = p_value;\n\tmetric->reset = reset;\n\n\tQ_snprintf(metric->name, sizeof(metric->name), \"%s.%s\", module, name);\n\n\tmetric->type = type;\n\tmetric->src_file = file;\n\tmetric->src_line = line;\n\tmetric->var_name = var_name;\n\tmetric->graph_index = -1;\n\n\t// TODO how to make universally adjustable?\n\tif (Q_strcmp(\"frame\", name) == 0) {\n\t\tmetric->low_watermark = TARGET_FRAME_TIME * 1000;\n\t\tmetric->high_watermark = TARGET_FRAME_TIME * 2000;\n\t\tmetric->max_value = TARGET_FRAME_TIME * 3000;\n\t} else {\n\t\tmetric->low_watermark = metric->high_watermark = metric->max_value;\n\t}\n}\n\nvoid R_SpeedsDisplayMore(uint32_t prev_frame_index, const struct VCombufProfilingResult *gpurofl, int gpurofl_count) {\n\tAPROF_SCOPE_DECLARE_BEGIN(function, __FUNCTION__);\n\n\t// Reads current font/DPI scale, many functions below use it\n\tgetCurrentFontMetrics();\n\n\tg_speeds.frame.message[0] = '\\0';\n\n\tconst uint32_t speeds_bits = r_speeds->value;\n\n\tif (speeds_bits) {\n\t\tspeedsPrintf( \"Renderer: ^1Vulkan%s^7\\n\", vk_frame.rtx_enabled ? \" RT\" : \"\" );\n\t\tint color_index = 7; // default color\n\t\tswitch (v_device_info.properties.vendorID) {\n\t\t\tcase 0x1002: /* AMD */ color_index = 1; break;\n\t\t\tcase 0x10DE: /* NVIDIA */ color_index = 2; break;\n\t\t\tcase 0x8086: /* INTEL */ color_index = 4; break;\n\t\t}\n\t\tspeedsPrintf( \"^%d%s^7\\n\", color_index, v_device_info.properties.deviceName);\n\t\tspeedsPrintf( \"Driver: %u.%u.%u, Vulkan: %u.%u.%u\\n\",\n\t\t\tXVK_PARSE_VERSION(v_device_info.properties.driverVersion),\n\t\t\tXVK_PARSE_VERSION(v_device_info.properties.apiVersion));\n\t\tspeedsPrintf( \"Resolution: %ux%u\\n\", vk_frame.width, vk_frame.height);\n\t}\n\n\tconst uint32_t events = g_aprof.events_last_frame - prev_frame_index;\n\tconst uint64_t frame_begin_time = APROF_EVENT_TIMESTAMP(g_aprof.events[prev_frame_index]);\n\tconst unsigned long long delta_ns = APROF_EVENT_TIMESTAMP(g_aprof.events[g_aprof.events_last_frame]) - frame_begin_time;\n\n\tg_speeds.frame.frame_time_us = delta_ns / 1000;\n\n\t{\n\t\t// TODO this is not strictly correct, just and approximation\n\t\t// E.g. it won't give correct result for multiple combufs with gaps between them.\n\t\tuint64_t gpu_frame_begin_ns = UINT64_MAX, gpu_frame_end_ns = 0;\n\t\tfor (int i = 0; i < gpurofl_count; ++i) {\n\t\t\tgpu_frame_begin_ns = Q_min(gpu_frame_begin_ns, gpurofl[i].begin_ns);\n\t\t\tgpu_frame_end_ns = Q_max(gpu_frame_end_ns, gpurofl[i].end_ns);\n\t\t}\n\t\tg_speeds.frame.gpu_time_us = (gpu_frame_end_ns - gpu_frame_begin_ns) / 1000;\n\t}\n\n\thandlePause( prev_frame_index );\n\n\tif (speeds_bits != 0)\n\t{\n\t\tint y = 100;\n\t\tconst int draw_frame = speeds_bits & SPEEDS_BIT_FRAME;\n\t\ty = analyzeScopesAndDrawFrames( draw_frame, prev_frame_index, y, gpurofl, gpurofl_count );\n\n\t\tconst int draw_graphs = speeds_bits & SPEEDS_BIT_GRAPHS;\n\t\tif (draw_graphs)\n\t\t\ty = drawGraphs(y + 10);\n\t}\n\n\tif (speeds_bits & SPEEDS_BIT_SIMPLE) {\n\t\tspeedsPrintf(\"frame: %.03fms GPU: %.03fms\\n\", g_speeds.frame.frame_time_us * 1e-3f, g_speeds.frame.gpu_time_us * 1e-3);\n\t\tspeedsPrintf(\"  (ref) CPU: %.03fms wait: %.03fms\\n\", g_speeds.frame.cpu_time_us * 1e-3, g_speeds.frame.cpu_wait_time_us * 1e-3);\n\t}\n\n\tif (speeds_bits & SPEEDS_BIT_STATS) {\n\t\tspeedsPrintf(\"profiler events: %u, wraps: %d\\n\", events, g_aprof.current_frame_wraparounds);\n\t\tprintMetrics();\n\t}\n\n\tprocessGraphCvar();\n\n\tdoPrintMetrics();\n\n\tresetMetrics();\n\n\tAPROF_SCOPE_END(function);\n}\n"
  },
  {
    "path": "ref/vk/r_speeds.h",
    "content": "#pragma once\n#include \"xash3d_types.h\"\n#include <stdint.h>\n\nvoid R_SpeedsInit( void );\n\n// TODO consolidate with regular CPU profiler; Their data is almost the same now\nstruct VCombufProfilingResult;\nvoid R_SpeedsDisplayMore(uint32_t prev_frame_index, const struct VCombufProfilingResult *gpurofl, int gpurofl_count);\n\n// Called from the engine into ref_api to get the latest speeds info\nqboolean R_SpeedsMessage( char *out, size_t size );\n\ntypedef enum {\n\tkSpeedsMetricCount,\n\tkSpeedsMetricBytes,\n\tkSpeedsMetricMicroseconds,\n\tkSpeedsMetricPermyriad, /* ‱, 1/100 of 1% */\n} r_speeds_metric_type_t;\n\n// TODO upper limit argument\nvoid R_SpeedsRegisterMetric(int* p_value, const char *module, const char *name, r_speeds_metric_type_t type, qboolean reset, const char *var_name, const char *file, int line);\n\n// A counter is a value accumulated during a single frame, and reset to zero between frames.\n// Examples: drawn models count, scope times, etc.\n#define R_SPEEDS_COUNTER(var, name, type) \\\n\tR_SpeedsRegisterMetric(&(var), MODULE_NAME, name, type, /*reset*/ true, #var, __FILE__, __LINE__)\n\n// A metric is computed and preserved across frame boundaries.\n// Examples: total allocated memory, cache sizes, etc.\n#define R_SPEEDS_METRIC(var, name, type) \\\n\tR_SpeedsRegisterMetric(&(var), MODULE_NAME, name, type, /*reset*/ false, #var, __FILE__, __LINE__)\n"
  },
  {
    "path": "ref/vk/r_textures.c",
    "content": "#include \"r_textures.h\"\n#include \"vk_textures.h\"\n\n#include \"vk_common.h\"\n#include \"vk_const.h\"\n#include \"vk_mapents.h\" // wadlist\n#include \"vk_logs.h\"\n#include \"std/profiler.h\"\n#include \"std/unordered_roadmap.h\"\n#include \"std/stringview.h\"\n\n#include \"xash3d_mathlib.h\"\n#include \"crtlib.h\"\n\n#include <memory.h>\n#include <math.h>\n\n#define MODULE_NAME \"textures\"\n#define LOG_MODULE tex\n\nvk_textures_global_t tglob = {0};\n\nstatic struct {\n\tpoolhandle_t mempool;\n\n\tvk_texture_t all[MAX_TEXTURES];\n\turmom_desc_t all_desc;\n\n\tstruct {\n\t\tr_skybox_info_t info;\n\t\tchar current_name[MAX_STRING];\n\t} skybox;\n} g_textures;\n\nstatic void createDefaultTextures( void );\nstatic void destroyDefaultTextures( void );\nstatic void destroyTexture( uint texnum );\n\n#define R_TextureUploadFromBufferNew(name, pic, flags) R_TextureUploadFromBuffer(name, pic, flags, /*update_only=*/false)\n\nqboolean R_TexturesInit( void ) {\n\tg_textures.mempool = Mem_AllocPool( \"vktextures\" );\n\n\tg_textures.all_desc = (urmom_desc_t){\n\t\t.array = g_textures.all,\n\t\t.count = COUNTOF(g_textures.all),\n\t\t.item_size = sizeof(g_textures.all[0]),\n\t\t.type = kUrmomStringInsensitive,\n\t};\n\n\turmomInit(&g_textures.all_desc);\n\n\t// Mark index 0 as occupied to have a special \"no texture\" value\n\tg_textures.all[0].hdr_.hash = 0x7fffffff;\n\tg_textures.all[0].hdr_.state = 1;\n\tQ_strncpy( g_textures.all[0].hdr_.key, \"*unused*\", sizeof(g_textures.all[0].hdr_.key));\n\n\tcreateDefaultTextures();\n\n\tif (!R_VkTexturesInit())\n\t\treturn false;\n\n\treturn true;\n}\n\nvoid R_TexturesShutdown( void )\n{\n\tdestroyDefaultTextures();\n\n\t// By this point ideally all texture should have been destroyed.\n\t// However, there are two possible ways some texture could have been left over:\n\t// 1. Our coding mistakes, not releasing textures when done\n\t// 2. Engine and other external things not cleaning up (e.g. mainui is known to leave textures)\n\tfor( int i = 1; i < COUNTOF(g_textures.all); i++ ) {\n\t\tconst vk_texture_t *const tex = g_textures.all + i;\n\t\tif (!URMOM_IS_OCCUPIED(tex->hdr_))\n\t\t\tcontinue;\n\n\t\t// Try to free external textures\n\t\tR_TextureFree( i );\n\n\t\t// If it is still not deleted, complain loudly\n\t\tif (URMOM_IS_OCCUPIED(tex->hdr_)) {\n\t\t\t// TODO consider ASSERT, as this is a coding mistake\n\t\t\tERR(\"stale texture[%d] '%s' refcount=%d\", i, TEX_NAME(tex), tex->refcount);\n\t\t\tdestroyTexture( i );\n\t\t}\n\t}\n\n\tint is_deleted_count = 0;\n\tint clusters[16] = {0};\n\tint current_cluster_begin = -1;\n\tfor( int i = 1; i < COUNTOF(g_textures.all); i++ ) {\n\t\tconst vk_texture_t *const tex = g_textures.all + i;\n\n\t\tif (URMOM_IS_EMPTY(tex->hdr_)) {\n\t\t\tif (current_cluster_begin >= 0) {\n\t\t\t\tconst int cluster_length = i - current_cluster_begin;\n\t\t\t\tclusters[cluster_length >= COUNTOF(clusters) ? 0 : cluster_length]++;\n\t\t\t}\n\t\t\tcurrent_cluster_begin = -1;\n\t\t} else {\n\t\t\tif (current_cluster_begin < 0)\n\t\t\t\tcurrent_cluster_begin = i;\n\t\t}\n\n\t\tif (URMOM_IS_DELETED(tex->hdr_))\n\t\t\t++is_deleted_count;\n\n\t\tASSERT(!URMOM_IS_OCCUPIED(tex->hdr_));\n\t}\n\n\t// TODO handle wraparound clusters\n\tif (current_cluster_begin >= 0) {\n\t\tconst int cluster_length = COUNTOF(g_textures.all) - current_cluster_begin;\n\t\tclusters[cluster_length >= COUNTOF(clusters) ? 0 : cluster_length]++;\n\t}\n\n\tDEBUG(\"Deleted slots in texture hash table: %d\", is_deleted_count);\n\tfor (int i = 1; i < COUNTOF(clusters); ++i)\n\t\tDEBUG(\"Texture hash table cluster[%d] = %d\", i, clusters[i]);\n\n\tDEBUG(\"Clusters longer than %d: %d\", (int)COUNTOF(clusters)-1, clusters[0]);\n\n\tR_VkTexturesShutdown();\n}\n\nstatic qboolean checkTextureName( const char *name )\n{\n\tint len;\n\n\tif( !COM_CheckString( name ))\n\t\treturn false;\n\n\tlen = Q_strlen( name );\n\n\t// because multi-layered textures can exceed name string\n\tif( len >= sizeof( g_textures.all[0].hdr_.key ))\n\t{\n\t\tERR(\"LoadTexture: too long name %s (%d)\", name, len );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic rgbdata_t *Common_FakeImage( int width, int height, int depth, int flags )\n{\n\t// TODO: Fix texture and it's buffer leaking.\n\trgbdata_t *r_image = Mem_Malloc( g_textures.mempool, sizeof( rgbdata_t ) );\n\n\t// also use this for bad textures, but without alpha\n\tr_image->width  = Q_max( 1, width );\n\tr_image->height = Q_max( 1, height );\n\tr_image->depth  = Q_max( 1, depth );\n\tr_image->flags  = flags;\n\tr_image->type   = PF_RGBA_32;\n\n\tr_image->size = r_image->width * r_image->height * r_image->depth * 4;\n\tif( FBitSet( r_image->flags, IMAGE_CUBEMAP )) r_image->size *= 6;\n\n\tr_image->buffer  = Mem_Malloc( g_textures.mempool, r_image->size);\n\tr_image->palette = NULL;\n\tr_image->numMips = 1;\n\tr_image->encode  = 0;\n\n\tmemset( r_image->buffer, 0xFF, r_image->size );\n\n\treturn r_image;\n}\n\nstatic void createDefaultTextures( void )\n{\n\tint\tdx2, dy, d;\n\tint\tx, y;\n\trgbdata_t\t*pic;\n\n\t// emo-texture from quake1\n\tpic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR );\n\tuint *const buffer = PTR_CAST(uint, pic->buffer);\n\n\tfor( y = 0; y < 16; y++ )\n\t{\n\t\tfor( x = 0; x < 16; x++ )\n\t\t{\n\t\t\tif(( y < 8 ) ^ ( x < 8 ))\n\t\t\t\tbuffer[y*16+x] = 0xFFFF00FF;\n\t\t\telse buffer[y*16+x] = 0xFF000000;\n\t\t}\n\t}\n\n\ttglob.defaultTexture = R_TextureUploadFromBufferNew( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP );\n\n\t// particle texture from quake1\n\tpic = Common_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA );\n\n\tfor( x = 0; x < 16; x++ )\n\t{\n\t\tdx2 = x - 8;\n\t\tdx2 = dx2 * dx2;\n\n\t\tfor( y = 0; y < 16; y++ )\n\t\t{\n\t\t\tdy = y - 8;\n\t\t\td = 255 - 35 * sqrt( dx2 + dy * dy );\n\t\t\tpic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 );\n\t\t}\n\t}\n\n\ttglob.particleTexture = R_TextureUploadFromBufferNew( REF_PARTICLE_TEXTURE, pic, TF_CLAMP );\n\n\t// white texture\n\tpic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\tbuffer[x] = 0xFFFFFFFF;\n\ttglob.whiteTexture = R_TextureUploadFromBufferNew( REF_WHITE_TEXTURE, pic, TF_COLORMAP );\n\n\t// gray texture\n\tpic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\tbuffer[x] = 0xFF7F7F7F;\n\ttglob.grayTexture = R_TextureUploadFromBufferNew( REF_GRAY_TEXTURE, pic, TF_COLORMAP );\n\n\t// black texture\n\tpic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR );\n\tfor( x = 0; x < 16; x++ )\n\t\tbuffer[x] = 0xFF000000;\n\ttglob.blackTexture = R_TextureUploadFromBufferNew( REF_BLACK_TEXTURE, pic, TF_COLORMAP );\n\n\t// cinematic dummy\n\tpic = Common_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR );\n\ttglob.cinTexture = R_TextureUploadFromBufferNew( \"*cintexture\", pic, TF_NOMIPMAP|TF_CLAMP );\n\n\t{\n\t\tpic = Common_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR | IMAGE_CUBEMAP );\n\t\tmemset(pic->buffer, 0, pic->size);\n\n\t\tR_VkTexturesSkyboxUpload( \"skybox_placeholder\", pic, kColorspaceGamma, kSkyboxPlaceholder );\n\t}\n}\n\nstatic void destroyDefaultTextures( void ) {\n\tif (tglob.cinTexture > 0)\n\t\tR_TextureFree( tglob.cinTexture );\n\n\tif (tglob.blackTexture > 0)\n\t\tR_TextureFree( tglob.blackTexture );\n\n\tif (tglob.grayTexture > 0)\n\t\tR_TextureFree( tglob.grayTexture );\n\n\tif (tglob.whiteTexture > 0)\n\t\tR_TextureFree( tglob.whiteTexture );\n\n\tif (tglob.particleTexture > 0)\n\t\tR_TextureFree( tglob.particleTexture );\n\n\tif (tglob.defaultTexture > 0)\n\t\tR_TextureFree( tglob.defaultTexture );\n}\n\nstatic void ProcessImage( vk_texture_t *tex, rgbdata_t *pic )\n{\n\tfloat\temboss_scale = 0.0f;\n\tuint\timg_flags = 0;\n\n\t// force upload texture as RGB or RGBA (detail textures requires this)\n\tif( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR;\n\tif( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA;\n\n\t//FIXME provod: ??? tex->encode = pic->encode; // share encode method\n\n\tif( ImageCompressed( pic->type ))\n\t{\n\t\tif( !pic->numMips )\n\t\t\ttex->flags |= TF_NOMIPMAP; // disable mipmapping by user request\n\n\t\t// clear all the unsupported flags\n\t\ttex->flags &= ~TF_KEEP_SOURCE;\n\t}\n\telse\n\t{\n\t\t// copy flag about luma pixels\n\t\tif( pic->flags & IMAGE_HAS_LUMA )\n\t\t\ttex->flags |= TF_HAS_LUMA;\n\n\t\tif( pic->flags & IMAGE_QUAKEPAL )\n\t\t\ttex->flags |= TF_QUAKEPAL;\n\n\t\t// create luma texture from quake texture\n\t\tif( tex->flags & TF_MAKELUMA )\n\t\t{\n\t\t\timg_flags |= IMAGE_MAKE_LUMA;\n\t\t\ttex->flags &= ~TF_MAKELUMA;\n\t\t}\n\n\t\t/* FIXME provod: ???\n\t\tif( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE ))\n\t\t\ttex->original = gEngine.FS_CopyImage( pic ); // because current pic will be expanded to rgba\n\t\t*/\n\n\t\t// we need to expand image into RGBA buffer\n\t\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\t\timg_flags |= IMAGE_FORCE_RGBA;\n\n\t\t/* FIXME provod: ???\n\t\t// dedicated server doesn't register this variable\n\t\tif( gl_emboss_scale != NULL )\n\t\t\temboss_scale = gl_emboss_scale->value;\n\t\t*/\n\n\t\t// processing image before uploading (force to rgba, make luma etc)\n\t\tif( pic->buffer ) gEngine.Image_Process( &pic, 0, 0, img_flags, emboss_scale );\n\n\t\tif( FBitSet( tex->flags, TF_LUMINANCE ))\n\t\t\tClearBits( pic->flags, IMAGE_HAS_COLOR );\n\t}\n}\n\nsize_t CalcImageSize( pixformat_t format, int width, int height, int depth ) {\n\tsize_t\tsize = 0;\n\n\t// check the depth error\n\tdepth = Q_max( 1, depth );\n\n\tswitch( format )\n\t{\n\tcase PF_LUMINANCE:\n\t\tsize = width * height * depth;\n\t\tbreak;\n\tcase PF_RGB_24:\n\tcase PF_BGR_24:\n\t\tsize = width * height * depth * 3;\n\t\tbreak;\n\tcase PF_BGRA_32:\n\tcase PF_RGBA_32:\n\t\tsize = width * height * depth * 4;\n\t\tbreak;\n\tcase PF_DXT1:\n\tcase PF_BC4_UNSIGNED:\n\tcase PF_BC4_SIGNED:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth;\n\t\tbreak;\n\tcase PF_DXT3:\n\tcase PF_DXT5:\n\tcase PF_BC6H_UNSIGNED:\n\tcase PF_BC6H_SIGNED:\n\tcase PF_BC7_UNORM:\n\tcase PF_BC7_SRGB:\n\tcase PF_ATI2:\n\tcase PF_BC5_UNSIGNED:\n\tcase PF_BC5_SIGNED:\n\t\tsize = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth;\n\t\tbreak;\n\tdefault:\n\t\tERR(\"%s: unsupported pixformat_t %d\", __FUNCTION__, format);\n\t\tASSERT(!\"Unsupported format encountered\");\n\t}\n\n\treturn size;\n}\n\nint CalcMipmapCount( int width, int height, int depth, uint32_t flags, qboolean haveBuffer )\n{\n\tint\tmipcount;\n\n\tif( !haveBuffer )// || tex->target == GL_TEXTURE_3D )\n\t\treturn 1;\n\n\t// generate mip-levels by user request\n\tif( FBitSet( flags, TF_NOMIPMAP ))\n\t\treturn 1;\n\n\t// mip-maps can't exceeds 16\n\tfor( mipcount = 0; mipcount < 16; mipcount++ )\n\t{\n\t\tconst int mip_width = Q_max( 1, ( width >> mipcount ));\n\t\tconst int mip_height = Q_max( 1, ( height >> mipcount ));\n\t\tconst int mip_depth = Q_max( 1, ( depth >> mipcount ));\n\t\tif( mip_width == 1 && mip_height == 1 && mip_depth == 1 )\n\t\t\tbreak;\n\t}\n\n\treturn mipcount + 1;\n}\n\nvoid BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags )\n{\n\tbyte *out = in;\n\tint\tinstride = ALIGN( srcWidth * 4, 1 );\n\tint\tmipWidth, mipHeight, outpadding;\n\tint\trow, x, y, z;\n\tvec3_t\tnormal;\n\n\tif( !in ) return;\n\n\tmipWidth = Q_max( 1, ( srcWidth >> 1 ));\n\tmipHeight = Q_max( 1, ( srcHeight >> 1 ));\n\toutpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4;\n\n\tif( FBitSet( flags, TF_ALPHACONTRAST ))\n\t{\n\t\tmemset( in, mipWidth, mipWidth * mipHeight * 4 );\n\t\treturn;\n\t}\n\n\t// move through all layers\n\tfor( z = 0; z < srcDepth; z++ )\n\t{\n\t\tif( FBitSet( flags, TF_NORMALMAP ))\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] )\n\t\t\t\t\t\t+ MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tnormal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] );\n\t\t\t\t\t\tnormal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] );\n\t\t\t\t\t\tnormal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] );\n\t\t\t\t\t}\n\n\t\t\t\t\tif( !VectorNormalizeLength( normal ))\n\t\t\t\t\t\tVectorSet( normal, 0.5f, 0.5f, 1.0f );\n\n\t\t\t\t\tout[0] = 128 + (byte)(127.0f * normal[0]);\n\t\t\t\t\tout[1] = 128 + (byte)(127.0f * normal[1]);\n\t\t\t\t\tout[2] = 128 + (byte)(127.0f * normal[2]);\n\t\t\t\t\tout[3] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding )\n\t\t\t{\n\t\t\t\tbyte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in;\n\t\t\t\tfor( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 )\n\t\t\t\t{\n\t\t\t\t\tif((( x << 1 ) + 1 ) < srcWidth )\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2;\n\t\t\t\t\t\tout[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2;\n\t\t\t\t\t\tout[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2;\n\t\t\t\t\t\tout[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tout[0] = (in[row+0] + next[row+0]) >> 1;\n\t\t\t\t\t\tout[1] = (in[row+1] + next[row+1]) >> 1;\n\t\t\t\t\t\tout[2] = (in[row+2] + next[row+2]) >> 1;\n\t\t\t\t\t\tout[3] = (in[row+3] + next[row+3]) >> 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n///////////// Render API funcs /////////////\nint R_TextureFindByName( const char *name )\n{\n\tif( !checkTextureName( name ))\n\t\treturn 0;\n\n\tconst int index = urmomFind(&g_textures.all_desc, name);\n\treturn index > 0 ? index : 0;\n}\n\nconst char* R_TextureGetNameByIndex( unsigned int texnum )\n{\n\tASSERT( texnum >= 0 && texnum < MAX_TEXTURES );\n\treturn g_textures.all[texnum].hdr_.key;\n}\n\nstatic int loadTextureInternalFromFile( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint, qboolean force_update, qboolean ref_interface ) {\n\tqboolean success = false;\n\tif( !checkTextureName( name ))\n\t\treturn 0;\n\n\tconst urmom_insert_t insert = urmomInsert(&g_textures.all_desc, name);\n\tif (insert.index < 0) {\n\t\tERR(\"Cannot allocate texture slot for \\\"%s\\\"\", name);\n\t\treturn 0;\n\t}\n\n\tASSERT(insert.index < COUNTOF(g_textures.all));\n\n\tvk_texture_t *const tex = g_textures.all + insert.index;\n\n\t// return existing if already loaded and was not forced to reload\n\tif (!insert.created && !force_update) {\n\t\tDEBUG(\"Found existing texture %s(%d) refcount=%d\", TEX_NAME(tex), insert.index, tex->refcount);\n\n\t\t// Increment refcount for refcount-aware calls (e.g. materials)\n\t\tif (!ref_interface) {\n\t\t\ttex->refcount++;\n\t\t} else if (!tex->ref_interface_visible) {\n\t\t\ttex->ref_interface_visible = true;\n\t\t\ttex->refcount++;\n\t\t}\n\n\t\treturn insert.index;\n\t}\n\n\tuint picFlags = 0;\n\n\tif( FBitSet( flags, TF_NOFLIP_TGA ))\n\t\tSetBits( picFlags, IL_DONTFLIP_TGA );\n\n\tif( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE ))\n\t\tSetBits( picFlags, IL_KEEP_8BIT );\n\n\t// set some image flags\n\tgEngine.Image_SetForceFlags( picFlags );\n\n\trgbdata_t *const pic = gEngine.FS_LoadImage( name, buf, size );\n\tif( !pic )\n\t\tgoto cleanup;\n\n\tif (pic->flags & IMAGE_CUBEMAP) {\n\t\tERR(\"%s: '%s' is invalid: cubemaps are not supported here\", __FUNCTION__, name);\n\t\tgoto cleanup;\n\t}\n\n\t// Process flags, convert to rgba, etc\n\ttex->flags = flags;\n\tProcessImage( tex, pic );\n\n\tif( !R_VkTextureUpload( insert.index, tex, pic, colorspace_hint ))\n\t\tgoto cleanup;\n\n\t// New textures should have refcount = 1 regardless of refcount-aware calls\n\tif (insert.created) {\n\t\ttex->refcount = 1;\n\n\t\t// Mark it as visible from refount-unaware calls if it came from one\n\t\tif (ref_interface)\n\t\t\ttex->ref_interface_visible = true;\n\t}\n\n\tsuccess = true;\n\ncleanup:\n\tif ( !success && insert.created )\n\t\turmomRemoveByIndex(&g_textures.all_desc, insert.index);\n\tif ( pic )\n\t\tgEngine.FS_FreeImage( pic );\n\n\treturn success ? insert.index : 0;\n}\n\nint R_TextureUploadFromFile( const char *name, const byte *buf, size_t size, int flags ) {\n\tconst qboolean force_update = false;\n\tconst qboolean ref_interface = true;\n\treturn loadTextureInternalFromFile(name, buf, size, flags, kColorspaceGamma, force_update, ref_interface);\n}\n\nint R_TextureUploadFromFileExAcquire( const char *filename, colorspace_hint_e colorspace, qboolean force_reload) {\n\tconst qboolean ref_interface = false;\n\treturn loadTextureInternalFromFile( filename, NULL, 0, 0, colorspace, force_reload, ref_interface );\n}\n\n// Unconditionally destroy the texture\nstatic void destroyTexture( uint texnum ) {\n\tASSERT(texnum > 0); // 0 is *unused, cannot be destroyed\n\tASSERT(texnum < COUNTOF(g_textures.all));\n\tvk_texture_t *const tex = g_textures.all + texnum;\n\n\tDEBUG(\"Destroying texture=%d(%s)\", texnum, TEX_NAME(tex));\n\n\tif (tex->refcount > 0)\n\t\tWARN(\"Texture '%s'(%d) has refcount=%d\", TEX_NAME(tex), texnum, tex->refcount);\n\n\tASSERT(URMOM_IS_OCCUPIED(tex->hdr_));\n\n\t// remove from hash table\n\turmomRemoveByIndex(&g_textures.all_desc, texnum);\n\n\t/*\n\t// release source\n\tif( tex->original )\n\t\tgEngine.FS_FreeImage( tex->original );\n\t*/\n\n\tR_VkTextureDestroy( texnum, tex );\n\ttex->refcount = 0;\n\ttex->ref_interface_visible = false;\n\ttex->flags = 0;\n}\n\n// Decrement refcount and destroy the texture if refcount has reached zero\nstatic void releaseTexture( unsigned int texnum, qboolean ref_interface ) {\n\tvk_texture_t *tex;\n\n\tAPROF_SCOPE_DECLARE_BEGIN(free, __FUNCTION__);\n\n\tif( texnum <= 0 )\n\t\tgoto end;\n\n\tASSERT(texnum < COUNTOF(g_textures.all));\n\n\ttex = g_textures.all + texnum;\n\n\t// already freed?\n\tif( !tex->vk.image.image )\n\t\tgoto end;\n\n\t// debug\n\tif( !TEX_NAME(tex)[0] )\n\t{\n\t\tERR(\"%s: trying to free unnamed texture with index %u\", __FUNCTION__, texnum );\n\t\tgoto end;\n\t}\n\n\t// Textures coming from legacy ref_interface_t api are not refcount-friendly\n\t// Track them separately with a flags (and a single refcount ++/--)\n\tif (ref_interface) {\n\t\tif (!tex->ref_interface_visible)\n\t\t\treturn;\n\t\ttex->ref_interface_visible = false;\n\t}\n\n\tDEBUG(\"Releasing texture=%d(%s) refcount=%d\", texnum, TEX_NAME(tex), tex->refcount);\n\tASSERT(tex->refcount > 0);\n\t--tex->refcount;\n\n\tif (tex->refcount > 0)\n\t\tgoto end;\n\n\tdestroyTexture(texnum);\n\nend:\n\tAPROF_SCOPE_END(free);\n}\n\nvoid R_TextureFree( unsigned int texnum ) {\n\tconst qboolean ref_interface = true;\n\treleaseTexture( texnum, ref_interface );\n}\n\n\nint R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update_only ) {\n\t// This functions is effectively is called either from texture module init sequence,\n\t// or from the engine using ref_api_t. So it has ref_api_t refcount semantics.\n\tconst qboolean ref_interface = true;\n\n\t// couldn't loading image\n\tif( !pic )\n\t\treturn 0;\n\n\tif (pic->flags & IMAGE_CUBEMAP) {\n\t\tERR(\"%s: '%s' is invalid: cubemaps are not supported here\", __FUNCTION__, name);\n\t\treturn 0;\n\t}\n\n\tif( !checkTextureName( name ))\n\t\treturn 0;\n\n\turmom_insert_t insert = {0};\n\tif (update_only)\n\t\tinsert.index = urmomFind(&g_textures.all_desc, name);\n\telse\n\t\tinsert = urmomInsert(&g_textures.all_desc, name);\n\n\tif (insert.index < 0) {\n\t\tif (update_only) {\n\t\t\tgEngine.Host_Error( \"%s: couldn't find texture %s for update\\n\", __FUNCTION__, name );\n\t\t} else {\n\t\t\tERR(\"Cannot allocate texture slot for \\\"%s\\\"\", name);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tASSERT(insert.index < COUNTOF(g_textures.all));\n\n\tvk_texture_t *const tex = g_textures.all + insert.index;\n\t// see if already loaded\n\tif (!insert.created && !update_only) {\n\t\tif (ref_interface && !tex->ref_interface_visible) {\n\t\t\ttex->refcount++;\n\t\t\ttex->ref_interface_visible = true;\n\t\t}\n\t\treturn insert.index;\n\t}\n\n\tif( update_only )\n\t\tSetBits( tex->flags, flags );\n\telse\n\t\ttex->flags = flags;\n\n\tProcessImage( tex, pic );\n\n\tif( !R_VkTextureUpload( insert.index, tex, pic, kColorspaceGamma ))\n\t{\n\t\tif ( !update_only && insert.created )\n\t\t\turmomRemoveByIndex(&g_textures.all_desc, insert.index);\n\t\treturn 0;\n\t}\n\n\t// This functions is effectively is called either from texture module init sequence,\n\t// or from the engine using ref_api_t. So it has ref_api_t refcount semantics.\n\tif (insert.created) {\n\t\ttex->refcount = 1;\n\t\ttex->ref_interface_visible = ref_interface;\n\t} else if (ref_interface && !tex->ref_interface_visible) {\n\t\ttex->refcount++;\n\t\ttex->ref_interface_visible = true;\n\t}\n\n\treturn insert.index;\n}\n\nstatic void skyboxParseInfo( const char *name ) {\n\t// Start with default info\n\tg_textures.skybox.info = (r_skybox_info_t) {\n\t\t.exposure = 1.f,\n\t\t.sun_solid_angle = 6.794e-5, // Wikipedia\n\t};\n\n\tchar filename[MAX_STRING];\n\tQ_snprintf(filename, sizeof(filename), \"%s.mat\", name);\n\tbyte *data = gEngine.fsapi->LoadFile( filename, 0, false );\n\n\tif (!data) {\n\t\tINFO(\"Couldn't read skybox info '%s'\", filename);\n\t\tgoto cleanup;\n\t}\n\n\tfor (char *pos = (char*)data;;) {\n\t\tchar key[MAX_STRING];\n\t\tchar value[MAX_STRING];\n\n\t\tpos = COM_ParseFile(pos, key, sizeof(key));\n\t\tif (!pos)\n\t\t\tbreak;\n\n\t\tpos = COM_ParseFile(pos, value, sizeof(value));\n\t\tif (!pos)\n\t\t\tbreak;\n\n\t\tif (Q_strcmp(key, \"exposure\") == 0) {\n\t\t\tfloat exposure = 0;\n\t\t\tif (1 != sscanf(value, \"%f\", &exposure)) {\n\t\t\t\tERR(\"Cannot parse exposure '%s' in skybox info '%s'\", value, filename);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tg_textures.skybox.info.exposure = exposure;\n\t\t\tDEBUG(\"Loaded skybox exposure=%f from '%s'\", exposure, filename);\n\t\t} else if (Q_strcmp(key, \"sun_solid_angle\") == 0) {\n\t\t\tfloat sun_solid_angle = 0;\n\t\t\tif (1 != sscanf(value, \"%f\", &sun_solid_angle)) {\n\t\t\t\tERR(\"Cannot parse sun_solid_angle '%s' in skybox info '%s'\", value, filename);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tg_textures.skybox.info.sun_solid_angle = sun_solid_angle;\n\t\t\tDEBUG(\"Loaded skybox sun_solid_angle=%f from '%s'\", sun_solid_angle, filename);\n\t\t} else {\n\t\t\tWARN(\"Unexpected key '%s' in skybox info '%s'\", key, filename);\n\t\t\tbreak;\n\t\t}\n\t}\n\ncleanup:\n\tif (data)\n\t\tMem_Free(data);\n}\n\nstatic qboolean skyboxLoadF(skybox_slot_e slot, const char *fmt, ...) {\n\tqboolean success = false;\n\tchar buffer[MAX_STRING];\n\n\tva_list argptr;\n\tva_start( argptr, fmt );\n\tQ_vsnprintf( buffer, sizeof buffer, fmt, argptr );\n\tva_end( argptr );\n\n\trgbdata_t *pic = gEngine.FS_LoadImage( buffer, NULL, 0 );\n\tif (!pic)\n\t\treturn false;\n\n\tif (!(pic->flags & IMAGE_CUBEMAP)) {\n\t\tERR(\"%s: '%s' is invalid: skybox is expected to be a cubemap \", __FUNCTION__, buffer);\n\t\tgoto cleanup;\n\t}\n\n\t{\n\t\tuint img_flags = pic->flags;\n\t\tif( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )\n\t\t\timg_flags |= IMAGE_FORCE_RGBA;\n\n\t\tgEngine.Image_Process( &pic, 0, 0, img_flags, 0.f );\n\t}\n\n\t{\n\t\tsuccess = R_VkTexturesSkyboxUpload( buffer, pic, kColorspaceGamma, slot );\n\t}\n\n\tif (success)\n\t\tskyboxParseInfo(buffer);\n\ncleanup:\n\tif (pic)\n\t\tgEngine.FS_FreeImage(pic);\n\n\tif (success)\n\t\tDEBUG( \"Loaded skybox %s\", buffer );\n\n\treturn success;\n}\n\nstatic void skyboxUnload( void ) {\n\tDEBUG(\"%s\", __FUNCTION__);\n\tR_VkTexturesSkyboxUnload();\n\tg_textures.skybox.info = (r_skybox_info_t) {\n\t\t.exposure = 1.f,\n\t};\n\n\tg_textures.skybox.current_name[0] = '\\0';\n}\n\nstatic qboolean skyboxTryLoad( const char *skyboxname, qboolean force_reload ) {\n\t// Check whether we even need skybox\n\tif (!tglob.current_map_has_surf_sky) {\n\t\tDEBUG(\"No SURF_DRAWSKY surfaces in this map, skipping loading skybox\");\n\t\tskyboxUnload();\n\t\treturn true;\n\t}\n\n\tconst_string_view_t basename = svStripExtension(svFromNullTerminated(skyboxname));\n\tif (basename.len > 0 && basename.s[basename.len - 1] == '_')\n\t\tbasename.len--;\n\n\tif ( !basename.len ) {\n\t\tskyboxUnload();\n\t\t// No skybox\n\t\treturn true;\n\t}\n\n\t// Do not reload the same skybox\n\t// TODO except explicit patches reload\n\tif (!force_reload && svCmp(basename, g_textures.skybox.current_name) == 0)\n\t\treturn true;\n\n\t// Unload previous skybox\n\tskyboxUnload();\n\n\t// Try loading original game skybox\n\tconst qboolean original = skyboxLoadF(kSkyboxOriginal, \"gfx/env/%.*s\", basename.len, basename.s);\n\n\t// Try loading newer \"PBR\" upscaled skybox\n\tconst qboolean patched = skyboxLoadF(kSkyboxPatched, \"pbr/env/%.*s\", basename.len, basename.s);\n\n\tif (original || patched)\n\t\tgoto success;\n\n\treturn false;\n\nsuccess:\n\tsvStrncpy(basename, g_textures.skybox.current_name, sizeof(g_textures.skybox.current_name));\n\treturn true;\n}\n\nstatic const char *k_skybox_default = \"desert\";\n\nstatic void skyboxSetup( const char *skyboxname, qboolean is_custom, qboolean force_reload ) {\n\tDEBUG(\"%s: skyboxname='%s' is_custom=%d force_reload=%d\", __FUNCTION__, skyboxname, is_custom, force_reload);\n\n\tif (!skyboxTryLoad(skyboxname, force_reload)) {\n\t\tWARN(\"missed or incomplete skybox '%s', trying default '%s'\", skyboxname, k_skybox_default);\n\t\tif (!skyboxTryLoad(k_skybox_default, force_reload)) {\n\t\t\tERR(\"Failed to load default skybox \\\"%s\\\"\", k_skybox_default);\n\t\t}\n\n\t\treturn;\n\t}\n\n\ttglob.fCustomSkybox = is_custom;\n}\n\nvoid R_TextureSetupCustomSky( int *skyboxTextures ) {\n\tPRINT_NOT_IMPLEMENTED();\n//void R_TextureSetupCustomSky( const char *skyboxname ) {\n\t//const qboolean is_custom = true;\n\t//const qboolean force_reload = false;\n\t//skyboxSetup(skyboxname, is_custom, force_reload);\n}\n\nvoid R_TextureSetupSky( const char *skyboxname, qboolean force_reload ) {\n\tconst qboolean is_custom = false;\n\tskyboxSetup(skyboxname, is_custom, force_reload);\n}\n\nr_skybox_info_t R_TexturesGetSkyboxInfo( void ) {\n\treturn g_textures.skybox.info;\n}\n\n// FIXME move to r_textures_extra.h\n\nint R_TextureFindByNameF( const char *fmt, ...) {\n\tint tex_id = 0;\n\tchar buffer[1024];\n\tva_list argptr;\n\tva_start( argptr, fmt );\n\tvsnprintf( buffer, sizeof buffer, fmt, argptr );\n\tva_end( argptr );\n\n\ttex_id = R_TextureFindByName(buffer);\n\t//DEBUG(\"Looked up texture %s -> %d\", buffer, tex_id);\n\treturn tex_id;\n}\n\nint R_TextureFindByNameLike( const char *texture_name ) {\n\tconst model_t *map = WORLDMODEL;\n\n\t// Try texture name as-is first\n\tint tex_id = R_TextureFindByNameF(\"%s\", texture_name);\n\n\t// Try bsp name\n\tif (!tex_id)\n\t\ttex_id = R_TextureFindByNameF(\"#%s:%s.mip\", map->name, texture_name);\n\n\tif (!tex_id) {\n\t\tconst char *wad = g_map_entities.wadlist;\n\t\tfor (; *wad;) {\n\t\t\tconst char *const wad_end = Q_strchr(wad, ';');\n\t\t\ttex_id = R_TextureFindByNameF(\"%.*s/%s.mip\", wad_end - wad, wad, texture_name);\n\t\t\tif (tex_id)\n\t\t\t\tbreak;\n\t\t\twad = wad_end + 1;\n\t\t}\n\t}\n\n\treturn tex_id ? tex_id : -1;\n}\n\nstruct vk_texture_s *R_TextureGetByIndex( uint index )\n{\n\tASSERT(index >= 0);\n\tASSERT(index < MAX_TEXTURES);\n\treturn g_textures.all + index;\n}\n\nint R_TexturesGetParm( int parm, int arg ) {\n\tconst vk_texture_t *const tex = R_TextureGetByIndex( arg );\n\tif (!URMOM_IS_OCCUPIED(tex->hdr_))\n\t\tWARN(\"%s: accessing empty texture %d\", __FUNCTION__, arg);\n\n\tif (!tex->ref_interface_visible)\n\t\treturn 0;\n\n\tswitch(parm){\n\tcase PARM_TEX_WIDTH:\n\tcase PARM_TEX_SRC_WIDTH: // TODO why is this separate?\n\t\treturn tex->width;\n\tcase PARM_TEX_HEIGHT:\n\tcase PARM_TEX_SRC_HEIGHT:\n\t\treturn tex->height;\n\tcase PARM_TEX_FLAGS:\n\t\treturn tex->flags;\n\tcase PARM_TEX_FILTERING:\n\t\treturn !FBitSet( tex->flags, TF_NEAREST );\n\t// TODO\n\tcase PARM_TEX_SKYBOX:\n\tcase PARM_TEX_SKYTEXNUM:\n\tcase PARM_TEX_LIGHTMAP:\n\tcase PARM_TEX_TARGET:\n\tcase PARM_TEX_TEXNUM:\n\tcase PARM_TEX_DEPTH:\n\tcase PARM_TEX_GLFORMAT:\n\tcase PARM_TEX_ENCODE:\n\tcase PARM_TEX_MIPCOUNT:\n\tcase PARM_TEX_MEMORY:\n\t\treturn 0;\n\tdefault:\n\t\treturn 0;\n\t}\n}\n\nvoid R_TextureAcquire( unsigned int texnum ) {\n\tASSERT(texnum > 0);\n\tvk_texture_t *const tex = R_TextureGetByIndex(texnum);\n\tASSERT(URMOM_IS_OCCUPIED(tex->hdr_));\n\t++tex->refcount;\n\n\tDEBUG(\"Acquiring existing texture %s(%d) refcount=%d\", TEX_NAME(tex), texnum, tex->refcount);\n}\n\nvoid R_TextureRelease( unsigned int texnum ) {\n\tconst qboolean ref_interface = false;\n\treleaseTexture( texnum, ref_interface );\n}\n"
  },
  {
    "path": "ref/vk/r_textures.h",
    "content": "#pragma once\n\n#include \"const.h\" // required for com_model.h, ref_api.h\n#include \"cvardef.h\" // required for ref_api.h\n#include \"com_model.h\" // required for ref_api.h\n#include \"ref_api.h\" // texFlags_t\n\n#define MAX_LIGHTMAPS 256\n\ntypedef struct vk_textures_global_s\n{\n\t// TODO Fix these at compile time statically, akin to BLUE_NOISE_TEXTURE_ID\n\tint defaultTexture;   \t// use for bad textures\n\tint particleTexture;\n\tint whiteTexture;\n\tint grayTexture;\n\tint blackTexture;\n\tint solidskyTexture;\t// quake1 solid-sky layer\n\tint alphaskyTexture;\t// quake1 alpha-sky layer\n\tint lightmapTextures[MAX_LIGHTMAPS];\n\tint dlightTexture;\t// custom dlight texture\n\tint cinTexture;      \t// cinematic texture\n\n\t// TODO wire it up for ref_interface_t return\n\tqboolean fCustomSkybox;\n\n\tqboolean current_map_has_surf_sky;\n} vk_textures_global_t;\n\n// TODO rename this consistently\nextern vk_textures_global_t tglob;\n\n// Exported from r_textures.h\nsize_t CalcImageSize( pixformat_t format, int width, int height, int depth );\nint CalcMipmapCount( int width, int height, int depth, uint32_t flags, qboolean haveBuffer );\nvoid BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags );\n\nqboolean R_TexturesInit( void );\nvoid R_TexturesShutdown( void );\n\n////////////////////////////////////////////////////////////\n// Ref interface functions, exported\n// TODO mark names somehow, ie. R_TextureApi... ?\nint R_TextureFindByName( const char *name );\nconst char* R_TextureGetNameByIndex( unsigned int texnum );\n\nvoid R_TextureSetupCustomSky( int *skyboxTextures );\n\nint R_TextureUploadFromFile( const char *name, const byte *buf, size_t size, int flags );\nint R_TextureUploadFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update_only );\nvoid R_TextureFree( unsigned int texnum );\n\nint R_TexturesGetParm( int parm, int arg );\n\n////////////////////////////////////////////////////////////\n// Extra functions used in ref_vk\nvoid R_TextureAcquire( unsigned int texnum );\nvoid R_TextureRelease( unsigned int texnum );\n\ntypedef enum {\n\tkColorspaceNative,\n\tkColorspaceLinear,\n\tkColorspaceGamma,\n} colorspace_hint_e;\n\ntypedef enum {\n\tkSkyboxPlaceholder,\n\tkSkyboxOriginal,\n\tkSkyboxPatched,\n\n\tkSkybox_COUNT,\n} skybox_slot_e;\n\nint R_TextureUploadFromFileExAcquire( const char *filename, colorspace_hint_e colorspace, qboolean force_reload );\n\nint R_TextureFindByNameF( const char *fmt, ...);\n\n// Tries to find a texture by its short name\n// Full names depend on map name, wad name, etc. This function tries them all.\n// Returns -1 if not found\nint R_TextureFindByNameLike( const char *texture_name );\n\nstruct vk_texture_s;\nstruct vk_texture_s *R_TextureGetByIndex( uint index );\n\nvoid R_TextureSetupSky( const char *skyboxname, qboolean force_reload );\n\ntypedef struct r_skybox_info_s {\n\tfloat exposure;\n\tfloat sun_solid_angle;\n} r_skybox_info_t;\nr_skybox_info_t R_TexturesGetSkyboxInfo( void );\n"
  },
  {
    "path": "ref/vk/ray_materials.md",
    "content": "- orig:\n- HL: (how does this map?)\n\t- Render: Normal (по умолчанию), Color, Texture, Glow, Solid, Additive\n\t- Render FX: разные пульсации, строб, плавные переходы, Constant Glow, Distort, Hologram (Distort + fade)\n- brush:\n\t- opaque:\n\t\t- \"diffuse\": pbr (diffuse, metallic, roughness, ...)\n\t\t- reflective: specular, ...\n\t\t- emissive\n\t- semi-opaque: alpha mask + same as opaque\n\t- transparent:\n\t\t- glass:\n\t\t\t- reflective\n\t\t\t- translucent + refractions\n\t\t- additive:\n\t\t\t- ???\n- studio:\n\t- normal: ~same as opaque\n\t- float: ???\n\t- chrome:\n\t\t- reflective\n\t- glow shell\n\t\t- transparent-additive\n\t- x rendermode ???\n- sprite\n\t- transparent additive:\n\t\t- fake bloom (rtx: just disable)\n\t\t- misc: smoke, explosions (rtx: custom shader?)\n\t- can generally be in all \"HL Render/FX\" modes\n- beams:\n\t- can have custom color?\n\t- transparent additive\n\t\t- rtx: needs custom shader\n\t- can generally be in all \"HL Render/FX\" modes\n- decals: ???\n- rtx proposal:\n- kusok\n\t- bool alpha_mask -- whether need to check alpha mask for boolean anyhit transparency\n\t- render modes:\n\t\t- opaque\n\t\t\t- emissive\n\t\t\t- diffuse\n\t\t\t- specular/reflection\n\t\t- transparent additive -- render_mode\n\t\t\t- force emissive\n\t\t\t- no diffuse, specular, ...\n\t\t- translucent -- render_mode\n\t\t\t- ? diffuse\n\t\t\t- (? specular)/reflection\n\t\t\t- refraction\n\t\t- sky?\n\t\t\t- emissive cubemap\n"
  },
  {
    "path": "ref/vk/rlight.c",
    "content": "/*\ngl_rlight.c - dynamic and static lights\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"const.h\"\n#include \"xash3d_types.h\"\n#include \"com_model.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n#include \"xash3d_mathlib.h\"\n#include \"ref_params.h\"\n\n#include \"vk_common.h\"\n\n/*\n=======================================================================\n\n\tAMBIENT LIGHTING\n\n=======================================================================\n*/\nstatic vec3_t\tg_trace_lightspot;\nstatic vec3_t\tg_trace_lightvec;\nstatic float\tg_trace_fraction;\n\n/*\n=================\nR_RecursiveLightPoint\n=================\n*/\nstatic qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end )\n{\n\tfloat\t\tfront, back, frac, midf;\n\tint\t\ti, map, side, size;\n\tfloat\t\tds, dt, s, t;\n\tint\t\tsample_size;\n\tcolor24\t\t*lm, *dm;\n\tmextrasurf_t\t*info;\n\tmsurface_t\t*surf;\n\tmatrix3x4\t\ttbn;\n\tvec3_t\t\tmid;\n\n\t// didn't hit anything\n\tif( !node || node->contents < 0 )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false;\n\t}\n\n\t// calculate mid point\n\tfront = PlaneDiff( start, node->plane );\n\tback = PlaneDiff( end, node->plane );\n\n\tside = front < 0;\n\tif(( back < 0 ) == side )\n\t\treturn R_RecursiveLightPoint( model, node_child(node, side, model), p1f, p2f, cv, start, end );\n\n\tfrac = front / ( front - back );\n\n\tVectorLerp( start, frac, end, mid );\n\tmidf = p1f + ( p2f - p1f ) * frac;\n\n\t// co down front side\n\tif( R_RecursiveLightPoint( model, node_child(node, side, model), p1f, midf, cv, start, mid ))\n\t\treturn true; // hit something\n\n\tif(( back < 0 ) == side )\n\t{\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\t\treturn false; // didn't hit anything\n\t}\n\n\t// check for impact on this node\n\tsurf = model->surfaces + node_firstsurface(node, model);\n\tVectorCopy( mid, g_trace_lightspot );\n\n\tfor( i = 0; i < node_numsurfaces(node, model); i++, surf++ )\n\t{\n\t\tint\tsmax, tmax;\n\n\t\tinfo = surf->info;\n\n\t\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\t\tcontinue;\t// no lightmaps\n\n\t\ts = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3];\n\t\tt = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3];\n\n\t\tif( s < info->lightmapmins[0] || t < info->lightmapmins[1] )\n\t\t\tcontinue;\n\n\t\tds = s - info->lightmapmins[0];\n\t\tdt = t - info->lightmapmins[1];\n\n\t\tif ( ds > info->lightextents[0] || dt > info->lightextents[1] )\n\t\t\tcontinue;\n\n\t\tcv->r = cv->g = cv->b = cv->a = 0;\n\n\t\tif( !surf->samples )\n\t\t\treturn true;\n\n\t\tsample_size = gEngine.Mod_SampleSizeForFace( surf );\n\t\tsmax = (info->lightextents[0] / sample_size) + 1;\n\t\ttmax = (info->lightextents[1] / sample_size) + 1;\n\t\tds /= sample_size;\n\t\tdt /= sample_size;\n\n\t\tlm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );\n\t\tg_trace_fraction = midf;\n\t\tsize = smax * tmax;\n\t\tdm = NULL;\n\n\t\tif( surf->info->deluxemap )\n\t\t{\n\t\t\tvec3_t\tfaceNormal;\n\n\t\t\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\t\t\tVectorNegate( surf->plane->normal, faceNormal );\n\t\t\telse VectorCopy( surf->plane->normal, faceNormal );\n\n\t\t\t// compute face TBN\n#if 1\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], surf->info->lmvecs[0][1], surf->info->lmvecs[0][2], 0.0f );\n\t\t\tVector4Set( tbn[1], -surf->info->lmvecs[1][0], -surf->info->lmvecs[1][1], -surf->info->lmvecs[1][2], 0.0f );\n\t\t\tVector4Set( tbn[2], faceNormal[0], faceNormal[1], faceNormal[2], 0.0f );\n#else\n\t\t\tVector4Set( tbn[0], surf->info->lmvecs[0][0], -surf->info->lmvecs[1][0], faceNormal[0], 0.0f );\n\t\t\tVector4Set( tbn[1], surf->info->lmvecs[0][1], -surf->info->lmvecs[1][1], faceNormal[1], 0.0f );\n\t\t\tVector4Set( tbn[2], surf->info->lmvecs[0][2], -surf->info->lmvecs[1][2], faceNormal[2], 0.0f );\n#endif\n\t\t\tVectorNormalize( tbn[0] );\n\t\t\tVectorNormalize( tbn[1] );\n\t\t\tVectorNormalize( tbn[2] );\n\t\t\tdm = surf->info->deluxemap + Q_rint( dt ) * smax + Q_rint( ds );\n\t\t}\n\n\t\tfor( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )\n\t\t{\n\t\t\t// FIXME VK uint\tscale = tr.lightstylevalue[surf->styles[map]];\n            uint scale = 255;\n\n\t\t\t/* FIXME VK if( tr.ignore_lightgamma )\n\t\t\t{\n\t\t\t\tcv->r += lm->r * scale;\n\t\t\t\tcv->g += lm->g * scale;\n\t\t\t\tcv->b += lm->b * scale;\n\t\t\t}\n\t\t\telse */\n\t\t\t{\n\t\t\t\tcv->r += LightToTexGamma( lm->r ) * scale;\n\t\t\t\tcv->g += LightToTexGamma( lm->g ) * scale;\n\t\t\t\tcv->b += LightToTexGamma( lm->b ) * scale;\n\t\t\t}\n\t\t\tlm += size; // skip to next lightmap\n\n\t\t\tif( dm != NULL )\n\t\t\t{\n\t\t\t\tvec3_t\tsrcNormal, lightNormal;\n\t\t\t\tfloat\tf = (1.0f / 128.0f);\n\n\t\t\t\tVectorSet( srcNormal, ((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f );\n\t\t\t\tMatrix3x4_VectorIRotate( tbn, srcNormal, lightNormal );\t\t// turn to world space\n\t\t\t\tVectorScale( lightNormal, (float)scale * -1.0f, lightNormal );\t// turn direction from light\n\t\t\t\tVectorAdd( g_trace_lightvec, lightNormal, g_trace_lightvec );\n\t\t\t\tdm += size; // skip to next deluxmap\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// go down back side\n\treturn R_RecursiveLightPoint( model, node_child(node, !side, model), midf, p2f, cv, mid, end );\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\nstatic colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tfloat\tlast_fraction;\n\tint\ti, maxEnts = 1;\n\tcolorVec\tlight, cv;\n\tconst model_t* world_model = WORLDMODEL;\n\n\tif( lspot ) VectorClear( lspot );\n\tif( lvec ) VectorClear( lvec );\n\n\tif( world_model && world_model->lightdata )\n\t{\n\t\tlight.r = light.g = light.b = light.a = 0;\n\t\tlast_fraction = 1.0f;\n\n\t\t// get light from bmodels too\n\t\t// FIXME VK if( CVAR_TO_BOOL( r_lighting_extended ))\n        //   maxEnts = MAX_PHYSENTS;\n\n\t\t// check all the bsp-models\n\t\tfor( i = 0; i < maxEnts; i++ )\n\t\t{\n\t\t\tphysent_t\t*pe = gEngine.EV_GetPhysent( i );\n\t\t\tvec3_t\toffset, start_l, end_l;\n\t\t\tmnode_t\t*pnodes;\n\t\t\tmatrix4x4\tmatrix;\n\n\t\t\tif( !pe )\n\t\t\t\tbreak;\n\n\t\t\tif( !pe->model || pe->model->type != mod_brush )\n\t\t\t\tcontinue; // skip non-bsp models\n\n\t\t\tpnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode];\n\t\t\tVectorSubtract( pe->model->hulls[0].clip_mins, vec3_origin, offset );\n\t\t\tVectorAdd( offset, pe->origin, offset );\n\t\t\tVectorSubtract( start, offset, start_l );\n\t\t\tVectorSubtract( end, offset, end_l );\n\n\t\t\t// rotate start and end into the models frame of reference\n\t\t\tif( !VectorIsNull( pe->angles ))\n\t\t\t{\n\t\t\t\tMatrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, start, start_l );\n\t\t\t\tMatrix4x4_VectorITransform( matrix, end, end_l );\n\t\t\t}\n\n\t\t\tVectorClear( g_trace_lightspot );\n\t\t\tVectorClear( g_trace_lightvec );\n\t\t\tg_trace_fraction = 1.0f;\n\n\t\t\tif( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l ))\n\t\t\t\tcontinue;\t// didn't hit anything\n\n\t\t\tif( g_trace_fraction < last_fraction )\n\t\t\t{\n\t\t\t\tif( lspot ) VectorCopy( g_trace_lightspot, lspot );\n\t\t\t\tif( lvec ) VectorNormalize2( g_trace_lightvec, lvec );\n\t\t\t\tlight.r = Q_min(( cv.r >> 7 ), 255 );\n\t\t\t\tlight.g = Q_min(( cv.g >> 7 ), 255 );\n\t\t\t\tlight.b = Q_min(( cv.b >> 7 ), 255 );\n\t\t\t\tlast_fraction = g_trace_fraction;\n\n\t\t\t\tif(( light.r + light.g + light.b ) != 0 )\n\t\t\t\t\tbreak; // we get light now\n\t\t\t}\n\t\t}\n\t}\n\telse\n\t{\n\t\tlight.r = light.g = light.b = 255;\n\t\tlight.a = 0;\n\t}\n\n\treturn light;\n}\n\n/*\n=================\nR_LightVec\n\ncheck bspmodels to get light from\n=================\n*/\ncolorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec )\n{\n\tcolorVec\tlight = R_LightVecInternal( start, end, lspot, lvec );\n\n\tif( /* FIXME VK CVAR_TO_BOOL( r_lighting_extended ) &&*/ lspot != NULL && lvec != NULL )\n\t{\n\t\t// trying to get light from ceiling (but ignore gradient analyze)\n\t\tif(( light.r + light.g + light.b ) == 0 )\n\t\t\treturn R_LightVecInternal( end, start, lspot, lvec );\n\t}\n\n\treturn light;\n}\n"
  },
  {
    "path": "ref/vk/rt_kusochki.c",
    "content": "#include \"rt_kusochki.h\"\n\n#include \"vk_materials.h\"\n#include \"vulkan/VResource.h\"\n#include \"vk_render.h\" // vk_render_geometry_t\n#include \"vulkan/VBuffer.h\"\n#include \"vk_logs.h\"\n\n#include \"xash3d_mathlib.h\" // VectorCopy, ...\n\n#include \"shaders/ray_interop.h\" // Kusok\ntypedef struct Kusok vk_kusok_data_t;\n\n#define MAX_KUSOCHKI 32768\n\nstatic struct {\n\t// Geometry metadata. Lifetime is similar to geometry lifetime itself.\n\t// Semantically close to render buffer (describes layout for those objects)\n\t// TODO unify with render buffer?\n\t// Needs: STORAGE_BUFFER\n\tvk_buffer_t buffer;\n\tr_debuffer_t alloc;\n\n\tProducer producer;\n} g_kusochki;\n\nvoid RT_KusochkiClear(void) {\n\tR_DEBuffer_Init(&g_kusochki.alloc, MAX_KUSOCHKI / 2, MAX_KUSOCHKI / 2);\n}\n\nvoid RT_KusochkiFlip(void) {\n\tR_DEBuffer_Flip(&g_kusochki.alloc);\n}\n\nrt_kusochki_t RT_KusochkiAllocLong(int count) {\n\t// TODO Proper block allocator, not just double-ended buffer\n\tuint32_t kusochki_offset = R_DEBuffer_Alloc(&g_kusochki.alloc, LifetimeStatic, count, 1);\n\n\tif (kusochki_offset == ALO_ALLOC_FAILED) {\n\t\tgEngine.Con_Printf(S_ERROR \"Maximum number of kusochki exceeded\\n\");\n\t\treturn (rt_kusochki_t){0,0,-1};\n\t}\n\n\treturn (rt_kusochki_t){\n\t\t.offset = kusochki_offset,\n\t\t.count = count,\n\t\t.internal_index__ = 0, // ???\n\t};\n}\n\nuint32_t RT_KusochkiAllocOnce(int count) {\n\t// TODO Proper block allocator\n\tuint32_t kusochki_offset = R_DEBuffer_Alloc(&g_kusochki.alloc, LifetimeDynamic, count, 1);\n\n\tif (kusochki_offset == ALO_ALLOC_FAILED) {\n\t\tgEngine.Con_Printf(S_ERROR \"Maximum number of kusochki exceeded\\n\");\n\t\treturn ALO_ALLOC_FAILED;\n\t}\n\n\treturn kusochki_offset;\n}\n\nvoid RT_KusochkiFree(const rt_kusochki_t *kusochki) {\n\t// TODO block alloc\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const r_vk_material_t *override_material, const vec4_t override_color) {\n\tconst r_vk_material_t *const mat = override_material ? override_material : &geom->material;\n\tASSERT(mat);\n\n\tASSERT(mat->tex_base_color >= 0);\n\tASSERT(mat->tex_base_color < MAX_TEXTURES || mat->tex_base_color == TEX_BASE_SKYBOX);\n\n\tASSERT(mat->tex_roughness >= 0);\n\tASSERT(mat->tex_roughness < MAX_TEXTURES);\n\n\tASSERT(mat->tex_metalness >= 0);\n\tASSERT(mat->tex_metalness < MAX_TEXTURES);\n\n\tASSERT(mat->tex_normalmap >= 0);\n\tASSERT(mat->tex_normalmap < MAX_TEXTURES);\n\n\t// TODO split kusochki into static geometry data and potentially dynamic material data\n\t// This data is static, should never change\n\tkusok->vertex_offset = geom->vertex_offset;\n\tkusok->index_offset = geom->index_offset;\n\n\t// Material data itself is mostly static. Except for animated textures, which just get a new material slot for each frame.\n\tkusok->material = (struct Material){\n\t\t.tex_base_color = mat->tex_base_color,\n\t\t.tex_roughness = mat->tex_roughness,\n\t\t.tex_metalness = mat->tex_metalness,\n\t\t.tex_normalmap = mat->tex_normalmap,\n\n\t\t.roughness = mat->roughness,\n\t\t.metalness = mat->metalness,\n\t\t.normal_scale = mat->normal_scale,\n\t};\n\n\t// TODO emissive is potentially \"dynamic\", not tied to the material directly, as it is specified per-surface in rad files\n\tVectorCopy(geom->emissive, kusok->emissive);\n\tVector4Copy(mat->base_color, kusok->material.base_color);\n\n\tif (override_color) {\n\t\tkusok->material.base_color[0] *= override_color[0];\n\t\tkusok->material.base_color[1] *= override_color[1];\n\t\tkusok->material.base_color[2] *= override_color[2];\n\t\tkusok->material.base_color[3] *= override_color[3];\n\t}\n}\n\n// TODO this function can't really fail. It'd mean that staging is completely broken.\nqboolean RT_KusochkiUpload(uint32_t kusochki_offset, const struct vk_render_geometry_s *geoms, int geoms_count, const r_vk_material_t *override_material, const vec4_t *override_colors) {\n\tconst vk_buffer_lock_t lock_args = {\n\t\t.offset = kusochki_offset * sizeof(vk_kusok_data_t),\n\t\t.size = geoms_count * sizeof(vk_kusok_data_t),\n\t};\n\tconst vk_buffer_locked_t lock = R_VkBufferLock(&g_kusochki.buffer, lock_args);\n\n\tif (!lock.ptr) {\n\t\tgEngine.Con_Printf(S_ERROR \"Couldn't allocate staging for %d kusochkov\\n\", geoms_count);\n\t\treturn false;\n\t}\n\n\tvk_kusok_data_t *const p = lock.ptr;\n\tfor (int i = 0; i < geoms_count; ++i) {\n\t\tconst vk_render_geometry_t *geom = geoms + i;\n\t\tapplyMaterialToKusok(p + i, geom, override_material, override_colors ? override_colors[i] : NULL);\n\t}\n\n\tR_VkBufferUnlock(lock);\n\treturn true;\n}\n\nstatic void produceKusochki(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\t(void)p; (void)ctx;\n\tR_VkBufferStagingCommit(&g_kusochki.buffer, combuf);\n}\n\nqboolean RT_KusochkiInit(void) {\n\tif (!VK_BufferCreate(\"rt_kusochki\", &g_kusochki.buffer, sizeof(vk_kusok_data_t) * MAX_KUSOCHKI,\n\t\tVK_BUFFER_USAGE_STORAGE_BUFFER_BIT  | VK_BUFFER_USAGE_TRANSFER_DST_BIT,\n\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {\n\t\t// FIXME complain, handle\n\t\treturn false;\n\t}\n\n\tg_kusochki.producer = (Producer) {\n\t\t.name = \"kusochki\",\n\t\t.frame_sequence_tag = 0,\n\t\t.produce = produceKusochki,\n\t};\n\n\tR_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = \"kusochki\",\n\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t.buffer = &g_kusochki.buffer,\n\t\t.offset = 0,\n\t\t.size = g_kusochki.buffer.size,\n\t\t.producer = &g_kusochki.producer,\n\t});\n\n\treturn true;\n}\n\nvoid RT_KusochkiShutdown(void) {\n\tVK_BufferDestroy(&g_kusochki.buffer);\n}\n"
  },
  {
    "path": "ref/vk/rt_kusochki.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\" // qboolean\n\n#include <stdint.h> // uint32_t\n\ntypedef struct rt_kusochki_s {\n\tuint32_t offset;\n\tint count;\n\tint internal_index__;\n} rt_kusochki_t;\n\nqboolean RT_KusochkiInit(void);\nvoid RT_KusochkiShutdown(void);\n\nvoid RT_KusochkiClear(void);\n\n// TODO producer->consumed\nvoid RT_KusochkiFlip(void);\n\nrt_kusochki_t RT_KusochkiAllocLong(int count);\nuint32_t RT_KusochkiAllocOnce(int count);\nvoid RT_KusochkiFree(const rt_kusochki_t *kusochki);\n\nstruct vk_render_geometry_s;\nstruct r_vk_material_s;\nqboolean RT_KusochkiUpload(uint32_t kusochki_offset, const struct vk_render_geometry_s *geoms, int geoms_count, const struct r_vk_material_s *override_material, const vec4_t *override_colors);\n"
  },
  {
    "path": "ref/vk/sebastian.py",
    "content": "#!/usr/bin/env python3\n\nimport json\nimport argparse\nimport struct\nimport copy\nfrom spirv import spv\nimport os\n\n# import sys\n# print(sys.argv, file=sys.stderr)\n\nparser = argparse.ArgumentParser(description='Build pipeline descriptor')\nparser.add_argument('--path', '-p', help='Directory where to look for .spv shader files')\nparser.add_argument('--output', '-o', type=argparse.FileType('wb'), help='Compiled pipeline')\nparser.add_argument('--depend', '-d', type=argparse.FileType('w'), help='Generate dependency file (.json)')\nparser.add_argument('--dot', '-g', type=argparse.FileType('w'), help='Generate graphviz dependency tree (.dot)')\nparser.add_argument('pipelines', type=argparse.FileType('r'))\n# TODO strip debug OpName OpLine etc\nargs = parser.parse_args()\n\nspvOp = spv['Op']\nspvOpNames = dict()\nfor name, n in spvOp.items():\n\tspvOpNames[n] = name\n\n#print(\"cwd\", os.path.abspath('.'), file=sys.stderr)\n\nsrc_dir = os.path.abspath(os.path.dirname(args.pipelines.name))\n#print(\"src\", src_dir, file=sys.stderr)\n\n# #dst_dir = os.path.abspath(os.path.dirname(args.output.name))\n# #print(\"dst\", dst_dir, file=sys.stderr)\n\nshaders_path = os.path.abspath(args.path if args.path else '.')\n#print(\"shaders_path\", shaders_path, file=sys.stderr)\n\ndef removeprefix(s, pre):\n\treturn s[len(pre):] if s.startswith(pre) else s\n\n# remove comment lines and fix comma\ndef prepareJSON(path):\n\traw_json = buffer = result = \"\"\n\tonecomment = blockcomment = 0\n\tfor char in path.read():\n\t\tif (len(buffer) > 1):\n\t\t\tbuffer = buffer[1:]\n\t\tbuffer += char\n\t\tif buffer == \"*/\":\n\t\t\tblockcomment = 0\n\t\t\traw_json = raw_json[:-1]\n\t\telif blockcomment:\n\t\t\tcontinue\n\t\telif buffer == \"/*\":\n\t\t\tblockcomment = 1\n\t\telif char == \"\\n\" or char == \"\\r\":\n\t\t\tbuffer = \"\"\n\t\t\tonecomment = 0\n\t\telif char == \"\\t\" or char == \" \" or onecomment:\n\t\t\tcontinue\n\t\telif buffer == \"//\":\n\t\t\traw_json = raw_json[:-1]\n\t\t\tonecomment = 1\n\t\telif buffer != \"\":\n\t\t\traw_json += char\n\traw_json = raw_json.replace(\",]\",\"]\")\n\traw_json = raw_json.replace(\",}\",\"}\")\n\ttry:\n\t\tresult = json.loads(raw_json)\n\t\t#print(json.dumps(result, sort_keys=False, indent=4))\n\texcept json.decoder.JSONDecodeError as exp:\n\t\tprint(\"Decoding JSON has failed\")\n\t\tprint(raw_json)\n\t\traise\n\treturn result\n\nclass Serializer:\n\tdef __init__(self, file):\n\t\tself.file = file\n\n\tdef write(self, v):\n\t\tself.file.write(v)\n\n\tdef writeU32(self, v):\n\t\tself.write(struct.pack('I', v))\n\n\tdef writeBytes(self, v):\n\t\tself.writeU32(len(v))\n\t\tself.write(v)\n\n\tdef writeString(self, v):\n\t\tbs = v.encode('utf-8') + b'\\x00'\n\t\trem = len(bs) % 4\n\t\tif rem != 0:\n\t\t\tbs += b'\\x00' * (4 - rem)\n\t\tself.writeBytes(bs)\n\n\tdef writeArray(self, v):\n\t\tself.writeU32(len(v))\n\t\tfor i in v:\n\t\t\tif isinstance(i, int):\n\t\t\t\tself.writeU32(i)\n\t\t\telse:\n\t\t\t\ti.serialize(self)\n\nclass ImageFormat:\n    VK_FORMAT_UNDEFINED = 0\n    VK_FORMAT_R8G8B8A8_UNORM = 37\n    VK_FORMAT_R16G16B16A16_SFLOAT = 97\n    VK_FORMAT_R32G32B32A32_SFLOAT = 109\n\n    __map = {\n        'Unknown': VK_FORMAT_UNDEFINED,\n        'Rgba32f' : VK_FORMAT_R32G32B32A32_SFLOAT,\n        'Rgba16f' : VK_FORMAT_R16G16B16A16_SFLOAT,\n        'Rgba8' : VK_FORMAT_R8G8B8A8_UNORM,\n        # TODO map more\n    }\n\n    __revmap = None\n\n    def mapToVk(fmt):\n        if not ImageFormat.__revmap:\n            revmap = {}\n            formats = spv['ImageFormat']\n            for k, v in formats.items():\n                if k in ImageFormat.__map:\n                    revmap[v] = ImageFormat.__map[k]\n            ImageFormat.__revmap = revmap\n\n        return ImageFormat.__revmap[fmt]\n\nclass TypeInfo:\n\tTYPE_SAMPLER = 0\n\tTYPE_COMBINED_IMAGE_SAMPLER = 1\n\tTYPE_SAMPLED_IMAGE = 2\n\tTYPE_STORAGE_IMAGE = 3\n\tTYPE_UNIFORM_TEXEL_BUFFER = 4\n\tTYPE_STORAGE_TEXEL_BUFFER = 5\n\tTYPE_UNIFORM_BUFFER = 6\n\tTYPE_STORAGE_BUFFER = 7\n\tTYPE_UNIFORM_BUFFER_DYNAMIC = 8\n\tTYPE_STORAGE_BUFFER_DYNAMIC = 9\n\tTYPE_INPUT_ATTACHMENT = 10\n\tTYPE_INLINE_UNIFORM_BLOCK = 1000138000\n\tTYPE_ACCELERATION_STRUCTURE_KHR = 1000150000\n\tTYPE_MUTABLE_VALVE = 1000351000\n\tTYPE_SAMPLE_WEIGHT_IMAGE_QCOM = 1000440000\n\tTYPE_BLOCK_MATCH_IMAGE_QCOM = 1000440001\n\n\tdef __init__(self, type=None, parent=None, count=1):\n\t\tself.type = parent.type if parent else type\n\t\tself.is_image = parent.is_image if parent else False\n\t\tself.image_format = parent.image_format if parent else None\n\t\tself.count = count\n\n\t\t# TODO self.writable = None\n\t\t# TODO self.readable = None\n\n\tdef __eq__(self, other):\n\t\tif self.type != other.type:\n\t\t\treturn False\n\n\t\tif self.count != other.count:\n\t\t\treturn False\n\n\t\tassert(self.is_image == other.is_image)\n\n\t\tif self.is_image:\n\t\t\tassert(self.image_format != None)\n\t\t\tassert(other.image_format != None)\n\n\t\t\tif self.image_format != other.image_format and self.image_format != 0 and other.image_format != 0:\n\t\t\t\treturn False\n\n\t\treturn True\n\n\tdef __repr__(self):\n\t\treturn 'Type(type=%x is_image=%d image_format=%x count=%d)' % (self.type, self.is_image, self.image_format, self.count)\n\n\tdef serialize(self, out):\n\t\tout.writeU32(self.type)\n\t\tout.writeU32(self.count)\n\t\tif self.is_image:\n\t\t\tout.writeU32(ImageFormat.mapToVk(self.image_format))\n\nclass SpirvNode:\n\tdef __init__(self):\n\t\tself.descriptor_set = None\n\t\tself.binding = None\n\t\tself.name = None\n\t\tself.parent_type_node = None\n\t\tself.type = None\n\t\tself.storage_class = None\n\t\tself.value = None\n\t\tself.count = None\n\n\tdef getType(self):\n\t\tnode = self\n\t\twhile node:\n\t\t\t#print(f\"Checking node {node.name}, {node.storage_class}, {node.type}\")\n\n\t\t\tif node.type:\n\t\t\t\treturn node.type\n\n\t\t\tif node.count:\n\t\t\t\treturn TypeInfo(parent=node.parent_type_node.getType(), count = node.count)\n\n\t\t\tnode = node.parent_type_node\n\t\traise Exception('Couldn\\'t find type for node %s' % self.name)\n\n\tdef setStorageClass(self, storage_class):\n\t\tif storage_class == spv['StorageClass']['Uniform']:\n\t\t\t# TODO support older spirv shaders\n\t\t\t# E.g. for the same\n\t\t\t# layout (set = 0, binding = 7) readonly buffer SBOLights { LightsMetadata m; } lights;\n\n\t\t\t# Newer, with --target-env=vulkan-1.2:\n\t\t\t# OpName %lights \"lights\"\n\t\t\t# OpDecorate %lights DescriptorSet 0\n\t\t\t# OpDecorate %lights Binding 7\n\t\t\t# %lights = OpVariable %_ptr_StorageBuffer_SBOLights StorageBuffer\n\t\t\t#\n\t\t\t# %_ptr_StorageBuffer_SBOLights = OpTypePointer StorageBuffer %SBOLights\n\t\t\t#\n\t\t\t# OpName %SBOLights \"SBOLights\"\n\t\t\t# OpMemberName %SBOLights 0 \"m\"\n\t\t\t# OpMemberDecorate %SBOLights 0 NonWritable\n\t\t\t# OpMemberDecorate %SBOLights 0 Offset 0\n\t\t\t# OpDecorate %SBOLights Block\n\n\t\t\t# Older:\n\t\t\t# OpName %lights \"lights\"\n\t\t\t# OpDecorate %lights DescriptorSet 0\n\t\t\t# OpDecorate %lights Binding 7\n\t\t\t# %lights = OpVariable %_ptr_Uniform_SBOLights Uniform\n\t\t\t#\n\t\t\t# %_ptr_Uniform_SBOLights = OpTypePointer Uniform %SBOLights\n\t\t\t#\n\t\t\t# OpName %SBOLights \"SBOLights\"\n\t\t\t# OpMemberName %SBOLights 0 \"m\"\n\t\t\t# OpMemberDecorate %SBOLights 0 NonWritable\n\t\t\t# OpMemberDecorate %SBOLights 0 Offset 0\n\t\t\t# OpDecorate %SBOLights BufferBlock\n\t\t\t# %SBOLights = OpTypeStruct %LightsMetadata\n\n\t\t\tself.type = TypeInfo(TypeInfo.TYPE_UNIFORM_BUFFER)\n\t\telif storage_class == spv['StorageClass']['StorageBuffer']:\n\t\t\tself.type = TypeInfo(TypeInfo.TYPE_STORAGE_BUFFER)\n\t\tself.storage_class = storage_class\n\t\t#print(f\"Set node {self.name} storage class {storage_class}, type {self.type}\")\n\nclass SpirvContext:\n\tdef __init__(self, nodes_count):\n\t\tself.nodes = [SpirvNode() for i in range(0, nodes_count)]\n\t\t#self.bindings = dict()\n\t\tpass\n\n\tdef getNode(self, index):\n\t\treturn self.nodes[index]\n\ndef spvOpName(ctx, args):\n\tindex = args[0]\n\tname = struct.pack(str(len(args)-1)+'I', *args[1:]).split(b'\\x00')[0].decode('utf8')\n\tctx.getNode(index).name = name\n\t#print('Name for', args[0], name, len(name))\n\ndef spvOpDecorate(ctx, args):\n\tnode = ctx.getNode(args[0])\n\tdecor = args[1]\n\tif decor == spv['Decoration']['DescriptorSet']:\n\t\tnode.descriptor_set = args[2]\n\telif decor == spv['Decoration']['Binding']:\n\t\tnode.binding = args[2]\n\t#else:\n\t\t#print('Decor ', id, decor)\n\ndef spvOpVariable(ctx, args):\n\tparent_type_node = ctx.getNode(args[0])\n\tnode = ctx.getNode(args[1])\n\tstorage_class = args[2]\n\n\tnode.parent_type_node = parent_type_node\n\tnode.setStorageClass(storage_class)\n\t#node.op_type = 'OpVariable'\n\t#print(node.name, \"=(var)>\", parent_type_node.name, args[0])\n\ndef spvOpTypePointer(ctx, args):\n\tnode = ctx.getNode(args[0])\n\tstorage_class = args[1]\n\tparent_type_node = ctx.getNode(args[2])\n\n\tnode.parent_type_node = parent_type_node\n\tnode.setStorageClass(storage_class)\n\t#node.op_type = 'OpTypePointer'\n\t#print(node.name, \"=(ptr)>\", parent_type_node.name, args[2])\n\ndef spvOpTypeAccelerationStructureKHR(ctx, args):\n\tnode = ctx.getNode(args[0])\n\tnode.type = TypeInfo(TypeInfo.TYPE_ACCELERATION_STRUCTURE_KHR)\n\ndef spvOpTypeImage(ctx, args):\n\tnode = ctx.getNode(args[0])\n\tsampled_type = args[1]\n\tdim = args[2]\n\tdepth = args[3]\n\tarrayed = args[4]\n\tms = args[5]\n\tsampled = args[6]\n\timage_format = args[7]\n\n\ttype = TypeInfo.TYPE_STORAGE_IMAGE if sampled == 0 or sampled == 2 else TypeInfo.TYPE_COMBINED_IMAGE_SAMPLER # FIXME ?\n\n\tnode.type = TypeInfo(type)\n\tnode.type.is_image = True\n\tnode.type.image_format = image_format\n\n\tqualifier = None if len(args) < 9 else args[8]\n\n\t#print(f\"{args[0]}: Image(type={sampled_type}, dim={dim}, depth={depth}, arrayed={arrayed}, ms={ms}, sampled={sampled}, image_format={image_format}, qualifier={qualifier})\")\n\ndef spvOpTypeSampledImage(ctx, args):\n\tnode = ctx.getNode(args[0])\n\timage_type = ctx.getNode(args[1])\n\tnode.parent_type_node = image_type\n\n\tnode.type = TypeInfo(TypeInfo.TYPE_COMBINED_IMAGE_SAMPLER)\n\tnode.type.is_image = True\n\tnode.type.image_format = spv['ImageFormat']['Unknown']\n\ndef spvOpTypeArray(ctx, args):\n\tnode = ctx.getNode(args[0])\n\telement_type = ctx.getNode(args[1])\n\tlength_node = args[2]\n\n\tnode.count = ctx.getNode(length_node).value\n\tnode.parent_type_node = element_type\n\n\t#print(f\"{args[0]}: Array(type={args[1]}, length={node.count})\")\n\ndef spvOpSpecConstant(ctx, args):\n\ttype_node = ctx.getNode(args[0])\n\tnode = ctx.getNode(args[1])\n\tvalue = args[2]\n\n\t# TODO mind the type\n\tnode.value = value\n\nspvOpHandlers = {\n\tspvOp['OpName']: spvOpName,\n\tspvOp['OpDecorate']: spvOpDecorate,\n\tspvOp['OpVariable']: spvOpVariable,\n\tspvOp['OpTypePointer']: spvOpTypePointer,\n\tspvOp['OpTypeAccelerationStructureKHR']: spvOpTypeAccelerationStructureKHR,\n\tspvOp['OpTypeImage']: spvOpTypeImage,\n\tspvOp['OpTypeSampledImage']: spvOpTypeSampledImage,\n\tspvOp['OpTypeArray']: spvOpTypeArray,\n\tspvOp['OpSpecConstant']: spvOpSpecConstant,\n}\n\ndef parseSpirv(raw_data):\n\tif len(raw_data) % 4 != 0:\n\t\traise Exception('SPIR-V size should be divisible by 4')\n\n\tsize = len(raw_data) // 4\n\tif size < 5:\n\t\traise Exception('SPIR-V data is too short')\n\n\tdata = struct.unpack(str(size) + 'I', raw_data)\n\n\tif data[0] != spv['MagicNumber']:\n\t\traise Exception('Unexpected magic ' + str(data[0]))\n\n\tnodes_count = data[3]\n\tctx = SpirvContext(nodes_count)\n\n\toff = 5\n\twhile off < size:\n\t\top = data[off] & 0xffff\n\t\twords = data[off] >> 16\n\t\targs = data[off+1:off+words]\n\t\tif op in spvOpHandlers:\n\t\t\tspvOpHandlers[op](ctx, args)\n\t\t#print(spvOpNames[op], args)\n\t\toff += words\n\n\treturn ctx\n\nclass NameIndex:\n\tdef __init__(self):\n\t\tself.__name_to_index = {}\n\t\tself.__all = []\n\n\tdef getIndex(self, name):\n\t\treturn self.__name_to_index[name] if name in self.__name_to_index else -1\n\n\tdef getByName(self, name):\n\t\treturn self.__all[self.__name_to_index[name]] if name in self.__name_to_index else None\n\n\tdef getByIndex(self, index):\n\t\treturn self.__all[index]\n\n\tdef put(self, name, value):\n\t\tif name in self.__name_to_index:\n\t\t\traise Exception('Already have a value for \"%s\"' % (name))\n\n\t\tindex = len(self.__all)\n\t\tself.__all.append(value)\n\t\tself.__name_to_index[name] = index\n\t\treturn index\n\n\tdef __iter__(self):\n\t\treturn iter(self.__all)\n\n\tdef serialize(self, out):\n\t\tout.writeArray(self.__all)\n\n\nclass Resources:\n\tdef __init__(self):\n\t\tself.__storage = NameIndex()\n\t\tself.__map = None\n\n\tdef getIndex(self, name, node, dependency = None):\n\t\tindex = self.__storage.getIndex(name)\n\n\t\tif index >= 0:\n\t\t\tres = self.__storage.getByIndex(index)\n\t\t\tres.checkSameTypeNode(node)\n\t\t\treturn index\n\n\t\treturn self.__storage.put(name, self.Resource(name, node, dependency))\n\n\tdef getMappedIndex(self, index):\n\t\treturn self.__map.get(index, index)\n\n\tdef getByIndex(self, index):\n\t\treturn self.__storage.getByIndex(index)\n\n\tdef getByName(self, name):\n\t\tindex = self.__storage.getIndex(name)\n\t\treturn self.__storage.getByIndex(index)\n\n\tdef __sortDependencies(self):\n\t\t# We should sort only once at export time\n\t\tassert(not self.__map)\n\t\tself.__map = dict()\n\n\t\t# We need to make sure that all the images that are referenced by their prev_ counterparts\n\t\t# have been created (i.e. listed in resources) earlier than all the referencees\n\t\tfor i, r in enumerate(self.__storage):\n\t\t\tdep = r.dependency\n\t\t\tif not dep:\n\t\t\t\tcontinue\n\n\t\t\t# Cannot R/W the same resource\n\t\t\tassert(dep != i)\n\n\t\t\t# Check that their formats are congruent\n\t\t\tdepr = self.__storage.getByIndex(dep)\n\t\t\tif depr.type != r.type:\n\t\t\t\traise Exception('Conflicting types for resource %s (%s) and %s (%s)' % (depr.name, depr.type, r.name, r.type))\n\n\t\t\tif dep < i:\n\t\t\t\tcontinue\n\n\t\t\t# It is an error to have multiple entries for the same pair\n\t\t\tassert(i not in self.__map)\n\t\t\tassert(dep not in self.__map)\n\n\t\t\t# Just swap their externally-referenced indexes, don't swap entries in the array itself\n\t\t\t# This should be enough so that the writer index is less than the reader one\n\t\t\tself.__map[i] = dep\n\t\t\tself.__map[dep] = i\n\t\t\tr.dependency = i\n\n\tdef serialize(self, out):\n\t\tself.__sortDependencies()\n\t\tself.__storage.serialize(out)\n\n\tclass Resource:\n\t\tdef __init__(self, name, node, dependency = None):\n\t\t\tself.name = name\n\t\t\tself.type = node.getType() if node else None\n\t\t\tself.dependency = dependency\n\t\t\tself.producer = None\n\n\t\tdef checkSameTypeNode(self, node):\n\t\t\tif not self.type:\n\t\t\t\tself.type = node.getType()\n\t\t\t\treturn\n\n\t\t\tif self.type != node.getType():\n\t\t\t\traise Exception('Conflicting types for resource \"%s\": %s != %s' % (self.name, self.type, type))\n\n\t\tdef serialize(self, out):\n\t\t\tout.writeString(self.name)\n\t\t\tself.type.serialize(out)\n\t\t\tif self.type.is_image:\n\t\t\t\tout.writeU32((self.dependency + 1) if self.dependency is not None else 0)\n\nresources = Resources()\n\nclass Binding:\n\tSTAGE_VERTEX_BIT = 0x00000001\n\tSTAGE_TESSELLATION_CONTROL_BIT = 0x00000002\n\tSTAGE_TESSELLATION_EVALUATION_BIT = 0x00000004\n\tSTAGE_GEOMETRY_BIT = 0x00000008\n\tSTAGE_FRAGMENT_BIT = 0x00000010\n\tSTAGE_COMPUTE_BIT = 0x00000020\n\tSTAGE_ALL_GRAPHICS = 0x0000001F\n\tSTAGE_ALL = 0x7FFFFFFF\n\tSTAGE_RAYGEN_BIT_KHR = 0x00000100\n\tSTAGE_ANY_HIT_BIT_KHR = 0x00000200\n\tSTAGE_CLOSEST_HIT_BIT_KHR = 0x00000400\n\tSTAGE_MISS_BIT_KHR = 0x00000800\n\tSTAGE_INTERSECTION_BIT_KHR = 0x00001000\n\tSTAGE_CALLABLE_BIT_KHR = 0x00002000\n\tSTAGE_TASK_BIT_NV = 0x00000040\n\tSTAGE_MESH_BIT_NV = 0x00000080\n\tSTAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000\n\n\t# TODO same values for meatpipe.c too\n\tWRITE_BIT = 0x80000000\n\tCREATE_BIT = 0x40000000\n\n\tdef __init__(self, node):\n\t\tself.write = node.name.startswith('out_')\n\t\tself.create = self.write\n\t\tself.index = node.binding\n\t\tself.descriptor_set = node.descriptor_set\n\t\tself.stages = 0\n\n\t\tprev_name = removeprefix(node.name, 'prev_') if node.name.startswith('prev_') else None\n\t\tprev_resource_index = resources.getIndex(prev_name, None) if prev_name else None\n\n\t\tresource_name = removeprefix(node.name, 'out_') if self.write else node.name\n\t\tself.__resource_index = resources.getIndex(resource_name, node, prev_resource_index)\n\n\t\tif prev_resource_index is not None:\n\t\t\tself.create = True\n\n\t\tassert(self.descriptor_set >= 0)\n\t\tassert(self.descriptor_set < 255)\n\n\t\tassert(self.index >= 0)\n\t\tassert(self.index < 255)\n\n\t# For sorting READ-ONLY first, and WRITE later\n\tdef __lt__(self, right):\n\t\tif self.write < right.write:\n\t\t\treturn True\n\t\telif self.write > right.write:\n\t\t\treturn False\n\n\t\tif self.descriptor_set < right.descriptor_set:\n\t\t\treturn True\n\t\telif self.descriptor_set > right.descriptor_set:\n\t\t\treturn False\n\n\t\tif self.index < right.index:\n\t\t\treturn True\n\t\telif self.index > right.index:\n\t\t\treturn False\n\n\t\treturn self.__resource_index < right.__resource_index\n\n\tdef __str__(self):\n\t\treturn f\"Binding(resource_index={self.__resource_index}, ds={self.descriptor_set}, b={self.index}, write={self.write}, stages={self.stages})\"\n\n\tdef __repr__(self):\n\t\treturn self.__str__()\n\n\tdef getResource(self):\n\t\treturn resources.getByIndex(self.__resource_index)\n\n\tdef serialize(self, out):\n\t\theader = (self.descriptor_set << 8) | self.index\n\t\tif self.write:\n\t\t\theader |= Binding.WRITE_BIT\n\t\tif self.create:\n\t\t\theader |= Binding.CREATE_BIT\n\t\tout.writeU32(header)\n\t\tout.writeU32(resources.getMappedIndex(self.__resource_index))\n\t\tout.writeU32(self.stages)\n\nclass Shader:\n\tdef __init__(self, name, fullpath):\n\t\tself.name = name\n\t\tself.__fullpath = fullpath\n\t\tself.__raw_data = None\n\t\tself.__bindings = None\n\t\t#print(name, '=>', len(self.raw_data))\n\n\tdef __str__(self):\n\t\treturn self.name\n\t\t# ret = ''\n\t\t# for index, node in enumerate(self.__spirv.nodes):\n\t\t# \tif node.descriptor_set is not None:\n\t\t# \t\tret += ('[%d:%d] (id=%d) %s\\n' % (node.descriptor_set, node.binding, index, node.name))\n\t\t# return ret\n\n\tdef getRawData(self):\n\t\tif not self.__raw_data:\n\t\t\tself.__raw_data = open(self.__fullpath, 'rb').read()\n\n\t\treturn self.__raw_data\n\n\tdef getBindings(self):\n\t\tif self.__bindings:\n\t\t\treturn self.__bindings\n\n\t\t#print(\"Parsing\", self.name)\n\t\tspirv = parseSpirv(self.getRawData())\n\n\t\tbindings = []\n\t\tfor node in spirv.nodes:\n\t\t\tif node.binding == None or node.descriptor_set == None:\n\t\t\t\tcontinue\n\t\t\tbindings.append(Binding(node))\n\n\t\tself.__bindings = bindings\n\t\treturn self.__bindings\n\n\tdef getFilePath(self):\n\t\treturn self.__fullpath\n\nclass Shaders:\n\t__suffixes = {\n\t\tBinding.STAGE_COMPUTE_BIT: '.comp.spv',\n\t\tBinding.STAGE_RAYGEN_BIT_KHR: '.rgen.spv',\n\t\tBinding.STAGE_ANY_HIT_BIT_KHR: '.rahit.spv',\n\t\tBinding.STAGE_CLOSEST_HIT_BIT_KHR: '.rchit.spv',\n\t\tBinding.STAGE_MISS_BIT_KHR: '.rmiss.spv'\n\t}\n\n\tdef __init__(self):\n\t\tself.__map = dict()\n\t\tself.__shaders = []\n\n\tdef load(self, name, stage):\n\t\tname = name + self.__suffixes[stage]\n\t\tfullpath = os.path.join(shaders_path, name)\n\t\tif name in self.__map:\n\t\t\treturn self.__shaders[self.__map[name]]\n\n\t\tshader = Shader(name, fullpath)\n\n\t\tindex = len(self.__shaders)\n\t\tself.__shaders.append(shader)\n\t\tself.__map[name] = index\n\n\t\treturn shader\n\n\tdef parse(self):\n\t\tfor s in self.__shaders:\n\t\t\ts.getBindings()\n\n\tdef getIndex(self, shader):\n\t\treturn self.__map[shader.name]\n\n\tdef serialize(self, out):\n\t\tout.writeU32(len(self.__shaders))\n\t\tfor shader in self.__shaders:\n\t\t\tout.writeString(shader.name)\n\t\t\tout.writeBytes(shader.getRawData())\n\n\tdef getAllFiles(self):\n\t\treturn [shader.getFilePath() for shader in self.__shaders]\n\nshaders = Shaders()\n\nPIPELINE_COMPUTE = 1\nPIPELINE_RAYTRACING = 2\nNO_SHADER = 0xffffffff\n\nclass Pipeline:\n\tdef __init__(self, name, type_id):\n\t\tself.name = name\n\t\tself.type = type_id\n\t\tself.__shaders = []\n\t\tself.__sorted_bindings = None\n\n\tdef addShader(self, shader_name, stage):\n\t\tshader = shaders.load(shader_name, stage)\n\t\tself.__shaders.append((shader, stage))\n\t\treturn shader\n\n\tdef __mergeBindings(self):\n\t\tbindings = {}\n\t\tfor shader, stage in self.__shaders:\n\t\t\tfor binding in shader.getBindings():\n\t\t\t\taddr = (binding.descriptor_set, binding.index)\n\t\t\t\tif not addr in bindings:\n\t\t\t\t\tbindings[addr] = copy.deepcopy(binding)\n\n\t\t\t\tbindings[addr].stages |= stage\n\t\treturn bindings\n\n\tdef bindings(self):\n\t\tif not self.__sorted_bindings:\n\t\t\tself.__sorted_bindings = sorted(self.__mergeBindings().values())\n\n\t\treturn self.__sorted_bindings\n\n\tdef serialize(self, out):\n\t\t#print(self.name)\n\t\t#for binding in bindings:\n\t\t#print(f\"  ds={binding.descriptor_set}, b={binding.index}, stages={binding.stages:#x}, write={binding.write}\")\n\n\t\tout.writeU32(self.type)\n\t\tout.writeString(self.name)\n\t\tout.writeArray(self.bindings())\n\nclass PipelineRayTracing(Pipeline):\n\t__hit2stage = {\n\t\t'closest': Binding.STAGE_CLOSEST_HIT_BIT_KHR,\n\t\t'any': Binding.STAGE_ANY_HIT_BIT_KHR,\n\t}\n\tdef __init__(self, name, desc):\n\t\tsuper().__init__(name, PIPELINE_RAYTRACING)\n\t\tself.rgen = self.addShader(desc['rgen'], Binding.STAGE_RAYGEN_BIT_KHR)\n\t\tself.miss = [] if not 'miss' in desc else [self.addShader(s, Binding.STAGE_MISS_BIT_KHR) for s in desc['miss']]\n\t\tself.hit = [] if not 'hit' in desc else [self.__loadHit(hit) for hit in desc['hit']]\n\n\tdef serialize(self, out):\n\t\tsuper().serialize(out)\n\t\tout.writeU32(shaders.getIndex(self.rgen))\n\t\tout.writeArray([shaders.getIndex(s) for s in self.miss])\n\n\t\tout.writeU32(len(self.hit))\n\t\tfor hit in self.hit:\n\t\t\tout.writeU32(shaders.getIndex(hit['closest']) if 'closest' in hit else NO_SHADER)\n\t\t\tout.writeU32(shaders.getIndex(hit['any']) if 'any' in hit else NO_SHADER)\n\n\tdef __loadHit(self, hit):\n\t\tret = dict()\n\t\tfor k, v in hit.items():\n\t\t\tret[k] = self.addShader(v, self.__hit2stage[k])\n\t\treturn ret\n\nclass PipelineCompute(Pipeline):\n\tdef __init__(self, name, desc):\n\t\tsuper().__init__(name, PIPELINE_COMPUTE)\n\t\tself.comp = self.addShader(desc['comp'], Binding.STAGE_COMPUTE_BIT)\n\n\tdef serialize(self, out):\n\t\tsuper().serialize(out)\n\t\tout.writeU32(shaders.getIndex(self.comp))\n\ndef parsePipeline(pipelines_desc, name, desc):\n\tif 'inherit' in desc:\n\t\tinherit = pipelines_desc[desc['inherit']]\n\t\tfor k, v in inherit.items():\n\t\t\tif not k in desc:\n\t\t\t\tdesc[k] = v\n\tif 'rgen' in desc:\n\t\treturn PipelineRayTracing(name, desc)\n\telif 'comp' in desc:\n\t\treturn PipelineCompute(name, desc)\n\ndef fillResourceProducers(pipelines):\n\tfor (index, pipeline) in enumerate(pipelines):\n\t\tfor binding in pipeline.bindings():\n\t\t\tif not binding.create:\n\t\t\t\tcontinue\n\n\t\t\tresource = binding.getResource()\n\t\t\tif resource.producer:\n\t\t\t\traise Exception('Resource \"%s\" already has producer \"%s\"' % (resource.name, pipelines.getByIndex(resource.producer).name))\n\n\t\t\tresource.producer = index\n\ndef loadPipelines():\n\tpipelines = NameIndex()\n\tpipelines_desc = prepareJSON(args.pipelines)\n\tfor k, v in pipelines_desc.items():\n\t\tif 'template' in v and v['template']:\n\t\t\tcontinue\n\t\tpipelines.put(k, parsePipeline(pipelines_desc, k, v))\n\n\t# FIXME: currently doesn't work due to denoiser reusing shX_ping/pong resources inbetween passes\n\t# FIXME: blocked by https://github.com/w23/xash3d-fwgs/issues/774\n\t#fillResourceProducers(pipelines)\n\n\treturn pipelines\n\ndef writeOutput(file, pipelines):\n\tMAGIC = bytearray([ord(c) for c in 'MEAT'])\n\tout = Serializer(file)\n\tout.write(MAGIC)\n\tresources.serialize(out)\n\tshaders.serialize(out)\n\tpipelines.serialize(out)\n\npipelines = loadPipelines()\n\nif args.depend:\n\tjson.dump([os.path.relpath(file) for file in shaders.getAllFiles()], args.depend)\n\nif args.output:\n\tshaders.parse()\n\twriteOutput(args.output, pipelines)\n\n# TODO make an integration test for this\nif args.dot:\n\tdest = resources.getByName('dest')\n\t# TODO check that it's an image\n\n\t# Walk the tree\n\tvisited_res = dict()\n\tvisited_pipe = dict()\n\tdef visitResource(res):\n\t\tif res.name in visited_res:\n\t\t\treturn\n\t\tvisited_res[res.name] = True\n\n\t\tproducer = pipelines.getByIndex(res.producer) if res.producer else None\n\n\t\tif producer:\n\t\t\targs.dot.write('%s [shape=oval];\\n' % res.name)\n\t\telse:\n\t\t\targs.dot.write('%s [shape=oval,style=filled,color=\"#ffff80\"];\\n' % res.name)\n\t\t\treturn\n\n\t\tif producer.name in visited_pipe:\n\t\t\treturn\n\t\tvisited_pipe[producer.name] = True\n\n\t\tpipeline = pipelines.getByIndex(res.producer)\n\t\targs.dot.write('%s [shape=box,style=filled,color=\"#ff9090\"];\\n' % pipeline.name)\n\n\t\tfor binding in pipeline.bindings():\n\t\t\tresource = binding.getResource()\n\n\t\t\tif binding.create:\n\t\t\t\targs.dot.write('%s -> %s;\\n' % (pipeline.name, resource.name))\n\t\t\telse:\n\t\t\t\targs.dot.write('%s -> %s;\\n' % (resource.name, pipeline.name))\n\t\t\t\tvisitResource(resource)\n\n\t#args.dot.write('digraph %s {\\n' % args.pipelines.name)\n\targs.dot.write('digraph rendergraph {\\n')\n\tvisitResource(dest)\n\targs.dot.write('}\\n')\n"
  },
  {
    "path": "ref/vk/shaders/2d.frag",
    "content": "#version 450\n\nlayout(set=0,binding=0) uniform sampler2D tex;\n\nlayout(location=0) in vec2 vUv;\nlayout(location=1) in vec4 vColor;\n\nlayout(location = 0) out vec4 outColor;\n\nvoid main() {\n\toutColor = texture(tex, vUv) * vColor;\n}\n"
  },
  {
    "path": "ref/vk/shaders/2d.vert",
    "content": "#version 450\n\nlayout(location=0) in vec2 aPos;\nlayout(location=1) in vec2 aUv;\nlayout(location=2) in vec4 aColor;\n\nlayout(location=0) out vec2 vUv;\nlayout(location=1) out vec4 vColor;\n\nvoid main() {\n\tgl_Position = vec4(aPos, 0., 1.);\n\tvUv = aUv;\n\tvColor = aColor;\n}\n"
  },
  {
    "path": "ref/vk/shaders/additive.rahit",
    "content": "#version 460 core\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_shader_16bit_storage : require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_common.glsl\"\n#include \"ray_kusochki.glsl\"\n\nlayout(location = PAYLOAD_LOCATION_ADDITIVE) rayPayloadInEXT RayPayloadAdditive payload_additive;\n\nhitAttributeEXT vec2 bary;\n\nvoid main() {\n\tconst int instance_kusochki_offset = gl_InstanceCustomIndexEXT;\n\tconst int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT;\n\tconst uint first_index_offset = getKusok(kusok_index).index_offset + gl_PrimitiveID * 3;\n\n\tconst uint vi1 = uint(getIndex(first_index_offset+0)) + getKusok(kusok_index).vertex_offset;\n\tconst uint vi2 = uint(getIndex(first_index_offset+1)) + getKusok(kusok_index).vertex_offset;\n\tconst uint vi3 = uint(getIndex(first_index_offset+2)) + getKusok(kusok_index).vertex_offset;\n\n\tconst vec2 texture_uv = GET_VERTEX(vi1).gl_tc * (1. - bary.x - bary.y) + GET_VERTEX(vi2).gl_tc * bary.x + GET_VERTEX(vi3).gl_tc * bary.y;\n\n\tconst Kusok kusok = getKusok(kusok_index);\n\t// TODO mips\n\tconst uint tex_index = kusok.material.tex_base_color;\n\tconst vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], texture_uv);\n\tconst vec4 mm_color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;\n\tconst vec3 color = texture_color.rgb * mm_color.rgb * texture_color.a * mm_color.a;\n\n\tconst float overshoot = gl_HitTEXT - payload_additive.ray_distance;\n\n\tpayload_additive.color += color * smoothstep(additive_soft_overshoot, 0., overshoot);\n\n\tignoreIntersectionEXT;\n}\n"
  },
  {
    "path": "ref/vk/shaders/alphamask.rahit",
    "content": "#version 460 core\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_shader_16bit_storage : require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout (set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_common.glsl\"\n#include \"ray_kusochki.glsl\"\n\nhitAttributeEXT vec2 bary;\n\nvoid main() {\n    const int instance_kusochki_offset = gl_InstanceCustomIndexEXT;\n    const int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT;\n    const uint first_index_offset = getKusok(kusok_index).index_offset + gl_PrimitiveID * 3;\n\n    const uint vi1 = uint(getIndex(first_index_offset+0)) + getKusok(kusok_index).vertex_offset;\n    const uint vi2 = uint(getIndex(first_index_offset+1)) + getKusok(kusok_index).vertex_offset;\n    const uint vi3 = uint(getIndex(first_index_offset+2)) + getKusok(kusok_index).vertex_offset;\n\n    const vec2 texture_uv = GET_VERTEX(vi1).gl_tc * (1. - bary.x - bary.y) + GET_VERTEX(vi2).gl_tc * bary.x + GET_VERTEX(vi3).gl_tc * bary.y;\n    const uint tex_index = getKusok(kusok_index).material.tex_base_color;\n    const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], texture_uv);\n\n    if (texture_color.a < 0.1) {\n        ignoreIntersectionEXT;\n    }\n}\n"
  },
  {
    "path": "ref/vk/shaders/atrous.glsl",
    "content": "#ifndef ATROUS_CONSTS_DECLARED\n#define ATROUS_CONSTS_DECLARED\n\n#include \"utils.glsl\"\n\n// https://jo.dreggn.org/home/2010_atrous.pdf\n// https://www.shadertoy.com/view/ldKBzG\n#define ATROUS_KERNEL_WIDTH 5\n#define ATROUS_KERNEL_HALF 2\nconst float kATrousKernel[ATROUS_KERNEL_WIDTH] = { 1./16., 1./4., 3./8., 1./4., 1./16. };\n\n\n// Depends on:\n// - image2D normals_gs\n// - image2D position_t\nfloat aTrousSampleWeigth(const ivec2 res, const ivec2 pix, vec3 pos, vec3 shading_normal, ivec2 offset, int step_width, int pix_scale, float phi_normal, float phi_pos, out ivec2 p) {\n\tconst float x_kernel = kATrousKernel[offset.x];\n\tconst float y_kernel = kATrousKernel[offset.y];\n\n\tconst float inv_step_width_sq = 1. / float(step_width * step_width);\n\tp = pix + (offset - ivec2(ATROUS_KERNEL_HALF)) * step_width;\n\tconst ivec2 p_scaled = p * pix_scale;\n\n\tif (any(greaterThanEqual(p_scaled, res)) || any(lessThan(p_scaled, ivec2(0)))) {\n\t\treturn 0.;\n\t}\n\n\t// Weight normals\n\tconst vec4 ngs = imageLoad(normals_gs, p_scaled);\n\tconst vec3 sample_shading_normal = normalDecode(ngs.zw);\n\n\t// TODO should we go geometry_normal too?\n\tconst vec3 sn_diff = sample_shading_normal - shading_normal;\n\tconst float sn_dist2 = max(dot(sn_diff,sn_diff) * inv_step_width_sq, 0.);\n\tconst float weight_sn = min(exp(-(sn_dist2)/phi_normal), 1.0);\n\n\t// Weight positions\n\tconst vec3 sample_position = imageLoad(position_t, p_scaled).xyz;\n\tconst vec3 p_diff = sample_position - pos;\n\t//Original paper: const float p_dist2 = dot(p_diff, p_diff);\n\tconst float p_dist2 = max(dot(p_diff,p_diff) * inv_step_width_sq, 0.);\n\tconst float weight_pos = min(exp(-(p_dist2)/phi_pos),1.0);\n\n\tconst float weight = (weight_pos * weight_sn) * x_kernel * y_kernel;\n\treturn weight;\n}\n\n#endif // ifndef ATROUS_CONSTS_DECLARED\n"
  },
  {
    "path": "ref/vk/shaders/bluenoise.glsl",
    "content": "#ifndef BLUENOISE_H_INCLUDED\n#define BLUENOISE_H_INCLUDED\n\n// Depends on uniform sampler3D blue_noise_texture binding being defined\n\n// Also see vk_textures.h, keep in sync, etc etc\n#define BLUE_NOISE_SIZE 64\n\nvec4 blueNoise(ivec3 v) {\n\tivec3 size = textureSize(blue_noise_texture, 0);\n\treturn texelFetch(blue_noise_texture, v % size, 0);\n}\n\n#endif // ifndef BLUENOISE_H_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/bounce.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#include \"debug.glsl\"\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\n#define RAY_BOUNCE\n#define RAY_QUERY\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;\n\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\nlayout(set = 0, binding = 7) uniform samplerCube skybox;\n\nlayout (set = 0, binding = 8) readonly buffer SBOLights { LightsMetadata m; } lights;\nlayout (set = 0, binding = 9, align = 1) readonly buffer UBOLightClusters {\n\tLightCluster clusters_[MAX_LIGHT_CLUSTERS];\n} light_grid;\n\nlayout(set = 0, binding = 10, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 11, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 12, rgba8) uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 13, rgba8) uniform readonly image2D base_color_a;\n\nlayout(set = 0, binding = 20, rgba16f) uniform writeonly image2D out_indirect_diffuse;\nlayout(set = 0, binding = 21, rgba16f) uniform writeonly image2D out_indirect_specular;\nlayout(set = 0, binding = 22, rgba32f) uniform writeonly image2D out_first_bounce_direction; // for spherical harmonics denoising\nlayout(set = 0, binding = 23, rgba32f) uniform writeonly image2D out_reflection_direction_pdf; // for spatial reconstruction\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_primary_common.glsl\"\n#include \"ray_primary_hit.glsl\"\n#include \"noise.glsl\"\n#include \"brdf.glsl\"\n\n#define LIGHT_POLYGON 1\n#define LIGHT_POINT 1\n\n#include \"light.glsl\"\n\n#include \"trace_simple_blending.glsl\"\n#include \"trace_decals.glsl\"\n#include \"skybox.glsl\"\n\nvoid readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) {\n\tconst vec4 n = imageLoad(normals_gs, uv);\n\tgeometry_normal = normalDecode(n.xy);\n\tshading_normal = normalDecode(n.zw);\n}\n\nbool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) {\n\trayQueryEXT rq;\n\tconst uint flags = 0\n\t\t| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsOpaqueEXT\n\t\t//| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t//| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t;\n\n\tconst float L = 10000.; // TODO Why 10k? Use the real max distance, as in ray_primary.comp\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST, origin, 0., direction, L);\n\twhile (rayQueryProceedEXT(rq)) {\n\t\tif (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT))\n\t\t\tcontinue;\n\n\t\t// alpha test\n\t\t// TODO check other possible ways of doing alpha test. They might be more efficient\n\t\t// (although in this particular primary ray case it's not taht important):\n\t\t// 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive\n\t\t//    texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence.\n\t\t//    Separate pass could be more efficient as it'd be doing the same thing for every invocation.\n\t\t// 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha\n\t\tconst MiniGeometry geom = readCandidateMiniGeometry(rq);\n\t\tconst uint tex_base_color = getKusok(geom.kusok_index).material.tex_base_color;\n\t\t// tex_base_color cannot be TEX_BASE_SKYBOX, as skybox is opaque\n\t\tconst vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv);\n\n\t\tconst float alpha_mask_threshold = .1f;\n\t\tif (texture_color.a >= alpha_mask_threshold) {\n\t\t\trayQueryConfirmIntersectionEXT(rq);\n\t\t}\n\t}\n\n\tif (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionTriangleEXT) {\n\t\tpayload.hit_t.w = L;\n\t\tpayload.emissive.rgb = sampleSkybox(direction);\n\t\treturn false;\n\t} else {\n\t\tpayload.emissive.rgb = vec3(0.); // emissive polygons already calculated in directional polygon lighting pass (remove fireflyes here)\n\t}\n\n\tprimaryRayHit(rq, payload);\n\tconst bool hit_skybox = payload.hit_t.w < 0.;\n\tif (hit_skybox) {\n\t\tpayload.hit_t.w *= -1;\n\t\treturn false;\n\t}\n\n\treturn true;\n}\nconst int INDIRECT_SCALE = 2;\n\nstruct MaterialEx {\n\tMaterialProperties prop;\n\tvec3 geometry_normal, shading_normal;\n\t//TODO float alpha;\n};\n\nconst int kMaxBounces = 1;\nconst vec3 kThrougputExtinctionThreshold = vec3(1e-3);\nconst float kRayNormalOffsetFudge = .01;\n\nivec2 pix;\nvoid computeBounces(MaterialEx mat, vec3 pos, vec3 direction, int bouncesCount, inout vec3 diffuse, inout vec3 specular, inout vec3 first_bounce_direction) {\n\tvec3 throughput = vec3(1.);\n\n\t// TODO split into two distinct passes, see #734\n\tint first_brdf_type = BRDF_TYPE_NONE;\n\n\tfor (int i = 0; i < bouncesCount; ++i) {\n\t\tvec3 bounce_direction;\n\n\t\t// TODO blue noise\n\t\tconst vec2 rnd = vec2(rand01(), rand01());\n\t\tconst int brdf_type = brdfGetSample(rnd, mat.prop, -direction, mat.geometry_normal, mat.shading_normal/* TODO, mat.base_color_a.a*/, bounce_direction, throughput);\n\n\t\tif (brdf_type == BRDF_TYPE_NONE)\n\t\t\treturn;\n\n\t\tif (IS_INVALIDV(throughput)) {\n#ifdef SHADER_DEBUG_ENABLE\n\t\t\tdebugPrintfEXT(\"pix=(%d,%d) pos=(%f,%f,%f) dir=(%f,%f,%f) throughput=invalid\",\n\t\t\t\tpix.x, pix.y, pos.x, pos.y, pos.z, direction.x, direction.y, direction.z);\n#endif\n\t\t\tthroughput = vec3(0.);\n\t\t}\n\n/*\n\t\tif (any(lessThan(throughput, vec3(0.)))) {\n#ifdef SHADER_DEBUG_ENABLE\n\t\t\tdebugPrintfEXT(\"pix=(%d,%d) pos=(%f,%f,%f) dir=(%f,%f,%f) throughput=(%f, %f, %f)\",\n\t\t\t\tpix.x, pix.y, pos.x, pos.y, pos.z, direction.x, direction.y, direction.z,\n\t\t\t\tthroughput.r, throughput.g, throughput.b\n\t\t\t\t);\n#endif\n\t\t}\n*/\n\n\t\tif (all(lessThan(throughput, kThrougputExtinctionThreshold)))\n\t\t\treturn;\n\n\t\tif (first_brdf_type == BRDF_TYPE_NONE)\n\t\t\tfirst_brdf_type = brdf_type;\n\n\t\tRayPayloadPrimary payload;\n\t\tpayload.base_color_a = vec4(0.);\n\t\tpayload.emissive = vec4(0.);\n\t\tpayload.material_rmxx = vec4(0.);\n\t\tpayload.normals_gs = vec4(0.);\n\n\t\tvec3 contribution = vec3(0.);\n\t\tpos += mat.geometry_normal * kRayNormalOffsetFudge;\n\n\t\tvec3 hit_color = vec3(0.);\n\t\t// FIXME figure out how could this happen, see https://github.com/w23/xash3d-fwgs/issues/771\n\t\tif (any(isnan(bounce_direction)))\n\t\t\tbreak;\n\t\tconst bool did_hit = getHit(pos, bounce_direction, payload);\n\t\tMaterialProperties hit_material;\n\t\tconst vec3 hit_pos = payload.hit_t.xyz;\n\t\tconst vec3 hit_shading_normal = normalDecode(payload.normals_gs.zw);\n\t\tif (did_hit) {\n\t\t\ttraceDecals(payload);\n\n\t\t\tvec3 ldiffuse = vec3(0.);\n\t\t\tvec3 lspecular = vec3(0.);\n\t\t\thit_material.base_color = payload.base_color_a.rgb;\n\t\t\thit_material.metalness = payload.material_rmxx.g;\n\t\t\thit_material.roughness = payload.material_rmxx.r;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tif (IS_INVALIDV(hit_pos)) {\n\t\t\tdebugPrintfEXT(\"bounce.comp:%d INVALID hit_pos=(%f,%f,%f)\",\n\t\t\t\t__LINE__, PRIVEC3(hit_pos));\n\t\t\tbreak;\n\t\t}\n#endif\n\t\t\tcomputeLighting(hit_pos, hit_shading_normal, -bounce_direction, hit_material, ldiffuse, lspecular);\n\n\t\t\tif (IS_INVALIDV(ldiffuse)) {\n#ifdef SHADER_DEBUG_ENABLE\n\t\t\t\tdebugPrintfEXT(\"pix=(%d,%d) pos=(%f,%f,%f) dir=(%f,%f,%f) ldiffuse=invalid\",\n\t\t\t\t\tpix.x, pix.y, pos.x, pos.y, pos.z, direction.x, direction.y, direction.z);\n#endif\n\t\t\t\tldiffuse = vec3(0.);\n\t\t\t}\n\n\t\t\tif (IS_INVALIDV(lspecular)) {\n#ifdef SHADER_DEBUG_ENABLE\n\t\t\t\tdebugPrintfEXT(\"pix=(%d,%d) pos=(%f,%f,%f) dir=(%f,%f,%f) lspecular=invalid\",\n\t\t\t\t\tpix.x, pix.y, pos.x, pos.y, pos.z, direction.x, direction.y, direction.z);\n#endif\n\t\t\t\tlspecular = vec3(0.);\n\t\t\t}\n\n\t\t\thit_color = mixFinalColor(payload.base_color_a.rgb, ldiffuse, lspecular, hit_material.metalness);\n\t\t} /* if (did_hit) */ else {\n\t\t\t// not hit -- hit sky\n\t\t\thit_color = payload.emissive.rgb;\n\t\t}\n\n\t\tconst vec4 blend = traceLegacyBlending(pos, bounce_direction, payload.hit_t.w);\n\t\tcontribution = throughput * (SRGBtoLINEAR(blend.rgb) + hit_color * blend.a);\n\n\t\tif (first_brdf_type == BRDF_TYPE_DIFFUSE)\n\t\t\tdiffuse += contribution;\n\t\telse\n\t\t\tspecular += contribution;\n\n\t\tif (i == 0)\n\t\t\tfirst_bounce_direction = hit_pos - pos;\n\n\t\tif (!did_hit)\n\t\t\tbreak;\n\n\t\t// Prepare next bounce state\n\t\tpos = hit_pos;\n\t\tdirection = bounce_direction;\n\t\tmat.prop = hit_material;\n\t\tmat.geometry_normal = normalDecode(payload.normals_gs.xy);\n\t\tmat.shading_normal = hit_shading_normal;\n\n\t\tif (brdf_type == BRDF_TYPE_DIFFUSE) {\n\t\t\tconst vec3 diffuse_color = mix(payload.base_color_a.rgb, vec3(0.), payload.material_rmxx.g);\n\t\t\tthroughput *= diffuse_color;\n\t\t}\n\t} // for bounces\n}\n\nvoid main() {\n\tpix = ivec2(gl_GlobalInvocationID);\n\tconst ivec2 res = ubo.ubo.res / INDIRECT_SCALE;\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\tconst vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.;\n\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DISABLE_GI) != 0) {\n\t\timageStore(out_indirect_diffuse, pix, vec4(0.));\n\t\timageStore(out_indirect_specular, pix, vec4(0.));\n\t\treturn;\n\t}\n\n#ifdef BRDF_COMPARE\n\tg_mat_gltf2 = pix.x > ubo.ubo.res.x / INDIRECT_SCALE / 2.;\n#endif\n\n\tconst vec4 pos_t = imageLoad(position_t, pix * INDIRECT_SCALE);\n\tvec3 diffuse = vec3(0.), specular = vec3(0.);\n\tvec3 first_bounce_direction = vec3(0.), reflection_direction = vec3(0.);\n\tif (pos_t.w > 0.) {\n\t\tconst vec3 origin    = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz;\n\t\tconst vec4 target    = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1);\n\t\tconst vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz);\n\n\t\trand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337 + 12;\n\n\t\tconst vec4 material_data = imageLoad(material_rmxx, pix * INDIRECT_SCALE);\n\t\tconst vec4 base_a = SRGBtoLINEAR(imageLoad(base_color_a, pix * INDIRECT_SCALE));\n\n\t\tvec3 geometry_normal, shading_normal;\n\t\treadNormals(pix * INDIRECT_SCALE, geometry_normal, shading_normal);\n\n\t\tMaterialEx mat;\n\t\tmat.prop.base_color = base_a.rgb;\n\t\tmat.geometry_normal = geometry_normal;\n\t\tmat.shading_normal = shading_normal;\n\n\t\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_ONLY_DIFFUSE_GI) != 0) {\n\t\t\tmat.prop.metalness = 0.0;\n\t\t\tmat.prop.roughness = 1.0;\n\t\t} else {\n\t\t\tmat.prop.metalness = material_data.g;\n\t\t\tmat.prop.roughness = material_data.r;\n\t\t}\n\n\t\tcomputeBounces(mat, pos_t.xyz, direction, kMaxBounces, diffuse, specular, first_bounce_direction);\n\n\t\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_ONLY_DIFFUSE_GI) != 0) {\n\t\t\tdiffuse += specular;\n\t\t\tspecular = vec3(0.);\n\t\t}\n\n\t\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_SEPARATED_REFLECTION) != 0) {\n\t\t\tspecular = vec3(0.);\n\n\t\t\tmat.prop.base_color = vec3(1.);\n\t\t\tmat.prop.metalness = 1.0;\n\t\t\tmat.prop.roughness = material_data.r;\n\n\t\t\tvec3 unusedDiffuse = vec3(0.);\n\t\t\tcomputeBounces(mat, pos_t.xyz, direction, 1, unusedDiffuse, specular, reflection_direction);\n\t\t}\n\t}\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(specular)) {\n\t\tdebugPrintfEXT(\"pix=(%d,%d) specular=(%f, %f, %f)\", pix.x, pix.y, specular.r, specular.g, specular.b);\n\t\tspecular = vec3(0.);\n\t}\n\n\tif (IS_INVALIDV(diffuse)) {\n\t\tdebugPrintfEXT(\"pix=(%d,%d) diffuse=(%f, %f, %f)\", pix.x, pix.y, diffuse.r, diffuse.g, diffuse.b);\n\t\tdiffuse = vec3(0.);\n\t}\n#else\n\tDEBUG_VALIDATE_RANGE_VEC3(\"bounce.diffuse\", diffuse, 0., 1e6);\n\tDEBUG_VALIDATE_RANGE_VEC3(\"bounce.specular\", specular, 0., 1e6);\n#endif\n\n\tif (any(equal(reflection_direction, vec3(0.)))) {\n\t\treflection_direction = first_bounce_direction;\n\t}\n\n\timageStore(out_indirect_diffuse, pix, vec4(diffuse, 0.f));\n\timageStore(out_indirect_specular, pix, vec4(specular, 0.f));\n\timageStore(out_first_bounce_direction, pix, vec4(first_bounce_direction, 0.f)); // for spherical harmonics denoising\n\timageStore(out_reflection_direction_pdf, pix, vec4(reflection_direction, 0.f)); // TODO: calculate specular pdf in w for spatial reconstruction\n}\n"
  },
  {
    "path": "ref/vk/shaders/brdf.glsl",
    "content": "#ifndef BRDF_GLSL_INCLUDED\n#define BRDF_GLSL_INCLUDED\n\n#include \"debug.glsl\"\n\n// TODO math|common.glsl\nconst float kPi = 3.1415926;\nconst float kOneOverPi = 1. / kPi;\n\n//#define BRDF_COMPARE\n#ifdef BRDF_COMPARE\nbool g_mat_gltf2 = true;\n#endif\n\n#ifdef BRDF_COMPARE\n#include \"brdf.h\"\n#else\nstruct MaterialProperties {\n\tvec3 base_color;\n\t//vec3 normal;\n\tfloat metalness;\n\tfloat roughness;\n};\n\nfloat saturate(float x) { return clamp(x, 0.0f, 1.0f); }\nvec3 saturate(vec3 x) { return clamp(x, vec3(0.0f), vec3(1.0f)); }\nfloat mad(float a, float b, float c) { return a * b + c; }\nfloat luminance(vec3 rgb) { return dot(rgb, vec3(0.2126f, 0.7152f, 0.0722f)); }\n#endif\n\n// Ray Tracing Gems, §16.6.3\n// Sample cone oriented in Z+ direction\nvec3 sampleConeZ(vec2 rnd, float cos_theta_max) {\n\tconst float cos_theta = (1. - rnd.x) + rnd.x * cos_theta_max;\n\tconst float sin_theta = sqrt(1. - clamp(cos_theta * cos_theta, 0., 1.));\n\tconst float phi = rnd.y * 2. * kPi;\n\treturn vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);\n}\n\n// Building an Orthonormal Basis, Revisited\n// https://jcgt.org/published/0006/01/01/\nmat3 orthonormalBasisZ(vec3 z) {\n\tconst float s = signP(z.z);\n\tconst float a = -1. / (s + z.z);\n\tconst float b = z.x * z.y * a;\n\treturn mat3(\n\t\tvec3(1. + s * z.x * z.x * a, s * b, -s * z.x),\n\t\tvec3(b, s + z.y * z.y * a, -z.y),\n\t\tz\n\t);\n}\n\nfloat ggxD(float a2, float h_dot_n) {\n\tif (h_dot_n <= 0.)\n\t\treturn 0.;\n\n\t// Need to make alpha^2 non-zero to make sure that smooth surfaces get at least some specular reflections\n\t// Otherwise it will just multiply by zero.\n\t// This also helps with limiting denom to 1e-5\n\ta2 = max(1e-5, a2);\n\t// Limit in case H is \"random\" due to L~=-V\n\th_dot_n = clamp(h_dot_n, 1e-5, 1.);\n\n\tconst float denom = h_dot_n * h_dot_n * (a2 - 1.) + 1.;\n\treturn a2 * kOneOverPi / (denom * denom);\n}\n\nfloat ggxG(float a2, float l_dot_n, float h_dot_l, float n_dot_v, float h_dot_v) {\n\tif (h_dot_l <= 0. || h_dot_v <= 0.)\n\t\treturn 0.;\n\n\tn_dot_v = max(1e-4, n_dot_v);\n\tconst float denom1 = abs(l_dot_n) + sqrt(a2 + (1. - a2) * l_dot_n * l_dot_n);\n\tconst float denom2 = abs(n_dot_v) + sqrt(a2 + (1. - a2) * n_dot_v * n_dot_v);\n\treturn 4. * abs(l_dot_n) * abs(n_dot_v) / (denom1 * denom2);\n}\n\nfloat ggxV(float a2, float l_dot_n, float h_dot_l, float n_dot_v, float h_dot_v) {\n\tif (h_dot_l <= 0. || h_dot_v <= 0.)\n\t\treturn 0.;\n\n\tn_dot_v = max(1e-4, n_dot_v);\n\tconst float denom1 = abs(l_dot_n) + sqrt(a2 + (1. - a2) * l_dot_n * l_dot_n);\n\tconst float denom2 = abs(n_dot_v) + sqrt(a2 + (1. - a2) * n_dot_v * n_dot_v);\n\treturn 1. / (denom1 * denom2);\n}\n\n// glTF 2.0 BRDF sample implementation\n// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation\n// TODO oneMinusVdotH5\n//vec3 gltf2_conductor_fresnel(vec3 f0, vec3 bsdf, float VdotH) {\n//  return bsdf * (f0 + (1. - f0) * pow(1. - abs(VdotH), 5.));\n//}\n//\n//vec3 fresnel_mix(float ior, vec3 base, vec3 layer, float VdotH) {\n//  const float f0 = pow((1.-ior)/(1.+ior), 2.);\n//  const float fr = f0 + (1. - f0) * pow(1. - abs(VdotH), 5.);\n//  return mix(base, layer, fr)\n//}\n\nvoid brdfComputeGltfModel(vec3 N, vec3 L, vec3 V, MaterialProperties material, out float l_dot_n, out vec3 out_diffuse, out vec3 out_specular) {\n\t// L facing away from N can happen fairly often, exit early if so.\n\tl_dot_n = max(0., dot(L, N));\n\tif (l_dot_n == 0.) {\n\t\tout_diffuse = vec3(0.);\n\t\tout_specular = vec3(0.);\n\t\treturn;\n\t}\n\n\tconst float alpha = material.roughness * material.roughness;\n\tconst float a2 = alpha * alpha;\n\n\t// If L ~= -V, then H will be roughly random, maybe mostly orthogonal to both. See ~03:00:00 E360\n\t// If L == V, then H will be NaN.\n\tconst vec3 H = normalize(L + V);\n\n\tconst float h_dot_n = dot(H, N);\n\tconst float n_dot_v = dot(N, V);\n\n\tconst float h_dot_v = max(0., dot(H, V));\n\tconst float h_dot_l = max(0., dot(H, L));\n\n/*\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALID(h_dot_v) || h_dot_v < 0.) {\n\t\tdebugPrintfEXT(\"INVALID h_dot_v=%f, L=(%f,%f,%f) V=(%f,%f,%f) N=(%f, %f, %f)\",\n\t\t\th_dot_v, PRIVEC3(L), PRIVEC3(V), PRIVEC3(N));\n\t}\n\n\tif (IS_INVALID(h_dot_l) || h_dot_l < 0.) {\n\t\tdebugPrintfEXT(\"INVALID h_dot_l=%f, L=(%f,%f,%f) V=(%f,%f,%f) N=(%f, %f, %f)\",\n\t\t\th_dot_l, PRIVEC3(L), PRIVEC3(V), PRIVEC3(N));\n\t}\n#endif\n*/\n\n\t/* Original for when the color is known already.\n\tconst vec3 diffuse_color = mix(material.base_color, vec3(0.), material.metalness);\n\tconst vec3 f0 = mix(vec3(.04), material.base_color, material.metalness);\n\tconst vec3 fresnel = f0 + (vec3(1.) - f0) * pow(1. - abs(h_dot_v), 5.);\n\t*/\n\n\t// Use white for diffuse color here, as compositing happens way later in denoiser/smesitel\n\tconst vec3 diffuse_color = mix(vec3(1.), vec3(0.), material.metalness);\n\n\t// Specular does get the real color, as its contribution is light-direction-dependent\n\tconst vec3 f0 = mix(vec3(.04), material.base_color, material.metalness);\n\tconst float fresnel_factor = max(0., pow(1. - abs(h_dot_v), 5.));\n\tconst vec3 fresnel = vec3(1.) * fresnel_factor + f0 * (1. - fresnel_factor);\n\n\t// This is taken directly from glTF 2.0 spec. It seems incorrect to me: it should not include the base_color twice.\n\t// Note: here diffuse_color doesn't include base_color as it is mixed later, but we can clearly see that\n\t// base_color is still visible in the direct_diff term, which it shouldn't be. See E357 ~37:00\n\t// TODO make a PR against glTF spec with the correct derivation.\n\t//out_diffuse = (vec3(1.) - fresnel) * kOneOverPi * diffuse_color * l_dot_n;\n\n\t// This is the correctly derived diffuse term that doesn't include the base_color twice\n\tout_diffuse = diffuse_color * kOneOverPi * .96 * (1. - fresnel_factor);\n\n\tfloat ggxd = ggxD(a2, h_dot_n);\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALID(ggxd) || ggxd < 0. /* || ggxd > 1.*/) {\n\t\tdebugPrintfEXT(\"N=(%f,%f,%f) L=(%f,%f,%f) V=(%f,%f,%f) a2=%f h_dot_n=%f INVALID ggxd=%f\",\n\t\t\tPRIVEC3(N), PRIVEC3(L), PRIVEC3(V), a2, h_dot_n, ggxd);\n\t\tggxd = 0.;\n\t}\n#endif\n\n\tfloat ggxv = ggxV(a2, l_dot_n, h_dot_l, n_dot_v, h_dot_v);\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALID(ggxv) || ggxv < 0. /*|| ggxv > 1.*/) {\n\t\tdebugPrintfEXT(\"N=(%f,%f,%f) L=(%f,%f,%f) V=(%f,%f,%f) a2=%f h_dot_n=%f INVALID ggxv=%f\",\n\t\t\tPRIVEC3(N), PRIVEC3(L), PRIVEC3(V), a2, h_dot_n, ggxv);\n\t\tggxv = 0.;\n\t}\n#endif\n\n\tout_specular = fresnel * ggxd * ggxv;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(out_diffuse) || any(lessThan(out_diffuse, vec3(0.)))) {\n\t\tdebugPrintfEXT(\"%s:%d INVALID out_diffuse=(%f,%f,%f)\", __FILE__, __LINE__, PRIVEC3(out_diffuse));\n\t}\n\tif (IS_INVALIDV(out_specular) || any(lessThan(out_specular, vec3(0.)))) {\n\t\tdebugPrintfEXT(\"%s:%d INVALID out_specular=(%f,%f,%f)\", __FILE__, __LINE__, PRIVEC3(out_specular));\n\t}\n#endif\n}\n\nvoid evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 out_diffuse, out vec3 out_specular) {\n\tout_diffuse = vec3(0.);\n\tout_specular = vec3(0.);\n\n/* glTF 2.0 BRDF mixing model\nhttps://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation\n\nmaterial = mix(dielectric_brdf, metal_brdf, metallic)\n         = (1.0 - metallic) * dielectric_brdf + metallic * metal_brdf\n\nmetal_brdf =\n  conductor_fresnel(\n    f0 = baseColor,\n    bsdf = specular_brdf(α = roughness ^ 2))\n\ndielectric_brdf =\n  fresnel_mix(\n    ior = 1.5,\n    base = diffuse_brdf(color = baseColor),\n    layer = specular_brdf(α = roughness ^ 2))\n */\n\n/* glTF 2.0 BRDF sample implementation\nhttps://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation\n\nmetal_brdf = specular_brdf(roughness^2) * (baseColor.rgb + (1 - baseColor.rgb) * (1 - abs(VdotH))^5)\ndielectric_brdf = mix(diffuse_brdf(baseColor.rgb), specular_brdf(roughness^2), 0.04 + (1 - 0.04) * (1 - abs(VdotH))^5)\n */\n\n/* suggested simplification\nconst black = 0\n\nc_diff = lerp(baseColor.rgb, black, metallic)\nf0 = lerp(0.04, baseColor.rgb, metallic)\nα = roughness^2\n\nF = f0 + (1 - f0) * (1 - abs(VdotH))^5\n\nf_diffuse = (1 - F) * (1 / π) * c_diff\nf_specular = F * D(α) * G(α) / (4 * abs(VdotN) * abs(LdotN))\n\nmaterial = f_diffuse + f_specular\n */\n\n#ifdef BRDF_COMPARE\nif (g_mat_gltf2) {\n#endif\n\tfloat l_dot_n;\n\tbrdfComputeGltfModel(N, L, V, material, l_dot_n, out_diffuse, out_specular);\n\tout_diffuse *= l_dot_n;\n\tout_specular *= l_dot_n;\n#ifdef BRDF_COMPARE\n} else {\n\t// Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...)\n\tBrdfData data = prepareBRDFData(N, L, V, material);\n\n\t// Ignore V and L rays \"below\" the hemisphere\n\t//if (data.Vbackfacing || data.Lbackfacing) return vec3(0.0f, 0.0f, 0.0f);\n\n\t// Eval specular and diffuse BRDFs\n\tout_specular = evalSpecular(data);\n\n\t// Our renderer mixes base_color into diffuse component later in denoiser/smesitel\n\tdata.diffuseReflectance = baseColorToDiffuseReflectance(vec3(1.), material.metalness);\n\tout_diffuse = evalDiffuse(data);\n\n\t// Combine specular and diffuse layers\n#if COMBINE_BRDFS_WITH_FRESNEL\n\t// Specular is already multiplied by F, just attenuate diffuse\n\tout_diffuse *= vec3(1.) - data.F;\n#endif\n}\n#endif\n}\n\n#define TEST_LOCAL_FRAME\n#ifdef TEST_LOCAL_FRAME\n#ifndef BRDF_H_INCLUDED\n// brdf.h\n// Calculates rotation quaternion from input vector to the vector (0, 0, 1)\n// Input vector must be normalized!\nvec4 getRotationToZAxis(vec3 v) {\n\t// Handle special case when input is exact or near opposite of (0, 0, 1)\n\tif (v.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f);\n\n\treturn normalize(vec4(v.y, -v.x, 0.0f, 1.0f + v.z));\n}\n// Returns the quaternion with inverted rotation\nvec4 invertRotation(vec4 q)\n{\n\treturn vec4(-q.x, -q.y, -q.z, q.w);\n}\n// Optimized point rotation using quaternion\n// Source: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion\nvec3 rotatePoint(vec4 q, vec3 v) {\n\tconst vec3 qAxis = vec3(q.x, q.y, q.z);\n\treturn 2.0f * dot(qAxis, v) * qAxis + (q.w * q.w - dot(qAxis, qAxis)) * v + 2.0f * q.w * cross(qAxis, v);\n}\n// Samples a direction within a hemisphere oriented along +Z axis with a cosine-weighted distribution\n// Source: \"Sampling Transformations Zoo\" in Ray Tracing Gems by Shirley et al.\nvec3 sampleHemisphereCosine(vec2 u, out float pdf) {\n\tfloat a = sqrt(u.x);\n\tfloat b = 2. * kPi * u.y;\n\n\tvec3 result = vec3(\n\t\ta * cos(b),\n\t\ta * sin(b),\n\t\tsqrt(1.0f - u.x));\n\n\tpdf = result.z * kOneOverPi;\n\n\treturn result;\n}\n#endif // #ifndef BRDF_H_INCLUDED\n\nvec3 sampleCosineHemisphereAroundVectorUnnormalizedLocalFrame(vec2 rnd, vec3 n) {\n\tconst vec4 qRotationToZ = getRotationToZAxis(n);\n\tfloat pdf;\n\treturn rotatePoint(invertRotation(qRotationToZ), sampleHemisphereCosine(rnd, pdf));\n}\n#endif // #ifdef TEST_LOCAL_FRAME\n\nvec3 sampleCosineHemisphereAroundVectorUnnormalized(vec2 rnd, vec3 n) {\n\t// Ray Tracing Gems, §16.6.2\n\tconst float a = 1. - 2. * rnd.x;\n\tconst float b = sqrt(1. - a * a);\n\tconst float phi = 2. * kPi * rnd.y;\n\n\t// TODO const float pdf = a / kPi;\n\treturn n + vec3(b * cos(phi), b * sin(phi), a);\n}\n\n// Bounded VNDF Sampling for Smith–GGX Reflections\n// Kenta Eto and Yusuke Tokuyoshi. SIGGRAPH Asia 2023.\n// https://doi.org/10.1145/3610543.3626163\nvec3 SampleGGXReflection ( vec3 i , vec2 alpha , vec2 rand ) {\n\tvec3 i_std = normalize ( vec3 (i. xy * alpha , i.z));\n\t// Sample a spherical cap\n\tfloat phi = 2.0 * kPi * rand .x;\n\tfloat a = saturate ( min ( alpha .x , alpha .y)); // Eq . 6\n\tfloat s = 1.0 + length ( vec2 (i.x , i.y)); // Omit sgn for a <=1\n\tfloat a2 = a * a; float s2 = s * s;\n\tfloat k = (1.0 - a2 ) * s2 / ( s2 + a2 * i.z * i.z); // Eq . 5\n\tfloat b = i.z > 0 ? k * i_std .z : i_std .z;\n\tfloat z = mad (1.0 - rand .y , 1.0 + b , -b);\n\tfloat sinTheta = sqrt ( saturate (1.0 - z * z));\n\tvec3 o_std = vec3( sinTheta * cos ( phi ) , sinTheta * sin ( phi ) , z );\n\t// Compute the microfacet normal m\n\tvec3 m_std = i_std + o_std ;\n\tvec3 m = normalize ( vec3 ( m_std . xy * alpha , m_std .z));\n\t// Return the reflection vector o\n\treturn 2.0 * dot (i , m) * m - i;\n}\n\n#define BRDF_TYPE_NONE 0\n#define BRDF_TYPE_DIFFUSE 1\n#define BRDF_TYPE_SPECULAR 2\n\nint brdfGetSample(vec2 rnd, MaterialProperties material, vec3 view, vec3 geometry_normal, vec3 shading_normal, /*float alpha, */out vec3 out_direction, inout vec3 inout_throughput) {\n#if 1\n\t// See SELECTING BRDF LOBES in 14.3.6 RT Gems 2\n\t// TODO DRY brdfComputeGltfModel\n\t// Use shading_normal as H estimate\n\tconst float h_dot_v = max(0., dot(shading_normal, view));\n\tconst vec3 f0 = mix(vec3(.04), material.base_color, material.metalness);\n\tconst float fresnel_factor = max(0., pow(1. - abs(h_dot_v), 5.));\n\tconst vec3 fresnel = vec3(1.) * fresnel_factor + f0 * (1. - fresnel_factor);\n\tconst float est_spec = luminance(fresnel);\n\tconst float est_diff = (1. - fresnel_factor) * (1. - material.metalness);\n\n\tconst float specular_probability = clamp(est_spec / (est_spec + est_diff), 0., 1.);\n\tconst int brdf_type = (rand01() > specular_probability) ? BRDF_TYPE_DIFFUSE : BRDF_TYPE_SPECULAR;\n\n\tif (brdf_type == BRDF_TYPE_DIFFUSE) {\n#if defined(BRDF_COMPARE) && defined(TEST_LOCAL_FRAME)\nif (g_mat_gltf2) {\n#endif\n\tout_direction = sampleCosineHemisphereAroundVectorUnnormalized(rnd, shading_normal);\n#if defined(BRDF_COMPARE) && defined(TEST_LOCAL_FRAME)\n} else {\n\tout_direction = sampleCosineHemisphereAroundVectorUnnormalizedLocalFrame(rnd, shading_normal);\n}\n#endif\n\n\t\t// Cosine weight is already \"encoded\" in cosine hemisphere sampling.\n\t\tconst vec3 lambert_diffuse_term = vec3(1.f);\n\t\tinout_throughput *= lambert_diffuse_term / (1. - specular_probability);\n\t} else {\n\t\t// Specular\n\n\t\t// nspace = normal vector is Z\n\t\tconst vec4 to_nspace = getRotationToZAxis(shading_normal);\n\t\tconst vec3 view_nspace = rotatePoint(to_nspace, view);\n\t\tconst vec3 sample_dir = SampleGGXReflection(view_nspace, vec2(material.roughness), rnd);\n\t\tout_direction = rotatePoint(invertRotation(to_nspace), sample_dir);\n\n\t\tconst vec3 H = normalize(out_direction + view);\n\t\tconst float h_dot_v = max(0., dot(H, view));\n\t\tconst float fresnel_factor = max(0., pow(1. - abs(h_dot_v), 5.));\n\t\t// TODO use mix(), higher chance for it to be optimized\n\t\tconst vec3 fresnel = vec3(1.) * fresnel_factor + f0 * (1. - fresnel_factor);\n\n\t\tinout_throughput *= fresnel / specular_probability;\n\t}\n\n\tif (dot(out_direction, out_direction) < 1e-5 || dot(out_direction, geometry_normal) <= 0.)\n\t\treturn BRDF_TYPE_NONE;\n\n\tout_direction = normalize(out_direction);\n\n\tconst float throughput_threshold = 1e-3;\n\tif (dot(inout_throughput, inout_throughput) < throughput_threshold)\n\t\treturn BRDF_TYPE_NONE;\n\n\treturn brdf_type;\n#else\n\tint brdf_type = BRDF_TYPE_DIFFUSE;\n\t// FIXME address translucency properly\n\tconst float alpha = (base_a.a);\n\tif (1. > alpha && rand01() > alpha) {\n\t\tbrdf_type = BRDF_TYPE_SPECULAR;\n\t\t// TODO: when not sampling randomly: throughput *= (1. - base_a.a);\n\t\tbounce_direction = normalize(refract(direction, geometry_normal, .95));\n\t\tgeometry_normal = -geometry_normal;\n\t\t//throughput /= base_a.rgb;\n\t} else {\n\t\tif (material.metalness == 1.0f && material.roughness == 0.0f) {\n\t\t\t// Fast path for mirrors\n\t\t\tbrdf_type = BRDF_TYPE_SPECULAR;\n\t\t} else {\n\t\t\t// Decide whether to sample diffuse or specular BRDF (based on Fresnel term)\n\t\t\tconst float brdf_probability = getBrdfProbability(material, -direction, shading_normal);\n\t\t\tif (rand01() < brdf_probability) {\n\t\t\t\tbrdf_type = BRDF_TYPE_SPECULAR;\n\t\t\t\tthroughput /= brdf_probability;\n\t\t\t}\n\t\t}\n\n\t\tconst vec2 u = vec2(rand01(), rand01());\n\t\tvec3 brdf_weight = vec3(0.);\n\t\tif (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight))\n\t\t\treturn false;\n\t\tthroughput *= brdf_weight;\n\t}\n\n\tconst float throughput_threshold = 1e-3;\n\tif (dot(throughput, throughput) < throughput_threshold)\n\t\treturn false;\n\n\treturn true;\n#endif\n}\n\n#endif //ifndef BRDF_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/brdf.h",
    "content": "/* Creative Commons CC0 Public Domain. To the extent possible under law,\nJakub Boksansky has waived all copyright and related or neighboring rights\nto Crash Course in BRDF Implementation Code Sample.\nThis work is published from: Germany. */\n\n// This is a code sample accompanying the \"Crash Course in BRDF Implementation\" article\n// v1.1, February 2021\n\n// Xash3D amendments:\n// - remove c++ compat\n// - port to glsl\n\n#ifndef BRDF_H_INCLUDED\n#define BRDF_H_INCLUDED\n\nfloat rsqrt(float x) { return inversesqrt(x); }\nfloat saturate(float x) { return clamp(x, 0.0f, 1.0f); }\nvec3 saturate(vec3 x) { return clamp(x, vec3(0.0f), vec3(1.0f)); }\n\n#define OUT_PARAMETER(X) out X\n\n// -------------------------------------------------------------------------\n//    Constant Definitions\n// -------------------------------------------------------------------------\n\n#define NONE 0\n\n// NDF definitions\n#define GGX 1\n#define BECKMANN 2\n\n// Specular BRDFs\n#define MICROFACET 1\n#define PHONG 2\n\n// Diffuse BRDFs\n#define LAMBERTIAN 1\n#define OREN_NAYAR 2\n#define DISNEY 3\n#define FROSTBITE 4\n\n// BRDF types\n#define DIFFUSE_TYPE 1\n#define SPECULAR_TYPE 2\n\n// PIs\n#ifndef PI\n#define PI 3.141592653589f\n#endif\n\n#ifndef TWO_PI\n#define TWO_PI (2.0f * PI)\n#endif\n\n#ifndef ONE_OVER_PI\n#define ONE_OVER_PI (1.0f / PI)\n#endif\n\n#ifndef ONE_OVER_TWO_PI\n#define ONE_OVER_TWO_PI (1.0f / TWO_PI)\n#endif\n\n// -------------------------------------------------------------------------\n//    Configuration macros (user editable - set your preferences here)\n// -------------------------------------------------------------------------\n\n// Specify what NDF (GGX or BECKMANN you want to use)\n#ifndef MICROFACET_DISTRIBUTION\n#define MICROFACET_DISTRIBUTION GGX\n//#define MICROFACET_DISTRIBUTION BECKMANN\n#endif\n\n// Specify default specular and diffuse BRDFs\n#ifndef SPECULAR_BRDF\n#define SPECULAR_BRDF MICROFACET\n//#define SPECULAR_BRDF PHONG\n//#define SPECULAR_BRDF NONE\n#endif\n\n#ifndef DIFFUSE_BRDF\n#define DIFFUSE_BRDF LAMBERTIAN\n//#define DIFFUSE_BRDF OREN_NAYAR\n//#define DIFFUSE_BRDF DISNEY\n//#define DIFFUSE_BRDF FROSTBITE\n//#define DIFFUSE_BRDF NONE\n#endif\n\n// Specifies minimal reflectance for dielectrics (when metalness is zero)\n// Nothing has lower reflectance than 2%, but we use 4% to have consistent results with UE4, Frostbite, et al.\n#define MIN_DIELECTRICS_F0 0.04f\n\n// Enable this to weigh diffuse by Fresnel too, otherwise specular and diffuse will be simply added together\n// (this is disabled by default for Frostbite diffuse which is normalized to combine well with GGX Specular BRDF)\n#if DIFFUSE_BRDF != FROSTBITE\n#define COMBINE_BRDFS_WITH_FRESNEL 1\n#endif\n\n// Uncomment this to use \"general\" version of G1 which is not optimized and uses NDF-specific G_Lambda (can be useful for experimenting and debugging)\n//#define Smith_G1 Smith_G1_General\n\n// Enable optimized G2 implementation which includes division by specular BRDF denominator (not available for all NDFs, check macro G2_DIVIDED_BY_DENOMINATOR if it was actually used)\n#define USE_OPTIMIZED_G2 1\n\n// Enable height correlated version of G2 term. Separable version will be used otherwise\n#define USE_HEIGHT_CORRELATED_G2 1\n\n// -------------------------------------------------------------------------\n//    Automatically resolved macros based on preferences (don't edit these)\n// -------------------------------------------------------------------------\n\n// Select distribution function\n#if MICROFACET_DISTRIBUTION == GGX\n#define Microfacet_D GGX_D\n#elif MICROFACET_DISTRIBUTION == BECKMANN\n#define Microfacet_D Beckmann_D\n#endif\n\n// Select G functions (masking/shadowing) depending on selected distribution\n#if MICROFACET_DISTRIBUTION == GGX\n#define Smith_G_Lambda Smith_G_Lambda_GGX\n#elif MICROFACET_DISTRIBUTION == BECKMANN\n#define Smith_G_Lambda Smith_G_Lambda_Beckmann_Walter\n#endif\n\n#ifndef Smith_G1\n// Define version of G1 optimized specifically for selected NDF\n#if MICROFACET_DISTRIBUTION == GGX\n#define Smith_G1 Smith_G1_GGX\n#elif MICROFACET_DISTRIBUTION == BECKMANN\n#define Smith_G1 Smith_G1_Beckmann_Walter\n#endif\n#endif\n\n// Select default specular and diffuse BRDF functions\n#if SPECULAR_BRDF == MICROFACET\n#define evalSpecular evalMicrofacet\n#define sampleSpecular sampleSpecularMicrofacet\n#if MICROFACET_DISTRIBUTION == GGX\n#define sampleSpecularHalfVector sampleGGXVNDF\n#else\n#define sampleSpecularHalfVector sampleBeckmannWalter\n#endif\n#elif SPECULAR_BRDF == PHONG\n#define evalSpecular evalPhong\n#define sampleSpecular sampleSpecularPhong\n#define sampleSpecularHalfVector samplePhong\n#else\n#define evalSpecular evalVoid\n#define sampleSpecular sampleSpecularVoid\n#define sampleSpecularHalfVector sampleSpecularHalfVectorVoid\n#endif\n\n#if MICROFACET_DISTRIBUTION == GGX\n#define specularSampleWeight specularSampleWeightGGXVNDF\n#define specularPdf sampleGGXVNDFReflectionPdf\n#else\n#define specularSampleWeight specularSampleWeightBeckmannWalter\n#define specularPdf sampleBeckmannWalterReflectionPdf\n#endif\n\n#if DIFFUSE_BRDF == LAMBERTIAN\n#define evalDiffuse evalLambertian\n#define diffuseTerm lambertian\n#elif DIFFUSE_BRDF == OREN_NAYAR\n#define evalDiffuse evalOrenNayar\n#define diffuseTerm orenNayar\n#elif DIFFUSE_BRDF == DISNEY\n#define evalDiffuse evalDisneyDiffuse\n#define diffuseTerm disneyDiffuse\n#elif DIFFUSE_BRDF == FROSTBITE\n#define evalDiffuse evalFrostbiteDisneyDiffuse\n#define diffuseTerm frostbiteDisneyDiffuse\n#else\n#define evalDiffuse evalVoid\n#define evalIndirectDiffuse evalIndirectVoid\n#define diffuseTerm none\n#endif\n\n// -------------------------------------------------------------------------\n//    Structures\n// -------------------------------------------------------------------------\n\nstruct MaterialProperties\n{\n\tvec3 base_color;\n\tfloat metalness;\n\n\tvec3 emissive;\n\tfloat roughness;\n\n\t//float transmissivness;\n\t//float opacity;\n};\n\n// Data needed to evaluate BRDF (surface and material properties at given point + configuration of light and normal vectors)\nstruct BrdfData\n{\n\t// Material properties\n\tvec3 specularF0;\n\tvec3 diffuseReflectance;\n\n\t// Roughnesses\n\tfloat roughness;    //< perceptively linear roughness (artist's input)\n\tfloat alpha;        //< linear roughness - often 'alpha' in specular BRDF equations\n\tfloat alphaSquared; //< alpha squared - pre-calculated value commonly used in BRDF equations\n\n\t// Commonly used terms for BRDF evaluation\n\tvec3 F; //< Fresnel term\n\n\t// Vectors\n\tvec3 V; //< Direction to viewer (or opposite direction of incident ray)\n\tvec3 N; //< Shading normal\n\tvec3 H; //< Half vector (microfacet normal)\n\tvec3 L; //< Direction to light (or direction of reflecting ray)\n\n\tfloat NdotL;\n\tfloat NdotV;\n\n\tfloat LdotH;\n\tfloat NdotH;\n\tfloat VdotH;\n\n\t// True when V/L is backfacing wrt. shading normal N\n\tbool Vbackfacing;\n\tbool Lbackfacing;\n};\n\n// -------------------------------------------------------------------------\n//    Utilities\n// -------------------------------------------------------------------------\n\n// Converts Phong's exponent (shininess) to Beckmann roughness (alpha)\n// Source: \"Microfacet Models for Refraction through Rough Surfaces\" by Walter et al.\nfloat shininessToBeckmannAlpha(float shininess) {\n\treturn sqrt(2.0f / (shininess + 2.0f));\n}\n\n// Converts Beckmann roughness (alpha) to Phong's exponent (shininess)\n// Source: \"Microfacet Models for Refraction through Rough Surfaces\" by Walter et al.\nfloat beckmannAlphaToShininess(float alpha) {\n\treturn 2.0f / min(0.9999f, max(0.0002f, (alpha * alpha))) - 2.0f;\n}\n\n// Converts Beckmann roughness (alpha) to Oren-Nayar roughness (sigma)\n// Source: \"Moving Frostbite to Physically Based Rendering\" by Lagarde & de Rousiers\nfloat beckmannAlphaToOrenNayarRoughness(float alpha) {\n\treturn 0.7071067f * atan(alpha);\n}\n\nfloat luminance(vec3 rgb)\n{\n\treturn dot(rgb, vec3(0.2126f, 0.7152f, 0.0722f));\n}\n\nvec3 baseColorToSpecularF0(vec3 base_color, float metalness) {\n\treturn mix(vec3(MIN_DIELECTRICS_F0, MIN_DIELECTRICS_F0, MIN_DIELECTRICS_F0), base_color, metalness);\n}\n\nvec3 baseColorToDiffuseReflectance(vec3 base_color, float metalness)\n{\n\treturn base_color * (1.0f - metalness);\n}\n\nfloat none(const BrdfData data) {\n\treturn 0.0f;\n}\n\nvec3 evalVoid(const BrdfData data) {\n\treturn vec3(0.0f, 0.0f, 0.0f);\n}\n\nvoid evalIndirectVoid(const BrdfData data, vec2 u, OUT_PARAMETER(vec3) rayDirection, OUT_PARAMETER(vec3) weight) {\n\trayDirection = vec3(0.0f, 0.0f, 1.0f);\n\tweight = vec3(0.0f, 0.0f, 0.0f);\n}\n\nvec3 sampleSpecularVoid(vec3 Vlocal, float alpha, float alphaSquared, vec3 specularF0, vec2 u, OUT_PARAMETER(vec3) weight) {\n\tweight = vec3(0.0f, 0.0f, 0.0f);\n\treturn vec3(0.0f, 0.0f, 0.0f);\n}\n\nvec3 sampleSpecularHalfVectorVoid(vec3 Vlocal, vec2 alpha2D, vec2 u) {\n\treturn vec3(0.0f, 0.0f, 0.0f);\n}\n\n// -------------------------------------------------------------------------\n//    Quaternion rotations\n// -------------------------------------------------------------------------\n\n// Calculates rotation quaternion from input vector to the vector (0, 0, 1)\n// Input vector must be normalized!\nvec4 getRotationToZAxis(vec3 v) {\n\n\t// Handle special case when input is exact or near opposite of (0, 0, 1)\n\tif (v.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f);\n\n\treturn normalize(vec4(v.y, -v.x, 0.0f, 1.0f + v.z));\n}\n\n// Calculates rotation quaternion from vector (0, 0, 1) to the input vector\n// Input vector must be normalized!\nvec4 getRotationFromZAxis(vec3 v) {\n\n\t// Handle special case when input is exact or near opposite of (0, 0, 1)\n\tif (v.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f);\n\n\treturn normalize(vec4(-v.y, v.x, 0.0f, 1.0f + v.z));\n}\n\n// Returns the quaternion with inverted rotation\nvec4 invertRotation(vec4 q)\n{\n\treturn vec4(-q.x, -q.y, -q.z, q.w);\n}\n\n// Optimized point rotation using quaternion\n// Source: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion\nvec3 rotatePoint(vec4 q, vec3 v) {\n\tconst vec3 qAxis = vec3(q.x, q.y, q.z);\n\treturn 2.0f * dot(qAxis, v) * qAxis + (q.w * q.w - dot(qAxis, qAxis)) * v + 2.0f * q.w * cross(qAxis, v);\n}\n\n// -------------------------------------------------------------------------\n//    Sampling\n// -------------------------------------------------------------------------\n\n// Samples a direction within a hemisphere oriented along +Z axis with a cosine-weighted distribution\n// Source: \"Sampling Transformations Zoo\" in Ray Tracing Gems by Shirley et al.\nvec3 sampleHemisphere(vec2 u, OUT_PARAMETER(float) pdf) {\n\n\tfloat a = sqrt(u.x);\n\tfloat b = TWO_PI * u.y;\n\n\tvec3 result = vec3(\n\t\ta * cos(b),\n\t\ta * sin(b),\n\t\tsqrt(1.0f - u.x));\n\n\tpdf = result.z * ONE_OVER_PI;\n\n\treturn result;\n}\n\nvec3 sampleHemisphere(vec2 u) {\n\tfloat pdf;\n\treturn sampleHemisphere(u, pdf);\n}\n\n// For sampling of all our diffuse BRDFs we use cosine-weighted hemisphere sampling, with PDF equal to (NdotL/PI)\nfloat diffusePdf(float NdotL) {\n\treturn NdotL * ONE_OVER_PI;\n}\n\n// -------------------------------------------------------------------------\n//    Fresnel\n// -------------------------------------------------------------------------\n\n// Schlick's approximation to Fresnel term\n// f90 should be 1.0, except for the trick used by Schuler (see 'shadowedF90' function)\nvec3 evalFresnelSchlick(vec3 f0, float f90, float NdotS)\n{\n\treturn f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f);\n}\n\n// Schlick's approximation to Fresnel term calculated using spherical gaussian approximation\n// Source: https://seblagarde.wordpress.com/2012/06/03/spherical-gaussien-approximation-for-blinn-phong-phong-and-fresnel/ by Lagarde\nvec3 evalFresnelSchlickSphericalGaussian(vec3 f0, float f90, float NdotV)\n{\n\treturn f0 + (f90 - f0) * exp2((-5.55473f * NdotV - 6.983146f) * NdotV);\n}\n\n// Schlick's approximation to Fresnel term with Hoffman's improvement using the Lazanyi's error term\n// Source: \"Fresnel Equations Considered Harmful\" by Hoffman\n// Also see slides http://renderwonk.com/publications/mam2019/naty_mam2019.pdf for examples and explanation of f82 term\nvec3 evalFresnelHoffman(vec3 f0, float f82, float f90, float NdotS)\n{\n\tconst float alpha = 6.0f; //< Fixed to 6 in order to put peak angle for Lazanyi's error term at 82 degrees (f82)\n\tvec3 a = 17.6513846f * (f0 - f82) + 8.166666f * (vec3(1.0f, 1.0f, 1.0f) - f0);\n\treturn saturate(f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f) - a * NdotS * pow(1.0f - NdotS, alpha));\n}\n\nvec3 evalFresnel(vec3 f0, float f90, float NdotS)\n{\n\t// Default is Schlick's approximation\n\treturn evalFresnelSchlick(f0, f90, NdotS);\n}\n\n// Attenuates F90 for very low F0 values\n// Source: \"An efficient and Physically Plausible Real-Time Shading Model\" in ShaderX7 by Schuler\n// Also see section \"Overbright highlights\" in Hoffman's 2010 \"Crafting Physically Motivated Shading Models for Game Development\" for discussion\n// IMPORTANT: Note that when F0 is calculated using metalness, it's value is never less than MIN_DIELECTRICS_F0, and therefore,\n// this adjustment has no effect. To be effective, F0 must be authored separately, or calculated in different way. See main text for discussion.\nfloat shadowedF90(vec3 F0) {\n\t// This scaler value is somewhat arbitrary, Schuler used 60 in his article. In here, we derive it from MIN_DIELECTRICS_F0 so\n\t// that it takes effect for any reflectance lower than least reflective dielectrics\n\t//const float t = 60.0f;\n\tconst float t = (1.0f / MIN_DIELECTRICS_F0);\n\treturn min(1.0f, t * luminance(F0));\n}\n\n// -------------------------------------------------------------------------\n//    Lambert\n// -------------------------------------------------------------------------\n\nfloat lambertian(const BrdfData data) {\n\treturn 1.0f;\n}\n\nvec3 evalLambertian(const BrdfData data) {\n\treturn data.diffuseReflectance * (ONE_OVER_PI * data.NdotL);\n}\n\n// -------------------------------------------------------------------------\n//    Phong\n// -------------------------------------------------------------------------\n\n// For derivation see \"Phong Normalization Factor derivation\" by Giesen\nfloat phongNormalizationTerm(float shininess) {\n\n\treturn (1.0f + shininess) * ONE_OVER_TWO_PI;\n}\n\nvec3 evalPhong(const BrdfData data) {\n\n\t// First convert roughness to shininess (Phong exponent)\n\tfloat shininess = beckmannAlphaToShininess(data.alpha);\n\n\tvec3 R = reflect(-data.L, data.N);\n\treturn data.specularF0 * (phongNormalizationTerm(shininess) * pow(max(0.0f, dot(R, data.V)), shininess) * data.NdotL);\n}\n\n// Samples a Phong distribution lobe oriented along +Z axis\n// Source: \"Sampling Transformations Zoo\" in Ray Tracing Gems by Shirley et al.\nvec3 samplePhong(vec3 Vlocal, float shininess, vec2 u, OUT_PARAMETER(float) pdf) {\n\n\tfloat cosTheta = pow(1.0f - u.x, 1.0f / (1.0f + shininess));\n\tfloat sinTheta = sqrt(1.0f - cosTheta * cosTheta);\n\n\tfloat phi = TWO_PI * u.y;\n\n\tpdf = phongNormalizationTerm(shininess) * pow(cosTheta, shininess);\n\n\treturn vec3(\n\t\tcos(phi) * sinTheta,\n\t\tsin(phi) * sinTheta,\n\t\tcosTheta);\n}\n\nvec3 samplePhong(vec3 Vlocal, vec2 alpha2D, vec2 u) {\n\tfloat shininess = beckmannAlphaToShininess(dot(alpha2D, vec2(0.5f, 0.5f)));\n\tfloat pdf;\n\treturn samplePhong(Vlocal, shininess, u, pdf);\n}\n\n// Sampling the specular BRDF based on Phong, includes normalization term\nvec3 sampleSpecularPhong(vec3 Vlocal, float alpha, float alphaSquared, vec3 specularF0, vec2 u, OUT_PARAMETER(vec3) weight) {\n\n\t// First convert roughness to shininess (Phong exponent)\n\tfloat shininess = beckmannAlphaToShininess(alpha);\n\n\tfloat pdf;\n\tvec3 LPhong = samplePhong(Vlocal, shininess, u, pdf);\n\n\t// Sampled LPhong is in \"lobe space\" - where Phong lobe is centered around +Z axis\n\t// We need to rotate it in direction of perfect reflection\n\tvec3 Nlocal = vec3(0.0f, 0.0f, 1.0f);\n\tvec3 lobeDirection = reflect(-Vlocal, Nlocal);\n\tvec3 Llocal = rotatePoint(getRotationFromZAxis(lobeDirection), LPhong);\n\n\t// Calculate the weight of the sample\n\tvec3 Rlocal = reflect(-Llocal, Nlocal);\n\tfloat NdotL = max(0.00001f, dot(Nlocal, Llocal));\n\tweight = max(vec3(0.0f, 0.0f, 0.0f), specularF0 * NdotL);\n\n\t// Unoptimized formula was:\n\t//weight = specularF0 * (phongNormalizationTerm(shininess) * pow(max(0.0f, dot(Rlocal, Vlocal)), shininess) * NdotL) / pdf;\n\n\treturn Llocal;\n}\n\n// -------------------------------------------------------------------------\n//    Oren-Nayar\n// -------------------------------------------------------------------------\n\n// Based on Oren-Nayar's qualitative model\n// Source: \"Generalization of Lambert's Reflectance Model\" by Oren & Nayar\nfloat orenNayar(BrdfData data) {\n\n\t// Oren-Nayar roughness (sigma) is in radians - use conversion from Beckmann roughness here\n\tfloat sigma = beckmannAlphaToOrenNayarRoughness(data.alpha);\n\n\tfloat thetaV = acos(data.NdotV);\n\tfloat thetaL = acos(data.NdotL);\n\n\tfloat alpha = max(thetaV, thetaL);\n\tfloat beta = min(thetaV, thetaL);\n\n\t// Calculate cosine of azimuth angles difference - by projecting L and V onto plane defined by N. Assume L, V, N are normalized.\n\tvec3 l = data.L - data.NdotL * data.N;\n\tvec3 v = data.V - data.NdotV * data.N;\n\tfloat cosPhiDifference = dot(normalize(v), normalize(l));\n\n\tfloat sigma2 = sigma * sigma;\n\tfloat A = 1.0f - 0.5f * (sigma2 / (sigma2 + 0.33f));\n\tfloat B = 0.45f * (sigma2 / (sigma2 + 0.09f));\n\n\treturn (A + B * max(0.0f, cosPhiDifference) * sin(alpha) * tan(beta));\n}\n\nvec3 evalOrenNayar(const BrdfData data) {\n\treturn data.diffuseReflectance * (orenNayar(data) * ONE_OVER_PI * data.NdotL);\n}\n\n// -------------------------------------------------------------------------\n//    Disney\n// -------------------------------------------------------------------------\n\n// Disney's diffuse term\n// Source \"Physically-Based Shading at Disney\" by Burley\nfloat disneyDiffuse(const BrdfData data) {\n\n\tfloat FD90MinusOne = 2.0f * data.roughness * data.LdotH * data.LdotH - 0.5f;\n\n\tfloat FDL = 1.0f + (FD90MinusOne * pow(1.0f - data.NdotL, 5.0f));\n\tfloat FDV = 1.0F + (FD90MinusOne * pow(1.0f - data.NdotV, 5.0f));\n\n\treturn FDL * FDV;\n}\n\nvec3 evalDisneyDiffuse(const BrdfData data) {\n\treturn data.diffuseReflectance * (disneyDiffuse(data) * ONE_OVER_PI * data.NdotL);\n}\n\n// Frostbite's version of Disney diffuse with energy normalization.\n// Source: \"Moving Frostbite to Physically Based Rendering\" by Lagarde & de Rousiers\nfloat frostbiteDisneyDiffuse(const BrdfData data) {\n\tfloat energyBias = 0.5f * data.roughness;\n\tfloat energyFactor = mix(1.0f, 1.0f / 1.51f, data.roughness);\n\n\tfloat FD90MinusOne = energyBias + 2.0 * data.LdotH * data.LdotH * data.roughness - 1.0f;\n\n\tfloat FDL = 1.0f + (FD90MinusOne * pow(1.0f - data.NdotL, 5.0f));\n\tfloat FDV = 1.0f + (FD90MinusOne * pow(1.0f - data.NdotV, 5.0f));\n\n\treturn FDL * FDV * energyFactor;\n}\n\nvec3 evalFrostbiteDisneyDiffuse(const BrdfData data) {\n\treturn data.diffuseReflectance * (frostbiteDisneyDiffuse(data) * ONE_OVER_PI * data.NdotL);\n}\n\n// -------------------------------------------------------------------------\n//    Smith G term\n// -------------------------------------------------------------------------\n\n// Function to calculate 'a' parameter for lambda functions needed in Smith G term\n// This is a version for shape invariant (isotropic) NDFs\n// Note: makse sure NdotS is not negative\nfloat Smith_G_a(float alpha, float NdotS) {\n\treturn NdotS / (max(0.00001f, alpha) * sqrt(1.0f - min(0.99999f, NdotS * NdotS)));\n}\n\n// Lambda function for Smith G term derived for GGX distribution\nfloat Smith_G_Lambda_GGX(float a) {\n\treturn (-1.0f + sqrt(1.0f + (1.0f / (a * a)))) * 0.5f;\n}\n\n// Lambda function for Smith G term derived for Beckmann distribution\n// This is Walter's rational approximation (avoids evaluating of error function)\n// Source: \"Real-time Rendering\", 4th edition, p.339 by Akenine-Moller et al.\n// Note that this formulation is slightly optimized and different from Walter's\nfloat Smith_G_Lambda_Beckmann_Walter(float a) {\n\tif (a < 1.6f) {\n\t\treturn (1.0f - (1.259f - 0.396f * a) * a) / ((3.535f + 2.181f * a) * a);\n\t\t//return ((1.0f + (2.276f + 2.577f * a) * a) / ((3.535f + 2.181f * a) * a)) - 1.0f; //< Walter's original\n\t} else {\n\t\treturn 0.0f;\n\t}\n}\n\n// Smith G1 term (masking function)\n// This non-optimized version uses NDF specific lambda function (G_Lambda) resolved bia macro based on selected NDF\nfloat Smith_G1_General(float a) {\n\treturn 1.0f / (1.0f + Smith_G_Lambda(a));\n}\n\n// Smith G1 term (masking function) optimized for GGX distribution (by substituting G_Lambda_GGX into G1)\nfloat Smith_G1_GGX(float a) {\n\tfloat a2 = a * a;\n\treturn 2.0f / (sqrt((a2 + 1.0f) / a2) + 1.0f);\n}\n\n// Smith G1 term (masking function) further optimized for GGX distribution (by substituting G_a into G1_GGX)\nfloat Smith_G1_GGX(float alpha, float NdotS, float alphaSquared, float NdotSSquared) {\n\treturn 2.0f / (sqrt(((alphaSquared * (1.0f - NdotSSquared)) + NdotSSquared) / NdotSSquared) + 1.0f);\n}\n\n// Smith G1 term (masking function) optimized for Beckmann distribution (by substituting G_Lambda_Beckmann_Walter into G1)\n// Source: \"Microfacet Models for Refraction through Rough Surfaces\" by Walter et al.\nfloat Smith_G1_Beckmann_Walter(float a) {\n\tif (a < 1.6f) {\n\t\treturn ((3.535f + 2.181f * a) * a) / (1.0f + (2.276f + 2.577f * a) * a);\n\t} else {\n\t\treturn 1.0f;\n\t}\n}\n\nfloat Smith_G1_Beckmann_Walter(float alpha, float NdotS, float alphaSquared, float NdotSSquared) {\n\treturn Smith_G1_Beckmann_Walter(Smith_G_a(alpha, NdotS));\n}\n\n// Smith G2 term (masking-shadowing function)\n// Separable version assuming independent (uncorrelated) masking and shadowing, uses G1 functions for selected NDF\nfloat Smith_G2_Separable(float alpha, float NdotL, float NdotV) {\n\tfloat aL = Smith_G_a(alpha, NdotL);\n\tfloat aV = Smith_G_a(alpha, NdotV);\n\treturn Smith_G1(aL) * Smith_G1(aV);\n}\n\n// Smith G2 term (masking-shadowing function)\n// Height correlated version - non-optimized, uses G_Lambda functions for selected NDF\nfloat Smith_G2_Height_Correlated(float alpha, float NdotL, float NdotV) {\n\tfloat aL = Smith_G_a(alpha, NdotL);\n\tfloat aV = Smith_G_a(alpha, NdotV);\n\treturn 1.0f / (1.0f + Smith_G_Lambda(aL) + Smith_G_Lambda(aV));\n}\n\n// Smith G2 term (masking-shadowing function) for GGX distribution\n// Separable version assuming independent (uncorrelated) masking and shadowing - optimized by substituing G_Lambda for G_Lambda_GGX and\n// dividing by (4 * NdotL * NdotV) to cancel out these terms in specular BRDF denominator\n// Source: \"Moving Frostbite to Physically Based Rendering\" by Lagarde & de Rousiers\n// Note that returned value is G2 / (4 * NdotL * NdotV) and therefore includes division by specular BRDF denominator\nfloat Smith_G2_Separable_GGX_Lagarde(float alphaSquared, float NdotL, float NdotV) {\n\tfloat a = NdotV + sqrt(alphaSquared + NdotV * (NdotV - alphaSquared * NdotV));\n\tfloat b = NdotL + sqrt(alphaSquared + NdotL * (NdotL - alphaSquared * NdotL));\n\treturn 1.0f / (a * b);\n}\n\n// Smith G2 term (masking-shadowing function) for GGX distribution\n// Height correlated version - optimized by substituing G_Lambda for G_Lambda_GGX and dividing by (4 * NdotL * NdotV) to cancel out\n// the terms in specular BRDF denominator\n// Source: \"Moving Frostbite to Physically Based Rendering\" by Lagarde & de Rousiers\n// Note that returned value is G2 / (4 * NdotL * NdotV) and therefore includes division by specular BRDF denominator\nfloat Smith_G2_Height_Correlated_GGX_Lagarde(float alphaSquared, float NdotL, float NdotV) {\n\tfloat a = NdotV * sqrt(alphaSquared + NdotL * (NdotL - alphaSquared * NdotL));\n\tfloat b = NdotL * sqrt(alphaSquared + NdotV * (NdotV - alphaSquared * NdotV));\n\treturn 0.5f / (a + b);\n}\n\n// Smith G2 term (masking-shadowing function) for GGX distribution\n// Height correlated version - approximation by Hammon\n// Source: \"PBR Diffuse Lighting for GGX + Smith Microsurfaces\", slide 84 by Hammon\n// Note that returned value is G2 / (4 * NdotL * NdotV) and therefore includes division by specular BRDF denominator\nfloat Smith_G2_Height_Correlated_GGX_Hammon(float alpha, float NdotL, float NdotV) {\n\treturn 0.5f / (mix(2.0f * NdotL * NdotV, NdotL + NdotV, alpha));\n}\n\n// A fraction G2/G1 where G2 is height correlated can be expressed using only G1 terms\n// Source: \"Implementing a Simple Anisotropic Rough Diffuse Material with Stochastic Evaluation\", Appendix A by Heitz & Dupuy\nfloat Smith_G2_Over_G1_Height_Correlated(float alpha, float alphaSquared, float NdotL, float NdotV) {\n\tfloat G1V = Smith_G1(alpha, NdotV, alphaSquared, NdotV * NdotV);\n\tfloat G1L = Smith_G1(alpha, NdotL, alphaSquared, NdotL * NdotL);\n\treturn G1L / (G1V + G1L - G1V * G1L);\n}\n\n// Evaluates G2 for selected configuration (GGX/Beckmann, optimized/non-optimized, separable/height-correlated)\n// Note that some paths aren't optimized too much...\n// Also note that when USE_OPTIMIZED_G2 is specified, returned value will be: G2 / (4 * NdotL * NdotV) if GG-X is selected\nfloat Smith_G2(float alpha, float alphaSquared, float NdotL, float NdotV) {\n\n#if USE_OPTIMIZED_G2 && (MICROFACET_DISTRIBUTION == GGX)\n#if USE_HEIGHT_CORRELATED_G2\n#define G2_DIVIDED_BY_DENOMINATOR 1\n\treturn Smith_G2_Height_Correlated_GGX_Lagarde(alphaSquared, NdotL, NdotV);\n#else\n#define G2_DIVIDED_BY_DENOMINATOR 1\n\treturn Smith_G2_Separable_GGX_Lagarde(alphaSquared, NdotL, NdotV);\n#endif\n#else\n#if USE_HEIGHT_CORRELATED_G2\n\treturn Smith_G2_Height_Correlated(alpha, NdotL, NdotV);\n#else\n\treturn Smith_G2_Separable(alpha, NdotL, NdotV);\n#endif\n#endif\n\n}\n\n// -------------------------------------------------------------------------\n//    Normal distribution functions\n// -------------------------------------------------------------------------\n\nfloat Beckmann_D(float alphaSquared, float NdotH)\n{\n\tfloat cos2Theta = NdotH * NdotH;\n\tfloat numerator = exp((cos2Theta - 1.0f) / (alphaSquared * cos2Theta));\n\tfloat denominator = PI * alphaSquared * cos2Theta * cos2Theta;\n\treturn numerator / denominator;\n}\n\nfloat GGX_D(float alphaSquared, float NdotH) {\n\tfloat b = ((alphaSquared - 1.0f) * NdotH * NdotH + 1.0f);\n\treturn alphaSquared / (PI * b * b);\n}\n\n// -------------------------------------------------------------------------\n//    Microfacet model\n// -------------------------------------------------------------------------\n\n// Samples a microfacet normal for the GGX distribution using VNDF method.\n// Source: \"Sampling the GGX Distribution of Visible Normals\" by Heitz\n// See also https://hal.inria.fr/hal-00996995v1/document and http://jcgt.org/published/0007/04/01/\n// Random variables 'u' must be in <0;1) interval\n// PDF is 'G1(NdotV) * D'\nvec3 sampleGGXVNDF(vec3 Ve, vec2 alpha2D, vec2 u) {\n\n\t// Section 3.2: transforming the view direction to the hemisphere configuration\n\tvec3 Vh = normalize(vec3(alpha2D.x * Ve.x, alpha2D.y * Ve.y, Ve.z));\n\n\t// Section 4.1: orthonormal basis (with special case if cross product is zero)\n\tfloat lensq = Vh.x * Vh.x + Vh.y * Vh.y;\n\tvec3 T1 = lensq > 0.0f ? vec3(-Vh.y, Vh.x, 0.0f) * rsqrt(lensq) : vec3(1.0f, 0.0f, 0.0f);\n\tvec3 T2 = cross(Vh, T1);\n\n\t// Section 4.2: parameterization of the projected area\n\tfloat r = sqrt(u.x);\n\tfloat phi = TWO_PI * u.y;\n\tfloat t1 = r * cos(phi);\n\tfloat t2 = r * sin(phi);\n\tfloat s = 0.5f * (1.0f + Vh.z);\n\tt2 = mix(sqrt(1.0f - t1 * t1), t2, s);\n\n\t// Section 4.3: reprojection onto hemisphere\n\tvec3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0f, 1.0f - t1 * t1 - t2 * t2)) * Vh;\n\n\t// Section 3.4: transforming the normal back to the ellipsoid configuration\n\treturn normalize(vec3(alpha2D.x * Nh.x, alpha2D.y * Nh.y, max(0.0f, Nh.z)));\n}\n\n// PDF of sampling a reflection vector L using 'sampleGGXVNDF'.\n// Note that PDF of sampling given microfacet normal is (G1 * D) when vectors are in local space (in the hemisphere around shading normal).\n// Remaining terms (1.0f / (4.0f * NdotV)) are specific for reflection case, and come from multiplying PDF by jacobian of reflection operator\nfloat sampleGGXVNDFReflectionPdf(float alpha, float alphaSquared, float NdotH, float NdotV, float LdotH) {\n\tNdotH = max(0.00001f, NdotH);\n\tNdotV = max(0.00001f, NdotV);\n\treturn (GGX_D(max(0.00001f, alphaSquared), NdotH) * Smith_G1_GGX(alpha, NdotV, alphaSquared, NdotV * NdotV)) / (4.0f * NdotV);\n}\n\n// \"Walter's trick\" is an adjustment of alpha value for Walter's sampling to reduce maximal weight of sample to about 4\n// Source: \"Microfacet Models for Refraction through Rough Surfaces\" by Walter et al., page 8\nfloat waltersTrick(float alpha, float NdotV) {\n\treturn (1.2f - 0.2f * sqrt(abs(NdotV))) * alpha;\n}\n\n// PDF of sampling a reflection vector L using 'sampleBeckmannWalter'.\n// Note that PDF of sampling given microfacet normal is (D * NdotH). Remaining terms (1.0f / (4.0f * LdotH)) are specific for\n// reflection case, and come from multiplying PDF by jacobian of reflection operator\nfloat sampleBeckmannWalterReflectionPdf(float alpha, float alphaSquared, float NdotH, float NdotV, float LdotH) {\n\tNdotH = max(0.00001f, NdotH);\n\tLdotH = max(0.00001f, LdotH);\n\treturn Beckmann_D(max(0.00001f, alphaSquared), NdotH) * NdotH / (4.0f * LdotH);\n}\n\n// Samples a microfacet normal for the Beckmann distribution using walter's method.\n// Source: \"Microfacet Models for Refraction through Rough Surfaces\" by Walter et al.\n// PDF is 'D * NdotH'\nvec3 sampleBeckmannWalter(vec3 Vlocal, vec2 alpha2D, vec2 u) {\n\tfloat alpha = dot(alpha2D, vec2(0.5f, 0.5f));\n\n\t// Equations (28) and (29) from Walter's paper for Beckmann distribution\n\tfloat tanThetaSquared = -(alpha * alpha) * log(1.0f - u.x);\n\tfloat phi = TWO_PI * u.y;\n\n\t// Calculate cosTheta and sinTheta needed for conversion to H vector\n\tfloat cosTheta = rsqrt(1.0f + tanThetaSquared);\n\tfloat sinTheta = sqrt(1.0f - cosTheta * cosTheta);\n\n\t// Convert sampled spherical coordinates to H vector\n\treturn normalize(vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta));\n}\n\n// Weight for the reflection ray sampled from GGX distribution using VNDF method\nfloat specularSampleWeightGGXVNDF(float alpha, float alphaSquared, float NdotL, float NdotV, float HdotL, float NdotH) {\n#if USE_HEIGHT_CORRELATED_G2\n\treturn Smith_G2_Over_G1_Height_Correlated(alpha, alphaSquared, NdotL, NdotV);\n#else\n\treturn Smith_G1_GGX(alpha, NdotL, alphaSquared, NdotL * NdotL);\n#endif\n}\n\n// Weight for the reflection ray sampled from Beckmann distribution using Walter's method\nfloat specularSampleWeightBeckmannWalter(float alpha, float alphaSquared, float NdotL, float NdotV, float HdotL, float NdotH) {\n\treturn (HdotL * Smith_G2(alpha, alphaSquared, NdotL, NdotV)) / (NdotV * NdotH);\n}\n\n// Samples a reflection ray from the rough surface using selected microfacet distribution and sampling method\n// Resulting weight includes multiplication by cosine (NdotL) term\nvec3 sampleSpecularMicrofacet(vec3 Vlocal, float alpha, float alphaSquared, vec3 specularF0, vec2 u, OUT_PARAMETER(vec3) weight) {\n\n\t// Sample a microfacet normal (H) in local space\n\tvec3 Hlocal;\n\tif (alpha == 0.0f) {\n\t\t// Fast path for zero roughness (perfect reflection), also prevents NaNs appearing due to divisions by zeroes\n\t\tHlocal = vec3(0.0f, 0.0f, 1.0f);\n\t} else {\n\t\t// For non-zero roughness, this calls VNDF sampling for GG-X distribution or Walter's sampling for Beckmann distribution\n\t\tHlocal = sampleSpecularHalfVector(Vlocal, vec2(alpha, alpha), u);\n\t}\n\n\t// Reflect view direction to obtain light vector\n\tvec3 Llocal = reflect(-Vlocal, Hlocal);\n\n\t// Note: HdotL is same as HdotV here\n\t// Clamp dot products here to small value to prevent numerical instability. Assume that rays incident from below the hemisphere have been filtered\n\tfloat HdotL = max(0.00001f, min(1.0f, dot(Hlocal, Llocal)));\n\tconst vec3 Nlocal = vec3(0.0f, 0.0f, 1.0f);\n\tfloat NdotL = max(0.00001f, min(1.0f, dot(Nlocal, Llocal)));\n\tfloat NdotV = max(0.00001f, min(1.0f, dot(Nlocal, Vlocal)));\n\tfloat NdotH = max(0.00001f, min(1.0f, dot(Nlocal, Hlocal)));\n\tvec3 F = evalFresnel(specularF0, shadowedF90(specularF0), HdotL);\n\n\t// Calculate weight of the sample specific for selected sampling method\n\t// (this is microfacet BRDF divided by PDF of sampling method - notice how most terms cancel out)\n\tweight = F * specularSampleWeight(alpha, alphaSquared, NdotL, NdotV, HdotL, NdotH);\n\n\treturn Llocal;\n}\n\n// Evaluates microfacet specular BRDF\nvec3 evalMicrofacet(const BrdfData data) {\n\n\tfloat D = Microfacet_D(max(0.00001f, data.alphaSquared), data.NdotH);\n\tfloat G2 = Smith_G2(data.alpha, data.alphaSquared, data.NdotL, data.NdotV);\n\t//vec3 F = evalFresnel(data.specularF0, shadowedF90(data.specularF0), data.VdotH); //< Unused, F is precomputed already\n\n#if G2_DIVIDED_BY_DENOMINATOR\n\treturn data.F * (G2 * D * data.NdotL);\n#else\n\treturn ((data.F * G2 * D) / (4.0f * data.NdotL * data.NdotV)) * data.NdotL;\n#endif\n}\n\n// -------------------------------------------------------------------------\n//    Combined BRDF\n// -------------------------------------------------------------------------\n\n// Precalculates commonly used terms in BRDF evaluation\n// Clamps around dot products prevent NaNs and ensure numerical stability, but make sure to\n// correctly ignore rays outside of the sampling hemisphere, by using 'Vbackfacing' and 'Lbackfacing' flags\nBrdfData prepareBRDFData(vec3 N, vec3 L, vec3 V, MaterialProperties material) {\n\tBrdfData data;\n\n\t// Evaluate VNHL vectors\n\tdata.V = V;\n\tdata.N = N;\n\tdata.H = normalize(L + V);\n\tdata.L = L;\n\n\tfloat NdotL = dot(N, L);\n\tfloat NdotV = dot(N, V);\n\tdata.Vbackfacing = (NdotV <= 0.0f);\n\tdata.Lbackfacing = (NdotL <= 0.0f);\n\n\t// Clamp NdotS to prevent numerical instability. Assume vectors below the hemisphere will be filtered using 'Vbackfacing' and 'Lbackfacing' flags\n\tdata.NdotL = min(max(0.00001f, NdotL), 1.0f);\n\tdata.NdotV = min(max(0.00001f, NdotV), 1.0f);\n\n\tdata.LdotH = saturate(dot(L, data.H));\n\tdata.NdotH = saturate(dot(N, data.H));\n\tdata.VdotH = saturate(dot(V, data.H));\n\n\t// Unpack material properties\n\tdata.specularF0 = baseColorToSpecularF0(material.base_color, material.metalness);\n\tdata.diffuseReflectance = baseColorToDiffuseReflectance(material.base_color, material.metalness);\n\n\t// Unpack 'perceptively linear' -> 'linear' -> 'squared' roughness\n\tdata.roughness = material.roughness;\n\tdata.alpha = material.roughness * material.roughness;\n\tdata.alphaSquared = data.alpha * data.alpha;\n\n\t// Pre-calculate some more BRDF terms\n\tdata.F = evalFresnel(data.specularF0, shadowedF90(data.specularF0), data.LdotH);\n\n\treturn data;\n}\n\n// This is an entry point for evaluation of all other BRDFs based on selected configuration (for direct light)\nvec3 evalCombinedBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material) {\n\n\t// Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...)\n\tconst BrdfData data = prepareBRDFData(N, L, V, material);\n\n\t// Ignore V and L rays \"below\" the hemisphere\n\t//if (data.Vbackfacing || data.Lbackfacing) return vec3(0.0f, 0.0f, 0.0f);\n\n\t// Eval specular and diffuse BRDFs\n\tvec3 specular = evalSpecular(data);\n\tvec3 diffuse = evalDiffuse(data);\n\n\t// Combine specular and diffuse layers\n#if COMBINE_BRDFS_WITH_FRESNEL\n\t// Specular is already multiplied by F, just attenuate diffuse\n\treturn (vec3(1.0f, 1.0f, 1.0f) - data.F) * diffuse + specular;\n#else\n\treturn diffuse + specular;\n#endif\n}\n\n// This is an entry point for evaluation of all other BRDFs based on selected configuration (for indirect light)\nbool evalIndirectCombinedBRDF(vec2 u, vec3 shadingNormal, vec3 geometryNormal, vec3 V, MaterialProperties material, const int brdfType, OUT_PARAMETER(vec3) rayDirection, OUT_PARAMETER(vec3) sampleWeight) {\n\n\t// Ignore incident ray coming from \"below\" the hemisphere\n\tif (dot(shadingNormal, V) <= 0.0f) return false;\n\n\t// Transform view direction into local space of our sampling routines\n\t// (local space is oriented so that its positive Z axis points along the shading normal)\n\tvec4 qRotationToZ = getRotationToZAxis(shadingNormal);\n\tvec3 Vlocal = rotatePoint(qRotationToZ, V);\n\tconst vec3 Nlocal = vec3(0.0f, 0.0f, 1.0f);\n\n\tvec3 rayDirectionLocal = vec3(0.0f, 0.0f, 0.0f);\n\n\tif (brdfType == DIFFUSE_TYPE) {\n\n\t\t// Sample diffuse ray using cosine-weighted hemisphere sampling\n\t\trayDirectionLocal = sampleHemisphere(u);\n\t\tconst BrdfData data = prepareBRDFData(Nlocal, rayDirectionLocal, Vlocal, material);\n\n\t\t// Function 'diffuseTerm' is predivided by PDF of sampling the cosine weighted hemisphere\n\t\tsampleWeight = data.diffuseReflectance * diffuseTerm(data);\n\n#if COMBINE_BRDFS_WITH_FRESNEL\n\t\t// Sample a half-vector of specular BRDF. Note that we're reusing random variable 'u' here, but correctly it should be an new independent random number\n\t\tvec3 Hspecular = sampleSpecularHalfVector(Vlocal, vec2(data.alpha, data.alpha), u);\n\n\t\t// Clamp HdotL to small value to prevent numerical instability. Assume that rays incident from below the hemisphere have been filtered\n\t\tfloat VdotH = max(0.00001f, min(1.0f, dot(Vlocal, Hspecular)));\n\t\tsampleWeight *= (vec3(1.0f, 1.0f, 1.0f) - evalFresnel(data.specularF0, shadowedF90(data.specularF0), VdotH));\n#endif\n\n\t} else if (brdfType == SPECULAR_TYPE) {\n\t\tconst BrdfData data = prepareBRDFData(Nlocal, vec3(0.0f, 0.0f, 1.0f) /* unused L vector */, Vlocal, material);\n\t\trayDirectionLocal = sampleSpecular(Vlocal, data.alpha, data.alphaSquared, data.specularF0, u, sampleWeight);\n\t}\n\n\t// Prevent tracing direction with no contribution\n\tif (luminance(sampleWeight) == 0.0f) return false;\n\n\t// Transform sampled direction Llocal back to V vector space\n\trayDirection = normalize(rotatePoint(invertRotation(qRotationToZ), rayDirectionLocal));\n\n\t// Prevent tracing direction \"under\" the hemisphere (behind the triangle)\n\tif (dot(geometryNormal, rayDirection) <= 0.0f) return false;\n\n\treturn true;\n}\n\n// Calculates probability of selecting BRDF (specular or diffuse) using the approximate Fresnel term\nfloat getBrdfProbability(MaterialProperties material, vec3 V, vec3 shadingNormal) {\n\n\t// Evaluate Fresnel term using the shading normal\n\t// Note: we use the shading normal instead of the microfacet normal (half-vector) for Fresnel term here. That's suboptimal for rough surfaces at grazing angles, but half-vector is yet unknown at this point\n\tfloat specularF0 = luminance(baseColorToSpecularF0(material.base_color, material.metalness));\n\tfloat diffuseReflectance = luminance(baseColorToDiffuseReflectance(material.base_color, material.metalness));\n\tfloat Fresnel = saturate(luminance(evalFresnel(vec3(specularF0), shadowedF90(vec3(specularF0)), max(0.0f, dot(V, shadingNormal)))));\n\n\t// Approximate relative contribution of BRDFs using the Fresnel term\n\tfloat specular = Fresnel;\n\tfloat diffuse = diffuseReflectance * (1.0f - Fresnel); //< If diffuse term is weighted by Fresnel, apply it here as well\n\n\t// Return probability of selecting specular BRDF over diffuse BRDF\n\tfloat p = (specular / max(0.0001f, (specular + diffuse)));\n\n\t// Clamp probability to avoid undersampling of less prominent BRDF\n\treturn clamp(p, 0.1f, 0.9f);\n}\n#endif // ifndef BRDF_H_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/brush.frag",
    "content": "#version 450\n\nlayout (constant_id = 0) const float alpha_test_threshold = 0.;\nlayout (constant_id = 1) const uint max_dlights = 1;\n\nlayout(set=1,binding=0) uniform sampler2D sTexture0;\nlayout(set=2,binding=0) uniform sampler2D sLightmap;\n\nstruct Light {\n\tvec4 pos_r;\n\tvec4 color;\n};\n\nlayout(set=3,binding=0) uniform UBO {\n\tuint num_lights;\n\tuint debug_r_lightmap;\n\tLight lights[max_dlights];\n} ubo;\n\nlayout(location=0) in vec3 vPos;\nlayout(location=1) in vec3 vNormal;\nlayout(location=2) in vec2 vTexture0;\nlayout(location=3) in vec2 vLightmapUV;\nlayout(location=4) in vec4 vColor;\n\nlayout(location=0) out vec4 outColor;\n\n// FIXME what should this be?\nconst float dlight_attenuation_const = 5000.;\n\nvoid main() {\n\toutColor = vec4(0.);\n\tconst vec4 tex_color = texture(sTexture0, vTexture0);\n\n\t// TODO make sure textures are premultiplied alpha\n\tconst vec4 baseColor = vColor * tex_color;\n\n\tif (baseColor.a < alpha_test_threshold)\n\t\tdiscard;\n\n\toutColor.a = baseColor.a;\n\toutColor.rgb = texture(sLightmap, vLightmapUV).rgb;\n\n\tfor (uint i = 0; i < ubo.num_lights; ++i) {\n\t\tconst vec4 light_pos_r = ubo.lights[i].pos_r;\n\t\tconst vec3 light_dir = light_pos_r.xyz - vPos;\n\t\tconst vec3 light_color = ubo.lights[i].color.rgb;\n\t\tconst float d2 = dot(light_dir, light_dir);\n\t\tconst float r2 = light_pos_r.w * light_pos_r.w;\n\t\tconst float attenuation = dlight_attenuation_const / (d2 + r2 * .5);\n\t\toutColor.rgb += light_color * max(0., dot(normalize(light_dir), vNormal)) * attenuation;\n\t}\n\n\tif (ubo.debug_r_lightmap == 0)\n\t\toutColor.rgb *= baseColor.rgb;\n}\n"
  },
  {
    "path": "ref/vk/shaders/brush.vert",
    "content": "#version 450\n\nlayout(set=0,binding=0) uniform UBO {\n\tmat4 mvp;\n\tvec4 color;\n} ubo;\n\nlayout(location=0) in vec3 aPos;\nlayout(location=1) in vec3 aNormal;\nlayout(location=2) in vec2 aTexture0;\nlayout(location=3) in vec2 aLightmapUV;\nlayout(location=4) in vec4 aLightColor;\n\nlayout(location=0) out vec3 vPos;\nlayout(location=1) out vec3 vNormal;\nlayout(location=2) out vec2 vTexture0;\nlayout(location=3) out vec2 vLightmapUV;\nlayout(location=4) out vec4 vColor;\n\nvoid main() {\n\tvPos = aPos.xyz;\n\t// FIXME mul by normal matrix\n\tvNormal = aNormal;\n\tvTexture0 = aTexture0;\n\tvLightmapUV = aLightmapUV;\n\tvColor = ubo.color * aLightColor;\n\tgl_Position = ubo.mvp * vec4(aPos.xyz, 1.);\n}\n"
  },
  {
    "path": "ref/vk/shaders/color_spaces.glsl",
    "content": "#ifndef RT_COLOR_SPACES_GLSL_INCLUDED\n#define RT_COLOR_SPACES_GLSL_INCLUDED\n#ifdef SRGB_FAST_APPROXIMATION\n#define LINEARtoSRGB OECF_sRGBFast\n#define SRGBtoLINEAR sRGB_OECFFast\n#else\n#define LINEARtoSRGB OECF_sRGB\n#define SRGBtoLINEAR sRGB_OECF\n#endif\n\n// based on https://github.com/OGRECave/ogre/blob/f49bc9be79f6711a88f01892711120da717f6148/Samples/Media/PBR/filament/pbr_filament.frag.glsl#L108-L124\nfloat sRGB_OECF(const float sRGB) {\n\t// IEC 61966-2-1:1999\n\tfloat linearLow = sRGB / 12.92;\n\tfloat linearHigh = pow((sRGB + 0.055) / 1.055, 2.4);\n\treturn sRGB <= 0.04045 ? linearLow : linearHigh;\n}\n/**\n * Reverse opto-electronic conversion function to the one that filament\n * provides. Filament version has LDR RGB linear color -> LDR RGB non-linear\n * color in sRGB space. This function will thus provide LDR RGB non-linear\n * color in sRGB space -> LDR RGB linear color conversion.\n */\nvec3 sRGB_OECF(const vec3 sRGB)\n{\n\treturn vec3(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b));\n}\nvec4 sRGB_OECF(const vec4 sRGB)\n{\n\treturn vec4(sRGB_OECF(sRGB.r), sRGB_OECF(sRGB.g), sRGB_OECF(sRGB.b), sRGB.w);\n}\nvec3 sRGB_OECFFast(const vec3 sRGB) {\n\treturn pow(sRGB, vec3(2.2));\n}\nfloat sRGB_OECFFast(const float sRGB) {\n\treturn pow(sRGB, 2.2);\n}\nvec4 sRGB_OECFFast(const vec4 sRGB) {\n\treturn vec4(pow(sRGB.rgb, vec3(2.2)), sRGB.w);\n}\n\n// based on https://github.com/abhirocks1211/filament/blob/3e97ac5268a47d5625c7d166eb7dda0bbba14a4d/shaders/src/conversion_functions.fs#L20-L55\n//------------------------------------------------------------------------------\n// Opto-electronic conversion functions (linear to non-linear)\n//------------------------------------------------------------------------------\n\nfloat OECF_sRGB(const float linear) {\n\t// IEC 61966-2-1:1999\n\tfloat sRGBLow  = linear * 12.92;\n\tfloat sRGBHigh = (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;\n\treturn linear <= 0.0031308 ? sRGBLow : sRGBHigh;\n}\n\nvec3 OECF_sRGB(const vec3 linear) {\n\treturn vec3(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b));\n}\nvec4 OECF_sRGB(const vec4 linear) {\n\treturn vec4(OECF_sRGB(linear.r), OECF_sRGB(linear.g), OECF_sRGB(linear.b), linear.w);\n}\nvec3 OECF_sRGBFast(const vec3 linear) {\n\treturn pow(linear, vec3(1.0 / 2.2));\n}\nvec4 OECF_sRGBFast(const vec4 linear) {\n\treturn vec4(pow(linear.rgb, vec3(1.0 / 2.2)), linear.w);\n}\n\n#endif //ifndef RT_COLOR_SPACES_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/debug.glsl",
    "content": "#ifndef DEBUG_GLSL_INCLUDED\n#define DEBUG_GLSL_INCLUDED\n\n// 1. No validation -- fastest, expects no math errors, will break if anything goes wrong.\n//    Public release?\n\n// 2. Validate and clamp sensitive/output values -- cheap, fast, hides any math errors\n//    Public beta? Release?\n//#define DEBUG_VALIDATE\n\n// 3. Validate, print and clamp sensitive -- printing is relatively slow, can detect errors for subsequent analysis\n//    Public alpha/debug? -vkdebug? -vkvalidate?\n#define DEBUG_VALIDATE_PRINT\n\n// 4. Validate and clamp everything -- slow, but can help with analysis\n//    Development/shader debugging only.\n//#define DEBUG_VALIDATE_EXTRA\n\n// Enable extra shader debug printing for custom things\n//#define DEBUG_ENABLE_PRINT\n\n// Extra implies print\n#ifdef DEBUG_VALIDATE_EXTRA\n#define DEBUG_VALIDATE_PRINT\n#endif\n\n// Print implies validation\n#ifdef DEBUG_VALIDATE_PRINT\n#define DEBUG_VALIDATE\n#endif\n\n#if defined(DEBUG_ENABLE_PRINT) || defined(DEBUG_VALIDATE_PRINT)\n#extension GL_EXT_debug_printf: enable\n#endif // SHADER_DEBUG_ENABLE\n\n#define IS_INVALID(v) (isnan(v) || isinf(v))\n#define IS_INVALIDV(v) (any(isnan(v)) || any(isinf(v)))\n#define PRIVEC3(v) (v).r, (v).g, (v).b\n#define PRIVEC4(v) (v).r, (v).g, (v).b, (v).a\n\n#ifndef DEBUG_VALIDATE\n// Dummy for no validation\n#define DEBUG_VALIDATE_RANGE_VEC3(s, v, min_v, max_v)\n#define DEBUG_VALIDATE_RANGE(v, min_v, max_v)\n#define DEBUG_VALIDATE_VEC3(v, msg)\n#elif !defined(DEBUG_VALIDATE_PRINT) // #indef DEBUG_VALIDATE\n// DEBUG_VALIDATE is defined, DEBUG_VALIDATE_PRINT are not\n#define DEBUG_VALIDATE_RANGE_VEC3(s, v, min_v, max_v) \\\n\tif (IS_INVALIDV(v) || any(lessThan(v,vec3(min_v))) || any(greaterThan(v,vec3(max_v)))) { \\\n\t\tv = clamp(v, vec3(min_v), vec3(max_v)); \\\n\t}\n#define DEBUG_VALIDATE_RANGE(v, min_v, max_v) \\\n\tif (IS_INVALID(v) || v < min_v || v > max_v) { \\\n\t\tv = clamp(v, min_v, max_v); \\\n\t}\n#define DEBUG_VALIDATE_VEC3(v, msg) \\\n\tif (IS_INVALIDV(v)) { \\\n\t\tv = vec3(0.); \\\n\t}\n#else // #ifndef DEBUG_VALIDATE_PRINT\n// Both DEBUG_VALIDATE and DEBUG_VALIDATE_PRINT are defined\n#define DEBUG_VALIDATE_RANGE_VEC3(s, v, min_v, max_v) \\\n\tif (IS_INVALIDV(v) || any(lessThan(v,vec3(min_v))) || any(greaterThan(v,vec3(max_v)))) { \\\n\t\tdebugPrintfEXT(\"%d INVALID vec3=(%f, %f, %f)\", __LINE__, PRIVEC3(v)); \\\n\t\tv = clamp(v, vec3(min_v), vec3(max_v)); \\\n\t}\n#define DEBUG_VALIDATE_RANGE(v, min_v, max_v) \\\n\tif (IS_INVALID(v) || v < min_v || v > max_v) { \\\n\t\tdebugPrintfEXT(\"%d INVALID %f\", __LINE__, v); \\\n\t\tv = clamp(v, min_v, max_v); \\\n\t}\n// msg should begin with \"%d\" for __LINE__\n// GLSL u y no string concatenation ;_;\n#define DEBUG_VALIDATE_VEC3(v, msg) \\\n\tif (IS_INVALIDV(v)) { \\\n\t\tdebugPrintfEXT(msg, __LINE__, PRIVEC3(v)); \\\n\t\tv = vec3(0.); \\\n\t}\n#endif // #else #ifndef DEBUG_VALIDATE\n\n#endif // ifndef DEBUG_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/denoiser.comp",
    "content": "#version 460\n#include \"debug.glsl\"\n#include \"utils.glsl\"\n#include \"color_spaces.glsl\"\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\n#define DIFFUSE_TEMPORAL_HISTORY_MIX_WEIGHT 0.8\n#define SPECULAR_TEMPORAL_HISTORY_MIX_WEIGHT 0.8\n#define DIELECTRIC_SPECULAR_MULTIPLIER 0.02 // default value from pbr papers, but 0.04 in Unreal Engine\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0, rgba16f) uniform image2D out_dest;\n\nlayout(set = 0, binding = 1, rgba8) uniform readonly image2D base_color_a;\n\nlayout(set = 0, binding = 2, rgba16f) uniform readonly image2D light_poly_diffuse;\nlayout(set = 0, binding = 3, rgba16f) uniform readonly image2D light_poly_specular;\n\nlayout(set = 0, binding = 4, rgba16f) uniform readonly image2D light_point_diffuse;\nlayout(set = 0, binding = 5, rgba16f) uniform readonly image2D light_point_specular;\nlayout(set = 0, binding = 6, rgba16f) uniform readonly image2D emissive;\n\nlayout(set = 0, binding = 7, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 8, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 9, rgba8) uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 10, rgba32f) uniform readonly image2D geometry_prev_position;\n\nlayout(set = 0, binding = 11) uniform UBO { UniformBuffer ubo; } ubo;\n\nlayout(set = 0, binding = 12, rgba16f) uniform readonly image2D indirect_diffuse;\nlayout(set = 0, binding = 13, rgba16f) uniform readonly image2D indirect_diffuse_atrous1;\nlayout(set = 0, binding = 14, rgba16f) uniform readonly image2D indirect_specular_reconstructed;\nlayout(set = 0, binding = 15, rgba16f) uniform readonly image2D indirect_diffuse_denoised_by_sh;\nlayout(set = 0, binding = 16, rgba32f) uniform readonly image2D reflection_direction_pdf;\n\nlayout(set = 0, binding = 17, rgba16f) uniform image2D out_temporal_diffuse;\nlayout(set = 0, binding = 18, rgba16f) uniform image2D prev_temporal_diffuse;\n\nlayout(set = 0, binding = 19, rgba16f) uniform image2D out_temporal_specular;\nlayout(set = 0, binding = 20, rgba16f) uniform image2D prev_temporal_specular;\n\n//#define DEBUG_NOISE\n#ifdef DEBUG_NOISE\nlayout(set = 0, binding = 21) uniform sampler3D blue_noise_texture;\n#include \"bluenoise.glsl\"\n#endif\n\nlayout(set = 0, binding = 22, rgba16f) uniform readonly image2D legacy_blend;\n\n//layout(set = 0, binding = 23) uniform sampler2D textures[MAX_TEXTURES];\n\n#include \"atrous.glsl\"\n\nconst int INDIRECT_SCALE = 2;\n\n// Blatantly copypasted from https://www.shadertoy.com/view/XsGfWV\nvec3 aces_tonemap(vec3 color){\n\tmat3 m1 = mat3(\n\t\t0.59719, 0.07600, 0.02840,\n\t\t0.35458, 0.90834, 0.13383,\n\t\t0.04823, 0.01566, 0.83777\n\t);\n\tmat3 m2 = mat3(\n\t\t1.60475, -0.10208, -0.00327,\n\t\t-0.53108,  1.10813, -0.07276,\n\t\t-0.07367, -0.00605,  1.07602\n\t);\n\tvec3 v = m1 * color;\n\tvec3 a = v * (v + 0.0245786) - 0.000090537;\n\tvec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n\t//return pow(clamp(m2 * (a / b), 0.0, 1.0), vec3(1.0 / 2.2));\n\treturn clamp(m2 * (a / b), 0.0, 1.0);\n}\n\nvec3 reinhard(vec3 color){\n\treturn color / (color + 1.0);\n}\n\nvec3 reinhard02(vec3 c, vec3 Cwhite2) {\n\treturn c * (1. + c / Cwhite2) / (1. + c);\n}\n\nfloat normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; }\nfloat normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); }\n\nvoid readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) {\n\tconst vec4 n = imageLoad(normals_gs, uv);\n\tgeometry_normal = normalDecode(n.xy);\n\tshading_normal = normalDecode(n.zw);\n}\n\nvec3 closestColor(vec3 color1, vec3 color2, vec3 sampleColor) {\n    float dist1 = dot(color1 - sampleColor, color1 - sampleColor);\n    float dist2 = dot(color2 - sampleColor, color2 - sampleColor);\n    return (dist1 < dist2) ? color1 : color2;\n}\n\nstruct Components {\n\tvec3 direct_diffuse, direct_specular, indirect_diffuse, indirect_specular;\n};\n\nComponents dontBlurSamples(const ivec2 res, const ivec2 pix) {\n\tComponents c;\n\tc.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.);\n\tconst ivec2 p = pix;\n\tconst ivec2 p_indirect = pix / INDIRECT_SCALE;\n\tc.direct_diffuse += imageLoad(light_point_diffuse, p).rgb;\n\tc.direct_diffuse += imageLoad(light_poly_diffuse, p).rgb;\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) == 0) {\n\tc.indirect_diffuse += imageLoad(indirect_diffuse, p_indirect).rgb;\n\t} else {\n\t\tc.indirect_diffuse += imageLoad(indirect_diffuse_denoised_by_sh, p).rgb;\n\t}\n\tc.direct_specular += imageLoad(light_poly_specular, p).rgb;\n\tc.direct_specular += imageLoad(light_point_specular, p).rgb;\n\tc.indirect_specular += imageLoad(indirect_specular_reconstructed, p_indirect).rgb;\n\treturn c;\n}\n\n#define BOX_BLUR(out, tex, res, center, kernel_range) \\\n{ \\\n\tconst float scale = 1. / pow(float(kernel_range * 2 + 1), 2.); \\\n\tfor (int x = -kernel_range; x <= kernel_range; ++x) { \\\n\t\tfor (int y = -kernel_range; y <= kernel_range; ++y) { \\\n\t\t\tconst ivec2 p = center + ivec2(x, y); \\\n\t\t\tif (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) \\\n\t\t\t\tcontinue; \\\n\t\t\tout += imageLoad(tex, p).rgb * scale; \\\n\t\t} /* for y */ \\\n\t} /* for x */ \\\n}\n\n#if 1\n\tconst int DIRECT_DIFFUSE_KERNEL = 3;\n\tconst int DIRECT_SPECULAR_KERNEL = 2;\n\tconst int INDIRECT_DIFFUSE_KERNEL = 5;\n\tconst int INDIRECT_SPECULAR_KERNEL = 2;\n#else\n\tconst int DIRECT_DIFFUSE_KERNEL = 1;\n\tconst int DIRECT_SPECULAR_KERNEL = 1;\n\tconst int INDIRECT_DIFFUSE_KERNEL = 1;\n\tconst int INDIRECT_SPECULAR_KERNEL = 1;\n#endif\n\nComponents boxBlurSamples(ivec2 res, ivec2 pix) {\n\tComponents c;\n\tc.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.);\n\n\tBOX_BLUR(c.direct_diffuse, light_poly_diffuse, res, pix, DIRECT_DIFFUSE_KERNEL);\n\tBOX_BLUR(c.direct_diffuse, light_point_diffuse, res, pix, DIRECT_DIFFUSE_KERNEL);\n\n\tBOX_BLUR(c.direct_specular, light_poly_specular, res, pix, DIRECT_SPECULAR_KERNEL);\n\tBOX_BLUR(c.direct_specular, light_point_specular, res, pix, DIRECT_SPECULAR_KERNEL);\n\n\tres /= 2;\n\tpix /= 2;\n\t\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) == 0) {\n\tBOX_BLUR(c.indirect_diffuse, indirect_diffuse, res, pix, INDIRECT_DIFFUSE_KERNEL);\n\t}\n\n\tBOX_BLUR(c.indirect_specular, indirect_specular_reconstructed, res, pix, INDIRECT_SPECULAR_KERNEL);\n\n\treturn c;\n}\n\nvec3 restoreSpecular(vec3 decolorized_specular, vec3 base_color, float metalness) {\n\t// TODO: add fresnel and do like PBR\n\tconst vec3 plasticSpecular = decolorized_specular * DIELECTRIC_SPECULAR_MULTIPLIER;\n\tconst vec3 metalSpecular = decolorized_specular * base_color;\n\treturn mix(plasticSpecular, metalSpecular, metalness);\n}\n\nComponents blurATrous(const ivec2 res, const ivec2 pix, vec3 pos, vec3 shading_normal, vec3 geometry_normal, vec3 base_color, float metalness) {\n\tComponents c;\n\tc.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.);\n\n\tfloat weight_total_diffuse = 0.;\n\tfloat weight_total_specular = 0.;\n\tfloat weight_total_indirect_diffuse = 0.;\n\tfloat weight_total_indirect_specular = 0.;\n\tvec3 indirect_diffuse_c2 = vec3(0.);\n\tconst ivec2 res_scaled = res / INDIRECT_SCALE;\n\tfor (int x = 0; x <= ATROUS_KERNEL_WIDTH; ++x) {\n\t\tfor (int y = 0; y <= ATROUS_KERNEL_WIDTH; ++y) {\n\t\t\tconst ivec2 offset = ivec2(x, y);\n\t\t\t// 1. Direct diffuse\n\t\t\t{\n\t\t\t\tconst float sn_phi = .5;\n\t\t\t\tconst float p_phi = 2.;\n\t\t\t\tconst int step_width = 3;\n\t\t\t\tivec2 p;\n\t\t\t\tconst float weight = aTrousSampleWeigth(\n\t\t\t\t\tres, pix, pos, shading_normal, offset, step_width, 1, sn_phi, p_phi, p);\n\n\t\t\t\tif (weight > 0.) {\n\t\t\t\t\tweight_total_diffuse += weight;\n\t\t\t\t\tc.direct_diffuse +=\n\t\t\t\t\t\t(imageLoad(light_poly_diffuse, p).rgb\n\t\t\t\t\t\t+imageLoad(light_point_diffuse, p).rgb) * weight;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 2. Direct specular\n\t\t\t{\n\t\t\t\tconst float sn_phi = .5;\n\t\t\t\tconst float p_phi = 1.;\n\t\t\t\tconst int step_width = 1;\n\t\t\t\tivec2 p;\n\t\t\t\tconst float weight = aTrousSampleWeigth(\n\t\t\t\t\tres, pix, pos, shading_normal, offset, step_width, 1, sn_phi, p_phi, p);\n\n\t\t\t\tif (weight > 0.) {\n\t\t\t\t\tweight_total_specular += weight;\n\t\t\t\t\tc.direct_specular +=\n\t\t\t\t\t\t(imageLoad(light_poly_specular, p).rgb\n\t\t\t\t\t\t+imageLoad(light_point_specular, p).rgb) * weight;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 3. Indirect diffuse\n\t\t\t{\n\t\t\t\tconst float sn_phi = .5;\n\t\t\t\tconst float p_phi = 3.;\n\t\t\t\tconst int step_width = 2;\n\t\t\t\tivec2 p;\n\t\t\t\tconst float weight = aTrousSampleWeigth(\n\t\t\t\t\tres, pix, pos, shading_normal, offset, step_width, 1, sn_phi, p_phi, p);\n\n\t\t\t\tif (weight > 0.) {\n\t\t\t\t\tconst ivec2 p_scaled = p / INDIRECT_SCALE;\n\t\t\t\t\tconst bool do_indirect = all(lessThan(p_scaled, res_scaled));\n\t\t\t\t\tif (do_indirect) {\n\t\t\t\t\t\tweight_total_indirect_diffuse += weight;\n\t\t\t\t\t\tindirect_diffuse_c2 += imageLoad(indirect_diffuse_atrous1, p_scaled).rgb * weight;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 4. Indirect specular\n\t\t\t{\n\t\t\t\tconst float sn_phi = .5;\n\t\t\t\tconst float p_phi = 1.;\n\t\t\t\tconst int step_width = 1;\n\t\t\t\tivec2 p;\n\t\t\t\tconst float weight = aTrousSampleWeigth(\n\t\t\t\t\tres, pix, pos, shading_normal, offset, step_width, 1, sn_phi, p_phi, p);\n\n\t\t\t\tif (weight > 0.) {\n\t\t\t\t\tconst ivec2 p_scaled = p / INDIRECT_SCALE;\n\t\t\t\t\tconst bool do_indirect = all(lessThan(p_scaled, res_scaled));\n\t\t\t\t\tif (do_indirect) {\n\t\t\t\t\t\tweight_total_indirect_specular += weight;\n\t\t\t\t\t\tc.indirect_specular += restoreSpecular(imageLoad(indirect_specular_reconstructed, p_scaled).rgb, base_color, metalness) * weight;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} // for y\n\t} // for x\n\n\tconst float one_over_weight_diffuse = 1. / weight_total_diffuse;\n\tconst float one_over_weight_specular = 1. / weight_total_specular;\n\tconst float one_over_weight_indirect_diffuse = 1. / weight_total_indirect_diffuse;\n\tconst float one_over_weight_indirect_specular = 1. / weight_total_indirect_specular;\n\tc.direct_diffuse *= one_over_weight_diffuse;\n\tc.direct_specular *= one_over_weight_specular;\n\n\tindirect_diffuse_c2 *= one_over_weight_indirect_diffuse;\n\tc.indirect_specular *= one_over_weight_indirect_specular;\n\n\tconst vec3 indirect_diffuse_c0 = imageLoad(indirect_diffuse, pix / INDIRECT_SCALE).rgb;\n\tconst vec3 indirect_diffuse_c1 = imageLoad(indirect_diffuse_atrous1, pix / INDIRECT_SCALE).rgb;\n\tconst vec3 d0 = indirect_diffuse_c1 - indirect_diffuse_c0;\n\tconst vec3 d1 = indirect_diffuse_c2 - indirect_diffuse_c1;\n\n\t// TODO(or not todo): The Á-Trous paper mentions that it should be c2 + d1 + d0, but\n\t// it gives horrible artifacts. Either I'm misreading the paper, or something else is broken here,\n\t// Using just c2 seems fine enough (although still not up to original paper image quality)\n\tc.indirect_diffuse = indirect_diffuse_c2;// + d1 + d0;\n\treturn c;\n}\n\nComponents blurSamples(const ivec2 res, const ivec2 pix) {\n\tComponents c;\n\tc.direct_diffuse = c.direct_specular = c.indirect_diffuse = c.indirect_specular = vec3(0.);\n\n\tconst vec4 center_pos = imageLoad(position_t, pix);\n\n\tconst int KERNEL_SIZE = max(max(max(DIRECT_DIFFUSE_KERNEL, INDIRECT_DIFFUSE_KERNEL), DIRECT_SPECULAR_KERNEL), INDIRECT_SPECULAR_KERNEL);\n\n\tconst float direct_diffuse_sigma = DIRECT_DIFFUSE_KERNEL / 2.;\n\tconst float indirect_diffuse_sigma = INDIRECT_DIFFUSE_KERNEL / 2.;\n\tconst float direct_specular_sigma = DIRECT_SPECULAR_KERNEL / 2.;\n\tconst float indirect_specular_sigma = INDIRECT_SPECULAR_KERNEL / 2.;\n\n\tfloat direct_diffuse_total = 0.;\n\tfloat indirect_diffuse_total = 0.;\n\tfloat direct_specular_total = 0.;\n\tfloat indirect_specular_total = 0.;\n\n\tconst ivec2 res_scaled = res / INDIRECT_SCALE;\n\tfor (int x = -KERNEL_SIZE; x <= KERNEL_SIZE; ++x)\n\t\tfor (int y = -KERNEL_SIZE; y <= KERNEL_SIZE; ++y) {\n\t\t\tconst ivec2 p = pix + ivec2(x, y);\n\t\t\tif (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvec3 sample_geometry_normal, sample_shading_normal;\n\t\t\treadNormals(p, sample_geometry_normal, sample_shading_normal);\n\n\t\t\tfloat scale = 1.f;\n\t\t\t// FIXME also filter by depth, (kusok index?), etc\n\t\t\t//scale *= smoothstep(.9, 1., dot(sample_geometry_normal, geometry_normal));\n\n\t\t\tconst vec4 sample_pos = imageLoad(position_t, p);\n\t\t\t// FIXME what are these magic numbers?\n\t\t\tscale *= smoothstep(center_pos.w * 4. / 100., 0., distance(center_pos.xyz, sample_pos.xyz));\n\n\t\t\tif ( scale <= 0. )\n\t\t\t\tcontinue;\n\n\t\t\tconst ivec2 p_indirect = pix / INDIRECT_SCALE + ivec2(x, y);\n\t\t\tconst bool do_indirect = all(lessThan(p_indirect, res_scaled)) && all(greaterThanEqual(p_indirect, ivec2(0)));\n\n\t\t\tif (all(lessThan(abs(ivec2(x, y)), ivec2(DIRECT_DIFFUSE_KERNEL))))\n\t\t\t{\n\t\t\t\tconst float direct_diffuse_scale = scale * normpdf(x, direct_diffuse_sigma) * normpdf(y, direct_diffuse_sigma);\n\t\t\t\tdirect_diffuse_total += direct_diffuse_scale;\n\n\t\t\t\tc.direct_diffuse += imageLoad(light_point_diffuse, p).rgb * direct_diffuse_scale;\n\t\t\t\tc.direct_diffuse += imageLoad(light_poly_diffuse, p).rgb * direct_diffuse_scale;\n\t\t\t}\n\n\t\t\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) == 0 &&\n\t\t\t\tall(lessThan(abs(ivec2(x, y)), ivec2(INDIRECT_DIFFUSE_KERNEL))) && do_indirect)\n\t\t\t{\n\t\t\t\t// TODO indirect operates at different scale, do a separate pass\n\t\t\t\tconst float indirect_diffuse_scale = scale\n\t\t\t\t\t* normpdf(x, indirect_diffuse_sigma)\n\t\t\t\t\t* normpdf(y, indirect_diffuse_sigma);\n\n\t\t\t\tindirect_diffuse_total += indirect_diffuse_scale;\n\t\t\t\tc.indirect_diffuse += imageLoad(indirect_diffuse, p_indirect).rgb * indirect_diffuse_scale;\n\t\t\t}\n\n\t\t\tif (all(lessThan(abs(ivec2(x, y)), ivec2(DIRECT_SPECULAR_KERNEL))))\n\t\t\t{\n\t\t\t\tconst float specular_scale = scale * normpdf(x, direct_specular_sigma) * normpdf(y, direct_specular_sigma);\n\t\t\t\tdirect_specular_total += specular_scale;\n\n\t\t\t\tc.direct_specular += imageLoad(light_poly_specular, p).rgb * specular_scale;\n\t\t\t\tDEBUG_VALIDATE_VEC3(c.direct_specular, \"%d c.direct_specular=(%f,%f,%f) poly\");\n\n\t\t\t\tc.direct_specular += imageLoad(light_point_specular, p).rgb * specular_scale;\n\t\t\t\tDEBUG_VALIDATE_VEC3(c.direct_specular, \"%d c.direct_specular=(%f,%f,%f) point\");\n\t\t\t}\n\n\t\t\tif (all(lessThan(abs(ivec2(x, y)), ivec2(INDIRECT_SPECULAR_KERNEL)))) {\n\t\t\t\tconst ivec2 p_indirect = (pix + ivec2(x, y)) / INDIRECT_SCALE;// + ivec2(x, y);\n\t\t\t\tconst bool do_indirect = all(lessThan(p_indirect, res_scaled)) && all(greaterThanEqual(p_indirect, ivec2(0)));\n\n\t\t\t\tif (do_indirect) {\n\t\t\t\t\t// TODO indirect operates at different scale, do a separate pass\n\t\t\t\t\tconst float specular_scale = scale * normpdf(x, indirect_specular_sigma) * normpdf(y, indirect_specular_sigma);\n\t\t\t\t\tindirect_specular_total += specular_scale;\n\t\t\t\t\tc.indirect_specular += imageLoad(indirect_specular_reconstructed, p_indirect).rgb * specular_scale;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tif (direct_diffuse_total > 0.)\n\t\tc.direct_diffuse /= direct_diffuse_total;\n\n\tif (indirect_diffuse_total > 0.)\n\t\tc.indirect_diffuse *= indirect_diffuse_total;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(c.direct_specular)) {\n\t\tdebugPrintfEXT(\"c.direct_specular=(%f,%f,%f)\", PRIVEC3(c.direct_specular));\n\t\tc.direct_specular = vec3(0.);\n\t}\n\n\tif (IS_INVALID(direct_specular_total)) {\n\t\tdebugPrintfEXT(\"direct_specular_total=%f\", direct_specular_total);\n\t\tdirect_specular_total = 0.;\n\t}\n#endif\n\n\tif (direct_specular_total > 0.)\n\t\tc.direct_specular *= direct_specular_total;\n\n\tif (indirect_specular_total > 0.)\n\t\tc.indirect_specular *= indirect_specular_total;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(c.indirect_specular)) {\n\t\tdebugPrintfEXT(\"c.indirect_specular=(%f,%f,%f)\", PRIVEC3(c.indirect_specular));\n\t\tc.indirect_specular = vec3(0.);\n\t}\n#endif\n\n\treturn c;\n}\n\nvoid main() {\n\tconst ivec2 res = ubo.ubo.res;\n\tconst ivec2 pix = ivec2(gl_GlobalInvocationID);\n\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\n\tconst vec3 position = imageLoad(position_t, pix).xyz;\n\n\tconst vec3 base_color = SRGBtoLINEAR(imageLoad(base_color_a, pix).rgb);\n\tconst float metalness = imageLoad(material_rmxx, pix).g;\n\n\tvec3 geometry_normal, shading_normal;\n\treadNormals(pix, geometry_normal, shading_normal);\n\n\tif (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DISABLED) {\n\t\t// no-op, just continue\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_BASECOLOR) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(imageLoad(base_color_a, pix).rgb), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_BASEALPHA) {\n\t\timageStore(out_dest, pix, imageLoad(base_color_a, pix).aaaa); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_EMISSIVE) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(imageLoad(emissive, pix).rgb), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_MATERIAL) {\n\t\timageStore(out_dest, pix, vec4(imageLoad(material_rmxx, pix).rg, 0., 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_NSHADE) {\n\t\timageStore(out_dest, pix, vec4(.5 + shading_normal * .5, 0.));\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_NGEOM) {\n\t\timageStore(out_dest, pix, vec4(.5 + geometry_normal * .5, 0.));\n\t\treturn;\n\t}\n\n#ifdef DEBUG_NOISE\n\timageStore(out_dest, pix, blueNoise(ivec3(pix.xy, ubo.ubo.frame_counter))); return;\n#endif\n\n\t//const Components c = blurSamples(res, pix);\n\t//const Components c = boxBlurSamples(res, pix);\n\t//const Components c = dontBlurSamples(res, pix);\n\tconst Components c = blurATrous(res, pix, position, shading_normal, geometry_normal, base_color, metalness);\n\n\tif (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DISABLED) {\n\t\t// Skip\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DIRECT) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.direct_diffuse + c.direct_specular), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DIRECT_DIFF) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.direct_diffuse), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DIRECT_SPEC) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.direct_specular), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_INDIRECT) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.indirect_diffuse + c.indirect_specular), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_INDIRECT_SPEC) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.indirect_specular), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_INDIRECT_DIFF) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.indirect_diffuse), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DIFFUSE) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.indirect_diffuse + c.direct_diffuse), 0.)); return;\n\t\treturn;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_SPECULAR) {\n\t\timageStore(out_dest, pix, vec4(LINEARtoSRGB(c.indirect_specular + c.direct_specular), 0.)); return;\n\t\treturn;\n\t}\n\n\tvec3 diffuse = c.direct_diffuse + c.indirect_diffuse;\n\tvec3 specular = c.direct_specular + c.indirect_specular;\n\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) != 0) {\n\t\tdiffuse += imageLoad(indirect_diffuse_denoised_by_sh, pix).rgb;\n\t}\n\n\t{\n//#define DISABLE_TEMPORAL_DENOISER\n#ifndef DISABLE_TEMPORAL_DENOISER\n\t\t// TODO: need to extract reprojecting from this shader because reprojected stuff need svgf denoising pass after it\n\t\tconst vec3 origin = (ubo.ubo.inv_view * vec4(0., 0., 0., 1.)).xyz;\n\t\tconst float depth = length(origin - position);\n\t\tconst vec3 prev_position = imageLoad(geometry_prev_position, pix).rgb;\n\t\tconst vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(prev_position, 1.)).xyz, 1.);\n\t\tconst vec2 reproj_uv = clip_space.xy / clip_space.w;\n\t\tconst ivec2 reproj_pix = ivec2((reproj_uv * 0.5 + vec2(0.5)) * vec2(res));\n\t\tconst vec3 prev_origin = (ubo.ubo.prev_inv_view * vec4(0., 0., 0., 1.)).xyz;\n\t\tconst float depth_nessesary = length(prev_position - prev_origin);\n\t\tconst float depth_treshold = 0.01 * clip_space.w;\n\t\tfloat better_depth_offset = depth_treshold;\n\t\tvec3 history_diffuse = diffuse;\n\t\tvec3 history_specular = specular;\n\t\t\n\t\tif (any(greaterThanEqual(reproj_pix, ivec2(0))) && any(lessThan(reproj_pix, res))) {\t\t\t\n\t\t\tconst vec4 history_diffuse_depth = imageLoad( prev_temporal_diffuse, reproj_pix );\n\t\t\tconst vec4 history_specular_sample = imageLoad( prev_temporal_specular, reproj_pix );\n\n\t\t\tconst float history_depth = history_diffuse_depth.w;\n\t\t\tconst float depth_offset = abs(history_depth - depth_nessesary);\n\t\t\tif ( depth_offset < better_depth_offset ) {\n\t\t\t\tbetter_depth_offset = depth_offset;\n\t\t\t\thistory_diffuse = history_diffuse_depth.rgb;\n\t\t\t\thistory_specular = history_specular_sample.rgb;\n\t\t\t}\n\t\t}\n\n\t\t// parallax reprojecting for specular\n\t\tfloat average_ray_length = 0.;\n\t\tfloat ray_length_samples_count = 0.;\n\t\tconst int AVERAGE_RAY_LENGTH_KERNEL = 1;\n\t\tfor(int x = -AVERAGE_RAY_LENGTH_KERNEL; x <=AVERAGE_RAY_LENGTH_KERNEL; x++) {\n\t\t\tfor(int y = -AVERAGE_RAY_LENGTH_KERNEL; y <=AVERAGE_RAY_LENGTH_KERNEL; y++) {\n\t\t\t\tconst ivec2 p = pix / INDIRECT_SCALE + ivec2(x, y);\n\t\t\t\tif (any(greaterThanEqual(p, res / INDIRECT_SCALE)) || any(lessThan(p, ivec2(0)))) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\taverage_ray_length += length(imageLoad( reflection_direction_pdf, p ).xyz);\n\t\t\t\tray_length_samples_count += 1.;\n\t\t\t}\n\t\t}\n\t\tif (ray_length_samples_count > 0. && average_ray_length > 0.) {\n\t\t\taverage_ray_length /= ray_length_samples_count;\n\n\t\t\t//\torigin (camera)\n\t\t\t//\t       []<          reflection destination\n\t\t\t//\t\t \t| \\        (UwU) (reflected in\n\t\t\t//\t\t\t|   \\     /  |    surface texel)\n\t\t\t//\t\t\t|     \\ /    |\n\t\t\t//\t--------x------x-----x------------\n\t\t\t//\torigin on      ^   reflection on plane\n\t\t\t//\t   plane\t   |\n\t\t\t//\t\t\t\treflection center\n\t\t\t//\t\t(surface texel in current frame)\n\t\t\t//\t  (need to find this in previous frame)\n\t\t\t//\t\t\n\n\t\t\tconst vec3 refl_position = reflect(normalize(position - origin), geometry_normal) * average_ray_length + position;\n\t\t\tconst float refl_distance_to_plane = dot(geometry_normal, refl_position - prev_position);\n\t\t\tconst vec3 refl_on_plane = refl_position - geometry_normal * refl_distance_to_plane;\n\t\t\tconst float prev_distance_to_plane = dot(geometry_normal, prev_origin - prev_position);\n\t\t\tconst vec3 prev_origin_on_plane = prev_origin - geometry_normal * prev_distance_to_plane;\n\t\t\tconst float refl_center = prev_distance_to_plane / (prev_distance_to_plane + refl_distance_to_plane);\n\t\t\tconst vec3 parallax_position = mix(prev_origin_on_plane, refl_on_plane, refl_center);\n\n\t\t\tconst vec4 clip_space = inverse(ubo.ubo.prev_inv_proj) * vec4((inverse(ubo.ubo.prev_inv_view) * vec4(parallax_position, 1.)).xyz, 1.);\n\t\t\tconst vec2 parallax_uv = clip_space.xy / clip_space.w;\n\t\t\tconst ivec2 parallax_pix = ivec2((parallax_uv * 0.5 + vec2(0.5)) * vec2(res));\n\n\t\t\tif (any(greaterThanEqual(parallax_pix, ivec2(0))) && any(lessThan(parallax_pix, res))) {\n\t\t\t\tconst vec4 history_specular_sample = imageLoad( prev_temporal_specular, parallax_pix );\n\t\t\t\thistory_specular = closestColor(history_specular_sample.xyz, history_specular, specular);\n\t\t\t}\n\t\t}\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tif (IS_INVALIDV(history_specular)) {\n\t\t\tdebugPrintfEXT(\"PRE pix=(%d,%d) history_specular=inv\", pix.x, pix.y);\n\t\t\thistory_specular = vec3(0.);\n\t\t}\n\n\t\tif (IS_INVALIDV(specular)) {\n\t\t\tdebugPrintfEXT(\"PRE pix=(%d,%d) specular=(%f,%f,%f)\", pix.x, pix.y, PRIVEC3(specular));\n\t\t\tspecular = vec3(0.);\n\t\t}\n#endif\n\n\t\tif (better_depth_offset < depth_treshold) {\n\t\t\tdiffuse = mix(diffuse, history_diffuse, DIFFUSE_TEMPORAL_HISTORY_MIX_WEIGHT);\n\t\t\tspecular = mix(specular, history_specular, SPECULAR_TEMPORAL_HISTORY_MIX_WEIGHT);\n\t\t}\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tif (IS_INVALIDV(diffuse)) {\n\t\t\tdebugPrintfEXT(\"pix=(%d,%d) diffuse=inv\", pix.x, pix.y);\n\t\t\tdiffuse = vec3(0.);\n\t\t}\n\n\t\tif (IS_INVALIDV(specular)) {\n\t\t\tdebugPrintfEXT(\"pix=(%d,%d) new_specular=inv, specular=(%f, %f, %f) history_specular=(%f, %f, %f)\",\n\t\t\t\tpix.x, pix.y,\n\t\t\t\tspecular.r, specular.g, specular.b,\n\t\t\t\thistory_specular.r, history_specular.g, history_specular.b\n\t\t\t\t);\n\t\t\tspecular = vec3(0.);\n\t\t}\n#endif\n\n\t\tDEBUG_VALIDATE_RANGE_VEC3(\"denoiser.diffuse\", diffuse, 0., 1e6);\n\t\tDEBUG_VALIDATE_RANGE_VEC3(\"denoiser.specular\", specular, 0., 1e6);\n\n\t\timageStore(out_temporal_diffuse, pix, vec4(diffuse, depth));\n\t\timageStore(out_temporal_specular, pix, vec4(specular, 0./*unused*/));\n#endif // ifndef DISABLE_TEMPORAL_DENOISER\n\t}\n\n\tvec3 colour = vec3(0.);\n\n\tif (ubo.ubo.debug_display_only != DEBUG_DISPLAY_LIGHTING) {\n\t\tcolour = mixFinalColor(base_color, diffuse, specular, metalness);\n\t} else {\n\t\tcolour = diffuse + specular;\n\t}\n\n\tconst vec4 legacy_blend = imageLoad(legacy_blend, pix);\n\n\tcolour += imageLoad(emissive, pix).rgb;\n\t// Revealage. TODO: which colorspace?\n\tcolour *= legacy_blend.a;\n\n\tcolour = LINEARtoSRGB(colour);\n\n// See issue https://github.com/w23/xash3d-fwgs/issues/668, map test_blendmode_additive_alpha.\n// Adding emissive_blend to the final color in the *incorrect* sRGB-γ space. It makes\n// it look much more like the original. Adding emissive in the *correct* linear space differs\n// from the original a lot, and looks perceptively worse.\n\tcolour += legacy_blend.rgb;\n\n\tDEBUG_VALIDATE_RANGE_VEC3(\"denoiser.colour\", colour, 0., 1e6);\n\n\timageStore(out_dest, pix, vec4(colour, 0./*unused*/));\n}\n"
  },
  {
    "path": "ref/vk/shaders/denoiser_config.glsl",
    "content": "\n// not plane, it's sphere, but working\n#define NEAR_PLANE_OFFSET 5.\n\n// we downsample gi map and store bounces positions in neighboor texels\n// downsample image dimensions by 2 = store 4 bounces\n// downsample image dimensions by 3 = store 9 bounces\n#define GI_DOWNSAMPLE 2\n\n// max bounces for testing bounces visiblity\n#define GI_BOUNCES_MAX 1\n"
  },
  {
    "path": "ref/vk/shaders/denoiser_utils.glsl",
    "content": "#ifndef lk_dnsr_utils_LK_12231312\n#define lk_dnsr_utils_LK_12231312 1\n\n#define TEXEL_FLAG_TRANSPARENT 1\n#define TEXEL_FLAG_REFRACTION 2\n\n// clamp light exposition without loosing of color\nvec3 clamp_color(vec3 color, float clamp_value) {\n\tfloat max_color = max(max(color.r, color.g), color.b);\n\treturn max_color > clamp_value ? (color / max_color) * clamp_value : color;\n}\n\n// 3-th component is transparent texel status 0 or 1\nivec3 PixToCheckerboard(ivec2 pix, ivec2 res) {\n\tint is_transparent_texel = (pix.x + pix.y) % 2;\n\tivec2 out_pix = ivec2(pix.x / 2 + is_transparent_texel * (res.x / 2), pix.y);\n\treturn ivec3(out_pix, is_transparent_texel);\n}\n\n// 3-th component is transparent texel status 0 or 1, targeted to nesessary texel status\nivec3 PixToCheckerboard(ivec2 pix, ivec2 res, int is_transparent_texel) {\n\tivec2 out_pix = ivec2(pix.x / 2 + is_transparent_texel * (res.x / 2), pix.y);\n\treturn ivec3(out_pix, is_transparent_texel);\n}\n\n// optional choose checkerboard conversion if there is real transparence or not\nivec3 PixToCheckerboard(ivec2 pix, ivec2 res, int is_transparent_texel, int texel_flags) {\n\tif (texel_flags == TEXEL_FLAG_TRANSPARENT || texel_flags == TEXEL_FLAG_REFRACTION) {\n\t\treturn PixToCheckerboard(pix, res, is_transparent_texel);\n\t}\n\treturn PixToCheckerboard(pix, res);\n}\n\n\n// 3-th component is transparent texel status 0 or 1\nivec3 CheckerboardToPix(ivec2 pix, ivec2 res) {\n\tint half_res = res.x / 2;\n\tint is_transparent_texel = pix.x / half_res;\n\tint out_pix_x = (pix.x % half_res) * 2;\n\tint row_index = pix.y % 2;\n\tint checker_addition = is_transparent_texel + row_index - row_index*is_transparent_texel*2;\n\tivec2 out_pix = ivec2(out_pix_x + checker_addition, pix.y);\n\treturn ivec3(out_pix, is_transparent_texel);\n}\n\n\nvec3 OriginWorldPosition(mat4 inv_view) {\n\treturn (inv_view * vec4(0, 0, 0, 1)).xyz;\n}\n\nvec3 ScreenToWorldDirection(vec2 uv, mat4 inv_view, mat4 inv_proj) {\n\tvec4 target    = inv_proj * vec4(uv.x, uv.y, 1, 1);\n\tvec3 direction = (inv_view * vec4(normalize(target.xyz), 0)).xyz;\n\treturn normalize(direction);\n}\n\nvec3 WorldPositionFromDirection(vec3 origin, vec3 direction, float depth) {\n\treturn origin + normalize(direction) * depth;\n}\n\nvec3 FarPlaneDirectedVector(vec2 uv, vec3 forward, mat4 inv_view, mat4 inv_proj) {\n\tvec3 dir = ScreenToWorldDirection(uv, inv_view, inv_proj);\n\tfloat plane_length = dot(forward, dir);\n\treturn dir / max(0.001, plane_length);\n}\n\nvec2 WorldPositionToUV(vec3 position, mat4 proj, mat4 view) {\n\tvec4 clip_space = proj * vec4((view * vec4(position, 1.)).xyz, 1.);\n\treturn clip_space.xy / clip_space.w;\n}\n\nvec3 WorldPositionToUV2(vec3 position, mat4 inv_proj, mat4 inv_view) {\n\tconst vec3 out_of_bounds = vec3(0.,0.,-1.);\n\tconst float near_plane_treshold = 1.;\n\tvec3 origin = OriginWorldPosition(inv_view);\n\tvec3 forwardDirection = normalize(ScreenToWorldDirection(vec2(0.), inv_view, inv_proj));\n\tfloat depth = dot(forwardDirection, position - origin);\n\tif (depth < near_plane_treshold) return out_of_bounds;\n\tvec3 positionNearPlane = (position - origin) / depth;\n\tvec3 rightForwardDirection = ScreenToWorldDirection(vec2(1., 0.), inv_view, inv_proj);\n\tvec3 upForwardDirection = ScreenToWorldDirection(vec2(0., 1.), inv_view, inv_proj);\n\trightForwardDirection /= dot(forwardDirection, rightForwardDirection);\n\tupForwardDirection /= dot(forwardDirection, upForwardDirection);\n\tvec3 rightDirection = rightForwardDirection - forwardDirection;\n\tvec3 upDirection = upForwardDirection - forwardDirection;\n\tfloat x = dot(normalize(rightDirection), positionNearPlane - forwardDirection) / length(rightDirection);\n\tfloat y = dot(normalize(upDirection), positionNearPlane - forwardDirection) / length(upDirection);\n\tif (x < -1. || y < -1. || x > 1. || y > 1.) return out_of_bounds;\n\treturn vec3(x, y, 1.);\n}\n\nfloat normpdf2(in float x2, in float sigma) { return 0.39894*exp(-0.5*x2/(sigma*sigma))/sigma; }\nfloat normpdf(in float x, in float sigma) { return normpdf2(x*x, sigma); }\n\nivec2 UVToPix(vec2 uv, ivec2 res) {\n\tvec2 screen_uv = uv * 0.5 + vec2(0.5);\n\treturn ivec2(screen_uv.x * float(res.x), screen_uv.y * float(res.y));\n}\n\nvec2 PixToUV(ivec2 pix, ivec2 res) {\n\treturn (vec2(pix) /*+ vec2(0.5)*/) / vec2(res) * 2. - vec2(1.);\n}\n\nvec3 PBRMix(vec3 base_color_a, vec3 diffuse, vec3 specular, float metalness) {\n\tvec3 metal_colour = specular * base_color_a;\n\tvec3 dielectric_colour = mix(diffuse * base_color_a, specular, 0.04); // like in Unreal\n\treturn mix(dielectric_colour, metal_colour, metalness);\n}\n\nvec3 PBRMixFresnel(vec3 base_color_a, vec3 diffuse, vec3 specular, float metalness, float fresnel) {\n\tvec3 metal_colour = specular * base_color_a;\n\tfloat diffuse_specular_factor = mix(0.2, 0.04, fresnel);\n\tvec3 dielectric_colour = mix(diffuse * base_color_a, specular, diffuse_specular_factor);\n\treturn mix(dielectric_colour, metal_colour, metalness);\n}\n\nint per_frame_offset = 0;\n\nint quarterPart(ivec2 pix_in) {\n\tivec2 pix = pix_in % 2;\n\treturn (pix.x + 2 * pix.y + per_frame_offset) % 4;\n}\n\nint ninefoldPart(ivec2 pix_in) {\n\tivec2 pix = pix_in % 3;\n\treturn (pix.x + 3 * pix.y + per_frame_offset) % 9;\n}\n\nint texel_transparent_type(float transparent_alpha) {\n\treturn abs(transparent_alpha) < 0.05 ? 0 : transparent_alpha > 0. ? 2 : 3;\n}\n\nint checker_texel(ivec2 pix) {\n\treturn (pix.x + pix.y) % 2;\n}\n\nivec2 closest_checker_texel(ivec2 pix, int source_checker_texel) {\n\treturn checker_texel(pix) == source_checker_texel ? pix : pix + ivec2(1, 0);\n}\n\n\n#ifndef M_PI\n#define M_PI 3.1488\n#endif\n\n// Schlick's approximation to Fresnel term\n// f90 should be 1.0, except for the trick used by Schuler (see 'shadowedF90' function)\nvec3 evalFresnelSchlickM(vec3 f0, float f90, float NdotS) {\n\treturn f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f);\n}\n\nfloat luminanceM(vec3 rgb) {\n\treturn dot(rgb, vec3(0.2126f, 0.7152f, 0.0722f));\n}\n\nvec3 randomizedOnHemisphere(vec3 randomVec, vec3 normal) {\n\tfloat directionality = dot(normal, randomVec);\n\tif (directionality > 0.) return normalize(randomVec);\n\treturn -normalize(randomVec);\n}\n\nvec3 sampleSphere(vec2 uv)\n{\n    float y = 2.0 * uv.x - 1;\n    float theta = 2.0 * M_PI * uv.y;\n    float r = sqrt(1.0 - y * y);\n    return vec3(cos(theta) * r, y, sin(theta) * r);\n}\n\n// Microfacet bounce from this example https://www.shadertoy.com/view/Md3yWl\n\nvec3 SphereRand( vec2 rand )\n{\n    rand += vec2(.5);\n    float sina = rand.x*2. - 1.;\n    float b = 6.283*rand.y;\n    float cosa = sqrt(1.-sina*sina);\n    return vec3(cosa*cos(b),sina,cosa*sin(b));\n}\n\nvec3 PowRand( vec3 rand, vec3 axis, float fpow )\n{\n\t//vec3 r = normalize(rand  - vec3(0.5));\n\tvec3 r = sampleSphere(rand.xz);\n    //vec3 r = SphereRand(rand.xy);\n    float d = dot(r,axis);\n    r -= d*axis;\n    r = normalize(r);\n    float h = d*.5+.5;\n    r *= sqrt( 1. - pow( h, 2./(fpow+1.) ) );\n    r += axis*sqrt(1.-dot(r,r));\n    return r;\n}\n\n#define FIX_NAN(COLOR) (any(isnan(COLOR)) ? vec4(0.) : COLOR)\n\n#endif // #ifndef lk_dnsr_utils_LK_12231312\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_atrous.glsl",
    "content": "\n#ifndef KERNEL_X\n#define KERNEL_X 1\n#endif\n\n#ifndef KERNEL_Y\n#define KERNEL_Y 1\n#endif\n\n\n#ifndef OFFSET\n#define OFFSET ivec(1, 1)\n#endif\n\n#ifndef DEPTH_THRESHOLD\n#define DEPTH_THRESHOLD 0.1\n#endif\n\n#define GI_BLUR_NORMALS_THRESHOLD_LOW 0.5\n#define GI_BLUR_NORMALS_THRESHOLD_MAX 0.9\n\n#include \"noise.glsl\"\n#include \"brdf.h\"\n#include \"utils.glsl\"\n#include \"denoiser_config.glsl\"\n#include \"denoiser_utils.glsl\"\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0, rgba16f) uniform image2D OUTPUT_GI_1;\nlayout(set = 0, binding = 1, rgba16f) uniform image2D OUTPUT_GI_2;\n\nlayout(set = 0, binding = 2, rgba16f) uniform readonly image2D INPUT_GI_1;\nlayout(set = 0, binding = 3, rgba16f) uniform readonly image2D INPUT_GI_2;\nlayout(set = 0, binding = 4, rgba8)   uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 5, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 6, rgba16f) uniform readonly image2D normals_gs;\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(set = 0, binding = 7) uniform UBO { UniformBuffer ubo; } ubo;\n\nvoid main() {\n\tivec2 res = ivec2(imageSize(INPUT_GI_1));\n\tivec2 pix = ivec2(gl_GlobalInvocationID);\n\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) == 0) {\n\t\treturn;\n\t}\n\n\tconst vec4 gi_sh2_src = FIX_NAN(imageLoad(INPUT_GI_2, pix));\n\tconst float depth = FIX_NAN(imageLoad(position_t, pix)).w;\n\tconst float metalness_factor = /*FIX_NAN(imageLoad(material_rmxx, pix)).y > .5 ? 1. : 0.*/ 1.;\n\tconst vec3 normal = normalDecode(FIX_NAN(imageLoad(normals_gs, pix)).xy);\n\n\tvec4 gi_sh1 = vec4(0.);\n\tvec2 gi_sh2 = vec2(0.);\n\n\tfloat weight_sum = 0.;\n\tfor (int x = -KERNEL_X; x <= KERNEL_X; ++x) {\n\t\tfor (int y = -KERNEL_Y; y <= KERNEL_Y; ++y) {\n\t\t\tconst ivec2 p = (pix + ivec2(x, y) * OFFSET);\n\t\t\tif (any(greaterThanEqual(p, res)) || any(lessThan(p, ivec2(0)))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// metal surfaces have gi after 2 bounce, diffuse after 1, don't mix them\n//\t\t\tconst float current_metalness = FIX_NAN(imageLoad(material_rmxx, p)).y;\n//\t\t\tif (abs(metalness_factor - current_metalness) > .5)\n//\t\t\t\tcontinue;\n\n\t\t\tconst vec4 current_gi_sh1 = FIX_NAN(imageLoad(INPUT_GI_1, p));\n\t\t\tconst vec4 current_gi_sh2 = FIX_NAN(imageLoad(INPUT_GI_2, p));\n\t\t\tconst vec3 current_normal = normalDecode(FIX_NAN(imageLoad(normals_gs, p)).xy);\n\n\t\t\tconst float depth_current = FIX_NAN(imageLoad(position_t, p)).w;\n\t\t\tconst float depth_offset = abs(depth - depth_current) / max(0.001, depth);\n\t\t\tconst float gi_depth_factor = 1. - smoothstep(0., DEPTH_THRESHOLD, depth_offset);\n\t\t\tconst float normals_factor = smoothstep(GI_BLUR_NORMALS_THRESHOLD_LOW, GI_BLUR_NORMALS_THRESHOLD_MAX, dot(normal, current_normal));\n\n\t\t\tfloat weight = gi_depth_factor * normals_factor; // square blur for more efficient light spreading\n\n//\t\t#ifdef SPREAD_UPSCALED\n//\t\t\tweight *= (GI_DOWNSAMPLE * GI_DOWNSAMPLE);\n//\t\t#endif\n\n//\t\t\tconst float sigma = KERNEL_X / 2.;\n//\t\t\tconst float weight = normpdf(x, sigma) * normpdf(y, sigma) * gi_depth_factor * normals_factor;\n\n\t\t\tgi_sh1 += current_gi_sh1 * weight;\n\t\t\tgi_sh2 += current_gi_sh2.xy * weight;\n\t\t\tweight_sum += weight;\n\t\t}\n\t}\n\n\tif (weight_sum > 0.) {\n\t\tgi_sh1 /= weight_sum;\n\t\tgi_sh2 /= weight_sum;\n\t}\n\n\timageStore(OUTPUT_GI_1, pix, gi_sh1);\n\timageStore(OUTPUT_GI_2, pix, vec4(gi_sh2, gi_sh2_src.zw));\n}\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_init.comp",
    "content": "#version 460\n\n#include \"noise.glsl\"\n#include \"utils.glsl\"\n#include \"brdf.h\"\n#include \"denoiser_config.glsl\"\n#include \"denoiser_utils.glsl\"\n#include \"spherical_harmonics.glsl\"\n#include \"color_spaces.glsl\"\n\n#define GI_LIMIT_LUMINANCE 1.0 // aggressive and dumb removing fireflyes\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0, rgba16f) uniform image2D out_sh1_ping;\nlayout(set = 0, binding = 1, rgba16f) uniform image2D out_sh2_ping;\n\nlayout(set = 0, binding = 2, rgba8) uniform readonly image2D base_color_a;\nlayout(set = 0, binding = 3, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 4, rgba16f) uniform readonly image2D indirect_diffuse;\nlayout(set = 0, binding = 5, rgba32f) uniform readonly image2D first_bounce_direction;\n\nvoid main() {\n\tivec2 res = ivec2(imageSize(base_color_a));\n\tivec2 pix = ivec2(gl_GlobalInvocationID);\n\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\n\tif (any(lessThan(pix, res / GI_DOWNSAMPLE))) {\n\t\tvec3 global_illumination = FIX_NAN(imageLoad(indirect_diffuse, pix)).rgb;\n\n\t\t// limit irradiance for fireflyes reducion\n\t\tfloat gi_lum = luminance(global_illumination);\n\t\tif (gi_lum > 0.) {\n\t\t\tfloat limit = smoothstep(0., GI_LIMIT_LUMINANCE, gi_lum);\n\t\t\tglobal_illumination = mix(global_illumination, global_illumination / gi_lum, limit);\n\t\t}\n\n\t\tvec4 gi_sh1 = vec4(0.);\n\t\tvec2 gi_sh2 = vec2(0.);\n\n\t\t// store indirectional light in spherical harmonics\n\t\tif (any(greaterThan(global_illumination.rgb, vec3(0.)))) {\n\t\t\tconst vec3 indirect_color = global_illumination.rgb * STORAGE_SCALE_LF;\n\t\t\tconst vec3 direction = FIX_NAN(imageLoad(first_bounce_direction, pix)).xyz;\n\n\t\t\tif (length(direction) > 0.) {\n\t\t\t\tSH low_freq = irradiance_to_SH(indirect_color, normalize(direction));\n\t\t\t\tgi_sh1 = low_freq.shY;\n\t\t\t\tgi_sh2 = low_freq.CoCg;\n\t\t\t}\n\t\t}\n\n\t\tfor(int x = 0; x < GI_DOWNSAMPLE; x++) {\n\t\t\tfor(int y = 0; y < GI_DOWNSAMPLE; y++) {\n\t\t\t\tconst ivec2 pix_upscaled = pix * GI_DOWNSAMPLE + ivec2(x,y);\n\t\t\t\tif (any(greaterThanEqual(pix_upscaled, res)))\n\t\t\t\t\tcontinue;\n\n\t\t\t\timageStore(out_sh1_ping, pix_upscaled, gi_sh1);\n\t\t\t\timageStore(out_sh2_ping, pix_upscaled, vec4(gi_sh2, 0., 0.));\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_pass_1.comp",
    "content": "#version 460\n#extension GL_GOOGLE_include_directive : require\n\n#define OFFSET ivec2(1, 1)\n\n#define INPUT_GI_1 sh1_ping\n#define INPUT_GI_2 sh2_ping\n\n#define OUTPUT_GI_1 out_sh1_pong\n#define OUTPUT_GI_2 out_sh2_pong\n\n#include \"diffuse_gi_sh_atrous.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_pass_2.comp",
    "content": "#version 460\n#extension GL_GOOGLE_include_directive : require\n\n#define OFFSET ivec2(2, 2)\n\n#define INPUT_GI_1 sh1_pong\n#define INPUT_GI_2 sh2_pong\n\n#define OUTPUT_GI_1 out_sh1_ping\n#define OUTPUT_GI_2 out_sh2_ping\n\n#include \"diffuse_gi_sh_atrous.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_pass_3.comp",
    "content": "#version 460\n#extension GL_GOOGLE_include_directive : require\n\n#define OFFSET ivec2(4, 4)\n\n#define INPUT_GI_1 sh1_ping\n#define INPUT_GI_2 sh2_ping\n\n#define OUTPUT_GI_1 out_sh1_pong\n#define OUTPUT_GI_2 out_sh2_pong\n\n#include \"diffuse_gi_sh_atrous.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_pass_4.comp",
    "content": "#version 460\n#extension GL_GOOGLE_include_directive : require\n\n#define OFFSET ivec2(8, 8)\n\n#define INPUT_GI_1 sh1_pong\n#define INPUT_GI_2 sh2_pong\n\n#define OUTPUT_GI_1 out_sh1_ping\n#define OUTPUT_GI_2 out_sh2_ping\n\n#include \"diffuse_gi_sh_atrous.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_pass_5.comp",
    "content": "#version 460\n#extension GL_GOOGLE_include_directive : require\n\n#define OFFSET ivec2(32, 32)\n\n#define INPUT_GI_1 sh1_ping\n#define INPUT_GI_2 sh2_ping\n\n#define OUTPUT_GI_1 out_sh1_pong\n#define OUTPUT_GI_2 out_sh2_pong\n\n#include \"diffuse_gi_sh_atrous.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/diffuse_gi_sh_denoise_save.comp",
    "content": "#version 460\n\n#include \"brdf.h\"\n#include \"noise.glsl\"\n#include \"utils.glsl\"\n#include \"color_spaces.glsl\"\n#include \"denoiser_config.glsl\"\n#include \"denoiser_utils.glsl\"\n#include \"spherical_harmonics.glsl\"\n\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0, rgba16f) uniform writeonly image2D out_indirect_diffuse_denoised_by_sh;\nlayout(set = 0, binding = 1, rgba16f) uniform writeonly image2D out_indirect_diffuse;\nlayout(set = 0, binding = 2, rgba16f) uniform writeonly image2D out_indirect_diffuse_atrous1;\n\nlayout(set = 0, binding = 3, rgba8)   uniform readonly image2D base_color_a;\nlayout(set = 0, binding = 4, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 5, rgba16f) uniform readonly image2D sh1_pong;\nlayout(set = 0, binding = 6, rgba16f) uniform readonly image2D sh2_pong;\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(set = 0, binding = 7) uniform UBO { UniformBuffer ubo; } ubo;\n\nvoid readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) {\n\tconst vec4 n = FIX_NAN(imageLoad(normals_gs, uv));\n\tgeometry_normal = normalDecode(n.xy);\n\tshading_normal = normalDecode(n.zw);\n}\n\n\nvoid main() {\n\tivec2 res = ivec2(imageSize(base_color_a));\n\tivec2 pix = ivec2(gl_GlobalInvocationID);\n\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) == 0) {\n\t\timageStore(out_indirect_diffuse_denoised_by_sh, pix, vec4(0.));\n\t\treturn;\n\t}\n\n\tvec3 geometry_normal, shading_normal;\n\treadNormals(pix, geometry_normal, shading_normal);\n\n\t// albedo\n\tconst vec4 base_color_src = FIX_NAN(imageLoad(base_color_a, pix));\n\tconst vec3 base_color = SRGBtoLINEAR(base_color_src.rgb);\n\n\t// global illumination re-light\n\tSH low_freq;\n\tlow_freq.shY = FIX_NAN(imageLoad(sh1_pong, pix));\n\tlow_freq.CoCg = FIX_NAN(imageLoad(sh2_pong, pix)).xy;\n\n\tconst vec3 diffuse_gi = project_SH_irradiance(low_freq, shading_normal) / STORAGE_SCALE_LF;\n\n\timageStore(out_indirect_diffuse_denoised_by_sh, pix, vec4(diffuse_gi, 0.));\n\timageStore(out_indirect_diffuse, pix, vec4(0.));\n\timageStore(out_indirect_diffuse_atrous1, pix, vec4(0.));\n}\n"
  },
  {
    "path": "ref/vk/shaders/empty.rmiss",
    "content": "#version 460 core\n#extension GL_EXT_ray_tracing: require\n\nvoid main() {\n}\n"
  },
  {
    "path": "ref/vk/shaders/indirect_diffuse_atrous1.comp",
    "content": "#version 460\n//#include \"debug.glsl\"\n//#include \"utils.glsl\"\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0) uniform UBO { UniformBuffer ubo; } ubo;\n\nlayout(set = 0, binding = 1, rgba16f) uniform image2D out_indirect_diffuse_atrous1;\n\nlayout(set = 0, binding = 2, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 3, rgba16f) uniform readonly image2D normals_gs;\n\nlayout(set = 0, binding = 4, rgba16f) uniform readonly image2D indirect_diffuse;\n\n#include \"atrous.glsl\"\n\nconst int INDIRECT_SCALE = 2;\n\nvoid main() {\n\tconst ivec2 res = ubo.ubo.res;\n\tconst ivec2 pix = ivec2(gl_GlobalInvocationID);\n\n\t// skip this pass if we used other denoising pipeline\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_DENOISE_GI_BY_SH) != 0) {\n\t\timageStore(out_indirect_diffuse_atrous1, pix, vec4(0.));\n\t\treturn;\n\t}\n\n\tconst ivec2 res_scaled = res / INDIRECT_SCALE;\n\tif (any(greaterThanEqual(pix, res_scaled))) {\n\t\treturn;\n\t}\n\n\tconst vec3 pos = imageLoad(position_t, pix * INDIRECT_SCALE).xyz;\n\tconst vec3 shading_normal = normalDecode(imageLoad(normals_gs, pix * INDIRECT_SCALE).zw);\n\n\tvec3 indiff = vec3(0.);\n\tfloat weight_total_indirect_diffuse = 0.;\n\tfor (int x = 0; x <= ATROUS_KERNEL_WIDTH; ++x) {\n\t\tfor (int y = 0; y <= ATROUS_KERNEL_WIDTH; ++y) {\n\t\t\tconst ivec2 offset = ivec2(x, y);\n\n\t\t\t// 3. Indirect diffuse\n\t\t\t{\n\t\t\t\tconst float sn_phi = .5;\n\t\t\t\tconst float p_phi = 3.;\n\t\t\t\tconst int step_width = 1;\n\t\t\t\tivec2 p;\n\t\t\t\tconst float weight = aTrousSampleWeigth(\n\t\t\t\t\tres, pix, pos, shading_normal, offset, step_width, INDIRECT_SCALE, sn_phi, p_phi, p);\n\n\t\t\t\tif (weight > 0.) {\n\t\t\t\t\tconst bool do_indirect = all(lessThan(p, res_scaled));\n\t\t\t\t\tif (do_indirect) {\n\t\t\t\t\t\tweight_total_indirect_diffuse += weight;\n\t\t\t\t\t\tindiff += imageLoad(indirect_diffuse, p).rgb * weight;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} // for y\n\t} // for x\n\n\tconst float one_over_weight_indirect_diffuse = weight_total_indirect_diffuse == 0. ? 0 : 1. / weight_total_indirect_diffuse;\n\tindiff *= one_over_weight_indirect_diffuse;\n\n\t//indiff = imageLoad(indirect_diffuse, pix).rgb;\n\n\timageStore(out_indirect_diffuse_atrous1, pix, vec4(indiff, 0./*unused*/));\n}\n"
  },
  {
    "path": "ref/vk/shaders/light.glsl",
    "content": "#extension GL_EXT_control_flow_attributes : require\n#include \"debug.glsl\"\n\nconst float color_culling_threshold = 0;//600./color_factor;\nconst float shadow_offset_fudge = .1;\n\n#include \"brdf.glsl\"\n#include \"light_common.glsl\"\n\n#if LIGHT_POLYGON\n#include \"light_polygon.glsl\"\n#endif\n\n#if LIGHT_POINT\n// TODO Consider splitting into different arrays:\n// 1. Spherical lights\n// 2. Spotlights\n// 3. Env|dir lights\nvoid computePointLights(vec3 P, vec3 N, uint cluster_index, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {\n\tdiffuse = specular = vec3(0.);\n\n\t//diffuse = vec3(1.);//float(lights.m.num_point_lights) / 64.);\n#define USE_CLUSTERS\n#ifdef USE_CLUSTERS\n\tconst uint num_point_lights = uint(light_grid.clusters_[cluster_index].num_point_lights);\n\tfor (uint j = 0; j < num_point_lights; ++j) {\n\t\tconst uint i = uint(light_grid.clusters_[cluster_index].point_lights[j]);\n\n\t\t// HACK: work around corrupted/stale cluster indexes\n\t\t// See https://github.com/w23/xash3d-fwgs/issues/730\n\t\tif (i >= lights.m.num_point_lights)\n\t\t\tcontinue;\n#else\n\tfor (uint i = 0; i < lights.m.num_point_lights; ++i) {\n#endif\n\n\t\tconst vec3 spotlight_dir = lights.m.point_lights[i].dir_stopdot2.xyz;\n\t\tconst bool is_environment = (lights.m.point_lights[i].environment != 0);\n\n\t\t// TODO blue noise\n\t\tconst vec2 rnd = vec2(rand01(), rand01());\n\n\t\tvec3 light_dir;\n\t\tvec3 color = lights.m.point_lights[i].color_stopdot.rgb;\n\t\tfloat light_dist = 0.;\n\t\tfloat one_over_pdf = 1.;\n\t\tif (is_environment) {\n\t\t\t// Environment/directional light\n\t\t\t// FIXME extract, it is rather different from other point/sphere/spotlights\n\t\t\tconst float cos_theta_max = lights.m.point_lights[i].dir_stopdot2.a;\n\t\t\tconst vec3 dir_sample_z = sampleConeZ(rnd, cos_theta_max);\n\t\t\tlight_dir = normalize(orthonormalBasisZ(spotlight_dir) * dir_sample_z);\n\n\t\t\t// If light sample is below horizon, skip\n\t\t\tconst float light_dot = dot(light_dir, N);\n\t\t\tif (light_dot < 1e-5)\n\t\t\t\tcontinue;\n\n\t\t\tone_over_pdf = 2. * kPi * max(0., 1. - cos_theta_max);\n\t\t} /* is_environment */ else {\n\t\t\t// Spherical lights\n\t\t\tconst vec3 light_pos = lights.m.point_lights[i].origin_r2.xyz;\n\t\t\tconst float light_r2 = lights.m.point_lights[i].origin_r2.w;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\tif (IS_INVALID(light_r2) || light_r2 <= 0.) {\n\t\t\t\tdebugPrintfEXT(\"light %d INVALID light_r2 = %f\", i, light_r2);\n\t\t\t}\n#endif\n\n\t\t\t//const vec3 ld = light_pos - P;\n\t\t\tlight_dir = light_pos - P;\n\t\t\tconst float light_dist2 = dot(light_dir, light_dir);\n\n\t\t\t// Light is too close, skip\n\t\t\tconst float d2_minus_r2 = light_dist2 - light_r2;\n\t\t\tif (d2_minus_r2 <= 0.)\n\t\t\t\tcontinue;\n\n\t\t\tlight_dist = sqrt(light_dist2);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\tif (IS_INVALID(light_dist)) {\n\t\t\t\tdebugPrintfEXT(\"light.glsl:%d P=(%f,%f,%f) light_pos=(%f,%f,%f) light_dist2=%f light_r2=%f INVALID light_dist=%f\",\n\t\t\t\t\t__LINE__, PRIVEC3(P), PRIVEC3(light_pos), light_dist2, light_r2, light_dist);\n\t\t\t}\n#endif\n\n\t\t\t// Cosine of \"solid angle\"\n\t\t\t// Bad precision: const float cos_theta_max = min(1., sqrt(d2_minus_r2) / light_dist);\n\t\t\t// Meh precision (for small r and big light dist):\n\t\t\tconst float cos_theta_max = min(1., sqrt(d2_minus_r2 / light_dist2));\n\t\t\t// Worse precision: const float cos_theta_max = min(1., sqrt(1. - light_r2 / light_dist2));\n\t\t\t// Possible TODO: dither cos_theta_max for large distances a bit\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tif (IS_INVALID(cos_theta_max) || cos_theta_max < 0. || cos_theta_max > 1.) {\n\t\t\tdebugPrintfEXT(\"light.glsl:%d P=(%f,%f,%f) light_pos=(%f,%f,%f) light_dist2=%f light_r2=%f INVALID cos_theta_max=%f\",\n\t\t\t\t__LINE__, PRIVEC3(P), PRIVEC3(light_pos), light_dist2, light_r2, cos_theta_max);\n\t\t\tcontinue;\n\t\t}\n#endif\n\n\t\t\t// Sample on the visible disc\n\t\t\tconst vec3 dir_sample_z = sampleConeZ(rnd, cos_theta_max);\n\t\t\tconst mat3 basis = orthonormalBasisZ(light_dir / light_dist);\n\t\t\tlight_dir = normalize(basis * dir_sample_z);\n\t\t\t//light_dir = normalize(light_dir);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\t//DEBUG_VALIDATE_RANGE_VEC3(\"light.glsl\", light_dir, -1., 1.);\n\t\t\tif (IS_INVALIDV(light_dir) || any(lessThan(light_dir,vec3(-1.))) || any(greaterThan(light_dir,vec3(1.)))) { \\\n\t\t\t\t/*debugPrintfEXT(\"ld=(%f,%f,%f), ldn=(%f,%f,%f); basis=((%f,%f,%f), (%f,%f,%f), (%f,%f,%f))\",\n\t\t\t\t\tPRIVEC3(ld),\n\t\t\t\t\tPRIVEC3(normalize(ld)),\n\t\t\t\t\tPRIVEC3(basis[0]),\n\t\t\t\t\tPRIVEC3(basis[1]),\n\t\t\t\t\tPRIVEC3(basis[2]));*/\n\t\t\t\tdebugPrintfEXT(\"light.glsl:%d cos_theta_max=%f light_dist=%f dir_sample_z=(%f,%f,%f) INVALID light_dir=(%f, %f, %f)\",\n\t\t\t\t\t__LINE__, cos_theta_max, light_dist, PRIVEC3(dir_sample_z), PRIVEC3(light_dir));\n\t\t\t}\n#endif\n\n\t\t\t// If light sample is below horizon, skip\n\t\t\tconst float light_dot = dot(light_dir, N);\n\t\t\tif (light_dot < 1e-5)\n\t\t\t\tcontinue;\n\n\t\t\tfloat spot_attenuation = 1.;\n\t\t\t// Spotlights\n\t\t\t// Check for angles early\n\t\t\t// TODO split into separate spotlights and point lights arrays\n\t\t\tconst float spot_dot = dot(light_dir, spotlight_dir);\n\t\t\tconst float stopdot2 = lights.m.point_lights[i].dir_stopdot2.a;\n\t\t\tif (spot_dot < stopdot2)\n\t\t\t\tcontinue;\n\n\t\t\tconst float stopdot = lights.m.point_lights[i].color_stopdot.a;\n\n\t\t\t// For non-spotlighths stopdot will be -1.. spot_dot can never be less than that\n\t\t\tif (spot_dot < stopdot) {\n\t\t\t\tspot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2);\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\t\tif (IS_INVALID(spot_attenuation)) {\n\t\t\t\t\tdebugPrintfEXT(\"light.glsl:%d spot_dot=%f stopdot=%f stopdot2=%f INVALID spot_attenuation=%f\",\n\t\t\t\t\t\t__LINE__, spot_dot, stopdot, stopdot2, spot_attenuation);\n\t\t\t\t}\n#endif\n\t\t\t\t// Skip the rest of the computation for points completely outside of the light cone\n\t\t\t\tif (spot_attenuation <= 0.)\n\t\t\t\t\tcontinue;\n\t\t\t} // Spotlight\n\n\t\t\t// d2=4489108.500000 r2=1.000000 dist=2118.751465 spot_attenuation=0.092902 INVALID pdf=-316492608.000000\n\t\t\t// Therefore, need to clamp denom with max\n\t\t\t// TODO need better sampling right nao\n\t\t\tone_over_pdf = 2. * kPi * max(0., 1. - cos_theta_max) * spot_attenuation;\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\tif (IS_INVALID(one_over_pdf) || one_over_pdf < 0.) {\n\t\t\t\tdebugPrintfEXT(\"light.glsl:%d light_dist2=%f light_r2=%f light_dist=%f spot_attenuation=%f INVALID one_over_pdf=%f\",\n\t\t\t\t\t__LINE__, light_dist2, light_r2, light_dist, spot_attenuation, one_over_pdf);\n\t\t\t}\n#endif\n\t\t} // Sphere/spot lights\n\n\t\tcolor *= one_over_pdf;\n\n\t\tvec3 ldiffuse, lspecular;\n\t\tevalSplitBRDF(N, light_dir, view_dir, material, ldiffuse, lspecular);\n\t\tldiffuse *= color;\n\t\tlspecular *= color;\n\n\t\t// TODO does this make sense for diffuse-vs-specular bounce modes?\n\t\tconst vec3 combined = ldiffuse + lspecular;\n\t\tif (dot(combined,combined) < color_culling_threshold)\n\t\t\tcontinue;\n\n\t\tif (is_environment) {\n\t\t\tif (shadowedSky(P, light_dir))\n\t\t\t\tcontinue;\n\t\t} else {\n\t\t\tif (shadowed(P, light_dir, light_dist + shadow_offset_fudge))\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tdiffuse += ldiffuse;\n\t\tspecular += lspecular;\n\t} // for all lights\n}\n#endif\n\nvoid computeLighting(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, out vec3 diffuse, out vec3 specular) {\n\tdiffuse = specular = vec3(0.);\n\n\t// No direct lighting for white furnace mode. The only light sources is no-hit|SURF_SKY bounce indirect light.\n\tif ((ubo.ubo.debug_flags & DEBUG_FLAG_WHITE_FURNACE) != 0) {\n\t\treturn;\n\t}\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(P) || IS_INVALIDV(N) || IS_INVALIDV(view_dir)) {\n\t\tdebugPrintfEXT(\"INVALID computeLighting(P=(%f,%f,%f), N=(%f,%f,%f), view_dir=(%f,%f,%f))\",\n\t\t\tPRIVEC3(P), PRIVEC3(N), PRIVEC3(view_dir));\n\t\treturn;\n\t}\n#endif\n\n\tconst ivec3 light_cell = ivec3(floor(P / LIGHT_GRID_CELL_SIZE)) - lights.m.grid_min_cell;\n\tconst uint cluster_index = uint(dot(light_cell, ivec3(1, lights.m.grid_size.x, lights.m.grid_size.x * lights.m.grid_size.y)));\n\n#ifdef USE_CLUSTERS\n\tif (any(greaterThanEqual(light_cell, lights.m.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) {\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tdebugPrintfEXT(\"light_cell=(%d,%d,%d) OOB size=(%d, %d, %d)\", PRIVEC3(light_cell), PRIVEC3(lights.m.grid_size));\n#endif\n\t\treturn; // vec3(1., 0., 0.);\n\t}\n#endif\n\n\t// const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET;\n\t// const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]);\n\t// const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]);\n\t// const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET;\n\t//C = vec3(float(num_emissive_surfaces));\n\n\t//C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces)));\n\t//C += .3 * fract(vec3(light_cell) / 4.);\n\n#if LIGHT_POLYGON\n\tsampleEmissiveSurfaces(P, N, view_dir, material, cluster_index, diffuse, specular);\n#endif\n\n#if LIGHT_POINT\n\tvec3 ldiffuse = vec3(0.), lspecular = vec3(0.);\n\tcomputePointLights(P, N, cluster_index, view_dir, material, ldiffuse, lspecular);\n\tdiffuse += ldiffuse;\n\tspecular += lspecular;\n#endif\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(diffuse) || any(lessThan(diffuse,vec3(0.)))) {\n\t\tdebugPrintfEXT(\"P=(%f,%f,%f) N=(%f,%f,%f) INVALID diffuse=(%f,%f,%f)\",\n\t\t\tPRIVEC3(P), PRIVEC3(N), PRIVEC3(diffuse));\n\t\tdiffuse = vec3(0.);\n\t}\n\n\tif (IS_INVALIDV(specular) || any(lessThan(specular,vec3(0.)))) {\n\t\tdebugPrintfEXT(\"P=(%f,%f,%f) N=(%f,%f,%f) INVALID specular=(%f,%f,%f)\",\n\t\t\tPRIVEC3(P), PRIVEC3(N), PRIVEC3(specular));\n\t\tspecular = vec3(0.);\n\t}\n#endif\n}\n"
  },
  {
    "path": "ref/vk/shaders/light_common.glsl",
    "content": "#ifndef LIGHT_COMMON_GLSL_INCLUDED\n#define LIGHT_COMMON_GLSL_INCLUDED\n#extension GL_EXT_nonuniform_qualifier : enable\n\n#include \"ray_kusochki.glsl\"\n\n#ifdef RAY_TRACE2\n#include \"ray_shadow_interface.glsl\"\nlayout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadEXT RayPayloadShadow payload_shadow;\n#endif\n\n#ifdef RAY_TRACE\nuint traceShadowRay(vec3 pos, vec3 dir, float dist, uint flags) {\n\tpayload_shadow.hit_type = SHADOW_HIT;\n\ttraceRayEXT(tlas,\n\t\tflags,\n\t\tGEOMETRY_BIT_CASTS_SHADOW,\n\t\tSHADER_OFFSET_HIT_SHADOW_BASE, SBT_RECORD_SIZE, SHADER_OFFSET_MISS_SHADOW,\n\t\tpos, 0., dir, dist - shadow_offset_fudge, PAYLOAD_LOCATION_SHADOW);\n\treturn payload_shadow.hit_type;\n}\n#endif\n\n#if defined(RAY_QUERY)\nbool shadowTestAlphaMask(vec3 pos, vec3 dir, float dist) {\n\trayQueryEXT rq;\n\tconst uint flags =  0\n\t\t// TODO figure out whether to turn off culling for alpha-tested geometry.\n\t\t// Alpha tested geometry usually comes as thick double-sided brushes.\n\t\t// Turning culling on makes shadows disappear from one side, which makes it look rather weird from one side.\n\t\t// Turning culling off makes such geometry cast \"double shadow\", which looks a bit weird from both sides.\n\t\t//| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsNoOpaqueEXT\n\t\t| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t;\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_ALPHA_TEST, pos, 0., dir, dist);\n\n\twhile (rayQueryProceedEXT(rq)) {\n\t\t// Alpha test, takes 10ms\n\t\t// TODO check other possible ways of doing alpha test. They might be more efficient:\n\t\t// 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive\n\t\t//    texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence.\n\t\t//    Separate pass could be more efficient as it'd be doing the same thing for every invocation.\n\t\t// 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha\n\t\tconst uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false);\n\t\tconst uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false);\n\t\tconst uint kusok_index = instance_kusochki_offset + geometry_index;\n\t\tconst Kusok kusok = getKusok(kusok_index);\n\n\t\tconst uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false);\n\t\tconst uint first_index_offset = kusok.index_offset + primitive_index * 3;\n\t\tconst uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset;\n\t\tconst uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset;\n\t\tconst uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset;\n\t\tconst vec2 uvs[3] = {\n\t\t\tGET_VERTEX(vi1).gl_tc,\n\t\t\tGET_VERTEX(vi2).gl_tc,\n\t\t\tGET_VERTEX(vi3).gl_tc,\n\t\t};\n\t\tconst vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false);\n\t\tconst vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary);\n\t\tconst vec4 texture_color = texture(textures[nonuniformEXT(kusok.material.tex_base_color)], uv);\n\n\t\tconst float alpha_mask_threshold = .1f;\n\t\tif (texture_color.a >= alpha_mask_threshold) {\n\t\t\trayQueryConfirmIntersectionEXT(rq);\n\t\t}\n\t}\n\n\treturn rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT;\n}\n#endif\n\nbool shadowed(vec3 pos, vec3 dir, float dist) {\n\t// FIXME figure out how could this happen, see https://github.com/w23/xash3d-fwgs/issues/771\n\tif (isnan(dist))\n\t\treturn true;\n\n#ifdef RAY_TRACE\n\tconst uint flags =  0\n\t\t//| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsOpaqueEXT\n\t\t| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t;\n\tconst uint hit_type = traceShadowRay(pos, dir, dist, flags);\n\treturn payload_shadow.hit_type == SHADOW_HIT;\n#elif defined(RAY_QUERY)\n\t{\n\t\tdist -= shadow_offset_fudge;\n\t\tif (dist <= 0.)\n\t\t\treturn false;\n\n\t\tconst uint flags =  0\n\t\t\t// Culling for shadows breaks more things (e.g. de_cbble slightly off the ground boxes) than it probably fixes. Keep it turned off.\n\t\t\t//| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t\t| gl_RayFlagsOpaqueEXT\n\t\t\t| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t\t;\n\t\trayQueryEXT rq;\n\t\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_CASTS_SHADOW, pos, 0., dir, dist);\n\t\twhile (rayQueryProceedEXT(rq)) {}\n\n\t\tif (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT)\n\t\t\treturn true;\n\t}\n\n\treturn shadowTestAlphaMask(pos, dir, dist);\n\n#else\n#error RAY_TRACE or RAY_QUERY\n#endif\n}\n\nbool shadowedSky(vec3 pos, vec3 dir) {\n#ifdef RAY_TRACE\n\tconst uint flags =  0\n\t\t//| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsOpaqueEXT\n\t\t| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t;\n\tconst uint hit_type = traceShadowRay(pos, dir, dist, flags);\n\treturn payload_shadow.hit_type != SHADOW_SKY;\n#elif defined(RAY_QUERY)\n\n\trayQueryEXT rq;\n\tconst uint flags = 0\n\t\t// Culling for shadows breaks more things (e.g. de_cbble slightly off the ground boxes) than it probably fixes. Keep it turned off.\n\t\t//| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t| gl_RayFlagsOpaqueEXT\n\t\t//| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t//| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t;\n\tconst float L = 10000.; // TODO Why 10k?\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_CASTS_SHADOW, pos, 0., dir, L);\n\n\t// Find closest intersection, and then check whether that was a skybox\n\twhile (rayQueryProceedEXT(rq)) {}\n\n\tif (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) {\n\t\tconst uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true);\n\t\tconst uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true);\n\t\tconst uint kusok_index = instance_kusochki_offset + geometry_index;\n\t\tconst Kusok kusok = getKusok(kusok_index);\n\n\t\tif (kusok.material.tex_base_color != TEX_BASE_SKYBOX)\n\t\t\treturn true;\n\t}\n\n\t// check for alpha-masked surfaces separately\n\tconst float hit_t = rayQueryGetIntersectionTEXT(rq, true);\n\treturn shadowTestAlphaMask(pos, dir, hit_t);\n\n#else\n#error RAY_TRACE or RAY_QUERY\n#endif\n}\n\n#endif //ifndef LIGHT_COMMON_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/light_polygon.glsl",
    "content": "#define MAX_POLYGON_VERTEX_COUNT 8\n#define MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING 3\n#include \"peters2021-sampling/polygon_clipping.glsl\"\n#include \"peters2021-sampling/polygon_sampling.glsl\"\n\n#include \"debug.glsl\"\n#include \"noise.glsl\"\n#include \"utils.glsl\"\n\n#define DO_ALL_IN_CLUSTER 1\n\n#ifndef RAY_BOUNCE\n#define PROJECTED\n//#define SOLID\n//#define SIMPLE_SOLID\n#else\n//#define PROJECTED\n//#define SOLID\n//#define SIMPLE_SOLID\n#endif\n\nstruct SampleContext {\n\tmat4x3 world_to_shading;\n};\n\nSampleContext buildSampleContext(vec3 position, vec3 normal, vec3 view_dir) {\n\tSampleContext ctx;\n\tconst float normal_dot_outgoing = dot(normal, -view_dir);\n\tconst vec3 x_axis = normalize(fma(vec3(-normal_dot_outgoing), normal, -view_dir));\n\tconst vec3 y_axis = cross(normal, x_axis);\n\tconst mat3 rotation = transpose(mat3(x_axis, y_axis, normal));\n\tctx.world_to_shading = mat4x3(rotation[0], rotation[1], rotation[2], -rotation * position);\n\treturn ctx;\n}\n\nvec4 getPolygonLightSampleSimple(vec3 P, vec3 view_dir, const PolygonLight poly) {\n\tconst uint vertices_offset = poly.vertices_count_offset & 0xffffu;\n\tuint vertices_count = poly.vertices_count_offset >> 16;\n\n\tvec3 v[3];\n\tvertices_count = 3; // FIXME\n\n\tfor (uint i = 0; i < vertices_count; ++i) {\n\t\tv[i] = lights.m.polygon_vertices[vertices_offset + i].xyz;\n\t}\n\n\tvec2 rnd = vec2(sqrt(rand01()), rand01());\n\trnd.y *= rnd.x;\n\trnd.x = 1.f - rnd.x;\n\n\tconst vec3 light_dir = baryMix(v[0], v[1], v[2], rnd) - P;\n\tconst vec3 light_dir_n = normalize(light_dir);\n\tconst float contrib = - poly.area * dot(light_dir_n, poly.plane.xyz ) / dot(light_dir, light_dir);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALID(contrib)) {\n\t\tdebugPrintfEXT(\"getPolygonLightSampleSimple: poly.area=%f light_dir=(%f,%f,%f) INVALID contrib=%f\",\n\t\t\tpoly.area,\n\t\t\tPRIVEC3(light_dir),\n\t\t\tcontrib);\n\t}\n#endif\n\n\treturn vec4(light_dir_n, contrib);\n}\n\nvec4 getPolygonLightSampleSimpleSolid(vec3 P, vec3 view_dir, const PolygonLight poly) {\n\tconst uint vertices_offset = poly.vertices_count_offset & 0xffffu;\n\tuint vertices_count = poly.vertices_count_offset >> 16;\n\n\tuint selected = 0;\n\tfloat total_contrib = 0.;\n\tfloat eps1 = rand01();\n\tvec3 v[3];\n\tv[0] = normalize(lights.m.polygon_vertices[vertices_offset + 0].xyz - P);\n\tv[1] = normalize(lights.m.polygon_vertices[vertices_offset + 1].xyz - P);\n\tconst float householder_sign = (v[0].x > 0.0f) ? -1.0f : 1.0f;\n\tconst vec2 householder_yz = v[0].yz * (1.0f / (abs(v[0].x) + 1.0f));\n\tfor (uint i = 2; i < vertices_count; ++i) {\n\t\tv[2] = normalize(lights.m.polygon_vertices[vertices_offset + i].xyz - P);\n\n\t\t// effectively mindlessly copypasted from polygon_sampling.glsl, Peters 2021\n\t\t// https://github.com/MomentsInGraphics/vulkan_renderer/blob/main/src/shaders/polygon_sampling.glsl\n\t\tconst float dot_0_1 = dot(v[0], v[1]);\n\t\tconst float dot_0_2 = dot(v[1], v[2]);\n\t\tconst float dot_1_2 = dot(v[0], v[2]);\n\t\tconst float dot_householder_0 = fma(-householder_sign, v[1].x, dot_0_1);\n\t\tconst float dot_householder_2 = fma(-householder_sign, v[2].x, dot_1_2);\n\t\tconst mat2 bottom_right_minor = mat2(\n\t\t\tfma(vec2(-dot_householder_0), householder_yz, v[1].yz),\n\t\t\tfma(vec2(-dot_householder_2), householder_yz, v[2].yz));\n\t\tconst float simplex_volume = abs(determinant(bottom_right_minor));\n\t\tconst float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2;\n\t\tconst float one_plus_dot_0_1 = 1.0f + dot_0_1;\n\t\tconst float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2);\n\t\tconst float contrib = 2.f * (atan(tangent) + (tangent < 0.f ? M_PI : 0.));\n\n\t\tif (contrib < 1e-6)\n\t\t\tcontinue;\n\n\t\tconst float tau = total_contrib / (total_contrib + contrib);\n\t\ttotal_contrib += contrib;\n\n\t\tif (eps1 < tau) {\n\t\t\teps1 /= tau;\n\t\t} else {\n\t\t\tselected = i;\n\t\t\teps1 = (eps1 - tau) / (1. - tau);\n\t\t}\n\n\t\t// selected = 2;\n\t\t// break;\n\t\tv[1] = v[2];\n\t}\n\n\tif (selected == 0)\n\t\treturn vec4(0.);\n\n\tvec2 rnd = vec2(sqrt(rand01()), rand01());\n\trnd.y *= rnd.x;\n\trnd.x = 1.f - rnd.x;\n\n\tconst vec3 light_dir = baryMix(\n\t\tlights.m.polygon_vertices[vertices_offset + 0].xyz,\n\t\tlights.m.polygon_vertices[vertices_offset + selected - 1].xyz,\n\t\tlights.m.polygon_vertices[vertices_offset + selected].xyz,\n\t\trnd) - P;\n\tconst vec3 light_dir_n = normalize(light_dir);\n\treturn vec4(light_dir_n, total_contrib);\n}\n\nvec4 getPolygonLightSampleProjected(vec3 view_dir, SampleContext ctx, const PolygonLight poly) {\n\tvec3 clipped[MAX_POLYGON_VERTEX_COUNT];\n\n\tconst uint vertices_offset = poly.vertices_count_offset & 0xffffu;\n\tuint vertices_count = poly.vertices_count_offset >> 16;\n\n\tfor (uint i = 0; i < vertices_count; ++i) {\n\t\tclipped[i] = ctx.world_to_shading * vec4(lights.m.polygon_vertices[vertices_offset + i].xyz, 1.);\n\t}\n\n\tvertices_count = clip_polygon(vertices_count, clipped);\n\tif (vertices_count == 0)\n\t\treturn vec4(0.f);\n\n\tconst projected_solid_angle_polygon_t sap = prepare_projected_solid_angle_polygon_sampling(vertices_count, clipped);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALID(sap.projected_solid_angle)) {\n\t\tdebugPrintfEXT(\"getPolygonLightSampleProjected: vertices_count=%d v0=(%f,%f,%f) v1=(%f,%f,%f) v2=(%f,%f,%f) INVALID sap.projected_solid_angle = %f\",\n\t\t\tvertices_count, PRIVEC3(clipped[0]), PRIVEC3(clipped[1]), PRIVEC3(clipped[2]),\n\t\t\tsap.projected_solid_angle);\n\t}\n#endif\n\n\tconst float contrib = sap.projected_solid_angle;\n\tif (contrib <= 0.f)\n\t\treturn vec4(0.f);\n\n\tvec2 rnd = vec2(rand01(), rand01());\n\tconst vec3 light_dir = (transpose(ctx.world_to_shading) * sample_projected_solid_angle_polygon(sap, rnd)).xyz;\n\n\treturn vec4(light_dir, contrib);\n}\n\nvec4 getPolygonLightSampleSolid(vec3 P, vec3 view_dir, SampleContext ctx, const PolygonLight poly) {\n\tvec3 clipped[MAX_POLYGON_VERTEX_COUNT];\n\n\tconst uint vertices_offset = poly.vertices_count_offset & 0xffffu;\n\tuint vertices_count = poly.vertices_count_offset >> 16;\n\n\tfor (uint i = 0; i < vertices_count; ++i) {\n\t\tclipped[i] = lights.m.polygon_vertices[vertices_offset + i].xyz;\n\t}\n\n#define DONT_CLIP\n#ifndef DONT_CLIP\n\tvertices_count = clip_polygon(vertices_count, clipped);\n\tif (vertices_count == 0)\n\t\treturn vec4(0.f);\n#endif\n\n\tconst solid_angle_polygon_t sap = prepare_solid_angle_polygon_sampling(vertices_count, clipped, P);\n\tconst float contrib = sap.solid_angle;\n\tif (contrib <= 0.f)\n\t\treturn vec4(0.f);\n\n\tvec2 rnd = vec2(rand01(), rand01());\n\tconst vec3 light_dir = sample_solid_angle_polygon(sap, rnd).xyz;\n\n\treturn vec4(light_dir, contrib);\n}\n\nvoid sampleSinglePolygonLight(in vec3 P, in vec3 N, in vec3 view_dir, in SampleContext ctx, in MaterialProperties material, in PolygonLight poly, inout vec3 diffuse, inout vec3 specular) {\n\t// TODO cull by poly plane\n\n#ifdef PROJECTED\n\tconst vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);\n#else\n\tconst vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);\n#endif\n\tif (light_sample_dir.w <= 0.)\n\t\treturn;\n\n\tconst float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz);\n\n\tif (shadowed(P, light_sample_dir.xyz, dist))\n\t\treturn;\n\n\tvec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);\n\tevalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);\n\tconst float estimate = light_sample_dir.w;\n\tconst vec3 emissive = poly.emissive * estimate;\n\tdiffuse += emissive * poly_diffuse;\n\tspecular += emissive * poly_specular;\n}\n\n#if 0\n// Sample random one\nvoid sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) {\n\tconst uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);\n\n\tif (num_polygons == 0)\n\t\treturn;\n\n\tconst uint selected = uint(light_grid.clusters_[cluster_index].polygons[rand_range(num_polygons)]);\n\n\tconst PolygonLight poly = lights.m.polygons[selected];\n\tconst SampleContext ctx = buildSampleContext(P, N, view_dir);\n\tsampleSinglePolygonLight(P, N, view_dir, ctx, material, poly, diffuse, specular);\n\n\tconst float sampling_factor = float(num_polygons);\n\tdiffuse *= sampling_factor;\n\tspecular *= sampling_factor;\n}\n\n#elif 1\nvoid sampleEmissiveSurfaces(vec3 P, vec3 N, vec3 view_dir, MaterialProperties material, uint cluster_index, inout vec3 diffuse, inout vec3 specular) {\n#if DO_ALL_IN_CLUSTER\n\tconst SampleContext ctx = buildSampleContext(P, N, view_dir);\n\n#define USE_CLUSTERS\n#ifdef USE_CLUSTERS\n\tconst uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);\n\tfor (uint i = 0; i < num_polygons; ++i) {\n\t\tconst uint index = uint(light_grid.clusters_[cluster_index].polygons[i]);\n\n\t\t//if (index != 5) // < 6 || index >= 8)\n\t\t//\tcontinue;\n#else\n\tfor (uint index = 0; index < lights.m.num_polygons; ++index) {\n#endif\n\n\t\tconst PolygonLight poly = lights.m.polygons[index];\n\n\t\tconst float plane_dist = dot(poly.plane, vec4(P, 1.f));\n\n\t\tif (plane_dist < 0.)\n\t\t\tcontinue;\n\n#ifdef PROJECTED\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);\n#elif defined(SOLID)\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);\n#elif defined(SIMPLE_SOLID)\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleSimpleSolid(P, view_dir, poly);\n#else\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleSimple(P, view_dir, poly);\n#endif\n\n\t\tif (light_sample_dir.w <= 0.)\n\t\t\tcontinue;\n\n\t\tconst float dist = - plane_dist / dot(light_sample_dir.xyz, poly.plane.xyz);\n\t\tconst vec3 emissive = poly.emissive;\n\n\t\tif (!shadowed(P, light_sample_dir.xyz, dist)) {\n\t\t\t//const float estimate = total_contrib;\n\t\t\tconst float estimate = light_sample_dir.w;\n\t\t\tvec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);\n\t\t\tevalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);\n\t\t\tdiffuse += emissive * estimate * poly_diffuse;\n\t\t\tspecular += emissive * estimate * poly_specular;\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\tif (IS_INVALIDV(specular) || any(lessThan(specular,vec3(0.)))) {\n\t\t\t\tdebugPrintfEXT(\"%d INVALID specular=(%f,%f,%f) light=%d emissive=(%f,%f,%f) estimate=%f poly_specular=(%f,%f,%f)\",\n\t\t\t\t\t__LINE__, PRIVEC3(specular), index, PRIVEC3(emissive), estimate, PRIVEC3(poly_specular));\n\t\t\t\tspecular = vec3(0.);\n\t\t\t}\n\t\t\tif (IS_INVALIDV(diffuse) || any(lessThan(diffuse,vec3(0.)))) {\n\t\t\t\tdebugPrintfEXT(\"%d INVALID diffuse=(%f,%f,%f) light=%d emissive=(%f,%f,%f) estimate=%f poly_diffuse=(%f,%f,%f)\",\n\t\t\t\t\t__LINE__, PRIVEC3(diffuse), index, PRIVEC3(emissive), estimate, PRIVEC3(poly_diffuse));\n\t\t\t\tdiffuse = vec3(0.);\n\t\t\t}\n#endif\n\t\t}\n\t}\n#else // DO_ALL_IN_CLUSTERS\n\n#ifdef USE_CLUSTERS\n\t// TODO move this to pickPolygonLight function\n\tconst uint num_polygons = uint(light_grid.clusters_[cluster_index].num_polygons);\n#else\n\tconst uint num_polygons = lights.m.num_polygons;\n#endif\n\n\tuint selected = 0;\n\tfloat total_contrib = 0.;\n\tfloat eps1 = rand01();\n\tfor (uint i = 0; i < num_polygons; ++i) {\n#ifdef USE_CLUSTERS\n\t\tconst uint index = uint(light_grid.clusters_[cluster_index].polygons[i]);\n#else\n\t\tconst uint index = i;\n#endif\n\n\t\tconst PolygonLight poly = lights.m.polygons[index];\n\n\t\tconst vec3 dir = poly.center - P;\n\t\tconst vec3 light_dir = normalize(dir);\n\t\tfloat contrib_estimate = poly.area * dot(-light_dir, poly.plane.xyz) / (1e-3 + dot(dir, dir));\n\n\t\tif (contrib_estimate < 1e-6)\n\t\t \tcontinue;\n\n\t\tcontrib_estimate = 1.f;\n\t\tconst float tau = total_contrib / (total_contrib + contrib_estimate);\n\t\ttotal_contrib += contrib_estimate;\n\n\t\tif (eps1 < tau) {\n\t\t\teps1 /= tau;\n\t\t} else {\n\t\t\tselected = index + 1;\n\t\t\teps1 = (eps1 - tau) / (1. - tau);\n\t\t}\n\t}\n\n\tif (selected == 0) {\n\t\t//diffuse = vec3(1., 0., 0.);\n\t\treturn;\n\t}\n\n#if 0\n\tconst PolygonLight poly = lights.m.polygons[selected - 1];\n\tconst vec3 emissive = poly.emissive;\n\tvec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);\n\tevalSplitBRDF(N, normalize(poly.center-P), view_dir, material, poly_diffuse, poly_specular);\n\tdiffuse += emissive * total_contrib;\n\tspecular += emissive * total_contrib;\n#else\n\tconst SampleContext ctx = buildSampleContext(P, N, view_dir);\n\tconst PolygonLight poly = lights.m.polygons[selected - 1];\n#ifdef PROJECTED\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleProjected(view_dir, ctx, poly);\n#else\n\t\tconst vec4 light_sample_dir = getPolygonLightSampleSolid(P, view_dir, ctx, poly);\n#endif\n\tif (light_sample_dir.w <= 0.)\n\t\treturn;\n\n\tconst float dist = - dot(vec4(P, 1.f), poly.plane) / dot(light_sample_dir.xyz, poly.plane.xyz);\n\tconst vec3 emissive = poly.emissive;\n\n\t//if (true) {//!shadowed(P, light_sample_dir.xyz, dist)) {\n\tif (!shadowed(P, light_sample_dir.xyz, dist)) {\n\t\t//const float estimate = total_contrib;\n\t\tconst float estimate = light_sample_dir.w;\n\t\tvec3 poly_diffuse = vec3(0.), poly_specular = vec3(0.);\n\t\tevalSplitBRDF(N, light_sample_dir.xyz, view_dir, material, poly_diffuse, poly_specular);\n\t\tdiffuse += emissive * estimate;\n\t\tspecular += emissive * estimate;\n\t}\n#endif\n#endif\n}\n#endif\n"
  },
  {
    "path": "ref/vk/shaders/noise.glsl",
    "content": "#ifndef NOISE_GLSL_INCLUDED\n#define NOISE_GLSL_INCLUDED\n// Copypasted from Mark Jarzynski and Marc Olano, Hash Functions for GPU Rendering, Journal of Computer Graphics Techniques (JCGT), vol. 9, no. 3, 21-38, 2020\n// http://www.jcgt.org/published/0009/03/02/\n// https://www.shadertoy.com/view/XlGcRh\n\n// xxhash (https://github.com/Cyan4973/xxHash)\n//   From https://www.shadertoy.com/view/Xt3cDn\nuint xxhash32(uint p) {\n\tconst uint PRIME32_2 = 2246822519U, PRIME32_3 = 3266489917U;\n\tconst uint PRIME32_4 = 668265263U, PRIME32_5 = 374761393U;\n\tuint h32 = p + PRIME32_5;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 = PRIME32_2*(h32^(h32 >> 15));\n\th32 = PRIME32_3*(h32^(h32 >> 13));\n\treturn h32^(h32 >> 16);\n}\n\nuint xxhash32(uvec2 p) {\n\tconst uint PRIME32_2 = 2246822519U, PRIME32_3 = 3266489917U;\n\tconst uint PRIME32_4 = 668265263U, PRIME32_5 = 374761393U;\n\tuint h32 = p.y + PRIME32_5 + p.x*PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 = PRIME32_2*(h32^(h32 >> 15));\n\th32 = PRIME32_3*(h32^(h32 >> 13));\n\treturn h32^(h32 >> 16);\n}\n\nuint xxhash32(uvec3 p) {\n\tconst uint PRIME32_2 = 2246822519U, PRIME32_3 = 3266489917U;\n\tconst uint PRIME32_4 = 668265263U, PRIME32_5 = 374761393U;\n\tuint h32 =  p.z + PRIME32_5 + p.x*PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 += p.y * PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 = PRIME32_2*(h32^(h32 >> 15));\n\th32 = PRIME32_3*(h32^(h32 >> 13));\n\treturn h32^(h32 >> 16);\n}\n\nuint xxhash32(uvec4 p) {\n\tconst uint PRIME32_2 = 2246822519U, PRIME32_3 = 3266489917U;\n\tconst uint PRIME32_4 = 668265263U, PRIME32_5 = 374761393U;\n\tuint h32 =  p.w + PRIME32_5 + p.x*PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 += p.y * PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 += p.z * PRIME32_3;\n\th32 = PRIME32_4*((h32 << 17) | (h32 >> (32 - 17)));\n\th32 = PRIME32_2*(h32^(h32 >> 15));\n\th32 = PRIME32_3*(h32^(h32 >> 13));\n\treturn h32^(h32 >> 16);\n}\n\n// https://www.pcg-random.org/\nuint pcg(uint v) {\n\tuint state = v * 747796405u + 2891336453u;\n\tuint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n\treturn (word >> 22u) ^ word;\n}\n\nuvec2 pcg2d(uvec2 v) {\n\tv = v * 1664525u + 1013904223u;\n\n\tv.x += v.y * 1664525u;\n\tv.y += v.x * 1664525u;\n\n\tv = v ^ (v>>16u);\n\n\tv.x += v.y * 1664525u;\n\tv.y += v.x * 1664525u;\n\n\tv = v ^ (v>>16u);\n\n\treturn v;\n}\n\n// http://www.jcgt.org/published/0009/03/02/\nuvec3 pcg3d(uvec3 v) {\n\tv = v * 1664525u + 1013904223u;\n\n\tv.x += v.y*v.z;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\n\tv ^= v >> 16u;\n\n\tv.x += v.y*v.z;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\n\treturn v;\n}\n\n// http://www.jcgt.org/published/0009/03/02/\nuvec3 pcg3d16(uvec3 v) {\n\tv = v * 12829u + 47989u;\n\n\tv.x += v.y*v.z;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\n\tv.x += v.y*v.z;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\n\tv >>= 16u;\n\n\treturn v;\n}\n\n// http://www.jcgt.org/published/0009/03/02/\nuvec4 pcg4d(uvec4 v) {\n\tv = v * 1664525u + 1013904223u;\n\n\tv.x += v.y*v.w;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\tv.w += v.y*v.z;\n\n\tv ^= v >> 16u;\n\n\tv.x += v.y*v.w;\n\tv.y += v.z*v.x;\n\tv.z += v.x*v.y;\n\tv.w += v.y*v.z;\n\n\treturn v;\n}\n\nuint rand01_state = 0;\nuint rand() {\n\treturn rand01_state = xxhash32(rand01_state);\n}\nuint rand_range(uint rmax) {\n\treturn rand() % rmax;\n}\n\nfloat uintToFloat01(uint x) {\n\treturn uintBitsToFloat(0x3f800000 | (x & 0x007fffff)) - 1.;\n}\n\nfloat rand01() {\n\treturn uintToFloat01(rand());\n}\n\nvec3 rand3_f01(uvec3 seed) {\n\tuvec3 v = pcg3d(seed);\n\treturn vec3(uintToFloat01(v.x), uintToFloat01(v.y), uintToFloat01(v.z));\n}\n#endif // NOISE_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/peters2021-sampling/math_constants.glsl",
    "content": "//  Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology\n//\n//  This program is free software: you can redistribute it and/or modify\n//  it under the terms of the GNU General Public License as published by\n//  the Free Software Foundation, either version 3 of the License, or\n//  (at your option) any later version.\n//\n//  This program is distributed in the hope that it will be useful,\n//  but WITHOUT ANY WARRANTY; without even the implied warranty of\n//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n//  GNU General Public License for more details.\n//\n//  You should have received a copy of the GNU General Public License\n//  along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n\n#ifndef M_PI\n\t#define M_PI 3.1415926535897932384626433832795f\n#endif\n#ifndef M_INV_PI\n\t#define M_INV_PI 0.31830988618379067153776752674503f\n#endif\n#ifndef M_HALF_PI\n\t#define M_HALF_PI 1.5707963267948966192313216916398f\n#endif\n#ifndef M_INFINITY\n\t#define M_INFINITY (1.0f / 0.0f)\n#endif\n"
  },
  {
    "path": "ref/vk/shaders/peters2021-sampling/polygon_clipping.glsl",
    "content": "//  Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology\n//\n//  This program is free software: you can redistribute it and/or modify\n//  it under the terms of the GNU General Public License as published by\n//  the Free Software Foundation, either version 3 of the License, or\n//  (at your option) any later version.\n//\n//  This program is distributed in the hope that it will be useful,\n//  but WITHOUT ANY WARRANTY; without even the implied warranty of\n//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n//  GNU General Public License for more details.\n//\n//  You should have received a copy of the GNU General Public License\n//  along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n\n/*! Returns the intersection of the line connecting the given two points with \n\tthe plane z == 0.0f.*/\nvec3 iz0(vec3 lhs, vec3 rhs) {\n\tfloat lerp_factor = lhs.z / (lhs.z - rhs.z);\n\t// Equivalent to the following but I have trust issues regarding the\n\t// stability of mix()\n\t// return vec3(mix(lhs.xy, rhs.xy, lerp_factor), 0.0f);\n\treturn vec3(fma(vec2(lerp_factor), rhs.xy, fma(-vec2(lerp_factor), lhs.xy, lhs.xy)), 0.0f);\n}\n\n\n/*! This function clips the given convex polygon with vertices in v to the\n\tupper hemisphere (i.e. the half-space with non-negative z-coordinate).\n\tThe vertex count after clipping is returned. It is either zero or between\n\tthree and vertex_count + 1. If it is less than MAX_POLYGON_VERTEX_COUNT,\n\tthe first entry of v is repeated at the returned vertex count for the\n\toutput. vertex_count must be at least\n\tMIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING.*/\nuint clip_polygon(uint vertex_count, inout vec3 v[MAX_POLYGON_VERTEX_COUNT]) {\n\t// The vertex count after clipping\n\tuint vc;\n\t// Encode the whole configuration into a single integer\n\tuint bit_mask = vertex_count;\n\t[[unroll]]\n\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i)\n\t\tbit_mask |= (v[i].z > 0.0f && (i < MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING || i < vertex_count)) ? (1 << (i + 3)) : 0;\n\t// This code has been generated automatically to handle all possible cases\n\t// with a single conditional jump and no unnecessary instructions\n\tswitch (bit_mask) {\n\t// AUTOGENERATED PART BEGIN\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT >= 4\n\tcase    3:   vc = 0;   break;\n\tcase   59:   vc = 3;   v[3] = v[0];   break;\n\tcase   11:   vc = 3;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[2], v[0]);   v[3] = v[0];   break;\n\tcase   19:   vc = 3;   v[0] = iz0(v[0], v[1]);   v[2] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   35:   vc = 3;   v[0] = iz0(v[2], v[0]);   v[1] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT == 4\n\tcase   27:   vc = 4;   v[3] = iz0(v[2], v[0]);   v[2] = iz0(v[1], v[2]);   break;\n\tcase   51:   vc = 4;   v[3] = iz0(v[2], v[0]);   v[0] = iz0(v[0], v[1]);   break;\n\tcase   43:   vc = 4;   v[3] = v[2];   v[2] = iz0(v[1], v[2]);   v[1] = iz0(v[0], v[1]);   break;\n#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 3 && MAX_POLYGON_VERTEX_COUNT > 4\n\tcase   27:   vc = 4;   v[3] = iz0(v[2], v[0]);   v[2] = iz0(v[1], v[2]);   v[4] = v[0];   break;\n\tcase   51:   vc = 4;   v[3] = iz0(v[2], v[0]);   v[0] = iz0(v[0], v[1]);   v[4] = v[0];   break;\n\tcase   43:   vc = 4;   v[3] = v[2];   v[2] = iz0(v[1], v[2]);   v[1] = iz0(v[0], v[1]);   v[4] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT >= 5\n\tcase    4:   vc = 0;   break;\n\tcase  124:   vc = 4;   v[4] = v[0];   break;\n\tcase   12:   vc = 3;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[3], v[0]);   v[3] = v[0];   break;\n\tcase   20:   vc = 3;   v[0] = iz0(v[0], v[1]);   v[2] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   36:   vc = 3;   v[0] = iz0(v[2], v[3]);   v[1] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   68:   vc = 3;   v[1] = iz0(v[3], v[0]);   v[0] = v[3];   v[2] = iz0(v[2], v[3]);   break;\n\tcase   28:   vc = 4;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[3], v[0]);   v[4] = v[0];   break;\n\tcase   52:   vc = 4;   v[0] = iz0(v[0], v[1]);   v[3] = iz0(v[2], v[3]);   v[4] = v[0];   break;\n\tcase  100:   vc = 4;   v[0] = iz0(v[3], v[0]);   v[1] = iz0(v[1], v[2]);   v[4] = v[0];   break;\n\tcase   76:   vc = 4;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[2], v[3]);   v[4] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT == 5\n\tcase   60:   vc = 5;   v[4] = iz0(v[3], v[0]);   v[3] = iz0(v[2], v[3]);   break;\n\tcase  116:   vc = 5;   v[4] = iz0(v[3], v[0]);   v[0] = iz0(v[0], v[1]);   break;\n\tcase  108:   vc = 5;   v[4] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   break;\n\tcase   92:   vc = 5;   v[4] = v[3];   v[3] = iz0(v[2], v[3]);   v[2] = iz0(v[1], v[2]);   break;\n#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 4 && MAX_POLYGON_VERTEX_COUNT > 5\n\tcase   60:   vc = 5;   v[4] = iz0(v[3], v[0]);   v[3] = iz0(v[2], v[3]);   v[5] = v[0];   break;\n\tcase  116:   vc = 5;   v[4] = iz0(v[3], v[0]);   v[0] = iz0(v[0], v[1]);   v[5] = v[0];   break;\n\tcase  108:   vc = 5;   v[4] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   v[5] = v[0];   break;\n\tcase   92:   vc = 5;   v[4] = v[3];   v[3] = iz0(v[2], v[3]);   v[2] = iz0(v[1], v[2]);   v[5] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT >= 6\n\tcase    5:   vc = 0;   break;\n\tcase  253:   vc = 5;   v[5] = v[0];   break;\n\tcase   13:   vc = 3;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[4], v[0]);   v[3] = v[0];   break;\n\tcase   21:   vc = 3;   v[0] = iz0(v[0], v[1]);   v[2] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   37:   vc = 3;   v[0] = iz0(v[2], v[3]);   v[1] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   69:   vc = 3;   v[0] = v[3];   v[1] = iz0(v[3], v[4]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  133:   vc = 3;   v[1] = v[4];   v[2] = iz0(v[4], v[0]);   v[0] = iz0(v[3], v[4]);   v[3] = v[0];   break;\n\tcase   29:   vc = 4;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[4], v[0]);   v[4] = v[0];   break;\n\tcase   53:   vc = 4;   v[0] = iz0(v[0], v[1]);   v[3] = iz0(v[2], v[3]);   v[4] = v[0];   break;\n\tcase  101:   vc = 4;   v[0] = iz0(v[3], v[4]);   v[1] = iz0(v[1], v[2]);   v[4] = v[0];   break;\n\tcase  197:   vc = 4;   v[1] = iz0(v[4], v[0]);   v[0] = v[4];   v[2] = iz0(v[2], v[3]);   break;\n\tcase  141:   vc = 4;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[3], v[4]);   v[3] = v[4];   v[4] = v[0];   break;\n\tcase   61:   vc = 5;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[4], v[0]);   v[5] = v[0];   break;\n\tcase  117:   vc = 5;   v[0] = iz0(v[0], v[1]);   v[4] = iz0(v[3], v[4]);   v[5] = v[0];   break;\n\tcase  229:   vc = 5;   v[0] = iz0(v[4], v[0]);   v[1] = iz0(v[1], v[2]);   v[5] = v[0];   break;\n\tcase  205:   vc = 5;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[2], v[3]);   v[5] = v[0];   break;\n\tcase  157:   vc = 5;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[3], v[4]);   v[5] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT == 6\n\tcase  125:   vc = 6;   v[5] = iz0(v[4], v[0]);   v[4] = iz0(v[3], v[4]);   break;\n\tcase  245:   vc = 6;   v[5] = iz0(v[4], v[0]);   v[0] = iz0(v[0], v[1]);   break;\n\tcase  237:   vc = 6;   v[5] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   break;\n\tcase  221:   vc = 6;   v[5] = v[4];   v[4] = v[3];   v[3] = iz0(v[2], v[3]);   v[2] = iz0(v[1], v[2]);   break;\n\tcase  189:   vc = 6;   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   break;\n#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 5 && MAX_POLYGON_VERTEX_COUNT > 6\n\tcase  125:   vc = 6;   v[5] = iz0(v[4], v[0]);   v[4] = iz0(v[3], v[4]);   v[6] = v[0];   break;\n\tcase  245:   vc = 6;   v[5] = iz0(v[4], v[0]);   v[0] = iz0(v[0], v[1]);   v[6] = v[0];   break;\n\tcase  237:   vc = 6;   v[5] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   v[6] = v[0];   break;\n\tcase  221:   vc = 6;   v[5] = v[4];   v[4] = v[3];   v[3] = iz0(v[2], v[3]);   v[2] = iz0(v[1], v[2]);   v[6] = v[0];   break;\n\tcase  189:   vc = 6;   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   v[6] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT >= 7\n\tcase    6:   vc = 0;   break;\n\tcase  510:   vc = 6;   v[6] = v[0];   break;\n\tcase   14:   vc = 3;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[5], v[0]);   v[3] = v[0];   break;\n\tcase   22:   vc = 3;   v[0] = iz0(v[0], v[1]);   v[2] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   38:   vc = 3;   v[0] = iz0(v[2], v[3]);   v[1] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   70:   vc = 3;   v[0] = v[3];   v[1] = iz0(v[3], v[4]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  134:   vc = 3;   v[0] = iz0(v[3], v[4]);   v[1] = v[4];   v[2] = iz0(v[4], v[5]);   v[3] = v[0];   break;\n\tcase  262:   vc = 3;   v[1] = v[5];   v[2] = iz0(v[5], v[0]);   v[0] = iz0(v[4], v[5]);   v[3] = v[0];   break;\n\tcase   30:   vc = 4;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[5], v[0]);   v[4] = v[0];   break;\n\tcase   54:   vc = 4;   v[0] = iz0(v[0], v[1]);   v[3] = iz0(v[2], v[3]);   v[4] = v[0];   break;\n\tcase  102:   vc = 4;   v[0] = iz0(v[3], v[4]);   v[1] = iz0(v[1], v[2]);   v[4] = v[0];   break;\n\tcase  198:   vc = 4;   v[0] = v[4];   v[1] = iz0(v[4], v[5]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  390:   vc = 4;   v[1] = v[5];   v[2] = iz0(v[5], v[0]);   v[0] = v[4];   v[3] = iz0(v[3], v[4]);   break;\n\tcase  270:   vc = 4;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[4], v[5]);   v[3] = v[5];   v[4] = v[0];   break;\n\tcase   62:   vc = 5;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[5], v[0]);   v[5] = v[0];   break;\n\tcase  118:   vc = 5;   v[0] = iz0(v[0], v[1]);   v[4] = iz0(v[3], v[4]);   v[5] = v[0];   break;\n\tcase  230:   vc = 5;   v[0] = iz0(v[4], v[5]);   v[1] = iz0(v[1], v[2]);   v[5] = v[0];   break;\n\tcase  454:   vc = 5;   v[1] = iz0(v[5], v[0]);   v[0] = v[5];   v[2] = iz0(v[2], v[3]);   break;\n\tcase  398:   vc = 5;   v[2] = iz0(v[0], v[1]);   v[1] = v[0];   v[0] = v[5];   v[3] = iz0(v[3], v[4]);   break;\n\tcase  286:   vc = 5;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[4], v[5]);   v[4] = v[5];   v[5] = v[0];   break;\n\tcase  126:   vc = 6;   v[4] = iz0(v[3], v[4]);   v[5] = iz0(v[5], v[0]);   v[6] = v[0];   break;\n\tcase  246:   vc = 6;   v[0] = iz0(v[0], v[1]);   v[5] = iz0(v[4], v[5]);   v[6] = v[0];   break;\n\tcase  486:   vc = 6;   v[0] = iz0(v[5], v[0]);   v[1] = iz0(v[1], v[2]);   v[6] = v[0];   break;\n\tcase  462:   vc = 6;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[2], v[3]);   v[6] = v[0];   break;\n\tcase  414:   vc = 6;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[3], v[4]);   v[6] = v[0];   break;\n\tcase  318:   vc = 6;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[4], v[5]);   v[6] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT == 7\n\tcase  254:   vc = 7;   v[6] = iz0(v[5], v[0]);   v[5] = iz0(v[4], v[5]);   break;\n\tcase  502:   vc = 7;   v[6] = iz0(v[5], v[0]);   v[0] = iz0(v[0], v[1]);   break;\n\tcase  494:   vc = 7;   v[6] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   break;\n\tcase  478:   vc = 7;   v[6] = v[0];   v[0] = v[1];   v[1] = iz0(v[1], v[2]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  446:   vc = 7;   v[6] = v[5];   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   break;\n\tcase  382:   vc = 7;   v[6] = v[5];   v[5] = iz0(v[4], v[5]);   v[4] = iz0(v[3], v[4]);   break;\n#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 6 && MAX_POLYGON_VERTEX_COUNT > 7\n\tcase  254:   vc = 7;   v[6] = iz0(v[5], v[0]);   v[5] = iz0(v[4], v[5]);   v[7] = v[0];   break;\n\tcase  502:   vc = 7;   v[6] = iz0(v[5], v[0]);   v[0] = iz0(v[0], v[1]);   v[7] = v[0];   break;\n\tcase  494:   vc = 7;   v[6] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   v[7] = v[0];   break;\n\tcase  478:   vc = 7;   v[6] = v[0];   v[0] = v[1];   v[1] = iz0(v[1], v[2]);   v[2] = iz0(v[2], v[3]);   v[7] = v[0];   break;\n\tcase  446:   vc = 7;   v[6] = v[5];   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   v[7] = v[0];   break;\n\tcase  382:   vc = 7;   v[6] = v[5];   v[5] = iz0(v[4], v[5]);   v[4] = iz0(v[3], v[4]);   v[7] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT >= 8\n\tcase    7:   vc = 0;   break;\n\tcase 1023:   vc = 7;   v[7] = v[0];   break;\n\tcase   15:   vc = 3;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[6], v[0]);   v[3] = v[0];   break;\n\tcase   23:   vc = 3;   v[0] = iz0(v[0], v[1]);   v[2] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   39:   vc = 3;   v[0] = iz0(v[2], v[3]);   v[1] = iz0(v[1], v[2]);   v[3] = v[0];   break;\n\tcase   71:   vc = 3;   v[0] = v[3];   v[1] = iz0(v[3], v[4]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  135:   vc = 3;   v[0] = iz0(v[3], v[4]);   v[1] = v[4];   v[2] = iz0(v[4], v[5]);   v[3] = v[0];   break;\n\tcase  263:   vc = 3;   v[0] = iz0(v[4], v[5]);   v[1] = v[5];   v[2] = iz0(v[5], v[6]);   v[3] = v[0];   break;\n\tcase  519:   vc = 3;   v[1] = v[6];   v[2] = iz0(v[6], v[0]);   v[0] = iz0(v[5], v[6]);   v[3] = v[0];   break;\n\tcase   31:   vc = 4;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[6], v[0]);   v[4] = v[0];   break;\n\tcase   55:   vc = 4;   v[0] = iz0(v[0], v[1]);   v[3] = iz0(v[2], v[3]);   v[4] = v[0];   break;\n\tcase  103:   vc = 4;   v[0] = iz0(v[3], v[4]);   v[1] = iz0(v[1], v[2]);   v[4] = v[0];   break;\n\tcase  199:   vc = 4;   v[0] = v[4];   v[1] = iz0(v[4], v[5]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  391:   vc = 4;   v[0] = v[4];   v[1] = v[5];   v[2] = iz0(v[5], v[6]);   v[3] = iz0(v[3], v[4]);   break;\n\tcase  775:   vc = 4;   v[1] = v[5];   v[2] = v[6];   v[3] = iz0(v[6], v[0]);   v[0] = iz0(v[4], v[5]);   v[4] = v[0];   break;\n\tcase  527:   vc = 4;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[5], v[6]);   v[3] = v[6];   v[4] = v[0];   break;\n\tcase   63:   vc = 5;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[6], v[0]);   v[5] = v[0];   break;\n\tcase  119:   vc = 5;   v[0] = iz0(v[0], v[1]);   v[4] = iz0(v[3], v[4]);   v[5] = v[0];   break;\n\tcase  231:   vc = 5;   v[0] = iz0(v[4], v[5]);   v[1] = iz0(v[1], v[2]);   v[5] = v[0];   break;\n\tcase  455:   vc = 5;   v[0] = v[5];   v[1] = iz0(v[5], v[6]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  903:   vc = 5;   v[1] = v[6];   v[2] = iz0(v[6], v[0]);   v[0] = v[5];   v[3] = iz0(v[3], v[4]);   break;\n\tcase  783:   vc = 5;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[4], v[5]);   v[3] = v[5];   v[4] = v[6];   v[5] = v[0];   break;\n\tcase  543:   vc = 5;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[5], v[6]);   v[4] = v[6];   v[5] = v[0];   break;\n\tcase  127:   vc = 6;   v[4] = iz0(v[3], v[4]);   v[5] = iz0(v[6], v[0]);   v[6] = v[0];   break;\n\tcase  247:   vc = 6;   v[0] = iz0(v[0], v[1]);   v[5] = iz0(v[4], v[5]);   v[6] = v[0];   break;\n\tcase  487:   vc = 6;   v[0] = iz0(v[5], v[6]);   v[1] = iz0(v[1], v[2]);   v[6] = v[0];   break;\n\tcase  967:   vc = 6;   v[1] = iz0(v[6], v[0]);   v[0] = v[6];   v[2] = iz0(v[2], v[3]);   break;\n\tcase  911:   vc = 6;   v[2] = iz0(v[0], v[1]);   v[1] = v[0];   v[0] = v[6];   v[3] = iz0(v[3], v[4]);   break;\n\tcase  799:   vc = 6;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[4], v[5]);   v[4] = v[5];   v[5] = v[6];   v[6] = v[0];   break;\n\tcase  575:   vc = 6;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[5], v[6]);   v[5] = v[6];   v[6] = v[0];   break;\n\tcase  255:   vc = 7;   v[5] = iz0(v[4], v[5]);   v[6] = iz0(v[6], v[0]);   v[7] = v[0];   break;\n\tcase  503:   vc = 7;   v[0] = iz0(v[0], v[1]);   v[6] = iz0(v[5], v[6]);   v[7] = v[0];   break;\n\tcase  999:   vc = 7;   v[0] = iz0(v[6], v[0]);   v[1] = iz0(v[1], v[2]);   v[7] = v[0];   break;\n\tcase  975:   vc = 7;   v[1] = iz0(v[0], v[1]);   v[2] = iz0(v[2], v[3]);   v[7] = v[0];   break;\n\tcase  927:   vc = 7;   v[2] = iz0(v[1], v[2]);   v[3] = iz0(v[3], v[4]);   v[7] = v[0];   break;\n\tcase  831:   vc = 7;   v[3] = iz0(v[2], v[3]);   v[4] = iz0(v[4], v[5]);   v[7] = v[0];   break;\n\tcase  639:   vc = 7;   v[4] = iz0(v[3], v[4]);   v[5] = iz0(v[5], v[6]);   v[7] = v[0];   break;\n#endif\n#if MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT == 8\n\tcase  511:   vc = 8;   v[7] = iz0(v[6], v[0]);   v[6] = iz0(v[5], v[6]);   break;\n\tcase 1015:   vc = 8;   v[7] = iz0(v[6], v[0]);   v[0] = iz0(v[0], v[1]);   break;\n\tcase 1007:   vc = 8;   v[7] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   break;\n\tcase  991:   vc = 8;   v[7] = v[0];   v[0] = v[1];   v[1] = iz0(v[1], v[2]);   v[2] = iz0(v[2], v[3]);   break;\n\tcase  959:   vc = 8;   v[7] = v[6];   v[6] = v[5];   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   break;\n\tcase  895:   vc = 8;   v[7] = v[6];   v[6] = v[5];   v[5] = iz0(v[4], v[5]);   v[4] = iz0(v[3], v[4]);   break;\n\tcase  767:   vc = 8;   v[7] = v[6];   v[6] = iz0(v[5], v[6]);   v[5] = iz0(v[4], v[5]);   break;\n#elif MIN_POLYGON_VERTEX_COUNT_BEFORE_CLIPPING <= 7 && MAX_POLYGON_VERTEX_COUNT > 8\n\tcase  511:   vc = 8;   v[7] = iz0(v[6], v[0]);   v[6] = iz0(v[5], v[6]);   v[8] = v[0];   break;\n\tcase 1015:   vc = 8;   v[7] = iz0(v[6], v[0]);   v[0] = iz0(v[0], v[1]);   v[8] = v[0];   break;\n\tcase 1007:   vc = 8;   v[7] = v[0];   v[0] = iz0(v[0], v[1]);   v[1] = iz0(v[1], v[2]);   v[8] = v[0];   break;\n\tcase  991:   vc = 8;   v[7] = v[0];   v[0] = v[1];   v[1] = iz0(v[1], v[2]);   v[2] = iz0(v[2], v[3]);   v[8] = v[0];   break;\n\tcase  959:   vc = 8;   v[7] = v[6];   v[6] = v[5];   v[5] = v[4];   v[4] = iz0(v[3], v[4]);   v[3] = iz0(v[2], v[3]);   v[8] = v[0];   break;\n\tcase  895:   vc = 8;   v[7] = v[6];   v[6] = v[5];   v[5] = iz0(v[4], v[5]);   v[4] = iz0(v[3], v[4]);   v[8] = v[0];   break;\n\tcase  767:   vc = 8;   v[7] = v[6];   v[6] = iz0(v[5], v[6]);   v[5] = iz0(v[4], v[5]);   v[8] = v[0];   break;\n#endif\n\t// AUTOGENERATED PART END\n\tdefault:\n\t\t// This should never happen. Just pretend the polygon is below the\n\t\t// horizon.\n\t\tvc = 0;\n\t\tbreak;\n\t};\n\treturn vc;\n}\n"
  },
  {
    "path": "ref/vk/shaders/peters2021-sampling/polygon_sampling.glsl",
    "content": "// Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology\n//\n// This source code file is licensed under both the three-clause BSD license\n// and the GPLv3. You may select, at your option, one of the two licenses. The\n// corresponding license headers follow:\n//\n//\n// Three-clause BSD license:\n//\n//  Redistribution and use in source and binary forms, with or without\n//  modification, are permitted provided that the following conditions are met:\n//\n//  1. Redistributions of source code must retain the above copyright notice,\n//     this list of conditions and the following disclaimer.\n//\n//  2. Redistributions in binary form must reproduce the above copyright\n//     notice, this list of conditions and the following disclaimer in the\n//     documentation and/or other materials provided with the distribution.\n//\n//  3. Neither the name of the copyright holder nor the names of its\n//     contributors may be used to endorse or promote products derived from\n//     this software without specific prior written permission.\n//\n//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n//  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n//  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n//  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n//  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n//  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n//  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n//  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n//  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n//  POSSIBILITY OF SUCH DAMAGE.\n//\n//\n// GPLv3:\n//\n//  This program is free software: you can redistribute it and/or modify\n//  it under the terms of the GNU General Public License as published by\n//  the Free Software Foundation, either version 3 of the License, or\n//  (at your option) any later version.\n//\n//  This program is distributed in the hope that it will be useful,\n//  but WITHOUT ANY WARRANTY; without even the implied warranty of\n//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n//  GNU General Public License for more details.\n//\n//  You should have received a copy of the GNU General Public License\n//  along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n\n#include \"math_constants.glsl\"\n\n\n/*! This structure carries intermediate results that only need to be computed\n\tonce per polygon and shading point to take samples proportional to solid\n\tangle. Sampling is performed by subdividing the convex polygon into\n\ttriangles as triangle fan around vertex 0 and then using our variant of\n\tArvo's method.*/\nstruct solid_angle_polygon_t {\n\t//! The number of vertices that form the polygon\n\tuint vertex_count;\n\t//! Normalized direction vectors from the shading point to each vertex\n\tvec3 vertex_dirs[MAX_POLYGON_VERTEX_COUNT];\n\t/*! A few intermediate quantities about the triangle consisting of vertices\n\t\ti + 1, 0, and i + 2. If the three vertices are v0, v1, v2, the entries\n\t\tare determinant(mat3(v0, v1, v2)), dot(v0 + v1, v2) and\n\t\t1.0f + dot(v0, v1).*/\n\tvec3 triangle_parameters[MAX_POLYGON_VERTEX_COUNT - 2];\n\t//! At index i, this array holds the solid angle of the triangle fan formed\n\t//! by vertices 0 to i + 2.\n\tfloat fan_solid_angles[MAX_POLYGON_VERTEX_COUNT - 2];\n\t//! The total solid angle of the polygon\n\tfloat solid_angle;\n};\n\n\n/*! A piecewise polynomial approximation to positive_atan(y). The maximal\n\tabsolute error is 1.16e-05f. At least on Turing GPUs, it is faster but also\n\tsignificantly less accurate. The proper atan has at most 2 ulps of error\n\tthere.*/\nfloat fast_positive_atan(float y) {\n\tfloat rx;\n\tfloat ry;\n\tfloat rz;\n\trx = (abs(y) > 1.0f) ? (1.0f / abs(y)) : abs(y);\n\try = rx * rx;\n\trz = fma(ry, 0.02083509974181652f, -0.08513300120830536);\n\trz = fma(ry, rz, 0.18014100193977356f);\n\trz = fma(ry, rz, -0.3302994966506958f);\n\try = fma(ry, rz, 0.9998660087585449f);\n\trz = fma(-2.0f * ry, rx, M_HALF_PI);\n\trz = (abs(y) > 1.0f) ? rz : 0.0f;\n\trx = fma(rx, ry, rz);\n\treturn (y < 0.0f) ? (M_PI - rx) : rx;\n}\n\n\n/*! Returns an angle between 0 and M_PI such that tan(angle) == tangent. In\n\tother words, it is a version of atan() that is offset to be non-negative.\n\tNote that it may be switched to an approximate mode by the\n\tUSE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING flag.*/\nfloat positive_atan(float tangent) {\n#ifdef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING\n\treturn fast_positive_atan(tangent);\n#else\n\tfloat offset = (tangent < 0.0f) ? M_PI : 0.0f;\n\treturn atan(tangent) + offset;\n#endif\n}\n\n\n/*! Prepares all intermediate values to sample a triangle fan around vertex 0\n\t(e.g. a convex polygon) proportional to solid angle using our method.\n\t\\param vertex_count Number of vertices forming the polygon.\n\t\\param vertices List of vertex locations.\n\t\\param shading_position The location of the shading point.\n\t\\return Input for sample_solid_angle_polygon().*/\nsolid_angle_polygon_t prepare_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT], vec3 shading_position) {\n\tsolid_angle_polygon_t polygon;\n\tpolygon.vertex_count = vertex_count;\n\t// Normalize vertex directions\n\t[[unroll]]\n\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {\n\t\tpolygon.vertex_dirs[i] = normalize(vertices[i] - shading_position);\n\t}\n\t// Prepare a Householder transform that maps vertex 0 onto (+/-1, 0, 0). We\n\t// only store the yz-components of that Householder vector and a factor of\n\t// 2.0f / sqrt(abs(polygon.vertex_dirs[0].x) + 1.0f) is pulled in there to\n\t// save on multiplications later. This approach is necessary to avoid\n\t// numerical instabilities in determinant computation below.\n\tfloat householder_sign = (polygon.vertex_dirs[0].x > 0.0f) ? -1.0f : 1.0f;\n\tvec2 householder_yz = polygon.vertex_dirs[0].yz * (1.0f / (abs(polygon.vertex_dirs[0].x) + 1.0f));\n\t// Compute solid angles and prepare sampling\n\tpolygon.solid_angle = 0.0f;\n\tfloat previous_dot_1_2 = dot(polygon.vertex_dirs[0], polygon.vertex_dirs[1]);\n\t[[unroll]]\n\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 2; ++i) {\n\t\tif (i >= 1 && i + 2 >= vertex_count) break;\n\t\t// We look at one triangle of the triangle fan at a time\n\t\tvec3 vertices[3] = {\n\t\t\tpolygon.vertex_dirs[i + 1],\n\t\t\tpolygon.vertex_dirs[0],\n\t\t\tpolygon.vertex_dirs[i + 2]};\n\t\tfloat dot_0_1 = previous_dot_1_2;\n\t\tfloat dot_0_2 = dot(vertices[0], vertices[2]);\n\t\tfloat dot_1_2 = dot(vertices[1], vertices[2]);\n\t\tprevious_dot_1_2 = dot_1_2;\n\t\t// Compute the bottom right minor of vertices after application of the\n\t\t// Householder transform\n\t\tfloat dot_householder_0 = fma(-householder_sign, vertices[0].x, dot_0_1);\n\t\tfloat dot_householder_2 = fma(-householder_sign, vertices[2].x, dot_1_2);\n\t\tmat2 bottom_right_minor = mat2(\n\t\t\tfma(vec2(-dot_householder_0), householder_yz, vertices[0].yz),\n\t\t\tfma(vec2(-dot_householder_2), householder_yz, vertices[2].yz));\n\t\t// The absolute value of the determinant of vertices equals the 2x2\n\t\t// determinant because the Householder transform turns the first column\n\t\t// into (+/-1, 0, 0)\n\t\tfloat simplex_volume = abs(determinant(bottom_right_minor));\n\t\t// Compute the solid angle of the triangle using a formula proposed by:\n\t\t// A. Van Oosterom and J. Strackee, 1983, The Solid Angle of a\n\t\t// Plane Triangle, IEEE Transactions on Biomedical Engineering 30:2\n\t\t// https://doi.org/10.1109/TBME.1983.325207\n\t\tfloat dot_0_2_plus_1_2 = dot_0_2 + dot_1_2;\n\t\tfloat one_plus_dot_0_1 = 1.0f + dot_0_1;\n\t\tfloat tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2);\n\t\tfloat triangle_solid_angle = 2.0f * positive_atan(tangent);\n\t\tpolygon.solid_angle += triangle_solid_angle;\n\t\tpolygon.fan_solid_angles[i] = polygon.solid_angle;\n\t\t// Some intermediate results from above help us with sampling\n\t\tpolygon.triangle_parameters[i] = vec3(simplex_volume, dot_0_2_plus_1_2, one_plus_dot_0_1);\n\t}\n\treturn polygon;\n}\n\n\n/*! An implementation of mix() using two fused-multiply add instructions. Used\n\tbecause the native mix() implementation had stability issues in a few\n\tspots. Credit to Fabian Giessen's blog, see:\n\thttps://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/\n\t*/\nfloat mix_fma(float x, float y, float a) {\n\treturn fma(a, y, fma(-a, x, x));\n}\n\n\n/*! Given the output of prepare_solid_angle_polygon_sampling(), this function\n\tmaps the given random numbers in the range from 0 to 1 to a normalized\n\tdirection vector providing a sample of the solid angle of the polygon in\n\tthe original space (used for arguments of\n\tprepare_solid_angle_polygon_sampling()). Samples are distributed in\n\tproportion to solid angle assuming uniform inputs.*/\nvec3 sample_solid_angle_polygon(solid_angle_polygon_t polygon, vec2 random_numbers) {\n\t// Decide which triangle needs to be sampled\n\tfloat target_solid_angle = polygon.solid_angle * random_numbers[0];\n\tfloat subtriangle_solid_angle = target_solid_angle;\n\tvec3 parameters = polygon.triangle_parameters[0];\n\tvec3 vertices[3] = {\n\t\tpolygon.vertex_dirs[1], polygon.vertex_dirs[0], polygon.vertex_dirs[2]\n\t};\n\t[[unroll]]\n\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 3; ++i) {\n\t\tif (i + 3 >= polygon.vertex_count || polygon.fan_solid_angles[i] >= target_solid_angle) break;\n\t\tsubtriangle_solid_angle = target_solid_angle - polygon.fan_solid_angles[i];\n\t\tvertices[0] = polygon.vertex_dirs[i + 2];\n\t\tvertices[2] = polygon.vertex_dirs[i + 3];\n\t\tparameters = polygon.triangle_parameters[i + 1];\n\t}\n\t// Construct a new vertex 2 on the arc between vertices 0 and 2 such that\n\t// the resulting triangle has solid angle subtriangle_solid_angle\n\tvec2 cos_sin = vec2(cos(0.5f * subtriangle_solid_angle), sin(0.5f * subtriangle_solid_angle));\n\tvec3 offset = vertices[0] * (parameters[0] * cos_sin.x - parameters[1] * cos_sin.y) + vertices[2] * (parameters[2] * cos_sin.y);\n\tvec3 new_vertex_2 = fma(2.0f * vec3(dot(vertices[0], offset) / dot(offset, offset)), offset, -vertices[0]);\n\t// Now sample the line between vertex 1 and the newly created vertex 2\n\tfloat s2 = dot(vertices[1], new_vertex_2);\n\tfloat s = mix_fma(1.0f, s2, random_numbers[1]);\n\tfloat denominator = fma(-s2, s2, 1.0f);\n\tfloat t_normed = sqrt(fma(-s, s, 1.0f) / denominator);\n\t// s2 may exceed one due to rounding error. random_numbers[1] is the\n\t// limit of t_normed for s2 -> 1.\n\tt_normed = (denominator > 0.0f) ? t_normed : random_numbers[1];\n\treturn fma(-t_normed, s2, s) * vertices[1] + t_normed * new_vertex_2;\n}\n\n\n/*! This structure carries intermediate results that only need to be computed\n\tonce per polygon and shading point to take samples proportional to\n\tprojected solid angle.*/\nstruct projected_solid_angle_polygon_t {\n\t//! The number of vertices that form the polygon\n\tuint vertex_count;\n\t/*! The x- and y-coordinates of each polygon vertex in a coordinate system\n\t\twhere the normal is the z-axis. The vertices are sorted\n\t\tcounterclockwise.*/\n\tvec2 vertices[MAX_POLYGON_VERTEX_COUNT];\n\t/*! For each vertex in vertices, this vector describes the ellipse for the\n\t\tnext edge in counterclockwise direction. The last entry is meaningless,\n\t\texcept in the central case. For vertex 0, it holds the outer ellipse.\n\t\t\\see ellipse_from_edge() */\n\tvec2 ellipses[MAX_POLYGON_VERTEX_COUNT];\n\t//! The inner ellipse adjacent to vertex 0. If the x-component is positive,\n\t//! the central case is present.\n\tvec2 inner_ellipse_0;\n\t/*! At index i, this array holds the projected solid angle of the polygon\n\t\tin the sector between (sorted) vertices i and (i + 1) % vertex_count\n\t\tIn the central case, entry vertex_count - 1 is meaningful, otherwise\n\t\tnot.*/\n\tfloat sector_projected_solid_angles[MAX_POLYGON_VERTEX_COUNT];\n\t//! The total projected solid angle of the polygon\n\tfloat projected_solid_angle;\n};\n\n\n//! Computes a * b - c * d with at most 1.5 ulps of error in the result. See\n//! https://pharr.org/matt/blog/2019/11/03/difference-of-floats.html or\n//! Claude-Pierre Jeannerod, Nicolas Louvet and Jean-Michel Muller, 2013,\n//! Further analysis of Kahan's algorithm for the accurate computation of 2x2\n//! determinants, AMS Mathematics of Computation 82:284,\n//! https://doi.org/10.1090/S0025-5718-2013-02679-8\nfloat kahan(float a, float b, float c, float d) {\n\t// Uncomment the line below to improve efficiency but reduce accuracy\n\t// return a * b - c * d;\n\tfloat cd = c * d;\n\tfloat error = fma(c, d, -cd);\n\tfloat result = fma(a, b, -cd);\n\treturn result - error;\n}\n\n\n//! Implements a cross product using Kahan's algorithm for every single entry,\n//! i.e. the error in each output entry is at most 1.5 ulps\nvec3 cross_stable(vec3 lhs, vec3 rhs) {\n\treturn vec3(\n\t\tkahan(lhs.y, rhs.z, lhs.z, rhs.y),\n\t\tkahan(lhs.z, rhs.x, lhs.x, rhs.z),\n\t\tkahan(lhs.x, rhs.y, lhs.y, rhs.x)\n\t);\n}\n\n\n//! \\return The given vector, rotated 90 degrees counterclockwise around the\n//! \t\torigin\nvec2 rotate_90(vec2 input_vector) {\n\treturn vec2(-input_vector.y, input_vector.x);\n}\n\n\n//! \\return true iff the given ellipse is marked as inner ellipse, i.e. iff the\n//!\t\tspherical polygon that is bounded by it is further away from the zenith\n//!\t\tthan this ellipse.\nbool is_inner_ellipse(vec2 ellipse) {\n\t// If the implementation below causes you trouble, e.g. because you want to\n\t// port to a different language, you may replace it by the commented line\n\t// below but it will lead to seldom artifacts when ellipse.x == 0.0f.\n\t// return ellipse.x < 0.0f;\n\t// Extract the sign bit from ellipse.x (to be able to tell apart +0 and -0)\n\treturn (floatBitsToUint(ellipse.x) & 0x80000000) != 0;\n}\n\n\n//! \\return true iff the given polygon contains the zenith (also known as\n//!\t\tnormal vector).\nbool is_central_case(projected_solid_angle_polygon_t polygon) {\n\treturn polygon.inner_ellipse_0.x > 0.0f;\n}\n\n\n/*! Takes the great circle for the plane through the origin and the given two\n\tpoints and constructs an ellipse for its projection to the xy-plane.\n\t\\return A vector ellipse such that a point is on the ellipse if and only if\n\t\tdot(ellipse, point) * dot(ellipse, point) + dot(point, point) == 1.0f.\n\t\tIn other words, it is a normal vector of the great circle in half-\n\t\tvector space. The sign bit of x encodes whether the edge runs clockwise\n\t\tfrom vertex_0 to vertex_1 (inner ellipse) or not.\n\t\\see is_inner_ellipse() */\nvec2 ellipse_from_edge(vec3 vertex_0, vec3 vertex_1) {\n\tvec3 normal = cross_stable(vertex_0, vertex_1);\n\tfloat scaling = 1.0f / normal.z;\n\tscaling = is_inner_ellipse(normal.xy) ? -scaling : scaling;\n\tvec2 ellipse = normal.xy * scaling;\n\t// By convention, degenerate ellipses are outer ellipses, i.e. the first\n\t// component is infinite\n\tellipse.x = (normal.z != 0.0f) ? ellipse.x : M_INFINITY;\n\treturn ellipse;\n}\n\n\n//! Transforms the given point using the matrix that characterizes the given\n//! ellipse (as produced by ellipse_from_edge()). To be precise, this matrix is\n//! identity + outerProduct(ellipse, ellipse).\nvec2 ellipse_transform(vec2 ellipse, vec2 point) {\n\treturn fma(vec2(dot(ellipse, point)), ellipse, point);\n}\n\n\n//! Given an ellipse in the format produced by ellipse_from_edge(), this\n//! function returns the determinant of the matrix characterizing this\n//! ellipse.\nfloat get_ellipse_det(vec2 ellipse) {\n\treturn fma(ellipse.x, ellipse.x, fma(ellipse.y, ellipse.y, 1.0f));\n}\n\n//! Returns the reciprocal square root of the ellipse determinant produced by\n//! get_ellipse_det().\nfloat get_ellipse_rsqrt_det(vec2 ellipse) {\n\treturn inversesqrt(get_ellipse_det(ellipse));\n}\n\n//! \\return Reciprocal square of get_ellipse_direction_factor(ellipse, dir)\nfloat get_ellipse_direction_factor_rsq(vec2 ellipse, vec2 dir) {\n\tfloat ellipse_dot_dir = dot(ellipse, dir);\n\tfloat dir_dot_dir = dot(dir, dir);\n\treturn fma(ellipse_dot_dir, ellipse_dot_dir, dir_dot_dir);\n}\n\n/*! Computes a factor by which a direction vector has to be multiplied to\n\tobtain a point on the given ellipse.\n\t\\param ellipse An ellipse as produced by ellipse_from_edge().\n\t\\param dir The direction vector to be scaled onto the ellipse.\n\t\\return get_ellipse_direction_factor(ellipse, dir) * dir is a point on\n\t\tthe ellipse.*/\nfloat get_ellipse_direction_factor(vec2 ellipse, vec2 dir) {\n\treturn inversesqrt(get_ellipse_direction_factor_rsq(ellipse, dir));\n}\n\n//! Like get_ellipse_direction_factor() but assumes that the given direction is\n//! normalized. Faster.\nfloat get_ellipse_normalized_direction_factor(vec2 ellipse, vec2 normalized_dir) {\n\tfloat ellipse_dot_dir = dot(ellipse, normalized_dir);\n\treturn inversesqrt(fma(ellipse_dot_dir, ellipse_dot_dir, 1.0f));\n}\n\n\n//! Helper for get_area_between_ellipses_in_sector() and\n//! sample_sector_between_ellipses()\nfloat get_area_between_ellipses_in_sector_from_tangents(float inner_rsqrt_det, float inner_tangent, float outer_rsqrt_det, float outer_tangent) {\n\tfloat inner_area = inner_rsqrt_det * positive_atan(inner_tangent);\n\tfloat result = fma(outer_rsqrt_det, positive_atan(outer_tangent), -inner_area);\n\t// Sort out NaNs and negative results\n\treturn (result > 0.0f) ? (0.5f * result) : 0.0f;\n}\n\n\n/*! Returns the signed area between the given outer and inner ellipses within\n\tthe sector enclosed by dir_0 and dir_1. Besides ellipses as produced by\n\tellipse_from_edge(), you also have to pass output of\n\tget_ellipse_rsqrt_det(). Faster than calling get_ellipse_area_in_sector()\n\ttwice.*/\nfloat get_area_between_ellipses_in_sector(vec2 inner_ellipse, float inner_rsqrt_det, vec2 outer_ellipse, float outer_rsqrt_det, vec2 dir_0, vec2 dir_1) {\n\tfloat det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0)));\n\tfloat inner_dot = inner_rsqrt_det * dot(dir_0, ellipse_transform(inner_ellipse, dir_1));\n\tfloat outer_dot = outer_rsqrt_det * dot(dir_0, ellipse_transform(outer_ellipse, dir_1));\n\treturn get_area_between_ellipses_in_sector_from_tangents(\n\t\tinner_rsqrt_det, det_dirs / inner_dot,\n\t\touter_rsqrt_det, det_dirs / outer_dot);\n}\n\n\n/*! Computes the area for the intersection of the given ellipse and the sector\n\tbetween the given two directions (going counterclockwise from dir_0 to\n\tdir_1 for at most 180 degrees). The scaling of the directions is\n\tirrelevant.\n\t\\see ellipse_from_edge() */\nfloat get_ellipse_area_in_sector(vec2 ellipse, vec2 dir_0, vec2 dir_1) {\n\tfloat ellipse_rsqrt_det = get_ellipse_rsqrt_det(ellipse);\n\tfloat det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0)));\n\tfloat ellipse_dot = ellipse_rsqrt_det * dot(dir_0, ellipse_transform(ellipse, dir_1));\n\tfloat area = 0.5f * ellipse_rsqrt_det * positive_atan(det_dirs / ellipse_dot);\n\t// For degenerate ellipses, the result may be NaN but must be 0.0f\n\treturn (ellipse_rsqrt_det > 0.0f) ? area : 0.0f;\n}\n\n\n/*! Swaps vertices lhs and rhs (along with corresponding ellipses) of the given\n\tpolygon if the shorter path from lhs to rhs is clockwise. If the vertices\n\thave identical directions in the xy-plane, vertices with degenerate\n\tellipses come first.\n\t\\note To avoid costly register spilling, lhs and rhs must be compile time\n\t\tconstants.*/\nvoid compare_and_swap(inout projected_solid_angle_polygon_t polygon, uint lhs, uint rhs) {\n\tvec2 lhs_copy = polygon.vertices[lhs];\n\t// This line is designed to agree with the implementation of cross_stable\n\t// for the z-coordinate, which determines if ellipses are inner or outer\n\tfloat normal_z = kahan(lhs_copy.x, -polygon.vertices[rhs].y, lhs_copy.y, -polygon.vertices[rhs].x);\n\t// Tie breaker: If both vertices are at the same angle (i.e. on a common\n\t// great circle through the zenith), the one with the degenerate ellipse\n\t// comes first\n\tbool swap = (normal_z == 0.0f) ? isinf(polygon.ellipses[rhs].x) : (normal_z > 0.0f);\n\tpolygon.vertices[lhs] = swap ? polygon.vertices[rhs] : lhs_copy;\n\tpolygon.vertices[rhs] = swap ? lhs_copy : polygon.vertices[rhs];\n\tlhs_copy = polygon.ellipses[lhs];\n\tpolygon.ellipses[lhs] = swap ? polygon.ellipses[rhs] : lhs_copy;\n\tpolygon.ellipses[rhs] = swap ? lhs_copy : polygon.ellipses[rhs];\n}\n\n\n//! Sorts the vertices of the given convex polygon counterclockwise using a\n//! special sorting network. For non-convex polygons, the method may fail.\nvoid sort_convex_polygon_vertices(inout projected_solid_angle_polygon_t polygon) {\n\tif (polygon.vertex_count == 3) {\n\t\tcompare_and_swap(polygon, 1, 2);\n\t}\n#if MAX_POLYGON_VERTEX_COUNT >= 4\n\telse if (polygon.vertex_count == 4) {\n\t\tcompare_and_swap(polygon, 1, 3);\n\t}\n#endif\n#if MAX_POLYGON_VERTEX_COUNT >= 5\n\telse if (polygon.vertex_count == 5) {\n\t\tcompare_and_swap(polygon, 2, 4);\n\t\tcompare_and_swap(polygon, 1, 3);\n\t\tcompare_and_swap(polygon, 1, 2);\n\t\tcompare_and_swap(polygon, 0, 3);\n\t\tcompare_and_swap(polygon, 3, 4);\n\t}\n#endif\n#if MAX_POLYGON_VERTEX_COUNT >= 6\n\telse if (polygon.vertex_count == 6) {\n\t\tcompare_and_swap(polygon, 3, 5);\n\t\tcompare_and_swap(polygon, 2, 4);\n\t\tcompare_and_swap(polygon, 1, 5);\n\t\tcompare_and_swap(polygon, 0, 4);\n\t\tcompare_and_swap(polygon, 4, 5);\n\t\tcompare_and_swap(polygon, 1, 3);\n\t}\n#endif\n#if MAX_POLYGON_VERTEX_COUNT >= 7\n\telse if (polygon.vertex_count == 7) {\n\t\tcompare_and_swap(polygon, 2, 5);\n\t\tcompare_and_swap(polygon, 1, 6);\n\t\tcompare_and_swap(polygon, 5, 6);\n\t\tcompare_and_swap(polygon, 3, 4);\n\t\tcompare_and_swap(polygon, 0, 4);\n\t\tcompare_and_swap(polygon, 4, 6);\n\t\tcompare_and_swap(polygon, 1, 3);\n\t\tcompare_and_swap(polygon, 3, 5);\n\t\tcompare_and_swap(polygon, 4, 5);\n\t}\n#endif\n#if MAX_POLYGON_VERTEX_COUNT >= 8\n\telse if (polygon.vertex_count == 8) {\n\t\tcompare_and_swap(polygon, 2, 6);\n\t\tcompare_and_swap(polygon, 3, 7);\n\t\tcompare_and_swap(polygon, 1, 5);\n\t\tcompare_and_swap(polygon, 0, 4);\n\t\tcompare_and_swap(polygon, 4, 6);\n\t\tcompare_and_swap(polygon, 5, 7);\n\t\tcompare_and_swap(polygon, 6, 7);\n\t\tcompare_and_swap(polygon, 4, 5);\n\t\tcompare_and_swap(polygon, 1, 3);\n\t}\n#endif\n\t// This comparison is shared by all sorting networks\n\tcompare_and_swap(polygon, 0, 2);\n#if MAX_POLYGON_VERTEX_COUNT >= 4\n\tif (polygon.vertex_count >= 4) {\n\t\t// This comparison is shared by all sorting networks except the one for\n\t\t// triangles\n\t\tcompare_and_swap(polygon, 2, 3);\n\t}\n#endif\n\t// This comparison is shared by all sorting networks\n\tcompare_and_swap(polygon, 0, 1);\n}\n\n\n/*! Prepares all intermediate values to sample a convex polygon proportional to\n\tprojected solid angle.\n\t\\param vertex_count Number of vertices forming the polygon (at least 3).\n\t\\param vertices List of vertex locations in a coordinate system where the\n\t\tshading position is the origin and the normal is the z-axis. The\n\t\tpolygon should be already clipped against the plane z=0. If\n\t\tvertex_count < MAX_POLYGON_VERTEX_COUNT, the first vertex has to be\n\t\trepeated at vertex_count. They need not be normalized but if you\n\t\tencounter issues with under- or overflow (e.g. NaN or INF outputs),\n\t\tnormalization may help. The polygon must be convex, and the winding of\n\t\tthe vertices as seen from the origin must be clockwise. No three\n\t\tvertices should be collinear.\n\t\\return Intermediate values for sampling.*/\nprojected_solid_angle_polygon_t prepare_projected_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT]) {\n\tprojected_solid_angle_polygon_t polygon;\n\t// Copy vertices and assign ellipses\n\tpolygon.vertex_count = vertex_count;\n\tpolygon.inner_ellipse_0 = vec2(1.0f, 0.0f);\n\tpolygon.vertices[0] = vertices[0].xy;\n\tpolygon.ellipses[0] = ellipse_from_edge(vertices[0], vertices[1]);\n\tvec2 previous_ellipse = polygon.ellipses[0];\n\t[[unroll]]\n\tfor (uint i = 1; i != MAX_POLYGON_VERTEX_COUNT; ++i) {\n\t\tpolygon.vertices[i] = vertices[i].xy;\n\t\tif (i > 2 && i == polygon.vertex_count) break;\n\t\tvec2 ellipse = ellipse_from_edge(vertices[i], vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]);\n\t\tbool ellipse_inner = is_inner_ellipse(ellipse);\n\t\t// If the edge is an inner edge, the order is going to flip\n\t\tpolygon.ellipses[i] = ellipse_inner ? previous_ellipse : ellipse;\n\t\t// In doing so, we drop one ellipse, unless we store it explicitly\n\t\tpolygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0;\n\t\tprevious_ellipse = ellipse;\n\t}\n\t// Same thing for the first vertex (i.e. here we close the loop)\n\tvec2 ellipse = polygon.ellipses[0];\n\tbool ellipse_inner = is_inner_ellipse(ellipse);\n\tpolygon.ellipses[0] = ellipse_inner ? previous_ellipse : ellipse;\n\tpolygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0;\n\t// Compute projected solid angles per sector and in total\n\tpolygon.projected_solid_angle = 0.0f;\n\tif (is_central_case(polygon)) {\n\t\t// In the central case, we have polygon.vertex_count sectors, each\n\t\t// bounded by a single ellipse\n\t\t[[unroll]]\n\t\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {\n\t\t\tif (i > 2 && i == polygon.vertex_count) break;\n\t\t\tpolygon.sector_projected_solid_angles[i] = get_ellipse_area_in_sector(polygon.ellipses[i], polygon.vertices[i], polygon.vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]);\n\t\t\tpolygon.projected_solid_angle += polygon.sector_projected_solid_angles[i];\n\t\t}\n\t}\n\telse {\n\t\t// Sort vertices counter clockwise\n\t\tsort_convex_polygon_vertices(polygon);\n\t\t// There are polygon.vertex_count - 1 sectors, each bounded by an inner\n\t\t// and an outer ellipse\n\t\tvec2 inner_ellipse = polygon.inner_ellipse_0;\n\t\tfloat inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse);\n\t\tvec2 outer_ellipse;\n\t\tfloat outer_rsqrt_det;\n\t\t[[unroll]]\n\t\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {\n\t\t\tif (i > 1 && i + 1 == polygon.vertex_count) break;\n\t\t\tvec2 vertex_ellipse = polygon.ellipses[i];\n\t\t\tbool vertex_inner = is_inner_ellipse(vertex_ellipse);\n\t\t\tfloat vertex_rsqrt_det = get_ellipse_rsqrt_det(vertex_ellipse);\n\t\t\tif (i == 0) {\n\t\t\t\touter_ellipse = vertex_ellipse;\n\t\t\t\touter_rsqrt_det = vertex_rsqrt_det;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tinner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;\n\t\t\t\tinner_rsqrt_det = vertex_inner ? vertex_rsqrt_det : inner_rsqrt_det;\n\t\t\t\touter_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;\n\t\t\t\touter_rsqrt_det = vertex_inner ? outer_rsqrt_det : vertex_rsqrt_det;\n\t\t\t}\n\t\t\tpolygon.sector_projected_solid_angles[i] = get_area_between_ellipses_in_sector(\n\t\t\t\tinner_ellipse, inner_rsqrt_det, outer_ellipse, outer_rsqrt_det, polygon.vertices[i], polygon.vertices[i + 1]);\n\t\t\tpolygon.projected_solid_angle += polygon.sector_projected_solid_angles[i];\n\t\t}\n\t}\n\treturn polygon;\n}\n\n\n/*! \\return A scalar multiple of rhs that is not too far from being normalized.\n\t\tFor the result, length() returns something between sqrt(2.0f) and 8.0f.\n\t\tThe sign gets flipped such that the dot product of semi_circle and the\n\t\tresult is non-negative.\n\t\\note Introduces less latency than normalize() and does not use special\n\t\tfunctions. Useful to avoid under- and overflow when working with\n\t\thomogeneous coordinates. The result is undefined if rhs is zero.*/\nvec2 normalize_approx_and_flip(vec2 rhs, vec2 semi_circle) {\n\tfloat scaling = abs(rhs.x) + abs(rhs.y);\n\t// By flipping each bit on the exponent E, we turn it into 1 - E, which is\n\t// close enough to a reciprocal.\n\tscaling = uintBitsToFloat(floatBitsToUint(scaling) ^ 0x7F800000u);\n\t// If the line above causes you any sort of trouble (e.g. because you want\n\t// to port the code to another language or you are doing differentiable\n\t// rendering), just use this one instead:\n\t// scaling = 1.0f / scaling;\n\t// Flip the sign as needed\n\tscaling = (dot(rhs, semi_circle) >= 0.0f) ? scaling : -scaling;\n\treturn scaling * rhs;\n}\n\n\n/*! Returns a solution to the given homogeneous quadratic equation, i.e. a\n\tnon-zero vector root such that dot(root, quadratic * root) == 0.0f. The\n\treturned root depends continuously on quadratic. Pass -quadratic if you\n\twant the other root.\n\t\\note The implementation is as proposed by Blinn, except that we do not\n\thave a special case for quadratic[0][1] + quadratic[1][0] == 0.0f. Unlike\n\tthe standard quadratic formula, it allows us to postpone a division and is\n\tstable in all cases.\n\tJames F. Blinn 2006, How to Solve a Quadratic Equation, Part 2, IEEE\n\tComputer Graphics and Applications 26:2 https://doi.org/10.1109/MCG.2006.35\n*/\nvec2 solve_homogeneous_quadratic(mat2 quadratic) {\n\tfloat coeff_xy = 0.5f * (quadratic[0][1] + quadratic[1][0]);\n\tfloat sqrt_discriminant = sqrt(max(0.0f, coeff_xy * coeff_xy - quadratic[0][0] * quadratic[1][1]));\n\tfloat scaled_root = abs(coeff_xy) + sqrt_discriminant;\n\treturn (coeff_xy >= 0.0f) ? vec2(scaled_root, -quadratic[0][0]) : vec2(quadratic[1][1], scaled_root);\n}\n\n\n/*! Generates a sample between two ellipses and in a specified sector. The\n\tsample is distributed uniformly with respect to the area measure.\n\t\\param random_numbers A pair of independent uniform random numbers on [0,1]\n\t\\param target_area random_numbers[0] multiplied by the projected solid\n\t\tangle of the area to be sampled.\n\t\\param inner_ellipse, outer_ellipse The inner and outer ellipse, as\n\t\t produced by ellipse_from_edge().\n\t\\param dir_0, dir_1 Two direction vectors bounding the sector. They need\n\t\tnot be normalized.\n\t\\param iteration_count The number of iterations to perform. Lower values\n\t\ttrade speed for bias. Two iterations give practically no bias.\n\t\\return The sample in Cartesian coordinates.*/\nvec2 sample_sector_between_ellipses(vec2 random_numbers, float target_area, vec2 inner_ellipse, vec2 outer_ellipse, vec2 dir_0, vec2 dir_1, uint iteration_count) {\n\t// For the initialization, split the sector in half\n\tvec2 quad_dirs[3];\n\tquad_dirs[0] = normalize(dir_0);\n\tquad_dirs[2] = normalize(dir_1);\n\tquad_dirs[1] = quad_dirs[0] + quad_dirs[2];\n\t// Compute where these lines intersect the ellipses. The six intersection\n\t// points define two adjacent quads.\n\tfloat normalization_factor[2][3] = {\n\t\t{\n\t\t\tget_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[0]),\n\t\t\tget_ellipse_direction_factor(inner_ellipse, quad_dirs[1]),\n\t\t\tget_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[2])\n\t\t},\n\t\t{\n\t\t\tget_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[0]),\n\t\t\tget_ellipse_direction_factor(outer_ellipse, quad_dirs[1]),\n\t\t\tget_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[2])\n\t\t}\n\t};\n\t// Compute the relative size of the areas inside these quads\n\tfloat sector_areas[2] = {\n\t\tnormalization_factor[1][0] * normalization_factor[1][1] - normalization_factor[0][0] * normalization_factor[0][1],\n\t\tnormalization_factor[1][1] * normalization_factor[1][2] - normalization_factor[0][1] * normalization_factor[0][2]\n\t};\n\t// Now pick which of the two quads should be sampled for the\n\t// initialization. If it is not the second, we move data such that the\n\t// relevant array indices are 1 and 2 anyway.\n\tfloat target_quad_area = mix_fma(-sector_areas[0], sector_areas[1], random_numbers[0]);\n\tquad_dirs[2] = (target_quad_area <= 0.0f) ? quad_dirs[0] : quad_dirs[2];\n\tnormalization_factor[0][2] = (target_quad_area <= 0.0f) ? normalization_factor[0][0] : normalization_factor[0][2];\n\tnormalization_factor[1][2] = (target_quad_area <= 0.0f) ? normalization_factor[1][0] : normalization_factor[1][2];\n\ttarget_quad_area += (target_quad_area <= 0.0f) ? sector_areas[0] : -sector_areas[1];\n\t// We have been a bit lazy about area computation before but now we need\n\t// all the factors (except for a factor of 0.5 that cancels with a 2 later)\n\ttarget_quad_area *= abs(determinant(mat2(quad_dirs[1], quad_dirs[2])));\n\t// Construct normal vectors for the inner and outer edge of the selected\n\t// quad. We construct the normal like a half vector (i.e. by addition)\n\t// because it is less prone to cancellation than an approach using the edge\n\t// direction (i.e. subtraction of sometimes nearly identical vectors)\n\tvec2 quad_normals[2] = {\n\t\tquad_dirs[1] * normalization_factor[0][1] + quad_dirs[2] * normalization_factor[0][2],\n\t\tquad_dirs[1] * normalization_factor[1][1] + quad_dirs[2] * normalization_factor[1][2]\n\t};\n\tquad_normals[0] = ellipse_transform(inner_ellipse, quad_normals[0]);\n\tquad_normals[1] = ellipse_transform(outer_ellipse, quad_normals[1]);\n\t// Construct complete line equations\n\tfloat quad_offsets[2] = {\n\t\tdot(quad_normals[0], quad_dirs[1]) * normalization_factor[0][1],\n\t\tdot(quad_normals[1], quad_dirs[1]) * normalization_factor[1][1]\n\t};\n\t// Now sample the direction within the selected quad by constructing a\n\t// quadratic equation. This is the initialization for the iteration.\n\tmat2 quadratic = outerProduct((quad_offsets[1] * normalization_factor[1][2]) * rotate_90(quad_dirs[2]), quad_normals[0]);\n\tquadratic -= outerProduct((quad_offsets[0] * normalization_factor[0][2]) * rotate_90(quad_dirs[2]) + target_quad_area * quad_normals[0], quad_normals[1]);\n\tvec2 current_dir = solve_homogeneous_quadratic(quadratic);\n\n#ifndef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING\n\t// For boundary values, the initialization is perfect but the iteration may\n\t// be unstable, so we disable it\n\tfloat acceptable_error = 1.0e-5f;\n\titeration_count = (abs(random_numbers.x - 0.5f) <= 0.5f - acceptable_error) ? iteration_count : 0;\n\n\t// Now refine this initialization iteratively\n\tfloat inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse);\n\tfloat outer_rsqrt_det = get_ellipse_rsqrt_det(outer_ellipse);\n\t[[dont_unroll]]\n\tfor (uint i = 0; i != iteration_count; ++i) {\n\t\t// Avoid under- or overflow and flip the sign so that the clamping to\n\t\t// zero below makes sense\n\t\tcurrent_dir = normalize_approx_and_flip(current_dir, quad_dirs[1]);\n\t\t// Transform current_dir using both ellipses\n\t\tvec2 inner_dir = ellipse_transform(inner_ellipse, current_dir);\n\t\tvec2 outer_dir = ellipse_transform(outer_ellipse, current_dir);\n\t\t// Evaluate the objective function (reusing inner_dir and outer_dir)\n\t\tfloat det_dirs = max(+0.0f, dot(current_dir, rotate_90(quad_dirs[0])));\n\t\tfloat error = target_area - get_area_between_ellipses_in_sector_from_tangents(\n\t\t\tinner_rsqrt_det, det_dirs / (inner_rsqrt_det * dot(quad_dirs[0], inner_dir)),\n\t\t\touter_rsqrt_det, det_dirs / (outer_rsqrt_det * dot(quad_dirs[0], outer_dir)));\n\t\t// Construct a homogeneous quadratic whose solutions include the next\n\t\t// step of the iteration\n\t\tquadratic = outerProduct(inner_dir - outer_dir, rotate_90(current_dir)) - outerProduct((2.0f * error) * inner_dir, outer_dir);\n\t\tcurrent_dir = solve_homogeneous_quadratic(quadratic);\n\t}\n#endif\n\n\t// The halved sector is at most 90 degrees large, so the dot product with\n\t// the half vector has to be positive\n\tcurrent_dir = (dot(current_dir, quad_dirs[1]) >= 0.0f) ? current_dir : -current_dir;\n\t// Sample a squared radius uniformly between the two ellipses\n\tfloat inner_factor = 1.0f / get_ellipse_direction_factor_rsq(inner_ellipse, current_dir);\n\tfloat outer_factor = 1.0f / get_ellipse_direction_factor_rsq(outer_ellipse, current_dir);\n\tcurrent_dir *= sqrt(mix_fma(inner_factor, outer_factor, random_numbers[1]));\n\treturn current_dir;\n}\n\n\n/*! Produces a sample in the solid angle of the given polygon. If the random \n\tnumbers are uniform in [0,1]^2, the sample is uniform in the projected\n\tsolid angle of the polygon.\n\t\\param polygon Output of prepare_projected_solid_angle_polygon_sampling().\n\t\\param random_numbers A uniform point in [0,1]^2.\n\t\\return A sample on the upper hemisphere (i.e. z>=0) in Cartesian\n\t\tcoordinates.*/\nvec3 sample_projected_solid_angle_polygon(projected_solid_angle_polygon_t polygon, vec2 random_numbers) {\n\tfloat target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle;\n\t// Distinguish between the central case\n\tvec3 sampled_dir;\n\tvec2 outer_ellipse;\n\tvec2 dir_0;\n\tif (is_central_case(polygon)) {\n\t\t// Select a sector and copy the relevant attributes\n\t\t[[unroll]]\n\t\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {\n\t\t\tif (i > 0) {\n\t\t\t\ttarget_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1];\n\t\t\t}\n\t\t\touter_ellipse = polygon.ellipses[i];\n\t\t\tdir_0 = polygon.vertices[i];\n\t\t\tif ((i >= 2 && i + 1 == polygon.vertex_count) || target_projected_solid_angle < polygon.sector_projected_solid_angles[i])\n\t\t\t\tbreak;\n\t\t}\n\t\t// Sample a direction within the sector\n\t\tfloat sqrt_det = sqrt(get_ellipse_det(outer_ellipse));\n\t\tfloat angle = 2.0f * target_projected_solid_angle * sqrt_det;\n\t\tsampled_dir.xy = (cos(angle) * sqrt_det) * dir_0 + sin(angle) * rotate_90(ellipse_transform(outer_ellipse, dir_0));\n\t\t// Sample a squared radius uniformly within the ellipse\n\t\tsampled_dir.xy *= sqrt(random_numbers[1] / get_ellipse_direction_factor_rsq(outer_ellipse, sampled_dir.xy));\n\t}\n\t// And the decentral case\n\telse {\n\t\t// Select a sector and copy the relevant attributes\n\t\tfloat sector_projected_solid_angle;\n\t\tvec2 inner_ellipse = polygon.inner_ellipse_0;\n\t\tvec2 dir_1;\n\t\t[[unroll]]\n\t\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {\n\t\t\tvec2 vertex_ellipse = polygon.ellipses[i];\n\t\t\tif (i == 0) {\n\t\t\t\touter_ellipse = vertex_ellipse;\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttarget_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1];\n\t\t\t\tbool vertex_inner = is_inner_ellipse(vertex_ellipse);\n\t\t\t\tinner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;\n\t\t\t\touter_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;\n\t\t\t}\n\t\t\tdir_0 = polygon.vertices[i];\n\t\t\tdir_1 = polygon.vertices[i + 1];\n\t\t\tsector_projected_solid_angle = polygon.sector_projected_solid_angles[i];\n\t\t\tif ((i >= 1 && i + 2 == polygon.vertex_count) || target_projected_solid_angle < sector_projected_solid_angle)\n\t\t\t\tbreak;\n\t\t}\n\t\t// Sample it\n\t\trandom_numbers[0] = target_projected_solid_angle / sector_projected_solid_angle;\n\t\tsampled_dir.xy = sample_sector_between_ellipses(random_numbers, target_projected_solid_angle, inner_ellipse, outer_ellipse, dir_0, dir_1, 2);\n\t}\n\t// Construct the sample\n\tsampled_dir.z = sqrt(max(0.0f, fma(-sampled_dir.x, sampled_dir.x, fma(-sampled_dir.y, sampled_dir.y, 1.0f))));\n\treturn sampled_dir;\n}\n\n\n/*! Determines the error of a sample from the projected solid angle of a\n\tpolygon due to the iterative procedure.\n\t\\param polygon Output of prepare_projected_solid_angle_polygon_sampling().\n\t\\param random_numbers The value passed for random_numbers in\n\t\tsample_projected_solid_angle_polygon().\n\t\\param sampled_dir The direction returned by\n\t\tsample_projected_solid_angle_polygon().\n\t\\return The first component is the signed backward error: The difference\n\t\tbetween random_number_0 and the random number that would yield\n\t\tsampled_dir with perfect computation. The second component is the\n\t\tbackward error, multiplied by the projected solid angle of the polygon.\n\t\tThe third component is the signed forward error (at least a first-order\n\t\testimate of it): The difference between the exact result of sampling\n\t\tand the actual result in radians. Note that all of these are subject\n\t\tto rounding error themselves. In the central case, zero is returned.*/\nvec3 compute_projected_solid_angle_polygon_sampling_error(projected_solid_angle_polygon_t polygon, vec2 random_numbers, vec3 sampled_dir) {\n\tfloat target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle;\n\t// In the central case, the sampling procedure is exact except for rounding\n\t// error\n\tif (is_central_case(polygon)) {\n\t\treturn vec3(0.0f);\n\t}\n\t// In the other case, we repeat some computations to find the error\n\telse {\n\t\t// Select a sector and copy the relevant attributes\n\t\tfloat sector_projected_solid_angle;\n\t\tvec2 outer_ellipse;\n\t\tvec2 inner_ellipse = polygon.inner_ellipse_0;\n\t\tvec2 dir_0;\n\t\t[[unroll]]\n\t\tfor (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {\n\t\t\tif ((i > 1 && i + 1 == polygon.vertex_count) || (i > 0 && target_projected_solid_angle < 0.0f))\n\t\t\t\tbreak;\n\t\t\tsector_projected_solid_angle = polygon.sector_projected_solid_angles[i];\n\t\t\ttarget_projected_solid_angle -= sector_projected_solid_angle;\n\t\t\tvec2 vertex_ellipse = polygon.ellipses[i];\n\t\t\tbool vertex_inner = is_inner_ellipse(vertex_ellipse);\n\t\t\tif (i == 0) {\n\t\t\t\touter_ellipse = vertex_ellipse;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tinner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;\n\t\t\t\touter_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;\n\t\t\t}\n\t\t\tdir_0 = polygon.vertices[i];\n\t\t}\n\t\ttarget_projected_solid_angle += sector_projected_solid_angle;\n\t\t// Compute the area in the sector from sampled_dir and dir_0 between\n\t\t// the two ellipses\n\t\tfloat sampled_projected_solid_angle = get_area_between_ellipses_in_sector(\n\t\t\tinner_ellipse, get_ellipse_rsqrt_det(inner_ellipse),\n\t\t\touter_ellipse, get_ellipse_rsqrt_det(outer_ellipse),\n\t\t\tdir_0, sampled_dir.xy);\n\t\t// Compute error in the projected solid angle\n\t\tfloat scaled_backward_error = target_projected_solid_angle - sampled_projected_solid_angle;\n\t\tfloat backward_error = scaled_backward_error / polygon.projected_solid_angle;\n\t\t// Evaluate the derivative of the sampled direction with respect to the\n\t\t// projected solid angle\n\t\tmat2 constraint_matrix;\n\t\tvec2 inner_dir = ellipse_transform(inner_ellipse, sampled_dir.xy);\n\t\tvec2 outer_dir = ellipse_transform(outer_ellipse, sampled_dir.xy);\n\t\tfloat inner_factor = 1.0f / dot(sampled_dir.xy, inner_dir);\n\t\tfloat outer_factor = 1.0f / dot(sampled_dir.xy, outer_dir);\n\t\tconstraint_matrix[0] = 0.5f * (inner_factor - outer_factor) * rotate_90(sampled_dir.xy);\n\t\tconstraint_matrix[1] = ((1.0f - random_numbers[1]) / (inner_factor * inner_factor)) * inner_dir;\n\t\tconstraint_matrix[1] += (random_numbers[1] / (outer_factor * outer_factor)) * outer_dir;\n\t\tconstraint_matrix = transpose(constraint_matrix);\n\t\tvec3 sample_derivative;\n\t\tsample_derivative.xy = (1.0f / determinant(constraint_matrix)) * vec2(constraint_matrix[1][1], -constraint_matrix[0][1]);\n\t\tsample_derivative.z = -dot(sampled_dir.xy, sample_derivative.xy) / sampled_dir.z;\n\t\t// Evaluate the forward error in radians\n\t\tfloat forward_error = length(sample_derivative) * scaled_backward_error;\n\t\t// Return both errors\n\t\treturn vec3(backward_error, scaled_backward_error, forward_error);\n\t}\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_common.glsl",
    "content": "#extension GL_EXT_ray_tracing: require\n\n#define PAYLOAD_LOCATION_OPAQUE 0\n#define PAYLOAD_LOCATION_SHADOW 1\n#define PAYLOAD_LOCATION_ADDITIVE 2\n\nstruct RayPayloadOpaque {\n\tfloat t_offset, pixel_cone_spread_angle;\n\tvec4 hit_pos_t;\n\tvec4 prev_pos_t;\n\tvec3 normal;\n\tvec3 geometry_normal;\n\tvec3 base_color;\n\tfloat transmissiveness;\n\tvec3 emissive;\n\tfloat roughness;\n\tfloat metalness;\n\tint kusok_index;\n\tuint material_index;\n\tvec4 debug;\n};\n\n\n#define SHADOW_MISS 0\n#define SHADOW_HIT 1\n#define SHADOW_SKY 2\n\nstruct RayPayloadShadow {\n\tuint hit_type;\n};\n\n\nconst float additive_soft_overshoot = 16.;\nstruct RayPayloadAdditive {\n\tvec3 color;\n\tfloat ray_distance;\n};\n"
  },
  {
    "path": "ref/vk/shaders/ray_common_alphatest.rahit",
    "content": "#version 460 core\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_tracing: require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\n// TODO not really needed here?\n// It's an artifact of readHitGeometry() computing uv_lods, which we don't really use in this shader\n// Split readHitGeometry into basic and advanced\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_primary_common.glsl\"\n#include \"ray_kusochki.glsl\"\n\nhitAttributeEXT vec2 bary;\n\n#include \"rt_geometry.glsl\"\n\nconst float alpha_mask_threshold = .1f;\n\nvoid main() {\n\tconst Geometry geom = readHitGeometry(bary, ubo.ubo.ray_cone_width);\n\tconst uint tex_index = getKusok(geom.kusok_index).material.tex_base_color;\n\tconst vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], geom.uv);\n\n\tif (texture_color.a < alpha_mask_threshold) {\n\t\tignoreIntersectionEXT;\n\t}\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_interop.h",
    "content": "// Common definitions for both shaders and native code\n#ifndef RAY_INTEROP_H_INCLUDED\n#define RAY_INTEROP_H_INCLUDED\n\n#define LIST_SPECIALIZATION_CONSTANTS(X) \\\n\tX(0, uint, MAX_POINT_LIGHTS, 256) \\\n\tX(1, uint, MAX_EMISSIVE_KUSOCHKI, 256) \\\n\tX(2, uint, MAX_VISIBLE_POINT_LIGHTS, 63) \\\n\tX(3, uint, MAX_VISIBLE_SURFACE_LIGHTS, 255) \\\n\tX(4, float, LIGHT_GRID_CELL_SIZE, 128.) \\\n\tX(5, uint, MAX_LIGHT_CLUSTERS, 262144) \\\n\tX(6, uint, MAX_TEXTURES, 4096) \\\n\tX(7, uint, SBT_RECORD_SIZE, 32) \\\n\n#ifndef GLSL\n#include \"xash3d_types.h\"\n#include \"vk_const.h\"\n#define MAX_EMISSIVE_KUSOCHKI 256\n#define uint uint32_t\n#define vec2 vec2_t\n#define vec3 vec3_t\n#define vec4 vec4_t\n#define mat4 matrix4x4\ntypedef int ivec3[3];\ntypedef int ivec2[2];\n#define TOKENPASTE(x, y) x ## y\n#define TOKENPASTE2(x, y) TOKENPASTE(x, y)\n#define PAD(x) float TOKENPASTE2(pad_, __LINE__)[x];\n#define STRUCT struct\n\nenum {\n#define DECLARE_SPECIALIZATION_CONSTANT(index, type, name, default_value) \\\n\tSPEC_##name##_INDEX = index,\nLIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT)\n#undef DECLARE_SPECIALIZATION_CONSTANT\n};\n\n#else // if GLSL else\n#extension GL_EXT_shader_8bit_storage : require\n\n#define PAD(x)\n#define STRUCT\n\n#define DECLARE_SPECIALIZATION_CONSTANT(index, type, name, default_value) \\\n\tlayout (constant_id = index) const type name = default_value;\nLIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT)\n#undef DECLARE_SPECIALIZATION_CONSTANT\n\n#endif // not GLSL\n\nstruct Vertex {\n\tvec3 pos;\n\tvec3 prev_pos;\n\tvec3 normal;\n\tvec3 tangent;\n\tvec2 gl_tc;\n\tvec2 _unused_lm_tc;\n\tuint color;\n};\n\n#define GEOMETRY_BIT_OPAQUE 0x01\n#define GEOMETRY_BIT_ALPHA_TEST 0x02\n#define GEOMETRY_BIT_BLEND 0x04\n#define GEOMETRY_BIT_REFRACTIVE 0x08\n#define GEOMETRY_BIT_CASTS_SHADOW 0x10\n\n#define SHADER_OFFSET_MISS_REGULAR 0\n#define SHADER_OFFSET_MISS_SHADOW 1\n#define SHADER_OFFSET_MISS_EMPTY 2\n\n#define SHADER_OFFSET_HIT_REGULAR 0\n#define SHADER_OFFSET_HIT_ALPHA_TEST 1\n#define SHADER_OFFSET_HIT_ADDITIVE 2\n\n#define SHADER_OFFSET_HIT_REGULAR_BASE 0\n#define SHADER_OFFSET_HIT_SHADOW_BASE 3\n\n#define MATERIAL_MODE_OPAQUE 0\n#define MATERIAL_MODE_OPAQUE_ALPHA_TEST 1\n#define MATERIAL_MODE_TRANSLUCENT 2\n#define MATERIAL_MODE_BLEND_ADD 3\n#define MATERIAL_MODE_BLEND_MIX 4\n#define MATERIAL_MODE_BLEND_GLOW 5\n#define MATERIAL_MODE_DECAL 6\n#define MATERIAL_MODE_COUNT 7\n\n#define TEX_BASE_SKYBOX 0x0f000000u\n\nstruct Material {\n\tuint tex_base_color;\n\n\t// TODO can be combined into a single texture\n\tuint tex_roughness;\n\tuint tex_metalness;\n\tuint tex_normalmap;\n\n\t// TODO:\n\t// uint tex_emissive;\n\t// uint tex_detail;\n\n\tfloat roughness;\n\tfloat metalness;\n\tfloat normal_scale;\n\tPAD(1)\n\n\tvec4 base_color;\n};\n\nstruct ModelHeader {\n\tmat4 prev_transform;\n\tvec4 color;\n\tuint mode;\n\tPAD(3)\n};\n\nstruct Kusok {\n\t// Geometry data, static\n\tuint index_offset;\n\tuint vertex_offset;\n\n\t// material below consists of scalar fields only, so it's not aligned to vec4.\n\t// Alignt it here to vec4 explicitly, so that later vector fields are properly aligned (for simplicity).\n\tuint _padding0[2];\n\n\t// Per-kusok because individual surfaces can be patched\n\t// TODO? still move to material, or its own table? As this can be dynamic\n\tvec3 emissive;\n\tPAD(1)\n\n\t// TODO reference into material table\n\tSTRUCT Material material;\n};\n\nstruct PointLight {\n\tvec4 origin_r2; // vec4(center.xyz, radius²)\n\tvec4 color_stopdot;\n\tvec4 dir_stopdot2;\n\n\t// TODO move to either dedicated array, or section of array (by-index type delimiter)\n\tuint environment; // Is directional-only environment light\n\tPAD(3)\n};\n\nstruct PolygonLight {\n\tvec4 plane;\n\n\tvec3 center;\n\tfloat area;\n\n\tvec3 emissive;\n\tuint vertices_count_offset;\n};\n\nstruct LightsMetadata {\n\tuint num_polygons;\n\tuint num_point_lights;\n\tPAD(2)\n\tivec3 grid_min_cell;\n\tPAD(1)\n\tivec3 grid_size;\n\tPAD(1)\n\tSTRUCT PointLight point_lights[MAX_POINT_LIGHTS];\n\tSTRUCT PolygonLight polygons[MAX_EMISSIVE_KUSOCHKI];\n\tvec4 polygon_vertices[MAX_EMISSIVE_KUSOCHKI * 7]; // vec3 but aligned\n};\n\nstruct LightCluster {\n\tuint8_t num_point_lights;\n\tuint8_t num_polygons;\n\tuint8_t point_lights[MAX_VISIBLE_POINT_LIGHTS];\n\tuint8_t polygons[MAX_VISIBLE_SURFACE_LIGHTS];\n};\n\n#define PUSH_FLAG_LIGHTMAP_ONLY 0x01\n\n#define DEBUG_DISPLAY_DISABLED 0\n#define DEBUG_DISPLAY_BASECOLOR 1\n#define DEBUG_DISPLAY_BASEALPHA 2\n#define DEBUG_DISPLAY_EMISSIVE 3\n#define DEBUG_DISPLAY_NSHADE 4\n#define DEBUG_DISPLAY_NGEOM 5\n#define DEBUG_DISPLAY_LIGHTING 6\n#define DEBUG_DISPLAY_SURFHASH 7\n#define DEBUG_DISPLAY_DIRECT 8\n#define DEBUG_DISPLAY_DIRECT_DIFF 9\n#define DEBUG_DISPLAY_DIRECT_SPEC 10\n#define DEBUG_DISPLAY_INDIRECT 11\n#define DEBUG_DISPLAY_INDIRECT_DIFF 12\n#define DEBUG_DISPLAY_INDIRECT_SPEC 13\n#define DEBUG_DISPLAY_TRIHASH 14\n#define DEBUG_DISPLAY_MATERIAL 15\n#define DEBUG_DISPLAY_DIFFUSE 16\n#define DEBUG_DISPLAY_SPECULAR 17\n// add more when needed\n\n#define DEBUG_FLAG_WHITE_FURNACE (1<<0)\n\n#define RENDERER_FLAG_ONLY_DIFFUSE_GI (1<<0)\n#define RENDERER_FLAG_SEPARATED_REFLECTION (1<<1)\n#define RENDERER_FLAG_DENOISE_GI_BY_SH (1<<2)\n#define RENDERER_FLAG_DISABLE_GI (1<<3)\n#define RENDERER_FLAG_SPATIAL_RECONSTRUCTION (1<<4)\n\nstruct UniformBuffer {\n\tmat4 inv_proj, inv_view;\n\tmat4 prev_inv_proj, prev_inv_view;\n\tivec2 res;\n\tfloat ray_cone_width;\n\tuint random_seed;\n\tuint frame_counter;\n\tfloat skybox_exposure;\n\n\tuint debug_display_only;\n\tuint debug_flags;\n\n\tuint renderer_flags;\n};\n\n#undef PAD\n#undef STRUCT\n\n#ifndef GLSL\n#undef uint\n#undef vec3\n#undef vec4\n#undef TOKENPASTE\n#undef TOKENPASTE2\n#endif\n\n#endif // RAY_INTEROP_H_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/ray_kusochki.glsl",
    "content": "#ifndef RAY_KUSOCHKI_GLSL_INCLUDED\n#define RAY_KUSOCHKI_GLSL_INCLUDED\n\nKusok getKusok(uint index) { return kusochki.a[index]; }\nuint16_t getIndex(uint index) { return indices.a[index]; }\nModelHeader getModelHeader(uint index) { return model_headers.a[index]; }\n#define GET_VERTEX(index) (vertices.a[index])\n\n#endif //ifndef RAY_KUSOCHKI_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/ray_light_direct.glsl",
    "content": "#include \"utils.glsl\"\n#include \"noise.glsl\"\n\n#include \"ray_kusochki.glsl\"\n#include \"color_spaces.glsl\"\n\n#include \"light.glsl\"\n\nvoid readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) {\n\tconst vec4 n = imageLoad(normals_gs, uv);\n\tgeometry_normal = normalDecode(n.xy);\n\tshading_normal = normalDecode(n.zw);\n}\n\nvoid main() {\n#ifdef RAY_TRACE\n\tconst vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.;\n\tconst ivec2 pix = ivec2(gl_LaunchIDEXT.xy);\n#elif defined(RAY_QUERY)\n\tconst ivec2 pix = ivec2(gl_GlobalInvocationID);\n\tconst ivec2 res = ubo.ubo.res;\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\tconst vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.;\n#else\n#error You have two choices here. Ray trace, or Rake Yuri. So what it's gonna be, huh? Choose wisely.\n#endif\n\n\trand01_state = ubo.ubo.random_seed + pix.x * 1833 + pix.y * 31337;\n\n\t// FIXME incorrect for reflection/refraction\n\tconst vec4 target    = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1);\n\tconst vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz);\n\n\tconst vec4 material_data = imageLoad(material_rmxx, pix);\n\n\tMaterialProperties material;\n\tmaterial.base_color = SRGBtoLINEAR(imageLoad(base_color_a, pix).rgb);\n\tmaterial.metalness = material_data.g;\n\tmaterial.roughness = material_data.r;\n\n#ifdef BRDF_COMPARE\n\tg_mat_gltf2 = pix.x > ubo.ubo.res.x / 2.;\n#endif\n\n\tconst vec4 pos_t = imageLoad(position_t, pix);\n\n\tvec3 diffuse = vec3(0.), specular = vec3(0.);\n\n\tif (pos_t.w > 0.) {\n\t\tconst vec4 packed_normal = imageLoad(normals_gs, pix);\n\t\tconst vec3 geometry_normal = normalDecode(packed_normal.xy);\n\t\tconst vec3 shading_normal = normalDecode(packed_normal.zw);\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tif (IS_INVALIDV(pos_t.xyz) || IS_INVALIDV(geometry_normal)) {\n\t\t\tdebugPrintfEXT(\"ray_light_direct.glsl:%d INVALID pos_t.xyz=(%f,%f,%f) geometry_normal=(%f,%f,%f) packed_normal=(%f,%f,%f,%f)\",\n\t\t\t\t__LINE__, PRIVEC3(pos_t.xyz), PRIVEC3(geometry_normal), PRIVEC4(packed_normal));\n\t\t} else\n#endif\n\t\tcomputeLighting(pos_t.xyz + geometry_normal * .001, shading_normal, -direction, material, diffuse, specular);\n\t}\n\n\tDEBUG_VALIDATE_RANGE_VEC3(\"direct.diffuse\", diffuse, 0., 1e6);\n\tDEBUG_VALIDATE_RANGE_VEC3(\"direct.specular\", specular, 0., 1e6);\n\n#if LIGHT_POINT\n\timageStore(out_light_point_diffuse, pix, vec4(diffuse, 0.f));\n\timageStore(out_light_point_specular, pix, vec4(specular, 0.f));\n#endif\n\n#if LIGHT_POLYGON\n\timageStore(out_light_poly_diffuse, pix, vec4(diffuse, 0.f));\n\timageStore(out_light_poly_specular, pix, vec4(specular, 0.f));\n#endif\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_light_direct_point.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\n\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\n\nlayout (set = 0, binding = 7) readonly buffer SBOLights { LightsMetadata m; } lights;\nlayout (set = 0, binding = 8, align = 1) readonly buffer UBOLightClusters {\n\tLightCluster clusters_[MAX_LIGHT_CLUSTERS];\n} light_grid;\n\nlayout(set = 0, binding = 10, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 11, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 12, rgba8) uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 13, rgba8) uniform readonly image2D base_color_a;\n\nlayout(set = 0, binding = 20, rgba16f) uniform writeonly image2D out_light_point_diffuse;\nlayout(set = 0, binding = 21, rgba16f) uniform writeonly image2D out_light_point_specular;\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#define RAY_QUERY\n#define LIGHT_POINT 1\n#include \"ray_light_direct.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/ray_light_direct_poly.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\n\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\n\nlayout (set = 0, binding = 7) readonly buffer SBOLights { LightsMetadata m; } lights;\nlayout (set = 0, binding = 8, align = 1) readonly buffer UBOLightClusters {\n\tLightCluster clusters_[MAX_LIGHT_CLUSTERS];\n} light_grid;\n\nlayout(set = 0, binding = 10, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 11, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 12, rgba8) uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 13, rgba8) uniform readonly image2D base_color_a;\n\nlayout(set = 0, binding = 20, rgba16f) uniform writeonly image2D out_light_poly_diffuse;\nlayout(set = 0, binding = 21, rgba16f) uniform writeonly image2D out_light_poly_specular;\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#define RAY_QUERY\n#define LIGHT_POLYGON 1\n#include \"ray_light_direct.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\n#define RAY_QUERY\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;\n\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\nlayout(set = 0, binding = 7) uniform samplerCube skybox;\n\nlayout(set = 0, binding = 10, rgba8) uniform writeonly image2D out_base_color_a;\nlayout(set = 0, binding = 11, rgba32f) uniform writeonly image2D out_position_t;\nlayout(set = 0, binding = 12, rgba16f) uniform writeonly image2D out_normals_gs;\nlayout(set = 0, binding = 13, rgba8) uniform writeonly image2D out_material_rmxx;\nlayout(set = 0, binding = 14, rgba16f) uniform writeonly image2D out_emissive;\nlayout(set = 0, binding = 15, rgba32f) uniform writeonly image2D out_geometry_prev_position;\nlayout(set = 0, binding = 16, rgba16f) uniform writeonly image2D out_legacy_blend;\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_primary_common.glsl\"\n#include \"ray_primary_hit.glsl\"\n\n#include \"trace_simple_blending.glsl\"\n#include \"trace_decals.glsl\"\n#include \"skybox.glsl\"\n\nstruct Ray {\n\tvec3 origin, direction;\n\tfloat dist;\n};\n\nvec3 clipToWorldSpace(vec3 clip) {\n\tconst vec4 eye_space = ubo.ubo.inv_proj * vec4(clip, 1.);\n\treturn (ubo.ubo.inv_view * vec4(eye_space.xyz / eye_space.w, 1.)).xyz;\n}\n\nRay getPrimaryRay(in vec2 uv) {\n\tuv = uv * 2. - 1.;\n\tconst vec3 world_near = clipToWorldSpace(vec3(uv, 0.));\n\tconst vec3 world_far = clipToWorldSpace(vec3(uv, 1.));\n\n\tRay ret;\n\tret.origin = world_near;\n\tret.direction = world_far - world_near;\n\tret.dist = length(ret.direction);\n\tret.direction /= ret.dist;\n\treturn ret;\n}\n\nvoid main() {\n\tconst ivec2 pix = ivec2(gl_GlobalInvocationID);\n\tconst ivec2 res = ubo.ubo.res;\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\n\tconst vec2 uv = (gl_GlobalInvocationID.xy + .5) / res;\n\tconst Ray ray = getPrimaryRay(uv);\n\n\tRayPayloadPrimary payload;\n\tpayload.hit_t = vec4(0.);\n\tpayload.prev_pos_t = vec4(0.);\n\tpayload.base_color_a = vec4(0.);\n\tpayload.normals_gs = vec4(0.);\n\tpayload.material_rmxx = vec4(0.);\n\tpayload.emissive = vec4(0.);\n\n\trayQueryEXT rq;\n\tconst uint flags = 0\n\t\t| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsOpaqueEXT\n\t\t//| gl_RayFlagsTerminateOnFirstHitEXT\n\t\t//| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t;\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_OPAQUE | GEOMETRY_BIT_ALPHA_TEST | GEOMETRY_BIT_REFRACTIVE, ray.origin, 0., ray.direction, ray.dist);\n\twhile (rayQueryProceedEXT(rq)) {\n\t\t// FIXME this is a no-op. It doesn't do what I though it did. Should check for SBT index for alpha-test material instead.\n\t\tif (0 != (rayQueryGetRayFlagsEXT(rq) & gl_RayFlagsOpaqueEXT))\n\t\t\tcontinue;\n\n\t\t// alpha test\n\t\t// TODO check other possible ways of doing alpha test. They might be more efficient\n\t\t// (although in this particular primary ray case it's not taht important):\n\t\t// 1. Do a separate ray query for alpha masked geometry. Reason: here we might accidentally do the expensive\n\t\t//    texture sampling for geometry that's ultimately invisible (i.e. behind walls). Also, shader threads congruence.\n\t\t//    Separate pass could be more efficient as it'd be doing the same thing for every invocation.\n\t\t// 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha\n\t\tconst MiniGeometry geom = readCandidateMiniGeometry(rq);\n\t\tconst uint tex_base_color = getKusok(geom.kusok_index).material.tex_base_color;\n\t\t// Should never happen: skybox is opaque if (tex_base_color == TEX_BASE_SKYBOX)\n\t\tconst vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv);\n\n\t\tconst float alpha_mask_threshold = .1f;\n\t\tif (texture_color.a >= alpha_mask_threshold) {\n\t\t\trayQueryConfirmIntersectionEXT(rq);\n\t\t}\n\t}\n\n\tfloat L = ray.dist;\n\t//uint debug_geometry_index = 0;\n\tif (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionTriangleEXT) {\n\t\t//debug_geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true);\n\t\t//debug_geometry_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);\n\t\tprimaryRayHit(rq, payload);\n\t\tL = rayQueryGetIntersectionTEXT(rq, true);\n\n\t\ttraceDecals(payload);\n\t} else {\n\t\t// Draw skybox when nothing is hit\n\t\tpayload.emissive.rgb = sampleSkybox(ray.direction);\n\t}\n\n\tconst vec4 blend = traceLegacyBlending(ray.origin, ray.direction, L);\n\timageStore(out_legacy_blend, pix, blend);\n\n\timageStore(out_position_t, pix, payload.hit_t);\n\timageStore(out_base_color_a, pix, LINEARtoSRGB(payload.base_color_a));\n\timageStore(out_normals_gs, pix, payload.normals_gs);\n\timageStore(out_material_rmxx, pix, payload.material_rmxx);\n\timageStore(out_emissive, pix, payload.emissive);\n\timageStore(out_geometry_prev_position, pix, payload.prev_pos_t);\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary.rchit",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_tracing: require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\nlayout(set = 0, binding = 6) uniform sampler2D textures[MAX_TEXTURES];\nlayout(set = 0, binding = 7) uniform samplerCube skybox;\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_primary_common.glsl\"\n\nlayout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadInEXT RayPayloadPrimary payload;\nhitAttributeEXT vec2 bary;\n\n#include \"utils.glsl\"\n#include \"ray_kusochki.glsl\"\n#include \"color_spaces.glsl\"\n\n#include \"rt_geometry.glsl\"\n#include \"skybox.glsl\"\n\nvec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) {\n\treturn textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw);\n}\n\nvoid main() {\n\tGeometry geom = readHitGeometry(bary, ubo.ubo.ray_cone_width);\n\n\tpayload.hit_t = vec4(geom.pos, gl_HitTEXT);\n\tpayload.prev_pos_t = vec4(geom.prev_pos, 0.);\n\n\tconst Kusok kusok = getKusok(geom.kusok_index);\n\n\tif (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {\n\t\tpayload.emissive.rgb = sampleSkybox(gl_WorldRayDirectionEXT);\n\t\treturn;\n\t} else {\n\t\tconst vec4 color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;\n\t\tpayload.base_color_a = sampleTexture(kusok.material.tex_base_color, geom.uv, geom.uv_lods) * color;\n\t\tpayload.material_rmxx.r = sampleTexture(kusok.material.tex_roughness, geom.uv, geom.uv_lods).r * kusok.material.roughness;\n\t\tpayload.material_rmxx.g = sampleTexture(kusok.material.tex_metalness, geom.uv, geom.uv_lods).r * kusok.material.metalness;\n\n\t\tconst uint tex_normal = kusok.material.tex_normalmap;\n\t\tvec3 T = geom.tangent;\n\t\tif (tex_normal > 0 && dot(T,T) > .5) {\n\t\t\tT = normalize(T - dot(T, geom.normal_shading) * geom.normal_shading);\n\t\t\tconst vec3 B = normalize(cross(geom.normal_shading, T));\n\t\t\tconst mat3 TBN = mat3(T, B, geom.normal_shading);\n\t\t\tconst vec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.; // TODO is this sampling correct for normal data?\n\t\t\tgeom.normal_shading = normalize(TBN * tnorm);\n\t\t}\n\t}\n\n\tpayload.normals_gs.xy = normalEncode(geom.normal_geometry);\n\tpayload.normals_gs.zw = normalEncode(geom.normal_shading);\n\n#if 1\n\t// Real correct emissive color\n\t//payload.emissive.rgb = kusok.emissive;\n\tpayload.emissive.rgb = clamp(kusok.emissive / (1.0/3.0) / 25, 0, 1.5) * payload.base_color_a.rgb;\n#else\n\t// Fake texture color\n\tif (any(greaterThan(kusok.emissive, vec3(0.))))\n\t\tpayload.emissive.rgb = payload.base_color_a.rgb;\n#endif\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary.rgen",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_ray_tracing: require\n\n#include \"ray_primary_common.glsl\"\n\nlayout(set = 0, binding = 1) uniform accelerationStructureEXT tlas;\nlayout(set = 0, binding = 2) uniform UBO { UniformBuffer ubo; } ubo;\n\nlayout(set = 0, binding = 10, rgba8) uniform writeonly image2D out_base_color_a;\nlayout(set = 0, binding = 11, rgba32f) uniform writeonly image2D out_position_t;\nlayout(set = 0, binding = 12, rgba16f) uniform writeonly image2D out_normals_gs;\nlayout(set = 0, binding = 13, rgba8) uniform writeonly image2D out_material_rmxx;\nlayout(set = 0, binding = 14, rgba16f) uniform writeonly image2D out_emissive;\nlayout(set = 0, binding = 15, rgba32f) uniform writeonly image2D out_geometry_prev_position;\n\nlayout(location = PAYLOAD_LOCATION_PRIMARY) rayPayloadEXT RayPayloadPrimary payload;\n\nvoid main() {\n\tconst vec2 uv = (gl_LaunchIDEXT.xy + .5) / gl_LaunchSizeEXT.xy * 2. - 1.;\n\n\t// FIXME start on a near plane\n\tconst vec3 origin    = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz;\n\tconst vec4 target    = ubo.ubo.inv_proj * vec4(uv.x, uv.y, 1, 1);\n\tconst vec3 direction = normalize((ubo.ubo.inv_view * vec4(target.xyz, 0)).xyz);\n\n\tpayload.hit_t = vec4(0.);\n\tpayload.base_color_a = vec4(0.);\n\tpayload.normals_gs = vec4(0.);\n\tpayload.material_rmxx = vec4(0.);\n\tpayload.emissive = vec4(0.);\n\n\tconst uint flags = gl_RayFlagsCullFrontFacingTrianglesEXT;\n\tconst uint sbt_offset = 0;\n\tconst uint sbt_stride = 0;\n\tconst float L = 10000.; // TODO Why 10k?\n\ttraceRayEXT(tlas, flags, GEOMETRY_BIT_OPAQUE, // | GEOMETRY_BIT_REFRACTIVE,\n\t\tsbt_offset, sbt_stride, SHADER_OFFSET_MISS_REGULAR,\n\t\torigin, 0., direction, L,\n\t\tPAYLOAD_LOCATION_PRIMARY);\n\n\timageStore(out_position_t, ivec2(gl_LaunchIDEXT.xy), payload.hit_t);\n\timageStore(out_base_color_a, ivec2(gl_LaunchIDEXT.xy), payload.base_color_a);\n\timageStore(out_normals_gs, ivec2(gl_LaunchIDEXT.xy), payload.normals_gs);\n\timageStore(out_material_rmxx, ivec2(gl_LaunchIDEXT.xy), payload.material_rmxx);\n\timageStore(out_emissive, ivec2(gl_LaunchIDEXT.xy), payload.emissive);\n\timageStore(out_geometry_prev_position, ivec2(gl_LaunchIDEXT.xy), payload.prev_pos_t);\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary.rmiss",
    "content": "#version 460 core\n#extension GL_EXT_ray_tracing: require\n\nvoid main() {}\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary_common.glsl",
    "content": "#ifndef RAY_PRIMARY_COMMON_GLSL_INCLUDED\n#define RAY_PRIMARY_COMMON_GLSL_INCLUDED\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nstruct RayPayloadPrimary {\n\tvec4 hit_t;\n\tvec4 prev_pos_t;\n\tvec4 base_color_a;\n\tvec4 normals_gs;\n\tvec4 material_rmxx;\n\tvec4 emissive;\n};\n\n#define PAYLOAD_LOCATION_PRIMARY 0\n\n#endif //ifndef RAY_PRIMARY_COMMON_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/ray_primary_hit.glsl",
    "content": "#ifndef RAY_PRIMARY_HIT_GLSL_INCLUDED\n#define RAY_PRIMARY_HIT_GLSL_INCLUDED\n#extension GL_EXT_nonuniform_qualifier : enable\n\n#include \"debug.glsl\"\n#include \"utils.glsl\"\n#include \"ray_primary_common.glsl\"\n#include \"ray_kusochki.glsl\"\n#include \"rt_geometry.glsl\"\n#include \"color_spaces.glsl\"\n#include \"skybox.glsl\"\n\n#include \"noise.glsl\" // for DEBUG_DISPLAY_SURFHASH\n\nvec4 sampleTexture(uint tex_index, vec2 uv, vec4 uv_lods) {\n#ifndef RAY_BOUNCE\n\treturn textureGrad(textures[nonuniformEXT(tex_index)], uv, uv_lods.xy, uv_lods.zw);\n#else\n\treturn textureLod(textures[nonuniformEXT(tex_index)], uv, 2.);\n#endif\n}\n\nvoid primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {\n\tGeometry geom = readHitGeometry(rq, ubo.ubo.ray_cone_width, rayQueryGetIntersectionBarycentricsEXT(rq, true));\n\tconst float hitT = rayQueryGetIntersectionTEXT(rq, true);  //gl_HitTEXT;\n\tconst vec3 rayDirection = rayQueryGetWorldRayDirectionEXT(rq); //gl_WorldRayDirectionEXT\n\tpayload.hit_t = vec4(geom.pos, hitT);\n\tpayload.prev_pos_t = vec4(geom.prev_pos, 0.);\n\n\tconst Kusok kusok = getKusok(geom.kusok_index);\n\tconst Material material = kusok.material;\n\n\tif (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {\n\t\t// Mark as non-geometry\n\t\tpayload.hit_t.w = -payload.hit_t.w;\n\t\tpayload.emissive.rgb = sampleSkybox(rayDirection);\n\t\treturn;\n\t} else {\n\t\tpayload.base_color_a = sampleTexture(material.tex_base_color, geom.uv, geom.uv_lods);\n\t\tpayload.material_rmxx.r = sampleTexture(material.tex_roughness, geom.uv, geom.uv_lods).r * material.roughness;\n\t\tpayload.material_rmxx.g = sampleTexture(material.tex_metalness, geom.uv, geom.uv_lods).r * material.metalness;\n\n#ifndef RAY_BOUNCE\n\t\tconst uint tex_normal = material.tex_normalmap;\n\t\tvec3 T = geom.tangent;\n\t\tif (tex_normal > 0 && dot(T,T) > .5) {\n\t\t\tT = normalize(T - dot(T, geom.normal_shading) * geom.normal_shading);\n\t\t\tconst vec3 B = normalize(cross(geom.normal_shading, T));\n\t\t\tconst mat3 TBN = mat3(T, B, geom.normal_shading);\n\n// Get to KTX2 normal maps eventually\n//#define KTX2\n#ifdef KTX2\n// We expect KTX2 normalmaps to have only 2 SNORM components.\n// TODO: BC6H only can do signed or unsigned 16-bit floats. It can't normalize them on its own. So we either deal with\n// sub-par 10bit precision for <1 values. Or do normalization manually in shader. Manual normalization implies prepa-\n// ring normalmaps in a special way, i.e. scaling vector components to full f16 scale.\n#define NORMALMAP_SNORM\n#define NORMALMAP_2COMP\n#endif\n\n#ifdef NORMALMAP_SNORM // [-1..1]\n\t\t\t// TODO is this sampling correct for normal data?\n\t\t\tvec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz;\n#else // Older UNORM [0..1]\n\t\t\tvec3 tnorm = sampleTexture(tex_normal, geom.uv, geom.uv_lods).xyz * 2. - 1.;\n#endif\n\n#ifndef NORMALMAP_2COMP\n\t\t\t// Older 8-bit PNG suffers from quantization.\n\t\t\t// Smoothen quantization by normalizing it\n\t\t\ttnorm = normalize(tnorm);\n#endif\n\n\t\t\ttnorm.xy *= material.normal_scale;\n\n\t\t\t// Restore z based on scaled xy\n\t\t\ttnorm.z = sqrt(max(0., 1. - dot(tnorm.xy, tnorm.xy)));\n\n\t\t\tgeom.normal_shading = normalize(TBN * tnorm);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\t\tif (IS_INVALIDV(geom.normal_shading)) {\n\t\t\t\tdebugPrintfEXT(\"ray_primary_hit.glsl:%d geom.tangent=(%f,%f,%f) T=(%f,%f,%f) nscale=%f tnorm=(%f,%f,%f) INVALID nshade=(%f,%f,%f)\",\n\t\t\t\t\t__LINE__,\n\t\t\t\t\tPRIVEC3(geom.tangent),\n\t\t\t\t\tPRIVEC3(T),\n\t\t\t\t\tmaterial.normal_scale,\n\t\t\t\t\tPRIVEC3(tnorm),\n\t\t\t\t\tPRIVEC3(geom.normal_shading)\n\t\t\t\t);\n\t\t\t\t// TODO ???\n\t\t\t\tgeom.normal_shading = geom.normal_geometry;\n\t\t\t}\n#endif\n\t\t}\n#endif\n\t}\n\n\tpayload.normals_gs.xy = normalEncode(geom.normal_geometry);\n\tpayload.normals_gs.zw = normalEncode(geom.normal_shading);\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(payload.normals_gs)) {\n\t\tdebugPrintfEXT(\"ngeom=(%f,%f,%f) nshade=(%f,%f,%f) INVALID normals_gs=(%f,%f,%f,%f)\",\n\t\t\tPRIVEC3(geom.normal_geometry),\n\t\t\tPRIVEC3(geom.normal_shading),\n\t\t\tPRIVEC4(payload.normals_gs));\n\t}\n#endif\n\n#if 1\n\t// Real correct emissive color\n\t//payload.emissive.rgb = kusok.emissive;\n\t//payload.emissive.rgb = kusok.emissive * SRGBtoLINEAR(payload.base_color_a.rgb);\n\t//payload.emissive.rgb = clamp((kusok.emissive * (1.0/3.0) / 20), 0, 1.0) * SRGBtoLINEAR(payload.base_color_a.rgb);\n\t//payload.emissive.rgb = (sqrt(sqrt(kusok.emissive)) * (1.0/3.0)) * SRGBtoLINEAR(payload.base_color_a.rgb);\n\tpayload.emissive.rgb = (sqrt(kusok.emissive) / 8) * payload.base_color_a.rgb;\n\t//payload.emissive.rgb = kusok.emissive * payload.base_color_a.rgb;\n#else\n\t// Fake texture color\n\tif (any(greaterThan(kusok.emissive, vec3(0.))))\n\t\tpayload.emissive.rgb *= payload.base_color_a.rgb;\n#endif\n\n\tconst int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, true);\n\tconst ModelHeader model = getModelHeader(model_index);\n\tconst vec4 color = model.color * kusok.material.base_color;\n\n\tpayload.base_color_a *= color;\n\tpayload.emissive.rgb *= color.rgb;\n\n\t// α-masked materials leak non-1 alpha values to bounces, leading to weird translucent edges, see\n\t// https://github.com/w23/xash3d-fwgs/issues/721\n\t// Non-translucent materials should be fully opaque\n\tif (model.mode != MATERIAL_MODE_TRANSLUCENT)\n\t\tpayload.base_color_a.a = 1.;\n\n\tif ((ubo.ubo.debug_flags & DEBUG_FLAG_WHITE_FURNACE) != 0) {\n\t\t// White furnace mode: everything is diffuse and white\n\t\tpayload.base_color_a.rgb = vec3(1.);\n\t\tpayload.emissive.rgb = vec3(0.);\n\t\tpayload.material_rmxx.rg = vec2(1., 0.);\n\t}\n\n\tif (ubo.ubo.debug_display_only == DEBUG_DISPLAY_DISABLED) {\n\t\t// Nop\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_SURFHASH) {\n\t\tconst uint hash = xxhash32(geom.kusok_index);\n\t\tpayload.emissive.rgb = vec3(0xff & (hash>>16), 0xff & (hash>>8), 0xff & hash) / 255.;\n\t} else if (ubo.ubo.debug_display_only == DEBUG_DISPLAY_TRIHASH) {\n\t\tconst int primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);\n\t\tconst uint hash = xxhash32(geom.kusok_index + primitive_index * 2246822519U);\n\t\tpayload.emissive.rgb = vec3(0xff & (hash>>16), 0xff & (hash>>8), 0xff & hash) / 255.;\n\t}\n}\n\n#endif // ifndef RAY_PRIMARY_HIT_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/ray_shadow.rchit",
    "content": "#version 460 core\n#extension GL_EXT_ray_tracing: require\n#extension GL_EXT_shader_16bit_storage : require\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\nlayout(set = 0, binding = 30, std430) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;\nlayout(set = 0, binding = 31, std430) readonly buffer Kusochki { Kusok a[]; } kusochki;\nlayout(set = 0, binding = 32, std430) readonly buffer Indices { uint16_t a[]; } indices;\nlayout(set = 0, binding = 33, std430) readonly buffer Vertices { Vertex a[]; } vertices;\n\n#include \"ray_kusochki.glsl\"\n#include \"ray_common.glsl\"\n\nlayout(location = PAYLOAD_LOCATION_SHADOW) rayPayloadInEXT RayPayloadShadow payload_shadow;\n\nvoid main() {\n\tconst int instance_kusochki_offset = gl_InstanceCustomIndexEXT;\n\tconst int kusok_index = instance_kusochki_offset + gl_GeometryIndexEXT;\n\tconst Kusok kusok = getKusok(kusok_index);\n\tconst uint tex_base_color = kusok.material.tex_base_color;\n\n\tpayload_shadow.hit_type = (kusok.material.tex_base_color != TEX_BASE_SKYBOX) ? SHADOW_HIT : SHADOW_SKY ;\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_shadow.rmiss",
    "content": "#version 460 core\n#extension GL_EXT_ray_tracing: require\n\n#include \"ray_shadow_interface.glsl\"\n\nlayout(location = 0) rayPayloadInEXT RayPayloadShadow payload_shadow;\n\nvoid main() {\n    payload_shadow.hit_type = SHADOW_MISS;\n}\n"
  },
  {
    "path": "ref/vk/shaders/ray_shadow_interface.glsl",
    "content": "#ifndef RAY_SHADOW_INTERFACE_GLSL_INCLUDED\n#define RAY_SHADOW_INTERFACE_GLSL_INCLUDED\n\n#define SHADOW_MISS 0\n#define SHADOW_HIT 1\n#define SHADOW_SKY 2\n\nstruct RayPayloadShadow {\n\tuint hit_type;\n};\n\n#endif //ifndef RAY_SHADOW_INTERFACE_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/rt.json",
    "content": "{\n\t\"primary_ray\": {\n\t\t/*\n\t\t\"rgen\": \"ray_primary\",\n\t\t\"miss\": [\n\t\t\t\"ray_primary\"\n\t\t],\n\t\t\"hit\": [\n\t\t\t{\"closest\": \"ray_primary\"},\n\t\t\t{\"closest\": \"ray_primary\", \"any\": \"ray_common_alphatest\"}\n\t\t]\n\t\t*/\n\t\t\"comp\": \"ray_primary\"\n\t},\n//\t\"light_direct\": {\n//\t\t\"template\": true,\n//\t\t\"miss\": [\n//\t\t\t\"ray_shadow\"\n//\t\t],\n//\t\t\"hit\": [\n//\t\t\t{\"closest\": \"ray_shadow\", \"any\": \"ray_common_alphatest\"}\n//\t\t]\n//\t},\n//\t\"light_direct_poly\": {\n//\t\t\"inherit\": \"light_direct\",\n//\t\t\"rgen\": \"ray_light_poly_direct\"\n//\t},\n//\t\"light_direct_point\": {\n//\t\t\"inherit\": \"light_direct\",\n//\t\t\"rgen\": \"ray_light_direct_point\"\n//\t},\n\t\"light_direct_poly\": {\n\t\t\"comp\": \"ray_light_direct_poly\"\n\t},\n\t\"light_direct_point\": {\n\t\t\"comp\": \"ray_light_direct_point\"\n\t},\n\t\"bounce\": {\n\t\t\"comp\": \"bounce\"\n\t},\n\t\"indiff_at1\": {\n\t\t\"comp\": \"indirect_diffuse_atrous1\"\n\t},\n\t\"indiff_sh_init\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_init\"\n\t},\n\t\"indiff_sh_pass1\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_pass_1\"\n\t},\n\t\"indiff_sh_pass2\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_pass_2\"\n\t},\n\t\"indiff_sh_pass3\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_pass_3\"\n\t},\n\t\"indiff_sh_pass4\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_pass_4\"\n\t},\n\t\"indiff_sh_pass5\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_pass_5\"\n\t},\n\t\"indiff_sh_save\": {\n\t\t\"comp\": \"diffuse_gi_sh_denoise_save\"\n\t},\n\t\"spatial_reconstruction_pass1\": {\n\t\t\"comp\": \"spatial_reconstruction_pass1\"\n\t},\n\t\"spatial_reconstruction_pass2\": {\n\t\t\"comp\": \"spatial_reconstruction_pass2\"\n\t},\n\t\"denoiser\": {\n\t\t\"comp\": \"denoiser\"\n\t},\n\t//\"RESOURCES\": { \"position_t_prev\": { \"previous_frame\": \"position_t\" }, },\n}\n"
  },
  {
    "path": "ref/vk/shaders/rt_geometry.glsl",
    "content": "#ifndef RT_GEOMETRY_GLSL_INCLUDED\n#define RT_GEOMETRY_GLSL_INCLUDED\n\n#include \"debug.glsl\"\n#include \"utils.glsl\"\n#include \"color_spaces.glsl\"\n\n// Taken from Journal of Computer Graphics Techniques, Vol. 10, No. 1, 2021.\n// Improved Shader and Texture Level of Detail Using Ray Cones,\n// by T. Akenine-Moller, C. Crassin, J. Boksansky, L. Belcour, A. Panteleev, and O. Wright\n// https://jcgt.org/published/0010/01/01/\n// P is the intersection point\n// f is the triangle normal\n// d is the ray cone direction\nvec4 computeAnisotropicEllipseAxes(in vec3 P, in vec3 f,\n\tin vec3 d, in float rayConeRadiusAtIntersection,\n\tin vec3 positions[3], in vec2 txcoords[3],\n\tin vec2 interpolatedTexCoordsAtIntersection)\n{\n\tvec4 texGradient;\n\t// Compute ellipse axes.\n\tvec3 a1 = d - dot(f, d) * f;\n\tvec3 p1 = a1 - dot(d, a1) * d;\n\ta1 *= rayConeRadiusAtIntersection / max(0.0001, length(p1));\n\tvec3 a2 = cross(f, a1);\n\tvec3 p2 = a2 - dot(d, a2) * d;\n\ta2 *= rayConeRadiusAtIntersection / max(0.0001, length(p2));\n\t// Compute texture coordinate gradients.\n\tvec3 eP, delta = P - positions[0];\n\tvec3 e1 = positions[1] - positions[0];\n\tvec3 e2 = positions[2] - positions[0];\n\tfloat oneOverAreaTriangle = 1.0 / dot(f, cross(e1, e2));\n\teP = delta + a1;\n\tfloat u1 = dot(f, cross(eP, e2)) * oneOverAreaTriangle;\n\tfloat v1 = dot(f, cross(e1, eP)) * oneOverAreaTriangle;\n\ttexGradient.xy = (1.0-u1-v1) * txcoords[0] + u1 * txcoords[1] +\n\tv1 * txcoords[2] - interpolatedTexCoordsAtIntersection;\n\teP = delta + a2;\n\tfloat u2 = dot(f, cross(eP, e2)) * oneOverAreaTriangle;\n\tfloat v2 = dot(f, cross(e1, eP)) * oneOverAreaTriangle;\n\ttexGradient.zw = (1.0-u2-v2) * txcoords[0] + u2 * txcoords[1] +\n\tv2 * txcoords[2] - interpolatedTexCoordsAtIntersection;\n\treturn texGradient;\n}\n\nstruct Geometry {\n\tvec3 pos;\n\tvec3 prev_pos;\n\n\tvec2 uv;\n\tvec4 uv_lods;\n\n\tvec3 normal_geometry;\n\tvec3 normal_shading;\n\tvec3 tangent;\n\n\tint kusok_index;\n};\n\n#ifdef RAY_QUERY\nGeometry readHitGeometry(rayQueryEXT rq, float ray_cone_width, vec2 bary) {\n\tconst int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, true);\n\tconst int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true);\n\tconst int geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true);\n\tconst int primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);\n\tconst mat4x3 objectToWorld = rayQueryGetIntersectionObjectToWorldEXT(rq, true);\n\tconst vec3 ray_direction = rayQueryGetWorldRayDirectionEXT(rq);\n\tconst float hit_t = rayQueryGetIntersectionTEXT(rq, true);\n#else\nGeometry readHitGeometry(vec2 bary, float ray_cone_width) {\n\tconst int model_index = gl_InstanceID;\n\tconst int instance_kusochki_offset = gl_InstanceCustomIndexEXT;\n\tconst int geometry_index = gl_GeometryIndexEXT;\n\tconst int primitive_index = gl_PrimitiveID;\n\tconst mat4x3 objectToWorld = gl_ObjectToWorldEXT;\n\tconst vec3 ray_direction = gl_WorldRayDirectionEXT;\n\tconst float hit_t = gl_HitTEXT;\n#endif\n\n\tGeometry geom;\n\n\tgeom.kusok_index = instance_kusochki_offset + geometry_index;\n\tconst Kusok kusok = getKusok(geom.kusok_index);\n\n\tconst uint first_index_offset = kusok.index_offset + primitive_index * 3;\n\tconst uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset;\n\tconst uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset;\n\tconst uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset;\n\n\tconst vec3 pos[3] = {\n\t\tobjectToWorld * vec4(GET_VERTEX(vi1).pos, 1.f),\n\t\tobjectToWorld * vec4(GET_VERTEX(vi2).pos, 1.f),\n\t\tobjectToWorld * vec4(GET_VERTEX(vi3).pos, 1.f),\n\t};\n\n\tconst ModelHeader model = getModelHeader(model_index);\n\tconst vec3 prev_pos[3] = {\n\t\t(model.prev_transform * vec4(GET_VERTEX(vi1).prev_pos, 1.f)).xyz,\n\t\t(model.prev_transform * vec4(GET_VERTEX(vi2).prev_pos, 1.f)).xyz,\n\t\t(model.prev_transform * vec4(GET_VERTEX(vi3).prev_pos, 1.f)).xyz,\n\t};\n\n\tconst vec2 uvs[3] = {\n\t\tGET_VERTEX(vi1).gl_tc,\n\t\tGET_VERTEX(vi2).gl_tc,\n\t\tGET_VERTEX(vi3).gl_tc,\n\t};\n\n\tgeom.pos = baryMix(pos[0], pos[1], pos[2], bary);\n\tgeom.prev_pos = baryMix(prev_pos[0], prev_pos[1], prev_pos[2], bary);\n\tgeom.uv = baryMix(uvs[0], uvs[1], uvs[2], bary);\n\t//TODO or not TODO? const vec2 texture_uv = texture_uv_stationary + push_constants.time * kusok.uv_speed;\n\n\t// NOTE: need to flip if back-facing\n\tgeom.normal_geometry = normalize(cross(pos[2]-pos[0], pos[1]-pos[0]));\n\n\t// NOTE: only support rotations, for arbitrary transform would need to do transpose(inverse(mat3(gl_ObjectToWorldEXT)))\n\tconst mat3 normalTransform = mat3(objectToWorld); // mat3(gl_ObjectToWorldEXT);\n\tgeom.normal_shading = normalize(normalTransform * baryMix(\n\t\tGET_VERTEX(vi1).normal,\n\t\tGET_VERTEX(vi2).normal,\n\t\tGET_VERTEX(vi3).normal,\n\t\tbary));\n\n#ifdef DEBUG_VALIDATE_EXTRA\n\tif (IS_INVALIDV(geom.normal_shading)) {\n\t\tdebugPrintfEXT(\"rt_geometry.glsl:%d model=%d geom=%d prim=%d v1n=(%f,%f,%f) v2n=(%f,%f,%f) v3n=(%f,%f,%f) nt=(%f,%f,%f;%f,%f,%f;%f,%f,%f) INVALID nshade=(%f,%f,%f)\",\n\t\t\t__LINE__,\n\t\t\tmodel_index, geometry_index, primitive_index,\n\t\t\tPRIVEC3(GET_VERTEX(vi1).normal),\n\t\t\tPRIVEC3(GET_VERTEX(vi2).normal),\n\t\t\tPRIVEC3(GET_VERTEX(vi3).normal),\n\t\t\tPRIVEC3(normalTransform[0]),\n\t\t\tPRIVEC3(normalTransform[1]),\n\t\t\tPRIVEC3(normalTransform[2])\n\t\t);\n\t\t// TODO ???\n\t\tgeom.normal_shading = geom.normal_geometry;\n\t}\n#endif\n\n\tgeom.tangent = normalize(normalTransform * baryMix(\n\t\tGET_VERTEX(vi1).tangent,\n\t\tGET_VERTEX(vi2).tangent,\n\t\tGET_VERTEX(vi3).tangent,\n\t\tbary));\n\n\tif (IS_INVALIDV(geom.normal_geometry)) {\n\t\t// This shouldn't happen -- such triangles are pre-filtere-out in vk_brush.c\n#ifdef DEBUG_VALIDATE_EXTRA\n\t\tconst vec3 e0 = pos[2] - pos[0];\n\t\tconst vec3 e1 = pos[1] - pos[0];\n\t\tdebugPrintfEXT(\"readHitGeometry(model=%d geom=%d prim=%d) pos[0]=(%f,%f,%f) e0=(%f,%f,%f), e1=(%f,%f,%f) INVALID normal_geometry=(%f,%f,%f)\",\n\t\t\tmodel_index, geometry_index, primitive_index,\n\t\t\tPRIVEC3(pos[0]), PRIVEC3(e0), PRIVEC3(e1), PRIVEC3(geom.normal_geometry));\n#endif\n\t\t// TODO is this correct?\n\t\tgeom.normal_geometry = geom.normal_shading;\n\t}\n\n\tgeom.uv_lods = computeAnisotropicEllipseAxes(geom.pos, geom.normal_geometry, ray_direction, ray_cone_width * hit_t, pos, uvs, geom.uv);\n\n\treturn geom;\n}\n\n#ifdef RAY_QUERY\nstruct MiniGeometry {\n\tvec2 uv;\n\tuint kusok_index;\n\tvec4 vertex_color_srgb;\n};\n\nMiniGeometry readCandidateMiniGeometry(rayQueryEXT rq) {\n\t\tconst uint instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, false);\n\t\tconst uint geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, false);\n\t\tconst uint kusok_index = instance_kusochki_offset + geometry_index;\n\t\tconst Kusok kusok = getKusok(kusok_index);\n\n\t\tconst uint primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, false);\n\t\tconst uint first_index_offset = kusok.index_offset + primitive_index * 3;\n\t\tconst uint vi1 = uint(getIndex(first_index_offset+0)) + kusok.vertex_offset;\n\t\tconst uint vi2 = uint(getIndex(first_index_offset+1)) + kusok.vertex_offset;\n\t\tconst uint vi3 = uint(getIndex(first_index_offset+2)) + kusok.vertex_offset;\n\t\tconst vec2 uvs[3] = {\n\t\t\tGET_VERTEX(vi1).gl_tc,\n\t\t\tGET_VERTEX(vi2).gl_tc,\n\t\t\tGET_VERTEX(vi3).gl_tc,\n\t\t};\n\t\tconst vec2 bary = rayQueryGetIntersectionBarycentricsEXT(rq, false);\n\t\tconst vec2 uv = baryMix(uvs[0], uvs[1], uvs[2], bary);\n\n\t\t/*\n\t\tconst vec4 colors[3] = {\n\t\t\tSRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi1).color)),\n\t\t\tSRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi2).color)),\n\t\t\tSRGBtoLINEAR(unpackUnorm4x8(GET_VERTEX(vi3).color)),\n\t\t};\n\t\t*/\n\t\tconst vec4 colors_srgb[3] = {\n\t\t\tunpackUnorm4x8(GET_VERTEX(vi1).color),\n\t\t\tunpackUnorm4x8(GET_VERTEX(vi2).color),\n\t\t\tunpackUnorm4x8(GET_VERTEX(vi3).color),\n\t\t};\n\n\t\tMiniGeometry ret;\n\t\tret.uv = uv;\n\t\tret.kusok_index = kusok_index;\n\t\tret.vertex_color_srgb = baryMix(colors_srgb[0], colors_srgb[1], colors_srgb[2], bary);\n\t\treturn ret;\n}\n#endif // #ifdef RAY_QUERY\n\n#endif // RT_GEOMETRY_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/sky.frag",
    "content": "#version 450\n\nlayout(set = 0, binding = 0) uniform UBO {\n\tmat4 mvp;\n\tmat4 inv_proj;\n\tmat4 inv_view;\n\tvec2 resolution;\n} ubo;\n\nlayout(set = 0, binding = 1) uniform samplerCube skybox;\n\nlayout(location = 0) out vec4 out_color;\n\nvec3 clipToWorldSpace(vec3 clip) {\n\tconst vec4 eye_space = ubo.inv_proj * vec4(clip, 1.);\n\treturn (ubo.inv_view * vec4(eye_space.xyz / eye_space.w, 1.)).xyz;\n}\n\nvec3 getDirection(in vec2 uv) {\n\tuv = uv * 2. - 1.;\n\tconst vec3 world_near = clipToWorldSpace(vec3(uv, 0.));\n\tconst vec3 world_far = clipToWorldSpace(vec3(uv, 1.));\n\treturn normalize(world_far - world_near);\n}\n\nvoid main() {\n\tconst vec2 uv = gl_FragCoord.xy / ubo.resolution;\n\tconst vec3 direction = getDirection(uv);\n\tout_color = texture(skybox, direction);\n}\n"
  },
  {
    "path": "ref/vk/shaders/sky.vert",
    "content": "#version 450\n\nlayout(set=0,binding=0) uniform UBO {\n\tmat4 mvp;\n\tmat4 inv_proj;\n\tmat4 inv_view;\n\tvec2 resolution;\n} ubo;\n\nlayout(location=0) in vec3 a_pos;\n\nvoid main() {\n\tgl_Position = ubo.mvp * vec4(a_pos.xyz, 1.);\n}\n"
  },
  {
    "path": "ref/vk/shaders/skybox.glsl",
    "content": "#ifndef SKYBOX_GLSL_INCLUDED\n#define SKYBOX_GLSL_INCLUDED\n\nvec3 sampleSkybox(vec3 direction) {\n\tif ((ubo.ubo.debug_flags & DEBUG_FLAG_WHITE_FURNACE) == 0) {\n\t\treturn texture(skybox, direction).rgb * ubo.ubo.skybox_exposure;\n\t} else {\n\t\treturn vec3(1.);\n\t}\n}\n\n#endif // #ifndef SKYBOX_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/spatial_reconstruction.glsl",
    "content": "// originally implemented by Mikhail Gorobets for Diligent Engine\n// https://github.com/DiligentGraphics/DiligentEngine\n\n#ifndef SPATIAL_RECONSTRUCTION_RADIUS\n#define SPATIAL_RECONSTRUCTION_RADIUS 7.\n#endif\n\n#ifndef SPECULAR_INPUT_IMAGE\n#define SPECULAR_INPUT_IMAGE indirect_specular\n#endif\n\n#ifndef SPECULAR_OUTPUT_IMAGE\n#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed\n#endif\n\n#include \"debug.glsl\"\n\n#define SPECULAR_CLAMPING_MAX 1.2\n#define SPATIAL_RECONSTRUCTION_SAMPLES 8\n#define SPATIAL_RECONSTRUCTION_ROUGHNESS_FACTOR 5.\n#define SPATIAL_RECONSTRUCTION_SIGMA 0.9\n#define INDIRECT_SCALE 2\n\n#define GLSL\n#include \"ray_interop.h\"\n#undef GLSL\n\n#define RAY_BOUNCE\n#define RAY_QUERY\nlayout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;\n\nlayout(set = 0, binding = 0, rgba16f) uniform image2D SPECULAR_OUTPUT_IMAGE;\n\nlayout(set = 0, binding = 1, rgba32f) uniform readonly image2D position_t;\nlayout(set = 0, binding = 2, rgba16f) uniform readonly image2D normals_gs;\nlayout(set = 0, binding = 3, rgba8) uniform readonly image2D material_rmxx;\nlayout(set = 0, binding = 4, rgba16f) uniform readonly image2D SPECULAR_INPUT_IMAGE;\nlayout(set = 0, binding = 5, rgba32f) uniform readonly image2D reflection_direction_pdf;\n\nlayout(set = 0, binding = 6) uniform UBO { UniformBuffer ubo; } ubo;\n\n#include \"utils.glsl\"\n#include \"noise.glsl\"\n#include \"brdf.glsl\"\n\n#ifndef PI\n\t#define PI 3.14 // FIXME please\n#endif\n\nvoid readNormals(ivec2 uv, out vec3 geometry_normal, out vec3 shading_normal) {\n\tconst vec4 n = imageLoad(normals_gs, uv);\n\tgeometry_normal = normalDecode(n.xy);\n\tshading_normal = normalDecode(n.zw);\n}\n\nstruct PixelAreaStatistic {\n\tfloat mean;\n\tfloat variance;\n\tfloat weightSum;\n\tvec4 colorSum;\n};\n\nfloat computeGaussianWeight(float texelDistance) {\n\treturn exp(-0.66 * texelDistance * texelDistance); // assuming texelDistance is normalized to 1\n}\n\n// Visibility = G2(v,l,a) / (4 * (n,v) * (n,l))\n// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf\nfloat smithGGXVisibilityCorrelated(float NdotL, float NdotV, float alphaRoughness) {\n\t// G1 (masking) is % microfacets visible in 1 direction\n\t// G2 (shadow-masking) is % microfacets visible in 2 directions\n\t// If uncorrelated:\n\t//\tG2(NdotL, NdotV) = G1(NdotL) * G1(NdotV)\n\t//\tLess realistic as higher points are more likely visible to both L and V\n\t//\n\t// https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdc2017/Presentations/Hammon_Earl_PBR_Diffuse_Lighting.pdf\n\n\tfloat a2 = alphaRoughness * alphaRoughness;\n\n\tfloat GGXV = NdotL * sqrt(max(NdotV * NdotV * (1.0 - a2) + a2, 1e-7));\n\tfloat GGXL = NdotV * sqrt(max(NdotL * NdotL * (1.0 - a2) + a2, 1e-7));\n\n\treturn 0.5 / (GGXV + GGXL);\n}\n\n// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())\n// Implementation from \"Average Irregularity Representation of a Roughened Surface for Ray Reflection\" by T. S. Trowbridge, and K. P. Reitz\n// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games, Equation 3.\nfloat normalDistribution_GGX(float NdotH, float alphaRoughness) {\n\t// \"Sampling the GGX Distribution of Visible Normals\" (2018) by Eric Heitz - eq. (1)\n\t// https://jcgt.org/published/0007/04/01/\n\n\t// Make sure we reasonably handle alphaRoughness == 0\n\t// (which corresponds to delta function)\n\talphaRoughness = max(alphaRoughness, 1e-3);\n\n\tfloat a2  = alphaRoughness * alphaRoughness;\n\tfloat nh2 = NdotH * NdotH;\n\tfloat f   = nh2 * a2 + (1.0 - nh2);\n\treturn a2 / max(PI * f * f, 1e-9);\n}\n\nvec2 computeWeightRayLength(ivec2 pix, vec3 V, vec3 N, float roughness, float NdotV, float weight) {\n\tvec4 rayDirectionPDF = imageLoad(reflection_direction_pdf, pix);\n\tfloat rayLength = length(rayDirectionPDF.xyz);\n\tvec3 rayDirection = normalize(rayDirectionPDF.xyz);\n\tfloat PDF = rayDirectionPDF.w;\n\tfloat alphaRoughness = roughness * roughness;\n\n\tvec3 L = rayDirection;\n\tvec3 H = normalize(L + V);\n\n\tfloat NdotH = saturate(dot(N, H));\n\tfloat NdotL = saturate(dot(N, L));\n\n\tfloat vis = smithGGXVisibilityCorrelated(NdotL, NdotV, alphaRoughness);\n\tfloat D = normalDistribution_GGX(NdotH, alphaRoughness);\n\tfloat localBRDF = vis * D * NdotL;\n\tlocalBRDF *= computeGaussianWeight(weight);\n\tfloat rcpRayLength = rayLength == 0. ? 0. : 1. / rayLength;\n\treturn vec2(max(localBRDF / max(PDF, 1.0e-5f), 1e-6), rcpRayLength);\n}\n\n// Weighted incremental variance\n// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance\nvoid computeWeightedVariance(inout PixelAreaStatistic stat, vec3 sampleColor, float weight) {\n\tstat.colorSum.xyz += weight * sampleColor;\n\tstat.weightSum += weight;\n\n\tfloat value = luminance(sampleColor.rgb);\n\tfloat prevMean = stat.mean;\n\n\tfloat rcpWeightSum = stat.weightSum == 0. ? 0. : 1. / stat.weightSum;\n\n\tstat.mean += weight * rcpWeightSum * (value - prevMean);\n\tstat.variance += weight * (value - prevMean) * (value - stat.mean);\n}\n\nfloat computeResolvedDepth(vec3 origin, vec3 position, float surfaceHitDistance) {\n\treturn distance(origin, position) + surfaceHitDistance;\n}\n\nivec2 clampScreenCoord(ivec2 pix, ivec2 res) {\n\treturn max(ivec2(0), min(ivec2(res - 1), pix));\n}\n\n\nfloat computeSpatialWeight(float texelDistance, float sigma) {\n\treturn exp(-(texelDistance) / (2.0 * sigma * sigma));\n}\n\nvec3 clampSpecular(vec3 specular, float maxLuminace) {\n\tfloat lum = luminance(specular);\n\tif (lum == 0.)\n\t\treturn vec3(0.);\n\n\tfloat clamped = min(maxLuminace, lum);\n\treturn specular * (clamped / lum);\n}\n\nvoid main() {\n\tconst ivec2 pix = ivec2(gl_GlobalInvocationID);\n\tconst ivec2 res = ubo.ubo.res / INDIRECT_SCALE;\n\tif (any(greaterThanEqual(pix, res))) {\n\t\treturn;\n\t}\n\n\tif ((ubo.ubo.renderer_flags & RENDERER_FLAG_SPATIAL_RECONSTRUCTION) == 0) {\n\t\timageStore(SPECULAR_OUTPUT_IMAGE, pix, imageLoad(SPECULAR_INPUT_IMAGE, pix));\n\t\treturn;\n\t}\n\n\tconst vec2 uv = (gl_GlobalInvocationID.xy + .5) / res * 2. - 1.;\n\t\n\tconst vec3 origin = (ubo.ubo.inv_view * vec4(0, 0, 0, 1)).xyz;\n\tconst vec3 position = imageLoad(position_t, pix * INDIRECT_SCALE).xyz;\n\n\t// samples = 8, min distance = 0.5, average samples on radius = 2\n\tvec3 poisson[SPATIAL_RECONSTRUCTION_SAMPLES];\n\tpoisson[0] = vec3(-0.4706069, -0.4427112, +0.6461146);\n\tpoisson[1] = vec3(-0.9057375, +0.3003471, +0.9542373);\n\tpoisson[2] = vec3(-0.3487388, +0.4037880, +0.5335386);\n\tpoisson[3] = vec3(+0.1023042, +0.6439373, +0.6520134);\n\tpoisson[4] = vec3(+0.5699277, +0.3513750, +0.6695386);\n\tpoisson[5] = vec3(+0.2939128, -0.1131226, +0.3149309);\n\tpoisson[6] = vec3(+0.7836658, -0.4208784, +0.8895339);\n\tpoisson[7] = vec3(+0.1564120, -0.8198990, +0.8346850);\n\n\tvec3 geometry_normal, shading_normal;\n\treadNormals(pix * INDIRECT_SCALE, geometry_normal, shading_normal);\n\n\tvec3 V = normalize(origin - position);\n\tfloat NdotV = saturate(dot(shading_normal, V));\n\n\tfloat roughness = imageLoad(material_rmxx, pix * INDIRECT_SCALE).x;\n\n\tfloat roughness_factor = saturate(float(SPATIAL_RECONSTRUCTION_ROUGHNESS_FACTOR) * roughness);\n\tfloat radius = mix(0.0, SPATIAL_RECONSTRUCTION_RADIUS, roughness_factor);\n\n\tPixelAreaStatistic pixelAreaStat;\n\tpixelAreaStat.colorSum = vec4(0.0, 0.0, 0.0, 0.0);\n\tpixelAreaStat.weightSum = 0.0;\n\tpixelAreaStat.variance = 0.0;\n\tpixelAreaStat.mean = 0.0;\n\n\tfloat nearestSurfaceHitDistance = 0.0;\n\n\tvec3 result_color = vec3(0.);\n\tfloat weights_sum = 0.;\n\n\t// TODO: Try to implement sampling from https://youtu.be/MyTOGHqyquU?t=1043\n\tfor (int i = 0; i < SPATIAL_RECONSTRUCTION_SAMPLES; i++)\n\t{\n\t\tivec2 p = max(ivec2(0), min(ivec2(res) - ivec2(1), ivec2(pix + radius * poisson[i].xy))); \n\n\t\tfloat weightS = computeSpatialWeight(poisson[i].z * poisson[i].z, SPATIAL_RECONSTRUCTION_SIGMA);\n\t\tvec2 weightLength = computeWeightRayLength(p, V, shading_normal, roughness, NdotV, weightS);\n\t\tvec3 sampleColor = clampSpecular(imageLoad(SPECULAR_INPUT_IMAGE, p).xyz, SPECULAR_CLAMPING_MAX);\n\t\tcomputeWeightedVariance(pixelAreaStat, sampleColor, weightLength.x);\n\n\t\tif (weightLength.x > 1.0e-6)\n\t\t\tnearestSurfaceHitDistance = max(weightLength.y, nearestSurfaceHitDistance);\n\n\t\tresult_color += sampleColor.xyz * weightLength.x;\n\t\tweights_sum += weightLength.x;\n\t}\n\n\tif (weights_sum > 0.) {\n\t\tresult_color /= weights_sum;\n\t}\n\n\tvec4 resolvedRadiance = pixelAreaStat.colorSum / max(pixelAreaStat.weightSum, 1e-6f);\n\tfloat resolvedVariance = pixelAreaStat.variance / max(pixelAreaStat.weightSum, 1e-6f);\n\tfloat resolvedDepth = computeResolvedDepth(origin, position, nearestSurfaceHitDistance);\n\n\timageStore(SPECULAR_OUTPUT_IMAGE, pix, vec4(resolvedRadiance.xyz, resolvedVariance));\n}\n"
  },
  {
    "path": "ref/vk/shaders/spatial_reconstruction_pass1.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#define SPATIAL_RECONSTRUCTION_RADIUS 11.\n#define SPECULAR_INPUT_IMAGE indirect_specular\n#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed_pass1\n\n#include \"spatial_reconstruction.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/spatial_reconstruction_pass2.comp",
    "content": "#version 460 core\n#extension GL_GOOGLE_include_directive : require\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_shader_16bit_storage : require\n#extension GL_EXT_ray_query: require\n\n#define SPATIAL_RECONSTRUCTION_RADIUS 5.\n#define SPECULAR_INPUT_IMAGE indirect_specular_reconstructed_pass1\n#define SPECULAR_OUTPUT_IMAGE out_indirect_specular_reconstructed\n\n#include \"spatial_reconstruction.glsl\"\n"
  },
  {
    "path": "ref/vk/shaders/spherical_harmonics.glsl",
    "content": "// Copypasted from Quake 2 RTX\n// https://github.com/NVIDIA/Q2RTX\n//\n// Original licence code:\n//\n//\tCopyright (C) 2018 Christoph Schied\n//\tCopyright (C) 2019, NVIDIA CORPORATION. All rights reserved.\n//\n//\tThis program is free software; you can redistribute it and/or modify\n//\tit under the terms of the GNU General Public License as published by\n//\tthe Free Software Foundation; either version 2 of the License, or\n//\t(at your option) any later version.\n//\n//\tThis program is distributed in the hope that it will be useful,\n//\tbut WITHOUT ANY WARRANTY; without even the implied warranty of\n//\tMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n//\tGNU General Public License for more details.\n//\n//\tYou should have received a copy of the GNU General Public License along\n//\twith this program; if not, write to the Free Software Foundation, Inc.,\n//\t51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n//\n\n#define STORAGE_SCALE_LF 1024.0\n\nstruct SH\n{\n    vec4 shY;\n    vec2 CoCg;\n};\n\nvec3 project_SH_irradiance(SH sh, vec3 N)\n{\n    float d = dot(sh.shY.xyz, N);\n    float Y = 2.0 * (1.023326 * d + 0.886226 * sh.shY.w);\n    Y = max(Y, 0.0);\n\n    sh.CoCg *= Y * 0.282095 / (sh.shY.w + 1e-6);\n\n    float   T       = Y - sh.CoCg.y * 0.5;\n    float   G       = sh.CoCg.y + T;\n    float   B       = T - sh.CoCg.x * 0.5;\n    float   R       = B + sh.CoCg.x;\n\n    return max(vec3(R, G, B), vec3(0.0));\n}\n\nSH irradiance_to_SH(vec3 color, vec3 dir)\n{\n    SH result;\n\n    float   Co      = color.r - color.b;\n    float   t       = color.b + Co * 0.5;\n    float   Cg      = color.g - t;\n    float   Y       = max(t + Cg * 0.5, 0.0);\n\n    result.CoCg = vec2(Co, Cg);\n\n    float   L00     = 0.282095;\n    float   L1_1    = 0.488603 * dir.y;\n    float   L10     = 0.488603 * dir.z;\n    float   L11     = 0.488603 * dir.x;\n\n    result.shY = vec4 (L11, L1_1, L10, L00) * Y;\n\n    return result;\n}\n\nvec3 SH_to_irradiance(SH sh)\n{\n    float   Y       = sh.shY.w / 0.282095;\n\n    float   T       = Y - sh.CoCg.y * 0.5;\n    float   G       = sh.CoCg.y + T;\n    float   B       = T - sh.CoCg.x * 0.5;\n    float   R       = B + sh.CoCg.x;\n\n    return max(vec3(R, G, B), vec3(0.0));\n}\n\nvoid accumulate_SH(inout SH accum, SH b, float scale)\n{\n    accum.shY += b.shY * scale;\n    accum.CoCg += b.CoCg * scale;\n}\n\nSH mix_SH(SH a, SH b, float s)\n{\n    SH result;\n    result.shY = mix(a.shY, b.shY, vec4(s));\n    result.CoCg = mix(a.CoCg, b.CoCg, vec2(s));\n    return result;\n}\n\nfloat fade_by_depth(float depthA, float depthB, float max_offset) {\n\treturn 1. - smoothstep(0., max_offset, abs(depthA - depthB));\n}\n"
  },
  {
    "path": "ref/vk/shaders/trace_decals.glsl",
    "content": "#ifndef TRACE_DECALS_GLSL_INCLUDED\n#define TRACE_DECALS_GLSL_INCLUDED\n\n#include \"debug.glsl\"\n\n// Traces geometry with decals.\n// Modifies RayPayloadPrimary by blending in decals found near the original surface.\n// Decals are sorted by kusok_index to ensure a stable blending order.\n// This is a copy-paste from traceLegacyBlending.comp.\nvoid traceDecals(inout RayPayloadPrimary payload) {\n\tconst float MAX_DECALS_DISTANCE = 0.25; // DECAL_DEPTH_OFFSET from vk_decals.c with additional distance\n\n\tconst vec3 geometry_normal = normalDecode(payload.normals_gs.xy);\n\n\t// trace from outside to polygon for right face culling\n\tconst vec3 pos = payload.hit_t.xyz + geometry_normal * MAX_DECALS_DISTANCE;\n\tconst vec3 dir = -geometry_normal;\n\tconst float L = MAX_DECALS_DISTANCE;\n\n\tstruct DecalEntry {\n\t\tuint id; // sorting by id for stable drawing order\n\t\tvec4 base_color_a;\n\t\tvec4 material_rmxx; // TODO: add also shading normal and emissive\n\t};\n\n\t// VGPR usage :FeelsBadMan:\n#define MAX_ENTRIES 8\n\tuint entries_count = 0;\n\tDecalEntry entries[MAX_ENTRIES];\n\n\trayQueryEXT rq;\n\tconst uint flags = 0\n\t\t| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t| gl_RayFlagsNoOpaqueEXT // force all to be non-opaque\n\t\t;\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_BLEND, pos, 0., dir, L);\n\twhile (rayQueryProceedEXT(rq)) {\n\t\tconst MiniGeometry geom = readCandidateMiniGeometry(rq);\n\t\tconst int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, false);\n\t\tconst ModelHeader model = getModelHeader(model_index);\n\n\t\tif (model.mode != MATERIAL_MODE_DECAL)\n\t\t\tcontinue;\n\n\t\tconst Kusok kusok = getKusok(geom.kusok_index);\n\t\tconst float hit_t = rayQueryGetIntersectionTEXT(rq, false);\n\n\t\tconst vec4 texture_color = texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv);\n\t\tconst vec4 mm_color = model.color * kusok.material.base_color;\n\t\tfloat alpha = mm_color.a * texture_color.a * geom.vertex_color_srgb.a;\n\t\tvec3 color = mm_color.rgb * texture_color.rgb * SRGBtoLINEAR(geom.vertex_color_srgb.rgb);\n\n\t\t// Collect in random order\n\t\tentries[entries_count].id = geom.kusok_index;\n\t\tentries[entries_count].base_color_a = vec4(color, alpha);\n\t\tentries[entries_count].material_rmxx = vec4(kusok.material.roughness, kusok.material.metalness, 0., 0.);\n\n\t\t++entries_count;\n\n\t\tif (entries_count == MAX_ENTRIES) {\n\t\t\t// Max blended entries count exceeded\n\t\t\t// TODO show it as error somehow?\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfloat revealage = 1.;\n\tif (entries_count > 0) {\n\t\t// Tyno O(N^2) sort\n\t\tfor (uint i = 0; i < entries_count; ++i) {\n\t\t\tuint min_i = i;\n\t\t\tfor (uint j = i+1; j < entries_count; ++j) {\n\t\t\t\tif (entries[min_i].id < entries[j].id) {\n\t\t\t\t\tmin_i = j;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (min_i != i) {\n\t\t\t\tDecalEntry tmp = entries[min_i];\n\t\t\t\tentries[min_i] = entries[i];\n\t\t\t\tentries[i] = tmp;\n\t\t\t}\n\t\t}\n\n\t\t// Composite everything in the right order\n\t\tfor (uint i = 0; i < entries_count; ++i) {\n\t\t\tfloat a = entries[i].base_color_a.w;\n\t\t\tpayload.base_color_a = mix(payload.base_color_a, vec4(entries[i].base_color_a.rgb, 1.0), a);\n\t\t\tpayload.material_rmxx = mix(payload.material_rmxx, entries[i].material_rmxx, a);\n\t\t}\n\t}\n}\n\n#endif //ifndef TRACE_DECALS_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/trace_simple_blending.glsl",
    "content": "#ifndef TRACE_SIMPLE_BLENDING_GLSL_INCLUDED\n#define TRACE_SIMPLE_BLENDING_GLSL_INCLUDED\n\n#include \"debug.glsl\"\n\n// Traces geometry with simple blending. Simple means that it's only additive or mix/coverage, and it doesn't participate in lighting, and it doesn't reflect/refract rays.\n// Done in sRGB-γ space for legacy-look reasons.\n// Returns vec4(emissive_srgb.rgb, revealage)\nvec4 traceLegacyBlending(vec3 pos, vec3 dir, float L) {\n\tconst float kGlowSoftOvershoot = 16.;\n\tvec3 emissive = vec3(0.);\n\n\t// TODO probably a better way would be to sort only MIX entries.\n\t// ADD/GLOW are order-independent relative to each other, but not to MIX\n\tstruct BlendEntry {\n\t\tvec3 add;\n\t\tfloat blend;\n\t\tfloat depth;\n\t};\n\n\t// VGPR usage :FeelsBadMan:\n#define MAX_ENTRIES 8\n\tuint entries_count = 0;\n\tBlendEntry entries[MAX_ENTRIES];\n\n\trayQueryEXT rq;\n\tconst uint flags = 0\n\t\t| gl_RayFlagsCullFrontFacingTrianglesEXT\n\t\t//| gl_RayFlagsSkipClosestHitShaderEXT\n\t\t| gl_RayFlagsNoOpaqueEXT // force all to be non-opaque\n\t\t;\n\trayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_BLEND, pos, 0., dir, L + kGlowSoftOvershoot);\n\twhile (rayQueryProceedEXT(rq)) {\n\t\tconst MiniGeometry geom = readCandidateMiniGeometry(rq);\n\t\tconst int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, false);\n\t\tconst ModelHeader model = getModelHeader(model_index);\n\t\t\n\t\tif (model.mode == MATERIAL_MODE_DECAL)\n\t\t\tcontinue; // TODO: move decals to separated queue\n\t\t\n\t\tconst Kusok kusok = getKusok(geom.kusok_index);\n\t\tconst float hit_t = rayQueryGetIntersectionTEXT(rq, false);\n\n\t\t// Engage soft particles/blending gradually after a certain distance.\n\t\t// Makes see-beams-through-weapons visual glitch mostly disappear.\n\t\t// Engage-vs-Full difference to make soft particles appear gradually, and not pop immediately.\n\t\tconst float kOvershootEngageDist = 20.;\n\t\tconst float kOvershootFullDist = 40.;\n\t\tconst float glow_soft_overshoot = kGlowSoftOvershoot * smoothstep(kOvershootEngageDist, kOvershootFullDist, hit_t);\n\n\t\tconst float overshoot = hit_t - L;\n\n// Use soft alpha depth effect globally, not only for glow\n#define GLOBAL_SOFT_DEPTH\n#ifndef GLOBAL_SOFT_DEPTH\n\t\tif (overshoot > 0. && model.mode != MATERIAL_MODE_BLEND_GLOW)\n\t\t\tcontinue;\n#endif\n\n\n//#define DEBUG_BLEND_MODES\n#ifdef DEBUG_BLEND_MODES\n\t\tif (model.mode == MATERIAL_MODE_BLEND_GLOW) {\n\t\t\temissive += vec3(1., 0., 0.);\n\t\t\t//emissive += color * smoothstep(glow_soft_overshoot, 0., overshoot);\n\t\t} else if (model.mode == MATERIAL_MODE_BLEND_ADD) {\n\t\t\temissive += vec3(0., 1., 0.);\n\t\t} else if (model.mode == MATERIAL_MODE_BLEND_MIX) {\n\t\t\temissive += vec3(0., 0., 1.);\n\t\t} else if (model.mode == MATERIAL_MODE_TRANSLUCENT) {\n\t\t\temissive += vec3(0., 1., 1.);\n\t\t} else if (model.mode == MATERIAL_MODE_OPAQUE) {\n\t\t\temissive += vec3(1., 1., 1.);\n\t\t}\n#else\n\t\t// Note that simple blending is legacy blending really.\n\t\t// It is done in sRGB-γ space for correct legacy-look reasons.\n\t\tconst vec4 texture_color = LINEARtoSRGB(texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv));\n\t\tconst vec4 mm_color = model.color * kusok.material.base_color;\n\t\tfloat alpha = mm_color.a * texture_color.a * geom.vertex_color_srgb.a;\n\t\tvec3 color = mm_color.rgb * texture_color.rgb * geom.vertex_color_srgb.rgb * alpha;\n\n#ifdef GLOBAL_SOFT_DEPTH\n\t\tconst float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot);\n\t\tcolor *= overshoot_factor;\n#endif\n\n\t\tif (model.mode == MATERIAL_MODE_BLEND_GLOW) {\n\t\t\t// Glow is additive + small overshoot\n#ifndef GLOBAL_SOFT_DEPTH\n\t\t\tconst float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot);\n\t\t\tcolor *= overshoot_factor;\n#endif\n\t\t\talpha = 0.;\n\t\t} else if (model.mode == MATERIAL_MODE_BLEND_ADD) {\n\t\t\t// Additive doesn't attenuate what's behind\n\t\t\talpha = 0.;\n\t\t} else if (model.mode == MATERIAL_MODE_BLEND_MIX) {\n\t\t\t// Handled in composite step below\n\t\t} else {\n\t\t\t// Signal unhandled blending type\n\t\t\tcolor = vec3(1., 0., 1.);\n\t\t}\n\n\t\t// Collect in random order\n\t\tentries[entries_count].add = color;\n\t\tentries[entries_count].blend = alpha;\n\t\tentries[entries_count].depth = hit_t;\n\n\t\t++entries_count;\n\n\t\tif (entries_count == MAX_ENTRIES) {\n\t\t\t// Max blended entries count exceeded\n\t\t\t// TODO show it as error somehow?\n\t\t\tbreak;\n\t\t}\n#endif // !DEBUG_BLEND_MODES\n\t}\n\n\tfloat revealage = 1.;\n\tif (entries_count > 0) {\n\t\t// Tyno O(N^2) sort\n\t\tfor (uint i = 0; i < entries_count; ++i) {\n\t\t\tuint min_i = i;\n\t\t\tfor (uint j = i+1; j < entries_count; ++j) {\n\t\t\t\tif (entries[min_i].depth > entries[j].depth) {\n\t\t\t\t\tmin_i = j;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (min_i != i) {\n\t\t\t\tBlendEntry tmp = entries[min_i];\n\t\t\t\tentries[min_i] = entries[i];\n\t\t\t\tentries[i] = tmp;\n\t\t\t}\n\t\t}\n\n\t\t// Composite everything in the right order\n\t\tfor (uint i = 0; i < entries_count; ++i) {\n\t\t\temissive += entries[i].add * revealage;\n\t\t\trevealage *= 1. - entries[i].blend;\n\t\t}\n\t}\n\n\tDEBUG_VALIDATE_RANGE_VEC3(\"blend.emissive\", emissive, 0., 1e6);\n\tDEBUG_VALIDATE_RANGE(revealage, 0., 1.);\n\n\treturn vec4(emissive, revealage);\n}\n\n#endif //ifndef TRACE_SIMPLE_BLENDING_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/shaders/utils.glsl",
    "content": "#ifndef UTILS_GLSL_INCLUDED\n#define UTILS_GLSL_INCLUDED\n\n// Compared to builtin GLSL sign() will be 1.0 if v == 0.\nfloat signP(float v) { return v >= 0.f ? 1.f : -1.f; }\nvec2 signP(vec2 v) { return vec2(signP(v.x), signP(v.y)); }\n\n// https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/\n// https://www.shadertoy.com/view/Mtfyzl\nvec2 OctWrap( vec2 v )\n{\n    return ( 1.0 - abs( v.yx ) ) * signP(v.xy);\n}\n\nvec2 normalEncode( vec3 n )\n{\n    n /= ( abs( n.x ) + abs( n.y ) + abs( n.z ) );\n    n.xy = n.z >= 0.0 ? n.xy : OctWrap( n.xy );\n    n.xy = n.xy * 0.5 + 0.5;\n    return n.xy;\n}\n\nvec3 normalDecode( vec2 f )\n{\n    f = f * 2.0 - 1.0;\n\n    // https://twitter.com/Stubbesaurus/status/937994790553227264\n    vec3 n = vec3( f, 1.0 - abs( f.x ) - abs( f.y ) );\n    const float t = max( -n.z, 0.f );\n    n.xy -= t * signP(n.xy);\n    return normalize( n );\n}\n\nvec2 baryMix(vec2 v1, vec2 v2, vec2 v3, vec2 bary) {\n\treturn v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y;\n}\n\nvec3 baryMix(vec3 v1, vec3 v2, vec3 v3, vec2 bary) {\n\treturn v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y;\n}\n\nvec4 baryMix(vec4 v1, vec4 v2, vec4 v3, vec2 bary) {\n\treturn v1 * (1. - bary.x - bary.y) + v2 * bary.x + v3 * bary.y;\n}\n\nvec3 mixFinalColor(vec3 base_color, vec3 diffuse, vec3 specular, float metalness) {\n\t\t// Late base_color compositing with diffuse lighting\n\t\t// see brdf.glsl\n\t\tconst vec3 diffuse_color = mix(base_color, vec3(0.), metalness);\n\t\t// Specular color is already computed-in as it is both view and light-source-direction dependent\n\t\treturn diffuse * diffuse_color + specular;\n}\n#endif // UTILS_GLSL_INCLUDED\n"
  },
  {
    "path": "ref/vk/spirv.py",
    "content": "# Copyright (c) 2014-2020 The Khronos Group Inc.\n# \n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and/or associated documentation files (the \"Materials\"),\n# to deal in the Materials without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish, distribute, sublicense,\n# and/or sell copies of the Materials, and to permit persons to whom the\n# Materials are furnished to do so, subject to the following conditions:\n# \n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Materials.\n# \n# MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS\n# STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND\n# HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ \n# \n# THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n# FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS\n# IN THE MATERIALS.\n\n# This header is automatically generated by the same tool that creates\n# the Binary Section of the SPIR-V specification.\n\n# Enumeration tokens for SPIR-V, in various styles:\n#   C, C++, C++11, JSON, Lua, Python, C#, D, Beef\n# \n# - C will have tokens with a \"Spv\" prefix, e.g.: SpvSourceLanguageGLSL\n# - C++ will have tokens in the \"spv\" name space, e.g.: spv::SourceLanguageGLSL\n# - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL\n# - Lua will use tables, e.g.: spv.SourceLanguage.GLSL\n# - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL']\n# - C# will use enum classes in the Specification class located in the \"Spv\" namespace,\n#     e.g.: Spv.Specification.SourceLanguage.GLSL\n# - D will have tokens under the \"spv\" module, e.g: spv.SourceLanguage.GLSL\n# - Beef will use enum classes in the Specification class located in the \"Spv\" namespace,\n#     e.g.: Spv.Specification.SourceLanguage.GLSL\n# \n# Some tokens act like mask values, which can be OR'd together,\n# while others are mutually exclusive.  The mask-like ones have\n# \"Mask\" in their name, and a parallel enum that has the shift\n# amount (1 << x) for each corresponding enumerant.\n\nspv = {\n    'MagicNumber' : 0x07230203,\n    'Version' : 0x00010600,\n    'Revision' : 1,\n    'OpCodeMask' : 0xffff,\n    'WordCountShift' : 16,\n\n    'SourceLanguage' : {\n        'Unknown' : 0,\n        'ESSL' : 1,\n        'GLSL' : 2,\n        'OpenCL_C' : 3,\n        'OpenCL_CPP' : 4,\n        'HLSL' : 5,\n        'CPP_for_OpenCL' : 6,\n        'SYCL' : 7,\n    },\n\n    'ExecutionModel' : {\n        'Vertex' : 0,\n        'TessellationControl' : 1,\n        'TessellationEvaluation' : 2,\n        'Geometry' : 3,\n        'Fragment' : 4,\n        'GLCompute' : 5,\n        'Kernel' : 6,\n        'TaskNV' : 5267,\n        'MeshNV' : 5268,\n        'RayGenerationKHR' : 5313,\n        'RayGenerationNV' : 5313,\n        'IntersectionKHR' : 5314,\n        'IntersectionNV' : 5314,\n        'AnyHitKHR' : 5315,\n        'AnyHitNV' : 5315,\n        'ClosestHitKHR' : 5316,\n        'ClosestHitNV' : 5316,\n        'MissKHR' : 5317,\n        'MissNV' : 5317,\n        'CallableKHR' : 5318,\n        'CallableNV' : 5318,\n    },\n\n    'AddressingModel' : {\n        'Logical' : 0,\n        'Physical32' : 1,\n        'Physical64' : 2,\n        'PhysicalStorageBuffer64' : 5348,\n        'PhysicalStorageBuffer64EXT' : 5348,\n    },\n\n    'MemoryModel' : {\n        'Simple' : 0,\n        'GLSL450' : 1,\n        'OpenCL' : 2,\n        'Vulkan' : 3,\n        'VulkanKHR' : 3,\n    },\n\n    'ExecutionMode' : {\n        'Invocations' : 0,\n        'SpacingEqual' : 1,\n        'SpacingFractionalEven' : 2,\n        'SpacingFractionalOdd' : 3,\n        'VertexOrderCw' : 4,\n        'VertexOrderCcw' : 5,\n        'PixelCenterInteger' : 6,\n        'OriginUpperLeft' : 7,\n        'OriginLowerLeft' : 8,\n        'EarlyFragmentTests' : 9,\n        'PointMode' : 10,\n        'Xfb' : 11,\n        'DepthReplacing' : 12,\n        'DepthGreater' : 14,\n        'DepthLess' : 15,\n        'DepthUnchanged' : 16,\n        'LocalSize' : 17,\n        'LocalSizeHint' : 18,\n        'InputPoints' : 19,\n        'InputLines' : 20,\n        'InputLinesAdjacency' : 21,\n        'Triangles' : 22,\n        'InputTrianglesAdjacency' : 23,\n        'Quads' : 24,\n        'Isolines' : 25,\n        'OutputVertices' : 26,\n        'OutputPoints' : 27,\n        'OutputLineStrip' : 28,\n        'OutputTriangleStrip' : 29,\n        'VecTypeHint' : 30,\n        'ContractionOff' : 31,\n        'Initializer' : 33,\n        'Finalizer' : 34,\n        'SubgroupSize' : 35,\n        'SubgroupsPerWorkgroup' : 36,\n        'SubgroupsPerWorkgroupId' : 37,\n        'LocalSizeId' : 38,\n        'LocalSizeHintId' : 39,\n        'SubgroupUniformControlFlowKHR' : 4421,\n        'PostDepthCoverage' : 4446,\n        'DenormPreserve' : 4459,\n        'DenormFlushToZero' : 4460,\n        'SignedZeroInfNanPreserve' : 4461,\n        'RoundingModeRTE' : 4462,\n        'RoundingModeRTZ' : 4463,\n        'EarlyAndLateFragmentTestsAMD' : 5017,\n        'StencilRefReplacingEXT' : 5027,\n        'StencilRefUnchangedFrontAMD' : 5079,\n        'StencilRefGreaterFrontAMD' : 5080,\n        'StencilRefLessFrontAMD' : 5081,\n        'StencilRefUnchangedBackAMD' : 5082,\n        'StencilRefGreaterBackAMD' : 5083,\n        'StencilRefLessBackAMD' : 5084,\n        'OutputLinesNV' : 5269,\n        'OutputPrimitivesNV' : 5270,\n        'DerivativeGroupQuadsNV' : 5289,\n        'DerivativeGroupLinearNV' : 5290,\n        'OutputTrianglesNV' : 5298,\n        'PixelInterlockOrderedEXT' : 5366,\n        'PixelInterlockUnorderedEXT' : 5367,\n        'SampleInterlockOrderedEXT' : 5368,\n        'SampleInterlockUnorderedEXT' : 5369,\n        'ShadingRateInterlockOrderedEXT' : 5370,\n        'ShadingRateInterlockUnorderedEXT' : 5371,\n        'SharedLocalMemorySizeINTEL' : 5618,\n        'RoundingModeRTPINTEL' : 5620,\n        'RoundingModeRTNINTEL' : 5621,\n        'FloatingPointModeALTINTEL' : 5622,\n        'FloatingPointModeIEEEINTEL' : 5623,\n        'MaxWorkgroupSizeINTEL' : 5893,\n        'MaxWorkDimINTEL' : 5894,\n        'NoGlobalOffsetINTEL' : 5895,\n        'NumSIMDWorkitemsINTEL' : 5896,\n        'SchedulerTargetFmaxMhzINTEL' : 5903,\n        'NamedBarrierCountINTEL' : 6417,\n    },\n\n    'StorageClass' : {\n        'UniformConstant' : 0,\n        'Input' : 1,\n        'Uniform' : 2,\n        'Output' : 3,\n        'Workgroup' : 4,\n        'CrossWorkgroup' : 5,\n        'Private' : 6,\n        'Function' : 7,\n        'Generic' : 8,\n        'PushConstant' : 9,\n        'AtomicCounter' : 10,\n        'Image' : 11,\n        'StorageBuffer' : 12,\n        'CallableDataKHR' : 5328,\n        'CallableDataNV' : 5328,\n        'IncomingCallableDataKHR' : 5329,\n        'IncomingCallableDataNV' : 5329,\n        'RayPayloadKHR' : 5338,\n        'RayPayloadNV' : 5338,\n        'HitAttributeKHR' : 5339,\n        'HitAttributeNV' : 5339,\n        'IncomingRayPayloadKHR' : 5342,\n        'IncomingRayPayloadNV' : 5342,\n        'ShaderRecordBufferKHR' : 5343,\n        'ShaderRecordBufferNV' : 5343,\n        'PhysicalStorageBuffer' : 5349,\n        'PhysicalStorageBufferEXT' : 5349,\n        'CodeSectionINTEL' : 5605,\n        'DeviceOnlyINTEL' : 5936,\n        'HostOnlyINTEL' : 5937,\n    },\n\n    'Dim' : {\n        'Dim1D' : 0,\n        'Dim2D' : 1,\n        'Dim3D' : 2,\n        'Cube' : 3,\n        'Rect' : 4,\n        'Buffer' : 5,\n        'SubpassData' : 6,\n    },\n\n    'SamplerAddressingMode' : {\n        'None' : 0,\n        'ClampToEdge' : 1,\n        'Clamp' : 2,\n        'Repeat' : 3,\n        'RepeatMirrored' : 4,\n    },\n\n    'SamplerFilterMode' : {\n        'Nearest' : 0,\n        'Linear' : 1,\n    },\n\n    'ImageFormat' : {\n        'Unknown' : 0,\n        'Rgba32f' : 1,\n        'Rgba16f' : 2,\n        'R32f' : 3,\n        'Rgba8' : 4,\n        'Rgba8Snorm' : 5,\n        'Rg32f' : 6,\n        'Rg16f' : 7,\n        'R11fG11fB10f' : 8,\n        'R16f' : 9,\n        'Rgba16' : 10,\n        'Rgb10A2' : 11,\n        'Rg16' : 12,\n        'Rg8' : 13,\n        'R16' : 14,\n        'R8' : 15,\n        'Rgba16Snorm' : 16,\n        'Rg16Snorm' : 17,\n        'Rg8Snorm' : 18,\n        'R16Snorm' : 19,\n        'R8Snorm' : 20,\n        'Rgba32i' : 21,\n        'Rgba16i' : 22,\n        'Rgba8i' : 23,\n        'R32i' : 24,\n        'Rg32i' : 25,\n        'Rg16i' : 26,\n        'Rg8i' : 27,\n        'R16i' : 28,\n        'R8i' : 29,\n        'Rgba32ui' : 30,\n        'Rgba16ui' : 31,\n        'Rgba8ui' : 32,\n        'R32ui' : 33,\n        'Rgb10a2ui' : 34,\n        'Rg32ui' : 35,\n        'Rg16ui' : 36,\n        'Rg8ui' : 37,\n        'R16ui' : 38,\n        'R8ui' : 39,\n        'R64ui' : 40,\n        'R64i' : 41,\n    },\n\n    'ImageChannelOrder' : {\n        'R' : 0,\n        'A' : 1,\n        'RG' : 2,\n        'RA' : 3,\n        'RGB' : 4,\n        'RGBA' : 5,\n        'BGRA' : 6,\n        'ARGB' : 7,\n        'Intensity' : 8,\n        'Luminance' : 9,\n        'Rx' : 10,\n        'RGx' : 11,\n        'RGBx' : 12,\n        'Depth' : 13,\n        'DepthStencil' : 14,\n        'sRGB' : 15,\n        'sRGBx' : 16,\n        'sRGBA' : 17,\n        'sBGRA' : 18,\n        'ABGR' : 19,\n    },\n\n    'ImageChannelDataType' : {\n        'SnormInt8' : 0,\n        'SnormInt16' : 1,\n        'UnormInt8' : 2,\n        'UnormInt16' : 3,\n        'UnormShort565' : 4,\n        'UnormShort555' : 5,\n        'UnormInt101010' : 6,\n        'SignedInt8' : 7,\n        'SignedInt16' : 8,\n        'SignedInt32' : 9,\n        'UnsignedInt8' : 10,\n        'UnsignedInt16' : 11,\n        'UnsignedInt32' : 12,\n        'HalfFloat' : 13,\n        'Float' : 14,\n        'UnormInt24' : 15,\n        'UnormInt101010_2' : 16,\n    },\n\n    'ImageOperandsShift' : {\n        'Bias' : 0,\n        'Lod' : 1,\n        'Grad' : 2,\n        'ConstOffset' : 3,\n        'Offset' : 4,\n        'ConstOffsets' : 5,\n        'Sample' : 6,\n        'MinLod' : 7,\n        'MakeTexelAvailable' : 8,\n        'MakeTexelAvailableKHR' : 8,\n        'MakeTexelVisible' : 9,\n        'MakeTexelVisibleKHR' : 9,\n        'NonPrivateTexel' : 10,\n        'NonPrivateTexelKHR' : 10,\n        'VolatileTexel' : 11,\n        'VolatileTexelKHR' : 11,\n        'SignExtend' : 12,\n        'ZeroExtend' : 13,\n        'Nontemporal' : 14,\n        'Offsets' : 16,\n    },\n\n    'ImageOperandsMask' : {\n        'MaskNone' : 0,\n        'Bias' : 0x00000001,\n        'Lod' : 0x00000002,\n        'Grad' : 0x00000004,\n        'ConstOffset' : 0x00000008,\n        'Offset' : 0x00000010,\n        'ConstOffsets' : 0x00000020,\n        'Sample' : 0x00000040,\n        'MinLod' : 0x00000080,\n        'MakeTexelAvailable' : 0x00000100,\n        'MakeTexelAvailableKHR' : 0x00000100,\n        'MakeTexelVisible' : 0x00000200,\n        'MakeTexelVisibleKHR' : 0x00000200,\n        'NonPrivateTexel' : 0x00000400,\n        'NonPrivateTexelKHR' : 0x00000400,\n        'VolatileTexel' : 0x00000800,\n        'VolatileTexelKHR' : 0x00000800,\n        'SignExtend' : 0x00001000,\n        'ZeroExtend' : 0x00002000,\n        'Nontemporal' : 0x00004000,\n        'Offsets' : 0x00010000,\n    },\n\n    'FPFastMathModeShift' : {\n        'NotNaN' : 0,\n        'NotInf' : 1,\n        'NSZ' : 2,\n        'AllowRecip' : 3,\n        'Fast' : 4,\n        'AllowContractFastINTEL' : 16,\n        'AllowReassocINTEL' : 17,\n    },\n\n    'FPFastMathModeMask' : {\n        'MaskNone' : 0,\n        'NotNaN' : 0x00000001,\n        'NotInf' : 0x00000002,\n        'NSZ' : 0x00000004,\n        'AllowRecip' : 0x00000008,\n        'Fast' : 0x00000010,\n        'AllowContractFastINTEL' : 0x00010000,\n        'AllowReassocINTEL' : 0x00020000,\n    },\n\n    'FPRoundingMode' : {\n        'RTE' : 0,\n        'RTZ' : 1,\n        'RTP' : 2,\n        'RTN' : 3,\n    },\n\n    'LinkageType' : {\n        'Export' : 0,\n        'Import' : 1,\n        'LinkOnceODR' : 2,\n    },\n\n    'AccessQualifier' : {\n        'ReadOnly' : 0,\n        'WriteOnly' : 1,\n        'ReadWrite' : 2,\n    },\n\n    'FunctionParameterAttribute' : {\n        'Zext' : 0,\n        'Sext' : 1,\n        'ByVal' : 2,\n        'Sret' : 3,\n        'NoAlias' : 4,\n        'NoCapture' : 5,\n        'NoWrite' : 6,\n        'NoReadWrite' : 7,\n    },\n\n    'Decoration' : {\n        'RelaxedPrecision' : 0,\n        'SpecId' : 1,\n        'Block' : 2,\n        'BufferBlock' : 3,\n        'RowMajor' : 4,\n        'ColMajor' : 5,\n        'ArrayStride' : 6,\n        'MatrixStride' : 7,\n        'GLSLShared' : 8,\n        'GLSLPacked' : 9,\n        'CPacked' : 10,\n        'BuiltIn' : 11,\n        'NoPerspective' : 13,\n        'Flat' : 14,\n        'Patch' : 15,\n        'Centroid' : 16,\n        'Sample' : 17,\n        'Invariant' : 18,\n        'Restrict' : 19,\n        'Aliased' : 20,\n        'Volatile' : 21,\n        'Constant' : 22,\n        'Coherent' : 23,\n        'NonWritable' : 24,\n        'NonReadable' : 25,\n        'Uniform' : 26,\n        'UniformId' : 27,\n        'SaturatedConversion' : 28,\n        'Stream' : 29,\n        'Location' : 30,\n        'Component' : 31,\n        'Index' : 32,\n        'Binding' : 33,\n        'DescriptorSet' : 34,\n        'Offset' : 35,\n        'XfbBuffer' : 36,\n        'XfbStride' : 37,\n        'FuncParamAttr' : 38,\n        'FPRoundingMode' : 39,\n        'FPFastMathMode' : 40,\n        'LinkageAttributes' : 41,\n        'NoContraction' : 42,\n        'InputAttachmentIndex' : 43,\n        'Alignment' : 44,\n        'MaxByteOffset' : 45,\n        'AlignmentId' : 46,\n        'MaxByteOffsetId' : 47,\n        'NoSignedWrap' : 4469,\n        'NoUnsignedWrap' : 4470,\n        'ExplicitInterpAMD' : 4999,\n        'OverrideCoverageNV' : 5248,\n        'PassthroughNV' : 5250,\n        'ViewportRelativeNV' : 5252,\n        'SecondaryViewportRelativeNV' : 5256,\n        'PerPrimitiveNV' : 5271,\n        'PerViewNV' : 5272,\n        'PerTaskNV' : 5273,\n        'PerVertexKHR' : 5285,\n        'PerVertexNV' : 5285,\n        'NonUniform' : 5300,\n        'NonUniformEXT' : 5300,\n        'RestrictPointer' : 5355,\n        'RestrictPointerEXT' : 5355,\n        'AliasedPointer' : 5356,\n        'AliasedPointerEXT' : 5356,\n        'BindlessSamplerNV' : 5398,\n        'BindlessImageNV' : 5399,\n        'BoundSamplerNV' : 5400,\n        'BoundImageNV' : 5401,\n        'SIMTCallINTEL' : 5599,\n        'ReferencedIndirectlyINTEL' : 5602,\n        'ClobberINTEL' : 5607,\n        'SideEffectsINTEL' : 5608,\n        'VectorComputeVariableINTEL' : 5624,\n        'FuncParamIOKindINTEL' : 5625,\n        'VectorComputeFunctionINTEL' : 5626,\n        'StackCallINTEL' : 5627,\n        'GlobalVariableOffsetINTEL' : 5628,\n        'CounterBuffer' : 5634,\n        'HlslCounterBufferGOOGLE' : 5634,\n        'HlslSemanticGOOGLE' : 5635,\n        'UserSemantic' : 5635,\n        'UserTypeGOOGLE' : 5636,\n        'FunctionRoundingModeINTEL' : 5822,\n        'FunctionDenormModeINTEL' : 5823,\n        'RegisterINTEL' : 5825,\n        'MemoryINTEL' : 5826,\n        'NumbanksINTEL' : 5827,\n        'BankwidthINTEL' : 5828,\n        'MaxPrivateCopiesINTEL' : 5829,\n        'SinglepumpINTEL' : 5830,\n        'DoublepumpINTEL' : 5831,\n        'MaxReplicatesINTEL' : 5832,\n        'SimpleDualPortINTEL' : 5833,\n        'MergeINTEL' : 5834,\n        'BankBitsINTEL' : 5835,\n        'ForcePow2DepthINTEL' : 5836,\n        'BurstCoalesceINTEL' : 5899,\n        'CacheSizeINTEL' : 5900,\n        'DontStaticallyCoalesceINTEL' : 5901,\n        'PrefetchINTEL' : 5902,\n        'StallEnableINTEL' : 5905,\n        'FuseLoopsInFunctionINTEL' : 5907,\n        'AliasScopeINTEL' : 5914,\n        'NoAliasINTEL' : 5915,\n        'BufferLocationINTEL' : 5921,\n        'IOPipeStorageINTEL' : 5944,\n        'FunctionFloatingPointModeINTEL' : 6080,\n        'SingleElementVectorINTEL' : 6085,\n        'VectorComputeCallableFunctionINTEL' : 6087,\n        'MediaBlockIOINTEL' : 6140,\n    },\n\n    'BuiltIn' : {\n        'Position' : 0,\n        'PointSize' : 1,\n        'ClipDistance' : 3,\n        'CullDistance' : 4,\n        'VertexId' : 5,\n        'InstanceId' : 6,\n        'PrimitiveId' : 7,\n        'InvocationId' : 8,\n        'Layer' : 9,\n        'ViewportIndex' : 10,\n        'TessLevelOuter' : 11,\n        'TessLevelInner' : 12,\n        'TessCoord' : 13,\n        'PatchVertices' : 14,\n        'FragCoord' : 15,\n        'PointCoord' : 16,\n        'FrontFacing' : 17,\n        'SampleId' : 18,\n        'SamplePosition' : 19,\n        'SampleMask' : 20,\n        'FragDepth' : 22,\n        'HelperInvocation' : 23,\n        'NumWorkgroups' : 24,\n        'WorkgroupSize' : 25,\n        'WorkgroupId' : 26,\n        'LocalInvocationId' : 27,\n        'GlobalInvocationId' : 28,\n        'LocalInvocationIndex' : 29,\n        'WorkDim' : 30,\n        'GlobalSize' : 31,\n        'EnqueuedWorkgroupSize' : 32,\n        'GlobalOffset' : 33,\n        'GlobalLinearId' : 34,\n        'SubgroupSize' : 36,\n        'SubgroupMaxSize' : 37,\n        'NumSubgroups' : 38,\n        'NumEnqueuedSubgroups' : 39,\n        'SubgroupId' : 40,\n        'SubgroupLocalInvocationId' : 41,\n        'VertexIndex' : 42,\n        'InstanceIndex' : 43,\n        'SubgroupEqMask' : 4416,\n        'SubgroupEqMaskKHR' : 4416,\n        'SubgroupGeMask' : 4417,\n        'SubgroupGeMaskKHR' : 4417,\n        'SubgroupGtMask' : 4418,\n        'SubgroupGtMaskKHR' : 4418,\n        'SubgroupLeMask' : 4419,\n        'SubgroupLeMaskKHR' : 4419,\n        'SubgroupLtMask' : 4420,\n        'SubgroupLtMaskKHR' : 4420,\n        'BaseVertex' : 4424,\n        'BaseInstance' : 4425,\n        'DrawIndex' : 4426,\n        'PrimitiveShadingRateKHR' : 4432,\n        'DeviceIndex' : 4438,\n        'ViewIndex' : 4440,\n        'ShadingRateKHR' : 4444,\n        'BaryCoordNoPerspAMD' : 4992,\n        'BaryCoordNoPerspCentroidAMD' : 4993,\n        'BaryCoordNoPerspSampleAMD' : 4994,\n        'BaryCoordSmoothAMD' : 4995,\n        'BaryCoordSmoothCentroidAMD' : 4996,\n        'BaryCoordSmoothSampleAMD' : 4997,\n        'BaryCoordPullModelAMD' : 4998,\n        'FragStencilRefEXT' : 5014,\n        'ViewportMaskNV' : 5253,\n        'SecondaryPositionNV' : 5257,\n        'SecondaryViewportMaskNV' : 5258,\n        'PositionPerViewNV' : 5261,\n        'ViewportMaskPerViewNV' : 5262,\n        'FullyCoveredEXT' : 5264,\n        'TaskCountNV' : 5274,\n        'PrimitiveCountNV' : 5275,\n        'PrimitiveIndicesNV' : 5276,\n        'ClipDistancePerViewNV' : 5277,\n        'CullDistancePerViewNV' : 5278,\n        'LayerPerViewNV' : 5279,\n        'MeshViewCountNV' : 5280,\n        'MeshViewIndicesNV' : 5281,\n        'BaryCoordKHR' : 5286,\n        'BaryCoordNV' : 5286,\n        'BaryCoordNoPerspKHR' : 5287,\n        'BaryCoordNoPerspNV' : 5287,\n        'FragSizeEXT' : 5292,\n        'FragmentSizeNV' : 5292,\n        'FragInvocationCountEXT' : 5293,\n        'InvocationsPerPixelNV' : 5293,\n        'LaunchIdKHR' : 5319,\n        'LaunchIdNV' : 5319,\n        'LaunchSizeKHR' : 5320,\n        'LaunchSizeNV' : 5320,\n        'WorldRayOriginKHR' : 5321,\n        'WorldRayOriginNV' : 5321,\n        'WorldRayDirectionKHR' : 5322,\n        'WorldRayDirectionNV' : 5322,\n        'ObjectRayOriginKHR' : 5323,\n        'ObjectRayOriginNV' : 5323,\n        'ObjectRayDirectionKHR' : 5324,\n        'ObjectRayDirectionNV' : 5324,\n        'RayTminKHR' : 5325,\n        'RayTminNV' : 5325,\n        'RayTmaxKHR' : 5326,\n        'RayTmaxNV' : 5326,\n        'InstanceCustomIndexKHR' : 5327,\n        'InstanceCustomIndexNV' : 5327,\n        'ObjectToWorldKHR' : 5330,\n        'ObjectToWorldNV' : 5330,\n        'WorldToObjectKHR' : 5331,\n        'WorldToObjectNV' : 5331,\n        'HitTNV' : 5332,\n        'HitKindKHR' : 5333,\n        'HitKindNV' : 5333,\n        'CurrentRayTimeNV' : 5334,\n        'IncomingRayFlagsKHR' : 5351,\n        'IncomingRayFlagsNV' : 5351,\n        'RayGeometryIndexKHR' : 5352,\n        'WarpsPerSMNV' : 5374,\n        'SMCountNV' : 5375,\n        'WarpIDNV' : 5376,\n        'SMIDNV' : 5377,\n        'CullMaskKHR' : 6021,\n    },\n\n    'SelectionControlShift' : {\n        'Flatten' : 0,\n        'DontFlatten' : 1,\n    },\n\n    'SelectionControlMask' : {\n        'MaskNone' : 0,\n        'Flatten' : 0x00000001,\n        'DontFlatten' : 0x00000002,\n    },\n\n    'LoopControlShift' : {\n        'Unroll' : 0,\n        'DontUnroll' : 1,\n        'DependencyInfinite' : 2,\n        'DependencyLength' : 3,\n        'MinIterations' : 4,\n        'MaxIterations' : 5,\n        'IterationMultiple' : 6,\n        'PeelCount' : 7,\n        'PartialCount' : 8,\n        'InitiationIntervalINTEL' : 16,\n        'MaxConcurrencyINTEL' : 17,\n        'DependencyArrayINTEL' : 18,\n        'PipelineEnableINTEL' : 19,\n        'LoopCoalesceINTEL' : 20,\n        'MaxInterleavingINTEL' : 21,\n        'SpeculatedIterationsINTEL' : 22,\n        'NoFusionINTEL' : 23,\n    },\n\n    'LoopControlMask' : {\n        'MaskNone' : 0,\n        'Unroll' : 0x00000001,\n        'DontUnroll' : 0x00000002,\n        'DependencyInfinite' : 0x00000004,\n        'DependencyLength' : 0x00000008,\n        'MinIterations' : 0x00000010,\n        'MaxIterations' : 0x00000020,\n        'IterationMultiple' : 0x00000040,\n        'PeelCount' : 0x00000080,\n        'PartialCount' : 0x00000100,\n        'InitiationIntervalINTEL' : 0x00010000,\n        'MaxConcurrencyINTEL' : 0x00020000,\n        'DependencyArrayINTEL' : 0x00040000,\n        'PipelineEnableINTEL' : 0x00080000,\n        'LoopCoalesceINTEL' : 0x00100000,\n        'MaxInterleavingINTEL' : 0x00200000,\n        'SpeculatedIterationsINTEL' : 0x00400000,\n        'NoFusionINTEL' : 0x00800000,\n    },\n\n    'FunctionControlShift' : {\n        'Inline' : 0,\n        'DontInline' : 1,\n        'Pure' : 2,\n        'Const' : 3,\n        'OptNoneINTEL' : 16,\n    },\n\n    'FunctionControlMask' : {\n        'MaskNone' : 0,\n        'Inline' : 0x00000001,\n        'DontInline' : 0x00000002,\n        'Pure' : 0x00000004,\n        'Const' : 0x00000008,\n        'OptNoneINTEL' : 0x00010000,\n    },\n\n    'MemorySemanticsShift' : {\n        'Acquire' : 1,\n        'Release' : 2,\n        'AcquireRelease' : 3,\n        'SequentiallyConsistent' : 4,\n        'UniformMemory' : 6,\n        'SubgroupMemory' : 7,\n        'WorkgroupMemory' : 8,\n        'CrossWorkgroupMemory' : 9,\n        'AtomicCounterMemory' : 10,\n        'ImageMemory' : 11,\n        'OutputMemory' : 12,\n        'OutputMemoryKHR' : 12,\n        'MakeAvailable' : 13,\n        'MakeAvailableKHR' : 13,\n        'MakeVisible' : 14,\n        'MakeVisibleKHR' : 14,\n        'Volatile' : 15,\n    },\n\n    'MemorySemanticsMask' : {\n        'MaskNone' : 0,\n        'Acquire' : 0x00000002,\n        'Release' : 0x00000004,\n        'AcquireRelease' : 0x00000008,\n        'SequentiallyConsistent' : 0x00000010,\n        'UniformMemory' : 0x00000040,\n        'SubgroupMemory' : 0x00000080,\n        'WorkgroupMemory' : 0x00000100,\n        'CrossWorkgroupMemory' : 0x00000200,\n        'AtomicCounterMemory' : 0x00000400,\n        'ImageMemory' : 0x00000800,\n        'OutputMemory' : 0x00001000,\n        'OutputMemoryKHR' : 0x00001000,\n        'MakeAvailable' : 0x00002000,\n        'MakeAvailableKHR' : 0x00002000,\n        'MakeVisible' : 0x00004000,\n        'MakeVisibleKHR' : 0x00004000,\n        'Volatile' : 0x00008000,\n    },\n\n    'MemoryAccessShift' : {\n        'Volatile' : 0,\n        'Aligned' : 1,\n        'Nontemporal' : 2,\n        'MakePointerAvailable' : 3,\n        'MakePointerAvailableKHR' : 3,\n        'MakePointerVisible' : 4,\n        'MakePointerVisibleKHR' : 4,\n        'NonPrivatePointer' : 5,\n        'NonPrivatePointerKHR' : 5,\n        'AliasScopeINTELMask' : 16,\n        'NoAliasINTELMask' : 17,\n    },\n\n    'MemoryAccessMask' : {\n        'MaskNone' : 0,\n        'Volatile' : 0x00000001,\n        'Aligned' : 0x00000002,\n        'Nontemporal' : 0x00000004,\n        'MakePointerAvailable' : 0x00000008,\n        'MakePointerAvailableKHR' : 0x00000008,\n        'MakePointerVisible' : 0x00000010,\n        'MakePointerVisibleKHR' : 0x00000010,\n        'NonPrivatePointer' : 0x00000020,\n        'NonPrivatePointerKHR' : 0x00000020,\n        'AliasScopeINTELMask' : 0x00010000,\n        'NoAliasINTELMask' : 0x00020000,\n    },\n\n    'Scope' : {\n        'CrossDevice' : 0,\n        'Device' : 1,\n        'Workgroup' : 2,\n        'Subgroup' : 3,\n        'Invocation' : 4,\n        'QueueFamily' : 5,\n        'QueueFamilyKHR' : 5,\n        'ShaderCallKHR' : 6,\n    },\n\n    'GroupOperation' : {\n        'Reduce' : 0,\n        'InclusiveScan' : 1,\n        'ExclusiveScan' : 2,\n        'ClusteredReduce' : 3,\n        'PartitionedReduceNV' : 6,\n        'PartitionedInclusiveScanNV' : 7,\n        'PartitionedExclusiveScanNV' : 8,\n    },\n\n    'KernelEnqueueFlags' : {\n        'NoWait' : 0,\n        'WaitKernel' : 1,\n        'WaitWorkGroup' : 2,\n    },\n\n    'KernelProfilingInfoShift' : {\n        'CmdExecTime' : 0,\n    },\n\n    'KernelProfilingInfoMask' : {\n        'MaskNone' : 0,\n        'CmdExecTime' : 0x00000001,\n    },\n\n    'Capability' : {\n        'Matrix' : 0,\n        'Shader' : 1,\n        'Geometry' : 2,\n        'Tessellation' : 3,\n        'Addresses' : 4,\n        'Linkage' : 5,\n        'Kernel' : 6,\n        'Vector16' : 7,\n        'Float16Buffer' : 8,\n        'Float16' : 9,\n        'Float64' : 10,\n        'Int64' : 11,\n        'Int64Atomics' : 12,\n        'ImageBasic' : 13,\n        'ImageReadWrite' : 14,\n        'ImageMipmap' : 15,\n        'Pipes' : 17,\n        'Groups' : 18,\n        'DeviceEnqueue' : 19,\n        'LiteralSampler' : 20,\n        'AtomicStorage' : 21,\n        'Int16' : 22,\n        'TessellationPointSize' : 23,\n        'GeometryPointSize' : 24,\n        'ImageGatherExtended' : 25,\n        'StorageImageMultisample' : 27,\n        'UniformBufferArrayDynamicIndexing' : 28,\n        'SampledImageArrayDynamicIndexing' : 29,\n        'StorageBufferArrayDynamicIndexing' : 30,\n        'StorageImageArrayDynamicIndexing' : 31,\n        'ClipDistance' : 32,\n        'CullDistance' : 33,\n        'ImageCubeArray' : 34,\n        'SampleRateShading' : 35,\n        'ImageRect' : 36,\n        'SampledRect' : 37,\n        'GenericPointer' : 38,\n        'Int8' : 39,\n        'InputAttachment' : 40,\n        'SparseResidency' : 41,\n        'MinLod' : 42,\n        'Sampled1D' : 43,\n        'Image1D' : 44,\n        'SampledCubeArray' : 45,\n        'SampledBuffer' : 46,\n        'ImageBuffer' : 47,\n        'ImageMSArray' : 48,\n        'StorageImageExtendedFormats' : 49,\n        'ImageQuery' : 50,\n        'DerivativeControl' : 51,\n        'InterpolationFunction' : 52,\n        'TransformFeedback' : 53,\n        'GeometryStreams' : 54,\n        'StorageImageReadWithoutFormat' : 55,\n        'StorageImageWriteWithoutFormat' : 56,\n        'MultiViewport' : 57,\n        'SubgroupDispatch' : 58,\n        'NamedBarrier' : 59,\n        'PipeStorage' : 60,\n        'GroupNonUniform' : 61,\n        'GroupNonUniformVote' : 62,\n        'GroupNonUniformArithmetic' : 63,\n        'GroupNonUniformBallot' : 64,\n        'GroupNonUniformShuffle' : 65,\n        'GroupNonUniformShuffleRelative' : 66,\n        'GroupNonUniformClustered' : 67,\n        'GroupNonUniformQuad' : 68,\n        'ShaderLayer' : 69,\n        'ShaderViewportIndex' : 70,\n        'UniformDecoration' : 71,\n        'FragmentShadingRateKHR' : 4422,\n        'SubgroupBallotKHR' : 4423,\n        'DrawParameters' : 4427,\n        'WorkgroupMemoryExplicitLayoutKHR' : 4428,\n        'WorkgroupMemoryExplicitLayout8BitAccessKHR' : 4429,\n        'WorkgroupMemoryExplicitLayout16BitAccessKHR' : 4430,\n        'SubgroupVoteKHR' : 4431,\n        'StorageBuffer16BitAccess' : 4433,\n        'StorageUniformBufferBlock16' : 4433,\n        'StorageUniform16' : 4434,\n        'UniformAndStorageBuffer16BitAccess' : 4434,\n        'StoragePushConstant16' : 4435,\n        'StorageInputOutput16' : 4436,\n        'DeviceGroup' : 4437,\n        'MultiView' : 4439,\n        'VariablePointersStorageBuffer' : 4441,\n        'VariablePointers' : 4442,\n        'AtomicStorageOps' : 4445,\n        'SampleMaskPostDepthCoverage' : 4447,\n        'StorageBuffer8BitAccess' : 4448,\n        'UniformAndStorageBuffer8BitAccess' : 4449,\n        'StoragePushConstant8' : 4450,\n        'DenormPreserve' : 4464,\n        'DenormFlushToZero' : 4465,\n        'SignedZeroInfNanPreserve' : 4466,\n        'RoundingModeRTE' : 4467,\n        'RoundingModeRTZ' : 4468,\n        'RayQueryProvisionalKHR' : 4471,\n        'RayQueryKHR' : 4472,\n        'RayTraversalPrimitiveCullingKHR' : 4478,\n        'RayTracingKHR' : 4479,\n        'Float16ImageAMD' : 5008,\n        'ImageGatherBiasLodAMD' : 5009,\n        'FragmentMaskAMD' : 5010,\n        'StencilExportEXT' : 5013,\n        'ImageReadWriteLodAMD' : 5015,\n        'Int64ImageEXT' : 5016,\n        'ShaderClockKHR' : 5055,\n        'SampleMaskOverrideCoverageNV' : 5249,\n        'GeometryShaderPassthroughNV' : 5251,\n        'ShaderViewportIndexLayerEXT' : 5254,\n        'ShaderViewportIndexLayerNV' : 5254,\n        'ShaderViewportMaskNV' : 5255,\n        'ShaderStereoViewNV' : 5259,\n        'PerViewAttributesNV' : 5260,\n        'FragmentFullyCoveredEXT' : 5265,\n        'MeshShadingNV' : 5266,\n        'ImageFootprintNV' : 5282,\n        'FragmentBarycentricKHR' : 5284,\n        'FragmentBarycentricNV' : 5284,\n        'ComputeDerivativeGroupQuadsNV' : 5288,\n        'FragmentDensityEXT' : 5291,\n        'ShadingRateNV' : 5291,\n        'GroupNonUniformPartitionedNV' : 5297,\n        'ShaderNonUniform' : 5301,\n        'ShaderNonUniformEXT' : 5301,\n        'RuntimeDescriptorArray' : 5302,\n        'RuntimeDescriptorArrayEXT' : 5302,\n        'InputAttachmentArrayDynamicIndexing' : 5303,\n        'InputAttachmentArrayDynamicIndexingEXT' : 5303,\n        'UniformTexelBufferArrayDynamicIndexing' : 5304,\n        'UniformTexelBufferArrayDynamicIndexingEXT' : 5304,\n        'StorageTexelBufferArrayDynamicIndexing' : 5305,\n        'StorageTexelBufferArrayDynamicIndexingEXT' : 5305,\n        'UniformBufferArrayNonUniformIndexing' : 5306,\n        'UniformBufferArrayNonUniformIndexingEXT' : 5306,\n        'SampledImageArrayNonUniformIndexing' : 5307,\n        'SampledImageArrayNonUniformIndexingEXT' : 5307,\n        'StorageBufferArrayNonUniformIndexing' : 5308,\n        'StorageBufferArrayNonUniformIndexingEXT' : 5308,\n        'StorageImageArrayNonUniformIndexing' : 5309,\n        'StorageImageArrayNonUniformIndexingEXT' : 5309,\n        'InputAttachmentArrayNonUniformIndexing' : 5310,\n        'InputAttachmentArrayNonUniformIndexingEXT' : 5310,\n        'UniformTexelBufferArrayNonUniformIndexing' : 5311,\n        'UniformTexelBufferArrayNonUniformIndexingEXT' : 5311,\n        'StorageTexelBufferArrayNonUniformIndexing' : 5312,\n        'StorageTexelBufferArrayNonUniformIndexingEXT' : 5312,\n        'RayTracingNV' : 5340,\n        'RayTracingMotionBlurNV' : 5341,\n        'VulkanMemoryModel' : 5345,\n        'VulkanMemoryModelKHR' : 5345,\n        'VulkanMemoryModelDeviceScope' : 5346,\n        'VulkanMemoryModelDeviceScopeKHR' : 5346,\n        'PhysicalStorageBufferAddresses' : 5347,\n        'PhysicalStorageBufferAddressesEXT' : 5347,\n        'ComputeDerivativeGroupLinearNV' : 5350,\n        'RayTracingProvisionalKHR' : 5353,\n        'CooperativeMatrixNV' : 5357,\n        'FragmentShaderSampleInterlockEXT' : 5363,\n        'FragmentShaderShadingRateInterlockEXT' : 5372,\n        'ShaderSMBuiltinsNV' : 5373,\n        'FragmentShaderPixelInterlockEXT' : 5378,\n        'DemoteToHelperInvocation' : 5379,\n        'DemoteToHelperInvocationEXT' : 5379,\n        'BindlessTextureNV' : 5390,\n        'SubgroupShuffleINTEL' : 5568,\n        'SubgroupBufferBlockIOINTEL' : 5569,\n        'SubgroupImageBlockIOINTEL' : 5570,\n        'SubgroupImageMediaBlockIOINTEL' : 5579,\n        'RoundToInfinityINTEL' : 5582,\n        'FloatingPointModeINTEL' : 5583,\n        'IntegerFunctions2INTEL' : 5584,\n        'FunctionPointersINTEL' : 5603,\n        'IndirectReferencesINTEL' : 5604,\n        'AsmINTEL' : 5606,\n        'AtomicFloat32MinMaxEXT' : 5612,\n        'AtomicFloat64MinMaxEXT' : 5613,\n        'AtomicFloat16MinMaxEXT' : 5616,\n        'VectorComputeINTEL' : 5617,\n        'VectorAnyINTEL' : 5619,\n        'ExpectAssumeKHR' : 5629,\n        'SubgroupAvcMotionEstimationINTEL' : 5696,\n        'SubgroupAvcMotionEstimationIntraINTEL' : 5697,\n        'SubgroupAvcMotionEstimationChromaINTEL' : 5698,\n        'VariableLengthArrayINTEL' : 5817,\n        'FunctionFloatControlINTEL' : 5821,\n        'FPGAMemoryAttributesINTEL' : 5824,\n        'FPFastMathModeINTEL' : 5837,\n        'ArbitraryPrecisionIntegersINTEL' : 5844,\n        'ArbitraryPrecisionFloatingPointINTEL' : 5845,\n        'UnstructuredLoopControlsINTEL' : 5886,\n        'FPGALoopControlsINTEL' : 5888,\n        'KernelAttributesINTEL' : 5892,\n        'FPGAKernelAttributesINTEL' : 5897,\n        'FPGAMemoryAccessesINTEL' : 5898,\n        'FPGAClusterAttributesINTEL' : 5904,\n        'LoopFuseINTEL' : 5906,\n        'MemoryAccessAliasingINTEL' : 5910,\n        'FPGABufferLocationINTEL' : 5920,\n        'ArbitraryPrecisionFixedPointINTEL' : 5922,\n        'USMStorageClassesINTEL' : 5935,\n        'IOPipesINTEL' : 5943,\n        'BlockingPipesINTEL' : 5945,\n        'FPGARegINTEL' : 5948,\n        'DotProductInputAll' : 6016,\n        'DotProductInputAllKHR' : 6016,\n        'DotProductInput4x8Bit' : 6017,\n        'DotProductInput4x8BitKHR' : 6017,\n        'DotProductInput4x8BitPacked' : 6018,\n        'DotProductInput4x8BitPackedKHR' : 6018,\n        'DotProduct' : 6019,\n        'DotProductKHR' : 6019,\n        'RayCullMaskKHR' : 6020,\n        'BitInstructions' : 6025,\n        'GroupNonUniformRotateKHR' : 6026,\n        'AtomicFloat32AddEXT' : 6033,\n        'AtomicFloat64AddEXT' : 6034,\n        'LongConstantCompositeINTEL' : 6089,\n        'OptNoneINTEL' : 6094,\n        'AtomicFloat16AddEXT' : 6095,\n        'DebugInfoModuleINTEL' : 6114,\n        'SplitBarrierINTEL' : 6141,\n        'GroupUniformArithmeticKHR' : 6400,\n    },\n\n    'RayFlagsShift' : {\n        'OpaqueKHR' : 0,\n        'NoOpaqueKHR' : 1,\n        'TerminateOnFirstHitKHR' : 2,\n        'SkipClosestHitShaderKHR' : 3,\n        'CullBackFacingTrianglesKHR' : 4,\n        'CullFrontFacingTrianglesKHR' : 5,\n        'CullOpaqueKHR' : 6,\n        'CullNoOpaqueKHR' : 7,\n        'SkipTrianglesKHR' : 8,\n        'SkipAABBsKHR' : 9,\n    },\n\n    'RayFlagsMask' : {\n        'MaskNone' : 0,\n        'OpaqueKHR' : 0x00000001,\n        'NoOpaqueKHR' : 0x00000002,\n        'TerminateOnFirstHitKHR' : 0x00000004,\n        'SkipClosestHitShaderKHR' : 0x00000008,\n        'CullBackFacingTrianglesKHR' : 0x00000010,\n        'CullFrontFacingTrianglesKHR' : 0x00000020,\n        'CullOpaqueKHR' : 0x00000040,\n        'CullNoOpaqueKHR' : 0x00000080,\n        'SkipTrianglesKHR' : 0x00000100,\n        'SkipAABBsKHR' : 0x00000200,\n    },\n\n    'RayQueryIntersection' : {\n        'RayQueryCandidateIntersectionKHR' : 0,\n        'RayQueryCommittedIntersectionKHR' : 1,\n    },\n\n    'RayQueryCommittedIntersectionType' : {\n        'RayQueryCommittedIntersectionNoneKHR' : 0,\n        'RayQueryCommittedIntersectionTriangleKHR' : 1,\n        'RayQueryCommittedIntersectionGeneratedKHR' : 2,\n    },\n\n    'RayQueryCandidateIntersectionType' : {\n        'RayQueryCandidateIntersectionTriangleKHR' : 0,\n        'RayQueryCandidateIntersectionAABBKHR' : 1,\n    },\n\n    'FragmentShadingRateShift' : {\n        'Vertical2Pixels' : 0,\n        'Vertical4Pixels' : 1,\n        'Horizontal2Pixels' : 2,\n        'Horizontal4Pixels' : 3,\n    },\n\n    'FragmentShadingRateMask' : {\n        'MaskNone' : 0,\n        'Vertical2Pixels' : 0x00000001,\n        'Vertical4Pixels' : 0x00000002,\n        'Horizontal2Pixels' : 0x00000004,\n        'Horizontal4Pixels' : 0x00000008,\n    },\n\n    'FPDenormMode' : {\n        'Preserve' : 0,\n        'FlushToZero' : 1,\n    },\n\n    'FPOperationMode' : {\n        'IEEE' : 0,\n        'ALT' : 1,\n    },\n\n    'QuantizationModes' : {\n        'TRN' : 0,\n        'TRN_ZERO' : 1,\n        'RND' : 2,\n        'RND_ZERO' : 3,\n        'RND_INF' : 4,\n        'RND_MIN_INF' : 5,\n        'RND_CONV' : 6,\n        'RND_CONV_ODD' : 7,\n    },\n\n    'OverflowModes' : {\n        'WRAP' : 0,\n        'SAT' : 1,\n        'SAT_ZERO' : 2,\n        'SAT_SYM' : 3,\n    },\n\n    'PackedVectorFormat' : {\n        'PackedVectorFormat4x8Bit' : 0,\n        'PackedVectorFormat4x8BitKHR' : 0,\n    },\n\n    'Op' : {\n        'OpNop' : 0,\n        'OpUndef' : 1,\n        'OpSourceContinued' : 2,\n        'OpSource' : 3,\n        'OpSourceExtension' : 4,\n        'OpName' : 5,\n        'OpMemberName' : 6,\n        'OpString' : 7,\n        'OpLine' : 8,\n        'OpExtension' : 10,\n        'OpExtInstImport' : 11,\n        'OpExtInst' : 12,\n        'OpMemoryModel' : 14,\n        'OpEntryPoint' : 15,\n        'OpExecutionMode' : 16,\n        'OpCapability' : 17,\n        'OpTypeVoid' : 19,\n        'OpTypeBool' : 20,\n        'OpTypeInt' : 21,\n        'OpTypeFloat' : 22,\n        'OpTypeVector' : 23,\n        'OpTypeMatrix' : 24,\n        'OpTypeImage' : 25,\n        'OpTypeSampler' : 26,\n        'OpTypeSampledImage' : 27,\n        'OpTypeArray' : 28,\n        'OpTypeRuntimeArray' : 29,\n        'OpTypeStruct' : 30,\n        'OpTypeOpaque' : 31,\n        'OpTypePointer' : 32,\n        'OpTypeFunction' : 33,\n        'OpTypeEvent' : 34,\n        'OpTypeDeviceEvent' : 35,\n        'OpTypeReserveId' : 36,\n        'OpTypeQueue' : 37,\n        'OpTypePipe' : 38,\n        'OpTypeForwardPointer' : 39,\n        'OpConstantTrue' : 41,\n        'OpConstantFalse' : 42,\n        'OpConstant' : 43,\n        'OpConstantComposite' : 44,\n        'OpConstantSampler' : 45,\n        'OpConstantNull' : 46,\n        'OpSpecConstantTrue' : 48,\n        'OpSpecConstantFalse' : 49,\n        'OpSpecConstant' : 50,\n        'OpSpecConstantComposite' : 51,\n        'OpSpecConstantOp' : 52,\n        'OpFunction' : 54,\n        'OpFunctionParameter' : 55,\n        'OpFunctionEnd' : 56,\n        'OpFunctionCall' : 57,\n        'OpVariable' : 59,\n        'OpImageTexelPointer' : 60,\n        'OpLoad' : 61,\n        'OpStore' : 62,\n        'OpCopyMemory' : 63,\n        'OpCopyMemorySized' : 64,\n        'OpAccessChain' : 65,\n        'OpInBoundsAccessChain' : 66,\n        'OpPtrAccessChain' : 67,\n        'OpArrayLength' : 68,\n        'OpGenericPtrMemSemantics' : 69,\n        'OpInBoundsPtrAccessChain' : 70,\n        'OpDecorate' : 71,\n        'OpMemberDecorate' : 72,\n        'OpDecorationGroup' : 73,\n        'OpGroupDecorate' : 74,\n        'OpGroupMemberDecorate' : 75,\n        'OpVectorExtractDynamic' : 77,\n        'OpVectorInsertDynamic' : 78,\n        'OpVectorShuffle' : 79,\n        'OpCompositeConstruct' : 80,\n        'OpCompositeExtract' : 81,\n        'OpCompositeInsert' : 82,\n        'OpCopyObject' : 83,\n        'OpTranspose' : 84,\n        'OpSampledImage' : 86,\n        'OpImageSampleImplicitLod' : 87,\n        'OpImageSampleExplicitLod' : 88,\n        'OpImageSampleDrefImplicitLod' : 89,\n        'OpImageSampleDrefExplicitLod' : 90,\n        'OpImageSampleProjImplicitLod' : 91,\n        'OpImageSampleProjExplicitLod' : 92,\n        'OpImageSampleProjDrefImplicitLod' : 93,\n        'OpImageSampleProjDrefExplicitLod' : 94,\n        'OpImageFetch' : 95,\n        'OpImageGather' : 96,\n        'OpImageDrefGather' : 97,\n        'OpImageRead' : 98,\n        'OpImageWrite' : 99,\n        'OpImage' : 100,\n        'OpImageQueryFormat' : 101,\n        'OpImageQueryOrder' : 102,\n        'OpImageQuerySizeLod' : 103,\n        'OpImageQuerySize' : 104,\n        'OpImageQueryLod' : 105,\n        'OpImageQueryLevels' : 106,\n        'OpImageQuerySamples' : 107,\n        'OpConvertFToU' : 109,\n        'OpConvertFToS' : 110,\n        'OpConvertSToF' : 111,\n        'OpConvertUToF' : 112,\n        'OpUConvert' : 113,\n        'OpSConvert' : 114,\n        'OpFConvert' : 115,\n        'OpQuantizeToF16' : 116,\n        'OpConvertPtrToU' : 117,\n        'OpSatConvertSToU' : 118,\n        'OpSatConvertUToS' : 119,\n        'OpConvertUToPtr' : 120,\n        'OpPtrCastToGeneric' : 121,\n        'OpGenericCastToPtr' : 122,\n        'OpGenericCastToPtrExplicit' : 123,\n        'OpBitcast' : 124,\n        'OpSNegate' : 126,\n        'OpFNegate' : 127,\n        'OpIAdd' : 128,\n        'OpFAdd' : 129,\n        'OpISub' : 130,\n        'OpFSub' : 131,\n        'OpIMul' : 132,\n        'OpFMul' : 133,\n        'OpUDiv' : 134,\n        'OpSDiv' : 135,\n        'OpFDiv' : 136,\n        'OpUMod' : 137,\n        'OpSRem' : 138,\n        'OpSMod' : 139,\n        'OpFRem' : 140,\n        'OpFMod' : 141,\n        'OpVectorTimesScalar' : 142,\n        'OpMatrixTimesScalar' : 143,\n        'OpVectorTimesMatrix' : 144,\n        'OpMatrixTimesVector' : 145,\n        'OpMatrixTimesMatrix' : 146,\n        'OpOuterProduct' : 147,\n        'OpDot' : 148,\n        'OpIAddCarry' : 149,\n        'OpISubBorrow' : 150,\n        'OpUMulExtended' : 151,\n        'OpSMulExtended' : 152,\n        'OpAny' : 154,\n        'OpAll' : 155,\n        'OpIsNan' : 156,\n        'OpIsInf' : 157,\n        'OpIsFinite' : 158,\n        'OpIsNormal' : 159,\n        'OpSignBitSet' : 160,\n        'OpLessOrGreater' : 161,\n        'OpOrdered' : 162,\n        'OpUnordered' : 163,\n        'OpLogicalEqual' : 164,\n        'OpLogicalNotEqual' : 165,\n        'OpLogicalOr' : 166,\n        'OpLogicalAnd' : 167,\n        'OpLogicalNot' : 168,\n        'OpSelect' : 169,\n        'OpIEqual' : 170,\n        'OpINotEqual' : 171,\n        'OpUGreaterThan' : 172,\n        'OpSGreaterThan' : 173,\n        'OpUGreaterThanEqual' : 174,\n        'OpSGreaterThanEqual' : 175,\n        'OpULessThan' : 176,\n        'OpSLessThan' : 177,\n        'OpULessThanEqual' : 178,\n        'OpSLessThanEqual' : 179,\n        'OpFOrdEqual' : 180,\n        'OpFUnordEqual' : 181,\n        'OpFOrdNotEqual' : 182,\n        'OpFUnordNotEqual' : 183,\n        'OpFOrdLessThan' : 184,\n        'OpFUnordLessThan' : 185,\n        'OpFOrdGreaterThan' : 186,\n        'OpFUnordGreaterThan' : 187,\n        'OpFOrdLessThanEqual' : 188,\n        'OpFUnordLessThanEqual' : 189,\n        'OpFOrdGreaterThanEqual' : 190,\n        'OpFUnordGreaterThanEqual' : 191,\n        'OpShiftRightLogical' : 194,\n        'OpShiftRightArithmetic' : 195,\n        'OpShiftLeftLogical' : 196,\n        'OpBitwiseOr' : 197,\n        'OpBitwiseXor' : 198,\n        'OpBitwiseAnd' : 199,\n        'OpNot' : 200,\n        'OpBitFieldInsert' : 201,\n        'OpBitFieldSExtract' : 202,\n        'OpBitFieldUExtract' : 203,\n        'OpBitReverse' : 204,\n        'OpBitCount' : 205,\n        'OpDPdx' : 207,\n        'OpDPdy' : 208,\n        'OpFwidth' : 209,\n        'OpDPdxFine' : 210,\n        'OpDPdyFine' : 211,\n        'OpFwidthFine' : 212,\n        'OpDPdxCoarse' : 213,\n        'OpDPdyCoarse' : 214,\n        'OpFwidthCoarse' : 215,\n        'OpEmitVertex' : 218,\n        'OpEndPrimitive' : 219,\n        'OpEmitStreamVertex' : 220,\n        'OpEndStreamPrimitive' : 221,\n        'OpControlBarrier' : 224,\n        'OpMemoryBarrier' : 225,\n        'OpAtomicLoad' : 227,\n        'OpAtomicStore' : 228,\n        'OpAtomicExchange' : 229,\n        'OpAtomicCompareExchange' : 230,\n        'OpAtomicCompareExchangeWeak' : 231,\n        'OpAtomicIIncrement' : 232,\n        'OpAtomicIDecrement' : 233,\n        'OpAtomicIAdd' : 234,\n        'OpAtomicISub' : 235,\n        'OpAtomicSMin' : 236,\n        'OpAtomicUMin' : 237,\n        'OpAtomicSMax' : 238,\n        'OpAtomicUMax' : 239,\n        'OpAtomicAnd' : 240,\n        'OpAtomicOr' : 241,\n        'OpAtomicXor' : 242,\n        'OpPhi' : 245,\n        'OpLoopMerge' : 246,\n        'OpSelectionMerge' : 247,\n        'OpLabel' : 248,\n        'OpBranch' : 249,\n        'OpBranchConditional' : 250,\n        'OpSwitch' : 251,\n        'OpKill' : 252,\n        'OpReturn' : 253,\n        'OpReturnValue' : 254,\n        'OpUnreachable' : 255,\n        'OpLifetimeStart' : 256,\n        'OpLifetimeStop' : 257,\n        'OpGroupAsyncCopy' : 259,\n        'OpGroupWaitEvents' : 260,\n        'OpGroupAll' : 261,\n        'OpGroupAny' : 262,\n        'OpGroupBroadcast' : 263,\n        'OpGroupIAdd' : 264,\n        'OpGroupFAdd' : 265,\n        'OpGroupFMin' : 266,\n        'OpGroupUMin' : 267,\n        'OpGroupSMin' : 268,\n        'OpGroupFMax' : 269,\n        'OpGroupUMax' : 270,\n        'OpGroupSMax' : 271,\n        'OpReadPipe' : 274,\n        'OpWritePipe' : 275,\n        'OpReservedReadPipe' : 276,\n        'OpReservedWritePipe' : 277,\n        'OpReserveReadPipePackets' : 278,\n        'OpReserveWritePipePackets' : 279,\n        'OpCommitReadPipe' : 280,\n        'OpCommitWritePipe' : 281,\n        'OpIsValidReserveId' : 282,\n        'OpGetNumPipePackets' : 283,\n        'OpGetMaxPipePackets' : 284,\n        'OpGroupReserveReadPipePackets' : 285,\n        'OpGroupReserveWritePipePackets' : 286,\n        'OpGroupCommitReadPipe' : 287,\n        'OpGroupCommitWritePipe' : 288,\n        'OpEnqueueMarker' : 291,\n        'OpEnqueueKernel' : 292,\n        'OpGetKernelNDrangeSubGroupCount' : 293,\n        'OpGetKernelNDrangeMaxSubGroupSize' : 294,\n        'OpGetKernelWorkGroupSize' : 295,\n        'OpGetKernelPreferredWorkGroupSizeMultiple' : 296,\n        'OpRetainEvent' : 297,\n        'OpReleaseEvent' : 298,\n        'OpCreateUserEvent' : 299,\n        'OpIsValidEvent' : 300,\n        'OpSetUserEventStatus' : 301,\n        'OpCaptureEventProfilingInfo' : 302,\n        'OpGetDefaultQueue' : 303,\n        'OpBuildNDRange' : 304,\n        'OpImageSparseSampleImplicitLod' : 305,\n        'OpImageSparseSampleExplicitLod' : 306,\n        'OpImageSparseSampleDrefImplicitLod' : 307,\n        'OpImageSparseSampleDrefExplicitLod' : 308,\n        'OpImageSparseSampleProjImplicitLod' : 309,\n        'OpImageSparseSampleProjExplicitLod' : 310,\n        'OpImageSparseSampleProjDrefImplicitLod' : 311,\n        'OpImageSparseSampleProjDrefExplicitLod' : 312,\n        'OpImageSparseFetch' : 313,\n        'OpImageSparseGather' : 314,\n        'OpImageSparseDrefGather' : 315,\n        'OpImageSparseTexelsResident' : 316,\n        'OpNoLine' : 317,\n        'OpAtomicFlagTestAndSet' : 318,\n        'OpAtomicFlagClear' : 319,\n        'OpImageSparseRead' : 320,\n        'OpSizeOf' : 321,\n        'OpTypePipeStorage' : 322,\n        'OpConstantPipeStorage' : 323,\n        'OpCreatePipeFromPipeStorage' : 324,\n        'OpGetKernelLocalSizeForSubgroupCount' : 325,\n        'OpGetKernelMaxNumSubgroups' : 326,\n        'OpTypeNamedBarrier' : 327,\n        'OpNamedBarrierInitialize' : 328,\n        'OpMemoryNamedBarrier' : 329,\n        'OpModuleProcessed' : 330,\n        'OpExecutionModeId' : 331,\n        'OpDecorateId' : 332,\n        'OpGroupNonUniformElect' : 333,\n        'OpGroupNonUniformAll' : 334,\n        'OpGroupNonUniformAny' : 335,\n        'OpGroupNonUniformAllEqual' : 336,\n        'OpGroupNonUniformBroadcast' : 337,\n        'OpGroupNonUniformBroadcastFirst' : 338,\n        'OpGroupNonUniformBallot' : 339,\n        'OpGroupNonUniformInverseBallot' : 340,\n        'OpGroupNonUniformBallotBitExtract' : 341,\n        'OpGroupNonUniformBallotBitCount' : 342,\n        'OpGroupNonUniformBallotFindLSB' : 343,\n        'OpGroupNonUniformBallotFindMSB' : 344,\n        'OpGroupNonUniformShuffle' : 345,\n        'OpGroupNonUniformShuffleXor' : 346,\n        'OpGroupNonUniformShuffleUp' : 347,\n        'OpGroupNonUniformShuffleDown' : 348,\n        'OpGroupNonUniformIAdd' : 349,\n        'OpGroupNonUniformFAdd' : 350,\n        'OpGroupNonUniformIMul' : 351,\n        'OpGroupNonUniformFMul' : 352,\n        'OpGroupNonUniformSMin' : 353,\n        'OpGroupNonUniformUMin' : 354,\n        'OpGroupNonUniformFMin' : 355,\n        'OpGroupNonUniformSMax' : 356,\n        'OpGroupNonUniformUMax' : 357,\n        'OpGroupNonUniformFMax' : 358,\n        'OpGroupNonUniformBitwiseAnd' : 359,\n        'OpGroupNonUniformBitwiseOr' : 360,\n        'OpGroupNonUniformBitwiseXor' : 361,\n        'OpGroupNonUniformLogicalAnd' : 362,\n        'OpGroupNonUniformLogicalOr' : 363,\n        'OpGroupNonUniformLogicalXor' : 364,\n        'OpGroupNonUniformQuadBroadcast' : 365,\n        'OpGroupNonUniformQuadSwap' : 366,\n        'OpCopyLogical' : 400,\n        'OpPtrEqual' : 401,\n        'OpPtrNotEqual' : 402,\n        'OpPtrDiff' : 403,\n        'OpTerminateInvocation' : 4416,\n        'OpSubgroupBallotKHR' : 4421,\n        'OpSubgroupFirstInvocationKHR' : 4422,\n        'OpSubgroupAllKHR' : 4428,\n        'OpSubgroupAnyKHR' : 4429,\n        'OpSubgroupAllEqualKHR' : 4430,\n        'OpGroupNonUniformRotateKHR' : 4431,\n        'OpSubgroupReadInvocationKHR' : 4432,\n        'OpTraceRayKHR' : 4445,\n        'OpExecuteCallableKHR' : 4446,\n        'OpConvertUToAccelerationStructureKHR' : 4447,\n        'OpIgnoreIntersectionKHR' : 4448,\n        'OpTerminateRayKHR' : 4449,\n        'OpSDot' : 4450,\n        'OpSDotKHR' : 4450,\n        'OpUDot' : 4451,\n        'OpUDotKHR' : 4451,\n        'OpSUDot' : 4452,\n        'OpSUDotKHR' : 4452,\n        'OpSDotAccSat' : 4453,\n        'OpSDotAccSatKHR' : 4453,\n        'OpUDotAccSat' : 4454,\n        'OpUDotAccSatKHR' : 4454,\n        'OpSUDotAccSat' : 4455,\n        'OpSUDotAccSatKHR' : 4455,\n        'OpTypeRayQueryKHR' : 4472,\n        'OpRayQueryInitializeKHR' : 4473,\n        'OpRayQueryTerminateKHR' : 4474,\n        'OpRayQueryGenerateIntersectionKHR' : 4475,\n        'OpRayQueryConfirmIntersectionKHR' : 4476,\n        'OpRayQueryProceedKHR' : 4477,\n        'OpRayQueryGetIntersectionTypeKHR' : 4479,\n        'OpGroupIAddNonUniformAMD' : 5000,\n        'OpGroupFAddNonUniformAMD' : 5001,\n        'OpGroupFMinNonUniformAMD' : 5002,\n        'OpGroupUMinNonUniformAMD' : 5003,\n        'OpGroupSMinNonUniformAMD' : 5004,\n        'OpGroupFMaxNonUniformAMD' : 5005,\n        'OpGroupUMaxNonUniformAMD' : 5006,\n        'OpGroupSMaxNonUniformAMD' : 5007,\n        'OpFragmentMaskFetchAMD' : 5011,\n        'OpFragmentFetchAMD' : 5012,\n        'OpReadClockKHR' : 5056,\n        'OpImageSampleFootprintNV' : 5283,\n        'OpGroupNonUniformPartitionNV' : 5296,\n        'OpWritePackedPrimitiveIndices4x8NV' : 5299,\n        'OpReportIntersectionKHR' : 5334,\n        'OpReportIntersectionNV' : 5334,\n        'OpIgnoreIntersectionNV' : 5335,\n        'OpTerminateRayNV' : 5336,\n        'OpTraceNV' : 5337,\n        'OpTraceMotionNV' : 5338,\n        'OpTraceRayMotionNV' : 5339,\n        'OpTypeAccelerationStructureKHR' : 5341,\n        'OpTypeAccelerationStructureNV' : 5341,\n        'OpExecuteCallableNV' : 5344,\n        'OpTypeCooperativeMatrixNV' : 5358,\n        'OpCooperativeMatrixLoadNV' : 5359,\n        'OpCooperativeMatrixStoreNV' : 5360,\n        'OpCooperativeMatrixMulAddNV' : 5361,\n        'OpCooperativeMatrixLengthNV' : 5362,\n        'OpBeginInvocationInterlockEXT' : 5364,\n        'OpEndInvocationInterlockEXT' : 5365,\n        'OpDemoteToHelperInvocation' : 5380,\n        'OpDemoteToHelperInvocationEXT' : 5380,\n        'OpIsHelperInvocationEXT' : 5381,\n        'OpConvertUToImageNV' : 5391,\n        'OpConvertUToSamplerNV' : 5392,\n        'OpConvertImageToUNV' : 5393,\n        'OpConvertSamplerToUNV' : 5394,\n        'OpConvertUToSampledImageNV' : 5395,\n        'OpConvertSampledImageToUNV' : 5396,\n        'OpSamplerImageAddressingModeNV' : 5397,\n        'OpSubgroupShuffleINTEL' : 5571,\n        'OpSubgroupShuffleDownINTEL' : 5572,\n        'OpSubgroupShuffleUpINTEL' : 5573,\n        'OpSubgroupShuffleXorINTEL' : 5574,\n        'OpSubgroupBlockReadINTEL' : 5575,\n        'OpSubgroupBlockWriteINTEL' : 5576,\n        'OpSubgroupImageBlockReadINTEL' : 5577,\n        'OpSubgroupImageBlockWriteINTEL' : 5578,\n        'OpSubgroupImageMediaBlockReadINTEL' : 5580,\n        'OpSubgroupImageMediaBlockWriteINTEL' : 5581,\n        'OpUCountLeadingZerosINTEL' : 5585,\n        'OpUCountTrailingZerosINTEL' : 5586,\n        'OpAbsISubINTEL' : 5587,\n        'OpAbsUSubINTEL' : 5588,\n        'OpIAddSatINTEL' : 5589,\n        'OpUAddSatINTEL' : 5590,\n        'OpIAverageINTEL' : 5591,\n        'OpUAverageINTEL' : 5592,\n        'OpIAverageRoundedINTEL' : 5593,\n        'OpUAverageRoundedINTEL' : 5594,\n        'OpISubSatINTEL' : 5595,\n        'OpUSubSatINTEL' : 5596,\n        'OpIMul32x16INTEL' : 5597,\n        'OpUMul32x16INTEL' : 5598,\n        'OpConstantFunctionPointerINTEL' : 5600,\n        'OpFunctionPointerCallINTEL' : 5601,\n        'OpAsmTargetINTEL' : 5609,\n        'OpAsmINTEL' : 5610,\n        'OpAsmCallINTEL' : 5611,\n        'OpAtomicFMinEXT' : 5614,\n        'OpAtomicFMaxEXT' : 5615,\n        'OpAssumeTrueKHR' : 5630,\n        'OpExpectKHR' : 5631,\n        'OpDecorateString' : 5632,\n        'OpDecorateStringGOOGLE' : 5632,\n        'OpMemberDecorateString' : 5633,\n        'OpMemberDecorateStringGOOGLE' : 5633,\n        'OpVmeImageINTEL' : 5699,\n        'OpTypeVmeImageINTEL' : 5700,\n        'OpTypeAvcImePayloadINTEL' : 5701,\n        'OpTypeAvcRefPayloadINTEL' : 5702,\n        'OpTypeAvcSicPayloadINTEL' : 5703,\n        'OpTypeAvcMcePayloadINTEL' : 5704,\n        'OpTypeAvcMceResultINTEL' : 5705,\n        'OpTypeAvcImeResultINTEL' : 5706,\n        'OpTypeAvcImeResultSingleReferenceStreamoutINTEL' : 5707,\n        'OpTypeAvcImeResultDualReferenceStreamoutINTEL' : 5708,\n        'OpTypeAvcImeSingleReferenceStreaminINTEL' : 5709,\n        'OpTypeAvcImeDualReferenceStreaminINTEL' : 5710,\n        'OpTypeAvcRefResultINTEL' : 5711,\n        'OpTypeAvcSicResultINTEL' : 5712,\n        'OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL' : 5713,\n        'OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL' : 5714,\n        'OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL' : 5715,\n        'OpSubgroupAvcMceSetInterShapePenaltyINTEL' : 5716,\n        'OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL' : 5717,\n        'OpSubgroupAvcMceSetInterDirectionPenaltyINTEL' : 5718,\n        'OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL' : 5719,\n        'OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL' : 5720,\n        'OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL' : 5721,\n        'OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL' : 5722,\n        'OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL' : 5723,\n        'OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL' : 5724,\n        'OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL' : 5725,\n        'OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL' : 5726,\n        'OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL' : 5727,\n        'OpSubgroupAvcMceSetAcOnlyHaarINTEL' : 5728,\n        'OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL' : 5729,\n        'OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL' : 5730,\n        'OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL' : 5731,\n        'OpSubgroupAvcMceConvertToImePayloadINTEL' : 5732,\n        'OpSubgroupAvcMceConvertToImeResultINTEL' : 5733,\n        'OpSubgroupAvcMceConvertToRefPayloadINTEL' : 5734,\n        'OpSubgroupAvcMceConvertToRefResultINTEL' : 5735,\n        'OpSubgroupAvcMceConvertToSicPayloadINTEL' : 5736,\n        'OpSubgroupAvcMceConvertToSicResultINTEL' : 5737,\n        'OpSubgroupAvcMceGetMotionVectorsINTEL' : 5738,\n        'OpSubgroupAvcMceGetInterDistortionsINTEL' : 5739,\n        'OpSubgroupAvcMceGetBestInterDistortionsINTEL' : 5740,\n        'OpSubgroupAvcMceGetInterMajorShapeINTEL' : 5741,\n        'OpSubgroupAvcMceGetInterMinorShapeINTEL' : 5742,\n        'OpSubgroupAvcMceGetInterDirectionsINTEL' : 5743,\n        'OpSubgroupAvcMceGetInterMotionVectorCountINTEL' : 5744,\n        'OpSubgroupAvcMceGetInterReferenceIdsINTEL' : 5745,\n        'OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL' : 5746,\n        'OpSubgroupAvcImeInitializeINTEL' : 5747,\n        'OpSubgroupAvcImeSetSingleReferenceINTEL' : 5748,\n        'OpSubgroupAvcImeSetDualReferenceINTEL' : 5749,\n        'OpSubgroupAvcImeRefWindowSizeINTEL' : 5750,\n        'OpSubgroupAvcImeAdjustRefOffsetINTEL' : 5751,\n        'OpSubgroupAvcImeConvertToMcePayloadINTEL' : 5752,\n        'OpSubgroupAvcImeSetMaxMotionVectorCountINTEL' : 5753,\n        'OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL' : 5754,\n        'OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL' : 5755,\n        'OpSubgroupAvcImeSetWeightedSadINTEL' : 5756,\n        'OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL' : 5757,\n        'OpSubgroupAvcImeEvaluateWithDualReferenceINTEL' : 5758,\n        'OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL' : 5759,\n        'OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL' : 5760,\n        'OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL' : 5761,\n        'OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL' : 5762,\n        'OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL' : 5763,\n        'OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL' : 5764,\n        'OpSubgroupAvcImeConvertToMceResultINTEL' : 5765,\n        'OpSubgroupAvcImeGetSingleReferenceStreaminINTEL' : 5766,\n        'OpSubgroupAvcImeGetDualReferenceStreaminINTEL' : 5767,\n        'OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL' : 5768,\n        'OpSubgroupAvcImeStripDualReferenceStreamoutINTEL' : 5769,\n        'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL' : 5770,\n        'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL' : 5771,\n        'OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL' : 5772,\n        'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL' : 5773,\n        'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL' : 5774,\n        'OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL' : 5775,\n        'OpSubgroupAvcImeGetBorderReachedINTEL' : 5776,\n        'OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL' : 5777,\n        'OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL' : 5778,\n        'OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL' : 5779,\n        'OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL' : 5780,\n        'OpSubgroupAvcFmeInitializeINTEL' : 5781,\n        'OpSubgroupAvcBmeInitializeINTEL' : 5782,\n        'OpSubgroupAvcRefConvertToMcePayloadINTEL' : 5783,\n        'OpSubgroupAvcRefSetBidirectionalMixDisableINTEL' : 5784,\n        'OpSubgroupAvcRefSetBilinearFilterEnableINTEL' : 5785,\n        'OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL' : 5786,\n        'OpSubgroupAvcRefEvaluateWithDualReferenceINTEL' : 5787,\n        'OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL' : 5788,\n        'OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL' : 5789,\n        'OpSubgroupAvcRefConvertToMceResultINTEL' : 5790,\n        'OpSubgroupAvcSicInitializeINTEL' : 5791,\n        'OpSubgroupAvcSicConfigureSkcINTEL' : 5792,\n        'OpSubgroupAvcSicConfigureIpeLumaINTEL' : 5793,\n        'OpSubgroupAvcSicConfigureIpeLumaChromaINTEL' : 5794,\n        'OpSubgroupAvcSicGetMotionVectorMaskINTEL' : 5795,\n        'OpSubgroupAvcSicConvertToMcePayloadINTEL' : 5796,\n        'OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL' : 5797,\n        'OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL' : 5798,\n        'OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL' : 5799,\n        'OpSubgroupAvcSicSetBilinearFilterEnableINTEL' : 5800,\n        'OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL' : 5801,\n        'OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL' : 5802,\n        'OpSubgroupAvcSicEvaluateIpeINTEL' : 5803,\n        'OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL' : 5804,\n        'OpSubgroupAvcSicEvaluateWithDualReferenceINTEL' : 5805,\n        'OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL' : 5806,\n        'OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL' : 5807,\n        'OpSubgroupAvcSicConvertToMceResultINTEL' : 5808,\n        'OpSubgroupAvcSicGetIpeLumaShapeINTEL' : 5809,\n        'OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL' : 5810,\n        'OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL' : 5811,\n        'OpSubgroupAvcSicGetPackedIpeLumaModesINTEL' : 5812,\n        'OpSubgroupAvcSicGetIpeChromaModeINTEL' : 5813,\n        'OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL' : 5814,\n        'OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL' : 5815,\n        'OpSubgroupAvcSicGetInterRawSadsINTEL' : 5816,\n        'OpVariableLengthArrayINTEL' : 5818,\n        'OpSaveMemoryINTEL' : 5819,\n        'OpRestoreMemoryINTEL' : 5820,\n        'OpArbitraryFloatSinCosPiINTEL' : 5840,\n        'OpArbitraryFloatCastINTEL' : 5841,\n        'OpArbitraryFloatCastFromIntINTEL' : 5842,\n        'OpArbitraryFloatCastToIntINTEL' : 5843,\n        'OpArbitraryFloatAddINTEL' : 5846,\n        'OpArbitraryFloatSubINTEL' : 5847,\n        'OpArbitraryFloatMulINTEL' : 5848,\n        'OpArbitraryFloatDivINTEL' : 5849,\n        'OpArbitraryFloatGTINTEL' : 5850,\n        'OpArbitraryFloatGEINTEL' : 5851,\n        'OpArbitraryFloatLTINTEL' : 5852,\n        'OpArbitraryFloatLEINTEL' : 5853,\n        'OpArbitraryFloatEQINTEL' : 5854,\n        'OpArbitraryFloatRecipINTEL' : 5855,\n        'OpArbitraryFloatRSqrtINTEL' : 5856,\n        'OpArbitraryFloatCbrtINTEL' : 5857,\n        'OpArbitraryFloatHypotINTEL' : 5858,\n        'OpArbitraryFloatSqrtINTEL' : 5859,\n        'OpArbitraryFloatLogINTEL' : 5860,\n        'OpArbitraryFloatLog2INTEL' : 5861,\n        'OpArbitraryFloatLog10INTEL' : 5862,\n        'OpArbitraryFloatLog1pINTEL' : 5863,\n        'OpArbitraryFloatExpINTEL' : 5864,\n        'OpArbitraryFloatExp2INTEL' : 5865,\n        'OpArbitraryFloatExp10INTEL' : 5866,\n        'OpArbitraryFloatExpm1INTEL' : 5867,\n        'OpArbitraryFloatSinINTEL' : 5868,\n        'OpArbitraryFloatCosINTEL' : 5869,\n        'OpArbitraryFloatSinCosINTEL' : 5870,\n        'OpArbitraryFloatSinPiINTEL' : 5871,\n        'OpArbitraryFloatCosPiINTEL' : 5872,\n        'OpArbitraryFloatASinINTEL' : 5873,\n        'OpArbitraryFloatASinPiINTEL' : 5874,\n        'OpArbitraryFloatACosINTEL' : 5875,\n        'OpArbitraryFloatACosPiINTEL' : 5876,\n        'OpArbitraryFloatATanINTEL' : 5877,\n        'OpArbitraryFloatATanPiINTEL' : 5878,\n        'OpArbitraryFloatATan2INTEL' : 5879,\n        'OpArbitraryFloatPowINTEL' : 5880,\n        'OpArbitraryFloatPowRINTEL' : 5881,\n        'OpArbitraryFloatPowNINTEL' : 5882,\n        'OpLoopControlINTEL' : 5887,\n        'OpAliasDomainDeclINTEL' : 5911,\n        'OpAliasScopeDeclINTEL' : 5912,\n        'OpAliasScopeListDeclINTEL' : 5913,\n        'OpFixedSqrtINTEL' : 5923,\n        'OpFixedRecipINTEL' : 5924,\n        'OpFixedRsqrtINTEL' : 5925,\n        'OpFixedSinINTEL' : 5926,\n        'OpFixedCosINTEL' : 5927,\n        'OpFixedSinCosINTEL' : 5928,\n        'OpFixedSinPiINTEL' : 5929,\n        'OpFixedCosPiINTEL' : 5930,\n        'OpFixedSinCosPiINTEL' : 5931,\n        'OpFixedLogINTEL' : 5932,\n        'OpFixedExpINTEL' : 5933,\n        'OpPtrCastToCrossWorkgroupINTEL' : 5934,\n        'OpCrossWorkgroupCastToPtrINTEL' : 5938,\n        'OpReadPipeBlockingINTEL' : 5946,\n        'OpWritePipeBlockingINTEL' : 5947,\n        'OpFPGARegINTEL' : 5949,\n        'OpRayQueryGetRayTMinKHR' : 6016,\n        'OpRayQueryGetRayFlagsKHR' : 6017,\n        'OpRayQueryGetIntersectionTKHR' : 6018,\n        'OpRayQueryGetIntersectionInstanceCustomIndexKHR' : 6019,\n        'OpRayQueryGetIntersectionInstanceIdKHR' : 6020,\n        'OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR' : 6021,\n        'OpRayQueryGetIntersectionGeometryIndexKHR' : 6022,\n        'OpRayQueryGetIntersectionPrimitiveIndexKHR' : 6023,\n        'OpRayQueryGetIntersectionBarycentricsKHR' : 6024,\n        'OpRayQueryGetIntersectionFrontFaceKHR' : 6025,\n        'OpRayQueryGetIntersectionCandidateAABBOpaqueKHR' : 6026,\n        'OpRayQueryGetIntersectionObjectRayDirectionKHR' : 6027,\n        'OpRayQueryGetIntersectionObjectRayOriginKHR' : 6028,\n        'OpRayQueryGetWorldRayDirectionKHR' : 6029,\n        'OpRayQueryGetWorldRayOriginKHR' : 6030,\n        'OpRayQueryGetIntersectionObjectToWorldKHR' : 6031,\n        'OpRayQueryGetIntersectionWorldToObjectKHR' : 6032,\n        'OpAtomicFAddEXT' : 6035,\n        'OpTypeBufferSurfaceINTEL' : 6086,\n        'OpTypeStructContinuedINTEL' : 6090,\n        'OpConstantCompositeContinuedINTEL' : 6091,\n        'OpSpecConstantCompositeContinuedINTEL' : 6092,\n        'OpControlBarrierArriveINTEL' : 6142,\n        'OpControlBarrierWaitINTEL' : 6143,\n        'OpGroupIMulKHR' : 6401,\n        'OpGroupFMulKHR' : 6402,\n        'OpGroupBitwiseAndKHR' : 6403,\n        'OpGroupBitwiseOrKHR' : 6404,\n        'OpGroupBitwiseXorKHR' : 6405,\n        'OpGroupLogicalAndKHR' : 6406,\n        'OpGroupLogicalOrKHR' : 6407,\n        'OpGroupLogicalXorKHR' : 6408,\n    },\n\n}\n\n"
  },
  {
    "path": "ref/vk/std/alolcator.c",
    "content": "#include \"alolcator.h\"\n#include <stdlib.h> // malloc/free\n#include <string.h> // memcpy\n\n#define MALLOC malloc\n#define FREE free\n\n#ifndef ASSERT\n#include <assert.h>\n#define ASSERT(...) assert(__VA_ARGS__)\n#endif\n\n#ifndef ALIGN_UP\n#define ALIGN_UP(ptr, align) ((((ptr) + (align) - 1) / (align)) * (align))\n#endif\n\ntypedef struct {\n\tint item_size;\n\tint capacity;\n\tint free;\n\tchar *pool;\n\tint *free_list;\n} pool_t;\n\nstatic void poolGrow(pool_t *pool, int new_capacity) {\n\tconst size_t new_pool_size = pool->item_size * new_capacity;\n\tint *const new_free_list = MALLOC(new_pool_size + sizeof(int) * new_capacity);\n\tchar *const new_pool = (char*)(new_free_list + new_capacity);\n\tconst int new_items = new_capacity - pool->capacity;\n\n\tfor (int i = 0; i < pool->free; ++i)\n\t\tnew_free_list[i] = pool->free_list[i];\n\n\tfor (int i = 0; i < new_items; ++i)\n\t\tnew_free_list[pool->free + i] = new_capacity - i - 1;\n\n\tif (pool->capacity)\n\t\tmemcpy(new_pool, pool->pool, pool->item_size * pool->capacity);\n\n\tif (pool->free_list)\n\t\tFREE(pool->free_list);\n\n\tpool->free_list = new_free_list;\n\tpool->pool = new_pool;\n\tpool->free += new_items;\n\tpool->capacity = new_capacity;\n}\n\nstatic pool_t poolCreate(int item_size, int capacity) {\n\tpool_t pool = {0};\n\tpool.item_size = item_size;\n\tpoolGrow(&pool, capacity);\n\treturn pool;\n}\n\nstatic void poolDestroy(pool_t* pool) {\n\tFREE(pool->free_list);\n\tpool->capacity = 0;\n}\n\n// invalidates all poolGet pointers returned prior to this function\nstatic int poolAlloc(pool_t* pool) {\n\tif (pool->free == 0) {\n\t\tconst int new_capacity = pool->capacity * 3 / 2;\n\t\tpoolGrow(pool, new_capacity);\n\t}\n\n\tpool->free--;\n\treturn pool->free_list[pool->free];\n}\n\ninline static void* poolGet(pool_t* pool, int item) {\n\tASSERT(item >= 0);\n\tASSERT(item < pool->capacity);\n\treturn pool->pool + pool->item_size * item;\n}\n\nstatic void poolFree(pool_t* pool, int item) {\n\tASSERT(item >= 0);\n\tASSERT(item < pool->capacity);\n\tASSERT(pool->free < pool->capacity);\n\n\tpool->free_list[pool->free++] = item;\n}\n\nenum {\n\tBlockFlag_Empty = 0,\n\tBlockFlag_Allocated = 1,\n};\n\ntypedef struct {\n\tint next, prev;\n\tint flags;\n\n\talo_size_t begin;\n\talo_size_t end;\n} block_t;\n\ntypedef struct alo_pool_s {\n\tpool_t blocks;\n\n\talo_size_t min_alignment;\n\talo_size_t size;\n\tint first_block;\n\n\t// TODO optimize: first_free block, and chain of free blocks\n} alo_pool_t;\n\n#define DEFAULT_CAPACITY 256\n\n// TODO make it not a pointer. Just Init\nstruct alo_pool_s* aloPoolCreate(alo_size_t size, int expected_allocations, alo_size_t min_alignment) {\n\talo_pool_t *pool = MALLOC(sizeof(*pool));\n\tblock_t *b;\n\tpool->min_alignment = min_alignment;\n\tpool->size = size;\n\tpool->blocks = poolCreate(sizeof(block_t), expected_allocations > 0 ? expected_allocations : DEFAULT_CAPACITY);\n\tpool->first_block = poolAlloc(&pool->blocks);\n\tb = poolGet(&pool->blocks, pool->first_block);\n\tb->flags = BlockFlag_Empty;\n\tb->next = b->prev = -1;\n\tb->begin = 0;\n\tb->end = size;\n\treturn pool;\n}\n\nvoid aloPoolDestroy(struct alo_pool_s *pool) {\n\tpoolDestroy(&pool->blocks);\n\tFREE(pool);\n}\n\nstatic int splitBlockAt(pool_t *blocks, int index, alo_size_t at) {\n\tblock_t *block = poolGet(blocks, index);\n\tASSERT(block->begin < at);\n\tASSERT(block->end > at);\n\n\tconst int new_index = poolAlloc(blocks);\n\tblock_t *const new_block = poolGet(blocks, new_index);\n\n\t// poolAlloc may reallocate, retrieve pointer again\n\tblock = poolGet(blocks, index);\n\n\tif (block->next >= 0) {\n\t\tblock_t *const next = poolGet(blocks, block->next);\n\t\tASSERT(next->prev == index);\n\t\tnext->prev = new_index;\n\t}\n\n\tnew_block->next = block->next;\n\tnew_block->prev = index;\n\tnew_block->flags = block->flags;\n\tnew_block->end = block->end;\n\tnew_block->begin = at;\n\n\tblock->next = new_index;\n\tblock->end = at;\n\treturn new_index;\n}\n\nalo_block_t aloPoolAllocate(struct alo_pool_s* pool, alo_size_t size, alo_size_t alignment) {\n\talo_block_t ret = {.offset = ALO_ALLOC_FAILED};\n\tblock_t *b;\n\tASSERT(size > 0);\n\n\talignment = alignment > pool->min_alignment ? alignment : pool->min_alignment;\n\n\tfor (int i = pool->first_block; i >= 0; i = b->next) {\n\t\tb = poolGet(&pool->blocks, i);\n\t\tif (b->flags != BlockFlag_Empty)\n\t\t\tcontinue;\n\n\t\t{\n\t\t\tconst alo_size_t offset = ALIGN_UP(b->begin, alignment);\n\t\t\tconst alo_size_t end = offset + size;\n\t\t\tconst alo_size_t alignment_hole = offset - b->begin;\n\n\t\t\tif (end > b->end)\n\t\t\t\tcontinue;\n\n\t\t\t// TODO min allocation size?\n\t\t\tif (alignment_hole > 0) {\n\t\t\t\t// old block remains the alignment_hole\n\t\t\t\t// new block is where we'll allocate\n\t\t\t\ti = splitBlockAt(&pool->blocks, i, offset);\n\t\t\t\tb = poolGet(&pool->blocks, i);\n\t\t\t}\n\n\t\t\tif (end != b->end) {\n\t\t\t\t// new block is after the one we'll allocate on\n\t\t\t\t// so we don't care about it\n\t\t\t\tsplitBlockAt(&pool->blocks, i, end);\n\n\t\t\t\t// splitting may have incurred realloc, retrieve the pointer again\n\t\t\t\tb = poolGet(&pool->blocks, i);\n\t\t\t}\n\n\t\t\tb->flags = BlockFlag_Allocated;\n\n\t\t\tret.index = i;\n\t\t\tret.offset = offset;\n\t\t\tret.size = size;\n\t\t\tret.alignment_hole = alignment_hole;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nvoid aloPoolFree(struct alo_pool_s *pool, int index) {\n\tblock_t *iblock = poolGet(&pool->blocks, index);\n\tASSERT((iblock->flags & BlockFlag_Allocated) != 0);\n\n\tiblock->flags = BlockFlag_Empty;\n\n\t{\n\t\tblock_t *const prev = (iblock->prev >= 0) ? poolGet(&pool->blocks, iblock->prev) : NULL;\n\t\tblock_t *const next = (iblock->next >= 0) ? poolGet(&pool->blocks, iblock->next) : NULL;\n\n\t\t// join with previous block if empty\n\t\tif (prev && prev->flags == BlockFlag_Empty) {\n\t\t\tconst int prev_index = iblock->prev;\n\t\t\tprev->end = iblock->end;\n\t\t\tprev->next = iblock->next;\n\t\t\tif (next)\n\t\t\t\tnext->prev = iblock->prev;\n\n\t\t\tpoolFree(&pool->blocks, index);\n\t\t\tindex = prev_index;\n\t\t\tiblock = prev;\n\t\t}\n\n\t\t// join with next block if empty\n\t\tif (next && next->flags == BlockFlag_Empty) {\n\t\t\tconst int next_index = iblock->next;\n\n\t\t\tconst int next_next_index = next->next;\n\t\t\tblock_t *next_next = next_next_index >=0 ? poolGet(&pool->blocks, next_next_index) : NULL;\n\n\t\t\tiblock->end = next->end;\n\t\t\tiblock->next = next_next_index;\n\t\t\tif (next_next)\n\t\t\t\tnext_next->prev = index;\n\n\t\t\tpoolFree(&pool->blocks, next_index);\n\t\t}\n\t}\n}\n\nvoid aloRingInit(alo_ring_t* ring, uint32_t size) {\n\tring->size = size;\n\tring->head = 0;\n\tring->tail = size;\n}\n\n// Marks everything up-to-pos as free (expects up-to-pos to be valid)\nvoid aloRingFree(alo_ring_t* ring, uint32_t up_to_pos) {\n\tASSERT(up_to_pos < ring->size);\n\t// FIXME assert that up_to_pos is valid and within allocated region\n\tif (up_to_pos == ring->head) {\n\t\tring->head = 0;\n\t\tring->tail = ring->size;\n\t} else\n\t\tring->tail = up_to_pos;\n}\n\n// Allocates a new aligned region and returns offset to it (AllocFailed if allocation failed)\nuint32_t aloRingAlloc(alo_ring_t* ring, uint32_t size, uint32_t alignment) {\n\t// FIXME const uint32_t align = (alignment > 0) ? alignment : 1;\n\tconst uint32_t pos = ALIGN_UP(ring->head, alignment);\n\n\tASSERT(size != 0);\n\n\t// [XXX.....XXX]\n\t//     h    t\n\tif (ring->head <= ring->tail) {\n\t\tif (pos + size > ring->tail)\n\t\t\treturn ALO_ALLOC_FAILED;\n\n\t\tring->head = pos + size;\n\t\treturn pos;\n\t}\n\n\t// [...XXXXXX...]\n\t//     t     h\n\t//  2        1\n\n\t// 1. Check if we have enough space immediately in front of head\n\tif (pos + size <= ring->size) {\n\t\tring->head = pos + size;\n\t\treturn pos;\n\t}\n\n\t// 2. wrap around\n\tif (size > ring->tail)\n\t\treturn ALO_ALLOC_FAILED;\n\n\tring->head = size;\n\treturn 0;\n}\n\n//  free--><- allocated\n// [a....p|q.r.]\n//  free->\n// [a....|pq.r.]\n//  freeing item:\n//  - swap with first allocated\n// [a....r|q.p.]\n\nvoid aloIntPoolGrow(alo_int_pool_t *pool, int new_capacity) {\n\tint *const new_free_list = MALLOC(sizeof(int) * new_capacity);\n\tconst int new_items = new_capacity - pool->capacity;\n\n\tfor (int i = 0; i < pool->free; ++i)\n\t\tnew_free_list[i] = pool->free_list[i];\n\n\tfor (int i = 0; i < new_items; ++i)\n\t\tnew_free_list[pool->free + i] = new_capacity - i - 1;\n\n\tif (pool->free_list)\n\t\tFREE(pool->free_list);\n\n\tpool->free_list = new_free_list;\n\tpool->free += new_items;\n\tpool->capacity = new_capacity;\n}\n\nint aloIntPoolAlloc(alo_int_pool_t *pool) {\n\tif (pool->free == 0)\n\t\treturn -1;\n\n\tpool->free--;\n\treturn pool->free_list[pool->free];\n}\n\nvoid aloIntPoolFree(alo_int_pool_t *pool, int val) {\n\tASSERT(pool->free < pool->capacity);\n\tASSERT(val >= 0);\n\tASSERT(val < pool->capacity);\n\n\t// Manager allocated tail list\n\tfor (int i = pool->free; i < pool->capacity; ++i) {\n\t\tif (pool->free_list[i] != val)\n\t\t\tcontinue;\n\n\t\tconst int tmp = pool->free_list[pool->free];\n\t\tpool->free_list[pool->free] = val;\n\t\tpool->free_list[i] = tmp;\n\n\t\t++pool->free;\n\t\treturn;\n\t}\n\n\tASSERT(!\"Item not found\");\n}\n\nvoid aloIntPoolClear(alo_int_pool_t *pool) {\n\t// Depends on the fact that the tail free_list contains properly maintained allocated ints\n\tpool->free = pool->capacity;\n}\n\nvoid aloIntPoolDestroy(alo_int_pool_t *pool) {\n\tif (pool->free_list)\n\t\tFREE(pool->free_list);\n}\n\n#if defined(ALOLCATOR_TEST)\n#include <stdio.h>\nuint32_t rand_pcg32(uint32_t max) {\n\tif (!max) return 0;\n#define PCG32_INITIALIZER   { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL }\n\tstatic struct { uint64_t state;  uint64_t inc; } rng = PCG32_INITIALIZER;\n\tuint64_t oldstate = rng.state;\n\t// Advance internal state\n\trng.state = oldstate * 6364136223846793005ULL + (rng.inc|1);\n\t// Calculate output function (XSH RR), uses old state for max ILP\n\tuint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;\n\tuint32_t rot = oldstate >> 59u;\n\tuint32_t ret = (xorshifted >> rot) | (xorshifted << ((-rot) & 31));\n\treturn ret % max;\n}\n\nint testRandom(int num_allocs, alo_size_t pool_size, alo_size_t max_align, alo_size_t size_spread) {\n\tstruct alo_pool_s *pool = aloPoolCreate(pool_size, num_allocs / 4 + 1, 1);\n\talo_block_t *blocks = MALLOC(num_allocs * sizeof(alo_block_t));\n\n\tint allocated = 0;\n\tfor (int i = 0; i < num_allocs; ++i) {\n\t\tconst alo_size_t align = 1 + rand_pcg32(max_align - 1);\n\t\tconst alo_size_t size = pool_size / num_allocs - size_spread + rand_pcg32(size_spread * 2);\n\t\tblocks[i] = aloPoolAllocate(pool, size, align);\n\n\t\tif (blocks[i].size == 0) {\n\t\t\t// FIXME assert somehow that we really can't fit anything here\n\t\t\tcontinue;\n\t\t}\n\n\t\t++allocated;\n\n\t\tfor (int j = 0; j < i; ++j) {\n\t\t\tif (blocks[j].size == 0)\n\t\t\t\tcontinue;\n\n\t\t\tASSERT((blocks[j].offset >= (blocks[i].offset + blocks[i].size)) || (blocks[i].offset >= (blocks[j].offset + blocks[j].size)));\n\t\t}\n\t}\n\n\tFREE(blocks);\n\taloPoolDestroy(pool);\n\treturn allocated;\n}\n\nint test(void) {\n\tstruct alo_pool_s *pool = aloPoolCreate(1000, 5, 1);\n\n\t{\n\t\t// Allocate three blocks to fill the memory entirely\n\t\talo_block_t block1 = aloPoolAllocate(pool, 700, 1);\n\t\talo_block_t block2 = aloPoolAllocate(pool, 200, 1);\n\t\talo_block_t block3 = aloPoolAllocate(pool, 100, 1);\n\n\t\tASSERT(block1.offset == 0);\n\t\tASSERT(block1.size == 700);\n\n\t\tASSERT(block2.offset == 700);\n\t\tASSERT(block2.size == 200);\n\n\t\tASSERT(block3.offset == 900);\n\t\tASSERT(block3.size == 100);\n\n\t\t// Delete an realloc the block in the middle\n\t\taloPoolFree(pool, block2.index);\n\t\tblock2 = aloPoolAllocate(pool, 150, 1);\n\t\tASSERT(block2.offset == 700);\n\t\tASSERT(block2.size == 150);\n\n\t\t// Delete the first block\n\t\taloPoolFree(pool, block1.index);\n\t\tblock1 = aloPoolAllocate(pool, 650, 1);\n\t\tASSERT(block1.offset == 0);\n\t\tASSERT(block1.size == 650);\n\n\t\t// Delete the last block\n\t\taloPoolFree(pool, block3.index);\n\t\tblock3 = aloPoolAllocate(pool, 80, 1);\n\t\tASSERT(block3.offset == 850);\n\t\tASSERT(block3.size == 80);\n\n\t\taloPoolFree(pool, block1.index);\n\t\taloPoolFree(pool, block2.index);\n\t\taloPoolFree(pool, block3.index);\n\n\t\tblock1 = aloPoolAllocate(pool, 1000, 1);\n\t\tASSERT(block1.offset == 0);\n\t\tASSERT(block1.size == 1000);\n\t\taloPoolFree(pool, block1.index);\n\t}\n\n\t{\n\t\t// Allocate many small blocks\n\t\talo_block_t b[10];\n\t\tfor (int i = 0; i < 10; ++i) {\n\t\t\tb[i] = aloPoolAllocate(pool, 100, 1);\n\t\t\tASSERT(b[i].size == 100);\n\t\t\tASSERT(b[i].offset == 100*i);\n\t\t}\n\n\t\t{\n\t\t\t// Ensure the pool is full\n\t\t\talo_block_t fail = aloPoolAllocate(pool, 100, 1);\n\t\t\tASSERT(fail.size == 0);\n\t\t}\n\n\t\t// free some blocks in a specific order\n\t\taloPoolFree(pool, b[2].index);\n\t\taloPoolFree(pool, b[4].index);\n\t\taloPoolFree(pool, b[3].index);\n\n\t\t// allocate in the hole\n\t\t{\n\t\t\talo_block_t block1, block2 = aloPoolAllocate(pool, 300, 1);\n\t\t\tASSERT(block2.size == 300);\n\t\t\tASSERT(block2.offset == 200);\n\n\t\t\taloPoolFree(pool, b[7].index);\n\t\t\taloPoolFree(pool, b[6].index);\n\t\t\taloPoolFree(pool, b[5].index);\n\n\t\t\tblock1 = aloPoolAllocate(pool, 300, 1);\n\t\t\tASSERT(block1.size == 300);\n\t\t\tASSERT(block1.offset == 500);\n\n\t\t\taloPoolFree(pool, block1.index);\n\t\t\taloPoolFree(pool, b[8].index);\n\t\t\taloPoolFree(pool, b[9].index);\n\t\t\taloPoolFree(pool, block2.index);\n\n\t\t\tblock1 = aloPoolAllocate(pool, 800, 1);\n\t\t\tASSERT(block1.size == 800);\n\t\t\tASSERT(block1.offset == 200);\n\n\t\t\taloPoolFree(pool, b[0].index);\n\t\t\taloPoolFree(pool, b[1].index);\n\t\t\taloPoolFree(pool, block1.index);\n\n\t\t\tblock1 = aloPoolAllocate(pool, 1000, 1);\n\t\t\tASSERT(block1.size == 1000);\n\t\t\tASSERT(block1.offset == 0);\n\t\t\taloPoolFree(pool, block1.index);\n\t\t}\n\t}\n\n\t// Alignment\n\t{\n\t\talo_block_t b[6];\n\t\tb[0] = aloPoolAllocate(pool, 5, 1);\n\t\tASSERT(b[0].size == 5);\n\t\tASSERT(b[0].offset == 0);\n\n\t\tb[1] = aloPoolAllocate(pool, 19, 4);\n\t\tASSERT(b[1].size == 19);\n\t\tASSERT(b[1].offset == 8);\n\n\t\tb[2] = aloPoolAllocate(pool, 39, 16);\n\t\tASSERT(b[2].size == 39);\n\t\tASSERT(b[2].offset == 32);\n\n\t\tb[3] = aloPoolAllocate(pool, 200, 128);\n\t\tASSERT(b[3].size == 200);\n\t\tASSERT(b[3].offset == 128);\n\n\t\tb[4] = aloPoolAllocate(pool, 488, 512);\n\t\tASSERT(b[4].size == 488);\n\t\tASSERT(b[4].offset == 512);\n\n\t\tb[5] = aloPoolAllocate(pool, 200, 256);\n\t\tASSERT(b[5].size == 0);\n\n\t\taloPoolFree(pool, b[3].index);\n\n\t\tb[5] = aloPoolAllocate(pool, 200, 256);\n\t\tASSERT(b[5].size == 200);\n\t\tASSERT(b[5].offset == 256);\n\t}\n\n\taloPoolDestroy(pool);\n\treturn 0;\n}\n\n#define REQUIRE_EQUAL_UINT32(a, b) \\\n\tdo { \\\n\t\tconst uint32_t va = (a), vb = (b); \\\n\t\tif (va != vb) { \\\n\t\t\tfprintf(stderr, \"%s:%d (%s == %s) FAILED: %u != %u\\n\", \\\n\t\t\t\t__FILE__, __LINE__, \\\n\t\t\t\t#a, #b, \\\n\t\t\t\tva, vb); \\\n\t\t} \\\n\t\tASSERT(va == vb); \\\n\t} while(0)\n\nstatic void dumpRing(int line, const alo_ring_t* ring) {\n\tfprintf(stderr, \"%d \", line);\n\tif (ring->tail < ring->head) {\n\t\tfprintf(stderr, \"t=%03d h=%03d [\", ring->tail, ring->head);\n\t\tfor (int i = 0; i < (int)ring->tail; ++i) fputc('.', stderr);\n\t\tfputc('T', stderr);\n\t\tfor (int i = (int)ring->tail + 1; i < (int)ring->head; ++i) fputc('#', stderr);\n\t\tfputc('h', stderr);\n\t\tfor (int i = (int)ring->head + 1; i < (int)ring->size; ++i) fputc('.', stderr);\n\t} else {\n\t\tfprintf(stderr, \"h=%03d t=%03d [\", ring->head, ring->tail);\n\t\tfor (int i = 0; i < (int)ring->head; ++i) fputc('#', stderr);\n\t\tfputc('h', stderr);\n\t\tfor (int i = (int)ring->head + 1; i < (int)ring->tail; ++i) fputc('.', stderr);\n\t\tfputc('T', stderr);\n\t\tfor (int i = (int)ring->tail + 1; i < (int)ring->size; ++i) fputc('#', stderr);\n\t}\n\tfputs(\"]\\n\", stderr);\n}\n\n#define TEST_ALLOC(name, size, expected, alignment) \\\n\tconst uint32_t name = aloRingAlloc(&ring, size, alignment); \\\n\tdumpRing(__LINE__, &ring); \\\n\tREQUIRE_EQUAL_UINT32(name, expected)\n\n#define TEST_FREE(to) \\\n\taloRingFree(&ring, to); \\\n\tdumpRing(__LINE__, &ring)\n\nvoid testRing(void) {\n\talo_ring_t ring;\n\taloRingInit(&ring, 128);\n\n\tfprintf(stderr, \"%s\\n\", __FUNCTION__);\n\n\tTEST_ALLOC(p0, 64, 0, 1);\n\tTEST_ALLOC(p1, 64, 64, 1);\n\tTEST_ALLOC(p2, 64, ALO_ALLOC_FAILED, 1);\n\tTEST_FREE(p1);\n\tTEST_ALLOC(p3, 32, 0, 1);\n\tTEST_FREE(p3);\n\tTEST_ALLOC(p4, 64, 32, 1);\n\tTEST_ALLOC(p5, 64, ALO_ALLOC_FAILED, 1);\n\tTEST_ALLOC(p6, 16, 96, 1);\n\tTEST_ALLOC(p7, 32, ALO_ALLOC_FAILED, 1);\n\tTEST_FREE(p4);\n\tTEST_ALLOC(p8, 32, 0, 1);\n}\n\nvoid stressTestRing(void) {\n\t#define BUFSIZE 128\n\t#define NUM_ALLOCS 16\n\tconst int rounds = 10000;\n\tstruct { uint32_t pos, size, val; } allocs[NUM_ALLOCS];\n\tint count = 0, wr = 0, rd = 0;\n\tuint32_t buf[BUFSIZE];\n\n\talo_ring_t ring;\n\taloRingInit(&ring, BUFSIZE);\n\n\tfor (int i = 0; i < NUM_ALLOCS; ++i)\n\t\tallocs[i].pos = ALO_ALLOC_FAILED;\n\n\tfprintf(stderr, \"%s\\n\", __FUNCTION__);\n\n\tfor (int i = 0; i < rounds; ++i) {\n\t\tif (count < NUM_ALLOCS) {\n\t\t\tconst uint32_t align = 1 << rand_pcg32(5);\n\t\t\tconst uint32_t size = 1 + rand_pcg32(BUFSIZE / 5);\n\t\t\tconst uint32_t pos = aloRingAlloc(&ring, size, align);\n\t\t\tfprintf(stderr, \"ALLOC(%d;%d) size=%d align=%d => pos=%d\\n\", wr, count, size, align, pos);\n\n\t\t\tif (pos != ALO_ALLOC_FAILED) {\n\t\t\t\tdumpRing(__LINE__, &ring);\n\t\t\t\tallocs[wr].pos = pos;\n\t\t\t\tallocs[wr].size = size;\n\t\t\t\tallocs[wr].val = rand_pcg32(0xFFFFFFFFul);\n\t\t\t\tfor (int i = 0; i < (int)size; ++i)\n\t\t\t\t\tbuf[pos + i] = allocs[wr].val;\n\t\t\t\twr = (wr + 1) % NUM_ALLOCS;\n\t\t\t\tcount++;\n\t\t\t} else {\n\t\t\t\tASSERT(count);\n\t\t\t}\n\t\t}\n\n\t\tif (rand_pcg32(5) == 0) {\n\t\t\tint to_remove = rand_pcg32(5) + 1;\n\t\t\twhile (to_remove-- > 0 && count > 0) {\n\t\t\t\tASSERT(allocs[rd].pos != ALO_ALLOC_FAILED);\n\t\t\t\tfprintf(stderr, \"FREE(%d;%d) pos=%d(%d) count=%d to_remove=%d\\n\", rd, count, allocs[rd].pos, allocs[rd].size, count, to_remove);\n\t\t\t\tfor (int i = 0; i < (int)allocs[rd].size; ++i)\n\t\t\t\t\tREQUIRE_EQUAL_UINT32(buf[allocs[rd].pos + i], allocs[rd].val);\n\t\t\t\taloRingFree(&ring, allocs[rd].pos);\n\t\t\t\tdumpRing(__LINE__, &ring);\n\t\t\t\tallocs[rd].pos = ALO_ALLOC_FAILED;\n\t\t\t\trd = (rd + 1) % NUM_ALLOCS;\n\t\t\t\t--count;\n\t\t\t}\n\t\t}\n\t}\n}\n\nint main(void) {\n\ttest();\n\n\tASSERT(1000 == testRandom(1000, 1000000, 1, 0));\n\ttestRandom(1000, 1000000, 32, 999);\n\n\ttestRing();\n\tstressTestRing();\n\treturn 0;\n}\n#endif\n"
  },
  {
    "path": "ref/vk/std/alolcator.h",
    "content": "#pragma once\n#include <stdint.h>\n\ntypedef uint32_t alo_size_t;\n\nstruct alo_pool_s;\n\nstruct alo_pool_s* aloPoolCreate(alo_size_t size, int expected_allocations, alo_size_t min_alignment);\nvoid aloPoolDestroy(struct alo_pool_s*);\n\ntypedef struct {\n\talo_size_t offset;\n\talo_size_t size;\n\talo_size_t alignment_hole;\n\n\tint index;\n} alo_block_t;\n\nalo_block_t aloPoolAllocate(struct alo_pool_s*, alo_size_t size, alo_size_t alignment);\nvoid aloPoolFree(struct alo_pool_s *pool, int index);\n\n//  <-          size          ->\n// [.....|AAAAAAAAAAAAAAA|......]\n//        ^ -- tail       ^ -- head\ntypedef struct {\n\tuint32_t size, head, tail;\n} alo_ring_t;\n\n#define ALO_ALLOC_FAILED 0xffffffffu\n\n// Marks the entire buffer as free\nvoid aloRingInit(alo_ring_t* ring, uint32_t size);\n\n// Allocates a new aligned region and returns offset to it (AllocFailed if allocation failed)\nuint32_t aloRingAlloc(alo_ring_t* ring, uint32_t size, uint32_t alignment);\n\n// Marks everything up-to-pos as free (expects up-to-pos to be valid)\nvoid aloRingFree(alo_ring_t* ring, uint32_t up_to_pos);\n\n// Integer pool/freelist\n// Get integers from 0 to capacity\ntypedef struct alo_int_pool_s {\n\tint *free_list;\n\tint capacity;\n\tint free;\n} alo_int_pool_t;\n\nvoid aloIntPoolGrow(alo_int_pool_t *pool, int new_capacity);\nint aloIntPoolAlloc(alo_int_pool_t *pool);\nvoid aloIntPoolFree(alo_int_pool_t *pool, int);\nvoid aloIntPoolClear(alo_int_pool_t *pool);\nvoid aloIntPoolDestroy(alo_int_pool_t *pool);\n"
  },
  {
    "path": "ref/vk/std/arrays.c",
    "content": "#include \"arrays.h\"\n\n#include \"vk_core.h\" // Mem_Malloc\n\n#include <stddef.h> // NULL\n\n\nvoid arrayDynamicInit(array_dynamic_t *array, int item_size) {\n\tarray->items = NULL;\n\tarray->count = 0;\n\tarray->capacity = 0;\n\tarray->item_size = item_size;\n}\n\nvoid arrayDynamicDestroy(array_dynamic_t *array) {\n\tif (array->items)\n\t\tMem_Free(array->items);\n\tarray->items = NULL;\n\tarray->count = 0;\n\tarray->capacity = 0;\n}\n\nstatic void arrayDynamicEnsureCapacity(array_dynamic_t *array, int min_capacity) {\n\tif (array->capacity >= min_capacity)\n\t\treturn;\n\n\tif (array->capacity == 0)\n\t\tarray->capacity = 2;\n\n\twhile (array->capacity < min_capacity)\n\t\tarray->capacity = array->capacity * 3 / 2;\n\n\tvoid *new_buffer = Mem_Malloc(vk_core.pool, array->capacity * array->item_size);\n\tif (array->items) {\n\t\tmemcpy(new_buffer, array->items, array->count * array->item_size);\n\t\tMem_Free(array->items);\n\t}\n\tarray->items = new_buffer;\n}\n\nvoid arrayDynamicResize(array_dynamic_t *array, int count) {\n\tarrayDynamicEnsureCapacity(array, count);\n\tarray->count = count;\n}\n\nvoid arrayDynamicAppend(array_dynamic_t *array, const void *item) {\n\tconst int new_count = array->count + 1;\n\tarrayDynamicEnsureCapacity(array, new_count);\n\n\tif (item)\n\t\tmemcpy((char*)array->items + array->count * array->item_size, item, array->item_size);\n\n\tarray->count = new_count;\n}\n\n"
  },
  {
    "path": "ref/vk/std/arrays.h",
    "content": "#pragma once\n\n#include <stddef.h> // size_t\n\n#define VIEW_DECLARE_CONST(TYPE, NAME) \\\n\tstruct { \\\n\t\tconst TYPE *items; \\\n\t\tint count; \\\n\t} NAME\n\n#define VIEW_DECLARE(TYPE, NAME) \\\n\tstruct { \\\n\t\tTYPE *items; \\\n\t\tint count; \\\n\t} NAME\n\n// Array with compile-time maximum size\n#define BOUNDED_ARRAY_DECLARE(TYPE, NAME, MAX_SIZE) \\\n\t\tstruct { \\\n\t\t\tTYPE items[MAX_SIZE]; \\\n\t\t\tint count; \\\n\t\t} NAME\n\n#define BOUNDED_ARRAY(TYPE, NAME, MAX_SIZE) \\\n\tBOUNDED_ARRAY_DECLARE(TYPE, NAME, MAX_SIZE) = {0}\n\n#define BOUNDED_ARRAY_HAS_SPACE(array_, space_) \\\n\t((COUNTOF((array_).items) - (array_).count) >= space_)\n\n#define BOUNDED_ARRAY_APPEND_UNSAFE(array_) \\\n\t((array_).items[(array_).count++])\n\n#define BOUNDED_ARRAY_APPEND_ITEM(var, item) \\\n\tdo { \\\n\t\tASSERT(BOUNDED_ARRAY_HAS_SPACE(var, 1)); \\\n\t\tvar.items[var.count++] = item; \\\n\t} while(0)\n\n\n// Dynamically-sized array\n// I. Type-agnostic\n\ntypedef struct array_dynamic_s {\n\tvoid *items;\n\tsize_t count, capacity;\n\tsize_t item_size;\n} array_dynamic_t;\n\nvoid arrayDynamicInit(array_dynamic_t *array, int item_size);\nvoid arrayDynamicDestroy(array_dynamic_t *array);\n\nvoid arrayDynamicReserve(array_dynamic_t *array, int capacity);\nvoid arrayDynamicAppend(array_dynamic_t *array, const void *item);\n#define arrayDynamicAppendItem(array, item) \\\n\tdo { \\\n\t\tASSERT((array)->item_size == sizeof(&(item))); \\\n\t\tarrayDynamicAppend(array, item); \\\n\t} while (0)\n/* void *arrayDynamicGet(array_dynamic_t *array, int index); */\n/* #define arrayDynamicAt(array, type, index) \\ */\n/* \t\t(ASSERT((array)->item_size == sizeof(type)), \\ */\n/* \t\t ASSERT((array)->count > (index)), \\ */\n/* \t\t arrayDynamicGet(array, index)) */\nvoid arrayDynamicResize(array_dynamic_t *array, int count);\n//void arrayDynamicErase(array_dynamic_t *array, int begin, int end);\n\n//void arrayDynamicInsert(array_dynamic_t *array, int before, int count, void *items);\n\n// II. Type-specific\n#define ARRAY_DYNAMIC_DECLARE(TYPE, NAME) \\\n\tstruct { \\\n\t\tTYPE *items; \\\n\t\tsize_t count, capacity; \\\n\t\tsize_t item_size; \\\n\t} NAME\n\n#define arrayDynamicInitT(array) \\\n\tarrayDynamicInit((array_dynamic_t*)array, sizeof((array)->items[0]))\n\n#define arrayDynamicDestroyT(array) \\\n\tarrayDynamicDestroy((array_dynamic_t*)array)\n\n#define arrayDynamicResizeT(array, size) \\\n\tarrayDynamicResize((array_dynamic_t*)(array), (size))\n\n#define arrayDynamicReserveT(array, size) \\\n\tarrayDynamicReserve((array_dynamic_t*)(array), (size))\n\n#define arrayDynamicAppendT(array, item) \\\n\tarrayDynamicAppend((array_dynamic_t*)(array), (item))\n\n#define arrayDynamicInsertT(array, before, count, items) \\\n\tarrayDynamicInsert((array_dynamic_t*)(array), before, count, items)\n\n#define arrayDynamicAppendManyT(array, items_count, items) \\\n\tarrayDynamicInsert((array_dynamic_t*)(array), (array)->count, items_count, items)\n"
  },
  {
    "path": "ref/vk/std/bitarray.c",
    "content": "#include \"bitarray.h\"\n\n#include \"vk_core.h\"\n\nbit_array_t bitArrayCreate(uint32_t size) {\n\tsize = (size + 31) / 32;\n\tbit_array_t ret = {\n\t\t.size = size,\n\t\t.bits = Mem_Malloc(vk_core.pool, size * sizeof(uint32_t))\n\t};\n\tbitArrayClear(&ret);\n\treturn ret;\n}\n\nvoid bitArrayDestroy(bit_array_t *ba) {\n\tif (ba->bits)\n\t\tMem_Free(ba->bits);\n\tba->bits = NULL;\n\tba->size = 0;\n}\n\nvoid bitArrayClear(bit_array_t *ba) {\n\tmemset(ba->bits, 0, ba->size * sizeof(uint32_t));\n}\n\nqboolean bitArrayCheckOrSet(bit_array_t *ba, uint32_t index) {\n\tconst uint32_t offset = index / 32;\n\tASSERT(offset < ba->size);\n\n\tuint32_t* bits = ba->bits + offset;\n\n\tconst uint32_t bit = 1u << (index % 32);\n\tif ((*bits) & bit)\n\t\treturn false;\n\n\t(*bits) |= bit;\n\treturn true;\n}\n"
  },
  {
    "path": "ref/vk/std/bitarray.h",
    "content": "#pragma once\n\n#include <xash3d_types.h>\n\ntypedef struct {\n\tuint32_t size;\n\tuint32_t *bits;\n} bit_array_t;\n\nbit_array_t bitArrayCreate(uint32_t size);\nvoid bitArrayDestroy(bit_array_t *ba);\nvoid bitArrayClear(bit_array_t *ba);\n\n// Returns true if wasn't set\nqboolean bitArrayCheckOrSet(bit_array_t *ba, uint32_t index);\n"
  },
  {
    "path": "ref/vk/std/debugbreak.h",
    "content": "/* Copyright (c) 2011-2021, Scott Tsai\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n *    this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n *    this list of conditions and the following disclaimer in the documentation\n *    and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n */\n#ifndef DEBUG_BREAK_H\n#define DEBUG_BREAK_H\n\n#ifdef _MSC_VER\n\n#define debug_break __debugbreak\n\n#else\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1\n#define DEBUG_BREAK_USE_BULTIN_TRAP      2\n#define DEBUG_BREAK_USE_SIGTRAP          3\n\n#if defined(__i386__) || defined(__x86_64__)\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__inline__ static void trap_instruction(void)\n{\n\t__asm__ volatile(\"int $0x03\");\n}\n#elif defined(__thumb__)\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n/* FIXME: handle __THUMB_INTERWORK__ */\n__attribute__((always_inline))\n__inline__ static void trap_instruction(void)\n{\n\t/* See 'arm-linux-tdep.c' in GDB source.\n\t * Both instruction sequences below work. */\n#if 1\n\t/* 'eabi_linux_thumb_le_breakpoint' */\n\t__asm__ volatile(\".inst 0xde01\");\n#else\n\t/* 'eabi_linux_thumb2_le_breakpoint' */\n\t__asm__ volatile(\".inst.w 0xf7f0a000\");\n#endif\n\n\t/* Known problem:\n\t * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB.\n\t * 'step' would keep getting stuck on the same instruction.\n\t *\n\t * Workaround: use the new GDB commands 'debugbreak-step' and\n\t * 'debugbreak-continue' that become available\n\t * after you source the script from GDB:\n\t *\n\t * $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...>\n\t *\n\t * 'debugbreak-step' would jump over the breakpoint instruction with\n\t * roughly equivalent of:\n\t * (gdb) set $instruction_len = 2\n\t * (gdb) tbreak *($pc + $instruction_len)\n\t * (gdb) jump   *($pc + $instruction_len)\n\t */\n}\n#elif defined(__arm__) && !defined(__thumb__)\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__attribute__((always_inline))\n__inline__ static void trap_instruction(void)\n{\n\t/* See 'arm-linux-tdep.c' in GDB source,\n\t * 'eabi_linux_arm_le_breakpoint' */\n\t__asm__ volatile(\".inst 0xe7f001f0\");\n\t/* Known problem:\n\t * Same problem and workaround as Thumb mode */\n}\n#elif defined(__aarch64__) && defined(__APPLE__)\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BULTIN_DEBUGTRAP\n#elif defined(__aarch64__)\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__attribute__((always_inline))\n__inline__ static void trap_instruction(void)\n{\n\t/* See 'aarch64-tdep.c' in GDB source,\n\t * 'aarch64_default_breakpoint' */\n\t__asm__ volatile(\".inst 0xd4200000\");\n}\n#elif defined(__powerpc__)\n\t/* PPC 32 or 64-bit, big or little endian */\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__attribute__((always_inline))\n__inline__ static void trap_instruction(void)\n{\n\t/* See 'rs6000-tdep.c' in GDB source,\n\t * 'rs6000_breakpoint' */\n\t__asm__ volatile(\".4byte 0x7d821008\");\n\n\t/* Known problem:\n\t * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB.\n\t * 'step' stuck on the same instruction (\"twge r2,r2\").\n\t *\n\t * The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py\n\t * or manually jump over the instruction. */\n}\n#elif defined(__riscv)\n\t/* RISC-V 32 or 64-bit, whether the \"C\" extension\n\t * for compressed, 16-bit instructions are supported or not */\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__attribute__((always_inline))\n__inline__ static void trap_instruction(void)\n{\n\t/* See 'riscv-tdep.c' in GDB source,\n\t * 'riscv_sw_breakpoint_from_kind' */\n\t__asm__ volatile(\".4byte 0x00100073\");\n}\n#else\n\t#define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP\n#endif\n\n\n#ifndef DEBUG_BREAK_IMPL\n#error \"debugbreak.h is not supported on this target\"\n#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION\n__attribute__((always_inline))\n__inline__ static void debug_break(void)\n{\n\ttrap_instruction();\n}\n#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_DEBUGTRAP\n__attribute__((always_inline))\n__inline__ static void debug_break(void)\n{\n\t__builtin_debugtrap();\n}\n#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_TRAP\n__attribute__((always_inline))\n__inline__ static void debug_break(void)\n{\n\t__builtin_trap();\n}\n#elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP\n#include <signal.h>\n__attribute__((always_inline))\n__inline__ static void debug_break(void)\n{\n\traise(SIGTRAP);\n}\n#else\n#error \"invalid DEBUG_BREAK_IMPL value\"\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* ifdef _MSC_VER */\n\n#endif /* ifndef DEBUG_BREAK_H */\n"
  },
  {
    "path": "ref/vk/std/flipping.c",
    "content": "#include \"flipping.h\"\n\nvoid R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size) {\n\taloRingInit(&flibuf->ring, size);\n\tR_FlippingBuffer_Clear(flibuf);\n}\n\nvoid R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf) {\n\taloRingInit(&flibuf->ring, flibuf->ring.size);\n\tflibuf->frame_offsets[0] = flibuf->frame_offsets[1] = ALO_ALLOC_FAILED;\n}\n\nuint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align) {\n\tconst uint32_t offset = aloRingAlloc(&flibuf->ring, size, align);\n\tif (offset == ALO_ALLOC_FAILED)\n\t\treturn ALO_ALLOC_FAILED;\n\n\tif (flibuf->frame_offsets[1] == ALO_ALLOC_FAILED)\n\t\tflibuf->frame_offsets[1] = offset;\n\n\treturn offset;\n}\n\nvoid R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf) {\n\tif (flibuf->frame_offsets[0] != ALO_ALLOC_FAILED)\n\t\taloRingFree(&flibuf->ring, flibuf->frame_offsets[0]);\n\n\tflibuf->frame_offsets[0] = flibuf->frame_offsets[1];\n\tflibuf->frame_offsets[1] = ALO_ALLOC_FAILED;\n}\n"
  },
  {
    "path": "ref/vk/std/flipping.h",
    "content": "#pragma once\n\n#include \"std/alolcator.h\"\n\ntypedef struct {\n\talo_ring_t ring;\n\tuint32_t frame_offsets[2];\n} r_flipping_buffer_t;\n\nvoid R_FlippingBuffer_Init(r_flipping_buffer_t *flibuf, uint32_t size);\nuint32_t R_FlippingBuffer_Alloc(r_flipping_buffer_t* flibuf, uint32_t size, uint32_t align);\n\n// (╯°□°)╯︵ ┻━┻\nvoid R_FlippingBuffer_Flip(r_flipping_buffer_t* flibuf);\n\n// ┬─┬ノ( º _ ºノ)\nvoid R_FlippingBuffer_Clear(r_flipping_buffer_t *flibuf);\n"
  },
  {
    "path": "ref/vk/std/pcg.h",
    "content": "// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org\n// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)\n#pragma once\n\n#include <stdint.h>\n\ntypedef struct { uint64_t state;  uint64_t inc; } pcg32_random_t;\n\nuint32_t pcg32_random_r(pcg32_random_t* rng);\n\n#if defined(PCG_IMPLEMENT)\nuint32_t pcg32_random_r(pcg32_random_t* rng)\n{\n    uint64_t oldstate = rng->state;\n    // Advance internal state\n    rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);\n    // Calculate output function (XSH RR), uses old state for max ILP\n    uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;\n    uint32_t rot = oldstate >> 59u;\n    return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));\n}\n#endif\n"
  },
  {
    "path": "ref/vk/std/profiler.c",
    "content": "#define APROF_IMPLEMENT\n#include \"profiler.h\"\n"
  },
  {
    "path": "ref/vk/std/profiler.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n// Note: this module initializes itself on the first scope initialization.\n// I.e. it is invalid to call any of the functions before the first of aprof_scope_init/APROF_SCOPE_INIT/APROF_SCOPE_DECLARE_BEGIN is called.\n// TODO: explicit initialization function\n\n#define APROF_SCOPE_DECLARE(scope) \\\n\tstatic aprof_scope_id_t _aprof_scope_id_##scope = -1\n\n// scope_name is expected to be static and alive for the entire duration of the program\n#define APROF_SCOPE_INIT_EX(scope, scope_name, flags) \\\n\t_aprof_scope_id_##scope = aprof_scope_init(scope_name, flags, __FILE__, __LINE__)\n\n#define APROF_SCOPE_INIT(scope, scope_name) APROF_SCOPE_INIT_EX(scope, scope_name, 0)\n\n#define APROF_SCOPE_BEGIN(scope) \\\n\taprof_scope_event(_aprof_scope_id_##scope, 1)\n\n#define APROF_SCOPE_DECLARE_BEGIN_EX(scope, scope_name, flags) \\\n\tstatic aprof_scope_id_t _aprof_scope_id_##scope = -1; \\\n\tif (_aprof_scope_id_##scope == -1) { \\\n\t\t_aprof_scope_id_##scope = aprof_scope_init(scope_name, flags, __FILE__, __LINE__); \\\n\t} \\\n\taprof_scope_event(_aprof_scope_id_##scope, 1)\n\n#define APROF_SCOPE_DECLARE_BEGIN(scope, scope_name) APROF_SCOPE_DECLARE_BEGIN_EX(scope, scope_name, 0)\n\n#define APROF_TOKENPASTE(x, y) x ## y\n#define APROF_TOKENPASTE2(x, y) APROF_TOKENPASTE(x, y)\n\n#define APROF_SCOPE_BEGIN_EARLY(scope) \\\n\taprof_scope_event(_aprof_scope_id_##scope, 1)\n\n#define APROF_SCOPE_END(scope) \\\n\taprof_scope_event(_aprof_scope_id_##scope, 0)\n\n\ntypedef int aprof_scope_id_t;\n\n// scope_name should be static const, and not on stack\naprof_scope_id_t aprof_scope_init(const char *scope_name, uint32_t flags, const char *source_file, int source_line);\nvoid aprof_scope_event(aprof_scope_id_t, int begin);\n// Returns event index for previous frame\nuint32_t aprof_scope_frame( void );\nuint64_t aprof_time_now_ns( void );\n\n#if defined(_WIN32)\nuint64_t aprof_time_platform_to_ns( uint64_t );\n#else\n#define aprof_time_platform_to_ns(time) ((time) - g_aprof.time_begin_ns)\n#endif\n\nenum {\n\t// This scope covers waiting for external event.\n\t// Its entire subtree shouldn't count towards CPU usage by external analyzers.\n\tAPROF_SCOPE_FLAG_WAIT = (1<<0),\n\n\t// This scope itself doesn't really mean CPU work, and used only as a frame structure decoration.\n\t// External tools shouldn't count it itself as CPU time used, but its children should be.\n\tAPROF_SCOPE_FLAG_DECOR = (1<<1),\n};\n\ntypedef struct {\n\tconst char *name;\n\tuint32_t flags;\n\tconst char *source_file;\n\tint source_line;\n} aprof_scope_t;\n\n#define APROF_MAX_SCOPES 256\n\nenum {\n\tAPROF_EVENT_FRAME_BOUNDARY = 0,\n\tAPROF_EVENT_SCOPE_BEGIN = 1,\n\tAPROF_EVENT_SCOPE_END = 2,\n\tAPROF_EVENT_COUNTER = 3,\n\tAPROF_EVENT_TYPE_MAX = 15,\n};\n\n// Event bits usage\n// Scope begin/end events:\n// 63                   47                   31                   15                0\n// TTTT TTTT TTTT TTTT  TTTT TTTT TTTT TTTT  TTTT TTTT TTTT TTTT  SSSS SSSS .... EEEE\n// T -- timestamp value, ns (48 bits)\n// S -- scope id (16 bits)\n// . -- unused (8 bits)\n// E -- event type (8 bits)\n\n// 4 bits, 0-15\n#define APROF_EVENT_TYPE_MASK 0x0full\n#define APROF_EVENT_TYPE_SHIFT 0\n#define APROF_EVENT_TYPE(event) (((event)&APROF_EVENT_TYPE_MASK) >> APROF_EVENT_TYPE_SHIFT)\n\n// 4 bits hole\n\n// 8 bits, 255\n#define APROF_EVENT_SCOPE_ID_MASK 0xff00ull\n#define APROF_EVENT_SCOPE_ID_SHIFT 8\n#define APROF_EVENT_SCOPE_ID(event) (((event)&APROF_EVENT_SCOPE_ID_MASK) >> APROF_EVENT_SCOPE_ID_SHIFT)\n\n// 48 bits, (a plethora)\n#define APROF_EVENT_TIMESTAMP_SHIFT 16\n#define APROF_EVENT_TIMESTAMP(event) ((event) >> APROF_EVENT_TIMESTAMP_SHIFT)\n\n#define APROF_EVENT_MAKE(type, scope_id, timestamp) \\\n\t(((type) << APROF_EVENT_TYPE_SHIFT) & APROF_EVENT_TYPE_MASK) | \\\n\t(((scope_id) << APROF_EVENT_SCOPE_ID_SHIFT) & APROF_EVENT_SCOPE_ID_MASK) | \\\n\t((timestamp) << APROF_EVENT_TIMESTAMP_SHIFT)\n\n// APROF_EVENT_COUNTER\n// 63                   47                   31                   15                0\n// VVVV VVVV VVVV VVVV  VVVV VVVV VVVV VVVV  VVVV VVVV VVVV VVVV  CCCC CCCC .... EEEE\n// V -- counter value (48 bits)\n// C -- counter index (16 bits)\n// . -- unused (8 bits)\n// E -- event type (8 bits)\n#define APROF_EVENT_COUNTER_INDEX_MASK 0xff00ull\n#define APROF_EVENT_COUNTER_INDEX_SHIFT 8\n#define APROF_EVENT_COUNTER_INDEX(event) (((event)&APROF_EVENT_COUNTER_INDEX_MASK) >> APROF_EVENT_COUNTER_INDEX_SHIFT)\n\n#define APROF_EVENT_COUNTER_VALUE_SHIFT 16\n#define APROF_EVENT_COUNTER_VALUE(event) ((event) >> APROF_EVENT_COUNTER_VALUE_SHIFT)\n\n#define APROF_EVENT_MAKE_COUNTER(counter, value) \\\n\t((((uint64_t)(APROF_EVENT_COUNTER)) << APROF_EVENT_TYPE_SHIFT) & APROF_EVENT_TYPE_MASK) | \\\n\t((((uint64_t)(counter) << APROF_EVENT_COUNTER_INDEX_SHIFT)) & APROF_EVENT_COUNTER_INDEX_MASK) | \\\n\t(((uint64_t)(value)) << APROF_EVENT_COUNTER_VALUE_SHIFT)\n\ntypedef enum {\n\tAprofCounterUnit_Generic,\n\tAprofCounterUnit_Nanoseconds,\n\tAprofCounterUnit_Bytes,\n\tAprofCounterUnit_Permyriad,\n} aprof_counter_unit_t;\n\ntypedef struct {\n\tconst char* name;\n\taprof_counter_unit_t unit;\n} aprof_counter_desc_t;\n\n// MUST be power of 2\n#define APROF_EVENT_BUFFER_SIZE (1<<20)\n#define APROF_EVENT_BUFFER_SIZE_MASK (APROF_EVENT_BUFFER_SIZE - 1)\n\ntypedef uint64_t aprof_event_t;\n\ntypedef struct {\n\tuint64_t time_begin_ns;\n\n\taprof_scope_t scopes[APROF_MAX_SCOPES];\n\tint num_scopes;\n\n\taprof_event_t events[APROF_EVENT_BUFFER_SIZE];\n\tuint32_t events_write;\n\tuint32_t events_last_frame;\n\n\tint current_frame_wraparounds;\n\n\t// TODO event log for chrome://trace (or similar) export and analysis\n} aprof_state_t;\n\nextern aprof_state_t g_aprof;\n\n#if defined(APROF_IMPLEMENT)\n\n#include <string.h>\n\n#ifdef __linux__\n#include <time.h>\nuint64_t aprof_time_now_ns( void ) {\n\tstruct timespec tp;\n\tclock_gettime(CLOCK_MONOTONIC, &tp);\n\treturn tp.tv_nsec + tp.tv_sec * 1000000000ull;\n}\n#elif defined(_WIN32)\n#define WIN32_LEAN_AND_MEAN\n#define WIN32_EXTRA_LEAN\n#include <windows.h>\nstatic LARGE_INTEGER _aprof_frequency;\nuint64_t aprof_time_now_ns( void ) {\n\tLARGE_INTEGER pc;\n\tQueryPerformanceCounter(&pc);\n\treturn pc.QuadPart * 1000000000ull / _aprof_frequency.QuadPart;\n}\n\nuint64_t aprof_time_platform_to_ns( uint64_t platform_time ) {\n\treturn platform_time * 1000000000ull / _aprof_frequency.QuadPart - g_aprof.time_begin_ns;\n}\n#else\n#error aprof is not implemented for this os\n#endif\n\naprof_state_t g_aprof = {0};\n\naprof_scope_id_t aprof_scope_init(const char *scope_name, uint32_t flags, const char *source_file, int source_line) {\n#if defined(_WIN32)\n\tif (_aprof_frequency.QuadPart == 0)\n\t\tQueryPerformanceFrequency(&_aprof_frequency);\n#endif\n\n\tif (!g_aprof.time_begin_ns)\n\t\tg_aprof.time_begin_ns = aprof_time_now_ns();\n\n\tif (g_aprof.num_scopes == APROF_MAX_SCOPES)\n\t\treturn -1;\n\n\tg_aprof.scopes[g_aprof.num_scopes].name = scope_name;\n\tg_aprof.scopes[g_aprof.num_scopes].flags = flags;\n\tg_aprof.scopes[g_aprof.num_scopes].source_file = source_file;\n\tg_aprof.scopes[g_aprof.num_scopes].source_line = source_line;\n\treturn g_aprof.num_scopes++;\n}\n\nvoid aprof_scope_event(aprof_scope_id_t scope_id, int begin) {\n\tconst uint64_t now = aprof_time_now_ns() - g_aprof.time_begin_ns;\n\tif (scope_id < 0 || scope_id >= g_aprof.num_scopes)\n\t\treturn;\n\n\tg_aprof.events[g_aprof.events_write] = APROF_EVENT_MAKE(begin?APROF_EVENT_SCOPE_BEGIN:APROF_EVENT_SCOPE_END, scope_id, now);\n\tg_aprof.events_write = (g_aprof.events_write + 1) & APROF_EVENT_BUFFER_SIZE_MASK;\n\n\tif (g_aprof.events_write == g_aprof.events_last_frame)\n\t\t++g_aprof.current_frame_wraparounds;\n}\n\nuint32_t aprof_scope_frame( void ) {\n\tconst uint64_t now = aprof_time_now_ns() - g_aprof.time_begin_ns;\n\tconst uint32_t previous_frame = g_aprof.events_last_frame;\n\n\tg_aprof.events_last_frame = g_aprof.events_write;\n\tg_aprof.events[g_aprof.events_write] = APROF_EVENT_MAKE(APROF_EVENT_FRAME_BOUNDARY, 0, now);\n\tg_aprof.events_write = (g_aprof.events_write + 1) & APROF_EVENT_BUFFER_SIZE_MASK;\n\n\tg_aprof.current_frame_wraparounds = 0;\n\n\treturn previous_frame;\n}\n\n#endif\n"
  },
  {
    "path": "ref/vk/std/stringview.c",
    "content": "#include \"stringview.h\"\n\n#include <string.h>\n#include <ctype.h> // isspace\n\nconst_string_view_t svFromNullTerminated( const char *s ) {\n\treturn (const_string_view_t){.len = s?strlen(s):0, .s = s};\n}\n\nint svCmp(const_string_view_t sv, const char* s) {\n\tfor (int i = 0; i < sv.len; ++i) {\n\t\tconst int d = sv.s[i] - s[i];\n\t\tif (d != 0)\n\t\t\treturn d;\n\t\tif (s[i] == '\\0')\n\t\t\treturn 1;\n\t}\n\n\t// Check that both strings end the same\n\treturn '\\0' - s[sv.len];\n}\n\nconst_string_view_t svStripExtension(const_string_view_t sv) {\n\tfor (int i = sv.len - 1; i >= 0; --i) {\n\t\tconst char c = sv.s[i];\n\t\tif (c == '.')\n\t\t\treturn (const_string_view_t){ .len = i, .s = sv.s };\n\n\t\tif (c == '/' || c == '\\\\' || c == ':')\n\t\t\tbreak;\n\t}\n\n\treturn sv;\n}\n\n#define MIN(a,b) ((a)<(b)?(a):(b))\n\nvoid svStrncpy(const_string_view_t sv, char *dest, int size) {\n\tconst int to_copy = MIN(sv.len, size - 1);\n\tmemcpy(dest, sv.s, to_copy);\n\tdest[to_copy] = '\\0';\n}\n\nconst_string_view_t svSkipWhitespace(const_string_view_t sv) {\n\twhile (sv.len > 0 && isspace(sv.s[0])) {\n\t\tsv.len--; sv.s++;\n\t}\n\treturn sv;\n}\n\nSVParseLongResult svParseLong(const_string_view_t sv) {\n\tint i = 0;\n\tlong value = 0;\n\tlong sign = 1;\n\n\tif (i < sv.len && sv.s[0] == '-') {\n\t\tsign = -1;\n\t\t++i;\n\t}\n\n\twhile (i < sv.len) {\n\t\tconst char c = sv.s[i++];\n\t\tif (c < '0' || c > '9')\n\t\t\tbreak;\n\n\t\tvalue = value * 10 + (c - '0');\n\t}\n\n\treturn (SVParseLongResult){\n\t\t.value = value * sign,\n\t\t// If only sign was read, then it's not a number\n\t\t.chars_converted = (sign < 0 && i == 1) ? 0 : i,\n\t};\n}\n"
  },
  {
    "path": "ref/vk/std/stringview.h",
    "content": "#pragma once\n\ntypedef struct {\n\tconst char *s;\n\tint len;\n} const_string_view_t;\n\nconst_string_view_t svFromNullTerminated( const char *s );\n\nint svCmp(const_string_view_t sv, const char* s);\nvoid svStrncpy(const_string_view_t sv, char *dest, int size);\n\nconst_string_view_t svStripExtension(const_string_view_t sv);\n\nconst_string_view_t svSkipWhitespace(const_string_view_t sv);\n\ntypedef struct {\n\tlong value;\n\tint chars_converted;\n} SVParseLongResult;\n\n// Parse string view into long\n// Decimal-only for now\n// Does not skip whitespace, do it explicitly\n// Very simple and not very robust -- cannot read LONG_MIN\n// Does not accept '+' as prefix\nSVParseLongResult svParseLong(const_string_view_t sv);\n"
  },
  {
    "path": "ref/vk/std/unordered_roadmap.c",
    "content": "#include \"unordered_roadmap.h\"\n\n#ifndef URMOM_TEST\n#include \"vk_common.h\"\n#include \"vk_logs.h\"\n#else\n#include <string.h>\n#include <stdio.h>\n#include <assert.h>\n#define ERR(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__)\n#define ASSERT(...) assert(__VA_ARGS__)\n#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))\n#define PTR_CAST(type, ptr) ((type*)(void*)(ptr))\n#endif\n\n#if defined(_WIN32) && !defined(strcasecmp)\n#define strcasecmp _stricmp\n#endif\n\nstatic uint32_t hash32FNV1aStr(const char *str) {\n\tstatic const uint32_t fnv_offset_basis = 0x811c9dc5u;\n\tstatic const uint32_t fnv_prime = 0x01000193u;\n\n\tuint32_t hash = fnv_offset_basis;\n\twhile (*str) {\n\t\thash ^= *str;\n\t\thash *= fnv_prime;\n\t\t++str;\n\t}\n\treturn hash;\n}\nstatic uint32_t hash32FNV1aStrI(const char *str) {\n\tstatic const uint32_t fnv_offset_basis = 0x811c9dc5u;\n\tstatic const uint32_t fnv_prime = 0x01000193u;\n\n\tuint32_t hash = fnv_offset_basis;\n\twhile (*str) {\n\t\thash ^= (*str & 0xdf);\n\t\thash *= fnv_prime;\n\t\t++str;\n\t}\n\treturn hash;\n}\n\n// Sets all items to empty\nvoid urmomInit(const urmom_desc_t* desc) {\n\tchar *ptr = desc->array;\n\n\t// Make sure that count is 2^N\n\tASSERT((desc->count & (desc->count - 1)) == 0);\n\n\tfor (int i = 0; i < desc->count; ++i) {\n\t\turmom_header_t *hdr = PTR_CAST(urmom_header_t, ptr + desc->item_size * i);\n\t\thdr->state = 0;\n\t\thdr->hash = 0;\n\t}\n}\n\nstatic uint32_t hashKey(urmom_type_t type, const char *key) {\n\tuint32_t hash;\n\tswitch (type) {\n\t\tcase kUrmomString:\n\t\t\thash = hash32FNV1aStr(key);\n\t\t\tbreak;\n\t\tcase kUrmomStringInsensitive:\n\t\t\thash = hash32FNV1aStrI(key);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tASSERT(!\"Invalid hash table key type\");\n\t}\n\n\treturn hash & 0x7fffffffu;\n}\n\nstatic int sameKey(urmom_type_t type, const char *key1, const char *key2) {\n\tswitch (type) {\n\t\tcase kUrmomString:\n\t\t\treturn strcmp(key1, key2) == 0;\n\t\tcase kUrmomStringInsensitive:\n\t\t\treturn strcasecmp(key1, key2) == 0;\n\t\tdefault:\n\t\t\tASSERT(!\"Invalid hash table key type\");\n\t}\n\n\treturn 0;\n}\n\n// Returns index of the element with the key if found, -1 otherwise\nint urmomFind(const urmom_desc_t* desc, const char* key) {\n\tconst char *ptr = desc->array;\n\tconst uint32_t hash = hashKey(desc->type, key);\n\tconst uint32_t mask = (desc->count - 1);\n\tconst int start_index = hash & mask;\n\n\tfor (int index = start_index;;) {\n\t\tconst urmom_header_t *hdr = PTR_CAST(const urmom_header_t, ptr + desc->item_size * index);\n\n\t\tif (URMOM_IS_OCCUPIED(*hdr)) {\n\t\t\tif (hdr->hash == hash && sameKey(desc->type, key, hdr->key))\n\t\t\t\treturn index;\n\t\t} else if (URMOM_IS_EMPTY(*hdr))\n\t\t\t// Reached the end of non-empty chain, not found\n\t\t\tbreak;\n\n\t\t// No match ;_;, check the next one\n\t\tindex = (index + 1) & mask;\n\n\t\t// Searched through the entire thing\n\t\tif (index == start_index)\n\t\t\tbreak;\n\t}\n\n\treturn -1;\n}\n\n// Returns index of the element either found or empty slot where this could be inserted. If full, -1.\nurmom_insert_t urmomInsert(const urmom_desc_t* desc, const char *key) {\n\tchar *ptr = desc->array;\n\tconst uint32_t hash = hashKey(desc->type, key);\n\tconst uint32_t mask = (desc->count - 1);\n\tconst int start_index = hash & mask;\n\n\tint index = start_index;\n\tint first_available = -1;\n\tfor (;;) {\n\t\tconst urmom_header_t *hdr = PTR_CAST(const urmom_header_t, ptr + desc->item_size * index);\n\n\t\tif (URMOM_IS_OCCUPIED(*hdr)) {\n\t\t\tif (hdr->hash == hash && sameKey(desc->type, key, hdr->key))\n\t\t\t\t// Return existing item\n\t\t\t\treturn (urmom_insert_t){.index = index, .created = 0};\n\t\t} else {\n\t\t\t// Remember the very first item that wasn't occupied\n\t\t\tif (first_available < 0)\n\t\t\t\tfirst_available = index;\n\n\t\t\t// Reached the end of occupied chain, return the available slot\n\t\t\tif (URMOM_IS_EMPTY(*hdr))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tindex = (index + 1) & mask;\n\n\t\t// Searched through the entire thing\n\t\tif (index == start_index)\n\t\t\tbreak;\n\t}\n\n\t// If no slots were encountered, exit with error\n\tif (first_available < 0)\n\t\treturn (urmom_insert_t){.index = -1, .created = 0};\n\n\turmom_header_t *hdr = PTR_CAST(urmom_header_t, ptr + desc->item_size * first_available);\n\thdr->hash = hash;\n\thdr->state = 1;\n\n\t// TODO check for key length\n\tstrncpy(hdr->key, key, sizeof(hdr->key));\n\n\treturn (urmom_insert_t){.index = first_available, .created = 1};\n}\n\n// Return the index of item deleted (if found), -1 otherwise\nint urmomRemove(const urmom_desc_t* desc, const char *key) {\n\tconst int index = urmomFind(desc, key);\n\tif (index >= 0)\n\t\turmomRemoveByIndex(desc, index);\n\treturn index;\n}\n\nvoid urmomRemoveByIndex(const urmom_desc_t* desc, int index) {\n\tchar *ptr = desc->array;\n\turmom_header_t *hdr = PTR_CAST(urmom_header_t, ptr + desc->item_size * index);\n\n\tif (!URMOM_IS_OCCUPIED(*hdr)) {\n\t\tERR(\"Hashmap=%p(is=%d, n=%d): lot %d is not occupied\", desc->array, desc->item_size, desc->count, index);\n\t\treturn;\n\t}\n\n\t// Mark it as deleted\n\t// TODO when can we mark it as empty? For linear search we can do so if the next one is empty\n\thdr->state = 0; // not occupied\n\thdr->hash = 1; // deleted, not empty\n\thdr->key[0] = '\\0';\n}\n"
  },
  {
    "path": "ref/vk/std/unordered_roadmap.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define MAX_KEY_STRING_LENGTH 256\n\n// URMOM = Unordered RoadMap Open addressiMg\n\n// Open-addressed hash table item header\ntypedef struct urmom_header_s {\n\t// state == 1, hash == 0 -- item with hash==0\n\t// state == 0, hash != 0 -- deleted\n\t// state == 0, hash == 0 -- empty\n\tuint32_t state:1;\n\tuint32_t hash:31;\n\n\tchar key[MAX_KEY_STRING_LENGTH];\n} urmom_header_t;\n\n#define URMOM_IS_OCCUPIED(hdr) ((hdr).state != 0)\n#define URMOM_IS_EMPTY(hdr) ((hdr).state == 0 && (hdr).hash == 0)\n#define URMOM_IS_DELETED(hdr) ((hdr).state == 0 && (hdr).hash != 0)\n\n// TODO:\n// - rename this to key type\n// - allow passing key not as const char*, but as string_view\n// - (or even just void*+size, which would almost make it universal)\ntypedef enum {\n\tkUrmomString,\n\tkUrmomStringInsensitive,\n} urmom_type_t;\n\ntypedef struct urmom_desc_s {\n\t// Pointer to the beginning of the array of items.\n\t// Each item is a struct that has urmom_header_t as its first field\n\tvoid *array;\n\n\t// Array item size, including the urmom_header_t\n\tuint32_t item_size;\n\n\t// Maximum number of items in the array\n\tuint32_t count;\n\n\turmom_type_t type;\n} urmom_desc_t;\n\n// Sets all items to empty\nvoid urmomInit(const urmom_desc_t* desc);\n\n// Returns index of the element with the key if found, -1 otherwise\nint urmomFind(const urmom_desc_t* desc, const char* key);\n\n// Returns index of the element either found or empty slot where this could be inserted. If full, -1.\ntypedef struct urmom_insert_s {\n\tint index;\n\tint created;\n} urmom_insert_t;\nurmom_insert_t urmomInsert(const urmom_desc_t* desc, const char *key);\n\n// Return the index of item deleted (if found), -1 otherwise\nint urmomRemove(const urmom_desc_t* desc, const char *key);\n\nvoid urmomRemoveByIndex(const urmom_desc_t* desc, int index);\n\n// TODO erase IS_DELETED tails\n// void urmomCleanup()\n\n// TODO optimize storage: collapse non-tail IS_DELETED sequences. Items will have new placement, so all indexes will be stale\n"
  },
  {
    "path": "ref/vk/tests/unordered_roadmap.c",
    "content": "#include \"../std/unordered_roadmap.h\"\n\n#define URMOM_TEST\n#include \"../std/unordered_roadmap.c\"\n\n#define LOG(msg, ...) \\\n\tfprintf(stderr, \"%s:%d: \" msg \"\\n\", __FILE__, __LINE__, ##__VA_ARGS__)\n\n#define CHECK_EQUAL_I(a, b) \\\n\tdo { \\\n\t\tconst int ar = (a), br = (b); \\\n\t\tif (ar != br) { \\\n\t\t\tLOG(\"CHECK_EQUAL_I(\"#a\", \"#b\") failed: %d != %d\", ar, br); \\\n\t\t\treturn 0; \\\n\t\t} \\\n\t} while (0)\n\n#define CHECK_EQUAL_S(a, b) \\\n\tdo { \\\n\t\tconst char *ar = (a), *br = (b); \\\n\t\tif (strcmp(ar, br) != 0) { \\\n\t\t\tLOG(\"CHECK_EQUAL_S(\"#a\", \"#b\") failed: \\\"%s\\\" != \\\"%s\\\"\", ar, br); \\\n\t\t\treturn 0; \\\n\t\t} \\\n\t} while (0)\n\n#define CHECK_NOT_EQUAL_I(a, b) \\\n\tdo { \\\n\t\tconst int ar = (a), br = (b); \\\n\t\tif (ar == br) { \\\n\t\t\tLOG(\"CHECK_NOT_EQUAL_I(\"#a\", \"#b\") failed: %d == %d\", ar, br); \\\n\t\t\treturn 0; \\\n\t\t} \\\n\t} while (0)\n\ntypedef struct {\n\turmom_header_t hdr_;\n\tint i;\n\tfloat f;\n} item_t;\n\n#define PREAMBLE(N, type_) \\\n\titem_t items[N]; \\\n\tconst urmom_desc_t desc = { \\\n\t\t.array = items, \\\n\t\t.count = COUNTOF(items), \\\n\t\t.item_size = sizeof(item_t), \\\n\t\t.type = type_, \\\n\t}; \\\n\turmomInit(&desc)\n\nstatic int test_insert_find_remove( void ) {\n\tPREAMBLE(4, kUrmomString);\n\n\tconst urmom_insert_t i = urmomInsert(&desc, \"bidonchik\");\n\tCHECK_NOT_EQUAL_I(i.index, -1);\n\tCHECK_EQUAL_I(i.created, 1);\n\tCHECK_EQUAL_S(items[i.index].hdr_.key, \"bidonchik\");\n\n\tconst int found = urmomFind(&desc, \"bidonchik\");\n\tCHECK_EQUAL_I(found, i.index);\n\n\tconst urmom_insert_t i2 = urmomInsert(&desc, \"bidonchik\");\n\tCHECK_EQUAL_I(i2.index, i.index);\n\tCHECK_EQUAL_I(i2.created, 0);\n\tCHECK_EQUAL_S(items[i.index].hdr_.key, \"bidonchik\");\n\n\tconst int removed = urmomRemove(&desc, \"bidonchik\");\n\tCHECK_EQUAL_I(removed, i.index);\n\tCHECK_EQUAL_I(items[i.index].hdr_.key[0], '\\0');\n\n\tconst int not_found = urmomFind(&desc, \"bidonchik\");\n\tCHECK_EQUAL_I(not_found, -1);\n\n\treturn 1;\n}\n\nstatic int test_find_nonexistent( void ) {\n\tPREAMBLE(4, kUrmomString);\n\n\tconst int found = urmomFind(&desc, \"kishochki\");\n\tCHECK_EQUAL_I(found, -1);\n\treturn 1;\n}\n\nstatic int test_insert_find_many( void ) {\n\tPREAMBLE(4, kUrmomString);\n\n\tconst urmom_insert_t a = urmomInsert(&desc, \"smetanka\");\n\tCHECK_NOT_EQUAL_I(a.index, -1);\n\tCHECK_EQUAL_I(a.created, 1);\n\tCHECK_EQUAL_S(items[a.index].hdr_.key, \"smetanka\");\n\n\tconst urmom_insert_t b = urmomInsert(&desc, \"tworog\");\n\tCHECK_NOT_EQUAL_I(b.index, -1);\n\tCHECK_EQUAL_I(b.created, 1);\n\tCHECK_NOT_EQUAL_I(a.index, b.index);\n\tCHECK_EQUAL_S(items[b.index].hdr_.key, \"tworog\");\n\n\tconst int a_found = urmomFind(&desc, \"smetanka\");\n\tconst int b_found = urmomFind(&desc, \"tworog\");\n\n\tCHECK_EQUAL_I(a_found, a.index);\n\tCHECK_EQUAL_I(b_found, b.index);\n\n\treturn 1;\n}\n\nstatic int test_overflow( void ) {\n\tPREAMBLE(4, kUrmomString);\n\n\tconst urmom_insert_t a = urmomInsert(&desc, \"smetanka\");\n\tCHECK_NOT_EQUAL_I(a.index, -1);\n\tCHECK_EQUAL_I(a.created, 1);\n\tCHECK_EQUAL_S(items[a.index].hdr_.key, \"smetanka\");\n\n\tconst urmom_insert_t b = urmomInsert(&desc, \"tworog\");\n\tCHECK_NOT_EQUAL_I(b.index, -1);\n\tCHECK_EQUAL_I(b.created, 1);\n\tCHECK_NOT_EQUAL_I(a.index, b.index);\n\tCHECK_EQUAL_S(items[b.index].hdr_.key, \"tworog\");\n\n\n\tconst urmom_insert_t c = urmomInsert(&desc, \"kefirushka\");\n\tCHECK_NOT_EQUAL_I(c.index, -1);\n\tCHECK_EQUAL_I(c.created, 1);\n\tCHECK_NOT_EQUAL_I(a.index, c.index);\n\tCHECK_NOT_EQUAL_I(b.index, c.index);\n\tCHECK_EQUAL_S(items[c.index].hdr_.key, \"kefirushka\");\n\n\tconst urmom_insert_t d = urmomInsert(&desc, \"ryazhenka\");\n\tCHECK_NOT_EQUAL_I(d.index, -1);\n\tCHECK_EQUAL_I(d.created, 1);\n\tCHECK_NOT_EQUAL_I(a.index, d.index);\n\tCHECK_NOT_EQUAL_I(b.index, d.index);\n\tCHECK_NOT_EQUAL_I(c.index, d.index);\n\tCHECK_EQUAL_S(items[d.index].hdr_.key, \"ryazhenka\");\n\n\t{\n\t\tconst urmom_insert_t e = urmomInsert(&desc, \"riajenka\");\n\t\tCHECK_EQUAL_I(e.index, -1);\n\t\tCHECK_EQUAL_I(e.created, 0);\n\t}\n\n\tconst int d_remove = urmomRemove(&desc, \"ryazhenka\");\n\tCHECK_EQUAL_I(d_remove, d.index);\n\tCHECK_EQUAL_I(items[d_remove].hdr_.state, 0);\n\tCHECK_NOT_EQUAL_I(items[d_remove].hdr_.hash, 0);\n\tCHECK_EQUAL_I(items[d_remove].hdr_.key[0], '\\0');\n\n\tconst urmom_insert_t e = urmomInsert(&desc, \"riajenka\");\n\tCHECK_NOT_EQUAL_I(e.index, -1);\n\tCHECK_EQUAL_I(e.created, 1);\n\tCHECK_NOT_EQUAL_I(a.index, e.index);\n\tCHECK_NOT_EQUAL_I(b.index, e.index);\n\tCHECK_NOT_EQUAL_I(c.index, e.index);\n\tCHECK_EQUAL_S(items[e.index].hdr_.key, \"riajenka\");\n\n\treturn 1;\n}\n\n// Assumes FNV-1a\nstatic int test_hash_collision( void ) {\n\tPREAMBLE(4, kUrmomString);\n\n\tconst urmom_insert_t a = urmomInsert(&desc, \"costarring\");\n\tCHECK_NOT_EQUAL_I(a.index, -1);\n\tCHECK_EQUAL_I(a.created, 1);\n\n\tconst urmom_insert_t b = urmomInsert(&desc, \"liquid\");\n\tCHECK_NOT_EQUAL_I(b.index, -1);\n\tCHECK_EQUAL_I(b.created, 1);\n\tCHECK_NOT_EQUAL_I(b.index, a.index);\n\n\tCHECK_EQUAL_I(items[a.index].hdr_.hash, items[b.index].hdr_.hash);\n\n\tconst int a_found = urmomFind(&desc, \"costarring\");\n\tCHECK_EQUAL_I(a_found, a.index);\n\n\tconst int b_found = urmomFind(&desc, \"liquid\");\n\tCHECK_EQUAL_I(b_found, b.index);\n\n\treturn 1;\n}\n\nstatic int test_insert_find_remove_insensitive( void ) {\n\tPREAMBLE(4, kUrmomStringInsensitive);\n\n\tconst urmom_insert_t i = urmomInsert(&desc, \"bidonchik\");\n\tCHECK_NOT_EQUAL_I(i.index, -1);\n\tCHECK_EQUAL_I(i.created, 1);\n\tCHECK_EQUAL_S(items[i.index].hdr_.key, \"bidonchik\");\n\n\tconst int found = urmomFind(&desc, \"BIDONCHIk\");\n\tCHECK_EQUAL_I(found, i.index);\n\n\tconst urmom_insert_t i2 = urmomInsert(&desc, \"biDONChik\");\n\tCHECK_EQUAL_I(i2.index, i.index);\n\tCHECK_EQUAL_I(i2.created, 0);\n\tCHECK_EQUAL_S(items[i.index].hdr_.key, \"bidonchik\");\n\n\tconst int removed = urmomRemove(&desc, \"bidonCHIK\");\n\tCHECK_EQUAL_I(removed, i.index);\n\tCHECK_EQUAL_I(items[i.index].hdr_.key[0], '\\0');\n\n\tconst int not_found = urmomFind(&desc, \"bidonchik\");\n\tCHECK_EQUAL_I(not_found, -1);\n\n\treturn 1;\n}\n\nstatic int test_fail( void ) {\n\t//CHECK_EQUAL_S(\"sapogi\", \"tapki\");\n\treturn 1;\n}\n\n#define LIST_TESTS(X) \\\n\tX(test_insert_find_remove) \\\n\tX(test_find_nonexistent) \\\n\tX(test_insert_find_many) \\\n\tX(test_hash_collision) \\\n\tX(test_insert_find_remove_insensitive) \\\n\tX(test_fail) \\\n\nint main( void ) {\n\tint retval = 0;\n#define X(f) \\\n\tdo { \\\n\t\tfprintf(stderr, \"Running \" #f \"...\\n\"); \\\n\t\tconst int result = f(); \\\n\t\tfprintf(stderr, #f \" => %s\\n\", result == 0 ? \"FAIL\" : \"OK\" ); \\\n\t\tif (!result) \\\n\t\t\t++retval;\\\n\t} while (0);\n\nLIST_TESTS(X)\n#undef X\n\n\treturn retval;\n}\n"
  },
  {
    "path": "ref/vk/vk_beams.c",
    "content": "#include \"vk_beams.h\"\n#include \"vk_common.h\"\n#include \"camera.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"r_textures.h\"\n#include \"vk_sprite.h\"\n#include \"vk_scene.h\"\n#include \"vk_math.h\"\n#include \"vk_triapi.h\"\n#include \"r_speeds.h\"\n\n#include \"xash3d_types.h\"\n#include \"xash3d_mathlib.h\"\n#include \"customentity.h\"\n#include \"beamdef.h\"\n\n#define NOISE_DIVISIONS\t64\t// don't touch - many tripmines cause the crash when it equal 128\n#define MODULE_NAME \"beams\"\n\ntypedef struct\n{\n\tvec3_t\tpos;\n\tfloat\ttexcoord;\t// Y texture coordinate\n\tfloat\twidth;\n} beamseg_t;\n\nstatic struct {\n\tstruct {\n\t\tint beams;\n\t} stats;\n} g_beam;\n\nqboolean R_BeamInit(void) {\n\tR_SPEEDS_COUNTER(g_beam.stats.beams, \"count\", kSpeedsMetricCount);\n\treturn true;\n}\n\n/*\n==============================================================\n\nFRACTAL NOISE\n\n==============================================================\n*/\nstatic float\trgNoise[NOISE_DIVISIONS+1];\t// global noise array\n\n// freq2 += step * 0.1;\n// Fractal noise generator, power of 2 wavelength\nstatic void FracNoise( float *noise, int divs )\n{\n\tint\tdiv2;\n\n\tdiv2 = divs >> 1;\n\tif( divs < 2 ) return;\n\n\t// noise is normalized to +/- scale\n\tnoise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * gEngine.COM_RandomFloat( -0.125f, 0.125f );\n\n\tif( div2 > 1 )\n\t{\n\t\tFracNoise( &noise[div2], div2 );\n\t\tFracNoise( noise, div2 );\n\t}\n}\n\nstatic void SineNoise( float *noise, int divs )\n{\n\tfloat\tfreq = 0;\n\tfloat\tstep = M_PI_F / (float)divs;\n\tint\ti;\n\n\tfor( i = 0; i < divs; i++ )\n\t{\n\t\tnoise[i] = sin( freq );\n\t\tfreq += step;\n\t}\n}\n\n\n/*\n==============================================================\n\nBEAM MATHLIB\n\n==============================================================\n*/\nstatic void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp )\n{\n\t// direction in worldspace of the center of the beam\n\tvec3_t\tvecBeamCenter;\n\n\tVectorNormalize2( vecBeamDelta, vecBeamCenter );\n\tCrossProduct( g_camera.vforward, vecBeamCenter, pPerp );\n\tVectorNormalize( pPerp );\n}\n\nstatic void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal )\n{\n\t// vTangentY = line vector for beam\n\tvec3_t\tvTangentY, vDirToBeam;\n\n\tVectorSubtract( vStartPos, vNextPos, vTangentY );\n\n\t// vDirToBeam = vector from viewer origin to beam\n\tVectorSubtract( vStartPos, g_camera.vieworg, vDirToBeam );\n\n\t// get a vector that is perpendicular to us and perpendicular to the beam.\n\t// this is used to fatten the beam.\n\tCrossProduct( vTangentY, vDirToBeam, pNormal );\n\tVectorNormalizeFast( pNormal );\n}\n\n\n/*\n==============\nR_BeamCull\n\nCull the beam by bbox\n==============\n*/\nqboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly )\n{\n\tvec3_t\tmins, maxs;\n\tint\ti;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t\tmaxs[i] += 1.0f;\n\t}\n\n\t/* FIXME VK\n\t// check bbox\n\tif( gEngine.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )))\n\t{\n\t\tif( pvsOnly || !R_CullBox( mins, maxs ))\n\t\t{\n\t\t\t// beam is visible\n\t\t\treturn false;\n\t\t}\n\t}\n\t*/\n\treturn false;\n\n\t// beam is culled\n\treturn true;\n}\n\n/*\nstatic float clampf(float v, float min, float max) {\n\tif (v < min) return min;\n\tif (v > max) return max;\n\treturn v;\n}\n\n// FIXME unclear how to organize this\nstatic void applyBrightness( float brightness, rgba_t out ) {\n\tout[0] = out[1] = out[2] = clampf(brightness, 0, 1) * 255.f;\n\tout[3] = 255;\n}\n*/\n\nstatic void TriBrightness( float brightness ) {\n\tTriColor4f( brightness, brightness, brightness, 1.f );\n}\n\nstatic void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags, const vec4_t color )\n{\n\tint\tnoiseIndex, noiseStep;\n\tint\ti, total_segs, segs_drawn;\n\tfloat\tdiv, length, fraction, factor;\n\tfloat\tflMaxWidth, vLast, vStep, brightness;\n\tvec3_t\tperp1, vLastNormal;\n\tbeamseg_t\tcurSeg;\n\n\tif( segments < 2 ) return;\n\n\tlength = VectorLength( delta );\n\tflMaxWidth = width * 0.5f;\n\tdiv = 1.0f / ( segments - 1 );\n\n\tif( length * div < flMaxWidth * 1.414f )\n\t{\n\t\t// here, we have too many segments; we could get overlap... so lets have less segments\n\t\tsegments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f;\n\t\tif( segments < 2 ) segments = 2;\n\t}\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tdiv = 1.0f / (segments - 1);\n\tlength *= 0.01f;\n\tvStep = length * div;\t// Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\n\tif( flags & FBEAM_SINENOISE )\n\t{\n\t\tif( segments < 16 )\n\t\t{\n\t\t\tsegments = 16;\n\t\t\tdiv = 1.0f / ( segments - 1 );\n\t\t}\n\t\tscale *= 100.0f;\n\t\tlength = segments * 0.1f;\n\t}\n\telse\n\t{\n\t\tscale *= length * 2.0f;\n\t}\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tbrightness = 1.0f;\n\tnoiseIndex = 0;\n\n\tif( FBitSet( flags, FBEAM_SHADEIN ))\n\t\tbrightness = 0;\n\n\t// Choose two vectors that are perpendicular to the beam\n\tR_BeamComputePerpendicular( delta, perp1 );\n\n\ttotal_segs = segments;\n\tsegs_drawn = 0;\n\n\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\t// specify all the segments.\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tbeamseg_t\tnextSeg;\n\t\tvec3_t\tvPoint1, vPoint2;\n\n\t\tASSERT( noiseIndex < ( NOISE_DIVISIONS << 16 ));\n\n\t\tfraction = i * div;\n\n\t\tVectorMA( source, fraction, delta, nextSeg.pos );\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tfactor = rgNoise[noiseIndex>>16] * scale;\n\n\t\t\tif( FBitSet( flags, FBEAM_SINENOISE ))\n\t\t\t{\n\t\t\t\tfloat\ts, c;\n\n\t\t\t\tSinCos( fraction * M_PI_F * length + freq, &s, &c );\n\t\t\t\tVectorMA( nextSeg.pos, (factor * s), g_camera.vup, nextSeg.pos );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tVectorMA( nextSeg.pos, (factor * c), g_camera.vright, nextSeg.pos );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorMA( nextSeg.pos, factor, perp1, nextSeg.pos );\n\t\t\t}\n\t\t}\n\n\t\t// specify the next segment.\n\t\tnextSeg.width = width * 2.0f;\n\t\tnextSeg.texcoord = vLast;\n\n\t\tif( segs_drawn > 0 )\n\t\t{\n\t\t\t// Get a vector that is perpendicular to us and perpendicular to the beam.\n\t\t\t// This is used to fatten the beam.\n\t\t\tvec3_t\tvNormal, vAveNormal;\n\n\t\t\tR_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal );\n\n\t\t\tif( segs_drawn > 1 )\n\t\t\t{\n\t\t\t\t// Average this with the previous normal\n\t\t\t\tVectorAdd( vNormal, vLastNormal, vAveNormal );\n\t\t\t\tVectorScale( vAveNormal, 0.5f, vAveNormal );\n\t\t\t\tVectorNormalizeFast( vAveNormal );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVectorCopy( vNormal, vAveNormal );\n\t\t\t}\n\n\t\t\tVectorCopy( vNormal, vLastNormal );\n\n\t\t\t// draw regular segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 );\n\n\t\t\tTriTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tTriNormal3fv( vAveNormal );\n\t\t\tTriVertex3fv( vPoint1 );\n\n\t\t\tTriTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tTriNormal3fv( vAveNormal );\n\t\t\tTriVertex3fv( vPoint2 );\n\t\t}\n\n\t\tcurSeg = nextSeg;\n\t\tsegs_drawn++;\n\n\t\tif( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tif( fraction < 0.5f ) brightness = fraction;\n\t\t\telse brightness = ( 1.0f - fraction );\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEIN ))\n\t\t{\n\t\t\tbrightness = fraction;\n\t\t}\n\t\telse if( FBitSet( flags, FBEAM_SHADEOUT ))\n\t\t{\n\t\t\tbrightness = 1.0f - fraction;\n\t\t}\n\n\t\tif( segs_drawn == total_segs )\n\t\t{\n\t\t\t// draw the last segment\n\t\t\tVectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 );\n\t\t\tVectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 );\n\n\t\t\t// specify the points.\n\t\t\tTriTexCoord2f( 0.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tTriNormal3fv( vLastNormal );\n\t\t\tTriVertex3fv( vPoint1 );\n\n\t\t\tTriTexCoord2f( 1.0f, curSeg.texcoord );\n\t\t\tTriBrightness( brightness );\n\t\t\tTriNormal3fv( vLastNormal );\n\t\t\tTriVertex3fv( vPoint2 );\n\t\t}\n\n\t\tvLast += vStep; // Advance texture scroll (v axis only)\n\t\tnoiseIndex += noiseStep;\n\t}\n\n\tTriEndEx(color, \"beam segs\");\n}\n\nstatic void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, const vec4_t color )\n{\n\tint\ti, noiseIndex, noiseStep;\n\tfloat\tdiv, length, fraction, factor, vLast, vStep;\n\tvec3_t\tlast1, last2, point, screen, screenLast, tmp, normal;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\n\tvStep = length * div; // Texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );\n\tnoiseIndex = 0;\n\n\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2];\n\n\t\t// distort using noise\n\t\tif( scale != 0 )\n\t\t{\n\t\t\tif(( noiseIndex >> 16 ) < NOISE_DIVISIONS )\n\t\t\t{\n\t\t\t\tfactor = rgNoise[noiseIndex>>16] * scale;\n\t\t\t\tVectorMA( point, factor, g_camera.vup, point );\n\n\t\t\t\t// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\t\t\tfactor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI_F * 3 + freq );\n\t\t\t\tVectorMA( point, factor, g_camera.vright, point );\n\t\t\t}\n\t\t}\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\t\t\tVectorScale( g_camera.vup, -tmp[0], normal );\t// Build point along noraml line (normal is -y, x)\n\t\t\tVectorMA( normal, tmp[1], g_camera.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep; // advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t}\n\n\tTriEndEx(color, \"beam torus\");\n}\n\nstatic void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, const vec4_t color )\n{\n\tfloat\tdiv, length, fraction;\n\tfloat\tw, vLast, vStep;\n\tvec3_t\tpoint;\n\tint\ti;\n\n\tif( segments < 2 ) return;\n\n\tif( segments > NOISE_DIVISIONS )\t\t// UNDONE: Allow more segments?\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f;\t// don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\tvStep = length * div;\t\t// Texture length texels per space pixel\n\n\t// scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\t// clamp the beam width\n\tw = fmod( freq, width * 0.1f ) * delta[2];\n\n\tTriBegin( TRI_TRIANGLE_STRIP );\n\n\t// NOTE: we must force the degenerate triangles to be on the edge\n\tfor( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tVectorCopy( source, point );\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 1.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\t\tpoint[0] = s * w + source[0];\n\t\tpoint[1] = c * w + source[1];\n\t\tpoint[2] = source[2];\n\n\t\tTriBrightness( 1.0f );\n\t\tTriTexCoord2f( 0.0f, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep;\t// advance texture scroll (v axis only)\n\t}\n\n\tTriEndEx(color, \"beam disk\");\n}\n\nstatic void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, const vec4_t color )\n{\n\tfloat\tdiv, length, fraction;\n\tfloat\tvLast, vStep;\n\tvec3_t\tpoint;\n\tint\ti;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tif( segments > NOISE_DIVISIONS )\n\t\tsegments = NOISE_DIVISIONS;\n\n\tlength = VectorLength( delta ) * 0.01f;\n\tif( length < 0.5f ) length = 0.5f;\t// don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / (segments - 1);\n\tvStep = length * div;\t\t// texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1 );\n\tscale = scale * length;\n\n\tTriBegin( TRI_TRIANGLE_STRIP );\n\tfor ( i = 0; i < segments; i++ )\n\t{\n\t\tfloat\ts, c;\n\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &s, &c );\n\n\t\tpoint[0] = s * freq * delta[2] + source[0];\n\t\tpoint[1] = c * freq * delta[2] + source[1];\n\t\tpoint[2] = source[2] + width;\n\n\t\tTriBrightness( 0 );\n\t\tTriTexCoord2f( 1, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tpoint[0] = s * freq * ( delta[2] + width ) + source[0];\n\t\tpoint[1] = c * freq * ( delta[2] + width ) + source[1];\n\t\tpoint[2] = source[2] - width;\n\n\t\tTriBrightness( 1 );\n\t\tTriTexCoord2f( 0, vLast );\n\t\tTriVertex3fv( point );\n\n\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\t}\n\tTriEndEx(color, \"beam cylinder\");\n}\n\nstatic void R_DrawBeamFollow( BEAM *pbeam, float frametime, const vec4_t color )\n{\n\tparticle_t\t*pnew, *particles;\n\tfloat\t\tfraction, div, vLast, vStep;\n\tvec3_t\t\tlast1, last2, tmp, screen;\n\tvec3_t\t\tdelta, screenLast, normal;\n\n\tgEngine.R_FreeDeadParticles( &pbeam->particles );\n\n\tparticles = pbeam->particles;\n\tpnew = NULL;\n\n\tdiv = 0;\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tif( particles )\n\t\t{\n\t\t\tVectorSubtract( particles->org, pbeam->source, delta );\n\t\t\tdiv = VectorLength( delta );\n\n\t\t\tif( div >= 32 )\n\t\t\t{\n\t\t\t\tpnew = gEngine.CL_AllocParticleFast();\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tpnew = gEngine.CL_AllocParticleFast();\n\t\t}\n\t}\n\n\tif( pnew )\n\t{\n\t\tVectorCopy( pbeam->source, pnew->org );\n\t\tpnew->die = gp_cl->time + pbeam->amplitude;\n\t\tVectorClear( pnew->vel );\n\n\t\tpnew->next = particles;\n\t\tpbeam->particles = pnew;\n\t\tparticles = pnew;\n\t}\n\n\t// nothing to draw\n\tif( !particles ) return;\n\n\tif( !pnew && div != 0 )\n\t{\n\t\tVectorCopy( pbeam->source, delta );\n\t\tTriWorldToScreen( pbeam->source, screenLast );\n\t\tTriWorldToScreen( particles->org, screen );\n\t}\n\telse if( particles && particles->next )\n\t{\n\t\tVectorCopy( particles->org, delta );\n\t\tTriWorldToScreen( particles->org, screenLast );\n\t\tTriWorldToScreen( particles->next->org, screen );\n\t\tparticles = particles->next;\n\t}\n\telse\n\t{\n\t\treturn;\n\t}\n\n\t// UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the\n\t// first beam segment for this trail\n\n\t// build world-space normal to screen-space direction vector\n\tVectorSubtract( screen, screenLast, tmp );\n\t// we don't need Z, we're in screen space\n\ttmp[2] = 0;\n\tVectorNormalize( tmp );\n\n\t// Build point along noraml line (normal is -y, x)\n\tVectorScale( g_camera.vup, tmp[0], normal );\t// Build point along normal line (normal is -y, x)\n\tVectorMA( normal, tmp[1], g_camera.vright, normal );\n\n\t// Make a wide line\n\tVectorMA( delta, pbeam->width, normal, last1 );\n\tVectorMA( delta, -pbeam->width, normal, last2 );\n\n\tdiv = 1.0f / pbeam->amplitude;\n\tfraction = ( pbeam->die - gp_cl->time ) * div;\n\n\tvLast = 0.0f;\n\tvStep = 1.0f;\n\n\tTriBegin( TRI_QUADS );\n\twhile( particles )\n\t{\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 1 );\n\t\tTriVertex3fv( last2 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 1 );\n\t\tTriVertex3fv( last1 );\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( particles->org, screen );\n\t\t// Build world-space normal to screen-space direction vector\n\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t// we don't need Z, we're in screen space\n\t\ttmp[2] = 0;\n\t\tVectorNormalize( tmp );\n\t\tVectorScale( g_camera.vup, tmp[0], normal );\t// Build point along noraml line (normal is -y, x)\n\t\tVectorMA( normal, tmp[1], g_camera.vright, normal );\n\n\t\t// Make a wide line\n\t\tVectorMA( particles->org, pbeam->width, normal, last1 );\n\t\tVectorMA( particles->org, -pbeam->width, normal, last2 );\n\n\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\n\t\tif( particles->next != NULL )\n\t\t{\n\t\t\tfraction = (particles->die - gp_cl->time) * div;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfraction = 0.0;\n\t\t}\n\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 0, 0 );\n\t\tTriVertex3fv( last1 );\n\t\tTriBrightness( fraction );\n\t\tTriTexCoord2f( 1, 0 );\n\t\tTriVertex3fv( last2 );\n\n\t\tVectorCopy( screen, screenLast );\n\n\t\tparticles = particles->next;\n\t}\n\n\t// drift popcorn trail if there is a velocity\n\tparticles = pbeam->particles;\n\n\twhile( particles )\n\t{\n\t\tVectorMA( particles->org, frametime, particles->vel, particles->org );\n\t\tparticles = particles->next;\n\t}\n\n\tTriEndEx(color, \"beam follow\");\n}\n\nstatic void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments, const vec4_t color )\n{\n\tint\ti, j, noiseIndex, noiseStep;\n\tfloat\tdiv, length, fraction, factor, vLast, vStep;\n\tvec3_t\tlast1, last2, point, screen, screenLast;\n\tvec3_t\ttmp, normal, center, xaxis, yaxis;\n\tfloat\tradius, x, y, scale;\n\n\tif( segments < 2 )\n\t\treturn;\n\n\tVectorClear( screenLast );\n\tsegments = segments * M_PI_F;\n\n\tif( segments > NOISE_DIVISIONS * 8 )\n\t\tsegments = NOISE_DIVISIONS * 8;\n\n\tlength = VectorLength( delta ) * 0.01f * M_PI_F;\n\tif( length < 0.5f ) length = 0.5f;\t\t// Don't lose all of the noise/texture on short beams\n\n\tdiv = 1.0f / ( segments - 1 );\n\n\tvStep = length * div / 8.0f;\t\t\t// texture length texels per space pixel\n\n\t// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)\n\tvLast = fmod( freq * speed, 1.0f );\n\tscale = amplitude * length / 8.0f;\n\n\t// Iterator to resample noise waveform (it needs to be generated in powers of 2)\n\tnoiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8;\n\tnoiseIndex = 0;\n\n\tVectorScale( delta, 0.5f, delta );\n\tVectorAdd( source, delta, center );\n\n\tVectorCopy( delta, xaxis );\n\tradius = VectorLength( xaxis );\n\n\t// cull beamring\n\t// --------------------------------\n\t// Compute box center +/- radius\n\tVectorSet( last1, radius, radius, scale );\n\tVectorAdd( center, last1, tmp );\t\t// maxs\n\tVectorSubtract( center, last1, screen );\t// mins\n\n\t/* FIXME VK\n\tif( !WORLDMODEL )\n\t\treturn;\n\n\t// is that box in PVS && frustum?\n\tif( !gEngine.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp ))\n\t{\n\t\treturn;\n\t}\n\t*/\n\n\tVectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f );\n\tVectorNormalize( yaxis );\n\tVectorScale( yaxis, radius, yaxis );\n\n\tj = segments / 8;\n\n\tTriBegin( TRI_TRIANGLE_STRIP );\n\tfor( i = 0; i < segments + 1; i++ )\n\t{\n\t\tfraction = i * div;\n\t\tSinCos( fraction * M_PI2_F, &x, &y );\n\n\t\tVectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point );\n\n\t\t// distort using noise\n\t\tfactor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;\n\t\tVectorMA( point, factor, g_camera.vup, point );\n\n\t\t// Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal\n\t\tfactor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;\n\t\tfactor *= cos( fraction * M_PI_F * 24 + freq );\n\t\tVectorMA( point, factor, g_camera.vright, point );\n\n\t\t// Transform point into screen space\n\t\tTriWorldToScreen( point, screen );\n\n\t\tif( i != 0 )\n\t\t{\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\t// Build point along normal line (normal is -y, x)\n\t\t\tVectorScale( g_camera.vup, tmp[0], normal );\n\t\t\tVectorMA( normal, tmp[1], g_camera.vright, normal );\n\n\t\t\t// Make a wide line\n\t\t\tVectorMA( point, width, normal, last1 );\n\t\t\tVectorMA( point, -width, normal, last2 );\n\n\t\t\tvLast += vStep;\t// Advance texture scroll (v axis only)\n\t\t\tTriTexCoord2f( 1.0f, vLast );\n\t\t\tTriVertex3fv( last2 );\n\t\t\tTriTexCoord2f( 0.0f, vLast );\n\t\t\tTriVertex3fv( last1 );\n\t\t}\n\n\t\tVectorCopy( screen, screenLast );\n\t\tnoiseIndex += noiseStep;\n\t\tj--;\n\n\t\tif( j == 0 && amplitude != 0 )\n\t\t{\n\t\t\tj = segments / 8;\n\t\t\tFracNoise( rgNoise, NOISE_DIVISIONS );\n\t\t}\n\t}\n\tTriEndEx( color, \"beam ring\" );\n}\n\n/*\n==============\nR_BeamComputePoint\n\ncompute attachment point for beam\n==============\n*/\nstatic qboolean R_BeamComputePoint( int beamEnt, vec3_t pt )\n{\n\tcl_entity_t\t*ent;\n\tint\t\tattach;\n\n\tent = gEngine.R_BeamGetEntity( beamEnt );\n\n\tif( beamEnt < 0 )\n\t\tattach = BEAMENT_ATTACHMENT( -beamEnt );\n\telse attach = BEAMENT_ATTACHMENT( beamEnt );\n\n\tif( !ent )\n\t{\n\t\tgEngine.Con_DPrintf( S_ERROR \"R_BeamComputePoint: invalid entity %i\\n\", BEAMENT_ENTITY( beamEnt ));\n\t\tVectorClear( pt );\n\t\treturn false;\n\t}\n\n\t// get attachment\n\tif( attach > 0 )\n\t\tVectorCopy( ent->attachment[attach - 1], pt );\n\telse if( ent->index == gp_cl->playernum + 1 )\n\t{\n\t\tpt = gp_cl->simorg;\n\t}\n\telse VectorCopy( ent->origin, pt );\n\n\treturn true;\n}\n\n/*\n==============\nR_BeamRecomputeEndpoints\n\nRecomputes beam endpoints..\n==============\n*/\nstatic qboolean R_BeamRecomputeEndpoints( BEAM *pbeam )\n{\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))\n\t{\n\t\tcl_entity_t *start = gEngine.R_BeamGetEntity( pbeam->startEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->startEntity, pbeam->source ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = start->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_STARTVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_STARTENTITY );\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_ENDENTITY ))\n\t{\n\t\tcl_entity_t *end = gEngine.R_BeamGetEntity( pbeam->endEntity );\n\n\t\tif( R_BeamComputePoint( pbeam->endEntity, pbeam->target ))\n\t\t{\n\t\t\tif( !pbeam->pFollowModel )\n\t\t\t\tpbeam->pFollowModel = end->model;\n\t\t\tSetBits( pbeam->flags, FBEAM_ENDVISIBLE );\n\t\t}\n\t\telse if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ENDENTITY );\n\t\t\tpbeam->die = gp_cl->time;\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE ))\n\t\treturn false;\n\treturn true;\n}\n\n\n/*\n==============\nR_BeamDraw\n\nUpdate beam vars and draw it\n==============\n*/\nvoid R_BeamDraw( BEAM *pbeam, float frametime )\n{\n\tmodel_t\t*model;\n\tvec3_t\tdelta;\n\tvec4_t color;\n\tint render_mode;\n\tint\ttexturenum;\n\n\tmodel = gp_cl->models[pbeam->modelIndex];\n\tSetBits( pbeam->flags, FBEAM_ISACTIVE );\n\n\tif( !model || model->type != mod_sprite )\n\t{\n\t\tpbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore\n\t\tpbeam->die = gp_cl->time;\n\t\treturn;\n\t}\n\n\t++g_beam.stats.beams;\n\n\t// update frequency\n\tpbeam->freq += frametime;\n\n\t// generate fractal noise\n\tif( frametime != 0.0f )\n\t{\n\t\trgNoise[0] = 0;\n\t\trgNoise[NOISE_DIVISIONS] = 0;\n\t}\n\n\tif( pbeam->amplitude != 0 && frametime != 0.0f )\n\t{\n\t\tif( FBitSet( pbeam->flags, FBEAM_SINENOISE ))\n\t\t\tSineNoise( rgNoise, NOISE_DIVISIONS );\n\t\telse FracNoise( rgNoise, NOISE_DIVISIONS );\n\t}\n\n\t// update end points\n\tif( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY ))\n\t{\n\t\t// makes sure attachment[0] + attachment[1] are valid\n\t\tif( !R_BeamRecomputeEndpoints( pbeam ))\n\t\t{\n\t\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore\n\t\t\treturn;\n\t\t}\n\n\t\t// compute segments from the new endpoints\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorClear( pbeam->delta );\n\n\t\tif( VectorLength( delta ) > 0.0000001f )\n\t\t\tVectorCopy( delta, pbeam->delta );\n\n\t\tif( pbeam->amplitude >= 0.50f )\n\t\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels\n\t\telse pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels\n\t}\n\n\tif( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 ))\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\t// don't draw really short or inactive beams\n\tif( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f )\n\t{\n\t\treturn;\n\t}\n\n\tif( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT ))\n\t{\n\t\t// update life cycle\n\t\tpbeam->t = pbeam->freq + ( pbeam->die - gp_cl->time );\n\t\tif( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t;\n\t}\n\n\tif( pbeam->type == TE_BEAMHOSE )\n\t{\n\t\tfloat\tflDot;\n\n\t\tVectorSubtract( pbeam->target, pbeam->source, delta );\n\t\tVectorNormalize( delta );\n\n\t\tflDot = DotProduct( delta, g_camera.vforward );\n\n\t\t// abort if the player's looking along it away from the source\n\t\tif( flDot > 0 )\n\t\t{\n\t\t\treturn;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat\tflFade = pow( flDot, 10 );\n\t\t\tvec3_t\tlocalDir, vecProjection, tmp;\n\t\t\tfloat\tflDistance;\n\n\t\t\t// fade the beam if the player's not looking at the source\n\t\t\tVectorSubtract( g_camera.vieworg, pbeam->source, localDir );\n\t\t\tflDot = DotProduct( delta, localDir );\n\t\t\tVectorScale( delta, flDot, vecProjection );\n\t\t\tVectorSubtract( localDir, vecProjection, tmp );\n\t\t\tflDistance = VectorLength( tmp );\n\n\t\t\tif( flDistance > 30 )\n\t\t\t{\n\t\t\t\tflDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f );\n\t\t\t\tif( flDistance <= 0 ) flFade = 0;\n\t\t\t\telse flFade *= pow( flDistance, 3 );\n\t\t\t}\n\n\t\t\tif( flFade < ( 1.0f / 255.0f ))\n\t\t\t\treturn;\n\n\t\t\t// FIXME: needs to be testing\n\t\t\tpbeam->brightness *= flFade;\n\t\t}\n\t}\n\n\trender_mode = FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd;\n\n\ttexturenum = R_GetSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gp_cl->time) % pbeam->frameCount);\n\tif( texturenum <= 0 ) // FIXME VK || texturenum > MAX_TEXTURES )\n\t{\n\t\tClearBits( pbeam->flags, FBEAM_ISACTIVE );\n\t\treturn;\n\t}\n\n\tif( pbeam->type == TE_BEAMFOLLOW )\n\t{\n\t\tcl_entity_t\t*pStart;\n\n\t\t// XASH SPECIFIC: get brightness from head entity\n\t\tpStart = gEngine.R_BeamGetEntity( pbeam->startEntity );\n\t\tif( pStart && pStart->curstate.rendermode != kRenderNormal )\n\t\t\tpbeam->brightness = CL_FxBlend( pStart ) / 255.0f;\n\t}\n\n\tcolor[0] = pbeam->r;\n\tcolor[1] = pbeam->g;\n\tcolor[2] = pbeam->b;\n\tif( FBitSet( pbeam->flags, FBEAM_FADEIN ))\n\t\tcolor[3] = pbeam->t * pbeam->brightness;\n\telse if( FBitSet( pbeam->flags, FBEAM_FADEOUT ))\n\t\tcolor[3] = (1.f - pbeam->t) * pbeam->brightness;\n\telse\n\t\tcolor[3] = pbeam->brightness;\n\n\t// TODO gl renderer has per-vertex color that is updated using brightness and whatever\n\tVK_RenderDebugLabelBegin( \"beam\" );\n\n\tTriSetTexture( texturenum );\n\tTriRenderMode( render_mode );\n\tTriColor4f( color[0], color[1], color[2], color[3] );\n\n\tswitch( pbeam->type )\n\t{\n\tcase TE_BEAMTORUS:\n\t\t// FIXME VK GL_Cull( GL_NONE );\n\t\tR_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );\n\t\tbreak;\n\tcase TE_BEAMDISK:\n\t\t// FIXME VK GL_Cull( GL_NONE );\n\t\tR_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );\n\t\tbreak;\n\tcase TE_BEAMCYLINDER:\n\t\t// FIXME VK GL_Cull( GL_NONE );\n\t\tR_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );\n\t\tbreak;\n\tcase TE_BEAMPOINTS:\n\tcase TE_BEAMHOSE:\n\t\tR_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags, color );\n\t\tbreak;\n\tcase TE_BEAMFOLLOW:\n\t\tR_DrawBeamFollow( pbeam, frametime, color );\n\t\tbreak;\n\tcase TE_BEAMRING:\n\t\t// FIXME VK GL_Cull( GL_NONE );\n\t\tR_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );\n\t\tbreak;\n\t}\n\n\tVK_RenderDebugLabelEnd();\n\n\t// FIXME VK r_stats.c_view_beams_count++;\n}\n\n/*\n==============\nR_BeamSetAttributes\n\nset beam attributes\n==============\n*/\nstatic void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame )\n{\n\tpbeam->frame = (float)startFrame;\n\tpbeam->frameRate = framerate;\n\tpbeam->r = r;\n\tpbeam->g = g;\n\tpbeam->b = b;\n}\n\n/*\n==============\nR_BeamSetup\n\ngeneric function. all beams must be\npassed through this\n==============\n*/\nstatic void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )\n{\n\tmodel_t\t*sprite = gp_cl->models[modelIndex];\n\n\tif( !sprite ) return;\n\n\tpbeam->type = BEAM_POINTS;\n\tpbeam->modelIndex = modelIndex;\n\tpbeam->frame = 0;\n\tpbeam->frameRate = 0;\n\tpbeam->frameCount = sprite->numframes;\n\n\tVectorCopy( start, pbeam->source );\n\tVectorCopy( end, pbeam->target );\n\tVectorSubtract( end, start, pbeam->delta );\n\n\tpbeam->freq = speed * gp_cl->time;\n\tpbeam->die = life + gp_cl->time;\n\tpbeam->amplitude = amplitude;\n\tpbeam->brightness = brightness;\n\tpbeam->width = width;\n\tpbeam->speed = speed;\n\n\tif( amplitude >= 0.50f )\n\t\tpbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f;\t// one per 4 pixels\n\telse pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f;\t\t// one per 16 pixels\n\n\tpbeam->pFollowModel = NULL;\n\tpbeam->flags = 0;\n}\n\n/*\n==============\nR_BeamDrawCustomEntity\n\ninitialize beam from server entity\n==============\n*/\nvoid R_BeamDrawCustomEntity( cl_entity_t *ent, float frametime )\n{\n\tBEAM\tbeam;\n\tfloat\tamp = ent->curstate.body / 100.0f;\n\tfloat\tblend = CL_FxBlend( ent ) / 255.0f;\n\tfloat\tr, g, b;\n\tint\tbeamFlags;\n\n\tr = ent->curstate.rendercolor.r / 255.0f;\n\tg = ent->curstate.rendercolor.g / 255.0f;\n\tb = ent->curstate.rendercolor.b / 255.0f;\n\n\tR_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime );\n\tR_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame );\n\tbeam.pFollowModel = NULL;\n\n\tswitch( ent->curstate.rendermode & 0x0F )\n\t{\n\tcase BEAM_ENTPOINT:\n\t\tbeam.type\t= TE_BEAMPOINTS;\n\t\tif( ent->curstate.sequence )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_STARTENTITY );\n\t\t\tbeam.startEntity = ent->curstate.sequence;\n\t\t}\n\t\tif( ent->curstate.skin )\n\t\t{\n\t\t\tSetBits( beam.flags, FBEAM_ENDENTITY );\n\t\t\tbeam.endEntity = ent->curstate.skin;\n\t\t}\n\t\tbreak;\n\tcase BEAM_ENTS:\n\t\tbeam.type\t= TE_BEAMPOINTS;\n\t\tSetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );\n\t\tbeam.startEntity = ent->curstate.sequence;\n\t\tbeam.endEntity = ent->curstate.skin;\n\t\tbreak;\n\tcase BEAM_HOSE:\n\t\tbeam.type\t= TE_BEAMHOSE;\n\t\tbreak;\n\tcase BEAM_POINTS:\n\t\t// already set up\n\t\tbreak;\n\t}\n\n\tbeamFlags = ( ent->curstate.rendermode & 0xF0 );\n\n\tif( FBitSet( beamFlags, BEAM_FSINE ))\n\t\tSetBits( beam.flags, FBEAM_SINENOISE );\n\n\tif( FBitSet( beamFlags, BEAM_FSOLID ))\n\t\tSetBits( beam.flags, FBEAM_SOLID );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEIN ))\n\t\tSetBits( beam.flags, FBEAM_SHADEIN );\n\n\tif( FBitSet( beamFlags, BEAM_FSHADEOUT ))\n\t\tSetBits( beam.flags, FBEAM_SHADEOUT );\n\n\t// draw it\n\tR_BeamDraw( &beam, frametime );\n}\n\n"
  },
  {
    "path": "ref/vk/vk_beams.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\nstruct cl_entity_s;\nstruct beam_s;\n\nqboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly );\n\nvoid R_BeamDrawCustomEntity( struct cl_entity_s *ent, float frametime );\nvoid R_BeamDraw( struct beam_s *pbeam, float frametime );\n\nqboolean R_BeamInit(void);\n"
  },
  {
    "path": "ref/vk/vk_brush.c",
    "content": "#include \"vk_brush.h\"\n\n#include \"vk_core.h\"\n#include \"vk_math.h\"\n#include \"r_textures.h\"\n#include \"vk_lightmap.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"vk_light.h\"\n#include \"vk_mapents.h\"\n#include \"r_decals.h\"\n#include \"r_speeds.h\"\n#include \"vk_logs.h\"\n#include \"std/profiler.h\"\n#include \"std/arrays.h\"\n\n#include <math.h>\n#include <memory.h>\n\n#define MODULE_NAME \"brush\"\n#define LOG_MODULE brush\n\ntypedef struct {\n\tint surfaces_count;\n\tconst int *surfaces_indices;\n\n\tr_geometry_range_t geometry;\n\tvk_render_model_t render_model;\n} r_brush_water_model_t;\n\ntypedef struct {\n\tfloat texture_width;\n\tint vertices_count;\n\tint vertices_src_offset;\n\tint vertices_dst_offset;\n\tint geometry_index;\n} r_conveyor_t;\n\ntypedef struct vk_brush_model_s {\n\tmodel_t *engine_model;\n\tint patch_rendermode;\n\n\tr_geometry_range_t geometry;\n\n\tvk_render_model_t render_model;\n\tint *surface_to_geometry_index;\n\n\tint *animated_indexes;\n\tint animated_indexes_count;\n\n\tmatrix4x4 prev_transform;\n\tfloat prev_time;\n\n\tr_brush_water_model_t water;\n\tr_brush_water_model_t water_sides;\n\n\tint conveyors_count;\n\tr_conveyor_t *conveyors;\n\tvk_vertex_t *conveyors_vertices;\n\n\t// Polylights which need to be added per-frame dynamically\n\tARRAY_DYNAMIC_DECLARE(struct rt_light_add_polygon_s, dynamic_polylights);\n} vk_brush_model_t;\n\ntypedef struct {\n\tint surfaces;\n\tint vertices;\n\tint indices;\n} water_model_sizes_t;\n\ntypedef struct {\n\tint num_surfaces, num_vertices, num_indices;\n\tint max_texture_id;\n\tint animated_count;\n\tint conveyors_count;\n\tint conveyors_vertices_count;\n\tint sky_surfaces_count;\n\n\twater_model_sizes_t water, side_water;\n} model_sizes_t;\n\ntypedef struct conn_edge_s {\n\tint first_surface;\n\tint count;\n} conn_edge_t;\n\ntypedef struct linked_value_s {\n\t\tint value, link;\n} linked_value_t;\n\n#define MAX_VERTEX_SURFACES 16\ntypedef struct conn_vertex_s {\n\tint count;\n\tlinked_value_t surfs[MAX_VERTEX_SURFACES];\n} conn_vertex_t;\n\nstatic struct {\n\tstruct {\n\t\tint total_vertices, total_indices;\n\t\tint models_drawn;\n\t\tint water_surfaces_drawn;\n\t\tint water_polys_drawn;\n\t} stat;\n\n\tint rtable[MOD_FRAMES][MOD_FRAMES];\n\n\t// Unfortunately the engine only tracks the toplevel worldmodel. *xx submodels, while having their own entities and models, are not lifetime-tracked.\n\t// I.e. the engine doesn't call Mod_ProcessRenderData() on them, so we don't directly know when to create or destroy them.\n\t// Therefore, we need to track them manually and destroy them based on some other external event, e.g. Mod_ProcessRenderData(worldmodel)\n\tvk_brush_model_t *models[MAX_MODELS];\n\tint models_count;\n\n#define MAX_ANIMATED_TEXTURES 256\n\tint updated_textures[MAX_ANIMATED_TEXTURES];\n\n\t// Smoothed normals comptutation\n\t// Connectome for edges and vertices\n\tstruct {\n\t\tint edges_capacity;\n\t\tconn_edge_t *edges;\n\n\t\tint vertices_capacity;\n\t\tconn_vertex_t *vertices;\n\t} conn;\n} g_brush;\n\nstatic void VK_InitRandomTable( void )\n{\n\tint\ttu, tv;\n\n\t// make random predictable\n\tgEngine.COM_SetRandomSeed( 255 );\n\n\tfor( tu = 0; tu < MOD_FRAMES; tu++ )\n\t{\n\t\tfor( tv = 0; tv < MOD_FRAMES; tv++ )\n\t\t{\n\t\t\tg_brush.rtable[tu][tv] = gEngine.COM_RandomLong( 0, 0x7FFF );\n\t\t}\n\t}\n\n\tgEngine.COM_SetRandomSeed( 0 );\n}\n\nqboolean R_BrushInit( void )\n{\n\tVK_InitRandomTable ();\n\n\tR_SPEEDS_COUNTER(g_brush.stat.models_drawn, \"drawn\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_brush.stat.water_surfaces_drawn, \"water.surfaces\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_brush.stat.water_polys_drawn, \"water.polys\", kSpeedsMetricCount);\n\n\treturn true;\n}\n\nvoid R_BrushShutdown( void ) {\n\tif (g_brush.conn.edges)\n\t\tMem_Free(g_brush.conn.edges);\n}\n\n// speed up sin calculations\nstatic const float r_turbsin[] =\n{\n\t#include \"warpsin.h\"\n};\n\n#define SUBDIVIDE_SIZE\t64\n#define TURBSCALE\t\t( 256.0f / ( M_PI2 ))\n\nstatic void addWarpVertIndCounts(const msurface_t *warp, int *num_vertices, int *num_indices) {\n\tfor( const glpoly2_t *p = warp->polys; p; p = p->next ) {\n\t\tconst int triangles = p->numverts - 2;\n\t\t*num_vertices += p->numverts;\n\t\t*num_indices += triangles * 3;\n\t}\n}\n\ntypedef struct {\n\tfloat prev_time;\n\tfloat wave_height;\n\tmsurface_t *warp;\n\n\tvk_vertex_t *dst_vertices;\n\tuint16_t *dst_indices;\n\tvk_render_geometry_t *dst_geometry;\n\n\tint *out_vertex_count, *out_index_count;\n\tqboolean debug;\n} compute_water_polys_t;\n\n#if 0\nstatic qboolean tesselationHasSameOrientation( const msurface_t *surf, qboolean debug ) {\n\tconst glpoly_t *poly = surf->polys;\n\tASSERT(poly);\n\tASSERT(poly->numverts > 2);\n\n\tconst float *v = poly->verts[0];\n\tconst float *const v0 = poly->verts[0];\n\tconst float *const v1 = poly->verts[1];\n\tconst float *const v2 = poly->verts[2];\n\n\tvec3_t e0, e1, normal;\n\tVectorSubtract( v2, v0, e0 );\n\tVectorSubtract( v1, v0, e1 );\n\t/* if (surf->flags & SURF_PLANEBACK) */\n\t/* \tCrossProduct( e1, e0, normal ); */\n\t/* else */\n\t\tCrossProduct( e0, e1, normal );\n\n\t// Debug only\n\tVectorNormalize(normal);\n\tconst float dot = DotProduct(normal, surf->plane->normal);\n\tconst qboolean same = dot > 0;\n\n\tif (debug)\n\tDEBUG(\"   surf=%p back=%d plane=(%f, %f, %f), poly=(%f, %f, %f) dot=%f same=%d\",\n\t\tsurf, !!(surf->flags & SURF_PLANEBACK),\n\t\tsurf->plane->normal[0], surf->plane->normal[1], surf->plane->normal[2],\n\t\tnormal[0], normal[1], normal[2],\n\t\tdot, same\n\t);\n\n\treturn same;\n}\n#endif\n\nstatic void brushComputeWaterPolys( compute_water_polys_t args ) {\n\tconst float time = gp_cl->time;\n\tconst qboolean reverse = false;//!tesselationHasSameOrientation( args.warp, args.debug );\n\n#define MAX_WATER_VERTICES 16\n\tvk_vertex_t poly_vertices[MAX_WATER_VERTICES];\n\n\t// FIXME unused? const qboolean useQuads = FBitSet( warp->flags, SURF_DRAWTURB_QUADS );\n\n\tASSERT(args.warp->polys);\n\n\t// reset fog color for nonlightmapped water\n\t// FIXME VK GL_ResetFogColor();\n\n\tint vertices = 0;\n\tint indices = 0;\n\n\t/* 0x18 = 0001 1000 */\n\t/* 0x9A = 1001 1010 */\n\n\tif (args.debug)\n\tDEBUG(\"W: surf=%p reverse=%d flags=(%08X)%c%c%c%c%c%c%c%c type=%s normal=(%f %f %f)\",\n\t\targs.warp, reverse, args.warp->flags,\n\t\t(args.warp->flags & SURF_PLANEBACK) ? 'B' : '.',\n\t\t(args.warp->flags & SURF_DRAWSKY) ? 'S' : '.',\n\t\t(args.warp->flags & SURF_DRAWTURB_QUADS) ? 'Q' : '.',\n\t\t(args.warp->flags & SURF_DRAWTURB) ? 'U' : '.',\n\t\t(args.warp->flags & SURF_DRAWTILED) ? 'T' : '.',\n\t\t(args.warp->flags & SURF_CONVEYOR) ? 'C' : '.',\n\t\t(args.warp->flags & SURF_UNDERWATER) ? 'W' : '.',\n\t\t(args.warp->flags & SURF_TRANSPARENT) ? 'A' : '.',\n\t\targs.warp->plane->type == PLANE_Z ? \"Z\" :\n\t\t\targs.warp->plane->type == PLANE_Y ? \"Y\" :\n\t\t\targs.warp->plane->type == PLANE_X ? \"X\" :\n\t\t\targs.warp->plane->type == PLANE_NONAXIAL ? \"N\" : \"?\",\n\t\targs.warp->plane->normal[0],\n\t\targs.warp->plane->normal[1],\n\t\targs.warp->plane->normal[2]\n\t);\n\n\n\tfor( const glpoly2_t *p = args.warp->polys; p; p = p->next ) {\n\t\tASSERT(p->numverts <= MAX_WATER_VERTICES);\n\n\t\tconst float *v;\n\t\tif( reverse )\n\t\t\tv = p->verts[0] + ( p->numverts - 1 ) * VERTEXSIZE;\n\t\telse\n\t\t\tv = p->verts[0];\n\n\t\tfor( int i = 0; i < p->numverts; i++ )\n\t\t{\n\t\t\tfloat nv, prev_nv;\n\t\t\tif( args.wave_height )\n\t\t\t{\n\t\t\t\tnv = r_turbsin[(int)(time * 160.0f + v[1] + v[0]) & 255] + 8.0f;\n\t\t\t\tnv = (r_turbsin[(int)(v[0] * 5.0f + time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv;\n\t\t\t\tnv = nv * args.wave_height + v[2];\n\n\t\t\t\tprev_nv = r_turbsin[(int)(args.prev_time * 160.0f + v[1] + v[0]) & 255] + 8.0f;\n\t\t\t\tprev_nv = (r_turbsin[(int)(v[0] * 5.0f + args.prev_time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + prev_nv;\n\t\t\t\tprev_nv = prev_nv * args.wave_height + v[2];\n\t\t\t}\n\t\t\telse\n\t\t\t\tprev_nv = nv = v[2];\n\n\t\t\tconst float os = v[3];\n\t\t\tconst float ot = v[4];\n\n\t\t\tfloat s = os + r_turbsin[(int)((ot * 0.125f + gp_cl->time) * TURBSCALE) & 255];\n\t\t\ts *= ( 1.0f / SUBDIVIDE_SIZE );\n\n\t\t\tfloat t = ot + r_turbsin[(int)((os * 0.125f + gp_cl->time) * TURBSCALE) & 255];\n\t\t\tt *= ( 1.0f / SUBDIVIDE_SIZE );\n\n\t\t\tpoly_vertices[i].pos[0] = v[0];\n\t\t\tpoly_vertices[i].pos[1] = v[1];\n\t\t\tpoly_vertices[i].pos[2] = nv;\n\n\t\t\tpoly_vertices[i].prev_pos[0] = v[0];\n\t\t\tpoly_vertices[i].prev_pos[1] = v[1];\n\t\t\tpoly_vertices[i].prev_pos[2] = prev_nv;\n\n\t\t\tpoly_vertices[i].gl_tc[0] = s;\n\t\t\tpoly_vertices[i].gl_tc[1] = t;\n\n\t\t\tpoly_vertices[i].lm_tc[0] = 0;\n\t\t\tpoly_vertices[i].lm_tc[1] = 0;\n\n\t\t\tVector4Set(poly_vertices[i].color, 255, 255, 255, 255);\n\n\t\t\tpoly_vertices[i].normal[0] = 0;\n\t\t\tpoly_vertices[i].normal[1] = 0;\n\t\t\tpoly_vertices[i].normal[2] = 0;\n\n\t\t\tpoly_vertices[i].tangent[0] = 0;\n\t\t\tpoly_vertices[i].tangent[1] = 0;\n\t\t\tpoly_vertices[i].tangent[2] = 0;\n\n\t\t\tif (i > 1) {\n\t\t\t\tvec3_t e0, e1, normal, tangent;\n\t\t\t\tVectorSubtract( poly_vertices[i - 1].pos, poly_vertices[0].pos, e0 );\n\t\t\t\tVectorSubtract( poly_vertices[i].pos, poly_vertices[0].pos, e1 );\n\t\t\t\tCrossProduct( e1, e0, normal );\n\n\t\t\t\tVectorAdd(normal, poly_vertices[0].normal, poly_vertices[0].normal);\n\t\t\t\tVectorAdd(normal, poly_vertices[i].normal, poly_vertices[i].normal);\n\t\t\t\tVectorAdd(normal, poly_vertices[i - 1].normal, poly_vertices[i - 1].normal);\n\n\t\t\t\tcomputeTangentE(tangent, e0, e1,\n\t\t\t\t\tpoly_vertices[0].gl_tc, poly_vertices[i-1].gl_tc, poly_vertices[i].gl_tc);\n\n\t\t\t\tVectorAdd(tangent, poly_vertices[0].tangent, poly_vertices[0].tangent);\n\t\t\t\tVectorAdd(tangent, poly_vertices[i].tangent, poly_vertices[i].tangent);\n\t\t\t\tVectorAdd(tangent, poly_vertices[i - 1].tangent, poly_vertices[i - 1].tangent);\n\n\t\t\t\targs.dst_indices[indices++] = (uint16_t)(vertices);\n\t\t\t\targs.dst_indices[indices++] = (uint16_t)(vertices + i - 1);\n\t\t\t\targs.dst_indices[indices++] = (uint16_t)(vertices + i);\n\t\t\t}\n\n\t\t\tif( reverse )\n\t\t\t\tv -= VERTEXSIZE;\n\t\t\telse\n\t\t\t\tv += VERTEXSIZE;\n\t\t}\n\n\t\tfor( int i = 0; i < p->numverts; i++ ) {\n\t\t\tVectorNormalize(poly_vertices[i].normal);\n\t\t\tVectorNormalize(poly_vertices[i].tangent);\n#if 0\n\t\t\t//const float dot = DotProduct(poly_vertices[i].normal, args.warp->plane->normal);\n\t\t\t//if (dot < 0.) {\n\t\t\tif (poly_vertices[i].normal[2] < 0.f) {\n\t\t\t\tVector4Set(poly_vertices[i].color, 255, 0, 0, 255);\n\t\t\t\tpoly_vertices[i].pos[0] -= 30.f;\n\t\t\t\tpoly_vertices[i].prev_pos[0] -= 30.f;\n\t\t\t\tpoly_vertices[i].pos[2] -= 1.f;\n\t\t\t\tpoly_vertices[i].prev_pos[2] -= 1.f;\n\t\t\t} else {\n\t\t\t\tVector4Set(poly_vertices[i].color, 0, 255, 0, 255);\n\t\t\t\tpoly_vertices[i].pos[0] += 30.f;\n\t\t\t\tpoly_vertices[i].prev_pos[0] += 30.f;\n\t\t\t\tpoly_vertices[i].pos[2] += 1.f;\n\t\t\t\tpoly_vertices[i].prev_pos[2] += 1.f;\n\t\t\t}\n#endif\n\t\t}\n\n\t\tif (args.debug)\n\t\tDEBUG(\"  poly numvers=%d flags=%08X normal=(%f %f %f)\",\n\t\t\t\tp->numverts, p->flags,\n\t\t\t\tpoly_vertices[0].normal[0],\n\t\t\t\tpoly_vertices[0].normal[1],\n\t\t\t\tpoly_vertices[0].normal[2]\n\t\t\t);\n\n\t\tmemcpy(args.dst_vertices + vertices, poly_vertices, sizeof(vk_vertex_t) * p->numverts);\n\t\tvertices += p->numverts;\n\t}\n\n\t// FIXME VK GL_SetupFogColorForSurfaces();\n\n\t// Render\n\tconst int tex_id = args.warp->texinfo->texture->gl_texturenum;\n\tconst r_vk_material_t material = R_VkMaterialGetForTexture(tex_id);\n\t*args.dst_geometry = (vk_render_geometry_t){\n\t\t.material = material,\n\n\t\t.ye_olde_texture = tex_id,\n\n\t\t.surf_deprecate = args.warp,\n\n\t\t.max_vertex = vertices,\n\t\t.element_count = indices,\n\n\t\t.emissive = {0,0,0},\n\t};\n\n\tRT_GetEmissiveForTexture(args.dst_geometry->emissive, tex_id);\n\t*args.out_vertex_count = vertices;\n\t*args.out_index_count = indices;\n\n\tg_brush.stat.water_surfaces_drawn++;\n\tg_brush.stat.water_polys_drawn += indices / 3;\n}\n\n/*\nstatic vk_render_type_e brushRenderModeToRenderType( int render_mode ) {\n\tswitch (render_mode) {\n\t\tcase kRenderNormal:       return kVkRenderTypeSolid;\n\t\tcase kRenderTransColor:   return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderTransTexture: return kVkRenderType_A_1mA_R;\n\t\tcase kRenderGlow:         return kVkRenderType_A_1mA_R;\n\t\tcase kRenderTransAlpha:   return kVkRenderType_AT;\n\t\tcase kRenderTransAdd:     return kVkRenderType_A_1_R;\n\t\tdefault: ASSERT(!\"Unxpected render_mode\");\n\t}\n\n\treturn kVkRenderTypeSolid;\n}\n*/\n\ntypedef enum {\n\tBrushSurface_Hidden = 0,\n\tBrushSurface_Regular,\n\tBrushSurface_Animated,\n\tBrushSurface_Water,\n\tBrushSurface_WaterSide,\n\tBrushSurface_Sky,\n\tBrushSurface_Conveyor,\n} brush_surface_type_e;\n\nstatic brush_surface_type_e getSurfaceType( const msurface_t *surf, int i, qboolean is_worldmodel );\n\ntypedef struct {\n\tconst model_t *mod;\n\tconst xvk_mapent_func_any_t *func_any;\n\tqboolean is_static;\n\tvk_brush_model_t *bmodel;\n\tconst msurface_t *surf;\n\tint surface_index;\n\tbrush_surface_type_e type;\n\tint tex_id;\n\tconst xvk_patch_surface_t *psurf;\n\tvk_render_geometry_t *model_geometry;\n\tint *emissive_surfaces_count;\n} SurfaceHandleEmissiveArgs;\n\nstatic void surfaceHandleEmissive(SurfaceHandleEmissiveArgs args);\n\ntypedef struct {\n\tconst cl_entity_t *ent;\n\tmsurface_t *surfaces;\n\tr_brush_water_model_t *wmodel;\n\tvk_render_geometry_t *geometries;\n\tfloat prev_time;\n\tqboolean is_creating;\n\n\tvk_brush_model_t *bmodel;\n\tconst xvk_mapent_func_any_t *func_any;\n\tqboolean is_worldmodel;\n\tqboolean is_static;\n} fill_water_surfaces_args_t;\n\nstatic void fillWaterSurfaces( fill_water_surfaces_args_t args ) {\n\tASSERT(args.wmodel->surfaces_count > 0);\n\n\tconst float wave_height = (!args.ent) ? 0.f : args.ent->curstate.scale;\n\n\tconst r_geometry_range_lock_t geom_lock = R_GeometryRangeLock(&args.wmodel->geometry);\n\n\tint vertices_offset = 0;\n\tint indices_offset = 0;\n\tint emissive_surfaces_count = 0;\n\tfor (int i = 0; i < args.wmodel->surfaces_count; ++i) {\n\t\tconst int surf_index = args.wmodel->surfaces_indices[i];\n\t\tmsurface_t *warp = args.surfaces + surf_index;\n\n\t\tif (args.is_creating) {\n\t\t\tconst int orig_tex_id = warp->texinfo->texture->gl_texturenum;\n\t\t\tconst xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surf_index);\n\t\t\tconst brush_surface_type_e type = getSurfaceType(warp, surf_index, args.is_worldmodel);\n\t\t\tsurfaceHandleEmissive((SurfaceHandleEmissiveArgs){\n\t\t\t\t.mod = args.bmodel->engine_model,\n\t\t\t\t.func_any = args.func_any,\n\t\t\t\t.is_static = args.is_static,\n\t\t\t\t.bmodel = args.bmodel,\n\t\t\t\t.surf = warp,\n\t\t\t\t.surface_index = surf_index,\n\t\t\t\t.type = type,\n\t\t\t\t.tex_id = orig_tex_id,\n\t\t\t\t.psurf = psurf,\n\t\t\t\t.model_geometry = args.geometries + i,\n\t\t\t\t.emissive_surfaces_count = &emissive_surfaces_count,\n\t\t\t});\n\t\t}\n\n\t\tint vertices = 0, indices = 0;\n\t\tbrushComputeWaterPolys((compute_water_polys_t){\n\t\t\t.prev_time = args.prev_time,\n\t\t\t.wave_height = wave_height,\n\t\t\t.warp = warp,\n\n\t\t\t.dst_vertices = geom_lock.vertices + vertices_offset,\n\t\t\t.dst_indices = geom_lock.indices + indices_offset,\n\t\t\t.dst_geometry = args.geometries + i,\n\n\t\t\t.out_vertex_count = &vertices,\n\t\t\t.out_index_count = &indices,\n\t\t\t.debug = args.is_creating,\n\t\t});\n\n\t\targs.geometries[i].vertex_offset = args.wmodel->geometry.vertices.unit_offset + vertices_offset;\n\t\targs.geometries[i].index_offset = args.wmodel->geometry.indices.unit_offset + indices_offset;\n\n\t\tvertices_offset += vertices;\n\t\tindices_offset += indices;\n\n\t\tASSERT(vertices_offset  <= args.wmodel->geometry.vertices.count);\n\t\tASSERT(indices_offset <= args.wmodel->geometry.indices.count);\n\t}\n\n\t// Apply all emissive surfaces found\n\tif (emissive_surfaces_count > 0) {\n\t\tINFO(\"Loaded %d polylights, %d dynamic for %s model %s\",\n\t\t\temissive_surfaces_count, (int)args.bmodel->dynamic_polylights.count, args.is_static ? \"static\" : \"movable\", args.bmodel->engine_model->name);\n\t}\n\n\tR_GeometryRangeUnlock( &geom_lock );\n}\n\nstatic qboolean loadPolyLight(rt_light_add_polygon_t *out_polygon, const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive);\n\nstatic qboolean doesTextureChainChange( const texture_t *const base ) {\n\tconst texture_t *cur = base;\n\tif (!cur)\n\t\treturn false;\n\n\tcur = cur->anim_next;\n\twhile (cur && cur != base) {\n\t\tif (cur->gl_texturenum != base->gl_texturenum)\n\t\t\treturn true;\n\t\tcur = cur->anim_next;\n\t}\n\treturn false;\n}\n\nstatic qboolean isSurfaceAnimated( const msurface_t *s, qboolean is_worldmodel ) {\n\tconst texture_t *const base = s->texinfo->texture;\n\n\tif( !base->anim_total && !base->alternate_anims )\n\t\treturn false;\n\n\t/* TODO why did we do this? It doesn't seem to rule out animation really.\n\tif( base->name[0] == '-' )\n\t\treturn false;\n\t*/\n\n\t// Worldmodel cannot be triggered and change between alternate_anims and regular anims,\n\t// therefore it should not be checked. There are lights (e.g. in c2a5) which have alternate anims.\n\t// These lights get incorrectly marked as dynamic, tanking the performance.\n\n\tif (!is_worldmodel && base->alternate_anims && base->gl_texturenum != base->alternate_anims->gl_texturenum)\n\t\treturn true;\n\n\treturn doesTextureChainChange(base) || (!is_worldmodel && doesTextureChainChange(base->alternate_anims));\n}\n\nstatic brush_surface_type_e getSurfaceType( const msurface_t *surf, int i, qboolean is_worldmodel ) {\n// \tif ( i >= 0 && (surf->flags & ~(SURF_PLANEBACK | SURF_UNDERWATER | SURF_TRANSPARENT)) != 0)\n// \t{\n// \t\tDEBUG(\"\\t%d flags: \", i);\n// #define PRINTFLAGS(X) \\\n// \tX(SURF_PLANEBACK) \\\n// \tX(SURF_DRAWSKY) \\\n// \tX(SURF_DRAWTURB_QUADS) \\\n// \tX(SURF_DRAWTURB) \\\n// \tX(SURF_DRAWTILED) \\\n// \tX(SURF_CONVEYOR) \\\n// \tX(SURF_UNDERWATER) \\\n// \tX(SURF_TRANSPARENT)\n\n// #define PRINTFLAG(f) if (FBitSet(surf->flags, f)) DEBUG(\" %s\", #f);\n// \t\tPRINTFLAGS(PRINTFLAG)\n// \t\tDEBUG(\"\\n\");\n// \t}\n\tconst xvk_patch_surface_t *patch_surface = R_VkPatchGetSurface(i);\n\tif (patch_surface && patch_surface->flags & Patch_Surface_Delete)\n\t\treturn BrushSurface_Hidden;\n\n\tif (surf->flags & (SURF_DRAWTURB | SURF_DRAWTURB_QUADS)) {\n\t\tif (!surf->polys)\n\t\t\treturn BrushSurface_Hidden;\n\n\t\t// Water surfaces come in pairs: regular front and the opposite back\n\t\t// This makes ray tracing unhappy as there are coplanar surfaces.\n\t\t// We'd want to turn of the back surface, but SURF_PLANEBACK is not really congruent with\n\t\t// the logical direction of the surface, it just means that glpolys have been produced in\n\t\t// an opposite winding order.\n\t\t// SURF_UNDERWATER seems to be the right flag: it does seem to signal that the surface is\n\t\t// lookint \"out\" from the water, directed towards \"air\".\n\t\tif (surf->flags & SURF_UNDERWATER)\n\t\t\treturn BrushSurface_Hidden;\n\t\t//}\n\n\t\t// Worldmodel doesn't distinguish between !=PLANE_Z sides and not sides.\n\t\t// All water surfaces should be present for worldmodel\n\t\treturn (is_worldmodel || surf->plane->type == PLANE_Z) ? BrushSurface_Water : BrushSurface_WaterSide;\n\t}\n\n\t// Explicitly enable SURF_SKY, otherwise they will be skipped by SURF_DRAWTILED\n\tif( FBitSet( surf->flags, SURF_DRAWSKY ))\n\t\treturn BrushSurface_Sky;\n\n\tif( surf->flags & SURF_CONVEYOR ) {\n\t\treturn BrushSurface_Conveyor;\n\t}\n\n\t//if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) {\n\tif( surf->flags & ( SURF_DRAWTURB | SURF_DRAWTURB_QUADS ) ) {\n\t\t// FIXME don't print this on second sort-by-texture pass\n\t\t//DEBUG(\"Skipping surface %d because of flags %08x\", i, surf->flags);\n\t\treturn BrushSurface_Hidden;\n\t}\n\n\tif( FBitSet( surf->flags, SURF_DRAWTILED )) {\n\t\t//DEBUG(\"Skipping surface %d because of tiled flag\", i);\n\t\treturn BrushSurface_Hidden;\n\t}\n\n\tconst qboolean patched_material = patch_surface && !!(patch_surface->flags & Patch_Surface_Material);\n\tif (!patched_material && isSurfaceAnimated(surf, is_worldmodel)) {\n\t\treturn BrushSurface_Animated;\n\t}\n\n\treturn BrushSurface_Regular;\n}\n\nstatic const xvk_mapent_func_any_t *getModelFuncAnyPatch( const model_t *const mod );\n\ntypedef struct {\n\tvk_brush_model_t *bmodel;\n\tr_brush_water_model_t *wmodel;\n\tconst water_model_sizes_t sizes;\n\tbrush_surface_type_e type;\n\tqboolean is_worldmodel;\n\tqboolean is_static;\n} brush_create_water_model_t;\n\nstatic qboolean brushCreateWaterModel(brush_create_water_model_t args) {\n\tconst model_t *const mod = args.bmodel->engine_model;\n\tconst xvk_mapent_func_any_t *func_any = getModelFuncAnyPatch(mod);\n\n\tconst r_geometry_range_t geometry = R_GeometryRangeAlloc(args.sizes.vertices, args.sizes.indices);\n\tif (!geometry.block_handle.size) {\n\t\tERR(\"Cannot allocate geometry (v=%d, i=%d) for water model %s\",\n\t\t\targs.sizes.vertices, args.sizes.indices, mod->name );\n\t\treturn false;\n\t}\n\n\tvk_render_geometry_t *const geometries = Mem_Malloc(vk_core.pool, sizeof(vk_render_geometry_t) * args.sizes.surfaces);\n\n\tint* const surfaces_indices = Mem_Malloc(vk_core.pool, args.sizes.surfaces * sizeof(int));\n\tint surfaces_count = 0;\n\tfor( int i = 0; i < mod->nummodelsurfaces; ++i) {\n\t\tconst int surface_index = mod->firstmodelsurface + i;\n\t\tconst msurface_t *surf = mod->surfaces + surface_index;\n\n\t\tif (getSurfaceType(surf, surface_index, args.is_worldmodel) == args.type) {\n\t\t\tsurfaces_indices[surfaces_count++] = surface_index;\n\t\t}\n\t}\n\n\tASSERT(surfaces_count == args.sizes.surfaces);\n\n\targs.wmodel->surfaces_indices = surfaces_indices;\n\targs.wmodel->surfaces_count = surfaces_count;\n\targs.wmodel->surfaces_indices = surfaces_indices;\n\targs.wmodel->geometry = geometry;\n\n\tfillWaterSurfaces( (fill_water_surfaces_args_t){\n\t\t.ent = NULL,\n\t\t.surfaces = mod->surfaces,\n\t\t.wmodel = args.wmodel,\n\t\t.geometries = geometries,\n\t\t.prev_time = 0.f,\n\t\t.is_creating = true,\n\n\t\t.bmodel = args.bmodel,\n\t\t.func_any = func_any,\n\t\t.is_worldmodel = args.is_worldmodel,\n\t\t.is_static = args.is_static,\n\t});\n\n\tif (!R_RenderModelCreate(&args.wmodel->render_model, (vk_render_model_init_t){\n\t\t.name = mod->name,\n\t\t.geometries = geometries,\n\t\t.geometries_count = surfaces_count,\n\t\t.dynamic = true,\n\t\t})) {\n\t\tERR(\"Could not create water render model for brush model %s\", mod->name);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic material_mode_e brushMaterialModeForRenderType(vk_render_type_e render_type) {\n\tswitch (render_type) {\n\t\tcase kVkRenderTypeSolid:\n\t\t\treturn kMaterialMode_Opaque;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1mA_RW: // blend: scr*a + dst*(1-a), depth: RW\n\t\tcase kVkRenderType_A_1mA_R:  // blend: scr*a + dst*(1-a), depth test\n\t\t\treturn kMaterialMode_Translucent;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1:   // blend: scr*a + dst, no depth test or write; sprite:kRenderGlow only\n\t\t\treturn kMaterialMode_BlendGlow;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1_R: // blend: scr*a + dst, depth test\n\t\tcase kVkRenderType_1_1_R: // blend: scr + dst, depth test\n\t\t\treturn kMaterialMode_BlendAdd;\n\t\t\tbreak;\n\t\tcase kVkRenderType_AT: // no blend, depth RW, alpha test\n\t\t\treturn kMaterialMode_AlphaTest;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tgEngine.Host_Error(\"Unexpected render type %d\\n\", render_type);\n\t}\n\n\treturn kMaterialMode_Opaque;\n}\n\nstatic void brushDrawWater(r_brush_water_model_t *wmodel, const cl_entity_t *ent, msurface_t *surfaces, int render_type, const vec4_t color, const matrix4x4 transform, const matrix4x4 prev_transform, float prev_time) {\n\tAPROF_SCOPE_DECLARE_BEGIN(brush_draw_water, __FUNCTION__);\n\tASSERT(wmodel->surfaces_count > 0);\n\n\tfillWaterSurfaces((fill_water_surfaces_args_t){\n\t\t.ent = ent,\n\t\t.surfaces = surfaces,\n\t\t.wmodel = wmodel,\n\t\t.geometries = wmodel->render_model.geometries,\n\t\t.prev_time = prev_time,\n\t\t.is_creating = false,\n\t});\n\n\tif (!R_RenderModelUpdate(&wmodel->render_model)) {\n\t\tERR(\"Failed to update brush model \\\"%s\\\" water\", wmodel->render_model.debug_name);\n\t\treturn;\n\t}\n\n\tconst material_mode_e material_mode = brushMaterialModeForRenderType(render_type);\n\tR_RenderModelDraw(&wmodel->render_model, (r_model_draw_t){\n\t\t.render_type = render_type,\n\t\t.material_mode = material_mode,\n\t\t.material_flags = kMaterialFlag_DontCastShadow_Bit,\n\t\t.color = (const vec4_t*)color,\n\t\t.transform = (const matrix4x4*)transform,\n\t\t.prev_transform = (const matrix4x4*)prev_transform,\n\t\t.override = {\n\t\t\t.material = NULL,\n\t\t\t.old_texture = -1,\n\t\t},\n\t});\n\n\tAPROF_SCOPE_END(brush_draw_water);\n}\n\nstatic void computeConveyorOffset(const color24 rendercolor, float tex_width, float time, vec2_t out_offset) {\n\tfloat sy, cy;\n\tfloat flConveyorSpeed = 0.0f;\n\tfloat flRate, flAngle;\n\n\t// TODO\n\t/* if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) ) */\n\t/* { */\n\t/* \t// same as doom speed */\n\t/* \tflConveyorSpeed = -35.0f; */\n\t/* } */\n\t/* else */\n\t{\n\t\tflConveyorSpeed = (rendercolor.g<<8|rendercolor.b) / 16.0f;\n\t\tif( rendercolor.r ) flConveyorSpeed = -flConveyorSpeed;\n\t}\n\n\tflRate = fabs( flConveyorSpeed ) / tex_width;\n\tflAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0;\n\n\t// TODO no SinCos, no\n\tSinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy );\n\tout_offset[0] = cy * flRate * time;\n\tout_offset[1] = sy * flRate * time;\n\n\t// make sure that we are positive\n\tif( out_offset[0] < 0.0f ) out_offset[0] += 1.0f + -(int)out_offset[0];\n\tif( out_offset[1] < 0.0f ) out_offset[1] += 1.0f + -(int)out_offset[1];\n\n\t// make sure that we are in a [0,1] range\n\tout_offset[0] = out_offset[0] - (int)out_offset[0];\n\tout_offset[1] = out_offset[1] - (int)out_offset[1];\n}\n\n/*\n===============\nR_TextureAnimation\n\nReturns the proper texture for a given time and surface\n===============\n*/\nconst texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s )\n{\n\tconst texture_t *base = s->texinfo->texture;\n\tint\tcount, reletive;\n\n\tif( ent && ent->curstate.frame )\n\t{\n\t\tif( base->alternate_anims )\n\t\t\tbase = base->alternate_anims;\n\t}\n\n\tif( !base->anim_total )\n\t\treturn base;\n\n\tif( base->name[0] == '-' )\n\t{\n\t\tint\ttx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES;\n\t\tint\tty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES;\n\n\t\treletive = g_brush.rtable[tx][ty] % base->anim_total;\n\t}\n\telse\n\t{\n\t\tint\tspeed;\n\n\t\t// Quake1 textures uses 10 frames per second\n\t\t/* TODO\n\t\tif( FBitSet( R_TextureGetByIndex( base->gl_texturenum )->flags, TF_QUAKEPAL ))\n\t\t\tspeed = 10;\n\t\telse */ speed = 20;\n\n\t\treletive = (int)(gp_cl->time * speed) % base->anim_total;\n\t}\n\n\tcount = 0;\n\n\twhile( base->anim_min > reletive || base->anim_max <= reletive )\n\t{\n\t\tbase = base->anim_next;\n\n\t\tif( !base || ++count > MOD_FRAMES )\n\t\t\treturn s->texinfo->texture;\n\t}\n\n\treturn base;\n}\n\nvoid R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, const matrix4x4 in_transform ) {\n\t// Expect all buffers to be bound\n\tconst model_t *mod = ent->model;\n\tvk_brush_model_t *bmodel = mod->cache.data;\n\n\tif (!bmodel) {\n\t\tERR(\"Model %s wasn't loaded\", mod->name);\n\t\treturn;\n\t}\n\n\tmatrix4x4 transform;\n\tif (in_transform)\n\t\tMatrix4x4_Copy(transform, in_transform);\n\telse\n\t\tMatrix4x4_LoadIdentity(transform);\n\n\tif (bmodel->patch_rendermode >= 0)\n\t\trender_mode = bmodel->patch_rendermode;\n\n\t// Add dynamic polylights if any\n\tfor (int i = 0; i < bmodel->dynamic_polylights.count; ++i) {\n\t\trt_light_add_polygon_t *const polylight = bmodel->dynamic_polylights.items + i;\n\t\tpolylight->transform_row = (const matrix3x4*)transform;\n\t\tpolylight->dynamic = true;\n\t\tRT_LightAddPolygon(polylight);\n\t}\n\n\tvec4_t color = {1, 1, 1, 1};\n\tvk_render_type_e render_type = kVkRenderTypeSolid;\n\tuint32_t material_flags = kMaterialFlag_None;\n\tswitch (render_mode) {\n\t\tcase kRenderNormal:\n\t\t\tVector4Set(color, 1.f, 1.f, 1.f, 1.f);\n\t\t\trender_type = kVkRenderTypeSolid;\n\t\t\tbreak;\n\t\tcase kRenderTransColor:\n\t\t\trender_type = kVkRenderType_A_1mA_RW;\n\t\t\tVector4Set(color,\n\t\t\t\tent->curstate.rendercolor.r / 255.f,\n\t\t\t\tent->curstate.rendercolor.g / 255.f,\n\t\t\t\tent->curstate.rendercolor.b / 255.f,\n\t\t\t\tblend);\n\t\t\tbreak;\n\t\tcase kRenderTransAdd:\n\t\t\tVector4Set(color, blend, blend, blend, 1.f);\n\t\t\trender_type = kVkRenderType_A_1_R;\n\t\t\tmaterial_flags |= kMaterialFlag_CullBackFace_Bit;\n\t\t\tbreak;\n\t\tcase kRenderTransAlpha:\n\t\t\tif( gEngine.EngineGetParm( PARM_QUAKE_COMPATIBLE, 0 ))\n\t\t\t{\n\t\t\t\trender_type = kVkRenderType_A_1mA_RW;\n\t\t\t\tVector4Set(color, 1.f, 1.f, 1.f, blend);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tVector4Set(color, 1.f, 1.f, 1.f, 1.f);\n\t\t\t\trender_type = kVkRenderType_AT;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase kRenderTransTexture:\n\t\tcase kRenderGlow:\n\t\t\trender_type = kVkRenderType_A_1mA_R;\n\t\t\tVector4Set(color, 1.f, 1.f, 1.f, blend);\n\t\t\tbreak;\n\t}\n\n\t// Only Normal and TransAlpha have lightmaps\n\t// TODO: on big maps more than a single lightmap texture is possible\n\tbmodel->render_model.lightmap = (render_mode == kRenderNormal || render_mode == kRenderTransAlpha) ? 1 : 0;\n\n\tif (bmodel->water.surfaces_count)\n\t\tbrushDrawWater(&bmodel->water, ent, bmodel->engine_model->surfaces, render_type, color, transform, bmodel->prev_transform, bmodel->prev_time);\n\n\tif (bmodel->water_sides.surfaces_count && FBitSet( ent->curstate.effects, EF_WATERSIDES ) ) {\n\t\tbrushDrawWater(&bmodel->water_sides, ent, bmodel->engine_model->surfaces, render_type, color, transform, bmodel->prev_transform, bmodel->prev_time);\n\t}\n\n\t++g_brush.stat.models_drawn;\n\n\tif (bmodel->render_model.num_geometries == 0)\n\t\treturn;\n\n\t// Animate textures\n\t{\n\t\tAPROF_SCOPE_DECLARE_BEGIN(brush_update_textures, \"brush: update animated textures\");\n\t\t// Update animated textures\n\t\tint updated_textures_count = 0;\n\t\tfor (int i = 0; i < bmodel->animated_indexes_count; ++i) {\n\t\t\tconst int geom_index = bmodel->animated_indexes[i];\n\t\t\tvk_render_geometry_t *geom = bmodel->render_model.geometries + geom_index;\n\t\t\tconst int surface_index = geom->surf_deprecate - mod->surfaces;\n\n\t\t\t// Optionally patch by texture_s pointer and run animations\n\t\t\tconst texture_t *t = R_TextureAnimation(ent, geom->surf_deprecate);\n\t\t\tconst int new_tex_id = t->gl_texturenum;\n\t\t\tASSERT(new_tex_id >= 0);\n\n\t\t\t// Animated textures can be emissive\n\t\t\t// Add them as dynamic lights for now. It would probably be better if they were static lights (for worldmodel),\n\t\t\t// but there's no easy way to do it for now.\n\t\t\tvec3_t *emissive = &bmodel->render_model.geometries[geom_index].emissive;\n\t\t\tif (RT_GetEmissiveForTexture(*emissive, new_tex_id)) {\n\t\t\t\trt_light_add_polygon_t polylight;\n\t\t\t\tif (loadPolyLight(&polylight, mod, surface_index, geom->surf_deprecate, *emissive)) {\n\t\t\t\t\tpolylight.dynamic = true;\n\t\t\t\t\tpolylight.transform_row = (const matrix3x4*)&transform;\n\t\t\t\t\tRT_LightAddPolygon(&polylight);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (new_tex_id == geom->ye_olde_texture)\n\t\t\t\tcontinue;\n\n\t\t\tgeom->ye_olde_texture = new_tex_id;\n\t\t\tgeom->material = R_VkMaterialGetForTexture(new_tex_id);\n\t\t\tif (updated_textures_count < MAX_ANIMATED_TEXTURES) {\n\t\t\t\tg_brush.updated_textures[updated_textures_count++] = bmodel->animated_indexes[i];\n\t\t\t}\n\t\t}\n\n\t\tif (updated_textures_count > 0) {\n\t\t\tR_RenderModelUpdateMaterials(&bmodel->render_model, g_brush.updated_textures, updated_textures_count);\n\t\t}\n\t\tAPROF_SCOPE_END(brush_update_textures);\n\t}\n\n\t// Move conveyors\n\tfor (int i = 0; i < bmodel->conveyors_count; ++i) {\n\t\tconst r_conveyor_t *const conv = bmodel->conveyors + i;\n\t\tvec2_t offset = {0, 0};\n\t\tcomputeConveyorOffset(ent->curstate.rendercolor, conv->texture_width, gp_cl->time, offset);\n\n\t\tASSERT(conv->geometry_index >= 0);\n\t\tASSERT(conv->geometry_index < bmodel->render_model.num_geometries);\n\t\tconst r_geometry_range_lock_t lock = R_GeometryRangeLockSubrange(&bmodel->geometry, conv->vertices_dst_offset, conv->vertices_count);\n\n\t\tfor (int j = 0; j < conv->vertices_count; ++j) {\n\t\t\tconst vk_vertex_t *const src = bmodel->conveyors_vertices + conv->vertices_src_offset + j;\n\t\t\tvk_vertex_t *const dst = lock.vertices + j;\n\t\t\t*dst = *src;\n\t\t\tdst->gl_tc[0] = src->gl_tc[0] + offset[0];\n\t\t\tdst->gl_tc[1] = src->gl_tc[1] + offset[1];\n\t\t}\n\n\t\tR_GeometryRangeUnlock(&lock);\n\t}\n\n\tconst material_mode_e material_mode = brushMaterialModeForRenderType(render_type);\n\tR_RenderModelDraw(&bmodel->render_model, (r_model_draw_t){\n\t\t.render_type = render_type,\n\t\t.material_mode = material_mode,\n\t\t.material_flags = material_flags,\n\t\t.color = &color,\n\t\t.transform = &transform,\n\t\t.prev_transform = &bmodel->prev_transform,\n\t\t.override = {\n\t\t\t.material = NULL,\n\t\t\t.old_texture = -1,\n\t\t},\n\t});\n\n\t// draw decals for brush model\n\tfor (int i = 0; i < bmodel->render_model.num_geometries; ++i) {\n\t\tmsurface_t* s = bmodel->render_model.geometries[i].surf_deprecate;\n\n\t\tif (!s || !s->pdecals)\n\t\t\tcontinue;\n\n\t\tR_SetDecalsTransform(&transform);\n\t\tR_DrawSurfaceDecals( s, true, false );\n\t}\n\n\tMatrix4x4_Copy(bmodel->prev_transform, transform);\n\tbmodel->prev_time = gp_cl->time;\n}\n\nstatic model_sizes_t computeSizes( const model_t *mod, qboolean is_worldmodel ) {\n\tmodel_sizes_t sizes = {0};\n\n\tfor( int i = 0; i < mod->nummodelsurfaces; ++i)\n\t{\n\t\tconst int surface_index = mod->firstmodelsurface + i;\n\t\tconst msurface_t *surf = mod->surfaces + surface_index;\n\t\tconst int tex_id = surf->texinfo->texture->gl_texturenum;\n\n\t\tif (tex_id > sizes.max_texture_id)\n\t\t\tsizes.max_texture_id = tex_id;\n\n\t\tswitch (getSurfaceType(surf, surface_index, is_worldmodel)) {\n\t\tcase BrushSurface_Water:\n\t\t\tsizes.water.surfaces++;\n\t\t\taddWarpVertIndCounts(surf, &sizes.water.vertices, &sizes.water.indices);\n\t\t\tcontinue;\n\t\tcase BrushSurface_WaterSide:\n\t\t\tsizes.side_water.surfaces++;\n\t\t\taddWarpVertIndCounts(surf, &sizes.side_water.vertices, &sizes.side_water.indices);\n\t\t\tcontinue;\n\t\tcase BrushSurface_Hidden:\n\t\t\tcontinue;\n\n\t\tcase BrushSurface_Animated:\n\t\t\tsizes.animated_count++;\n\t\t\tbreak;\n\t\tcase BrushSurface_Conveyor:\n\t\t\tsizes.conveyors_count++;\n\t\t\tsizes.conveyors_vertices_count += surf->numedges;\n\t\t\tbreak;\n\t\tcase BrushSurface_Sky:\n\t\t\tsizes.sky_surfaces_count++;\n\n\t\t\t// Do not count towards surfaces that we'll load (still need to count if for the purpose of loading skybox)\n\t\t\tif (g_map_entities.remove_all_sky_surfaces)\n\t\t\t\tcontinue;\n\t\t\tbreak;\n\t\tcase BrushSurface_Regular:\n\t\t\tbreak;\n\t\t}\n\n\t\t++sizes.num_surfaces;\n\t\tsizes.num_vertices += surf->numedges;\n\t\tsizes.num_indices += 3 * (surf->numedges - 1);\n\t}\n\n\tDEBUG(\"Computed sizes for brush model \\\"%s\\\":\", mod->name);\n\tDEBUG(\"  num_surfaces=%d animated_count=%d num_vertices=%d num_indices=%d max_texture_id=%d\",\n\t\tsizes.num_surfaces, sizes.animated_count, sizes.num_vertices, sizes.num_indices, sizes.max_texture_id);\n\tDEBUG(\"  conveyors_count=%d conveyors_vertices_count=%d\",\n\t\tsizes.conveyors_count, sizes.conveyors_vertices_count);\n\tDEBUG(\"  water_surfaces=%d water_vertices=%d water_indices=%d\",\n\t\tsizes.water.surfaces, sizes.water.vertices, sizes.water.indices);\n\tDEBUG(\"  side_water_surfaces=%d side_water_vertices=%d side_water_indices=%d\",\n\t\tsizes.side_water.surfaces, sizes.side_water.vertices, sizes.side_water.indices);\n\n\treturn sizes;\n}\n\ntypedef struct {\n\tconst model_t *mod;\n\tvk_brush_model_t *bmodel;\n\tmodel_sizes_t sizes;\n\tuint32_t base_vertex_offset;\n\tuint32_t base_index_offset;\n\n\tvk_render_geometry_t *out_geometries;\n\tvk_vertex_t *out_vertices;\n\tuint16_t *out_indices;\n\tconst xvk_mapent_func_any_t *func_any;\n\tqboolean is_worldmodel;\n\tqboolean is_static;\n} fill_geometries_args_t;\n\nstatic void getSurfaceNormal( const msurface_t *surf, vec3_t out_normal) {\n\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\tVectorNegate( surf->plane->normal, out_normal );\n\telse\n\t\tVectorCopy( surf->plane->normal, out_normal );\n\n\t// TODO scale normal by area -- bigger surfaces should have bigger impact\n\t// NOTE scaling normal by area might be totally incorrect in many circumstances\n\t// The more corect logic there is way more difficult\n\t//VectorScale(normal, surf->plane.\n}\n\nstatic qboolean shouldSmoothLinkSurfaces(const model_t* mod, qboolean smooth_entire_model, int surf1, int surf2) {\n\t// Filter explicit exclusion\n\tfor (int i = 0; i < g_map_entities.smoothing.excluded_pairs_count; i+=2) {\n\t\tconst int cand1 = g_map_entities.smoothing.excluded_pairs[i];\n\t\tconst int cand2 = g_map_entities.smoothing.excluded_pairs[i+1];\n\n\t\tif ((cand1 == surf1 && cand2 == surf2)\n\t\t\t|| (cand1 == surf2 && cand2 == surf1))\n\t\t\treturn false;\n\t}\n\n\tqboolean excluded = false;\n\tfor (int i = 0; i < g_map_entities.smoothing.excluded_count; ++i) {\n\t\tconst int cand = g_map_entities.smoothing.excluded[i];\n\t\tif (cand == surf1 || cand == surf2) {\n\t\t\texcluded = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (smooth_entire_model && !excluded)\n\t\treturn true;\n\n\t// Smoothing groups have priority over individual exclusion.\n\t// That way we can exclude a surface from smoothing with most of its neighbours,\n\t// but still smooth it with some.\n\tfor (int i = 0; i < g_map_entities.smoothing.groups_count; ++i) {\n\t\tconst xvk_smoothing_group_t *g = g_map_entities.smoothing.groups + i;\n\t\tuint32_t bits = 0;\n\t\tfor (int j = 0; j < g->count; ++j) {\n\t\t\tif (g->surfaces[j] == surf1) {\n\t\t\t\tbits |= 1;\n\t\t\t\tif (bits == 3)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse if (g->surfaces[j] == surf2) {\n\t\t\t\tbits |= 2;\n\t\t\t\tif (bits == 3)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (excluded)\n\t\treturn false;\n\n\t// Do not join surfaces with different textures. Assume they belong to different objects.\n\t{\n\t\t// Should we also check texture/material patches too to filter out pairs which originally had\n\t\t// same textures, but with patches do not?\n\t\tif (mod->surfaces[surf1].texinfo->texture->gl_texturenum\n\t\t\t!= mod->surfaces[surf2].texinfo->texture->gl_texturenum)\n\t\t\treturn false;\n\t}\n\n\tvec3_t n1, n2;\n\tgetSurfaceNormal(mod->surfaces + surf1, n1);\n\tgetSurfaceNormal(mod->surfaces + surf2, n2);\n\n\tconst float dot = DotProduct(n1, n2);\n\t// TODO smooth verbose group DEBUG(\"Smoothing: dot(%d, %d) = %f (t=%f)\", surf1, surf2, dot, g_map_entities.smoothing.threshold);\n\n\treturn dot >= g_map_entities.smoothing.threshold;\n}\n\nstatic int lvFindValue(const linked_value_t *li, int count, int needle) {\n\tfor (int i = 0; i < count; ++i)\n\t\tif (li[i].value == needle)\n\t\t\treturn i;\n\treturn -1;\n}\nstatic int lvFindOrAddValue(linked_value_t *li, int *count, int capacity, int needle) {\n\tconst int found = lvFindValue(li, *count, needle);\n\tif (found >= 0)\n\t\treturn found;\n\tif (*count == capacity)\n\t\treturn -1;\n\tli[*count].value = needle;\n\tli[*count].link = *count;\n\treturn (*count)++;\n}\n\nstatic int lvFindBaseIndex(const linked_value_t *li, int index) {\n\twhile (li[index].link != index)\n\t\tindex = li[index].link;\n\treturn index;\n}\n\nstatic void lvFlatten(linked_value_t *li, int count) {\n\tfor (int i = 0; i < count; ++i) {\n\t\tfor (int j = i; j < count; ++j) {\n\t\t\tif (lvFindBaseIndex(li, j) == i) {\n\t\t\t\tli[j].link = i;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void linkSmoothSurfaces(const model_t* mod, int surf1, int surf2, int vertex_index) {\n\tconn_vertex_t *v = g_brush.conn.vertices + vertex_index;\n\n\tint i1 = lvFindOrAddValue(v->surfs, &v->count, COUNTOF(v->surfs), surf1);\n\tint i2 = lvFindOrAddValue(v->surfs, &v->count, COUNTOF(v->surfs), surf2);\n\n\t// TODO smooth_verbose DEBUG(\"Link %d(%d)<->%d(%d) v=%d\", surf1, i1, surf2, i2, vertex_index);\n\n\tif (i1 < 0 || i2 < 0) {\n\t\tERR(\"Model %s cannot smooth link surf %d<->%d for vertex %d\", mod->name, surf1, surf2, vertex_index);\n\t\treturn;\n\t}\n\n\ti1 = lvFindBaseIndex(v->surfs, i1);\n\ti2 = lvFindBaseIndex(v->surfs, i2);\n\n\t// Link them\n\tv->surfs[Q_max(i1, i2)].link = Q_min(i1, i2);\n}\n\nstatic void connectVertices( const model_t *mod, qboolean smooth_entire_model ) {\n\tif (mod->numedges > g_brush.conn.edges_capacity) {\n\t\tif (g_brush.conn.edges)\n\t\t\tMem_Free(g_brush.conn.edges);\n\n\t\tg_brush.conn.edges_capacity = mod->numedges;\n\t\tg_brush.conn.edges = Mem_Calloc(vk_core.pool, sizeof(*g_brush.conn.edges) * g_brush.conn.edges_capacity);\n\t}\n\n\tif (mod->numvertexes > g_brush.conn.vertices_capacity) {\n\t\tif (g_brush.conn.vertices)\n\t\t\tMem_Free(g_brush.conn.vertices);\n\n\t\tg_brush.conn.vertices_capacity = mod->numvertexes;\n\t\tg_brush.conn.vertices = Mem_Calloc(vk_core.pool, sizeof(*g_brush.conn.vertices) * g_brush.conn.vertices_capacity);\n\t}\n\n\t// Find connection edges\n\tfor (int i = 0; i < mod->nummodelsurfaces; ++i) {\n\t\tconst int surface_index = mod->firstmodelsurface + i;\n\t\tconst msurface_t *surf = mod->surfaces + surface_index;\n\n\t\tfor(int k = 0; k < surf->numedges; k++) {\n\t\t\tconst int iedge_dir = mod->surfedges[surf->firstedge + k];\n\t\t\tconst int iedge = iedge_dir >= 0 ? iedge_dir : -iedge_dir;\n\n\t\t\tASSERT(iedge >= 0);\n\t\t\tASSERT(iedge < mod->numedges);\n\n\t\t\tconn_edge_t *cedge = g_brush.conn.edges + iedge;\n\t\t\tif (cedge->count == 0) {\n\t\t\t\tcedge->first_surface = surface_index;\n\t\t\t} else {\n\t\t\t\tconst medge16_t *edge = mod->edges16 + iedge;\n\t\t\t\tif (shouldSmoothLinkSurfaces(mod, smooth_entire_model, cedge->first_surface, surface_index)) {\n\t\t\t\t\tlinkSmoothSurfaces(mod, cedge->first_surface, surface_index, edge->v[0]);\n\t\t\t\t\tlinkSmoothSurfaces(mod, cedge->first_surface, surface_index, edge->v[1]);\n\t\t\t\t}\n\n\t\t\t\tif (cedge->count > 1) {\n\t\t\t\t\tWARN(\"Model %s edge %d has %d surfaces\", mod->name, i, cedge->count);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcedge->count++;\n\t\t} // for surf->numedges\n\t} // for mod->nummodelsurfaces\n\n\tint hist[17] = {0};\n\tfor (int i = 0; i < mod->numvertexes; ++i) {\n\t\tconn_vertex_t *vtx = g_brush.conn.vertices + i;\n\t\tif (vtx->count < 16) {\n\t\t\thist[vtx->count]++;\n\t\t} else {\n\t\t\thist[16]++;\n\t\t}\n\n\t\tlvFlatten(vtx->surfs, vtx->count);\n\n// Too verbose\n#if 0\n\t\tif (vtx->count) {\n\t\t\tDEBUG(\"Vertex %d linked count %d\", i, vtx->count);\n\t\t\tfor (int j = 0; j < vtx->count; ++j) {\n\t\t\t\tDEBUG(\"  %d: l=%d v=%d\", j, vtx->surfs[j].link, vtx->surfs[j].value);\n\t\t\t}\n\t\t}\n#endif\n\t}\n\n\t/* TODO smooth_debug\n\tfor (int i = 0; i < COUNTOF(hist); ++i) {\n\t\tDEBUG(\"VTX hist[%d] = %d\", i, hist[i]);\n\t}\n\t*/\n}\n\nstatic qboolean getSmoothedNormalFor(const model_t* mod, int vertex_index, int surface_index, vec3_t out_normal) {\n\tconst conn_vertex_t *v = g_brush.conn.vertices + vertex_index;\n\tconst int index = lvFindValue(v->surfs, v->count, surface_index);\n\tif (index < 0)\n\t\treturn false;\n\tconst int base = lvFindBaseIndex(v->surfs, index);\n\n\tvec3_t normal = {0};\n\tfor (int i = 0; i < v->count; ++i) {\n\t\tif (v->surfs[i].link == base) {\n\t\t\tconst int surface = v->surfs[i].value;\n\t\t\tvec3_t surf_normal = {0};\n\t\t\tgetSurfaceNormal(mod->surfaces + surface, surf_normal);\n\t\t\tVectorAdd(normal, surf_normal, normal);\n\t\t}\n\t}\n\n\tVectorNormalize(normal);\n\tVectorCopy(normal, out_normal);\n\treturn true;\n}\n\nstatic const xvk_mapent_func_any_t *getModelFuncAnyPatch( const model_t *const mod ) {\n\tfor (int i = 0; i < g_map_entities.func_any_count; ++i) {\n\t\tconst xvk_mapent_func_any_t *const fw = g_map_entities.func_any + i;\n\t\tif (Q_strcmp(mod->name, fw->model) == 0) {\n\t\t\treturn fw;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic void surfaceHandleEmissive(SurfaceHandleEmissiveArgs args) {\n\tVectorClear(args.model_geometry->emissive);\n\n\tswitch (args.type) {\n\tcase BrushSurface_Regular:\n\tcase BrushSurface_Water:\n\t// No known cases, also needs to be dynamic case BrushSurface_WaterSide:\n\t\tbreak;\n\t// Animated textures are enumerated in `R_BrushModelDraw()` and are added as dynamic lights\n\t// when their current frame is emissive. Do not add such surfaces here to avoid adding them twice.\n\t// TODO: Most of the animated surfaces are techically static: i.e. they don't really move.\n\t// Make a special case for static lights that can be off.\n\tcase BrushSurface_Animated:\n\tdefault:\n\t\treturn;\n\t}\n\n\tvec3_t emissive;\n\tif (args.psurf && (args.psurf->flags & Patch_Surface_Emissive)) {\n\t\tVectorCopy(args.psurf->emissive, emissive);\n\t} else if (RT_GetEmissiveForTexture(emissive, args.tex_id)) {\n\t\t// emissive\n\t} else {\n\t\t// not emissive, continue to the next\n\t\treturn;\n\t}\n\n\tDEBUG(\"emissive[%d] surf_index=%d tex_id=%d patch=%d(%#x) => emissive=(%f,%f,%f)\",\n\t\t*args.emissive_surfaces_count, args.surface_index, args.tex_id, !!args.psurf, args.psurf?args.psurf->flags:0, emissive[0], emissive[1], emissive[2]);\n\n\t(*args.emissive_surfaces_count)++;\n\n\t/* const qboolean is_water = type == BrushSurface_Water; */\n\tVectorCopy(emissive, args.model_geometry->emissive);\n\n\trt_light_add_polygon_t polylight;\n\tif (!loadPolyLight(&polylight, args.mod, args.surface_index, args.surf, emissive))\n\t\treturn;\n\n\t// func_any surfaces do not really belong to BSP+PVS system, so they can't be used\n\t// for lights visibility calculation directly.\n\tif (args.func_any && args.func_any->origin_patched) {\n\t\t// TODO this is not really dynamic, but this flag signals using MovingSurface visibility calc\n\t\tpolylight.dynamic = true;\n\t\tmatrix3x4 m;\n\t\tMatrix3x4_LoadIdentity(m);\n\t\tMatrix3x4_SetOrigin(m, args.func_any->origin[0], args.func_any->origin[1], args.func_any->origin[2]);\n\t\tpolylight.transform_row = &m;\n\t}\n\n\t// Static emissive surfaces are added immediately, as they are drawn all the time.\n\t// Non-static ones will be applied later when the model is actually rendered\n\t// Non-static brush models may move around and so must have their emissive surfaces treated as dynamic\n\tif (args.is_static) {\n\t\tRT_LightAddPolygon(&polylight);\n\n\t\t/* TODO figure out when this is needed.\n\t\t * This is needed in cases where we can dive into emissive acid, which should illuminate what's under it\n\t\t * Likely, this is not a correct fix, though, see https://github.com/w23/xash3d-fwgs/issues/56\n\t\tif (is_water) {\n\t\t\t// Add backside for water\n\t\t\tfor (int i = 0; i < polylight.num_vertices; ++i) {\n\t\t\t\tvec3_t tmp;\n\t\t\t\tVectorCopy(polylight.vertices[i], tmp);\n\t\t\t\tVectorCopy(polylight.vertices[polylight.num_vertices-1-i], polylight.vertices[i]);\n\t\t\t\tVectorCopy(tmp, polylight.vertices[polylight.num_vertices-1-i]);\n\t\t\t\tRT_LightAddPolygon(&polylight);\n\t\t\t}\n\t\t}\n\t\t*/\n\t} else {\n\t\tarrayDynamicAppendT(&args.bmodel->dynamic_polylights, &polylight);\n\t}\n}\n\nstatic qboolean fillBrushSurfaces(fill_geometries_args_t args) {\n\tint vertex_offset = 0;\n\tint num_geometries = 0;\n\tint animated_count = 0;\n\tint conveyors_count = 0;\n\tint conveyors_vertices_count = 0;\n\tint emissive_surfaces_count = 0;\n\n\tvk_vertex_t *p_vert = args.out_vertices;\n\tuint16_t *p_ind = args.out_indices;\n\tint index_offset = args.base_index_offset;\n\n\tconst xvk_mapent_func_any_t *const entity_patch = getModelFuncAnyPatch(args.mod);\n\tif (entity_patch) {\n\t\tDEBUG(\"Found entity_patch(matmap_count=%d, rendermode_patched=%d rendermode=%d) for model \\\"%s\\\"\",\n\t\t\tentity_patch->matmap_count, entity_patch->rendermode_patched, entity_patch->rendermode, args.mod->name);\n\n\t\tif (entity_patch->rendermode_patched > 0)\n\t\t\targs.bmodel->patch_rendermode = entity_patch->rendermode;\n\t}\n\n\tconnectVertices(args.mod, entity_patch ? entity_patch->smooth_entire_model : false);\n\n\t// Load sorted by gl_texturenum\n\t// TODO this does not make that much sense in vulkan (can sort later)\n\tfor (int t = 0; t <= args.sizes.max_texture_id; ++t) {\n\t\tfor( int i = 0; i < args.mod->nummodelsurfaces; ++i) {\n\t\t\tconst int surface_index = args.mod->firstmodelsurface + i;\n\t\t\tmsurface_t *surf = args.mod->surfaces + surface_index;\n\t\t\tconst mextrasurf_t *info = surf->info;\n\t\t\tvk_render_geometry_t *model_geometry = args.out_geometries + num_geometries;\n\t\t\tconst float sample_size = gEngine.Mod_SampleSizeForFace( surf );\n\t\t\tint index_count = 0;\n\t\t\tvec3_t tangent;\n\t\t\tconst int orig_tex_id = surf->texinfo->texture->gl_texturenum;\n\t\t\tif (t != orig_tex_id)\n\t\t\t\tcontinue;\n\n\t\t\tint tex_id = orig_tex_id;\n\n\t\t\t// TODO this patching should probably override entity patching below\n\t\t\tconst xvk_patch_surface_t *const psurf = R_VkPatchGetSurface(surface_index);\n\t\t\tconst brush_surface_type_e type = getSurfaceType(surf, surface_index, args.is_worldmodel);\n\t\t\tswitch (type) {\n\t\t\tcase BrushSurface_Water:\n\t\t\tcase BrushSurface_WaterSide:\n\t\t\tcase BrushSurface_Hidden:\n\t\t\t\tcontinue;\n\t\t\tcase BrushSurface_Animated:\n\t\t\t\targs.bmodel->animated_indexes[animated_count++] = num_geometries;\n\t\t\t\tbreak;\n\t\t\tcase BrushSurface_Conveyor:\n\t\t\t\tbreak;\n\t\t\tcase BrushSurface_Sky:\n\t\t\t\tif (g_map_entities.remove_all_sky_surfaces)\n\t\t\t\t\tcontinue;\n\t\t\tcase BrushSurface_Regular:\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tsurfaceHandleEmissive((SurfaceHandleEmissiveArgs){\n\t\t\t\t.mod = args.mod,\n\t\t\t\t.func_any = args.func_any,\n\t\t\t\t.is_static = args.is_static,\n\t\t\t\t.bmodel = args.bmodel,\n\t\t\t\t.surf = surf,\n\t\t\t\t.surface_index = surface_index,\n\t\t\t\t.type = type,\n\t\t\t\t.tex_id = tex_id,\n\t\t\t\t.psurf = psurf,\n\t\t\t\t.model_geometry = model_geometry,\n\t\t\t\t.emissive_surfaces_count = &emissive_surfaces_count,\n\t\t\t});\n\n\t\t\targs.bmodel->surface_to_geometry_index[i] = num_geometries;\n\n\t\t\t// Fill conveyor data if conveyor\n\t\t\tr_conveyor_t *conv = NULL;\n\t\t\tif (type == BrushSurface_Conveyor) {\n\t\t\t\tASSERT(conveyors_count < args.sizes.conveyors_count);\n\t\t\t\tconv = &args.bmodel->conveyors[conveyors_count++];\n\n\t\t\t\tconv->vertices_count = surf->numedges;\n\n\t\t\t\tconv->vertices_dst_offset = vertex_offset;\n\t\t\t\tconv->vertices_src_offset = conveyors_vertices_count;\n\t\t\t\tconveyors_vertices_count += conv->vertices_count;\n\t\t\t\tASSERT(conveyors_vertices_count <= args.sizes.conveyors_vertices_count);\n\n\t\t\t\tconv->geometry_index = num_geometries;\n\n\t\t\t\tconv->texture_width = R_TexturesGetParm(PARM_TEX_WIDTH, orig_tex_id);\n\t\t\t}\n\n\t\t\t++num_geometries;\n\n\t\t\t//DEBUG( \"surface %d: numverts=%d numedges=%d\", i, surf->polys ? surf->polys->numverts : -1, surf->numedges );\n\n\t\t\tif (vertex_offset + surf->numedges >= UINT16_MAX) {\n\t\t\t\t// We might be able to handle it by adjusting base_vertex_offset, etc\n\t\t\t\tERR(\"Model %s indices don't fit into 16 bits\", args.mod->name);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tmodel_geometry->ye_olde_texture = orig_tex_id;\n\t\t\tqboolean material_assigned = false;\n\n\t\t\tif (psurf && (psurf->flags & Patch_Surface_Material)) {\n\t\t\t\tmodel_geometry->material = R_VkMaterialGetForRef(psurf->material_ref);\n\t\t\t\tmaterial_assigned = true;\n\t\t\t}\n\n\t\t\tif (!material_assigned && entity_patch) {\n\t\t\t\tfor (int i = 0; i < entity_patch->matmap_count; ++i) {\n\t\t\t\t\tif (entity_patch->matmap[i].from_tex == orig_tex_id) {\n\t\t\t\t\t\tmodel_geometry->material = R_VkMaterialGetForRef(entity_patch->matmap[i].to_mat);\n\t\t\t\t\t\tDEBUG(\"  Assigning entity_patch/material[%d] for surf=%d to mat ref=%d\",\n\t\t\t\t\t\t\ti, surface_index, entity_patch->matmap[i].to_mat.index);\n\t\t\t\t\t\tmaterial_assigned = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!material_assigned && entity_patch->rendermode > 0) {\n\t\t\t\t\tmaterial_assigned = R_VkMaterialGetEx(tex_id, entity_patch->rendermode, &model_geometry->material);\n\t\t\t\t\tif (!material_assigned && entity_patch->rendermode == kRenderTransColor) {\n\t\t\t\t\t\t// TransColor means ignore textures and draw just color\n\t\t\t\t\t\tmodel_geometry->material = R_VkMaterialGetForTexture(tglob.whiteTexture);\n\t\t\t\t\t\tmodel_geometry->ye_olde_texture = tglob.whiteTexture;\n\t\t\t\t\t\tmaterial_assigned = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!material_assigned) {\n\t\t\t\tmodel_geometry->material = R_VkMaterialGetForTexture(tex_id);\n\t\t\t\tmaterial_assigned = true;\n\t\t\t}\n\n\t\t\t// Make sure animated textures undergo at least the first update\n\t\t\t// To update emissive and other texture states\n\t\t\tif (type == BrushSurface_Animated)\n\t\t\t\tmodel_geometry->ye_olde_texture = -1;\n\n\t\t\tmodel_geometry->surf_deprecate = surf;\n\n\t\t\tmodel_geometry->vertex_offset = args.base_vertex_offset;\n\t\t\tmodel_geometry->max_vertex = vertex_offset + surf->numedges;\n\n\t\t\tmodel_geometry->index_offset = index_offset;\n\n\t\t\tif ( type == BrushSurface_Sky ) {\n\t\t\t\tmodel_geometry->material.tex_base_color = TEX_BASE_SKYBOX;\n\t\t\t\tmodel_geometry->ye_olde_texture = TEX_BASE_SKYBOX;\n\t\t\t} else {\n\t\t\t\tASSERT(!FBitSet( surf->flags, SURF_DRAWTILED ));\n\t\t\t\tVK_CreateSurfaceLightmap( surf, args.mod );\n\t\t\t}\n\n\t\t\tvec3_t surf_normal;\n\t\t\tgetSurfaceNormal(surf, surf_normal);\n\n\t\t\tvec3_t p[3];\n\t\t\tfor( int k = 0; k < surf->numedges; k++ )\n\t\t\t{\n\t\t\t\tconst int iedge_dir = args.mod->surfedges[surf->firstedge + k];\n\t\t\t\tconst int iedge = iedge_dir >= 0 ? iedge_dir : -iedge_dir;\n\t\t\t\tconst medge16_t *edge = args.mod->edges16 + iedge;\n\t\t\t\tconst int vertex_index = iedge_dir >= 0 ? edge->v[0] : edge->v[1];\n\t\t\t\tconst mvertex_t *in_vertex = args.mod->vertexes + vertex_index;\n\n\n\t\t\t\tvk_vertex_t vertex = {\n\t\t\t\t\t.pos = {in_vertex->position[0], in_vertex->position[1], in_vertex->position[2]},\n\t\t\t\t};\n\n\t\t\t\tvertex.prev_pos[0] = in_vertex->position[0];\n\t\t\t\tvertex.prev_pos[1] = in_vertex->position[1];\n\t\t\t\tvertex.prev_pos[2] = in_vertex->position[2];\n\n\t\t\t\t// Compute texture coordinates, process tangent\n\t\t\t\t{\n\t\t\t\t\tvec4_t svec, tvec;\n\t\t\t\t\tif (psurf && (psurf->flags & Patch_Surface_TexMatrix)) {\n\t\t\t\t\t\tsvec[0] = surf->texinfo->vecs[0][0] * psurf->texmat_s[0] + surf->texinfo->vecs[1][0] * psurf->texmat_s[1];\n\t\t\t\t\t\tsvec[1] = surf->texinfo->vecs[0][1] * psurf->texmat_s[0] + surf->texinfo->vecs[1][1] * psurf->texmat_s[1];\n\t\t\t\t\t\tsvec[2] = surf->texinfo->vecs[0][2] * psurf->texmat_s[0] + surf->texinfo->vecs[1][2] * psurf->texmat_s[1];\n\t\t\t\t\t\tsvec[3] = surf->texinfo->vecs[0][3] + psurf->texmat_s[2];\n\n\t\t\t\t\t\ttvec[0] = surf->texinfo->vecs[0][0] * psurf->texmat_t[0] + surf->texinfo->vecs[1][0] * psurf->texmat_t[1];\n\t\t\t\t\t\ttvec[1] = surf->texinfo->vecs[0][1] * psurf->texmat_t[0] + surf->texinfo->vecs[1][1] * psurf->texmat_t[1];\n\t\t\t\t\t\ttvec[2] = surf->texinfo->vecs[0][2] * psurf->texmat_t[0] + surf->texinfo->vecs[1][2] * psurf->texmat_t[1];\n\t\t\t\t\t\ttvec[3] = surf->texinfo->vecs[1][3] + psurf->texmat_t[2];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tVector4Copy(surf->texinfo->vecs[0], svec);\n\t\t\t\t\t\tVector4Copy(surf->texinfo->vecs[1], tvec);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst float s = DotProduct( in_vertex->position, svec ) + svec[3];\n\t\t\t\t\tconst float t = DotProduct( in_vertex->position, tvec ) + tvec[3];\n\n\t\t\t\t\tvertex.gl_tc[0] = s / surf->texinfo->texture->width;\n\t\t\t\t\tvertex.gl_tc[1] = t / surf->texinfo->texture->height;\n\n\t\t\t\t\tVectorCopy(svec, tangent);\n\t\t\t\t\tVectorNormalize(tangent);\n\n\t\t\t\t\t// \"Inverted\" texture mapping should not lead to inverted tangent/normal map\n\t\t\t\t\t// Make sure that orientation is preserved.\n\t\t\t\t\t{\n\t\t\t\t\t\tvec4_t stnorm;\n\t\t\t\t\t\tCrossProduct(tvec, svec, stnorm);\n\t\t\t\t\t\tif (DotProduct(stnorm, surf_normal) < 0.)\n\t\t\t\t\t\t\tVectorNegate(tangent, tangent);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// lightmap texture coordinates\n\t\t\t\t{\n\t\t\t\t\tfloat s = DotProduct( in_vertex->position, info->lmvecs[0] ) + info->lmvecs[0][3];\n\t\t\t\t\ts -= info->lightmapmins[0];\n\t\t\t\t\ts += surf->light_s * sample_size;\n\t\t\t\t\ts += sample_size * 0.5f;\n\t\t\t\t\ts /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;\n\n\t\t\t\t\tfloat t = DotProduct( in_vertex->position, info->lmvecs[1] ) + info->lmvecs[1][3];\n\t\t\t\t\tt -= info->lightmapmins[1];\n\t\t\t\t\tt += surf->light_t * sample_size;\n\t\t\t\t\tt += sample_size * 0.5f;\n\t\t\t\t\tt /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height;\n\n\t\t\t\t\tvertex.lm_tc[0] = s;\n\t\t\t\t\tvertex.lm_tc[1] = t;\n\t\t\t\t}\n\n\t\t\t\t// Compute smoothed normal if needed\n\t\t\t\tif (!getSmoothedNormalFor(args.mod, vertex_index, surface_index, vertex.normal)) {\n\t\t\t\t\tVectorCopy(surf_normal, vertex.normal);\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tconst float normal_len2 = DotProduct(vertex.normal, vertex.normal);\n\t\t\t\t\tif (normal_len2 < .9f) {\n\t\t\t\t\t\tERR(\"model=%s surf=%d vert=%d surf_normal=(%f, %f, %f) vertex.normal=(%f,%f,%f) INVALID len2=%f\",\n\t\t\t\t\t\t\targs.mod->name, surface_index, k,\n\t\t\t\t\t\t\tsurf_normal[0], surf_normal[1], surf_normal[2],\n\t\t\t\t\t\t\tvertex.normal[0], vertex.normal[1], vertex.normal[2],\n\t\t\t\t\t\t\tnormal_len2\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tVectorCopy(tangent, vertex.tangent);\n\n\t\t\t\tVector4Set(vertex.color, 255, 255, 255, 255);\n\n\t\t\t\t// Store original vertex data for conveyor reasons\n\t\t\t\tif (conv) {\n\t\t\t\t\tconst int vertex_index = conv->vertices_src_offset + k;\n\t\t\t\t\tASSERT(vertex_index < args.sizes.conveyors_vertices_count);\n\t\t\t\t\targs.bmodel->conveyors_vertices[vertex_index] = vertex;\n\t\t\t\t}\n\n\t\t\t\t//DEBUG(\" p[%d]=(%f,%f,%f)\", k, vertex.pos[0], vertex.pos[1], vertex.pos[2]);\n\n\t\t\t\t*(p_vert++) = vertex;\n\n\t\t\t\t// Write vertex window: p[0] = first, p[1] = prev, p[2] = current\n\t\t\t\tVectorCopy(in_vertex->position, p[Q_min(k, 2)]);\n\n\t\t\t\t// Ray tracing apparently expects triangle list only (although spec is not very clear about this kekw)\n\t\t\t\tif (k > 1) {\n\t\t\t\t\t// Check for collinear points/degenerate triangles\n\t\t\t\t\tvec3_t tri_normal;\n\t\t\t\t\tcomputeNormal(p[0], p[1], p[2], tri_normal);\n\t\t\t\t\tconst float area2 = VectorLength2(tri_normal);\n\n\t\t\t\t\tif (area2 <= 0.) {\n\t\t\t\t\t\t// Do not produce triangle if it has zero area\n\t\t\t\t\t\t// NOTE: this is suboptimal in the sense that points that might be necessary for proper\n\t\t\t\t\t\t// normal smoothing might be skipped. In case that this causes undesirable rendering\n\t\t\t\t\t\t// artifacts, a more proper triangulation algorithm, that doesn't skip points, would\n\t\t\t\t\t\t// be needed. E.g. ear clipping.\n\t\t\t\t\t\t/* diagnostics\n\t\t\t\t\t\tWARN(\"surface=%d numedges=%d triangle=%d has degenerate normal, area2=%f\",\n\t\t\t\t\t\t\tsurface_index, surf->numedges, index_count / 3, area2);\n\t\t\t\t\t\tDEBUG(\"  p[0]=(%f,%f,%f)\", p[0][0], p[0][1], p[0][2]);\n\t\t\t\t\t\tDEBUG(\"  p[%d]=(%f,%f,%f)\", k - 1, p[1][0], p[1][1], p[1][2]);\n\t\t\t\t\t\tDEBUG(\"  p[%d]=(%f,%f,%f)\", k, p[2][0], p[2][1], p[2][2]);\n\t\t\t\t\t\t*/\n\t\t\t\t\t} else {\n\t\t\t\t\t\t*(p_ind++) = (uint16_t)(vertex_offset + 0);\n\t\t\t\t\t\t*(p_ind++) = (uint16_t)(vertex_offset + k - 1);\n\t\t\t\t\t\t*(p_ind++) = (uint16_t)(vertex_offset + k);\n\t\t\t\t\t\tindex_count += 3;\n\t\t\t\t\t\tindex_offset += 3;\n\n\t\t\t\t\t\t/* diagnostics for degenerate triangles\n\t\t\t\t\t\tconst float dot = DotProduct(tri_normal, surf_normal) / sqrt(area2);\n\t\t\t\t\t\tif (fabs(dot-1.) > 1e-2) {\n\t\t\t\t\t\t\tWARN(\"surface=%d triangle=%d tri_normal=(%f,%f,%f) sn=(%f,%f,%f) dot=%f\",\n\t\t\t\t\t\t\t\tsurface_index, index_count / 3,\n\t\t\t\t\t\t\t\ttri_normal[0], tri_normal[1], tri_normal[2],\n\t\t\t\t\t\t\t\tsurf_normal[0], surf_normal[1], surf_normal[2],\n\t\t\t\t\t\t\t\tdot\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t*/\n\t\t\t\t\t} // valid triangle\n\n\t\t\t\t\t// Move current vertex to prev\n\t\t\t\t\tVectorCopy(p[2], p[1]);\n\t\t\t\t} // if (k > 1)\n\t\t\t} // for surf->numedges\n\n\t\t\tmodel_geometry->element_count = index_count;\n\t\t\tvertex_offset += surf->numedges;\n\t\t} // for mod->nummodelsurfaces\n\t}\n\n\t// Apply all emissive surfaces found\n\tif (emissive_surfaces_count > 0) {\n\t\tINFO(\"Loaded %d polylights, %d dynamic for %s model %s\",\n\t\t\temissive_surfaces_count, (int)args.bmodel->dynamic_polylights.count, args.is_static ? \"static\" : \"movable\", args.mod->name);\n\t}\n\n\tASSERT(args.sizes.num_surfaces == num_geometries);\n\tASSERT(args.sizes.animated_count == animated_count);\n\tASSERT(args.sizes.conveyors_count == conveyors_count);\n\tASSERT(args.sizes.conveyors_vertices_count == conveyors_vertices_count);\n\treturn true;\n}\n\nstatic qboolean createRenderModel( const model_t *mod, vk_brush_model_t *bmodel, const model_sizes_t sizes, qboolean is_worldmodel ) {\n\tbmodel->geometry = R_GeometryRangeAlloc(sizes.num_vertices, sizes.num_indices);\n\tif (!bmodel->geometry.block_handle.size) {\n\t\tERR(\"Cannot allocate geometry for %s\", mod->name );\n\t\treturn false;\n\t}\n\n\tvk_render_geometry_t *const geometries = Mem_Malloc(vk_core.pool, sizeof(vk_render_geometry_t) * sizes.num_surfaces);\n\tbmodel->surface_to_geometry_index = Mem_Malloc(vk_core.pool, sizeof(int) * mod->nummodelsurfaces);\n\tfor (int i = 0; i < mod->nummodelsurfaces; ++i)\n\t\tbmodel->surface_to_geometry_index[i] = -1;\n\tbmodel->animated_indexes = Mem_Malloc(vk_core.pool, sizeof(int) * sizes.animated_count);\n\tbmodel->animated_indexes_count = sizes.animated_count;\n\n\tif (sizes.animated_count > MAX_ANIMATED_TEXTURES) {\n\t\tWARN(\"Too many animated textures %d for model \\\"%s\\\" some surfaces can be static\", sizes.animated_count, mod->name);\n\t}\n\n\tif (sizes.conveyors_count > 0) {\n\t\tASSERT(sizes.conveyors_vertices_count > 3);\n\t\tbmodel->conveyors_count = sizes.conveyors_count;\n\t\tbmodel->conveyors_vertices = Mem_Malloc(vk_core.pool, sizeof(vk_vertex_t) * sizes.conveyors_vertices_count);\n\t\tbmodel->conveyors = Mem_Malloc(vk_core.pool, sizeof(r_conveyor_t) * sizes.conveyors_count);\n\t}\n\n\tconst r_geometry_range_lock_t geom_lock = R_GeometryRangeLock(&bmodel->geometry);\n\tconst xvk_mapent_func_any_t *func_any = getModelFuncAnyPatch(mod);\n\tconst qboolean is_static = is_worldmodel || (func_any && func_any->origin_patched);\n\n\tconst qboolean fill_result = fillBrushSurfaces((fill_geometries_args_t){\n\t\t\t.mod = mod,\n\t\t\t.bmodel = bmodel,\n\t\t\t.sizes = sizes,\n\t\t\t.base_vertex_offset = bmodel->geometry.vertices.unit_offset,\n\t\t\t.base_index_offset = bmodel->geometry.indices.unit_offset,\n\t\t\t.out_geometries = geometries,\n\t\t\t.out_vertices = geom_lock.vertices,\n\t\t\t.out_indices = geom_lock.indices,\n\t\t\t.func_any = func_any,\n\t\t\t.is_worldmodel = is_worldmodel,\n\t\t\t.is_static = is_static,\n\t\t});\n\n\tR_GeometryRangeUnlock( &geom_lock );\n\n\tif (!fill_result) {\n\t\t// TODO unlock and free buffers if failed? Currently we can't free geometry range, as it is being implicitly referenced by staging queue. Flush staging and free?\n\t\t// This shouldn't really happen btw, kind of unrecoverable for now tbh.\n\t\t// Also, we might just handle it, as the only reason it can fail is 16 bit index overflow.\n\t\t// I. Split into smaller geometries sets.\n\t\t// II. Make indices 32 bit\n\t\treturn false;\n\t}\n\n\tif (!R_RenderModelCreate(&bmodel->render_model, (vk_render_model_init_t){\n\t\t.name = mod->name,\n\t\t.geometries = geometries,\n\t\t.geometries_count = sizes.num_surfaces,\n\t\t.dynamic = false,\n\t\t})) {\n\t\tERR(\"Could not create render model for brush model %s\", mod->name);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nqboolean R_BrushModelLoad( model_t *mod, qboolean is_worldmodel ) {\n\tif (mod->cache.data) {\n\t\tWARN(\"Model %s was already loaded\", mod->name );\n\t\treturn true;\n\t}\n\n\tDEBUG(\"%s: %s flags=%08x\", __FUNCTION__, mod->name, mod->flags);\n\n\tvk_brush_model_t *bmodel = Mem_Calloc(vk_core.pool, sizeof(*bmodel));\n\tASSERT(g_brush.models_count < COUNTOF(g_brush.models));\n\tg_brush.models[g_brush.models_count++] = bmodel;\n\n\tbmodel->engine_model = mod;\n\tbmodel->patch_rendermode = -1;\n\tmod->cache.data = bmodel;\n\n\tMatrix4x4_LoadIdentity(bmodel->prev_transform);\n\tbmodel->prev_time = gp_cl->time;\n\n\tarrayDynamicInitT(&bmodel->dynamic_polylights);\n\n\tconst model_sizes_t sizes = computeSizes( mod, is_worldmodel );\n\tconst xvk_mapent_func_any_t *func_any = getModelFuncAnyPatch(mod);\n\tconst qboolean is_static = is_worldmodel || (func_any && func_any->origin_patched);\n\n\tif (is_worldmodel) {\n\t\ttglob.current_map_has_surf_sky = sizes.sky_surfaces_count != 0;\n\t\tDEBUG(\"sky_surfaces_count=%d, current_map_has_surf_sky=%d\", sizes.sky_surfaces_count, tglob.current_map_has_surf_sky);\n\t}\n\n\tif (sizes.num_surfaces != 0) {\n\t\tif (!createRenderModel(mod, bmodel, sizes, is_worldmodel)) {\n\t\t\tERR(\"Could not load brush model %s\", mod->name);\n\t\t\t// FIXME Cannot deallocate bmodel as we might still have staging references to its memory\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (sizes.water.surfaces) {\n\t\tif (!brushCreateWaterModel((brush_create_water_model_t) {\n\t\t\t\t.bmodel = bmodel,\n\t\t\t\t.wmodel = &bmodel->water,\n\t\t\t\t.sizes = sizes.water,\n\t\t\t\t.type = BrushSurface_Water,\n\t\t\t\t.is_worldmodel = is_worldmodel,\n\t\t\t\t.is_static = is_static,\n\t\t\t})) {\n\t\t\tERR(\"Could not load brush water model %s\", mod->name);\n\t\t\t// FIXME Cannot deallocate bmodel as we might still have staging references to its memory\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (sizes.side_water.surfaces) {\n\t\tif (!brushCreateWaterModel((brush_create_water_model_t) {\n\t\t\t\t.bmodel = bmodel,\n\t\t\t\t.wmodel = &bmodel->water_sides,\n\t\t\t\t.sizes = sizes.side_water,\n\t\t\t\t.type = BrushSurface_WaterSide,\n\t\t\t\t.is_worldmodel = is_worldmodel,\n\t\t\t\t.is_static = is_static,\n\t\t\t})) {\n\t\t\tERR(\"Could not load brush water_side model %s\", mod->name);\n\t\t\t// FIXME Cannot deallocate bmodel as we might still have staging references to its memory\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tg_brush.stat.total_vertices += sizes.num_indices + sizes.water.vertices + sizes.side_water.vertices;\n\tg_brush.stat.total_indices += sizes.num_vertices + sizes.water.indices + sizes.side_water.indices;\n\n\tDEBUG(\"Model %s loaded surfaces: %d (of %d); total vertices: %u, total indices: %u\",\n\t\tmod->name, bmodel->render_model.num_geometries, mod->nummodelsurfaces, g_brush.stat.total_vertices, g_brush.stat.total_indices);\n\n\treturn true;\n}\n\nstatic void R_BrushModelDestroy( vk_brush_model_t *bmodel ) {\n\tASSERT(bmodel->engine_model);\n\n\tDEBUG(\"%s: %s\", __FUNCTION__, bmodel->engine_model->name);\n\n\tASSERT(bmodel->engine_model->cache.data == bmodel);\n\tASSERT(bmodel->engine_model->type == mod_brush);\n\n\tarrayDynamicDestroyT(&bmodel->dynamic_polylights);\n\n\tif (bmodel->conveyors_vertices)\n\t\tMem_Free(bmodel->conveyors_vertices);\n\n\tif (bmodel->conveyors)\n\t\tMem_Free(bmodel->conveyors);\n\n\tif (bmodel->water.surfaces_count) {\n\t\tR_RenderModelDestroy(&bmodel->water.render_model);\n\t\tMem_Free((int*)bmodel->water.surfaces_indices);\n\t\tMem_Free(bmodel->water.render_model.geometries);\n\t\tR_GeometryRangeFree(&bmodel->water.geometry);\n\t}\n\n\tif (bmodel->water_sides.surfaces_count) {\n\t\tR_RenderModelDestroy(&bmodel->water_sides.render_model);\n\t\tMem_Free((int*)bmodel->water_sides.surfaces_indices);\n\t\tMem_Free(bmodel->water_sides.render_model.geometries);\n\t\tR_GeometryRangeFree(&bmodel->water_sides.geometry);\n\t}\n\n\tR_RenderModelDestroy(&bmodel->render_model);\n\n\tif (bmodel->animated_indexes)\n\t\tMem_Free(bmodel->animated_indexes);\n\n\tif (bmodel->surface_to_geometry_index)\n\t\tMem_Free(bmodel->surface_to_geometry_index);\n\n\tif (bmodel->render_model.geometries) {\n\t\tMem_Free(bmodel->render_model.geometries);\n\t\tR_GeometryRangeFree(&bmodel->geometry);\n\t}\n\n\tbmodel->engine_model->cache.data = NULL;\n\tMem_Free(bmodel);\n}\n\nvoid R_BrushModelDestroyAll( void ) {\n\tDEBUG(\"Destroying %d brush models\", g_brush.models_count);\n\tfor( int i = 0; i < g_brush.models_count; i++ )\n\t\tR_BrushModelDestroy(g_brush.models[i]);\n\n\tg_brush.stat.total_vertices = 0;\n\tg_brush.stat.total_indices = 0;\n\tg_brush.models_count = 0;\n\n\tmemset(g_brush.conn.edges, 0, sizeof(*g_brush.conn.edges) * g_brush.conn.edges_capacity);\n\n\tmemset(g_brush.conn.vertices, 0, sizeof(*g_brush.conn.vertices) * g_brush.conn.vertices_capacity);\n}\n\nstatic float computeArea(vec3_t *vertices, int vertices_count) {\n\t\tvec3_t normal = {0, 0, 0};\n\n\t\tfor (int i = 2; i < vertices_count; ++i) {\n\t\t\tvec3_t e[2], lnormal;\n\t\t\tVectorSubtract(vertices[i-0], vertices[0], e[0]);\n\t\t\tVectorSubtract(vertices[i-1], vertices[0], e[1]);\n\t\t\tCrossProduct(e[0], e[1], lnormal);\n\t\t\tVectorAdd(lnormal, normal, normal);\n\t\t}\n\n\t\treturn VectorLength(normal);\n}\n\nstatic qboolean loadPolyLight(rt_light_add_polygon_t *out_polygon, const model_t *mod, const int surface_index, const msurface_t *surf, const vec3_t emissive) {\n\t(*out_polygon) = (rt_light_add_polygon_t){0};\n\tout_polygon->num_vertices = Q_min(7, surf->numedges);\n\n\t// TODO split, don't clip\n\tif (surf->numedges > 7)\n\t\tWARN_THROTTLED(10, \"emissive surface %d has %d vertices; clipping to 7\", surface_index, surf->numedges);\n\n\tVectorCopy(emissive, out_polygon->emissive);\n\n\tfor (int i = 0; i < out_polygon->num_vertices; ++i) {\n\t\tconst int iedge = mod->surfedges[surf->firstedge + i];\n\t\tconst medge16_t *edge = mod->edges16 + (iedge >= 0 ? iedge : -iedge);\n\t\tconst mvertex_t *vertex = mod->vertexes + (iedge >= 0 ? edge->v[0] : edge->v[1]);\n\t\tVectorCopy(vertex->position, out_polygon->vertices[i]);\n\t}\n\n\tconst float area = computeArea(out_polygon->vertices, out_polygon->num_vertices);\n\tif (area <= 0) {\n\t\tERR(\"%s: emissive surface=%d has area=%f, skipping\", __FUNCTION__, surface_index, area);\n\t\treturn false;\n\t}\n\n\tout_polygon->surface = surf;\n\treturn true;\n}\n\nvoid R_BrushUnloadTextures( model_t *mod )\n{\n\tint i;\n\n\tfor( i = 0; i < mod->numtextures; i++ )\n\t{\n\t\ttexture_t *tx = mod->textures[i];\n\t\tif( !tx || tx->gl_texturenum == tglob.defaultTexture )\n\t\t\tcontinue; // free slot\n\n\t\tR_TextureFree( tx->gl_texturenum );    // main texture\n\t\tR_TextureFree( tx->fb_texturenum );    // luma texture\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_brush.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n#include \"vk_render.h\" // cl_entity_t\n\nstruct ref_viewpass_s;\nstruct draw_list_s;\nstruct model_s;\nstruct cl_entity_s;\n\nqboolean R_BrushInit( void );\nvoid R_BrushShutdown( void );\n\nqboolean R_BrushModelLoad(struct model_s *mod, qboolean is_worldmodel);\nvoid R_BrushModelDestroyAll( void );\n\nvoid R_BrushModelDraw( const cl_entity_t *ent, int render_mode, float blend, const matrix4x4 transform );\n\nconst texture_t *R_TextureAnimation( const cl_entity_t *ent, const msurface_t *s );\n\nvoid R_BrushUnloadTextures( model_t *mod );\n"
  },
  {
    "path": "ref/vk/vk_common.h",
    "content": "#pragma once\n\n#include \"const.h\" // required for ref_api.h\n#include \"cvardef.h\"\n#include \"com_model.h\"\n#include \"ref_api.h\"\n#include \"com_strings.h\"\n#include \"crtlib.h\"\n#include \"enginefeatures.h\" // ENGINE_LINEAR_GAMMA_SPACE\n\n#define ASSERT(x) do { if(!( x )) gEngine.Host_Error( \"assert %s failed at %s:%d\\n\", #x, __FILE__, __LINE__ ); } while (0)\n// TODO ASSERTF(x, fmt, ...)\n\n#define Mem_Malloc( pool, size ) gEngine._Mem_Alloc( pool, size, false, __FILE__, __LINE__ )\n#define Mem_Calloc( pool, size ) gEngine._Mem_Alloc( pool, size, true, __FILE__, __LINE__ )\n#define Mem_Realloc( pool, ptr, size ) gEngine._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ )\n#define Mem_Free( mem ) gEngine._Mem_Free( mem, __FILE__, __LINE__ )\n#define Mem_AllocPool( name ) gEngine._Mem_AllocPool( name, __FILE__, __LINE__ )\n#define Mem_FreePool( pool ) gEngine._Mem_FreePool( pool, __FILE__, __LINE__ )\n#define Mem_EmptyPool( pool ) gEngine._Mem_EmptyPool( pool, __FILE__, __LINE__ )\n\n#define ALIGN_UP(ptr, align) ((((ptr) + (align) - 1) / (align)) * (align))\n\n#define COUNTOF(a) (sizeof(a)/sizeof((a)[0]))\n\n// Sliences -Werror=cast-align\n// TODO assert for proper alignment for type_\n#define PTR_CAST(type_, ptr_) ((type_*)(void*)(ptr_))\n\ninline static int clampi32(int v, int min, int max) {\n\tif (v < min) return min;\n\tif (v > max) return max;\n\treturn v;\n}\n\n/* TODO? something like\n * struct {\n\t\tref_api_t api;\n\t\tref_globals_t *globals;\n\t\tref_client_t  *client;\n\t\tref_host_t    *host;\n\n\t\tstruct world_static_s *world;\n\t\t...\n * } r_globals_t;\n * extern r_globals_t *G;\n */\n\ntypedef struct {\n\tstruct world_static_s *world;\n\tcl_entity_t *entities;\n\tunsigned int max_entities;\n\tstruct movevars_s *movevars;\n\tcolor24 *palette;\n\tcl_entity_t *viewent;\n\tdlight_t *dlights;\n\tdlight_t *elights;\n\tbyte *texgammatable;\n\tuint *lightgammatable;\n\tuint *lineargammatable;\n\tuint *screengammatable;\n} r_globals_t;\n\nextern r_globals_t globals;\nextern ref_api_t gEngine;\nextern ref_globals_t *gpGlobals;\nextern ref_client_t  *gp_cl;\nextern ref_host_t    *gp_host;\n\n#define ENGINE_GET_PARM_ (*gEngine.EngineGetParm)\n#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 )\n\n#define WORLDMODEL (gp_cl->models[1])\n#define MOVEVARS (globals.movevars)\n\nstatic inline byte TextureToGamma( byte b )\n{\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? globals.texgammatable[b] : b;\n}\n\nstatic inline uint LightToTexGamma( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? globals.lightgammatable[b] : b;\n}\n\nstatic inline uint ScreenGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? globals.screengammatable[b] : b;\n}\n\nstatic inline uint LinearGammaTable( uint b )\n{\n\tif( unlikely( b >= 1024 ))\n\t\treturn 0;\n\n\treturn !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? globals.lineargammatable[b] : b;\n}\n\nvoid GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa );\ncolorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec );\n\nvoid R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords );\nint R_BuildPolygonFromSurface( model_t *mod, msurface_t *fa );\n"
  },
  {
    "path": "ref/vk/vk_const.h",
    "content": "#pragma once\n\n#define MAX_SCENE_STACK 2\n#define MAX_SCENE_ENTITIES 2048\n\n#define MAX_TEXTURES\t4096\n\n// indexed by uint8_t\n#define MAX_SURFACE_LIGHTS 256\n// indexed by uint8_t\n#define MAX_POINT_LIGHTS 256\n\n// indexed by uint8_t\n#define MAX_VISIBLE_POINT_LIGHTS 63\n// indexed by uint8_t\n#define MAX_VISIBLE_SURFACE_LIGHTS 255\n\n#define MAX_LIGHT_CLUSTERS 262144 //131072 //32768\n#define LIGHT_GRID_CELL_SIZE 128\n"
  },
  {
    "path": "ref/vk/vk_core.c",
    "content": "#include \"vk_core.h\"\n\n#include \"vk_common.h\"\n#include \"r_textures.h\"\n#include \"vk_overlay.h\"\n#include \"vulkan/VImage.h\"\n#include \"vulkan/VStaging.h\"\n#include \"vk_framectl.h\"\n#include \"vk_brush.h\"\n#include \"vk_scene.h\"\n#include \"vk_cvar.h\"\n#include \"vulkan/VPipeline.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"vk_studio.h\"\n#include \"vk_rtx.h\"\n#include \"vulkan/VDescriptor.h\"\n#include \"vulkan/VResource.h\"\n#include \"vulkan/VNvAftermath.h\"\n#include \"vulkan/VDevmem.h\"\n#include \"r_speeds.h\"\n#include \"vk_speeds.h\"\n#include \"vk_sprite.h\"\n#include \"vk_beams.h\"\n#include \"r_decals.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"vk_entity_data.h\"\n#include \"vk_logs.h\"\n#include \"std/arrays.h\"\n\n// FIXME move this rt-specific stuff out\n#include \"vk_light.h\"\n\n#include \"xash3d_types.h\"\n#include \"cvardef.h\"\n#include \"const.h\" // required for ref_api.h\n#include \"ref_api.h\"\n#include \"crtlib.h\"\n#include \"com_strings.h\"\n#include \"eiface.h\"\n\n#include \"std/debugbreak.h\"\n\n#include <string.h>\n\n#define LOG_MODULE core\n\n#define NULLINST_FUNCS(X) \\\n\tX(vkEnumerateInstanceVersion) \\\n\tX(vkCreateInstance) \\\n\nstatic PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;\n\n#define X(f) PFN_##f f = NULL;\n\tNULLINST_FUNCS(X)\n\tINSTANCE_FUNCS(X)\n\tINSTANCE_DEBUG_FUNCS(X)\n#undef X\n\nstatic dllfunc_t nullinst_funcs[] = {\n#define X(f) {#f, (void**)&f},\n\tNULLINST_FUNCS(X)\n#undef X\n};\n\nstatic dllfunc_t instance_funcs[] = {\n#define X(f) {#f, (void**)&f},\n\tINSTANCE_FUNCS(X)\n#undef X\n};\n\nstatic dllfunc_t instance_debug_funcs[] = {\n#define X(f) {#f, (void**)&f},\n\tINSTANCE_DEBUG_FUNCS(X)\n#undef X\n};\n\nstatic const char *validation_layers[] = {\n\t\"VK_LAYER_KHRONOS_validation\",\n};\n\nstatic VkBool32 VKAPI_PTR debugCallback(\n    VkDebugUtilsMessageSeverityFlagBitsEXT           messageSeverity,\n    VkDebugUtilsMessageTypeFlagsEXT                  messageTypes,\n    const VkDebugUtilsMessengerCallbackDataEXT*      pCallbackData,\n\tvoid *pUserData) {\n\t(void)(pUserData);\n\t(void)(messageTypes);\n\t(void)(messageSeverity);\n\n\t// TODO better messages, not only errors, what are other arguments for, ...\n\tif (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {\n\t\tgEngine.Con_Printf(S_ERROR \"vk/dbg: %s\\n\", pCallbackData->pMessage);\n#ifdef _MSC_VER\n\t\t__debugbreak();\n#else\n\t\tdebug_break();\n#endif\n\t} else {\n\t\tif (Q_strcmp(pCallbackData->pMessageIdName, \"UNASSIGNED-DEBUG-PRINTF\") == 0) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"vk/dbg: %s\\n\", pCallbackData->pMessage);\n\t\t} else {\n\t\t\tgEngine.Con_Printf(S_WARN \"vk/dbg: %s\\n\", pCallbackData->pMessage);\n\t\t}\n\t}\n\n\treturn VK_FALSE;\n}\n\nvulkan_core_t vk_core = {0};\n\nstatic void loadInstanceFunctions(dllfunc_t *funcs, int count)\n{\n\tfor (int i = 0; i < count; ++i)\n\t{\n\t\t*funcs[i].func = vkGetInstanceProcAddr(vk_core.instance, funcs[i].name);\n\t\tif (!*funcs[i].func)\n\t\t{\n\t\t\tgEngine.Con_Printf( S_WARN \"Function %s was not loaded\\n\", funcs[i].name);\n\t\t}\n\t}\n}\n\nstatic qboolean createInstance( void )\n{\n\tconst char ** instance_extensions = NULL;\n\tunsigned int num_instance_extensions = vk_core.debug ? 1 : 0;\n\tconst VkApplicationInfo app_info = {\n\t\t.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,\n\t\t// TODO support versions 1.0 and 1.1 for simple traditional rendering\n\t\t// This would require using older physical device features and props query structures\n\t\t// .apiVersion = vk_core.rtx ? VK_API_VERSION_1_2 : VK_API_VERSION_1_1,\n\t\t.apiVersion = VK_API_VERSION_1_3,\n\t\t.applicationVersion = VK_MAKE_VERSION(0, 0, 0), // TODO\n\t\t.engineVersion = VK_MAKE_VERSION(0, 0, 0),\n\t\t.pApplicationName = \"\",\n\t\t.pEngineName = \"xash3d-fwgs\",\n\t};\n\n\tBOUNDED_ARRAY(VkValidationFeatureEnableEXT, validation_features, 8);\n\tBOUNDED_ARRAY_APPEND_ITEM(validation_features, VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT);\n\tBOUNDED_ARRAY_APPEND_ITEM(validation_features, VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT);\n\tBOUNDED_ARRAY_APPEND_ITEM(validation_features, VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT);\n\n\tif (!!gEngine.Sys_CheckParm(\"-vkdbg_shaderprintf\"))\n\t\tBOUNDED_ARRAY_APPEND_ITEM(validation_features, VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT);\n\n\tconst VkValidationFeaturesEXT validation_ext = {\n\t\t.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,\n\t\t.pEnabledValidationFeatures = validation_features.items,\n\t\t.enabledValidationFeatureCount = validation_features.count,\n\t};\n\n\tVkInstanceCreateInfo create_info = {\n\t\t.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,\n\t\t.pApplicationInfo = &app_info,\n\t\t.pNext = vk_core.validate ? &validation_ext : NULL,\n\t};\n\n\tint vid_extensions = gEngine.XVK_GetInstanceExtensions(0, NULL);\n\tif (vid_extensions < 0)\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot get Vulkan instance extensions\\n\" );\n\t\treturn false;\n\t}\n\n\tnum_instance_extensions += vid_extensions;\n\n\tinstance_extensions = Mem_Malloc(vk_core.pool, sizeof(const char*) * num_instance_extensions);\n\tvid_extensions = gEngine.XVK_GetInstanceExtensions(vid_extensions, instance_extensions);\n\tif (vid_extensions < 0)\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot get Vulkan instance extensions\\n\" );\n\t\tMem_Free((void*)instance_extensions);\n\t\treturn false;\n\t}\n\n\tif (vk_core.debug)\n\t{\n\t\tinstance_extensions[vid_extensions] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;\n\t}\n\n\tgEngine.Con_Reportf(\"Requesting instance extensions: %d\\n\", num_instance_extensions);\n\tfor (int i = 0; i < num_instance_extensions; ++i)\n\t{\n\t\tgEngine.Con_Reportf(\"\\t%d: %s\\n\", i, instance_extensions[i]);\n\t}\n\n\tcreate_info.enabledExtensionCount = num_instance_extensions;\n\tcreate_info.ppEnabledExtensionNames = instance_extensions;\n\n\tif (vk_core.validate)\n\t{\n\t\tcreate_info.enabledLayerCount = ARRAYSIZE(validation_layers);\n\t\tcreate_info.ppEnabledLayerNames = validation_layers;\n\n\t\tgEngine.Con_Printf(S_WARN \"Using Vulkan validation layers, expect severely degraded performance\\n\");\n\t}\n\n\t// TODO handle errors gracefully -- let it try next renderer\n\tXVK_CHECK(vkCreateInstance(&create_info, NULL, &vk_core.instance));\n\n\tloadInstanceFunctions(instance_funcs, ARRAYSIZE(instance_funcs));\n\n\tif (vk_core.debug || vk_core.validate)\n\t{\n\t\tloadInstanceFunctions(instance_debug_funcs, ARRAYSIZE(instance_debug_funcs));\n\n \t\tif (vk_core.validate) {\n\t\t\tif (vkCreateDebugUtilsMessengerEXT) {\n\t\t\t\tVkDebugUtilsMessengerCreateInfoEXT debug_create_info = {\n\t\t\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,\n\t\t\t\t\t.messageSeverity = 0x1111, //:vovka: VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,\n\t\t\t\t\t.messageType = 0x07,\n\t\t\t\t\t.pfnUserCallback = debugCallback,\n\t\t\t\t};\n\t\t\t\tXVK_CHECK(vkCreateDebugUtilsMessengerEXT(vk_core.instance, &debug_create_info, NULL, &vk_core.debug_messenger));\n\t\t\t} else {\n\t\t\t\tgEngine.Con_Printf(S_WARN \"Vulkan debug utils messenger is not available\\n\");\n\t\t\t}\n\t\t}\n\t}\n\n\tMem_Free((void*)instance_extensions);\n\treturn true;\n}\n\nstatic qboolean initSurface( void )\n{\n\tXVK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(v_device_info.physical_device, vk_core.surface.surface, &vk_core.surface.num_present_modes, vk_core.surface.present_modes));\n\tvk_core.surface.present_modes = Mem_Malloc(vk_core.pool, sizeof(*vk_core.surface.present_modes) * vk_core.surface.num_present_modes);\n\tXVK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(v_device_info.physical_device, vk_core.surface.surface, &vk_core.surface.num_present_modes, vk_core.surface.present_modes));\n\n\tgEngine.Con_Printf(\"Supported surface present modes: %u\\n\", vk_core.surface.num_present_modes);\n\tfor (uint32_t i = 0; i < vk_core.surface.num_present_modes; ++i)\n\t{\n\t\tgEngine.Con_Reportf(\"\\t%u: %s (%u)\\n\", i, R_VkPresentModeName(vk_core.surface.present_modes[i]), vk_core.surface.present_modes[i]);\n\t}\n\n\tXVK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(v_device_info.physical_device, vk_core.surface.surface, &vk_core.surface.num_surface_formats, vk_core.surface.surface_formats));\n\tvk_core.surface.surface_formats = Mem_Malloc(vk_core.pool, sizeof(*vk_core.surface.surface_formats) * vk_core.surface.num_surface_formats);\n\tXVK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(v_device_info.physical_device, vk_core.surface.surface, &vk_core.surface.num_surface_formats, vk_core.surface.surface_formats));\n\n\tgEngine.Con_Reportf(\"Supported surface formats: %u\\n\", vk_core.surface.num_surface_formats);\n\tfor (uint32_t i = 0; i < vk_core.surface.num_surface_formats; ++i)\n\t{\n\t\tgEngine.Con_Reportf(\"\\t%u: %s(%u) %s(%u)\\n\", i,\n\t\t\tR_VkFormatName(vk_core.surface.surface_formats[i].format), vk_core.surface.surface_formats[i].format,\n\t\t\tR_VkColorSpaceName(vk_core.surface.surface_formats[i].colorSpace), vk_core.surface.surface_formats[i].colorSpace);\n\t}\n\n\treturn true;\n}\n\n// TODO modules\n/*\ntypedef struct r_vk_module_s {\n\tqboolean (*init)(void);\n\tvoid (*destroy)(void);\n\n\t// TODO next: dependecies, refcounts, ...\n} r_vk_module_t;\n\n#define LIST_MODULES(X) ...\n\n=>\nextern const r_vk_module_t vk_instance_module;\n...\nextern const r_vk_module_t vk_rtx_module;\n...\n\n=>\nstatic const r_vk_module_t *const modules[] = {\n\t&vk_instance_module,\n\t&vk_device_module,\n\t&vk_aftermath_module,\n\t&vk_texture_module,\n\t...\n\t&vk_rtx_module,\n\t...\n};\n*/\n\nqboolean R_VkInit( void )\n{\n\t// FIXME !!!! handle initialization errors properly: destroy what has already been created\n\tINFO(\"R_VkInit\");\n\n\tvk_core.validate = !!gEngine.Sys_CheckParm(\"-vkvalidate\");\n\tvk_core.debug = vk_core.validate || !!(gEngine.Sys_CheckParm(\"-vkdebug\") || gEngine.Sys_CheckParm(\"-gldebug\"));\n\tvk_core.rtx = false;\n\n\tVK_LoadCvars();\n\n\t// Force extremely verbose logs at startup.\n\t// This is instrumental in some investigations, because the usual \"vk_debug_log\" cvar is not set\n\t// at this point and cannot be used to selectively swith things on.\n\tif (gEngine.Sys_CheckParm(\"-vkverboselogs\"))\n\t\tg_log_debug_bits = 0xffffffffu;\n\n\tR_SpeedsInit();\n\tVK_SpeedsInit();\n\n\tif( !gEngine.R_Init_Video( REF_VULKAN )) // request Vulkan surface\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot initialize Vulkan video\\n\" );\n\t\treturn false;\n\t}\n\n\tvkGetInstanceProcAddr = gEngine.XVK_GetVkGetInstanceProcAddr();\n\tif (!vkGetInstanceProcAddr)\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot get vkGetInstanceProcAddr address\\n\" );\n\t\treturn false;\n\t}\n\n\tvk_core.pool = Mem_AllocPool(\"Vulkan pool\");\n\n\tloadInstanceFunctions(nullinst_funcs, ARRAYSIZE(nullinst_funcs));\n\n\tif (vkEnumerateInstanceVersion)\n\t{\n\t\tvkEnumerateInstanceVersion(&vk_core.vulkan_version);\n\t}\n\telse\n\t{\n\t\tvk_core.vulkan_version = VK_MAKE_VERSION(1, 0, 0);\n\t}\n\n\tgEngine.Con_Printf( \"Vulkan version %u.%u.%u\\n\", XVK_PARSE_VERSION(vk_core.vulkan_version));\n\n\tif (!createInstance())\n\t\treturn false;\n\n\tvk_core.surface.surface = gEngine.XVK_CreateSurface(vk_core.instance);\n\tif (!vk_core.surface.surface)\n\t{\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot create Vulkan surface\\n\" );\n\t\treturn false;\n\t}\n\n#if USE_AFTERMATH\n\tif (!VK_AftermathInit()) {\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot initialize Nvidia Nsight Aftermath SDK\\n\" );\n\t}\n#endif\n\n\tif (!vDeviceInit((VDeviceInitArgs){\n\t\t\t\t.force_disable_rt = CVAR_TO_BOOL(rt_force_disable),\n\t\t\t\t.enable_perf_query = gEngine.Sys_CheckParm(\"-vkperfquery\"),\n\t\t\t}))\n\t\treturn false;\n\n\tVK_LoadCvarsAfterInit();\n\n\tR_VkResourcesInit();\n\n\tif (!R_VkImageInit())\n\t\treturn false;\n\n\tif (!R_VkCombuf_Init())\n\t\treturn false;\n\n\tif (!initSurface())\n\t\treturn false;\n\n\tif (!VK_DevMemInit())\n\t\treturn false;\n\n\tif (!R_VkStagingInit())\n\t\treturn false;\n\n\tif (!VK_PipelineInit())\n\t\treturn false;\n\n\t// TODO ...\n\tif (!VK_DescriptorInit())\n\t\treturn false;\n\n\tif (!VK_FrameCtlInit())\n\t\treturn false;\n\n\tif (!R_GeometryBuffer_Init())\n\t\treturn false;\n\n\tif (!VK_RenderInit())\n\t\treturn false;\n\n\tVK_StudioInit();\n\n\tVK_SceneInit();\n\n\tR_TexturesInit();\n\n\t// All below need render_pass\n\n\tif (!R_VkOverlay_Init())\n\t\treturn false;\n\n\tif (!R_BrushInit())\n\t\treturn false;\n\n\tif (vk_core.rtx)\n\t{\n\t\t// FIXME move all this to rt-specific modules\n\t\tif (!VK_LightsInit())\n\t\t\treturn false;\n\n\t\tif (!VK_RayInit())\n\t\t\treturn false;\n\t}\n\n\tR_SpriteInit();\n\tR_BeamInit();\n\n\tR_ClearDecals();\n\n\tINFO(\"R_VkInit done\");\n\treturn true;\n}\n\nvoid R_VkShutdown( void ) {\n\tXVK_CHECK(vkDeviceWaitIdle(vk_core.device));\n\n\tVK_EntityDataClear();\n\n\tR_SpriteShutdown();\n\n\tif (vk_core.rtx)\n\t{\n\t\tVK_LightsShutdown();\n\t\tVK_RayShutdown();\n\t}\n\n\tR_BrushShutdown();\n\tVK_StudioShutdown();\n\tR_VkOverlay_Shutdown();\n\n\tVK_RenderShutdown();\n\tR_GeometryBuffer_Shutdown();\n\n\tVK_FrameCtlShutdown();\n\n\tR_VkMaterialsShutdown();\n\n\tR_TexturesShutdown();\n\n\tVK_PipelineShutdown();\n\n\tVK_DescriptorShutdown();\n\n\tR_VkStagingShutdown();\n\n\tR_VkCombuf_Destroy();\n\n\tVK_DevMemDestroy();\n\n\tvDeviceShutdown();\n\n#if USE_AFTERMATH\n\tVK_AftermathShutdown();\n#endif\n\n\tif (vk_core.debug_messenger)\n\t{\n\t\tvkDestroyDebugUtilsMessengerEXT(vk_core.instance, vk_core.debug_messenger, NULL);\n\t}\n\n\tMem_Free(vk_core.surface.present_modes);\n\tMem_Free(vk_core.surface.surface_formats);\n\tvkDestroySurfaceKHR(vk_core.instance, vk_core.surface.surface, NULL);\n\tvkDestroyInstance(vk_core.instance, NULL);\n\tMem_FreePool(&vk_core.pool);\n\n\tgEngine.R_Free_Video();\n}\n\nVkSemaphore R_VkSemaphoreCreate( void ) {\n\tVkSemaphore sema;\n\tVkSemaphoreCreateInfo sci = {\n\t\t.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,\n\t\t.flags = 0,\n\t};\n\tXVK_CHECK(vkCreateSemaphore(vk_core.device, &sci, NULL, &sema));\n\treturn sema;\n}\n\nvoid R_VkSemaphoreDestroy(VkSemaphore sema) {\n\tvkDestroySemaphore(vk_core.device, sema, NULL);\n}\n\nVkFence R_VkFenceCreate( qboolean signaled ) {\n\tVkFence fence;\n\tconst VkFenceCreateInfo fci = {\n\t\t.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,\n\t\t.flags = signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0,\n\t};\n\tXVK_CHECK(vkCreateFence(vk_core.device, &fci, NULL, &fence));\n\treturn fence;\n}\n\nvoid R_VkFenceDestroy(VkFence fence) {\n\tvkDestroyFence(vk_core.device, fence, NULL);\n}\n"
  },
  {
    "path": "ref/vk/vk_core.h",
    "content": "#pragma once\n#include \"vk_common.h\"\n\n#include \"xash3d_types.h\"\n#include \"com_strings.h\" // S_ERROR\n\n#include \"vulkan/VNvAftermath.h\" // TODO remove explicit usage in XVK_CHECK\n#include \"vulkan/VDevice.h\"\n\n#define VK_NO_PROTOTYPES\n#include <vulkan/vulkan.h>\n\n#define XVK_PARSE_VERSION(v) \\\n\tVK_VERSION_MAJOR(v), \\\n\tVK_VERSION_MINOR(v), \\\n\tVK_VERSION_PATCH(v)\n\nqboolean R_VkInit( void );\nvoid R_VkShutdown( void );\n\nVkSemaphore R_VkSemaphoreCreate( void );\nvoid R_VkSemaphoreDestroy(VkSemaphore sema);\n\nVkFence R_VkFenceCreate( qboolean signaled );\nvoid R_VkFenceDestroy(VkFence fence);\n\ntypedef struct vulkan_core_s {\n\tuint32_t vulkan_version;\n\tVkInstance instance;\n\tVkDebugUtilsMessengerEXT debug_messenger;\n\n\tpoolhandle_t pool;\n\n\t// TODO store important capabilities that affect render code paths\n\t// (as rtx, dedicated gpu memory, bindless, etc) separately in a struct\n\tqboolean debug, validate, rtx, nv_checkpoint;\n\tstruct {\n\t\tVkSurfaceKHR surface;\n\t\tuint32_t num_surface_formats;\n\t\tVkSurfaceFormatKHR *surface_formats;\n\n\t\tuint32_t num_present_modes;\n\t\tVkPresentModeKHR *present_modes;\n\t} surface;\n\n\tVkDevice device;\n\tVkQueue queue;\n\n\tunsigned int num_devices;\n\tref_device_t *devices;\n} vulkan_core_t;\n\nextern vulkan_core_t vk_core;\n\nconst char *R_VkResultName(VkResult result);\nconst char *R_VkPresentModeName(VkPresentModeKHR present_mode);\nconst char *R_VkFormatName(VkFormat format);\nconst char *R_VkColorSpaceName(VkColorSpaceKHR colorspace);\nconst char *R_VkImageLayoutName(VkImageLayout);\nconst char *R_VkDescriptorTypeName(VkDescriptorType);\n\n#define SET_DEBUG_NAME(object, type, name) \\\ndo { \\\n\tif (vk_core.debug) { \\\n\t\tVkDebugUtilsObjectNameInfoEXT duoni = { \\\n\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, \\\n\t\t\t.objectHandle = (uint64_t)object, \\\n\t\t\t.objectType = type, \\\n\t\t\t.pObjectName = name, \\\n\t\t}; \\\n\t\tXVK_CHECK(vkSetDebugUtilsObjectNameEXT(vk_core.device, &duoni)); \\\n\t} \\\n} while (0)\n\n#define SET_DEBUG_NAMEF(object, type, fmt, ...) \\\ndo { \\\n\tif (vk_core.debug) { \\\n\t\tchar buffer[1024]; \\\n\t\tVkDebugUtilsObjectNameInfoEXT duoni = { \\\n\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, \\\n\t\t\t.objectHandle = (uint64_t)(uintptr_t)object, \\\n\t\t\t.objectType = type, \\\n\t\t\t.pObjectName = buffer, \\\n\t\t}; \\\n\t\tQ_snprintf(buffer, sizeof(buffer), fmt, ##__VA_ARGS__); \\\n\t\tXVK_CHECK(vkSetDebugUtilsObjectNameEXT(vk_core.device, &duoni)); \\\n\t} \\\n} while (0)\n\n#define DEBUG_BEGIN(cmdbuf, msg) \\\n\tdo { \\\n\t\tif (vk_core.debug) { \\\n\t\t\tconst VkDebugUtilsLabelEXT label = { \\\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, \\\n\t\t\t\t.pLabelName = msg, \\\n\t\t\t}; \\\n\t\t\tvkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \\\n\t\t\tDEBUG_NV_CHECKPOINTF(cmdbuf, \"begin %s\", msg); \\\n\t\t} \\\n\t} while(0)\n\n#define DEBUG_BEGINF(cmdbuf, fmt, ...) \\\n\tdo { \\\n\t\tif (vk_core.debug) { \\\n\t\t\tchar buf[128]; \\\n\t\t\tsnprintf(buf, sizeof(buf), fmt, ##__VA_ARGS__); \\\n\t\t\tconst VkDebugUtilsLabelEXT label = { \\\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, \\\n\t\t\t\t.pLabelName = buf, \\\n\t\t\t}; \\\n\t\t\tvkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label); \\\n\t\t\tDEBUG_NV_CHECKPOINTF(cmdbuf, \"begin \" fmt, ##__VA_ARGS__); \\\n\t\t} \\\n\t} while(0)\n\n#define DEBUG_END(cmdbuf) \\\n\tdo { \\\n\t\tif (vk_core.debug) { \\\n\t\t\tvkCmdEndDebugUtilsLabelEXT(cmdbuf); \\\n\t\t\tDEBUG_NV_CHECKPOINTF(cmdbuf, \"end \"); /* TODO: find corresponding begin */ \\\n\t\t} \\\n\t} while(0)\n\n// TODO make this not fatal: devise proper error handling strategies\n// FIXME Host_Error does not cause process to exit, we need to handle this manually\n#define XVK_CHECK(f) do { \\\n\t\tconst VkResult result = f; \\\n\t\tif (result != VK_SUCCESS) { \\\n\t\t\tgEngine.Con_Printf( S_ERROR \"%s:%d \" #f \" failed (%d): %s\\n\", \\\n\t\t\t\t__FILE__, __LINE__, result, R_VkResultName(result)); \\\n\t\t\tDEBUG_NV_CHECKPOINT_DUMP(); \\\n\t\t\tgEngine.Host_Error( S_ERROR \"%s:%d \" #f \" failed (%d): %s\\n\", \\\n\t\t\t\t__FILE__, __LINE__, result, R_VkResultName(result)); \\\n\t\t} \\\n\t} while(0)\n\n#define INSTANCE_FUNCS(X) \\\n\tX(vkDestroyInstance) \\\n\tX(vkEnumeratePhysicalDevices) \\\n\tX(vkGetPhysicalDeviceProperties) \\\n\tX(vkGetPhysicalDeviceProperties2) \\\n\tX(vkGetPhysicalDeviceFeatures2) \\\n\tX(vkGetPhysicalDeviceQueueFamilyProperties) \\\n\tX(vkGetPhysicalDeviceSurfaceSupportKHR) \\\n\tX(vkGetPhysicalDeviceMemoryProperties2) \\\n\tX(vkGetPhysicalDeviceSurfacePresentModesKHR) \\\n\tX(vkGetPhysicalDeviceSurfaceFormatsKHR) \\\n\tX(vkGetPhysicalDeviceSurfaceCapabilitiesKHR) \\\n\tX(vkGetPhysicalDeviceFormatProperties) \\\n\tX(vkCreateDevice) \\\n\tX(vkGetDeviceProcAddr) \\\n\tX(vkDestroyDevice) \\\n\tX(vkDestroySurfaceKHR) \\\n\tX(vkEnumerateDeviceExtensionProperties) \\\n\tX(vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR) \\\n\tX(vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR) \\\n\n#define INSTANCE_DEBUG_FUNCS(X) \\\n\tX(vkCreateDebugUtilsMessengerEXT) \\\n\tX(vkDestroyDebugUtilsMessengerEXT) \\\n\tX(vkCmdBeginDebugUtilsLabelEXT) \\\n\tX(vkCmdEndDebugUtilsLabelEXT) \\\n\tX(vkCmdInsertDebugUtilsLabelEXT) \\\n\tX(vkSetDebugUtilsObjectNameEXT) \\\n\n#define DEVICE_FUNCS(X) \\\n\tX(vkGetDeviceQueue) \\\n\tX(vkCreateSwapchainKHR) \\\n\tX(vkGetSwapchainImagesKHR) \\\n\tX(vkDestroySwapchainKHR) \\\n\tX(vkCreateImageView) \\\n\tX(vkCreateFramebuffer) \\\n\tX(vkCreateRenderPass) \\\n\tX(vkCreatePipelineCache) \\\n\tX(vkDestroyPipelineCache) \\\n\tX(vkCreatePipelineLayout) \\\n\tX(vkCreateGraphicsPipelines) \\\n\tX(vkCreateShaderModule) \\\n\tX(vkCreateCommandPool) \\\n\tX(vkAllocateCommandBuffers) \\\n\tX(vkCreateBuffer) \\\n\tX(vkGetBufferMemoryRequirements) \\\n\tX(vkAllocateMemory) \\\n\tX(vkBindBufferMemory) \\\n\tX(vkMapMemory) \\\n\tX(vkUnmapMemory) \\\n\tX(vkDestroyBuffer) \\\n\tX(vkFreeMemory) \\\n\tX(vkAcquireNextImageKHR) \\\n\tX(vkCmdBeginRenderPass) \\\n\tX(vkCmdExecuteCommands) \\\n\tX(vkCmdEndRenderPass) \\\n\tX(vkEndCommandBuffer) \\\n\tX(vkQueueSubmit) \\\n\tX(vkQueuePresentKHR) \\\n\tX(vkWaitForFences) \\\n\tX(vkWaitSemaphores) \\\n\tX(vkResetFences) \\\n\tX(vkCreateSemaphore) \\\n\tX(vkDestroySemaphore) \\\n\tX(vkCreateFence) \\\n\tX(vkDestroyFence) \\\n\tX(vkBeginCommandBuffer) \\\n\tX(vkCmdBindPipeline) \\\n\tX(vkCmdBindVertexBuffers) \\\n\tX(vkCmdDraw) \\\n\tX(vkDestroyCommandPool) \\\n\tX(vkDestroyImageView) \\\n\tX(vkDestroyFramebuffer) \\\n\tX(vkDestroyRenderPass) \\\n\tX(vkDestroyShaderModule) \\\n\tX(vkDestroyPipeline) \\\n\tX(vkDestroyPipelineLayout) \\\n\tX(vkCreateImage) \\\n\tX(vkGetImageMemoryRequirements) \\\n\tX(vkBindImageMemory) \\\n\tX(vkCmdPipelineBarrier) \\\n\tX(vkCmdPipelineBarrier2) \\\n\tX(vkCmdCopyBufferToImage) \\\n\tX(vkCmdCopyBuffer) \\\n\tX(vkQueueWaitIdle) \\\n\tX(vkDeviceWaitIdle) \\\n\tX(vkDestroyImage) \\\n\tX(vkCmdBindDescriptorSets) \\\n\tX(vkCreateSampler) \\\n\tX(vkDestroySampler) \\\n\tX(vkCreateDescriptorPool) \\\n\tX(vkDestroyDescriptorPool) \\\n\tX(vkCreateDescriptorSetLayout) \\\n\tX(vkAllocateDescriptorSets) \\\n\tX(vkUpdateDescriptorSets) \\\n\tX(vkDestroyDescriptorSetLayout) \\\n\tX(vkCmdSetViewport) \\\n\tX(vkCmdSetScissor) \\\n\tX(vkCmdUpdateBuffer) \\\n\tX(vkCmdBindIndexBuffer) \\\n\tX(vkCmdDrawIndexed) \\\n\tX(vkCmdPushConstants) \\\n\tX(vkCreateComputePipelines) \\\n\tX(vkCmdDispatch) \\\n\tX(vkCmdBlitImage) \\\n\tX(vkCmdClearColorImage) \\\n\tX(vkCmdCopyImage) \\\n\tX(vkGetImageSubresourceLayout) \\\n\tX(vkCmdSetCheckpointNV) \\\n\tX(vkGetQueueCheckpointDataNV) \\\n\tX(vkCreateQueryPool) \\\n\tX(vkDestroyQueryPool) \\\n\tX(vkCmdResetQueryPool) \\\n\tX(vkCmdBeginQuery) \\\n\tX(vkCmdEndQuery) \\\n\tX(vkCmdWriteTimestamp) \\\n\tX(vkGetQueryPoolResults) \\\n\tX(vkGetCalibratedTimestampsEXT) \\\n\tX(vkAcquireProfilingLockKHR) \\\n\tX(vkReleaseProfilingLockKHR) \\\n\tX(vkResetQueryPool) \\\n\n#define DEVICE_FUNCS_RTX(X) \\\n\tX(vkGetAccelerationStructureBuildSizesKHR) \\\n\tX(vkCreateAccelerationStructureKHR) \\\n\tX(vkGetBufferDeviceAddress) \\\n\tX(vkCmdBuildAccelerationStructuresKHR) \\\n\tX(vkDestroyAccelerationStructureKHR) \\\n\tX(vkGetAccelerationStructureDeviceAddressKHR) \\\n\tX(vkCmdTraceRaysKHR) \\\n\tX(vkCreateRayTracingPipelinesKHR) \\\n\tX(vkGetRayTracingShaderGroupHandlesKHR) \\\n\n#define X(f) extern PFN_##f f;\n\tDEVICE_FUNCS(X)\n\tDEVICE_FUNCS_RTX(X)\n\tINSTANCE_FUNCS(X)\n\tINSTANCE_DEBUG_FUNCS(X)\n#undef X\n\n// TODO is there a better place for this, vk_utils.h?\ntypedef struct {\n\tVkAccessFlags2 access;\n\tVkPipelineStageFlagBits2 stage;\n} r_vksync_scope_t;\n"
  },
  {
    "path": "ref/vk/vk_cvar.c",
    "content": "#include \"vk_cvar.h\"\n#include \"vk_common.h\"\n#include \"vk_core.h\"\n#include \"vk_logs.h\"\n\n#define NONEXTERN_CVAR(cvar) cvar_t *cvar;\nDECLARE_CVAR(NONEXTERN_CVAR)\n#undef NONEXTERN_CVAR\n\nDEFINE_ENGINE_SHARED_CVAR_LIST()\n\nstatic void setDebugLog( void ) {\n\tconst int argc = gEngine.Cmd_Argc();\n\tconst char *const modules = argc > 1 ? gEngine.Cmd_Argv(1) : \"\";\n\tgEngine.Cvar_Set(\"vk_debug_log_\", modules);\n\tR_LogSetVerboseModules( modules );\n}\n\nvoid VK_LoadCvars( void )\n{\n#define gEngfuncs gEngine // ...\n\tRETRIEVE_ENGINE_SHARED_CVAR_LIST()\n\n\tr_lighting_modulate = gEngine.Cvar_Get( \"r_lighting_modulate\", \"0.6\", FCVAR_ARCHIVE, \"lightstyles modulate scale\" );\n\tcl_lightstyle_lerping = gEngine.pfnGetCvarPointer( \"cl_lightstyle_lerping\", 0 );\n\tr_lightmap = gEngine.Cvar_Get( \"r_lightmap\", \"0\", FCVAR_CHEAT, \"lightmap debugging tool\" );\n\tr_infotool = gEngine.Cvar_Get( \"r_infotool\", \"0\", FCVAR_CHEAT, \"DEBUG: print entity info under crosshair\" );\n\trt_force_disable = gEngine.Cvar_Get( \"rt_force_disable\", \"0\", FCVAR_GLCONFIG, \"Force disable Ray Tracing\" );\n\tvk_device_target_id = gEngine.Cvar_Get( \"vk_device_target_id\", \"\", FCVAR_GLCONFIG, \"Selected video device id\" );\n\n\tvk_debug_log = gEngine.Cvar_Get(\"vk_debug_log_\", \"\", FCVAR_GLCONFIG | FCVAR_READ_ONLY, \"\");\n\tR_LogSetVerboseModules( vk_debug_log->string );\n\n\tgEngine.Cmd_AddCommand(\"vk_debug_log\", setDebugLog, \"Set modules to enable debug logs for\");\n}\n\nvoid VK_LoadCvarsAfterInit( void )\n{\n\trt_capable = gEngine.Cvar_Get( \"rt_capable\", vk_core.rtx ? \"1\" : \"0\", FCVAR_READ_ONLY, \"\" );\n\n\tif (vk_core.rtx) {\n\t\trt_enable = gEngine.Cvar_Get( \"rt_enable\", \"1\", FCVAR_GLCONFIG, \"Enable or disable Ray Tracing mode\" );\n\t\trt_bounces = gEngine.Cvar_Get( \"rt_bounces\", \"3\", FCVAR_GLCONFIG, \"Path tracing ray bounces\" );\n\t\trt_only_diffuse_gi = gEngine.Cvar_Get(\"rt_only_diffuse_gi\", \"\", FCVAR_GLCONFIG, \"Make global illumination only diffuse\");\n\t\trt_separated_reflection = gEngine.Cvar_Get(\"rt_separated_reflection\", \"\", FCVAR_GLCONFIG, \"Add separated high quality reflection pass\");\n\t\trt_denoise_gi_by_sh = gEngine.Cvar_Get(\"rt_denoise_gi_by_sh\", \"\", FCVAR_GLCONFIG, \"Denoise global illumination by spherical harmonics\");\n\t\trt_disable_gi = gEngine.Cvar_Get(\"rt_disable_gi\", \"\", FCVAR_GLCONFIG, \"Disable global illumination calculation\");\n\t\trt_spatial_reconstruction = gEngine.Cvar_Get(\"rt_spatial_reconstruction\", \"\", FCVAR_GLCONFIG, \"Apply spatial reconstruction to specular\");\n\t} else {\n\t\trt_enable = gEngine.Cvar_Get( \"rt_enable\", \"0\", FCVAR_READ_ONLY, \"DISABLED: Ray tracing is not supported by your hardware/drivers\" );\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_cvar.h",
    "content": "#pragma once\n\n#include \"cvardef.h\"\n\n#include \"xash3d_types.h\" // required for ref_api.h\n#include \"const.h\" // required for ref_api.h\n#include \"com_model.h\" // required for ref_api.h\n#include \"ref_api.h\"\n\n// from engine/common/cvar.h\n#define FCVAR_READ_ONLY\t\t(1<<17)\t// cannot be set by user at all, and can't be requested by CvarGetPointer from game dlls\n\n#define CVAR_TO_BOOL( x )\t\t((x) && ((x)->value != 0.0f) ? true : false )\n\nvoid VK_LoadCvars( void );\nvoid VK_LoadCvarsAfterInit( void );\n\n#define DECLARE_CVAR(X) \\\n\tX(cl_lightstyle_lerping) \\\n\tX(r_lighting_modulate) \\\n\tX(r_lightmap) \\\n\tX(r_infotool) \\\n\tX(vk_device_target_id) \\\n\tX(vk_debug_log) \\\n\tX(rt_capable) \\\n\tX(rt_force_disable) \\\n\tX(rt_enable) \\\n\tX(rt_bounces) \\\n\tX(rt_only_diffuse_gi) \\\n\tX(rt_separated_reflection) \\\n\tX(rt_denoise_gi_by_sh) \\\n\tX(rt_disable_gi) \\\n\tX(rt_spatial_reconstruction) \\\n\n#define EXTERN_CVAR(cvar) extern cvar_t *cvar;\nDECLARE_CVAR(EXTERN_CVAR)\n#undef EXTERN_CVAR\n\nDECLARE_ENGINE_SHARED_CVAR_LIST()\n"
  },
  {
    "path": "ref/vk/vk_entity_data.c",
    "content": "#include \"vk_entity_data.h\"\n\n#include \"vk_common.h\" // ASSERT\n\n#include <stddef.h> // NULL\n\n// TODO proper hash map with dynamic size, etc\n#define MAX_ENTITIES 1024\n\ntypedef struct {\n\tconst struct cl_entity_s *entity;\n\tvoid *userdata;\n\tentity_data_dtor_f *dtor;\n} entity_data_cache_entry_t;\n\nstruct {\n\tint entries_count;\n\tentity_data_cache_entry_t entries[MAX_ENTITIES];\n} g_entdata;\n\nvoid* VK_EntityDataGet(const struct cl_entity_s* entity) {\n\tfor (int i = 0; i < g_entdata.entries_count; ++i) {\n\t\tentity_data_cache_entry_t *const entry = g_entdata.entries + i;\n\t\tif (entry->entity == entity)\n\t\t\treturn entry->userdata;\n\t}\n\n\treturn NULL;\n}\n\nvoid VK_EntityDataClear(void) {\n\tfor (int i = 0; i < g_entdata.entries_count; ++i) {\n\t\tentity_data_cache_entry_t *const entry = g_entdata.entries + i;\n\t\tentry->dtor(entry->userdata);\n\t}\n\n\tg_entdata.entries_count = 0;\n}\n\nvoid VK_EntityDataSet(const struct cl_entity_s* entity, void *userdata, entity_data_dtor_f *dtor) {\n\tfor (int i = 0; i < g_entdata.entries_count; ++i) {\n\t\tentity_data_cache_entry_t *const entry = g_entdata.entries + i;\n\t\tif (entry->entity == entity) {\n\t\t\tentry->dtor(entry->userdata);\n\t\t\tentry->userdata = userdata;\n\t\t\tentry->dtor = dtor;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tASSERT(g_entdata.entries_count < MAX_ENTITIES);\n\tentity_data_cache_entry_t *const entry = g_entdata.entries + g_entdata.entries_count;\n\tentry->entity = entity;\n\tentry->userdata = userdata;\n\tentry->dtor = dtor;\n\t++g_entdata.entries_count;\n}\n"
  },
  {
    "path": "ref/vk/vk_entity_data.h",
    "content": "#pragma once\n\nstruct cl_entity_s;\nvoid* VK_EntityDataGet(const struct cl_entity_s*);\n\ntypedef void (entity_data_dtor_f)(void*);\n\n// Will destroy and overwrite the older userdata if it exists.\n// TODO: Make sure that the older userdata is not used (i.e. in parallel on GPU for rendering a still in-flight frame).\n//   This'd require a proper usage tracking (e.g. using refcounts) with changes to the rest of the renderer.\n//   Someday...\nvoid VK_EntityDataSet(const struct cl_entity_s*, void *userdata, entity_data_dtor_f *dtor);\n\nvoid VK_EntityDataClear(void);\n\n// TODO a function to LRU-clear userdata that hasn't been used for a few frames\n"
  },
  {
    "path": "ref/vk/vk_framectl.c",
    "content": "#include \"vk_framectl.h\"\n\n#include \"vk_overlay.h\"\n#include \"vk_scene.h\"\n#include \"vk_render.h\"\n#include \"vk_cvar.h\"\n#include \"vulkan/VDevmem.h\"\n#include \"vulkan/VSwapchain.h\"\n#include \"vulkan/VImage.h\"\n#include \"vulkan/VStaging.h\"\n#include \"vulkan/VCommandPool.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"vk_logs.h\"\n#include \"vulkan/VBarrier.h\"\n#include \"vulkan/VResource.h\"\n\n#include \"std/arrays.h\"\n#include \"std/profiler.h\"\n#include \"r_speeds.h\"\n\n#include \"eiface.h\" // ARRAYSIZE\n\n#include <string.h>\n\n#define LOG_MODULE fctl\n\nextern ref_globals_t *gpGlobals;\n\nvk_framectl_t vk_frame = {0};\n\n// Phase tracking is needed for getting screenshots. Basically, getting a screenshot does the same things as R_EndFrame, and they need to be congruent.\ntypedef enum {\n\tPhase_Idle,\n\tPhase_FrameBegan, // Called R_BeginFrame()\n\tPhase_FrameRendered, // Called VK_RenderFrame()\n\tPhase_RenderingEnqueued, //\n\tPhase_Submitted,\n} frame_phase_t;\n\ntypedef struct {\n\tvk_combuf_t *combuf;\n\tVkFence fence_done;\n\tVkSemaphore sem_framebuffer_ready;\n\n\t// This extra semaphore is required because we need to synchronize 2 things on GPU:\n\t// 1. swapchain\n\t// 2. next frame command buffer\n\t// Unfortunately waiting on semaphore also means resetting it when it is signaled\n\t// so we can't reuse the same one for two purposes and need to mnozhit sunchnosti\n\tVkSemaphore sem_done2;\n\n\tuint32_t staging_frame_tag;\n} vk_framectl_frame_t;\n\nstatic struct {\n\tvk_framectl_frame_t frames[MAX_CONCURRENT_FRAMES];\n\n\tstruct {\n\t\tint index;\n\t\tr_vk_swapchain_framebuffer_t framebuffer;\n\t\tframe_phase_t phase;\n\t} current;\n\n\tuint32_t sequence;\n} g_frame;\n\n#define PROFILER_SCOPES(X) \\\n\tX(frame, \"Frame\", APROF_SCOPE_FLAG_DECOR); \\\n\tX(begin_frame, \"R_BeginFrame\", 0); \\\n\tX(render_frame, \"VK_RenderFrame\", 0); \\\n\tX(end_frame, \"R_EndFrame\", 0); \\\n\tX(frame_gpu_wait, \"Wait for GPU\", APROF_SCOPE_FLAG_WAIT); \\\n\tX(wait_for_frame_fence, \"waitForFrameFence\", APROF_SCOPE_FLAG_WAIT); \\\n\n#define SCOPE_DECLARE(scope, name, flags) APROF_SCOPE_DECLARE(scope)\nPROFILER_SCOPES(SCOPE_DECLARE)\n#undef SCOPE_DECLARE\n\n// TODO move into vk_image\nstatic VkFormat findSupportedImageFormat(const VkFormat *candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {\n\tfor (int i = 0; candidates[i] != VK_FORMAT_UNDEFINED; ++i) {\n\t\tVkFormatProperties props;\n\t\tVkFormatFeatureFlags props_format;\n\t\tvkGetPhysicalDeviceFormatProperties(v_device_info.physical_device, candidates[i], &props);\n\t\tswitch (tiling) {\n\t\t\tcase VK_IMAGE_TILING_OPTIMAL:\n\t\t\t\tprops_format = props.optimalTilingFeatures; break;\n\t\t\tcase VK_IMAGE_TILING_LINEAR:\n\t\t\t\tprops_format = props.linearTilingFeatures; break;\n\t\t\tdefault:\n\t\t\t\treturn VK_FORMAT_UNDEFINED;\n\t\t}\n\t\tif ((props_format & features) == features)\n\t\t\treturn candidates[i];\n\t}\n\n\treturn VK_FORMAT_UNDEFINED;\n}\n\n// TODO sort these based on ???\nstatic const VkFormat depth_formats[] = {\n\tVK_FORMAT_D32_SFLOAT,\n\tVK_FORMAT_D24_UNORM_S8_UINT,\n\tVK_FORMAT_X8_D24_UNORM_PACK32,\n\tVK_FORMAT_D16_UNORM,\n\tVK_FORMAT_D32_SFLOAT_S8_UINT,\n\tVK_FORMAT_D16_UNORM_S8_UINT,\n\tVK_FORMAT_UNDEFINED\n};\n\nstatic VkRenderPass createRenderPass( VkFormat depth_format, qboolean ray_tracing ) {\n\tVkRenderPass render_pass;\n\n\tconst VkAttachmentDescription attachments[] = {{\n\t\t.format = SWAPCHAIN_FORMAT,\n\t\t.samples = VK_SAMPLE_COUNT_1_BIT,\n\t\t.loadOp = ray_tracing ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR /* TODO: prod renderer should not care VK_ATTACHMENT_LOAD_OP_DONT_CARE */,\n\t\t.storeOp = VK_ATTACHMENT_STORE_OP_STORE,\n\t\t.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,\n\t\t.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,\n\t\t.initialLayout = ray_tracing ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,\n\t\t.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR\n\t}, {\n\t\t// Depth\n\t\t.format = depth_format,\n\t\t.samples = VK_SAMPLE_COUNT_1_BIT,\n\t\t.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,\n\t\t.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,\n\t\t.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,\n\t\t.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,\n\t\t.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,\n\t\t.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL\n\t}};\n\n\tconst VkAttachmentReference color_attachment = {\n\t\t.attachment = 0,\n\t\t.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,\n\t};\n\n\tconst VkAttachmentReference depth_attachment = {\n\t\t.attachment = 1,\n\t\t.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,\n\t};\n\n\tconst VkSubpassDescription subdesc = {\n\t\t.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,\n\t\t.colorAttachmentCount = 1,\n\t\t.pColorAttachments = &color_attachment,\n\t\t.pDepthStencilAttachment = &depth_attachment,\n\t};\n\n\tBOUNDED_ARRAY(VkSubpassDependency, dependencies, 2);\n\tif (vk_core.rtx) {\n\t\tconst VkSubpassDependency color = {\n\t\t\t.srcSubpass = VK_SUBPASS_EXTERNAL,\n\t\t\t.dstSubpass = 0,\n\t\t\t.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,\n\t\t\t.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,\n\t\t\t.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,\n\t\t\t.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,\n\t\t\t.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,\n\t\t};\n\t\tBOUNDED_ARRAY_APPEND_ITEM(dependencies, color);\n\t} else {\n\t\tconst VkSubpassDependency color = {\n\t\t\t.srcSubpass = VK_SUBPASS_EXTERNAL,\n\t\t\t.dstSubpass = 0,\n\t\t\t.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,\n\t\t\t.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,\n\t\t\t.srcAccessMask = 0,\n\t\t\t.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,\n\t\t\t.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,\n\t\t};\n\t\tBOUNDED_ARRAY_APPEND_ITEM(dependencies, color);\n\t}\n\n\tconst VkSubpassDependency depth = {\n\t\t.srcSubpass = VK_SUBPASS_EXTERNAL,\n\t\t.dstSubpass = 0,\n\t\t.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,\n\t\t.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,\n\t\t.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,\n\t\t.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,\n\t\t.dependencyFlags = 0,\n\t};\n\tBOUNDED_ARRAY_APPEND_ITEM(dependencies, depth);\n\n\tconst VkRenderPassCreateInfo rpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,\n\t\t.attachmentCount = ARRAYSIZE(attachments),\n\t\t.pAttachments = attachments,\n\t\t.subpassCount = 1,\n\t\t.pSubpasses = &subdesc,\n\t\t.dependencyCount = dependencies.count,\n\t\t.pDependencies = dependencies.items,\n\t};\n\n\tXVK_CHECK(vkCreateRenderPass(vk_core.device, &rpci, NULL, &render_pass));\n\treturn render_pass;\n}\n\nstatic void waitForFrameFence( void ) {\n\n\t// TODO: wait for small amount of time (~1-10ms?), calling gEngine.CL_ExtraUpdate() each time we wake up\n\t// Why: CL_ExtraUpdate() is needed when the renderer is stuck doing something for a long time\n\t// It allows the engine to make progress on other things. Think cooperative multitasking.\n\t// Alt: Make dedicated render thread and wait there. But that'd be a huge and messy project.\n\n\tAPROF_SCOPE_BEGIN(wait_for_frame_fence);\n\tconst VkFence fence_done[1] = {g_frame.frames[g_frame.current.index].fence_done};\n\tfor(qboolean loop = true; loop; ) {\n#define MAX_WAIT (10ull * 1000*1000*1000)\n\t\tconst VkResult fence_result = vkWaitForFences(vk_core.device, COUNTOF(fence_done), fence_done, VK_TRUE, MAX_WAIT);\n#undef MAX_WAIT\n\t\tswitch (fence_result) {\n\t\t\tcase VK_SUCCESS:\n\t\t\t\tloop = false;\n\t\t\t\tbreak;\n\t\t\tcase VK_TIMEOUT:\n\t\t\t\tgEngine.Con_Printf(S_ERROR \"Waiting for frame fence to be signaled timed out after 10 seconds. Wat\\n\");\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tXVK_CHECK(fence_result);\n\t\t}\n\t}\n\n\tXVK_CHECK(vkResetFences(vk_core.device, COUNTOF(fence_done), fence_done));\n\tAPROF_SCOPE_END(wait_for_frame_fence);\n}\n\n/*\nstatic void updateGamma( void ) {\n\t// FIXME when\n\t{\n\t\tcvar_t* vid_gamma = gEngine.pfnGetCvarPointer( \"gamma\", 0 );\n\t\tcvar_t* vid_brightness = gEngine.pfnGetCvarPointer( \"brightness\", 0 );\n\t\tif( gEngine.R_DoResetGamma( ))\n\t\t{\n\t\t\t// paranoia cubemaps uses this\n\t\t\tgEngine.BuildGammaTable( 1.8f, 0.0f );\n\n\t\t\t// paranoia cubemap rendering\n\t\t\tif( gEngine.drawFuncs->GL_BuildLightmaps )\n\t\t\t\tgEngine.drawFuncs->GL_BuildLightmaps( );\n\t\t}\n\t\telse if( FBitSet( vid_gamma->flags, FCVAR_CHANGED ) || FBitSet( vid_brightness->flags, FCVAR_CHANGED ))\n\t\t{\n\t\t\tgEngine.BuildGammaTable( vid_gamma->value, vid_brightness->value );\n\t\t\t// FIXME rebuild lightmaps\n\t\t}\n\t}\n}\n*/\n\nvoid R_BeginFrame( qboolean clearScene ) {\n\tif (g_frame.current.phase == Phase_FrameBegan) {\n\t\tWARN(\"R_BeginFrame() called without finishing the previous frame\");\n\t\treturn;\n\t}\n\n\tAPROF_SCOPE_DECLARE_BEGIN(begin_frame_tail, \"R_BeginFrame_tail\");\n\tASSERT(g_frame.current.phase == Phase_Submitted || g_frame.current.phase == Phase_Idle);\n\tg_frame.current.index = (g_frame.current.index + 1) % MAX_CONCURRENT_FRAMES;\n\tvk_framectl_frame_t *const frame = g_frame.frames + g_frame.current.index;\n\n\tg_frame.sequence++;\n\n\t{\n\t\twaitForFrameFence();\n\t\t// Current command buffer is done and available\n\t\t// Previous might still be in flight\n\t}\n\n\t// Now that's the previous frame is done, we can mark all its resources as released\n\t// TODO foreach(resource)->release()\n\n\tAPROF_SCOPE_END(begin_frame_tail);\n\n\tconst uint32_t prev_frame_event_index = aprof_scope_frame();\n\n\tAPROF_SCOPE_BEGIN(frame);\n\tAPROF_SCOPE_BEGIN(begin_frame);\n\n\t{\n\t\tconst VCombufProfilingResult gpurofl[] = { R_VkCombufProfilingGetResult(frame->combuf) };\n\t\tR_SpeedsDisplayMore(prev_frame_event_index, gpurofl, COUNTOF(gpurofl));\n\t}\n\n\tif (vk_core.rtx && FBitSet( rt_enable->flags, FCVAR_CHANGED )) {\n\t\tvk_frame.rtx_enabled = CVAR_TO_BOOL( rt_enable );\n\t}\n\tClearBits( rt_enable->flags, FCVAR_CHANGED );\n\n\t//updateGamma();\n\n\tASSERT(!g_frame.current.framebuffer.framebuffer);\n\n\t// TODO explicit frame dependency synced on frame-end-event/sema\n\t// see release() above\n\tR_VkStagingFrameCompleted(frame->staging_frame_tag);\n\n\tg_frame.current.framebuffer = R_VkSwapchainAcquire( frame->sem_framebuffer_ready );\n\tvk_frame.width = g_frame.current.framebuffer.image.width;\n\tvk_frame.height = g_frame.current.framebuffer.image.height;\n\n\t// TODO replace this with resource release above\n\t// Mind the frame & resolution, though\n\tVK_RenderBegin( vk_frame.rtx_enabled );\n\n\tg_frame.current.phase = Phase_FrameBegan;\n\tAPROF_SCOPE_END(begin_frame);\n}\n\nvoid VK_RenderFrame( const struct ref_viewpass_s *rvp )\n{\n\tASSERT(g_frame.current.phase == Phase_FrameBegan || g_frame.current.phase == Phase_FrameRendered);\n\tAPROF_SCOPE_BEGIN(render_frame);\n\tVK_SceneRender( rvp );\n\tg_frame.current.phase = Phase_FrameRendered;\n\tAPROF_SCOPE_END(render_frame);\n}\n\nstatic void enqueueRendering( vk_combuf_t* combuf, qboolean draw ) {\n\tAPROF_SCOPE_DECLARE_BEGIN(enqueue, __FUNCTION__);\n\tconst uint32_t frame_width = g_frame.current.framebuffer.image.width;\n\tconst uint32_t frame_height = g_frame.current.framebuffer.image.height;\n\n\tASSERT(g_frame.current.phase == Phase_FrameBegan || g_frame.current.phase == Phase_FrameRendered);\n\n\tR_VkCombufBegin( combuf );\n\n\t// TODO: should be done by rendering when it requests textures\n\tR_VkImageUploadCommit(combuf,\n\t\tVK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | (vk_frame.rtx_enabled ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : 0));\n\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\tif (vk_frame.rtx_enabled) {\n\t\tVK_RenderEndRTX( combuf, &g_frame.current.framebuffer.image );\n\t} else {\n\t\tVK_RenderEndPrepare_FIXME(combuf, &(FrameContext){\n\t\t\t.frame_sequence = g_frame.sequence,\n\t\t});\n\t}\n\n\tif (draw) {\n\t\t{\n\t\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);\n\t\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t\t.image = &g_frame.current.framebuffer.image,\n\t\t\t\t.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,\n\t\t\t\t.access = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,\n\t\t\t});\n\t\t\tbarrierCommit(&barrier, combuf);\n\t\t}\n\n\t\tconst VkClearValue clear_value[] = {\n\t\t\t// *_UNORM is float\n\t\t\t{.color = {.float32 = {1.f, 0.f, 0.f, 0.f}}},\n\t\t\t{.depthStencil = {1., 0.}} // TODO reverse-z\n\t\t};\n\t\tconst VkRenderPassBeginInfo rpbi = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,\n\t\t\t.renderPass = vk_frame.rtx_enabled ? vk_frame.render_pass.after_ray_tracing : vk_frame.render_pass.raster,\n\t\t\t.renderArea.extent.width = frame_width,\n\t\t\t.renderArea.extent.height = frame_height,\n\t\t\t.clearValueCount = ARRAYSIZE(clear_value),\n\t\t\t.pClearValues = clear_value,\n\t\t\t.framebuffer = g_frame.current.framebuffer.framebuffer,\n\t\t};\n\t\tvkCmdBeginRenderPass(cmdbuf, &rpbi, VK_SUBPASS_CONTENTS_INLINE);\n\n\t\t{\n\t\t\tconst VkViewport viewport[] = {\n\t\t\t\t{0.f, 0.f, (float)frame_width, (float)frame_height, 0.f, 1.f},\n\t\t\t};\n\t\t\tconst VkRect2D scissor[] = {{\n\t\t\t\t{0, 0},\n\t\t\t\t{frame_width, frame_height},\n\t\t\t}};\n\n\t\t\tvkCmdSetViewport(cmdbuf, 0, ARRAYSIZE(viewport), viewport);\n\t\t\tvkCmdSetScissor(cmdbuf, 0, ARRAYSIZE(scissor), scissor);\n\t\t}\n\t}\n\n\tif (!vk_frame.rtx_enabled)\n\t\tVK_RenderEnd( combuf, draw,\n\t\t\tframe_width, frame_height,\n\t\t\tg_frame.current.index\n\t\t\t);\n\n\tR_VkOverlay_DrawAndFlip( cmdbuf, draw );\n\n\tif (draw) {\n\t\tvkCmdEndRenderPass(cmdbuf);\n\n\t\t// Render pass's finalLayout transitions the image into this one\n\t\tg_frame.current.framebuffer.image.sync.read.access = 0;\n\t\tg_frame.current.framebuffer.image.sync.write.access = 0;\n\t\tg_frame.current.framebuffer.image.sync.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;\n\t}\n\n\tg_frame.current.phase = Phase_RenderingEnqueued;\n\tAPROF_SCOPE_END(enqueue);\n}\n\n// FIXME pass frame, not combuf (possible desync)\nstatic void submit( vk_combuf_t* combuf, qboolean wait, qboolean draw ) {\n\tAPROF_SCOPE_DECLARE_BEGIN(submit, __FUNCTION__);\n\tASSERT(g_frame.current.phase == Phase_RenderingEnqueued);\n\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\tvk_framectl_frame_t *const frame = g_frame.frames + g_frame.current.index;\n\tvk_framectl_frame_t *const prev_frame = g_frame.frames + (g_frame.current.index + 1) % MAX_CONCURRENT_FRAMES;\n\n\t// Push things from staging that weren't explicitly pulled by frame builder\n\tframe->staging_frame_tag = R_VkStagingFrameEpilogue(combuf);\n\n\tR_VkCombufEnd(combuf);\n\n\n\tBOUNDED_ARRAY(VkCommandBuffer, cmdbufs, 2);\n\tBOUNDED_ARRAY_APPEND_ITEM(cmdbufs, cmdbuf);\n\n\t{\n\t\tBOUNDED_ARRAY(VkSemaphore, waitophores, 2);\n\t\tBOUNDED_ARRAY(VkPipelineStageFlags, wait_stageflags, 2);\n\t\tBOUNDED_ARRAY(VkSemaphore, signalphores, 2);\n\n\t\tif (draw) {\n\t\t\tBOUNDED_ARRAY_APPEND_ITEM(waitophores, frame->sem_framebuffer_ready);\n\t\t\tBOUNDED_ARRAY_APPEND_ITEM(wait_stageflags, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);\n\n\t\t\tBOUNDED_ARRAY_APPEND_ITEM(signalphores, g_frame.current.framebuffer.done);\n\t\t}\n\n\t\tBOUNDED_ARRAY_APPEND_ITEM(waitophores, prev_frame->sem_done2);\n\t\t// TODO remove this second semaphore altogether, replace it with properly tracked barriers.\n\t\t// Why: would allow more parallelizm between consecutive frames.\n\t\tBOUNDED_ARRAY_APPEND_ITEM(wait_stageflags, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT);\n\t\tBOUNDED_ARRAY_APPEND_ITEM(signalphores, frame->sem_done2);\n\n\t\tDEBUG(\"submit: frame=%d, staging_tag=%u, combuf=%p, wait for semaphores[%d]={%llx, %llx}, signal semaphores[%d]={%llx, %llx}\",\n\t\t\tg_frame.current.index,\n\t\t\tframe->staging_frame_tag,\n\t\t\tframe->combuf->cmdbuf,\n\t\t\twaitophores.count,\n\t\t\t(unsigned long long)waitophores.items[0],\n\t\t\t(unsigned long long)waitophores.items[1],\n\t\t\tsignalphores.count,\n\t\t\t(unsigned long long)signalphores.items[0],\n\t\t\t(unsigned long long)signalphores.items[1]\n\t\t);\n\n\t\tASSERT(waitophores.count == wait_stageflags.count);\n\n\t\tconst VkSubmitInfo subinfo = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,\n\t\t\t.pNext = NULL,\n\t\t\t.waitSemaphoreCount = waitophores.count,\n\t\t\t.pWaitSemaphores = waitophores.items,\n\t\t\t.pWaitDstStageMask = wait_stageflags.items,\n\t\t\t.commandBufferCount = cmdbufs.count,\n\t\t\t.pCommandBuffers = cmdbufs.items,\n\t\t\t.signalSemaphoreCount = signalphores.count,\n\t\t\t.pSignalSemaphores = signalphores.items,\n\t\t};\n\t\tXVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, frame->fence_done));\n\t\tg_frame.current.phase = Phase_Submitted;\n\t}\n\n\tif (g_frame.current.framebuffer.framebuffer != VK_NULL_HANDLE)\n\t\tR_VkSwapchainPresent(g_frame.current.framebuffer.index);\n\n\tg_frame.current.framebuffer = (r_vk_swapchain_framebuffer_t){0};\n\n\tif (wait) {\n\t\tAPROF_SCOPE_BEGIN(frame_gpu_wait);\n\t\tXVK_CHECK(vkWaitForFences(vk_core.device, 1, &frame->fence_done, VK_TRUE, INT64_MAX));\n\t\tAPROF_SCOPE_END(frame_gpu_wait);\n\n\t\t/* if (vk_core.debug) { */\n\t\t/* \t// FIXME more scopes */\n\t\t/* \tXVK_CHECK(vkQueueWaitIdle(vk_core.queue)); */\n\t\t/* } */\n\t\tg_frame.current.phase = Phase_Idle;\n\t}\n\n\tAPROF_SCOPE_END(submit);\n}\n\nvoid R_EndFrame( void )\n{\n\tAPROF_SCOPE_BEGIN_EARLY(end_frame);\n\n\tif (g_frame.current.phase == Phase_FrameBegan || g_frame.current.phase == Phase_FrameRendered) {\n\t\tvk_combuf_t *const combuf = g_frame.frames[g_frame.current.index].combuf;\n\t\tconst qboolean draw = g_frame.current.framebuffer.framebuffer != VK_NULL_HANDLE;\n\t\tenqueueRendering( combuf, draw );\n\t\tsubmit( combuf, false, draw );\n\t\t//submit( cmdbuf, true, draw );\n\t}\n\n\tAPROF_SCOPE_END(end_frame);\n\tAPROF_SCOPE_END(frame);\n}\n\nqboolean VK_FrameCtlInit( void )\n{\n\tPROFILER_SCOPES(APROF_SCOPE_INIT_EX);\n\n\tconst VkFormat depth_format = findSupportedImageFormat(depth_formats, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);\n\n\t// FIXME move this out to renderers\n\tvk_frame.render_pass.raster = createRenderPass(depth_format, false);\n\tif (vk_core.rtx)\n\t\tvk_frame.render_pass.after_ray_tracing = createRenderPass(depth_format, true);\n\n\tif (!R_VkSwapchainInit(vk_frame.render_pass.raster, depth_format))\n\t\treturn false;\n\n\tfor (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) {\n\t\tvk_framectl_frame_t *const frame = g_frame.frames + i;\n\t\tframe->combuf = R_VkCombufOpen();\n\n\t\tframe->sem_framebuffer_ready = R_VkSemaphoreCreate();\n\t\tSET_DEBUG_NAMEF(frame->sem_framebuffer_ready, VK_OBJECT_TYPE_SEMAPHORE, \"framebuffer_ready[%d]\", i);\n\t\tframe->sem_done2 = R_VkSemaphoreCreate();\n\t\tSET_DEBUG_NAMEF(frame->sem_done2, VK_OBJECT_TYPE_SEMAPHORE, \"done2[%d]\", i);\n\t\tframe->fence_done = R_VkFenceCreate(true);\n\t\tSET_DEBUG_NAMEF(frame->fence_done, VK_OBJECT_TYPE_FENCE, \"done[%d]\", i);\n\t}\n\n\t// Signal first frame semaphore as done\n\t{\n\t\tconst VkSubmitInfo subinfo = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,\n\t\t\t.pNext = NULL,\n\t\t\t.commandBufferCount = 0,\n\t\t\t.pCommandBuffers = NULL,\n\t\t\t.waitSemaphoreCount = 0,\n\t\t\t.pWaitSemaphores = NULL,\n\t\t\t.pWaitDstStageMask = NULL,\n\t\t\t.signalSemaphoreCount = 1,\n\t\t\t.pSignalSemaphores = &g_frame.frames[0].sem_done2,\n\t\t};\n\t\tXVK_CHECK(vkQueueSubmit(vk_core.queue, 1, &subinfo, VK_NULL_HANDLE));\n\t}\n\n\tvk_frame.rtx_enabled = vk_core.rtx;\n\n\treturn true;\n}\n\nvoid VK_FrameCtlShutdown( void ) {\n\tfor (int i = 0; i < MAX_CONCURRENT_FRAMES; ++i) {\n\t\tvk_framectl_frame_t *const frame = g_frame.frames + i;\n\t\tR_VkCombufClose(frame->combuf);\n\t\tR_VkSemaphoreDestroy(frame->sem_framebuffer_ready);\n\t\tR_VkSemaphoreDestroy(frame->sem_done2);\n\t\tR_VkFenceDestroy(frame->fence_done);\n\t}\n\n\tR_VkSwapchainShutdown();\n\n\tvkDestroyRenderPass(vk_core.device, vk_frame.render_pass.raster, NULL);\n\tif (vk_core.rtx)\n\t\tvkDestroyRenderPass(vk_core.device, vk_frame.render_pass.after_ray_tracing, NULL);\n}\n\nstatic qboolean canBlitFromSwapchainToFormat( VkFormat dest_format ) {\n\tVkFormatProperties props;\n\n\tvkGetPhysicalDeviceFormatProperties(v_device_info.physical_device, SWAPCHAIN_FORMAT, &props);\n\tif (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {\n\t\tgEngine.Con_Reportf(S_WARN \"Swapchain source format doesn't support blit\\n\");\n\t\treturn false;\n\t}\n\n\tvkGetPhysicalDeviceFormatProperties(v_device_info.physical_device, dest_format, &props);\n\tif (!(props.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) {\n\t\tgEngine.Con_Reportf(S_WARN \"Destination format doesn't support blit\\n\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic rgbdata_t *R_VkReadPixels( void ) {\n\tconst VkFormat dest_format = VK_FORMAT_R8G8B8A8_UNORM;\n\tr_vk_image_t temp_image;\n\tr_vk_image_t *const framebuffer_image = &g_frame.current.framebuffer.image;\n\trgbdata_t *r_shot = NULL;\n\tqboolean blit = canBlitFromSwapchainToFormat( dest_format );\n\n\tvk_combuf_t *const combuf = g_frame.frames[g_frame.current.index].combuf;\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\tif (framebuffer_image->image == VK_NULL_HANDLE) {\n\t\tgEngine.Con_Printf(S_ERROR \"no current image, can't take screenshot\\n\");\n\t\treturn NULL;\n\t}\n\n\t// Create destination image to blit/copy framebuffer pixels to\n\t{\n\t\tconst r_vk_image_create_t xic = {\n\t\t\t.debug_name = \"screenshot\",\n\t\t\t.width = vk_frame.width,\n\t\t\t.height = vk_frame.height,\n\t\t\t.depth = 1,\n\t\t\t.mips = 1,\n\t\t\t.layers = 1,\n\t\t\t.format = dest_format,\n\t\t\t.tiling = VK_IMAGE_TILING_LINEAR,\n\t\t\t.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,\n\t\t\t.flags = 0,\n\t\t\t.memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT,\n\t\t};\n\t\ttemp_image = R_VkImageCreate(&xic);\n\t}\n\n\t// Make sure that all rendering ops are enqueued\n\tconst qboolean draw = true;\n\tenqueueRendering( combuf, draw );\n\n\t// Blit/transfer\n\tif (blit) {\n\t\tR_VkImageBlit(combuf, &(r_vkimage_blit_args){\n\t\t\t.src = {\n\t\t\t\t.image = framebuffer_image,\n\t\t\t\t.width = vk_frame.width,\n\t\t\t\t.height = vk_frame.height,\n\t\t\t\t.depth = 1,\n\t\t\t},\n\t\t\t.dst = {\n\t\t\t\t.image = &temp_image,\n\t\t\t\t.width = vk_frame.width,\n\t\t\t\t.height = vk_frame.height,\n\t\t\t\t.depth = 1,\n\t\t\t},\n\t\t});\n\t} else {\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_COPY_BIT);\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t.image = &temp_image,\n\t\t\t.access = VK_ACCESS_2_TRANSFER_WRITE_BIT,\n\t\t\t.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t});\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t.image = framebuffer_image,\n\t\t\t.access = VK_ACCESS_2_TRANSFER_READ_BIT,\n\t\t\t.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\n\t\tconst VkImageCopy copy = {\n\t\t\t.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.srcSubresource.layerCount = 1,\n\t\t\t.dstSubresource.layerCount = 1,\n\t\t\t.extent.width = vk_frame.width,\n\t\t\t.extent.height = vk_frame.height,\n\t\t\t.extent.depth = 1,\n\t\t};\n\n\t\tvkCmdCopyImage(cmdbuf,\n\t\t\tframebuffer_image->image, framebuffer_image->sync.layout,\n\t\t\ttemp_image.image, temp_image.sync.layout, 1, &copy);\n\n\t\tgEngine.Con_Printf(S_WARN \"Blit is not supported, screenshot will likely have mixed components; TODO: swizzle in software\\n\");\n\t}\n\n\t{\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT | VK_PIPELINE_STAGE_2_HOST_BIT);\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t// Temp image: prepare for reading on CPU\n\t\t\t.image = &temp_image,\n\t\t\t.access = VK_ACCESS_2_MEMORY_READ_BIT,\n\t\t\t.layout = VK_IMAGE_LAYOUT_GENERAL,\n\t\t});\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t// Framebuffer image: prepare for displaying\n\t\t\t.image = framebuffer_image,\n\t\t\t.access = 0,\n\t\t\t.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,\n\t\t});\n\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\t{\n\t\tconst qboolean wait = true;\n\t\tsubmit( combuf, wait, draw );\n\t}\n\n\t// copy bytes to buffer\n\t{\n\t\tconst VkImageSubresource subres = {\n\t\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t};\n\t\tVkSubresourceLayout layout;\n\t\tconst char *mapped = temp_image.devmem.mapped;\n\t\tvkGetImageSubresourceLayout(vk_core.device, temp_image.image, &subres, &layout);\n\n\t\tmapped += layout.offset;\n\n\t\t{\n\t\t\tconst int row_size = 4 * vk_frame.width;\n\t\t\tpoolhandle_t r_temppool = vk_core.pool; // TODO\n\n\t\t\tr_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));\n\t\t\tr_shot->width = vk_frame.width;\n\t\t\tr_shot->height = vk_frame.height;\n\t\t\tr_shot->flags = IMAGE_HAS_COLOR;\n\t\t\tr_shot->type = PF_RGBA_32;\n\t\t\tr_shot->size = r_shot->width * r_shot->height * gEngine.Image_GetPFDesc( r_shot->type )->bpp;\n\t\t\tr_shot->palette = NULL;\n\t\t\tr_shot->buffer = Mem_Malloc( r_temppool, r_shot->size );\n\n\t\t\tif (!blit) {\n\t\t\t\tif (dest_format != VK_FORMAT_R8G8B8A8_UNORM || SWAPCHAIN_FORMAT != VK_FORMAT_B8G8R8A8_UNORM) {\n\t\t\t\t\tgEngine.Con_Printf(S_WARN \"Don't have a blit function for this format pair, will save as-is without conversion; expect image to look wrong\\n\");\n\t\t\t\t\tblit = true;\n\t\t\t\t} else {\n\t\t\t\t\tbyte *dst = r_shot->buffer;\n\t\t\t\t\tfor (int y = 0; y < vk_frame.height; ++y, mapped += layout.rowPitch) {\n\t\t\t\t\t\tconst byte *src = (const byte*)mapped;\n\t\t\t\t\t\tfor (int x = 0; x < vk_frame.width; ++x, dst += 4, src += 4) {\n\t\t\t\t\t\t\tdst[0] = src[2];\n\t\t\t\t\t\t\tdst[1] = src[1];\n\t\t\t\t\t\t\tdst[2] = src[0];\n\t\t\t\t\t\t\tdst[3] = src[3];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (blit) {\n\t\t\t\tfor (int y = 0; y < vk_frame.height; ++y, mapped += layout.rowPitch) {\n\t\t\t\t\tmemcpy(r_shot->buffer + row_size * y, mapped, row_size);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tR_VkImageDestroy( &temp_image );\n\n\treturn r_shot;\n}\n\nqboolean VID_ScreenShot( const char *filename, int shot_type )\n{\n\tuint flags = 0;\n\tint\twidth = 0, height = 0;\n\tqboolean\tresult;\n\n\tconst uint64_t start_ns = aprof_time_now_ns();\n\n\t// get screen frame\n\trgbdata_t *r_shot = R_VkReadPixels();\n\tif (!r_shot)\n\t\treturn false;\n\n\tswitch( shot_type )\n\t{\n\tcase VID_SCREENSHOT:\n\t\tbreak;\n\tcase VID_SNAPSHOT:\n\t\tgEngine.fsapi->AllowDirectPaths( true );\n\t\tbreak;\n\tcase VID_LEVELSHOT:\n\t\tflags |= IMAGE_RESAMPLE;\n\t\tif( gpGlobals->wideScreen )\n\t\t{\n\t\t\theight = 480;\n\t\t\twidth = 800;\n\t\t}\n\t\telse\n\t\t{\n\t\t\theight = 480;\n\t\t\twidth = 640;\n\t\t}\n\t\tbreak;\n\tcase VID_MINISHOT:\n\t\tflags |= IMAGE_RESAMPLE;\n\t\theight = 200;\n\t\twidth = 320;\n\t\tbreak;\n\tcase VID_MAPSHOT:\n\t\tflags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE;\t// GoldSrc request overviews in 8-bit format\n\t\theight = 768;\n\t\twidth = 1024;\n\t\tbreak;\n\t}\n\n\tgEngine.Image_Process( &r_shot, width, height, flags, 0.0f );\n\n\t// write image\n\tconst uint64_t save_begin_ns = aprof_time_now_ns();\n\tresult = gEngine.FS_SaveImage( filename, r_shot );\n\tconst uint64_t save_end_ns = aprof_time_now_ns();\n\n\tgEngine.fsapi->AllowDirectPaths( false );\t\t\t// always reset after store screenshot\n\tgEngine.FS_FreeImage( r_shot );\n\n\tconst uint64_t end_ns = aprof_time_now_ns();\n\tgEngine.Con_Printf(\"Wrote screenshot %s. Saving file: %.03fms, total: %.03fms\\n\",\n\t\tfilename, (save_end_ns - save_begin_ns) / 1e6, (end_ns - start_ns) / 1e6);\n\treturn result;\n}\n"
  },
  {
    "path": "ref/vk/vk_framectl.h",
    "content": "#pragma once\n#include \"vk_core.h\"\n\n#include \"xash3d_types.h\"\n\n#define MAX_CONCURRENT_FRAMES 2\n\n// TODO most of the things below should not be global. Instead, they should be passed as an argument/context to all the drawing functions that want this info\ntypedef struct vk_framectl_s {\n\t// TODO only used from 2d and r_speeds, remove\n\tuint32_t width, height;\n\n\t// TODO move these into renderer and 2d\n\tstruct {\n\t\t// Used when the entire rendering is traditional triangle rasterization\n\t\t// Discards and clears color buffer\n\t\tVkRenderPass raster;\n\n\t\t// Used for 2D overlay rendering after ray tracing pass\n\t\t// Preserves color buffer contents\n\t\tVkRenderPass after_ray_tracing;\n\t} render_pass;\n\n\tqboolean rtx_enabled;\n} vk_framectl_t;\n\nextern vk_framectl_t vk_frame;\n\nqboolean VK_FrameCtlInit( void );\nvoid VK_FrameCtlShutdown( void );\n\nvoid R_BeginFrame( qboolean clearScene );\nvoid VK_RenderFrame( const struct ref_viewpass_s *rvp );\nvoid R_EndFrame( void );\n\nqboolean VID_ScreenShot( const char *filename, int shot_type );\n"
  },
  {
    "path": "ref/vk/vk_geometry.c",
    "content": "#include \"vk_geometry.h\"\n#include \"vulkan/VBuffer.h\"\n#include \"vulkan/VResource.h\"\n#include \"r_speeds.h\"\n\n#define MODULE_NAME \"geom\"\n\n#define MAX_BUFFER_VERTICES_STATIC (128 * 1024)\n#define MAX_BUFFER_INDICES_STATIC (MAX_BUFFER_VERTICES_STATIC * 3)\n#define GEOMETRY_BUFFER_STATIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_STATIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_STATIC * sizeof(uint16_t), sizeof(vk_vertex_t))\n\n#define MAX_BUFFER_VERTICES_DYNAMIC (128 * 1024 * 2)\n#define MAX_BUFFER_INDICES_DYNAMIC (MAX_BUFFER_VERTICES_DYNAMIC * 3)\n#define GEOMETRY_BUFFER_DYNAMIC_SIZE ALIGN_UP(MAX_BUFFER_VERTICES_DYNAMIC * sizeof(vk_vertex_t) + MAX_BUFFER_INDICES_DYNAMIC * sizeof(uint16_t), sizeof(vk_vertex_t))\n\n#define GEOMETRY_BUFFER_SIZE (GEOMETRY_BUFFER_STATIC_SIZE + GEOMETRY_BUFFER_DYNAMIC_SIZE)\n\n// TODO profiler counters\n\nstatic struct {\n\tvk_buffer_t buffer;\n\tr_blocks_t alloc;\n\n\tProducer producer;\n\n\tstruct {\n\t\tint vertices, indices;\n\t\tint dyn_vertices, dyn_indices;\n\t} stats;\n} g_geom;\n\nr_geometry_range_t R_GeometryRangeAlloc(int vertices, int indices) {\n\tconst uint32_t vertices_size = vertices * sizeof(vk_vertex_t);\n\tconst uint32_t indices_size = indices * sizeof(uint16_t);\n\tconst uint32_t total_size = vertices_size + indices_size;\n\n\tr_geometry_range_t ret = {\n\t\t.block_handle = R_BlockAllocLong(&g_geom.alloc, total_size, sizeof(vk_vertex_t)),\n\t};\n\n\tif (!ret.block_handle.size)\n\t\treturn ret;\n\n\tret.vertices.unit_offset = ret.block_handle.offset / sizeof(vk_vertex_t);\n\tret.indices.unit_offset = (ret.block_handle.offset + vertices_size) / sizeof(uint16_t);\n\n\tret.vertices.count = vertices;\n\tret.indices.count = indices;\n\n\tg_geom.stats.indices += indices;\n\tg_geom.stats.vertices += vertices;\n\n\treturn ret;\n}\n\nvoid R_GeometryRangeFree(const r_geometry_range_t* range) {\n\tR_BlockRelease(&range->block_handle);\n\n\tg_geom.stats.indices -= range->indices.count;\n\tg_geom.stats.vertices -= range->vertices.count;\n}\n\nr_geometry_range_lock_t R_GeometryRangeLock(const r_geometry_range_t *range) {\n\tconst vk_buffer_lock_t staging_args = {\n\t\t.offset = range->block_handle.offset,\n\t\t.size = range->block_handle.size,\n\t};\n\n\tconst vk_buffer_locked_t staging = R_VkBufferLock(&g_geom.buffer, staging_args);\n\tASSERT(staging.ptr);\n\n\tconst uint32_t vertices_size = range->vertices.count * sizeof(vk_vertex_t);\n\n\tASSERT( range->block_handle.offset % sizeof(vk_vertex_t) == 0 );\n\tASSERT( (range->block_handle.offset + vertices_size) % sizeof(uint16_t) == 0 );\n\n\treturn (r_geometry_range_lock_t){\n\t\t.vertices = (vk_vertex_t *)staging.ptr,\n\t\t.indices = PTR_CAST(uint16_t, (char*)staging.ptr + vertices_size),\n\t\t.impl_ = {\n\t\t\t.staging_handle = staging,\n\t\t},\n\t};\n}\n\nr_geometry_range_lock_t R_GeometryRangeLockSubrange(const r_geometry_range_t *range, int vertices_offset, int vertices_count ) {\n\tconst vk_buffer_lock_t staging_args = {\n\t\t.offset = range->block_handle.offset + sizeof(vk_vertex_t) * vertices_offset,\n\t\t.size = sizeof(vk_vertex_t) * vertices_count,\n\t};\n\n\tASSERT(staging_args.offset >= range->block_handle.offset);\n\tASSERT(staging_args.offset + staging_args.size <= range->block_handle.offset + range->block_handle.size);\n\n\tconst vk_buffer_locked_t staging = R_VkBufferLock(&g_geom.buffer, staging_args);\n\tASSERT(staging.ptr);\n\n\tASSERT( range->block_handle.offset % sizeof(vk_vertex_t) == 0 );\n\n\treturn (r_geometry_range_lock_t){\n\t\t.vertices = (vk_vertex_t *)staging.ptr,\n\t\t.indices = NULL,\n\t\t.impl_ = {\n\t\t\t.staging_handle = staging,\n\t\t},\n\t};\n}\n\nvoid R_GeometryRangeUnlock(const r_geometry_range_lock_t *lock) {\n\tR_VkBufferUnlock(lock->impl_.staging_handle);\n}\n\nqboolean R_GeometryBufferAllocOnceAndLock(r_geometry_buffer_lock_t *lock, int vertex_count, int index_count) {\n\tconst uint32_t vertices_size = vertex_count * sizeof(vk_vertex_t);\n\tconst uint32_t indices_size = index_count * sizeof(uint16_t);\n\tconst uint32_t total_size = vertices_size + indices_size;\n\n\tconst uint32_t offset = R_BlockAllocOnce(&g_geom.alloc, total_size, sizeof(vk_vertex_t));\n\n\tif (offset == ALO_ALLOC_FAILED) {\n\t\t/* gEngine.Con_Printf(S_ERROR \"Cannot allocate %s geometry buffer for %d vertices (%d bytes) and %d indices (%d bytes)\\n\", */\n\t\t/* \tlifetime == LifetimeSingleFrame ? \"dynamic\" : \"static\", */\n\t\t/* \tvertex_count, vertices_size, index_count, indices_size); */\n\t\treturn false;\n\t}\n\n\t{\n\t\tconst uint32_t vertices_offset = offset / sizeof(vk_vertex_t);\n\t\tconst uint32_t indices_offset = (offset + vertices_size) / sizeof(uint16_t);\n\t\tconst vk_buffer_lock_t staging_args = {\n\t\t\t.offset = offset,\n\t\t\t.size = total_size,\n\t\t};\n\n\t\tconst vk_buffer_locked_t staging = R_VkBufferLock(&g_geom.buffer, staging_args);\n\t\tASSERT(staging.ptr);\n\n\t\tASSERT( offset % sizeof(vk_vertex_t) == 0 );\n\t\tASSERT( (offset + vertices_size) % sizeof(uint16_t) == 0 );\n\n\t\t*lock = (r_geometry_buffer_lock_t) {\n\t\t\t.vertices = {\n\t\t\t\t.count = vertex_count,\n\t\t\t\t.ptr = (vk_vertex_t *)staging.ptr,\n\t\t\t\t.unit_offset = vertices_offset,\n\t\t\t},\n\t\t\t.indices = {\n\t\t\t\t.count = index_count,\n\t\t\t\t.ptr = PTR_CAST(uint16_t, (char*)staging.ptr + vertices_size),\n\t\t\t\t.unit_offset = indices_offset,\n\t\t\t},\n\t\t\t.impl_ = {\n\t\t\t\t.handle_ = staging,\n\t\t\t},\n\t\t};\n\t}\n\n\tg_geom.stats.dyn_vertices += vertex_count;\n\tg_geom.stats.dyn_indices += index_count;\n\n\treturn true;\n}\n\nvoid R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock ) {\n\tR_VkBufferUnlock(lock->impl_.handle_);\n}\n\nvoid R_GeometryBuffer_MapClear( void ) {\n\t// Obsolete, don't really need to do anything\n\t// TODO for diag/debug reasons we might want to check that there are no leaks, i.e.\n\t// allocated blocks count remains constant and doesn't grow between maps\n}\n\nstatic void produceGeometry(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx);\n\nstatic void registerGeometryBufferAs(const char *name) {\n\tR_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = name,\n\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t.buffer = &g_geom.buffer,\n\t\t.offset = 0,\n\t\t.size = g_geom.buffer.size,\n\t\t.producer = &g_geom.producer,\n\t});\n}\n\nqboolean R_GeometryBuffer_Init(void) {\n\t// TODO device memory and friends (e.g. handle mobile memory ...)\n\n\tif (!VK_BufferCreate(\"geometry buffer\", &g_geom.buffer, GEOMETRY_BUFFER_SIZE,\n\t\tVK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | (vk_core.rtx ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0),\n\t\t(vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0)))\n\t\treturn false;\n\n\tg_geom.producer = (Producer) {\n\t\t.name = \"geometry\",\n\t\t.produce = produceGeometry,\n\t};\n\n\tregisterGeometryBufferAs(\"geometry\");\n\n\t// TODO resource alias?\n\tregisterGeometryBufferAs(\"vertices\");\n\tregisterGeometryBufferAs(\"indices\");\n\n#define EXPECTED_ALLOCS 1024\n\tR_BlocksCreate(&g_geom.alloc, GEOMETRY_BUFFER_SIZE, GEOMETRY_BUFFER_DYNAMIC_SIZE, EXPECTED_ALLOCS);\n\n\tR_SPEEDS_METRIC(g_geom.alloc.allocated_long, \"used\", kSpeedsMetricBytes);\n\tR_SPEEDS_METRIC(g_geom.stats.vertices, \"vertices\", kSpeedsMetricCount);\n\tR_SPEEDS_METRIC(g_geom.stats.indices, \"indices\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_geom.stats.dyn_vertices, \"dyn_vertices\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_geom.stats.dyn_indices, \"dyn_indices\", kSpeedsMetricCount);\n\n\treturn true;\n}\n\nvoid R_GeometryBuffer_Shutdown(void) {\n\tR_BlocksDestroy(&g_geom.alloc);\n\tVK_BufferDestroy( &g_geom.buffer );\n}\n\nvoid R_GeometryBuffer_Flip(void) {\n\tR_BlocksClearOnce(&g_geom.alloc);\n}\n\nstatic void produceGeometry(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\t(void)ctx;\n\tASSERT(p == &g_geom.producer);\n\tR_VkBufferStagingCommit(&g_geom.buffer, combuf);\n}\n"
  },
  {
    "path": "ref/vk/vk_geometry.h",
    "content": "#pragma once\n#include \"vk_common.h\"\n#include \"r_block.h\"\n#include \"vulkan/VBuffer.h\" // FIXME vk_buffer_locked_t should not be exposed\n#include \"vk_core.h\"\n\n#include <stdint.h>\n\n// General buffer usage pattern\n// 1. alloc (allocates buffer mem, stores allocation data)\n// 2. (returns void* buf and handle) write to buf\n// 3. upload and lock (ensures that all this data is in gpu mem, e.g. uploads from staging)\n// 4. ... use it\n// 5. free (frame/map end)\n\n// TODO is this a good place?\ntypedef struct vk_vertex_s {\n\t// TODO padding needed for storage buffer reading, figure out how to fix in GLSL/SPV side\n\tvec3_t pos; float pad0_;\n\tvec3_t prev_pos; float pad1_;\n\tvec3_t normal; uint32_t pad2_;\n\tvec3_t tangent; uint32_t pad3_;\n\tvec2_t gl_tc; //float p2_[2];\n\tvec2_t lm_tc; //float p3_[2];\n\trgba_t color;\n\tfloat pad4_[3];\n} vk_vertex_t;\n\ntypedef struct {\n\tstruct {\n\t\tint count, unit_offset;\n\t} vertices;\n\n\tstruct {\n\t\tint count, unit_offset;\n\t} indices;\n\n\tr_block_t block_handle;\n} r_geometry_range_t;\n\n// Allocates a range in geometry buffer with a long lifetime\nr_geometry_range_t R_GeometryRangeAlloc(int vertices, int indices);\nvoid R_GeometryRangeFree(const r_geometry_range_t*);\n\n// TODO combine with r_geometry_buffer_lock_t\ntypedef struct {\n\tvk_vertex_t *vertices;\n\tuint16_t *indices;\n\n\tstruct {\n\t\t// FIXME hide behind some index in geometry buffer\n\t\t// Think: what's the max simultaneously locked regions count\n\t\tvk_buffer_locked_t staging_handle;\n\t} impl_;\n} r_geometry_range_lock_t;\n\n// Lock staging memory for uploading\nr_geometry_range_lock_t R_GeometryRangeLock(const r_geometry_range_t *range);\nr_geometry_range_lock_t R_GeometryRangeLockSubrange(const r_geometry_range_t *range, int vertices_offset, int vertices_count );\nvoid R_GeometryRangeUnlock(const r_geometry_range_lock_t *lock);\n\ntypedef struct {\n\tstruct {\n\t\tvk_vertex_t *ptr;\n\t\tint count;\n\t\tint unit_offset;\n\t} vertices;\n\n\tstruct {\n\t\tuint16_t *ptr;\n\t\tint count;\n\t\tint unit_offset;\n\t} indices;\n\n\tstruct {\n\t\t// FIXME hide behind some index in geometry buffer\n\t\t// Think: what's the max simultaneously locked regions count\n\t\tvk_buffer_locked_t handle_;\n\t} impl_;\n} r_geometry_buffer_lock_t;\n\ntypedef enum {\n\tLifetimeLong,\n\tLifetimeSingleFrame\n} r_geometry_lifetime_t;\n\nqboolean R_GeometryBufferAllocOnceAndLock(r_geometry_buffer_lock_t *lock, int vertex_count, int index_count);\nvoid R_GeometryBufferUnlock( const r_geometry_buffer_lock_t *lock );\n\nvoid R_GeometryBuffer_MapClear( void ); // Free the entire buffer for a new map\n\nqboolean R_GeometryBuffer_Init(void);\nvoid R_GeometryBuffer_Shutdown(void);\n\nvoid R_GeometryBuffer_Flip(void);\n"
  },
  {
    "path": "ref/vk/vk_light.c",
    "content": "#include \"vk_light.h\"\n#include \"vulkan/VBuffer.h\"\n#include \"vk_mapents.h\"\n#include \"r_textures.h\"\n#include \"vk_lightmap.h\"\n#include \"vk_common.h\"\n#include \"shaders/ray_interop.h\"\n#include \"std/bitarray.h\"\n#include \"std/profiler.h\"\n#include \"vulkan/VStaging.h\"\n#include \"r_speeds.h\"\n#include \"vk_logs.h\"\n#include \"vk_framectl.h\"\n#include \"vulkan/VResource.h\"\n\n#include \"mod_local.h\"\n#include \"xash3d_mathlib.h\"\n\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h> // isalnum...\n\n#include \"camera.h\"\n#include \"pm_defs.h\"\n#include \"pmtrace.h\"\n\n#define MODULE_NAME \"light\"\n#define LOG_MODULE light\n\n#define PROFILER_SCOPES(X) \\\n\tX(finalize , \"RT_LightsFrameEnd\"); \\\n\tX(emissive_surface, \"VK_LightsAddEmissiveSurface\"); \\\n\tX(static_lights, \"add static lights\"); \\\n\tX(dlights, \"add dlights\"); \\\n\t//X(canSurfaceLightAffectAABB, \"canSurfaceLightAffectAABB\"); \\\n\n#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope)\nPROFILER_SCOPES(SCOPE_DECLARE)\n#undef SCOPE_DECLARE\n\ntypedef struct {\n\tuint8_t num_point_lights;\n\tuint8_t num_polygons;\n\n\tuint8_t point_lights[MAX_VISIBLE_POINT_LIGHTS];\n\tuint8_t polygons[MAX_VISIBLE_SURFACE_LIGHTS];\n\n\tstruct {\n\t\tuint8_t point_lights;\n\t\tuint8_t polygons;\n\t} num_static;\n\n\tuint32_t frame_sequence;\n} vk_lights_cell_t;\n\ntypedef struct {\n\tvec3_t emissive;\n\tqboolean set;\n} vk_emissive_texture_t;\n\ntypedef struct {\n\tvec4_t plane;\n\tvec3_t center;\n\tfloat area;\n\n\tvec3_t emissive;\n\n\tstruct {\n\t\tint offset, count; // reference g_light.polygon_vertices\n\t} vertices;\n\n\t// uint32_t kusok_index;\n} rt_light_polygon_t;\n\nenum {\n\tLightFlag_Environment = 0x1,\n};\n\ntypedef struct {\n\tvec3_t origin;\n\tvec3_t color;\n\tvec3_t dir;\n\tfloat stopdot;\n\tfloat stopdot2_or_costheta;\n\tfloat radius;\n\tint flags;\n\n\tint lightstyle;\n\tvec3_t base_color;\n} vk_point_light_t;\n\nstatic struct {\n\tstruct {\n\t\tvk_emissive_texture_t emissive_textures[MAX_TEXTURES];\n\t} map;\n\n\tstruct {\n\t\tint min_cell[3];\n\t\tint size[3];\n\t\tint cells;\n\t} grid;\n\n\tvk_lights_cell_t cells[MAX_LIGHT_CLUSTERS];\n\n\tvk_buffer_t buffer;\n\n\tint num_polygons;\n\trt_light_polygon_t polygons[MAX_SURFACE_LIGHTS];\n\n\tint num_point_lights;\n\tvk_point_light_t point_lights[MAX_POINT_LIGHTS];\n\n\tint num_polygon_vertices;\n\tvec3_t polygon_vertices[MAX_SURFACE_LIGHTS * 7];\n\n\tstruct {\n\t\tint point_lights;\n\t\tint polygons;\n\t\tint polygon_vertices;\n\t} num_static;\n\n\tbit_array_t visited_cells;\n\n\t// TODO depend on producer->produce(ctx.frame_sequence)\n\tuint32_t frame_sequence;\n\tProducer producer;\n\n\tstruct {\n\t\tint dirty_cells;\n\t\tint dirty_cells_size;\n\t\tint ranges_uploaded;\n\t\tint dynamic_polygons, dynamic_points;\n\t\tint dlights, elights;\n\t} stats;\n} g_lights_;\n\nstatic struct {\n\tqboolean enabled;\n\tchar name_filter[256];\n} debug_dump_lights;\n\nstatic void debugDumpLights( void ) {\n\tdebug_dump_lights.enabled = true;\n\tif (gEngine.Cmd_Argc() > 1) {\n\t\tQ_strncpy(debug_dump_lights.name_filter, gEngine.Cmd_Argv(1), sizeof(debug_dump_lights.name_filter));\n\t} else {\n\t\tdebug_dump_lights.name_filter[0] = '\\0';\n\t}\n}\n\nstatic void lightsProduce(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx);\n\nqboolean VK_LightsInit( void ) {\n\tPROFILER_SCOPES(APROF_SCOPE_INIT);\n\n\tgEngine.Cmd_AddCommand(\"rt_debug_lights_dump\", debugDumpLights, \"Dump all light sources for next frame\");\n\n\tconst int buffer_size = sizeof(struct LightsMetadata) + sizeof(struct LightCluster) * MAX_LIGHT_CLUSTERS;\n\n\tif (!VK_BufferCreate(\"rt lights buffer\", &g_lights_.buffer, buffer_size,\n\t\tVK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,\n\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {\n\t\t// FIXME complain, handle\n\t\treturn false;\n\t}\n\n\tg_lights_.producer = (Producer) {\n\t\t.name = \"lights\",\n\t\t.frame_sequence_tag = 0,\n\t\t.produce = lightsProduce,\n\t};\n\n\tR_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = \"lights\",\n\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t.buffer = &g_lights_.buffer,\n\t\t.offset = 0,\n\t\t.size = sizeof(struct LightsMetadata),\n\t\t.producer = &g_lights_.producer,\n\t});\n\n\tR_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = \"light_grid\",\n\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t.buffer = &g_lights_.buffer,\n\t\t.offset = sizeof(struct LightsMetadata),\n\t\t.size = sizeof(struct LightCluster) * MAX_LIGHT_CLUSTERS,\n\t\t.producer = &g_lights_.producer,\n\t});\n\n\tR_SPEEDS_COUNTER(g_lights_.stats.dirty_cells, \"dirty_cells\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.stats.dirty_cells_size, \"dirty_cells_size\", kSpeedsMetricBytes);\n\tR_SPEEDS_COUNTER(g_lights_.stats.ranges_uploaded, \"ranges_uploaded\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.num_polygons, \"polygons\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.num_point_lights, \"points\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.stats.dynamic_polygons, \"polygons_dynamic\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.stats.dynamic_points, \"points_dynamic\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.stats.dlights, \"dlights\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_lights_.stats.elights, \"elights\", kSpeedsMetricCount);\n\n\treturn true;\n}\n\nvoid VK_LightsShutdown( void ) {\n\tVK_BufferDestroy(&g_lights_.buffer);\n\n\tgEngine.Cmd_RemoveCommand(\"vk_lights_dump\");\n\tbitArrayDestroy(&g_lights_.visited_cells);\n}\n\ntypedef struct {\n\tint num;\n\tint leafs[];\n} vk_light_leaf_set_t;\n\ntypedef struct {\n\tvk_light_leaf_set_t *potentially_visible_leafs;\n} vk_surface_metadata_t;\n\nstatic struct {\n\t// Worldmodel surfaces\n\tint num_surfaces;\n\tvk_surface_metadata_t *surfaces;\n\n\t// Used for accumulating potentially visible leafs\n\tstruct {\n\t\tint count;\n\n\t\t// This buffer space is used for two things:\n\t\t// As a growing array of u16 leaf indexes (low 16 bits)\n\t\t// As a bit field for marking added leafs (highest {31st} bit)\n\t\tuint32_t leafs[MAX_MAP_LEAFS];\n\n\t\tbyte visbytes[(MAX_MAP_LEAFS+7)/8];\n\t} accum;\n\n} g_lights_bsp = {0};\n\nstatic qboolean loadRadData( const model_t *map, const char *fmt, ... ) {\n\tfs_offset_t size;\n\tchar *data;\n\tbyte *buffer;\n\tchar filename[1024];\n\n\tva_list argptr;\n\tva_start( argptr, fmt );\n\tvsnprintf( filename, sizeof filename, fmt, argptr );\n\tva_end( argptr );\n\n\tbuffer = gEngine.fsapi->LoadFile( filename, &size, false);\n\n\tif (!buffer) {\n\t\tDEBUG(\"Couldn't load RAD data from file %s\", filename);\n\t\treturn false;\n\t}\n\n\tDEBUG(\"Loading RAD data from file %s\", filename);\n\n\tdata = (char*)buffer;\n\tfor (;;) {\n\t\tstring name;\n\t\tfloat r=0, g=0, b=0, scale=0;\n\t\tint num;\n\t\tchar* line_end;\n\n\t\twhile (*data != '\\0' && isspace(*data)) ++data;\n\t\tif (*data == '\\0')\n\t\t\tbreak;\n\n\t\tline_end = Q_strchr(data, '\\n');\n\t\tif (line_end) *line_end = '\\0';\n\n\t\tname[0] = '\\0';\n\t\tnum = sscanf(data, \"%s %f %f %f %f\", name, &r, &g, &b, &scale);\n\t\t//DEBUG(\"raw rad entry (%d): %s %f %f %f %f\", num, name, r, g, b, scale);\n\t\tif (Q_strstr(name, \"//\") != NULL) {\n\t\t\tnum = 0;\n\t\t}\n\n\t\tif (num == 2) {\n\t\t\tr = g = b;\n\t\t} else if (num == 5) {\n\t\t\tscale /= 255.f;\n\t\t\tr *= scale;\n\t\t\tg *= scale;\n\t\t\tb *= scale;\n\t\t} else if (num == 4) {\n\t\t\t// Ok, rgb only, no scaling\n\t\t} else {\n\t\t\tDEBUG( \"skipping rad entry %s\", name[0] ? name : \"(empty)\" );\n\t\t\tnum = 0;\n\t\t}\n\n\t\tif (num != 0) {\n\t\t\tDEBUG(\"rad entry (%d): %s %f %f %f (%f)\", num, name, r, g, b, scale);\n\n\t\t\t{\n\t\t\t\tconst char *wad_name = NULL;\n\t\t\t\tchar *texture_name = Q_strchr(name, '/');\n\t\t\t\tint tex_id;\n\t\t\t\tconst qboolean enabled = (r != 0 || g != 0 || b != 0);\n\n\t\t\t\tif (!texture_name) {\n\t\t\t\t\ttexture_name = name;\n\t\t\t\t} else {\n\t\t\t\t\t// name is now just a wad name\n\t\t\t\t\ttexture_name[0] = '\\0';\n\t\t\t\t\twad_name = name;\n\n\t\t\t\t\ttexture_name += 1;\n\t\t\t\t}\n\n\t\t\t\t// FIXME replace this with findTexturesNamedLike from vk_materials.c\n\t\t\t\t// It has slightly different logic, though, and is a bit scary to change\n\n\t\t\t\t// Try bsp texture first\n\t\t\t\ttex_id = R_TextureFindByNameF(\"#%s:%s.mip\", map->name, texture_name);\n\n\t\t\t\t// Try wad texture if bsp is not there\n\t\t\t\tif (!tex_id && wad_name) {\n\t\t\t\t\ttex_id = R_TextureFindByNameF(\"%s.wad/%s.mip\", wad_name, texture_name);\n\t\t\t\t}\n\n\t\t\t\tif (!tex_id) {\n\t\t\t\t\tconst char *wad = g_map_entities.wadlist;\n\t\t\t\t\tfor (; *wad;) {\n\t\t\t\t\t\tconst char *const wad_end = Q_strchr(wad, ';');\n\t\t\t\t\t\ttex_id = R_TextureFindByNameF(\"%.*s/%s.mip\", wad_end - wad, wad, texture_name);\n\t\t\t\t\t\tif (tex_id)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\twad = wad_end + 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (tex_id) {\n\t\t\t\t\tvk_emissive_texture_t *const etex = g_lights_.map.emissive_textures + tex_id;\n\t\t\t\t\tASSERT(tex_id < MAX_TEXTURES);\n\n\t\t\t\t\tetex->emissive[0] = r;\n\t\t\t\t\tetex->emissive[1] = g;\n\t\t\t\t\tetex->emissive[2] = b;\n\t\t\t\t\tetex->set = enabled;\n\n\t\t\t\t\t// See DIRECT_SCALE in qrad/lightmap.c\n\t\t\t\t\tVectorScale(etex->emissive, 0.1f, etex->emissive);\n\n\t\t\t\t\tDEBUG(\"  texture(%s?, %d) set emissive(%f, %f, %f)\", texture_name, tex_id, etex->emissive[0], etex->emissive[1], etex->emissive[2]);\n\n\t\t\t\t\tif (!enabled)\n\t\t\t\t\t\tDEBUG(\"rad entry %s disabled due to zero intensity\", name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!line_end)\n\t\t\tbreak;\n\n\t\tdata = line_end + 1;\n\t}\n\n\tMem_Free(buffer);\n\treturn true;\n}\n\nstatic void leafAccumPrepare( void ) {\n\tmemset(&g_lights_bsp.accum, 0, sizeof(g_lights_bsp.accum));\n}\n\n#define LEAF_ADDED_BIT 0x8000000ul\n\nstatic qboolean leafAccumAdd( uint16_t leaf_index ) {\n\t// Check whether this leaf was already added\n\tif (g_lights_bsp.accum.leafs[leaf_index] & LEAF_ADDED_BIT)\n\t\treturn false;\n\n\tg_lights_bsp.accum.leafs[leaf_index] |= LEAF_ADDED_BIT;\n\n\tg_lights_bsp.accum.leafs[g_lights_bsp.accum.count++] |= leaf_index;\n\treturn true;\n}\n\nstatic void leafAccumFinalize( void ) {\n\tfor (int i = 0; i < g_lights_bsp.accum.count; ++i)\n\t\tg_lights_bsp.accum.leafs[i] &= 0xffffu;\n}\n\nstatic int leafAccumAddPotentiallyVisibleFromLeaf(const model_t *const map, const mleaf_t *leaf, qboolean print_debug) {\n\tint pvs_leaf_index = 0;\n\tint leafs_added = 0;\n\tASSERT(leaf->compressed_vis);\n\tconst byte *pvs = leaf->compressed_vis;\n\tfor (;pvs_leaf_index < map->numleafs; ++pvs) {\n\t\tuint8_t bits = pvs[0];\n\n\t\t// PVS is RLE encoded\n\t\tif (bits == 0) {\n\t\t\tconst int skip = pvs[1];\n\t\t\tpvs_leaf_index += skip * 8;\n\t\t\t++pvs;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (int k = 0; k < 8; ++k, ++pvs_leaf_index, bits >>= 1) {\n\t\t\tif ((bits&1) == 0)\n\t\t\t\tcontinue;\n\n\t\t\tif (leafAccumAdd( pvs_leaf_index + 1 )) {\n\t\t\t\tleafs_added++;\n\t\t\t\tif (print_debug)\n\t\t\t\t\tDEBUG(\" .%d\", pvs_leaf_index + 1);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn leafs_added;\n}\n\nstatic vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) {\n\tconst model_t *const map = WORLDMODEL;\n\tconst int surf_index = surf - map->surfaces;\n\tvk_surface_metadata_t * const smeta = g_lights_bsp.surfaces + surf_index;\n\tconst qboolean verbose_debug = false;\n\n\tif (surf_index < 0 || surf_index >= g_lights_bsp.num_surfaces) {\n\t\tERR(\"FIXME not implemented: attempting to add non-static polygon light\");\n\t\treturn NULL;\n\t}\n\n\tASSERT(surf_index >= 0);\n\tASSERT(surf_index < g_lights_bsp.num_surfaces);\n\n\t// Check if PVL hasn't been collected yet\n\tif (!smeta->potentially_visible_leafs) {\n\t\tint leafs_direct = 0, leafs_pvs = 0;\n\t\tleafAccumPrepare();\n\n\t\t// Enumerate all the map leafs and pick ones that have this surface referenced\n\t\tif (verbose_debug)\n\t\t\tDEBUG(\"Collecting visible leafs for surface %d:\", surf_index);\n\t\tfor (int i = 1; i <= map->numleafs; ++i) {\n\t\t\tconst mleaf_t *leaf = map->leafs + i;\n\t\t\t//if (verbose_debug) DEBUG(\"    leaf %d(c%d)/%d:\", i, leaf->cluster, map->numleafs);\n\t\t\tfor (int j = 0; j < leaf->nummarksurfaces; ++j) {\n\t\t\t\tconst msurface_t *leaf_surf = leaf->firstmarksurface[j];\n\t\t\t\tif (leaf_surf != surf) {\n\t\t\t\t\t/* if (verbose_debug) { */\n\t\t\t\t\t/* \tconst int leaf_surf_index = leaf_surf - map->surfaces; */\n\t\t\t\t\t/* \tDEBUG(\" !%d\", leaf_surf_index); */\n\t\t\t\t\t/* } */\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// FIXME split direct leafs marking from pvs propagation\n\t\t\t\tleafs_direct++;\n\t\t\t\tif (leafAccumAdd( i )) {\n\t\t\t\t\tif (verbose_debug) DEBUG(\" %d\", i);\n\t\t\t\t} else {\n\t\t\t\t\t// This leaf was already added earlier by PVS\n\t\t\t\t\t// but it really should be counted as direct\n\t\t\t\t\t--leafs_pvs;\n\t\t\t\t}\n\n\t\t\t\t// Get all PVS leafs\n\t\t\t\tleafs_pvs += leafAccumAddPotentiallyVisibleFromLeaf(map, leaf, verbose_debug);\n\t\t\t}\n\n\t\t\t//if (verbose_debug) DEBUG(\"\\n\");\n\t\t}\n\t\tif (verbose_debug)\n\t\t\tDEBUG(\" (sum=%d, direct=%d, pvs=%d)\", g_lights_bsp.accum.count, leafs_direct, leafs_pvs);\n\n\t\tleafAccumFinalize();\n\n\t\tsmeta->potentially_visible_leafs = (vk_light_leaf_set_t*)Mem_Malloc(vk_core.pool, sizeof(smeta->potentially_visible_leafs[0]) + sizeof(int) * g_lights_bsp.accum.count);\n\t\tsmeta->potentially_visible_leafs->num = g_lights_bsp.accum.count;\n\n\t\tfor (int i = 0; i < g_lights_bsp.accum.count; ++i) {\n\t\t\tsmeta->potentially_visible_leafs->leafs[i] = g_lights_bsp.accum.leafs[i];\n\t\t}\n\t}\n\n\treturn smeta->potentially_visible_leafs;\n}\n\nint RT_LightCellIndex( const int light_cell[3] ) {\n\tif (light_cell[0] < 0 || light_cell[1] < 0 || light_cell[2] < 0\n\t\t|| (light_cell[0] >= g_lights_.grid.size[0])\n\t\t|| (light_cell[1] >= g_lights_.grid.size[1])\n\t\t|| (light_cell[2] >= g_lights_.grid.size[2]))\n\t\treturn -1;\n\n\treturn light_cell[0] + light_cell[1] * g_lights_.grid.size[0] + light_cell[2] * g_lights_.grid.size[0] * g_lights_.grid.size[1];\n}\n\nstatic vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, const matrix3x4 *transform_row ) {\n\tconst model_t *const map = WORLDMODEL;\n\tconst mextrasurf_t *const extra = surf->info;\n\n\t// This is a very conservative way to construct a bounding sphere. It's not great.\n\tconst vec3_t bbox_center = {\n\t\t(extra->mins[0] + extra->maxs[0]) / 2.f,\n\t\t(extra->mins[1] + extra->maxs[1]) / 2.f,\n\t\t(extra->mins[2] + extra->maxs[2]) / 2.f,\n\t};\n\n\tconst vec3_t bbox_size = {\n\t\textra->maxs[0] - extra->mins[0],\n\t\textra->maxs[1] - extra->mins[1],\n\t\textra->maxs[2] - extra->mins[2],\n\t};\n\n\tint leafs_direct = 0, leafs_pvs = 0;\n\n\tconst float radius = .5f * VectorLength(bbox_size);\n\tvec3_t origin;\n\n\tMatrix3x4_VectorTransform(*transform_row, bbox_center, origin);\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"\\torigin = %f, %f, %f, R = %f\",\n\t\t\torigin[0], origin[1], origin[2], radius\n\t\t);\n\t}\n\n\tleafAccumPrepare();\n\n\t// TODO it's possible to somehow more efficiently traverse the bsp and collect only the affected leafs\n\t// (origin + radius will accidentally touch leafs that are really should not be affected)\n\tgEngine.R_FatPVS(origin, radius, g_lights_bsp.accum.visbytes, /*merge*/ false, /*fullvis*/ false);\n\tif (debug_dump_lights.enabled)\n\t\tDEBUG(\"Collecting visible leafs for moving surface %p: %f,%f,%f %f: \", surf,\n\t\t\torigin[0], origin[1], origin[2], radius);\n\n\tfor (int i = 0; i <= map->numleafs; ++i) {\n\t\tif( !CHECKVISBIT( g_lights_bsp.accum.visbytes, i ))\n\t\t\tcontinue;\n\n\t\tleafs_direct++;\n\n\t\tif (leafAccumAdd( i + 1 )) {\n\t\t\tif (debug_dump_lights.enabled)\n\t\t\t\tDEBUG(\" %d\", i + 1);\n\t\t} else {\n\t\t\t// This leaf was already added earlier by PVS\n\t\t\t// but it really should be counted as direct\n\t\t\tleafs_pvs--;\n\t\t}\n\t}\n\n\tif (debug_dump_lights.enabled)\n\t\tDEBUG(\" (sum=%d, direct=%d, pvs=%d)\", g_lights_bsp.accum.count, leafs_direct, leafs_pvs);\n\n\tleafAccumFinalize();\n\n\t// ...... oh no\n\treturn (vk_light_leaf_set_t*)&g_lights_bsp.accum.count;\n}\n\nstatic void prepareSurfacesLeafVisibilityCache( const struct model_s *map ) {\n\tif (g_lights_bsp.surfaces != NULL) {\n\t\tfor (int i = 0; i < g_lights_bsp.num_surfaces; ++i) {\n\t\t\tvk_surface_metadata_t *smeta = g_lights_bsp.surfaces + i;\n\t\t\tif (smeta->potentially_visible_leafs)\n\t\t\t\tMem_Free(smeta->potentially_visible_leafs);\n\t\t}\n\t\tMem_Free(g_lights_bsp.surfaces);\n\t}\n\n\tg_lights_bsp.num_surfaces = map->numsurfaces;\n\tg_lights_bsp.surfaces = Mem_Malloc(vk_core.pool, g_lights_bsp.num_surfaces * sizeof(vk_surface_metadata_t));\n\tfor (int i = 0; i < g_lights_bsp.num_surfaces; ++i)\n\t\tg_lights_bsp.surfaces[i].potentially_visible_leafs = NULL;\n}\n\nvoid RT_LightsNewMap( const struct model_s *map ) {\n\t// 1. Determine map bounding box (and optimal grid size?)\n\t\t// map->mins, maxs\n\tvec3_t map_size, min_cell, max_cell;\n\tVectorSubtract(map->maxs, map->mins, map_size);\n\n\tVectorDivide(map->mins, LIGHT_GRID_CELL_SIZE, min_cell);\n\tmin_cell[0] = floorf(min_cell[0]);\n\tmin_cell[1] = floorf(min_cell[1]);\n\tmin_cell[2] = floorf(min_cell[2]);\n\tVectorCopy(min_cell, g_lights_.grid.min_cell);\n\n\tVectorDivide(map->maxs, LIGHT_GRID_CELL_SIZE, max_cell);\n\tmax_cell[0] = ceilf(max_cell[0]);\n\tmax_cell[1] = ceilf(max_cell[1]);\n\tmax_cell[2] = ceilf(max_cell[2]);\n\n\tVectorSubtract(max_cell, min_cell, g_lights_.grid.size);\n\tg_lights_.grid.cells = g_lights_.grid.size[0] * g_lights_.grid.size[1] * g_lights_.grid.size[2];\n\n\tASSERT(g_lights_.grid.cells < MAX_LIGHT_CLUSTERS);\n\n\tDEBUG(\"Map mins:(%f, %f, %f), maxs:(%f, %f, %f), size:(%f, %f, %f), min_cell:(%f, %f, %f) cells:(%d, %d, %d); total: %d\",\n\t\tmap->mins[0], map->mins[1], map->mins[2],\n\t\tmap->maxs[0], map->maxs[1], map->maxs[2],\n\t\tmap_size[0], map_size[1], map_size[2],\n\t\tmin_cell[0], min_cell[1], min_cell[2],\n\t\tg_lights_.grid.size[0],\n\t\tg_lights_.grid.size[1],\n\t\tg_lights_.grid.size[2],\n\t\tg_lights_.grid.cells\n\t);\n\n\tbitArrayDestroy(&g_lights_.visited_cells);\n\tg_lights_.visited_cells = bitArrayCreate(g_lights_.grid.cells);\n\n\tprepareSurfacesLeafVisibilityCache( map );\n}\n\nstatic qboolean addSurfaceLightToCell( int cell_index, int polygon_light_index ) {\n\tvk_lights_cell_t *const cluster = g_lights_.cells + cell_index;\n\n\tif (cluster->num_polygons == MAX_VISIBLE_SURFACE_LIGHTS) {\n\t\treturn false;\n\t}\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"    adding polygon light %d to cell %d (count=%d)\", polygon_light_index, cell_index, cluster->num_polygons+1);\n\t}\n\n\tcluster->polygons[cluster->num_polygons++] = polygon_light_index;\n\tif (cluster->frame_sequence != g_lights_.frame_sequence) {\n\t\t++g_lights_.stats.dirty_cells;\n\t\tcluster->frame_sequence = g_lights_.frame_sequence;\n\t}\n\treturn true;\n}\n\nstatic qboolean addLightToCell( int cell_index, int light_index ) {\n\tvk_lights_cell_t *const cluster = g_lights_.cells + cell_index;\n\n\tif (cluster->num_point_lights == MAX_VISIBLE_POINT_LIGHTS)\n\t\treturn false;\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"    adding point light %d to cell %d (count=%d)\", light_index, cell_index, cluster->num_point_lights+1);\n\t}\n\n\tcluster->point_lights[cluster->num_point_lights++] = light_index;\n\n\tif (cluster->frame_sequence != g_lights_.frame_sequence) {\n\t\t++g_lights_.stats.dirty_cells;\n\t\tcluster->frame_sequence = g_lights_.frame_sequence;\n\t}\n\treturn true;\n}\n\n/*\nstatic qboolean canSurfaceLightAffectAABB(const model_t *mod, const msurface_t *surf, const vec3_t emissive, const float minmax[6]) {\n\t//APROF_SCOPE_BEGIN_EARLY(canSurfaceLightAffectAABB); // DO NOT DO THIS. We have like 600k of these calls per frame :feelsbadman:\n\tqboolean retval = true;\n\t// FIXME transform surface\n\t// this here only works for static map model\n\n\t// Use bbox center for normal culling estimation\n\tconst vec3_t bbox_center = {\n\t\t(minmax[0] + minmax[3]) / 2.f,\n\t\t(minmax[1] + minmax[4]) / 2.f,\n\t\t(minmax[2] + minmax[5]) / 2.f,\n\t};\n\n\tfloat bbox_plane_dist = PlaneDiff(bbox_center, surf->plane);\n\tif( FBitSet( surf->flags, SURF_PLANEBACK ))\n\t\tbbox_plane_dist = -bbox_plane_dist;\n\n\tif (bbox_plane_dist < 0.f) {\n\t\t// Fast conservative estimate by max distance from bbox center\n\t\t// TODO is enumerating all points or finding a closest one is better/faster?\n\t\tconst float size_x = minmax[0] - minmax[3];\n\t\tconst float size_y = minmax[1] - minmax[4];\n\t\tconst float size_z = minmax[2] - minmax[5];\n\t\tconst float plane_dist_guard_sqr = (size_x * size_x + size_y * size_y + size_z * size_z) * .25f;\n\n\t\t// Check whether this bbox is completely behind the surface\n\t\tif (bbox_plane_dist*bbox_plane_dist > plane_dist_guard_sqr)\n\t\t\tretval = false;\n\t}\n\n\t//APROF_SCOPE_END(canSurfaceLightAffectAABB);\n\n\treturn retval;\n}\n*/\n\nstatic void addLightIndexToLeaf( const mleaf_t *leaf, int index ) {\n\tconst int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE);\n\tconst int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE);\n\tconst int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE);\n\n\tconst int max_x = ceilf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE);\n\tconst int max_y = ceilf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE);\n\tconst int max_z = ceilf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE);\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"  adding leaf %d min=(%d, %d, %d), max=(%d, %d, %d) total=%d\",\n\t\t\tleaf->cluster,\n\t\t\tmin_x, min_y, min_z,\n\t\t\tmax_x, max_y, max_z,\n\t\t\t(max_x - min_x) * (max_y - min_y) * (max_z - min_z)\n\t\t);\n\t}\n\n\tfor (int x = min_x; x < max_x; ++x)\n\tfor (int y = min_y; y < max_y; ++y)\n\tfor (int z = min_z; z < max_z; ++z) {\n\t\tconst int cell[3] = {\n\t\t\tx - g_lights_.grid.min_cell[0],\n\t\t\ty - g_lights_.grid.min_cell[1],\n\t\t\tz - g_lights_.grid.min_cell[2]\n\t\t};\n\n\t\tconst int cell_index = RT_LightCellIndex( cell );\n\t\tif (cell_index < 0)\n\t\t\tcontinue;\n\n\t\tif (bitArrayCheckOrSet(&g_lights_.visited_cells, cell_index)) {\n\t\t\tif (!addLightToCell(cell_index, index)) {\n\t\t\t\tERROR_THROTTLED(10, \"Cluster %d,%d,%d(%d) ran out of light slots\",\n\t\t\t\t\tcell[0], cell[1],  cell[2], cell_index);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void addPointLightToAllClusters( int index ) {\n\tconst model_t* const world = WORLDMODEL;\n\n\t// FIXME there's certainly a better way to do this: just enumerate\n\t// all clusters, not all leafs\n\n\tbitArrayClear(&g_lights_.visited_cells);\n\tfor (int i = 1; i <= world->numleafs; ++i) {\n\t\tconst mleaf_t *const leaf = world->leafs + i;\n\t\taddLightIndexToLeaf( leaf, index );\n\t}\n}\n\nstatic void addPointLightToClusters( int index ) {\n\tconst model_t* const world = WORLDMODEL;\n\n\tif (!world->visdata) {\n\t\taddPointLightToAllClusters( index );\n\t\treturn;\n\t}\n\n\tvk_point_light_t *const light = g_lights_.point_lights + index;\n\tconst mleaf_t* leaf = gEngine.Mod_PointInLeaf(light->origin, world->nodes);\n\tconst vk_light_leaf_set_t *const leafs = (vk_light_leaf_set_t*)&g_lights_bsp.accum.count;\n\n\tleafAccumPrepare();\n\tleafAccumAddPotentiallyVisibleFromLeaf( world, leaf, false);\n\tleafAccumFinalize();\n\n\tbitArrayClear(&g_lights_.visited_cells);\n\tfor (int i = 0; i < leafs->num; ++i) {\n\t\tconst mleaf_t *const leaf = world->leafs + leafs->leafs[i];\n\t\taddLightIndexToLeaf( leaf, index );\n\t}\n}\n\nstatic int addPointLight( const vec3_t origin, const vec3_t color, float radius, int lightstyle, float hack_attenuation ) {\n\tconst int index = g_lights_.num_point_lights;\n\tvk_point_light_t *const plight = g_lights_.point_lights + index;\n\n\tif (g_lights_.num_point_lights >= MAX_POINT_LIGHTS) {\n\t\tERROR_THROTTLED(10, \"Too many lights, MAX_POINT_LIGHTS=%d\", MAX_POINT_LIGHTS);\n\t\treturn -1;\n\t}\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"point light %d: origin=(%f %f %f) R=%f color=(%f %f %f)\", index,\n\t\t\torigin[0], origin[1], origin[2], radius,\n\t\t\tcolor[0], color[1], color[2]);\n\t}\n\n\t*plight = (vk_point_light_t){0};\n\tVectorCopy(origin, plight->origin);\n\tplight->radius = radius;\n\n\tVectorScale(color, hack_attenuation, plight->base_color);\n\tVectorCopy(plight->base_color, plight->color);\n\tplight->lightstyle = lightstyle;\n\n\t// Omnidirectional light\n\tplight->stopdot = plight->stopdot2_or_costheta = -1.f;\n\tVectorSet(plight->dir, 0, 0, 0);\n\n\taddPointLightToClusters( index );\n\tg_lights_.num_point_lights++;\n\treturn index;\n}\n\nstatic int addSpotLight( const vk_light_entity_t *le, float radius, float solid_angle, int lightstyle, float hack_attenuation, qboolean all_clusters ) {\n\tconst int index = g_lights_.num_point_lights;\n\tvk_point_light_t *const plight = g_lights_.point_lights + index;\n\n\tif (g_lights_.num_point_lights >= MAX_POINT_LIGHTS) {\n\t\tERROR_THROTTLED(10, \"Too many lights, MAX_POINT_LIGHTS=%d\", MAX_POINT_LIGHTS);\n\t\treturn -1;\n\t}\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"%s light %d: origin=(%f %f %f) color=(%f %f %f) dir=(%f %f %f)\",\n\t\t\tle->type == LightTypeEnvironment ? \"environment\" : \"spot\",\n\t\t\tindex,\n\t\t\tle->origin[0], le->origin[1], le->origin[2],\n\t\t\tle->color[0], le->color[1], le->color[2],\n\t\t\tle->dir[0], le->dir[1], le->dir[2]);\n\t}\n\n\t*plight = (vk_point_light_t){0};\n\tVectorCopy(le->origin, plight->origin);\n\tplight->radius = radius;\n\n\tVectorCopy(plight->base_color, plight->color);\n\tplight->lightstyle = lightstyle;\n\n\tVectorCopy(le->dir, plight->dir);\n\tplight->stopdot = le->stopdot;\n\n\tif (le->type == LightTypeEnvironment) {\n\t\t// Baseline values\n\t\tconst float kSunSolidAngle = 6.794e-5; // Wikipedia\n\t\tconst float kSunCosTheta = 1. - kSunSolidAngle / (2 * M_PI);\n\n\t\tconst float cos_theta_max = Q_min(kSunCosTheta, 1. - solid_angle / (2 * M_PI));\n\n\t\t// Make sure that the brightness is preserved\n\t\t// light.glsl will multiply color by one_over_pdf for future MIS reasons\n\t\thack_attenuation /= (1. - cos_theta_max) / (1. - kSunCosTheta);\n\n\t\tplight->flags = LightFlag_Environment;\n\t\tplight->stopdot2_or_costheta = cos_theta_max;\n\t} else {\n\t\tplight->stopdot2_or_costheta = le->stopdot2;\n\t}\n\n\tVectorScale(le->color, hack_attenuation, plight->base_color);\n\n\tif (all_clusters)\n\t\taddPointLightToAllClusters( index );\n\telse\n\t\taddPointLightToClusters( index );\n\n\tg_lights_.num_point_lights++;\n\treturn index;\n}\n\nvoid RT_LightAddFlashlight(const struct cl_entity_s *ent, qboolean local_player ) {\n\t// parameters\n\tconst float hack_attenuation = 0.1f / 25.f;\n\tfloat radius = 1.0;\n\t// TODO: better tune it\n\tconst float _cone = 10.0;\n\tconst float _cone2 = 30.0;\n\tconst vec3_t light_color = {255, 255, 192};\n\tfloat light_intensity = 300;\n\n\tvec3_t color;\n\tvec3_t origin;\n\tvec3_t angles;\n\tvk_light_entity_t le = { .type = LightTypeSpot };\n\n\tfloat thirdperson_offset = 25;\n\tvec3_t forward, view_ofs;\n\tvec3_t vecSrc, vecEnd;\n\tpmtrace_t *trace;\n\tif( local_player )\n\t{\n\t\t// local player case\n\t\t// position\n\t\tif (gEngine.EngineGetParm(PARM_THIRDPERSON, 0)) { // thirdperson\n\t\t\tAngleVectors( g_camera.viewangles, forward, NULL, NULL );\n\t\t\tview_ofs[0] = view_ofs[1] = 0.0f;\n\t\t\tif( ent->curstate.usehull == 1 ) {\n\t\t\t\tview_ofs[2] = 12.0f; // VEC_DUCK_VIEW;\n\t\t\t} else {\n\t\t\t\tview_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT\n\t\t\t}\n\t\t\tVectorAdd( ent->origin, view_ofs, vecSrc );\n\t\t\tVectorMA( vecSrc, thirdperson_offset, forward, vecEnd );\n\t\t\ttrace = gEngine.EV_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );\n\t\t\tVectorCopy( trace->endpos, origin );\n\t\t\tVectorCopy( forward, le.dir);\n\t\t} else { // firstperson\n\t\t\t// based on https://github.com/SNMetamorph/PrimeXT/blob/0869b1abbddd13c1229769d8cd71941610be0bf3/client/flashlight.cpp#L35\n\t\t\torigin[0] = g_camera.vieworg[0] + (g_camera.vright[0] * (-4.0f)) + (g_camera.vforward[0] * 14.0); // forward-back\n\t\t\torigin[1] = g_camera.vieworg[1] + (g_camera.vright[1] * (-4.0f)) + (g_camera.vforward[1] * 14.0); // left-right\n\t\t\torigin[2] = g_camera.vieworg[2] + (g_camera.vright[2] * (-4.0f)) + (g_camera.vforward[2] * 14.0); // up-down\n\t\t\torigin[2] += 2.0f;\n\t\t\tVectorCopy(g_camera.vforward, le.dir);\n\t\t}\n\t}\n\telse // non-local player case\n\t{\n\t\tthirdperson_offset = 10;\n\t\tradius = 10;\n\t\tlight_intensity = 60;\n\n\t\tVectorCopy( ent->angles, angles );\n\t\t// NOTE: pitch divided by 3.0 twice. So we need apply 3^2 = 9\n\t\tangles[PITCH] = ent->curstate.angles[PITCH] * 9.0f;\n\t\tangles[YAW] = ent->angles[YAW];\n\t\tangles[ROLL] = 0.0f; // roll not used\n\n\t\tAngleVectors( angles, angles, NULL, NULL );\n\t\tview_ofs[0] = view_ofs[1] = 0.0f;\n\t\tif( ent->curstate.usehull == 1 ) {\n\t\t\tview_ofs[2] = 12.0f; // VEC_DUCK_VIEW;\n\t\t} else {\n\t\t\tview_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT\n\t\t}\n\t\tVectorAdd( ent->origin, view_ofs, vecSrc );\n\t\tVectorMA( vecSrc, thirdperson_offset, angles, vecEnd );\n\t\ttrace = gEngine.EV_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );\n\t\tVectorCopy( trace->endpos, origin );\n\t\tVectorCopy( angles, le.dir );\n\t}\n\n\tVectorCopy(origin, le.origin);\n\n\t// prepare colors by parseEntPropRgbav\n\tVectorScale(light_color, light_intensity / 255.0f, color);\n\n\t// convert colors by weirdGoldsrcLightScaling\n\tfloat l1 = Q_max(color[0], Q_max(color[1], color[2]));\n\tl1 = l1 * l1 / 10;\n\tVectorScale(color, l1, le.color);\n\n\t// convert stopdots by parseStopDot\n\tle.stopdot = cosf(_cone * M_PI / 180.f);\n\tle.stopdot2 = cosf(_cone2 * M_PI / 180.f);\n\n\t/*\n\tDEBUG(\"flashlight: origin=(%f %f %f) color=(%f %f %f) dir=(%f %f %f)\",\n\t\tle.origin[0], le.origin[1], le.origin[2],\n\t\tle.color[0], le.color[1], le.color[2],\n\t\tle.dir[0], le.dir[1], le.dir[2]);\n\t*/\n\n\tconst float solid_angle_unused = 0.;\n\taddSpotLight(&le, radius, 0, solid_angle_unused, hack_attenuation, false);\n}\n\nstatic float sphereSolidAngleFromDistDiv2Pi(float r, float d) {\n\treturn 1. - sqrt(d*d - r*r)/d;\n}\n\nstatic qboolean addDlight( const dlight_t *dlight ) {\n\tconst float k_light_radius = 2.f;\n\tconst float k_threshold = 2.f;\n\tfloat max_comp;\n\tvec3_t color;\n\tfloat scaler;\n\n\tmax_comp = Q_max(dlight->color.r, Q_max(dlight->color.g, dlight->color.b));\n\tif (max_comp < k_threshold || dlight->radius <= k_light_radius)\n\t\treturn false;\n\n\tscaler = k_threshold / (max_comp * sphereSolidAngleFromDistDiv2Pi(k_light_radius, dlight->radius));\n\n\t// These constants are empirical. There's no known math reason behind them\n\tscaler /= 25.;\n\n\tVectorSet(\n\t\tcolor,\n\t\tdlight->color.r * scaler,\n\t\tdlight->color.g * scaler,\n\t\tdlight->color.b * scaler);\n\n\taddPointLight(dlight->origin, color, k_light_radius, -1, 1.f);\n\treturn true;\n}\n\nstatic void processStaticPointLights( void ) {\n\tAPROF_SCOPE_BEGIN_EARLY(static_lights);\n\tconst model_t* const world = WORLDMODEL;\n\tASSERT(world);\n\n\tg_lights_.num_point_lights = 0;\n\tfor (int i = 0; i < g_map_entities.num_lights; ++i) {\n\t\tconst vk_light_entity_t *le = g_map_entities.lights + i;\n\t\tconst float default_radius = 2.f; // TODO tune\n\t\tconst float radius = le->radius > 0.f ? le->radius : default_radius;\n\n\t\t// Expects skybox to be loaded already.\n\t\tconst float solid_angle = le->solid_angle > 0.f ? le->solid_angle : R_TexturesGetSkyboxInfo().sun_solid_angle;\n\n\t\t// These constants are empirical. There's no known math reason behind them\n\t\tconst float hack_attenuation = (le->type == LightTypeEnvironment)\n\t\t\t? 700.f // FIXME why?\n\t\t\t: .1f / 25.f; // FIXME why?\n\n\t\tint index;\n\t\tswitch (le->type) {\n\t\t\tcase LightTypePoint:\n\t\t\t\tindex = addPointLight(le->origin, le->color, radius, le->style, hack_attenuation);\n\t\t\t\tbreak;\n\n\t\t\tcase LightTypeEnvironment:\n\t\t\tcase LightTypeSpot:\n\t\t\t\tindex = addSpotLight(le, radius, solid_angle, le->style, hack_attenuation, i == g_map_entities.single_environment_index);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tASSERT(!\"Unexpected light type\");\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tif (index < 0)\n\t\t\tbreak;\n\t}\n\tAPROF_SCOPE_END(static_lights);\n}\n\nvoid RT_LightsLoadBegin( const struct model_s *map ) {\n\t// Load RAD data based on map name\n\t{\n\t\tint name_len = Q_strlen(map->name);\n\n\t\t// Strip \".bsp\" suffix\n\t\tif (name_len > 4 && 0 == Q_stricmp(map->name + name_len - 4, \".bsp\"))\n\t\t\tname_len -= 4;\n\n\t\tmemset(g_lights_.map.emissive_textures, 0, sizeof(g_lights_.map.emissive_textures));\n\t\tconst qboolean loaded = loadRadData( map, \"maps/lights.rad\" ) | loadRadData( map, \"%.*s.rad\", name_len, map->name );\n\t\tif (!loaded) {\n\t\t\tERR(\"No RAD files loaded. The map will be completely black\");\n\t\t}\n\t}\n\n\t// Clear static lights counts\n\t{\n\t\tg_lights_.num_polygons = g_lights_.num_static.polygons = 0;\n\t\tg_lights_.num_point_lights = g_lights_.num_static.point_lights = 0;\n\t\tg_lights_.num_polygon_vertices = g_lights_.num_static.polygon_vertices = 0;\n\n\t\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\t\tvk_lights_cell_t *const cell = g_lights_.cells + i;\n\t\t\tcell->num_point_lights = cell->num_static.point_lights = 0;\n\t\t\tcell->num_polygons = cell->num_static.polygons = 0;\n\t\t\tcell->frame_sequence = g_lights_.frame_sequence;\n\t\t}\n\t}\n\n\tprocessStaticPointLights();\n}\n\nvoid RT_LightsLoadEnd( void ) {\n\t//debug_dump_lights.enabled = true;\n\n\t// Fix static counts\n\t{\n\t\tg_lights_.num_static.polygons = g_lights_.num_polygons;\n\t\tg_lights_.num_static.point_lights = g_lights_.num_point_lights;\n\t\tg_lights_.num_static.polygon_vertices = g_lights_.num_polygon_vertices;\n\n\t\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\t\tvk_lights_cell_t *const cell = g_lights_.cells + i;\n\t\t\tcell->num_static.point_lights = cell->num_point_lights;\n\t\t\tcell->num_static.polygons = cell->num_polygons;\n\t\t}\n\t}\n\n\tg_lights_.stats.dirty_cells = g_lights_.grid.cells;\n}\n\nqboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) {\n\tASSERT(texture_id >= 0);\n\tASSERT(texture_id < MAX_TEXTURES);\n\n\tvk_emissive_texture_t *const etex = g_lights_.map.emissive_textures + texture_id;\n\tif (etex->set) {\n\t\tVectorCopy(etex->emissive, out);\n\t\treturn true;\n\t} else {\n\t\tVectorClear(out);\n\t\treturn false;\n\t}\n}\n\nstatic void addPolygonLightIndexToLeaf(const mleaf_t* leaf, int poly_index) {\n\tconst int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE);\n\tconst int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE);\n\tconst int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE);\n\n\tconst int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1;\n\tconst int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1;\n\tconst int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1;\n\n\tconst qboolean not_visible = false; //TODO static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs);\n\n\tif (debug_dump_lights.enabled) {\n\t\tDEBUG(\"  adding leaf %d min=(%d, %d, %d), max=(%d, %d, %d) total=%d\",\n\t\t\tleaf->cluster,\n\t\t\tmin_x, min_y, min_z,\n\t\t\tmax_x, max_y, max_z,\n\t\t\t(max_x - min_x) * (max_y - min_y) * (max_z - min_z)\n\t\t);\n\t}\n\n\tif (not_visible)\n\t\treturn;\n\n\tfor (int x = min_x; x < max_x; ++x)\n\tfor (int y = min_y; y < max_y; ++y)\n\tfor (int z = min_z; z < max_z; ++z) {\n\t\tconst int cell[3] = {\n\t\t\tx - g_lights_.grid.min_cell[0],\n\t\t\ty - g_lights_.grid.min_cell[1],\n\t\t\tz - g_lights_.grid.min_cell[2]\n\t\t};\n\n\t\tconst int cell_index = RT_LightCellIndex( cell );\n\t\tif (cell_index < 0)\n\t\t\tcontinue;\n\n\t\tif (bitArrayCheckOrSet(&g_lights_.visited_cells, cell_index)) {\n\t\t\t/*\n\t\t\tconst float minmaxs[6] = {\n\t\t\t\tx * LIGHT_GRID_CELL_SIZE,\n\t\t\t\ty * LIGHT_GRID_CELL_SIZE,\n\t\t\t\tz * LIGHT_GRID_CELL_SIZE,\n\t\t\t\t(x+1) * LIGHT_GRID_CELL_SIZE,\n\t\t\t\t(y+1) * LIGHT_GRID_CELL_SIZE,\n\t\t\t\t(z+1) * LIGHT_GRID_CELL_SIZE,\n\t\t\t};\n\n\t\t\tTODO if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) */\n\t\t\t/* \tcontinue; */\n\n\t\t\tif (!addSurfaceLightToCell(cell_index, poly_index)) {\n\t\t\t\tERROR_THROTTLED(10, \"Cluster %d,%d,%d(%d) ran out of polygon light slots\",\n\t\t\t\t\tcell[0], cell[1],  cell[2], cell_index);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void addPolygonLightToAllClusters( int poly_index ) {\n\tconst model_t* const world = WORLDMODEL;\n\n\t// FIXME there's certainly a better way to do this: just enumerate\n\t// all clusters, not all leafs\n\n\tbitArrayClear(&g_lights_.visited_cells);\n\tfor (int i = 1; i <= world->numleafs; ++i) {\n\t\tconst mleaf_t *const leaf = world->leafs + i;\n\t\taddPolygonLightIndexToLeaf( leaf, poly_index );\n\t}\n}\n\nstatic void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int poly_index) {\n\tconst model_t* const world = WORLDMODEL;\n\n\t// FIXME this shouldn't happen in prod\n\tif (!leafs)\n\t\treturn;\n\n\tbitArrayClear(&g_lights_.visited_cells);\n\n\t// Iterate through each visible/potentially affected leaf to get a range of grid cells\n\tfor (int i = 0; i < leafs->num; ++i) {\n\t\tconst mleaf_t *const leaf = world->leafs + leafs->leafs[i];\n\t\taddPolygonLightIndexToLeaf(leaf, poly_index);\n\t}\n}\n\nint RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) {\n\t// FIXME We're adding lights directly from vk_brush.c w/o knowing whether current frame is\n\t// ray traced. If not, this will break.\n\tif (addpoly->dynamic && !vk_frame.rtx_enabled)\n\t\treturn -1;\n\n\tif (g_lights_.num_polygons == MAX_SURFACE_LIGHTS) {\n\t\tERROR_THROTTLED(10, \"Max number of polygon lights %d reached\", MAX_SURFACE_LIGHTS);\n\t\treturn -1;\n\t}\n\n\tASSERT(addpoly->num_vertices > 2);\n\tASSERT(addpoly->num_vertices < 8);\n\tASSERT(g_lights_.num_polygon_vertices + addpoly->num_vertices <= COUNTOF(g_lights_.polygon_vertices));\n\n\t{\n\t\tAPROF_SCOPE_DECLARE_BEGIN(add_polygon, __FUNCTION__);\n\t\trt_light_polygon_t *const poly = g_lights_.polygons + g_lights_.num_polygons;\n\t\tvec3_t *vertices = g_lights_.polygon_vertices + g_lights_.num_polygon_vertices;\n\t\tvec3_t normal;\n\n\t\tpoly->vertices.offset = g_lights_.num_polygon_vertices;\n\t\tpoly->vertices.count = addpoly->num_vertices;\n\n\t\t{\n\t\t\t// These constants are empirical. There's no known math reason behind them\n\t\t\tconst float hack_attenuation_poly = 1.f / 25.f;\n\t\t\tVectorScale(addpoly->emissive, hack_attenuation_poly, poly->emissive);\n\t\t}\n\n\t\tVectorSet(poly->center, 0, 0, 0);\n\t\tVectorSet(normal, 0, 0, 0);\n\n\t\tfor (int i = 0; i < addpoly->num_vertices; ++i) {\n\t\t\tif (addpoly->transform_row)\n\t\t\t\tMatrix3x4_VectorTransform(*addpoly->transform_row, addpoly->vertices[i], vertices[i]);\n\t\t\telse\n\t\t\t\tVectorCopy(addpoly->vertices[i], vertices[i]);\n\t\t\tVectorAdd(vertices[i], poly->center, poly->center);\n\n\t\t\tif (i > 1) {\n\t\t\t\tvec3_t e[2], lnormal;\n\t\t\t\tVectorSubtract(vertices[i-0], vertices[0], e[0]);\n\t\t\t\tVectorSubtract(vertices[i-1], vertices[0], e[1]);\n\t\t\t\tCrossProduct(e[0], e[1], lnormal);\n\t\t\t\tVectorAdd(lnormal, normal, normal);\n\t\t\t}\n\t\t}\n\n\t\tpoly->area = VectorLength(normal);\n\t\tif (poly->area <= 0) {\n\t\t\tERR(\"%s: Polygon light has zero area\", __FUNCTION__);\n\t\t\treturn -1;\n\t\t}\n\n\t\tVectorM(1.f / poly->area, normal, poly->plane);\n\t\tpoly->plane[3] = -DotProduct(vertices[0], poly->plane);\n\n\t\tVectorM(1.f / poly->vertices.count, poly->center, poly->center);\n\n\t\tif (!addpoly->dynamic || debug_dump_lights.enabled) {\n\t\t\tDEBUG(\"added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\",\n\t\t\t\tg_lights_.num_polygons,\n\t\t\t\tpoly->emissive[0],\n\t\t\t\tpoly->emissive[1],\n\t\t\t\tpoly->emissive[2],\n\t\t\t\tpoly->center[0],\n\t\t\t\tpoly->center[1],\n\t\t\t\tpoly->center[2],\n\t\t\t\tpoly->plane[0],\n\t\t\t\tpoly->plane[1],\n\t\t\t\tpoly->plane[2],\n\t\t\t\tpoly->plane[3],\n\t\t\t\tpoly->area,\n\t\t\t\tpoly->vertices.count\n\t\t\t);\n\t\t}\n\n\t\tconst model_t* const world = WORLDMODEL;\n\t\tif (world->visdata) {\n\t\t\tconst vk_light_leaf_set_t *const leafs = addpoly->dynamic\n\t\t\t\t? getMapLeafsAffectedByMovingSurface( addpoly->surface, addpoly->transform_row )\n\t\t\t\t: getMapLeafsAffectedByMapSurface( addpoly->surface );\n\t\t\taddPolygonLeafSetToClusters(leafs, g_lights_.num_polygons);\n\t\t} else {\n\t\t\taddPolygonLightToAllClusters( g_lights_.num_polygons );\n\t\t}\n\n\t\tg_lights_.num_polygon_vertices += addpoly->num_vertices;\n\t\tAPROF_SCOPE_END(add_polygon);\n\t\treturn g_lights_.num_polygons++;\n\t}\n}\n\nvoid RT_LightsFrameBegin( void ) {\n\tg_lights_.num_polygons = g_lights_.num_static.polygons;\n\tg_lights_.num_point_lights = g_lights_.num_static.point_lights;\n\tg_lights_.num_polygon_vertices = g_lights_.num_static.polygon_vertices;\n\n\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\tvk_lights_cell_t *const cell = g_lights_.cells + i;\n\t\tcell->num_polygons = cell->num_static.polygons;\n\t\tcell->num_point_lights = cell->num_static.point_lights;\n\t}\n}\n\nstatic void uploadGridRange( int begin, int end ) {\n\tconst int count = end - begin;\n\tASSERT( count > 0 );\n\n\tconst int size = count * sizeof(struct LightCluster);\n\tconst vk_buffer_locked_t locked = R_VkBufferLock(&g_lights_.buffer,\n\t\t(vk_buffer_lock_t) {\n\t\t\t.offset = sizeof(struct LightsMetadata) + begin * sizeof(struct LightCluster),\n\t\t\t.size = size,\n\t} );\n\n\tASSERT(locked.ptr);\n\n\tstruct LightCluster *const grid = locked.ptr;\n\tmemset(grid, 0, size);\n\n\tfor (int i = 0; i < count; ++i) {\n\t\tconst vk_lights_cell_t *const src = g_lights_.cells + i + begin;\n\t\tstruct LightCluster *const dst = grid + i;\n\n\t\tdst->num_point_lights = src->num_point_lights;\n\t\tdst->num_polygons = src->num_polygons;\n\t\tmemcpy(dst->point_lights, src->point_lights, sizeof(uint8_t) * src->num_point_lights);\n\t\tmemcpy(dst->polygons, src->polygons, sizeof(uint8_t) * src->num_polygons);\n\t}\n\n\tR_VkBufferUnlock( locked );\n\n\tg_lights_.stats.ranges_uploaded++;\n}\n\nstatic void uploadGrid( void ) {\n\tASSERT(g_lights_.grid.cells <= MAX_LIGHT_CLUSTERS);\n\n\tint begin = -1;\n\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\tconst vk_lights_cell_t *const cell = g_lights_.cells + i;\n\n\t\tconst qboolean dirty = cell->frame_sequence == g_lights_.frame_sequence;\n\t\tif (dirty && begin < 0)\n\t\t\tbegin = i;\n\n\t\tif (!dirty && begin >= 0) {\n\t\t\tuploadGridRange(begin, i);\n\t\t\tbegin = -1;\n\t\t}\n\t}\n\n\tif (begin >= 0)\n\t\tuploadGridRange(begin, g_lights_.grid.cells);\n}\n\nstatic void uploadPolygonLights( struct LightsMetadata *metadata ) {\n\tASSERT(g_lights_.num_polygons <= MAX_EMISSIVE_KUSOCHKI);\n\tmetadata->num_polygons = g_lights_.num_polygons;\n\tfor (int i = 0; i < g_lights_.num_polygons; ++i) {\n\t\tconst rt_light_polygon_t *const src_poly = g_lights_.polygons + i;\n\t\tstruct PolygonLight *const dst_poly = metadata->polygons + i;\n\n\t\tVector4Copy(src_poly->plane, dst_poly->plane);\n\t\tVectorCopy(src_poly->center, dst_poly->center);\n\t\tdst_poly->area = src_poly->area;\n\t\tVectorCopy(src_poly->emissive, dst_poly->emissive);\n\n\t\t// TODO DEBUG_ASSERT\n\t\tASSERT(src_poly->vertices.count > 2);\n\t\tASSERT(src_poly->vertices.offset < 0xffffu);\n\t\tASSERT(src_poly->vertices.count < 0xffffu);\n\n\t\tASSERT(src_poly->vertices.offset + src_poly->vertices.count < COUNTOF(metadata->polygon_vertices));\n\n\t\tdst_poly->vertices_count_offset = (src_poly->vertices.count << 16) | (src_poly->vertices.offset);\n\t}\n\n\t// TODO static assert\n\tASSERT(sizeof(metadata->polygon_vertices) >= sizeof(g_lights_.polygon_vertices));\n\tfor (int i = 0; i < g_lights_.num_polygon_vertices; ++i) {\n\t\tVectorCopy(g_lights_.polygon_vertices[i], metadata->polygon_vertices[i]);\n\t}\n}\n\nstatic void uploadPointLights( struct LightsMetadata *metadata ) {\n\tmetadata->num_point_lights = g_lights_.num_point_lights;\n\tfor (int i = 0; i < g_lights_.num_point_lights; ++i) {\n\t\tvk_point_light_t *const src = g_lights_.point_lights + i;\n\t\tstruct PointLight *const dst = metadata->point_lights + i;\n\n\t\tVectorCopy(src->origin, dst->origin_r2);\n\t\tdst->origin_r2[3] = src->radius * src->radius;\n\n\t\tVectorCopy(src->color, dst->color_stopdot);\n\t\tdst->color_stopdot[3] = src->stopdot;\n\n\t\tVectorNegate(src->dir, dst->dir_stopdot2);\n\t\tdst->dir_stopdot2[3] = src->stopdot2_or_costheta;\n\n\t\tdst->environment = !!(src->flags & LightFlag_Environment);\n\t}\n}\n\nstatic void VK_LightsUpload( struct vk_combuf_s *combuf ) {\n\tAPROF_SCOPE_DECLARE_BEGIN(upload, __FUNCTION__);\n\tconst vk_buffer_locked_t locked = R_VkBufferLock(&g_lights_.buffer,\n\t\t(vk_buffer_lock_t) {\n\t\t\t.offset = 0,\n\t\t\t.size = sizeof(struct LightsMetadata),\n\t} );\n\n\tASSERT(locked.ptr);\n\n\tstruct LightsMetadata *metadata = locked.ptr;\n\tmemset(metadata, 0, sizeof(*metadata));\n\n\tVectorCopy(g_lights_.grid.min_cell, metadata->grid_min_cell);\n\tVectorCopy(g_lights_.grid.size, metadata->grid_size);\n\n\tuploadPolygonLights( metadata );\n\tuploadPointLights( metadata );\n\n\tR_VkBufferUnlock( locked );\n\n\tuploadGrid();\n\n\tg_lights_.frame_sequence++;\n\n\tAPROF_SCOPE_END(upload);\n\n\tR_VkBufferStagingCommit(&g_lights_.buffer, combuf);\n}\n\nstatic void RT_LightsFrameEnd( void ) {\n\tAPROF_SCOPE_BEGIN_EARLY(finalize);\n\tif (g_lights_.num_polygons > UINT8_MAX) {\n\t\tERROR_THROTTLED(10, \"Too many emissive surfaces found: %d; some areas will be dark\", g_lights_.num_polygons);\n\t\tg_lights_.num_polygons = UINT8_MAX;\n\t}\n\n\tfor (int i = 0; i < MAX_ELIGHTS; ++i) {\n\t\tconst dlight_t *dlight = globals.elights + i;\n\t\tif (!dlight)\n\t\t\tcontinue;\n\n\t\tif (addDlight(dlight))\n\t\t\t++g_lights_.stats.elights;\n\t}\n\n\tfor (int i = 0; i < g_lights_.num_point_lights; ++i) {\n\t\tvk_point_light_t *const light = g_lights_.point_lights + i;\n\t\tif (light->lightstyle < 0 || light->lightstyle >= MAX_LIGHTSTYLES)\n\t\t\tcontinue;\n\n\t\t{\n\t\t\tconst float scale = g_lightmap.lightstylevalue[light->lightstyle] / 255.f;\n\t\t\tVectorScale(light->base_color, scale, light->color);\n\t\t}\n\t}\n\n\tAPROF_SCOPE_BEGIN(dlights);\n\tfor (int i = 0; i < MAX_DLIGHTS; ++i) {\n\t\tconst dlight_t *dlight = globals.dlights + i;\n\t\tif( !dlight || dlight->die < gp_cl->time || !dlight->radius )\n\t\t\tcontinue;\n\n\t\tif (addDlight(dlight))\n\t\t\t++g_lights_.stats.dlights;\n\t}\n\tAPROF_SCOPE_END(dlights);\n\n\tif (debug_dump_lights.enabled) {\n#if 0\n\t\t// Print light grid stats\n\t\tDEBUG(\"Emissive surfaces found: %d\", g_lights_.num_polygons);\n\n\t\t{\n\t\t\t#define GROUPSIZE 4\n\t\t\tint histogram[1 + (MAX_VISIBLE_SURFACE_LIGHTS + GROUPSIZE - 1) / GROUPSIZE] = {0};\n\t\t\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\t\t\tconst vk_lights_cell_t *cluster = g_lights_.cells + i;\n\t\t\t\tconst int hist_index = cluster->num_polygons ? 1 + cluster->num_polygons / GROUPSIZE : 0;\n\t\t\t\thistogram[hist_index]++;\n\t\t\t}\n\n\t\t\tDEBUG(\"Built %d light clusters. Stats:\", g_lights_.grid.cells);\n\t\t\tDEBUG(\"  0: %d\", histogram[0]);\n\t\t\tfor (int i = 1; i < ARRAYSIZE(histogram); ++i)\n\t\t\t\tDEBUG(\"  %d-%d: %d\",\n\t\t\t\t\t(i - 1) * GROUPSIZE,\n\t\t\t\t\ti * GROUPSIZE - 1,\n\t\t\t\t\thistogram[i]);\n\t\t}\n\n\t\t{\n\t\t\tint num_clusters_with_lights_in_range = 0;\n\t\t\tfor (int i = 0; i < g_lights_.grid.cells; ++i) {\n\t\t\t\tconst vk_lights_cell_t *cluster = g_lights_.cells + i;\n\t\t\t\tif (cluster->num_polygons > 0) {\n\t\t\t\t\tDEBUG(\" cluster %d: polygons=%d\", i, cluster->num_polygons);\n\t\t\t\t}\n\n\t\t\t\tfor (int j = 0; j < cluster->num_polygons; ++j) {\n\t\t\t\t\tconst int index = cluster->polygons[j];\n\t\t\t\t\tif (index >= vk_rtx_light_begin->value && index < vk_rtx_light_end->value) {\n\t\t\t\t\t\t++num_clusters_with_lights_in_range;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tDEBUG(\"Clusters with filtered lights: %d\", num_clusters_with_lights_in_range);\n\t\t}\n#endif\n\t}\n\n\tg_lights_.stats.dirty_cells_size = g_lights_.stats.dirty_cells * sizeof(struct LightCluster);\n\tg_lights_.stats.dynamic_points = g_lights_.num_point_lights - g_lights_.num_static.point_lights;\n\tg_lights_.stats.dynamic_polygons = g_lights_.num_polygons - g_lights_.num_static.polygons;\n\n\tdebug_dump_lights.enabled = false;\n\tAPROF_SCOPE_END(finalize);\n}\n\nstatic void lightsProduce(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\tASSERT(p->frame_sequence_tag != ctx->frame_sequence);\n\tRT_LightsFrameEnd();\n\tVK_LightsUpload(combuf);\n\t// TODO frame begin\n}\n\nchar *RT_LightPrintCellInfo(char *p, char *const end, vec3_t pos) {\n\tconst int cell_raw[3] = {\n\t\tfloor(pos[0] / LIGHT_GRID_CELL_SIZE),\n\t\tfloor(pos[1] / LIGHT_GRID_CELL_SIZE),\n\t\tfloor(pos[2] / LIGHT_GRID_CELL_SIZE),\n\t};\n\tconst int light_cell[3] = {\n\t\tcell_raw[0] - g_lights_.grid.min_cell[0],\n\t\tcell_raw[1] - g_lights_.grid.min_cell[1],\n\t\tcell_raw[2] - g_lights_.grid.min_cell[2],\n\t};\n\tconst int cell_index = RT_LightCellIndex( light_cell );\n\n\tconst vk_lights_cell_t *cell = (cell_index >= 0 && cell_index < MAX_LIGHT_CLUSTERS) ? g_lights_.cells + cell_index : NULL;\n\tp += Q_snprintf(p, end - p,\n\t\t\"light raw=(%d, %d, %d) cell=(%d, %d, %d) index=%d poly=%d point=%d\\n\",\n\t\tcell_raw[0],\n\t\tcell_raw[1],\n\t\tcell_raw[2],\n\t\tlight_cell[0],\n\t\tlight_cell[1],\n\t\tlight_cell[2],\n\t\tcell_index,\n\t\tcell ? cell->num_polygons : -1,\n\t\tcell ? cell->num_point_lights : -1);\n\n\tif (cell && cell->num_polygons > 0) {\n\t\tp += Q_snprintf(p, end - p, \"poly:\");\n\t\tfor (int i = 0; i < cell->num_polygons; ++i) {\n\t\t\tp += Q_snprintf(p, end - p, \" %d\", cell->polygons[i]);\n\t\t}\n\t\tp += Q_snprintf(p, end - p, \"\\n\");\n\t}\n\n\tif (cell && cell->num_point_lights > 0) {\n\t\tp += Q_snprintf(p, end - p, \"point:\");\n\t\tfor (int i = 0; i < cell->num_point_lights; ++i) {\n\t\t\tp += Q_snprintf(p, end - p, \" %d\", cell->point_lights[i]);\n\t\t}\n\t\tp += Q_snprintf(p, end - p, \"\\n\");\n\t}\n\n\treturn p;\n}\n"
  },
  {
    "path": "ref/vk/vk_light.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\nqboolean VK_LightsInit( void );\nvoid VK_LightsShutdown( void );\n\n// Allocate clusters and vis data for the new map\nstruct model_s;\nvoid RT_LightsNewMap( const struct model_s *map );\n\n// Clear light data and prepare for loading\n// RT_LightsNewMap should have been already called for current map\nvoid RT_LightsLoadBegin( const struct model_s *map );\n// Finalize loading light data, i.e. mark everything loaded so far as static light data\nvoid RT_LightsLoadEnd( void );\n\n// TODO can we call it from produce/consumed?\nvoid RT_LightsFrameBegin( void );\n\nqboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id );\n\nint RT_LightCellIndex( const int light_cell[3] );\n\nstruct cl_entity_s;\nvoid RT_LightAddFlashlight( const struct cl_entity_s *ent, qboolean local_player );\n\nstruct msurface_s;\ntypedef struct rt_light_add_polygon_s {\n\tint num_vertices;\n\tvec3_t vertices[7];\n\n\tvec3_t emissive;\n\n\t// Needed for BSP visibilty purposes\n\t// TODO can we layer light code? like:\n\t// - bsp/xash/rad/patch-specific stuff\n\t// - mostly engine-agnostic light clusters\n\tconst struct msurface_s *surface;\n\n\tqboolean dynamic;\n\tconst matrix3x4 *transform_row;\n} rt_light_add_polygon_t;\nint RT_LightAddPolygon(const rt_light_add_polygon_t *light);\n\nchar *RT_LightPrintCellInfo(char *p, char *const end, vec3_t pos);\n"
  },
  {
    "path": "ref/vk/vk_lightmap.c",
    "content": "#include \"vk_lightmap.h\"\n#include \"vk_common.h\"\n#include \"r_textures.h\"\n#include \"vk_cvar.h\"\n\n#include \"com_strings.h\"\n#include \"xash3d_mathlib.h\"\n#include \"protocol.h\"\n\n#include <memory.h>\n\ntypedef struct\n{\n\tint\t\tallocated[BLOCK_SIZE_MAX];\n\tint\t\tcurrent_lightmap_texture;\n\t//msurface_t\t*dynamic_surfaces;\n\t//msurface_t\t*lightmap_surfaces[MAX_LIGHTMAPS];\n\tbyte\t\tlightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4];\n} gllightmapstate_t;\n\nstatic gllightmapstate_t gl_lms;\n\nxvk_lightmap_state_t g_lightmap;\n\n// TODO this doesn't really need to be this huge\nstatic uint\t\tr_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3]; // This is just a temp HDR-ish buffer for lightmap generation\n\nstatic void LM_InitBlock( void )\n{\n\tmemset( gl_lms.allocated, 0, sizeof( gl_lms.allocated ));\n}\n\nstatic int LM_AllocBlock( int w, int h, int *x, int *y )\n{\n\tint\ti, j;\n\tint\tbest, best2;\n\n\tbest = BLOCK_SIZE;\n\n\tfor( i = 0; i < BLOCK_SIZE - w; i++ )\n\t{\n\t\tbest2 = 0;\n\n\t\tfor( j = 0; j < w; j++ )\n\t\t{\n\t\t\tif( gl_lms.allocated[i+j] >= best )\n\t\t\t\tbreak;\n\t\t\tif( gl_lms.allocated[i+j] > best2 )\n\t\t\t\tbest2 = gl_lms.allocated[i+j];\n\t\t}\n\n\t\tif( j == w )\n\t\t{\n\t\t\t// this is a valid spot\n\t\t\t*x = i;\n\t\t\t*y = best = best2;\n\t\t}\n\t}\n\n\tif( best + h > BLOCK_SIZE )\n\t\treturn false;\n\n\tfor( i = 0; i < w; i++ )\n\t\tgl_lms.allocated[*x + i] = best + h;\n\n\treturn true;\n}\n\n/*\nstatic void LM_UploadDynamicBlock( void )\n{\n\tint\theight = 0, i;\n\n\tfor( i = 0; i < BLOCK_SIZE; i++ )\n\t{\n\t\tif( gl_lms.allocated[i] > height )\n\t\t\theight = gl_lms.allocated[i];\n\t}\n\n\tgEngine.Con_Printf(S_ERROR \"VK NOT IMPLEMENTED %s\\n\", __FUNCTION__);\n\t//pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer );\n}\n*/\n\nstatic void LM_UploadBlock( qboolean dynamic )\n{\n\tint\ti;\n\n\tif( dynamic )\n\t{\n\t\tint\theight = 0;\n\n\t\tfor( i = 0; i < BLOCK_SIZE; i++ )\n\t\t{\n\t\t\tif( gl_lms.allocated[i] > height )\n\t\t\t\theight = gl_lms.allocated[i];\n\t\t}\n\n\t\tgEngine.Con_Printf(S_ERROR \"VK NOT IMPLEMENTED %s dynamic \\n\", __FUNCTION__);\n\t\t/* GL_Bind( XASH_TEXTURE0, gl_lms.dlightTexture ); */\n\t\t/* pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); */\n\t}\n\telse\n\t{\n\t\trgbdata_t\tr_lightmap;\n\t\tchar\tlmName[16];\n\n\t\ti = gl_lms.current_lightmap_texture;\n\n\t\t// upload static lightmaps only during loading\n\t\tmemset( &r_lightmap, 0, sizeof( r_lightmap ));\n\t\tQ_snprintf( lmName, sizeof( lmName ), \"*lightmap%i\", i );\n\n\t\tr_lightmap.width = BLOCK_SIZE;\n\t\tr_lightmap.height = BLOCK_SIZE;\n\t\tr_lightmap.type = PF_RGBA_32;\n\t\tr_lightmap.size = r_lightmap.width * r_lightmap.height * 4;\n\t\tr_lightmap.flags = IMAGE_HAS_COLOR;\n\t\tr_lightmap.buffer = gl_lms.lightmap_buffer;\n\t\ttglob.lightmapTextures[i] = R_TextureUploadFromBuffer( lmName, &r_lightmap, TF_ATLAS_PAGE|TF_NOMIPMAP|TF_CLAMP, false );\n\n\t\tif( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS )\n\t\t\tgEngine.Host_Error( \"AllocBlock: full\\n\" );\n\t}\n}\n\n/*\n=================\nR_BuildLightmap\n\nCombine and scale multiple lightmaps into the floating\nformat in r_blocklights\n=================\n*/\nstatic void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean dynamic )\n{\n\tint\t\tsmax, tmax;\n\tuint\t\t*bl;\n\tint\t\ti, map, size, s, t;\n\tint\t\tsample_size;\n\tmextrasurf_t\t*info = surf->info;\n\tcolor24\t\t*lm;\n\tsample_size = gEngine.Mod_SampleSizeForFace( surf );\n\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\tsize = smax * tmax;\n\n\tlm = surf->samples;\n\n\tmemset( r_blocklights, 0, sizeof( uint ) * size * 3 );\n\n\t// add all the lightmaps\n\tfor( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ )\n\t{\n\t\tconst uint scale = g_lightmap.lightstylevalue[surf->styles[map]];\n\t\tfor( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ )\n\t\t{\n\t\t\tbl[0] += LightToTexGamma( lm->r ) * scale;\n\t\t\tbl[1] += LightToTexGamma( lm->g ) * scale;\n\t\t\tbl[2] += LightToTexGamma( lm->b ) * scale;\n\t\t}\n\t}\n\n\t/* TODO\n\t// add all the dynamic lights\n\tif( surf->dlightframe == gl_lms.framecount && dynamic )\n\t\tR_AddDynamicLights( surf );\n\t*/\n\n\t// Put into texture format\n\tstride -= (smax << 2);\n\tbl = r_blocklights;\n\n\tfor( t = 0; t < tmax; t++, dest += stride )\n\t{\n\t\tfor( s = 0; s < smax; s++ )\n\t\t{\n\t\t\tdest[0] = Q_min((bl[0] >> 7), 255 );\n\t\t\tdest[1] = Q_min((bl[1] >> 7), 255 );\n\t\t\tdest[2] = Q_min((bl[2] >> 7), 255 );\n\t\t\tdest[3] = 255;\n\n\t\t\tbl += 3;\n\t\t\tdest += 4;\n\t\t}\n\t}\n}\n\nvoid VK_CreateSurfaceLightmap( msurface_t *surf, const model_t *loadmodel )\n{\n\tint\t\tsmax, tmax;\n\tint\t\tsample_size;\n\tmextrasurf_t\t*info = surf->info;\n\tbyte\t\t*base;\n\n\tif( !loadmodel->lightdata )\n\t\treturn;\n\n\tif( FBitSet( surf->flags, SURF_DRAWTILED ))\n\t\treturn;\n\n\tsample_size = gEngine.Mod_SampleSizeForFace( surf );\n\tsmax = ( info->lightextents[0] / sample_size ) + 1;\n\ttmax = ( info->lightextents[1] / sample_size ) + 1;\n\n\tif( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))\n\t{\n\t\tLM_UploadBlock( false );\n\t\tLM_InitBlock();\n\n\t\tif( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))\n\t\t\tgEngine.Host_Error( \"AllocBlock: full\\n\" );\n\t}\n\n\tsurf->lightmaptexturenum = gl_lms.current_lightmap_texture;\n\n\tbase = gl_lms.lightmap_buffer;\n\tbase += ( surf->light_t * BLOCK_SIZE + surf->light_s ) * 4;\n\n\t// FIXME R_SetCacheState( surf );\n\tR_BuildLightMap( surf, base, BLOCK_SIZE * 4, false );\n}\n\nvoid VK_UploadLightmap( void )\n{\n\tLM_UploadBlock( false );\n}\n\nvoid VK_ClearLightmap( void )\n{\n\tfor (int i = 0; i < gl_lms.current_lightmap_texture; ++i)\n\t\tR_TextureFree(tglob.lightmapTextures[i]);\n\tgl_lms.current_lightmap_texture = 0;\n\n\tLM_InitBlock();\n}\n\nvoid VK_RunLightStyles( lightstyle_t *styles )\n{\n\tint\t\ti, k, flight, clight;\n\tfloat\t\tl, lerpfrac, backlerp;\n\tfloat\t\tframetime = (gp_cl->time -   gp_cl->oldtime);\n\tfloat\t\tscale;\n\tlightstyle_t\t*ls;\n\tconst model_t *world = WORLDMODEL;\n\n\tif( !world ) return;\n\n\tscale = r_lighting_modulate->value;\n\n\t// light animations\n\t// 'm' is normal light, 'a' is no light, 'z' is double bright\n\n\t// TODO\n\tfor( i = 0; i < MAX_LIGHTSTYLES; i++ )\n\t{\n\t\tls = styles + i;\n\t\tif( !world->lightdata )\n\t\t{\n\t\t\tg_lightmap.lightstylevalue[i] = 256 * 256;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif( !gEngine.EngineGetParm( PARAM_GAMEPAUSED, 0 ) && frametime <= 0.1f )\n\t\t\tls->time += frametime; // evaluate local time\n\n\t\tflight = (int)Q_floor( ls->time * 10 );\n\t\tclight = (int)Q_ceil( ls->time * 10 );\n\t\tlerpfrac = ( ls->time * 10 ) - flight;\n\t\tbacklerp = 1.0f - lerpfrac;\n\n\t\tif( !ls->length )\n\t\t{\n\t\t\tg_lightmap.lightstylevalue[i] = 256 * scale;\n\t\t\tcontinue;\n\t\t}\n\t\telse if( ls->length == 1 )\n\t\t{\n\t\t\t// single length style so don't bother interpolating\n\t\t\tg_lightmap.lightstylevalue[i] = ls->map[0] * 22 * scale;\n\t\t\tcontinue;\n\t\t}\n\t\telse if( !ls->interp || !CVAR_TO_BOOL( cl_lightstyle_lerping ))\n\t\t{\n\t\t\tg_lightmap.lightstylevalue[i] = ls->map[flight%ls->length] * 22 * scale;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// interpolate animating light\n\t\t// frame just gone\n\t\tk = ls->map[flight % ls->length];\n\t\tl = (float)( k * 22.0f ) * backlerp;\n\n\t\t// upcoming frame\n\t\tk = ls->map[clight % ls->length];\n\t\tl += (float)( k * 22.0f ) * lerpfrac;\n\n\t\tg_lightmap.lightstylevalue[i] = (int)l * scale;\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_lightmap.h",
    "content": "#pragma once\n\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"protocol.h\"\n#include \"lightstyle.h\"\n\n#define BLOCK_SIZE_MAX\t1024\n#define BLOCK_SIZE BLOCK_SIZE_MAX\n\ntypedef struct {\n\tint lightstylevalue[MAX_LIGHTSTYLES];\t// value 0 - 65536\n} xvk_lightmap_state_t;\n\nextern xvk_lightmap_state_t g_lightmap;\n\nvoid VK_ClearLightmap( void );\nvoid VK_CreateSurfaceLightmap( msurface_t *surf, const model_t *loadmodel );\nvoid VK_UploadLightmap( void );\n\nvoid VK_RunLightStyles( lightstyle_t *ls );\n"
  },
  {
    "path": "ref/vk/vk_logs.c",
    "content": "#include \"vk_logs.h\"\n#include \"std/stringview.h\"\n\nuint32_t g_log_debug_bits = 0;\n\nstatic const struct log_pair_t {\n\tconst char *name;\n\tuint32_t bit;\n} g_log_module_pairs[] = {\n#define X(m) {LOG_NAME(m), LOG_BIT(m)},\n\tLIST_LOG_MODULES(X)\n#undef X\n};\n\nvoid R_LogSetVerboseModules( const char *p ) {\n\tg_log_debug_bits = 0;\n\twhile (*p) {\n\t\tconst char *next = Q_strchrnul(p, ',');\n\t\tconst const_string_view_t name = {p, next - p};\n\t\tuint32_t bit = 0;\n\n\t\tfor (int i = 0; i < COUNTOF(g_log_module_pairs); ++i) {\n\t\t\tconst struct log_pair_t *const pair = g_log_module_pairs + i;\n\t\t\tif (svCmp(name, pair->name) == 0) {\n\t\t\t\tgEngine.Con_Reportf(\"Enabling verbose logs for module \\\"%.*s\\\"\\n\", name.len, name.s);\n\t\t\t\tbit = pair->bit;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!bit) {\n\t\t\tgEngine.Con_Reportf(S_ERROR \"Unknown log module \\\"%.*s\\\"\\n\", name.len, name.s);\n\t\t}\n\n\t\tg_log_debug_bits |= bit;\n\n\t\tif (!*next)\n\t\t\tbreak;\n\t\tp = next + 1;\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_logs.h",
    "content": "#pragma once\n\n#include \"vk_common.h\"\n\n#define LIST_LOG_MODULES(X) \\\n\tX(core) \\\n\tX(misc) \\\n\tX(tex) \\\n\tX(brush) \\\n\tX(light) \\\n\tX(studio) \\\n\tX(patch) \\\n\tX(mat) \\\n\tX(meat) \\\n\tX(rt) \\\n\tX(rmain) \\\n\tX(sprite) \\\n\tX(img) \\\n\tX(staging) \\\n\tX(buf) \\\n\tX(fctl) \\\n\tX(combuf) \\\n\nenum {\n#define X(m) LogModule_##m,\nLIST_LOG_MODULES(X)\n#undef X\n};\n\nextern uint32_t g_log_debug_bits;\n\n// TODO:\n// - load bits early at startup somehow. cvar is empty at init for some reason\n// - module name in message\n// - file:line in message\n// - consistent prefixes (see THROTTLED variant)\n\n#define LOG_BIT(m) (1 << LogModule_##m)\n#define LOG_NAME(m) #m\n\n#define LOG_VERBOSE_IMPL(m) (g_log_debug_bits & LOG_BIT(m))\n#define LOG_VERBOSE LOG_VERBOSE_IMPL(LOG_MODULE)\n\n#define DEBUG_IMPL(module, msg, ...) \\\n\tdo { \\\n\t\tif (g_log_debug_bits & LOG_BIT(module)) { \\\n\t\t\tgEngine.Con_Reportf(\"vk/\" LOG_NAME(module) \": \" msg \"\\n\", ##__VA_ARGS__); \\\n\t\t} \\\n\t} while(0)\n\n#define WARN_IMPL(module, msg, ...) \\\n\tdo { \\\n\t\tgEngine.Con_Printf(S_WARN \"vk/\" LOG_NAME(module) \": \" msg \"\\n\", ##__VA_ARGS__); \\\n\t} while(0)\n\n#define ERR_IMPL(module, msg, ...) \\\n\tdo { \\\n\t\tgEngine.Con_Printf(S_ERROR \"vk/\" LOG_NAME(module) \": \" msg \"\\n\", ##__VA_ARGS__); \\\n\t} while(0)\n\n#define INFO_IMPL(module, msg, ...) \\\n\tdo { \\\n\t\tgEngine.Con_Printf(\"vk/\" LOG_NAME(module) \": \" msg \"\\n\", ##__VA_ARGS__); \\\n\t} while(0)\n\n#define PRINT_THROTTLED(delay, prefix, msg, ...) \\\n\tdo { \\\n\t\tstatic int called = 0; \\\n\t\tstatic double next_message_time = 0.; \\\n\t\tif (gp_host->realtime > next_message_time) { \\\n\t\t\tgEngine.Con_Printf( prefix \"(x%d) \" msg \"\\n\", called, ##__VA_ARGS__ ); \\\n\t\t\tnext_message_time = gp_host->realtime + delay; \\\n\t\t} \\\n\t\t++called; \\\n\t} while(0)\n\n#define DEBUG(msg, ...) DEBUG_IMPL(LOG_MODULE, msg, ##__VA_ARGS__)\n#define WARN(msg, ...) WARN_IMPL(LOG_MODULE, msg, ##__VA_ARGS__)\n#define ERR(msg, ...) ERR_IMPL(LOG_MODULE, msg, ##__VA_ARGS__)\n#define INFO(msg, ...) INFO_IMPL(LOG_MODULE, msg, ##__VA_ARGS__)\n\n#define ERROR_THROTTLED(delay, msg, ...) PRINT_THROTTLED(delay, S_ERROR \"vk: \", msg, ##__VA_ARGS__)\n\n#define WARN_THROTTLED(delay, msg, ...) PRINT_THROTTLED(delay, S_WARN \"vk: \", msg, ##__VA_ARGS__)\n\n#define PRINT_NOT_IMPLEMENTED_ARGS(msg, ...) do { \\\n\t\tstatic int called = 0; \\\n\t\tif ((called&1023) == 0) { \\\n\t\t\tgEngine.Con_Printf( S_ERROR \"VK NOT_IMPLEMENTED(x%d): %s \" msg \"\\n\", called, __FUNCTION__, ##__VA_ARGS__ ); \\\n\t\t} \\\n\t\t++called; \\\n\t} while(0)\n\n#define PRINT_NOT_IMPLEMENTED() do { \\\n\t\tstatic int called = 0; \\\n\t\tif ((called&1023) == 0) { \\\n\t\t\tgEngine.Con_Printf( S_ERROR \"VK NOT_IMPLEMENTED(x%d): %s\\n\", called, __FUNCTION__ ); \\\n\t\t} \\\n\t\t++called; \\\n\t} while(0)\n\nvoid R_LogSetVerboseModules( const char *modules );\n"
  },
  {
    "path": "ref/vk/vk_mapents.c",
    "content": "#include \"vk_common.h\"\n#include \"vk_mapents.h\"\n#include \"vk_core.h\" // TODO we need only pool from there, not the entire vulkan garbage\n#include \"r_textures.h\"\n#include \"vk_logs.h\"\n\n#include \"eiface.h\" // ARRAYSIZE\n#include \"xash3d_mathlib.h\"\n#include <string.h>\n#include <ctype.h>\n\n#define LOG_MODULE patch\n\nxvk_map_entities_t g_map_entities;\n\nstatic struct {\n\txvk_patch_surface_t *surfaces;\n\tint surfaces_count;\n} g_patch;\n\nstatic unsigned parseEntPropWadList(const char* value, string *out, unsigned bit) {\n\tint dst_left = sizeof(string) - 2; // ; \\0\n\tchar *dst = *out;\n\t*dst = '\\0';\n\tDEBUG(\"WADS: %s\", value);\n\n\tfor (; *value;) {\n\t\tconst char *file_begin = value;\n\n\t\tfor (; *value && *value != ';'; ++value) {\n\t\t\tif (*value == '\\\\' || *value == '/')\n\t\t\t\tfile_begin = value + 1;\n\t\t}\n\n\t\t{\n\t\t\tconst int len = value - file_begin;\n\t\t\tDEBUG(\"WAD: %.*s\", len, file_begin);\n\n\t\t\tif (len < dst_left) {\n\t\t\t\tQ_strncpy(dst, file_begin, len + 1);\n\t\t\t\tdst += len;\n\t\t\t\tdst[0] = ';';\n\t\t\t\tdst++;\n\t\t\t\tdst[0] = '\\0';\n\t\t\t\tdst_left -= len;\n\t\t\t}\n\t\t}\n\n\t\tif (*value) value++;\n\t}\n\n\tDEBUG(\"wad list: %s\", *out);\n\treturn bit;\n}\n\nstatic unsigned parseEntPropFloat(const char* value, float *out, unsigned bit) {\n\treturn (1 == sscanf(value, \"%f\", out)) ? bit : 0;\n}\n\nstatic unsigned parseEntPropInt(const char* value, int *out, unsigned bit) {\n\treturn (1 == sscanf(value, \"%d\", out)) ? bit : 0;\n}\n\nstatic unsigned parseEntPropIntArray(const char* value, int_array_t *out, unsigned bit) {\n\tunsigned retval = 0;\n\tout->num = 0;\n\twhile (*value) {\n\t\tint i = 0;\n\t\tif (0 == sscanf(value, \"%d\", &i))\n\t\t\tbreak;\n\n\t\tif (out->num == MAX_INT_ARRAY_SIZE)\n\t\t\tbreak;\n\n\t\tretval |= bit;\n\n\t\tout->values[out->num++] = i;\n\n\t\twhile (*value && isdigit(*value)) ++value;\n\t\twhile (*value && isspace(*value)) ++value;\n\t}\n\n\tif (*value) {\n\t\tERR(\"Error parsing mapents patch IntArray (wrong format? too many entries (max=%d)), portion not parsed: %s\", MAX_INT_ARRAY_SIZE, value);\n\t}\n\treturn retval;\n}\n\nstatic unsigned parseEntPropString(const char* value, string *out, unsigned bit) {\n\tconst int len = Q_strlen(value);\n\tif (len >= sizeof(string))\n\t\tERR(\"Map entity value '%s' is too long, max length is %d\",\n\t\t\tvalue, (int)sizeof(string));\n\tQ_strncpy(*out, value, sizeof(*out));\n\treturn bit;\n}\n\nstatic unsigned parseEntPropVec2(const char* value, vec2_t *out, unsigned bit) {\n\treturn (2 == sscanf(value, \"%f %f\", &(*out)[0], &(*out)[1])) ? bit : 0;\n}\n\nstatic unsigned parseEntPropVec3(const char* value, vec3_t *out, unsigned bit) {\n\treturn (3 == sscanf(value, \"%f %f %f\", &(*out)[0], &(*out)[1], &(*out)[2])) ? bit : 0;\n}\n\n/*\nstatic unsigned parseEntPropVec4(const char* value, vec4_t *out, unsigned bit) {\n\treturn (4 == sscanf(value, \"%f %f %f %f\", &(*out)[0], &(*out)[1], &(*out)[2], &(*out)[3])) ? bit : 0;\n}\n*/\n\nstatic unsigned parseEntPropRgbav(const char* value, vec3_t *out, unsigned bit) {\n\tfloat scale = 1.f;\n\tconst int components = sscanf(value, \"%f %f %f %f\", &(*out)[0], &(*out)[1], &(*out)[2], &scale);\n\tif (components == 1) {\n\t\t(*out)[2] = (*out)[1] = (*out)[0] = (*out)[0];\n\t\treturn bit;\n\t} else if (components == 4) {\n\t\tscale /= 255.f;\n\t\t(*out)[0] *= scale;\n\t\t(*out)[1] *= scale;\n\t\t(*out)[2] *= scale;\n\t\treturn bit;\n\t} else if (components == 3) {\n\t\t(*out)[0] *= scale;\n\t\t(*out)[1] *= scale;\n\t\t(*out)[2] *= scale;\n\t\treturn bit;\n\t}\n\n\treturn 0;\n}\n\nstatic unsigned parseEntPropClassname(const char* value, class_name_e *out, unsigned bit) {\n\tif (Q_strcmp(value, \"light\") == 0) {\n\t\t*out = Light;\n\t} else if (Q_strcmp(value, \"light_spot\") == 0) {\n\t\t*out = LightSpot;\n\t} else if (Q_strcmp(value, \"light_environment\") == 0) {\n\t\t*out = LightEnvironment;\n\t} else if (Q_strcmp(value, \"worldspawn\") == 0) {\n\t\t*out = Worldspawn;\n\t} else if (Q_strncmp(value, \"func_\", 5) == 0) {\n\t\t*out = FuncAny;\n\t} else {\n\t\t*out = Ignored;\n\t}\n\n\treturn bit;\n}\n\nstatic void weirdGoldsrcLightScaling( vec3_t intensity ) {\n\tfloat l1 = Q_max( intensity[0], Q_max( intensity[1], intensity[2] ) );\n\tl1 = l1 * l1 / 10;\n\tVectorScale( intensity, l1, intensity );\n}\n\nstatic void parseAngles( const entity_props_t *props, vk_light_entity_t *le) {\n\tfloat angle = props->angle;\n\tVectorSet( le->dir, 0, 0, 0 );\n\n\tif (angle == -1) { // UP\n\t\tle->dir[0] = le->dir[1] = 0;\n\t\tle->dir[2] = 1;\n\t} else if (angle == -2) { // DOWN\n\t\tle->dir[0] = le->dir[1] = 0;\n\t\tle->dir[2] = -1;\n\t} else {\n\t\tif (angle == 0) {\n\t\t\tangle = props->angles[1];\n\t\t}\n\n\t\tangle *= M_PI / 180.f;\n\n\t\tle->dir[2] = 0;\n\t\tle->dir[0] = cosf(angle);\n\t\tle->dir[1] = sinf(angle);\n\t}\n\n\tangle = props->pitch ? props->pitch : props->angles[0];\n\n\tangle *= M_PI / 180.f;\n\tle->dir[2] = sinf(angle);\n\tle->dir[0] *= cosf(angle);\n\tle->dir[1] *= cosf(angle);\n}\n\nstatic void parseStopDot( const entity_props_t *props, vk_light_entity_t *le) {\n\tle->stopdot = props->_cone ? props->_cone : 10;\n\tle->stopdot2 = Q_max(le->stopdot, props->_cone2);\n\n\tle->stopdot = cosf(le->stopdot * M_PI / 180.f);\n\tle->stopdot2 = cosf(le->stopdot2 * M_PI / 180.f);\n}\n\nstatic void fillLightFromProps( vk_light_entity_t *le, const entity_props_t *props, unsigned have_fields, qboolean patch, int entity_index ) {\n\tswitch (le->type) {\n\t\tcase LightTypePoint:\n\t\t\tbreak;\n\n\t\tcase LightTypeSpot:\n\t\tcase LightTypeEnvironment:\n\t\t\tif (!patch || (have_fields & Field_pitch) || (have_fields & Field_angles) || (have_fields & Field_angle)) {\n\t\t\t\tparseAngles(props, le);\n\t\t\t}\n\n\t\t\tif (!patch || (have_fields & Field__cone) || (have_fields & Field__cone2)) {\n\t\t\t\tparseStopDot(props, le);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tASSERT(false);\n\t}\n\n\tif (have_fields & Field_target)\n\t\tQ_strncpy( le->target_entity, props->target, sizeof( le->target_entity ));\n\n\tif (have_fields & Field_origin)\n\t\tVectorCopy(props->origin, le->origin);\n\n\tif (have_fields & Field__light)\n\t{\n\t\tVectorCopy(props->_light, le->color);\n\t} else if (!patch) {\n\t\t// same as qrad\n\t\tVectorSet(le->color, 300, 300, 300);\n\t}\n\n\tif (have_fields & Field__xvk_radius) {\n\t\tle->radius = props->_xvk_radius;\n\t}\n\n\tif (have_fields & Field__xvk_solid_angle) {\n\t\tle->solid_angle = props->_xvk_solid_angle;\n\t}\n\n\tif (have_fields & Field_style) {\n\t\tle->style = props->style;\n\t}\n\n\tif (le->type != LightTypeEnvironment && (!patch || (have_fields & Field__light))) {\n\t\tweirdGoldsrcLightScaling(le->color);\n\t}\n\n\tDEBUG(\"%s light %d (ent=%d): %s targetname=%s color=(%f %f %f) origin=(%f %f %f) style=%d R=%f SA=%f dir=(%f %f %f) stopdot=(%f %f)\",\n\t\tpatch ? \"Patch\" : \"Added\",\n\t\tg_map_entities.num_lights, entity_index,\n\t\tle->type == LightTypeEnvironment ? \"environment\" : le->type == LightTypeSpot ? \"spot\" : \"point\",\n\t\tprops->targetname,\n\t\tle->color[0], le->color[1], le->color[2],\n\t\tle->origin[0], le->origin[1], le->origin[2],\n\t\tle->style,\n\t\tle->radius, le->solid_angle,\n\t\tle->dir[0], le->dir[1], le->dir[2],\n\t\tle->stopdot, le->stopdot2);\n}\n\nstatic void addLightEntity( const entity_props_t *props, unsigned have_fields ) {\n\tconst int index = g_map_entities.num_lights;\n\tvk_light_entity_t *le = g_map_entities.lights + index;\n\tunsigned expected_fields = 0;\n\n\tif (g_map_entities.num_lights == ARRAYSIZE(g_map_entities.lights)) {\n\t\tERR(\"Too many lights entities in map\");\n\t\treturn;\n\t}\n\n\t*le = (vk_light_entity_t){0};\n\n\tswitch (props->classname) {\n\t\tcase Light:\n\t\t\tle->type = LightTypePoint;\n\t\t\texpected_fields = Field_origin;\n\t\t\tbreak;\n\n\t\tcase LightSpot:\n\t\t\tif ((have_fields & Field__sky) && props->_sky != 0) {\n\t\t\t\tle->type = LightTypeEnvironment;\n\t\t\t\texpected_fields = Field__cone | Field__cone2;\n\t\t\t} else {\n\t\t\t\tle->type = LightTypeSpot;\n\t\t\t\texpected_fields = Field_origin | Field__cone | Field__cone2;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase LightEnvironment:\n\t\t\tle->type = LightTypeEnvironment;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tASSERT(false);\n\t}\n\n\tif ((have_fields & expected_fields) != expected_fields) {\n\t\tERR(\"Missing some fields for light entity\");\n\t\treturn;\n\t}\n\n\tif (le->type == LightTypeEnvironment) {\n\t\tif (g_map_entities.single_environment_index == NoEnvironmentLights) {\n\t\t\tg_map_entities.single_environment_index = index;\n\t\t} else {\n\t\t\tg_map_entities.single_environment_index = MoreThanOneEnvironmentLight;\n\t\t}\n\t}\n\n\tfillLightFromProps(le, props, have_fields, false, g_map_entities.entity_count);\n\n\tle->entity_index = g_map_entities.entity_count;\n\tg_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){\n\t\t.class = props->classname,\n\t\t.index = g_map_entities.num_lights,\n\t};\n\tg_map_entities.num_lights++;\n}\n\nstatic void addTargetEntity( const entity_props_t *props ) {\n\txvk_mapent_target_t *target = g_map_entities.targets + g_map_entities.num_targets;\n\n\tDEBUG(\"Adding target entity %s at (%f, %f, %f)\",\n\t\tprops->targetname, props->origin[0], props->origin[1], props->origin[2]);\n\n\tif (g_map_entities.num_targets == MAX_MAPENT_TARGETS) {\n\t\tERR(\"Too many map target entities\");\n\t\treturn;\n\t}\n\n\tQ_strncpy( target->targetname, props->targetname, sizeof( target->targetname ));\n\tVectorCopy(props->origin, target->origin);\n\n\tg_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){\n\t\t.class = Xvk_Target,\n\t\t.index = g_map_entities.num_targets,\n\t};\n\n\t++g_map_entities.num_targets;\n}\n\nstatic void readWorldspawn( const entity_props_t *props ) {\n\tQ_strncpy( g_map_entities.wadlist, props->wad, sizeof( g_map_entities.wadlist ));\n\tg_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){\n\t\t.class = Worldspawn,\n\t\t.index = -1,\n\t};\n}\n\nint R_VkRenderModeFromString( const char *s ) {\n#define CHECK_IF_MODE(mode) if (Q_strcmp(s, #mode) == 0) { return mode; }\n\t\tCHECK_IF_MODE(kRenderNormal)\n\t\telse CHECK_IF_MODE(kRenderTransColor)\n\t\telse CHECK_IF_MODE(kRenderTransTexture)\n\t\telse CHECK_IF_MODE(kRenderGlow)\n\t\telse CHECK_IF_MODE(kRenderTransAlpha)\n\t\telse CHECK_IF_MODE(kRenderTransAdd)\n\t\treturn -1;\n}\n\nstatic void readFuncAny( const entity_props_t *const props, uint32_t have_fields, int props_count ) {\n\tDEBUG(\"func_any entity=%d model=\\\"%s\\\", props_count=%d\", g_map_entities.entity_count, (have_fields & Field_model) ? props->model : \"N/A\", props_count);\n\n\tif (g_map_entities.func_any_count >= MAX_FUNC_ANY_ENTITIES) {\n\t\tERR(\"Too many func_any entities, max supported = %d\", MAX_FUNC_ANY_ENTITIES);\n\t\treturn;\n\t}\n\n\txvk_mapent_func_any_t *const e = g_map_entities.func_any + g_map_entities.func_any_count;\n\n\t*e = (xvk_mapent_func_any_t){0};\n\te->rendermode = -1;\n\n\tQ_strncpy( e->model, props->model, sizeof( e->model ));\n\n\tif (have_fields & Field_rendermode)\n\t\te->rendermode = props->rendermode;\n\n\t/* NOTE: not used\n\te->rendercolor.r = 255;\n\te->rendercolor.g = 255;\n\te->rendercolor.b = 255;\n\n\tif (have_fields & Field_renderamt)\n\t\te->renderamt = props->renderamt;\n\n\tif (have_fields & Field_renderfx)\n\t\te->renderfx = props->renderfx;\n\n\tif (have_fields & Field_rendercolor) {\n\t\te->rendercolor.r = props->rendercolor[0];\n\t\te->rendercolor.g = props->rendercolor[1];\n\t\te->rendercolor.b = props->rendercolor[2];\n\t}\n\t*/\n\n\te->entity_index = g_map_entities.entity_count;\n\tg_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){\n\t\t.class = FuncAny,\n\t\t.index = g_map_entities.func_any_count,\n\t};\n\t++g_map_entities.func_any_count;\n}\n\nstatic void addPatchSurface( const entity_props_t *props, uint32_t have_fields ) {\n\tconst model_t* const map = WORLDMODEL;\n\tconst int num_surfaces = map->numsurfaces;\n\tconst qboolean should_remove = (have_fields == Field__xvk_surface_id) || (have_fields & Field__xvk_material && props->_xvk_material[0] == '\\0');\n\n\tfor (int i = 0; i < props->_xvk_surface_id.num; ++i) {\n\t\tconst int index = props->_xvk_surface_id.values[i];\n\t\txvk_patch_surface_t *psurf = NULL;\n\t\tif (index < 0 || index >= num_surfaces) {\n\t\t\tERR(\"Incorrect patch for surface_index %d where numsurfaces=%d\", index, num_surfaces);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!g_patch.surfaces) {\n\t\t\tg_patch.surfaces = Mem_Malloc(vk_core.pool, num_surfaces * sizeof(xvk_patch_surface_t));\n\t\t\tg_patch.surfaces_count = num_surfaces;\n\t\t\tfor (int i = 0; i < num_surfaces; ++i) {\n\t\t\t\tg_patch.surfaces[i].flags = Patch_Surface_NoPatch;\n\t\t\t\tg_patch.surfaces[i].material_ref.index = -1;\n\t\t\t}\n\t\t}\n\n\t\tpsurf = g_patch.surfaces + index;\n\n\t\tif (should_remove) {\n\t\t\tDEBUG(\"Patch: surface %d removed\", index);\n\t\t\tpsurf->flags = Patch_Surface_Delete;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (have_fields & Field__xvk_material) {\n\t\t\tconst r_vk_material_ref_t mat = R_VkMaterialGetForName( props->_xvk_material );\n\t\t\tif (mat.index >= 0) {\n\t\t\t\tDEBUG(\"Patch for surface %d with material \\\"%s\\\" -> %d\", index, props->_xvk_material, mat.index);\n\t\t\t\tpsurf->material_ref = mat;\n\t\t\t\tpsurf->flags |= Patch_Surface_Material;\n\t\t\t} else {\n\t\t\t\tERR(\"Cannot patch surface %d with material \\\"%s\\\": material not found\", index, props->_xvk_material);\n\t\t\t}\n\t\t}\n\n\t\tif (have_fields & Field__light) {\n\t\t\tVectorScale(props->_light, 0.1f, psurf->emissive);\n\t\t\tpsurf->flags |= Patch_Surface_Emissive;\n\t\t\tDEBUG(\"Patch for surface %d: assign emissive %f %f %f\", index,\n\t\t\t\tpsurf->emissive[0],\n\t\t\t\tpsurf->emissive[1],\n\t\t\t\tpsurf->emissive[2]\n\t\t\t);\n\t\t}\n\n\t\t// Set default texture identity matrix\n\t\tVectorSet(psurf->texmat_s, 1, 0, 0);\n\t\tVectorSet(psurf->texmat_t, 0, 1, 0);\n\n\t\tif (have_fields & Field__xvk_tex_scale) {\n\t\t\tDEBUG(\"Patch for surface %d: assign tex_scale %f %f\",\n\t\t\t\tindex, props->_xvk_tex_scale[0], props->_xvk_tex_scale[1]);\n\n\t\t\tpsurf->texmat_s[0] = props->_xvk_tex_scale[0];\n\t\t\tpsurf->texmat_t[1] = props->_xvk_tex_scale[1];\n\t\t\tpsurf->flags |= Patch_Surface_TexMatrix;\n\n\t\t}\n\n\t\tif (have_fields & Field__xvk_tex_offset) {\n\t\t\tDEBUG(\"Patch for surface %d: assign tex_offset %f %f\",\n\t\t\t\tindex, props->_xvk_tex_offset[0], props->_xvk_tex_offset[1]);\n\n\t\t\tpsurf->texmat_s[2] = props->_xvk_tex_offset[0];\n\t\t\tpsurf->texmat_t[2] = props->_xvk_tex_offset[1];\n\t\t\tpsurf->flags |= Patch_Surface_TexMatrix;\n\t\t}\n\n\t\tif (have_fields & Field__xvk_tex_rotate) {\n\t\t\tconst float rad = props->_xvk_tex_rotate * M_PI / 180.;\n\t\t\tconst float co = cos(rad), si = sin(rad);\n\n\t\t\tconst float a0 = psurf->texmat_s[0], a1 = psurf->texmat_s[1];\n\t\t\tconst float a2 = psurf->texmat_t[0], a3 = psurf->texmat_t[1];\n\n\t\t\tconst float b0 = co, b1 = -si;\n\t\t\tconst float b2 = si, b3 = co;\n\n\t\t\tconst vec2_t s = {a0 * b0 + a1 * b2, a0 * b1 + a1 * b3};\n\t\t\tconst vec2_t t = {a2 * b0 + a3 * b2, a2 * b1 + a3 * b3};\n\n\t\t\tDEBUG(\"Patch for surface %d: rotate by %f degrees:\\n%f %f\\n%f %f\",\n\t\t\t\tindex, props->_xvk_tex_rotate,\n\t\t\t\ts[0], s[1],\n\t\t\t\tt[0], t[1]\n\t\t\t);\n\n\t\t\tpsurf->texmat_s[0] = s[0];\n\t\t\tpsurf->texmat_s[1] = s[1];\n\n\t\t\tpsurf->texmat_t[0] = t[0];\n\t\t\tpsurf->texmat_t[1] = t[1];\n\n\t\t\tpsurf->flags |= Patch_Surface_TexMatrix;\n\t\t}\n\t}\n}\n\nstatic void patchLightEntity( const entity_props_t *props, int ent_id, uint32_t have_fields, int index ) {\n\tASSERT(index >= 0);\n\tASSERT(index < g_map_entities.num_lights);\n\n\tvk_light_entity_t *const light = g_map_entities.lights + index;\n\n\tif (have_fields == Field__xvk_ent_id) {\n\t\tDEBUG(\"Deleting light entity (%d of %d) with index=%d\", index, g_map_entities.num_lights, ent_id);\n\n\t\t// Mark it as deleted\n\t\tlight->entity_index = -1;\n\t\treturn;\n\t}\n\n\tfillLightFromProps(light, props, have_fields, true, ent_id);\n}\n\nstatic void patchFuncAnyEntity( const entity_props_t *props, uint32_t have_fields, int index ) {\n\tASSERT(index >= 0);\n\tASSERT(index < g_map_entities.func_any_count);\n\txvk_mapent_func_any_t *const e = g_map_entities.func_any + index;\n\n\tif (have_fields & Field_origin) {\n\t\tVectorCopy(props->origin, e->origin);\n\t\te->origin_patched = true;\n\t\tDEBUG(\"Patching ent=%d func_any=%d %f %f %f\", e->entity_index, index, e->origin[0], e->origin[1], e->origin[2]);\n\t}\n\n\tif (have_fields & Field_rendermode) {\n\t\te->rendermode = props->rendermode;\n\t\te->rendermode_patched = true;\n\t\tDEBUG(\"Patching ent=%d func_any=%d rendermode=%d\", e->entity_index, index, e->rendermode);\n\t}\n\n\tif (have_fields & Field__xvk_smooth_entire_model) {\n\t\tDEBUG(\"Patching ent=%d func_any=%d smooth_entire_model =%d\", e->entity_index, index, props->_xvk_smooth_entire_model);\n\t\te->smooth_entire_model = props->_xvk_smooth_entire_model;\n\t}\n\n\tif (have_fields & Field__xvk_map_material) {\n\t\tconst char *s = props->_xvk_map_material;\n\t\twhile (*s) {\n\t\t\twhile (*s && isspace(*s)) ++s; // skip space\n\t\t\tconst char *from_begin = s;\n\t\t\twhile (*s && !isspace(*s)) ++s; // find first space or end\n\t\t\tconst int from_len = s - from_begin;\n\t\t\tif (!from_len)\n\t\t\t\tbreak;\n\n\t\t\twhile (*s && isspace(*s)) ++s; // skip space\n\t\t\tconst char *to_begin = s;\n\t\t\twhile (*s && !isspace(*s)) ++s; // find first space or end\n\t\t\tconst int to_len = s - to_begin;\n\t\t\tif (!to_len)\n\t\t\t\tbreak;\n\n\t\t\tstring from_tex, to_mat;\n\t\t\tQ_strncpy(from_tex, from_begin, Q_min(sizeof from_tex, from_len + 1));\n\t\t\tQ_strncpy(to_mat, to_begin, Q_min(sizeof to_mat, to_len + 1));\n\n\t\t\tconst int from_tex_index = R_TextureFindByNameLike(from_tex);\n\t\t\tconst r_vk_material_ref_t to_mat_ref = R_VkMaterialGetForName(to_mat);\n\n\t\t\tDEBUG(\"Adding mapping from tex \\\"%s\\\"(%d) to mat \\\"%s\\\"(%d) for entity=%d\",\n\t\t\t\tfrom_tex, from_tex_index, to_mat, to_mat_ref.index, e->entity_index);\n\n\t\t\tif (from_tex_index <= 0) {\n\t\t\t\tERR(\"When patching entity=%d couldn't find map-from texture \\\"%s\\\"\", e->entity_index, from_tex);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (to_mat_ref.index <= 0) {\n\t\t\t\tERR(\"When patching entity=%d couldn't find map-to material \\\"%s\\\"\", e->entity_index, to_mat);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (e->matmap_count == MAX_MATERIAL_MAPPINGS) {\n\t\t\t\tERR(\"Cannot map tex \\\"%s\\\"(%d) to mat \\\"%s\\\"(%d) for entity=%d: too many mappings, \"\n\t\t\t\t\t\t\"consider increasing MAX_MATERIAL_MAPPINGS\",\n\t\t\t\t\tfrom_tex, from_tex_index, to_mat, to_mat_ref.index, e->entity_index);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\te->matmap[e->matmap_count].from_tex = from_tex_index;\n\t\t\te->matmap[e->matmap_count].to_mat = to_mat_ref;\n\t\t\t++e->matmap_count;\n\t\t}\n\t}\n}\n\nstatic void patchEntity( const entity_props_t *props, uint32_t have_fields ) {\n\tASSERT(have_fields & Field__xvk_ent_id);\n\n\tfor (int i = 0; i < props->_xvk_ent_id.num; ++i) {\n\t\tconst int ei = props->_xvk_ent_id.values[i];\n\t\tif (ei < 0 || ei >= g_map_entities.entity_count) {\n\t\t\tERR(\"_xvk_ent_id value %d is out of bounds, max=%d\", ei, g_map_entities.entity_count);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst xvk_mapent_ref_t *const ref = g_map_entities.refs + ei;\n\t\tswitch (ref->class) {\n\t\t\tcase Light:\n\t\t\tcase LightSpot:\n\t\t\tcase LightEnvironment:\n\t\t\t\tpatchLightEntity(props, ei, have_fields, ref->index);\n\t\t\t\tbreak;\n\t\t\tcase FuncAny:\n\t\t\t\tpatchFuncAnyEntity(props, have_fields, ref->index);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tWARN(\"vk_mapents: trying to patch unsupported entity %d class %d\", ei, ref->class);\n\t\t}\n\t}\n}\n\nstatic void appendExcludedPairs(const entity_props_t *props) {\n\tif (props->_xvk_smoothing_excluded_pairs.num % 2 != 0) {\n\t\tERR(\"vk_mapents: smoothing group exclusion pairs list should be list of pairs -- divisible by 2; cutting the tail\");\n\t}\n\n\tint count = props->_xvk_smoothing_excluded_pairs.num & ~1;\n\tif (g_map_entities.smoothing.excluded_pairs_count + count > COUNTOF(g_map_entities.smoothing.excluded_pairs)) {\n\t\tERR(\"vk_mapents: smoothing exclusion pairs capacity exceeded, go complain in github issues\");\n\t\tcount = COUNTOF(g_map_entities.smoothing.excluded_pairs) - g_map_entities.smoothing.excluded_pairs_count;\n\t}\n\n\tmemcpy(g_map_entities.smoothing.excluded_pairs + g_map_entities.smoothing.excluded_pairs_count, props->_xvk_smoothing_excluded_pairs.values, count * sizeof(int));\n\n\tg_map_entities.smoothing.excluded_pairs_count += count;\n}\n\nstatic void appendExcludedSingles(const entity_props_t *props) {\n\tint count = props->_xvk_smoothing_excluded.num;\n\n\tif (g_map_entities.smoothing.excluded_count + count > COUNTOF(g_map_entities.smoothing.excluded)) {\n\t\tERR(\"vk_mapents: smoothing exclusion group capacity exceeded, go complain in github issues\");\n\t\tcount = COUNTOF(g_map_entities.smoothing.excluded) - g_map_entities.smoothing.excluded_count;\n\t}\n\n\tmemcpy(g_map_entities.smoothing.excluded + g_map_entities.smoothing.excluded_count, props->_xvk_smoothing_excluded.values, count * sizeof(int));\n\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\"Adding %d smoothing-excluded surfaces\", props->_xvk_smoothing_excluded.num);\n\t\tfor (int i = 0; i < props->_xvk_smoothing_excluded.num; ++i) {\n\t\t\tDEBUG(\"%d\", props->_xvk_smoothing_excluded.values[i]);\n\t\t}\n\t}\n\n\tg_map_entities.smoothing.excluded_count += count;\n}\n\nstatic void addSmoothingGroup(const entity_props_t *props) {\n\tif (g_map_entities.smoothing.groups_count == MAX_INCLUDED_SMOOTHING_GROUPS) {\n\t\tERR(\"vk_mapents: limit of %d smoothing groups reached\", MAX_INCLUDED_SMOOTHING_GROUPS);\n\t\treturn;\n\t}\n\n\txvk_smoothing_group_t *g = g_map_entities.smoothing.groups + (g_map_entities.smoothing.groups_count++);\n\n\tint count = props->_xvk_smoothing_group.num;\n\tif (count > MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP) {\n\t\tERR(\"vk_mapents: too many surfaces in a smoothing group. Max %d, got %d. Culling\", MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP, props->_xvk_smoothing_group.num);\n\t\tcount = MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP;\n\t}\n\n\tmemcpy(g->surfaces, props->_xvk_smoothing_group.values, sizeof(int) * count);\n\tg->count = count;\n}\n\nstatic void parseEntities( char *string, qboolean is_patch ) {\n\tunsigned have_fields = 0;\n\tint props_count = 0;\n\tentity_props_t values;\n\tchar *pos = string;\n\t//DEBUG(\"ENTITIES: %s\", pos);\n\tfor (;;) {\n\t\tchar key[1024];\n\t\tchar value[1024];\n\n\t\tpos = COM_ParseFile(pos, key, sizeof(key));\n\t\tASSERT(Q_strlen(key) < sizeof(key));\n\t\tif (!pos)\n\t\t\tbreak;\n\n\t\tif (key[0] == '{') {\n\t\t\thave_fields = None;\n\t\t\tvalues = (entity_props_t){0};\n\t\t\tprops_count = 0;\n\t\t\tg_map_entities.refs[g_map_entities.entity_count] = (xvk_mapent_ref_t){\n\t\t\t\t.class = Unknown,\n\t\t\t\t.index = -1,\n\t\t\t};\n\t\t\tcontinue;\n\t\t} else if (key[0] == '}') {\n\t\t\tconst int target_fields = Field_targetname | Field_origin;\n\t\t\tif ((have_fields & target_fields) == target_fields)\n\t\t\t\taddTargetEntity( &values );\n\t\t\tswitch (values.classname) {\n\t\t\t\tcase Light:\n\t\t\t\tcase LightSpot:\n\t\t\t\tcase LightEnvironment:\n\t\t\t\t\taddLightEntity( &values, have_fields );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Worldspawn:\n\t\t\t\t\treadWorldspawn( &values );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FuncAny:\n\t\t\t\t\treadFuncAny( &values, have_fields, props_count );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase Unknown:\n\t\t\t\t\tif (is_patch) {\n\t\t\t\t\t\tif (have_fields & Field__xvk_surface_id) {\n\t\t\t\t\t\t\taddPatchSurface( &values, have_fields );\n\t\t\t\t\t\t} else if (have_fields & Field__xvk_ent_id) {\n\t\t\t\t\t\t\tpatchEntity( &values, have_fields );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (have_fields & Field__xvk_smoothing_threshold) {\n\t\t\t\t\t\t\t\tg_map_entities.smoothing.threshold = cosf(DEG2RAD(values._xvk_smoothing_threshold));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (have_fields & Field__xvk_smoothing_excluded_pairs) {\n\t\t\t\t\t\t\t\tappendExcludedPairs(&values);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (have_fields & Field__xvk_smoothing_excluded) {\n\t\t\t\t\t\t\t\tappendExcludedSingles(&values);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (have_fields & Field__xvk_smoothing_group) {\n\t\t\t\t\t\t\t\taddSmoothingGroup(&values);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (have_fields & Field__xvk_remove_all_sky_surfaces) {\n\t\t\t\t\t\t\t\tDEBUG(\"_xvk_remove_all_sky_surfaces=%d\", values._xvk_remove_all_sky_surfaces);\n\t\t\t\t\t\t\t\tg_map_entities.remove_all_sky_surfaces = values._xvk_remove_all_sky_surfaces;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase Ignored:\n\t\t\t\tcase Xvk_Target:\n\t\t\t\t\t// Skip\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tg_map_entities.entity_count++;\n\t\t\tif (g_map_entities.entity_count == MAX_MAP_ENTITIES) {\n\t\t\t\tERR(\"vk_mapents: too many entities, skipping the rest\");\\\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tpos = COM_ParseFile(pos, value, sizeof(value));\n\t\tASSERT(Q_strlen(value) < sizeof(value));\n\t\tif (!pos)\n\t\t\tbreak;\n\n#define READ_FIELD(num, type, name, kind) \\\n\t\tif (Q_strcmp(key, #name) == 0) { \\\n\t\t\tconst unsigned bit = parseEntProp##kind(value, &values.name, Field_##name); \\\n\t\t\tif (bit == 0) { \\\n\t\t\t\tERR(\"Error parsing entity property \" #name \", invalid value: %s\", value); \\\n\t\t\t} else have_fields |= bit; \\\n\t\t} else\n\t\tENT_PROP_LIST(READ_FIELD)\n\t\t{\n\t\t\t//DEBUG(\"Unknown field %s with value %s\", key, value);\n\t\t}\n\t\t++props_count;\n#undef CHECK_FIELD\n\t}\n}\n\nstatic const xvk_mapent_target_t *findTargetByName(const char *name) {\n\tfor (int i = 0; i < g_map_entities.num_targets; ++i) {\n\t\tconst xvk_mapent_target_t *target = g_map_entities.targets + i;\n\t\tif (Q_strcmp(name, target->targetname) == 0)\n\t\t\treturn target;\n\t}\n\n\treturn NULL;\n}\n\nstatic void orientSpotlights( void ) {\n\t// Patch spotlight directions based on target entities\n\tfor (int i = 0; i < g_map_entities.num_lights; ++i) {\n\t\tvk_light_entity_t *const light = g_map_entities.lights + i;\n\t\tconst xvk_mapent_target_t *target;\n\n\t\tif (light->type != LightTypeSpot && light->type != LightTypeEnvironment)\n\t\t\tcontinue;\n\n\t\tif (light->target_entity[0] == '\\0')\n\t\t\tcontinue;\n\n\t\ttarget = findTargetByName(light->target_entity);\n\t\tif (!target) {\n\t\t\tERR(\"Couldn't find target entity '%s' for spot light %d\", light->target_entity, i);\n\t\t\tcontinue;\n\t\t}\n\n\t\tVectorSubtract(target->origin, light->origin, light->dir);\n\t\tVectorNormalize(light->dir);\n\n\t\tDEBUG(\"Light %d patched direction towards '%s': %f %f %f\", i, target->targetname,\n\t\t\tlight->dir[0], light->dir[1], light->dir[2]);\n\t}\n}\n\nstatic void parsePatches( const model_t *const map ) {\n\tchar filename[256];\n\tbyte *data;\n\n\tif (g_patch.surfaces) {\n\t\tMem_Free(g_patch.surfaces);\n\t\tg_patch.surfaces = NULL;\n\t\tg_patch.surfaces_count = 0;\n\t}\n\n\t{\n\t\tconst char *ext = NULL;\n\n\t\t// Find extension (if any)\n\t\t{\n\t\t\tconst char *p = map->name;\n\t\t\tfor(; *p; ++p)\n\t\t\t\tif (*p == '.')\n\t\t\t\t\text = p;\n\t\t\tif (!ext)\n\t\t\t\text = p;\n\t\t}\n\n\t\tQ_snprintf(filename, sizeof(filename), \"luchiki/%.*s.patch\", (int)(ext - map->name), map->name);\n\t}\n\n\tDEBUG(\"Loading patches from file \\\"%s\\\"\", filename);\n\tdata = gEngine.fsapi->LoadFile( filename, 0, false );\n\tif (!data) {\n\t\tDEBUG(\"No patch file \\\"%s\\\"\", filename);\n\t\treturn;\n\t}\n\n\tparseEntities( (char*)data, true );\n\tMem_Free(data);\n}\n\nvoid XVK_ParseMapEntities( void ) {\n\tconst model_t* const map = WORLDMODEL;\n\n\tASSERT(map);\n\n\tg_map_entities.num_targets = 0;\n\tg_map_entities.num_lights = 0;\n\tg_map_entities.single_environment_index = NoEnvironmentLights;\n\tg_map_entities.entity_count = 0;\n\tg_map_entities.func_any_count = 0;\n\tg_map_entities.smoothing.threshold = cosf(DEG2RAD(45.f));\n\tg_map_entities.smoothing.excluded_pairs_count = 0;\n\tg_map_entities.smoothing.excluded_count = 0;\n\tfor (int i = 0; i < g_map_entities.smoothing.groups_count; ++i)\n\t\tg_map_entities.smoothing.groups[i].count = 0;\n\tg_map_entities.smoothing.groups_count = 0;\n\tg_map_entities.remove_all_sky_surfaces = 0;\n\n\tparseEntities( map->entities, false );\n\torientSpotlights();\n}\n\nvoid XVK_ParseMapPatches( void ) {\n\tconst model_t* const map = WORLDMODEL;\n\n\tparsePatches( map );\n\n\t// Perform light deletion and compaction\n\t{\n\t\tint w = 0;\n\t\tfor (int r = 0; r < g_map_entities.num_lights; ++r) {\n\t\t\t// Deleted\n\t\t\tif (g_map_entities.lights[r].entity_index < 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (r != w)\n\t\t\t\tmemcpy(g_map_entities.lights + w, g_map_entities.lights + r, sizeof(vk_light_entity_t));\n\t\t\t++w;\n\t\t}\n\n\t\tg_map_entities.num_lights = w;\n\t}\n\n\torientSpotlights();\n}\n\nconst xvk_patch_surface_t* R_VkPatchGetSurface( int surface_index ) {\n\tif (!g_patch.surfaces_count)\n\t\treturn NULL;\n\n\tASSERT(g_patch.surfaces);\n\tASSERT(surface_index >= 0);\n\tASSERT(surface_index < g_patch.surfaces_count);\n\n\treturn g_patch.surfaces + surface_index;\n}\n"
  },
  {
    "path": "ref/vk/vk_mapents.h",
    "content": "#pragma once\n#include \"vk_materials.h\"\n\n#include \"xash3d_types.h\"\n#include \"const.h\" // typedef word, needed for bspfile.h\n#include \"bspfile.h\" // MAX_MAP_ENTITIES\n\n// TODO string_view instead of string. map entities string/buffer is supposed to be alive for the entire map duration\n// NOTE that the above is not true for string in patches. but we can change that in parsePatches\n\n#define ENT_PROP_LIST(X) \\\n\tX(0, vec3_t, origin, Vec3) \\\n\tX(1, vec3_t, angles, Vec3) \\\n\tX(2, float, pitch, Float) \\\n\tX(3, vec3_t, _light, Rgbav) \\\n\tX(4, class_name_e, classname, Classname) \\\n\tX(5, float, angle, Float) \\\n\tX(6, float, _cone, Float) \\\n\tX(7, float, _cone2, Float) \\\n\tX(8, int, _sky, Int) \\\n\tX(9, string, wad, WadList) \\\n\tX(10, string, targetname, String) \\\n\tX(11, string, target, String) \\\n\tX(12, int, style, Int) \\\n\tX(13, int_array_t, _xvk_surface_id, IntArray) \\\n\tX(14, string, _xvk_material, String) \\\n\tX(15, int_array_t, _xvk_ent_id, IntArray) \\\n\tX(16, float, _xvk_radius, Float) \\\n\tX(17, vec2_t, _xvk_tex_offset, Vec2) \\\n\tX(18, vec2_t, _xvk_tex_scale, Vec2) \\\n\tX(19, string, model, String) \\\n\tX(20, float, _xvk_smoothing_threshold, Float) \\\n\tX(21, int_array_t, _xvk_smoothing_excluded_pairs, IntArray) \\\n\tX(22, int_array_t, _xvk_smoothing_group, IntArray) \\\n\tX(23, string, _xvk_map_material, String) \\\n\tX(24, int, rendermode, Int) \\\n\tX(25, int, _xvk_smooth_entire_model, Int) \\\n\tX(26, int_array_t, _xvk_smoothing_excluded, IntArray) \\\n\tX(27, float, _xvk_tex_rotate, Float) \\\n\tX(28, int, _xvk_remove_all_sky_surfaces, Int) \\\n\tX(29, float, _xvk_solid_angle, Float) \\\n\n/* NOTE: not used\n\tX(23, int, renderamt, Int) \\\n\tX(24, vec3_t, rendercolor, Vec3) \\\n\tX(25, int, renderfx, Int) \\\n\tX(26, vec3_t, _xvk_offset, Vec3) \\\n*/\n\ntypedef enum {\n\tUnknown = 0,\n\tLight,\n\tLightSpot,\n\tLightEnvironment,\n\tWorldspawn,\n\tFuncAny,\n\tIgnored,\n\tXvk_Target,\n} class_name_e;\n\n#define MAX_INT_ARRAY_SIZE 64\n\ntypedef struct {\n\tint num;\n\tint values[MAX_INT_ARRAY_SIZE];\n} int_array_t;\n\ntypedef struct {\n#define DECLARE_FIELD(num, type, name, kind) type name;\n\tENT_PROP_LIST(DECLARE_FIELD)\n#undef DECLARE_FIELD\n} entity_props_t;\n\ntypedef enum {\n\tNone = 0,\n#define DECLARE_FIELD(num, type, name, kind) Field_##name = (1<<num),\n\tENT_PROP_LIST(DECLARE_FIELD)\n#undef DECLARE_FIELD\n} fields_read_e;\n\ntypedef enum { LightTypePoint, LightTypeSurface, LightTypeSpot, LightTypeEnvironment} LightType;\n\ntypedef struct {\n\tint entity_index;\n\tLightType type;\n\n\tvec3_t origin;\n\tvec3_t color;\n\tvec3_t dir;\n\n\tfloat radius;\n\tfloat solid_angle; // for LightEnvironment\n\n\tint style;\n\tfloat stopdot, stopdot2;\n\n\tstring target_entity;\n} vk_light_entity_t;\n\ntypedef struct {\n\tstring targetname;\n\tvec3_t origin;\n} xvk_mapent_target_t;\n\n#define MAX_MAPENT_TARGETS 256\n\ntypedef struct {\n\tint entity_index;\n\tstring model;\n\tvec3_t origin;\n\n#define MAX_MATERIAL_MAPPINGS 8\n\tint matmap_count;\n\tstruct {\n\t\tint from_tex;\n\t\tr_vk_material_ref_t to_mat;\n\t} matmap[MAX_MATERIAL_MAPPINGS];\n\n\tint rendermode;\n\n\tqboolean smooth_entire_model;\n\n\t/* NOTE: not used. Might be needed for #118 in the future.\n\tint renderamt, renderfx;\n\tcolor24 rendercolor;\n\n\tstruct cl_entity_s *ent;\n\t*/\n\n\t// TODO flags\n\tqboolean origin_patched;\n\tqboolean rendermode_patched;\n} xvk_mapent_func_any_t;\n\ntypedef struct {\n\tclass_name_e class;\n\tint index;\n} xvk_mapent_ref_t;\n\n#define MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP 16\ntypedef struct {\n\tint count;\n\tint surfaces[MAX_INCLUDED_SMOOTHING_SURFACES_IN_A_GROUP];\n} xvk_smoothing_group_t;\n\ntypedef struct {\n\tint num_lights;\n\tvk_light_entity_t lights[256];\n\n\tint single_environment_index;\n\tint entity_count;\n\n\tstring wadlist;\n\n\tint num_targets;\n\txvk_mapent_target_t targets[MAX_MAPENT_TARGETS];\n\n#define MAX_FUNC_ANY_ENTITIES 1024\n\tint func_any_count;\n\txvk_mapent_func_any_t func_any[MAX_FUNC_ANY_ENTITIES];\n\n\t// TODO find out how to read this from the engine, or make its size dynamic\n//#define MAX_MAP_ENTITIES 2048\n\txvk_mapent_ref_t refs[MAX_MAP_ENTITIES];\n\n\tstruct {\n\t\tfloat threshold;\n\n#define MAX_EXCLUDED_SMOOTHING_SURFACES_PAIRS 32\n\t\tint excluded_pairs[MAX_EXCLUDED_SMOOTHING_SURFACES_PAIRS * 2];\n\t\tint excluded_pairs_count;\n\n#define MAX_INCLUDED_SMOOTHING_GROUPS 32\n\t\tint groups_count;\n\t\txvk_smoothing_group_t groups[MAX_INCLUDED_SMOOTHING_GROUPS];\n\n#define MAX_EXCLUDED_SMOOTHING_SURFACES 256\n\t\tint excluded_count;\n\t\tint excluded[MAX_EXCLUDED_SMOOTHING_SURFACES];\n\t} smoothing;\n\n\tqboolean remove_all_sky_surfaces;\n} xvk_map_entities_t;\n\n// TODO expose a bunch of things here as funtions, not as internal structures\nextern xvk_map_entities_t g_map_entities;\n\nenum { NoEnvironmentLights = -1, MoreThanOneEnvironmentLight = -2 };\n\nvoid XVK_ParseMapEntities( void );\nvoid XVK_ParseMapPatches( void );\n\nenum {\n\tPatch_Surface_NoPatch = 0,\n\tPatch_Surface_Delete = (1<<0),\n\tPatch_Surface_Material = (1<<1),\n\tPatch_Surface_Emissive = (1<<2),\n\tPatch_Surface_TexMatrix = (1<<3),\n};\n\nstruct texture_s;\n\ntypedef struct {\n\tuint32_t flags;\n\n\tr_vk_material_ref_t material_ref;\n\n\tvec3_t emissive;\n\n\t// Texture coordinate patches\n\tvec3_t texmat_s, texmat_t;\n} xvk_patch_surface_t;\n\nconst xvk_patch_surface_t* R_VkPatchGetSurface( int surface_index );\n\n// -1 if failed\nint R_VkRenderModeFromString( const char *s );\n"
  },
  {
    "path": "ref/vk/vk_materials.c",
    "content": "#include \"vk_materials.h\"\n#include \"vk_textures.h\"\n#include \"vk_mapents.h\"\n#include \"vk_const.h\"\n#include \"std/profiler.h\"\n#include \"vk_logs.h\"\n#include \"std/unordered_roadmap.h\"\n\n#include <stdio.h>\n\n#define LOG_MODULE mat\n\n#define MAX_INCLUDE_DEPTH 4\n\n#define MAX_MATERIALS 2048\n#define MAX_NEW_MATERIALS 128\n\nstatic r_vk_material_t k_default_material = {\n\t.tex_base_color = -1,\n\t.tex_metalness = 0,\n\t.tex_roughness = 0,\n\t.tex_normalmap = 0, // 0 means no normal map, checked in shaders\n\n\t.metalness = 0.f,\n\t.roughness = 1.f,\n\t.normal_scale = 1.f,\n\t.base_color = { 1.f, 1.f, 1.f, 1.f },\n};\n\n/* TODO\nenum {\n#define X(bit, type, name, key, func) kMatField_##key = (1 << (bit)),\nMATERIAL_FIELDS_LIST(X)\n#undef X\n};\n*/\n\n#define MAX_RENDERMODE_MATERIALS 32\ntypedef struct {\n\t\tstruct {\n\t\t\tint tex_id;\n\t\t\tr_vk_material_ref_t mat;\n\t\t} map[MAX_RENDERMODE_MATERIALS];\n\t\tint count;\n} r_vk_material_per_mode_t;\n\nenum {\n\tkMaterialNotChecked = 0,\n\tkMaterialNoReplacement = -1,\n};\n\ntypedef struct {\n\tint mat_id;\n\n\t// TODO rendermode chain\n} texture_to_material_t;\n\ntypedef struct {\n\t//int for_tex_id;\n\tstring name;\n\n\tr_vk_material_t material;\n} material_entry_t;\n\ntypedef struct {\n\turmom_header_t hdr_;\n\tint mat_id; // into g_materials.table\n} material_name_map_t;\n\nstatic struct {\n\tint count;\n\tmaterial_entry_t table[MAX_MATERIALS];\n\n\ttexture_to_material_t tex_to_mat[MAX_TEXTURES];\n\n\t// TODO embed into tex_to_mat\n\tr_vk_material_per_mode_t for_rendermode[kRenderTransAdd+1];\n\n\turmom_desc_t map_desc;\n\tmaterial_name_map_t map[MAX_NEW_MATERIALS];\n} g_materials;\n\nstatic struct {\n\tint mat_files_read;\n\tint texture_lookups;\n\tint texture_loads;\n\tuint64_t material_file_read_duration_ns;\n\tuint64_t texture_lookup_duration_ns;\n\tuint64_t texture_load_duration_ns;\n} g_stats;\n\nstatic int loadTexture( const char *filename, qboolean force_reload, colorspace_hint_e colorspace ) {\n\tconst uint64_t load_begin_ns = aprof_time_now_ns();\n\tconst int tex_id = R_TextureUploadFromFileExAcquire( filename, colorspace, force_reload );\n\tDEBUG(\"Loaded texture %s => %d\", filename, tex_id);\n\tg_stats.texture_loads++;\n\tg_stats.texture_load_duration_ns += aprof_time_now_ns() - load_begin_ns;\n\treturn tex_id ? tex_id : -1;\n}\n\nstatic void makePath(char *out, size_t out_size, const char *value, const char *path_begin, const char *path_end) {\n\tif (value[0] == '/') {\n\t\t// Path relative to valve/pbr dir\n\t\tQ_snprintf(out, out_size, \"pbr%s\", value);\n\t} else {\n\t\t// Path relative to current material.mat file\n\t\tQ_snprintf(out, out_size, \"%.*s%s\", (int)(path_end - path_begin), path_begin, value);\n\t}\n}\n\n#define MAKE_PATH(out, value) \\\n\tmakePath(out, sizeof(out), value, path_begin, path_end)\n\nstatic void printMaterial(int index) {\n\tconst char* const name = g_materials.table[index].name;\n\tconst r_vk_material_t* const mat = &g_materials.table[index].material;\n\n\tDEBUG(\"material[%d] \\\"%s\\\" (tbc=%d, tr=%d, tm=%d, tn=%d bc=(%.03f,%.03f,%.03f,%.03f) r=%.03f m=%.03f ns=%.03f\",\n\t\tindex, name,\n\t\tmat->tex_base_color, mat->tex_roughness, mat->tex_metalness, mat->tex_normalmap,\n\t\tmat->base_color[0], mat->base_color[1], mat->base_color[2], mat->base_color[3],\n\t\tmat->roughness, mat->metalness, mat->normal_scale\n\t\t);\n}\n\nstatic void acquireTexturesForMaterial( int index ) {\n\tconst r_vk_material_t *mat = &g_materials.table[index].material;\n\tDEBUG(\"%s(%d: %s)\", __FUNCTION__, index, g_materials.table[index].name);\n\tif (mat->tex_base_color > 0)\n\t\tR_TextureAcquire(mat->tex_base_color);\n\tR_TextureAcquire(mat->tex_metalness);\n\tR_TextureAcquire(mat->tex_roughness);\n\tif (mat->tex_normalmap > 0)\n\t\tR_TextureAcquire(mat->tex_normalmap);\n}\n\nstatic void releaseTexturesForMaterialPtr( const r_vk_material_t *mat ) {\n\tif (mat->tex_base_color > 0)\n\t\tR_TextureRelease(mat->tex_base_color);\n\tR_TextureRelease(mat->tex_metalness);\n\tR_TextureRelease(mat->tex_roughness);\n\tif (mat->tex_normalmap > 0)\n\t\tR_TextureRelease(mat->tex_normalmap);\n}\n\nstatic void releaseTexturesForMaterial( int index ) {\n\tconst r_vk_material_t *mat = &g_materials.table[index].material;\n\tDEBUG(\"%s(%d: %s)\", __FUNCTION__, index, g_materials.table[index].name);\n\treleaseTexturesForMaterialPtr( mat );\n}\n\nstatic int addMaterial(const char *name, const r_vk_material_t* mat) {\n\tif (g_materials.count == MAX_MATERIALS) {\n\t\tERR(\"Max count of materials %d reached\", MAX_MATERIALS);\n\t\treturn -1;\n\t}\n\n\tQ_strncpy(g_materials.table[g_materials.count].name, name, sizeof g_materials.table[g_materials.count].name);\n\tg_materials.table[g_materials.count].material = *mat;\n\tacquireTexturesForMaterial(g_materials.count);\n\n\tprintMaterial(g_materials.count);\n\n\tASSERT(mat->tex_base_color >= 0);\n\tASSERT(mat->tex_metalness >= 0);\n\tASSERT(mat->tex_roughness >= 0);\n\tASSERT(mat->tex_normalmap >= 0);\n\n\treturn g_materials.count++;\n}\n\nstatic void assignMaterialForTexture(const char *name, int for_tex_id, int mat_id) {\n\tconst char* const tex_name = R_TextureGetNameByIndex(for_tex_id);\n\tDEBUG(\"Assigning material \\\"%s\\\" for_tex_id=\\\"%s\\\"(%d)\", name, tex_name, for_tex_id);\n\n\tASSERT(mat_id >= 0);\n\tASSERT(mat_id < g_materials.count);\n\n\tASSERT(for_tex_id < COUNTOF(g_materials.tex_to_mat));\n\ttexture_to_material_t* const t2m = g_materials.tex_to_mat + for_tex_id;\n\n\tif (t2m->mat_id == kMaterialNoReplacement) {\n\t\tERR(\"Texture \\\"%s\\\"(%d) has been already queried by something. Only future queries will get the new material\", tex_name, for_tex_id);\n\t} else if (t2m->mat_id != kMaterialNotChecked) {\n\t\tERR(\"Texture \\\"%s\\\"(%d) already has material assigned, will replace\", tex_name, for_tex_id);\n\t}\n\n\tt2m->mat_id = mat_id;\n}\n\nstatic void loadMaterialsFromFile( const char *filename, int depth ) {\n\tconst uint64_t load_file_begin_ns = aprof_time_now_ns();\n\tbyte *data = gEngine.fsapi->LoadFile( filename, 0, false );\n\tg_stats.material_file_read_duration_ns +=  aprof_time_now_ns() - load_file_begin_ns;\n\n\tr_vk_material_t current_material = k_default_material;\n\tint for_tex_id = -1;\n\tqboolean force_reload = false;\n\tqboolean create = false;\n\tqboolean metalness_set = false;\n\n\tstring name;\n\tstring basecolor_map, normal_map, metal_map, roughness_map;\n\t//uint32_t fields;\n\n\tint rendermode = 0;\n\n\tDEBUG(\"Loading materials from %s (exists=%d)\", filename, data != 0);\n\n\tif ( !data )\n\t\treturn;\n\n\tconst char *const path_begin = filename;\n\tconst char *path_end = Q_strrchr(filename, '/');\n\tif ( !path_end )\n\t\tpath_end = path_begin;\n\telse\n\t\tpath_end++;\n\n\tchar *pos = (char*)data;\n\tfor (;;) {\n\t\tchar key[1024];\n\t\tchar value[1024];\n\n\t\tconst char *const line_begin = pos;\n\t\tpos = COM_ParseFile(pos, key, sizeof(key));\n\t\tASSERT(Q_strlen(key) < sizeof(key));\n\t\tif (!pos)\n\t\t\tbreak;\n\n\t\tif (key[0] == '{') {\n\t\t\tcurrent_material = k_default_material;\n\t\t\tfor_tex_id = -1;\n\t\t\tforce_reload = false;\n\t\t\tcreate = false;\n\t\t\tmetalness_set = false;\n\t\t\tname[0] = basecolor_map[0] = normal_map[0] = metal_map[0] = roughness_map[0] = '\\0';\n\t\t\trendermode = 0;\n\t\t\t//fields = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (key[0] == '}') {\n\t\t\tif (for_tex_id <= 0 && !create) {\n\t\t\t\t// Skip this material, as its texture hasn't been loaded\n\t\t\t\t// NOTE: might want to check whether it makes sense wrt late-loading stuff\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (name[0] == '\\0') {\n\t\t\t\tWARN(\"Unreferenceable (no \\\"for_texture\\\", no \\\"new\\\") material found in %s\", filename);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If basecolor_map wasn't inherited\n\t\t\tif (current_material.tex_base_color < 0) {\n\t\t\t// Start with *default texture for base color, it will be acquired if no replacement is specified or could be loaded.\n\t\t\t\tcurrent_material.tex_base_color = for_tex_id >= 0 ? for_tex_id : 0;\n\t\t\t}\n\n#define LOAD_TEXTURE_FOR(name, field, colorspace) \\\n\t\t\tdo { \\\n\t\t\t\tif (name[0] != '\\0') { \\\n\t\t\t\t\tchar texture_path[256]; \\\n\t\t\t\t\tMAKE_PATH(texture_path, name); \\\n\t\t\t\t\tconst int tex_id = loadTexture(texture_path, force_reload, colorspace); \\\n\t\t\t\t\tif (tex_id < 0) { \\\n\t\t\t\t\t\tERR(\"Failed to load texture \\\"%s\\\" for \"#name\"\", name); \\\n\t\t\t\t\t\tif (current_material.field > 0) \\\n\t\t\t\t\t\t\tR_TextureAcquire(current_material.field); \\\n\t\t\t\t\t} else { \\\n\t\t\t\t\t\tcurrent_material.field = tex_id; \\\n\t\t\t\t\t} \\\n\t\t\t\t} else { \\\n\t\t\t\t\tif (current_material.field > 0) \\\n\t\t\t\t\t\tR_TextureAcquire(current_material.field); \\\n\t\t\t\t} \\\n\t\t\t} while(0)\n\n\t\t\tLOAD_TEXTURE_FOR(basecolor_map, tex_base_color, kColorspaceNative);\n\t\t\tLOAD_TEXTURE_FOR(normal_map, tex_normalmap, kColorspaceLinear);\n\t\t\tLOAD_TEXTURE_FOR(metal_map, tex_metalness, kColorspaceLinear);\n\t\t\tLOAD_TEXTURE_FOR(roughness_map, tex_roughness, kColorspaceLinear);\n\n\t\t\tif (!metalness_set && current_material.tex_metalness != tglob.whiteTexture) {\n\t\t\t\t// If metalness factor wasn't set explicitly, but texture was specified, set it to match the texture value.\n\t\t\t\tcurrent_material.metalness = 1.f;\n\t\t\t}\n\n\t\t\tconst int mat_id = addMaterial(name, &current_material);\n\n\t\t\treleaseTexturesForMaterialPtr(&current_material);\n\n\t\t\tif (mat_id < 0) {\n\t\t\t\tERR(\"Cannot add material \\\"%s\\\" for_tex_id=\\\"%s\\\"(%d)\", name, for_tex_id >= 0 ? R_TextureGetNameByIndex(for_tex_id) : \"N/A\", for_tex_id);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (create)\n\t\t\t{\n\t\t\t\tconst urmom_insert_t insert = urmomInsert(&g_materials.map_desc, name);\n\t\t\t\tif (insert.index < 0) {\n\t\t\t\t\tERR(\"Cannot add new material '%s', ran out of space (max=%d)\", name, MAX_NEW_MATERIALS);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tmaterial_name_map_t *const item = g_materials.map + insert.index;\n\n\t\t\t\tif (!insert.created)\n\t\t\t\t\tWARN(\"Replacing material '%s'@%d %d=>%d\", name, insert.index, item->mat_id, mat_id);\n\t\t\t\telse\n\t\t\t\t\tDEBUG(\"Mapping new material '%s'@%d => %d\", name, insert.index, mat_id);\n\n\t\t\t\titem->mat_id = mat_id;\n\t\t\t}\n\n\t\t\t// Assign from-texture mapping if there's a texture\n\t\t\tif (for_tex_id >= 0) {\n\t\t\t\t// Assign rendermode-specific materials\n\t\t\t\tif (rendermode > 0) {\n\t\t\t\t\tconst char* const tex_name = R_TextureGetNameByIndex(for_tex_id);\n\t\t\t\t\tDEBUG(\"Adding material \\\"%s\\\" for_tex_id=\\\"%s\\\"(%d) for rendermode %d\", name, tex_name, for_tex_id, rendermode);\n\n\t\t\t\t\tr_vk_material_per_mode_t* const rm = g_materials.for_rendermode + rendermode;\n\t\t\t\t\tif (rm->count == COUNTOF(rm->map)) {\n\t\t\t\t\t\tERR(\"Too many rendermode/tex_id mappings\");\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\trm->map[rm->count].tex_id = for_tex_id;\n\t\t\t\t\trm->map[rm->count].mat.index = mat_id;\n\t\t\t\t\trm->count++;\n\t\t\t\t} else {\n\t\t\t\t\tassignMaterialForTexture(name, for_tex_id, mat_id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t} // if (key[0] == '}') -- closing material block\n\n\t\tpos = COM_ParseFile(pos, value, sizeof(value));\n\t\tif (!pos)\n\t\t\tbreak;\n\n\t\t//DEBUG(\"key=\\\"%s\\\", value=\\\"%s\\\"\", key, value);\n\n\t\tif (Q_stricmp(key, \"for\") == 0) {\n\t\t\tif (name[0] != '\\0')\n\t\t\t\tWARN(\"Material already has \\\"new\\\" or \\\"for_texture\\\" old=\\\"%s\\\" new=\\\"%s\\\"\", name, value);\n\n\t\t\tconst uint64_t lookup_begin_ns = aprof_time_now_ns();\n\t\t\tfor_tex_id = R_TextureFindByNameLike(value);\n\t\t\tDEBUG(\"R_TextureFindByNameLike(%s)=%d\", value, for_tex_id);\n\t\t\tif (for_tex_id >= 0)\n\t\t\t\tASSERT(Q_stristr(R_TextureGetNameByIndex(for_tex_id), value) != NULL);\n\t\t\tg_stats.texture_lookup_duration_ns += aprof_time_now_ns() - lookup_begin_ns;\n\t\t\tg_stats.texture_lookups++;\n\t\t\tQ_strncpy(name, value, sizeof name);\n\t\t} else if (Q_stricmp(key, \"new\") == 0) {\n\t\t\tif (name[0] != '\\0')\n\t\t\t\tWARN(\"Material already has \\\"new\\\" or \\\"for_texture\\\" old=\\\"%s\\\" new=\\\"%s\\\"\", name, value);\n\t\t\tQ_strncpy(name, value, sizeof name);\n\t\t\tcreate = true;\n\t\t} else if (Q_stricmp(key, \"force_reload\") == 0) {\n\t\t\tforce_reload = Q_atoi(value) != 0;\n\t\t} else if (Q_stricmp(key, \"include\") == 0) {\n\t\t\tif (depth > 0) {\n\t\t\t\tchar include_path[256];\n\t\t\t\tMAKE_PATH(include_path, value);\n\t\t\t\tloadMaterialsFromFile( include_path, depth - 1);\n\t\t\t} else {\n\t\t\t\tERR(\"material: max include depth %d reached when including '%s' from '%s'\", MAX_INCLUDE_DEPTH, value, filename);\n\t\t\t}\n\t\t} else {\n\t\t\tif (Q_stricmp(key, \"basecolor_map\") == 0) {\n\t\t\t\tQ_strncpy(basecolor_map, value, sizeof(basecolor_map));\n\t\t\t\t//fields |= kMatField_basecolor_map;\n\t\t\t} else if (Q_stricmp(key, \"normal_map\") == 0) {\n\t\t\t\tQ_strncpy(normal_map, value, sizeof(normal_map));\n\t\t\t\t//fields |= kMatField_normal_map;\n\t\t\t} else if (Q_stricmp(key, \"metal_map\") == 0) {\n\t\t\t\tQ_strncpy(metal_map, value, sizeof(metal_map));\n\t\t\t\t//fields |= kMatField_metal_map;\n\t\t\t} else if (Q_stricmp(key, \"roughness_map\") == 0) {\n\t\t\t\tQ_strncpy(roughness_map, value, sizeof(roughness_map));\n\t\t\t\t//fields |= kMatField_roughness_map;\n\t\t\t} else if (Q_stricmp(key, \"inherit\") == 0 || Q_stricmp(key, \"use\") == 0) {\n\t\t\t\tconst r_vk_material_ref_t ref = R_VkMaterialGetForName(value);\n\t\t\t\tif (ref.index < 0) {\n\t\t\t\t\tERR(\"In material \\\"%s\\\" cannot find material \\\"%s\\\" to inherit\", name, value);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst r_vk_material_t inherited = R_VkMaterialGetForRef(ref);\n\t\t\t\tcurrent_material = inherited;\n\t\t\t} else if (Q_stricmp(key, \"roughness\") == 0) {\n\t\t\t\tsscanf(value, \"%f\", &current_material.roughness);\n\t\t\t\t//fields |= kMatField_roughness;\n\t\t\t} else if (Q_stricmp(key, \"metalness\") == 0) {\n\t\t\t\tsscanf(value, \"%f\", &current_material.metalness);\n\t\t\t\t//fields |= kMatField_metalness;\n\t\t\t\tmetalness_set = true;\n\t\t\t} else if (Q_stricmp(key, \"normal_scale\") == 0) {\n\t\t\t\tsscanf(value, \"%f\", &current_material.normal_scale);\n\t\t\t\t//fields |= kMatField_normal_scale;\n\t\t\t} else if (Q_stricmp(key, \"base_color\") == 0) {\n\t\t\t\tsscanf(value, \"%f %f %f %f\", &current_material.base_color[0], &current_material.base_color[1], &current_material.base_color[2], &current_material.base_color[3]);\n\t\t\t\t//fields |= kMatField_base_color;\n\t\t\t} else if (Q_stricmp(key, \"for_rendermode\") == 0) {\n\t\t\t\trendermode = R_VkRenderModeFromString(value);\n\t\t\t\tif (rendermode < 0)\n\t\t\t\t\tERR(\"Invalid rendermode \\\"%s\\\"\", value);\n\t\t\t\tASSERT(rendermode < COUNTOF(g_materials.for_rendermode[0].map));\n\t\t\t\t//fields |= kMatField_rendermode;\n\t\t\t} else {\n\t\t\t\tERR(\"Unknown material key \\\"%s\\\" on line `%.*s`\", key, (int)(pos - line_begin), line_begin);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\n\tMem_Free( data );\n\tg_stats.mat_files_read++;\n}\n\nstatic void loadMaterialsFromFileF( const char *fmt, ... ) {\n\tchar buffer[256];\n\tva_list argptr;\n\n\tva_start( argptr, fmt );\n\tvsnprintf( buffer, sizeof buffer, fmt, argptr );\n\tva_end( argptr );\n\n\tloadMaterialsFromFile( buffer, MAX_INCLUDE_DEPTH );\n}\n\nstatic int findFilenameExtension(const char *s, int len) {\n\tif (len < 0)\n\t\tlen = Q_strlen(s);\n\n\tfor (int i = len - 1; i >= 0; --i) {\n\t\tif (s[i] == '.')\n\t\t\treturn i;\n\t}\n\n\treturn len;\n}\n\nstatic void materialsReleaseTextures( void ) {\n\tfor (int i = 1; i < g_materials.count; ++i)\n\t\treleaseTexturesForMaterial(i);\n}\n\nvoid R_VkMaterialsReload( void ) {\n\tconst uint64_t begin_time_ns = aprof_time_now_ns();\n\tmemset(&g_stats, 0, sizeof(g_stats));\n\n\tmaterialsReleaseTextures();\n\n\tg_materials.count = 1;\n\n\tmemset(g_materials.tex_to_mat, 0, sizeof g_materials.tex_to_mat);\n\n\tg_materials.map_desc = (urmom_desc_t){\n\t\t.type = kUrmomStringInsensitive,\n\t\t.array = g_materials.map,\n\t\t.count = COUNTOF(g_materials.map),\n\t\t.item_size = sizeof(g_materials.map[0]),\n\t};\n\turmomInit(&g_materials.map_desc);\n\n\tfor (int i = 0; i < COUNTOF(g_materials.for_rendermode); ++i)\n\t\tg_materials.for_rendermode[i].count = 0;\n\n\t// TODO make these texture constants static constants\n\tk_default_material.tex_metalness = tglob.whiteTexture;\n\tk_default_material.tex_roughness = tglob.whiteTexture;\n\n\t// TODO name?\n\tg_materials.table[0].material = k_default_material;\n\tg_materials.table[0].material.tex_base_color = 0;\n\n\tloadMaterialsFromFile( \"pbr/materials.mat\", MAX_INCLUDE_DEPTH );\n\n\t// Load materials by WAD files\n\t{\n\t\tfor(const char *wad = g_map_entities.wadlist; *wad;) {\n\t\t\tconst char *wad_end = wad;\n\t\t\tconst char *ext = NULL;\n\t\t\twhile (*wad_end && *wad_end != ';') {\n\t\t\t\tif (*wad_end == '.')\n\t\t\t\t\text = wad_end;\n\t\t\t\t++wad_end;\n\t\t\t}\n\n\t\t\tconst int full_length = wad_end - wad;\n\n\t\t\t// Length without extension\n\t\t\tconst int short_length = ext ? ext - wad : full_length;\n\n\t\t\tloadMaterialsFromFileF(\"pbr/%.*s/%.*s.mat\", full_length, wad, short_length, wad);\n\t\t\twad = wad_end + 1;\n\t\t}\n\t}\n\n\t// Load materials by map/BSP file\n\t{\n\t\tconst model_t *map = WORLDMODEL;\n\t\tconst char *filename = COM_FileWithoutPath(map->name);\n\t\tconst int no_ext_len = findFilenameExtension(filename, -1);\n\t\tloadMaterialsFromFileF(\"pbr/%s/%.*s.mat\", map->name, no_ext_len, filename);\n\t}\n\n\t// Print out statistics\n\t{\n\t\tconst int duration_ms = (aprof_time_now_ns() - begin_time_ns) / 1000000ull;\n\t\tINFO(\"Loading materials took %dms, .mat files parsed: %d (fread: %dms). Texture lookups: %d (%dms). Texture loads: %d (%dms).\",\n\t\t\tduration_ms,\n\t\t\tg_stats.mat_files_read,\n\t\t\t(int)(g_stats.material_file_read_duration_ns / 1000000ull),\n\t\t\tg_stats.texture_lookups,\n\t\t\t(int)(g_stats.texture_lookup_duration_ns / 1000000ull),\n\t\t\tg_stats.texture_loads,\n\t\t\t(int)(g_stats.texture_load_duration_ns / 1000000ull)\n\t\t\t);\n\t}\n}\n\nvoid R_VkMaterialsLoadForModel( const struct model_s* mod ) {\n\t// Brush models are loaded separately\n\tif (mod->type == mod_brush)\n\t\treturn;\n\n\t// TODO add stats\n\n\tconst char *filename = COM_FileWithoutPath(mod->name);\n\tconst int no_ext_len = findFilenameExtension(filename, -1);\n\tloadMaterialsFromFileF(\"pbr/%s/%.*s.mat\", mod->name, no_ext_len, filename);\n}\n\nr_vk_material_t R_VkMaterialGetForTexture( int tex_index ) {\n\treturn R_VkMaterialGetForTextureWithFlags( tex_index, kVkMaterialFlagNone );\n}\n\nr_vk_material_t R_VkMaterialGetForTextureWithFlags( int tex_index, uint32_t flags ) {\n\t//DEBUG(\"Getting material for tex_id=%d\", tex_index);\n\tASSERT(tex_index >= 0);\n\tASSERT(tex_index < MAX_TEXTURES);\n\n\ttexture_to_material_t* const t2m = g_materials.tex_to_mat + tex_index;\n\n\tif (t2m->mat_id > 0) {\n\t\tASSERT(t2m->mat_id < g_materials.count);\n\t\t//DEBUG(\"Getting material for tex_id=%d\", tex_index);\n\t\t//printMaterial(t2m->mat_id);\n\t\treturn g_materials.table[t2m->mat_id].material;\n\t}\n\n\tif (t2m->mat_id == kMaterialNotChecked) {\n\t\t// TODO check for replacement textures named in a predictable way\n\t\t// If there are, create a new material and assign it here\n\n\t\tconst char* texname = R_TextureGetNameByIndex(tex_index);\n\t\tDEBUG(\"Would try to load texture files by default names of \\\"%s\\\"\", texname);\n\n\t\t// If no PBR textures found, continue using legacy+default ones\n\t\tt2m->mat_id = kMaterialNoReplacement;\n\t}\n\n\tr_vk_material_t ret = k_default_material;\n\tret.tex_base_color = tex_index;\n\n\tif ( flags & kVkMaterialFlagChrome )\n\t\tret.tex_roughness = tglob.grayTexture;\n\n\t//DEBUG(\"Returning default material with tex_base_color=%d\", tex_index);\n\treturn ret;\n}\n\nr_vk_material_ref_t R_VkMaterialGetForName( const char *name ) {\n\t// Find in internal map first\n\t// New materials have preference over texture names\n\tconst int index = urmomFind(&g_materials.map_desc, name);\n\tif (index >= 0)\n\t\treturn (r_vk_material_ref_t){.index = g_materials.map[index].mat_id};\n\tDEBUG(\"Couldn't find material '%s', fallback to texture lookup\", name);\n\n\t// Find by texture name\n\tconst int tex_id = R_TextureFindByNameLike(name);\n\tif (tex_id <= 0) {\n\t\tERR(\"Neither material nor texture with name \\\"%s\\\" was found\", name);\n\t\treturn (r_vk_material_ref_t){.index = -1,};\n\t}\n\n\tASSERT(tex_id > 0);\n\tASSERT(tex_id < MAX_TEXTURES);\n\n\treturn (r_vk_material_ref_t){.index = g_materials.tex_to_mat[tex_id].mat_id};\n}\n\nr_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref ) {\n\tif (ref.index < 0) {\n\t\tr_vk_material_t ret = k_default_material;\n\t\tret.tex_base_color = 0; // Default/error texture\n\t\treturn ret;\n\t}\n\n\tASSERT(ref.index < g_materials.count);\n\treturn g_materials.table[ref.index].material;\n}\n\nqboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_material ) {\n\tDEBUG(\"Getting material for tex_id=%d rendermode=%d\", tex_id, rendermode);\n\n\tif (rendermode == 0) {\n\t\tWARN(\"rendermode==0: fallback to regular tex_id=%d\", tex_id);\n\t\t*out_material = R_VkMaterialGetForTexture(tex_id);\n\t\treturn true;\n\t}\n\n\t// TODO move rendermode-specifit things to by-texid-chains\n\tASSERT(rendermode < COUNTOF(g_materials.for_rendermode));\n\tconst r_vk_material_per_mode_t* const mode = &g_materials.for_rendermode[rendermode];\n\tfor (int i = 0; i < mode->count; ++i) {\n\t\tif (mode->map[i].tex_id == tex_id) {\n\t\t\tconst int index = mode->map[i].mat.index;\n\t\t\tASSERT(index >= 0);\n\t\t\tASSERT(index < g_materials.count);\n\t\t\t*out_material = g_materials.table[index].material;\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\nvoid R_VkMaterialsShutdown( void ) {\n\tmaterialsReleaseTextures();\n}\n\n"
  },
  {
    "path": "ref/vk/vk_materials.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\n/* TODO\n#define MATERIAL_FIELDS_LIST(X) \\\n\tX(0, int, tex_base_color, basecolor_map, readTexture) \\\n\tX(1, int, tex_roughness, normal_map, readTexture) \\\n\tX(2, int, tex_metalness, metal_map, readTexture) \\\n\tX(3, int, tex_normalmap, roughness_map, readTexture) \\\n\tX(4, vec4_t, base_color, base_color, readVec4) \\\n\tX(5, float, roughness, roughness, readFloat) \\\n\tX(6, float, metalness, metalness, readFloat) \\\n\tX(7, float, normal_scale, normal_scale, readFloat) \\\n\tX(7, int, rendermode, rendermode, readRendermode) \\\n\tX(8, int, _inherit, inherit, readInerit) \\\n*/\n\ntypedef struct r_vk_material_s {\n\tint tex_base_color;\n\tint tex_roughness;\n\tint tex_metalness;\n\tint tex_normalmap;\n\n\tvec4_t base_color;\n\tfloat roughness;\n\tfloat metalness;\n\tfloat normal_scale;\n} r_vk_material_t;\n\ntypedef struct { int index; } r_vk_material_ref_t;\n\n// Note: invalidates all previously issued material refs\n// TODO: track \"version\" in high bits?\nvoid R_VkMaterialsReload( void );\n\nvoid R_VkMaterialsShutdown( void );\n\nstruct model_s;\nvoid R_VkMaterialsLoadForModel( const struct model_s* mod );\n\nr_vk_material_ref_t R_VkMaterialGetForName( const char *name );\nr_vk_material_t R_VkMaterialGetForRef( r_vk_material_ref_t ref );\n\nr_vk_material_t R_VkMaterialGetForTexture( int tex_id );\n\nenum {\n\tkVkMaterialFlagNone = 0,\n\t// Studio model STUDIO_NF_CHROME\n\tkVkMaterialFlagChrome = (1<<0),\n};\n\nr_vk_material_t R_VkMaterialGetForTextureWithFlags( int tex_id, uint32_t flags );\n\nqboolean R_VkMaterialGetEx( int tex_id, int rendermode, r_vk_material_t *out_material );\n"
  },
  {
    "path": "ref/vk/vk_math.c",
    "content": "/*\ngl_rmath.c - renderer mathlib\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"vk_math.h\"\n\n#include <memory.h>\n\n/*\n========================================================================\n\n\t       Matrix4x4 operations (private to renderer)\n\n========================================================================\n*/\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 )\n{\n\tout[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0];\n\tout[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1];\n\tout[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2];\n\tout[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3];\n\tout[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0];\n\tout[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1];\n\tout[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2];\n\tout[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3];\n\tout[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0];\n\tout[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1];\n\tout[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2];\n\tout[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3];\n\tout[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0];\n\tout[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1];\n\tout[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2];\n\tout[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3];\n}\n\n/*\n================\nMatrix4x4_CreateProjection\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar )\n{\n\tout[0][0] = ( 2.0f * zNear ) / ( xMax - xMin );\n\tout[1][1] = ( 2.0f * zNear ) / ( yMax - yMin );\n\tout[2][2] = -( zFar + zNear ) / ( zFar - zNear );\n\tout[3][3] = out[0][1] = out[1][0] = out[3][0] = out[0][3] = out[3][1] = out[1][3] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][2] = ( xMax + xMin ) / ( xMax - xMin );\n\tout[1][2] = ( yMax + yMin ) / ( yMax - yMin );\n\tout[3][2] = -1.0f;\n\tout[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear );\n}\n\nvoid Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar )\n{\n\tout[0][0] = 2.0f / (xRight - xLeft);\n\tout[1][1] = 2.0f / (yTop - yBottom);\n\tout[2][2] = -2.0f / (zFar - zNear);\n\tout[3][3] = 1.0f;\n\tout[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f;\n\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[0][3] = -(xRight + xLeft) / (xRight - xLeft);\n\tout[1][3] = -(yTop + yBottom) / (yTop - yBottom);\n\tout[2][3] = -(zFar + zNear) / (zFar - zNear);\n}\n\n/*\n================\nMatrix4x4_CreateModelview\n\nNOTE: produce quake style world orientation\n================\n*/\nvoid Matrix4x4_CreateModelview( matrix4x4 out )\n{\n\tout[0][0] = out[1][1] = out[2][2] = 0.0f;\n\tout[3][0] = out[0][3] = 0.0f;\n\tout[3][1] = out[1][3] = 0.0f;\n\tout[3][2] = out[2][3] = 0.0f;\n\tout[3][3] = 1.0f;\n\tout[1][0] = out[0][2] = out[2][1] = 0.0f;\n\tout[2][0] = out[0][1] = -1.0f;\n\tout[1][2] = 1.0f;\n}\n\nvoid Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] )\n{\n\tout[ 0] = in[0][0];\n\tout[ 1] = in[1][0];\n\tout[ 2] = in[2][0];\n\tout[ 3] = in[3][0];\n\tout[ 4] = in[0][1];\n\tout[ 5] = in[1][1];\n\tout[ 6] = in[2][1];\n\tout[ 7] = in[3][1];\n\tout[ 8] = in[0][2];\n\tout[ 9] = in[1][2];\n\tout[10] = in[2][2];\n\tout[11] = in[3][2];\n\tout[12] = in[0][3];\n\tout[13] = in[1][3];\n\tout[14] = in[2][3];\n\tout[15] = in[3][3];\n}\n\nvoid Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] )\n{\n\tout[0][0] = in[0];\n\tout[1][0] = in[1];\n\tout[2][0] = in[2];\n\tout[3][0] = in[3];\n\tout[0][1] = in[4];\n\tout[1][1] = in[5];\n\tout[2][1] = in[6];\n\tout[3][1] = in[7];\n\tout[0][2] = in[8];\n\tout[1][2] = in[9];\n\tout[2][2] = in[10];\n\tout[3][2] = in[11];\n\tout[0][3] = in[12];\n\tout[1][3] = in[13];\n\tout[2][3] = in[14];\n\tout[3][3] = in[15];\n}\n\nvoid Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][0] = 1.0f;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = x;\n\tout[1][0] = 0.0f;\n\tout[1][1] = 1.0f;\n\tout[1][2] = 0.0f;\n\tout[1][3] = y;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = 1.0f;\n\tout[2][3] = z;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nvoid Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tfloat\tlen, c, s;\n\n\tlen = x * x + y * y + z * z;\n\tif( len != 0.0f ) len = 1.0f / sqrt( len );\n\tx *= len;\n\ty *= len;\n\tz *= len;\n\n\tangle *= (-M_PI_F / 180.0f);\n\tSinCos( angle, &s, &c );\n\n\tout[0][0]=x * x + c * (1 - x * x);\n\tout[0][1]=x * y * (1 - c) + z * s;\n\tout[0][2]=z * x * (1 - c) - y * s;\n\tout[0][3]=0.0f;\n\tout[1][0]=x * y * (1 - c) - z * s;\n\tout[1][1]=y * y + c * (1 - y * y);\n\tout[1][2]=y * z * (1 - c) + x * s;\n\tout[1][3]=0.0f;\n\tout[2][0]=z * x * (1 - c) + y * s;\n\tout[2][1]=y * z * (1 - c) - x * s;\n\tout[2][2]=z * z + c * (1 - z * z);\n\tout[2][3]=0.0f;\n\tout[3][0]=0.0f;\n\tout[3][1]=0.0f;\n\tout[3][2]=0.0f;\n\tout[3][3]=1.0f;\n}\n\nvoid Matrix4x4_CreateScale( matrix4x4 out, float x )\n{\n\tout[0][0] = x;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = 0.0f;\n\tout[1][0] = 0.0f;\n\tout[1][1] = x;\n\tout[1][2] = 0.0f;\n\tout[1][3] = 0.0f;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = x;\n\tout[2][3] = 0.0f;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nvoid Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][0] = x;\n\tout[0][1] = 0.0f;\n\tout[0][2] = 0.0f;\n\tout[0][3] = 0.0f;\n\tout[1][0] = 0.0f;\n\tout[1][1] = y;\n\tout[1][2] = 0.0f;\n\tout[1][3] = 0.0f;\n\tout[2][0] = 0.0f;\n\tout[2][1] = 0.0f;\n\tout[2][2] = z;\n\tout[2][3] = 0.0f;\n\tout[3][0] = 0.0f;\n\tout[3][1] = 0.0f;\n\tout[3][2] = 0.0f;\n\tout[3][3] = 1.0f;\n}\n\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateTranslate( temp, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z )\n{\n\tmatrix4x4 base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateRotate( temp, angle, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid Matrix4x4_ConcatScale( matrix4x4 out, float x )\n{\n\tmatrix4x4\tbase, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateScale( temp, x );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z )\n{\n\tmatrix4x4  base, temp;\n\n\tMatrix4x4_Copy( base, out );\n\tMatrix4x4_CreateScale3( temp, x, y, z );\n\tMatrix4x4_Concat( out, base, temp );\n}\n\nvoid computeTangentE(vec3_t out_tangent, const vec3_t e1, const vec3_t e2, const vec2_t uv0, const vec2_t uv1, const vec2_t uv2) {\n\tvec2_t duv1, duv2;\n\n\tVector2Subtract(uv1, uv0, duv1);\n\tVector2Subtract(uv2, uv0, duv2);\n\n\tconst float div = duv1[0] * duv2[1] - duv1[1] * duv2[0];\n\tif (fabs(div) < 1e-5f) {\n\t\tVectorClear(out_tangent);\n\t\treturn;\n\t}\n\tconst float f = 1.f / div;\n\tout_tangent[0] = f * (duv2[1] * e1[0] - duv1[1] * e2[0]);\n\tout_tangent[1] = f * (duv2[1] * e1[1] - duv1[1] * e2[1]);\n\tout_tangent[2] = f * (duv2[1] * e1[2] - duv1[1] * e2[2]);\n}\n\nvoid computeTangent(vec3_t out_tangent, const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t uv0, const vec2_t uv1, const vec2_t uv2) {\n\tvec3_t e1, e2;\n\n\tVectorSubtract(v1, v0, e1);\n\tVectorSubtract(v2, v0, e2);\n\n\tcomputeTangentE(out_tangent, e1, e2, uv0, uv1, uv2);\n}\n\nvoid Matrix4x4_CreateFromVectors(matrix4x4 out, const vec3_t right, const vec3_t up, const vec3_t z, const vec3_t translate) {\n\tout[0][0] = right[0];\n\tout[1][0] = right[1];\n\tout[2][0] = right[2];\n\tout[3][0] = 0;\n\n\tout[0][1] = up[0];\n\tout[1][1] = up[1];\n\tout[2][1] = up[2];\n\tout[3][1] = 0;\n\n\tout[0][2] = z[0];\n\tout[1][2] = z[1];\n\tout[2][2] = z[2];\n\tout[3][2] = 0;\n\n\tout[0][3] = translate[0];\n\tout[1][3] = translate[1];\n\tout[2][3] = translate[2];\n\tout[3][3] = 1;\n}\n\nvoid computeNormal(vec3_t p0, vec3_t p1, vec3_t p2, vec3_t out_normal) {\n\tvec3_t e0, e1;\n\tVectorSubtract(p2, p0, e0);\n\tVectorSubtract(p1, p0, e1);\n\tCrossProduct(e0, e1, out_normal);\n}\n"
  },
  {
    "path": "ref/vk/vk_math.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include <string.h>\n#include \"xash3d_mathlib.h\"\n\nvoid Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] );\nvoid Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] );\nvoid Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 );\nvoid Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z );\nvoid Matrix4x4_ConcatScale( matrix4x4 out, float x );\nvoid Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z );\nvoid Matrix4x4_CreateScale( matrix4x4 out, float x );\nvoid Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z );\nvoid Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar);\nvoid Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar);\nvoid Matrix4x4_CreateModelview( matrix4x4 out );\n\nvoid computeTangentE(vec3_t out_tangent, const vec3_t e1, const vec3_t e2, const vec2_t uv0, const vec2_t uv1, const vec2_t uv2);\nvoid computeTangent(vec3_t out_tangent, const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t uv0, const vec2_t uv1, const vec2_t uv2);\n\nvoid Matrix4x4_CreateFromVectors(matrix4x4 out, const vec3_t right, const vec3_t up, const vec3_t z, const vec3_t translate);\n\nvoid computeNormal(vec3_t p0, vec3_t p1, vec3_t p2, vec3_t out_normal);\n"
  },
  {
    "path": "ref/vk/vk_overlay.c",
    "content": "#include \"vk_overlay.h\"\n\n#include \"vulkan/VBuffer.h\"\n#include \"vk_core.h\"\n#include \"vk_common.h\"\n#include \"vk_textures.h\"\n#include \"vk_framectl.h\"\n#include \"vk_renderstate.h\"\n#include \"vulkan/VPipeline.h\"\n#include \"vulkan/VDescriptor.h\"\n#include \"vk_logs.h\"\n\n#include \"com_strings.h\"\n#include \"eiface.h\"\n\n\ntypedef struct vertex_2d_s {\n\tfloat x, y;\n\tfloat u, v;\n\tcolor_rgba8_t color;\n} vertex_2d_t;\n\n// TODO should these be dynamic?\n#define MAX_PICS 16384\n#define MAX_VERTICES (MAX_PICS * 6)\n#define MAX_BATCHES 256\n\ntypedef struct {\n\tuint32_t vertex_offset, vertex_count;\n\tint texture;\n\tint blending_mode;\n} batch_t;\n\nstatic struct {\n\tVkPipelineLayout pipeline_layout;\n\tVkPipeline pipelines[kRenderTransAdd + 1];\n\n\tvk_buffer_t pics_buffer;\n\tr_flipping_buffer_t pics_buffer_alloc;\n\tqboolean exhausted_this_frame;\n\n\tbatch_t batch[MAX_BATCHES];\n\tint batch_count;\n\n\t// TODO texture bindings?\n} g2d;\n\nstatic vertex_2d_t* allocQuadVerts(int blending_mode, int texnum) {\n\tconst uint32_t pics_offset = R_FlippingBuffer_Alloc(&g2d.pics_buffer_alloc, 6, 1);\n\tvertex_2d_t* const ptr = ((vertex_2d_t*)(g2d.pics_buffer.mapped)) + pics_offset;\n\tbatch_t *batch = g2d.batch + (g2d.batch_count-1);\n\n\tif (pics_offset == ALO_ALLOC_FAILED) {\n\t\tif (!g2d.exhausted_this_frame) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"2d: ran out of vertex memory\\n\");\n\t\t\tg2d.exhausted_this_frame = true;\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tif (batch->texture != texnum\n\t\t|| batch->blending_mode != blending_mode\n\t\t|| batch->vertex_offset > pics_offset) {\n\t\tif (batch->vertex_count != 0) {\n\t\t\tif (g2d.batch_count == MAX_BATCHES) {\n\t\t\t\tif (!g2d.exhausted_this_frame) {\n\t\t\t\t\tgEngine.Con_Printf(S_ERROR \"2d: ran out of batch memory\\n\");\n\t\t\t\t\tg2d.exhausted_this_frame = true;\n\t\t\t\t}\n\t\t\t\treturn NULL;\n\t\t\t}\n\n\t\t\t++g2d.batch_count;\n\t\t\tbatch++;\n\t\t}\n\n\t\tbatch->vertex_offset = pics_offset;\n\t\tbatch->vertex_count = 0;\n\t\tbatch->texture = texnum;\n\t\tbatch->blending_mode = blending_mode;\n\t}\n\n\tbatch->vertex_count += 6;\n\tASSERT(batch->vertex_count + batch->vertex_offset <= MAX_VERTICES);\n\treturn ptr;\n}\n\nvoid R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum )\n{\n\tvertex_2d_t *const p = allocQuadVerts(vk_renderstate.blending_mode, texnum);\n\n\tif (!p) {\n\t\t/* gEngine.Con_Printf(S_ERROR \"VK FIXME %s(%f, %f, %f, %f, %f, %f, %f, %f, %d(%s))\\n\", __FUNCTION__, */\n\t\t/* \tx, y, w, h, s1, t1, s2, t2, texnum, R_TextureGetByIndex(texnum)->name); */\n\t\treturn;\n\t}\n\n\t{\n\t\t// TODO do this in shader bro\n\t\tconst float vw = vk_frame.width;\n\t\tconst float vh = vk_frame.height;\n\t\tconst float x1 = (x / vw)*2.f - 1.f;\n\t\tconst float y1 = (y / vh)*2.f - 1.f;\n\t\tconst float x2 = ((x + w) / vw)*2.f - 1.f;\n\t\tconst float y2 = ((y + h) / vh)*2.f - 1.f;\n\t\tconst color_rgba8_t color = vk_renderstate.tri_color;\n\n\t\tp[0] = (vertex_2d_t){x1, y1, s1, t1, color};\n\t\tp[1] = (vertex_2d_t){x1, y2, s1, t2, color};\n\t\tp[2] = (vertex_2d_t){x2, y1, s2, t1, color};\n\t\tp[3] = (vertex_2d_t){x2, y1, s2, t1, color};\n\t\tp[4] = (vertex_2d_t){x1, y2, s1, t2, color};\n\t\tp[5] = (vertex_2d_t){x2, y2, s2, t2, color};\n\t}\n}\n\nstatic void drawFill( float x, float y, float w, float h, int r, int g, int b, int a, int blending_mode )\n{\n\tconst color_rgba8_t prev_color = vk_renderstate.tri_color;\n\tconst int prev_blending = vk_renderstate.blending_mode;\n\tvk_renderstate.blending_mode = blending_mode;\n\tvk_renderstate.tri_color = (color_rgba8_t){r, g, b, a};\n\tR_DrawStretchPic(x, y, w, h, 0, 0, 1, 1, /* TODO what is this garbage, get it by number */ R_TextureFindByName(REF_WHITE_TEXTURE));\n\tvk_renderstate.tri_color = prev_color;\n\tvk_renderstate.blending_mode = prev_blending;\n}\n\nstatic void clearAccumulated( void ) {\n\tR_FlippingBuffer_Flip(&g2d.pics_buffer_alloc);\n\n\tg2d.batch_count = 1;\n\tg2d.batch[0].texture = -1;\n\tg2d.batch[0].vertex_offset = 0;\n\tg2d.batch[0].vertex_count = 0;\n\tg2d.exhausted_this_frame = false;\n}\n\nstatic qboolean createPipelines( void )\n{\n\t{\n\t\t/* VkPushConstantRange push_const = { */\n\t\t/* \t.offset = 0, */\n\t\t/* \t.size = sizeof(AVec3f), */\n\t\t/* \t.stageFlags = VK_SHADER_STAGE_VERTEX_BIT, */\n\t\t/* }; */\n\n\t\tVkDescriptorSetLayout descriptor_layouts[] = {\n\t\t\tvk_desc_fixme.one_texture_layout,\n\t\t};\n\n\t\tVkPipelineLayoutCreateInfo plci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,\n\t\t\t.setLayoutCount = ARRAYSIZE(descriptor_layouts),\n\t\t\t.pSetLayouts = descriptor_layouts,\n\t\t\t/* .pushConstantRangeCount = 1, */\n\t\t\t/* .pPushConstantRanges = &push_const, */\n\t\t};\n\n\t\tXVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &g2d.pipeline_layout));\n\t}\n\n\t{\n\t\tconst VkVertexInputAttributeDescription attribs[] = {\n\t\t\t{.binding = 0, .location = 0, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vertex_2d_t, x)},\n\t\t\t{.binding = 0, .location = 1, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vertex_2d_t, u)},\n\t\t\t{.binding = 0, .location = 2, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(vertex_2d_t, color)},\n\t\t};\n\n\t\tconst vk_shader_stage_t shader_stages[] = {\n\t\t{\n\t\t\t.stage = VK_SHADER_STAGE_VERTEX_BIT,\n\t\t\t.filename = \"2d.vert.spv\",\n\t\t}, {\n\t\t\t.stage = VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t\t.filename = \"2d.frag.spv\",\n\t\t}};\n\n\t\tvk_pipeline_graphics_create_info_t pci = {\n\t\t\t.layout = g2d.pipeline_layout,\n\t\t\t.attribs = attribs,\n\t\t\t.num_attribs = ARRAYSIZE(attribs),\n\t\t\t.stages = shader_stages,\n\t\t\t.num_stages = ARRAYSIZE(shader_stages),\n\t\t\t.vertex_stride = sizeof(vertex_2d_t),\n\t\t\t.depthTestEnable = VK_FALSE,\n\t\t\t.depthWriteEnable = VK_FALSE,\n\t\t\t.depthCompareOp = VK_COMPARE_OP_ALWAYS,\n\t\t\t.cullMode = VK_CULL_MODE_NONE,\n\t\t};\n\n\t\tfor (int i = 0; i < ARRAYSIZE(g2d.pipelines); ++i)\n\t\t{\n\t\t\tswitch (i)\n\t\t\t{\n\t\t\t\tcase kRenderNormal:\n\t\t\t\t\tpci.blendEnable = VK_FALSE;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase kRenderTransColor:\n\t\t\t\tcase kRenderTransTexture:\n\t\t\t\t\tpci.blendEnable = VK_TRUE;\n\t\t\t\t\tpci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\t\t\tpci.srcAlphaBlendFactor = pci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\t\t\tpci.dstAlphaBlendFactor = pci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase kRenderTransAlpha:\n\t\t\t\t\tpci.blendEnable = VK_FALSE;\n\t\t\t\t\t// FIXME pglEnable( GL_ALPHA_TEST );\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase kRenderGlow:\n\t\t\t\tcase kRenderTransAdd:\n\t\t\t\t\tpci.blendEnable = VK_TRUE;\n\t\t\t\t\tpci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\t\t\tpci.srcAlphaBlendFactor = pci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\t\t\tpci.dstAlphaBlendFactor = pci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tg2d.pipelines[i] = VK_PipelineGraphicsCreate(&pci);\n\n\t\t\tif (!g2d.pipelines[i])\n\t\t\t{\n\t\t\t\t// TODO complain\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nqboolean R_VkOverlay_Init( void ) {\n\tif (!createPipelines())\n\t\treturn false;\n\n\t// TODO this doesn't need to be host visible, could use staging too\n\tif (!VK_BufferCreate(\"2d pics_buffer\", &g2d.pics_buffer, sizeof(vertex_2d_t) * MAX_VERTICES,\n\t\t\t\tVK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ))\n\t\t// FIXME cleanup\n\t\treturn false;\n\n\tR_FlippingBuffer_Init(&g2d.pics_buffer_alloc, MAX_VERTICES);\n\n\treturn true;\n}\n\nvoid R_VkOverlay_Shutdown( void ) {\n\tVK_BufferDestroy(&g2d.pics_buffer);\n\tfor (int i = 0; i < ARRAYSIZE(g2d.pipelines); ++i)\n\t\tvkDestroyPipeline(vk_core.device, g2d.pipelines[i], NULL);\n\n\tvkDestroyPipelineLayout(vk_core.device, g2d.pipeline_layout, NULL);\n}\n\nstatic void drawOverlay( VkCommandBuffer cmdbuf ) {\n\tDEBUG_BEGIN(cmdbuf, \"2d overlay\");\n\n\t{\n\t\tconst VkDeviceSize offset = 0;\n\t\tvkCmdBindVertexBuffers(cmdbuf, 0, 1, &g2d.pics_buffer.buffer, &offset);\n\t}\n\n\tfor (int i = 0; i < g2d.batch_count && g2d.batch[i].vertex_count > 0; ++i)\n\t{\n\t\tconst VkDescriptorSet tex_unorm = R_VkTextureGetDescriptorUnorm( g2d.batch[i].texture );\n\t\tconst VkPipeline pipeline = g2d.pipelines[g2d.batch[i].blending_mode];\n\t\tif (tex_unorm)\n\t\t{\n\t\t\tvkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);\n\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g2d.pipeline_layout, 0, 1, &tex_unorm, 0, NULL);\n\t\t\tvkCmdDraw(cmdbuf, g2d.batch[i].vertex_count, 1, g2d.batch[i].vertex_offset, 0);\n\t\t} // FIXME else what?\n\t}\n\n\tDEBUG_END(cmdbuf);\n}\n\nvoid R_VkOverlay_DrawAndFlip( VkCommandBuffer cmdbuf, qboolean draw ) {\n\tif (draw)\n\t\tdrawOverlay(cmdbuf);\n\n\tclearAccumulated();\n}\n\nvoid R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nvoid CL_FillRGBA( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a )\n{\n\tdrawFill(x, y, w, h, r, g, b, a, rendermode);\n}\n"
  },
  {
    "path": "ref/vk/vk_overlay.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n#include \"xash3d_types.h\"\n\nvoid R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty );\nvoid R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum );\nvoid R_DrawTileClear( int texnum, int x, int y, int w, int h );\nvoid CL_FillRGBA( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a );\n\nqboolean R_VkOverlay_Init( void );\nvoid R_VkOverlay_Shutdown( void );\n\nvoid R_VkOverlay_DrawAndFlip( VkCommandBuffer cmdbuf, qboolean draw );\n"
  },
  {
    "path": "ref/vk/vk_ray_accel.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\" // qboolean, math types\n\nqboolean RT_VkAccelInit(void);\nvoid RT_VkAccelShutdown(void);\n\nvoid RT_VkAccelNewMap(void);\n\ntypedef struct rt_draw_instance_t {\n\tstruct rt_blas_s *blas;\n\tuint32_t kusochki_offset;\n\tmatrix3x4 transform_row;\n\tmatrix4x4 prev_transform_row;\n\tvec4_t color;\n\tuint32_t material_mode; // MATERIAL_MODE_ from ray_interop.h\n\tuint32_t material_flags; // material_flag_bits_e\n} rt_draw_instance_t;\n\nvoid RT_VkAccelAddDrawInstance(const rt_draw_instance_t*);\n\nqboolean RT_VkAccelIsEmpty(void);\n"
  },
  {
    "path": "ref/vk/vk_ray_internal.h",
    "content": "#pragma once\n\n#include \"vk_rtx.h\"\n\n#define MAX_INSTANCES 2048\n#define MODEL_CACHE_SIZE 2048\n\nvoid XVK_RayModel_ClearForNextFrame( void );\nvoid XVK_RayModel_Validate(void);\n\nvoid RT_RayModel_Clear(void);\n\n// Memory pointed to by name must remain alive until RT_BlasDestroy\ntypedef struct {\n\tconst char *name;\n\trt_blas_usage_e usage;\n\tconst struct vk_render_geometry_s *geoms;\n\tint geoms_count;\n} rt_blas_create_t;\n\n// Creates BLAS and schedules it to be built next frame\nstruct rt_blas_s* RT_BlasCreate(rt_blas_create_t args);\n\nvoid RT_BlasDestroy(struct rt_blas_s* blas);\n\n// Update dynamic BLAS, schedule it for build/update\nqboolean RT_BlasUpdate(struct rt_blas_s *blas, const struct vk_render_geometry_s *geoms, int geoms_count);\n\nqboolean RT_DynamicModelInit(void);\nvoid RT_DynamicModelShutdown(void);\n\nvoid RT_DynamicModelProcessFrame(void);\n"
  },
  {
    "path": "ref/vk/vk_ray_model.c",
    "content": "#include \"vk_ray_internal.h\"\n\n#include \"shaders/ray_interop.h\" // MATERIAL_MODE_...\n\n#include \"vk_rtx.h\"\n#include \"vk_render.h\"\n#include \"vk_logs.h\"\n#include \"vk_ray_accel.h\"\n#include \"rt_kusochki.h\"\n#include \"std/profiler.h\"\n#include \"std/alolcator.h\" // ALO_ALLOC_FAILED\n\n#include \"xash3d_mathlib.h\"\n\ntypedef struct rt_model_s {\n\tstruct rt_blas_s *blas;\n\trt_kusochki_t kusochki;\n} rt_model_t;\n\n// TODO this material mapping is context dependent. I.e. different entity types might need different ray tracing behaviours for\n// same render_mode/type and even texture.\nuint32_t R_VkMaterialModeFromRenderType(vk_render_type_e render_type) {\n\tswitch (render_type) {\n\t\tcase kVkRenderTypeSolid:\n\t\t\treturn MATERIAL_MODE_OPAQUE;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1mA_RW: // blend: scr*a + dst*(1-a), depth: RW\n\t\tcase kVkRenderType_A_1mA_R:  // blend: scr*a + dst*(1-a), depth test\n\t\t\t// FIXME where is MATERIAL_MODE_TRANSLUCENT??1\n\t\t\treturn MATERIAL_MODE_BLEND_MIX;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1:   // blend: scr*a + dst, no depth test or write; sprite:kRenderGlow only\n\t\t\treturn MATERIAL_MODE_BLEND_GLOW;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1_R: // blend: scr*a + dst, depth test\n\t\tcase kVkRenderType_1_1_R: // blend: scr + dst, depth test\n\t\t\treturn MATERIAL_MODE_BLEND_ADD;\n\t\t\tbreak;\n\t\tcase kVkRenderType_AT: // no blend, depth RW, alpha test\n\t\t\treturn MATERIAL_MODE_OPAQUE_ALPHA_TEST;\n\t\t\tbreak;\n\n\t\tcase kVkRenderType_Decal: // decals changing diffuse and rmxx of surface material\n\t\t\treturn MATERIAL_MODE_DECAL;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tgEngine.Host_Error(\"Unexpected render type %d\\n\", render_type);\n\t}\n\n\treturn MATERIAL_MODE_OPAQUE;\n}\n\nvoid RT_RayModel_Clear(void) {\n\tRT_KusochkiClear();\n\n\t// FIXME\n\t// This is a dirty workaround for sub-part memory management in this little project\n\t// Accel backing buffer gets cleared on NewMap. Therefore, we need to recreate BLASes for dynamic\n\t// models, even though they might have lived for the entire process lifetime.\n\t// See #729\n\tRT_DynamicModelShutdown();\n\tRT_DynamicModelInit();\n}\n\nvoid XVK_RayModel_ClearForNextFrame( void ) {\n\tRT_KusochkiFlip();\n}\n\nstruct rt_model_s *RT_ModelCreate(rt_model_create_t args) {\n\tconst rt_kusochki_t kusochki = RT_KusochkiAllocLong(args.geometries_count);\n\tif (kusochki.count == 0) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot allocate kusochki for %s\\n\", args.debug_name);\n\t\treturn NULL;\n\t}\n\n\tstruct rt_blas_s *blas = RT_BlasCreate((rt_blas_create_t){\n\t\t.name = args.debug_name,\n\t\t.usage = args.usage,\n\t\t.geoms = args.geometries,\n\t\t.geoms_count = args.geometries_count,\n\t});\n\tif (!blas) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot create BLAS for %s\\n\", args.debug_name);\n\t\tgoto fail;\n\t}\n\n\t// Invokes staging, so this should be after all resource creation\n\tRT_KusochkiUpload(kusochki.offset, args.geometries, args.geometries_count, NULL, NULL);\n\n\t{\n\t\trt_model_t *const ret = Mem_Malloc(vk_core.pool, sizeof(*ret));\n\t\tret->blas = blas;\n\t\tret->kusochki = kusochki;\n\t\treturn ret;\n\t}\n\nfail:\n\tif (blas)\n\t\tRT_BlasDestroy(blas);\n\n\tif (kusochki.count)\n\t\tRT_KusochkiFree(&kusochki);\n\n\treturn NULL;\n}\n\nvoid RT_ModelDestroy(struct rt_model_s* model) {\n\tif (!model)\n\t\treturn;\n\n\tif (model->blas)\n\t\tRT_BlasDestroy(model->blas);\n\n\tif (model->kusochki.count)\n\t\tRT_KusochkiFree(&model->kusochki);\n\n\tMem_Free(model);\n}\n\nqboolean RT_ModelUpdate(struct rt_model_s *model, const struct vk_render_geometry_s *geometries, int geometries_count) {\n\t// TODO: It might be beneficial to be able to supply which parts of the RT model should be updated.\n\t// E.g.:\n\t// - A flag to update BLAS (not all model updates need BLAS updates, e.g. waveHeight=0 water updates\n\t// only update UVs)\n\t// - A flag to update kusochki. Not all updates update offsets and textures, e.g. studio models have\n\t// stable textures that don't change.\n\n\t// Schedule rebuilding blas\n\tif (!RT_BlasUpdate(model->blas, geometries, geometries_count))\n\t\treturn false;\n\n\t// Also update materials\n\tRT_KusochkiUpload(model->kusochki.offset, geometries, geometries_count, NULL, NULL);\n\treturn true;\n}\n\nqboolean RT_ModelUpdateMaterials(struct rt_model_s *model, const struct vk_render_geometry_s *geometries, int geometries_count, const int *geom_indices, int geom_indices_count) {\n\tif (!geom_indices_count)\n\t\treturn true;\n\n\tAPROF_SCOPE_DECLARE_BEGIN(update_materials, __FUNCTION__);\n\n\tint begin = 0;\n\tfor (int i = 1; i < geom_indices_count; ++i) {\n\t\tconst int geom_index = geom_indices[i];\n\t\tASSERT(geom_index >= 0);\n\t\tASSERT(geom_index < geometries_count);\n\n\t\tif (geom_indices[i - 1] + 1 != geom_index) {\n\t\t\tconst int offset = geom_indices[begin];\n\t\t\tconst int count = i - begin;\n\t\t\tASSERT(offset + count <= geometries_count);\n\t\t\tif (!RT_KusochkiUpload(model->kusochki.offset + offset, geometries + offset, count, NULL, NULL)) {\n\t\t\t\tAPROF_SCOPE_END(update_materials);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbegin = i;\n\t\t}\n\t}\n\n\t{\n\t\tconst int offset = geom_indices[begin];\n\t\tconst int count = geom_indices_count - begin;\n\t\tASSERT(offset + count <= geometries_count);\n\t\tif (!RT_KusochkiUpload(model->kusochki.offset + offset, geometries + offset, count, NULL, NULL)) {\n\n\t\t\tAPROF_SCOPE_END(update_materials);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tAPROF_SCOPE_END(update_materials);\n\treturn true;\n}\n\nstatic qboolean isLegacyBlendingMode(int material_mode) {\n\tswitch (material_mode) {\n\t\tcase MATERIAL_MODE_BLEND_ADD:\n\t\tcase MATERIAL_MODE_BLEND_MIX:\n\t\tcase MATERIAL_MODE_BLEND_GLOW:\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nstatic float sRGBtoLinearScalar(const float sRGB) {\n\t// IEC 61966-2-1:1999\n\tconst float linearLow = sRGB / 12.92f;\n\tconst float linearHigh = powf((sRGB + 0.055f) / 1.055f, 2.4f);\n\treturn sRGB <= 0.04045f ? linearLow : linearHigh;\n}\n\nstatic void sRGBtoLinearVec4(const vec4_t in, vec4_t out) {\n\tout[0] = sRGBtoLinearScalar(in[0]);\n\tout[1] = sRGBtoLinearScalar(in[1]);\n\tout[2] = sRGBtoLinearScalar(in[2]);\n\n\t// Historically: sprite animation lerping is linear\n\t// To-linear conversion should not be done on anything with blending, therefore\n\t// it's irrelevant really.\n\tout[3] = in[3];\n}\n\n/*\nstatic void sRGBAtoLinearVec4(const vec4_t in, vec4_t out) {\n\tout[0] = sRGBtoLinearScalar(in[0]);\n\tout[1] = sRGBtoLinearScalar(in[1]);\n\tout[2] = sRGBtoLinearScalar(in[2]);\n\n\t// α also needs to be linearized for tau-cannon hit position sprite to look okay\n\tout[3] = sRGBtoLinearScalar(in[3]);\n}\n*/\n\nvoid RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args ) {\n\tif (!model || !model->blas)\n\t\treturn;\n\n\tuint32_t kusochki_offset = model->kusochki.offset;\n\n\tif (args.override.material != NULL) {\n\t\tkusochki_offset = RT_KusochkiAllocOnce(args.override.geoms_count);\n\t\tif (kusochki_offset == ALO_ALLOC_FAILED)\n\t\t\treturn;\n\n\t\tif (!RT_KusochkiUpload(kusochki_offset, args.override.geoms, args.override.geoms_count, args.override.material, NULL)) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Couldn't upload kusochki for instanced model\\n\");\n\t\t\treturn;\n\t\t}\n\t}\n\n\trt_draw_instance_t draw_instance = {\n\t\t.blas = model->blas,\n\t\t.kusochki_offset = kusochki_offset,\n\t\t.material_mode = args.material_mode,\n\t\t.material_flags = args.material_flags,\n\t};\n\n\t// Legacy blending is done in sRGB-γ space\n\tif (isLegacyBlendingMode(args.material_mode))\n\t\tVector4Copy(*args.color_srgb, draw_instance.color);\n\telse\n\t\tsRGBtoLinearVec4(*args.color_srgb, draw_instance.color);\n\n\tMatrix3x4_Copy(draw_instance.transform_row, args.transform);\n\tMatrix4x4_Copy(draw_instance.prev_transform_row, args.prev_transform);\n\n\tRT_VkAccelAddDrawInstance(&draw_instance);\n}\n\n#define MAX_RT_DYNAMIC_GEOMETRIES 1024\n#define MAX_RT_DYNAMIC_GEOMETRIES_VERTICES 256\n#define MAX_RT_DYNAMIC_GEOMETRIES_PRIMITIVES 256\n\ntypedef struct {\n\tstruct rt_blas_s *blas;\n\tVkDeviceAddress blas_addr;\n\tvk_render_geometry_t geometries[MAX_RT_DYNAMIC_GEOMETRIES];\n\tint geometries_count;\n\tvec4_t colors[MAX_RT_DYNAMIC_GEOMETRIES];\n} rt_dynamic_t;\n\nstatic const char* group_names[MATERIAL_MODE_COUNT] = {\n\t\"MATERIAL_MODE_OPAQUE\",\n\t\"MATERIAL_MODE_OPAQUE_ALPHA_TEST\",\n\t\"MATERIAL_MODE_TRANSLUCENT\",\n\t\"MATERIAL_MODE_BLEND_ADD\",\n\t\"MATERIAL_MODE_BLEND_MIX\",\n\t\"MATERIAL_MODE_BLEND_GLOW\",\n\t\"MATERIAL_MODE_DECAL\",\n};\n\nstatic struct {\n\trt_dynamic_t groups[MATERIAL_MODE_COUNT];\n} g_dyn;\n\nqboolean RT_DynamicModelInit(void) {\n\tvk_render_geometry_t *const fake_geoms = Mem_Calloc(vk_core.pool, MAX_RT_DYNAMIC_GEOMETRIES * sizeof(*fake_geoms));\n\tfor (int i = 0; i < MAX_RT_DYNAMIC_GEOMETRIES; ++i) {\n\t\tfake_geoms[i].max_vertex = MAX_RT_DYNAMIC_GEOMETRIES_VERTICES;\n\t\tfake_geoms[i].element_count = MAX_RT_DYNAMIC_GEOMETRIES_PRIMITIVES * 3;\n\t}\n\n\tfor (int i = 0; i < MATERIAL_MODE_COUNT; ++i) {\n\t\tstruct rt_blas_s *blas = RT_BlasCreate((rt_blas_create_t){\n\t\t\t.name = group_names[i],\n\t\t\t.usage = kBlasBuildDynamicFast,\n\t\t\t.geoms = fake_geoms,\n\t\t\t.geoms_count = MAX_RT_DYNAMIC_GEOMETRIES,\n\t\t});\n\n\t\tif (!blas) {\n\t\t\t// FIXME destroy allocated\n\t\t\tgEngine.Con_Printf(S_ERROR \"Couldn't create blas for %s\\n\", group_names[i]);\n\t\t\treturn false;\n\t\t}\n\n\t\tg_dyn.groups[i].blas = blas;\n\t}\n\n\tMem_Free(fake_geoms);\n\n\treturn true;\n}\n\nvoid RT_DynamicModelShutdown(void) {\n\tfor (int i = 0; i < MATERIAL_MODE_COUNT; ++i) {\n\t\tRT_BlasDestroy(g_dyn.groups[i].blas);\n\t\tg_dyn.groups[i].blas = NULL;\n\t}\n}\n\nvoid RT_DynamicModelProcessFrame(void) {\n\tAPROF_SCOPE_DECLARE_BEGIN(process, __FUNCTION__);\n\tfor (int i = 0; i < MATERIAL_MODE_COUNT; ++i) {\n\t\trt_dynamic_t *const dyn = g_dyn.groups + i;\n\t\trt_draw_instance_t draw_instance;\n\n\t\tif (!dyn->geometries_count)\n\t\t\tcontinue;\n\n\t\tconst uint32_t kusochki_offset = RT_KusochkiAllocOnce(dyn->geometries_count);\n\t\tif (kusochki_offset == ALO_ALLOC_FAILED) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Couldn't allocate kusochki once for %d geoms of %s, skipping\\n\", dyn->geometries_count, group_names[i]);\n\t\t\tgoto tail;\n\t\t}\n\n\t\tif (!RT_KusochkiUpload(kusochki_offset, dyn->geometries, dyn->geometries_count, NULL, dyn->colors)) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Couldn't build blas for %d geoms of %s, skipping\\n\", dyn->geometries_count, group_names[i]);\n\t\t\tgoto tail;\n\t\t}\n\n\t\tif (!RT_BlasUpdate(dyn->blas, dyn->geometries, dyn->geometries_count)) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Couldn't build blas for %d geoms of %s, skipping\\n\", dyn->geometries_count, group_names[i]);\n\t\t\tgoto tail;\n\t\t}\n\n\t\tdraw_instance = (rt_draw_instance_t){\n\t\t\t.blas = dyn->blas,\n\t\t\t.kusochki_offset = kusochki_offset,\n\t\t\t.material_mode = i,\n\t\t\t.material_flags = 0,\n\t\t\t.color = {1, 1, 1, 1},\n\t\t};\n\n\t\t// xash3d_mathlib is weird, can't just assign these\n\t\t// TODO: make my own mathlib of perfectly assignable structs\n\t\tMatrix3x4_LoadIdentity(draw_instance.transform_row);\n\t\tMatrix4x4_LoadIdentity(draw_instance.prev_transform_row);\n\n\t\tRT_VkAccelAddDrawInstance(&draw_instance);\n\ntail:\n\t\tdyn->geometries_count = 0;\n\t}\n\tAPROF_SCOPE_END(process);\n}\n\nvoid RT_FrameAddOnce( rt_frame_add_once_t args ) {\n\t// TODO pass material_mode explicitly\n\tconst int material_mode = R_VkMaterialModeFromRenderType(args.render_type);\n\trt_dynamic_t *const dyn = g_dyn.groups + material_mode;\n\n\tfor (int i = 0; i < args.geometries_count; ++i) {\n\t\tif (dyn->geometries_count == MAX_RT_DYNAMIC_GEOMETRIES) {\n\t\t\tERROR_THROTTLED(1, \"Too many (>%d) dynamic geometries for mode %s\\n\", MAX_RT_DYNAMIC_GEOMETRIES, group_names[material_mode]);\n\t\t\tbreak;\n\t\t}\n\n\t\t// Legacy blending is done in sRGB-γ space\n\t\tif (isLegacyBlendingMode(material_mode))\n\t\t\tVector4Copy(*args.color_srgb, dyn->colors[dyn->geometries_count]);\n\t\telse\n\t\t\tsRGBtoLinearVec4(*args.color_srgb, dyn->colors[dyn->geometries_count]);\n\n\t\tdyn->geometries[dyn->geometries_count++] = args.geometries[i];\n\t}\n}\n\n"
  },
  {
    "path": "ref/vk/vk_render.c",
    "content": "#include \"vk_render.h\"\n\n#include \"vk_core.h\"\n#include \"vulkan/VBuffer.h\"\n#include \"vk_geometry.h\"\n#include \"vulkan/VBarrier.h\"\n#include \"vulkan/VResource.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"vk_const.h\"\n#include \"vk_common.h\"\n#include \"vk_cvar.h\"\n#include \"vulkan/VPipeline.h\"\n#include \"vk_textures.h\"\n#include \"vk_math.h\"\n#include \"vk_rtx.h\"\n#include \"vulkan/VDescriptor.h\"\n#include \"std/alolcator.h\"\n#include \"std/profiler.h\"\n#include \"r_speeds.h\"\n#include \"camera.h\"\n#include \"r_decals.h\"\n\n#include \"eiface.h\"\n#include \"xash3d_mathlib.h\"\n#include \"protocol.h\" // MAX_DLIGHTS\n#include \"xash3d_types.h\"\n\n#include <memory.h>\n\n#define MODULE_NAME \"render\"\n\n#define MAX_UNIFORM_SLOTS (MAX_SCENE_ENTITIES * 2 /* solid + trans */ + 1)\n\n#define PROFILER_SCOPES(X) \\\n\tX(renderbegin, \"VK_RenderBegin\"); \\\n\n#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope)\nPROFILER_SCOPES(SCOPE_DECLARE)\n#undef SCOPE_DECLARE\n\ntypedef struct {\n\tmatrix4x4 mvp;\n\tvec4_t color;\n} uniform_data_t;\n\ntypedef struct {\n\tmatrix4x4 mvp;\n\tmatrix4x4 inv_proj;\n\tmatrix4x4 inv_view;\n\tvec2_t resolution;\n\tfloat pad_[2];\n} sky_uniform_data_t;\n\nenum {\n\t// These correspond to kVkRenderType*\n\tkVkPipeline_Solid,    // no blending, depth RW\n\tkVkPipeline_A_1mA_RW, // blend: src*a + dst*(1-a), depth: RW\n\tkVkPipeline_A_1mA_R,  // blend: src*a + dst*(1-a), depth test\n\tkVkPipeline_A_1,      // blend: src*a + dst, no depth test or write\n\tkVkPipeline_A_1_R,    // blend: src*a + dst, depth test\n\tkVkPipeline_AT,       // no blend, depth RW, alpha test\n\tkVkPipeline_1_1_R,    // blend: src + dst, depth test\n\tkVkPipeline_Decal, \t  // copy of kVkPipeline_A_1mA_R but for separated decal material\n\tkVkPipeline_COUNT,\n};\n\ntypedef struct {\n\tVkPipeline pipeline;\n#define MAX_CONCURRENT_FRAMES 2\n\tVkDescriptorSet sets[MAX_CONCURRENT_FRAMES];\n\tVkDescriptorSetLayoutBinding bindings[2];\n\tvk_descriptor_value_t values[2];\n\tvk_descriptors_t descs;\n} r_pipeline_sky_t;\n\nstatic struct {\n\tVkPipelineLayout pipeline_layout;\n\tVkPipeline pipelines[kVkPipeline_COUNT];\n\n\tr_pipeline_sky_t pipeline_sky;\n\n\tvk_resource_buffer_t *geometry;\n\n\tvk_buffer_t uniform_buffer;\n\tuint32_t ubo_align;\n\n\tcvar_t *use_material_textures;\n\n\tstruct {\n\t\tint dynamic_model_count;\n\t\tint models_count;\n\t} stats;\n} g_render;\n\nstatic qboolean createPipeline( VkPipeline* out, const char *name, const vk_pipeline_graphics_create_info_t *ci ) {\n\t*out = VK_PipelineGraphicsCreate(ci);\n\n\tif (*out == VK_NULL_HANDLE)\n\t{\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot create render pipeline \\\"%s\\\"\\n\", name);\n\t\treturn false;\n\t}\n\n\tif (vk_core.debug)\n\t{\n\t\tVkDebugUtilsObjectNameInfoEXT debug_name = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,\n\t\t\t.objectHandle = (uint64_t)*out,\n\t\t\t.objectType = VK_OBJECT_TYPE_PIPELINE,\n\t\t\t.pObjectName = name,\n\t\t};\n\t\tXVK_CHECK(vkSetDebugUtilsObjectNameEXT(vk_core.device, &debug_name));\n\t}\n\n\treturn true;\n}\n\nstatic qboolean createSkyboxPipeline( void ) {\n\tconst vk_shader_stage_t sky_shaders[] = {\n\t{\n\t\t.stage = VK_SHADER_STAGE_VERTEX_BIT,\n\t\t.filename = \"sky.vert.spv\",\n\t\t.specialization_info = NULL,\n\t}, {\n\t\t.stage = VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t.filename = \"sky.frag.spv\",\n\t\t.specialization_info = NULL,\n\t}};\n\n\tconst VkVertexInputAttributeDescription attribs[] = {\n\t\t{.binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, pos)},\n\t};\n\n\tg_render.pipeline_sky.bindings[0] = (VkDescriptorSetLayoutBinding){\n    .binding = 0,\n    .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,\n    .descriptorCount = 1,\n    .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t.pImmutableSamplers = NULL,\n\t};\n\tg_render.pipeline_sky.bindings[1] = (VkDescriptorSetLayoutBinding) {\n    .binding = 1,\n    .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n    .descriptorCount = 1,\n    .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t.pImmutableSamplers = NULL,\n\t};\n\n\tg_render.pipeline_sky.descs = (vk_descriptors_t){\n\t\t.num_bindings = COUNTOF(g_render.pipeline_sky.bindings),\n\t\t.bindings = g_render.pipeline_sky.bindings,\n\n\t\t.values = g_render.pipeline_sky.values,\n\n\t\t.push_constants = (VkPushConstantRange){0},\n\n\t\t.num_sets = COUNTOF(g_render.pipeline_sky.sets),\n\t\t.desc_sets = g_render.pipeline_sky.sets,\n\t};\n\n\tVK_DescriptorsCreate(&g_render.pipeline_sky.descs);\n\n\tvk_pipeline_graphics_create_info_t ci = {\n\t\t.layout = g_render.pipeline_sky.descs.pipeline_layout,\n\n\t\t.attribs = attribs,\n\t\t.num_attribs = ARRAYSIZE(attribs),\n\n\t\t.stages = sky_shaders,\n\t\t.num_stages = ARRAYSIZE(sky_shaders),\n\n\t\t.vertex_stride = sizeof(vk_vertex_t),\n\n\t\t.depthTestEnable = VK_TRUE,\n\t\t.depthWriteEnable = VK_TRUE,\n\t\t.depthCompareOp = VK_COMPARE_OP_LESS,\n\n\t\t.blendEnable = VK_FALSE,\n\n\t\t.cullMode = VK_CULL_MODE_FRONT_BIT,\n\t};\n\n\treturn createPipeline(&g_render.pipeline_sky.pipeline, \"sky\", &ci);\n}\n\nstatic qboolean createPipelines( void )\n{\n\tVkDescriptorSetLayout descriptor_layouts[] = {\n\t\tvk_desc_fixme.one_uniform_buffer_layout,\n\t\tvk_desc_fixme.one_texture_layout,\n\t\tvk_desc_fixme.one_texture_layout,\n\t\tvk_desc_fixme.one_uniform_buffer_layout,\n\t};\n\n\tVkPipelineLayoutCreateInfo plci = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,\n\t\t.setLayoutCount = ARRAYSIZE(descriptor_layouts),\n\t\t.pSetLayouts = descriptor_layouts,\n\t};\n\n\t// FIXME store layout separately\n\tXVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &g_render.pipeline_layout));\n\n\t{\n\t\tstruct ShaderSpec {\n\t\t\tfloat alpha_test_threshold;\n\t\t\tuint32_t max_dlights;\n\t\t} spec_data = { .25f, MAX_DLIGHTS };\n\t\tconst VkSpecializationMapEntry spec_map[] = {\n\t\t\t{.constantID = 0, .offset = offsetof(struct ShaderSpec, alpha_test_threshold), .size = sizeof(float) },\n\t\t\t{.constantID = 1, .offset = offsetof(struct ShaderSpec, max_dlights), .size = sizeof(uint32_t) },\n\t\t};\n\n\t\tVkSpecializationInfo shader_spec = {\n\t\t\t.mapEntryCount = ARRAYSIZE(spec_map),\n\t\t\t.pMapEntries = spec_map,\n\t\t\t.dataSize = sizeof(struct ShaderSpec),\n\t\t\t.pData = &spec_data\n\t\t};\n\n\t\tconst VkVertexInputAttributeDescription attribs[] = {\n\t\t\t{.binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, pos)},\n\t\t\t{.binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, normal)},\n\t\t\t{.binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vk_vertex_t, gl_tc)},\n\t\t\t{.binding = 0, .location = 3, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vk_vertex_t, lm_tc)},\n\t\t\t{.binding = 0, .location = 4, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(vk_vertex_t, color)},\n\t\t\t// Not used {.binding = 0, .location = 6, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, prev_pos)},\n\t\t};\n\n\t\tconst vk_shader_stage_t shader_stages[] = {\n\t\t{\n\t\t\t.stage = VK_SHADER_STAGE_VERTEX_BIT,\n\t\t\t.filename = \"brush.vert.spv\",\n\t\t\t.specialization_info = NULL,\n\t\t}, {\n\t\t\t.stage = VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t\t.filename = \"brush.frag.spv\",\n\t\t\t.specialization_info = &shader_spec,\n\t\t}};\n\n\t\tvk_pipeline_graphics_create_info_t ci = {\n\t\t\t.layout = g_render.pipeline_layout,\n\t\t\t.attribs = attribs,\n\t\t\t.num_attribs = ARRAYSIZE(attribs),\n\n\t\t\t.stages = shader_stages,\n\t\t\t.num_stages = ARRAYSIZE(shader_stages),\n\n\t\t\t.vertex_stride = sizeof(vk_vertex_t),\n\n\t\t\t.depthTestEnable = VK_TRUE,\n\t\t\t.depthWriteEnable = VK_TRUE,\n\t\t\t.depthCompareOp = VK_COMPARE_OP_LESS,\n\n\t\t\t.blendEnable = VK_FALSE,\n\n\t\t\t.cullMode = VK_CULL_MODE_FRONT_BIT,\n\t\t};\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.blendEnable = VK_FALSE;\n\t\t\tci.depthWriteEnable = VK_TRUE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_Solid, \"solid\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_TRUE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_A_1mA_RW, \"A_1ma_RW\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_FALSE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_A_1mA_R, \"A_1ma_R\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_FALSE;\n\t\t\tci.depthTestEnable = VK_FALSE; // Fake bloom, should be over geometry too\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_A_1, \"A_1\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_FALSE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_A_1_R, \"A_1_R\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = .25f;\n\t\t\tci.depthWriteEnable = VK_TRUE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_FALSE;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_AT, \"AT\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_FALSE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_1_1_R, \"1_1_R\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\n\t\t{\n\t\t\tspec_data.alpha_test_threshold = 0.f;\n\t\t\tci.depthWriteEnable = VK_FALSE;\n\t\t\tci.depthTestEnable = VK_TRUE;\n\t\t\tci.blendEnable = VK_TRUE;\n\t\t\tci.colorBlendOp = VK_BLEND_OP_ADD;\n\t\t\tci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;\n\t\t\tci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;\n\t\t\tif (!createPipeline(g_render.pipelines + kVkPipeline_Decal, \"Decal\", &ci))\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (!createSkyboxPipeline())\n\t\treturn false;\n\n\treturn true;\n}\n\ntypedef struct {\n\tuint32_t num_lights;\n\tuint32_t debug_r_lightmap;\n\tuint32_t padding_[2];\n\tstruct {\n\t\tvec4_t pos_r;\n\t\tvec4_t color;\n\t} light[MAX_DLIGHTS];\n} vk_ubo_lights_t;\n\n#define MAX_DRAW_COMMANDS 8192 // TODO estimate\n#define MAX_DEBUG_NAME_LENGTH 32\n\ntypedef struct render_draw_s {\n\tuint32_t ubo_offset; // FIXME move this to draw\n\tint lightmap, texture;\n\tint pipeline_index;\n\tuint32_t element_count;\n\tuint32_t index_offset, vertex_offset;\n} render_draw_t;\n\ntypedef struct render_draw_sky_s {\n\tuint32_t element_count;\n\tuint32_t index_offset, vertex_offset;\n\t// TODO matrix4x4 model;\n} render_draw_sky_t;\n\nenum draw_command_type_e {\n\tDrawLabelBegin,\n\tDrawLabelEnd,\n\tDrawDraw,\n\tDrawSky,\n};\n\ntypedef struct {\n\tenum draw_command_type_e type;\n\tunion {\n\t\tchar debug_label[MAX_DEBUG_NAME_LENGTH];\n\t\trender_draw_t draw;\n\t\trender_draw_sky_t draw_sky;\n\t};\n} draw_command_t;\n\nstatic struct {\n\tint uniform_data_set_mask;\n\tuniform_data_t current_uniform_data;\n\tuniform_data_t dirty_uniform_data;\n\n\tr_flipping_buffer_t uniform_alloc;\n\tuint32_t current_ubo_offset_FIXME;\n\n\tdraw_command_t draw_commands[MAX_DRAW_COMMANDS];\n\tint num_draw_commands;\n\n\tmatrix4x4 vk_projection;\n\tmatrix4x4 projection_view;\n\n\tqboolean current_frame_is_ray_traced;\n} g_render_state;\n\nqboolean VK_RenderInit( void ) {\n\tPROFILER_SCOPES(APROF_SCOPE_INIT);\n\n\tg_render.use_material_textures = gEngine.Cvar_Get( \"vk_use_material_textures\", \"0\", FCVAR_GLCONFIG, \"Use PBR material textures for traditional rendering too\" );\n\n\t// TODO type safety\n\tg_render.geometry = (void*)R_VkResourceFindByName(\"geometry\");\n\tASSERT(g_render.geometry);\n\n\tg_render.ubo_align = Q_max(4, v_device_info.properties.limits.minUniformBufferOffsetAlignment);\n\n\tconst uint32_t uniform_unit_size = ((sizeof(uniform_data_t) + g_render.ubo_align - 1) / g_render.ubo_align) * g_render.ubo_align;\n\tconst uint32_t uniform_buffer_size = uniform_unit_size * MAX_UNIFORM_SLOTS;\n\tR_FlippingBuffer_Init(&g_render_state.uniform_alloc, uniform_buffer_size);\n\n\tif (!VK_BufferCreate(\"render uniform_buffer\", &g_render.uniform_buffer, uniform_buffer_size,\n\t\tVK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n\t\tVK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0)))\n\t\treturn false;\n\n\t{\n\t\tVkDescriptorBufferInfo dbi_uniform_data = {\n\t\t\t.buffer = g_render.uniform_buffer.buffer,\n\t\t\t.offset = 0,\n\t\t\t.range = sizeof(uniform_data_t),\n\t\t};\n\t\tVkDescriptorBufferInfo dbi_uniform_lights = {\n\t\t\t.buffer = g_render.uniform_buffer.buffer,\n\t\t\t.offset = 0,\n\t\t\t.range = sizeof(vk_ubo_lights_t),\n\t\t};\n\t\tVkWriteDescriptorSet wds[] = {{\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,\n\t\t\t\t.dstBinding = 0,\n\t\t\t\t.dstArrayElement = 0,\n\t\t\t\t.descriptorCount = 1,\n\t\t\t\t.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,\n\t\t\t\t.pBufferInfo = &dbi_uniform_data,\n\t\t\t\t.dstSet = vk_desc_fixme.ubo_sets[0], // FIXME\n\t\t\t}, {\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,\n\t\t\t\t.dstBinding = 0,\n\t\t\t\t.dstArrayElement = 0,\n\t\t\t\t.descriptorCount = 1,\n\t\t\t\t.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,\n\t\t\t\t.pBufferInfo = &dbi_uniform_lights,\n\t\t\t\t.dstSet = vk_desc_fixme.ubo_sets[1], // FIXME\n\t\t\t}};\n\t\tvkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL);\n\t}\n\n\tif (!createPipelines())\n\t\treturn false;\n\n\tR_SPEEDS_COUNTER(g_render.stats.dynamic_model_count, \"models_dynamic\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_render.stats.models_count, \"models\", kSpeedsMetricCount);\n\treturn true;\n}\n\nvoid VK_RenderShutdown( void )\n{\n\tfor (int i = 0; i < ARRAYSIZE(g_render.pipelines); ++i)\n\t\tvkDestroyPipeline(vk_core.device, g_render.pipelines[i], NULL);\n\tvkDestroyPipelineLayout( vk_core.device, g_render.pipeline_layout, NULL );\n\n\tvkDestroyPipeline(vk_core.device, g_render.pipeline_sky.pipeline, NULL);\n\tVK_DescriptorsDestroy(&g_render.pipeline_sky.descs);\n\n\tVK_BufferDestroy( &g_render.uniform_buffer );\n}\n\nenum {\n\tUNIFORM_UNSET = 0,\n\tUNIFORM_UPLOADED = 16,\n};\n\nvoid VK_RenderBegin( qboolean ray_tracing ) {\n\tAPROF_SCOPE_BEGIN(renderbegin);\n\n\tg_render_state.uniform_data_set_mask = UNIFORM_UNSET;\n\tg_render_state.current_ubo_offset_FIXME = UINT32_MAX;\n\tmemset(&g_render_state.current_uniform_data, 0, sizeof(g_render_state.current_uniform_data));\n\tmemset(&g_render_state.dirty_uniform_data, 0, sizeof(g_render_state.dirty_uniform_data));\n\tR_FlippingBuffer_Flip(&g_render_state.uniform_alloc);\n\n\tg_render_state.num_draw_commands = 0;\n\tg_render_state.current_frame_is_ray_traced = ray_tracing;\n\n\tR_GeometryBuffer_Flip();\n\tR_DecalsFrameBegin();\n\n\tif (ray_tracing)\n\t\tVK_RayFrameBegin();\n\n\tAPROF_SCOPE_END(renderbegin);\n}\n\n// Vulkan has Y pointing down, and z should end up in (0, 1)\n// NOTE this matrix is row-major\nstatic const matrix4x4 vk_proj_fixup = {\n\t{1, 0, 0, 0},\n\t{0, -1, 0, 0},\n\t{0, 0, .5, .5},\n\t{0, 0, 0, 1}\n};\n\nvoid VK_RenderSetupCamera( const struct ref_viewpass_s *rvp ) {\n\tR_SetupCamera(rvp);\n\tMatrix4x4_Concat(g_render_state.vk_projection, vk_proj_fixup, g_camera.projectionMatrix);\n\tMatrix4x4_Concat(g_render_state.projection_view, g_render_state.vk_projection, g_camera.viewMatrix);\n}\n\nstatic uint32_t allocUniform( uint32_t size, uint32_t alignment ) {\n\t// FIXME Q_max is not correct, we need NAIMENSCHEEE OBSCHEEE KRATNOE\n\tconst uint32_t align = Q_max(alignment, g_render.ubo_align);\n\tconst uint32_t offset = R_FlippingBuffer_Alloc(&g_render_state.uniform_alloc, size, align);\n\treturn offset;\n}\n\nstatic draw_command_t *drawCmdAlloc( void ) {\n\tASSERT(g_render_state.num_draw_commands < ARRAYSIZE(g_render_state.draw_commands));\n\treturn g_render_state.draw_commands + (g_render_state.num_draw_commands++);\n}\n\nstatic void drawCmdPushDebugLabelBegin( const char *debug_label ) {\n\tif (vk_core.debug) {\n\t\tdraw_command_t *draw_command = drawCmdAlloc();\n\t\tdraw_command->type = DrawLabelBegin;\n\t\tQ_strncpy(draw_command->debug_label, debug_label, sizeof draw_command->debug_label);\n\t}\n}\n\nstatic void drawCmdPushDebugLabelEnd( void ) {\n\tif (vk_core.debug) {\n\t\tdraw_command_t *draw_command = drawCmdAlloc();\n\t\tdraw_command->type = DrawLabelEnd;\n\t}\n}\n\n// FIXME get rid of this garbage\nstatic uint32_t getUboOffset_FIXME( void ) {\n\t// Figure out whether we need to update UBO data, and upload new data if we do\n\t// TODO generally it's not safe to do memcmp for structures comparison\n\tif (g_render_state.current_ubo_offset_FIXME == UINT32_MAX\n\t\t|| ((g_render_state.uniform_data_set_mask & UNIFORM_UPLOADED) == 0)\n\t\t|| memcmp(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.current_uniform_data)) != 0) {\n\t\tg_render_state.current_ubo_offset_FIXME = allocUniform(sizeof(uniform_data_t), 16 /* why 16? vec4? */);\n\n\t\tif (g_render_state.current_ubo_offset_FIXME == ALO_ALLOC_FAILED)\n\t\t\treturn UINT32_MAX;\n\n\t\tuniform_data_t *const ubo = PTR_CAST(uniform_data_t, (byte*)g_render.uniform_buffer.mapped + g_render_state.current_ubo_offset_FIXME);\n\t\tmemcpy(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.dirty_uniform_data));\n\t\tmemcpy(ubo, &g_render_state.current_uniform_data, sizeof(*ubo));\n\t\tg_render_state.uniform_data_set_mask |= UNIFORM_UPLOADED;\n\t}\n\n\treturn g_render_state.current_ubo_offset_FIXME;\n}\n\nstatic void drawCmdPushDraw( const render_draw_t *draw )\n{\n\tdraw_command_t *draw_command;\n\n\tASSERT(draw->pipeline_index >= 0);\n\tASSERT(draw->pipeline_index < ARRAYSIZE(g_render.pipelines));\n\tASSERT(draw->lightmap >= 0);\n\tASSERT(draw->texture >= 0);\n\tASSERT(draw->texture < MAX_TEXTURES);\n\n\tif (g_render_state.num_draw_commands >= ARRAYSIZE(g_render_state.draw_commands)) {\n\t\tgEngine.Con_Printf( S_ERROR \"Maximum number of draw commands reached\\n\" );\n\t\treturn;\n\t}\n\n\tconst uint32_t ubo_offset = getUboOffset_FIXME();\n\tif (ubo_offset == ALO_ALLOC_FAILED) {\n\t\t// TODO stagger this\n\t\tgEngine.Con_Printf( S_ERROR \"Ran out of uniform slots\\n\" );\n\t\treturn;\n\t}\n\n\tdraw_command = drawCmdAlloc();\n\tdraw_command->draw = *draw;\n\tdraw_command->draw.ubo_offset = ubo_offset;\n\tdraw_command->type = DrawDraw;\n}\n\nstatic void drawCmdPushDrawSky( const render_draw_sky_t *draw_sky )\n{\n\tdraw_command_t *draw_command;\n\n\tif (g_render_state.num_draw_commands >= ARRAYSIZE(g_render_state.draw_commands)) {\n\t\tgEngine.Con_Printf( S_ERROR \"Maximum number of draw commands reached\\n\" );\n\t\treturn;\n\t}\n\n\tdraw_command = drawCmdAlloc();\n\tdraw_command->draw_sky = *draw_sky;\n\tdraw_command->type = DrawSky;\n}\n\n// Return offset of dlights data into UBO buffer\nstatic uint32_t writeDlightsToUBO( void )\n{\n\tvk_ubo_lights_t* ubo_lights;\n\tint num_lights = 0;\n\tconst uint32_t ubo_lights_offset = allocUniform(sizeof(*ubo_lights), 4);\n\tif (ubo_lights_offset == UINT32_MAX) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot allocate UBO for DLights\\n\");\n\t\treturn UINT32_MAX;\n\t}\n\tubo_lights = PTR_CAST(vk_ubo_lights_t, (byte*)(g_render.uniform_buffer.mapped) + ubo_lights_offset);\n\n\t// TODO this should not be here (where? vk_scene?)\n\tfor (int i = 0; i < MAX_DLIGHTS && num_lights < ARRAYSIZE(ubo_lights->light); ++i) {\n\t\tconst dlight_t *l = globals.dlights + i;\n\t\tif( !l || l->die < gp_cl->time || !l->radius )\n\t\t\tcontinue;\n\t\tVector4Set(\n\t\t\tubo_lights->light[num_lights].color,\n\t\t\tl->color.r / 255.f,\n\t\t\tl->color.g / 255.f,\n\t\t\tl->color.b / 255.f,\n\t\t\t1.f);\n\t\tVector4Set(\n\t\t\tubo_lights->light[num_lights].pos_r,\n\t\t\tl->origin[0],\n\t\t\tl->origin[1],\n\t\t\tl->origin[2],\n\t\t\tl->radius);\n\n\t\tnum_lights++;\n\t}\n\n\tubo_lights->num_lights = num_lights;\n\tubo_lights->debug_r_lightmap = r_lightmap->value != 0;\n\treturn ubo_lights_offset;\n}\n\n// FIXME: how to do this properly before render pass?\n// Needed to avoid VUID-vkCmdCopyBuffer-renderpass\nvoid VK_RenderEndPrepare_FIXME( struct vk_combuf_s* combuf, const FrameContext *ctx ) {\n\tR_VkResourceProduce(&g_render.geometry->header, combuf, ctx);\n\n\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT);\n\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t){\n\t\t.buffer = g_render.geometry->buffer,\n\t\t.access = VK_ACCESS_2_INDEX_READ_BIT | VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT,\n\t});\n\tbarrierCommit(&barrier, combuf);\n}\n\nvoid VK_RenderEnd( vk_combuf_t* combuf, qboolean draw, uint32_t width, uint32_t height, int frame_index )\n{\n\tif (!draw)\n\t\treturn;\n\n\tVkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\t// TODO we can sort collected draw commands for more efficient and correct rendering\n\t// that requires adding info about distance to camera for correct order-dependent blending\n\n\tstruct {\n\t\tVkPipeline pipeline;\n\t\tint texture;\n\t\tint lightmap;\n\t\tuint32_t ubo_offset;\n\t} cur = {\n\t\t.pipeline = VK_NULL_HANDLE,\n\t\t.texture = -1,\n\t\t.lightmap = -1,\n\t\t.ubo_offset = -1,\n\t};\n\n\tconst uint32_t dlights_ubo_offset = writeDlightsToUBO();\n\tif (dlights_ubo_offset == UINT32_MAX)\n\t\treturn;\n\n\tASSERT(!g_render_state.current_frame_is_ray_traced);\n\n\t{\n\t\tvk_buffer_t* const geom = g_render.geometry->buffer;\n\t\tASSERT(geom->sync.read.stage & VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT);\n\t\tASSERT(geom->sync.read.access & VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT);\n\t\tASSERT(geom->sync.read.access & VK_ACCESS_2_INDEX_READ_BIT);\n\t\tconst VkDeviceSize offset = 0;\n\t\tvkCmdBindVertexBuffers(cmdbuf, 0, 1, &geom->buffer, &offset);\n\t\tvkCmdBindIndexBuffer(cmdbuf, geom->buffer, 0, VK_INDEX_TYPE_UINT16);\n\t}\n\n\tfor (int i = 0; i < g_render_state.num_draw_commands; ++i) {\n\t\tconst draw_command_t *const draw = g_render_state.draw_commands + i;\n\n\t\tswitch (draw->type) {\n\t\t\tcase DrawLabelBegin:\n\t\t\t{\n\t\t\t\tconst VkDebugUtilsLabelEXT label = {\n\t\t\t\t\t.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,\n\t\t\t\t\t.pLabelName = draw->debug_label,\n\t\t\t\t};\n\t\t\t\tvkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcase DrawLabelEnd:\n\t\t\t\tvkCmdEndDebugUtilsLabelEXT(cmdbuf);\n\t\t\t\tcontinue;\n\n\t\t\tcase DrawSky:\n\t\t\t{\n\t\t\t\tconst render_draw_sky_t *draw_sky = &draw->draw_sky;\n\n\t\t\t\tif (cur.pipeline != g_render.pipeline_sky.pipeline) {\n\t\t\t\t\tconst uint32_t ubo_offset = allocUniform(sizeof(sky_uniform_data_t), 16 /*?*/);\n\t\t\t\t\tif (g_render_state.current_ubo_offset_FIXME == ALO_ALLOC_FAILED)\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t// Compute and upload UBO stuff\n\t\t\t\t\t{\n\t\t\t\t\t\tsky_uniform_data_t* const sky_ubo = PTR_CAST(sky_uniform_data_t, (byte*)g_render.uniform_buffer.mapped + ubo_offset);\n\n\t\t\t\t\t\t// FIXME model matrix\n\t\t\t\t\t\tMatrix4x4_ToArrayFloatGL(g_render_state.projection_view, (float*)sky_ubo->mvp);\n\n\t\t\t\t\t\tsky_ubo->resolution[0] = width;\n\t\t\t\t\t\tsky_ubo->resolution[1] = height;\n\n\t\t\t\t\t\t// TODO DRY, this is copypasted from vk_rtx.c\n\t\t\t\t\t\tmatrix4x4 proj_inv, view_inv;\n\t\t\t\t\t\tMatrix4x4_Invert_Full(proj_inv, g_render_state.vk_projection);\n\t\t\t\t\t\tMatrix4x4_ToArrayFloatGL(proj_inv, (float*)sky_ubo->inv_proj);\n\n\t\t\t\t\t\t// TODO there's a more efficient way to construct an inverse view matrix\n\t\t\t\t\t\t// from vforward/right/up vectors and origin in g_camera\n\t\t\t\t\t\tMatrix4x4_Invert_Full(view_inv, g_camera.viewMatrix);\n\t\t\t\t\t\tMatrix4x4_ToArrayFloatGL(view_inv, (float*)sky_ubo->inv_view);\n\t\t\t\t\t}\n\n\t\t\t\t\tcur.pipeline = g_render.pipeline_sky.pipeline;\n\t\t\t\t\tvkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, cur.pipeline);\n\n\t\t\t\t\tg_render.pipeline_sky.values[0].buffer = (VkDescriptorBufferInfo){\n\t\t\t\t\t\t.buffer = g_render.uniform_buffer.buffer,\n\t\t\t\t\t\t.offset = 0,\n\t\t\t\t\t\t.range = sizeof(sky_uniform_data_t),\n\t\t\t\t\t};\n\t\t\t\t\tg_render.pipeline_sky.values[1].image = R_VkTexturesGetSkyboxDescriptorImageInfo( kSkyboxOriginal );\n\t\t\t\t\tVK_DescriptorsWrite(&g_render.pipeline_sky.descs, frame_index);\n\n\t\t\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS,\n\t\t\t\t\t\tg_render.pipeline_sky.descs.pipeline_layout, 0, 1, g_render.pipeline_sky.sets + frame_index, 1, &ubo_offset);\n\t\t\t\t}\n\n\t\t\t\tASSERT(draw_sky->index_offset >= 0);\n\t\t\t\tvkCmdDrawIndexed(cmdbuf, draw_sky->element_count, 1, draw_sky->index_offset, draw_sky->vertex_offset, 0);\n\n\t\t\t\t// Reset current draw state\n\t\t\t\tcur.texture = -1;\n\t\t\t\tcur.lightmap = -1;\n\t\t\t\tcur.ubo_offset = -1;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcase DrawDraw:\n\t\t\t\t// Continue drawing below\n\t\t\t\tbreak;\n\t\t}\n\n\t\tASSERT(draw->draw.pipeline_index >= 0);\n\t\tASSERT(draw->draw.pipeline_index < COUNTOF(g_render.pipelines));\n\t\tconst VkPipeline pipeline = g_render.pipelines[draw->draw.pipeline_index];\n\n\t\tif (cur.pipeline != pipeline) {\n\t\t\tcur.pipeline = pipeline;\n\t\t\tvkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, cur.pipeline);\n\n\t\t\t// Make sure that after pipeline change we have this bound correctly\n\t\t\t// Pipeline change might be due to previous pipeline being skybox, which has\n\t\t\t// incompatible layout\n\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc_fixme.ubo_sets + 1, 1, &dlights_ubo_offset);\n\t\t}\n\n\t\tif (cur.ubo_offset != draw->draw.ubo_offset)\n\t\t{\n\t\t\tcur.ubo_offset = draw->draw.ubo_offset;\n\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc_fixme.ubo_sets, 1, &cur.ubo_offset);\n\t\t}\n\n\t\tif (cur.lightmap != draw->draw.lightmap) {\n\t\t\tcur.lightmap = draw->draw.lightmap;\n\t\t\tconst VkDescriptorSet lm_unorm = R_VkTextureGetDescriptorUnorm(cur.lightmap);\n\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &lm_unorm, 0, NULL);\n\t\t}\n\n\t\tif (cur.texture != draw->draw.texture)\n\t\t{\n\t\t\tcur.texture = draw->draw.texture;\n\t\t\tconst VkDescriptorSet tex_unorm = R_VkTextureGetDescriptorUnorm(cur.texture);\n\t\t\t// TODO names/enums for binding points\n\t\t\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &tex_unorm, 0, NULL);\n\t\t}\n\n\t\t// Only indexed mode is supported\n\t\tASSERT(draw->draw.index_offset >= 0);\n\t\tvkCmdDrawIndexed(cmdbuf, draw->draw.element_count, 1, draw->draw.index_offset, draw->draw.vertex_offset, 0);\n\t}\n}\n\nvoid VK_RenderDebugLabelBegin( const char *name )\n{\n\tdrawCmdPushDebugLabelBegin(name);\n}\n\nvoid VK_RenderDebugLabelEnd( void )\n{\n\tdrawCmdPushDebugLabelEnd();\n}\n\nvoid VK_RenderEndRTX( struct vk_combuf_s* combuf, struct r_vk_image_s *dst) {\n\tASSERT(vk_core.rtx);\n\n\t{\n\t\tconst vk_ray_frame_render_args_t args = {\n\t\t\t.combuf = combuf,\n\t\t\t.dst = dst,\n\n\t\t\t.projection = &g_render_state.vk_projection,\n\t\t\t.view = &g_camera.viewMatrix,\n\n\t\t\t.fov_angle_y = g_camera.fov_y,\n\t\t};\n\n\t\tVK_RayFrameEnd(&args);\n\t}\n}\n\nqboolean R_RenderModelCreate( vk_render_model_t *model, vk_render_model_init_t args ) {\n\tmemset(model, 0, sizeof(*model));\n\tQ_strncpy(model->debug_name, args.name, sizeof(model->debug_name));\n\n\tmodel->geometries = args.geometries;\n\tmodel->num_geometries = args.geometries_count;\n\n\tif (!vk_core.rtx)\n\t\treturn true;\n\n\tmodel->rt_model = RT_ModelCreate((rt_model_create_t){\n\t\t.debug_name = model->debug_name,\n\t\t.geometries = args.geometries,\n\t\t.geometries_count = args.geometries_count,\n\t\t.usage = args.dynamic ? kBlasBuildDynamicUpdate : kBlasBuildStatic,\n\t});\n\treturn !!model->rt_model;\n}\n\nvoid R_RenderModelDestroy( vk_render_model_t* model ) {\n\tif (model->rt_model)\n\t\tRT_ModelDestroy(model->rt_model);\n}\n\nqboolean R_RenderModelUpdate( const vk_render_model_t *model ) {\n\t// Non-RT rendering doesn't need to update anything, assuming that geometry regions offsets are not changed, and losing intermediate states is fine\n\tif (!g_render_state.current_frame_is_ray_traced)\n\t\treturn true;\n\n\tASSERT(model->rt_model);\n\n\treturn RT_ModelUpdate(model->rt_model, model->geometries, model->num_geometries);\n}\n\nqboolean R_RenderModelUpdateMaterials( const vk_render_model_t *model, const int *geom_indices, int geom_indices_count) {\n\tif (!model->rt_model)\n\t\treturn true;\n\n\treturn RT_ModelUpdateMaterials(model->rt_model, model->geometries, model->num_geometries, geom_indices, geom_indices_count);\n}\n\nstatic void uboComputeAndSetMVPFromModel( const matrix4x4 model ) {\n\tmatrix4x4 mvp;\n\tMatrix4x4_Concat(mvp, g_render_state.projection_view, model);\n\tMatrix4x4_ToArrayFloatGL(mvp, (float*)g_render_state.dirty_uniform_data.mvp);\n}\n\ntypedef struct {\n\tconst char *debug_name;\n\tint lightmap; // TODO per-geometry\n\tconst vk_render_geometry_t *geometries;\n\tint geometries_count;\n\tconst matrix4x4 *transform;\n\tconst vec4_t *color;\n\tint render_type;\n\tint textures_override;\n} trad_submit_t;\n\nstatic void submitToTraditionalRender( trad_submit_t args ) {\n\tint current_texture = args.textures_override;\n\tint element_count = 0;\n\tint index_offset = -1;\n\tint vertex_offset = 0;\n\n\t// TODO get rid of this dirty ubo thing\n\tuboComputeAndSetMVPFromModel( *args.transform );\n\tVector4Copy(*args.color, g_render_state.dirty_uniform_data.color);\n\n\tASSERT(args.lightmap <= MAX_LIGHTMAPS);\n\tconst int lightmap = args.lightmap > 0 ? tglob.lightmapTextures[args.lightmap - 1] : tglob.whiteTexture;\n\n\tdrawCmdPushDebugLabelBegin( args.debug_name );\n\n\tfor (int i = 0; i < args.geometries_count; ++i) {\n\t\tconst vk_render_geometry_t *geom = args.geometries + i;\n\t\tconst int tex_mat = geom->material.tex_base_color;\n\t\tconst int geom_tex = g_render.use_material_textures->value && (tex_mat > 0 && tex_mat < MAX_TEXTURES) ? tex_mat : geom->ye_olde_texture;\n\t\tconst int tex = args.textures_override > 0 ? args.textures_override : geom_tex;\n\t\tconst qboolean split =\n\t\t\t   current_texture != tex\n\t\t\t|| vertex_offset != geom->vertex_offset\n\t\t\t|| (index_offset + element_count) != geom->index_offset;\n\n\t\t// We only support indexed geometry\n\t\tASSERT(geom->index_offset >= 0);\n\n\t\tif (tex < 0)\n\t\t\tcontinue;\n\n\t\t// TODO consider tracking contiguousness in drawCmdPushDraw(Sky)()\n\t\t// Why: we could easily check that the previous command in the command list\n\t\t// is contiguous, and could just increase its counts w/o submitting a new command\n\t\t// This would make this code here a bit more readable and single-purpose.\n\t\tif (split) {\n\t\t\tif (element_count) {\n\t\t\t\tif (current_texture == TEX_BASE_SKYBOX) {\n\t\t\t\t\tdrawCmdPushDrawSky(&(render_draw_sky_t){\n\t\t\t\t\t\t.element_count = element_count,\n\t\t\t\t\t\t.vertex_offset = vertex_offset,\n\t\t\t\t\t\t.index_offset = index_offset,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\trender_draw_t draw = {\n\t\t\t\t\t\t.lightmap = lightmap,\n\t\t\t\t\t\t.texture = current_texture,\n\t\t\t\t\t\t.pipeline_index = args.render_type,\n\t\t\t\t\t\t.element_count = element_count,\n\t\t\t\t\t\t.vertex_offset = vertex_offset,\n\t\t\t\t\t\t.index_offset = index_offset,\n\t\t\t\t\t};\n\n\t\t\t\t\tdrawCmdPushDraw( &draw );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent_texture = tex;\n\t\t\tindex_offset = geom->index_offset;\n\t\t\tvertex_offset = geom->vertex_offset;\n\t\t\telement_count = 0;\n\t\t}\n\n\t\t// Make sure that all surfaces are concatenated in buffers\n\t\tASSERT(index_offset + element_count == geom->index_offset);\n\t\telement_count += geom->element_count;\n\t}\n\n\tif (element_count) {\n\t\tif (current_texture == TEX_BASE_SKYBOX) {\n\t\t\tdrawCmdPushDrawSky(&(render_draw_sky_t){\n\t\t\t\t.element_count = element_count,\n\t\t\t\t.vertex_offset = vertex_offset,\n\t\t\t\t.index_offset = index_offset,\n\t\t\t});\n\t\t} else {\n\t\t\tconst render_draw_t draw = {\n\t\t\t\t.lightmap = lightmap,\n\t\t\t\t.texture = current_texture,\n\t\t\t\t.pipeline_index = args.render_type,\n\t\t\t\t.element_count = element_count,\n\t\t\t\t.vertex_offset = vertex_offset,\n\t\t\t\t.index_offset = index_offset,\n\t\t\t};\n\n\t\t\tdrawCmdPushDraw( &draw );\n\t\t}\n\t}\n\n\tdrawCmdPushDebugLabelEnd();\n}\n\nvoid R_RenderModelDraw(const vk_render_model_t *model, r_model_draw_t args) {\n\t++g_render.stats.models_count;\n\n\tif (g_render_state.current_frame_is_ray_traced) {\n\t\tASSERT(model->rt_model);\n\t\tRT_FrameAddModel(model->rt_model, (rt_frame_add_model_t){\n\t\t\t.material_mode = args.material_mode,\n\t\t\t.material_flags = args.material_flags,\n\t\t\t.transform = (const matrix3x4*)args.transform,\n\t\t\t.prev_transform = (const matrix3x4*)args.prev_transform,\n\t\t\t.color_srgb = args.color,\n\t\t\t.override = {\n\t\t\t\t.material = args.override.material,\n\t\t\t\t.geoms = model->geometries,\n\t\t\t\t.geoms_count = model->num_geometries,\n\t\t\t},\n\t\t});\n\t} else {\n\t\tsubmitToTraditionalRender((trad_submit_t){\n\t\t\t.debug_name = model->debug_name,\n\t\t\t.lightmap = model->lightmap,\n\t\t\t.geometries = model->geometries,\n\t\t\t.geometries_count = model->num_geometries,\n\t\t\t.transform = args.transform,\n\t\t\t.color = args.color,\n\t\t\t.render_type = args.render_type,\n\t\t\t.textures_override = args.override.old_texture,\n\t\t});\n\t}\n}\n\nvoid R_RenderDrawOnce(r_draw_once_t args) {\n\tr_geometry_buffer_lock_t buffer;\n\tif (!R_GeometryBufferAllocOnceAndLock( &buffer, args.vertices_count, args.indices_count)) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot allocate geometry for dynamic draw\\n\");\n\t\treturn;\n\t}\n\n\tmemcpy(buffer.vertices.ptr, args.vertices, sizeof(vk_vertex_t) * args.vertices_count);\n\tmemcpy(buffer.indices.ptr, args.indices, sizeof(uint16_t) * args.indices_count);\n\n\tR_GeometryBufferUnlock( &buffer );\n\n\tconst vk_render_geometry_t geometry = {\n\t\t.material = args.material,\n\t\t.ye_olde_texture = args.ye_olde_texture,\n\n\t\t.max_vertex = args.vertices_count,\n\t\t.vertex_offset = buffer.vertices.unit_offset,\n\n\t\t.element_count = args.indices_count,\n\t\t.index_offset = buffer.indices.unit_offset,\n\n\t\t.emissive = { (*args.color)[0], (*args.color)[1], (*args.color)[2] },\n\t};\n\n\tif (g_render_state.current_frame_is_ray_traced) {\n\t\tRT_FrameAddOnce((rt_frame_add_once_t){\n\t\t\t.debug_name = args.name,\n\t\t\t.geometries = &geometry,\n\t\t\t.color_srgb = args.color,\n\t\t\t.geometries_count = 1,\n\t\t\t.render_type = args.render_type,\n\t\t});\n\t} else {\n\t\tmatrix4x4 identity;\n\t\tMatrix4x4_LoadIdentity(identity);\n\t\tsubmitToTraditionalRender((trad_submit_t){\n\t\t\t.debug_name = args.name,\n\t\t\t.lightmap = args.lightmap,\n\t\t\t.geometries = &geometry,\n\t\t\t.geometries_count = 1,\n\t\t\t.transform = &identity,\n\t\t\t.color = args.color,\n\t\t\t.render_type = args.render_type,\n\t\t\t.textures_override = -1,\n\t\t});\n\t}\n\n\tg_render.stats.dynamic_model_count++;\n}\n"
  },
  {
    "path": "ref/vk/vk_render.h",
    "content": "#pragma once\n#include \"vk_materials.h\"\n#include \"vk_common.h\"\n#include \"vk_const.h\"\n#include \"vk_core.h\"\n\nqboolean VK_RenderInit( void );\nvoid VK_RenderShutdown( void );\n\nstruct ref_viewpass_s;\nvoid VK_RenderSetupCamera( const struct ref_viewpass_s *rvp );\n\n#define TEX_BASE_SKYBOX 0x0f000000u // FIXME ray_interop.h\n\ntypedef struct vk_render_geometry_s {\n\tint index_offset, vertex_offset;\n\n\tuint32_t element_count;\n\n\t// Maximum index of vertex used for this geometry; needed for ray tracing BLAS building\n\tuint32_t max_vertex;\n\n\t// Non-null only for brush models\n\t// Used for updating animated textures for brush models\n\t// Remove: have an explicit list of surfaces with animated textures\n\tstruct msurface_s *surf_deprecate;\n\n\t// If this geometry is special, it will have a material type override\n\tr_vk_material_t material;\n\n\t// Olde unpatched texture used for traditional renderer\n\tint ye_olde_texture;\n\n\t// for kXVkMaterialEmissive{,Glow} and others\n\tvec3_t emissive;\n} vk_render_geometry_t;\n\ntypedef enum {\n\tkVkRenderTypeSolid,     // no blending, depth RW\n\n\t// Mix alpha blending with depth test and write\n\t// Set by:\n\t// - brush:  kRenderTransColor\n\t// - studio: kRenderTransColor, kRenderTransTexture, kRenderTransAlpha, kRenderGlow\n\t// - sprite: kRenderTransColor, kRenderTransTexture\n\t// - triapi: kRenderTransColor, kRenderTransTexture\n\tkVkRenderType_A_1mA_RW, // blend: src*a + dst*(1-a), depth: RW\n\n\t// Mix alpha blending with depth test only\n\t// Set by:\n\t// - brush:  kRenderTransTexture, kRenderGlow\n\t// - sprite: kRenderTransAlpha\n\t// - triapi: kRenderTransAlpha\n\tkVkRenderType_A_1mA_R,  // blend: src*a + dst*(1-a), depth test\n\n\t// Additive alpha blending, no depth\n\t// Set by:\n\t// - sprite: kRenderGlow\n\tkVkRenderType_A_1,      // blend: src*a + dst, no depth test or write\n\n\t// Additive alpha blending with depth test\n\t// Set by:\n\t// - brush: kRenderTransAdd\n\t// - beams: all modes except kRenderNormal and beams going through triapi\n\t// - sprite: kRenderTransAdd\n\t// - triapi: kRenderTransAdd, kRenderGlow\n\tkVkRenderType_A_1_R,    // blend: src*a + dst, depth test\n\n\t// No blend, alpha test, depth test and write\n\t// Set by:\n\t// - brush: kRenderTransAlpha\n\tkVkRenderType_AT,       // no blend, depth RW, alpha test\n\n\t// Additive no alpha blend, depth test only\n\t// Set by:\n\t// - studio: kRenderTransAdd\n\tkVkRenderType_1_1_R,    // blend: src + dst, depth test\n\n\t// Decals changing diffuse and rmxx of surface material\n\tkVkRenderType_Decal,    // blend: src*a + dst (1-a), depth test, no depth write\n\n\tkVkRenderType_COUNT\n} vk_render_type_e;\n\ntypedef enum {\n\t// MUST be congruent to MATERIAL_MODE_* definitions in shaders/ray_interop.h\n\tkMaterialMode_Opaque = 0,\n\tkMaterialMode_AlphaTest = 1,\n\tkMaterialMode_Translucent = 2,\n\tkMaterialMode_BlendAdd = 3,\n\tkMaterialMode_BlendMix = 4,\n\tkMaterialMode_BlendGlow = 5,\n\n\tkMaterialMode_COUNT = 6,\n} material_mode_e;\n\nuint32_t R_VkMaterialModeFromRenderType(vk_render_type_e render_type);\n\nstruct rt_light_add_polygon_s;\nstruct rt_model_s;\n\ntypedef struct vk_render_model_s {\n#define MAX_MODEL_NAME_LENGTH 64\n\tchar debug_name[MAX_MODEL_NAME_LENGTH];\n\n\t// TODO per-geometry?\n\tint lightmap; // <= 0 if no lightmap\n\n\tint num_geometries;\n\tvk_render_geometry_t *geometries;\n\n\tstruct rt_model_s *rt_model;\n} vk_render_model_t;\n\n// Initialize model from scratch\ntypedef struct {\n\tconst char *name;\n\tvk_render_geometry_t *geometries;\n\tint geometries_count;\n\n\t// Geometry data can and will be updated\n\t// Upading geometry locations is not supported though, only vertex/index values\n\tqboolean dynamic;\n} vk_render_model_init_t;\nqboolean R_RenderModelCreate( vk_render_model_t *model, vk_render_model_init_t args );\nvoid R_RenderModelDestroy( vk_render_model_t* model );\n\nqboolean R_RenderModelUpdate( const vk_render_model_t *model );\nqboolean R_RenderModelUpdateMaterials( const vk_render_model_t *model, const int *geom_indices, int geom_indices_count);\n\ntypedef enum {\n\tkMaterialFlag_None = 0,\n\tkMaterialFlag_CullBackFace_Bit = (1<<0),\n\tkMaterialFlag_DontCastShadow_Bit = (1<<1),\n} material_flag_bits_e;\n\ntypedef struct {\n\tvk_render_type_e render_type; // TODO rename legacy\n\tmaterial_mode_e material_mode;\n\tuint32_t material_flags; // material_flag_bits_e\n\n\t// These are \"consumed\": copied into internal storage and can be pointers to stack vars\n\tconst vec4_t *color;\n\tconst matrix4x4 *transform, *prev_transform;\n\n\tstruct {\n\t\tconst r_vk_material_t* material;\n\t\tint old_texture;\n\t} override;\n} r_model_draw_t;\n\nvoid R_RenderModelDraw(const vk_render_model_t *model, r_model_draw_t args);\n\ntypedef struct {\n\tconst char *name;\n\tconst struct vk_vertex_s *vertices;\n\tconst uint16_t *indices;\n\tint vertices_count, indices_count;\n\n\tint render_type;\n\tr_vk_material_t material;\n\tint ye_olde_texture;\n\tint lightmap;\n\tconst vec4_t *emissive;\n\tconst vec4_t *color;\n} r_draw_once_t;\nvoid R_RenderDrawOnce(r_draw_once_t args);\n\nvoid VK_RenderDebugLabelBegin( const char *label );\nvoid VK_RenderDebugLabelEnd( void );\n\nvoid VK_RenderBegin( qboolean ray_tracing );\n\nstruct vk_combuf_s;\nstruct FrameContext;\nvoid VK_RenderEndPrepare_FIXME( struct vk_combuf_s* combuf, const struct FrameContext *ctx );\nvoid VK_RenderEnd( struct vk_combuf_s*, qboolean draw, uint32_t width, uint32_t height, int frame_index );\n\nstruct r_vk_image_s;\nvoid VK_RenderEndRTX( struct vk_combuf_s* combuf, struct r_vk_image_s *dst);\n"
  },
  {
    "path": "ref/vk/vk_renderstate.c",
    "content": "#include \"vk_renderstate.h\"\n\n#include \"vk_core.h\"\n\n#include \"cvardef.h\"\n#include \"const.h\"\n#include \"ref_api.h\"\n#include \"com_strings.h\"\n#include \"eiface.h\" // ARRAYSIZE\n\nrender_state_t vk_renderstate = {0};\n\n/*\nstatic const char *renderModeName(int mode)\n{\n\tswitch(mode)\n\t{\n\t\tcase kRenderNormal: return \"kRenderNormal\";\n\t\tcase kRenderTransColor: return \"kRenderTransColor\";\n\t\tcase kRenderTransTexture: return \"kRenderTransTexture\";\n\t\tcase kRenderGlow: return \"kRenderGlow\";\n\t\tcase kRenderTransAlpha: return \"kRenderTransAlpha\";\n\t\tcase kRenderTransAdd: return \"kRenderTransAdd\";\n\t\tdefault: return \"INVALID\";\n\t}\n}\n*/\n\nvoid GL_SetRenderMode( int renderMode )\n{\n\tvk_renderstate.blending_mode = renderMode;\n}\n\nvoid TriColor4ub( unsigned char r, unsigned char g, unsigned char b, unsigned char a )\n{\n\tvk_renderstate.tri_color = (color_rgba8_t){r, g, b, a};\n}\n\nvoid R_AllowFog( qboolean allow )\n{\n\tvk_renderstate.fog_allowed = allow;\n}\n\nvoid R_Set2DMode( qboolean enable )\n{\n\tvk_renderstate.mode_2d = enable;\n}\n\n"
  },
  {
    "path": "ref/vk/vk_renderstate.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\ntypedef struct { uint8_t r, g, b, a; } color_rgba8_t;\n\ntypedef struct render_state_s {\n\tcolor_rgba8_t tri_color;\n\tqboolean fog_allowed;\n\tqboolean mode_2d;\n\tint blending_mode; // kRenderNormal, ...\n} render_state_t;\n\nextern render_state_t vk_renderstate;\n\nvoid GL_SetRenderMode( int renderMode );\nvoid TriColor4ub( unsigned char r, unsigned char g, unsigned char b, unsigned char a );\nvoid R_AllowFog( qboolean allow );\nvoid R_Set2DMode( qboolean enable );\n"
  },
  {
    "path": "ref/vk/vk_rmain.c",
    "content": "#include \"vk_core.h\"\n#include \"vk_cvar.h\"\n#include \"vk_common.h\"\n#include \"r_textures.h\"\n#include \"vk_renderstate.h\"\n#include \"vk_overlay.h\"\n#include \"vk_scene.h\"\n#include \"vk_framectl.h\"\n#include \"vk_lightmap.h\"\n#include \"vk_sprite.h\"\n#include \"vk_studio.h\"\n#include \"vk_beams.h\"\n#include \"vk_brush.h\"\n#include \"r_decals.h\"\n#include \"vk_rpart.h\"\n#include \"vk_triapi.h\"\n#include \"r_speeds.h\"\n#include \"vk_logs.h\"\n\n#include \"xash3d_types.h\"\n#include \"com_strings.h\"\n\n#include <memory.h>\n\n#define LOG_MODULE rmain\n\nr_globals_t globals = {0};\nref_api_t gEngine = {0};\nref_globals_t *gpGlobals = NULL;\nref_client_t  *gp_cl = NULL;\nref_host_t    *gp_host = NULL;\n\nstatic const char *R_GetConfigName( void )\n{\n\treturn \"vk\";\n}\n\nstatic qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int x, int y, float scale_x, float scale_y )\n{\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"(%d, %d, %d, %f, %f)\", rotate, x, y, scale_x, scale_y);\n\n\treturn true;\n}\n\nstatic void GL_SetupAttributes( int safegl )\n{\n\t// Nothing to do for Vulkan\n}\nstatic void GL_ClearExtensions( void )\n{\n\t// Nothing to do for Vulkan\n}\nstatic void GL_BackendStartFrame_UNUSED( void )\n{\n\t/* Unused in Vulkan renderer. GL renderer only uses this to clear the r_speeds_msg string */\n}\nstatic void GL_BackendEndFrame_UNUSED( void )\n{\n\t/* Unused in Vulkan renderer. GL renderer only uses this to populate r_speeds_msg string. In Vulkan this is done naturally in R_EndFrame */\n}\n\n// debug\nstatic void R_ShowTextures_UNUSED( void )\n{\n\t/* Unused in Vulkan renderer. No need to debug textures this way */\n}\n\n// texture management\nstatic const byte *R_GetTextureOriginalBuffer_UNUSED( unsigned int idx )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn NULL;\n}\n\nstatic void GL_ProcessTexture_UNUSED( int texnum, float gamma, int topColor, int bottomColor )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn false;\n}\n\n// light\nstatic colorVec R_LightPoint( const float *p )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn (colorVec){0};\n}\n\n\nextern void GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa );\n\nstatic void Mod_UnloadTextures( model_t *mod )\n{\n\tASSERT( mod != NULL );\n\n\tswitch( mod->type )\n\t{\n\tcase mod_studio:\n\t\tMod_StudioUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_alias:\n\t\t// FIXME Mod_AliasUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tcase mod_brush:\n\t\tR_BrushUnloadTextures( mod );\n\t\tbreak;\n\tcase mod_sprite:\n\t\tMod_SpriteUnloadTextures( mod->cache.data );\n\t\tbreak;\n\tdefault:\n\t\tASSERT( 0 );\n\t\tbreak;\n\t}\n}\n\nstatic qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buffer )\n{\n\tqboolean loaded = true;\n\n\tDEBUG(\"%s(%s, create=%d)\", __FUNCTION__, mod->name, create);\n\n\t// TODO does this ever happen?\n\tif (!create && mod->type == mod_brush)\n\t\tgEngine.Con_Printf( S_WARN \"VK FIXME Trying to unload brush model %s\\n\", mod->name);\n\n\tif( create )\n\t{\n\t\tswitch( mod->type )\n\t\t{\n\t\t\tcase mod_studio:\n\t\t\t\t// This call happens before we get R_NewMap, which frees all current buffers\n\t\t\t\t// So we can't really load anything here\n\t\t\t\t// TODO we might benefit a tiny bit (a few ms loading time) from reusing studio models from previous map\n\t\t\t\tbreak;\n\t\t\tcase mod_sprite:\n\t\t\t\tMod_LoadSpriteModel( mod, buffer, &loaded, mod->numtexinfo );\n\t\t\t\tbreak;\n\t\t\tcase mod_alias:\n\t\t\t\t// TODO what ARE mod_alias? We just don't know.\n\t\t\t\tloaded = false;\n\t\t\t\tbreak;\n\t\t\tcase mod_brush:\n\t\t\t\t// This call happens before we get R_NewMap, which frees all current buffers\n\t\t\t\t// So we can't really load anything here\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tgEngine.Host_Error( \"Mod_LoadModel: unsupported type %d\\n\", mod->type );\n\t\t\t\tloaded = false;\n\t\t}\n\t}\n\n\tif( loaded && gEngine.drawFuncs->Mod_ProcessUserData )\n\t\tgEngine.drawFuncs->Mod_ProcessUserData( mod, create, buffer );\n\n\tif( !create ) {\n\t\tMod_UnloadTextures( mod );\n\t\tswitch( mod->type ) {\n\t\t\tcase mod_brush:\n\t\t\t\t// Empirically, this function only attempts to destroy the worldmodel before loading the next map.\n\t\t\t\t// However, all brush models need to be destroyed. Use this as a signal to destroy them too.\n\t\t\t\t// Assert that this observation is correct.\n\t\t\t\t// ASSERT(mod == gEngine.pfnGetModelByIndex(1)); not correct when closing the game. At this point model count is zero.\n\n\t\t\t\tR_SceneMapDestroy();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tPRINT_NOT_IMPLEMENTED_ARGS(\"destroy (%p, %d, %s)\", mod, mod->type, mod->name);\n\t\t}\n\t}\n\n\treturn loaded;\n}\n\n// Xash3D Render Interface\n// Get renderer info (doesn't changes engine state at all)\n\nstatic const char *getParmName(int parm)\n{\n\tswitch(parm){\n\tcase PARM_TEX_WIDTH: return \"PARM_TEX_WIDTH\";\n\tcase PARM_TEX_HEIGHT: return \"PARM_TEX_HEIGHT\";\n\tcase PARM_TEX_SRC_WIDTH: return \"PARM_TEX_SRC_WIDTH\";\n\tcase PARM_TEX_SRC_HEIGHT: return \"PARM_TEX_SRC_HEIGHT\";\n\tcase PARM_TEX_SKYBOX: return \"PARM_TEX_SKYBOX\";\n\tcase PARM_TEX_SKYTEXNUM: return \"PARM_TEX_SKYTEXNUM\";\n\tcase PARM_TEX_LIGHTMAP: return \"PARM_TEX_LIGHTMAP\";\n\tcase PARM_TEX_TARGET: return \"PARM_TEX_TARGET\";\n\tcase PARM_TEX_TEXNUM: return \"PARM_TEX_TEXNUM\";\n\tcase PARM_TEX_FLAGS: return \"PARM_TEX_FLAGS\";\n\tcase PARM_TEX_DEPTH: return \"PARM_TEX_DEPTH\";\n\tcase PARM_TEX_GLFORMAT: return \"PARM_TEX_GLFORMAT\";\n\tcase PARM_TEX_ENCODE: return \"PARM_TEX_ENCODE\";\n\tcase PARM_TEX_MIPCOUNT: return \"PARM_TEX_MIPCOUNT\";\n\tcase PARM_BSP2_SUPPORTED: return \"PARM_BSP2_SUPPORTED\";\n\tcase PARM_SKY_SPHERE: return \"PARM_SKY_SPHERE\";\n\tcase PARAM_GAMEPAUSED: return \"PARAM_GAMEPAUSED\";\n\tcase PARM_MAP_HAS_DELUXE: return \"PARM_MAP_HAS_DELUXE\";\n\tcase PARM_MAX_ENTITIES: return \"PARM_MAX_ENTITIES\";\n\tcase PARM_WIDESCREEN: return \"PARM_WIDESCREEN\";\n\tcase PARM_FULLSCREEN: return \"PARM_FULLSCREEN\";\n\tcase PARM_SCREEN_WIDTH: return \"PARM_SCREEN_WIDTH\";\n\tcase PARM_SCREEN_HEIGHT: return \"PARM_SCREEN_HEIGHT\";\n\tcase PARM_CLIENT_INGAME: return \"PARM_CLIENT_INGAME\";\n\tcase PARM_FEATURES: return \"PARM_FEATURES\";\n\tcase PARM_ACTIVE_TMU: return \"PARM_ACTIVE_TMU\";\n\tcase PARM_LIGHTSTYLEVALUE: return \"PARM_LIGHTSTYLEVALUE\";\n\tcase PARM_MAX_IMAGE_UNITS: return \"PARM_MAX_IMAGE_UNITS\";\n\tcase PARM_CLIENT_ACTIVE: return \"PARM_CLIENT_ACTIVE\";\n\tcase PARM_REBUILD_GAMMA: return \"PARM_REBUILD_GAMMA\";\n\tcase PARM_DEDICATED_SERVER: return \"PARM_DEDICATED_SERVER\";\n\tcase PARM_SURF_SAMPLESIZE: return \"PARM_SURF_SAMPLESIZE\";\n\tcase PARM_GL_CONTEXT_TYPE: return \"PARM_GL_CONTEXT_TYPE\";\n\tcase PARM_GLES_WRAPPER: return \"PARM_GLES_WRAPPER\";\n\tcase PARM_STENCIL_ACTIVE: return \"PARM_STENCIL_ACTIVE\";\n\tcase PARM_WATER_ALPHA: return \"PARM_WATER_ALPHA\";\n\tcase PARM_TEX_MEMORY: return \"PARM_TEX_MEMORY\";\n\tcase PARM_DELUXEDATA: return \"PARM_DELUXEDATA\";\n\tcase PARM_SHADOWDATA: return \"PARM_SHADOWDATA\";\n\tcase PARM_MODERNFLASHLIGHT: return \"PARM_MODERNFLASHLIGHT\";\n\tcase PARM_TEX_FILTERING: return \"PARM_TEX_FILTERING\";\n\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nstatic int VK_RefGetParm( int parm, int arg )\n{\n\t// TODO all PARM_TEX handle in r_texture internally\n\tswitch(parm){\n\tcase PARM_TEX_WIDTH:\n\tcase PARM_TEX_HEIGHT:\n\tcase PARM_TEX_SRC_WIDTH: // TODO why is this separate?\n\tcase PARM_TEX_SRC_HEIGHT:\n\tcase PARM_TEX_FLAGS:\n\tcase PARM_TEX_FILTERING:\n\t/* TODO\n\tcase PARM_TEX_SKYBOX:\n\tcase PARM_TEX_SKYTEXNUM:\n\tcase PARM_TEX_LIGHTMAP:\n\tcase PARM_TEX_TARGET:\n\tcase PARM_TEX_TEXNUM:\n\tcase PARM_TEX_DEPTH:\n\tcase PARM_TEX_GLFORMAT:\n\tcase PARM_TEX_ENCODE:\n\tcase PARM_TEX_MIPCOUNT:\n\tcase PARM_TEX_MEMORY:\n\t*/\n\t\treturn R_TexturesGetParm( parm, arg );\n\tcase PARM_MODERNFLASHLIGHT:\n\t\tif (CVAR_TO_BOOL( rt_enable )) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\tcase PARM_WIDESCREEN:\n\t\treturn gpGlobals->wideScreen;\n\tcase PARM_FULLSCREEN:\n\t\treturn gpGlobals->fullScreen;\n\tcase PARM_SCREEN_WIDTH:\n\t\treturn gpGlobals->width;\n\tcase PARM_SCREEN_HEIGHT:\n\t\treturn gpGlobals->height;\n\t}\n\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"(%s(%d), %d)\", getParmName(parm), parm, arg);\n\n\treturn 0;\n}\nstatic void\t\tGetDetailScaleForTexture( int texture, float *xScale, float *yScale )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *alpha )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic float\t\tGetFrameTime( void )\n{\n\t/* TODO as in gl R_RenderScene()\n\t// frametime is valid only for normal pass\n\tif( RP_NORMALPASS( ))\n\t\ttr.frametime = gp_cl->time -   gp_cl->oldtime;\n\telse tr.frametime = 0.0;\n\t*/\n\n\tPRINT_NOT_IMPLEMENTED();\n\treturn 1.f;\n}\n\n// Set renderer info (tell engine about changes)\nstatic void\t\tR_SetCurrentEntity( struct cl_entity_s *ent )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tR_SetCurrentModel( struct model_s *mod )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\n\n\n// AVI\nstatic void\t\tAVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\n// glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client)\nstatic void GL_Bind( int tmu, unsigned int texnum )\n{\n\tif (tmu != 0) {\n\t\tPRINT_NOT_IMPLEMENTED_ARGS(\"non-zero tmu=%d\", tmu);\n\t}\n\n\tTriSetTexture(texnum);\n}\nstatic void\t\tGL_SelectTexture( int tmu )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGL_LoadTextureMatrix( const float *glmatrix )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGL_TexMatrixIdentity( void )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGL_CleanUpTextureUnits( int last )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGL_TexGen( unsigned int coord, unsigned int mode )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\t\tGL_TextureTarget( unsigned int target )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void GL_TexCoordArrayMode( unsigned int texmode )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void GL_UpdateTexSize( int texnum, int width, int height, int depth )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\n// Misc renderer functions\nstatic void GL_DrawParticles( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\ncolorVec R_LightVec( const float *start, const float *end, float *lightspot, float *lightvec );\n\nstatic struct mstudiotex_s *R_StudioGetTexture( struct cl_entity_s *e )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn NULL;\n}\n\n// setup map bounds for ortho-projection when we in dev_overview mode\nstatic void\t\tGL_OrthoBounds( const float *mins, const float *maxs )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\n// get visdata for current frame from custom renderer\nstatic byte* Mod_GetCurrentVis( void ) {\n\t// ref_soft just returns NULL here\n\t// Not sure if we need to copy what ref_gl does. What it does is:\n\t// - Setup camera and call R_MarkLeaves() in R_RenderScene()\n\t// - R_MarkLeaves() sets RI.visbytes\n\t//   will be eventually needed for culling in traditional renderer, see:\n\t//   - https://github.com/w23/xash3d-fwgs/pull/96\n\t//   - https://github.com/w23/xash3d-fwgs/issues/93\n\t// - Return RI.visbytes here (if not using custom rendering)\n\treturn NULL;\n}\n\n// GL_GetProcAddress for client renderer\nstatic void*\t\tR_GetProcAddress( const char *name )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn NULL;\n}\n\n// TriAPI Interface\nstatic void\tTriFog( float flFogColor[3], float flStart, float flEnd, int bOn )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\tR_ScreenToWorld( const float *screen, float *world  )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\tTriGetMatrix( const int pname, float *matrix )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\tTriFogParams( float flDensity, int iFogSkybox )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\nstatic void\tTriCullFace( TRICULLSTYLE mode )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic const byte* R_TextureData_UNUSED( unsigned int texnum )\n{\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"texnum=%d\", texnum);\n\t// We don't store original texture data\n\t// TODO do we need to?\n\treturn NULL;\n}\n\nstatic int R_CreateTexture_UNUSED( const char *name, int width, int height, const void *buffer, texFlags_t flags )\n{\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"name=%s width=%d height=%d buffer=%p flags=%08x\", name, width, height, buffer, flags);\n\treturn 0;\n}\n\nstatic int R_LoadTextureArray_UNUSED( const char **names, int flags )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\treturn 0;\n}\n\nstatic int R_CreateTextureArray_UNUSED( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags )\n{\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"name=%s width=%d height=%d buffer=%p flags=%08x\", name, width, height, buffer, flags);\n\treturn 0;\n}\n\nstatic const ref_device_t *pfnGetRenderDevice( unsigned int idx )\n{\n\tif( idx >= vk_core.num_devices )\n\t\treturn NULL;\n\n\treturn &vk_core.devices[idx];\n}\n\nstatic void R_GammaChanged( qboolean do_reset_gamma )\n{\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"do_reset_gamma=%d\", do_reset_gamma);\n}\n\nstatic qboolean R_Init(void) {\n\tglobals.world = (struct world_static_s *)ENGINE_GET_PARM( PARM_GET_WORLD_PTR );\n\tglobals.movevars = (struct movevars_s *)ENGINE_GET_PARM( PARM_GET_MOVEVARS_PTR );\n\tglobals.palette = (color24 *)ENGINE_GET_PARM( PARM_GET_PALETTE_PTR );\n\tglobals.viewent = (cl_entity_t *)ENGINE_GET_PARM( PARM_GET_VIEWENT_PTR );\n\tglobals.texgammatable = (byte *)ENGINE_GET_PARM( PARM_GET_TEXGAMMATABLE_PTR );\n\tglobals.lightgammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LIGHTGAMMATABLE_PTR );\n\tglobals.screengammatable = (uint *)ENGINE_GET_PARM( PARM_GET_SCREENGAMMATABLE_PTR );\n\tglobals.lineargammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LINEARGAMMATABLE_PTR );\n\tglobals.dlights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_DLIGHTS_PTR );\n\tglobals.elights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_ELIGHTS_PTR );\n\n\treturn R_VkInit();\n}\n\nstatic void R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture ) {\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"solidskyTexture=%d alphaskyTexture=%d\", solidskyTexture, alphaskyTexture);\n}\n\nstatic void R_OverrideTextureSourceSize( unsigned int texnum, unsigned int srcWidth, unsigned int srcHeight ) { // used to override decal size for texture replacement\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"texnum=%u srcWidth=%u srcHeight=%u\", texnum, srcWidth, srcHeight);\n}\n\nstatic void VGUI_SetupDrawing( qboolean rect ) {\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"rect=%d\", rect);\n}\n\nstatic void VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) {\n\tPRINT_NOT_IMPLEMENTED_ARGS(\"drawX=%d drawY=%d rgba=%p blockWidth=%d blockHeight=%d\",\n\t\tdrawX, drawY, rgba, blockWidth, blockHeight);\n}\n\nstatic const ref_interface_t gReffuncs =\n{\n\t.R_Init = R_Init,\n\t.R_Shutdown = R_VkShutdown,\n\n\t.R_GetConfigName = R_GetConfigName,\n\t.R_SetDisplayTransform = R_SetDisplayTransform,\n\n\t// only called for GL contexts\n\t.GL_SetupAttributes = GL_SetupAttributes,\n\t.GL_InitExtensions = NULL, // Unused in Vulkan renderer\n\t.GL_ClearExtensions = GL_ClearExtensions,\n\n\t.R_GammaChanged = R_GammaChanged,\n\n\t.R_BeginFrame = R_BeginFrame,\n\t.R_RenderScene = R_RenderScene, // Not called ever?\n\t.R_EndFrame = R_EndFrame,\n\t.R_PushScene = R_PushScene,\n\t.R_PopScene = R_PopScene,\n\t.GL_BackendStartFrame = GL_BackendStartFrame_UNUSED,\n\t.GL_BackendEndFrame = GL_BackendEndFrame_UNUSED,\n\n\t.R_ClearScreen = R_ClearScreen,\n\t.R_AllowFog = R_AllowFog,\n\t.GL_SetRenderMode = GL_SetRenderMode,\n\n\t.R_AddEntity = R_AddEntity,\n\t.CL_AddCustomBeam = CL_AddCustomBeam,\n\t.R_ProcessEntData = R_ProcessEntData,\n\t.R_Flush = NULL,\n\n\t// debug\n\t.R_ShowTextures = R_ShowTextures_UNUSED,\n\n\t// texture management\n\t.R_GetTextureOriginalBuffer = R_GetTextureOriginalBuffer_UNUSED,\n\t.GL_LoadTextureFromBuffer = R_TextureUploadFromBuffer,\n\t.GL_ProcessTexture = GL_ProcessTexture_UNUSED,\n\t.R_SetupSky = R_TextureSetupCustomSky,\n\n\t// 2D\n\t.R_Set2DMode = R_Set2DMode,\n\t.R_DrawStretchRaw = R_DrawStretchRaw,\n\t.R_DrawStretchPic = R_DrawStretchPic,\n\t.FillRGBA = CL_FillRGBA,\n\t.WorldToScreen = R_WorldToScreen,\n\n\t// screenshot, cubemapshot\n\t.VID_ScreenShot = VID_ScreenShot,\n\t.VID_CubemapShot = VID_CubemapShot,\n\n\t// light\n\t.R_LightPoint = R_LightPoint,\n\n\t// decals\n\t.R_DecalShoot = R_DecalShoot,\n\t.R_DecalRemoveAll = R_DecalRemoveAll,\n\t.R_CreateDecalList = R_CreateDecalList,\n\t.R_ClearAllDecals = R_ClearAllDecals,\n\n\t.R_StudioEstimateFrame = R_StudioEstimateFrame,\n\t.R_StudioLerpMovement = R_StudioLerpMovement,\n\t.CL_InitStudioAPI = CL_InitStudioAPI,\n\n\t.R_SetSkyCloudsTextures = R_SetSkyCloudsTextures,\n\t.GL_SubdivideSurface = GL_SubdivideSurface,\n\t.CL_RunLightStyles = VK_RunLightStyles,\n\n\t.R_GetSpriteParms = R_GetSpriteParms,\n\t.R_GetSpriteTexture = R_GetSpriteTexture,\n\n\t.Mod_ProcessRenderData = Mod_ProcessRenderData,\n\t.Mod_StudioLoadTextures = Mod_StudioLoadTextures,\n\n\t.CL_DrawParticles = CL_DrawParticles,\n\t.CL_DrawTracers = CL_DrawTracers,\n\t.CL_DrawBeams = CL_DrawBeams,\n\t.R_BeamCull = R_BeamCull,\n\n\t.RefGetParm = VK_RefGetParm,\n\t.GetDetailScaleForTexture = GetDetailScaleForTexture,\n\t.GetExtraParmsForTexture = GetExtraParmsForTexture,\n\t.GetFrameTime = GetFrameTime,\n\n\t.R_SetCurrentEntity = R_SetCurrentEntity,\n\t.R_SetCurrentModel = R_SetCurrentModel,\n\n\t// Texture tools\n\t.GL_FindTexture = R_TextureFindByName,\n\t.GL_TextureName = R_TextureGetNameByIndex,\n\t.GL_TextureData = R_TextureData_UNUSED,\n\t.GL_LoadTexture = R_TextureUploadFromFile,\n\t.GL_CreateTexture = R_CreateTexture_UNUSED,\n\t.GL_LoadTextureArray = R_LoadTextureArray_UNUSED,\n\t.GL_CreateTextureArray = R_CreateTextureArray_UNUSED,\n\t.GL_FreeTexture = R_TextureFree,\n\t.R_OverrideTextureSourceSize = R_OverrideTextureSourceSize,\n\n\t// Decals manipulating (draw & remove)\n\t.DrawSingleDecal = R_DrawSingleDecal,\n\t.R_DecalSetupVerts = R_DecalSetupVerts,\n\t.R_EntityRemoveDecals = R_EntityRemoveDecals,\n\n\t.AVI_UploadRawFrame = AVI_UploadRawFrame,\n\n\t.GL_Bind = GL_Bind,\n\t.GL_SelectTexture = GL_SelectTexture,\n\t.GL_LoadTextureMatrix = GL_LoadTextureMatrix,\n\t.GL_TexMatrixIdentity = GL_TexMatrixIdentity,\n\t.GL_CleanUpTextureUnits = GL_CleanUpTextureUnits,\n\t.GL_TexGen = GL_TexGen,\n\t.GL_TextureTarget = GL_TextureTarget,\n\t.GL_TexCoordArrayMode = GL_TexCoordArrayMode,\n\t.GL_UpdateTexSize = GL_UpdateTexSize,\n\tNULL, // Reserved0\n\tNULL, // Reserved1\n\n\t.GL_DrawParticles = GL_DrawParticles,\n\t.LightVec = R_LightVec,\n\t.StudioGetTexture = R_StudioGetTexture,\n\n\t.GL_RenderFrame = VK_RenderFrame,\n\t.GL_OrthoBounds = GL_OrthoBounds,\n\t.R_SpeedsMessage = R_SpeedsMessage,\n\t.Mod_GetCurrentVis = Mod_GetCurrentVis,\n\t.R_NewMap = R_NewMap,\n\t.R_ClearScene = R_ClearScene,\n\t.R_GetProcAddress = R_GetProcAddress,\n\n\t.TriRenderMode = TriRenderMode,\n\t.Begin = TriBegin,\n\t.End = TriEnd,\n\t.Color4f = TriColor4f,\n\t.Color4ub = TriColor4ub,\n\t.TexCoord2f = TriTexCoord2f,\n\t.Vertex3fv = TriVertex3fv,\n\t.Vertex3f = TriVertex3f,\n\t.Fog = TriFog,\n\t.ScreenToWorld = R_ScreenToWorld,\n\t.GetMatrix = TriGetMatrix,\n\t.FogParams= TriFogParams,\n\t.CullFace = TriCullFace,\n\n\t.VGUI_SetupDrawing = VGUI_SetupDrawing,\n\t.VGUI_UploadTextureBlock = VGUI_UploadTextureBlock,\n\n\t.pfnGetVulkanRenderDevice = pfnGetRenderDevice,\n};\n\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals );\nint EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals )\n{\n\tif( version != REF_API_VERSION )\n\t\treturn 0;\n\n\t// fill in our callbacks\n\tmemcpy( funcs, &gReffuncs, sizeof( ref_interface_t ));\n\tmemcpy( &gEngine, engfuncs, sizeof( ref_api_t ));\n\tgpGlobals = globals;\n\tgp_cl = (ref_client_t *)ENGINE_GET_PARM( PARM_GET_CLIENT_PTR );\n\tgp_host = (ref_host_t *)ENGINE_GET_PARM( PARM_GET_HOST_PTR );\n\n\tINFO(\"GetRefAPI version=%d (REF_API_VERSION=%d) funcs=%p engfuncs=%p globals=%p\",\n\t\tversion, REF_API_VERSION, funcs, engfuncs, globals);\n\n\treturn REF_API_VERSION;\n}\n"
  },
  {
    "path": "ref/vk/vk_rpart.c",
    "content": "/*\ncl_part.c - particles and tracers\nCopyright (C) 2010 Uncle Mike\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"vk_rpart.h\"\n#include \"vk_triapi.h\"\n#include \"camera.h\"\n#include \"r_textures.h\" // tglob.particleTexture\n#include \"vk_common.h\"\n#include \"vk_sprite.h\" // R_GetSpriteTexture\n\n#include \"xash3d_types.h\" // vec3_t for r_efx.h\n#include \"const.h\" // color24 for r_efx.h\n#include \"r_efx.h\"\n#include \"event_flags.h\"\n#include \"entity_types.h\"\n#include \"triangleapi.h\"\n#include \"pm_local.h\"\n#include \"studio.h\"\n#include \"pm_movevars.h\" // movevars_t\n\nstatic float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };\nstatic color24 gTracerColors[] =\n{\n{ 255, 255, 255 },\t\t// White\n{ 255, 0, 0 },\t\t// Red\n{ 0, 255, 0 },\t\t// Green\n{ 0, 0, 255 },\t\t// Blue\n{ 0, 0, 0 },\t\t// Tracer default, filled in from cvars, etc.\n{ 255, 167, 17 },\t\t// Yellow-orange sparks\n{ 255, 130, 90 },\t\t// Yellowish streaks (garg)\n{ 55, 60, 144 },\t\t// Blue egon streak\n{ 255, 130, 90 },\t\t// More Yellowish streaks (garg)\n{ 255, 140, 90 },\t\t// More Yellowish streaks (garg)\n{ 200, 130, 90 },\t\t// More red streaks (garg)\n{ 255, 120, 70 },\t\t// Darker red streaks (garg)\n};\n\n/*\n================\nCL_DrawParticles\n\nupdate particle color, position, free expired and draw it\n================\n*/\nvoid CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize )\n{\n\tparticle_t\t*p;\n\tvec3_t\t\tright, up;\n\tcolor24\t\t*pColor;\n\tint\t\talpha;\n\tfloat\t\tsize;\n\n\tif( !cl_active_particles )\n\t\treturn;\t// nothing to draw?\n\n\tTriRenderMode( kRenderTransAlpha );\n\tTriSetTexture( tglob.particleTexture );\n\n\tTriBegin( TRI_QUADS );\n\n\tfor( p = cl_active_particles; p; p = p->next )\n\t{\n\t\tif(( p->type != pt_blob ) || ( p->packedColor == 255 ))\n\t\t{\n\t\t\tsize = partsize; // get initial size of particle\n\n\t\t\t// scale up to keep particles from disappearing\n\t\t\t// FIXME are these really the same?\n\t\t\tvec3_t cull_vforward, cull_vup, cull_vright;\n\t\t\tVectorCopy(g_camera.vforward, cull_vforward);\n\t\t\tVectorCopy(g_camera.vup, cull_vup);\n\t\t\tVectorCopy(g_camera.vright, cull_vright);\n\t\t\tsize += (p->org[0] - g_camera.vieworg[0]) * cull_vforward[0];\n\t\t\tsize += (p->org[1] - g_camera.vieworg[1]) * cull_vforward[1];\n\t\t\tsize += (p->org[2] - g_camera.vieworg[2]) * cull_vforward[2];\n\n\t\t\tif( size < 20.0f ) size = partsize;\n\t\t\telse size = partsize + size * 0.002f;\n\n\t\t\t// scale the axes by radius\n\t\t\tVectorScale( cull_vright, size, right );\n\t\t\tVectorScale( cull_vup, size, up );\n\n\t\t\tp->color = bound( 0, p->color, 255 );\n\t\t\tpColor = &globals.palette[ p->color ];\n\n\t\t\talpha = 255 * (p->die - gp_cl->time) * 16.0f;\n\t\t\tif( alpha > 255 || p->type == pt_static )\n\t\t\t\talpha = 255;\n\n\t\t\tTriColor4ub_(\n\t\t\t\tLightToTexGamma( pColor->r ),\n\t\t\t\tLightToTexGamma( pColor->g ),\n\t\t\t\tLightToTexGamma( pColor->b ), alpha );\n\n\t\t\tTriTexCoord2f( 0.0f, 1.0f );\n\t\t\tTriVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] );\n\t\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\t\tTriVertex3f( p->org[0] + right[0] + up[0], p->org[1] + right[1] + up[1], p->org[2] + right[2] + up[2] );\n\t\t\tTriTexCoord2f( 1.0f, 0.0f );\n\t\t\tTriVertex3f( p->org[0] + right[0] - up[0], p->org[1] + right[1] - up[1], p->org[2] + right[2] - up[2] );\n\t\t\tTriTexCoord2f( 1.0f, 1.0f );\n\t\t\tTriVertex3f( p->org[0] - right[0] - up[0], p->org[1] - right[1] - up[1], p->org[2] - right[2] - up[2] );\n\t\t}\n\n\t\tgEngine.CL_ThinkParticle( frametime, p );\n\t}\n\n\tconst vec4_t color = { 1, 1, 1, 1 }; // pColor->r / 255.f, pColor->g / 255.f, pColor->b / 255.f, 1.f };\n\tTriEndEx( color, \"particle\");\n}\n\n/*\n================\nCL_CullTracer\n\ncheck tracer bbox\n================\n*/\nstatic qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end )\n{\n\tvec3_t\tmins, maxs;\n\tint\ti;\n\n\t// compute the bounding box\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tif( start[i] < end[i] )\n\t\t{\n\t\t\tmins[i] = start[i];\n\t\t\tmaxs[i] = end[i];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmins[i] = end[i];\n\t\t\tmaxs[i] = start[i];\n\t\t}\n\n\t\t// don't let it be zero sized\n\t\tif( mins[i] == maxs[i] )\n\t\t{\n\t\t\tmaxs[i] += gTracerSize[p->type] * 2.0f;\n\t\t}\n\t}\n\n\t// check bbox\n\treturn false; // FIXME VK R_CullBox( mins, maxs );\n}\n\n/*\n================\nCL_DrawTracers\n\nupdate tracer color, position, free expired and draw it\n================\n*/\nvoid CL_DrawTracers( double frametime, particle_t *cl_active_tracers )\n{\n\tfloat\t\tscale, atten, gravity;\n\tvec3_t\t\tscreenLast, screen;\n\tvec3_t\t\tstart, end, delta;\n\tparticle_t\t*p;\n\n\t/* FIXME VK\n\t// update tracer color if this is changed\n\tif( FBitSet( tracerred->flags|tracergreen->flags|tracerblue->flags|traceralpha->flags, FCVAR_CHANGED ))\n\t{\n\t\tcolor24 *customColors = &gTracerColors[4];\n\t\tcustomColors->r = (byte)(tracerred->value * traceralpha->value * 255);\n\t\tcustomColors->g = (byte)(tracergreen->value * traceralpha->value * 255);\n\t\tcustomColors->b = (byte)(tracerblue->value * traceralpha->value * 255);\n\t\tClearBits( tracerred->flags, FCVAR_CHANGED );\n\t\tClearBits( tracergreen->flags, FCVAR_CHANGED );\n\t\tClearBits( tracerblue->flags, FCVAR_CHANGED );\n\t\tClearBits( traceralpha->flags, FCVAR_CHANGED );\n\t}\n\t*/\n\n\tif( !cl_active_tracers )\n\t\treturn;\t// nothing to draw?\n\n\tif( !TriSpriteTexture( gEngine.GetDefaultSprite( REF_DOT_SPRITE ), 0 ))\n\t\treturn;\n\n\tTriRenderMode( kRenderTransAdd );\n\n\tgravity = frametime * globals.movevars->gravity;\n\tscale = 1.0 - (frametime * 0.9);\n\tif( scale < 0.0f ) scale = 0.0f;\n\n\tfor( p = cl_active_tracers; p; p = p->next )\n\t{\n\t\tatten = (p->die - gp_cl->time);\n\t\tif( atten > 0.1f ) atten = 0.1f;\n\n\t\tVectorScale( p->vel, ( p->ramp * atten ), delta );\n\t\tVectorAdd( p->org, delta, end );\n\t\tVectorCopy( p->org, start );\n\n\t\tif( !CL_CullTracer( p, start, end ))\n\t\t{\n\t\t\tvec3_t\tverts[4], tmp2;\n\t\t\tvec3_t\ttmp, normal;\n\t\t\tcolor24\t*pColor;\n\n\t\t\t// Transform point into screen space\n\t\t\tTriWorldToScreen( start, screen );\n\t\t\tTriWorldToScreen( end, screenLast );\n\n\t\t\t// build world-space normal to screen-space direction vector\n\t\t\tVectorSubtract( screen, screenLast, tmp );\n\n\t\t\t// we don't need Z, we're in screen space\n\t\t\ttmp[2] = 0;\n\t\t\tVectorNormalize( tmp );\n\n\t\t\tvec3_t /*cull_vforward,*/ cull_vup, cull_vright;\n\t\t\t//VectorCopy(g_camera.vforward, cull_vforward);\n\t\t\tVectorCopy(g_camera.vup, cull_vup);\n\t\t\tVectorCopy(g_camera.vright, cull_vright);\n\n\t\t\t// build point along noraml line (normal is -y, x)\n\t\t\tVectorScale( cull_vup, tmp[0] * gTracerSize[p->type], normal );\n\t\t\tVectorScale( cull_vright, -tmp[1] * gTracerSize[p->type], tmp2 );\n\t\t\tVectorSubtract( normal, tmp2, normal );\n\n\t\t\t// compute four vertexes\n\t\t\tVectorSubtract( start, normal, verts[0] );\n\t\t\tVectorAdd( start, normal, verts[1] );\n\t\t\tVectorAdd( verts[0], delta, verts[2] );\n\t\t\tVectorAdd( verts[1], delta, verts[3] );\n\n\t\t\tif( p->color > sizeof( gTracerColors ) / sizeof( color24 ) )\n\t\t\t{\n\t\t\t\tgEngine.Con_Printf( S_ERROR \"UserTracer with color > %d\\n\", (int)(sizeof( gTracerColors ) / sizeof( color24 )));\n\t\t\t\tp->color = 0;\n\t\t\t}\n\n\t\t\tpColor = &gTracerColors[p->color];\n\t\t\tTriColor4ub_( pColor->r, pColor->g, pColor->b, p->packedColor );\n\n\t\t\tTriBegin( TRI_QUADS );\n\t\t\t\tTriTexCoord2f( 0.0f, 0.8f );\n\t\t\t\tTriVertex3fv( verts[2] );\n\t\t\t\tTriTexCoord2f( 1.0f, 0.8f );\n\t\t\t\tTriVertex3fv( verts[3] );\n\t\t\t\tTriTexCoord2f( 1.0f, 0.0f );\n\t\t\t\tTriVertex3fv( verts[1] );\n\t\t\t\tTriTexCoord2f( 0.0f, 0.0f );\n\t\t\t\tTriVertex3fv( verts[0] );\n\t\t\tconst vec4_t color = { 1, 1, 1, 1 }; //pColor->r / 255.f, pColor->g / 255.f, pColor->b / 255.f, p->packedColor / 255.f };\n\t\t\tTriEndEx( color, \"tracer\" );\n\t\t}\n\n\t\t// evaluate position\n\t\tVectorMA( p->org, frametime, p->vel, p->org );\n\n\t\tif( p->type == pt_grav )\n\t\t{\n\t\t\tp->vel[0] *= scale;\n\t\t\tp->vel[1] *= scale;\n\t\t\tp->vel[2] -= gravity;\n\n\t\t\tp->packedColor = 255 * (p->die - gp_cl->time) * 2;\n\t\t\tif( p->packedColor > 255 ) p->packedColor = 255;\n\t\t}\n\t\telse if( p->type == pt_slowgrav )\n\t\t{\n\t\t\tp->vel[2] = gravity * 0.05f;\n\t\t}\n\t}\n}\n\n/*\n===============\nCL_DrawParticlesExternal\n\nallow to draw effects from custom renderer\n===============\n*/\n\n/* FIXME VK\nvoid CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime )\n{\n\tref_instance_t\toldRI = RI;\n\n\tmemcpy( &oldRI, &RI, sizeof( ref_instance_t ));\n\tR_SetupRefParams( rvp );\n\tR_SetupFrustum();\n\tR_SetuTri( false );\t// don't touch GL-states\n\ttr.frametime = frametime;\n\n\tgEngfuncs.CL_DrawEFX( frametime, trans_pass );\n\n\t// restore internal state\n\tmemcpy( &RI, &oldRI, sizeof( ref_instance_t ));\n}\n*/\n"
  },
  {
    "path": "ref/vk/vk_rpart.h",
    "content": "#pragma once\n\ntypedef struct particle_s particle_t;\n\nvoid CL_DrawParticles( double frametime, particle_t *particles, float partsize );\nvoid CL_DrawTracers( double frametime, particle_t *tracers );\n"
  },
  {
    "path": "ref/vk/vk_rtx.c",
    "content": "#include \"vk_rtx.h\"\n\n#include \"shaders/ray_interop.h\" // DEBUG_DISPLAY_...\n\n#include \"vulkan/VResource.h\"\n#include \"vk_ray_accel.h\"\n#include \"vulkan/VBuffer.h\"\n#include \"vk_common.h\"\n#include \"vk_core.h\"\n#include \"vk_cvar.h\"\n#include \"vk_light.h\"\n#include \"vk_math.h\"\n#include \"vulkan/VMeatpipe.h\"\n#include \"vk_ray_internal.h\"\n#include \"r_textures.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"vk_logs.h\"\n#include \"rt_kusochki.h\"\n\n#include \"std/profiler.h\"\n\n#include \"xash3d_mathlib.h\"\n\n#include <string.h>\n\n#define LOG_MODULE rt\n\n#define MAX_FRAMES_IN_FLIGHT 2\n\n#define MIN_FRAME_WIDTH 1280\n#define MIN_FRAME_HEIGHT 800\n\nstatic struct {\n\tstruct {\n\t\t// Holds UniformBuffer data\n\t\tvk_buffer_t buffer;\n\t\tuint32_t unit_size;\n\n\t\tvk_resource_buffer_t *resource;\n\t\tProducer producer;\n\n\t\tstruct UniformBuffer current;\n\t} uniform;\n\n\t// TODO with proper intra-cmdbuf sync we don't really need 2x images\n\tunsigned frame_number;\n\n\tstruct vk_meatpipe_s *meatpipe;\n\trt_resource_t *meatpipe_out;\n\n\tmatrix4x4 prev_inv_proj, prev_inv_view;\n\n\tqboolean reload_pipeline;\n\tqboolean discontinuity;\n\n\tint max_frame_width, max_frame_height;\n\n\tstruct {\n\t\tcvar_t *rt_debug_display_only;\n\t\tuint32_t rt_debug_display_only_value;\n\n\t\tcvar_t *rt_debug_flags;\n\t\tuint32_t rt_debug_flags_value;\n\n\t\tcvar_t *rt_debug_fixed_random_seed;\n\t} debug;\n} g_rtx = {0};\n\nvoid VK_RayNewMapBegin( void ) {\n\t// TODO it seems like these are unnecessary leftovers. Moreover, they are actively harmful,\n\t// as they recreate things that are in fact pretty much static. Untangle this.\n\tRT_VkAccelNewMap();\n\tRT_RayModel_Clear();\n}\n\nvoid VK_RayFrameBegin( void ) {\n\tASSERT(vk_core.rtx);\n\n\tXVK_RayModel_ClearForNextFrame();\n\tRT_LightsFrameBegin();\n}\n\nstatic void parseDebugDisplayValue( void ) {\n\tif (!(g_rtx.debug.rt_debug_display_only->flags & FCVAR_CHANGED))\n\t\treturn;\n\n\tg_rtx.debug.rt_debug_display_only->flags &= ~FCVAR_CHANGED;\n\n\tconst char *cvalue = g_rtx.debug.rt_debug_display_only->string;\n#define LIST_DISPLAYS(X) \\\n\tX(BASECOLOR, \"material base_color value\") \\\n\tX(BASEALPHA, \"material alpha value\") \\\n\tX(EMISSIVE, \"emissive color\") \\\n\tX(NSHADE, \"shading normal\") \\\n\tX(NGEOM, \"geometry normal\") \\\n\tX(LIGHTING, \"all lighting, direct and indirect, w/o base_color\") \\\n\tX(SURFHASH, \"each surface has random color\") \\\n\tX(DIRECT, \"direct lighting only, both diffuse and specular\") \\\n\tX(DIRECT_DIFF, \"direct diffuse lighting only\") \\\n\tX(DIRECT_SPEC, \"direct specular lighting only\") \\\n\tX(INDIRECT, \"indirect lighting only (bounced), diffuse and specular together\") \\\n\tX(INDIRECT_DIFF, \"indirect diffuse only\") \\\n\tX(INDIRECT_SPEC, \"indirect specular only\") \\\n\tX(TRIHASH, \"each triangle is drawn with random color\") \\\n\tX(MATERIAL, \"red = roughness, green = metalness\") \\\n\tX(DIFFUSE, \"direct + indirect diffuse, spatially denoised\") \\\n\tX(SPECULAR, \"direct + indirect specular, spatially denoised\") \\\n\n#define X(suffix, info) \\\n\tif (0 == Q_stricmp(cvalue, #suffix)) { \\\n\t\tWARN(\"setting debug display to %s\", \"DEBUG_DISPLAY_\"#suffix); \\\n\t\tg_rtx.debug.rt_debug_display_only_value = DEBUG_DISPLAY_##suffix; \\\n\t\treturn; \\\n\t}\nLIST_DISPLAYS(X)\n#undef X\n\n\tif (Q_strlen(cvalue) > 0) {\n\t\tgEngine.Con_Printf(\"Invalid rt_debug_display_only mode %s. Valid modes are:\\n\", cvalue);\n#define X(suffix, info) gEngine.Con_Printf(\"\\t%s -- %s\\n\", #suffix, info);\nLIST_DISPLAYS(X)\n#undef X\n\t}\n\n\tg_rtx.debug.rt_debug_display_only_value = DEBUG_DISPLAY_DISABLED;\n//#undef LIST_DISPLAYS\n}\n\nstatic void parseDebugFlags( void ) {\n\tif (!(g_rtx.debug.rt_debug_flags->flags & FCVAR_CHANGED))\n\t\treturn;\n\n\tg_rtx.debug.rt_debug_flags->flags &= ~FCVAR_CHANGED;\n\tg_rtx.debug.rt_debug_flags_value = 0;\n\n#define LIST_DEBUG_FLAGS(X) \\\n\tX(WHITE_FURNACE, \"white furnace mode: diffuse white materials, diffuse sky light only\") \\\n\n\tconst char *cvalue = g_rtx.debug.rt_debug_flags->string;\n#define X(suffix, info) \\\n\tif (0 == Q_stricmp(cvalue, #suffix)) { \\\n\t\tWARN(\"setting debug flags to %s\", \"DEBUG_FLAG_\"#suffix); \\\n\t\tg_rtx.debug.rt_debug_flags_value |= DEBUG_FLAG_##suffix; \\\n\t} else\nLIST_DEBUG_FLAGS(X)\n#undef X\n\n\t/* else: no valid flags found */\n\tif (Q_strlen(cvalue) > 0) {\n\t\tgEngine.Con_Printf(\"Invalid rt_debug_flags value %s. Valid flags are:\\n\", cvalue);\n#define X(suffix, info) gEngine.Con_Printf(\"\\t%s -- %s\\n\", #suffix, info);\nLIST_DEBUG_FLAGS(X)\n#undef X\n\t}\n\n//#undef LIST_DEBUG_FLAGS\n}\n\nstatic uint32_t getRandomSeed( void ) {\n\tif (g_rtx.debug.rt_debug_fixed_random_seed->string[0])\n\t\treturn (uint32_t)g_rtx.debug.rt_debug_fixed_random_seed->value;\n\n\treturn (uint32_t)gEngine.COM_RandomLong(0, INT32_MAX);\n}\n\nstatic void produceUboResource(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\t// TODO using frame_sequence is only accidental synchronization. It should be done via e.g. resource->consumed or smth.\n\tconst size_t ubo_slot_offset = (ctx->frame_sequence % MAX_FRAMES_IN_FLIGHT) * g_rtx.uniform.unit_size;\n\tstruct UniformBuffer *const ubo = PTR_CAST(struct UniformBuffer, (char*)g_rtx.uniform.buffer.mapped + ubo_slot_offset);\n\tg_rtx.uniform.resource->offset = ubo_slot_offset;\n\tubo->frame_counter = ctx->frame_sequence;\n\tmemcpy(ubo, &g_rtx.uniform.current, sizeof(struct UniformBuffer));\n}\n\nstatic struct UniformBuffer prepareUniformBuffer( const vk_ray_frame_render_args_t *args, float fov_angle_y, int frame_width, int frame_height ) {\n\tstruct UniformBuffer ret;\n\tmatrix4x4 proj_inv, view_inv;\n\tMatrix4x4_Invert_Full(proj_inv, *args->projection);\n\tMatrix4x4_ToArrayFloatGL(proj_inv, (float*)ret.inv_proj);\n\n\t// TODO there's a more efficient way to construct an inverse view matrix\n\t// from vforward/right/up vectors and origin in g_camera\n\tMatrix4x4_Invert_Full(view_inv, *args->view);\n\tMatrix4x4_ToArrayFloatGL(view_inv, (float*)ret.inv_view);\n\n\t// previous frame matrices\n\tMatrix4x4_ToArrayFloatGL(g_rtx.prev_inv_proj, (float*)ret.prev_inv_proj);\n\tMatrix4x4_ToArrayFloatGL(g_rtx.prev_inv_view, (float*)ret.prev_inv_view);\n\tMatrix4x4_Copy(g_rtx.prev_inv_view, view_inv);\n\tMatrix4x4_Copy(g_rtx.prev_inv_proj, proj_inv);\n\n\tret.res[0] = frame_width;\n\tret.res[1] = frame_height;\n\tret.ray_cone_width = atanf((2.0f*tanf(DEG2RAD(fov_angle_y) * 0.5f)) / (float)frame_height);\n\tret.skybox_exposure = R_TexturesGetSkyboxInfo().exposure;\n\n\tparseDebugDisplayValue();\n\tif (g_rtx.debug.rt_debug_display_only_value) {\n\t\tret.debug_display_only = g_rtx.debug.rt_debug_display_only_value;\n\t} else {\n\t\tret.debug_display_only = r_lightmap->value != 0 ? DEBUG_DISPLAY_LIGHTING : DEBUG_DISPLAY_DISABLED;\n\t}\n\n\tparseDebugFlags();\n\tret.debug_flags = g_rtx.debug.rt_debug_flags_value;\n\n\tret.random_seed = getRandomSeed();\n\n#define SET_RENDERER_FLAG(cvar,flag) (CVAR_TO_BOOL(cvar) ? flag : 0)\n\tret.renderer_flags = SET_RENDERER_FLAG(rt_only_diffuse_gi, RENDERER_FLAG_ONLY_DIFFUSE_GI) |\n\t\t\t\t\t\t  SET_RENDERER_FLAG(rt_separated_reflection, RENDERER_FLAG_SEPARATED_REFLECTION) |\n\t\t\t\t\t\t  SET_RENDERER_FLAG(rt_denoise_gi_by_sh, RENDERER_FLAG_DENOISE_GI_BY_SH) |\n\t\t\t\t\t\t  SET_RENDERER_FLAG(rt_disable_gi, RENDERER_FLAG_DISABLE_GI) |\n\t\t\t\t\t\t  SET_RENDERER_FLAG(rt_spatial_reconstruction, RENDERER_FLAG_SPATIAL_RECONSTRUCTION);\n#undef SET_RENDERER_FLAG\n\n\treturn ret;\n}\n\ntypedef struct {\n\tconst vk_ray_frame_render_args_t* render_args;\n\tint frame_index;\n\tuint32_t frame_counter;\n\tfloat fov_angle_y;\n\tint frame_width, frame_height;\n} perform_tracing_args_t;\n\nstatic r_vk_image_t* performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* args) {\n\tAPROF_SCOPE_DECLARE_BEGIN(perform, __FUNCTION__);\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\tDEBUG_BEGIN(cmdbuf, \"yay tracing\");\n\n\tg_rtx.uniform.current = prepareUniformBuffer(args->render_args, args->fov_angle_y, args->frame_width, args->frame_height);\n\n\tASSERT(g_rtx.meatpipe);\n\tr_vk_image_t *const ret = R_VkMeatpipeDispatch(g_rtx.meatpipe, (vk_meatpipe_dispatch_t){\n\t\t.combuf = combuf,\n\t\t.frame_sequence = args->frame_counter,\n\t\t.frame_set_slot = args->frame_index,\n\t\t.width = args->frame_width,\n\t\t.height = args->frame_height,\n\t\t.is_discontinuous = g_rtx.discontinuity,\n\t});\n\n\tif (g_rtx.discontinuity) {\n\t\tDEBUG(\"discontinuity => false\");\n\t\tg_rtx.discontinuity = false;\n\t}\n\n\tDEBUG_END(cmdbuf);\n\tAPROF_SCOPE_END(perform);\n\n\treturn ret;\n}\n\nstatic void destroyMeatpipe(void) {\n\tR_VkMeatpipeDestroy(g_rtx.meatpipe);\n\tg_rtx.meatpipe = NULL;\n}\n\nstatic qboolean reloadMeatpipe(void) {\n\tstruct vk_meatpipe_s *const newpipe = R_VkMeatpipeCreateFromFile(\"rt.meat\");\n\tif (!newpipe)\n\t\treturn false;\n\n\tif (!R_VkMeatpipeAcquireResources(newpipe, g_rtx.max_frame_width, g_rtx.max_frame_height))\n\t\tgoto fail;\n\n\tdestroyMeatpipe();\n\n\tg_rtx.meatpipe = newpipe;\n\tg_rtx.meatpipe_out = R_VkResourceFindByName(\"dest\");\n\tASSERT(g_rtx.meatpipe_out);\n\n\treturn true;\n\nfail:\n\tR_VkMeatpipeDestroy(newpipe);\n\treturn false;\n}\n\nstatic void reloadOrResizeIfNeeded(const vk_ray_frame_render_args_t* args) {\n\tqboolean need_resize = false;\n\n\tif (g_rtx.max_frame_width < args->dst->width) {\n\t\tg_rtx.max_frame_width = ALIGN_UP(args->dst->width, 16);\n\t\tWARN(\"Increasing max_frame_width to %d\", g_rtx.max_frame_width);\n\t\tneed_resize = true;\n\t}\n\n\tif (g_rtx.max_frame_height < args->dst->height) {\n\t\tg_rtx.max_frame_height = ALIGN_UP(args->dst->height, 16);\n\t\tWARN(\"Increasing max_frame_height to %d\", g_rtx.max_frame_height);\n\t\tneed_resize = true;\n\t}\n\n\tif (g_rtx.reload_pipeline) {\n\t\tWARN(\"Reloading RTX shaders/pipelines\");\n\t\tXVK_CHECK(vkDeviceWaitIdle(vk_core.device));\n\n\t\tif (reloadMeatpipe())\n\t\t\tneed_resize = false;\n\n\t\tg_rtx.reload_pipeline = false;\n\t}\n\n\tif (need_resize) {\n\t\tif (!R_VkMeatpipeAcquireResources(g_rtx.meatpipe, g_rtx.max_frame_width, g_rtx.max_frame_height)) {\n\t\t\tERR(\"Unable to reacquire resources and resize RT framebuffer. Bad things will happen.\");\n\t\t}\n\t\tneed_resize = false;\n\t}\n\n\tASSERT(args->dst->width <= g_rtx.max_frame_width);\n\tASSERT(args->dst->height <= g_rtx.max_frame_height);\n}\n\nvoid VK_RayFrameEnd(const vk_ray_frame_render_args_t* args)\n{\n\tAPROF_SCOPE_DECLARE_BEGIN(ray_frame_end, __FUNCTION__);\n\n\tASSERT(vk_core.rtx);\n\t// ubo should contain two matrices\n\t// FIXME pass these matrices explicitly to let RTX module handle ubo itself\n\n\tg_rtx.frame_number++;\n\n\treloadOrResizeIfNeeded(args);\n\n\t// TODO dynamic scaling based on perf\n\tconst int frame_width = args->dst->width;\n\tconst int frame_height = args->dst->height;\n\n\t// Do not draw when we have no swapchain\n\tif (!args->dst->image)\n\t\tgoto tail;\n\n\tif (RT_VkAccelIsEmpty()) {\n\t\tR_VkImageClear( args->dst, args->combuf, NULL );\n\t} else {\n\t\tconst perform_tracing_args_t trace_args = {\n\t\t\t.render_args = args,\n\t\t\t.frame_index = (g_rtx.frame_number % 2),\n\t\t\t.frame_counter = g_rtx.frame_number,\n\t\t\t.fov_angle_y = args->fov_angle_y,\n\t\t\t.frame_width = frame_width,\n\t\t\t.frame_height = frame_height,\n\t\t};\n\t\tr_vk_image_t *const result = performTracing( args->combuf, &trace_args );\n\t\tASSERT(g_rtx.meatpipe_out);\n\t\tconst r_vkimage_blit_args blit_args = {\n\t\t\t.src = {\n\t\t\t\t.image = result,\n\t\t\t\t.width = frame_width,\n\t\t\t\t.height = frame_height,\n\t\t\t},\n\t\t\t.dst = {\n\t\t\t\t.image = args->dst,\n\t\t\t},\n\t\t};\n\n\t\tR_VkImageBlit( args->combuf, &blit_args );\n\t}\n\ntail:\n\tAPROF_SCOPE_END(ray_frame_end);\n}\n\nstatic void reloadPipeline( void ) {\n\tg_rtx.reload_pipeline = true;\n}\n\nqboolean VK_RayInit( void )\n{\n\tASSERT(vk_core.rtx);\n\t// TODO complain and cleanup on failure\n\n\tg_rtx.max_frame_width = MIN_FRAME_WIDTH;\n\tg_rtx.max_frame_height = MIN_FRAME_HEIGHT;\n\n\tif (!RT_VkAccelInit())\n\t\treturn false;\n\n\t// FIXME shutdown accel\n\tif (!RT_DynamicModelInit())\n\t\treturn false;\n\n\tg_rtx.uniform.unit_size = ALIGN_UP(sizeof(struct UniformBuffer), v_device_info.properties.limits.minUniformBufferOffsetAlignment);\n\n\tif (!VK_BufferCreate(\"ray uniform.buffer\", &g_rtx.uniform.buffer, g_rtx.uniform.unit_size * MAX_FRAMES_IN_FLIGHT,\n\t\tVK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n\t\tVK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT))\n\t{\n\t\t// TODO cleanup\n\t\treturn false;\n\t}\n\n\tg_rtx.uniform.producer = (Producer) {\n\t\t.name = \"ubo\",\n\t\t.produce = produceUboResource,\n\t};\n\n\tg_rtx.uniform.resource = R_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = \"ubo\",\n\t\t.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,\n\t\t.buffer = &g_rtx.uniform.buffer,\n\t\t.offset = 0, // Will be set dynamically each frame\n\t\t.size = sizeof(struct UniformBuffer),\n\t\t.producer = &g_rtx.uniform.producer,\n\t});\n\n\tif (!RT_KusochkiInit()) {\n\t\t// TODO cleanup\n\t\treturn false;\n\t}\n\n\treloadMeatpipe();\n\tif (!g_rtx.meatpipe)\n\t\treturn false;\n\n\tRT_RayModel_Clear();\n\n\tgEngine.Cmd_AddCommand(\"rt_debug_reload_pipelines\", reloadPipeline, \"Reload RT pipelines\");\n\n#define X(name, info) #name \", \"\n\tg_rtx.debug.rt_debug_display_only = gEngine.Cvar_Get(\"rt_debug_display_only\", \"\", FCVAR_GLCONFIG,\n\t\t\"Display only the specified channel (\" LIST_DISPLAYS(X) \"etc)\");\n\n\tg_rtx.debug.rt_debug_flags = gEngine.Cvar_Get(\"rt_debug_flags\", \"\", FCVAR_GLCONFIG,\n\t\t\"Enable shader debug flags (\" LIST_DEBUG_FLAGS(X) \"etc)\");\n#undef X\n\n\tg_rtx.debug.rt_debug_fixed_random_seed = gEngine.Cvar_Get(\"rt_debug_fixed_random_seed\", \"\", FCVAR_GLCONFIG,\n\t\t\"Fix random seed value for RT monte carlo sampling. Used for reproducible regression testing\");\n\n\treturn true;\n}\n\nvoid VK_RayShutdown( void ) {\n\tASSERT(vk_core.rtx);\n\n\tdestroyMeatpipe();\n\n\tRT_KusochkiShutdown();\n\tVK_BufferDestroy(&g_rtx.uniform.buffer);\n\n\tRT_VkAccelShutdown();\n\tRT_DynamicModelShutdown();\n}\n\nvoid RT_FrameDiscontinuity( void ) {\n\tDEBUG(\"%s\", __FUNCTION__);\n\tg_rtx.discontinuity = true;\n}\n"
  },
  {
    "path": "ref/vk/vk_rtx.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\nvoid VK_RayFrameBegin( void );\n\nstruct vk_combuf_s;\nstruct r_vk_image_s;\ntypedef struct {\n\tstruct vk_combuf_s *combuf;\n\n\tstruct r_vk_image_s *dst;\n\n\tconst matrix4x4 *projection, *view;\n\n\tfloat fov_angle_y;\n} vk_ray_frame_render_args_t;\nvoid VK_RayFrameEnd(const vk_ray_frame_render_args_t* args);\n\nvoid VK_RayNewMapBegin( void );\n\nqboolean VK_RayInit( void );\nvoid VK_RayShutdown( void );\n\nstruct vk_render_geometry_s;\nstruct rt_model_s;\nstruct r_vk_material_s;\n\ntypedef enum {\n\tkBlasBuildStatic, // builds slow for fast trace\n\tkBlasBuildDynamicUpdate, // builds if not built, updates if built\n\tkBlasBuildDynamicFast, // builds fast from scratch (no correlation with previous frame guaranteed, e.g. triapi)\n} rt_blas_usage_e;\n\ntypedef struct {\n\tconst char *debug_name; // Must remain alive until RT_ModelDestroy\n\tconst struct vk_render_geometry_s *geometries;\n\tint geometries_count;\n\trt_blas_usage_e usage;\n} rt_model_create_t;\nstruct rt_model_s *RT_ModelCreate(rt_model_create_t args);\nvoid RT_ModelDestroy(struct rt_model_s *model);\n\nqboolean RT_ModelUpdate(struct rt_model_s *model, const struct vk_render_geometry_s *geometries, int geometries_count);\n\nqboolean RT_ModelUpdateMaterials(struct rt_model_s *model, const struct vk_render_geometry_s *geometries, int geometries_count, const int *geom_indices, int geom_indices_count);\n\ntypedef struct {\n\tint material_mode;\n\tuint32_t material_flags;\n\n\tconst matrix3x4 *transform, *prev_transform;\n\tconst vec4_t *color_srgb;\n\n\tstruct {\n\t\tconst struct r_vk_material_s *material;\n\n\t\t// These are needed in order to recreate kusochki geometry data\n\t\t// TODO remove when material data is split from kusochki\n\t\tint geoms_count;\n\t\tconst struct vk_render_geometry_s *geoms;\n\t} override;\n} rt_frame_add_model_t;\n\nvoid RT_FrameAddModel( struct rt_model_s *model, rt_frame_add_model_t args );\n\ntypedef struct {\n\tconst char *debug_name;\n\tconst struct vk_render_geometry_s *geometries;\n\tconst vec4_t *color_srgb;\n\tint geometries_count;\n\tint render_type;\n} rt_frame_add_once_t;\n\nvoid RT_FrameAddOnce( rt_frame_add_once_t args );\n\n// Signal that the next frame is discontinuous, and all accumulated screen-space\n// statistics should be reset. Should help with newmap/saveload/teleport denoiser artifacts.\nvoid RT_FrameDiscontinuity( void );\n"
  },
  {
    "path": "ref/vk/vk_scene.c",
    "content": "#include \"vk_scene.h\"\n#include \"vk_brush.h\"\n#include \"vk_studio.h\"\n#include \"vk_lightmap.h\"\n#include \"vk_const.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"vk_studio.h\"\n#include \"vk_common.h\"\n#include \"vk_core.h\"\n#include \"vk_sprite.h\"\n#include \"vk_beams.h\"\n#include \"r_decals.h\"\n#include \"vk_light.h\"\n#include \"vk_rtx.h\"\n#include \"r_textures.h\"\n#include \"vk_cvar.h\"\n#include \"vk_materials.h\"\n#include \"camera.h\"\n#include \"vk_mapents.h\"\n#include \"std/profiler.h\"\n#include \"vk_entity_data.h\"\n#include \"vk_logs.h\"\n\n#include \"ref_params.h\"\n#include \"eiface.h\"\n#include \"pm_movevars.h\"\n#include \"xash3d_mathlib.h\"\n\n#include <stdlib.h> // qsort\n#include <memory.h>\n\n#define LOG_MODULE misc\n\n#define PROFILER_SCOPES(X) \\\n\tX(scene_render, \"VK_SceneRender\"); \\\n\tX(draw_viewmodel, \"draw viewmodel\"); \\\n\tX(draw_worldbrush, \"draw worldbrush\"); \\\n\tX(draw_opaques, \"draw opaque entities\"); \\\n\tX(draw_opaque_beams, \"draw opaque beams\"); \\\n\tX(draw_translucent, \"draw translucent entities\"); \\\n\tX(draw_transparent_beams, \"draw transparent beams\"); \\\n\n#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope)\nPROFILER_SCOPES(SCOPE_DECLARE)\n#undef SCOPE_DECLARE\n\ntypedef struct vk_trans_entity_s {\n\tstruct cl_entity_s *entity;\n\tint render_mode;\n} vk_trans_entity_t;\n\ntypedef struct draw_list_s {\n\tstruct cl_entity_s\t*solid_entities[MAX_SCENE_ENTITIES];\t// opaque moving or alpha brushes\n\tvk_trans_entity_t trans_entities[MAX_SCENE_ENTITIES];\t// translucent brushes or studio models kek\n\tstruct cl_entity_s\t*beam_entities[MAX_SCENE_ENTITIES];\n\tuint\t\tnum_solid_entities;\n\tuint\t\tnum_trans_entities;\n\tuint\t\tnum_beam_entities;\n} draw_list_t;\n\nstatic struct {\n\tdraw_list_t\tdraw_stack[MAX_SCENE_STACK];\n\tint\t\tdraw_stack_pos;\n\tdraw_list_t\t*draw_list;\n} g_lists;\n\nstatic void preloadModels( void ) {\n\tconst int num_models = gp_cl->nummodels;\n\n\t// Load all models at once\n\tDEBUG( \"Num models: %d:\", num_models );\n\tfor( int i = 0; i < num_models; i++ )\n\t{\n\t\tmodel_t\t*m;\n\t\tif(( m = gp_cl->models[i + 1]) == NULL )\n\t\t\tcontinue;\n\n\t\tconst qboolean is_worldmodel = i == 0;\n\n\t\tDEBUG( \"  %d: name=%s, type=%d, submodels=%d, nodes=%d, surfaces=%d, nummodelsurfaces=%d\", i, m->name, m->type, m->numsubmodels, m->numnodes, m->numsurfaces, m->nummodelsurfaces);\n\n\t\tR_VkMaterialsLoadForModel(m);\n\n\t\tswitch (m->type) {\n\t\t\tcase mod_brush:\n\t\t\t\tif (!R_BrushModelLoad(m, is_worldmodel))\n\t\t\t\t\tgEngine.Host_Error( \"Couldn't load brush model %s\\n\", m->name );\n\t\t\t\tbreak;\n\n\t\t\tcase mod_studio:\n\t\t\t\tif (!R_StudioModelPreload(m))\n\t\t\t\t\tgEngine.Host_Error( \"Couldn't preload studio model %s\\n\", m->name );\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void loadMap(const model_t* const map, qboolean force_reload) {\n\tVK_EntityDataClear();\n\n\t// Depends on VK_EntityDataClear()\n\tR_StudioCacheClear();\n\tR_GeometryBuffer_MapClear();\n\n\tVK_ClearLightmap();\n\n\t// This is to ensure that we have computed lightstyles properly\n\tVK_RunLightStyles((lightstyle_t *)ENGINE_GET_PARM( PARM_GET_LIGHTSTYLES_PTR ));\n\n\tif (vk_core.rtx)\n\t\tVK_RayNewMapBegin();\n\n\tRT_LightsNewMap(map);\n\n\t// TODO doesn't really need to exist: sprite instance models are static\n\tR_SpriteNewMapFIXME();\n\n\t// Load light entities and patch data prior to loading map brush model\n\tXVK_ParseMapEntities();\n\n\t// Load PBR materials (depends on wadlist from parsed map entities)\n\tR_VkMaterialsReload();\n\n\t// Parse patch data\n\t// Depends on loaded materials. Must preceed loading brush models.\n\tXVK_ParseMapPatches();\n\n\tRT_LightsLoadBegin(map);\n\tpreloadModels();\n\t// Marks all loaded lights as static. Should happen after preloadModels(), where brush models are loaded.\n\tRT_LightsLoadEnd();\n\n\t// Can only do after preloadModels(), as we need to know whether there are SURF_DRAWSKY\n\tR_TextureSetupSky( MOVEVARS->skyName, force_reload );\n\n\t// TODO should we do something like R_BrushEndLoad?\n\tVK_UploadLightmap();\n}\n\nstatic void reloadPatches( void ) {\n\tINFO(\"Reloading patches and materials\");\n\n\t// FIXME R_VkStagingFlushSync();\n\n\tXVK_CHECK(vkDeviceWaitIdle( vk_core.device ));\n\n\tR_BrushModelDestroyAll();\n\n\tconst model_t *const map = WORLDMODEL;\n\tconst qboolean force_reload = true;\n\tloadMap(map, force_reload);\n}\n\ncl_entity_t* R_GetEntityByIndex(int index)\n{\n\tif (index > 0 && index < globals.max_entities)\n\t\treturn globals.entities + index;\n\n\treturn NULL;\n}\n\nmodel_t* R_ModelHandle(int index)\n{\n\tif (index > 0 && index < gp_cl->nummodels)\n\t\treturn gp_cl->models[index];\n\n\treturn NULL;\n}\n\nvoid R_InitModelsPolys ( void )\n{\n\t// copypaste from GL_BuildLightmaps\n\tint\ti, j = 0;\n\tmodel_t\t*m;\n\n\tfor( i = 0; i < gp_cl->nummodels; i++ )\n\t{\n\t\tif(( m = R_ModelHandle( i + 1 )) == NULL )\n\t\t\tcontinue;\n\n\t\tif( m->name[0] == '*' || m->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < m->numsurfaces; j++ )\n\t\t{\n\t\t\t// clearing all decal chains\n\t\t\tm->surfaces[j].pdecals = NULL;\n\t\t\tm->surfaces[j].visframe = 0;\n\n\t\t\t//GL_CreateSurfaceLightmap( m->surfaces + j, m ); // TODO: it is really needed?\n\n\t\t\tif( m->surfaces[j].flags & SURF_DRAWTURB )\n\t\t\t\tcontinue;\n\n\t\t\tR_BuildPolygonFromSurface( m, m->surfaces + j );\n\t\t}\n\t}\n}\n\nvoid VK_SceneInit( void )\n{\n\tPROFILER_SCOPES(APROF_SCOPE_INIT);\n\n\tg_lists.draw_list = g_lists.draw_stack;\n\tg_lists.draw_stack_pos = 0;\n\n\tif (vk_core.rtx) {\n\t\tgEngine.Cmd_AddCommand(\"rt_debug_reload_patches\", reloadPatches, \"Reload patched entities, lights and extra PBR materials\");\n\t}\n}\n\n#define R_ModelOpaque( rm )\t( rm == kRenderNormal )\nstatic int R_FIXME_GetEntityRenderMode( cl_entity_t *ent )\n{\n\t//int\t\ti, opaque, trans;\n\t//mstudiotexture_t\t*ptexture;\n\tmodel_t\t\t*model;\n\t//studiohdr_t\t*phdr;\n\n\t/* TODO\n\tif( ent->player ) // check it for real playermodel\n\t\tmodel = R_StudioSetupPlayerModel( ent->curstate.number - 1 );\n\telse */ model = ent->model;\n\n\tif( R_ModelOpaque( ent->curstate.rendermode ))\n\t{\n\t\tif(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT ))\n\t\t\treturn kRenderTransAlpha;\n\t}\n\n\t/* TODO studio models hack\n\tptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex);\n\n\tfor( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ )\n\t{\n\t\t// ignore chrome & additive it's just a specular-like effect\n\t\tif( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME ))\n\t\t\ttrans++;\n\t\telse opaque++;\n\t}\n\n\t// if model is more additive than opaque\n\tif( trans > opaque )\n\t\treturn kRenderTransAdd;\n\t*/\n\treturn ent->curstate.rendermode;\n}\n\nvoid R_SceneMapDestroy( void ) {\n\t// Make sure no rendering is happening\n\tXVK_CHECK(vkDeviceWaitIdle( vk_core.device ));\n\n\tR_BrushModelDestroyAll();\n}\n\nstatic void R_ClearModelDecalChains( void )\n{\n\tint i, j;\n\tmodel_t *m;\n\n\tfor( i = 0; i < gp_cl->nummodels; i++ )\n\t{\n\t\tif(( m = R_ModelHandle( i + 1 )) == NULL )\n\t\t\tcontinue;\n\n\t\tif( m->name[0] == '*' || m->type != mod_brush )\n\t\t\tcontinue;\n\n\t\tfor( j = 0; j < m->numsurfaces; j++ )\n\t\t\tm->surfaces[j].pdecals = NULL;\n\t}\n}\n\n// tell the renderer what new map is started\nvoid R_NewMap( void ) {\n\tconst model_t *const map = WORLDMODEL;\n\n\t// Existence of cache.data for the world means that we've already have loaded this map\n\t// and this R_NewMap call is from within loading of a saved game.\n\tconst qboolean is_save_load = !!map->cache.data;\n\n\tINFO( \"R_NewMap(%s) is_save_load=%d\", map->name, is_save_load );\n\n\t// New map causes entites to be reallocated regardless of whether it was save-load.\n\t// This realloc invalidates all previous entity data and pointers.\n\t// Make sure that EntityData doesn't accidentally reference old pointers.\n\tVK_EntityDataClear();\n\n\tR_ClearDecals();\n\tR_ClearModelDecalChains();\n\n\tRT_FrameDiscontinuity();\n\n\t// Skip clearing already loaded data if the map hasn't changed.\n\tif (is_save_load)\n\t\treturn;\n\n\t// Make sure that we're not rendering anything before starting to mess with GPU objects\n\tXVK_CHECK(vkDeviceWaitIdle(vk_core.device));\n\tconst qboolean force_reload = false;\n\tloadMap(map, force_reload);\n\n\tR_StudioResetPlayerModels();\n\n\tR_InitModelsPolys();\n}\n\nqboolean R_AddEntity( struct cl_entity_s *clent, int type )\n{\n\t/* if( !r_drawentities->value ) */\n\t/* \treturn false; // not allow to drawing */\n\tint render_mode;\n\n\tif( !clent || !clent->model )\n\t\treturn false; // if set to invisible, skip\n\n\tif( FBitSet( clent->curstate.effects, EF_NODRAW ))\n\t\treturn false; // done\n\n\trender_mode = R_FIXME_GetEntityRenderMode( clent );\n\n\t/* TODO\n\tif( !R_ModelOpaque( clent->curstate.rendermode ) && CL_FxBlend( clent ) <= 0 )\n\t\treturn true; // invisible\n\n\tswitch( type )\n\t{\n\tcase ET_FRAGMENTED:\n\t\tr_stats.c_client_ents++;\n\t\tbreak;\n\tcase ET_TEMPENTITY:\n\t\tr_stats.c_active_tents_count++;\n\t\tbreak;\n\tdefault: break;\n\t}\n\t*/\n\n\tif( render_mode == kRenderNormal )\n\t{\n\t\tif( g_lists.draw_list->num_solid_entities >= ARRAYSIZE(g_lists.draw_list->solid_entities) )\n\t\t\treturn false;\n\n\t\tg_lists.draw_list->solid_entities[g_lists.draw_list->num_solid_entities] = clent;\n\t\tg_lists.draw_list->num_solid_entities++;\n\t}\n\telse\n\t{\n\t\tif( g_lists.draw_list->num_trans_entities >= ARRAYSIZE(g_lists.draw_list->trans_entities) )\n\t\t\treturn false;\n\n\t\tg_lists.draw_list->trans_entities[g_lists.draw_list->num_trans_entities] = (vk_trans_entity_t){ clent, render_mode };\n\t\tg_lists.draw_list->num_trans_entities++;\n\t}\n\n\treturn true;\n}\n\nvoid R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities )\n{\n\tif( !allocate )\n\t{\n\t\tg_lists.draw_list->num_solid_entities = 0;\n\t\tg_lists.draw_list->num_trans_entities = 0;\n\t\tg_lists.draw_list->num_beam_entities = 0;\n\t}\n\n\t{\n\t\tglobals.max_entities = max_entities;\n\t\tglobals.entities = entities;\n\t}\n\n\tif( gEngine.drawFuncs->R_ProcessEntData )\n\t\tgEngine.drawFuncs->R_ProcessEntData( allocate );\n}\n\nvoid R_ClearScreen( void )\n{\n\tg_lists.draw_list->num_solid_entities = 0;\n\tg_lists.draw_list->num_trans_entities = 0;\n\tg_lists.draw_list->num_beam_entities = 0;\n\n\t// clear the scene befor start new frame\n\tif( gEngine.drawFuncs->R_ClearScene != NULL )\n\t\tgEngine.drawFuncs->R_ClearScene();\n\n}\n\nvoid R_PushScene( void )\n{\n\tif( ++g_lists.draw_stack_pos >= MAX_SCENE_STACK )\n\t\tgEngine.Host_Error( \"draw stack overflow\\n\" );\n\tg_lists.draw_list = &g_lists.draw_stack[g_lists.draw_stack_pos];\n}\n\nvoid R_PopScene( void )\n{\n\tif( --g_lists.draw_stack_pos < 0 )\n\t\tgEngine.Host_Error( \"draw stack underflow\\n\" );\n\tg_lists.draw_list = &g_lists.draw_stack[g_lists.draw_stack_pos];\n}\n\n// clear the render entities before each frame\nvoid R_ClearScene( void )\n{\n\tg_lists.draw_list->num_solid_entities = 0;\n\tg_lists.draw_list->num_trans_entities = 0;\n\tg_lists.draw_list->num_beam_entities = 0;\n}\n\n\nvoid R_RenderScene( void )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic void R_RotateForEntity( matrix4x4 out, const cl_entity_t *e )\n{\n\tfloat\tscale = 1.0f;\n\n\t// TODO we should be able to remove this, as worldmodel is draw in a separate code path\n\tif( e == globals.entities )\n\t{\n\t\tMatrix4x4_LoadIdentity(out);\n\t\treturn;\n\t}\n\n\tif( e->model->type != mod_brush && e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\tMatrix4x4_CreateFromEntity( out, e->angles, e->origin, scale );\n}\n\n// FIXME find a better place for this function\nstatic int R_RankForRenderMode( int rendermode )\n{\n\tswitch( rendermode )\n\t{\n\tcase kRenderTransTexture:\n\t\treturn 1;\t// draw second\n\tcase kRenderTransAdd:\n\t\treturn 2;\t// draw third\n\tcase kRenderGlow:\n\t\treturn 3;\t// must be last!\n\t}\n\treturn 0;\n}\n\n/*\n===============\nR_TransEntityCompare\n\nSorting translucent entities by rendermode then by distance\n\nFIXME find a better place for this function\n===============\n*/\nstatic int R_TransEntityCompare( const void *a, const void *b)\n{\n\tvk_trans_entity_t *tent1, *tent2;\n\tcl_entity_t\t*ent1, *ent2;\n\tvec3_t\t\tvecLen, org;\n\tfloat\t\tdist1, dist2;\n\tint\t\trendermode1;\n\tint\t\trendermode2;\n\n\ttent1 = (vk_trans_entity_t*)a;\n\ttent2 = (vk_trans_entity_t*)b;\n\n\tent1 = tent1->entity;\n\tent2 = tent2->entity;\n\n\trendermode1 = tent1->render_mode;\n\trendermode2 = tent2->render_mode;\n\n\t// sort by distance\n\tif( ent1->model->type != mod_brush || rendermode1 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent1->model->mins, ent1->model->maxs, org );\n\t\tVectorAdd( ent1->origin, org, org );\n\t\tVectorSubtract( g_camera.vieworg, org, vecLen );\n\t\tdist1 = DotProduct( vecLen, vecLen );\n\t}\n\telse dist1 = 1000000000;\n\n\tif( ent2->model->type != mod_brush || rendermode2 != kRenderTransAlpha )\n\t{\n\t\tVectorAverage( ent2->model->mins, ent2->model->maxs, org );\n\t\tVectorAdd( ent2->origin, org, org );\n\t\tVectorSubtract( g_camera.vieworg, org, vecLen );\n\t\tdist2 = DotProduct( vecLen, vecLen );\n\t}\n\telse dist2 = 1000000000;\n\n\tif( dist1 > dist2 )\n\t\treturn -1;\n\tif( dist1 < dist2 )\n\t\treturn 1;\n\n\t// then sort by rendermode\n\tif( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 ))\n\t\treturn 1;\n\tif( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 ))\n\t\treturn -1;\n\n\treturn 0;\n}\n\n// FIXME where should this function be\n#define RP_NORMALPASS() true // FIXME ???\nint CL_FxBlend( cl_entity_t *e ) // FIXME do R_SetupFrustum: , vec3_t vforward )\n{\n\tint\tblend = 0;\n\tfloat\toffset, dist;\n\tvec3_t\ttmp;\n\n\toffset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx\n\n\tswitch( e->curstate.renderfx )\n\t{\n\tcase kRenderFxPulseSlowWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFastWide:\n\t\tblend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseSlow:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset );\n\t\tbreak;\n\tcase kRenderFxPulseFast:\n\t\tblend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset );\n\t\tbreak;\n\tcase kRenderFxFadeSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 0 )\n\t\t\t\te->curstate.renderamt -= 1;\n\t\t\telse e->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFadeFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt > 3 )\n\t\t\t\te->curstate.renderamt -= 4;\n\t\t\telse e->curstate.renderamt = 0;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidSlow:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 255 )\n\t\t\t\te->curstate.renderamt += 1;\n\t\t\telse e->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxSolidFast:\n\t\tif( RP_NORMALPASS( ))\n\t\t{\n\t\t\tif( e->curstate.renderamt < 252 )\n\t\t\t\te->curstate.renderamt += 4;\n\t\t\telse e->curstate.renderamt = 255;\n\t\t}\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeSlow:\n\t\tblend = 20 * sin( gp_cl->time * 4 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFast:\n\t\tblend = 20 * sin( gp_cl->time * 16 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxStrobeFaster:\n\t\tblend = 20 * sin( gp_cl->time * 36 + offset );\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerSlow:\n\t\tblend = 20 * (sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset ));\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxFlickerFast:\n\t\tblend = 20 * (sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset ));\n\t\tif( blend < 0 ) blend = 0;\n\t\telse blend = e->curstate.renderamt;\n\t\tbreak;\n\tcase kRenderFxHologram:\n\tcase kRenderFxDistort:\n\t\tVectorCopy( e->origin, tmp );\n\t\tVectorSubtract( tmp, g_camera.vieworg, tmp );\n\t\tdist = DotProduct( tmp, g_camera.vforward );\n\n\t\t// turn off distance fade\n\t\tif( e->curstate.renderfx == kRenderFxDistort )\n\t\t\tdist = 1;\n\n\t\tif( dist <= 0 )\n\t\t{\n\t\t\tblend = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\te->curstate.renderamt = 180;\n\t\t\tif( dist <= 100 ) blend = e->curstate.renderamt;\n\t\t\telse blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt );\n\t\t\tblend += gEngine.COM_RandomLong( -32, 31 );\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tblend = e->curstate.renderamt;\n\t\tbreak;\n\t}\n\n\tblend = bound( 0, blend, 255 );\n\n\treturn blend;\n}\n\nstatic void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z )\n{\n\tout[0][3] = x;\n\tout[1][3] = y;\n\tout[2][3] = z;\n}\n\nstatic void drawEntity( cl_entity_t *ent, int render_mode )\n{\n\tconst model_t *mod = ent->model;\n\tmatrix4x4 model;\n\n\tif (!mod)\n\t\treturn;\n\n\t// handle studiomodels with custom rendermodes on texture\n\tconst float blend = render_mode == kRenderNormal ? 1.f : CL_FxBlend( ent ) / 255.0f;\n\n\t// TODO ref_gl does this earlier (when adding entity), can we too?\n\tif( blend <= 0.0f )\n\t\treturn;\n\n\tswitch (mod->type)\n\t{\n\t\tcase mod_brush:\n\t\t\tR_RotateForEntity( model, ent );\n\n\t\t\t// If this is potentially a func_any model\n\t\t\tif (ent->model->name[0] == '*') {\n\t\t\t\tfor (int i = 0; i < g_map_entities.func_any_count; ++i) {\n\t\t\t\t\txvk_mapent_func_any_t *const fw = g_map_entities.func_any + i;\n\t\t\t\t\tif (Q_strcmp(ent->model->name, fw->model) == 0 && fw->origin_patched) {\n\t\t\t\t\t\t/* DEBUG(\"ent->index=%d (%s) mapent:%d off=%f %f %f\", */\n\t\t\t\t\t\t/* \t\tent->index, ent->model->name, fw->entity_index, */\n\t\t\t\t\t\t/* \t\tfw->origin[0], fw->origin[1], fw->origin[2]); */\n\t\t\t\t\t\tMatrix3x4_LoadIdentity(model);\n\t\t\t\t\t\tMatrix4x4_SetOrigin(model, fw->origin[0], fw->origin[1], fw->origin[2]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR_BrushModelDraw( ent, render_mode, blend, model );\n\t\t\tbreak;\n\n\t\tcase mod_studio:\n\t\t\t// TODO R_RotateForEntity ?\n\t\t\tVK_StudioDrawModel( ent, render_mode, blend );\n\t\t\tbreak;\n\n\t\tcase mod_sprite:\n\t\t\tR_VkSpriteDrawModel( ent, blend );\n\t\t\tbreak;\n\n\t\tcase mod_alias:\n\t\tcase mod_bad:\n\t\t\tPRINT_NOT_IMPLEMENTED();\n\t\t\tbreak;\n\t}\n}\n\nstatic float g_frametime = 0;\n\nvoid VK_SceneRender( const ref_viewpass_t *rvp ) {\n\tAPROF_SCOPE_BEGIN_EARLY(scene_render);\n\tconst cl_entity_t* const local_player = globals.entities + gp_cl->playernum + 1;\n\n\tg_frametime = /*FIXME VK RP_NORMALPASS( )) ? */\n\tgp_cl->time - gp_cl->oldtime\n\t/* FIXME VK : 0.f */;\n\n\tVK_RenderSetupCamera( rvp );\n\n\tVK_RenderDebugLabelBegin( \"opaque\" );\n\n\t// Draw view model\n\t{\n\t\tAPROF_SCOPE_BEGIN(draw_viewmodel);\n\t\tR_RunViewmodelEvents();\n\t\tR_DrawViewModel();\n\t\tAPROF_SCOPE_END(draw_viewmodel);\n\t}\n\n\t// Draw world brush\n\t{\n\t\tAPROF_SCOPE_BEGIN(draw_worldbrush);\n\t\tcl_entity_t *world = globals.entities + 0;\n\t\tif( world && world->model )\n\t\t{\n\t\t\tconst float blend = 1.f;\n\t\t\tR_BrushModelDraw( world, kRenderNormal, blend, NULL );\n\t\t}\n\t\tAPROF_SCOPE_END(draw_worldbrush);\n\t}\n\n\t{\n\t\t// Draw flashlight for local player\n\t\tif( FBitSet( local_player->curstate.effects, EF_DIMLIGHT )) {\n\t\t\tRT_LightAddFlashlight(local_player, true);\n\t\t}\n\t}\n\n\tAPROF_SCOPE_BEGIN(draw_opaques);\n\t// Draw opaque entities\n\tfor (int i = 0; i < g_lists.draw_list->num_solid_entities; ++i)\n\t{\n\t\tcl_entity_t *ent = g_lists.draw_list->solid_entities[i];\n\t\tdrawEntity(ent, kRenderNormal);\n\n\t\t// Draw flashlight for other players\n\t\tif( FBitSet( ent->curstate.effects, EF_DIMLIGHT ) && ent != local_player) {\n\t\t\tRT_LightAddFlashlight(ent, false);\n\t\t}\n\t}\n\tAPROF_SCOPE_END(draw_opaques);\n\tR_DecalsFlush();\n\n\t// Draw opaque beams\n\tAPROF_SCOPE_BEGIN(draw_opaque_beams);\n\tgEngine.CL_DrawEFX( g_frametime, false );\n\tAPROF_SCOPE_END(draw_opaque_beams);\n\n\tVK_RenderDebugLabelEnd();\n\n\tVK_RenderDebugLabelBegin( \"tranparent\" );\n\n\t{\n\t\tAPROF_SCOPE_BEGIN(draw_translucent);\n\t\t// sort translucents entities by rendermode and distance\n\t\tqsort( g_lists.draw_list->trans_entities, g_lists.draw_list->num_trans_entities, sizeof( vk_trans_entity_t ), R_TransEntityCompare );\n\n\t\t// Draw transparent ents\n\t\tfor (int i = 0; i < g_lists.draw_list->num_trans_entities; ++i)\n\t\t{\n\t\t\tconst vk_trans_entity_t *ent = g_lists.draw_list->trans_entities + i;\n\t\t\tdrawEntity(ent->entity, ent->render_mode);\n\t\t}\n\t\tAPROF_SCOPE_END(draw_translucent);\n\t}\n\tR_DecalsFlush();\n\n\t// Draw transparent beams\n\tAPROF_SCOPE_BEGIN(draw_transparent_beams);\n\tgEngine.CL_DrawEFX( g_frametime, true );\n\tAPROF_SCOPE_END(draw_transparent_beams);\n\n\tVK_RenderDebugLabelEnd();\n\n\tif (r_infotool->value > 0)\n\t\tXVK_CameraDebugPrintCenterEntity();\n\n\tAPROF_SCOPE_END(scene_render);\n}\n\n/*\n================\nCL_AddCustomBeam\n\nAdd the beam that encoded as custom entity\n================\n*/\nvoid CL_AddCustomBeam( cl_entity_t *pEnvBeam )\n{\n\tif( g_lists.draw_list->num_beam_entities >= ARRAYSIZE(g_lists.draw_list->beam_entities) )\n\t{\n\t\tERR(\"Too many beams %d!\", g_lists.draw_list->num_beam_entities );\n\t\treturn;\n\t}\n\n\tif( pEnvBeam )\n\t{\n\t\tg_lists.draw_list->beam_entities[g_lists.draw_list->num_beam_entities] = pEnvBeam;\n\t\tg_lists.draw_list->num_beam_entities++;\n\t}\n}\n\nvoid CL_DrawBeams( int fTrans, BEAM *active_beams )\n{\n\tBEAM\t*pBeam;\n\tint\ti, flags;\n\n\t// FIXME VK pglDepthMask( fTrans ? GL_FALSE : GL_TRUE );\n\n\t// server beams don't allocate beam chains\n\t// all params are stored in cl_entity_t\n\tfor( i = 0; i < g_lists.draw_list->num_beam_entities; i++ )\n\t{\n\t\tcl_entity_t *currentbeam = g_lists.draw_list->beam_entities[i];\n\t\tflags = currentbeam->curstate.rendermode & 0xF0;\n\n\t\tif( fTrans && FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDrawCustomEntity( currentbeam, g_frametime );\n\t\t// FIXME VK r_stats.c_view_beams_count++;\n\t}\n\n\t// draw temporary entity beams\n\tfor( pBeam = active_beams; pBeam; pBeam = pBeam->next )\n\t{\n\t\tif( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tif( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID ))\n\t\t\tcontinue;\n\n\t\tR_BeamDraw( pBeam, g_frametime );\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_scene.h",
    "content": "#pragma once\n#include \"vk_const.h\"\n\n#include \"xash3d_types.h\"\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"ref_params.h\"\n\nstruct ref_viewpass_s;\nstruct cl_entity_s;\n\nvoid VK_SceneInit( void );\n\nvoid VK_SceneRender( const struct ref_viewpass_s *rvp );\n\nqboolean R_AddEntity( struct cl_entity_s *clent, int type );\nvoid R_ProcessEntData( qboolean allocate, struct cl_entity_s *entities, unsigned int max_entities );\nvoid R_ClearScreen( void );\nvoid R_ClearScene( void );\nvoid R_PushScene( void );\nvoid R_PopScene( void );\n\nvoid R_SceneMapDestroy( void );\nvoid R_NewMap( void );\nvoid R_RenderScene( void );\n\nint R_WorldToScreen( const vec3_t point, vec3_t screen );\nint TriWorldToScreen( const float *world, float *screen );\n\n// TODO should this be here?\nint CL_FxBlend( struct cl_entity_s *e );\nstruct beam_s;\nvoid CL_DrawBeams( int fTrans, struct beam_s *active_beams );\nvoid CL_AddCustomBeam( struct cl_entity_s *pEnvBeam );\n\nstruct cl_entity_s* R_GetEntityByIndex(int index);\nmodel_t* R_ModelHandle(int index);\nvoid R_InitModelsPolys ( void );\n"
  },
  {
    "path": "ref/vk/vk_speeds.c",
    "content": "#include \"vk_speeds.h\"\n#include \"vulkan/VDevice.h\"\n#include \"vulkan/VCombuf.h\"\n#include \"vk_common.h\"\n#include \"std/arrays.h\"\n#include \"std/stringview.h\"\n#include \"vk_logs.h\"\n\n#define MODULE_NAME \"speeds\"\n\nstatic void listGpuPerfCounters(void) {\n\tvDevicePrintPerformanceCounters(&v_device_info);\n}\n\nstatic void enableGpuPerfCounters(void) {\n\tARRAY_DYNAMIC_DECLARE(uint32_t, counters);\n\tarrayDynamicInitT(&counters);\n\n\tconst int argc = gEngine.Cmd_Argc();\n\tfor (int i = 1; i < argc; ++i) {\n\t\tconst char *const arg = gEngine.Cmd_Argv(i);\n\t\tconst const_string_view_t arg_sv = {arg, Q_strlen(arg) };\n\t\tconst SVParseLongResult num = svParseLong(arg_sv);\n\t\tif (num.chars_converted != arg_sv.len) {\n\t\t\tERR(\"Invalid perf query counter index \\\"%.*s\\\"\", arg_sv.len, arg_sv.s);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (num.value < 0 || num.value >= v_device_info.perf_counters.count) {\n\t\t\tERR(\"Perf query counter index %ld is out of bounds. Max %d\", num.value, v_device_info.perf_counters.count);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst uint32_t value = num.value;\n\t\tarrayDynamicAppendT(&counters, &value);\n\t}\n\n\tR_VkCombufPerfQueryEnable(counters.items, counters.count);\n}\n\nvoid VK_SpeedsInit(void) {\n\tgEngine.Cmd_AddCommand(\"vk_speeds_counters\", listGpuPerfCounters, \"List GPU performance query counters\");\n\tgEngine.Cmd_AddCommand(\"vk_speeds_counters_enable\", enableGpuPerfCounters, \"Enable GPU performance query counters\");\n\n}\n"
  },
  {
    "path": "ref/vk/vk_speeds.h",
    "content": "#pragma once\n\nvoid VK_SpeedsInit(void);\n"
  },
  {
    "path": "ref/vk/vk_sprite.c",
    "content": "#include \"vk_sprite.h\"\n#include \"r_textures.h\"\n#include \"camera.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"vk_scene.h\"\n#include \"r_speeds.h\"\n#include \"vk_math.h\"\n#include \"vk_logs.h\"\n\n#include \"sprite.h\"\n#include \"xash3d_mathlib.h\"\n#include \"com_strings.h\"\n#include \"pmtrace.h\"\n#include \"pm_defs.h\"\n\n#include <memory.h>\n\n#define MODULE_NAME \"sprite\"\n#define LOG_MODULE sprite\n\n// it's a Valve default value for LoadMapSprite (probably must be power of two)\n#define MAPSPRITE_SIZE\t128\n#define GLARE_FALLOFF\t19000.0f\n\nstatic struct {\n\tstruct {\n\t\tint sprites;\n\t} stats;\n\n\tstruct {\n\t\tr_geometry_range_t geom;\n\t\tvk_render_geometry_t geometry;\n\t\tvk_render_model_t model;\n\t} quad;\n} g_sprite;\n\nstatic qboolean createQuadModel(void) {\n\tg_sprite.quad.geom = R_GeometryRangeAlloc(4, 6);\n\tif (g_sprite.quad.geom.block_handle.size == 0) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot allocate geometry for sprite quad\\n\");\n\t\treturn false;\n\t}\n\n\tconst r_geometry_range_lock_t lock = R_GeometryRangeLock(&g_sprite.quad.geom);\n\n\tvec3_t point;\n\tvk_vertex_t *dst_vtx;\n\tuint16_t *dst_idx;\n\n\tdst_vtx = lock.vertices;\n\tdst_idx = lock.indices;\n\n\tconst vec3_t org = {0, 0, 0};\n\tconst vec3_t v_right = {1, 0, 0};\n\tconst vec3_t v_up = {0, 1, 0};\n\tvec3_t v_normal;\n\tCrossProduct(v_right, v_up, v_normal);\n\n\tVectorMA( org, -1.f, v_up, point );\n\tVectorMA( point, -1.f, v_right, dst_vtx[0].pos );\n\tdst_vtx[0].gl_tc[0] = 0.f;\n\tdst_vtx[0].gl_tc[1] = 1.f;\n\tdst_vtx[0].lm_tc[0] = dst_vtx[0].lm_tc[1] = 0.f;\n\tVector4Set(dst_vtx[0].color, 255, 255, 255, 255);\n\tVectorCopy(v_normal, dst_vtx[0].normal);\n\n\tVectorMA( org, 1.f, v_up, point );\n\tVectorMA( point, -1.f, v_right, dst_vtx[1].pos );\n\tdst_vtx[1].gl_tc[0] = 0.f;\n\tdst_vtx[1].gl_tc[1] = 0.f;\n\tdst_vtx[1].lm_tc[0] = dst_vtx[1].lm_tc[1] = 0.f;\n\tVector4Set(dst_vtx[1].color, 255, 255, 255, 255);\n\tVectorCopy(v_normal, dst_vtx[1].normal);\n\n\tVectorMA( org, 1.f, v_up, point );\n\tVectorMA( point, 1.f, v_right, dst_vtx[2].pos );\n\tdst_vtx[2].gl_tc[0] = 1.f;\n\tdst_vtx[2].gl_tc[1] = 0.f;\n\tdst_vtx[2].lm_tc[0] = dst_vtx[2].lm_tc[1] = 0.f;\n\tVector4Set(dst_vtx[2].color, 255, 255, 255, 255);\n\tVectorCopy(v_normal, dst_vtx[2].normal);\n\n\tVectorMA( org, -1.f, v_up, point );\n\tVectorMA( point, 1.f, v_right, dst_vtx[3].pos );\n\tdst_vtx[3].gl_tc[0] = 1.f;\n\tdst_vtx[3].gl_tc[1] = 1.f;\n\tdst_vtx[3].lm_tc[0] = dst_vtx[3].lm_tc[1] = 0.f;\n\tVector4Set(dst_vtx[3].color, 255, 255, 255, 255);\n\tVectorCopy(v_normal, dst_vtx[3].normal);\n\n\tdst_idx[0] = 0;\n\tdst_idx[1] = 1;\n\tdst_idx[2] = 2;\n\tdst_idx[3] = 0;\n\tdst_idx[4] = 2;\n\tdst_idx[5] = 3;\n\n\tR_GeometryRangeUnlock( &lock );\n\n\tg_sprite.quad.geometry = (vk_render_geometry_t){\n\t\t.max_vertex = 4,\n\t\t.vertex_offset = g_sprite.quad.geom.vertices.unit_offset,\n\n\t\t.element_count = 6,\n\t\t.index_offset = g_sprite.quad.geom.indices.unit_offset,\n\n\t\t.material = R_VkMaterialGetForTexture(tglob.defaultTexture),\n\t\t.ye_olde_texture = tglob.defaultTexture,\n\t\t.emissive = {1,1,1},\n\t};\n\n\treturn R_RenderModelCreate(&g_sprite.quad.model, (vk_render_model_init_t){\n\t\t.name = \"sprite\",\n\t\t.geometries = &g_sprite.quad.geometry,\n\t\t.geometries_count = 1,\n\t\t.dynamic = false,\n\t\t});\n}\n\nstatic void destroyQuadModel(void) {\n\tif (g_sprite.quad.model.num_geometries)\n\t\tR_RenderModelDestroy(&g_sprite.quad.model);\n\n\tif (g_sprite.quad.geom.block_handle.size)\n\t\tR_GeometryRangeFree(&g_sprite.quad.geom);\n\n\tg_sprite.quad.model.num_geometries = 0;\n\tg_sprite.quad.geom.block_handle.size = 0;\n}\n\nqboolean R_SpriteInit(void) {\n\tR_SPEEDS_COUNTER(g_sprite.stats.sprites, \"count\", kSpeedsMetricCount);\n\n\treturn true;\n\t// TODO return createQuadModel();\n}\n\nvoid R_SpriteShutdown(void) {\n\tdestroyQuadModel();\n}\n\nvoid R_SpriteNewMapFIXME(void) {\n\tdestroyQuadModel();\n\tASSERT(createQuadModel());\n}\n\nstatic mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )\n{\n\tmsprite_t\t\t*psprite;\n\tmspritegroup_t\t*pspritegroup;\n\tmspriteframe_t\t*pspriteframe = NULL;\n\tfloat\t\t*pintervals, fullinterval;\n\tint\t\ti, numframes;\n\tfloat\t\ttargettime;\n\n\tASSERT( pModel != NULL );\n\tpsprite = pModel->cache.data;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tif( frame > psprite->numframes )\n\t\t\tgEngine.Con_Printf( S_WARN \"R_GetSpriteFrame: no such frame %d (%s)\\n\", frame, pModel->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == SPR_SINGLE )\n\t{\n\t\tpspriteframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == SPR_GROUP )\n\t{\n\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[frame].frameptr);\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes-1];\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = gp_cl->time - ((int)( gp_cl->time / fullinterval )) * fullinterval;\n\n\t\tfor( i = 0; i < (numframes - 1); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t}\n\t\tpspriteframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == SPR_ANGLED )\n\t{\n\t\t//int\tangleframe = (int)(Q_rint(( g_camera.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7;\n\t\tconst int\tangleframe = (int)(Q_rint(( 0 - yaw + 45.0f ) / 360 * 8) - 4) & 7;\n\n\t\tgEngine.Con_Printf(S_WARN \"VK FIXME: %s doesn't know about viewangles\\n\", __FUNCTION__);\n\n\t\t// e.g. doom-style sprite monsters\n\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[frame].frameptr);\n\t\tpspriteframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn pspriteframe;\n}\n\nvoid R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )\n{\n\tmspriteframe_t\t*pFrame;\n\n\tif( !pSprite || pSprite->type != mod_sprite ) return; // bad model ?\n\tpFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );\n\n\tif( frameWidth ) *frameWidth = pFrame->width;\n\tif( frameHeight ) *frameHeight = pFrame->height;\n\tif( numFrames ) *numFrames = pSprite->numframes;\n}\n\ntypedef struct {\n\tchar sprite_name[MAX_QPATH];\n\tchar group_suffix[8];\n\tuint r_texFlags;\n\tint sprite_version;\n\tfloat sprite_radius;\n} SpriteLoadContext;\n\nstatic const dframetype_t *VK_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num, const SpriteLoadContext *ctx )\n{\n\tdspriteframe_t\tpinframe;\n\tmspriteframe_t\t*pspriteframe;\n\tint\t\tgl_texturenum = 0;\n\tchar\t\ttexname[128];\n\tint\t\tbytes = 1;\n\n\tmemcpy( &pinframe, pin, sizeof(dspriteframe_t));\n\n\tif( ctx->sprite_version == SPRITE_VERSION_32 )\n\t\tbytes = 4;\n\n\t// build uinque frame name\n\tif( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"#HUD/%s(%s:%i%i).spr\", ctx->sprite_name, ctx->group_suffix, num / 10, num % 10 );\n\t\tgl_texturenum = R_TextureUploadFromFile( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );\n\t}\n\telse\n\t{\n\t\tQ_snprintf( texname, sizeof( texname ), \"#%s(%s:%i%i).spr\", ctx->sprite_name, ctx->group_suffix, num / 10, num % 10 );\n\t\tgl_texturenum = R_TextureUploadFromFile( texname, pin, pinframe.width * pinframe.height * bytes, ctx->r_texFlags );\n\t}\n\n\t// setup frame description\n\tpspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));\n\tpspriteframe->width = pinframe.width;\n\tpspriteframe->height = pinframe.height;\n\tpspriteframe->up = pinframe.origin[1];\n\tpspriteframe->left = pinframe.origin[0];\n\tpspriteframe->down = pinframe.origin[1] - pinframe.height;\n\tpspriteframe->right = pinframe.width + pinframe.origin[0];\n\tpspriteframe->gl_texturenum = gl_texturenum;\n\t*ppframe = pspriteframe;\n\n\treturn PTR_CAST(const dframetype_t, ( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes );\n}\n\nstatic const dframetype_t *VK_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum, const SpriteLoadContext *ctx )\n{\n\tconst dspritegroup_t\t*pingroup;\n\tmspritegroup_t\t*pspritegroup;\n\tconst dspriteinterval_t\t*pin_intervals;\n\tfloat\t\t*poutintervals;\n\tint\t\ti, groupsize, numframes;\n\tconst void\t\t*ptemp;\n\n\tpingroup = (const dspritegroup_t *)pin;\n\tnumframes = pingroup->numframes;\n\n\tgroupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] );\n\tpspritegroup = Mem_Calloc( mod->mempool, groupsize );\n\tpspritegroup->numframes = numframes;\n\n\t*ppframe = (mspriteframe_t *)pspritegroup;\n\tpin_intervals = (const dspriteinterval_t *)(pingroup + 1);\n\tpoutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));\n\tpspritegroup->intervals = poutintervals;\n\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\t*poutintervals = pin_intervals->interval;\n\t\tif( *poutintervals <= 0.0f )\n\t\t\t*poutintervals = 1.0f; // set error value\n\t\tpoutintervals++;\n\t\tpin_intervals++;\n\t}\n\n\tptemp = (const void *)pin_intervals;\n\tfor( i = 0; i < numframes; i++ )\n\t{\n\t\tptemp = VK_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i, ctx );\n\t}\n\n\treturn (const dframetype_t *)ptemp;\n}\n\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags )\n{\n\tconst dsprite_t\t\t*pin;\n\tconst short\t\t*numi = NULL;\n\tconst dframetype_t\t*pframetype;\n\tmsprite_t\t\t*psprite;\n\tint\t\ti;\n\tSpriteLoadContext ctx = {0};\n\n\tpin = buffer;\n\tpsprite = mod->cache.data;\n\n\tif( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )\n\t\tnumi = NULL;\n\telse if( pin->version == SPRITE_VERSION_HL )\n\t\tnumi = (const short *)(void *)((const byte*)buffer + sizeof( dsprite_hl_t ));\n\n\tctx.r_texFlags = texFlags;\n\tctx.sprite_version = pin->version;\n\tQ_strncpy( ctx.sprite_name, mod->name, sizeof( ctx.sprite_name ));\n\tCOM_StripExtension( ctx.sprite_name );\n\n\tif( numi == NULL )\n\t{\n\t\trgbdata_t\t*pal;\n\n\t\tpal = gEngine.FS_LoadImage( \"#id.pal\", (byte *)&i, 768 );\n\t\tpframetype = (const dframetype_t *)(void *)((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1\n\t\tgEngine.FS_FreeImage( pal ); // palette installed, no reason to keep this data\n\t}\n\telse if( *numi == 256 )\n\t{\n\t\tconst byte\t*src = (const byte *)(numi+1);\n\t\trgbdata_t\t*pal;\n\n\t\t// install palette\n\t\tswitch( psprite->texFormat )\n\t\t{\n\t\tcase SPR_INDEXALPHA:\n\t\t\tpal = gEngine.FS_LoadImage( \"#gradient.pal\", src, 768 );\n\t\t\tbreak;\n\t\tcase SPR_ALPHTEST:\n\t\t\tpal = gEngine.FS_LoadImage( \"#masked.pal\", src, 768 );\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tpal = gEngine.FS_LoadImage( \"#normal.pal\", src, 768 );\n\t\t\tbreak;\n\t\t}\n\n\t\tpframetype = (const dframetype_t *)(void *)(src + 768);\n\t\tgEngine.FS_FreeImage( pal ); // palette installed, no reason to keep this data\n\t}\n\telse\n\t{\n\t\tgEngine.Con_DPrintf( S_ERROR \"%s has wrong number of palette colors %i (should be 256)\\n\", mod->name, *numi );\n\t\treturn;\n\t}\n\n\tif( mod->numframes < 1 )\n\t\treturn;\n\n\tfor( i = 0; i < mod->numframes; i++ )\n\t{\n\t\tframetype_t frametype = pframetype->type;\n\t\tpsprite->frames[i].type = (spriteframetype_t)frametype;\n\n\t\tswitch( frametype )\n\t\t{\n\t\tcase FRAME_SINGLE:\n\t\t\tQ_strncpy( ctx.group_suffix, \"frame\", sizeof( ctx.group_suffix ));\n\t\t\tpframetype = VK_SpriteLoadFrame( mod, pframetype + 1, &psprite->frames[i].frameptr, i, &ctx );\n\t\t\tbreak;\n\t\tcase FRAME_GROUP:\n\t\t\tQ_strncpy( ctx.group_suffix, \"group\", sizeof( ctx.group_suffix ));\n\t\t\tpframetype = VK_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i, &ctx );\n\t\t\tbreak;\n\t\tcase FRAME_ANGLED:\n\t\t\tQ_strncpy( ctx.group_suffix, \"angle\", sizeof( ctx.group_suffix ));\n\t\t\tpframetype = VK_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i, &ctx );\n\t\t\tbreak;\n\t\t}\n\t\tif( pframetype == NULL ) break; // technically an error\n\t}\n\n\tif( loaded ) *loaded = true;\t// done\n}\n\nint R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )\n{\n\tif( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )\n\t\treturn 0;\n\n\treturn R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;\n}\n\n/*\n================\nR_GetSpriteFrameInterpolant\n\nNOTE: we using prevblending[0] and [1] for holds interval\nbetween frames where are we lerping\n================\n*/\nstatic float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe )\n{\n\tmsprite_t\t\t*psprite;\n\tmspritegroup_t\t*pspritegroup;\n\tint\t\ti, j, numframes, frame;\n\tfloat\t\tlerpFrac, time, jtime, jinterval;\n\tfloat\t\t*pintervals, fullinterval, targettime;\n\tint\t\tm_fDoInterp;\n\n\tpsprite = ent->model->cache.data;\n\tframe = (int)ent->curstate.frame;\n\tlerpFrac = 1.0f;\n\n\t// misc info\n\tm_fDoInterp = (ent->curstate.effects & EF_NOINTERP) ? false : true;\n\n\tif( frame < 0 )\n\t{\n\t\tframe = 0;\n\t}\n\telse if( frame >= psprite->numframes )\n\t{\n\t\tgEngine.Con_Reportf( S_WARN \"R_GetSpriteFrameInterpolant: no such frame %d (%s)\\n\", frame, ent->model->name );\n\t\tframe = psprite->numframes - 1;\n\t}\n\n\tif( psprite->frames[frame].type == SPR_SINGLE )\n\t{\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != SPR_SINGLE )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse lerpFrac = (gp_cl->time - ent->latched.sequencetime) * 11.0f;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tif( ent->latched.prevblending[0] >= psprite->numframes )\n\t\t{\n\t\t\t// reset interpolation on change model\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\tlerpFrac = 0.0f;\n\t\t}\n\n\t\t// get the interpolated frames\n\t\tif( oldframe ) *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr;\n\t\tif( curframe ) *curframe = psprite->frames[frame].frameptr;\n\t}\n\telse if( psprite->frames[frame].type == SPR_GROUP )\n\t{\n\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[frame].frameptr);\n\t\tpintervals = pspritegroup->intervals;\n\t\tnumframes = pspritegroup->numframes;\n\t\tfullinterval = pintervals[numframes-1];\n\t\tjinterval = pintervals[1] - pintervals[0];\n\t\ttime = gp_cl->time;\n\t\tjtime = 0.0f;\n\n\t\t// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values\n\t\t// are positive, so we don't have to worry about division by zero\n\t\ttargettime = time - ((int)(time / fullinterval)) * fullinterval;\n\n\t\t// LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0,\n\t\t// i instead measure the time of the first frame, hoping it is consistent\n\t\tfor( i = 0, j = numframes - 1; i < (numframes - 1); i++ )\n\t\t{\n\t\t\tif( pintervals[i] > targettime )\n\t\t\t\tbreak;\n\t\t\tj = i;\n\t\t\tjinterval = pintervals[i] - jtime;\n\t\t\tjtime = pintervals[i];\n\t\t}\n\n\t\tif( m_fDoInterp )\n\t\t\tlerpFrac = (targettime - jtime) / jinterval;\n\t\telse j = i; // no lerping\n\n\t\t// get the interpolated frames\n\t\tif( oldframe ) *oldframe = pspritegroup->frames[j];\n\t\tif( curframe ) *curframe = pspritegroup->frames[i];\n\t}\n\telse if( psprite->frames[frame].type == SPR_ANGLED )\n\t{\n\t\t// e.g. doom-style sprite monsters\n\t\tfloat\tyaw = ent->angles[YAW];\n\t\tint\tangleframe = (int)(Q_rint(( g_camera.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7;\n\n\t\tif( m_fDoInterp )\n\t\t{\n\t\t\tif( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != SPR_ANGLED )\n\t\t\t{\n\t\t\t\t// this can be happens when rendering switched between single and angled frames\n\t\t\t\t// or change model on replace delta-entity\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 1.0f;\n\t\t\t}\n\n\t\t\tif( ent->latched.sequencetime < gp_cl->time )\n\t\t\t{\n\t\t\t\tif( frame != ent->latched.prevblending[1] )\n\t\t\t\t{\n\t\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1];\n\t\t\t\t\tent->latched.prevblending[1] = frame;\n\t\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\t\tlerpFrac = 0.0f;\n\t\t\t\t}\n\t\t\t\telse lerpFrac = (gp_cl->time - ent->latched.sequencetime) * ent->curstate.framerate;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\t\tent->latched.sequencetime = gp_cl->time;\n\t\t\t\tlerpFrac = 0.0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tent->latched.prevblending[0] = ent->latched.prevblending[1] = frame;\n\t\t\tlerpFrac = 1.0f;\n\t\t}\n\n\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[ent->latched.prevblending[0]].frameptr);\n\t\tif( oldframe ) *oldframe = pspritegroup->frames[angleframe];\n\n\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[frame].frameptr);\n\t\tif( curframe ) *curframe = pspritegroup->frames[angleframe];\n\t}\n\n\treturn lerpFrac;\n}\n\n/* FIXME VK\n// Cull sprite model by bbox\nqboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin )\n{\n\tvec3_t\tsprite_mins, sprite_maxs;\n\tfloat\tscale = 1.0f;\n\n\tif( !e->model->cache.data )\n\t\treturn true;\n\n\tif( e->curstate.scale > 0.0f )\n\t\tscale = e->curstate.scale;\n\n\t// scale original bbox (no rotation for sprites)\n\tVectorScale( e->model->mins, scale, sprite_mins );\n\tVectorScale( e->model->maxs, scale, sprite_maxs );\n\n\tsprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs );\n\n\tVectorAdd( sprite_mins, origin, sprite_mins );\n\tVectorAdd( sprite_maxs, origin, sprite_maxs );\n\n\treturn R_CullModel( e, sprite_mins, sprite_maxs );\n}\n*/\n\n// Set sprite brightness factor\nstatic float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale )\n{\n\tfloat\tdist, brightness;\n\tvec3_t\tglowDist;\n\tpmtrace_t\t*tr;\n\n\tVectorSubtract( origin, g_camera.vieworg, glowDist );\n\tdist = VectorLength( glowDist );\n\n\t// FIXME VK if( RP_NORMALPASS( ))\n\t{\n\t\ttr = gEngine.EV_VisTraceLine( g_camera.vieworg, origin,\n\t\t\t\t// FIXME VK r_traceglow->value ? PM_GLASS_IGNORE :\n\t\t\t\t(PM_GLASS_IGNORE|PM_STUDIO_IGNORE));\n\n\t\tif(( 1.0f - tr->fraction ) * dist > 8.0f )\n\t\t\treturn 0.0f;\n\t}\n\n\tif( renderfx == kRenderFxNoDissipation )\n\t\treturn 1.0f;\n\n\tbrightness = GLARE_FALLOFF / ( dist * dist );\n\tbrightness = bound( 0.05f, brightness, 1.0f );\n\t*pscale *= dist * ( 1.0f / 200.0f );\n\n\treturn brightness;\n}\n\n// Do occlusion test for glow-sprites\nstatic qboolean spriteIsOccluded( const cl_entity_t *e, vec3_t origin, float *pscale, float *blend )\n{\n\tif( e->curstate.rendermode == kRenderGlow )\n\t{\n\t\tvec3_t\tv;\n\n\t\tTriWorldToScreen( origin, v );\n\n\t\tif( v[0] < g_camera.viewport[0] || v[0] > g_camera.viewport[0] + g_camera.viewport[2] )\n\t\t\treturn true; // do scissor\n\t\tif( v[1] < g_camera.viewport[1] || v[1] > g_camera.viewport[1] + g_camera.viewport[3] )\n\t\t\treturn true; // do scissor\n\n\t\t*blend *= R_SpriteGlowBlend( origin, e->curstate.rendermode, e->curstate.renderfx, pscale );\n\n\t\tif( *blend <= 0.01f )\n\t\t\treturn true; // faded\n\t}\n\telse\n\t{\n\t\t// FIXME VK if( R_CullSpriteModel( e, origin )) return true;\n\t\treturn false;\n\t}\n\n\treturn false;\n}\n\nstatic vk_render_type_e spriteRenderModeToRenderType( int render_mode ) {\n\tswitch (render_mode) {\n\t\tcase kRenderNormal:       return kVkRenderTypeSolid;\n\t\tcase kRenderTransColor:   return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderTransTexture: return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderGlow:         return kVkRenderType_A_1;\n\t\tcase kRenderTransAlpha:   return kVkRenderType_A_1mA_R;\n\t\tcase kRenderTransAdd:     return kVkRenderType_A_1_R;\n\t\tdefault: ASSERT(!\"Unxpected render_mode\");\n\t}\n\n\treturn kVkRenderTypeSolid;\n}\n\nstatic void R_DrawSpriteQuad( const char *debug_name, const mspriteframe_t *frame, const vec3_t org, const vec3_t v_right, const vec3_t v_up, float scale, int texture, int render_mode, const vec4_t color ) {\n\tvec3_t v_normal;\n\tCrossProduct(v_right, v_up, v_normal);\n\n\t// TODO can frame->right/left and frame->up/down be asymmetric?\n\tvec3_t right, up;\n\tVectorScale(v_right, frame->right * scale, right);\n\tVectorScale(v_up, frame->up * scale, up);\n\n\tmatrix4x4 transform;\n\tMatrix4x4_CreateFromVectors(transform, right, up, v_normal, org);\n\n\tconst vk_render_type_e render_type = spriteRenderModeToRenderType(render_mode);\n\tconst r_vk_material_t material_override = R_VkMaterialGetForTexture(texture);\n\tconst material_mode_e material_mode = R_VkMaterialModeFromRenderType(render_type);\n\n\tR_RenderModelDraw(&g_sprite.quad.model, (r_model_draw_t){\n\t\t.render_type = render_type,\n\t\t.material_mode = material_mode,\n\t\t.material_flags = kMaterialFlag_None,\n\t\t.color = (const vec4_t*)color,\n\t\t.transform = &transform,\n\t\t.prev_transform = &transform,\n\t\t.override = {\n\t\t\t.material = &material_override,\n\t\t\t.old_texture = texture,\n\t\t},\n\t});\n}\n\n#if 0\nstatic qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat )\n{\n\t/* FIXME VK\n\tif( !r_sprite_lighting->value )\n\t\treturn false;\n\t*/\n\n\tif( texFormat != SPR_ALPHTEST )\n\t\treturn false;\n\n\tif( e->curstate.effects & EF_FULLBRIGHT )\n\t\treturn false;\n\n\tif( e->curstate.renderamt <= 127 )\n\t\treturn false;\n\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderNormal:\n\tcase kRenderTransAlpha:\n\tcase kRenderTransTexture:\n\t\tbreak;\n\tdefault:\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n#endif\n\nstatic qboolean R_SpriteAllowLerping( const cl_entity_t *e, msprite_t *psprite )\n{\n\t/* FIXME VK\n\tif( !r_sprite_lerping->value )\n\t\treturn false;\n\t*/\n\n\tif( psprite->numframes <= 1 )\n\t\treturn false;\n\n\tif( psprite->texFormat != SPR_ADDITIVE )\n\t\treturn false;\n\n\tif( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha )\n\t\treturn false;\n\n\treturn true;\n}\n\nvoid R_VkSpriteDrawModel( cl_entity_t *e, float blend )\n{\n\tmspriteframe_t\t*frame = NULL, *oldframe = NULL;\n\tmsprite_t\t\t*psprite;\n\tmodel_t\t\t*model;\n\tint\t\ti, type;\n\tfloat\t\tangle, dot, sr, cr;\n\tfloat\t\tlerp = 1.0f, ilerp, scale;\n\tvec3_t\t\tv_forward, v_right, v_up;\n\tvec3_t\t\torigin, color;\n\n\t/* FIXME VK\n\tif( RI.params & RP_ENVVIEW )\n\t\treturn;\n\t*/\n\n\tmodel = e->model;\n\tpsprite = (msprite_t * )model->cache.data;\n\tVectorCopy( e->origin, origin );\t// set render origin\n\n\t// do movewith\n\tif( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW )\n\t{\n\t\tcl_entity_t\t*parent;\n\n\t\tparent = globals.entities + e->curstate.aiment;\n\n\t\tif( parent && parent->model )\n\t\t{\n\t\t\tif( parent->model->type == mod_studio && e->curstate.body > 0 )\n\t\t\t{\n\t\t\t\tint num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS );\n\t\t\t\tVectorCopy( parent->attachment[num-1], origin );\n\t\t\t}\n\t\t\telse VectorCopy( parent->origin, origin );\n\t\t}\n\t}\n\n\tscale = e->curstate.scale;\n\tif( !scale ) scale = 1.0f;\n\n\tif( spriteIsOccluded( e, origin, &scale, &blend))\n\t\treturn; // sprite culled\n\n\tg_sprite.stats.sprites++;\n\n\t/* FIXME VK\n\tr_stats.c_sprite_models_drawn++;\n\n\tif( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd )\n\t\tR_AllowFog( false );\n\t*/\n\n\t/* FIXME VK compare with pipeline state\n\t// select properly rendermode\n\tswitch( e->curstate.rendermode )\n\t{\n\tcase kRenderTransAlpha:\n\t\tpglDepthMask( GL_FALSE ); // <-- FIXME this is different. GL render doesn't write depth, VK one does, as it expects it to be solid-like\n\t\t// fallthrough\n\tcase kRenderTransColor:\n\tcase kRenderTransTexture:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tbreak;\n\tcase kRenderGlow:\n\t\tpglDisable( GL_DEPTH_TEST );\n\t\t// fallthrough\n\tcase kRenderTransAdd:\n\t\tpglEnable( GL_BLEND );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\tpglDepthMask( GL_FALSE );\n\t\tbreak;\n\tcase kRenderNormal:\n\tdefault:\n\t\tpglDisable( GL_BLEND );\n\t\tbreak;\n\t}\n\n\t// all sprites can have color\n\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglEnable( GL_ALPHA_TEST );\n\t*/\n\n\t// NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t{\n\t\tcolor[0] = (float)e->curstate.rendercolor.r * ( 1.0f / 255.0f );\n\t\tcolor[1] = (float)e->curstate.rendercolor.g * ( 1.0f / 255.0f );\n\t\tcolor[2] = (float)e->curstate.rendercolor.b * ( 1.0f / 255.0f );\n\t}\n\telse\n\t{\n\t\tcolor[0] = 1.0f;\n\t\tcolor[1] = 1.0f;\n\t\tcolor[2] = 1.0f;\n\t}\n\n\t/* FIXME VK\n\tif( R_SpriteHasLightmap( e, psprite->texFormat ))\n\t{\n\t\tcolorVec lightColor = R_LightPoint( origin );\n\t\t// FIXME: collect light from dlights?\n\t\tcolor2[0] = (float)lightColor.r * ( 1.0f / 255.0f );\n\t\tcolor2[1] = (float)lightColor.g * ( 1.0f / 255.0f );\n\t\tcolor2[2] = (float)lightColor.b * ( 1.0f / 255.0f );\n\t\t// NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0\n\t\tpglAlphaFunc( GL_GREATER, 0.5f );\n\t}\n\t*/\n\n\tif( R_SpriteAllowLerping( e, psprite ))\n\t\tlerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame );\n\telse\n\t\tframe = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] );\n\n\ttype = psprite->type;\n\n\t// automatically roll parallel sprites if requested\n\tif( e->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL )\n\t\ttype = SPR_FWD_PARALLEL_ORIENTED;\n\n\tswitch( type )\n\t{\n\tcase SPR_ORIENTED:\n\t\tAngleVectors( e->angles, v_forward, v_right, v_up );\n\t\tVectorScale( v_forward, 0.01f, v_forward );\t// to avoid z-fighting\n\t\tVectorSubtract( origin, v_forward, origin );\n\t\tbreak;\n\tcase SPR_FACING_UPRIGHT:\n\t\tVectorSet( v_right, origin[1] - g_camera.vieworg[1], -(origin[0] - g_camera.vieworg[0]), 0.0f );\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_UPRIGHT:\n\t\tdot = g_camera.vforward[2];\n\t\tif(( dot > 0.999848f ) || ( dot < -0.999848f ))\t// cos(1 degree) = 0.999848\n\t\t\treturn; // invisible\n\t\tVectorSet( v_up, 0.0f, 0.0f, 1.0f );\n\t\tVectorSet( v_right, g_camera.vforward[1], -g_camera.vforward[0], 0.0f );\n\t\tVectorNormalize( v_right );\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL_ORIENTED:\n\t\tangle = e->angles[ROLL] * (M_PI2 / 360.0f);\n\t\tSinCos( angle, &sr, &cr );\n\t\tfor( i = 0; i < 3; i++ )\n\t\t{\n\t\t\tv_right[i] = (g_camera.vright[i] * cr + g_camera.vup[i] * sr);\n\t\t\tv_up[i] = g_camera.vright[i] * -sr + g_camera.vup[i] * cr;\n\t\t}\n\t\tbreak;\n\tcase SPR_FWD_PARALLEL: // normal sprite\n\tdefault:\n\t\tVectorCopy( g_camera.vright, v_right );\n\t\tVectorCopy( g_camera.vup, v_up );\n\t\tbreak;\n\t}\n\n\t/* FIXME VK\n\tif( psprite->facecull == SPR_CULL_NONE )\n\t\tGL_Cull( GL_NONE );\n\t*/\n\n\tif( oldframe == frame )\n\t{\n\t\t// draw the single non-lerped frame\n\t\tconst vec4_t color4 = {color[0], color[1], color[2], blend};\n\t\tR_DrawSpriteQuad( model->name, frame, origin, v_right, v_up, scale, frame->gl_texturenum, e->curstate.rendermode, color4 );\n\t}\n\telse\n\t{\n\t\t// draw two combined lerped frames\n\t\tlerp = bound( 0.0f, lerp, 1.0f );\n\t\tilerp = 1.0f - lerp;\n\n\t\tif( ilerp != 0.0f )\n\t\t{\n\t\t\tconst vec4_t color4 = {color[0], color[1], color[2], blend * ilerp};\n\t\t\tASSERT(oldframe);\n\t\t\tR_DrawSpriteQuad( model->name, oldframe, origin, v_right, v_up, scale, oldframe->gl_texturenum, e->curstate.rendermode, color4 );\n\t\t}\n\n\t\tif( lerp != 0.0f )\n\t\t{\n\t\t\tconst vec4_t color4 = {color[0], color[1], color[2], blend * lerp};\n\t\t\tASSERT(frame);\n\t\t\tR_DrawSpriteQuad( model->name, frame, origin, v_right, v_up, scale, frame->gl_texturenum, e->curstate.rendermode, color4 );\n\t\t}\n\t}\n\n\t/* FIXME VK\n\t// draw the sprite 'lightmap' :-)\n\tif( R_SpriteHasLightmap( e, psprite->texFormat ))\n\t{\n\t\tif( !r_lightmap->value )\n\t\t\tpglEnable( GL_BLEND );\n\t\telse pglDisable( GL_BLEND );\n\t\tpglDepthFunc( GL_EQUAL );\n\t\tpglDisable( GL_ALPHA_TEST );\n\t\tpglBlendFunc( GL_ZERO, GL_SRC_COLOR );\n\t\tpglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\n\t\tpglColor4f( color2[0], color2[1], color2[2], tr.blend );\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\t\tR_DrawSpriteQuad( frame, origin, v_right, v_up, scale, ubo_index, frame->gl_texturenum, e->curstate.rendermode  );\n\t\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\t\tpglDepthFunc( GL_LEQUAL );\n\t}\n\t*/\n}\n\nvoid Mod_SpriteUnloadTextures( void *data )\n{\n\tmsprite_t\t\t*psprite;\n\tmspritegroup_t\t*pspritegroup;\n\tmspriteframe_t\t*pspriteframe;\n\tint\t\ti, j;\n\n\tpsprite = data;\n\n\tif( psprite )\n\t{\n\t\t// release all textures\n\t\tfor( i = 0; i < psprite->numframes; i++ )\n\t\t{\n\t\t\tif( psprite->frames[i].type == SPR_SINGLE )\n\t\t\t{\n\t\t\t\tpspriteframe = psprite->frames[i].frameptr;\n\t\t\t\tR_TextureFree( pspriteframe->gl_texturenum );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpspritegroup = PTR_CAST(mspritegroup_t, psprite->frames[i].frameptr);\n\n\t\t\t\tfor( j = 0; j < pspritegroup->numframes; j++ )\n\t\t\t\t{\n\t\t\t\t\tpspriteframe = pspritegroup->frames[i];\n\t\t\t\t\tR_TextureFree( pspriteframe->gl_texturenum );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vk_sprite.h",
    "content": "#pragma once\n\n#include \"vk_common.h\"\n\nvoid R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite );\nint R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame );\nvoid Mod_LoadMapSprite( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded );\nvoid Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags );\n\nvoid R_VkSpriteDrawModel( cl_entity_t *e, float blend );\n\nqboolean R_SpriteInit(void);\nvoid R_SpriteShutdown(void);\n\n// FIXME needed to recreate the sprite quad model, otherwise its memory will be freed, reused and corrupted\nvoid R_SpriteNewMapFIXME(void);\n\nvoid Mod_SpriteUnloadTextures( void *data );\n"
  },
  {
    "path": "ref/vk/vk_studio.c",
    "content": "#include \"vk_studio.h\"\n#include \"com_model.h\"\n#include \"vk_common.h\"\n#include \"r_textures.h\"\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n#include \"vk_renderstate.h\"\n#include \"vk_math.h\"\n#include \"vk_cvar.h\"\n#include \"camera.h\"\n#include \"r_speeds.h\"\n#include \"vk_studio_model.h\"\n#include \"vk_entity_data.h\"\n#include \"vk_logs.h\"\n\n#include \"xash3d_mathlib.h\"\n#include \"const.h\"\n#include \"r_studioint.h\"\n#include \"triangleapi.h\"\n#include \"studio.h\"\n#include \"pm_local.h\"\n#include \"pmtrace.h\"\n#include \"protocol.h\"\n#include \"enginefeatures.h\"\n#include \"pm_movevars.h\"\n\n#include <memory.h>\n#include <stdlib.h>\n\n#define MODULE_NAME \"studio\"\n#define LOG_MODULE studio\n\n#define EVENT_CLIENT\t5000\t// less than this value it's a server-side studio events\n#define MAX_LOCALLIGHTS\t4\n\n// TODO get rid of this\n#define ENGINE_GET_PARM_ (*gEngine.EngineGetParm)\n#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 )\n#define CL_IsViewEntityLocalPlayer() ( gp_cl->viewentity == (gp_cl->playernum + 1) )\n\n// FIXME VK should not be declared here\ncolorVec\t\tR_LightVec( const float *start, const float *end, float *lightspot, float *lightvec );\n\ntypedef struct\n{\n\tchar\t\tname[MAX_OSPATH];\n\tchar\t\tmodelname[MAX_OSPATH];\n\tmodel_t\t\t*model;\n} player_model_t;\n\ncvar_t r_shadows = { (char*)\"r_shadows\", (char*)\"0\", 0 };\n\ntypedef struct sortedmesh_s\n{\n\tconst mstudiomesh_t\t*mesh;\n\tint\t\tflags;\t\t\t// face flags\n} sortedmesh_t;\n\ntypedef struct\n{\n\tdouble\t\ttime;\n\tdouble\t\tframetime;\n\tint\t\tframecount;\t\t// studio framecount\n\tqboolean\t\tinterpolate;\n\tint\t\trendermode, rendermode2;\n\tfloat\t\tblend;\t\t\t// blend value\n\n\t// bones\n\tmatrix3x4\t\trotationmatrix;\n\tmatrix3x4\t\tbonestransform[MAXSTUDIOBONES];\n\tmatrix3x4\t\tlighttransform[MAXSTUDIOBONES];\n\n\t// boneweighting stuff\n\tmatrix3x4\t\tworldtransform[MAXSTUDIOBONES];\n\n\t// cached bones\n\tmatrix3x4\t\tcached_bonestransform[MAXSTUDIOBONES];\n\tmatrix3x4\t\tcached_lighttransform[MAXSTUDIOBONES];\n\tchar\t\tcached_bonenames[MAXSTUDIOBONES][32];\n\tint\t\tcached_numbones;\t\t// number of bones in cache\n\n\tsortedmesh_t\tmeshes[MAXSTUDIOMESHES];\t// sorted meshes\n\tvec3_t\t\tverts[MAXSTUDIOVERTS];\n\tvec3_t\t\tnorms[MAXSTUDIOVERTS];\n\tvec3_t tangents[MAXSTUDIOVERTS];\n\n\t// lighting state\n\tfloat\t\tambientlight;\n\tfloat\t\tshadelight;\n\tvec3_t\t\tlightvec;\t\t\t// averaging light direction\n\tvec3_t\t\tlightspot;\t\t// shadow spot\n\tvec3_t\t\tlightcolor;\t\t// averaging lightcolor\n\tvec3_t\t\tblightvec[MAXSTUDIOBONES];\t// bone light vecs\n\tvec3_t\t\tlightvalues[MAXSTUDIOVERTS];\t// precomputed lightvalues per each shared vertex of submodel\n\n\t// chrome stuff\n\tvec3_t\t\tchrome_origin;\n\tvec2_t\t\tchrome[MAXSTUDIOVERTS];\t// texture coords for surface normals\n\tvec3_t\t\tchromeright[MAXSTUDIOBONES];\t// chrome vector \"right\" in bone reference frames\n\tvec3_t\t\tchromeup[MAXSTUDIOBONES];\t// chrome vector \"up\" in bone reference frames\n\tint\t\tchromeage[MAXSTUDIOBONES];\t// last time chrome vectors were updated\n\n\t// glowshell stuff\n\tint\t\tnormaltable[MAXSTUDIOVERTS];\t// glowshell uses this\n\n\t// elights cache\n\tint\t\tnumlocallights;\n\tint\t\tlightage[MAXSTUDIOBONES];\n\tdlight_t\t\t*locallight[MAX_LOCALLIGHTS];\n\tcolor24\t\tlocallightcolor[MAX_LOCALLIGHTS];\n\tvec4_t\t\tlightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS];\n\tvec3_t\t\tlightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS];\n\tfloat\t\tlocallightR2[MAX_LOCALLIGHTS];\n\n\t// playermodels\n\tplayer_model_t  player_models[MAX_CLIENTS];\n} studio_draw_state_t;\n\n// studio-related cvars\ncvar_t\t\t\t*cl_righthand = NULL;\n\nstatic r_studio_interface_t\t*pStudioDraw;\nstatic studio_draw_state_t\tg_studio;\t\t// global studio state\n\nstatic struct {\n\tint models_count;\n\tint submodels_total;\n\tint submodels_static;\n\tint submodels_dynamic;\n} g_studio_stats;\n\n// global variables\nstatic qboolean\t\tm_fDoRemap;\nmstudiomodel_t\t\t*m_pSubModel;\nmstudiobodyparts_t\t\t*m_pBodyPart;\nplayer_info_t\t\t*m_pPlayerInfo;\nstudiohdr_t\t\t*m_pStudioHeader;\nfloat\t\t\tm_flGaitMovement;\nint\t\t\tg_iBackFaceCull;\nint\t\t\tg_nTopColor, g_nBottomColor;\t// remap colors\nint\t\t\tg_nForceFaceFlags;\n\n// FIXME VK this should be promoted to somewhere global-ish, and done properly\n// For now it's just a hack to get studio models to compile basically\nstatic struct {\n\tqboolean drawWorld;\t// ignore world for drawing PlayerModel\n\tcl_entity_t *currententity;\n\tmodel_t *currentmodel;\n} RI;\n\nstatic struct {\n\tr_studio_entity_model_t *entmodel;\n\tint bodypart_index;\n} g_studio_current;\n\n/*\n================\nR_StudioSetupTimings\n\ninit current time for a given model\n================\n*/\nstatic void R_StudioSetupTimings( void )\n{\n\tif( RI.drawWorld )\n\t{\n\t\t// synchronize with server time\n\t\tg_studio.time = gp_cl->time;\n\t\tg_studio.frametime = gp_cl->time -   gp_cl->oldtime;\n\t}\n\telse\n\t{\n\t\t// menu stuff\n\t\tg_studio.time = gp_host->realtime;\n\t\tg_studio.frametime = gp_host->frametime;\n\t}\n}\n\n/*\n================\nR_AllowFlipViewModel\n\nshould a flip the viewmodel if cl_righthand is set to 1\n================\n*/\n/*\nstatic qboolean R_AllowFlipViewModel( cl_entity_t *e )\n{\n\tif( cl_righthand && cl_righthand->value > 0 )\n\t{\n\t\tif( e == globals.viewent )\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n*/\n\n/*\n================\nR_StudioComputeBBox\n\nCompute a full bounding box for current sequence\n================\n*/\nstatic qboolean R_StudioComputeBBox( vec3_t bbox[8] )\n{\n\tvec3_t\t\tstudio_mins, studio_maxs;\n\tvec3_t\t\tmins, maxs, p1, p2;\n\tcl_entity_t\t*e = RI.currententity;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tint\t\ti;\n\n\tif( !m_pStudioHeader )\n\t\treturn false;\n\n\t// check if we have valid mins\\maxs\n\tif( !VectorCompare( vec3_origin, RI.currentmodel->mins ))\n\t{\n\t\t// clipping bounding box\n\t\tVectorCopy( RI.currentmodel->mins, mins );\n\t\tVectorCopy( RI.currentmodel->maxs, maxs );\n\t}\n\telse\n\t{\n\t\tClearBounds( mins, maxs );\n\t}\n\n\t// check sequence range\n\tif( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\t// add sequence box to the model box\n\tAddPointToBounds( pseqdesc->bbmin, mins, maxs );\n\tAddPointToBounds( pseqdesc->bbmax, mins, maxs );\n\tClearBounds( studio_mins, studio_maxs );\n\n\t// compute a full bounding box\n\tfor( i = 0; i < 8; i++ )\n\t{\n  \t\tp1[0] = ( i & 1 ) ? mins[0] : maxs[0];\n  \t\tp1[1] = ( i & 2 ) ? mins[1] : maxs[1];\n  \t\tp1[2] = ( i & 4 ) ? mins[2] : maxs[2];\n\n\t\tMatrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 );\n\t\tAddPointToBounds( p2, studio_mins, studio_maxs );\n\t\tif( bbox ) VectorCopy( p2, bbox[i] );\n\t}\n\n\t/* VK FIXME NOT IMPLEMENTED */\n\t/* if( !bbox && R_CullModel( e, studio_mins, studio_maxs )) */\n\t/* \treturn false; // model culled */\n\n\treturn true; // visible\n}\n\nstatic void R_StudioComputeSkinMatrix( const mstudioboneweight_t *boneweights, matrix3x4 *worldtransform, matrix3x4 result )\n{\n\tfloat\tflWeight0, flWeight1, flWeight2, flWeight3;\n\tint\ti, numbones = 0;\n\tfloat\tflTotal;\n\n\tfor( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )\n\t{\n\t\tif( boneweights->bone[i] != -1 )\n\t\t\tnumbones++;\n\t}\n\n\tif( numbones == 4 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]];\n\t\tvec4_t *boneMat3 = (vec4_t *)worldtransform[boneweights->bone[3]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflWeight3 = boneweights->weight[3] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3;\n\t}\n\telse if( numbones == 3 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];\n\t\tvec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflWeight2 = boneweights->weight[2] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1 + flWeight2;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2;\n\t}\n\telse if( numbones == 2 )\n\t{\n\t\tvec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];\n\t\tvec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];\n\t\tflWeight0 = boneweights->weight[0] / 255.0f;\n\t\tflWeight1 = boneweights->weight[1] / 255.0f;\n\t\tflTotal = flWeight0 + flWeight1;\n\n\t\tif( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal;\t// compensate rounding error\n\n\t\tresult[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1;\n\t\tresult[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1;\n\t\tresult[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1;\n\t\tresult[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1;\n\t\tresult[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1;\n\t\tresult[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1;\n\t\tresult[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1;\n\t\tresult[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1;\n\t\tresult[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1;\n\t\tresult[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1;\n\t\tresult[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1;\n\t\tresult[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1;\n\t}\n\telse\n\t{\n\t\tMatrix3x4_Copy( result, worldtransform[boneweights->bone[0]] );\n\t}\n}\n\n\nstatic model_t *R_GetChromeSprite( void )\n{\n\treturn gEngine.GetDefaultSprite( REF_CHROME_SPRITE );\n}\n\nstatic void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )\n{\n\t// calc up/down pointing\n\t*pBlend = (*pPitch * 3.0f);\n\n\tif( *pBlend < pseqdesc->blendstart[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendstart[0] / 3.0f;\n\t\t*pBlend = 0;\n\t}\n\telse if( *pBlend > pseqdesc->blendend[0] )\n\t{\n\t\t*pPitch -= pseqdesc->blendend[0] / 3.0f;\n\t\t*pBlend = 255;\n\t}\n\telse\n\t{\n\t\tif( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error\n\t\t\t*pBlend = 127;\n\t\telse *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]);\n\t\t*pPitch = 0.0f;\n\t}\n}\n\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles )\n{\n\tfloat\tf = 1.0f;\n\n\t// don't do it if the goalstarttime hasn't updated in a while.\n\t// NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit\n\t// was increased to 1.0 s., which is 2x the max lag we are accounting for.\n\tif( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime ))\n\t\tf = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime );\n\n\t// Con_Printf( \"%4.2f %.2f %.2f\\n\", f, e->curstate.animtime, g_studio.time );\n\tVectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin );\n\n\tif( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON ))\n\t{\n\t\tvec4_t\tq, q1, q2;\n\n\t\tAngleQuaternion( e->curstate.angles, q1, false );\n\t\tAngleQuaternion( e->latched.prevangles, q2, false );\n\t\tQuaternionSlerp( q2, q1, f, q );\n\t\tQuaternionAngle( q, angles );\n\t}\n\telse VectorCopy( e->curstate.angles, angles );\n}\n\nstatic void R_StudioSetUpTransform( cl_entity_t *e )\n{\n\tvec3_t\torigin, angles;\n\n\tVectorCopy( e->origin, origin );\n\tVectorCopy( e->angles, angles );\n\n\t// interpolate monsters position (moved into UpdateEntityFields by user request)\n\tif( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gEngine.EngineGetParm( PARM_FEATURES, 0 ), ENGINE_COMPUTE_STUDIO_LERP ))\n\t{\n\t\tR_StudioLerpMovement( e, g_studio.time, origin, angles );\n\t}\n\n\tif( !FBitSet( gEngine.EngineGetParm( PARM_FEATURES, 0 ), ENGINE_COMPENSATE_QUAKE_BUG ))\n\t\tangles[PITCH] = -angles[PITCH]; // stupid quake bug\n\n\t// don't rotate clients, only aim\n\tif( e->player ) angles[PITCH] = 0.0f;\n\n\tMatrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f );\n\n\t/* FIXME VK NOT IMPLEMENTED */\n\t/* if( tr.fFlipViewModel ) */\n\t/* { */\n\t/* \tg_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1]; */\n\t/* \tg_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1]; */\n\t/* \tg_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1]; */\n\t/* } */\n}\n\nfloat R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time )\n{\n\tdouble\tdfdt, f;\n\n\tif( g_studio.interpolate )\n\t{\n\t\tif( time < e->curstate.animtime ) dfdt = 0.0;\n\t\telse dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps;\n\t}\n\telse dfdt = 0;\n\n\tif( pseqdesc->numframes <= 1 ) f = 0.0;\n\telse f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0f;\n\n\tf += dfdt;\n\n\tif( pseqdesc->flags & STUDIO_LOOPING )\n\t{\n\t\tif( pseqdesc->numframes > 1 )\n\t\t\tf -= (int)(f / (pseqdesc->numframes - 1)) *  (pseqdesc->numframes - 1);\n\t\tif( f < 0 ) f += (pseqdesc->numframes - 1);\n\t}\n\telse\n\t{\n\t\tif( f >= pseqdesc->numframes - 1.001 )\n\t\t\tf = pseqdesc->numframes - 1.001;\n\t\tif( f < 0.0 )  f = 0.0;\n\t}\n\treturn f;\n}\n\nstatic float R_StudioEstimateInterpolant( cl_entity_t *e )\n{\n\tfloat\tdadt = 1.0f;\n\n\tif( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f ))\n\t{\n\t\tdadt = ( g_studio.time - e->curstate.animtime ) / 0.1f;\n\t\tif( dadt > 2.0f ) dadt = 2.0f;\n\t}\n\n\treturn dadt;\n}\n\nstatic void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform )\n{\n\tswitch( ent->curstate.renderfx )\n\t{\n\tcase kRenderFxDistort:\n\tcase kRenderFxHologram:\n\t\tif( !gEngine.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tint\taxis = gEngine.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 ) axis = 2; // choose between x & z\n\t\t\tVectorScale( transform[axis], gEngine.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] );\n\t\t}\n\t\telse if( !gEngine.COM_RandomLong( 0, 49 ))\n\t\t{\n\t\t\tfloat\toffset;\n\t\t\tint\taxis = gEngine.COM_RandomLong( 0, 1 );\n\n\t\t\tif( axis == 1 ) axis = 2; // choose between x & z\n\t\t\toffset = gEngine.COM_RandomFloat( -10.0f, 10.0f );\n\t\t\ttransform[gEngine.COM_RandomLong( 0, 2 )][3] += offset;\n\t\t}\n\t\tbreak;\n\tcase kRenderFxExplode:\n\t\t{\n\t\t\tfloat\tscale;\n\n\t\t\tscale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f;\n\t\t\tif( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200%\n\n\t\t\ttransform[0][1] *= scale;\n\t\t\ttransform[1][1] *= scale;\n\t\t\ttransform[2][1] *= scale;\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen )\n{\n\tmstudiobonecontroller_t\t*pbonecontroller;\n\tfloat\t\t\tvalue = 0.0f;\n\tint\t\t\ti, j;\n\n\tpbonecontroller = PTR_CAST(mstudiobonecontroller_t, (byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex);\n\n\tfor( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )\n\t{\n\t\ti = pbonecontroller[j].index;\n\n\t\tif( i == STUDIO_MOUTH )\n\t\t{\n\t\t\t// mouth hardcoded at controller 4\n\t\t\tvalue = (float)mouthopen / 64.0f;\n\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\tvalue = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t}\n\t\telse if( i < 4 )\n\t\t{\n\t\t\t// check for 360% wrapping\n\t\t\tif( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP ))\n\t\t\t{\n\t\t\t\tif( abs( pcontroller1[i] - pcontroller2[i] ) > 128 )\n\t\t\t\t{\n\t\t\t\t\tint a = (pcontroller1[i] + 128) % 256;\n\t\t\t\t\tint b = (pcontroller2[i] + 128) % 256;\n\t\t\t\t\tvalue = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tvalue = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvalue = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f;\n\t\t\t\tvalue = bound( 0.0f, value, 1.0f );\n\t\t\t\tvalue = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;\n\t\t\t}\n\t\t}\n\n\t\tswitch( pbonecontroller[j].type & STUDIO_TYPES )\n\t\t{\n\t\tcase STUDIO_XR:\n\t\tcase STUDIO_YR:\n\t\tcase STUDIO_ZR:\n\t\t\tadj[j] = DEG2RAD( value );\n\t\t\tbreak;\n\t\tcase STUDIO_X:\n\t\tcase STUDIO_Y:\n\t\tcase STUDIO_Z:\n\t\t\tadj[j] = value;\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f )\n{\n\tint\t\ti, frame;\n\tfloat\t\tadj[MAXSTUDIOCONTROLLERS];\n\tfloat\t\ts, dadt;\n\tmstudiobone_t\t*pbone;\n\n\t// bah, fix this bug with changing sequences too fast\n\tif( f > pseqdesc->numframes - 1 )\n\t{\n\t\tf = 0.0f;\n\t}\n\telse if( f < -0.01f )\n\t{\n\t\t// BUG ( somewhere else ) but this code should validate this data.\n\t\t// This could cause a crash if the frame # is negative, so we'll go ahead\n\t\t// and clamp it here\n\t\tf = -0.01f;\n\t}\n\n\tframe = (int)f;\n\n\tdadt = R_StudioEstimateInterpolant( e );\n\ts = (f - frame);\n\n\t// add in programtic controllers\n\tpbone = PTR_CAST(mstudiobone_t, (byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\tR_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ )\n\t\tR_StudioCalcBones( frame, s, pbone, panim, adj, pos[i], q[i] );\n\n\tif( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f;\n\tif( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f;\n}\n\nstatic void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel )\n{\n\tint\t\ti, j;\n\tmstudiobone_t\t*pbones;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioanim_t\t*panim;\n\tmatrix3x4\t\tbonematrix;\n\tstatic vec4_t\tq[MAXSTUDIOBONES];\n\tstatic float\tpos[MAXSTUDIOBONES][3];\n\tfloat\t\tf;\n\n\tif( e->curstate.sequence >=  m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngine.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\tpbones = PTR_CAST(mstudiobone_t, (byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tfor( j = 0; j < g_studio.cached_numbones; j++ )\n\t\t{\n\t\t\tif( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] ))\n\t\t\t{\n\t\t\t\tMatrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif( j >= g_studio.cached_numbones )\n\t\t{\n\t\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\t\t\tif( pbones[i].parent == -1 )\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t\t// apply client-side effects to the transformation matrix\n\t\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void R_StudioSetupBones( cl_entity_t *e )\n{\n\tfloat\t\tf;\n\tmstudiobone_t\t*pbones;\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioanim_t\t*panim;\n\tmatrix3x4\t\tbonematrix;\n\tstatic vec3_t\tpos[MAXSTUDIOBONES];\n\tstatic vec4_t\tq[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos2[MAXSTUDIOBONES];\n\tstatic vec4_t\tq2[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos3[MAXSTUDIOBONES];\n\tstatic vec4_t\tq3[MAXSTUDIOBONES];\n\tstatic vec3_t\tpos4[MAXSTUDIOBONES];\n\tstatic vec4_t\tq4[MAXSTUDIOBONES];\n\tint\t\ti;\n\n\tif( e->curstate.sequence >= m_pStudioHeader->numseq )\n\t\te->curstate.sequence = 0;\n\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;\n\n\tf = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\n\tpanim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\tR_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );\n\n\tif( pseqdesc->numblends > 1 )\n\t{\n\t\tfloat\ts;\n\t\tfloat\tdadt;\n\n\t\tpanim += m_pStudioHeader->numbones;\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f );\n\n\t\tdadt = R_StudioEstimateInterpolant( e );\n\t\ts = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;\n\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s );\n\n\t\tif( pseqdesc->numblends == 4 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f );\n\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f );\n\n\t\t\ts = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\ts = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s );\n\t\t}\n\t}\n\n\tif( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq ))\n\t{\n\t\t// blend from last sequence\n\t\tstatic vec3_t\tpos1b[MAXSTUDIOBONES];\n\t\tstatic vec4_t\tq1b[MAXSTUDIOBONES];\n\t\tfloat\t\ts;\n\n\t\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence;\n\t\tpanim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\n\t\t// clip prevframe\n\t\tR_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe );\n\n\t\tif( pseqdesc->numblends > 1 )\n\t\t{\n\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\ts = (e->latched.prevseqblending[0]) / 255.0f;\n\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s );\n\n\t\t\tif( pseqdesc->numblends == 4 )\n\t\t\t{\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\tpanim += m_pStudioHeader->numbones;\n\t\t\t\tR_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe );\n\n\t\t\t\ts = (e->latched.prevseqblending[0]) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );\n\n\t\t\t\ts = (e->latched.prevseqblending[1]) / 255.0f;\n\t\t\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s );\n\t\t\t}\n\t\t}\n\n\t\ts = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f;\n\t\tR_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s );\n\t}\n\telse\n\t{\n\t\t// store prevframe otherwise\n\t\te->latched.prevframe = f;\n\t}\n\n\tpbones = PTR_CAST(mstudiobone_t, (byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\n\t// calc gait animation\n\tif( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 )\n\t{\n\t\tqboolean\tcopy_bones = true;\n\n\t\tif( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq )\n\t\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence;\n\n\t\tpanim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );\n\t\tR_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe );\n\n\t\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\t{\n\t\t\tif( !Q_strcmp( pbones[i].name, \"Bip01 Spine\" ))\n\t\t\t\tcopy_bones = false;\n\t\t\telse if( !Q_strcmp( pbones[pbones[i].parent].name, \"Bip01 Pelvis\" ))\n\t\t\t\tcopy_bones = true;\n\n\t\t\tif( !copy_bones ) continue;\n\n\t\t\tVectorCopy( pos2[i], pos[i] );\n\t\t\tVector4Copy( q2[i], q[i] );\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );\n\n\t\tif( pbones[i].parent == -1 )\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );\n\t\t\tMatrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );\n\n\t\t\t// apply client-side effects to the transformation matrix\n\t\t\tR_StudioFxTransform( e, g_studio.bonestransform[i] );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );\n\t\t}\n\t}\n}\n\nstatic void R_StudioSaveBones( void )\n{\n\tmstudiobone_t\t*pbones;\n\tint\t\ti;\n\n\tpbones = PTR_CAST(mstudiobone_t, (byte *)m_pStudioHeader + m_pStudioHeader->boneindex);\n\tg_studio.cached_numbones = m_pStudioHeader->numbones;\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] );\n\t\tMatrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] );\n\t\tQ_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 );\n\t}\n}\n\n/*\n====================\nStudioBuildNormalTable\n\nNOTE: m_pSubModel must be set\n====================\n*/\nstatic void R_StudioBuildNormalTable( void )\n{\n\tcl_entity_t\t*e = RI.currententity;\n\tmstudiomesh_t\t*pmesh;\n\tint\t\ti, j;\n\n\tASSERT( m_pSubModel != NULL );\n\n\t// reset chrome cache\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t\tg_studio.chromeage[i] = 0;\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ )\n\t\tg_studio.normaltable[i] = -1;\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort\t*ptricmds;\n\n\t\tpmesh = PTR_CAST(mstudiomesh_t, (byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;\n\t\tptricmds = PTR_CAST(short, (byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 ) i = -i;\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tif( g_studio.normaltable[ptricmds[0]] < 0 )\n\t\t\t\t\tg_studio.normaltable[ptricmds[0]] = ptricmds[1];\n\t\t\t}\n\t\t}\n\t}\n\n\tg_studio.chrome_origin[0] = cos( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[1] = sin( r_glowshellfreq->value * g_studio.time ) * 4000.0f;\n\tg_studio.chrome_origin[2] = cos( r_glowshellfreq->value * g_studio.time * 0.33f ) * 4000.0f;\n\n\t// FIXME VK: pass this to model color\n\tif( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )\n\t\tTriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 );\n\telse TriColor4ub( 255, 255, 255, 255 );\n}\n\n/*\n====================\nStudioGenerateNormals\n\nNOTE: m_pSubModel must be set\ng_studio.verts must be computed\n====================\n*/\nstatic void R_StudioGenerateNormals( void )\n{\n\tint\t\tv0, v1, v2;\n\tvec3_t\t\te0, e1, norm, tangent;\n\tmstudiomesh_t\t*pmesh;\n\tint\t\ti, j;\n\n\tASSERT( m_pSubModel != NULL );\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ ) {\n\t\tVectorClear( g_studio.norms[i] );\n\t\tVectorClear( g_studio.tangents[i] );\n\t}\n\n\tfor( j = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tshort\t*ptricmds;\n\n\t\tpmesh = PTR_CAST(mstudiomesh_t, (byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;\n\t\tptricmds = PTR_CAST(short, (byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\ti = -i;\n\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\t// TODO should we get uv (for tangents) for STUDIO_NF_CHROME differently?\n\t\t\t\t\tconst vec2_t uv0 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tvec2_t uv1 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tconst vec2_t uv2 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tcomputeTangent(tangent, g_studio.verts[v0], g_studio.verts[v1], g_studio.verts[v2], uv0, uv1, uv2);\n\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v0], tangent, g_studio.tangents[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v1], tangent, g_studio.tangents[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v2], tangent, g_studio.tangents[v2] );\n\n\t\t\t\t\t\tv1 = v2;\n\t\t\t\t\t\tVector2Copy(uv2, uv1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif( i > 2 )\n\t\t\t\t{\n\t\t\t\t\tqboolean\todd = false;\n\n\t\t\t\t\t// TODO should we get uv (for tangents) for STUDIO_NF_CHROME differently?\n\t\t\t\t\tvec2_t uv0 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\tv0 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tvec2_t uv1 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\tv1 = ptricmds[0]; ptricmds += 4;\n\n\t\t\t\t\tfor( i -= 2; i > 0; i--, ptricmds += 4 )\n\t\t\t\t\t{\n\t\t\t\t\t\tconst vec2_t uv2 = {ptricmds[2], ptricmds[3]};\n\t\t\t\t\t\tv2 = ptricmds[0];\n\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );\n\t\t\t\t\t\tVectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );\n\t\t\t\t\t\tCrossProduct( e1, e0, norm );\n\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );\n\n\t\t\t\t\t\tcomputeTangent(tangent, g_studio.verts[v0], g_studio.verts[v1], g_studio.verts[v2], uv0, uv1, uv2);\n\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v0], tangent, g_studio.tangents[v0] );\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v1], tangent, g_studio.tangents[v1] );\n\t\t\t\t\t\tVectorAdd( g_studio.tangents[v2], tangent, g_studio.tangents[v2] );\n\n\t\t\t\t\t\tif( odd ) {\n\t\t\t\t\t\t\tv1 = v2;\n\t\t\t\t\t\t\tVector2Copy(uv2, uv1);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tv0 = v2;\n\t\t\t\t\t\t\tVector2Copy(uv2, uv0);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\todd = !odd;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tptricmds += i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor( i = 0; i < m_pSubModel->numverts; i++ ) {\n\t\tVectorNormalize( g_studio.norms[i] );\n\t\tVectorNormalize( g_studio.tangents[i] );\n\t}\n}\n\nstatic void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal )\n{\n\tfloat\tn;\n\n\tif( g_studio.chromeage[bone] != g_studio.framecount )\n\t{\n\t\t// calculate vectors from the viewer to the bone. This roughly adjusts for position\n\t\tvec3_t\tchromeupvec;\t// g_studio.chrome t vector in world reference frame\n\t\tvec3_t\tchromerightvec;\t// g_studio.chrome s vector in world reference frame\n\t\tvec3_t\ttmp;\t\t// vector pointing at bone in world reference frame\n\n\t\tVectorNegate( g_studio.chrome_origin, tmp );\n\t\ttmp[0] += g_studio.bonestransform[bone][0][3];\n\t\ttmp[1] += g_studio.bonestransform[bone][1][3];\n\t\ttmp[2] += g_studio.bonestransform[bone][2][3];\n\n\t\tVectorNormalize( tmp );\n\t\tCrossProduct( tmp, g_camera.vright, chromeupvec );\n\t\tVectorNormalize( chromeupvec );\n\t\tCrossProduct( tmp, chromeupvec, chromerightvec );\n\t\tVectorNormalize( chromerightvec );\n\n\t\tMatrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] );\n\t\tMatrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] );\n\n\t\tg_studio.chromeage[bone] = g_studio.framecount;\n\t}\n\n\t// calc s coord\n\tn = DotProduct( normal, g_studio.chromeright[bone] );\n\tpchrome[0] = (n + 1.0f) * 32.0f;\n\n\t// calc t coord\n\tn = DotProduct( normal, g_studio.chromeup[bone] );\n\tpchrome[1] = (n + 1.0f) * 32.0f;\n}\n\nstatic void R_StudioCalcAttachments( void )\n{\n\tmstudioattachment_t\t*pAtt;\n\tint\t\ti;\n\n\t// calculate attachment points\n\tpAtt = PTR_CAST(mstudioattachment_t, (byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex);\n\n\tfor( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ )\n\t{\n\t\tMatrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] );\n\t}\n}\n\nstatic void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel )\n{\n\tint\tindex;\n\n\tif( bodypart > m_pStudioHeader->numbodyparts )\n\t\tbodypart = 0;\n\n\tg_studio_current.bodypart_index = bodypart;\n\n\tm_pBodyPart = PTR_CAST(mstudiobodyparts_t, (byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart;\n\n\tindex = RI.currententity->curstate.body / m_pBodyPart->base;\n\tindex = index % m_pBodyPart->nummodels;\n\n\tm_pSubModel = PTR_CAST(mstudiomodel_t, (byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index;\n\n\tif( ppbodypart ) *ppbodypart = m_pBodyPart;\n\tif( ppsubmodel ) *ppsubmodel = m_pSubModel;\n}\n\nstatic int R_StudioCheckBBox( void )\n{\n\tif( !RI.currententity || !RI.currentmodel )\n\t\treturn false;\n\n\treturn R_StudioComputeBBox( NULL );\n}\n\nstatic void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight )\n{\n\tmovevars_t\t*mv = MOVEVARS;\n\tvec3_t\t\tlightDir, vecSrc, vecEnd;\n\tvec3_t\t\torigin, dist, finalLight;\n\tfloat\t\tadd, radius, total;\n\tcolorVec\t\tlight;\n\tuint\t\tlnum;\n\tdlight_t\t\t*dl;\n\n\tif( !plight || !ent || !ent->model )\n\t\treturn;\n\n\tif( !RI.drawWorld /* FIXME VK NOT IMPLEMENTED || r_fullbright->value */ || FBitSet( ent->curstate.effects, EF_FULLBRIGHT ))\n\t{\n\t\tplight->shadelight = 0;\n\t\tplight->ambientlight = 192;\n\n\t\tVectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f );\n\t\tVectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\t\treturn;\n\t}\n\n\t// determine plane to get lightvalues from: ceil or floor\n\tif( FBitSet( ent->curstate.effects, EF_INVLIGHT ))\n\t\tVectorSet( lightDir, 0.0f, 0.0f, 1.0f );\n\telse VectorSet( lightDir, 0.0f, 0.0f, -1.0f );\n\n\tVectorCopy( ent->origin, origin );\n\n\tVectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f );\n\tlight.r = light.g = light.b = light.a = 0;\n\n\tif(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 )\n\t{\n\t\tmsurface_t\t*psurf = NULL;\n\t\tpmtrace_t\t\ttrace;\n\n\t\tif( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD ))\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f;\n\t\t\tvecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f;\n\t\t\tvecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f;\n\t\t}\n\n\t\ttrace = gEngine.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY );\n\t\tif( trace.ent > 0 ) psurf = gEngine.EV_TraceSurface( trace.ent, vecSrc, vecEnd );\n\t\telse psurf = gEngine.EV_TraceSurface( 0, vecSrc, vecEnd );\n\n\t\tif( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )))\n\t\t{\n\t\t\tVectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z );\n\n\t\t\tlight.r = LightToTexGamma( bound( 0, mv->skycolor_r, 255 ));\n\t\t\tlight.g = LightToTexGamma( bound( 0, mv->skycolor_g, 255 ));\n\t\t\tlight.b = LightToTexGamma( bound( 0, mv->skycolor_b, 255 ));\n\t\t}\n\t}\n\n\tif(( light.r + light.g + light.b ) < 16 ) // TESTTEST\n\t{\n\t\tcolorVec\tgcolor;\n\t\tfloat\tgrad[4];\n\n\t\tVectorScale( lightDir, 2048.0f, vecEnd );\n\t\tVectorAdd( vecEnd, vecSrc, vecEnd );\n\n\t\tlight = R_LightVec( vecSrc, vecEnd, g_studio.lightspot, g_studio.lightvec );\n\n\t\tif( VectorIsNull( g_studio.lightvec ))\n\t\t{\n\t\t\tvecSrc[0] -= 16.0f;\n\t\t\tvecSrc[1] -= 16.0f;\n\t\t\tvecEnd[0] -= 16.0f;\n\t\t\tvecEnd[1] -= 16.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] += 32.0f;\n\t\t\tvecEnd[0] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[1] += 32.0f;\n\t\t\tvecEnd[1] += 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tvecSrc[0] -= 32.0f;\n\t\t\tvecEnd[0] -= 32.0f;\n\n\t\t\tgcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );\n\t\t\tgrad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;\n\n\t\t\tlightDir[0] = grad[0] - grad[1] - grad[2] + grad[3];\n\t\t\tlightDir[1] = grad[1] + grad[0] - grad[2] - grad[3];\n\t\t\tVectorNormalize( lightDir );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tVectorCopy( g_studio.lightvec, lightDir );\n\t\t}\n\t}\n\n\tVectorSet( finalLight, light.r, light.g, light.b );\n\tent->cvFloorColor = light;\n\n\ttotal = Q_max( Q_max( light.r, light.g ), light.b );\n\tif( total == 0.0f ) total = 1.0f;\n\n\t// scale lightdir by light intentsity\n\tVectorScale( lightDir, total, lightDir );\n\n\tfor( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )\n\t{\n\t\tdl = globals.dlights + lnum;\n\n\t\tif( dl->die < g_studio.time) // VK FIXME || !r_dynamic->value )\n\t\t\tcontinue;\n\n\t\tVectorSubtract( ent->origin, dl->origin, dist );\n\n\t\tradius = VectorLength( dist );\n\t\tadd = (dl->radius - radius);\n\n\t\tif( add > 0.0f )\n\t\t{\n\t\t\ttotal += add;\n\n\t\t\tif( radius > 1.0f )\n\t\t\t\tVectorScale( dist, ( add / radius ), dist );\n\t\t\telse VectorScale( dist, add, dist );\n\n\t\t\tVectorAdd( lightDir, dist, lightDir );\n\n\t\t\tfinalLight[0] += LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f;\n\t\t\tfinalLight[1] += LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f;\n\t\t\tfinalLight[2] += LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f;\n\t\t}\n\t}\n\n\tif( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT ))\n\t\tadd = 0.6f;\n\telse add = 0.9f;\n\n\tVectorScale( lightDir, add, lightDir );\n\n\tplight->shadelight = VectorLength( lightDir );\n\tplight->ambientlight = total - plight->shadelight;\n\n\ttotal = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] );\n\n\tif( total > 0.0f )\n\t{\n\t\tplight->color[0] = finalLight[0] * ( 1.0f / total );\n\t\tplight->color[1] = finalLight[1] * ( 1.0f / total );\n\t\tplight->color[2] = finalLight[2] * ( 1.0f / total );\n\t}\n\telse VectorSet( plight->color, 1.0f, 1.0f, 1.0f );\n\n\tif( plight->ambientlight > 128 )\n\t\tplight->ambientlight = 128;\n\n\tif( plight->ambientlight + plight->shadelight > 255 )\n\t\tplight->shadelight = 255 - plight->ambientlight;\n\n\tVectorNormalize2( lightDir, plight->plightvec );\n}\n\n/*\n===============\npfnStudioEntityLight\n\n===============\n*/\nstatic void R_StudioEntityLight( alight_t *lightinfo )\n{\n\tint\t\tlnum, i, j, k;\n\tfloat\t\tminstrength, dist2, f, r2;\n\tfloat\t\tlstrength[MAX_LOCALLIGHTS];\n\tcl_entity_t\t*ent = RI.currententity;\n\tvec3_t\t\tmid, origin;\n\t//vec3_t\t\tpos;\n\tdlight_t\t\t*el;\n\n\tg_studio.numlocallights = 0;\n\n\tif( !ent ) // VK FIXME || !r_dynamic->value )\n\t\treturn;\n\n\tfor( i = 0; i < MAX_LOCALLIGHTS; i++ )\n\t\tlstrength[i] = 0;\n\n\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin );\n\tdist2 = 1000000.0f;\n\tk = 0;\n\n\tfor( lnum = 0; lnum < MAX_ELIGHTS; lnum++ )\n\t{\n\t\tel = globals.elights + lnum;\n\n\t\tif( el->die < g_studio.time || el->radius <= 0.0f )\n\t\t\tcontinue;\n\n\t\tif(( el->key & 0xFFF ) == ent->index )\n\t\t{\n\t\t\tint\tatt = (el->key >> 12) & 0xF;\n\n\t\t\tif( att ) VectorCopy( ent->attachment[att], el->origin );\n\t\t\telse VectorCopy( ent->origin, el->origin );\n\t\t}\n\n\t\t//VectorCopy( el->origin, pos );\n\t\tVectorSubtract( origin, el->origin, mid );\n\n\t\tf = DotProduct( mid, mid );\n\t\tr2 = el->radius * el->radius;\n\n\t\tif( f > r2 ) minstrength = r2 / f;\n\t\telse minstrength = 1.0f;\n\n\t\tif( minstrength > 0.05f )\n\t\t{\n\t\t\tif( g_studio.numlocallights >= MAX_LOCALLIGHTS )\n\t\t\t{\n\t\t\t\tfor( j = 0, k = -1; j < g_studio.numlocallights; j++ )\n\t\t\t\t{\n\t\t\t\t\tif( lstrength[j] < dist2 && lstrength[j] < minstrength )\n\t\t\t\t\t{\n\t\t\t\t\t\tdist2 = lstrength[j];\n\t\t\t\t\t\tk = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse k = g_studio.numlocallights;\n\n\t\t\tif( k != -1 )\n\t\t\t{\n\t\t\t\tg_studio.locallightcolor[k].r = LightToTexGamma( el->color.r );\n\t\t\t\tg_studio.locallightcolor[k].g = LightToTexGamma( el->color.g );\n\t\t\t\tg_studio.locallightcolor[k].b = LightToTexGamma( el->color.b );\n\t\t\t\tg_studio.locallightR2[k] = r2;\n\t\t\t\tg_studio.locallight[k] = el;\n\t\t\t\tlstrength[k] = minstrength;\n\n\t\t\t\tif( k >= g_studio.numlocallights )\n\t\t\t\t\tg_studio.numlocallights = k + 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/*\n===============\nR_StudioSetupLighting\n\n===============\n*/\nstatic void R_StudioSetupLighting( alight_t *plight )\n{\n\tfloat\tscale = 1.0f;\n\tint\ti;\n\n\tif( !m_pStudioHeader || !plight )\n\t\treturn;\n\n\tif( RI.currententity != NULL )\n\t\tscale = RI.currententity->curstate.scale;\n\n\tg_studio.ambientlight = plight->ambientlight;\n\tg_studio.shadelight = plight->shadelight;\n\tVectorCopy( plight->plightvec, g_studio.lightvec );\n\n\tfor( i = 0; i < m_pStudioHeader->numbones; i++ )\n\t{\n\t\tMatrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] );\n\t\tif( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled\n\t}\n\n\tVectorCopy( plight->color, g_studio.lightcolor );\n}\n\n/*\n===============\nR_StudioLighting\n\n===============\n*/\nstatic void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal )\n{\n\tfloat \tillum;\n\n\tif( FBitSet( flags, STUDIO_NF_FULLBRIGHT ))\n\t{\n\t\t*lv = 1.0f;\n\t\treturn;\n\t}\n\n\tillum = g_studio.ambientlight;\n\n\tif( FBitSet( flags, STUDIO_NF_FLATSHADE ))\n\t{\n\t\tillum += g_studio.shadelight * 0.8f;\n\t}\n\telse\n\t{\n\t\tfloat\tr, lightcos;\n\n\t\tif( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] );\n\t\telse lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite\n\t\tif( lightcos > 1.0f ) lightcos = 1.0f;\n\n\t\tillum += g_studio.shadelight;\n\n\t\t// TODO VK what\n#define SHADE_LAMBERT\t1.495f\n\t\tr = SHADE_LAMBERT;\n\n \t\t// do modified hemispherical lighting\n\t\tif( r <= 1.0f )\n\t\t{\n\t\t\tr += 1.0f;\n\t\t\tlightcos = (( r - 1.0f ) - lightcos) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum += g_studio.shadelight * lightcos;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlightcos = (lightcos + ( r - 1.0f )) / r;\n\t\t\tif( lightcos > 0.0f )\n\t\t\t\tillum -= g_studio.shadelight * lightcos;\n\t\t}\n\n\t\tillum = Q_max( illum, 0.0f );\n\t}\n\n\tillum = Q_min( illum, 255.0f );\n\t*lv = illum * (1.0f / 255.0f);\n}\n\nstatic void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out )\n{\n\tvec3_t\tfinalLight;\n\tint\ti;\n\n\tif( !g_studio.numlocallights )\n\t{\n\t\tVectorScale( color, 255.0f, out );\n\t\treturn;\n\t}\n\n\tVectorCopy( color, finalLight );\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tfloat\tr;\n\n\t\tr = DotProduct( normal, light[i] );\n#if 0 // VKTODO\n\t\tif( likely( !tr.fFlipViewModel ))\n\t\t\tr = -r;\n#endif\n\n\t\tif( r > 0.0f )\n\t\t{\n\t\t\tvec3_t localLight;\n\t\t\tfloat temp;\n\n\t\t\tif( light[i][3] == 0.0f )\n\t\t\t{\n\t\t\t\tfloat r2 = DotProduct( light[i], light[i] );\n\n\t\t\t\tif( r2 > 0.0f )\n\t\t\t\t\tlight[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 ));\n\t\t\t\telse light[i][3] = 0.0001f;\n\t\t\t}\n\n\t\t\ttemp = Q_min( r * light[i][3] / 255.0f, 1.0f );\n\n\t\t\tlocalLight[0] = (float)g_studio.locallightcolor[i].r * temp;\n\t\t\tlocalLight[1] = (float)g_studio.locallightcolor[i].g * temp;\n\t\t\tlocalLight[2] = (float)g_studio.locallightcolor[i].b * temp;\n\n\t\t\tVectorAdd( finalLight, localLight, finalLight );\n\t\t}\n\t}\n\n\tVectorScale( finalLight, 255.0f, finalLight );\n\n\tout[0] = Q_min( (int)( finalLight[0] ), 255 );\n\tout[1] = Q_min( (int)( finalLight[1] ), 255 );\n\tout[2] = Q_min( (int)( finalLight[2] ), 255 );\n}\n\nstatic void R_StudioSetColorArray(const short *ptricmds, const vec3_t *pstudionorms, byte *color )\n{\n\tfloat\t*lv = (float *)g_studio.lightvalues[ptricmds[1]];\n\n\tcolor[3] = g_studio.blend * 255;\n\tR_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color );\n}\n\nstatic void R_StudioSetColorBegin(const short *ptricmds, const vec3_t *pstudionorms, rgba_t out_color )\n{\n\tR_StudioSetColorArray( ptricmds, pstudionorms, out_color );\n}\n\nstatic void R_LightStrength( int bone, const vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] )\n{\n\tint\ti;\n\n\tif( g_studio.lightage[bone] != g_studio.framecount )\n\t{\n\t\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t\t{\n\t\t\tdlight_t *el = g_studio.locallight[i];\n\t\t\tMatrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] );\n\t\t}\n\n\t\tg_studio.lightage[bone] = g_studio.framecount;\n\t}\n\n\tfor( i = 0; i < g_studio.numlocallights; i++ )\n\t{\n\t\tVectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] );\n\t\tlight[i][3] = 0.0f;\n\t}\n}\n\nstatic int R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index )\n{\n\tmstudiotexture_t\t*ptexture = NULL;\n\n\tif( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME ))\n\t\treturn -1;\n\n\tif( ptexturehdr == NULL )\n\t\treturn -1;\n\n\t// NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable\n\tif( m_fDoRemap ) ptexture = gEngine.CL_GetRemapInfoForEntity( RI.currententity )->ptexture;\n\tif( !ptexture ) ptexture = PTR_CAST(mstudiotexture_t, (byte *)ptexturehdr + ptexturehdr->textureindex); // fallback\n\n\t/* FIXME VK\n\tif( r_lightmap->value && !r_fullbright->value )\n\t\tGL_Bind( XASH_TEXTURE0, tr.whiteTexture );\n\telse GL_Bind( XASH_TEXTURE0, ptexture[index].index );\n\t*/\n\treturn ptexture[index].index;\n}\n\n/*\n===============\nR_StudioGetTexture\n\nDoesn't changes studio global state at all\n===============\n*/\n/*\nstatic mstudiotexture_t *R_StudioGetTexture( cl_entity_t *e )\n{\n\tmstudiotexture_t\t*ptexture;\n\tstudiohdr_t\t*phdr, *thdr;\n\n\tif(( phdr = gEngine.Mod_Extradata( mod_studio, e->model )) == NULL )\n\t\treturn NULL;\n\n\tthdr = m_pStudioHeader;\n\tif( !thdr ) return NULL;\n\n\tif( m_fDoRemap ) ptexture = gEngine.CL_GetRemapInfoForEntity( e )->ptexture;\n\telse ptexture = PTR_CAST(mstudiotexture_t, (byte *)thdr + thdr->textureindex);\n\n\treturn ptexture;\n}\n*/\n\n// TODO where does this need to be declared and defined? currently it's in vk_scene.c\nextern int CL_FxBlend( cl_entity_t *e );\n\nstatic void R_StudioSetRenderamt( int iRenderamt )\n{\n\tif( !RI.currententity ) return;\n\n\tRI.currententity->curstate.renderamt = iRenderamt;\n\tg_studio.blend = CL_FxBlend( RI.currententity ) / 255.0f;\n}\n\n/*\n===============\nR_StudioSetCullState\n\nsets true for enable backculling (for left-hand viewmodel)\n===============\n*/\nstatic void R_StudioSetCullState( int iCull )\n{\n\tg_iBackFaceCull = iCull;\n}\n\n/*\n===============\nR_StudioRenderShadow\n\njust a prefab for render shadow\n===============\n*/\nstatic void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 )\n{\n\tPRINT_NOT_IMPLEMENTED();\n\n\t/*\n\tif( !p1 || !p2 || !p3 || !p4 )\n\t\treturn;\n\n\tif( TriSpriteTexture( gEngine.pfnGetModelByIndex( iSprite ), 0 ))\n\t{\n\t\tTriRenderMode( kRenderTransAlpha );\n\t\tTriColor4f( 0.0f, 0.0f, 0.0f, 1.0f );\n\n\t\tpglBegin( GL_QUADS );\n\t\t\tpglTexCoord2f( 0.0f, 0.0f );\n\t\t\tpglVertex3fv( p1 );\n\t\t\tpglTexCoord2f( 0.0f, 1.0f );\n\t\t\tpglVertex3fv( p2 );\n\t\t\tpglTexCoord2f( 1.0f, 1.0f );\n\t\t\tpglVertex3fv( p3 );\n\t\t\tpglTexCoord2f( 1.0f, 0.0f );\n\t\t\tpglVertex3fv( p4 );\n\t\tpglEnd();\n\n\t\tTriRenderMode( kRenderNormal );\n\t}\n\t*/\n}\n\n/*\n===============\nR_StudioMeshCompare\n\nSorting opaque entities by model type\n===============\n*/\n/*\nstatic int R_StudioMeshCompare( const void *a, const void *b )\n{\n\tif( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_ADDITIVE ))\n\t\treturn 1;\n\n\tif( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_MASKED ))\n\t\treturn -1;\n\n\treturn 0;\n}\n*/\n\nstatic void addVerticesIndicesCounts( const short *ptricmds, int *num_vertices, int *num_indices ) {\n\tint i;\n\n\twhile(( i = *( ptricmds++ ))) {\n\t\tenum { FAN, STRIP } mode = i < 0 ? FAN : STRIP;\n\t\tconst int vertices = mode == FAN ? -i : i;\n\t\tASSERT(vertices > 2);\n\t\t*num_vertices += vertices;\n\t\t*num_indices += (vertices-2) * 3;\n\t\tptricmds += 4 * vertices;\n\t}\n}\n\ntypedef struct {\n\tconst short *ptricmds;\n\tconst vec3_t *pstudionorms;\n\tconst vec3_t *prev_verts;\n\n\tfloat s, t;\n\tint texture;\n\tint face_flags;\n\n\tuint32_t vertices_offset;\n\tuint32_t indices_offset;\n\n\tvk_vertex_t *dst_vertices;\n\tuint16_t *dst_indices;\n\tvk_render_geometry_t *out_geometry;\n\n\tint *out_vertices_count;\n\tint *out_indices_count;\n} build_submodel_mesh_t;\n\nstatic void buildSubmodelMeshGeometry( build_submodel_mesh_t args ) {\n\tint i;\n\tuint32_t vertex_offset = 0, index_offset = 0;\n\n\tint num_vertices = 0, num_indices = 0;\n\taddVerticesIndicesCounts(args.ptricmds, &num_vertices, &num_indices);\n\tASSERT(num_vertices > 0);\n\tASSERT(num_indices > 0);\n\n\tvk_vertex_t *dst_vtx = args.dst_vertices;\n\tuint16_t *dst_idx = args.dst_indices;\n\n\t// Restore ptricmds and upload vertices\n\twhile(( i = *( args.ptricmds++ )))\n\t{\n\t\tenum { FAN, STRIP } mode = i < 0 ? FAN : STRIP;\n\t\tconst int vertices = mode == FAN ? -i : i;\n\t\tuint32_t elements = 0;\n\n\t\tfor(int j = 0; j < vertices ; ++j, ++dst_vtx, args.ptricmds += 4 )\n\t\t{\n\t\t\tconst int vi = args.ptricmds[0];\n\t\t\t*dst_vtx = (vk_vertex_t){0};\n\n\t\t\tVectorCopy(g_studio.verts[vi], dst_vtx->pos);\n\t\t\tVectorCopy(args.prev_verts[vi], dst_vtx->prev_pos);\n\n\t\t\tVectorCopy(g_studio.norms[vi], dst_vtx->normal);\n\n\t\t\t{\n\t\t\t\tconst float normal_len2 = DotProduct(g_studio.norms[vi], g_studio.norms[vi]);\n\t\t\t\tif (normal_len2 < .9f) {\n\t\t\t\t\tERROR_THROTTLED(10,\n\t\t\t\t\t\t\"model=%s bodypart=%d vert=%d+%d=%d vi=%d normal=(%f,%f,%f) INVALID len2=%f\",\n\t\t\t\t\t\tg_studio_current.entmodel->studio_header->name,\n\t\t\t\t\t\tg_studio_current.bodypart_index,\n\t\t\t\t\t\tj, vertex_offset, j + vertex_offset, vi,\n\t\t\t\t\t\tg_studio.norms[vi][0],\n\t\t\t\t\t\tg_studio.norms[vi][1],\n\t\t\t\t\t\tg_studio.norms[vi][2],\n\t\t\t\t\t\tnormal_len2\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\tVectorCopy(g_studio.tangents[vi], dst_vtx->tangent);\n\t\t\tdst_vtx->lm_tc[0] = dst_vtx->lm_tc[1] = 0.f;\n\n\t\t\tif (FBitSet( args.face_flags, STUDIO_NF_CHROME ))\n\t\t\t{\n\t\t\t\t// FIXME also support glow mode\n\t\t\t\tconst int idx = args.ptricmds[1];\n\t\t\t\tdst_vtx->gl_tc[0] = g_studio.chrome[idx][0] * args.s;\n\t\t\t\tdst_vtx->gl_tc[1] = g_studio.chrome[idx][1] * args.t;\n\t\t\t} else {\n\t\t\t\tdst_vtx->gl_tc[0] = args.ptricmds[2] * args.s;\n\t\t\t\tdst_vtx->gl_tc[1] = args.ptricmds[3] * args.t;\n\t\t\t}\n\n\t\t\tR_StudioSetColorBegin( args.ptricmds, args.pstudionorms, dst_vtx->color );\n\n\t\t\tif (j > 1) {\n\t\t\t\tswitch (mode) {\n\t\t\t\t\tcase FAN:\n\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + 0;\n\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j - 1;\n\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase STRIP:\n\t\t\t\t\t\t// flip triangles between clockwise and counter clockwise\n\t\t\t\t\t\tif( j & 1 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// draw triangle [n-1 n-2 n]\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j - 1;\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j - 2;\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// draw triangle [n-2 n-1 n]\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j - 2;\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j - 1;\n\t\t\t\t\t\t\tdst_idx[elements++] = vertex_offset + j;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tASSERT(elements == (vertices-2)*3);\n\t\tindex_offset += elements;\n\t\tvertex_offset += vertices;\n\t\tdst_idx += elements;\n\t}\n\n\tASSERT(vertex_offset < UINT16_MAX);\n\tASSERT(index_offset == num_indices);\n\tASSERT(vertex_offset == num_vertices);\n\n\t*args.out_geometry = (vk_render_geometry_t){\n\t\t.material = R_VkMaterialGetForTextureWithFlags(args.texture, FBitSet( args.face_flags, STUDIO_NF_CHROME ) ? kVkMaterialFlagChrome : kVkMaterialFlagNone),\n\t\t.ye_olde_texture = args.texture,\n\n\t\t.vertex_offset = args.vertices_offset,\n\t\t.max_vertex = num_vertices,\n\n\t\t.index_offset = args.indices_offset,\n\t\t.element_count = num_indices,\n\n\t\t.emissive = {0, 0, 0},\n\t};\n\n\t*args.out_vertices_count += num_vertices;\n\t*args.out_indices_count += num_indices;\n}\n\n/* FIXME VK\nstatic void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms )\n{\n\tfloat\t*lv;\n\tint\ti;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse pglBegin( GL_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\tpglTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] ));\n\t\t\tpglVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t}\n\n\t\tpglEnd();\n\t}\n}\n*/\n\n/* FIXME VK\n_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale )\n{\n\tfloat\t*lv, *av;\n\tint\ti, idx;\n\tqboolean\tglowShell = (scale > 0.0f) ? true : false;\n\tvec3_t\tvert;\n\n\twhile(( i = *( ptricmds++ )))\n\t{\n\t\tif( i < 0 )\n\t\t{\n\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\ti = -i;\n\t\t}\n\t\telse pglBegin( GL_TRIANGLE_STRIP );\n\n\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t{\n\t\t\tif( glowShell )\n\t\t\t{\n\t\t\t\tcolor24 *clr = &RI.currententity->curstate.rendercolor;\n\n\t\t\t\tidx = g_studio.normaltable[ptricmds[0]];\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tlv = g_studio.norms[ptricmds[0]];\n\t\t\t\tVectorMA( av, scale, lv, vert );\n\t\t\t\tpglColor4ub( clr->r, clr->g, clr->b, 255 );\n\t\t\t\tpglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tpglVertex3fv( vert );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tidx = ptricmds[1];\n\t\t\t\tlv = (float *)g_studio.lightvalues[ptricmds[1]];\n\t\t\t\tR_StudioSetColorBegin( ptricmds, pstudionorms );\n\t\t\t\tpglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );\n\t\t\t\tpglVertex3fv( g_studio.verts[ptricmds[0]] );\n\t\t\t}\n\t\t}\n\n\t\tpglEnd();\n\t}\n}\n*/\n\nstatic vk_render_type_e studioRenderModeToRenderType( int render_mode ) {\n\tswitch (render_mode) {\n\t\tcase kRenderNormal:       return kVkRenderTypeSolid;\n\t\tcase kRenderTransColor:   return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderTransTexture: return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderGlow:         return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderTransAlpha:   return kVkRenderType_A_1mA_RW;\n\t\tcase kRenderTransAdd:     return kVkRenderType_1_1_R;\n\t\tdefault: ASSERT(!\"Unxpected render_mode\");\n\t}\n\n\treturn kVkRenderTypeSolid;\n}\n\ntypedef struct {\n\t//const mstudiomodel_t *submodel;\n\tconst r_geometry_range_t *geometry;\n\tvk_render_geometry_t *geometries;\n\tint vertex_count, index_count;\n\tconst vec3_t *prev_verts;\n} build_submodel_geometry_t;\n\nstatic void buildStudioSubmodelGeometry(build_submodel_geometry_t args) {\n\t// FIXME: do not reference global things like RI.* m_pStudio* here, pass everything by args\n\tconst r_geometry_range_lock_t geom_lock = R_GeometryRangeLock(args.geometry);\n\tASSERT(geom_lock.vertices);\n\tASSERT(geom_lock.indices);\n\n\t// FIXME VK entity->curstate.skin can potentially be animated\n\n\t// safety bounding the skinnum\n\tconst int m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 ));\n\tconst mstudiotexture_t *const ptexture = PTR_CAST(const mstudiotexture_t, (const byte *)m_pStudioHeader + m_pStudioHeader->textureindex);\n\tconst byte *const pvertbone = ((const byte *)m_pStudioHeader + m_pSubModel->vertinfoindex);\n\tconst byte *pnormbone = ((const byte *)m_pStudioHeader + m_pSubModel->norminfoindex);\n\n\tconst vec3_t *pstudioverts = PTR_CAST(const vec3_t, (const byte *)m_pStudioHeader + m_pSubModel->vertindex);\n\tconst vec3_t *pstudionorms = PTR_CAST(const vec3_t, (const byte *)m_pStudioHeader + m_pSubModel->normindex);\n\n\tconst short *pskinref = PTR_CAST(const short, (byte *)m_pStudioHeader + m_pStudioHeader->skinindex);\n\tif( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref);\n\n\t// Compute inverse entity matrix, as we need vertices to be in local model space instead of global world space.\n\t// Ideally, we'd just avoid multiplying vertices by entity matrix in R_StudioMergeBones and friends. But unfortunately games themselves seem to be premultiplying bone matrices by entity matrix, so we need to manually undo this multiplication here.\n\tmatrix4x4 rotationmatrix = {0}, rotationmatrix_inv = {0};\n\tMatrix3x4_Copy(rotationmatrix, g_studio.rotationmatrix);\n\tMatrix4x4_Invert_Simple(rotationmatrix_inv, rotationmatrix);\n\n\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 )\n\t{\n\t\tconst mstudioboneweight_t *const pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex);\n\t\tconst mstudioboneweight_t *const pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex);\n\t\tmatrix3x4 skinMat;\n\n\t\tfor( int i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pvertweight[i], g_studio.worldtransform, skinMat );\n\n\t\t\tvec3_t v;\n\t\t\tMatrix3x4_VectorTransform( skinMat, pstudioverts[i], v);\n\t\t\tMatrix3x4_VectorTransform( rotationmatrix_inv, v, g_studio.verts[i] );\n\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\n\t\tfor( int i = 0; i < m_pSubModel->numnorms; i++ )\n\t\t{\n\t\t\tR_StudioComputeSkinMatrix( &pnormweight[i], g_studio.worldtransform, skinMat );\n\n\t\t\tvec3_t v;\n\t\t\tMatrix3x4_VectorRotate( skinMat, pstudionorms[i], v);\n\t\t\tMatrix3x4_VectorRotate( rotationmatrix_inv, v, g_studio.norms[i] );\n\t\t}\n\t}\n\telse\n\t{\n\t\tfor( int i = 0; i < m_pSubModel->numverts; i++ )\n\t\t{\n\t\t\tvec3_t v;\n\t\t\tMatrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], v);\n\t\t\tMatrix3x4_VectorTransform( rotationmatrix_inv, v, g_studio.verts[i] );\n\t\t\tR_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );\n\t\t}\n\t}\n\n\t// generate shared normals for properly scaling glowing shell\n\t//float shellscale = 0.0f;\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\t//float factor = (1.0f / 128.0f);\n\t\t//shellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor );\n\t\tR_StudioBuildNormalTable();\n\t}\n\n\tR_StudioGenerateNormals();\n\n\tconst mstudiomesh_t *const pmesh = PTR_CAST(const mstudiomesh_t, (byte *)m_pStudioHeader + m_pSubModel->meshindex);\n\n\t//qboolean need_sort = false;\n\tfor( int j = 0, k = 0; j < m_pSubModel->nummesh; j++ )\n\t{\n\t\tconst int face_flags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags;\n\n\t\t// fill in sortedmesh info\n\t\tg_studio.meshes[j].flags = face_flags;\n\t\tg_studio.meshes[j].mesh = &pmesh[j];\n\n\t\t// FIXME VK cannot into \"dynamic\" blending/alpha-test\n\t\t/*if( FBitSet( face_flags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE ))*/\n\t\t/*\tneed_sort = true;*/\n\n\t\tif( RI.currententity->curstate.rendermode == kRenderTransAdd )\n\t\t{\n\t\t\tfor( int i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )\n\t\t\t{\n\t\t\t\t// FIXME VK\n\t\t\t\t//const struct { float blend; } tr = {1.f};\n\t\t\t\tif( FBitSet( face_flags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorSet( g_studio.lightvalues[k], g_studio.blend, g_studio.blend, g_studio.blend );\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfor( int  i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) {\n\t\t\t\tfloat lv_tmp;\n\t\t\t\tif( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ))\n\t\t\t\t\tR_StudioLighting( &lv_tmp, -1, face_flags, g_studio.norms[k] );\n\t\t\t\telse R_StudioLighting( &lv_tmp, *pnormbone, face_flags, (float *)pstudionorms );\n\n\t\t\t\tif( FBitSet( face_flags, STUDIO_NF_CHROME ))\n\t\t\t\t\tR_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );\n\t\t\t\tVectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] );\n\t\t\t}\n\t\t}\n\t}\n\n\t/* FIXME VK\n\t * this might potentially break blas update topology\n\tif( need_sort )\n\t{\n\t\t// resort opaque and translucent meshes draw order\n\t\tqsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare );\n\t}\n\t*/\n\n\t\t// NOTE: rewind normals at start\n\tpstudionorms = PTR_CAST(const vec3_t, (const byte *)m_pStudioHeader + m_pSubModel->normindex);\n\n\tint vertices_offset = 0, indices_offset = 0;\n\tfor( int j = 0; j < m_pSubModel->nummesh; j++ ) {\n\t\tconst mstudiomesh_t *const pmesh = g_studio.meshes[j].mesh;\n\t\tconst short *const ptricmds = PTR_CAST(const short, (byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\tconst int face_flags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags;\n\n\t\tconst float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;\n\t\tconst float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;\n\n\t\t/* FIXME VK\n\t\tconst float oldblend = g_studio.blend;\n\t\tif( FBitSet( face_flags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\tpglEnable( GL_ALPHA_TEST );\n\t\t\tpglAlphaFunc( GL_GREATER, 0.5f );\n\t\t\tpglDepthMask( GL_TRUE );\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t\tg_studio.blend = 1.0f;\n\t\t}\n\t\telse if( FBitSet( face_flags, STUDIO_NF_ADDITIVE ))\n\t\t{\n\t\t\tif( R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t\t{\n\t\t\t\tpglBlendFunc( GL_ONE, GL_ONE );\n\t\t\t\tpglDepthMask( GL_FALSE );\n\t\t\t\tpglEnable( GL_BLEND );\n\t\t\t\tR_AllowFog( false );\n\t\t\t}\n\t\t\telse pglBlendFunc( GL_SRC_ALPHA, GL_ONE );\n\t\t}\n\t\t*/\n\n\t\tconst int texture = R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] );\n\n\t\t/* FIXME VK if( FBitSet( face_flags, STUDIO_NF_CHROME ))\n\t\t\tR_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );\n\t\telse if( FBitSet( face_flags, STUDIO_NF_UV_COORDS ))\n\t\t\tR_StudioDrawFloatMesh( ptricmds, pstudionorms );\n\t\telse*/\n\n\t\tbuildSubmodelMeshGeometry((build_submodel_mesh_t){\n\t\t\t.ptricmds = ptricmds,\n\t\t\t.pstudionorms = pstudionorms,\n\t\t\t.prev_verts = args.prev_verts,\n\t\t\t.s = s,\n\t\t\t.t = t,\n\t\t\t.texture = texture,\n\t\t\t.face_flags = face_flags,\n\t\t\t.vertices_offset = args.geometry->vertices.unit_offset + vertices_offset,\n\t\t\t.indices_offset = args.geometry->indices.unit_offset + indices_offset,\n\t\t\t.dst_vertices = geom_lock.vertices + vertices_offset,\n\t\t\t.dst_indices = geom_lock.indices + indices_offset,\n\t\t\t.out_geometry = args.geometries + j,\n\t\t\t.out_vertices_count = &vertices_offset,\n\t\t\t.out_indices_count = &indices_offset,\n\t\t});\n\n\t\tASSERT(vertices_offset <= args.vertex_count);\n\t\tASSERT(indices_offset <= args.index_count);\n\n\t\t/* FIXME VK\n\t\tif( FBitSet( face_flags, STUDIO_NF_MASKED ))\n\t\t{\n\t\t\tpglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );\n\t\t\tpglDisable( GL_ALPHA_TEST );\n\t\t}\n\t\telse if( FBitSet( face_flags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode ))\n\t\t{\n\t\t\tpglDepthMask( GL_TRUE );\n\t\t\tpglDisable( GL_BLEND );\n\t\t\tR_AllowFog( true );\n\t\t}\n\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\t\tg_studio.blend = oldblend;\n\t\t*/\n\t}\n\n\tR_GeometryRangeUnlock(&geom_lock);\n}\n\nstatic qboolean studioSubmodelRenderInit(r_studio_submodel_render_t *render_submodel, const mstudiomodel_t *submodel, qboolean is_dynamic) {\n\t// Compute vertex and index counts.\n\t// TODO should this be part of r_studio_model_info_t?\n\tint vertex_count = 0, index_count = 0;\n\t{\n\t\tconst mstudiomesh_t *const pmesh = PTR_CAST(const mstudiomesh_t, (byte *)m_pStudioHeader + m_pSubModel->meshindex);\n\t\tfor(int i = 0; i < submodel->nummesh; i++) {\n\t\t\tconst short* const ptricmds = PTR_CAST(const short, (byte *)m_pStudioHeader + pmesh[i].triindex);\n\t\t\taddVerticesIndicesCounts(ptricmds, &vertex_count, &index_count);\n\t\t}\n\n\t\tASSERT(vertex_count > 0);\n\t\tASSERT(index_count > 0);\n\t}\n\n\t// TODO can be coalesced into a single allocation for the entire model\n\tconst r_geometry_range_t geometry = R_GeometryRangeAlloc(vertex_count, index_count);\n\tif (geometry.block_handle.size == 0) {\n\t\tERR(\"Unable to allocate %d vertices %d indices for submodel %s\",\n\t\t\tvertex_count, index_count, submodel->name);\n\t\treturn false;\n\t}\n\n\t// TODO can be coalesced\n\tvk_render_geometry_t *const geometries = Mem_Malloc(vk_core.pool, submodel->nummesh * sizeof(*geometries));\n\tASSERT(geometries);\n\n\tbuildStudioSubmodelGeometry((build_submodel_geometry_t){\n\t\t.geometry = &geometry,\n\t\t.geometries = geometries,\n\t\t.vertex_count = vertex_count,\n\t\t.index_count = index_count,\n\t\t.prev_verts = g_studio.verts,\n\t});\n\n\t// Store vertices computed by bulidStudioSubmodelGeometry as intial prev_verts\n\tconst size_t verts_size = sizeof(vec3_t) * submodel->numverts;\n\trender_submodel->prev_verts = Mem_Malloc(vk_core.pool, verts_size);\n\tmemcpy(render_submodel->prev_verts, g_studio.verts, verts_size);\n\n\trender_submodel->geometries = geometries;\n\trender_submodel->geometries_count = submodel->nummesh;\n\trender_submodel->geometry_range = geometry;\n\trender_submodel->vertex_count = vertex_count;\n\trender_submodel->index_count = index_count;\n\n\tif (!R_RenderModelCreate(&render_submodel->model, (vk_render_model_init_t){\n\t\t.name = submodel->name,\n\t\t.geometries = geometries,\n\t\t.geometries_count = submodel->nummesh,\n\t\t.dynamic = is_dynamic,\n\t})) {\n\t\tERR(\"Unable to create render model for studio submodel %s\", submodel->name);\n\t\tMem_Free(geometries);\n\t\t// FIXME everything else leaks ;_;\n\t\t// FIXME sync up with staging and free\n\t\tmemset(render_submodel, 0, sizeof(*render_submodel));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic qboolean studioSubmodelRenderUpdate(const r_studio_submodel_render_t *submodel_render, const mstudiomodel_t *submodel) {\n\tbuildStudioSubmodelGeometry((build_submodel_geometry_t){\n\t\t//.submodel = submodel_render->key_submodel,\n\t\t.geometry = &submodel_render->geometry_range,\n\t\t.geometries = submodel_render->geometries,\n\t\t.vertex_count = submodel_render->vertex_count,\n\t\t.index_count = submodel_render->index_count,\n\t\t.prev_verts = submodel_render->prev_verts,\n\t});\n\n\t// Remember previous frame verts\n\tconst size_t verts_size = sizeof(vec3_t) * submodel->numverts;\n\tmemcpy(submodel_render->prev_verts, g_studio.verts, verts_size);\n\n\treturn R_RenderModelUpdate(&submodel_render->model);\n}\n\nstatic void studioEntityModelDestroy(void *userdata) {\n\tr_studio_entity_model_t *entmodel = (r_studio_entity_model_t*)userdata;\n\tfor (int i = 0; i < entmodel->bodyparts_count; ++i) {\n\t\tr_studio_submodel_render_t *const render = entmodel->bodyparts[i];\n\t\tstudioSubmodelRenderModelRelease(render);\n\t}\n\tif (entmodel->bodyparts)\n\t\tMem_Free(entmodel->bodyparts);\n}\n\nstatic r_studio_entity_model_t *studioEntityModelCreate(const studiohdr_t *hdr, model_t *model) {\n\tr_studio_entity_model_t *const entmodel = Mem_Calloc(vk_core.pool, sizeof(r_studio_entity_model_t));\n\n\tentmodel->studio_header = hdr;\n\tentmodel->bodyparts_count = hdr->numbodyparts; // TODO is this correct number?\n\tentmodel->bodyparts = Mem_Calloc(vk_core.pool, sizeof(*entmodel->bodyparts) * entmodel->bodyparts_count);\n\n\tMatrix4x4_LoadIdentity(entmodel->transform);\n\tMatrix4x4_LoadIdentity(entmodel->prev_transform);\n\tMatrix3x4_Copy(entmodel->prev_transform, g_studio.rotationmatrix);\n\n\tentmodel->model_info = getStudioModelInfo(model);\n\tASSERT(entmodel->model_info);\n\n\treturn entmodel;\n}\n\nstatic r_studio_entity_model_t *studioEntityModelGet(const cl_entity_t* entity) {\n\tr_studio_entity_model_t *entmodel = (r_studio_entity_model_t*)VK_EntityDataGet(entity);\n\tif (entmodel && entmodel->studio_header == m_pStudioHeader)\n\t\treturn entmodel;\n\n\tentmodel = studioEntityModelCreate(m_pStudioHeader, RI.currentmodel);\n\tif (!entmodel) {\n\t\tERR(\"Cannot create studio entity model for model=%p(%s)\", RI.currentmodel, RI.currentmodel->name);\n\t\treturn NULL;\n\t}\n\n\tDEBUG(\"Created studioEntityModel=%p entity=%p(%d) model=%p(%s) hdr=%p(%s), bodyparts=%d\",\n\t\tentmodel,\n\t\tentity, entity->index,\n\t\tRI.currentmodel, RI.currentmodel->name,\n\t\tm_pStudioHeader, m_pStudioHeader->name,\n\t\tentmodel->bodyparts_count);\n\n\tVK_EntityDataSet(entity, entmodel, &studioEntityModelDestroy);\n\treturn entmodel;\n}\n\nstatic r_studio_submodel_info_t *studioModelFindSubmodelInfo(void) {\n\tfor (int i = 0; i < g_studio_current.entmodel->model_info->submodels_count; ++i) {\n\t\tr_studio_submodel_info_t *const subinfo = g_studio_current.entmodel->model_info->submodels + i;\n\t\tif (subinfo->submodel_key == m_pSubModel)\n\t\t\treturn subinfo;\n\t}\n\n\treturn NULL;\n}\n\nstatic material_mode_e studioMaterialModeForRenderType(vk_render_type_e render_type) {\n\tswitch (render_type) {\n\t\tcase kVkRenderTypeSolid:\n\t\t\treturn kMaterialMode_Opaque;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1mA_RW: // blend: scr*a + dst*(1-a), depth: RW\n\t\tcase kVkRenderType_A_1mA_R:  // blend: scr*a + dst*(1-a), depth test\n\t\t\treturn kMaterialMode_Translucent;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1:   // blend: scr*a + dst, no depth test or write; sprite:kRenderGlow only\n\t\t\treturn kMaterialMode_BlendGlow;\n\t\t\tbreak;\n\t\tcase kVkRenderType_A_1_R: // blend: scr*a + dst, depth test\n\t\tcase kVkRenderType_1_1_R: // blend: scr + dst, depth test\n\t\t\treturn kMaterialMode_BlendAdd;\n\t\t\tbreak;\n\t\tcase kVkRenderType_AT: // no blend, depth RW, alpha test\n\t\t\treturn kMaterialMode_AlphaTest;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tgEngine.Host_Error(\"Unexpected render type %d\\n\", render_type);\n\t}\n\n\treturn kMaterialMode_Opaque;\n}\n\n// Draws current studio model submodel\n// Can be called externally, i.e. from game dll.\n// Expects m_pStudioHeader, m_pSubModel, RI.currententity, etc to be already set up\nstatic void R_StudioDrawPoints( void ) {\n\tif( !m_pStudioHeader || !m_pSubModel || !m_pSubModel->nummesh)\n\t\treturn;\n\n\tASSERT(g_studio_current.bodypart_index >= 0);\n\n\t// Ideally, this \"get current entity and model\" stuff should happen early, when we're just starting to\n\t// draw this entity/model. However, call structure/graph is a bit weird: we start rendering in ref code,\n\t// but relevant states (transform, various headers) are updated only later, and potentially in game dll code.\n\t// So we're forced to do this later here, when it is guaranteed that all the relevant state has been set.\n\tif (!g_studio_current.entmodel) {\n\t\tg_studio_current.entmodel = studioEntityModelGet(RI.currententity);\n\t\tMatrix3x4_Copy(g_studio_current.entmodel->transform, g_studio.rotationmatrix);\n\t}\n\n\tASSERT(g_studio_current.bodypart_index >= 0);\n\tASSERT(g_studio_current.bodypart_index < g_studio_current.entmodel->bodyparts_count);\n\n\tr_studio_submodel_render_t *render_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index];\n\n\t// Submodels for bodyparts can potentially change at runtime\n\tif (!render_submodel || render_submodel->_.info->submodel_key != m_pSubModel) {\n\t\tif (render_submodel) {\n\t\t\t// This does happen in practice a lot. Shouldn't be a warning.\n\t\t\tDEBUG(\"Detected bodypart submodel change from %s to %s for model %s entity %p(%d)\", render_submodel->_.info->submodel_key->name, m_pSubModel->name, m_pStudioHeader->name, RI.currententity, RI.currententity->index);\n\n\t\t\tstudioSubmodelRenderModelRelease(render_submodel);\n\t\t\trender_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index] = NULL;\n\t\t}\n\n\t\tr_studio_submodel_info_t *const subinfo = studioModelFindSubmodelInfo();\n\t\tif (!subinfo) {\n\t\t\tERR(\"Submodel %s info not found for model %s, this should be impossible\", m_pSubModel->name, m_pStudioHeader->name);\n\t\t\treturn;\n\t\t}\n\n\t\trender_submodel = g_studio_current.entmodel->bodyparts[g_studio_current.bodypart_index] = studioSubmodelRenderModelAcquire(subinfo);\n\t\tASSERT(render_submodel);\n\t\tASSERT(render_submodel->_.info);\n\t}\n\n\tconst qboolean is_dynamic = render_submodel->_.info->is_dynamic;\n\n\tif (!render_submodel->geometries) {\n\t\tif (!studioSubmodelRenderInit(render_submodel, m_pSubModel, is_dynamic)) {\n\t\t\tERR(\"Unable to init studio submodel for %s/%d\", RI.currentmodel->name, g_studio_current.bodypart_index);\n\t\t\treturn;\n\t\t}\n\n\t\tDEBUG(\"Initialized studio submodel for %s // %s\", RI.currentmodel->name, render_submodel->_.info->submodel_key->name);\n\t} else if (is_dynamic) {\n\t\tif (!studioSubmodelRenderUpdate(render_submodel, m_pSubModel)) {\n\t\t\tERR(\"Unable to update studio submodel for %s/%d\", RI.currentmodel->name, g_studio_current.bodypart_index);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (is_dynamic)\n\t\t++g_studio_stats.submodels_dynamic;\n\telse\n\t\t++g_studio_stats.submodels_static;\n\n\tvec4_t color = {1, 1, 1, g_studio.blend};\n\tif (g_studio.rendermode2 == kRenderTransAdd)\n\t\tVector4Set(color, g_studio.blend, g_studio.blend, g_studio.blend, 1.f);\n\n\t// TODO r_model_draw_t.transform should be matrix3x4\n\tconst vk_render_type_e render_type = studioRenderModeToRenderType(RI.currententity->curstate.rendermode);\n\tconst material_mode_e material_mode = studioMaterialModeForRenderType(render_type);\n\tR_RenderModelDraw(&render_submodel->model, (r_model_draw_t){\n\t\t.render_type = render_type,\n\t\t.material_mode = material_mode,\n\t\t.material_flags = kMaterialFlag_CullBackFace_Bit, // TODO for transparent only?\n\t\t.color = &color,\n\t\t.transform = &g_studio_current.entmodel->transform,\n\t\t.prev_transform = &g_studio_current.entmodel->prev_transform,\n\t\t.override = {\n\t\t\t.material = NULL,\n\t\t\t.old_texture = -1,\n\t\t},\n\t});\n\n\t++g_studio_stats.submodels_total;\n}\n\nstatic void R_StudioSetRemapColors( int newTop, int newBottom )\n{\n\tif( gEngine.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom ))\n\t{\n\t\tm_fDoRemap = true;\n\t}\n}\n\nvoid R_StudioResetPlayerModels( void )\n{\n\tmemset( g_studio.player_models, 0, sizeof( g_studio.player_models ));\n}\n\nstatic player_info_t *pfnPlayerInfo( int index );\n\nstatic model_t *R_StudioSetupPlayerModel( int index )\n{\n\tplayer_info_t\t*info = gEngine.pfnPlayerInfo( index );\n\tplayer_model_t\t*state;\n\n\tstate = &g_studio.player_models[index];\n\n\t// g-cont: force for \"dev-mode\", non-local games and menu preview\n\tif(( gpGlobals->developer || !ENGINE_GET_PARM( PARM_LOCAL_GAME ) || !RI.drawWorld ) && info->model[0] )\n\t{\n\t\tif( Q_strcmp( state->name, info->model ))\n\t\t{\n\t\t\tQ_strncpy( state->name, info->model, sizeof( state->name ));\n\t\t\tstate->name[sizeof( state->name ) - 1] = 0;\n\n\t\t\tQ_snprintf( state->modelname, sizeof( state->modelname ), \"models/player/%s/%s.mdl\", info->model, info->model );\n\n\t\t\tif( gEngine.fsapi->FileExists( state->modelname, false ))\n\t\t\t\tstate->model = gEngine.Mod_ForName( state->modelname, false, true );\n\t\t\telse state->model = NULL;\n\n\t\t\tif( !state->model )\n\t\t\t\tstate->model = RI.currententity->model;\n\t\t}\n\t}\n\telse\n\t{\n\t\tif( state->model != RI.currententity->model )\n\t\t\tstate->model = RI.currententity->model;\n\t\tstate->name[0] = 0;\n\t}\n\n\treturn state->model;\n}\n\n/*\n================\nR_GetEntityRenderMode\n\ncheck for texture flags\n================\n*/\n/*\nstatic int R_GetEntityRenderMode( cl_entity_t *ent )\n{\n\tint\t\ti, opaque, trans;\n\tmstudiotexture_t\t*ptexture;\n\tcl_entity_t\t*oldent;\n\tmodel_t\t\t*model;\n\tstudiohdr_t\t*phdr;\n\n\toldent = RI.currententity;\n\tRI.currententity = ent;\n\n\tif( ent->player ) // check it for real playermodel\n\t\tmodel = R_StudioSetupPlayerModel( ent->curstate.number - 1 );\n\telse model = ent->model;\n\n\tRI.currententity = oldent;\n\n\tif(( phdr = gEngine.Mod_Extradata( mod_studio, model )) == NULL )\n\t{\n\t\tif( ent->curstate.rendermode == kRenderNormal )\n\t\t{\n\t\t\t// forcing to choose right sorting type\n\t\t\tif(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT ))\n\t\t\t\treturn kRenderTransAlpha;\n\t\t}\n\t\treturn ent->curstate.rendermode;\n\t}\n\tptexture = PTR_CAST(mstudiotexture_t, (byte *)phdr + phdr->textureindex);\n\n\tfor( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ )\n\t{\n\t\t// ignore chrome & additive it's just a specular-like effect\n\t\tif( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME ))\n\t\t\ttrans++;\n\t\telse opaque++;\n\t}\n\n\t// if model is more additive than opaque\n\tif( trans > opaque )\n\t\treturn kRenderTransAdd;\n\treturn ent->curstate.rendermode;\n}\n*/\n\n/*\n===============\nR_StudioClientEvents\n\n===============\n*/\nstatic void R_StudioClientEvents( void )\n{\n\tmstudioseqdesc_t\t*pseqdesc;\n\tmstudioevent_t\t*pevent;\n\tcl_entity_t\t*e = RI.currententity;\n\tint\t\ti, sequence;\n\tfloat\t\tend, start;\n\n\tif( g_studio.frametime == 0.0 )\n\t\treturn; // gamepaused\n\n\t// fill attachments with interpolated origin\n\tif( m_pStudioHeader->numattachments <= 0 )\n\t{\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] );\n\t\tMatrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] );\n\t}\n\n\tif( FBitSet( e->curstate.effects, EF_MUZZLEFLASH ))\n\t{\n\t\tdlight_t\t*el = gEngine.CL_AllocElight( 0 );\n\n\t\tClearBits( e->curstate.effects, EF_MUZZLEFLASH );\n\t\tVectorCopy( e->attachment[0], el->origin );\n\t\tel->die = gp_cl->time + 0.05f;\n\t\tel->color.r = 255;\n\t\tel->color.g = 192;\n\t\tel->color.b = 64;\n\t\tel->decay = 320;\n\t\tel->radius = 24;\n\t}\n\n\tsequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 );\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;\n\n\t// no events for this animation\n\tif( pseqdesc->numevents == 0 )\n\t\treturn;\n\n\tend = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );\n\tstart = end - e->curstate.framerate * gp_host->frametime * pseqdesc->fps;\n\tpevent = PTR_CAST(mstudioevent_t, (byte *)m_pStudioHeader + pseqdesc->eventindex);\n\n\tif( e->latched.sequencetime == e->curstate.animtime )\n\t{\n\t\tif( !FBitSet( pseqdesc->flags, STUDIO_LOOPING ))\n\t\t\tstart = -0.01f;\n\t}\n\n\tfor( i = 0; i < pseqdesc->numevents; i++ )\n\t{\n\t\t// ignore all non-client-side events\n\t\tif( pevent[i].event < EVENT_CLIENT )\n\t\t\tcontinue;\n\n\t\tif( (float)pevent[i].frame > start && pevent[i].frame <= end )\n\t\t\tgEngine.pfnStudioEvent( &pevent[i], e );\n\t}\n}\n\nstatic int R_StudioGetForceFaceFlags( void )\n{\n\treturn g_nForceFaceFlags;\n}\n\nstatic void R_StudioSetForceFaceFlags( int flags )\n{\n\tg_nForceFaceFlags = flags;\n}\n\nstatic void R_StudioSetHeader( studiohdr_t *pheader )\n{\n\tm_pStudioHeader = pheader;\n\tm_fDoRemap = false;\n}\n\nstatic void R_StudioSetRenderModel( model_t *model )\n{\n\tRI.currentmodel = model;\n}\n\nstatic void R_StudioSetupRenderer( int rendermode )\n{\n\tstudiohdr_t\t*phdr = m_pStudioHeader;\n\tint\t\ti;\n\n\tif( rendermode > kRenderTransAdd ) rendermode = 0;\n\tg_studio.rendermode = bound( 0, rendermode, kRenderTransAdd );\n\n\t/* FIXME VK\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );\n\tpglDisable( GL_ALPHA_TEST );\n\tpglShadeModel( GL_SMOOTH );\n\t*/\n\n\t// a point to setup local to world transform for boneweighted models\n\tif( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO ))\n\t{\n\t\t// NOTE: extended boneinfo goes immediately after bones\n\t\tmstudioboneinfo_t *boneinfo = PTR_CAST(mstudioboneinfo_t, (byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t ));\n\n\t\tfor( i = 0; i < phdr->numbones; i++ )\n\t\t\tMatrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone );\n\t}\n}\n\nstatic void R_StudioRestoreRenderer( void )\n{\n\t/* FIXME VK\n\tif( g_studio.rendermode != kRenderNormal )\n\t\tpglDisable( GL_BLEND );\n\n\tpglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );\n\tpglShadeModel( GL_FLAT );\n\t*/\n\tm_fDoRemap = false;\n}\n\nstatic void R_StudioSetChromeOrigin( void )\n{\n\tVectorCopy( g_camera.vieworg, g_studio.chrome_origin );\n}\n\nstatic int pfnIsHardware( void )\n{\n\treturn 3;\t// 0 is Software, 1 is OpenGL, 2 is Direct3D, 3 is Vulkan\n}\n\nstatic void R_StudioDrawPointsShadow( void )\n{\n\t/*float\t\t*av, height;*/\n\t/*float\t\tvec_x, vec_y;*/\n\t//mstudiomesh_t\t*pmesh;\n\t//vec3_t\t\tpoint;\n\tint\t\t/*i,*/ k;\n\n\tif( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW ))\n\t\treturn;\n\n\t/* FIXME VK\n\tif( glState.stencilEnabled )\n\t\tpglEnable( GL_STENCIL_TEST );\n\t*/\n\n\t/*height = g_studio.lightspot[2] + 1.0f;*/\n\t/*vec_x = -g_studio.lightvec[0] * 8.0f;*/\n\t/*vec_y = -g_studio.lightvec[1] * 8.0f;*/\n\n\tfor( k = 0; k < m_pSubModel->nummesh; k++ )\n\t{\n\t\t//short\t*ptricmds;\n\n\t\t//pmesh = PTR_CAST(mstudiomesh_t, (byte *)m_pStudioHeader + m_pSubModel->meshindex) + k;\n\t\t//ptricmds = PTR_CAST(short, (byte *)m_pStudioHeader + pmesh->triindex);\n\n\t\t/* FIXME VK\n\t\tr_stats.c_studio_polys += pmesh->numtris;\n\n\t\twhile(( i = *( ptricmds++ )))\n\t\t{\n\t\t\tif( i < 0 )\n\t\t\t{\n\t\t\t\tpglBegin( GL_TRIANGLE_FAN );\n\t\t\t\ti = -i;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpglBegin( GL_TRIANGLE_STRIP );\n\t\t\t}\n\n\n\t\t\tfor( ; i > 0; i--, ptricmds += 4 )\n\t\t\t{\n\t\t\t\tav = g_studio.verts[ptricmds[0]];\n\t\t\t\tpoint[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] ));\n\t\t\t\tpoint[2] = g_studio.lightspot[2] + 1.0f;\n\n\t\t\t\tpglVertex3fv( point );\n\t\t\t}\n\n\t\t\tpglEnd();\n\t\t}\n\t\t*/\n\t}\n\n\t/* FIXME VK\n\tif( glState.stencilEnabled )\n\t\tpglDisable( GL_STENCIL_TEST );\n\t*/\n}\n\nstatic void GL_StudioSetRenderMode( int rendermode )\n{\n\tg_studio.rendermode2 = rendermode;\n}\n\n/*\n===============\nGL_StudioDrawShadow\n\ng-cont: don't modify this code it's 100% matched with\noriginal GoldSrc code and used in some mods to enable\nstudio shadows with some asm tricks\n===============\n*/\nstatic void GL_StudioDrawShadow( void )\n{\n\t/* FIXME VK\n\tpglDepthMask( GL_TRUE );\n\t*/\n\n\tif( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT ))\n\t{\n\t\t//float\tcolor = 1.0f - (g_studio.blend * 0.5f);\n\n\t/* FIXME VK\n\t\tpglDisable( GL_TEXTURE_2D );\n\t\tpglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );\n\t\tpglEnable( GL_BLEND );\n\t\tpglColor4f( 0.0f, 0.0f, 0.0f, 1.0f - color );\n\n\t\tpglDepthFunc( GL_LESS );\n\t\t*/\n\t\tR_StudioDrawPointsShadow();\n\t/* FIXME VK\n\t\tpglDepthFunc( GL_LEQUAL );\n\n\t\tpglEnable( GL_TEXTURE_2D );\n\t\tpglDisable( GL_BLEND );\n\t\tpglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );\n\t\tpglShadeModel( GL_SMOOTH );\n\t\t*/\n\t}\n}\n\nstatic void R_StudioRenderFinal( void )\n{\n\tint\ti, rendermode;\n\n\trendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode;\n\tR_StudioSetupRenderer( rendermode );\n\n\tVK_RenderDebugLabelBegin( RI.currentmodel->name );\n\tfor( i = 0; i < m_pStudioHeader->numbodyparts; i++ )\n\t{\n\t\tR_StudioSetupModel( i, (void**)&m_pBodyPart, (void**)&m_pSubModel );\n\n\t\t// TODO does literally nothing GL_StudioSetRenderMode( rendermode );\n\t\tR_StudioDrawPoints();\n\t\tGL_StudioDrawShadow();\n\t}\n\tVK_RenderDebugLabelEnd();\n\n\tR_StudioRestoreRenderer();\n}\n\nstatic void R_StudioRenderModel( void )\n{\n\tR_StudioSetChromeOrigin();\n\tR_StudioSetForceFaceFlags( 0 );\n\n\t/* FIXME VK\n\tif( RI.currententity->curstate.renderfx == kRenderFxGlowShell )\n\t{\n\t\tRI.currententity->curstate.renderfx = kRenderFxNone;\n\n\t\tR_StudioRenderFinal( );\n\n\t\tR_StudioSetForceFaceFlags( STUDIO_NF_CHROME );\n\t\tTriSpriteTexture( R_GetChromeSprite(), 0 );\n\t\tRI.currententity->curstate.renderfx = kRenderFxGlowShell;\n\n\t\tR_StudioRenderFinal( );\n\t}\n\telse */\n\t{\n\t\tR_StudioRenderFinal( );\n\t}\n}\n\nstatic void R_StudioEstimateGait( entity_state_t *pplayer )\n{\n\tvec3_t\test_velocity;\n\tfloat\tdt;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tif( dt == 0.0f) // FIXME VK || m_pPlayerInfo->renderframe == tr.realframecount )\n\t{\n\t\tm_flGaitMovement = 0;\n\t\treturn;\n\t}\n\n\tVectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity );\n\tVectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin );\n\tm_flGaitMovement = VectorLength( est_velocity );\n\n\tif( dt <= 0.0f || m_flGaitMovement / dt < 5.0f )\n\t{\n\t\tm_flGaitMovement = 0.0f;\n\t\test_velocity[0] = 0.0f;\n\t\test_velocity[1] = 0.0f;\n\t}\n\n\tif( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f )\n\t{\n\t\tfloat\tflYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\n\t\tflYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360;\n\t\tif( flYawDiff > 180.0f ) flYawDiff -= 360.0f;\n\t\tif( flYawDiff < -180.0f ) flYawDiff += 360.0f;\n\n\t\tif( dt < 0.25f )\n\t\t\tflYawDiff *= dt * 4.0f;\n\t\telse flYawDiff *= dt;\n\n\t\tm_pPlayerInfo->gaityaw += flYawDiff;\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360;\n\n\t\tm_flGaitMovement = 0.0f;\n\t}\n\telse\n\t{\n\t\tm_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F );\n\t\tif( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f;\n\t\tif( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f;\n\t}\n\n}\n\nstatic void R_StudioProcessGait( entity_state_t *pplayer )\n{\n\tmstudioseqdesc_t\t*pseqdesc;\n\tint\t\tiBlend;\n\tfloat\t\tdt, flYaw; // view direction relative to movement\n\n\tif( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq )\n\t\tRI.currententity->curstate.sequence = 0;\n\n\tdt = bound( 0.0f, g_studio.frametime, 1.0f );\n\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence;\n\n\tR_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] );\n\n\tRI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH];\n\tRI.currententity->curstate.blending[0] = iBlend;\n\tRI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0];\n\tRI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0];\n\tR_StudioEstimateGait( pplayer );\n\n\t// calc side to side turning\n\tflYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;\n\tflYaw = flYaw - (int)(flYaw / 360) * 360;\n\tif( flYaw < -180.0f ) flYaw = flYaw + 360.0f;\n\tif( flYaw > 180.0f ) flYaw = flYaw - 360.0f;\n\n\tif( flYaw > 120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw - 180.0f;\n\t}\n\telse if( flYaw < -120.0f )\n\t{\n\t\tm_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f;\n\t\tm_flGaitMovement = -m_flGaitMovement;\n\t\tflYaw = flYaw + 180.0f;\n\t}\n\n\t// adjust torso\n\tRI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);\n\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\tRI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw;\n\tif( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f;\n\tRI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW];\n\n\tif( pplayer->gaitsequence >= m_pStudioHeader->numseq )\n\t\tpplayer->gaitsequence = 0;\n\n\tpseqdesc = PTR_CAST(mstudioseqdesc_t, (byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence;\n\n\t// calc gait frame\n\tif( pseqdesc->linearmovement[0] > 0 )\n\t\tm_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes;\n\telse m_pPlayerInfo->gaitframe += pseqdesc->fps * dt;\n\n\t// do modulo\n\tm_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes;\n\tif( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes;\n}\n\nstatic int R_StudioDrawPlayer( int flags, entity_state_t *pplayer )\n{\n\tint\tm_nPlayerIndex;\n\talight_t\tlighting;\n\tvec3_t\tdir;\n\n\tm_nPlayerIndex = pplayer->number - 1;\n\n\tif( m_nPlayerIndex < 0 || m_nPlayerIndex >= gp_cl->maxclients )\n\t\treturn 0;\n\n\tRI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex );\n\tif( RI.currentmodel == NULL )\n\t\treturn 0;\n\n\tR_StudioSetHeader((studiohdr_t *)gEngine.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tif( pplayer->gaitsequence )\n\t{\n\t\tvec3_t orig_angles;\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tVectorCopy( RI.currententity->angles, orig_angles );\n\n\t\tR_StudioProcessGait( pplayer );\n\n\t\tm_pPlayerInfo->gaitsequence = pplayer->gaitsequence;\n\t\tm_pPlayerInfo = NULL;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t\tVectorCopy( orig_angles, RI.currententity->angles );\n\t}\n\telse\n\t{\n\t\tRI.currententity->curstate.controller[0] = 127;\n\t\tRI.currententity->curstate.controller[1] = 127;\n\t\tRI.currententity->curstate.controller[2] = 127;\n\t\tRI.currententity->curstate.controller[3] = 127;\n\t\tRI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];\n\t\tRI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];\n\t\tRI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];\n\t\tRI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\t\tm_pPlayerInfo->gaitsequence = 0;\n\n\t\tR_StudioSetUpTransform( RI.currententity );\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\t// FIXME VK r_stats.c_studio_models_drawn++;\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\tR_StudioSetupBones( RI.currententity );\n\tR_StudioSaveBones( );\n\n\t// FIXME VK m_pPlayerInfo->renderframe = tr.realframecount;\n\tm_pPlayerInfo = NULL;\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = globals.entities + ( RI.currententity->index );\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\tif( cl_himodels->value && RI.currentmodel != RI.currententity->model  )\n\t\t{\n\t\t\t// show highest resolution multiplayer model\n\t\t\tRI.currententity->curstate.body = 255;\n\t\t}\n\n\t\tif( !( !gpGlobals->developer && gp_cl->maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model ))\n\t\t\tRI.currententity->curstate.body = 1; // force helmet\n\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\tm_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );\n\n\t\t// get remap colors\n\t\tg_nTopColor = m_pPlayerInfo->topcolor;\n\t\tg_nBottomColor = m_pPlayerInfo->bottomcolor;\n\n\t\tif( g_nTopColor < 0 ) g_nTopColor = 0;\n\t\tif( g_nTopColor > 360 ) g_nTopColor = 360;\n\t\tif( g_nBottomColor < 0 ) g_nBottomColor = 0;\n\t\tif( g_nBottomColor > 360 ) g_nBottomColor = 360;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel( );\n\t\tm_pPlayerInfo = NULL;\n\n\t\tif( pplayer->weaponmodel )\n\t\t{\n\t\t\tcl_entity_t\tsaveent = *RI.currententity;\n\t\t\tmodel_t\t\t*pweaponmodel = gp_cl->models[pplayer->weaponmodel];\n\n\t\t\tm_pStudioHeader = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, pweaponmodel );\n\n\t\t\tR_StudioMergeBones( RI.currententity, pweaponmodel );\n\t\t\tR_StudioSetupLighting( &lighting );\n\t\t\tR_StudioRenderModel( );\n\t\t\tR_StudioCalcAttachments( );\n\n\t\t\t*RI.currententity = saveent;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nstatic entity_state_t *R_StudioGetPlayerState( int index );\n\nstatic int R_StudioDrawModel( int flags )\n{\n\talight_t\tlighting;\n\tvec3_t\tdir;\n\n\tif( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer )\n\t{\n\t\tentity_state_t\tdeadplayer;\n\t\tint\t\tresult;\n\n\t\tif( RI.currententity->curstate.renderamt <= 0 ||\n\t\t\tRI.currententity->curstate.renderamt > gp_cl->maxclients )\n\t\t\treturn 0;\n\n\t\t// get copy of player\n\t\tdeadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 );\n\n\t\t// clear weapon, movement state\n\t\tdeadplayer.number = RI.currententity->curstate.renderamt;\n\t\tdeadplayer.weaponmodel = 0;\n\t\tdeadplayer.gaitsequence = 0;\n\n\t\tdeadplayer.movetype = MOVETYPE_NONE;\n\t\tVectorCopy( RI.currententity->curstate.angles, deadplayer.angles );\n\t\tVectorCopy( RI.currententity->curstate.origin, deadplayer.origin );\n\n\t\tg_studio.interpolate = false;\n\t\tresult = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player\n\t\tg_studio.interpolate = true;\n\n\t\treturn result;\n\t}\n\n\tR_StudioSetHeader((studiohdr_t *)gEngine.Mod_Extradata( mod_studio, RI.currentmodel ));\n\n\tR_StudioSetUpTransform( RI.currententity );\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\t// see if the bounding box lets us trivially reject, also sets\n\t\tif( !R_StudioCheckBBox( ))\n\t\t\treturn 0;\n\n\t\tg_studio.framecount++; // render data cache cookie\n\n\t\tif( m_pStudioHeader->numbodyparts == 0 )\n\t\t\treturn 1;\n\t}\n\n\tif( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW )\n\t\tR_StudioMergeBones( RI.currententity, RI.currentmodel );\n\telse R_StudioSetupBones( RI.currententity );\n\n\tR_StudioSaveBones();\n\n\tif( flags & STUDIO_EVENTS )\n\t{\n\t\tR_StudioCalcAttachments( );\n\t\tR_StudioClientEvents( );\n\n\t\t// copy attachments into global entity array\n\t\tif( RI.currententity->index > 0 )\n\t\t{\n\t\t\tcl_entity_t *ent = globals.entities + RI.currententity->index;\n\t\t\tmemcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );\n\t\t}\n\t}\n\n\tif( flags & STUDIO_RENDER )\n\t{\n\t\tlighting.plightvec = dir;\n\t\tR_StudioDynamicLight( RI.currententity, &lighting );\n\n\t\tR_StudioEntityLight( &lighting );\n\n\t\t// model and frame independant\n\t\tR_StudioSetupLighting( &lighting );\n\n\t\t// get remap colors\n\t\tg_nTopColor = RI.currententity->curstate.colormap & 0xFF;\n\t\tg_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8;\n\n\t\tR_StudioSetRemapColors( g_nTopColor, g_nBottomColor );\n\n\t\tR_StudioRenderModel();\n\t}\n\n\treturn 1;\n}\n\nstatic void R_StudioDrawModelInternal( cl_entity_t *e, int flags )\n{\n\tVK_RenderDebugLabelBegin( e->model->name );\n\n\t// Mark this a new model to draw\n\tg_studio_current.entmodel = NULL;\n\tg_studio_current.bodypart_index = -1;\n\n\t++g_studio_stats.models_count;\n\n\tif( !RI.drawWorld )\n\t{\n\t\tif( e->player )\n\t\t\tR_StudioDrawPlayer( flags, &e->curstate );\n\t\telse\n\t\t\tR_StudioDrawModel( flags );\n\t}\n\telse\n\t{\n\t\t// select the properly method\n\t\tif( e->player )\n\t\t\tpStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));\n\t\telse\n\t\t\tpStudioDraw->StudioDrawModel( flags );\n\t}\n\n\tif (g_studio_current.entmodel) {\n\t\tMatrix4x4_Copy(g_studio_current.entmodel->prev_transform, g_studio_current.entmodel->transform);\n\t}\n\n\t// Reset current state, no drawing should happen outside of this function\n\tg_studio_current.entmodel = NULL;\n\tg_studio_current.bodypart_index = -1;\n\n\tVK_RenderDebugLabelEnd();\n}\n\nstatic void R_DrawStudioModel( cl_entity_t *e )\n{\n\t/* FIXME VK\n\t * RP_ENVVIEW is never set even in gl/soft renderers\n\tif( FBitSet( RI.params, RP_ENVVIEW ))\n\t\treturn;\n\t*/\n\n\tR_StudioSetupTimings();\n\n\tif( e->player )\n\t{\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );\n\t}\n\telse\n\t{\n\t\tif( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 )\n\t\t{\n\t\t\tcl_entity_t *parent = globals.entities + e->curstate.aiment;\n\n\t\t\tif( parent && parent->model && parent->model->type == mod_studio )\n\t\t\t{\n\t\t\t\tRI.currententity = parent;\n\t\t\t\tR_StudioDrawModelInternal( RI.currententity, 0 );\n\t\t\t\tVectorCopy( parent->curstate.origin, e->curstate.origin );\n\t\t\t\tVectorCopy( parent->origin, e->origin );\n\t\t\t\tRI.currententity = e;\n\t\t\t}\n\t\t}\n\n\t\tR_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );\n\t}\n}\n\nvoid R_RunViewmodelEvents( void )\n{\n\tint\ti;\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t/* FIXME VK\n\t * RP_NORMALPASS is noop?\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\t*/\n\n\tif( ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\tRI.currententity = globals.viewent;\n\n\tif( !RI.currententity->model || RI.currententity->model->type != mod_studio )\n\t\treturn;\n\n\tR_StudioSetupTimings();\n\n\tfor( i = 0; i < 4; i++ )\n\t\tVectorCopy( gp_cl->simorg, RI.currententity->attachment[i] );\n\tRI.currentmodel = RI.currententity->model;\n\n\tR_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS );\n}\n\nstatic void R_GatherPlayerLight( void )\n{\n\t//cl_entity_t\t*view = globals.viewent;\n\t//colorVec\t\tc;\n\n\t/* FIXME VK\n\ttr.ignore_lightgamma = true;\n\tc = R_LightPoint( view->origin );\n\ttr.ignore_lightgamma = false;\n\tgEngine.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 );\n\t*/\n}\n\nvoid R_DrawViewModel( void )\n{\n\tcl_entity_t\t*view = globals.viewent;\n\n\tR_GatherPlayerLight();\n\n\tif( r_drawviewmodel->value == 0 )\n\t\treturn;\n\n\tif( ENGINE_GET_PARM( PARM_THIRDPERSON ))\n\t\treturn;\n\n\t/* FIXME VK\n\t * RP_NORMALPASS is noop?\n\t// ignore in thirdperson, camera view or client is died\n\tif( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\tg_studio.blend = CL_FxBlend( view ) / 255.0f;\n\tif( !R_ModelOpaque( view->curstate.rendermode ) && g_studio.blend <= 0.0f )\n\t\treturn; // invisible ?\n\t*/\n\n\tif( ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())\n\t\treturn;\n\n\tRI.currententity = view;\n\n\tif( !RI.currententity->model )\n\t\treturn;\n\n\tRI.currentmodel = RI.currententity->model;\n\n\t/* FIXME VK\n\t// adjust the depth range to prevent view model from poking into walls\n\tpglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin ));\n\n\t// backface culling for left-handed weapons\n\tif( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull )\n\t{\n\t\ttr.fFlipViewModel = true;\n\t\tpglFrontFace( GL_CW );\n\t}\n\t*/\n\n\tswitch( RI.currententity->model->type )\n\t{\n\tcase mod_alias:\n\t\t/* FIXME VK\n\t\tR_DrawAliasModel( RI.currententity );\n\t\tbreak;\n\t\t*/\n\tcase mod_studio:\n\t\tR_StudioSetupTimings();\n\t\tR_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER );\n\t\tbreak;\n\tcase mod_sprite:\n\tcase mod_brush:\n\tcase mod_bad:\n\t\t// Complain, impossible\n\t\tbreak;\n\t}\n\n\tRI.currententity = NULL;\n\tRI.currentmodel = NULL;\n\n\t/* FIXME VK\n\t// restore depth range\n\tpglDepthRange( gldepthmin, gldepthmax );\n\n\t// backface culling for left-handed weapons\n\tif( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull )\n\t{\n\t\ttr.fFlipViewModel = false;\n\t\tpglFrontFace( GL_CCW );\n\t}\n\t*/\n}\n\n/*\n====================\nR_StudioLoadTexture\n\nload model texture with unique name\n====================\n*/\nstatic void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture )\n{\n\tsize_t\t\tsize;\n\tint\t\tflags = 0;\n\tchar\t\ttexname[128], name[128], mdlname[128];\n\ttexture_t\t\t*tx = NULL;\n\n\tif( ptexture->flags & STUDIO_NF_NORMALMAP )\n\t\tflags |= (TF_NORMALMAP);\n\n\t// store some textures for remapping\n\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ) || !Q_strnicmp( ptexture->name, \"remap\", 5 ))\n\t{\n\t\tint\ti, size;\n\t\tchar\tval[6];\n\t\tbyte\t*pixels;\n\n\t\ti = mod->numtextures;\n\t\tmod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* ));\n\t\tsize = ptexture->width * ptexture->height + 768;\n\t\ttx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size );\n\t\tmod->textures[i] = tx;\n\n\t\t// store ranges into anim_min, anim_max etc\n\t\tif( !Q_strnicmp( ptexture->name, \"DM_Base\", 7 ))\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_Base\", sizeof( tx->name ));\n\t\t\ttx->anim_min = PLATE_HUE_START; // topcolor start\n\t\t\ttx->anim_max = PLATE_HUE_END; // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\ttx->anim_total = SUIT_HUE_END;// bottomcolor end\n\t\t}\n\t\telse\n\t\t{\n\t\t\tQ_strncpy( tx->name, \"DM_User\", sizeof( tx->name )); // custom remapped\n\t\t\tQ_strncpy( val, ptexture->name + 7, 4 );\n\t\t\ttx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start\n\t\t\tQ_strncpy( val, ptexture->name + 11, 4 );\n\t\t\ttx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end\n\t\t\t// bottomcolor start always equal is (topcolor end + 1)\n\t\t\tQ_strncpy( val, ptexture->name + 15, 4 );\n\t\t\ttx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end\n\t\t}\n\n\t\ttx->width = ptexture->width;\n\t\ttx->height = ptexture->height;\n\n\t\t// the pixels immediately follow the structures\n\t\tpixels = (byte *)phdr + ptexture->index;\n\t\tmemcpy( tx+1, pixels, size );\n\n\t\tptexture->flags |= STUDIO_NF_COLORMAP;\t// yes, this is colormap image\n\t\tflags |= TF_FORCE_COLOR;\n\n\t\tmod->numtextures++;\t// done\n\t}\n\n\tQ_strncpy( mdlname, mod->name, sizeof( mdlname ));\n\tCOM_FileBase( ptexture->name, name, sizeof( name ));\n\tCOM_StripExtension( mdlname );\n\n\tif( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS ))\n\t\tSetBits( flags, TF_NOMIPMAP );\n\n\t// NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it\n\t//ptexture->index = (int)((byte *)phdr) + ptexture->index;\n\tgEngine.Image_SetMDLPointer((byte *)phdr + ptexture->index);\n\tsize = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768;\n\n\tif( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED ))\n\t\tflags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing\n\n\t// build the texname\n\tQ_snprintf( texname, sizeof( texname ), \"#%s/%s.mdl\", mdlname, name );\n\tptexture->index = R_TextureUploadFromFile( texname, (byte *)ptexture, size, flags );\n\n\tif( !ptexture->index )\n\t{\n\t\tptexture->index = tglob.defaultTexture;\n\t}\n\telse if( tx )\n\t{\n\t\t// duplicate texnum for easy acess\n\t\ttx->gl_texturenum = ptexture->index;\n\t}\n}\n\nvoid Mod_StudioLoadTextures( model_t *mod, void *data )\n{\n\tstudiohdr_t\t*phdr = (studiohdr_t *)data;\n\tmstudiotexture_t\t*ptexture;\n\tint\t\ti;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = PTR_CAST(mstudiotexture_t, ((byte *)phdr) + phdr->textureindex);\n\tif( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS )\n\t{\n\t\tfor( i = 0; i < phdr->numtextures; i++ )\n\t\t\tR_StudioLoadTexture( mod, phdr, &ptexture[i] );\n\t}\n}\n\nvoid Mod_StudioUnloadTextures( void *data )\n{\n\tstudiohdr_t\t*phdr = (studiohdr_t *)data;\n\tmstudiotexture_t\t*ptexture;\n\tint\t\ti;\n\n\tif( !phdr )\n\t\treturn;\n\n\tptexture = PTR_CAST(mstudiotexture_t, ((byte *)phdr) + phdr->textureindex);\n\n\t// release all textures\n\tfor( i = 0; i < phdr->numtextures; i++ )\n\t{\n\t\tif( ptexture[i].index == tglob.defaultTexture )\n\t\t\tcontinue;\n\t\tR_TextureFree( ptexture[i].index );\n\t}\n}\n\nstatic cl_entity_t *pfnGetCurrentEntity( void )\n{\n\treturn RI.currententity;\n}\n\nstatic player_info_t *pfnPlayerInfo( int index )\n{\n\tif( !RI.drawWorld )\n\t\tindex = -1;\n\n\treturn gEngine.pfnPlayerInfo( index );\n}\n\nstatic model_t *pfnMod_ForName( const char *model, int crash )\n{\n\treturn gEngine.Mod_ForName( model, crash, false );\n}\n\nstatic entity_state_t *R_StudioGetPlayerState( int index )\n{\n\tif( !RI.drawWorld )\n\t\treturn &RI.currententity->curstate;\n\n\treturn gEngine.pfnGetPlayerState( index );\n}\n\nstatic cl_entity_t *pfnGetViewEntity( void )\n{\n\treturn globals.viewent;\n}\n\nstatic void pfnGetEngineTimes( int *framecount, double *current, double *old )\n{\n\t// TODO is framecount enough? Should it be \"REAL\" framecount?\n\t/* if( framecount ) *framecount = tr.realframecount; */\n\tif( framecount ) *framecount = g_studio.framecount;\n\tif( current ) *current = gp_cl->time;\n\tif( old ) *old =   gp_cl->oldtime;\n}\n\nstatic void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv )\n{\n\tif( origin ) VectorCopy( g_camera.vieworg, origin );\n\tif( forwardv ) VectorCopy( g_camera.vforward, forwardv );\n\tif( rightv ) VectorCopy( g_camera.vright, rightv );\n\tif( upv ) VectorCopy( g_camera.vup, upv );\n}\n\nstatic model_t *pfnModelHandle( int modelindex )\n{\n\treturn gp_cl->models[modelindex];\n}\n\nstatic void *pfnMod_CacheCheck( struct cache_user_s *c )\n{\n\treturn gEngine.Mod_CacheCheck( c );\n}\n\nstatic void *pfnMod_StudioExtradata( model_t *mod )\n{\n\treturn gEngine.Mod_Extradata( mod_studio, mod );\n}\n\nstatic void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu )\n{\n\tgEngine.Mod_LoadCacheFile( path, cu );\n}\n\nstatic cvar_t *pfnGetCvarPointer( const char *name )\n{\n\treturn (cvar_t*)gEngine.pfnGetCvarPointer( name, 0 );\n}\n\nstatic void *pfnMod_Calloc( int number, size_t size )\n{\n\treturn gEngine.Mod_Calloc( number, size );\n}\n\nstatic void R_StudioDrawHulls( void )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic void R_StudioDrawAbsBBox( void )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic void R_StudioDrawBones( void )\n{\n\tPRINT_NOT_IMPLEMENTED();\n}\n\nstatic void pfnGetModelCounters( int **s, int **a )\n{\n\t*s = &g_studio.framecount;\n\t*a = &g_studio_stats.models_count;\n}\n\nstatic void pfnGetAliasScale( float *x, float *y )\n{\n\tif( x ) *x = 1.0f;\n\tif( y ) *y = 1.0f;\n}\n\nstatic float ****pfnStudioGetBoneTransform( void )\n{\n\treturn PTR_CAST(float ***, g_studio.bonestransform);\n}\n\nstatic float ****pfnStudioGetLightTransform( void )\n{\n\treturn PTR_CAST(float ***, g_studio.lighttransform);\n}\n\nstatic float ***pfnStudioGetAliasTransform( void )\n{\n\treturn NULL;\n}\n\nstatic float ***pfnStudioGetRotationMatrix( void )\n{\n\treturn PTR_CAST(float **, g_studio.rotationmatrix);\n}\n\nstatic engine_studio_api_t gStudioAPI =\n{\n\tpfnMod_Calloc,\n\tpfnMod_CacheCheck,\n\tpfnMod_LoadCacheFile,\n\tpfnMod_ForName,\n\tpfnMod_StudioExtradata,\n\tpfnModelHandle,\n\tpfnGetCurrentEntity,\n\tpfnPlayerInfo,\n\tR_StudioGetPlayerState,\n\tpfnGetViewEntity,\n\tpfnGetEngineTimes,\n\tpfnGetCvarPointer,\n\tpfnGetViewInfo,\n\tR_GetChromeSprite,\n\tpfnGetModelCounters,\n\tpfnGetAliasScale,\n\tpfnStudioGetBoneTransform,\n\tpfnStudioGetLightTransform,\n\tpfnStudioGetAliasTransform,\n\tpfnStudioGetRotationMatrix,\n\tR_StudioSetupModel,\n\tR_StudioCheckBBox,\n\tR_StudioDynamicLight,\n\tR_StudioEntityLight,\n\tR_StudioSetupLighting,\n\tR_StudioDrawPoints,\n\tR_StudioDrawHulls,\n\tR_StudioDrawAbsBBox,\n\tR_StudioDrawBones,\n\t(void*)R_StudioSetupSkin,\n\tR_StudioSetRemapColors,\n\tR_StudioSetupPlayerModel,\n\tR_StudioClientEvents,\n\tR_StudioGetForceFaceFlags,\n\tR_StudioSetForceFaceFlags,\n\t(void*)R_StudioSetHeader,\n\tR_StudioSetRenderModel,\n\tR_StudioSetupRenderer,\n\tR_StudioRestoreRenderer,\n\tR_StudioSetChromeOrigin,\n\tpfnIsHardware,\n\tGL_StudioDrawShadow,\n\tGL_StudioSetRenderMode,\n\tR_StudioSetRenderamt,\n\tR_StudioSetCullState,\n\tR_StudioRenderShadow,\n};\n\nstatic r_studio_interface_t gStudioDraw =\n{\n\tSTUDIO_INTERFACE_VERSION,\n\tR_StudioDrawModel,\n\tR_StudioDrawPlayer,\n};\n\nvoid CL_InitStudioAPI( void )\n{\n\tpStudioDraw = &gStudioDraw;\n\n\t// trying to grab them from client.dll\n\tcl_righthand = gEngine.pfnGetCvarPointer( \"cl_righthand\", 0 );\n\n\t// Xash will be used internal StudioModelRenderer\n\tif( gEngine.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI ))\n\t\treturn;\n\n\t// NOTE: we always return true even if game interface was not correct\n\t// because we need Draw our StudioModels\n\t// just restore pointer to builtin function\n\tpStudioDraw = &gStudioDraw;\n}\n\nvoid VK_StudioInit( void )\n{\n\tMatrix3x4_LoadIdentity( g_studio.rotationmatrix );\n\n\t// g-cont. cvar disabled by Valve\n//\tgEngine.Cvar_RegisterVariable( &r_shadows );\n\n\tg_studio.interpolate = true;\n\tg_studio.framecount = 0;\n\tm_fDoRemap = false;\n\n\tR_SPEEDS_COUNTER(g_studio_stats.models_count, \"models\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_studio_stats.submodels_total, \"submodels_total\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_studio_stats.submodels_static, \"submodels_static\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_studio_stats.submodels_dynamic, \"submodels_dynamic\", kSpeedsMetricCount);\n\n\tVK_StudioModelInit();\n}\n\nvoid VK_StudioShutdown( void )\n{\n\tR_StudioCacheClear();\n}\n\nvoid VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend )\n{\n\tRI.currententity = ent;\n\tRI.currentmodel = ent->model;\n\tRI.drawWorld = true;\n\n\tg_studio.blend = blend;\n\n\tR_DrawStudioModel( ent );\n\n\tRI.currentmodel = NULL;\n\tRI.currententity = NULL;\n}\n"
  },
  {
    "path": "ref/vk/vk_studio.h",
    "content": "#pragma once\n\n#include \"vk_common.h\"\n\nstruct ref_viewpass_s;\nstruct draw_list_s;\nstruct model_s;\n\nvoid VK_StudioInit( void );\nvoid VK_StudioShutdown( void );\n\nvoid Mod_StudioLoadTextures( model_t *mod, void *data );\nvoid Mod_StudioUnloadTextures( void *data );\n\nvoid VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend );\n\nvoid R_RunViewmodelEvents( void );\nvoid R_DrawViewModel( void );\n\nvoid CL_InitStudioAPI( void );\n\nfloat R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time );\nvoid R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles );\n\nstruct r_studio_model_info_s;\nconst struct r_studio_model_info_s *R_StudioModelPreload(model_t *mod);\n\nvoid R_StudioCacheClear( void );\n\nvoid R_StudioResetPlayerModels( void );\n"
  },
  {
    "path": "ref/vk/vk_studio_model.c",
    "content": "#include \"vk_studio_model.h\"\n#include \"r_speeds.h\"\n#include \"vk_logs.h\"\n#include \"vk_studio.h\"\n\n#include \"xash3d_mathlib.h\"\n\n#define MODULE_NAME \"studio\"\n#define LOG_MODULE studio\n\ntypedef struct {\n\tconst studiohdr_t *studio_header_key;\n\tr_studio_model_info_t info;\n} r_studio_model_info_entry_t;\n\nstatic struct {\n#define MAX_STUDIO_MODELS 256\n\tr_studio_model_info_entry_t models[MAX_STUDIO_MODELS];\n\tint models_count;\n\n\tint submodels_cached_dynamic;\n\tint submodels_cached_static;\n} g_studio_cache;\n\nstatic void studioRenderSubmodelDestroy( r_studio_submodel_render_t *submodel ) {\n\tR_RenderModelDestroy(&submodel->model);\n\tR_GeometryRangeFree(&submodel->geometry_range);\n\tif (submodel->geometries)\n\t\tMem_Free(submodel->geometries);\n\tif (submodel->prev_verts)\n\t\tMem_Free(submodel->prev_verts);\n}\n\nstatic void studioSubmodelInfoDestroy(r_studio_submodel_info_t *subinfo) {\n\t// Not zero means that something still holds a cached render submodel instance somewhere\n\tASSERT(subinfo->render_refcount == 0);\n\n\twhile (subinfo->cached_head) {\n\t\tr_studio_submodel_render_t *render = subinfo->cached_head;\n\t\tsubinfo->cached_head = subinfo->cached_head->_.next;\n\t\tstudioRenderSubmodelDestroy(render);\n\t}\n}\n\nvoid R_StudioCacheClear( void ) {\n\tfor (int i = 0; i < g_studio_cache.models_count; ++i) {\n\t\tr_studio_model_info_t *info = &g_studio_cache.models[i].info;\n\n\t\tfor (int j = 0; j < info->submodels_count; ++j)\n\t\t\tstudioSubmodelInfoDestroy(info->submodels + j);\n\n\t\tif (info->submodels)\n\t\t\tMem_Free(info->submodels);\n\t}\n\tg_studio_cache.models_count = 0;\n\n\tg_studio_cache.submodels_cached_dynamic = g_studio_cache.submodels_cached_static = 0;\n}\n\ntypedef struct {\n\tmatrix3x4 mat;\n} bone_transform_t;\n\nstatic struct {\n\tbone_transform_t first[MAXSTUDIOBONES];\n\tbone_transform_t current[MAXSTUDIOBONES];\n} gb;\n\nstatic void studioModelCalcBones(int numbones, const mstudiobone_t *pbone, const mstudioanim_t *panim, int frame, float interpolation, bone_transform_t *out) {\n\tfor(int b = 0; b < numbones; b++ ) {\n\t\t// TODO check pbone->bonecontroller, if the bone can be dynamically controlled by entity\n\t\t// So far we havent't seen any cases where bonecontroller presence makes static submodels dynamic\n\t\tfloat *const adj = NULL;\n\t\tvec4_t q;\n\t\tvec3_t pos;\n\t\tR_StudioCalcBones(frame, interpolation, pbone + b, panim + b, adj, pos, q);\n\n\t\tmatrix3x4 bonematrix;\n\t\tMatrix3x4_FromOriginQuat(bonematrix, q, pos);\n\t\tif (pbone[b].parent >= 0) {\n\t\t\tMatrix3x4_ConcatTransforms(out[b].mat, out[pbone[b].parent].mat, bonematrix);\n\t\t} else {\n\t\t\tMatrix3x4_Copy(out[b].mat, bonematrix);\n\t\t}\n\t}\n}\n\nstatic qboolean Vector4CompareEpsilon( const vec4_t vec1, const vec4_t vec2, vec_t epsilon )\n{\n\tvec_t\tax, ay, az, aw;\n\n\tax = fabs( vec1[0] - vec2[0] );\n\tay = fabs( vec1[1] - vec2[1] );\n\taz = fabs( vec1[2] - vec2[2] );\n\taw = fabs( vec1[3] - vec2[3] );\n\n\tif(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) && ( aw <= epsilon ))\n\t\treturn true;\n\treturn false;\n}\n\nstatic qboolean isBoneSame(int b) {\n\tfor (int i = 0; i < 3; ++i)\n\t\tif (!Vector4CompareEpsilon(gb.first[b].mat[i], gb.current[b].mat[i], 1e-4f))\n\t\t\treturn false;\n\n\treturn true;\n}\n\n/* static qboolean canBoneBeControlled(const mstudiobone_t* pbone, int b) { */\n/* \tpbone += b; */\n/* \tfor (int i = 0; i < COUNTOF(pbone->bonecontroller); ++i) { */\n/* \t\tif (pbone->bonecontroller[i] >= 0) */\n/* \t\t\treturn true; */\n/* \t} */\n/* \treturn false; */\n/* } */\n\nstatic void studioModelProcessBonesAnimations(const model_t *const model, const studiohdr_t *const hdr, r_studio_submodel_info_t *submodels, int submodels_count) {\n\tconst mstudiobone_t* const pbone = PTR_CAST(const mstudiobone_t, (byte *)hdr + hdr->boneindex);\n\n\t/* for (int i = 0; i < hdr->numbones; ++i) { */\n\t/* \tconst mstudiobone_t* const bone = pbone + i; */\n\t/* \tINFO(\"  Bone %i: %s\", i, bone->name); */\n\t/* } */\n\n\tfor (int i = 0; i < hdr->numseq; ++i) {\n\t\tconst mstudioseqdesc_t *const pseqdesc = PTR_CAST(const mstudioseqdesc_t, (byte *)hdr + hdr->seqindex) + i;\n\n\t\tconst mstudioanim_t* const panim = gEngine.R_StudioGetAnim( (studiohdr_t*)hdr, (model_t*)model, (mstudioseqdesc_t*)pseqdesc );\n\n\t\t// Compute the first frame bones to compare with\n\t\tstudioModelCalcBones(hdr->numbones, pbone, panim, /* frame = */ 0, /* interpolation = */ 0, gb.first);\n\n\t\t// Compute bones for each frame\n\t\t// Last frame is not reachable\n\t\tconst int max_frames = pseqdesc->numframes - 1;\n\t\tfor (int frame = 0; frame < max_frames; ++frame) {\n\t\t\tconst float interpolation = 0.9f;\n\t\t\tstudioModelCalcBones(hdr->numbones, pbone, panim, frame, interpolation, gb.current);\n\n\t\t\t// Compate bones for each submodel\n\t\t\tfor (int si = 0; si < submodels_count; ++si) {\n\t\t\t\tr_studio_submodel_info_t *const subinfo = submodels + si;\n\n\t\t\t\t// Once detected as dynamic, there's no point in checking further\n\t\t\t\tif (subinfo->is_dynamic)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tconst mstudiomodel_t *const submodel = subinfo->submodel_key;\n\t\t\t\tconst qboolean use_boneweights = FBitSet(hdr->flags, STUDIO_HAS_BONEWEIGHTS) && submodel->blendvertinfoindex != 0 && submodel->blendnorminfoindex != 0;\n\n\t\t\t\tif (use_boneweights) {\n\t\t\t\t\tconst mstudioboneweight_t *const pvertweight = (mstudioboneweight_t *)((byte *)hdr + submodel->blendvertinfoindex);\n\t\t\t\t\tfor(int vi = 0; vi < submodel->numverts; vi++) {\n\t\t\t\t\t\tfor (int bi = 0; bi < 4; ++bi) {\n\t\t\t\t\t\t\tconst int8_t bone = pvertweight[vi].bone[bi];\n\t\t\t\t\t\t\tif (bone == -1)\n\t\t\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t\t\tsubinfo->is_dynamic |= !isBoneSame(bone);\n\t\t\t\t\t\t\tif (subinfo->is_dynamic)\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (subinfo->is_dynamic)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t} // for submodel verts\n\n\t\t\t\t} /* use_boneweights */ else {\n\t\t\t\t\tconst byte *const pvertbone = ((const byte *)hdr + submodel->vertinfoindex);\n\t\t\t\t\tfor(int vi = 0; vi < submodel->numverts; vi++) {\n\t\t\t\t\t\tconst byte bone = pvertbone[vi];\n\t\t\t\t\t\tsubinfo->is_dynamic |= !isBoneSame(bone);\n\t\t\t\t\t\tif (subinfo->is_dynamic)\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} // no use_boneweights\n\n\t\t\t\t/* if (subinfo->has_bonecontroller && !subinfo->is_dynamic) { */\n\t\t\t\t/* \tWARN(\"Submodel %s is static, but can be affected by bonecontroller\", subinfo->submodel_key->name); */\n\t\t\t\t/* } */\n\t\t\t} // for all submodels\n\t\t} // for all frames\n\t} // for all sequences\n}\n\n// Get submodels count and/or fill submodels array\nstatic int studioModelGetSubmodels(const studiohdr_t *hdr, r_studio_submodel_info_t *out_submodels) {\n\tint count = 0;\n\tfor (int i = 0; i < hdr->numbodyparts; ++i) {\n\t\tconst mstudiobodyparts_t* const bodypart = PTR_CAST(const mstudiobodyparts_t, (byte *)hdr + hdr->bodypartindex) + i;\n\t\tif (out_submodels) {\n\t\t\tDEBUG(\" Bodypart %d/%d: %s (nummodels=%d)\", i, hdr->numbodyparts - 1, bodypart->name, bodypart->nummodels);\n\t\t\tfor (int j = 0; j < bodypart->nummodels; ++j) {\n\t\t\t\tconst mstudiomodel_t * const submodel = PTR_CAST(const mstudiomodel_t, (byte *)hdr + bodypart->modelindex) + j;\n\t\t\t\tDEBUG(\"  Submodel %d: %s\", j, submodel->name);\n\t\t\t\tout_submodels[count++].submodel_key = submodel;\n\t\t\t}\n\t\t} else {\n\t\t\tcount += bodypart->nummodels;\n\t\t}\n\t}\n\treturn count;\n}\n\nconst r_studio_model_info_t* R_StudioModelPreload(model_t *mod) {\n\tconst studiohdr_t *const hdr = (const studiohdr_t *)gEngine.Mod_Extradata(mod_studio, mod);\n\n\tASSERT(g_studio_cache.models_count < MAX_STUDIO_MODELS);\n\n\tr_studio_model_info_entry_t *entry = &g_studio_cache.models[g_studio_cache.models_count++];\n\tentry->studio_header_key = hdr;\n\n\tDEBUG(\"Studio model %p(%s) hdr=%p(%s), sequences=%d:\", mod, mod->name, hdr, hdr->name, hdr->numseq);\n\tfor (int i = 0; i < hdr->numseq; ++i) {\n\t\tconst mstudioseqdesc_t *const pseqdesc = PTR_CAST(const mstudioseqdesc_t, (byte *)hdr + hdr->seqindex) + i;\n\t\tDEBUG(\"  %d: fps=%f numframes=%d\", i, pseqdesc->fps, pseqdesc->numframes);\n\t}\n\n\t// Get submodel array\n\tconst int submodels_count = studioModelGetSubmodels(hdr, NULL);\n\tr_studio_submodel_info_t *submodels = Mem_Calloc(vk_core.pool, sizeof(*submodels) * submodels_count);\n\tstudioModelGetSubmodels(hdr, submodels);\n\n\tstudioModelProcessBonesAnimations(mod, hdr, submodels, submodels_count);\n\n\tDEBUG(\" submodels_count: %d\", submodels_count);\n\tfor (int i = 0; i < submodels_count; ++i) {\n\t\tconst r_studio_submodel_info_t *const subinfo = submodels + i;\n\t\t//DEBUG(\"  Submodel %d/%d: name=\\\"%s\\\", is_dynamic=%d has_bonecontroller=%d\", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic, subinfo->has_bonecontroller);\n\t\tDEBUG(\"  Submodel %d/%d: name=\\\"%s\\\", is_dynamic=%d\", i, submodels_count-1, subinfo->submodel_key->name, subinfo->is_dynamic);\n\t}\n\n\tentry->info.submodels_count = submodels_count;\n\tentry->info.submodels = submodels;\n\n\treturn &entry->info;\n}\n\nconst r_studio_model_info_t *getStudioModelInfo(model_t *model) {\n\tconst studiohdr_t *const hdr = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, model );\n\n\tfor (int i = 0; i < g_studio_cache.models_count; ++i) {\n\t\tr_studio_model_info_entry_t *const entry = g_studio_cache.models + i;\n\t\tif (entry->studio_header_key == hdr) {\n\t\t\treturn &entry->info;\n\t\t}\n\t}\n\n\tWARN(\"Studio model \\\"%s\\\" wasn't preloaded. How did that happen?\", hdr->name);\n\n\treturn R_StudioModelPreload(model);\n}\n\nvoid VK_StudioModelInit(void) {\n\tR_SPEEDS_METRIC(g_studio_cache.submodels_cached_static, \"submodels_cached_static\", kSpeedsMetricCount);\n\tR_SPEEDS_METRIC(g_studio_cache.submodels_cached_dynamic, \"submodels_cached_dynamic\", kSpeedsMetricCount);\n}\n\nr_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *subinfo) {\n\tconst char *mode = \"\";\n\n\tr_studio_submodel_render_t *render = NULL;\n\tif (subinfo->cached_head) {\n\t\trender = subinfo->cached_head;\n\t\tif (subinfo->is_dynamic) {\n\t\t\tsubinfo->cached_head = render->_.next;\n\t\t\trender->_.next = NULL;\n\t\t}\n\n\t\tmode = \"new\";\n\t} else {\n\t\trender = Mem_Calloc(vk_core.pool, sizeof(*render));\n\t\trender->_.info = subinfo;\n\n\t\tif (!subinfo->is_dynamic) {\n\t\t\tsubinfo->cached_head = render;\n\t\t\t++g_studio_cache.submodels_cached_static;\n\t\t} else {\n\t\t\t++g_studio_cache.submodels_cached_dynamic;\n\t\t}\n\n\t\tmode = \"cached\";\n\t}\n\n\tsubinfo->render_refcount++;\n\tDEBUG(\"%s: submodel=%p(%s) %s rendermodel=%p refcount=%d\",\n\t\t__FUNCTION__, subinfo->submodel_key, subinfo->submodel_key->name, mode, render, subinfo->render_refcount);\n\treturn render;\n}\n\nvoid studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel) {\n\tif (!render_submodel)\n\t\treturn;\n\n\tASSERT(render_submodel->_.info->render_refcount > 0);\n\trender_submodel->_.info->render_refcount--;\n\n\tconst r_studio_submodel_info_t* const subinfo = render_submodel->_.info;\n\tDEBUG(\"%s: submodel=%p(%s) rendermodel=%p refcount=%d\",\n\t\t__FUNCTION__, subinfo->submodel_key, subinfo->submodel_key->name, render_submodel, subinfo->render_refcount);\n\n\tif (!render_submodel->_.info->is_dynamic)\n\t\treturn;\n\n\trender_submodel->_.next = render_submodel->_.info->cached_head;\n\trender_submodel->_.info->cached_head = render_submodel;\n}\n"
  },
  {
    "path": "ref/vk/vk_studio_model.h",
    "content": "#pragma once\n\n#include \"vk_render.h\"\n#include \"vk_geometry.h\"\n\nstruct r_studio_submodel_info_s;\n\n// Submodel render data that is enough to render given submodel\n// Included render model (that also incapsulates BLAS)\n// This can be static (built once), or dynamic (updated frequently)\n// Lives in per-model-info submodel cache\ntypedef struct r_studio_submodel_render_s {\n\tvk_render_model_t model;\n\tr_geometry_range_t geometry_range;\n\tvk_render_geometry_t *geometries;\n\n\t// TODO figure out how to precompute this and store it in info\n\tint geometries_count;\n\tint vertex_count, index_count;\n\n\tvec3_t *prev_verts;\n\n\tstruct {\n\t\tstruct r_studio_submodel_info_s *info;\n\t\tstruct r_studio_submodel_render_s *next;\n\t} _;\n} r_studio_submodel_render_t;\n\n// Submodel metadata and render-model cache\ntypedef struct r_studio_submodel_info_s {\n\tconst mstudiomodel_t *submodel_key;\n\tqboolean is_dynamic;\n\t//qboolean has_bonecontroller;\n\n\t// TODO int verts_count; for prev_verts\n\n\tr_studio_submodel_render_t *cached_head;\n\n\t// Mostly for debug: how many cached render models were acquired and not given back\n\tint render_refcount;\n} r_studio_submodel_info_t;\n\n// Submodel cache functions, used in vk_studio.c\nr_studio_submodel_render_t *studioSubmodelRenderModelAcquire(r_studio_submodel_info_t *info);\nvoid studioSubmodelRenderModelRelease(r_studio_submodel_render_t *render_submodel);\n\ntypedef struct r_studio_model_info_s {\n\tint submodels_count;\n\tr_studio_submodel_info_t *submodels;\n} r_studio_model_info_t;\n\nconst r_studio_model_info_t *getStudioModelInfo(model_t *model);\n\n// Entity model cache/pool\ntypedef struct {\n\tconst studiohdr_t *studio_header;\n\tconst r_studio_model_info_t *model_info;\n\n\t// TODO 3x4\n\tmatrix4x4 transform;\n\tmatrix4x4 prev_transform;\n\n\tint bodyparts_count;\n\tr_studio_submodel_render_t **bodyparts;\n} r_studio_entity_model_t;\n\nvoid VK_StudioModelInit(void);\n//void VK_StudioModelShutdown(void);\n"
  },
  {
    "path": "ref/vk/vk_textures.c",
    "content": "#include \"vk_textures.h\"\n\n#include \"vk_core.h\"\n#include \"vk_logs.h\"\n#include \"vulkan/VResource.h\"\n#include \"r_textures.h\"\n#include \"r_speeds.h\"\n#include \"vulkan/VBarrier.h\"\n\n#include \"xash3d_mathlib.h\" // bound\n\n#include \"ktx2.h\"\n\n#define PCG_IMPLEMENT\n#include \"std/pcg.h\"\n\n#define LOG_MODULE tex\n#define MODULE_NAME \"textures\"\n\n#define MAX_SAMPLERS 8 // TF_NEAREST x 2 * TF_BORDER x 2 * TF_CLAMP x 2\n\ntypedef struct {\n\trt_resource_t header;\n\tr_vk_image_t *image;\n} sampled_image_resource_t;\n\nstatic struct {\n\tstruct {\n\t\tint count;\n\t\tint size_total;\n\t} stats;\n\n\tstruct {\n\t\tuint32_t flags;\n\t\tVkSampler sampler;\n\t} samplers[MAX_SAMPLERS];\n\n\tVkSampler default_sampler;\n\n\t//vk_texture_t textures[MAX_TEXTURES];\n\t//alo_int_pool_t textures_free;\n\n\t// All textures descriptors in their native formats used for RT\n\tVkDescriptorImageInfo dii_all_textures[MAX_TEXTURES];\n\trt_resource_dummy_t textures_resource;\n\n\tvk_texture_t skybox[kSkybox_COUNT];\n\tsampled_image_resource_t skybox_resource;\n\tProducer skybox_producer;\n\n\t// TODO is this used as vk_texture_t object anywhere after loading?\n\tvk_texture_t blue_noise;\n\tsampled_image_resource_t blue_noise_resource;\n} g_vktextures;\n\nstatic VkSampler pickSamplerForFlags( texFlags_t flags );\nstatic qboolean uploadTexture(int index, vk_texture_t *tex, const rgbdata_t *layers, colorspace_hint_e colorspace_hint);\n\nstatic vk_descriptor_value_t acquireSampledImageDescriptor(struct rt_resource_s* r, vk_resource_acquire_descriptor_args_t args) {\n\tsampled_image_resource_t *const res = (void*)r;\n\n\tbarrierAddImage(args.barriers, (r_vkcombuf_barrier_image_t) {\n\t\t.image = res->image,\n\t\t.layout = args.image_layout,\n\t\t.access = args.access,\n\t});\n\n\treturn (vk_descriptor_value_t){\n\t\t.image = (VkDescriptorImageInfo) {\n\t\t\t.sampler = g_vktextures.default_sampler,\n\t\t\t.imageView = res->image->view,\n\t\t\t.imageLayout = args.image_layout,\n\t\t},\n\t};\n}\n\n// Hardcode blue noise texture size to 64x64x64\n#define BLUE_NOISE_SIZE 64\n#define BLUE_NOISE_NAME_F \"bluenoise/LDR_RGBA_%d.png\"\n\nstatic void generateFallbackNoiseTextures( const rgbdata_t *pic ) {\n\tERR(\"Generating bad quality regular noise textures as a fallback for blue noise textures\");\n\n\tconst int blue_noise_count = pic->size / sizeof(uint32_t);\n\tuint32_t *const scratch = PTR_CAST(uint32_t, pic->buffer);\n\n\t// Fill with random data\n\t{\n\t\tpcg32_random_t pcg_state = { blue_noise_count - 1, 17 };\n\t\tfor (int j = 0; j < blue_noise_count; ++j)\n\t\t\tscratch[j] = pcg32_random_r(&pcg_state);\n\t}\n}\n\nstatic void loadBlueNoiseTextures(void) {\n\tconst size_t blue_noise_count = BLUE_NOISE_SIZE * BLUE_NOISE_SIZE * BLUE_NOISE_SIZE;\n\tconst size_t blue_noise_size = blue_noise_count * sizeof(uint32_t);\n\tuint32_t *const scratch = Mem_Malloc(vk_core.pool /* TODO textures pool */, blue_noise_size);\n\tconst rgbdata_t pic = {\n\t\t.width = BLUE_NOISE_SIZE,\n\t\t.height = BLUE_NOISE_SIZE,\n\t\t.depth = BLUE_NOISE_SIZE,\n\t\t.flags = 0,\n\t\t.type = PF_RGBA_32,\n\t\t.size = blue_noise_size,\n\t\t.buffer = (byte*)scratch,\n\t\t.palette = NULL,\n\t\t.numMips = 1,\n\t\t.encode = 0,\n\t};\n\n\tint loaded = 0;\n\tfor (int i = 0, cursor = 0; i < BLUE_NOISE_SIZE; ++i, ++loaded) {\n\t\tchar filename[1024];\n\t\tsnprintf( filename, sizeof filename, BLUE_NOISE_NAME_F, i );\n\t\trgbdata_t *const filepic = gEngine.FS_LoadImage( filename, NULL, 0 );\n\n\t\tif ( !filepic ) {\n\t\t\tERR(\"Couldn't load precomputed blue noise texture '%s'\", filename);\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( filepic->type != PF_RGBA_32 ) {\n\t\t\tERR(\"Precomputed blue noise texture '%s' has unexpected format %d\", filename, filepic->type);\n\t\t\tgEngine.FS_FreeImage( filepic );\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( filepic->width != BLUE_NOISE_SIZE ) {\n\t\t\tERR(\"Precomputed blue noise texture '%s' has unexpected width %d, expected %d\", filename, filepic->width, BLUE_NOISE_SIZE);\n\t\t\tgEngine.FS_FreeImage( filepic );\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( filepic->height != BLUE_NOISE_SIZE ) {\n\t\t\tERR(\"Precomputed blue noise texture '%s' has unexpected height %d, expected %d\", filename, filepic->height, BLUE_NOISE_SIZE);\n\t\t\tgEngine.FS_FreeImage( filepic );\n\t\t\tbreak;\n\t\t}\n\n\t\tASSERT( filepic->size == BLUE_NOISE_SIZE * BLUE_NOISE_SIZE * sizeof(uint32_t) );\n\n\t\tmemcpy(pic.buffer + cursor, filepic->buffer, filepic->size);\n\t\tcursor += filepic->size;\n\n\t\tgEngine.FS_FreeImage( filepic );\n\t}\n\n\tconst qboolean fail = loaded != BLUE_NOISE_SIZE;\n\tif (fail)\n\t\tgenerateFallbackNoiseTextures( &pic );\n\n\tconst char *const name = fail ? \"*bluenoise/pcg_fallback\" : \"*bluenoise\";\n\tQ_strncpy(g_vktextures.blue_noise.hdr_.key, name, sizeof(g_vktextures.blue_noise.hdr_.key));\n\tg_vktextures.blue_noise.flags = TF_NOMIPMAP;\n\tASSERT(uploadTexture(-1, &g_vktextures.blue_noise, &pic, kColorspaceLinear));\n\tMem_Free(scratch);\n\n\t{\n\t\t// TODO move vk_texture_t blue_noise.vk.image into here\n\t\tg_vktextures.blue_noise_resource = (sampled_image_resource_t) {\n\t\t\t.header = {\n\t\t\t\t.name = \"blue_noise_texture\",\n\t\t\t\t.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t\t\t.acquire_descriptor = acquireSampledImageDescriptor,\n\t\t\t\t.refcount = 1,\n\t\t\t},\n\t\t\t.image = &g_vktextures.blue_noise.vk.image,\n\t\t};\n\t\tASSERT(R_VkResourceRegister(&g_vktextures.blue_noise_resource.header));\n\t}\n}\n\nstatic void produceSkybox(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\tASSERT(p == &g_vktextures.skybox_producer);\n\n\tg_vktextures.skybox_resource.image =\n\t\t(g_vktextures.skybox[kSkyboxPatched].vk.image.view != VK_NULL_HANDLE)\n\t\t? &g_vktextures.skybox[kSkyboxPatched].vk.image\n\t  : (g_vktextures.skybox[kSkyboxOriginal].vk.image.view != VK_NULL_HANDLE)\n\t\t? &g_vktextures.skybox[kSkyboxOriginal].vk.image\n\t\t: &g_vktextures.skybox[kSkyboxPlaceholder].vk.image;\n}\n\nqboolean R_VkTexturesInit( void ) {\n\tR_SPEEDS_METRIC(g_vktextures.stats.count, \"count\", kSpeedsMetricCount);\n\tR_SPEEDS_METRIC(g_vktextures.stats.size_total, \"size_total\", kSpeedsMetricBytes);\n\n\t// TODO really check device caps for this\n\tgEngine.Image_AddCmdFlags( IL_DDS_HARDWARE | IL_KTX2_RAW );\n\n\tg_vktextures.default_sampler = pickSamplerForFlags(0);\n\tASSERT(g_vktextures.default_sampler != VK_NULL_HANDLE);\n\n\t/* FIXME\n\t// validate cvars\n\tR_SetTextureParameters();\n\t*/\n\n\t/* FIXME\n\tgEngine.Cmd_AddCommand( \"texturelist\", R_TextureList_f, \"display loaded textures list\" );\n\t*/\n\n\t// Fill empty texture with references to the default texture\n\t{\n\t\tconst VkImageView default_view = R_TextureGetByIndex(tglob.defaultTexture)->vk.image.view;\n\t\tASSERT(default_view != VK_NULL_HANDLE);\n\t\tfor (int i = 0; i < MAX_TEXTURES; ++i) {\n\t\t\tconst vk_texture_t *const tex = R_TextureGetByIndex(i);\n\t\t\tif (tex->vk.image.view)\n\t\t\t\tcontinue;\n\n\t\t\tg_vktextures.dii_all_textures[i] = (VkDescriptorImageInfo){\n\t\t\t\t.imageView =  default_view,\n\t\t\t\t.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n\t\t\t\t.sampler = g_vktextures.default_sampler,\n\t\t\t};\n\t\t}\n\t}\n\n\t{\n\t\tR_VkResourceDummyInit(&g_vktextures.textures_resource,\n\t\t\t\"textures\",\n\t\t\tVK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t\t(vk_descriptor_value_t) {\n\t\t\t\t.image_array = g_vktextures.dii_all_textures,\n\t\t\t});\n\t\tASSERT(R_VkResourceRegister(&g_vktextures.textures_resource.header));\n\t}\n\n\t{\n\t\tg_vktextures.skybox_producer = (Producer) {\n\t\t\t.name = \"skybox\",\n\t\t\t.produce = produceSkybox,\n\t\t};\n\n\t\tg_vktextures.skybox_resource = (sampled_image_resource_t) {\n\t\t\t.header = {\n\t\t\t\t.name = \"skybox\",\n\t\t\t\t.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t\t\t.acquire_descriptor = acquireSampledImageDescriptor,\n\t\t\t\t.producer = &g_vktextures.skybox_producer,\n\t\t\t\t.refcount = 1,\n\t\t\t},\n\t\t\t.image = &g_vktextures.skybox[kSkyboxPlaceholder].vk.image,\n\t\t};\n\t\tASSERT(R_VkResourceRegister(&g_vktextures.skybox_resource.header));\n\t}\n\n\tif (vk_core.rtx)\n\t\tloadBlueNoiseTextures();\n\n\treturn true;\n}\n\nvoid R_VkTexturesShutdown( void ) {\n\tR_VkTexturesSkyboxUnload();\n\n\tfor (int i = 0; i < COUNTOF(g_vktextures.skybox); ++i) {\n\t\tR_VkTextureDestroy(-1, g_vktextures.skybox + i);\n\t}\n\n\tif (vk_core.rtx)\n\t\tR_VkTextureDestroy(-1, &g_vktextures.blue_noise);\n\n\tfor (int i = 0; i < COUNTOF(g_vktextures.samplers); ++i) {\n\t\tif (g_vktextures.samplers[i].sampler != VK_NULL_HANDLE)\n\t\t\tvkDestroySampler(vk_core.device, g_vktextures.samplers[i].sampler, NULL);\n\t}\n}\n\nstatic VkFormat VK_GetFormat(pixformat_t format, colorspace_hint_e colorspace_hint ) {\n\tswitch(format)\n\t{\n\t\tcase PF_RGBA_32:\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_R8G8B8A8_UNORM\n\t\t\t\t: VK_FORMAT_R8G8B8A8_SRGB;\n\t\tcase PF_BGRA_32:\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_B8G8R8A8_UNORM\n\t\t\t\t: VK_FORMAT_B8G8R8A8_SRGB;\n\t\tcase PF_RGB_24:\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_R8G8B8_UNORM\n\t\t\t\t: VK_FORMAT_R8G8B8_SRGB;\n\t\tcase PF_BGR_24:\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_B8G8R8_UNORM\n\t\t\t\t: VK_FORMAT_B8G8R8_SRGB;\n\t\tcase PF_LUMINANCE:\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_R8_UNORM\n\t\t\t\t: VK_FORMAT_R8_SRGB;\n\t\tcase PF_DXT1:\n\t\t\t// TODO UNORM vs SRGB encoded in the format itself\n\t\t\t// ref_gl mentions that alpha is never used\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_BC1_RGB_UNORM_BLOCK\n\t\t\t\t: VK_FORMAT_BC1_RGB_SRGB_BLOCK;\n\t\tcase PF_DXT3:\n\t\t\t// TODO UNORM vs SRGB encoded in the format itself\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_BC2_UNORM_BLOCK\n\t\t\t\t: VK_FORMAT_BC2_SRGB_BLOCK;\n\t\tcase PF_DXT5:\n\t\t\t// TODO UNORM vs SRGB encoded in the format itself\n\t\t\treturn (colorspace_hint == kColorspaceLinear)\n\t\t\t\t? VK_FORMAT_BC3_UNORM_BLOCK\n\t\t\t\t: VK_FORMAT_BC3_SRGB_BLOCK;\n\t\tcase PF_ATI2:\n\t\t\t// TODO UNORM vs SNORM?\n\t\t\treturn VK_FORMAT_BC5_UNORM_BLOCK;\n\t\tcase PF_BC4_UNSIGNED: return VK_FORMAT_BC4_UNORM_BLOCK;\n\t\tcase PF_BC4_SIGNED:   return VK_FORMAT_BC4_SNORM_BLOCK;\n\t\tcase PF_BC5_UNSIGNED: return VK_FORMAT_BC5_UNORM_BLOCK;\n\t\tcase PF_BC5_SIGNED:   return VK_FORMAT_BC5_SNORM_BLOCK;\n\t\tcase PF_BC6H_UNSIGNED: return VK_FORMAT_BC6H_UFLOAT_BLOCK;\n\t\tcase PF_BC6H_SIGNED:   return VK_FORMAT_BC6H_SFLOAT_BLOCK;\n\t\tcase PF_BC7_UNORM: return VK_FORMAT_BC7_UNORM_BLOCK;\n\t\tcase PF_BC7_SRGB:  return VK_FORMAT_BC7_SRGB_BLOCK;\n\t\tdefault:\n\t\t\tWARN(\"FIXME unsupported pixformat_t %d\", format);\n\t\t\treturn VK_FORMAT_UNDEFINED;\n\t}\n}\n\nstatic VkSampler createSamplerForFlags( texFlags_t flags ) {\n\tVkSampler sampler;\n\tconst VkFilter filter_mode = (flags & TF_NEAREST) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;\n\tconst VkSamplerAddressMode addr_mode =\n\t\t  (flags & TF_BORDER) ? VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER\n\t\t: ((flags & TF_CLAMP) ? VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE : VK_SAMPLER_ADDRESS_MODE_REPEAT);\n\tconst VkSamplerCreateInfo sci = {\n\t\t.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,\n\t\t.magFilter =  filter_mode,\n\t\t.minFilter = filter_mode,\n\t\t.addressModeU = addr_mode,\n\t\t.addressModeV = addr_mode,\n\t\t.addressModeW = addr_mode,\n\t\t.anisotropyEnable = v_device_info.anisotropy,\n\t\t.maxAnisotropy = v_device_info.properties.limits.maxSamplerAnisotropy,\n\t\t.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,\n\t\t.unnormalizedCoordinates = VK_FALSE,\n\t\t.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,\n\t\t.minLod = 0.f,\n\t\t.maxLod = 16.,\n\t};\n\tXVK_CHECK(vkCreateSampler(vk_core.device, &sci, NULL, &sampler));\n\treturn sampler;\n}\n\nstatic VkSampler pickSamplerForFlags( texFlags_t flags ) {\n\tflags &= (TF_BORDER | TF_CLAMP | TF_NEAREST);\n\n\tfor (int i = 0; i < COUNTOF(g_vktextures.samplers); ++i) {\n\t\tif (g_vktextures.samplers[i].sampler == VK_NULL_HANDLE) {\n\t\t\tg_vktextures.samplers[i].flags = flags;\n\t\t\treturn g_vktextures.samplers[i].sampler = createSamplerForFlags(flags);\n\t\t}\n\n\t\tif (g_vktextures.samplers[i].flags == flags)\n\t\t\treturn g_vktextures.samplers[i].sampler;\n\t}\n\n\tERR(\"Couldn't find/allocate sampler for flags %x\", flags);\n\treturn g_vktextures.default_sampler;\n}\n\nstatic void setDescriptorSet(int index, vk_texture_t* const tex, colorspace_hint_e colorspace_hint) {\n\tif (index < 0)\n\t\treturn;\n\n\tASSERT(index < MAX_TEXTURES);\n\n\tconst VkImageView view = tex->vk.image.view != VK_NULL_HANDLE\n\t\t? tex->vk.image.view\n\t\t: R_TextureGetByIndex(tglob.defaultTexture)->vk.image.view;\n\n\tif (view == VK_NULL_HANDLE)\n\t\treturn;\n\n\tVkDescriptorImageInfo dii = {\n\t\t.imageView = view,\n\t\t.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n\t\t.sampler = pickSamplerForFlags( tex->flags ),\n\t};\n\n\t// Set descriptor for bindless/ray tracing\n\tg_vktextures.dii_all_textures[index] = dii;\n\n\t// Continue with setting unorm descriptor for traditional renderer\n\n\t// TODO how should we approach this:\n\t// - per-texture desc sets can be inconvenient if texture is used in different incompatible contexts\n\t// - update descriptor sets in batch?\n\n\tif (colorspace_hint == kColorspaceGamma && tex->vk.image.view_unorm != VK_NULL_HANDLE)\n\t\tdii.imageView = tex->vk.image.view_unorm;\n\n\tconst VkDescriptorSet ds = vk_desc_fixme.texture_sets[index];\n\tVkWriteDescriptorSet wds[1] = { {\n\t\t.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,\n\t\t.dstBinding = 0,\n\t\t.dstArrayElement = 0,\n\t\t.descriptorCount = 1,\n\t\t.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t.pImageInfo = &dii,\n\t\t.dstSet = ds,\n\t}};\n\n\tvkUpdateDescriptorSets(vk_core.device, COUNTOF(wds), wds, 0, NULL);\n\n\ttex->vk.descriptor_unorm = ds;\n}\n\nstatic qboolean uploadRawKtx2( int tex_index, vk_texture_t *tex, const rgbdata_t* pic ) {\n\tDEBUG(\"Uploading raw KTX2 texture[%d] %s\", tex_index, TEX_NAME(tex));\n\n\tconst byte *const data = pic->buffer;\n\n\tconst ktx2_header_t* header;\n\tconst ktx2_index_t* index;\n\tconst ktx2_level_t* levels;\n\n\theader = PTR_CAST(const ktx2_header_t, data + KTX2_IDENTIFIER_SIZE);\n\tindex = PTR_CAST(const ktx2_index_t, data + KTX2_IDENTIFIER_SIZE + sizeof(ktx2_header_t));\n\tlevels = PTR_CAST(const ktx2_level_t, data + KTX2_IDENTIFIER_SIZE + sizeof(ktx2_header_t) + sizeof(ktx2_index_t));\n\n\tDEBUG(\" header:\");\n#define X(field) DEBUG(\"  \" # field \"=%d\", header->field);\n\tDEBUG(\"  vkFormat = %s(%d)\", R_VkFormatName(header->vkFormat), header->vkFormat);\n\tX(typeSize)\n\tX(pixelWidth)\n\tX(pixelHeight)\n\tX(pixelDepth)\n\tX(layerCount)\n\tX(faceCount)\n\tX(levelCount)\n\tX(supercompressionScheme)\n#undef X\n\tDEBUG(\" index:\");\n#define X(field) DEBUG(\"  \" # field \"=%llu\", (unsigned long long)index->field);\n\tX(dfdByteOffset)\n\tX(dfdByteLength)\n\tX(kvdByteOffset)\n\tX(kvdByteLength)\n\tX(sgdByteOffset)\n\tX(sgdByteLength)\n#undef X\n\n\tfor (int mip = 0; mip < header->levelCount; ++mip) {\n\t\tconst ktx2_level_t* const level = levels + mip;\n\t\tDEBUG(\" level[%d]:\", mip);\n\t\tDEBUG(\"  byteOffset=%llu\", (unsigned long long)level->byteOffset);\n\t\tDEBUG(\"  byteLength=%llu\", (unsigned long long)level->byteLength);\n\t\tDEBUG(\"  uncompressedByteLength=%llu\", (unsigned long long)level->uncompressedByteLength);\n\t}\n\n\t// FIXME check that format is supported\n\t// FIXME layers == 0\n\t// FIXME has_alpha\n\t// FIXME no supercompressionScheme\n\n\t{\n\t\tconst r_vk_image_create_t create = {\n\t\t\t.debug_name = TEX_NAME(tex),\n\t\t\t.width = header->pixelWidth,\n\t\t\t.height = header->pixelHeight,\n\t\t\t.depth = Q_max(1, header->pixelDepth),\n\t\t\t.mips = header->levelCount,\n\t\t\t// header->layerCount? header->faceCount?\n\t\t\t.layers = 1, // TODO or 6 for cubemap; header->faceCount\n\t\t\t.format = header->vkFormat,\n\t\t\t.tiling = VK_IMAGE_TILING_OPTIMAL,\n\t\t\t.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,\n\t\t\t// FIXME find out if there's alpha\n\t\t\t.flags = 0,\n\t\t};\n\t\ttex->vk.image = R_VkImageCreate(&create);\n\t}\n\n\t{\n\t\tR_VkImageUploadBegin(&tex->vk.image);\n\n\t\tfor (int mip = 0; mip < header->levelCount; ++mip) {\n\t\t\tconst ktx2_level_t* const level = levels + mip;\n\t\t\tconst size_t mip_size = level->byteLength;\n\t\t\tconst void* const image_data = data + level->byteOffset;\n\t\t\t// FIXME validate wrt file size\n\n\t\t\tconst int layer = 0;\n\t\t\tR_VkImageUploadSlice(&tex->vk.image, layer, mip, mip_size, image_data);\n\t\t\ttex->total_size += mip_size;\n\t\t} // for mip levels\n\n\t\tR_VkImageUploadEnd(&tex->vk.image);\n\t}\n\n\t{\n\t\t// KTX2 textures are inaccessible from trad renderer (for now)\n\t\ttex->vk.descriptor_unorm = VK_NULL_HANDLE;\n\n\t\tconst VkDescriptorImageInfo dii = {\n\t\t\t.imageView = tex->vk.image.view,\n\t\t\t.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n\t\t\t.sampler = pickSamplerForFlags( tex->flags ),\n\t\t};\n\t\tg_vktextures.dii_all_textures[tex_index] = dii;\n\t}\n\n\tg_vktextures.stats.size_total += tex->total_size;\n\tg_vktextures.stats.count++;\n\n\ttex->width = header->pixelWidth;\n\ttex->height = header->pixelHeight;\n\n\treturn true;\n}\n\nstatic qboolean needToCreateImage( int index, vk_texture_t *tex, const r_vk_image_create_t *create ) {\n\tif (tex->vk.image.image == VK_NULL_HANDLE)\n\t\treturn true;\n\n\tif (tex->vk.image.width == create->width\n\t\t&& tex->vk.image.height == create->height\n\t\t&& tex->vk.image.format == create->format\n\t\t&& tex->vk.image.mips == create->mips\n\t\t&& tex->vk.image.layers == create->layers\n\t\t&& tex->vk.image.flags == create->flags)\n\t\treturn false;\n\n\tWARN(\"Re-creating texture '%s' image\", create->debug_name);\n\tR_VkTextureDestroy( index, tex );\n\treturn true;\n}\n\nstatic const char *getPFName(int pf_type) {\n\tswitch (pf_type) {\n\t\tcase PF_UNKNOWN: return \"PF_UNKNOWN\";\n\t\tcase PF_INDEXED_24: return \"PF_INDEXED_24\";\n\t\tcase PF_INDEXED_32: return \"PF_INDEXED_32\";\n\t\tcase PF_RGBA_32: return \"PF_RGBA_32\";\n\t\tcase PF_BGRA_32: return \"PF_BGRA_32\";\n\t\tcase PF_RGB_24: return \"PF_RGB_24\";\n\t\tcase PF_BGR_24: return \"PF_BGR_24\";\n\t\tcase PF_LUMINANCE: return \"PF_LUMINANCE\";\n\t\tcase PF_DXT1: return \"PF_DXT1\";\n\t\tcase PF_DXT3: return \"PF_DXT3\";\n\t\tcase PF_DXT5: return \"PF_DXT5\";\n\t\tcase PF_ATI2: return \"PF_ATI2\";\n\t\tcase PF_BC4_SIGNED: return \"PF_BC4_SIGNED\";\n\t\tcase PF_BC4_UNSIGNED: return \"PF_BC4_UNSIGNED\";\n\t\tcase PF_BC5_SIGNED: return \"PF_BC5_SIGNED\";\n\t\tcase PF_BC5_UNSIGNED: return \"PF_BC5_UNSIGNED\";\n\t\tcase PF_BC6H_SIGNED: return \"PF_BC6H_SIGNED\";\n\t\tcase PF_BC6H_UNSIGNED: return \"PF_BC6H_UNSIGNED\";\n\t\tcase PF_BC7_UNORM: return \"PF_BC7_UNORM\";\n\t\tcase PF_BC7_SRGB: return \"PF_BC7_SRGB\";\n\t\tcase PF_KTX2_RAW: return \"PF_KTX2_RAW\";\n\t}\n\n\treturn \"INVALID\";\n}\n\nstatic const char* getColorspaceHintName(colorspace_hint_e ch) {\n\tswitch (ch) {\n\t\tcase kColorspaceGamma: return \"gamma\";\n\t\tcase kColorspaceLinear: return \"linear\";\n\t\tcase kColorspaceNative: return \"native\";\n\t}\n\n\treturn \"INVALID\";\n}\n\n// xash imagelib cubemap layer order is not the one that vulkan expects\nstatic const int g_remap_cube_layer[6] = {\n\t/* ft = */ 3,\n\t/* bk = */ 2,\n\t/* up = */ 4,\n\t/* dn = */ 5,\n\t/* rt = */ 0,\n\t/* lf = */ 1,\n};\n\nstatic qboolean uploadTexture(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint) {\n\ttex->total_size = 0;\n\n\tif (pic->type == PF_KTX2_RAW) {\n\t\tif (!uploadRawKtx2(index, tex, pic))\n\t\t\treturn false;\n\t} else {\n\t\tconst int width = pic->width;\n\t\tconst int height = pic->height;\n\t\tconst int depth = Q_max(1, pic->depth);\n\t\tconst qboolean compute_mips = !(tex->flags & TF_NOMIPMAP) && pic->type == PF_RGBA_32 && pic->numMips < 2;\n\t\tconst VkFormat format = VK_GetFormat(pic->type, colorspace_hint);\n\t\tconst int mipCount = compute_mips ? CalcMipmapCount( width, height, depth, tex->flags, true ) : Q_max(1, pic->numMips);\n\t\tconst qboolean is_cubemap = !!(pic->flags & IMAGE_CUBEMAP);\n\n\t\tif (format == VK_FORMAT_UNDEFINED) {\n\t\t\tERR(\"Unsupported PF format %d\", pic->type);\n\t\t\treturn false;\n\t\t}\n\n\t\tDEBUG(\"Uploading texture[%d] %s, %dx%d fmt=%s(%s) cs=%s mips=%d(build=%d), is_cubemap=%d\",\n\t\t\tindex, TEX_NAME(tex), width, height,\n\t\t\tgetPFName(pic->type), R_VkFormatName(format),\n\t\t\tgetColorspaceHintName(colorspace_hint),\n\t\t\tmipCount, compute_mips, is_cubemap);\n\n\t\t// TODO (not sure why, but GL does this)\n\t\t// if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))\n\t\t// \tdata = GL_ApplyFilter( data, tex->width, tex->height );\n\n\t\t{\n\t\t\tconst r_vk_image_create_t create = {\n\t\t\t\t.debug_name = TEX_NAME(tex),\n\t\t\t\t.width = width,\n\t\t\t\t.height = height,\n\t\t\t\t.depth = depth,\n\t\t\t\t.mips = mipCount,\n\t\t\t\t.layers = is_cubemap ? 6 : 1,\n\t\t\t\t.format = format,\n\t\t\t\t.tiling = VK_IMAGE_TILING_OPTIMAL,\n\t\t\t\t.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,\n\t\t\t\t.flags = 0\n\t\t\t\t\t| ((pic->flags & IMAGE_HAS_ALPHA) ? 0 : kVkImageFlagIgnoreAlpha)\n\t\t\t\t\t| (is_cubemap ? kVkImageFlagIsCubemap : 0)\n\t\t\t\t\t| (colorspace_hint == kColorspaceGamma ? kVkImageFlagCreateUnormView : 0),\n\t\t\t};\n\n\t\t\tif (needToCreateImage(index, tex, &create))\n\t\t\t\ttex->vk.image = R_VkImageCreate(&create);\n\t\t}\n\n\t\ttex->width = width;\n\t\ttex->height = height;\n\t\ttex->depth = depth;\n\n\t\t{\n\t\t\tR_VkImageUploadBegin(&tex->vk.image);\n\n\t\t\tbyte *buf = pic->buffer;\n\t\t\tconst int layers_count = is_cubemap ? 6 : 1;\n\t\t\tfor (int layer = 0; layer < layers_count; ++layer) {\n\t\t\t\tfor (int mip = 0; mip < mipCount; ++mip) {\n\t\t\t\t\tconst int width = Q_max( 1, ( pic->width >> mip ));\n\t\t\t\t\tconst int height = Q_max( 1, ( pic->height >> mip ));\n\t\t\t\t\tconst int depth = Q_max( 1, ( pic->depth >> mip ));\n\t\t\t\t\tconst size_t mip_size = CalcImageSize( pic->type, width, height, depth );\n\n\t\t\t\t\tR_VkImageUploadSlice(&tex->vk.image, is_cubemap ? g_remap_cube_layer[layer] : 0, mip, mip_size, buf);\n\t\t\t\t\ttex->total_size += mip_size;\n\n\t\t\t\t\t// Build mip in place for the next mip level\n\t\t\t\t\tif (compute_mips) {\n\t\t\t\t\t\tif ( mip < mipCount - 1 )\n\t\t\t\t\t\t\tBuildMipMap( buf, width, height, depth, tex->flags );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbuf += mip_size;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (compute_mips) {\n\t\t\t\t\tbuf += CalcImageSize(pic->type, width, height, depth);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tR_VkImageUploadEnd(&tex->vk.image);\n\t\t}\n\t}\n\n\tsetDescriptorSet(index, tex, colorspace_hint);\n\n\tg_vktextures.stats.size_total += tex->total_size;\n\tg_vktextures.stats.count++;\n\treturn true;\n}\n\nqboolean R_VkTextureUpload(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint) {\n\treturn uploadTexture( index, tex, pic, colorspace_hint );\n}\n\nvoid R_VkTextureDestroy( int index, vk_texture_t *tex ) {\n\tif (!tex)\n\t\treturn;\n\n\tif (tex->vk.image.image == VK_NULL_HANDLE)\n\t\treturn;\n\n\tR_VkImageDestroy(&tex->vk.image);\n\tg_vktextures.stats.size_total -= tex->total_size;\n\tg_vktextures.stats.count--;\n\n\t// Reset descriptor sets to default texture\n\tsetDescriptorSet(index, tex, kColorspaceNative);\n\n\ttex->total_size = 0;\n\ttex->width = tex->height = 0;\n\n\t// TODO: currently cannot do this because vk_render depends on all textures having some descriptor regardless of their alive-ness\n\t// TODO tex->vk.descriptor_unorm = VK_NULL_HANDLE;\n}\n\nvoid R_VkTexturesSkyboxUnload(void) {\n\tDEBUG(\"%s\", __FUNCTION__);\n\n\tfor (int i = 0; i < kSkybox_COUNT; ++i) {\n\t\tif (i == kSkyboxPlaceholder)\n\t\t\tcontinue;\n\n\t\tvk_texture_t* const skybox = g_vktextures.skybox + i;\n\t\tif (skybox->vk.image.image) {\n\t\t\tR_VkTextureDestroy( -1, skybox );\n\t\t\tmemset(skybox, 0, sizeof(*skybox));\n\t\t}\n\t}\n\n\t// Revert skybox resource back to the placeholder slot\n\tg_vktextures.skybox_resource.image = &g_vktextures.skybox[kSkyboxPlaceholder].vk.image;\n}\n\nVkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( skybox_slot_e slot ) {\n\tvk_texture_t *skybox = g_vktextures.skybox + slot;\n\n\tif (skybox->vk.image.view == VK_NULL_HANDLE)\n\t\tskybox = g_vktextures.skybox + kSkyboxOriginal;\n\n\tif (skybox->vk.image.view == VK_NULL_HANDLE)\n\t\tskybox = g_vktextures.skybox + kSkyboxPlaceholder;\n\n\tASSERT(skybox->vk.image.view != VK_NULL_HANDLE);\n\n\treturn (VkDescriptorImageInfo){\n\t\t.sampler = g_vktextures.default_sampler,\n\t\t.imageView = skybox->vk.image.view,\n\t\t.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n\t};\n}\n\nqboolean R_VkTexturesSkyboxUpload( const char *name, const rgbdata_t *pic, colorspace_hint_e colorspace_hint, skybox_slot_e skybox_slot ) {\n\tASSERT(skybox_slot >= 0);\n\tASSERT(skybox_slot < kSkybox_COUNT);\n\n\tvk_texture_t *const dest = g_vktextures.skybox + skybox_slot;\n\tQ_strncpy( TEX_NAME(dest), name, sizeof( TEX_NAME(dest) ));\n\tdest->flags |= TF_NOMIPMAP;\n\tASSERT(pic->flags & IMAGE_CUBEMAP);\n\n\tconst qboolean uploaded = uploadTexture(-1, dest, pic, colorspace_hint);\n\tif (!uploaded)\n\t\treturn false;\n\n\treturn uploaded;\n}\n\nVkDescriptorSet R_VkTextureGetDescriptorUnorm( uint index ) {\n\tASSERT( index < MAX_TEXTURES );\n\t// TODO make an array of unorm descriptors\n\tconst vk_texture_t *const tex = R_TextureGetByIndex(index);\n\tASSERT(tex->vk.descriptor_unorm != VK_NULL_HANDLE);\n\treturn tex->vk.descriptor_unorm;\n}\n"
  },
  {
    "path": "ref/vk/vk_textures.h",
    "content": "#pragma once\n\n#include \"r_textures.h\"\n#include \"vk_core.h\"\n#include \"vulkan/VImage.h\"\n#include \"vk_const.h\"\n\n#include \"std/unordered_roadmap.h\"\n\ntypedef struct vk_texture_s\n{\n\turmom_header_t hdr_;\n\n\tint width, height, depth;\n\tuint32_t flags;\n\tint total_size;\n\n\tstruct {\n\t\tr_vk_image_t image;\n\n\t\t// TODO external table\n\t\tVkDescriptorSet descriptor_unorm;\n\t} vk;\n\n\tint refcount;\n\tqboolean ref_interface_visible;\n\n\t// TODO \"cache\" eviction\n\t// int used_maps_ago;\n} vk_texture_t;\n\n#define TEX_NAME(tex) ((tex)->hdr_.key)\n\nqboolean R_VkTexturesInit( void );\nvoid R_VkTexturesShutdown( void );\n\nqboolean R_VkTexturesSkyboxUpload( const char *name, const rgbdata_t *pic, colorspace_hint_e colorspace_hint, skybox_slot_e skybox_slot );\nvoid R_VkTexturesSkyboxUnload(void);\n\nqboolean R_VkTextureUpload(int index, vk_texture_t *tex, const rgbdata_t *pic, colorspace_hint_e colorspace_hint);\nvoid R_VkTextureDestroy(int index, vk_texture_t *tex);\n\nVkDescriptorImageInfo R_VkTexturesGetSkyboxDescriptorImageInfo( skybox_slot_e slot );\nVkDescriptorSet R_VkTextureGetDescriptorUnorm( uint index );\n"
  },
  {
    "path": "ref/vk/vk_triapi.c",
    "content": "#include \"vk_triapi.h\"\n#include \"vk_geometry.h\"\n#include \"vk_render.h\"\n#include \"vk_sprite.h\" // R_GetSpriteTexture\n#include \"vk_logs.h\"\n\n#include \"xash3d_mathlib.h\"\n\n#define MAX_TRIAPI_VERTICES 1024\n#define MAX_TRIAPI_INDICES 4096\n\nstatic struct {\n\tvk_vertex_t vertices[MAX_TRIAPI_VERTICES];\n\tuint16_t indices[MAX_TRIAPI_INDICES];\n\n\tint num_vertices;\n\tint primitive_mode;\n\tint texture_index;\n\tint lightmap_index;\n\n\tvk_render_type_e render_type;\n\n\tqboolean initialized;\n} g_triapi = {0};\n\nvoid TriSetTexture( int texture_index ) {\n\t g_triapi.texture_index = texture_index;\n}\n\nvoid TriSetLightmap( int lightmap_index ) {\n\t g_triapi.lightmap_index = lightmap_index;\n}\n\nint TriSpriteTexture( model_t *pSpriteModel, int frame )\n{\n\tint\tgl_texturenum;\n\n\tif(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )\n\t\treturn 0;\n\n\tTriSetTexture( gl_texturenum );\n\n\treturn 1;\n}\n\nvoid TriRenderMode( int render_mode ) {\n\tswitch( render_mode )\n\t{\n\tcase kRenderTransAlpha:\n\t\tg_triapi.render_type = kVkRenderType_A_1mA_R;\n\t\tbreak;\n\tcase kRenderTransColor:\n\tcase kRenderTransTexture:\n\t\tg_triapi.render_type = kVkRenderType_A_1mA_RW;\n\t\tbreak;\n\tcase kRenderGlow:\n\tcase kRenderTransAdd:\n\t\tg_triapi.render_type = kVkRenderType_A_1_R;\n\t\tbreak;\n\tcase kRenderNormal:\n\tdefault:\n\t\tg_triapi.render_type = kVkRenderTypeSolid;\n\t\tbreak;\n\t}\n}\n\nvoid TriRenderType( int render_type ) {\n\tASSERT(render_type >= 0 && render_type < kVkRenderType_COUNT);\n\n\tg_triapi.render_type = render_type;\n}\n\nvoid TriBegin( int primitive_mode ) {\n\tASSERT(!g_triapi.primitive_mode);\n\n\tswitch(primitive_mode) {\n\t\tcase TRI_TRIANGLES: break;\n\t\tcase TRI_TRIANGLE_STRIP: break;\n\t\tcase TRI_QUADS: break;\n\t\tcase TRI_POLYGON: break;\n\t\tdefault:\n\t\t\tgEngine.Con_Printf(S_ERROR \"TriBegin: unsupported primitive_mode %d\\n\", primitive_mode);\n\t\t\treturn;\n\t}\n\n\tvk_vertex_t *const ve = g_triapi.vertices + 0;\n\tif (g_triapi.num_vertices > 1)\n\t\t*ve = g_triapi.vertices[g_triapi.num_vertices-1];\n\n\tif (!g_triapi.initialized) {\n\t\tVector4Set(ve->color, 255, 255, 255, 255);\n\t\tg_triapi.initialized = true;\n\t}\n\n\tg_triapi.primitive_mode = primitive_mode + 1;\n\tg_triapi.num_vertices = 0;\n}\n\nstatic int genTrianglesIndices(void) {\n\tint num_indices = 0;\n\tuint16_t *const dst_idx = g_triapi.indices;\n\tconst int num_vertices = g_triapi.num_vertices - (g_triapi.num_vertices % 3);\n\n\tfor (int i = 0; i < num_vertices; i += 3) {\n\t\tif (num_indices > MAX_TRIAPI_INDICES - 3) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Triapi ran out of indices space, max %d (vertices=%d)\\n\", MAX_TRIAPI_INDICES, g_triapi.num_vertices);\n\t\t\tbreak;\n\t\t}\n\n\t\tdst_idx[num_indices++] = i;\n\t\tdst_idx[num_indices++] = i + 1;\n\t\tdst_idx[num_indices++] = i + 2;\n\t}\n\n\treturn num_indices;\n}\n\nstatic int genQuadsIndices(void) {\n\tint num_indices = 0;\n\tuint16_t *const dst_idx = g_triapi.indices;\n\tfor (int i = 0; i < g_triapi.num_vertices - 3; i+=4) {\n\t\tif (num_indices > MAX_TRIAPI_INDICES - 6) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Triapi ran out of indices space, max %d (vertices=%d)\\n\", MAX_TRIAPI_INDICES, g_triapi.num_vertices);\n\t\t\tbreak;\n\t\t}\n\n\t\tdst_idx[num_indices++] = 0 + i;\n\t\tdst_idx[num_indices++] = 1 + i;\n\t\tdst_idx[num_indices++] = 2 + i;\n\n\t\tdst_idx[num_indices++] = 0 + i;\n\t\tdst_idx[num_indices++] = 2 + i;\n\t\tdst_idx[num_indices++] = 3 + i;\n\t}\n\treturn num_indices;\n}\n\nstatic int genTriangleStripIndices(void) {\n\tint num_indices = 0;\n\tuint16_t *const dst_idx = g_triapi.indices;\n\tfor (int i = 2; i < g_triapi.num_vertices; ++i) {\n\t\tif (num_indices > MAX_TRIAPI_INDICES - 3) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Triapi ran out of indices space, max %d (vertices=%d)\\n\", MAX_TRIAPI_INDICES, g_triapi.num_vertices);\n\t\t\tbreak;\n\t\t}\n\n\t\tif( i & 1 )\n\t\t{\n\t\t\t// draw triangle [n-1 n-2 n]\n\t\t\tdst_idx[num_indices++] = i - 1;\n\t\t\tdst_idx[num_indices++] = i - 2;\n\t\t\tdst_idx[num_indices++] = i;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// draw triangle [n-2 n-1 n]\n\t\t\tdst_idx[num_indices++] = i - 2;\n\t\t\tdst_idx[num_indices++] = i - 1;\n\t\t\tdst_idx[num_indices++] = i;\n\t\t}\n\t}\n\treturn num_indices;\n}\n\nstatic int genPolygonIndices(void) {\n\tint num_indices = 0;\n\tuint16_t *const dst_idx = g_triapi.indices;\n\tint num_vertices = g_triapi.num_vertices;\n\n\tif (num_vertices < 3)\n\t\treturn 0;\n\n\tfor (int i = 1; i < num_vertices - 1; ++i) {\n\t\tif (num_indices > MAX_TRIAPI_INDICES - 3) {\n\t\t\tgEngine.Con_Printf(S_ERROR \"Triapi ran out of indices space, max %d (vertices=%d)\\n\",\n\t\t\t\tMAX_TRIAPI_INDICES, g_triapi.num_vertices);\n\t\t\tbreak;\n\t\t}\n\n\t\t// Triangle fan: (0, i, i+1)\n\t\tdst_idx[num_indices++] = 0;\n\t\tdst_idx[num_indices++] = i;\n\t\tdst_idx[num_indices++] = i + 1;\n\t}\n\n\treturn num_indices;\n}\n\nvoid TriEnd( void ) {\n\tif (!g_triapi.primitive_mode)\n\t\treturn;\n\n\tconst vk_vertex_t *const v = g_triapi.vertices + g_triapi.num_vertices - 1;\n\tconst vec4_t color = {v->color[0] / 255.f, v->color[1] / 255.f, v->color[2] / 255.f, 1.f};\n\tTriEndEx( color, \"unnamed triapi\" );\n}\n\nvoid TriEndEx( const vec4_t color, const char* name ) {\n\tif (!g_triapi.primitive_mode)\n\t\treturn;\n\n\tint num_indices = 0;\n\tswitch(g_triapi.primitive_mode - 1) {\n\t\tcase TRI_TRIANGLES: num_indices = genTrianglesIndices(); break;\n\t\tcase TRI_TRIANGLE_STRIP: num_indices = genTriangleStripIndices(); break;\n\t\tcase TRI_QUADS: num_indices = genQuadsIndices(); break;\n\t\tcase TRI_POLYGON: num_indices = genPolygonIndices(); break;\n\t\tdefault:\n\t\t\tgEngine.Con_Printf(S_ERROR \"TriEnd: unsupported primitive_mode %d\\n\", g_triapi.primitive_mode - 1);\n\t\t\tbreak;\n\t}\n\n\tif (num_indices) {\n\t\tR_RenderDrawOnce((r_draw_once_t){\n\t\t\t.name = name,\n\t\t\t.vertices = g_triapi.vertices,\n\t\t\t.indices = g_triapi.indices,\n\t\t\t.vertices_count = g_triapi.num_vertices,\n\t\t\t.indices_count = num_indices,\n\t\t\t.render_type = g_triapi.render_type,\n\t\t\t.material = R_VkMaterialGetForTexture(g_triapi.texture_index),\n\t\t\t.ye_olde_texture = g_triapi.texture_index,\n\t\t\t.lightmap = g_triapi.lightmap_index,\n\t\t\t.emissive = (const vec4_t*)color,\n\t\t\t.color = (const vec4_t*)color,\n\t\t});\n\t}\n\n\tg_triapi.num_vertices = 0;\n\tg_triapi.primitive_mode = 0;\n}\n\nvoid TriTexCoord2f( float u, float v ) {\n\tvk_vertex_t *const ve = g_triapi.vertices + g_triapi.num_vertices;\n\tVector2Set(ve->gl_tc, u, v);\n}\n\nvoid TriLightmapCoord2f( float u, float v ) {\n\tvk_vertex_t *const ve = g_triapi.vertices + g_triapi.num_vertices;\n\tVector2Set(ve->lm_tc, u, v);\n}\n\nvoid TriVertex3fv( const float *v ) {\n\tTriVertex3f(v[0], v[1], v[2]);\n}\n\nvoid TriVertex3f( float x, float y, float z ) {\n\tif (g_triapi.num_vertices == MAX_TRIAPI_VERTICES - 1) {\n\t\tERROR_THROTTLED(1, \"vk TriApi: trying to emit more than %d vertices in one batch\\n\", MAX_TRIAPI_VERTICES);\n\t\treturn;\n\t}\n\n\tvk_vertex_t *const ve = g_triapi.vertices + g_triapi.num_vertices;\n\tVectorSet(ve->pos, x, y, z);\n\n\t// Emit vertex preserving previous vertex values\n\t++g_triapi.num_vertices;\n\tg_triapi.vertices[g_triapi.num_vertices] = g_triapi.vertices[g_triapi.num_vertices-1];\n}\n\nvoid TriColor4ub_( byte r, byte g, byte b, byte a ) {\n\tVector4Set(g_triapi.vertices[g_triapi.num_vertices].color, r, g, b, a);\n}\n\nvoid TriColor4f( float r, float g, float b, float a ) {\n\tTriColor4ub_(\n\t\tclampi32(r*255.f, 0, 255),\n\t\tclampi32(g*255.f, 0, 255),\n\t\tclampi32(b*255.f, 0, 255),\n\t\tclampi32(a*255.f, 0, 255));\n}\n\nvoid TriNormal3fv( const float *v ) {\n\tTriNormal3f(v[0], v[1], v[2]);\n}\n\nvoid TriNormal3f( float x, float y, float z ) {\n\tvk_vertex_t *const ve = g_triapi.vertices + g_triapi.num_vertices;\n\tVectorSet(ve->normal, x, y, z);\n}\n"
  },
  {
    "path": "ref/vk/vk_triapi.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\ntypedef struct model_s model_t;\n\nvoid TriRenderMode( int mode );\nvoid TriRenderType( int render_type );\nvoid TriSetTexture( int texture_index );\nvoid TriSetLightmap( int lightmap_index );\nint TriSpriteTexture( model_t *pSpriteModel, int frame );\n\nvoid TriBegin( int mode );\n\nvoid TriTexCoord2f( float u, float v );\nvoid TriLightmapCoord2f( float u, float v );\nvoid TriColor4f( float r, float g, float b, float a );\nvoid TriColor4ub_( byte r, byte g, byte b, byte a ); // FIXME consolidate with vk_renderstate\n\nvoid TriNormal3fv( const float *v );\nvoid TriNormal3f( float x, float y, float z );\n\n// Emits next vertex\nvoid TriVertex3fv( const float *v );\nvoid TriVertex3f( float x, float y, float z );\n\nvoid TriEnd( void );\nvoid TriEndEx( const vec4_t color, const char* name );\n"
  },
  {
    "path": "ref/vk/vulkan/VBarrier.c",
    "content": "#include \"VBarrier.h\"\n\n#include \"vk_logs.h\"\n#include \"VBuffer.h\"\n#include \"VImage.h\"\n#include \"VCombuf.h\"\n\n#define LOG_MODULE combuf\n\n#define ACCESS_WRITE_BITS (0 \\\n\t| VK_ACCESS_2_SHADER_WRITE_BIT \\\n\t| VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT \\\n\t| VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT \\\n\t| VK_ACCESS_2_TRANSFER_WRITE_BIT \\\n\t| VK_ACCESS_2_HOST_WRITE_BIT \\\n\t| VK_ACCESS_2_MEMORY_WRITE_BIT \\\n\t| VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT \\\n\t| VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR \\\n\t)\n\n#define ACCESS_READ_BITS (0 \\\n\t| VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT \\\n\t| VK_ACCESS_2_INDEX_READ_BIT \\\n\t| VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT \\\n\t| VK_ACCESS_2_UNIFORM_READ_BIT \\\n\t| VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT \\\n\t| VK_ACCESS_2_SHADER_READ_BIT \\\n\t| VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT \\\n\t| VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT \\\n\t| VK_ACCESS_2_TRANSFER_READ_BIT \\\n\t| VK_ACCESS_2_HOST_READ_BIT \\\n\t| VK_ACCESS_2_MEMORY_READ_BIT \\\n\t| VK_ACCESS_2_SHADER_SAMPLED_READ_BIT \\\n\t| VK_ACCESS_2_SHADER_STORAGE_READ_BIT \\\n\t| VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR \\\n\t)\n\n#define ACCESS_KNOWN_BITS (ACCESS_WRITE_BITS | ACCESS_READ_BITS)\n\n#define PRINT_FLAG(mask, flag) \\\n\tif ((flag) & (mask)) DEBUG(\"%s%s\", prefix, #flag)\nstatic void printAccessMask(const char *prefix, VkAccessFlags2 access) {\n\tPRINT_FLAG(access, VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_INDEX_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_UNIFORM_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_TRANSFER_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_TRANSFER_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_HOST_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_HOST_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_MEMORY_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_MEMORY_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_STORAGE_READ_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);\n\tPRINT_FLAG(access, VK_ACCESS_2_VIDEO_DECODE_READ_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_VIDEO_DECODE_WRITE_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_VIDEO_ENCODE_READ_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_VIDEO_ENCODE_WRITE_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_TRANSFORM_FEEDBACK_WRITE_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_CONDITIONAL_RENDERING_READ_BIT_EXT);\n#ifdef VK_EXT_device_generated_commands\n\tPRINT_FLAG(access, VK_ACCESS_2_COMMAND_PREPROCESS_READ_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_COMMAND_PREPROCESS_WRITE_BIT_EXT);\n#endif\n\tPRINT_FLAG(access, VK_ACCESS_2_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADING_RATE_IMAGE_READ_BIT_NV);\n\tPRINT_FLAG(access, VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_NV);\n\tPRINT_FLAG(access, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_NV);\n\tPRINT_FLAG(access, VK_ACCESS_2_FRAGMENT_DENSITY_MAP_READ_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_DESCRIPTOR_BUFFER_READ_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_INVOCATION_MASK_READ_BIT_HUAWEI);\n\tPRINT_FLAG(access, VK_ACCESS_2_SHADER_BINDING_TABLE_READ_BIT_KHR);\n\tPRINT_FLAG(access, VK_ACCESS_2_MICROMAP_READ_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_MICROMAP_WRITE_BIT_EXT);\n\tPRINT_FLAG(access, VK_ACCESS_2_OPTICAL_FLOW_READ_BIT_NV);\n\tPRINT_FLAG(access, VK_ACCESS_2_OPTICAL_FLOW_WRITE_BIT_NV);\n}\n\nstatic void printStageMask(const char *prefix, VkPipelineStageFlags2 stages) {\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_TESSELLATION_CONTROL_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_TESSELLATION_EVALUATION_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_HOST_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_COPY_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_RESOLVE_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_BLIT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_CLEAR_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_PRE_RASTERIZATION_SHADERS_BIT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_TRANSFORM_FEEDBACK_BIT_EXT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_CONDITIONAL_RENDERING_BIT_EXT);\n#ifdef VK_EXT_device_generated_commands\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_COMMAND_PREPROCESS_BIT_EXT);\n#endif\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_SHADING_RATE_IMAGE_BIT_NV);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_FRAGMENT_DENSITY_PROCESS_BIT_EXT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_TASK_SHADER_BIT_EXT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_SUBPASS_SHADER_BIT_HUAWEI);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_INVOCATION_MASK_BIT_HUAWEI);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT_KHR);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_CLUSTER_CULLING_SHADER_BIT_HUAWEI);\n\tPRINT_FLAG(stages, VK_PIPELINE_STAGE_2_OPTICAL_FLOW_BIT_NV);\n}\n\nstatic int makeBufferBarrier(VkBufferMemoryBarrier2* out_bmb, const r_vkcombuf_barrier_buffer_t *const bufbar, VkPipelineStageFlags2 dst_stage) {\n\tvk_buffer_t *const buf = bufbar->buffer;\n\tconst int is_write = (bufbar->access & ACCESS_WRITE_BITS) != 0;\n\tconst int is_read = (bufbar->access & ACCESS_READ_BITS) != 0;\n\tASSERT((bufbar->access & ~(ACCESS_KNOWN_BITS)) == 0);\n\n\t*out_bmb = (VkBufferMemoryBarrier2) {\n\t\t.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,\n\t\t.pNext = NULL,\n\t\t.buffer = buf->buffer,\n\t\t.offset = 0,\n\t\t.size = VK_WHOLE_SIZE,\n\t\t.dstStageMask = dst_stage,\n\t\t.dstAccessMask = bufbar->access,\n\t};\n\n\t// TODO: support read-and-write scenarios\n\tASSERT(is_read ^ is_write);\n\tif (is_write) {\n\t\t// Write is synchronized with previous reads and writes\n\t\tout_bmb->srcStageMask = buf->sync.write.stage | buf->sync.read.stage;\n\t\tout_bmb->srcAccessMask = buf->sync.write.access | buf->sync.read.access;\n\n\t\t// Store where write happened\n\t\tbuf->sync.write.access = bufbar->access;\n\t\tbuf->sync.write.stage = dst_stage;\n\n\t\t// If there were no previous reads or writes, there no reason to synchronize with anything\n\t\tif (out_bmb->srcStageMask == 0)\n\t\t\treturn 0;\n\n\t\t// Reset read state\n\t\t// TOOD is_read? for read-and-write\n\t\tbuf->sync.read.access = 0;\n\t\tbuf->sync.read.stage = 0;\n\t}\n\n\tif (is_read) {\n\t\t// Read is synchronized with previous writes only\n\t\tout_bmb->srcStageMask = buf->sync.write.stage;\n\t\tout_bmb->srcAccessMask = buf->sync.write.access;\n\n\t\t// Check whether this is a new barrier\n\t\tif ((buf->sync.read.access & bufbar->access) != bufbar->access\n\t\t\t&& (buf->sync.read.stage & dst_stage) != dst_stage) {\n\t\t\t// Remember this read happened\n\t\t\tbuf->sync.read.access |= bufbar->access;\n\t\t\tbuf->sync.read.stage |= dst_stage;\n\t\t} else {\n\t\t\t// Already synchronized, no need to do anything\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Also skip issuing a barrier, if there were no previous writes -- nothing to sync with\n\t\t// Note that this needs to happen late, as all reads must still be recorded in sync.read fields\n\t\tif (buf->sync.write.stage == 0)\n\t\t\treturn 0;\n\t}\n\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\"  srcAccessMask = %llx\", (unsigned long long)out_bmb->srcAccessMask);\n\t\tprintAccessMask(\"   \", out_bmb->srcAccessMask);\n\t\tDEBUG(\"  dstAccessMask = %llx\", (unsigned long long)out_bmb->dstAccessMask);\n\t\tprintAccessMask(\"   \", out_bmb->dstAccessMask);\n\t\tDEBUG(\"  srcStageMask = %llx\", (unsigned long long)out_bmb->srcStageMask);\n\t\tprintStageMask(\"   \", out_bmb->srcStageMask);\n\t\tDEBUG(\"  dstStageMask = %llx\", (unsigned long long)out_bmb->dstStageMask);\n\t\tprintStageMask(\"   \", out_bmb->dstStageMask);\n\t}\n\n\treturn 1;\n}\n\nstatic int makeImageBarrier(VkImageMemoryBarrier2* out_imb, const r_vkcombuf_barrier_image_t *const imgbar, VkPipelineStageFlags2 dst_stage) {\n\tr_vk_image_t *const img = imgbar->image;\n\tconst int is_write = (imgbar->access & ACCESS_WRITE_BITS) != 0;\n\tconst int is_read = (imgbar->access & ACCESS_READ_BITS) != 0;\n\tconst VkImageLayout old_layout = (!is_read) ? VK_IMAGE_LAYOUT_UNDEFINED : img->sync.layout;\n\tconst int is_layout_transfer = imgbar->layout != old_layout;\n\tASSERT((imgbar->access & ~(ACCESS_KNOWN_BITS)) == 0);\n\n\t*out_imb = (VkImageMemoryBarrier2) {\n\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,\n\t\t.pNext = NULL,\n\t\t.srcStageMask = img->sync.write.stage,\n\t\t.srcAccessMask = img->sync.write.access,\n\t\t.dstStageMask = dst_stage,\n\t\t.dstAccessMask = imgbar->access,\n\t\t.oldLayout = old_layout,\n\t\t.newLayout = imgbar->layout,\n\t\t.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,\n\t\t.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,\n\t\t.image = img->image,\n\t\t.subresourceRange = (VkImageSubresourceRange) {\n\t\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.baseMipLevel = 0,\n\t\t\t.levelCount = 1,\n\t\t\t.baseArrayLayer = 0,\n\t\t\t.layerCount = 1,\n\t\t},\n\t};\n\n\t// TODO: support read-and-write scenarios\n\t//ASSERT(is_read ^ is_write);\n\n\tif (is_write || is_layout_transfer) {\n\t\tout_imb->srcStageMask |= img->sync.read.stage;\n\t\tout_imb->srcAccessMask |= img->sync.read.access;\n\n\t\timg->sync.write.access = imgbar->access;\n\t\timg->sync.write.stage = dst_stage;\n\n\t\timg->sync.read.access = 0;\n\t\timg->sync.read.stage = 0;\n\t}\n\n\tif (is_read) {\n\t\tconst int same_access = (img->sync.read.access & imgbar->access) == imgbar->access;\n\t\tconst int same_stage = (img->sync.read.stage & dst_stage) == dst_stage;\n\n\t\tif (same_access && same_stage && !is_layout_transfer)\n\t\t\treturn 0;\n\n\t\timg->sync.read.access |= imgbar->access;\n\t\timg->sync.read.stage |= dst_stage;\n\n\t\t// Layout transfer makes write state no longer usable (supposedly)\n\t\tif (is_layout_transfer) {\n\t\t\timg->sync.write.access = 0;\n\t\t\timg->sync.write.stage = 0;\n\t\t}\n\t}\n\n\tif (!is_layout_transfer && out_imb->srcAccessMask == 0 && out_imb->srcStageMask == 0) {\n\t\treturn 0;\n\t}\n\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\"  srcAccessMask = %llx\", (unsigned long long)out_imb->srcAccessMask);\n\t\tprintAccessMask(\"   \", out_imb->srcAccessMask);\n\t\tDEBUG(\"  dstAccessMask = %llx\", (unsigned long long)out_imb->dstAccessMask);\n\t\tprintAccessMask(\"   \", out_imb->dstAccessMask);\n\t\tDEBUG(\"  srcStageMask = %llx\", (unsigned long long)out_imb->srcStageMask);\n\t\tprintStageMask(\"   \", out_imb->srcStageMask);\n\t\tDEBUG(\"  dstStageMask = %llx\", (unsigned long long)out_imb->dstStageMask);\n\t\tprintStageMask(\"   \", out_imb->dstStageMask);\n\t\tDEBUG(\"  oldLayout = %s (%llx)\", R_VkImageLayoutName(out_imb->oldLayout), (unsigned long long)out_imb->oldLayout);\n\t\tDEBUG(\"  newLayout = %s (%llx)\", R_VkImageLayoutName(out_imb->newLayout), (unsigned long long)out_imb->newLayout);\n\t}\n\n\t// Store new layout\n\timg->sync.layout = imgbar->layout;\n\n\treturn 1;\n}\n\nvoid barrierAddImage(Barrier *bar, r_vkcombuf_barrier_image_t image) {\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\" barrier img=0x%llx (%s) barrier:\", (unsigned long long)image.image->image, image.image->name);\n\t}\n\n\tVkImageMemoryBarrier2 imb;\n\tif (makeImageBarrier(&imb, &image, bar->stage))\n\t\tBOUNDED_ARRAY_APPEND_ITEM(bar->images, imb);\n}\n\nvoid barrierAddBuffer(Barrier *bar, r_vkcombuf_barrier_buffer_t buffer) {\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\" barrier buf=0x%llx (%s) barrier:\",\n\t\t\t(unsigned long long)buffer.buffer->buffer,\n\t\t\tbuffer.buffer->name);\n\t}\n\n\tVkBufferMemoryBarrier2 bmb;\n\tif (makeBufferBarrier(&bmb, &buffer, bar->stage)) {\n\t\tBOUNDED_ARRAY_APPEND_ITEM(bar->buffers, bmb);\n\t}\n}\n\nvoid barrierCommit(Barrier *bar, struct vk_combuf_s * combuf) {\n\tASSERT(bar->stage != 0);\n\n\tif (bar->buffers.count == 0 && bar->images.count == 0)\n\t\treturn;\n\n\tvkCmdPipelineBarrier2(combuf->cmdbuf, &(VkDependencyInfo) {\n\t\t.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,\n\t\t.pNext = NULL,\n\t\t.dependencyFlags = 0,\n\t\t.bufferMemoryBarrierCount = bar->buffers.count,\n\t\t.pBufferMemoryBarriers = bar->buffers.items,\n\t\t.imageMemoryBarrierCount = bar->images.count,\n\t\t.pImageMemoryBarriers = bar->images.items,\n\t});\n\n\tbar->stage = 0;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VBarrier.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n#include \"std/arrays.h\"\n\n#define MAX_BUFFER_BARRIERS 16\n#define MAX_IMAGE_BARRIERS 32\n\nstruct vk_combuf_s;\n\nstruct vk_buffer_s;\ntypedef struct {\n\tstruct vk_buffer_s *buffer;\n\tVkAccessFlags2 access;\n} r_vkcombuf_barrier_buffer_t;\n\nstruct r_vk_image_s;\ntypedef struct {\n\tstruct r_vk_image_s *image;\n\tVkImageLayout layout;\n\tVkAccessFlags2 access;\n} r_vkcombuf_barrier_image_t;\n\ntypedef struct Barrier {\n\tVkPipelineStageFlagBits2 stage;\n\tBOUNDED_ARRAY_DECLARE(VkBufferMemoryBarrier2, buffers, MAX_BUFFER_BARRIERS);\n\tBOUNDED_ARRAY_DECLARE(VkImageMemoryBarrier2, images, MAX_IMAGE_BARRIERS);\n} Barrier;\n\nstatic inline Barrier barrierMake(VkPipelineStageFlagBits2 stage) {\n\treturn (Barrier) {\n\t\t.stage = stage,\n\t\t.images.count = 0,\n\t\t.buffers.count = 0,\n\t};\n}\n\nvoid barrierAddImage(Barrier *, r_vkcombuf_barrier_image_t);\nvoid barrierAddBuffer(Barrier *, r_vkcombuf_barrier_buffer_t);\nvoid barrierCommit(Barrier *, struct vk_combuf_s *);\n"
  },
  {
    "path": "ref/vk/vulkan/VBuffer.c",
    "content": "#include \"VBuffer.h\"\n#include \"vk_logs.h\"\n#include \"VCombuf.h\"\n#include \"VBarrier.h\"\n\n#include \"std/arrays.h\"\n\n#define LOG_MODULE buf\n\nqboolean VK_BufferCreate(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags)\n{\n\tVkBufferCreateInfo bci = {\n\t\t.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,\n\t\t.size = size,\n\t\t.usage = usage,\n\t\t.sharingMode = VK_SHARING_MODE_EXCLUSIVE,\n\t};\n\tVkMemoryRequirements memreq;\n\tXVK_CHECK(vkCreateBuffer(vk_core.device, &bci, NULL, &buf->buffer));\n\tSET_DEBUG_NAME(buf->buffer, VK_OBJECT_TYPE_BUFFER, debug_name);\n\n\tvkGetBufferMemoryRequirements(vk_core.device, buf->buffer, &memreq);\n\n\tif (usage & VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR) {\n\t\tmemreq.alignment = ALIGN_UP(memreq.alignment, v_device_info.properties_ray_tracing_pipeline.shaderGroupBaseAlignment);\n\t}\n\n\tvk_devmem_allocate_args_t args = (vk_devmem_allocate_args_t) {\n\t\t.requirements = memreq,\n\t\t.property_flags = flags,\n\t\t.allocate_flags = (usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) ? VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT : 0,\n\t};\n\tbuf->devmem = VK_DevMemAllocateBuffer( debug_name, args );\n\n\tXVK_CHECK(vkBindBufferMemory(vk_core.device, buf->buffer, buf->devmem.device_memory, buf->devmem.offset));\n\n\tbuf->mapped = buf->devmem.mapped;\n\tbuf->size = size;\n\tbuf->name = debug_name;\n\n\tINFO(\"Created buffer=%llx, name=\\\"%s\\\", size=%u\", (unsigned long long)buf->buffer, debug_name, size);\n\n\treturn true;\n}\n\nvoid VK_BufferDestroy(vk_buffer_t *buf) {\n\t// FIXME destroy staging slot\n\n\tif (buf->buffer) {\n\t\tvkDestroyBuffer(vk_core.device, buf->buffer, NULL);\n\t\tbuf->buffer = VK_NULL_HANDLE;\n\t}\n\n\tif (buf->devmem.device_memory) {\n\t\tVK_DevMemFree(&buf->devmem);\n\t\tbuf->devmem.device_memory = VK_NULL_HANDLE;\n\t\tbuf->devmem.offset = 0;\n\t\tbuf->mapped = 0;\n\t\tbuf->size = 0;\n\t}\n}\n\nVkDeviceAddress R_VkBufferGetDeviceAddress(VkBuffer buffer) {\n\tconst VkBufferDeviceAddressInfo bdai = {.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = buffer};\n\treturn vkGetBufferDeviceAddress(vk_core.device, &bdai);\n}\n\nvoid R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size) {\n\tR_FlippingBuffer_Init(&debuf->dynamic, dynamic_size);\n\tdebuf->static_size = static_size;\n\tdebuf->static_offset = 0;\n}\n\nuint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align) {\n\tswitch (lifetime) {\n\t\tcase LifetimeDynamic:\n\t\t{\n\t\t\tconst uint32_t offset = R_FlippingBuffer_Alloc(&debuf->dynamic, size, align);\n\t\t\tif (offset == ALO_ALLOC_FAILED)\n\t\t\t\treturn ALO_ALLOC_FAILED;\n\t\t\treturn offset + debuf->static_size;\n\t\t}\n\t\tcase LifetimeStatic:\n\t\t{\n\t\t\tconst uint32_t offset = ALIGN_UP(debuf->static_offset, align);\n\t\t\tconst uint32_t end = offset + size;\n\t\t\tif (end > debuf->static_size)\n\t\t\t\treturn ALO_ALLOC_FAILED;\n\n\t\t\tdebuf->static_offset = end;\n\t\t\treturn offset;\n\t\t}\n\t}\n\n\treturn ALO_ALLOC_FAILED;\n}\n\nvoid R_DEBuffer_Flip(r_debuffer_t* debuf) {\n\tR_FlippingBuffer_Flip(&debuf->dynamic);\n}\n\n#define MAX_STAGING_BUFFERS 16\n#define MAX_STAGING_ENTRIES 3072\n\n// TODO this should be part of the vk_buffer_t object itself\ntypedef struct {\n\tvk_buffer_t *buffer;\n\tr_vkstaging_user_handle_t staging_handle;\n\tVkBuffer staging_buffer;\n\tBOUNDED_ARRAY_DECLARE(VkBufferCopy, regions, MAX_STAGING_ENTRIES);\n} r_vk_staging_buffer_t;\n\n// TODO remove this when staging is tracked by the buffer object itself\nstatic struct {\n\tBOUNDED_ARRAY_DECLARE(r_vk_staging_buffer_t, staging, MAX_STAGING_BUFFERS);\n} g_buf;\n\nstatic r_vk_staging_buffer_t *findExistingStagingSlotForBuffer(vk_buffer_t *buf) {\n\tfor (int i = 0; i < g_buf.staging.count; ++i) {\n\t\tr_vk_staging_buffer_t *const stb = g_buf.staging.items + i;\n\t\tif (stb->buffer == buf)\n\t\t\treturn stb;\n\t}\n\n\treturn NULL;\n}\n\nstatic void stagingBufferPush(void* userptr, struct vk_combuf_s *combuf, uint32_t pending) {\n\tr_vk_staging_buffer_t *const stb = userptr;\n\tASSERT(pending == stb->regions.count);\n\tR_VkBufferStagingCommit(stb->buffer, combuf);\n}\n\nstatic r_vk_staging_buffer_t *findOrCreateStagingSlotForBuffer(vk_buffer_t *buf) {\n\tr_vk_staging_buffer_t *stb = findExistingStagingSlotForBuffer(buf);\n\tif (stb)\n\t\treturn stb;\n\n\tASSERT(BOUNDED_ARRAY_HAS_SPACE(g_buf.staging, 1));\n\tstb = &BOUNDED_ARRAY_APPEND_UNSAFE(g_buf.staging);\n\tstb->staging_buffer = VK_NULL_HANDLE;\n\tstb->buffer = buf;\n\tstb->regions.count = 0;\n\tstb->staging_handle = R_VkStagingUserCreate((r_vkstaging_user_create_t){\n\t\t.name = buf->name,\n\t\t.userptr = stb,\n\t\t.push = stagingBufferPush,\n\t});\n\treturn stb;\n}\n\nvk_buffer_locked_t R_VkBufferLock(vk_buffer_t *buf, vk_buffer_lock_t lock) {\n\t//DEBUG(\"Lock buf=%p size=%d region=%d..%d\", buf, lock.size, lock.offset, lock.offset + lock.size);\n\n\tr_vk_staging_buffer_t *const stb = findOrCreateStagingSlotForBuffer(buf);\n\tASSERT(stb);\n\n\tr_vkstaging_region_t staging_lock = R_VkStagingLock(stb->staging_handle, lock.size);\n\tASSERT(staging_lock.ptr);\n\n\t// TODO perf: adjacent region coalescing\n\n\tASSERT(BOUNDED_ARRAY_HAS_SPACE(stb->regions, 1));\n\tBOUNDED_ARRAY_APPEND_UNSAFE(stb->regions) = (VkBufferCopy){\n\t\t.srcOffset = staging_lock.offset,\n\t\t.dstOffset = lock.offset,\n\t\t.size = lock.size,\n\t};\n\n\tif (stb->staging_buffer != VK_NULL_HANDLE)\n\t\t// TODO implement this if staging ever grows to multiple buffers\n\t\tASSERT(stb->staging_buffer == staging_lock.buffer);\n\telse\n\t\tstb->staging_buffer = staging_lock.buffer;\n\n\treturn (vk_buffer_locked_t) {\n\t\t.ptr = staging_lock.ptr,\n\t\t.impl_ = {\n\t\t\t.buf = buf,\n\t\t},\n\t};\n}\n\nvoid R_VkBufferUnlock(vk_buffer_locked_t lock) {\n\t//DEBUG(\"buf=%llx staging pending++\", (unsigned long long)lock.impl_.buf->buffer);\n\t// Nothing to do?\n}\n\nvoid R_VkBufferStagingCommit(vk_buffer_t *buf, struct vk_combuf_s *combuf) {\n\tr_vk_staging_buffer_t *const stb = findExistingStagingSlotForBuffer(buf);\n\tif (!stb || stb->regions.count == 0)\n\t\treturn;\n\n\t{\n\t\t// TODO accept external barrier in a particular context, could be an optimization for group barriers\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_COPY_BIT);\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = buf,\n\t\t\t.access = VK_ACCESS_TRANSFER_WRITE_BIT,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\t//TODO const int begin_index = R_VkCombufScopeBegin(combuf, g_staging.buffer_upload_scope_id);\n\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\tDEBUG_NV_CHECKPOINTF(cmdbuf, \"staging dst_buffer=%p count=%d\", buf->buffer, stb->regions.count);\n\t//DEBUG(\"buffer=%p copy %d regions from staging buffer=%p\", buf->buffer, stb->regions.count, stb->staging);\n\tvkCmdCopyBuffer(cmdbuf, stb->staging_buffer, buf->buffer, stb->regions.count, stb->regions.items);\n\n\tDEBUG(\"buf=%llx staging pending-=%u\", (unsigned long long)buf->buffer, stb->regions.count);\n\tR_VkStagingUnlockBulk(stb->staging_handle, stb->regions.count);\n\tstb->regions.count = 0;\n\n\t//TODO R_VkCombufScopeEnd(combuf, begin_index, VK_PIPELINE_STAGE_TRANSFER_BIT);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VBuffer.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n#include \"VDevmem.h\"\n#include \"VStaging.h\"\n#include \"std/flipping.h\"\n\ntypedef struct {\n\tr_vksync_scope_t write, read;\n} r_vksync_state_t;\n\ntypedef struct vk_buffer_s {\n\tconst char *name; // static\n\tvk_devmem_t devmem;\n\tVkBuffer buffer;\n\n\tvoid *mapped;\n\tuint32_t size;\n\n\tr_vksync_state_t sync;\n} vk_buffer_t;\n\nqboolean VK_BufferCreate(const char *debug_name, vk_buffer_t *buf, uint32_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags flags);\nvoid VK_BufferDestroy(vk_buffer_t *buf);\n\nVkDeviceAddress R_VkBufferGetDeviceAddress(VkBuffer buffer);\n\ntypedef struct {\n\tr_flipping_buffer_t dynamic;\n\tuint32_t static_size;\n\tuint32_t static_offset;\n} r_debuffer_t;\n\ntypedef enum {\n\tLifetimeStatic, LifetimeDynamic,\n} r_lifetime_t;\n\nvoid R_DEBuffer_Init(r_debuffer_t *debuf, uint32_t static_size, uint32_t dynamic_size);\nuint32_t R_DEBuffer_Alloc(r_debuffer_t* debuf, r_lifetime_t lifetime, uint32_t size, uint32_t align);\nvoid R_DEBuffer_Flip(r_debuffer_t* debuf);\n\ntypedef struct {\n\tvoid *ptr;\n\n\tstruct {\n\t\tvk_buffer_t *buf;\n\t} impl_;\n} vk_buffer_locked_t;\n\ntypedef struct {\n\tuint32_t offset;\n\tuint32_t size;\n} vk_buffer_lock_t;\n\nvk_buffer_locked_t R_VkBufferLock(vk_buffer_t *buf, vk_buffer_lock_t lock);\n\nvoid R_VkBufferUnlock(vk_buffer_locked_t lock);\n\n// Commits any staged regions for the specified buffer\nstruct vk_combuf_s;\nvoid R_VkBufferStagingCommit(vk_buffer_t *buf, struct vk_combuf_s *combuf);\n"
  },
  {
    "path": "ref/vk/vulkan/VCombuf.c",
    "content": "#include \"VCombuf.h\"\n#include \"VCommandPool.h\"\n#include \"VPerfQuery.h\"\n#include \"vk_logs.h\"\n\n#include \"std/arrays.h\"\n#include \"std/profiler.h\"\n\n#define LOG_MODULE combuf\n\n#define MAX_GPU_SCOPES 64\n#define MAX_COMMANDBUFFERS 6\n#define MAX_TIMESTAMP_QUERIES 128\n#define MAX_PERFORMANCE_QUERIES 64\n#define MAX_PERFORMANCE_QUERY_COUNTERS 16\n\n// Rough theoretical max is (((MAX_TIMESTAMP_QUERIES) + (MAX_PERFORMANCE_QUERIES) * (MAX_PERFORMANCE_QUERY_COUNTERS))\n#define MAX_PROF_EVENTS 1024\n\nstatic const char* myStrdup(const char *src) {\n\tconst int len = strlen(src);\n\tchar *ret = Mem_Malloc(vk_core.pool, len + 1);\n\tmemcpy(ret, src, len);\n\tret[len] = '\\0';\n\treturn ret;\n}\n\ntypedef struct {\n\tint refcount;\n\tVPerfQuery *query;\n\tARRAY_DYNAMIC_DECLARE(uint32_t, counters);\n} PerfQuery;\n\ntypedef struct {\n\tvk_combuf_t public;\n\tint used;\n\tstruct {\n\t\t// Offset into timestamp query results array\n\t\tint timestamps_offset;\n\t\tint timestamp_queries;\n\n\t\taprof_event_t events[MAX_PROF_EVENTS];\n\t\tint events_count;\n\n\t\tPerfQuery *perf_query;\n\t\tint active_perf_query;\n\n\t} profiler;\n} vk_combuf_impl_t;\n\nstatic struct {\n\tvk_command_pool_t pool;\n\n\tvk_combuf_impl_t combufs[MAX_COMMANDBUFFERS];\n\tstruct {\n\t\tVkQueryPool pool;\n\t\tuint64_t values[MAX_TIMESTAMP_QUERIES * MAX_COMMANDBUFFERS];\n\t} timestamp;\n\n\taprof_scope_t scopes[MAX_GPU_SCOPES];\n\tint scopes_count;\n\n\tint entire_combuf_scope_id;\n\n\tstruct {\n\t\t// Current performance query, for the next command buffer to acquire\n\t\tPerfQuery *pquery;\n\n\t\t// Global set of gpu perf query counters\n\t\tARRAY_DYNAMIC_DECLARE(aprof_counter_desc_t, aprof_counters);\n\t} perf;\n} g_combuf;\n\nstatic PerfQuery *makePerfQuery(const uint32_t *counters, uint32_t counters_count) {\n\tif (counters_count == 0)\n\t\treturn NULL;\n\n\t// Validate counters\n\tfor (uint32_t i = 0; i < counters_count; ++i) {\n\t\tconst uint32_t counter = counters[i];\n\t\tif (counter > v_device_info.perf_counters.count) {\n\t\t\tERR(\"Counter %u is invalid, max %u counters are available\", counter, v_device_info.perf_counters.count);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tfor (uint32_t j = 0; j < i; ++j) {\n\t\t\tif (counters[j] == counter) {\n\t\t\t\tERR(\"Duplicate counter %u\", counter);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t}\n\n\tVPerfQuery *const query = vPerfQueryCreate(counters, counters_count, MAX_COMMANDBUFFERS * MAX_TIMESTAMP_QUERIES);\n\tif (!query) {\n\t\tERR(\"Couldn't create performance query with %u counters\", counters_count);\n\t\treturn NULL;\n\t}\n\n\tPerfQuery *pq = Mem_Malloc(vk_core.pool, sizeof(*pq));\n\tpq->refcount = 0; // Start not acquired\n\tpq->query = query;\n\n\tarrayDynamicInitT(&pq->counters);\n\tarrayDynamicResizeT(&pq->counters, counters_count);\n\tfor (uint32_t i = 0; i < counters_count; ++i) {\n\t\tpq->counters.items[i] = counters[i];\n\t}\n\n\treturn pq;\n}\n\nstatic PerfQuery *acquirePerfQuery(void) {\n\tif (!g_combuf.perf.pquery)\n\t\treturn NULL;\n\n\tg_combuf.perf.pquery->refcount++;\n\treturn g_combuf.perf.pquery;\n}\n\nstatic void releasePerfQuery(PerfQuery *pq) {\n\tif (!pq)\n\t\treturn;\n\n\tASSERT(pq->refcount > 0);\n\tpq->refcount--;\n\tif (pq->refcount > 0)\n\t\treturn;\n\n\tvPerfQueryDestroy(pq->query);\n\tarrayDynamicDestroyT(&pq->counters);\n\tMem_Free(pq);\n}\n\nstatic aprof_counter_unit_t counterUnit(VkPerformanceCounterUnitKHR unit) {\n\tswitch (unit) {\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_PERCENTAGE_KHR:\n\t\t\treturn AprofCounterUnit_Permyriad;\n\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_NANOSECONDS_KHR:\n\t\t\treturn AprofCounterUnit_Nanoseconds;\n\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_BYTES_KHR:\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_BYTES_PER_SECOND_KHR:\n\t\t\treturn AprofCounterUnit_Bytes;\n\n\t\tdefault:\n\t\t\treturn AprofCounterUnit_Generic;\n\t}\n}\n\nqboolean R_VkCombuf_Init( void ) {\n\tg_combuf.pool = R_VkCommandPoolCreate(MAX_COMMANDBUFFERS);\n\tif (!g_combuf.pool.pool)\n\t\treturn false;\n\n\tconst VkQueryPoolCreateInfo qpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,\n\t\t.pNext = NULL,\n\t\t.queryType = VK_QUERY_TYPE_TIMESTAMP,\n\t\t.queryCount = COUNTOF(g_combuf.timestamp.values),\n\t\t.flags = 0,\n\t};\n\n\tXVK_CHECK(vkCreateQueryPool(vk_core.device, &qpci, NULL, &g_combuf.timestamp.pool));\n\n\tfor (int i = 0; i < MAX_COMMANDBUFFERS; ++i) {\n\t\tvk_combuf_impl_t *const cb = g_combuf.combufs + i;\n\n\t\tcb->public.cmdbuf = g_combuf.pool.buffers[i];\n\t\tSET_DEBUG_NAMEF(cb->public.cmdbuf, VK_OBJECT_TYPE_COMMAND_BUFFER, \"cmdbuf[%d]\", i);\n\n\t\tcb->profiler.timestamps_offset = i * MAX_TIMESTAMP_QUERIES;\n\t}\n\n\tg_combuf.entire_combuf_scope_id = R_VkGpuScope_Register(\"GPU\");\n\n\t// Initialize global-lifetime counters descriptors\n\tarrayDynamicInitT(&g_combuf.perf.aprof_counters);\n\tif (v_device_info.perf_counters.count > 0) {\n\t\tarrayDynamicResizeT(&g_combuf.perf.aprof_counters, v_device_info.perf_counters.count);\n\t\tfor (uint32_t i = 0; i < v_device_info.perf_counters.count; ++i) {\n\t\t\taprof_counter_desc_t *const cdesc = g_combuf.perf.aprof_counters.items + i;\n\t\t\tcdesc->name = myStrdup(v_device_info.perf_counters.desc[i].name);\n\t\t\tcdesc->unit = counterUnit(v_device_info.perf_counters.counters[i].unit);\n\t\t}\n\t}\n\n\treturn true;\n}\n\nvoid R_VkCombuf_Destroy( void ) {\n\tfor (int i = 0; i < MAX_COMMANDBUFFERS; ++i) {\n\t\tvk_combuf_impl_t *const cb = g_combuf.combufs + i;\n\t\treleasePerfQuery(cb->profiler.perf_query);\n\t\tcb->profiler.perf_query = NULL;\n\t}\n\treleasePerfQuery(g_combuf.perf.pquery);\n\n\tfor (uint32_t i = 0; i < g_combuf.perf.aprof_counters.count; ++i) {\n\t\tconst aprof_counter_desc_t *const cdesc = g_combuf.perf.aprof_counters.items + i;\n\t\tMem_Free((char*)cdesc->name);\n\t}\n\tarrayDynamicDestroyT(&g_combuf.perf.aprof_counters);\n\n\tvkDestroyQueryPool(vk_core.device, g_combuf.timestamp.pool, NULL);\n\tR_VkCommandPoolDestroy(&g_combuf.pool);\n\n\tfor (int i = 0; i < g_combuf.scopes_count; ++i) {\n\t\tMem_Free((char*)g_combuf.scopes[i].name);\n\t}\n}\n\nvk_combuf_t* R_VkCombufOpen( void ) {\n\tfor (int i = 0; i < MAX_COMMANDBUFFERS; ++i) {\n\t\tvk_combuf_impl_t *const cb = g_combuf.combufs + i;\n\t\tif (!cb->used) {\n\t\t\tcb->used = 1;\n\t\t\tcb->profiler.active_perf_query = -1;\n\t\t\treturn &cb->public;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nvoid R_VkCombufClose( vk_combuf_t* pub ) {\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)pub;\n\tcb->used = 0;\n\n\t// TODO synchronize?\n\t// For now, external synchronization expected\n}\n\nvoid R_VkCombufBegin( vk_combuf_t* pub ) {\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)pub;\n\n\tcb->profiler.events_count = 0;\n\tcb->profiler.timestamp_queries = 0;\n\tcb->profiler.active_perf_query = -1;\n\n\t// Release previous perf query (if any), and acquire a new one\n\treleasePerfQuery(cb->profiler.perf_query);\n\tcb->profiler.perf_query = NULL;\n\tcb->profiler.perf_query = acquirePerfQuery();\n\n\tconst VkCommandBufferBeginInfo beginfo = {\n\t\t.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,\n\t\t.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,\n\t};\n\tXVK_CHECK(vkBeginCommandBuffer(cb->public.cmdbuf, &beginfo));\n\n\tvkCmdResetQueryPool(cb->public.cmdbuf, g_combuf.timestamp.pool, cb->profiler.timestamps_offset, MAX_TIMESTAMP_QUERIES);\n\tR_VkCombufScopeBegin(pub, g_combuf.entire_combuf_scope_id, VCombufScopeFlag_None);\n}\n\nvoid R_VkCombufEnd( vk_combuf_t* pub ) {\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)pub;\n\n\tR_VkCombufScopeEnd(pub, 0, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);\n\tXVK_CHECK(vkEndCommandBuffer(cb->public.cmdbuf));\n}\n\nint R_VkGpuScope_Register(const char *name) {\n\t// Find existing scope with the same name\n\tfor (int i = 0; i < g_combuf.scopes_count; ++i) {\n\t\tif (Q_strcmp(name, g_combuf.scopes[i].name) == 0)\n\t\t\treturn i;\n\t}\n\n\tif (g_combuf.scopes_count == MAX_GPU_SCOPES) {\n\t\tgEngine.Con_Printf(S_ERROR \"Cannot register GPU profiler scope \\\"%s\\\": max number of scope %d reached\\n\", name, MAX_GPU_SCOPES);\n\t\treturn -1;\n\t}\n\n\tg_combuf.scopes[g_combuf.scopes_count] = (aprof_scope_t) {\n\t\t.name = myStrdup(name),\n\t\t.flags = 0,\n\t\t.source_file = __FILE__, // TODO\n\t\t.source_line = __LINE__, // TODO\n\t};\n\n\treturn g_combuf.scopes_count++;\n}\n\nstatic int combufAppendPerfEvent(vk_combuf_impl_t *cb, aprof_event_t event) {\n\tif (cb->profiler.events_count >= COUNTOF(cb->profiler.events)) {\n\t\tERROR_THROTTLED(10, \"Command buffer %p ran out of profiler event slots (max %d) trying to write event %#08llx\",\n\t\t\tcb, (int)COUNTOF(cb->profiler.events), (unsigned long long)event);\n\t\treturn -1;\n\t}\n\n\tconst int event_index = cb->profiler.events_count++;\n\tcb->profiler.events[event_index] = event;\n\treturn event_index;\n}\n\nstatic void scopePerfQueryBegin(vk_combuf_impl_t *cb, uint32_t flags) {\n\t// There should be no active query\n\tASSERT(cb->profiler.active_perf_query == -1);\n\n\tif ((flags & VCombufScopeFlag_PerfQuery) == 0)\n\t\treturn;\n\n\tif (!cb->profiler.perf_query)\n\t\treturn;\n\n\tconst int perf_query_index = vPerfQueryBegin(cb->profiler.perf_query->query, &cb->public);\n\n\tif (LOG_VERBOSE)\n\t\tDEBUG(\"Begin perf_query id=%d\", perf_query_index);\n\n\tif (perf_query_index < 0)\n\t\treturn;\n\n\tcb->profiler.active_perf_query = perf_query_index;\n}\n\nstatic void scopePerfQueryEnd(vk_combuf_impl_t *cb) {\n\tif (cb->profiler.active_perf_query < 0)\n\t\treturn;\n\n\tASSERT(cb->profiler.perf_query);\n\n\tif (LOG_VERBOSE)\n\t\tDEBUG(\"End perf_query id=%d\", cb->profiler.active_perf_query);\n\n\tvPerfQueryEnd(cb->profiler.perf_query->query, &cb->public, cb->profiler.active_perf_query);\n\n\tfor (size_t i = 0; i < cb->profiler.perf_query->counters.count; ++i) {\n\t\tcombufAppendPerfEvent(cb, APROF_EVENT_MAKE_COUNTER(i, cb->profiler.active_perf_query));\n\t}\n\n\tcb->profiler.active_perf_query = -1;\n}\n\nstatic int writeTimestamp(vk_combuf_impl_t *cb, int scope_id, int event_type, VkPipelineStageFlagBits pipeline_stage) {\n\tif (cb->profiler.timestamp_queries >= MAX_TIMESTAMP_QUERIES) {\n\t\tERROR_THROTTLED(10, \"Command buffer %p ran out of max timestamp query slots (%d) with scope \\\"%s\\\" (%d)\",\n\t\t\tcb, MAX_TIMESTAMP_QUERIES, g_combuf.scopes[scope_id].name, scope_id);\n\t\treturn -1;\n\t}\n\n\tconst uint32_t timestamp_index = cb->profiler.timestamp_queries++;\n\tconst uint32_t timestamp_query_index = cb->profiler.timestamps_offset + timestamp_index;\n\tvkCmdWriteTimestamp(cb->public.cmdbuf, pipeline_stage, g_combuf.timestamp.pool, timestamp_query_index);\n\treturn combufAppendPerfEvent(cb, APROF_EVENT_MAKE(event_type, scope_id, timestamp_index));\n}\n\nint R_VkCombufScopeBegin(vk_combuf_t* cumbuf, int scope_id, uint32_t flags) {\n\tif (scope_id < 0)\n\t\treturn -1;\n\n\tASSERT(scope_id < g_combuf.scopes_count);\n\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\"Begin scope id=%d (%s) flags=%#x\", scope_id, g_combuf.scopes[scope_id].name, flags);\n\t}\n\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)cumbuf;\n\tconst int event_index = writeTimestamp(cb, scope_id, APROF_EVENT_SCOPE_BEGIN, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);\n\n\tscopePerfQueryBegin(cb, flags);\n\n\treturn event_index;\n}\n\nvoid R_VkCombufScopeEnd(vk_combuf_t* combuf, int begin_index, VkPipelineStageFlagBits pipeline_stage) {\n\tif (begin_index < 0)\n\t\treturn;\n\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)combuf;\n\t// TODO: ASSERT that this is the right scope\n\tconst int scope_id = APROF_EVENT_SCOPE_ID(cb->profiler.events[begin_index]);\n\n\tif (LOG_VERBOSE) {\n\t\tDEBUG(\"End scope id=%d (%s)\", scope_id, g_combuf.scopes[scope_id].name);\n\t}\n\n\tscopePerfQueryEnd(cb);\n\twriteTimestamp(cb, scope_id, APROF_EVENT_SCOPE_END, pipeline_stage);\n}\n\nint R_VkCombufPerfQueryEnable(const uint32_t *counters, uint32_t counters_count) {\n\tif (!v_device_info.perf_query) {\n\t\tERR(\"Cannot enable perf query counters, as VK_KHR_performance_query is not available, or -vkperfquery was not supplied\");\n\t\treturn 0;\n\t}\n\n\tPerfQuery *const new_query = makePerfQuery(counters, counters_count);\n\n\treleasePerfQuery(g_combuf.perf.pquery);\n\tg_combuf.perf.pquery = new_query;\n\n\t// Make sure that it's properly acquired\n\tacquirePerfQuery();\n\n\treturn 1;\n}\n\nstatic uint64_t getGpuTimestampOffsetNs( uint64_t latest_gpu_timestamp, uint64_t latest_cpu_timestamp_ns ) {\n\t// FIXME this is an incorrect check, we need to carry per-device extensions availability somehow. vk_core-vs-device refactoring pending\n\tif (!vkGetCalibratedTimestampsEXT) {\n\t\t// Estimate based on supposed submission time, assuming that we submit, and it starts computing right after cmdbuffer closure\n\t\t// which may not be true. But it's all we got\n\t\t// TODO alternative approach: estimate based on end timestamp\n\t\tconst uint64_t gpu_begin_ns = (double) latest_gpu_timestamp * v_device_info.properties.limits.timestampPeriod;\n\t\treturn latest_cpu_timestamp_ns - gpu_begin_ns;\n\t}\n\n\tconst VkCalibratedTimestampInfoEXT cti[2] = {\n\t\t{\n\t\t\t.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,\n\t\t\t.pNext = NULL,\n\t\t\t.timeDomain = VK_TIME_DOMAIN_DEVICE_EXT,\n\t\t},\n\t\t{\n\t\t\t.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,\n\t\t\t.pNext = NULL,\n#if defined(_WIN32)\n\t\t\t.timeDomain = VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT,\n#else\n\t\t\t.timeDomain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT,\n#endif\n\t\t},\n\t};\n\n\tuint64_t timestamps[2] = {0};\n\tuint64_t max_deviation[2] = {0};\n\tvkGetCalibratedTimestampsEXT(vk_core.device, 2, cti, timestamps, max_deviation);\n\n\tconst uint64_t cpu = aprof_time_platform_to_ns(timestamps[1]);\n\tconst uint64_t gpu = (double)timestamps[0] * v_device_info.properties.limits.timestampPeriod;\n\treturn cpu - gpu;\n}\n\nstatic void patchTimestampQueryEvents(vk_combuf_impl_t *cb) {\n\tconst int timestamps_count = cb->profiler.timestamp_queries;\n\tif (timestamps_count <= 0)\n\t\treturn;\n\n\tASSERT(timestamps_count <= MAX_TIMESTAMP_QUERIES);\n\tuint64_t timestamps[MAX_TIMESTAMP_QUERIES];\n\n\tXVK_CHECK(vkGetQueryPoolResults(vk_core.device, g_combuf.timestamp.pool, cb->profiler.timestamps_offset,\n\t\ttimestamps_count, timestamps_count * sizeof(uint64_t),\n\t\ttimestamps, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT));\n\n\tconst uint64_t timestamp_offset_ns = getGpuTimestampOffsetNs(timestamps[1], aprof_time_now_ns());\n\t// `double` is necessary here for 64 bit precision\n\tconst double timestamp_period = v_device_info.properties.limits.timestampPeriod;\n\n\t// Patch timestamp events with timestamp indexes with real timestamp values\n\tfor (int i = 0; i < cb->profiler.events_count; ++i) {\n\t\taprof_event_t *const event = &cb->profiler.events[i];\n\t\tconst int event_type = APROF_EVENT_TYPE(*event);\n\t\tswitch (event_type) {\n\t\t\tcase APROF_EVENT_SCOPE_BEGIN:\n\t\t\tcase APROF_EVENT_SCOPE_END:\n\t\t\t\t{\n\t\t\t\t\tconst uint64_t scope_id = APROF_EVENT_SCOPE_ID(*event);\n\t\t\t\t\tconst uint64_t timestamp_index = APROF_EVENT_TIMESTAMP(*event);\n\t\t\t\t\tASSERT(timestamp_index < timestamps_count);\n\t\t\t\t\tconst uint64_t timestamp = (uint64_t)(timestamps[timestamp_index] * timestamp_period) + timestamp_offset_ns;\n\t\t\t\t\tconst aprof_event_t new_event = APROF_EVENT_MAKE(event_type, scope_id, timestamp);\n\t\t\t\t\t*event = new_event;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t}\n\t}\n}\n\nstatic uint64_t scaledCounter(VkPerformanceCounterStorageKHR storage, VkPerformanceCounterResultKHR result, int scale) {\n\tswitch (storage) {\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR: return result.int32 * scale; break;\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR: return result.int64 * scale; break;\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR: return result.uint32 * scale; break;\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR: return result.uint64 * scale; break;\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR: return result.float32 * scale; break;\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR: return result.float64 * scale; break;\n\t\tdefault:\n\t\t\tERR(\"Invalud performance counter storage %08x\", storage);\n\t\t\treturn 0;\n\t}\n}\n\nstatic uint64_t computeCounterValue(uint32_t counter_index, VkPerformanceCounterResultKHR result) {\n\tconst VkPerformanceCounterKHR *const cnt = &v_device_info.perf_counters.counters[counter_index];\n\tswitch (cnt->unit) {\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_PERCENTAGE_KHR:\n\t\t\t// Percentage is always in permyriad, i.e. hundredth-percent: 10000 is 100%, 2317 is 23.17%\n\t\t\treturn scaledCounter(cnt->storage, result, 100);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn scaledCounter(cnt->storage, result, 1);\n\t\t\tbreak;\n\t}\n}\n\nstatic void patchPeformanceQueryEvents(vk_combuf_impl_t *cb) {\n\tASSERT(cb->profiler.active_perf_query < 0);\n\n\tif (!cb->profiler.perf_query)\n\t\treturn;\n\n\tfor (int i = 0; i < cb->profiler.events_count; ++i) {\n\t\taprof_event_t *const event = &cb->profiler.events[i];\n\t\tconst int event_type = APROF_EVENT_TYPE(*event);\n\t\tif (event_type != APROF_EVENT_COUNTER)\n\t\t\tcontinue;\n\n\t\tconst uint64_t query_index = APROF_EVENT_COUNTER_VALUE(*event);\n\t\tconst VkPerformanceCounterResultKHR* const results = vPerfQueryRead(cb->profiler.perf_query->query, &cb->public, query_index);\n\n\t\tfor (uint32_t j = 0; j < cb->profiler.perf_query->counters.count; ++j) {\n\t\t\taprof_event_t *const event = &cb->profiler.events[i + j];\n\n\t\t\t// Make sure that the right slot is reserved\n\t\t\tconst int event_type = APROF_EVENT_TYPE(*event);\n\t\t\tASSERT(event_type == APROF_EVENT_COUNTER);\n\n\t\t\t// Make sure we're writing into the correct slot\n\t\t\tconst uint64_t counter_index = APROF_EVENT_COUNTER_INDEX(*event);\n\t\t\tASSERT(counter_index == j);\n\n\t\t\tconst uint32_t vk_perf_counter_index = cb->profiler.perf_query->counters.items[j];\n\n\t\t\t*event = APROF_EVENT_MAKE_COUNTER(vk_perf_counter_index, computeCounterValue(vk_perf_counter_index, results[j]));\n\t\t} // for events in counters reserved block\n\n\t\t// Skip the entire reserved block\n\t\ti += cb->profiler.perf_query->counters.count;\n\t} // for all events\n} // patchTimestampQueryEvents()\n\nVCombufProfilingResult R_VkCombufProfilingGetResult(vk_combuf_t *pub) {\n\tAPROF_SCOPE_DECLARE_BEGIN(function, __FUNCTION__);\n\tvk_combuf_impl_t *const cb = (vk_combuf_impl_t*)pub;\n\n\tpatchTimestampQueryEvents(cb);\n\tpatchPeformanceQueryEvents(cb);\n\n\tuint64_t begin_ns = 0, end_ns = 0;\n\tif (cb->profiler.events_count > 1) {\n\t\tbegin_ns = APROF_EVENT_TIMESTAMP(cb->profiler.events[0]);\n\t\tend_ns = APROF_EVENT_TIMESTAMP(cb->profiler.events[cb->profiler.events_count-1]);\n\t}\n\n\tAPROF_SCOPE_END(function);\n\n\treturn (VCombufProfilingResult) {\n\t\t.begin_ns = begin_ns,\n\t\t.end_ns = end_ns,\n\t\t.scopes = {\n\t\t\t.items = g_combuf.scopes,\n\t\t\t.count = g_combuf.scopes_count,\n\t\t},\n\t\t.counters = {\n\t\t\t.items = g_combuf.perf.aprof_counters.items,\n\t\t\t.count = g_combuf.perf.aprof_counters.count,\n\t\t},\n\t\t.events = {\n\t\t\t.items = cb->profiler.events,\n\t\t\t.count = cb->profiler.events_count,\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VCombuf.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n#include \"std/arrays.h\"\n#include \"std/profiler.h\"\n\ntypedef struct vk_combuf_s {\n\tVkCommandBuffer cmdbuf;\n} vk_combuf_t;\n\nqboolean R_VkCombuf_Init( void );\nvoid R_VkCombuf_Destroy( void );\n\nvk_combuf_t* R_VkCombufOpen( void );\nvoid R_VkCombufClose( vk_combuf_t* );\n\nvoid R_VkCombufBegin( vk_combuf_t* );\nvoid R_VkCombufEnd( vk_combuf_t* );\n\n\n// TODO rename consistently\nint R_VkGpuScope_Register(const char *name);\n\nenum {\n\tVCombufScopeFlag_None = 0,\n\tVCombufScopeFlag_PerfQuery = (1<<0),\n};\nint R_VkCombufScopeBegin(vk_combuf_t*, int scope_id, uint32_t flags);\nvoid R_VkCombufScopeEnd(vk_combuf_t*, int begin_index, VkPipelineStageFlagBits pipeline_stage);\n\n// Non-null counters enable perf query for the set of counters, NULL+0 -- disable.\n// returns 0 if failed, 1 on success\n// Counters then are reported for each GPU scope with VCombufScopeFlag_PerfQuery flag.\nint R_VkCombufPerfQueryEnable(const uint32_t *counters, uint32_t counters_count);\n\ntypedef struct VCombufProfilingResult {\n\t// Command buffer execution lifetime\n\tuint64_t begin_ns, end_ns;\n\n\t// All registered GPU profiler scopes and counters\n\tVIEW_DECLARE_CONST(aprof_scope_t, scopes);\n\n\t// Same indexes as into `v_device_info.perf_counters` arrays\n\tVIEW_DECLARE_CONST(aprof_counter_desc_t, counters);\n\n\t// All events during this command buffer submission\n\tVIEW_DECLARE_CONST(aprof_event_t, events);\n} VCombufProfilingResult;\n\n// Reads all the scope timing data (timestamp queries) and returns a list of things happened this frame.\n// Prerequisite: all relevant recorded command buffers should've been completed and waited on already.\n// The returned pointer remains valid until any next R_VkGpu*() call.\nVCombufProfilingResult R_VkCombufProfilingGetResult(vk_combuf_t *);\n"
  },
  {
    "path": "ref/vk/vulkan/VCommandPool.c",
    "content": "#include \"VCommandPool.h\"\n\nvk_command_pool_t R_VkCommandPoolCreate( int count ) {\n\tvk_command_pool_t ret = {0};\n\n\tconst VkCommandPoolCreateInfo cpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,\n\t\t.queueFamilyIndex = 0,\n\t\t.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,\n\t};\n\n\tVkCommandBufferAllocateInfo cbai = {\n\t\t.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,\n\t\t.commandBufferCount = count,\n\t\t.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,\n\t};\n\n\tXVK_CHECK(vkCreateCommandPool(vk_core.device, &cpci, NULL, &ret.pool));\n\n\tcbai.commandPool = ret.pool;\n\tret.buffers = Mem_Malloc(vk_core.pool, sizeof(VkCommandBuffer) * count);\n\tret.buffers_count = count;\n\tXVK_CHECK(vkAllocateCommandBuffers(vk_core.device, &cbai, ret.buffers));\n\n\treturn ret;\n}\n\nvoid R_VkCommandPoolDestroy( vk_command_pool_t *pool ) {\n\tASSERT(pool->buffers);\n\tvkDestroyCommandPool(vk_core.device, pool->pool, NULL);\n\tMem_Free(pool->buffers);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VCommandPool.h",
    "content": "#include \"vk_core.h\"\n\ntypedef struct {\n\tVkCommandPool pool;\n\tVkCommandBuffer *buffers;\n\tint buffers_count;\n} vk_command_pool_t;\n\nvk_command_pool_t R_VkCommandPoolCreate( int count );\nvoid R_VkCommandPoolDestroy( vk_command_pool_t *pool );\n"
  },
  {
    "path": "ref/vk/vulkan/VDescriptor.c",
    "content": "#include \"VDescriptor.h\"\n\n#include \"eiface.h\" // ARRAYSIZE\n\ndescriptor_pool_t vk_desc_fixme;\n\nqboolean VK_DescriptorInit( void )\n{\n\tint max_desc_sets = 0;\n\n\tVkDescriptorPoolSize dps[] = {\n\t\t{\n\t\t\t.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t\t.descriptorCount = MAX_TEXTURES,\n\t\t}, {\n\t\t\t.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,\n\t\t\t.descriptorCount = ARRAYSIZE(vk_desc_fixme.ubo_sets),\n\t\t/*\n\t\t}, {\n\t\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t\t.descriptorCount = 1,\n\t\t*/\n\t\t},\n\t};\n\tVkDescriptorPoolCreateInfo dpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,\n\t\t.pPoolSizes = dps,\n\t\t.poolSizeCount = ARRAYSIZE(dps),\n\t};\n\n\tfor (int i = 0; i < ARRAYSIZE(dps); ++i)\n\t\tmax_desc_sets += dps[i].descriptorCount;\n\n\tdpci.maxSets = max_desc_sets;\n\n\tXVK_CHECK(vkCreateDescriptorPool(vk_core.device, &dpci, NULL, &vk_desc_fixme.pool));\n\n\t{\n\t\tconst int num_sets = MAX_TEXTURES;\n\t\t// ... TODO find better place for this; this should be per-pipeline/shader\n\t\tconst VkDescriptorSetLayoutBinding bindings[] = { {\n\t\t\t.binding = 0,\n\t\t\t.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,\n\t\t\t.descriptorCount = 1,\n\t\t\t.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t\t.pImmutableSamplers = NULL,\n\t\t}};\n\t\tconst VkDescriptorSetLayoutCreateInfo dslci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,\n\t\t\t.bindingCount = ARRAYSIZE(bindings),\n\t\t\t.pBindings = bindings,\n\t\t};\n\t\tVkDescriptorSetLayout* tmp_layouts = Mem_Malloc(vk_core.pool, sizeof(VkDescriptorSetLayout) * num_sets);\n\t\tconst VkDescriptorSetAllocateInfo dsai = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,\n\t\t\t.descriptorPool = vk_desc_fixme.pool,\n\t\t\t.descriptorSetCount = num_sets,\n\t\t\t.pSetLayouts = tmp_layouts,\n\t\t};\n\t\tXVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc_fixme.one_texture_layout));\n\t\tfor (int i = 0; i < num_sets; ++i)\n\t\t\ttmp_layouts[i] = vk_desc_fixme.one_texture_layout;\n\n\t\tXVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc_fixme.texture_sets));\n\n\t\tMem_Free(tmp_layouts);\n\t}\n\n\t{\n\t\tconst int num_sets = ARRAYSIZE(vk_desc_fixme.ubo_sets);\n\t\t// ... TODO find better place for this; this should be per-pipeline/shader\n\t\tVkDescriptorSetLayoutBinding bindings[] = { {\n\t\t\t\t.binding = 0,\n\t\t\t\t.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,\n\t\t\t\t.descriptorCount = 1,\n\t\t\t\t// TODO we use these sets for both vertex-only and fragment-only bindings; improve\n\t\t\t\t.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,\n\t\t}};\n\t\tVkDescriptorSetLayoutCreateInfo dslci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,\n\t\t\t.bindingCount = ARRAYSIZE(bindings),\n\t\t\t.pBindings = bindings,\n\t\t};\n\t\tVkDescriptorSetLayout* tmp_layouts = Mem_Malloc(vk_core.pool, sizeof(VkDescriptorSetLayout) * num_sets);\n\t\tVkDescriptorSetAllocateInfo dsai = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,\n\t\t\t.descriptorPool = vk_desc_fixme.pool,\n\t\t\t.descriptorSetCount = num_sets,\n\t\t\t.pSetLayouts = tmp_layouts,\n\t\t};\n\t\tXVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &vk_desc_fixme.one_uniform_buffer_layout));\n\t\tfor (int i = 0; i < num_sets; ++i)\n\t\t\t\ttmp_layouts[i] = vk_desc_fixme.one_uniform_buffer_layout;\n\n\t\tXVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, vk_desc_fixme.ubo_sets));\n\n\t\tMem_Free(tmp_layouts);\n\t}\n\n\treturn true;\n}\n\nvoid VK_DescriptorShutdown( void )\n{\n\tvkDestroyDescriptorPool(vk_core.device, vk_desc_fixme.pool, NULL);\n\tvkDestroyDescriptorSetLayout(vk_core.device, vk_desc_fixme.one_texture_layout, NULL);\n\tvkDestroyDescriptorSetLayout(vk_core.device, vk_desc_fixme.one_uniform_buffer_layout, NULL);\n}\n\nvoid VK_DescriptorsCreate(vk_descriptors_t *desc)\n{\n\t{\n\t\tVkDescriptorSetLayoutCreateInfo dslci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,\n\t\t\t.bindingCount = desc->num_bindings,\n\t\t\t.pBindings = desc->bindings,\n\t\t};\n\t\tXVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &desc->desc_layout));\n\t}\n\n\t{\n\t\tVkPipelineLayoutCreateInfo plci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,\n\t\t\t.setLayoutCount = 1,\n\t\t\t.pSetLayouts = &desc->desc_layout,\n\t\t\t.pushConstantRangeCount = desc->push_constants.size > 0 ? 1 : 0,\n\t\t\t.pPushConstantRanges = &desc->push_constants,\n\t\t};\n\t\tXVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &desc->pipeline_layout));\n\t}\n\n\t{\n\t\tVkDescriptorPoolSize pools[8] = {0};\n\t\tVkDescriptorPoolCreateInfo dpci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,\n\t\t\t.maxSets = desc->num_sets,\n\t\t\t.poolSizeCount = 0,\n\t\t\t.pPoolSizes = pools,\n\t\t};\n\n\t\tfor (int i = 0; i < desc->num_bindings; ++i) {\n\t\t\tconst VkDescriptorSetLayoutBinding *bind = desc->bindings + i;\n\t\t\tint j;\n\t\t\tfor (j = 0; j < dpci.poolSizeCount; ++j) {\n\t\t\t\tif (pools[j].type == bind->descriptorType) {\n\t\t\t\t\tpools[j].descriptorCount += bind->descriptorCount * desc->num_sets;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (j == dpci.poolSizeCount) {\n\t\t\t\tASSERT(dpci.poolSizeCount < ARRAYSIZE(pools));\n\t\t\t\tpools[j].descriptorCount = bind->descriptorCount * desc->num_sets;\n\t\t\t\tpools[j].type = bind->descriptorType;\n\t\t\t\t++dpci.poolSizeCount;\n\t\t\t}\n\t\t}\n\n\t\tXVK_CHECK(vkCreateDescriptorPool(vk_core.device, &dpci, NULL, &desc->desc_pool));\n\t}\n\n\tfor (int i = 0; i < desc->num_sets; ++i)\n\t{\n\t\tVkDescriptorSetAllocateInfo dsai = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,\n\t\t\t.descriptorPool = desc->desc_pool,\n\t\t\t.descriptorSetCount = 1,\n\t\t\t.pSetLayouts = &desc->desc_layout,\n\t\t};\n\t\tXVK_CHECK(vkAllocateDescriptorSets(vk_core.device, &dsai, desc->desc_sets + i));\n\t}\n}\n\nvoid VK_DescriptorsWrite(const vk_descriptors_t *desc, int set_slot)\n{\n\tVkWriteDescriptorSet wds[64];\n\tASSERT(ARRAYSIZE(wds) >= desc->num_bindings);\n\tfor (int i = 0; i < desc->num_bindings; ++i){\n\t\tconst VkDescriptorSetLayoutBinding *binding = desc->bindings + i;\n\t\twds[i] = (VkWriteDescriptorSet) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,\n\t\t\t.descriptorCount = binding->descriptorCount,\n\t\t\t.descriptorType = binding->descriptorType,\n\t\t\t.dstSet = desc->desc_sets[set_slot],\n\t\t\t.dstBinding = binding->binding,\n\t\t\t.dstArrayElement = 0,\n\t\t};\n\n\t\tswitch (binding->descriptorType) {\n\t\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:\n\t\t\tcase VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:\n\t\t\tcase VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:\n\t\t\t\t// TODO\n\t\t\t\tASSERT(wds[i].descriptorCount == 1);\n\t\t\t\twds[i].pBufferInfo = &desc->values[i].buffer;\n\t\t\t\tbreak;\n\t\t\tcase VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:\n\t\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:\n\t\t\t\tif (wds[i].descriptorCount > 1)\n\t\t\t\t\twds[i].pImageInfo = desc->values[i].image_array;\n\t\t\t\telse\n\t\t\t\t\twds[i].pImageInfo = &desc->values[i].image;\n\t\t\t\tbreak;\n\t\t\tcase VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:\n\t\t\t\t// TODO\n\t\t\t\tASSERT(wds[i].descriptorCount == 1);\n\t\t\t\twds[i].pNext = &desc->values[i].accel;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tgEngine.Con_Printf(S_ERROR \"Unexpected descriptor type %d\\n\", binding->descriptorType);\n\t\t\t\tASSERT(\"Unexpected descriptor type\");\n\t\t}\n\t}\n\n\tvkUpdateDescriptorSets(vk_core.device, desc->num_bindings, wds, 0, NULL);\n}\n\nvoid VK_DescriptorsDestroy(const vk_descriptors_t *desc)\n{\n\tvkDestroyDescriptorPool(vk_core.device, desc->desc_pool, NULL);\n\tvkDestroyPipelineLayout(vk_core.device, desc->pipeline_layout, NULL);\n\tvkDestroyDescriptorSetLayout(vk_core.device, desc->desc_layout, NULL);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VDescriptor.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\n#include \"vk_const.h\"\n\n// Only used for traditional renderer\ntypedef struct descriptor_pool_s\n{\n\tVkDescriptorPool pool;\n\n\tVkDescriptorSet texture_sets[MAX_TEXTURES];\n\tVkDescriptorSetLayout one_texture_layout;\n\n\t// FIXME HOW THE F\n\tVkDescriptorSet ubo_sets[2];\n\tVkDescriptorSetLayout one_uniform_buffer_layout;\n} descriptor_pool_t;\n\n// FIXME: move to traditional renderer\nextern descriptor_pool_t vk_desc_fixme;\n\nqboolean VK_DescriptorInit( void );\nvoid VK_DescriptorShutdown( void );\n\ntypedef union {\n\tVkDescriptorBufferInfo buffer;\n\tVkDescriptorImageInfo image;\n\tconst VkDescriptorImageInfo *image_array;\n\tVkWriteDescriptorSetAccelerationStructureKHR accel;\n} vk_descriptor_value_t;\n\ntypedef struct {\n\tint num_bindings;\n\tconst VkDescriptorSetLayoutBinding *bindings;\n\n\t// Used in Write only\n\tvk_descriptor_value_t *values;\n\n\tVkPushConstantRange push_constants;\n\n\tVkPipelineLayout pipeline_layout;\n\tVkDescriptorSetLayout desc_layout;\n\tVkDescriptorPool desc_pool;\n\n\tint num_sets;\n\tVkDescriptorSet *desc_sets;\n} vk_descriptors_t;\n\nvoid VK_DescriptorsCreate(vk_descriptors_t *desc);\nvoid VK_DescriptorsWrite(const vk_descriptors_t *desc, int set_slot);\nvoid VK_DescriptorsDestroy(const vk_descriptors_t *desc);\n"
  },
  {
    "path": "ref/vk/vulkan/VDevice.c",
    "content": "#include \"VDevice.h\"\n\n#include \"vk_common.h\"\n#include \"std/arrays.h\"\n#include \"vk_logs.h\"\n\n#define LOG_MODULE core\n\nVDeviceInfo v_device_info = {0};\nVkDevice v_device = VK_NULL_HANDLE;\n\n#define X(f) PFN_##f f = NULL;\n\tDEVICE_FUNCS(X)\n\tDEVICE_FUNCS_RTX(X)\n#undef X\n\nstatic dllfunc_t device_funcs[] = {\n#define X(f) {#f, (void**)&f},\n\tDEVICE_FUNCS(X)\n#undef X\n};\n\nstatic dllfunc_t device_funcs_rtx[] = {\n#define X(f) {#f, (void**)&f},\n\tDEVICE_FUNCS_RTX(X)\n#undef X\n};\n\nstatic const char* device_extensions_req[] = {\n\tVK_KHR_SWAPCHAIN_EXTENSION_NAME,\n};\n\nstatic const char* device_extensions_rt[] = {\n\tVK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,\n\tVK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,\n\tVK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME,\n\tVK_KHR_RAY_QUERY_EXTENSION_NAME,\n\n\t// TODO optional under -vkvalidate\n\tVK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME,\n};\n\nstatic const char* device_extensions_nv_checkpoint[] = {\n\tVK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME,\n\tVK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME,\n};\n\nstatic const char* device_extensions_extra[] = {\n\tVK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,\n};\n\nstatic const char* device_extensions_perf_query[] = {\n\tVK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME,\n};\n\nstatic const VkExtensionProperties *findExtension( const VkExtensionProperties *exts, uint32_t num_exts, const char *extension ) {\n\tfor (uint32_t i = 0; i < num_exts; ++i) {\n\t\tif (strncmp(exts[i].extensionName, extension, sizeof(exts[i].extensionName)) == 0)\n\t\t\treturn exts + i;\n\t}\n\treturn NULL;\n}\n\nstatic qboolean deviceSupportsExtensions(const VkExtensionProperties *exts, uint32_t num_exts, const char *check_extensions[], int check_extensions_count) {\n\tqboolean result = true;\n\tfor (int i = 0; i < check_extensions_count; ++i) {\n\t\tif (!findExtension(exts, num_exts, check_extensions[i])) {\n\t\t\tWARN(\"Extension %s is not supported\", check_extensions[i]);\n\t\t\tresult = false;\n\t\t}\n\t}\n\treturn result;\n}\n\nstatic void devicePrintExtensionsFromList(const VkExtensionProperties *exts, uint32_t num_exts, const char *print_extensions[], int print_extensions_count) {\n\tfor (int i = 0; i < print_extensions_count; ++i) {\n\t\tconst VkExtensionProperties *const ext_prop = findExtension(exts, num_exts, print_extensions[i]);\n\t\tif (!ext_prop) {\n\t\t\tINFO(\"\\t\\t\\t%s: N/A\", print_extensions[i]);\n\t\t} else {\n\t\t\tINFO(\"\\t\\t\\t%s: %u.%u.%u\", ext_prop->extensionName, XVK_PARSE_VERSION(ext_prop->specVersion));\n\t\t}\n\t}\n}\n\n#define MAX_DEVICE_EXTENSIONS 16\nstatic int appendDeviceExtensions(const char** out, int out_count, const char *in_extensions[], int in_extensions_count) {\n\tfor (int i = 0; i < in_extensions_count; ++i) {\n\t\tASSERT(out_count < MAX_DEVICE_EXTENSIONS);\n\t\tout[out_count++] = in_extensions[i];\n\t}\n\treturn out_count;\n}\n\n#define GET_VULKAN_ARRAY(TYPE, NAME, FUNC) \\\n\tstruct { \\\n\t\tTYPE *items; \\\n\t\tuint32_t count; \\\n\t} NAME = {0}; \\\n\tFUNC(&NAME.count, NULL); \\\n\tNAME.items = Mem_Malloc(vk_core.pool, sizeof(TYPE) * NAME.count); \\\n\tFUNC(&NAME.count, NAME.items)\n\nstatic uint32_t findUsableQueueFamilyIndex(VkPhysicalDevice physdev) {\n#define FUNC(COUNT, ITEMS) vkGetPhysicalDeviceQueueFamilyProperties(physdev, COUNT, ITEMS)\n\tGET_VULKAN_ARRAY(VkQueueFamilyProperties, queue_family_props, FUNC);\n#undef FUNC\n\n\t// Find queue family that supports needed properties\n\tuint32_t usable_queue_index = VK_QUEUE_FAMILY_IGNORED;\n\tfor (uint32_t i = 0; i < queue_family_props.count; ++i) {\n\t\tVkBool32 supports_present = 0;\n\t\tconst qboolean supports_graphics = !!(queue_family_props.items[i].queueFlags & VK_QUEUE_GRAPHICS_BIT);\n\t\tconst qboolean supports_compute = !!(queue_family_props.items[i].queueFlags & VK_QUEUE_COMPUTE_BIT);\n\t\tvkGetPhysicalDeviceSurfaceSupportKHR(physdev, i, vk_core.surface.surface, &supports_present);\n\n\t\tINFO(\"\\t\\tQueue %d/%d present: %d graphics: %d compute: %d\", i, queue_family_props.count,\n\t\t\tsupports_present, supports_graphics, supports_compute);\n\n\t\tif (!supports_present)\n\t\t\tcontinue;\n\n\t\t// ray tracing needs compute\n\t\t// also, by vk spec graphics queue must support compute\n\t\tif (!supports_graphics || !supports_compute)\n\t\t\tcontinue;\n\n\t\tusable_queue_index = i;\n\t\tbreak;\n\t}\n\n\tMem_Free(queue_family_props.items);\n\treturn usable_queue_index;\n}\n\n#if 0 // TODO\nstatic void addRefDeviceT(void) {\n\t\t// Store devices list in vk_core.devices for pfnGetRenderDevices\n\t\tvk_core.devices[i].vendorID = props.vendorID;\n\t\tvk_core.devices[i].deviceID = props.deviceID;\n\t\tswitch( props.deviceType )\n\t\t{\n\t\tcase VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:\n\t\t\tvk_core.devices[i].deviceType = REF_DEVICE_TYPE_INTERGRATED_GPU;\n\t\t\tbreak;\n\t\tcase VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:\n\t\t\tvk_core.devices[i].deviceType = REF_DEVICE_TYPE_DISCRETE_GPU;\n\t\t\tbreak;\n\t\tcase VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:\n\t\t\tvk_core.devices[i].deviceType = REF_DEVICE_TYPE_VIRTUAL_GPU;\n\t\t\tbreak;\n\t\tcase VK_PHYSICAL_DEVICE_TYPE_CPU:\n\t\t\tvk_core.devices[i].deviceType = REF_DEVICE_TYPE_CPU;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tvk_core.devices[i].deviceType = REF_DEVICE_TYPE_OTHER;\n\t\t\tbreak;\n\t\t}\n\t\tQ_strncpy( vk_core.devices[i].deviceName, props.deviceName, sizeof( vk_core.devices[i].deviceName ));\n}\n#endif\n\nstatic void devicePrintMemoryInfo(const VkPhysicalDeviceMemoryProperties *props, const VkPhysicalDeviceMemoryBudgetPropertiesEXT *budget) {\n\tINFO(\"Memory heaps: %d\", props->memoryHeapCount);\n\tfor (int i = 0; i < (int)props->memoryHeapCount; ++i) {\n\t\tconst VkMemoryHeap* const heap = props->memoryHeaps + i;\n\t\tINFO(\"  %d: size=%dMb used=%dMb avail=%dMb device_local=%d\", i,\n\t\t\t(int)(heap->size / (1024 * 1024)),\n\t\t\t(int)(budget->heapUsage[i] / (1024 * 1024)),\n\t\t\t(int)(budget->heapBudget[i] / (1024 * 1024)),\n\t\t\t!!(heap->flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT));\n\t}\n\n\tINFO(\"Memory types: %d\", props->memoryTypeCount);\n\tfor (int i = 0; i < (int)props->memoryTypeCount; ++i) {\n\t\tconst VkMemoryType* const type = props->memoryTypes + i;\n\t\tINFO(\"  %d: bit=0x%x heap=%d flags=%c%c%c%c%c\", i,\n\t\t\t(1 << i),\n\t\t\ttype->heapIndex,\n\t\t\ttype->propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT ? 'D' : '.',\n\t\t\ttype->propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ? 'V' : '.',\n\t\t\ttype->propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 'C' : '.',\n\t\t\ttype->propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT ? '$' : '.',\n\t\t\ttype->propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ? 'L' : '.'\n\t\t);\n\t}\n}\n\nstatic const char* perfCounterUnitName(VkPerformanceCounterUnitKHR unit) {\n\tswitch (unit) {\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_GENERIC_KHR: return \"generic\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_PERCENTAGE_KHR: return \"%\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_NANOSECONDS_KHR: return \"ns\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_BYTES_KHR: return \"b\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_BYTES_PER_SECOND_KHR: return \"bps\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_KELVIN_KHR: return \"K\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_WATTS_KHR: return \"W\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_VOLTS_KHR: return \"V\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_AMPS_KHR: return \"A\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_HERTZ_KHR: return \"Hz\";\n\t\tcase VK_PERFORMANCE_COUNTER_UNIT_CYCLES_KHR: return \"C\";\n\t\tdefault: return \"?\";\n\t}\n}\n\nstatic const char *perfCounterScopeName(VkPerformanceCounterScopeKHR scope) {\n\tswitch(scope) {\n\t\tcase VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_BUFFER_KHR: return \"cmdbuf\";\n\t\tcase VK_PERFORMANCE_COUNTER_SCOPE_RENDER_PASS_KHR: return \"renderpass\";\n\t\tcase VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_KHR: return \"command\";\n\t\tdefault: return \"unknown\";\n\t}\n}\n\nstatic const char *perfCounterStorageName(VkPerformanceCounterStorageKHR storage) {\n\tswitch (storage) {\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR: return \"i32\";\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR: return \"i64\";\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR: return \"u32\";\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR: return \"u64\";\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR: return \"f32\";\n\t\tcase VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR: return \"f64\";\n\t\tdefault: return \"unknown\";\n\t}\n}\n\nvoid vDevicePrintPerformanceCounters(const VDeviceInfo *info) {\n\tINFO(\"Got %d counters:\", info->perf_counters.count);\n\tfor (uint32_t i = 0; i < info->perf_counters.count; ++i) {\n\t\tconst VkPerformanceCounterKHR *const cnt = info->perf_counters.counters + i;\n\t\tconst VkPerformanceCounterDescriptionKHR *const desc = info->perf_counters.desc + i;\n\t\tINFO(\"  %d: %s %s/%s, %s@%s (%s)\",\n\t\t\ti, perfCounterScopeName(cnt->scope),\n\t\t\tdesc->category, desc->name,\n\t\t\tperfCounterUnitName(cnt->unit), perfCounterStorageName(cnt->storage),\n\t\t\tdesc->description);\n\t}\n}\n\nstatic void queryPerformanceCounters(VDeviceInfo *info) {\n\tuint32_t counters_count = 0;\n\tXVK_CHECK(vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(info->physical_device, info->queue_index, &counters_count, NULL, NULL));\n\n\tVkPerformanceCounterKHR *const counters = Mem_Malloc(vk_core.pool, counters_count * sizeof(*counters));\n\tVkPerformanceCounterDescriptionKHR *const counters_desc = Mem_Malloc(vk_core.pool, counters_count * sizeof(*counters_desc));\n\n\tfor (uint32_t i = 0; i < counters_count; ++i) {\n\t\tcounters[i] = (VkPerformanceCounterKHR) { .sType = VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_KHR, };\n\t\tcounters_desc[i] = (VkPerformanceCounterDescriptionKHR) { .sType = VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_DESCRIPTION_KHR, };\n\t}\n\n\tXVK_CHECK(vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(info->physical_device, info->queue_index, &counters_count, counters, counters_desc));\n\n\tinfo->perf_counters.count = counters_count;\n\tinfo->perf_counters.counters = counters;\n\tinfo->perf_counters.desc = counters_desc;\n\n\tvDevicePrintPerformanceCounters(info);\n}\n\nstatic void readPhysicalDeviceInfo(VDeviceInfo *info) {\n#define FUNC(COUNT, ITEMS) XVK_CHECK(vkEnumerateDeviceExtensionProperties(info->physical_device, NULL, COUNT, ITEMS))\n\t\tGET_VULKAN_ARRAY(VkExtensionProperties, extensions, FUNC);\n#undef FUNC\n\t{\n\t\tINFO( \"\\t\\tSupported device extensions: %u\", extensions.count);\n\t\tdevicePrintExtensionsFromList(extensions.items, extensions.count, device_extensions_req, COUNTOF(device_extensions_req));\n\t\tdevicePrintExtensionsFromList(extensions.items, extensions.count, device_extensions_rt, COUNTOF(device_extensions_rt));\n\t\tdevicePrintExtensionsFromList(extensions.items, extensions.count, device_extensions_nv_checkpoint, COUNTOF(device_extensions_nv_checkpoint));\n\t\tdevicePrintExtensionsFromList(extensions.items, extensions.count, device_extensions_extra, COUNTOF(device_extensions_extra));\n\t\tdevicePrintExtensionsFromList(extensions.items, extensions.count, device_extensions_perf_query, COUNTOF(device_extensions_perf_query));\n\t}\n\n\tvoid *features_head = NULL;\n\tVkPhysicalDevicePerformanceQueryFeaturesKHR perf_query_features = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR,\n\t\t.pNext = features_head,\n\t};\n\n\tconst qboolean perf_query_extension_supported = deviceSupportsExtensions(extensions.items, extensions.count, device_extensions_perf_query, COUNTOF(device_extensions_perf_query));\n\tif (perf_query_extension_supported)\n\t\tfeatures_head = &perf_query_features;\n\n\tinfo->features = (VkPhysicalDeviceFeatures2) {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,\n\t\t.pNext = features_head,\n\t};\n\tvkGetPhysicalDeviceFeatures2(info->physical_device, &info->features);\n\n\t{\n\t\tinfo->anisotropy = info->features.features.samplerAnisotropy;\n\t\tINFO(\"\\t\\tAnistoropy supported: %d\", info->anisotropy);\n\n\t\tinfo->ray_tracing = deviceSupportsExtensions(extensions.items, extensions.count, device_extensions_rt, COUNTOF(device_extensions_rt));\n\t\tINFO(\"\\t\\tRay tracing supported: %d\", info->ray_tracing);\n\n\t\tinfo->nv_checkpoint = vk_core.debug && deviceSupportsExtensions(extensions.items, extensions.count, device_extensions_nv_checkpoint, COUNTOF(device_extensions_nv_checkpoint));\n\t\tINFO(\"\\t\\tNV checkpoints supported: %d\", info->nv_checkpoint);\n\n\t\tinfo->calibrated_timestamps = deviceSupportsExtensions(extensions.items, extensions.count, device_extensions_extra, COUNTOF(device_extensions_extra));\n\t\tINFO(\"\\t\\tCalibrated timestamps supported: %d\", info->calibrated_timestamps);\n\n\t\tinfo->perf_query = perf_query_extension_supported && perf_query_features.performanceCounterQueryPools;\n\t\tINFO(\"\\t\\tPerformance query supported: %d\", info->perf_query);\n\t}\n\n\tif (perf_query_extension_supported)\n\t\tqueryPerformanceCounters(info);\n\n\t// Get memory properties and budget\n\t{\n\t\tinfo->memory_properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;\n\t\tinfo->memory_properties2.pNext = &info->memory_budget;\n\t\tinfo->memory_budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;\n\t\tinfo->memory_budget.pNext = NULL;\n\t\tvkGetPhysicalDeviceMemoryProperties2(info->physical_device, &info->memory_properties2);\n\t\tdevicePrintMemoryInfo(&info->memory_properties2.memoryProperties, &info->memory_budget);\n\t}\n\n\n\t{\n\t\t// TODO should we check Vk version first?\n\t\tinfo->properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;\n\t\tinfo->properties2.pNext = NULL;\n\t\tif (info->ray_tracing) {\n\t\t\tinfo->properties2.pNext = &info->properties_accel;\n\t\t\tinfo->properties_accel.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR;\n\t\t\tinfo->properties_accel.pNext = &info->properties_ray_tracing_pipeline;\n\t\t\tinfo->properties_ray_tracing_pipeline.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;\n\t\t\tinfo->properties_ray_tracing_pipeline.pNext = NULL;\n\t\t}\n\t\tvkGetPhysicalDeviceProperties2(info->physical_device, &info->properties2);\n\t\tif (info->ray_tracing) {\n\t\t\t//??? g_rtx.sbt_record_size = ALIGN_UP(info->properties_ray_tracing_pipeline.shaderGroupHandleSize, info->properties_ray_tracing_pipeline.shaderGroupHandleAlignment);\n\t\t\tinfo->sbt_record_size = ALIGN_UP(info->properties_ray_tracing_pipeline.shaderGroupHandleSize, info->properties_ray_tracing_pipeline.shaderGroupBaseAlignment);\n\t\t}\n\t}\n\n\tMem_Free(extensions.items);\n}\n\ntypedef ARRAY_DYNAMIC_DECLARE(VDeviceInfo, VDeviceInfos);\n\nstatic VDeviceInfos enumerateDevices(void) {\n\tVDeviceInfos infos;\n\tarrayDynamicInitT(&infos);\n\n#define FUNC(COUNT, ITEMS) XVK_CHECK(vkEnumeratePhysicalDevices(vk_core.instance, COUNT, ITEMS))\n\tGET_VULKAN_ARRAY(VkPhysicalDevice, physical_devices, FUNC);\n#undef FUNC\n\tif (physical_devices.count == 0) {\n\t\tERR(\"No physical Vulkan devices found\");\n\t\treturn infos;\n\t}\n\n\tarrayDynamicResizeT(&infos, physical_devices.count);\n\n\tINFO(\"Got %u physical devices:\", physical_devices.count);\n\tint devices_having_rt = 0;\n\tfor (uint32_t i = 0; i < physical_devices.count; ++i) {\n\t\tVDeviceInfo *const info = infos.items + i;\n\t\t*info = (VDeviceInfo) {\n\t\t\t.physical_device = physical_devices.items[i],\n\t\t};\n\n\t\tvkGetPhysicalDeviceProperties(info->physical_device, &info->properties);\n\n\t\tINFO(\"\\t%u: %04x:%04x %d %s %u.%u.%u %u.%u.%u\",\n\t\t\ti, info->properties.vendorID, info->properties.deviceID, info->properties.deviceType, info->properties.deviceName,\n\t\t\tXVK_PARSE_VERSION(info->properties.driverVersion), XVK_PARSE_VERSION(info->properties.apiVersion));\n\n\t\tinfo->queue_index = findUsableQueueFamilyIndex(info->physical_device);\n\t\tif (info->queue_index == VK_QUEUE_FAMILY_IGNORED) {\n\t\t\tWARN(\"\\t\\tSkipping this device as compatible queue (which has both compute and graphics and also can present) not found\" );\n\t\t\tcontinue;\n\t\t}\n\n\t\treadPhysicalDeviceInfo(info);\n\t\tdevices_having_rt += !!info->ray_tracing;\n\n\t\t// FIXME also pay attention to various device limits. We depend on them implicitly now.\n\t}\n\n\tMem_Free(physical_devices.items);\n\n\tif (devices_having_rt == 0) {\n\t\tgEngine.Con_Printf( \"^6===================================================^7\\n\" );\n\t\tgEngine.Con_Printf(S_ERROR \"^1No ray tracing extensions found.^7\\n\");\n\t\t#if defined XASH_64BIT\n\t\tgEngine.Con_Printf(S_NOTE \"^3Check that you have compatible hardware and drivers.^7\\n\");\n\t\t#else\n\t\tgEngine.Con_Printf(S_WARN \"^3You're running in ^132-bit ^3mode!^7\\n\");\n\t\tgEngine.Con_Printf(S_NOTE \"^3Ray Tracing REQUIRES ^264-bit ^3process!\\n^5Please rebuild and start the 64-bit xash3d binary.^7\\n\");\n\t\t#endif\n\t\tgEngine.Con_Printf( \"^6===================================================^7\\n\" );\n\t}\n\n\treturn infos;\n}\n\nstatic void loadDeviceFunctions(dllfunc_t *funcs, int count) {\n\tfor (int i = 0; i < count; ++i) {\n\t\t*funcs[i].func = vkGetDeviceProcAddr(v_device, funcs[i].name);\n\t\tif (!*funcs[i].func) {\n\t\t\tWARN(\"Function %s was not loaded\", funcs[i].name);\n\t\t}\n\t}\n}\n\nstatic qboolean createDevice(const VDeviceInfo* info) {\n\tvoid *head = NULL;\n\n\tVkPhysicalDeviceAccelerationStructureFeaturesKHR accel_feature = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR,\n\t\t.pNext = head,\n\t\t.accelerationStructure = VK_TRUE,\n\t};\n\thead = &accel_feature;\n\tVkPhysicalDevice16BitStorageFeatures sixteen_bit_feature = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,\n\t\t.pNext = head,\n\t\t// TODO verify support\n\t\t.storageBuffer16BitAccess = VK_TRUE,\n\t};\n\thead = &sixteen_bit_feature;\n\tVkPhysicalDeviceVulkan12Features vk12_features = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,\n\t\t.pNext = head,\n\t\t.shaderSampledImageArrayNonUniformIndexing = VK_TRUE, // Needed for texture sampling in closest hit shader\n\t\t.storageBuffer8BitAccess = VK_TRUE,\n\t\t.uniformAndStorageBuffer8BitAccess = VK_TRUE,\n\t\t.bufferDeviceAddress = VK_TRUE,\n\n\t\t// VK_KHR_performance_query requires host-side query reset, cause it doesn't like query reset cmd in the same cmdbuf\n\t\t.hostQueryReset = info->perf_query ? VK_TRUE : VK_FALSE,\n\t};\n\thead = &vk12_features;\n\tVkPhysicalDeviceRayTracingPipelineFeaturesKHR ray_tracing_pipeline_feature = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR,\n\t\t.pNext = head,\n\t\t.rayTracingPipeline = VK_TRUE,\n\t\t// TODO .rayTraversalPrimitiveCulling = VK_TRUE,\n\t};\n\thead = &ray_tracing_pipeline_feature;\n\tVkPhysicalDeviceRayQueryFeaturesKHR ray_query_pipeline_feature = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR,\n\t\t.pNext = head,\n\t\t.rayQuery = VK_TRUE,\n\t};\n\thead = info->ray_tracing ? &ray_query_pipeline_feature : NULL;\n\n\tVkPhysicalDevicePerformanceQueryFeaturesKHR perf_query_features = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR,\n\t\t.pNext = head,\n\t\t.performanceCounterQueryPools = VK_TRUE,\n\t};\n\tif (info->perf_query)\n\t\thead = &perf_query_features;\n\n\tVkPhysicalDeviceVulkan13Features vk13_features = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,\n\t\t.pNext = head,\n\t\t.synchronization2 = VK_TRUE,\n\t};\n\thead = &vk13_features;\n\n\tVkPhysicalDeviceFeatures2 features = {\n\t\t.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,\n\t\t.pNext = head,\n\t\t.features.samplerAnisotropy = info->features.features.samplerAnisotropy,\n\t\t.features.shaderInt16 = true, // TODO verfiy support first\n\t};\n\thead = &features;\n\n\tVkDeviceDiagnosticsConfigCreateInfoNV diag_config_nv = {\n\t\t.sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV,\n\t\t.pNext = head,\n\t\t.flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV | 0x00000008 /*VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_ERROR_REPORTING_BIT_NV */\n\t};\n\n\tif (info->nv_checkpoint)\n\t\thead = &diag_config_nv;\n\n\tconst float queue_priorities[1] = {1.f};\n\tVkDeviceQueueCreateInfo queue_info = {\n\t\t.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,\n\t\t.flags = 0,\n\t\t.queueFamilyIndex = info->queue_index,\n\t\t.queueCount = COUNTOF(queue_priorities),\n\t\t.pQueuePriorities = queue_priorities,\n\t};\n\n\tconst char* device_extensions[MAX_DEVICE_EXTENSIONS];\n\tint device_extensions_count = 0;\n\tdevice_extensions_count = appendDeviceExtensions(device_extensions, device_extensions_count, device_extensions_req, COUNTOF(device_extensions_req));\n\tif (info->ray_tracing)\n\t\tdevice_extensions_count = appendDeviceExtensions(device_extensions, device_extensions_count, device_extensions_rt, COUNTOF(device_extensions_rt));\n\tif (info->nv_checkpoint)\n\t\tdevice_extensions_count = appendDeviceExtensions(device_extensions, device_extensions_count, device_extensions_nv_checkpoint, COUNTOF(device_extensions_nv_checkpoint));\n\tif (info->calibrated_timestamps)\n\t\tdevice_extensions_count = appendDeviceExtensions(device_extensions, device_extensions_count, device_extensions_extra, COUNTOF(device_extensions_extra));\n\tif (info->perf_query)\n\t\tdevice_extensions_count = appendDeviceExtensions(device_extensions, device_extensions_count, device_extensions_perf_query, COUNTOF(device_extensions_perf_query));\n\n\tVkDeviceCreateInfo create_info = {\n\t\t.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,\n\t\t.pNext = head,\n\t\t.flags = 0,\n\t\t.queueCreateInfoCount = 1,\n\t\t.pQueueCreateInfos = &queue_info,\n\t\t.enabledExtensionCount = device_extensions_count,\n\t\t.ppEnabledExtensionNames = device_extensions,\n\t};\n\n\t{\n\t\tconst VkResult result = vkCreateDevice(info->physical_device, &create_info, NULL, &v_device);\n\t\tif (result != VK_SUCCESS) {\n\t\t\tgEngine.Con_Printf( S_ERROR \"%s:%d vkCreateDevice failed (%d): %s\\n\",\n\t\t\t\t__FILE__, __LINE__, result, R_VkResultName(result));\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tloadDeviceFunctions(device_funcs, COUNTOF(device_funcs));\n\n\tif (info->ray_tracing)\n\t\tloadDeviceFunctions(device_funcs_rtx, COUNTOF(device_funcs_rtx));\n\n\t// TODO do not access vk_core directly\n\tvk_core.device = v_device;\n\tvk_core.rtx = info->ray_tracing;\n\tvk_core.nv_checkpoint = info->nv_checkpoint;\n\tvkGetDeviceQueue(v_device, 0, 0, &vk_core.queue);\n\n\tv_device_info = *info;\n\n\tif (info->perf_query) {\n\t\tconst VkAcquireProfilingLockInfoKHR apli = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_ACQUIRE_PROFILING_LOCK_INFO_KHR,\n\t\t\t.timeout = UINT64_MAX,\n\t\t};\n\t\tconst VkResult result = vkAcquireProfilingLockKHR(v_device, &apli);\n\t\tif (result != VK_SUCCESS) {\n\t\t\tERR(\"Failed to acquire profiling lock: %#x: %s. Disabling performance query.\\n\",\n\t\t\t\tresult, R_VkResultName(result));\n\t\t\tv_device_info.perf_query = false;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nint vDeviceInit(VDeviceInitArgs args) {\n\tASSERT(v_device == VK_NULL_HANDLE);\n\tVDeviceInfos physical_devices = enumerateDevices();\n\n#if 0 // TODO\n\tchar unique_deviceID[16];\n\tconst qboolean is_target_device = vk_device_target_id && Q_stricmp(vk_device_target_id->string, \"\") && num_available_devices > 0;\n\tqboolean is_target_device_found = false;\n#endif\n\n\tfor (uint32_t i = 0; i < physical_devices.count; ++i) {\n\t\tVDeviceInfo* const devinfo = physical_devices.items + i;\n\n\t\t// Skip non-target device\n#if 0 // TODO\n\t\tQ_snprintf( unique_deviceID, sizeof( unique_deviceID ), \"%04x:%04x\", candidate_device->props.vendorID, candidate_device->props.deviceID );\n\t\tif (is_target_device && !is_target_device_found && Q_stricmp(vk_device_target_id->string, unique_deviceID)) {\n\t\t\tif (i == num_available_devices-1) {\n\t\t\t\tgEngine.Con_Printf(\"Not found device %s, start on %s. Please set a valid device.\\n\", vk_device_target_id->string, unique_deviceID);\n\t\t\t} else {\n\t\t\t\tgEngine.Con_Printf(\"Skip device %s, because selected %s\\n\", unique_deviceID, vk_device_target_id->string);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\tis_target_device_found = true;\n\t\t}\n#endif\n\n\t\tif (args.force_disable_rt && devinfo->ray_tracing) {\n\t\t\tWARN(\"Device[%d] supports ray tracing, but rt_force_disable is set, force-disabling ray tracing support.\", i);\n\t\t\tdevinfo->ray_tracing = 0;\n\t\t}\n\n\t\tif (devinfo->perf_query && !args.enable_perf_query) {\n\t\t\tINFO(\"Device[%d] supports performance query, but -vkperfquery wasn't supplied. Peformance query support will not be enabled.\", i);\n\t\t\tdevinfo->perf_query = 0;\n\t\t}\n\n\t\tINFO(\"Trying device #%d: %04x:%04x %d %s %u.%u.%u %u.%u.%u\",\n\t\t\ti, devinfo->properties.vendorID, devinfo->properties.deviceID, devinfo->properties.deviceType, devinfo->properties.deviceName,\n\t\t\tXVK_PARSE_VERSION(devinfo->properties.driverVersion), XVK_PARSE_VERSION(devinfo->properties.apiVersion));\n\n\t\tif (createDevice(devinfo) == 1)\n\t\t\tbreak;\n\t}\n\n\tMem_Free(physical_devices.items);\n\n\tif (v_device == VK_NULL_HANDLE) {\n\t\tERR(\"No compatibe Vulkan devices found. Vulkan render will not be available\" );\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid vDeviceShutdown(void) {\n\tif (v_device_info.perf_query) {\n\t\tvkReleaseProfilingLockKHR(v_device);\n\t}\n\n\tvkDestroyDevice(v_device, NULL);\n\tv_device = VK_NULL_HANDLE;\n\n\tif (v_device_info.perf_counters.count) {\n\t\tMem_Free((void*)v_device_info.perf_counters.counters);\n\t\tMem_Free((void*)v_device_info.perf_counters.desc);\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VDevice.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\ntypedef struct VDeviceInfo {\n\tVkPhysicalDevice physical_device;\n\n\tVkPhysicalDeviceProperties properties;\n\tVkPhysicalDeviceFeatures2 features;\n\n\tVkPhysicalDeviceMemoryProperties2 memory_properties2;\n\tVkPhysicalDeviceMemoryBudgetPropertiesEXT memory_budget;\n\n\tVkPhysicalDeviceProperties2 properties2;\n\tVkPhysicalDeviceAccelerationStructurePropertiesKHR properties_accel;\n\tVkPhysicalDeviceRayTracingPipelinePropertiesKHR properties_ray_tracing_pipeline;\n\n\tuint32_t sbt_record_size;\n\tuint32_t queue_index;\n\n\tqboolean anisotropy;\n\tqboolean ray_tracing;\n\tqboolean nv_checkpoint;\n\tqboolean calibrated_timestamps;\n\tqboolean perf_query;\n\n\tstruct {\n\t\tuint32_t count;\n\t\tconst VkPerformanceCounterKHR *counters;\n\t\tVkPerformanceCounterDescriptionKHR *desc;\n\t} perf_counters;\n} VDeviceInfo;\n\nextern VDeviceInfo v_device_info;\nextern VkDevice v_device;\n\ntypedef struct {\n\tint force_disable_rt;\n\tint enable_perf_query;\n} VDeviceInitArgs;\n\nint vDeviceInit(VDeviceInitArgs);\nvoid vDeviceShutdown(void);\n\nvoid vDevicePrintPerformanceCounters(const VDeviceInfo *info);\n"
  },
  {
    "path": "ref/vk/vulkan/VDevmem.c",
    "content": "#include \"VDevmem.h\"\n#include \"std/alolcator.h\"\n#include \"r_speeds.h\"\n\n#define MAX_DEVMEM_ALLOC_SLOTS 128\n#define DEFAULT_ALLOCATION_SIZE (64 * 1024 * 1024)\n\n#define MODULE_NAME \"devmem\"\n\ntypedef struct vk_device_memory_slot_s {\n\tuint32_t type_index;\n\tVkMemoryPropertyFlags property_flags; // device vs host\n\tVkMemoryAllocateFlags allocate_flags;\n\tVkDeviceMemory device_memory;\n\tVkDeviceSize size;\n\n\tvoid *mapped;\n\tint refcount;\n\n\tstruct alo_pool_s *allocator;\n} vk_device_memory_slot_t;\n\ntypedef struct vk_devmem_allocation_stats_s {\n\t// Metrics updated on every allocation and deallocation.\n\tstruct {\n\t\tint allocations;          // Current number of active (not freed) allocations.\n\t\tint allocated;            // Current size of allocated memory.\n\t\tint align_holes;          // Current number of alignment holes in active (not freed) allocations.\n\t\tint align_holes_size;     // Current size of alignment holes in active (not freed) allocations.\n\t} current;\n\n\t// Metrics updated whenever new highest value is registered.\n\tstruct {\n\t\tint allocations;          // Highest number of allocations made.\n\t\tint allocated;            // Largest size of allocated memory.\n\t\tint allocation;           // Largest size of single allocation made.\n\t\tint align_holes;          // Highest number of alignment holes made.\n\t\tint align_holes_size;     // Largest size of alignment holes made.\n\t\tint align_hole_size;      // Largest size of single alignment hole made.\n\t} peak;\n} vk_devmem_allocation_stats_t;\n\nstatic struct {\n\tvk_device_memory_slot_t alloc_slots[MAX_DEVMEM_ALLOC_SLOTS];\n\tint alloc_slots_count;\n\n\t// Size of memory allocated on logical device `VkDevice`\n\t// (which is basically bound to physical device `VkPhysicalDevice`).\n\tint device_allocated;\n\n\t// Allocation statistics for each usage type.\n\tvk_devmem_allocation_stats_t stats[VK_DEVMEM_USAGE_TYPES_COUNT];\n\n\tqboolean verbose;\n} g_devmem;\n\n// Format for printf-like functions to represent bits of `VkMemoryPropertyFlags`.\n// Usage example:   gEngine.Con_Reportf( \"property_flags: \" PRI_VKMEMPROPFLAGS_FMT \"\\n\", PRI_VKMEMPROPFLAGS_ARG( property_flags ) );\n#define PRI_VKMEMPROPFLAGS_FMT \"%c%c%c%c%c\"\n\n// Inline arguments for `PRI_VKMEMPROPFLAGS_FMT` format macro.\n#define PRI_VKMEMPROPFLAGS_ARG( flags ) \\\n\t( flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT     ) ? 'D' : '-', \\\n\t( flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT     ) ? 'V' : '-', \\\n\t( flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT    ) ? 'C' : '-', \\\n\t( flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT      ) ? '$' : '-', \\\n\t( flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT ) ? 'L' : '-'\n\t// Not used:\n\t// VK_MEMORY_PROPERTY_PROTECTED_BIT\n\t// VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD\n\t// VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD\n\t// VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV\n\n// Format for printf-like functions to represent bits of `VkMemoryAllocateFlags`.\n// Usage example:   gEngine.Con_Reportf( \"allocate_flags: \" PRI_VKMEMALLOCFLAGS_FMT \"\\n\", PRI_VKMEMALLOCFLAGS_ARG( allocate_flags ) );\n#define PRI_VKMEMALLOCFLAGS_FMT \"%c%c%c\"\n\n// Inline arguments for `PRI_VKMEMALLOCFLAGS_FMT` format macro.\n#define PRI_VKMEMALLOCFLAGS_ARG( flags ) \\\n\t( flags & VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT                   ) ? 'M' : '-', \\\n\t( flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT                ) ? 'A' : '-', \\\n\t( flags & VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT ) ? 'R' : '-'\n\n// Register allocation in overall stats and for the corresponding type stats too.\n#define REGISTER_ALLOCATION( type, size, alignment, alignment_hole ) \\\n\tregister_allocation_for_type( VK_DEVMEM_USAGE_TYPE_ALL, size, alignment, alignment_hole ); \\\n\tregister_allocation_for_type( type, size, alignment, alignment_hole );\n\n// Register deallocation (freeing) in overall stats and for the corresponding type stats too.\n#define REGISTER_FREE( type, size, alignment, alignment_hole ) \\\n\tregister_free_for_type( VK_DEVMEM_USAGE_TYPE_ALL, size, alignment, alignment_hole ); \\\n\tregister_free_for_type( type, size, alignment, alignment_hole );\n\n// Register allocation in stats of the provided type.\nstatic void register_allocation_for_type( vk_devmem_usage_type_t type, int size, int alignment, int alignment_hole ) {\n\tASSERT( type >= VK_DEVMEM_USAGE_TYPE_ALL );\n\tASSERT( type <  VK_DEVMEM_USAGE_TYPES_COUNT );\n\n\tvk_devmem_allocation_stats_t *const stats = &g_devmem.stats[type];\n\n\t/* Update allocations stats. */\n\n\t// Update current allocations.\n\tstats->current.allocations += 1;\n\tstats->current.allocated   += size;\n\n\t// Update peak allocations.\n\tif ( stats->peak.allocations < stats->current.allocations )\n\t\tstats->peak.allocations = stats->current.allocations;\n\n\tif ( stats->peak.allocated < stats->current.allocated )\n\t\tstats->peak.allocated = stats->current.allocated;\n\n\tif ( stats->peak.allocation < size )\n\t\tstats->peak.allocation = size;\n\n\t/* Update alignment holes stats. */\n\n\tif ( alignment_hole > 0 ) {\n\t\t// Update current alignment holes stats.\n\t\tstats->current.align_holes      += 1;\n\t\tstats->current.align_holes_size += alignment_hole;\n\n\t\t// Update peak alignment holes stats.\n\t\tif ( stats->peak.align_holes < stats->current.align_holes )\n\t\t\tstats->peak.align_holes = stats->current.align_holes;\n\n\t\tif ( stats->peak.align_holes_size < stats->current.align_holes_size )\n\t\t\tstats->peak.align_holes_size = stats->current.align_holes_size;\n\n\t\tif ( stats->peak.align_hole_size < alignment_hole )\n\t\t\tstats->peak.align_hole_size = alignment_hole;\n\t}\n}\n\n// Register deallocation (freeing) in stats of the provided type.\nstatic void register_free_for_type( vk_devmem_usage_type_t type, int size, int alignment, int alignment_hole ) {\n\tASSERT( type >= VK_DEVMEM_USAGE_TYPE_ALL );\n\tASSERT( type <  VK_DEVMEM_USAGE_TYPES_COUNT );\n\n\tvk_devmem_allocation_stats_t *const stats = &g_devmem.stats[type];\n\n\t/* Update current allocations stats. */\n\n\tstats->current.allocations -= 1;\n\tstats->current.allocated   -= size;\n\n\t/* Update current alignment holes stats. */\n\n\tif ( alignment_hole > 0 ) {\n\t\tstats->current.align_holes      -= 1;\n\t\tstats->current.align_holes_size -= size;\n\t}\n}\n\n// Returns short string representation of `vk_devmem_usage_type_t` usage type.\nstatic const char *VK_DevMemUsageTypeString( vk_devmem_usage_type_t type ) {\n\tASSERT( type >= VK_DEVMEM_USAGE_TYPE_ALL );\n\tASSERT( type < VK_DEVMEM_USAGE_TYPES_COUNT );\n\n\tswitch ( type ) {\n\t\tcase VK_DEVMEM_USAGE_TYPE_ALL:     return \"ALL\";\n\t\tcase VK_DEVMEM_USAGE_TYPE_BUFFER:  return \"BUFFER\";\n\t\tcase VK_DEVMEM_USAGE_TYPE_IMAGE:   return \"IMAGE\";\n\t}\n\n\treturn \"(unknown)\";\n}\n\nstatic int findMemoryWithType(uint32_t type_index_bits, VkMemoryPropertyFlags flags) {\n\tconst VkPhysicalDeviceMemoryProperties *const properties = &v_device_info.memory_properties2.memoryProperties;\n\tfor ( int type = 0; type < (int)properties->memoryTypeCount; type += 1 ) {\n\t\tif ( !( type_index_bits & ( 1 << type ) ) )\n\t\t\tcontinue;\n\n\t\tif ( ( properties->memoryTypes[type].propertyFlags & flags ) == flags )\n\t\t\treturn type;\n\t}\n\n\treturn UINT32_MAX;\n}\n\nstatic VkDeviceSize optimalSize(VkDeviceSize size) {\n\tif ( size < DEFAULT_ALLOCATION_SIZE )\n\t\treturn DEFAULT_ALLOCATION_SIZE;\n\n\t// TODO:\n\t// 1. have a way to iterate for smaller sizes if allocation failed\n\t// 2. bump to nearest power-of-two-ish based size (e.g. a multiple of 32Mb or something)\n\n\treturn size;\n}\n\nstatic int allocateDeviceMemory(VkMemoryRequirements req, int type_index, VkMemoryAllocateFlags allocate_flags) {\n\tif ( g_devmem.alloc_slots_count == MAX_DEVMEM_ALLOC_SLOTS ) {\n\t\tgEngine.Host_Error( \"Ran out of %d device memory allocation slots\\n\", (int)MAX_DEVMEM_ALLOC_SLOTS );\n\t\treturn -1;\n\t}\n\n\t{\n\t\tconst VkMemoryAllocateFlagsInfo mafi = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,\n\t\t\t.flags = allocate_flags,\n\t\t};\n\n\t\tconst VkMemoryAllocateInfo mai = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,\n\t\t\t.pNext = allocate_flags ? &mafi : NULL,\n\t\t\t.allocationSize = optimalSize(req.size),\n\t\t\t.memoryTypeIndex = type_index,\n\t\t};\n\n\t\tif ( g_devmem.verbose ) {\n\t\t\tgEngine.Con_Reportf( \"  ^3->^7 ^6AllocateDeviceMemory:^7 { size: %s, memoryTypeBits: 0x%x, allocate_flags: \" PRI_VKMEMALLOCFLAGS_FMT \" => typeIndex: %d }\\n\",\n\t\t\t\tQ_memprint( (float)mai.allocationSize ), req.memoryTypeBits, PRI_VKMEMALLOCFLAGS_ARG( allocate_flags ), mai.memoryTypeIndex );\n\t\t}\n\t\tASSERT( mai.memoryTypeIndex != UINT32_MAX );\n\n\t\tvk_device_memory_slot_t *slot = &g_devmem.alloc_slots[g_devmem.alloc_slots_count];\n\t\tXVK_CHECK( vkAllocateMemory( vk_core.device, &mai, NULL, &slot->device_memory ) );\n\n\t\tconst VkPhysicalDeviceMemoryProperties *const properties = &v_device_info.memory_properties2.memoryProperties;\n\t\tslot->property_flags = properties->memoryTypes[mai.memoryTypeIndex].propertyFlags;\n\t\tslot->allocate_flags = allocate_flags;\n\t\tslot->type_index     = mai.memoryTypeIndex;\n\t\tslot->refcount       = 0;\n\t\tslot->size           = mai.allocationSize;\n\n\t\tg_devmem.device_allocated += mai.allocationSize;\n\n\t\tconst int expected_allocations = 0;\n\t\tconst int min_alignment = 16;\n\t\tslot->allocator = aloPoolCreate( slot->size, expected_allocations, min_alignment );\n\n\t\tif ( slot->property_flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT ) {\n\t\t\tXVK_CHECK( vkMapMemory( vk_core.device, slot->device_memory, 0, slot->size, 0, &slot->mapped ) );\n\t\t\tif ( g_devmem.verbose ) {\n\t\t\t\tsize_t device        = (size_t) vk_core.device;\n\t\t\t\tsize_t mapped        = (size_t) slot->mapped;\n\t\t\t\tsize_t device_memory = (size_t) slot->device_memory;\n\t\t\t\t// `z` - specifies `size_t` length\n\t\t\t\tgEngine.Con_Reportf( \"  ^3->^7 ^6Mapped:^7 { device: 0x%zx, mapped: 0x%zx, device_memory: 0x%zx, size: %s }\\n\",\n\t\t\t\t\tdevice, mapped, device_memory, Q_memprint( (float)slot->size ) );\n\t\t\t}\n\t\t} else {\n\t\t\tslot->mapped = NULL;\n\t\t}\n\t}\n\n\treturn g_devmem.alloc_slots_count++;\n}\n\nvk_devmem_t VK_DevMemAllocate(const char *name, vk_devmem_usage_type_t usage_type, vk_devmem_allocate_args_t devmem_allocate_args) {\n\tVkMemoryRequirements  req            = devmem_allocate_args.requirements;\n\tVkMemoryPropertyFlags property_flags = devmem_allocate_args.property_flags;\n\tVkMemoryAllocateFlags allocate_flags = devmem_allocate_args.allocate_flags;\n\n\tvk_devmem_t devmem = { .usage_type = usage_type };\n\tconst int type_index = findMemoryWithType(req.memoryTypeBits, property_flags);\n\n\tif ( g_devmem.verbose ) {\n\t\tconst char *usage_type_str = VK_DevMemUsageTypeString( usage_type );\n\t\tsize_t alignment = (size_t) req.alignment;\n\t\tgEngine.Con_Reportf( \"^3VK_DevMemAllocate:^7 { name: \\\"%s\\\", usage: %s, size: %s, alignment: %zu, memoryTypeBits: 0x%x, property_flags: \" PRI_VKMEMPROPFLAGS_FMT \", allocate_flags: \" PRI_VKMEMALLOCFLAGS_FMT \" => type_index: %d }\\n\",\n\t\t\tname, usage_type_str, Q_memprint( (float)req.size ), alignment, req.memoryTypeBits, PRI_VKMEMPROPFLAGS_ARG( property_flags ), PRI_VKMEMALLOCFLAGS_ARG( allocate_flags ), type_index );\n\t}\n\n\talo_block_t block;\n\tint selected_slot_index = -1;\n\tfor ( int slot_index = 0; slot_index < g_devmem.alloc_slots_count; slot_index += 1 ) {\n\t\tvk_device_memory_slot_t *const slot = g_devmem.alloc_slots + slot_index;\n\t\tif ( slot->type_index != type_index )\n\t\t\tcontinue;\n\n\t\tif ( ( slot->allocate_flags & allocate_flags ) != allocate_flags )\n\t\t\tcontinue;\n\n\t\tif ( ( slot->property_flags & property_flags ) != property_flags )\n\t\t\tcontinue;\n\n\t\tblock = aloPoolAllocate( slot->allocator, req.size, req.alignment );\n\t\tif ( block.size == 0 )\n\t\t\tcontinue;\n\n\t\tselected_slot_index = slot_index;\n\t\tbreak;\n\t}\n\n\tif ( selected_slot_index < 0 ) {\n\t\tselected_slot_index = allocateDeviceMemory( req, type_index, allocate_flags );\n\t\tASSERT( selected_slot_index >= 0 );\n\t\tif ( selected_slot_index < 0 )\n\t\t\treturn devmem;\n\n\t\tstruct alo_pool_s *allocator = g_devmem.alloc_slots[selected_slot_index].allocator;\n\t\tblock = aloPoolAllocate( allocator, req.size, req.alignment );\n\t\tASSERT( block.size != 0 );\n\t}\n\n\t{\n\t\tvk_device_memory_slot_t *const slot = g_devmem.alloc_slots + selected_slot_index;\n\t\tdevmem.device_memory = slot->device_memory;\n\t\tdevmem.offset        = block.offset;\n\t\tdevmem.mapped        = slot->mapped ? (char *)slot->mapped + block.offset : NULL;\n\n\t\tif ( g_devmem.verbose ) {\n\t\t\tgEngine.Con_Reportf( \"  ^3->^7 Allocated: { slot: %d, block: %d, offset: %u, size: %u, hole: %u }\\n\",\n\t\t\t\tselected_slot_index, block.index, block.offset, block.size, block.alignment_hole );\n\t\t}\n\n\t\tslot->refcount++;\n\t\tdevmem.internal.slot_index           = selected_slot_index;\n\t\tdevmem.internal.block_index          = block.index;\n\t\tdevmem.internal.block_size           = block.size;\n\t\tdevmem.internal.block_alignment      = req.alignment;\n\t\tdevmem.internal.block_alignment_hole = block.alignment_hole;\n\n\t\tREGISTER_ALLOCATION( usage_type, block.size, req.alignment, block.alignment_hole );\n\n\t\treturn devmem;\n\t}\n}\n\nvoid VK_DevMemFree(const vk_devmem_t *mem) {\n\tint slot_index = mem->internal.slot_index;\n\tASSERT( slot_index >= 0 );\n\tASSERT( slot_index < g_devmem.alloc_slots_count );\n\n\tvk_device_memory_slot_t *const slot = g_devmem.alloc_slots + slot_index;\n\tASSERT( mem->device_memory == slot->device_memory );\n\n\tif ( g_devmem.verbose ) {\n\t\tconst char *usage_type = VK_DevMemUsageTypeString( mem->usage_type );\n\t\tgEngine.Con_Reportf( \"^2VK_DevMemFree:^7 { slot: %d, block: %d, usage: %s, size: %s, alignment: %d, alignment_hole: %d }\\n\",\n\t\t\tslot_index, mem->internal.block_index, usage_type, Q_memprint( (float)mem->internal.block_size ), mem->internal.block_alignment, mem->internal.block_alignment_hole );\n\t}\n\n\taloPoolFree( slot->allocator, mem->internal.block_index );\n\n\tREGISTER_FREE( mem->usage_type, mem->internal.block_size, mem->internal.block_alignment, mem->internal.block_alignment_hole );\n\n\tASSERT(slot->refcount > 0);\n\tslot->refcount--;\n\n\tif (slot->refcount == 0) {\n\t\t// FIXME free empty\n\t\tgEngine.Con_Reportf(S_WARN \"device_memory_slot[%d] reached refcount=0\\n\", slot_index);\n\t}\n}\n\n// Register single stats variable.\n#define REGISTER_STATS_METRIC( var, metric_name, var_name, metric_type ) \\\n\tR_SpeedsRegisterMetric( &(var), MODULE_NAME, #metric_name, metric_type, /*reset*/ false, #var_name, __FILE__, __LINE__ );\n\n// NOTE(nilsoncore): I know, this is a mess... Sorry.\n// It could have been avoided by having short `VK_DevMemUsageTypes` enum names,\n// but I have done it this way because I want those enum names to be as descriptive as possible.\n// This basically replaces those enum names with ones provided by suffixes, which are just their endings.\n//\n//\t                     | var                              | metric_name                            | var_name                                              | metric_type        |\n//\t                     | -------------------------------- | -------------------------------------- | ----------------------------------------------------- | ------------------ |\n#define REGISTER_STATS_METRICS( usage_type, usage_suffix ) { \\\n\tvk_devmem_allocation_stats_t *const stats = &g_devmem.stats[usage_type]; \\\n \tREGISTER_STATS_METRIC( stats->current.allocations,       current_allocations##usage_suffix,       g_devmem.stats[usage_suffix].current.allocations,       kSpeedsMetricCount ); \\\n \tREGISTER_STATS_METRIC( stats->current.allocated,         current_allocated##usage_suffix,         g_devmem.stats[usage_suffix].current.allocated,         kSpeedsMetricBytes ); \\\n \tREGISTER_STATS_METRIC( stats->current.align_holes,       current_align_holes##usage_suffix,       g_devmem.stats[usage_suffix].current.align_holes,       kSpeedsMetricCount ); \\\n \tREGISTER_STATS_METRIC( stats->current.align_holes_size,  current_align_holes_size##usage_suffix,  g_devmem.stats[usage_suffix].current.align_holes_size,  kSpeedsMetricBytes ); \\\n \tREGISTER_STATS_METRIC( stats->peak.allocations,          peak_allocations##usage_suffix,          g_devmem.stats[usage_suffix].peak.allocations,          kSpeedsMetricCount ); \\\n \tREGISTER_STATS_METRIC( stats->peak.allocated,            peak_allocated##usage_suffix,            g_devmem.stats[usage_suffix].peak.allocated,            kSpeedsMetricBytes ); \\\n \tREGISTER_STATS_METRIC( stats->peak.allocation,           peak_allocation##usage_suffix,           g_devmem.stats[usage_suffix].peak.allocation,           kSpeedsMetricBytes ); \\\n \tREGISTER_STATS_METRIC( stats->peak.align_holes,          peak_align_holes##usage_suffix,          g_devmem.stats[usage_suffix].peak.align_holes,          kSpeedsMetricCount ); \\\n \tREGISTER_STATS_METRIC( stats->peak.align_holes_size,     peak_align_holes_size##usage_suffix,     g_devmem.stats[usage_suffix].peak.align_holes_size,     kSpeedsMetricBytes ); \\\n \tREGISTER_STATS_METRIC( stats->peak.align_hole_size,      peak_align_hole_size##usage_suffix,      g_devmem.stats[usage_suffix].peak.align_hole_size,      kSpeedsMetricBytes ); \\\n}\n\nqboolean VK_DevMemInit( void ) {\n\tg_devmem.verbose = !!gEngine.Sys_CheckParm( \"-vkdebugmem\" );\n\n\t// Register standalone metrics.\n\tR_SPEEDS_METRIC( g_devmem.alloc_slots_count, \"allocated_slots\", kSpeedsMetricCount );\n\tR_SPEEDS_METRIC( g_devmem.device_allocated, \"device_allocated\", kSpeedsMetricBytes );\n\n\t// Register stats metrics for each usage type.\n\tREGISTER_STATS_METRICS( VK_DEVMEM_USAGE_TYPE_ALL,    _ALL );\n\tREGISTER_STATS_METRICS( VK_DEVMEM_USAGE_TYPE_BUFFER, _BUFFER );\n\tREGISTER_STATS_METRICS( VK_DEVMEM_USAGE_TYPE_IMAGE,  _IMAGE );\n\n\treturn true;\n}\n\nvoid VK_DevMemDestroy( void ) {\n\tfor ( int slot_index = 0; slot_index < g_devmem.alloc_slots_count; slot_index += 1 ) {\n\t\tconst vk_device_memory_slot_t *const slot = g_devmem.alloc_slots + slot_index;\n\t\tASSERT( slot->refcount == 0 );\n\n\t\t// TODO check that everything has been freed\n\t\taloPoolDestroy( slot->allocator );\n\n\t\tif ( slot->mapped )\n\t\t\tvkUnmapMemory( vk_core.device, slot->device_memory );\n\n\t\tvkFreeMemory( vk_core.device, slot->device_memory, NULL );\n\t}\n\n\tg_devmem.alloc_slots_count = 0;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VDevmem.h",
    "content": "#pragma once\n#include \"vk_core.h\"\n\nqboolean VK_DevMemInit( void );\nvoid VK_DevMemDestroy( void );\n\ntypedef int vk_devmem_usage_type_t;\nenum VK_DevMemUsageTypes {\n\t// NOTE(nilsoncore):\n\t// This type should not be used as it is there\n\t// to not overcomplicate things and index through\n\t// internal global stats directly by these indices.\n\tVK_DEVMEM_USAGE_TYPE_ALL     = 0,\n\n\t// Those are `vk_buffer_t` buffers.\n\tVK_DEVMEM_USAGE_TYPE_BUFFER  = 1,\n\n\t// Those are `xvk_image_t` images.\n\tVK_DEVMEM_USAGE_TYPE_IMAGE   = 2,\n\n\tVK_DEVMEM_USAGE_TYPES_COUNT\n};\n\ntypedef struct vk_devmem_s {\n\tVkDeviceMemory device_memory;\n\tuint32_t offset;\n\tvk_devmem_usage_type_t usage_type;\n\tvoid *mapped;\n\n\tstruct {\n\t\tint slot_index;\n\t\tint block_index;\n\n\t\t// alolcator.h:\n\t\t// typedef uint32_t alo_size_t;\n\t\tuint32_t block_size;\n\t\tuint32_t block_alignment;\n\t\tuint32_t block_alignment_hole;\n\t} internal;\n} vk_devmem_t;\n\ntypedef struct vk_devmem_allocate_args_s {\n\tVkMemoryRequirements  requirements;\n\tVkMemoryPropertyFlags property_flags;\n\tVkMemoryAllocateFlags allocate_flags;\n} vk_devmem_allocate_args_t;\n\n#define VK_DevMemAllocateBuffer( name, devmem_allocate_args ) \\\n\tVK_DevMemAllocate( name, VK_DEVMEM_USAGE_TYPE_BUFFER, devmem_allocate_args );\n\n#define VK_DevMemAllocateImage( name, devmem_allocate_args ) \\\n\tVK_DevMemAllocate( name, VK_DEVMEM_USAGE_TYPE_IMAGE, devmem_allocate_args );\n\nvk_devmem_t VK_DevMemAllocate(const char *name, vk_devmem_usage_type_t usage_type, vk_devmem_allocate_args_t devmem_allocate_args);\nvoid VK_DevMemFree(const vk_devmem_t *mem);\n\n\n"
  },
  {
    "path": "ref/vk/vulkan/VImage.c",
    "content": "#include \"VImage.h\"\n#include \"VStaging.h\"\n#include \"VCombuf.h\"\n#include \"vk_logs.h\"\n#include \"VBarrier.h\"\n#include \"std/arrays.h\"\n\n#include \"xash3d_mathlib.h\" // Q_max\n\n// Long type lists functions\n#include \"VImageExtra.h\"\n\n#define LOG_MODULE img\n\nstatic const VkImageUsageFlags usage_bits_implying_views =\n\tVK_IMAGE_USAGE_SAMPLED_BIT |\n\tVK_IMAGE_USAGE_STORAGE_BIT |\n\tVK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |\n\tVK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |\n\tVK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |\n\tVK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT |\n\tVK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR |\n\tVK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT;\n/*\n\tVK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR |\n\tVK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR |\n\tVK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR |\n\tVK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR;\n*/\n\nr_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create) {\n\tconst qboolean is_depth = !!(create->usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);\n\tr_vk_image_t image = {0};\n\tVkMemoryRequirements memreq;\n\n\tconst qboolean is_cubemap = !!(create->flags & kVkImageFlagIsCubemap);\n\tconst qboolean is_3d = create->depth > 1;\n\n\tASSERT(create->depth > 0);\n\n\tASSERT(is_cubemap + is_3d != 2);\n\n\tconst VkFormat unorm_format = unormFormatFor(create->format);\n\tconst qboolean create_unorm =\n\t\t!!(create->flags & kVkImageFlagCreateUnormView)\n\t\t&& unorm_format != VK_FORMAT_UNDEFINED\n\t\t&& unorm_format != create->format;\n\n\tVkImageCreateInfo ici = {\n\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,\n\t\t.imageType = is_3d ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,\n\t\t.extent.width = create->width,\n\t\t.extent.height = create->height,\n\t\t.extent.depth = create->depth,\n\t\t.mipLevels = create->mips,\n\t\t.arrayLayers = create->layers,\n\t\t.format = create->format,\n\t\t.tiling = create->tiling,\n\t\t.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,\n\t\t.usage = create->usage,\n\t\t.samples = VK_SAMPLE_COUNT_1_BIT,\n\t\t.sharingMode = VK_SHARING_MODE_EXCLUSIVE,\n\t\t.flags = 0\n\t\t\t| (is_cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0)\n\t\t\t| (create_unorm ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0),\n\t};\n\n\tXVK_CHECK(vkCreateImage(vk_core.device, &ici, NULL, &image.image));\n\n\timage.format = ici.format;\n\n\tif (create->debug_name)\n\t\tSET_DEBUG_NAME(image.image, VK_OBJECT_TYPE_IMAGE, create->debug_name);\n\n\tvkGetImageMemoryRequirements(vk_core.device, image.image, &memreq);\n\n\tvk_devmem_allocate_args_t args = (vk_devmem_allocate_args_t) {\n\t\t.requirements = memreq,\n\t\t.property_flags = (create->memory_props) ? create->memory_props : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,\n\t\t.allocate_flags = 0,\n\t};\n\timage.devmem = VK_DevMemAllocateImage( create->debug_name, args );\n\n\tXVK_CHECK(vkBindImageMemory(vk_core.device, image.image, image.devmem.device_memory, image.devmem.offset));\n\n\tif (create->usage & usage_bits_implying_views) {\n\t\tconst qboolean ignore_alpha = !!(create->flags & kVkImageFlagIgnoreAlpha) && !is_depth;\n\n\t\tVkImageViewCreateInfo ivci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,\n\t\t\t.viewType = is_cubemap ? VK_IMAGE_VIEW_TYPE_CUBE : (is_3d ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D),\n\t\t\t.format = ici.format,\n\t\t\t.image = image.image,\n\t\t\t.subresourceRange.aspectMask = is_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.subresourceRange.baseMipLevel = 0,\n\t\t\t.subresourceRange.levelCount = ici.mipLevels,\n\t\t\t.subresourceRange.baseArrayLayer = 0,\n\t\t\t.subresourceRange.layerCount = ici.arrayLayers,\n\t\t\t.components = componentMappingForFormat(ici.format, ignore_alpha),\n\t\t};\n\t\tXVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, &image.view));\n\n\t\tif (create->debug_name)\n\t\t\tSET_DEBUG_NAME(image.view, VK_OBJECT_TYPE_IMAGE_VIEW, create->debug_name);\n\n\t\tif (create_unorm) {\n\t\t\tivci.format = unorm_format;\n\t\t\tXVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, &image.view_unorm));\n\n\t\t\tif (create->debug_name)\n\t\t\t\tSET_DEBUG_NAMEF(image.view_unorm, VK_OBJECT_TYPE_IMAGE_VIEW, \"%s_unorm\", create->debug_name);\n\t\t}\n\t}\n\n\tQ_strncpy(image.name, create->debug_name, sizeof(image.name));\n\timage.width = create->width;\n\timage.height = create->height;\n\timage.depth = create->depth;\n\timage.mips = create->mips;\n\timage.layers = create->layers;\n\timage.flags = create->flags;\n\timage.image_size = memreq.size;\n\timage.upload_slot = -1;\n\n\treturn image;\n}\n\nstatic void cancelUpload( r_vk_image_t *img );\n\nvoid R_VkImageDestroy(r_vk_image_t *img) {\n\t// Need to make sure that there are no references to this image anywhere.\n\t// It might have been added to upload queue, but then immediately deleted, leaving references\n\t// in the queue. See https://github.com/w23/xash3d-fwgs/issues/464\n\tcancelUpload(img);\n\n\t// Image destroy calls are not explicitly synchronized with rendering. GPU might still be\n\t// processing previous frame. We need to make sure that GPU is done by the time we start\n\t// messing with any VkImage objects.\n\t// TODO: textures are usually destroyed in bulk, so we don't really need to wait for each one.\n\t// TODO: check with framectl for any in-flight frames or any other GPU activity\n\tXVK_CHECK(vkDeviceWaitIdle(vk_core.device));\n\n\tif (img->view_unorm != VK_NULL_HANDLE)\n\t\tvkDestroyImageView(vk_core.device, img->view_unorm, NULL);\n\n\tif (img->view != VK_NULL_HANDLE)\n\t\tvkDestroyImageView(vk_core.device, img->view, NULL);\n\n\tif (img->image != VK_NULL_HANDLE)\n\t\tvkDestroyImage(vk_core.device, img->image, NULL);\n\n\tVK_DevMemFree(&img->devmem);\n\t*img = (r_vk_image_t){0};\n}\n\nvoid R_VkImageClear(r_vk_image_t *img, struct vk_combuf_s* combuf, const VkClearColorValue* value) {\n\t{\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_CLEAR_BIT);\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t.image = img,\n\t\t\t// Could be VK_IMAGE_LAYOUT_GENERAL too\n\t\t\t.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t\t.access = VK_ACCESS_2_TRANSFER_WRITE_BIT,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\tconst VkImageSubresourceRange ranges[] = {{\n\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t.baseMipLevel = 0,\n\t\t.levelCount = VK_REMAINING_MIP_LEVELS,\n\t\t.baseArrayLayer = 0,\n\t\t.layerCount = VK_REMAINING_ARRAY_LAYERS,\n\t}};\n\tconst VkClearColorValue zero = {0};\n\tvkCmdClearColorImage(combuf->cmdbuf, img->image, img->sync.layout,\n\t\tvalue ? value : &zero,\n\t\tCOUNTOF(ranges), ranges);\n}\n\nvoid R_VkImageBlit(struct vk_combuf_s *combuf, const r_vkimage_blit_args *args ) {\n\t{\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_2_BLIT_BIT);\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t.image = args->src.image,\n\t\t\t.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,\n\t\t\t.access = VK_ACCESS_2_TRANSFER_READ_BIT,\n\t\t});\n\t\tbarrierAddImage(&barrier, (r_vkcombuf_barrier_image_t) {\n\t\t\t.image = args->dst.image,\n\t\t\t.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t\t.access = VK_ACCESS_2_TRANSFER_WRITE_BIT,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\t{\n\t\tVkImageBlit region = {0};\n\t\tregion.srcOffsets[1].x = args->src.width ? args->src.width : args->src.image->width;\n\t\tregion.srcOffsets[1].y = args->src.height ? args->src.height : args->src.image->height;\n\t\tregion.srcOffsets[1].z = args->src.depth ? args->src.depth : args->src.image->depth;\n\n\t\tregion.dstOffsets[1].x = args->dst.width ? args->dst.width : args->dst.image->width;\n\t\tregion.dstOffsets[1].y = args->dst.height ? args->dst.height : args->dst.image->height;\n\t\tregion.dstOffsets[1].z = args->dst.depth ? args->dst.depth : args->dst.image->depth;\n\n\t\tregion.srcSubresource.aspectMask = region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n\t\tregion.srcSubresource.layerCount = region.dstSubresource.layerCount = 1; // VK_REMAINING_ARRAY_LAYERS requires maintenance5. No need to use it now.\n\t\tvkCmdBlitImage(combuf->cmdbuf,\n\t\t\targs->src.image->image, args->src.image->sync.layout,\n\t\t\targs->dst.image->image, args->dst.image->sync.layout,\n\t\t\t1, &region,\n\t\t\tVK_FILTER_NEAREST);\n\t}\n}\n\ntypedef struct {\n\tr_vk_image_t *image;\n\n\tstruct {\n\t\t// arena for entire layers * mips image\n\t\tr_vkstaging_region_t lock;\n\n\t\t// current write offset into the arena\n\t\tint cursor;\n\t} staging;\n\n\tstruct {\n\t\tint begin, cursor, end;\n\t} slices;\n} image_upload_t;\n\nstatic struct {\n\tr_vkstaging_user_handle_t staging;\n\n\tARRAY_DYNAMIC_DECLARE(image_upload_t, images);\n\tARRAY_DYNAMIC_DECLARE(VkBufferImageCopy, slices);\n\tARRAY_DYNAMIC_DECLARE(VkImageMemoryBarrier, barriers);\n} g_image_upload;\n\nstatic void imageStagingPush(void* userptr, struct vk_combuf_s *combuf, uint32_t allocations) {\n\t(void)userptr;\n\tconst VkPipelineStageFlags2 assume_stage\n\t\t= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;\n\tR_VkImageUploadCommit(combuf, assume_stage);\n}\n\nqboolean R_VkImageInit(void) {\n\tarrayDynamicInitT(&g_image_upload.images);\n\tarrayDynamicInitT(&g_image_upload.slices);\n\tarrayDynamicInitT(&g_image_upload.barriers);\n\n\tg_image_upload.staging = R_VkStagingUserCreate((r_vkstaging_user_create_t){\n\t\t.name = \"image\",\n\t\t.userptr = NULL,\n\t\t.push = imageStagingPush,\n\t});\n\n\treturn true;\n}\n\nvoid R_VkImageShutdown(void) {\n\tASSERT(g_image_upload.images.count == 0);\n\tR_VkStagingUserDestroy(g_image_upload.staging);\n\tarrayDynamicDestroyT(&g_image_upload.images);\n\tarrayDynamicDestroyT(&g_image_upload.slices);\n\tarrayDynamicDestroyT(&g_image_upload.barriers);\n}\n\nvoid R_VkImageUploadCommit( struct vk_combuf_s *combuf, VkPipelineStageFlagBits dst_stages ) {\n\tconst int images_count = g_image_upload.images.count;\n\tif (images_count == 0)\n\t\treturn;\n\n\tDEBUG(\"Uploading %d images\", images_count);\n\n\tstatic int gpu_scope_id = -2;\n\tif (gpu_scope_id == -2)\n\t\tgpu_scope_id = R_VkGpuScope_Register(\"image_upload\");\n\tconst int gpu_scope_begin = R_VkCombufScopeBegin(combuf, gpu_scope_id, VCombufScopeFlag_None);\n\n\t// Pre-allocate temp barriers buffer\n\tarrayDynamicResizeT(&g_image_upload.barriers, images_count);\n\n\t// 1. Phase I: prepare all images to be transferred into\n\t// 1.a Set up barriers for every valid image\n\tint barriers_count = 0;\n\tfor (int i = 0; i < images_count; ++i) {\n\t\timage_upload_t *const up = g_image_upload.images.items + i;\n\t\tif (!up->image) {\n\t\t\tDEBUG(\"Skipping image upload slot %d\", i);\n\t\t\tcontinue;\n\t\t}\n\n\t\tASSERT(up->image->upload_slot == i);\n\n\t\tg_image_upload.barriers.items[barriers_count++] = (VkImageMemoryBarrier) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,\n\t\t\t.image = up->image->image,\n\t\t\t.srcAccessMask = 0,\n\t\t\t.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,\n\t\t\t.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,\n\t\t\t.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t\t.subresourceRange = (VkImageSubresourceRange) {\n\t\t\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t\t.baseMipLevel = 0,\n\t\t\t\t.levelCount = up->image->mips,\n\t\t\t\t.baseArrayLayer = 0,\n\t\t\t\t.layerCount = up->image->layers,\n\t\t\t},\n\t\t};\n\t}\n\n\t// 1.b Invoke the barriers\n\tvkCmdPipelineBarrier(combuf->cmdbuf,\n\t\tVK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n\t\tVK_PIPELINE_STAGE_TRANSFER_BIT, // TODO VK_PIPELINE_STAGE_2_COPY_BIT\n\t\t0, 0, NULL, 0, NULL,\n\t\tbarriers_count, g_image_upload.barriers.items\n\t);\n\n\t// 2. Phase 2: issue copy commands for each valid image\n\tfor (int i = 0; i < images_count; ++i) {\n\t\timage_upload_t *const up = g_image_upload.images.items + i;\n\t\tif (!up->image)\n\t\t\tcontinue;\n\n\t\tconst int slices_count = up->slices.end - up->slices.begin;\n\t\tDEBUG(\"Uploading image \\\"%s\\\": buffer=%08llx slices=%d\", up->image->name, (unsigned long long)up->staging.lock.buffer, slices_count);\n\n\t\tASSERT(up->staging.lock.buffer != VK_NULL_HANDLE);\n\t\tASSERT(up->slices.end == up->slices.cursor);\n\t\tASSERT(slices_count > 0);\n\n\t\tfor (int j = 0; j < slices_count; ++j) {\n\t\t\tconst VkBufferImageCopy *const slice = g_image_upload.slices.items + up->slices.begin + j;\n\t\t\tDEBUG(\"  slice[%d]: off=%llu rowl=%d height=%d off=(%d,%d,%d) ext=(%d,%d,%d)\",\n\t\t\t\tj, (unsigned long long)slice->bufferOffset, slice->bufferRowLength, slice->bufferImageHeight,\n\t\t\t\tslice->imageOffset.x,\n\t\t\t\tslice->imageOffset.y,\n\t\t\t\tslice->imageOffset.z,\n\t\t\t\tslice->imageExtent.width,\n\t\t\t\tslice->imageExtent.height,\n\t\t\t\tslice->imageExtent.depth\n\t\t\t);\n\t\t}\n\n\t\tvkCmdCopyBufferToImage(combuf->cmdbuf,\n\t\t\tup->staging.lock.buffer,\n\t\t\tup->image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t\tslices_count,\n\t\t\tg_image_upload.slices.items + up->slices.begin);\n\t}\n\n\t// 3. Phase 3: change all images layout to shader read only optimal\n\t// 3.a Set up barriers for layout transition\n\tbarriers_count = 0;\n\tfor (int i = 0; i < images_count; ++i) {\n\t\timage_upload_t *const up = g_image_upload.images.items + i;\n\t\tif (!up->image)\n\t\t\tcontinue;\n\n\t\t// Update image tracking state\n\t\tup->image->sync.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;\n\t\tup->image->sync.read.access = VK_ACCESS_2_SHADER_READ_BIT;\n\t\tup->image->sync.read.stage = dst_stages;\n\t\t/* SUPPOSEDLY: Write state is no longer relevant due to layout transfer below.\n\t\t * At least this fixes validation woes. */\n\t\t/* up->image->sync.write.access = VK_ACCESS_2_TRANSFER_WRITE_BIT; */\n\t\t/* up->image->sync.write.stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT; */\n\t\tup->image->sync.write.access = 0;\n\t\tup->image->sync.write.stage = 0;\n\n\t\tg_image_upload.barriers.items[barriers_count++] = (VkImageMemoryBarrier) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,\n\t\t\t.image = up->image->image,\n\t\t\t.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,\n\t\t\t.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,\n\t\t\t.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,\n\t\t\t.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n\t\t\t.subresourceRange = (VkImageSubresourceRange) {\n\t\t\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t\t.baseMipLevel = 0,\n\t\t\t\t.levelCount = up->image->mips,\n\t\t\t\t.baseArrayLayer = 0,\n\t\t\t\t.layerCount = up->image->layers,\n\t\t\t},\n\t\t};\n\n\t\t// Mark image as uploaded\n\t\tup->image->upload_slot = -1;\n\t\tup->image = NULL;\n\n\t\t// TODO it would be nice to track uploading status further:\n\t\t// 1. When uploading cmdbuf has been submitted to the GPU\n\t\t// 2. When that cmdbuf has been processed.\n\t\t// But that would entail quite a bit more state tracking, etc etc. Discomfort.\n\t}\n\n\t// 3.b Submit the barriers\n\t// It's a massive set of barriers (1e3+), so using manual barriers instead of automatic combuf ones\n\tvkCmdPipelineBarrier(combuf->cmdbuf,\n\t\tVK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages,\n\t\t0, 0, NULL, 0, NULL,\n\t\tbarriers_count, (VkImageMemoryBarrier*)g_image_upload.barriers.items\n\t);\n\n\tR_VkStagingUnlockBulk(g_image_upload.staging, barriers_count);\n\n\tR_VkCombufScopeEnd(combuf, gpu_scope_begin, VK_PIPELINE_STAGE_TRANSFER_BIT);\n\n\t// Clear out image upload queue\n\tarrayDynamicResizeT(&g_image_upload.images, 0);\n\tarrayDynamicResizeT(&g_image_upload.slices, 0);\n\tarrayDynamicResizeT(&g_image_upload.barriers, 0);\n}\n\nvoid R_VkImageUploadBegin( r_vk_image_t *img ) {\n\tASSERT(img->upload_slot == -1);\n\n\t/* TODO compute staging slices sizes properly\n\tconst uint32_t texel_block_size = R_VkImageFormatTexelBlockSize(img->format);\n\tfor (int layer = 0; layer < img->layers; ++layer) {\n\t\tfor (int mip = 0; mip < img->mips; ++mip) {\n\t\t\tconst int width = Q_max( 1, ( img->width >> mip ));\n\t\t\tconst int height = Q_max( 1, ( img->height >> mip ));\n\t\t\tconst int depth = Q_max( 1, ( img->depth >> mip ));\n\t\t\tconst size_t mip_size = CalcImageSize( pic->type, width, height, depth );\n\t\t}\n\t}\n\t*/\n\tconst size_t staging_size = img->image_size;\n\n\t// This is done speculatively to preserve internal image_upload invariant.\n\t// Speculation: we might end up with staging implementation that, upon discovering that it ran out of free memory,\n\t// would notify other modules that they'd need to commit their staging data, and thus we'd return to this module's\n\t// R_VkImageUploadCommit(), which needs to see valid data. Therefore, don't touch its state until\n\t// R_VkStagingLock returns.\n\tconst r_vkstaging_region_t staging_lock = R_VkStagingLock(g_image_upload.staging, staging_size);\n\n\timg->upload_slot = g_image_upload.images.count;\n\tarrayDynamicAppendT(&g_image_upload.images, NULL);\n\timage_upload_t *const up = g_image_upload.images.items + img->upload_slot;\n\n\tup->image = img;\n\tup->staging.lock = staging_lock;\n\tup->staging.cursor = 0;\n\n\tconst int slices = img->layers * img->mips;\n\tup->slices.begin = up->slices.cursor = g_image_upload.slices.count;\n\tup->slices.end = up->slices.begin + slices;\n\n\t//arrayDynamicAppendManyT(&g_image_upload.slices, slices, NULL);\n\tarrayDynamicResizeT(&g_image_upload.slices, g_image_upload.slices.count + slices);\n}\n\nvoid R_VkImageUploadSlice( r_vk_image_t *img, int layer, int mip, int size, const void *data ) {\n\tconst uint32_t width = Q_max(1, img->width >> mip);\n\tconst uint32_t height = Q_max(1, img->height >> mip);\n\tconst uint32_t depth = Q_max(1, img->depth >> mip);\n\t//const uint32_t texel_block_size = R_VkImageFormatTexelBlockSize(img->format);\n\n\tASSERT(img->upload_slot >= 0);\n\tASSERT(img->upload_slot < g_image_upload.images.count);\n\n\timage_upload_t *const up = g_image_upload.images.items + img->upload_slot;\n\tASSERT(up->image == img);\n\n\tASSERT(up->slices.cursor < up->slices.end);\n\tASSERT(up->staging.cursor < img->image_size);\n\tASSERT(img->image_size - up->staging.cursor >= size);\n\n\tmemcpy((char*)up->staging.lock.ptr + up->staging.cursor, data, size);\n\n\tg_image_upload.slices.items[up->slices.cursor] = (VkBufferImageCopy) {\n\t\t.bufferOffset = up->staging.lock.offset + up->staging.cursor,\n\t\t.bufferRowLength = 0,\n\t\t.bufferImageHeight = 0,\n\t\t.imageSubresource = (VkImageSubresourceLayers){\n\t\t\t.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.mipLevel = mip,\n\t\t\t.baseArrayLayer = layer,\n\t\t\t.layerCount = 1,\n\t\t},\n\t\t.imageExtent = (VkExtent3D){\n\t\t\t.width = width,\n\t\t\t.height = height,\n\t\t\t.depth = depth,\n\t\t},\n\t};\n\n\tup->staging.cursor += size;\n\tup->slices.cursor += 1;\n}\n\nvoid R_VkImageUploadEnd( r_vk_image_t *img ) {\n\tASSERT(img->upload_slot >= 0);\n\tASSERT(img->upload_slot < g_image_upload.images.count);\n\n\timage_upload_t *const up = g_image_upload.images.items + img->upload_slot;\n\tASSERT(up->image == img);\n\n\tASSERT(up->slices.cursor == up->slices.end);\n\tASSERT(up->staging.cursor <= img->image_size);\n}\n\nstatic void cancelUpload( r_vk_image_t *img ) {\n\t// Skip already uploaded (or never uploaded) images\n\tif (img->upload_slot < 0)\n\t\treturn;\n\n\tWARN(\"Canceling uploading image \\\"%s\\\"\", img->name);\n\n\timage_upload_t *const up = g_image_upload.images.items + img->upload_slot;\n\tASSERT(up->image == img);\n\n\t// Technically we won't need that staging region anymore at all, but it doesn't matter,\n\t// it's just easier to mark it to be freed this way.\n\tR_VkStagingUnlockBulk(g_image_upload.staging, 1);\n\n\t// Mark upload slot as unused, and image as not subjet to uploading\n\tup->image = NULL;\n\timg->upload_slot = -1;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VImage.h",
    "content": "#pragma once\n#include \"vk_core.h\"\n#include \"VDevmem.h\"\n\nqboolean R_VkImageInit(void);\nvoid R_VkImageShutdown(void);\n\ntypedef struct r_vk_image_s {\n\tchar name[64];\n\n\tvk_devmem_t devmem;\n\tVkImage image;\n\tVkImageView view;\n\n\t// Optional, created by kVkImageFlagCreateUnormView\n\t// Used for sRGB-γ-unaware traditional renderer\n\tVkImageView view_unorm;\n\n\tuint32_t width, height, depth;\n\tint mips, layers;\n\tVkFormat format;\n\tuint32_t flags;\n\tuint32_t image_size;\n\n\tint upload_slot;\n\n\tstruct {\n\t\tVkImageLayout layout;\n\t\tr_vksync_scope_t write, read;\n\t} sync;\n} r_vk_image_t;\n\nenum {\n\tkVkImageFlagIgnoreAlpha = (1<<0),\n\tkVkImageFlagIsCubemap = (1<<1),\n\tkVkImageFlagCreateUnormView = (1<<2),\n};\n\ntypedef struct {\n\tconst char *debug_name;\n\tuint32_t width, height, depth;\n\tint mips, layers;\n\tVkFormat format;\n\tVkImageTiling tiling;\n\tVkImageUsageFlags usage;\n\tVkMemoryPropertyFlags memory_props;\n\tuint32_t flags;\n} r_vk_image_create_t;\n\nr_vk_image_t R_VkImageCreate(const r_vk_image_create_t *create);\nvoid R_VkImageDestroy(r_vk_image_t *img);\n\nstruct vk_combuf_s;\nvoid R_VkImageClear(r_vk_image_t *img, struct vk_combuf_s* combuf, const VkClearColorValue*);\n\ntypedef struct {\n\tstruct {\n\t\tr_vk_image_t *image;\n\t\tint width, height, depth;\n\t} src, dst;\n} r_vkimage_blit_args;\n\nvoid R_VkImageBlit(struct vk_combuf_s *combuf, const r_vkimage_blit_args *blit_args );\n\nuint32_t R_VkImageFormatTexelBlockSize( VkFormat format );\n\n// Expects *img to be pinned and valid until either cancel or commit is called\nvoid R_VkImageUploadBegin( r_vk_image_t *img );\nvoid R_VkImageUploadSlice( r_vk_image_t *img, int layer, int mip, int size, const void *data );\nvoid R_VkImageUploadEnd( r_vk_image_t *img );\n\n// Upload all enqueued images using the given command buffer\nvoid R_VkImageUploadCommit( struct vk_combuf_s *combuf, VkPipelineStageFlagBits dst_stages );\n"
  },
  {
    "path": "ref/vk/vulkan/VImageExtra.h",
    "content": "// Only included from vk_image.c\n\nuint32_t R_VkImageFormatTexelBlockSize( VkFormat format ) {\n\tswitch (format) {\n\t\tcase VK_FORMAT_R4G4_UNORM_PACK8:\n\t\tcase VK_FORMAT_R8_UNORM:\n\t\tcase VK_FORMAT_R8_SNORM:\n\t\tcase VK_FORMAT_R8_USCALED:\n\t\tcase VK_FORMAT_R8_SSCALED:\n\t\tcase VK_FORMAT_R8_UINT:\n\t\tcase VK_FORMAT_R8_SINT:\n\t\tcase VK_FORMAT_R8_SRGB:\n\t\t\treturn 1;\n\t\tcase VK_FORMAT_R10X6_UNORM_PACK16:\n\t\tcase VK_FORMAT_R12X4_UNORM_PACK16:\n\t\tcase VK_FORMAT_A4R4G4B4_UNORM_PACK16:\n\t\tcase VK_FORMAT_A4B4G4R4_UNORM_PACK16:\n\t\tcase VK_FORMAT_R4G4B4A4_UNORM_PACK16:\n\t\tcase VK_FORMAT_B4G4R4A4_UNORM_PACK16:\n\t\tcase VK_FORMAT_R5G6B5_UNORM_PACK16:\n\t\tcase VK_FORMAT_B5G6R5_UNORM_PACK16:\n\t\tcase VK_FORMAT_R5G5B5A1_UNORM_PACK16:\n\t\tcase VK_FORMAT_B5G5R5A1_UNORM_PACK16:\n\t\tcase VK_FORMAT_A1R5G5B5_UNORM_PACK16:\n\t\tcase VK_FORMAT_R8G8_UNORM:\n\t\tcase VK_FORMAT_R8G8_SNORM:\n\t\tcase VK_FORMAT_R8G8_USCALED:\n\t\tcase VK_FORMAT_R8G8_SSCALED:\n\t\tcase VK_FORMAT_R8G8_UINT:\n\t\tcase VK_FORMAT_R8G8_SINT:\n\t\tcase VK_FORMAT_R8G8_SRGB:\n\t\tcase VK_FORMAT_R16_UNORM:\n\t\tcase VK_FORMAT_R16_SNORM:\n\t\tcase VK_FORMAT_R16_USCALED:\n\t\tcase VK_FORMAT_R16_SSCALED:\n\t\tcase VK_FORMAT_R16_UINT:\n\t\tcase VK_FORMAT_R16_SINT:\n\t\tcase VK_FORMAT_R16_SFLOAT:\n\t\t\treturn 2;\n\t\tcase VK_FORMAT_R8G8B8_UNORM:\n\t\tcase VK_FORMAT_R8G8B8_SNORM:\n\t\tcase VK_FORMAT_R8G8B8_USCALED:\n\t\tcase VK_FORMAT_R8G8B8_SSCALED:\n\t\tcase VK_FORMAT_R8G8B8_UINT:\n\t\tcase VK_FORMAT_R8G8B8_SINT:\n\t\tcase VK_FORMAT_R8G8B8_SRGB:\n\t\tcase VK_FORMAT_B8G8R8_UNORM:\n\t\tcase VK_FORMAT_B8G8R8_SNORM:\n\t\tcase VK_FORMAT_B8G8R8_USCALED:\n\t\tcase VK_FORMAT_B8G8R8_SSCALED:\n\t\tcase VK_FORMAT_B8G8R8_UINT:\n\t\tcase VK_FORMAT_B8G8R8_SINT:\n\t\tcase VK_FORMAT_B8G8R8_SRGB:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_R10X6G10X6_UNORM_2PACK16:\n\t\tcase VK_FORMAT_R12X4G12X4_UNORM_2PACK16:\n\t\tcase VK_FORMAT_R16G16_S10_5_NV:\n\t\tcase VK_FORMAT_R8G8B8A8_UNORM:\n\t\tcase VK_FORMAT_R8G8B8A8_SNORM:\n\t\tcase VK_FORMAT_R8G8B8A8_USCALED:\n\t\tcase VK_FORMAT_R8G8B8A8_SSCALED:\n\t\tcase VK_FORMAT_R8G8B8A8_UINT:\n\t\tcase VK_FORMAT_R8G8B8A8_SINT:\n\t\tcase VK_FORMAT_R8G8B8A8_SRGB:\n\t\tcase VK_FORMAT_B8G8R8A8_UNORM:\n\t\tcase VK_FORMAT_B8G8R8A8_SNORM:\n\t\tcase VK_FORMAT_B8G8R8A8_USCALED:\n\t\tcase VK_FORMAT_B8G8R8A8_SSCALED:\n\t\tcase VK_FORMAT_B8G8R8A8_UINT:\n\t\tcase VK_FORMAT_B8G8R8A8_SINT:\n\t\tcase VK_FORMAT_B8G8R8A8_SRGB:\n\t\tcase VK_FORMAT_A8B8G8R8_UNORM_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_SNORM_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_USCALED_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_SSCALED_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_UINT_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_SINT_PACK32:\n\t\tcase VK_FORMAT_A8B8G8R8_SRGB_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_UNORM_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_SNORM_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_USCALED_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_SSCALED_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_UINT_PACK32:\n\t\tcase VK_FORMAT_A2R10G10B10_SINT_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_UNORM_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_SNORM_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_USCALED_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_SSCALED_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_UINT_PACK32:\n\t\tcase VK_FORMAT_A2B10G10R10_SINT_PACK32:\n\t\tcase VK_FORMAT_R16G16_UNORM:\n\t\tcase VK_FORMAT_R16G16_SNORM:\n\t\tcase VK_FORMAT_R16G16_USCALED:\n\t\tcase VK_FORMAT_R16G16_SSCALED:\n\t\tcase VK_FORMAT_R16G16_UINT:\n\t\tcase VK_FORMAT_R16G16_SINT:\n\t\tcase VK_FORMAT_R16G16_SFLOAT:\n\t\tcase VK_FORMAT_R32_UINT:\n\t\tcase VK_FORMAT_R32_SINT:\n\t\tcase VK_FORMAT_R32_SFLOAT:\n\t\tcase VK_FORMAT_B10G11R11_UFLOAT_PACK32:\n\t\tcase VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:\n\t\t\treturn 4;\n\t\tcase VK_FORMAT_R16G16B16_UNORM:\n\t\tcase VK_FORMAT_R16G16B16_SNORM:\n\t\tcase VK_FORMAT_R16G16B16_USCALED:\n\t\tcase VK_FORMAT_R16G16B16_SSCALED:\n\t\tcase VK_FORMAT_R16G16B16_UINT:\n\t\tcase VK_FORMAT_R16G16B16_SINT:\n\t\tcase VK_FORMAT_R16G16B16_SFLOAT:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_R16G16B16A16_UNORM:\n\t\tcase VK_FORMAT_R16G16B16A16_SNORM:\n\t\tcase VK_FORMAT_R16G16B16A16_USCALED:\n\t\tcase VK_FORMAT_R16G16B16A16_SSCALED:\n\t\tcase VK_FORMAT_R16G16B16A16_UINT:\n\t\tcase VK_FORMAT_R16G16B16A16_SINT:\n\t\tcase VK_FORMAT_R16G16B16A16_SFLOAT:\n\t\tcase VK_FORMAT_R32G32_UINT:\n\t\tcase VK_FORMAT_R32G32_SINT:\n\t\tcase VK_FORMAT_R32G32_SFLOAT:\n\t\tcase VK_FORMAT_R64_UINT:\n\t\tcase VK_FORMAT_R64_SINT:\n\t\tcase VK_FORMAT_R64_SFLOAT:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_R32G32B32_UINT:\n\t\tcase VK_FORMAT_R32G32B32_SINT:\n\t\tcase VK_FORMAT_R32G32B32_SFLOAT:\n\t\t\treturn 12;\n\t\tcase VK_FORMAT_R32G32B32A32_UINT:\n\t\tcase VK_FORMAT_R32G32B32A32_SINT:\n\t\tcase VK_FORMAT_R32G32B32A32_SFLOAT:\n\t\tcase VK_FORMAT_R64G64_UINT:\n\t\tcase VK_FORMAT_R64G64_SINT:\n\t\tcase VK_FORMAT_R64G64_SFLOAT:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_R64G64B64_UINT:\n\t\tcase VK_FORMAT_R64G64B64_SINT:\n\t\tcase VK_FORMAT_R64G64B64_SFLOAT:\n\t\t\treturn 24;\n\t\tcase VK_FORMAT_R64G64B64A64_UINT:\n\t\tcase VK_FORMAT_R64G64B64A64_SINT:\n\t\tcase VK_FORMAT_R64G64B64A64_SFLOAT:\n\t\t\treturn 32;\n\n\t\tcase VK_FORMAT_D16_UNORM:\n\t\t\treturn 2;\n\n\t\tcase VK_FORMAT_X8_D24_UNORM_PACK32:\n\t\t\treturn 4;\n\n\t\tcase VK_FORMAT_D32_SFLOAT:\n\t\t\treturn 4;\n\n\t\tcase VK_FORMAT_S8_UINT:\n\t\t\treturn 2;\n\n\t\tcase VK_FORMAT_D16_UNORM_S8_UINT:\n\t\t\treturn 3;\n\n\t\tcase VK_FORMAT_D24_UNORM_S8_UINT:\n\t\t\treturn 4;\n\n\t\tcase VK_FORMAT_D32_SFLOAT_S8_UINT:\n\t\t\treturn 5;\n\n\t\tcase VK_FORMAT_BC1_RGB_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC1_RGB_SRGB_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_BC1_RGBA_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC1_RGBA_SRGB_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_BC2_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC2_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_BC3_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC3_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_BC4_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC4_SNORM_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_BC5_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC5_SNORM_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_BC6H_UFLOAT_BLOCK:\n\t\tcase VK_FORMAT_BC6H_SFLOAT_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_BC7_UNORM_BLOCK:\n\t\tcase VK_FORMAT_BC7_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_EAC_R11_UNORM_BLOCK:\n\t\tcase VK_FORMAT_EAC_R11_SNORM_BLOCK:\n\t\t\treturn 8;\n\n\t\tcase VK_FORMAT_EAC_R11G11_UNORM_BLOCK:\n\t\tcase VK_FORMAT_EAC_R11G11_SNORM_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_4x4_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_4x4_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_5x4_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_5x4_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_5x5_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_5x5_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_6x5_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_6x5_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_6x6_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_6x6_SRGB_BLOCK:\n\t\t\treturn 16;\n\n\t\tcase VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x5_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x5_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x6_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x6_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x8_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_8x8_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x5_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x5_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x6_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x6_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x8_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x8_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x10_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_10x10_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_12x10_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_12x10_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK:\n\t\tcase VK_FORMAT_ASTC_12x12_UNORM_BLOCK:\n\t\tcase VK_FORMAT_ASTC_12x12_SRGB_BLOCK:\n\t\t\treturn 16;\n\t\tcase VK_FORMAT_G8B8G8R8_422_UNORM:\n\t\t\treturn 4;\n\t\tcase VK_FORMAT_B8G8R8G8_422_UNORM:\n\t\t\treturn 4;\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16B16G16R16_422_UNORM:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_B16G16R16G16_422_UNORM:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:\n\t\tcase VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:\n\t\tcase VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:\n\t\tcase VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:\n\t\tcase VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:\n\t\t\treturn 8;\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:\n\t\t\treturn 3;\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:\n\t\t\treturn 6;\n\t\tcase VK_FORMAT_UNDEFINED:\n\t\tcase VK_FORMAT_MAX_ENUM:\n\t\t\treturn 4;\n\t}\n\n\treturn 4;\n}\n\nstatic VkFormat unormFormatFor(VkFormat fmt) {\n\tswitch (fmt) {\n\t\tcase VK_FORMAT_R8_SRGB: return VK_FORMAT_R8_UNORM;\n\t\tcase VK_FORMAT_R8_UNORM: return VK_FORMAT_R8_UNORM;\n\t\tcase VK_FORMAT_R8G8_SRGB: return VK_FORMAT_R8G8_UNORM;\n\t\tcase VK_FORMAT_R8G8_UNORM: return VK_FORMAT_R8G8_UNORM;\n\t\tcase VK_FORMAT_R8G8B8_SRGB: return VK_FORMAT_R8G8B8_UNORM;\n\t\tcase VK_FORMAT_R8G8B8_UNORM: return VK_FORMAT_R8G8B8_UNORM;\n\t\tcase VK_FORMAT_B8G8R8_SRGB: return VK_FORMAT_B8G8R8_UNORM;\n\t\tcase VK_FORMAT_B8G8R8_UNORM: return VK_FORMAT_B8G8R8_UNORM;\n\t\tcase VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM;\n\t\tcase VK_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM;\n\t\tcase VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM;\n\t\tcase VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_UNORM;\n\t\tcase VK_FORMAT_A8B8G8R8_SRGB_PACK32: return VK_FORMAT_A8B8G8R8_UNORM_PACK32;\n\t\tcase VK_FORMAT_A8B8G8R8_UNORM_PACK32: return VK_FORMAT_A8B8G8R8_UNORM_PACK32;\n\t\tcase VK_FORMAT_BC1_RGB_SRGB_BLOCK: return VK_FORMAT_BC1_RGB_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC1_RGB_UNORM_BLOCK: return VK_FORMAT_BC1_RGB_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC1_RGBA_UNORM_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC2_SRGB_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC2_UNORM_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC3_SRGB_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC3_UNORM_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC7_SRGB_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK;\n\t\tcase VK_FORMAT_BC7_UNORM_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_4x4_UNORM_BLOCK: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_5x4_UNORM_BLOCK: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_5x5_UNORM_BLOCK: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_6x5_UNORM_BLOCK: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_6x6_UNORM_BLOCK: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x5_UNORM_BLOCK: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x6_UNORM_BLOCK: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_8x8_UNORM_BLOCK: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x5_UNORM_BLOCK: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x6_UNORM_BLOCK: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x8_UNORM_BLOCK: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_10x10_UNORM_BLOCK: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_12x10_UNORM_BLOCK: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;\n\t\tcase VK_FORMAT_ASTC_12x12_UNORM_BLOCK: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;\n\t\tcase VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;\n\t\tcase VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;\n\t\tdefault:\n\t\t\treturn VK_FORMAT_UNDEFINED;\n\t}\n}\n\nstatic VkComponentMapping componentMappingForFormat( VkFormat format, qboolean ignore_alpha ) {\n\tVkComponentMapping map = {\n\t\tVK_COMPONENT_SWIZZLE_IDENTITY,\n\t\tVK_COMPONENT_SWIZZLE_IDENTITY,\n\t\tVK_COMPONENT_SWIZZLE_IDENTITY,\n\t\tVK_COMPONENT_SWIZZLE_IDENTITY,\n\t};\n\n\tswitch ( format ) {\n\t\tcase VK_FORMAT_BC4_UNORM_BLOCK:\n\t\t\tmap.g = map.b = map.a = VK_COMPONENT_SWIZZLE_R;\n\t\t\tbreak;\n\t\tcase VK_FORMAT_BC4_SNORM_BLOCK:\n\t\t\tmap.g = map.b = map.a = VK_COMPONENT_SWIZZLE_R;\n\t\t\tbreak;\n\t\tcase VK_FORMAT_BC5_UNORM_BLOCK:\n\t\t\tmap.b = VK_COMPONENT_SWIZZLE_R;\n\t\t\tmap.a = VK_COMPONENT_SWIZZLE_G;\n\t\t\tbreak;\n\t\tcase VK_FORMAT_BC5_SNORM_BLOCK:\n\t\t\tmap.b = VK_COMPONENT_SWIZZLE_R;\n\t\t\tmap.a = VK_COMPONENT_SWIZZLE_G;\n\t\t\tbreak;\n\t}\n\n\tif (ignore_alpha)\n\t\tmap.a = VK_COMPONENT_SWIZZLE_ONE;\n\n\treturn map;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VMeatpipe.c",
    "content": "#include \"VMeatpipe.h\"\n\n#include \"VPipeline.h\"\n#include \"VResource.h\"\n#include \"VBarrier.h\"\n#include \"VPass.h\"\n#include \"vk_common.h\"\n#include \"vk_logs.h\"\n#include \"std/profiler.h\"\n\n#define LOG_MODULE meat\n\n#define MIN(a,b) ((a)<(b)?(a):(b))\n\n#define CHAR4UINT(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a))\nstatic const uint32_t k_meatpipe_magic = CHAR4UINT('M', 'E', 'A', 'T');\n\nenum {\n\tMEATPIPE_RES_WRITE = (1<<0),\n\tMEATPIPE_RES_CREATE = (1<<1),\n\t// TMP ..\n};\n\ntypedef struct {\n\tchar name[64];\n\tuint32_t descriptor_type;\n\tint count;\n\tuint32_t flags;\n\tunion {\n\t\tuint32_t image_format;\n\t};\n\n\t// Index+1 of resource image to read data from if this resource is a \"previous frame\" contents of another one.\n\t// Value of zero means that it is a standalone resource. The real index is the value - 1.\n\tint prev_frame_index_plus_1;\n} vk_meatpipe_resource_t;\n\nstruct vk_meatpipe_pass_s;\ntypedef struct vk_meatpipe_s {\n\tint passes_count;\n\tstruct vk_meatpipe_pass_s *passes;\n\n\tint resources_count;\n\tvk_meatpipe_resource_t *resources;\n\n\t// TODO move these into passes as ready-to-go rt_resource_p[]\n\t// Helper list of resource pointers to global resource map\n\t// Needed as an argument to `R_VkMeatpipePerform()` so that meatpipe can access resources\n\trt_resource_t* *acquired_resources;\n\n\tr_vk_image_t *image_dest;\n} vk_meatpipe_t;\n\nstruct ray_pass_s;\ntypedef struct vk_meatpipe_pass_s {\n\tstruct ray_pass_s* pass;\n\tint write_from;\n\tint resource_count;\n\tint *resource_map;\n} vk_meatpipe_pass_t;\n\ntypedef struct cursor_t {\n\tconst byte *data;\n\tint off, size;\n\tqboolean error;\n} cursor_t;\n\ntypedef struct load_context_t {\n\tcursor_t cur;\n\n\tint shaders_count;\n\tVkShaderModule *shaders;\n\n\tvk_meatpipe_t meatpipe;\n} load_context_t;\n\nstatic const void* curReadPtr(cursor_t *cur, int size) {\n\tconst int left = cur->size - cur->off;\n\tif (left < size) {\n\t\tcur->error = true;\n\t\treturn NULL;\n\t}\n\n\tconst void* const ret = cur->data + cur->off;\n\tcur->off += size;\n\treturn ret;\n}\n\n#define CUR_ERROR(errmsg, ...) \\\n\tif (ctx->cur.error) { \\\n\t\tERR(\"(off=%d left=%d) \" errmsg \"\", ctx->cur.off, (ctx->cur.size - ctx->cur.off), ##__VA_ARGS__); \\\n\t\tgoto finalize; \\\n\t}\n\n#define CUR_ERROR_RETURN(retval, errmsg, ...) \\\n\tif (ctx->cur.error) { \\\n\t\tERR(\"(off=%d left=%d) \" errmsg \"\", ctx->cur.off, (ctx->cur.size - ctx->cur.off), ##__VA_ARGS__); \\\n\t\treturn retval; \\\n\t}\n\n#define READ_PTR(size, errmsg, ...) \\\n\tcurReadPtr(&ctx->cur, size); CUR_ERROR(errmsg, ##__VA_ARGS__)\n\nstatic uint32_t curReadU32(cursor_t *cur) {\n\tconst void *src = curReadPtr(cur, sizeof(uint32_t));\n\tif (!src)\n\t\treturn 0;\n\n\tuint32_t ret;\n\tmemcpy(&ret, src, sizeof(uint32_t));\n\treturn ret;\n}\n\n#define READ_U32(errmsg, ...) \\\n\tcurReadU32(&ctx->cur); CUR_ERROR(errmsg, ##__VA_ARGS__)\n\n#define READ_U32_RETURN(retval, errmsg, ...) \\\n\tcurReadU32(&ctx->cur); CUR_ERROR_RETURN(retval, errmsg, ##__VA_ARGS__)\n\nstatic int curReadStr(cursor_t *cur, char* out, int out_size) {\n\tconst int len = curReadU32(cur);\n\tif (cur->error)\n\t\treturn -1;\n\n\tconst char *src = curReadPtr(cur, len);\n\tif (cur->error)\n\t\treturn -1;\n\n\tconst int max = MIN(out_size, len); \\\n\tmemcpy(out, src, max); \\\n\tout[max] = '\\0';\n\treturn len;\n}\n\n#define READ_STR(out, errmsg, ...) \\\n\tcurReadStr(&ctx->cur, out, sizeof(out)); CUR_ERROR(errmsg, ##__VA_ARGS__)\n\n#define READ_STR_RETURN(retval, out, errmsg, ...) \\\n\tcurReadStr(&ctx->cur, out, sizeof(out)); CUR_ERROR_RETURN(retval, errmsg, ##__VA_ARGS__)\n\n#define NO_SHADER 0xffffffff\n\nstatic struct ray_pass_s *pipelineLoadCompute(load_context_t *ctx, int i, const char *name, const ray_pass_layout_t *layout) {\n\tconst uint32_t shader_comp = READ_U32_RETURN(NULL, \"Couldn't read comp shader for %d %s\", i, name);\n\n\tif (shader_comp >= ctx->shaders_count) {\n\t\tERR(\"Pipeline %s shader index out of bounds %d (count %d)\", name, shader_comp, ctx->shaders_count);\n\t\treturn NULL;\n\t}\n\n\tconst ray_pass_create_compute_t rpcc = {\n\t\t.debug_name = name,\n\t\t.layout = *layout,\n\t\t.shader_module = ctx->shaders[shader_comp],\n\t};\n\n\treturn RayPassCreateCompute(&rpcc);\n}\n\nstatic struct ray_pass_s *pipelineLoadRT(load_context_t *ctx, int i, const char *name, const ray_pass_layout_t *layout) {\n\tray_pass_p ret = NULL;\n\tray_pass_create_tracing_t rpct = {\n\t\t.debug_name = name,\n\t\t.layout = *layout,\n\t};\n\n\t// FIXME bounds check shader indices\n\n\tconst uint32_t shader_rgen = READ_U32(\"Couldn't read rgen shader for %d %s\", i, name);\n\trpct.raygen_module = ctx->shaders[shader_rgen];\n\n\trpct.miss_count = READ_U32(\"Couldn't read miss count for %d %s\", i, name);\n\tif (rpct.miss_count) {\n\t\trpct.miss_module = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * rpct.miss_count);\n\t\tfor (int j = 0; j < rpct.miss_count; ++j) {\n\t\t\tconst uint32_t shader_miss = READ_U32(\"Couldn't read miss shader %d for %d %s\", j, i, name);\n\t\t\trpct.miss_module[j] = ctx->shaders[shader_miss];\n\t\t}\n\t}\n\n\trpct.hit_count = READ_U32(\"Couldn't read hit count for %d %s\", i, name);\n\tif (rpct.hit_count) {\n\t\tray_pass_hit_group_t *hit = Mem_Malloc(vk_core.pool, sizeof(rpct.hit[0]) * rpct.hit_count);\n\t\trpct.hit = hit;\n\t\tfor (int j = 0; j < rpct.hit_count; ++j) {\n\t\t\tconst uint32_t closest = READ_U32(\"Couldn't read closest shader %d for %d %s\", j, i, name);\n\t\t\tconst uint32_t any = READ_U32(\"Couldn't read any shader %d for %d %s\", j, i, name);\n\n\t\t\thit[j] = (ray_pass_hit_group_t){\n\t\t\t\t.closest_module = (closest == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[closest],\n\t\t\t\t.any_module = (any == NO_SHADER) ? VK_NULL_HANDLE : ctx->shaders[any],\n\t\t\t};\n\t\t}\n\t}\n\n\tret = RayPassCreateTracing(&rpct);\n\nfinalize:\n\tif (rpct.hit)\n\t\tMem_Free((void*)rpct.hit);\n\n\tif (rpct.miss_module)\n\t\tMem_Free(rpct.miss_module);\n\n\treturn ret;\n}\n\n#define MAX_BINDINGS 64\nstatic qboolean readBindings(load_context_t *ctx, VkDescriptorSetLayoutBinding *bindings, vk_meatpipe_pass_t* pass ) {\n\tpass->resource_map = NULL;\n\tint write_from = -1;\n\tconst int count = READ_U32(\"Coulnd't read bindings count\");\n\n\tif (count > MAX_BINDINGS) {\n\t\tERR(\"Too many binding (%d), max: %d\", count, MAX_BINDINGS);\n\t\tgoto finalize;\n\t}\n\n\tpass->resource_map = count ? Mem_Malloc(vk_core.pool, sizeof(pass->resource_map[0]) * count) : NULL;\n\n\tfor (int i = 0; i < count; ++i) {\n\t\tconst uint32_t header = READ_U32(\"Couldn't read header for binding %d\", i);\n\t\tconst uint32_t res_index = READ_U32(\"Couldn't read res index for binding %d\", i);\n\t\tconst uint32_t stages = READ_U32(\"Couldn't read stages for binding %d\", i);\n\n\t\tif (res_index >= ctx->meatpipe.resources_count) {\n\t\t\tERR(\"Resource %d is out of bound %d for binding %d\", res_index, ctx->meatpipe.resources_count, i);\n\t\t\tgoto finalize;\n\t\t}\n\n\t\tvk_meatpipe_resource_t *res = ctx->meatpipe.resources + res_index;\n\n#define BINDING_WRITE_BIT 0x80000000u\n#define BINDING_CREATE_BIT 0x40000000u\n\t\tconst qboolean write = !!(header & BINDING_WRITE_BIT);\n\t\tconst qboolean create = !!(header & BINDING_CREATE_BIT);\n\t\tconst uint32_t descriptor_set = (header >> 8) & 0xffu;\n\t\tconst uint32_t binding = header & 0xffu;\n\n\t\tif (write && write_from < 0)\n\t\t\twrite_from = i;\n\n\t\tif (!write && write_from >= 0) {\n\t\t\tERR(\"Unsorted non-write binding found at %d(%s), writable started at %d\",\n\t\t\t\ti, res->name, write_from);\n\t\t\tgoto finalize;\n\t\t}\n\n\t\tconst char *name = res->name;\n\n\t\tbindings[i] = (VkDescriptorSetLayoutBinding){\n\t\t\t.binding = binding,\n\t\t\t.descriptorType = res->descriptor_type,\n\t\t\t.descriptorCount = res->count,\n\t\t\t.stageFlags = stages,\n\t\t\t.pImmutableSamplers = NULL,\n\t\t};\n\n\t\tpass->resource_map[i] = res_index;\n\n\t\tif (write)\n\t\t\tres->flags |= MEATPIPE_RES_WRITE;\n\n\t\tif (create)\n\t\t\tres->flags |= MEATPIPE_RES_CREATE;\n\n\t\tDEBUG(\"Binding %d: %s ds=%d b=%d s=%08x res=%d type=%d write=%d\",\n\t\t\ti, name, descriptor_set, binding, stages, res_index, res->descriptor_type, write);\n\t}\n\n\tpass->write_from = write_from;\n\tpass->resource_count = count;\n\treturn true;\n\nfinalize:\n\tif (pass->resource_map)\n\t\tMem_Free(pass->resource_map);\n\n\tpass->resource_map = NULL;\n\treturn false;\n}\n\nstatic qboolean readAndCreatePass(load_context_t *ctx, int i) {\n\tVkDescriptorSetLayoutBinding bindings[MAX_BINDINGS];\n\tray_pass_layout_t layout = {\n\t\t.bindings = bindings,\n\t\t.push_constants = {0},\n\t};\n\n\tvk_meatpipe_pass_t *pass = ctx->meatpipe.passes + i;\n\tmemset(pass, 0, sizeof(*pass));\n\n\tconst uint32_t type = READ_U32(\"Couldn't read pipeline %d type\", i);\n\n\tchar name[64];\n\tREAD_STR(name, \"Couldn't read pipeline %d name\", i);\n\n\tDEBUG(\"%d: loading pipeline %s\", i, name);\n\n\tif (!readBindings(ctx, bindings, pass)) {\n\t\tERR(\"Couldn't read bindings for pipeline %s\", name);\n\t\treturn false;\n\t}\n\n\tlayout.bindings_count = pass->resource_count;\n\tlayout.write_from = pass->write_from;\n\n#define PIPELINE_COMPUTE 1\n#define PIPELINE_RAYTRACING 2\n\n\tswitch (type) {\n\t\tcase PIPELINE_COMPUTE:\n\t\t\tpass->pass = pipelineLoadCompute(ctx, i, name, &layout);\n\t\t\tbreak;\n\t\tcase PIPELINE_RAYTRACING:\n\t\t\tpass->pass = pipelineLoadRT(ctx, i, name, &layout);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tERR(\"Unexpected pipeline type %d\", type);\n\t}\n\n\tif (pass->pass)\n\t\treturn true;\n\nfinalize:\n\tif (pass->resource_map)\n\t\tMem_Free(pass->resource_map);\n\treturn false;\n}\n\nstatic qboolean readResources(load_context_t *ctx) {\n\tctx->meatpipe.resources_count = READ_U32(\"Couldn't read resources count\");\n\tctx->meatpipe.resources = Mem_Malloc(vk_core.pool, sizeof(ctx->meatpipe.resources[0]) * ctx->meatpipe.resources_count);\n\n\tfor (int i = 0; i < ctx->meatpipe.resources_count; ++i) {\n\t\tvk_meatpipe_resource_t *res = ctx->meatpipe.resources + i;\n\t\t*res = (vk_meatpipe_resource_t){0};\n\n\t\tREAD_STR(res->name, \"Couldn't read resource %d name\", i);\n\n\t\tres->descriptor_type = READ_U32(\"Couldn't read resource %d:%s type\", i, res->name);\n\t\tres->count = READ_U32(\"Couldn't read resource %d:%s count\", i, res->name);\n\n\t\tconst qboolean is_image = res->descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || res->descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;\n\n\t\tif (is_image) {\n\t\t\tres->image_format = READ_U32(\"Couldn't read image format for res %d:%s\", i, res->name);\n\t\t\tres->prev_frame_index_plus_1 = READ_U32(\"Couldn't read resource %d:%s previous frame index\", i, res->name);\n\t\t}\n\n\t\tDEBUG(\"Resource %d:%s = %08x is_image=%d image_format=%08x count=%d\",\n\t\t\ti, res->name, res->descriptor_type, is_image, res->image_format, res->count);\n\t}\n\n\treturn true;\nfinalize:\n\treturn false;\n}\n\nstatic qboolean readAndLoadShaders(load_context_t *ctx) {\n\tctx->shaders_count = READ_U32(\"Couldn't read shaders count\");\n\tctx->shaders = Mem_Malloc(vk_core.pool, sizeof(VkShaderModule) * ctx->shaders_count);\n\tfor (int i = 0; i < ctx->shaders_count; ++i) {\n\t\tctx->shaders[i] = VK_NULL_HANDLE;\n\n\t\tchar name[64];\n\t\tREAD_STR(name, \"Couldn't read shader %d name\", i);\n\n\t\tconst int size = READ_U32(\"Couldn't read shader %s size\", name);\n\t\tconst void *src = READ_PTR(size, \"Couldn't read shader %s data\", name);\n\n\t\tif (VK_NULL_HANDLE == (ctx->shaders[i] = R_VkShaderLoadFromMem(src, size, name))) {\n\t\t\tERR(\"Failed to load shader %d:%s\", i, name);\n\t\t\tgoto finalize;\n\t\t}\n\n\t\tDEBUG(\"%d: Shader loaded %s\", i, name);\n\t}\n\n\treturn true;\nfinalize:\n\treturn false;\n}\n\nvk_meatpipe_t* R_VkMeatpipeCreateFromFile(const char *filename) {\n\tvk_meatpipe_t *ret = NULL;\n\tfs_offset_t size = 0;\n\tbyte* const buf = gEngine.fsapi->LoadFile(filename, &size, false);\n\n\tif (!buf) {\n\t\tERR(\"Couldn't read \\\"%s\\\"\", filename);\n\t\treturn NULL;\n\t}\n\n\tload_context_t context = {\n\t\t.cur = { .data = buf, .off = 0, .size = size },\n\t\t.shaders_count = 0,\n\t\t.shaders = NULL,\n\t\t.meatpipe = (vk_meatpipe_t){0},\n\t};\n\tload_context_t *ctx = &context;\n\n\t{\n\t\tconst uint32_t magic = READ_U32(\"Couldn't read magic\");\n\n\t\tif (magic != k_meatpipe_magic) {\n\t\t\tERR(\"Meatpipe magic invalid for \\\"%s\\\": got %08x expected %08x\", filename, magic, k_meatpipe_magic);\n\t\t\tgoto finalize;\n\t\t}\n\t}\n\n\tif (!readResources(ctx))\n\t\tgoto finalize;\n\n\tif (!readAndLoadShaders(ctx))\n\t\tgoto finalize;\n\n\tctx->meatpipe.passes_count = READ_U32(\"Couldn't read pipelines count\");\n\tctx->meatpipe.passes = Mem_Malloc(vk_core.pool, sizeof(ctx->meatpipe.passes[0]) * ctx->meatpipe.passes_count);\n\tfor (int i = 0; i < ctx->meatpipe.passes_count; ++i) {\n\t\tif (!readAndCreatePass(ctx, i)) {\n\t\t\tfor (int j = 0; j < i; ++j) {\n\t\t\t\tRayPassDestroy(ctx->meatpipe.passes[j].pass);\n\t\t\t\tMem_Free(ctx->meatpipe.passes[j].resource_map);\n\t\t\t}\n\t\t\tgoto finalize;\n\t\t}\n\t}\n\n\t// Loading successful, allocate and fill\n\tret = Mem_Malloc(vk_core.pool, sizeof(*ret));\n\tmemcpy(ret, &ctx->meatpipe, sizeof(*ret));\n\tctx->meatpipe.resources = NULL;\n\n\tINFO(\"Loaded meatpipe %s with %d passes and %d resources\", filename, ret->passes_count, ret->resources_count);\n\nfinalize:\n\tfor (int i = 0; i < ctx->shaders_count; ++i) {\n\t\tif (ctx->shaders[i] == VK_NULL_HANDLE)\n\t\t\tbreak;\n\n\t\tR_VkShaderDestroy(ctx->shaders[i]);\n\t}\n\n\tif (ctx->shaders)\n\t\tMem_Free(ctx->shaders);\n\n\tif (ctx->meatpipe.resources)\n\t\tMem_Free(ctx->meatpipe.resources);\n\n\tMem_Free(buf);\n\treturn ret;\n}\n\nvoid R_VkMeatpipeDestroy(vk_meatpipe_t *mp) {\n\tif (!mp)\n\t\treturn;\n\n\tif (mp->acquired_resources) {\n\t\tfor (int i = 0; i < mp->resources_count; ++i) {\n\t\t\tconst vk_meatpipe_resource_t *mr = mp->resources + i;\n\t\t\trt_resource_t *const res = R_VkResourceFindByName(mr->name);\n\t\t\tASSERT(res);\n\t\t\tASSERT(res->refcount > 0);\n\t\t\tres->refcount--;\n\t\t}\n\n\t\tR_VkResourcesCleanup();\n\n\t\tMem_Free(mp->acquired_resources);\n\t}\n\n\tfor (int i = 0; i < mp->passes_count; ++i) {\n\t\tvk_meatpipe_pass_t *pass = mp->passes + i;\n\t\tRayPassDestroy(pass->pass);\n\t\tMem_Free(pass->resource_map);\n\t}\n\n\tMem_Free(mp->passes);\n\tMem_Free(mp->resources);\n\tMem_Free(mp);\n}\n\n\ntypedef struct {\n\trt_resource_t header;\n\tr_vk_image_t image;\n} vk_resource_storage_image_t;\n\nstatic vk_descriptor_value_t acquireStorageImageDescriptor(struct rt_resource_s* r, vk_resource_acquire_descriptor_args_t args) {\n\tvk_resource_storage_image_t *const res = (void*)r;\n\n\tbarrierAddImage(args.barriers, (r_vkcombuf_barrier_image_t) {\n\t\t.image = &res->image,\n\t\t.layout = args.image_layout,\n\t\t.access = args.access,\n\t});\n\n\t// TODO how do we make sure that the same image isn't used more than once with different layouts in the same barrier set?\n\n\treturn (vk_descriptor_value_t){\n\t\t.image = (VkDescriptorImageInfo) {\n\t\t\t.sampler = VK_NULL_HANDLE,\n\t\t\t.imageView = res->image.view,\n\t\t\t.imageLayout = args.image_layout,\n\t\t},\n\t};\n}\n\nstatic void destroyStorageImage(rt_resource_t *r) {\n\tvk_resource_storage_image_t *const res = (void*)r;\n\tif (res->image.image != VK_NULL_HANDLE)\n\t\tR_VkImageDestroy(&res->image);\n\tMem_Free(res);\n}\n\nstatic rt_resource_t *createStorageImageResource(const vk_meatpipe_resource_t *const mr, Producer *producer,\n\t\tint max_width, int max_height) {\n\tif (mr->descriptor_type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) {\n\t\tERR(\"Only storage image creation is supported for meatpipes\");\n\t\treturn NULL;\n\t}\n\n\tvk_resource_storage_image_t *res = NULL;\n\trt_resource_t *found = R_VkResourceFindByName(mr->name);\n\tif (found) {\n\t\tif (found->type != mr->descriptor_type) {\n\t\t\tERR(\"Expected resource[%s] type %s(%d) doesn't match registered %s(%d)\",\n\t\t\t\tmr->name,\n\t\t\t\tR_VkDescriptorTypeName(mr->descriptor_type), mr->descriptor_type,\n\t\t\t\tR_VkDescriptorTypeName(found->type), found->type);\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// TODO how to check that it's really vk_resource_storage_image_t?\n\t\tres = (void*)found;\n\t} else {\n\t\tres = Mem_Calloc(vk_core.pool, sizeof *res);\n\t\tQ_strncpy(res->header.name, mr->name, sizeof(res->header.name));\n\t\tres->header.type = mr->descriptor_type;\n\t\tres->header.destroy = destroyStorageImage;\n\t\tres->header.acquire_descriptor = acquireStorageImageDescriptor;\n\t\tres->header.producer = producer;\n\t\tASSERT(R_VkResourceRegister(&res->header));\n\t}\n\n\tconst qboolean is_compatible = (res->image.image != VK_NULL_HANDLE)\n\t\t&& (mr->image_format == res->image.format)\n\t\t&& (max_width <= res->image.width)\n\t\t&& (max_height <= res->image.height);\n\n\tif (!is_compatible) {\n\t\tif (res->image.image != VK_NULL_HANDLE)\n\t\t\tR_VkImageDestroy(&res->image);\n\n\t\tconst r_vk_image_create_t create = {\n\t\t\t.debug_name = mr->name,\n\t\t\t.width = max_width,\n\t\t\t.height = max_height,\n\t\t\t.depth = 1,\n\t\t\t.mips = 1,\n\t\t\t.layers = 1,\n\t\t\t.format = mr->image_format,\n\t\t\t.tiling = VK_IMAGE_TILING_OPTIMAL,\n\t\t\t// TODO figure out how to detect this need properly. prev_dest is not defined as \"output\"\n\t\t\t//.usage = VK_IMAGE_USAGE_STORAGE_BIT | (output ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0),\n\t\t\t.usage = VK_IMAGE_USAGE_STORAGE_BIT\n\t\t\t\t//| VK_IMAGE_USAGE_SAMPLED_BIT // required by VK_IMAGE_LAYOUT_SHADER_READ_OPTIMAL\n\t\t\t\t| VK_IMAGE_USAGE_TRANSFER_SRC_BIT\n\t\t\t\t| VK_IMAGE_USAGE_TRANSFER_DST_BIT,\n\t\t\t.flags = 0,\n\t\t};\n\t\tres->image = R_VkImageCreate(&create);\n\t}\n\n\treturn &res->header;\n}\n\nstatic void producePass(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\t(void)p;\n\t(void)combuf;\n\t(void)ctx;\n}\n\n// FIXME make the real producer when https://github.com/w23/xash3d-fwgs/issues/774 is solved or worked around\nstatic Producer fake_producer = {\n\t.name = \"fake_meatpipe_producer\",\n\t.produce = producePass,\n};\n\nint R_VkMeatpipeAcquireResources(struct vk_meatpipe_s *meatpipe, int max_width, int max_height) {\n\tconst size_t newpipe_resources_size = sizeof(rt_resource_t*) * meatpipe->resources_count;\n\trt_resource_t* *acquired_resources = Mem_Calloc(vk_core.pool, newpipe_resources_size);\n\tr_vk_image_t *newpipe_out = NULL;\n\n\t// TODO acquire/release resources properly\n\t// TODO recreate images later -- only when we're sure that older resources won't be used anymore\n\t// - etc\n\n\tfor (int i = 0; i < meatpipe->resources_count; ++i) {\n\t\tconst vk_meatpipe_resource_t *mr = meatpipe->resources + i;\n\t\tDEBUG(\"res %d/%d: %s descriptor=%u count=%d flags=[%c%c] image_format=(%s)%u\",\n\t\t\ti, meatpipe->resources_count, mr->name, mr->descriptor_type, mr->count,\n\t\t\t(mr->flags & MEATPIPE_RES_WRITE) ? 'W' : ' ',\n\t\t\t(mr->flags & MEATPIPE_RES_CREATE) ? 'C' : ' ',\n\t\t\tR_VkFormatName(mr->image_format),\n\t\t\tmr->image_format);\n\n\t\t// TODO this should be specified as a flag, from rt.json\n\t\tconst qboolean output = Q_strcmp(\"dest\", mr->name) == 0;\n\n\t\tconst qboolean create = !!(mr->flags & MEATPIPE_RES_CREATE);\n\n\t\trt_resource_t *const res = create\n\t\t\t? createStorageImageResource(mr, &fake_producer, max_width, max_height)\n\t\t\t: R_VkResourceFindByName(mr->name);\n\n\t\tif (!res) {\n\t\t\tERR(\"Couldn't acquire resource with name \\\"%s\\\"\", mr->name);\n\t\t\tgoto fail;\n\t\t}\n\n\t\tif (res->type != mr->descriptor_type) {\n\t\t\tERR(\"Expected resource[%s] type %s(%d) doesn't match registered %s(%d)\",\n\t\t\t\tres->name,\n\t\t\t\tR_VkDescriptorTypeName(mr->descriptor_type), mr->descriptor_type,\n\t\t\t\tR_VkDescriptorTypeName(res->type), res->type);\n\t\t\tgoto fail;\n\t\t}\n\n\t\tif (output)\n\t\t\tnewpipe_out = &((vk_resource_storage_image_t*)res)->image;\n\n\t\tacquired_resources[i] = res;\n\t}\n\n\tif (!newpipe_out) {\n\t\tERR(\"New rt.json doesn't define an 'dest' output texture\");\n\t\tgoto fail;\n\t}\n\n\t// Validate prev frame ping pong links\n\tfor (int i = 0; i < meatpipe->resources_count; ++i) {\n\t\tconst vk_meatpipe_resource_t *mr = meatpipe->resources + i;\n\t\tif (mr->prev_frame_index_plus_1 <= 0)\n\t\t\tcontinue;\n\n\t\tif (mr->descriptor_type != VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) {\n\t\t\tERR(\"Resource[%s] prev_frame_index_plus_1=%d: unsupported descriptor_type=%s(%d), only storage image is supported\",\n\t\t\t\tmr->name,\n\t\t\t\tmr->prev_frame_index_plus_1,\n\t\t\t\tR_VkDescriptorTypeName(mr->descriptor_type),\n\t\t\t\tmr->descriptor_type);\n\t\t\tgoto fail;\n\t\t}\n\n\t\t// TODO check, don't crash\n\t\tASSERT(mr->prev_frame_index_plus_1 < meatpipe->resources_count);\n\n\t\trt_resource_t *const res = R_VkResourceFindByName(mr->name);\n\t\tASSERT(res);\n\t\tASSERT(res->type == mr->descriptor_type);\n\n\t\tconst vk_meatpipe_resource_t *pr = meatpipe->resources + (mr->prev_frame_index_plus_1 - 1);\n\t\tif (mr->descriptor_type != pr->descriptor_type) {\n\t\t\tERR(\"Current and previous resource typed don't match\");\n\t\t\tgoto fail;\n\t\t}\n\n\t\trt_resource_t *const prev = R_VkResourceFindByName(pr->name);\n\t\tASSERT(prev);\n\t\tASSERT(prev->type == pr->descriptor_type);\n\n\t\t// TODO check for image compatibility\n\t}\n\n\t// Loading successful\n\t// Update refcounts if no resources were previously acquired\n\tif (!meatpipe->acquired_resources) {\n\t\tfor (int i = 0; i < meatpipe->resources_count; ++i) {\n\t\t\tconst vk_meatpipe_resource_t *mr = meatpipe->resources + i;\n\t\t\trt_resource_t *const res = R_VkResourceFindByName(mr->name);\n\t\t\tASSERT(res);\n\t\t\tres->refcount++;\n\t\t}\n\n\t\tmeatpipe->acquired_resources = acquired_resources;\n\t} else {\n\t\t// FIXME release?\n\t\t// FIXME reuse prev allocation\n\t\tMem_Free(meatpipe->acquired_resources);\n\t\tmeatpipe->acquired_resources = acquired_resources;\n\t}\n\n\tmeatpipe->image_dest = newpipe_out;\n\treturn 1;\n\nfail:\n\tR_VkResourcesCleanup();\n\n\tif (acquired_resources)\n\t\tMem_Free(acquired_resources);\n\n\treturn 0;\n}\n\nstatic void swapPingPongImages(vk_meatpipe_t *meatpipe, vk_combuf_t* combuf, qboolean discontinuity) {\n\t// TODO special rt_resource_t ping-pong subclass\n\t// Transfer previous frames before they had a chance of their resource-barrier metadata overwritten (as there's no guaranteed order for them)\n\t// Assumes resources were validated already\n\tfor (int i = 0; i < meatpipe->resources_count; ++i) {\n\t\tconst vk_meatpipe_resource_t *mr = meatpipe->resources + i;\n\t\tif (mr->prev_frame_index_plus_1 <= 0)\n\t\t\tcontinue;\n\n\t\tvk_resource_storage_image_t *const write = (void*)meatpipe->acquired_resources[i];\n\t\tvk_resource_storage_image_t *const read = (void*)meatpipe->acquired_resources[mr->prev_frame_index_plus_1 - 1];\n\n\t\t// Swap images\n\t\tconst r_vk_image_t tmp_img = write->image;\n\t\twrite->image = read->image;\n\t\tread->image = tmp_img;\n\n\t\t// If there was no initial state, prepare it. (this should happen only for the first frame)\n\t\tif (discontinuity || read->image.sync.write.stage == 0) {\n\t\t\t// TODO is there a better way? Can image be cleared w/o explicit clear op?\n\t\t\tWARN(\"discontinuity: %s\", read->header.name);\n\t\t\tR_VkImageClear( &read->image, combuf, NULL );\n\t\t}\n\t}\n}\n\nstruct r_vk_image_s* R_VkMeatpipeDispatch(struct vk_meatpipe_s *meatpipe, vk_meatpipe_dispatch_t args) {\n\tAPROF_SCOPE_DECLARE_BEGIN(dispatch, __FUNCTION__);\n\n\tswapPingPongImages(meatpipe, args.combuf, args.is_discontinuous);\n\n\tconst vk_meatpipe_t *const mp = meatpipe;\n\tfor (int i = 0; i < mp->passes_count; ++i) {\n\t\tconst struct vk_meatpipe_pass_s *pass = meatpipe->passes + i;\n\t\tRayPassPerform(pass->pass, args.combuf,\n\t\t\t(ray_pass_perform_args_t){\n\t\t\t\t.frame_sequence = args.frame_sequence,\n\t\t\t\t.frame_set_slot = args.frame_set_slot,\n\t\t\t\t.width = args.width,\n\t\t\t\t.height = args.height,\n\t\t\t\t.resources = meatpipe->acquired_resources,\n\t\t\t\t.resources_map = pass->resource_map,\n\t\t\t}\n\t\t);\n\t}\n\tAPROF_SCOPE_END(dispatch);\n\n\treturn meatpipe->image_dest;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VMeatpipe.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\nstruct vk_meatpipe_s* R_VkMeatpipeCreateFromFile(const char *filename);\nvoid R_VkMeatpipeDestroy(struct vk_meatpipe_s *mp);\n\nint R_VkMeatpipeAcquireResources(struct vk_meatpipe_s *meatpipe, int max_width, int max_height);\n\ntypedef struct {\n\tstruct vk_combuf_s* combuf;\n\tuint32_t frame_sequence;\n\n\t// TODO This is kinda asking to be struct r_frame_context_t or something\n\tint frame_set_slot; // 0 or 1, until we do num_frame_slots\n\tint width, height;\n\tint is_discontinuous;\n} vk_meatpipe_dispatch_t;\n\nstruct r_vk_image_s* R_VkMeatpipeDispatch(struct vk_meatpipe_s *mp, vk_meatpipe_dispatch_t);\n"
  },
  {
    "path": "ref/vk/vulkan/VMisc.c",
    "content": "#include \"vk_core.h\"\n\nconst char *R_VkResultName(VkResult result) {\n\tswitch (result) {\n\tcase VK_SUCCESS: return \"VK_SUCCESS\";\n\tcase VK_NOT_READY: return \"VK_NOT_READY\";\n\tcase VK_TIMEOUT: return \"VK_TIMEOUT\";\n\tcase VK_EVENT_SET: return \"VK_EVENT_SET\";\n\tcase VK_EVENT_RESET: return \"VK_EVENT_RESET\";\n\tcase VK_INCOMPLETE: return \"VK_INCOMPLETE\";\n\tcase VK_ERROR_OUT_OF_HOST_MEMORY: return \"VK_ERROR_OUT_OF_HOST_MEMORY\";\n\tcase VK_ERROR_OUT_OF_DEVICE_MEMORY: return \"VK_ERROR_OUT_OF_DEVICE_MEMORY\";\n\tcase VK_ERROR_INITIALIZATION_FAILED: return \"VK_ERROR_INITIALIZATION_FAILED\";\n\tcase VK_ERROR_DEVICE_LOST: return \"VK_ERROR_DEVICE_LOST\";\n\tcase VK_ERROR_MEMORY_MAP_FAILED: return \"VK_ERROR_MEMORY_MAP_FAILED\";\n\tcase VK_ERROR_LAYER_NOT_PRESENT: return \"VK_ERROR_LAYER_NOT_PRESENT\";\n\tcase VK_ERROR_EXTENSION_NOT_PRESENT: return \"VK_ERROR_EXTENSION_NOT_PRESENT\";\n\tcase VK_ERROR_FEATURE_NOT_PRESENT: return \"VK_ERROR_FEATURE_NOT_PRESENT\";\n\tcase VK_ERROR_INCOMPATIBLE_DRIVER: return \"VK_ERROR_INCOMPATIBLE_DRIVER\";\n\tcase VK_ERROR_TOO_MANY_OBJECTS: return \"VK_ERROR_TOO_MANY_OBJECTS\";\n\tcase VK_ERROR_FORMAT_NOT_SUPPORTED: return \"VK_ERROR_FORMAT_NOT_SUPPORTED\";\n\tcase VK_ERROR_FRAGMENTED_POOL: return \"VK_ERROR_FRAGMENTED_POOL\";\n\tcase VK_ERROR_UNKNOWN: return \"VK_ERROR_UNKNOWN\";\n\tcase VK_ERROR_OUT_OF_POOL_MEMORY: return \"VK_ERROR_OUT_OF_POOL_MEMORY\";\n\tcase VK_ERROR_INVALID_EXTERNAL_HANDLE: return \"VK_ERROR_INVALID_EXTERNAL_HANDLE\";\n\tcase VK_ERROR_FRAGMENTATION: return \"VK_ERROR_FRAGMENTATION\";\n\tcase VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return \"VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS\";\n\tcase VK_ERROR_SURFACE_LOST_KHR: return \"VK_ERROR_SURFACE_LOST_KHR\";\n\tcase VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return \"VK_ERROR_NATIVE_WINDOW_IN_USE_KHR\";\n\tcase VK_SUBOPTIMAL_KHR: return \"VK_SUBOPTIMAL_KHR\";\n\tcase VK_ERROR_OUT_OF_DATE_KHR: return \"VK_ERROR_OUT_OF_DATE_KHR\";\n\tcase VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return \"VK_ERROR_INCOMPATIBLE_DISPLAY_KHR\";\n\tcase VK_ERROR_VALIDATION_FAILED_EXT: return \"VK_ERROR_VALIDATION_FAILED_EXT\";\n\tcase VK_ERROR_INVALID_SHADER_NV: return \"VK_ERROR_INVALID_SHADER_NV\";\n\tcase VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return \"VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT\";\n\tcase VK_ERROR_NOT_PERMITTED_EXT: return \"VK_ERROR_NOT_PERMITTED_EXT\";\n\tcase VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return \"VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT\";\n\tcase VK_THREAD_IDLE_KHR: return \"VK_THREAD_IDLE_KHR\";\n\tcase VK_THREAD_DONE_KHR: return \"VK_THREAD_DONE_KHR\";\n\tcase VK_OPERATION_DEFERRED_KHR: return \"VK_OPERATION_DEFERRED_KHR\";\n\tcase VK_OPERATION_NOT_DEFERRED_KHR: return \"VK_OPERATION_NOT_DEFERRED_KHR\";\n\tcase VK_PIPELINE_COMPILE_REQUIRED_EXT: return \"VK_PIPELINE_COMPILE_REQUIRED_EXT\";\n\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nconst char *R_VkPresentModeName(VkPresentModeKHR present_mode) {\n\tswitch (present_mode) {\n\t\tcase VK_PRESENT_MODE_IMMEDIATE_KHR: return \"VK_PRESENT_MODE_IMMEDIATE_KHR\";\n\t\tcase VK_PRESENT_MODE_MAILBOX_KHR: return \"VK_PRESENT_MODE_MAILBOX_KHR\";\n\t\tcase VK_PRESENT_MODE_FIFO_KHR: return \"VK_PRESENT_MODE_FIFO_KHR\";\n\t\tcase VK_PRESENT_MODE_FIFO_RELAXED_KHR: return \"VK_PRESENT_MODE_FIFO_RELAXED_KHR\";\n\t\tcase VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: return \"VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR\";\n\t\tcase VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: return \"VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR\";\n\t\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nconst char *R_VkFormatName( VkFormat format ) {\n\tswitch (format) {\n\t\tcase VK_FORMAT_UNDEFINED: return \"VK_FORMAT_UNDEFINED\";\n\t\tcase VK_FORMAT_R4G4_UNORM_PACK8: return \"VK_FORMAT_R4G4_UNORM_PACK8\";\n\t\tcase VK_FORMAT_R4G4B4A4_UNORM_PACK16: return \"VK_FORMAT_R4G4B4A4_UNORM_PACK16\";\n\t\tcase VK_FORMAT_B4G4R4A4_UNORM_PACK16: return \"VK_FORMAT_B4G4R4A4_UNORM_PACK16\";\n\t\tcase VK_FORMAT_R5G6B5_UNORM_PACK16: return \"VK_FORMAT_R5G6B5_UNORM_PACK16\";\n\t\tcase VK_FORMAT_B5G6R5_UNORM_PACK16: return \"VK_FORMAT_B5G6R5_UNORM_PACK16\";\n\t\tcase VK_FORMAT_R5G5B5A1_UNORM_PACK16: return \"VK_FORMAT_R5G5B5A1_UNORM_PACK16\";\n\t\tcase VK_FORMAT_B5G5R5A1_UNORM_PACK16: return \"VK_FORMAT_B5G5R5A1_UNORM_PACK16\";\n\t\tcase VK_FORMAT_A1R5G5B5_UNORM_PACK16: return \"VK_FORMAT_A1R5G5B5_UNORM_PACK16\";\n\t\tcase VK_FORMAT_R8_UNORM: return \"VK_FORMAT_R8_UNORM\";\n\t\tcase VK_FORMAT_R8_SNORM: return \"VK_FORMAT_R8_SNORM\";\n\t\tcase VK_FORMAT_R8_USCALED: return \"VK_FORMAT_R8_USCALED\";\n\t\tcase VK_FORMAT_R8_SSCALED: return \"VK_FORMAT_R8_SSCALED\";\n\t\tcase VK_FORMAT_R8_UINT: return \"VK_FORMAT_R8_UINT\";\n\t\tcase VK_FORMAT_R8_SINT: return \"VK_FORMAT_R8_SINT\";\n\t\tcase VK_FORMAT_R8_SRGB: return \"VK_FORMAT_R8_SRGB\";\n\t\tcase VK_FORMAT_R8G8_UNORM: return \"VK_FORMAT_R8G8_UNORM\";\n\t\tcase VK_FORMAT_R8G8_SNORM: return \"VK_FORMAT_R8G8_SNORM\";\n\t\tcase VK_FORMAT_R8G8_USCALED: return \"VK_FORMAT_R8G8_USCALED\";\n\t\tcase VK_FORMAT_R8G8_SSCALED: return \"VK_FORMAT_R8G8_SSCALED\";\n\t\tcase VK_FORMAT_R8G8_UINT: return \"VK_FORMAT_R8G8_UINT\";\n\t\tcase VK_FORMAT_R8G8_SINT: return \"VK_FORMAT_R8G8_SINT\";\n\t\tcase VK_FORMAT_R8G8_SRGB: return \"VK_FORMAT_R8G8_SRGB\";\n\t\tcase VK_FORMAT_R8G8B8_UNORM: return \"VK_FORMAT_R8G8B8_UNORM\";\n\t\tcase VK_FORMAT_R8G8B8_SNORM: return \"VK_FORMAT_R8G8B8_SNORM\";\n\t\tcase VK_FORMAT_R8G8B8_USCALED: return \"VK_FORMAT_R8G8B8_USCALED\";\n\t\tcase VK_FORMAT_R8G8B8_SSCALED: return \"VK_FORMAT_R8G8B8_SSCALED\";\n\t\tcase VK_FORMAT_R8G8B8_UINT: return \"VK_FORMAT_R8G8B8_UINT\";\n\t\tcase VK_FORMAT_R8G8B8_SINT: return \"VK_FORMAT_R8G8B8_SINT\";\n\t\tcase VK_FORMAT_R8G8B8_SRGB: return \"VK_FORMAT_R8G8B8_SRGB\";\n\t\tcase VK_FORMAT_B8G8R8_UNORM: return \"VK_FORMAT_B8G8R8_UNORM\";\n\t\tcase VK_FORMAT_B8G8R8_SNORM: return \"VK_FORMAT_B8G8R8_SNORM\";\n\t\tcase VK_FORMAT_B8G8R8_USCALED: return \"VK_FORMAT_B8G8R8_USCALED\";\n\t\tcase VK_FORMAT_B8G8R8_SSCALED: return \"VK_FORMAT_B8G8R8_SSCALED\";\n\t\tcase VK_FORMAT_B8G8R8_UINT: return \"VK_FORMAT_B8G8R8_UINT\";\n\t\tcase VK_FORMAT_B8G8R8_SINT: return \"VK_FORMAT_B8G8R8_SINT\";\n\t\tcase VK_FORMAT_B8G8R8_SRGB: return \"VK_FORMAT_B8G8R8_SRGB\";\n\t\tcase VK_FORMAT_R8G8B8A8_UNORM: return \"VK_FORMAT_R8G8B8A8_UNORM\";\n\t\tcase VK_FORMAT_R8G8B8A8_SNORM: return \"VK_FORMAT_R8G8B8A8_SNORM\";\n\t\tcase VK_FORMAT_R8G8B8A8_USCALED: return \"VK_FORMAT_R8G8B8A8_USCALED\";\n\t\tcase VK_FORMAT_R8G8B8A8_SSCALED: return \"VK_FORMAT_R8G8B8A8_SSCALED\";\n\t\tcase VK_FORMAT_R8G8B8A8_UINT: return \"VK_FORMAT_R8G8B8A8_UINT\";\n\t\tcase VK_FORMAT_R8G8B8A8_SINT: return \"VK_FORMAT_R8G8B8A8_SINT\";\n\t\tcase VK_FORMAT_R8G8B8A8_SRGB: return \"VK_FORMAT_R8G8B8A8_SRGB\";\n\t\tcase VK_FORMAT_B8G8R8A8_UNORM: return \"VK_FORMAT_B8G8R8A8_UNORM\";\n\t\tcase VK_FORMAT_B8G8R8A8_SNORM: return \"VK_FORMAT_B8G8R8A8_SNORM\";\n\t\tcase VK_FORMAT_B8G8R8A8_USCALED: return \"VK_FORMAT_B8G8R8A8_USCALED\";\n\t\tcase VK_FORMAT_B8G8R8A8_SSCALED: return \"VK_FORMAT_B8G8R8A8_SSCALED\";\n\t\tcase VK_FORMAT_B8G8R8A8_UINT: return \"VK_FORMAT_B8G8R8A8_UINT\";\n\t\tcase VK_FORMAT_B8G8R8A8_SINT: return \"VK_FORMAT_B8G8R8A8_SINT\";\n\t\tcase VK_FORMAT_B8G8R8A8_SRGB: return \"VK_FORMAT_B8G8R8A8_SRGB\";\n\t\tcase VK_FORMAT_A8B8G8R8_UNORM_PACK32: return \"VK_FORMAT_A8B8G8R8_UNORM_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_SNORM_PACK32: return \"VK_FORMAT_A8B8G8R8_SNORM_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_USCALED_PACK32: return \"VK_FORMAT_A8B8G8R8_USCALED_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_SSCALED_PACK32: return \"VK_FORMAT_A8B8G8R8_SSCALED_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_UINT_PACK32: return \"VK_FORMAT_A8B8G8R8_UINT_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_SINT_PACK32: return \"VK_FORMAT_A8B8G8R8_SINT_PACK32\";\n\t\tcase VK_FORMAT_A8B8G8R8_SRGB_PACK32: return \"VK_FORMAT_A8B8G8R8_SRGB_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_UNORM_PACK32: return \"VK_FORMAT_A2R10G10B10_UNORM_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_SNORM_PACK32: return \"VK_FORMAT_A2R10G10B10_SNORM_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_USCALED_PACK32: return \"VK_FORMAT_A2R10G10B10_USCALED_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_SSCALED_PACK32: return \"VK_FORMAT_A2R10G10B10_SSCALED_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_UINT_PACK32: return \"VK_FORMAT_A2R10G10B10_UINT_PACK32\";\n\t\tcase VK_FORMAT_A2R10G10B10_SINT_PACK32: return \"VK_FORMAT_A2R10G10B10_SINT_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_UNORM_PACK32: return \"VK_FORMAT_A2B10G10R10_UNORM_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_SNORM_PACK32: return \"VK_FORMAT_A2B10G10R10_SNORM_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_USCALED_PACK32: return \"VK_FORMAT_A2B10G10R10_USCALED_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_SSCALED_PACK32: return \"VK_FORMAT_A2B10G10R10_SSCALED_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_UINT_PACK32: return \"VK_FORMAT_A2B10G10R10_UINT_PACK32\";\n\t\tcase VK_FORMAT_A2B10G10R10_SINT_PACK32: return \"VK_FORMAT_A2B10G10R10_SINT_PACK32\";\n\t\tcase VK_FORMAT_R16_UNORM: return \"VK_FORMAT_R16_UNORM\";\n\t\tcase VK_FORMAT_R16_SNORM: return \"VK_FORMAT_R16_SNORM\";\n\t\tcase VK_FORMAT_R16_USCALED: return \"VK_FORMAT_R16_USCALED\";\n\t\tcase VK_FORMAT_R16_SSCALED: return \"VK_FORMAT_R16_SSCALED\";\n\t\tcase VK_FORMAT_R16_UINT: return \"VK_FORMAT_R16_UINT\";\n\t\tcase VK_FORMAT_R16_SINT: return \"VK_FORMAT_R16_SINT\";\n\t\tcase VK_FORMAT_R16_SFLOAT: return \"VK_FORMAT_R16_SFLOAT\";\n\t\tcase VK_FORMAT_R16G16_UNORM: return \"VK_FORMAT_R16G16_UNORM\";\n\t\tcase VK_FORMAT_R16G16_SNORM: return \"VK_FORMAT_R16G16_SNORM\";\n\t\tcase VK_FORMAT_R16G16_USCALED: return \"VK_FORMAT_R16G16_USCALED\";\n\t\tcase VK_FORMAT_R16G16_SSCALED: return \"VK_FORMAT_R16G16_SSCALED\";\n\t\tcase VK_FORMAT_R16G16_UINT: return \"VK_FORMAT_R16G16_UINT\";\n\t\tcase VK_FORMAT_R16G16_SINT: return \"VK_FORMAT_R16G16_SINT\";\n\t\tcase VK_FORMAT_R16G16_SFLOAT: return \"VK_FORMAT_R16G16_SFLOAT\";\n\t\tcase VK_FORMAT_R16G16B16_UNORM: return \"VK_FORMAT_R16G16B16_UNORM\";\n\t\tcase VK_FORMAT_R16G16B16_SNORM: return \"VK_FORMAT_R16G16B16_SNORM\";\n\t\tcase VK_FORMAT_R16G16B16_USCALED: return \"VK_FORMAT_R16G16B16_USCALED\";\n\t\tcase VK_FORMAT_R16G16B16_SSCALED: return \"VK_FORMAT_R16G16B16_SSCALED\";\n\t\tcase VK_FORMAT_R16G16B16_UINT: return \"VK_FORMAT_R16G16B16_UINT\";\n\t\tcase VK_FORMAT_R16G16B16_SINT: return \"VK_FORMAT_R16G16B16_SINT\";\n\t\tcase VK_FORMAT_R16G16B16_SFLOAT: return \"VK_FORMAT_R16G16B16_SFLOAT\";\n\t\tcase VK_FORMAT_R16G16B16A16_UNORM: return \"VK_FORMAT_R16G16B16A16_UNORM\";\n\t\tcase VK_FORMAT_R16G16B16A16_SNORM: return \"VK_FORMAT_R16G16B16A16_SNORM\";\n\t\tcase VK_FORMAT_R16G16B16A16_USCALED: return \"VK_FORMAT_R16G16B16A16_USCALED\";\n\t\tcase VK_FORMAT_R16G16B16A16_SSCALED: return \"VK_FORMAT_R16G16B16A16_SSCALED\";\n\t\tcase VK_FORMAT_R16G16B16A16_UINT: return \"VK_FORMAT_R16G16B16A16_UINT\";\n\t\tcase VK_FORMAT_R16G16B16A16_SINT: return \"VK_FORMAT_R16G16B16A16_SINT\";\n\t\tcase VK_FORMAT_R16G16B16A16_SFLOAT: return \"VK_FORMAT_R16G16B16A16_SFLOAT\";\n\t\tcase VK_FORMAT_R32_UINT: return \"VK_FORMAT_R32_UINT\";\n\t\tcase VK_FORMAT_R32_SINT: return \"VK_FORMAT_R32_SINT\";\n\t\tcase VK_FORMAT_R32_SFLOAT: return \"VK_FORMAT_R32_SFLOAT\";\n\t\tcase VK_FORMAT_R32G32_UINT: return \"VK_FORMAT_R32G32_UINT\";\n\t\tcase VK_FORMAT_R32G32_SINT: return \"VK_FORMAT_R32G32_SINT\";\n\t\tcase VK_FORMAT_R32G32_SFLOAT: return \"VK_FORMAT_R32G32_SFLOAT\";\n\t\tcase VK_FORMAT_R32G32B32_UINT: return \"VK_FORMAT_R32G32B32_UINT\";\n\t\tcase VK_FORMAT_R32G32B32_SINT: return \"VK_FORMAT_R32G32B32_SINT\";\n\t\tcase VK_FORMAT_R32G32B32_SFLOAT: return \"VK_FORMAT_R32G32B32_SFLOAT\";\n\t\tcase VK_FORMAT_R32G32B32A32_UINT: return \"VK_FORMAT_R32G32B32A32_UINT\";\n\t\tcase VK_FORMAT_R32G32B32A32_SINT: return \"VK_FORMAT_R32G32B32A32_SINT\";\n\t\tcase VK_FORMAT_R32G32B32A32_SFLOAT: return \"VK_FORMAT_R32G32B32A32_SFLOAT\";\n\t\tcase VK_FORMAT_R64_UINT: return \"VK_FORMAT_R64_UINT\";\n\t\tcase VK_FORMAT_R64_SINT: return \"VK_FORMAT_R64_SINT\";\n\t\tcase VK_FORMAT_R64_SFLOAT: return \"VK_FORMAT_R64_SFLOAT\";\n\t\tcase VK_FORMAT_R64G64_UINT: return \"VK_FORMAT_R64G64_UINT\";\n\t\tcase VK_FORMAT_R64G64_SINT: return \"VK_FORMAT_R64G64_SINT\";\n\t\tcase VK_FORMAT_R64G64_SFLOAT: return \"VK_FORMAT_R64G64_SFLOAT\";\n\t\tcase VK_FORMAT_R64G64B64_UINT: return \"VK_FORMAT_R64G64B64_UINT\";\n\t\tcase VK_FORMAT_R64G64B64_SINT: return \"VK_FORMAT_R64G64B64_SINT\";\n\t\tcase VK_FORMAT_R64G64B64_SFLOAT: return \"VK_FORMAT_R64G64B64_SFLOAT\";\n\t\tcase VK_FORMAT_R64G64B64A64_UINT: return \"VK_FORMAT_R64G64B64A64_UINT\";\n\t\tcase VK_FORMAT_R64G64B64A64_SINT: return \"VK_FORMAT_R64G64B64A64_SINT\";\n\t\tcase VK_FORMAT_R64G64B64A64_SFLOAT: return \"VK_FORMAT_R64G64B64A64_SFLOAT\";\n\t\tcase VK_FORMAT_B10G11R11_UFLOAT_PACK32: return \"VK_FORMAT_B10G11R11_UFLOAT_PACK32\";\n\t\tcase VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: return \"VK_FORMAT_E5B9G9R9_UFLOAT_PACK32\";\n\t\tcase VK_FORMAT_D16_UNORM: return \"VK_FORMAT_D16_UNORM\";\n\t\tcase VK_FORMAT_X8_D24_UNORM_PACK32: return \"VK_FORMAT_X8_D24_UNORM_PACK32\";\n\t\tcase VK_FORMAT_D32_SFLOAT: return \"VK_FORMAT_D32_SFLOAT\";\n\t\tcase VK_FORMAT_S8_UINT: return \"VK_FORMAT_S8_UINT\";\n\t\tcase VK_FORMAT_D16_UNORM_S8_UINT: return \"VK_FORMAT_D16_UNORM_S8_UINT\";\n\t\tcase VK_FORMAT_D24_UNORM_S8_UINT: return \"VK_FORMAT_D24_UNORM_S8_UINT\";\n\t\tcase VK_FORMAT_D32_SFLOAT_S8_UINT: return \"VK_FORMAT_D32_SFLOAT_S8_UINT\";\n\t\tcase VK_FORMAT_BC1_RGB_UNORM_BLOCK: return \"VK_FORMAT_BC1_RGB_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC1_RGB_SRGB_BLOCK: return \"VK_FORMAT_BC1_RGB_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_BC1_RGBA_UNORM_BLOCK: return \"VK_FORMAT_BC1_RGBA_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return \"VK_FORMAT_BC1_RGBA_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_BC2_UNORM_BLOCK: return \"VK_FORMAT_BC2_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC2_SRGB_BLOCK: return \"VK_FORMAT_BC2_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_BC3_UNORM_BLOCK: return \"VK_FORMAT_BC3_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC3_SRGB_BLOCK: return \"VK_FORMAT_BC3_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_BC4_UNORM_BLOCK: return \"VK_FORMAT_BC4_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC4_SNORM_BLOCK: return \"VK_FORMAT_BC4_SNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC5_UNORM_BLOCK: return \"VK_FORMAT_BC5_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC5_SNORM_BLOCK: return \"VK_FORMAT_BC5_SNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC6H_UFLOAT_BLOCK: return \"VK_FORMAT_BC6H_UFLOAT_BLOCK\";\n\t\tcase VK_FORMAT_BC6H_SFLOAT_BLOCK: return \"VK_FORMAT_BC6H_SFLOAT_BLOCK\";\n\t\tcase VK_FORMAT_BC7_UNORM_BLOCK: return \"VK_FORMAT_BC7_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_BC7_SRGB_BLOCK: return \"VK_FORMAT_BC7_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: return \"VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_EAC_R11_UNORM_BLOCK: return \"VK_FORMAT_EAC_R11_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_EAC_R11_SNORM_BLOCK: return \"VK_FORMAT_EAC_R11_SNORM_BLOCK\";\n\t\tcase VK_FORMAT_EAC_R11G11_UNORM_BLOCK: return \"VK_FORMAT_EAC_R11G11_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_EAC_R11G11_SNORM_BLOCK: return \"VK_FORMAT_EAC_R11G11_SNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_4x4_UNORM_BLOCK: return \"VK_FORMAT_ASTC_4x4_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return \"VK_FORMAT_ASTC_4x4_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_5x4_UNORM_BLOCK: return \"VK_FORMAT_ASTC_5x4_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return \"VK_FORMAT_ASTC_5x4_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_5x5_UNORM_BLOCK: return \"VK_FORMAT_ASTC_5x5_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return \"VK_FORMAT_ASTC_5x5_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_6x5_UNORM_BLOCK: return \"VK_FORMAT_ASTC_6x5_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return \"VK_FORMAT_ASTC_6x5_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_6x6_UNORM_BLOCK: return \"VK_FORMAT_ASTC_6x6_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return \"VK_FORMAT_ASTC_6x6_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x5_UNORM_BLOCK: return \"VK_FORMAT_ASTC_8x5_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return \"VK_FORMAT_ASTC_8x5_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x6_UNORM_BLOCK: return \"VK_FORMAT_ASTC_8x6_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return \"VK_FORMAT_ASTC_8x6_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x8_UNORM_BLOCK: return \"VK_FORMAT_ASTC_8x8_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return \"VK_FORMAT_ASTC_8x8_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x5_UNORM_BLOCK: return \"VK_FORMAT_ASTC_10x5_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return \"VK_FORMAT_ASTC_10x5_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x6_UNORM_BLOCK: return \"VK_FORMAT_ASTC_10x6_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return \"VK_FORMAT_ASTC_10x6_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x8_UNORM_BLOCK: return \"VK_FORMAT_ASTC_10x8_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return \"VK_FORMAT_ASTC_10x8_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x10_UNORM_BLOCK: return \"VK_FORMAT_ASTC_10x10_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return \"VK_FORMAT_ASTC_10x10_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_12x10_UNORM_BLOCK: return \"VK_FORMAT_ASTC_12x10_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return \"VK_FORMAT_ASTC_12x10_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_12x12_UNORM_BLOCK: return \"VK_FORMAT_ASTC_12x12_UNORM_BLOCK\";\n\t\tcase VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return \"VK_FORMAT_ASTC_12x12_SRGB_BLOCK\";\n\t\tcase VK_FORMAT_G8B8G8R8_422_UNORM: return \"VK_FORMAT_G8B8G8R8_422_UNORM\";\n\t\tcase VK_FORMAT_B8G8R8G8_422_UNORM: return \"VK_FORMAT_B8G8R8G8_422_UNORM\";\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: return \"VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM\";\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: return \"VK_FORMAT_G8_B8R8_2PLANE_420_UNORM\";\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM: return \"VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM\";\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_422_UNORM: return \"VK_FORMAT_G8_B8R8_2PLANE_422_UNORM\";\n\t\tcase VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM: return \"VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM\";\n\t\tcase VK_FORMAT_R10X6_UNORM_PACK16: return \"VK_FORMAT_R10X6_UNORM_PACK16\";\n\t\tcase VK_FORMAT_R10X6G10X6_UNORM_2PACK16: return \"VK_FORMAT_R10X6G10X6_UNORM_2PACK16\";\n\t\tcase VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16: return \"VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16: return \"VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16: return \"VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16: return \"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16: return \"VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16: return \"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16: return \"VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16: return \"VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_R12X4_UNORM_PACK16: return \"VK_FORMAT_R12X4_UNORM_PACK16\";\n\t\tcase VK_FORMAT_R12X4G12X4_UNORM_2PACK16: return \"VK_FORMAT_R12X4G12X4_UNORM_2PACK16\";\n\t\tcase VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16: return \"VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16: return \"VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16: return \"VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16\";\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16: return \"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16: return \"VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16: return \"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16: return \"VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16: return \"VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16\";\n\t\tcase VK_FORMAT_G16B16G16R16_422_UNORM: return \"VK_FORMAT_G16B16G16R16_422_UNORM\";\n\t\tcase VK_FORMAT_B16G16R16G16_422_UNORM: return \"VK_FORMAT_B16G16R16G16_422_UNORM\";\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM: return \"VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM\";\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_420_UNORM: return \"VK_FORMAT_G16_B16R16_2PLANE_420_UNORM\";\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM: return \"VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM\";\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_422_UNORM: return \"VK_FORMAT_G16_B16R16_2PLANE_422_UNORM\";\n\t\tcase VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM: return \"VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM\";\n\t\tcase VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: return \"VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: return \"VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: return \"VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: return \"VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: return \"VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: return \"VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: return \"VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG\";\n\t\tcase VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: return \"VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG\";\n\t\tcase VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT: return \"VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT\";\n\t\tcase VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT: return \"VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT\";\n\t\tcase VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT: return \"VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT\";\n\t\tcase VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT: return \"VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT\";\n\t\tcase VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT: return \"VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT\";\n\t\tcase VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT: return \"VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT\";\n\t\tcase VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: return \"VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT\";\n\t\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nconst char *R_VkColorSpaceName(VkColorSpaceKHR colorspace) {\n\tswitch (colorspace) {\n\t\tcase VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: return \"VK_COLOR_SPACE_SRGB_NONLINEAR_KHR\";\n\t\tcase VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT: return \"VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: return \"VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT: return \"VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT: return \"VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_BT709_LINEAR_EXT: return \"VK_COLOR_SPACE_BT709_LINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_BT709_NONLINEAR_EXT: return \"VK_COLOR_SPACE_BT709_NONLINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_BT2020_LINEAR_EXT: return \"VK_COLOR_SPACE_BT2020_LINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_HDR10_ST2084_EXT: return \"VK_COLOR_SPACE_HDR10_ST2084_EXT\";\n\t\tcase VK_COLOR_SPACE_DOLBYVISION_EXT: return \"VK_COLOR_SPACE_DOLBYVISION_EXT\";\n\t\tcase VK_COLOR_SPACE_HDR10_HLG_EXT: return \"VK_COLOR_SPACE_HDR10_HLG_EXT\";\n\t\tcase VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT: return \"VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT: return \"VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_PASS_THROUGH_EXT: return \"VK_COLOR_SPACE_PASS_THROUGH_EXT\";\n\t\tcase VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT: return \"VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT\";\n\t\tcase VK_COLOR_SPACE_DISPLAY_NATIVE_AMD: return \"VK_COLOR_SPACE_DISPLAY_NATIVE_AMD\";\n\t\tcase VK_COLOR_SPACE_MAX_ENUM_KHR: return \"VK_COLOR_SPACE_MAX_ENUM_KHR\";\n\t\tdefault: return \"UNKNOWN\";\n\t}\n}\n\nconst char *R_VkImageLayoutName(VkImageLayout layout) {\n\tswitch (layout) {\n\t\tcase VK_IMAGE_LAYOUT_UNDEFINED: return \"VK_IMAGE_LAYOUT_UNDEFINED\";\n\t\tcase VK_IMAGE_LAYOUT_GENERAL: return \"VK_IMAGE_LAYOUT_GENERAL\";\n\t\tcase VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: return \"VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: return \"VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_PREINITIALIZED: return \"VK_IMAGE_LAYOUT_PREINITIALIZED\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL: return \"VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL: return \"VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL\";\n\t\tcase VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: return \"VK_IMAGE_LAYOUT_PRESENT_SRC_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_DECODE_SRC_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_DECODE_SRC_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR: return \"VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT: return \"VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT\";\n\t\tcase VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR: return \"VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR: return \"VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_ENCODE_DST_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_ENCODE_DST_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR: return \"VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR\";\n\t\tcase VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT: return \"VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT\";\n\t\tcase VK_IMAGE_LAYOUT_MAX_ENUM: break;\n\t}\n\treturn \"UNKNOWN\";\n}\n\nconst char *R_VkDescriptorTypeName(VkDescriptorType descriptor_type) {\n\tswitch (descriptor_type) {\n\t\tcase VK_DESCRIPTOR_TYPE_SAMPLER: return \"VK_DESCRIPTOR_TYPE_SAMPLER\";\n\t\tcase VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: return \"VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER\";\n\t\tcase VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: return \"VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE\";\n\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: return \"VK_DESCRIPTOR_TYPE_STORAGE_IMAGE\";\n\t\tcase VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: return \"VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER\";\n\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: return \"VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER\";\n\t\tcase VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: return \"VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER\";\n\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: return \"VK_DESCRIPTOR_TYPE_STORAGE_BUFFER\";\n\t\tcase VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: return \"VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC\";\n\t\tcase VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: return \"VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC\";\n\t\tcase VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: return \"VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT\";\n\t\tcase VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK: return \"VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK\";\n\t\tcase VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: return \"VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR\";\n\t\tcase VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV: return \"VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV\";\n\t\tcase VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM: return \"VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM\";\n\t\tcase VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM: return \"VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM\";\n\t\tcase VK_DESCRIPTOR_TYPE_MUTABLE_EXT: return \"VK_DESCRIPTOR_TYPE_MUTABLE_EXT\";\n\t\tdefault: return \"UNKNOWN\";\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VNvAftermath.c",
    "content": "#include \"VNvAftermath.h\"\n\n#include \"vk_common.h\"\n#include \"vk_core.h\"\n\n#include \"xash3d_types.h\"\n\n#ifdef USE_AFTERMATH\n#include \"GFSDK_Aftermath.h\"\n#include \"GFSDK_Aftermath_GpuCrashDump.h\"\n#include \"GFSDK_Aftermath_GpuCrashDumpDecoding.h\"\n#endif // ifdef USE_AFTERMATH\n\n#include <stdio.h>\n\n#define MAX_NV_CHECKPOINTS 2048\n\ntypedef struct {\n\tunsigned sequence;\n\tchar message[256];\n} vk_nv_checkpoint_entry_t;\n\nstatic struct {\n\tunsigned sequence;\n\tvk_nv_checkpoint_entry_t entries[MAX_NV_CHECKPOINTS];\n} g_nv_checkpoint = {0};\n\n#ifdef USE_AFTERMATH\nstatic const char *aftermathErrorName(GFSDK_Aftermath_Result result) {\n\tswitch (result) {\n#define CASE(c) case c: return #c;\n\t\tCASE(GFSDK_Aftermath_Result_NotAvailable)\n\t\tCASE(GFSDK_Aftermath_Result_Fail)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_VersionMismatch)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_NotInitialized)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_InvalidAdapter)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_InvalidParameter)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_Unknown)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_ApiError)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_NvApiIncompatible)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_GettingContextDataWithNewCommandList)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_AlreadyInitialized)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_D3DDebugLayerNotCompatible)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_DriverInitFailed)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_DriverVersionNotSupported)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_OutOfMemory)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_GetDataOnBundle)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_GetDataOnDeferredContext)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_FeatureNotEnabled)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_NoResourcesRegistered)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_ThisResourceNeverRegistered)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_NotSupportedInUWP)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_D3dDllNotSupported)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_D3dDllInterceptionNotSupported)\n\t\tCASE(GFSDK_Aftermath_Result_FAIL_Disabled)\n#undef CASE\n\t}\n\n\treturn \"UNKNOWN\";\n}\n\n#define AM_CHECK(F) \\\ndo { \\\n\tGFSDK_Aftermath_Result result = F; \\\n\tif (!GFSDK_Aftermath_SUCCEED(result)) { \\\n\t\tgEngine.Con_Printf( S_ERROR \"%s:%d \" #F \" failed (%#x): %s\\n\", \\\n\t\t\t__FILE__, __LINE__, result, aftermathErrorName(result)); \\\n\t} \\\n} while (0)\n\nstatic qboolean writeFile(const char *filename, const void *data, size_t size) {\n\tFILE *f = fopen(filename, \"wb\");\n\tqboolean result = false;\n\tif (!f)\n\t\treturn result;\n\tresult = fwrite(data, 1, size, f) == size;\n\tfclose(f);\n\treturn result;\n}\n\nstatic void callbackGpuCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize, void* pUserData) {\n\tgEngine.Con_Printf(S_ERROR \"AFTERMATH GPU CRASH DUMP: %p, size=%d\\n\", pGpuCrashDump, gpuCrashDumpSize);\n\twriteFile(\"ref_vk.nv-gpudmp\", pGpuCrashDump, gpuCrashDumpSize);\n}\n\nstatic void callbackShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize, void* pUserData) {\n\t\tGFSDK_Aftermath_ShaderDebugInfoIdentifier identifier = {0};\n\tgEngine.Con_Printf(S_ERROR \"AFTERMATH Shader Debug Info: %p, size=%d\\n\", pShaderDebugInfo, shaderDebugInfoSize);\n\n\t\tAM_CHECK(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(\n\t\t\t\tGFSDK_Aftermath_Version_API,\n\t\t\t\tpShaderDebugInfo,\n\t\t\t\tshaderDebugInfoSize,\n\t\t\t\t&identifier));\n\n\tchar filename[64];\n\tQ_snprintf(filename, sizeof(filename), \"shader-%016llX-%016llX.nvdbg\", identifier.id[0], identifier.id[1]);\n\twriteFile(filename, pShaderDebugInfo, shaderDebugInfoSize);\n}\n\nstatic void callbackGpuCrashDumpDescription(PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addValue, void* pUserData) {\n\tgEngine.Con_Printf(S_ERROR \"AFTERMATH asks for crash dump description\\n\");\n\taddValue(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, \"xash3d-fwgs-ref-vk\");\n\taddValue(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationVersion, \"v0.0.1\");\n}\n\n\nstatic const char obsolete[] = \"[OBSOLETE]\";\n\nstatic void callbackResolveMarkers(const void* pMarker, void* pUserData, void** resolvedMarkerData, uint32_t* markerSize) {\n\tconst unsigned sequence = (uintptr_t)pMarker;\n\tconst vk_nv_checkpoint_entry_t *const entry = g_nv_checkpoint.entries + (sequence % MAX_NV_CHECKPOINTS);\n\n\tconst char *msg = entry->sequence == sequence ? entry->message : obsolete;\n\tgEngine.Con_Reportf(S_ERROR \"resolved marker %u: msg: %s\\n\", sequence, msg);\n\n\t*resolvedMarkerData = (void*)msg;\n\t*markerSize = strlen(msg);\n}\n\nstatic qboolean initialized = false;\nqboolean VK_AftermathInit() {\n\tAM_CHECK(GFSDK_Aftermath_EnableGpuCrashDumps(\n\t\tGFSDK_Aftermath_Version_API,\n\t\tGFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,\n\t\tGFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks,\n\t\tcallbackGpuCrashDump,\n\t\tcallbackShaderDebugInfo,\n\t\tcallbackGpuCrashDumpDescription,\n\t\tcallbackResolveMarkers,\n\t\tNULL));\n\n\tinitialized = true;\n\treturn true;\n}\n\nvoid VK_AftermathShutdown() {\n\tif (initialized) {\n\t\tGFSDK_Aftermath_DisableGpuCrashDumps();\n\t}\n}\n#endif //ifdef USE_AFTERMATH\n\nvoid R_Vk_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...) {\n\tASSERT(vkCmdSetCheckpointNV);\n\n\tva_list argptr;\n\t++g_nv_checkpoint.sequence;\n\n\tvk_nv_checkpoint_entry_t *entry = g_nv_checkpoint.entries + (g_nv_checkpoint.sequence % MAX_NV_CHECKPOINTS);\n\tentry->sequence = g_nv_checkpoint.sequence;\n\n\tva_start( argptr, fmt );\n\tvsnprintf( entry->message, sizeof entry->message, fmt, argptr );\n\tva_end( argptr );\n\n\tconst uintptr_t marker = entry->sequence;\n\tvkCmdSetCheckpointNV(cmdbuf, (const void*)marker);\n}\n\nvoid R_Vk_NV_Checkpoint_Dump(void) {\n\tASSERT(vkGetQueueCheckpointDataNV);\n\n\tuint32_t checkpoints_count = 0;\n\tvkGetQueueCheckpointDataNV(vk_core.queue, &checkpoints_count, NULL);\n\n\tVkCheckpointDataNV checkpoints[32];\n\tif (checkpoints_count > COUNTOF(checkpoints))\n\t\tcheckpoints_count = COUNTOF(checkpoints);\n\n\tfor (int i = 0; i < checkpoints_count; ++i) {\n\t\tcheckpoints[i].pNext = NULL;\n\t\tcheckpoints[i].sType = VK_STRUCTURE_TYPE_CHECKPOINT_DATA_NV;\n\t}\n\n\tvkGetQueueCheckpointDataNV(vk_core.queue, &checkpoints_count, checkpoints);\n\n\tgEngine.Con_Reportf(S_ERROR \"Checkpoints: %d\\n\", checkpoints_count);\n\tfor (int i = 0; i < checkpoints_count; ++i) {\n\t\tconst VkCheckpointDataNV *const checkpoint = checkpoints + i;\n\t\tconst unsigned sequence = (uintptr_t)checkpoint->pCheckpointMarker;\n\t\tconst vk_nv_checkpoint_entry_t *const entry = g_nv_checkpoint.entries + (sequence % MAX_NV_CHECKPOINTS);\n\t\tgEngine.Con_Reportf(S_ERROR \"\\t%u: stage=%04x msg: %s\\n\", sequence, checkpoint->stage, entry->sequence == sequence ? entry->message : \"[OBSOLETE]\");\n\t}\n}\n\n"
  },
  {
    "path": "ref/vk/vulkan/VNvAftermath.h",
    "content": "#pragma once\n\n#include \"xash3d_types.h\"\n\n#define VK_NO_PROTOTYPES\n#include <vulkan/vulkan.h>\n\n#ifdef USE_AFTERMATH\nqboolean VK_AftermathInit();\nvoid VK_AftermathShutdown();\n#endif\n\nvoid R_Vk_NV_CheckpointF(VkCommandBuffer cmdbuf, const char *fmt, ...);\nvoid R_Vk_NV_Checkpoint_Dump(void);\n\n#define DEBUG_NV_CHECKPOINTF(cmdbuf, fmt, ...) \\\n\tdo { \\\n\t\tif (vk_core.nv_checkpoint) { \\\n\t\t\tR_Vk_NV_CheckpointF(cmdbuf, fmt, ##__VA_ARGS__); \\\n\t\t} \\\n\t} while(0)\n\n#define DEBUG_NV_CHECKPOINT_DUMP() \\\n\tdo { \\\n\t\tif (vk_core.nv_checkpoint) { \\\n\t\t\tR_Vk_NV_Checkpoint_Dump(); \\\n\t\t} \\\n\t} while(0)\n"
  },
  {
    "path": "ref/vk/vulkan/VPass.c",
    "content": "#include \"VPass.h\"\n#include \"shaders/ray_interop.h\" // for SPEC_SBT_RECORD_SIZE_INDEX\n#include \"VResource.h\"\n#include \"VPipeline.h\"\n#include \"VDescriptor.h\"\n#include \"VCombuf.h\"\n#include \"VBarrier.h\"\n\n// FIXME this is only needed for MAX_CONCURRENT_FRAMES\n// TODO specify it externally as ctor arg\n#include \"vk_framectl.h\"\n\n#define MAX_STAGES 16\n#define MAX_MISS_GROUPS 8\n#define MAX_HIT_GROUPS 8\n\ntypedef enum {\n\tRayPassType_Compute,\n\tRayPassType_Tracing,\n} ray_pass_type_t;\n\ntypedef struct ray_pass_s {\n\tray_pass_type_t type; // TODO remove this in favor of VkPipelineStageFlagBits\n\tVkPipelineStageFlagBits pipeline_type;\n\tchar debug_name[32];\n\tint gpurofl_scope_id;\n\n\tstruct {\n\t\tint write_from;\n\t\tvk_descriptors_t riptors;\n\t\tVkDescriptorSet sets[MAX_CONCURRENT_FRAMES];\n\t} desc;\n} ray_pass_t;\n\ntypedef struct {\n\tray_pass_t header;\n\tvk_pipeline_ray_t pipeline;\n} ray_pass_tracing_impl_t;\n\ntypedef struct {\n\tray_pass_t header;\n\tVkPipeline pipeline;\n} ray_pass_compute_impl_t;\n\nstatic void initPassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) {\n\theader->desc.riptors = (vk_descriptors_t) {\n\t\t.bindings = layout->bindings,\n\t\t.num_bindings = layout->bindings_count,\n\t\t.num_sets = COUNTOF(header->desc.sets),\n\t\t.desc_sets = header->desc.sets,\n\t\t.push_constants = layout->push_constants,\n\t};\n\n\tVK_DescriptorsCreate(&header->desc.riptors);\n\n\theader->desc.write_from = layout->write_from;\n}\n\nstatic void finalizePassDescriptors( ray_pass_t *header, const ray_pass_layout_t *layout ) {\n\tconst size_t bindings_size = sizeof(layout->bindings[0]) * layout->bindings_count;\n\tVkDescriptorSetLayoutBinding *bindings = Mem_Malloc(vk_core.pool, bindings_size);\n\tmemcpy(bindings, layout->bindings, bindings_size);\n\theader->desc.riptors.bindings = bindings;\n\n\theader->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(header->desc.riptors.values[0]) * layout->bindings_count);\n}\n\nstruct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create ) {\n\tray_pass_tracing_impl_t *const pass = Mem_Malloc(vk_core.pool, sizeof(*pass));\n\tray_pass_t *const header = &pass->header;\n\n\t// TODO support external specialization\n\tASSERT(!create->specialization);\n\n\tconst struct SpecializationData {\n\t\tuint32_t sbt_record_size;\n\t} spec_data = {\n\t\t.sbt_record_size = v_device_info.sbt_record_size,\n\t};\n\tconst VkSpecializationMapEntry spec_map[] = {\n\t\t{.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) },\n\t};\n\tconst VkSpecializationInfo spec = {\n\t\t.mapEntryCount = COUNTOF(spec_map),\n\t\t.pMapEntries = spec_map,\n\t\t.dataSize = sizeof(spec_data),\n\t\t.pData = &spec_data,\n\t};\n\n\tinitPassDescriptors(header, &create->layout);\n\n\t{\n\t\tint stage_index = 0;\n\t\tvk_shader_stage_t stages[MAX_STAGES];\n\t\tint miss_index = 0;\n\t\tint misses[MAX_MISS_GROUPS];\n\t\tint hit_index = 0;\n\t\tvk_pipeline_ray_hit_group_t hits[MAX_HIT_GROUPS];\n\n\t\tvk_pipeline_ray_create_info_t prci = {\n\t\t\t.debug_name = create->debug_name,\n\t\t\t.layout = header->desc.riptors.pipeline_layout,\n\t\t\t.stages = stages,\n\t\t\t.groups = {\n\t\t\t\t.hit = hits,\n\t\t\t\t.miss = misses,\n\t\t\t},\n\t\t};\n\n\t\tstages[stage_index++] = (vk_shader_stage_t) {\n\t\t\t.module = create->raygen_module,\n\t\t\t.filename = NULL,\n\t\t\t.stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR,\n\t\t\t.specialization_info = &spec,\n\t\t};\n\n\t\tfor (int i = 0; i < create->miss_count; ++i) {\n\t\t\tconst VkShaderModule shader_module = create->miss_module ? create->miss_module[i] : VK_NULL_HANDLE;\n\n\t\t\tASSERT(stage_index < MAX_STAGES);\n\t\t\tASSERT(miss_index < MAX_MISS_GROUPS);\n\n\t\t\t// TODO handle duplicate filenames\n\t\t\t// TODO really, there should be a global table of shader modules as some of them are used across several passes (e.g. any hit alpha test)\n\t\t\tmisses[miss_index++] = stage_index;\n\t\t\tstages[stage_index++] = (vk_shader_stage_t) {\n\t\t\t\t.module = shader_module,\n\t\t\t\t.filename = NULL,\n\t\t\t\t.stage = VK_SHADER_STAGE_MISS_BIT_KHR,\n\t\t\t\t.specialization_info = &spec,\n\t\t\t};\n\t\t}\n\n\t\tfor (int i = 0; i < create->hit_count; ++i) {\n\t\t\tconst ray_pass_hit_group_t *const group = create->hit + i;\n\n\t\t\tASSERT(hit_index < MAX_HIT_GROUPS);\n\n\t\t\t// TODO handle duplicate filenames\n\t\t\tif (group->any_module) {\n\t\t\t\tASSERT(stage_index < MAX_STAGES);\n\t\t\t\thits[hit_index].any = stage_index;\n\t\t\t\tstages[stage_index++] = (vk_shader_stage_t) {\n\t\t\t\t\t.module = group->any_module,\n\t\t\t\t\t.filename = NULL,\n\t\t\t\t\t.stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR,\n\t\t\t\t\t.specialization_info = &spec,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\thits[hit_index].any = -1;\n\t\t\t}\n\n\t\t\tif (group->closest_module) {\n\t\t\t\tASSERT(stage_index < MAX_STAGES);\n\t\t\t\thits[hit_index].closest = stage_index;\n\t\t\t\tstages[stage_index++] = (vk_shader_stage_t) {\n\t\t\t\t\t.module = group->closest_module,\n\t\t\t\t\t.filename = NULL,\n\t\t\t\t\t.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR,\n\t\t\t\t\t.specialization_info = &spec,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\thits[hit_index].closest = -1;\n\t\t\t}\n\n\t\t\t++hit_index;\n\t\t}\n\n\t\tprci.groups.hit_count = hit_index;\n\t\tprci.groups.miss_count = miss_index;\n\t\tprci.stages_count = stage_index;\n\n\t\tpass->pipeline = VK_PipelineRayTracingCreate(&prci);\n\t}\n\n\tif (pass->pipeline.pipeline == VK_NULL_HANDLE) {\n\t\tVK_DescriptorsDestroy(&header->desc.riptors);\n\t\tMem_Free(pass);\n\t\treturn NULL;\n\t}\n\n\tfinalizePassDescriptors(header, &create->layout);\n\n\tQ_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name));\n\theader->type = RayPassType_Tracing;\n\theader->pipeline_type = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;\n\theader->gpurofl_scope_id = R_VkGpuScope_Register(create->debug_name);\n\n\treturn header;\n}\n\nstruct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create ) {\n\tray_pass_compute_impl_t *const pass = Mem_Malloc(vk_core.pool, sizeof(*pass));\n\tray_pass_t *const header = &pass->header;\n\n\tinitPassDescriptors(header, &create->layout);\n\n\tconst vk_pipeline_compute_create_info_t pcci = {\n\t\t.layout = header->desc.riptors.pipeline_layout,\n\t\t.shader_module = create->shader_module,\n\t\t.specialization_info = create->specialization,\n\t};\n\n\tpass->pipeline = VK_PipelineComputeCreate( &pcci );\n\tif (pass->pipeline == VK_NULL_HANDLE) {\n\t\tVK_DescriptorsDestroy(&header->desc.riptors);\n\t\tMem_Free(pass);\n\t\treturn NULL;\n\t}\n\n\tfinalizePassDescriptors(header, &create->layout);\n\n\tQ_strncpy(header->debug_name, create->debug_name, sizeof(header->debug_name));\n\theader->type = RayPassType_Compute;\n\theader->pipeline_type = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;\n\theader->gpurofl_scope_id = R_VkGpuScope_Register(create->debug_name);\n\n\treturn header;\n}\n\nvoid RayPassDestroy( struct ray_pass_s *pass ) {\n\tswitch (pass->type) {\n\t\tcase RayPassType_Tracing:\n\t\t\t{\n\t\t\t\tray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass;\n\t\t\t\tVK_PipelineRayTracingDestroy(&tracing->pipeline);\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase RayPassType_Compute:\n\t\t\t{\n\t\t\t\tray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass;\n\t\t\t\tvkDestroyPipeline(vk_core.device, compute->pipeline, NULL);\n\t\t\t\tbreak;\n\t\t\t}\n\t}\n\n\tVK_DescriptorsDestroy(&pass->desc.riptors);\n\tMem_Free(pass->desc.riptors.values);\n\tMem_Free((void*)pass->desc.riptors.bindings);\n\tMem_Free(pass);\n}\n\nstatic void performTracing( vk_combuf_t* combuf, int set_slot, const ray_pass_tracing_impl_t *tracing, int width, int height, int scope_id ) {\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\tvkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->pipeline.pipeline);\n\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tracing->header.desc.riptors.pipeline_layout, 0, 1, tracing->header.desc.riptors.desc_sets + set_slot, 0, NULL);\n\tVK_PipelineRayTracingTrace(combuf, &tracing->pipeline, width, height, scope_id);\n}\n\nstatic void performCompute( vk_combuf_t *combuf, int set_slot, const ray_pass_compute_impl_t *compute, int width, int height, int scope_id) {\n\t// TODO tunable\n\tconst uint32_t WG_W = 8;\n\tconst uint32_t WG_H = 8;\n\n\tconst VkCommandBuffer cmdbuf = combuf->cmdbuf;\n\n\tvkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->pipeline);\n\tvkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, compute->header.desc.riptors.pipeline_layout, 0, 1, compute->header.desc.riptors.desc_sets + set_slot, 0, NULL);\n\n\tconst int begin_id = R_VkCombufScopeBegin(combuf, scope_id, VCombufScopeFlag_PerfQuery);\n\tvkCmdDispatch(cmdbuf, (width + WG_W - 1) / WG_W, (height + WG_H - 1) / WG_H, 1);\n\tR_VkCombufScopeEnd(combuf, begin_id, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);\n}\n\nvoid RayPassPerform(struct ray_pass_s *pass, vk_combuf_t *combuf, ray_pass_perform_args_t args ) {\n\tBarrier barrier = barrierMake(pass->pipeline_type);\n\n\tconst int num_bindings = pass->desc.riptors.num_bindings;\n\tfor (int i = 0; i < num_bindings; ++i) {\n\t\tconst int index = args.resources_map ? args.resources_map[i] : i;\n\t\trt_resource_t* const res = args.resources[index];\n\t\tconst qboolean write = i >= pass->desc.write_from;\n\n\t\tASSERT(pass->desc.riptors.bindings[i].descriptorType == res->type);\n\n\t\tR_VkResourceProduce(res, combuf, &(FrameContext){\n\t\t\t.frame_sequence = args.frame_sequence,\n\t\t});\n\n\t\tpass->desc.riptors.values[i] = res->acquire_descriptor(res, (vk_resource_acquire_descriptor_args_t){\n\t\t\t.combuf = combuf,\n\t\t\t.barriers = &barrier,\n\t\t\t.access = write ? VK_ACCESS_2_SHADER_WRITE_BIT : VK_ACCESS_2_SHADER_READ_BIT,\n\t\t\t// Image must remain in GENERAL layout regardless of r/w.\n\t\t\t// Storage image reads still require GENERAL, not SHADER_READ_ONLY_OPTIMAL\n\t\t\t// TODO figure out why exactly -- i remain not convinced\n\t\t\t// Also, layout var only makes sense for images, for other descriptor types it is ignored.\n\t\t\t.image_layout = VK_IMAGE_LAYOUT_GENERAL,\n\t\t});\n\t}\n\n\tDEBUG_BEGIN(combuf->cmdbuf, pass->debug_name);\n\tbarrierCommit(&barrier, combuf);\n\n\tVK_DescriptorsWrite(&pass->desc.riptors, args.frame_set_slot);\n\n\tswitch (pass->type) {\n\t\tcase RayPassType_Tracing:\n\t\t\t{\n\t\t\t\tray_pass_tracing_impl_t *tracing = (ray_pass_tracing_impl_t*)pass;\n\t\t\t\tperformTracing(combuf, args.frame_set_slot, tracing, args.width, args.height, pass->gpurofl_scope_id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\tcase RayPassType_Compute:\n\t\t\t{\n\t\t\t\tray_pass_compute_impl_t *compute = (ray_pass_compute_impl_t*)pass;\n\t\t\t\tperformCompute(combuf, args.frame_set_slot, compute, args.width, args.height, pass->gpurofl_scope_id);\n\t\t\t\tbreak;\n\t\t\t}\n\t}\n\n\tDEBUG_END(combuf->cmdbuf);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VPass.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\n// TODO these should be like:\n// - parse the entire layout from shaders\n// - expose it as a struct[] interface of the pass\n// - resource/interface should prepare descriptors outside of pass code and just pass them to pass\ntypedef struct {\n\tconst VkDescriptorSetLayoutBinding *bindings;\n\tint bindings_count;\n\tint write_from;\n\n\tVkPushConstantRange push_constants;\n} ray_pass_layout_t;\n\n\nstruct ray_pass_s;\ntypedef struct ray_pass_s* ray_pass_p;\n\ntypedef struct {\n\t// TODO int num_frame_slots;\n\tconst char *debug_name;\n\tray_pass_layout_t layout;\n\n\tVkShaderModule shader_module;\n\tconst VkSpecializationInfo *specialization;\n} ray_pass_create_compute_t;\n\nstruct ray_pass_s *RayPassCreateCompute( const ray_pass_create_compute_t *create );\n\n\ntypedef struct {\n\tVkShaderModule closest_module;\n\tVkShaderModule any_module;\n} ray_pass_hit_group_t;\n\ntypedef struct {\n\t// TODO int num_frame_slots;\n\tconst char *debug_name;\n\tray_pass_layout_t layout;\n\n\t// TODO make a single tables of all shader modules\n\t// and then reference them by index in raygen/miss/hit tables\n\t// like it's done in vk_pipeline_ray_create_info_t\n\tVkShaderModule raygen_module;\n\n\tVkShaderModule *miss_module;\n\tint miss_count;\n\n\tconst ray_pass_hit_group_t *hit;\n\tint hit_count;\n\n\tconst VkSpecializationInfo *specialization;\n} ray_pass_create_tracing_t;\n\nstruct ray_pass_s *RayPassCreateTracing( const ray_pass_create_tracing_t *create );\n\n\nvoid RayPassDestroy( struct ray_pass_s *pass );\n\nstruct rt_resource_s;\n\ntypedef struct ray_pass_perform_args_s {\n\tuint32_t frame_sequence;\n\tint frame_set_slot; // 0 or 1, until we do num_frame_slots\n\tint width, height;\n\tstruct rt_resource_s* *const resources;\n\tconst int *resources_map; // TODO remove?\n} ray_pass_perform_args_t;\n\nstruct vk_combuf_s;\nvoid RayPassPerform(struct ray_pass_s *pass, struct vk_combuf_s* combuf, ray_pass_perform_args_t args );\n\n"
  },
  {
    "path": "ref/vk/vulkan/VPerfQuery.c",
    "content": "#include \"VPerfQuery.h\"\n#include \"VCombuf.h\"\n\n#include \"vk_logs.h\"\n\n#define LOG_MODULE combuf\n\ntypedef enum {\n\tQueryState_Available,\n\tQueryState_Began,\n\tQueryState_Ended,\n} QueryState;\n\nstruct VPerfQuery {\n\tVkQueryPool pool;\n\n\tVkPerformanceCounterResultKHR *results;\n\tuint32_t counters;\n\n\tstruct {\n\t\tQueryState *states;\n\t\tuint32_t max;\n\t} queries;\n};\n\nVPerfQuery *vPerfQueryCreate(const uint32_t *counters, uint32_t counters_count, uint32_t max_queries) {\n\tVPerfQuery pq = {0};\n\n\tVkQueryPoolPerformanceCreateInfoKHR qppci = {\n\t\t.sType = VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_CREATE_INFO_KHR,\n\t\t.pNext = NULL,\n\t\t.counterIndexCount = counters_count,\n\t\t.pCounterIndices = counters,\n\t\t.queueFamilyIndex = v_device_info.queue_index,\n\t};\n\n\tuint32_t passes_count = 0;\n\tvkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(v_device_info.physical_device, &qppci, &passes_count);\n\tif (passes_count != 1) {\n\t\tERR(\"Performance query with %d counters needs %d passes. Only a single pass is supported.\",\n\t\t\tcounters_count, passes_count);\n\t\treturn NULL;\n\t}\n\n\tVkQueryPoolCreateInfo qpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,\n\t\t.pNext = &qppci,\n\t\t.queryType = VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR,\n\t\t.queryCount = max_queries,\n\t};\n\n\tXVK_CHECK(vkCreateQueryPool(v_device, &qpci, NULL, &pq.pool));\n\tpq.queries.max = max_queries;\n\n\tvkResetQueryPool(v_device, pq.pool, 0, max_queries);\n\n\tconst size_t results_size = sizeof(VkPerformanceCounterResultKHR) * counters_count;\n\tconst size_t queries_size = sizeof(QueryState) * max_queries;\n\tconst size_t total_size = sizeof(VPerfQuery) + queries_size + results_size;\n\n\tVPerfQuery *const ret = Mem_Malloc(vk_core.pool, total_size);\n\t*ret = pq;\n\tret->queries.states = (QueryState*)((char*)ret + sizeof(VPerfQuery));\n\tret->results = (VkPerformanceCounterResultKHR*)((char*)ret->queries.states + queries_size);\n\tret->counters = counters_count;\n\tfor (uint32_t i = 0; i < ret->queries.max; ++i) {\n\t\tret->queries.states[i] = QueryState_Available;\n\t}\n\treturn ret;\n}\n\nvoid vPerfQueryDestroy(VPerfQuery *pq) {\n\tvkDestroyQueryPool(v_device, pq->pool, NULL);\n}\n\nint vPerfQueryBegin(VPerfQuery *pq, struct vk_combuf_s *cb) {\n\tfor (uint32_t i = 0; i < pq->queries.max; ++i) {\n\t\tif (pq->queries.states[i] != QueryState_Available)\n\t\t\tcontinue;\n\n\t\tvkCmdBeginQuery(cb->cmdbuf, pq->pool, i, 0);\n\t\tpq->queries.states[i] = QueryState_Began;\n\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nvoid vPerfQueryEnd(VPerfQuery *pq, struct vk_combuf_s *cb, uint32_t query_index) {\n\tif (query_index >= pq->queries.max)\n\t\treturn;\n\n\tASSERT(pq->queries.states[query_index] == QueryState_Began);\n\n\tvkCmdPipelineBarrier(cb->cmdbuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,\n\t\t0, 0, NULL, 0, NULL, 0, NULL);\n\tvkCmdEndQuery(cb->cmdbuf, pq->pool, query_index);\n\n\tpq->queries.states[query_index] = QueryState_Ended;\n}\n\nconst VkPerformanceCounterResultKHR* vPerfQueryRead(VPerfQuery *pq, struct vk_combuf_s *cb, uint32_t query_index) {\n\tif (query_index >= pq->queries.max)\n\t\treturn NULL;\n\n\tASSERT(pq->queries.states[query_index] == QueryState_Ended);\n\n\tconst uint32_t firstQuery = query_index;\n\tconst uint32_t queryCount = 1;\n\tconst size_t dataSize = pq->counters * sizeof(VkPerformanceCounterResultKHR);\n\tconst VkDeviceSize stride = pq->counters * sizeof(VkPerformanceCounterResultKHR);\n\tXVK_CHECK(vkGetQueryPoolResults(v_device, pq->pool,\n\t\tfirstQuery, queryCount, dataSize,\n\t\tpq->results, stride,\n\t\tVK_QUERY_RESULT_WAIT_BIT));\n\tvkResetQueryPool(v_device, pq->pool, firstQuery, queryCount);\n\n\tpq->queries.states[query_index] = QueryState_Available;\n\treturn pq->results;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VPerfQuery.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\nstruct vk_combuf_s;\n\ntypedef struct VPerfQuery VPerfQuery;\n\nVPerfQuery *vPerfQueryCreate(const uint32_t *counters, uint32_t counters_count, uint32_t max_queries);\nvoid vPerfQueryDestroy(VPerfQuery *pq);\n\nint vPerfQueryBegin(VPerfQuery *pq, struct vk_combuf_s *cb);\nvoid vPerfQueryEnd(VPerfQuery *pq, struct vk_combuf_s *cb, uint32_t query_index);\n\n// TODO profile, and possibly optimize?\n// Returns:\n// - pointer to `counters_count` array of values\n// - contents is valid only until the next vPerfQueryRead() call\nconst VkPerformanceCounterResultKHR* vPerfQueryRead(VPerfQuery *pq, struct vk_combuf_s *cb, uint32_t query_index);\n"
  },
  {
    "path": "ref/vk/vulkan/VPipeline.c",
    "content": "#include \"VPipeline.h\"\n\n#include \"vk_framectl.h\" // VkRenderPass\n#include \"VCombuf.h\"\n\n#include \"eiface.h\"\n\n#define MAX_STAGES 2\n\nVkPipelineCache g_pipeline_cache;\n\nqboolean VK_PipelineInit( void )\n{\n\tVkPipelineCacheCreateInfo pcci = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,\n\t\t.initialDataSize = 0,\n\t\t.pInitialData = NULL,\n\t};\n\n\tXVK_CHECK(vkCreatePipelineCache(vk_core.device, &pcci, NULL, &g_pipeline_cache));\n\treturn true;\n}\n\nvoid VK_PipelineShutdown( void )\n{\n\tvkDestroyPipelineCache(vk_core.device, g_pipeline_cache, NULL);\n}\n\nVkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char *name) {\n\tif ((size % 4 != 0) || (((uintptr_t)ptr & 3) != 0)) {\n\t\tgEngine.Con_Printf(S_ERROR \"Couldn't load shader %s: size %u or buf %p is not aligned to 4 bytes as required by SPIR-V/Vulkan spec\\n\", name, size, ptr);\n\t\treturn VK_NULL_HANDLE;\n\t}\n\n\tconst VkShaderModuleCreateInfo smci = {\n\t\t.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,\n\t\t.codeSize = size,\n\t\t.pCode = (const uint32_t*)(void*)ptr,\n\t};\n\n\tVkShaderModule module = VK_NULL_HANDLE;\n\tconst VkResult result = vkCreateShaderModule(vk_core.device, &smci, NULL, &module);\n\tif (result != VK_SUCCESS) {\n\t\tgEngine.Con_Printf(S_ERROR \"Couldn't load shader %s: error (%d): %s\\n\", name, result, R_VkResultName(result));\n\t\treturn VK_NULL_HANDLE;\n\t}\n\n\tSET_DEBUG_NAME(module, VK_OBJECT_TYPE_SHADER_MODULE, name);\n\treturn module;\n}\n\nstatic VkShaderModule R_VkShaderLoadFromFile(const char *filename) {\n\tfs_offset_t size = 0;\n\tbyte* const buf = gEngine.fsapi->LoadFile(filename, &size, false);\n\n\tif (!buf) {\n\t\tgEngine.Con_Printf( S_ERROR \"Cannot open shader file \\\"%s\\\"\\n\", filename);\n\t\treturn VK_NULL_HANDLE;\n\t}\n\n\tconst VkShaderModule module = R_VkShaderLoadFromMem(buf, size, filename);\n\nfinalize:\n\tMem_Free(buf);\n\treturn module;\n}\n\nvoid R_VkShaderDestroy(VkShaderModule module) {\n\tvkDestroyShaderModule(vk_core.device, module, NULL);\n}\n\nVkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *ci)\n{\n\tVkPipeline pipeline;\n\tVkVertexInputBindingDescription vibd = {\n\t\t.binding = 0,\n\t\t.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,\n\t\t.stride = ci->vertex_stride,\n\t};\n\n\tVkPipelineVertexInputStateCreateInfo vertex_input = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,\n\t\t.vertexBindingDescriptionCount = 1,\n\t\t.pVertexBindingDescriptions = &vibd,\n\t\t.vertexAttributeDescriptionCount = ci->num_attribs,\n\t\t.pVertexAttributeDescriptions = ci->attribs,\n\t};\n\n\tVkPipelineInputAssemblyStateCreateInfo input_assembly = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,\n\t\t.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,\n\t};\n\n\tVkPipelineViewportStateCreateInfo viewport_state = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,\n\t\t.viewportCount = 1,\n\t\t.scissorCount = 1,\n\t};\n\n\tVkPipelineRasterizationStateCreateInfo raster_state = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,\n\t\t.polygonMode = VK_POLYGON_MODE_FILL,\n\t\t.cullMode = ci->cullMode,\n\t\t.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,\n\t\t.lineWidth = 1.f,\n\t};\n\n\tVkPipelineMultisampleStateCreateInfo multi_state = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,\n\t\t.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,\n\t};\n\n\tVkPipelineColorBlendAttachmentState blend_attachment = {\n\t\t.blendEnable = ci->blendEnable,\n\t\t.srcColorBlendFactor = ci->srcColorBlendFactor,\n\t\t.dstColorBlendFactor = ci->dstColorBlendFactor,\n\t\t.colorBlendOp = ci->colorBlendOp,\n\t\t.srcAlphaBlendFactor = ci->srcAlphaBlendFactor,\n\t\t.dstAlphaBlendFactor = ci->dstAlphaBlendFactor,\n\t\t.alphaBlendOp = ci->alphaBlendOp,\n\t\t.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,\n\t};\n\n\tVkPipelineColorBlendStateCreateInfo color_blend = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,\n\t\t.attachmentCount = 1,\n\t\t.pAttachments = &blend_attachment,\n\t};\n\n\tVkPipelineDepthStencilStateCreateInfo depth = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,\n\t\t.depthTestEnable = ci->depthTestEnable,\n\t\t.depthWriteEnable = ci->depthWriteEnable,\n\t\t.depthCompareOp = ci->depthCompareOp,\n\t};\n\n\tVkDynamicState dynamic_states[] = {\n\t\tVK_DYNAMIC_STATE_VIEWPORT,\n\t\tVK_DYNAMIC_STATE_SCISSOR,\n\t};\n\n\tVkPipelineDynamicStateCreateInfo dynamic_state_create_info = {\n\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,\n\t\t.dynamicStateCount = ARRAYSIZE(dynamic_states),\n\t\t.pDynamicStates = dynamic_states,\n\t};\n\n\tVkPipelineShaderStageCreateInfo stage_create_infos[MAX_STAGES];\n\n\tVkGraphicsPipelineCreateInfo gpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,\n\t\t.stageCount = ci->num_stages,\n\t\t.pStages = stage_create_infos,\n\t\t.pVertexInputState = &vertex_input,\n\t\t.pInputAssemblyState = &input_assembly,\n\t\t.pViewportState = &viewport_state,\n\t\t.pRasterizationState = &raster_state,\n\t\t.pMultisampleState = &multi_state,\n\t\t.pColorBlendState = &color_blend,\n\t\t.pDepthStencilState = &depth,\n\t\t.layout = ci->layout,\n\t\t.renderPass = vk_frame.render_pass.raster,\n\t\t.pDynamicState = &dynamic_state_create_info,\n\t\t.subpass = 0,\n\t};\n\n\tif (ci->num_stages > MAX_STAGES)\n\t\treturn VK_NULL_HANDLE;\n\n\tVkShaderModule shaders[MAX_STAGES] = {VK_NULL_HANDLE};\n\n\tfor (int i = 0; i < ci->num_stages; ++i) {\n\t\tif (VK_NULL_HANDLE == (shaders[i] = R_VkShaderLoadFromFile(ci->stages[i].filename)))\n\t\t\tgoto finalize;\n\n\t\tstage_create_infos[i] = (VkPipelineShaderStageCreateInfo){\n\t\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,\n\t\t\t.stage = ci->stages[i].stage,\n\t\t\t.module = shaders[i],\n\t\t\t.pSpecializationInfo = ci->stages[i].specialization_info,\n\t\t\t.pName = \"main\",\n\t\t};\n\t}\n\n\tXVK_CHECK(vkCreateGraphicsPipelines(vk_core.device, g_pipeline_cache, 1, &gpci, NULL, &pipeline));\nfinalize:\n\tfor (int i = 0; i < ci->num_stages; ++i)\n\t\tR_VkShaderDestroy(shaders[i]);\n\n\treturn pipeline;\n}\n\nVkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci) {\n\tconst VkComputePipelineCreateInfo cpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,\n\t\t.layout = ci->layout,\n\t\t.stage = (VkPipelineShaderStageCreateInfo){\n\t\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,\n\t\t\t.stage = VK_SHADER_STAGE_COMPUTE_BIT,\n\t\t\t.module = ci->shader_module,\n\t\t\t.pName = \"main\",\n\t\t\t.pSpecializationInfo = ci->specialization_info,\n\t\t},\n\t};\n\n\tVkPipeline pipeline;\n\tXVK_CHECK(vkCreateComputePipelines(vk_core.device, VK_NULL_HANDLE, 1, &cpci, NULL, &pipeline));\n\n\treturn pipeline;\n}\n\nvk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_t *create) {\n#define MAX_SHADER_STAGES 16\n#define MAX_SHADER_GROUPS 16\n\tvk_pipeline_ray_t ret = {0};\n\tVkPipelineShaderStageCreateInfo stages[MAX_SHADER_STAGES];\n\tVkRayTracingShaderGroupCreateInfoKHR shader_groups[MAX_SHADER_GROUPS];\n\tconst int shader_groups_count = create->groups.hit_count + create->groups.miss_count + 1;\n\tint raygen_index = -1;\n\tint group_index = 0;\n\n\tconst VkRayTracingPipelineCreateInfoKHR rtpci = {\n\t\t.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR,\n\t\t//TODO .flags = VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR  ....\n\t\t.stageCount = create->stages_count,\n\t\t.pStages = stages,\n\t\t.groupCount = shader_groups_count,\n\t\t.pGroups = shader_groups,\n\t\t.maxPipelineRayRecursionDepth = 1,\n\t\t.layout = create->layout,\n\t};\n\n\tASSERT(shader_groups_count <= MAX_SHADER_GROUPS);\n\n\tif (create->stages_count > MAX_SHADER_STAGES) {\n\t\tgEngine.Con_Printf(S_ERROR \"Too many shader stages %d, max=%d\\n\", create->stages_count, MAX_SHADER_STAGES);\n\t\treturn ret;\n\t}\n\n\tfor (int i = 0; i < create->stages_count; ++i) {\n\t\tconst vk_shader_stage_t *const stage = create->stages + i;\n\n\t\t// FIXME going away from loading shaders directly\n\t\tASSERT(!stage->filename);\n\n\t\tif (stage->stage == VK_SHADER_STAGE_RAYGEN_BIT_KHR) {\n\t\t\tASSERT(raygen_index == -1);\n\t\t\traygen_index = i;\n\t\t}\n\n\t\tstages[i] = (VkPipelineShaderStageCreateInfo){\n\t\t\t.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,\n\t\t\t.stage = stage->stage,\n\t\t\t.module = stage->module,\n\t\t\t.pName = \"main\",\n\t\t\t.pSpecializationInfo = stage->specialization_info,\n\t\t};\n\t}\n\n\tASSERT(raygen_index >= 0);\n\n\tshader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) {\n\t\t.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,\n\t\t.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,\n\t\t.anyHitShader = VK_SHADER_UNUSED_KHR,\n\t\t.closestHitShader = VK_SHADER_UNUSED_KHR,\n\t\t.generalShader = raygen_index,\n\t\t.intersectionShader = VK_SHADER_UNUSED_KHR,\n\t};\n\n\tfor (int i = 0; i < create->groups.miss_count; ++i) {\n\t\tconst int miss_index = create->groups.miss[i];\n\n\t\tASSERT(miss_index >= 0);\n\t\tASSERT(miss_index < create->stages_count);\n\t\tASSERT(create->stages[miss_index].stage == VK_SHADER_STAGE_MISS_BIT_KHR);\n\n\t\tshader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,\n\t\t\t.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,\n\t\t\t.anyHitShader = VK_SHADER_UNUSED_KHR,\n\t\t\t.closestHitShader = VK_SHADER_UNUSED_KHR,\n\t\t\t.generalShader = miss_index,\n\t\t\t.intersectionShader = VK_SHADER_UNUSED_KHR,\n\t\t};\n\t}\n\n\tfor (int i = 0; i < create->groups.hit_count; ++i) {\n\t\tconst vk_pipeline_ray_hit_group_t *const groups = create->groups.hit + i;\n\t\tconst int closest_index = groups->closest >= 0 ? groups->closest : VK_SHADER_UNUSED_KHR;\n\t\tconst int any_index = groups->any >= 0 ? groups->any : VK_SHADER_UNUSED_KHR;\n\n\t\tif (closest_index != VK_SHADER_UNUSED_KHR) {\n\t\t\tASSERT(closest_index < create->stages_count);\n\t\t\tASSERT(create->stages[closest_index].stage == VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR);\n\t\t}\n\n\t\tif (any_index != VK_SHADER_UNUSED_KHR) {\n\t\t\tASSERT(any_index < create->stages_count);\n\t\t\tASSERT(create->stages[any_index].stage == VK_SHADER_STAGE_ANY_HIT_BIT_KHR);\n\t\t}\n\n\t\tshader_groups[group_index++] = (VkRayTracingShaderGroupCreateInfoKHR) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,\n\t\t\t.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR,\n\t\t\t.anyHitShader = any_index,\n\t\t\t.closestHitShader = closest_index,\n\t\t\t.generalShader = VK_SHADER_UNUSED_KHR,\n\t\t\t.intersectionShader = VK_SHADER_UNUSED_KHR,\n\t\t};\n\t}\n\n\tXVK_CHECK(vkCreateRayTracingPipelinesKHR(vk_core.device, VK_NULL_HANDLE, g_pipeline_cache, 1, &rtpci, NULL, &ret.pipeline));\n\n\tif (ret.pipeline == VK_NULL_HANDLE)\n\t\treturn ret;\n\n\t// TODO: do not allocate sbt buffer per pipeline. make a central buffer and use that\n\t// TODO: does it really need to be host-visible?\n\t{\n\t\tchar buf[64];\n\t\tQ_snprintf(buf, sizeof(buf), \"%s sbt\", create->debug_name);\n\t\tif (!VK_BufferCreate(buf, &ret.sbt_buffer, shader_groups_count * v_device_info.sbt_record_size,\n\t\t\t\tVK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR,\n\t\t\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))\n\t\t{\n\t\t\tvkDestroyPipeline(vk_core.device, ret.pipeline, NULL);\n\t\t\tret.pipeline = VK_NULL_HANDLE;\n\t\t\treturn ret;\n\t\t}\n\t}\n\n\t{\n\t\tconst uint32_t sbt_handle_size = v_device_info.properties_ray_tracing_pipeline.shaderGroupHandleSize;\n\t\tconst uint32_t sbt_handles_buffer_size = shader_groups_count * sbt_handle_size;\n\t\tuint8_t *sbt_handles = Mem_Malloc(vk_core.pool, sbt_handles_buffer_size);\n\t\tXVK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(vk_core.device, ret.pipeline, 0, shader_groups_count, sbt_handles_buffer_size, sbt_handles));\n\t\tfor (int i = 0; i < shader_groups_count; ++i)\n\t\t{\n\t\t\tuint8_t *sbt_dst = ret.sbt_buffer.mapped;\n\t\t\tmemcpy(sbt_dst + v_device_info.sbt_record_size * i, sbt_handles + sbt_handle_size * i, sbt_handle_size);\n\t\t}\n\t\tMem_Free(sbt_handles);\n\t}\n\n\t{\n\t\tconst VkDeviceAddress sbt_addr = R_VkBufferGetDeviceAddress(ret.sbt_buffer.buffer);\n\t\tconst uint32_t sbt_record_size = v_device_info.sbt_record_size;\n\t\tuint32_t index = 0;\n\n#define SBT_INDEX(count) (VkStridedDeviceAddressRegionKHR){ \\\n\t\t.deviceAddress = sbt_addr + sbt_record_size * index, \\\n\t\t.size = sbt_record_size * (count), \\\n\t\t.stride = sbt_record_size, \\\n\t}; index += count\n\t\tret.sbt.raygen = SBT_INDEX(1);\n\t\tret.sbt.miss = SBT_INDEX(create->groups.miss_count);\n\t\tret.sbt.hit = SBT_INDEX(create->groups.hit_count);\n\t\tret.sbt.callable = (VkStridedDeviceAddressRegionKHR){ 0 };\n\t}\n\n\tQ_strncpy(ret.debug_name, create->debug_name, sizeof(ret.debug_name));\n\n\treturn ret;\n}\n\nvoid VK_PipelineRayTracingDestroy(vk_pipeline_ray_t* pipeline) {\n\tvkDestroyPipeline(vk_core.device, pipeline->pipeline, NULL);\n\tVK_BufferDestroy(&pipeline->sbt_buffer);\n\tpipeline->pipeline = VK_NULL_HANDLE;\n}\n\nvoid VK_PipelineRayTracingTrace(vk_combuf_t *combuf, const vk_pipeline_ray_t *pipeline, uint32_t width, uint32_t height, int scope_id) {\n\t\t// TODO bind this and accepts descriptors as args? vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline);\n\t\tconst int begin_id = R_VkCombufScopeBegin(combuf, scope_id, VCombufScopeFlag_PerfQuery);\n\t\tvkCmdTraceRaysKHR(combuf->cmdbuf, &pipeline->sbt.raygen, &pipeline->sbt.miss, &pipeline->sbt.hit, &pipeline->sbt.callable, width, height, 1 );\n\t\tR_VkCombufScopeEnd(combuf, begin_id, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VPipeline.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n#include \"VBuffer.h\"\n\nVkShaderModule R_VkShaderLoadFromMem(const void *ptr, uint32_t size, const char *name);\nvoid R_VkShaderDestroy(VkShaderModule module);\n\ntypedef struct {\n\tVkShaderModule module;\n\tconst char *filename;\n\tVkShaderStageFlagBits stage;\n\tconst VkSpecializationInfo *specialization_info;\n} vk_shader_stage_t;\n\ntypedef struct {\n\tVkPipelineLayout layout;\n\tconst VkVertexInputAttributeDescription *attribs;\n\tuint32_t num_attribs;\n\n\tconst vk_shader_stage_t *stages;\n\tuint32_t num_stages;\n\n\tuint32_t vertex_stride;\n\n  VkBool32 depthTestEnable;\n  VkBool32 depthWriteEnable;\n  VkCompareOp depthCompareOp;\n\n  VkBool32                 blendEnable;\n  VkBlendFactor            srcColorBlendFactor;\n  VkBlendFactor            dstColorBlendFactor;\n  VkBlendOp                colorBlendOp;\n  VkBlendFactor            srcAlphaBlendFactor;\n  VkBlendFactor            dstAlphaBlendFactor;\n  VkBlendOp                alphaBlendOp;\n\n  VkCullModeFlags cullMode;\n} vk_pipeline_graphics_create_info_t;\n\nVkPipeline VK_PipelineGraphicsCreate(const vk_pipeline_graphics_create_info_t *ci);\n\ntypedef struct {\n\tVkPipelineLayout layout;\n\tVkShaderModule shader_module;\n\tconst VkSpecializationInfo *specialization_info;\n} vk_pipeline_compute_create_info_t;\n\nVkPipeline VK_PipelineComputeCreate(const vk_pipeline_compute_create_info_t *ci);\n\ntypedef struct {\n\tint closest;\n\tint any;\n} vk_pipeline_ray_hit_group_t;\n\ntypedef struct {\n\tconst char *debug_name;\n\tVkPipelineLayout layout;\n\n\t// FIXME make this pointer to shader modules, add int raygen_index\n\tconst vk_shader_stage_t *stages;\n\tint stages_count;\n\n\tstruct {\n\t\tconst int *miss;\n\t\tint miss_count;\n\n\t\tconst vk_pipeline_ray_hit_group_t *hit;\n\t\tint hit_count;\n\t} groups;\n} vk_pipeline_ray_create_info_t;\n\ntypedef struct {\n\tVkPipeline pipeline;\n\tvk_buffer_t sbt_buffer; // TODO suballocate this from a single central buffer or something\n\tstruct {\n\t\tVkStridedDeviceAddressRegionKHR raygen, miss, hit, callable;\n\t} sbt;\n\tchar debug_name[32];\n} vk_pipeline_ray_t;\n\nvk_pipeline_ray_t VK_PipelineRayTracingCreate(const vk_pipeline_ray_create_info_t *create);\nstruct vk_combuf_s;\nvoid VK_PipelineRayTracingTrace(struct vk_combuf_s *combuf, const vk_pipeline_ray_t *pipeline, uint32_t width, uint32_t height, int scope_id);\nvoid VK_PipelineRayTracingDestroy(vk_pipeline_ray_t* pipeline);\n\n\nqboolean VK_PipelineInit( void );\nvoid VK_PipelineShutdown( void );\n\nextern VkPipelineCache g_pipeline_cache;\n"
  },
  {
    "path": "ref/vk/vulkan/VRayAccel.c",
    "content": "#include \"vk_ray_accel.h\"\n\n#include \"shaders/ray_interop.h\" // ModelHeader, ...\n\n#include \"vk_core.h\"\n#include \"vk_rtx.h\"\n#include \"vk_ray_internal.h\"\n#include \"r_speeds.h\"\n#include \"VCombuf.h\"\n#include \"VBarrier.h\"\n#include \"vk_math.h\"\n#include \"vk_geometry.h\"\n#include \"vk_render.h\"\n#include \"vk_logs.h\"\n#include \"vulkan/VResource.h\"\n\n#include \"std/arrays.h\"\n#include \"std/profiler.h\"\n\n#include \"xash3d_mathlib.h\"\n\n#define MODULE_NAME \"accel\"\n#define LOG_MODULE rt\n\n#define MAX_SCRATCH_BUFFER (128*1024*1024)\n// FIXME compute this by lazily allocating #define MAX_ACCELS_BUFFER (128*1024*1024)\n#define MAX_ACCELS_BUFFER (512*1024*1024)\n\ntypedef struct rt_blas_s {\n\tconst char *debug_name;\n\trt_blas_usage_e usage;\n\n\tVkAccelerationStructureKHR blas;\n\n\t// Zero if not built\n\tVkDeviceAddress address;\n\n\t// Max dynamic geoms for usage == kBlasBuildDynamicFast\n\tint max_geoms;\n\n\tstruct {\n\t\tVkAccelerationStructureBuildSizesInfoKHR sizes;\n\t\tVkAccelerationStructureBuildGeometryInfoKHR info;\n\t\tVkAccelerationStructureGeometryKHR *geoms;\n\t\tuint32_t *max_prim_counts;\n\t\tVkAccelerationStructureBuildRangeInfoKHR *ranges;\n\n\t\tqboolean is_built, needs_to_be_built;\n\t} build;\n} rt_blas_t;\n\nstatic struct {\n\t// Model header\n\t// Array of struct ModelHeader: color, material_mode, prev_transform\n\tvk_buffer_t model_headers_buffer;\n\n\t// Stores AS built data. Lifetime similar to render buffer:\n\t// - some portion lives for entire map lifetime\n\t// - some portion lives only for a single frame (may have several frames in flight)\n\t// TODO: unify this with render buffer -- really?\n\t// Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT\n\tvk_buffer_t accels_buffer;\n\tVkDeviceAddress accels_buffer_addr;\n\tstruct alo_pool_s *accels_buffer_alloc;\n\n\t// Temp: lives only during a single frame (may have many in flight)\n\t// Used for building ASes;\n\t// Needs: AS_STORAGE_BIT, SHADER_DEVICE_ADDRESS_BIT\n\tvk_buffer_t scratch_buffer;\n\tVkDeviceAddress scratch_buffer_addr;\n\n\t// Temp-ish: used for making TLAS, contains addressed to all used BLASes\n\t// Lifetime and nature of usage similar to scratch_buffer\n\t// TODO: unify them\n\t// Needs: SHADER_DEVICE_ADDRESS, STORAGE_BUFFER, AS_BUILD_INPUT_READ_ONLY\n\tvk_buffer_t tlas_geom_buffer;\n\tVkDeviceAddress tlas_geom_buffer_addr;\n\tr_flipping_buffer_t tlas_geom_buffer_alloc;\n\n\tstruct {\n\t\trt_resource_t resource;\n\t\tVkAccelerationStructureKHR handle;\n\n\t\tVkAccelerationStructureGeometryKHR geometry;\n\t\tuint32_t max_prim_count;\n\t\tVkAccelerationStructureBuildRangeInfoKHR range_info;\n\t\tVkAccelerationStructureBuildGeometryInfoKHR geometry_info;\n\t\tVkAccelerationStructureBuildSizesInfoKHR sizes_info;\n\t} tlas;\n\n\t// Produces TLAS and model_headers\n\tProducer producer;\n\n\t// Per-frame data that is accumulated between RayFrameBegin and End calls\n\tstruct {\n\t\tBOUNDED_ARRAY_DECLARE(rt_draw_instance_t, instances, MAX_INSTANCES);\n\n\t\tuint32_t scratch_offset; // for building dynamic blases\n\t} frame;\n\n\tstruct {\n\t\tint instances_count;\n\t\tint accels_built;\n\t} stats;\n\n\tstruct {\n\t\tBOUNDED_ARRAY_DECLARE(VkAccelerationStructureBuildGeometryInfoKHR, geometry_infos, MAX_INSTANCES);\n\t\tBOUNDED_ARRAY_DECLARE(VkAccelerationStructureBuildRangeInfoKHR*, range_infos, MAX_INSTANCES);\n\t} build;\n\n\tcvar_t *cv_force_culling;\n} g_accel;\n\nstatic VkAccelerationStructureBuildSizesInfoKHR getAccelSizes(const VkAccelerationStructureBuildGeometryInfoKHR *build_info, const uint32_t *max_prim_counts) {\n\tVkAccelerationStructureBuildSizesInfoKHR build_size = {\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR\n\t};\n\n\tvkGetAccelerationStructureBuildSizesKHR(\n\t\tvk_core.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, build_info, max_prim_counts, &build_size);\n\n\treturn build_size;\n}\n\nstatic VkAccelerationStructureKHR createAccel(const char *name, VkAccelerationStructureTypeKHR type, uint32_t size) {\n\tconst alo_block_t block = aloPoolAllocate(g_accel.accels_buffer_alloc, size, /*TODO why? align=*/256);\n\n\tif (block.offset == ALO_ALLOC_FAILED) {\n\t\tERR(\"Failed to allocate %u bytes for blas \\\"%s\\\"\", size, name);\n\t\treturn VK_NULL_HANDLE;\n\t}\n\n\tconst VkAccelerationStructureCreateInfoKHR asci = {\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,\n\t\t.buffer = g_accel.accels_buffer.buffer,\n\t\t.offset = block.offset,\n\t\t.type = type,\n\t\t.size = size,\n\t};\n\n\tVkAccelerationStructureKHR accel = VK_NULL_HANDLE;\n\tXVK_CHECK(vkCreateAccelerationStructureKHR(vk_core.device, &asci, NULL, &accel));\n\tSET_DEBUG_NAME(accel, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, name);\n\treturn accel;\n}\n\nstatic VkDeviceAddress getAccelAddress(VkAccelerationStructureKHR as) {\n\tVkAccelerationStructureDeviceAddressInfoKHR asdai = {\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR,\n\t\t.accelerationStructure = as,\n\t};\n\treturn vkGetAccelerationStructureDeviceAddressKHR(vk_core.device, &asdai);\n}\n\nstatic void tlasCreate(void) {\n\tg_accel.tlas.geometry = (VkAccelerationStructureGeometryKHR) {\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,\n\t\t.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR,\n\t\t.geometry.instances =\n\t\t\t(VkAccelerationStructureGeometryInstancesDataKHR){\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR,\n\t\t\t\t.data.deviceAddress = 0,\n\t\t\t\t.arrayOfPointers = VK_FALSE,\n\t\t\t},\n\t};\n\tg_accel.tlas.max_prim_count = MAX_INSTANCES;\n\tg_accel.tlas.range_info = (VkAccelerationStructureBuildRangeInfoKHR) {\n\t\t.primitiveCount = g_accel.frame.instances.count,\n\t};\n\tg_accel.tlas.geometry_info = (VkAccelerationStructureBuildGeometryInfoKHR) {\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,\n\t\t.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR,\n\t\t.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR,\n\t\t.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,\n\t\t.geometryCount = 1,\n\t\t.pGeometries = &g_accel.tlas.geometry,\n\t\t.srcAccelerationStructure = VK_NULL_HANDLE,\n\t};\n\tg_accel.tlas.sizes_info = getAccelSizes(&g_accel.tlas.geometry_info, &g_accel.tlas.max_prim_count);\n\tg_accel.tlas.handle = createAccel(\"TLAS\", g_accel.tlas.geometry_info.type, g_accel.tlas.sizes_info.accelerationStructureSize);\n\tASSERT(g_accel.tlas.handle != VK_NULL_HANDLE);\n\tg_accel.tlas.geometry_info.dstAccelerationStructure = g_accel.tlas.handle;\n}\n\nstatic void tlasBuild(vk_combuf_t *combuf, VkDeviceAddress instances_addr) {\n\n\tconst uint32_t scratch_buffer_size = g_accel.tlas.sizes_info.buildScratchSize;\n\n\t//gEngine.Con_Reportf(\"sratch offset = %d, req=%d\", g_accel.frame.scratch_offset, scratch_buffer_size);\n\n\tif (MAX_SCRATCH_BUFFER < g_accel.frame.scratch_offset + scratch_buffer_size) {\n\t\tERR(\"Scratch buffer overflow: left %u bytes, but need %u\",\n\t\t\tMAX_SCRATCH_BUFFER - g_accel.frame.scratch_offset,\n\t\t\tscratch_buffer_size);\n\t\tASSERT(!\"Scratch buffer overflow\");\n\t}\n\n\tg_accel.tlas.geometry.geometry.instances.data.deviceAddress = instances_addr;\n\tg_accel.tlas.range_info.primitiveCount = g_accel.frame.instances.count;\n\tg_accel.tlas.geometry_info.scratchData.deviceAddress = g_accel.scratch_buffer_addr + g_accel.frame.scratch_offset;\n\n\t//uint32_t scratch_offset_initial = g_accel.frame.scratch_offset;\n\tg_accel.frame.scratch_offset += scratch_buffer_size;\n\tg_accel.frame.scratch_offset = ALIGN_UP(g_accel.frame.scratch_offset, v_device_info.properties_accel.minAccelerationStructureScratchOffsetAlignment);\n\n\t//gEngine.Con_Reportf(\"AS=%p, n_geoms=%u, scratch: %#x %d %#x\", *args->p_accel, args->n_geoms, scratch_offset_initial, scratch_buffer_size, scratch_offset_initial + scratch_buffer_size);\n\n\tR_VkBufferStagingCommit(&g_accel.tlas_geom_buffer, combuf);\n\n\t{\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = &g_accel.accels_buffer,\n\t\t\t.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR, // TODO? WRITE? we're writing tlas here too\n\t\t});\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = &g_accel.tlas_geom_buffer,\n\t\t\t.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR,\n\t\t});\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = &g_accel.scratch_buffer,\n\t\t\t.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\tstatic int scope_id = -2;\n\tif (scope_id == -2)\n\t\tscope_id = R_VkGpuScope_Register(\"build_tlas\");\n\tconst int begin_index = R_VkCombufScopeBegin(combuf, scope_id, VCombufScopeFlag_PerfQuery);\n\tconst VkAccelerationStructureBuildRangeInfoKHR *p_build_ranges = &g_accel.tlas.range_info;\n\tvkCmdBuildAccelerationStructuresKHR(combuf->cmdbuf, 1, &g_accel.tlas.geometry_info, &p_build_ranges);\n\tR_VkCombufScopeEnd(combuf, begin_index, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);\n}\n\nstatic qboolean blasPrepareBuild(struct rt_blas_s *blas, VkDeviceAddress geometry_addr) {\n\tASSERT(blas);\n\tASSERT(blas->blas);\n\n\tif (blas->build.is_built && blas->usage == kBlasBuildStatic) {\n\t\tASSERT(!\"Attempting to build static BLAS twice\");\n\t\treturn false;\n\t}\n\n\tfor (int i = 0; i < blas->build.info.geometryCount; ++i) {\n\t\tVkAccelerationStructureGeometryKHR *const geom = blas->build.geoms + i;\n\t\tgeom->geometry.triangles.vertexData.deviceAddress = geometry_addr;\n\t\tgeom->geometry.triangles.indexData.deviceAddress = geometry_addr;\n\t}\n\n\tconst qboolean is_update = blas->build.info.mode == VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR;\n\tconst uint32_t scratch_size = is_update ? blas->build.sizes.updateScratchSize : blas->build.sizes.buildScratchSize;\n\n\tif (MAX_SCRATCH_BUFFER < g_accel.frame.scratch_offset + scratch_size) {\n\t\tERR(\"Scratch buffer overflow: left %u bytes, but need %u\",\n\t\t\tMAX_SCRATCH_BUFFER - g_accel.frame.scratch_offset, scratch_size);\n\t\t// TODO handle this somehow ?!\n\t\tASSERT(!\"Ran out of scratch buffer size\");\n\t\treturn false;\n\t}\n\n\tblas->build.info.scratchData.deviceAddress = g_accel.scratch_buffer_addr + g_accel.frame.scratch_offset;\n\n\t//uint32_t scratch_offset_initial = g_accel.frame.scratch_offset;\n\tg_accel.frame.scratch_offset += scratch_size;\n\tg_accel.frame.scratch_offset = ALIGN_UP(g_accel.frame.scratch_offset, v_device_info.properties_accel.minAccelerationStructureScratchOffsetAlignment);\n\n\t//gEngine.Con_Reportf(\"AS=%p, n_geoms=%u, scratch: %#x %d %#x\", *args->p_accel, args->n_geoms, scratch_offset_initial, scratch_buffer_size, scratch_offset_initial + scratch_buffer_size);\n\n\treturn true;\n}\n\nstatic void blasBuildEnqueue(rt_blas_t* blas, VkDeviceAddress geometry_buffer_adderss) {\n\t// If all sequences match, no rebuild is needed\n\tif (!blas->build.needs_to_be_built)\n\t\treturn;\n\n\t// FIXME handle: at the very least we could just ignore this BLAS for this frame\n\tASSERT(blasPrepareBuild(blas, geometry_buffer_adderss));\n\n\t// Mark as built, and also store address for future use\n\tblas->build.is_built = true;\n\tblas->build.needs_to_be_built = false;\n\n\tBOUNDED_ARRAY_APPEND_ITEM(g_accel.build.geometry_infos, blas->build.info);\n\tBOUNDED_ARRAY_APPEND_ITEM(g_accel.build.range_infos, blas->build.ranges);\n\tASSERT(g_accel.build.geometry_infos.count == g_accel.build.range_infos.count);\n}\n\nstatic void blasBuildPerform(vk_combuf_t *combuf, vk_resource_buffer_t *geometry) {\n\t{\n\t\tBarrier barrier = barrierMake(VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = &g_accel.accels_buffer,\n\t\t\t.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,\n\t\t});\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = geometry->buffer,\n\t\t\t.access = VK_ACCESS_2_SHADER_READ_BIT,\n\t\t});\n\t\tbarrierAddBuffer(&barrier, (r_vkcombuf_barrier_buffer_t) {\n\t\t\t.buffer = &g_accel.scratch_buffer,\n\t\t\t.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,\n\t\t});\n\t\tbarrierCommit(&barrier, combuf);\n\t}\n\n\tASSERT(g_accel.build.geometry_infos.count == g_accel.build.range_infos.count);\n\tconst uint32_t count = g_accel.build.geometry_infos.count;\n\tif (count == 0)\n\t\treturn; // Nothing to build\n\n\tstatic int scope_id = -2;\n\tif (scope_id == -2)\n\t\tscope_id = R_VkGpuScope_Register(\"build_blases\");\n\n\tconst int begin_index = R_VkCombufScopeBegin(combuf, scope_id, VCombufScopeFlag_PerfQuery);\n\tvkCmdBuildAccelerationStructuresKHR(combuf->cmdbuf, count,\n\t\tg_accel.build.geometry_infos.items,\n\t\t(const VkAccelerationStructureBuildRangeInfoKHR* const *)g_accel.build.range_infos.items);\n\n\tR_VkCombufScopeEnd(combuf, begin_index, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);\n\n\tg_accel.stats.accels_built = count;\n\tg_accel.build.geometry_infos.count = 0;\n\tg_accel.build.range_infos.count = 0;\n}\n\nstatic uint32_t processEnqueuedInstances(vk_combuf_t *combuf, VkDeviceAddress geometry_buffer_address) {\n\tconst uint32_t instances_count = g_accel.frame.instances.count;\n\tASSERT(instances_count > 0);\n\n\tconst uint32_t instance_offset = R_FlippingBuffer_Alloc(&g_accel.tlas_geom_buffer_alloc, instances_count, 1);\n\tASSERT(instance_offset != ALO_ALLOC_FAILED);\n\n\tconst vk_buffer_locked_t headers_lock = R_VkBufferLock(&g_accel.model_headers_buffer,\n\t\t(vk_buffer_lock_t){\n\t\t\t.offset = 0,\n\t\t\t.size = instances_count * sizeof(struct ModelHeader),\n\t});\n\n\tASSERT(headers_lock.ptr);\n\n\tVkAccelerationStructureInstanceKHR* inst = ((VkAccelerationStructureInstanceKHR*)g_accel.tlas_geom_buffer.mapped) + instance_offset;\n\tfor (uint32_t i = 0; i < instances_count; ++i) {\n\t\tconst rt_draw_instance_t* const instance = g_accel.frame.instances.items + i;\n\n\t\tblasBuildEnqueue(instance->blas, geometry_buffer_address);\n\n\t\tASSERT(instance->blas->address != 0);\n\t\tinst[i] = (VkAccelerationStructureInstanceKHR){\n\t\t\t.instanceCustomIndex = instance->kusochki_offset,\n\t\t\t.instanceShaderBindingTableRecordOffset = 0,\n\t\t\t.accelerationStructureReference = instance->blas->address,\n\t\t};\n\n\t\tconst VkGeometryInstanceFlagsKHR flags =\n\t\t\t(instance->material_flags & kMaterialFlag_CullBackFace_Bit) || g_accel.cv_force_culling->value\n\t\t\t? 0\n\t\t\t: VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;\n\n\t\tswitch (instance->material_mode) {\n\t\t\tcase MATERIAL_MODE_OPAQUE:\n\t\t\t\tinst[i].mask = GEOMETRY_BIT_OPAQUE;\n\t\t\t\tif (!(instance->material_flags & kMaterialFlag_DontCastShadow_Bit))\n\t\t\t\t\tinst[i].mask |= GEOMETRY_BIT_CASTS_SHADOW;\n\t\t\t\tinst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR,\n\t\t\t\t// Force no-culling because there are cases where culling leads to leaking shadows, holes in reflections, etc\n\t\t\t\t// CULL_DISABLE_BIT disables culling even if the gl_RayFlagsCullFrontFacingTrianglesEXT bit is set in shaders\n\t\t\t\tinst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | flags;\n\t\t\t\tbreak;\n\t\t\tcase MATERIAL_MODE_OPAQUE_ALPHA_TEST:\n\t\t\t\tinst[i].mask = GEOMETRY_BIT_ALPHA_TEST;\n\t\t\t\tinst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ALPHA_TEST,\n\t\t\t\tinst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; // Alpha test always culls\n\t\t\t\tbreak;\n\t\t\tcase MATERIAL_MODE_TRANSLUCENT:\n\t\t\t\tinst[i].mask = GEOMETRY_BIT_REFRACTIVE;\n\t\t\t\tinst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR,\n\t\t\t\t// Disable culling for translucent surfaces: decide what side it is based on normal wrt ray directions\n\t\t\t\tinst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR | flags;\n\t\t\t\tbreak;\n\t\t\tcase MATERIAL_MODE_BLEND_ADD:\n\t\t\tcase MATERIAL_MODE_BLEND_MIX:\n\t\t\tcase MATERIAL_MODE_BLEND_GLOW:\n\t\t\tcase MATERIAL_MODE_DECAL:\n\t\t\t\tinst[i].mask = GEOMETRY_BIT_BLEND;\n\t\t\t\tinst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_ADDITIVE,\n\t\t\t\t// Force no-culling because these should be visible from any angle\n\t\t\t\tinst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR | flags;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tgEngine.Host_Error(\"Unexpected material mode %d\\n\", instance->material_mode);\n\t\t\t\tbreak;\n\t\t}\n\t\tmemcpy(&inst[i].transform, instance->transform_row, sizeof(VkTransformMatrixKHR));\n\n\t\tstruct ModelHeader *const header = ((struct ModelHeader*)headers_lock.ptr) + i;\n\t\theader->mode = instance->material_mode;\n\t\tVector4Copy(instance->color, header->color);\n\t\tMatrix4x4_ToArrayFloatGL(instance->prev_transform_row, (float*)header->prev_transform);\n\t}\n\n\tR_VkBufferUnlock(headers_lock);\n\tR_VkBufferStagingCommit(&g_accel.model_headers_buffer, combuf);\n\n\tg_accel.stats.instances_count = instances_count;\n\n\treturn instance_offset;\n}\n\nstatic void produceTlasAndModelHeaders(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\tAPROF_SCOPE_DECLARE_BEGIN(prepare, __FUNCTION__);\n\tDEBUG_BEGIN(combuf->cmdbuf, \"produceTlas\");\n\n\tASSERT(p == &g_accel.producer);\n\n\t// Feed tlas with dynamic data\n\tRT_DynamicModelProcessFrame();\n\n\tR_FlippingBuffer_Flip( &g_accel.tlas_geom_buffer_alloc );\n\n\tvk_resource_buffer_t *const geometry = (void*)R_VkResourceFindByName(\"geometry\");\n\t// TODO vk_buffer_t addr field\n\tconst VkDeviceAddress geometry_buffer_address = R_VkBufferGetDeviceAddress(geometry->buffer->buffer);\n\tR_VkResourceProduce(&geometry->header, combuf, ctx);\n\n\t// Upload all blas instances references to GPU mem\n\tconst uint32_t instance_offset = processEnqueuedInstances(combuf, geometry_buffer_address);\n\n\t// Build all scheduled BLASes\n\tblasBuildPerform(combuf, geometry);\n\n\t// 2. Build TLAS\n\ttlasBuild(combuf, g_accel.tlas_geom_buffer_addr + instance_offset * sizeof(VkAccelerationStructureInstanceKHR));\n\tDEBUG_END(combuf->cmdbuf);\n\n\t// Consume instances into this frame, no further instances are expected\n\tg_accel.frame.instances.count = 0;\n\tg_accel.frame.scratch_offset = 0;\n\n\tAPROF_SCOPE_END(prepare);\n}\n\nstatic vk_descriptor_value_t acquireTlasDescriptor(struct rt_resource_s* res, vk_resource_acquire_descriptor_args_t args) {\n\t(void)args;\n\n\t// TODO barrier\n\n\treturn (vk_descriptor_value_t){\n\t\t.accel = (VkWriteDescriptorSetAccelerationStructureKHR) {\n\t\t\t.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,\n\t\t\t.accelerationStructureCount = 1,\n\t\t\t.pAccelerationStructures = &g_accel.tlas.handle,\n\t\t\t.pNext = NULL,\n\t\t},\n\t};\n}\n\n// TODO move to rt_model.c (s/vk_ray_model/rt_model)\nstatic qboolean modelHeadersCreate(void) {\n\tif (!VK_BufferCreate(\"model headers\", &g_accel.model_headers_buffer, sizeof(struct ModelHeader) * MAX_INSTANCES,\n\t\tVK_BUFFER_USAGE_STORAGE_BUFFER_BIT  | VK_BUFFER_USAGE_TRANSFER_DST_BIT,\n\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {\n\t\t// FIXME complain, handle\n\t\treturn false;\n\t}\n\n\tR_VkBufferRegisterAsResource((r_vkbuffer_register_as_resource_t){\n\t\t.name = \"model_headers\",\n\t\t.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,\n\t\t.buffer = &g_accel.model_headers_buffer,\n\t\t.offset = 0,\n\t\t.size = g_accel.model_headers_buffer.size,\n\t\t.producer = &g_accel.producer,\n\t});\n\n\treturn true;\n}\n\nqboolean RT_VkAccelInit(void) {\n\tg_accel.producer = (Producer) {\n\t\t.name = \"tlas\",\n\t\t.produce = produceTlasAndModelHeaders,\n\t};\n\n\tif (!modelHeadersCreate()) {\n\t\t// TODO cleanup\n\t\treturn false;\n\t}\n\n\tif (!VK_BufferCreate(\"ray accels_buffer\", &g_accel.accels_buffer, MAX_ACCELS_BUFFER,\n\t\t\tVK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,\n\t\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT\n\t\t))\n\t{\n\t\treturn false;\n\t}\n\tg_accel.accels_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.accels_buffer.buffer);\n\n\tif (!VK_BufferCreate(\"ray scratch_buffer\", &g_accel.scratch_buffer, MAX_SCRATCH_BUFFER,\n\t\t\tVK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,\n\t\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT\n\t\t)) {\n\t\treturn false;\n\t}\n\tg_accel.scratch_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.scratch_buffer.buffer);\n\n\t// TODO this doesn't really need to be host visible, use staging\n\tif (!VK_BufferCreate(\"ray tlas_geom_buffer\", &g_accel.tlas_geom_buffer, sizeof(VkAccelerationStructureInstanceKHR) * MAX_INSTANCES * 2,\n\t\t\tVK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |\n\t\t\tVK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,\n\t\t\tVK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {\n\t\t// FIXME complain, handle\n\t\treturn false;\n\t}\n\tg_accel.tlas_geom_buffer_addr = R_VkBufferGetDeviceAddress(g_accel.tlas_geom_buffer.buffer);\n\tR_FlippingBuffer_Init(&g_accel.tlas_geom_buffer_alloc, MAX_INSTANCES * 2);\n\n\tg_accel.accels_buffer_alloc = aloPoolCreate(MAX_ACCELS_BUFFER, MAX_INSTANCES, /* why */ 256);\n\n\tR_SPEEDS_COUNTER(g_accel.stats.instances_count, \"instances\", kSpeedsMetricCount);\n\tR_SPEEDS_COUNTER(g_accel.stats.accels_built, \"built\", kSpeedsMetricCount);\n\n\tg_accel.cv_force_culling = gEngine.Cvar_Get(\"rt_debug_force_backface_culling\", \"0\", FCVAR_GLCONFIG | FCVAR_CHEAT, \"Force backface culling for testing\");\n\n\t{\n\t\tg_accel.tlas.resource = (rt_resource_t) {\n\t\t\t.name = \"tlas\",\n\t\t\t.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,\n\t\t\t.producer = &g_accel.producer,\n\t\t\t.acquire_descriptor = acquireTlasDescriptor,\n\t\t\t.refcount = 1,\n\t\t};\n\n\t\tASSERT(R_VkResourceRegister(&g_accel.tlas.resource));\n\t}\n\n\treturn true;\n}\n\nvoid RT_VkAccelShutdown(void) {\n\tif (g_accel.tlas.handle != VK_NULL_HANDLE)\n\t\tvkDestroyAccelerationStructureKHR(vk_core.device, g_accel.tlas.handle, NULL);\n\n\tVK_BufferDestroy(&g_accel.scratch_buffer);\n\tVK_BufferDestroy(&g_accel.accels_buffer);\n\tVK_BufferDestroy(&g_accel.tlas_geom_buffer);\n\n\tif (g_accel.accels_buffer_alloc)\n\t\taloPoolDestroy(g_accel.accels_buffer_alloc);\n\n\tVK_BufferDestroy(&g_accel.model_headers_buffer);\n}\n\nvoid RT_VkAccelNewMap(void) {\n\tconst int expected_accels = 512; // TODO actually get this from playing the game\n\tconst int accels_alignment = 256; // TODO where does this come from?\n\tASSERT(vk_core.rtx);\n\n\tg_accel.frame.scratch_offset = 0;\n\n\t// FIXME this clears up memory before its users are deallocated (e.g. dynamic models BLASes)\n\tif (g_accel.accels_buffer_alloc)\n\t\taloPoolDestroy(g_accel.accels_buffer_alloc);\n\tg_accel.accels_buffer_alloc = aloPoolCreate(MAX_ACCELS_BUFFER, expected_accels, accels_alignment);\n\n\t// Recreate tlas\n\t// Why here and not in init: to make sure that its memory is preserved. Map init will clear all memory regions.\n\t{\n\t\tif (g_accel.tlas.handle != VK_NULL_HANDLE) {\n\t\t\tvkDestroyAccelerationStructureKHR(vk_core.device, g_accel.tlas.handle, NULL);\n\t\t\tg_accel.tlas.handle = VK_NULL_HANDLE;\n\t\t}\n\n\t\ttlasCreate();\n\t}\n}\n\nstatic void blasFillGeometries(rt_blas_t *blas, const vk_render_geometry_t *geoms, int geoms_count) {\n\t// geoms_count is not constant for dynamic models, and it shouldn't exceed max_geoms by design\n\tASSERT(geoms_count <= blas->max_geoms);\n\n\tblas->build.info.geometryCount = geoms_count;\n\n\tfor (int i = 0; i < geoms_count; ++i) {\n\t\tconst vk_render_geometry_t *mg = geoms + i;\n\t\tconst uint32_t prim_count = mg->element_count / 3;\n\n\t\tblas->build.max_prim_counts[i] = prim_count;\n\t\tblas->build.geoms[i] = (VkAccelerationStructureGeometryKHR)\n\t\t\t{\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,\n\t\t\t\t.flags = VK_GEOMETRY_OPAQUE_BIT_KHR, // FIXME this is not true. incoming mode might have transparency eventually (and also dynamically)\n\t\t\t\t.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR,\n\t\t\t\t.geometry.triangles =\n\t\t\t\t\t(VkAccelerationStructureGeometryTrianglesDataKHR){\n\t\t\t\t\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR,\n\t\t\t\t\t\t.indexType = VK_INDEX_TYPE_UINT16,\n\t\t\t\t\t\t.maxVertex = mg->max_vertex,\n\t\t\t\t\t\t.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT,\n\t\t\t\t\t\t.vertexStride = sizeof(vk_vertex_t),\n\n\t\t\t\t\t\t// Will be set to correct value at blas build time\n\t\t\t\t\t\t.vertexData.deviceAddress = 0,\n\t\t\t\t\t\t.indexData.deviceAddress = 0,\n\t\t\t\t\t},\n\t\t\t};\n\n\t\tblas->build.ranges[i] = (VkAccelerationStructureBuildRangeInfoKHR) {\n\t\t\t.primitiveCount = prim_count,\n\t\t\t.primitiveOffset = mg->index_offset * sizeof(uint16_t),\n\t\t\t.firstVertex = mg->vertex_offset,\n\t\t};\n\t}\n}\n\nstruct rt_blas_s* RT_BlasCreate(rt_blas_create_t args) {\n\trt_blas_t *blas = Mem_Calloc(vk_core.pool, sizeof(*blas));\n\n\tblas->debug_name = args.name;\n\tblas->usage = args.usage;\n\tblas->max_geoms = args.geoms_count;\n\n\tblas->build.info = (VkAccelerationStructureBuildGeometryInfoKHR){\n\t\t.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,\n\t\t.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,\n\t\t.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,\n\t\t.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR,\n\t\t.geometryCount = args.geoms_count,\n\t\t.srcAccelerationStructure = VK_NULL_HANDLE,\n\t};\n\n\tswitch (blas->usage) {\n\t\tcase kBlasBuildStatic:\n\t\t\tbreak;\n\t\tcase kBlasBuildDynamicUpdate:\n\t\t\tblas->build.info.flags |= VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;\n\t\t\tbreak;\n\t\tcase kBlasBuildDynamicFast:\n\t\t\tblas->build.info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR;\n\t\t\tbreak;\n\t}\n\n\t// TODO allocate these from static pool\n\tblas->build.geoms = Mem_Calloc(vk_core.pool, args.geoms_count * sizeof(VkAccelerationStructureGeometryKHR));\n\tblas->build.max_prim_counts = Mem_Malloc(vk_core.pool, args.geoms_count * sizeof(uint32_t));\n\tblas->build.ranges = Mem_Calloc(vk_core.pool, args.geoms_count * sizeof(VkAccelerationStructureBuildRangeInfoKHR));\n\n\tblasFillGeometries(blas, args.geoms, args.geoms_count);\n\n\tblas->build.info.pGeometries = blas->build.geoms;\n\n\tblas->build.sizes = getAccelSizes(&blas->build.info, blas->build.max_prim_counts);\n\n\tblas->blas = createAccel(blas->debug_name, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, blas->build.sizes.accelerationStructureSize);\n\tblas->address = getAccelAddress(blas->blas);\n\n\tif (!blas->blas) {\n\t\tERR(\"Couldn't create vk accel\");\n\t\tgoto fail;\n\t}\n\n\tblas->build.info.dstAccelerationStructure = blas->blas;\n\tblas->max_geoms = blas->build.info.geometryCount;\n\n\tblas->build.is_built = false;\n\tblas->build.needs_to_be_built = true;\n\n\treturn blas;\n\nfail:\n\tRT_BlasDestroy(blas);\n\treturn NULL;\n}\n\nvoid RT_BlasDestroy(struct rt_blas_s* blas) {\n\tif (!blas)\n\t\treturn;\n\n\tif (blas->build.max_prim_counts)\n\t\tMem_Free(blas->build.max_prim_counts);\n\n\tif (blas->build.geoms)\n\t\tMem_Free(blas->build.geoms);\n\n\tif (blas->build.ranges)\n\t\tMem_Free(blas->build.ranges);\n\n\tif (blas->blas)\n\t\tvkDestroyAccelerationStructureKHR(vk_core.device, blas->blas, NULL);\n\n\tMem_Free(blas);\n}\n\nqboolean RT_BlasUpdate(struct rt_blas_s *blas, const struct vk_render_geometry_s *geoms, int geoms_count) {\n\tswitch (blas->usage) {\n\t\tcase kBlasBuildStatic:\n\t\t\tASSERT(!\"Updating static BLAS is invalid\");\n\t\t\tbreak;\n\t\tcase kBlasBuildDynamicUpdate:\n\t\t\tASSERT(geoms_count == blas->max_geoms);\n\t\t\tif (blas->build.is_built) {\n\t\t\t\tblas->build.info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR;\n\t\t\t\tblas->build.info.srcAccelerationStructure = blas->blas;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase kBlasBuildDynamicFast:\n\t\t\tbreak;\n\t}\n\n\tblasFillGeometries(blas, geoms, geoms_count);\n\n\tconst VkAccelerationStructureBuildSizesInfoKHR sizes = getAccelSizes(&blas->build.info, blas->build.max_prim_counts);\n\n\tif (blas->build.sizes.accelerationStructureSize < sizes.accelerationStructureSize) {\n\t\tERR(\"Fast dynamic BLAS %s size exceeded (need %dKiB, have %dKiB, geoms = %d)\", blas->debug_name,\n\t\t\t(int)(sizes.accelerationStructureSize / 1024),\n\t\t\t(int)(blas->build.sizes.accelerationStructureSize / 1024),\n\t\t\tgeoms_count\n\t\t);\n\n\t\t// FIXME mark blas as invalid to avoid including it into TLAS\n\t\treturn false;\n\t}\n\n\tblas->build.needs_to_be_built = true;\n\treturn true;\n}\n\nvoid RT_VkAccelAddDrawInstance(const rt_draw_instance_t* instance) {\n\tconst int max_instances = (int)COUNTOF(g_accel.frame.instances.items);\n\tif (g_accel.frame.instances.count >= max_instances) {\n\t\tgEngine.Con_Printf(S_ERROR \"Too many RT draw instances, max = %d\\n\", max_instances);\n\t\treturn;\n\t}\n\n\tBOUNDED_ARRAY_APPEND_UNSAFE(g_accel.frame.instances) = *instance;\n}\n\nqboolean RT_VkAccelIsEmpty(void) {\n\treturn g_accel.frame.instances.count == 0;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VResource.c",
    "content": "#include \"VResource.h\"\n#include \"vk_common.h\"\n#include \"VBarrier.h\"\n#include \"std/arrays.h\"\n\n#define LOG_MODULE rt\n\nstatic struct {\n\tARRAY_DYNAMIC_DECLARE(rt_resource_t*, table);\n\n\t// Note that frame_sequence_tag will be shared between all dummy users\n\tProducer dummy_producer;\n} g_res;\n\nstatic void produceDummyNoop(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx) {\n\t(void)p;\n\t(void)combuf;\n\t(void)ctx;\n}\n\nvoid R_VkResourcesInit(void) {\n\tarrayDynamicInitT(&g_res.table);\n\n\tg_res.dummy_producer = (Producer) {\n\t\t.name = \"dummy\",\n\t\t.produce = produceDummyNoop,\n\t};\n}\n\nrt_resource_t *R_VkResourceGetByIndex(int index) {\n\tASSERT(index >= 0);\n\tASSERT(index < g_res.table.count);\n\treturn g_res.table.items[index];\n}\n\nint R_VkResourceFindIndexByName(const char *name) {\n\t// TODO hash table\n\t// Find the exact match if exists\n\t// There might be gaps, so we need to check everything\n\tfor (int i = 0; i < g_res.table.count; ++i) {\n\t\trt_resource_t *const res = g_res.table.items[i];\n\t\tif (strcmp(res->name, name) == 0)\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\nrt_resource_t *R_VkResourceFindByName(const char *name) {\n\tconst int index = R_VkResourceFindIndexByName(name);\n\treturn index < 0 ? NULL : g_res.table.items[index];\n}\n\nqboolean R_VkResourceRegister(rt_resource_t *res) {\n\tif (R_VkResourceFindByName(res->name))\n\t\treturn false;\n\n\tarrayDynamicAppendT(&g_res.table, &res);\n\treturn true;\n}\n\nvoid R_VkResourceProduce(rt_resource_t *res, vk_combuf_t *combuf, const FrameContext *ctx) {\n\tASSERT(res);\n\tASSERT(res->producer);\n\n\tif (!res->producer)\n\t\treturn;\n\n\tASSERT(res->producer->produce);\n\n\tif (res->producer->frame_sequence_tag == ctx->frame_sequence)\n\t\treturn;\n\n\tres->producer->produce(res->producer, combuf, ctx);\n\tres->producer->frame_sequence_tag = ctx->frame_sequence;\n}\n\nvoid R_VkResourcesCleanup(void) {\n\tfor (int i = 0; i < g_res.table.count; ++i) {\n\t\trt_resource_t *const res = g_res.table.items[i];\n\t\tif (!res->name[0] || res->refcount)\n\t\t\tcontinue;\n\n\t\tif (res->destroy)\n\t\t\tres->destroy(res);\n\n\t\t// Delete item: replace it last resource into current slot\n\t\tg_res.table.items[i] = g_res.table.items[g_res.table.count-1];\n\t\tg_res.table.count--;\n\t\tg_res.table.items[g_res.table.count] = NULL;\n\t\ti--;\n\t}\n}\n\nstatic vk_descriptor_value_t acquireDummyDescriptor(struct rt_resource_s *res, vk_resource_acquire_descriptor_args_t args) {\n\t(void)args;\n\trt_resource_dummy_t *const dummy = (void*)res;\n\treturn dummy->descriptor_value;\n}\n\nvoid R_VkResourceDummyInit(rt_resource_dummy_t *res, const char *name, VkDescriptorType type, vk_descriptor_value_t value) {\n\tQ_strncpy(res->header.name, name, sizeof(res->header.name));\n\tres->header.type = type;\n\tres->header.acquire_descriptor = acquireDummyDescriptor;\n\tres->header.producer = &g_res.dummy_producer;\n\tres->descriptor_value = value;\n}\n\nstatic vk_descriptor_value_t acquireBufferResourceDescriptor(struct rt_resource_s* r, vk_resource_acquire_descriptor_args_t args) {\n\tvk_resource_buffer_t *const res = (void*)r;\n\n\tbarrierAddBuffer(args.barriers, (r_vkcombuf_barrier_buffer_t) {\n\t\t.buffer = res->buffer,\n\t\t.access = args.access,\n\t});\n\n\treturn (vk_descriptor_value_t) {\n\t\t.buffer = (VkDescriptorBufferInfo) {\n\t\t\t.buffer = res->buffer->buffer,\n\t\t\t.offset = res->offset,\n\t\t\t.range = res->size,\n\t\t}\n\t};\n}\n\nvk_resource_buffer_t* R_VkBufferRegisterAsResource(r_vkbuffer_register_as_resource_t args) {\n\t// FIXME this leaks, add dtor?\n\tvk_resource_buffer_t *const res = Mem_Calloc(vk_core.pool, sizeof *res);\n\n\tQ_strncpy(res->header.name, args.name, sizeof(res->header.name));\n\tres->header.type = args.type;\n\tres->header.acquire_descriptor = acquireBufferResourceDescriptor;\n\tres->header.refcount = 1;\n\tres->header.producer = args.producer;\n\n\tres->buffer = args.buffer;\n\tres->offset = args.offset;\n\tres->size = args.size;\n\n\tASSERT(R_VkResourceRegister(&res->header));\n\treturn res;\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VResource.h",
    "content": "#pragma once\n\n#include \"VDescriptor.h\"\n#include \"VImage.h\"\n#include \"VBuffer.h\"\n#include \"VCombuf.h\" // r_vkcombuf_barrier_buffer_t\n\ntypedef struct FrameContext {\n\tuint32_t frame_sequence;\n} FrameContext;\n\nstruct Producer;\ntypedef void (ProducerProduceFunc)(struct Producer* p, struct vk_combuf_s *combuf, const FrameContext *ctx);\ntypedef struct Producer {\n\tchar name[64];\n\tProducerProduceFunc *produce;\n\t// TODO function to tell that previously produced data has been consumed and can be freed.\n\t// This is an alternative to FrameBegin/End functions\n\t// Valuable for anything that has dynamic data, ring/debuffers, etc:\n\t// - kusochki\n\t// - geometry\n\t// - ...\n\t// ProducerConsumedFunc *consumed;\n\n\t// Used by visitor for calling `produce()` only once per frame\n\tuint32_t frame_sequence_tag;\n} Producer;\n\ntypedef struct vk_resource_acquire_descriptor_args_s {\n\tstruct vk_combuf_s *combuf;\n\tstruct Barrier *barriers;\n\tVkAccessFlags2 access;\n\tVkImageLayout image_layout;\n} vk_resource_acquire_descriptor_args_t;\n\nstruct rt_resource_s;\ntypedef void (vk_resource_dtor_f)(struct rt_resource_s*);\ntypedef vk_descriptor_value_t (vk_resource_acquire_descriptor_f)(struct rt_resource_s*, vk_resource_acquire_descriptor_args_t);\n\ntypedef struct rt_resource_s {\n\tchar name[64];\n\tVkDescriptorType type;\n\tvk_resource_dtor_f *destroy;\n\tvk_resource_acquire_descriptor_f *acquire_descriptor;\n\n\tProducer *producer;\n\n\t// Used for tracking meatpipe resources when reloading meatpipes\n\tint refcount;\n} rt_resource_t;\n\n\nvoid R_VkResourcesInit(void);\n\nrt_resource_t *R_VkResourceFindByName(const char *name);\nqboolean R_VkResourceRegister(rt_resource_t *res);\n\nvoid R_VkResourceProduce(rt_resource_t *res, vk_combuf_t *combuf, const FrameContext *ctx);\n\n// TODO remove these when ping-pong resource is a dedicated type of resource\nrt_resource_t *R_VkResourceGetByIndex(int index);\nint R_VkResourceFindIndexByName(const char *name);\n\n// Destroys all resources with refcount = 0\nvoid R_VkResourcesCleanup(void);\n\n\n// Dummy resource that just returns `descriptor_value` without doing anything else\ntypedef struct rt_resource_dummy_s {\n\trt_resource_t header;\n\tvk_descriptor_value_t descriptor_value;\n} rt_resource_dummy_t;\n\nvoid R_VkResourceDummyInit(rt_resource_dummy_t *res, const char *name, VkDescriptorType, vk_descriptor_value_t);\n\n\ntypedef struct vk_resource_buffer_t {\n\trt_resource_t header;\n\tvk_buffer_t *buffer;\n\tsize_t offset;\n\tsize_t size;\n} vk_resource_buffer_t;\n\ntypedef struct {\n\tconst char *name;\n\tVkDescriptorType type;\n\tvk_buffer_t *buffer;\n\tsize_t offset;\n\tsize_t size;\n\tProducer *producer;\n} r_vkbuffer_register_as_resource_t;\n\nvk_resource_buffer_t *R_VkBufferRegisterAsResource(r_vkbuffer_register_as_resource_t args);\n"
  },
  {
    "path": "ref/vk/vulkan/VStaging.c",
    "content": "#include \"VStaging.h\"\n\n#include \"VBuffer.h\"\n#include \"VCombuf.h\"\n#include \"vk_logs.h\"\n#include \"r_speeds.h\"\n\n#include \"std/alolcator.h\"\n#include \"std/arrays.h\"\n\n#include <memory.h>\n\n#define MODULE_NAME \"staging\"\n#define LOG_MODULE staging\n\n// FIXME decrease size to something reasonable, see https://github.com/w23/xash3d-fwgs/issues/746\n#define DEFAULT_STAGING_SIZE (4*128*1024*1024)\n\n#define MAX_STAGING_USERS 8\n\ntypedef struct r_vkstaging_user_t {\n\tr_vkstaging_user_create_t info;\n\tuint32_t locked_count;\n\n\tstruct {\n\t\tint allocs;\n\t\tint size;\n\t} stats;\n} r_vkstaging_user_t;\n\nstatic struct {\n\tvk_buffer_t buffer;\n\talo_ring_t buffer_alloc_ring;\n\n\tBOUNDED_ARRAY_DECLARE(r_vkstaging_user_t, users, MAX_STAGING_USERS);\n\n\tstruct {\n\t\tint total_size;\n\t\tint total_chunks;\n\t} stats;\n\n\t//int buffer_upload_scope_id;\n\t//int image_upload_scope_id;\n} g_staging = {0};\n\nqboolean R_VkStagingInit(void) {\n\tif (!VK_BufferCreate(\"staging\", &g_staging.buffer, DEFAULT_STAGING_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,\n\t\t\tVK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))\n\t\treturn false;\n\n\taloRingInit(&g_staging.buffer_alloc_ring, g_staging.buffer.size);\n\n\tR_SPEEDS_COUNTER(g_staging.stats.total_size, \"total_size\", kSpeedsMetricBytes);\n\tR_SPEEDS_COUNTER(g_staging.stats.total_chunks, \"total_chunks\", kSpeedsMetricBytes);\n\n\t//g_staging.buffer_upload_scope_id = R_VkGpuScope_Register(\"staging_buffers\");\n\t//g_staging.image_upload_scope_id = R_VkGpuScope_Register(\"staging_images\");\n\n\treturn true;\n}\n\nvoid R_VkStagingShutdown(void) {\n\t// TODO ASSERT(g_staging.users.count == 0);\n\tVK_BufferDestroy(&g_staging.buffer);\n}\n\nr_vkstaging_user_t *R_VkStagingUserCreate(r_vkstaging_user_create_t info) {\n\tASSERT(g_staging.users.count < MAX_STAGING_USERS);\n\n\tr_vkstaging_user_t *const user = g_staging.users.items + (g_staging.users.count++);\n\t*user = (r_vkstaging_user_t) {\n\t\t.info = info,\n\t};\n\n\tchar buf[64];\n\tsnprintf(buf, sizeof(buf), \"%s.size\", info.name);\n\tR_SPEEDS_COUNTER(user->stats.size, buf, kSpeedsMetricBytes);\n\n\tsnprintf(buf, sizeof(buf), \"%s.allocs\", info.name);\n\tR_SPEEDS_COUNTER(user->stats.allocs, buf, kSpeedsMetricCount);\n\n\treturn user;\n}\n\nvoid R_VkStagingUserDestroy(r_vkstaging_user_t *user) {\n\tASSERT(user->locked_count == 0);\n\t// TODO remove from the table\n}\n\nr_vkstaging_region_t R_VkStagingLock(r_vkstaging_user_t* user, uint32_t size) {\n\tconst uint32_t alignment = 4;\n\tconst uint32_t offset = aloRingAlloc(&g_staging.buffer_alloc_ring, size, alignment);\n\tASSERT(offset != ALO_ALLOC_FAILED && \"FIXME: workaround: increase staging buffer size\");\n\n\tDEBUG(\"Lock alignment=%d size=%d region=%d..%d\", alignment, size, offset, offset + size);\n\n\tuser->locked_count++;\n\n\tuser->stats.allocs++;\n\tuser->stats.size += size;\n\n\tg_staging.stats.total_chunks++;\n\tg_staging.stats.total_size += size;\n\n\treturn (r_vkstaging_region_t){\n\t\t.offset = offset,\n\t\t.buffer = g_staging.buffer.buffer,\n\t\t.ptr = (char*)g_staging.buffer.mapped + offset,\n\t};\n}\n\nvoid R_VkStagingUnlockBulk(r_vkstaging_user_t* user, uint32_t count) {\n\tASSERT(user->locked_count >= count);\n\tuser->locked_count -= count;\n}\n\nuint32_t R_VkStagingFrameEpilogue(vk_combuf_t* combuf) {\n\tfor (int i = 0; i < g_staging.users.count; ++i) {\n\t\tr_vkstaging_user_t *const user = g_staging.users.items + i;\n\t\tif (user->locked_count == 0)\n\t\t\tcontinue;\n\n\t\tWARN(\"%s has %u locked staging items, pushing\", user->info.name, user->locked_count);\n\t\tuser->info.push(user->info.userptr, combuf, user->locked_count);\n\t\tASSERT(user->locked_count == 0);\n\t}\n\n\t// TODO it would be nice to attach a finalization callback to combuf\n\t// So that when the combuf is done on GPU, the callback is called and we can clean its memory\n\t// instead of depending on framectl calling Completed function manually.\n\n\treturn g_staging.buffer_alloc_ring.head;\n}\n\nvoid R_VkStagingFrameCompleted(uint32_t frame_boundary_addr) {\n\t// Note that these stats are for latest frame, not the one for which the frame boundary is.\n\tg_staging.stats.total_size = 0;\n\tg_staging.stats.total_chunks = 0;\n\n\tfor (int i = 0; i < g_staging.users.count; ++i) {\n\t\tr_vkstaging_user_t *const user = g_staging.users.items + i;\n\t\tuser->stats.allocs = 0;\n\t\tuser->stats.size = 0;\n\t}\n\n\taloRingFree(&g_staging.buffer_alloc_ring, frame_boundary_addr);\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VStaging.h",
    "content": "#pragma once\n\n#include \"vk_core.h\"\n\nqboolean R_VkStagingInit(void);\nvoid R_VkStagingShutdown(void);\n\nstruct vk_combuf_s;\ntypedef void (r_vkstaging_push_f)(void* userptr, struct vk_combuf_s *combuf, uint32_t pending);\n\ntypedef struct {\n\t// Expected to be static, stored as a pointer\n\tconst char *name;\n\n\tvoid *userptr;\n\tr_vkstaging_push_f *push;\n} r_vkstaging_user_create_t;\n\nstruct r_vkstaging_user_t;\ntypedef struct r_vkstaging_user_t *r_vkstaging_user_handle_t;\nr_vkstaging_user_handle_t R_VkStagingUserCreate(r_vkstaging_user_create_t);\nvoid R_VkStagingUserDestroy(r_vkstaging_user_handle_t);\n\ntypedef struct {\n\t// CPU-accessible memory\n\tvoid *ptr;\n\n\t// GPU buffer to copy from\n\tVkBuffer buffer;\n\tVkDeviceSize offset;\n} r_vkstaging_region_t;\n\n// Allocate CPU-accessible memory in staging buffer\nr_vkstaging_region_t R_VkStagingLock(r_vkstaging_user_handle_t, uint32_t size);\n\n// Notify staging that this amount of regions are about to be consumed when the next combuf ends\n// I.e. they're \"free\" from the staging standpoint\nvoid R_VkStagingUnlockBulk(r_vkstaging_user_handle_t, uint32_t count);\n\n// This gets called just before the combuf is ended and submitted.\n// Gives the last chance for the users that haven't yet used their data.\n// This is a workaround to patch up the impedance mismatch between top-down push model,\n// where the engine \"pushes down\" the data to be rendered, and \"bottom-up\" pull model,\n// where the frame is constructed based on render graph dependency tree. Not all pushed\n// resources could be used, and this gives the opportunity to at least ingest the data\n// to make sure that it remains complete, in case it might be needed in the future.\n// Returns current frame tag to be closed in the R_VkStagingCombufCompleted() function.\nuint32_t R_VkStagingFrameEpilogue(struct vk_combuf_s*);\n\n// This function is called when a frame is finished. It allows staging to free all the\n// data used in that frame.\n// TODO make this dependency more explicit, i.e. combuf should track when it's done\n// and what finalization functions it should call when it's done (there are many).\nvoid R_VkStagingFrameCompleted(uint32_t tag);\n"
  },
  {
    "path": "ref/vk/vulkan/VSwapchain.c",
    "content": "#include \"VSwapchain.h\"\n#include \"VImage.h\"\n#include \"std/profiler.h\"\n\n#include \"eiface.h\" // ARRAYSIZE\n\nextern ref_globals_t *gpGlobals;\n\nstatic struct {\n\t// These don't belong here\n\tVkRenderPass render_pass;\n\tVkFormat depth_format;\n\n\tVkSwapchainKHR swapchain;\n\tVkFormat image_format;\n\tuint32_t num_images;\n\tVkImage *images;\n\tVkImageView *image_views;\n\tVkFramebuffer *framebuffers;\n\tVkSemaphore *present_sema;\n\n\tr_vk_image_t depth;\n\n\tuint32_t width, height;\n\n\tuint32_t recreate_requested;\n} g_swapchain = {0};\n\n// TODO move to common\nstatic uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max) {\n\tif (v < min) v = min;\n\tif (v > max) v = max;\n\treturn v;\n}\n\nstatic void createDepthImage(int w, int h, VkFormat depth_format) {\n\tconst r_vk_image_create_t xic = {\n\t\t.debug_name = \"depth\",\n\t\t.format = depth_format,\n\t\t.flags = 0,\n\t\t.mips = 1,\n\t\t.layers = 1,\n\t\t.width = w,\n\t\t.height = h,\n\t\t.depth = 1,\n\t\t.tiling = VK_IMAGE_TILING_OPTIMAL,\n\t\t.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,\n\t};\n\tg_swapchain.depth = R_VkImageCreate( &xic );\n}\n\nstatic void destroySwapchainAndFramebuffers( VkSwapchainKHR swapchain ) {\n\tXVK_CHECK(vkDeviceWaitIdle( vk_core.device ));\n\n\tfor (uint32_t i = 0; i < g_swapchain.num_images; ++i) {\n\t\tvkDestroyImageView(vk_core.device, g_swapchain.image_views[i], NULL);\n\t\tvkDestroyFramebuffer(vk_core.device, g_swapchain.framebuffers[i], NULL);\n\t\tvkDestroySemaphore(vk_core.device, g_swapchain.present_sema[i], NULL);\n\t}\n\n\tR_VkImageDestroy( &g_swapchain.depth );\n\n\tvkDestroySwapchainKHR(vk_core.device, swapchain, NULL);\n}\n\nstatic qboolean recreateSwapchainIfNeeded( qboolean force ) {\n\tconst uint32_t prev_num_images = g_swapchain.num_images;\n\tuint32_t new_width, new_height;\n\n\tVkSurfaceCapabilitiesKHR surface_caps;\n\tXVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(v_device_info.physical_device, vk_core.surface.surface, &surface_caps));\n\n\tnew_width = surface_caps.currentExtent.width;\n\tnew_height = surface_caps.currentExtent.height;\n\n\tif (new_width == 0xfffffffful || new_width == 0)\n\t\tnew_width = gpGlobals->width;\n\n\tif (new_height == 0xfffffffful || new_height == 0)\n\t\tnew_height = gpGlobals->height;\n\n\tnew_width = clamp_u32(new_width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width);\n\tnew_height = clamp_u32(new_height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height);\n\n\tif (new_height == 0 || new_width == 0) {\n\t\treturn false;\n\t}\n\n\tif (new_width == g_swapchain.width && new_height == g_swapchain.height && !force)\n\t\treturn true;\n\n\tg_swapchain.width = new_width;\n\tg_swapchain.height = new_height;\n\n\t{\n\t\tVkSwapchainCreateInfoKHR create_info = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,\n\t\t\t.pNext = NULL,\n\t\t\t.surface = vk_core.surface.surface,\n\t\t\t.imageFormat = g_swapchain.image_format,\n\t\t\t.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR, // TODO get from surface_formats\n\t\t\t.imageExtent.width = g_swapchain.width,\n\t\t\t.imageExtent.height = g_swapchain.height,\n\t\t\t.imageArrayLayers = 1,\n\t\t\t.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | (vk_core.rtx ? /* TODO is it used really? why not? */ VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0),\n\t\t\t.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,\n\t\t\t.preTransform = surface_caps.currentTransform,\n\t\t\t.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,\n\t\t\t.presentMode = VK_PRESENT_MODE_FIFO_KHR, // TODO caps, MAILBOX is better\n\t\t\t//.presentMode = VK_PRESENT_MODE_MAILBOX_KHR, // TODO caps, MAILBOX is better\n\t\t\t//.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR, // TODO caps, MAILBOX is better\n\t\t\t.clipped = VK_TRUE,\n\t\t\t.oldSwapchain = g_swapchain.swapchain,\n\t\t\t.minImageCount = surface_caps.minImageCount + 1,\n\t\t};\n\n\t\tif (surface_caps.maxImageCount && surface_caps.maxImageCount < create_info.minImageCount)\n\t\t\tcreate_info.minImageCount = surface_caps.maxImageCount;\n\n\t\tgEngine.Con_Printf(\"Creating swapchain %dx%d min_count=%d (extent %dx%d)\\n\",\n\t\t\tg_swapchain.width, g_swapchain.height, create_info.minImageCount,\n\t\t\tsurface_caps.currentExtent.width, surface_caps.currentExtent.height);\n\n\t\tXVK_CHECK(vkCreateSwapchainKHR(vk_core.device, &create_info, NULL, &g_swapchain.swapchain));\n\t\tif (create_info.oldSwapchain)\n\t\t\tdestroySwapchainAndFramebuffers( create_info.oldSwapchain );\n\t}\n\n\tcreateDepthImage(g_swapchain.width, g_swapchain.height, g_swapchain.depth_format);\n\n\tg_swapchain.num_images = 0;\n\tXVK_CHECK(vkGetSwapchainImagesKHR(vk_core.device, g_swapchain.swapchain, &g_swapchain.num_images, NULL));\n\tif (prev_num_images != g_swapchain.num_images)\n\t{\n\t\tif (g_swapchain.images)\n\t\t{\n\t\t\tMem_Free(g_swapchain.images);\n\t\t\tMem_Free(g_swapchain.image_views);\n\t\t\tMem_Free(g_swapchain.framebuffers);\n\t\t\tMem_Free(g_swapchain.present_sema);\n\t\t}\n\n\t\tg_swapchain.images = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.images) * g_swapchain.num_images);\n\t\tg_swapchain.image_views = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.image_views) * g_swapchain.num_images);\n\t\tg_swapchain.framebuffers = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.framebuffers) * g_swapchain.num_images);\n\t\tg_swapchain.present_sema = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.present_sema) * g_swapchain.num_images);\n\t}\n\n\tXVK_CHECK(vkGetSwapchainImagesKHR(vk_core.device, g_swapchain.swapchain, &g_swapchain.num_images, g_swapchain.images));\n\n\t// TODO move this out to where render pipelines are created\n\tfor (uint32_t i = 0; i < g_swapchain.num_images; ++i) {\n\t\tconst VkImageViewCreateInfo ivci = {\n\t\t\t.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,\n\t\t\t.viewType = VK_IMAGE_VIEW_TYPE_2D,\n\t\t\t.format = g_swapchain.image_format,\n\t\t\t.image = g_swapchain.images[i],\n\t\t\t.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,\n\t\t\t.subresourceRange.levelCount = 1,\n\t\t\t.subresourceRange.layerCount = 1,\n\t\t};\n\n\t\tXVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, g_swapchain.image_views + i));\n\n\t\t{\n\t\t\tconst VkImageView attachments[] = {\n\t\t\t\tg_swapchain.image_views[i],\n\t\t\t\tg_swapchain.depth.view,\n\t\t\t};\n\t\t\tconst VkFramebufferCreateInfo fbci = {\n\t\t\t\t.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,\n\t\t\t\t.renderPass = g_swapchain.render_pass,\n\t\t\t\t.attachmentCount = ARRAYSIZE(attachments),\n\t\t\t\t.pAttachments = attachments,\n\t\t\t\t.width = g_swapchain.width,\n\t\t\t\t.height = g_swapchain.height,\n\t\t\t\t.layers = 1,\n\t\t\t};\n\t\t\tXVK_CHECK(vkCreateFramebuffer(vk_core.device, &fbci, NULL, g_swapchain.framebuffers + i));\n\n\t\t\tg_swapchain.present_sema[i] = R_VkSemaphoreCreate();\n\t\t\tSET_DEBUG_NAMEF(g_swapchain.present_sema[i], VK_OBJECT_TYPE_SEMAPHORE, \"swapchain done[%d]\", i);\n\t\t}\n\n\t\tSET_DEBUG_NAMEF(g_swapchain.images[i], VK_OBJECT_TYPE_IMAGE, \"swapchain image[%d]\", i);\n\t}\n\n\treturn true;\n}\n\nqboolean R_VkSwapchainInit( VkRenderPass render_pass, VkFormat depth_format ) {\n\t//const uint32_t prev_num_images = g_swapchain.num_images;\n\n\tVkSurfaceCapabilitiesKHR surface_caps;\n\tXVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(v_device_info.physical_device, vk_core.surface.surface, &surface_caps));\n\n/*\n[2023:10:09|13:03:52] Error: Validation: Validation Error: [ VUID-VkSwapchainCreateInfoKHR-imageFormat-01778 ] Object 0: handle = 0x555556af6a00, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xc036022f | vkCreateSwapchainKHR(): pCreateInfo->imageFormat VK_FORMAT_B8G8R8A8_SRGB with tiling VK_IMAGE_TILING_OPTIMAL does not support usage that includes VK_IMAGE_USAGE_STORAGE_BIT. The Vulkan spec states: The implied image creation parameters of the swapchain must be supported as reported by vkGetPhysicalDeviceImageFormatProperties (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkSwapchainCreateInfoKHR-imageFormat-01778)\n*/\n\t//g_swapchain.image_format = VK_FORMAT_B8G8R8A8_SRGB;\n\n\tg_swapchain.image_format = VK_FORMAT_B8G8R8A8_UNORM; // TODO get from surface_formats\n\tg_swapchain.render_pass = render_pass;\n\tg_swapchain.depth_format = depth_format;\n\n\treturn recreateSwapchainIfNeeded( true );\n}\n\nvoid R_VkSwapchainShutdown( void ) {\n\tdestroySwapchainAndFramebuffers( g_swapchain.swapchain );\n}\n\nr_vk_swapchain_framebuffer_t R_VkSwapchainAcquire(  VkSemaphore sem_image_available ) {\n\tAPROF_SCOPE_DECLARE_BEGIN(function, __FUNCTION__);\n\n\tr_vk_swapchain_framebuffer_t ret = {0};\n\n\tfor (;;) {\n\t\t// Check that swapchain has the same size\n\t\tif (!recreateSwapchainIfNeeded(!!g_swapchain.recreate_requested)) {\n\t\t\tgoto finalize;\n\t\t}\n\n\t\tAPROF_SCOPE_DECLARE_BEGIN_EX(vkAcquireNextImageKHR, \"vkAcquireNextImageKHR\", APROF_SCOPE_FLAG_WAIT);\n\t\tconst VkResult acquire_result = vkAcquireNextImageKHR(vk_core.device, g_swapchain.swapchain, UINT64_MAX, sem_image_available, VK_NULL_HANDLE, &ret.index);\n\t\tAPROF_SCOPE_END(vkAcquireNextImageKHR);\n\n\t\tswitch (acquire_result) {\n\t\t\tcase VK_SUCCESS:\n\t\t\t\tg_swapchain.recreate_requested = 0;\n\t\t\t\tbreak;\n\n\t\t\tcase VK_SUBOPTIMAL_KHR:\n\t\t\t\t// Would need to wait on the semaphore here somehow\n\t\t\t\tgEngine.Con_Printf(S_WARN \"vkAcquireNextImageKHR returned %s (%0#x), will recreate swapchain for the next frame\\n\", R_VkResultName(acquire_result), acquire_result);\n\t\t\t\t++g_swapchain.recreate_requested;\n\t\t\t\tbreak;\n\n\t\t\tcase VK_ERROR_OUT_OF_HOST_MEMORY:\n\t\t\tcase VK_ERROR_OUT_OF_DEVICE_MEMORY:\n\t\t\tcase VK_ERROR_DEVICE_LOST:\n\t\t\t\tgEngine.Host_Error(\"vkAcquireNextImageKHR returned %s, this is unrecoverable, crashing.\\n\", R_VkResultName(acquire_result));\n\t\t\t\tXVK_CHECK(acquire_result);\n\t\t\t\tgoto finalize;\n\n\t\t\tcase VK_TIMEOUT:\n\t\t\tcase VK_NOT_READY:\n\t\t\t\tgEngine.Con_Printf(S_ERROR \"vkAcquireNextImageKHR returned %s (%0#x), frame will be lost\\n\", R_VkResultName(acquire_result), acquire_result);\n\t\t\t\tgoto finalize;\n\n\t\t\tcase VK_ERROR_OUT_OF_DATE_KHR:\n\t\t\tcase VK_ERROR_SURFACE_LOST_KHR:\n\t\t\tcase VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:\n\t\t\tdefault:\n\t\t\t\tgEngine.Con_Printf(S_WARN \"vkAcquireNextImageKHR returned %s (%0#x)\\n\", R_VkResultName(acquire_result), acquire_result);\n\n\t\t\t\tif (g_swapchain.recreate_requested) {\n\t\t\t\t\tgEngine.Con_Printf(S_WARN \"second vkAcquireNextImageKHR failed with %s, frame will be lost\\n\", R_VkResultName(acquire_result));\n\t\t\t\t\tgoto finalize;\n\t\t\t\t}\n\n\t\t\t\t++g_swapchain.recreate_requested;\n\t\t\t\tcontinue;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\t// This is temporary non-owning placeholder object.\n\t// It is used only for combuf barrier tracking.\n\tret.image = (r_vk_image_t) {\n\t\t.image = g_swapchain.images[ret.index],\n\t\t.view = g_swapchain.image_views[ret.index],\n\t\t.width = g_swapchain.width,\n\t\t.height = g_swapchain.height,\n\t\t.depth = 1,\n\t\t.mips = 1,\n\t\t.layers = 1,\n\n\t\t.format = g_swapchain.image_format,\n\t\t// TODO? .image_size = ???\n\n\t\t.sync = {\n\t\t\t.layout = VK_IMAGE_LAYOUT_UNDEFINED,\n\t\t\t.write = {\n\t\t\t\t.access = VK_ACCESS_2_NONE,\n\t\t\t\t.stage = VK_PIPELINE_STAGE_2_NONE,\n\t\t\t},\n\t\t\t.read = {\n\t\t\t\t.access = VK_ACCESS_2_NONE,\n\t\t\t\t.stage = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,\n\t\t\t},\n\t\t},\n\t};\n\tsnprintf(ret.image.name, sizeof(ret.image.name), \"framebuffer[%u]\", ret.index);\n\tret.framebuffer = g_swapchain.framebuffers[ret.index];\n\tret.done = g_swapchain.present_sema[ret.index];\n\nfinalize:\n\tAPROF_SCOPE_END(function);\n\treturn ret;\n}\n\nvoid R_VkSwapchainPresent(uint32_t index) {\n\tconst VkPresentInfoKHR presinfo = {\n\t\t.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,\n\t\t.pSwapchains = &g_swapchain.swapchain,\n\t\t.pImageIndices = &index,\n\t\t.swapchainCount = 1,\n\t\t.pWaitSemaphores = &g_swapchain.present_sema[index],\n\t\t.waitSemaphoreCount = 1,\n\t};\n\n\tconst VkResult present_result = vkQueuePresentKHR(vk_core.queue, &presinfo);\n\tswitch (present_result) {\n\t\tcase VK_ERROR_OUT_OF_DATE_KHR:\n\t\tcase VK_ERROR_SURFACE_LOST_KHR:\n\t\tcase VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:\n\t\t\tgEngine.Con_Printf(S_WARN \"vkQueuePresentKHR returned %s, frame will be lost\\n\", R_VkResultName(present_result));\n\t\t\tbreak;\n\n\t\tcase VK_SUBOPTIMAL_KHR:\n\t\t\tgEngine.Con_Printf(S_WARN \"vkQueuePresentKHR returned %s\\n\", R_VkResultName(present_result));\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tXVK_CHECK(present_result);\n\t}\n}\n"
  },
  {
    "path": "ref/vk/vulkan/VSwapchain.h",
    "content": "#include \"vk_core.h\"\n#include \"VImage.h\"\n\n// TODO this needs to be negotiated by swapchain creation\n// however, currently render pass also needs it so ugh\n#define SWAPCHAIN_FORMAT VK_FORMAT_B8G8R8A8_UNORM //SRGB\n//#define SWAPCHAIN_FORMAT VK_FORMAT_B8G8R8A8_SRGB\n\n// TODO: move render pass and depth format away from this\nqboolean R_VkSwapchainInit( VkRenderPass pass, VkFormat depth_format );\nvoid R_VkSwapchainShutdown( void );\n\ntypedef struct {\n\tuint32_t index;\n\t// Non-owned image mostly for for sync/barrier tracking purposes\n\tr_vk_image_t image;\n\tVkFramebuffer framebuffer;\n\tVkSemaphore done;\n} r_vk_swapchain_framebuffer_t;\n\nr_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_available );\n\nvoid R_VkSwapchainPresent( uint32_t index );\n"
  },
  {
    "path": "ref/vk/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib import Logs\nimport os\n\ntop = '.'\n\ndef options(opt):\n\tgrp = opt.add_option_group('ref_vk options')\n\n\tgrp.add_option('--disable-vulkan', action='store_false', dest='VK', default=True,\n\t\thelp = 'disable vulkan [default: %(default)s]')\n\n\tgrp.add_option('--aftermath', action='store', dest = 'NSIGHT_AFTERMATH_SDK', default = None,\n\t\thelp = 'path to Nvidia Nsight Aftermath SDK (optional) [default: %(default)s]')\n\ndef configure(conf):\n\tif conf.options.DEDICATED:\n\t\treturn\n\n\tif not conf.options.VK:\n\t\treturn\n\n\tif conf.env.DEST_OS != 'win32' and conf.env.DEST_OS != 'linux':\n\t\tLogs.warn('Vulkan renderer is only supported on Windows and Linux for now')\n\t\treturn\n\n\tconf.env.VK = True\n\n\tconf.start_msg('Vulkan SDK available?')\n\tif 'VULKAN_SDK' in conf.environ:\n\t\tconf.env.VULKAN_SDK = conf.environ['VULKAN_SDK']\n\t\tif not os.path.isdir(conf.env.VULKAN_SDK):\n\t\t\tconf.fatal(conf.env.VULKAN_SDK + ' is not a valid directory')\n\t\tconf.end_msg('using ' + conf.env.VULKAN_SDK)\n\telse:\n\t\tif conf.env.DEST_OS == 'win32':\n\t\t\tconf.fatal('VULKAN_SDK environment variable is not available, ref_vk will not be built')\n\t\t\tconf.end_msg('no')\n\t\telse:\n\t\t\t# FIXME check for valid vulkan/vulkan.h\n\t\t\tconf.end_msg('assuming installed')\n\n\tconf.load('glslc')\n\tconf.load('sebastian')\n\n\tconf.define('REF_DLL', 1)\n\n\tif conf.options.NSIGHT_AFTERMATH_SDK:\n\t\tconf.start_msg('Nvidia Nsight Aftermath SDK')\n\t\tconf.env.HAVE_AFTERMATH = 1\n\t\tpath = conf.options.NSIGHT_AFTERMATH_SDK\n\t\tconf.env.INCLUDES_AFTERMATH = [os.path.abspath(os.path.join(path, 'include'))]\n\t\tlibdir = 'lib'\n\t\tlib = 'GFSDK_Aftermath_Lib'\n\t\tif conf.env.COMPILER_CC == 'msvc':\n\t\t\tif conf.env.DEST_CPU == 'x86_64':\n\t\t\t\tlibdir = 'lib/x64'\n\t\t\t\tlib += '.x64'\n\t\t\telse:\n\t\t\t\tlibdir = 'lib/' + conf.env.DEST_CPU\n\t\t\t\tlib += conf.env.DEST_CPU\n\t\tlibdir = os.path.abspath(os.path.join(path, libdir))\n\t\tconf.env.LIBPATH_AFTERMATH = [libdir]\n\t\tconf.env.LIB_AFTERMATH = [lib]\n\t\tconf.end_msg('SDK: {0}, includes: {1}, libpath: {2}, lib: {3}'.format(path, conf.env.INCLUDES_AFTERMATH, conf.env.LIBPATH_AFTERMATH, conf.env.LIB_AFTERMATH))\n\n\tif not conf.options.ALLOW64:\n\t\tLogs.warn('\\x1b[35;20m==============================================================')\n\t\tLogs.error('\\x1b[33;20mNo ray tracing extensions in \\x1b[31;1m32-bit \\x1b[33;20mmode!')\n\t\tLogs.warn(\"Note that Ray Tracing REQUIRES \\x1b[32;1m64-bit \\x1b[33;20mprocess!\\n\\x1b[36;20mPlease configure with the 64-bit using \\x1b[32;1m-8 \\x1b[36;20mor \\x1b[32;1m--64bits \\x1b[36;20mflags.\");\n\t\tLogs.warn('\\x1b[35;20m==============================================================')\n\n\t# TODO if debug\n\tconf.env.GLSLCFLAGS += ['-g', '-O']\n\n\tif '-Werror=declaration-after-statement' in conf.env.CFLAGS:\n\t\tconf.env.CFLAGS.remove('-Werror=declaration-after-statement')\n\ndef printTestSummary(bld):\n\tresults = getattr(bld, 'utest_results', [])\n\tfor (f, code, out, err) in results:\n\t\tif code == 0:\n\t\t\tLogs.pprint('GREEN', '%s: ok' % f)\n\t\t\tcontinue\n\n\t\tLogs.pprint('RED', '%s: test failed' % f)\n\t\tLogs.pprint('CYAN', out.decode('utf-8'))\n\t\tLogs.pprint('CYAN', err.decode('utf-8'))\n\ndef build(bld):\n\tif not bld.env.VK:\n\t\treturn\n\n\tlibs = [ 'engine_includes', 'public', 'M' ]\n\tdefines = []\n\tlibpath = []\n\n\tsource = bld.path.ant_glob(['*.c', 'std/*.c', 'vulkan/*.c'])\n\tglsl_source = bld.path.ant_glob(['shaders/*.vert', 'shaders/*.frag'])\n\trtx_glsl_source = bld.path.ant_glob(['shaders/*.rgen', 'shaders/*.rchit', 'shaders/*.rmiss', 'shaders/*.rahit', 'shaders/*.comp'])\n\n\tmeatpipes = bld.path.ant_glob(['shaders/*.json'])\n\n\tincludes = ['.',\n\t\t'../filesystem',\n\t\t'../engine',\n\t\t'../engine/common',\n\t\t'../engine/server',\n\t\t'../engine/client',\n\t\t'../public',\n\t\t'../common',\n\t\t'../pm_shared' ]\n\n\t# Use include from VULKAN_SDK explicitly, if specified\n\tif bld.env.VULKAN_SDK:\n\t\tincludes.append(os.path.join(bld.env.VULKAN_SDK, 'include'))\n\n\tif bld.env.HAVE_AFTERMATH:\n\t\tdefines.append('USE_AFTERMATH')\n\t\tlibs.append('AFTERMATH')\n\t\tfor file in ['GFSDK_Aftermath_Lib.x64.dll', 'llvm_7_0_1.dll']:\n\t\t\tbld.install_files(\n\t\t\t\tbld.env.LIBDIR,\n\t\t\t\tbld.root.find_dir(bld.env.LIBPATH_AFTERMATH[0]).find_node(file))\n\n\tif bld.env.COMPILER_CC == 'msvc':\n\t\tbld.env.CFLAGS += ['/WX']\n\telse:\n\t\tbld.env.CFLAGS += ['-Werror', '-Wno-format-nonliteral'] # TODO , '-Wall']\n\n\tbld.shlib(\n\t\tsource   = source,\n\t\ttarget   = 'ref_vk',\n\t\tfeatures = 'c',\n\t\tincludes = includes,\n\t\tuse      = libs,\n\t\tdefines  = defines,\n\t\tlibpath  = libpath,\n\t\tinstall_path = bld.env.LIBDIR,\n\t\tsubsystem = bld.env.MSVC_SUBSYSTEM\n\t)\n\n\tbld(\n\t\tsource = glsl_source,\n\t\tfeatures = 'glsl',\n\t\t# includes = 'shaders/', # write your includes here\n\t\t# defines = 'TEST', # write your C preprocessor defines here\n\t\tinstall_path = bld.env.LIBDIR + '/valve' # FIXME TEMPORARY!!!!\n\t)\n\n\tbld(\n\t\tsource = rtx_glsl_source,\n\t\tfeatures = 'glsl',\n\t\tglslcflags = '--target-env=vulkan1.2'\n\t)\n\n\tbld(\n\t\tsource = meatpipes,\n\t\tfeatures = 'sebastian',\n\t\tinstall_path = bld.env.LIBDIR + '/valve'\n\t)\n\t#print(things.tasks())\n\t#bld.install_files(bld.env.LIBDIR + '/valve', things)\n\n\tbld.install_files(bld.env.LIBDIR,\n\t\t\t\t\tbld.path.ant_glob('data/**'),\n\t\t\t\t\tcwd=bld.path.find_dir('data/'),\n\t\t\t\t\trelative_trick=True)\n\n\tif bld.env.TESTS:\n\t\tbld.program(\n\t\t\tfeatures='test',\n\t\t\tdefines=['ALOLCATOR_TEST'],\n\t\t\tsource='std/alolcator.c',\n\t\t\ttarget='test_alolcator',\n\t\t\tsubsystem = bld.env.CONSOLE_SUBSYSTEM,\n\t\t\tinstall_path = None)\n\n\t\ttests = {\n\t\t\t'unordered_roadmap': 'tests/unordered_roadmap.c',\n\t\t}\n\n\t\tfor i in tests:\n\t\t\tbld.program(features = 'test',\n\t\t\t\tsource = tests[i],\n\t\t\t\ttarget = 'test_%s' % i,\n\t\t\t\tsubsystem = bld.env.CONSOLE_SUBSYSTEM,\n\t\t\t\tinstall_path = None)\n\n\t\t#bld.add_post_fun(printTestSummary)\n"
  },
  {
    "path": "scripts/build-ninja.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n# Copyright (C) 2025 Velaron\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nimport argparse\nimport os\nimport shutil\nimport subprocess\nimport sys\n\n\ndef run_cmake(bin_path, libs, inst_path):\n\tcmake_exec = [\"cmake\", \"--build\", bin_path]\n\tcmake_process = subprocess.Popen(cmake_exec)\n\tcmake_process.communicate()\n\n\tif libs:\n\t\tfor lib in libs:\n\t\t\tsrc = os.path.join(bin_path, *lib.split(\"/\"))\n\t\t\tdest = os.path.join(inst_path, lib.split(\"/\")[-1])\n\n\t\t\tdest_dir = os.path.dirname(dest)\n\n\t\t\tif not os.path.exists(dest_dir):\n\t\t\t\tos.makedirs(dest_dir)\n\n\t\t\tshutil.copyfile(src, dest)\n\telse:\n\t\tcmake_exec = [\"cmake\", \"--install\", bin_path, \"--prefix\", inst_path]\n\t\tcmake_process = subprocess.Popen(cmake_exec)\n\t\tcmake_process.communicate()\n\ndef main():\n\tparser = argparse.ArgumentParser()\n\tparser.add_argument(\"cmd\")\n\tparser.add_argument(\"top_dir\")\n\tparser.add_argument(\"out_dir\")\n\tparser.add_argument(\"waflock\")\n\tparser.add_argument(\"--targets\", type=str, default=\"\")\n\n\targs = parser.parse_args()\n\n\twaf_path = os.path.join(args.top_dir, \"waf\")\n\n\tenv = os.environ.copy()\n\tenv[\"WAFLOCK\"] = args.waflock\n\n\twaf_exec = [sys.executable, waf_path, args.cmd, \"-t\", args.top_dir]\n\n\tif args.targets:\n\t\twaf_exec += [\"--targets={}\".format(args.targets)]\n\telse:\n\t\t# build SDL2 and hlsdk-portable with cmake\n\t\tsdl_bin_path = os.path.join(args.out_dir, \"SDL\")\n\t\thlsdk_bin_path = os.path.join(args.out_dir, \"hlsdk-portable\")\n\n\t\tabi = args.waflock.replace(\".lock-waf_android_\", \"\").replace(\"_build\", \"\")\n\t\tinst_path = os.path.join(args.top_dir, \"android\", \"app\", \"src\", \"main\", \"jniLibs\", abi)\n\n\t\tif not os.path.exists(inst_path):\n\t\t\tos.makedirs(inst_path)\n\n\t\trun_cmake(sdl_bin_path, [\"libSDL2.so\"], inst_path)\n\t\trun_cmake(hlsdk_bin_path, None, inst_path)\n\n\tprocess = subprocess.Popen(waf_exec, env=env)\n\tprocess.communicate()\n\n\treturn 0\n\nif __name__ == \"__main__\":\n\tsys.exit(main())\n"
  },
  {
    "path": "scripts/cirrus/build_freebsd.sh",
    "content": "#!/bin/sh\n\n. scripts/lib.sh\n\nbuild_engine()\n{\n\t# Build engine\n\tcd \"$CIRRUS_WORKING_DIR\" || die\n\n\t./waf configure --enable-utils --enable-all-renderers --enable-tests --enable-dedicated || die_configure\n\t./waf build || die\n}\n\nrm -rf build # clean-up build directory\n\nbuild_engine\n"
  },
  {
    "path": "scripts/configure-ninja.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n# Copyright (C) 2025 Velaron\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nfrom __future__ import print_function\n\nimport argparse\nimport os\nimport subprocess\nimport sys\nimport io\n\n\ndef check_repo(name, branch, url, path):\n\tif not os.path.exists(path):\n\t\tprint(\"{} not found. Cloning...\".format(name))\n\t\tgit_exec = [\"git\", \"clone\", \"--branch\", branch, url, path]\n\t\tgit_process = subprocess.Popen(git_exec)\n\t\tgit_process.communicate()\n\n\ndef run_cmake(root, out, toolchain, abi, build_type, ndk_root, min_sdk, *args):\n\tcmake_exec = [\"cmake\", \"-H{}\".format(root), \"-DCMAKE_BUILD_TYPE={}\".format(build_type),\n\t\t\t\t  \"-DCMAKE_TOOLCHAIN_FILE={}\".format(toolchain), \"-DANDROID_ABI={}\".format(abi),\n\t\t\t\t  \"-DANDROID_NDK={}\".format(ndk_root),\n\t\t\t\t  \"-DANDROID_PLATFORM=android-{}\".format(min_sdk),\n\t\t\t\t  \"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON\",\n\t\t\t\t  \"-DCMAKE_SYSTEM_NAME=Android\", \"-DCMAKE_SYSTEM_VERSION={}\".format(min_sdk),\n\t\t\t\t  \"-B{}\".format(out), \"-GNinja\"]\n\n\tcmake_exec.extend(args)\n\tcmake_process = subprocess.Popen(cmake_exec)\n\tcmake_process.communicate()\n\n\ndef main():\n\tparser = argparse.ArgumentParser()\n\tparser.add_argument(\"wscript_path\")\n\tparser.add_argument(\"--variant\")\n\tparser.add_argument(\"--abi\")\n\tparser.add_argument(\"--configuration-dir\")\n\tparser.add_argument(\"--ndk-version\")\n\tparser.add_argument(\"--min-sdk-version\")\n\tparser.add_argument(\"--ndk-root\")\n\n\targs, unknown = parser.parse_known_args()\n\n\tabi = args.abi\n\n\tcmake_build_type = \"Debug\" if args.variant in [\"debug\", \"asan\"] else \"Release\"\n\tcmake_toolchain_path = os.path.join(args.ndk_root, \"build\", \"cmake\", \"android.toolchain.cmake\")\n\n\t# configure SDL2\n\tsdl_path = os.path.join(args.wscript_path, \"3rdparty\", \"SDL\")\n\tcheck_repo(\"SDL\", \"release-2.32.8\", \"https://github.com/libsdl-org/SDL\", sdl_path)\n\n\tsdl_out_path = os.path.join(args.configuration_dir, \"SDL\")\n\n\trun_cmake(sdl_path, sdl_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root, args.min_sdk_version,\n\t\t\t  \"-DSDL_RENDER=OFF\", \"-DSDL_POWER=OFF\", \"-DSDL_VULKAN=OFF\", \"-DSDL_DISKAUDIO=OFF\",\n\t\t\t  \"-DSDL_DUMMYAUDIO=OFF\", \"-DSDL_DUMMYVIDEO=OFF\",\n\t\t\t  \"-DSDL_VULKAN=OFF\", \"-DSDL_OFFSCREEN=OFF\", \"-DSDL_STATIC=OFF\")\n\n\t# configure hlsdk-portable\n\thlsdk_path = os.path.join(args.wscript_path, \"3rdparty\", \"hlsdk-portable\")\n\tcheck_repo(\"hlsdk-portable\", \"mobile_hacks\", \"https://github.com/FWGS/hlsdk-portable\", hlsdk_path)\n\n\thlsdk_out_path = os.path.join(args.configuration_dir, \"hlsdk-portable\")\n\n\trun_cmake(hlsdk_path, hlsdk_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root,\n\t\t\t  args.min_sdk_version, \"-DANDROID_APK=ON\")\n\n\t# waf configure\n\twaf_path = os.path.join(args.wscript_path, \"waf\")\n\tout_path = os.path.join(args.configuration_dir, \"xash3d-fwgs\")\n\n\twaf_build_type = \"debug\" if args.variant in [\"debug\", \"asan\"] else \"release\"\n\n\tenv = os.environ.copy()\n\tenv[\"WAFLOCK\"] = \".lock-waf_android_{}_build\".format(abi)\n\tenv[\"ANDROID_NDK\"] = args.ndk_root\n\tenv[\"BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY\"] = sdl_out_path\n\n\twaf_exec = [sys.executable, waf_path, \"configure\", \"-t\", args.wscript_path, \"-o\", out_path,\n\t\t\t\t\"-T\", waf_build_type, \"--android={},,{}\".format(abi, args.min_sdk_version), \"-s\",\n\t\t\t\tsdl_path, \"--skip-sdl2-sanity-check\", \"--enable-bundled-deps\", \"ninja\"]\n\n\tprocess = subprocess.Popen(waf_exec, env=env)\n\tprocess.communicate()\n\n\twith io.open(os.path.join(args.configuration_dir, \"build.ninja.txt\"), \"w\", encoding=\"utf-8\") as f:\n\t\tf.write(os.path.join(out_path, \"build.ninja\"))\n\n\t# required for Android Studio\n\treturn 0\n\n\nif __name__ == \"__main__\":\n\tsys.exit(main())\n"
  },
  {
    "path": "scripts/flatpak/run.sh",
    "content": "#!/bin/sh\n\ndie()\n{\n        echo \"$@\"\n        exit 1\n}\n\necho \"Xash3D FWGS installed as Flatpak.\"\n\n# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n# $XDG_DATA_HOME defines the base directory relative to which user-specific data files should be stored.\n# If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.\nif [ -z \"$XDG_DATA_HOME\" ]; then\n        export XDG_DATA_HOME=\"$HOME/.local/share\"\nfi\n\nif [ -z \"$XASH3D_BASEDIR\" ]; then\n        export XASH3D_BASEDIR=\"$XDG_DATA_HOME/xash3d-fwgs/\"\nfi\n\nmkdir -p \"$XASH3D_BASEDIR\"\ncd \"$XASH3D_BASEDIR\" || die \"Can't cd into $XASH3D_BASEDIR\"\necho \"XASH3D_BASEDIR is $XASH3D_BASEDIR\"\n\nif [ -z \"$XASH3D_RODIR\" ]; then\n        # TODO: detect by libraryfolders.vdf and installed apps\n        STEAMDIRS=\"\\\n                $HOME/.local/share/Steam/steamapps/common \\\n                $HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common \\\n                $HOME/.steam/steam/steamapps/common\"\n        HALFLIFEDIR=\"Half-Life\"\n\n        for i in $STEAMDIRS; do\n                if [ ! -d \"$i\" ]; then\n                        continue\n                fi\n\n                echo \"Detected Steam library in $i, probing Half-Life...\"\n\n                if [ ! -d \"$i/$HALFLIFEDIR\" ]; then\n                        continue\n                fi\n\n                echo \"Detected Half-Life installation in $i/$HALFLIFEDIR...\"\n\n                export XASH3D_RODIR=\"$i/$HALFLIFEDIR\"\n                break\n        done\nfi\necho \"XASH3D_RODIR is $XASH3D_RODIR\"\n\nif [ -z \"$XASH3D_EXTRAS_PAK1\" ]; then\n        export XASH3D_EXTRAS_PAK1=/app/share/xash3d/valve/extras.pk3\nfi\necho \"XASH3D_EXTRAS_PAK1 is $XASH3D_EXTRAS_PAK1\"\n\nexec $DEBUGGER /app/lib32/xash3d/xash3d \"$@\"\n"
  },
  {
    "path": "scripts/flatpak/su.xash.Engine.Compat.i386.desktop",
    "content": "[Desktop Entry]\nCategories=Game;Shooter;\nComment=Half-Life compatible game engine\nExec=\nIcon=su.xash.Engine.Compat.i386\nKeywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer;\nName=Xash3D FWGS\nPrefersNonDefaultGPU=true\nTerminal=false\nType=Application\n"
  },
  {
    "path": "scripts/flatpak/su.xash.Engine.Compat.i386.yml",
    "content": "app-id: su.xash.Engine.Compat.i386\nruntime: org.freedesktop.Platform\nruntime-version: &runtime-version '24.08'\nx-gl-version: &gl-version '1.4'\nx-gl-versions: &gl-versions 24.08;1.4\nsdk: org.freedesktop.Sdk\ncommand: run.sh\nseparate-locales: false\nsdk-extensions:\n  - org.freedesktop.Sdk.Compat.i386\n  - org.freedesktop.Sdk.Extension.toolchain-i386\n\nfinish-args:\n  - --share=ipc\n  - --socket=wayland\n  - --socket=x11\n  - --socket=pulseaudio\n  - --share=network\n  # Steam library path on Ubuntu\n  - --filesystem=~/.steam/steam/steamapps/common:ro\n  # ... on SteamOS 3\n  - --filesystem=~/.local/share/Steam/steamapps/common:ro\n  # ... on Steam Flatpak\n  - --filesystem=~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common:ro\n  # allow talking with Discord for mods\n  # TODO: enable when somebody asks for it\n  # - --filesystem=xdg-run/app/com.discordapp.Discord:create\n  - --device=all\n  - --allow=multiarch\n  - --allow=devel\n  - --allow=bluetooth\n\nadd-extensions:\n  org.freedesktop.Platform.Compat.i386:\n    directory: lib/i386-linux-gnu\n    version: *runtime-version\n\n  org.freedesktop.Platform.Compat.i386.Debug:\n    directory: lib/debug/lib/i386-linux-gnu\n    version: *runtime-version\n    no-autodownload: true\n\n  org.freedesktop.Platform.GL32:\n    directory: lib/i386-linux-gnu/GL\n    version: *gl-version\n    versions: *gl-versions\n    subdirectories: true\n    autodelete: false\n    add-ld-path: lib\n    merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d;OpenCL/vendors;lib/dri;lib/d3d;vulkan/explicit_layer.d;vulkan/implicit_layer.d\n    download-if: active-gl-driver\n    enable-if: active-gl-driver\n\nx-compat-i386-opts: &compat-i386-opts\n  prepend-pkg-config-path: /app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig\n  ldflags: -L/app/lib32\n  prepend-path: /usr/lib/sdk/toolchain-i386/bin\n  env:\n    CC: i686-unknown-linux-gnu-gcc\n    CXX: i686-unknown-linux-gnu-g++\n  libdir: /app/lib32\n  no-debuginfo: true # libbacktrace relies on this\n\nmodules:\n  - name: bundle-setup\n    buildsystem: simple\n    build-commands:\n      - |\n        mkdir -p /app/lib/i386-linux-gnu\n        mkdir -p /app/lib/debug/lib/i386-linux-gnu\n        mkdir -p /app/lib/i386-linux-gnu/GL\n        mkdir -p /app/etc\n        echo \"/app/lib32\" > /app/etc/ld.so.conf\n        echo \"/app/lib/i386-linux-gnu\" >> /app/etc/ld.so.conf\n  - name: xash\n    buildsystem: simple\n    build-options: *compat-i386-opts\n    build-commands:\n# HACKHACK: removed --enable-lto because it causes linker errors in Flatpak build\n      - |\n        python waf configure -T release --enable-all-renderers --enable-packaging --prefix=/app --libdir=/app/lib32 --bindir=/app/lib32/xash3d\n        python waf build\n        python waf install --destdir=/\n        mkdir -p /app/bin\n        mkdir -p /app/share/icons/hicolor/256x256/apps/\n        mkdir -p /app/share/applications\n        install -m 0755 3rdparty/vgui_support/vgui-dev/lib/vgui.so /app/lib32/xash3d/vgui.so\n        install -m 0755 scripts/flatpak/run.sh /app/bin/run.sh\n        install -m 0644 game_launch/icon-xash-material.png /app/share/icons/hicolor/256x256/apps/su.xash.Engine.Compat.i386.png\n        install -m 0644 scripts/flatpak/su.xash.Engine.Compat.i386.desktop /app/share/applications/su.xash.Engine.Compat.i386.desktop\n    sources:\n    - type: dir\n      path: ../../\n"
  },
  {
    "path": "scripts/gha/build_android.sh",
    "content": "#!/bin/bash\n\nunset ANDROID_SDK_ROOT\nexport JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6\nexport ANDROID_HOME=$GITHUB_WORKSPACE/sdk\nexport PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin\n\npushd android\n\n./gradlew assembleContinuous --no-daemon || exit 1\n\npushd app/build/outputs/apk/continuous\n\n$ANDROID_HOME/build-tools/36.0.0/apksigner sign \\\n\t--ks $GITHUB_WORKSPACE/android/debug.keystore \\\n\t--ks-key-alias androiddebugkey \\\n\t--ks-pass pass:android \\\n\t--key-pass pass:android \\\n\t--out app-continuous-signed.apk app-continuous-unsigned.apk || exit 1\n\npopd\npopd\n\nmkdir -p artifacts/\n\nmv android/app/build/outputs/apk/continuous/app-continuous-signed.apk artifacts/xash3d-fwgs-android.apk\ntar -cJvf artifacts/xash3d-fwgs-android-mappings.tar.zst -C android/app/build/outputs/mapping/continuous '.'\n\n"
  },
  {
    "path": "scripts/gha/build_apple.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\ncd $GITHUB_WORKSPACE || die\n\npushd hlsdk || die\n./waf configure build install --destdir=../bin || die\npopd\n\n./waf configure --enable-utils --enable-tests --enable-lto --enable-tui build install --destdir=bin || die_configure\n\ncp -vr /Library/Frameworks/SDL2.framework bin\n\nmkdir -p artifacts/\ntar -cJvf artifacts/xash3d-fwgs-apple-$ARCH.tar.xz -C bin . # skip the bin directory from resulting tar archive\n"
  },
  {
    "path": "scripts/gha/build_linux-e2k.sh",
    "content": "#!/bin/bash\n\nset -e\n\n. scripts/lib.sh\n. scripts/lib-e2k.sh\n\nexport CC=${E2K_CROSS_COMPILER_PATH[$GH_CPU_ARCH]}/bin/lcc\nexport CXX=${E2K_CROSS_COMPILER_PATH[$GH_CPU_ARCH]}/bin/l++\n\nAPP=xash3d-fwgs\n\nAPPDIR=$APP-linux-$ARCH # FIXME: not conforms to libpublic's build arch strings but in parity with xashds directory name\nAPPTARGZ=$APP-linux-$ARCH.tar.gz\n\nDS=xashds-linux\nDSDIR=$DS-$ARCH\nDSTARGZ=$DS-$ARCH.tar.gz\n\nbuild_engine()\n{\n\t# Build engine\n\tcd \"$BUILDDIR\"\n\n\t./waf configure --enable-dedicated -s usr --enable-stbtt --enable-utils --enable-bundled-deps --enable-all-renderers || die_configure\n\t./waf build || die_configure\n}\n\nmake_client_tarball()\n{\n\tcd \"$BUILDDIR\"\n\t./waf install --destdir=\"$APPDIR\"\n\ttar -czvf \"artifacts/$APPTARGZ\" \"$APPDIR\"\n}\n\nmake_server_tarball()\n{\n\tcd \"$BUILDDIR\"\n\n\t# FIXME: make an option for Waf to only install dedicated\n\tmkdir -p \"$DSDIR\"\n\tcp -v \"$APPDIR\"/xash \"$APPDIR\"/filesystem_stdio.so \"$DSDIR\"\n\ttar -czvf \"artifacts/$DSTARGZ\" \"$DSDIR\"\n}\n\nmkdir -p artifacts/\n\nrm -rf build # clean-up build directory\nbuild_engine\n\nmake_client_tarball\nmake_server_tarball\n"
  },
  {
    "path": "scripts/gha/build_linux.sh",
    "content": "#!/bin/bash\n\n# As e2k builds for distro that's vastly different from Ubuntu/Debian and specially handles cross-compiling\n# keep it in separate script for now\nif [[ $GH_CPU_ARCH == e2k* ]]; then\n\texec bash scripts/gha/build_linux-e2k.sh\nfi\n\n. scripts/lib.sh\n\n# \"booo, bash feature!\"\ndeclare -A ARCH_TRIPLET CROSS_COMPILE_CC CROSS_COMPILE_CXX\nARCH_TRIPLET[amd64]=x86_64-linux-gnu\nARCH_TRIPLET[i386]=i386-linux-gnu\nARCH_TRIPLET[arm64]=aarch64-linux-gnu\nARCH_TRIPLET[armhf]=arm-linux-gnueabihf\nARCH_TRIPLET[riscv64]=riscv64-linux-gnu\nARCH_TRIPLET[ppc64el]=powerpc64le-linux-gnu\nCROSS_COMPILE_CC[amd64]=cc\nCROSS_COMPILE_CC[i386]=\"cc -m32\"\nCROSS_COMPILE_CXX[amd64]=c++\nCROSS_COMPILE_CXX[i386]=\"c++ -m32\"\nfor i in arm64 armhf riscv64 ppc64el; do\n\tCROSS_COMPILE_CC[$i]=${ARCH_TRIPLET[$i]}-gcc\n\tCROSS_COMPILE_CXX[$i]=${ARCH_TRIPLET[$i]}-g++\ndone\nexport PKG_CONFIG_PATH=$PWD/ffmpeg/lib/pkgconfig:${ARCH_TRIPLET[$GH_CPU_ARCH]}\nexport CC=${CROSS_COMPILE_CC[$GH_CPU_ARCH]}\nexport CXX=${CROSS_COMPILE_CXX[$GH_CPU_ARCH]}\n\nAPP=xash3d-fwgs\nAPPDIR=$APP.AppDir\nAPPIMAGE=$APP-$ARCH.AppImage\n\nAPPDIR2=$APP-linux-$ARCH # FIXME: not conforms to libpublic's build arch strings but in parity with xashds directory name\nAPPTARGZ=$APP-linux-$ARCH.tar.gz\n\nDS=xashds-linux\nDSDIR=$DS-$ARCH\nDSTARGZ=$DS-$ARCH.tar.gz\nN=$(nproc)\n\nbuild_sdl2()\n{\n\tcd \"$BUILDDIR\"/SDL2_src || die\n\n\t# a1ba: let's make something different. Rather than removing features\n\t# let's enable everything we can\n\tmkdir -p build || die\n\tpushd build || die\n\t\tcmake ../ -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"$BUILDDIR\"/SDL2_linux -DCMAKE_C_FLAGS=-O3 -DSDL_STATIC=OFF -DSDL_RENDER=OFF || die\n\t\tninja install -j$((N+1)) || die\n\tpopd || die\n}\n\nbuild_engine()\n{\n\t# Build engine\n\tcd \"$BUILDDIR\" || die\n\n\tif [ \"$ARCH\" = \"amd64\" ]; then # we need enabling 64-bit target only on Intel-compatible CPUs\n\t\tWAF_EXTRA_ARGS=\"-8\"\n\tfi\n\n\tif [ -d \"ffmpeg\" ]; then\n\t\tWAF_EXTRA_ARGS+=\" --enable-ffmpeg --enable-ffmpeg-dlopen\"\n\tfi\n\n\tif [ \"$GH_CROSSCOMPILING\" != \"true\" ]; then\n\t\tWAF_EXTRA_ARGS+=\" --enable-tests\"\n\tfi\n\n\t./waf configure $WAF_EXTRA_ARGS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stbtt --enable-utils --enable-tui --enable-dedicated || die_configure\n\n\t./waf build || die_configure\n}\n\ndeploy_engine()\n{\n\tcd \"$BUILDDIR\" || die\n\t./waf install --destdir=\"$APPDIR\" || die\n\n\tcp -av SDL2_linux/lib/libSDL2.so SDL2_linux/lib/libSDL2-* \"$APPDIR/\"\n\n\tif [ \"$GH_CPU_ARCH\" = \"i386\" ]; then\n\t\tcp -av 3rdparty/vgui_support/vgui-dev/lib/vgui.so \"$APPDIR/\"\n\tfi\n\n\tif [ -d \"ffmpeg\" ]; then\n\t\tcp -av ffmpeg/lib/libav* ffmpeg/lib/libsw* \"$APPDIR/\"\n\tfi\n}\n\nbuild_appimage()\n{\n\tdeploy_engine\n\n\tcp scripts/gha/linux/AppRun \"$APPDIR/AppRun\"\n\tcp scripts/gha/linux/xash3d-fwgs.desktop \"$APPDIR/$APP.desktop\"\n\twget \"https://raw.githubusercontent.com/FWGS/fwgs-artwork/master/xash3d/icon_512.png\" -O \"$APPDIR/$APP.png\"\n\n\tchmod +x \"$APPDIR\"/AppRun # Engine launcher & engine launcher script\n\techo \"Contents of AppImage: \"\n\tls -R \"$APPDIR\"\n\t./appimagetool.AppImage \"$APPDIR\" \"artifacts/$APPIMAGE\"\n}\n\nbuild_engine_tarball()\n{\n\tmv \"$APPDIR\" \"$APPDIR2\"\n\ttar -czvf \"artifacts/$APPTARGZ\" \"$APPDIR2\"\n}\n\nbuild_dedicated_tarball()\n{\n\tcd \"$BUILDDIR\" || die\n\t# FIXME: make an option for Waf to only install dedicated\n\tmkdir -p \"$DSDIR\"\n\tcp -v \"$APPDIR\"/filesystem_stdio.so \"$DSDIR\"\n\tmv -v \"$APPDIR\"/xash \"$DSDIR\"\n\ttar -czvf \"artifacts/$DSTARGZ\" \"$DSDIR\"\n}\n\nmkdir -p artifacts/\n\nrm -rf build # clean-up build directory\nbuild_sdl2\nbuild_engine\ndeploy_engine\nbuild_dedicated_tarball\n\nif [ -x appimagetool.AppImage ]; then\n\tbuild_appimage\nelse\n\tbuild_engine_tarball\nfi\n"
  },
  {
    "path": "scripts/gha/build_motomagx.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n. /opt/toolchains/motomagx/setenv-z6.sh\n\ncd $GITHUB_WORKSPACE || die\n\nmkdir -p Xash/valve/cl_dlls\nmkdir -p Xash/valve/dlls\n\npushd hlsdk || die\n./waf configure -T fast --enable-magx --enable-simple-mod-hacks build install --destdir=../Xash || die\npopd\n\n./waf configure -T fast --enable-magx build install --destdir=Xash/ || die\n\ncat > Xash/run.sh << 'EOF'\nmypath=${0%/*}\nLIBDIR1=/ezxlocal/download/mystuff/games/lib\nLIBDIR2=/mmc/mmca1/games/lib\nLIBDIR3=$mypath\nexport LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBDIR1:$LIBDIR2:$LIBDIR3\nexport HOME=$mypath\nexport SDL_QT_INVERT_ROTATION=1\nexport SWAP_PATH=$HOME/xash.swap\ncd $mypath\nsleep 1\n\nexec $mypath/xash -dev $@\nEOF\n\nmkdir -p artifacts/\n7z a -t7z artifacts/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/\n"
  },
  {
    "path": "scripts/gha/build_nswitch.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\ncd \"$BUILDDIR\" || die\n\nrm -rf artifacts build pkgtemp\n\nmkdir -p pkgtemp/xash3d/{valve,gearbox,bshift}/{dlls,cl_dlls} || die\nmkdir -p artifacts/ || die\n\necho \"Running build script in Docker container...\"\n\ndocker run --name xash-build --rm -v `pwd`:`pwd` -w `pwd` devkitpro/devkita64:latest bash ./scripts/gha/build_nswitch_docker.sh || die\n\necho \"Packaging artifacts...\"\n\npushd pkgtemp || die\n7z a -t7z ../artifacts/xash3d-fwgs-nswitch.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r xash3d/\npopd\n"
  },
  {
    "path": "scripts/gha/build_nswitch_docker.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\nbuild_hlsdk()\n{\n\techo \"Building HLSDK: $1 branch...\"\n\tgit checkout $1\n\n\t# This is not our bug if HLSDK doesn't build with -Werrors enabled\n\t./waf configure -T release --nswitch --disable-werror || die_configure\n\t./waf build install --destdir=../pkgtemp/xash3d || die\n}\n\ngit config --global --add safe.directory '*'\n\necho \"Setting up environment...\"\n\n# we can't actually download dkp-toolchain-vars even from here, so\nexport PORTLIBS_ROOT=${DEVKITPRO}/portlibs\nexport PATH=${DEVKITPRO}/tools/bin:${DEVKITPRO}/devkitA64/bin:$PATH\nexport TOOL_PREFIX=aarch64-none-elf-\nexport CC=${TOOL_PREFIX}gcc\nexport CXX=${TOOL_PREFIX}g++\nexport AR=${TOOL_PREFIX}gcc-ar\nexport RANLIB=${TOOL_PREFIX}gcc-ranlib\nexport PORTLIBS_PREFIX=${DEVKITPRO}/portlibs/switch\nexport PATH=$PORTLIBS_PREFIX/bin:$PATH\nexport ARCH=\"-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec\"\nexport CFLAGS=\"${ARCH} -O2 -ffunction-sections -fdata-sections\"\nexport CXXFLAGS=\"${CFLAGS}\"\nexport CPPFLAGS=\"-D__SWITCH__ -I${PORTLIBS_PREFIX}/include -isystem ${DEVKITPRO}/libnx/include\"\nexport LDFLAGS=\"${ARCH} -L${PORTLIBS_PREFIX}/lib -L${DEVKITPRO}/libnx/lib\"\nexport LIBS=\"-lnx\"\n\n# forgive me father, for I have sinned\nln -s /usr/bin/python3 /usr/bin/python\n\necho \"Building libsolder...\"\n\nmake -C libsolder install || die\n\necho \"Building engine...\"\n\n./waf configure -T release --nswitch || die_configure\n./waf build install --destdir=pkgtemp/xash3d -v || die\n\necho \"Building HLSDK...\"\n\npushd hlsdk-portable || die\nbuild_hlsdk mobile_hacks valve\nbuild_hlsdk opfor gearbox\npopd\n\n# bshift can be used from mobile_hacks branch\npushd pkgtemp/xash3d\ncp -v valve/dlls/hl_nswitch_arm64.so bshift/dlls/bshift_nswitch_arm64.so\npopd\n"
  },
  {
    "path": "scripts/gha/build_psvita.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\nbuild_hlsdk()\n{\n\techo \"Building HLSDK: $1 branch...\"\n\tgit checkout $1\n\n\t# This is not our bug if HLSDK doesn't build with -Werrors enabled\n\t./waf configure -T release --psvita --disable-werror || die_configure\n\t./waf build install --destdir=../pkgtemp/data/xash3d || die\n}\n\nexport VITASDK=/usr/local/vitasdk\nexport PATH=$VITASDK/bin:$PATH\n\nJOBS=$(($(nproc)+1))\n\ncd \"$BUILDDIR\" || die\n\nrm -rf artifacts build pkgtemp\n\nmkdir -p pkgtemp/data/xash3d/{valve,gearbox,bshift}/{dlls,cl_dlls} || die\nmkdir -p artifacts/ || die\n\necho \"Building vitaGL...\"\n\nmake -C vitaGL NO_TEX_COMBINER=1 HAVE_UNFLIPPED_FBOS=1 HAVE_PTHREAD=1 MATH_SPEEDHACK=1 DRAW_SPEEDHACK=1 -j$JOBS install || die\n\necho \"Building vrtld...\"\n\npushd vita-rtld || die\ncmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release || die_configure\ncmake --build build -- -j$JOBS || die\ncmake --install build || die\npopd\n\necho \"Building SDL...\"\n\npushd SDL || die\ncmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON -DSDL_RENDER=OFF  || die_configure\ncmake --build build -- -j$JOBS || die\ncmake --install build || die\npopd\n\necho \"Building engine...\"\n\n./waf configure -T release --psvita || die_configure\n./waf build install --destdir=pkgtemp/data/xash3d -v || die\ncp build/engine/xash.vpk pkgtemp/\n\necho \"Building HLSDK...\"\n\npushd hlsdk-portable || die\nbuild_hlsdk mobile_hacks valve\nbuild_hlsdk opfor gearbox\npopd\n\n# bshift can be used from mobile_hacks branch\npushd pkgtemp/data/xash3d\ncp -v valve/dlls/hl_psvita_armv7hf.so bshift/dlls/bshift_psvita_armv7hf.so\npopd\n\necho \"Generating default config files...\"\n\npushd pkgtemp/data/xash3d/valve\n\ntouch config.cfg\necho 'unbindall'                    >> config.cfg\necho 'bind A_BUTTON \"+use\"'         >> config.cfg\necho 'bind B_BUTTON \"+jump\"'        >> config.cfg\necho 'bind X_BUTTON \"+reload\"'      >> config.cfg\necho 'bind Y_BUTTON \"+duck\"'        >> config.cfg\necho 'bind L1_BUTTON \"+attack2\"'    >> config.cfg\necho 'bind R1_BUTTON \"+attack\"'     >> config.cfg\necho 'bind START \"escape\"'          >> config.cfg\necho 'bind DPAD_UP \"lastinv\"'       >> config.cfg\necho 'bind DPAD_DOWN \"impulse 100\"' >> config.cfg\necho 'bind DPAD_LEFT \"invprev\"'     >> config.cfg\necho 'bind DPAD_RIGHT \"invnext\"'    >> config.cfg\necho 'gl_vsync \"1\"'                 >> config.cfg\necho 'sv_autosave \"0\"'              >> config.cfg\n\ntouch video.cfg\necho 'fullscreen \"1\"' >> video.cfg\necho 'width \"960\"'    >> video.cfg\necho 'height \"544\"'   >> video.cfg\necho 'r_refdll \"gl\"'  >> video.cfg\n\ntouch opengl.cfg\necho 'gl_nosort \"1\"'  >> opengl.cfg\n\ncp *.cfg ../gearbox/\ncp *.cfg ../bshift/\n\npopd\n\necho \"Packaging artifacts...\"\n\npushd pkgtemp || die\n7z a -t7z ../artifacts/xash3d-fwgs-psvita.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r xash.vpk data/\npopd\n"
  },
  {
    "path": "scripts/gha/build_win32.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\n# Build engine\ncd $BUILDDIR\n\nWAF_EXTRA_ARGS=\"\"\n\nif [ \"$ARCH\" = \"amd64\" ]; then # we need enabling 64-bit target only on Intel-compatible CPUs\n\tWAF_EXTRA_ARGS=\"-8\"\nfi\n\nif [ -d \"ffmpeg\" ]; then\n\texport PKGCONFIG=\"$PWD/pkgconf/bin/pkgconf.exe\"\n\texport PKG_CONFIG_PATH=\"$PWD/ffmpeg/lib/pkgconfig\"\n\tWAF_EXTRA_ARGS+=\" --enable-ffmpeg --enable-ffmpeg-dlopen\"\nfi\n\n# NOTE: to build with other version use --msvc_version during configuration\n# NOTE: sometimes you may need to add WinSDK to %PATH%\n# NOTE: --enable-msvcdeps only used for CI builds, enabling it non-English versions of MSVC causes useless console spam\n./waf.bat configure -s \"SDL2_VC\" -T release --enable-utils --enable-tests --enable-lto --enable-msvcdeps --enable-tui $WAF_EXTRA_ARGS || die_configure\n./waf.bat build || die\n./waf.bat install --destdir=. || die\n\nif [ \"$ARCH\" = \"i386\" ]; then\n\tcp -v SDL2_VC/lib/x86/SDL2.dll . # Install SDL2\n\tcp -v SDL2_VC/lib/x86/SDL2.pdb .\nelif [ \"$ARCH\" = \"amd64\" ]; then\n\tcp -v SDL2_VC/lib/x64/SDL2.dll .\n\tcp -v SDL2_VC/lib/x64/SDL2.pdb .\nelse\n\tdie\nfi\n\nif [ -d \"ffmpeg\" ]; then\n\tcp -v ffmpeg/bin/av* ffmpeg/bin/sw* .\nfi\n\nWINSDK_LATEST=$(ls -1 \"C:/Program Files (x86)/Windows Kits/10/bin\" | grep -E '^10' | sort -rV | head -n1)\necho \"Latest installed Windows SDK is $WINSDK_LATEST\"\n\n\"C:/Program Files (x86)/Windows Kits/10/bin/$WINSDK_LATEST/x64/signtool.exe\" \\\n\tsign //f scripts/fwgs.pfx //fd SHA256 //p \"$FWGS_PFX_PASSWORD\" *.dll *.exe\n\nif [ \"$ARCH\" = \"i386\" ]; then # VGUI is already signed\n\tcp 3rdparty/vgui_support/vgui-dev/lib/win32_vc6/vgui.dll .\nfi\n\nmkdir -p artifacts/\n7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on \\\n\t*.dll *.exe *.pdb activities.txt \\\n\tvalve/\n"
  },
  {
    "path": "scripts/gha/deps_android.sh",
    "content": "#!/bin/bash\n\ncd $GITHUB_WORKSPACE\n\nANDROID_COMMANDLINE_TOOLS_VER=\"13114758\"\nANDROID_BUILD_TOOLS_VER=\"36.0.0\"\nANDROID_PLATFORM_VER=\"android-35\"\nANDROID_NDK_VERSION=\"28.2.13676358\"\n\necho \"Download JDK 17\"\nwget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz -qO- | tar -xzf - || exit 1\nexport JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6\nexport PATH=$PATH:$JAVA_HOME/bin\n\necho \"Download hlsdk-portable\"\ngit clone --depth 1 --recursive https://github.com/FWGS/hlsdk-portable -b mobile_hacks 3rdparty/hlsdk-portable || exit 1\n\necho \"Download SDL\"\npushd 3rdparty\nwget https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz -qO- | tar -xzf - || exit 1\nmv SDL2-$SDL_VERSION SDL\npopd\n\necho \"Download Android SDK\"\nmkdir -p sdk || exit 1\npushd sdk\nwget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_COMMANDLINE_TOOLS_VER}_latest.zip -qO sdk.zip || exit 1\nunzip -q sdk.zip || exit 1\nmv cmdline-tools tools\nmkdir -p cmdline-tools\nmv tools cmdline-tools/tools\nunset ANDROID_SDK_ROOT\nexport ANDROID_HOME=$GITHUB_WORKSPACE/sdk\nexport PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin\npopd\n\necho \"Download all needed tools and Android NDK\"\nyes | sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :)\nsdkmanager --install build-tools\\;${ANDROID_BUILD_TOOLS_VER} platform-tools platforms\\;${ANDROID_PLATFORM_VER} ndk\\;${ANDROID_NDK_VERSION}\n"
  },
  {
    "path": "scripts/gha/deps_apple.sh",
    "content": "#!/bin/bash\n\ncd $GITHUB_WORKSPACE\n\nwget https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.dmg -O SDL2.dmg\nhdiutil mount SDL2.dmg\nsudo cp -vr /Volumes/SDL2/SDL2.framework /Library/Frameworks\n\ngit clone https://github.com/FWGS/hlsdk-portable hlsdk --depth=1\n"
  },
  {
    "path": "scripts/gha/deps_linux-e2k.sh",
    "content": "#!/bin/bash\n\nset -e\n\ncd \"$GITHUB_WORKSPACE\"\n\n. scripts/lib-e2k.sh\n\nwget \"${E2K_CROSS_COMPILER_URL[$GH_CPU_ARCH]}\" -O- | sudo tar -C / -xzvf -\n\nfor i in ${E2K_PACKAGES_URLS[$GH_CPU_ARCH]}; do\n\twget \"$i\" -O package.deb\n\tar x package.deb data.tar.xz\n\ttar -xvf data.tar.xz\n\trm package.deb data.tar.xz\ndone\n"
  },
  {
    "path": "scripts/gha/deps_linux.sh",
    "content": "#!/bin/bash\nset -x\n\n# As e2k builds for distro that's vastly different from Ubuntu/Debian and specially handles cross-compiling\n# keep it in separate script for now\nif [[ $GH_CPU_ARCH == e2k* ]]; then\n\texec bash scripts/gha/deps_linux-e2k.sh\nfi\n\n. scripts/lib.sh\n\ncd \"$GITHUB_WORKSPACE\" || exit 1\n\n# \"booo, bash feature!\", -- posix sh users, probably\ndeclare -A BASE_BUILD_PACKAGES SDL_BUILD_PACKAGES APPIMAGETOOL RUST_TARGET\n\n# bzip2 and opus are added from submodules, freetype replaced by stb_truetype in this build, so it's just compiler toolchain\nBASE_BUILD_PACKAGES[common]=\"desktop-file-utils\"\nBASE_BUILD_PACKAGES[amd64]=\"build-essential\"\nBASE_BUILD_PACKAGES[i386]=\"gcc-multilib g++-multilib\"\nBASE_BUILD_PACKAGES[arm64]=\"crossbuild-essential-arm64\"\nBASE_BUILD_PACKAGES[armhf]=\"crossbuild-essential-armhf\"\nBASE_BUILD_PACKAGES[riscv64]=\"crossbuild-essential-riscv64\"\nBASE_BUILD_PACKAGES[ppc64el]=\"crossbuild-essential-ppc64el\"\n\nSDL_BUILD_PACKAGES[common]=\"cmake ninja-build\"\n# TODO: add libpipewire-0.3-dev and libdecor-0-dev after we migrate from 20.04\n# TODO: figure out how to install fcitx and ibus dev in cross compile environment on gha\n# In theory, we better run this in limited container. Right now, some preinstalled PHP shit breaks libpcre builds\n# and prevents us from installing crosscompiling packages\nSDL_BUILD_PACKAGES[amd64]=\"libasound2-dev libpulse-dev \\\n\tlibaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev \\\n\tlibxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libwayland-dev \\\n\tlibxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \\\n\tlibegl1-mesa-dev libdbus-1-dev libudev-dev\"\nSDL_BUILD_PACKAGES[i386]=\"${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:i386} libjack0:i386\" # test\nSDL_BUILD_PACKAGES[arm64]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:arm64}\nSDL_BUILD_PACKAGES[armhf]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:armhf}\nSDL_BUILD_PACKAGES[riscv64]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:riscv64}\nSDL_BUILD_PACKAGES[ppc64el]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:ppc64el}\n\nAPPIMAGETOOL[amd64]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage\nAPPIMAGETOOL[i386]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-i686.AppImage\n\n# can't run AppImageTool yet because it's compiled for these platforms natively and don't support cross compilation yet\n# uncomment when we will enable qemu-user for tests\n# APPIMAGETOOL[arm64]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage\n# APPIMAGETOOL[armhf]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-armhf.AppImage\n\nRUST_TARGET[amd64]=x86_64-unknown-linux-gnu\nRUST_TARGET[i386]=i686-unknown-linux-gnu\nRUST_TARGET[arm64]=aarch64-unknown-linux-gnu\nRUST_TARGET[armhf]=thumbv7neon-unknown-linux-gnueabihf\nRUST_TARGET[riscv64]=riscv64gc-unknown-linux-gnu\nRUST_TARGET[ppc64el]=powerpc64le-unknown-linux-gnu\n\nregenerate_sources_list()\n{\n\t# this is evil but to speed up update, specify all repositories manually\n\tsudo rm /etc/apt/sources.list\n\tsudo rm -rf /etc/apt/sources.list.d\n\n\tcodename=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2)\n\n\tfor i in \"$codename\" \"$codename-updates\" \"$codename-backports\" \"$codename-security\"; do\n\t\techo \"deb [arch=$GH_CPU_ARCH] http://azure.ports.ubuntu.com/ubuntu-ports $i main universe\" | sudo tee -a /etc/apt/sources.list\n\t\techo \"deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu $i main universe\" | sudo tee -a /etc/apt/sources.list\n\tdone\n}\n\nif [ \"$GH_CPU_ARCH\" != \"amd64\" ] && [ -n \"$GH_CPU_ARCH\" ]; then\n\tif [ \"$GH_CPU_ARCH\" != \"i386\" ]; then\n\t\tregenerate_sources_list\n\tfi\n\tsudo dpkg --add-architecture \"$GH_CPU_ARCH\"\nfi\n\nsudo apt update || die\nsudo apt install aptitude || die # aptitude is just more reliable at resolving dependencies\n\n# shellcheck disable=SC2086 # splitting is intended here\nsudo aptitude install -y ${BASE_BUILD_PACKAGES[common]} ${BASE_BUILD_PACKAGES[$GH_CPU_ARCH]} ${SDL_BUILD_PACKAGES[common]} ${SDL_BUILD_PACKAGES[$GH_CPU_ARCH]} || die\n\nif [ -n \"${APPIMAGETOOL[$GH_CPU_ARCH]}\" ]; then\n\twget -O appimagetool.AppImage \"${APPIMAGETOOL[$GH_CPU_ARCH]}\"\n\tchmod +x appimagetool.AppImage\nfi\n\nFFMPEG_ARCHIVE=$(get_ffmpeg_archive)\nwget https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.tar.xz -qO- | tar -xJf -\nmv $FFMPEG_ARCHIVE ffmpeg\n\nwget \"https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz\" -qO- | tar -xzf -\nmv \"SDL2-$SDL_VERSION\" SDL2_src\n\nrustup target add \"${RUST_TARGET[$GH_CPU_ARCH]}\"\n"
  },
  {
    "path": "scripts/gha/deps_motomagx.sh",
    "content": "#!/bin/bash\n\ncd $GITHUB_WORKSPACE\n\nsudo dpkg --add-architecture i386\nsudo apt update\nsudo apt install libc6:i386 libstdc++6:i386 gcc-multilib g++-multilib p7zip-full\n\nsudo mkdir -p /opt/toolchains\n\npushd /opt/toolchains/\nsudo git clone https://github.com/a1batross/motomagx_toolchain motomagx --depth=1\npopd\n\ngit clone https://github.com/FWGS/hlsdk-xash3d hlsdk -b mobile_hacks --depth=1\n"
  },
  {
    "path": "scripts/gha/deps_nswitch.sh",
    "content": "#!/bin/bash\n\ncd $GITHUB_WORKSPACE\n\necho \"Downloading devkitA64 docker container...\"\n\ndocker pull devkitpro/devkita64:latest || exit 1\n\necho \"Downloading libsolder...\"\n\nrm -rf libsolder\ngit clone https://github.com/fgsfdsfgs/libsolder.git --depth=1 || exit 1\n\necho \"Downloading HLSDK...\"\n\nrm -rf hlsdk-xash3d hlsdk-portable\ngit clone --recursive https://github.com/FWGS/hlsdk-portable || exit 1\n"
  },
  {
    "path": "scripts/gha/deps_psvita.sh",
    "content": "#!/bin/bash\n\ncd $GITHUB_WORKSPACE\n\n# pinning cmake version to 3.28.3, because with cmake 4.x SDL vita fork doesn't building\n# it is known problem and cmake 4.x update breaks many CI pipelines around the world :)\nsudo apt-get update || exit 1\nsudo apt-get install cmake=3.28.3-1build7\nsudo ln -sf /usr/bin/cmake /usr/local/bin/cmake\n\necho \"Downloading vitasdk...\"\n\nexport VITASDK=/usr/local/vitasdk\n\nVITAGL_SRCREV=\"064db9efb15833e18777a3e768b8b1fb2abee78f\" # lock vitaGL version to avoid compilation errors\n\ninstall_package()\n{\n\t./vdpm $1 || exit 1\n}\n\ngit clone https://github.com/vitasdk/vdpm.git --depth=1 || exit 1\npushd vdpm\n./bootstrap-vitasdk.sh || exit 1\ninstall_package taihen\ninstall_package kubridge\ninstall_package zlib\ninstall_package SceShaccCgExt\ninstall_package vitaShaRK\ninstall_package libmathneon\npopd\n\necho \"Downloading vitaGL...\"\n\ngit clone https://github.com/Rinnegatamante/vitaGL.git || exit 1\npushd vitaGL\ngit checkout $VITAGL_SRCREV || exit 1\npopd\n\necho \"Downloading vitaGL fork of SDL2...\"\n\ngit clone https://github.com/Northfear/SDL.git --depth=1 || exit 1\n\necho \"Downloading vita-rtld...\"\n\ngit clone https://github.com/fgsfdsfgs/vita-rtld.git --depth=1 || exit 1\n\necho \"Downloading HLSDK...\"\n\nrm -rf hlsdk-xash3d hlsdk-portable\ngit clone --recursive https://github.com/FWGS/hlsdk-portable || exit 1\n"
  },
  {
    "path": "scripts/gha/deps_win32.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\ncurl -L http://libsdl.org/release/SDL2-devel-$SDL_VERSION-VC.zip -o SDL2.zip\nunzip -q SDL2.zip\nmv SDL2-$SDL_VERSION SDL2_VC\n\nif [ \"$GH_CPU_ARCH\" = \"i386\" ]; then\n\trustup target add i686-pc-windows-msvc\nfi\n\ncurl -L https://github.com/FWGS/potential-meme/releases/download/prebuilts/mingw-w64-x86_64-pkgconf-1.2.3.0-1-any.pkg.tar.zst -o pkgconf.tar.zst\n7z x pkgconf.tar.zst\n7z x pkgconf.tar\nrm pkgconf.tar*\nmv mingw64 pkgconf\n\nFFMPEG_ARCHIVE=$(get_ffmpeg_archive)\ncurl -L https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.zip -o ffmpeg.zip\nif [ -f ffmpeg.zip ]; then\n\tunzip -x ffmpeg.zip\n\tmv $FFMPEG_ARCHIVE ffmpeg\nfi\n"
  },
  {
    "path": "scripts/gha/linux/AppRun",
    "content": "#!/bin/sh\n\nif [ \"$XASH3D_BASEDIR\" = \"\" ]; then\n\texport XASH3D_BASEDIR=\"$PWD\"\nfi\necho \"Xash3D FWGS installed as AppImage.\"\necho \"Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this.\"\n\nexport XASH3D_EXTRAS_PAK1=\"${APPDIR}/valve/extras.pk3\"\nexec $DEBUGGER \"${APPDIR}/xash3d\" \"$@\"\n"
  },
  {
    "path": "scripts/gha/linux/xash3d-fwgs.desktop",
    "content": "[Desktop Entry]\nCategories=Game;Shooter;\nComment=Half-Life compatible game engine\nExec=AppRun\nIcon=xash3d-fwgs\nKeywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer;\nName=Xash3D FWGS\n# Doesn't pass validation with old desktop-file-utils\n#PrefersNonDefaultGPU=true\nTerminal=false\nType=Application\n"
  },
  {
    "path": "scripts/lib-e2k.sh",
    "content": "declare -A E2K_CROSS_COMPILER_URL E2K_CROSS_COMPILER_PATH E2K_PACKAGES_URLS\n\n# NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE\n# NOTE                                                                       NOTE\n# NOTE  The cross-compiler is officially distributed on dev.mcst.ru website. NOTE\n# NOTE   setwd.ws is OpenE2K's unofficial mirror for Elbrus dev community.   NOTE\n# NOTE Do NOT hammer down the server with your CI/CD, AI and other bullshit! NOTE\n# NOTE           Cache the archives on YOUR OWN INFRASTRUCTURE!              NOTE\n# NOTE                                                                       NOTE\n# NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE\nE2K_CROSS_COMPILER_URL[e2k-4c]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v3.5.10_64.tgz\"\nE2K_CROSS_COMPILER_URL[e2k-1c]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v4.1c%2B.5.10_64.tgz\"\nE2K_CROSS_COMPILER_URL[e2k-8c]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v4.5.10_64.tgz\"\nE2K_CROSS_COMPILER_URL[e2k-8c2]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v5.5.10_64.tgz\"\nE2K_CROSS_COMPILER_URL[e2k-16c]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v6.5.10-e16c_64.tgz\"\nE2K_CROSS_COMPILER_URL[e2k-2c3]=\"https://setwd.ws/sp/1.27/1.27.21/cross-sp-rel-1.27.21.e2k-v6.5.10-e2c3_64.tgz\"\n\n# TODO: fill in the gaps\nE2K_CROSS_COMPILER_PATH[e2k-8c]=\"/opt/mcst/lcc-1.27.21.e2k-v4.5.10/\"\n\n# TODO: split by some character, as bash can't assign lists to array members\nE2K_PACKAGES_URLS[e2k-4c]=\"https://setwd.ws/osl/8.2/pool/main/e2k-4c/SDL2_2.30.0-vd8u4_e2k-4c.deb\"\nE2K_PACKAGES_URLS[e2k-8c]=\"https://setwd.ws/osl/8.2/pool/main/e2k-8c/SDL2_2.30.0-vd8u4_e2k-8c.deb\"\nE2K_PACKAGES_URLS[e2k-8c2]=\"https://setwd.ws/osl/8.2/pool/main/e2k-8c2/SDL2_2.30.0-vd8u4_e2k-8c2.deb\"\nE2K_PACKAGES_URLS[e2k-16c]=\"https://setwd.ws/osl/8.2/pool/main/e2k-16c/SDL2_2.30.0-vd8u4_e2k-16c.deb\"\n"
  },
  {
    "path": "scripts/lib.sh",
    "content": "die()\n{\n\texit 1\n}\n\ndie_configure()\n{\n\tcat build/config.log\n\tdie\n}\n\nget_ffmpeg_archive()\n{\n\tif [ \"$GH_CPU_OS\" == \"win32\" ]; then\n\t\tA=win\n\telse\n\t\tA=\"$GH_CPU_OS\"\n\tfi\n\n\tif [ \"$GH_CPU_ARCH\" == \"amd64\" ]; then\n\t\tB=64\n\telif [ \"$GH_CPU_ARCH\" == \"i386\" ]; then\n\t\tB=32\n\telse\n\t\tB=\"$GH_CPU_ARCH\"\n\tfi\n\n\tFLAVOR=lgpl-shared-minimal\n\n\techo \"ffmpeg-n$FFMPEG_VERSION-latest-$A$B-$FLAVOR-$FFMPEG_VERSION\"\n}\n\nif [ -n \"$TRAVIS_BUILD_DIR\" ]; then\n\tBUILDDIR=$TRAVIS_BUILD_DIR\nelif [ -n \"$GITHUB_WORKSPACE\" ]; then\n\tBUILDDIR=$GITHUB_WORKSPACE\nfi\n\nif [ -n \"$TRAVIS_CPU_ARCH\" ]; then\n\tARCH=$TRAVIS_CPU_ARCH\nelif [ -n \"$GH_CPU_ARCH\" ]; then\n\tARCH=$GH_CPU_ARCH\nfi\n"
  },
  {
    "path": "scripts/makepak.py",
    "content": "from __future__ import print_function\nimport sys\nimport struct\nimport os\n\n#dummy class for stuffing the file headers into\nclass FileEntry:\n\tpass\n\n#arguments are source directory, then target filename e.g. \"pak1.pak\"\nrootdir = sys.argv[1]\npakfilename = sys.argv[2]\n\npakfile = open(pakfilename,\"wb\")\n\n#write a dummy header to start with\npakfile.write(struct.Struct(\"<4s2l\").pack(b\"PACK\",0,0))\n\n#walk the directory recursively, add the files and record the file entries\noffset = 12\nfileentries = []\nfor root, subFolders, files in os.walk(rootdir):\n\tfor file in files:\n\t\tentry = FileEntry()\n\t\timpfilename = os.path.join(root,file)\n\t\tentry.filename = os.path.relpath(impfilename,rootdir).replace(\"\\\\\",\"/\")\n\t\tif(entry.filename.startswith(\".\")):continue\n\t\tprint(\"pak: \"+entry.filename)\n\t\twith open(impfilename, \"rb\") as importfile:\n\t\t\tpakfile.write(importfile.read())\n\t\t\tentry.offset = offset\n\t\t\tentry.length = importfile.tell()\n\t\t\toffset = offset + entry.length\n\t\tfileentries.append(entry)\ntablesize = 0\n\n#after all the file data, write the list of entries\nfor entry in fileentries:\n\tpakfile.write(struct.Struct(\"<56s\").pack(entry.filename.encode(\"ascii\")))\n\tpakfile.write(struct.Struct(\"<l\").pack(entry.offset))\n\tpakfile.write(struct.Struct(\"<l\").pack(entry.length))\n\ttablesize = tablesize + 64\n\n#return to the header and write the values correctly\npakfile.seek(0)\npakfile.write(struct.Struct(\"<4s2l\").pack(b\"PACK\",offset,tablesize))\n"
  },
  {
    "path": "scripts/sailfish/build.sh",
    "content": "#!/bin/bash\n\n. scripts/lib.sh\n\ndownload_sign_key()\n{\n\techo -n \"Downloading $1: \"\n\tcurl https://community.omprussia.ru/documentation/files/doc/$1 -o $1 &> /dev/null || die \"Can't download $1\" && echo \"OK\"\n}\n\npack_sources()\n{\n\techo \"Packing sources to $1...\"\n\tgit ls-files --recurse-submodules | tar caf \"$1\" -T-\n}\n\ndependencies=\"SDL2-devel ImageMagick\"\nbuild_dir=\"build_rpm\"\n\nif [ \"$1\" == \"aurora\" ]; then\n\tname=\"su.xash.Engine\"\n\tflavor=\"aurora\"\nelse\n\tname=\"harbour-xash3d-fwgs\"\n\tflavor=\"sailfish\"\nfi\n\nif [ $flavor == 'aurora' ]; then\n\tdownload_sign_key regular_key.pem\n\tdownload_sign_key regular_cert.pem\nfi\n\nrm -fr ${build_dir}/{BUILD,SRPMS}\nmkdir -p ${build_dir}/SOURCES\npack_sources ${build_dir}/SOURCES/${name}.tar\ngit clone https://github.com/FWGS/hlsdk-portable -b mobile_hacks\npushd hlsdk-portable\npack_sources ../${build_dir}/SOURCES/hlsdk-portable.tar\npopd\n\nsfdk_targets=`sfdk engine exec sb2-config -l | grep default | grep -v i486`\n\nfor each in $sfdk_targets; do\n\ttarget_arch=${each##*-}\n\ttarget_arch=${target_arch/.default/}\n\techo \"Build for '$each' target with '$target_arch' architecture\"\n\n\t# install deps for current target\n\tsfdk engine exec sb2 -t $each -R -m sdk-install zypper in -y ${dependencies}\n\n\t# build RPM for current target\n\tsfdk engine exec sb2 -t $each rpmbuild \\\n\t\t--define \"_topdir `pwd`/${build_dir}\" \\\n\t\t--define \"_arch $target_arch\" \\\n\t\t--define \"_packagename $name\" \\\n\t\t--define \"_flavor $flavor\" \\\n\t\t-ba scripts/sailfish/harbour-xash3d-fwgs.spec || die \"Build for ${each}: FAIL\"\n\n\t# sign RPM packacge\n\tif [ \"$flavor\" == \"aurora\" ]; then\n\t\techo -n \"Signing RPMs: \"\n\t\tsfdk engine exec sb2 -t $each rpmsign-external sign --key `pwd`/regular_key.pem --cert `pwd`/regular_cert.pem `pwd`/${build_dir}/RPMS/${target_arch}/$name-0*.rpm || die \"FAIL\" && echo \"OK\"\n\tfi\n\n\techo -n \"Validate RPM: \"\n\tif [ \"$flavor\" == \"aurora\" ]; then\n\t\tvalidator_output=`sfdk engine exec rpm-validator -p regular $(pwd)/${build_dir}/RPMS/${target_arch}/$name-0* 2>&1`\n\telse\n\t\tsfdk config target=${each/.default/}\n\t\tvalidator_output=`sfdk check $(pwd)/${build_dir}/RPMS/${target_arch}/$name-0* 2>&1`\n\tfi\n\tif [ $? -ne 0 ] ; then\n\t\techo \"FAIL\"\n\t\techo \"${validator_output}\"\n\t\tbreak;\n\tfi\n\techo \"OK\"\ndone\necho \"All build done! All your packages in `pwd`/build_rpm/RPMS\"\n"
  },
  {
    "path": "scripts/sailfish/deploy.sh",
    "content": "#!/bin/bash\n\nPASSWORD=\"12345\"\nIP=\"192.168.1.10\"\n\nif [ \"$1\" == \"aurora\" ]; then\n\tname=\"su.xash.Engine\"\n\tarch=\"armv7hl\" # lame!\nelse\n\tname=\"harbour-xash3d-fwgs\"\n\tarch=\"aarch64\" # absolutely lame...\nfi\npackage=$(ls build_rpm/RPMS/$arch/$name-0*.rpm)\n\nsshpass -p $PASSWORD scp $package defaultuser@$IP:~/\n\n# sandwich of programs:\n# 1. Call sshpass to automatically fill password for ssh session\n# 2. Call devel-su that reads password from stdin\n# 3. devel-su calls pkcon install-local -y and installs the package\necho $PASSWORD | sshpass -p $PASSWORD ssh defaultuser@$IP devel-su pkcon install-local -y $(basename $package)\n"
  },
  {
    "path": "scripts/sailfish/harbour-xash3d-fwgs.desktop",
    "content": "[Desktop Entry]\nCategories=Game;Shooter;\nComment=Half-Life compatible game engine\nExec=__REPLACE_EXEC__\nIcon=__REPLACE_ICON__\nKeywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer;\nName=Xash3D FWGS\nName[en]=Xash3D FWGS\nPrefersNonDefaultGPU=true\nTerminal=false\nType=Application\nX-Nemo-Application-Type=silica-qt5\n\n[X-Sailjail]\nPermissions=Audio;Internet;Microphone;PublicDir;\nOrganizationName=su.xash\nApplicationName=Engine\n"
  },
  {
    "path": "scripts/sailfish/harbour-xash3d-fwgs.spec",
    "content": "# Based on harbour-quake2 spec file\n\nName: %{_packagename}\nSummary: Xash3D FWGS\nRelease: 1\nVersion: 0.21\nGroup: Amusements/Games\nLicense: GPLv2\nBuildArch: %{_arch}\nURL: https://github.com/FWGS/xash3d-fwgs\nSource0: %{name}.tar\nSource1: hlsdk-portable.tar\nBuildRequires: SDL2-devel ImageMagick\n\n%define __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$\n\n%description\nXash3D FWGS is a game engine compatible with Half-Life 1 and mods.\n\n%prep\ntar -xf %{_topdir}/SOURCES/%{name}.tar\npython3 waf configure \\\n\t-T release \\\n\t--sailfish=%{_flavor} \\\n\t--enable-stbtt \\\n\t--enable-bundled-deps \\\n\t--enable-packaging \\\n\t--disable-gl \\\n\t--enable-gles2 \\\n\t--enable-gl4es \\\n\t--prefix=/usr \\\n\t--libdir=%{_datadir}/%{name}/lib \\\n\t--bindir=%{_bindir}\n\nmkdir -p hlsdk-portable\npushd hlsdk-portable\ntar -xf %{_topdir}/SOURCES/hlsdk-portable.tar\npython3 waf configure -T release\npopd\n\n%build\npython3 waf build -j$(($(nproc)+1))\npushd hlsdk-portable\npython3 waf build -j$(($(nproc)+1))\npopd\n\n%install\npython3 waf install --destdir=%{buildroot}\npushd hlsdk-portable\npython3 waf install --destdir=%{buildroot}%{_datadir}/%{name}/rodir\npopd\n# rename real binary\nmv %{buildroot}/usr/bin/xash3d %{buildroot}/usr/bin/%{name}\n\ninstall -d %{buildroot}/%{_datadir}/applications\nsed \"s/__REPLACE_ICON__/su.xash.Engine/g;s/__REPLACE_EXEC__/su.xash.Engine/g;\" scripts/sailfish/harbour-xash3d-fwgs.desktop > %{buildroot}/%{_datadir}/applications/%{name}.desktop\nchmod 644 %{buildroot}/%{_datadir}/applications/%{name}.desktop\n\ninstall -d %{buildroot}/%{_datadir}/icons/hicolor/86x86/apps\ninstall -d %{buildroot}/%{_datadir}/icons/hicolor/108x108/apps\ninstall -d %{buildroot}/%{_datadir}/icons/hicolor/128x128/apps\ninstall -d %{buildroot}/%{_datadir}/icons/hicolor/172x172/apps\nconvert game_launch/icon-xash-material.png -resize 86x86 %{buildroot}/%{_datadir}/icons/hicolor/86x86/apps/%{name}.png\nconvert game_launch/icon-xash-material.png -resize 108x108 %{buildroot}/%{_datadir}/icons/hicolor/108x108/apps/%{name}.png\nconvert game_launch/icon-xash-material.png -resize 128x128 %{buildroot}/%{_datadir}/icons/hicolor/128x128/apps/%{name}.png\nconvert game_launch/icon-xash-material.png -resize 172x172 %{buildroot}/%{_datadir}/icons/hicolor/172x172/apps/%{name}.png\n\n%files\n%defattr(-,root,root,-)\n%attr(755,root,root) %{_bindir}/%{name}\n%attr(755,root,root) %{_datadir}/%{name}/lib/*\n%attr(644,root,root) %{_datadir}/%{name}/rodir/valve/extras.pk3\n%attr(755,root,root) %{_datadir}/%{name}/rodir/valve/cl_dlls/*\n%attr(755,root,root) %{_datadir}/%{name}/rodir/valve/dlls/*\n%attr(644,root,root) %{_datadir}/icons/hicolor/86x86/apps/%{name}.png\n%attr(644,root,root) %{_datadir}/icons/hicolor/108x108/apps/%{name}.png\n%attr(644,root,root) %{_datadir}/icons/hicolor/128x128/apps/%{name}.png\n%attr(644,root,root) %{_datadir}/icons/hicolor/172x172/apps/%{name}.png\n%{_datadir}/applications/%{name}.desktop\n\n%changelog\n* Thu Jun 1 2023 a1batross <a1ba.omarov@gmail.com>\n- initial port\n"
  },
  {
    "path": "scripts/sailfish/run.sh",
    "content": "#!/bin/sh\n\ndie()\n{\n        echo \"$@\"\n        exit 1\n}\n\necho \"Xash3D FWGS installed as Sailfish RPM\"\n\n# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n# $XDG_DATA_HOME defines the base directory relative to which user-specific data files should be stored.\n# If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.\nif [ -z \"$XDG_DATA_HOME\" ]; then\n        export XDG_DATA_HOME=\"$HOME/.local/share\"\nfi\n\nif [ -z \"$XASH3D_BASEDIR\" ]; then\n        export XASH3D_BASEDIR=\"$XDG_DATA_HOME/xash3d-fwgs/\"\nfi\n\nmkdir -p \"$XASH3D_BASEDIR\"\ncd \"$XASH3D_BASEDIR\" || die \"Can't cd into $XASH3D_BASEDIR\"\necho \"XASH3D_BASEDIR is $XASH3D_BASEDIR\"\n\n#if [ -z \"$XASH3D_EXTRAS_PAK1\" ]; then\n#        export XASH3D_EXTRAS_PAK1=/app/share/xash3d/valve/extras.pk3\n#fi\n#echo \"XASH3D_EXTRAS_PAK1 is $XASH3D_EXTRAS_PAK1\"\n\nexec $DEBUGGER /app/lib32/xash3d/xash3d \"$@\"\n"
  },
  {
    "path": "scripts/waifulib/c_emscripten.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 vi:ts=4:noexpandtab\n\nfrom waflib.Tools import ccroot, gcc, gxx\nfrom waflib.Configure import conf\nfrom waflib.TaskGen import feature, after_method\nfrom waflib import Utils\n\n@conf\ndef get_emscripten_version(conf, cc):\n\tk = conf.get_cc_version(cc, clang=True)\n\n\tif '__EMSCRIPTEN__' not in k:\n\t\tconf.fatal('Could not determine the emscripten compiler version')\n\n\tconf.env.DEST_OS = 'emscripten'\n\tconf.env.DEST_CPU = 'wasm'\n\n@conf\ndef find_emcc(conf):\n\tcc = conf.find_program(['emcc'], var='CC')\n\tconf.get_emscripten_version(cc)\n\tconf.env.CC_NAME = 'clang'\n\n@conf\ndef find_emxx(conf):\n\tcxx = conf.find_program(['em++'], var='CXX')\n\tconf.get_emscripten_version(cxx)\n\tconf.env.CXX_NAME = 'clang'\n\n@conf\ndef gcc_modifier_emscripten(conf):\n\tv = conf.env\n\n\tconf.env.cshlib_PATTERN = 'lib%s.wasm'\n\tconf.env.cprogram_PATTERN = '%s.html'\n\n\tconf.env.CFLAGS_cshlib = ['-fPIC', '-sSIDE_MODULE=1']\n\tconf.env.CFLAGS_cstlib = ['-fPIC']\n\tconf.env.CFLAGS_cprogram = ['-sMAIN_MODULE=1']\n\n\tconf.env.LINKFLAGS_cshlib = ['-sSIDE_MODULE=1']\n\tconf.env.LINKFLAGS_cprogram = ['-sMAIN_MODULE=1']\n\n@conf\ndef gxx_modifier_emscripten(conf):\n\tv = conf.env\n\n\tconf.env.cxxshlib_PATTERN = 'lib%s.wasm'\n\tconf.env.cxxprogram_PATTERN = '%s.html'\n\tconf.env.CXXFLAGS_cxxshlib = ['-fPIC', '-sSIDE_MODULE=1']\n\tconf.env.CXXFLAGS_cxxstlib = ['-fPIC']\n\tconf.env.CXXFLAGS_cxxprogram = ['-sMAIN_MODULE=1']\n\n\tconf.env.LINKFLAGS_cxxshlib = ['-sSIDE_MODULE=1']\n\tconf.env.LINKFLAGS_cxxprogram = ['-sMAIN_MODULE=1']\n\n@feature('cxxprogram', 'cprogram')\n@after_method('apply_link')\ndef apply_indexhtml(self):\n\tif self.env.DEST_OS != 'emscripten':\n\t\treturn\n\n\ttsk = self.link_task\n\tnode = tsk.outputs[0]\n\n\ttsk.outputs.append(node.change_ext('.js'))\n\ttsk.outputs.append(node.change_ext('.wasm'))\n\n\tif '--preload-file' in getattr(self, 'linkflags', []) + self.env.LINKFLAGS:\n\t\ttsk.outputs.append(node.change_ext('.data'))\n\n\tinst_to = getattr(self, 'special_install_path', None)\n\tif inst_to:\n\t\tself.add_install_as(install_to=inst_to + '/index.html',\n\t\t\tinstall_from=tsk.outputs[0], chmod=Utils.O644, task=tsk)\n\n\t\tself.add_install_files(install_to=inst_to,\n\t\t\tinstall_from=tsk.outputs[1:], chmod=Utils.O644, task=tsk)\n\ndef configure(conf):\n\tif not conf.env.CC:\n\t\tconf.find_emcc()\n\t\tconf.gcc_common_flags()\n\t\tconf.gcc_modifier_platform()\n\t\tconf.cc_load_tools()\n\t\tconf.cc_add_flags()\n\t\tconf.link_add_flags()\n\n\tif not conf.env.AR:\n\t\tconf.find_program(['emar'], var='AR')\n\t\tconf.find_ar()\n\n\tif not conf.env.CXX:\n\t\tconf.find_emxx()\n\t\tconf.gxx_common_flags()\n\t\tconf.gxx_modifier_platform()\n\t\tconf.cxx_load_tools()\n\t\tconf.cxx_add_flags()\n\t\tconf.link_add_flags()\n"
  },
  {
    "path": "scripts/waifulib/compiler_optimizations.py",
    "content": "# encoding: utf-8\n# compiler_optimizations.py -- main entry point for configuring C/C++ compilers\n# Copyright (C) 2021 a1batross\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\ntry: from fwgslib import get_flags_by_type, get_flags_by_compiler\nexcept: from waflib.extras.fwgslib import get_flags_by_type, get_flags_by_compiler\nfrom waflib.Configure import conf\nfrom waflib import Logs\n\n'''\nFlags can be overriden and new types can be added\nby importing this as normal Python module\n\nExample:\n#!/usr/bin/env python\nfrom waflib.extras import compiler_optimizations\n\ncompiler_optimizations.VALID_BUILD_TYPES += 'gottagofast'\ncompiler_optimizations.CFLAGS['gottagofast'] = {\n\t'gcc': ['-Ogentoo']\n}\n'''\n\nVALID_BUILD_TYPES = ['fastnative', 'fast', 'humanrights', 'debug', 'sanitize', 'msan', 'asan', 'none']\n\nLINKFLAGS = {\n\t'common': {\n\t\t'msvc':  ['/DEBUG'], # always create PDB, doesn't affect result binaries\n\t\t'gcc':   ['-Wl,--no-undefined'],\n\t\t'owcc':  ['-Wl,option stack=512k']\n\t},\n\t'msan': {\n\t\t'clang': ['-fsanitize=memory', '-pthread'],\n\t\t'default': ['NO_MSAN_HERE']\n\t},\n\t'asan': {\n\t\t'clang': ['-fsanitize=address', '-pthread'],\n\t\t'gcc':   ['-fsanitize=address', '-pthread'],\n\t\t'msvc': ['/SAFESEH:NO']\n\t},\n\t'sanitize': {\n\t\t'clang': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'],\n\t\t'gcc':   ['-fsanitize=undefined', '-fsanitize=address', '-pthread'],\n\t\t'msvc': ['/SAFESEH:NO']\n\t},\n\t'debug': {\n\t\t'msvc': ['/INCREMENTAL', '/SAFESEH:NO']\n\t}\n}\n\nCFLAGS = {\n\t'common': {\n\t\t# disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP\n\t\t'msvc':    ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP', '/Zc:__cplusplus'],\n\t\t'clang':   ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics', '-fasynchronous-unwind-tables'],\n\t\t'gcc':     ['-g', '-fvisibility=hidden', '-fasynchronous-unwind-tables'],\n\t\t'owcc':\t   ['-fno-short-enum', '-ffloat-store', '-g3']\n\t},\n\t'fast': {\n\t\t'msvc':    ['/O2', '/Oy', '/Zi'],\n\t\t'gcc': {\n\t\t\t'3':       ['-O3', '-fomit-frame-pointer'],\n\t\t\t'4':       ['-Ofast', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer'],\n\t\t\t'default': ['-Ofast', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer', '-fno-semantic-interposition']\n\t\t},\n\t\t'clang':   ['-Ofast'],\n\t\t'default': ['-O3']\n\t},\n\t'fastnative': {\n\t\t'msvc':    ['/O2', '/Oy', '/Zi'],\n\t\t'gcc':     ['-Ofast', '-march=native', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer', '-fno-semantic-interposition'],\n\t\t'clang':   ['-Ofast', '-march=native'],\n\t\t'default': ['-O3']\n\t},\n\t'humanrights': {\n\t\t'msvc':    ['/O2', '/Zi'],\n\t\t'owcc':    ['-O3', '-foptimize-sibling-calls', '-fomit-leaf-frame-pointer', '-fomit-frame-pointer', '-fschedule-insns', '-funsafe-math-optimizations', '-funroll-loops', '-frerun-optimizer', '-finline-functions', '-finline-limit=512', '-fguess-branch-probability', '-fno-strict-aliasing', '-floop-optimize'],\n\t\t'gcc': {\n\t\t\t'4':   ['-O3'],\n\t\t\t'3':   ['-O3'],\n\t\t\t'default': ['-O3','-fno-semantic-interposition'],\n\t\t},\n\t\t'default': ['-O3']\n\t},\n\t'debug': {\n\t\t'msvc':    ['/Od', '/ZI'],\n\t\t'owcc':    ['-O0', '-fno-omit-frame-pointer', '-funwind-tables', '-fno-omit-leaf-frame-pointer'],\n\t\t'default': ['-O0']\n\t},\n\t'msan': {\n\t\t'clang':   ['-O2', '-g', '-fno-omit-frame-pointer', '-fsanitize=memory', '-pthread'],\n\t\t'default': ['NO_MSAN_HERE']\n\t},\n\t'asan': {\n\t\t'msvc':    ['/Od', '/RTC1', '/Zi', '/fsanitize=address'],\n\t\t'gcc':     ['-Og', '-fsanitize=address', '-pthread'],\n\t\t'clang':   ['-Og', '-fsanitize=address', '-pthread'],\n\t\t'default': ['-O0']\n\t},\n\t'sanitize': {\n\t\t'msvc':    ['/Od', '/RTC1', '/Zi', '/fsanitize=address'],\n\t\t'gcc':     ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'],\n\t\t'clang':   ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'],\n\t\t'default': ['-O0']\n\t},\n}\n\nLTO_CFLAGS = {\n\t'msvc':  ['/GL'],\n\t'gcc':   ['-flto=auto'],\n\t'clang': ['-flto']\n}\n\nLTO_LINKFLAGS = {\n\t'msvc':  ['/LTCG'],\n\t'gcc':   ['-flto=auto'],\n\t'clang': ['-flto']\n}\n\nPOLLY_CFLAGS = {\n\t'gcc':   ['-fgraphite-identity'],\n\t'clang': ['-mllvm', '-polly']\n\t# msvc sosat :(\n}\n\nOPENMP_CFLAGS = {\n\t'gcc':   ['-fopenmp', '-DHAVE_OPENMP=1'],\n\t'clang': ['-fopenmp', '-DHAVE_OPENMP=1'],\n\t'msvc':  ['/openmp', '/DHAVE_OPENMP=1']\n}\n\nOPENMP_LINKFLAGS = {\n\t'gcc':   ['-fopenmp'],\n\t'clang': ['-fopenmp'],\n}\n\nPROFILE_GENERATE_CFLAGS = {\n\t'gcc':   ['-fprofile-generate=xash3d-prof'],\n}\n\nPROFILE_GENERATE_LINKFLAGS = {\n\t'gcc':   ['-fprofile-generate=xash3d-prof'],\n}\n\nPROFILE_USE_CFLAGS = {\n\t'gcc':   ['-fprofile-use=%s'],\n}\n\nPROFILE_USE_LINKFLAGS = {\n\t'gcc':   ['-fprofile-use=%s'],\n}\n\ndef options(opt):\n\tgrp = opt.add_option_group('Compiler optimization options')\n\n\tgrp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default='humanrights',\n\t\thelp = 'build type: debug, release or none(custom flags)')\n\n\tgrp.add_option('--enable-lto', action = 'store_true', dest = 'LTO', default = False,\n\t\thelp = 'enable Link Time Optimization if possible [default: %(default)s]')\n\n\tgrp.add_option('--enable-poly-opt', action = 'store_true', dest = 'POLLY', default = False,\n\t\thelp = 'enable polyhedral optimization if possible [default: %(default)s]')\n\n\tgrp.add_option('--enable-openmp', action = 'store_true', dest = 'OPENMP', default = False,\n\t\thelp = 'enable OpenMP extensions [default: %(default)s]')\n\n\tgrp.add_option('--enable-profile', action = 'store_true', dest = 'PROFILE_GENERATE', default = False,\n\t\thelp = 'enable profile generating build (stored in xash3d-prof directory) [default: %(default)s]')\n\n\tgrp.add_option('--enable-limited-debuginfo', action = 'store_true', dest = 'LIMITED_DEBUGINFO', default = False,\n\t\thelp = 'only save line debuginfo, useful for release builds [default: %(default)s]')\n\n\tgrp.add_option('--use-profile', action = 'store', dest = 'PROFILE_USE', default = None,\n\t\thelp = 'use profile during build [default: %(default)s]')\n\ndef configure(conf):\n\tconf.start_msg('Build type')\n\n\t# legacy naming for default release build\n\t# https://chaos.social/@karolherbst/111340511652012860\n\tif conf.options.BUILD_TYPE == 'release':\n\t\tconf.options.BUILD_TYPE = 'humanrights'\n\n\tif not conf.options.BUILD_TYPE in VALID_BUILD_TYPES:\n\t\tconf.end_msg(conf.options.BUILD_TYPE, color='RED')\n\t\tconf.fatal('Invalid build type. Valid are: %s' % ', '.join(VALID_BUILD_TYPES))\n\n\tconf.end_msg(conf.options.BUILD_TYPE)\n\n\tconf.msg('LTO build', 'yes' if conf.options.LTO else 'no')\n\tconf.msg('PolyOpt build', 'yes' if conf.options.POLLY else 'no')\n\tconf.msg('OpenMP build', 'yes' if conf.options.OPENMP else 'no')\n\tconf.msg('Generate profile', 'yes' if conf.options.PROFILE_GENERATE else 'no')\n\tconf.msg('Use profile', conf.options.PROFILE_USE if not conf.options.PROFILE_GENERATE else 'no')\n\n\t# -march=native should not be used\n\tif conf.options.BUILD_TYPE.startswith('fast'):\n\t\tLogs.warn('WARNING: \\'%s\\' build type should not be used in release builds', conf.options.BUILD_TYPE)\n\n\ttry:\n\t\tconf.env.CC_VERSION[0]\n\texcept IndexError:\n\t\tconf.env.CC_VERSION = (0,)\n\n@conf\ndef get_optimization_flags(conf):\n\t'''Returns a list of compile flags,\n\tdepending on build type and options set by user\n\n\tNOTE: it doesn't filter out unsupported flags\n\n\t:returns: tuple of cflags and linkflags\n\t'''\n\tlinkflags = conf.get_flags_by_type(LINKFLAGS, conf.options.BUILD_TYPE, conf.env.COMPILER_CC, conf.env.CC_VERSION[0])\n\n\tcflags = conf.get_flags_by_type(CFLAGS, conf.options.BUILD_TYPE, conf.env.COMPILER_CC, conf.env.CC_VERSION[0])\n\n\tif conf.options.LTO:\n\t\tlinkflags+= conf.get_flags_by_compiler(LTO_LINKFLAGS, conf.env.COMPILER_CC)\n\t\tcflags   += conf.get_flags_by_compiler(LTO_CFLAGS, conf.env.COMPILER_CC)\n\n\tif conf.options.POLLY:\n\t\tcflags   += conf.get_flags_by_compiler(POLLY_CFLAGS, conf.env.COMPILER_CC)\n\n\tif conf.options.OPENMP:\n\t\tlinkflags+= conf.get_flags_by_compiler(OPENMP_LINKFLAGS, conf.env.COMPILER_CC)\n\t\tcflags   += conf.get_flags_by_compiler(OPENMP_CFLAGS, conf.env.COMPILER_CC)\n\n\tif conf.options.PROFILE_GENERATE:\n\t\tlinkflags+= conf.get_flags_by_compiler(PROFILE_GENERATE_LINKFLAGS, conf.env.COMPILER_CC)\n\t\tcflags   += conf.get_flags_by_compiler(PROFILE_GENERATE_CFLAGS, conf.env.COMPILER_CC)\n\telif conf.options.PROFILE_USE:\n\t\tlinkflags+= [conf.get_flags_by_compiler(PROFILE_USE_LINKFLAGS, conf.env.COMPILER_CC)[0] % conf.options.PROFILE_USE]\n\t\tcflags   += [conf.get_flags_by_compiler(PROFILE_USE_CFLAGS, conf.env.COMPILER_CC)[0] % conf.options.PROFILE_USE]\n\n\tif conf.env.DEST_OS == 'nswitch':\n\t\tif conf.options.BUILD_TYPE == 'debug':\n\t\t\t# enable remote debugger\n\t\t\tcflags.append('-DNSWITCH_DEBUG')\n\t\t# this port don't have stack printing support\n\t\tcflags.remove('-fasynchronous-unwind-tables')\n\telif conf.env.DEST_OS == 'psvita':\n\t\t# this optimization is broken in vitasdk\n\t\tcflags.append('-fno-optimize-sibling-calls')\n\t\t# remove fvisibility to allow everything to be exported by default\n\t\tcflags.remove('-fvisibility=hidden')\n\t\t# this port don't have stack printing support\n\t\tcflags.remove('-fasynchronous-unwind-tables')\n\n\tif conf.env.COMPILER_CC in ['gcc', 'clang'] and conf.options.LIMITED_DEBUGINFO:\n\t\t# probably not a good idea to do this, but it should save space on Android builds especially\n\t\t# that are never going to be run under debugger, but we still want that readable fileline\n\t\t# info in backtraces\n\t\t# might enable this for release/fast/fastnative builds in the future\n\t\tcflags = ['-gline-tables-only' if flag.startswith('-g') else flag for flag in cflags]\n\n\tif conf.env.COMPILER_CC in ['gcc', 'clang'] and conf.env.DEST_OS not in ['android']:\n\t\t# HLSDK by default compiles with these options under Linux\n\t\t# no reason for us to not do the same\n\t\tif conf.env.DEST_CPU == 'x86':\n\t\t\tcflags.append('-march=pentium-m')\n\t\t\tcflags.append('-mtune=core2')\n\n\t# on all compilers (except MSVC?) we need to copy CFLAGS to LINKFLAGS\n\tif conf.options.LTO and conf.env.COMPILER_CC != 'msvc':\n\t\tlinkflags += cflags\n\n\treturn cflags, linkflags\n"
  },
  {
    "path": "scripts/waifulib/glslc.py",
    "content": "# encoding: utf-8\n# a1batross, 2020\n\nimport os\nfrom waflib import *\nfrom waflib.Tools import c_preproc, ccroot\n\ndef configure(conf):\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.find_program('glslc', path_list=[os.path.join(conf.env.VULKAN_SDK, 'Bin')])\n\telse:\n\t\tconf.find_program('glslc')\n\n\n\tconf.add_os_flags('GLSLCPPFLAGS', dup=False)\n\tconf.add_os_flags('GLSLCFLAGS', dup=False)\n\n\tv = conf.env\n\n\tv.GLSLCINCLUDES = []\n\tv.GLSLCDEFINES  = []\n\n\tv.GLSLCPPPATH_ST = '-I%s'\n\tv.GLSLDEFINES_ST = '-D%s'\n\tv.GLSLC_SRC_F    = []\n\tv.GLSLC_TGT_F    = ['-c', '-o']\n\nclass glsl(Task.Task):\n\tcolor = 'PINK'\n\trun_str = '${GLSLC} ${GLSLCFLAGS} ${GLSLCPPPATH_ST:INCPATHS} ${GLSLDEFINES_ST:GLSLCDEFINES} ${GLSLC_SRC_F}${SRC} ${GLSLC_TGT_F}${TGT[0].abspath()} ${GLSLCPPFLAGS}'\n\tvars = ['GLSLCDEPS'] # unused variable to depend on, just in case\n\text_in  = ['.h'] # set the build order easily by using ext_out=['.h']\n\tscan = c_preproc.scan\n\n\tdef keyword(self):\n\t\treturn 'Compiling shader'\n\n@TaskGen.extension('.vert', '.frag', '.comp', '.rgen', '.rchit', '.rmiss', '.rahit')\ndef process_glsl_source(self, src):\n\t# see ccroot.apply_incpaths\n\tlst = self.to_incnodes(self.to_list(getattr(self, 'includes', [])) + self.env.GLSLCINCLUDES)\n\tself.includes_nodes = lst\n\tcwd = self.get_cwd()\n\tself.env.INCPATHS = [x.path_from(cwd) for x in lst]\n\tself.env.append_unique('GLSLCDEFINES', self.to_list(getattr(self, 'defines', [])))\n\n\tflags = getattr(self, 'glslcflags', None)\n\tif flags:\n\t\tself.env.append_unique('GLSLCFLAGS', self.to_list(flags))\n\n\ttsk = self.create_task('glsl', src, src.parent.find_or_declare('%s.spv' % src.name))\n\n\tinst_to = getattr(self, 'install_path', None)\n\tif inst_to:\n\t\tself.add_install_files(install_to=inst_to,\n\t\t\tinstall_from=tsk.outputs[:], chmod=Utils.O755, task=tsk)\n"
  },
  {
    "path": "scripts/waifulib/ninja.py",
    "content": "#!/usr/bin/env python\n# encoding: utf-8\n# Copyright (C) 2025 Velaron\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nimport io\nimport os\nimport abc\n\ntry:\n\tfrom cStringIO import StringIO\nexcept ImportError:\n\tfrom io import StringIO\n\nfrom abc import abstractmethod\n\ntry:\n\tfrom typing import List, Type, Union\nexcept ImportError:\n\tpass\n\nfrom waflib import Logs, Task, Build, Options, Node\nfrom ninja_syntax import Writer, escape_path\n\ntry:\n\tABC = abc.ABC\nexcept AttributeError:\n\tclass ABC(object):\n\t\t__metaclass__ = abc.ABCMeta\n\nTask.Task.keep_last_cmd = True\n\n\ndef get_node_path(node):  # type: (Union[Node, str]) -> str\n\tif isinstance(node, str):\n\t\treturn escape_path(node)\n\telse:\n\t\treturn escape_path(node.abspath())\n\n\nclass TaskAdapter(ABC):\n\ttask_type = None  # type: str\n\n\tdef __init__(self, task):  # type: (Task) -> None\n\t\tif not task.inputs:\n\t\t\traise ValueError()\n\n\t\tif not task.outputs:\n\t\t\traise ValueError()\n\n\t\tself.task = task  # type: Task\n\n\t@classmethod\n\tdef write_rule(cls, writer):  # type: (Writer) -> None\n\t\twriter.rule(\n\t\t\tname=cls.task_type,\n\t\t\tcommand=\"$cmd\",\n\t\t\tdescription=\"Building {} object $out\".format(cls.task_type)\n\t\t)\n\t\twriter.newline()\n\n\t@abstractmethod\n\tdef write_build(self, writer):  # type: (Writer) -> None\n\t\tpass\n\n\tdef write_target(self, writer):  # type: (Writer) -> None\n\t\tpass\n\n\tdef get_inputs(self):  # type: () -> List[str]\n\t\treturn []\n\n\tdef get_outputs(self):  # type: () -> List[str]\n\t\treturn []\n\n\nclass CAdapter(TaskAdapter):\n\ttask_type = \"c\"  # type: str\n\n\tdef write_build(self, writer):  # type: (Writer) -> None\n\t\toutput_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)\n\t\tinput_files = []\n\n\t\tcmd = \" \".join(self.task.last_cmd)\n\n\t\tfor node in self.task.inputs:\n\t\t\tcmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())\n\t\t\tinput_files.append(node.abspath())\n\n\t\tcmd = cmd.replace(self.task.outputs[0].abspath(), output_file)\n\n\t\tfor inc in self.task.env.INCPATHS:\n\t\t\tcwd = self.task.get_cwd().abspath()\n\t\t\tpath = os.path.normpath(os.path.join(cwd, inc))\n\t\t\tcmd = cmd.replace(self.task.env.CPPPATH_ST % inc,\n\t\t\t\t\t\t\t  self.task.env.CPPPATH_ST % path)\n\n\t\twriter.build(\n\t\t\trule=self.task_type,\n\t\t\toutputs=output_file,\n\t\t\tinputs=input_files,\n\t\t\tvariables={\n\t\t\t\t\"cmd\": cmd,\n\t\t\t})\n\t\twriter.newline()\n\n\nclass CxxAdapter(CAdapter):\n\ttask_type = \"cxx\"  # type: str\n\n\nclass CStLibAdapter(TaskAdapter):\n\ttask_type = \"cstlib\"  # type: str\n\n\tdef write_build(self, writer):  # type: (Writer) -> None\n\t\toutput_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)\n\t\tinput_files = []\n\n\t\tcmd = \" \".join(self.task.last_cmd)\n\n\t\tfor node in self.task.inputs:\n\t\t\tcmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())\n\t\t\tinput_files.append(node.abspath())\n\n\t\tcmd = cmd.replace(self.task.outputs[0].abspath(), output_file)\n\n\t\twriter.build(\n\t\t\trule=self.task_type,\n\t\t\toutputs=output_file,\n\t\t\tinputs=input_files,\n\t\t\tvariables={\n\t\t\t\t\"cmd\": cmd,\n\t\t\t})\n\t\twriter.newline()\n\n\tdef write_target(self, writer):  # type: (Writer) -> None\n\t\twriter.build(\n\t\t\trule=\"waf_build\",\n\t\t\toutputs=\"{}.passthrough\".format(self.get_outputs()[0]),\n\t\t\tinputs=self.get_inputs(),\n\t\t\tvariables={\n\t\t\t\t\"tgt\": self.task.generator.name\n\t\t\t})\n\t\twriter.newline()\n\n\tdef get_inputs(self):  # type: () -> List[str]\n\t\treturn [node.abspath() for node in self.task.generator.source]\n\n\tdef get_outputs(self):  # type: () -> List[str]\n\t\treturn [self.task.outputs[0].path_from(self.task.generator.bld.bldnode)]\n\n\nclass CShLibAdapter(CStLibAdapter):\n\ttask_type = \"cshlib\"  # type: str\n\n\tdef write_build(self, writer):  # type: (Writer) -> None\n\t\toutput_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode)\n\t\tinput_files = []\n\n\t\tcmd = \" \".join(self.task.last_cmd)\n\n\t\tfor node in self.task.inputs:\n\t\t\tcmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath())\n\t\t\tinput_files.append(node.abspath())\n\n\t\tcmd = cmd.replace(self.task.outputs[0].abspath(), output_file)\n\n\t\tfor lib in self.task.env.STLIBPATH:\n\t\t\tcwd = self.task.get_cwd().abspath()\n\t\t\tpath = os.path.normpath(os.path.join(cwd, lib))\n\t\t\tcmd = cmd.replace(self.task.env.STLIBPATH_ST % lib,\n\t\t\t\t\t\t\t  self.task.env.STLIBPATH_ST % path)\n\n\t\tfor lib in self.task.env.LIBPATH:\n\t\t\tcwd = self.task.get_cwd().abspath()\n\t\t\tpath = os.path.normpath(os.path.join(cwd, lib))\n\t\t\tcmd = cmd.replace(self.task.env.LIBPATH_ST % lib,\n\t\t\t\t\t\t\t  self.task.env.LIBPATH_ST % path)\n\n\t\twriter.build(\n\t\t\trule=self.task_type,\n\t\t\toutputs=output_file,\n\t\t\tinputs=input_files,\n\t\t\tvariables={\n\t\t\t\t\"cmd\": cmd,\n\t\t\t})\n\t\twriter.newline()\n\n\nclass CxxStLibAdapter(CStLibAdapter):\n\ttask_type = \"cxxstlib\"  # type: str\n\n\nclass CxxShLibAdapter(CShLibAdapter):\n\ttask_type = \"cxxshlib\"  # type: str\n\n\ndef get_subclasses(cls):\n\tsubs = set()\n\tfor sub in cls.__subclasses__():\n\t\tsubs.add(sub)\n\t\tsubs.update(get_subclasses(sub))\n\treturn list(subs)\n\n\nAdapters = get_subclasses(TaskAdapter)  # type: List[Type[TaskAdapter]]\n\n\nclass NinjaContext(Build.BuildContext):\n\tcmd = \"ninja\"\n\n\tdef execute(self):\n\t\tself.restore()\n\n\t\ttasks = []  # type: List[TaskAdapter]\n\n\t\tif not self.all_envs:\n\t\t\tself.load_envs()\n\n\t\tself.recurse([self.run_dir])\n\t\tself.pre_build()\n\n\t\tdef exec_command(self, *k, **kw):\n\t\t\treturn 0\n\n\t\tfor group in self.groups:\n\t\t\tfor task_gen in group:\n\t\t\t\ttry:\n\t\t\t\t\tif hasattr(task_gen, \"post\"):\n\t\t\t\t\t\ttask_gen.post()\n\t\t\t\texcept AttributeError:\n\t\t\t\t\tpass\n\n\t\t\t\tif isinstance(task_gen, Task.Task):\n\t\t\t\t\tcurrent_tasks = [task_gen]\n\t\t\t\telse:\n\t\t\t\t\tcurrent_tasks = task_gen.tasks\n\n\t\t\t\tfor task in current_tasks:\n\t\t\t\t\ttry:\n\t\t\t\t\t\tadapter = next(a for a in Adapters if a.task_type == task.__class__.__name__)\n\t\t\t\t\texcept StopIteration:\n\t\t\t\t\t\tcontinue\n\n\t\t\t\t\tif adapter:\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\ttasks.append(adapter(task))\n\t\t\t\t\t\texcept ValueError:\n\t\t\t\t\t\t\tcontinue\n\n\t\t\t\t\t\ttask.nocache = True\n\n\t\t\t\t\t\told_exec = task.exec_command\n\t\t\t\t\t\ttask.exec_command = exec_command\n\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\ttask.run()\n\t\t\t\t\t\texcept Exception as e:\n\t\t\t\t\t\t\tLogs.error(\"Error running task {}: {}\".format(task, e))\n\t\t\t\t\t\tfinally:\n\t\t\t\t\t\t\ttask.exec_command = old_exec\n\n\t\tninja_file_node = self.bldnode.make_node(\"build.ninja\")\n\n\t\tLogs.info(\"Ninja build commands will be stored in %s\", ninja_file_node.abspath())\n\n\t\tstring_buffer = StringIO()\n\t\twriter = Writer(string_buffer)\n\n\t\twriter.variable(key=\"ninja_required_version\", value=\"1.5\")\n\t\twriter.newline()\n\n\t\tfor a in Adapters:\n\t\t\ta.write_rule(writer)\n\n\t\twriter.rule(\n\t\t\t\"waf_build\",\n\t\t\tcommand=\"python {} build {} {} {} --targets=$tgt\".format(\n\t\t\t\tos.path.join(self.top_dir, \"scripts\", \"build-ninja.py\"),\n\t\t\t\tself.top_dir, os.path.dirname(self.out_dir), Options.lockfile)\n\t\t)\n\t\twriter.newline()\n\n\t\twriter.rule(\n\t\t\t\"waf_build_all\",\n\t\t\tcommand=\"python {} build {} {} {}\".format(os.path.join(self.top_dir, \"scripts\", \"build-ninja.py\"),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)\n\t\t)\n\t\twriter.newline()\n\n\t\twriter.rule(\n\t\t\t\"waf_clean\",\n\t\t\tcommand=\"python {} clean {} {} {}\".format(os.path.join(self.top_dir, \"scripts\", \"build-ninja.py\"),\n\t\t\t\t\t\t\t\t\t\t\t\t\t  self.top_dir, os.path.dirname(self.out_dir), Options.lockfile)\n\t\t)\n\t\twriter.newline()\n\n\t\tfor task in tasks:\n\t\t\ttask.write_build(writer)\n\n\t\tfor task in tasks:\n\t\t\ttask.write_target(writer)\n\n\t\toutputs = []  # type: List[str]\n\t\tfor task in tasks:\n\t\t\toutputs += task.get_outputs()\n\n\t\twriter.build(\n\t\t\toutputs=\"all\",\n\t\t\trule=\"phony\",\n\t\t\tinputs=outputs\n\t\t)\n\t\twriter.newline()\n\n\t\tinputs = []  # type: List[str]\n\t\tfor task in tasks:\n\t\t\tinputs += task.get_inputs()\n\n\t\twriter.build(\n\t\t\toutputs=\"all.passthrough\",\n\t\t\trule=\"waf_build_all\",\n\t\t\tinputs=inputs\n\t\t)\n\t\twriter.newline()\n\n\t\twriter.build(outputs=\"clean\", rule=\"waf_clean\")\n\t\twriter.newline()\n\n\t\twriter.default(\"all\")\n\n\t\tfile_content = string_buffer.getvalue()\n\n\t\twith io.open(ninja_file_node.abspath(), \"w\", encoding=\"utf-8\") as f:\n\t\t\tf.write(file_content)\n"
  },
  {
    "path": "scripts/waifulib/ninja_syntax.py",
    "content": "#!/usr/bin/python\n\n# Copyright 2011 Google Inc. All Rights Reserved.\n# Copyright 2025 Velaron (edited for Python 2.7 compatibility)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Python module for generating .ninja files.\n\nNote that this is emphatically not a required piece of Ninja; it's\njust a helpful utility for build-file-generation systems that already\nuse Python.\n\"\"\"\n\nimport re\nimport textwrap\nfrom io import TextIOWrapper\n\ntry:\n\tfrom typing import Dict, List, Optional, Tuple, Union\nexcept ImportError:\n\tpass\n\n\ndef escape_path(word):  # type: (str) -> str\n\treturn word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')\n\n\nclass Writer(object):\n\tdef __init__(self, output, width=78):  # type: (TextIOWrapper, int) -> None\n\t\tself.output = output\n\t\tself.width = width\n\n\tdef newline(self):\n\t\tself.output.write('\\n')\n\n\tdef comment(self, text):  # type: (str) -> None\n\t\tfor line in textwrap.wrap(text, self.width - 2, break_long_words=False,\n\t\t\t\t\t\t\t\t  break_on_hyphens=False):\n\t\t\tself.output.write('# ' + line + '\\n')\n\n\tdef variable(\n\t\tself,\n\t\tkey,\n\t\tvalue,\n\t\tindent=0,\n\t):  # type: (str, Optional[Union[bool, int, float, str, List[str]]], int) -> None\n\t\tif value is None:\n\t\t\treturn\n\t\tif isinstance(value, list):\n\t\t\tvalue = ' '.join(filter(None, value))  # Filter out empty strings.\n\t\tself._line('%s = %s' % (key, value), indent)\n\n\tdef pool(self, name, depth):  # type: (str, int) -> None\n\t\tself._line('pool %s' % name)\n\t\tself.variable('depth', depth, indent=1)\n\n\tdef rule(\n\t\tself,\n\t\tname,\n\t\tcommand,\n\t\tdescription=None,\n\t\tdepfile=None,\n\t\tgenerator=False,\n\t\tpool=None,\n\t\trestat=False,\n\t\trspfile=None,\n\t\trspfile_content=None,\n\t\tdeps=None,\n\t):  # type: (str, str, Optional[str], Optional[str], bool, Optional[str], bool, Optional[str], Optional[str], Optional[Union[str, List[str]]]) -> None\n\t\tself._line('rule %s' % name)\n\t\tself.variable('command', command, indent=1)\n\t\tif description:\n\t\t\tself.variable('description', description, indent=1)\n\t\tif depfile:\n\t\t\tself.variable('depfile', depfile, indent=1)\n\t\tif generator:\n\t\t\tself.variable('generator', '1', indent=1)\n\t\tif pool:\n\t\t\tself.variable('pool', pool, indent=1)\n\t\tif restat:\n\t\t\tself.variable('restat', '1', indent=1)\n\t\tif rspfile:\n\t\t\tself.variable('rspfile', rspfile, indent=1)\n\t\tif rspfile_content:\n\t\t\tself.variable('rspfile_content', rspfile_content, indent=1)\n\t\tif deps:\n\t\t\tself.variable('deps', deps, indent=1)\n\n\tdef build(\n\t\tself,\n\t\toutputs,\n\t\trule,\n\t\tinputs=None,\n\t\timplicit=None,\n\t\torder_only=None,\n\t\tvariables=None,\n\t\timplicit_outputs=None,\n\t\tpool=None,\n\t\tdyndep=None,\n\t):  # type: (Union[str, List[str]], str, Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[str, List[str]]], Optional[Union[List[Tuple[str, Optional[Union[str, List[str]]]]],Dict[str, Optional[Union[str, List[str]]]],]], Optional[Union[str, List[str]]], Optional[str], Optional[str]) -> List[str]\n\t\toutputs = as_list(outputs)\n\t\tout_outputs = [escape_path(x) for x in outputs]\n\t\tall_inputs = [escape_path(x) for x in as_list(inputs)]\n\n\t\tif implicit:\n\t\t\timplicit = [escape_path(x) for x in as_list(implicit)]\n\t\t\tall_inputs.append('|')\n\t\t\tall_inputs.extend(implicit)\n\t\tif order_only:\n\t\t\torder_only = [escape_path(x) for x in as_list(order_only)]\n\t\t\tall_inputs.append('||')\n\t\t\tall_inputs.extend(order_only)\n\t\tif implicit_outputs:\n\t\t\timplicit_outputs = [escape_path(x)\n\t\t\t\t\t\t\t\tfor x in as_list(implicit_outputs)]\n\t\t\tout_outputs.append('|')\n\t\t\tout_outputs.extend(implicit_outputs)\n\n\t\tself._line('build %s: %s' % (' '.join(out_outputs),\n\t\t\t\t\t\t\t\t\t ' '.join([rule] + all_inputs)))\n\t\tif pool is not None:\n\t\t\tself._line('  pool = %s' % pool)\n\t\tif dyndep is not None:\n\t\t\tself._line('  dyndep = %s' % dyndep)\n\n\t\tif variables:\n\t\t\tif isinstance(variables, dict):\n\t\t\t\titerator = iter(variables.items())\n\t\t\telse:\n\t\t\t\titerator = iter(variables)\n\n\t\t\tfor key, val in iterator:\n\t\t\t\tself.variable(key, val, indent=1)\n\n\t\treturn outputs\n\n\tdef include(self, path):  # type: (str) -> None\n\t\tself._line('include %s' % path)\n\n\tdef subninja(self, path):  # type: (str) -> None\n\t\tself._line('subninja %s' % path)\n\n\tdef default(self, paths):  # type: (Union[str, List[str]]) -> None\n\t\tself._line('default %s' % ' '.join(as_list(paths)))\n\n\tdef _count_dollars_before_index(self, s, i):  # type: (str, int) -> int\n\t\t\"\"\"Returns the number of '$' characters right in front of s[i].\"\"\"\n\t\tdollar_count = 0\n\t\tdollar_index = i - 1\n\t\twhile dollar_index > 0 and s[dollar_index] == '$':\n\t\t\tdollar_count += 1\n\t\t\tdollar_index -= 1\n\t\treturn dollar_count\n\n\tdef _line(self, text, indent=0):  # type: (str, int) -> None\n\t\t\"\"\"Write 'text' word-wrapped at self.width characters.\"\"\"\n\t\tleading_space = '  ' * indent\n\t\twhile len(leading_space) + len(text) > self.width:\n\t\t\t# The text is too wide; wrap if possible.\n\n\t\t\t# Find the rightmost space that would obey our width constraint and\n\t\t\t# that's not an escaped space.\n\t\t\tavailable_space = self.width - len(leading_space) - len(' $')\n\t\t\tspace = available_space\n\t\t\twhile True:\n\t\t\t\tspace = text.rfind(' ', 0, space)\n\t\t\t\tif (space < 0 or\n\t\t\t\t\tself._count_dollars_before_index(text, space) % 2 == 0):\n\t\t\t\t\tbreak\n\n\t\t\tif space < 0:\n\t\t\t\t# No such space; just use the first unescaped space we can find.\n\t\t\t\tspace = available_space - 1\n\t\t\t\twhile True:\n\t\t\t\t\tspace = text.find(' ', space + 1)\n\t\t\t\t\tif (space < 0 or\n\t\t\t\t\t\tself._count_dollars_before_index(text, space) % 2 == 0):\n\t\t\t\t\t\tbreak\n\t\t\tif space < 0:\n\t\t\t\t# Give up on breaking.\n\t\t\t\tbreak\n\n\t\t\tself.output.write(leading_space + text[0:space] + ' $\\n')\n\t\t\ttext = text[space + 1:]\n\n\t\t\t# Subsequent lines are continuations, so indent them.\n\t\t\tleading_space = '  ' * (indent + 2)\n\n\t\tself.output.write(leading_space + text + '\\n')\n\n\tdef close(self):\n\t\tself.output.close()\n\n\ndef as_list(input):  # type: (Optional[Union[str, List[str]]]) -> List[str]\n\tif input is None:\n\t\treturn []\n\tif isinstance(input, list):\n\t\treturn input\n\treturn [input]\n\n\ndef escape(string):  # type: (str) -> str\n\t\"\"\"Escape a string such that it can be embedded into a Ninja file without\n\tfurther interpretation.\"\"\"\n\tassert '\\n' not in string, 'Ninja syntax does not allow newlines'\n\t# We only have one special metacharacter: '$'.\n\treturn string.replace('$', '$$')\n\n\ndef expand(string, vars, local_vars={}):  # type: (str, Dict[str, str], Dict[str, str]) -> str\n\t\"\"\"Expand a string containing $vars as Ninja would.\n\n\tNote: doesn't handle the full Ninja variable syntax, but it's enough\n\tto make configure.py's use of it work.\n\t\"\"\"\n\n\tdef exp(m):  # type (Match[str]) -> str:\n\t\tvar = m.group(1)\n\t\tif var == '$':\n\t\t\treturn '$'\n\t\treturn local_vars.get(var, vars.get(var, ''))\n\n\treturn re.sub(r'\\$(\\$|\\w*)', exp, string)\n"
  },
  {
    "path": "scripts/waifulib/nswitch.py",
    "content": "# encoding: utf-8\n# nswitch.py -- switch NRO task\n# Copyright (C) 2018 a1batross\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nfrom waflib.Tools import ccroot\nfrom waflib import *\n\ndef configure(conf):\n\tconf.find_program('elf2nro')\n\n\tv = conf.env\n\n\tv.ELF2NRO_NACP_F     = ['--nacp=']\n\tv.ELF2NRO_ICON_F     = ['--icon=']\n\nclass elf2nro(Task.Task):\n\tcolor = 'RED'\n\trun_str = '${ELF2NRO} ${ELFFILE} ${TGT} ${ELF2NRO_NACP_F?NACP}${NACP} ${ELF2NRO_ICON_F?ICON}${ICON}'\n\n\tdef keyword(self):\n\t\tif Logs.colors_lst['USE']: # red/blue switch colors :)\n\t\t\treturn '%sConverting to NRO' % Logs.colors_lst['CYAN']\n\t\treturn 'Converting to NRO'\n\n@TaskGen.feature('cxxprogram', 'cprogram')\n@TaskGen.after_method('apply_link')\ndef apply_nro(self):\n\telffile = self.link_task.outputs[0]\n\n\tnodes = [elffile]\n\n\tdef add_source_file(ctx, nodes, f):\n\t\tif f:\n\t\t\tif isinstance(f, str):\n\t\t\t\tnode = ctx.path.make_node(f)\n\t\t\telif isinstance(f, Node.Node):\n\t\t\t\tnode = f\n\n\t\t\tnodes += [node]\n\t\t\treturn node\n\t\treturn None\n\n\tnacpfile = add_source_file(self, nodes, getattr(self, 'nacp', None))\n\ticonfile = add_source_file(self, nodes, getattr(self, 'icon', None))\n\tself.env.ELFFILE = str(elffile)\n\tif nacpfile: self.env.NACP = str(nacpfile)\n\tif iconfile: self.env.ICON = str(iconfile)\n\n\ttsk = self.nro_task = self.create_task('elf2nro', nodes)\n\tself.nro_task.set_outputs(nodes[0].change_ext('.nro'))\n\n\tinst_to = getattr(self, 'special_install_path', None)\n\tif inst_to:\n\t\tself.add_install_files(install_to=inst_to,\n\t\t\tinstall_from=tsk.outputs[:], chmod=Utils.O755, task=tsk)\n"
  },
  {
    "path": "scripts/waifulib/owcc.py",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\n\"\"\"\nCompiler definition for OpenWatcom's owcc\n\"\"\"\n\nfrom waflib import Errors, Utils\nfrom waflib.Tools import ccroot, ar\nfrom waflib.Configure import conf\n\n@conf\ndef find_owcc(conf):\n\tv = conf.env\n\tcc = None\n\tif v.CC:\n\t\tcc = v.CC\n\telse:\n\t\tcc = conf.find_program('cc', var='CC')\n\tif not cc:\n\t\tconf.fatal('owcc was not found')\n\n\ttry:\n\t\tout = conf.cmd_and_log(cc + ['-v'])\n\texcept Errors.WafError:\n\t\tconf.fatal('%r -v could not be executed' % cc)\n\tif not 'Open Watcom' in out:\n\t\tconf.fatal('failed to detect owcc')\n\n\tv.CC = cc\n\tv.CC_NAME = 'owcc'\n\tv.CXX = v.CC\n\tv.CXX_NAME = v.cc_NAME\n\tif not v.AR:\n\t\tconf.find_program('wlib', var='AR')\n\tconf.add_os_flags('ARFLAGS')\n\tif not v.ARFLAGS:\n\t\tv.ARFLAGS = ['-fo']\n\n@conf\ndef owcc_common_flags(conf):\n\tv = conf.env\n\n\tv.CC_SRC_F            = ''\n\tv.CXX_SRC_F            = ''\n\tv.CC_TGT_F            = ['-c', '-o']\n\tv.CXX_TGT_F            = ['-c', '-o']\n\tv.CPPPATH_ST          = '-I%s'\n\tv.DEFINES_ST          = '-D%s'\n\n\tif not v.LINK_CC:\n\t\tv.LINK_CC = v.CC\n\tif not v.LINK_CXX:\n\t\tv.LINK_CXX = v.CXX\n\n\tv.CCLNK_SRC_F         = ''\n\tv.CCLNK_TGT_F         = ['-o']\n\tv.CXXLNK_SRC_F         = ''\n\tv.CXXLNK_TGT_F         = ['-o']\n\n\tv.LIB_ST              = '-l%s' # template for adding libs\n\tv.LIBPATH_ST          = '-L%s' # template for adding libpaths\n\tv.STLIB_ST            = '-l%s'\n\tv.STLIBPATH_ST        = '-L%s'\n\n\tv.cprogram_PATTERN    = '%s.exe'\n\tv.cxxprogram_PATTERN    = '%s.exe'\n\tv.cshlib_PATTERN      = 'lib%s.so'\n\tv.cxxshlib_PATTERN      = 'lib%s.so'\n\tv.cstlib_PATTERN      = '%s.a'\n\tv.cxxstlib_PATTERN      = '%s.a'\n\ndef find_target(flags):\n\tif '-b' in flags:\n\t\treturn flags[flags.index('-b')+1]\n\n@conf\ndef owcc_detect_platform(conf):\n\tv = conf.env\n\ttarget = find_target(v.LINKFLAGS)\n\tif not target:\n\t\ttarget = find_target(v.CC)\n\tif not target:\n\t\ttarget = find_target(v.CFLAGS)\n\tif not target:\n\t\ttarget = Utils.unversioned_sys_platform()\n\tif target in ['dos4g', 'dos4gnz', 'dos32a', 'stub32a', 'stub32ac']:\n\t\tv.DEST_BINFMT = 'le'\n\t\tv.DEST_OS = 'dos'\n\telif target in ['dos32x', 'stub32x', 'stub32xc']:\n\t\tv.DEST_BINFMT = 'lx'\n\t\tv.DEST_OS = 'dos'\n\telif target.startswith('win') or target.startswith('nt'):\n\t\tv.DEST_BINFMT = 'pe'\n\t\tv.DEST_OS = 'win32'\n\telif target == 'qnx386':\n\t\tv.DEST_OS = 'qnx'\n\t\tv.DEST_BINFMT = 'qnx'\n\telif target in ['linux', '386']:\n\t\tv.DEST_OS = 'linux'\n\t\tv.DEST_BINFMT = 'elf'\n\telse:\n\t\tv.DEST_OS = target\n\t\tv.DEST_BINFMT = None\n\n\tv.DEST_CPU = 'i386'\n\n\tfor f in v.LINKFLAGS + v.CC + v.CFLAGS:\n\t\tif f.startswith('-march'):\n\t\t\tv.DEST_CPU=f.split('=')[1]\n\t\t\tbreak\n\n\ndef configure(conf):\n\tconf.find_owcc()\n\tconf.owcc_common_flags()\n\tconf.cc_load_tools()\n\tconf.cc_add_flags()\n\tconf.env.append_unique('CFLAGS','-Wc,-xx')\n\tconf.cxx_load_tools()\n\tconf.cxx_add_flags()\n\tconf.env.append_unique('CXXFLAGS','-Wc,-xx')\n\tconf.link_add_flags()\n\tconf.owcc_detect_platform()\n"
  },
  {
    "path": "scripts/waifulib/psp.py",
    "content": "# encoding: utf-8\n# psp.py -- PSP EBOOT task\n# Copyright (C) 2023 Sergey Galushko\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\n##################################\n#            PSP Tools           #\n##################################\nclass psp_fixup(Task.Task):\n\trun_str = '${FIXUP} -o ${TGT} ${SRC}'\n\tcolor   = 'BLUE'\n\nclass psp_prxgen(Task.Task):\n\trun_str = '${PRXGEN} ${SRC} ${TGT}'\n\tcolor   = 'BLUE'\n\nclass psp_strip(Task.Task):\n\trun_str = '${STRIP} -o ${TGT} ${SRC}'\n\tcolor   = 'BLUE'\n\nclass psp_mksfo(Task.Task):\n\trun_str = '${MKSFO} -d MEMSIZE=1 ${PSP_EBOOT_TITLE} ${TGT}'\n\tcolor   = 'YELLOW'\n\nclass psp_packpbp(Task.Task):\n\trun_str = '${PACK_PBP} ${TGT} ${SRC[1].abspath()} ${PSP_EBOOT_ICON} ${PSP_EBOOT_ICON1} ${PSP_EBOOT_UNKPNG} ${PSP_EBOOT_PIC1} ${PSP_EBOOT_SND0} ${SRC[0].abspath()} ${PSP_EBOOT_PSAR}'\n\tcolor   = 'GREEN'\n\n\n@TaskGen.feature('cshlib', 'cxxshlib')\n@TaskGen.after_method('apply_link')\ndef build_module(self):\n\tlink_output = self.link_task.outputs[0]\n\tfor d in self.env.STATIC_LINKING:\n\t\tif link_output.name.startswith(d):\n\t\t\treturn\n\tfixup_output = self.path.find_or_declare(link_output.name + '_fixup')\n\tprxgen_output = self.path.find_or_declare(link_output.change_ext('.prx').name)\n\n\ttask = self.create_task('psp_fixup', src=link_output, tgt=fixup_output)\n\ttask = self.create_task('psp_prxgen', src=fixup_output, tgt=prxgen_output)\n\n\tif getattr(self, 'install_path', None):\n\t\tif self.bld.is_install:\n\t\t\tfor k in self.install_task.inputs:\n\t\t\t\tif k == self.path.find_or_declare(link_output.name):\n\t\t\t\t\tself.install_task.inputs.remove(k)\n\t\tself.add_install_files(install_to=self.install_path, install_from=prxgen_output)\n\n@TaskGen.feature('cprogram', 'cxxprogram', 'cprogram_static', 'cxxprogram_static')\n@TaskGen.after_method('apply_link')\ndef build_eboot(self):\n\tfinalobj_ext = '.elf'\n\tfinalobj_tool = 'psp_strip'\n\tif self.env.PSP_BUILD_PRX:\n\t\tfinalobj_ext = '.prx'\n\t\tfinalobj_tool = 'psp_prxgen'\n\n\tlink_output = self.link_task.outputs[0]\n\tfixup_output = self.path.find_or_declare(link_output.name + '_fixup')\n\tfinalobj_output = self.path.find_or_declare(link_output.change_ext(finalobj_ext).name)\n\n\tmksfo_output = self.path.find_or_declare('PARAM.SFO')\n\tpackpbp_output = self.path.find_or_declare('EBOOT.PBP')\n\n\ttask = self.create_task('psp_fixup', src=link_output, tgt=fixup_output)\n\ttask = self.create_task(finalobj_tool, src=fixup_output, tgt=finalobj_output)\n\ttask = self.create_task('psp_mksfo', tgt=mksfo_output)\n\ttask = self.create_task('psp_packpbp', src=[finalobj_output, mksfo_output], tgt=packpbp_output)\n\n\tif getattr(self, 'install_path', None):\n\t\tif getattr(self, 'install_task', None):\n\t\t\tself.install_task.inputs = self.install_task.outputs = []\n\t\tself.add_install_files(install_to=self.install_path, install_from=[packpbp_output, finalobj_output])\n"
  },
  {
    "path": "scripts/waifulib/psvita.py",
    "content": "# encoding: utf-8\n# psvita.py -- PSVita VPK task\n# Copyright (C) 2023 fgsfds\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nfrom waflib.Tools import ccroot\nfrom waflib import *\n\ndef add_source_file(ctx, nodes, f):\n\tif f:\n\t\tif isinstance(f, str):\n\t\t\tnode = ctx.path.make_node(f)\n\t\telif isinstance(f, Node.Node):\n\t\t\tnode = f\n\t\tnodes += [node]\n\t\treturn node\n\treturn None\n\ndef configure(conf):\n\tconf.find_program('vita-elf-create', var='ELF_CREATE')\n\tconf.find_program('vita-make-fself', var='MAKE_FSELF')\n\tconf.find_program('vita-mksfoex', var='MKSFOEX')\n\tconf.find_program('vita-pack-vpk', var='PACKVPK')\n\n\tconf.env.SCESYS_ST = '-a%s=sce_sys'\n\nclass mkvelf(Task.Task):\n\tcolor = 'CYAN'\n\trun_str = '${ELF_CREATE} -g ${YMLFILE} ${ELFFILE} ${TGT}'\n\nclass mkfself(Task.Task):\n\tcolor = 'CYAN'\n\trun_str = '${MAKE_FSELF} ${VELFFILE} ${TGT}'\n\nclass mksfoex(Task.Task):\n\tcolor = 'CYAN'\n\t# ATTRIBUTE2=12 enables the biggest extended memory mode\n\trun_str = '${MKSFOEX} -s TITLE_ID=${TITLEID} -d ATTRIBUTE2=12 ${APPNAME} ${TGT}'\n\nclass mkvpk(Task.Task):\n\tcolor = 'CYAN'\n\trun_str = '${PACKVPK} -s ${SFOFILE} -b ${FSELFFILE} ${SCESYS_ST:SCESYS} ${TGT}'\n\n@TaskGen.feature('cxxprogram', 'cprogram')\n@TaskGen.after_method('apply_link')\ndef apply_velf(self):\n\telffile = self.link_task.outputs[0]\n\tin_nodes = [elffile]\n\n\tymlfile = elffile.change_ext('.yml')\n\tvelffile = elffile.change_ext('.velf')\n\tout_nodes = [velffile, ymlfile]\n\n\tself.env.ELFFILE = str(elffile)\n\tself.env.VELFFILE = str(velffile)\n\tself.env.YMLFILE = str(ymlfile)\n\n\tself.velf_task = self.create_task('mkvelf', in_nodes)\n\tself.velf_task.set_outputs(out_nodes)\n\n@TaskGen.feature('cxxprogram', 'cprogram')\n@TaskGen.after_method('apply_velf')\ndef apply_fself(self):\n\tvelffile = self.velf_task.outputs[0]\n\tin_nodes = [velffile]\n\n\tfselffile = velffile.change_ext('.bin')\n\tout_nodes = [fselffile]\n\n\tself.env.FSELFFILE = str(fselffile)\n\n\tself.fself_task = self.create_task('mkfself', in_nodes)\n\tself.fself_task.set_outputs(out_nodes)\n\n@TaskGen.feature('cxxprogram', 'cprogram')\n@TaskGen.after_method('apply_fself')\ndef apply_sfo(self):\n\tfselffile = self.fself_task.outputs[0]\n\tin_nodes = [fselffile]\n\tscetitleid = getattr(self, 'title_id', 'TEST10000')\n\tsceappname = getattr(self, 'app_name', 'test')\n\n\tsfofile = fselffile.change_ext('.sfo')\n\tout_nodes = [sfofile]\n\n\tif scetitleid: self.env.TITLEID = scetitleid\n\tif sceappname: self.env.APPNAME = sceappname\n\tself.env.SFOFILE = str(sfofile)\n\n\tself.sfo_task = self.create_task('mksfoex', in_nodes)\n\tself.sfo_task.set_outputs(out_nodes)\n\n@TaskGen.feature('cxxprogram', 'cprogram')\n@TaskGen.after_method('apply_sfo')\ndef apply_vpk(self):\n\tfselffile = self.fself_task.outputs[0]\n\tsfofile = self.sfo_task.outputs[0]\n\tin_nodes = [fselffile, sfofile]\n\tscesysdir = add_source_file(self, in_nodes, getattr(self, 'sce_sys', None))\n\n\tvpkfile = sfofile.change_ext('.vpk')\n\tout_nodes = [vpkfile]\n\n\tif scesysdir:\n\t\tself.env.SCESYS = [str(scesysdir)]\n\tself.env.VPKFILE = str(vpkfile)\n\n\ttsk = self.vpk_task = self.create_task('mkvpk', in_nodes)\n\tself.vpk_task.set_outputs(out_nodes)\n\n\tinst_to = getattr(self, 'special_install_path', None)\n\tif inst_to:\n\t\tself.add_install_files(install_to=inst_to,\n\t\t\tinstall_from=tsk.outputs[:], chmod=Utils.O755, task=tsk)\n"
  },
  {
    "path": "scripts/waifulib/sdl2.py",
    "content": "# encoding: utf-8\n# sdl2.py -- sdl2 waf plugin\n# Copyright (C) 2018 a1batross\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nimport os\n\nSDL_SANITY_FRAGMENT='''#define SDL_MAIN_HANDLED\n#include <%s>\nint main( void )\n{\n\tSDL_Init( SDL_INIT_AUDIO );\n\treturn 0;\n}'''\n\ndef options(opt):\n\tgrp = opt.add_option_group('SDL2/SDL3 options')\n\tgrp.add_option('-s', '--sdl2', action='store', dest = 'SDL_PATH', default = None,\n\t\thelp = 'path to precompiled SDL library (required for Windows)')\n\n\tgrp.add_option('--use-sdl3', action='store_true', dest='SDL3', default = False, help = 'configure for SDL3 [default: %(default)s]')\n\n\tgrp.add_option('--skip-sdl2-sanity-check', action='store_false', default = True, dest='SDL_SANITY_CHECK',\n\t\thelp = 'skip checking SDL sanity [default: %(default)s]')\n\n\tgrp.add_option('--sdl-use-pkgconfig', action='store_true', default = False, dest='SDL_USE_PKGCONFIG',\n\t\thelp = 'force use of pkg-config to find sdl [default: %(default)s]')\n\ndef my_dirname(path):\n\t# really dumb, will not work with /path/framework//, but still enough\n\tif path[-1] == '/':\n\t\tpath = path[:-1]\n\treturn os.path.dirname(path)\n\ndef sdl2_configure_path(conf, path, libname):\n\tconf.start_msg('Configuring %s by path' % libname)\n\n\tHAVE          = 'HAVE_' + libname\n\tINCLUDES      = 'INCLUDES_' + libname\n\tFRAMEWORKPATH = 'FRAMEWORKPATH_' + libname\n\tFRAMEWORK     = 'FRAMEWORK_' + libname\n\tLIBPATH       = 'LIBPATH_' + libname\n\tLIB           = 'LIB_' + libname\n\n\tconf.env[HAVE] = 1\n\tif conf.env.DEST_OS == 'darwin':\n\t\tconf.env[INCLUDES] = [os.path.abspath(os.path.join(path, 'Headers'))]\n\t\tconf.env[FRAMEWORKPATH] = [my_dirname(path)]\n\t\tconf.env[FRAMEWORK] = [libname]\n\t\tconf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[FRAMEWORK], conf.env[FRAMEWORKPATH], conf.env[INCLUDES]))\n\telif conf.env.DEST_OS == 'android':\n\t\t# Special setup for waf called from CMake, through ExternalProject_Add\n\t\tconf.env[INCLUDES] = [os.path.abspath(os.path.join(path, 'include'))]\n\t\tconf.env[LIBPATH] = [os.environ['BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY']]\n\t\tconf.env[LIB] = [libname]\n\t\tconf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[LIB], conf.env[LIBPATH], conf.env[INCLUDES]))\n\telse:\n\t\tconf.env[INCLUDES] = [\n\t\t\tos.path.abspath(os.path.join(path, 'include')),\n\t\t\tos.path.abspath(os.path.join(path, 'include/%s' % libname))\n\t\t]\n\t\tlibpath = 'lib'\n\t\tif conf.env.COMPILER_CC == 'msvc':\n\t\t\tif conf.env.DEST_CPU == 'x86_64':\n\t\t\t\tlibpath = 'lib/x64'\n\t\t\telse:\n\t\t\t\tlibpath = 'lib/' + conf.env.DEST_CPU\n\t\tconf.env[LIBPATH] = [os.path.abspath(os.path.join(path, libpath))]\n\t\tconf.env[LIB] = [libname]\n\t\tconf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[LIB], conf.env[LIBPATH], conf.env[INCLUDES]))\n\ndef configure(conf):\n\tif conf.options.SDL3:\n\t\tlibname = 'SDL3'\n\telse:\n\t\tlibname = 'SDL2'\n\n\tHAVE          = 'HAVE_' + libname\n\tCFLAGS        = 'CFLAGS_' + libname\n\tCXXFLAGS      = 'CFLAGS_' + libname\n\tLINKFLAGS     = 'LINKFLAGS_' + libname\n\n\tif conf.options.SDL_PATH:\n\t\tsdl2_configure_path(conf, conf.options.SDL_PATH, libname)\n\telif conf.env.DEST_OS == 'darwin' and conf.options.SDL_USE_PKGCONFIG == False:\n\t\tsdl2_configure_path(conf, '/Library/Frameworks/%s.framework' % libname, libname)\n\telif conf.env.DEST_OS == 'emscripten':\n\t\tflag = '-sUSE_SDL=%d' % (3 if conf.options.SDL3 else 2)\n\t\tconf.env[HAVE] = 1\n\t\tconf.env[CFLAGS] = [flag]\n\t\tconf.env[CXXFLAGS] = [flag]\n\t\tconf.env[LINKFLAGS] = [flag]\n\telse:\n\t\ttry:\n\t\t\tconf.check_cfg(package=libname.lower(), args='--cflags --libs',\n\t\t\t\tmsg='Checking for %s (pkg-config)' % libname)\n\t\texcept conf.errors.ConfigurationError:\n\t\t\ttry:\n\t\t\t\tif not conf.env.SDLCONFIG:\n\t\t\t\t\tconf.find_program('%s-config' % libname.lower(), var='SDLCONFIG')\n\n\t\t\t\tconf.check_cfg(path=conf.env.SDLCONFIG, args='--cflags --libs',\n\t\t\t\t\tmsg='Checking for %s (%s-config)' % (libname, libname.lower()), package='',\n\t\t\t\t\tuselib_store=libname)\n\t\t\texcept conf.errors.ConfigurationError:\n\t\t\t\tconf.env[HAVE] = 0\n\n\tif conf.env[HAVE] and conf.options.SDL_SANITY_CHECK:\n\t\tif conf.options.SDL3:\n\t\t\tfragment = SDL_SANITY_FRAGMENT % 'SDL3/SDL.h'\n\t\telse:\n\t\t\tfragment = SDL_SANITY_FRAGMENT % 'SDL.h'\n\n\t\tconf.env[HAVE] = conf.check_cc(fragment=fragment, msg = 'Checking for %s sanity' % libname, use = libname, execute = False, mandatory = False)\n"
  },
  {
    "path": "scripts/waifulib/sebastian.py",
    "content": "from waflib import TaskGen, Task, Utils\nimport json, os\n\ndef configure(conf):\n\tconf.find_program('sebastian', var='SEBASTIAN', exts='.py', path_list=[conf.path.abspath()])\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.find_program('python')\n\t\tconf.env.SEBASTIAN = conf.env.PYTHON + conf.env.SEBASTIAN\n\nclass sebastian(Task.Task):\n\tcolor = 'CYAN'\n\trun_str = '${SEBASTIAN} -o ${TGT} ${SRC} --path ${TGT[0].parent.abspath()}'\n\text_in  = ['.json']\n\n\tdef keyword(self):\n\t\treturn 'Compiling meatpipe'\n\n\tdef scan(self):\n\t\tenv = self.env\n\t\tbld = self.generator.bld\n\n\t\tnode = self.inputs[0]\n\t\tout = self.outputs[0]\n\n\t\tcmd = env.SEBASTIAN + [node.abspath(), '--path', out.parent.abspath(), '--depend', '-']\n\t\toutput = bld.cmd_and_log(cmd, cwd = self.get_cwd(), env = env.env or None, quiet = True)\n\n\t\tdeps = json.loads(output)\n\t\tndeps = [bld.path.find_resource(str(dep)) for dep in deps]\n\t\tscript_index = 1 if env.DEST_OS == 'win32' else 0\n\t\tndeps.append(bld.path.find_resource(os.path.relpath(env.SEBASTIAN[script_index])))\n\n\t\treturn (ndeps, [])\n\n@TaskGen.extension('.json')\ndef process_meatpipe(self, src):\n\ttsk = self.create_task('sebastian', src, src.change_ext('.meat'))\n\n\tinst_to = getattr(self, 'install_path', None)\n\tif inst_to:\n\t\tself.add_install_files(install_to=inst_to,\n\t\t\tinstall_from=tsk.outputs[:], chmod=Utils.O755, task=tsk)\n"
  },
  {
    "path": "scripts/waifulib/vgui.py",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# mittorn, 2018\n\nfrom waflib.Configure import conf\nfrom waflib import Logs\nimport os\n\nVGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux']\n\nVGUI_FRAGMENT = '''#include <VGUI.h>\nint main() { return 0; }'''\n\ndef options(opt):\n\tgrp = opt.add_option_group('VGUI options')\n\n\tvgui_dev_path = os.path.join(opt.path.path_from(opt.root), 'vgui-dev')\n\n\tgrp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default=vgui_dev_path,\n\t\thelp = 'path to vgui-dev repo [default: %(default)s]')\n\n\tgrp.add_option('--enable-unsupported-vgui', action = 'store_true', dest = 'ENABLE_UNSUPPORTED_VGUI', default=False,\n\t\thelp = 'ignore all checks and allow link against anything [default: %(default)s]')\n\n\tgrp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=True,\n\t\thelp = 'skip checking VGUI sanity [default: %(default)s]' )\n\treturn\n\n@conf\ndef check_vgui(conf):\n\tif not conf.options.ENABLE_UNSUPPORTED_VGUI:\n\t\tconf.start_msg('Does this architecture support VGUI?')\n\n\t\tif conf.env.DEST_CPU != 'x86':\n\t\t\tconf.end_msg('no')\n\t\t\tLogs.warn('vgui is not supported on this CPU: ' + str(conf.env.DEST_CPU))\n\t\t\treturn False\n\t\telse: conf.end_msg('yes')\n\n\t\tconf.start_msg('Does this OS support VGUI?')\n\t\tif conf.env.DEST_OS not in VGUI_SUPPORTED_OS:\n\t\t\tconf.end_msg('no')\n\t\t\tLogs.warn('vgui is not supported on this OS: ' + str(conf.env.DEST_OS))\n\t\t\treturn False\n\t\telse: conf.end_msg('yes')\n\n\t\tconf.start_msg('Does this toolchain able to link VGUI?')\n\t\tif conf.env.DEST_OS == 'win32' and conf.env.COMPILER_CXX == 'g++':\n\t\t\tconf.end_msg('no')\n\t\t\t# we have ABI incompatibility ONLY on MinGW\n\t\t\tLogs.warn('vgui can\\'t be linked with MinGW')\n\t\t\treturn False\n\t\telse: conf.end_msg('yes')\n\n\tconf.start_msg('Configuring VGUI by provided path')\n\tvgui_dev = conf.options.VGUI_DEV\n\n\tlibpath = os.path.abspath(os.path.join(vgui_dev, 'lib'))\n\n\tif conf.env.DEST_OS == 'win32':\n\t\tconf.env.LIB_VGUI = ['vgui']\n\t\tlibpath = os.path.join(libpath, 'win32_vc6')\n\t\tif conf.env.DEST_CPU != 'x86':\n\t\t\t# for 32-bit x86 it's expected to be under win32_vc6\n\t\t\t# for others, it's expected to be under win32_vc6 subdirectory matching CPU arch (x86_64 for 64-bit CPUs)\n\t\t\tlibpath = os.path.join(libpath, conf.env.DEST_CPU)\n\t\tconf.env.LIBPATH_VGUI = [libpath]\n\telif conf.env.DEST_OS == 'linux':\n\t\tconf.env.LIB_VGUI = [':vgui.so']\n\t\tif conf.env.DEST_CPU != 'x86':\n\t\t\tlibpath = os.path.join(libpath, conf.env.DEST_CPU)\n\t\tconf.env.LIBPATH_VGUI = [libpath]\n\telif conf.env.DEST_OS == 'darwin':\n\t\tif conf.env.DEST_CPU != 'x86':\n\t\t\tconf.env.LDFLAGS_VGUI = [os.path.join(libpath, conf.env.DEST_CPU, 'vgui.dylib')]\n\t\telse:\n\t\t\tconf.env.LDFLAGS_VGUI = [os.path.join(libpath, 'vgui.dylib')]\n\telse:\n\t\t# TODO: figure out what to do here\n\t\tconf.env.LIB_VGUI = ['vgui']\n\t\tconf.env.LIBPATH_VGUI = [os.path.join(libpath, conf.env.DEST_OS, conf.env.DEST_CPU)]\n\n\tconf.env.INCLUDES_VGUI = [os.path.abspath(os.path.join(vgui_dev, 'include'))]\n\n\tconf.env.HAVE_VGUI = 1\n\tconf.end_msg('yes: {0}, {1}, {2}'.format(conf.env.LIB_VGUI, conf.env.LIBPATH_VGUI, conf.env.INCLUDES_VGUI))\n\n\tif conf.env.HAVE_VGUI and conf.options.VGUI_SANITY_CHECK:\n\t\ttry:\n\t\t\tconf.check_cxx(fragment=VGUI_FRAGMENT,\n\t\t\t\tmsg = 'Checking for library VGUI sanity',\n\t\t\t\tuse = 'VGUI',\n\t\t\t\texecute = False)\n\t\texcept conf.errors.ConfigurationError:\n\t\t\tconf.fatal(\"Can't compile simple program. Check your path to vgui-dev repository.\")\n\n\treturn True\n"
  },
  {
    "path": "scripts/waifulib/xcompile.py",
    "content": "# encoding: utf-8\n# xcompile.py -- crosscompiling utils\n# Copyright (C) 2018 a1batross\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\ntry: from fwgslib import get_flags_by_compiler\nexcept: from waflib.extras.fwgslib import get_flags_by_compiler\nfrom waflib import Logs, TaskGen\nfrom waflib.Tools import c_config\nfrom collections import OrderedDict\nimport os\nimport sys\n\nANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK']\nANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25, 27, 28]\nANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp\nANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC\nANDROID_NDK_UNIFIED_SYSROOT_MIN = 15\nANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag\nANDROID_NDK_BUGGED_LINKER_MAX = 22\nANDROID_NDK_API_MIN = {\n\t10: 3,\n\t19: 16,\n\t20: 16,\n\t23: 16,\n\t25: 19,\n\t27: 19,\n\t28: 21,\n} # minimal API level ndk revision supports\n\nANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21\nANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets\n\nNSWITCH_ENVVARS = ['DEVKITPRO']\n\nPSVITA_ENVVARS = ['VITASDK']\n\n# This class does support ONLY r10e and r19c/r20 NDK\nclass Android:\n\tctx            = None # waf context\n\tarch           = None\n\ttoolchain      = None\n\tapi            = None\n\tndk_home       = None\n\tndk_rev        = 0\n\tis_hardfloat   = False\n\tclang          = False\n\n\tdef __init__(self, ctx, arch, toolchain, api):\n\t\tself.ctx = ctx\n\t\tself.api = api\n\t\tself.toolchain = toolchain\n\t\tself.arch = arch\n\t\tself.exe = '.exe' if sys.platform.startswith('win32') or sys.platform.startswith('cygwin') else ''\n\n\t\tfor i in ANDROID_NDK_ENVVARS:\n\t\t\tself.ndk_home = os.getenv(i)\n\t\t\tif self.ndk_home != None:\n\t\t\t\tbreak\n\t\telse:\n\t\t\tctx.fatal('Set %s environment variable pointing to the root of Android NDK!' %\n\t\t\t\t' or '.join(ANDROID_NDK_ENVVARS))\n\n\t\t# TODO: this were added at some point of NDK development\n\t\t# but I don't know at which version\n\t\t# r10e don't have it\n\t\tsource_prop = os.path.join(self.ndk_home, 'source.properties')\n\t\tif os.path.exists(source_prop):\n\t\t\twith open(source_prop) as ndk_props_file:\n\t\t\t\tfor line in ndk_props_file.readlines():\n\t\t\t\t\ttokens = line.split('=')\n\t\t\t\t\ttrimed_tokens = [token.strip() for token in tokens]\n\n\t\t\t\t\tif 'Pkg.Revision' in trimed_tokens:\n\t\t\t\t\t\tself.ndk_rev = int(trimed_tokens[1].split('.')[0])\n\n\t\t\tif self.ndk_rev not in ANDROID_NDK_SUPPORTED:\n\t\t\t\tctx.fatal('Unknown NDK revision: %d' % (self.ndk_rev))\n\t\telse:\n\t\t\tself.ndk_rev = ANDROID_NDK_SUPPORTED[0]\n\n\t\tif 'clang' in self.toolchain or self.ndk_rev > ANDROID_NDK_GCC_MAX:\n\t\t\tself.clang = True\n\n\t\tif self.arch == 'armeabi-v7a-hard':\n\t\t\tif self.ndk_rev <= ANDROID_NDK_HARDFP_MAX:\n\t\t\t\tself.arch = 'armeabi-v7a' # Only armeabi-v7a have hard float ABI\n\t\t\t\tself.is_hardfloat = True\n\t\t\telse:\n\t\t\t\tctx.fatal('NDK does not support hardfloat ABI')\n\n\t\tif self.api < ANDROID_NDK_API_MIN[self.ndk_rev]:\n\t\t\tself.api = ANDROID_NDK_API_MIN[self.ndk_rev]\n\t\t\tLogs.warn('API level automatically was set to %d due to NDK support' % self.api)\n\n\t\tif self.is_arm64() or self.is_amd64() and self.api < ANDROID_64BIT_API_MIN:\n\t\t\tself.api = ANDROID_64BIT_API_MIN\n\t\t\tLogs.warn('API level for 64-bit target automatically was set to %d' % self.api)\n\n\tdef is_host(self):\n\t\t'''\n\t\tChecks if we using host compiler(implies clang)\n\t\t'''\n\t\treturn self.toolchain == 'host'\n\n\tdef is_arm(self):\n\t\t'''\n\t\tChecks if selected architecture is **32-bit** ARM\n\t\t'''\n\t\treturn self.arch.startswith('armeabi')\n\n\tdef is_x86(self):\n\t\t'''\n\t\tChecks if selected architecture is **32-bit** or **64-bit** x86\n\t\t'''\n\t\treturn self.arch == 'x86'\n\n\tdef is_amd64(self):\n\t\t'''\n\t\tChecks if selected architecture is **64-bit** x86\n\t\t'''\n\t\treturn self.arch == 'x86_64'\n\n\tdef is_arm64(self):\n\t\t'''\n\t\tChecks if selected architecture is AArch64\n\t\t'''\n\t\treturn self.arch == 'aarch64'\n\n\tdef is_clang(self):\n\t\t'''\n\t\tChecks if selected toolchain is Clang (TODO)\n\t\t'''\n\t\treturn self.clang\n\n\tdef is_hardfp(self):\n\t\treturn self.is_hardfloat\n\n\tdef ndk_triplet(self, llvm_toolchain = False, toolchain_folder = False):\n\t\tif self.is_x86():\n\t\t\tif toolchain_folder:\n\t\t\t\treturn 'x86'\n\t\t\telse:\n\t\t\t\treturn 'i686-linux-android'\n\t\telif self.is_arm():\n\t\t\tif llvm_toolchain:\n\t\t\t\treturn 'armv7a-linux-androideabi'\n\t\t\telse:\n\t\t\t\treturn 'arm-linux-androideabi'\n\t\telif self.is_amd64() and toolchain_folder:\n\t\t\treturn 'x86_64'\n\t\telse:\n\t\t\treturn self.arch + '-linux-android'\n\n\tdef apk_arch(self):\n\t\tif self.is_arm64():\n\t\t\treturn 'arm64-v8a'\n\t\treturn self.arch\n\n\tdef gen_host_toolchain(self):\n\t\t# With host toolchain we don't care about OS\n\t\t# so just download NDK for Linux x86_64\n\t\tif 'HOST_TOOLCHAIN' in self.ctx.environ:\n\t\t\treturn self.ctx.environ['HOST_TOOLCHAIN']\n\t\tif self.is_host():\n\t\t\treturn 'linux-x86_64'\n\n\t\tif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):\n\t\t\tosname = 'windows'\n\t\telif sys.platform.startswith('darwin'):\n\t\t\tosname = 'darwin'\n\t\telif sys.platform.startswith('linux'):\n\t\t\tosname = 'linux'\n\t\telse:\n\t\t\tself.ctx.fatal('Unsupported by NDK host platform')\n\n\t\tif sys.maxsize > 2**32:\n\t\t\tarch = 'x86_64'\n\t\telse: arch = 'x86'\n\n\t\treturn '%s-%s' % (osname, arch)\n\n\tdef gen_gcc_toolchain_path(self):\n\t\tpath = 'toolchains'\n\t\ttoolchain_host = self.gen_host_toolchain()\n\n\t\tif self.is_clang():\n\t\t\ttoolchain_folder = 'llvm'\n\t\telse:\n\t\t\tif self.is_host():\n\t\t\t\ttoolchain = '4.9'\n\t\t\telse:\n\t\t\t\ttoolchain = self.toolchain\n\n\t\t\ttoolchain_folder = '%s-%s' % (self.ndk_triplet(toolchain_folder = True), toolchain)\n\n\t\treturn os.path.abspath(os.path.join(self.ndk_home, path, toolchain_folder, 'prebuilt', toolchain_host))\n\n\tdef gen_toolchain_path(self):\n\t\tif self.is_clang():\n\t\t\tbase = ''\n\t\telse:\n\t\t\tbase = self.ndk_triplet() + '-'\n\t\treturn os.path.join(self.gen_gcc_toolchain_path(), 'bin', base)\n\n\tdef gen_binutils_path(self):\n\t\tif self.ndk_rev >= 23:\n\t\t\treturn os.path.join(self.gen_gcc_toolchain_path(), 'bin')\n\t\treturn os.path.join(self.gen_gcc_toolchain_path(), self.ndk_triplet(), 'bin')\n\n\tdef cc(self):\n\t\tif self.is_host():\n\t\t\ts = 'clang'\n\t\t\tenviron = getattr(self.ctx, 'environ', os.environ)\n\n\t\t\tif 'CC' in environ:\n\t\t\t\ts = environ['CC']\n\n\t\t\treturn '%s --target=%s%d' % (s, self.ndk_triplet(), self.api)\n\n\t\tif self.is_clang():\n\t\t\treturn '%s --target=%s%d' % (self.gen_toolchain_path() + 'clang' + self.exe, self.ndk_triplet(), self.api)\n\n\t\treturn self.gen_toolchain_path() + 'gcc'\n\n\tdef cxx(self):\n\t\tif self.is_host():\n\t\t\ts = 'clang++'\n\t\t\tenviron = getattr(self.ctx, 'environ', os.environ)\n\n\t\t\tif 'CXX' in environ:\n\t\t\t\ts = environ['CXX']\n\n\t\t\treturn '%s --target=%s%d' % (s, self.ndk_triplet(), self.api)\n\n\t\tif self.is_clang():\n\t\t\treturn '%s --target=%s%d' % (self.gen_toolchain_path() + 'clang++' + self.exe, self.ndk_triplet(), self.api)\n\n\t\treturn self.gen_toolchain_path() + 'g++'\n\n\tdef strip(self):\n\t\tif self.is_host():\n\t\t\tenviron = getattr(self.ctx, 'environ', os.environ)\n\t\t\tif 'STRIP' in environ:\n\t\t\t\treturn environ['STRIP']\n\t\t\treturn 'llvm-strip'\n\n\t\tif self.ndk_rev >= 23:\n\t\t\treturn os.path.join(self.gen_binutils_path(), 'llvm-strip' + self.exe)\n\t\treturn os.path.join(self.gen_binutils_path(), 'strip' + self.exe)\n\n\tdef ar(self):\n\t\tif self.is_host():\n\t\t\tenviron = getattr(self.ctx, 'environ', os.environ)\n\t\t\tif 'AR' in environ:\n\t\t\t\treturn environ['AR']\n\t\t\treturn 'llvm-ar'\n\n\t\tif self.ndk_rev >= 23:\n\t\t\treturn os.path.join(self.gen_binutils_path(), 'llvm-ar' + self.exe)\n\t\treturn os.path.join(self.gen_binutils_path(), 'ar' + self.exe)\n\n\tdef system_stl(self):\n\t\t# TODO: proper STL support\n\t\treturn os.path.abspath(os.path.join(self.ndk_home, 'sources', 'cxx-stl', 'system', 'include'))\n\n\tdef libsysroot(self):\n\t\tarch = self.arch\n\t\tif self.is_arm():\n\t\t\tarch = 'arm'\n\t\telif self.is_arm64():\n\t\t\tarch = 'arm64'\n\t\tpath = 'platforms/android-%s/arch-%s' % (self.api, arch)\n\n\t\treturn os.path.abspath(os.path.join(self.ndk_home, path))\n\n\tdef sysroot(self):\n\t\tif self.ndk_rev >= ANDROID_NDK_UNIFIED_SYSROOT_MIN:\n\t\t\treturn os.path.abspath(os.path.join(self.ndk_home, 'sysroot'))\n\t\telse:\n\t\t\treturn self.libsysroot()\n\n\tdef cflags(self, cxx = False):\n\t\tcflags = []\n\n\t\tif self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX:\n\t\t\tcflags += ['--sysroot=%s' % (self.sysroot())]\n\t\telse:\n\t\t\tif self.is_host():\n\t\t\t\tcflags += [\n\t\t\t\t\t'--sysroot=%s/sysroot' % (self.gen_gcc_toolchain_path()),\n\t\t\t\t\t'-isystem', '%s/usr/include/' % (self.sysroot())\n\t\t\t\t]\n\n\t\tcflags += ['-I%s' % (self.system_stl())]\n\t\tif not self.is_clang():\n\t\t\tcflags += ['-DANDROID', '-D__ANDROID__']\n\n\t\tif cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']:\n\t\t\tcflags += ['-fno-sized-deallocation']\n\n\t\tif self.is_clang():\n\t\t\t# stpcpy() isn't available in early Android versions\n\t\t\t# disable it here so Clang won't use it\n\t\t\tif self.api < ANDROID_STPCPY_API_MIN:\n\t\t\t\tcflags += ['-fno-builtin-stpcpy']\n\n\t\tif self.is_arm():\n\t\t\tif self.arch == 'armeabi-v7a':\n\t\t\t\t# ARMv7 support\n\t\t\t\tcflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9']\n\n\t\t\t\tif self.is_hardfp():\n\t\t\t\t\tcflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK']\n\n\t\t\t\t\tif self.is_host():\n\t\t\t\t\t\t# Clang builtin redefine w/ different calling convention bug\n\t\t\t\t\t\t# NOTE: I did not added complex.h functions here, despite\n\t\t\t\t\t\t# that NDK devs forgot to put __NDK_FPABI_MATH__ for complex\n\t\t\t\t\t\t# math functions\n\t\t\t\t\t\t# I personally don't need complex numbers support, but if you want it\n\t\t\t\t\t\t# just run sed to patch header\n\t\t\t\t\t\tfor f in ['strtod', 'strtof', 'strtold']:\n\t\t\t\t\t\t\tcflags += ['-fno-builtin-%s' % f]\n\t\t\t\telse:\n\t\t\t\t\tcflags += ['-mfloat-abi=softfp']\n\t\t\telse:\n\t\t\t\t# ARMv5 support\n\t\t\t\tcflags += ['-march=armv5te', '-msoft-float']\n\t\telif self.is_x86():\n\t\t\tcflags += ['-mtune=atom', '-march=atom', '-mssse3', '-mfpmath=sse']\n\t\treturn cflags\n\n\t# they go before object list\n\tdef linkflags(self):\n\t\tlinkflags = []\n\t\tif self.is_host():\n\t\t\tlinkflags += ['--gcc-toolchain=%s' % self.gen_gcc_toolchain_path()]\n\n\t\tif self.ndk_rev <= ANDROID_NDK_SYSROOT_FLAG_MAX:\n\t\t\tlinkflags += ['--sysroot=%s' % (self.sysroot())]\n\t\telif self.is_host():\n\t\t\tlinkflags += ['--sysroot=%s/sysroot' % (self.gen_gcc_toolchain_path())]\n\n\t\tif self.is_clang() or self.is_host():\n\t\t\tlinkflags += ['-fuse-ld=lld']\n\t\telse: linkflags += ['-no-canonical-prefixes']\n\n\t\tlinkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined']\n\n\t\tlinkflags += [\"-Wl,-z,max-page-size=16384\"]\n\n\t\tif self.ndk_rev <= ANDROID_NDK_BUGGED_LINKER_MAX:\n\t\t\tlinkflags += [\"-Wl,-z,common-page-size=16384\"]\n\n\t\treturn linkflags\n\n\tdef ldflags(self):\n\t\tldflags = []\n\n\t\tif self.ndk_rev < 23:\n\t\t\tldflags += ['-lgcc']\n\n\t\tif self.is_clang() or self.is_host():\n\t\t\tldflags += ['-stdlib=libstdc++', '-lc++abi']\n\t\telse: ldflags += ['-no-canonical-prefixes']\n\n\t\tif self.is_arm():\n\t\t\tif self.arch == 'armeabi-v7a':\n\t\t\t\tldflags += ['-march=armv7-a', '-mthumb']\n\n\t\t\t\tif not self.is_clang() and not self.is_host(): # lld only\n\t\t\t\t\tldflags += ['-Wl,--fix-cortex-a8']\n\n\t\t\t\tif self.is_hardfp():\n\t\t\t\t\tldflags += ['-Wl,--no-warn-mismatch', '-lm_hard']\n\t\t\telse:\n\t\t\t\tldflags += ['-march=armv5te']\n\t\treturn ldflags\n\nclass NintendoSwitch:\n\tctx          = None # waf context\n\tarch         = \"arm64\"\n\tdkp_dir      = None\n\tportlibs_dir = None\n\tdka64_dir    = None\n\tlibnx_dir    = None\n\n\tdef __init__(self, ctx):\n\t\tself.ctx = ctx\n\n\t\tfor i in NSWITCH_ENVVARS:\n\t\t\tself.dkp_dir = os.getenv(i)\n\t\t\tif self.dkp_dir != None:\n\t\t\t\tbreak\n\t\telse:\n\t\t\tctx.fatal('Set %s environment variable pointing to the DEVKITPRO home!' %\n\t\t\t\t' or '.join(NSWITCH_ENVVARS))\n\n\t\tself.dkp_dir = os.path.abspath(self.dkp_dir)\n\n\t\tself.dka64_dir = os.path.join(self.dkp_dir, 'devkitA64')\n\t\tif not os.path.exists(self.dka64_dir):\n\t\t\tctx.fatal('devkitA64 not found in `%s`. Install devkitA64!' % self.dka64_dir)\n\n\t\tself.libnx_dir = os.path.join(self.dkp_dir, 'libnx')\n\t\tif not os.path.exists(self.libnx_dir):\n\t\t\tctx.fatal('libnx not found in `%s`. Install libnx!' % self.libnx_dir)\n\n\t\tself.portlibs_dir = os.path.join(self.dkp_dir, 'portlibs', 'switch')\n\t\tif not os.path.exists(self.portlibs_dir):\n\t\t\tctx.fatal('No Switch libraries found in `%s`!' % self.portlibs_dir)\n\n\tdef gen_toolchain_prefix(self):\n\t\treturn 'aarch64-none-elf-'\n\n\tdef gen_gcc_toolchain_path(self):\n\t\treturn os.path.join(self.dka64_dir, 'bin', self.gen_toolchain_prefix())\n\n\tdef cc(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'gcc'\n\n\tdef cxx(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'g++'\n\n\tdef strip(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'strip'\n\n\tdef pkgconfig(self):\n\t\t# counter-intuitively, this motherfucker is in $DEVKITPRO/portlibs/switch/bin\n\t\treturn os.path.join(self.portlibs_dir, 'bin', self.gen_toolchain_prefix() + 'pkg-config')\n\n\tdef cflags(self, cxx = False):\n\t\tcflags = []\n\t\t# arch flags\n\t\tcflags += ['-D__SWITCH__', '-march=armv8-a+crc+crypto', '-mtune=cortex-a57', '-mtp=soft', '-ftls-model=local-exec', '-fPIE']\n\t\t# help the linker out\n\t\tcflags += ['-ffunction-sections', '-fdata-sections']\n\t\t# base include dirs\n\t\tcflags += ['-isystem %s/include' % self.libnx_dir, '-I%s/include' % self.portlibs_dir]\n\t\t# the game wants GNU extensions\n\t\tif cxx:\n\t\t\tcflags += ['-std=gnu++17', '-D_GNU_SOURCE']\n\t\telse:\n\t\t\tcflags += ['-std=gnu11', '-D_GNU_SOURCE']\n\t\treturn cflags\n\n\t# they go before object list\n\tdef linkflags(self):\n\t\tlinkflags = ['-fPIE', '-specs=%s/switch.specs' % self.libnx_dir]\n\t\t# libsolder only supports sysv hashes and we need to build everything with -rdynamic\n\t\tlinkflags += ['-Wl,--hash-style=sysv', '-rdynamic']\n\t\t# avoid pulling in and exposing mesa's internals, that crashes it for some god forsaken reason\n\t\tlinkflags += ['-Wl,--exclude-libs=libglapi.a', '-Wl,--exclude-libs=libdrm_nouveau.a']\n\t\treturn linkflags\n\n\tdef ldflags(self):\n\t\t# NOTE: shared libraries should be built without standard libs, so that they could import their contents from the NRO,\n\t\t# but executables, including the SDL2 sanity check, will generally require libstdc++ and libm, which we will add manually\n\t\tldflags = [] # ['-lm', '-lstdc++']\n\t\treturn ldflags\n\nclass PSVita:\n\tctx          = None # waf context\n\tarch         ='armeabi-v7a-hard'\n\tvitasdk_dir  = None\n\n\tdef __init__(self, ctx):\n\t\tself.ctx = ctx\n\n\t\tfor i in PSVITA_ENVVARS:\n\t\t\tself.vitasdk_dir = os.getenv(i)\n\t\t\tif self.vitasdk_dir != None:\n\t\t\t\tbreak\n\t\telse:\n\t\t\tctx.fatal('Set %s environment variable pointing to the VitaSDK directory!' %\n\t\t\t\t' or '.join(PSVITA_ENVVARS))\n\n\tdef gen_toolchain_prefix(self):\n\t\treturn 'arm-vita-eabi-'\n\n\tdef gen_gcc_toolchain_path(self):\n\t\treturn os.path.join(self.vitasdk_dir, 'bin', self.gen_toolchain_prefix())\n\n\tdef cc(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'gcc'\n\n\tdef cxx(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'g++'\n\n\tdef strip(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'strip'\n\n\tdef ar(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'ar'\n\n\tdef pkgconfig(self):\n\t\treturn self.gen_gcc_toolchain_path() + 'pkg-config'\n\n\tdef cflags(self, cxx = False):\n\t\tcflags = []\n\t\t# arch flags\n\t\tcflags += ['-D__vita__', '-mtune=cortex-a9', '-mfpu=neon']\n\t\t# necessary linker flags\n\t\tcflags += ['-Wl,-q', '-Wl,-z,nocopyreloc']\n\t\t# this optimization is broken in vitasdk\n\t\tcflags += ['-fno-optimize-sibling-calls']\n\t\t# disable some ARM bullshit\n\t\tcflags += ['-fno-short-enums', '-Wno-attributes']\n\t\t# base include dir\n\t\tcflags += ['-isystem %s/arm-vita-eabi/include' % self.vitasdk_dir]\n\t\t# SDL include dir\n\t\tcflags += ['-I%s/arm-vita-eabi/include/SDL2' % self.vitasdk_dir]\n\t\treturn cflags\n\n\t# they go before object list\n\tdef linkflags(self):\n\t\tlinkflags = ['-Wl,--hash-style=sysv', '-Wl,-q', '-Wl,-z,nocopyreloc', '-mtune=cortex-a9', '-mfpu=neon']\n\t\t# enforce no-short-enums again\n\t\tlinkflags += ['-Wl,-no-enum-size-warning', '-fno-short-enums']\n\t\treturn linkflags\n\n\tdef ldflags(self):\n\t\tldflags = []\n\t\treturn ldflags\n\ndef options(opt):\n\txc = opt.add_option_group('Cross compile options')\n\txc.add_option('--android', action='store', dest='ANDROID_OPTS', default=None,\n\t\thelp='enable building for android, format: --android=<arch>,<toolchain>,<api>, example: --android=armeabi-v7a-hard,4.9,9')\n\txc.add_option('--enable-magx', action='store_true', dest='MAGX', default=False,\n\t\thelp='enable building for Motorola MAGX [default: %(default)s]')\n\txc.add_option('--enable-msvc-wine', action='store_true', dest='MSVC_WINE', default=False,\n\t\thelp='enable building with MSVC using Wine [default: %(default)s]')\n\txc.add_option('--nswitch', action='store_true', dest='NSWITCH', default = False,\n\t\thelp='enable building for Nintendo Switch [default: %(default)s]')\n\txc.add_option('--psvita', action='store_true', dest='PSVITA', default = False,\n\t\thelp='enable building for PlayStation Vita [default: %(default)s]')\n\txc.add_option('--sailfish', action='store', dest='SAILFISH', default = None,\n\t\thelp='enable building for Sailfish/Aurora')\n\txc.add_option('--emscripten', action='store_true', dest='EMSCRIPTEN', default = None,\n\t\thelp='enable building for Emscripten')\n\ndef configure(conf):\n\tif 'CROSS_COMPILE' in conf.environ:\n\t\ttoolchain_path = conf.environ['CROSS_COMPILE']\n\t\tconf.environ['CC'] = toolchain_path + 'cc'\n\t\tconf.environ['CXX'] = toolchain_path + 'c++'\n\t\tconf.environ['STRIP'] = toolchain_path + 'strip'\n\t\tconf.environ['OBJDUMP'] = toolchain_path + 'objdump'\n\t\tconf.environ['AR'] = toolchain_path + 'ar'\n\telif conf.options.ANDROID_OPTS:\n\t\tvalues = conf.options.ANDROID_OPTS.split(',')\n\t\tif len(values) != 3:\n\t\t\tconf.fatal('Invalid --android paramater value!')\n\n\t\tvalid_archs = ['x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'armeabi-v7a-hard', 'aarch64']\n\n\t\tif values[0] == 'arm64-v8a':\n\t\t\tvalues[0] = 'aarch64'\n\n\t\tif values[0] not in valid_archs:\n\t\t\tconf.fatal('Unknown arch: %s. Supported: %r' % (values[0], ', '.join(valid_archs)))\n\n\t\tconf.android = android = Android(conf, values[0], values[1], int(values[2]))\n\n\t\tconf.environ['CC'] = android.cc()\n\t\tconf.environ['CXX'] = android.cxx()\n\t\tconf.environ['STRIP'] = android.strip()\n\t\tconf.environ['AR'] = android.ar()\n\t\tconf.env.CFLAGS += android.cflags()\n\t\tconf.env.CXXFLAGS += android.cflags(True)\n\t\tconf.env.LINKFLAGS += android.linkflags()\n\t\tconf.env.LDFLAGS += android.ldflags()\n\n\t\tfrom waflib.Tools.compiler_c import c_compiler\n\t\tfrom waflib.Tools.compiler_cxx import cxx_compiler\n\t\tc_compiler['win32'] = ['clang' if android.is_clang() or android.is_host() else 'gcc']\n\t\tcxx_compiler['win32'] = ['clang++' if android.is_clang() or android.is_host() else 'gxx']\n\n\t\tconf.env.HAVE_M = True\n\t\tif android.is_hardfp():\n\t\t\tconf.env.LIB_M = ['m_hard']\n\t\telse: conf.env.LIB_M = ['m']\n\n\t\tconf.msg('Selected Android NDK', '%s, version: %d' % (android.ndk_home, android.ndk_rev))\n\t\t# no need to print C/C++ compiler, as it would be printed by compiler_c/cxx\n\t\tconf.msg('... C/C++ flags', ' '.join(android.cflags()).replace(android.ndk_home, '$NDK/'))\n\t\tconf.msg('... link flags', ' '.join(android.linkflags()).replace(android.ndk_home, '$NDK/'))\n\t\tconf.msg('... ld flags', ' '.join(android.ldflags()).replace(android.ndk_home, '$NDK/'))\n\telif conf.options.MAGX:\n\t\t# useless to change toolchain path, as toolchain meant to be placed in this path\n\t\ttoolchain_path = '/opt/toolchains/motomagx/arm-eabi2/lib/'\n\t\tconf.env.INCLUDES_MAGX = [toolchain_path + i for i in ['ezx-z6/include', 'qt-2.3.8/include']]\n\t\tconf.env.LIBPATH_MAGX  = [toolchain_path + i for i in ['ezx-z6/lib', 'qt-2.3.8/lib']]\n\t\tconf.env.LINKFLAGS_MAGX = ['-Wl,-rpath-link=' + i for i in conf.env.LIBPATH_MAGX]\n\telif conf.options.MSVC_WINE:\n\t\ttry:\n\t\t\ttoolchain_path = conf.environ['MSVC_WINE_PATH']\n\t\texcept KeyError:\n\t\t\tconf.fatal('Set MSVC_WINE_PATH environment variable to the MSVC toolchain root!')\n\n\t\tconf.environ['CC'] = conf.environ['CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'cl')\n\t\tconf.environ['LINK_CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'link')\n\t\tconf.environ['AR'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'lib')\n\t\tconf.environ['WINRC'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'rc')\n\t\tconf.env.DEST_OS = 'win32'\n\t\tconf.env.DEST_CPU = conf.env.MSVC_TARGETS[0]\n\t\tconf.env.COMPILER_CXX = conf.env.COMPILER_CC = 'msvc'\n\telif conf.options.NSWITCH:\n\t\tconf.nswitch = nswitch = NintendoSwitch(conf)\n\t\tconf.environ['CC'] = nswitch.cc()\n\t\tconf.environ['CXX'] = nswitch.cxx()\n\t\tconf.environ['STRIP'] = nswitch.strip()\n\t\tconf.env.PKGCONFIG = nswitch.pkgconfig()\n\t\tconf.env.CFLAGS += nswitch.cflags()\n\t\tconf.env.CXXFLAGS += nswitch.cflags(True)\n\t\tconf.env.LINKFLAGS += nswitch.linkflags()\n\t\tconf.env.LDFLAGS += nswitch.ldflags()\n\t\tconf.env.HAVE_M = True\n\t\tconf.env.LIB_M = ['m']\n\t\tconf.env.DEST_OS = 'nswitch'\n\telif conf.options.PSVITA:\n\t\tconf.psvita = psvita = PSVita(conf)\n\t\tconf.environ['CC'] = psvita.cc()\n\t\tconf.environ['CXX'] = psvita.cxx()\n\t\tconf.environ['STRIP'] = psvita.strip()\n\t\tconf.environ['AR'] = psvita.ar()\n\t\tconf.env.PKGCONFIG = psvita.pkgconfig()\n\t\tconf.env.CFLAGS += psvita.cflags()\n\t\tconf.env.CXXFLAGS += psvita.cflags(True)\n\t\tconf.env.LINKFLAGS += psvita.linkflags()\n\t\tconf.env.LDFLAGS += psvita.ldflags()\n\t\tconf.env.HAVE_M = True\n\t\tconf.env.LIB_M = ['m']\n\t\tconf.env.VRTLD = ['vrtld']\n\t\tconf.env.DEST_OS = 'psvita'\n\telif conf.options.EMSCRIPTEN:\n\t\t# Emscripten compiler is just wrapper to clang\n\t\t# But we need to setup platform modifiers, they all are contained inside c_emscripten.py for now\n\t\t# In future, that could be upstreamed to waf itself and this wouldn't be needed\n\t\tconf.environ['CC'] = 'emcc'\n\t\tconf.environ['CXX'] = 'em++'\n\t\tconf.environ['AR'] = 'emar'\n\t\tconf.environ['STRIP'] = 'emstrip'\n\t\tconf.environ['OBJCOPY'] = 'llvm-objcopy'\n\t\tconf.load('c_emscripten')\n\n\tconf.env.MAGX = conf.options.MAGX\n\tconf.env.MSVC_WINE = conf.options.MSVC_WINE\n\tconf.env.SAILFISH = conf.options.SAILFISH\n\tMACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch', '__vita__' : 'psvita', '__wasi__': 'wasi', '__EMSCRIPTEN__' : 'emscripten' })\n\tfor k in c_config.MACRO_TO_DESTOS:\n\t\tMACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important\n\tc_config.MACRO_TO_DESTOS  = MACRO_TO_DESTOS\n\ndef post_compiler_cxx_configure(conf):\n\tconf.msg('Target OS', conf.env.DEST_OS)\n\tconf.msg('Target CPU', conf.env.DEST_CPU)\n\tconf.msg('Target binfmt', conf.env.DEST_BINFMT)\n\n\tif conf.options.ANDROID_OPTS:\n\t\tif conf.android.ndk_rev == 19:\n\t\t\tconf.env.CXXFLAGS_cxxshlib += ['-static-libstdc++']\n\t\t\tconf.env.LDFLAGS_cxxshlib += ['-static-libstdc++']\n\telif conf.options.MAGX:\n\t\tfor lib in ['qte-mt', 'ezxappbase', 'ezxpm', 'log_util']:\n\t\t\tconf.check_cc(lib=lib, use='MAGX', uselib_store='MAGX')\n\n\treturn\n\ndef post_compiler_c_configure(conf):\n\tconf.msg('Target OS', conf.env.DEST_OS)\n\tconf.msg('Target CPU', conf.env.DEST_CPU)\n\tconf.msg('Target binfmt', conf.env.DEST_BINFMT)\n\n\treturn\n\nfrom waflib.Tools import compiler_cxx, compiler_c\n\ncompiler_cxx_configure = getattr(compiler_cxx, 'configure')\ncompiler_c_configure = getattr(compiler_c, 'configure')\n\ndef patch_compiler_cxx_configure(conf):\n\tif not conf.env.MSVC_WINE:\n\t\tcompiler_cxx_configure(conf)\n\telse:\n\t\tconf.load('msvc', funs='no_autodetect')\n\tpost_compiler_cxx_configure(conf)\n\ndef patch_compiler_c_configure(conf):\n\tif not conf.env.MSVC_WINE:\n\t\tcompiler_c_configure(conf)\n\telse:\n\t\tconf.load('msvc', funs='no_autodetect')\n\tpost_compiler_c_configure(conf)\n\nsetattr(compiler_cxx, 'configure', patch_compiler_cxx_configure)\nsetattr(compiler_c, 'configure', patch_compiler_c_configure)\n\n@TaskGen.feature('cshlib', 'cxxshlib', 'dshlib', 'fcshlib', 'vnum')\n@TaskGen.after_method('apply_link', 'propagate_uselib_vars')\n@TaskGen.before_method('apply_vnum')\ndef apply_android_soname(self):\n\t\"\"\"\n\tEnforce SONAME on Android\n\t\"\"\"\n\tif self.env.DEST_OS != 'android':\n\t\treturn\n\n\tsetattr(self, 'vnum', None) # remove vnum, so SONAME would not be overwritten\n\tlink = self.link_task\n\tnode = link.outputs[0]\n\tlibname = node.name\n\tv = self.env.SONAME_ST % libname\n\tself.env.append_value('LINKFLAGS', v.split())\n"
  },
  {
    "path": "scripts/waifulib/xshlib.py",
    "content": "# encoding: utf-8\n# xshlib.py -- advanced linking utils\n# Copyright (C) 2019 mittorn\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n\nfrom waflib import Logs, Utils, TaskGen, Task\nfrom waflib.Tools import ccroot, c, cxx\n\nMAIN_BINARY = 'xash'\n\ndef options(opt):\n\topt.add_option('--static-linking', action='store', dest='STATIC_LINKING', default=None)\n\ndef configure(conf):\n\tif conf.options.STATIC_LINKING:\n\t\tconf.find_program('ld')\n\t\tconf.find_program('objcopy')\n\t\tconf.env.STATIC_LINKING = conf.options.STATIC_LINKING\n\t\tconf.add_os_flags('LD_RELOCATABLE_FLAGS')\n\ndef build(bld):\n\tif bld.env.STATIC_LINKING:\n\t\tapply_static(MAIN_BINARY,*bld.env.STATIC_LINKING.split(','))\n\nclass objcopy_relocatable_lib(Task.Task):\n\t\"remove all exports except of lib_${NAME}_exports\"\n\tno_errcheck_out = True\n\trun_str = '${OBJCOPY} -G lib_${NAME}_exports ${SRC[0].abspath()} ${TGT[0].abspath()}'\n\tdef keyword(self):\n\t\treturn 'ObjCopy'\n\nclass xshlib(ccroot.link_task):\n\t\"make relocatable library\"\n\tno_errcheck_out = True\n\trun_str = '${LD} -r -o ${TGT[0].abspath()} ${LD_RELOCATABLE_FLAGS} ${CCLNK_SRC_F}${SRC}'\n\n\tdef add_target(self, target):\n\t\t\"create objcopy task for target\"\n\t\tif not self.env.LD_RELOCATABLE_FLAGS:\n\t\t\tself.env.LD_RELOCATABLE_FLAGS = []\n\t\t\tif '-m32' in self.env.LINKFLAGS:\n\t\t\t\tself.env.LD_RELOCATABLE_FLAGS.append('-melf_i386')\n\n\t\tbase = self.generator.path\n\t\ttarget_unstripped = base.find_or_declare('%s.unstripped.o'% target)\n\t\ttarget_stripped = base.find_or_declare('%s.o'% target)\n\n\t\tself.set_outputs(target_unstripped)\n\t\tself.generator.objcopy_task= self.generator.create_task('objcopy_relocatable_lib', target_unstripped, target_stripped)\n\t\tself.generator.objcopy_task.env['NAME'] = target\n\nclass cprogram_static(c.cprogram):\n\t\"build static c program\"\n\trun_str = '${LINK_CC} -static ${LINKFLAGS} ${CCLNK_SRC_F}${SRC} ${CCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${STLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} ${LDFLAGS}'\n\nclass cxxprogram_static(cxx.cxxprogram):\n\t\"build static cxx program\"\n\trun_str = '${LINK_CXX} -static ${LINKFLAGS} ${CXXLNK_SRC_F}${SRC} ${CXXLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${STLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} ${LDFLAGS}'\n\n# usevars are same\nccroot.USELIB_VARS['cprogram_static'] = ccroot.USELIB_VARS['cxxprogram_static'] = ccroot.USELIB_VARS['cxxprogram']\n\ndef apply_static(main, *reloc):\n\t\"apply xshlib tasks and generate files\"\n\n\tdef write_libraries_list(out_node):\n\t\t\"generate library list\"\n\n\t\tlibraries = reloc\n\t\texterns = '\\n'.join(['extern table_t lib_%s_exports[];' % e for e in libraries])\n\t\ttable = '\\n'.join(['{ \"%s\", &lib_%s_exports },' % (e, e) for e in libraries])\n\t\tout_node.write('%s\\nstruct {const char *name;void *func;} libs[] = {\\n%s\\n{0,0}\\n};\\n' % (externs, table ))\n\n\n\tdef write_export_list(name, in_node, out_node):\n\t\t\"generate exports list for library\"\n\n\t\texports = in_node.read().splitlines()\n\t\texterns = '\\n'.join(['extern void %s(void);' % e for e in exports])\n\t\ttable = '\\n'.join(['{ \"%s\", &%s },' % (e, e) for e in exports])\n\t\tout_node.write('%s\\nstruct {const char *name;void *func;} lib_%s_exports[] = {\\n%s\\n{0,0}\\n};\\n' % (externs, name, table ))\n\n\t@TaskGen.feature('cshlib', 'cxxshlib')\n\t@TaskGen.before('process_source', 'propogate_uselib_vars')\n\tdef apply_xshlib(self):\n\t\t\"apply xshlib feature and inject link_helper.c to sources\"\n\t\tif self.name in reloc:\n\t\t\tfor k in ('cshlib', 'cxxshlib'):\n\t\t\t\tif k in self.features:\n\t\t\t\t\tself.features.remove(k)\n\t\t\tself.features.insert(0, 'xshlib')\n\t\t\tin_node = self.path.get_src().make_node('exports.txt')\n\t\t\tbldnode = self.path.get_bld()\n\t\t\tbldnode.mkdir()\n\t\t\tout_node = bldnode.make_node('link_helper.c')\n\t\t\twrite_export_list(self.name,in_node, out_node)\n\t\t\tself.source = Utils.to_list(self.source) + [out_node]\n\n\t@TaskGen.feature('cshlib', 'cxxshlib', 'cprogram', 'cxxprogram', 'cprogram_static', 'cxxprogram_static')\n\t@TaskGen.before('process_source')\n\tdef add_deps(self):\n\t\t\"add all relocatable objects to main binary source list\"\n\t\tif self.name == main:\n\t\t\twrite_libraries_list(self.path.get_bld().make_node('generated_library_tables.h'))\n\n\t\t\tfor t in reloc:\n\t\t\t\tself.source += [self.bld.get_tgen_by_name(t).objcopy_task.outputs[0]]\n"
  },
  {
    "path": "scripts/waifulib/zip.py",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n\nfrom waflib import TaskGen, Task, Logs, Utils\nimport zipfile, sys\n\nclass ziparchive(Task.Task):\n\tcolor = 'YELLOW'\n\n\tdef __str__(self):\n\t\ttgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])\n\t\tcount = len(self.inputs)\n\t\treturn '%s: %d files -> %s' % (self.__class__.__name__, count, tgt_str)\n\n\tdef keyword(self):\n\t\treturn 'Creating'\n\n\tdef run(self):\n\t\toutfile = self.outputs[0].abspath()\n\t\tkwargs = {}\n\t\tkwargs['mode'] = 'w'\n\t\tkwargs['compression'] = zipfile.ZIP_STORED if self.compresslevel == 0 else zipfile.ZIP_DEFLATED\n\n\t\tif sys.hexversion >= 0x3070000:\n\t\t\tkwargs['compresslevel'] = self.compresslevel\n\n\t\twith zipfile.ZipFile(outfile, **kwargs) as zf:\n\t\t\tfor src in self.inputs:\n\t\t\t\tinfile  = src.path_from(src.ctx.launch_node())\n\t\t\t\tarcfile = src.path_from(self.relative_to)\n\n\t\t\t\t# TODO: pass excluded list from TaskGen\n\t\t\t\tLogs.debug('%s: %s <- %s as %s', self.__class__.__name__, outfile, infile, arcfile)\n\n\t\t\t\tif infile.endswith('.png'):\n\t\t\t\t\tzf.write(infile, arcfile, zipfile.ZIP_STORED)\n\t\t\t\telse:\n\t\t\t\t\tzf.write(infile, arcfile)\n\n@TaskGen.feature('zip')\ndef create_zip_archive(self):\n\tcompresslevel = getattr(self, 'compresslevel', 6) # 6 is zip default\n\tif compresslevel < 0 or compresslevel > 9:\n\t\tself.bld.fatal('Invalid compress level')\n\n\tfiles = getattr(self, 'files', None)\n\tif not files:\n\t\tself.bld.fatal('No files to archive')\n\n\trelative_to = getattr(self, 'relative_to', None)\n\tif not relative_to:\n\t\tself.bld.fatal('No relative directory supplied')\n\n\tself.path.get_bld().mkdir()\n\ttarget = self.path.get_bld().make_node(self.name)\n\n\ttsk = self.create_task('ziparchive', files, target)\n\n\tsetattr(tsk, 'compresslevel', compresslevel)\n\tsetattr(tsk, 'relative_to', relative_to)\n\n\ttry:\n\t\tinst_to = self.install_path\n\t\tself.install_task = self.add_install_files(\n\t\t\tinstall_to=inst_to, install_from=target,\n\t\t\tchmod=Utils.O644, task=tsk)\n\n\texcept AttributeError:\n\t\tpass\n"
  },
  {
    "path": "scripts/xashds@.service",
    "content": "[Unit]\nDescription=%I - Xash3D FWGS Dedicated Server\nAfter=network.target\n\n[Service]\n# replace xash by your user\n# !!! do not run xash as root !!!\nUser=xash\nGroup=xash\nRestart=always\n\n# replace /opt/xash/srv by your path\nWorkingDirectory=/opt/xash/srv/%I\nEnvironmentFile=-/opt/xash/srv/%I/env\nExecStart=/opt/xash/bin/xash $EXTRA_OPTS\n\n# engine should call sd_notify once half a second\nWatchdogSec=5s\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "uncrustify.cfg",
    "content": "# NOTE: Uncrustify 0.80+ is required\n\nalign_enum_equ_span = 5\nalign_func_params_span = 1\nalign_func_params = true\nalign_keep_extra_space = false\nalign_nl_cont = 1\nalign_number_right = true\nalign_on_tabstop = false\nalign_pp_define_span = 1\nalign_right_cmt_same_level = true\nalign_right_cmt_span = 2\nalign_var_class_span = 3\nalign_var_class_thresh = 5\nalign_var_def_amp_style = 2\nalign_var_def_colon = true\nalign_var_def_span = 3\nalign_var_def_star_style = 1\nalign_var_def_thresh = 5\nalign_var_struct_span = 3\nalign_var_struct_thresh = 5\nalign_with_tabs = false\ncmt_indent_multi = false\nindent_align_string = true\nindent_with_tabs = 2\nindent_class = true\nnewlines = lf\nnl_brace_catch = force\nnl_brace_else = force\nnl_brace_while = force\nnl_catch_brace = force\nnl_do_brace = force\nnl_else_brace = force\nnl_elseif_brace = force\nnl_enum_brace = force\nnl_finally_brace = force\nnl_for_brace = force\nnl_if_brace = force\nnl_start_of_file = remove\nnl_struct_brace = force\nnl_switch_brace = force\nnl_try_brace = force\nnl_union_brace = force\nnl_while_brace = force\nnl_func_decl_start = remove\nnl_func_decl_end = remove\nnl_func_decl_args_multi_line = false\nnl_func_decl_args = remove\nnl_func_decl_empty = remove\nnl_func_def_start = remove\nnl_func_def_paren = remove\nnl_func_def_end = remove\nnl_func_def_args_multi_line = false\nnl_func_def_args = remove\nnl_func_def_empty = remove\nnl_func_paren = remove\nnl_fdef_brace = force\nnl_after_func_body = 2\nnl_after_semicolon = true\npos_arith = lead\npos_assign = lead\npos_bool = lead\npos_comma = trail\npos_compare = lead\npos_conditional = lead\npos_shift = lead\nsp_after_comma = force\nsp_after_ptr_star = remove\nsp_after_ptr_star_qualifier = remove\nsp_after_ptr_star_func = remove\nsp_after_semi_for_empty = force\nsp_after_semi_for = force\nsp_after_tparen_close = remove\nsp_arith = force\nsp_assign = force\nsp_attribute_paren = remove\nsp_before_ptr_star = force\nsp_before_semi_for_empty = force\nsp_before_semi_for = remove\nsp_before_sparen = remove\nsp_before_tr_cmt = force\nsp_between_semi_for_empty = remove\nsp_bool = force\nsp_brace_brace = remove\nsp_catch_paren = remove\nsp_cmt_cpp_start = add\nsp_compare = force\nsp_cond_colon = force\nsp_cond_question = force\nsp_cparen_oparen = remove\nsp_cpp_cast_paren = remove\nsp_decltype_paren = remove\nsp_defined_paren = remove\nsp_endif_cmt = add\nsp_endif_cmt = force\nsp_enum_assign = force\nsp_func_call_paren_empty = remove\nsp_func_call_paren = remove\nsp_func_class_paren_empty = remove\nsp_func_class_paren = remove\nsp_func_def_paren_empty = remove\nsp_func_def_paren = remove\nsp_func_proto_paren_empty = remove\nsp_func_proto_paren = remove\nsp_inside_for = force\nsp_inside_fparen = force\nsp_inside_paren_cast = remove\nsp_inside_paren = force\nsp_inside_rparen = force\nsp_inside_sparen = force\nsp_inside_tparen = remove\nsp_num_before_tr_cmt = 1\nsp_paren_paren = remove\nsp_pp_concat = force\nsp_pp_stringify = remove\nsp_return_paren = remove\nsp_sizeof_paren = remove\nsp_sparen_paren = remove\nsp_square_fparen = remove\nsp_throw_paren = remove\nsp_type_func = remove\n"
  },
  {
    "path": "utils/mdldec/mdldec.c",
    "content": "/*\nmdldec.c - Half-Life Studio Model Decompiler\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"build.h\"\n#if !XASH_WIN32\n\t#include <unistd.h>\n#else\n\t#include \"getopt.h\"\n#endif\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"crtlib.h\"\n#include \"studio.h\"\n#include \"qc.h\"\n#include \"smd.h\"\n#include \"texture.h\"\n#include \"utils.h\"\n#include \"version.h\"\n#include \"settings.h\"\n#include \"mdldec.h\"\n\nchar\t\t  destdir[MAX_SYSPATH];\nchar\t\t  modelfile[MAX_SYSPATH];\nstudiohdr_t\t *model_hdr;\nstudiohdr_t\t *texture_hdr;\nstudiohdr_t\t**anim_hdr;\nint\t\t  globalsettings;\n\n/*\n============\nIsValidName\n============\n*/\nstatic qboolean IsValidName( char *name )\n{\n\tif( !( isalpha( *name ) || isdigit( *name )))\n\t\treturn false;\n\n\twhile( *( ++name ))\n\t{\n\t\tif( isalpha( *name ) || isdigit( *name )\n\t\t    || *name == '.' || *name == '-' || *name == '_'\n\t\t    || *name == ' ' || *name == '(' || *name == ')'\n\t\t    || *name == '[' || *name == ']')\n\t\t\tcontinue;\n\t\t// Found control character Ctrl+Shift+A(SOH|^A|0x1) in the end of name in some models.\n\t\telse if( name[1] == '\\0' )\n\t\t{\n\t\t\t*name = '\\0';\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nTextureNameFix\n============\n*/\nstatic void TextureNameFix( void )\n{\n\tint\t\t\t i, j, len, counter, protected = 0;\n\tqboolean\t\t hasduplicates = false;\n\tmstudiotexture_t\t*texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ), *texture1;\n\n\tfor( i = 0; i < texture_hdr->numtextures; ++i, ++texture )\n\t{\n\t\tExtractFileName( texture->name, sizeof( texture->name ));\n\n\t\tif( !( ( ( texture->width == 1 || texture->height == 1 )\n\t\t    && texture->name[0] == '#' ) || IsValidName( texture->name )))\n\t\t\tQ_snprintf( texture->name, sizeof( texture->name ), \"MDLDEC_Texture%i.bmp\", ++protected );\n\t}\n\n\ttexture -= i;\n\n\tfor( i = 0; i < texture_hdr->numtextures; ++i, ++texture )\n\t{\n\t\tcounter = 0;\n\n\t\ttexture1 = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex );\n\n\t\tfor( j = 0; j < texture_hdr->numtextures; ++j, ++texture1 )\n\t\t{\n\t\t\tif( j != i && !Q_strncmp( texture1->name, texture->name, sizeof( texture1->name)))\n\t\t\t{\n\t\t\t\tlen = Q_snprintf( texture1->name, sizeof( texture1->name ), \"%s_%i.bmp\", texture1->name, ++counter );\n\n\t\t\t\tif( len == -1 )\n\t\t\t\t\tQ_snprintf( texture1->name, sizeof( texture1->name ), \"MDLDEC_Texture%i_%i.bmp\", j, counter );\n\t\t\t}\n\t\t}\n\n\t\tif( counter > 0 )\n\t\t{\n\t\t\tLogPrintf( \"WARNING: Texture name \\\"%s\\\" is repeated %i times.\", texture->name, counter );\n\n\t\t\thasduplicates = true;\n\t\t}\n\t}\n\n\tif( protected )\n\t\tLogPrintf( \"WARNING: Gived name to %i protected texture%s.\", protected, protected > 1 ? \"s\" : \"\" );\n\n\tif( hasduplicates )\n\t\tLogPutS( \"WARNING: Added numeric suffix to repeated texture name(s).\" );\n}\n\n/*\n============\nBodypartNameFix\n============\n*/\nstatic void BodypartNameFix( void )\n{\n\tint\t\t\t i, j, k, l, len, counter, protected = 0, protected_models = 0;\n\tqboolean\t\t hasduplicates = false;\n\tmstudiobodyparts_t\t*bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex );\n\tmstudiobodyparts_t\t*bodypart1;\n\tmstudiomodel_t\t\t*model, *model1;\n\n\tfor( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart )\n\t{\n\t\tExtractFileName( bodypart->name, sizeof( bodypart->name ));\n\t\tif( !IsValidName( bodypart->name ))\n\t\t\tQ_snprintf( bodypart->name, sizeof( bodypart->name ), \"MDLDEC_Bodypart%i\", ++protected );\n\n\t\tmodel = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex );\n\n\t\tfor( j = 0; j < bodypart->nummodels; ++j, ++model )\n\t\t{\n\t\t\tExtractFileName( model->name, sizeof( model->name ));\n\t\t\tCOM_StripExtension( model->name );\n\t\t\tif( !IsValidName( model->name ))\n\t\t\t\tQ_snprintf( model->name, sizeof( model->name ), \"MDLDEC_Model%i\", ++protected_models );\n\t\t}\n\t}\n\n\tbodypart -= i;\n\n\tfor( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart )\n\t{\n\t\tmodel = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex );\n\n\t\tfor( j = 0; j < bodypart->nummodels; ++j, ++model )\n\t\t{\n\t\t\tcounter = 0;\n\n\t\t\tbodypart1 = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex );\n\n\t\t\tfor( k = 0; k < model_hdr->numbodyparts; ++k, ++bodypart1 )\n\t\t\t{\n\t\t\t\tmodel1 = (mstudiomodel_t *)( (byte *)model_hdr + bodypart1->modelindex );\n\n\t\t\t\tfor( l = 0; l < bodypart1->nummodels; ++l, ++model1 )\n\t\t\t\t{\n\t\t\t\t\tif( !( i==k && j==l ) && !Q_strncmp( model1->name, model->name, sizeof( model1->name )))\n\t\t\t\t\t{\n\t\t\t\t\t\tlen = Q_snprintf( model1->name, sizeof( model1->name ), \"%s_%i\", model1->name, ++counter );\n\n\t\t\t\t\t\tif( len == -1 )\n\t\t\t\t\t\t\tQ_snprintf( model1->name, sizeof( model1->name ), \"MDLDEC_Model%i_%i\", l, counter );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif( counter > 0 )\n\t\t\t\t{\n\t\t\t\t\tLogPrintf( \"WARNING: Model name \\\"%s\\\" is repeated %i times.\", model->name, counter );\n\n\t\t\t\t\thasduplicates = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif( protected )\n\t\tLogPrintf( \"WARNING: Gived name to %i protected bodypart%s.\", protected, protected > 1 ? \"s\" : \"\" );\n\n\tif( protected_models )\n\t\tLogPrintf( \"WARNING: Gived name to %i protected model%s.\", protected_models, protected_models > 1 ? \"s\" : \"\" );\n\n\tif( hasduplicates )\n\t\tLogPutS( \"WARNING: Added numeric suffix to repeated bodypart name(s).\" );\n}\n\n/*\n============\nSequenceNameFix\n============\n*/\nstatic void SequenceNameFix( void )\n{\n\tint\t\t\t i, j, len, counter, protected = 0;\n\tqboolean\t\t hasduplicates = false;\n\tmstudioseqdesc_t\t*seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ), *seqdesc1;\n\n\tfor( i = 0; i < model_hdr->numseq; ++i, ++seqdesc )\n\t{\n\t\tExtractFileName( seqdesc->label, sizeof( seqdesc->label ));\n\t\tCOM_StripExtension( seqdesc->label );\n\t\tif( !IsValidName( seqdesc->label ))\n\t\t\tQ_snprintf( seqdesc->label, sizeof( seqdesc->label ), \"MDLDEC_Sequence%i\", ++protected );\n\t}\n\n\tseqdesc -= i;\n\n\tfor( i = 0; i < model_hdr->numseq; ++i, ++seqdesc )\n\t{\n\t\tcounter = 0;\n\n\t\tseqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex );\n\n\t\tfor( j = 0; j < model_hdr->numseq; ++j, ++seqdesc1 )\n\t\t{\n\t\t\tif( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label )))\n\t\t\t{\n\t\t\t\tlen = Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), \"%s_%i\", seqdesc1->label, ++counter );\n\n\t\t\t\tif( len == -1 )\n\t\t\t\t\tQ_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), \"MDLDEC_Sequence%i_%i\", j, counter );\n\t\t\t}\n\t\t}\n\n\t\tif( counter > 0 )\n\t\t{\n\t\t\tLogPrintf( \"WARNING: Sequence name \\\"%s\\\" is repeated %i times.\", seqdesc->label, counter );\n\n\t\t\thasduplicates = true;\n\t\t}\n\t}\n\n\tif( protected )\n\t\tLogPrintf( \"WARNING: Gived name to %i protected sequence%s.\", protected, protected > 1 ? \"s\" : \"\" );\n\n\tif( hasduplicates )\n\t\tLogPutS( \"WARNING: Added numeric suffix to repeated sequence name(s).\" );\n}\n\n/*\n============\nBoneNameFix\n============\n*/\nstatic void BoneNameFix( void )\n{\n\tint\t\t i, protected = 0;\n\tmstudiobone_t\t*bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++bone )\n\t{\n\t\tbone->name[sizeof( bone->name ) - 1] = '\\0';\n\n\t\tif( !IsValidName( bone->name ) )\n\t\t\tQ_snprintf( bone->name, sizeof( bone->name ), \"MDLDEC_Bone%i\", ++protected );\n\t}\n\n\tif( protected )\n\t\tLogPrintf( \"WARNING: Gived name to %i protected bone%s.\", protected, protected > 1 ? \"s\" : \"\" );\n}\n\n/*\n============\nLoadMDL\n============\n*/\nstatic qboolean LoadMDL( const char *modelname )\n{\n\tint\t\t i;\n\tsize_t\t\t len;\n\toff_t\t\t filesize;\n\tchar\t\t texturename[MAX_SYSPATH];\n\tchar\t\t seqgroupname[MAX_SYSPATH];\n\tconst char\t*ext;\n\tconst char\t id_mdlhdr[] = {'I', 'D', 'S', 'T'};\n\tconst char\t id_seqhdr[] = {'I', 'D', 'S', 'Q'};\n\n\tLogPrintf( \"MDL: %s.\", modelname );\n\n\tlen = Q_strlen( modelname );\n\n\tif( len > MAX_SYSPATH - 3 )\n\t{\n\t\tLogPutS( \"ERROR: Source path is too long.\");\n\t\treturn false;\n\t}\n\n\text = COM_FileExtension( modelname );\n\n\tif( !ext )\n\t{\n\t\tLogPutS( \"ERROR: Source file does not have extension.\" );\n\t\treturn false;\n\t}\n\n\tif( Q_stricmp( ext, \"mdl\" ) )\n\t{\n\t\tLogPutS( \"ERROR: Only .mdl-files is supported.\" );\n\t\treturn false;\n\t}\n\n\tmodel_hdr = (studiohdr_t *)LoadFile( modelname, &filesize );\n\n\tif( !model_hdr )\n\t{\n\t\tLogPrintf( \"ERROR: Can't open %s.\", modelname );\n\t\treturn false;\n\t}\n\n\tif( filesize < sizeof( studiohdr_t ))\n\t{\n\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", modelname );\n\t\treturn false;\n\t}\n\n\tif( filesize != model_hdr->length )\n\t{\n\t\t// Some bad studio model compiler don't write file length\n\t\tif( globalsettings & SETTINGS_NOVALIDATION )\n\t\t\tLogPrintf( \"WARNING: Wrong file size! File %s may be corrupted!\", modelname );\n\t\telse\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", modelname );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif( memcmp( &model_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) )\n\t{\n\t\tif( !memcmp( &model_hdr->ident, id_seqhdr, sizeof( id_seqhdr ) ) )\n\t\t\tLogPrintf( \"ERROR: %s is not a main HL model file.\", modelname );\n\t\telse\n\t\t\tLogPrintf( \"ERROR: %s is not a valid HL model file.\", modelname );\n\n\t\treturn false;\n\t}\n\n\tif( model_hdr->version != STUDIO_VERSION )\n\t{\n\t\tLogPrintf( \"ERROR: %s has unknown Studio MDL format version %d.\", modelname, model_hdr->version );\n\t\treturn false;\n\t}\n\n\tif( !model_hdr->numbodyparts )\n\t{\n\t\tLogPrintf( \"ERROR: %s is not a main HL model file.\", modelname );\n\t\treturn false;\n\t}\n\n\tif( destdir[0] != '\\0' )\n\t{\n\t\tif( !MakeFullPath( destdir ))\n\t\t\treturn false;\n\t}\n\telse\n\t\tCOM_ExtractFilePath( modelname, destdir );\n\n\tif( destdir[0] != '\\0' )\n\t\tCOM_PathSlashFix( destdir );\n\n\tlen -= ( sizeof( \".mdl\" ) - 1 ); // path length without extension\n\n\tif( !model_hdr->numtextures )\n\t{\n\t\tQ_strncpy( texturename, modelname, sizeof( texturename ));\n\t\tQ_strncpy( &texturename[len], \"t.mdl\", sizeof( texturename ) - len );\n\n\t\ttexture_hdr = (studiohdr_t *)LoadFile( texturename, &filesize );\n\n\t\tif( !texture_hdr )\n\t\t{\n#if !XASH_WIN32\n\t\t\t// dirty hack for casesensetive filesystems\n\t\t\ttexturename[len] = 'T';\n\n\t\t\ttexture_hdr = (studiohdr_t *)LoadFile( texturename, &filesize );\n\n\t\t\tif( !texture_hdr )\n#endif\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Can't open external textures file %s.\", texturename );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif( filesize < sizeof( studiohdr_t ) )\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", texturename );\n\t\t\treturn false;\n\t\t}\n\n\t\tif( filesize != texture_hdr->length )\n\t\t{\n\t\t\t// Some bad studio model compiler don't write file length\n\t\t\tif( globalsettings & SETTINGS_NOVALIDATION )\n\t\t\t\tLogPrintf( \"WARNING: Wrong file size! File %s may be corrupted!\", texturename );\n\t\t\telse\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", texturename );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif( memcmp( &texture_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) )\n\t\t    || !texture_hdr->numtextures )\n\t\t{\n\t\t\tLogPrintf( \"ERROR: %s is not a valid external textures file.\", texturename );\n\t\t\treturn false;\n\t\t}\n\t}\n\telse\n\t\ttexture_hdr = model_hdr;\n\n\tanim_hdr = malloc( sizeof( studiohdr_t* ) * model_hdr->numseqgroups );\n\n\tif( !anim_hdr )\n\t{\n\t\tLogPutS( \"ERROR: Couldn't allocate memory for sequences.\" );\n\t\treturn false;\n\t}\n\n\tanim_hdr[0] = model_hdr;\n\n\tif( model_hdr->numseqgroups > 1 )\n\t{\n\t\tQ_strncpy( seqgroupname, modelname, sizeof( seqgroupname ));\n\n\t\tfor( i = 1; i < model_hdr->numseqgroups; i++ )\n\t\t{\n\t\t\tQ_snprintf( &seqgroupname[len], sizeof( seqgroupname ) - len, \"%02d.mdl\", i );\n\n\t\t\tanim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname, &filesize );\n\n\t\t\tif( !anim_hdr[i] )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Can't open sequence file %s.\", seqgroupname );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif( filesize < sizeof( studiohdr_t ))\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", seqgroupname );\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif( filesize != anim_hdr[i]->length )\n\t\t\t{\n\t\t\t\t// Some bad studio model compiler don't write file length\n\t\t\t\tif( globalsettings & SETTINGS_NOVALIDATION )\n\t\t\t\t\tLogPrintf( \"WARNING: Wrong file size! File %s may be corrupted!\", seqgroupname );\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tLogPrintf( \"ERROR: Wrong file size! File %s may be corrupted!\", seqgroupname );\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif( memcmp( &anim_hdr[i]->ident, id_seqhdr, sizeof( id_seqhdr ) ) )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: %s is not a valid sequence file.\", seqgroupname );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\tCOM_FileBase( modelname, modelfile, sizeof( modelfile ));\n\n\t// Some validation checks was found in mdldec-golang by Psycrow101\n\tif( model_hdr->numhitboxes > model_hdr->numbones * ( MAXSTUDIOSRCBONES / MAXSTUDIOBONES ))\n\t{\n\t\tLogPrintf( \"WARNING: Invalid hitboxes number %d.\", model_hdr->numhitboxes );\n\t\tmodel_hdr->numhitboxes = 0;\n\t}\n\telse if( model_hdr->hitboxindex + model_hdr->numhitboxes * ( sizeof( mstudiobbox_t ) + sizeof( mstudiohitboxset_t )) > model_hdr->length )\n\t{\n\t\tLogPrintf( \"WARNING: Invalid hitboxes offset %d.\", model_hdr->hitboxindex );\n\t\tmodel_hdr->numhitboxes = 0;\n\t}\n\n\tTextureNameFix();\n\n\tBodypartNameFix();\n\n\tSequenceNameFix();\n\n\tBoneNameFix();\n\n\treturn true;\n}\n\n/*\n============\nShowVersion\n============\n*/\nstatic void ShowVersion( void )\n{\n\tLogPutS( \"\\nHalf-Life Studio Model Decompiler \" APP_VERSION );\n\tLogPutS( \"Copyright Flying With Gauss 2020-2025 (c) \" );\n\tLogPutS( \"--------------------------------------------------\" );\n}\n\n/*\n============\nShowHelp\n============\n*/\nstatic void ShowHelp( const char *app_name )\n{\n\tLogPrintf( \"usage: %s [-ahlmtuVv] <source_file>\", app_name );\n\tLogPrintf( \"       %s [-ahlmtuVv] <source_file> <target_directory>\", app_name );\n\tLogPutS( \"\\nnote:\" );\n\tLogPutS( \"\\tby default this decompiler aimed to support extended MDL10 format from XashXT/PrimeXT/Paranoia2.\" );\n\tLogPutS( \"\\tif you use an old GoldSource studio model compiler you may be need to edit .qc-file after decompilation.\" );\n\tLogPutS( \"\\noptions:\" );\n\tLogPutS( \"\\t-a\\tplace files with animations to separate directory.\" );\n\tLogPutS( \"\\t-l\\tdo not output logs.\" );\n\tLogPutS( \"\\t-m\\tuse GoldSource-compatible motion types.\" );\n\tLogPutS( \"\\t-t\\tplace texture files to separate directory.\" );\n\tLogPutS( \"\\t-u\\tenable UV coords shifting for DoomMusic's and Sven-Coop's studio model compilers.\" );\n\tLogPutS( \"\\t-V\\tignore some validation checks for broken models.\" );\n\tLogPutS( \"\\t-v\\tshow version.\" );\n\tLogPutS( \"\\t-h\\tthis message.\" );\n}\n\nint main( int argc, char *argv[] )\n{\n\tint opt, ret = 0 ;\n\n\twhile( ( opt = getopt( argc, argv, \"ahlmtuVv\" )) != -1 )\n\t{\n\t\tswitch( opt )\n\t\t{\n\t\tcase 'a': globalsettings |= SETTINGS_SEPARATEANIMSFOLDER; break;\n\t\tcase 't': globalsettings |= SETTINGS_SEPARATETEXTURESFOLDER; break;\n\t\tcase 'l': globalsettings |= SETTINGS_NOLOGS; break;\n\t\tcase 'm': globalsettings |= SETTINGS_LEGACYMOTION; break;\n\t\tcase 'u': globalsettings |= SETTINGS_UVSHIFTING; break;\n\t\tcase 'V': globalsettings |= SETTINGS_NOVALIDATION; break;\n\t\tcase 'h':\n\t\tcase '?':\n\t\t\tglobalsettings = 0;\n\t\t\tShowVersion();\n\t\t\tShowHelp( argv[0] );\n\t\t\tret = 2;\n\t\t\tgoto end;\n\t\tcase 'v':\n\t\t\tglobalsettings = 0;\n\t\t\tShowVersion();\n\t\t\tret = 2;\n\t\t\tgoto end;\n\t\t}\n\t}\n\n\tShowVersion();\n\n\targc -= (optind - 1);\n\n\tif( argc == 1 )\n\t{\n\t\tShowHelp( argv[0] );\n\t\tret = 2;\n\t\tgoto end;\n\t}\n\telse if( argc == 3 )\n\t{\n\t\tif( Q_strlen( argv[optind + 1] ) > MAX_SYSPATH - 2 )\n\t\t{\n\t\t\tLogPutS( \"ERROR: Destination path is too long.\");\n\t\t\tret = 1;\n\t\t\tgoto end;\n\t\t}\n\n\t\tQ_strncpy( destdir, argv[optind + 1], sizeof( destdir ));\n\t}\n\n\tif( !(LoadActivityList( argv[0] ) && LoadMDL( argv[optind] )))\n\t{\n\t\tret = 1;\n\t\tgoto end;\n\t}\n\n\tWriteQCScript();\n\tWriteSMD();\n\tWriteTextures();\n\n\tLogPutS( \"Done.\" );\n\nend:\n\tLogPutS( \"--------------------------------------------------\" );\n\n\treturn ret;\n}\n\n"
  },
  {
    "path": "utils/mdldec/mdldec.h",
    "content": "/*\nmdldec.h - Half-Life Studio Model Decompiler\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef MDLDEC_H\n#define MDLDEC_H\n\nextern char\t\t  destdir[MAX_SYSPATH];\nextern char\t\t  modelfile[MAX_SYSPATH];\nextern studiohdr_t\t *model_hdr;\nextern studiohdr_t\t *texture_hdr;\nextern studiohdr_t\t**anim_hdr;\n\n#endif // MDLDEC_H\n\n"
  },
  {
    "path": "utils/mdldec/qc.c",
    "content": "/*\nqc.c - Quake C script writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdlib.h>\n#include <string.h>\n#include \"xash3d_mathlib.h\"\n#include \"eiface.h\"\n#include \"studio.h\"\n#include \"crtlib.h\"\n#include \"version.h\"\n#include \"mdldec.h\"\n#include \"utils.h\"\n#include \"smd.h\"\n#include \"texture.h\"\n#include \"settings.h\"\n#include \"qc.h\"\n\nstatic char\t**activity_names;\nstatic int\t  activity_count;\n\n/*\n============\nLoadActivityList\n============\n*/\nqboolean LoadActivityList( const char *appname )\n{\n\tFILE\t\t*fp;\n\tconst char\t*p;\n\tchar\t\t path[MAX_SYSPATH];\n\tchar\t\t buf[256];\n\n\tfp = fopen( ACTIVITIES_FILE, \"r\" );\n\n\tif( !fp )\n\t{\n\t\tp = getenv( \"MDLDEC_ACT_PATH\" );\n\n\t\tif( !p )\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Couldn't find file \" ACTIVITIES_FILE \".\\n\" \\\n\t\t\t    \"Place \" ACTIVITIES_FILE \" beside %s or set MDLDEC_ACT_PATH environment variable.\", appname );\n\t\t\treturn false;\n\t\t}\n\n\t\tQ_strncpy( path, p, MAX_SYSPATH - 1 );\n\n\t\tCOM_PathSlashFix( path );\n\n\t\tQ_strncat( path, ACTIVITIES_FILE, MAX_SYSPATH );\n\n\t\tfp = fopen( path, \"r\" );\n\n\t\tif( !fp )\n\t\t{\n\t\t\tLogPutS( \"ERROR: Couldn't open file \" ACTIVITIES_FILE \".\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\twhile( fgets( buf, sizeof( buf ), fp ) )\n\t{\n\t\tactivity_names = realloc( activity_names, sizeof( char* ) * ++activity_count );\n\n\t\tif( !activity_names )\n\t\t{\n\t\t\tLogPutS( \"ERROR: Couldn't allocate memory for activities strings.\" );\n\t\t\treturn false;\n\t\t}\n\n\t\tCOM_RemoveLineFeed( buf, sizeof( buf ));\n\n\t\tactivity_names[activity_count - 1] = strdup( buf );\n\n\t\tif( !activity_names[activity_count - 1] )\n\t\t{\n\t\t\tLogPutS( \"ERROR: Couldn't allocate memory for activities strings.\" );\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfclose( fp );\n\n\treturn true;\n}\n\n/*\n============\nFindActivityName\n============\n*/\nstatic const char *FindActivityName( int type )\n{\n\tif( type >= 0 && type < activity_count )\n\t\treturn activity_names[type];\n\n\treturn NULL;\n}\n\n/*\n============\nGetMotionTypeString\n============\n*/\nstatic void GetMotionTypeString( int type, char *str, size_t size, qboolean is_composite )\n{\n\tconst char\t*p = NULL;\n\n\tstr[0] = '\\0';\n\n\tif( is_composite )\n\t{\n\t\tif( type & STUDIO_X )\n\t\t\tQ_strncat( str, \" X\", size );\n\n\t\tif( type & STUDIO_Y )\n\t\t\tQ_strncat( str, \" Y\", size );\n\n\t\tif( type & STUDIO_Z )\n\t\t\tQ_strncat( str, \" Z\", size );\n\n\t\tif( type & STUDIO_XR )\n\t\t\tQ_strncat( str, \" XR\", size );\n\n\t\tif( type & STUDIO_YR )\n\t\t\tQ_strncat( str, \" YR\", size );\n\n\t\tif( type & STUDIO_ZR )\n\t\t\tQ_strncat( str, \" ZR\", size );\n\n\t\tif( type & STUDIO_LX )\n\t\t\tQ_strncat( str, \" LX\", size );\n\n\t\tif( type & STUDIO_LY )\n\t\t\tQ_strncat( str, \" LY\", size );\n\n\t\tif( type & STUDIO_LZ )\n\t\t\tQ_strncat( str, \" LZ\", size );\n\n\t\tif( globalsettings & SETTINGS_LEGACYMOTION )\n\t\t{\n\t\t\tif( type & STUDIO_LXR )\n\t\t\t\tQ_strncat( str, \" AX\", size );\n\n\t\t\tif( type & STUDIO_LYR )\n\t\t\t\tQ_strncat( str, \" AY\", size );\n\n\t\t\tif( type & STUDIO_LZR )\n\t\t\t\tQ_strncat( str, \" AZ\", size );\n\n\t\t\tif( type & STUDIO_LINEAR )\n\t\t\t\tQ_strncat( str, \" AXR\", size );\n\n\t\t\tif( type & STUDIO_QUADRATIC_MOTION )\n\t\t\t\tQ_strncat( str, \" AYR\", size );\n\n\t\t\tif( type & STUDIO_RESERVED )\n\t\t\t\tQ_strncat( str, \" AZR\", size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif( type & STUDIO_LXR )\n\t\t\t\tQ_strncat( str, \" LXR\", size );\n\n\t\t\tif( type & STUDIO_LYR )\n\t\t\t\tQ_strncat( str, \" LYR\", size );\n\n\t\t\tif( type & STUDIO_LZR )\n\t\t\t\tQ_strncat( str, \" LZR\", size );\n\n\t\t\tif( type & STUDIO_LINEAR )\n\t\t\t\tQ_strncat( str, \" LM\", size );\n\n\t\t\tif( type & STUDIO_QUADRATIC_MOTION )\n\t\t\t\tQ_strncat( str, \" LQ\", size );\n\t\t}\n\t\treturn;\n\t}\n\n\ttype &= STUDIO_TYPES;\n\n\tswitch( type )\n\t{\n\tcase STUDIO_X:    p = \"X\";    break;\n\tcase STUDIO_Y:    p = \"Y\";    break;\n\tcase STUDIO_Z:    p = \"Z\";    break;\n\tcase STUDIO_XR:   p = \"XR\";   break;\n\tcase STUDIO_YR:   p = \"YR\";   break;\n\tcase STUDIO_ZR:   p = \"ZR\";   break;\n\tcase STUDIO_LX:   p = \"LX\";   break;\n\tcase STUDIO_LY:   p = \"LY\";   break;\n\tcase STUDIO_LZ:   p = \"LZ\";   break;\n\tdefault: break;\n\t}\n\n\tif( globalsettings & SETTINGS_LEGACYMOTION )\n\t{\n\t\tswitch( type )\n\t\t{\n\t\tcase STUDIO_LXR:  p = \"AX\";  break;\n\t\tcase STUDIO_LYR:  p = \"AY\";  break;\n\t\tcase STUDIO_LZR:  p = \"AZ\";  break;\n\t\tcase STUDIO_LINEAR: p = \"AXR\"; break;\n\t\tcase STUDIO_QUADRATIC_MOTION: p = \"AYR\"; break;\n\t\tcase STUDIO_RESERVED: p = \"AZR\"; break;\n\t\tdefault: break;\n\t\t}\n\t}\n\telse\n\t{\n\t\tswitch( type )\n\t\t{\n\t\tcase STUDIO_LXR:  p = \"LXR\";  break;\n\t\tcase STUDIO_LYR:  p = \"LYR\";  break;\n\t\tcase STUDIO_LZR:  p = \"LZR\";  break;\n\t\tcase STUDIO_LINEAR: p = \"LM\"; break;\n\t\tcase STUDIO_QUADRATIC_MOTION: p = \"LQ\"; break;\n\t\tdefault: break;\n\t\t}\n\t}\n\n\tif( p )\n\t\tQ_strncpy( str, p, size );\n}\n\n/*\n============\nWriteTextureRenderMode\n============\n*/\nstatic void WriteTextureRenderMode( FILE *fp )\n{\n\tint\t\t  i;\n\tmstudiotexture_t *texture;\n\tlong\t\t  pos = ftell( fp );\n\n\tfor( i = 0; i < texture_hdr->numtextures; i++ )\n\t{\n\t\ttexture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i;\n\n\t\tif( texture->flags & STUDIO_NF_FLATSHADE )\n\t\t\tfprintf( fp,\"$texrendermode \\\"%s\\\" \\\"flatshade\\\" \\n\", texture->name ); // sven-coop extension\n\n\t\tif( texture->flags & STUDIO_NF_CHROME )\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"chrome\\\" \\n\", texture->name ); // sven-coop extension/may be added in HLMV\n\n\t\tif( texture->flags & STUDIO_NF_FULLBRIGHT )\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"fullbright\\\" \\n\", texture->name ); // sven-coop extension/xash3d extension\n\n\t\tif( texture->flags & STUDIO_NF_NOMIPS )\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"nomips\\\" \\n\", texture->name ); // sven-coop extension\n\n\t\tif( texture->flags & STUDIO_NF_SMOOTH )\n\t\t{\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"alpha\\\" \\n\", texture->name ); // sven-coop extension\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"smooth\\\" \\n\", texture->name ); // xash3d extension\n\t\t}\n\n\t\tif( texture->flags & STUDIO_NF_ADDITIVE )\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"additive\\\" \\n\", texture->name );\n\n\t\tif( texture->flags & STUDIO_NF_MASKED )\n\t\t{\n\t\t\tif( texture->flags & STUDIO_NF_ALPHASOLID )\n\t\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"masked_solid\\\" \\n\", texture->name ); // xash3d extension\n\t\t\telse\n\t\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"masked\\\" \\n\", texture->name );\n\t\t}\n\n\t\tif( texture->flags & STUDIO_NF_TWOSIDE )\n\t\t\tfprintf( fp, \"$texrendermode \\\"%s\\\" \\\"twoside\\\" \\n\", texture->name );\n\t}\n\n\tif( ftell( fp ) != pos )\n\t\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nWriteSkinFamilyInfo\n============\n*/\nstatic void WriteSkinFamilyInfo( FILE *fp )\n{\n\tint\t\t\t i, j, k;\n\tshort\t\t\t*skinref, *index;\n\tmstudiotexture_t\t*texture;\n\n\tif( texture_hdr->numskinfamilies < 2 )\n\t\treturn;\n\n\tfprintf( fp, \"// %i skin families\\n\", texture_hdr->numskinfamilies );\n\n\tfputs( \"$texturegroup \\\"skinfamilies\\\"\\n{\\n\", fp );\n\n\tskinref = (short *)( (byte *)texture_hdr + texture_hdr->skinindex );\n\ttexture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex );\n\n\tfor( i = 0; i < texture_hdr->numskinfamilies; ++i )\n\t{\n\t\tfputs( \"\\t{\\n\", fp );\n\n\t\tindex = skinref + i * texture_hdr->numskinref;\n\n\t\tfor( j = 0; j < texture_hdr->numskinref; ++j, ++index )\n\t\t{\n\t\t\tfor( k = 0; k < texture_hdr->numskinfamilies; ++k )\n\t\t\t{\n\t\t\t\tif( *index == *( skinref + k * texture_hdr->numskinref + j ) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tfprintf( fp, \"\\t\\t\\\"%s\\\"\\n\", texture[*index].name );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfputs( \"\\t}\\n\", fp );\n\t}\n\n\tfputs( \"}\\n\\n\", fp );\n}\n\n/*\n============\nWriteAttachmentInfo\n============\n*/\nstatic void WriteAttachmentInfo( FILE *fp )\n{\n\tint\t\t\t i;\n\tmstudioattachment_t\t*attachment;\n\tmstudiobone_t\t\t*bone;\n\n\tif( !model_hdr->numattachments )\n\t\treturn;\n\n\tattachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex );\n\tbone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfprintf( fp, \"// %i attachment%s\\n\", model_hdr->numattachments, model_hdr->numattachments > 1 ? \"s\" : \"\" );\n\n\tfor( i = 0; i < model_hdr->numattachments; ++i, ++attachment )\n\t\tfprintf( fp, \"$attachment %i \\\"%s\\\" %f %f %f\\n\", i, bone[attachment->bone].name, attachment->org[0], attachment->org[1], attachment->org[2] );\n\n\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nWriteBodyGroupInfo\n============\n*/\nstatic void WriteBodyGroupInfo( FILE *fp )\n{\n\tint\t\t\t i, j;\n\tmstudiobodyparts_t\t*bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex );\n\tmstudiomodel_t\t\t*model;\n\n\tfprintf( fp, \"// %i reference mesh%s\\n\", model_hdr->numbodyparts, model_hdr->numbodyparts > 1 ? \"es\" : \"\" );\n\n\tfor( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart )\n\t{\n\t\tmodel = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex );\n\n\t\tif( bodypart->nummodels == 1 )\n\t\t{\n\t\t\tfprintf( fp, \"$body \\\"%s\\\" \\\"%s\\\"\\n\", bodypart->name, model->name );\n\t\t\tcontinue;\n\t\t}\n\n\t\tfprintf( fp, \"$bodygroup \\\"%s\\\"\\n\", bodypart->name );\n\n\t\tfputs( \"{\\n\", fp );\n\n\t\tfor( j = 0; j < bodypart->nummodels; ++j, ++model )\n\t\t{\n\t\t\tif( !Q_strncmp( model->name, \"blank\", 5 ) )\n\t\t\t{\n\t\t\t\tfputs( \"\\tblank\\n\", fp );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfprintf( fp, \"\\tstudio \\\"%s\\\"\\n\", model->name );\n\t\t}\n\n\t\tfputs( \"}\\n\", fp );\n\t}\n\n\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nWriteControllerInfo\n============\n*/\nstatic void WriteControllerInfo( FILE *fp )\n{\n\tint\t\t\t i;\n\tmstudiobonecontroller_t\t*bonecontroller;\n\tmstudiobone_t\t\t*bone;\n\tchar\t\t\t motion_types[64];\n\n\tif( !model_hdr->numbonecontrollers )\n\t\treturn;\n\n\tbonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex );\n\tbone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfprintf( fp, \"// %i bone controller%s\\n\", model_hdr->numbonecontrollers, model_hdr->numbonecontrollers > 1 ? \"s\" : \"\" );\n\n\tfor( i = 0; i < model_hdr->numbonecontrollers; ++i, ++bonecontroller )\n\t{\n\t\tGetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, sizeof( motion_types ), false );\n\n\t\tfputs( \"$controller \", fp );\n\n\t\tif( bonecontroller->index == 4 )\n\t\t\tfputs( \"Mouth\", fp );\n\t\telse\n\t\t\tfprintf( fp, \"%i\", bonecontroller->index );\n\n\t\tfprintf( fp, \" \\\"%s\\\" %s %f %f\\n\",\n\t\t    bone[bonecontroller->bone].name, motion_types,\n\t\t    bonecontroller->start, bonecontroller->end );\n\t}\n\n\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nWriteHitBoxInfo\n============\n*/\nstatic void WriteHitBoxInfo( FILE *fp )\n{\n\tint\t\t i;\n\tmstudiobbox_t\t*hitbox;\n\tmstudiobone_t\t*bone;\n\n\tif( !model_hdr->numhitboxes )\n\t\treturn;\n\n\thitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex );\n\tbone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfprintf( fp, \"// %i hit box%s\\n\", model_hdr->numhitboxes, model_hdr->numhitboxes > 1 ? \"es\" : \"\" );\n\n\tfor( i = 0; i < model_hdr->numhitboxes; ++i, ++hitbox )\n\t\tfprintf( fp, \"$hbox %i \\\"%s\\\" %f %f %f %f %f %f\\n\",\n\t\t    hitbox->group, bone[hitbox->bone].name,\n\t\t    hitbox->bbmin[0], hitbox->bbmin[1], hitbox->bbmin[2],\n\t\t    hitbox->bbmax[0], hitbox->bbmax[1], hitbox->bbmax[2] );\n\n\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nCalcSequenceGroupSize\n============\n*/\nstatic int CalcSequenceGroupSize( void )\n{\n\tint\t\t\ti, maxsize = 0, groupsize = DEFAULT_SEQGROUPSIZE;\n\n\tfor( i = 1; i < model_hdr->numseqgroups; i++ )\n\t\tmaxsize = Q_max( anim_hdr[i]->length, maxsize );\n\n\tif( maxsize > 0 )\n\t{\n\t\tgroupsize = maxsize / 1024;\n\n\t\tif( maxsize % 1024 )\n\t\t\tgroupsize++;\n\t}\n\n\treturn groupsize;\n}\n\n/*\n============\nWriteSequenceInfo\n============\n*/\nstatic void WriteSequenceInfo( FILE *fp )\n{\n\tint\t\t\t i, j;\n\tconst char\t\t*activity;\n\tchar\t\t\t motion_types[256];\n\tmstudioevent_t\t\t*event;\n\tmstudioseqdesc_t\t*seqdesc;\n\tconst char\t\t*seq_path;\n\n\tif( model_hdr->numseqgroups > 1 )\n\t\tfprintf( fp, \"$sequencegroupsize %d\\n\\n\", CalcSequenceGroupSize( ) );\n\n\tif( model_hdr->numseq > 0 )\n\t\tfprintf( fp, \"// %i animation sequence%s\\n\", model_hdr->numseq, model_hdr->numseq > 1 ? \"s\" : \"\" );\n\telse return;\n\n\tseqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex );\n\n\tseq_path = ( globalsettings & SETTINGS_SEPARATEANIMSFOLDER ) ? SEQUENCEPATH : \"\";\n\n\tfor( i = 0; i < model_hdr->numseq; ++i, ++seqdesc )\n\t{\n\t\tfprintf( fp, \"$sequence \\\"%s\\\" {\\n\", seqdesc->label );\n\n\t\tif( seqdesc->numblends > 1 )\n\t\t{\n\t\t\tfor( j = 0; j < seqdesc->numblends; j++ )\n\t\t\t\tfprintf( fp, \"\\t\\\"%s%s_blend%02i\\\"\\n\", seq_path, seqdesc->label, j + 1 );\n\t\t}\n\t\telse fprintf( fp, \"\\t\\\"%s%s\\\"\\n\", seq_path, seqdesc->label );\n\n\t\tif( seqdesc->activity )\n\t\t{\n\t\t\tactivity = FindActivityName( seqdesc->activity );\n\n\t\t\tif( activity )\n\t\t\t{\n\t\t\t\tfprintf( fp, \"\\t%s %i\\n\", activity, seqdesc->actweight );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tLogPrintf( \"WARNING: Sequence %s has a custom activity flag (ACT_%i %i).\",\n\t\t\t\t    seqdesc->label, seqdesc->activity, seqdesc->actweight );\n\n\t\t\t\tfprintf( fp, \"\\tACT_%i %i\\n\", seqdesc->activity, seqdesc->actweight );\n\t\t\t}\n\t\t}\n\n\t\tif( seqdesc->numblends > 1 )\n\t\t{\n\t\t\tGetMotionTypeString( seqdesc->blendtype[0], motion_types, sizeof( motion_types ), false );\n\n\t\t\tfprintf( fp, \"\\tblend %s %.0f %.0f\\n\",\n\t\t\t    motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] );\n\n\t\t\tif( !seqdesc->blendtype[0] )\n\t\t\t\tLogPrintf( \"WARNING: Something wrong with blending type for sequence: %s\", seqdesc->label );\n\t\t}\n\n\t\tevent = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex );\n\n\t\tfor( j = 0; j < seqdesc->numevents; ++j, ++event )\n\t\t{\n\t\t\tfprintf( fp, \"\\t{ event %i %i\", event->event, event->frame );\n\n\t\t\tif( event->options[0] != '\\0' )\n\t\t\t\tfprintf( fp, \" \\\"%s\\\"\", event->options );\n\n\t\t\tfputs( \" }\\n\", fp );\n\t\t}\n\n\n\t\tfprintf( fp, \"\\tfps %.0f\\n\", seqdesc->fps );\n\n\t\tif( seqdesc->flags == 1 )\n\t\t\tfputs( \"\\tloop\\n\", fp );\n\n\t\tif( seqdesc->motiontype )\n\t\t{\n\t\t\tGetMotionTypeString( seqdesc->motiontype, motion_types, sizeof( motion_types ), true );\n\n\t\t\tfprintf( fp, \"\\t%s\\n\", motion_types );\n\t\t}\n\n\t\tif( seqdesc->entrynode && seqdesc->exitnode )\n\t\t{\n\t\t\tif( seqdesc->entrynode == seqdesc->exitnode )\n\t\t\t\tfprintf( fp, \"\\tnode %i\\n\", seqdesc->entrynode );\n\t\t\telse if( seqdesc->nodeflags )\n\t\t\t\tfprintf( fp, \"\\trtransition %i %i\\n\", seqdesc->entrynode, seqdesc->exitnode );\n\t\t\telse\n\t\t\t\tfprintf( fp, \"\\ttransition %i %i\\n\", seqdesc->entrynode, seqdesc->exitnode );\n\t\t}\n\n\t\tfputs( \"}\\n\", fp );\n\t}\n\n\tfputs( \"\\n\", fp );\n}\n\n/*\n============\nWriteQCScript\n============\n*/\nvoid WriteQCScript( void )\n{\n\tFILE\t*fp;\n\tchar\t filename[MAX_SYSPATH];\n\tint\t len;\n\n\tlen = Q_snprintf( filename, MAX_SYSPATH, \"%s%s.qc\", destdir, modelfile );\n\n\tif( len == -1 )\n\t{\n\t\tLogPrintf( \"ERROR: Destination path is too long. Couldn't write %s.qc.\", modelfile );\n\t\treturn;\n\t}\n\n\tfp = fopen( filename, \"w\" );\n\n\tif( !fp )\n\t{\n\t\tLogPrintf( \"ERROR: Couldn't write %s.\", filename );\n\t\treturn;\n\t}\n\n\tfputs( \"/*\\n\", fp );\n\tfputs( \"==============================================================================\\n\\n\", fp );\n\tfputs( \"QC script generated by Half-Life Studio Model Decompiler \" APP_VERSION \"\\n\", fp );\n\n\tfprintf( fp, \"Copyright Flying With Gauss %s (c) \\n\\n\", Q_timestamp( TIME_YEAR_ONLY ) );\n\tfprintf( fp, \"%s.mdl\\n\\n\", modelfile );\n\n\tfputs( \"Original internal name:\\n\", fp );\n\n\tfprintf( fp, \"\\\"%s\\\"\\n\\n\", model_hdr->name );\n\n\tfputs( \"==============================================================================\\n\", fp );\n\tfputs( \"*/\\n\\n\", fp );\n\n\tfprintf( fp, \"$modelname \\\"%s.mdl\\\"\\n\", modelfile );\n\n\tfputs( \"$cd \\\".\\\"\\n\", fp );\n\tif( globalsettings & SETTINGS_SEPARATETEXTURESFOLDER )\n\t\tfputs( \"$cdtexture \\\"./\" TEXTUREPATH \"\\\"\\n\", fp );\n\telse fputs( \"$cdtexture \\\"./\\\"\\n\", fp );\n\tfputs( \"$cliptotextures\\n\", fp );\n\tfputs( \"$scale 1.0\\n\", fp );\n\tfputs( \"\\n\", fp );\n\n\tif( model_hdr->flags & STUDIO_HAS_BONEINFO )\n\t{\n\t\tif( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS )\n\t\t\tfputs( \"$boneweights\\n\\n\", fp );\n\t}\n\n\tWriteBodyGroupInfo( fp );\n\n\tfprintf( fp, \"$flags %u\\n\\n\", model_hdr->flags &~( STUDIO_HAS_BONEINFO | STUDIO_HAS_BONEWEIGHTS ) );\n\tfprintf( fp, \"$eyeposition %f %f %f\\n\\n\", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] );\n\n\tif( !model_hdr->numtextures )\n\t\tfputs( \"$externaltextures\\n\\n\", fp );\n\n\tWriteSkinFamilyInfo( fp );\n\tWriteTextureRenderMode( fp );\n\tWriteAttachmentInfo( fp );\n\n\tfprintf( fp, \"$bbox %f %f %f\", model_hdr->min[0], model_hdr->min[1], model_hdr->min[2] );\n\tfprintf( fp, \" %f %f %f\\n\\n\", model_hdr->max[0], model_hdr->max[1], model_hdr->max[2] );\n\tfprintf( fp, \"$cbox %f %f %f\", model_hdr->bbmin[0], model_hdr->bbmin[1], model_hdr->bbmin[2] );\n\tfprintf( fp, \" %f %f %f\\n\\n\", model_hdr->bbmax[0], model_hdr->bbmax[1], model_hdr->bbmax[2] );\n\n\tWriteHitBoxInfo( fp );\n\tWriteControllerInfo( fp );\n\tWriteSequenceInfo( fp );\n\n\tfputs( \"// End of QC script.\\n\", fp );\n\tfclose( fp );\n\n\tLogPrintf( \"QC Script: %s\", filename );\n}\n\n"
  },
  {
    "path": "utils/mdldec/qc.h",
    "content": "/*\nqc.h - Quake C script writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef QC_H\n#define QC_H\n\n#define DEFAULT_SEQGROUPSIZE\t64\n#define ACTIVITIES_FILE\t\"activities.txt\"\n\nqboolean\tLoadActivityList( const char *appname );\nvoid\t\tWriteQCScript( void );\n\n#endif // QC_H\n\n"
  },
  {
    "path": "utils/mdldec/res/activities.txt",
    "content": "ACT_RESET\nACT_IDLE\nACT_GUARD\nACT_WALK\nACT_RUN\nACT_FLY\nACT_SWIM\nACT_HOP\nACT_LEAP\nACT_FALL\nACT_LAND\nACT_STRAFE_LEFT\nACT_STRAFE_RIGHT\nACT_ROLL_LEFT\nACT_ROLL_RIGHT\nACT_TURN_LEFT\nACT_TURN_RIGHT\nACT_CROUCH\nACT_CROUCHIDLE\nACT_STAND\nACT_USE\nACT_SIGNAL1\nACT_SIGNAL2\nACT_SIGNAL3\nACT_TWITCH\nACT_COWER\nACT_SMALL_FLINCH\nACT_BIG_FLINCH\nACT_RANGE_ATTACK1\nACT_RANGE_ATTACK2\nACT_MELEE_ATTACK1\nACT_MELEE_ATTACK2\nACT_RELOAD\nACT_ARM\nACT_DISARM\nACT_EAT\nACT_DIESIMPLE\nACT_DIEBACKWARD\nACT_DIEFORWARD\nACT_DIEVIOLENT\nACT_BARNACLE_HIT\nACT_BARNACLE_PULL\nACT_BARNACLE_CHOMP\nACT_BARNACLE_CHEW\nACT_SLEEP\nACT_INSPECT_FLOOR\nACT_INSPECT_WALL\nACT_IDLE_ANGRY\nACT_WALK_HURT\nACT_RUN_HURT\nACT_HOVER\nACT_GLIDE\nACT_FLY_LEFT\nACT_FLY_RIGHT\nACT_DETECT_SCENT\nACT_SNIFF\nACT_BITE\nACT_THREAT_DISPLAY\nACT_FEAR_DISPLAY\nACT_EXCITED\nACT_SPECIAL_ATTACK1\nACT_SPECIAL_ATTACK2\nACT_COMBAT_IDLE\nACT_WALK_SCARED\nACT_RUN_SCARED\nACT_VICTORY_DANCE\nACT_DIE_HEADSHOT\nACT_DIE_CHESTSHOT\nACT_DIE_GUTSHOT\nACT_DIE_BACKSHOT\nACT_FLINCH_HEAD\nACT_FLINCH_CHEST\nACT_FLINCH_STOMACH\nACT_FLINCH_LEFTARM\nACT_FLINCH_RIGHTARM\nACT_FLINCH_LEFTLEG\nACT_FLINCH_RIGHTLEG\n"
  },
  {
    "path": "utils/mdldec/settings.h",
    "content": "#pragma once\n#ifndef SETTINGS_H\n#define SETTINGS_H\n\nextern int globalsettings;\n\nenum\n{\n\tSETTINGS_LEGACYMOTION = BIT(0),\n\tSETTINGS_UVSHIFTING = BIT(1),\n\tSETTINGS_NOLOGS = BIT(2),\n\tSETTINGS_NOVALIDATION =  BIT(3),\n\tSETTINGS_SEPARATEANIMSFOLDER = BIT(4),\n\tSETTINGS_SEPARATETEXTURESFOLDER = BIT(5),\n};\n\n#endif // SETTINGS_H\n"
  },
  {
    "path": "utils/mdldec/smd.c",
    "content": "/*\nsmd.c - Studio Model Data format writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"const.h\"\n#include \"com_model.h\"\n#include \"xash3d_mathlib.h\"\n#include \"crtlib.h\"\n#include \"studio.h\"\n#include \"mdldec.h\"\n#include \"utils.h\"\n#include \"settings.h\"\n#include \"smd.h\"\n\nstatic matrix3x4\t*bonetransform;\nstatic matrix3x4\t*worldtransform;\n\n/*\n============\nCreateBoneTransformMatrices\n============\n*/\nstatic qboolean CreateBoneTransformMatrices( matrix3x4 **matrix )\n{\n\t*matrix = calloc( model_hdr->numbones, sizeof( matrix3x4 ) );\n\n\tif( !*matrix )\n\t{\n\t\tLogPutS( \"ERROR: Couldn't allocate memory for bone transformation matrices!\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nFillBoneTransformMatrices\n============\n*/\nstatic void FillBoneTransformMatrices( void )\n{\n\tint\t\t i;\n\tmstudiobone_t\t*bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\tmatrix3x4\t bonematrix;\n\tvec4_t\t\t q;\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++bone )\n\t{\n\t\tAngleQuaternion( &bone->value[3], q, true );\n\t\tMatrix3x4_FromOriginQuat( bonematrix, q, bone->value );\n\n\t\tif( bone->parent == -1 )\n\t\t{\n\t\t\tMatrix3x4_Copy( bonetransform[i], bonematrix );\n\t\t\tcontinue;\n\t\t}\n\n\t\tMatrix3x4_ConcatTransforms( bonetransform[i], bonetransform[bone->parent], bonematrix );\n\t}\n}\n\n/*\n============\nFillWorldTransformMatrices\n============\n*/\nstatic void FillWorldTransformMatrices( void )\n{\n\tint\t\t\t i;\n\tmstudioboneinfo_t\t*boneinfo = (mstudioboneinfo_t *)( (byte *)model_hdr + model_hdr->boneindex + model_hdr->numbones * sizeof( mstudiobone_t ) );\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++boneinfo )\n\t\tMatrix3x4_ConcatTransforms( worldtransform[i], bonetransform[i], boneinfo->poseToBone );\n}\n\n/*\n============\nRemoveBoneTransformMatrices\n============\n*/\nstatic void RemoveBoneTransformMatrices( matrix3x4 **matrix )\n{\n\tfree( *matrix );\n}\n\n/*\n============\nClipRotations\n============\n*/\nstatic void ClipRotations( vec3_t angle )\n{\n\tint i;\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\twhile( angle[i] >= M_PI_F )\n\t\t\tangle[i] -= M_PI2_F;\n\n\t\twhile( angle[i] < -M_PI_F )\n\t\t\tangle[i] += M_PI2_F;\n\t}\n}\n\n/*\n============\nProperBoneRotationZ\n============\n*/\nstatic void ProperBoneRotationZ( vec_t *motion, float angle )\n{\n\tfloat\ttmp, rot;\n\n\trot = DEG2RAD( angle );\n\n\ttmp = motion[0];\n\tmotion[0] = motion[1];\n\tmotion[1] = -tmp;\n\n\tmotion[5] += rot;\n}\n\n/*\n============\nCalcBonePosition\n============\n*/\nstatic void CalcBonePosition( mstudioanim_t *anim, mstudiobone_t *bone, vec_t *motion, int frame )\n{\n\tint\t\t\t i, j;\n\tfloat\t\t\t value;\n\tmstudioanimvalue_t\t*animvalue;\n\n\tfor( i = 0; i < 6; i++ )\n\t{\n\t\tmotion[i] = bone->value[i];\n\n\t\tif( !anim->offset[i] )\n\t\t\tcontinue;\n\n\t\tanimvalue = (mstudioanimvalue_t *)( (byte *)anim + anim->offset[i] );\n\n\t\tj = frame;\n\n\t\twhile( animvalue->num.total <= j )\n\t\t{\n\t\t\tj -= animvalue->num.total;\n\t\t\tanimvalue += animvalue->num.valid + 1;\n\t\t}\n\n\t\tif( animvalue->num.valid > j )\n\t\t\tvalue = animvalue[j + 1].value;\n\t\telse\n\t\t\tvalue = animvalue[animvalue->num.valid].value;\n\n\t\tmotion[i] += value * bone->scale[i];\n\t}\n}\n\n/*\n============\nWriteNodes\n============\n*/\nstatic void WriteNodes( FILE *fp )\n{\n\tint\t\t i;\n\tmstudiobone_t\t*bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfputs( \"nodes\\n\", fp );\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++bone )\n\t\tfprintf( fp, \"%3i \\\"%s\\\" %i\\n\", i, bone->name, bone->parent );\n\n\tfputs( \"end\\n\", fp );\n}\n\n/*\n============\nWriteSkeleton\n============\n*/\nstatic void WriteSkeleton( FILE *fp )\n{\n\tint\t\t i, j;\n\tmstudiobone_t\t*bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfputs( \"skeleton\\n\", fp );\n\tfputs( \"time 0\\n\", fp );\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++bone )\n\t{\n\t\tfprintf( fp, \"%3i\", i );\n\n\t\tfor( j = 0; j < 6; j++ )\n\t\t\tfprintf( fp, \" %f\", bone->value[j] );\n\n\t\tfputs( \"\\n\", fp );\n\t}\n\n\tfputs( \"end\\n\", fp );\n}\n\n/*\n============\nWriteTriangleInfo\n============\n*/\nstatic void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t *texture, mstudiotrivert_t **triverts, qboolean isevenstrip )\n{\n\tint\t\t\t i, j, k, l, index;\n\tint\t\t\t vert_index;\n\tint\t\t\t norm_index;\n\tint\t\t\t bone_index;\n\tint\t\t\t valid_bones;\n\tint\t\t\t width, height;\n\tfloat\t\t\t u, v;\n\tbyte\t\t\t*vertbone;\n\tvec3_t\t\t\t*studioverts;\n\tvec3_t\t\t\t*studionorms;\n\tvec3_t\t\t\t vert, norm;\n\tfloat\t\t\t weights[MAXSTUDIOBONEWEIGHTS], oldweight, totalweight;\n\tmatrix3x4\t\t bonematrix[MAXSTUDIOBONEWEIGHTS], skinmatrix, *pskinmatrix;\n\tmstudioboneweight_t\t*studioboneweights;\n\tchar\t\t\t buffer[64];\n\n\tvertbone    = ( (byte *)model_hdr + model->vertinfoindex );\n\tstudioverts = (vec3_t *)( (byte *)model_hdr + model->vertindex );\n\tstudionorms = (vec3_t *)( (byte *)model_hdr + model->normindex );\n\tstudioboneweights = (mstudioboneweight_t *)( (byte *)model_hdr + model->blendvertinfoindex );\n\n\tQ_strncpy( buffer, texture->name, sizeof( buffer ));\n\n\t// Many filesystems couldn't write files if \"#\" is first character in the name.\n\tif( buffer[0] == '#' ) buffer[0] = 's';\n\n\tfprintf( fp, \"%s\\n\", buffer );\n\n\tfor( i = 0; i < 3; i++ )\n\t{\n\t\tindex = isevenstrip ? ( i + 1 ) % 3 : i;\n\t\tvert_index = triverts[index]->vertindex;\n\t\tnorm_index = triverts[index]->normindex;\n\t\tbone_index = vertbone[vert_index];\n\n\t\tif( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS )\n\t\t{\n\t\t\tvalid_bones = 0, totalweight = 0;\n\t\t\tmemset( skinmatrix, 0, sizeof( matrix3x4 ) );\n\n\t\t\tfor( j = 0; j < MAXSTUDIOBONEWEIGHTS; ++j )\n\t\t\t\tif( studioboneweights[vert_index].bone[j] != -1 )\n\t\t\t\t\tvalid_bones++;\n\n\t\t\tfor( j = 0; j < valid_bones; ++j )\n\t\t\t{\n\t\t\t\tMatrix3x4_Copy( bonematrix[j], worldtransform[studioboneweights[vert_index].bone[j]] );\n\t\t\t\tweights[j] = studioboneweights[vert_index].weight[j] / 255.0f;\n\t\t\t\ttotalweight += weights[j];\n\t\t\t}\n\n\t\t\toldweight = weights[0];\n\n\t\t\tif( totalweight < 1.0f )\n\t\t\t\tweights[0] += 1.0f - totalweight;\n\n\t\t\tfor( j = 0; j < valid_bones; ++j )\n\t\t\t\tfor( k = 0; k < 3; ++k )\n\t\t\t\t\tfor( l = 0; l < 4; ++l )\n\t\t\t\t\t\tskinmatrix[k][l] += bonematrix[j][k][l] * weights[j];\n\n\t\t\tpskinmatrix = &skinmatrix;\n\t\t}\n\t\telse\n\t\t\tpskinmatrix = &bonetransform[bone_index];\n\n\t\tMatrix3x4_VectorTransform( *pskinmatrix, studioverts[vert_index], vert );\n\t\tMatrix3x4_VectorRotate( *pskinmatrix, studionorms[norm_index], norm );\n\t\tVectorNormalize( norm );\n\n\t\tif( texture->flags & STUDIO_NF_UV_COORDS )\n\t\t{\n\t\t\tu = HalfToFloat( triverts[index]->s );\n\t\t\tv = -HalfToFloat( triverts[index]->t );\n\t\t}\n\t\telse if( texture->width == 1 || texture->height == 1 )\n\t\t{\n\t\t\tif( texture->name[0] == '#' )\n\t\t\t{\n\t\t\t\tQ_strncpy( buffer, &texture->name[1], 4 );\n\t\t\t\twidth = Q_atoi( buffer );\n\n\t\t\t\tQ_strncpy( buffer, &texture->name[4], 4 );\n\t\t\t\theight = Q_atoi( buffer );\n\n\t\t\t\tu = (float)triverts[index]->s / width;\n\t\t\t\tv = 1.0f - (float)triverts[index]->t / height;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tu = (float)triverts[index]->s;\n\t\t\t\tv = 1.0f - (float)triverts[index]->t;\n\t\t\t}\n\t\t}\n\t\telse if( globalsettings & SETTINGS_UVSHIFTING )\n\t\t{\n\t\t\tu = (float)triverts[index]->s / texture->width;\n\t\t\tv = 1.0f - (float)triverts[index]->t / texture->height;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tu = (float)triverts[index]->s / ( texture->width - 1 );\n\t\t\tv = 1.0f - (float)triverts[index]->t / ( texture->height - 1 );\n\t\t}\n\n\t\tfprintf( fp, \"%3i %f %f %f %f %f %f %f %f\",\n\t\t    bone_index,\n\t\t    vert[0], vert[1], vert[2],\n\t\t    norm[0], norm[1], norm[2],\n\t\t    u, v );\n\n\t\tif( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS )\n\t\t{\n\t\t\tfprintf( fp, \" %d\", valid_bones );\n\n\t\t\tweights[0] = oldweight;\n\n\t\t\tfor( j = 0; j < valid_bones; ++j )\n\t\t\t\tfprintf( fp, \" %d %f\",\n\t\t\t\t    studioboneweights[vert_index].bone[j],\n\t\t\t\t    weights[j] );\n\t\t}\n\n\t\tfputs( \"\\n\", fp );\n\t}\n}\n\n/*\n============\nWriteTriangles\n============\n*/\nstatic void WriteTriangles( FILE *fp, mstudiomodel_t *model )\n{\n\tint\t\t\t i, j, k;\n\tmstudiomesh_t\t\t*mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex );\n\tmstudiotexture_t\t*texture;\n\tmstudiotrivert_t\t*triverts[3];\n\tshort\t\t\t*tricmds;\n\n\tfputs( \"triangles\\n\", fp );\n\n\tfor( i = 0; i < model->nummesh; ++i, ++mesh )\n\t{\n\t\ttricmds = (short *)( (byte *)model_hdr + mesh->triindex );\n\t\ttexture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + mesh->skinref;\n\n\t\twhile( ( j = *( tricmds++ ) ) )\n\t\t{\n\t\t\tif( j >= 0 )\n\t\t\t{\n\t\t\t\t// triangle strip\n\t\t\t\tfor( k = 0; j > 0; j--, k++, tricmds += 4 )\n\t\t\t\t{\n\t\t\t\t\tif( k == 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[0] = (mstudiotrivert_t *)tricmds;\n\t\t\t\t\t}\n\t\t\t\t\telse if( k == 1 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[2] = (mstudiotrivert_t *)tricmds;\n\t\t\t\t\t}\n\t\t\t\t\telse if( k == 2 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[1] = (mstudiotrivert_t *)tricmds;\n\n\t\t\t\t\t\tWriteTriangleInfo( fp, model, texture, triverts, true );\n\t\t\t\t\t}\n\t\t\t\t\telse if( k % 2 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[0] = triverts[2];\n\t\t\t\t\t\ttriverts[2] = (mstudiotrivert_t *)tricmds;\n\n\t\t\t\t\t\tWriteTriangleInfo( fp, model, texture, triverts, false );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[0] = triverts[1];\n\t\t\t\t\t\ttriverts[1] = (mstudiotrivert_t *)tricmds;\n\n\t\t\t\t\t\tWriteTriangleInfo( fp, model, texture, triverts, true );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// triangle fan\n\t\t\t\tj = abs( j );\n\n\t\t\t\tfor( k = 0; j > 0; j--, k++, tricmds += 4 )\n\t\t\t\t{\n\t\t\t\t\tif( k == 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[0] = (mstudiotrivert_t *)tricmds;\n\t\t\t\t\t}\n\t\t\t\t\telse if( k == 1 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[2] = (mstudiotrivert_t *)tricmds;\n\t\t\t\t\t}\n\t\t\t\t\telse if( k == 2 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[1] = (mstudiotrivert_t *)tricmds;\n\n\t\t\t\t\t\tWriteTriangleInfo( fp, model, texture, triverts, false );\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\ttriverts[2] = triverts[1];\n\t\t\t\t\t\ttriverts[1] = (mstudiotrivert_t *)tricmds;\n\n\t\t\t\t\t\tWriteTriangleInfo( fp, model, texture, triverts, false );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfputs( \"end\\n\", fp );\n}\n\n/*\n============\nWriteFrameInfo\n============\n*/\nstatic void WriteFrameInfo( FILE *fp, mstudioanim_t *anim, mstudioseqdesc_t *seqdesc, int frame )\n{\n\tint\t\t\t i, j;\n\tfloat\t\t\t scale;\n\tvec_t\t\t\t motion[6]; // x, y, z, xr, yr, zr\n\tmstudiobone_t\t\t*bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex );\n\n\tfprintf( fp, \"time %i\\n\", frame );\n\n\tfor( i = 0; i < model_hdr->numbones; ++i, ++anim, ++bone )\n\t{\n\t\tCalcBonePosition( anim, bone, motion, frame );\n\n\t\tif( bone->parent == -1 )\n\t\t{\n\t\t\tif( seqdesc->numframes > 1 && frame > 0 )\n\t\t\t{\n\t\t\t\tscale = frame / (float)( seqdesc->numframes - 1 );\n\n\t\t\t\tVectorMA( motion, scale, seqdesc->linearmovement, motion );\n\t\t\t}\n\n\t\t\tProperBoneRotationZ( motion, 270.0f );\n\t\t}\n\n\t\tClipRotations( &motion[3] );\n\n\t\tfprintf( fp, \"%3i  \", i );\n\n\t\tfor( j = 0; j < 6; j++ )\n\t\t\tfprintf( fp, \" %f\", motion[j] );\n\n\t\tfputs( \"\\n\", fp );\n\t}\n}\n\n/*\n============\nWriteAnimations\n============\n*/\nstatic void WriteAnimations( FILE *fp, mstudioseqdesc_t *seqdesc, int blend )\n{\n\tint\t\t i;\n\tmstudioanim_t\t*anim;\n\n\tfputs( \"skeleton\\n\", fp );\n\n\tanim = (mstudioanim_t *)( (byte *)anim_hdr[seqdesc->seqgroup] + seqdesc->animindex );\n\tanim += blend * model_hdr->numbones;\n\n\tfor( i = 0; i < seqdesc->numframes; i++ )\n\t\tWriteFrameInfo( fp, anim, seqdesc, i );\n\n\tfputs( \"end\\n\", fp );\n}\n\n/*\n============\nWriteReferences\n============\n*/\nstatic void WriteReferences( void )\n{\n\tint\t\t\t i, j;\n\tint\t\t\t len;\n\tFILE\t\t\t*fp;\n\tmstudiomodel_t\t\t*model;\n\tmstudiobodyparts_t\t*bodypart;\n\tchar\t\t\t filename[MAX_SYSPATH];\n\n\tif( !CreateBoneTransformMatrices( &bonetransform ) )\n\t\treturn;\n\n\tFillBoneTransformMatrices();\n\n\tif( model_hdr->flags & STUDIO_HAS_BONEINFO )\n\t{\n\t\tif( !CreateBoneTransformMatrices( &worldtransform ) )\n\t\t\treturn;\n\n\t\tFillWorldTransformMatrices();\n\t}\n\n\tbodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex );\n\n\tfor( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart )\n\t{\n\t\tmodel = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex );\n\n\t\tfor( j = 0; j < bodypart->nummodels; ++j, ++model )\n\t\t{\n\t\t\tif( !Q_strncmp( model->name, \"blank\", 5 ) )\n\t\t\t\tcontinue;\n\n\t\t\tlen = Q_snprintf( filename, MAX_SYSPATH, \"%s%s.smd\", destdir, model->name );\n\n\t\t\tif( len == -1 )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Destination path is too long. Couldn't write %s.smd.\", model->name );\n\t\t\t\tgoto _fail;\n\t\t\t}\n\n\t\t\tfp = fopen( filename, \"w\" );\n\n\t\t\tif( !fp )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Couldn't write %s.\", filename );\n\t\t\t\tgoto _fail;\n\t\t\t}\n\n\t\t\tfputs( \"version 1\\n\", fp );\n\n\t\t\tWriteNodes( fp );\n\t\t\tWriteSkeleton( fp );\n\t\t\tWriteTriangles( fp, model );\n\n\t\t\tfclose( fp );\n\n\t\t\tLogPrintf( \"Reference: %s.\", filename );\n\t\t}\n\t}\n\n_fail:\n\tRemoveBoneTransformMatrices( &bonetransform );\n\n\tif( model_hdr->flags & STUDIO_HAS_BONEINFO )\n\t\tRemoveBoneTransformMatrices( &worldtransform );\n}\n\n/*\n============\nWriteSequences\n============\n*/\nstatic void WriteSequences( void )\n{\n\tint\t\t\t i, j;\n\tint\t\t\t len, namelen, emptyplace;\n\tFILE\t\t\t*fp;\n\tchar\t\t\t path[MAX_SYSPATH];\n\tmstudioseqdesc_t\t*seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex );\n\n\tlen = Q_snprintf( path, MAX_SYSPATH, ( globalsettings & SETTINGS_SEPARATEANIMSFOLDER ) ? \"%s\" SEQUENCEPATH : \"%s\", destdir );\n\n\tif( len == -1 || !MakeDirectory( path ))\n\t{\n\t\tLogPutS( \"ERROR: Destination path is too long or write permission denied. Couldn't create directory for sequences.\" );\n\t\treturn;\n\t}\n\n\temptyplace = MAX_SYSPATH - len;\n\n\tfor( i = 0; i < model_hdr->numseq; ++i, ++seqdesc )\n\t{\n\t\tfor( j = 0; j < seqdesc->numblends; j++ )\n\t\t{\n\t\t\tif( seqdesc->numblends == 1 )\n\t\t\t\tnamelen = Q_snprintf( &path[len], emptyplace, \"%s.smd\", seqdesc->label );\n\t\t\telse\n\t\t\t\tnamelen = Q_snprintf( &path[len], emptyplace, \"%s_blend%02i.smd\", seqdesc->label, j + 1 );\n\n\t\t\tif( namelen == -1 )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Destination path is too long. Couldn't write %s.smd.\", seqdesc->label );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfp = fopen( path, \"w\" );\n\n\t\t\tif( !fp )\n\t\t\t{\n\t\t\t\tLogPrintf( \"ERROR: Couldn't write %s.\", path );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfputs( \"version 1\\n\", fp );\n\n\t\t\tWriteNodes( fp );\n\t\t\tWriteAnimations( fp, seqdesc, j );\n\n\t\t\tfclose( fp );\n\n\t\t\tLogPrintf( \"Sequence: %s.\", path );\n\t\t}\n\t}\n}\n\nvoid WriteSMD( void )\n{\n\tWriteReferences();\n\tWriteSequences();\n}\n\n"
  },
  {
    "path": "utils/mdldec/smd.h",
    "content": "/*\nsmd.h - Studio Model Data format writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef SMD_H\n#define SMD_H\n\n#define SEQUENCEPATH\t\"anims/\"\n\nvoid\tWriteSMD( void );\n\n#endif // SMD_H\n\n"
  },
  {
    "path": "utils/mdldec/texture.c",
    "content": "/*\ntexture.c - texture writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"const.h\"\n#include \"crtlib.h\"\n#include \"studio.h\"\n#include \"img_bmp.h\"\n#include \"img_tga.h\"\n#include \"mdldec.h\"\n#include \"utils.h\"\n#include \"settings.h\"\n#include \"texture.h\"\n\n/*\n============\nWriteBMP\n============\n*/\nstatic void WriteBMP( FILE *fp, mstudiotexture_t *texture )\n{\n\tint\t\t i;\n\tconst byte\t*p;\n\tbyte\t\t*palette, *pic;\n\trgba_t\t\t rgba_palette[256];\n\tbmp_t\t\t bmp_hdr = {0,};\n\n\tbmp_hdr.id[0] = 'B';\n\tbmp_hdr.id[1] = 'M';\n\tbmp_hdr.width = texture->width;\n\tbmp_hdr.height = texture->height;\n\tbmp_hdr.planes = 1;\n\tbmp_hdr.bitsPerPixel = 8;\n\tbmp_hdr.bitmapDataSize = bmp_hdr.width * bmp_hdr.height;\n\tbmp_hdr.colors = 256;\n\n\tbmp_hdr.fileSize =  sizeof( bmp_hdr ) + bmp_hdr.bitmapDataSize + sizeof( rgba_palette );\n\tbmp_hdr.bitmapDataOffset = sizeof( bmp_hdr ) + sizeof( rgba_palette );\n\tbmp_hdr.bitmapHeaderSize = BI_SIZE;\n\n\tpic = (byte *)texture_hdr + texture->index;\n\tpalette = pic + bmp_hdr.bitmapDataSize;\n\n\tfwrite( &bmp_hdr, sizeof( bmp_hdr ), 1, fp );\n\n\tp = palette;\n\n\tfor( i = 0; i < (int)bmp_hdr.colors; i++ )\n\t{\n\t\trgba_palette[i][2] = *p++;\n\t\trgba_palette[i][1] = *p++;\n\t\trgba_palette[i][0] = *p++;\n\t\trgba_palette[i][3] = 0;\n\t}\n\n\tfwrite( rgba_palette, sizeof( rgba_palette ), 1, fp );\n\n\tp = pic;\n\tp += ( bmp_hdr.height - 1 ) * bmp_hdr.width;\n\n\tfor( i = 0; i < bmp_hdr.height; i++ )\n\t{\n\t\tfwrite( p, bmp_hdr.width, 1, fp );\n\t\tp -= bmp_hdr.width;\n\t}\n}\n\n/*\n============\nWriteTGA\n============\n*/\nstatic void WriteTGA( FILE *fp, mstudiotexture_t *texture )\n{\n\tint              i;\n\tconst byte      *p;\n\tbyte            *palette, *pic;\n\trgb_t\t\t rgb_palette[256];\n\ttga_t\t\t tga_hdr = {0,};\n\n\ttga_hdr.colormap_type = tga_hdr.image_type = 1;\n\ttga_hdr.colormap_length = 256;\n\ttga_hdr.colormap_size = 24;\n\ttga_hdr.pixel_size = 8;\n\ttga_hdr.width = texture->width;\n\ttga_hdr.height = texture->height;\n\n\tpic = (byte *)texture_hdr + texture->index;\n\tpalette = pic + tga_hdr.width * tga_hdr.height;\n\n\tfwrite( &tga_hdr, sizeof( tga_hdr ), 1, fp );\n\n\tp = palette;\n\n\tfor( i = 0; i < (int)tga_hdr.colormap_length; i++ )\n\t{\n\t\trgb_palette[i][2] = *p++;\n\t\trgb_palette[i][1] = *p++;\n\t\trgb_palette[i][0] = *p++;\n\t}\n\n\tfwrite( rgb_palette, sizeof( rgb_palette ), 1, fp );\n\n\tp = pic;\n\tp += ( tga_hdr.height - 1 ) * tga_hdr.width;\n\n\tfor( i = 0; i < tga_hdr.height; i++ )\n\t{\n\t\tfwrite( p, tga_hdr.width, 1, fp );\n\t\tp -= tga_hdr.width;\n\t}\n}\n\n/*\n============\nWriteTextures\n============\n*/\nvoid WriteTextures( void )\n{\n\tint\t\t\t i, len, namelen, emptyplace;\n\tFILE\t\t\t*fp;\n\tmstudiotexture_t\t*texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex );\n\tchar\t\t\t path[MAX_SYSPATH];\n\n\tlen = Q_snprintf( path, MAX_SYSPATH, ( globalsettings & SETTINGS_SEPARATETEXTURESFOLDER ) ? \"%s\" TEXTUREPATH : \"%s\", destdir );\n\n\tif( len == -1 || !MakeDirectory( path ))\n\t{\n\t\tLogPutS( \"ERROR: Destination path is too long or write permission denied. Couldn't create directory for textures.\" );\n\t\treturn;\n\t}\n\n\temptyplace = MAX_SYSPATH - len;\n\n\tfor( i = 0; i < texture_hdr->numtextures; ++i, ++texture )\n\t{\n\t\tnamelen = Q_strncpy( &path[len], texture->name, emptyplace );\n\n\t\tif( emptyplace - namelen < 0 )\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Destination path is too long. Couldn't write %s.\", texture->name );\n\t\t\treturn;\n\t\t}\n\n\t\tfp = fopen( path, \"wb\" );\n\n\t\tif( !fp )\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Couldn't write texture file %s.\", path );\n\t\t\treturn;\n\t\t}\n\n\t\t// Many filesystems couldn't write files if \"#\" is first character in the name.\n\t\tif( texture->name[0] == '#' ) texture->name[0] = 's';\n\n\t\tif( !Q_stricmp( COM_FileExtension( texture->name ), \"tga\" ))\n\t\t\tWriteTGA( fp, texture );\n\t\telse\n\t\t\tWriteBMP( fp, texture );\n\n\t\tfclose( fp );\n\n\t\tLogPrintf( \"Texture: %s.\", path );\n\t}\n}\n\n"
  },
  {
    "path": "utils/mdldec/texture.h",
    "content": "/*\ntexture.h - texture writer\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef TEXTURE_H\n#define TEXTURE_H\n\n#define TEXTUREPATH\t\"textures/\"\n\nvoid\tWriteTextures( void );\n\n#endif // TEXTURE_H\n\n"
  },
  {
    "path": "utils/mdldec/utils.c",
    "content": "/*\nutils.c - Useful helper functions\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <errno.h>\n#include \"xash3d_types.h\"\n#include \"port.h\"\n#include \"crtlib.h\"\n#include \"settings.h\"\n#include \"utils.h\"\n\n/*\n============\nMakeDirectory\n============\n*/\nqboolean MakeDirectory( const char *path )\n{\n\tif( -1 == _mkdir( path ))\n\t{\n\t\tif( errno == EEXIST )\n\t\t{\n\t\t\t// TODO: when filesystem library will be ready\n\t\t\t// use FS_SysFolderExists here or replace this whole function\n\t\t\t// with FS_CreatePath\n#if XASH_WIN32\n\t\t        DWORD   dwFlags = GetFileAttributes( path );\n\t\t        return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY );\n#else\n\t\t        struct stat buf;\n\n\t\t        if( !stat( path, &buf ))\n\t\t\t\treturn S_ISDIR( buf.st_mode );\n#endif\n\t\t}\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nMakeFullPath\n============\n*/\nqboolean MakeFullPath( const char *path )\n{\n\tchar *p = (char *)path, tmp;\n\n\tif( *p == '/' )\n\t\tp++;\n\n\tfor( ; *p; )\n\t{\n\t\tp = Q_strpbrk( p, \"/\\\\\" );\n\n\t\tif( p )\n\t\t{\n\t\t\ttmp = *p;\n\t\t\t*p = '\\0';\n\t\t}\n\n\t\tif( !MakeDirectory( path ))\n\t\t{\n\t\t\tLogPrintf( \"ERROR: Couldn't create directory %s.\", path );\n\t\t\treturn false;\n\t\t}\n\n\t\tif( !p )\n\t\t\tbreak;\n\n\t\t*p++ = tmp;\n\t}\n\n\treturn true;\n}\n\n/*\n============\nExtractFileName\n============\n*/\nvoid ExtractFileName( char *name, size_t size )\n{\n\tchar\ttmp[MAX_SYSPATH];\n\n\tif( !( name && *name ) || size <= 0 )\n\t\treturn;\n\n\tname[size - 1] = '\\0';\n\n\tif( Q_strpbrk( name, \"/\\\\\" ))\n\t{\n\t\tQ_strncpy( tmp, COM_FileWithoutPath( name ), sizeof( tmp ));\n\t\tQ_strncpy( name, tmp, size );\n\t}\n}\n\n/*\n============\nGetFileSize\n============\n*/\noff_t GetSizeOfFile( FILE *fp )\n{\n\tstruct stat\tst;\n\tint\t\tfd;\n\n\tfd = fileno( fp );\n\tfstat( fd, &st );\n\n\treturn st.st_size;\n}\n\n/*\n============\nLoadFile\n============\n*/\nbyte *LoadFile( const char *filename, off_t *size )\n{\n\tFILE\t*fp;\n\tbyte\t*buf;\n\n\tfp = fopen( filename, \"rb\" );\n\n\tif( !fp )\n\t\treturn NULL;\n\n\t*size = GetSizeOfFile( fp );\n\n\tbuf = malloc( *size );\n\n\tif( !buf )\n\t\treturn NULL;\n\n\tfread( buf, *size, 1, fp );\n\tfclose( fp );\n\n\treturn buf;\n}\n\n/*\n============\nLogPutS\n============\n*/\nvoid LogPutS( const char *str )\n{\n\tif( ( globalsettings & SETTINGS_NOLOGS ))\n\t\treturn;\n\n\tif( Q_strncmp( str, \"ERROR:\", sizeof( \"ERROR:\" ) - 1 ))\n\t\tputs( str );\n\telse fprintf( stderr, \"%s\\n\", str );\n}\n\n/*\n============\nLogPrintf\n============\n*/\nvoid LogPrintf( const char *szFmt, ... )\n{\n\tva_list args;\n\tstatic char buffer[2048];\n\n\tif( ( globalsettings & SETTINGS_NOLOGS ))\n\t\treturn;\n\n\tva_start( args, szFmt );\n        Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args );\n\tva_end( args );\n\n\tif( Q_strncmp( buffer, \"ERROR:\", sizeof( \"ERROR:\" ) - 1 ))\n\t\tputs( buffer );\n\telse fprintf( stderr, \"%s\\n\", buffer );\n}\n\n"
  },
  {
    "path": "utils/mdldec/utils.h",
    "content": "/*\nutils.h - Useful helper functions\nCopyright (C) 2020 Andrey Akhmichin\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n#pragma once\n#ifndef\tUTILS_H\n#define UTILS_H\n\nqboolean\t MakeDirectory( const char *path );\nqboolean\t MakeFullPath( const char *path );\nvoid\t\t ExtractFileName( char *name, size_t size );\noff_t\t\t GetSizeOfFile( FILE *fp );\nbyte\t\t*LoadFile( const char *filename, off_t *size );\nvoid\t\t LogPutS( const char *str );\nvoid\t\t LogPrintf( const char *szFmt, ... );\n\n#endif // UTILS_H\n\n"
  },
  {
    "path": "utils/mdldec/version.h",
    "content": "#pragma once\n#ifndef VERSION_H\n#define VERSION_H\n\n#define APP_VERSION\t\"1.2.1\"\n\n#endif // VERSION_H\n\n"
  },
  {
    "path": "utils/mdldec/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# a1batross, mittorn, 2018\n\ndef options(opt):\n\t# TODO: any options for mdldec?\n\tgrp = opt.get_option_group('Utilities options')\n\n\tgrp.add_option('--disable-utils-mdldec', action = 'store_true', dest = 'DISABLE_UTILS_MDLDEC', default = False,\n                help = 'disable studio model decompiler utility [default: %(default)s]')\n\ndef configure(conf):\n\tconf.env.DISABLE_UTILS_MDLDEC = conf.options.DISABLE_UTILS_MDLDEC\n\ndef build(bld):\n\tif bld.env.DISABLE_UTILS_MDLDEC:\n\t\treturn\n\n\tbld.program(source   = bld.path.ant_glob('*.c'),\n\t\ttarget   = 'mdldec',\n\t\tincludes = '.',\n\t\tuse      = 'engine_includes public M werror',\n\t\tinstall_path = bld.env.BINDIR,\n\t\tsubsystem = bld.env.CONSOLE_SUBSYSTEM\n\t)\n\n\tbld.install_files(bld.env.SHAREDIR, 'res/activities.txt')\n"
  },
  {
    "path": "utils/run-fuzzer/run-fuzzer.c",
    "content": "#include <dlfcn.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#if !defined LIB || !defined FUNC\n#error\n#endif\n\ntypedef int (*FuzzFunc)(const char *Data, size_t Size);\n\nvoid *handle = NULL;\nFuzzFunc f = NULL;\n\nint LLVMFuzzerTestOneInput( const char *Data, size_t Size )\n{\n\tif( !handle )\n\t\thandle = dlopen( LIB, RTLD_NOW );\n\n\tif( handle )\n\t{\n\t\tif( !f )\n\t\t\tf = dlsym( handle, FUNC );\n\n\t\tif( f )\n\t\t{\n\t\t\treturn f( Data, Size );\n\t\t}\n\t}\n\n\tfprintf( stderr, \"Fail: %s\\n\", dlerror() );\n\n\tabort();\n\treturn 0;\n}\n"
  },
  {
    "path": "utils/run-fuzzer/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# a1batross, mittorn, 2018\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tif conf.options.BUILD_TYPE not in ['sanitize', 'asan']:\n\t\tconf.fatal('useless without -T sanitize')\n\n\tif conf.env.COMPILER_CC != 'clang':\n\t\tconf.fatal('only clang is supported')\n\n\tconf.env.append_unique('CFLAGS', '-fsanitize=fuzzer')\n\tconf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer')\n\ndef add_runner_target(bld, lib, func):\n\tsource = bld.path.ant_glob('*.c')\n\tincludes = '.'\n\n\tbld.program(\n\t\tsource   = source,\n\t\ttarget   = 'run-fuzzer-' + func,\n\t\tincludes = includes,\n\t\tuse      = 'DL werror',\n\t\tdefines  = ['FUNC=\"Fuzz_' + func + '\"', 'LIB=\"' + lib + '\"'],\n\t\tinstall_path = bld.env.BINDIR,\n\t\tsubsystem = bld.env.CONSOLE_SUBSYSTEM\n\t)\n\ndef build(bld):\n\tadd_runner_target(bld, 'libxash.so', 'Sound_LoadMPG')\n\tadd_runner_target(bld, 'libxash.so', 'Sound_ParseID3Tag')\n\tadd_runner_target(bld, 'libxash.so', 'Sound_LoadWAV')\n\tadd_runner_target(bld, 'libxash.so', 'Image_LoadBMP')\n\tadd_runner_target(bld, 'libxash.so', 'Image_LoadPNG')\n\tadd_runner_target(bld, 'libxash.so', 'Image_LoadDDS')\n\tadd_runner_target(bld, 'libxash.so', 'Image_LoadTGA')\n\tadd_runner_target(bld, 'libxash.so', 'Mod_LoadModel')\n"
  },
  {
    "path": "utils/xar/wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# a1batross, mittorn, 2018\n\ndef options(opt):\n\tpass\n\ndef configure(conf):\n\tpass\n\ndef build(bld):\n\tbld.program(source   = bld.path.ant_glob('*.c'),\n\t\ttarget   = 'xar',\n\t\tincludes = '.',\n\t\tuse      = 'public filesystem_includes werror',\n\t\trpath = bld.env.DEFAULT_RPATH,\n\t\tinstall_path = bld.env.BINDIR,\n\t\tsubsystem = bld.env.CONSOLE_SUBSYSTEM\n\t)\n"
  },
  {
    "path": "utils/xar/xar.c",
    "content": "/*\nxar.c -- Xash ARchives (XAR)\nCopyright (C) 2023 Alibek Omarov\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n*/\n\n#include \"port.h\"\n#include \"build.h\"\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <errno.h>\n#include \"filesystem.h\"\n#if XASH_POSIX\n#include <sys/stat.h>\n#include <dlfcn.h>\n#define LoadLibrary( x ) dlopen( x, RTLD_NOW )\n#define GetProcAddress( x, y ) dlsym( x, y )\n#define FreeLibrary( x ) dlclose( x )\n#elif XASH_WIN32\n#include <windows.h>\n#endif\n\nstatic void *g_hModule;\nstatic FSAPI g_pfnGetFSAPI;\nstatic fs_api_t g_fs;\nstatic fs_globals_t *g_nullglobals;\n\nstatic qboolean LoadFilesystem( void )\n{\n#if 0\n\tstring cwd;\n\n\tif( getcwd( cwd, sizeof( cwd )) == NULL )\n\t{\n\t\tprintf( \"getcwd() failed: %s\\n\", strerror( errno ));\n\t\treturn false;\n\t}\n#endif\n\tg_hModule = LoadLibrary( \"filesystem_stdio.\" OS_LIB_EXT );\n\tif( !g_hModule )\n\t\treturn false;\n\n\tg_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API );\n\tif( !g_pfnGetFSAPI )\n\t\treturn false;\n\n\tif( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL ))\n\t\treturn false;\n\n\t// g_fs.InitStdio( true, cwd, );\n\n\treturn true;\n}\n\nstatic void FS_CreatePath( char *path )\n{\n\tchar\t*ofs, save;\n\n\tfor( ofs = path + 1; *ofs; ofs++ )\n\t{\n\t\tif( *ofs == '/' || *ofs == '\\\\' )\n\t\t{\n\t\t\t// create the directory\n\t\t\tsave = *ofs;\n\t\t\t*ofs = 0;\n\t\t\t_mkdir( path );\n\t\t\t*ofs = save;\n\t\t}\n\t}\n}\n\nstatic void usage( const char *arg0 )\n{\n\tprintf( \"%s: <action> [option...] <file>\\n\", arg0 );\n\tputs( \"XAR is a simple frontend to Xash3D FWGS's filesystem_stdio library\" );\n\tputs( \"that allows interacting with archive types supported by it\" );\n\tputs( \"Options:\" );\n\tputs( \"\\tx\\t\\teXtract the archive\" );\n\tputs( \"\\tt\\t\\tlisT the archive\" );\n\t// TODO: make an interface for modifying\n\t// puts( \"\\tc\\t\\tCreate the archive\" );\n\t// puts( \"\\tu\\t\\tUpdate the archive\" );\n\t// puts( \"\\tr\\t\\tRemove from the archive\" );\n\n\tputs( \"Extract and list options:\" );\n\tputs( \"\\t-wads\\tauto-mount WADs inside archives\" );\n\texit( 1 );\n}\n\nint main( int argc, char **argv )\n{\n\tconst char *filename;\n\tsearch_t *search;\n\tchar action;\n\tint i, flags = FS_NOWRITE_PATH | FS_SKIP_ARCHIVED_WADS;\n\n\tif( argc < 3 )\n\t\tusage( argv[0] );\n\n\tif( !LoadFilesystem())\n\t{\n\t\tputs( \"Can't load filesystem_stdio!\" );\n\t\treturn 2;\n\t}\n\n\taction = argv[1][0];\n\tif( action != 'x' && action != 't' )\n\t{\n\t\tprintf( \"Unknown action: %c\\n\", action );\n\t\tusage( argv[0] );\n\t}\n\n\tfor( i = 2; i < argc - 1; i++ )\n\t{\n\t\tif( !strcmp( argv[i], \"-wads\" ))\n\t\t\tClearBits( flags, FS_SKIP_ARCHIVED_WADS );\n\t\telse\n\t\t{\n\t\t\tprintf( \"Unknown option: %s\\n\", argv[i] );\n\t\t\tusage( argv[0] );\n\t\t}\n\t}\n\n\tfilename = argv[argc-1];\n\n\tif( g_fs.MountArchive_Fullpath( filename, flags ) == NULL )\n\t{\n\t\tprintf( \"Can't mount %s\\n\", filename );\n\t\treturn 3;\n\t}\n\n\t// suboptimal, but that's what is available with current FS API\n\tsearch = g_fs.Search( \"*\", false, false );\n\tif( !search )\n\t{\n\t\tprintf( \"Can't find any files in %s\\n\", filename );\n\t\treturn 4;\n\t}\n\n\tif( action == 't' )\n\t{\n\t\tputs( \"File list:\" );\n\t\tfor( i = 0; i < search->numfilenames; i++ )\n\t\t{\n\t\t\tprintf( \"\\t%s\\n\", search->filenames[i] );\n\t\t}\n\t}\n\telse if( action == 'x' )\n\t{\n\t\tfor( i = 0; i < search->numfilenames; i++ )\n\t\t{\n\t\t\tstruct stat st;\n\t\t\tchar *path = search->filenames[i];\n\t\t\tfile_t *from;\n\t\t\tchar buffer[4096];\n\t\t\tFILE *to;\n\n\t\t\tif(( from = g_fs.Open( path, \"rb\", false )) == NULL )\n\t\t\t{\n\t\t\t\tprintf( \"Can't open %s in archive, skipping...\\n\", path );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tFS_CreatePath( path );\n\t\t\tif( stat( path, &st ) == 0 )\n\t\t\t{\n\t\t\t\tprintf( \"Will not overwrite existing %s file\\n\", path );\n\t\t\t\tg_fs.Close( from );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif(( to = fopen( search->filenames[i], \"wb\" )) == NULL )\n\t\t\t{\n\t\t\t\tprintf( \"fopen() failed: %s\\n\", strerror( errno ));\n\t\t\t\tg_fs.Close( from );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tprintf( \"Unpacking %s...\\n\", path );\n\t\t\twhile( !g_fs.Eof( from ))\n\t\t\t{\n\t\t\t\tsize_t len = g_fs.Read( from, buffer, sizeof( buffer ));\n\t\t\t\tfwrite( buffer, 1, len, to );\n\t\t\t}\n\t\t\tfclose( to );\n\t\t\tg_fs.Close( from );\n\t\t}\n\t}\n\n\tfree( search );\n\n\treturn 0;\n}\n"
  },
  {
    "path": "waf",
    "content": "#!/usr/bin/env python3\n# encoding: latin-1\n# Thomas Nagy, 2005-2018\n# SPDX-License-Identifier: BSD-3-Clause\n\"\"\"\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\n3. The name of the author may not be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \"AS IS\" AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,\nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\nSTRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\nIN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\"\"\"\n\nimport os, sys, inspect\n\nVERSION=\"2.1.6\"\nREVISION=\"d84a728949459c912824e2e01d861c89\"\nGIT=\"f233bf2c8d1a89b06541a9be576a8064d0b6af38\"\nINSTALL=''\nC1='#>'\nC2='#='\nC3='#6'\ncwd = os.getcwd()\njoin = os.path.join\n\n\nWAF='waf'\ndef b(x):\n\treturn x\nif sys.hexversion>0x300000f:\n\tWAF='waf3'\n\tdef b(x):\n\t\treturn x.encode()\n\ndef err(m):\n\tprint(('\\033[91mError: %s\\033[0m' % m))\n\tsys.exit(1)\n\ndef unpack_wafdir(dir, src):\n\tf = open(src,'rb')\n\tc = 'corrupt archive (%d)'\n\twhile 1:\n\t\tline = f.readline()\n\t\tif not line: err('run waf-light from a folder containing waflib')\n\t\tif line == b('#==>\\n'):\n\t\t\ttxt = f.readline()\n\t\t\tif not txt: err(c % 1)\n\t\t\tif f.readline() != b('#<==\\n'): err(c % 2)\n\t\t\tbreak\n\tif not txt: err(c % 3)\n\ttxt = txt[1:-1].replace(b(C1), b('\\n')).replace(b(C2), b('\\r')).replace(b(C3), b('\\x00'))\n\n\timport shutil, tarfile\n\ttry: shutil.rmtree(dir)\n\texcept OSError: pass\n\ttry:\n\t\tfor x in ('Tools', 'extras'):\n\t\t\tos.makedirs(join(dir, 'waflib', x))\n\texcept OSError:\n\t\terr(\"Cannot unpack waf lib into %s\\nMove waf in a writable directory\" % dir)\n\n\tos.chdir(dir)\n\ttmp = 't.bz2'\n\tt = open(tmp,'wb')\n\ttry: t.write(txt)\n\tfinally: t.close()\n\n\ttry:\n\t\tt = tarfile.open(tmp)\n\texcept:\n\t\ttry:\n\t\t\tos.system('bunzip2 t.bz2')\n\t\t\tt = tarfile.open('t')\n\t\t\ttmp = 't'\n\t\texcept:\n\t\t\tos.chdir(cwd)\n\t\t\ttry: shutil.rmtree(dir)\n\t\t\texcept OSError: pass\n\t\t\terr(\"Waf cannot be unpacked, check that bzip2 support is present\")\n\n\ttry:\n\t\tfor x in t:\n\t\t\tif hasattr(tarfile, 'data_filter'):\n\t\t\t\tt.extract(x, filter='data')\n\t\t\telse:\n\t\t\t\tt.extract(x)\n\tfinally:\n\t\tt.close()\n\n\tfor x in ('Tools', 'extras'):\n\t\tos.chmod(join('waflib',x), 493)\n\n\tif sys.hexversion<0x300000f:\n\t\tsys.path = [join(dir, 'waflib')] + sys.path\n\t\timport fixpy2\n\t\tfixpy2.fixdir(dir)\n\n\tos.remove(tmp)\n\tos.chdir(cwd)\n\n\ttry: dir = unicode(dir, 'mbcs')\n\texcept: pass\n\ttry:\n\t\tfrom ctypes import windll\n\t\twindll.kernel32.SetFileAttributesW(dir, 2)\n\texcept:\n\t\tpass\n\ndef test(dir):\n\ttry:\n\t\tos.stat(join(dir, 'waflib'))\n\t\treturn os.path.abspath(dir)\n\texcept OSError:\n\t\tpass\n\ndef find_lib():\n\tsrc = os.path.abspath(inspect.getfile(inspect.getmodule(err)))\n\tbase, name = os.path.split(src)\n\n\t#devs use $WAFDIR\n\tw=test(os.environ.get('WAFDIR', ''))\n\tif w: return w\n\n\t#waf-light\n\tif name.endswith('waf-light'):\n\t\tw = test(base)\n\t\tif w: return w\n\t\tfor dir in sys.path:\n\t\t\tif test(dir):\n\t\t\t\treturn dir\n\t\terr('waf-light requires waflib -> export WAFDIR=/folder')\n\n\tdirname = '%s-%s-%s' % (WAF, VERSION, REVISION)\n\tfor i in (INSTALL,'/usr','/usr/local','/opt'):\n\t\tw = test(i + '/lib/' + dirname)\n\t\tif w: return w\n\n\t#waf-local\n\tdir = join(base, (sys.platform != 'win32' and '.' or '') + dirname)\n\tw = test(dir)\n\tif w: return w\n\n\t#unpack\n\tunpack_wafdir(dir, src)\n\treturn dir\n\nwafdir = find_lib()\nsys.path.insert(0, wafdir)\n\nif __name__ == '__main__':\n\tfrom waflib import Context\n\tContext.WAIFUVERSION='1.3.0'\n\tsys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts', 'waifulib'))\n\tfrom waflib import Scripting\n\tScripting.waf_entry_point(cwd, VERSION, wafdir)\n\n#==>\n#BZh91AY&SYJ2\u0001\u000bWh\u001dfW\u001aA>q@x\u0018c\\{w(#>\u0010#6#6#6#6#6#6#6#6#6#6#6#6\u0014#6\u0014#6#6#6#6#6#6#6#6#6#6#6#6#6#6#6#6#6#6(-}7=ZC{{R{r\u001aMށ{X˻|}\u001bܑm۫-ۛ]͕m\u001e\u001ex1{kqd=vt]vPեv\u001a^ۗw8.[fݴ\u0001`o6|*ύg\u0011n%\u001dvl+Q]\u0004Z`@\u001d:h#>)s\u0016#>0\u0005#>)@U(D#6֮΀dV(auWn}o#6\u0001\u0017#6#6=ԗt8;gF\u0005DIJ\\Δ+\u00122Jh\u0007@HSfۺrg\u001ex:7#6e\u0002ai*.}\u000fk_]ZV6ӹ\u0006\u0007G-y-^\u0007\u001dR\u0014\u0001}|TYJmì\fS}y{S]֫\u0006Jw|U=+t*hh\u0014#>*J#6*\u0012IUk*\"z=u($7nUB{\u0004\u000e\u0006\u001d_z4d:y>:PJ\u0017a|#6#6#6#64#6(#6#6\u0003}ekN\u001f\u000eUK\u0006\u000en\u000ekwsn쾷#M#==\u0001#=\u0019rݨ6tk&vk$Dq#>\u000eZGV#6(#6\u0002J#6#>\u0017܏m@@#6\u0005#6\u0014\u0002@(\u0006dQݎ!T#6=}}\u000fϯy^mh5@\u0002n#=E;`m\u0005#=_U[\u0005@\u001a`3'J]\u000f;]w[wb5c=z7޾;\u001b\u001dy/m\u00156vA]^J7{ϸ\u0015v4\u000fvST0n{`E\"\u0019\u001e]Nܮ4)\u0016ֺLU\u0007\u0010튐=d{}z {۳-ij\u000e\u001dk|)\u0004\u0004أL=ۻۺ\u0013q׽veqvu\u0001\\o}o}de>/\u0001첾SOnL{Ǽg^xq\u001exx#=;\u001fe6}ۮܹ\u0019wwփwμ\u0003CnʕUև\u0013}׾^\u001bvz'f/w=ynkMso\u001e*ncx<M\u0017q\\ͽ*`wM_sw-\u0002'9Ǭ\u001eG[;A}}wq띲Ӱ.sU\u0003F&CW]\u001bvZ^m׽WMJHz#=$C}tM_vۼSS.:#]vwemjl\u000eE.KswSeޛnpwB\u000f{;;n^.YnJ]\bu;\u0001m.!\\|sjw9^#>(\u0015\u0018j`jҋ{\u0002(95m#6#6\u0007gr\u0014\u0001#>U\u0016{#6qlPުy\u000e\u0014(fM\u0007\u0011:}x4\u0018ͷ:B\u0014]#=gE2f\u0014#6)K#>ny7bV\\]\u001d\u001duBͦ¶\u001cQ.\u0001%#6\u0002{uee\u001bڮVnwizF6r9|\u0001^{J'\u000fJ\u00184#6\u0003feݜ-o\u001etee5܎٢;mXM#=@wl\u000e/.`#6f\\ɍx#6(&*\tZ1jYZ.\tݦ;uv5jbw@\u0003͘KnOlVM:\u001a\u0018\u0011O>n;+\u001d}\u001d8\u001dHzfu0>u/o[>v3WڢqR\u0019޺\u001e\u0002Y\u0017GZz\u001d#>#6:<@vj@aM۳Nq^5\u0005@d@vD++r9\u0011u*\u0014#>ݝp1>\u000e{`\u0006\u0001\u0007\u0012ZQPw .\u0011*](*/*ӓaݖu{\u001fv{mK*llVb[#=py\u0007\u0007ŕT\t2ۣy\u0006(JWvnwYvXuҮi:ӭ\u0003\u001d\u000eA2\u0006\u0015f#6ݪ\u001b{kT(k*` #6#6\t\u0006N\u000e'm2\u000e.܁ݞu{oy(gF{kq^v0\u001b;\u001d۷UjԵ]\\N@\u0006\u000e\u0015S\u0007n;2׮:\u000eۻ(({2k\u0010\u0005\u0015\u001bwj>yiOcJ\u000b.+c\u0011i3lu\u0004g\u0019 t\u0003UȫÏr\u000e]t}nZ\u0014nF*v\u0007tyc\"\u0011>>4zt#6s\u0003+]q4vwVεϷs&s7l-\u001et\f瓧)E\u001e\\#=پJ3Ʃ@i#=ᦄ\u0002#6 \t#6@\u0002i\u00024cFjO$zSi#=444\u001a4dib\u0007Pz\u0010Jh\u0010B\u0010&A2#6#SL#=\u000fQ6\u001fSOIlGz4#6#6#6#6#6#6#6#6H$D\bM\u0002\u001a\u0004j#>SL=O'Qқʞ=F&\u0011#6#6\u0006#6#6#6#6I꒒!\u0002dbzISC\u0011P\u0019\u001f\u001a\u0001\u001e#6#6#=\u0006\u0010z@#6#6#6#6#6#6\u0001#>D\u0001#6\u0004\u0004b4\u0013\u0013A1\u001ah\u0013MO&\u00194M\u0014S\u001bSFF4#6#64#6#6h4hA&\u0010#6M4\u00021\u0004iQi6S\u001e\u001a4Ѧ#=\u0001\u0006#6#6#6#6o*;\u0001w7rZMo*UUnf\u0013C\"ڪˤԚe.Y\u0010`\u000bP\u000b#6a҂Wb-b&**z|c\u00128\u0012\u0013\u001e.%D(\u0015c1fg\u00014p\"\u001c\u0012DG\u0005q͏`#6\u0004SРy*`\u000fx'Јp#6?@#6↠h\u001aOD4ƃbV鬒۳fGWyi娻9{QQx]<<S3xVbb\u0007Rq7\tYr.I!4{\u0012swO%&K\u0015,6\u0013\u0010(s\u00111bs\f0\u0007\u0011V\u0004\f#>\u0004)E\u0010yUM$ڊک+r\u0002\u0015\u0014P)iEDS\u0002D\u001cU\u0011PCKn?\u000eRp6\u0002m\u0016%`\u0004\u0019\u0006@bTH\u000e@)@0qTLH%J\u0015iDiV$8@\u0005\u0001^\u0003#6\u001d(c\u0001#6+kn0$1cR)F\u0007\u00050hz,Dl#6x/t#6dr`ԫ#6\u0014\u0003#6\bw¢)J#644\b* \u0014\")%+\u0004\u001bj\u0018E\u001aI\u0012i\u0019\u0012K\u0019$S)\u0019)I\u001ai\"Df$\u00122\"\tC l,`\u0019I \u0011\u0006 (L\u0019B&`bTZf-\u0016&66-\u0011\u0012c6HThAFH665Um&Ԛe\u001ad)&b$HM$0\u0004\u0014I0T8Ԑ16(ҋe6Q\u00021\"mRĠ\b\u0015\u0013R\b̔FH*,lQKXD@\u000b4IM\u0019h6\u0005\u001b\t\u0010\"Fɤd#>\bY\fD1IFƆ&ج,I-\u001bI\u001a4cX%\u0014ьmD143cEl61e3#=VS\u001ab\u001amI\u0015\u001aRC6PICI+\u0019E\u0019*b2\tH\u001bE6(c\u0002)KE%UmmZ6Bd\u0006\u0013lD2@U%`ZJMaHl\u0012fjee)\u0006M@IFi6m-\u0016Mfe\u0013FPQ\"F6\u0005K\u0011!HQ\u0005\"XEFPP\u001a$6K2\fcA b\u0005\u0003%\u0011\u0011B\u0001f\u0018\u001bI$dEl\b!b\u0001RYk\u0012\u0012a0Ҁ)\u0014&,3D&4bK#\u0019425&4XL5$2YI\bY,ԒRdi\u0018M2\u0014)P\u0018bm%\b\u001bI#>QZ#>$\u0011D̓6$\u0019L1l&)LR$VLI$)-\u0005\u0012m%\t\"QH#>\bQ\u0004ђJHAlY(2lD\u0011\tLe6#>BbIc\u0005\u001811LT(4$l\"\u0011\u0011)iHI43d&\u000bM\u00194h\fS\"L\u001a&c \u0019DXlm(I0D\u00116#=\u001b!RJ#hM\u0010)\u0015\u0006FM\tH), 3*RƉ45K\u0015)J%\fȩ\f\"Y@djj\thD`de L\u0006LAHАb\fe\u0014\u0011L)bʊM\"jiD!\u0014$\t),b\u001aE111QSC\u0015$\u00166\",i\u0003\u0002lIi-&4llFBl3@FZ2e\u0019+D\u0018#\u0014d̓)\u0013bԩ\"͊$,XLZ2\u0015\u0014F2\u0018\u0016\"&QI&$қF#b\u0012!6blQ4m4#>(2\u00110)F\"HHXl!\u0004)Li\u0018C%J1Y1a(H\u0012TLd\u0016Eh6SFDС\u0010f$D$I,Vm5FM\u0018\u0013I5-M)\u0005\u0012SMi#a#>fDV&\u0019e3i\u0018LX\f\u0018)\u000bQ\th,\"$T#=j4X2\u001ad%4$Y!a+AKe,f\"2eK5\u001aJQE\u00191d\u0014E)B\u0019\u001aFdI\u0010C,\fR\u0011]R֭fLalL\u0014LRR\"\t\"\fZAaf)a\"d2ZCDd*\t1$#=6A\u0013( $\u0016d\u001aXF&Ch5Db4`DB&((#`Q$4\u0001D$I\fT\b\u001ad0H\u0018\u0011b%\u000b2LRfĆ4mk\u0013#=c\u0018\bM\u0004\u0005$\u0013#=\u0006ɴ)6+2\u0012_:BXT fٖefѦa[&hSi\bM\u001ak1bJ&M0\u0012RZY2ҁ!&&ő\u000630Q#>ʚ(\u0018ƕElIic\u0010SM#\u0018(\u0013\u0019Ȓ)ILHbВY\"\u000b*e\u0012#>fHDVf\t*e+,\u001a)ѩ46YJl\u001aL(ҥFV4\u0010TjZDm\u001b4\u000564ڕ۹Y\u00111\fQ JJ 4ȍhlRF23M\u0004hŔДJ24Ȱ\u0001(\u0015%$&2A\u0010E#bјI\u0018b%FڒJ`LM1C\u0014LF&C)\u0019-\fIJ(\u00034 ,ƈ*(1#>Y1\u00180i a#2ƂS3Hm$\u0013)4,$3M\u0012jMjIS\u00126\"\u000413\u0006fd#%,hZ5)cEF$P)TX3\u001bQhcFb\u0015E\"\u0013R\u001b3K\u0014\u001b#6l\u0019ɐ\u001bd\u001aQR*6i m\u001bJ\u0016*ɖJ)cK\u0019F\u001a*DVɦZ!\u000b3dZk)Z\tlk`!E\fX\u0018ɱ\u001b\u001a%4L!صAE0-&j֓\u001a&Bmԕ\u0011X[\u001bY4jSbSEj5b**DDш֒*ѣ\u0014[Fذ2Ѩ\u0006U$dі1\u001a5bU1#=`(\u0012\bƄj2\u0004,[\u0011ThbV-I\u001b\u0015imT5\u0016DeE%!6S5%4ͩ,I\u0015, \u001a-\u001be,IJ[5K4)R̖Գ\u0012cmdM)djQ@Ml4ʉ6ɶk(5!؁1i\u0010(%4TT$l\u0006I0)\"D6$DDI2Ԛ#I,)\u0005\u0013ITm$E%&K\u0015bADf\u001ahږfR\u0012k\u00022\u0019Q6e@&F\u001bMbJ\u0005#=c\f#\u0006#6l\b3($1!\fPQB\t$R6#Dd\u0018I#\u001b\u00194$HJ4T,Z\u0012#6sI\u0012$L!5\u0016\u0001eHٔ(&H\u0012\u001afiMda%\u00116\u001a5\fؚQƩ6\u0014 \f$j)\fPIEJIeLi&&H4ز\u0013*!Hc3Tl\u00152j\u00050HF2\"IdHP1iS\u0013\u0018HdL\u0012X-I0M%X\u00166`Hik\u0012\u0019L\u001aeEb1\u0015\u0018\bi4EI\f4E(F1I\tI2dC$V4k\tTZH(FePBLJe\u0016#\u0013\u001b\u0005fĨ\u0014H4\u0018Q%$E\u0006LQ&T\"Dl\u001ah\u0014k\u001b\u001bhXAY4h-E+\u0006f2\u0012#A0&cm63,MQhѤ!FbMBT%\u0015U0V-*h\u0016A#=H*K\te)@Ic\u0004 (k\u0018c6\"\u0014S\u0011%K-lk\u0011KPLE^&M1SKE%bm%\u0015FP֒6bRMB-\u0014F5M\u0015\u0006ME\u0012@ѕ$R(ġ$\u0012\tJ6\u001bfXb1\u0016MIHj ̦d 5L`4+3\"iH\u0016Jj#$Q\u0018ڀ$%\u0014YJ-%\u0018Չ-X%ѱjDKh5%\u0016)\u0012M$3\"\"c6DbPe3Z6i5\u0015lm\u0012j\u0014T(FT֋bڋE\u001a\u0018\u0005T[FY\u00115F[EbQB*\u0014fJabV\u0005E\u0011Iэ\u0015&\u001a2#h2+HVMT\u001b4\u0003\u0004J\u0016F(بڥ`\u0011)\u0013*\u0015\u0013\u0012y\u0018\u0015\u0014)E(эQC1E\u000bjM\u0012IV-53Vђ#\t\u0010\u0012T,\u0010ĨHK$Le\u0014TF(\u00166\u001656MRfdʖ\u0006h@*d`&\"\u00112Qm2tïp\u0002\u0003(z>\u000f#=\u0003r#>WFۿ]6\b\u0006y?gkHA\u0014\u001c]\u001epӿ\u0017xя;OǗYZ=\u000eGf\u000f\u001bÖ3t3$vʇfOFuB4 ï^K~V\u000f=z#bZFkռycA\u0005S:Sv{W\u0018Ք\u0004\u000f 2\t$\u000e̓34Sc\u0006(?꼍\u0004:34\u001d]!\u0015Z\u0010F]?%>87R\u0005&J\"\u000f\u0007gk¿\u001fgAZ?y&$e\u0004\u0017\u0005#>`_ZaFVm\\yO&Xu#=Zy2َ%?\u0005hk\u0016\u0016}~*<O\u000e\u0013#J~p\u0018GRvngAgX58S\u0006y\u001fȧ#e\u0018sk/\u001a0F\u0018o3{rQ\u001f\u0018i\f\u0015Bŝ~\u000f\fopS=X8<̍\"?4\u0014HA4\t!\u0011\u0003}\u0019^_:&\t\u000e\u0010-eҪe\f<7\u001bo00m<̣S͚pG\u0011K7\u0004{گٻ,I0>4\u0012vZgJd\u0011\u001deE\u0014N5a1U\u0006B\u000fH'IÓ\u0007#=-Y:&W\u0010)3Ɲ瘺#\u0002ҁfB~_Xu9{3#>\u001d!bj#>Br\u0006G2\u001cWz\\ػ=o!?Vȫ:)ϭ_kd\u0001C>ʛ8\u0017IGz(3WX) Aߞ\f\u001cP<a\u0006#7LW#\u001c\u001f\u000e7[vɼ9\u000e{0)Mǋ\u0019Z\u001dHc\u0006b,U#=\u0006OIh\f2fX[@\u0018fCe3șh\u001a\u000b,=$\u001b\u0010[Er\u001495T(\u0012LWo韮q\u0003Dيív\u001a(tEL\u0019q*BUI|>G0)+|@j\u0015\u000e\u00140R#=0bGo٦ư'(\u0001Oӻ\u0006a;!\u0019fu94\u000b_(NrϕDaZujtk\u0016\u0018\u001cFޛ\u001a!\u001a\u0003r#>\u0015)F?nus_v6BQ>U/{C\u001f4+#>}9HF:Ѯ,|̠\u0014|ZVp6ϗ\u0017f짇LmANj~\u001fP\u001f/j,Л\u000eÊyB#=Auԟ\u0002+\b0\u0011s1S0dЛ\u001e\u0003\u001fJg\u0013^}16lEE\b+*E?+>3Vn;1!l2\u0016\fzPW砥eGmˎӟ;CO\b;_||\u001e~%}7;ݭU-w\\\\\u000e\u0015s\u0002)Β^a\bS'\u000eX\u0013#>d\u0015m?;w#}gC4\fv@\u0002C\t)S\u0013\u0013=\t\u001b9m\u0018|'q\tFG[\u001f6(PbS톍iY\u0019\u001a#>\u0019\u0018I\\N\u001ffFr\u001a\u000e\u0019Q \u0012}&54g癵k3ЁQ\bCdρu(e?$\u0005S!-`\u0013g\u0012p\u0019\u0019nv6\u0006P}Wg2٫\u00130&\u001f\u00058lj00XG'=rL徹6*\u001b9g%'GN-C\u000f\u0017\u0007\u0019A=H=7U9\u000ee4ڎ&'qEtTX\u0014wQGnG\u0005pWrˤ}V\\\u000fDD\u0006q#~\u0003Z8gLg=%\u001bh2VauK#>uV!hcNi\u00170c#=ab'XHO8\u001f͗Yÿ0OwL^ M\b?_(M\b\u0010a\u0004K\u001fTL7\u0012\u000be\fY\u000b`\u0005R,PNu:-A՝YOk&\u001f,NP\u0003}Έ\b\"|U\u0012U\t_o=\u001e[4WC\u0017\u0007z2\u0016Bz\u001574\u001c5$Ha)B4A8хT\u0005\"O\u0006_\u000fR~!0\u000fAj4>W zY٪5نc(Gz\u00145T\u00020?;-%\u001c\u0017Q_\u0010<֤D\u0003\u0013\u001c\"\u0018)CU\\6O*\u001bYAͩQ\u0005H\u0014\f\u0007W叵+JPnb#>JzB#=#=W0=dc\u0003=:WAi#>\fi]p\"Ƞa)<\u001frJ4\u000bkkmE8\t9Gb*\u0017P\\1#6*<\u00173)\u0004zWm11M\u0013})X6v%f6w\u0011esх;yc\u00051\u000fDb\u000baIԬ6s\u0007!\f&ZH#\u000eXXݬnȴ\u001c\u0006pyMzf>?^=q\ta\u001dB\u001e^%u(xvǾ\u000fb\u0019L0i\fr1uyntRs&o)⧎#=\u001bUTO\u000exvxO\u001f'ڦpuRL\u0010\b'\u0013yfr[|tlaܘХ\u0019_zQLr1}yʌaw&k\"^\u0012B\u001c+\u001b1o\u0015\t\u0015#=N[\f\u001f:o\u0016w)\u0006#>(\"\tRD\u0015\u0002'\u0004FEIi6\u0018ِ\fQ(\u0010PEl5Q(?Am7f\u0006zW\u001dpU#>ѭOGp׺45\u000e]+\u0017\u001aQn.lD*Lrhj\u0006aaInpq@QnC \b>L۳\u001d\u001dFgT/0P\fs=\u000f#|7̷\u0010j\u0007t{+#>\u0011rAn0M\baFv'Ftj\u0014멘\u001f\u000bq\fu(>,1QqHc\u0014%]\u0011Z#>&\tgdLK@`H(S\u0013K,c#6KȾu2\u0005L!Ҕgsm:QwV\u001f3Wmt@7kŵ\u0019ŦF~|U/C\fAMccT˜\u0014&b\"\u0005v&Avw/Ʉ\u0012a!fyrvr#>J\u0003sO\u000f؝7J(x !ݗ\u0006FƲY\u001b#=,2KM1K`\u0018i\u001bՎ\u0012#=\u001aI\f<7\u0014RI!-\u0001M@S\u0019!qoW_-s6\u00199G MV\u0007\u001c\u0017\u0017pd\u0005خ>\u001ba\u0007c9t\u0016D.sG6<o/tÈ#=3k#6\u0017&3)R~.x\u0018\u001ad*\u0006l\u0017nib=+8 A<Wal\u0002P%`V\u000eǅ`\u0018!)7Qb~\u000e\u00153G?\u0005uOR}˦SS4?ә^7(@Li\u0017ZkWϗ?5\u00153?Z{y\u0007B\u001c\u000b\u001a\u001aMTs@b\u0005#6)$j?m\u0012\u0018\u0005\u0013/\u0015ZzҰS{0寽=rc#>:A\tt^i6\u0001{ç\u0006#>\u0019\u0007<q8*LYX\u0018\u0010\u001dpl\u00040ۃ9!׿ild\u0016k[܇0Cl>F0'1i#=oV*o\u0019@u#6H\u00033(#¤=Tv*WNǭ`#(\b c90\"2V\u001fPđڽ$~هK\u0019`X\u0006n\u001cn#=X1fYyt>yG\u001f<\u0016\u0001c(\u0002\u000f\u0003\u0007(\u001b\ts_\bTxE\u0007.w\u001a\u0013\u001f{_BȼfaqȍUՎTGZq\u0010RwwQIzI8F\u0013\u001fZ\u001es`BJy\u0013D\u0016\u001d\u0019'Eٜ\u001ec0?Kt,SXF\u001be4\u001f/۰#>9v].SC䝤&#7\\QB%`,L}\u0017^POGx57K\u001alf-\u00138(KSQs_CM0-L\bRf\u001dwIWc$Gh6&Sq\"\u001a\u0018\u00173sfNdO`<q5̱:N9\u000fx9j5<S1\u0010#\u0016~4JMU1\\_ɜ-ZzT\u0014&Z\u0013k1(qq[\u00147\u001a)Ga+JQ\u000bd#N\u001a\u0007'N%\f#X^u9Y+*/y\u0002\u001fB\u0014,]\u0002+:b6\u0005{I28'kB\u0014,8}\u001a6_/}95\u0013_\u0013[\u0018X}rV~6Osя\u0018-9#F,1ƻߘ|#\u00166$\u0015#>^M2_!\u0006ʽ\f#\u0006\u0017w,n&\u0010b|29N#bR\u0016\u0011X4b\u0014DDJ$2tEf\fa5\\=&kuՉ\u0014Yȝr~\u0011y\u0018i\b\u0012\t/o%\u0016_dbU\u0014_*:烟\u0016V8p\u00026\u0015Q턐Qْ\u0017\u001ba\\s0ٽ\u0018D*_\u001b/*fSa%L\u001a~Mul15\"\u001blt)\\64ѥ=\\aˁm:;|?VQe/\u0017\u0010qKP\u000ePPQ\u0006}m\u0006U4\u001fĬ6+\u0003r\u0014I\u0003=JqZ\u0010c#>\u001d0$\u0014FD@-n|#>~κg\u000bt?M5v4GRA\u0019\u0017\u00172\u0001[o2yOv\u0005\"g-\u000bԌ\t~ t$ҢG)V\trS\u0015*kb>T)8r,{hᯱϙAlR޳korت!\u0006\u0007(dr\u0003K^\u0005\u001cT:Rm\t\u0004}X\u001c箳{\u0019\u000fX\bI+E\u001fN\\z?w\u0016|\u001808)¥\u0014{3Y\u0006\u0004Ba9xMU~Q3㚇[%lx\u001eY؅\t\u0018IW*ˊ.|js5ڛ?;}^2|\u0010=RO+\u000b\u001a\u001bNWvT\b\u0017̤IzM\u000b\u001eUN;5\u001f\u001aą+t벰\u0013\\'\u001bloI+Yƌ\u001ax1\u000bnL&苗tKpA^Ĉ\u0015!RE\u0010+C$\u001e]-*JUGV\u0012\b'G?\u000eG\u001fV\\06u0Q~ 6g݀o|C\u0019ʅoGK&-wǖ\u0005gό*m\u001dtc>/\u001bK\u0003r(!\fQw\u0005b\u0015PxU;\u001cK't(\u0010:E\\\b\"Y[qV?\u001c9R#=fwj]k\u000e\u0018}mܐْ\u001b\u001c3̅\u000e\u001dp2\u0014*(\u000fP]ű}\tLuݓ<L`G^t|uP\u0014\u00030¡:]SHEh@T(6\u0019g*\u0016\u0006=`\u000b6fo1\u0001\u0019(^\u0001u50I(S-Y 7S%\u001fJ\"4P\u0010Ot깭L䞙Py { #=\u0004\u0007 8>\u0012\u0004\u0004>&@v\u0018\u0012mu\u001f\u0003ɔ(#=1^\u0011K\u000e@]\u0011w#=/M\f#><CAgN8\b\f,\b_ŕH.Q\u0003<ǯe}}m[P\u001cYA\\\u0019=B#>fҬJR\b \u001eoGxiQ^_KY\u000b\u0001Y҈>q:s3b^<AK}\"?ɽ\u0012\u001f'ႦxmrvYN{||W\\?V\u001f~-8!!!uQQ(J\u0010agdCN3go}/b_~{&~A(\u001fP :\u0004\u0003+\t_/\b}O#=8\u001f\u0019õ\u0010(92g4tc\u0017?tv⭜\u000f'#=T\u001eŘr͓v~\fQكƼ(GxS΋nP6\u001fJ25W`B;~6\u0007gf=%\u0017NIHS?_\u0010@\t%\\\u0010A홽Y6\u001bfZ*=JZ;xVg:&\u0007\u0001lbC\u000ehݶIjZkX&rqt>\u0014\u0016`9f\u00152р\\0#> \u001cQHZ\fGB[]\u0015NWt8\u0017I2]2q\"7jeM\u001fd\u000e%$>\u001fbwxnŬ\u001fZur\u0003@a\u001di($OW؏/]w$%T:l=JKL{*=Ov!cTd/TrMC{\u00068\\\"0c\u000e\u0012\u000e\"2f.{-(r\u001c\f-SS#>iz\u0005B)}|P_\t\u0001q\u0018k\f\u001eが8&\u0004ɒJ8f\u0014a/+3]I\u0014\u0016\u001d(3\u0005\u0010'ԓv׿qD\t##6#=\u001e%)\u0007\u001fi\fxG\u0014\u001a%u#>Ò';ّ\u0010\u000fEԕWZ>#=\u0019#6<S,΁U\tĻנ͙zԚb9p_,SQ\u0018';b \u001b$oHI\u001b\u0016\u0004R\u0010{K\u0012C\fB *C*\u000e\u0015bAMnݮt]\u0002\u0010I}9($~\u0002H\u001bT+%0N@d(Eb,>\u0010\u0019qƠnė4O1ݗ\u0007uC\t3@Zʈ;s/\u001e8\u000535٭Tc%o?Qa;6a\u0014U\u000b<\u0010'3-%kTo'\b#=(,2I)\b;P\u001c$tf\u0005u#>:2Hwh\u001c\"X{qâКԅn\"EnJI$dm\u0017~TWuHS2\u000eL4luѦM4 DQQƪ8D6\u0006h;\u0004q(v\u0003rq9#6\u0006\u0014\u00115)ԙ+x\u001c }쁔@X\u0006\u0006-xYw\u001d``dLdM$\u0002\u001fӧ\u00106\u0011I~^5\bݹ\u001aNǚ2\u0016RN3\u0001U\u0015/~h-\u0017}Z9I#6 I\u0012wHiTK\u0016Q( $\u000eGGĳv q|Vvjڟ+/\u001fFݤb\u0003㲛w8}2.OU}(-\u000b*r\\Psy1Ŵ\bR}:r\u0003~g#pL#69\u001fZ1g#=#=\"1x \u0013=߆V\u000e@ٵ\u000fO\u001e\u0006k;J\u001dX}\u0012r\u0012k׬s|AlH\u0012n\u0007j\t'B)]EVk\u0004T.ioYϔG/?ve\u001b;3zR_sJ\u0013Зls\u0005sAdgĕD;1X3\tdPL\u001dA\"(>=:\u0004\u001a\u0019\u0007\u0002WW[\u0010!%G(}/XFg{NYPhd\u0005r\f^}f3~/VQ̩$U#>\u0013rlqA\u000f\u0002.Oc\b\u0014\u001dЦ.*.j?k4\\xfiXk+q\u0016_*\t\u000b2YHV2_2O\u0013ߞ/髪f|\u001c򡞰X\u000b\u00055bnΖv\bj}g})tj7FG]%hV{>^_Е\u0016w\u0018~.\"\u001ef9\u001b\u0007^\fcZțk|x|yRUjE GȆ⤁\u0005\u0016r\u0015`\u0012\u001cE\u0006x\bb;>\u0013\u0006+ \u0019\u0003)_<RדQ#6|I%\u00017Xsv\u0019ܪRǒ'{>k}\u0015ҩ{ڔ7?FUfˋK3ōm~/v*\u0011]W}ߵK璟T\u001e?Oϣf?/<\u0019Pt!<@dψs\u0018D`ݕ\u000eaڐɖ\u0014# ||\u0012wU\u000fa$\bдt(}rce9<ü7/0gΛ.`;&6Wyo<[\u001b[J\b.N\u001bjUM|\u001da>w#=gshhF\u000e\u001dFs_\u0016+ݻQ`S\u00135ĩ%Zl3\t\u001aD\u000f\u001e?\\:\u0011\u0007\u0004\"]TV0'\u001acUiabR63]$;\u0014LMQ#=ƣO$pVd\u000b\u0007c\b2%U_-%\f09;|-|u\u001d߶5M/ո|\u0018a?\u000bXJ\u0011#=[sX#~r6:W]\u0018\u000b6\u0011\u0019:OJp\\\u0014 u\u0015\u000fp7s#=t|1>6:]J*\u0018=fSņ\u000bFַv\u0015w¿55qUj<?W\u0012^U>tdvte&\u0012sxy.6y؛#ubj8\u0013Ju<\u001a\u0013Q1o\tE\u0014>f\u00198X\u00112\u001615\u0011|9ƕ\u0003Q7Y2\u0001\u001bt%IAA\u0010\u001c\u001eW/)ZS,H[b)\u001bhgWm#A;L\t\u0019xue\u0001\u0001{wgp\u0005UAug4+n\u001d\u0013FP͖HP\u0003\u0001ƪq@s0.X/s)3xTP5ZgW[\u0014ۆ\u0015\u0014k62\u001ex0rFʡ#=\u000f3\u000b+\u0004$^I\b\u0010o\u0007,Uyr\u0016,\u0010&ʓ\bQnT~\u0018\f8p!Nt6)E\u00145v\u0011A\u0019*wGK\u0001%Y*frEdR0\u0013s\u0007}mGkT9qbEg\fQ2 ϡ~˘7\u001e򙑾g\u001aWu/9\u0007#6o\u000b|v\u0016vB_|$_xСe\u001d#\u0015wgǎ=I\u001da\u0011\u001c\u001b凲 \u0016QZ~2e\u0005\u0003\u0012XOT(#>u\u0013ڠ\\C\u0019:^BhPhDM;\"@S#\u000333\u0013QPrDK\u0012\u0007\u0005k\u001beH\u001cy\u001ag)SJmsPMNuo?X䬸?(fm\u0015\u0012\u000f͡I\u001fJ.#~ŝW\u0003\u0017&\u0005+\u0017\u0005D\u001d\u001dH#>#_P\u000e*\u001e\u0007\u001b)k9mTTT\u0015n\u0012E쟛Ttja\u001f\u0014\u001f3\u001a40ē!C;\u0004^6:i\tx\u0012fe\u001c>\u0001U-_&d\u000eC&C\u00108\u0015)w@;&RF'(ob1*_\u001bZs yOłZa\u000b\u00029C#=Z\u0010uH}%\u001e);ʹX(+\u0002}\u0012G,\u0017E2\\.v\u0005J\u0002M,upɍF\u00183z\u0002C\u0003 \u001f^+,.m\u001dl\u0002\tG4xú\tW\u0010<<dG2l)7h\u001co+j\u0014:Q\u001a#=/\u0015XdId9\u0016c_td)Π\f\u000e\u0002ۛS@\"\\\u0007Т$S.qEQKBvid@LJ\u001c)\u001a(sXWMOf?Oԁa_G=\u0013\u0002.J\u0014}T#=+5d.);\u0016Gu/\u0006G\u0010Ѥܢ\u0013ilAnu\u001b{\u0003RMws?gKޠŅG.u\u001e\f6/9$k\u001b0#>QvA#=/g\u0015I-#=#=\u0016b\u0018e\u000bnC:*\\E\u000fy\u001byMm&\u0014ب5=#}!\u0013>\u00032\u0004\u0015ǓU\u0017G\u0003#6@LI~\u0006_&\u0004^\u00129NH+*\u0001-\u000bkڴ\\\u0002'lXl50 JTR۫w\"\f\"68N\u0016h]{NS9طeg/_5Y\fɲS\u0014;%\u0007\u0004l)JpT*Sb!LDs0\u001d\u0019֏i+?av)Heqۧ9\u000e\"\u0006,7\u0010=\u0005DzYJn/B\"ʪWNVp9=+\fF\u00196fafJ+rEL9hj^Fp?]sfc\u0019\u0007|.QCT\u0006Ph#>nx2\u0002r`?i\u0003BZVE\u0015\u0018A\u0002j7Ax\b'7;?A8gf!$W*H\u0010=\u0019.4Z#>ID\u0006W(R_\u0010\u001fr\u0015Y\u001dlaoI9c\u0005z6t\u001fL\u0011\u0002 T0q4\u0011wð\fK(WU\u001d3?J8Ӧc+A\u0006V\u0019jypXx,\u001bKXH?j\u0017)2&Qb^Q\u0016_t*\u001fnJGJ{\u0014ILjS\u001f\u001eUfVFH4\u001f\u0016\u0013kg\u001cCl*4k\u0011\u000b\u000b(-}]n3xCayh\u0015T\u0011 \\ԟ\u0007CI/v\"zb +N/ya\u0006r?\u001aN\\z40FZUM{\u0005\u000eUy\u0002A?̑]Iי=|u Ds\u0012l^J\u0007/vʩ\u0011x\u0001\u000bN7\u000f\u0011|\b\u000elۿ|$=/3\u0006#6>#>\u001a\u000fFg PЎ\u0015U\u001d'\u0006$$-\u0014#6I N\u0004-E=*1uI\u000fTbc\u00121.Ε!+d;e;3`叝eYJ(R=n0!\u0010t*\f`l-i\u001f\u0007\u000fҹl=ߤ]/\\LE>̙\u0014ٴUi9mJ\u000b*a)=[r8\u001b#̣J 4nhmGV\u0004#>g}\u0019\u0019;we데~Dȧ\t&\u00100˧T{kW\u001f\u001e\u001cm\fW\bFؤ5ew\fx̵T(w\\˳>zP`f\u0011\u000f֠'\u0006!l+Ҿ`C\u000f!F'S\"\u000e i[]\u000b\u0006,\b\u0010iCg`-\u001d>В/,#6Ma:H.EI\u000e\u00030誋Sէ\u001aNW(i(Pݖ[\u0019wr#6<yU\u0019t!\u000e\u0014\t\u0007!\t\u0014nJ#>2!\u0004\u0004\u000fM7YLlD\u0005*?&=\\@b\u0005#68#6%\u0004\u0017+\u0001A?K8q.pG\fN\u001c~Q2\u000eN#6\u000eYL<\u0014\u0004a{iAw(!;{yiH%\u0011E\u0007\u0007GE]\u0014\u001dC&\u0017sE<[;>m\u0007D\u0018E#6PP#6$ (GQn\u000b\u0018_6Q˩G{goؾc_cS\u001c&[g\u0002l\u0001*xU\u0017\u000f`9\u001f\u0001\fJC24J#IT\u0013#>\u001fU\u0014m~mWrf2|#>9Y'apg!~QAC.\u001c?-JUhzy!\u0004!UJe?\u0007g#64TGyQdOF%;UD{}u0ܞ\u0013ѧAhUT\u0004\u0012ۺVʁt\u0014~k-(P`E؍\u00028v\u0002RP$;;\u0011J\u000fCh\u0004P#6|h\u000e\u0010ݒۧO˯|Ҵݮ^Z4m[y\u000e]\u0019^^o?N;TݒR\u000b_\u0015uX]]e\u0002**\u0002S8Jf%\\5;Vvva\u0017mͬme2\u001aM\u0013?7\u00022\u001b\u0014=\u0003'ZC팁Y\u0006ɇraR\\xh\u0015@*w\u0010Šhg\u0007\u001eO\"<A@$ф˧ů\u0013+}HxAzSTxԖ-a^\u000bFnLa8m\u000e&C[ĸ`AǝUƻUcO0s@X\u001d\u0012\u0018/Hݜ[\u0018}*3_u\u0016\u001d\u001a*e\u001c#=MfX\u001b4v2TPJ&3(4$$EX\u0007m#><\u0005\u0004#=zt#=\u0015qKysC;\u0002\u0003kr\bZvPvav\u001eR\u0006ffw\u0019\u0006{]ZR<n\u0015\u0003(IC{'\"\u0001oҦN(@aTWϷb\u001b^\u0013#6rjJ1*#6#6tCh#>\u0014N\u001fH\u0003A\u000b6\u0004#6\u000fBO\fB\u001f ݘ\u001bTEB>ҒrOn\teݘC(}\u001fm\u001b\u0011YuJW#T\u001d\u001dRg\u0004jpd\u0004\u0004PCT+T'#6~JR/D *,K121#6??f]I}!5S.\u0019&N\u000eDœFA3!jN#=!᪁d4\u0012$v}\u0011+\u000fqw@Gk#=⿕<\u000fv\t)N4J\u000f\u001e\u0011B \u0005?\u001dy#6pܶ\"/ogX\"/D#>T#67f@Á\u0001\u001e\u001c\u001d\u0015:H#>\u001f3\u0013\"DT\u0004U~>\u00073t\u0002NU9\u000e<\u00079\u001f%D\u0002Bl\u0001=U1\u001f*J\u000b}~>$WƲ\u0004tV\u0004+G\u0010\u000e#62PyTl\u001d;\u0007w\t~ه}1xE3\u0005/lq\u0017v+f\u0003P=W.O\u0019^Ùz\u001b\u0014\u0002z3\u0002[F\f\u0004#>U\u0019Q*'\u0001(V 2 .}>\u0001<GE!x\u0001EM'AUF%\u0018\u0010Ev*~\u0018\u001d\t2\u0012?GJ>Φ\u0003e\u0004a#=c`TF;sYѡ}\u0011jg_K|<\t.5\b\u001e8\u0010d_(,'\u0003Fǘ\"{k\u0011=(ITdY2XJ%\u0001%$\u001f\b\u0017PlzuLeQg\u0012$V0=2ܨ\u0007HС>2\u000b>LlUԋʙ.\u000e<\u0012\t\u0018?F\u000b|=!՝ ?Xbu\u0004(ݜn\u001f0ߕ\u001fGGED\u0014\u0015h\u001a2F*))Z\u0016\tZdW9!쏌_[\u0007r\u0014!BD\f\u0016,r\u0001L\u0018Ĕ\u00075\u0001\\\u0019O\u000b!vJ%\f\u0018(qy\u000f\u001d\u000f\u0016Z[a\u001dZ;Xpj\u0003\u0002p\u0019t#=iK\u001a\u000fLB\u001d:Z \u0002&\u0015[U\u0016tz\b\"1&9ʔ7*D;\u001f\u0003y4\u0005@cKu\\\u0018G@N\u0013[\u0017\u0001h#X\u001f\u001eo\u0012S\u001d\u001a.媓M(ªDz\u001cfj1\\\u0010yq@GJ\u001c\u0006oSϠ07Զ\u000b\u0013#6)ZHo.)Ӕ\\*8\u0002$Co:I|W\u0017.ځtIa\u0014`s\u001bo\u0012H=X0\u0006tPP\u001dY\u001cE+$[kg]\tj\u001f\u000e~ߟ\u0005i\u00198|<\u001dCvb7Lo\u0005\u0013c\u000e.~tY(o\u0005!#=\u001a-\u0016Qy)S];<|Zd\u001f#>Oq\bq\u001f\u001a\u001bGҫf\fmo7A\fF\u0018J8LԙHr9Σ\u0005(S;!0תi\u001a\u0011:&u\"\u001bC|\fz^fFhQG#=\"A\u001d`ߢ@L@\u0013*E@\t: 7\bp݈\u0015!\u0003A*ޭEÈꋍ4nA7\u0018PQrNT]'PY\u000fcv)i#>jA\u0014\\\u001e_vkXtgdK)$Q:Cbx\t`ktEĔ$;{NJ\u0010\u0016ih\u001dؖ\u0001IeSS{\f\u0006;3~*\u0006\u0012*<s^bc\u0018\u0014&|(#=\t56o\u0007Q4\u0010EЦFN\u0012\u0014\u001a4bF$FJ\u00069\u00121HjtB#=Z-@fZa\t 7ЫZ\u001bP`h9Ý\u0017+U5=k\u0018\u0018晁ؖ\u0016%\u0013\u000emKx=p9\u001c3F1\u000bLjb\u0019h\u0010SC8|\\\u0014X͊2\u001a0c4̓\bDx\u0016i5\"g#Skc\bm\u0016\u0016\"}\u0014\u001d;6\u000b;nb\u0012&6-\u000eĵb]\")R#=CU\\L,AEsَ*\u0013w6t6%\f\fc[\u0019Xfn+2\tM641qQ\u0006Up 7\u000bb\u0011\u0010FC5#=\u00104tMe \u0011<n9ѷ8B0B\b\u0002\u000b`@F&AvC{)-ˍ+pqVu|8y<gf&\u0018<PcmX3fJ\f!\\frmE\u000eZ^\u0019ZAגC1%εH4\u0010\u0011\u0003tZ#+l9bW\u0005)#=#$\b<\u0014gMԨ6W)ߥ&̚\u0005LbF0,DCd!\u0006#>[\u001bOA.\u001a\u0005\u0018\u001e#=3\u0017v(fr=\u0004Tf,1cø\tT7\u0002\u001f뢁rQ_v|q.漍Qy؏\u001eq8<X\u0004\u0002#=\u0004$\u0018fGBQj̣sMk\u0016\u00188f\u0019B>ި*P\u0015y0yW\u0011Y\u0005-&\u001f,ֱ\t\u0007w\u0010MǇO (u\u000fF{\u001a-Kr\u0017\u001b\u001dnѺO A0/\u001b\u0002\u0017Y|[\u0003K.ӄeAv\u001980!q]8\u000ea+̀\u0007amwqEs\u001dZ˃xPWb-\"#6A\u001co`\u00162L\u0006/\tc\u0005#6\u000bU\u0013P %\u0001|ʉD\u0001\u0010U#>_S\u0010'\u001d\u0017'a-1g}#w!qK\u000eaGx>͛7~\\\\\u0012vtȶq\u0019HAM&\u001b#\u0010fFװ-lm\u0007RP\u0016\u0010\u000b\u001eHg#V\u0017\u0005n+\u0004\u0006FP M?~\u000e̴L,\u0003\b!@l̈_ۼVA\b|8\u001d\u001136C\u001fᴚ?(%\u0006,X_x1_q\b^Cx\u001f\u000er\u0016twz8\u001e֋w\u0005<1tbfv.\u00173\u0004\u0018C%$s\u001cRe~ulG\u001f\u0017=xUݥ^\u000b}\bE<uOY{.e\u001fz8Rkvyn\u0016فsñ\u00191\u001dw/SW\u001a]V7\u0018h\u0012,\u0003 SR#>TU]Զ8\u0010\u001a%zu2\tT\u00105\u000f\u0010Q&\u001fUg\u0015+Q9\u0011q#>\t\u0011v+j*6aB愚|aOs'#6:v<nSmlymd߿vRAr6E\u0010I0{(&%\u001eI刑\u0003\"(\u000fۍnu̍Nz8\u0006\u0010y!\\3[?*6\u0011\u0005%%\tdAƖ\u0001bIAU\u0002V[\u001e\u000fb`\u0010\u001f̣\u0001K#>1SտXsp;S \f\u0004dT^4~4L:\u0010qyPd4_-z\u000f\u00034~'tS_C\u001f\u001b@osڕX\u0006z \u001c0c\u0014w\u00122Q1H\u0011$\tUP}\bGg2\u0006Za\u0001պ\u0003\u001e\u001a'w`'!#>&3\u0003N7\\\u000e\u000b\u0006F\u0010}<@VA\u0012\u0001O\u000f\u0018\u001e\b\f=9'Pd\u0001LLiTf\u0004Htw`#=Id\\14jy0!\u0012\u0007\"\u000b.v^\u0006\f!\u0007\u0003-v#6d#=1#6\u001b\u0007\u0012\u001cC1@xjl#>\\g/dL8T`<U\"4E\u0013@\u001bu(U)2Q\u0001Y=\u0010\u0001\u0004 <\u001cȢa$GS|.\t\t/c\u0005ߟ*\u0017ǩ\tN\u00068`ݮ\u0014eQ\u0007Ь\u0018'#6hN&E\u000b&a\u0006bCLUޯL=>m|0W}C4\u0005QTM/\u0006OTU#6\u0004#6}\u0004&\u0016ϱ!\u000fÔ?`bGB䞾GZ\u001cj\f#>\u0014=hΝ޹#=@LCdu\u0015/T0#>?U\u0014PϕgmĻ\u0007vKAGu>\u0003HPA%aPC\bMlP\u000bE\u0004?Ԋ*)A<d\u001e\u0001P\u0017=\u0010'd&3r)\u0006x\u001e\u001a@;oĚOvx:ć$&\u0019%RQ)jp\u0015\u0017_ݶ鱔Q\u0010C\u000f[R<\u0017a$\u001e\u0014[񖦾;v<aӢqӴc9n\u0015[w|zjP]ݰA;qшĶhhb(u*cz_պzNto\f\u0017!?O\u0014Nhm\u0004 \u0002$3o\u00037Rђl\u00146\u000fFcR7\u0004$Z}\u0007-G\f\u000e\u0010#ih\u0018\br\u001d\b\tR\u0001Db2\u0012#>;>>u\u0018@\u0012PU\u0015g^\u0017dGdbS?.,_AsK\u000fq\u0011xiPGs\u0011{0G\u0018_+\u0007L6ӱpS\\a&%\t\u0004\u0003ѵn!&ׁx]Zh\tz$\u0011ͱ\u0015\\p׆9bI$\u0004[\u0011z#>#>\tD%\u001a\u0003\fك`\u000eSZrc?EĸEXs\u0018J#6D3^ӦQӄ:*.,D[´ĩ;aIvR\u0006פzQD\u001aRSf]Y\u0013W\"\u001a_܅G\u0016Qr\u0006=60*$5}J\u001aJz\u0015\u001bH&(#=l\u001fz\u0006;o\u0013j\t=1\u0012Cl@?\u0005\u0012L*\tFA#=^uJ3;.Ә#>\u000b\u00033#6\u001f8R+s=]8\u00136s:\u0003죞ԉ~Rö\u001bs\u0015n_e\u000e<T_iN<\u001a\u001ar*#6#=H;\u0017\u0015#$(϶\u000eFHB+I\feKCG\u001f?\u001aɟ\u0018/y[:q>HTQ{\u000f|O}\u0013;!#>I~\u000e\u001d\t|<է#=J\u0011oD\u001b$H-X[\u0010d37\u0017,\u0017G>\u0002c</kiNYHt+?#&w\\'KAns؃G3#>B\u0019wa\u000b,&7ƷiK\u001b\u0011\u001aRS\u000eN\\Fִ9P\u0003=[I#aMN\u0001@{17I+\u001fz#\u0015w\u00156ow\u0003#[Du\u0012BM/F+b#Nt\u000ed\u0006\tn當e34~j,\u0007\\\u0018αA(tC\u0017\u000e\t#!zz\u0004\u0006\u001ei{p]T\u0018g\u0019>+:u\u001e\b*\b1 *\b\u00154aI\u0018\u0010PH\u0014\u001dQ̻,\u001dzUd\u0017W]_4CI \u0018\u001av\u001a\u000f\u001aM<'\u00031O\t\"]uX&c񈙈0\u0016\u000e]д.R4~4\u001eZ}~\u0018XQN$u_q7%p]w6g\u0011˱\u0014\u0011_#6\u0019\u0011P3\u000fRZ鋣\u0015-vp7@ ^2xUB\u001bs\u0017Zkwu\u0006[~q\u0010\u0013ʐ|t\u001e\u0018ǻHsu\u0010h\u0015\u0006g\"!#E#3BGu\u001dZLpr%3\u0010\t>u\u001dRt'jE&#>@F nQiG\u0018N1O?y\u0007E%/\u0014`\u001fGX@Iu]Cg\u0001\u0019#=aXIH$\u0012I3\u0011Dpx^/.~qj\u0019\u000bu\"Sѹwω\b\t\u001d\u0014#> 2\"n#>#=ػ\b;$=\u0012E\u001cJ\u0006*C\t>L\u0012\u0015Qs0\u001d[$yF2A{\u000bЉ\u0003\u0006D5\tsչG\u0019#>\u000e\u0005\u001cP\u0006\fݵ\\#=E\u001e5\u0002w#>\u001dKz5B\u0006<|eR' r\u0016NIHgxkOӏ}n6i\t1?BI\u0012w\u0007}\u0014(\f\u00117\u000e\u0010\u001b_lWCTGf>9dn\u0015#b\u00167#%C$j/(\u0016#=wHFh_\f\u0005\u0004R]Yi<kzȌD\u001b\u0003\u000f;)5V{21'k$XSʢBO\u0015gxss\u000e\u001du\u0017\u001a\u001aϑt\u000e\u0005[\u0012-5w\u000f#>\u0005\u0016JfB8qv\u0010K\u0019߳Wx\u0019\u0004\u000e\u001bd\u0007JV\\%\bBh\u0019%\u0017fVF|#6Szq&IF\u001c;\u000669pD\u0014\u001c\u0013]v\u0011-,\u0004L`S_\u0006[$PR#=s\u0004\\Rf\u0014\u0018^-\u001c`ѱp\"NJv\u001a|\u000e\u001eBp\u001dq%\u0017*|~3^Z.'Q+\f$\bs\u0003xaR`t\u0006Rl}zFFTHfu^ci\b<\u0018<hׇ\u0011\tsY#>N\u001c\t\u0001\u0019َ#>\bP\u00069u#=\u0012dB}arƁ<\u0017#=\u0001{u,V\u0001!-\u0019ߗ#>\u000bl\u001aҥ\"5;.ú;֝-\u000e\t\u0019;^\u0018(X\u0012[\u0018)\u000bS<G\\\u0017(J b=@DRt\u00183\\T#>;\u0010Y.]UuE1p#u\u001c\u001c\u000bӇrQm\u0007{EB$\"y-$(0\u0004&m#=s?bMՇup6tz3\u00121\fP\u0017D@#fL/{r *&.\u0004\u000bki\\mDSp\u0006\u0011`OgTwjo2c\u000f3\t\"P\u0013\u0001c3#>-\u001cBY.\u000e\u0015EB6\t\u0007XH#\u0010@0%<e!09C\\\u0011dRC\u0010^Z\u0016CC`'v#-\u001b;#=*'aA\u0005\u0003ii\u000bek=u\u0013(\u001f5r\u0004#>\u0002B\u0019\u0005\u0019\u001b5Qg=c\u0005\t<xv.\u001d&ZER\u001dFڗDv%s\u00042L`b@@P2\u001d_}X\u0015itV\u00033G\u0010\t Nѫ\u0003)y-\"xf\u0004A((6Ge2)*\u0002\u0006yiv\tFؼq`2\u0003}E\f^\b<b5|2E\u00115t۴\u000b#6qb 5$D$\u0002ۊ\f\u0006RP@-\u0016Hp\u001eB/~DhI\u0010'AH\u001d\u001aGx\u000e-xh`z\u0015\u0016\u000f,Xx\u0013Aa^\\\u001aQi_ui$H.\u00138#p\u001f8I#=#>G5V^W!8c\u0014ߪ\u0017,\\vNHb\u0018.\u000bb\u0018DU砠v?2撴\u0004jB0F\u0016'\u0018&Y{\u001eQH\u000e\"AnvQE\u001e@\\-6pȐt]c߃]f\u0003&\u0016o\u0004y\u0007_I?\u000f,G+$k#=0IV+l3EŶRAqZcNڀZׯw<\u001cӘϽ\u0004p\u000e\u0012͐or`꾉slo5 E`\\j\u0016Tbî_&Y{gz.\u000b(\u0003\u001cV㏎P!F\u0016w\u000fz~(\u001f\u0002t#6NW\u0015\u0003#6!\"#\u0016A]޾V\u0013\u001fX|=\u0017#^`x*T~~|F?\u0012-\\T I$FC\u0003@|\\)׏\f6#=@@pەaA\u000e\u0004\b\u0011\u0018#>oI \u0015*#>L~\u0014[Qp`cG\u0017ʹ`d\u0013\u001b:\u000b(/ҹVnX_\u0014M2?M\u001a\u0017}gM\\^on-z\u00128GqSj5UU\u001e\b\u0014?j*;\u0004@eh2Q{\u000fOK17#=7l\u0019\u0001æ\"4cl./:Nr\u001b2#\u0018l\u000er\u0016Pg`eO\u001c\u0018#=AIk\u0005\"o-\u00185$\u0012Jl3>s p#6\brxL>\u0017p\u001e.\u0017We\u001b\u0006\u0001gH:rp̍QGLX\u0002\u0006\u0004\u0001V(!ךw>\u001cuD|}\u0010PI\u001c y\u0001=\u0010\u0011AyU+ï\u001f\u0015ٮ߆\u00030\u001b/\u0001.Q{\u000e#>B\u0006\b#A\u001a?\u0004z;+w\b'\u0019[~\u0017NImޢ\u001f>B\u001d0\u0012#=-cx-\u000b\u000eG^?ExuEb+Sw؄:za$\u0011\u0012Dp?\u0011{R\u000b'-Em+rHuu\u0011\u0010'GP\u0016\u001fGx⾮tx\u0012x4[_zB\u0012:~(:sm~:ࠀ#>@E\u001a\u0001`)0^#=2]~h^7 .\u0013ݺ\u00101-z \u001el_\u001d\u001a1(\f-s\u0018\u000b&\u0010UT)pA\"\u0003\u0017ѤidU#6]pD#6bn\u0012'O\u001c0hSnclW:\ffEA\u001a\u000exbS\u0001Θ\u000f1ѼLtab\\9NET\u0014\u0015$Fǩ\\g9#>+\u0010\u001aGX\u001a5Գ\u0018ri\"h\u0002(\u0003/zO0\u0003,$<\u0016\u0004O#6\u001ab @\u0006l1di\u000f\u0002\u001cowY\u0011!u #6\u0005Y`\\bK{/\u0011/met߄\f0\u00124,\u0003Z\u0003c\u0016\t\u000e.7\u000b\u000e8 Uosب/Р9W_JKxc\bx^gtƜ4\u0015o\u001b\u0002q\u001e]br;4\u001f1X`Ԛrbɵ\u0007c\u0010gpG\u0018\u00190\u0004\u0010\u000e\u0018`\u0017\u0017p\u0013~I[*\u001f\u0001\t\u0002IV\u000f[o̐1m@B\u0016?<\u0013\f}\u0015cGO#6WαOtc~:ח\u001f/l\u0016D\u001fq~x`T\u001b$\u0002_26\u0019=[4_t>8lG\u001a0+M)\u0019˟\u0015c@BJe.c\u0017\\ޗCw6Pm\u000e!+7FHe9~} /KvTubk2\\$\u000fQE(\u0010!#6\u0003Q~\"-:\u00079e\u0010wvPz޾$<dF(\u0011\u0014h힨0mv\u001892\u0007u4\fߪUs?mC\u0005\u0018FDȔ~/\u001d\u0010\u0005#=&N3(T\bΑ#=qPxzu@ \u0013F\u001e\u0007 Ω)ET$\u0010ϻ#67;b߁Qquܢ}1\u001c+,xS\u001c\u001c+\\gMk\u00178\u0011apg?@dc:\\\u0002jǄ݅p\u000e\u0019אE#>fJ\\u~ey1&`0\b,\tJ\\9\u0001\tJS\u0006 A\u001d\u0011w\b\u0012ŋ\u0015\"D@[H#>\u001dfeb(P=ҎS\u000f\u0012ky\u001bxr&\u000f_!w)J!L\t\u001a\u0007o^#=9\bp#>VZ#a9\u0011X9\u0013M6\u0011ؑ\u0018(9$#o0S]/O\u001f H<\u001fY!L\b\u001d\u001e\u0011Y'{\u0016,\"hƒD\u001dv鯎4/pC\u0005\u0013UUi'r$k߹.p\u001eEKܷM+\u0016<\u0001$z\u0017IV\u000eNf^`\u000f-ëFwTEAY4\u0006\u000fȺһ>b=Իk\u0002}\u0006͊|nWjvUe:\bd҄iB\u0016{\u001eC7\u0011N7\u001e^\u0003|Vi\u0013Vd|`\u001fPv\u000b\u0004\bHxjxv㦒d֮7}Ixj+J9YWJќUk\u0012G\u000e#=/%\u0011v_C1\u0010rB#s~[W}[Y#=\u0015K9)\u0019Ls)&Tv2\u001ahq'ؗQ7\u001aə~͍b3=)䃷\u0015\u0015\u00055M|\bl:3I\u0004%EM\u001c\u001f\u001d8zkP>5o#>-s\u0006\u001e\\~r)\u0007'\u000f䎉\u001ecwxӆvTvQGO\u000bT/\u0017Cas$wP.#6ܰ\u0014)kYbFBN5L5x\u001cnp\u0017\u0014UBAxG0`\u001b\u0007@\b蹇\u000f{\u001fym\u0010MZV\u0010Vůtɮ\u001c+86##6_iPSygP\u0017C;^2N4'\u00132\u0011ÇӾ\u0006\u0012(N;@\";u2e\biA;\u001aBҹ\u0003\u001b#\"?\\\u0016\u0005]K,J\u001d\u0018,`\u0007\u000fC;\u00105.n$\u0010Jm\u0007Fx\u0015ɸ\u0003\u0018vYjS\u0014#=քZ#>\tn5\"\u0006\\L\u001b0#>*\u0013\u001aj)f\u00134ɰ\u0019#>\u0005\u0012&E%@\tF\u0002ѷ3\u0011\u0013 \br[[zPfb2^ʴ\u0018ɦT1n`עMQJ,ۚ+ʊ:;U9#\u0006\u001f˿;6o9>ac}h\u0007\u001diFq^mM\u0007\u001bm~־#=|\"6lH\u0001</.9~j@N}zvCOGNf(pCVM!\u001f\u001f\\=\u001c<{E\u0005O\u0012:߿\u001dt\u0004rV{n\u0005Cy~\u001c{#=.ˣG\to\u0005L\u001c?\t wUǧa+nQ:j0\u0012\u0007\\A\u0015\u001eu)/G\u0001\u0011,}_txBƞ.)1w1$p-\u000f\u0006\u001e͜}\u001fFSs\u00131\"+Wl:8R\u0017W\u001d|\u0017^$=%:\bEpxzfϲ^\u0010z=~+ñ&\u0013t1GTQ/:H8\u0013t#=b\u0003\u0018\f.\u001ek=ԉ\u001c=\u000e@\u0014\u001cF=쎄v\u0014m/\u0019ml28&Q@O=}Y\u00020Yߴ\u0002*\u0015\u0017\u001b0s\u0001E\b~\u001b\u0014\u0013\u0002E\u0005:\u0015#=Cg#M/K\b#>\u000e#6\fmG\u0001hWC\u0012\"\u0001kMK㽮\u0018wݢcrb\u001atO9B \u0018}?wp@ x+!\u0003\u001aĞ$\u0001$\u0001aiagVC\u0001\u001fs)\u001f9\b\u0019|=\u0007'S!2<'\t\u0015ˉ'r}Od;1vȼ\u000e<[l!0N\u001cT&I\u00139l|c\u000f.|Er\u001c\u0007]h\u000b[\u001b\u0014j\tM8\u00181.\u0018u[k%n#>DJ͙x#\u0002H~\u0010坭[Ro❢ЎM$u\u0011P9%,DQY#6\u0019ntr\b\u0015Ȅ꾱Us(\fǸlKWn\u0005 >lFR\"}#a#)lH\b\t?%ocU\u001duFڸK\u0015N:&xsȉ\u0015vw,ÞhKjY\u0015l՗,$ \"QcBA#6[0\u001aCʸ&~zϛ\u0018\u001d\u0018gEIEK\u0007D\u0016Y`ȝ5hO<\u0015N\b͑\u0012\u0002s_~?fZ\f\u0019`)%\u0010\u0003P6q-S\u00076Hs\u000b$\u00143Qm718@p|;8g\u0016N$\bBy9\u0013\u000e5n&֒\u001acnTRoj#=:a,.ndm6V[\bLv\f\u00174SNɍ#=2<4;\u0003pe#\u0012\u0004qfv\u001a3.5j\u001d5c\fҘkWVטS\u001a3\u001eL\u001b#CA2I\u0004``[>\u000e;l5I\u0018[fț#>9\u0014$Dha{}c0A4ͪfQKD\u0007]\u000b\u000fܣ3j:]@3\u000b\u001e?]ẁ`\f\u001b\u0007\u0010̟ܬ;4E]M\u001f<\u001cF8BZk%\u001d%`x`\u001d#=@$&5TQ\u0002K\u0001a\u000eI\f?3SI\u000eREFF2\u0002#=}D\u0006xfxDAMC2\u0011~KQDTm\u0011V9BS+S\u000f}lӲ\u0016g\t(\u0004hJQlU\u0011\u00058\u00191'k}Y(eCm5Bvh\u001cK\u0019\u0006stY\u0018ZU\u0003&R1+򞾺xD1\t\u000fF%\u0012\u001f\u000e{ldm\u00132\u0014\u0004\u00011W\u0012֌\u0017Jc Y*xcX:އ[||Q3I<B,\u00115ݛʻ1~\u001fZf{\u00194{v\"(\u0003^{*H\u0014F\u0002eG?ta\u0019;\u0016\u0011Ǡɭi̼<'z.AhF!X\f( 3:anN!Pvf\u0010\t6P4fޕ*^\u000f|㟢`_A\u001fiI\bc\u0015\u001e }(\u000f\u0001\u000bc&7H&DNSl\u0019@\u001eCM\u001f\t\u001b$oƭ\u000b'ha`\u00122\u0013f3$l@@ oѶ\bGc[Xk42:K\u0016˧Ǭ{knx\u000b9\u001e;\u001at:\u001a|j[_:EF\u000e_8WPv<˛8<.\u0007L.\u000e%\u0004y40q\u000e<zRߡWT\u0004[_O~ߋ\\>#=jx]^d求Y<@t ﴛ&zա#6U:\bS>Rks`\"!/w\u001d's\baA?v\u0013f'Z&ITz(\"Rw\u0003+7cx\u0015vi+,5b+(LGt\u0007\u0001z@\u0011V\u0015\"\u0010\u0005i\u000b]D\u0018|z\u001dM[MBd6=Q\u001c\u000bHfn5\u001dhiPI81rU1%};zbMFK%K{\u0006#=qg`VHS\u001b<N\bB\u000ba\bW^_nlTuK.\u000f9BJ\u0014\u0015%S\u001f\u0010<\u0003g9\bvS7T,\\3wNti|ǒA.\u0014\u001cl\u0010h.\u001dYd\u000b8kG+\u000b$\u0017\u0012ѷ\b#6`z\u001fh6C\\'1A`V\u000e*8ߏ!#6ًEww\u0003B\u0018(c/ݔ2\u0010\u00164:\u0018\u001caLk\u0002\\|ۧ\u000frN+3<ǴZH\u000ewIҴp]\u001a\u000bV+|Db iJ$x`,Q$\u001a\u0010\u000e25!@C8*|\u0007h^1S؂\u001d\u001b&Q1Ml%\t|1lE{Y{U\u00131}s_w\fz\\^k\u000f^8D\u001c\u001bf\u0012E\u0013yTu20 x\u00034 (͂\f\u0014m^)\u0012濞\u000eP#>${hXPɐ9\u0005K(+3nҷ\u000b\u0018\u000b\t#>K]]o\u0003ˍ[\u0015\u0004:7 @?\u0017`}@v<c\u0007\u0010\u001d#6ǟ\tChL_LokU0Κ+u\bU\u001flPkH7xb3\u0004G~O<zîF{\u0001x\u0010Dx-j#>)A\f<e3)0\u000fY\u0003\u0004-\b8P)\f\u0007\\#>[W't\u001ax,=$\bA\"%Yض\\Aa;#>Lԡ) .݆AϧlX)\u0010{\f\u0001\u0012!T[6PI'zŗ7/$rڮ,IU*k\b\u0003 \u0002u*7:SpB\u0002.21#6CJ4R\u0013|a(\u0017˾\b@R)\u0004\u0016/\fӮRS\u0018M5\u0011!,D\u0006n\u0014\u0012%sk7R7:7^\u0019\u0014:O\u0010¢O\u000e.<&>c}|d)R*eEo&\u0018܂ޮ\u001e4\u001d&Bb\u001dhLGݫ\u001cT=~fʨa\u00185\u0003\u000b\u000b\u0010<`>`\u001fbod?$<<4v/>\u0004\u000bޠN0\u001ew#6Ir~_8-\f*,gGN*N=\u0002ƱUBn\tX\u0014$#>3\u001co\t&اE,0 \u0010m2ʮuxN`\u0015Bk\u0013~4\u0012B9nz3PU\tF\u0002\u001d\u0001GaY_'*K\u001b#=pn^\u0014\u0016MpF#6Tp\u0006=^:\fAn\tء\u0017t\u0018\u0018fMt\u001ea&1~l94\u0002`3f~\u0016UΉ(<c\u0019t1*\u001cb\u0015]g\u0013V\u0014\u0013H\b\u0014\u0013d\u0014,:\u0003:́woϫ#>Og\u0010K\u0016\u0007U\tFi-5N)Tp\u0014(T*rzg\u0017L\u0016K\bm\u0018\u001c\u000e0N|n'5M`L@t_h|ae\\a([#6P\u0016#=l\u0001 \u0017)\u0014\u0003OOp#>\u0003!u$3s%K>&FG\u0001b\u0002N$Eń@L\u0001db(ٹK%D0S\u0013j۱lfP1;\u0003P7ߖzW\u001eu)6񫚌1#=; 6za5^\u0004 \u0002Ŝ#o\u0018GFO+do\u0010P\u0014!A]V\u0012=.RvuҩwmBwߗIfq\u001bw4\t#=G\u0010[YmoJGX\u00155R⠅O/$L\u001f\u0018kٸ`ˢG/&\u001d˒lHH\u001d>]/ÿV\u001d3ո\u0010C\u0003\f\u0018b#>\u0012\u0002,he͡#=\u001c\u001bI\u001b\u0018P/팷{\u001a<a\u0019:Z>*\"DT5\u0014%Qo۩JRm\t6r[a9?\u001bq\\|K𢌥v\b\u000e|)q(;+:Aگ|b#6}]\u0006\tw&*D\u0014od'w\u0001I#>V\u0003:J\u0011!`!\u0005J\u000fYNh\u001d(\u0010s\u0018?aT\u001cTlQq<ҙ\u00194#6\u00022+%\fɴyYiwŐ:apX[(\u0013xT_<\u0019@\u001dqcͼbuq\u001d.\u000f2+\u001cP\tEY9Ȅ\fIcTe#>l\u001f\b!(\u0011\u0011\u0007\t #6#6^#=ƣyY11ѡs'o6#>oc7u>\\\u0001K\u001e'fs\u0003ä\u0012t߶l_\u0012vֱ\b\u001dA\u000bIgPS\u0012qիq#/\u0002=Dr\trrm\u001c7'\u0010ͣ\u0019lD-\u001fǅ_\u0006Wr,ѱ߬Pp\f*\u0018=?cI݂E-̰S\u0018l+\u001c#>9#=4jO&w8`\u0017\u0010jf4#6\u0011C0~\b\u0013<:<\u00034l\f\u0013\u001f.NW\u0007f!ڍ\u001e\u0006\t\u001d{kwtN\u0006\u0013Re!9PjK\bL\f\u0010Y6%\u001bfz\tA\u000f\u0019\u0010s#=_u}R$\u0018G8?0U\u001a-\u0019\u000b(ȷ{D')∖\u0005\u0017\u0007\u0006Z\fe3M\f\u000b?\u001b4ܠ9k˶xu\u0015>ul2Q\u0011\u001b}F߲C\u0005Qmɒ\\\u0001\u001cBFg\u0013\u0014Xި\u0001c\u0018y? sM,#T\u0019-s_\fBH[8w#6wW`\u0015l+Rʷ\u0012T(C\u0005\u0012O{5sق2L!d/\u001a=fPFGᓮ\u0018C\u0016O\u001b\u0015pO2b^z\u0001\bSXj\u001eL5\u001f3\tA\bia\u0003\u000f\\nG8H$a\u0016A&1[^|#J\u0014%\u00107pmw_\t6\u001d\u0014\u000bU\u0002\u0002! \u0002+pLhd5R2ɷvjKEzUvLN\"\u0013i\u0015.7<\u000e\u001dqK\u001cꢐ[u\u0004Fݛn8\u0019\u0007EhCmX+\u0015o(7D?ǅ\b\tvE#>5Usa\u001bQ\u001fe\bEwJ#6Ue.f2XYW\u000b\"\u0012lU%\u0006ݶG5XEd\b;q,kF[lWٖ}L\u0016.HOs=2rO$^\u0002Ԃv\u000fdI[\u0017\fb/dwq\u0003&mmZڭ!x֝\u0001VJ.6o\u001cDߒ'z\u001b\t*yt-c@kxi[(j8\u001b\u0011\u0001!񭮹nMs\u001fLL%XL\t#>[#>4ŷ\u0017G,>\tU\"z&8e*Ѻ\u000b\u0001&\u0018\u001b8gU7(=*:%\u0003mI7TF\u0002\u001842\u0010@~#=1$6c@\u0003\u0001\u0014\bNl\u00108r&&\u001dC\u0006lN)\u0005~j'\u001d\u001aBTF1D#>\u001aO'L!\u0018a9%plH/ \u001cXf\u000b\u001f\u0010~\u001d\u001btS#=f\u000f7>/ˠ}\u0006qF\u0011\u0003ٮI)\u001c\u001d\u0004?\u0013ᔔ\faMO@#p^/\u0001\u0010'QBh\u0013\u0004\u001bOh\u001b\t<+\u0019?Ax*m'#=J:]#=:\u0012Yw@TBZ;&#\\R;RgN}3:\u0018\u0017~spz\u0018^ykN]#wRl1\u000bjb~=k7.|.!\u0016[\u0019Y,b\u001d\fU\f#ᒈzsZkY\u0007X\t><\u001c\\ռ]\\\u001et1/\u001e\u0018aQ\u0017yNԜ\u0003\u0004oIbU(f.+\u0001zߛ\f+(+@(I\u0004+&\u0001уᶢ@y\"L\u0004U鑫]\u001a<\u001f\u001eқ瞠#=a\u0007|#>j-3@SOj1F\u001c:xO\u001a\u0011޶=%5r\u000fs-Ƹ\u0016(;9UD`G|R\u001a\u001d1\u0016?xЭPbmq\u001aMO\u0004{*ZOO>Q1?zڕ0d=z;y\u0011A\u0018\u0016\u0015aM06#>\u0004\u0012#=[\u0011\u0011Tn\u001bD8PGZ~J8\u0003\u0012w*8\u001b#=#>ٴeyv\fM\u001dNfK]8.\u000b\u0010Kjd\u0011pBͱE\u0016x<T0qt}ߵ\u001ej\u0003\"h\f\u0001\u0015y#>_D-\u0018u\u0018?Iyu-mӅ\u0007H<t5Y|BONWG3ϥ\u001d[A\u001c[#{\u00198Jy\u000fF3J9+#ů\u0002}\u0015\u0016\\,72\u0012\\Y1#>?#=wGsc`]lAΖ\u001cwF*3+̓18m+ yMc\"Qҥg%H0#\u0019\\XzϾ'C]$\u0005H:}_s\u0019\\''3~T͡Jx2s\u0019n@0\u001f\u0005$AK\u001a\f@0hQ\u0004)kH/\\5\u0002\u0014\u00195Dܘ\u0016Qذx5`Asb\u0007\u0006F\u0013Q\u0010\u000e\u0019\u0005&;t~_yuy@<\u0006JdU[Ya)\u0004Xgy\u0006jc\u0003H!\u0016q.\u001a\u0018j j\u0010DƲ.\u0006\u001a\u001a=n4p#$\u0019\\WR\u0013t5\u001b[\b9Q(&6=-cm7+V@ld\u0019\u0002\bbF0d\u0013{~qo\u0019\u0012D\u0013fH[v8\u001b\u001d\u001a\u0010c0%rD\u000b\u001dr \u001dWOɀkK`\u001aDj@ا%\u0001_B\u001b\\\u000b`#WL0]GɅ|\u0006^BܬW&\u0004\tf\u0007poEBzZt\u0017A1j\u0011$\u0018\u000blN.U/\u000bB@#R#>\u0012\t ,ϯ,Ü'K\u000bLZS\u0018I`5\u0010:\u0016\u0011sÎϗM3\u0006\u001a#>BbH\t7\u00073`8!i-?2$/_»P'&<8-f6Uu\u0002\u0011\u0004\blPi';SH8H^A6\tH\u001dEO\u001f\u0019^1\u0016\u000bvUCH\u0018\u0011d\t\u0013_#=.{qۺ}yS{xͶ3c$\u0012lײg2 g\u001c\fQs凱@#6\u0013w\u001eX3ڀvIpPz\u0019D':l6\u00155NJ:, YU\b)*_a\u0018RT\u0006ys\u0017ձ#__@Y{\t=\u0004VRoczd\"ݯ\u0007p{\u001fXWO\u001a>б(?T\u0012bњm\u0015\u0012:\u0006\u001b\u0001\u0011\u0006b1\u0001\u001fbVo\u0015\u0006\u001f~P\u0001$Q\u001d5\u0005l9o\u001cjy\u001bl\u0005iF,\u0016M\u001653\u0012\f\u000fZ\fxTWťl}\u0005\u0006\u0003}7a?P\u0011\u0019\u0014WHw\"aw\f\u0012;\u0011sU[1+ݔ7\u001cm&\u0018`C\u0006Xw\u000bG!\b$l\u0006D=\u001bAP\u00108'߷\u00173{a<fu΁\u0015n5Z^-\u001f\u0019\u001cg]{β\u0002\fXs(H\u0011=Z\u0018\u001c\fXDg\f\u000bS?!}𺚇0Cu|\u0013Fj6ݦ\u0002ׅO\u0006\u001a\u0003*\f?(\u001c!\u001b(b\u001ckf\u0017J:S4ܣ\u0006\u0018\u00067cw\u001e>H5]\u0010%\u001d0:'E-pqUp\u001a*1\u001dx4\u0017$vN\"\u0014#>_Q[\"808q]\u001252\u0002zo\u0012\u000f\u0006\u0002d^Q)pCt~ě\\oE\u001a\u0007\t*\u001e2sVW>\u001eQ1lni!fg\u0016nJ0N\u0001o$\u0012D+\u0013v,SH\u0007A\bF)zv\u001cSyXм!zv1?íi\u0010e.\u001fYI;Uvr\u00029\u000eR,l0Q#60!\u0012$Vs\u001db=`Ŗᙥxq:,gأzXTq\u0010!<SZ\"A.bE q\u0007U\u0004\u0003TX+\u0017J\u000bg5Uոr}}h8\\\u001f5ګG81Eaec\u0017\u001cęo&Lӳ|1\u0004!4w>\u0006\u0014QӦ\u001c%\u001egC!L;wZ\u0015\tGb)سC_@Ϛ\u0004\u001dI\u0019\u00171Pb\u0019\"q epɞwHB[O\u0010/6'ȠUGUa\\sO.$9#6/[ȍY*0C\f\u0005z&K\u0019D5A\u001d\\\u0007\f8hGfYJ7mQջJkޒ\u001741f/m&׮y\u0010bHC(!=ba;WiP˶B\u0012:}\u0010~S#=%\u001b:8b1\u001fP̿?\u001bĆhFN}2Aрv\u001c\\eHGx\u0015\u0013i\u0019\u0010˛liYFā:Hys\u0013\u0010ʪ7=\u0018Cq3\u0010rQ\b8Zc\u0005\u0017\"ͱ^\u001a\u0005jeï%,\u0005\u001fwz?cSJ\t4HnS\u001dX\u000eYԌ\u0014si4Q\b\u0011\u000b\u0006\u0001T7\u0005\bUq%_=M|䬰4ߙw(W\t\b#pz_-\\@\u0019Jl\f\u001f؄\b\u0018;\u001c#6YTTkB@\u0007+lM\u0005tǧ0\u0012\u0015_NA5{L޼T&Z\u0011[h<$4r8bqM~IR\u0019\u00117i\u0001j\fU<z%\u001d=^\u0018ۦ\\\u0014 \u001f~hI `Hs\u0006\u00043\u0002UtQV\u0002Bc*=\u0014V/3\u001c#=x0O t8{/DB\u001b5.R\u0012\u0015~\u0019\u0007Jم8FjcgPWq\bKyµUMȊW#>)tU!6  \u00155,#>3׎+m\t@n~(c\u0003l\u0015e\u00111=1{Bo!\u0018Fk*\\#6ưG\b/elVO#L\u000533>\f%}sǫHwC3#>W]v\u001eӭ)R~T?&0WFL^c3AX\u001d2iPT{vwwZye\u0011\u000f\\Z\tr\u0012A]oX?rq6#6\u0012n\u0012P+f\u0007\u0005̰H\t\u000f\u0014\\!\u0019\u001c\u00181?:|\fK\u000bQޞ\u0012\u0005z}Y\u0003\u0001VË=:Y\\X(D\u0004F\"}9sӧ-0y6@a(2!ae\u000b\"n.'[o-\f\u001b5_Fگa\b`-\u0007M%ڥu4\u0015L\u0010\u0004A\u001d}!j0\u0001х0\u0013b!yt\u0018^\u0007&\u0010##=b\u001bC\u0007g7j7\u0003t\u0007N:Q\u0019~<#=22^I,#6M\b\\\u0003\u000758GkͱIg=?\u001e]{\u0003wΥ\fFNݕc^2\f\u00192\u0005 1ɐUR@\u000eA\u001dzU#=¡R˥i\u0013B%\u0014|\u001ce(c\u0014t{$%fFWt2V4Ȗdk@p\u0012Ɣ;([9g\u0017#6C\u001b(yk\f c-mu3#>p\u0004\u0017 p+\u0018#\u0016\u000bzs4\u0019Ni@,2[Lk\fJH~T\t\u0010+p\u001b3\u0019:gy\u0019Q\u0002yĭ40:lF\u0013IfC̈v>B^9ۻ\u0002~v\f 9<L\f7(`A}V88l7Z\u0017T_U]d\"-\u001cB} oVh[N_{eoa`A`+<e,j\u0016.\"7\u0005\u001d{C;|m$\u0019s#6b\u001f\u0003z9#P\\cQ:-/j\u0007<4-mXf~e\u0019eF!qvnEdg#z)\tgI)})@@\\w\u0005YB΂%Î{2uAD*[Ψ1-K\\cmN\bfA]5n4\fM\u0018\u0011E{p%66i@\\{P=ңB\u000ewcIЙ}xpya\u00127ѡ|˸$\u001cM\u0013o169]4\u0018\b\u0005lC\u00142x<\u001eys{70\u0002;\u0007\u001d>\u000eȞ;a[&./(m\u0002TՄ/q\u0001l_I<qt\b-<]EsjYް\u0001Wm&\u0001\tPQ4⸲\f' 9(\u0015\u000b#>\u0006seXR\u0011T)h;xFD\u0016\u0012һV%դez '\u00022\u0007#\u0007)fCJ\u00154P\u0006u?#жl\u0005\u000em\u0017o\u0007(ǧ}AG#d\u0013vY \u001a:m钥#67fWr\bYTSG;Ɨwʽ]\u001a#=ot\u001ask@PD\u0004!\u001c.g~\tY;HZZ\u000eԀ\u001aK\u000bA2#>PnX\u0017^\u0011\f~!{\u001f\u0014l;~RaA0\u0007#6x\u0015rT\u0001_\u0012gMBT<\bZlFՐI\u0016$\u0007b#7JW\u001d\tzɩ#Q\f-5W%77Ul\u001by\u001d_eC8#=g!c\t)D91\u000e0iO~L),[=lX1\u0003A\u0018-8\u000f`H\u001d݂{q\u0016ܤ8kէOiw3K٠\u0018#6GdAb\u0003#6̽v]~;XXYPD,0Y[#>\u001747b\u0001q\u001f-+(>[O?{I\u0006;E\u000b3SC\u0002+>=1̔a9bZFf\"!\u001bYF#>N$\u0016\u001c|X'DU\u0006WD{\u0016\u000f\u00123Ul<[3*I$xC}\u0006on)` \u0005\u0006#6\f@\u0010\b\u000bm\u001bWpn&^簥ãϨg}=\u0013\u0004a\u0018#\u0018\u001dqUURLn0ʆ#%hFP^\u000f3Ov\u0017^M@?ȢE\u0010<u%gI\u0004\u001bo~\u0003v/g\u0001W®SMزP\u001c[8XEv\u0015^s#>\u0013des\u000bf9_x8ϱc\u0013Iē߯\u0018zķ\u0013adǾv0z\u0012`304y`bRp`DxW\u000ep.\\Yو\u001f\u00143az`8O\u0011].קcu.m\u0002-:<Y#=\"8*)藨چ]q\u001awyK_뫆Z\u001f+ߦzhJ\u00175(卥\u0002wk\u00037Hv#6*\b-'\b\b^He}Õ:>ޘzLA\tZf\u001e]eVbV)z\u0014P(qjj\u0018\u0006\u001e{r\u0003Hq\u0011`A}9\u0018۾ֺ\bq_&MU\u0006*\u0016ft\u0005d#6}#65\u001de/0\u001aoar\u000ey%NHƠz.2hBͰȘ\u000b!ҳ<B(\u000f\u0018FY\u00032C `,e\u0006\u001a)M2?\u0001䅏M\fbÇeg\u0016Q\u0016A\u000b9\u0019\u0004e\u0019$'#6\u0002\u0015ҫ\u0003oŴe}N\u0005I*5(B\u001a\u0011Da).\u001b\u0003'\u001fox\u0018=FAVwu>0R#>\u0018D!L\u00065#LjȆtg8Հ\u00194#I1G˙sq,+8v霳h\u0017\u0007\u0001\u0011yƌ\u0017(;FJkX5#=L\u000e\u0019+sBf٭bǜQ\u0002DD$&\u001c\u0010vc&S\u001cà{-S5}\u0002\u001cU.ց0n)\u0006n\"O\u001c7)(jc Dlfh@J\u0003\fF4\u001e\u0011\u0005&]oWD\u0014a\u001boH\u0017Mg\u0012*^\u0011Ad(9\u0001% \f#>t\\(,shVXAe(䇖\"\u0010c#>r^3r:3Ƃ\u0019ϛ\u0003la4\f.\u0010\u0006dGqO&bm3Rfv9p=\u001asV\u0019č0R\u001b#>I\u0015\u001dYHb\u0018vqĄ\u0014\u001dֱY欩f#6K\u0019)\u0014)t\u000e\u0016:,\u0007|~~\fSԹt:\u0016N\b5#=PDnܲˬDM<f.i\u0016>𷨙;SAB0aC&UQ'wsg`\u000ePa\u000b(w\\}\u0007$ּ)pt\"jN_K\u001e\u0014wy\u001fZ^x>Dk&tm\u000b\u001f\u0005\f*2*5\u0015Lw{)/TGrh\u0002$b~p_<X{L\u0016>\u0017ߏnBq9N\u001a\u001fyҜ(\u001eNM\u0014x\u0014Y\t\u001e#E?d6s]\u0007^FR\\*EԈɃر0\u000eC45w\u0017\u000eΰ٬-B6ѡ_F<4\u0019 \u001e \bsA#=4ؿAya\u000bt\u0006z\u000eJB;f\u001d\u0007\u001d`u!r6Cx'9\u0012\u0010(\u001b|o\u0011\u0010r?Q\u0015?)U4\u001fg\u0010\u0004^QsW\u0019$\u0001ɰχ=M>@\u0001\u0010\u001f\u00074^<_q\u0018Fc\u0016>$R#S\u000e|#ǿkϲ-\u001b\u0017[}\u001avH\u0007oH 8Lt\u0001uq\u001d\u001e?6r\u001c)/XB;[N\u0004\u001fÃ%\fFM_\u0010 D{\u001b#<8x\u000b#6D\u0011\u0011ë\u001cy2(U\u0003J'\u0006[)\u001fl\\9\u0011#>1ݸj#JM\u0002Czf̓ w8QM!yË[\u0002-Tq(6M0\u001fh>?N\b\u0015jڝY\u001bic\u0005+E?'\u0001ü&e쒢\tVS\u001a?%\bt5\u0016Ǧ\u0004bۉ+\u0015\"H<\u001bQ\u0019#\u001b\u0002X$\u001cp\u0011W4WBu˩):\u0019Ţ\u0017U Xp\u001bw\u001e\u0017k!(\u000b4\u001fX#?\u001c8mKU\u0012(3C\u0010C-\u0004X4*\tFb#>\u0010\u000fl4I塄nw'\u001boZ)!\u0015KG5h\u0005/YѰ[0o;-.Hf;i\u0006a>~\u001bߟldoL\u0014Ll\u0013NS:^\u001dѲY|\"\u0003XB>)4\u0002C\u0015rಙE$\u0003\t.\u0011o;\u00077gp\u0017\f<8}ipK*E\"+dj3\u001exm(iHA$\b\u000f\u001b}\u0003x\u0011(̓6&Qqk4\u0005R(P_\u000b\u000flĈ%;.@ҡoqvag;Vv\u001b#6ɔ.6䟢&xhwbwj@f\u0019j<\"%w\u001ac[=\u0014:\u0011H<2fл09*wM]\u0016DF|\u0015p\u0012G%0\u001aD];HD\u001c)2qÜ\u001bTRPcwcNYrBM\u0001\u001dʺeK#=\u0001AF7cH2#=\u0001`o_nOuCՈM=+Ҹհ?y{:ʬA-\u0003\bm}/\u0007\u001clc\u0005$A[-C=74[k\u000f{\u0019DPKs{^UH]H\u0004m7Vt@NK\u0016I(Wi\u001c\u0014e#6=h̗%\u0018c״P`\u0006;=1\u001dC%Dx\f7/R \u0004~ʰ5AGb?+\u001c~!엍b#>n\u000eA\u000e\u0001y\u0004r\u0002g!}2ǎ]Ӝp#61SeL\u0002\u001d#=ܿT)L\u000bԒ\u0017=$8 \u0016\u0016L&0?ڽz#=DH'q\u0014\u0002h*\bȢ#>2cKqoW\u000fyck}\t<RmK3&\u00041\"B4\u0005\u000b$b 6F'\u000b˝\u0019\u000e~h2#<\u0016 \u000b\u0014Є#>CHE\u0016\b(`k7YYwpyv\tMF\u001bbA`o\u0018C-#=T@ǁÔR:=?@uzӨLvL\u001dO\u0010%}Ȕ d2!CJfUh$.* 0E{@ŏpn\u0017\u001cLZ\tq#t|pejӾzG.ŵ#=tFVA\t؀\u0001KkRxʗER&\u000bP\u001eL\u0003W´$@xQ]#>yWvYb\"Љ\u0017\u0010e#Եً<a\u0004\u0011\u0006\u000bva.yˤ\u00041-F6xoZ\u0011\u0001ߵf\u000fT\u000eDկ.%/|l#6\u001a(9'\u001dtx\u0004(\u0013#6XUaOꜼP\u00010Y^ݸ\u0017\u0007aQX\" 9՝\b\u00104I'>|T\u0015\u0004UY\u0006[cU\u00126&r8)3Ӎh\u001fu&\u001a\u000f\u000f\u00101#~\tNW\u0017\u0001T*\u0002\u0019\t\u0003]D\u001d#0%zh#6l3]2_}^BјA\u001a(Rx_L59]}#\u001b^F9 \u001cd֒\u001f'~t\u0011BDe\u0011A:\u0001pa\u0019fv#>6{Wb\u001f<oq\u000bFbҧ)v #6#=cJ.\u001cd\u0012k-\u0011wH^SMh(kD\u0010#=\u0010ɳŨ\u001eWCXΕ\u000e˨1v\u0007cW\u001eQ\u0011\u0011y0\u0013MhL\u001cZ(v䌮<dAS\u000b%\u0015N<&*GmZ\t\u0014]}\"\u0012\u0002^\u0004\u001edW|T*'\u001c\bL!EVoû\u001c_o\u001f\u001e>v?@cnq<\f1F\u000eȗ4c8õs\u001a5(\u0012\u001a)}o\u0019v=]ߒwXe.>\u0010y4]#>~\u0018CU砾!\u0017v !8D4KÎa\u0004\u00056e5,\u0005Ϊl#8\u000b@DD\u000ejGzB,|u\u0018K7q/\\A\u001c9MZb픶N[ឫW9(Ä\u0003R\u0012y\u001e\u001eih\u0003olA\u001eUy*+x\u0018\u0010\u0002x_odAm*|\"?<&E#{m6Фwxok2.B\u0019W\\kFSlnV\fq}8dP\u0016]d0\u0007/v\f.\u0012WtO8x\u000f,\u0015\u0011Q.쾶(AhGs秗\u0006\u001f,V\u0004UR\u001a#6c'ڲp\u0002i)R\t$DF\u001dD8\u001aiC;ꌃ7B/A\u001c&TV$u9ah#>h؉\bA\u0011W5\u001c2XnF١t\u0016#=\u001c/\u0006Vkna13^MVj\u00071yOuOJ\u0011U)S9%\u0010\u0018N#=kxI\u001cm\f,1\u001c(9P\u0019\\QWӎ$\u0012\u001b)7W\"9)]-:]\u0016)Os5\u0007H\u0011\u0005\u0019֩#0VF.\u0016baVu\u0006[\"!k\u0016R\u0007g\u0005F\u0014\u0015醘]\u0004*4\flU\u001aQj,(D\u001e\u001e2/\u0012񰧓\b41ƫ\u001br#=c\f(Onu}\b\u001f<Q11=\u0019xߓ#My\u001bZ\u000eHs<ъ\u0011\u0018F\u0010ANE\bd9&Z\u00048YZ&b\u001dD*;$ $Q\u000bDbfPG8s\u0003415 YaNN\u0015RF7&\u0019-1PN\u0012\\\u0018#>E\u001bLr\u0018\u0001K\u0004ډXBAM;\u0015,Dm\u001eF\u0006bNus]}>0®haՇmr뚩\u0002fFzs\u0011yڨ\u0011G;F$\u001b-3Q\u001e%\u0017\u0012\u001dlv\u001f杄~d2\u001f<[o\u0011\u001e0g!eּuvI!\u0014\b#6u\u00030\u0019\u0002}.4ܩv֬mYQ;9I]wM\u001757@Wb}tE\u0007l\\#>qS \u0010o*tG\u0002\u001e9Zrx0@j)\u0011Ff\u0016ZʨhA*w\u00180\u0005\u0001\u0017/*k\u001bat=eBY\u0019\u001dIUL[`\u000b)Dfa -eL\tjDu0m\u001fB\b!\bo#ԨU>\u001f\u0015\u001bg}%\u0002\u0011k>\\$\u0013s)\u0003za \u0019rTKA\\C\u0018>L\"\u0010\u0018#6ܚ(!#6elPV\u0001t0RLFTeA#=\u001cj=\f P(\u0014QR84\fT׼\u000bM$#=6Jv HA@h^k\u001a\be\u001e`%\u001bDWM.e+4\u0017pY\u000bu3tIUh03Y#6a\u000f7\u0002л#>\u0002\u000b]âTY\u001aJ1#==1<\u0019ۚ8rg}ˁFm~;vTl\"\u0004l\u0005wi\fϞ\u001fTZ҉\u0002\u0010Pl6M\u00048#=TK\u0004\u0002QRY\u0012̙\u0002a<\u0014#>m\b18\u0007.\u0011\t\u0005o\u0003\u000b\u0014\"\u0005X\tҥlM/|Wa\u0005BT@\t\u001bFJv髥kB(9RR\u0018<VtKOi@##=&}3Ѓ)2~yj\u0019ξ11\f\u000f3ٔ\\\u0018.\u0017#>τ!}#]ܳ\u0010T\u001d(5\u0010\u0016\u001d9[;U{;{k'sԳb$k\u0001$pXએ뛇((E\u0019\fVtZ>\u0001u(NXl'%2\u0010\u0002Cn3Jצ#=L\u001e\u0002נ\u001dct\u001e\u0004\\EER\u0019\fv79{cd;\u0010!kb\u001f>7I(Ld]H?3Hjg\u00050ge喌_\u0002\u000egN\u0014\u0002\u0001$^?ǉ`.4)o\u001cTCo+#=\u0013\u000eA\u0005Dyf_jO\u0018%0\u000bɟr;\u0002G\u001f\u0004'8a\"2MXd\\Ħ@\u0017\ts\u001e.Wzr\u0016{y\u001fR\u0004\u0005#6V\u000eEN(\u0017\t\u0003k`-\u0003g7ǆ/S[t=oҡyvC#=-\u000f#=\u001c3Ǖlty7ʹOڵj]P='Bo\u000bjJ%'\u0001J>|9EZ;u\u000e;ygMVmxz\u0004l\u001fF\u0010q(ca1B61fm=⬄n+_\u0003\u0001p\u00187(^ӳ\u0015\"\u001eQYĬ\\\u000e9\u0007qѝ~\u000e\u0004\u0018š-)W.<fT\u000bn#>\u0010>X\u000bd麒e\u000bq.\u0004kSpp\u0012#hѱm\fn\"#6.5@\u00016\u0013Y.\u0002T\f/\u001erK\u001d\u000e&1R\u001e`? m%@\u0004vq\u0006_G\u001c\u001dxj\u0012\u0017\u0010o;E(N8\u001dwag<ƿQ\u0007Ϟhfv\"\u0010\u0012}oᇶv5i`šXT0\u001av3_;T\u0019wg\u0002uaH\u0018e?I70oo\u001eN\u0017k\u001aQحk\u0011U\u000fC\u000f˶bt\u0010߉`Ɋ7\u0004Bj$\u001egL>}ПF.xc]b7\u0014s\u001bv>[\\\"]ۘu/ļ$#>\u0017yG\u0012-v5\"\u0014\\^uevJ*ֹI.}\u001f\u000fNw;,L2ubzu䨓ʸ(<@{9F\u000eB}efVY_y&1촩\u001cxraL4<\u0019'q%>u{툏,s\u00078lFyV洵\u001eAsne\u001e3\\ys98;Ku\u0019g|+mmmI9\u0010C}\u00180EUl1\u0018+.Z~]6ܿZ\u0005ݑ)~\u001fu\u0002#=h\u0016N<\u001cb6A\t(7\u0012_HƳs\u0018\u001c:!nS4ch\u001dBV9ksӷHz+dppw߇V2W/\u0018L\u001ax\u0007\u0015g&'|=k8gD\u001djf\b89=m[6fx(\u00146C3H)#(}n|Yg\u0013.\u0019zwsݑ9wsiY:(8i<f#>1yfKAic:\u00167|B$\u00181+;Y-Xذ4d\u0016ZP\u001b\\\u001csI^2w;NwіFBf\u0016uX\u0015zippZa+Fsȩ1ZgZ՞^v~ZoOx\u0011g\u0011zbr)\u000bezҝZоzOA2%)C\u0016+\u0001\"voԵ[ގ\u00077'MM>#=x١~ne\u001cinwld&dDε|-\t1D`#>Eq-fg!Qߛ3;-?\u001cRQS<\u0016Eq]s\u0001(07d9\u0007#\u0017W#>YhDn2g\u0016,fsj\u001f<\u0014F+{9|v#>3]<ER~7\u0007~\u0005]S+1#\\\fD#2fwWœþMY5ňQ$Hg1\"#=<j|cu\\i[p=\u0006\u0003cP\u001bޞ1ǥǇ1kc!cQs.c\tk1!#>o\u001dp6O.t:?Ekr:\u001dEe\u001a*Qf;o\u001c^\\0*\\`ζUg0nˈ]TCs\u0014C)tc谫\u001d^7M,TiFyGs\u001c\u0015p[\u001aφ\u000eLZuz\b0\u0018֙ṤٶPls9ٙq\u0011\u0003ݠ߇'.yYq6\u0006\u001f7!ڜ6IZ D«k'@yIc{*uXc\tpb?\u0010\f>ރ\\;=\u001eB5x4\u0015H#W{\u000e$p\u0014:\u0002cI\u0010X\u000e!\u0019a!\f\"BZ/E5<\bM\u00060v/D\t Ў3[\u000b0bAr\u0005Gxt;uY#>rfi%=gQZZm\u001d\u000b`,D:x#\u0011#=\u0014t=Cp5t?\u0006\u001czdJn\u0001g\u0011ͪo\u001cBB'cr\u000e\u0012V!b\bm#>`R2\u0010II-k埍Re\u0002)<ᬲ\u0005F\u001f;,~(6BW\t\u0001ǵ˱zv\u0019!P\bס4bjT$iZo\u0015_Q|*kx\u0006#U\u0018~.I\u0013'#>4\u000bF\u0012;,/a\u0018SKA \u0004ʈE_*\u0005JV\u000b$_*y|־\u001ej<A\t#gd#;LQ\tV%oëv\u001c}7nF%\u0016pV/9\u0006Q*o-`rYr'\u0013`$2\u0017+H\u0014`\u001c~\u0004޸\u0011AT8B8\b\u001a$e\"ªn2ܝ'X.bq`\u0007\u0012\u000bg0 (i\u001a\u0002\u0016\bٝLT7F!WqL'_ǯ浗K\u00100A&U:No:t9ɻ\u0005N:@\f\"Ӑ8D\"C0\u0018_]a\u001eh7z9\u000bt9A&\u0013={\u0016}+\u001fOy=L\u001eGw?x\"\f\\ڊs|C\tpj_fܜ#6\u000e[U6!2\u001f\\#L\u001f\u0018\u001e(C@7FV+|1[mQ-1\u00033|w\u0002y\u001bR%qg\u0003\u001ePQ2u8M+\u0007kת݁%,Z\u001d\u0006\u001d7U\u0014|UrǛ\u001dxþ\u001bG>l.\u0001Haݓ(\bh\u0010;\u0018K=\u001c׿=19mYۻ\u0014*LmMC%:ŕO{;7\u001ei\"Ζ&urh\u001b8zcgfr㒩\u001d\u000f\u0015}7#=\u0003M\\G+&8RBf'\f;\u000bL'1\u0001{遨Q\"l\u001c4$Cf8|\u001eQ2\u001eJ\u0002\u0016#.>\u0005`\u001d\u001d˾ِ<z^J珆WR%rr8iM-Q\u0016d\u0016Ss^Wʇqh7nZYi\u0003(CS_cqB\t2cLJ\u0018Jh.Î\b(D0h\u0012xG{)8rA1?d\bc4a\u0017X=>s\u0012'1=ЦT,\u0002H4FaFz>}Xqp0#|/(^3}\u0003\u00163g\u0005͒aK:)(t.3f=rq#=N\u0011\u0011\u0003vx޿\u001e7G2-\u0011(ww\u001cb-c\u001b'#0T%:цIC\u0018GC7uj]\u0018g\u0017u\u0006\u001b`\u001a!-:\u0016\b4eT\"\u0007\u001c4%шf\u0004,mS0B2:9Rz1q2E$<r6;\u001aK\u001ei1\u0011\"#=\\YL\u0005cfP\u0011!%\t|t\u0016Tb\"8p%aAxu'3K%Avp#^yaw@fy\u000f`>p̪\u0014C\u0017\b\u00119\u000b͹\"\u0012z#=\u001b>N\u000f&\u0017V}\u001al|\u000e%4#>80N;ߥ\u001aiN\u0005yIc1pl\u0015̲aqB\u000fA(\u0003{:`8@0\th\u001e\u001b#6#6_=D#>Ng\u0002#nC,]Cxy\u0006Q\u0001;+<39\u001f\u001eC|<YlO\u0005\u0014\u001d䙈xaLs_I`I\u0003j\u0012qI\u0013^#6K\u000b.\u000e.2MiQ\t\u0012l&ioy>18\u0005g[،ơE#6I\u0005\fU`\u001f~\b!\u0011\tN\u001bߤR;\u001fh\u001bG\u001eQbֽϨ>\u000fTN\u000f\"t\u001bT5i՘cB\u0005;UE\u0013M|U4T(gd\tP\u001cbᓼwz\t\u0001|*\tM\u0013<)\u001b\u0007|AߴX]﫟\\,f\u0011y1\u001b#=k\u0019&|)iM<\u0005a:\f\u001cu\u001f\\dbL\u0012#6y]D1߀w\u0007\u001eɼ\u0017\u000fT{nkZYV}ES;S9.#>]\u0005F8\u0004\u0014u|\u001f<\u000e7\u0010p\u001d1{!sE|{2$/5sML?\u0016\u001f}pJ\u0019\u001a\"-\u00012uW\u0006\u000edZ!3M\u0004-\u0016\"#=^\u000f\u001b洛~(Ҿ\u0011W/N9c7(le\u00076\u001d7F\u0002F,(:;hwd}#$ɉ\u001c]\u0012\u001e\u00183,\bV0E\u001aߦz|U@\u000eNiXPzCZl\u0003.\tFFN|ل\u001aJ\u0011ts{\u001e\u0017\b\u0016A\u000b\u00196\u0013U \u0002T\u0012k<'[LT#2\u001bb9\u0013\u001bc\bp\u0011xOMyb#6\u00016ܾ@U*YUE{~\u001f\u000f3E\u00019s\u001di :P#=-r[\tB&]J&\u0010\u0014C\u0010Y% \u0005\t*nljH\u0004\u0018\u001b:\\0XwlmʚM<ht.\u001a53CSŭ7\u0002IPjnzCz\u0015Fâc~2\u000fQ~\u0007h0M<]xC!jYvg \u0001rS&c![|1\u0018l[1!b#>WF֭C\u0015壟}\u0016g>%\u0006w״sr`mT\"\u0002B\u0012'CbX0\t^t:B]p#=ᣎy\u0007!A>w\u0012wa#6\u0004O#6\u0001\"#6\u0012\u00108w'BDVp`\u0019\u000fxU+Pz=\"\u0004\u0002\b\u0010 PMx\u001e߭Wj\u001ewo˘j\"v\u0014K\u00167U\u0017|J\u00021@\u0010\u0004\u001d\u0005>\u0010\u0012t\u0001k#6\u000e@<J<Bl=P@\u000fL\u0012\u0010r/)\u001euCOT'\u0007#>s\u0003A\\azp[\b\u001adW\u001e#\u0014H\u001e\u0015dMBm!Į\u0007\u0001(M\u00019\u0001Րԇ\u000fA\u0018bfts,\u001a\u0010_/Y:`Y\u0014ۗrϣYVM3kټo\u0013p\u0007f':h6\u0012珫}_b\u000e\u0011F@>AV#6\u0005\u000fTT>N_\ty#>9!(\u000fћwoPA\u0004:T#\u000erN \u0001Gő_\u0019\u0002\u000fX\u0013\u0012\u0003sLM;G\u0019!\u0010rvA\b\u0005&\u001d#6w*\b0\t\u001bL 3~B?7)*Fp*j9K\u001en\u0016!M5Wj#\bj^Rwʇg\u00109Hg\u0019tC\u0017Ho}V\u001aV\\]WHŽL[\u001aóI$\u001dJ\u0004\u0004{\u001etN!}FJ\u0019\t\\3\u001c]1$2l\u0018MF5I\u00126dѱD(P֭\u001d$%f~Cd-#>\u0016K.\u0005Rt߻,jc Dz2\u001f|@V9\u001c>Ѩ\u000fpt_'_.QmncI\u001b\u0005s'3#=P\u0003v9ƜziI\u00182q0>a?+YG?\u001f黲\u000ec\t8f>#=\u0001\u001c\u0005!}\u000f+#>N>TXdp52{oqa)N\\d}a٭_\u0014`\b\u0004v%H\u0004'/$^\u000eGy\bA\u0019OcIM\u0004Ff\u0014#=\u001f\f\u0013\u000b)\u0007\u001bg\f\u001ep\u0006b8aƑ `Ѭц)+c#=ic\u0011\u001a\u0013\u0019v*K`a\u0014SY$RC2B#\tю\u001a֧tP PF\u00168>.͘ƌ&\u0003\u001eV\u001e\u0010#O\u0018ʈ/\u0001\u0005GJ#6:C\u0011\fl-\u0019#=q\u001bZ\u0012?\u0006\u0017K\u0006\u001b\u0014g|cu9\u001cXg0\u0011\u0003\"ԭ-3\u0006X8*!X@U\u0012j\u0018Ebd)$\u0003F0w\u001e`\u0004b#=;:i\u0004\u001aV[aHmi*kǸӝ\u00179\u0017\u0005R]-9eD\u0004f\u0007MjxUR\u0003\\Q7QQ\"\b(bP+\u0015eedli\u0011QaPjG-\"lN=W\u0001%qAD\u001bhQ,Mb0%%\u0016\u001e{=4V#=ֈ2B\"*v\u0014`j?M[\u001aME͔`]j;@%FZ!\u000bw\u0011kKwu<5˦\u0018_ 0bn@6\u0015$\u0003\b̰2+3¿@\u0006\u0005\u000f5__\u0012nԗ(l\u001d0X1@\u0007MP#6\u0004\bl`\u0011pX l!N;\u001a\u000e\u0010c\u001f{\u0003M?Ꭻh#>d%#>.\u001a}~ \u0003TD}\u001b\u0007xe?\u0015R}}@LFh\u0010\u0014l*\u0014]s>A\u0014ͩ6DͥKҲ ,\u0001I-\u00011ˮ\u0019i\bs\u000b\fpyGAct\u0003~\u0015\b|Z\u0007\u001f\u000b#=^wc|\u001d!\u000fG\fTA=\f\u001f X\u0011\u000f© \u0017\u0001ݽ?z<\u000fMN|򶚋n\u0016$~m~sZ땂Z=FیG(bT\u001e\u000408#6)\u0015w\u0010DKH\u0003CD\b!\u001e\u0013OI^rƨ*\u0013\u001d\u001b\u0007:I9MrSy楴xw4qX\u0002I\bqzH\u000fC%t1hO\u0018Ӧ\u001f7\u000fL>gWn,i]\u000b[\u0018\u0012de{]I\"U\u0013*vD3 oؠGLz{?=bj`=u|H_b%3\u0011>IW#=\u0019)??=\u0018ܮ$\u001d0,2P'i\u000bΈ\u0019#=P\u000fOҌ\u0010RM%\u001ey߯_guE\u0007\u0002\u000bIjL`67HN aH:\u0007\t#>\u0013\u000f\u0010ˇf\u001a\u001a\u0018\u001f\u0015\u0002gRfʨ{{\t)!o{r(P޶,\u001fv9[\u0006\u0010`G\u0001\u0019\fG\u001eM^ߓ-}\u0001pӸ\u0011ף\b\u000e\u0012@~<\u001f٭z9Y!4f\u001fr\u0011a#=!wz\u000fl?nUN5>A\u0005HOw閇GEV[q`\u0007\u0007;dЉN$@`nEwv#U{O\u001d5#=\u0006 o>\u0019dtxGӽ3\u001e\"OIA:yxm\u001d\u0004UH\u0019\u001dF|ȟ#>*i>lg\f\u0001Ȗ\u0015>6\u0014\u001dt\u0014w\u0012\u0001C}:J\b\u001a3Q2U\u0011||)%\u000e^\u0018u*@\u0014\u001c?\u000eoz:_x\u00035+~__d*'wZ\b}\u001bz\u00155(=ݪ)\b#>Mbd\u0010O\u001b\u0001E\u0002Wߵ\u001alђL<^\u0007)5J\u0001(6\u0014\u001fN_#6P\brlǏ\u0010=\b\u001f?9\u0018a>?Z\u000b\u0011odr:\u0012=DqB:џTk?J\u0016\u0018O?r%--\u001f֨VSU(CN\" \u000fS~a;UԔ\u0019MW|_-\u0016hɔU^C?NRdoNR@9@۷2#=\u001bc%~.(&u\u001e\u0010J\u001eE\u0014Mi\u0006exl>|~\u001f2m\u0019?pdo@h'\b|E#6\u0011gyJ\u0014s_c\u0001w:j觨,Jܺ#=lÆjm_\u0018}j7'\u000fԈ0R\u001f\u0014K5q\u0010H\u0010C\u0006,\u0003\u0016?)#6GPs\"Tj\u000fq[ۯ\u0017[$\u0001HpI.\u0010\"\u000e\fcǗH{|\u000fn\u0017th\u000f!_D_r?O}OWL8Bx>?v5_?\u0002x_#>C{\u000fyֲ\u001ft6,\"<Y6A1g0S}'\u0004?9EPl.ڃ\u0019\u0003\b<x\u0014/=$kZqdB+\u0001?<\u0015k/w՗ov\u001a\u0004agh?m\u001e}}t\u000e<<]a}z\f\b\u001aKF\u001fW\u000e-'OO\u0010\u001d\u0004T}\u0018i?0v&5:\fY$1<d#=\u0003C\u001005!\u0011-as\u0010a\u0006\u001b#t1?d\u001b z\"\u0006r1BDy '\u0011\u0011SZIվ\u0004m\u0018DMY\u0015)5k2\u000fnX)n\u001eE~\u000e}m懧\u001d\u001e[t#6&9P`,t5!\u000b#,s\u001c%^\f\"]R\u0013,'ݺ\u001bͳ.@I#yD(k\u0004#6{{'#>_;|'Ǐ;C{z;\u0017e\u000ef?E<;\"Ϸ\u0003վo\u0017~A\u0003\u001fm\u001cuշ滴}(\u001e6|\u001cW\u000fMy~yǋՂis䃇g!{^>?C\u0014ӡl\u001d\u0007|dq\u0011K\u001faꗎ\u000fG#=uJ2xٗ@`U~8ӧ\u0016w6l|)yq~ wn/;²0Eu+{{ᇫW|{|^\\ƫ/\u001ai|:9]\u0005ÿ9y}08F^\u001eٳN8|=\u0012\u001ed!:\u0014=^\u0004\f}/yCoI2\u000e\u0002~\u0017;/D|{\"=\u0003̓uK#>#=Djâޯcrw~Rr^\u0018~\u0018\u000bwv#=1\u001c1\u0015l\f'+\u0014\u001fW{8\b}t>;;\".\u0003ݱVC|JD'џG.2\u0011U\u001e|\u001eѤy:n8pϠ)KnY޺|0fy\t\u0014ܫ}o8ì|cU/>5%\u001czy\u001f,__ٿ\u000b1OJnGGNn뚎x&\u00103C\u001f+WK\u000fD\u0016˜uǥ\u0017\u001aFUN˴Mh'tɷfw|?^tw5E˩#v\u001emz,/^\u000e7nhyl>@1.b\u0019w+4?\u000f@9wu\u0018[\u0017\u0013v1E\f\u000e&\u0007ê\u0001߄\u001f<=ۻ xRgz?\"njyL;.ק'i\u001e\u001e#=\"\u00173!\u0005ƃ|xWo\u001f/n|:Gf\u001c86bBW?Wf?!O7~\u0002|']?\u0017y=k0\u0014þ1xOzn\u001fl~B3\u001bYO>w\u0011CO\u0001\u0015M?6ϋu#=6W=J\u000f\u0011C\u0003\b\u000eCEڻ'|\u001c})l\u000fl2\u001fآY(<Y)\u001e\u000estk˕E\u0006U\fro]\u001f>]f#=,i{~Dľ\u0014)\f\u0007ϟ3^\u0001[S\u000f5e峃tnG7Xy|\u0014/ Kπi}2wT#>m㠊K\u001b£S#=6\u001dh\u0004GBӷǽ_\u001fV~w]9m}]JG3\u0014ڱw^ʠ\u0013Ջ\u001e~\u0017\u0005G|{/\u0007k\t\u0014=\bݠ_\u0002['\u001f_OXv=<#\u0019\u0012\u0011`\u0017`!~f#=#>'xyHϰ}_-n=b\u0012<+S\u0016|1N;4\u001e\u001e?6\\=;ovZƘ\f,n\u001f͘\u0001O`N7r\u0015F}\u001fv=}{sL\u0004TRN۟Z{!\t\u0006'UA??\u0018Q;KJQ~>MV!O\\\"\u0004?=\u001fewJG{~4\u001c\u0007HXO-<\u0003\u001d㿿_m߇DӘU.|%\u0015ې#=\u001b%#6E-I`\u000eWŧ=]Y/Ox\u00105\u001e/w>A\u000f8\u001eviw\u0018,|=n#Z:f sz P?$??\t\\<\u001bvtj\"6|kş/\u0003x\u0007.*=?b|\u0007XxQ\u0010D\u000ft嫁_7\u001fw\u00011o?\"Xvy\u0007S\")pWs\u0006\u000fz}7\u001e\u001f\\c'\u0004}R\u001f5t\b:ctJ'hk|\u001brW\u0010y=+0]\u001fÈ\u000f2|/\u0017\u0002)xv>|_Z\\az[*\u0013Q\u0004a#y\u0007Y\u0001\u0012\u0005J)8Ii\u0017ޣ6\u001el\u0003P5L\u0016i4$\u0013P\fpA#>O${r\u00101*?\u0003(#>e\u000ead\u0011mϙlޛoeL>⏶cQvY_\u000e;\u001cLR&;Y\u0018o9{0]4U'{Fz(\u0007js\u0019#=TpN4K'1|60G=t@%gg YD:kk\u0013#=\u00020*\f#>paT3goT׹HulծJO#>P\u001f\u000bAgr|13Nl\u001cxs&'Aܙ<\u0016XWj؉j\u0011V 8.mX1ZdQ\u0015W.\u00013,؝\u0019Nʭ\u0017r\u000f\u0017j.\u001eZ\b~\\7.K&Sm2dⴄ\u0011_}ͨ>mLF8\f\u0001\u001b{Jnx:x\u0011PxEp\u0013_$\u0013Ot-}\u0002<SPB/.\u0010?#o\u0003U@{O\u0011O*\u0003*w;2?;B#>W\u0018=\u0005\"i1*\u001cb\u0010D\u0007\u001b#\u001cq$ep#6r>{\bʏv'P8NEj,\u0007B.\u0011\b1Xj)pEfH95\u0012u\u001c:Ru1-\twFԎ\u0003O~spwiݜ\u000f\u001fKan=͚\u001d<\u00156KȔQx?Z<M\u000eў~kIora<;\u001a\u000fTk_.\u001ef\u00028{!r.#p's\u0018~\u0011Ck0# W2(>a%x<~\u001b/\u001c?۸L\u0006b6*J)UE#>\b$gR*^=\u0003$\u001dx\u00057\u001d\u0013\u0012K?f7\u0014|WoV\b./I܆gpcXtn[d}#\u0007-imDJ}Q\\@~'=\u0011&\u0007?\u000f9uIyo<\u00116\u001f>\u001a|\u001e\"zȐP(\u0013G_/|.\u000b3tdR'\t\u0006\u0011DBDETdb\u001f\u0012f\u0014+3\u00042L>q0v\u0006t \u0014`BG\u0003U%]nnuZ&9Add9reZƮ`T2&\u0014,q{K]\u001bqq'R^ˑ̕9,f\u001d#\u000bKJ\u0014:_z_\"\u001fZ]=b{\u001f\u0010VBߋ1\u0005\u00126#6\u0017R\u0010D'䇶`\fqx{\u001eqїZУ.#> (1\u0014)\u0015\u001f>Ӎ\u00019U\u0004BPĒD,=44FMyR6%R.,u\u0011پ5S$\u0001\t\u0001\u0013\u001fwv5Mb4{3'M~o[gd{t0JRC!pd+\\axv{}9}\u0017W6{nڽf/2yGnBSrHr\u0013<}ٔm>_@\u001d\u0003\u0006ߠ{>n\u0006Rx?XN9}\u0019k\u0013\f\"\u0014\u000b7BTpQӳU3\u001b\u0014h]Ŀ9G\u000b\u000bǧU#63\u0007(\u001f\tf\u00163\u001f\u000f\u001dၧD?~B\u0016c_O)L\u0004X6B\t#6ёt&y 'Lp\u000f/_=Bb#>\"X7$\u001fЩ5)%_16tfc#=PJ\" S_س1\bD#\u0012)2o&G#\u001bIBeN\u0010lUFAFT9,mK\u001acieCe\u0013bXQ.\u0013\b\u0001\u0014#>4\"9aSӗ\u0015l$ WPxժi[1q@Ѩg\u000bzZԇ\u0014Yd+CM\u001a`ZRXdheuc#=,t֣4fZj415\u0006\u00133Jf\u000f؂Qə\b\u0012#=A<X#63\u000b\f#2\\%\u0019Sfe\fn\u001b\u0018\u0013NJ#>*;\\&vW7\u0003E4X\u0018BpոU \u0010\f\u0012Xm3\u001cAni0\u0010\u001ak28:X2K\"6g\u00151\u000fZXv\u0015\u0010U5N\u001an21\"\u000f19';疱A\u0011\u001405&\u0012(\u0015\b³V#>)ҊDE?\u0010d\u0003\"\u0006ak\u0001e*7i2@Lg/wL\f:h$GH\u000fKi\u0012_Gy\u000f\u001b,rQav>O_/ƿ\u0001No\u0015V#\u0013\bzϏO\u001ffA \fQ\u001d7H*rЈ0 \u0010h\f~_Wg?\u0018 e\u00071:r\u001fHS/1\u0010HB\u0011bpcMc#=|,\"\"\u0012O|%&kxL:\u001d)ߗgQ4\u001cGry~#6\u0003tr\u0005a@\"u0Yٵ)\u0007\u001bd\bq\u0019mqx>[#6Cr|\u0013)\u0004y\u0015\u0014\u0019lIӊ\u0002#=ؕ\fcFC\u001fˏFo \\!|\u001fۗ\u001dV\u0011\"CD5+qӜ#>V\u001e@LǊ)\u0018;w\u001d];ΣN\u000e #>$o(pRO?W:Ӱryh8׳O>e\u0005`o\t!PJU=',xN Aڨ#6v\u0004\u0004RnM?/\f=\u0003p\u001f\u0003|u}?{\u001ebW\u000fzĊ\u001f>q~C\bWԴ0lѾVϑ{\u00101\fȲX7\u0005c\u000fbpsz]#ާuuiyI!Eej\u0003gXa\u00126n\u0011=2#=م0*%\u0019Py1\u0003ZM\u0018yy>:+v\u0017Q\u0012' +tɌlS\u001a\u0018\u001d\u0002>zQz4wz\u0012\u0015nB\u0019cIF`c\u0018\u0011\t>\u0018d%U0i\u0011F\u001c\u0004e:`\\xȆ4mކ\"#6ٮlc\u0006ѝf<\u0005\u0018Fy\u0010'R5@\u0006,MǦd,\u0004\u0011Sc<nf\\o~VnQR&ųQb\u001aY iѩ\u001bN2sfm*\u001axҙ\"gJ\u0013\u001a1IH\u0010f21K\"a\u0015!a\u0014lcViWy9v䦃a4ԐX\u0011ZhgQ⓯X\t\u0006R,\u001bj5BQ5db:\u0005\u0019\\\u001cN2RJl\u0015aqX\u0016buoyyO\t\u001f]Ι3`ͻҢp#Ha؈d\u000fi\u001e6Hk.Y۾\u001a\u0017gh98\u001d#=bE\u0011Kك+,a\u0004nGf0hFqƠ\u000e4;#6,hQ\u0016\u001628\f\u001aaC%L;!xZ$Ь\u0010\u00102Jj\tZ#\u0018(5!\u0014Tt[[R\u0011\u0011]\\Sa#=\u000fXVKLhUP\b4!\u0007Bv\\n>+Ɣ]&#>`\b\u0005\u0001#\t\u0001g̯n6t.O={\\|\\\u0005Tn+pI~]]/ۚ]3c*]!4@+'\u0005T\u001e(,#\u001d'{+2{\u001eԫCFQI\fy].]'urLw\\˧';7.F6j\u0014 䑸*3)&ef[=鷾0&(ђWZK\u00140+\u001a#=M\u0014Q|}:t-gNGJ´#tBA8e6S1Q<Z9f430$\"#>0\u0018p\"c\f\u0018Dr$sE%p&wq\u0013&JqS!EP\u0015d\u0017D1^ь⍹\u0003\u001aj8%'Y\u000b R53uϮK\u0013#>\u001b\u0017R\u001bT\u001f\u000e\u0014l[Hr7aWn󍅑+^k\u000bF#=chf\u0018\b5#>#>*j0Q@Hri0\u0018,νݝ\\ok ʊuL\u0001(\bIS\u0007s402\u0010 G\u0001ԏ*(\u0003]\u00074#w7$~J#sɌ\u001b0:\u001f)6w\u0013ÛȰǮ\u0017(Q~%\tW9\u0006eB6Fsgzy%\u000epDC\u001a0`\u001dJ\u0012cIFn\u0005<0\u000f7g\u0019u3X%nt(vd\u0018\u0012TL4M*\"6T6ߚmWTb֥Wo\u00197#aZ$\u0011D\u001ecUC\u0007\u0019\u000eX\u0017_)5Ù#>xפמs!`\u0010@\\U\u0004.\u000eM-\u0017[bQb\u0004O}XgUN[5LO)\u001c?YYD\u001d\u0006$(c\u0005\u0007W|(vBwppf3\u001bIv/-OG$\f\u001e9B\u0012c\tj\f¾yJ=DW:Y#6\u000e&LO̖yXaˎӊdw\u0011<䖯$g~9#6p0[N\u0017\u0012Ga\u001b0\u000bXiHK{Æ\u0007VdPt#6ff\u000f3\u0015YЛ\u0006|\u001er#:7u$HJ!#6$'@bQ&\u001acxf)\\x<<ɹٛSG\u0018pJ\u0017eLIF\u001e007MxU9N{tʏBDS}\u0007&\u0015\u0015Φ#6AkV\u0003 5R\u0013Ki\u0007\u0013>x4 \u0005#>HB1Whwգ\u000b9\u001dUiXp:lpp\u0018\u001f\u0011\u0018!\u0019+a[;B'\u0014\u001cqt^\u0014\u001c\u0018х9\u001a\u001b\u0002\u0019\u0010GJF! wuE8Eećs9\u0005m6M\u0012Ae\u0012\"\u0018c],i\u001f\t\fe\u001b2%j\u0004f\u0001܃-˦q\u0018\u001b|[7t׌;ӄ\u001e};\u0019JӠ>,\u0004VR/\u0003g\u001bNP\u000fɗf5_Hǲ^N45-3\u001c-\u0019rN\u0010q\u0017`me`b7'O\u001d\u0012}\t8\u001d(;Ƨ(x'&jOnqcT5U\u000e<twF\u0016zrTa'^.'\u0011oF<5&hTѬcʴK\tf#=TfKK\u00068\u0019\u0003Ԡ.\u001foL\u000f\\n\u001fX2)^*G,I\b^V'3*Y,L k9#)2nϳܳH8ݪP\u001c\u000bsq:qIk1\u0013}\u0010Sj\u001bZ#=\u0019/M|\u0011q.r]i\u0011֧-\u0002ϩ2\u001fmFMu%¿:R?Fuy]^*Jh.*Y.\u0013dkn,wlc.k\u0019|/up#.\u000b\u000b}\u0018џ<d^/\u001a\u0011\u0012SX*!WL𔆶ѭ\u0017Pc\u0018U 7\u001bw\u0019\tEn\u001d|M\u001d\tÝ7\u000e'Z/#\\\u0015DJ\u001d\u0004˖r&R\u0010q&_=%\u00118%\u0019HcY<]h8ij桌ìp|ɶ6%f#=LM0\u00048\u000bU*z\u000emj4I O,\u0011\u0014-f/&i&#=]1]D\u0019uzhnn\u001cM\u0011AUNz\f<zF\u0004*z{8\u00162\bPʷ/ljprl \u0017\u0005`\u0006M}Km9^+\f\u0018bHF=Tw~fJPz@o\u001anp@7#=\t\u000b\u0005>F\u000bm\f_JLV\u0019\u0016\u0019D\u001eo\u0011J\u0007\u001a߅䊰8+6c~<)\u001a\u000f4\u000fi\u001cuC;'\u001d}K\b\u0011#6>şD2i\u0003'\u0001soFqH`\u001d3\u0011@/@*ytm\u001fV#=:\u001f$\u0017`#=\u0004\u0003*#=;G)}w=O\u0018@r\fyBs\u0007P(Fm7\u0014?\u0016\u0011$v*<`$zvb6{q\u001c6VuOE\u0004\u0012,Pu)Qh\u000br\u0012\\\u0017!\u0004h\u0001 FIaxl\u0018\bd\u0005\u001cLp\u0019N#!u}ㆆ\u0006\"іrz|`ixlK\u0015ٲސomc>mR]C ћ\u000eF\u0002\u0004\u000bWݮ\u0019ˊC,t#>J\u0002F{v\u0003\u0007j?\u0013u_І4h}E6\u0011G#6\u0007,g#Ul\u0018kn65$\u0004ƶ#=6Q4flv͒}\u0017\u00101\u00109y\u000b\u0017\\Ǔ7\"\u000b-\bIt~ї߾=\"#=*AI@\u00079\u001e\\{X\u0001G#6$\u0018gn\u0013}\u001avLTD\u001e!QW\u001d\fZaӟ`X5K'\u0010B\u001d\u0006C\u0002Bq#C.\u001b\u001a5#62 yJj#_fԃ<;^\u0018\u000fngoػ<\u001bPB;$+\u0007W葨8z)5Zȑ'j蜔z\u001d8N\u001b\u0018G>\u0003ALK\u000f3#= 7l׋&\u001e\u001c\u001cqD`Ș|́D853\u000e*\b%\u0015z\u0019O \u0013C|FXQ\u0015\u0013\u00155\u0012\u0003́@_\u001cĐ\u001f;L\u0016}9Gq9d[nh{wYӂu0ǉdlBm{uI^̛z\tb̌I:W\u0016f˓֛U=ia\u0015K=iS\u001eG2Du'\u00043e\u0015\u0013WMAx7\u0006\u0019&\"k1՟\u0010ປ$Y\u0005wN\u00124=-#o\t\tqf;7јg'&*90^\\\u0005\u0019\u0019ҧ\u0005\f~\u000eβ2@eiwne\u001dqCqh]\u000bw؅bt8\u0013\f\u001e1\u0016LE\u000e\tb\u0010ϛɡ?\u001ao4q{-ک6b\u001fIȶ\u000fW#yϔ|\u0012s\u0012F h¸*l\u0018i@6ɣ']qe\tL9'U\u0004B\u001d\"\u000f޴f\u001fM;╏-A74{~G8(@ pp#>Ce\u00135;\u000fO\u001f[ \u0019viaWP)M㱤cDi7h\u0007\u001f`P#-ơXT8gg\u001aZLNTPc\u0010nIM\bzԽ\u000f\u000f,ԅcZ>ߢ\u001d\u0019z\u0011\u001dgΟm\u001d,_3)B\f[<vl\u0018\u001a\u000ffgm2lS7\u0001\u00159>\u001e2O\u000b\u000fPku!>9\u00024L\u001a`\u0011i\u0001\u0011E\u00179W\u0004`*_՗]\u0011U[r\\Hf垄\u001f8OpΈˉ\\'\u0012*\u001d#>\u0005#>\bq\u0017y8Nm\u0006e\u000eh\u000b\u0004#6sT\\\u0012\u000fD\u0016)\u000b?\u0015Kd)#=\u0004Jd}wru#=D환\u001a\u0012s\u0014\"Xf0^?\">5?5JF]z>\u001fDp:j1]Se#9HFV_K\u0013\u0005zO\f7=\\\fdƉ\u001d\u0014Y\u0011*\u000fѬImQ}\tjCW&sfPw6Kxat\u001dLr|?\u0012r\\4w0F\u001d\u0013ʟ,iol+\u001e7,\u000e\u0018c\u0005,YS\u0015e66q#\t}<\u0018e_Gܻ\\BV*\u0014Zfp\u001bHNbKAޮ.!L6K cS;\u0015tWfr\u0018\u0010'\u0010Lғa碇gF{l!H\u0017\u000bĸn?*OaCt\u0001kf\u000bmH*eE+sR<u\u0015\u001c])Wki\u0015]\u00034L=%䆵\u0002WDpj|\u000eU'N\u0017wo1\u0014!&,rr\u0017u)[ԝ\u000f\u000bI̖%l\u0019Z\u001cԊƭ\u001aEea9|: px9{Б\u001e|;b$SmB{#\\>\u0016\u001aq۹ϯr@CC\t\bhI=$^@݂o.)8\".KU6\u0001b\u001bʃc7U `CĶg\\>2H0*jQoBYk\u000e\u0013Cö$\u0010e5\u000b1\u0018͞\u0017;q\u0014S'ouQ6Í\u0014AU\"IР\\죾a ;2\u0013THIÎc\u0002`ф\u001b:Q\\7\u0007ƶpQa\u000ex#v\u001aM{V[m?:4\u0007v7Qߒ,܎{VݦEc#=УPX>sƸu-\fq\u001aI\u0007\u0019\u0014T\t\u001efOEėM!m:[U\u001a\":я1\u0006\u0007;oB!|\u0017w6(:R_<ziIa\u0013K\u0010޷o9|D=d8{#^r$O\u000b\u001cal\t\u0019O8~ĳ\u00117h^\u0007tf $t\u0010\u001c\u000e\u001aڬ!ܻ@]ޓ2aAE#=\u0003#>g\b</\u000f\"/\u0016\u0019\"[(%}\u0004\u0019|}=N\\]8 \u0012jD3%\u0018f=w$Y{k\f|\u0017!k\u000bGЗ܎51XAX36u+g\u0007*⢦7\";\u000e6MA\u000fsV\u0018xI#=Myv9ippbI?Y^\u0007];swJD\u0014\u0018ȓơY2<i-EjLIb3\u0007zc;Ԏ\u0011ymxga7*LSЛ\u0005[.XԁC-\u0011B!3#Kx5o\bM#>D/b(y#>R\u000b_fqp\u0002`I\u000e5\u0001)>MɭNx\u000f#=\u001e*!r\u0019#>!n\u0018{4\b\u001d'_YLDXδk\u0015F-\u0002\u001a\u0003UEG% b)LoOdE:ɼ\u0019@d\u000bcfF!䠿\u001dQqkNcօsԈIG#6=\t5~Ѿgn\u0017\u000bB;\u000fMIL^A\u001f\u0012٭w]Tr\u000e2\u001c\u0006\u000b_4Ǌ\u000b\b8ru+JU͡sɚũ\u0018E{1o:ת\u0014\u001d0ulvl\u0019w\u0017s7DL^g\u0006u[Ŵr\u000brm\u00143\u0003+kd.1\u000eƣcZK-,q\u0002\u000f>\u001f )J\u000bzQtx\u001d6klrʟDR\u000eYtŤ@ܥaN\u0017c0#=\u0011\f\u0005ĴZo{tۼ=+Q\u0019!\u000b!&+F\"],/6\u0010bham֔s\u001dM0<n\u001eiVh4hc\u0006u\u0016\u001c5=0bR޹Gm҂}\u001dlQ\u001cg\u000b@\u0011u3%Om\u0011\u0015nj4u0BٸDgm\u000bkmԌ.\brٺ\u0016J\"\"FtC\\m\u0013MyV+b9\u000e\u001bAi/\u001e\u0005wSp`Db\u000ba(f\u001d:a\u0011\u001c4h\u0018s\u000bs#}0P>$~hV~6\u0018 lwg,,.\u001eX6\u0014\u001b5tR3.W$%WS{bY)t%ٍzSZrA\u000fu\\!R7nAB9ol$hCWJwrʨ0xG\u0017D*o\u0013\u0018_\u00018@Bp;o\u0019φ#68\u000b0@/Y0^0\u0004J<VQo\f;-\u0001ZH0ࡧs&{\fuV\u0012?\u0016\f6ۊ;\u0018\t^ÅYADq\u0011tf6\u001d@*c¿NwDIvo8\u0001#6܊/Y:]1DS(*\u0004\"\u0004֤eV\u0016N\\(\u0012<\";%TVDT\u000eEyէD\u0011f@&8\u0013\u0005\u0007WXE8\u000bֈwk=}'{pe'st\u0012wy\u0015SOM}9gHǚ0`.:xZGGo{-\u001d\u000b\u0003X=l\u0014JAf6\u0013H/]@X#=Y,Jl29/B>8D\\|\u001e\u0005v?)n\u0001u\u00170\u001aq͗-ޫ\u001aѿ\u0014Ǟw`-\u0001V<\u0018wkW&k2/\u0018ˡa\tnfw?(/`2/tjZ7kbDu_]\u001fH\u0005,}.\u0019FSV#>:\u0018߿M\u001d\u0004F;cy!/0&̩r\u0016B\u000e\u0017r8\u000eB92\u001cZ^\u001e@9ΙF#6ыv`[\u0002.n=W̵+\u001b\u0014OU#=/a,MWs3Xa\u001b2Pŉɢeez\u0015ɏ.M]6n}\u0018S#>T#74wzp\u0007XZ7Ea80ݜpC(?? f\u0013\u000eܙ2\u0017&2)p#XЉ^u\u0007Ń^#l\u0015J\u001bV\"\u0004NB\u001b1\u0001|F\u000frةT\\i vHI7xud4\u0019D(WX$\ty^nze=>[\u001fLGUsЉk\t\u001b,'դ\u000f\u001bR\u0014|BDg#S\u0001\u000fwrO\u000b\u0012\bI@JR3gbIOBqYٗK{,bpvu1ۼbvᶰI{Xb_gY׬J;-*r)\u0007N&g\b\u000bæV~Sb'HSi6\u00167?n6\u0018\u0007i\u0002\u001c~s̈́4\u0014hM\u0004&\u0018S\u001cX~\u0017{C\u0007P/\u0001\b`t\u0002 ,! \u001az`m\u0014Ppk\u000e\u0013IdDX}uc\u001d\u0006/\u001ag\u0001|C/2><N^\u001dk\u001aa.ϑ\u000e;C*k^dQ׽Hb\fhu')Ewwa'wU\u000bWf7\u0002!\u000b4]#=\u00183LPS>\u0011dcoT:PE|cѵO>2TKko\\qhY^\u0007#=rc5%1n$j$N\u000bNUj\fT'>.T\f#>g$g\u0012&*j\u001b\u001fW-\t\u0012\u000f\u0019ɪf\\#>8\u0011C.R\u0001oT\u0015l3L)\f@候[fVB{)#6'>\u0003\f\u0005\u001e\f/in\b=1\u0007#>pV#oVB6\u0019QR*\u0002<ݛ/DE7am<(AA\u0013\u0005VjZq7qXi\u0001\u0011o[\b+\u0015ŀɐ\u001eJ.,Tػ\u0011|\u0014eV\u0011ؗ7P E\u0006USt\u0002\u0017BC\u0017z\u0019}<A\u000ePwg\u001d7.ԶMl4GvC卷LCQ\u00178#=:\u001ccuD@[;\u0016\u0013\u000fHŦm%&%8>\u0013o;\t\u0011iA\u0017+5\u001aFh#fR9>ߖ\u0002\u001c\"\u001dJ?\u0012qg\u0005\u001f+͟U\u0016UH8e\u001b\u0012V\u0010̍\\8Y$xD6& c1d`\u0004aЀ!ĩ\u000bǦƕ[\f\f(O\u0003<\u0016Uo\u0018`-tGzOԦ\u001crր\u0019uAr>τ\u0019k#6L#=oa6\u0007\f##>0^ti\u001e{pR\u000ep{D\u0011m%\\c΅D\u0016H5pyJq+u\u001a}\u0005pa|\u001c%\u0019,;\u000bd**K\u000fg,\u000e\u0016@wV\u00100x\u000e^}yLEi{B\u0017c6\u0012\u0016<xB4w+Fu\f\u001aK#=>Y\u0005#۳+\u001ap!X\u0001Д\u001e\u0018#>7ǰ>#=\u001dfGQ^yePkI\\#=\u0014e)㉶v>\u0015}\u0014X\"Ԯ#=#\u0002c\u0017Q9\u001d$i\u001fJH_c*\u001ed\tcwE#=\u000e9iX.xs;y\u001e0P1nΗ] 4\u000fHJ3qt6<\u001cG&oiZn(\u0016\u0006Wc\\\u001dH䰼xs7i\tz4`2]#6N,g\bj.ң$\u0019z\"\u0015-3\u0010}\u0014\u001b[ʥ#6\u0003\u001b\u0014 xo^~\\e.e-'l|?f\u001fw]k\u0006a2cI3jJ6.+@4-$uA/ؼ\u001eSZɗ{ #w\f!uC|!h?mg\u0004]^\u000b\u0007:$q\u001dT.+eMUmՆ\u0014\u0013/o]e~\u000e\u001ey,\u001d>}:v$\u0017\u0005\u0005B\u00169r9W!a\u001b\u0013#>dC\u0015\u0011ݐYwxs\u0011O\u000f>#!ш~v\u0014.#=c\u0014HHn}p\u0017mK\u0016\u000ePX_\u001f\u001c@(*\"zj\">wM\u001d!LFQ#6qSx\u0019\fpx~\\\u0017\u0001\u0012B,BS\"<#6\u00101\t5\b\u001e,\u000e\\bt`\u0010#6/s2fn(?+\u001bg\u0011z\u001al\u0001WH\u0005=sf\u0007's\u001e~\u0013lڍb~f6 s( V^G`\u001d#6ޞҞ?o׎;\u001d:fA׫pB`#6LΧ韰\u001da\u0001y\u0004\"U≗͓JIĜ#6R\u0012rX3\t#>%\u001d~ۅ~e@N6\u0017G\u0004m;:\u0004WBCHD2\u000eE\u0012;\u001a\u0002#>\u000e\u001a\u0013e}\u0014\u0002}\u0011Ǩ_\t\u0001\u001fxKB\bw\u0012`\u0017#iD\\J\u0019\u00142̻ؒv\u0011\u000b#\u0011ɝAU\u0005ͻsSv#}RG?>\u0005*g`mvy=\u0002\tsP$\t\u0017>^\u001fA\u0005TSqmD9B^Ȏ^eT]ʊ|{3h>Eת\u001a2S+\u001c\u0017`ޢPXI}#>=GL˱QoK#=0_ڣpw\u0016#6őQ\u0007K%C:ymaS:\u0018tr%Sv8c\u0002fjKma'#62\u000b.!y\\qiP0Ǌ9UϢ\u000b\u0018\u001dD\u001e\u001eGt\u0012ԏ=Wy\u0018#=HXI俷\u001f\u001e\u0010sݲ2k\u0006#g-Pa\u0005\u001d=\u0018#T-\u0007A:=WF\f:G=\u0017\"𤈑G.:?w1ߊ\u0017f.Y\u0003cLDG3\u0010H?\u001aESߙ\u001a\u0017\u0011AYw\u0012+\u001cz_v\u001al{O\u0015\u001d0˘*k|Ci\b\u000feKZ_\u0016#>uu\u0018\\\b\u0007Q!\u0010QuCA1#>v4Z`yKrv,\u0014\u001eX:]\b]&LC\u0012Te\u000eo̴^ʂ\u0010R@%\b\"\u0006UM;|b'?\u001dF8\u001e\f}r2\"ݜ\tG޳,;#>R}|Իlm@P\u0018}#6\u0003\u001f\u001fjƥB\u0013\f}\u0002\u001fx=|$k\u0015]lMt8)g\u0005z(}\u0018ZG\u000e:c\u0011x<^\u0004\u001e\u0016!%\u0019n\u0007x\u000fQ=CW#>J'#>v\u0018\u0002\u0001$\u000f{il|#>8Sw9\u001fRo\f@\u0014b#6]=Bn$´D\u001d=?E{18>򖐷|4*m\u0003+#>Cׇ\u001aGbbxȉ\u001eTԠy.w!n\u0014\u0010\"P蘾O_ Xb#h\u0018u~q^u6GnQ~<\u001fn#.H\u000e}H\u0003#=\u0012\bý ݐC=z;'D+nա\u0012D\u001dy|f&\u0005\u0012\u0019g\u0001\f\u0018gK,gf\u0003&v\t(GP(\u000eor\u0012rqw,DSP{O#>9#=%v6+J\u0001\u0016\u0010\t\u0006\u001f`JlRXEE\u000f=#53ջn\u0016y\u000bɶRd$\u001c\u0014\u001d#\u0018e[*zC<+YBZ^\u0003\u001aSe\u0018\u0017\u0010hB\u0010\u001a\u001e0B8R&QK¢[)eox߾s[e[\u0012_M\u0014ci|\u0019Ƅ8cC]Ҽ\u00039em(,Cy^G\u0015l\u0019Vf;W>?\b(w[R#+\u001enBi\u0014@b}\u0006\u0018jdj6A;;x5ReQPҍ76\u0016i+#>\"#\u0012+Tg\u0002!#\u0016\u0010\u0019Fk\u0015>j(JE2\u0015|tyS\u0011<;]%Ш&@.;ƌ&w!~߃Z\u0017dvsV<\u0013#]?\u001b#6E\u000beX\u000f\u0012\u0010\u0018pr\u0014b\"2F\bo\u0011M;\u00177R蟂ǀ@#>\u0006'0z#\u0005H*\u0010qN\u001f\u001f\u000e\u0018K\u0018J\u0013#ԩڅy(P3<[[))6ޕop#>Suy\u0017!=\fm{b٪`΀C&6V~\u000bc\u001exǜJვ=r9\u001f\u001f\u0015 7V\u0016#=ORX\u000fy-1럞\u0011<f*\u001cg\u0017nk0*o5҅{!dR\u00130p}8GyԤ7^e\u0003<9,Wyc\u001bXVz;\u0017yiE:\u001eZݎ\u001d\u00159\u0007\u001cW`\u0004\u0011#>3CR|c\u0006MAp\u001c#6'f >\u0010`\u0017r\u001eʺpv\u001e\u0001\u0011FivLA#=F!\u00020#=+0{vN0K\u0006!Wg0JU,+C~#0\u0015\u0013bd\u000e\u0007\u0006\u001c\u000e\u0001pզi\u0015tue^\u001fÂ\\zk\\%b\t:sFOx)\u0005|\u0015\u00074n1?}6ttnQ\u001e\u0016JnV{\"5CgY+ಣC}`\"u٨9ݰkZA\u0013\u000f\u001c!tQB#=H\u0017\t\f)U\f#jL]t(;\u0003\u0011C\u00053\u0014[1)\u0004PƵBOZ'`I\u0014\u000bs2%K\u001b~T{\tb\u00148x%@O&s댪\u0006\u001d'\u0007\u001et9q9sA2ǄXyєiΞV\u001d6QeFmq\u00153\u0005SZacl߄3\u001e[#>aN{bgd>Uw<CQ\u0002'\u0017\u001eěo@9\u0017-@c\u000fHn8d)lH=}@D\u0018?We\u00065kvn\u0015È\f\u0007=nX{\u000e?s섻=1~L#\u0016f[A\b!IK\u001c\u001bt\u0001\u001f\u0017\u001c<\u0015\u001f|U\u0015n\u0010O9g\u0007xK\\\u0007_w\u001fp\u001dqI81fd\u0003\br\u0016ˡA$)/ҿ#6\u001eSI:H\u0010#6\u0006K\u0003}Ğ'\u0007\u001f\u000e\u0018r92\u0014D\u001edU D\u0014#\u001c ,|#Aג6~f#>\u0001$DO<\u001eg\u0001\u001f\u0012%#6\b$,C\u001f`\"~芈pA@\u0013˄z~xYDxY\u001f\fC#=P\u0011^\u0019N_\u0013ePˏJi\u000ebdl\u0010\u0004U\u0001Iֈgt$(Tv\t-6K\u0012)\u000bqۍ\u001em5׎-d>G<\u0010\u0018{躰$\bj\u0006\u0006+HOU\f\"Z\u0012*\u0004\u0015#>#v{u\b;=\u001c\u000e@\"WfO~H7\u001f8_ټ{IZ!\"KMom)3Y5T\u0010\u0001\u0010\u0010\u0002\u0001A~a\u001b\u001b?h\u000b\t\u0002Gw>\u001cz8Vk\u000e\u001b8t\u0001SPć\u0005.Au=?ΩFnHNU/ݩ$1B7EXc\u0012\u0005a\"\tB#=\u0012*\u001b\"j@sݶx^ߓi9T~*$B2^}_n\u0002RV*)Z\u0002\b8T\u0012}bh=u\u0011.d&AU4w\tv>B\u0003Y\u0011,\u0014%\u0001'X\f\f01\u0016\u0002Eh(ĆIӮM9\u0003R;H\u0006l\u001eC9L͠?d::bUudX@EEWS\u0013%?Oo{6Hsi9fbR\u0006e~.z?SקD\u001fƢ(\u00034@o'oÂE\u0019:\u001dG\u000f\u0010\b,z.\u001a\u0011EDذl]dhia\u000eR|25[giÐR}*#=\tk OS67#>8\u0004\u001c'\u001dd\u0011:kl;0i\u0013\u000674>6oR>3ܗ.l\b\u0004\u0014\u00044nd!6\u0018<vv\u0007m_Ul]t|UVU\u0005e]F,6\u001a9t\u0003\bM\u00103\u0014\u0013\u0014AgOM%?z\u001e0\u000e> Q_g3\u0015߲k3y6{l''\u0006\u0012K~{ \u0019\tHGh\u000f\u001f2zG_\u0018Owه\\Jq\u0007P#ً\u0006\u001f\u001dSP\u001c\u001eb1\u000fX8(d#6uR\u0006̴C\u0003\u0013)\u000f\u0010\u0018\u0006SX((ڼ?ē(OQDaە䟂̆#6~\u0011Jp!\u0007qi5T)/rG/T\u0011\"Pk3| \u0003\u000e\u001fR<y_?l_o\u0014>r$VB0 ܇s&}DǏ\u0011q?c'Wľ}\u0001d*AصAb#>aX* AŅLG#>ČT\fdAvV\u001ckblvqe?VsG\u0004TI\u0014\u0011\u0002`u*\u0002\u0001\u0015z\frib*rW݈z\u001aaH~'\u0017G#62\u001d\u0011Vb7\u0018A#>9ǫ.=/[شch&wu\u001eH<[`\u001cvǵ'pڗJ;o\u0005{BkWɶ{0Uƽ\\\u001f\u0019rEЪ7<Q3\u0013\u0017\f#>ǫ\u0006\u001d^Xw$$6I7z3Tn(0b0g\u00046PB\u0018^5\u001f%PL#>h\u001a\u0013\tL:bg<^\"?Zn(.A#6\bG$9n\u0015\u0018-|<\u0006A#ED\u001a@*ҋX\f\u0014\u001b(\u0005\u0013\u0011\u0019T7l\u000f{GJ*\\>9bs#=0n7\u001eT'\u0018,`/3upw\u0005O\u000ey\u001cI#>U\u0005\u0001OGk#>\u000f\u0013,\u0005Y 2/ޡ/\u0004 *(=<W}\u0011\u001eƾzoIPv\u001e}rj\u0001x\u0011\u0003WBB=Lwb\u001dO\u001c=>֩j=l-VwZ`\u000f#>9,7\\<f!C\u0004ld\u00197ZĜ#=l\u0015T#b#>w\u0019Q>\fb\tLxno(/c#604+qYS)#60\u0004 %<\u0003\u0017`ߣ1wnY=^ *g0 \u0012(\f\u001bVΐ\f\u0014\u0002\u0003N\u001d}\u001c:\\h%#G$#>\u001eQ\u0017, ;|P\u0001E(\u0010w?\u001f|37qv#6y\u0002\b\u0006\u000b5\u001c9:chֲ{#魪fN\by^˩\u0016u$! E6^6@ZBWf[}\u001e$\u000b#6|?Iǝv=\u001c&L_aT*Q0\u0001\u0019E}K8PGreX\u0012Q| 2k$wBU}n$a^r\u001aqh\u00160\f\u0016\u001a\u0004߄e7q\u0006\u0005Pvᖨ\u0006\u0001kzp@g7āY\u001e5ˮ\u0014Y\u0002H\u0004ZN0M-feYL0#\u0016mڦ1H\u0019M\f=\u001d@\u001bF\u001b\u0012L@m\"P|\u0019c%'\u0001\te[\u001b8V7p@,\u0013n\u0005Wq[PŇ\u001c#>g\u000b\u0007O,ș\u0010~\u001e\bjJl+}\u0014n9=US\u0017\t}7d\u0018\u0010AIG\u0015\u0018\u0010\f@*L\u001d\u0002\u001c\u0015$i^PSM#6\u001a\u0018~\bҳGW\fD{4#=\u0016s$\u001ax8,.~89cPA\u0004m\u0004*<\u0007P\u001f\u001e>| ߄#=\u0018|m;\u0007À[$x\u000e\f#6\u001axX\u0004D=9p\bb\u0014'\u001bJw ǅ\u001d;#=6\u0002(0S\u0014[\u000f &=^\u0014I\u0016;s\frBQ\u0001T)\u001aLF\u0007sk(œ>}Jn@K\u0003Rn\u0005E)\"n0UDr}\u0003\u000ed\u0016A\f)NC|h#P\t0\u0002{ӄ\u0012\u0018b>#>K\u0018N\u0007\b\u001ct&kC\u001c&\u0003YL\u0013}qB#=~D{-QvVl_f3P2MboDO7}vdB9\u0001D@\u0006\"H`\u0007)\u0006ᱧ99\tY1T#>`\f\u001fkw-\u0010ܲ\u0018}\fp\u001dz\u0004\u0015\u0010`!\u0010->,\u0012\"\bќ[GX\u001eϴ(у'Q\u0014#=q\u0001\u0003\u001c_kaN\u0007dLl\u000e= e_l\u001d,|Y\u001d!s%#>\\\u0011S\u0013\u0015\u0010\u001c\u0006,1$\\-\u0001v?\u0003(\u0019q8v]X}ڡr=U0ޏ)Tyr0))-\u0015\u0007Nmp\u0007(v8q<,##>aaߩ!~:S:MT&\u000ea\u0012ouϬ\u0017\u001e{&t\u0005h`,x\u001a\u0002(%l\u00102\u0018&b?WV:Acq$41K\u0004[u%oRj\u001dv^6wЈz^d\u001dP+\u00066 (y^u1T`\"v8ԖP]/\u001aJbީ#!g,,\u0002q\u0011^ۖtm1ǶI\u0006\u0019\f\"'STe]\u000e5S\t\u001d>u\u0006<XMoٮQZ}?~ޟ%tqj=LB#tf!8\u0015D0\b!q)̃RI\u001d{\u0016|ZnA\u0012wv\u0019+\u000fBQ\u0018\u0005;aac\u000e\u001eY#zòƂ#6wu=P}n+v\u0004\u0007X3hy#\u001bȗ\u0019^|\"\u00030)6(7>\u001dFPF#=m5\u0006S~'5\u0006$\u0001ܠE\f\u000b\u0005T^|D}OJ?FoTSX3?#=`\u0014wJn\u000e\bhNp\u001e \u0003 E^J\\H銹I\\\u0010\u0002n!\u0007$>ڀ]\u0004G~z\u0013ٔіEG\u0019U\u0006\f&Y)ՓASqY嫾\u0010g]b\t\u001b\u0007\u001d\u0010Y\u0019sx!:+e}#=o\u000baH\u0005e`H;l\u0015es\u0003#=\u0004#>cAp<ݒQ\u0012IL'뢅NUA=V#=,vB_F:=Aw\u0014#>%n}^6\u00107\tvJŔ/t7֐5jo\t\u001bB.)\u001c\u001b{\u0013\u0004\u0011+$#63ɽ]O\u0019 \u0014#=5\u001ce\u001b2\u0017tFD1D\u0018jqz6F@\\2ih\u00016Ҷ\u0012\u001ei#=\\ \f-$(\u0013\u000bK\u0016`-H`s_P\u0007A#xp>#='҄~wǙ\u0014J#>=5\u0011Uc<W-\u00149cwɱCZ\"u\"\u0010MƉtLZќ\u0015홞\u001cU?x,H\u000e\u0016xp͑\u001e\u001f;\\Ws:R\u0004M`D\f^g&l\u0004gKhΏ1%ǹ-\u001eL\u000e\u0004\u0018\u0012\u0002ј43cto\u001c0Z\u0010yϺ\u000f\u00038\u0001A\u0011\u001a\t ?#6\u0011\bP8\u0004 ߞ:ݗ@\u00010YS(D~N<w\b[/#=\tz<a/\u0019(\u0004#*\u001eA꠰`v2[#6;|#=~^<S\u0017P\u0007N\u0018UI\u00077)-l+jԪ\"z{\u001aO\u001a}l{͸#=1C4VD\u0018\u0014X\u0012Y\u0007%!MńnEyN}n+>w@(;tw1܊@GrI2\u0014\u0001k\tsJU\b\u0004 \u000f= xo7(\u0018_aH\u0016Fǌ\u001eH=dF4c0?(#6,\u000fnHK~?4T}}EUT\u001c\f\u000e\u000ed?\\\u001b8(Q\u0002\u001f\u0011\u0011BWFX܀󳘟0/\u001f\t\u00075@}b@|!\u0007ҐE\u0002*>ah\u001eYJIoG!\u000b \u0004\u0012x#AS1z\u0001h\u000eȪ_0ޔC~/#>\u0019\u0010O=L\u0011ŲDř~5\u001etQ\u0010\u001c\u0014H?\u0001/\u0017\f\"\u0015,R\u0005H\u0007#>^#>#>ݝG!\u0002B\u001fEp\u0012o\u0003)t\u0015et\u001f0c\u0017Ȥ$\u0001(\u0019;xB\u0010DI2ۨ#6[\u0013x_H\u0014*U^C*͐\u0007zw #6=\u0004z(BR\u000eNg\u0014\u0005HبXq2\u0017;`(\u0002$(\u001d,?\bHiُ$\u0005\u0012J}5!Bl\u0017;NAkɛ~V\u000ecMb\u000f+\u001b}hJ\u001dM>=v6\u000boV}ԟFd02j\u0003ɾޒ 671gg4o1ݭf\\2Vvjk_=Q%`;P+.Dt7UFD9\u001a\u001c\u000b\u001eɬ3Σm,k5WkWJyqu\\8dcq77\u0013-̻Ba\u0010$q/ln\u0007OzCZ^\u000f\u0006w3p\u001bJ-qpf>\u001d]Т#~0ٕN>9z5\u0019=/z\";ENS\u0012Pknc\u0018IVFZ\u001b2kzDutYTƔg!OO[s8Yw\u0013/m:\u0010ǌ֢4R(7\u001c戌Vt\u0018߄\u001al\u000e_;pܱ(\u001fu%\u0019t\"6B%\u0006~\u0014\u001bsZ\u001d u\u0012\u001bo\u0011a+qּ\u0003D.[{\u0015a~T̮Gmϖ\u00165\u001a\b0O\u0001\u000fL1|(\"w{\u0004\u0005l,;[LC&zFZϣ!jk\u001cv\u000e<q\u001bZwb%r_O*I#=Op2_\u0016щ\\\fpm0z\u0002O\u0018$;\u000fu&q9*#=\\#Z]Qv\u0002(E\u0019*n1w\u0005\u0004$׎yYI6kz\u0019IXrd\u0018\t\u0005\u0010\u0001\f\u0002)0\u0003\u0004R\u0002\u001b#'\u001eO\u0017's#6$=&iC\u001a{L\u0014\u001dҡ\u0001.`fO\u0019(#Q\u0007(ay\u000f?\u001fo/__?g?O\u000fW/c>_?_g?P#6\u0004#6G\u000b\u001f\u001c/EU\u0002\u0014\u00170\u0010+\u0018\t]on\u001f[ˠ]uï2u\u0004WRa\u0003a$8j\u001f5\u0018\u001c\u0004\u001f\u00130~?\u001f\u0018\u0001X\u0018\u0007'ӡ\u000eHhNt/\u00054\u001dΑ\\WfptFV#6U4#>!)LC\u0015)܁\u000eh'fE\u000epDlBu>\u000e#6Yq8:@!\u0010\u001bAw\u0004J;2H\u000b\u0012_Fa\u0002H pNh\u000eG?#='wтy ni)\u0003\u0002C\u0005>6\u0001R#6^:\u0017J'\u0002N0@\u0001^/\u0002\u001eO5.$\u000eP\tP6飢\u001d|\u000bàvsW AB4tAbH<\u0007J(r\u0019n#=\u0018,7\u0010(6lUC3Yg#W.\u001f'?ЋWϏg7r\u0014ӽS@&љ(\u0016D*,\u0018\u000e\u0010R3ҵ\u001e>91\u0006%G\u000f泹 \u000e1\u001f_@\u0019㩁4\t\u001f7vD3kӧɌ\u0010epl^Zni\u0006u\u0004\u0004ҥRq;\u001ckf#G\u001a\u001dcE>.\u0010=x\u0016(Y}jEe`g?Z_eUđƃ>:$Vph\\^q$$s8B#=&\u001b\u0003HcJc@\u0014.\u0013ra\u001ev[;&m\tT\f\fC4\u0007-]\u0011~C0\u001d\u001f]q\u0005ɿztM\u0004\u0006y9\"#=FU\"\u0007h?;h`}\fz\u0002qG{+f'\u00186\u001fe?`hmd!뗸\u001fY\u000eZ[yN/\b\f1EG\";\u001e\u001afC);\u001e˯eS,7\u001a\u0013RN\u000ex\u001a:$\u0019$41#=΋tv1]5\u000bKc\u0004:B\\'<\u001b;֒cӚ\u0013S~N\u0002yo'#6x\u0019xY)>d>\u0011\u0018[\u000ehL}w<\u0003#=Py\u000f\u0004tq\u001c\u001aBPDHf\u0018\fA2@\u001035$\u0013CyxIN.9\u0019 \fm$>La\u000bA\u0005Ѩ?fi\u0019\u001cT<\"tB\u0007\"H9\u001e!zt!\u001e'\u001d'\u001el,\u0012ntod\t8DR1 koYVXP\u0007~c\u001c\u0007kb,HZ\u0010p\u0012$(gslǬï\u0015*R@!H0\u0017t<\u001ec҇g?F3\"\"\"2v`Cͤ' \u000fPSDx:'\u001c)]b\t(\u000e\u0002\u0017\u0013\u0012\u001aJ\u00110ݲc\u0001|l>$v#\u0013\u0013 q\u0003!0\u0019$\u001fA\u0007\u0007eOD\u0001T\u000eP\u001e\u0019%t\u0004B(z\u0007_fͶ#m\u0010o\u0012A90B`hGmSR5\u000famՐIl'/\u0014`D\u0004ܫAu!$Y5 ;! f)a#6Dl|.\u001fXz#>m28\u0003ʥ\u0003\u001fRm%:D$\u0002Q,8&d]jP\u000fLdX<6ơ\u0007>PBK͞s\tN\u0006ON0GY[37ѳ-ŭna\"w\u0012n27\u001eb)\"SMk\f4Y9#6#>u)j\u0005CvSa\u0001t@;!-\u0014x \u001a`\u001d\u0003R\u0019\u0001J#6M\u0012\u001e''Aī\b\u0012\u0010C3\u000foMDdPQ$1#=D\u0011T\u001e^eNwk\u0013y$ҥ%#><m4e\b\u001eSGS&m\u0018uY2{\u000fZK\u0007ڶ\u001aDYs\u0018s!p$xX.\u0010|>UQ\u0018HkbxǮ\u001b\u0016Q1vx?h掎\u001eb\u001e<\fh(\u001a\u0010)f\u0011]ˡ:\u000eS\u0017\u001d82]*x\u0005\u0003| b\u001edO0\u0010\u0018qsY4ޣQ=TW\u0006r#@A^;̇#^5&e\u0007a\u0019z\b\u001eL\u0011`*\u0002S#6\u0016\u0012o\u000e]Ag-M\"\u0012qc0\u0007\t\b\u0012,O\u0003zd\fbISwT\u0014\u0004Ha\u0005$f(^áyCuuQx\u00018\"ZB#=P\baF1XZ#=IXIJS̈\u001eɶP2\u0013$vhjV\u001bc'YF\u0003\u00185gt;wo\u00061X{\u001ci\u0004#\u0001t\u0005|xp$;uifb#hxD\"HR\u0014!B#6\\q\u0019\u001c\u0005Xqd0m\u0011\u0010\u0002\u0010}egcv |OWzc#6fQ<niq##=y\u000e}U>>\u001f3\u0001i__\u0007Ý#6+d:j/F#>\f<DDY\u0006I\u001cԆ\u0004n#\u000e8 \u0018N\u0019!C@{\u0019\u0004QkH#6o\u0019nAj=\u0002#6L\u0010D\u001e#3\u001f5\u0003G^/SMA\u0019y$\bs8ڼĂ\u0016 L*zQ}Q\u0017\u0019۳Ib\u0006c$`e{SR,\u0017DB-(\u0002LS_zE@v,\u0019s\u000es>js֍|̚Qp|vC5w/v{#=l|jNF9uE\u0019#>\u0003[$U\u001e\u0014R\u0013ؒƥ\tm\u0006dP`3Fm\u0004G\u0019N\u001eIՎϽ7fc\fr\u0006Q\u001c\u0013Y[b*\u0014;>EoN硂yg#67)3ھe.]mx;Jy&̦\\َ2Mdl[,tXoWnjs^C:\\i\u0011OjA\u0005DNn\u0012M@\u0006/F@\u0014\u001b]KvK\u000b \u0001\u0013\u0001\u000f\bsT\u001aU\u0012\bc&iK#6\u0019V\u001dI3Ň9\u001fv'wKSVҌ\u001fl\u0012A#=;_\u001dO.\u0010/H3=*n\u0013C\u00144{s#6\u0016\u0010j)O\u000bq\u00068\u00057j\u0016qC\u000eP\u000eP\u0019g#6co0~\ta\u0002@>u#=pq˚44\u0002\u000f#6'0\u0001^Ev@l^\\;\u001a\u0015&\u0004ѧT&N\u0018o!m^F1㒏w_>I\f\t(JB+,6\u0011]鈩iB1\u0005:08f\u001d϶5D9#>Ud\u0015Ed(+pi1bW%`Ym#>\u0010D9h78N!3\u0014u/CX^=~\t#bk$}D\u001a݄\u001cI.#=m\u001eZ\u001a\u0014wTnHi`ؑ\u001errpmVAd\"Qp$\u0018\u001f.\u000eV\u001fAX:c\f\u00119\u000416\u001b#>\u0007@LG\u0011!\u00132\u001f\"`u\u0019\u000e\u0017k>=_\fQNq7ס3xL\u000bϤ\u001ah巹\u0018`\u0003\u0010%@p7g}\"L*m\u001d\u001c}\u001b2vdNd7*ߪb%DwR\u00051\u001bpk+[\u001bR\u0015͸::-s\u0012Wg,:\u001a{\u001bfG*Y|a\u001b|\\od4-S\u00016Z\u001d\u0018?_'(7a^c=\u0018|ªԏ?rsad\f\u000bL}nv=\u001d!<\u001eIGsk9\u0011Fv9\u001f[\u0005Hy\u000f\u001aǓמE\u0005{cy\u0004,I.#>,\u0018ب\f<Dª 7\u0019\u00075!RAB\u0013\u001d\u0015#=\u0017(XEt;\u0006\u000e)\u001f\u00108\u0013wX\u0011mYI\u001do\u001d\u0003~h=T\u001f)[\u000erwNĝP08\u000fm\u0005\fbDTCa5;\u001e=\u001b\u0004܉FQU(cҿqwe\f@sUf\u001a`\"69\u001e\u0014h'%;ew蠸K0\u0019\u001dN/b#6,3*OOocޙ1\u0004}\u00199b\u001eqy<\u001d\u0004Mq\bORmD82|Ϣ\u001b\t_j\u0003\"gT8wHy\u001ag3 =?\u001bO\u0013B\u000e*s\u001b\u001b]9c~\u000f\u0011\u001f\u0006\u0010܇w:0&\\\\:&0/#.\u001b\u0005\u000eX_v|\u001eE>Z(\u0014b\u000bT\u001a(;/fr\u0017ɖn\u0003h\u0003#=\u0007\u0017v@vDyu\u000f\t0.%KT{\u0012\u0013PT0'tT\u0010?m\u000b\u000b%\u0002L\u0006j,F\u001dےDF#=B\u0007|6\u001b\u0010|Prߡ}Xl!\ftz\u0006`Է^zv4\u0018\u000f\u001c@{<\"I0|NQߣ\u000erx)R\u001e\u0007y\u0007)p#>CP`h\u0011\u001f!\u0019~\u000f\u0014!F\u0004\u0007=r\u001b tf\f\u0019f\u0019`\\*C3;\u001aSRLAHEw\u0011\u001aM\u0019Mm\u0001f\u0007}x+6Jys\u000bOo]\u001e$t\u0007\to}A\u0017\u001e#>\u0010\u000b0>M!>#>FC!=C\tGGxEfbaâ0c-\u0007I2'woS\ta>\u0002w\u0004Pw#>\u001d\u001e~ ar\fzz@w\u0018\u001f\u0017_Ts<#=S\u0006(\u0014\u0005\\U\u0005I\u0012^xS5\f#6\u0002:\u0005\u00190EAq\u0001R\u0007\u0002\b\u001dELa171#6gEd`\u0007\u0005QבWΌe<̏k\u0007\u0018\u0001aPCk%\u001673d~Pr-+\u001c#=\u0004z@\u0003k0#6[ J1-b\u0004z#>fY-G:0OD.#;Agz\u0013k9՟Le\u0003Ős#6?e<\u0001dA\u000f~~<\u000ebX#6oB\t)\u001c#6%\u0006׿d,/`\u0012cj\u001c%ApG=\u000b#=n?}Oѹr&iE&n\u0002OZ͔8!\u0011\u0013؊#>\bs,\u0005E\u0014z\u0017\u0011}=BYEnG\u0012vKvB\u0011\u0005o=5#6Gs#6G\u000eAG֌~\"n\u0005e;{\u0014\u000bb\u0011p\u001cu]2rD\b\u001bc]>L\u0018A<~d[\u000e\u001f0t:T3\u0006\u0002*\u0005\u000b{r#6#6mN؃\u001d#=\u000e\u001cU\u0004yͯ-\\#6\u000et;Sg׾ˢ\u0014E\u000bD(#>T\u0003D(\u0013\u0011#6`c#$$syb-x<\t\u001a\u0014/z\\:rs<]r{\u0005k\u0014\u0001^ä:\u0014Sӽ\u001f\u001b\u0003]L\u0015#=)$P\bD\u0003\u0015\"5e`X~\u001eyG#6O}lz٩GO\u0001>b\u0001??O$d0G\u000f?WSA!$.E\u000bMI₄)NƌYC_;3\\ݖ0\u000b\b\"\u000f\u001dF.\u0012CVT\u0015\u0012HK/#6XB}O\u0005]\u0004!Ss^;~%$tp\u0015eM\u001b\u000fGh\u0001ѐYwL#6\u0002j;:H\u00158 E\u001f\u0003?;[×!EGޏƿyG?P\u0013/T·Iܴk;=|+\t/Ԭ\u0007Vpx\u0015u\u001cy{\u001d#6A\u0003*#6R\u0011Oh#6(#6\u0007\u000bEI\t\u000e\u000f\u001f\u0003<\t\u001brB#6J %\u001e#>vz]b\u0010\u001f\u00192䔬#6jptD^\u000fG+\u001aJ8ÞH\u001c}\u0010[_qG\b^#B6\u0016&_f\tE[4\u000e?B\u001d#yJ\u001e&#6xv\u0003 \u0007h\u001fG#6\u001f~_>N\u0012\u0010\u000eP\u0001\u001e\u0013?I)\u000bt߼V}EkY\u0003\u0001ހnY\u0014\u0003*J<R.[z4(\u0001R\\\u00010ru\"e\u0015H\u0003 []u\u000e:\u000b#>\u000fA\\ǕU\u0010m\fU:?wFuIԏ~T^^3|+7\u001cu71A\u0004__uYm\u001b\u001f\\>JG#>\u000e#W)\t\f\u000f\b\fb98\tu,m>Gr'^àZ'MW\u0006azhWd\u000f\u0005h~C?#=ǎ2\fV:\" #6\th\u000fr4\u001b\u00180~<ۭmSڰɸ\u0002 \u0015\u0005\u001a\u0019\u000bI\u0018˪\f2 >',\u0002n8N\u0001\u001fbGr$2zx\u001eqFI#>>^3;\u0006#6t\fw\u001b\u0012Â'L|Ҋ4(\u0004?k\u0017~~~9톖!f\u0002 \u0018Leh&\"HӤ%rݖ\u0007#6\u0007Ò_?o!oo*\u001aH\u001f\"\u0004\u0010~5!>'~#=ZOtbhgo6! \u00195R/InɥOD\u0002\u000eY\u001b??8xŌ\b\u0003@B\u0002H\u000e\u0001?n\u0001@\u0011^Y\u0018_\u0001\u0013t\u0007{F#[-tw݂'d+վr0B?Y\t;l\u0007?\u001ceHd6+a7|A\u00026\u0002УwRQӚ#6\u0017rO\u0002O|S\u000f\u0014{'1\u00177H\t(`rg`è{5QY\u001faWe\u001d>U\u0018G\u0004\"YQ@\u0006\"|Y\u0015\u001b\u00042<P\u0004#>/-녗\u001c\u0013\u0010\u0006!\u00118\td>s\u001c\u0006\u001d\u0002Px16JN C҂C<jPe\u0015~\"/L#\u001e@ȏ0!#6BQ\u0010}\u0007fޥ0\u00180'r6\u0010W\u0003ْY\u0017\u0014ɋ\u000by?\\#>\u001b\u0013Z1BO\u000b\u0017M~a3#: u4-\f9_Hjʛ5\u0014\b\u0002\u0011վAC#6>qs#>\u0010>Wcڲ{ÓQ\u0012\u0005bI\u00178@0^\u0019W\\\u0013~S,c^W]q@\u0003\u000f4#z~AI\u0010+b~?\u0003\u0002H(RAu@<\u001d\u0011Bn[\b#6\u000f\u001cP\u0014\u0001+#6S\u001a?\u0005\u001d3\u0014\u0001#=\u0018s>\b\u0017Gg`\u00130\u0006|0e^\bEAH/#q)O#\u0007#6.(=\u0005)'6\u0017:g~?M\u0006?9#;\u001f@A\b.Up30z/N;\u0018\u001c\"|\u0013\u000b{\b\u0014>DA\u000fh4[̽7PH ,\u000f1\u0018\u0003H̸\u00032P\u0006\u001f\u000f8?:\u0007\u0013\u0004\u0012\u000etJ'R~Wڞ\u000fRY\u0004\b\u0004\u0016rAl\u000b< $#Bݬ4DUӚN:Aٳ\u001e]\u0018r3Lin:$\u000f}|\u0019=\f\u0012@$?NɾpO-{\u0002BT/lf2fB(DD4!\u0006$FX\u0018i3\u001e:lDHC]>\u0017\u0019Xdޡ\"u3#6yI\b;\"Oۻ6\u000eW>1\u001fx\u001c=ʃ\u0007\u0011\u0013k#;/\u00123D0q\u000e\u001cOJ/\u0018~Yr=p\u0019\u00197?\t\u0014#=\u000e\t\u0011#\u001f\u0010L^^i횋)&o\u001d\\VBw\u00046¾1j\u000f̰ȧ{?\u001f&?|p\u0002\u0003\u001eN$TRxrlO!Vwh\u0003`6x\u0011m\b#=~D,h0z?g\u001eL\u0007:c\u001d3^;\"q|\u001fu\f{`c\u0007MciV)f-Y#>$$z\u0002{Xcvh޾Z~#6\u00183\u00066\tL\fݦw,+dSvƋyp85cy#=EUI$Q\u0003\u0005\u0010\t\u001e&\u001c3]&LEsm\u001bnH=eC\bn\u0010@pJTd\u000be]\u0013\u0006jn*\"\u0001\u0019,`\u0002 \u0010\u0007\u000fC\bfr\u001c $I^\u0016S!\u001bm0<ZwC#>J]p#6\u0004owR{b\u00107*I\u0007GRQA n\b~Q\twoGA2v\u0019\b#v\u0003\f\u0006\u0003\u001dC@\u0004@4\u001a92#Ⱅ~\u001bxM\u001e\b\u0019\u00023\"B\fܾ\u0010̄\u001bk\u00151\u0011Ěִ:x\u001bC|.;5}Gx=cRcG3\u000bJ4ߙVAM\b9\u0014~~J΂k_Gw\"\u00134$#=CU\u0019N\u0012$6\u0005d\t(3M\u0015Q*6T?Ek\u0003\u001e\u0012~\u0018#6\u0019@EEu7;C_`duޟ\u000e\u0007ԟ_ɿ:5\u0017\fiIoQٽ;\u0002\u0014\u0017`BB)\u0001J&cA0$#>\u0001g3IҽSAǁS=tDHn`M\u001aD<#=\u0003\u0019u].A.5V̆\u0019Ff\u000eo\u0006\u0003{M\u0012\u0019׸0IWDz\u001djI}\u0011\u0010\u00068\b=7?w\u0014ed{c>ғ \b(_\u0010\u0003+\u0005\u001cgtGpx\u0014*M\u001fyJ\b.\u001bU\u0018\u000f9 T?DH\u0011=\u0006(\u0014\f8d=1\u00045>A-u-#6 \\zM\u0019X*H<\u0017{ha\u0004#6{M\u0018.%p3?\fuQU}\u001fC~N.^\tja\u0014\u001a\u001cX\u0005\"\u0017.+\u0005\u001a;\u000bElnz\u001b\u0012B0\"pf&fz17?'<L\\\u0003\u0012hkIR$\u0004\u0016\u0003)*-u^W\u0005^ieM\u0003`#y04\t7wz.>\u0010xeDx>3X{4n!'\u000fȺ!13\u0004$dE)\u001d\u0019R&\u001c岴{!ה~*\u0011F>n?j\"|8\u0004bFOƑ\u000bf$\u001f*`3,F#ñrM+\"m9>?H޶o'.E@Ԏ0<{&E#>\u000fV\u0010RfjPX\u0007\u0007Ҳ\u0011-zp`J\u0003##>:۞5Bv\u00173~HvP:8\u0011\u0003G\t\t{#=!\u00055XѬW`JR\u001b\tEz\u001cW*ht.C ,H1\\rvW\u0019\bpyl\u0006t\"<J\u0019)6=\u0005\u0017)\u0012 +L&$7WU779\u001f$\u001dui*y3ѡ\tMZ#>.QV~s\u001eGŬ\u0018R\u0010\u0011g\t`\u001d\u0001\u00129oulKAȷa牆!4V)CbW9\u0006+2\\G\u0002\u001cPv>\u001dw7\u0011W\u0017L߾G'\u0018n\u0011W\u0005Mpծ ګ'PN\u0019cu4Ԋ\u0006\"lf`>~ / v\\\f\u0004\u001dh ^̀Q)\u0016Kք\u00012⡄AʜfyMAC3f\u000eu`jiaOmPPQ(Q3Wj4\u001bD@\u0006\u001c_KɌUUi]sY_A_#>\u0004/\u0015<S\u0018^QBx*P\f`یD\u0017\u000e\t\u000f2Q\u0004\u0016/o/ey^*\u0019ni\bj!\u00115ެ\u000e\u0016h8{m\u0018)#\u0012J \fQ\u0015C\fÎG2o#Lp֫L:_t2/\u001f,A?k#lC:_\u001fg(]%͸\u001f/YĆQ8$'\"\u001a'H\u000bIqуU_Ր\u0018U\f\fȪ\u0006HXəT,\t\u0017\u001c\u0004\u0013\u001bìU\u000bifhON_\u0013}j݉;\u0010Q'/U릹+;fv\u0019\u000bi5\u0011ǘH~\u000b\u0018hSN$\u0014>d/I̬\u0019L#=#=\u001f/5vztƹlY\u0003#>\u0002b}\\g\u000f.7Yͥ$2f%ty)G;<\u0016?\u0018Ve<?(*p%D)\u000e˩g\u0016#+@K8|J\u0007%2\u0017CZ\u00177l\u0006(\u0012\"F;r\u0006\u0004{won<OE\u000eWxƝ\\\\\u0004\bM&\u001adjQKv\u0007p\u0005)tSR\u0015pW\u0004H\\\u0017\u001fD?S~\u0017~\u001fv9g&h\u0015Xo!C9;;\br#Cs80)s\"/\u0017n\u0017os@\u0018ɇc-`o\u0019z4N\u0005s4|\u0004]=\u0017J=)ĩEEL\u0014;\u00132޿#6i8:WkCщ3D\u0015y7Y:D~;\u0018{[Fw[_uݕHZ\u001cRX\b`\u0011#6l9*Hy\f>]?<0o\u000eVxa,sq\u0006§\u0010:gM/S.!\u000e6I\u001b\u0017#>\u0002OÍ\u0002Ub#6>J\u0012A\u0011\u0006j[BB: <\u0007\u000bӃ\u000f#-\u0004D\u0014Yif\u001c\bN`[zӟѫ\u0006\"db\u0014.\u000e\u0015\u0001~\u0005=Μ\b\u0010\u0004:\f(\u0007.\u001aj%Vi2t$\b{?UY^\u0017#=#c#6\u0015#=g\\ Qx\u0010[@2*8-AAU)@)ccA#>P4s#>fPO\u001f'ݚu\u0013\u0005\u0015\u000fHvC\u001dkR\u001d\u0012-'k\u001dߚ\u000e\u0007(\u000b!\u001cq\u001dF\u000e\u0007w\u0012a콼\\,JPLSVAiR(NXgQR42F9V.3sM$W9+\u0016T#6h\u001dVqҒ5+J6^kmgpä3#>,*z\u0007W1.Pw#>XSay_\u0004\u000b\u001bL~OzK\u0010\u001epBA#K#6-݃7g۳/\u0013P\u001ft\u001f\u00169óEv/`\"4N\u0010!w8\u0006CE\u0006GOa\u000eJ2#=uD&=xw$!b`țHذ\f\u0004K?~ûEgGC\f^f216F}χ5cNy#7!u'\u001fG\blwÆfr\u001e\u0005#\u0012\u0012=3|f_4\u0003\u00039>\u001d\b;Β^\u0014\u001b,Xs,dHz1z\u000f{0\u0007#=fJI\u001a\u0013d\u0003m\u000f<hN\u000e\u0010R\u000e\u0019?\\;H.g,y#6ADFHb`h-=SL.2#6G_w뚽\u00045O袼1r\tǍ\u001f!I\u0003)䁳tUOw\u001a7\b\u001cP\bHJFSk\u0002\u0012`n[\u0001l\u0010!(|\u001eB:#=;Wt¶\u001bA`\f 0sLbyC\u001f?\bP\f0\u00068\u0005[㪷{}`'4A\u0018#\u00031?W_?\u001fgYbޓB#=C\tm;t\u000bt! Ā=jQ2=g\u0011̤A\u0016Z\t0#=?\b'oƍǇi`8#=7+Qv~K/\u000e}\u00152=\u0017\u000fI|m#6xTY{[hk:F\u001cp\u0019]2plo\u0001׫,vD&#6\u0011T*\u000eT\u0001M\u000ffH(\u000b͕\t\u0011ss\u001c䓣HQ#>T`4\u0016#D@<\u001a2ϑ1O\u0016\bu<VQ뷖\u001eoq\u0006[DbH\"S\u001em?'?m\u0019'Zxm\u001f 盔\u0013OGÝc\u0002#GPfcp\u001a#=#c\"IT%$\\w`\t\u0014\u0001=@I\u0012XC'i0$Y*R*N?~\u0003)Jpn\u001bu#>P\u000f\u0019Pʕ5b\f[w1\t蓿D\u001d\u0005rfߌ6r䄼'\u001aMi;JJt\t#=\u0005DC^wD>?\u000ft{t\u0002\"Ba-.P\u0003\u0007#6str|ö_v\u001eP.N(\bɶ>ݙBV2Ӕ)zIxt\u001e\u0017\u0011;\u0011\u0019\u000b\u0014N [fm\t9:\u0002Nc/`'\f\u000b\u001a@-ճMUɴtJ)%.0\u0016q+D@1%z@rV7\u001btB6a*;)l\u001d\u001b95\u0003\u001a1\u0005\u0013e\u000f\u001bo\b\u0012X@!1ე%\u001c  TC]\u0006%'E#>TnAX7\u000bn\b\u001a@\b^\\\u001f#=\u000exT\u0019Tlq\u0010s+\u0005\u0010\u0001_\u0001\u001eo\u0014ߡqq\u0003cãy\u000b!0WS\f\"tmѝ~\u0011\t\u0014_\u0010S\\\u001d,A H*\u0013\u001f/\u000fJ#Ζ@\u001a}\u001d?j\u0013o\u0003_?<01#ڽq~\u0012}TYWj{\u0002\bvMɌJ|㙏wcX\u0010\u001bwˇF\u0016\u00046=:#(u}wWG7X8F\u0019p,\f#SYT+\u0015\u0004\"\u0012\u0003\u00017kă'2\u0005~/\u0006WA \bɤG\u001cJ\u001e%=U%Ao/L#>.Y[#Flo}[|ړhj^(\u001fT=AGh=L8USZMb&(1:0\u001d\u0013ǑG@_~њO+J\b\u001a6ۦu\u0004$uoi\u001emN\u001a\u0015\u001fNP\u001aj$ ?t\u0003H*]&@{mP\u0010\u0001Э4\u001a'@#6AHÒ#6@\u0011]4\u0002\u0004#6j@\u0015B\u0001hSP \u0015\u0001l\u0013\"\"#6\t@x}>QO(\u001aur#8\u0007&\u001fQR3#\u0010:ouI \\,\u0004?\u0011|2zfd>Bf\u0006!\u0019~\u0007m@\"Cg\u0010h#6d\u001ed\u000bzM8\u001dD&='yA(\u0010$\t\by\\yp\u0014>\u001fz<E\u000e#>\u0018Ā\u0007\u0005\u0001\f1\u0005AN\u00141W~/?,\u001d5+x\u0007ZϏ#>cҤ`IS1$f\u0018h´Vm\u0011%O\f\u001dA?\u000f\u0016^`#>\u0019\u0003uزiӡ}oW$H\u001dBI\u001f2\u000b@sο2\u0014I\u0006IR#>\u001aW\\`\u0016-\u001aHӰˑ\u001c&Ak\u0012f0;N=B\u001f\u000by@\u0018\u0011)H\ti}y+^8j\u0010$\u001eiU6Iձ\u0001Aȹ WSc+9W;/\f{@eŁ d<i\ty؝A\u0005Dr16\u0011\u00141CnH\u001b8#=n\u0012I$\bn F%\u000f:\bRK\\{\"STO81`E&\u0011H*\u0005>y$#6ȡ\u0019ј\u0018Vz.z_{|Ttr5̌Ti$\u001c!\u0011A\"\u000eg۫7k1v]\u00060ɠ#>D\f=\u0010\\U\u0016lZқt\u0017r\u0006SuT%#>*F^d{}9F2\u001eXv#r3Z\u001a8bO7X\u0014\u0010J%\"#q=\\8hR\u0007\f\u0016\u0019\u0003N6䀄z4 {S*J\u0003ύ\"\u0015@\tЅ|\u001d\\Ib9\u0003bn#6ɭ^\u0010#=n\u0017\f1]H\t\u0004\u0014*DĢe\fHZ\u0010*֬@\u0003S$#6;)\u0001\u0003#6:a\u0007\u001d\f\"\u0001\u0014\\IVHϟ\u001a\u0011.A+B\u001cNEP2\u0001\u001dB\u000e\u0006`@RWR(\u001cJ@WSrÕ\u0005 8WĠ: y\u0001D#>(&#>\u0004#}?#6\u0016#=]\f}%\u000fzT \u001fm\u0005\u000f&L\u0013\u0012,%Ip<S\u000e\u001e1EcqE\\\u001f0%\u0016x~5ϒ(\u000f#>\u001cXW\u00114 6)\u001eEP-\u0017!YP\u00118\u000e\u0001]\u00030kW_:P<|\u0012\u0002\u0002\u0015DfXo\u0002F6\f \u001a\u0019@{&\bؒQZd6k\u001ac8[AaLJ੒^,MC*/]\u001a2ekU\f\"wp\t\u0004?c\u000eU9\u000e㑍1:f\t8J֪|oT$\u0010љ+-\u001c4\u000eQ\u00100R\u0001j\u001ejw8\u0012#OYЕT\u0015(v\u0012\u001f3#6\u0004>\u0003tHWm\u0018\u0018NZ \b|\u001b`\u0012,#=\u001cJ?y\u0007\u001b\u0015}g\u0013\u0015\b|\u001fx`bݥHe\f\"0S@я\"XZj˪?z4x4RnG탶=\\\u001d\u0018Qm\u001a\u0003KZC\u0011\u001a~\u000e8DJy\"q.\u0014Zdqq\u0003u#>{X#=ߛ.K\u0012A\bUׇ+l\u00146=\u001c.,\u0010Q'7$\u0016\u0015=9:e\u0019,íi\u0013wC\u0012\u001df{sqG'&F!VI;^!#>x]rH7:M\u0003o!\u0011h0\u0012Y,\u001bS\u0004q(\b\u0002\u0011\u0019G\\n{,,\u0014\tgP\u001d-\u0002l!:2Q#\u0018|XQvd\u0018#=C\u0001\u001aUn\u0003ЈOޱǫ\u001f/N\f\u0012\u001e=-\u0006ǡ9=\u0012kM\u001a`\"a\u0019cg/\u001d,D\u001a\f\u0017\u001cx\u001d{;V`hC%cXhR-DĐ{\fߌ\u001fkvDqfꝳ\u0010\u0006̄~D]vfAT5\u001e7L\u0006(ӥ\u0014\u00179r߷~\u0001wpt\u0019. 0\u0002\u0011ӫr\u000b\u000e;!g\b8s\u0011^\u0017^(\u0007\u000f͗y\u000b\u000f¼0֣acB\u001bVw8\u0004\u0010 Ԣ\u000f!BgqB#6]p\u0017\u0019\u001c\u0011\u0004g}?#=\u00103o|\u0014~p\u0001?}\u001c9G6b\u0014#607\u000bD\u0002i$#=ߏy\u0004!OWI\u000e.\u0006^.U\u0017Lb \u0011\u0002Pz\u000eχ\u000b2''\u0001\u0011\u001e5#>?\f_#6\u000e?ɴCtN}?:k(ߏ\u000f^~Pz34sȝ\u000b_Ws\u001aIi\b\u0012?zIr\u001dIyK\u0014o\u0012\u0007\u0006T2D\u0006wu#6LE?ǕPp;<|\"Z^<lU.\u0010a\u000f\u0017T/U|\u001b:=\u0013K\u001e\\@,AC\u001f\u000e\u001e\tNOa~\u000fec}߉\u0001=#6;_1\u0012\u0013}\u000fO\u0013>?SiӬ8)a\u0014QI^Y\u0017rQ{{@o\u0010A=L}&\u000bH?=.G\u000eQ\u001eI܂|#6\u0002^b||\u0001㉷^\u0016G~\u001eߐmԑh\u001aY\u000eĒJ\u000f\t[)\t\u0005\u001a?aؤo'uص]FvqeĈǱ~\u0014\u001fF~|Bρ/J}/6#6~+\u000bވb}rO\u0017,^<v>w;:\u0015&OىC'y6!\u001e\u000f\tVI4#6\u001e\u0018\u001dKS'oz1xI~=#6\u0014\u000fS\u00170܃þ=\u0011i\u001fb#>#6Dȕ`(<1.Ƞh\u0015\u0014dX\u001b%#6<6\u0016G3@<UQR\u0001?H\u0004\u0017\u0005ǷY\u0001#bօC$&VU\u0001a\\Aa\u0002_AA\f|q̦\u0006\u0002iT z\u0018P6Qr#\taLh\u0005#=u\u0001ԀQs'ڏV\u001f#!#65@M\u0013ɛ\u001fwwwǸO\\\u001f+(y<{X6<\u0019\u0003r#>\u000eHQ\u0010eQ\u001dQ5\u0016\u00074;6'\u0017,|@o{ŉoU\u0007\u0006C¡H\u0010Xb\u0007~)\u001em0Bczp|+\u0007#6Yȫ_\u000e\u0004\u000f\u000f\u00033\u0001OV7`C\u001ch!-c\u0001Dh\f99\u001eBĝ\u0018C@s=o\u000fr\u0014\u001cx\u001c tq\u001c\u000eоt\t|7\u001a GB롑\bI\u0002Hm%6#\u0016\b}k/\t|\u000bUˬ\u001dܩE\"I\u000b\u0002\u001fOJ{ ~wExx{\u000fpInW\u0003fJgz͒v\"R.v\u001bL\u0013\u0010=\u001c>\u00141/-=#x~8Jci#>4a-G4C\u001bKh\u0001۰\u001e/zm)\u0007A\b\u0018\u000fp\fzc\fmpHѱ\u0012uM\u0018X\u0019\u0003\u001a,\u0010\u000e%ZK6D1\u0010!0E#6L#=\u0017\f'BBNےD\u0010ys\u0016h\u00188(\u001f,4\u000f\u0017t (0XKՀ~CR?=\u001d\u00107n np̂Jd!CGߙ\u0019\u0016no\b\f\u0011ࢧd1sG(\u0019\u0013\u0004\u0003#>}\t/};~p88;z+S\u0012\u0012EӃ[6p,Wf2\f#\bN\u0012\u0010-biTK\u0005\u0014\u001c\u0017\u001a7[ѡ$WO\"3Fdz25\u0007g;g<#ڛqH(\u0015a9CA-eQ\tL\u0005ߎDI2k\u0007r~Gi\u0011\u001eK\u0010d\u001efD\u001ey#<\u0014a:5\u000eH?2OzwyN\u0010\u0011.,ѠżRE$B\u0003+*ҳGZG?x\u0005\u0012\tw\u0007~\u0012\u0006d)J?\u0014>_ʝo\u001fS'M\u0011k#6\u001c\u000b$4\u001d\u001fwOj\u0002R\u0014\u0007\u0017\u0012vGG<\u0004\u0017Oh\u0004B0/3+\u0019=:\u001f\u0017\t\t\u000eZ4\u0014{=^W$\f7uc\u0005\u0012,)\"\u0010ϢƧyoNt\u0018z\u0010H0}f<'.p\u001fzLU\u001e\b&\u001d7A&AK-pvJMcZ\u0006-r@^\u000eyֲeDICTBO#g \u0010dm\u0002!N\u0005i\u0006\f\\J#dymU*Ub.\u0019o\u0003\u0013\u0004\u001cpѹ7\u0011aMG0WH\u00147Cc\u0014\bx#>\u0003Q~Kld^3N 7BA*PTR2^\u0007U&QP\u001e\u001br֙k{nJ\u000eCE5\u000e`&\u001a\t*?9~Ώ<\u001b1nKD\u001f)G\u0005\u0014O\u001f\u0011N_M\u001bc\u0007Yc?qEzb\u001b\u0019\u001fG\u001b\u0019_.\\#6%#6%T\u001b~}1ޟ\u0004j8P\u0015\u001b\u0016}L̟ٵ[@PVl`c\u00121Řu̇\u0014^8)N35T?\u00150ဟN'\u00113\u0005#=֢dwL){GZkmc%ˋL<\u000fn)fi2\u00032c @;\u0015\t\u000fX\u001f~P+x˂B;\u000f;1#=Y<d\u0002A\f?@\fUm\u0004r\u0002̬h`T\u0001iʭk\u0019\u001anQUY\u0012#\"\u001bLJL\u001d!z7\u001b\u001d@Ql(it\u001e\u0015p\u0005!\u000fI\u0019;?*w\u000f`6\u0003paq\u0006=9C'tH\u001fHCqO\f\u00047EP)\u0018YPZ\u0017@;a;M;}O\b9]\u0004\u001f0YOx\"-U[+*Doo\u0001b--9wqo\u0019\u0018\u0013\u00071Qt}\u001e/\u0014\u0014sQ\f4vx+Ϛld\u0019Vтx\u001d$\u0013Z%m9\u0017X9͏:@VHFϨ\u000eh\u0003TM\u0010<)L5̩Fv\u0003{{4а;0AZg\b\fzj\u0001 zN=|pߴ\u0011a;DX#=D- Sz6\u0003\u000f&Xp\u0002aq\u001bP\"o\u0001\"\u000e%\u001a\u000eUi\f\\~n{0s_\u001d\u000bN\u0003-\u0001-6,\u001d2[#=L\u0010`63M)#!ۜZeL\u001f\u001fZ~?۰\u0005oW#=\u000enLC$v\u001f-\u001f3\u0001\u001457N3\u0010t*\b?Ue&\u0016\u0015N\u0002%\u0014\bĪ\"#TPKX̙1\u000fuu3Ijj7Z\u0019Wniy/\u00193E#2,qp]#>PͺԒ73u\u001ak\u00132PqIUkUՍ1\u0013\u0015y.IfuQfX\u001c\u00185ZmXbۚY1\u0010XQ@df\u001a\u001b\b5<l\u0003N^H'Y\bh\u0010H\u0006\u0003W\u0017?\u0005\u000fo\";#D߰p!\ts#=F\u001a\u001b\u001c~^mIr<}\u001eA2D\u0004\u0003#6#!/f#6TN҇c.\u000fBaP(l5M\u0017\u001e#=(B::#=s\u0001\u0002n\u0011\u0012\u001cY6\u0002#6\u0017\u001edB\u0011\u0002\u0018o\\f\u001c{*\u001e>0U#=\u0014D\u0012\u00021\u0001\u0012w\u0012GIO\u0018ĸ#\u0018\u0002fps\u001fSmh9y%aŔ8\u0006a\b\u0010\u0010MCj\u0018pBe:dNYw5\u00060` I\u000fxhFL,mw8Jff3<\fh3\u0017\u0016dX#=//k~^w萨N|qӱ>o\\\u001e\u0004͠<\u001e\u0004.8fm\u0018\u0005DI\u0012\u0005aTDEl.!+1b\u001e' ??#=Hm9@YxO\u001egi<U\u000fP\u00116\u0004YYwY1d,+!e#>\u001a\u0002\u0004Of\u001c}`Q,\u0006>\u0007!2f\"3\u0016xr86\u0006\u000bv6\"7Vlv0pQ#=oĩѕ4O[9:HS?ɤ1#\u0015\u001fm'27YN#_?~\u0010猃[\u001a1|\u0005*dko\u001frw\u0003T~\u0011MaNZ\u0007*Pt\u0003!\u0019=Gu,\u000b`HV(mF$\u0012E\u001f\u0017nrzsāf\u0015BJ\u000f\u0007\u000fD)\u00119\u0018h \u0004.p@\u000f\u000fj\u001f?<9o=\u001e\u0013\u0005<\\<Hf\t~9\u001e\u000frt\bO/Cc*\u0017\u001fs\u001fhz\u00171\u001fKA}8Sk\u0001ԡfD\u0004ALҏ\u0018NA\u001f\u001e\u0003gD_$\fhFΐdC\fj\u001c\u0015\u0006I\u00078#>o\u001fA\u0017o%pv\u001f$8#6?\u00118'kI<Pe?7\u000bv\b\u0014\u0018\u0002\t C8.q=h|#6s#=}\u000bz\u001fmξ~F|?oA({\u0006)\u0016 С \u0017\u0007Kt`\u0011H\u000bu]!S\u001a=\u000f\t>\u0010#=ə}#=w\u001c8pr9Jv\t\u00036\u000fbB Ǩ\u001bSiI\u0016BD'\u001fG`?XD˼'m9\u001c\u0011~ҐG0;;$ι\fH\u001f[>!}^\u000b\u001eOE6\u0003'\u000f\u0010\u0002,\b>JH\b{:ǻ3<O|\u000e\u0015}|y\"/\u00021Dz\u001f/ȪeOo1\u001f9\u000eR\u0007B\u001e\u0015HRDB)\u001a)BvY\u0013\f\u0011\u001d{E\u0003g\u000f=CNT?!0Ugo>;G;߉TG\u0007D\u001avZ%읍GevpOL9\u000b9\u001b<W3\u00100P\u0012ҿW˭n\u0001\u0011\u0011VUR`\f#頏\u0007j\"}\u0016_1#;|\u001bc,63[3\u000fOTbW(\u0005!m\t+\u001c{\\Qj=a?y\u00035ؗm݀ac.Q+#ʢ?\u000e\u0013C9.R~~&\u0001a2=,~3\u0006qr\u001d?#6x\u0005)W#|<T\u0015\u001f]p\b?\u001d\u0001\u0017Q\u000e;~*t\u0007j\u0001/t\bo\u0007>=aqA'(R?׫o5=Fbq=ig˚ʷ\u0017\u000f;Ϫ0q\u0004m\u001d2$\u001bظ~\u001fӳ7:tKI!#\u0017(1\u001fB\u0017\u0007糵~9Yx\u001f\u0004?8qv\u0011a\u0012'Inr~\u0005W19\"\u0012E'h׃Dz\u0017\u000fwմ^S\u000fx\u001f0EG\u0005(\u0005E\tF:߼|;_AϦ/#u\u0011Y*Mo{=\f\u0006G}#0\u001f\u0006m=֐lr\u0001\u0001\u0017(U5\\*e*χsbXjVg%\u0006O\u0005\u0011\u0010\u0010\u0011\u0010E%\u0004\\[LԌK1sa#6(MQ<\u000fN\u0013I67\u000f\u0010p\u001c \u0016H\u0017,\u0010#>-1xS\t\u0010lT1*U7`H\u0013ϲz\u0011R傋bZ]<y\u0014K$qݱn\u0014>(|At}o#=~?L^y\fH\u000f v<N _7ޟ/Jř8$aB1SpN-ڋ#6\u0005*r\u0016\t\u0011v#6f\u0001M3\u001dh\u0019L3LpIN6#6Ē\u0005\u0016~/A\u0013?ЎH\u0011&'$+K6f\u0018\u001dXUX[\u0013D\u0016k\\]\u0012͍Qj\b1隰E^F\u0016Xly\\?/\u000f*}Wň12|\u000fƏ\t?}*\u0014\u001cxOٲA=h^\u001b'(lqA8yz\u000e}@w\u0004b\u0013\u001fԐ\u000f\b;Wo\u0003{`QS؀z\u0010k]PI$@y\u0018\u001e$q\t9\u0002@Lu|L\u00110m#6&Dȡ%\u000eA\u000eInK\u000b{;E~\u0017<ͼ+cIۯ_\u0010:#6yP2_qq\u0014S^\u001d\u0004 #>\u000f\u001e鄨a\u0004\u0006B\u00140\u0003\u000eRXh|\b\u0012X1CD=Oj=C;Q\u000fY\u001f\u001c\b\u001e?]~\u0014\u0012\u0017;7M ^@\"\u0004\u0002\u0002\u0003#|\u0019w\u000fM\\\u0017!sJ6S0E,9|p\u0017~J\u0018?D\u0001M\\F\f\u0015IdÝ\u001e_z^G99Ĕ?6ֳ[\u001b\u001e\u0019\u001aL[H2xʡ,jC`/\u0016\u0010\u0018LKs\u001fdu#6@3\u000e\u0014\f~\u0014#6+#6ւ$kKi\bK\u001eDL:Q\"z\u001bҧp!0#>̨=dUP4?T/\u0002:\u0002\\C\u000e ʠ@%Y{!\u001d<U_\u001ca\u001d?עNggf0&ϻtsF4rl^\u0004\u0017\t:0EE\u0002P\u0011<%\bc@6\u001865a;c!\u000etO J\u000e\u00017bhz\u001bۀC\u0012P\u0004cj?NiWb\u0005W?RrDb\fѪ@Y`M\f-D\u0005;nT\u001f`.Qi#6ST#\u00115.\u001aI\tZ*tL\u0005$Nw\"U\u0005\u0002#T,$M\u0012\u00191Rb:`#>\f\u0019AI\u0007\u001f\u0015b\u0011LWf\u000b\u001e\u0007<\u0007oDA\"#6%\u0004\u0013\u001cPPV 0?\u0017;\u00170\u00181\u000fJC½}.s9\u0017ZtH?N\taлt\u000e\u001d\u0015#=?:\u0007xG#\u001e\u00074\u0013\u0003\u001c<8\u001f#6+9N\f jPIk\u001d\u0016`%\u00061\u000e1O\\v\u000b4lyLm\u0001\u001e(H\"\u00111řt=$$\u0001#=\u0004wj*\u001d\u0001W!q9ISY\u001a<\u001eӃuGJ*^\u0017z\u00140\u0003\u0017־/htP\u0007t3\u000ex#~\u0018`5u'\u001bL\u000eE\u0014t\u001eB\fyh<`k3#6@+~rxv}C\u0019~Y\u0012Kϧ-\u000f韡Wf\u000eΡ\u000fFHp\u001dēҽ\u0007\f?+>TCu#>,'\u0016\u0001#>\u0011X@O\u0010OdӁ\u001b/!!~IPPd#6į|P\u000f*+\"\u0007zw\u001ff\u001d\u0019\u0001\u0002}AIǇ<dɤ)({&\u000bKi40bx\"¬\t[\u0016czfk4\u0018BBGEɣd؏ᾯTh>\t4\u0018]t،\f\u001e\u0006\u0018!\u0003|BG{O}0X\fق\u001bAti\b602$S|\u001fW=|=A\u0010jv\u001e#>Bs&x}i\u0012e~H{I4sϞvCt\u0015wQ\u000f\u0004}N\u001ejiPO; k\u0005L\u000e%\u0003\u000f'\u0011\u000e|>\u001eP=U?>Ss&Q=u+|H\u0005'/h\u0013>\u000471Jw\u000fP\u0010&Ӄy\u0011\u000ewA\u00019\u0001#=d\u001b=\u001b\u000e'#\u0003)Sb%#=-U\u001a+%\";\u001345.?C\u0001\u0017O\u0014,q(#6\u0019\u0003ៅ$W]B6\u001e\u0003\u0006\t\u000e%7\\rU@\u000e\u0004V\u0016BP\u001e?>͏y<|t\u000fMjb/\u0015Q\u001c/3msn?P1\u0002m`Li\u0014\b\\ރ\u001e#\u0003Gl}ZܨfJCdxpV[+r\u001aj(h\u001aQ\u001er\u0019Oꊸ}&\bFK\u001fl1!\u0018I?\fx\u001f@ Ě1\u0003nC\u0004c#>ƌl\u001dGe)T#=\u0003Mƃ\u0014`GѐKؐ#>\u0011*\u001e&wqO\"\u0002z,bWM={\u0017~{t=r1t\u0007w\f iT\u0012\u0005#6N\"ж?PMixF\u000eބ\u001a\u0006\u0019\u000b\u0011ސtGԁ.\u0001CHtČH\u00102a \b#6#><T\u0010(+\u001a\u0006}a\u0012B\u0002\u0011t?/_(%\u0001=U\u000fkz\u001f>\u001e\u0007ծ\u000e\u000fP8Z+Uc$ \b [0f;!dBP\br<\u0003ϬIepb1\u000f\u001etذۻۙ\u0003\u001a\u000f\tt\b*xNl\u0013\u0017\u001fa|`<3L\u001fL\u000e?ڱ,;W~EWkk\bX&XL\u0014~?Oǖ\u001d\"V+&*\u0016X[%L\u0014+ajh$3\u001f^d\u001f`5QeTgM%[x'\u00170`?_\u0018\u0012q3C`-\u0007RӼQ`\u0015ſ\"cNSފ\u0011n}\u0016.\u001cS\u0011Vxui߬Gn앩^?\u001epgҳԫAש\b6\u001b_e\\X-S/.\u001axp @t5\u0013J#p.8\"ݫ8lc\u0010OY'\u0017i6Fog+g\u000eN\u001c:k v~D>NSh[\u000f~#B.\\S#>P;i9χ2l>}\u001cDe\"\u0010s6A:\bAJ\u001bo`\u000e. \t,ާ\u0018\\\u0010I<\u000efpwz,!͊!~\u001dB\u001d<\u0010vUx}S:$?\u000e['myi$)#6|\u00108\u0003L\u001aT4_QH~^q#=oI:9.ñ\u000e\u0003\u0015@\u0019O\u00130IIH\u001fdb'񅏡=z\u000f{S#6b\u001aC\u001f\u0014@\u0005*K1#\u001d>\u001b\u0019+E\u0013\u00187c\u0001\u0003\u0015yK#6Pp:\u0003x^:\u001995.OO˸\"Wb\u0001(z>g\t;̼,\u000bi>\u0010?Ws3\u0001\u001fI$>~٣4>_hlk\u0014X\")r'|11>G)ݧ#6\b}T➫Y\u000fOU5+Cڊ&,=ٓ \u0011\u0015\u0013@'XF \u0019A#6lK$\u0018,b\u000f\u0017$}\t\u000f4fS4-'H\u0010$%44\u001ag!\u0011x\f=i\u0014՘ғ8 \u0016HaL\u0014!\u001aM\u00104\f\u001c \tM(\u001b{n<I+T_\b?G\u000b:\u001bC3P?o4_C0f*k~{\u0012H#>Ce\u0011sBh&n{`ec˴\u001bKWa4bsY5/\u0018\u0003m{s?nz\u0011i&\u001ehnFy&[£G1V\u0005\u0010\u000b\u0007o\u001d[@&\u0006r5cP\f2aE\u0017\u00159л4\u001eԒ\u0003\u0010\u0004\u001a;\u0019`\u00148\u001dþΤu\u0004\t#\u001e\u0011C6\u0015\u0015b}|.9o`p\u0012\u0012\u001e~'x\u0014\t}U\bT$0=ֿ\"\u001e\u0007>6l\u0003\u001e+쏈\u001e|Iu#=>Zq\b\u0007.\u0003\u0005;, _~'q=Oy糉O?.^.\u00030>أX.A#6E,~\u001eE\bvSFfmd@0?Ш\u0011}9#\u0003\bL1Q$37a7\u001a`1m\u001b\u0018#{X\u001e\u0001\u000b=S\u0018P?#6K\f\u001a\b2,'\u00161ϯ_g?t؆SU(_Q\u0011\u00068]IJ:\u0005r45a#Y%9\u000f'\u0003\u0006a#6db@ֆ\u0006\b\u0017`\u0017X\f\u0006B\u0011\u0018#6&s#=d)0R5AK\u0019\u0014~F&)ڞ?hx`b>\u001f\u001a\u000e59Xմ!{t7rL&[.K\"\u0015ƆX䌌l\u0019\u001bfb\bm24ݐle+8pP7Y\u0004CA)fZ5J\u001dE\u0005:T#=J#>Z\u0007/~pg-\u001d\u000fq\"pB\u001d@'\f8xE\u001f|\u0010сSj[\u0012wu\baUL\u0007ƼF\u0015\u0015n[}_\u00032}vr\u0001\u001a6$1|A\u0002G#6lU?\u0018 8Y\u000f:\u001exWߌ?\u0007\u001f#\u000f\u001c\u0007\u0018(122ۗM\u00115\u001dB#=@#6\u001elm$#6\u0011FJ\u0001W>Vu@n3/=I\u0011Z)8KAO0y#=ǉǪ:\u000b3#{\u0007\u0001\u0019\u001b\u0018;\bKY|#k`;>\u00061@\u001eu\b\u0001~\u0004\u0010r\u00045pI;BhGW\u0005\t\u0003a`\u0014C(jT)\u001a\u001eR\u000e+#6hV&2p6`?\u0006\u0007π$jdfA\u0007r\f۶\b\u0003\u0013\u0007\u0001_y\u0004n\u00108\u0018ia\u0014\u0002\u000eyͳ2\u0017KׇтT\u000bحaM4uB\u001d\u000fN\u001dM\u0016\u001fO\u001d\u001eM#6\u001b?_j#=zWsGp#>Xʈ\u0007\u000f\u0014\u0016g]/\u001f\u0003_1\u0005M}?n@\u0001Th]6\u0011ث2$CrD̫l\u001c;\u0007B\u0001hK]gtl\u000e)\u0010qqw\u001ao<\u000e#%8Ns\u0006%VaJiZl=BG:[\u000ffu&i\fȩ'\u0011r$%N*:~ X\u0002Q\u0015;~gEʬ\u0007]c\u00178#=;a#\u0014q\f/\u0017u\u001f(L;\fc^t&MPxm|_6B<4\u0014-*Ct'\\9GuW\u0015$#6#=\u001c\u001c\u001e\u0010\u0018\\@\u0018X(o~R%\" \u0014Gǀ,>\u0010\u0011\\G,U3]17Oj,\u000eXĉH[ѿ\u001b{\u000b\u0004#=5\u0013\u0014\u0006ϙ!\u0017g\u001d˿&\u0003F\u000ep=R\u0011#=}Gci'|8#=猟I)cY\u000f\u001e^\u001esL\u0013Ԋ}?\u0001\u001fQdO~ŧtuFkt07\u0019l\u000b\u001f$\u000f96Uͭʊ}#=zkR؂/Ao\u0019j%T1M`0\u0017900V\u000bXAjjnsMY.\u001bo_Ej(Q,L\f-٪i#V '&<xWZ;?F9\bQE\u0012d \u001f\u0014#>#6\u001f+:#SqNC\b5E*%;a\u0005\u001cD\u000e\u0001Ș[Fߣ[|\u0010ɢ`\u001eq[\u0001)\u0014ZЫ#^l-LQȏ@poFdX0:q2cp\u0016@\u0015&v\u0007\u0004H^\u001a\u0016W_\u0013yN--ti\u0005Ɇ\u000fQ|\u0014)#>]\b\u000eӐ\u000e^,cq_eBOʰo5UeUYeA_\u0017\u00126\u001fƸu\u0019gi#=#>\u0005U\u0001T\u0011\"Ϳ=  X\u00105\u0006tv#=Y:d2䰛#>Dse(/Ij\u0018mX\u0002}aYWGQእ\f;\u0011\u0002=Lλ/#=axo\u000eE/^\f#=)\b>!e`T)j5\u001eb\t#>\u0019\u001f\\)/dr\u0007̠E\u0010Т\u001ef\"8B KM;?G왟Jrn\u001d\u0019\u0013c_w#>E\u000e'zY()G\u000e\fX!47͜E}a?\u0014#=\u001fg#6\u0010}\u000f]7G}~a}\u0003\u0015E#=2ѽNfyZ6,c3\u0010ߗd;&$ɨ\u000fBigۊj͘?UJnT\u000fҟ\u001b8\u001fqF;֧ŚbF\u0013\u0010BHem w=8\u000b8\b\u000b,Rz˸~a}VYYR#6'JFDбL$y\u0015=݇&9C'(Y\u0003\u0003b&́\u0014\u000e\u001eid\u001d\u001c\u0018U\t\u00033L\u0018M;olqaQ\u0007@\u0005\u0006\u001d\u0014[}\u000fUS~8@P\u000f0P|;ي\u001bd<R_/\u0016o\u0010$/#>\u0014Cɑ\u0011A!#6p3M\u0013\u0002\u001c=3\u0012(6G:\u0003@\u0002l\u0013\"\u0011t\u0006%8c;[z\u0014\u0002\u0006\u0013\u0003QVæ\f}^a\u0018m\u0007۴ke\"ʿ%\u000b?nZ\u0004zEb\u0002\u0015VCƅ!\b|8\b§Cs\u0005`\u00180%\u0007a\u001b%\u0012'x;[\u001cxuf:{{'EW\u0001~苲ۆ\u000b\u001b]QQ\u0003:?נ \u001b8 脭ȃe}[Owۓ\u0010G8\u0016>O\u0010\"\u001a\t\u000bZ0t`IH[9͊`\u0014N\tחn\u00055-\u0002m \b<\u0004ۭ\bS\u001f]\u0015=pKhf}qRTWL5%=Y\u0001#6\b-_@Q9\u00122!*b+(6w#v\u000f\u0014#z?dG\u0018\u0007e#6\u0006#>9l#6\u0011\bz/\u000buU\u0006fbf!\\e\u001aDL +匴h%.\u0013\u0005d93)ekscigKBnfG8#5\u0019\u0016{#\u0006\u001cF*GFB%$]0\u001c#>*R:3ɣ-u3\u001b\u0014ۊ_sg\u000br\fF\u001al\u001b}Y\u0015\u001b\u0011\"]s$$!\u00013\u0018\u001dEcH!BR\u0012vW\u001a>ݷmP<\u000bY@\b\u0014F#W#jsi\\E9Ƀ2*ZtK2S\u0005QV&\"s(\"\froJ8+9\u001fEת\\X\b\u001f\u0013<Ro^ϞsWe5JE:L^$Qj4?5\u00104=׶( C\u001a\u001bVHNewuF5!0X\u0001ރTaY`#6\u001eOA7WpVB2wф\u0012&C\u0005\u001dETz>lW;\u001f^ch샿[lčz-ښ*\u0006:\u0013\b\u000e2ܲL6\u0015#>\"\t_N@?#=TyNǑL\u0002'l|`1Yo4O]ШVPavǋB\u0010m߇\u000e\u0001@\b\"xé%'tr,7vy7\u001b\u001eK\u001cX^\b[lXhsMsX7Yc9\u001cˋ\u0002\u001c{\u000b'޴\u0006|+:,k\u0019\\/T\u001cq\u0012b߇$\u000bD;i\u0012dMt2p5ј\u0017\u0002\u0001hႇ\\/cW\u0005Q\u001a\u0007cc\u0003Ż\u001cv\u001b*c]yT`S\u001fgɐjn)KEJ\u0001\u0001u\u0010V<\u0014Pz<\u0013W#63ڑ@}\u0005 9D^V96ޔAS@U'^e\u000b\f~+/\u000f>栰S܄h= /b<}\u0013F\u001e8LM\u0001\u0019\u0010\u000f(\u0013\u0014FXOKW'߽\t\u0012\u0010\u0006H@\u0018ò_@#\u001bw\u0011\u0017\u0014L%\u001eٱP%\u0015T(3T\".Y9\u0012s)<\u000e0\"LdE\u001azKAPA:D\u000e@>q^ŪUH#stPt#\u0010*ʙ\u001a'\t#vח\u0007$\u0005(H9*~S\b\\Ep\u0001?3$$O\u0006]#\u0005\bP?G?aF^`tGOho#=\"[A \u000fozd\f\u001dXP\u0015Tc\u0017z?*\u0010l(\u001f[״g]\u0005l:]\u0001f\u0001\u0007#6#6=.\u001f*>~\u0007Y\"(nD$\u000frGr@\u0001;Q$\u0005b3A\u0010(p-S\t*m7\u000f\u001dV?MW\u00105C\u0006!rN@:ubW;\u001dMgKx`܍\f\u0007nr\u001f\u0006 A?M#,җnlF:?\u0007#\u0007$}8r\u000b#E{\u000ffV!\u001e\u0010l#>Dt7w=\u001fd\u0010p\u001c\u000e|\u0001t#\u000f0O~g\u001fKIpwR\u00121\u0017#6\u001ezP,'\u0018#6s\bMfv\\\u00130\u0004M(0R̮RO$z\u001b3+,E\u0016n\u0013s#>,y?l\u0006{\fk-H7Z\u001a\u001aDzu\u000fS{@\u0016.\f\u0007\u0010ߚ\u0005FCIqꖕY2D>a#>\u0012Oׁ$%?\ti\u001dӝ\"\u0016EI},߬\u001a:πPP{C\u001ea\u0006C'o$Gw>A/5\fEP&\\н\u001d|\u0007.pjs\u0002m$0\u0001fq=OF*!<ggsK`\u0010\\%\\*`AƓ\f\u0019.IV\u001ap\u0015`:X\u0018P600n\u0001\u0002//\u0013ԧm:\u0010\u0012i$]y<L׵z\\ZE\u0017QF\u001bs&;=fy\u0017B\u0015<\u001aN\u0010成\u0007^q~Rr,'Z\u000e|P\f⭌̝C\u000fx \u0012:0O|\u001f$\u000e\u001c3ʨ#6~>~\u001e#׀{1zyi#>\u001b[\u001dُKː3H\u0004\u001cz#>!> \u001a\u001e\u0018\u0014\u000fhs0\u0007pr8$\u001b\u001a#lE`je*!HO2`ߕR\b?\u0001\u0005R[?Q\u0010.F)p?,\u0010đ\u0004S\u000bCAgQ7~mMAK-E\u00198a4\u0019\u0016z\u0007=%\u0013\f>J\u0001wm9I!sCqQ\u0015UV;'Z\u001b\u0004\b\u0016#=AFÏ\u0003@\u000f\u0002\u001d\u0006|Vx)Ъ\u0011\u0011\u001e];O9y\u001f3\u0006QHb\u0006P\u0014n'B~;\u0006fm\u0018!H\b\fs!vl\u0014iD\u001eV@)PBBC>dKjKI\u0007Q)\u0018\f}\u0019}ܓR!p5\f\u000b+a\u0010J#>\u0001Q\b8t:=\u0010yC\u0005$Dm\u000eH0\u0004r\u00014%\u000foVbC \u0007=!o\u0016Xr\u0006!&I\t#=^\u000fx(N\u000fU\u0018\"#>\u0007oH`h\u0001#=`١nA\\,2\u00184Q'ͪդG)2\u001f@\u0011Tс\u000eݨ{v'0l\tq?O\u000f\u0014\u0019\u0012\u0011`UIdQ\u0007#6;sm\tp+it\u0016\u0013+\u0012c9*\u0006Pyh=F%\u0002#=b)#\u0004r\u0016O=#=x\u0011=nSi001\f(y\u0019G祠\u000e\u001fi#=\u0003w\u0003-\u001d\f\u0007\t=CaNDjdda1fs.xMY90مbE\u0016\u001a\u0010b*3o\u0018`˖\u0017\t0\u0006\u0004\u0005p\u0001\u0006ɇ\u0007wWQrulv`ysX0o|]g-d9\u000e Mr8Û8Zk^I\u0012åZnƍ\u0006,\u001a\u001a\tT\u0010p6c*Q\u001cou\u0019\u0018hΜYkh\u0001Q\u0014bJF'&\u001c@\u0013sFEy\u0019Z\u000b&T`\u0016>~\u00079@`M\u001cr(;Y8T\u000f\u0016;vxrJj\u000f\u0003gv{W7\tg¿'\u001e>>oc|=>G\u0002Ul\tz9j']kI~}{\u0011 LxT>Hv%\u001c<\u001fD9V.q\u0005ox#5Gz[Z\u0011|?LE\u000fVd\tC~G1}z:aZ\t}w)\u0006\u0013Jt!fXe<r#V>oI\u0019(5HbG``\u0003AtM.xOrN\u001d{w#6q`DQ`\u00070|pʫ\u0012v`B\u001d+hqe'\u001buǔb\u001aQ\u0015\u000ecPr%N6\u0015B_IX\"\u001b)蝓}\u001f[;&;Ř\u0001Cs\u0003:+5#6b\u001bU]bx\u000eD.x+\u0015\u0004\u000e}%N\t\u001f4^|\u0014_hfkН|:끖4A\u0015\u001fl|Ջ\u001c\b\u000f\u0003D(\u000f`\u0016*<.Eݣӆ\u0003`{\u0004z׹~l/oZvO\u0014zyG\u0011{c條*\fF3\u0014:UU\u001c\u0001\u0012l{'c`4P߻Cأ1\u000ed;H#=U\u0006y/t6'E\u0017p*Ҧ,.\u000bӮ܍a\u000bv\u001dSa\"\u000f\u0017jH\u001e\b G6Hr'[\"Ny\u0012n\\h+c',_md9Z.ueEP\u0012\"\u001aAդ\u001b90k2JUb]^\u000b#6\u0019o>\u001d`Hf*t=jH\u0014CPk$xchr\u0019d^Rk1\u0003t7\u0006ݖ\u000bȏG\u0006X~M\u0012\u001c1i`S(y<q;\u0019\u0013ݘţ&\"h\"*udUhgbaKt\u0006gYLܖe1\u001bhfee\t2N^\u0006߆ˮC\u0011/\u000b$\u0006\t\u001aH\b=YR\u000eG\\y\u0014\u001erܱ#6!e}H`ozH|\u001e;mBdعM5mYk\u001a^<`^c\u0017}PDuӾAlq0bvNfĩ8;]&\":\u0015\u001fi/ym\u0003`wo#QT\u001cރ693V\u0015`T#=\u0019(=\u001e]O\u0002\u0011%Z\u0012!\u000e|A\u0019vd\"Kdw0&]X-\u001fxlYo!\t\u0002\u0010S/r#=m\u0018De@F\\\"\u0017\u0005\u00181{\u0016sã;z1\u0019fS!ڴUcӑ#ŚE#>\u0007C\u0007;Jxax-\u0018baZ\u0006z\u001e+1\u0014\u0011kn\u0001\f#6ObDCS(י޷\u00139$놆H\u001f?\u001d\u001f\u000e?\u000f֍Y9\u0010\u000f@k\\2`$~lU&N#=q4&O\u000e4/Be\u0018^#%V\t@\u0017;q\u0007jER\u0001\u001b8_䩴-VEZ_\u0016\u0018Kxc\fʢ\u001e\u0004.+gpv1D|eVV;p(,ro1U\u0010\u0001)\u000b*(~\u0003\u001cv|\u0011n\u001c\u001e\u001cdxAɾǆ\u001dB]\u0019-\u000e=\"\u0017:#>t.ûҲ\u0011)&\u0001vf6\u0016c\u00079&Ȉ\u001fkoy0\f>`r`(vCjD72~ZdN\u001a!x\u001eHh#6E\u0018\u0006@y\u0001,W\u001d\u001ehS|-\u0003\u000e! ۡ>LFoG4\u0011Mc8\u001cy\u000e'x\tI6\u0004ȅb\u0017\u00190x\u001c3\u0018=%\u0018To\u001a\u0003A݆'Xr;'0ܸ=v\u0007_b.\u0014f\u0006)*M)\u0010,WJ7\u0001F̭7E~X%0mHQ\u001e}\f\u00183\\1\u0011AŒB\u0016r89\u001cL=Lt\t3\u0003S/YU=Bx\u001e\u001b\u0001͇ţXcz~{{\u001e2&=ش%Q֙>~$BӴ{1\u0013Kz9bi\tR\u0004c\"NاБ\b4;\u0007\u000fhi\u001a=7#=hĈenE#>\u0001\u0006\u0003Y3*ַ\u001a9X\fh\bHT4D\u001a\u0018\u0006)27Eȇ\u001cQ!\u0006=\u001dQDam]A\\!I~Lw|aL|:up4\u001b N.9\u001c\u0018N#6\u0006:$\u0002R<q\f\u0002\u0018,#60{\u0004u<\u0003a'`\u000f+=!V_ \u0018\u0007O0\u001f׏<a\u0006{*m\u001c\u0007\u001b\u000e?Q#=F#>4Ǹ8=i|Á=\u0001A\u0018 8!\u001a9;¢ьuk?.Z̀D^'\u0011#>|c̡۝\u000b\u0005Z~\u0007y\u000fS\biZ\u0013\u0002[}ȣN8%\u001ffؘ\u001eFL#=(\u0014tIx7<f:|\u0011[5F&JLm|6!\u0019nwYy!!S`!5ԩ63(M(+S4\u0010\u0018:f`HʲI.3{i5\u0001\u0014\u001e3\b\u0004(wٞ\fi\u0011\\\u001bdyF\u0004\bv\u0013,~K\\=]K\u0011;\u001b1\u0006:3;k)6_\u001e-&p\u0017cA-o\u0011ȅK(\u0013g\u0012;GaX,YTh/P#>\\\u0003SPL*T!7\u0013SB\u001b^\u0012O\u0019#=8M~\u000e\u0015n+.W~3;E)ĥ?\u0006\f*B\u0015\u000bt #A#>ayd8AG]></مM-b2{;\u0019\u0012'\u000eOVN*0ɏzPBk,^.\t\u001b\u000f0%#=1Z\u0013'CX\u001ah\u0005[3\u0018G\u001f+==\u0004kL\u000e,* 1Ama_\u000f\u001c!@O48#=\t3|%\u001cv:{uW\u0011'Y)W`CZr\u001dg\u0001]K|-\u001a\u0015\u00178Ґ\"Oǣd{N\u0006\u0001\u0012\u0013\"M\u0016\b#6bh蝅#6\u0013H ~W$Oд4\u0014~\u001c$\u00121+\u0013J\u0001̒Đ\tC=<g7\u001eަ]\u0004=\u0004}\u001a\u00157\u0017gZ^Q'M1#=\u0012\u0006qJ#>|Mb\u001b&&3\u001etT=$\u0007&`\u0015S7͡q2)u'\u001acYtW/8_yGH<!E|.<\u0010G$\u001e\u001f\u0018\u001a;`=np;9\u001b/z\u0013R\u001dã#>nWWR\u0004z{0!lp\u0010\u0012a'a^l\u000b\b*x3a>@ \u0014\u000f\fJh@4\u0002\u0002P]\u0016\\C\u0012\b쥔|O-g]@\u0015'}e\u0011q(#=Q\u0016}JzW˙U+E\u0013\u0006d؅}KOsy%I3سӼ\u0019\u0013/䚞gXU\"\u001fCOyXV~#Eh\u0014y`T8\u0010e9Ӄ#>PTV\u00034jp(fEw\u0011X1\u001abri#==i&m{=4h4`@2\u001ep\u0004H<\u0007\u0002ޢ\u0013_I|/_g7\u0004A\u0019!\t\u001a\u0005D\u001860P\f\u000eII9=\u0005=Y?'_\tu,?\u001ez?ȬUO&u\u0003?0\u001fP<A;1^`6\u0014K\u0017*Kujv\u0012/e%ԩ#\u0007@(\u0007\u0010\u001bp\t]\u001f}\u0018\u0011\u0004wf\u0018AB0fJDJS!Z^|x\u0003Z\u001f\u0012v\u0016P\u0010@O\u0017X>x,\u000echc()M%\u0011G=m\f Q\u0013\fE51뜗9HFXYґ463RȑeOb\u000e\u0003\u0003\u001e_sB\u0005''3\u0012#=˯3&\u0003FǗQ\u0010\u0003\u0017mT[>ر\u000f ZT(4\u0016PJ$\u000f&i\u0002}\bh#6#6{dMJ\u001czb\u001fn2K\u0011\u00042PŇѡ\u00171_\u0011L\u0011\u0001Q#>\u0005\u0004\u00044CIё2\u0014Xr\u0014\u0003#6@\u0012T<}\u001a?VD\u0001絚~]Lr\u0017r\u0018\u0004]\u001egqbF\u001f&E݈ه`P'\t?\u0019\u001cpB\u0019!3WAaM\u001byͨZ\tvQPi\"Ԍ\u00047,@BPn\u001b&0e%(%B\u0001߂Đ(-@4|Y>Z'z\tɁC\u001e+-$!ϩ\u0007¶8ܑ\u0011\bPؘ\teFS[#6)\u001a\u0010\u001aJ]\u001eԡCBb*\u001f@||#6G\u0007$\u0010euO!_AB\u000f\u000e\u0012^2\fppo\u000e\u00043#>d\u0002\tH$\f\b\u0007C\u0005vFv0y[^r,ָ\u0014\"\"C!\u0017\u0018`#ZY%R*\u001e(w\fLLaS!#>\u001d\u000bo[2dܾ1_mC>c\u0012&R.0\u000b\u0007Wa8I#6G#pcD|W4#=>ɴ\u0005V\u0015i\u001d\u001a\f2+pBV(:pǃ\bY1\f;\u0003gp\u001bo,R\b\u001c\u0001A!`\t{dzϝ)771\u0003PQ?\\<ɰN#6\u0017\u0002Pˮ\u000fçB\u00165\u0019H,\u0010gRA\u001f%s躆>T\\\u0004J\u0015FTF\u0006\u0012.\u0004J\u000e\u000b\u00179xQ\u0002ȇ=7Bf4E\u000e/-#6#6.#6\u001f9\u0002!Ou\u00055#>B*rm\u001a|bjB}t\u001c\u0010K'QQbJT\u001a#6wwT\u0003CC̣\fBM-s$PPY{\u0004>g$ހ\u0012#>\u001d\u0014@\u001ct#=2uD>R\u0014*\u0012\t 4-|}\u0019\u001fm\bR\u0012Ҩ\u0014#6@2P$C\u000b@!H\fJgC\u0001KQtTS\u0011\u0003!\u001f҈!\u0013[AoL&$ʓsXZ)-W\"*\u0018#= \u000e((\u0006]\u0012\u0002\u0010p(\u000f3\"<Cټ1<Aof|rX#=\u001b%c\u0018\u00030ټح)fIQ\u0014\u0001zT=SE+6#=jRH> JB3%u\u0006I\u0017()P\u001apmG̍\u0006&\\k0A\u0010\u0010M\u0002Bd\u0010#\u0010|]\u000ed\u0014\u0004BDЈc:#j$\f#6\u001e\u0012ɴ.i5%)U\u001a7r+f*-v%*k\\pi5X}\u0010\"j\u001eي<BP!24!h\u0016F݅D6\u0007C\u000e_=\u0011\fN0)תp?٘$!O-#ό@p}S\u000f0aG\u0003>.\u0010&\u0003f\u0011EY:N\u0010S*{'@\u0019&,|PX\u001eCnH[\u0018\u0004\u0017y5(\u0003/]Q\u0012\u0012T\u000bqIAf\u001d1;I#>@\u0015\u001duw,\u000b\u0017Tk!D:$j#6^\u000fbWEZP\fu}W>ׯo+\u0007%]N(h\u000e# }\u001a\t\"%-\u0015V\u001bZ\u000fإ[U+D.\u0007J\\\bu\u0014DJ#6Z#6)Epp|*\fؾ=\u0018/g\"\b&hR0\u0014=Bg$\u0014H(|O#6\u0013\u001a\u001e+@O(\u000f\u001afєS#=Tg!tq\u0002\u001e\u0011V\u0004Ї,݆dt:[͞\u0012N\bg&\u000bXk(%5.-\u0018%ًCQcژ\u00042\u0013]\u0002!G4tI-b+_\u0007,bmt\u0006\u00059\u0018@\u0010?[T.DO)WZ\"^߼X\u0011{hD/FdT~FAv/DOÊ^\u001d3vzf$#=N\u0006\u001b/p,I\u001d\u000f\u000fz\u001fC>6Q.CV*27\u0006\u001f\u0017P!\u000fDPkX}0+Lci\u001c4p`\u0001H\u0010F/vSC*yb\u001eqA5RuOg\\8~#>\u0001\u0001?d#>H\u0002'\u000erS8;>\u0015.8ߕq0!90C\u0007A^sYؒG\u0002\u000ev)ޓA\u001cBаDB3\u0013ɳ\u00070??\bҊ@~\u001ah2\u001a\u0016b\u000b\f?njCX\u001f\u000f˜\u0019w\u00041\u0007#)z\u0011Clt{\u0003\u000fh[NmU\u001f\u001a\u0010L\u0002\u0003?BZJ \u00025?\u001di\u000bJ \u0006\u0005灌\u0014\u0011~\u001f\b8\u0019g\u0010\u0015QƉ!\u0007j\u0001\"d\u00028ӌdH\u0011DjdP\u0011\u0018JK\\\u0012hbl~jod̐#=\u0001\u0003Ut2\fvD\u0003\u0011u\u0004u4Dzz 1U(\u0003ZU#:E\u000b#6\f ^gO\u000eZ֠6&<\u0016l#=Df\\\biR\u0010\u0003{rՇo}ۖW\u001c/цcleN\"E\u001b\u001ah\tN6]뺈k{\b\u0011\u001a\u0011<7q\u0011$Л\u0010CH\u0014\u0001Dcm(\u001a:#u\u0012!\u001afU(b\u0006HDElZj\u001aY4j-)Q)mFlVd\b͚-dRil[43J+IѶMchS\u001b[5\tiQM4£ $!C*%4--\u0005\u0012,\u0014\u0018\u000ea|\u000fE\u001bht\u0006[y\u0016|?{Oqd,\u000fA\b_mKT3%Efcj5B6m\u001bHC}*~\\\u0010(\u0014֗\u0001eNi\f{-\u00039`7IoL7\u001a6\u001fG\u001f\u0003\u0012`& hQ\u0011R\u0014\ti\u0002Ww\u0010<)rYFa\u0005 WHC\u0006X\u0006!#=ϡ5\u001csP\u00022OSz\u00140\u001elg\t\u001c=\u001d\f'\u000eO\u0006:J8#=\u0010#\u0004 z\u0012G !¨!\u001c9\u001f\u0002\u001a8\u0010(ZVwS\u0011a[\u0005\u000fTyĂ\\ܓW#>r\u0010\u0013f&̨c\u0018v\u001f\u0013&Nd?\u0006:u:;1ˮ\u0018[\u0006-\u0013\u001b1\u0004{xg\u0019q#6X%ml\u0006B0ki#=\t..mb\u000bg6`cm#>\u0013\u0018-g,b8q\\`Ia\fM<\u0011Z\u001d4;ǧdlq\u0010-֧\u0018@iY\u0006C\u001egc\u0011*\u0017v =\u000fꖄ@\u000f\u0006d)B!\u0012$\u0015\u001a@P Xd#6<P@P5(\u0011+\u001c\u001d\u0010<M垘\u001b;\u0012stԳ\u001bd5Z!C#!)}fm5Õ\u0015`#=oh-J\u001bw0q@-(ɵY(RD\u0001VI[\u0018)YD\u0006K\u0010#=l{ʡxxI\u001d`#6N1\u001cMo)\u001bch{G+#m0'bA|\u0002\u0004b\u0006/_M{a\u001fi8i\u001dڒHD\u0002\u0001#6\u0015\bG\u0011zV\tFJ.~\\[\u0010\u0010tLϺ؞\u0005\u0002FY\u001aMXآMQ\\ֹۚL)P[3\u0006TQT\u0006u\"J\u001a\u000b\u0007\u001dTly\u000f\u0006\u000eMCHR|\u0019\u0014\u00151PZRQ\u00145\u00164PDC\u0011@T7}'vpoMB#=\u0012C:Us\u0002{?4(R#夈9S\u0005@x\u00061\u00142\u001f@ԧ.jk9|8{G\u0014#68\u000bSb\u0012M%\bEn\u001c\u0012c'1\u0007ڰ\u000e\u0011\u001d:El\f\u000e\u0018/}W~TDNe3\u0003M%|xr\u0018/,\u001cUkHgU'aq\bi\u0005X0\u0018;\u0015N;\u001dH@86\u001eFbPt޷Q\u000bF\";ٖ\u001fNƋ\u0014\u0002K%Cî\u0012`fQ$\u0018B\u0011\u0006$yF.Oy ijFF_\t\u001c\ti7#>IG, #6\u001e@#=^_\tAwx\u0016\u0019|jAd٨\u0017(|OHqb\u001fu\u0007szO}[)\t$P\u000f_\u0014A:o yLIb\fTyH\u0006DUO.C?g\\=]G\fpX_g_#ʝǙ\bX>P̶\u0006A\u0007]\u0001#=N!\u0003Q5Ad/\\?\u0016\u0010ןZ޽*Pp y_ªfa?أh\u001cCiٖ'jv\u001d2X\u0004ڦpTBZXH~\u0010I\t\u0010\u0004\u0002r\t5^\\\u0013@:Pu\u0001ꈻBO)O`\u001d!=\"'NP4jUh5P\u001e\u0017\u0005>gko]=!u\u0010?\u0003)!R\ty뒯{EO༄)\\_.0\u0003\u001c \u0002\u0006`E8HSVՇA\u001c\u0007٩=)1X>\bp󜅀ҹG\u0017v\u0004P\u0017BH>B\u001f9ӯlT\u0018;\u001e\f2#棲RG`\u0018g`qO\u0007`\bNP*\t\u001fXL@s|Wti&I\u0003*.T~\b-$CATUH4I7p|\u0013P$HQ@~\u0016\u0019FsnI\u001bgt6˸e qhcl18$o7+03\u0007\u000b\u0006\u0012\u000f\u0016r̙y,\u0015fޱVǝ2\u0006i6D;K?(\u0019J#=<jT\"squ#=.}\u001cE\f\u00113Ĵa!90Qta\u001fM$CL2lš;=2\u0004R%jmq'\u0003\u0013;\u0004\u001e> \u0004R%ViKIѴV\u0010` S|=\u0002'#6\u001a֐3\u000e1\u0010r12FH\u001fQ>r|wv\u000e\u001e\u0003Gs\u0007dW\u0005;\u0002v=HE<G'\u0005\u000fGxG#MOn3k䅴\u000b\u0011\u0001V,\u0015\u0007=P\u001e},U\"E\u0016\"1+Xc\u00112D\\s80A\\\u0004\u0002HWꩳ9TZ(R [t(\u00149d|\u0014cb\u0012#6+9CE>v\u001e\u0018#62C#\u000eUY^Y/\u0019lP-E\u0012\u001cc#Ek\u0002YB\u0006}y\u0019$\u0018\u001ev\u001b\u0003\u0003\u0011҈IM=l\u0007\t۹#6~P^-'t\u0014.CAAd\u001f|\u001c\"U7(r&\u001bБYnmbڋ\u001a`E\u0019\\B\u00141*\u001aUe1դ\u0014\u0003\tn\\-;#>5przWRc!\u001e`c\u001f\u0014\u001f#/\u0013\u0010|yB*gk%հ^[6xW1<ɖNm|\u0004xf8Lq]3\u001dbE\u0002 If4\u000e߷\u0016[#k8A#}/\u001c\u0011f|\u0019\u0012\u001d>\u0010v\"Z$z=\u000bEh`}L>E\u0001W\u001e\u0011\u00078F)B\"\u001caU\u0015\u000eD\\/˫ݱ%]\t\f=wí;\"@ϐj\u0018XQc\u0012j/|afcwDt\u001d#s#=\u000f\\0\u0001Ȝ@#=Ѕs\u001d\u000b*2#6<\u001a#=\u00060\u001a$Ɗl\u000bshN\u001c\"S3<oX\u001f]9}d魇)s;;\u0004ZmG'fɄf,qL(M\u001aE{9\u0003\b\u000b\u001a(X`{׼/Lto;ô\u0006\fII\u0004q-=M\u00030#qRAL\\ڣ\u0014\u001fݚ-vajy}7KY>[='B=5%DTy4=;W`⩯|\u001b\f?\u001b#k+mD/1>'\u0015Cx3.>h~G\f\u001e\u0007q5Cz\u0007L;\u001d@r@\u001d|ϧ\u001f;d\u0018\u001e_s8G^\u001fP!\u000b\"煳Ǧ^q\u000fr\u000f\u0018>ĕԔ\f,2\u001er%!ј7\u0006Mr\u0015\u0014\"\u0016\u001f\u0005@H\u0019\u001f\f\fŬv\u0016ȓ1V\u0015\bq\u0016>aέ\u001b#>\u0006`?C7\\M놑\u000f3!ĵ$fFK/#6p\u0004\u0013l8g+\u0004b/}=7\"vīIo/w\u000e<وрN׬\fG՘\u0010\u001aRDx;~>_UIzr\u001dz\u001aOͪ\u000ebO#6K4\u001a|H\u0013E\u0017s3R+R\u001d8ݖ\u0014:.=#\u0018\u0018pNox^#\u000fb\u000bn}{\u000e*!(0q1;#>UTCs\u0017q#=:g'\u001dđ\u0011,As\u0010<H|''/]\u0007_3\u000e\tȑQڧ^\u0005a\fBڨ(\u0011>j?\u001e\\)\\`44UM\u0007߰q@h\u0018Sh\f0\fkd\u0005\u000ebD0CR>^fA\u0004\u000f?gRl!w#>5\u001cH^RDI<Q>M!\u000e$\u0013E\u0005hjϵ}j\u0003\u001c#=I%\u0012l~t:{5Azu\u001en?c/\u0001=2{!OvzO`D]c\u0001jTſn}Y\u0005\u0004`xZgclyI.m,g,>D+\bh5HW~lLScU6:\u000ea\u000eƱ$$S\u0019o2$f@LL1M\u0007S\u0005fH\t\u0010\u001f'\u000f\u001aH_(,\\{:{O#=u<?&\"\"&H?n|`&:\u000fyv$_;?nC$R8ی#>ɑdDh)\u0007y^;{6jؐ}\u00157X̠̔v\u0006G&CrR]kMV.ڹY\u001a\u0019[].l\u0018ɕ\u0019\u0012ق\u0005\t)cwO\u001dr޽1^pN%ܑ8Q#>\u0006F\u00038p\u0017'D.,;!f8$\u0010mAz&db\u001a& B1D&B(\u0012En`r!9\u0014\u0015\u001c4EV\u0019cdӉ\u0003>߉E2Ƣ&(D5hW\u0015P%\fv?\u000e\u0007~:hG/-\u0010e\u0016\u001a{\u000b9?k\u0017ev\bȰ#>\u00041d\u001c#=n\u001b\b\u001b\u000fq\u0005\u0001Eqk\u0002O\u001aPȍ9\u0016:4J#=(ŖX[Uɧt\u0014y8VJO)\u001duև*{h\u0016}+뺾E\u00103\u0015HT<P\u0014H\u0018TJ`NG\u0004\u0014GIWȏ\u0007~/Ts~\"R\"\u0007|\bD\u0013(! Бǆ^]2\u001ar5\t-cs\u001f#=\u001a\u001eف\u0001Cp?k\u000f\\ϻwt/8G@^S\u0005XbQGO QQr\f<|X\u0007;` #\u0002$9\u001c\u001bRoY\u0015\u000f \u000f#6x\u0006#w*Se܀~\u0015]Mp\u0005\u0014QP8\\\u000f F\u0005\u0004y^ Wuq\u0003\t#6)ڐÎ\u0001@~볒\u0001A)]\u000bV6aw]tEr\"R\u0017z80\u0018bL#=da1ɐq\u00170#=Thªd,#>i\bG\u0012Ae\u0004\u0019t{Ɛ҆]\u001bD\u0011\u0004`Q\u001ak\u000bo\u00039mھ6F;\\$\u001a(R\u0017hFI\u0002n\t;4\u001b\u000fN\u0018Va\u001bk\u0003\b\u0010hըniݫ&.'!XO]HjvF\u001d\u0003.pI\u0004\u00014,JaKOeC?:I=\u0016k\\j1Q6hOFf1VQj\u00038Q4àrxx}\u0011\u0007q%NM\u000fz\t\bU#>xU\fL[aJE3dxfc'U#=[\u001a)5(0$_YBx<\u001f!l0ډB0R[Hu'0\u0019qju\u0004\u0012dcQC\u0001\u001cbh=Gu\"\u0002{\\]moZ`p)B\u0004\u0006g.&\u0005\u0011u\u0015\u0014>y쟇Bq%Isq\u0007K\u001aK\u0003ѹ޼(O71|Le ;{Kg\t\bɺ}I]JutS!#>b\b%\"[\u0006\u0011!IwA8++ƻ4XN!\"fi\u0014sz\u0001C~3Se]) #6\u0019r#=5&ڕ#\u0014$?I\thRAg`m;6\bx\u001f\u001dw|;U\b\u0015,tof#=Z#g29;_WM0B\u0005\u0001 \u0004#=h0KKqDẝC\u000e1\u001dlGq\u0003#=t4oQ\u0007\u0006l\u0013{(|\f\u0007\u0001tGMHr\u0003K!?V\\xj\u0002+=Ȓ#>R\u0013Izg|wQ*.Ea\u0006.\u00063S\u0017RD\u0003\u001c$6\u000f|p\u000b\u0004h:t6U#6\u0006\u0004\bLs2cb`&a\u0011\u0004\u0010J\u000b!M\b\u001c`?*\u0014C\u001e|\"$IăI;#Qq\u001dQ`Č\u0001\u0019\u0001\b0ZFS'8|nغ1,ZwJ\fL'\u0014@sÉ )h\u0012d\u0002bA\f\\\u000e|=TQy\u000b͚\u0017՘C\u000f\u001a #>X5\u0018J\u0011\u0004w`;\bB\u000f盹\u0005i0f2\u0011\u0006#>TO\u000224\u0018[j8p12Y.\u000e\u001clMd\u0005\u0012dR\u0012\u0012\u001b\f#6 I(su .F2lOa(#6\u001b7Z\u001c 1%)e\u0003\u000egnK0r;G]~jx0\u001cZSXP#>0>/\t5a(Ap\u0004780O\fO\u0002Nmǣ\u0007\t6W˸=!#6\u0018.qw롛\u0017\u0007ZF1\u000bHs\u0013!r׌8zWd%{X'\u000e\f$ya\u0006\u0007r2\u0004v,z8#x\u001cu)\ts\fyOA' u\u0001ٹ\u0013\u0002g\u001a&\u0019͇'d3΂\u001fCL\u0006<$~C3``:|\u0015n\tP}`oA=s\u001e\u000fٸ=fC3?\t\u001c'\u0005%\u001e\u000f*Sq5pPA\u000bl8[.E>JD\u001dћc\u0006F1!m\u001f{\u0011n#=\tI\u0019%}:0;U\u001ff\u001d`9b\u0011R\t*)\u0018_L'\u001ftjPn\u0006OBw\u00144J)\u000b\b\u0002>$\"L\u001d^_>^Y}O?NB#=nvCmA&t/u=z\\v&1<x?pFm\"p_ lL\u000f0o \u001d)#O#6\u0006rQp-#=e(K5#>68\u000f.|I\u0011')\"U_Y\u0002=Fԥ1\u000b~\u0016jZ\u0011b^A\u0017ѭ]jII#6\t#F<iff1#HN!ĩ**V#b`n#=I$Ale8Q+ZZ.{HONGp,r$\u00031Wr]ܳDX\u001a]ěW]\u0012h2\u0016j(ƃ[2+UD!yHfMHR&k\u0014#>\u0017%\u0003H\u001a($as<sl;!\u0016%1 E\u0011\u0015B\u0016\\\u001b\u001afe\u001blPbdeu#=b!D!\u0010shL/\u0010OT/\u001fJr\u000e}wbL.ؚKd[DZ\t!gqpS\u0002$2\u0010kF-\u0001X0K\u001bB_B@6\u0003\f\u001es<\u0011[d*\u0012#>\u0011ᐾ\u0010&l\u000f\u001b\u0006M/gox\u0019\u0005\u0017٥t0\u000fD:=\u0017$@\f|@\u0010\u0006\u0013ݢ_466dP#ƌ\u0013I<ƫ\u0006(.\bˎ#6},*\u00193?h#>\u0003!o\u001e9JT ƨq\u000e(ǻ!\u0006.|\\>*(!ZP\bJj58`M0\u001d\u00075W/1=\u00140v\u0015Ɋb\u0012S$@hg`,\u0012P&]^MFk\u001aa)\u001dXA/\u0017\u001ḍ3\u000f\u0012\u0002kF5\u0018^\\X%D\bJij?\u0001&\u001coBVaL#H-X~%ΰm0w8,;:\\W(̵v,RplFYЋ)\u001bc5\u0019I\u001a\u001dC\u001c7R\u0013\f4K;\u001a CU&\u0018a\u000bKZ&\u0006Esa4p\u0016\u001a(5#=93ѬލPj7\u001f+_#yw^WZ]\u0015]n0#>Xa \u001d\u000b%\u001d䋒\"2TUN\u001cFIwYįJfF!\t7~^\u0015\u001b`\u001d(A(4Ҋ&\u00054U7#=$Y\\q@vCCtN\fJ(d\\5P\u0019\u001cIX\\0_eka\u00065\u00046yĊ|EHaF\u000bLZa#U`f4\u001aM\u000e\u001c[dq\u0019\u0011\u0011\"#>д4X\u0011D\u000bPІX\u0005a\u0017\u001d\u0017\u0006\tPU\"l5Ex55\u001c7n\u0015\u0013|T,ޥ\b#6ڎ\fU\u001da`#=Y4*+\tS|8,ݡ[V7\u0005\u0015blU&1yp#1\u0012@Dv,:.&$*`bqYE#~\u001c\u001b\u0007VG\u00168$E0\f1N1h<\u0003lVhػ:aLL\tj+\u0003fgmٮ\u0002֙\\+{:l>yX40|g*\u0019ED6q\u0004aaWa\u00067dC\fL}$\u0011\u00182Al\u0003RV#=\b0cIˌCV2r8j\u0011\u001a4ƣ\u0011Œ!ƫ\f*S!+\u001a'ۺu}M\u0007\u001b\u0018řxkC\u0016\u0003J\u0006&Q\u001axZ\\*6{#>\u001b\u0013-ʌx.\u0005\u000eM2D{k/\\h%2]<]I\u001d\u0018A\u0011xV_]KZzN8_Cu{yNgZ݉#=5\u001b঻\f^9/yPz{_\u0015\u000f\u000frPygayC;uȕAJ֍G\u001f͟ݿ\u0013q\u0003å\"9\b\u001de[[zJS!`A$&$ \u001c\u0019\u0007b\u001a\u001e&B\u0003q]\u0004z\u0013 i0]̍'\u0016ˣ=~\u0004lAi@`\u0005.v1\u0003!\u001a;ni\u001d\u0006s@ݛsA\u00012L7#=#=Hnz \u001bSRtey\u0012\u00131R\u0013,80аtxu*Z\u0003,W\u0003crF0S/\b\u0006P,\u0019T\u001bLAC-hB5!\u001c+P(8!\u000e\u0002BX,Lp8vͣ\br݊q}uѪ\u001cee\" \buiG8\u0019XUw[B9 4\u0018\u00175!uiv5\u0001\"t\u001b2\u0005D׆M\u0010\u0015ɶ낅T,I.^ \u0017lBQ4 s؛6rB*\fg\u0017alK듳HoN\fk^<\u0010l]PʰmtikYM&\u0018@2&\\~\u0017\f\u0001*Khg\u0014M#=ǐo(X34!\u0007V3\u0003;d\u0007ъ'\u0003)@\u0007+:{M\u0002>_#\u0005<N\u0003\u0005;{@Ԛ`/p4${$BLE^f\u0019 c#=\u0019Z\u0001y;i\fE\u0001#1C\u000787#=:\u0016\u001ea#=5L<\u0012/mn{ ƟF\u0005ْ˄&1z8#>ɢbc3\u0004#\u0010(=X1\u0005\u001f;p+F\f\u0011'vC&\u0001\u0004\u0019kgI\u0003:'&\u0007 0L@,\u000e\u0018ߪA7S۷>b#=\u001b\u0018 \u0001#6P0a\u000b\fL`v{\u0017'(J4.͆ܐ؁>\tl;\u0002&&3J\u0012\u0001T\u0010@\u001d\u0005Q\u0001A\u0010#u\u0012#6\u0004\u000e\\\u001c%\u0004؁\u000b:O4#=4rh\fZ\u0002.D@@\u0001#=r\u001c.\u0004\u0002\u0010F\bA\u000b0=\u0013p(\u0001j\u001fd!]\u0013\u001f\u0002\u0002ó!\u001b\u0007S\u0001\u0017Jp\u000fLDe\u0004Î\u001cbd\u0015Bc\u0001\f>z67~S_X˪Y\u0013\u0002^y!%/-g(uPdc\\3$&&5\u0001dGx+^CM\u0011Vp]o\u001c04\u0014Gz98Y3-{p_\u0013!UL4QU꬛Q[j_=w\u0004%u\u0014\u0001\u0005\u001a\u0006Hb\u0002+ڼ\u0014ȆY\u0005\u0002X݊2D[\u000fZ嶹jkJ^3\u0019ky&LJ\\\u001b[\u0018m㴕ۮz\u001b\u00164M#h+@)B\u0010RЪ;2\u0003rnQ#6\"\u0005#6\u00035+C&A\u0015\u00180\\\u0002$\"P\u00042T\b㼓?>ObOT/x\u000bO\tʘ<\u0018M]\u001a\u0018\u0019t6bt\u0003@\u0007#\u000b\fR`1C\fkw~{rjR&c(˪<QjFk{Fkt1jkDם_Qs}PFB\u0013#\u001ch\"$2di3fMU&4X3\u0015\u00166\f*&(RҔbaEEQnk}2C&i%&)MX\")\u0019m$RbDQ\u0013\u0015E\f#=/DFYaAM\u0019A&LF\u0019S\u0012Hm#=\u0014#FXJwW!-\u0014*`LRd)p*L#4\u001afҙH&ZH#\\~\u000eu)X#~_\u0014ICq\u001c\u0012\u0006Ӣ\u000f&\u001a\u0010F\u001ezP\u0017W\u001a3leR\u001bzð)\u0005uA(ˬc\u0005FYl/fc#6]ꐞO/$Nv%Oޱ)Dň!N\\\u000b\u0007B'CL.\u0002@_#=\u001es=\\QA*\u0015e1G&:\u001cЅ\u0019{̅\u000fv#=抺\u000f*(:sCB|G\u0003xH\u001a51`O\u0019\u001a\u0005$P<nF\u001eN\u0013>9^^3(G`LݣdLpag\u0011$ؚm\u0005sO3\u000e#> \"7צӈm#=DĨ'\fB|#6_\u001c\t챡mu|-*3\u001dk\u001eQHի0X5c\u001ac`:B\u0006+.+fb#p\u001b@ƌp0#tN\u001bfa.;\u0001\u0007Tq\fȁK3\u0015\u0004T\u0002 B\u0012%,8QEdf\u001dݧ7TF$m\blF,Hm\u001a*XHhaZOg\u0018\u001bcFv>i\u0005\u001bܠϐP_Q\u0003Q&\u000fOI\u0016\u001b4U=eNuvw|K:h\u001eb\u0006\u0014\u0013e\u0018p\"ȌOPfI}L8#\u0007d#6~qO<:'\u000b\u000b'xf\u001f#6\u000f?_7:y&k/n\u0001\u0011N}ѓ!\u000730\u0016?`!\u001ej\fTr4\t\u0018*:\u0017\u000eCKq3;*~\u000fXƲ<m4\u0013f:[ī *\u0006cc1]Û\fh\b.X\u0019\u0007\u0012F\u0016\u0013#>A\u0007p\u000e\u0007\u0012#6:J2@{s79x~u\u001di\u001d~4UTFrC\u0010е\u0003\b/ܴ\u0002\u00130V\\r_\u0006lEZ#>\u0013\u0010&*T`gD\u0016f\bGϳKC\u001dW*f=\u0019J'ȯ\u0007l1O\u0013\u0002\u001fuG4yT,\u0018gX6\\CmU|Z7Tfڅj\u0013@Z(\u0017\u0005M'ys\u00114k\u0016Gls`) 6i<aΦyE\u0002cvpޥy̜kGUZ3OZ\t޶XY\"\u0010_\u000eĔ\u0005\u000bqub\u001b^;-e~'ecƏ\u0010\t3\u0005x9+XKbs\u001c^=vvt U\u001b\u0013wi\f\u001e\u000b\b>RD6opXM\u0011\u0014Cy\u0018`r}{BoYst\u001f+\u001er䁩\u0013\u001c;HmS\u0018i:ВE\u0017\\\u0010\u001f4?e\t \u0002GH\u0003r\b,\u0001\u0007_\u000bTatPA#=\u001eX\t.\u0011{>\u001eO\f\u0007퍚s>70#Ì`\u001a\u0013okՌ}%{\u0014_l\u001a]^UgK\u0001!6\u0007>$:2'6jL6I頩]0r<rePH\u000e\u0014\u001a\u001f*_)57#6mKV\u000623\u000ecx\u0001U/b.\\44V\u001e\u0010(\u001d,Q#>{dWì\u0015c\t3Cy#Ui!{a\u0011VW#6&GH=#=΁\u0018k*vTE\u000288Yd8%ZFp_\"=U\u0007:ym(H\u0016Aa\u0006+0@(\u0014`\u000b9\u0007ǸIz\u001c\u000b@\u000f\u0004Gp;y\u0012C:WRK9q\\cN#=XQ\u0002\u0002H\u0007#>\u0016\"\u00126v:&ռ7H.#6w;<ϧAy\fD1p} g}1\u001c5/7\u001fDőaڹ#R$m8b\u001dʪqX()\f\u0015EڂG\"RAi>+\b&q\u0018%T<(j\u0002E\u00106[V̊5\u001cm~\u001c\u001f;esn<X&o\u001floY\u0012*P9*-\u0011QMZ0\u001d=8Z\\Itap:g2\u0013@W1o+Z\\Tc\u000b\u0013ٻP\u0012D&\u0015OD\u0014xC&Ã\u0018u\u000eNuNN(E\u000eL>Qxi<;γ/!:{1<G%k\u0005#=}\u0013sq>ޮR;\u0010(t\fb/iR8*\u000eR\u0012z yy_?T*1\u0003fN\u001d#>*\\5;͋+;2ܩL^P\u0003\"=\u0011cWb;\u001d>{\u001e*dL\u0013P]<0Viygtk1;O\u0011^,+fr\u0012A\u000e\u001fTeTEpYwh\u0010:\"{˰47\t5[RJ;O\u000e\u001d\u0012;_\u001cmfQp\u0012,p\\C9Orfd5HYZ+\u000e{fTjՒX\u001cQO*#>1hÿ<\"L]S$ܲǕ\u0012\u0015s(s+-;4D+\u00129u!sӢPmRTI\u0005ChJ\u001dJuV[N`52!#>`pɍ`n\u0007*`\u0016oFT<\u001a%\u0016Twȝ.L<F\t\u000e~2k2Ȓ\u0014O'\f;mΆ6\u001ex\u00144ۆ+$\u0003v1&!9\u0012\u001au$\u0007\u0010A\u0007\u000f#>`5p2TLSq\u0010id5v;vw;_7k\u0005k\u0017px&}\u0011g/\u001a!OE yr Ȍ$t&|h-\u0005\u001ag,*c\u000eSłK$\t\u001cZD&\u0001@\u0018\b\\w\"Ǿ2s\u001al\u0013\u001dt\u0003<к\u000eȁFSa\u0012\"\u001c\"`(s]\b_:܈΍\t֩lǅN[#6ѬHqM\u0005IV'#>PISVzFN2u=M[@<\u0014lXh8\u0014p˺\u0011\u000e#5'XF8>\"T#>[.K=N\u001b81Z2\fC@갌6\u001e0x~cț}\u0017☤2\u0011:9bLcw̀'pU\bnF\u0018-#>\u0007ǚk;aj{\u000b\u0006p`Vs\"$Ƹ\\xr3cNvv~P)Wn^Ƈ\u0012<Q\u0001\u000fSJ`j!\u000b;w#=ׇM ͪvsj\u001e\u0006nӚ\u000b3â7\u001cM&\u001d4h#>:ti2\u000eB\u0010隄92\u0005UfN<!\u0002l\u001d%Oř\u001f|1R\u001cI:E&\u001axyqf\u0007wJ4򼪡?>5qtv\u0017RrC&\u0012c\u0014\\f\f%Co.ь\u0018԰\b˧9]l\u0005\u001ax_^G(`ĈR\u0013Rܐ|#=L\u0012\u001b(\u001bhFFZ,2\u0002{<#=〸h\u001aߗ\u001dt?\u0016\\vHi]jT\u0010♪%!\t2C\\<[\"\\c<\"4uA;\u0014x\tIngbzdo#>|B'DsQH7&kb\u001bX\ftw\u001aE?#6\u001cc\u0019,b9C\u0018\b\u0005Ơ|X\u0017:ņ)\u001b#6Î]IP\u0014/~.xpP[-C]a\u001evh<g{Lm[/\u0013#áq\u000e<F+F홻\u0012x0:(V\t첀+iu\b\u0010_pw#NY9\u00046\u001c\u0019\\bd}k85˺ZM`fȉ^-\u001cܼ&|DNw~0%\u001cܽCLrr\b#>AjsVCri\"\u001c\u001e 2!\u001a%3-\u0007~\u000f\u001cl\u001b-7( gR;\u001b7pCn\u001b]\bp\u001e_\u001cmJr\u00064a\u0006<F9}>QmJ\u0003c\u000eOnSE%Ş,6F\u001b0@κU\\\u0015OݬǞy)lBo\u000bI8dw\u0006\u0018ysvJ5f5\u00013\u0002.VI(+\fo\u001d\"\u000f\u001d\u001a',y\u0013v\u0010\u0010gTHAj\t9]C`j{@a/\u0019cd^l(qh\u0006c6mOh]p3i,RX0Eq\t\u001bCRf\u000eS160\u0011}JC\u0014l%NplҦ>%(g㇣W=8aӌBc7tԻ\u0006йe0?\u000e\u0014\"z~)`Nm4s.2wvd\u001dӮKD\u0014i:b\u0017n\u0003P\u000b3?SݮD{\b\u0007#6\u0010pJӤx3$00s\u0011\u0011pH\u001d@.wOx\u0019a뀄fp@tD!Cq^jБ\u001a\u001f(p\u00167\f{\\ﱳ/F{dUM62\u0007BZEwv4Evchݾ\u0015v\b\u0019Cلa-#\u0002p`5\u000eƭ\u0004RVDc6\u0010vK%\u001d\u0019ʔp)#6\u0014|̇\fh{⇀,959f\u0016ҳmZ\u001df\u000b\u0013oK\u001frj#=2iԳTI\"\bfS1\fԻ \u0019\\\u000eNV@ِ\t5\u0010^~+6ϴG.:Tt\u0018\"\\fG<bE\u001b(^l\u001b\u0007bh\u0004\fETR5\bv\u0017k9\u0007[p\u001a9SlL:tPQ\u0013ȁ\b\u001c]٣.TnܻM4i\u00115|`C6\u0014c\u000f8\u0015\u0004:W\u0018f\u0014pPK}#h\u0012\u0002,\u001e#OLaޞvѴ\u000e\u001c\u000e87\u0012S\u0007MͻEPc~U\u0003id\u0003\u0012\u000e99\u0018Bi\u0003f[\u0006#>\u000b\u001aK\u001d\u0014k\u0011hg\u0017hI87\u000f<bxkU^qY\u0010k\u000f1W'/\u0011\u0004XҤ/c\u0011b0+\u0016DrSH~o\u00185F]sqZ\u0014\"ڹN.\u000b~ID8<Kث\u0014vkE#f\"rW_Z]cRآNrki(ExEH^\u0007NʒgG3\u001d2B]WxaX)\u001aAh0P7WMjS#OyoP\u001f\fL:*3&J\u0011yo^K\u001fnJwF{ \u0019Cm>P\u0019s49s\u0015PZbp\"#\u0015}:N\u0012g\u0011L]\u0019&pQQG\u001c]GYs5f-$/%\u0003I\u0014\u0019ٮW\u000b3#=r2j^\u0010#\u001c\\oƫYG\u0011o䌫F:sZNqw㜫\u000b6OsE1X\bn\b,]]˹E5{;.q8g\u000793<n\u001d\u000fdep\u0018<@K\u0018\u000b\u0019$H#CY\u0019\u0013)\"`ڸ`#'=^xoj#=+ٙ9zt#6ts#>%.(P%evsHȧ]g\u0011`=\u001fC\u001cN΄\"8h=RYA'\u000blaid8yǤSG|ozM\u000b#.*.2[QhI\u001d;pkg'\u0017Y7\u0004\u0018aCD\u001e݉liK\u0018}(pv\u001c}qp#iHվ[%NP>\u0015%gNrwt\u0010iH$ņ\u001dK=0u&%N.f&NF#6\u0019am\u001b[\"C\u001e#=\u001a\"5uJ\u0004$\u0011\u001bbU\u0001Ԩ\u001a4D0T0\u001b9md\f3\"\u0019yRaaT4I,\u001a{)r\u000e*fz:;EW\u0002os7Cc4)q{\u0018V)\u0006+\u0004wkG-l\u001cHK>2T\u000eܗS\u000f\u00141=p\tZ9eMˡ8Ǚk*\u0004hJv7YކKx3)&(\u0019E#>>rV]s\u0019ʋi8외k8/b\u0019l\"Vsh12#=\"Gn0hsvm\u000b\u0004͜f\u001cH&\tjr\u0010K@\\sE/!\u0003zݡDHTTVY\u0014\u000fډ&\u00183*\\[p!IYx}?X\f&FTq\u001aO\u001bENMpLR!߲mĦj؇^\u0014-2tЅ1Lvh;mP\u0019G\u00164AɈcf&\u0014\"s*\"\u0013C\u0010Rɐ&\u001fUP\u001dYs\u0012լ\b)?4mh\b\u0014&;0\u000b\u000f;\u0016ꐅr92x4eeI\u0003ze ,\u001et\u0010E\u001cCfVtK&c,#>S\u000emA\u0006\u001b騮\u000eL2P6Yɨ%=MeiJA[;l[2ptxVNYs\\B\u001aN:VPbja\u0015#>\u0006C%;X#=La;j%76hOwZR%D,J\u00193OsIeVa9Ӯm5電\u001a2\u0004ܵǝƣ0m2#>\\tfEDM=U\u0002}E\u0010\\<Q䘴b 52D==\\$\u001eb_f=b\u0013\u0017\u0007QΥO\u0014\u0018)(Ǜpُ/\u0003ì~)\u0017a\u0015\u0005Af+E;gLSj&\u001d$H79{_\u001e$^|[Wn`F嶶\\6#8;!]WM#X)FSqqeɶ\u000bME*RVK\\[e\u0019&6ag&\u0007d\"\u001b#>h@\"]>rQ2e;\u00062\\%gT׉3(R:8yD\u0012&G\u0017Q\u001a:\u001f#=\u0019WVO\u0010(X\u001cn;αv#=\u0004aZv~\u001d\u000eQwG)]\u0015(N@B\u0010vy B\u001cy9GJx\t\u0005Č#=\u000f3\u001bՈftun1x\u0016LH\u0017s\b!,KmDm\fL1$Ƭ?\u0003\fqlڧ\"i\u0010\u000e5\u0005\u0012N1땴9NM[\u0004x%#>#6&o\u001a\fȢQUHCnI\u001a #>MKIO^}\u0017&iC\u0015.Ԉ4G0d|[\u0019Oб&n5\u0019ݳOxdRJ$\u0003rݿ$C\u001brjg@wI*3vZK\u0013r͐#>ö.{\u0013h>6eDt258: 9|爃\u0018\u0013mfZjO^oEi3\u000ejo<Ep\b\u0004,jL\\\u0005I6\t\u0014Jr>Lx*E\u0003N=fƫ\u001cQPn\\I,ʪ\u0002Çc^}\u0004<\u0004O(Jj;A*\u001a+i\u0005vPm\u0015ލ,\u0012tP*\u001cON;7FP%ؒ[x\u0006\u001dhay?\"\u001a\u001b엑ԃ34\twtO\u0010ѯ쑷2#=̈́J1\u0002\u0018:#ĀxI.6)i7\u0019:WX,aդV\u0019zԱ}44ً\u0003dRva;Q\u0003(sanZF\u0013.A5ϕM )\u0006\u00102\t\u001aCrt̲\"\u0006FM腰]\u0011WeT\t)c\u0010lM\u0017f\u001e\u0019\u0011\u000bf\u00046f\u0010\u000e({0q\u0011\u0017j90lm\u000buqEP4#=لI\u0016\u0013L\u000e\t](\be6aiƎ؞VXQɨɥۘ\u001a\u0013P\u001d\tv\u0004]٪;\t\u0014Nә4Ο2QK-勔d2MK2J`l#\u0012\b\t&wMX\u0012Ѹh@\u0011\"\f<R\u0014q6ƙw}blT̬͆#>\u001dfea<4kaK\u001cD:M\tsPԄA\u001e\u0013i?0O\f80G4R\"MCrBN\u0014UBJ\tuZ#6䋌SWHb\u00192jj2o,#6:\u0019ve0查zqm\u001c4ax;k[\u0005rn0Sk`eC\u0018$v}4\u001dcd\u0019پG\\E\u000eŃ#3pbCBð:ŖЎ\u0018\u0012cB\u00190F6\u001a\f\u001ch\u0016\u0018hT<01\u0018\u0007\\8\u000eyXYg\u0011[=\\\"\u0006\u000f\u0012Q0vy@Tb҅[Zkbyml2k#o]I?8K;IUωh#u\u0005\"tvnL%#>3\u000erapx\u001b;RF\u0007f#>\t\u0003YV\"\u001c˙%\u0019\u0001\u0019g(o\u0007#6;1oiR^[Hh4L\u00049e\u0004eTtfmM8$#=ylh\u001c\u0017c\u0013Y\u000b\th<dQ\u0018CjAʮf\u0001FsM!@RTI,hزi&m\u0005q1d0\"nA\u0018s=w!v<\u0016K82\u0011\u0004h0t.ȎaM\f\u001cy::\bI:/TA;b\u0002\t\u00130#>\u001dh4G\u001e\u0014\u0006uMi`hxȟDDu#,BdNA^r\u0005Ͱ'C,3\u00064\u0007\u0004\u0004\b21n\t#61+p8#=\u001cM\u0017(LCY&Isy*\u00058Q{\u000fIH\u00129lC#=@4NN\u0002&fm3Pr8Xm\u001a#6'\u00017\u0005\u0002\u000b i3sX؏Gr\u00157l֑M<N{lm~\u001b<ǕR3L*z\u0011l.G\"n0\t@L\t\"h\u0015?#6\u001d#0\tJ,H\u0003#>8y\u001fE\"dS7P4=\u0016A4 C/Sv'O7\u0014s\u000f \u0018\fߐv*1+Z\u0001񾍈\u001fw\"\u0010{@\u0014N\u0013X!v\u000f\u001eJ\u0001(\u0018!e\u001f8k\u000f}v;M#>\u0004x\u001b5o~\u001cV\u0013-5\u001fm\u001f`\u0001\u000fS\u000e!\"\u0005\u0015U0Q)Q\u001c\u001e,\u000fN)84rCs7)J7\u0018)r8Yu#>\u0019Q1\u0018b+11\u0014`R\u0005\u0011#6;dd.];j @ +\u001f@DVcpu\u001aPG\fZ\u00120\t+%\u000bx\"\u0004\u0007:\b\u0003r`P\u0010\u001c\u0012#6H\u0018ha#=0)]yp];LX]\u001fsfX\u0003cj,Fc>K~\f̛\u0019X{ܹbe+TKq-&\\js-*Xb\u00047+R\u001b\t$#>Ň+(y;[\u0014<xچ\\,x1\u0003ЊVp|k,F+\bH`\u00138f\u0004.υu\u0006\u001c|e0\u0016D?xzLd]K\";<nّ\f}y+wZWQ;R\u0017\u0005#>\u0005ҷa؀YS;\u0002\u0010THGfz#\u001bmy\u001dw#=d\u0015\u00130!$C!-;\fq7b.o\u0005r9ُp91![^V=&\u001ab\u001eV'8p\u0018rt\u0003No|{O\u001a׆O2a3}4^$x0^}w~G^V{={5\u001fAys%rtd\u0017w<6&~\u0014&bb\u001c\u0012d'eď\tLf+\u001dcq]ZUo>'pi4\u0019#=[\u000eI0տP\u0015\u0018E\u00145AadOiY]u NM]!CI\u0007\u0010j\u0019ƞ\u0011\u0012.\u001b^%%)Ӥ=FlzwD\u0015\u001bDQ2E\u001dg,tPC\b3\u0010\u0002IżxRZ|3:\u0013O37ғ\u0018\"\u0018!\u001aN\u0007LF׏\u000f-x\u0011\u0012\u000evgHu\u00063ߺDs#6N8;M@:\u0012SP\u0006ϡ\u0011\u0010JkN%\u0013\u0013Kdʘ+^\u0006`x*I)<+aTTE5Fd\u000f0=H\u001ebL'6\u0019\u0006(\b0ㅔ!\u0003L\t#TQѩVZ\bJD\u0004{D\u001f3bsAh\u0001\bcJ\u000bUʖ\u0013p\b{O)@\u001b\u0003b\u000b$\u0007\u0017/w\u001bRx\u0018GN\u001a\u0010ؽ>>=Jp\u001dNsG[^O=;cG\u0007'#Z)QBR\u0012\"lhN\u0013N69U\u0018ad\u0017#6X1\u001cf/w\u000fyC$N缑4c==ູ C\u0018\u0001\u0004Cp@?ԟ#%FH'F,,1T:sa\u0007\u0016Di\u001e~^!\tI@4+!z~p\bCAž\u001eL/\u001et\u0002\u0017؉Co#6Ox˝C\u0013%7\u00064ߚ\"<\u001e)M\bMTlh%(z\u00111J08!/Y\u0001W\u0013?1\t\tЏ~ˉ*<Л\u001cU\u000f\u0014#6\u0017Z:\u001fTl?2p?w\u0007AJZP\u0018y\u001d\u001fϰ\u0007\u0007㈔o;\u000eϐ\u000fl\u0012.b@K\u0003I#>!gB\u00184 圻`0#>Do3\u00186\u001b\u0007ቒl۽P\u0006p#==N#6ֲYs\u0012̆\u0018*\u001fpF\u0015\u0016#>Y:ȳ5sRxUH]{5<YPm{eZSK^j/:\u000eurl\u001d#>5b\b|;\u000bKzl\u000f:h6:IsI\u0002S+Az\u001cA݋^\b:Bs\u0010\u000f%=1Xڀ^ä\tK\u001eE`F~\u001afņ\u0005UD(\u0007d5o0\u000f 񵿧\bٳziU\u001a\u0003PE\u0007}\b\u000fOX\u001c4$\u0015Tm\u0015lDXc`ē2#TC@d\u0017p)Hb\bkX?r|\u000b\u0003gq΄\u0004\u001d\u001a#>\u0016$_)@2\u0014\u0006 C>}H\u001fX\u001c3MDr}H+\\\u001dy#\u0002KC\u0006X1Al\u0018*Bq⭚4`Q\u0002W!b Pmp6k\u000b5r\u001f2\u0010D`e391FD\u0006\u0012\"63d0J<,\tcd\u00188W1\u0015H\u0002\u001b@\u0011=-4\u0019͔W$hn\b9L0<sJߓ\u0005ԒjBqz\u000eZ\u0014_$4O\u0018\u000e\u0011Q\"']d#>\u0004*K`\u0006UYY\u0019Y\u000e\u000fs\u000f!D#=IGbi<\u001a□6W\u00187@3!DQ#G\b;\tX@\u0018b%UCMAQS\u001dGD\u001c@ā#>\b[w@Ĭ#=\u0019,*e\u0014ˢG\u0018Σ\u0004A!\u0006:\u001a7+iUͪ*|\u0017s5-vV)kxf#= \u0006H\u0016`X݌Qkﻑ}\b{\u0019Ҕ%\u000bL0ݣq\u0011TVc%1T)\u0016\bTBX\fci\u001d\u0001{iэ\u000eC-m\u0014濾s\u0017\u000ba6K/a\f+xC\u0006PD\u0014A\\Xo6:ˣ2#=ѓPu\u0019\u0006MT@\u0016\u001bb2356\u0006\u0013\u0004y*jLOT\u0014\u001c[.9/TC\u0007#>|\u0010\u0001\u0002cL>8mu\u0013F\u0019;\"(\"\u00120dITl*\u0006!1#>0\u0011\u0007S\t'\u0006\b\bXQ̅#=\u0002d:\u000f%\bDD\u0001\u0010\u0003Q\u001f#P芣\u0004KOBO$pGJ=Ҡb \u0005\u001f.2\u001939$E<jLOPBbcآ[7\u001fȦtn\"d\f{uPٲ*P;M\u0015ʝW\u0016fJQ\u0015(IL1\b/g\t;qSg\u0018,a􄻥ǸD\u001a9gj Г0ݖ*Gl\u001dĮH\\A\t\u0014;˺|8\u0014F1tFJAPb\u0007\u001d4..Aj\b\u0017{G\u0015l`ͭ\u000bYFc3\u0013-\u0002\u000f*'&\u0011U3C\u0010֝\t3+dn,'\u0007dn\u001d58I\u001d\u000fIn\u000fdJ\u0015V\u001d\u0012UQɩM\u0003RK[D滕0\u0006zlR3J3l69G\u0012;+A\u00113\u0013f9ēe3*yz?\u000f2\u0018ս#>-B\u0013Am\bpEi\u0012#>:Tqb0\"]Lñ1e\\yme&/00l\u0006\u0014UjMw,Pݡ\u0011fbnmJ¾9X\u001aMZ{\u0019?>\\2\u001d\u0017fr5^{&\u0019d*[n\u001d\u001a\u0013\u0003g\u000e\u0004ytfSeZkLX?\u0010<jz;[\u00023fC8\u0006;\b\u0012`7\u00031n\u000e\u000bj\u001fSd\u0005xI\"\u0011B\u00052v#65D9ux#$G\u0014tc#'r#=d3T#\fa#=\u0002:Ew\u0013@A/*C\u0012hQ$\u0016\u0017t;\t1`x\u0010;dK(\u0010a'\u000eu\u0014Èf\u0003/lVDfb~m?\bdz\u0006#:KtZSjB\u00142\u0011۵y6t\u0013\f\u0003U4\u001b#6ht\u0005\u0011r\u0014uب\t\"Q'\u0017\u0015.s!\u0013\u001cߨf\u001bE\u0007q\u0011s$@]M$\u0019\u000bJ+\u0010\u0014Р5@̃Aϐ\u001d=\u0010@)FK\u0010;񰩃O6qq I'\u0004\u001d\u0019!\u0003c\\\u0019\u000f\u001c=\u001c`!>\u0015#6W\u0010\u000b2\u000b#6`}CMw|Bpd\u001c}\u0006@\fm~mi-\u0010&d#6 OU\u0018S$\u001a3=@.\u0014:8K2uf\\\u001c\u001bx<a\u0003Ek#>?\fB\u001clX]\u0014\u0002\u0001\u0003_bQq\u001aj}UcPg4E6Bo59rThc9Ej%8Nw8?MeL0\"\t[BN\u001e\u0019a\u000e]Mp|x!0\u0007핥VdRJբFXY6ȝo\u000e`)HXJXČ+D@\u0017\u001c/S\u000e?\u0006\u00107s\u0016ߖ3.e7:\u001ds\u000b\u0011\u00173\u001fYޤnPooxxřf\u0010b\bgvTN'2\u0010\u000ep\u0018blO\u000fËo\\r'ȣk\u001enJb\u0010kP+҄\u0018\\2~\u0014K:E8c5fWM˞9=lNq\u000eڙy\u0006u(t\\@\u0017\\A\u0002_|w\u001dي5[v\"\u001c\byI\\uu|7FN\u001du|\u001by]\tiƠ5]\u001ca|=ƫXj$HNܸv=&4lc}I\u0014U^#>^Z0\u0013}甥5\u0015G\u000fqpnT\"HX-F 'gXRE<\u0003㼼knǲ<+jpɊþ{=F'OFd\u001eV1ʊ\"\u0018ʛ]*_6}b8\u0018\u0011\u001e}aJ`fH積vf\u001bB\u0002P\u00182\t,Z#>窱_\u000ecfl \u0007;\u001c\u001cj襡\u001f]X*\u0007&$\u000eTC\"I{D.\u00064\u0013qf106%ڱ16KshIy\u0010\u001b\u000b6\u0019N&/>s~߈0ds0!$ҥs\\b\u0005%a驻#>\u00039ǜ\u0017NNwX\u001d>xƹ\u00058\u001cA-\u001d4\u0016\fQ15<\\\u001bժ%^ӘADUv\\W\u0012Q\u00159\u001a\u0017h[Y*\t)Cv\u0013J'2HV\u0011=qnCKH9֠s;F\fQ\u0014E\u001d\u00107TlP0#=B8%iY{Um\taMT\u0007\\b.zeM\\I)ih>\u0011\u001bZYFmD\u0002e#<|v6T89Q[\u0019L\u0014e̐5S>\u0004K|\u001a$JeJ5\u000fsI?KcK^s4:Ykp&[ן\u0018ce\u0012^A6J4m˔JXb]89ظTYi2\u001aJSFx#N\bxe\u0014E\u0015\"tVV\u000eU˙c\u000b\u0007k\\Jc-\u0006Yx\u0013:)ٷ.m\u0019V3Lb\u0012âpя)#>,&Lr0cSL4z2\u0016]fq7rm\u00024w\u00198Z!\f[GBwzc\u0019\u0007,\u0007l:m\u0002\u0007<3d4|{\u0010WEB&\f\u001f\u0004<#g2\u0001>\u000f\u0012\u0015ԡ+{-rͼ6\u000eQr̳j[XC\u001fҒ \u001aJRlS԰\u0018\u000bu/\u001f-k^\u001dԔl\u0014[#74iK\feM\u001bET)X\\<s\"O$a\"P$#=,9#6\u00050\t*f\u000e \u000eDBsL(؎g2;c\u00110<C#62\u0017#6%K\u0011>a#=z\u001elsup+I\u00021%\u0006\u000b\u00194\f4=)0bx(t\u00129R8R\u0014\t\u001abd#6{xN\bf\u0018g\u0017R{M\u0018,DfT\"T?iZVD)6pؕR\"R1\bH-:t;u#=8\u00155\u0001\u001b\u0015E&֩v]ݣmv]I-\u0010\u0001H\u0018s\u001cE-*qlQVVa#6m\u0010M\u0016 VcB`&̎H\u0006vK\u0015tj1\u001dͳS\u0010D.\t%\u0011D\u001d=G\t#=#=]$B\u0004\u001d\u001a2\u000eK`\u0019\u0001\u0004\u000bc\"D!RKM\u0015`՛$&]\u0004\t$\u0010\u0012K!pϻ\u0014WSQ+%X,kLՅ\u0001B\u0004(E~$4guN\u0007<*\b)\u0019%LaBGAX*\"f@X\u0007ib}ER\b\u0007PW3 \u0007,\u0003M\u0007)\tJ\u0010T5\u0003r\u001e\"3|9iR#>vI\u0012`\"#=\u0002#J\u00114\fRR4Q15I\"#=1}g\u001fJ\u0003QO0Hf\u0011Q\u0010d\u001e\u001a\f\u0019\u000700=E_4؟J)01:k\u0017@r\u0007j<ٰHy{1Ԑg\fg}PP\u0018Y0QS\u0003!Jx\u0018χ\u001d\u0012;'Qu\u0003n-LԆ{2c_~\tu\tau\u0007\u001b'[j\u0007auҙ&*Мa\u0001#6MF@񉓄L\u000e\u0007]5T=\u0004~NXYkD\u0012}/J!5\u000e#=b\u000eJYJR@m\u0019]'\u0018(j\btmބ\u0015\u0019*$x \t@p\u0003\u000f飆L \u00067d%\bNB(16ɷ#UT&2sx\t\u000b\u0011!I|i\u0001ID0NDź\u000eN-34jVHdA\u0001\u001d\u001aB4H\bpYBodLB\u001c/9\u0017:\u0019\f'i\\\u00103\u000ecy{ípxD\"\u0019#&?ݹ$רͅK\u0001\u0005ڽ(;z\u0014IȖz\u001d$S#=\u0005Gb#P`\u0006\b(sfdx\b\u001a@\u001b\u0013-XS1Vƌ@\t\t\u00013[̆術^\u001c\u0005\u0014+e{\u001b\u001fҭpÒ\u0012w\u0001%\u000e\u0010#>\u0001c\u0014kFPւSZ46I8@=ؖW\u0006D\u0003\u0006<[\u0012M4\t֬\u0003C\u0010\u0005JULjp1\u0017\u0015FDdRH\u0012\b'螟W\u001e>kX1q{7Kb\u000fg\u001e?~%\u001aBG\\\u0004!yf\u001dHi\u0019\u001esv\u0012\fGw$\u0007iʇ#>P#6n5\u000f>Y\u000btmvxw3o;J5\u00111;-J\"O6B%\u0002c\u001b3'\u001a\bXw\u001c8M(R\u0006k\u00130xp2x0\u0018MKd/ju{׳r&nOz^.oj^\u000fk^s'-\u0001Af\u0003bL\u0010gQK9K7iv^]q+\u000b{u~KKbɹ0\u0003\u0003\f_\u001bF\u0014\u0018\u0019ÖOLx!$\u000bap\u00014D\u0005\u0005[[+Zhur{k&8Fc\u0019v\t'\u0006\u0006{51\u0012d\u0014j\u001b\\bkVtՠ.ن3I\u00110\u0003H\u0006HӪ1͎yJ\\\t\u001cNYhTP0[޽VUmLܪvӻP]zR9=Ճl\u0016#\"pJ0\u0019$vX(KJ;wi\u0018Z\fab05@rYi\u00124Ihȱb\\L+6\u0015\u001cލ\u0010ƑpFƤtnqq\bj67qLE(S\u001a\u000e%%u\u0018c,]̋\tvEEMzTkgyv'mS\u0012m%0o%{\"/>ukګ,+I #6(~w\u0001O9\u0010\u001anb\u0018DY$\u001cG\u000b&\u0001#=S#6M#=\u0016(Iudм=Փj:q`5SQ\u0014>\u001a0'\u0011{xU4AF\u0001`1F\u001a\u001a\u0011nYltYjkjSPkvE\u0014\u001a̚D53\u0010#\u0004\u0004K!5$LCT8)\u0016I\u0016\bi\u0007pdı+Ō,%\u0004zf&`b\u0018+-#6R\"U\bg.\u0014\u001d }\" 'H\u0007\u0001\u0002\u000f#6\b!r\u0011#6.IdYif46ɱbң[\u0016,ie*#6+,0,JI\u0013E)n#>)\u0012+,5IY4\u0013J\u0005,\f(4fYM\t6L\u001aEI1f2PT\u0011R֓e6-RF4\f(#=\u0016E\u0014Te\fR\u0013M)̤hPjH֔CY*6h\u0016PdBT̤S$&mF5D&5)ZLfڬV)-\u0016-\u0010B\u0012(@\u001cTE(6\u001aJ[fD\u0014\u0010\"\u0002\fJ\u0012\u001a\u0002\u0002)\u0007L\bҢu1)T\u0017JZE#>$4\u0002K\t\u0010\u001f:,A\u0004\u0014ؗS\u0015#=.@g\u0013\t[idԞF#>5\u0013E>o\t#6x\u0004'61C\u001cVpP\u00182v^\u000bOBx\u001e#6&4\u001d=\u0006('~(iy4,|gM\f>z(\u00034 ~\u000eP$\u000fe>+tp\u000fG\bM\u0014\u0003A;\u0007bs:û|SfGdT\u001e\u0015\u000fzn.a0\fC$NV_X1t)F<\\\bJ:m\tPk!J\"t\u0007\u0012vȃ*\u001b\u001f\u0006\u0001̐J#66/)#YZXe~\u0017P9\u0012\u001dPHS)1\u0002H\u0006֦I\u0019\u001d!\u001a`4)\bk\u0013U!󾿶8hӮ83ˆ\tZ \t\u00013\u000e@\u0007\u0003t#='jP\u0003\b$w$\u0010}(#6'\u0004=xs'_à\u0002vmeH[\u000e^#=:pQ5T`\u0003\bNlty@:\fO\u0006\u001090k\u001d:\u001em@u'p.\u0005M\u0016&\u0007X\u0010$\u000f>\u001bic&zԩyT\u0015|i\u0004.Jq̝!e,P\u001ftSqődG)I9B>fw\u000fl.=9OSd30fϷ#6|4i3]\u001b\u00197+C3\u001b \u001c$~\u0017\u0007\u0007\u0019\u0019#6S$)JU\fSE'\u000f\t2ܠ\b=!M@\u001f\u000f)\u0017\u0014IwD *\"'RϺiec\u0005tfBf\u000eHLj@A;:\u0011\fd\u0014\"\u001c\u0010a\u0014[Qt#=( >#6B5bYUT)\u0014\u0004 z#6\b\u0005lTj5T_-_j]!2H@A#=\b\u00050\u0007i\u0013$%\bP%#6 \"oV(}}+@Xfh\u001cρl٦\u0017Dr<Xj\"^\fÞ\u0003ڭ56QV#=.nyKpL\u001a\u0002V#|\f\u0018ɓ\u000fFՆ\"Ѕ\u001b\u0006P<\u0003MpmGҚÍ&4;N1d\u001cweo[D/I?o#6/t\u000eL\u0017ܾ?/f.\u0002w^1\fT= yª$\u0013\u001dD\u000f0#6\u0012\u0010\u0016K2[#6\u0010vNOٰ\u001a~f@7\u0017\u0018`\u001fS~\b}_\u0003\bx\u0018\u0005zP!D\u0016\t\t\u0014\u000f(1&@f=[\u0003Jnv\u00120KE\u0001HMg\u0011:YO猹B\tH\u0014~x\u0007?Ζ6`a\u0012D\u0012d8Y\\\t,7la\u0003pjd*C2\\#=V[\u0014cF4L\u0016a$7&)&ή.W#=\u001bH+Y\f8̇ycFX\u0003J\u0014}ne-)3jHA#C\u0016@:E\u0002wTSkEyz{Wkȁ(;R\u0004snM\u001c#=ܽ\t/Jk\u001a )jfB\u001c\u0018cD;f*\u001a%]>`\u0005\u0010~q\u000f\u0017\bX\\\u001dpx<E*1\u0013՛\u001a#69!\u0018jT#>$7\u0006$DW5\u0011.w\u0011xa:'3`\u001df1\t%#6}'\u001fr\u0002$\u0012\u001c\u000e\u000e\u0005\tWSjTLw\u001aY\u0019#!Ck=\u0019ׯ!=҇\u0012JX#68\u0007#An=f3m\u0018=/w!8C\u0010hu~D\u000f:\u000erwM\f䋐\u0007j J\u001c!\u0001rY\u0018*Jm+.mY$0\u0005T̄#6w\u0005\u001c\u0006`\u000epZOnBB@QC \u0018,\u0018Tpo\u001f\u00198o#>ݦAf\u00177TW\u0002\u001f\u0006:<\"F7=nxZV(o8yb\u0010C65\u00068`\f7oj\u0011'\u000b?;.\u001c\u000eHX\u0004L+y`>\u0019v\u000f#=\t@mW|\u000fQ\fhnȅ@=\u0007f$a\u000ft*LXn\u0014Y#6HRdF&G\u0014|\u0014\u001f7Ka:\u000e\u000bntSIIM\u0005([I4&Y.seb\u0006%<nov\u001bIǔ&\u0006\u000eN\u0014\u001bd4j\"\u0013&I\u0017M\u0013\u001d9ŕf#6\u0006Vuf.#puNR\u0006J\u001a\u001dE\u0005#>\u0004ىV&F\u000bm\u0017YNXu@9ȆC!\u001dPݘ\u0019#6\u0004\u0014#=\u0014#=\u001a룾]\u00128rRnqj\u001esƍ@\u0002xBa'9<$\u001eR8Q!\u0013<K\u0007\u0007\u0018h@݅d'XLoGVf\u0003,/#=wSRf[LF\u0010f*Aw\u001dz$\u0004I\u0014a\u0013\u001d%!4U(5#=#=j\tT:܁F2\\0#>\u0011Y4m;\u000e!\u0014\u0007n.)\u0003\u001e\u001e\bԧh:I1N#=\u0017l2hoY)31EPhFө\u0016=\u0003'Ts\"\u0016Y;aS1xMqw\u000f\u0005D/2M؟Hs/,B]ͼ(e\u001aS@$AQFy}_]9\u001dEڌZ6YFThF2\u0003೷*\u0014F,xje27P:P\u001f\u0013c4nĀ2\t#6Hpd\u0003/9X`\u00152EbYT\u0004X\u000eb5ЍGF&\b#\u0012\u001dhɮ\u0016-\u001d*.4=\u0010oT\u0016tr\"H\u0019lF{uhS\u000ee\u0010\u0005D\u0010#6<\u000b뵦6\u0017z\u0013\u001aId3\u0004\u001b.\u0010vh\u001f>\u001aT;\tZ\u001cT.UGe\u0012EӤXBh#=L^|4\u000bԽC,/T#=Cw1*#6.\u0006$έo3\u0016Vݤ#=`|V\u001etjHvUb*M0{\u0010w`\" O#_\u0018g%\u0002gک\u0014\u000eLj\u0013Uǧw#6\u0013\u001e=\u0007`wok뺒!\u00191\u0018k#=r;]QW5Ѷ\u000eV\"l_\u0015\u0003>\\oFΧPρLf$oKI1!=g*4?ύl\u0017-Kr8<F%҆/0B#=<=;A\u00182L\u000b\u0011<f\u001c0F\b\u0005\u001cW\u00132!ǻ\u000fOpLAJ\u0014\u001er94T#=#=\u0014R|T\u0001\u000ftJy\u0004\u00125K0T^\fJ<a\"\u0003ݎR6LU\u0019zXP\u001a[g\u0004e5M\u0015Կjc2<xp\u001d!N\u0005QW#>fT\u0015Fs\"|!)dRFy\u0018D;1$\"b\\(\u0005E5E\u0002\u0001̅$!#6սغ[\fe`J2L\u00173\"\f*J#=2\u0007\u0010hgiˤe8Dl\u00150W\u001f#˧4\t\u001e@u0vǑj8\u000eB\u001aT~e\u00052bcϧ?/\u0014\u0007GhP\u0013D#>Q\u00028\u000bKbC#='`\u001aR\u001cά\u00018i!ސRRl\u0014άۛRSI3e2Ӛ\u000e\u0015LS\u001e!e!\u0007\u0016'*\u001bJ\u000eR,?/@?HW\u0017 \u001dC%eIB8#\u000f>#>>89\u001flh;\u0014I\u0016\u0004KF?88A3~\u000e\u001dM#>e&\u0015}:\u001ap'fT=\u001eH\u0007׶E\u0018&\u001d:\u0016+{\u001a:4$QP(іu\u001a\u0018553\u000b\u0010I8\u0005(HÃ\f̘\u0011R,?'\u001e\u0015Pj\u0013`+b \u0010D9w΋;*rJ_ΤK7Ӕ5ϋj{rGŎ4\u0019f\"Z\u000eq= mXOz]wy^mnH\u0016\u00136{ڽkӒj04(\u0004b\u00073\u0010w3\u0016+U|O.su餛\u001d>< 1EQ5\u0019B#6՘hшJk\u000by\u0003coD?Y'\u0004N\u00078C\u0006{~\u001fǏr=#6_b\u0002LP7D6yWxӏ^\u0002>\u001a2Ȳ\u0007\u0014ealV\u0011X\u0014X̴,X3#=*!vˠC6Qӟk#6ml;\u0005 #=\u0010#>S\u001c$\b\tkg\u0006\u000f˝~lr[u\u0015˻\tdE݁\u001a;15\u001d^^B̆lV~ܲ]b\u0013wr\u001a\u001ftNd9#=F6i\u0014]\u00139l4fs;H\u0013\bj̠vɱ@s@:_\u0006\u0011#=K\u001bUXڿ\u0001iHld3mmtMSfJ9\\\u000efw]4mE[W\u0015Xb\u0005p#r\u000e'r\f\u0005 \u000fX\u000e\u000e<YQpB#6V\"7Q^\u0003I\u0010[pmLi(\u0014<$\fS;0cA$팼n\u001b8#6##={#>\tp\u001d)\u0002eU\u001a\u001a*#=@v{/v\u0003̻\u00190\u00075HY\u0007Z*=Hp)~F}}}ò~}\u0005]E]\u000f\u0006\t&Q:HL<\u0004LS\u0014{\u000bF%!#>\u0001<\\2#6rKN\"\u0006版\u001c>\u0001(\u0018\u0018\"\u0001\bz ]\u0006%93\u0011;wC3LT%&uCinWR\u0016)DB\u0007\bFr\u001e\u001bhwq6\\4I6ƨմ[DVƂ]mIhjC[\u0016ERrŮsTTlTF6Ѫbѭ;]*i\u001a*쵸mȺUF;WsM+ṶmZƭrQѝulh\u0011\u0015`Mkܶ(K\u001a]i5mEjM\t樍Z4s\u00165mƍW6*$*VI5\u001a#=F]7wX܋rJhi';k; \u0010#6ף^^辣\u000eױ@2qZ\u0018C\u000b|\u0001\u000b+\u0012\u000e\f%k\\&|#\u001e QI4$6ŴU[+lc\u0018Cܕ4\u0010@'\u000ebs.\u000e\u0011\u0003r]juV}LSsޟhlL\u000fo-\u0018\u0005i`BD\u001c/\u0007zP}+\u0013p5r\tM3h\"5Ȯ\tQf8n\fM)\u0001$x8bbFcci\u0002R\u0001!e\bd51\b*J42lh#=)2tr>:\u0007Z;i;;\u000f\u0017D\u0007`}Ƞ\u0018:њNwu\u0016~M\u0014R\u0018\u001b\u0018ɬD6\fу\u0012\u001aX2IJF)i#=\u0012!j7Ӝ:p\u001a@\u001d\u0001\u0011u$?AY\u000en\u001a#6\u0014u\u0003\u0001$\u0004\u0004VNb88@`\u00066p0\u000fv54\u0004ق&C#6\u0011A{Mz=ů\u001dX 杠\u0018\"\u0011>\u0006\u001b\b\u0014\u0003ā\u001c%G{\u000b _\fG\u001eH\u0017#=xOa \u0001Dj6Kx9$Nĉޫ-W\u000eds#>\tT\u0012\u00102\u000f?}\tr:\u0013ب)G=\u0017X`\u0004PH\u000ey\u00106AOG1x\u0002\bh'F0\u0012Zr#=s\u0003,7۶NA\u0001°,H=NR{Q\u0001\b/N\u001b\u001c\u0010vo(\t5DpiG~`=p)\u0010\u000f\u0007\u0014fx=Sӭ\u0018\u001b!\u001eCr\u0005\u001fWg'Q4\u0010pDQ49\u0018q!\u000f>\u0011#6T\u0002Wb\u0001\u0011|apV\u0016ZPC# LU\f%P#6GI\u0011\\$\u0003\b\fa;@a\u0002uڵoU\u0015bhCAp#lLLpIl>\u0006m']\u0017\u0003Q699yO\u0001\u0006vVA\u001b$5:C7*_\u0002P\u0017=8Ch\u001a2<_r\u001dLڛX%\u0015P=N?{\u0017?f&ױ]\u0007_j\u0007\u001e\u0007jB\u0018pi#6\u001e0\u0012\b\u0016wO\b}G\u0006QHy\u001c?#=\u0015/]D7}\u001eOG&\u001eK\u0015\"\u000e#6#61~\u0005\u0016f#>Ci\u000309s{\u001e\u001b3%?\u001e88'p\u0014:';TTc\u0018嗬A|F\u0011Il\u0006?i۷\u0018u tRxM\u000bAO6i_\u0006#/c_V9!D8C#>R}ԟ\u000e\f&ք0\u000enA\u0002D\u0019[\f#=/l;\u0017?/v#=瞤<,G\u0018}dapWOˉ@`Qo4\u0001 16F{GQ\tHb{4.X! 5V)*ED}iEd&\u0014(A/w)qC\u000bQM\u0016>iIwN\u0003\u001a_=\t*q}b\u001a\u0003\u001dn4޵czm\u001cMZ$Q(9F#=N(Q\"+\u0001pUS\u000ba0B_\u0015364]dnV#>F*w\u001bj*Kb[^E\\op%Z\tGXB\u0006-\u000f\u001a\u0016kvcB#=̅\u0019\u001f\u0014JDEdg\u0012\tS5J\u0011ۙ\u0011(t# 5#\u0010\u0006)\u0015M1Fme3#=[T+\u0018O\b\u0017\u001coDb\u0003r#C\u0002؛\u0005\u0004VDD2\u0018FL!w:\u0019\u000ev\bG\u0014QX\u0006#>q\u000f\"٨V68ԍ@\b|\u000e3n'#=EaUK\u0006\u0005xa7;)%5(jK(j\u0012((\"qx\u001d\u0003p햺NC}0Z\u001dG\u0012\u0007927ضcd*Jז*ZRRe3m[M(ڌ0\\x`m4i4I)\u001b+\u0004>\bW\u0006\u0016NޱEa#>Y\u0017e.z\u0013Q\u0015\u0012eCMnLIcd\u00067nu\u0001PX5#=!H\u0011#>4\f-4\u0016(\u0019\u0011sF\u000eU&!\u0018eD\u0014Q#=R\u0010\\VN*-FލD\u001b)Y!#M'\u000eCL7\u0013q*#\u0003`p m#,KAMD\u000b%\u0006ߞdakqWD\u0007)#65:r1CTMkQ#=`\bbֳ_@at_]\\\blJ(\u000ek!m\u0017a_\u001e%.Q'bp\fK\f1#*\u000fpdJ\u0012YV+iDˈH\u0012\f\f-ːi.LR\u0010~2&t#=\u0014\u001fȐ;^d?w\u0018\u001fC+\u00155\u0019\u0007AU'pA\u0007\u001f>IR=\fPI,a\u0010B\u0012\u0006\u000f\u001ftJB#>D#=\u0003L\b@\u00120R\u0005\u0007\u001a\u0014T[V7$nt]\bb\u0013NYFbD*qIr*ldC[$]esjrޛ&\u00064Ir@ѬS[Q*\u0018R\" a!6dҒ}c,T\bF\u0002.E`R#><ܶ5Dl\\66܅2TLr\u0005I!\u0012R\u0002@JOm4/n䢀#>D\u0017\b#>Ci48\fy\u001fa(D\u00111\"\u0012\"\u0007\u0010\u00176*\u0018\b\u0016؇\u001e\u000b3щ?^s\u0003kP*\u001d\u000b&`d;}\u0018'^Zv(#\\v\u001f(oI<u#\f\f#=U N(\u0006I\u0010Ѐ\u0011\u00064/͡c\u0003#\\q>w<Aq\u001d9dL\u0016b\u000b\u0011\tQ\u0006\u001b3\u00193oK#6=\u0004\u0007g_UeaQ|DQY4V0i_)\u001f(O ~0=w=d\u0013QZߩ?\u001bPI\u001c瀀jJ\u0001U\u0003`*#>Qy\u000b\"-;\u001fިx`5Q\u0004hFr)\b>p\u0006Jk\u001eTD$\u001dO(\u0004?=;a\u001e\u0003\u0013c4\u001c|ą}\u0017#6\u0012!#=ɴR\f`\u0002h<3k{@ߋqCrQSg`\u0015\u000b\u0004\",\u0012&l:_L۰sÚ٣QlKk\\\u000b*ZR\u0012q\t\f\u001c\u0002KC⨡UWvF9#6\t$t\"\u0002\u0010#=!\u001a&\u000f\u001cF:[n\u0013\u0010|~\u001bmͫK)rWaXĂ\u0018z\u001f=\u0016\t&Y-H\u0016NbK#>M;\u000e\\&LDUUFG@~?\u000eO͸(t\u0012\u0019j\f\",\"9\u0006*w_qS\u001f0攞\u0013#6p1?\u00187BU\u001f|HU\u0004KU\u001f\u000e\u0019\u001cO[0ɒH0(\u000fqgqdZo7H32\u0018LTҔƋc\"~ߠИ$$#6\u0012O怠RWLIE5/߸EUQ\u0014E&a\b* `,p\u000e^[})#r_K|\u001c?\f\\VD\u001dr\u0018#=\u001dcS\t_\u001c.Tʐ\u0014ÎI$><\ty#6\u001e\u001c\u001f\u001f_,Ux|Quo|m\u001dg1H]:I&G?u.x_\u001a<,pX1\u001a\u001eNA7p=H7멾3.R_eJJCr'~\u0018\u001e6ޑ3umTvS\u0019LGNlg\u0004uOzOI[3\u0004\u000eTn\u001a1`\u001d\f\b\u001f96r+\f}N\u0003Av#=CAZų7\u001d$x\u0016_4\u00041T\u0012qԖ3\u0006\u001fvg-/XS#>B R靻i\u0007\u000e]Y&\u001d-\u00113=n7m]f\u00025\\*7~\u001at\t0O\u0012\u0010!fۼ4o\u0012Z;e\u0018x\u0011_\u0004B\u001de,5dC\\y,-\u0018zK8}rg{ LtQ\u001cBD!S\u0011\u0019+q\\\u0019k\u0015Yȩ\u001bz1A\u0010\u000e\u000e\u0016㒉F1uqX*yqLXk0\u0011۽oXb\u0013P\u0011WD,b\u0018|pa@e`D;bobH;Fhy\u0004f\u0011\u00117`\u0002o\u0005\"wltv+핰Յ\u0014Ӌ).\u0005\u0001\u001e H3\u0005\b>Ep1>|+?\u0015\u0014ª/)\u000f\t\t*8ʊ#\t\u0014\u0018\u0001#=O:\u0007C}e\u0015鹌{P=$+ea#6}(\u0007R\u001f}\u0013*\u0006bMR!q#\"̹\u0014Ͱ#p\u0003\u001c\u000f\u001e}#><9V2.#=Q\u0006Jr`v\u000b?.\u0003EGf$(\u001c\u001c\u000bAzPb\u001a\b4Kdn7S5#T#=%\b5ǳ4LD2P5\u0018\u0010k}?,MnRI9\u001b\\\"CBT(\u0017\"`\u00148Ko\u001c\fzqھ\u0015!\u001cP>ǌ#6\u000bY@\"#6\u0019ߔ><i::!G\u001fwe\u0013k\u0007 \u001d`c\u0017.%!TT&#6e%*9|if\u00101;UD#=!\u00180<\u001c\u000f\\d0\u001aC7x>Vd׳\u0018c2\u0007\u0001\u000fobZSAOD;q\u0006\u000f\f#Wa\u0001\t\t510c\u0014l\u0016P#=6cJB!+\u0012*$\u0019\u0012(IIUE\u0018\u00194m-\u001b*&1FfcmFĔ\u0005Z5\u0018ѱ\u0014bb4a(Vٵ&QQ1FҸ,Y-+5f#vbVVZ$\u0014fjɑ3f5Ff\u0011AkR\u0013CB\t\u0012\u00102B2iyq%\"E!eKBe\u0005C&b5\f4\u0016Hڢ0\u0016RVI(\u00164QKe4\u001bZ\tAU-\u0004\u0001$\u001c^\u0013\u0010|\u0006\u0019aF!C1DeJQ\f~;fx\u001c\u001c\\\u0010Nb\u000fQG#>#=\"fl,#=0\u0003%\u001aUn*UQ[Q-| tP\u0015\u0012ʇD\u000e9E(\u0011\b\u0010X\u0007C#>R\u0004!\u000b#6^o\u000f\u0015\u001d\u0015FͮZhD\u001f\u0003\b)b|Ý\u0012Q-Dm#>.\u000bsrK(SCGrvLfD65JD\u001byshUSvkJhƥHȔɟ:ZFg!\b\u001aB8\u001dK\u001dZa\u0012i#>ލeM@\u001e\\슛M\u0016}Wz噯;{\"ᓈ?N\u0016}L\\&\u0012#\u00195\b!Q&#>u6769C\t\t*\u0007)$\u001d\u001f\u0013\u0010~=\u0015/9'.0R!)@_\"|W+>7}(Yzt\u0017쿏ƬL63h&eb,ci(\u0005hUIbU¯M^j29.`d\u0006M\u0012&\u0018)'@׾O\u0010D6k?(P\u0006\u0004A;Ha`\u0011/\u001e\u0013ԣ\u0015\u001234#6sYt_uݙ8BP\tarU-Fd2\u0017\t\u0014?;ORI\u0018\u00109'8\u000fC02MP\u0006\u0006r\u00115mVKsԁ8\u0016|4:i!Y\f͊;\u0003*$eAh$Л\u0013\u000f\fK\u001210[\u0007@;\u0006\u000e\u0002G9\u001b@t\u001e'g{syɒd!\u0012w`\u001b#6{\u0001TI^\u000f#mvS\u0004J\u0013\u000e\u0003xnT|;\u0001\u0017zܟ$A\u000f\u0012M_\u0014\u000f_\u0013\u001a.A\u0004`9n\\דtH8\u0013!_w<|r7>\u001e\u0014\u001b1\u0013}M8\u0012(R1&\f\b\u000b\u0001\fsfMȹ=3oR !(\u0015\bccu9\u0010\u001bA4D[T9H\u0018\fQG%\u0001J!J?\u0007勉\u0014nT#>\u0010D5\u0012 \f`hb#6O8a\u0007{0C#>6mR`Zٓ #7Jau\u0007\u001f?_\u0012sp\u0018\u0018U\u0005\tT8?\u0018\u0015s\u001c0\u0019gnx8.o$\u001b(~?\u0002&\u000e~R~?#=\u0012#\b%Z?++5`\u0001k\u0013#=4\u001f\u001cͫZa@E/zG\u0007f, DML1+`\u0010\t\u0014\u0003Phm\u0006 P֖V~!\u0010Jj-3*0 05\tO婞TxF8qU;l\u0010\u0007$1h\u0019%yR'4\u000b,z`׉x\u0015}\u001fN$N<\u001a\u0002H0m\u001a7q\fo4JVZXT(יI7!ǻW>\\\u001e1G\u0019x+i!7FD0þ5̇u<\u0011h,\u0019\"rOuj0d\u001e04T,5fA\u001a#11R@h_c4TFrX8xbCݕ=ur6]WU\u0016Ppf\u0002#>cURx2z3\u001ck\u001aWZ`2:Ma`$Tqos\u001965a\u0007vIxFh IdK\u0012j\u0004aXHL*!\u0006#m4GW\u0017֌bӢJFLAN6woR}9q n7\u0001M\u0010z\u001b\u0004IL(1&ZYb=۶\u0018ktPɎ\u000e6\t-\u001dt\u0018\u0005==@\u001dj4Є\t\\U=j54#>Qy[vG&pƀO\u000btl\u0010^\u0002֣L$vEB#>BB46H0V{:\u0018\u001bD\u0012H$K58\u0011D\u00109%wgvۭK.I8Oo\u001d\f\fE#$\fh %\b104f!\u0004&2@$\b \u0006`b\u0010\f`m̂mFD!9\u001f?b\u000fE\u00110Pthś\u0014IJ\u001awknlZskO!;\u001e\u00170$(P;\u001dP\b;P\u001f\u001e\u0011l(9M&=G\u0003'Kt@\u001c`\u000evSKE3LBM.\u001c\u000f\u001eD\fI\u0010'#+S$rji\u001d`\u0001?\u001b|U|]\u001e?i\u0003#>p=YcOG\u000bԪ\u0015\u001egE#\u0013#>\tw>b\u0006'N\tz衁x04HF!MSFEj\u001311;*&\u0018;B(\u001bbC\tw*jw^_\u0007|`5}4%67\"Co!{zn<\f>E[%.\\66+f$J\u0005\u0014\u0003\bO`a\u0013\u0001.Kd!5#64\u00146\fs\u001d{\u001f\u0006\u0012ab^]Цz-{3&=t$5_. *EZ'\u0019\u0017Ѐ]\u001fG}VRO?V1\u0013̱7\u001b?#Wi\flr5\t`c\tNL\u0017Ĝ4fM8bck1ՖNhko\u0006\u001b\u001fF\u001a\u0019j-QR\b\u0019wQh\u001bq\u0019XH\u0013Pqxʐ\u0005s\u001d0c\u00187HՃ\u00168\u001dt\u0014(<\u0003aCz*c-ʹ\u0019jA9htOO#\\\u0015LI(=7yk\u0011AԚi\u001eI\\2\u001d#=!\u0010b\u001eE\u000e=P\u0013#> 9 cbQPԌz4ۉq)M#=Y\"3#\u001d)I\b\"9,#6g\u0015\u0010|DNK\u001ci:\u001d\u001f:>_rr6\u0003LK}/\u001c\u0006pE\u001fѵEU-d\t)\u0019j\u001be֔\u0019\bWM\u0006(p\u001aM#=#RҰo\b\u001br\u001c:\u001amdq|\u0010tq,É%̊'\u0013=\u001eQx~#>kb\u0017[\bidLFLFJu\u00063w6Ý\u0014ķ#=@cTǩ3gn\u000e\u0015\u0004s-L`\u0006U&h:#>;KIF9E\u0017pkڙbK\u0013(u\u000f&Бcm,\u001aASUj~H/\u001a(\u0001\f\u000bV\f\u0003UW*ddlH{O?O\f\fJƘv \f+\u0005\u001a\u0002\u0001\u001a\u0012\u0019^H-jҮ%A1\u0002t&\bI\u001d%\u0012\b0=\f\u0019\u0010,0\f\u0003,p;\u001d\u0007ȇo\u0005~@}`⏂/\u001e\u0014#>Q\"@\u0013Nϕ_wypoL7z}\u00013\u0002P79ѐC#=Y9\u0013\u000f!,\u0002ǆPz'R5\u0014)@\"#6(\u001a\u0004\u0006a\u0006%)A8\u001f\u0003}x^OHNHz\u0007U#=:0\u000bv\u0011`\u0013\u0017-G^56wgk]D#>fA\u000eA\u000b\u0012\f#\u0004#@A\u000b\u0004\t\u0003r\b'#=jTRNfPHkYG!\u001e_#>!ˡ>J\u0005Đ0N=\u0016o}\u0013d~\bt\u0010F}s'N\u001dπMôE4XDBA5\u00128\u00016JS\u0006\u0011\b\b\u0013\u0006@\u0006@'\u0012\u0004\u0003{Zex$#m3%p*h֏+WP-$mIojۥ-\u0014@\u0001%%VJAH#6rHS \fBQ@\u0014\bJMVKQU\u0016|Ii\u00112\u0006\b\u0003\u001b\u0002\u00069\u0001\u0006e\b[\u0007آY\u0004͔_Ei@\u001ap\u0004x\u001f\u001db,{v}z83\u001bɀY17dxBc\u0015\u00066m\u0011$\u0010&7[(\u0010C\u0019+#n:C*\u001dK\u0004$\u001c>\u0019QHf\u001f@b#>:Vr\u001b]5\u0018q\u000bM*\u001c\u0017cs=\u001e*\u001bQiqB8e#=RQ{벭ApZ!q\u001f3L0tE\buɹEQf(pᷖ܈hb\u001a}yN\u0001D5ձ)u \u0019`J&`ӊT\bf\u00175D\u0014FU\u00141\f@PTل!9exR\"(\f8\u001c+9%uv!imY\u000b`qhl\u0019\t\u0004cF\u0014\fuLjA V KAdh`FG\\1\u001aUaHcW 6,f]<p^m\u00156|^0o+j\u0011Wbpcbfqru#6#۬\u001bdl#\u0013\u0013\u0006w1\u0010\u001b#\u0010V\u001b\u001a\byEj#=\u001b2c K\u0018R?\"\u0016pQZR\u0001BS\u001ezEL\u0006X@\u0012\u0013T\u001dۙ\u001en p!vqk3$Rf#6}*C(\f\u0001槢?/\u0006#6OQ\u00121-冢*^;׹i#:vQ]a\u0003 ~~^_%\u0013MNe\fQ\u0016\u0005\t`i?4i7#6P'$L9/O@Cy\u0010i}`^}2\u000fY\u001aZ\u0012$^.J F#=ʆ1$#>@DWP#>\bґ+̺w`\u001bknl1nZ%}*<\u001aY7ݷIb(Qt˻N\\֮dw;}uռ5wv{(X31J@0r\u000b1\f9G\u001a)6#64w\u001f\u0013_\u0014\u0017\u0004\u0013dd&\u001e\u0019I&\"\u0004\u000e>\u0011]łS윫\u000e\u000fr~(@\u0003I0\u0017Ť#6!\u0006ByNb\u0017\u0004\t۷k3b0;D\u0014\u0018:oH#;h9\u000e[C/\bG.\u001dmrc\u000f$\u001e+Mc2Z\u00049ݷQy\u001fN7ZG -P4Oo&i49erǩե\f.ؚ\u00043\u0019x̕.\b\u000eo O\u0013\u0010\u00052\u0010G8ƽr\u0001TIN\u0003@\b\u0017!tOQ6#6g\u001fy\u0001;ixñcx\u0001ᏟdS^À\u0017&\u001a\u0003\u0016\t2,i;ף\u0017\f8U.Va5~r\u001el|/%I\u000b\u0002-4\u0013\u0004c\u0007~2o\\!\u0002\u0011\u0006\bU7;\u001e\u0018\u0019L<FS\u000eg3WT'| +Ġx\"]ءi#6s\u0016\u0001i#>kn\u0002G|\u001fH\u0012\u001d\u0013\u0010݇Ӡ*J=\u0019tP\u0018z~&%:4\u001a>\u000783tC0!\u001dC #6UUKK\u00164\u0015tZC{`#=\u0006\u001faĽCm˝U!\u0010恊TYCmp`n8\u0011q?7ey?\u0015i\u0011\u0003\u0006~PeDF|ZNӿhtf\u001dple0놦ɗ劇&C\u0012Nz\u0019%kl9ǂA\u0001\u001b*Ax\u000fB\fʴ\"un\u0018M\u001b\bƧr;\u001d_\bO{\u001cڵCƷ5ip\u001fLpb\u0014\u0012`\u001d#>!\u0003?\u001e$@\b\u0006#>\u0019-\u0011\u0001%\u000b\u0003HЕI\u0013@U63[{Ji\f\u001f\u0003\"J\u0003A\u0006\u0001\u001e\u0001\"ՎC#\u0007\u0013: \u00189\u0001E<B'`nv\u0018H}R 6?b\u001b#\u000fv`^]c1O/š\u0014zuzm\u001a^r-i^듹&&G\tvMdڍZ\u001bK\u0018U4MSD\u0014l#=0#>\u0001\u0011hSL\"hS\u0013\"⡹C#=6\u0018\u0001؎5AƎ9ϧr:#>\u0012#\t\u000eyFw<Ϝ[LC\u000fI>%\u0010p=~\u001e\u0003dDVx+c\u0007T.DLh~DS!?o<h\u0006cw8w\u0016 2?\"\u0012#6P=1g!\u0003ɡdg\u0014\u001eL\u0010INs\u0010@]by#=\u0011DTk7V\u001bT\u001c\u00140\u0017\u0007ш\u001b,i%(<$A<\u001dyr>\f}װڹf\u0007\u0003\u0007j\u001erv\u0011S\u001e>h\u001c@-)[S\u0016L\u0018@I\tmPT@\u0014(\u00128\u000fy\u000b\u0002sR{(>}s\u0016%A;AEV\u0015DRH#>7Eau.yJ\bݛ֑%\u0002baaC Hf\u000eB.88&\u0012\u0018ѡt\u0003`Y\"bI,D+@#>#6\u00144\u001c^JL\u0016Z\u0016\u0005\u0002HR\u0012-*4#>Cba\u0010,{\u0007#>\u001f/n|7ӯ)\u000e;KG4\u001b\fEφjFQt\u001f3#>a\u001d @\"z\\\u0012;=\u000bcl]_2\u0004bԜO^g^x\u0006W\u001aժT*3g`\\-mn\u000bwMS\u0001Z\u0014iFJM&\u0014U$\u0017#=m3\u0015=4#6\u0014!DH'#=\t!ngOZ(xK\u0012{ t\f)<~}\u0005z\u000f@v{\u001e~_<ٙO8C()I\b g(P\u001c#6%S\u0001t_S>＜~\u0013\u001eԻf7Z?\t^476MΆj)i3\u001d:fوl%\u0014nw\bK#>10~71É+8up`Øiբ̅+p;r铞!יј$vTI(\u0019UvZ?\u000eG\u000e:&R?.$v#>\u000b\u001f,\u0012HQ##>XU>ؽ>*C6\u001a\u0016Ze\u0019(b\u0006Ew\u0003`\f\u001c\u001b-\u0010Qf9\u0016`d&n3Xp+\u00062\u0011\u0004Z\"#=X\u0007#61SF8\u0003B\th7\u0006E\u0010-f\u0005\u001c\u001bsRf`G!#\u0019Ř\t!\u0006\"c6*5{v隣mXDSVgPe3Y:z7\u0013dr\u0014#>-\u0015DD#>\u001ae\"\u0003*>004sc\u0006J\u001cKk55{ܪMha5)\u0013W=.O\u0019q߲qNdl3*\u000f?M?^b<<QS\u0004BR\u0019+C\u0019mi\u001f;H!lOqVu\u000eN\u001d&\t\u0001P\u0016MNqA:3.\u0012hO\u0007Uwlho^UМޠaj(C\u001dl\u0002EdhҮDkJ\u001f\u0014\u0013!Co8x^hM\u0010\t\u0014!q0`s1[Mϊ*hXrA~z\u001e/\u000b\u001c\u0015rpt&QLLQ\u000e\t;\f'>?uy_ +Or#>v\u001a\u00135\u0018#K\u0003;\u0006\u0003(bм\u0018\u0003\u0019ad<&C\u0006Z\u001fO\u001enD#\u001d;dā\u0010<@3w\u001diV2pP3!\u00128r6$\u0013r\u0019D6\u0007 \u0016ʣI6#6~#6;tXUMB,$>\u001e\u0007\u001a9'h+\u0017,D؄1\u000f.#=.8e~ɲvQ́v=\u0010*@Ey#>ρ0;\u0014fa\u0014dyaܼ\u001eu\u0017\u0018\u00171W\u0003,fU\u0003mq2S{o~eL1Ԥ?\u000b_oJd:W\u0012\u001f\u000fT:o\u0005szxG\"@}1\b=)\u0013j{#>\u001fp\u0006\u0011p6%G\u0010Əh%\u0007%{*\u00125ڛNR=۰@\u001f\u0011<@42 U`\u001f\u00185_g\\~d\u000fP\t\b\u000fd2P)\u0015\u00144\u001e\u0002y%A5@\u0012I~1~ŒѪ65Dk\u0011J>\u0011\u0001\u0005\\a'\u0018=0j\\F޻6Hd\u0012\u0011j!A7^o-~5km\u001a(%EX\u0015myUZyYL־+&5hQ5At#604@:F$G#\u00186oV\fISot\u000eO\u0003\u0014؟O13[ٛfdf&ez\b\u0011\u00194\u00056\u00161\fM\u001664jU\u0012`\u001d\u001c!2(h*)Y:ѦWZ6\u0003XQ#=\u001dSK\u0019*@=lO\u001dV\u001e'\u0007C5\u001c !]ȁ\u0005Ш0*\u0018\u0002aF\u0007\u0002@ϑCuQ*\u0010H\u0001$ \u001e#>_du\u0012.ö6L\u000e\u0011Qlz\u000f\u0007\u000e\f0\u000f8&3o4(\u0006GQA;d!uͅ**e$L\u0007DI-\u0019Y'DN}\u001b>b3@D02\u0017c?#>\u0018#6aR#|tx}>~\u0001AW>[#6Ы|C\u0010\u0003b\u001e:\u0010}G!61z\u0003\u00128(Ӟ'te2w锸?\u001c\u001a3Q\u000f@\u000fS\u000f׵<(A\u001f9~j\u0018F\u0016D~HP\u0003o\f&?;p=S*t5!G\u000e\u0012\u0004\u0005%<\u0002S#CJ\u001aQnͫG4ǚpbPfaHM4ha$\u0005\u0002裌Eiv:L;}5\u0003\u0001r\u0001\u000fs`6v\f^C*!߃j\u0014TVM)Tq\u0011\u0007|[fvFbm\u000eM\u0001Yʂp\u0014|'7) ;K\u001a!L9\u001fH\u001e\u0001\u0001ݠT\u0007W3jp4$H#=C\u0014\"\u0010?\u0007`≠E\u0011H#6i4ahM\u001c\u001ac\u0010r\u0010\u00128%J:mXQ6\u0018DBI!{F\bYjj\u001a*Lcº\u0014U\u0007$lވϊЍ\\+\u001a\u0018DBB\u001cljg\u0013^ܟ\u0017{#>\u001fW/i%1yj:}xH\u001ck~\u0016U&#B\bCQr`(& $^&2d-\u0011kZ57+8aNq G\u0006ry\u001c\u001cQFim\u0016ŷ_\u0002\u001em\u0014LQ:\u0018|y\b~a\"]\u0011.@)ٟ#6#>h\u0017t\u0014ȉRW\u0003ؽr\u001cs\u001aa4+>\fәEjvUW׳b\\7O\fk$#6?#6`9g;z3\b\u0001@\"q۰s\u000btku\u000fT@\u000f#=O\f}!2a#aJ=fT7G|@q\fb[\\\"4IE8ƓX0R4\u0019@,\u0015&\u0010-sc,pI$?\u0016[#=4j\u001b\u0012jL\u0017C\u0010\u001a\u001f#=F\u000fEb˃U\u000ev/f+ڋXhbu܍'+lzzSwnݼ`+w\t#=}H`%\u0010\"h14ce\u0014{O)o\u0006$3٦\u000bc!\u0015T~̚<:\tFDݽ΍o:sTHi*.Eظ#I,xB\u0004\u0012\f.\fdh\u0001\u0005u\u00070f\u0016\u0010ɐ.1\u001ci4~;62iH1l`s12XaN=\u0014(Bj]ee/0gRY\u0003DAWh[&7,Ȇgw/5\u0013̛\u001eMK\u0012\u0001fcDؖl6b4S\u00067d\u001bd\u0006AB#\f%l!\t$0UF44c&\u0019\u0006\u0006XX#>\fIP\u0019FƋA\u0018JPѓ\u0015QA#>ӻ֕ܫf.\u0015!R\u00042\u0005h`x;T$,\u0002@n\u0003Ճ#$+\u0005\u0004H\u00042FKdef1\u001e1\u0011\fe\"\u0015 L\u00164Cj\u000e\u001dYE\u000b\u001b܃1\u0006dk(/\u0005\u001aC@8#>*\"sއhE6|\u001a&3^YYW.xDdgQL\u0015'\u001a\u0006\u0004#=:bF\u00042Uyx\u0018?2A\u001b$Im\u0002=2\u0016\u0010\u0013\u001aޔ#(`\u0006AT\fi\bchh!5\u001dzǄ#>W\u0014k.c$\u001bӍ\u0007F_,dF\"\u0010c(b\u0018\u0012oW\u0004ab@؆6\u001c\u0019@\u001b[f\u0004i0m5\"\u0019\u0003\"\u0003iLl9\u0018hʜ5AE#>\u001b6LԸ\\\u000e\u0018\u0006d\b43\u0014o\u0010̘\f\u001chB\u0002Ɗp+8ԯz\u0016\u0012\u0006\u0006G\u0014VjfQ`\u0014JԦSbыi$534,24\u0012 C\u001c#>՘M\u0018WKJf\fƘHɼ̃\u0010j\u001aBD\u0015\b#6o\t\u001bxVHW\u001d(*M\fL<2ɐB#=\u001b$ \u0015.;#6H2G\b\u0018&4T5d!Q\u001fkn\u0017,\u0007{뛁\u000e#=7q\u00184F6c1Q4Ap\u001aY\t@ܮ\u0004#=$L\u001e\u001ab7\u001behDF\u0007\u001aQ#=e\u0017\u0012uceG#>(c&j%8x\u0004\u0016.T\u0006Mb\u00049&Q&#>uhE\u0005Ǔ.<\u0010X@i>q\u0005j\u0018PْQPLՏOy#i\f#HN\u0002\u0007\u0001-kZQA=6ckU7\u0003o\u00060j\u0006\u0004<aĥBA؛`0M#61p]L&<˅#V]U5kz\u0006܆Fg\u0010\u000eLlcN\u0019*R\u0012>#Z`8N\b4\u0011|Rk\u0002aȹ`aKtݭ{{oz#=3B,#>:U)a\\hM\u0006_\u0013u逸urKgή\u0018m̒ʠ\u001ekJ\u0005n#=FQHGK 6ɸ\u001b\u0006,hG*`G\u001d\f<´Z,ly!0Ʃ\u0003l44b1#τ5&BcKqs'HDN8[l:,lPmDn}:ޑ^<213w\"\u0007\u0003X\t\u0004*t!Fxh#\u0019\u001c&j#==Ohw\u000b\fҭ-j#>\u0016\u0004\u0014P\u0003\u001a\u0018=#=\u0001a#Y4\u0006\u0011Z\u001a\u001aJd\u0018ΈCM\u001e\u001b5\u000e\u0007`\u0018<&ژ@\u001eFJ3BIda[BNd\u0007\u00193$/(/JwTɋ2v\u0012\"s(>a$\u001es$r\u001cXzt\\Rx`N2\u001c?#=+/ҹݎQa\u0005Z\boUVȖX:$#=jqn1iDkdXb\u0004[8\u001d/#660V|sV2\b\u001b\u001b\u0011Gd#6U0\u0006qr\u000b\u00026XZ\u001egף\u0006GGP&tecK\u000br@|;e\b9\u001a0` 1\u001c%\b\u000ePi#>\u0012N\u0002ၠ\u001d\u0010&;\u0006MRQXц\u00122i0\u0014\u0012lXlD:q#>,dR<jo\u00101\u001f5X\u0003D'\u00173\u0011wu蚬\u0004\u001cbmZ(4DŖ4\tu|t\u001c|T'Mi\"-$\u0006\u00167JŔ3rz~3 CCdq\u0007A\u001ehe\u0018|\u0005)t{L?e\u0015o,0\u0016o\u0019\u00190͊v> #=4y7C\u000f\u001d\u00184Kmo<3%$ PM\u001fw֏&̔I^+X\u001eTR_j\f*\u0001pd\u001a\u0016jI\u0013id{\u001bъh3{\u0011\u001dM3&\u0019^\\2Q؛ǪJQ͑\t5B\u0005)\fbb-).jK\u00055LӃA\u0015QQ$7QL~e\u001b>#=FBђ(d5#=\u0016jU!s$\t0\u001a6Рh\u000fUhSx62FK{\"f\u00042˯\u0010a\u0004mJhHI\u000b\u0015Z#\u000f\u0006G9--'9\"\u001b\\t@Dҗg\u001cz1\u0003UY\t#>\u0004VbŃ[\u0006\\J2\u001b%tBƗԉ+5'vVtu70HPKX\u001d7[J85\u001a?Es\u000f&\u0001@h՜qU='}=\u000fz#>Gˌ\u0004K/w#=dE\fҋO\bD\u000fFY^\u000b#=a=b\u0001>`#=wbFf!BR\u0002T&8k6=\u0019\u0013\u0013QB\u0004m٢\"25{-cHQt],0GU\t\"\u0013b\u0001(A1IHP$\b#=\b\u0002O#6(@\u0003#>C9Jl#6\u000e\u001c\"\u001dZ2hhєa\u0011+N乶ǻzIj<KT\u0006\\Q5I\u0010ؐ\u001c41&\u001a۪B[%(\u0018\u0001\u001aZ4Ƥ;\fPL&H#=0(\u0007R\u00149]\u0010Eޠ;Rwj\u001dyvZZܷ+\u0005hLAv\u0019SLfe{N{Z-[E=JEke]\u001bMJᳺ]f4R)\u0013D\b䃍@d`kZ\u0012\"i\u0002CR+/~\u001a\u001cLn\u000b*#6(\u0014̂[8\u001di\t#=4H;\u0016UnJKڥ^@.ɏf1\u0010v0\u0001;\u0005\u0005\t9<6\u0010I\bZ\u0004\u001cS%2\u0012\u0016j\bL٤\u00038\u001ed{u\u001d5w4Fƹ\u0015\u001a׫e_&*kEҍF-6\u00162h6)F%b3\u0004٫.\u0019H}iBR\u0015\",ص\u0015F\u0005\u0016\u0002Ų\u001aEF[\u0015%EL$TYJh%\u0018\u0001B!\b'\"H\u0012x(ǙF\u00110\u0005MVA#>Ȏj)!N'w\b\u000f#\u0010@\u001c\" `_\u001c0JV!)b\u0011V@Td1\u0003{}}&˿ݩ@8?јGt>GP\u0005\u0006+0'q^/Q\u0003\u000b$#6\u000eH(d D3\u0002\u0012F@\u001c0EA\u000fm\u000e#6&\u0006I\u0014&h\u001e?\u001a\u0017g'm*\u00030\u0014*%&FA(VAc9\u0011\tĊjP]M#>XL46QE[;U^\u0004\u0012\"\u0006(\u001a\u0016%ThB01\t#>\tPdQ8e!RU-Ayڻ]\u00018~:\u000f3\b9\tܜ9\u0010IpLUz\u001b\f,qjȑG#rG\u0006#n\u0012\f #6k՗aH~\u0010w\u001c&#=hHF\u0006\u0013Q\u0004#6tC#\u0003~~|')\u0005(31\u0001ĕDáSF(j}֝Δ#zCiGp#TD\u0001-]&=&;Uw\u00078rW\t%)\u00181Pǰ\"GH\u001dx(\u0007&\u001d?RqP?&3x?KϏ\u0001\u0018ɲ(\u0010Ͻϑ,MD2\u0011y2(-m\u001d\u0016\\aCM}ɤȡ\u001582L4w~zh>UBW=a\"#>\u0004Hm\u0012pd\t\u000f$\u000fVea!ju㭧H)\u001bwauW\u001561oFjݸAZ\u0018\u001d\u001a\u0006{65E8lD\u001cLg\u0019\u0003,\\\u001a\u001bә.LR<Ǭ\u0015L>>^\u0016\u0013\u001a7\u0006Х\u0014\u0018`ݙ͡$jl9l\u001eOR0g\fq=\\\u001bۥ\u0002/OhB6D0|j9#>4^Veg;-$#=\u001e2CEC5#=x0\u000e\u00039\u0007y\u000f4/\"_>\u0005e\\|w$Akq\u0015Ɲ\u001eԑɽ\u0018|yCJw@Qf\u001dӒc\u0014T}y\u001fv\u0012tl*Ő\u0015|\u001e`]G`\"?i\u0010־#AEA\u001f7\u001b\u0011\u0018=P\u0004+DFAm&Qm\u0015\u001a;S!\u001c%WYH0\u0002-d\u001dʻ8#YЄ@\u000b\u0013PJtQw'Y|\u0002\\2w\u000fׄAb&oŽ\u001d\u0012\u001e$=L<̀(\u0012)#6xbsHU\u001c\u001c_Y.#6mc|c\u001c>d$ppj!p*7\u001b8K 2W.\u0012e\u000eܪa2&1\u0015oPEO$MqmLjإ=hr+!id\u0001C\b1P\u0011\u0003\u0016ZrZ\u0011w\u000bV\u001a#6\u0006#=HlWS\u001c9#=f]6~\u001cPXѠcdq`l.2\u001fd͛O\f\u0013\u000fp0Ѯτ\u000eIE(4\u0015\b\fU\"$\u000er,QD\u0002!\u0014JRQm\u0018ѶUm\u0016\u001d,&(#=\u000f#=j[3wl[\u001c+۔{]Oqanٖ\u0006iL\u0015tLm*\u001aaXŝPd@BPC9W[xkF&*\u0013\u0002A,\u0010\u0005r\\RKIXA+Jʀݯs\u000bkMtEeҩ\u001aq\u001c-##>\u001drM#W@3\u001dA\u001c\u0003\"BX9\u0002PCQ\u0014Ӳ\u001e#=1P&\u0007X*\u0007#@/$$#=CA\u0014W\u0010LM J\u0014X~7\\`\u0005#=\u0014\u0015ԲRw\u001eUʢt&&9\u0003{BM\u001dO\u0018+Þ\u0003\u0016q¸\u0003t|񎰄\u0011b_ѭT\u001c+5n2a\u000b\u001coړ\u000ePnܝUL9ws'>Lx\u001eK'Ё@D<V-߹y a L(5%L\u0004\b\u0015$\u000f\u0010\u000eo-M\u000ev\u001b\u001d,]5PJV3\u001f\u001b/p#6\u0004#>St$\u0010<e|Hx\u000efM?\u000e^yla+_p\u001e9\u000e[ރEb\u0017p\u0001J?Bd>\u0010I˿6< G\u0007\u0015\bpJ\u0006sE'(yF9\u0014\u001cAhy\f\u0003Ppz\u0016xR\\P\u001aj\u001ffWb\u0010\f-PP<\u0019)ڪG\u000e\u0019d\u000f!A\u0011Iu3R\u001b7\u001c4!EUJv۴#6\u0019wD-\u0005\u00148c\\\u0017~n\u0003#=(Q;$k̅D(w*&R4\u0006@L\fӴ%\u0010\b\u0018\u0003!\u0007\\ɫ38=\u0007-#1*0]i=J\u0004\u0003\u00168*\u0005\u001b@l\u0010m\u0005vɭZ\u0001Q\u000bӦ3ttVzO\u001c\u001f\u0005h<2mv\u0012$Z\u001bF<ێX\u0016\u0007pvF\u0019y&\u0006\u0003\t\u001eh\u000euZ\u0011\u0010ݚ\u001bߖkP8[c#6JK\fƿT\u001bS3fqtk\u0005\u0007\u0002+H\u0014bl\u0010N`D#6S-\u001d:7JAuW#=$T#=0l \u001e\b\u001e#=B遰̨T\fB@8}^\u000eH\u000b\u00196E\u0012`1\"E\u0003䄒\u001fƘJ'\u000fd\u00187w\u000b\u000bmqT\u0005z#>\u00143+qWfD2aI\u0018\u001c#6ٕbs)BSK2DW]}\u0005dzK#6ySrC\u000b!\bun͵]m\u0001\u000e\u0006`S)L\u0003c\u0001*y\u001c\u00159?#68\u0001T\u0019\u0001P%0DT6\u0002!˲p>\u001fω\u0010OB\u001b\b烋\t2\\m]=e]ʜ`(\u0013\u0013L\u0013\b\u000fO\u0013`rb1#dD\"J(TK+I:\u001b+-\u001cnk4\\ENEz2sZ\u0017u\\W&/v^swN|be\u0019u\\e2RK\t\u001bFRi<vREF!6)Q\\Ǜ{λ^k7qj95SSt:7\u00058\u0018\u0006@\u0011-Thu绯mv@C\tŘbl\u0005\u0010-)brwʋmj-t8弼R\u0016mrKZުA0\u0005(\bHX\t/bU`\u0007\"B'\u0004\u000fO)}\u001c|\u0006%\u0018<\u0010\b\u0013)H\u0014\u001e\u001f?\u0015@\u001dЩTCJBP\u0007>HQ\u0011\u0007'УԘ\u0005\u0003#>!\u0019#\f\u001d(jb@K{\u000b\u0002%\u0003\u0004\u0006\u0007g4\u001c\u00024h>\u0011}j|r\u000e*\u001e^Ө҈\u0002?#>~ep:t\u00170y\u0010$#6(zNJHT\bz3GԔz\u0005\u0015\u000fg?<Z (\u0013\"3\tV| 9\b\u0016_\u000fP#=y(\u0007`\u0001#>D -1D̀M)JP',{O{S\u0010\u0004\u0010\u0012)4\u0011>\u0010Ѩ\f\"\u0006E\u0004N0\u0010b\u001fHh\u0003ct(7\u00194~o\u0017\u0002R(@2\u0012\b\u0014EC\u0010A~W-\u0014\u0011\u0012$R[Z*֊cccZ)شbVRR\u0015m}l0@r5N@d.&p\u001d.EH]k\fF-&\u0012˫4ۮ#>WV)\u0017L;\u0006$\u0016;ܺ]^tKN\\hN\u0016\u00181\u00119n\u0017\u001b2\u0003e\u0013H:}z4@d0t\u001a\"H\fNDjZgŷ;\u001aM\u0018@x+2\u0004#>\u0003C$\u001d2j^@U\u000bDE\u0011݄Z\\J\f_\u000e>fHK\u0006ҍ#Mޕ=\u0013@RPD6b<\u00114&LQ6#>:A\t+#6ܾ\u0010#>\u0014\u0002\u001b\fr6w@\u0007\u0012#=v\u0002R_2)\u0016sA\u00194\u0001ᝤ#=\u0010\u001c`\\\b<\u0018\u0011@{bbᠽ\u0018TW\u00029֑Jy\u0002l\u0002\u0013\btpub\u001fP#>]69\u00191`\u0007T`@\u0001iu(\u0010 o1\u0007\u001b\bb\ts\u001dC*I\u0012_bu8j)<E\u0013T\u001a`\u0001X1DT4ѨQQ[\u0011ڕ%\u00164\u0012!\u0002\u0001\bBHK\u0014Cٜ\u0004=rPd\u0015\u0013t}{\u0002b\u0011iP\u0012)E\u0017\u001e\u0010*2^\f\b#>6B\u000eFkJ)-k\u001b\u0019\"($\f\u0010\u000b2*4\u0001\u0002߭\u0014\u000b,|8W2R~O\u0002\u0001\"\u000f\u001eQSE%H^\u0001\u0010\u001fCjh@A\t\u0002zѓMWjf@&\u001f6\u0016#><,.\u0010(\t\u0018aTT \u000eKr*g\u00175}G-+\f|b\u0007<d<*\u0018\u001aǻHOrOgr\u000eH\u0014\u0005\"\"\u000e;9\u001a[,-#6Qy~#6\u001f\u0007{>x\u001c ;SZaFke\u0010\u001f0?\u0007q@thHT\\JȓcfyU4IVĪE\bD [\bv7Y\u0012\u0010eIS\\i\u0016\u001eL\u0006C\u0003\t\u0002|͍\t\u0014\u0003Ih/i\u0010w|6۱k\"`hd\u00106\"*1\bʘfN$P#nܐ0\u0019Z9\u001aR^\u0003#\u0004FJ\u0006\t[.6lh\b@\u00124PD\\L'\u001cfяU$YKbEuҿ#=\u0006\f\u0010\b#i\"\\#@\u0018\u0016\u001cU'\u001aŚX#=CA\f\u0015AJ'F\"o\f\u0018G>վ)$bI9\u0007L1\u0010{|ބ'\\FmwQ-\u00021!\f\u0001\u001b\fC\u0001!*\u001c)kw\u0018učR\u001d\u001a#\u001dQ]\".\"Q4i\u0003P`q#6jD,;\u0007\u001b#qY\u0018K@W6#>rϻ\u001dYJ`$#>\u0016`g-\u0019\u0001\u001c\u0016i\u0003rdf\u001d2;gy\t@S\u0010R,Gb3#6\u0017hjǵ ($K蝙#=ћOLzu\u001dC$\u001aidiQZ\bVO0m\u001d\u00066%&\"vdBQ\"\u0001#6.@%\"#>+\u0003QcbJN?\f\t38#\u0010\u001bG\u0005G{-\u0006EC\u001dbl(C\u000fN\u000f\\R)\u0010~\u000f\f$\tp<)yAU\u0001'y\u001c~Ó]\u0016\u0001]:٪DDDӢ\ftm6\f#=EB1\u0018ǟ,\u0011\t \u0012*+NFnkvd\u0002Da1\u001c0\u0017jz \\N6\u0016DRbw\u0007w\u0001u?}{|w_.#6 fYW\u0011ʧ㴵UdYDG*%\u0017\u0012\u0007oH(\u0018EAH\u0017.;b\u0017#tМ\u0019\u0012D@\u0012f\t\u000e~2\u0002\u0006u\bU?d\"rv4f)ܺu7͹܈d\u0003$]0\u001cN\u0016\u0002#=Wo2X%#=$ST)B.#6D\u0013\u000f)D\b\u0002\u0019}>KXx9Ȋ\u00036tܞV\u001a\u0003R\u0012~\u0019Y\t\u0011Ji\u000fڇF#>\\\u001c\u0015#6}\u0004ӕ\u0014\fmFYC\u000fu؛{ܠ)ӳo:V拀Y94i\u001c\u0002Sw[\fѹ 5\u0005)we3/#<\u0004\bz\u0007X\u0018N\u0013\u0010z\u001b#=,\fI\u0018t!7RN@ʤ$\u000b@d'O92#6ROx\b~\u0014iDQ\u000f@\u0007TU\u0012\u0014x 9\u0007\u0012h{3Bㅂ\u0004\u000eqjA\u001a\u0014(Ur\u001a1!FC!\u0010`y\u001aGDRC\u0003x\u0004MBШX!I\bh`ڥi\t\u001b>kS\u0010is0\u001d2\u000fdއv5\fB(PP#!.\u001at\u0018\u001e8\u001f57\u0019!6\u0004雴~\u0012=H¢u\u001fi\\*BY&2R%a '\b\fa\fc3\u0004L#>O\u000e\u001dA}h\u0012 H?\u001f4y\u0006#\u0010[o$\u001f4Hd3$\u0003'YR\u0019E~-?\u0017YhauW5c&6[\u001aa5\u0018>]M']*[4?\u0012+iV>I+et\fEYkXt%m3OEL\u001dд\u000e(QN{\u0010Й\u00183݇\u001a!E%(\u0007Am2~&t\u0015\u001aV lgRc,ETq,\u0016H{\u0018n\b?i\u0007#=\u0017\u001dP\u00199\u001a\u0012\u0019r1\u0016!9\f\u0003A\t:l19c\u0019\u0018~?\u0007I\u001fY;2L\u0004\u0014O\u0003כ'vNP(#=o&Ѐ'͋\u0013T+\u0013;6b\\\u0017d\u0003\u0001)l #G$\u0014ϠQC\u001d\"2ʱ6Ȋz2\u001d\t#=\u001fOwN\u001f0>W-\u001f\u0012qj$!cJ{L\u000eg\u0011u#=c\u0006yٜf\u001e\u0019{i1g\u0011JL2)Y\u000e7\\zlo-\u001fOҧ9m]]5'A荁߿f`󇹨.\u0011FԉEUe5RΆELpή@=Ve\u001b91\u001f\u0013T׃wdaaÆN]~Qy#>L\u0019x3 LﴶMYgG{s#>\u0012Q̾[$SY\u0017pdp\u0010HLu'\u001c\u0017\u0005oEqVH\u001aHJ]S\u0006<LK,zJ^Ƿt\u001a5Vw\u0001M)\u00135\u0016\u000e셉E\u0007T[e?eyϞ#=ll\u0011>W+9-\u0018L\u0013o<DJ뙣Y\u0016\u001b.LRp\u0018\u0003'cAɄ></\u001c#>N\u0017 v8f\u0018^;T?Ѩϐ\u001fҤF\u001e8\b?\u001ez\u0006zr#6VBtҠ%DE6b#`W\u0018\\x4UJd\u001e!H\u0015\u0007a\u0012x\"\u0006rry\u001dwI661&\tٯ\"\u0007\u00048[\u000e.\u0005!3jp'Fr`pm\u0012/ChyxCo\u001c\u0006\u001c0.\"\u001eE\u0012\u001brㆪwug$;v#=\u0001'\\\"w!C\u0010\u0011\u00100~r{\u001e#=)\f^&v0=t\u001fېHa#\u0010d%\u0002#6c\u0017\u0011#i\u000f9\u000b'\bfdcz\u0001>OLTR ON\u00010bb\u0018M\"} Cқ1g^\u0010>J\u00066@1\u0014#6nE3Wr\u001b#>P#=\u0013r9\u0001rqB#还\u000e6\u001c71*%32V@a\"$ O\t%۬`~\u0019A\u0012cڮ'.V8:/q\u0006'\u0002kTeCwtO+\u0001[!Q}i\u0007\u00070!%k3ޓYáFHNӓ#悌\u0003\u0006\u0007#=\u0002܂LTf\u0007֋UZ%UVuV|}\u0006\u0007\u00078\u0011$l/H1q\u0003\u0011\u0019\u001br\u0006$l\u0018(1+\u001dV\u0019[jj5QZ\u001ah5am=OG|ղj'd\u001bdȝj7[ρ3\\m9\u000b{2\u0014݅nAVZM&.+!c#=\u0012[J*jGyR2\u0013 \u001e\u0016S)6\u0019\\\u001dSӈVIQ2\u0001)TJr\u0004i$$X}fSu)Cws\u0011~~Hw7c4;\u0016rF#1큙/c\u001d\u0004:Y\u00129[B{])\u001a27xl[ eZbw<kT&RNZtX&\u0002ϵNyRU'.\u0003M\t\u001fw\u0015\u0004E+\u001b]\u0005-B)8&{\u0001\b*qQ#=QQ^B\u0002:?S|\u00140#6j\u0007Z\u0004\u0001H\u0005$*\u00010\u0013(\u0006I]o\u0016!i \"rA'\u0002|V`\u000fD[zBr0\u001b' dv\u000fmLP\u0011TYZ\u001a-\u0014ZkoV\u0004eӖ\u0015pÚ\u000fe\u0010\u000eāx>0V\u001eJ|$J\tǒ{o\u000ef\u0012\\\u0013-K,8O#6!=\u0010)$X\u001d,\u0006\by\u0007\"r8\\\u0016YW#6>+C\u0007>$\u000bwUÃl\u0013f`\u001a\u000f#6*[ц>G~\u0016z \u001f\ttT\u001f\u000e\u0012m!󂓖b~xyȣD\u001aTw4\u0006\u00031\u0019\u0018\fz\u00053f:Dn71\u0006JqT(;\u0005AA\u0012|3\u001f\u0007\u000f%\u0006\fQ\"\bp\u001cK`J#>\u0007\u0014#6bwE~?{W]d\u0018#'$\u0004c\u00103\u001e>#!'N#=\u001fvk,\u001f\u0013b|0WoJL$1#=NĐ䇑(М\u000e`O y[\t\u000f\\#6RP`#(\u0014Dz%S%\u0006\"\tKY-`\t(Hpw\\\u0014WW~Hr/*ܰ\u0007\u0012;O72\u0007eKOB{N\u0003\u001cl0q\u0002]@Dc-#>b\u0002+\u0007\t\u0018\f8QwxsYO\u001bQKau/Ghw\u0006Z\u0013#>+1^oT\u001c\u0006\u000f2s?/^(L+#Q\b\u001a\u001cJB5\t1ʕ\u0003\u0014CU|N\u000e\\\u0010Rkz5\u0001\"NP?\u001d=gN\\ߺ\u0011\u001c\u00182\u0019K#>;\u0010\fL-\u001a7Tĵč.qt\u000683\u0012#>ތ/X[/X\u0003q\u0016\u000613#>[\u000fB\u001c;#=b[\u0018Q\u0004 I\u001ei:\u001dGS6-!nj g\u0010:\u001a|}ug_%Xb;kL\u001c\u001d@k\"D\u0003#z\u0010Dz\t\u0007rh&\b&HL \t\b$#=\u001dy '!\u0012\u0014!\u0014xălדI8\u0007jB\u0006MUL72E5\u001d\u0004lb037+2_͂N޴;CA#6=ܗ\u0013P?gl薑Mh\u000f\u001f\u0004\u000f0C8p82\u0010\"cs \u001b@tȊ\f3xi2⾝YPuHHG6`P@:\u001e\">%Qaø0_|4CI@y\u0006\u0006\u0002\fK[.#='C\u0006,Phv.Zh\u00199Z\u00180d#=j\u0010(,\u0016#=#=4?B\u0001~qyG \u001e0?\u0019{9A\u0003nc܁c_͜kz\u0013\u00140p\u00131i*p6n\u0017\u0011P6\u0004]̪_oײI\u0017Ԡ;#!!Y`ȇǏW\u001eFܨ:u㑓a\bB̈*\u0010\b1\u000b\u0014I1B2D\u0012R7(nh6k̍M\f$0$\u0007>1~\u0010\u001d=X\u001ęόɜ'a\t|#>)WCw\t#>sgicw[ϩV\"|{0\u0002\u0006VP\u0010\u0010\u0002JU\u0003}<\u00018D>&I\u000e}b\u001e\u0003׺N|j\u0016\u0014݌i#>\"W.M\u0002̙)7p$\"\u000e\u001diB`J\u0011ݘ)\t\"iD#=4wj,?.\u001c\u0018f#6&љD#>kݸƥ4\u0019MQ-\u0011I\u0019cC)2LCaDISl5TE3R{ax#6\u000fDQ&4[\u0015܄[Mv\u001fs\b\u0012\u0010&\b_rB\u0018[\u0007UvlL66-\"\"$\u0011-=-5-\b\u0006$Q\b\u0016I\u001e$xALF;gO\u001awu\u001f{zH48৘Sb\u000f08l6\u0003\u001fW\u0002xa<*J%kQ`}=\fs\u0019\u0005\u0012#>_,K{$\u0006V9\u0015\tn\u0004#\u000b\u001a]Dr\u001c\u0003#=\u0015'&\f&L\u0016Q?]\u0017aA2LKY\u0013ϱPZ\b_-r;ۆ\u000fp}R'\u0011\u001fr\u0013JD\"QZ\bza\u0011$M\\9#6\u0017#6\u0005\u0011cLy-.93\u0015Zb\u0014ue\f\u0018qU\u0018tev8\u001eEf㑓,\u0016\u000e\u0003\u00026T\u0006{p8CK5y\u0014\u001fI9\u000bs44D\u0012m۶`#=\u0002C$BDG\u0018H\u0014X\u0005Ld2l\u001as \u0005bAAwu\u000ep;oKk(#>3tXFQ,Yb\fha Ԁ`\u001cUA6rؐ4\u001a\u0010m&\u0016B)w\u0018fR\u0007\u0019F6 lq_4m٫0PBב3\u0011\u0010Ǭe\u0015ٟCtkM>Ƃj&>b qb@D!z\u0018n\u0011.NB\u0007WZlj\u0012\u0018jC\u000bUY\u0005,\u0010}!DAk{;\f*i(jGCjMF&ZƔF#=2B \u001b\u0017#Ljd.\u0016`\u0015UG#=15rY)\u0007\u001e \u001b M\u0013uz(J\u0012#>^s\u0015\u001a\u001bъ\u0004 )@\u000e\fu\u0001S/[E\u0015vs\u000ermcI9\u0011\u0003Dcp\t\b#=˼n\u0011^y1\u001bX\b!ˌeZ\tiE\u00134N=\u0014e)p_\u0015sv#=\u00064\u0004'#6b\u0002 J4#=4־׻j\u000enZ4h~פj/{\bMة\u0010n\u0011⍜\u000b:g\tI)\u0015u#6ژn\u001a~\u0017+j5aZIᜰ2@\u0019%q~ҢI4Ҿ-˨^\u0011綥)u\u0013\u00127줢*$HY$;Dд$\"\u000bz`6\u0010]֒A.&\f\\74Aba\u0013\u001bJ%fs:Z+slI-I&\u0006\u0004\u0015\u0003P\u0012\u0001\u000eޗlHɂD\"\u0010z9w_<sP9I}xTU(R;PӲ`Ǩƌt0WcbZ\u001dg2y\u0018\bͰ3\u00066fP\f \fTb#=]ƪ\u0006l\u0015a\u0017iڮa>Ti%ZfQG\u0004ښ\u00102\u0016!R\fzMO\u001d\u0004{lF#\bg\fMAz\"XV,Ʀ(\\ѥ!-\u0018c\u00188\u001a7=\\\u0010TX(i\u001cn28A,e$4\u001bdL\u0007Sln\u0019\u0010L4F⧍\u0019n٩b\b\u000en1br޽\u001d|\u001cOU\bqh\u001a luH*V!(\u0015۱\u0016`))\u0005,p5z\u0019B,\bT1(Gyz,dqjM=%f\u00128\\=p&\u0003kmȡ#l&\u00054\\\u0010BB*DDB)#>DS5Vf+o&8-\u0011f\u00138768#Nq&\u001a\u0004h(qiUi\u0014jXf\\K L.\\\u0014n\\\u0007\u00164'\u0004ꉨ#>L\u0017$&#\u0011݈c@dC#C\u0011kX5r$Ŏ&`\\d\u001b.MVx#!Z0\bj-3\fgjJpl\b|FK08a\"\u0016\u000eZd鸆Y\u0003e#$\u0011f@+\t\u0007.\u0006_\u001b,\f9\u0012C[Nё[:\u0004\u0011&5[ \u001b\u0019D\u0016(\"ȋ6H\u000e*\b\u0004F\u0004kL5\u0012\u001ed5*\u00154D+t\u00048\u0006#6j\u001bq#=1CjkvUXm\u0005\u00186\u0018A։H[L#>\u0019H\u0016ҳ:M\t2;;\\A-&@e4Tk'*_.5^%N\u0010hHƎ7\u0004d]\u0015wbd&v\u0014SF\u0003/XfC\u001c\\8H\u0017\u0006\u0017'&dUDFZLL\u0018\u0014f鷘0\u001aŭFq\u001c\u000161D\u00054\u00014JTmHq\u0001\u00066-M\"LBb(\feS*%%\u00029r1GD4)\u0007\u0006\u0018P٣\u0013\u0010w!\u0003@^\u0014\u0014䅡@ڶ\fYMl\u00048\u0004\u0006΃d\u0018$\u0019)N(P\u0014D\u001abcFhw$\u0001E:?Ć\fT̰'\u0016+w#=ד#=QFwWKS'ˑݱqM~#6_\u0010ܩ{|#6\bՓk\u0011QV++*ҔmF#[\u001aZť5U#=\u001aiLԕEo^\u0004;M!#6JDEBp*\u001eҏ؟tbT\u001a|\u000e}m\u0018~NʹI\u0016v#6\u0019\u0013v)%\u001f=\u001eZzv\u000ePddHR\t@PbHmW.؊*W\u0012\fRBR\u0012%G?)͋!ȯ\u001f\u001f`.Iݤ/\u0011#=K\u001b1z\u0011H\u0018/f\u0011WDr\f{9#6z\u0014=\u0004,ѭe2%D*\u0005\t2B0 P.\u0001P\u000f\u0014\u001f\u000e\u001duK\u0007ӏ\u0015!o\u0006\u0013&_~uy\u0012\u0011\u0014\u0018ӁgY[Fj#ׇF\"sk4\u000e=86.\u0004Zŋ\f\u0002`Q;L*J0wsE0\u000b\u0007:\u000f\u001a&&K F$U\u0003Bj\tZܯ7/sud\u0003Vܹn\u0019SĆeח\u0016-\u001b_Jpb\"$LLqblc!#>l`DiVRV\u000eJ/+K\u001cIrZr)\"hw rh\u0010aS\u0016@\u0018\u001a$4\u0018:̡ŋ7\\pdG#6m\u000bإ]\bA\u0017 c?\b\u0005\ffHU˕!TJ)k\u0019\u001bI)tu\u000e\u001820h(P\u000b'3Hg\u0007M\\\u0015117\u000b\u0018\u0011\t1hV^g0j?I\u0018tB\u0014##\u001d\u00075'!K[\u001b#Vʰ%\u000eyj$}\fQM$$A\\š$l(!z߇\tPu\u000eN\u0014\u0015LL\u0001d\u0017\u001a\u0013P\u0004<o>\u0014\u0007m\u001eGւ}f\f\tQ4T1#\fh[ʲD6̨\b\u001aM'l\u000fvQ\u00148\t\"/ΙV\u0014hA\u0015P\fOg_O\u000fԒ#6!䧊Fcv;:=qK򾖉JXY\u000b53\u0019&09\u001bB;\u0013%W&,K[C&r][њ\"\b\u000f_#=\u001c\u000ew+Ms\bfx\u0002\u00051.\\d fs\u0007(4$I(c\u001e#6/ۯs}A~fSw>\u001e\\ʓ\u001ew9`l1\u0019e>K*c/\u001eU\u001c\u0019z֒~ϗ>p?9W\u001f8_$-}sEp\u0017~Paun\u0007_\u0006@r뻓5GvZ,J#62A30\f%ϯ'\u001eF~\u0016R\u001f2\"`\u0002\u001dC-\"\u0002\u000e#6=ANP\u00042f=\tLSݚ#@d}2jS\bh\u001f\u0013n7\u0018m\u0012<\u001c+N\b\u000fl*Pd:\u001e=!0@@\u001e>F\u0018H\f\u001fOw}\u001cxco\u0012{\tTI#>J8|=\u000b\u0013\u0002H;\u001e&83=ĦD>x \u001e\u0010⛁ݼ̓))oۙC\u0013j!2ӹC;-]̽\u00116\u0001\u0001l\u000e|T\u000erB)#6IsTZMQord#J\u0019N\u0007\u001bVe\u0011\u0017,vsW-dn5z-&UrJj7/*I&]{njD.Gr\u0001Y\u000e@#-\u0015Fm&w-9\u0019\bRkX\u0004+L=\u000eE\u0006ޠ\u000fbm^$SM\u0006`s@/>p>g1 BvSn@\u001f=\u0013ϴ\tꆔ#>\t\u000f\u0004K4#>\u0006\u0004(TC\b@@\u000b\u0012wKA#60\u0018\u0007\u000f?\u0006\u0004\u0017cuЏڀ~G\u001aPjt\u0018#\u00110R`:]\u00032r\b{,\u001f;&H #6^\u0018[i\u0002\u0010\u0016\u0007I5*\u001aXR\u0019\u0015\u000f\u0002@SS#IT\u0007AҠz\u0004?w/3}g<B_IT\u0011\"\u0012\u001e8+n\u0011\u001e\u0010\u001fhUو86h)#>~\u0011R\u0016\u0014WCIy\u0013\u0001K8#=nf,qTr\u0010\\\u0018\u0001@\u00102ByfSт\u001a32Lmf$U]\u0006\u0003\tB\u0004$e\u001fe(L\u0006\u000e\u0001\u001d\u0004#>B@q\"H%4\u0014)\u0006\u0019-B\t\u0002{{7A\u0016j\u0006`\u00039EBsWnEX\u0011/܏?Rޙe^io5*MTHTkU\u0018V6ږljMXXZmƦUcbL-ʬi\u001f=ޟ{\u0005\u0007\u0002\"\u0001iQ&t#6#6\u000fz#Zϳ'|-\u000e\u0010ϥM\u0006I\u0001H95#>#>Z\"Hp .\u0006\f]$F-A|h\u0007 ;\u0011\u001e\u0014Wn\u0014$\u001b8 N|\u0014\u0004%!$\u000b '<\u001d%񪑡JF\u0019ꜢS=gq'߂O\u0001tw\u0012J\u0005+BA#6C'D6\"i\u0013ZڿSii&\u0012̘jLe#>\u000b\u0018-\u0013\u0012ihj*hmj-)Adʤb4\u0002\u0012\u0004\u0004R\fLٖثEH`;#6ư\u000e#=6f\u0012\\iXWcpT\u0018((FVF'Hݪ\u001a\u001bE\u001c\u001cTX5%B!1 [yW|v2а\u001b#ƒ\b4B2Ԍ\u0012FWٳ#=#`3*A\fjIK\u0002\u000e#=ŘcWVD\u0004f1vWXӤ&2\u0001qRIXƚm!!E*Q\u0005\u0007$\u0015ti\t\t\u0013\u001cQcM&FF(U\u0002ci1QYlf!F\"=ֺV붅,˦URƴ:\b\u0001EPB\"@9(Ǉ%\u001adQ̱s1rQ2\u0011\u001c 1r\u0018\u0002x#J\u0005KKRF9Yb%If&4F4s\f3#=ـ\u0006\f\b#6\t\"\u0016 Б)JR\u001cR4.m\u0004Yf>5(\u0001`٘X]PslǍ\u0019\u0004C@~\u00106\u001f̏{P\u001fZI_3J)O\t\bED\b\b%_g#>72.ɆZȡP(\\\u0016/3}`\u000b`8\u0019T!vɤb'NM6J\u0017MmDV\u0013d(EG7\\'\u0011ShJf\u0017s`a*`{d\u0016\u000bI.+(\u0007w\u0012\u0002>\u000eZ+<hU\u000bEEՋ\u0003=IUGFC0`d\u0019\u0006@RӺ;1NK\u0002C\u001c$\"Fd|ֆ2YunN\u001b\u001bm\u0005RU)\u0013Ph}Nܽ\u001a\u0019BoRвfa\u0013\u0019=:D}wS##3$sQVXEkh\fٹˑ\u0016/%8d\u000fg.ٵ\u000f郵\u00035熍\u000eK&M1yM^R\u000e\u000b$f<՜+ÿ3v7MW1\u000enHI@Wo\u001f4K\b-+%7\b8 g\u0010^jؘm5^eEQL\u0006\u0012\u00136-K> uOq5N#=!e ~\u001e\u001b\t̺ږrL\u0019\u001b\"IU\u000eSwfM*|;\u0002K$uGʛ|uG1q40vࡋQ\u001e\u0005Dʮ=\u000fUs&Aa\u0013lݝ_uaq0[\u001c\u00023gFta\u001b##\ta\u0004FSÎ\u001dgASY8\"\u0005\u0018@\u0015#>sPsN/C\u001b\u0010\f\u0015\u0005,MnB\u0014fX/wOOҍ8l)ý<Zu\u000eDvVé#Sq\\xY,a/KCmYF U!Ks\u001e\u0013/2e z\u0012(\\Way|;lmf\bRy&;&\t#ɚ\u001be\u0007]8s\u001dRXa2\u00119\u001e#>\u0010#\u001f%91\u001dݪN\u001bY|\u0011r<Ĺp#KO\u0013\u0001G\u000b\tI\u0010$+>\u0018\bc\"OR\u000fS;\u001d:8nz9JOwR\u0002:_ô\u0013>\u0016Kd,vfA\u001dTfUJ\u0019-IÝN!K{+3}%+\u0004yݣ^zRu\u0004\u0018ǭ<<elܓ~8hͫ\u000e\u000fl\u001ci\u000bw\u000e\u000e\u0006,ݖaK\u0018M|ϒO#KsPD=\u001c*4\u000fs%\u0013\u001bN7\b4e&'&ƲLZl|\t\u0019Q3D>M6bFx2l!|\u0019LHf\u0006 \u001d\u0017`26[\f\\\u0011\u0019:8\u0006{1`Y\\\u0005g\u001a\u0004*OTrwMGDjl]~\u0007Ql8r\u001dw8Ǥ`Fޘ,Fq#>uUd\u0011\u0004/_Zk2mg2=o>^y)u\u0004\u0015OE]~{3s#߄|=QW+~\",\u0004;c0(#=#6\u0013j1(4W|Ķ4Dp&5\u0006.\u001cl\f'G\f\u001ar&ݛ΄BVmӎ&\u0006eT7.0x#=\u0016M5m/\u000f/3Y\u001a\u000f,o{+&oM\u001co\u001dܔV'PcX))-~[#=#=U([UŹ\u0015\u000ftקP\u001bJ\u0004S:\u001exCWy%'YiuЗDO#=㳜2CW}\u000eQ=bQjطĘ҆翇\u0010q;&\u0012A\u0017ĲK3\u0006-E\u0017D\u0017ѧ7n]\u00122UYڪͳxj+\u0015RX-\u0012<o'\u0016mR6?\u0014n睄Iz^T1ulk=\u000b\u0015\u0001\u0019V.1Mbv,9c\u0004ST\u0014!#\u000f8~Rv\u000b#=\u001dfe-k\u001c>+-qs\u0006\bt\u0012\u001d#=\u000b;a4\u001e!\"6WSǢ\u000b_|֫\u0015 %Q\u0010d\u0010efL\u0011p<U͇P?^5\tRjG&;rT0n2j\u0007iLҠ\u0014D4\u00190/e-\u001c?\u0010\biߟ\u00182<TNsʵJLu\u0007}coI;qgҥ!ۇ<!+S̘\u001c\u0012aYrT\u0017O\u0018&{;|\u0007\u001677qW >Є\u0007Ri\u0006D!\u0004 L\u0012s(TT[1+^XG0\u000e:ŗڙ.)_\u0012KP)]\u0016ߺ߬D:4\\4@foG8<a21g]\u0018>H:[rQb\u0010-KsV9\u000b^;}X\u0004\u000b\\\\<I\te%0#70S%\u001cܮsxL0\fRL\u0005G(7%\fq~OXXE(\u00136\u0007(\u00172\u00108\u001c;M%6z\u0013Cufy^\u001btnh^V\u0004z\u001d{r28*\u000f\u0013k]`; a\t\u0018Pk7m>V^Ok )NؕzY\u0012l\u0001L4\u001e\u001aưmf@$ޘGbj@j\u0011\u001cE<OHc+\u001b[!\u0018Ƙ\u0016#]9\u001e\u001cY\u000f[*\"%.#>B]SfDEgA<\u0019_#=s\u000e:\u00070O\u001e\u001dm<#(Z{NōO_\u0016g\bi\u001dy\u0018{猂K\u0007TH3]&:2\u001darzxg\u0018u\b?51\u0011\u000eK\u001fgXg)Cq!n\u001c+\u001ar0MKzC^&'성6w9V{<_O5*&N Tbnjg?xj\u0005G&t;\u0013py\u0012Xj_S\u001bd$E\\m(0IohTJ8C$XAv霧nƫ=3m>f-L\u0018VbLy5u)~\u000b&\fjFy85u,}L5V]5\boR\u001ar\\%{{\u0012\u0011@.\u001aö*E$>\u001cPd5L\bn!U\u0010x1k\bS\u001bQ'4\u0019'Ib皒F5if{d|/tc+&썓K8rmw7rCd\u0017NS\u0015\"$2˽\u0019\u000b`\u0007\u001d\u0010;}x뾘%#>%7 ٯ+25/'o}]\u000b\u001ayK{#>bQ1H]\tŘjUq-5᛻֙vv7f.x\\;N<\u00116u>5zKٞ%b99\u0015)L`YݎK:6PBff>hf\u0012N\u001do\u001aPȏ';>8AN:`w\"Jc\u001e\u000fA\b0\u0018?oL!fOVy%\u0005,V!^\u0015!O\u0018#6osϮhZZ:`̆\u0013+vk*@:\u000eV\u000fB#>SJ]%o^D\u001cK5\u0016\t)L\u001dpDB\u0013׵{F\u001fP|S\u000eM2\u0017\u0018r(( $q\u0002\ftN6)Ai2\b\u0016\u001aM$n;e\u0003AKQH\\ܥm\fuxm#>5d#>>y'[x\u001d>\u0011t2cKò\u0016(sOzE\"a1.C`Lz\u001cTg\u0012AA CJ,nfq1Ì\u0011_5q`f6>\u001bm\u0004\u0010XSҢ+8\u001e\u000e\u0013#\u00187<\u001esL򍾷k\u0014\u001fc2I\u001d3\u0010AFaCiS7dErk#>\u001b:2lQ\u001e\u001eADw}d-\u00112֖\tD#>\\\u0016\t!\u0010;]\u0007\u0004վuxv(r\u001f^x\u0002\u000e,\t\u0003:\u001ai\u0005i0[\u0012fмޥv\u0003\u000e$l\f'n\u0018DaT[@Z8X\u000bѝ4\u0014\u0019\u0011דMkOjx&\bw2EжCR*ȣ\u0018\u001du.`֫])\u001d_h5L\u0012[jj<OiY!%wϖfLv$q(9#>2ϔ\u0010Nubz\u0018\u001bn.S5ٻvśʁ*ꔓEeW\u001fкQbPB{o\u000b#=X&\u0018<M݋*xx|= gؙt8>\u000eD\u000e޺y\t/F:s\u00181*J1$є_#% !~6m7rv;joB@(p4Ń팥[\u0013\u000eo\u0019_,Q]1\fΛN`o=Azl\u001eX&+D}mYb1+Av&Oz\u001aǯ\u0002ٜQ#>3}r5\u0007NOG0d5a1<^ؽu;W.翙\u000f\u0007?;H\u0014\t'\u0017qy_f\u00054Bd\tg`t3`1Hl:a,nB\b87\bK\u001am\u000e(<21\u00062% \u001cHKZY=kx|Z\u0017\u0002pK|\u00067K\fCXvz2Ɛ|Ŋj+_Pu;\u0012×>BucNP@\u0002\u001a[ncPwZ/5I'B ӛ\u0002\u0002\u0010(8Nr|6UMwj6vְr+KVr@<0\u0018\u000e#6 TYP\u0001 \u0006Nލ/GYǫszwT07A{##=O1\u0004m>6\u001e3\"#\u000b\t1;P2EsYĳm:2\u0016w]:&=s0,#=܎\u0006܊dBP+ãYӮF͆T$&\u0012v\\A|˪\u0016r|adRɅ!\u0010bh\u001e\u0001\f:֋\u0017-MT<\u001c\u000e\u0016*EH\" 1\u001dk#=W/^+uƍsRMY_>\u001cmi|'biJ\u0002#>#6=h.:ES4\u0018g{d1ҧ0=\u001dBz=9>]}(߹Bj$\f&C`vv\u0019:3\u0017ұ\u001byAb|_\u0014껝cxm3#6+\u0006,\u0019\u001er%_jd\u000fV'1l0P0a=\u0004\bL#>\u0012!2\"`\u0006xc1`#6#=8Ua-ӝ֪\u0005\u0006\u0010,9Jp!|Hҁ\u0006\u0017|&Sŧp<9n\u0010@I0K\f2\u00101'YUβ¹\u0006\u0012\u0006Y\u000b̠\u001b9\u00183fnOj\u001b4ETy2PA\u0015~5#>/lb؎\u0016,v]Q>\u001e\" L\u001dH\u000bE9s.\u0001I\u001f|\u000eX#6\u0017#>bq8\u0012l\u0018*\u000b\u001eFP\u001eC^SdC{ݎE,Jj47\u0013#=$nlϏFl\u0001\\xC\u0012\u001e\u0017<\u001d0bTB\u0016s\u001cOXy\u0011Sּ&Eo[ٕ\"d;p/x˯<HyI\u0006-+\u0005G\"Ln[-!:XZUl5i9 %\u0003\u0019coou\u0007wh桥?\u001a\u0007\u0010<:.̜u\u0011nH3\u001da\u001a\u001dKÕr;m9O&\u0003\u000bmJ?c\u001b\u0010!oZּ\u001d_/2@J;\u0001|ρB:\f{Ɗ\u0010#>$\u000bV#>t\u001b\u001a*ֵf:|#\u001eq#\"/F\f\u0006\u0018^\u001c_l\u0013[h|^L\u000b)+<c\u000eGKB~/\u0001/\u0014O%z\u001dl<3j)\u0006^C\u0015<z#\u0011X\u001aP\bĈC\u0003\u001eu)+\u001c橴/DaHGj\u001dQ#ۋ8T\u001ax#>k\u001e&IEM*[\bkl\u000bɡ\u001f\u0002\fV]*co\u001e\f\u0003ų\u0002\u0011</=^w\u0006d~FyBMyk\u000fa\u000ecӃ#6^僌a0@\u001f\bɿBCrJ\"pstfs`C\u00071\u0003ov0ث,#=FYn#6|v\u0006s6bTN^5ڨAkeRb;\u00189В2QI0yo't&wH\u0007hcA*\u0011\u0007Tb\u001d\u0011@H\u0016뒪5|#=10!\u001d\tw\u001cCC\u0007T@\u000e}v>r'\u000f\u0006b\u001e\\\u00165\u0016I3daL3\\\f\"lQ\u000bfA@\u0001B\u0010\u0001\u001d\u0010u{c&c\u0011\u000e\u000e\u0011`#6:'\t\b\u0014\u001b}#6\b^\b#6#= Qi͇PoQgm\u0003Bx\\ >=WF\u001fў:¡xc<9Ȱ\u001fZx*%*\u0002\u001d\"T\u0005\".\b.\u0007\u001f)1a<9\u0015fQOd\u000eH\\ޫ9=&\u001fR\u0004\u001bJ\u001bM\u0012YeIB\u001eh\fW\u0010;l?(}\u001e}<\u001aF3ff)\u0014\u000e8#=\u0017\u0003\u0012\u001fSUhX\f⣊(D4D\u0016,Ym$eB\u0012:Dv~̍h2.aZ`WD\u001c0\u0011\u001d\u0013̹]N=ރG؇\u001f\u000epq\f:\u001c*9Sو|\u001cE64oꌬC(ףlejE^jf͒ r3'\u00050aZP\u001a\u000eF\u000fn7pډF6eƒv\u0019[\u0016,jB6D\u0019r#x\u0019ٹZ\u0015\u0001\u0016/+\u001b6JϾ'T{Ì0&w'A+yJ\tG!C\u0017R\u0001\u0003\u0018#6\u0006\\(SPԀ\u001ab#=649ʎXq\u0006e#6\u001aQQ\u0005\u0013\u0012*3\u001a\f\u000eXa`$\u0004\u0017>\u001a7\u001ct4}\u0007WC&imE\u0019]\u0014A\u0006Cw-n\u0017p\\ȇ1ŅXr#>,'\u0019y\u001cܚbn#=pp!z3wAU\u0002\u000e:}bc\u0002\u0019\u0001A\u0003_߄a\u0012\bJE,#>j̵jE(A\u0018\u0013S\u0007\u0006Ff3_:ˬspw\u000eYY\u001e\u0011\u0012ˎKztn?ҽ'{׆z:kw\u001b)+:\u0017 c\u0014\u0015b9#=#=\"#=\f\u0018\u001e\u0018juA\u0006\u0018/э1$8Jn4yr2\b\u000eA~\u0012mu._[\u0019g9-\\3\u001c7P\u001dv\"#>QSB2Z\u001dB̆&\u001cm#=\u001bk\u001dc.&\u0012G\u00112)t\u0004\u000e4`dIR,\u0014\u0017p6B\u0016Ͻ&LM\u0019r'5v\u0006\u001cf\u001a\u0002'2g\u001at\u0017p(Fj\u0019\u0002\u0005R\u0006k\u001bd\u0018F%7\bːzv:#=**\u0018/\u0010\u0014#=4]ī΢Ѣ\"\u000b\u001dJ2&F\u0003\u0012<6{w\u0002J16.X.\u0012ŕ\u000eIBq6\u001a\u001c`܌N\b\u001al5,qԶ#6\u0004QQG%\u0013Nf*7%Out9%,\u0015CLgC\u000bX\u001adQ\u0011@UiRl{0D\u0010$U+0k\u0006XhcPL8) =51MF[,d#=\u001aNP ڴ#uCcR\u0004!\bns\u0012il\u001d\u0017$a\u0015hݺ\u0012a9u.\bA+Tv\"NZ\u0014\u001b:#6-\u0015fH,j5t\u001a\u0014bv\u0011uqXٲ\u0011s'\u000e\u00052@j( \u0010cN{C7_\u000eg\u001102Hd5<\u0006}ꮍ`^iӤm(H,Y\u0011\u001aMimB\u0015#=x7$ A#CVAy/,޻R].`8f&c4E\u0010KsyD=k\u000bU[[\u0007]F7H\u0011K\u0019\u0019RyH`H\u0014Gj][H1҃Tb\u0015\u0010YE\u0007;\bAh:5\u0016:\u000f\u000f0\u0014>?#Ȼfe\u0018k\u0013O\u000e\u0017'm[\u00164hO\u001a4\\BK\u000ed+-OJ{DT?Iv\u0016F\b+#>@\u0004Բ\u0004C!tF`xz\u000f1 20D\u0019v]e7J\u0012Q\b\u001d 2܄\u00137uԃd}DEsSvin\u000f.{K[VsF\u000fzuS_\u0007g\f<?.\u001d!:Iğd\u0001P\u0007\u0010OU\u0014A4LP1)~\u0017s{WҧWW!DܻJ#>9yD\u001fyJ&\u0014\u001c\u0007*\u0006Ctuw[g'޹r\u001cm«ܾׯ=eO\u000b\u0010#>\u001f\u0019\u0014\u0013D8`Ģ*!<9KS=\u0013_\"c{d5\u0001z_HnzŞU,q#0>H0\u0017s\u0007^\u00116\u0003aL\u000f\u001fP\u0015\u0011PNc\b:1Bσ{&&Z%1M*q^v\u0010h%@TFUVQ\u0003\u0017<\u0018\b\u0003%\u0001|L=\u001eV=Ә\\j!'f^с>\u00192<\u0011\u0007}s20 7#=\u0013\u00068͘YW\u001a\u0005\u00131$4uGך|p5\u001f\u0007k灔[;pWk'#FdosEC^dR\u0004\u001bAޖ,^[D\u001aR@G#=\u0016ډF$ƕ\u0018&֥Jm\u0015df\u000eS!0Ԋd\u0012M{(̝'w7uWNrٹ]P#=\u0010.$I#6#>6b@~φ/\u0001g`˟F~ƍ1\u0011Uʺf\u0015C\u000b0J\u0012D-26jZ#6\u0014بD#=MHJf\u001al*\u0016kHU53MЦ0#\u0004Ȧ4b(ő Ƅ\u001ak\u0011\u0012(A$S(Rɑ6&14嵷e#6!+{ϟ~]\u001fr\u000eG'\u000f\u0007/'>w}!\u0011?WH<J[\u000eM\u001f\u0007_xxr0&z5\u0014\u0010 pQ_-]@w('\u0010?0)B}FȢ:ck\u0011!\u0002fHB`<W }+h~@\u0006\u0012\u0015\u0013w5<$7'_G8p\u001d@l8L\u0005,_e\b˂\u0006ńq4⪌\u000f\"\u0001K\t>;5mt`Ja.sf0\u0017u{[ߗsaΊ\u000b(]2;\u0013}ڧ\u000eF{Yز/2|}s\u0019HS\u0011&A0nt&\u0011+#=c )\u0003iS0\u001d&r.?\u0012UM\u0013\u0013`\u001e:C\u00156g:#6\u001f?W}A\u0005\u001f=p>;O2dPlY\u001f%I\u001f/ǯ\u000f=\u0007\"Htb\u0001 y>J#>\u0006W.bK+&Ueeb2ɱkS%$]ewt;櫉`d\u0011YE\u0019DXM1\u0012i\u001aMDQhӺ\"] ڢfoޕuQ*(F@\u001cK#60R\u0005\u001ekW(\u0016PI\t\u0014\u0018MlC`\u0003\u001aU\u001aRc\u0005Tr4\u0003#=FiWk{&\u000f\u0005d\u0013|[\u001a%\u000fکPhw(ܱ\f\u0012@ӍRun/\\!j0M&!lXE&\u0018J\u0004c\u0014aHJQR\u001a3#=\u000b#=O}i]/\u001bU$*\"cWpbhJfǥ2ףCYH\"Zq1m\f[ƼޚL$ĝ\u0010\u0015B\u001cJ]A[le5aU\\h6<ǃyP[70+[2\u0014co\u0001њLXYYZA2#>8O\u0011\b\u001bl\u0006Leo\u0004Q\"!I\f)H\u0018B\u0016\u00120\u000b\u0016Bi=۷\u0003s+\u000fuS!s\f|`[HI\u0001\u0012ĀFn\u0012E7kPbn\u0011N\u000e\u001b\u0006LX\u001dL#oT\b\u0014\\4\"34\t\fd`Q)2!tlׂhe43e0X\u001c0C*ݽ_fݓvtݷ\u0005\u0017\u0006K\u0006\u0002X61rQ9\"\u001d!N0Eo1'*\u001d㎂W\"{\u001c9\u0015L\b\f\u0007O#>b\u001c\u0017\u0005 xN@$ѶAL9\u0019E[)\u0015+\"L@A{x$ \u001f\u0006g\bp#6{/\u0017d\u0007\u000em\u001a\u0016\fr=\u0016I\u0014$x*jș\u0014Nq#G\u0011`\u0002׈W@\u000e\u0006bVQxgG$P\u0003⇿Ʋ2\u0014\\܁VC`UBid\u0003ݬ\u0019g|A15ƌ<SHg&\u0015V\t9լ}0ϊŤp\u0004A?*\u001d&+P8G.\\\u0006Tu{6jUYjJ0za\u001d\u000bW`^Pdae\u0019\u0018\u0016~#6\b\u001d\u000f9$`\u0015޴\u001e\u000eę\u0010\u00129TIC~[\u001f\u0016Y\fj5M^>1﷿c|J+{r\u0014WHv0zm\u0002?O\u001c\u001c6./9L\u0007\u0015\u0002N\u0016UN4\u0002|3B\u0007R}\u0003B/h\u000e=6\u000b\u0012&\u0011]8wDA1$\u001c92\u000e퇌<v~UF+>pI}?iWПf  t\b\u0015v#`q\bWڞ״vp{\u00199s\u001b5O#>}51~,\u0011M!J3+KZ2\b#6\u0014TN\u0001\u0001v9Ʒ\u0007,`:\\`i@<ӽG0\u0003[LO\u000f\u001f z\u0018r9\u00016k\u0006&Z\u0019POK#=>fÎ.Br\f\u0014\u001cH~dt\u0002v\u000e\u0003 _N1_F\u0013AJD2\u0010Xr ;3Dvۙ'\u0006\bb?pG'X$9@\u001c\u0017˅>y~\u0010/\u0018h#6x\u0016k\u000b\u0016bM%@cm\u0014>d\u0012\u001f#=F\f\u001d\u0013\u001er#>#\u001a\u0017۽ U&pϪS\u0018\u001fOV:m|\u001b826j2\u000ebwI\u0001<\u0013#=I::{2#\u0011^uv6s1/>/؟OTV0\u001ar\u0002ݿh~N)\u0013ǡ\u001c3mZV)fpΕSɓ$=T\"$#`65]\u001b#=jaշ,<)ɹ6l`k*BA\u0017pg3\u000b\"j\u0001I85\u0015*%kon\bF]iJ^zfVޣv\u0014I\u0014㢗(\u0012t0nnhB̮\u001b\u001cFqivد#d_#=<9\u0017\u001a|Rv{\\\u0010L,A\u0005\u0016\u000fqSywHƀ4A9;!v񮳵&\u0007\u0013 \u0011e8_#>`\u0015֎\u001fn魛R`d-ʜ\u0007P_\u0017ӎVtG\u0015+\fbGN\u001aEƄE\u00104#;9=6\tZp\u0015o\u000e!ơ]8aIxSN\u0002!I9aP\u0004aB\"BZi\u001aɶȮ\u001c\u001b1y#Aˋ\u0003g\u0018\u0001vjf\u001c>Tia\u000e*xl; W\u0016Mp:r;\":rЭ|y5((EKg\u0014\b6\u001f&\u001eo\u000f\u001c)!6l\u0014'b.#=F\u0013'M5Y\fqevWD7EMp#`gUR\u0005\u0003H\bf\u0018͞V\u0018$&K\u0005`@-fzK\u0011\u0006'bZ?\u0001{;b)\u001c3_,lʋ3V8ld$Eq/#>Ǳ\u0015hs\u0011\u001bt.8Pg\u0003n4\u0016\u0013Tu#=\u00159|Wgl\u0019P>u\\cu<p.\u001b\f`d\\0FF⠖1C\u0018l[NZ8\u001eL\u0013$\u000bÔ\"#>ETwyӘVwLgj\u0016\u0013;u\u001a'Pgc2\u001f\u001b\u000f\u000bb\"G9B%\u0006θC\u001a\u0019\u0014{\\=pY!\u0003\u0012@7@'K^cgC1\f\u001b\u001aćLF&3\u000fJ8\"Ӧ\u001ffYF\u0014TC\u000e1%-NL4w\tAv7\u001d)rW\u0004#6#=o\u000fPd<#6e\fdjض\u0015BI@Є\u0013\u0010\u000bM10cC\u0004\u0017|/\u0011\u0013\u001e<;f[ӳ\u0017ferQ\u001cV\fmdv\u001ad\u001cv4II\u0012#6\u0019}\\G\u001d\u001d!2['\u0017s/5\u0011\u001c>5/k\b\u0018\u0003t^\b\u001cR9\u001cflnxJs>e©f\f˺рQ\u0002{g8+\"EOo\u0001\"ɷ\u000b޽bD֓Mq1˰g\\\u000f,pӢJX[EtUݶb]q>|^X]U.z\u0011\u001e\u000e&6\u0007\u0014糽\u001eI8j]Wq6!9\u001a\u000em\u0003\u001eߗx`2ɘHI$\\Brl\u00035\u0016؁J\u001eo\u00147f~k{D\u0014\u0013\u001d\u0014ڱ\u0003+,̌ޞ\u0002`2He6iBu-q\u0004hT\u001c}[La\u0005\u001c%hs,8EXܞy!V4LbCm\u0004hl\u001cXd,d9ţrX3a)H\u0012\u0001\u0012Д2R\u00100\u0010S\u001cL.m޵osZku2v~z~\u0007\u000eAu#=\u001a9B$t\b\u0015[\f\u0013H#6\u0019VCaaA$\u0012q;S\u0012Žjhehbi3i\u0019\u001aζ<\u0017teky\\R@Ӹf\u0015޴\bqs8zF24R#6v\u0002 L2\u0007\bt}gs\u001d,N\\L\"=Ⴣ7)Ou\u0016!\u000ey\\i\fhJ\u001b#>g\f\u001cՍ5L#*։u$@f)Ҝ,3\u001e\u0013*\"iJU$֊䉬̣ڊ\u001c%b\\)1L\u000fE[5I\u0005\u0013\u0011\u0011+|m\u001crL\u000fFuvec2VH\tuͯ\u0014?\u0019ϘF\u0006\u0016mD\tc\u001dD5#(\u001b..[Ļ\u0015{d\u001e\\Dق\u0016w7v\f&԰B'UH(V+\u0001dNq&+\u0013[#=r:OB yMcp(ŷF(\u0016'yɆ8-\u0006Q٪#9Ʈ)\u0003p\u0004ZY\u0017\u0015\u0016\u000f0$%˞P@\fv~\u0013^SNBQIY\u0013\u0017$T@W*v\u001bPvp\f\u0017tYiC&\u0013A:B~?\u0014o>\u0014i܅PWjN\f\u000f\u0006gۍ\u001cǑxC&c\u001ct\u0014\"Pu\\ˋ%\u0011\u0003IC\u0014f^~\"yX4\\*\u001aRE9'(\u0006B2\u0017#6dvüG!\u001d\u001c#6\bXi\u001aZ6nhzFU4ZT^RMwSJ:zjb)aIF6#=efd7qoT5LC+@^P&Pm+\u0001F\\\u0002≸Ď\u0014G*#>qX\fDݛ\u0018|Q:\fΜPPhή^<\u0005\f\b41\u00151cqa6٦[8L\u0016Lq\u0019&)M\u0019UÞ35C\u001avL4\u0014\u0007#=!K\u0005J0A56 VONq\u0007g%\u0019\u001di\u001a\u0011kU&i``0m\u000bk).1\u001dl:\u001d\u001aӜw\u0015#\u001975Щ\u0018\u000bÉʃ9*ܦIg0\u0012ٛL'TC&vQ1Xqn\u001c1z2$>YxPr-jM)\u0016\u001eR!1t\u001fV\u00122qqe\u00066\"&\f;\u000bKBfxf˅\u001d7:#*\u001cuU\u001a^9\\\u001a\f&m\u000e<YṈQg Jw+U+\u0014K[($Gt;&\")>xc\\\\ɴ$=,kR\u001fu\u0001fuXYv(Xr`wxSj\u001duXָ1\u0011\u001bI]82,\u001cqf\u001e#6M:.x-\u001c\u0019ʊ<\u000f-\fyu,VE#Q\\QC!K\u0019\u0014s\u0018\u000emy6l\u0010ݎU-),zC#=&\u001e#NFh@D&_N/؍\u0011<\u001a<\u0018=o#aݠ\u001c3\u00117\u0003Id.C\u000eM\t_Lo>tSË\t3\u000f#P|\\kTf'%18X\u0007R\u001d^mN\u0011<V0=#>Q\u0003L3!\u0001\f#=\u000eP\u0002h[\u0010U\u001cs'B\u0010MAִM\u0004QQ@R\u0005U&&L@Ľg$#\u0018hB56b:v;넕\\PG1t5\u001c@1C:xlEmcl\u0007)Ж\u001c\u0016Pu#=y\u001ajkM1\u0015#\u0014^p#>Y~11n\u001cs{vb\u0014\u0018!\u001c\tW,\u0016K\bw=[\u0018B\u000e\u000b\u0012\u001au,XNC3]ʠ.#=K֨89\u000fN%8)5|EϺ1},:V̀?:F5G<w/'C#=b5bmzߛ^}}:W.#\t2\u0012Mz4ۍ.\tqC(\u0016T8&\u00117,#Yv1L\b3MTH|kD!\u0002kj9B\u001cx\u001e!\u0014h(u-=\u0019\u0016|8nuX\u0019f1SZX<>ۓ\bC\u001fE~\f\u000esARt=P.׌me\b|\u001aġ!\b&\u0018ˆ\u0001\u0011\u0007G]U\\)rֹ\u0006CF\u001a*<Nƹ!s]s6\f(h\f\u001d\bt/\u0003ǫ2HAPRMB`6\u0013y&ag&\u0005o6\u0002\u001a\u0019C\u001c\u0018hgaƄba\u0013\"߲Ati#=B$V\u000e!G\u00181XSL6v\u001d%\u001cC\u001dGZ#=01Cy\u000eD,=U\u001e\u00171C<'y\fQ4`b%#\u0007t\u001dνj³.gp=\u0010O)yzB2pIq NM\u001c\u0018͊-%J7\u0019%\u0002ا\u0013\u0016!Er`L\u001d\u00018\u000692b#>*\u000f E)\u001b\fE\u000ezA\u0004 $)]8\u0016\u0019\u00189W*,YD3@w 8\u0011T\u0013Le\u0006\u000ea\u0016s\u0001BCm\u0018\"$bi\u0005@:zJ\u0013C#>̲d\":\u0003CnH \bIi\u001b@C&(َ\u0002lp\u001cgV\u0007`m\u0014#=\u0007P\f#6d˽.ð,;ۉX٭\u000fHiH\u0018#=dXT\u001176VoMך\"\u0010\u0014X+a\u000e&0Nx)G.\u001e8#=`\fB0\u0007l\u001a(\u0003[f,IM\u0005iMZ~\u00192e:w͗Nth+l\u0010W&\u00150#%)b\u0001|@ig\u0007I~o>\u0014yPEA0!6<c/,r@a\u0002@\u001adΑ\u001f#=AGxv8~3\u0003c^\u0016ȳ0i;ʽiXɴXJ_<$܇|\u001c\u0019Z\u0014\u0003\tbT\u0017Yt\u000e%\u0001&#6\"\u0004?g3\u0005#6\u0017X\u001e\b_fc\u000f06\u0017c݈j-4U9.b\u000b$\u0002e\u000e\u0018HM'\u0007\u000fYR\u0001#=¢܋!/\u0006\u0001ͨ#6rw;\u00069~l#O\u0010\u001d#\u0018Dtߍ@6\u0006ǃ\u000fΔ~_\u001fo\u001f@79#>A(*/H؃+Hn&2d]pE\u0014B@\u0003_d2\u000f9\u0007r\u00199!=O+1@]#\u0006\b/\u001d0\u001a]b=\u0002Pp\u00077'K\u001bw\u001d*bҁ\u001e*\bb`i\u001a)#>mءh\u0018܆\u000fp2w\u0010D\u001dYMq!X&\u001c\\G\u000e\u0018(\u001c\u0013\b8}x5%NkK!8ċ%\u0015E+X6L\u001eYhQç^\u0014_\u0017mlE.\u0014%4,m\u000bCz\u0014Cma\u0004a\u0010MvG\u00122*\u000b񄝀'dY#6#>~lJ<`\u001dM!v50Roz\u0003E\u000fBE\u0015\u0017Jl'\u0002,V6\u0002CՕ4₃\u0012@^r# >2epF##6`#=7'zo!$\u0011|u(\u001bjfXY Ic\"]\u0018ʮ__^|N8\u00137nwn.ݺe/-[Z4;z*5\u0012L2_\u001dM\fUHR@ǁ!l\u001bLhV#$\u00062\u0010(\u000eT ӄ\u001cmP\u0012P\b\"SDá\u0013\u0017\u00194I#><#6\u0018.\u001eϚu\u000e=T!D\u00175&b<Y8r#>q5̼E\u001c@\u000e*\u001b[Xú5\u0016qӜ&\fT95d8Ѫȵd}ۼ' 7\u00177o´ck6l[\u0018\u0006y\u000f+8k\u0012H(-RF4 h\u0003:ɞOP\bQ\u000fi9 \u001bD\u0014%#=/\"\u0003\u0011͑\u0003\u001e߾1C.\u0015f\u0018 9d\t0\u0003\u0002~\u0005]\u0006)_Jʼ#>|M{ύBsm439)(Re3ИJ鬡|\u00126ޜ8pŪz9\u0014j\u0015`S5#=2\u000ehla#==S[ko\u0011\u0016#>\u0012\"!\u001cdL:\u0005\u0017l\bI#=\u00110fXC,\"E9h<|@l4\u0007zM\u0001<bBx\u0016)ޙ\u0012ډEg%=Q\u0014\u0019Q|))ՍDF\u000e\u001axl 4F|vwC'o^C1/E}ａѠ!$TF܍宕!vgZ>\\$M#='A\u00170p0e9ض\u0006\u0002F4cq\u0011V[Ogx\u0003C-IA@*\u0007nV`q*p\u0002P|ﶵ\u0014W\u0012d2wq?'\u0007\u0013݋·8BqizSd#\u00104C\b跊\u0017~=`\"\u000e{I/~#>}\u0007?\u0019Q?ٓq]z,`#=IiL5lca\u001bj\u001a\"\u001c-np9\u0010J-8v`38#>i(`$jc\u0005<\u000eN\u001cN0>7 P' 3\u0003P;]˒@\u000e$#6i@\"ɢ\u0006Ky1!i4,qa4\u0001Eģ#6J\u0011\u0005#6) 2(bM~g-jKk\u0014\f@Ȕb#6(\u0016)US #=CH8DH\u0002+C!#=T m%\u0006H#6\u0016C\t\u0011|C\b\t\t(\u0014 \u0014R\b\u00141\u0010uj\u0015iE@Q\f]#@#=(\u0003C%\u001f\u0001tH@|@5 z#6\u0006\u0001 \u000e\u0018{\u001a#>X0W\u001chƥ؛\u001d&]beHi\u0003{Hh܃\u0013\u0003b\u000f8F\u001f!\u0019#H\u0012CЅ<O\u0019\u0003P\u000ew4\u001e\u001c+#6\u000e@\u0012C2\u001b!:#6\u001310\u001f&߯,9Dnbig\u0007G))\u0012R!(Z }\u001e\u000f\u001ff\u0018:F_#6\u0015{_\u001a\u0011)\u001eN\u0003g\u0019\u001cϖw7$9\u0010l$=$'c3\bӧ`wd\u0007\\O'x#=\u0015'Jل\u0019\u00019N!Fvo~8w!\u0003rx\u0006\u0006&\"\tyiD=o\u0011unBjL/eCڜ*g\"eC$SŔ\u0014RJ\u0017d<)\u0011Wv=!2#6b\u0019NP\t\u001c\u000b\u000eYH.fE#n!ZEa\u001c\u0014mZ6eqBZ7J\u001cXT.{Kyܯ-\u001a\t\u0010M\u001a&P\\Ė9\\Yju-,4UD (LZv>wyҹS9ڻn0aZ\u0018E\u0018ZX3%\fi9\\\u0004\u0016eDHCC+\"`\u0005\u001dhdY#>a#l<\fr;\u0004K(L\u0003\u001e4;ٖ\bTi\u00144J\t\"M2`\u001aT#=\u0006A\b1GMktۺ;Nrz|Q䃃X9\u0013Na-M7FS+X=m1P\b;J,3,36h\u00019\u0006\u0007r\u001dϘvW\u0005WHF\u0019F:cێf\u0004\u0019<\u0003\u0007]@u\"?ptr6\tAřƲl2&q#>f\u0013\u0001U?i-A\u0019%Q?T%<Nl(2\u0012ǐzӑ!<=[s|22\u001cb@6\\\u0003B4fL0L\u0016ݚБojZ1\u0012J(%\"\u0003H:\tS$\u001a@i#6\\i\u0012Y,W9FՊ s\u0003R}5\u000e\u0016#=\u000eL\u00019\bQ\"s#=p̎1]Z8@(8\u0012Q0,5U\u0011߭r\u0018\u001d~%u,v\u0018\u0003!$\f1#>5\u001fwn\u0017.|@^ \u001fH5}\u0004\u0010{!=I2Srϗ/VLq~9\u0005UzIՐH7\u001bh( \u0011BYkr(\u0014'è-'\u0013vPf\u0019{Se#=&S/#=_ 5\u0018p\u0010:N\u0010Z7}'Ƞ\u001eg{\tl#6\u001a>ؠԺ\u0014ɠ\u0003o@D\u0002Шn\u0007&(s9\u000bJzVs\\̑i4ؒ i#>\f\"©a\f\u000e~vK\"a2]\u000bU\u001e\u0010O=`'~_\u0010\u001d\u000fP@Mv&\u0018\u0010~F\u001fjt[lͦU&eLS\u0018,\u0018MY\u0013R\u001a*U*kmk^r\u0006\fa|;́i\u001aty\fHuiSC*'̇H~po]qǯ\u0005;$(\u0007B\u0003\u000e\u001f_~כ\u0012kJX5Q\u0014(_\u001b~#󝾒\u0007ro\u0010\u001c|~!\u0007x\u0013М\u0010nE? \u000f~퀏2@G܉8\u001aN\u001f\u001d|Q#c=nb#\u0015UX<q\u0016kUdGy\u0013㋙7&\u0018ԿoC#=6_9>U\u00133#\u000f.΂\u0019t\u0011vBj\u0010+D3\fM%4ʂ#=sC\u0003c,['Xp\u001b$ON'\u0013$!!Dzi\u0011S\u0012&#6û<P\u0017JPTK\u0004#>Wx\u001ei*L\u0012\bE(&Ȏ\u001a9FY#kinw%UST1]\"!c\u0011unur^*\u0006jX\u000báK92#6DIFsO\u0006>AR=:d\u0015\u000e^\u001d;*ym\u000f<*2!<<[q1I\u00062df;\u0013t\u0001хY\u001c௘5vb>\u0015,?/HFγ[sZv\u0013\u0014bL\u0018\u001c\u000f\u0018f\u0011\u0015=hU~,m3i#6I5\u0006#>kt:Mݷ\u001d\u001b\u0017$i\u0004Ԁa'j٘Hf\bs1(*t.H\u001f#>#=w<%\u0003XE\t\u000f\tJNZI|\tԛXq:ăS\u0018oJ&\u0006\u001d\u000221b0\u000e8#\u0018Jw{GG4|>\u0013ǩ}xk-)@\u001a,H\u0002%\u0013\b :ƈAÐ_ExQD92\f\u0013#6\u0012'\u0010IϨ\u001f\u001e{}̄S\u0014Q$Q\u000fuTd=D)YJإ!#[rضԆXRX\u00033\u0005r_T\u001ed\u000e/(ER%@GB!xv\t4~av/0\u0001}\u000e#6#`\u001f\u000fD#>2  cDS/3\fz6VPyX{3\f`łX\u0018\u001c!2L,\u0012T\u001ff\u001d}9\u001c\u0019yh\b^@\u0004\fUHHS]CM\u001c.8*1̋\u001cGb26f~\u0001^!P( I#=`E\u001cP!۳}?w'\u0016L\u0018K2\u0001e@w}>^A|}\u0007GN@kJyY\u000b H\"0=DK\u001b\u0002>H`f\u001f\bfV2\u00079WA= #B\u0019uLU\u0013(7C~+BU!\u0013#=\u0002\u0015)\u0013\u000f9h\u0001\u000eM\u0002tH\u0013'#>0+AR\u0007#6\u0002P\u000f~C$Q]IGۓ`c\u0019;u\u0019\u000eR`dMD2\u000e0zYJb$Ǧ\u0011y#J`\\}7[wFW\fXeC&i-6%\\Q4H٫\u0016q0XUmR\u0014HP)3\u0004(\u001fHT=_\u001c+DM\u0017G\u0012G0>@=qO_\bA_l|<\f2\u001fb&#\u000e\u0015\u001fdx\"\u001a=\b$\u000e\u0006f=\u0006\u0011Qh35\u001am\u0016l(B%UBd\"$DR\u001dl#6|\\\u0007\u001fh\u0019\u0019a4ڪجQ,**f\u0015\u0015Tm,dT\" %0Ƒ2:\u0012{\u0010?q(\"`64\u0014lfhIbdڍ\u0015$\u001a\u0011R&@aJ\u0007\u0005Mib\u0004\u001aX_}_*Ti\"\u0006@DX%#O!|aPqv\bI_6+J<p$h%4ܿ{75%gɳI\u0015(#=d-Dlhe+%\u00142RIS\u001buWDJB\fQI|>SP`<.j\u0003h#6 \u0017\u0013\u0005}ҝ#\u0005v\u0011X\u0015 }Ļ\u001dٯ{Q\u001c}B)4qNK\u0011`\u001b@rhH2\u0010cW\u0016s\f:0*rDޝCFøu-#>'})&c#6vzW=A.~U\u0007\u000e\b{\u0004z%\fCy#>S%0R#>\u0004hb+BQ$huԜ\u0019<88ԿN4v\u001a\u0006\u00010H#\u0011S6\u0019\t-#2*%9uN(w!a1``~X\u0016OA\u0005F\u0003Ha\u0007`\u001f5\u0004\u0010,\u00122\tAM\b{\u0015#6\u0017J>\u000f8U \u0001K\u0004\u0011֥k+^zk\b̳1hXc$ҨJ\u00032T°XD\u001asDKF5\u0001#6#6#J\u0010=2\u000ej(!h@|#6;\t(\u0006D2D(DҬ\u000fz\t(}DiI$z$Ó\u001b#=\u0001|S\u001e-)>\t3\u000f)=Aϔ{-eoaO9\u001f\u000b%b(pa@q!\\\u001d\u0004\u0014\u0001x\u000eg[t\u001a \u001dIn\u0010ՙef\u0011)ݚhʖPHd%\u0006LKrIh7\u0017lN_\\\bC\u000eL\u0013r\u000b\"2\u0013XC\u000f\u0015G>&LJ\u001f\u001aS\u000e\u000e\u0005\u0018y/A\u0007p?Z\u0006 v\u000e>\u0014\u001f#>\u001f(\u0018V\u0006DC!}!#6\u0007\u001e\u000fL\u0012K11ڏԶ*7m\u001aX\u0012ήͫۗ^r*$\u0011\u0010LB\tƣd!!\u0018t;=<i.4j$i)\u0015\u0014nlh5/wMU+\u001bU\u0004z1\\\u0001\u0010A0ю#^oa#>1\u001d!b1Uۭ#6R/8ŎDQ}0,&#=#=i$L;(I\ffAX\u001d$!2\u0006$NLAJ\u001b}lAn &qR\bϩ̱ES\u0001{!\u0010_\u0007\u001dxG|\u0019s\u0012suN\\%X\u0010Gvm'?ʇ\thU=5\bB\"\u0001BS4[Ѷ\u000bYB\u0011*Э\"2@?۳W{<!<ތή5tP\\*a(zN\u0007\u0002#=1\u0018PTcNm\u0006\tF\"Y?vf\u0015gC*0QE.]ɘg[h3AX\b\u001ch.7\f1D\u001cʩ-ϵ\u0016h1q&\u0010$~\u0003iF~-J\u0019\u001b\\{Щ/fSUXl]\u0014+JJXj46\u0014rCԁpi\u0005^*r\u001b\\jP#\u0015Jx%]OT[`'\u00150\b\u001aR$(C'\u0014\u001c0fq1\fP3\u0010%Y\u0016j]J\u0012jn֒IQ4Tu]uiuRt\b0Ǳ*p\u0013jB8ZQ`a\u0003\t\u0001\u001d@@DԴ\u00140\u0001+\u0004:s,ov]%kXr<\u001ah\f) <C\u001a\u00024C\u0005\u0019\u0003I\u000f\be\u001144R,P\u0003Q{\u0010&#=&\u0002\u0011px `\u0006I)3G\u0003\u0002q\u000f\u0002OHqu'r\u001f\"\u001f3-\u0007:F\u0007R0m Wb?2yJªX0\u0005C\u001c7(x3\u001aIqG\u0010ʯO8i<S+\u0018`ׯN\u0010\u0010bK\\-f~\u000f>ө\u0018P$\"UW\u001939Xq\u0002h1-#\u0012`qo\u0010\u0010s#= \u000f\u0005I0LI\u001c\u001bC0@\u0018\u001c?3:VBtds\u000f\\7ǪZ|\u001dDh>t̟r\u001fs'8m#>\u0005Dc\u0016\u0017}wi)~}kB\u0001J\u001e6D?i>R,&'\u000exw\u001b0I\u001d>,I~w|{\u0013'ABTXO|Qab`߫$$`j-K`&Ұ~\u001e1\u001cj%:\u0003ҤF\u000b\u0001\u0012p\u0001'vv7\u0012'B\u0014MN&bGMyx\u001f\u0019pɥNJb1E\u0002\u0010\t#\u0016\u0003$&l+Rҍdb{41;#NIw}:mE-44\t\u0012҉\u0010A\u0004PjG\u000e\u0002Z\u0003j#>$HR#\u0003G\u001f3\u000e#=(\u001aé\u0013\u0011\f2>pk<J\u000e~,0÷\u000f\\%ݗV\u0006Ǯ\u0016\u0005\u0003aUK4xl\u0004:@\u0016'uZ(gZ\u0002 \u000e\u000fbuZ\u0003P֞=0xQDRS#=P-6AA`\"O\u0006x/\u0019=n>5{\b3Wjku\u0002pav1\u001eN\u0018'Z#=\u0004qo\u001bRǍ\u001bZh!\u001aJD\f\u0007\u0005T\\-\"n\\$Yv\u0007\u0005XZQo\fA\u0005d\u0019=%#>y[V\u0002P[1\u0004\u001e\u0013\u0014zF=>_U2a\u000fC~>K,\tn\u00131oF\u00020\\Đ#=#6\u0018\u0003`cd9zzz2RF#\u00023\u0019~iiW`\f\u000f̢e\u000eB~\u0010x\u001d%E\u000b,\bRe'u0Ml\u001d\f\u001c<\u000ec\u001cJ`2'G\u001dGa\u0018Ͼ\u0015[7*WCNoozJ\u001a\u0017F?q\"\t1$Qui9fvLî\u001aO\u001a.b4R\u000e\u0012r\u0006T\u001e|\u001f\u0011N\u001d5׀\"\u0006 tIL\u0006!}\u0005\u001fX\f+\u000fb\u0002jb\u0015m(2z|\u0017(F|\u001c1#64)uJ2d#6n C\u0019gѽ\u0018i\u0001q\u001f#=\bF<#6\u001f\u0019EVVT$8B\u00060\u0016ۨ\u0012%u#>~\u0018b\u0001!\u001f-\b4\u00102L\u0019ۤ0T)S_[_&ؓTR\u001fm(\u000e9g=+\u0006b\u0010K͖#Ics#6J\"s3_/\u0007|#>Lgx`#Cf}#!@<\u0017\u0002wfp?'*CP\u001a\u0007\u0003R@uO5Э\u0018-i 8$9@\u0007(DѴi]Cj\bL \u000f\u000fI\u001d*:H9>#=?w;&\u0019\u0004ڭh0\u0007W_׊\u0005\u001c#=zU\t˙\u001d3)JO\u0007ُûy<\u000f9\u0001wH\u001a$QDyN\u000eg)\u0016pg{\\\"h6ㇱ#=/\u001e\u0019a!|<O\u001c\\ G\u0005#>3%2\u0015Fx\u0006tR\u001dQe\u0014Qa!܀(a\u0018cT\u0010:@U<+\u0010ɫ߾Rb8'=\u001dU#6\u0017\u0018hN\u0011ωq,JE1V\"x)UT\u0013\u00073S\t\f2yM\"r =CO]Cb\u001ez()cJ͑oڽ\u0004p\u001e܃cjnHT&ǚ\u0013N\u0014ߚ8Ll\u001b\u001b\u0016b{8\u000eP(Jzrӗ\u001c\\2mɀR81,`kF\u000e̱(gu\tHug]f\u001a\\l\u0006/QT#>ϛ\u0015M]96쬶\tX\u000f\fxI\u0018\u0010\u000e\u000fhSUu[Ǭ8k\u0013\u001f9  q\f^)\u000e*F\b(`\u000522ΰbn\u0002Y\u0015tscMr4lrjVA\u00154\u0004&6Bᵃ˶[ L=aBr\u001b%!\u001aԃ\u000bବ`Nd҇E\u0016A#=LŐ<ER@eZE=Q-\u00174\u001d\u000e\b`3\u001alOx\u0004!g\fQq\u0003*ZbE{f\u0003\u0006qmǧ̵C`#6g0\u00188;=|!_ᾏL\u001c(qdb$E!jkW'va$\u0004#&^\u0005!Ӧ U*#>J,C\u000f\u001e\u0013\\nt_[\u001fz)\u0012<3@\u0002!39eCľ$ϙEm7\u0017\u0003[&\u0016QѢof%G\u0001/\u0006\tO>\u001c\f(qȊ-@\u0014h\u0018rTzu\u0012\u0016jDHYE#;fã̼33\u0011>0!CO?a1\u0005vJT#6a\\wӡ@A\u0004a&lFٲUm0?/+2\u0019St\u0002'\"\u001bYsĆ0\u001c H\u0003Xoq\u0007bXEtGTӝ~fPm)#=0eh۩gO]@P(\u0010HE24}\t\u000f8E=w}z#=y{\t[\u0019Ld+*-754`ԀœD\u000f&VMPZj*bifJHAb\"Ϟ\u0011#>4%\u0003 \u0014\u0013a\tB\u000f\u000fXC\tN/B25kF\u001e!I\t1#=E\u0002q}m%p\u0016B8\u0001\u001cAA\u0018/#6X\u001ao<LakXL\u0012!\u001dun49k?zkmcgΙ\u000fS\\#o%j5X,4`\u0003]9.nޥdC#=A\u0004+~\u0017,lz\u001bz\u0006jo:`qN\u0018ʤvFpM\u001cwŌ7\bFG\u0015F#6&[rf\u001a4Q.k8|͛Cj5\u0018KSI#=#1\u000b9}_*Q2CLp%'n\u0018*BLj]K\u0012g\u0019\u0019I%d~@+\u0013+#6BVfby+*ӞqH<q\u001b\u0018[3(+H\u001c5ވ\u0011O\f%|c\u0015\u0017d\u000e枣 U$94t\\oȋ\u0017\u0004j!-\"S^\u0013).\u0012\u0015<b~3<0i\u0006yk\u001fP&RtK@#UBY$W3;Rlw3[co\u000e\u001c\u001f\\7\u001c}0pi2L>I\u0011\tE\u0011LA\u0005$e#>4Cm1UV\u001dfWɥU5\u0011@YB<5ve9:pV\baN8\u00112\u001cTd\u0015xq\u0019/d9\u0014Zn\u0011_G6\u000e6\\!\u001e\u001d.2XWghL4 5\u001azp#=\u0014,sXpzZ/Ge\b{\tӏ\u000e$΍3!toCU-\u0014\u0003\u00133\fk\u00180US\u0010)\u001d6D닳W\u0003d\u0001aC\u000fk\u001fwcȎ7Ħ\u0012!!\u0016rJ5\u000b\u0014*9w_(5LaDW=-ϸkQz{jv#>\f$\u0016-٣^Zɂ*(<\u0014^k\\SBnPtCB\t\u0012%R\u0010:\u000e\b\f\u0006\b^&f\u001c6#> jF,O2S\u001e\"\t,aP\u0004?C\u0001!޹\u0003F8|\u001f\u0007}#>p\u0010\u0018#6_ejd3\u0012݂gL\u001eGM\u001dxtĢ>=Iϛ8}!p2\u001d\b^\u001e\u001b\u0003Ơ\u0002zȒJM@5?\u000b*\u0013ށ_\u0012Js\u001f\u00071\u0013QB!\u000fP'\u0013 _'{አ%(\f\u0004\u00100Bx&\u0014>]Ԏb\u0012ٔ\u0015)ȣ0#2I8ҡo@70\u001f9#6#>bY`)]Gӏ@\u0001u7h@~HH*bK\u001aXJ\u001e-#>уA\u0012\u0011^&J>oާ\u00138C4'#>\b}p\t#6\u0001@#>\u0007_۴<f\u000e<EZ\f\u0019|QrUJR\u0010?Oy|p|΀Ѹ0h=~\u000f_#6\u0007H\u0007\u0003B\u0014'[zA<s\u0001iG1JGoJA\u000fR\u001e+?b/S\baP?\b;1A\u0012Ԣ\u0013?Wt_g\u000b>Z\u0006TP/|{,K Poޝ+{\\\u001e觎1\u0018S>Q\t\u0006Od\u0018\u0001ë\u000fX?0\t\u000e3J^1\u001b_Rv@D\u0005[W \u0016f\u0005yd\u001a\t2\u000fB\u0006V~ER\u001e/%\f\u0010\u0013\u0010ו.IO\bRk8\u001d+Q\u0014\tY\u001aw\u001f\u001fsm\u00103}\u000b?˶V2C\u0017D׫\bJ,\u00113Wܔ\u001d6G`ϟlE\f1ܻ\u001e\u001dЫ@$5\tBA\u0016QGʈ\u0005\u0003hw\b}SG!ׯ4Οx]bB^?+\fn\\y\u001d\u0017^@\u0017{{һ8=d\u0019:G*` ;\u00047m \\ouU\u000e\u0006d9\u0012b\u0011? ;A1=?81\u0005@_HfL`O\t,=\u001e?#=#6-\u00159R\u00175\u0007j8 KH\u0010\"(H%lK#6\n#<==\n"
  },
  {
    "path": "waf.bat",
    "content": "@echo off\r\n\r\nrem try fix py2 build\r\nchcp 65001\r\nset PYTHONIOENCODING=UTF-8\r\nrem from issue #964\r\n\r\nSetlocal EnableDelayedExpansion\r\n\r\nrem Check Windows Version\r\nset TOKEN=tokens=2*\r\nver | findstr /i \"5\\.0\\.\" > nul\r\nif %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3*\r\nver | findstr /i \"5\\.1\\.\" > nul\r\nif %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3*\r\nver | findstr /i \"5\\.2\\.\" > nul\r\nif %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3*\r\n\r\nrem Start calculating PYTHON and PYTHON_DIR\r\nset PYTHON=\r\nset PYTHON_DIR=\r\n\r\nSetlocal EnableDelayedExpansion\r\n\r\nset PYTHON_DIR_OK=FALSE\r\nset REGPATH=\r\n\r\nfor %%i in (3.13 3.12 3.11 3.10 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7) do (\r\nfor %%j in (HKCU HKLM) do (\r\nfor %%k in (SOFTWARE\\Wow6432Node SOFTWARE) do (\r\nfor %%l in (Python\\PythonCore IronPython) do (\r\nset REG_PYTHON_EXE=python.exe\r\nif \"%%l\"==\"IronPython\" (\r\nset REG_PYTHON_EXE=ipy.exe\r\n)\r\n\r\n@echo on\r\n\r\nset REGPATH=%%j\\%%k\\%%l\\%%i\\InstallPath\r\nrem @echo Regpath !REGPATH!\r\nREG QUERY \"!REGPATH!\" /ve 1>nul 2>nul\r\nif !ERRORLEVEL! equ 0 (\r\n  for /F \"%TOKEN% delims=\t \" %%A IN ('REG QUERY \"!REGPATH!\" /ve') do @set REG_PYTHON_DIR=%%B\r\n  if exist !REG_PYTHON_DIR!  (\r\n    IF NOT \"!REG_PYTHON_DIR:~-1!\"==\"\\\" SET REG_PYTHON_DIR=!REG_PYTHON_DIR!\\\r\n    set REG_PYTHON=!REG_PYTHON_DIR!!REG_PYTHON_EXE!\r\n    rem set PYTHON_DIR_OK=TRUE\r\n    if \"!PYTHON_DIR_OK!\"==\"FALSE\" (\r\n      set PYTHON_DIR=!REG_PYTHON_DIR!\r\n      set PYTHON=!REG_PYTHON!\r\n      set PYTHON_DIR_OK=TRUE\r\n    )\r\n\r\n    rem set PYTHON_DIR_OK=FALSE\r\n    rem @echo Find !REG_PYTHON!\r\n    rem goto finished\r\n  )\r\n)\r\n\r\necho off\r\n\r\n)\r\nrem for l\r\n)\r\nrem for k\r\n)\r\nrem for j\r\n)\r\nrem for i\r\n\r\n\r\n\r\n:finished\r\n\r\nEndlocal & SET PYTHON_DIR=%PYTHON_DIR% & SET PYTHON=%PYTHON%\r\n\r\nif \"%PYTHON_DIR%\" == \"\" (\r\nrem @echo No Python dir\r\nset PYTHON=python\r\ngoto running\r\n)\r\n\r\nrem @echo %PYTHON_DIR%\r\n\r\nif \"%PYTHON%\" == \"\" (\r\nrem @echo No Python\r\nset PYTHON=py\r\ngoto running\r\n)\r\n\r\n:running\r\n\r\n@echo Using %PYTHON%\r\n\r\n\"%PYTHON%\" -x \"%~dp0waf\" %* \r\nEndlocal\r\nexit /b %ERRORLEVEL%\r\n"
  },
  {
    "path": "wscript",
    "content": "#! /usr/bin/env python\n# encoding: utf-8\n# a1batross, mittorn, 2018\n\nfrom waflib import Build, Context, Logs, TaskGen\nfrom waflib.Tools import waf_unit_test, c_tests\nimport sys\nimport os\n\nVERSION = '0.99'\nAPPNAME = 'xash3d-fwgs'\ntop = '.'\ndefault_prefix = '/' # Waf uses it to set default prefix\n\nContext.Context.line_just = 55 # should fit for everything on 80x26\n\nc_tests.LARGE_FRAGMENT='''#include <unistd.h>\nint check[sizeof(off_t) >= 8 ? 1 : -1]; int main(void) { return 0; }'''\n\n@TaskGen.feature('cshlib', 'cxxshlib', 'fcshlib')\n@TaskGen.before_method('apply_implib')\ndef remove_implib_install(self):\n\tif not getattr(self, 'install_path_implib', None):\n\t\tself.install_path_implib = None\n\n@TaskGen.feature('cprogram', 'cxxprogram')\n@TaskGen.before_method('apply_flags_msvc')\ndef apply_subsystem_msvc(self):\n\tif getattr(self, 'subsystem', None):\n\t\treturn # have custom subsystem\n\n\tif 'test' in self.features:\n\t\tself.subsystem = self.env.CONSOLE_SUBSYSTEM\n\nclass Subproject:\n\tdef __init__(self, name, fnFilter = None):\n\t\tself.name = name\n\t\tself.fnFilter = fnFilter\n\n\tdef is_exists(self, ctx):\n\t\treturn ctx.path.find_node(self.name + '/wscript')\n\n\tdef is_enabled(self, ctx):\n\t\tif not self.is_exists(ctx):\n\t\t\treturn False\n\n\t\tif self.fnFilter:\n\t\t\treturn self.fnFilter(ctx)\n\n\t\treturn True\n\nclass RefDll:\n\tdef __init__(self, name, default, key = None):\n\t\tself.name = name\n\t\tself.default = default\n\t\tself.dest = key if key else name.upper()\n\n\tdef register_option(self, opt):\n\t\tkw = dict()\n\t\tif self.default:\n\t\t\tact = 'disable'\n\t\t\tkw['action'] = 'store_false'\n\t\telse:\n\t\t\tact = 'enable'\n\t\t\tkw['action'] = 'store_true'\n\n\t\tkey = '--%s-%s' % (act, self.name)\n\n\t\tkw['dest'] = self.dest\n\t\tkw['default'] = self.default\n\t\tkw['help'] = '%s %s renderer [default: %%(default)s]' % (act, self.name)\n\n\t\topt.add_option(key, **kw)\n\n\tdef register_env(self, env, opts, force):\n\t\tenv[self.dest] = force or opts.__dict__[self.dest]\n\n\tdef register_define(self, conf):\n\t\tconf.define_cond('XASH_REF_%s_ENABLED' % self.dest, conf.env[self.dest])\n\nSUBDIRS = [\n\t# always configured and built\n\tSubproject('public'),\n\tSubproject('filesystem'),\n\tSubproject('stub/server'),\n\tSubproject('dllemu'),\n\tSubproject('3rdparty/libbacktrace'),\n\n\t# disable only by engine feature, makes no sense to even parse subprojects in dedicated mode\n\tSubproject('3rdparty/extras',       lambda x: x.env.CLIENT and x.env.DEST_OS != 'android'),\n\tSubproject('3rdparty/nanogl',       lambda x: x.env.CLIENT and x.env.NANOGL),\n\tSubproject('3rdparty/gl-wes-v2',    lambda x: x.env.CLIENT and x.env.GLWES),\n\tSubproject('3rdparty/gl4es',        lambda x: x.env.CLIENT and x.env.GL4ES),\n\tSubproject('ref/gl',                lambda x: x.env.CLIENT and (x.env.GL or x.env.NANOGL or x.env.GLWES or x.env.GL4ES or x.env.GLES3COMPAT)),\n\tSubproject('ref/soft',              lambda x: x.env.CLIENT and x.env.SOFT),\n\tSubproject('ref/vk',                lambda x: x.env.CLIENT and x.env.VK),\n\tSubproject('ref/null',              lambda x: x.env.CLIENT and x.env.NULL),\n\tSubproject('3rdparty/bzip2',        lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_BZ2),\n\tSubproject('3rdparty/opus',         lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OPUS),\n\tSubproject('3rdparty/libogg',       lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OGG),\n\tSubproject('3rdparty/vorbis',       lambda x: x.env.CLIENT and (not x.env.HAVE_SYSTEM_VORBIS or not x.env.HAVE_SYSTEM_VORBISFILE)),\n\tSubproject('3rdparty/opusfile',     lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OPUSFILE),\n\tSubproject('3rdparty/maintui',      lambda x: x.env.CLIENT and x.env.TUI),\n\tSubproject('3rdparty/mainui',       lambda x: x.env.CLIENT),\n\tSubproject('3rdparty/vgui_support', lambda x: x.env.CLIENT),\n\tSubproject('3rdparty/MultiEmulator',lambda x: x.env.CLIENT),\n#\tSubproject('3rdparty/freevgui',     lambda x: x.env.CLIENT),\n\tSubproject('stub/client',           lambda x: x.env.CLIENT),\n\tSubproject('game_launch',           lambda x: x.env.LAUNCHER),\n\tSubproject('engine'), # keep latest for static linking\n\n\t# enabled optionally\n\tSubproject('utils/mdldec',     lambda x: x.env.ENABLE_UTILS),\n\tSubproject('utils/xar',        lambda x: x.env.ENABLE_UTILS and x.env.ENABLE_XAR),\n\tSubproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER),\n\n\t# enabled on PSVita only\n\tSubproject('ref/gl/vgl_shim',   lambda x: x.env.DEST_OS == 'psvita'),\n]\n\nREFDLLS = [\n\tRefDll('soft', True),\n\tRefDll('gl', True),\n\tRefDll('vk', True),\n\tRefDll('gles1', False, 'NANOGL'),\n\tRefDll('gles2', False, 'GLWES'),\n\tRefDll('gl4es', False),\n\tRefDll('gles3compat', False, 'GLES3COMPAT'),\n\tRefDll('null', False),\n]\n\ndef options(opt):\n\topt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject ninja')\n\n\tgrp = opt.add_option_group('Common options')\n\n\tgrp.add_option('-d', '--dedicated', action = 'store_true', dest = 'DEDICATED', default = False,\n\t\thelp = 'only build Xash Dedicated Server [default: %(default)s]')\n\n\tgrp.add_option('--enable-dedicated', action = 'store_true', dest = 'ENABLE_DEDICATED', default = False,\n\t\thelp = 'enable building Xash Dedicated Server alongside client [default: %(default)s]')\n\n\tgrp.add_option('--enable-tui', action = 'store_true', dest = 'ENABLE_TUI', default = False,\n\t\thelp = 'enable TUI main menu [default: %(default)s]')\n\n\tgrp.add_option('--gamedir', action = 'store', dest = 'GAMEDIR', default = 'valve',\n\t\thelp = 'engine default (base) game directory [default: %(default)s]')\n\n\tgrp.add_option('-8', '--64bits', action = 'store_true', dest = 'ALLOW64', default = False,\n\t\thelp = 'allow targetting 64-bit engine(Linux/Windows only) [default: %(default)s]')\n\n\tgrp.add_option('-4', '--32bits', action = 'store_true', dest = 'FORCE32', default = False,\n\t\thelp = 'force targetting 32-bit engine, usually unneeded [default: %(default)s]')\n\n\tgrp.add_option('-P', '--enable-packaging', action = 'store_true', dest = 'PACKAGING', default = False,\n\t\thelp = 'respect prefix option, useful for packaging for various operating systems [default: %(default)s]')\n\n\tgrp.add_option('--enable-bundled-deps', action = 'store_true', dest = 'BUILD_BUNDLED_DEPS', default = False,\n\t\thelp = 'prefer to build bundled dependencies (like opus) instead of relying on system provided')\n\n\tgrp.add_option('--enable-hl25-extended-structs', action = 'store_true', dest = 'SUPPORT_HL25_EXTENDED_STRUCTS', default = False,\n\t\thelp = 'build engine and renderers with HL25 extended structs compatibility (might be required for some mods) [default: %(default)s]')\n\n\tgrp.add_option('--low-memory-mode', action = 'store', dest = 'LOW_MEMORY', default = 0, type = int,\n\t\thelp = 'enable low memory mode (only for devices have <128 ram)')\n\n\tgrp.add_option('--disable-werror', action = 'store_true', dest = 'DISABLE_WERROR', default = False,\n\t\thelp = 'disable compilation abort on warning')\n\n\tgrp.add_option('--enable-tests', action = 'store_true', dest = 'TESTS', default = False,\n\t\thelp = 'enable building standalone tests (does not enable engine tests!) [default: %(default)s]')\n\n\t# a1ba: special option for me\n\tgrp.add_option('--debug-all-servers', action='store_true', dest='ALL_SERVERS', default=False, help='')\n\tgrp.add_option('--enable-msvcdeps', action='store_true', dest='MSVCDEPS', default=False, help='')\n\tgrp.add_option('--enable-wafcache', action='store_true', dest='WAFCACHE', default=False, help='')\n\n\tgrp = opt.add_option_group('Renderers options')\n\n\tgrp.add_option('--enable-all-renderers', action='store_true', dest='ALL_RENDERERS', default=False,\n\t\thelp = 'enable all renderers supported by Xash3D FWGS [default: %(default)s]')\n\n\tfor dll in REFDLLS:\n\t\tdll.register_option(grp)\n\n\tgrp = opt.add_option_group('Utilities options')\n\n\tgrp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False,\n\t\thelp = 'enable building various development utilities [default: %(default)s]')\n\n\tgrp.add_option('--enable-xar', action = 'store_true', dest = 'ENABLE_XAR', default = False,\n\t\thelp = 'enable building Xash ARchiver (experimental) [default: %(default)s]')\n\n\tgrp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False,\n\t\thelp = 'enable building libFuzzer runner [default: %(default)s]' )\n\n\tfor i in SUBDIRS:\n\t\tif not i.is_exists(opt):\n\t\t\tcontinue\n\n\t\topt.add_subproject(i.name)\n\ndef configure(conf):\n\tconf.load('fwgslib reconfigure compiler_optimizations')\n\tif conf.options.ALLOW64:\n\t\tconf.env.MSVC_TARGETS = ['x64']\n\telif sys.maxsize > 2 ** 32 and not conf.options.MSVC_WINE:\n\t\tconf.env.MSVC_TARGETS = ['amd64_x86', 'x86']\n\telse:\n\t\tconf.env.MSVC_TARGETS = ['x86']\n\n\t# Load compilers early\n\tconf.load('xshlib xcompile compiler_c compiler_cxx')\n\n\tif not conf.options.WAFCACHE:\n\t\tconf.load('gccdeps')\n\n\t\tif conf.options.MSVCDEPS:\n\t\t\tconf.load('msvcdeps')\n\n\tconf.env.WAFCACHE = conf.options.WAFCACHE\n\n\tif conf.options.NSWITCH:\n\t\tconf.load('nswitch')\n\n\tif conf.options.PSVITA:\n\t\tconf.load('psvita')\n\n\t# HACKHACK: override msvc DEST_CPU value by something that we understand\n\tif conf.env.DEST_CPU == 'amd64':\n\t\tconf.env.DEST_CPU = 'x86_64'\n\n\tif conf.env.COMPILER_CC == 'msvc':\n\t\tconf.load('msvc_pdb')\n\n\tconf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja')\n\n\tconf.env.MSVC_SUBSYSTEM = 'WINDOWS'\n\tconf.env.CONSOLE_SUBSYSTEM = 'CONSOLE'\n\n\t# Windows XP compatibility\n\tif conf.env.MSVC_TARGETS[0] == 'amd64_x86' or conf.env.MSVC_TARGETS[0] == 'x86':\n\t\tconf.env.MSVC_SUBSYSTEM += ',5.01'\n\t\tconf.env.CONSOLE_SUBSYSTEM += ',5.01'\n\n\t# Set default options for some platforms\n\tif conf.env.DEST_OS == 'android':\n\t\tconf.options.NANOGL           = True\n\t\tconf.options.GLWES            = False # deprecated\n\t\tconf.options.GL4ES            = True\n\t\tconf.options.GLES3COMPAT      = True\n\t\tconf.options.GL               = False\n\telif conf.env.MAGX:\n\t\tconf.options.SDL12            = True\n\t\tconf.options.GL               = False\n\t\tconf.options.VK               = False\n\t\tconf.options.LOW_MEMORY       = 1\n\t\tenforce_pic = False\n\telif conf.env.DEST_OS == 'emscripten':\n\t\tconf.options.BUILD_BUNDLED_DEPS = True\n\t\tconf.options.GLES3COMPAT      = True\n\t\tconf.options.GL               = False\n\t\tconf.options.VK               = False\n\n\t# psvita needs -fPIC set manually and static builds are incompatible with -fPIC\n\tenforce_pic = conf.env.DEST_OS != 'psvita' and not conf.env.STATIC_LINKING\n\tconf.check_pic(enforce_pic)\n\n\t# NOTE: We restrict 64-bit builds ONLY for Win/Linux running on Intel architecture\n\t# Because compatibility with original GoldSrc\n\t# NOTE: Since modern OSX (since Catalina) don't support 32-bit applications, there is no point\n\t# to restrict them to 32-bit engine, despite GoldSrc is still officially supported.\n\t# There is now `-4` (or `--32bits`) configure flag for those\n\t# who want to specifically build engine for 32-bit\n\tif conf.env.DEST_OS in ['win32', 'linux'] and conf.env.DEST_CPU == 'x86_64':\n\t\tforce_32bit = not conf.options.ALLOW64\n\telse:\n\t\tforce_32bit = conf.options.FORCE32\n\n\tif force_32bit:\n\t\tconf.force_32bit()\n\n\tcflags, linkflags = conf.get_optimization_flags()\n\tcxxflags = list(cflags) # optimization flags are common between C and C++ but we need a copy\n\n\t# on the Switch, allow undefined symbols by default, which is needed for libsolder to work\n\t# we'll specifically disallow them for the engine executable\n\t# additionally, shared libs are linked without standard libs, we'll add those back in the engine wscript\n\tif conf.env.DEST_OS == 'nswitch':\n\t\tlinkflags.remove('-Wl,--no-undefined')\n\t\tconf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-nostartfiles'])\n\t\tconf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-nostartfiles'])\n\t# same on the vita\n\telif conf.env.DEST_OS == 'psvita':\n\t\tconf.env.append_unique('CFLAGS_cshlib', ['-fPIC'])\n\t\tconf.env.append_unique('CXXFLAGS_cxxshlib', ['-fPIC', '-fno-use-cxa-atexit'])\n\t\tconf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all'])\n\t\tconf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all'])\n\t# check if we need to use irix linkflags\n\telif conf.env.DEST_OS == 'irix' and conf.env.COMPILER_CC == 'gcc':\n\t\tlinkflags.remove('-Wl,--no-undefined')\n\t\tlinkflags.append('-Wl,--unresolved-symbols=ignore-all')\n\t\t# check if we're in a sgug environment\n\t\tif 'sgug' in os.environ['LD_LIBRARYN32_PATH']:\n\t\t\tlinkflags.append('-lc')\n\telif conf.env.SAILFISH in ['aurora', 'sailfish']:\n\t\t# TODO: enable XASH_MOBILE_PLATFORM\n\t\tconf.define('XASH_SAILFISH', 1)\n\t\tif conf.env.SAILFISH == 'aurora':\n\t\t\tconf.define('XASH_AURORAOS', 1)\n\n\t\t# Do not warn us about bug in SDL_Audio headers\n\t\tconf.env.append_unique('CFLAGS', ['-Wno-attributes'])\n\t\tconf.env.append_unique('CXXFLAGS', ['-Wno-attributes'])\n\n\tconf.check_cc(cflags=cflags, linkflags=linkflags, msg='Checking for required C flags')\n\tconf.check_cxx(cxxflags=cxxflags, linkflags=linkflags, msg='Checking for required C++ flags')\n\n\tconf.env.append_unique('CFLAGS', cflags)\n\tconf.env.append_unique('CXXFLAGS', cxxflags)\n\tconf.env.append_unique('LINKFLAGS', linkflags)\n\n\tif conf.env.COMPILER_CC != 'msvc':\n\t\topt_flags = [\n\t\t\t# '-Wall', '-Wextra', '-Wpedantic',\n\t\t\t'-fdiagnostics-color=always',\n\n\t\t\t# stable diagnostics, forced to error, sorted\n\t\t\t'-Werror=alloc-size',\n\t\t\t'-Werror=bool-compare',\n\t\t\t'-Werror=bool-operation',\n\t\t\t# '-Werror=cast-align=strict',\n\t\t\t'-Werror=duplicated-cond',\n\t\t\t'-Werror=format=2',\n\t\t\t'-Werror=free-nonheap-object',\n\t\t\t'-Werror=implicit-fallthrough=2',\n\t\t\t'-Werror=logical-op',\n\t\t\t'-Werror=nonnull',\n\t\t\t'-Werror=packed',\n\t\t\t'-Werror=packed-not-aligned',\n\t\t\t'-Werror=parentheses',\n\t\t\t'-Werror=return-type',\n\t\t\t'-Werror=sequence-point',\n\t\t\t'-Werror=sizeof-pointer-memaccess',\n\t\t\t'-Werror=sizeof-array-div',\n\t\t\t'-Werror=sizeof-pointer-div',\n\t\t\t'-Werror=strict-aliasing',\n\t\t\t'-Werror=string-compare',\n\t\t\t'-Werror=tautological-compare',\n\t\t\t'-Werror=use-after-free=3',\n\t\t\t'-Werror=vla',\n\t\t\t'-Werror=write-strings',\n\n\t\t\t# unstable diagnostics, may cause false positives\n\t\t\t'-Walloc-zero',\n\t\t\t'-Winit-self',\n\t\t\t'-Wmisleading-indentation',\n\t\t\t'-Wmismatched-dealloc',\n\t\t\t'-Wstringop-overflow',\n\t\t\t'-Wunintialized',\n\t\t\t'-Wno-error=format-nonliteral',\n\n\t\t\t# disabled, flood\n\t\t\t# '-Wdouble-promotion',\n\n\t\t\t'-Wunused-function',\n\t\t\t'-Wunused-variable',\n\t\t\t'-Wunused-but-set-variable',\n\t\t]\n\n\t\tif conf.env.COMPILER_CC == 'clang':\n\t\t\topt_flags += [\n\t\t\t\t'-Werror=unsequenced', # clang's version of -Werror=sequence-point\n\t\t\t]\n\n\t\topt_cflags = [\n\t\t\t'-Werror=enum-conversion',\n\t\t\t'-Wno-error=enum-float-conversion', # need this for cvars\n\t\t\t'-Werror=implicit-int',\n\t\t\t'-Werror=implicit-function-declaration',\n\t\t\t'-Werror=incompatible-pointer-types',\n\t\t\t'-Werror=int-conversion',\n\t\t\t'-Werror=jump-misses-init',\n\t\t\t'-Werror=old-style-declaration',\n\t\t\t'-Werror=old-style-definition',\n\t\t\t'-Werror=strict-prototypes',\n\t\t\t'-fnonconst-initializers', # owcc\n\t\t\t'-Wmissing-prototypes', # not an error yet\n\t\t]\n\n\t\topt_cxxflags = [] # TODO:\n\n\t\tif conf.options.DISABLE_WERROR:\n\t\t\topt_flags = []\n\t\t\topt_cflags = ['-Werror=implicit-function-declaration']\n\t\t\topt_cxxflags = []\n\n\t\tconf.env.CFLAGS_werror = conf.filter_cflags(opt_flags + opt_cflags, cflags)\n\t\tconf.env.CXXFLAGS_werror = conf.filter_cxxflags(opt_flags + opt_cxxflags, cxxflags)\n\n\tconf.env.TESTS         = conf.options.TESTS\n\tconf.env.ENABLE_UTILS  = conf.options.ENABLE_UTILS\n\tconf.env.ENABLE_XAR    = conf.options.ENABLE_XAR\n\tconf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER\n\n\tif not conf.options.DEDICATED:\n\t\tconf.env.SERVER = conf.options.ENABLE_DEDICATED\n\t\tconf.env.CLIENT = True\n\t\tconf.env.LAUNCHER = conf.env.DEST_OS not in ['android', 'nswitch', 'psvita', 'dos', 'emscripten'] and not conf.env.MAGX and not conf.env.STATIC_LINKING\n\telse:\n\t\tconf.env.SERVER = True\n\t\tconf.env.CLIENT = False\n\t\tconf.env.LAUNCHER = False\n\n\tconf.env.TUI = conf.options.ENABLE_TUI\n\n\tconf.define_cond('SUPPORT_HL25_EXTENDED_STRUCTS', conf.options.SUPPORT_HL25_EXTENDED_STRUCTS)\n\n\tif conf.env.SAILFISH == 'aurora':\n\t\tconf.env.DEFAULT_RPATH = '/usr/share/su.xash.Engine/lib'\n\telif conf.env.DEST_OS == 'darwin':\n\t\tconf.env.DEFAULT_RPATH = '@loader_path'\n\telif conf.env.DEST_OS == 'openbsd':\n\t\t# OpenBSD requires -z origin to enable $ORIGIN expansion in RPATH\n\t\tconf.env.RPATH_ST = '-Wl,-z,origin,-rpath,%s'\n\t\tconf.env.DEFAULT_RPATH = '$ORIGIN'\n\telif conf.env.DEST_OS in ['nswitch', 'psvita']:\n\t\tconf.env.DEFAULT_RPATH = None\n\telse:\n\t\tconf.env.DEFAULT_RPATH = '$ORIGIN'\n\n\tsetattr(conf, 'refdlls', REFDLLS)\n\n\tfor refdll in REFDLLS:\n\t\trefdll.register_env(conf.env, conf.options, conf.options.ALL_RENDERERS)\n\n\tconf.env.GAMEDIR = conf.options.GAMEDIR\n\tconf.define('XASH_GAMEDIR', conf.options.GAMEDIR)\n\tconf.define_cond('XASH_ALL_SERVERS', conf.options.ALL_SERVERS)\n\n\tif conf.env.DEST_OS == 'nswitch':\n\t\tconf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER')\n\t\tif conf.env.HAVE_SOLDER and conf.env.LIB_SOLDER and conf.options.BUILD_TYPE == 'debug':\n\t\t\tconf.env.LIB_SOLDER[0] += 'd' # load libsolderd in debug mode\n\t\tconf.check_cc(lib='m')\n\telif conf.env.DEST_OS == 'psvita':\n\t\tconf.check_cc(lib='vrtld')\n\t\tconf.check_cc(lib='m')\n\telif conf.env.DEST_OS == 'android':\n\t\t# maybe there is some better check?\n\t\tif conf.find_program('termux-info', mandatory=False):\n\t\t\tconf.env.TERMUX = True\n\t\t\tconf.define('__TERMUX__', 1)\n\n\t\tconf.check_cc(lib='dl')\n\t\tconf.check_cc(lib='log')\n\t\tif not conf.options.ANDROID_OPTS:\n\t\t\t# if we're compiling on device itself\n\t\t\tconf.check_cc(lib='m')\n\t\t# otherwise LIB_M is defined by xcompile (as it might be libm_hard, depending on NDK configuration)\n\telif conf.env.DEST_OS == 'win32':\n\t\t# Common Win32 libraries\n\t\t# Don't check them more than once, to save time\n\t\t# Usually, they are always available\n\t\t# but we need them in uselib\n\t\ta = [ 'user32', 'shell32', 'gdi32', 'advapi32', 'dbghelp', 'psapi', 'ws2_32' ]\n\t\tif conf.env.COMPILER_CC == 'msvc':\n\t\t\tfor i in a:\n\t\t\t\tconf.start_msg('Checking for MSVC library')\n\t\t\t\tconf.check_lib_msvc(i)\n\t\t\t\tconf.end_msg(i)\n\t\telse:\n\t\t\tfor i in a:\n\t\t\t\tconf.check_cc(lib = i)\n\telse:\n\t\tconf.check_cc(lib='dl', mandatory = False)\n\t\tconf.check_cc(lib='m')\n\n\n\t# set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes\n\t# must be set globally as it changes ABI\n\tif conf.env.DEST_OS == 'android' and conf.env.DEST_SIZEOF_VOID_P == 4:\n\t\t# Android in 32-bit mode don't have good enough large file support\n\t\t# with our native API level\n\t\t# https://android.googlesource.com/platform/bionic/+/HEAD/docs/32-bit-abi.md\n\t\tpass\n\telif conf.env.DEST_OS == 'psvita':\n\t\t# PSVita don't have large file support at all\n\t\tpass\n\telse:\n\t\t# try to guess how to support large files\n\t\tconf.check_large_file(compiler = 'c', execute = False)\n\n\t# indicate if we are packaging for Linux/BSD\n\tif conf.options.PACKAGING:\n\t\tconf.env.PREFIX = conf.options.prefix\n\t\tif conf.env.SAILFISH == \"aurora\":\n\t\t\tconf.env.SHAREDIR = '${PREFIX}/share/su.xash.Engine/rodir'\n\t\telif conf.env.SAILFISH == \"sailfish\":\n\t\t\tconf.env.SHAREDIR = '${PREFIX}/share/harbour-xash3d-fwgs/rodir'\n\t\telse:\n\t\t\tconf.env.SHAREDIR = '${PREFIX}/share/xash3d'\n\t\t\tconf.env.LIBDIR += '/xash3d'\n\telse:\n\t\tconf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX\n\n\tif not conf.options.BUILD_BUNDLED_DEPS:\n\t\t# there was a check for system libbacktrace but we can't be sure if it supports fileline or not\n\t\t# therefore, always build libbacktrace ourselves\n\n\t\tif conf.env.CLIENT:\n\t\t\tfor i in ('ogg','opusfile','vorbis','vorbisfile'):\n\t\t\t\tif conf.check_cfg(package=i, uselib_store=i, args='--cflags --libs', mandatory=False):\n\t\t\t\t\tconf.env['HAVE_SYSTEM_%s' % i.upper()] = True\n\n\t\t\t\tif conf.env.HAVE_SYSTEM_OPUSFILE:\n\t\t\t\t\tfrag='''#include <opusfile.h>\nint main(int argc, char **argv) { return opus_tagcompare(argv[0], argv[1]); }'''\n\n\t\t\t\t\tconf.env.HAVE_SYSTEM_OPUSFILE = conf.check_cc(msg='Checking for libopusfile sanity', use='opusfile werror', fragment=frag, mandatory=False)\n\n\t\t\t# search for opus 1.4 only, it has fixes for custom modes\n\t\t\t# 1.5 breaks custom modes: https://github.com/xiph/opus/issues/374\n\t\t\tif conf.check_cfg(package='opus', uselib_store='opus', args='opus = 1.4 --cflags --libs', mandatory=False):\n\t\t\t\t# now try to link with export that only exists with CUSTOM_MODES defined\n\t\t\t\tfrag='''#include <opus_custom.h>\nint main(void) { return !opus_custom_encoder_init((OpusCustomEncoder *)1, (const OpusCustomMode *)1, 1); }'''\n\n\t\t\t\tconf.env.HAVE_SYSTEM_OPUS = conf.check_cc(msg='Checking if opus supports custom modes', defines='CUSTOM_MODES=1', use='opus werror', fragment=frag, mandatory=False)\n\n\t\t\t# search for bzip2\n\t\t\tBZIP2_CHECK='''#include <bzlib.h>\nint main(void) { return (int)BZ2_bzlibVersion(); }'''\n\n\t\t\tconf.env.HAVE_SYSTEM_BZ2 = conf.check_cc(lib='bz2', fragment=BZIP2_CHECK, uselib_store='bzip2', mandatory=False)\n\n\tconf.define('XASH_LOW_MEMORY', conf.options.LOW_MEMORY)\n\n\tfor i in SUBDIRS:\n\t\tif not i.is_enabled(conf):\n\t\t\tcontinue\n\n\t\tconf.add_subproject(i.name)\n\ndef build(bld):\n\tif bld.env.WAFCACHE:\n\t\tbld.load('wafcache')\n\n\t# guard rails to not let install to root\n\tif bld.is_install and not bld.options.PACKAGING and not bld.options.destdir:\n\t\tbld.fatal('Set the install destination directory using --destdir option')\n\n\t# don't clean QtCreator files and reconfigure saved options\n\tbld.clean_files = bld.bldnode.ant_glob('**',\n\t\texcl='*.user configuration.py .lock* *conf_check_*/** config.log 3rdparty/libbacktrace/*.h %s/*' % Build.CACHE_DIR,\n\t\tquiet=True, generator=True)\n\n\tbld.load('xshlib')\n\n\tfor i in SUBDIRS:\n\t\tif not i.is_enabled(bld):\n\t\t\tcontinue\n\n\t\tbld.add_subproject(i.name)\n\n\tif bld.env.TESTS:\n\t\tbld.add_post_fun(waf_unit_test.summary)\n\t\tbld.add_post_fun(waf_unit_test.set_exit_code)\n"
  }
]